@stackbone/cli 0.1.0-alpha.3 → 0.1.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/main.js CHANGED
@@ -965,7 +965,15 @@ var projectConfigSchema = z.object({
965
965
  controlPlaneUrl: z.url(),
966
966
  localDevInstallationId: z.uuid().optional(),
967
967
  organizationSlug: z.string().optional(),
968
- agentSlug: z.string().optional()
968
+ agentSlug: z.string().optional(),
969
+ /** F5: slug of the CLI starter the project was scaffolded from. */
970
+ starter: z.string().optional(),
971
+ /**
972
+ * F5 read-compat: pre-rename CLIs wrote a `template` field. Accepted on
973
+ * read for one release so a re-`init` over an existing project upgrades
974
+ * the file to `starter`; never written by current code.
975
+ */
976
+ template: z.string().optional()
969
977
  });
970
978
  /**
971
979
  * Project-shared config at `<cwd>/stackbone.config.json` (committable, unlike
@@ -1451,7 +1459,7 @@ var isVersionFlag = (rawArgs) => rawArgs.length === 1 && VERSION_FLAGS.has(rawAr
1451
1459
  var formatVersionOutput = (input) => `stackbone-cli ${input.cliVersion}\ncontract ${input.contractVersion}`;
1452
1460
  //#endregion
1453
1461
  //#region src/dev/contract.ts
1454
- var STACKBONE_CLI_BUILD_VERSION = "0.1.0-alpha.3";
1462
+ var STACKBONE_CLI_BUILD_VERSION = "0.1.0-alpha.4";
1455
1463
  /**
1456
1464
  * Capabilities the local emulator advertises on `GET /api/contract`. Typed as
1457
1465
  * `readonly Capability[]` so the compiler refuses any string outside the
@@ -6731,7 +6739,7 @@ var requestDevTunnelGrant = async (input) => {
6731
6739
  if (response.status === 503) throw new StackboneCliError({
6732
6740
  code: "tunnel_relay_unhealthy",
6733
6741
  message: "Stackbone tunnel relay is unhealthy and cannot mint grants right now",
6734
- suggestion: `Check ${isError503Body(parsedBody) && typeof parsedBody.statusUrl === "string" ? parsedBody.statusUrl : DEFAULT_STATUS_URL} for relay status and re-run with \`--no-tunnel\` to skip the tunnel.`,
6742
+ suggestion: `Check ${isError503Body(parsedBody) && typeof parsedBody.statusUrl === "string" ? parsedBody.statusUrl : DEFAULT_STATUS_URL} for relay status and re-run once the relay is back. The tunnel is mandatory, so there is no opt-out flag.`,
6735
6743
  cause: parsedBody
6736
6744
  });
6737
6745
  if (!response.ok) throw new StackboneCliError({
@@ -6874,7 +6882,7 @@ var resolveFrpcBin = async (input) => {
6874
6882
  throw new StackboneCliError({
6875
6883
  code: "generic",
6876
6884
  message: "Could not auto-fetch frpc",
6877
- suggestion: "Check your network or install frpc manually (https://github.com/fatedier/frp/releases) and re-run, or pass `--no-tunnel` / `--no-auto-frpc`",
6885
+ suggestion: "Check your network, or install frpc manually from https://github.com/fatedier/frp/releases and point STACKBONE_FRPC_BIN at the binary before re-running.",
6878
6886
  cause: err
6879
6887
  });
6880
6888
  } finally {
@@ -6958,7 +6966,7 @@ var TUNNEL_HOST_SUFFIX = "tun.stackbone.ai";
6958
6966
  var frpcNotInstalledError = (cause) => new StackboneCliError({
6959
6967
  code: "generic",
6960
6968
  message: "`frpc` is not installed or not on PATH",
6961
- suggestion: "Install frpc from https://github.com/fatedier/frp/releases, set STACKBONE_FRPC_BIN to override the binary, or run `stackbone dev --no-tunnel` to skip the tunnel",
6969
+ suggestion: "Install frpc from https://github.com/fatedier/frp/releases, or set STACKBONE_FRPC_BIN to point at an existing binary. The tunnel is mandatory there is no opt-out flag.",
6962
6970
  cause
6963
6971
  });
6964
6972
  var buildFrpcConfig = (grant, targetUrl) => {
@@ -7730,11 +7738,6 @@ var createDevCommand = (ctx) => defineCommand({
7730
7738
  type: "boolean",
7731
7739
  description: "Print the JSON contract this CLI advertises (the same payload the emulator serves at /api/contract) and exit without booting the dev session.",
7732
7740
  default: false
7733
- },
7734
- verbose: {
7735
- type: "boolean",
7736
- description: "Stream every log line and docker compose output. Default UI uses per-stage spinners; --verbose switches back to the raw firehose.",
7737
- default: false
7738
7741
  }
7739
7742
  },
7740
7743
  run: ({ args }) => safeRun(ctx.flags, async (a) => {
@@ -7751,7 +7754,7 @@ var createDevCommand = (ctx) => defineCommand({
7751
7754
  await buildHandler$2(ctx, {
7752
7755
  port,
7753
7756
  listen: Boolean(a.listen),
7754
- verbose: Boolean(a.verbose)
7757
+ verbose: ctx.flags.verbose
7755
7758
  });
7756
7759
  })(args)
7757
7760
  });
@@ -7797,18 +7800,20 @@ several exploratory calls.
7797
7800
 
7798
7801
  ## Commands shipped today
7799
7802
 
7803
+ - \`stackbone login\` / \`logout\` — device-code flow over magic link
7800
7804
  - \`stackbone whoami\` — show the active user and organization
7801
7805
  - \`stackbone current\` — print the active organization slug
7802
7806
  - \`stackbone list\` — list organizations the current user owns
7803
7807
  - \`stackbone metadata\` — agent-friendly one-shot overview
7808
+ - \`stackbone init\` / \`link\` — scaffold a new project, or link an existing one
7809
+ - \`stackbone dev\` — local emulator of the control plane
7810
+ - \`stackbone db migrate up | create | status | add-rag\` — Drizzle migrations
7811
+ - \`stackbone db studio\` — open Drizzle Studio against the dev Postgres
7804
7812
  - \`stackbone docs <topic>\` — print this kind of help inline
7805
7813
 
7806
- ## Commands shipped by sibling tasks
7814
+ ## Pending
7807
7815
 
7808
- - \`stackbone login\` / \`logout\` device-code flow over magic link
7809
- - \`stackbone init\` / \`link\` — scaffold a new project, or link an existing one
7810
- - \`stackbone dev\` — local emulator of the control plane
7811
- - \`stackbone deploy\` — push to Fly Machines
7816
+ - \`stackbone deploy\` — push to Fly Machines (stub)
7812
7817
 
7813
7818
  ## State on disk
7814
7819
 
@@ -7817,34 +7822,35 @@ several exploratory calls.
7817
7822
  `;
7818
7823
  var sdk = `# @stackbone/sdk
7819
7824
 
7820
- Monolithic SDK for the agent container with six modules and explicit
7821
- escape hatches to the upstream SDKs. Refer to the
7822
- \`Implementar @stackbone-sdk con 6 modulos monoliticos\` task for the canonical
7823
- shape.
7825
+ Monolithic SDK for the agent container with wrapper modules over the
7826
+ underlying data primitives plus control-plane façades. The creator
7827
+ keeps explicit escape hatches to the upstream SDKs.
7824
7828
 
7825
7829
  ## Modules
7826
7830
 
7827
- - \`client.database\` — Drizzle + @neondatabase/serverless (HTTP driver)
7828
- - \`client.storage\` — @aws-sdk/client-s3 + Cloudflare R2
7831
+ - \`client.database\` — Drizzle + postgres-js (any Postgres URL)
7832
+ - \`client.storage\` — @aws-sdk/client-s3 + Cloudflare R2 / MinIO
7829
7833
  - \`client.ai\` — openai SDK with OpenRouter base URL override
7830
- - \`client.queues\` — @upstash/qstash publisher + Hono receiver
7834
+ - \`client.queues\` — pgmq publisher (surface defined, runtime pending)
7831
7835
  - \`client.rag\` — LlamaParse (opt-in) + chunker + embeddings + pgvector + tsvector
7832
- - \`client.observability\` — @opentelemetry/sdk-node with auto-instrumentations
7836
+ - \`client.observability\` — OpenTelemetry span processor + run-cost aggregator + per-run logger
7837
+ - \`client.memory\` — long-term mem0-backed memory (surface defined, runtime pending)
7833
7838
 
7834
7839
  ## Façades on top of the control plane
7835
7840
 
7836
7841
  - \`client.approval.*\` — HITL queue and workflow resume
7837
7842
  - \`client.secrets.*\` — organization-scoped encrypted secrets
7838
7843
  - \`client.connections.*\` — OAuth connections (Notion, GDrive, Slack, …)
7839
- - \`client.config.*\` — runtime config injected via \`AGENT_CONFIG\`
7844
+ - \`client.config.*\` — typed reads of dynamic config from the control plane
7840
7845
  - \`client.events.*\` — emit events to the organization eventbus
7846
+ - \`client.prompts.*\` — typed prompt templates fetched from the control plane
7841
7847
 
7842
7848
  ## Escape hatch
7843
7849
 
7844
- Every upstream env var (\`DATABASE_URL\`, \`AWS_*\`, \`S3_ENDPOINT\`,
7850
+ Every upstream env var (\`STACKBONE_POSTGRES_URL\`, \`STACKBONE_S3_*\`,
7845
7851
  \`OPENROUTER_API_KEY\`, \`OPENROUTER_BASE_URL\`, \`LLAMA_PARSE_API_KEY\` if
7846
- opt-in, \`QSTASH_*\`) is also injected, so the creator can swap any module
7847
- for the upstream SDK without losing access to its data.
7852
+ opt-in, \`MEM0_API_KEY\` if opt-in) is also injected, so the creator can
7853
+ swap any module for the upstream SDK without losing access to its data.
7848
7854
 
7849
7855
  The runtime target is **Hono + Node 24 LTS** (Bun 1.x as opt-in via the
7850
7856
  \`runtime.engine: bun\` field of \`agent.yaml\`). No adapters for Next,
@@ -7858,15 +7864,23 @@ runs your agent under \`@stackbone/sdk\`, and exposes the Studio API at
7858
7864
 
7859
7865
  ## Defaults that matter
7860
7866
 
7861
- - \`--tunnel\` (default on) opens a public tunnel against the
7867
+ - Mandatory tunnel\`stackbone dev\` always opens a tunnel against the
7862
7868
  Stackbone-owned relay (\`*.tun.stackbone.ai\`) so the HTTPS bundle of
7863
- \`app.stackbone.ai/studio\` can reach the emulator from
7864
- Safari/Brave/Chrome (mixed-content / local-network-access
7865
- restrictions). Pass \`--no-tunnel\` to skip it on permissive
7866
- browsers. Override the binary with \`STACKBONE_FRPC_BIN\`.
7869
+ \`app.stackbone.ai\` can reach the emulator from Safari/Brave/Chrome
7870
+ (mixed-content / local-network-access restrictions). There is no
7871
+ opt-out flag the \`local_tunnel_url\` field on the installation row
7872
+ is what makes the session shareable from the dashboard. The CLI
7873
+ auto-fetches \`frpc\` into \`~/.cache/stackbone/bin/\` if missing.
7874
+ Override the binary with \`STACKBONE_FRPC_BIN\` and pin the release
7875
+ with \`STACKBONE_FRPC_VERSION\`.
7867
7876
  - HTTP server bound to \`127.0.0.1\` by default — not reachable from
7868
7877
  the LAN. Pass \`--listen\` to bind to \`0.0.0.0\` instead; the CLI
7869
7878
  prints a visible warning so the exposure isn't silent.
7879
+ - \`--print-contract\` — print the JSON contract this CLI advertises
7880
+ (same payload as the emulator's \`/api/contract\`) and exit without
7881
+ booting the dev session. Useful for cross-version handshake debugging.
7882
+ - \`--verbose\` (also \`STACKBONE_VERBOSE=1\`) — stream every log line
7883
+ and docker compose output. Default UI uses per-stage spinners.
7870
7884
 
7871
7885
  ## CORS allowlist
7872
7886
 
@@ -7972,6 +7986,32 @@ before booting the agent process and re-runs it whenever the migrations
7972
7986
  folder changes. The default is opt-out so a noisy schema cannot silently
7973
7987
  mutate your local database.
7974
7988
 
7989
+ ### \`rag\` (optional)
7990
+
7991
+ rag:
7992
+ embeddingModel: openai/text-embedding-3-small # default
7993
+ autoMigrate: false # default
7994
+
7995
+ The block \`client.rag\` reads. \`embeddingModel\` is the OpenRouter/OpenAI
7996
+ identifier the pipeline embeds chunks with — change it before ingesting
7997
+ the first document, since collections are pinned to the dimensionality of
7998
+ the model that created them. \`autoMigrate: true\` lets \`stackbone dev\`
7999
+ apply the per-agent RAG schema on boot (off by default to keep the
8000
+ migration explicit). Strict — unknown keys fail parse.
8001
+
8002
+ ### \`protocol\` (optional)
8003
+
8004
+ protocol:
8005
+ required: 10
8006
+
8007
+ Creator-side floor on the Stackbone Agent Protocol contract version. The
8008
+ CLI forwards \`protocol.required\` to the SDK as \`protocolRequired\`; the
8009
+ SDK's contract gate fails closed with \`contract_version_unsupported\`
8010
+ whenever the datapath advertises a contract below this floor, before any
8011
+ per-module capability check runs. Omit the block to fall back to the SDK's
8012
+ hard floor (\`MIN_SUPPORTED_CONTRACT_VERSION\`). Strict — unknown keys fail
8013
+ parse.
8014
+
7975
8015
  ## Reference
7976
8016
 
7977
8017
  This file lives in the validators contract (\`agentManifestSchema\`) and is
@@ -7979,7 +8019,7 @@ shared by \`stackbone init\`, \`stackbone dev\`, \`stackbone deploy\` and the
7979
8019
  \`stackbone db migrate ...\` family. Keep it under version control — the
7980
8020
  control plane never writes to it.
7981
8021
  `,
7982
- templates: stubFor("Epic 5 — official templates", "Crear template workflow LangGraph basico"),
8022
+ starters: stubFor("Epic 5 — official starters", "Document the starter catalogue (CLI scaffold sources)"),
7983
8023
  connections: stubFor("Epic 6 — data layer", "Montar docker-compose embebido con Postgres pgvector pgmq y MinIO para stackbone dev")
7984
8024
  };
7985
8025
  var docTopicList = Object.keys(docTopics);
@@ -8106,7 +8146,7 @@ stackbone metadata --json # one-shot overview that replaces 5–7 explorer call
8106
8146
  ### Start a new project
8107
8147
 
8108
8148
  \`\`\`sh
8109
- stackbone init # menu of templates (workflow / autonomous / RAG)
8149
+ stackbone init # menu of starters (hello-world + 9 capability demos)
8110
8150
  stackbone dev # local emulator at http://localhost:4242
8111
8151
  \`\`\`
8112
8152
 
@@ -8133,7 +8173,7 @@ stackbone deploy # build + push + Fly Machines
8133
8173
  - \`stackbone docs cli\` — command reference
8134
8174
  - \`stackbone docs sdk\` — \`@stackbone/sdk\` modules
8135
8175
  - \`stackbone docs agent-yaml\` — manifest reference (Epic 3)
8136
- - \`stackbone docs templates\` — official templates (Epic 5)
8176
+ - \`stackbone docs starters\` — official starters (Epic 5)
8137
8177
  - \`stackbone docs connections\` — data layer (Epic 6)
8138
8178
  `;
8139
8179
  //#endregion
@@ -8216,7 +8256,7 @@ var writeFileSafe = async (path, contents, force) => {
8216
8256
  await mkdir(dirname(path), { recursive: true });
8217
8257
  await writeFile(path, contents);
8218
8258
  };
8219
- var writeTemplateFiles = async (files, input) => {
8259
+ var writeStarterFiles = async (files, input) => {
8220
8260
  for (const file of files) await writeFileSafe(join(input.root, file.path), file.contents, input.force);
8221
8261
  };
8222
8262
  var writeAgentManifest = async (agentName, input) => {
@@ -8230,7 +8270,8 @@ var writeProjectConfig = async (ctx, agent, input, extras = {}) => {
8230
8270
  controlPlaneUrl: ctx.apiUrl,
8231
8271
  ...extras.localDevInstallationId !== void 0 ? { localDevInstallationId: extras.localDevInstallationId } : {},
8232
8272
  ...extras.organizationSlug !== void 0 ? { organizationSlug: extras.organizationSlug } : {},
8233
- ...extras.agentSlug !== void 0 ? { agentSlug: extras.agentSlug } : {}
8273
+ ...extras.agentSlug !== void 0 ? { agentSlug: extras.agentSlug } : {},
8274
+ ...extras.starter !== void 0 ? { starter: extras.starter } : {}
8234
8275
  };
8235
8276
  await mkdir(join(input.root, ".stackbone"), { recursive: true });
8236
8277
  await writeFile(join(input.root, ".stackbone", "project.json"), JSON.stringify(config, null, 2));
@@ -8262,209 +8303,779 @@ var detectExistingProjectFiles = async (root, paths) => {
8262
8303
  return found;
8263
8304
  };
8264
8305
  //#endregion
8265
- //#region src/templates/autonomous/template.yaml?raw
8266
- var template_default$2 = "id: autonomous\nlabel: autonomous (mock)\nhint: ReAct loop with two tools. Mock LLM output until @stackbone/sdk lands.\norder: 3\nvariables:\n - agentName\n - agentSlug\n";
8306
+ //#region ../../starters/ai/package.json?raw
8307
+ var package_default$9 = "{\n \"name\": \"starter-ai\",\n \"version\": \"0.0.1\",\n \"private\": true,\n \"type\": \"module\",\n \"scripts\": {\n \"dev\": \"node --watch src/index.ts\",\n \"start\": \"node src/index.ts\"\n },\n \"engines\": {\n \"node\": \">=24\"\n },\n \"dependencies\": {\n \"@hono/node-server\": \"^1.13.0\",\n \"@stackbone/sdk\": \"^0.1.0-alpha.1\",\n \"hono\": \"^4.6.0\"\n },\n \"stackbone\": {\n \"label\": \"AI (LLM via OpenRouter)\",\n \"hint\": \"Demo of `client.ai` — chat completions and embeddings against any OpenRouter model.\",\n \"order\": 1,\n \"category\": \"Capabilities\"\n }\n}\n";
8267
8308
  //#endregion
8268
- //#region src/templates/hello-world/template.yaml?raw
8269
- var template_default$1 = "id: hello-world\nlabel: hello-world\nhint: Minimal Hono /invoke that echoes its input. Best to learn the contract.\norder: 1\nvariables:\n - agentName\n - agentSlug\n";
8309
+ //#region ../../starters/config/package.json?raw
8310
+ var package_default$8 = "{\n \"name\": \"starter-config\",\n \"version\": \"0.0.1\",\n \"private\": true,\n \"type\": \"module\",\n \"scripts\": {\n \"dev\": \"node --watch src/index.ts\",\n \"start\": \"node src/index.ts\"\n },\n \"engines\": {\n \"node\": \">=24\"\n },\n \"dependencies\": {\n \"@hono/node-server\": \"^1.13.0\",\n \"@stackbone/sdk\": \"^0.1.0-alpha.1\",\n \"hono\": \"^4.6.0\"\n },\n \"stackbone\": {\n \"label\": \"Config\",\n \"hint\": \"Demo of `client.config` — runtime config injected via `AGENT_CONFIG`.\",\n \"order\": 9,\n \"category\": \"Capabilities\"\n }\n}\n";
8270
8311
  //#endregion
8271
- //#region src/templates/workflow/template.yaml?raw
8272
- var template_default = "id: workflow\nlabel: workflow (mock)\nhint: Multi-node state machine. Mock LLM output until @stackbone/sdk lands.\norder: 2\nvariables:\n - agentName\n - agentSlug\n";
8312
+ //#region ../../starters/db/package.json?raw
8313
+ var package_default$7 = "{\n \"name\": \"starter-db\",\n \"version\": \"0.0.1\",\n \"private\": true,\n \"type\": \"module\",\n \"scripts\": {\n \"dev\": \"node --watch src/index.ts\",\n \"start\": \"node src/index.ts\"\n },\n \"engines\": {\n \"node\": \">=24\"\n },\n \"dependencies\": {\n \"@hono/node-server\": \"^1.13.0\",\n \"@stackbone/sdk\": \"^0.1.0-alpha.1\",\n \"hono\": \"^4.6.0\"\n },\n \"stackbone\": {\n \"label\": \"Database (Drizzle + Postgres)\",\n \"hint\": \"Demo of `client.database` — Drizzle ORM against per-agent Postgres.\",\n \"order\": 3,\n \"category\": \"Capabilities\"\n }\n}\n";
8273
8314
  //#endregion
8274
- //#region src/templates/autonomous/.stackbone/migrations/0000_init.sql?raw
8275
- var _0000_init_default$2 = "CREATE TABLE \"examples\" (\n \"id\" serial PRIMARY KEY NOT NULL,\n \"name\" text NOT NULL,\n \"created_at\" timestamp DEFAULT now() NOT NULL\n);\n";
8315
+ //#region ../../starters/hello-world/package.json?raw
8316
+ var package_default$6 = "{\n \"name\": \"starter-hello-world\",\n \"version\": \"0.0.1\",\n \"private\": true,\n \"type\": \"module\",\n \"scripts\": {\n \"dev\": \"node --watch src/index.ts\",\n \"start\": \"node src/index.ts\"\n },\n \"engines\": {\n \"node\": \">=24\"\n },\n \"dependencies\": {\n \"@hono/node-server\": \"^1.13.0\",\n \"@stackbone/sdk\": \"^0.1.0-alpha.1\",\n \"hono\": \"^4.6.0\"\n },\n \"stackbone\": {\n \"label\": \"Hello world\",\n \"hint\": \"Minimal Hono /invoke that echoes its input. Best place to learn the contract.\",\n \"order\": 1,\n \"category\": \"Core\"\n }\n}\n";
8276
8317
  //#endregion
8277
- //#region src/templates/autonomous/.stackbone/migrations/meta/0000_snapshot.json?raw
8278
- var _0000_snapshot_default$2 = "{\n \"id\": \"00000000-0000-4000-8000-000000000001\",\n \"prevId\": \"00000000-0000-0000-0000-000000000000\",\n \"version\": \"7\",\n \"dialect\": \"postgresql\",\n \"tables\": {\n \"public.examples\": {\n \"name\": \"examples\",\n \"schema\": \"\",\n \"columns\": {\n \"id\": {\n \"name\": \"id\",\n \"type\": \"serial\",\n \"primaryKey\": true,\n \"notNull\": true\n },\n \"name\": {\n \"name\": \"name\",\n \"type\": \"text\",\n \"primaryKey\": false,\n \"notNull\": true\n },\n \"created_at\": {\n \"name\": \"created_at\",\n \"type\": \"timestamp\",\n \"primaryKey\": false,\n \"notNull\": true,\n \"default\": \"now()\"\n }\n },\n \"indexes\": {},\n \"foreignKeys\": {},\n \"compositePrimaryKeys\": {},\n \"uniqueConstraints\": {},\n \"policies\": {},\n \"checkConstraints\": {},\n \"isRLSEnabled\": false\n }\n },\n \"enums\": {},\n \"schemas\": {},\n \"sequences\": {},\n \"roles\": {},\n \"policies\": {},\n \"views\": {},\n \"_meta\": {\n \"columns\": {},\n \"schemas\": {},\n \"tables\": {}\n }\n}\n";
8318
+ //#region ../../starters/hitl/package.json?raw
8319
+ var package_default$5 = "{\n \"name\": \"starter-hitl\",\n \"version\": \"0.0.1\",\n \"private\": true,\n \"type\": \"module\",\n \"scripts\": {\n \"dev\": \"node --watch src/index.ts\",\n \"start\": \"node src/index.ts\"\n },\n \"engines\": {\n \"node\": \">=24\"\n },\n \"dependencies\": {\n \"@hono/node-server\": \"^1.13.0\",\n \"@stackbone/sdk\": \"^0.1.0-alpha.1\",\n \"hono\": \"^4.6.0\"\n },\n \"stackbone\": {\n \"label\": \"Human-in-the-loop\",\n \"hint\": \"Demo of `client.approval` pause and wait for human approval before continuing.\",\n \"order\": 6,\n \"category\": \"Capabilities\"\n }\n}\n";
8279
8320
  //#endregion
8280
- //#region src/templates/autonomous/.stackbone/migrations/meta/_journal.json?raw
8281
- var _journal_default$2 = "{\n \"version\": \"7\",\n \"dialect\": \"postgresql\",\n \"entries\": [\n {\n \"idx\": 0,\n \"version\": \"7\",\n \"when\": 1778889700000,\n \"tag\": \"0000_init\",\n \"breakpoints\": true\n }\n ]\n}\n";
8321
+ //#region ../../starters/prompts/package.json?raw
8322
+ var package_default$4 = "{\n \"name\": \"starter-prompts\",\n \"version\": \"0.0.1\",\n \"private\": true,\n \"type\": \"module\",\n \"scripts\": {\n \"dev\": \"node --watch src/index.ts\",\n \"start\": \"node src/index.ts\"\n },\n \"engines\": {\n \"node\": \">=24\"\n },\n \"dependencies\": {\n \"@hono/node-server\": \"^1.13.0\",\n \"@stackbone/sdk\": \"^0.1.0-alpha.1\",\n \"hono\": \"^4.6.0\"\n },\n \"stackbone\": {\n \"label\": \"Prompts\",\n \"hint\": \"Demo of `client.prompts` — managed prompts with versioned templates.\",\n \"order\": 8,\n \"category\": \"Capabilities\"\n }\n}\n";
8282
8323
  //#endregion
8283
- //#region src/templates/autonomous/Dockerfile?raw
8284
- var Dockerfile_raw_default$2 = "# Distroless Node 24 image. Multi-stage so the runtime stays minimal.\nFROM node:24-alpine AS deps\nWORKDIR /app\nCOPY package.json ./\nRUN npm install --omit=dev\n\nFROM gcr.io/distroless/nodejs24-debian12\nWORKDIR /app\nCOPY --from=deps /app/node_modules ./node_modules\nCOPY src ./src\nCOPY agent.yaml ./\nENV PORT=8080\nEXPOSE 8080\nCMD [\"src/index.ts\"]\n";
8324
+ //#region ../../starters/queues/package.json?raw
8325
+ var package_default$3 = "{\n \"name\": \"starter-queues\",\n \"version\": \"0.0.1\",\n \"private\": true,\n \"type\": \"module\",\n \"scripts\": {\n \"dev\": \"node --watch src/index.ts\",\n \"start\": \"node src/index.ts\"\n },\n \"engines\": {\n \"node\": \">=24\"\n },\n \"dependencies\": {\n \"@hono/node-server\": \"^1.13.0\",\n \"@stackbone/sdk\": \"^0.1.0-alpha.1\",\n \"hono\": \"^4.6.0\"\n },\n \"stackbone\": {\n \"label\": \"Queues (QStash)\",\n \"hint\": \"Demo of `client.queues` QStash publisher + signed webhook receiver.\",\n \"order\": 5,\n \"category\": \"Capabilities\"\n }\n}\n";
8285
8326
  //#endregion
8286
- //#region src/templates/autonomous/README.md?raw
8287
- var README_default$2 = "# {{agentName}}\n\nStackbone agent scaffolded by `stackbone init`. The runtime contract is the\nHTTP surface in `src/index.ts`: `/invoke`, `/health`, `/schema`.\n\n## Develop\n\n```sh\npnpm install\npnpm dev # node --watch src/index.ts\n```\n\n## Deploy\n\n```sh\nstackbone deploy # builds the image and pushes it to Fly Machines\n```\n\nSee `stackbone docs sdk` for the SDK reference and `stackbone docs cli`\nfor the full command surface.\n";
8327
+ //#region ../../starters/rag/package.json?raw
8328
+ var package_default$2 = "{\n \"name\": \"starter-rag\",\n \"version\": \"0.0.1\",\n \"private\": true,\n \"type\": \"module\",\n \"scripts\": {\n \"dev\": \"node --watch src/index.ts\",\n \"start\": \"node src/index.ts\"\n },\n \"engines\": {\n \"node\": \">=24\"\n },\n \"dependencies\": {\n \"@hono/node-server\": \"^1.13.0\",\n \"@stackbone/sdk\": \"^0.1.0-alpha.1\",\n \"hono\": \"^4.6.0\"\n },\n \"stackbone\": {\n \"label\": \"RAG (pgvector)\",\n \"hint\": \"Demo of `client.rag` pgvector-backed retrieval-augmented generation.\",\n \"order\": 2,\n \"category\": \"Capabilities\"\n }\n}\n";
8288
8329
  //#endregion
8289
- //#region src/templates/autonomous/package.json?raw
8290
- var package_default$2 = "{\n \"name\": \"{{agentSlug}}\",\n \"version\": \"0.0.1\",\n \"private\": true,\n \"type\": \"module\",\n \"scripts\": {\n \"dev\": \"node --watch src/index.ts\",\n \"start\": \"node src/index.ts\"\n },\n \"engines\": {\n \"node\": \">=24\"\n },\n \"dependencies\": {\n \"@hono/node-server\": \"^1.13.0\",\n \"@stackbone/sdk\": \"^0.0.1\",\n \"hono\": \"^4.6.0\"\n }\n}\n";
8330
+ //#region ../../starters/secrets/package.json?raw
8331
+ var package_default$1 = "{\n \"name\": \"starter-secrets\",\n \"version\": \"0.0.1\",\n \"private\": true,\n \"type\": \"module\",\n \"scripts\": {\n \"dev\": \"node --watch src/index.ts\",\n \"start\": \"node src/index.ts\"\n },\n \"engines\": {\n \"node\": \">=24\"\n },\n \"dependencies\": {\n \"@hono/node-server\": \"^1.13.0\",\n \"@stackbone/sdk\": \"^0.1.0-alpha.1\",\n \"hono\": \"^4.6.0\"\n },\n \"stackbone\": {\n \"label\": \"Secrets\",\n \"hint\": \"Demo of `client.secrets` — workspace-scoped encrypted values.\",\n \"order\": 7,\n \"category\": \"Capabilities\"\n }\n}\n";
8291
8332
  //#endregion
8292
- //#region src/templates/autonomous/src/index.ts?raw
8293
- var src_default$2 = "import { serve } from '@hono/node-server';\nimport { Hono } from 'hono';\n\n// MOCK autonomous loop. In MVP this is a real ReAct loop driven by\n// `client.ai.completion` from `@stackbone/sdk`. For POC the agent runs a\n// fixed-iteration loop with two example tools so the contract works and\n// `stackbone dev` has something to invoke.\n\nconst MAX_ITERATIONS = 4;\n\nconst tools = {\n search: (query: string): string => `mocked search result for \"${query}\"`,\n summarise: (text: string): string => `summary: ${text.slice(0, 80)}`,\n};\n\ninterface Step {\n tool: keyof typeof tools;\n input: string;\n output: string;\n}\n\nconst runLoop = (goal: string): { goal: string; steps: Step[]; answer: string } => {\n const steps: Step[] = [];\n for (let i = 0; i < MAX_ITERATIONS && steps.length < 2; i++) {\n if (steps.length === 0) {\n const output = tools.search(goal);\n steps.push({ tool: 'search', input: goal, output });\n } else {\n const lastOutput = steps[steps.length - 1]?.output ?? '';\n const output = tools.summarise(lastOutput);\n steps.push({ tool: 'summarise', input: lastOutput, output });\n }\n }\n return {\n goal,\n steps,\n answer: steps[steps.length - 1]?.output ?? 'no steps executed',\n };\n};\n\nconst app = new Hono();\n\napp.post('/invoke', async (c) => {\n const body = await c.req.json().catch(() => ({ goal: '' }));\n return c.json(runLoop(typeof body.goal === 'string' ? body.goal : ''));\n});\n\napp.get('/health', (c) => c.json({ status: 'ok' }));\napp.get('/schema', (c) =>\n c.json({\n input: { type: 'object', properties: { goal: { type: 'string' } }, required: ['goal'] },\n output: {\n type: 'object',\n properties: {\n goal: { type: 'string' },\n steps: { type: 'array' },\n answer: { type: 'string' },\n },\n },\n }),\n);\n\nconst port = Number(process.env.PORT ?? 8080);\nserve({ fetch: app.fetch, port });\nconsole.log(`autonomous agent listening on :${port}`);\n";
8333
+ //#region ../../starters/storage/package.json?raw
8334
+ var package_default = "{\n \"name\": \"starter-storage\",\n \"version\": \"0.0.1\",\n \"private\": true,\n \"type\": \"module\",\n \"scripts\": {\n \"dev\": \"node --watch src/index.ts\",\n \"start\": \"node src/index.ts\"\n },\n \"engines\": {\n \"node\": \">=24\"\n },\n \"dependencies\": {\n \"@hono/node-server\": \"^1.13.0\",\n \"@stackbone/sdk\": \"^0.1.0-alpha.1\",\n \"hono\": \"^4.6.0\"\n },\n \"stackbone\": {\n \"label\": \"Object storage (S3)\",\n \"hint\": \"Demo of `client.storage` S3 / MinIO object storage scoped to the agent.\",\n \"order\": 4,\n \"category\": \"Capabilities\"\n }\n}\n";
8294
8335
  //#endregion
8295
- //#region src/templates/autonomous/src/schema.ts?raw
8296
- var schema_default$2 = "// Drizzle schema for {{agentName}}.\n//\n// Tables declared here are managed by `stackbone db migrate up`. Edit this\n// file, then run `stackbone db migrate create <name>` to emit the next SQL\n// migration under `.stackbone/migrations/`. The first migration shipped with\n// the template (`0000_init.sql`) creates the `examples` table below so the\n// agent has something queryable on its first `stackbone dev` boot.\n//\n// Drizzle column helpers re-export through `@stackbone/sdk/db` never import\n// from `drizzle-orm/pg-core` directly. The SDK barrel is the single contract\n// the platform supports.\nimport { pgTable, serial, text, timestamp } from '@stackbone/sdk/db';\n\nexport const examples = pgTable('examples', {\n id: serial('id').primaryKey(),\n name: text('name').notNull(),\n createdAt: timestamp('created_at').defaultNow().notNull(),\n});\n";
8336
+ //#region ../../starters/ai/.claude/skills/stackbone/SKILL.md?raw
8337
+ var SKILL_default$8 = "---\nname: stackbone\ndescription: Use this skill when the user is working with the Stackbone CLI, the `@stackbone/sdk` package, the `agent.yaml` manifest, official templates, or connections to the Stackbone control plane. Triggers on commands like `stackbone init`, `stackbone dev`, `stackbone deploy`, on questions about organization state, exit codes, or environment variables, and on any reference to `@stackbone/*` imports in TypeScript code. Provides the command surface, output contract, exit codes, environment variables and idiomatic workflows.\n---\n\n# Stackbone agent operating manual\n\nThis skill is regenerated by `stackbone init` / `stackbone link` and ships\ninside the CLI release 0.1.0-alpha.1. Edit the upstream template in\n`apps/cli/src/skills/templates.ts` if something here is wrong.\n\n## CLI surface\n\nThe CLI binary is `stackbone` (also reachable as `npx @stackbone/cli@alpha ...`). Run\n`stackbone docs cli` for the canonical command/flag reference, or\n`stackbone docs sdk` for the `@stackbone/sdk` overview neither command does\nnetwork IO.\n\n## Output contract\n\nEvery command supports `--json` (or `STACKBONE_JSON=1`). Successful JSON\nresponses look like:\n\n```json\n{ \"schema_version\": 1, \"...\": \"...\" }\n```\n\nErrors look like:\n\n```json\n{\n \"schema_version\": 1,\n \"error\": { \"code\": \"auth\", \"message\": \"...\", \"suggestion\": \"Run \\`stackbone login\\`\" },\n \"exit_code\": 2\n}\n```\n\n`schema_version` only bumps on backward-incompatible shape changes. If you\nsee a different value, refuse to parse and ask the user to upgrade.\n\n## Exit codes\n\n- `0` ok\n- `1` generic\n- `2` auth (no session, expired, refused login)\n- `3` no project linked (no `.stackbone/project.json` in cwd)\n- `4` not found (organization, agent, deployment)\n- `5` permission denied\n\nBranch on these instead of parsing stack traces.\n\n## Environment variables\n\n- `STACKBONE_JSON=1` — same as `--json`\n- `STACKBONE_API_URL` — same as `--api-url <url>`\n- `STACKBONE_AUTO_LOGIN=0` same as `--no-auto-login`\n- `STACKBONE_LOG_LEVEL` `debug`, `info`, `warn`, `error` (logger goes\n to stderr, never pollutes the JSON payload on stdout)\n\n## State on disk\n\n- `<cwd>/.stackbone/project.json` — organization + agent linked to this folder.\n Created by `stackbone init` / `link`.\n- `~/.stackbone/credentials.json` (chmod 600) — sessions keyed by control\n plane URL.\n- `~/.stackbone/config.json` — global preferences (default API URL,\n telemetry opt-in).\n\nThe control plane this skill points at: `https://api.stackbone.ai`.\nActive organization slug: `template1-2`.\n\n## Common workflows\n\n### Authenticate\n\n```sh\nstackbone login # interactive (device-code flow)\nstackbone whoami # confirm session\nstackbone metadata --json # one-shot overview that replaces 5–7 explorer calls\n```\n\n### Start a new project\n\n```sh\nstackbone init # menu of templates (workflow / autonomous / RAG)\nstackbone dev # local emulator at http://localhost:4242\n```\n\n### Deploy\n\n```sh\nstackbone deploy # build + push + Fly Machines\n```\n\n## Anti-patterns\n\n- Do not hardcode organization IDs, credentials or control plane URLs into\n agent code — the SDK reads them from the environment at runtime.\n- Do not parse human output. Re-run the command with `--json` and use\n `JSON.parse`.\n- Do not call the control plane HTTP API directly from a creator's agent.\n Use `@stackbone/sdk` so credential rotation, observability and metering\n stay correct.\n- Do not commit `.stackbone/` or `~/.stackbone/` — the entries are in the\n project's `.gitignore` for a reason.\n\n## Where to learn more\n\n- `stackbone docs cli` — command reference\n- `stackbone docs sdk` — `@stackbone/sdk` modules\n- `stackbone docs agent-yaml` — manifest reference (Epic 3)\n- `stackbone docs templates` — official templates (Epic 5)\n- `stackbone docs connections` — data layer (Epic 6)\n";
8297
8338
  //#endregion
8298
- //#region src/templates/hello-world/.stackbone/migrations/0000_init.sql?raw
8299
- var _0000_init_default$1 = "CREATE TABLE \"examples\" (\n \"id\" serial PRIMARY KEY NOT NULL,\n \"name\" text NOT NULL,\n \"created_at\" timestamp DEFAULT now() NOT NULL\n);\n";
8339
+ //#region ../../starters/ai/.gitignore?raw
8340
+ var _gitignore_raw_default$8 = "# >>> stackbone >>>\n.stackbone/\nnode_modules/\ndist/\n# <<< stackbone <<<\n";
8300
8341
  //#endregion
8301
- //#region src/templates/hello-world/.stackbone/migrations/meta/0000_snapshot.json?raw
8302
- var _0000_snapshot_default$1 = "{\n \"id\": \"00000000-0000-4000-8000-000000000001\",\n \"prevId\": \"00000000-0000-0000-0000-000000000000\",\n \"version\": \"7\",\n \"dialect\": \"postgresql\",\n \"tables\": {\n \"public.examples\": {\n \"name\": \"examples\",\n \"schema\": \"\",\n \"columns\": {\n \"id\": {\n \"name\": \"id\",\n \"type\": \"serial\",\n \"primaryKey\": true,\n \"notNull\": true\n },\n \"name\": {\n \"name\": \"name\",\n \"type\": \"text\",\n \"primaryKey\": false,\n \"notNull\": true\n },\n \"created_at\": {\n \"name\": \"created_at\",\n \"type\": \"timestamp\",\n \"primaryKey\": false,\n \"notNull\": true,\n \"default\": \"now()\"\n }\n },\n \"indexes\": {},\n \"foreignKeys\": {},\n \"compositePrimaryKeys\": {},\n \"uniqueConstraints\": {},\n \"policies\": {},\n \"checkConstraints\": {},\n \"isRLSEnabled\": false\n }\n },\n \"enums\": {},\n \"schemas\": {},\n \"sequences\": {},\n \"roles\": {},\n \"policies\": {},\n \"views\": {},\n \"_meta\": {\n \"columns\": {},\n \"schemas\": {},\n \"tables\": {}\n }\n}\n";
8342
+ //#region ../../starters/ai/Dockerfile?raw
8343
+ var Dockerfile_raw_default$9 = "# Distroless Node 24 image. Multi-stage so the runtime stays minimal.\nFROM node:24-alpine AS deps\nWORKDIR /app\nCOPY package.json ./\nRUN npm install --omit=dev\n\nFROM gcr.io/distroless/nodejs24-debian12\nWORKDIR /app\nCOPY --from=deps /app/node_modules ./node_modules\nCOPY src ./src\nCOPY agent.yaml ./\nENV PORT=8080\nEXPOSE 8080\nCMD [\"src/index.ts\"]\n";
8303
8344
  //#endregion
8304
- //#region src/templates/hello-world/.stackbone/migrations/meta/_journal.json?raw
8305
- var _journal_default$1 = "{\n \"version\": \"7\",\n \"dialect\": \"postgresql\",\n \"entries\": [\n {\n \"idx\": 0,\n \"version\": \"7\",\n \"when\": 1778889700000,\n \"tag\": \"0000_init\",\n \"breakpoints\": true\n }\n ]\n}\n";
8345
+ //#region ../../starters/ai/README.md?raw
8346
+ var README_default$9 = "# starter-ai\n\nDemo of `client.ai` (chat completions, embeddings, models) from `@stackbone/sdk`.\n\n`client.ai` proxies the OpenAI SDK against OpenRouter, so any model id\n(`openai/gpt-4o-mini`, `anthropic/claude-3.5-sonnet`, …) works.\n\n## Layout\n\n```\nsrc/\n index.ts # Hono server, mounts routes, logs every request\n lib/\n client.ts # Shared Stackbone client\n logger.ts # JSON line logger\n routes/\n health.ts # GET /health\n schema-info.ts # GET /schema\n invoke.ts # POST /invoke -> chat completion\n embeddings.ts # POST /embeddings -> create embedding(s)\n models.ts # GET /models -> list available models\n```\n\n## Run\n\n```sh\nnpm install\nstackbone dev\n```\n\nThen:\n\n```sh\ncurl http://127.0.0.1:8080/health\ncurl -X POST http://127.0.0.1:8080/invoke -H 'content-type: application/json' \\\n -d '{\"prompt\":\"Say hi in one short sentence.\"}'\ncurl -X POST http://127.0.0.1:8080/embeddings -H 'content-type: application/json' \\\n -d '{\"input\":\"the quick brown fox\"}'\ncurl http://127.0.0.1:8080/models\n```\n\nRequires `OPENROUTER_API_KEY` (or whichever upstream env the SDK falls back to)\nin the agent environment.\n";
8306
8347
  //#endregion
8307
- //#region src/templates/hello-world/Dockerfile?raw
8308
- var Dockerfile_raw_default$1 = "# Distroless Node 24 image. Multi-stage so the runtime stays minimal.\nFROM node:24-alpine AS deps\nWORKDIR /app\nCOPY package.json ./\nRUN npm install --omit=dev\n\nFROM gcr.io/distroless/nodejs24-debian12\nWORKDIR /app\nCOPY --from=deps /app/node_modules ./node_modules\nCOPY src ./src\nCOPY agent.yaml ./\nENV PORT=8080\nEXPOSE 8080\nCMD [\"src/index.ts\"]\n";
8348
+ //#region ../../starters/ai/agent.yaml?raw
8349
+ var agent_default$9 = "# Stackbone agent manifest. Schema: stackbone.ai/v1.\n# Run `stackbone docs agent-yaml` for the full reference.\n\napiVersion: stackbone.ai/v1\nname: starter-ai\nruntime:\n engine: node\n entry: src/index.ts\ndatabase:\n schema: ./src/schema.ts\n migrations: ./.stackbone/migrations\ndev:\n autoMigrate: false\nrag:\n embeddingModel: openai/text-embedding-3-small\n autoMigrate: false\n";
8350
+ //#endregion
8351
+ //#region ../../starters/ai/src/index.ts?raw
8352
+ var src_default$9 = "import { serve } from '@hono/node-server';\nimport { Hono } from 'hono';\n\nimport { log } from './lib/logger.ts';\nimport embeddings from './routes/embeddings.ts';\nimport health from './routes/health.ts';\nimport invoke from './routes/invoke.ts';\nimport models from './routes/models.ts';\nimport schemaInfo from './routes/schema-info.ts';\n\nconst app = new Hono();\n\napp.use('*', async (c, next) => {\n const start = Date.now();\n await next();\n log('info', 'request', {\n method: c.req.method,\n path: c.req.path,\n status: c.res.status,\n durationMs: Date.now() - start,\n });\n});\n\napp.route('/', health);\napp.route('/', schemaInfo);\napp.route('/', invoke);\napp.route('/', embeddings);\napp.route('/', models);\n\nconst port = Number(process.env.PORT ?? 8080);\nserve({ fetch: app.fetch, port });\nlog('info', 'starter-ai listening', { port });\n";
8353
+ //#endregion
8354
+ //#region ../../starters/ai/src/lib/client.ts?raw
8355
+ var client_default$8 = "import { createClient } from '@stackbone/sdk';\n\nexport const client = createClient({\n stackboneApiUrl: process.env.STACKBONE_DEV_API_URL ?? 'http://127.0.0.1:4242',\n});\n";
8356
+ //#endregion
8357
+ //#region ../../starters/ai/src/lib/logger.ts?raw
8358
+ var logger_default$8 = "type Level = 'info' | 'warn' | 'error';\n\nexport function log(level: Level, msg: string, fields?: Record<string, unknown>): void {\n const line = JSON.stringify({ ts: new Date().toISOString(), level, msg, ...fields });\n if (level === 'error') console.error(line);\n else console.log(line);\n}\n";
8359
+ //#endregion
8360
+ //#region ../../starters/ai/src/routes/embeddings.ts?raw
8361
+ var embeddings_default = "import { Hono } from 'hono';\n\nimport { client } from '../lib/client.ts';\nimport { log } from '../lib/logger.ts';\n\nconst route = new Hono();\n\nroute.post('/embeddings', async (c) => {\n const body = await c.req.json().catch(() => ({}) as { input?: string | string[]; model?: string });\n const input = body.input ?? '';\n const model = typeof body.model === 'string' ? body.model : 'openai/text-embedding-3-small';\n\n log('info', 'embeddings', { model, count: Array.isArray(input) ? input.length : 1 });\n\n const result = await client.ai.embeddings.create({ model, input });\n\n if (result.error) {\n log('error', 'embeddings failed', { code: result.error.code });\n return c.json({ error: result.error }, 502);\n }\n\n const vectors = result.data.data.map((d) => ({ index: d.index, dimensions: d.embedding.length }));\n log('info', 'embeddings done', { model, vectors: vectors.length });\n return c.json({ model, vectors });\n});\n\nexport default route;\n";
8362
+ //#endregion
8363
+ //#region ../../starters/ai/src/routes/health.ts?raw
8364
+ var health_default$8 = "import { Hono } from 'hono';\n\nconst route = new Hono();\nroute.get('/health', (c) => c.json({ status: 'ok' }));\nexport default route;\n";
8365
+ //#endregion
8366
+ //#region ../../starters/ai/src/routes/invoke.ts?raw
8367
+ var invoke_default$8 = "import { Hono } from 'hono';\n\nimport { client } from '../lib/client.ts';\nimport { log } from '../lib/logger.ts';\n\nconst route = new Hono();\n\nroute.post('/invoke', async (c) => {\n const body = await c.req.json().catch(() => ({}) as { prompt?: string; model?: string });\n const prompt = typeof body.prompt === 'string' ? body.prompt : 'Say hi in one short sentence.';\n const model = typeof body.model === 'string' ? body.model : 'openai/gpt-4o-mini';\n\n log('info', 'invoke', { model, prompt });\n\n const result = await client.ai.chat.completions.create({\n model,\n messages: [{ role: 'user', content: prompt }],\n });\n\n if (result.error) {\n log('error', 'chat completion failed', { code: result.error.code });\n return c.json({ error: result.error }, 502);\n }\n\n const reply = result.data.choices[0]?.message?.content ?? '';\n log('info', 'invoke reply', { model, length: reply.length });\n return c.json({ reply, model });\n});\n\nexport default route;\n";
8368
+ //#endregion
8369
+ //#region ../../starters/ai/src/routes/models.ts?raw
8370
+ var models_default = "import { Hono } from 'hono';\n\nimport { client } from '../lib/client.ts';\nimport { log } from '../lib/logger.ts';\n\nconst route = new Hono();\n\nroute.get('/models', async (c) => {\n log('info', 'list models');\n\n const result = await client.ai.models.list();\n if (result.error) {\n log('error', 'list models failed', { code: result.error.code });\n return c.json({ error: result.error }, 502);\n }\n\n const ids = result.data.data.slice(0, 50).map((m) => m.id);\n log('info', 'list models done', { total: result.data.data.length, returned: ids.length });\n return c.json({ total: result.data.data.length, ids });\n});\n\nexport default route;\n";
8371
+ //#endregion
8372
+ //#region ../../starters/ai/src/routes/schema-info.ts?raw
8373
+ var schema_info_default$8 = "import { Hono } from 'hono';\n\nconst route = new Hono();\n\nroute.get('/schema', (c) =>\n c.json({\n input: {\n type: 'object',\n properties: {\n prompt: { type: 'string' },\n model: { type: 'string' },\n },\n required: ['prompt'],\n },\n output: {\n type: 'object',\n properties: {\n reply: { type: 'string' },\n model: { type: 'string' },\n },\n },\n }),\n);\n\nexport default route;\n";
8374
+ //#endregion
8375
+ //#region ../../starters/config/.claude/skills/stackbone/SKILL.md?raw
8376
+ var SKILL_default$7 = "---\nname: stackbone\ndescription: Use this skill when the user is working with the Stackbone CLI, the `@stackbone/sdk` package, the `agent.yaml` manifest, official templates, or connections to the Stackbone control plane. Triggers on commands like `stackbone init`, `stackbone dev`, `stackbone deploy`, on questions about organization state, exit codes, or environment variables, and on any reference to `@stackbone/*` imports in TypeScript code. Provides the command surface, output contract, exit codes, environment variables and idiomatic workflows.\n---\n\n# Stackbone — agent operating manual\n\nThis skill is regenerated by `stackbone init` / `stackbone link` and ships\ninside the CLI release 0.1.0-alpha.1. Edit the upstream template in\n`apps/cli/src/skills/templates.ts` if something here is wrong.\n\n## CLI surface\n\nThe CLI binary is `stackbone` (also reachable as `npx @stackbone/cli@alpha ...`). Run\n`stackbone docs cli` for the canonical command/flag reference, or\n`stackbone docs sdk` for the `@stackbone/sdk` overview — neither command does\nnetwork IO.\n\n## Output contract\n\nEvery command supports `--json` (or `STACKBONE_JSON=1`). Successful JSON\nresponses look like:\n\n```json\n{ \"schema_version\": 1, \"...\": \"...\" }\n```\n\nErrors look like:\n\n```json\n{\n \"schema_version\": 1,\n \"error\": { \"code\": \"auth\", \"message\": \"...\", \"suggestion\": \"Run \\`stackbone login\\`\" },\n \"exit_code\": 2\n}\n```\n\n`schema_version` only bumps on backward-incompatible shape changes. If you\nsee a different value, refuse to parse and ask the user to upgrade.\n\n## Exit codes\n\n- `0` ok\n- `1` generic\n- `2` auth (no session, expired, refused login)\n- `3` no project linked (no `.stackbone/project.json` in cwd)\n- `4` not found (organization, agent, deployment)\n- `5` permission denied\n\nBranch on these instead of parsing stack traces.\n\n## Environment variables\n\n- `STACKBONE_JSON=1` — same as `--json`\n- `STACKBONE_API_URL` — same as `--api-url <url>`\n- `STACKBONE_AUTO_LOGIN=0` — same as `--no-auto-login`\n- `STACKBONE_LOG_LEVEL` — `debug`, `info`, `warn`, `error` (logger goes\n to stderr, never pollutes the JSON payload on stdout)\n\n## State on disk\n\n- `<cwd>/.stackbone/project.json` — organization + agent linked to this folder.\n Created by `stackbone init` / `link`.\n- `~/.stackbone/credentials.json` (chmod 600) — sessions keyed by control\n plane URL.\n- `~/.stackbone/config.json` — global preferences (default API URL,\n telemetry opt-in).\n\nThe control plane this skill points at: `https://api.stackbone.ai`.\nActive organization slug: `template1-2`.\n\n## Common workflows\n\n### Authenticate\n\n```sh\nstackbone login # interactive (device-code flow)\nstackbone whoami # confirm session\nstackbone metadata --json # one-shot overview that replaces 5–7 explorer calls\n```\n\n### Start a new project\n\n```sh\nstackbone init # menu of templates (workflow / autonomous / RAG)\nstackbone dev # local emulator at http://localhost:4242\n```\n\n### Deploy\n\n```sh\nstackbone deploy # build + push + Fly Machines\n```\n\n## Anti-patterns\n\n- Do not hardcode organization IDs, credentials or control plane URLs into\n agent code — the SDK reads them from the environment at runtime.\n- Do not parse human output. Re-run the command with `--json` and use\n `JSON.parse`.\n- Do not call the control plane HTTP API directly from a creator's agent.\n Use `@stackbone/sdk` so credential rotation, observability and metering\n stay correct.\n- Do not commit `.stackbone/` or `~/.stackbone/` — the entries are in the\n project's `.gitignore` for a reason.\n\n## Where to learn more\n\n- `stackbone docs cli` — command reference\n- `stackbone docs sdk` — `@stackbone/sdk` modules\n- `stackbone docs agent-yaml` — manifest reference (Epic 3)\n- `stackbone docs templates` — official templates (Epic 5)\n- `stackbone docs connections` — data layer (Epic 6)\n";
8377
+ //#endregion
8378
+ //#region ../../starters/config/.gitignore?raw
8379
+ var _gitignore_raw_default$7 = "# >>> stackbone >>>\n.stackbone/\nnode_modules/\ndist/\n# <<< stackbone <<<\n";
8380
+ //#endregion
8381
+ //#region ../../starters/config/Dockerfile?raw
8382
+ var Dockerfile_raw_default$8 = "# Distroless Node 24 image. Multi-stage so the runtime stays minimal.\nFROM node:24-alpine AS deps\nWORKDIR /app\nCOPY package.json ./\nRUN npm install --omit=dev\n\nFROM gcr.io/distroless/nodejs24-debian12\nWORKDIR /app\nCOPY --from=deps /app/node_modules ./node_modules\nCOPY src ./src\nCOPY agent.yaml ./\nENV PORT=8080\nEXPOSE 8080\nCMD [\"src/index.ts\"]\n";
8383
+ //#endregion
8384
+ //#region ../../starters/config/README.md?raw
8385
+ var README_default$8 = "# starter-config\n\nDemo of `client.config` (runtime config injected via `AGENT_CONFIG`) from\n`@stackbone/sdk`.\n\n## Layout\n\n```\nsrc/\n index.ts # Hono server\n lib/\n client.ts # Shared Stackbone client\n logger.ts # JSON line logger\n routes/\n health.ts # GET /health\n schema-info.ts # GET /schema\n invoke.ts # POST /invoke -> get one key (soft-miss on absence)\n get-config.ts # GET /config/:key\n get-many.ts # POST /config/bulk -> { values: { key: any } }\n```\n\n## Run\n\n```sh\nnpm install\nstackbone dev\n```\n\nThen:\n\n```sh\ncurl -X POST http://127.0.0.1:8080/invoke -H 'content-type: application/json' \\\n -d '{\"key\":\"feature_flags.new_ui\"}'\ncurl http://127.0.0.1:8080/config/api.timeout_ms\ncurl -X POST http://127.0.0.1:8080/config/bulk -H 'content-type: application/json' \\\n -d '{\"keys\":[\"api.timeout_ms\",\"feature_flags.new_ui\"]}'\n```\n";
8386
+ //#endregion
8387
+ //#region ../../starters/config/agent.yaml?raw
8388
+ var agent_default$8 = "# Stackbone agent manifest. Schema: stackbone.ai/v1.\n# Run `stackbone docs agent-yaml` for the full reference.\n\napiVersion: stackbone.ai/v1\nname: starter-config\nruntime:\n engine: node\n entry: src/index.ts\ndatabase:\n schema: ./src/schema.ts\n migrations: ./.stackbone/migrations\ndev:\n autoMigrate: false\nrag:\n embeddingModel: openai/text-embedding-3-small\n autoMigrate: false\n";
8309
8389
  //#endregion
8310
- //#region src/templates/hello-world/README.md?raw
8311
- var README_default$1 = "# {{agentName}}\n\nStackbone agent scaffolded by `stackbone init`. The runtime contract is the\nHTTP surface in `src/index.ts`: `/invoke`, `/health`, `/schema`.\n\n## Develop\n\n```sh\npnpm install\npnpm dev # node --watch src/index.ts\n```\n\n## Deploy\n\n```sh\nstackbone deploy # builds the image and pushes it to Fly Machines\n```\n\nSee `stackbone docs sdk` for the SDK reference and `stackbone docs cli`\nfor the full command surface.\n";
8390
+ //#region ../../starters/config/src/index.ts?raw
8391
+ var src_default$8 = "import { serve } from '@hono/node-server';\nimport { Hono } from 'hono';\n\nimport { log } from './lib/logger.ts';\nimport getConfig from './routes/get-config.ts';\nimport getMany from './routes/get-many.ts';\nimport health from './routes/health.ts';\nimport invoke from './routes/invoke.ts';\nimport schemaInfo from './routes/schema-info.ts';\n\nconst app = new Hono();\n\napp.use('*', async (c, next) => {\n const start = Date.now();\n await next();\n log('info', 'request', {\n method: c.req.method,\n path: c.req.path,\n status: c.res.status,\n durationMs: Date.now() - start,\n });\n});\n\napp.route('/', health);\napp.route('/', schemaInfo);\napp.route('/', invoke);\napp.route('/', getConfig);\napp.route('/', getMany);\n\nconst port = Number(process.env.PORT ?? 8080);\nserve({ fetch: app.fetch, port });\nlog('info', 'starter-config listening', { port });\n";
8312
8392
  //#endregion
8313
- //#region src/templates/hello-world/package.json?raw
8314
- var package_default$1 = "{\n \"name\": \"{{agentSlug}}\",\n \"version\": \"0.0.1\",\n \"private\": true,\n \"type\": \"module\",\n \"scripts\": {\n \"dev\": \"node --watch src/index.ts\",\n \"start\": \"node src/index.ts\"\n },\n \"engines\": {\n \"node\": \">=24\"\n },\n \"dependencies\": {\n \"@hono/node-server\": \"^1.13.0\",\n \"@stackbone/sdk\": \"^0.0.1\",\n \"hono\": \"^4.6.0\"\n }\n}\n";
8393
+ //#region ../../starters/config/src/lib/client.ts?raw
8394
+ var client_default$7 = "import { createClient } from '@stackbone/sdk';\n\nexport const client = createClient({\n stackboneApiUrl: process.env.STACKBONE_DEV_API_URL ?? 'http://127.0.0.1:4242',\n});\n";
8315
8395
  //#endregion
8316
- //#region src/templates/hello-world/src/index.ts?raw
8317
- var src_default$1 = "import { serve } from '@hono/node-server';\nimport { Hono } from 'hono';\n\nconst app = new Hono();\n\napp.post('/invoke', async (c) => {\n const body = await c.req.json().catch(() => ({}));\n return c.json({ hello: body.who ?? 'world' });\n});\n\napp.get('/health', (c) => c.json({ status: 'ok' }));\n\napp.get('/schema', (c) =>\n c.json({\n input: { type: 'object', properties: { who: { type: 'string' } } },\n output: { type: 'object', properties: { hello: { type: 'string' } } },\n }),\n);\n\nconst port = Number(process.env.PORT ?? 8080);\nserve({ fetch: app.fetch, port });\nconsole.log(`hello-world agent listening on :${port}`);\n";
8396
+ //#region ../../starters/config/src/lib/logger.ts?raw
8397
+ var logger_default$7 = "type Level = 'info' | 'warn' | 'error';\n\nexport function log(level: Level, msg: string, fields?: Record<string, unknown>): void {\n const line = JSON.stringify({ ts: new Date().toISOString(), level, msg, ...fields });\n if (level === 'error') console.error(line);\n else console.log(line);\n}\n";
8318
8398
  //#endregion
8319
- //#region src/templates/hello-world/src/schema.ts?raw
8320
- var schema_default$1 = "// Drizzle schema for {{agentName}}.\n//\n// Tables declared here are managed by `stackbone db migrate up`. Edit this\n// file, then run `stackbone db migrate create <name>` to emit the next SQL\n// migration under `.stackbone/migrations/`. The first migration shipped with\n// the template (`0000_init.sql`) creates the `examples` table below so the\n// agent has something queryable on its first `stackbone dev` boot.\n//\n// Drizzle column helpers re-export through `@stackbone/sdk/db` — never import\n// from `drizzle-orm/pg-core` directly. The SDK barrel is the single contract\n// the platform supports.\nimport { pgTable, serial, text, timestamp } from '@stackbone/sdk/db';\n\nexport const examples = pgTable('examples', {\n id: serial('id').primaryKey(),\n name: text('name').notNull(),\n createdAt: timestamp('created_at').defaultNow().notNull(),\n});\n";
8399
+ //#region ../../starters/config/src/routes/get-config.ts?raw
8400
+ var get_config_default = "import { Hono } from 'hono';\n\nimport { client } from '../lib/client.ts';\nimport { log } from '../lib/logger.ts';\n\nconst route = new Hono();\n\nroute.get('/config/:key', async (c) => {\n const key = c.req.param('key');\n log('info', 'fetch config', { key });\n\n const result = await client.config.get(key);\n if (result.error) {\n log('error', 'fetch config failed', { key, code: result.error.code });\n return c.json({ error: result.error }, 502);\n }\n\n return c.json({ key, value: result.data });\n});\n\nexport default route;\n";
8321
8401
  //#endregion
8322
- //#region src/templates/workflow/.stackbone/migrations/0000_init.sql?raw
8402
+ //#region ../../starters/config/src/routes/get-many.ts?raw
8403
+ var get_many_default$1 = "import { Hono } from 'hono';\n\nimport { client } from '../lib/client.ts';\nimport { log } from '../lib/logger.ts';\n\nconst route = new Hono();\n\nroute.post('/config/bulk', async (c) => {\n const body = await c.req.json().catch(() => ({}) as { keys?: string[] });\n const keys = Array.isArray(body.keys) ? body.keys : [];\n if (keys.length === 0) return c.json({ error: 'missing keys[]' }, 400);\n\n log('info', 'config bulk', { count: keys.length });\n\n const result = await client.config.getMany(keys);\n if (result.error) {\n log('error', 'config bulk failed', { code: result.error.code });\n return c.json({ error: result.error }, 502);\n }\n\n log('info', 'config bulk done', { resolved: Object.keys(result.data).length });\n return c.json({ values: result.data });\n});\n\nexport default route;\n";
8404
+ //#endregion
8405
+ //#region ../../starters/config/src/routes/health.ts?raw
8406
+ var health_default$7 = "import { Hono } from 'hono';\n\nconst route = new Hono();\nroute.get('/health', (c) => c.json({ status: 'ok' }));\nexport default route;\n";
8407
+ //#endregion
8408
+ //#region ../../starters/config/src/routes/invoke.ts?raw
8409
+ var invoke_default$7 = "import { Hono } from 'hono';\n\nimport { client } from '../lib/client.ts';\nimport { log } from '../lib/logger.ts';\n\nconst route = new Hono();\n\nroute.post('/invoke', async (c) => {\n const body = await c.req.json().catch(() => ({}) as { key?: string });\n const key = typeof body.key === 'string' ? body.key : '';\n if (!key) return c.json({ error: 'missing key' }, 400);\n\n log('info', 'config get', { key });\n const result = await client.config.get(key);\n\n if (result.error) {\n log('warn', 'config miss', { key, code: result.error.code });\n return c.json({ key, value: null, code: result.error.code });\n }\n\n log('info', 'config hit', { key });\n return c.json({ key, value: result.data });\n});\n\nexport default route;\n";
8410
+ //#endregion
8411
+ //#region ../../starters/config/src/routes/schema-info.ts?raw
8412
+ var schema_info_default$7 = "import { Hono } from 'hono';\n\nconst route = new Hono();\n\nroute.get('/schema', (c) =>\n c.json({\n input: { type: 'object', properties: { key: { type: 'string' } }, required: ['key'] },\n output: { type: 'object', properties: { key: { type: 'string' }, value: {} } },\n }),\n);\n\nexport default route;\n";
8413
+ //#endregion
8414
+ //#region ../../starters/db/.claude/skills/stackbone/SKILL.md?raw
8415
+ var SKILL_default$6 = "---\nname: stackbone\ndescription: Use this skill when the user is working with the Stackbone CLI, the `@stackbone/sdk` package, the `agent.yaml` manifest, official templates, or connections to the Stackbone control plane. Triggers on commands like `stackbone init`, `stackbone dev`, `stackbone deploy`, on questions about organization state, exit codes, or environment variables, and on any reference to `@stackbone/*` imports in TypeScript code. Provides the command surface, output contract, exit codes, environment variables and idiomatic workflows.\n---\n\n# Stackbone — agent operating manual\n\nThis skill is regenerated by `stackbone init` / `stackbone link` and ships\ninside the CLI release 0.1.0-alpha.1. Edit the upstream template in\n`apps/cli/src/skills/templates.ts` if something here is wrong.\n\n## CLI surface\n\nThe CLI binary is `stackbone` (also reachable as `npx @stackbone/cli@alpha ...`). Run\n`stackbone docs cli` for the canonical command/flag reference, or\n`stackbone docs sdk` for the `@stackbone/sdk` overview — neither command does\nnetwork IO.\n\n## Output contract\n\nEvery command supports `--json` (or `STACKBONE_JSON=1`). Successful JSON\nresponses look like:\n\n```json\n{ \"schema_version\": 1, \"...\": \"...\" }\n```\n\nErrors look like:\n\n```json\n{\n \"schema_version\": 1,\n \"error\": { \"code\": \"auth\", \"message\": \"...\", \"suggestion\": \"Run \\`stackbone login\\`\" },\n \"exit_code\": 2\n}\n```\n\n`schema_version` only bumps on backward-incompatible shape changes. If you\nsee a different value, refuse to parse and ask the user to upgrade.\n\n## Exit codes\n\n- `0` ok\n- `1` generic\n- `2` auth (no session, expired, refused login)\n- `3` no project linked (no `.stackbone/project.json` in cwd)\n- `4` not found (organization, agent, deployment)\n- `5` permission denied\n\nBranch on these instead of parsing stack traces.\n\n## Environment variables\n\n- `STACKBONE_JSON=1` — same as `--json`\n- `STACKBONE_API_URL` — same as `--api-url <url>`\n- `STACKBONE_AUTO_LOGIN=0` — same as `--no-auto-login`\n- `STACKBONE_LOG_LEVEL` — `debug`, `info`, `warn`, `error` (logger goes\n to stderr, never pollutes the JSON payload on stdout)\n\n## State on disk\n\n- `<cwd>/.stackbone/project.json` — organization + agent linked to this folder.\n Created by `stackbone init` / `link`.\n- `~/.stackbone/credentials.json` (chmod 600) — sessions keyed by control\n plane URL.\n- `~/.stackbone/config.json` — global preferences (default API URL,\n telemetry opt-in).\n\nThe control plane this skill points at: `https://api.stackbone.ai`.\nActive organization slug: `template1-2`.\n\n## Common workflows\n\n### Authenticate\n\n```sh\nstackbone login # interactive (device-code flow)\nstackbone whoami # confirm session\nstackbone metadata --json # one-shot overview that replaces 5–7 explorer calls\n```\n\n### Start a new project\n\n```sh\nstackbone init # menu of templates (workflow / autonomous / RAG)\nstackbone dev # local emulator at http://localhost:4242\n```\n\n### Deploy\n\n```sh\nstackbone deploy # build + push + Fly Machines\n```\n\n## Anti-patterns\n\n- Do not hardcode organization IDs, credentials or control plane URLs into\n agent code — the SDK reads them from the environment at runtime.\n- Do not parse human output. Re-run the command with `--json` and use\n `JSON.parse`.\n- Do not call the control plane HTTP API directly from a creator's agent.\n Use `@stackbone/sdk` so credential rotation, observability and metering\n stay correct.\n- Do not commit `.stackbone/` or `~/.stackbone/` — the entries are in the\n project's `.gitignore` for a reason.\n\n## Where to learn more\n\n- `stackbone docs cli` — command reference\n- `stackbone docs sdk` — `@stackbone/sdk` modules\n- `stackbone docs agent-yaml` — manifest reference (Epic 3)\n- `stackbone docs templates` — official templates (Epic 5)\n- `stackbone docs connections` — data layer (Epic 6)\n";
8416
+ //#endregion
8417
+ //#region ../../starters/db/.gitignore?raw
8418
+ var _gitignore_raw_default$6 = "# >>> stackbone >>>\n.stackbone/\nnode_modules/\ndist/\n# <<< stackbone <<<\n";
8419
+ //#endregion
8420
+ //#region ../../starters/db/Dockerfile?raw
8421
+ var Dockerfile_raw_default$7 = "# Distroless Node 24 image. Multi-stage so the runtime stays minimal.\nFROM node:24-alpine AS deps\nWORKDIR /app\nCOPY package.json ./\nRUN npm install --omit=dev\n\nFROM gcr.io/distroless/nodejs24-debian12\nWORKDIR /app\nCOPY --from=deps /app/node_modules ./node_modules\nCOPY src ./src\nCOPY agent.yaml ./\nENV PORT=8080\nEXPOSE 8080\nCMD [\"src/index.ts\"]\n";
8422
+ //#endregion
8423
+ //#region ../../starters/db/README.md?raw
8424
+ var README_default$7 = "# starter-db\n\nDemo of `client.database` (Drizzle + Postgres) from `@stackbone/sdk`.\n\n## Layout\n\n```\nsrc/\n index.ts # Hono server, mounts routes, logs every request\n schema.ts # Drizzle table definitions\n lib/\n client.ts # Shared Stackbone client\n logger.ts # JSON line logger (captured by stackbone dev)\n routes/\n health.ts # GET /health\n schema-info.ts # GET /schema\n invoke.ts # POST /invoke -> insert example\n list-examples.ts # GET /examples -> list latest 20\n delete-example.ts # DELETE /examples/:id\n```\n\n## Run\n\n```sh\nnpm install\nstackbone dev\n```\n\nThen:\n\n```sh\ncurl http://127.0.0.1:8080/health\ncurl -X POST http://127.0.0.1:8080/invoke -d '{\"name\":\"luis\"}' -H 'content-type: application/json'\ncurl http://127.0.0.1:8080/examples\ncurl -X DELETE http://127.0.0.1:8080/examples/1\n```\n";
8425
+ //#endregion
8426
+ //#region ../../starters/db/agent.yaml?raw
8427
+ var agent_default$7 = "# Stackbone agent manifest. Schema: stackbone.ai/v1.\n# Run `stackbone docs agent-yaml` for the full reference.\n\napiVersion: stackbone.ai/v1\nname: starter-db\nruntime:\n engine: node\n entry: src/index.ts\ndatabase:\n schema: ./src/schema.ts\n migrations: ./.stackbone/migrations\ndev:\n autoMigrate: false\nrag:\n embeddingModel: openai/text-embedding-3-small\n autoMigrate: false\n";
8428
+ //#endregion
8429
+ //#region ../../starters/db/src/index.ts?raw
8430
+ var src_default$7 = "import { serve } from '@hono/node-server';\nimport { Hono } from 'hono';\n\nimport { log } from './lib/logger.ts';\nimport deleteExample from './routes/delete-example.ts';\nimport health from './routes/health.ts';\nimport invoke from './routes/invoke.ts';\nimport listExamples from './routes/list-examples.ts';\nimport schemaInfo from './routes/schema-info.ts';\n\nconst app = new Hono();\n\napp.use('*', async (c, next) => {\n const start = Date.now();\n await next();\n log('info', 'request', {\n method: c.req.method,\n path: c.req.path,\n status: c.res.status,\n durationMs: Date.now() - start,\n });\n});\n\napp.route('/', health);\napp.route('/', schemaInfo);\napp.route('/', invoke);\napp.route('/', listExamples);\napp.route('/', deleteExample);\n\nconst port = Number(process.env.PORT ?? 8080);\nserve({ fetch: app.fetch, port });\nlog('info', 'starter-db listening', { port });\n";
8431
+ //#endregion
8432
+ //#region ../../starters/db/src/lib/client.ts?raw
8433
+ var client_default$6 = "import { createClient } from '@stackbone/sdk';\n\n// Shared Stackbone client. Every route imports it from here so we open one\n// pool against STACKBONE_POSTGRES_URL and reuse the same datapath handle.\nexport const client = createClient({\n stackboneApiUrl: process.env.STACKBONE_DEV_API_URL ?? 'http://127.0.0.1:4242',\n});\n";
8434
+ //#endregion
8435
+ //#region ../../starters/db/src/lib/logger.ts?raw
8436
+ var logger_default$6 = "// Minimal JSON logger. Every line is captured by `stackbone dev` and surfaced\n// in Stackbone Studio. Keep one log per route entry/exit so traces are easy\n// to follow.\n\ntype Level = 'info' | 'warn' | 'error';\n\nexport function log(level: Level, msg: string, fields?: Record<string, unknown>): void {\n const record = { ts: new Date().toISOString(), level, msg, ...fields };\n const line = JSON.stringify(record);\n if (level === 'error') console.error(line);\n else console.log(line);\n}\n";
8437
+ //#endregion
8438
+ //#region ../../starters/db/src/routes/delete-example.ts?raw
8439
+ var delete_example_default = "import { eq } from '@stackbone/sdk/db';\nimport { Hono } from 'hono';\n\nimport { client } from '../lib/client.ts';\nimport { log } from '../lib/logger.ts';\nimport { examples } from '../schema.ts';\n\nconst route = new Hono();\n\nroute.delete('/examples/:id', async (c) => {\n const id = Number(c.req.param('id'));\n if (!Number.isInteger(id)) {\n log('warn', 'delete example: invalid id', { id: c.req.param('id') });\n return c.json({ error: 'invalid id' }, 400);\n }\n\n log('info', 'delete example', { id });\n const deleted = await client.database\n .delete(examples)\n .where(eq(examples.id, id))\n .returning();\n\n return c.json({ deleted });\n});\n\nexport default route;\n";
8440
+ //#endregion
8441
+ //#region ../../starters/db/src/routes/health.ts?raw
8442
+ var health_default$6 = "import { Hono } from 'hono';\n\nimport { log } from '../lib/logger.ts';\n\nconst route = new Hono();\n\nroute.get('/health', (c) => {\n log('info', 'health check');\n return c.json({ status: 'ok' });\n});\n\nexport default route;\n";
8443
+ //#endregion
8444
+ //#region ../../starters/db/src/routes/invoke.ts?raw
8445
+ var invoke_default$6 = "import { Hono } from 'hono';\n\nimport { client } from '../lib/client.ts';\nimport { log } from '../lib/logger.ts';\nimport { examples } from '../schema.ts';\n\nconst route = new Hono();\n\nroute.post('/invoke', async (c) => {\n const body = await c.req.json().catch(() => ({}) as { name?: string });\n const name = typeof body.name === 'string' ? body.name : 'anonymous';\n\n log('info', 'invoke received', { name });\n const [row] = await client.database.insert(examples).values({ name }).returning();\n log('info', 'invoke inserted', { id: row?.id, name });\n\n return c.json({ inserted: row });\n});\n\nexport default route;\n";
8446
+ //#endregion
8447
+ //#region ../../starters/db/src/routes/list-examples.ts?raw
8448
+ var list_examples_default = "import { desc } from '@stackbone/sdk/db';\nimport { Hono } from 'hono';\n\nimport { client } from '../lib/client.ts';\nimport { log } from '../lib/logger.ts';\nimport { examples } from '../schema.ts';\n\nconst route = new Hono();\n\nroute.get('/examples', async (c) => {\n const limit = Number(c.req.query('limit') ?? 20);\n log('info', 'list examples', { limit });\n\n const rows = await client.database\n .select()\n .from(examples)\n .orderBy(desc(examples.id))\n .limit(limit);\n\n return c.json({ rows });\n});\n\nexport default route;\n";
8449
+ //#endregion
8450
+ //#region ../../starters/db/src/routes/schema-info.ts?raw
8451
+ var schema_info_default$6 = "import { Hono } from 'hono';\n\nconst route = new Hono();\n\nroute.get('/schema', (c) =>\n c.json({\n input: {\n type: 'object',\n properties: { name: { type: 'string' } },\n required: ['name'],\n },\n output: {\n type: 'object',\n properties: {\n id: { type: 'number' },\n name: { type: 'string' },\n createdAt: { type: 'string' },\n },\n },\n }),\n);\n\nexport default route;\n";
8452
+ //#endregion
8453
+ //#region ../../starters/db/src/schema.ts?raw
8454
+ var schema_default$1 = "// Drizzle schema for template1.\n//\n// Tables declared here are managed by `stackbone db migrate up`. Edit this\n// file, then run `stackbone db migrate create <name>` to emit the next SQL\n// migration under `.stackbone/migrations/`. The first migration shipped with\n// the template (`0000_init.sql`) creates the `examples` table below so the\n// agent has something queryable on its first `stackbone dev` boot.\n//\n// Drizzle column helpers re-export through `@stackbone/sdk/db` — never import\n// from `drizzle-orm/pg-core` directly. The SDK barrel is the single contract\n// the platform supports.\nimport { pgTable, serial, text, timestamp } from '@stackbone/sdk/db';\n\nexport const examples = pgTable('examples', {\n id: serial('id').primaryKey(),\n name: text('name').notNull(),\n createdAt: timestamp('created_at').defaultNow().notNull(),\n});\n";
8455
+ //#endregion
8456
+ //#region ../../starters/hello-world/.stackbone/migrations/0000_init.sql?raw
8323
8457
  var _0000_init_default = "CREATE TABLE \"examples\" (\n \"id\" serial PRIMARY KEY NOT NULL,\n \"name\" text NOT NULL,\n \"created_at\" timestamp DEFAULT now() NOT NULL\n);\n";
8324
8458
  //#endregion
8325
- //#region src/templates/workflow/.stackbone/migrations/meta/0000_snapshot.json?raw
8459
+ //#region ../../starters/hello-world/.stackbone/migrations/meta/0000_snapshot.json?raw
8326
8460
  var _0000_snapshot_default = "{\n \"id\": \"00000000-0000-4000-8000-000000000001\",\n \"prevId\": \"00000000-0000-0000-0000-000000000000\",\n \"version\": \"7\",\n \"dialect\": \"postgresql\",\n \"tables\": {\n \"public.examples\": {\n \"name\": \"examples\",\n \"schema\": \"\",\n \"columns\": {\n \"id\": {\n \"name\": \"id\",\n \"type\": \"serial\",\n \"primaryKey\": true,\n \"notNull\": true\n },\n \"name\": {\n \"name\": \"name\",\n \"type\": \"text\",\n \"primaryKey\": false,\n \"notNull\": true\n },\n \"created_at\": {\n \"name\": \"created_at\",\n \"type\": \"timestamp\",\n \"primaryKey\": false,\n \"notNull\": true,\n \"default\": \"now()\"\n }\n },\n \"indexes\": {},\n \"foreignKeys\": {},\n \"compositePrimaryKeys\": {},\n \"uniqueConstraints\": {},\n \"policies\": {},\n \"checkConstraints\": {},\n \"isRLSEnabled\": false\n }\n },\n \"enums\": {},\n \"schemas\": {},\n \"sequences\": {},\n \"roles\": {},\n \"policies\": {},\n \"views\": {},\n \"_meta\": {\n \"columns\": {},\n \"schemas\": {},\n \"tables\": {}\n }\n}\n";
8327
8461
  //#endregion
8328
- //#region src/templates/workflow/.stackbone/migrations/meta/_journal.json?raw
8462
+ //#region ../../starters/hello-world/.stackbone/migrations/meta/_journal.json?raw
8329
8463
  var _journal_default = "{\n \"version\": \"7\",\n \"dialect\": \"postgresql\",\n \"entries\": [\n {\n \"idx\": 0,\n \"version\": \"7\",\n \"when\": 1778889700000,\n \"tag\": \"0000_init\",\n \"breakpoints\": true\n }\n ]\n}\n";
8330
8464
  //#endregion
8331
- //#region src/templates/workflow/Dockerfile?raw
8465
+ //#region ../../starters/hello-world/Dockerfile?raw
8466
+ var Dockerfile_raw_default$6 = "# Distroless Node 24 image. Multi-stage so the runtime stays minimal.\nFROM node:24-alpine AS deps\nWORKDIR /app\nCOPY package.json ./\nRUN npm install --omit=dev\n\nFROM gcr.io/distroless/nodejs24-debian12\nWORKDIR /app\nCOPY --from=deps /app/node_modules ./node_modules\nCOPY src ./src\nCOPY agent.yaml ./\nENV PORT=8080\nEXPOSE 8080\nCMD [\"src/index.ts\"]\n";
8467
+ //#endregion
8468
+ //#region ../../starters/hello-world/README.md?raw
8469
+ var README_default$6 = "# starter-hello-world\n\nStackbone agent scaffolded by `stackbone init`. The runtime contract is the\nHTTP surface in `src/index.ts`: `/invoke`, `/health`, `/schema`.\n\n## Develop\n\n```sh\npnpm install\npnpm dev # node --watch src/index.ts\n```\n\n## Deploy\n\n```sh\nstackbone deploy # builds the image and pushes it to Fly Machines\n```\n\nSee `stackbone docs sdk` for the SDK reference and `stackbone docs cli`\nfor the full command surface.\n";
8470
+ //#endregion
8471
+ //#region ../../starters/hello-world/agent.yaml?raw
8472
+ var agent_default$6 = "# Stackbone agent manifest. Schema: stackbone.ai/v1.\n# Run `stackbone docs agent-yaml` for the full reference.\n\napiVersion: stackbone.ai/v1\nname: starter-hello-world\nruntime:\n engine: node\n entry: src/index.ts\n";
8473
+ //#endregion
8474
+ //#region ../../starters/hello-world/src/index.ts?raw
8475
+ var src_default$6 = "import { serve } from '@hono/node-server';\nimport { Hono } from 'hono';\n\nconst app = new Hono();\n\napp.post('/invoke', async (c) => {\n const body = await c.req.json().catch(() => ({}));\n return c.json({ hello: body.who ?? 'world' });\n});\n\napp.get('/health', (c) => c.json({ status: 'ok' }));\n\napp.get('/schema', (c) =>\n c.json({\n input: { type: 'object', properties: { who: { type: 'string' } } },\n output: { type: 'object', properties: { hello: { type: 'string' } } },\n }),\n);\n\nconst port = Number(process.env.PORT ?? 8080);\nserve({ fetch: app.fetch, port });\nconsole.log(`hello-world agent listening on :${port}`);\n";
8476
+ //#endregion
8477
+ //#region ../../starters/hello-world/src/schema.ts?raw
8478
+ var schema_default = "// Drizzle schema for starter-hello-world.\n//\n// Tables declared here are managed by `stackbone db migrate up`. Edit this\n// file, then run `stackbone db migrate create <name>` to emit the next SQL\n// migration under `.stackbone/migrations/`. The first migration shipped with\n// the template (`0000_init.sql`) creates the `examples` table below so the\n// agent has something queryable on its first `stackbone dev` boot.\n//\n// Drizzle column helpers re-export through `@stackbone/sdk/db` — never import\n// from `drizzle-orm/pg-core` directly. The SDK barrel is the single contract\n// the platform supports.\nimport { pgTable, serial, text, timestamp } from '@stackbone/sdk/db';\n\nexport const examples = pgTable('examples', {\n id: serial('id').primaryKey(),\n name: text('name').notNull(),\n createdAt: timestamp('created_at').defaultNow().notNull(),\n});\n";
8479
+ //#endregion
8480
+ //#region ../../starters/hitl/.claude/skills/stackbone/SKILL.md?raw
8481
+ var SKILL_default$5 = "---\nname: stackbone\ndescription: Use this skill when the user is working with the Stackbone CLI, the `@stackbone/sdk` package, the `agent.yaml` manifest, official templates, or connections to the Stackbone control plane. Triggers on commands like `stackbone init`, `stackbone dev`, `stackbone deploy`, on questions about organization state, exit codes, or environment variables, and on any reference to `@stackbone/*` imports in TypeScript code. Provides the command surface, output contract, exit codes, environment variables and idiomatic workflows.\n---\n\n# Stackbone — agent operating manual\n\nThis skill is regenerated by `stackbone init` / `stackbone link` and ships\ninside the CLI release 0.1.0-alpha.1. Edit the upstream template in\n`apps/cli/src/skills/templates.ts` if something here is wrong.\n\n## CLI surface\n\nThe CLI binary is `stackbone` (also reachable as `npx @stackbone/cli@alpha ...`). Run\n`stackbone docs cli` for the canonical command/flag reference, or\n`stackbone docs sdk` for the `@stackbone/sdk` overview — neither command does\nnetwork IO.\n\n## Output contract\n\nEvery command supports `--json` (or `STACKBONE_JSON=1`). Successful JSON\nresponses look like:\n\n```json\n{ \"schema_version\": 1, \"...\": \"...\" }\n```\n\nErrors look like:\n\n```json\n{\n \"schema_version\": 1,\n \"error\": { \"code\": \"auth\", \"message\": \"...\", \"suggestion\": \"Run \\`stackbone login\\`\" },\n \"exit_code\": 2\n}\n```\n\n`schema_version` only bumps on backward-incompatible shape changes. If you\nsee a different value, refuse to parse and ask the user to upgrade.\n\n## Exit codes\n\n- `0` ok\n- `1` generic\n- `2` auth (no session, expired, refused login)\n- `3` no project linked (no `.stackbone/project.json` in cwd)\n- `4` not found (organization, agent, deployment)\n- `5` permission denied\n\nBranch on these instead of parsing stack traces.\n\n## Environment variables\n\n- `STACKBONE_JSON=1` — same as `--json`\n- `STACKBONE_API_URL` — same as `--api-url <url>`\n- `STACKBONE_AUTO_LOGIN=0` — same as `--no-auto-login`\n- `STACKBONE_LOG_LEVEL` — `debug`, `info`, `warn`, `error` (logger goes\n to stderr, never pollutes the JSON payload on stdout)\n\n## State on disk\n\n- `<cwd>/.stackbone/project.json` — organization + agent linked to this folder.\n Created by `stackbone init` / `link`.\n- `~/.stackbone/credentials.json` (chmod 600) — sessions keyed by control\n plane URL.\n- `~/.stackbone/config.json` — global preferences (default API URL,\n telemetry opt-in).\n\nThe control plane this skill points at: `https://api.stackbone.ai`.\nActive organization slug: `template1-2`.\n\n## Common workflows\n\n### Authenticate\n\n```sh\nstackbone login # interactive (device-code flow)\nstackbone whoami # confirm session\nstackbone metadata --json # one-shot overview that replaces 5–7 explorer calls\n```\n\n### Start a new project\n\n```sh\nstackbone init # menu of templates (workflow / autonomous / RAG)\nstackbone dev # local emulator at http://localhost:4242\n```\n\n### Deploy\n\n```sh\nstackbone deploy # build + push + Fly Machines\n```\n\n## Anti-patterns\n\n- Do not hardcode organization IDs, credentials or control plane URLs into\n agent code — the SDK reads them from the environment at runtime.\n- Do not parse human output. Re-run the command with `--json` and use\n `JSON.parse`.\n- Do not call the control plane HTTP API directly from a creator's agent.\n Use `@stackbone/sdk` so credential rotation, observability and metering\n stay correct.\n- Do not commit `.stackbone/` or `~/.stackbone/` — the entries are in the\n project's `.gitignore` for a reason.\n\n## Where to learn more\n\n- `stackbone docs cli` — command reference\n- `stackbone docs sdk` — `@stackbone/sdk` modules\n- `stackbone docs agent-yaml` — manifest reference (Epic 3)\n- `stackbone docs templates` — official templates (Epic 5)\n- `stackbone docs connections` — data layer (Epic 6)\n";
8482
+ //#endregion
8483
+ //#region ../../starters/hitl/.gitignore?raw
8484
+ var _gitignore_raw_default$5 = "# >>> stackbone >>>\n.stackbone/\nnode_modules/\ndist/\n# <<< stackbone <<<\n";
8485
+ //#endregion
8486
+ //#region ../../starters/hitl/Dockerfile?raw
8487
+ var Dockerfile_raw_default$5 = "# Distroless Node 24 image. Multi-stage so the runtime stays minimal.\nFROM node:24-alpine AS deps\nWORKDIR /app\nCOPY package.json ./\nRUN npm install --omit=dev\n\nFROM gcr.io/distroless/nodejs24-debian12\nWORKDIR /app\nCOPY --from=deps /app/node_modules ./node_modules\nCOPY src ./src\nCOPY agent.yaml ./\nENV PORT=8080\nEXPOSE 8080\nCMD [\"src/index.ts\"]\n";
8488
+ //#endregion
8489
+ //#region ../../starters/hitl/README.md?raw
8490
+ var README_default$5 = "# starter-hitl\n\nDemo of `client.approval` (Human-in-the-Loop) from `@stackbone/sdk`.\n\nThe agent opens an approval request on `POST /invoke`. The control plane\nnotifies the approver out-of-band, then POSTs the signed decision back to\n`POST /decide`. The agent verifies the signature with the workspace key and\nrecords the outcome.\n\n## Layout\n\n```\nsrc/\n index.ts # Hono server\n lib/\n client.ts # Shared Stackbone client\n logger.ts # JSON line logger\n routes/\n health.ts # GET /health\n schema-info.ts # GET /schema\n invoke.ts # POST /invoke -> open approval\n decide.ts # POST /decide -> signed decision callback\n get-approval.ts # GET /approvals/:id\n list-approvals.ts # GET /approvals -> ?status&topic\n cancel.ts # DELETE /approvals/:id -> cancel pending request\n```\n\n## Run\n\n```sh\nnpm install\nstackbone dev\n```\n\nThen:\n\n```sh\ncurl -X POST http://127.0.0.1:8080/invoke -H 'content-type: application/json' -d '{\n \"topic\": \"refund-request\",\n \"title\": \"Issue $50 refund\",\n \"payload\": {\"order_id\": \"ord_123\", \"amount\": 50}\n}'\n\ncurl http://127.0.0.1:8080/approvals\n```\n\nRequires `STACKBONE_APPROVAL_SIGNING_KEY` so `/decide` can verify the HMAC\nsignature the control plane attaches.\n";
8491
+ //#endregion
8492
+ //#region ../../starters/hitl/agent.yaml?raw
8493
+ var agent_default$5 = "# Stackbone agent manifest. Schema: stackbone.ai/v1.\n# Run `stackbone docs agent-yaml` for the full reference.\n\napiVersion: stackbone.ai/v1\nname: starter-hitl\nruntime:\n engine: node\n entry: src/index.ts\ndatabase:\n schema: ./src/schema.ts\n migrations: ./.stackbone/migrations\ndev:\n autoMigrate: false\nrag:\n embeddingModel: openai/text-embedding-3-small\n autoMigrate: false\n";
8494
+ //#endregion
8495
+ //#region ../../starters/hitl/src/index.ts?raw
8496
+ var src_default$5 = "import { serve } from '@hono/node-server';\nimport { Hono } from 'hono';\n\nimport { log } from './lib/logger.ts';\nimport cancel from './routes/cancel.ts';\nimport decide from './routes/decide.ts';\nimport getApproval from './routes/get-approval.ts';\nimport health from './routes/health.ts';\nimport invoke from './routes/invoke.ts';\nimport listApprovals from './routes/list-approvals.ts';\nimport schemaInfo from './routes/schema-info.ts';\n\nconst app = new Hono();\n\napp.use('*', async (c, next) => {\n const start = Date.now();\n await next();\n log('info', 'request', {\n method: c.req.method,\n path: c.req.path,\n status: c.res.status,\n durationMs: Date.now() - start,\n });\n});\n\napp.route('/', health);\napp.route('/', schemaInfo);\napp.route('/', invoke);\napp.route('/', decide);\napp.route('/', getApproval);\napp.route('/', listApprovals);\napp.route('/', cancel);\n\nconst port = Number(process.env.PORT ?? 8080);\nserve({ fetch: app.fetch, port });\nlog('info', 'starter-hitl listening', { port });\n";
8497
+ //#endregion
8498
+ //#region ../../starters/hitl/src/lib/client.ts?raw
8499
+ var client_default$5 = "import { createClient } from '@stackbone/sdk';\n\nexport const client = createClient({\n stackboneApiUrl: process.env.STACKBONE_DEV_API_URL ?? 'http://127.0.0.1:4242',\n});\n";
8500
+ //#endregion
8501
+ //#region ../../starters/hitl/src/lib/logger.ts?raw
8502
+ var logger_default$5 = "type Level = 'info' | 'warn' | 'error';\n\nexport function log(level: Level, msg: string, fields?: Record<string, unknown>): void {\n const line = JSON.stringify({ ts: new Date().toISOString(), level, msg, ...fields });\n if (level === 'error') console.error(line);\n else console.log(line);\n}\n";
8503
+ //#endregion
8504
+ //#region ../../starters/hitl/src/routes/cancel.ts?raw
8505
+ var cancel_default = "import { Hono } from 'hono';\n\nimport { client } from '../lib/client.ts';\nimport { log } from '../lib/logger.ts';\n\nconst route = new Hono();\n\nroute.delete('/approvals/:id', async (c) => {\n const id = c.req.param('id');\n const reason = c.req.query('reason') ?? undefined;\n log('info', 'cancel approval', { id, reason });\n\n const result = await client.approval.cancel(id, reason);\n if (result.error) {\n log('error', 'cancel failed', { id, code: result.error.code });\n return c.json({ error: result.error }, 502);\n }\n\n return c.json({ status: 'cancelled', id });\n});\n\nexport default route;\n";
8506
+ //#endregion
8507
+ //#region ../../starters/hitl/src/routes/decide.ts?raw
8508
+ var decide_default = "import { Hono } from 'hono';\n\nimport { client } from '../lib/client.ts';\nimport { log } from '../lib/logger.ts';\n\nconst route = new Hono();\n\n// Webhook the control plane POSTs to with the approver's decision.\n// `client.approval.verify` validates the HMAC signature using the workspace's\n// signing key (`STACKBONE_APPROVAL_SIGNING_KEY`).\nroute.post('/decide', async (c) => {\n log('info', 'decide received');\n\n const verified = await client.approval.verify(c.req.raw);\n if (verified.error) {\n log('warn', 'verify failed', { code: verified.error.code });\n return c.json({ error: verified.error }, 401);\n }\n\n log('info', 'decision', { status: verified.data.status });\n return c.json({ status: 'recorded', decision: verified.data });\n});\n\nexport default route;\n";
8509
+ //#endregion
8510
+ //#region ../../starters/hitl/src/routes/get-approval.ts?raw
8511
+ var get_approval_default = "import { Hono } from 'hono';\n\nimport { client } from '../lib/client.ts';\nimport { log } from '../lib/logger.ts';\n\nconst route = new Hono();\n\nroute.get('/approvals/:id', async (c) => {\n const id = c.req.param('id');\n log('info', 'get approval', { id });\n\n const result = await client.approval.get(id);\n if (result.error) {\n log('error', 'get failed', { id, code: result.error.code });\n return c.json({ error: result.error }, 502);\n }\n\n return c.json(result.data);\n});\n\nexport default route;\n";
8512
+ //#endregion
8513
+ //#region ../../starters/hitl/src/routes/health.ts?raw
8514
+ var health_default$5 = "import { Hono } from 'hono';\n\nconst route = new Hono();\nroute.get('/health', (c) => c.json({ status: 'ok' }));\nexport default route;\n";
8515
+ //#endregion
8516
+ //#region ../../starters/hitl/src/routes/invoke.ts?raw
8517
+ var invoke_default$5 = "import { Hono } from 'hono';\n\nimport { client } from '../lib/client.ts';\nimport { log } from '../lib/logger.ts';\n\nconst route = new Hono();\n\n// Opens a HITL approval request. The agent pauses on `/approvals/:id` (or via\n// the SDK's poll/wait helpers); the control plane delivers the decision to\n// the `/decide` route below.\nroute.post('/invoke', async (c) => {\n const body = await c.req.json().catch(\n () =>\n ({}) as {\n topic?: string;\n payload?: unknown;\n title?: string;\n description?: string;\n },\n );\n\n const topic = typeof body.topic === 'string' ? body.topic : 'demo-approval';\n log('info', 'request approval', { topic });\n\n const result = await client.approval.request({\n topic,\n payload: body.payload ?? { reason: 'starter-hitl demo' },\n title: body.title ?? 'Demo approval',\n description: body.description,\n onDecide: '/decide',\n });\n\n if (result.error) {\n log('error', 'request failed', { code: result.error.code });\n return c.json({ error: result.error }, 502);\n }\n\n log('info', 'request done', { approvalId: result.data.approvalId });\n return c.json(result.data);\n});\n\nexport default route;\n";
8518
+ //#endregion
8519
+ //#region ../../starters/hitl/src/routes/list-approvals.ts?raw
8520
+ var list_approvals_default = "import { Hono } from 'hono';\n\nimport { client } from '../lib/client.ts';\nimport { log } from '../lib/logger.ts';\n\nconst route = new Hono();\n\nroute.get('/approvals', async (c) => {\n const status = c.req.query('status');\n const topic = c.req.query('topic');\n\n log('info', 'list approvals', { status, topic });\n\n const result = await client.approval.list({\n status: status as never,\n topic,\n limit: 50,\n });\n\n if (result.error) {\n log('error', 'list failed', { code: result.error.code });\n return c.json({ error: result.error }, 502);\n }\n\n log('info', 'list done', { count: result.data.items.length });\n return c.json(result.data);\n});\n\nexport default route;\n";
8521
+ //#endregion
8522
+ //#region ../../starters/hitl/src/routes/schema-info.ts?raw
8523
+ var schema_info_default$5 = "import { Hono } from 'hono';\n\nconst route = new Hono();\n\nroute.get('/schema', (c) =>\n c.json({\n input: {\n type: 'object',\n properties: {\n topic: { type: 'string' },\n payload: {},\n },\n required: ['topic'],\n },\n output: {\n type: 'object',\n properties: {\n approvalId: { type: 'string' },\n status: { type: 'string' },\n },\n },\n }),\n);\n\nexport default route;\n";
8524
+ //#endregion
8525
+ //#region ../../starters/prompts/.claude/skills/stackbone/SKILL.md?raw
8526
+ var SKILL_default$4 = "---\nname: stackbone\ndescription: Use this skill when the user is working with the Stackbone CLI, the `@stackbone/sdk` package, the `agent.yaml` manifest, official templates, or connections to the Stackbone control plane. Triggers on commands like `stackbone init`, `stackbone dev`, `stackbone deploy`, on questions about organization state, exit codes, or environment variables, and on any reference to `@stackbone/*` imports in TypeScript code. Provides the command surface, output contract, exit codes, environment variables and idiomatic workflows.\n---\n\n# Stackbone — agent operating manual\n\nThis skill is regenerated by `stackbone init` / `stackbone link` and ships\ninside the CLI release 0.1.0-alpha.1. Edit the upstream template in\n`apps/cli/src/skills/templates.ts` if something here is wrong.\n\n## CLI surface\n\nThe CLI binary is `stackbone` (also reachable as `npx @stackbone/cli@alpha ...`). Run\n`stackbone docs cli` for the canonical command/flag reference, or\n`stackbone docs sdk` for the `@stackbone/sdk` overview — neither command does\nnetwork IO.\n\n## Output contract\n\nEvery command supports `--json` (or `STACKBONE_JSON=1`). Successful JSON\nresponses look like:\n\n```json\n{ \"schema_version\": 1, \"...\": \"...\" }\n```\n\nErrors look like:\n\n```json\n{\n \"schema_version\": 1,\n \"error\": { \"code\": \"auth\", \"message\": \"...\", \"suggestion\": \"Run \\`stackbone login\\`\" },\n \"exit_code\": 2\n}\n```\n\n`schema_version` only bumps on backward-incompatible shape changes. If you\nsee a different value, refuse to parse and ask the user to upgrade.\n\n## Exit codes\n\n- `0` ok\n- `1` generic\n- `2` auth (no session, expired, refused login)\n- `3` no project linked (no `.stackbone/project.json` in cwd)\n- `4` not found (organization, agent, deployment)\n- `5` permission denied\n\nBranch on these instead of parsing stack traces.\n\n## Environment variables\n\n- `STACKBONE_JSON=1` — same as `--json`\n- `STACKBONE_API_URL` — same as `--api-url <url>`\n- `STACKBONE_AUTO_LOGIN=0` — same as `--no-auto-login`\n- `STACKBONE_LOG_LEVEL` — `debug`, `info`, `warn`, `error` (logger goes\n to stderr, never pollutes the JSON payload on stdout)\n\n## State on disk\n\n- `<cwd>/.stackbone/project.json` — organization + agent linked to this folder.\n Created by `stackbone init` / `link`.\n- `~/.stackbone/credentials.json` (chmod 600) — sessions keyed by control\n plane URL.\n- `~/.stackbone/config.json` — global preferences (default API URL,\n telemetry opt-in).\n\nThe control plane this skill points at: `https://api.stackbone.ai`.\nActive organization slug: `template1-2`.\n\n## Common workflows\n\n### Authenticate\n\n```sh\nstackbone login # interactive (device-code flow)\nstackbone whoami # confirm session\nstackbone metadata --json # one-shot overview that replaces 5–7 explorer calls\n```\n\n### Start a new project\n\n```sh\nstackbone init # menu of templates (workflow / autonomous / RAG)\nstackbone dev # local emulator at http://localhost:4242\n```\n\n### Deploy\n\n```sh\nstackbone deploy # build + push + Fly Machines\n```\n\n## Anti-patterns\n\n- Do not hardcode organization IDs, credentials or control plane URLs into\n agent code — the SDK reads them from the environment at runtime.\n- Do not parse human output. Re-run the command with `--json` and use\n `JSON.parse`.\n- Do not call the control plane HTTP API directly from a creator's agent.\n Use `@stackbone/sdk` so credential rotation, observability and metering\n stay correct.\n- Do not commit `.stackbone/` or `~/.stackbone/` — the entries are in the\n project's `.gitignore` for a reason.\n\n## Where to learn more\n\n- `stackbone docs cli` — command reference\n- `stackbone docs sdk` — `@stackbone/sdk` modules\n- `stackbone docs agent-yaml` — manifest reference (Epic 3)\n- `stackbone docs templates` — official templates (Epic 5)\n- `stackbone docs connections` — data layer (Epic 6)\n";
8527
+ //#endregion
8528
+ //#region ../../starters/prompts/.gitignore?raw
8529
+ var _gitignore_raw_default$4 = "# >>> stackbone >>>\n.stackbone/\nnode_modules/\ndist/\n# <<< stackbone <<<\n";
8530
+ //#endregion
8531
+ //#region ../../starters/prompts/Dockerfile?raw
8532
+ var Dockerfile_raw_default$4 = "# Distroless Node 24 image. Multi-stage so the runtime stays minimal.\nFROM node:24-alpine AS deps\nWORKDIR /app\nCOPY package.json ./\nRUN npm install --omit=dev\n\nFROM gcr.io/distroless/nodejs24-debian12\nWORKDIR /app\nCOPY --from=deps /app/node_modules ./node_modules\nCOPY src ./src\nCOPY agent.yaml ./\nENV PORT=8080\nEXPOSE 8080\nCMD [\"src/index.ts\"]\n";
8533
+ //#endregion
8534
+ //#region ../../starters/prompts/README.md?raw
8535
+ var README_default$4 = "# starter-prompts\n\nDemo of `client.prompts` (managed prompts with Mustache-style `{{var}}`\nsubstitution, versioned in the control plane) from `@stackbone/sdk`.\n\n## Layout\n\n```\nsrc/\n index.ts # Hono server\n lib/\n client.ts # Shared Stackbone client\n logger.ts # JSON line logger\n routes/\n health.ts # GET /health\n schema-info.ts # GET /schema\n invoke.ts # POST /invoke -> compile a prompt with variables\n list-prompts.ts # GET /prompts -> latest version of each prompt\n get-prompt.ts # GET /prompts/:name -> single prompt (?version=N)\n create-prompt.ts # POST /prompts -> register a new prompt\n```\n\n## Run\n\n```sh\nnpm install\nstackbone dev\n```\n\nThen:\n\n```sh\ncurl -X POST http://127.0.0.1:8080/prompts -H 'content-type: application/json' -d '{\n \"name\": \"greeting\",\n \"template\": \"Hello {{user}}, welcome to {{product}}.\"\n}'\n\ncurl -X POST http://127.0.0.1:8080/invoke -H 'content-type: application/json' -d '{\n \"name\": \"greeting\",\n \"variables\": {\"user\": \"Luis\", \"product\": \"Stackbone\"}\n}'\n\ncurl http://127.0.0.1:8080/prompts\n```\n\nNote: the `client.prompts` facade is a scaffolding placeholder in this SDK\nversion — every call returns `not_implemented` until the control plane lands.\n";
8536
+ //#endregion
8537
+ //#region ../../starters/prompts/agent.yaml?raw
8538
+ var agent_default$4 = "# Stackbone agent manifest. Schema: stackbone.ai/v1.\n# Run `stackbone docs agent-yaml` for the full reference.\n\napiVersion: stackbone.ai/v1\nname: starter-prompts\nruntime:\n engine: node\n entry: src/index.ts\ndatabase:\n schema: ./src/schema.ts\n migrations: ./.stackbone/migrations\ndev:\n autoMigrate: false\nrag:\n embeddingModel: openai/text-embedding-3-small\n autoMigrate: false\n";
8539
+ //#endregion
8540
+ //#region ../../starters/prompts/src/index.ts?raw
8541
+ var src_default$4 = "import { serve } from '@hono/node-server';\nimport { Hono } from 'hono';\n\nimport { log } from './lib/logger.ts';\nimport createPrompt from './routes/create-prompt.ts';\nimport getPrompt from './routes/get-prompt.ts';\nimport health from './routes/health.ts';\nimport invoke from './routes/invoke.ts';\nimport listPrompts from './routes/list-prompts.ts';\nimport schemaInfo from './routes/schema-info.ts';\n\nconst app = new Hono();\n\napp.use('*', async (c, next) => {\n const start = Date.now();\n await next();\n log('info', 'request', {\n method: c.req.method,\n path: c.req.path,\n status: c.res.status,\n durationMs: Date.now() - start,\n });\n});\n\napp.route('/', health);\napp.route('/', schemaInfo);\napp.route('/', invoke);\napp.route('/', listPrompts);\napp.route('/', getPrompt);\napp.route('/', createPrompt);\n\nconst port = Number(process.env.PORT ?? 8080);\nserve({ fetch: app.fetch, port });\nlog('info', 'starter-prompts listening', { port });\n";
8542
+ //#endregion
8543
+ //#region ../../starters/prompts/src/lib/client.ts?raw
8544
+ var client_default$4 = "import { createClient } from '@stackbone/sdk';\n\nexport const client = createClient({\n stackboneApiUrl: process.env.STACKBONE_DEV_API_URL ?? 'http://127.0.0.1:4242',\n});\n";
8545
+ //#endregion
8546
+ //#region ../../starters/prompts/src/lib/logger.ts?raw
8547
+ var logger_default$4 = "type Level = 'info' | 'warn' | 'error';\n\nexport function log(level: Level, msg: string, fields?: Record<string, unknown>): void {\n const line = JSON.stringify({ ts: new Date().toISOString(), level, msg, ...fields });\n if (level === 'error') console.error(line);\n else console.log(line);\n}\n";
8548
+ //#endregion
8549
+ //#region ../../starters/prompts/src/routes/create-prompt.ts?raw
8550
+ var create_prompt_default = "import { Hono } from 'hono';\n\nimport { client } from '../lib/client.ts';\nimport { log } from '../lib/logger.ts';\n\nconst route = new Hono();\n\nroute.post('/prompts', async (c) => {\n const body = await c.req.json().catch(\n () =>\n ({}) as {\n name?: string;\n template?: string;\n metadata?: Record<string, unknown>;\n },\n );\n\n if (!body.name || !body.template) {\n return c.json({ error: 'missing name or template' }, 400);\n }\n\n log('info', 'create prompt', { name: body.name });\n\n const result = await client.prompts.create({\n name: body.name,\n template: body.template,\n metadata: body.metadata,\n });\n\n if (result.error) {\n log('error', 'create failed', { code: result.error.code });\n return c.json({ error: result.error }, 502);\n }\n\n log('info', 'create done', { name: result.data.name, version: result.data.version });\n return c.json(result.data);\n});\n\nexport default route;\n";
8551
+ //#endregion
8552
+ //#region ../../starters/prompts/src/routes/get-prompt.ts?raw
8553
+ var get_prompt_default = "import { Hono } from 'hono';\n\nimport { client } from '../lib/client.ts';\nimport { log } from '../lib/logger.ts';\n\nconst route = new Hono();\n\nroute.get('/prompts/:name', async (c) => {\n const name = c.req.param('name');\n const version = c.req.query('version') ? Number(c.req.query('version')) : undefined;\n\n log('info', 'fetch prompt', { name, version });\n\n const result = await client.prompts.get(name, { version });\n if (result.error) {\n log('error', 'fetch failed', { name, code: result.error.code });\n return c.json({ error: result.error }, 502);\n }\n\n return c.json(result.data);\n});\n\nexport default route;\n";
8554
+ //#endregion
8555
+ //#region ../../starters/prompts/src/routes/health.ts?raw
8556
+ var health_default$4 = "import { Hono } from 'hono';\n\nconst route = new Hono();\nroute.get('/health', (c) => c.json({ status: 'ok' }));\nexport default route;\n";
8557
+ //#endregion
8558
+ //#region ../../starters/prompts/src/routes/invoke.ts?raw
8559
+ var invoke_default$4 = "import { Hono } from 'hono';\n\nimport { client } from '../lib/client.ts';\nimport { log } from '../lib/logger.ts';\n\nconst route = new Hono();\n\n// Compiles a managed prompt with `{{var}}` substitution and returns the\n// resulting string. The agent typically passes this to `client.ai`.\nroute.post('/invoke', async (c) => {\n const body = await c.req.json().catch(\n () => ({}) as { name?: string; variables?: Record<string, unknown> },\n );\n const name = typeof body.name === 'string' ? body.name : '';\n if (!name) return c.json({ error: 'missing name' }, 400);\n\n log('info', 'compile', { name });\n\n const result = await client.prompts.compile(name, body.variables ?? {});\n if (result.error) {\n log('error', 'compile failed', { name, code: result.error.code });\n return c.json({ error: result.error }, 502);\n }\n\n log('info', 'compile done', { name, length: result.data.length });\n return c.json({ name, compiled: result.data });\n});\n\nexport default route;\n";
8560
+ //#endregion
8561
+ //#region ../../starters/prompts/src/routes/list-prompts.ts?raw
8562
+ var list_prompts_default = "import { Hono } from 'hono';\n\nimport { client } from '../lib/client.ts';\nimport { log } from '../lib/logger.ts';\n\nconst route = new Hono();\n\nroute.get('/prompts', async (c) => {\n log('info', 'list prompts');\n\n const result = await client.prompts.list({ limit: 50 });\n if (result.error) {\n log('error', 'list failed', { code: result.error.code });\n return c.json({ error: result.error }, 502);\n }\n\n log('info', 'list done', { count: result.data.items.length });\n return c.json(result.data);\n});\n\nexport default route;\n";
8563
+ //#endregion
8564
+ //#region ../../starters/prompts/src/routes/schema-info.ts?raw
8565
+ var schema_info_default$4 = "import { Hono } from 'hono';\n\nconst route = new Hono();\n\nroute.get('/schema', (c) =>\n c.json({\n input: {\n type: 'object',\n properties: {\n name: { type: 'string' },\n variables: { type: 'object' },\n },\n required: ['name'],\n },\n output: { type: 'object', properties: { compiled: { type: 'string' } } },\n }),\n);\n\nexport default route;\n";
8566
+ //#endregion
8567
+ //#region ../../starters/queues/.claude/skills/stackbone/SKILL.md?raw
8568
+ var SKILL_default$3 = "---\nname: stackbone\ndescription: Use this skill when the user is working with the Stackbone CLI, the `@stackbone/sdk` package, the `agent.yaml` manifest, official templates, or connections to the Stackbone control plane. Triggers on commands like `stackbone init`, `stackbone dev`, `stackbone deploy`, on questions about organization state, exit codes, or environment variables, and on any reference to `@stackbone/*` imports in TypeScript code. Provides the command surface, output contract, exit codes, environment variables and idiomatic workflows.\n---\n\n# Stackbone — agent operating manual\n\nThis skill is regenerated by `stackbone init` / `stackbone link` and ships\ninside the CLI release 0.1.0-alpha.1. Edit the upstream template in\n`apps/cli/src/skills/templates.ts` if something here is wrong.\n\n## CLI surface\n\nThe CLI binary is `stackbone` (also reachable as `npx @stackbone/cli@alpha ...`). Run\n`stackbone docs cli` for the canonical command/flag reference, or\n`stackbone docs sdk` for the `@stackbone/sdk` overview — neither command does\nnetwork IO.\n\n## Output contract\n\nEvery command supports `--json` (or `STACKBONE_JSON=1`). Successful JSON\nresponses look like:\n\n```json\n{ \"schema_version\": 1, \"...\": \"...\" }\n```\n\nErrors look like:\n\n```json\n{\n \"schema_version\": 1,\n \"error\": { \"code\": \"auth\", \"message\": \"...\", \"suggestion\": \"Run \\`stackbone login\\`\" },\n \"exit_code\": 2\n}\n```\n\n`schema_version` only bumps on backward-incompatible shape changes. If you\nsee a different value, refuse to parse and ask the user to upgrade.\n\n## Exit codes\n\n- `0` ok\n- `1` generic\n- `2` auth (no session, expired, refused login)\n- `3` no project linked (no `.stackbone/project.json` in cwd)\n- `4` not found (organization, agent, deployment)\n- `5` permission denied\n\nBranch on these instead of parsing stack traces.\n\n## Environment variables\n\n- `STACKBONE_JSON=1` — same as `--json`\n- `STACKBONE_API_URL` — same as `--api-url <url>`\n- `STACKBONE_AUTO_LOGIN=0` — same as `--no-auto-login`\n- `STACKBONE_LOG_LEVEL` — `debug`, `info`, `warn`, `error` (logger goes\n to stderr, never pollutes the JSON payload on stdout)\n\n## State on disk\n\n- `<cwd>/.stackbone/project.json` — organization + agent linked to this folder.\n Created by `stackbone init` / `link`.\n- `~/.stackbone/credentials.json` (chmod 600) — sessions keyed by control\n plane URL.\n- `~/.stackbone/config.json` — global preferences (default API URL,\n telemetry opt-in).\n\nThe control plane this skill points at: `https://api.stackbone.ai`.\nActive organization slug: `template1-2`.\n\n## Common workflows\n\n### Authenticate\n\n```sh\nstackbone login # interactive (device-code flow)\nstackbone whoami # confirm session\nstackbone metadata --json # one-shot overview that replaces 5–7 explorer calls\n```\n\n### Start a new project\n\n```sh\nstackbone init # menu of templates (workflow / autonomous / RAG)\nstackbone dev # local emulator at http://localhost:4242\n```\n\n### Deploy\n\n```sh\nstackbone deploy # build + push + Fly Machines\n```\n\n## Anti-patterns\n\n- Do not hardcode organization IDs, credentials or control plane URLs into\n agent code — the SDK reads them from the environment at runtime.\n- Do not parse human output. Re-run the command with `--json` and use\n `JSON.parse`.\n- Do not call the control plane HTTP API directly from a creator's agent.\n Use `@stackbone/sdk` so credential rotation, observability and metering\n stay correct.\n- Do not commit `.stackbone/` or `~/.stackbone/` — the entries are in the\n project's `.gitignore` for a reason.\n\n## Where to learn more\n\n- `stackbone docs cli` — command reference\n- `stackbone docs sdk` — `@stackbone/sdk` modules\n- `stackbone docs agent-yaml` — manifest reference (Epic 3)\n- `stackbone docs templates` — official templates (Epic 5)\n- `stackbone docs connections` — data layer (Epic 6)\n";
8569
+ //#endregion
8570
+ //#region ../../starters/queues/.gitignore?raw
8571
+ var _gitignore_raw_default$3 = "# >>> stackbone >>>\n.stackbone/\nnode_modules/\ndist/\n# <<< stackbone <<<\n";
8572
+ //#endregion
8573
+ //#region ../../starters/queues/Dockerfile?raw
8574
+ var Dockerfile_raw_default$3 = "# Distroless Node 24 image. Multi-stage so the runtime stays minimal.\nFROM node:24-alpine AS deps\nWORKDIR /app\nCOPY package.json ./\nRUN npm install --omit=dev\n\nFROM gcr.io/distroless/nodejs24-debian12\nWORKDIR /app\nCOPY --from=deps /app/node_modules ./node_modules\nCOPY src ./src\nCOPY agent.yaml ./\nENV PORT=8080\nEXPOSE 8080\nCMD [\"src/index.ts\"]\n";
8575
+ //#endregion
8576
+ //#region ../../starters/queues/README.md?raw
8577
+ var README_default$3 = "# starter-queues\n\nDemo of `client.queues` (QStash publisher + signed webhook receiver) from\n`@stackbone/sdk`.\n\n## Layout\n\n```\nsrc/\n index.ts # Hono server\n lib/\n client.ts # Shared Stackbone client\n logger.ts # JSON line logger\n routes/\n health.ts # GET /health\n schema-info.ts # GET /schema\n invoke.ts # POST /invoke -> publish to own /receive\n publish.ts # POST /publish -> publish to arbitrary url\n receive.ts # POST /receive -> queue webhook target\n```\n\n## Run\n\n```sh\nnpm install\nstackbone dev\n```\n\nThen:\n\n```sh\ncurl -X POST http://127.0.0.1:8080/publish -H 'content-type: application/json' \\\n -d '{\"url\":\"https://httpbin.org/post\",\"body\":{\"hello\":\"world\"}}'\n\ncurl -X POST http://127.0.0.1:8080/invoke -H 'content-type: application/json' \\\n -d '{\"payload\":{\"hello\":\"from invoke\"}}'\n```\n\nRequires `QSTASH_TOKEN` (and `QSTASH_*_SIGNING_KEY` if you want signature\nverification on `/receive`).\n";
8578
+ //#endregion
8579
+ //#region ../../starters/queues/agent.yaml?raw
8580
+ var agent_default$3 = "# Stackbone agent manifest. Schema: stackbone.ai/v1.\n# Run `stackbone docs agent-yaml` for the full reference.\n\napiVersion: stackbone.ai/v1\nname: starter-queues\nruntime:\n engine: node\n entry: src/index.ts\ndatabase:\n schema: ./src/schema.ts\n migrations: ./.stackbone/migrations\ndev:\n autoMigrate: false\nrag:\n embeddingModel: openai/text-embedding-3-small\n autoMigrate: false\n";
8581
+ //#endregion
8582
+ //#region ../../starters/queues/src/index.ts?raw
8583
+ var src_default$3 = "import { serve } from '@hono/node-server';\nimport { Hono } from 'hono';\n\nimport { log } from './lib/logger.ts';\nimport health from './routes/health.ts';\nimport invoke from './routes/invoke.ts';\nimport publish from './routes/publish.ts';\nimport receive from './routes/receive.ts';\nimport schemaInfo from './routes/schema-info.ts';\n\nconst app = new Hono();\n\napp.use('*', async (c, next) => {\n const start = Date.now();\n await next();\n log('info', 'request', {\n method: c.req.method,\n path: c.req.path,\n status: c.res.status,\n durationMs: Date.now() - start,\n });\n});\n\napp.route('/', health);\napp.route('/', schemaInfo);\napp.route('/', invoke);\napp.route('/', publish);\napp.route('/', receive);\n\nconst port = Number(process.env.PORT ?? 8080);\nserve({ fetch: app.fetch, port });\nlog('info', 'starter-queues listening', { port });\n";
8584
+ //#endregion
8585
+ //#region ../../starters/queues/src/lib/client.ts?raw
8586
+ var client_default$3 = "import { createClient } from '@stackbone/sdk';\n\nexport const client = createClient({\n stackboneApiUrl: process.env.STACKBONE_DEV_API_URL ?? 'http://127.0.0.1:4242',\n});\n";
8587
+ //#endregion
8588
+ //#region ../../starters/queues/src/lib/logger.ts?raw
8589
+ var logger_default$3 = "type Level = 'info' | 'warn' | 'error';\n\nexport function log(level: Level, msg: string, fields?: Record<string, unknown>): void {\n const line = JSON.stringify({ ts: new Date().toISOString(), level, msg, ...fields });\n if (level === 'error') console.error(line);\n else console.log(line);\n}\n";
8590
+ //#endregion
8591
+ //#region ../../starters/queues/src/routes/health.ts?raw
8592
+ var health_default$3 = "import { Hono } from 'hono';\n\nconst route = new Hono();\nroute.get('/health', (c) => c.json({ status: 'ok' }));\nexport default route;\n";
8593
+ //#endregion
8594
+ //#region ../../starters/queues/src/routes/invoke.ts?raw
8595
+ var invoke_default$3 = "import { Hono } from 'hono';\n\nimport { client } from '../lib/client.ts';\nimport { log } from '../lib/logger.ts';\n\nconst route = new Hono();\n\n// Convenience entrypoint: publishes a message back to this same agent's\n// `/receive` route, so the round-trip is visible end-to-end.\nroute.post('/invoke', async (c) => {\n const body = await c.req.json().catch(() => ({}) as { selfUrl?: string; payload?: unknown });\n const selfUrl = body.selfUrl ?? 'http://127.0.0.1:8080/receive';\n\n log('info', 'invoke -> queue', { selfUrl });\n\n const result = await client.queues.publish({\n url: selfUrl,\n body: body.payload ?? { hello: 'from invoke' },\n });\n\n if (result.error) {\n log('error', 'invoke publish failed', { code: result.error.code });\n return c.json({ error: result.error }, 502);\n }\n\n return c.json(result.data);\n});\n\nexport default route;\n";
8596
+ //#endregion
8597
+ //#region ../../starters/queues/src/routes/publish.ts?raw
8598
+ var publish_default = "import { Hono } from 'hono';\n\nimport { client } from '../lib/client.ts';\nimport { log } from '../lib/logger.ts';\n\nconst route = new Hono();\n\nroute.post('/publish', async (c) => {\n const body = await c.req.json().catch(\n () =>\n ({}) as {\n url?: string;\n body?: unknown;\n retries?: number;\n delay?: number;\n },\n );\n\n const url = typeof body.url === 'string' ? body.url : '';\n if (!url) return c.json({ error: 'missing url' }, 400);\n\n log('info', 'publish', { url, retries: body.retries, delay: body.delay });\n\n const result = await client.queues.publish({\n url,\n body: body.body ?? {},\n retries: body.retries,\n delay: body.delay,\n });\n\n if (result.error) {\n log('error', 'publish failed', { code: result.error.code });\n return c.json({ error: result.error }, 502);\n }\n\n log('info', 'publish done', { messageId: result.data.messageId });\n return c.json(result.data);\n});\n\nexport default route;\n";
8599
+ //#endregion
8600
+ //#region ../../starters/queues/src/routes/receive.ts?raw
8601
+ var receive_default = "import { Hono } from 'hono';\n\nimport { log } from '../lib/logger.ts';\n\nconst route = new Hono();\n\n// Webhook endpoint that the queue calls back into. In production QStash sends\n// the `Upstash-Signature` header and the SDK exposes a verifier — for the\n// template we just log the body so you can observe the round-trip from\n// `/publish` -> queue -> `/receive`.\nroute.post('/receive', async (c) => {\n const signature = c.req.header('upstash-signature');\n const body = await c.req.json().catch(() => null);\n\n log('info', 'receive', {\n hasSignature: Boolean(signature),\n bodySize: body ? JSON.stringify(body).length : 0,\n });\n\n return c.json({ status: 'received' });\n});\n\nexport default route;\n";
8602
+ //#endregion
8603
+ //#region ../../starters/queues/src/routes/schema-info.ts?raw
8604
+ var schema_info_default$3 = "import { Hono } from 'hono';\n\nconst route = new Hono();\n\nroute.get('/schema', (c) =>\n c.json({\n input: {\n type: 'object',\n properties: { url: { type: 'string' }, body: { type: 'object' } },\n required: ['url'],\n },\n output: {\n type: 'object',\n properties: { messageId: { type: 'string' } },\n },\n }),\n);\n\nexport default route;\n";
8605
+ //#endregion
8606
+ //#region ../../starters/rag/.claude/skills/stackbone/SKILL.md?raw
8607
+ var SKILL_default$2 = "---\nname: stackbone\ndescription: Use this skill when the user is working with the Stackbone CLI, the `@stackbone/sdk` package, the `agent.yaml` manifest, official templates, or connections to the Stackbone control plane. Triggers on commands like `stackbone init`, `stackbone dev`, `stackbone deploy`, on questions about organization state, exit codes, or environment variables, and on any reference to `@stackbone/*` imports in TypeScript code. Provides the command surface, output contract, exit codes, environment variables and idiomatic workflows.\n---\n\n# Stackbone — agent operating manual\n\nThis skill is regenerated by `stackbone init` / `stackbone link` and ships\ninside the CLI release 0.1.0-alpha.1. Edit the upstream template in\n`apps/cli/src/skills/templates.ts` if something here is wrong.\n\n## CLI surface\n\nThe CLI binary is `stackbone` (also reachable as `npx @stackbone/cli@alpha ...`). Run\n`stackbone docs cli` for the canonical command/flag reference, or\n`stackbone docs sdk` for the `@stackbone/sdk` overview — neither command does\nnetwork IO.\n\n## Output contract\n\nEvery command supports `--json` (or `STACKBONE_JSON=1`). Successful JSON\nresponses look like:\n\n```json\n{ \"schema_version\": 1, \"...\": \"...\" }\n```\n\nErrors look like:\n\n```json\n{\n \"schema_version\": 1,\n \"error\": { \"code\": \"auth\", \"message\": \"...\", \"suggestion\": \"Run \\`stackbone login\\`\" },\n \"exit_code\": 2\n}\n```\n\n`schema_version` only bumps on backward-incompatible shape changes. If you\nsee a different value, refuse to parse and ask the user to upgrade.\n\n## Exit codes\n\n- `0` ok\n- `1` generic\n- `2` auth (no session, expired, refused login)\n- `3` no project linked (no `.stackbone/project.json` in cwd)\n- `4` not found (organization, agent, deployment)\n- `5` permission denied\n\nBranch on these instead of parsing stack traces.\n\n## Environment variables\n\n- `STACKBONE_JSON=1` — same as `--json`\n- `STACKBONE_API_URL` — same as `--api-url <url>`\n- `STACKBONE_AUTO_LOGIN=0` — same as `--no-auto-login`\n- `STACKBONE_LOG_LEVEL` — `debug`, `info`, `warn`, `error` (logger goes\n to stderr, never pollutes the JSON payload on stdout)\n\n## State on disk\n\n- `<cwd>/.stackbone/project.json` — organization + agent linked to this folder.\n Created by `stackbone init` / `link`.\n- `~/.stackbone/credentials.json` (chmod 600) — sessions keyed by control\n plane URL.\n- `~/.stackbone/config.json` — global preferences (default API URL,\n telemetry opt-in).\n\nThe control plane this skill points at: `https://api.stackbone.ai`.\nActive organization slug: `template1-2`.\n\n## Common workflows\n\n### Authenticate\n\n```sh\nstackbone login # interactive (device-code flow)\nstackbone whoami # confirm session\nstackbone metadata --json # one-shot overview that replaces 5–7 explorer calls\n```\n\n### Start a new project\n\n```sh\nstackbone init # menu of templates (workflow / autonomous / RAG)\nstackbone dev # local emulator at http://localhost:4242\n```\n\n### Deploy\n\n```sh\nstackbone deploy # build + push + Fly Machines\n```\n\n## Anti-patterns\n\n- Do not hardcode organization IDs, credentials or control plane URLs into\n agent code — the SDK reads them from the environment at runtime.\n- Do not parse human output. Re-run the command with `--json` and use\n `JSON.parse`.\n- Do not call the control plane HTTP API directly from a creator's agent.\n Use `@stackbone/sdk` so credential rotation, observability and metering\n stay correct.\n- Do not commit `.stackbone/` or `~/.stackbone/` — the entries are in the\n project's `.gitignore` for a reason.\n\n## Where to learn more\n\n- `stackbone docs cli` — command reference\n- `stackbone docs sdk` — `@stackbone/sdk` modules\n- `stackbone docs agent-yaml` — manifest reference (Epic 3)\n- `stackbone docs templates` — official templates (Epic 5)\n- `stackbone docs connections` — data layer (Epic 6)\n";
8608
+ //#endregion
8609
+ //#region ../../starters/rag/.gitignore?raw
8610
+ var _gitignore_raw_default$2 = "# >>> stackbone >>>\n.stackbone/\nnode_modules/\ndist/\n# <<< stackbone <<<\n";
8611
+ //#endregion
8612
+ //#region ../../starters/rag/Dockerfile?raw
8613
+ var Dockerfile_raw_default$2 = "# Distroless Node 24 image. Multi-stage so the runtime stays minimal.\nFROM node:24-alpine AS deps\nWORKDIR /app\nCOPY package.json ./\nRUN npm install --omit=dev\n\nFROM gcr.io/distroless/nodejs24-debian12\nWORKDIR /app\nCOPY --from=deps /app/node_modules ./node_modules\nCOPY src ./src\nCOPY agent.yaml ./\nENV PORT=8080\nEXPOSE 8080\nCMD [\"src/index.ts\"]\n";
8614
+ //#endregion
8615
+ //#region ../../starters/rag/README.md?raw
8616
+ var README_default$2 = "# starter-rag\n\nDemo of `client.rag` (pgvector-backed retrieval) from `@stackbone/sdk`.\n\n## Layout\n\n```\nsrc/\n index.ts # Hono server\n lib/\n client.ts # Shared Stackbone client\n logger.ts # JSON line logger\n routes/\n health.ts # GET /health\n schema-info.ts # GET /schema\n invoke.ts # POST /invoke -> action: ingest | retrieve\n chunk.ts # POST /chunk -> preview chunker output\n ingest.ts # POST /ingest -> chunk + embed + write\n retrieve.ts # POST /retrieve -> embed + topK\n delete.ts # DELETE /documents/:id\n```\n\n## Run\n\n```sh\nnpm install\nstackbone dev\n```\n\nThen:\n\n```sh\ncurl -X POST http://127.0.0.1:8080/ingest -H 'content-type: application/json' -d '{\n \"id\": \"doc-1\",\n \"text\": \"Stackbone is a marketplace and hosting platform for AI agents.\"\n}'\n\ncurl -X POST http://127.0.0.1:8080/retrieve -H 'content-type: application/json' -d '{\n \"text\": \"what is stackbone\",\n \"topK\": 3\n}'\n```\n\nRequires `OPENROUTER_API_KEY` for the embedding model.\n";
8617
+ //#endregion
8618
+ //#region ../../starters/rag/agent.yaml?raw
8619
+ var agent_default$2 = "# Stackbone agent manifest. Schema: stackbone.ai/v1.\n# Run `stackbone docs agent-yaml` for the full reference.\n\napiVersion: stackbone.ai/v1\nname: starter-rag\nruntime:\n engine: node\n entry: src/index.ts\ndatabase:\n schema: ./src/schema.ts\n migrations: ./.stackbone/migrations\ndev:\n autoMigrate: false\nrag:\n embeddingModel: openai/text-embedding-3-small\n autoMigrate: true\n";
8620
+ //#endregion
8621
+ //#region ../../starters/rag/src/index.ts?raw
8622
+ var src_default$2 = "import { serve } from '@hono/node-server';\nimport { Hono } from 'hono';\n\nimport { log } from './lib/logger.ts';\nimport chunk from './routes/chunk.ts';\nimport deleteDoc from './routes/delete.ts';\nimport health from './routes/health.ts';\nimport ingest from './routes/ingest.ts';\nimport invoke from './routes/invoke.ts';\nimport retrieve from './routes/retrieve.ts';\nimport schemaInfo from './routes/schema-info.ts';\n\nconst app = new Hono();\n\napp.use('*', async (c, next) => {\n const start = Date.now();\n await next();\n log('info', 'request', {\n method: c.req.method,\n path: c.req.path,\n status: c.res.status,\n durationMs: Date.now() - start,\n });\n});\n\napp.route('/', health);\napp.route('/', schemaInfo);\napp.route('/', invoke);\napp.route('/', chunk);\napp.route('/', ingest);\napp.route('/', retrieve);\napp.route('/', deleteDoc);\n\nconst port = Number(process.env.PORT ?? 8080);\nserve({ fetch: app.fetch, port });\nlog('info', 'starter-rag listening', { port });\n";
8623
+ //#endregion
8624
+ //#region ../../starters/rag/src/lib/client.ts?raw
8625
+ var client_default$2 = "import { createClient } from '@stackbone/sdk';\n\nexport const client = createClient({\n stackboneApiUrl: process.env.STACKBONE_DEV_API_URL ?? 'http://127.0.0.1:4242',\n});\n";
8626
+ //#endregion
8627
+ //#region ../../starters/rag/src/lib/logger.ts?raw
8628
+ var logger_default$2 = "type Level = 'info' | 'warn' | 'error';\n\nexport function log(level: Level, msg: string, fields?: Record<string, unknown>): void {\n const line = JSON.stringify({ ts: new Date().toISOString(), level, msg, ...fields });\n if (level === 'error') console.error(line);\n else console.log(line);\n}\n";
8629
+ //#endregion
8630
+ //#region ../../starters/rag/src/routes/chunk.ts?raw
8631
+ var chunk_default = "import { Hono } from 'hono';\n\nimport { client } from '../lib/client.ts';\nimport { log } from '../lib/logger.ts';\n\nconst route = new Hono();\n\n// Pure helper — splits text into chunks without touching DB or AI. Useful to\n// preview the chunker before paying for embeddings.\nroute.post('/chunk', async (c) => {\n const body = await c.req.json().catch(() => ({}) as { text?: string; size?: number; overlap?: number });\n const text = typeof body.text === 'string' ? body.text : '';\n if (!text) return c.json({ error: 'missing text' }, 400);\n\n log('info', 'chunk', { length: text.length, size: body.size, overlap: body.overlap });\n const chunks = client.rag.chunk(text, { size: body.size, overlap: body.overlap });\n return c.json({ count: chunks.length, chunks });\n});\n\nexport default route;\n";
8632
+ //#endregion
8633
+ //#region ../../starters/rag/src/routes/delete.ts?raw
8634
+ var delete_default = "import { Hono } from 'hono';\n\nimport { client } from '../lib/client.ts';\nimport { log } from '../lib/logger.ts';\n\nconst route = new Hono();\n\nroute.delete('/documents/:id', async (c) => {\n const id = c.req.param('id');\n log('info', 'delete document', { id });\n\n const result = await client.rag.delete(id);\n if (result.error) {\n log('error', 'delete failed', { id, code: result.error.code });\n return c.json({ error: result.error }, 502);\n }\n\n log('info', 'delete done', { id, deleted: result.data.deleted });\n return c.json(result.data);\n});\n\nexport default route;\n";
8635
+ //#endregion
8636
+ //#region ../../starters/rag/src/routes/health.ts?raw
8637
+ var health_default$2 = "import { Hono } from 'hono';\n\nconst route = new Hono();\nroute.get('/health', (c) => c.json({ status: 'ok' }));\nexport default route;\n";
8638
+ //#endregion
8639
+ //#region ../../starters/rag/src/routes/ingest.ts?raw
8640
+ var ingest_default = "import { Hono } from 'hono';\n\nimport { client } from '../lib/client.ts';\nimport { log } from '../lib/logger.ts';\n\nconst route = new Hono();\n\nroute.post('/ingest', async (c) => {\n const body = await c.req.json().catch(\n () =>\n ({}) as {\n id?: string;\n text?: string;\n model?: string;\n metadata?: Record<string, unknown>;\n },\n );\n\n const id = typeof body.id === 'string' ? body.id : crypto.randomUUID();\n const text = typeof body.text === 'string' ? body.text : '';\n const model = typeof body.model === 'string' ? body.model : 'openai/text-embedding-3-small';\n\n if (!text) return c.json({ error: 'missing text' }, 400);\n\n log('info', 'ingest start', { id, model, length: text.length });\n const chunks = client.rag.chunk(text);\n log('info', 'ingest chunked', { id, chunks: chunks.length });\n\n const result = await client.rag.ingest({ id, chunks, model, metadata: body.metadata });\n\n if (result.error) {\n log('error', 'ingest failed', { id, code: result.error.code });\n return c.json({ error: result.error }, 502);\n }\n\n log('info', 'ingest done', { id, chunks: result.data.chunks });\n return c.json(result.data);\n});\n\nexport default route;\n";
8641
+ //#endregion
8642
+ //#region ../../starters/rag/src/routes/invoke.ts?raw
8643
+ var invoke_default$2 = "import { Hono } from 'hono';\n\nimport { client } from '../lib/client.ts';\nimport { log } from '../lib/logger.ts';\n\nconst route = new Hono();\n\n// `/invoke` is the canonical Stackbone agent entrypoint. We dispatch by an\n// `action` field so the agent has a single contract while the dev surface\n// still exposes the granular routes (ingest / retrieve / chunk / delete).\nroute.post('/invoke', async (c) => {\n const body = await c.req.json().catch(\n () => ({}) as { action?: 'ingest' | 'retrieve'; id?: string; text?: string },\n );\n\n log('info', 'invoke', { action: body.action });\n\n if (body.action === 'ingest') {\n const text = body.text ?? '';\n const id = body.id ?? crypto.randomUUID();\n const chunks = client.rag.chunk(text);\n const result = await client.rag.ingest({\n id,\n chunks,\n model: 'openai/text-embedding-3-small',\n });\n return result.error ? c.json({ error: result.error }, 502) : c.json(result.data);\n }\n\n if (body.action === 'retrieve') {\n const result = await client.rag.retrieve({\n text: body.text ?? '',\n model: 'openai/text-embedding-3-small',\n topK: 3,\n includeContent: true,\n });\n return result.error ? c.json({ error: result.error }, 502) : c.json({ hits: result.data });\n }\n\n return c.json({ error: 'unknown action — use \"ingest\" or \"retrieve\"' }, 400);\n});\n\nexport default route;\n";
8644
+ //#endregion
8645
+ //#region ../../starters/rag/src/routes/retrieve.ts?raw
8646
+ var retrieve_default = "import { Hono } from 'hono';\n\nimport { client } from '../lib/client.ts';\nimport { log } from '../lib/logger.ts';\n\nconst route = new Hono();\n\nroute.post('/retrieve', async (c) => {\n const body = await c.req.json().catch(\n () => ({}) as { text?: string; topK?: number; model?: string },\n );\n const text = typeof body.text === 'string' ? body.text : '';\n const model = typeof body.model === 'string' ? body.model : 'openai/text-embedding-3-small';\n const topK = typeof body.topK === 'number' ? body.topK : 5;\n\n if (!text) return c.json({ error: 'missing text' }, 400);\n\n log('info', 'retrieve', { topK, model, length: text.length });\n\n const result = await client.rag.retrieve({ text, model, topK, includeContent: true });\n if (result.error) {\n log('error', 'retrieve failed', { code: result.error.code });\n return c.json({ error: result.error }, 502);\n }\n\n log('info', 'retrieve done', { hits: result.data.length });\n return c.json({ hits: result.data });\n});\n\nexport default route;\n";
8647
+ //#endregion
8648
+ //#region ../../starters/rag/src/routes/schema-info.ts?raw
8649
+ var schema_info_default$2 = "import { Hono } from 'hono';\n\nconst route = new Hono();\n\nroute.get('/schema', (c) =>\n c.json({\n input: {\n type: 'object',\n properties: {\n action: { type: 'string', enum: ['ingest', 'retrieve'] },\n text: { type: 'string' },\n },\n required: ['action'],\n },\n }),\n);\n\nexport default route;\n";
8650
+ //#endregion
8651
+ //#region ../../starters/secrets/.claude/skills/stackbone/SKILL.md?raw
8652
+ var SKILL_default$1 = "---\nname: stackbone\ndescription: Use this skill when the user is working with the Stackbone CLI, the `@stackbone/sdk` package, the `agent.yaml` manifest, official templates, or connections to the Stackbone control plane. Triggers on commands like `stackbone init`, `stackbone dev`, `stackbone deploy`, on questions about organization state, exit codes, or environment variables, and on any reference to `@stackbone/*` imports in TypeScript code. Provides the command surface, output contract, exit codes, environment variables and idiomatic workflows.\n---\n\n# Stackbone — agent operating manual\n\nThis skill is regenerated by `stackbone init` / `stackbone link` and ships\ninside the CLI release 0.1.0-alpha.1. Edit the upstream template in\n`apps/cli/src/skills/templates.ts` if something here is wrong.\n\n## CLI surface\n\nThe CLI binary is `stackbone` (also reachable as `npx @stackbone/cli@alpha ...`). Run\n`stackbone docs cli` for the canonical command/flag reference, or\n`stackbone docs sdk` for the `@stackbone/sdk` overview — neither command does\nnetwork IO.\n\n## Output contract\n\nEvery command supports `--json` (or `STACKBONE_JSON=1`). Successful JSON\nresponses look like:\n\n```json\n{ \"schema_version\": 1, \"...\": \"...\" }\n```\n\nErrors look like:\n\n```json\n{\n \"schema_version\": 1,\n \"error\": { \"code\": \"auth\", \"message\": \"...\", \"suggestion\": \"Run \\`stackbone login\\`\" },\n \"exit_code\": 2\n}\n```\n\n`schema_version` only bumps on backward-incompatible shape changes. If you\nsee a different value, refuse to parse and ask the user to upgrade.\n\n## Exit codes\n\n- `0` ok\n- `1` generic\n- `2` auth (no session, expired, refused login)\n- `3` no project linked (no `.stackbone/project.json` in cwd)\n- `4` not found (organization, agent, deployment)\n- `5` permission denied\n\nBranch on these instead of parsing stack traces.\n\n## Environment variables\n\n- `STACKBONE_JSON=1` — same as `--json`\n- `STACKBONE_API_URL` — same as `--api-url <url>`\n- `STACKBONE_AUTO_LOGIN=0` — same as `--no-auto-login`\n- `STACKBONE_LOG_LEVEL` — `debug`, `info`, `warn`, `error` (logger goes\n to stderr, never pollutes the JSON payload on stdout)\n\n## State on disk\n\n- `<cwd>/.stackbone/project.json` — organization + agent linked to this folder.\n Created by `stackbone init` / `link`.\n- `~/.stackbone/credentials.json` (chmod 600) — sessions keyed by control\n plane URL.\n- `~/.stackbone/config.json` — global preferences (default API URL,\n telemetry opt-in).\n\nThe control plane this skill points at: `https://api.stackbone.ai`.\nActive organization slug: `template1-2`.\n\n## Common workflows\n\n### Authenticate\n\n```sh\nstackbone login # interactive (device-code flow)\nstackbone whoami # confirm session\nstackbone metadata --json # one-shot overview that replaces 5–7 explorer calls\n```\n\n### Start a new project\n\n```sh\nstackbone init # menu of templates (workflow / autonomous / RAG)\nstackbone dev # local emulator at http://localhost:4242\n```\n\n### Deploy\n\n```sh\nstackbone deploy # build + push + Fly Machines\n```\n\n## Anti-patterns\n\n- Do not hardcode organization IDs, credentials or control plane URLs into\n agent code — the SDK reads them from the environment at runtime.\n- Do not parse human output. Re-run the command with `--json` and use\n `JSON.parse`.\n- Do not call the control plane HTTP API directly from a creator's agent.\n Use `@stackbone/sdk` so credential rotation, observability and metering\n stay correct.\n- Do not commit `.stackbone/` or `~/.stackbone/` — the entries are in the\n project's `.gitignore` for a reason.\n\n## Where to learn more\n\n- `stackbone docs cli` — command reference\n- `stackbone docs sdk` — `@stackbone/sdk` modules\n- `stackbone docs agent-yaml` — manifest reference (Epic 3)\n- `stackbone docs templates` — official templates (Epic 5)\n- `stackbone docs connections` — data layer (Epic 6)\n";
8653
+ //#endregion
8654
+ //#region ../../starters/secrets/.gitignore?raw
8655
+ var _gitignore_raw_default$1 = "# >>> stackbone >>>\n.stackbone/\nnode_modules/\ndist/\n# <<< stackbone <<<\n";
8656
+ //#endregion
8657
+ //#region ../../starters/secrets/Dockerfile?raw
8658
+ var Dockerfile_raw_default$1 = "# Distroless Node 24 image. Multi-stage so the runtime stays minimal.\nFROM node:24-alpine AS deps\nWORKDIR /app\nCOPY package.json ./\nRUN npm install --omit=dev\n\nFROM gcr.io/distroless/nodejs24-debian12\nWORKDIR /app\nCOPY --from=deps /app/node_modules ./node_modules\nCOPY src ./src\nCOPY agent.yaml ./\nENV PORT=8080\nEXPOSE 8080\nCMD [\"src/index.ts\"]\n";
8659
+ //#endregion
8660
+ //#region ../../starters/secrets/README.md?raw
8661
+ var README_default$1 = "# starter-secrets\n\nDemo of `client.secrets` (workspace-scoped encrypted values) from\n`@stackbone/sdk`.\n\n## Layout\n\n```\nsrc/\n index.ts # Hono server\n lib/\n client.ts # Shared Stackbone client\n logger.ts # JSON line logger\n routes/\n health.ts # GET /health\n schema-info.ts # GET /schema\n invoke.ts # POST /invoke -> check if secret is present\n get-secret.ts # GET /secrets/:name -> fetch value (demo only)\n get-many.ts # POST /secrets/bulk -> bulk check (names[] -> present/missing)\n```\n\n## Run\n\n```sh\nnpm install\nstackbone dev\n```\n\nThen (after registering `OPENAI_API_KEY` and `STRIPE_KEY` in the control plane):\n\n```sh\ncurl -X POST http://127.0.0.1:8080/invoke -H 'content-type: application/json' \\\n -d '{\"name\":\"OPENAI_API_KEY\"}'\ncurl -X POST http://127.0.0.1:8080/secrets/bulk -H 'content-type: application/json' \\\n -d '{\"names\":[\"OPENAI_API_KEY\",\"STRIPE_KEY\",\"NOT_SET\"]}'\n```\n";
8662
+ //#endregion
8663
+ //#region ../../starters/secrets/agent.yaml?raw
8664
+ var agent_default$1 = "# Stackbone agent manifest. Schema: stackbone.ai/v1.\n# Run `stackbone docs agent-yaml` for the full reference.\n\napiVersion: stackbone.ai/v1\nname: starter-secrets\nruntime:\n engine: node\n entry: src/index.ts\ndatabase:\n schema: ./src/schema.ts\n migrations: ./.stackbone/migrations\ndev:\n autoMigrate: false\nrag:\n embeddingModel: openai/text-embedding-3-small\n autoMigrate: false\n";
8665
+ //#endregion
8666
+ //#region ../../starters/secrets/src/index.ts?raw
8667
+ var src_default$1 = "import { serve } from '@hono/node-server';\nimport { Hono } from 'hono';\n\nimport { log } from './lib/logger.ts';\nimport getMany from './routes/get-many.ts';\nimport getSecret from './routes/get-secret.ts';\nimport health from './routes/health.ts';\nimport invoke from './routes/invoke.ts';\nimport schemaInfo from './routes/schema-info.ts';\n\nconst app = new Hono();\n\napp.use('*', async (c, next) => {\n const start = Date.now();\n await next();\n log('info', 'request', {\n method: c.req.method,\n path: c.req.path,\n status: c.res.status,\n durationMs: Date.now() - start,\n });\n});\n\napp.route('/', health);\napp.route('/', schemaInfo);\napp.route('/', invoke);\napp.route('/', getSecret);\napp.route('/', getMany);\n\nconst port = Number(process.env.PORT ?? 8080);\nserve({ fetch: app.fetch, port });\nlog('info', 'starter-secrets listening', { port });\n";
8668
+ //#endregion
8669
+ //#region ../../starters/secrets/src/lib/client.ts?raw
8670
+ var client_default$1 = "import { createClient } from '@stackbone/sdk';\n\nexport const client = createClient({\n stackboneApiUrl: process.env.STACKBONE_DEV_API_URL ?? 'http://127.0.0.1:4242',\n});\n";
8671
+ //#endregion
8672
+ //#region ../../starters/secrets/src/lib/logger.ts?raw
8673
+ var logger_default$1 = "type Level = 'info' | 'warn' | 'error';\n\nexport function log(level: Level, msg: string, fields?: Record<string, unknown>): void {\n const line = JSON.stringify({ ts: new Date().toISOString(), level, msg, ...fields });\n if (level === 'error') console.error(line);\n else console.log(line);\n}\n";
8674
+ //#endregion
8675
+ //#region ../../starters/secrets/src/routes/get-many.ts?raw
8676
+ var get_many_default = "import { Hono } from 'hono';\n\nimport { client } from '../lib/client.ts';\nimport { log } from '../lib/logger.ts';\n\nconst route = new Hono();\n\nroute.post('/secrets/bulk', async (c) => {\n const body = await c.req.json().catch(() => ({}) as { names?: string[] });\n const names = Array.isArray(body.names) ? body.names : [];\n if (names.length === 0) return c.json({ error: 'missing names[]' }, 400);\n\n log('info', 'fetch secrets bulk', { count: names.length });\n\n const result = await client.secrets.getMany(names);\n if (result.error) {\n log('error', 'bulk failed', { code: result.error.code });\n return c.json({ error: result.error }, 502);\n }\n\n // Mask values — just return which names resolved.\n const present = Object.keys(result.data);\n const missing = names.filter((name) => !present.includes(name));\n log('info', 'bulk done', { present: present.length, missing: missing.length });\n return c.json({ present, missing });\n});\n\nexport default route;\n";
8677
+ //#endregion
8678
+ //#region ../../starters/secrets/src/routes/get-secret.ts?raw
8679
+ var get_secret_default = "import { Hono } from 'hono';\n\nimport { client } from '../lib/client.ts';\nimport { log } from '../lib/logger.ts';\n\nconst route = new Hono();\n\n// Returns the raw secret value. Real agents would consume the value\n// internally — exposing it over HTTP defeats the purpose. This endpoint is\n// scaffolded for the demo only.\nroute.get('/secrets/:name', async (c) => {\n const name = c.req.param('name');\n log('info', 'fetch secret', { name });\n\n const result = await client.secrets.get(name);\n if (result.error) {\n log('error', 'fetch secret failed', { name, code: result.error.code });\n return c.json({ error: result.error }, 502);\n }\n\n log('info', 'fetch secret done', { name, length: result.data.length });\n return c.json({ name, value: result.data });\n});\n\nexport default route;\n";
8680
+ //#endregion
8681
+ //#region ../../starters/secrets/src/routes/health.ts?raw
8682
+ var health_default$1 = "import { Hono } from 'hono';\n\nconst route = new Hono();\nroute.get('/health', (c) => c.json({ status: 'ok' }));\nexport default route;\n";
8683
+ //#endregion
8684
+ //#region ../../starters/secrets/src/routes/invoke.ts?raw
8685
+ var invoke_default$1 = "import { Hono } from 'hono';\n\nimport { client } from '../lib/client.ts';\nimport { log } from '../lib/logger.ts';\n\nconst route = new Hono();\n\n// Checks whether a named secret is present in the agent's workspace without\n// leaking the value. Useful as a probe before running the dependent task.\nroute.post('/invoke', async (c) => {\n const body = await c.req.json().catch(() => ({}) as { name?: string });\n const name = typeof body.name === 'string' ? body.name : '';\n if (!name) return c.json({ error: 'missing name' }, 400);\n\n log('info', 'check secret', { name });\n const result = await client.secrets.get(name);\n\n if (result.error) {\n log('warn', 'secret missing', { name, code: result.error.code });\n return c.json({ name, present: false, code: result.error.code });\n }\n\n log('info', 'secret present', { name, length: result.data.length });\n return c.json({ name, present: true, length: result.data.length });\n});\n\nexport default route;\n";
8686
+ //#endregion
8687
+ //#region ../../starters/secrets/src/routes/schema-info.ts?raw
8688
+ var schema_info_default$1 = "import { Hono } from 'hono';\n\nconst route = new Hono();\n\nroute.get('/schema', (c) =>\n c.json({\n input: {\n type: 'object',\n properties: { name: { type: 'string' } },\n required: ['name'],\n },\n output: {\n type: 'object',\n properties: { name: { type: 'string' }, present: { type: 'boolean' } },\n },\n }),\n);\n\nexport default route;\n";
8689
+ //#endregion
8690
+ //#region ../../starters/storage/.claude/skills/stackbone/SKILL.md?raw
8691
+ var SKILL_default = "---\nname: stackbone\ndescription: Use this skill when the user is working with the Stackbone CLI, the `@stackbone/sdk` package, the `agent.yaml` manifest, official templates, or connections to the Stackbone control plane. Triggers on commands like `stackbone init`, `stackbone dev`, `stackbone deploy`, on questions about organization state, exit codes, or environment variables, and on any reference to `@stackbone/*` imports in TypeScript code. Provides the command surface, output contract, exit codes, environment variables and idiomatic workflows.\n---\n\n# Stackbone — agent operating manual\n\nThis skill is regenerated by `stackbone init` / `stackbone link` and ships\ninside the CLI release 0.1.0-alpha.1. Edit the upstream template in\n`apps/cli/src/skills/templates.ts` if something here is wrong.\n\n## CLI surface\n\nThe CLI binary is `stackbone` (also reachable as `npx @stackbone/cli@alpha ...`). Run\n`stackbone docs cli` for the canonical command/flag reference, or\n`stackbone docs sdk` for the `@stackbone/sdk` overview — neither command does\nnetwork IO.\n\n## Output contract\n\nEvery command supports `--json` (or `STACKBONE_JSON=1`). Successful JSON\nresponses look like:\n\n```json\n{ \"schema_version\": 1, \"...\": \"...\" }\n```\n\nErrors look like:\n\n```json\n{\n \"schema_version\": 1,\n \"error\": { \"code\": \"auth\", \"message\": \"...\", \"suggestion\": \"Run \\`stackbone login\\`\" },\n \"exit_code\": 2\n}\n```\n\n`schema_version` only bumps on backward-incompatible shape changes. If you\nsee a different value, refuse to parse and ask the user to upgrade.\n\n## Exit codes\n\n- `0` ok\n- `1` generic\n- `2` auth (no session, expired, refused login)\n- `3` no project linked (no `.stackbone/project.json` in cwd)\n- `4` not found (organization, agent, deployment)\n- `5` permission denied\n\nBranch on these instead of parsing stack traces.\n\n## Environment variables\n\n- `STACKBONE_JSON=1` — same as `--json`\n- `STACKBONE_API_URL` — same as `--api-url <url>`\n- `STACKBONE_AUTO_LOGIN=0` — same as `--no-auto-login`\n- `STACKBONE_LOG_LEVEL` — `debug`, `info`, `warn`, `error` (logger goes\n to stderr, never pollutes the JSON payload on stdout)\n\n## State on disk\n\n- `<cwd>/.stackbone/project.json` — organization + agent linked to this folder.\n Created by `stackbone init` / `link`.\n- `~/.stackbone/credentials.json` (chmod 600) — sessions keyed by control\n plane URL.\n- `~/.stackbone/config.json` — global preferences (default API URL,\n telemetry opt-in).\n\nThe control plane this skill points at: `https://api.stackbone.ai`.\nActive organization slug: `template1-2`.\n\n## Common workflows\n\n### Authenticate\n\n```sh\nstackbone login # interactive (device-code flow)\nstackbone whoami # confirm session\nstackbone metadata --json # one-shot overview that replaces 5–7 explorer calls\n```\n\n### Start a new project\n\n```sh\nstackbone init # menu of templates (workflow / autonomous / RAG)\nstackbone dev # local emulator at http://localhost:4242\n```\n\n### Deploy\n\n```sh\nstackbone deploy # build + push + Fly Machines\n```\n\n## Anti-patterns\n\n- Do not hardcode organization IDs, credentials or control plane URLs into\n agent code — the SDK reads them from the environment at runtime.\n- Do not parse human output. Re-run the command with `--json` and use\n `JSON.parse`.\n- Do not call the control plane HTTP API directly from a creator's agent.\n Use `@stackbone/sdk` so credential rotation, observability and metering\n stay correct.\n- Do not commit `.stackbone/` or `~/.stackbone/` — the entries are in the\n project's `.gitignore` for a reason.\n\n## Where to learn more\n\n- `stackbone docs cli` — command reference\n- `stackbone docs sdk` — `@stackbone/sdk` modules\n- `stackbone docs agent-yaml` — manifest reference (Epic 3)\n- `stackbone docs templates` — official templates (Epic 5)\n- `stackbone docs connections` — data layer (Epic 6)\n";
8692
+ //#endregion
8693
+ //#region ../../starters/storage/.gitignore?raw
8694
+ var _gitignore_raw_default = "# >>> stackbone >>>\n.stackbone/\nnode_modules/\ndist/\n# <<< stackbone <<<\n";
8695
+ //#endregion
8696
+ //#region ../../starters/storage/Dockerfile?raw
8332
8697
  var Dockerfile_raw_default = "# Distroless Node 24 image. Multi-stage so the runtime stays minimal.\nFROM node:24-alpine AS deps\nWORKDIR /app\nCOPY package.json ./\nRUN npm install --omit=dev\n\nFROM gcr.io/distroless/nodejs24-debian12\nWORKDIR /app\nCOPY --from=deps /app/node_modules ./node_modules\nCOPY src ./src\nCOPY agent.yaml ./\nENV PORT=8080\nEXPOSE 8080\nCMD [\"src/index.ts\"]\n";
8333
8698
  //#endregion
8334
- //#region src/templates/workflow/README.md?raw
8335
- var README_default = "# {{agentName}}\n\nStackbone agent scaffolded by `stackbone init`. The runtime contract is the\nHTTP surface in `src/index.ts`: `/invoke`, `/health`, `/schema`.\n\n## Develop\n\n```sh\npnpm install\npnpm dev # node --watch src/index.ts\n```\n\n## Deploy\n\n```sh\nstackbone deploy # builds the image and pushes it to Fly Machines\n```\n\nSee `stackbone docs sdk` for the SDK reference and `stackbone docs cli`\nfor the full command surface.\n";
8699
+ //#region ../../starters/storage/README.md?raw
8700
+ var README_default = "# starter-storage\n\nDemo of `client.storage` (S3 / MinIO) from `@stackbone/sdk`.\n\nThe template writes into a logical bucket called `demo`. Real keys land in\n`<agentId>/demo/...` inside the physical bucket configured via `S3_BUCKET`.\n\n## Layout\n\n```\nsrc/\n index.ts # Hono server\n lib/\n client.ts # Shared Stackbone client + BUCKET constant\n logger.ts # JSON line logger\n routes/\n health.ts # GET /health\n schema-info.ts # GET /schema\n invoke.ts # POST /invoke -> upload text\n list-objects.ts # GET /objects -> list (prefix?, limit?)\n download.ts # GET /objects/:key -> download bytes\n delete-object.ts # DELETE /objects/:key\n signed-url.ts # GET /signed-download/:key -> presigned GET\n # POST /signed-upload -> presigned PUT\n```\n\n## Run\n\n```sh\nnpm install\nstackbone dev\n```\n\nThen:\n\n```sh\ncurl -X POST http://127.0.0.1:8080/invoke -H 'content-type: application/json' \\\n -d '{\"key\":\"hello.txt\",\"body\":\"hello stackbone\"}'\ncurl http://127.0.0.1:8080/objects\ncurl http://127.0.0.1:8080/objects/hello.txt\ncurl http://127.0.0.1:8080/signed-download/hello.txt\ncurl -X DELETE http://127.0.0.1:8080/objects/hello.txt\n```\n\n`stackbone dev` provisions a local MinIO but does not inject S3 credentials\ninto the agent process export them yourself before booting:\n\n```sh\nexport STACKBONE_AGENT_ID=$(jq -r .agentId .stackbone/project.json)\nexport AWS_ACCESS_KEY_ID=stackbone\nexport AWS_SECRET_ACCESS_KEY=stackbone-secret\nexport S3_ENDPOINT=http://127.0.0.1:9004\nexport S3_BUCKET=stackbone-dev\nstackbone dev\n```\n\nIn production these come from the control plane / Fly secrets.\n";
8336
8701
  //#endregion
8337
- //#region src/templates/workflow/package.json?raw
8338
- var package_default = "{\n \"name\": \"{{agentSlug}}\",\n \"version\": \"0.0.1\",\n \"private\": true,\n \"type\": \"module\",\n \"scripts\": {\n \"dev\": \"node --watch src/index.ts\",\n \"start\": \"node src/index.ts\"\n },\n \"engines\": {\n \"node\": \">=24\"\n },\n \"dependencies\": {\n \"@hono/node-server\": \"^1.13.0\",\n \"@stackbone/sdk\": \"^0.0.1\",\n \"hono\": \"^4.6.0\"\n }\n}\n";
8702
+ //#region ../../starters/storage/agent.yaml?raw
8703
+ var agent_default = "# Stackbone agent manifest. Schema: stackbone.ai/v1.\n# Run `stackbone docs agent-yaml` for the full reference.\n\napiVersion: stackbone.ai/v1\nname: starter-storage\nruntime:\n engine: node\n entry: src/index.ts\ndatabase:\n schema: ./src/schema.ts\n migrations: ./.stackbone/migrations\ndev:\n autoMigrate: false\nrag:\n embeddingModel: openai/text-embedding-3-small\n autoMigrate: false\n";
8339
8704
  //#endregion
8340
- //#region src/templates/workflow/src/index.ts?raw
8341
- var src_default = "import { serve } from '@hono/node-server';\nimport { Hono } from 'hono';\n\n// MOCK workflow. In MVP this becomes a LangGraph state machine driven by\n// `@stackbone/sdk` (`client.ai.completion`); for POC the nodes are pure JS\n// returning hardcoded structured output so `stackbone dev` has something to\n// iterate on without an LLM bill.\n\ninterface State {\n input: string;\n classified?: 'lead' | 'spam';\n reply?: string;\n}\n\nconst classify = (s: State): State => ({\n ...s,\n classified: s.input.toLowerCase().includes('buy') ? 'lead' : 'spam',\n});\n\nconst draftReply = (s: State): State => ({\n ...s,\n reply:\n s.classified === 'lead'\n ? `Thanks! A human will follow up about: ${s.input}`\n : 'Filtered as spam.',\n});\n\nconst fallback = (s: State): State =>\n s.reply ? s : { ...s, reply: 'Could not produce a reply, escalating.' };\n\nconst runGraph = (input: string): State => fallback(draftReply(classify({ input })));\n\nconst app = new Hono();\n\napp.post('/invoke', async (c) => {\n const body = await c.req.json().catch(() => ({ input: '' }));\n const state = runGraph(typeof body.input === 'string' ? body.input : '');\n return c.json(state);\n});\n\napp.get('/health', (c) => c.json({ status: 'ok' }));\napp.get('/schema', (c) =>\n c.json({\n input: { type: 'object', properties: { input: { type: 'string' } }, required: ['input'] },\n output: {\n type: 'object',\n properties: {\n input: { type: 'string' },\n classified: { type: 'string', enum: ['lead', 'spam'] },\n reply: { type: 'string' },\n },\n },\n }),\n);\n\nconst port = Number(process.env.PORT ?? 8080);\nserve({ fetch: app.fetch, port });\nconsole.log(`workflow agent listening on :${port}`);\n";
8705
+ //#region ../../starters/storage/src/index.ts?raw
8706
+ var src_default = "import { serve } from '@hono/node-server';\nimport { Hono } from 'hono';\n\nimport { log } from './lib/logger.ts';\nimport deleteObject from './routes/delete-object.ts';\nimport download from './routes/download.ts';\nimport health from './routes/health.ts';\nimport invoke from './routes/invoke.ts';\nimport listObjects from './routes/list-objects.ts';\nimport schemaInfo from './routes/schema-info.ts';\nimport signedUrl from './routes/signed-url.ts';\n\nconst app = new Hono();\n\napp.use('*', async (c, next) => {\n const start = Date.now();\n await next();\n log('info', 'request', {\n method: c.req.method,\n path: c.req.path,\n status: c.res.status,\n durationMs: Date.now() - start,\n });\n});\n\napp.route('/', health);\napp.route('/', schemaInfo);\napp.route('/', invoke);\napp.route('/', listObjects);\napp.route('/', download);\napp.route('/', deleteObject);\napp.route('/', signedUrl);\n\nconst port = Number(process.env.PORT ?? 8080);\nserve({ fetch: app.fetch, port });\nlog('info', 'starter-storage listening', { port });\n";
8342
8707
  //#endregion
8343
- //#region src/templates/workflow/src/schema.ts?raw
8344
- var schema_default = "// Drizzle schema for {{agentName}}.\n//\n// Tables declared here are managed by `stackbone db migrate up`. Edit this\n// file, then run `stackbone db migrate create <name>` to emit the next SQL\n// migration under `.stackbone/migrations/`. The first migration shipped with\n// the template (`0000_init.sql`) creates the `examples` table below so the\n// agent has something queryable on its first `stackbone dev` boot.\n//\n// Drizzle column helpers re-export through `@stackbone/sdk/db` — never import\n// from `drizzle-orm/pg-core` directly. The SDK barrel is the single contract\n// the platform supports.\nimport { pgTable, serial, text, timestamp } from '@stackbone/sdk/db';\n\nexport const examples = pgTable('examples', {\n id: serial('id').primaryKey(),\n name: text('name').notNull(),\n createdAt: timestamp('created_at').defaultNow().notNull(),\n});\n";
8708
+ //#region ../../starters/storage/src/lib/client.ts?raw
8709
+ var client_default = "import { createClient } from '@stackbone/sdk';\n\nexport const client = createClient({\n stackboneApiUrl: process.env.STACKBONE_DEV_API_URL ?? 'http://127.0.0.1:4242',\n});\n\n// Single logical bucket the template writes into. Real keys are namespaced\n// underneath by `client.storage` (prefixed with the agent id).\nexport const BUCKET = 'demo';\n";
8345
8710
  //#endregion
8346
- //#region src/services/scaffolding.ts
8347
- var TOKEN_PATTERN = /{{\s*(\w+)\s*}}/g;
8348
- var manifestModules = /* @__PURE__ */ Object.assign({
8349
- "../templates/autonomous/template.yaml": template_default$2,
8350
- "../templates/hello-world/template.yaml": template_default$1,
8351
- "../templates/workflow/template.yaml": template_default
8352
- });
8353
- var fileModules = /* @__PURE__ */ Object.assign({
8354
- "../templates/autonomous/.stackbone/migrations/0000_init.sql": _0000_init_default$2,
8355
- "../templates/autonomous/.stackbone/migrations/meta/0000_snapshot.json": _0000_snapshot_default$2,
8356
- "../templates/autonomous/.stackbone/migrations/meta/_journal.json": _journal_default$2,
8357
- "../templates/autonomous/Dockerfile": Dockerfile_raw_default$2,
8358
- "../templates/autonomous/README.md": README_default$2,
8359
- "../templates/autonomous/package.json": package_default$2,
8360
- "../templates/autonomous/src/index.ts": src_default$2,
8361
- "../templates/autonomous/src/schema.ts": schema_default$2,
8362
- "../templates/hello-world/.stackbone/migrations/0000_init.sql": _0000_init_default$1,
8363
- "../templates/hello-world/.stackbone/migrations/meta/0000_snapshot.json": _0000_snapshot_default$1,
8364
- "../templates/hello-world/.stackbone/migrations/meta/_journal.json": _journal_default$1,
8365
- "../templates/hello-world/Dockerfile": Dockerfile_raw_default$1,
8366
- "../templates/hello-world/README.md": README_default$1,
8367
- "../templates/hello-world/package.json": package_default$1,
8368
- "../templates/hello-world/src/index.ts": src_default$1,
8369
- "../templates/hello-world/src/schema.ts": schema_default$1,
8370
- "../templates/workflow/.stackbone/migrations/0000_init.sql": _0000_init_default,
8371
- "../templates/workflow/.stackbone/migrations/meta/0000_snapshot.json": _0000_snapshot_default,
8372
- "../templates/workflow/.stackbone/migrations/meta/_journal.json": _journal_default,
8373
- "../templates/workflow/Dockerfile": Dockerfile_raw_default,
8374
- "../templates/workflow/README.md": README_default,
8375
- "../templates/workflow/package.json": package_default,
8376
- "../templates/workflow/src/index.ts": src_default,
8377
- "../templates/workflow/src/schema.ts": schema_default
8378
- });
8379
- var requireString = (value, field, id) => {
8380
- if (typeof value !== "string" || value.length === 0) throw new Error(`Template '${id}': field '${field}' must be a non-empty string.`);
8711
+ //#region ../../starters/storage/src/lib/logger.ts?raw
8712
+ var logger_default = "type Level = 'info' | 'warn' | 'error';\n\nexport function log(level: Level, msg: string, fields?: Record<string, unknown>): void {\n const line = JSON.stringify({ ts: new Date().toISOString(), level, msg, ...fields });\n if (level === 'error') console.error(line);\n else console.log(line);\n}\n";
8713
+ //#endregion
8714
+ //#region ../../starters/storage/src/routes/delete-object.ts?raw
8715
+ var delete_object_default = "import { Hono } from 'hono';\n\nimport { BUCKET, client } from '../lib/client.ts';\nimport { log } from '../lib/logger.ts';\n\nconst route = new Hono();\n\nroute.delete('/objects/:key', async (c) => {\n const key = c.req.param('key');\n log('info', 'remove', { key });\n\n const result = await client.storage.from(BUCKET).remove(key);\n if (result.error) {\n log('error', 'remove failed', { key, code: result.error.code });\n return c.json({ error: result.error }, 502);\n }\n\n log('info', 'remove done', { key });\n return c.json(result.data);\n});\n\nexport default route;\n";
8716
+ //#endregion
8717
+ //#region ../../starters/storage/src/routes/download.ts?raw
8718
+ var download_default = "import { Hono } from 'hono';\n\nimport { BUCKET, client } from '../lib/client.ts';\nimport { log } from '../lib/logger.ts';\n\nconst route = new Hono();\n\n// Returns the object body verbatim. Streaming-safe via response cloning so\n// the agent does not have to materialise large objects fully in memory.\nroute.get('/objects/:key', async (c) => {\n const key = c.req.param('key');\n log('info', 'download', { key });\n\n const result = await client.storage.from(BUCKET).download(key);\n if (result.error) {\n log('error', 'download failed', { key, code: result.error.code });\n return c.json({ error: result.error }, 502);\n }\n\n const buffer = await result.data.arrayBuffer();\n log('info', 'download done', { key, bytes: buffer.byteLength });\n return new Response(buffer, {\n headers: { 'content-type': result.data.type || 'application/octet-stream' },\n });\n});\n\nexport default route;\n";
8719
+ //#endregion
8720
+ //#region ../../starters/storage/src/routes/health.ts?raw
8721
+ var health_default = "import { Hono } from 'hono';\n\nconst route = new Hono();\nroute.get('/health', (c) => c.json({ status: 'ok' }));\nexport default route;\n";
8722
+ //#endregion
8723
+ //#region ../../starters/storage/src/routes/invoke.ts?raw
8724
+ var invoke_default = "import { Hono } from 'hono';\n\nimport { BUCKET, client } from '../lib/client.ts';\nimport { log } from '../lib/logger.ts';\n\nconst route = new Hono();\n\n// `/invoke` uploads a small text blob to the demo bucket — the simplest path\n// to validate the agent has working S3 credentials.\nroute.post('/invoke', async (c) => {\n const body = await c.req.json().catch(() => ({}) as { key?: string; body?: string });\n const key = body.key ?? `invoke-${Date.now()}.txt`;\n const content = body.body ?? 'hello from starter-storage';\n\n log('info', 'upload', { key, bytes: content.length });\n\n const result = await client.storage.from(BUCKET).upload(key, content, {\n contentType: 'text/plain',\n });\n\n if (result.error) {\n log('error', 'upload failed', { key, code: result.error.code });\n return c.json({ error: result.error }, 502);\n }\n\n log('info', 'upload done', { key: result.data.key });\n return c.json(result.data);\n});\n\nexport default route;\n";
8725
+ //#endregion
8726
+ //#region ../../starters/storage/src/routes/list-objects.ts?raw
8727
+ var list_objects_default = "import { Hono } from 'hono';\n\nimport { BUCKET, client } from '../lib/client.ts';\nimport { log } from '../lib/logger.ts';\n\nconst route = new Hono();\n\nroute.get('/objects', async (c) => {\n const prefix = c.req.query('prefix');\n const limit = c.req.query('limit') ? Number(c.req.query('limit')) : undefined;\n\n log('info', 'list objects', { prefix, limit });\n\n const result = await client.storage.from(BUCKET).list({ prefix, limit });\n if (result.error) {\n log('error', 'list failed', { code: result.error.code });\n return c.json({ error: result.error }, 502);\n }\n\n log('info', 'list done', { count: result.data.objects.length });\n return c.json(result.data);\n});\n\nexport default route;\n";
8728
+ //#endregion
8729
+ //#region ../../starters/storage/src/routes/schema-info.ts?raw
8730
+ var schema_info_default = "import { Hono } from 'hono';\n\nconst route = new Hono();\n\nroute.get('/schema', (c) =>\n c.json({\n input: {\n type: 'object',\n properties: { key: { type: 'string' }, body: { type: 'string' } },\n required: ['key', 'body'],\n },\n output: {\n type: 'object',\n properties: { key: { type: 'string' }, etag: { type: 'string' } },\n },\n }),\n);\n\nexport default route;\n";
8731
+ //#endregion
8732
+ //#region ../../starters/storage/src/routes/signed-url.ts?raw
8733
+ var signed_url_default = "import { Hono } from 'hono';\n\nimport { BUCKET, client } from '../lib/client.ts';\nimport { log } from '../lib/logger.ts';\n\nconst route = new Hono();\n\n// Pre-signed download URL — used by clients that pull large objects directly\n// from S3/MinIO without going through the agent.\nroute.get('/signed-download/:key', async (c) => {\n const key = c.req.param('key');\n const expiresIn = c.req.query('expiresIn') ? Number(c.req.query('expiresIn')) : undefined;\n\n log('info', 'signed download', { key, expiresIn });\n\n const result = await client.storage.from(BUCKET).getSignedDownloadUrl(key, { expiresIn });\n if (result.error) {\n log('error', 'signed download failed', { key, code: result.error.code });\n return c.json({ error: result.error }, 502);\n }\n\n return c.json(result.data);\n});\n\n// Pre-signed upload URL — clients PUT directly to S3 with a matching\n// content-type to avoid going through the agent on the upload path.\nroute.post('/signed-upload', async (c) => {\n const body = await c.req.json().catch(() => ({}) as { key?: string; contentType?: string });\n const key = body.key ?? `upload-${Date.now()}.bin`;\n\n log('info', 'signed upload', { key, contentType: body.contentType });\n\n const result = await client.storage\n .from(BUCKET)\n .getSignedUploadUrl(key, { contentType: body.contentType });\n if (result.error) {\n log('error', 'signed upload failed', { key, code: result.error.code });\n return c.json({ error: result.error }, 502);\n }\n\n return c.json(result.data);\n});\n\nexport default route;\n";
8734
+ //#endregion
8735
+ //#region src/services/starter-loader.ts
8736
+ var STARTERS_PATH_RE = /(?:^|\/)starters\/([^/]+)\/(.+)$/;
8737
+ var OTHER_CATEGORY = "Other";
8738
+ var slugFromManifestPath$1 = (path) => {
8739
+ const match = STARTERS_PATH_RE.exec(path);
8740
+ if (!match || match[2] !== "package.json") return null;
8741
+ return match[1];
8742
+ };
8743
+ var slugFromFilePath = (path) => {
8744
+ const match = STARTERS_PATH_RE.exec(path);
8745
+ if (!match) return null;
8746
+ const rel = match[2];
8747
+ if (rel === "package.json") return null;
8748
+ return {
8749
+ slug: match[1],
8750
+ rel
8751
+ };
8752
+ };
8753
+ var requireString = (value, field, slug) => {
8754
+ if (typeof value !== "string" || value.length === 0) throw new Error(`Starter '${slug}': field 'stackbone.${field}' must be a non-empty string.`);
8381
8755
  return value;
8382
8756
  };
8383
- var idFromManifestPath = (path) => {
8384
- const match = path.match(/\/templates\/([^/]+)\/template\.yaml$/);
8385
- if (!match || !match[1]) throw new Error(`Unexpected manifest path '${path}' (expected '../templates/<id>/template.yaml').`);
8386
- return match[1];
8757
+ var parseManifest = (slug, raw) => {
8758
+ let parsed;
8759
+ try {
8760
+ parsed = JSON.parse(raw);
8761
+ } catch (error) {
8762
+ const reason = error instanceof Error ? error.message : String(error);
8763
+ throw new Error(`Starter '${slug}': package.json is not valid JSON (${reason}).`);
8764
+ }
8765
+ if (typeof parsed.name !== "string" || parsed.name.length === 0) throw new Error(`Starter '${slug}': package.json 'name' must be a non-empty string.`);
8766
+ const expected = `starter-${slug}`;
8767
+ if (parsed.name !== expected) throw new Error(`Starter '${slug}': package.json 'name' is '${parsed.name}' but must be '${expected}'.`);
8768
+ const stackbone = parsed.stackbone ?? {};
8769
+ const label = requireString(stackbone.label, "label", slug);
8770
+ const hint = requireString(stackbone.hint, "hint", slug);
8771
+ let order = null;
8772
+ if (stackbone.order !== void 0) {
8773
+ if (typeof stackbone.order !== "number" || !Number.isFinite(stackbone.order)) throw new Error(`Starter '${slug}': field 'stackbone.order' must be a finite number.`);
8774
+ order = stackbone.order;
8775
+ }
8776
+ let category = null;
8777
+ if (stackbone.category !== void 0) {
8778
+ if (typeof stackbone.category !== "string" || stackbone.category.length === 0) throw new Error(`Starter '${slug}': field 'stackbone.category' must be a non-empty string when present.`);
8779
+ category = stackbone.category;
8780
+ }
8781
+ return {
8782
+ packageName: parsed.name,
8783
+ meta: {
8784
+ label,
8785
+ hint,
8786
+ order,
8787
+ category
8788
+ }
8789
+ };
8387
8790
  };
8388
- var groupFilesByTemplate = () => {
8791
+ var groupFiles = (fileMap) => {
8389
8792
  const grouped = /* @__PURE__ */ new Map();
8390
- for (const [path, contents] of Object.entries(fileModules)) {
8391
- const match = path.match(/\/templates\/([^/]+)\/(.+)$/);
8392
- if (!match) continue;
8393
- const id = match[1];
8394
- const rel = match[2];
8395
- let bucket = grouped.get(id);
8793
+ for (const [path, contents] of Object.entries(fileMap)) {
8794
+ const parsed = slugFromFilePath(path);
8795
+ if (!parsed) continue;
8796
+ let bucket = grouped.get(parsed.slug);
8396
8797
  if (!bucket) {
8397
8798
  bucket = [];
8398
- grouped.set(id, bucket);
8799
+ grouped.set(parsed.slug, bucket);
8399
8800
  }
8400
8801
  bucket.push({
8401
- path: rel,
8802
+ path: parsed.rel,
8402
8803
  contents
8403
8804
  });
8404
8805
  }
8405
8806
  for (const bucket of grouped.values()) bucket.sort((a, b) => a.path.localeCompare(b.path));
8406
8807
  return grouped;
8407
8808
  };
8408
- var validateVariables = (id, variables, files) => {
8409
- const declared = new Set(variables);
8410
- const undeclared = /* @__PURE__ */ new Set();
8411
- for (const file of files) for (const match of file.contents.matchAll(TOKEN_PATTERN)) {
8412
- const name = match[1];
8413
- if (name && !declared.has(name)) undeclared.add(name);
8809
+ /**
8810
+ * Compare two starters by the documented ordering: `(category, order, slug)`.
8811
+ *
8812
+ * - `category`: starters without a declared category fall into an `Other` bucket
8813
+ * placed after named categories (lex order among named categories).
8814
+ * - `order`: lower numeric values come first; missing `order` is treated as
8815
+ * `+Infinity` so explicit-order starters always win against defaults.
8816
+ * - `slug`: final tiebreaker, lexicographic.
8817
+ */
8818
+ var compareStarters = (a, b) => {
8819
+ const catA = a.metadata.category ?? OTHER_CATEGORY;
8820
+ const catB = b.metadata.category ?? OTHER_CATEGORY;
8821
+ if (catA !== catB) {
8822
+ if (catA === OTHER_CATEGORY) return 1;
8823
+ if (catB === OTHER_CATEGORY) return -1;
8824
+ return catA.localeCompare(catB);
8414
8825
  }
8415
- if (undeclared.size > 0) throw new Error(`Template '${id}' uses undeclared variables: ${[...undeclared].join(", ")}. Add them to template.yaml 'variables'.`);
8416
- };
8417
- var parseManifest = (id, raw, files) => {
8418
- const parsed = parse(raw);
8419
- if (!parsed) throw new Error(`Template '${id}': empty or invalid YAML.`);
8420
- if (parsed.id !== id) throw new Error(`Template '${id}': manifest id '${String(parsed.id)}' does not match directory name.`);
8421
- if (typeof parsed.order !== "number") throw new Error(`Template '${id}': field 'order' must be a number.`);
8422
- const variables = Array.isArray(parsed.variables) ? parsed.variables.map(String) : [];
8423
- validateVariables(id, variables, files);
8424
- return {
8425
- id,
8426
- label: requireString(parsed.label, "label", id),
8427
- hint: requireString(parsed.hint, "hint", id),
8428
- order: parsed.order,
8429
- variables,
8430
- files
8431
- };
8826
+ const orderA = a.metadata.order ?? Number.POSITIVE_INFINITY;
8827
+ const orderB = b.metadata.order ?? Number.POSITIVE_INFINITY;
8828
+ if (orderA !== orderB) return orderA - orderB;
8829
+ return a.slug.localeCompare(b.slug);
8432
8830
  };
8433
- var loadTemplates = () => {
8434
- const filesByTemplate = groupFilesByTemplate();
8435
- const all = [];
8436
- for (const [path, raw] of Object.entries(manifestModules)) {
8437
- const id = idFromManifestPath(path);
8438
- all.push(parseManifest(id, raw, filesByTemplate.get(id) ?? []));
8831
+ /**
8832
+ * Load starters from raw `import.meta.glob` maps.
8833
+ *
8834
+ * @param manifestMap one entry per `starters/<slug>/package.json` (raw JSON contents)
8835
+ * @param fileMap every other file under `starters/<slug>/**` (raw string contents)
8836
+ * @returns the loaded starters ordered by `(category, order, slug)`
8837
+ */
8838
+ var loadStarters = (manifestMap, fileMap) => {
8839
+ const filesBySlug = groupFiles(fileMap);
8840
+ const loaded = [];
8841
+ for (const [path, raw] of Object.entries(manifestMap)) {
8842
+ const slug = slugFromManifestPath$1(path);
8843
+ if (!slug) continue;
8844
+ const { packageName, meta } = parseManifest(slug, raw);
8845
+ loaded.push({
8846
+ slug,
8847
+ packageName,
8848
+ metadata: meta,
8849
+ files: filesBySlug.get(slug) ?? []
8850
+ });
8439
8851
  }
8440
- all.sort((a, b) => a.order - b.order || a.id.localeCompare(b.id));
8441
- return new Map(all.map((t) => [t.id, t]));
8442
- };
8443
- var TEMPLATES_BY_ID = loadTemplates();
8444
- var TEMPLATES = [...TEMPLATES_BY_ID.values()].map((t) => ({
8445
- id: t.id,
8446
- label: t.label,
8447
- hint: t.hint
8448
- }));
8449
- var parseTemplateId = (value) => {
8450
- if (!value) return null;
8451
- return TEMPLATES_BY_ID.has(value) ? value : null;
8852
+ loaded.sort(compareStarters);
8853
+ return loaded;
8452
8854
  };
8453
- var substitute = (contents, input) => contents.replace(TOKEN_PATTERN, (_match, key) => {
8454
- if (key in input) return input[key];
8455
- throw new Error(`Template variable '${key}' is not provided. Known: ${Object.keys(input).join(", ")}.`);
8456
- });
8457
- var renderTemplate = (templateId, input) => {
8458
- const template = TEMPLATES_BY_ID.get(templateId);
8459
- if (!template) throw new Error(`Unknown template id '${templateId}'. Known: ${[...TEMPLATES_BY_ID.keys()].join(", ")}.`);
8460
- return template.files.map((file) => ({
8855
+ //#endregion
8856
+ //#region src/services/starter-renderer.ts
8857
+ /**
8858
+ * Replace every literal occurrence of `needle` in `haystack` with
8859
+ * `replacement`. The needle is treated as a literal string even when it
8860
+ * contains regex-special characters.
8861
+ */
8862
+ var replaceAllLiteral = (haystack, needle, replacement) => {
8863
+ if (needle.length === 0) return haystack;
8864
+ return haystack.split(needle).join(replacement);
8865
+ };
8866
+ /**
8867
+ * Render a loaded starter into a file collection ready to be written to disk.
8868
+ *
8869
+ * The renderer is pure and idempotent: re-running with the same `agentSlug`
8870
+ * on already-rendered contents is a no-op.
8871
+ *
8872
+ * @param starter the loaded starter, carrying its `packageName` source string
8873
+ * @param options `{ agentSlug }` — the target slug to substitute in contents
8874
+ * @returns a new file collection with substituted contents; paths preserved
8875
+ */
8876
+ var renderStarter$1 = (starter, options) => {
8877
+ const { agentSlug } = options;
8878
+ const { packageName } = starter;
8879
+ return starter.files.map((file) => ({
8461
8880
  path: file.path,
8462
- contents: substitute(file.contents, input),
8463
- ...file.executable ? { executable: true } : {}
8881
+ contents: replaceAllLiteral(file.contents, packageName, agentSlug)
8464
8882
  }));
8465
8883
  };
8466
8884
  //#endregion
8885
+ //#region src/services/scaffolding.ts
8886
+ var manifestModules = /* @__PURE__ */ Object.assign({
8887
+ "../../../../starters/ai/package.json": package_default$9,
8888
+ "../../../../starters/config/package.json": package_default$8,
8889
+ "../../../../starters/db/package.json": package_default$7,
8890
+ "../../../../starters/hello-world/package.json": package_default$6,
8891
+ "../../../../starters/hitl/package.json": package_default$5,
8892
+ "../../../../starters/prompts/package.json": package_default$4,
8893
+ "../../../../starters/queues/package.json": package_default$3,
8894
+ "../../../../starters/rag/package.json": package_default$2,
8895
+ "../../../../starters/secrets/package.json": package_default$1,
8896
+ "../../../../starters/storage/package.json": package_default
8897
+ });
8898
+ var fileModules = /* @__PURE__ */ Object.assign({
8899
+ "../../../../starters/ai/.claude/skills/stackbone/SKILL.md": SKILL_default$8,
8900
+ "../../../../starters/ai/.gitignore": _gitignore_raw_default$8,
8901
+ "../../../../starters/ai/Dockerfile": Dockerfile_raw_default$9,
8902
+ "../../../../starters/ai/README.md": README_default$9,
8903
+ "../../../../starters/ai/agent.yaml": agent_default$9,
8904
+ "../../../../starters/ai/src/index.ts": src_default$9,
8905
+ "../../../../starters/ai/src/lib/client.ts": client_default$8,
8906
+ "../../../../starters/ai/src/lib/logger.ts": logger_default$8,
8907
+ "../../../../starters/ai/src/routes/embeddings.ts": embeddings_default,
8908
+ "../../../../starters/ai/src/routes/health.ts": health_default$8,
8909
+ "../../../../starters/ai/src/routes/invoke.ts": invoke_default$8,
8910
+ "../../../../starters/ai/src/routes/models.ts": models_default,
8911
+ "../../../../starters/ai/src/routes/schema-info.ts": schema_info_default$8,
8912
+ "../../../../starters/config/.claude/skills/stackbone/SKILL.md": SKILL_default$7,
8913
+ "../../../../starters/config/.gitignore": _gitignore_raw_default$7,
8914
+ "../../../../starters/config/Dockerfile": Dockerfile_raw_default$8,
8915
+ "../../../../starters/config/README.md": README_default$8,
8916
+ "../../../../starters/config/agent.yaml": agent_default$8,
8917
+ "../../../../starters/config/src/index.ts": src_default$8,
8918
+ "../../../../starters/config/src/lib/client.ts": client_default$7,
8919
+ "../../../../starters/config/src/lib/logger.ts": logger_default$7,
8920
+ "../../../../starters/config/src/routes/get-config.ts": get_config_default,
8921
+ "../../../../starters/config/src/routes/get-many.ts": get_many_default$1,
8922
+ "../../../../starters/config/src/routes/health.ts": health_default$7,
8923
+ "../../../../starters/config/src/routes/invoke.ts": invoke_default$7,
8924
+ "../../../../starters/config/src/routes/schema-info.ts": schema_info_default$7,
8925
+ "../../../../starters/db/.claude/skills/stackbone/SKILL.md": SKILL_default$6,
8926
+ "../../../../starters/db/.gitignore": _gitignore_raw_default$6,
8927
+ "../../../../starters/db/Dockerfile": Dockerfile_raw_default$7,
8928
+ "../../../../starters/db/README.md": README_default$7,
8929
+ "../../../../starters/db/agent.yaml": agent_default$7,
8930
+ "../../../../starters/db/src/index.ts": src_default$7,
8931
+ "../../../../starters/db/src/lib/client.ts": client_default$6,
8932
+ "../../../../starters/db/src/lib/logger.ts": logger_default$6,
8933
+ "../../../../starters/db/src/routes/delete-example.ts": delete_example_default,
8934
+ "../../../../starters/db/src/routes/health.ts": health_default$6,
8935
+ "../../../../starters/db/src/routes/invoke.ts": invoke_default$6,
8936
+ "../../../../starters/db/src/routes/list-examples.ts": list_examples_default,
8937
+ "../../../../starters/db/src/routes/schema-info.ts": schema_info_default$6,
8938
+ "../../../../starters/db/src/schema.ts": schema_default$1,
8939
+ "../../../../starters/hello-world/.stackbone/migrations/0000_init.sql": _0000_init_default,
8940
+ "../../../../starters/hello-world/.stackbone/migrations/meta/0000_snapshot.json": _0000_snapshot_default,
8941
+ "../../../../starters/hello-world/.stackbone/migrations/meta/_journal.json": _journal_default,
8942
+ "../../../../starters/hello-world/Dockerfile": Dockerfile_raw_default$6,
8943
+ "../../../../starters/hello-world/README.md": README_default$6,
8944
+ "../../../../starters/hello-world/agent.yaml": agent_default$6,
8945
+ "../../../../starters/hello-world/src/index.ts": src_default$6,
8946
+ "../../../../starters/hello-world/src/schema.ts": schema_default,
8947
+ "../../../../starters/hitl/.claude/skills/stackbone/SKILL.md": SKILL_default$5,
8948
+ "../../../../starters/hitl/.gitignore": _gitignore_raw_default$5,
8949
+ "../../../../starters/hitl/Dockerfile": Dockerfile_raw_default$5,
8950
+ "../../../../starters/hitl/README.md": README_default$5,
8951
+ "../../../../starters/hitl/agent.yaml": agent_default$5,
8952
+ "../../../../starters/hitl/src/index.ts": src_default$5,
8953
+ "../../../../starters/hitl/src/lib/client.ts": client_default$5,
8954
+ "../../../../starters/hitl/src/lib/logger.ts": logger_default$5,
8955
+ "../../../../starters/hitl/src/routes/cancel.ts": cancel_default,
8956
+ "../../../../starters/hitl/src/routes/decide.ts": decide_default,
8957
+ "../../../../starters/hitl/src/routes/get-approval.ts": get_approval_default,
8958
+ "../../../../starters/hitl/src/routes/health.ts": health_default$5,
8959
+ "../../../../starters/hitl/src/routes/invoke.ts": invoke_default$5,
8960
+ "../../../../starters/hitl/src/routes/list-approvals.ts": list_approvals_default,
8961
+ "../../../../starters/hitl/src/routes/schema-info.ts": schema_info_default$5,
8962
+ "../../../../starters/prompts/.claude/skills/stackbone/SKILL.md": SKILL_default$4,
8963
+ "../../../../starters/prompts/.gitignore": _gitignore_raw_default$4,
8964
+ "../../../../starters/prompts/Dockerfile": Dockerfile_raw_default$4,
8965
+ "../../../../starters/prompts/README.md": README_default$4,
8966
+ "../../../../starters/prompts/agent.yaml": agent_default$4,
8967
+ "../../../../starters/prompts/src/index.ts": src_default$4,
8968
+ "../../../../starters/prompts/src/lib/client.ts": client_default$4,
8969
+ "../../../../starters/prompts/src/lib/logger.ts": logger_default$4,
8970
+ "../../../../starters/prompts/src/routes/create-prompt.ts": create_prompt_default,
8971
+ "../../../../starters/prompts/src/routes/get-prompt.ts": get_prompt_default,
8972
+ "../../../../starters/prompts/src/routes/health.ts": health_default$4,
8973
+ "../../../../starters/prompts/src/routes/invoke.ts": invoke_default$4,
8974
+ "../../../../starters/prompts/src/routes/list-prompts.ts": list_prompts_default,
8975
+ "../../../../starters/prompts/src/routes/schema-info.ts": schema_info_default$4,
8976
+ "../../../../starters/queues/.claude/skills/stackbone/SKILL.md": SKILL_default$3,
8977
+ "../../../../starters/queues/.gitignore": _gitignore_raw_default$3,
8978
+ "../../../../starters/queues/Dockerfile": Dockerfile_raw_default$3,
8979
+ "../../../../starters/queues/README.md": README_default$3,
8980
+ "../../../../starters/queues/agent.yaml": agent_default$3,
8981
+ "../../../../starters/queues/src/index.ts": src_default$3,
8982
+ "../../../../starters/queues/src/lib/client.ts": client_default$3,
8983
+ "../../../../starters/queues/src/lib/logger.ts": logger_default$3,
8984
+ "../../../../starters/queues/src/routes/health.ts": health_default$3,
8985
+ "../../../../starters/queues/src/routes/invoke.ts": invoke_default$3,
8986
+ "../../../../starters/queues/src/routes/publish.ts": publish_default,
8987
+ "../../../../starters/queues/src/routes/receive.ts": receive_default,
8988
+ "../../../../starters/queues/src/routes/schema-info.ts": schema_info_default$3,
8989
+ "../../../../starters/rag/.claude/skills/stackbone/SKILL.md": SKILL_default$2,
8990
+ "../../../../starters/rag/.gitignore": _gitignore_raw_default$2,
8991
+ "../../../../starters/rag/Dockerfile": Dockerfile_raw_default$2,
8992
+ "../../../../starters/rag/README.md": README_default$2,
8993
+ "../../../../starters/rag/agent.yaml": agent_default$2,
8994
+ "../../../../starters/rag/src/index.ts": src_default$2,
8995
+ "../../../../starters/rag/src/lib/client.ts": client_default$2,
8996
+ "../../../../starters/rag/src/lib/logger.ts": logger_default$2,
8997
+ "../../../../starters/rag/src/routes/chunk.ts": chunk_default,
8998
+ "../../../../starters/rag/src/routes/delete.ts": delete_default,
8999
+ "../../../../starters/rag/src/routes/health.ts": health_default$2,
9000
+ "../../../../starters/rag/src/routes/ingest.ts": ingest_default,
9001
+ "../../../../starters/rag/src/routes/invoke.ts": invoke_default$2,
9002
+ "../../../../starters/rag/src/routes/retrieve.ts": retrieve_default,
9003
+ "../../../../starters/rag/src/routes/schema-info.ts": schema_info_default$2,
9004
+ "../../../../starters/secrets/.claude/skills/stackbone/SKILL.md": SKILL_default$1,
9005
+ "../../../../starters/secrets/.gitignore": _gitignore_raw_default$1,
9006
+ "../../../../starters/secrets/Dockerfile": Dockerfile_raw_default$1,
9007
+ "../../../../starters/secrets/README.md": README_default$1,
9008
+ "../../../../starters/secrets/agent.yaml": agent_default$1,
9009
+ "../../../../starters/secrets/src/index.ts": src_default$1,
9010
+ "../../../../starters/secrets/src/lib/client.ts": client_default$1,
9011
+ "../../../../starters/secrets/src/lib/logger.ts": logger_default$1,
9012
+ "../../../../starters/secrets/src/routes/get-many.ts": get_many_default,
9013
+ "../../../../starters/secrets/src/routes/get-secret.ts": get_secret_default,
9014
+ "../../../../starters/secrets/src/routes/health.ts": health_default$1,
9015
+ "../../../../starters/secrets/src/routes/invoke.ts": invoke_default$1,
9016
+ "../../../../starters/secrets/src/routes/schema-info.ts": schema_info_default$1,
9017
+ "../../../../starters/storage/.claude/skills/stackbone/SKILL.md": SKILL_default,
9018
+ "../../../../starters/storage/.gitignore": _gitignore_raw_default,
9019
+ "../../../../starters/storage/Dockerfile": Dockerfile_raw_default,
9020
+ "../../../../starters/storage/README.md": README_default,
9021
+ "../../../../starters/storage/agent.yaml": agent_default,
9022
+ "../../../../starters/storage/src/index.ts": src_default,
9023
+ "../../../../starters/storage/src/lib/client.ts": client_default,
9024
+ "../../../../starters/storage/src/lib/logger.ts": logger_default,
9025
+ "../../../../starters/storage/src/routes/delete-object.ts": delete_object_default,
9026
+ "../../../../starters/storage/src/routes/download.ts": download_default,
9027
+ "../../../../starters/storage/src/routes/health.ts": health_default,
9028
+ "../../../../starters/storage/src/routes/invoke.ts": invoke_default,
9029
+ "../../../../starters/storage/src/routes/list-objects.ts": list_objects_default,
9030
+ "../../../../starters/storage/src/routes/schema-info.ts": schema_info_default,
9031
+ "../../../../starters/storage/src/routes/signed-url.ts": signed_url_default
9032
+ });
9033
+ var slugFromManifestPath = (path) => {
9034
+ const match = /(?:^|\/)starters\/([^/]+)\/package\.json$/.exec(path);
9035
+ return match ? match[1] : null;
9036
+ };
9037
+ var manifestContentsBySlug = new Map(Object.entries(manifestModules).map(([path, raw]) => {
9038
+ const slug = slugFromManifestPath(path);
9039
+ return slug ? [slug, raw] : null;
9040
+ }).filter((entry) => entry !== null));
9041
+ var withManifestFile = (starter) => {
9042
+ const manifestContents = manifestContentsBySlug.get(starter.slug);
9043
+ if (!manifestContents) return starter;
9044
+ const files = [{
9045
+ path: "package.json",
9046
+ contents: manifestContents
9047
+ }, ...starter.files];
9048
+ files.sort((a, b) => a.path.localeCompare(b.path));
9049
+ return {
9050
+ ...starter,
9051
+ files
9052
+ };
9053
+ };
9054
+ var STARTERS_BY_ID = new Map(loadStarters(manifestModules, fileModules).map(withManifestFile).map((s) => [s.slug, s]));
9055
+ var STARTERS = [...STARTERS_BY_ID.values()].map((s) => ({
9056
+ id: s.slug,
9057
+ label: s.metadata.label,
9058
+ hint: s.metadata.hint
9059
+ }));
9060
+ var parseStarterId = (value) => {
9061
+ if (!value) return null;
9062
+ return STARTERS_BY_ID.has(value) ? value : null;
9063
+ };
9064
+ var renderStarter = (starterId, input) => {
9065
+ const starter = STARTERS_BY_ID.get(starterId);
9066
+ if (!starter) throw new Error(`Unknown starter id '${starterId}'. Known: ${[...STARTERS_BY_ID.keys()].join(", ")}.`);
9067
+ return renderStarter$1(starter, { agentSlug: input.agentSlug });
9068
+ };
9069
+ var STARTER_DESCRIPTORS = [...STARTERS_BY_ID.values()].map((s) => ({
9070
+ id: s.slug,
9071
+ label: s.metadata.label,
9072
+ hint: s.metadata.hint,
9073
+ category: s.metadata.category,
9074
+ order: s.metadata.order
9075
+ }));
9076
+ //#endregion
8467
9077
  //#region src/commands/init/init.command.ts
9078
+ var DEFAULT_STARTER_ID = "hello-world";
8468
9079
  var STACKBONE_OWNED_FILES = [
8469
9080
  "agent.yaml",
8470
9081
  ".stackbone/project.json",
@@ -8479,6 +9090,11 @@ var STACKBONE_OWNED_FILES = [
8479
9090
  ".claude/skills/stackbone/SKILL.md"
8480
9091
  ];
8481
9092
  var NAME_REGEX = /^[a-z0-9][a-z0-9-]{0,63}$/i;
9093
+ var CATEGORY_PRIORITY = {
9094
+ Core: 0,
9095
+ Capabilities: 1
9096
+ };
9097
+ var UNCATEGORISED_LABEL = "Other";
8482
9098
  var directoryNameOrFallback = (cwd) => {
8483
9099
  const base = basename(cwd) || "stackbone-agent";
8484
9100
  return NAME_REGEX.test(base) ? base : "stackbone-agent";
@@ -8503,8 +9119,18 @@ var ensureEmptyOrForce = async (targetDir, force) => {
8503
9119
  suggestion: "Re-run with `--force` or pick an empty directory"
8504
9120
  });
8505
9121
  };
9122
+ var bailIfCancelled = (value) => {
9123
+ if (isCancel(value)) {
9124
+ cancel("Init cancelled.");
9125
+ throw new StackboneCliError({
9126
+ code: "generic",
9127
+ message: "Init cancelled by user"
9128
+ });
9129
+ }
9130
+ return value;
9131
+ };
8506
9132
  var promptForName = async (defaultName) => {
8507
- const value = await text({
9133
+ return bailIfCancelled(await text({
8508
9134
  message: "Agent name",
8509
9135
  placeholder: defaultName,
8510
9136
  defaultValue: defaultName,
@@ -8513,42 +9139,58 @@ var promptForName = async (defaultName) => {
8513
9139
  if (v.length === 0) return "Name cannot be empty";
8514
9140
  if (v.length > 120) return "Name is too long";
8515
9141
  }
8516
- });
8517
- if (isCancel(value)) {
8518
- cancel("Init cancelled.");
8519
- throw new StackboneCliError({
8520
- code: "generic",
8521
- message: "Init cancelled by user"
8522
- });
8523
- }
8524
- return value.trim();
8525
- };
8526
- var promptForTemplate = async () => {
8527
- const value = await select({
8528
- message: "Pick a template",
8529
- options: TEMPLATES.map((t) => ({
8530
- value: t.id,
8531
- label: t.label,
8532
- hint: t.hint
8533
- }))
8534
- });
8535
- if (isCancel(value)) {
8536
- cancel("Init cancelled.");
8537
- throw new StackboneCliError({
8538
- code: "generic",
8539
- message: "Init cancelled by user"
9142
+ })).trim();
9143
+ };
9144
+ var buildPickerOptions = () => {
9145
+ const sections = /* @__PURE__ */ new Map();
9146
+ for (const starter of STARTER_DESCRIPTORS) {
9147
+ const category = starter.category ?? UNCATEGORISED_LABEL;
9148
+ let bucket = sections.get(category);
9149
+ if (!bucket) {
9150
+ bucket = [];
9151
+ sections.set(category, bucket);
9152
+ }
9153
+ bucket.push({
9154
+ value: starter.id,
9155
+ label: `[${category}] ${starter.label}`,
9156
+ hint: starter.hint
8540
9157
  });
8541
9158
  }
8542
- return value;
9159
+ const sectionOrder = (name) => {
9160
+ if (name === UNCATEGORISED_LABEL) return [Number.POSITIVE_INFINITY, name];
9161
+ return [CATEGORY_PRIORITY[name] ?? Number.POSITIVE_INFINITY - 1, name];
9162
+ };
9163
+ return [...sections.entries()].sort(([a], [b]) => {
9164
+ const [pa, na] = sectionOrder(a);
9165
+ const [pb, nb] = sectionOrder(b);
9166
+ if (pa !== pb) return pa - pb;
9167
+ return na.localeCompare(nb);
9168
+ }).flatMap(([, options]) => options);
9169
+ };
9170
+ var promptForStarter = async () => {
9171
+ return bailIfCancelled(await select({
9172
+ message: "Pick a starter",
9173
+ options: buildPickerOptions()
9174
+ }));
8543
9175
  };
8544
- var resolveInputs = async (ctx, flags, namePositional) => {
9176
+ var validSlugList = () => STARTERS.map((s) => s.id).join(", ");
9177
+ var resolveInitInputs = async (ctx, flags, namePositional) => {
9178
+ if (flags.legacyTemplate !== void 0) throw new StackboneCliError({
9179
+ code: "generic",
9180
+ message: `--template was renamed to --starter; rerun with --starter ${flags.legacyTemplate}`
9181
+ });
8545
9182
  const targetDir = resolve(ctx.cwd, namePositional ?? ".");
8546
9183
  const fallbackName = namePositional ?? directoryNameOrFallback(targetDir);
8547
9184
  const interactive = isInteractiveStdin(ctx.env) && !ctx.flags.yes && !ctx.flags.json;
8548
9185
  let agentName = flags.name ?? namePositional ?? (interactive ? await promptForName(fallbackName) : fallbackName);
8549
9186
  agentName = agentName.trim();
8550
- let templateId = parseTemplateId(flags.template);
8551
- if (!templateId) templateId = interactive ? await promptForTemplate() : "hello-world";
9187
+ let starterId = parseStarterId(flags.starter);
9188
+ if (flags.starter !== void 0 && starterId === null) throw new StackboneCliError({
9189
+ code: "generic",
9190
+ message: `Unknown starter '${flags.starter}'`,
9191
+ suggestion: `Valid starters: ${validSlugList()}`
9192
+ });
9193
+ if (!starterId) starterId = interactive ? await promptForStarter() : DEFAULT_STARTER_ID;
8552
9194
  if (flags.slug !== void 0) {
8553
9195
  if (!agentSlugSchema.safeParse(flags.slug).success) throw new StackboneCliError({
8554
9196
  code: "generic",
@@ -8560,7 +9202,7 @@ var resolveInputs = async (ctx, flags, namePositional) => {
8560
9202
  agentName,
8561
9203
  agentSlug: flags.slug,
8562
9204
  description: flags.description,
8563
- templateId,
9205
+ starterId,
8564
9206
  targetDir
8565
9207
  };
8566
9208
  };
@@ -8570,21 +9212,19 @@ var writeProject = async (ctx, resolved, agent, force, localDev) => {
8570
9212
  force
8571
9213
  };
8572
9214
  await mkdir(resolved.targetDir, { recursive: true });
8573
- const templateFiles = renderTemplate(resolved.templateId, {
8574
- agentName: resolved.agentName,
8575
- agentSlug: agent.slug
8576
- });
8577
- await writeTemplateFiles(templateFiles, writeInput);
9215
+ const starterFiles = renderStarter(resolved.starterId, { agentSlug: agent.slug });
9216
+ await writeStarterFiles(starterFiles, writeInput);
8578
9217
  await writeAgentManifest(resolved.agentName, writeInput);
8579
9218
  const projectConfig = await writeProjectConfig(ctx, agent, writeInput, {
8580
9219
  localDevInstallationId: localDev.id,
8581
9220
  organizationSlug: localDev.organizationSlug,
8582
- agentSlug: localDev.agentSlug
9221
+ agentSlug: localDev.agentSlug,
9222
+ starter: resolved.starterId
8583
9223
  });
8584
9224
  await ensureGitignore(resolved.targetDir);
8585
9225
  const skillFiles = await writeSkillFiles(ctx, agent, projectConfig, writeInput);
8586
9226
  return { filesWritten: [
8587
- ...templateFiles.map((f) => f.path),
9227
+ ...starterFiles.map((f) => f.path),
8588
9228
  "agent.yaml",
8589
9229
  ".stackbone/project.json",
8590
9230
  ".gitignore",
@@ -8595,14 +9235,15 @@ var buildHandler$1 = (ctx, args) => protectedCommand(async (innerCtx) => {
8595
9235
  if (!innerCtx.flags.json) intro("stackbone init");
8596
9236
  const flags = {
8597
9237
  name: args.name,
8598
- template: args.template,
9238
+ starter: args.starter,
9239
+ legacyTemplate: args.template,
8599
9240
  slug: args.slug,
8600
9241
  description: args.description,
8601
9242
  force: Boolean(args.force)
8602
9243
  };
8603
- const resolved = await resolveInputs(innerCtx, flags, args.name);
9244
+ const resolved = await resolveInitInputs(innerCtx, flags, args.name);
8604
9245
  await ensureEmptyOrForce(resolved.targetDir, flags.force);
8605
- if (!innerCtx.flags.json) log.step(`Creating "${resolved.agentName}" (template: ${resolved.templateId})`);
9246
+ if (!innerCtx.flags.json) log.step(`Creating "${resolved.agentName}" (starter: ${resolved.starterId})`);
8606
9247
  const activeOrg = await getActiveOrganization(innerCtx);
8607
9248
  const agent = await createAgent(innerCtx, {
8608
9249
  organizationSlug: activeOrg.slug,
@@ -8631,7 +9272,7 @@ var buildHandler$1 = (ctx, args) => protectedCommand(async (innerCtx) => {
8631
9272
  organization_slug: localDev.organizationSlug
8632
9273
  },
8633
9274
  target_dir: resolved.targetDir,
8634
- template: resolved.templateId,
9275
+ starter: resolved.starterId,
8635
9276
  files_written: filesWritten
8636
9277
  });
8637
9278
  if (!innerCtx.flags.json) outro([
@@ -8647,7 +9288,7 @@ var buildHandler$1 = (ctx, args) => protectedCommand(async (innerCtx) => {
8647
9288
  var createInitCommand = (ctx) => defineCommand({
8648
9289
  meta: {
8649
9290
  name: "init",
8650
- description: "Scaffold a new Stackbone project with templates + Claude skill."
9291
+ description: "Scaffold a new Stackbone project with starters + Claude skill."
8651
9292
  },
8652
9293
  args: {
8653
9294
  name: {
@@ -8655,9 +9296,13 @@ var createInitCommand = (ctx) => defineCommand({
8655
9296
  description: "Subdirectory + initial agent name. Omit to use the current directory.",
8656
9297
  required: false
8657
9298
  },
9299
+ starter: {
9300
+ type: "string",
9301
+ description: `Starter slug. One of ${validSlugList()}. Default: ${DEFAULT_STARTER_ID}.`
9302
+ },
8658
9303
  template: {
8659
9304
  type: "string",
8660
- description: `Template id. One of ${TEMPLATES.map((t) => t.id).join(", ")}. Default: hello-world.`
9305
+ description: "(removed) renamed to --starter; pre-rename flag kept only to emit the migration message."
8661
9306
  },
8662
9307
  slug: {
8663
9308
  type: "string",
@@ -8977,6 +9622,11 @@ var buildRootCommand = async () => {
8977
9622
  "api-url": {
8978
9623
  type: "string",
8979
9624
  description: "Override the control plane URL."
9625
+ },
9626
+ verbose: {
9627
+ type: "boolean",
9628
+ description: "Stream every log line and docker compose output instead of the per-stage spinner UI.",
9629
+ default: false
8980
9630
  }
8981
9631
  },
8982
9632
  subCommands: {