feat: Bun workspace monorepo — packages/cli + packages/shared (#1853)

Restructure the repo as a Bun workspace monorepo:

- Move cli/ → packages/cli/
- Create packages/shared/ (@openrouter/spawn-shared) with type-guards and parse utilities
- Add root package.json with workspace configuration
- Update all CLI imports to use @openrouter/spawn-shared
- Deduplicate toRecord/toObjectArray helpers from 4 cloud modules
- Update SPA (slack-bot) to use shared package instead of local toObj()
- Update 48 agent shell scripts for new packages/cli/ path
- Update install.sh, install.ps1, e2e, and test scripts
- Update all GitHub workflows, .gitignore, pre-commit hooks
- Update CLAUDE.md, README.md, and skill prompt references
- Pin all dependency versions (no ^ ranges)
- Bump CLI version 0.9.1 → 0.10.0

All 1908 tests pass. Lint clean. All 8 cloud bundles build.

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
A 2026-02-23 22:07:05 -08:00 committed by GitHub
parent 8049b8b257
commit 65f6f1be32
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
180 changed files with 601 additions and 278 deletions

View file

@ -105,7 +105,7 @@ gh api graphql -f query='
Spawn an **implementer** teammate to:
1. Read the proposal issue for cloud/agent details
2. Implement it following CLAUDE.md Shell Script Rules
3. Add test coverage (`bun test` in `cli/src/__tests__/`)
3. Add test coverage (`bun test` in `packages/cli/src/__tests__/`)
4. Create PR referencing the proposal issue
5. Label the proposal `ready-for-implementation`
6. Comment on the proposal: "Implementation PR: #NUMBER -- discovery/implementer"

View file

@ -42,7 +42,7 @@ For each failed agent, investigate the root cause. The failure categories are:
- Missing env var for headless mode (e.g., `MODEL_ID` for openclaw)
- Fly.io API auth issues
- Agent-specific install script changed upstream
3. Read the agent's provisioning code: `cli/src/fly/agents.ts` and `cli/src/shared/agent-setup.ts`
3. Read the agent's provisioning code: `packages/cli/src/fly/agents.ts` and `packages/cli/src/shared/agent-setup.ts`
4. Read the E2E provision script: `sh/e2e/lib/provision.sh`
### Verification failure (app exists but checks fail)
@ -52,7 +52,7 @@ For each failed agent, investigate the root cause. The failure categories are:
flyctl machines list -a APP_NAME --json | jq -r '.[0].id'
flyctl machine exec MACHINE_ID -a APP_NAME --timeout 30 "bash -c 'ls -la ~; cat ~/.spawnrc; echo ---; env'"
```
2. Check if the binary path changed — read the agent's install script in `cli/src/shared/agent-setup.ts`
2. Check if the binary path changed — read the agent's install script in `packages/cli/src/shared/agent-setup.ts`
3. Check if the env var names changed — read the agent's config in `manifest.json`
4. Update the verification checks in `sh/e2e/lib/verify.sh` if they are stale
@ -116,7 +116,7 @@ After fixing:
- NEVER merge the PR — leave for review
- Run `bash -n` on all modified scripts before committing
- Only fix E2E infrastructure — do NOT modify the agent provisioning scripts in `cli/src/`
- Only fix E2E infrastructure — do NOT modify the agent provisioning scripts in `packages/cli/src/`
- **SIGN-OFF**: `-- qa/e2e-tester`
Begin now. Run the E2E suite.

View file

@ -63,7 +63,7 @@ curl -s -H "Authorization: Bearer ${DO_API_TOKEN}" "https://api.digitalocean.com
curl -s -H "Authorization: Bearer ${FLY_API_TOKEN}" "https://api.machines.dev/v1/apps?org_slug=personal"
```
For any other cloud directories found, read their TypeScript module in `cli/src/{cloud}/` to discover the API base URL and auth pattern, then call equivalent GET-only endpoints.
For any other cloud directories found, read their TypeScript module in `packages/cli/src/{cloud}/` to discover the API base URL and auth pattern, then call equivalent GET-only endpoints.
## Step 4 — Save Fixtures

View file

@ -36,7 +36,7 @@ cd REPO_ROOT_PLACEHOLDER && git worktree remove WORKTREE_BASE_PLACEHOLDER/TASK_N
**Protocol**:
1. Create worktree: `git worktree add WORKTREE_BASE_PLACEHOLDER/test-runner -b qa/test-runner origin/main`
2. `cd` into worktree
3. Run `bun test` in `cli/` directory — capture full output
3. Run `bun test` in `packages/cli/` directory — capture full output
4. If any tests fail:
- Read the failing test files and the source code they test
- Determine if the test is wrong (outdated assertion, wrong mock) or the source is wrong
@ -56,10 +56,10 @@ cd REPO_ROOT_PLACEHOLDER && git worktree remove WORKTREE_BASE_PLACEHOLDER/TASK_N
**Protocol**:
1. Create worktree: `git worktree add WORKTREE_BASE_PLACEHOLDER/dedup-scanner -b qa/dedup-scanner origin/main`
2. `cd` into worktree
3. Scan `cli/src/__tests__/` for these anti-patterns:
3. Scan `packages/cli/src/__tests__/` for these anti-patterns:
**a) Duplicate describe blocks**: Same function name tested in multiple files
- Use `grep -rn 'describe(' cli/src/__tests__/` to find all describe blocks
- Use `grep -rn 'describe(' packages/cli/src/__tests__/` to find all describe blocks
- Flag any function name that appears in 2+ files
- Consolidate into the most appropriate file, remove the duplicate
@ -94,7 +94,7 @@ cd REPO_ROOT_PLACEHOLDER && git worktree remove WORKTREE_BASE_PLACEHOLDER/TASK_N
2. `cd` into worktree
3. Scan for these issues:
**a) Dead code**: Functions in `shared/*.sh` or `cli/src/` that are never called
**a) Dead code**: Functions in `shared/*.sh` or `packages/cli/src/` that are never called
- Grep for the function name across all source files
- If only the definition exists (no callers), remove the function
@ -107,7 +107,7 @@ cd REPO_ROOT_PLACEHOLDER && git worktree remove WORKTREE_BASE_PLACEHOLDER/TASK_N
- Replace with `bun eval` or `jq` as appropriate per CLAUDE.md rules
**d) Duplicate utilities**: Same helper function defined in multiple TypeScript cloud modules
- If identical, move to `cli/src/shared/` and have cloud modules import it
- If identical, move to `packages/shared/src/` and have cloud modules import it
**e) Stale comments**: Comments referencing removed infrastructure, old test files, or deleted functions
- Remove or update these comments

