diff --git a/bin/install.js b/bin/install.js index bd901ccc..6c2409f1 100755 --- a/bin/install.js +++ b/bin/install.js @@ -6634,8 +6634,13 @@ function promptLocation(runtimes) { } /** - * Ensure `@gsd-build/sdk` (the `gsd-sdk` binary) is installed globally so - * workflow commands that shell out to `gsd-sdk query …` succeed. + * Build `@gsd-build/sdk` from the in-repo `sdk/` source tree and install the + * resulting `gsd-sdk` binary globally so workflow commands that shell out to + * `gsd-sdk query …` succeed. + * + * We build from source rather than `npm install -g @gsd-build/sdk` because the + * npm-published package lags the source tree and shipping a stale SDK breaks + * every /gsd-* command that depends on newer query handlers. * * Skip if --no-sdk. Skip if already on PATH (unless --sdk was explicit). * Failures are warnings, not fatal. @@ -6647,6 +6652,8 @@ function installSdkIfNeeded() { } const { spawnSync } = require('child_process'); + const path = require('path'); + const fs = require('fs'); if (!hasSdk) { const probe = spawnSync(process.platform === 'win32' ? 'where' : 'which', ['gsd-sdk'], { stdio: 'ignore' }); @@ -6656,19 +6663,48 @@ function installSdkIfNeeded() { } } - console.log(`\n ${cyan}Installing GSD SDK (@gsd-build/sdk)…${reset}`); - const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm'; - const result = spawnSync(npmCmd, ['install', '-g', '@gsd-build/sdk'], { stdio: 'inherit' }); + // Locate the in-repo sdk/ directory relative to this installer file. + // For global npm installs this resolves inside the published package dir; + // for git-based installs (npx github:..., local clone) it resolves to the + // repo's sdk/ tree. Both contain the source tree because root package.json + // includes "sdk" in its `files` array. + const sdkDir = path.resolve(__dirname, '..', 'sdk'); + const sdkPackageJson = path.join(sdkDir, 'package.json'); const warnManual = (reason) => { console.warn(` ${yellow}⚠${reset} ${reason}`); - console.warn(` Run manually: ${cyan}npm install -g @gsd-build/sdk${reset}`); + console.warn(` Build manually from the repo sdk/ directory:`); + console.warn(` ${cyan}cd ${sdkDir} && npm install && npm run build && npm install -g .${reset}`); console.warn(` Then restart your shell so the updated PATH is picked up.`); console.warn(` Without it, /gsd-* commands will fail with "command not found: gsd-sdk".`); }; - if (result.status !== 0) { - warnManual('Failed to install @gsd-build/sdk automatically.'); + if (!fs.existsSync(sdkPackageJson)) { + warnManual(`SDK source tree not found at ${sdkDir}.`); + return; + } + + console.log(`\n ${cyan}Building GSD SDK from source (${sdkDir})…${reset}`); + const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm'; + + // 1. Install sdk build-time dependencies (tsc, etc.) + const installResult = spawnSync(npmCmd, ['install'], { cwd: sdkDir, stdio: 'inherit' }); + if (installResult.status !== 0) { + warnManual('Failed to `npm install` in sdk/.'); + return; + } + + // 2. Compile TypeScript → sdk/dist/ + const buildResult = spawnSync(npmCmd, ['run', 'build'], { cwd: sdkDir, stdio: 'inherit' }); + if (buildResult.status !== 0) { + warnManual('Failed to `npm run build` in sdk/.'); + return; + } + + // 3. Install the built package globally so `gsd-sdk` lands on PATH. + const globalResult = spawnSync(npmCmd, ['install', '-g', '.'], { cwd: sdkDir, stdio: 'inherit' }); + if (globalResult.status !== 0) { + warnManual('Failed to `npm install -g .` from sdk/.'); return; } @@ -6679,9 +6715,9 @@ function installSdkIfNeeded() { const resolverCmd = process.platform === 'win32' ? 'where' : 'which'; const verify = spawnSync(resolverCmd, ['gsd-sdk'], { encoding: 'utf-8' }); if (verify.status === 0 && verify.stdout && verify.stdout.trim()) { - console.log(` ${green}✓${reset} Installed @gsd-build/sdk (gsd-sdk resolved at ${verify.stdout.trim().split('\n')[0]})`); + console.log(` ${green}✓${reset} Built and installed GSD SDK from source (gsd-sdk resolved at ${verify.stdout.trim().split('\n')[0]})`); } else { - warnManual('Installed @gsd-build/sdk but gsd-sdk is not on PATH — npm global bin may not be in your PATH.'); + warnManual('Built and installed GSD SDK from source but gsd-sdk is not on PATH — npm global bin may not be in your PATH.'); if (verify.stderr) console.warn(` resolver stderr: ${verify.stderr.trim()}`); } } @@ -6701,9 +6737,12 @@ function installAllRuntimes(runtimes, isGlobal, isInteractive) { const primaryStatuslineResult = results.find(r => statuslineRuntimes.includes(r.runtime)); const finalize = (shouldInstallStatusline) => { - // Install @gsd-build/sdk so `gsd-sdk` lands on PATH. - // Every /gsd-* command shells out to `gsd-sdk query …`; without this, - // commands fail with "command not found: gsd-sdk". + // Build @gsd-build/sdk from the in-repo sdk/ source and install it globally + // so `gsd-sdk` lands on PATH. Every /gsd-* command shells out to + // `gsd-sdk query …`; without this, commands fail with "command not found: + // gsd-sdk". The npm-published @gsd-build/sdk is kept intentionally frozen + // at an older version; we always build from source so users get the SDK + // that matches the installed GSD version. // Runs by default; skip with --no-sdk. Idempotent when already present. installSdkIfNeeded(); diff --git a/package.json b/package.json index 8087665b..a106c9fb 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,12 @@ "get-shit-done", "agents", "hooks", - "scripts" + "scripts", + "sdk/src", + "sdk/prompts", + "sdk/package.json", + "sdk/package-lock.json", + "sdk/tsconfig.json" ], "keywords": [ "claude", diff --git a/sdk/package-lock.json b/sdk/package-lock.json index 23acdadf..81d09b38 100644 --- a/sdk/package-lock.json +++ b/sdk/package-lock.json @@ -7,6 +7,7 @@ "": { "name": "@gsd-build/sdk", "version": "0.1.0", + "license": "MIT", "dependencies": { "@anthropic-ai/claude-agent-sdk": "^0.2.84", "ws": "^8.20.0" diff --git a/sdk/src/event-stream.ts b/sdk/src/event-stream.ts index d46fd668..426701a1 100644 --- a/sdk/src/event-stream.ts +++ b/sdk/src/event-stream.ts @@ -309,8 +309,10 @@ export class GSDEventStream extends EventEmitter { ): GSDEvent | null { const events: GSDEvent[] = []; - // Extract text blocks — content blocks are a discriminated union with a 'type' field - const content = msg.message.content as Array<{ type: string; [key: string]: unknown }>; + // Extract text blocks — content blocks are a discriminated union with a 'type' field. + // Double-cast via unknown because BetaContentBlock's internal variants don't + // carry an index signature, so TS rejects the direct cast without a widening step. + const content = msg.message.content as unknown as Array<{ type: string; [key: string]: unknown }>; const textBlocks = content.filter( (b): b is { type: 'text'; text: string } => b.type === 'text', diff --git a/tests/bugs-1656-1657.test.cjs b/tests/bugs-1656-1657.test.cjs index c940f8ac..f51527ad 100644 --- a/tests/bugs-1656-1657.test.cjs +++ b/tests/bugs-1656-1657.test.cjs @@ -80,11 +80,35 @@ describe('#1657 / #2385: SDK install must be wired into installer source', () => ); }); - test('install.js installs @gsd-build/sdk by default (#2385)', () => { + test('install.js builds gsd-sdk from in-repo sdk/ source (#2385)', () => { src = src || fs.readFileSync(INSTALL_SRC, 'utf-8'); + // The installer must locate the in-repo sdk/ directory, run the build, + // and install it globally. We intentionally do NOT install + // @gsd-build/sdk from npm because that published version lags the source + // tree and shipping it breaks query handlers added since the last + // publish. assert.ok( - src.includes('@gsd-build/sdk'), - 'installer must reference @gsd-build/sdk so gsd-sdk lands on PATH' + src.includes("path.resolve(__dirname, '..', 'sdk')") || + src.includes('path.resolve(__dirname, "..", "sdk")'), + 'installer must locate the in-repo sdk/ directory' + ); + assert.ok( + src.includes("'npm install -g .'") || + src.includes("['install', '-g', '.']"), + 'installer must run `npm install -g .` from sdk/ to install the built package globally' + ); + assert.ok( + src.includes("['run', 'build']"), + 'installer must compile TypeScript via `npm run build` before installing globally' + ); + }); + + test('package.json ships sdk source in published tarball (#2385)', () => { + const rootPkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf-8')); + const files = rootPkg.files || []; + assert.ok( + files.some((f) => f === 'sdk' || f.startsWith('sdk/')), + 'root package.json `files` must include sdk source so npm-registry installs can build gsd-sdk from source' ); }); });