@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/CHANGELOG.md +23 -0
- package/main.js +880 -230
- package/package.json +1 -1
- package/stackbone-cli-0.1.0-alpha.4.tgz +0 -0
- package/stackbone-cli-0.1.0-alpha.3.tgz +0 -0
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.
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
##
|
|
7814
|
+
## Pending
|
|
7807
7815
|
|
|
7808
|
-
- \`stackbone
|
|
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
|
|
7821
|
-
|
|
7822
|
-
|
|
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 +
|
|
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\` —
|
|
7834
|
+
- \`client.queues\` — pgmq publisher (surface defined, runtime pending)
|
|
7831
7835
|
- \`client.rag\` — LlamaParse (opt-in) + chunker + embeddings + pgvector + tsvector
|
|
7832
|
-
- \`client.observability\` —
|
|
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.*\` —
|
|
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 (\`
|
|
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, \`
|
|
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
|
-
-
|
|
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
|
|
7864
|
-
|
|
7865
|
-
|
|
7866
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
8266
|
-
var
|
|
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
|
|
8269
|
-
var
|
|
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
|
|
8272
|
-
var
|
|
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
|
|
8275
|
-
var
|
|
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
|
|
8278
|
-
var
|
|
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
|
|
8281
|
-
var
|
|
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
|
|
8284
|
-
var
|
|
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
|
|
8287
|
-
var
|
|
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
|
|
8290
|
-
var package_default$
|
|
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
|
|
8293
|
-
var
|
|
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
|
|
8296
|
-
var
|
|
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
|
|
8299
|
-
var
|
|
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
|
|
8302
|
-
var
|
|
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
|
|
8305
|
-
var
|
|
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
|
|
8308
|
-
var
|
|
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
|
|
8311
|
-
var
|
|
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/
|
|
8314
|
-
var
|
|
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
|
|
8317
|
-
var
|
|
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/
|
|
8320
|
-
var
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
8335
|
-
var README_default = "#
|
|
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
|
|
8338
|
-
var
|
|
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
|
|
8341
|
-
var src_default = "import { serve } from '@hono/node-server';\nimport { Hono } from 'hono';\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
|
|
8344
|
-
var
|
|
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/
|
|
8347
|
-
var
|
|
8348
|
-
|
|
8349
|
-
|
|
8350
|
-
|
|
8351
|
-
|
|
8352
|
-
|
|
8353
|
-
var
|
|
8354
|
-
|
|
8355
|
-
|
|
8356
|
-
|
|
8357
|
-
|
|
8358
|
-
|
|
8359
|
-
|
|
8360
|
-
|
|
8361
|
-
|
|
8362
|
-
|
|
8363
|
-
|
|
8364
|
-
|
|
8365
|
-
|
|
8366
|
-
|
|
8367
|
-
|
|
8368
|
-
|
|
8369
|
-
|
|
8370
|
-
|
|
8371
|
-
|
|
8372
|
-
|
|
8373
|
-
|
|
8374
|
-
|
|
8375
|
-
"
|
|
8376
|
-
|
|
8377
|
-
|
|
8378
|
-
|
|
8379
|
-
|
|
8380
|
-
if (
|
|
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
|
|
8384
|
-
|
|
8385
|
-
|
|
8386
|
-
|
|
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
|
|
8791
|
+
var groupFiles = (fileMap) => {
|
|
8389
8792
|
const grouped = /* @__PURE__ */ new Map();
|
|
8390
|
-
for (const [path, contents] of Object.entries(
|
|
8391
|
-
const
|
|
8392
|
-
if (!
|
|
8393
|
-
|
|
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(
|
|
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
|
-
|
|
8409
|
-
|
|
8410
|
-
|
|
8411
|
-
|
|
8412
|
-
|
|
8413
|
-
|
|
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
|
-
|
|
8416
|
-
|
|
8417
|
-
|
|
8418
|
-
|
|
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
|
-
|
|
8434
|
-
|
|
8435
|
-
|
|
8436
|
-
|
|
8437
|
-
|
|
8438
|
-
|
|
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
|
-
|
|
8441
|
-
return
|
|
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
|
-
|
|
8454
|
-
|
|
8455
|
-
|
|
8456
|
-
|
|
8457
|
-
|
|
8458
|
-
|
|
8459
|
-
|
|
8460
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
8518
|
-
|
|
8519
|
-
|
|
8520
|
-
|
|
8521
|
-
|
|
8522
|
-
|
|
8523
|
-
|
|
8524
|
-
|
|
8525
|
-
|
|
8526
|
-
|
|
8527
|
-
|
|
8528
|
-
|
|
8529
|
-
|
|
8530
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
8551
|
-
if (
|
|
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
|
-
|
|
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
|
|
8574
|
-
|
|
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
|
-
...
|
|
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
|
-
|
|
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
|
|
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}" (
|
|
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
|
-
|
|
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
|
|
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:
|
|
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: {
|