View file

@ -6,7 +6,8 @@
"start": "bun run slack-bot.ts"
},
"dependencies": {
"@slack/bolt": "^4.3.0",
"valibot": "^1.0.0"
"@openrouter/spawn-shared": "workspace:*",
"@slack/bolt": "4.6.0",
"valibot": "1.2.0"
}
}

View file

@ -2,6 +2,7 @@ import { App } from "@slack/bolt";
import { mkdirSync, readFileSync, writeFileSync, existsSync, rmSync, readdirSync, statSync } from "node:fs";
import { dirname } from "node:path";
import * as v from "valibot";
import { toRecord } from "@openrouter/spawn-shared";
// #region Environment
@ -104,18 +105,6 @@ const ResultSchema = v.object({
session_id: v.string(),
});
function toObj(val: unknown): Record<string, unknown> | null {
if (typeof val !== "object" || val === null || Array.isArray(val)) {
return null;
}
// val is narrowed to `object` — safe to index
const obj: Record<string, unknown> = {};
for (const [k, v] of Object.entries(val)) {
obj[k] = v;
}
return obj;
}
/**
* Format a Claude Code stream-json event into Slack-friendly text, or null to skip.
*
@ -130,7 +119,7 @@ function formatStreamEvent(event: Record<string, unknown>): string | null {
// Assistant messages — contain text, tool_use, or thinking blocks
if (type === "assistant") {
const msg = toObj(event.message);
const msg = toRecord(event.message);
if (!msg) {
return null;
}
@ -138,7 +127,7 @@ function formatStreamEvent(event: Record<string, unknown>): string | null {
const parts: string[] = [];
for (const rawBlock of content) {
const block = toObj(rawBlock);
const block = toRecord(rawBlock);
if (!block) {
continue;
}
@ -148,7 +137,7 @@ function formatStreamEvent(event: Record<string, unknown>): string | null {
}
if (block.type === "tool_use" && typeof block.name === "string") {
const input = toObj(block.input);
const input = toRecord(block.input);
// Show a short summary of the tool input
let summary = "";
if (input) {
@ -173,7 +162,7 @@ function formatStreamEvent(event: Record<string, unknown>): string | null {
// User messages — contain tool_result blocks
if (type === "user") {
const msg = toObj(event.message);
const msg = toRecord(event.message);
if (!msg) {
return null;
}
@ -181,7 +170,7 @@ function formatStreamEvent(event: Record<string, unknown>): string | null {
const parts: string[] = [];
for (const rawBlock of content) {
const block = toObj(rawBlock);
const block = toRecord(rawBlock);
if (!block || block.type !== "tool_result") {
continue;
}
@ -356,7 +345,7 @@ async function buildThreadPrompt(
// Files (images, docs, etc.) — download to local tmp
if (msg.files && Array.isArray(msg.files)) {
for (const file of msg.files) {
const f = toObj(file);
const f = toRecord(file);
if (!f) {
continue;
}
@ -375,7 +364,7 @@ async function buildThreadPrompt(
// Attachments (link unfurls, bot cards)
if (msg.attachments && Array.isArray(msg.attachments)) {
for (const att of msg.attachments) {
const a = toObj(att);
const a = toRecord(att);
if (!a) {
continue;
}
@ -488,7 +477,7 @@ async function runClaudeAndStream(
continue;
}
const obj = toObj(parsed);
const obj = toRecord(parsed);
if (!obj) {
continue;
}

View file

@ -58,14 +58,14 @@ fi
# ─── Biome lint for TypeScript ─────────────────────────────────────────
staged_ts=$(git diff --cached --name-only --diff-filter=ACM | grep '^cli/src/.*\.ts$' || true)
staged_ts=$(git diff --cached --name-only --diff-filter=ACM | grep '^packages/cli/src/.*\.ts$' || true)
if [[ -n "$staged_ts" ]]; then
echo "Running Biome lint on staged TypeScript files..."
if ! (cd cli && bunx @biomejs/biome lint src/); then
if ! (cd packages/cli && bunx @biomejs/biome lint src/); then
echo ""
printf "${RED}Biome lint failed. Commit blocked.${NC}\n"
echo "Run 'cd cli && bunx @biomejs/biome lint src/' to see details."
echo "Run 'cd packages/cli && bunx @biomejs/biome lint src/' to see details."
exit 1
fi
printf "${GREEN}Biome lint passed.${NC}\n"

View file

@ -4,9 +4,9 @@ on:
push:
branches: [main]
paths:
- 'cli/src/**'
- 'cli/package.json'
- 'cli/bun.lock'
- 'packages/cli/src/**'
- 'packages/cli/package.json'
- 'packages/cli/bun.lock'
workflow_dispatch:
concurrency:
@ -28,17 +28,17 @@ jobs:
uses: oven-sh/setup-bun@v2
- name: Install dependencies and build
working-directory: cli
working-directory: packages/cli
run: |
bun install
bun run build
- name: Build cloud bundles
run: bun run cli/build-clouds.ts
run: bun run packages/cli/build-clouds.ts
- name: Get version
id: version
working-directory: cli
working-directory: packages/cli
run: echo "version=$(jq -r .version package.json)" >> "$GITHUB_OUTPUT"
- name: Update cli-latest release
@ -60,14 +60,14 @@ jobs:
**Version:** ${{ steps.version.outputs.version }}
**Built:** $(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--prerelease \
cli/cli.js
packages/cli/cli.js
- name: Upload cloud bundles
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Upload each cloud bundle as a separate release (fly-latest/fly.js, etc.)
for bundle in cli/*.js; do
for bundle in packages/cli/*.js; do
name=$(basename "$bundle" .js)
[[ "$name" == "cli" ]] && continue # skip cli.js, already uploaded above

View file

@ -47,15 +47,15 @@ jobs:
uses: oven-sh/setup-bun@v2
- name: Install dependencies
working-directory: cli
working-directory: packages/cli
run: bun install
- name: Run Biome format check
working-directory: cli
working-directory: packages/cli
run: bunx @biomejs/biome format src/
- name: Run Biome lint
working-directory: cli
working-directory: packages/cli
run: bunx @biomejs/biome lint src/
macos-compat:

View file

@ -21,12 +21,11 @@ jobs:
uses: oven-sh/setup-bun@v2
- name: Install dependencies
working-directory: cli
run: bun install
- name: Run bun tests
run: bun test
- name: Verify cloud bundles build
run: bun run cli/build-clouds.ts
run: bun run packages/cli/build-clouds.ts

3
.gitignore vendored
View file

@ -2,8 +2,7 @@ node_modules/
.docs/
__pycache__/
.claude/skills/*/start-*.sh
cli/cli.js
cli/fly.js
# Build artifacts handled by packages/cli/.gitignore
# Sensitive files — never commit secrets or private keys
.env

