diff --git a/.claude/rules/type-safety.md b/.claude/rules/type-safety.md index bea737f0..75ad7ee5 100644 --- a/.claude/rules/type-safety.md +++ b/.claude/rules/type-safety.md @@ -64,7 +64,7 @@ If multiple modules validate the same shape, extract the schema to a shared file Shared schema locations: - `.claude/scripts/schemas.ts` — hook stdin payload schemas -- `packages/shared/src/parse.ts` — `parseJsonWith(text, schema)` and `parseJsonObj(text)` +- `packages/cli/src/shared/parse.ts` — `parseJsonWith(text, schema)` and `parseJsonObj(text)` ### For test mocks — use proper Response objects instead of `as any`: ```typescript @@ -83,5 +83,5 @@ global.fetch = mock(() => Promise.resolve(new Response("Error", { status: 500 }) ``` ### Shared utilities -- `packages/shared/src/parse.ts` — `parseJsonWith(text, schema)` and `parseJsonObj(text)` -- `packages/shared/src/type-guards.ts` — `isString`, `isNumber`, `hasStatus`, `hasMessage` +- `packages/cli/src/shared/parse.ts` — `parseJsonWith(text, schema)` and `parseJsonObj(text)` +- `packages/cli/src/shared/type-guards.ts` — `isString`, `isNumber`, `hasStatus`, `hasMessage` diff --git a/.claude/skills/setup-spa/helpers.ts b/.claude/skills/setup-spa/helpers.ts index 1c60b03c..5cf63570 100644 --- a/.claude/skills/setup-spa/helpers.ts +++ b/.claude/skills/setup-spa/helpers.ts @@ -1,13 +1,14 @@ // SPA helpers — pure functions for parsing Claude Code stream events, // Slack formatting, state management, and file download/cleanup. -import type { Result } from "@openrouter/spawn-shared"; +import type { Result } from "../../../packages/cli/src/shared/result"; import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs"; import { dirname } from "node:path"; -import { Err, isString, Ok, toRecord } from "@openrouter/spawn-shared"; import { slackifyMarkdown } from "slackify-markdown"; import * as v from "valibot"; +import { Err, Ok } from "../../../packages/cli/src/shared/result"; +import { isString, toRecord } from "../../../packages/cli/src/shared/type-guards"; // #region State diff --git a/.claude/skills/setup-spa/main.ts b/.claude/skills/setup-spa/main.ts index ac722c96..b79c54ed 100644 --- a/.claude/skills/setup-spa/main.ts +++ b/.claude/skills/setup-spa/main.ts @@ -4,9 +4,9 @@ import type { ContextBlock, KnownBlock, SectionBlock } from "@slack/bolt"; import type { State, ToolCall } from "./helpers"; -import { isString, toRecord } from "@openrouter/spawn-shared"; import { App } from "@slack/bolt"; import * as v from "valibot"; +import { isString, toRecord } from "../../../packages/cli/src/shared/type-guards"; import { addMapping, downloadSlackFile, diff --git a/.claude/skills/setup-spa/package.json b/.claude/skills/setup-spa/package.json index 7fbe1db8..868d6d5a 100644 --- a/.claude/skills/setup-spa/package.json +++ b/.claude/skills/setup-spa/package.json @@ -6,7 +6,6 @@ "start": "bun run main.ts" }, "dependencies": { - "@openrouter/spawn-shared": "workspace:*", "@slack/bolt": "4.6.0", "slackify-markdown": "^5.0.0", "valibot": "1.2.0" diff --git a/.claude/skills/setup-spa/spa.test.ts b/.claude/skills/setup-spa/spa.test.ts index 51b48a9e..fb75ff53 100644 --- a/.claude/skills/setup-spa/spa.test.ts +++ b/.claude/skills/setup-spa/spa.test.ts @@ -1,8 +1,8 @@ import type { ToolCall } from "./helpers"; import { afterEach, describe, expect, it, mock } from "bun:test"; -import { toRecord } from "@openrouter/spawn-shared"; import streamEvents from "../../../fixtures/claude-code/stream-events.json"; +import { toRecord } from "../../../packages/cli/src/shared/type-guards"; import { downloadSlackFile, extractToolHint, diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 1ef70ad2..c2edb6aa 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -50,7 +50,7 @@ jobs: run: bun install - name: Run Biome check (all packages) - run: bunx @biomejs/biome check packages/cli/src/ packages/shared/src/ .claude/scripts/ .claude/skills/setup-spa/ + run: bunx @biomejs/biome check packages/cli/src/ .claude/scripts/ .claude/skills/setup-spa/ macos-compat: name: macOS Compatibility diff --git a/CLAUDE.md b/CLAUDE.md index 8408c342..205710a2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -20,10 +20,6 @@ spawn/ src/commands/ # Per-command modules (interactive, list, run, etc.) src/commands.ts # Compatibility shim → re-exports from commands/ package.json # npm package (@openrouter/spawn) - shared/ - src/parse.ts # parseJsonWith(text, schema) and parseJsonObj(text) - src/type-guards.ts # isString, isNumber, hasStatus, hasMessage - package.json # npm package (@openrouter/spawn-shared) sh/ cli/ install.sh # One-liner installer (bun → npm → auto-install bun) diff --git a/bun.lock b/bun.lock index 278d0ed0..e72b5bd3 100644 --- a/bun.lock +++ b/bun.lock @@ -13,7 +13,6 @@ ".claude/skills/setup-spa": { "name": "spawn-slack-bot", "dependencies": { - "@openrouter/spawn-shared": "workspace:*", "@slack/bolt": "4.6.0", "slackify-markdown": "^5.0.0", "valibot": "1.2.0", @@ -21,7 +20,7 @@ }, "packages/cli": { "name": "@openrouter/spawn", - "version": "0.12.14", + "version": "0.15.3", "bin": { "spawn": "cli.js", }, @@ -35,13 +34,6 @@ "@types/bun": "1.3.8", }, }, - "packages/shared": { - "name": "@openrouter/spawn-shared", - "version": "0.1.1", - "dependencies": { - "valibot": "1.2.0", - }, - }, }, "packages": { "@biomejs/biome": ["@biomejs/biome@2.4.3", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.3", "@biomejs/cli-darwin-x64": "2.4.3", "@biomejs/cli-linux-arm64": "2.4.3", "@biomejs/cli-linux-arm64-musl": "2.4.3", "@biomejs/cli-linux-x64": "2.4.3", "@biomejs/cli-linux-x64-musl": "2.4.3", "@biomejs/cli-win32-arm64": "2.4.3", "@biomejs/cli-win32-x64": "2.4.3" }, "bin": { "biome": "bin/biome" } }, "sha512-cBrjf6PNF6yfL8+kcNl85AjiK2YHNsbU0EvDOwiZjBPbMbQ5QcgVGFpjD0O52p8nec5O8NYw7PKw3xUR7fPAkQ=="], @@ -68,8 +60,6 @@ "@openrouter/spawn": ["@openrouter/spawn@workspace:packages/cli"], - "@openrouter/spawn-shared": ["@openrouter/spawn-shared@workspace:packages/shared"], - "@slack/bolt": ["@slack/bolt@4.6.0", "", { "dependencies": { "@slack/logger": "^4.0.0", "@slack/oauth": "^3.0.4", "@slack/socket-mode": "^2.0.5", "@slack/types": "^2.18.0", "@slack/web-api": "^7.12.0", "axios": "^1.12.0", "express": "^5.0.0", "path-to-regexp": "^8.1.0", "raw-body": "^3", "tsscmp": "^1.0.6" }, "peerDependencies": { "@types/express": "^5.0.0" } }, "sha512-xPgfUs2+OXSugz54Ky07pA890+Qydk22SYToi8uGpXeHSt1JWwFJkRyd/9Vlg5I1AdfdpGXExDpwnbuN9Q/2dQ=="], "@slack/logger": ["@slack/logger@4.0.0", "", { "dependencies": { "@types/node": ">=18.0.0" } }, "sha512-Wz7QYfPAlG/DR+DfABddUZeNgoeY7d1J39OCR2jR+v7VBsB8ezulDK5szTnDDPDwLH5IWhLvXIHlCFZV7MSKgA=="], diff --git a/packages/cli/package.json b/packages/cli/package.json index a84ece0c..92899344 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@openrouter/spawn", - "version": "0.15.5", + "version": "0.15.6", "type": "module", "bin": { "spawn": "cli.js" diff --git a/packages/shared/biome.json b/packages/shared/biome.json deleted file mode 100644 index f42593d9..00000000 --- a/packages/shared/biome.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "root": false, - "$schema": "https://biomejs.dev/schemas/2.4.4/schema.json", - "extends": ["../../biome.json"], - "vcs": { - "enabled": true, - "clientKind": "git", - "useIgnoreFile": true, - "defaultBranch": "main" - }, - "files": { - "ignoreUnknown": false, - "includes": ["src/**/*.ts"] - } -} diff --git a/packages/shared/package.json b/packages/shared/package.json deleted file mode 100644 index 64b226ec..00000000 --- a/packages/shared/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "@openrouter/spawn-shared", - "version": "0.1.1", - "type": "module", - "main": "src/index.ts", - "dependencies": { - "valibot": "1.2.0" - } -} diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts deleted file mode 100644 index 0ecafa8b..00000000 --- a/packages/shared/src/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { parseJsonObj, parseJsonWith } from "./parse"; -export { Err, Ok, type Result } from "./result"; -export { hasMessage, hasStatus, isNumber, isString, toObjectArray, toRecord } from "./type-guards"; diff --git a/packages/shared/src/parse.ts b/packages/shared/src/parse.ts deleted file mode 100644 index bf900eb2..00000000 --- a/packages/shared/src/parse.ts +++ /dev/null @@ -1,35 +0,0 @@ -// shared/parse.ts — Schema-validated JSON parsing (replaces unsafe `as` casts) - -import * as v from "valibot"; - -/** - * Parse a JSON string and validate it against a valibot schema. - * Returns the validated value, or null if parsing/validation fails. - */ -export function parseJsonWith>>( - text: string, - schema: T, -): v.InferOutput | null { - try { - return v.parse(schema, JSON.parse(text)); - } catch { - return null; - } -} - -/** - * Parse a JSON string and return it as a Record or null. - * Rejects non-object results (arrays, primitives). - * Use for API responses that are always a JSON object. - */ -export function parseJsonObj(text: string): Record | null { - try { - const val = JSON.parse(text); - if (val !== null && typeof val === "object" && !Array.isArray(val)) { - return val; - } - return null; - } catch { - return null; - } -} diff --git a/packages/shared/src/result.ts b/packages/shared/src/result.ts deleted file mode 100644 index 0d954c08..00000000 --- a/packages/shared/src/result.ts +++ /dev/null @@ -1,24 +0,0 @@ -// shared/result.ts — Lightweight Result monad for retry-aware error handling. -// -// Returning Err() signals a retryable failure; throwing signals a non-retryable one. -// Used with withRetry() so callers decide at the point of failure whether an error -// is retryable (return Err) or fatal (throw), instead of relying on brittle -// error-message pattern matching after the fact. - -export type Result = - | { - ok: true; - data: T; - } - | { - ok: false; - error: Error; - }; -export const Ok = (data: T): Result => ({ - ok: true, - data, -}); -export const Err = (error: Error): Result => ({ - ok: false, - error, -}); diff --git a/packages/shared/src/type-guards.ts b/packages/shared/src/type-guards.ts deleted file mode 100644 index 9e544516..00000000 --- a/packages/shared/src/type-guards.ts +++ /dev/null @@ -1,44 +0,0 @@ -// shared/type-guards.ts — Runtime type guards (replaces unsafe `as` casts on non-API values) - -export function isString(val: unknown): val is string { - return typeof val === "string"; -} - -export function isNumber(val: unknown): val is number { - return typeof val === "number"; -} - -export function hasStatus(err: unknown): err is { - status: number; -} { - return err !== null && typeof err === "object" && "status" in err && typeof err.status === "number"; -} - -export function hasMessage(err: unknown): err is { - message: string; -} { - return err !== null && typeof err === "object" && "message" in err && typeof err.message === "string"; -} - -/** - * Safely narrow an unknown value to a Record or return null. - */ -export function toRecord(val: unknown): Record | null { - if (val !== null && typeof val === "object" && !Array.isArray(val)) { - return val satisfies Record; - } - return null; -} - -/** - * Safely narrow an unknown value to an array of Record. - * Filters out non-object items. - */ -export function toObjectArray(val: unknown): Record[] { - if (!Array.isArray(val)) { - return []; - } - return val.filter( - (item): item is Record => item !== null && typeof item === "object" && !Array.isArray(item), - ); -} diff --git a/packages/shared/tsconfig.json b/packages/shared/tsconfig.json deleted file mode 100644 index c9ac2d7e..00000000 --- a/packages/shared/tsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "compilerOptions": { - "target": "ESNext", - "module": "ESNext", - "moduleResolution": "bundler", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "outDir": "dist", - "rootDir": "src", - "declaration": true - }, - "include": ["src"] -} diff --git a/sh/cli/install.ps1 b/sh/cli/install.ps1 index b7c71291..be71f4f6 100644 --- a/sh/cli/install.ps1 +++ b/sh/cli/install.ps1 @@ -93,7 +93,7 @@ function Install-SpawnCli { git clone --depth 1 --filter=blob:none --sparse ` "https://github.com/$SPAWN_REPO.git" $repoDir 2>$null Push-Location $repoDir - git sparse-checkout set packages/cli packages/shared 2>$null + git sparse-checkout set packages/cli 2>$null Pop-Location Move-Item (Join-Path $repoDir "packages" "cli") $cliDir Remove-Item $repoDir -Recurse -Force -ErrorAction SilentlyContinue