diff --git a/bin/gstack-open-url b/bin/gstack-open-url new file mode 100644 index 000000000..68508f861 --- /dev/null +++ b/bin/gstack-open-url @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# gstack-open-url — cross-platform URL opener +# +# Usage: +# gstack-open-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 " >&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 diff --git a/scripts/resolvers/preamble.ts b/scripts/resolvers/preamble.ts index 4e5092f8a..f4d6a9969 100644 --- a/scripts/resolvers/preamble.ts +++ b/scripts/resolvers/preamble.ts @@ -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) @@ -61,18 +63,33 @@ of \`/qa\`, \`/gstack-ship\` instead of \`/ship\`). Disk paths are unaffected If output shows \`UPGRADE_AVAILABLE \`: 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 \`: 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 { @@ -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(), ];