View file

@ -56,15 +56,15 @@ We are currently shipping with **9 curated clouds** (sorted by price):
- **Prestige or unbeatable pricing** — must be a well-known brand OR beat our cheapest options
- **Must be manually testable** — you need an account and can verify scripts work
- **REST API or CLI with SSH/exec** — no proprietary-only access methods
- **Test coverage is mandatory** — add unit tests in `cli/src/__tests__/`
- **Test coverage is mandatory** — add unit tests in `packages/cli/src/__tests__/`
Steps to add one:
1. Add cloud-specific TypeScript module in `cli/src/{cloud}/`
1. Add cloud-specific TypeScript module in `packages/cli/src/{cloud}/`
2. Add an entry to `manifest.json``clouds`
3. Add `"missing"` entries to the matrix for every existing agent
4. Implement at least 2-3 agent scripts to prove the lib works
5. Update the cloud's `sh/{cloud}/README.md`
6. **Add test coverage** (mandatory) — add unit tests in `cli/src/__tests__/`
6. **Add test coverage** (mandatory) — add unit tests in `packages/cli/src/__tests__/`
**DO NOT add GPU clouds** (CoreWeave, RunPod, etc.). Spawn agents call remote LLM APIs for inference — they need cheap CPU instances with SSH, not expensive GPU VMs.
@ -95,19 +95,24 @@ Check `gh issue list --repo OpenRouterTeam/spawn --state open` for user requests
### 4. Extend tests
Tests use Bun's built-in test runner (`bun:test`). When adding a new cloud or agent:
- Add unit tests in `cli/src/__tests__/` with mocked fetch/prompts
- Add unit tests in `packages/cli/src/__tests__/` with mocked fetch/prompts
- Run `bun test` to verify
## File Structure Convention
```
spawn/
cli/
src/index.ts # CLI entry point (bun/TypeScript)
src/manifest.ts # Manifest fetch + cache logic
src/commands.ts # All subcommands (interactive, list, run, etc.)
src/version.ts # Version constant
package.json # npm package (@openrouter/spawn)
packages/
cli/
src/index.ts # CLI entry point (bun/TypeScript)
src/manifest.ts # Manifest fetch + cache logic
src/commands.ts # All subcommands (interactive, list, run, etc.)
src/version.ts # Version constant
package.json # npm package (@openrouter/spawn)
shared/
src/parse.ts # parseJsonWith(text, schema) and parseJsonRaw(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)
@ -158,9 +163,9 @@ If you need to create documentation during development, write it to `.docs/` and
### Architecture
All cloud provisioning and agent setup logic lives in TypeScript under `cli/src/`. Agent scripts (`sh/{cloud}/{agent}.sh`) are thin bash wrappers that bootstrap bun and invoke the CLI.
All cloud provisioning and agent setup logic lives in TypeScript under `packages/cli/src/`. Agent scripts (`sh/{cloud}/{agent}.sh`) are thin bash wrappers that bootstrap bun and invoke the CLI.
**`sh/shared/github-auth.sh`** — Standalone GitHub CLI installer + OAuth login helper. Used by `cli/src/shared/agent-setup.ts` to set up `gh` on remote VMs.
**`sh/shared/github-auth.sh`** — Standalone GitHub CLI installer + OAuth login helper. Used by `packages/cli/src/shared/agent-setup.ts` to set up `gh` on remote VMs.
**`sh/shared/key-request.sh`** — API key provisioning helpers sourced by the QA harness (`qa.sh`) for loading cloud credentials from `~/.config/spawn/{cloud}.json`.
@ -196,7 +201,7 @@ When shell scripts need JSON processing, HTTP calls, crypto, or any non-trivial
- For complex operations (SigV4 signing, API calls with retries), write a heredoc `.ts` file and `bun run` it
### ESM Only — NEVER use require() or CommonJS
All TypeScript code in `cli/src/` MUST use ESM (`import`/`export`):
All TypeScript code in `packages/cli/src/` MUST use ESM (`import`/`export`):
- **NEVER** use `require()` — always use `import` (static or dynamic `await import()`)
- **NEVER** use `module.exports` — always use `export` / `export default`
- **NEVER** use `createRequire` — it's a CJS compatibility hack that triggers Bun bugs
@ -206,7 +211,7 @@ All TypeScript code in `cli/src/` MUST use ESM (`import`/`export`):
## Type Safety — No `as` Type Assertions
**`as` type assertions are banned in all TypeScript code (production AND tests).** This is enforced by a GritQL biome plugin (`cli/no-type-assertion.grit`).
**`as` type assertions are banned in all TypeScript code (production AND tests).** This is enforced by a GritQL biome plugin (`packages/cli/no-type-assertion.grit`).
### Exemptions
- `as const` — allowed (compile-time only, no runtime risk)
@ -267,13 +272,13 @@ global.fetch = mock(() => Promise.resolve(new Response("Error", { status: 500 })
```
### Shared utilities
- `cli/src/shared/parse.ts` — `parseJsonWith(text, schema)` and `parseJsonRaw(text)`
- `cli/src/shared/type-guards.ts` — `isString`, `isNumber`, `hasStatus`, `hasMessage`
- `packages/shared/src/parse.ts` — `parseJsonWith(text, schema)` and `parseJsonRaw(text)`
- `packages/shared/src/type-guards.ts` — `isString`, `isNumber`, `hasStatus`, `hasMessage`
## Testing
- **NEVER use vitest** — use Bun's built-in test runner (`bun:test`) exclusively
- Test files go in `cli/src/__tests__/`
- Test files go in `packages/cli/src/__tests__/`
- Run tests with `bun test`
- Use `import { describe, it, expect, beforeEach, afterEach, mock, spyOn } from "bun:test"`
- All tests must be pure unit tests with mocked fetch/prompts — **no subprocess spawning** (`execSync`, `spawnSync`, `Bun.spawn`)
@ -283,14 +288,14 @@ global.fetch = mock(() => Promise.resolve(new Response("Error", { status: 500 })
**CRITICAL: Bump the version on every CLI change!**
- **ANY change to `cli/` requires a version bump** in `cli/package.json`
- **ANY change to `packages/cli/` requires a version bump** in `packages/cli/package.json`
- Use semantic versioning:
- **Patch** (0.2.X → 0.2.X+1): Bug fixes, minor improvements, documentation
- **Minor** (0.X.0 → 0.X+1.0): New features, significant improvements
- **Major** (X.0.0 → X+1.0.0): Breaking changes
- The CLI has auto-update enabled — users get new versions immediately on next run
- Version bumps ensure users always have the latest fixes and features
- **NEVER commit `cli/cli.js`** — it is a build artifact (already in `.gitignore`). It is produced during releases, not checked into the repo. Do NOT use `git add -f cli/cli.js`.
- **NEVER commit `packages/cli/cli.js`** — it is a build artifact (already in `.gitignore`). It is produced during releases, not checked into the repo. Do NOT use `git add -f packages/cli/cli.js`.
## Autonomous Loops
@ -367,8 +372,8 @@ Before editing ANY files:
```
2. **Edit files using absolute paths** into the worktree:
```
/tmp/spawn-worktrees/FEATURE/cli/src/foo.ts ← YES
/home/sprite/spawn/cli/src/foo.ts ← BLOCKED
/tmp/spawn-worktrees/FEATURE/packages/cli/src/foo.ts ← YES
/home/sprite/spawn/packages/cli/src/foo.ts ← BLOCKED
```
3. **Commit and push** from the worktree:
```bash
@ -401,7 +406,7 @@ Draft PRs that go stale (no updates for 1 week) will be auto-closed.
## After Each Change
1. `bash -n {file}` syntax check on all modified scripts
2. `cd cli && bunx @biomejs/biome lint src/` — **must pass with zero errors** on all modified TypeScript
2. `cd packages/cli && bunx @biomejs/biome lint src/` — **must pass with zero errors** on all modified TypeScript
3. Update `manifest.json` matrix status to `"implemented"`
4. Update the cloud's `sh/{cloud}/README.md` with usage instructions
5. Commit with a descriptive message

View file

@ -192,13 +192,13 @@ git config core.hooksPath .githooks
```
sh/{cloud}/{agent}.sh # Agent deployment script (thin bash → bun wrapper)
cli/ # TypeScript CLI — all provisioning logic (bun)
packages/cli/ # TypeScript CLI — all provisioning logic (bun)
manifest.json # Source of truth for the matrix
```
### Adding a new cloud
1. Add cloud-specific TypeScript module in `cli/src/{cloud}/`
1. Add cloud-specific TypeScript module in `packages/cli/src/{cloud}/`
2. Add to `manifest.json`
3. Implement agent scripts
4. See [CLAUDE.md](CLAUDE.md) for full contributor guide

322
bun.lock Normal file
View file

@ -0,0 +1,322 @@
{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {},
".claude/skills/setup-spa": {
"name": "spawn-slack-bot",
"dependencies": {
"@openrouter/spawn-shared": "workspace:*",
"@slack/bolt": "4.6.0",
"valibot": "1.2.0",
},
},
"packages/cli": {
"name": "@openrouter/spawn",
"version": "0.10.0",
"bin": {
"spawn": "cli.js",
},
"dependencies": {
"@clack/prompts": "1.0.0",
"@openrouter/spawn-shared": "workspace:*",
"picocolors": "1.1.1",
"valibot": "1.2.0",
},
"devDependencies": {
"@biomejs/biome": "2.4.3",
"@types/bun": "1.3.8",
},
},
"packages/shared": {
"name": "@openrouter/spawn-shared",
"version": "0.1.0",
"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=="],
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.4.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-eOafSFlI/CF4id2tlwq9CVHgeEqvTL5SrhWff6ZORp6S3NL65zdsR3ugybItkgF8Pf4D9GSgtbB6sE3UNgOM9w=="],
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.4.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-V2+av4ilbWcBMNufTtMMXVW00nPwyIjI5qf7n9wSvUaZ+tt0EvMGk46g9sAFDJBEDOzSyoRXiSP6pCvKTOEbPA=="],
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.4.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-0m+O0x9FgK99FAwDK+fiDtjs2wnqq7bvfj17KJVeCkTwT/liI+Q9njJG7lwXK0iSJVXeFNRIxukpVI3SifMYAA=="],
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.4.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-QuFzvsGo8BA4Xm7jGX5idkw6BqFblcCPySMTvq0AhGYnhUej5VJIDJbmTKfHqwjHepZiC4fA+T5i6wmiZolZNw=="],
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.4.3", "", { "os": "linux", "cpu": "x64" }, "sha512-NVqh0saIU0u5OfOp/0jFdlKRE59+XyMvWmtx0f6Nm/2OpdxBl04coRIftBbY9d1gfu+23JVv4CItAqPYrjYh5w=="],
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.4.3", "", { "os": "linux", "cpu": "x64" }, "sha512-qEc0OCpj/uytruQ4wLM0yWNJLZy0Up8H1Er5MW3SrstqM6J2d4XqdNA86xzCy8MQCHpoVZ3lFye3GBlIL4/ljw=="],
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.4.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-gRO96vrIARilv/Cp2ZnmNNL5LSZg3RO75GPp13hsLO3N4YVpE7saaMDp2bcyV48y2N2Pbit1brkGVGta0yd6VQ=="],
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.3", "", { "os": "win32", "cpu": "x64" }, "sha512-vSm/vOJe06pf14aGHfHl3Ar91Nlx4YYmohElDJ+17UbRwe99n987S/MhAlQOkONqf1utJor04ChkCPmKb8SWdw=="],
"@clack/core": ["@clack/core@1.0.0", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-Orf9Ltr5NeiEuVJS8Rk2XTw3IxNC2Bic3ash7GgYeA8LJ/zmSNpSQ/m5UAhe03lA6KFgklzZ5KTHs4OAMA/SAQ=="],
"@clack/prompts": ["@clack/prompts@1.0.0", "", { "dependencies": { "@clack/core": "1.0.0", "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-rWPXg9UaCFqErJVQ+MecOaWsozjaxol4yjnmYcGNipAWzdaWa2x+VJmKfGq7L0APwBohQOYdHC+9RO4qRXej+A=="],
"@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=="],
"@slack/oauth": ["@slack/oauth@3.0.4", "", { "dependencies": { "@slack/logger": "^4", "@slack/web-api": "^7.10.0", "@types/jsonwebtoken": "^9", "@types/node": ">=18", "jsonwebtoken": "^9" } }, "sha512-+8H0g7mbrHndEUbYCP7uYyBCbwqmm3E6Mo3nfsDvZZW74zKk1ochfH/fWSvGInYNCVvaBUbg3RZBbTp0j8yJCg=="],
"@slack/socket-mode": ["@slack/socket-mode@2.0.5", "", { "dependencies": { "@slack/logger": "^4", "@slack/web-api": "^7.10.0", "@types/node": ">=18", "@types/ws": "^8", "eventemitter3": "^5", "ws": "^8" } }, "sha512-VaapvmrAifeFLAFaDPfGhEwwunTKsI6bQhYzxRXw7BSujZUae5sANO76WqlVsLXuhVtCVrBWPiS2snAQR2RHJQ=="],
"@slack/types": ["@slack/types@2.20.0", "", {}, "sha512-PVF6P6nxzDMrzPC8fSCsnwaI+kF8YfEpxf3MqXmdyjyWTYsZQURpkK7WWUWvP5QpH55pB7zyYL9Qem/xSgc5VA=="],
"@slack/web-api": ["@slack/web-api@7.14.1", "", { "dependencies": { "@slack/logger": "^4.0.0", "@slack/types": "^2.20.0", "@types/node": ">=18.0.0", "@types/retry": "0.12.0", "axios": "^1.13.5", "eventemitter3": "^5.0.1", "form-data": "^4.0.4", "is-electron": "2.2.2", "is-stream": "^2", "p-queue": "^6", "p-retry": "^4", "retry": "^0.13.1" } }, "sha512-RoygyteJeFswxDPJjUMESn9dldWVMD2xUcHHd9DenVavSfVC6FeVnSdDerOO7m8LLvw4Q132nQM4hX8JiF7dng=="],
"@types/body-parser": ["@types/body-parser@1.19.6", "", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g=="],
"@types/bun": ["@types/bun@1.3.8", "", { "dependencies": { "bun-types": "1.3.8" } }, "sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA=="],
"@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="],
"@types/express": ["@types/express@5.0.6", "", { "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", "@types/serve-static": "^2" } }, "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA=="],
"@types/express-serve-static-core": ["@types/express-serve-static-core@5.1.1", "", { "dependencies": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*", "@types/send": "*" } }, "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A=="],
"@types/http-errors": ["@types/http-errors@2.0.5", "", {}, "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg=="],
"@types/jsonwebtoken": ["@types/jsonwebtoken@9.0.10", "", { "dependencies": { "@types/ms": "*", "@types/node": "*" } }, "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA=="],
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
"@types/node": ["@types/node@25.3.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A=="],
"@types/qs": ["@types/qs@6.14.0", "", {}, "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ=="],
"@types/range-parser": ["@types/range-parser@1.2.7", "", {}, "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="],
"@types/retry": ["@types/retry@0.12.0", "", {}, "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="],
"@types/send": ["@types/send@1.2.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ=="],
"@types/serve-static": ["@types/serve-static@2.2.0", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*" } }, "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ=="],
"@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
"axios": ["axios@1.13.5", "", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q=="],
"body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="],
"buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="],
"bun-types": ["bun-types@1.3.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="],
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
"call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
"combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
"content-disposition": ["content-disposition@1.0.1", "", {}, "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q=="],
"content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
"cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
"cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
"ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="],
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
"encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
"es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
"escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
"etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
"eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="],
"express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="],
"finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="],
"follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="],
"form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="],
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
"fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
"has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
"http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
"iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
"is-electron": ["is-electron@2.2.2", "", {}, "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg=="],
"is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="],
"is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
"jsonwebtoken": ["jsonwebtoken@9.0.3", "", { "dependencies": { "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g=="],
"jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="],
"jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="],
"lodash.includes": ["lodash.includes@4.3.0", "", {}, "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="],
"lodash.isboolean": ["lodash.isboolean@3.0.3", "", {}, "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="],
"lodash.isinteger": ["lodash.isinteger@4.0.4", "", {}, "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="],
"lodash.isnumber": ["lodash.isnumber@3.0.3", "", {}, "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="],
"lodash.isplainobject": ["lodash.isplainobject@4.0.6", "", {}, "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="],
"lodash.isstring": ["lodash.isstring@4.0.1", "", {}, "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="],
"lodash.once": ["lodash.once@4.1.1", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="],
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
"media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
"merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
"mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
"mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
"on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
"p-finally": ["p-finally@1.0.0", "", {}, "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow=="],
"p-queue": ["p-queue@6.6.2", "", { "dependencies": { "eventemitter3": "^4.0.4", "p-timeout": "^3.2.0" } }, "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ=="],
"p-retry": ["p-retry@4.6.2", "", { "dependencies": { "@types/retry": "0.12.0", "retry": "^0.13.1" } }, "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ=="],
"p-timeout": ["p-timeout@3.2.0", "", { "dependencies": { "p-finally": "^1.0.0" } }, "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg=="],
"parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
"path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
"proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
"qs": ["qs@6.15.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ=="],
"range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
"raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="],
"retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="],
"router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
"semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
"send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="],
"serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="],
"setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
"side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="],
"side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="],
"side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
"sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
"spawn-slack-bot": ["spawn-slack-bot@workspace:.claude/skills/setup-spa"],
"statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
"tsscmp": ["tsscmp@1.0.6", "", {}, "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA=="],
"type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
"undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
"valibot": ["valibot@1.2.0", "", { "peerDependencies": { "typescript": ">=5" }, "optionalPeers": ["typescript"] }, "sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg=="],
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
"ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="],
"form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"p-queue/eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="],
"form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
}
}

