@stackbone/cli 0.1.0-alpha.2 → 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 +68 -0
- package/main.js +1056 -317
- package/migrations/0009_rename_runs_partial_index.sql +22 -0
- package/package.json +1 -1
- package/stackbone-cli-0.1.0-alpha.4.tgz +0 -0
- package/stackbone-cli-0.1.0-alpha.2.tgz +0 -0
package/main.js
CHANGED
|
@@ -82,6 +82,7 @@ var installationStatusSchema = z.enum([
|
|
|
82
82
|
"deprovisioning",
|
|
83
83
|
"deprovisioned"
|
|
84
84
|
]);
|
|
85
|
+
var installationKindSchema = z.enum(["cloud", "local"]);
|
|
85
86
|
var installationProviderSchema = z.enum([
|
|
86
87
|
"neon",
|
|
87
88
|
"r2",
|
|
@@ -109,17 +110,30 @@ z.object({
|
|
|
109
110
|
slug: organizationSlugSchema,
|
|
110
111
|
id: z.uuid()
|
|
111
112
|
});
|
|
112
|
-
z.object({
|
|
113
|
+
var installationResponseSchema = z.object({
|
|
113
114
|
id: z.uuid(),
|
|
114
115
|
organizationId: z.uuid(),
|
|
115
116
|
agentId: z.uuid(),
|
|
116
117
|
agentVersion: z.string(),
|
|
117
118
|
status: installationStatusSchema,
|
|
119
|
+
kind: installationKindSchema.default("cloud"),
|
|
120
|
+
localTunnelUrl: z.url().nullable().optional(),
|
|
121
|
+
lastActiveAt: z.iso.datetime().nullable().optional(),
|
|
118
122
|
config: jsonObject(),
|
|
119
123
|
createdByUserId: z.uuid(),
|
|
120
124
|
createdAt: z.iso.datetime(),
|
|
121
125
|
updatedAt: z.iso.datetime()
|
|
122
126
|
});
|
|
127
|
+
z.object({
|
|
128
|
+
creatorOrganizationSlug: organizationSlugSchema,
|
|
129
|
+
agentTemplateSlug: z.string().min(1).max(80),
|
|
130
|
+
endUserOrganizationSlug: organizationSlugSchema.optional()
|
|
131
|
+
});
|
|
132
|
+
z.object({ localTunnelUrl: z.url().nullable() });
|
|
133
|
+
var localDevInstallationResponseSchema = installationResponseSchema.extend({
|
|
134
|
+
organizationSlug: organizationSlugSchema,
|
|
135
|
+
agentSlug: z.string().min(1).max(80)
|
|
136
|
+
});
|
|
123
137
|
z.object({
|
|
124
138
|
id: z.uuid(),
|
|
125
139
|
installationId: z.uuid(),
|
|
@@ -468,6 +482,11 @@ z.object({
|
|
|
468
482
|
nextCursor: z.string().nullable(),
|
|
469
483
|
prevCursor: z.string().nullable()
|
|
470
484
|
});
|
|
485
|
+
z.object({ fxtId: z.uuid() });
|
|
486
|
+
z.object({
|
|
487
|
+
input: jsonObject().nullable(),
|
|
488
|
+
output: jsonObject().nullable()
|
|
489
|
+
});
|
|
471
490
|
//#endregion
|
|
472
491
|
//#region ../../libs/validators/src/lib/auth/session.ts
|
|
473
492
|
var sessionUserSchema = z.object({
|
|
@@ -931,12 +950,30 @@ var credentialsSchema = z.object({
|
|
|
931
950
|
* Project-local config is written by `stackbone init`/`link` to
|
|
932
951
|
* `<cwd>/.stackbone/project.json`. Tracks which agent in which organization this
|
|
933
952
|
* directory points at. Excluded from git by the same `.gitignore` patch.
|
|
953
|
+
*
|
|
954
|
+
* Feature 42: `localDevInstallationId` + `organizationSlug` + `agentSlug` are
|
|
955
|
+
* captured at `init`/`link` time so `stackbone dev` can build the path-aware
|
|
956
|
+
* deeplink (`/app/<orgSlug>/installations/<id>?stackbone-dev=…`) and patch
|
|
957
|
+
* the tunnel URL without a separate `GET` round-trip. They are optional so
|
|
958
|
+
* old project.json files continue to load — a future `stackbone dev` against
|
|
959
|
+
* a pre-feature-42 directory simply re-registers the local-dev row.
|
|
934
960
|
*/
|
|
935
961
|
var projectConfigSchema = z.object({
|
|
936
962
|
schemaVersion: z.literal(1),
|
|
937
963
|
organizationId: z.uuid(),
|
|
938
964
|
agentId: z.uuid(),
|
|
939
|
-
controlPlaneUrl: z.url()
|
|
965
|
+
controlPlaneUrl: z.url(),
|
|
966
|
+
localDevInstallationId: z.uuid().optional(),
|
|
967
|
+
organizationSlug: 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()
|
|
940
977
|
});
|
|
941
978
|
/**
|
|
942
979
|
* Project-shared config at `<cwd>/stackbone.config.json` (committable, unlike
|
|
@@ -1263,7 +1300,8 @@ var createHttpAdapter = (opts) => {
|
|
|
1263
1300
|
};
|
|
1264
1301
|
return {
|
|
1265
1302
|
get: (path) => request("GET", path),
|
|
1266
|
-
post: (path, body) => request("POST", path, body)
|
|
1303
|
+
post: (path, body) => request("POST", path, body),
|
|
1304
|
+
patch: (path, body) => request("PATCH", path, body)
|
|
1267
1305
|
};
|
|
1268
1306
|
};
|
|
1269
1307
|
//#endregion
|
|
@@ -1361,7 +1399,6 @@ var flagValue = (argv, flag) => {
|
|
|
1361
1399
|
var parseFlags = (argv, env) => ({
|
|
1362
1400
|
json: detectJsonMode(argv, env),
|
|
1363
1401
|
yes: flagPresent(argv, "-y") || flagPresent(argv, "--yes"),
|
|
1364
|
-
autoLogin: env["STACKBONE_AUTO_LOGIN"] !== "0" && !flagPresent(argv, "--no-auto-login"),
|
|
1365
1402
|
apiUrlOverride: flagValue(argv, "--api-url") ?? env["STACKBONE_API_URL"] ?? null,
|
|
1366
1403
|
verbose: flagPresent(argv, "--verbose") || env["STACKBONE_VERBOSE"] === "1"
|
|
1367
1404
|
});
|
|
@@ -1422,7 +1459,7 @@ var isVersionFlag = (rawArgs) => rawArgs.length === 1 && VERSION_FLAGS.has(rawAr
|
|
|
1422
1459
|
var formatVersionOutput = (input) => `stackbone-cli ${input.cliVersion}\ncontract ${input.contractVersion}`;
|
|
1423
1460
|
//#endregion
|
|
1424
1461
|
//#region src/dev/contract.ts
|
|
1425
|
-
var STACKBONE_CLI_BUILD_VERSION = "0.1.0-alpha.
|
|
1462
|
+
var STACKBONE_CLI_BUILD_VERSION = "0.1.0-alpha.4";
|
|
1426
1463
|
/**
|
|
1427
1464
|
* Capabilities the local emulator advertises on `GET /api/contract`. Typed as
|
|
1428
1465
|
* `readonly Capability[]` so the compiler refuses any string outside the
|
|
@@ -1743,55 +1780,25 @@ var clearSession = async (ctx) => {
|
|
|
1743
1780
|
};
|
|
1744
1781
|
//#endregion
|
|
1745
1782
|
//#region src/commands/_shared/protected.ts
|
|
1746
|
-
var isAuthError = (err) => isStackboneCliError(err) && err.code === "auth";
|
|
1747
|
-
var refuseWithAuthError = () => {
|
|
1748
|
-
throw new StackboneCliError({
|
|
1749
|
-
code: "auth",
|
|
1750
|
-
message: "Not authenticated",
|
|
1751
|
-
suggestion: "Run `stackbone login`"
|
|
1752
|
-
});
|
|
1753
|
-
};
|
|
1754
|
-
var ensureSession = async (ctx) => {
|
|
1755
|
-
const existing = await getCurrentSession(ctx);
|
|
1756
|
-
if (existing) return existing;
|
|
1757
|
-
if (!ctx.flags.autoLogin) refuseWithAuthError();
|
|
1758
|
-
await runLoginFlow(ctx);
|
|
1759
|
-
return await getCurrentSession(ctx) ?? refuseWithAuthError();
|
|
1760
|
-
};
|
|
1761
1783
|
/**
|
|
1762
1784
|
* Wraps a command handler so it always receives a non-null `Session`.
|
|
1763
1785
|
*
|
|
1764
|
-
* If the credentials store is empty or the session expired
|
|
1765
|
-
*
|
|
1766
|
-
*
|
|
1767
|
-
*
|
|
1768
|
-
*
|
|
1769
|
-
*
|
|
1770
|
-
* Retries once if the handler itself throws an auth error mid-flight
|
|
1771
|
-
* (token expired between the pre-check and the request). If the retry
|
|
1772
|
-
* also fails with auth, the corrupt credentials are dropped from disk so
|
|
1773
|
-
* the next invocation does not loop through the same broken session.
|
|
1786
|
+
* If the credentials store is empty or the session has expired, refuses
|
|
1787
|
+
* immediately with `code: 'auth'` (exit 2) and points the user at
|
|
1788
|
+
* `stackbone login`. Auth flow and command flow are kept strictly separate
|
|
1789
|
+
* — protected commands never trigger the device-code dance on the user's
|
|
1790
|
+
* behalf, and a 401 mid-flight propagates as-is so the same `stackbone
|
|
1791
|
+
* login` instruction is the only path back.
|
|
1774
1792
|
*/
|
|
1775
1793
|
var protectedCommand = (handler) => {
|
|
1776
1794
|
return async (ctx) => {
|
|
1777
|
-
const session = await
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
const refreshed = await getCurrentSession(ctx) ?? refuseWithAuthError();
|
|
1785
|
-
try {
|
|
1786
|
-
await handler(ctx, refreshed);
|
|
1787
|
-
} catch (retryErr) {
|
|
1788
|
-
if (isAuthError(retryErr)) {
|
|
1789
|
-
ctx.logger.warn("auth still failing after re-login, clearing the stored session");
|
|
1790
|
-
await clearSession(ctx);
|
|
1791
|
-
}
|
|
1792
|
-
throw retryErr;
|
|
1793
|
-
}
|
|
1794
|
-
}
|
|
1795
|
+
const session = await getCurrentSession(ctx);
|
|
1796
|
+
if (!session) throw new StackboneCliError({
|
|
1797
|
+
code: "auth",
|
|
1798
|
+
message: "Not authenticated",
|
|
1799
|
+
suggestion: "Run `stackbone login`"
|
|
1800
|
+
});
|
|
1801
|
+
await handler(ctx, session);
|
|
1795
1802
|
};
|
|
1796
1803
|
};
|
|
1797
1804
|
//#endregion
|
|
@@ -2614,7 +2621,7 @@ var buildMigrateStatusCommand = (ctx) => defineCommand({
|
|
|
2614
2621
|
env: ctx.env,
|
|
2615
2622
|
json: ctx.flags.json
|
|
2616
2623
|
});
|
|
2617
|
-
})(
|
|
2624
|
+
})()
|
|
2618
2625
|
});
|
|
2619
2626
|
/**
|
|
2620
2627
|
* Pure handler for `stackbone db migrate add-rag`. Reads `agent.yaml` to
|
|
@@ -2702,7 +2709,7 @@ var buildStudioCommand = (ctx) => defineCommand({
|
|
|
2702
2709
|
env: ctx.env,
|
|
2703
2710
|
json: ctx.flags.json
|
|
2704
2711
|
});
|
|
2705
|
-
})(
|
|
2712
|
+
})()
|
|
2706
2713
|
});
|
|
2707
2714
|
var createDbCommand = (ctx) => defineCommand({
|
|
2708
2715
|
meta: {
|
|
@@ -2747,7 +2754,6 @@ var createDeployCommand = (ctx) => buildStubCommand(ctx, {
|
|
|
2747
2754
|
});
|
|
2748
2755
|
//#endregion
|
|
2749
2756
|
//#region src/dev/boot-banner.ts
|
|
2750
|
-
var STUDIO_CLOUD_BASE_URL = "https://app.stackbone.ai/app";
|
|
2751
2757
|
var ANSI = {
|
|
2752
2758
|
reset: "\x1B[0m",
|
|
2753
2759
|
bold: "\x1B[1m",
|
|
@@ -2757,7 +2763,6 @@ var ANSI = {
|
|
|
2757
2763
|
};
|
|
2758
2764
|
var ANSI_RE = /\x1b\[[0-9;]*m/g;
|
|
2759
2765
|
var visibleLength = (s) => s.replace(ANSI_RE, "").length;
|
|
2760
|
-
var buildDeeplink = (tunnelUrl) => `${STUDIO_CLOUD_BASE_URL}?stackbone-dev=${encodeURIComponent(tunnelUrl)}`;
|
|
2761
2766
|
var LABEL_WIDTH = 11;
|
|
2762
2767
|
var renderRow = (row, colors) => {
|
|
2763
2768
|
const marker = colors && row.marker.trim() ? `${ANSI.cyan}${row.marker}${ANSI.reset}` : row.marker;
|
|
@@ -2768,11 +2773,11 @@ var TITLE = "stackbone studio";
|
|
|
2768
2773
|
var formatBootBanner = (input) => {
|
|
2769
2774
|
const colors = input.colors ?? false;
|
|
2770
2775
|
const rows = [];
|
|
2771
|
-
if (input.tunnelUrl) {
|
|
2776
|
+
if (input.deeplink && input.tunnelUrl) {
|
|
2772
2777
|
rows.push({
|
|
2773
2778
|
marker: "▸",
|
|
2774
2779
|
label: "Open Studio",
|
|
2775
|
-
value:
|
|
2780
|
+
value: input.deeplink,
|
|
2776
2781
|
primary: true
|
|
2777
2782
|
});
|
|
2778
2783
|
rows.push({
|
|
@@ -2790,6 +2795,23 @@ var formatBootBanner = (input) => {
|
|
|
2790
2795
|
label: "Local",
|
|
2791
2796
|
value: input.localUrl
|
|
2792
2797
|
});
|
|
2798
|
+
} else if (input.tunnelUrl) {
|
|
2799
|
+
rows.push({
|
|
2800
|
+
marker: "▸",
|
|
2801
|
+
label: "Tunnel",
|
|
2802
|
+
value: input.tunnelUrl,
|
|
2803
|
+
primary: true
|
|
2804
|
+
});
|
|
2805
|
+
rows.push({
|
|
2806
|
+
marker: " ",
|
|
2807
|
+
label: "",
|
|
2808
|
+
value: ""
|
|
2809
|
+
});
|
|
2810
|
+
rows.push({
|
|
2811
|
+
marker: " ",
|
|
2812
|
+
label: "Local",
|
|
2813
|
+
value: input.localUrl
|
|
2814
|
+
});
|
|
2793
2815
|
} else rows.push({
|
|
2794
2816
|
marker: "▸",
|
|
2795
2817
|
label: "Local",
|
|
@@ -2973,6 +2995,7 @@ var fetchActiveConfigValue = async (client) => {
|
|
|
2973
2995
|
//#region src/dev/connections.ts
|
|
2974
2996
|
var STACKBONE_ENV_KEYS = {
|
|
2975
2997
|
contractVersion: "STACKBONE_CONTRACT_VERSION",
|
|
2998
|
+
agentId: "STACKBONE_AGENT_ID",
|
|
2976
2999
|
postgresUrl: "STACKBONE_POSTGRES_URL",
|
|
2977
3000
|
queueUrl: "STACKBONE_QUEUE_URL",
|
|
2978
3001
|
s3Endpoint: "STACKBONE_S3_ENDPOINT",
|
|
@@ -2987,6 +3010,7 @@ var buildAgentEnv = (base, injection, options = {}) => {
|
|
|
2987
3010
|
const env = {
|
|
2988
3011
|
...base,
|
|
2989
3012
|
[STACKBONE_ENV_KEYS.contractVersion]: String(10),
|
|
3013
|
+
[STACKBONE_ENV_KEYS.agentId]: injection.agentId,
|
|
2990
3014
|
[STACKBONE_ENV_KEYS.postgresUrl]: injection.services.postgresUrl,
|
|
2991
3015
|
[STACKBONE_ENV_KEYS.queueUrl]: injection.services.queueUrl,
|
|
2992
3016
|
[STACKBONE_ENV_KEYS.s3Endpoint]: injection.services.s3Endpoint,
|
|
@@ -4142,7 +4166,7 @@ var quoteIdent = (raw) => `"${raw.replace(/"/g, "\"\"")}"`;
|
|
|
4142
4166
|
//#region src/dev/fixtures-store.ts
|
|
4143
4167
|
var defaultFixturesDir = () => resolve(homedir(), ".stackbone", "dev", "fixtures");
|
|
4144
4168
|
var filenameFor = (baseDir, agentSlug) => resolve(baseDir, `${agentSlug}.jsonl`);
|
|
4145
|
-
var isSerializedFixture = (value) => !!value && typeof value === "object" && typeof value.id === "string" && typeof value.name === "string" && typeof value.
|
|
4169
|
+
var isSerializedFixture = (value) => !!value && typeof value === "object" && typeof value.id === "string" && typeof value.name === "string" && typeof value.createdAt === "string";
|
|
4146
4170
|
var readJsonl = async (path) => {
|
|
4147
4171
|
let raw;
|
|
4148
4172
|
try {
|
|
@@ -4169,7 +4193,7 @@ var createFixturesStore = (opts) => {
|
|
|
4169
4193
|
};
|
|
4170
4194
|
return {
|
|
4171
4195
|
async list() {
|
|
4172
|
-
return (await readJsonl(path)).sort((a, b) => a.
|
|
4196
|
+
return (await readJsonl(path)).sort((a, b) => a.createdAt < b.createdAt ? 1 : -1);
|
|
4173
4197
|
},
|
|
4174
4198
|
async create(input) {
|
|
4175
4199
|
await ensureDir();
|
|
@@ -4179,7 +4203,7 @@ var createFixturesStore = (opts) => {
|
|
|
4179
4203
|
name: input.name,
|
|
4180
4204
|
input: input.input,
|
|
4181
4205
|
description: input.description ?? null,
|
|
4182
|
-
|
|
4206
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4183
4207
|
};
|
|
4184
4208
|
await appendFile(path, `${JSON.stringify(fixture)}\n`, "utf8");
|
|
4185
4209
|
return fixture;
|
|
@@ -5426,22 +5450,25 @@ var buildEmulatorApp = (opts) => {
|
|
|
5426
5450
|
app.get("/api/logs/stream", (c) => streamSSE(c, (stream) => {
|
|
5427
5451
|
return runLogTail(stream, parseLogFilters(new URL(c.req.url).searchParams), (emit) => tailJsonlDirectory(logsDir, emit));
|
|
5428
5452
|
}));
|
|
5429
|
-
|
|
5453
|
+
app.get("/api/schema", async (c) => {
|
|
5430
5454
|
try {
|
|
5431
|
-
const resp = await fetch(`${agentBaseUrl}
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5455
|
+
const resp = await fetch(`${agentBaseUrl}/schema`);
|
|
5456
|
+
if (!resp.ok) return c.json({
|
|
5457
|
+
input: null,
|
|
5458
|
+
output: null
|
|
5459
|
+
});
|
|
5460
|
+
const raw = await resp.json().catch(() => null);
|
|
5461
|
+
return c.json({
|
|
5462
|
+
input: extractSchemaField(raw, "input"),
|
|
5463
|
+
output: extractSchemaField(raw, "output")
|
|
5436
5464
|
});
|
|
5437
5465
|
} catch {
|
|
5438
|
-
return
|
|
5439
|
-
|
|
5440
|
-
|
|
5466
|
+
return c.json({
|
|
5467
|
+
input: null,
|
|
5468
|
+
output: null
|
|
5441
5469
|
});
|
|
5442
5470
|
}
|
|
5443
|
-
};
|
|
5444
|
-
app.get("/api/schema", () => proxyAgentJson("/schema"));
|
|
5471
|
+
});
|
|
5445
5472
|
const dbExplorerOptions = { connectionString: opts.injection.services.postgresUrl };
|
|
5446
5473
|
app.get("/api/db/schemas", async (c) => {
|
|
5447
5474
|
const response = await listSchemas(dbExplorerOptions);
|
|
@@ -6044,6 +6071,12 @@ var buildEmulatorApp = (opts) => {
|
|
|
6044
6071
|
}));
|
|
6045
6072
|
return app;
|
|
6046
6073
|
};
|
|
6074
|
+
var extractSchemaField = (raw, field) => {
|
|
6075
|
+
if (!raw) return null;
|
|
6076
|
+
const value = raw[field];
|
|
6077
|
+
if (value && typeof value === "object" && !Array.isArray(value)) return value;
|
|
6078
|
+
return null;
|
|
6079
|
+
};
|
|
6047
6080
|
var eventMatchesRunId = (event, runId) => {
|
|
6048
6081
|
const data = event.data;
|
|
6049
6082
|
if (!data || typeof data["run_id"] === "undefined") return true;
|
|
@@ -6706,7 +6739,7 @@ var requestDevTunnelGrant = async (input) => {
|
|
|
6706
6739
|
if (response.status === 503) throw new StackboneCliError({
|
|
6707
6740
|
code: "tunnel_relay_unhealthy",
|
|
6708
6741
|
message: "Stackbone tunnel relay is unhealthy and cannot mint grants right now",
|
|
6709
|
-
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.`,
|
|
6710
6743
|
cause: parsedBody
|
|
6711
6744
|
});
|
|
6712
6745
|
if (!response.ok) throw new StackboneCliError({
|
|
@@ -6849,7 +6882,7 @@ var resolveFrpcBin = async (input) => {
|
|
|
6849
6882
|
throw new StackboneCliError({
|
|
6850
6883
|
code: "generic",
|
|
6851
6884
|
message: "Could not auto-fetch frpc",
|
|
6852
|
-
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.",
|
|
6853
6886
|
cause: err
|
|
6854
6887
|
});
|
|
6855
6888
|
} finally {
|
|
@@ -6933,7 +6966,7 @@ var TUNNEL_HOST_SUFFIX = "tun.stackbone.ai";
|
|
|
6933
6966
|
var frpcNotInstalledError = (cause) => new StackboneCliError({
|
|
6934
6967
|
code: "generic",
|
|
6935
6968
|
message: "`frpc` is not installed or not on PATH",
|
|
6936
|
-
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.",
|
|
6937
6970
|
cause
|
|
6938
6971
|
});
|
|
6939
6972
|
var buildFrpcConfig = (grant, targetUrl) => {
|
|
@@ -7176,6 +7209,7 @@ var startDevSession = async (input) => {
|
|
|
7176
7209
|
const bus = createEventBus();
|
|
7177
7210
|
const agentPort = await pickFreePort();
|
|
7178
7211
|
const injection = {
|
|
7212
|
+
agentId: input.agent.slug,
|
|
7179
7213
|
agentPort,
|
|
7180
7214
|
services: DEFAULT_DEV_SERVICES
|
|
7181
7215
|
};
|
|
@@ -7503,6 +7537,24 @@ var startDevSession = async (input) => {
|
|
|
7503
7537
|
}
|
|
7504
7538
|
};
|
|
7505
7539
|
//#endregion
|
|
7540
|
+
//#region src/services/local-dev-installations.service.ts
|
|
7541
|
+
var parse$1 = (raw, context) => {
|
|
7542
|
+
const parsed = localDevInstallationResponseSchema.safeParse(raw);
|
|
7543
|
+
if (!parsed.success) throw new StackboneCliError({
|
|
7544
|
+
code: "generic",
|
|
7545
|
+
message: `Unexpected response shape from ${context}`,
|
|
7546
|
+
cause: parsed.error
|
|
7547
|
+
});
|
|
7548
|
+
return parsed.data;
|
|
7549
|
+
};
|
|
7550
|
+
var upsertLocalDevInstallation = async (ctx, body) => {
|
|
7551
|
+
return parse$1(await ctx.http.post("/api/v1/installations/local-dev", body), "POST /api/v1/installations/local-dev");
|
|
7552
|
+
};
|
|
7553
|
+
var patchLocalDevInstallation = async (ctx, id, body) => {
|
|
7554
|
+
const path = `/api/v1/installations/local-dev/${encodeURIComponent(id)}`;
|
|
7555
|
+
return parse$1(await ctx.http.patch(path, body), `PATCH ${path}`);
|
|
7556
|
+
};
|
|
7557
|
+
//#endregion
|
|
7506
7558
|
//#region src/commands/dev/print-contract.ts
|
|
7507
7559
|
var defaultWriteStdout = (chunk) => {
|
|
7508
7560
|
process.stdout.write(chunk);
|
|
@@ -7599,15 +7651,37 @@ var buildHandler$2 = (ctx, flags) => protectedCommand(async (innerCtx) => {
|
|
|
7599
7651
|
name: agent.name
|
|
7600
7652
|
},
|
|
7601
7653
|
emulatorPort: flags.port,
|
|
7602
|
-
tunnel:
|
|
7654
|
+
tunnel: true,
|
|
7603
7655
|
listen: flags.listen,
|
|
7604
7656
|
verbose: flags.verbose,
|
|
7605
7657
|
...renderStage ? { onStage: renderStage } : {}
|
|
7606
7658
|
});
|
|
7659
|
+
const activeOrg = await getActiveOrganization(innerCtx);
|
|
7660
|
+
let localDevId = config.localDevInstallationId;
|
|
7661
|
+
let installationOrgSlug = config.organizationSlug ?? activeOrg.slug;
|
|
7662
|
+
if (!localDevId) {
|
|
7663
|
+
const upserted = await upsertLocalDevInstallation(innerCtx, {
|
|
7664
|
+
creatorOrganizationSlug: activeOrg.slug,
|
|
7665
|
+
agentTemplateSlug: agent.slug
|
|
7666
|
+
});
|
|
7667
|
+
localDevId = upserted.id;
|
|
7668
|
+
installationOrgSlug = upserted.organizationSlug;
|
|
7669
|
+
}
|
|
7670
|
+
if (session.publicUrl) {
|
|
7671
|
+
const patched = await patchLocalDevInstallation(innerCtx, localDevId, { localTunnelUrl: session.publicUrl }).catch((err) => {
|
|
7672
|
+
innerCtx.logger.warn({ err: err.message }, "failed to PATCH local-dev tunnel URL (continuing)");
|
|
7673
|
+
return null;
|
|
7674
|
+
});
|
|
7675
|
+
if (patched) installationOrgSlug = patched.organizationSlug;
|
|
7676
|
+
}
|
|
7677
|
+
const studioBaseUrl = innerCtx.apiUrl.includes("localhost") ? "http://localhost:4200/app" : "https://app.stackbone.ai/app";
|
|
7678
|
+
const deeplink = session.publicUrl ? `${studioBaseUrl}/${installationOrgSlug}/installations/${localDevId}?stackbone-dev=${encodeURIComponent(session.publicUrl)}` : null;
|
|
7607
7679
|
if (innerCtx.flags.json) emit(innerCtx.flags, () => "", {
|
|
7608
7680
|
emulator_url: session.emulatorUrl,
|
|
7609
7681
|
agent_url: session.agentUrl,
|
|
7610
7682
|
public_url: session.publicUrl,
|
|
7683
|
+
deeplink,
|
|
7684
|
+
local_dev_installation_id: localDevId,
|
|
7611
7685
|
services: {
|
|
7612
7686
|
postgres_url: session.injection.services.postgresUrl,
|
|
7613
7687
|
queue_url: session.injection.services.queueUrl,
|
|
@@ -7619,6 +7693,7 @@ var buildHandler$2 = (ctx, flags) => protectedCommand(async (innerCtx) => {
|
|
|
7619
7693
|
const inlineLines = formatBootBanner({
|
|
7620
7694
|
tunnelUrl: session.publicUrl,
|
|
7621
7695
|
localUrl: session.emulatorUrl,
|
|
7696
|
+
deeplink,
|
|
7622
7697
|
contractVersion: 10,
|
|
7623
7698
|
capabilityCount: EMULATOR_CAPABILITIES.length,
|
|
7624
7699
|
agentName: agent.name,
|
|
@@ -7630,7 +7705,13 @@ var buildHandler$2 = (ctx, flags) => protectedCommand(async (innerCtx) => {
|
|
|
7630
7705
|
log.step("Press Ctrl-C to stop");
|
|
7631
7706
|
}
|
|
7632
7707
|
const sigintHandler = () => {
|
|
7633
|
-
|
|
7708
|
+
(async () => {
|
|
7709
|
+
if (localDevId) await patchLocalDevInstallation(innerCtx, localDevId, { localTunnelUrl: null }).catch((err) => {
|
|
7710
|
+
innerCtx.logger.debug({ err: err.message }, "failed to null local-dev tunnel URL on exit (safety net will catch up)");
|
|
7711
|
+
return null;
|
|
7712
|
+
});
|
|
7713
|
+
await session.stop();
|
|
7714
|
+
})();
|
|
7634
7715
|
};
|
|
7635
7716
|
process.once("SIGINT", sigintHandler);
|
|
7636
7717
|
process.once("SIGTERM", sigintHandler);
|
|
@@ -7648,12 +7729,6 @@ var createDevCommand = (ctx) => defineCommand({
|
|
|
7648
7729
|
description: `Emulator port. Defaults to ${DEFAULT_EMULATOR_PORT}.`,
|
|
7649
7730
|
default: String(DEFAULT_EMULATOR_PORT)
|
|
7650
7731
|
},
|
|
7651
|
-
tunnel: {
|
|
7652
|
-
type: "boolean",
|
|
7653
|
-
description: "Open a public tunnel to the emulator (default: on).",
|
|
7654
|
-
negativeDescription: "Skip the public tunnel and serve the emulator only on localhost.",
|
|
7655
|
-
default: true
|
|
7656
|
-
},
|
|
7657
7732
|
listen: {
|
|
7658
7733
|
type: "boolean",
|
|
7659
7734
|
description: "Bind the HTTP server to 0.0.0.0 (reachable from your LAN). Default binds to 127.0.0.1 only.",
|
|
@@ -7663,11 +7738,6 @@ var createDevCommand = (ctx) => defineCommand({
|
|
|
7663
7738
|
type: "boolean",
|
|
7664
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.",
|
|
7665
7740
|
default: false
|
|
7666
|
-
},
|
|
7667
|
-
verbose: {
|
|
7668
|
-
type: "boolean",
|
|
7669
|
-
description: "Stream every log line and docker compose output. Default UI uses per-stage spinners; --verbose switches back to the raw firehose.",
|
|
7670
|
-
default: false
|
|
7671
7741
|
}
|
|
7672
7742
|
},
|
|
7673
7743
|
run: ({ args }) => safeRun(ctx.flags, async (a) => {
|
|
@@ -7683,9 +7753,8 @@ var createDevCommand = (ctx) => defineCommand({
|
|
|
7683
7753
|
});
|
|
7684
7754
|
await buildHandler$2(ctx, {
|
|
7685
7755
|
port,
|
|
7686
|
-
tunnel: Boolean(a.tunnel),
|
|
7687
7756
|
listen: Boolean(a.listen),
|
|
7688
|
-
verbose:
|
|
7757
|
+
verbose: ctx.flags.verbose
|
|
7689
7758
|
});
|
|
7690
7759
|
})(args)
|
|
7691
7760
|
});
|
|
@@ -7720,10 +7789,6 @@ several exploratory calls.
|
|
|
7720
7789
|
- \`-y, --yes\`
|
|
7721
7790
|
Skip every confirmation prompt. Required in CI / non-TTY contexts.
|
|
7722
7791
|
|
|
7723
|
-
- \`--no-auto-login\` (or \`STACKBONE_AUTO_LOGIN=0\`)
|
|
7724
|
-
When a protected command finds no valid session, fail fast with exit
|
|
7725
|
-
code 2 instead of triggering the device-code flow inline.
|
|
7726
|
-
|
|
7727
7792
|
## Exit codes
|
|
7728
7793
|
|
|
7729
7794
|
- \`0\` ok
|
|
@@ -7735,18 +7800,20 @@ several exploratory calls.
|
|
|
7735
7800
|
|
|
7736
7801
|
## Commands shipped today
|
|
7737
7802
|
|
|
7803
|
+
- \`stackbone login\` / \`logout\` — device-code flow over magic link
|
|
7738
7804
|
- \`stackbone whoami\` — show the active user and organization
|
|
7739
7805
|
- \`stackbone current\` — print the active organization slug
|
|
7740
7806
|
- \`stackbone list\` — list organizations the current user owns
|
|
7741
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
|
|
7742
7812
|
- \`stackbone docs <topic>\` — print this kind of help inline
|
|
7743
7813
|
|
|
7744
|
-
##
|
|
7814
|
+
## Pending
|
|
7745
7815
|
|
|
7746
|
-
- \`stackbone
|
|
7747
|
-
- \`stackbone init\` / \`link\` — scaffold a new project, or link an existing one
|
|
7748
|
-
- \`stackbone dev\` — local emulator of the control plane
|
|
7749
|
-
- \`stackbone deploy\` — push to Fly Machines
|
|
7816
|
+
- \`stackbone deploy\` — push to Fly Machines (stub)
|
|
7750
7817
|
|
|
7751
7818
|
## State on disk
|
|
7752
7819
|
|
|
@@ -7755,34 +7822,35 @@ several exploratory calls.
|
|
|
7755
7822
|
`;
|
|
7756
7823
|
var sdk = `# @stackbone/sdk
|
|
7757
7824
|
|
|
7758
|
-
Monolithic SDK for the agent container with
|
|
7759
|
-
|
|
7760
|
-
|
|
7761
|
-
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.
|
|
7762
7828
|
|
|
7763
7829
|
## Modules
|
|
7764
7830
|
|
|
7765
|
-
- \`client.database\` — Drizzle +
|
|
7766
|
-
- \`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
|
|
7767
7833
|
- \`client.ai\` — openai SDK with OpenRouter base URL override
|
|
7768
|
-
- \`client.queues\` —
|
|
7834
|
+
- \`client.queues\` — pgmq publisher (surface defined, runtime pending)
|
|
7769
7835
|
- \`client.rag\` — LlamaParse (opt-in) + chunker + embeddings + pgvector + tsvector
|
|
7770
|
-
- \`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)
|
|
7771
7838
|
|
|
7772
7839
|
## Façades on top of the control plane
|
|
7773
7840
|
|
|
7774
7841
|
- \`client.approval.*\` — HITL queue and workflow resume
|
|
7775
7842
|
- \`client.secrets.*\` — organization-scoped encrypted secrets
|
|
7776
7843
|
- \`client.connections.*\` — OAuth connections (Notion, GDrive, Slack, …)
|
|
7777
|
-
- \`client.config.*\` —
|
|
7844
|
+
- \`client.config.*\` — typed reads of dynamic config from the control plane
|
|
7778
7845
|
- \`client.events.*\` — emit events to the organization eventbus
|
|
7846
|
+
- \`client.prompts.*\` — typed prompt templates fetched from the control plane
|
|
7779
7847
|
|
|
7780
7848
|
## Escape hatch
|
|
7781
7849
|
|
|
7782
|
-
Every upstream env var (\`
|
|
7850
|
+
Every upstream env var (\`STACKBONE_POSTGRES_URL\`, \`STACKBONE_S3_*\`,
|
|
7783
7851
|
\`OPENROUTER_API_KEY\`, \`OPENROUTER_BASE_URL\`, \`LLAMA_PARSE_API_KEY\` if
|
|
7784
|
-
opt-in, \`
|
|
7785
|
-
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.
|
|
7786
7854
|
|
|
7787
7855
|
The runtime target is **Hono + Node 24 LTS** (Bun 1.x as opt-in via the
|
|
7788
7856
|
\`runtime.engine: bun\` field of \`agent.yaml\`). No adapters for Next,
|
|
@@ -7796,15 +7864,23 @@ runs your agent under \`@stackbone/sdk\`, and exposes the Studio API at
|
|
|
7796
7864
|
|
|
7797
7865
|
## Defaults that matter
|
|
7798
7866
|
|
|
7799
|
-
-
|
|
7867
|
+
- Mandatory tunnel — \`stackbone dev\` always opens a tunnel against the
|
|
7800
7868
|
Stackbone-owned relay (\`*.tun.stackbone.ai\`) so the HTTPS bundle of
|
|
7801
|
-
\`app.stackbone.ai
|
|
7802
|
-
|
|
7803
|
-
|
|
7804
|
-
|
|
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\`.
|
|
7805
7876
|
- HTTP server bound to \`127.0.0.1\` by default — not reachable from
|
|
7806
7877
|
the LAN. Pass \`--listen\` to bind to \`0.0.0.0\` instead; the CLI
|
|
7807
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.
|
|
7808
7884
|
|
|
7809
7885
|
## CORS allowlist
|
|
7810
7886
|
|
|
@@ -7910,6 +7986,32 @@ before booting the agent process and re-runs it whenever the migrations
|
|
|
7910
7986
|
folder changes. The default is opt-out so a noisy schema cannot silently
|
|
7911
7987
|
mutate your local database.
|
|
7912
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
|
+
|
|
7913
8015
|
## Reference
|
|
7914
8016
|
|
|
7915
8017
|
This file lives in the validators contract (\`agentManifestSchema\`) and is
|
|
@@ -7917,7 +8019,7 @@ shared by \`stackbone init\`, \`stackbone dev\`, \`stackbone deploy\` and the
|
|
|
7917
8019
|
\`stackbone db migrate ...\` family. Keep it under version control — the
|
|
7918
8020
|
control plane never writes to it.
|
|
7919
8021
|
`,
|
|
7920
|
-
|
|
8022
|
+
starters: stubFor("Epic 5 — official starters", "Document the starter catalogue (CLI scaffold sources)"),
|
|
7921
8023
|
connections: stubFor("Epic 6 — data layer", "Montar docker-compose embebido con Postgres pgvector pgmq y MinIO para stackbone dev")
|
|
7922
8024
|
};
|
|
7923
8025
|
var docTopicList = Object.keys(docTopics);
|
|
@@ -8016,7 +8118,6 @@ Branch on these instead of parsing stack traces.
|
|
|
8016
8118
|
|
|
8017
8119
|
- \`STACKBONE_JSON=1\` — same as \`--json\`
|
|
8018
8120
|
- \`STACKBONE_API_URL\` — same as \`--api-url <url>\`
|
|
8019
|
-
- \`STACKBONE_AUTO_LOGIN=0\` — same as \`--no-auto-login\`
|
|
8020
8121
|
- \`STACKBONE_LOG_LEVEL\` — \`debug\`, \`info\`, \`warn\`, \`error\` (logger goes
|
|
8021
8122
|
to stderr, never pollutes the JSON payload on stdout)
|
|
8022
8123
|
|
|
@@ -8045,7 +8146,7 @@ stackbone metadata --json # one-shot overview that replaces 5–7 explorer call
|
|
|
8045
8146
|
### Start a new project
|
|
8046
8147
|
|
|
8047
8148
|
\`\`\`sh
|
|
8048
|
-
stackbone init # menu of
|
|
8149
|
+
stackbone init # menu of starters (hello-world + 9 capability demos)
|
|
8049
8150
|
stackbone dev # local emulator at http://localhost:4242
|
|
8050
8151
|
\`\`\`
|
|
8051
8152
|
|
|
@@ -8072,7 +8173,7 @@ stackbone deploy # build + push + Fly Machines
|
|
|
8072
8173
|
- \`stackbone docs cli\` — command reference
|
|
8073
8174
|
- \`stackbone docs sdk\` — \`@stackbone/sdk\` modules
|
|
8074
8175
|
- \`stackbone docs agent-yaml\` — manifest reference (Epic 3)
|
|
8075
|
-
- \`stackbone docs
|
|
8176
|
+
- \`stackbone docs starters\` — official starters (Epic 5)
|
|
8076
8177
|
- \`stackbone docs connections\` — data layer (Epic 6)
|
|
8077
8178
|
`;
|
|
8078
8179
|
//#endregion
|
|
@@ -8155,18 +8256,22 @@ var writeFileSafe = async (path, contents, force) => {
|
|
|
8155
8256
|
await mkdir(dirname(path), { recursive: true });
|
|
8156
8257
|
await writeFile(path, contents);
|
|
8157
8258
|
};
|
|
8158
|
-
var
|
|
8259
|
+
var writeStarterFiles = async (files, input) => {
|
|
8159
8260
|
for (const file of files) await writeFileSafe(join(input.root, file.path), file.contents, input.force);
|
|
8160
8261
|
};
|
|
8161
8262
|
var writeAgentManifest = async (agentName, input) => {
|
|
8162
8263
|
await writeFileSafe(join(input.root, "agent.yaml"), renderAgentYaml({ name: agentName }), input.force);
|
|
8163
8264
|
};
|
|
8164
|
-
var writeProjectConfig = async (ctx, agent, input) => {
|
|
8265
|
+
var writeProjectConfig = async (ctx, agent, input, extras = {}) => {
|
|
8165
8266
|
const config = {
|
|
8166
8267
|
schemaVersion: 1,
|
|
8167
8268
|
organizationId: agent.organizationId,
|
|
8168
8269
|
agentId: agent.id,
|
|
8169
|
-
controlPlaneUrl: ctx.apiUrl
|
|
8270
|
+
controlPlaneUrl: ctx.apiUrl,
|
|
8271
|
+
...extras.localDevInstallationId !== void 0 ? { localDevInstallationId: extras.localDevInstallationId } : {},
|
|
8272
|
+
...extras.organizationSlug !== void 0 ? { organizationSlug: extras.organizationSlug } : {},
|
|
8273
|
+
...extras.agentSlug !== void 0 ? { agentSlug: extras.agentSlug } : {},
|
|
8274
|
+
...extras.starter !== void 0 ? { starter: extras.starter } : {}
|
|
8170
8275
|
};
|
|
8171
8276
|
await mkdir(join(input.root, ".stackbone"), { recursive: true });
|
|
8172
8277
|
await writeFile(join(input.root, ".stackbone", "project.json"), JSON.stringify(config, null, 2));
|
|
@@ -8198,209 +8303,779 @@ var detectExistingProjectFiles = async (root, paths) => {
|
|
|
8198
8303
|
return found;
|
|
8199
8304
|
};
|
|
8200
8305
|
//#endregion
|
|
8201
|
-
//#region
|
|
8202
|
-
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";
|
|
8203
8308
|
//#endregion
|
|
8204
|
-
//#region
|
|
8205
|
-
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";
|
|
8206
8311
|
//#endregion
|
|
8207
|
-
//#region
|
|
8208
|
-
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";
|
|
8209
8314
|
//#endregion
|
|
8210
|
-
//#region
|
|
8211
|
-
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";
|
|
8212
8317
|
//#endregion
|
|
8213
|
-
//#region
|
|
8214
|
-
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";
|
|
8215
8320
|
//#endregion
|
|
8216
|
-
//#region
|
|
8217
|
-
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";
|
|
8218
8323
|
//#endregion
|
|
8219
|
-
//#region
|
|
8220
|
-
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";
|
|
8221
8326
|
//#endregion
|
|
8222
|
-
//#region
|
|
8223
|
-
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";
|
|
8224
8329
|
//#endregion
|
|
8225
|
-
//#region
|
|
8226
|
-
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";
|
|
8227
8332
|
//#endregion
|
|
8228
|
-
//#region
|
|
8229
|
-
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";
|
|
8230
8335
|
//#endregion
|
|
8231
|
-
//#region
|
|
8232
|
-
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";
|
|
8233
8338
|
//#endregion
|
|
8234
|
-
//#region
|
|
8235
|
-
var
|
|
8339
|
+
//#region ../../starters/ai/.gitignore?raw
|
|
8340
|
+
var _gitignore_raw_default$8 = "# >>> stackbone >>>\n.stackbone/\nnode_modules/\ndist/\n# <<< stackbone <<<\n";
|
|
8236
8341
|
//#endregion
|
|
8237
|
-
//#region
|
|
8238
|
-
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";
|
|
8239
8344
|
//#endregion
|
|
8240
|
-
//#region
|
|
8241
|
-
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";
|
|
8242
8347
|
//#endregion
|
|
8243
|
-
//#region
|
|
8244
|
-
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";
|
|
8389
|
+
//#endregion
|
|
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";
|
|
8392
|
+
//#endregion
|
|
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";
|
|
8395
|
+
//#endregion
|
|
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";
|
|
8398
|
+
//#endregion
|
|
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";
|
|
8401
|
+
//#endregion
|
|
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";
|
|
8245
8434
|
//#endregion
|
|
8246
|
-
//#region src/
|
|
8247
|
-
var
|
|
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";
|
|
8248
8437
|
//#endregion
|
|
8249
|
-
//#region src/
|
|
8250
|
-
var
|
|
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";
|
|
8251
8440
|
//#endregion
|
|
8252
|
-
//#region
|
|
8253
|
-
var
|
|
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";
|
|
8254
8443
|
//#endregion
|
|
8255
|
-
//#region
|
|
8256
|
-
var
|
|
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";
|
|
8257
8446
|
//#endregion
|
|
8258
|
-
//#region
|
|
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
|
|
8259
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";
|
|
8260
8458
|
//#endregion
|
|
8261
|
-
//#region
|
|
8459
|
+
//#region ../../starters/hello-world/.stackbone/migrations/meta/0000_snapshot.json?raw
|
|
8262
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";
|
|
8263
8461
|
//#endregion
|
|
8264
|
-
//#region
|
|
8462
|
+
//#region ../../starters/hello-world/.stackbone/migrations/meta/_journal.json?raw
|
|
8265
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";
|
|
8266
8464
|
//#endregion
|
|
8267
|
-
//#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
|
|
8268
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";
|
|
8269
8698
|
//#endregion
|
|
8270
|
-
//#region
|
|
8271
|
-
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";
|
|
8272
8701
|
//#endregion
|
|
8273
|
-
//#region
|
|
8274
|
-
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";
|
|
8275
8704
|
//#endregion
|
|
8276
|
-
//#region
|
|
8277
|
-
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";
|
|
8278
8707
|
//#endregion
|
|
8279
|
-
//#region
|
|
8280
|
-
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";
|
|
8281
8710
|
//#endregion
|
|
8282
|
-
//#region src/
|
|
8283
|
-
var
|
|
8284
|
-
|
|
8285
|
-
|
|
8286
|
-
|
|
8287
|
-
|
|
8288
|
-
|
|
8289
|
-
var
|
|
8290
|
-
|
|
8291
|
-
|
|
8292
|
-
|
|
8293
|
-
|
|
8294
|
-
|
|
8295
|
-
|
|
8296
|
-
|
|
8297
|
-
|
|
8298
|
-
|
|
8299
|
-
|
|
8300
|
-
|
|
8301
|
-
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
|
|
8305
|
-
|
|
8306
|
-
|
|
8307
|
-
|
|
8308
|
-
|
|
8309
|
-
|
|
8310
|
-
|
|
8311
|
-
"
|
|
8312
|
-
|
|
8313
|
-
|
|
8314
|
-
|
|
8315
|
-
|
|
8316
|
-
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.`);
|
|
8317
8755
|
return value;
|
|
8318
8756
|
};
|
|
8319
|
-
var
|
|
8320
|
-
|
|
8321
|
-
|
|
8322
|
-
|
|
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
|
+
};
|
|
8323
8790
|
};
|
|
8324
|
-
var
|
|
8791
|
+
var groupFiles = (fileMap) => {
|
|
8325
8792
|
const grouped = /* @__PURE__ */ new Map();
|
|
8326
|
-
for (const [path, contents] of Object.entries(
|
|
8327
|
-
const
|
|
8328
|
-
if (!
|
|
8329
|
-
|
|
8330
|
-
const rel = match[2];
|
|
8331
|
-
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);
|
|
8332
8797
|
if (!bucket) {
|
|
8333
8798
|
bucket = [];
|
|
8334
|
-
grouped.set(
|
|
8799
|
+
grouped.set(parsed.slug, bucket);
|
|
8335
8800
|
}
|
|
8336
8801
|
bucket.push({
|
|
8337
|
-
path: rel,
|
|
8802
|
+
path: parsed.rel,
|
|
8338
8803
|
contents
|
|
8339
8804
|
});
|
|
8340
8805
|
}
|
|
8341
8806
|
for (const bucket of grouped.values()) bucket.sort((a, b) => a.path.localeCompare(b.path));
|
|
8342
8807
|
return grouped;
|
|
8343
8808
|
};
|
|
8344
|
-
|
|
8345
|
-
|
|
8346
|
-
|
|
8347
|
-
|
|
8348
|
-
|
|
8349
|
-
|
|
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);
|
|
8350
8825
|
}
|
|
8351
|
-
|
|
8352
|
-
|
|
8353
|
-
|
|
8354
|
-
|
|
8355
|
-
if (!parsed) throw new Error(`Template '${id}': empty or invalid YAML.`);
|
|
8356
|
-
if (parsed.id !== id) throw new Error(`Template '${id}': manifest id '${String(parsed.id)}' does not match directory name.`);
|
|
8357
|
-
if (typeof parsed.order !== "number") throw new Error(`Template '${id}': field 'order' must be a number.`);
|
|
8358
|
-
const variables = Array.isArray(parsed.variables) ? parsed.variables.map(String) : [];
|
|
8359
|
-
validateVariables(id, variables, files);
|
|
8360
|
-
return {
|
|
8361
|
-
id,
|
|
8362
|
-
label: requireString(parsed.label, "label", id),
|
|
8363
|
-
hint: requireString(parsed.hint, "hint", id),
|
|
8364
|
-
order: parsed.order,
|
|
8365
|
-
variables,
|
|
8366
|
-
files
|
|
8367
|
-
};
|
|
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);
|
|
8368
8830
|
};
|
|
8369
|
-
|
|
8370
|
-
|
|
8371
|
-
|
|
8372
|
-
|
|
8373
|
-
|
|
8374
|
-
|
|
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
|
+
});
|
|
8375
8851
|
}
|
|
8376
|
-
|
|
8377
|
-
return
|
|
8378
|
-
};
|
|
8379
|
-
var TEMPLATES_BY_ID = loadTemplates();
|
|
8380
|
-
var TEMPLATES = [...TEMPLATES_BY_ID.values()].map((t) => ({
|
|
8381
|
-
id: t.id,
|
|
8382
|
-
label: t.label,
|
|
8383
|
-
hint: t.hint
|
|
8384
|
-
}));
|
|
8385
|
-
var parseTemplateId = (value) => {
|
|
8386
|
-
if (!value) return null;
|
|
8387
|
-
return TEMPLATES_BY_ID.has(value) ? value : null;
|
|
8852
|
+
loaded.sort(compareStarters);
|
|
8853
|
+
return loaded;
|
|
8388
8854
|
};
|
|
8389
|
-
|
|
8390
|
-
|
|
8391
|
-
|
|
8392
|
-
|
|
8393
|
-
|
|
8394
|
-
|
|
8395
|
-
|
|
8396
|
-
|
|
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) => ({
|
|
8397
8880
|
path: file.path,
|
|
8398
|
-
contents:
|
|
8399
|
-
...file.executable ? { executable: true } : {}
|
|
8881
|
+
contents: replaceAllLiteral(file.contents, packageName, agentSlug)
|
|
8400
8882
|
}));
|
|
8401
8883
|
};
|
|
8402
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
|
|
8403
9077
|
//#region src/commands/init/init.command.ts
|
|
9078
|
+
var DEFAULT_STARTER_ID = "hello-world";
|
|
8404
9079
|
var STACKBONE_OWNED_FILES = [
|
|
8405
9080
|
"agent.yaml",
|
|
8406
9081
|
".stackbone/project.json",
|
|
@@ -8415,6 +9090,11 @@ var STACKBONE_OWNED_FILES = [
|
|
|
8415
9090
|
".claude/skills/stackbone/SKILL.md"
|
|
8416
9091
|
];
|
|
8417
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";
|
|
8418
9098
|
var directoryNameOrFallback = (cwd) => {
|
|
8419
9099
|
const base = basename(cwd) || "stackbone-agent";
|
|
8420
9100
|
return NAME_REGEX.test(base) ? base : "stackbone-agent";
|
|
@@ -8439,8 +9119,18 @@ var ensureEmptyOrForce = async (targetDir, force) => {
|
|
|
8439
9119
|
suggestion: "Re-run with `--force` or pick an empty directory"
|
|
8440
9120
|
});
|
|
8441
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
|
+
};
|
|
8442
9132
|
var promptForName = async (defaultName) => {
|
|
8443
|
-
|
|
9133
|
+
return bailIfCancelled(await text({
|
|
8444
9134
|
message: "Agent name",
|
|
8445
9135
|
placeholder: defaultName,
|
|
8446
9136
|
defaultValue: defaultName,
|
|
@@ -8449,42 +9139,58 @@ var promptForName = async (defaultName) => {
|
|
|
8449
9139
|
if (v.length === 0) return "Name cannot be empty";
|
|
8450
9140
|
if (v.length > 120) return "Name is too long";
|
|
8451
9141
|
}
|
|
8452
|
-
});
|
|
8453
|
-
|
|
8454
|
-
|
|
8455
|
-
|
|
8456
|
-
|
|
8457
|
-
|
|
8458
|
-
|
|
8459
|
-
|
|
8460
|
-
|
|
8461
|
-
|
|
8462
|
-
|
|
8463
|
-
|
|
8464
|
-
|
|
8465
|
-
|
|
8466
|
-
|
|
8467
|
-
label: t.label,
|
|
8468
|
-
hint: t.hint
|
|
8469
|
-
}))
|
|
8470
|
-
});
|
|
8471
|
-
if (isCancel(value)) {
|
|
8472
|
-
cancel("Init cancelled.");
|
|
8473
|
-
throw new StackboneCliError({
|
|
8474
|
-
code: "generic",
|
|
8475
|
-
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
|
|
8476
9157
|
});
|
|
8477
9158
|
}
|
|
8478
|
-
|
|
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
|
+
}));
|
|
8479
9175
|
};
|
|
8480
|
-
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
|
+
});
|
|
8481
9182
|
const targetDir = resolve(ctx.cwd, namePositional ?? ".");
|
|
8482
9183
|
const fallbackName = namePositional ?? directoryNameOrFallback(targetDir);
|
|
8483
9184
|
const interactive = isInteractiveStdin(ctx.env) && !ctx.flags.yes && !ctx.flags.json;
|
|
8484
9185
|
let agentName = flags.name ?? namePositional ?? (interactive ? await promptForName(fallbackName) : fallbackName);
|
|
8485
9186
|
agentName = agentName.trim();
|
|
8486
|
-
let
|
|
8487
|
-
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;
|
|
8488
9194
|
if (flags.slug !== void 0) {
|
|
8489
9195
|
if (!agentSlugSchema.safeParse(flags.slug).success) throw new StackboneCliError({
|
|
8490
9196
|
code: "generic",
|
|
@@ -8496,27 +9202,29 @@ var resolveInputs = async (ctx, flags, namePositional) => {
|
|
|
8496
9202
|
agentName,
|
|
8497
9203
|
agentSlug: flags.slug,
|
|
8498
9204
|
description: flags.description,
|
|
8499
|
-
|
|
9205
|
+
starterId,
|
|
8500
9206
|
targetDir
|
|
8501
9207
|
};
|
|
8502
9208
|
};
|
|
8503
|
-
var writeProject = async (ctx, resolved, agent, force) => {
|
|
9209
|
+
var writeProject = async (ctx, resolved, agent, force, localDev) => {
|
|
8504
9210
|
const writeInput = {
|
|
8505
9211
|
root: resolved.targetDir,
|
|
8506
9212
|
force
|
|
8507
9213
|
};
|
|
8508
9214
|
await mkdir(resolved.targetDir, { recursive: true });
|
|
8509
|
-
const
|
|
8510
|
-
|
|
8511
|
-
agentSlug: agent.slug
|
|
8512
|
-
});
|
|
8513
|
-
await writeTemplateFiles(templateFiles, writeInput);
|
|
9215
|
+
const starterFiles = renderStarter(resolved.starterId, { agentSlug: agent.slug });
|
|
9216
|
+
await writeStarterFiles(starterFiles, writeInput);
|
|
8514
9217
|
await writeAgentManifest(resolved.agentName, writeInput);
|
|
8515
|
-
const projectConfig = await writeProjectConfig(ctx, agent, writeInput
|
|
9218
|
+
const projectConfig = await writeProjectConfig(ctx, agent, writeInput, {
|
|
9219
|
+
localDevInstallationId: localDev.id,
|
|
9220
|
+
organizationSlug: localDev.organizationSlug,
|
|
9221
|
+
agentSlug: localDev.agentSlug,
|
|
9222
|
+
starter: resolved.starterId
|
|
9223
|
+
});
|
|
8516
9224
|
await ensureGitignore(resolved.targetDir);
|
|
8517
9225
|
const skillFiles = await writeSkillFiles(ctx, agent, projectConfig, writeInput);
|
|
8518
9226
|
return { filesWritten: [
|
|
8519
|
-
...
|
|
9227
|
+
...starterFiles.map((f) => f.path),
|
|
8520
9228
|
"agent.yaml",
|
|
8521
9229
|
".stackbone/project.json",
|
|
8522
9230
|
".gitignore",
|
|
@@ -8527,21 +9235,31 @@ var buildHandler$1 = (ctx, args) => protectedCommand(async (innerCtx) => {
|
|
|
8527
9235
|
if (!innerCtx.flags.json) intro("stackbone init");
|
|
8528
9236
|
const flags = {
|
|
8529
9237
|
name: args.name,
|
|
8530
|
-
|
|
9238
|
+
starter: args.starter,
|
|
9239
|
+
legacyTemplate: args.template,
|
|
8531
9240
|
slug: args.slug,
|
|
8532
9241
|
description: args.description,
|
|
8533
9242
|
force: Boolean(args.force)
|
|
8534
9243
|
};
|
|
8535
|
-
const resolved = await
|
|
9244
|
+
const resolved = await resolveInitInputs(innerCtx, flags, args.name);
|
|
8536
9245
|
await ensureEmptyOrForce(resolved.targetDir, flags.force);
|
|
8537
|
-
if (!innerCtx.flags.json) log.step(`Creating "${resolved.agentName}" (
|
|
9246
|
+
if (!innerCtx.flags.json) log.step(`Creating "${resolved.agentName}" (starter: ${resolved.starterId})`);
|
|
9247
|
+
const activeOrg = await getActiveOrganization(innerCtx);
|
|
8538
9248
|
const agent = await createAgent(innerCtx, {
|
|
8539
|
-
organizationSlug:
|
|
9249
|
+
organizationSlug: activeOrg.slug,
|
|
8540
9250
|
name: resolved.agentName,
|
|
8541
9251
|
...resolved.agentSlug !== void 0 ? { slug: resolved.agentSlug } : {},
|
|
8542
9252
|
...resolved.description !== void 0 ? { description: resolved.description } : {}
|
|
8543
9253
|
});
|
|
8544
|
-
const
|
|
9254
|
+
const localDev = await upsertLocalDevInstallation(innerCtx, {
|
|
9255
|
+
creatorOrganizationSlug: activeOrg.slug,
|
|
9256
|
+
agentTemplateSlug: agent.slug
|
|
9257
|
+
});
|
|
9258
|
+
const { filesWritten } = await writeProject(innerCtx, resolved, agent, flags.force, {
|
|
9259
|
+
id: localDev.id,
|
|
9260
|
+
organizationSlug: localDev.organizationSlug,
|
|
9261
|
+
agentSlug: localDev.agentSlug
|
|
9262
|
+
});
|
|
8545
9263
|
emit(innerCtx.flags, () => "", {
|
|
8546
9264
|
agent: {
|
|
8547
9265
|
id: agent.id,
|
|
@@ -8549,8 +9267,12 @@ var buildHandler$1 = (ctx, args) => protectedCommand(async (innerCtx) => {
|
|
|
8549
9267
|
name: agent.name
|
|
8550
9268
|
},
|
|
8551
9269
|
organization_id: agent.organizationId,
|
|
9270
|
+
local_dev_installation: {
|
|
9271
|
+
id: localDev.id,
|
|
9272
|
+
organization_slug: localDev.organizationSlug
|
|
9273
|
+
},
|
|
8552
9274
|
target_dir: resolved.targetDir,
|
|
8553
|
-
|
|
9275
|
+
starter: resolved.starterId,
|
|
8554
9276
|
files_written: filesWritten
|
|
8555
9277
|
});
|
|
8556
9278
|
if (!innerCtx.flags.json) outro([
|
|
@@ -8566,7 +9288,7 @@ var buildHandler$1 = (ctx, args) => protectedCommand(async (innerCtx) => {
|
|
|
8566
9288
|
var createInitCommand = (ctx) => defineCommand({
|
|
8567
9289
|
meta: {
|
|
8568
9290
|
name: "init",
|
|
8569
|
-
description: "Scaffold a new Stackbone project with
|
|
9291
|
+
description: "Scaffold a new Stackbone project with starters + Claude skill."
|
|
8570
9292
|
},
|
|
8571
9293
|
args: {
|
|
8572
9294
|
name: {
|
|
@@ -8574,9 +9296,13 @@ var createInitCommand = (ctx) => defineCommand({
|
|
|
8574
9296
|
description: "Subdirectory + initial agent name. Omit to use the current directory.",
|
|
8575
9297
|
required: false
|
|
8576
9298
|
},
|
|
9299
|
+
starter: {
|
|
9300
|
+
type: "string",
|
|
9301
|
+
description: `Starter slug. One of ${validSlugList()}. Default: ${DEFAULT_STARTER_ID}.`
|
|
9302
|
+
},
|
|
8577
9303
|
template: {
|
|
8578
9304
|
type: "string",
|
|
8579
|
-
description:
|
|
9305
|
+
description: "(removed) renamed to --starter; pre-rename flag kept only to emit the migration message."
|
|
8580
9306
|
},
|
|
8581
9307
|
slug: {
|
|
8582
9308
|
type: "string",
|
|
@@ -8637,11 +9363,20 @@ var buildHandler = (ctx, args) => protectedCommand(async (innerCtx) => {
|
|
|
8637
9363
|
}
|
|
8638
9364
|
chosenSlug = value;
|
|
8639
9365
|
}
|
|
8640
|
-
const
|
|
9366
|
+
const activeOrg = await getActiveOrganization(innerCtx);
|
|
9367
|
+
const agent = await getAgentByOwnerAndSlug(innerCtx, activeOrg.slug, chosenSlug);
|
|
8641
9368
|
if (!innerCtx.flags.json) log.step(`Linking ${targetDir} to ${agent.slug}`);
|
|
9369
|
+
const localDev = await upsertLocalDevInstallation(innerCtx, {
|
|
9370
|
+
creatorOrganizationSlug: activeOrg.slug,
|
|
9371
|
+
agentTemplateSlug: agent.slug
|
|
9372
|
+
});
|
|
8642
9373
|
const projectConfig = await writeProjectConfig(innerCtx, agent, {
|
|
8643
9374
|
root: targetDir,
|
|
8644
9375
|
force: true
|
|
9376
|
+
}, {
|
|
9377
|
+
localDevInstallationId: localDev.id,
|
|
9378
|
+
organizationSlug: localDev.organizationSlug,
|
|
9379
|
+
agentSlug: localDev.agentSlug
|
|
8645
9380
|
});
|
|
8646
9381
|
await ensureGitignore(targetDir);
|
|
8647
9382
|
const skillFiles = await writeSkillFiles(innerCtx, agent, projectConfig, {
|
|
@@ -8655,6 +9390,10 @@ var buildHandler = (ctx, args) => protectedCommand(async (innerCtx) => {
|
|
|
8655
9390
|
name: agent.name
|
|
8656
9391
|
},
|
|
8657
9392
|
organization_id: agent.organizationId,
|
|
9393
|
+
local_dev_installation: {
|
|
9394
|
+
id: localDev.id,
|
|
9395
|
+
organization_slug: localDev.organizationSlug
|
|
9396
|
+
},
|
|
8658
9397
|
target_dir: targetDir,
|
|
8659
9398
|
files_written: [
|
|
8660
9399
|
".stackbone/project.json",
|
|
@@ -8884,9 +9623,9 @@ var buildRootCommand = async () => {
|
|
|
8884
9623
|
type: "string",
|
|
8885
9624
|
description: "Override the control plane URL."
|
|
8886
9625
|
},
|
|
8887
|
-
|
|
9626
|
+
verbose: {
|
|
8888
9627
|
type: "boolean",
|
|
8889
|
-
description: "
|
|
9628
|
+
description: "Stream every log line and docker compose output instead of the per-stage spinner UI.",
|
|
8890
9629
|
default: false
|
|
8891
9630
|
}
|
|
8892
9631
|
},
|