Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions bin/gstack-open-url
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env bash
# gstack-open-url — cross-platform URL opener
#
# Usage:
# gstack-open-url <url> — open URL in default browser
#
# Supports macOS (open), Linux (xdg-open), and Windows (start)
#
# Env overrides (for testing):
# GSTACK_OPEN_CMD — override the open command
set -euo pipefail

URL="${1:-}"

if [ -z "$URL" ]; then
echo "Usage: gstack-open-url <url>" >&2
exit 1
fi

# Allow explicit override via environment
if [ -n "${GSTACK_OPEN_CMD:-}" ]; then
$GSTACK_OPEN_CMD "$URL"
exit 0
fi

# Detect platform and use appropriate command
case "$(uname -s)" in
Darwin)
# macOS
open "$URL"
;;
Linux)
# Linux - use xdg-open
if command -v xdg-open >/dev/null 2>&1; then
xdg-open "$URL"
else
echo "Error: xdg-open not found. Install xdg-utils or set GSTACK_OPEN_CMD." >&2
exit 1
fi
;;
CYGWIN*|MINGW*|MSYS*)
# Windows
start "" "$URL"
;;
*)
echo "Error: Unknown platform $(uname -s). Set GSTACK_OPEN_CMD to override." >&2
exit 1
;;
esac
16 changes: 12 additions & 4 deletions browse/src/sidebar-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ async function askClaude(queueEntry: any): Promise<void> {
return new Promise((resolve) => {
// Build args fresh — don't trust --resume from queue (session may be stale)
let claudeArgs = ['-p', prompt, '--output-format', 'stream-json', '--verbose',
'--allowedTools', 'Bash,Read,Glob,Grep'];
'--allowedTools', 'Bash,Read,Glob,Grep,Write'];

// Validate cwd exists — queue may reference a stale worktree
let effectiveCwd = cwd || process.cwd();
Expand All @@ -186,7 +186,11 @@ async function askClaude(queueEntry: any): Promise<void> {
}
});

proc.stderr.on('data', () => {}); // Claude logs to stderr, ignore
// Capture stderr for error reporting
let stderrBuffer = '';
proc.stderr.on('data', (data: Buffer) => {
stderrBuffer += data.toString();
});

proc.on('close', (code) => {
if (buffer.trim()) {
Expand All @@ -199,7 +203,8 @@ async function askClaude(queueEntry: any): Promise<void> {
});

proc.on('error', (err) => {
sendEvent({ type: 'agent_error', error: err.message }).then(() => {
const errorMsg = stderrBuffer ? `${err.message}\nStderr: ${stderrBuffer}` : err.message;
sendEvent({ type: 'agent_error', error: errorMsg }).then(() => {
isProcessing = false;
resolve();
});
Expand All @@ -209,7 +214,10 @@ async function askClaude(queueEntry: any): Promise<void> {
const timeoutMs = parseInt(process.env.SIDEBAR_AGENT_TIMEOUT || '300000', 10);
setTimeout(() => {
try { proc.kill(); } catch {}
sendEvent({ type: 'agent_error', error: `Timed out after ${timeoutMs / 1000}s` }).then(() => {
const errorMsg = stderrBuffer
? `Timed out after ${timeoutMs / 1000}s\nStderr: ${stderrBuffer}`
: `Timed out after ${timeoutMs / 1000}s`;
sendEvent({ type: 'agent_error', error: errorMsg }).then(() => {
isProcessing = false;
resolve();
});
Expand Down
27 changes: 22 additions & 5 deletions scripts/resolvers/preamble.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ REPO_MODE=\${REPO_MODE:-unknown}
echo "REPO_MODE: $REPO_MODE"
_LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no")
echo "LAKE_INTRO: $_LAKE_SEEN"
_SEARCH_SEEN=$([ -f ~/.gstack/.search-intro-seen ] && echo "yes" || echo "no")
echo "SEARCH_INTRO: $_SEARCH_SEEN"
_TEL=$(${ctx.paths.binDir}/gstack-config get telemetry 2>/dev/null || true)
_TEL_PROMPTED=$([ -f ~/.gstack/.telemetry-prompted ] && echo "yes" || echo "no")
_TEL_START=$(date +%s)
Expand Down Expand Up @@ -61,18 +63,33 @@ of \`/qa\`, \`/gstack-ship\` instead of \`/ship\`). Disk paths are unaffected
If output shows \`UPGRADE_AVAILABLE <old> <new>\`: read \`${ctx.paths.skillRoot}/gstack-upgrade/SKILL.md\` and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined). If \`JUST_UPGRADED <from> <to>\`: tell user "Running gstack v{to} (just updated!)" and continue.`;
}

function generateLakeIntro(): string {
function generateLakeIntro(ctx: TemplateContext): string {
return `If \`LAKE_INTRO\` is \`no\`: Before continuing, introduce the Completeness Principle.
Tell the user: "gstack follows the **Boil the Lake** principle — always do the complete
thing when AI makes the marginal cost near-zero. Read more: https://garryslist.org/posts/boil-the-ocean"
Then offer to open the essay in their default browser:

\`\`\`bash
open https://garryslist.org/posts/boil-the-ocean
${ctx.paths.binDir}/gstack-open-url https://garryslist.org/posts/boil-the-ocean
touch ~/.gstack/.completeness-intro-seen
\`\`\`

Only run \`open\` if the user says yes. Always run \`touch\` to mark as seen. This only happens once.`;
Only run \`gstack-open-url\` if the user says yes. Always run \`touch\` to mark as seen. This only happens once.`;
}

function generateSearchIntro(ctx: TemplateContext): string {
return `If \`SEARCH_INTRO\` is \`no\`: Before continuing, introduce the Search Before Building principle.
Tell the user: "gstack follows the **Search Before Building** principle — always search
for existing solutions before building from scratch. When the conventional approach is
wrong for your specific case, that's where brilliance occurs. Read more: https://garryslist.org/posts/search-before-building"
Then offer to open the essay in their default browser:

\`\`\`bash
${ctx.paths.binDir}/gstack-open-url https://garryslist.org/posts/search-before-building
touch ~/.gstack/.search-intro-seen
\`\`\`

Only run \`gstack-open-url\` if the user says yes. Always run \`touch\` to mark as seen. This only happens once.`;
}

function generateTelemetryPrompt(ctx: TemplateContext): string {
Expand Down Expand Up @@ -477,12 +494,12 @@ export function generatePreamble(ctx: TemplateContext): string {
const sections = [
generatePreambleBash(ctx),
generateUpgradeCheck(ctx),
generateLakeIntro(),
generateLakeIntro(ctx),
generateTelemetryPrompt(ctx),
generateProactivePrompt(ctx),
generateVoiceDirective(tier),
...(tier >= 2 ? [generateAskUserFormat(ctx), generateCompletenessSection()] : []),
...(tier >= 3 ? [generateRepoModeSection(), generateSearchBeforeBuildingSection(ctx)] : []),
...(tier >= 3 ? [generateRepoModeSection(), generateSearchBeforeBuildingSection(ctx), generateSearchIntro(ctx)] : []),
generateContributorMode(),
generateCompletionStatus(),
];
Expand Down