5
cli/.gitignore vendored
View file

@ -1,5 +0,0 @@
node_modules/
cli.js
spawn
dist/
*.tgz

5
package.json Normal file
View file

@ -0,0 +1,5 @@
{
"private": true,
"type": "module",
"workspaces": ["packages/*", ".claude/skills/setup-spa"]
}

14
packages/cli/.gitignore vendored Normal file
View file

@ -0,0 +1,14 @@
node_modules/
cli.js
spawn
dist/
*.tgz
# Cloud provider bundles (built by build-clouds.ts)
aws.js
daytona.js
digitalocean.js
fly.js
gcp.js
hetzner.js
local.js
sprite.js

View file

@ -1,6 +1,6 @@
{
"$schema": "https://biomejs.dev/schemas/2.4.3/schema.json",
"extends": ["../lint/biome.json"],
"extends": ["../../lint/biome.json"],
"vcs": {
"enabled": true,
"clientKind": "git",
@ -29,5 +29,5 @@
}
}
],
"plugins": ["../lint/no-type-assertion.grit", "../lint/no-typeof-string-number.grit"]
"plugins": ["../../lint/no-type-assertion.grit", "../../lint/no-typeof-string-number.grit"]
}

View file

@ -1,6 +1,6 @@
{
"name": "@openrouter/spawn",
"version": "0.9.1",
"version": "0.10.0",
"type": "module",
"bin": {
"spawn": "cli.js"
@ -14,12 +14,13 @@
"test:watch": "bun test --watch"
},
"dependencies": {
"@clack/prompts": "^1.0.0",
"picocolors": "^1.1.1",
"valibot": "^1.2.0"
"@clack/prompts": "1.0.0",
"@openrouter/spawn-shared": "workspace:*",
"picocolors": "1.1.1",
"valibot": "1.2.0"
},
"devDependencies": {
"@biomejs/biome": "^2.4.3",
"@types/bun": "^1.3.8"
"@biomejs/biome": "2.4.3",
"@types/bun": "1.3.8"
}
}

View file

@ -1,7 +1,7 @@
import { describe, it, expect, beforeEach, afterEach, mock, spyOn } from "bun:test";
import { createMockManifest, createConsoleMocks, restoreMocks } from "./test-helpers";
import { loadManifest } from "../manifest";
import { isString } from "../shared/type-guards";
import { isString } from "@openrouter/spawn-shared";
/**
* Tests for cmdInteractive() in commands.ts.

View file

@ -4,7 +4,7 @@ import { join } from "node:path";
import { homedir } from "node:os";
import { createMockManifest, createConsoleMocks, restoreMocks } from "./test-helpers";
import { loadManifest } from "../manifest";
import { isString } from "../shared/type-guards";
import { isString } from "@openrouter/spawn-shared";
/**
* Tests for the cmdRun happy-path pipeline: successful download, history

View file

@ -1,7 +1,7 @@
import { describe, it, expect, beforeEach, afterEach, mock, spyOn } from "bun:test";
import { createMockManifest, createConsoleMocks, restoreMocks } from "./test-helpers";
import { loadManifest } from "../manifest";
import { isString } from "../shared/type-guards";
import { isString } from "@openrouter/spawn-shared";
/**
* Tests for commands.ts error/validation paths that call process.exit(1).

View file

@ -1,7 +1,7 @@
import { describe, it, expect, beforeEach, afterEach, mock, spyOn } from "bun:test";
import { createMockManifest, createConsoleMocks, restoreMocks } from "./test-helpers";
import { loadManifest } from "../manifest";
import { isString } from "../shared/type-guards";
import { isString } from "@openrouter/spawn-shared";
/**
* Tests for cmdRun display-name resolution and validateImplementation

View file

@ -1,7 +1,7 @@
import { describe, it, expect, beforeEach, afterEach, mock, spyOn } from "bun:test";
import { createMockManifest, createConsoleMocks, restoreMocks } from "./test-helpers";
import { loadManifest } from "../manifest";
import { isString } from "../shared/type-guards";
import { isString } from "@openrouter/spawn-shared";
/**
* Tests for detectAndFixSwappedArgs and resolveAndLog logic in commands.ts.

View file

@ -1,7 +1,7 @@
import { describe, it, expect, beforeEach, afterEach, mock, spyOn } from "bun:test";
import { createMockManifest, createConsoleMocks, restoreMocks } from "./test-helpers";
import { loadManifest } from "../manifest";
import { isString } from "../shared/type-guards";
import { isString } from "@openrouter/spawn-shared";
import pkg from "../../package.json" with { type: "json" };
const VERSION = pkg.version;

View file

@ -1,7 +1,7 @@
import { describe, it, expect, beforeEach, afterEach, mock, spyOn } from "bun:test";
import { createMockManifest, createConsoleMocks, restoreMocks } from "./test-helpers";
import { loadManifest } from "../manifest";
import { isString } from "../shared/type-guards";
import { isString } from "@openrouter/spawn-shared";
/**
* Tests for the download fallback pipeline and script failure reporting

View file

@ -1,7 +1,7 @@
import { describe, it, expect } from "bun:test";
import { readFileSync, existsSync } from "node:fs";
import { resolve, join } from "node:path";
import { isString } from "../shared/type-guards";
import { isString } from "@openrouter/spawn-shared";
/**
* Validation tests for sh/cli/install.sh.
@ -14,7 +14,7 @@ import { isString } from "../shared/type-guards";
* Agent: test-engineer
*/
const REPO_ROOT = resolve(import.meta.dir, "../../..");
const REPO_ROOT = resolve(import.meta.dir, "../../../..");
const INSTALL_SH = join(REPO_ROOT, "sh", "cli", "install.sh");
const content = readFileSync(INSTALL_SH, "utf-8");
const lines = content.split("\n");
@ -404,7 +404,7 @@ describe("install.sh validation", () => {
describe("consistency with package.json", () => {
it("should reference same repo as package.json", () => {
const pkgContent = readFileSync(join(REPO_ROOT, "cli", "package.json"), "utf-8");
const pkgContent = readFileSync(join(REPO_ROOT, "packages", "cli", "package.json"), "utf-8");
const pkg = JSON.parse(pkgContent);
// install.sh uses OpenRouterTeam/spawn
expect(content).toContain("OpenRouterTeam/spawn");
@ -418,7 +418,7 @@ describe("install.sh validation", () => {
it("should download the correct files that exist in cli/src/", () => {
// The curl-based download path downloads .ts files from cli/src/
// Verify that the files listed in install.sh actually exist
const srcDir = join(REPO_ROOT, "cli", "src");
const srcDir = join(REPO_ROOT, "packages", "cli", "src");
expect(existsSync(join(srcDir, "index.ts"))).toBe(true);
expect(existsSync(join(srcDir, "commands.ts"))).toBe(true);
expect(existsSync(join(srcDir, "manifest.ts"))).toBe(true);

View file

@ -18,7 +18,7 @@ import type { Manifest } from "../manifest";
* Agent: test-engineer
*/
const REPO_ROOT = resolve(import.meta.dir, "../../..");
const REPO_ROOT = resolve(import.meta.dir, "../../../..");
const manifestPath = join(REPO_ROOT, "manifest.json");
const manifestRaw = readFileSync(manifestPath, "utf-8");
const manifest: Manifest = JSON.parse(manifestRaw);

View file

@ -27,7 +27,7 @@ import type { Manifest } from "../manifest";
* Agent: test-engineer
*/
const REPO_ROOT = resolve(import.meta.dir, "../../..");
const REPO_ROOT = resolve(import.meta.dir, "../../../..");
const manifest: Manifest = JSON.parse(readFileSync(resolve(REPO_ROOT, "manifest.json"), "utf-8"));
const allAgents = Object.entries(manifest.agents);

View file

@ -1,6 +1,6 @@
import { describe, it, expect } from "bun:test";
import * as v from "valibot";
import { parseJsonWith, parseJsonRaw } from "../shared/parse";
import { parseJsonWith, parseJsonRaw } from "@openrouter/spawn-shared";
describe("parseJsonWith", () => {
const NumberSchema = v.object({

View file

@ -22,7 +22,7 @@ import { getPackagesForTier, needsNode, needsBun, NODE_INSTALL_CMD } from "../sh
import { SSH_BASE_OPTS, SSH_INTERACTIVE_OPTS, sleep, waitForSsh as sharedWaitForSsh } from "../shared/ssh";
import { ensureSshKeys, getSshKeyOpts } from "../shared/ssh-keys";
import * as v from "valibot";
import { parseJsonWith } from "../shared/parse";
import { parseJsonWith } from "@openrouter/spawn-shared";
import { saveVmConnection } from "../history.js";
const DASHBOARD_URL = "https://lightsail.aws.amazon.com/";

View file

@ -2,7 +2,7 @@ import "./unicode-detect.js"; // Must be first: configures TERM before clack rea
import * as p from "@clack/prompts";
import pc from "picocolors";
import * as v from "valibot";
import { parseJsonWith } from "./shared/parse";
import { parseJsonWith, isString } from "@openrouter/spawn-shared";
import { spawn } from "node:child_process";
import * as fs from "node:fs";
import * as path from "node:path";
@ -40,7 +40,6 @@ import {
type VMConnection,
} from "./history.js";
import { buildDashboardHint, EXIT_CODE_GUIDANCE, SIGNAL_GUIDANCE } from "./guidance-data.js";
import { isString } from "./shared/type-guards";
import { destroyServer as flyDestroyServer, ensureFlyCli, ensureFlyToken } from "./fly/fly.js";
import { destroyServer as hetznerDestroyServer, ensureHcloudToken } from "./hetzner/hetzner.js";
import { destroyServer as doDestroyServer, ensureDoToken } from "./digitalocean/digitalocean.js";
@ -3228,7 +3227,7 @@ export async function cmdCloudInfo(cloud: string, preloadedManifest?: Manifest):
// ── Update ─────────────────────────────────────────────────────────────────────
async function fetchRemoteVersion(): Promise<string> {
const res = await fetch(`${RAW_BASE}/cli/package.json`, {
const res = await fetch(`${RAW_BASE}/packages/cli/package.json`, {
signal: AbortSignal.timeout(FETCH_TIMEOUT),
});
if (!res.ok) {

View file

@ -17,10 +17,9 @@ import {
} from "../shared/ui";
import type { CloudInitTier } from "../shared/agents";
import { getPackagesForTier, needsNode, needsBun, NODE_INSTALL_CMD } from "../shared/cloud-init";
import { parseJsonWith, parseJsonRaw } from "../shared/parse";
import { parseJsonWith, parseJsonRaw, isString, toObjectArray, toRecord } from "@openrouter/spawn-shared";
import * as v from "valibot";
import { saveVmConnection } from "../history.js";
import { isString } from "../shared/type-guards";
const DAYTONA_API_BASE = "https://app.daytona.io/api";
const DAYTONA_DASHBOARD_URL = "https://app.daytona.io/";
@ -56,21 +55,6 @@ function parseJson(text: string): Record<string, unknown> | null {
return parseJsonWith(text, LooseObject);
}
/** Narrow an already-parsed unknown value to a Record<string, unknown>, or null. */
function toRecord(val: unknown): Record<string, unknown> | null {
const result = v.safeParse(LooseObject, val);
return result.success ? result.output : null;
}
/** Filter an array to only Record<string, unknown> entries. */
function toObjectArray(val: unknown): Record<string, unknown>[] {
if (!Array.isArray(val)) {
return [];
}
return val.filter(
(item): item is Record<string, unknown> => item !== null && typeof item === "object" && !Array.isArray(item),
);
}
async function daytonaApi(method: string, endpoint: string, body?: string, maxRetries = 3): Promise<string> {
const url = `${DAYTONA_API_BASE}${endpoint}`;

View file

@ -19,8 +19,7 @@ import {
} from "../shared/ui";
import type { CloudInitTier } from "../shared/agents";
import { getPackagesForTier, needsNode, needsBun, NODE_INSTALL_CMD } from "../shared/cloud-init";
import { parseJsonWith } from "../shared/parse";
import { isString, isNumber } from "../shared/type-guards";
import { parseJsonWith, isString, isNumber, toObjectArray } from "@openrouter/spawn-shared";
import { SSH_BASE_OPTS, SSH_INTERACTIVE_OPTS, sleep, waitForSsh as sharedWaitForSsh } from "../shared/ssh";
import { ensureSshKeys, getSshFingerprint, getSshKeyOpts } from "../shared/ssh-keys";
import { saveVmConnection } from "../history.js";
@ -128,15 +127,6 @@ function parseJson(text: string): Record<string, unknown> | null {
return parseJsonWith(text, LooseObject);
}
function toObjectArray(val: unknown): Record<string, unknown>[] {
if (!Array.isArray(val)) {
return [];
}
return val.filter(
(item): item is Record<string, unknown> => item !== null && typeof item === "object" && !Array.isArray(item),
);
}
// ─── Token Persistence ───────────────────────────────────────────────────────
const DO_CONFIG_PATH = `${process.env.HOME}/.config/spawn/digitalocean.json`;

View file

@ -19,8 +19,7 @@ import {
import type { CloudInitTier } from "../shared/agents";
import { getPackagesForTier, needsNode, needsBun, NODE_INSTALL_CMD } from "../shared/cloud-init";
import * as v from "valibot";
import { parseJsonWith, parseJsonRaw } from "../shared/parse";
import { isString, isNumber } from "../shared/type-guards";
import { parseJsonWith, parseJsonRaw, isString, isNumber, toObjectArray } from "@openrouter/spawn-shared";
import { saveVmConnection } from "../history.js";
const FLY_API_BASE = "https://api.machines.dev/v1";
@ -169,15 +168,6 @@ function parseJson(text: string): Record<string, unknown> | null {
return parseJsonWith(text, LooseObject);
}
function toObjectArray(val: unknown): Record<string, unknown>[] {
if (!Array.isArray(val)) {
return [];
}
return val.filter(
(item): item is Record<string, unknown> => item !== null && typeof item === "object" && !Array.isArray(item),
);
}
function hasError(text: string): boolean {
return text.includes('"error"') || text.includes('"errors"');
}

View file

@ -21,8 +21,7 @@ import { getPackagesForTier, needsNode, needsBun, NODE_INSTALL_CMD } from "../sh
import { SSH_BASE_OPTS, SSH_INTERACTIVE_OPTS, sleep, waitForSsh as sharedWaitForSsh } from "../shared/ssh";
import { ensureSshKeys, getSshFingerprint, getSshKeyOpts } from "../shared/ssh-keys";
import * as v from "valibot";
import { parseJsonWith } from "../shared/parse";
import { isString, isNumber } from "../shared/type-guards";
import { parseJsonWith, isString, isNumber, toObjectArray } from "@openrouter/spawn-shared";
import { saveVmConnection } from "../history.js";
const HETZNER_API_BASE = "https://api.hetzner.cloud/v1";
@ -98,20 +97,6 @@ function rec(val: unknown): Record<string, unknown> | undefined {
return undefined;
}
/** Extract an array of record objects from an unknown value */
function toObjectArray(val: unknown): Record<string, unknown>[] {
if (!Array.isArray(val)) {
return [];
}
const result: Record<string, unknown>[] = [];
for (const item of val) {
if (item && typeof item === "object" && !Array.isArray(item)) {
result.push(Object.fromEntries(Object.entries(item)));
}
}
return result;
}
// ─── Token Persistence ───────────────────────────────────────────────────────
const HETZNER_CONFIG_PATH = `${process.env.HOME}/.config/spawn/hetzner.json`;

Some files were not shown because too many files have changed in this diff Show more