@usecontextlayer/cli 0.14.2 → 0.15.0

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.
Files changed (32) hide show
  1. package/README.md +36 -0
  2. package/dist/auth-CMrKVwsH.mjs +6847 -0
  3. package/dist/auth-CMrKVwsH.mjs.map +1 -0
  4. package/dist/auth.d.mts +23 -0
  5. package/dist/auth.d.mts.map +1 -0
  6. package/dist/auth.mjs +2 -0
  7. package/dist/cli.mjs +500 -192
  8. package/dist/cli.mjs.map +1 -1
  9. package/dist/{app-CJgT3IKd.mjs → resources-5FTDu19i.mjs} +263 -5552
  10. package/dist/resources-5FTDu19i.mjs.map +1 -0
  11. package/dist/rolldown-runtime-EAsCxrDo.mjs +14 -0
  12. package/dist/template/AGENTS.md +13 -0
  13. package/dist/template/README.md +44 -0
  14. package/dist/template/app/root.tsx +28 -0
  15. package/dist/template/app/routes/home.tsx +16 -3
  16. package/dist/template/databases/updates/drizzle.config.ts +8 -0
  17. package/dist/template/databases/updates/migrations/0000_create_updates.sql +6 -0
  18. package/dist/template/databases/updates/migrations/meta/0000_snapshot.json +14 -0
  19. package/dist/template/databases/updates/migrations/meta/_journal.json +13 -0
  20. package/dist/template/databases/updates/migrations/migrations.js +10 -0
  21. package/dist/template/databases/updates/schema.ts +8 -0
  22. package/dist/template/workers/app.ts +2 -1
  23. package/dist/template/workers/local-resources/index.ts +1 -0
  24. package/dist/template/workers/local-resources/wrangler.jsonc +19 -0
  25. package/dist/vite.d.mts +4439 -11
  26. package/dist/vite.d.mts.map +1 -1
  27. package/dist/vite.mjs +95 -9
  28. package/dist/vite.mjs.map +1 -1
  29. package/package.json +20 -3
  30. package/dist/app-CJgT3IKd.mjs.map +0 -1
  31. package/dist/template/workers/local-tools/index.ts +0 -1
  32. package/dist/template/workers/local-tools/wrangler.jsonc +0 -5
package/dist/cli.mjs CHANGED
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { S as execa, _ as record, a as getAccessToken, b as url, c as whoami, d as boolean, f as discriminatedUnion, g as object, h as looseObject, i as secretNameSchema, l as env, m as literal, n as readLocalManifest, o as getCurrentUser, p as hostname, r as connectionRequirementSchema, s as login, t as getLocalAppId, u as array, v as string, x as uuid, y as unknown } from "./app-CJgT3IKd.mjs";
2
+ import { t as __commonJSMin } from "./rolldown-runtime-EAsCxrDo.mjs";
3
+ import { S as uuid, a as login, b as unknown, c as array, d as hostname, f as literal, g as record, h as object, i as listOrganizations, l as boolean, n as getAccessToken, p as looseObject, r as getCurrentUser, t as LoginRequiredError, v as string, x as url, y as union } from "./auth-CMrKVwsH.mjs";
4
+ import { c as resourcesForConsumer, f as execa, i as ownerSchema, l as secretNameSchema, n as saveLocalConnection, o as joinApp, r as appResourceConfigurationSchema, s as env, t as prepareLocalResourceConfiguration, u as databaseQuerySchema } from "./resources-5FTDu19i.mjs";
3
5
  import { cp, readFile, writeFile } from "node:fs/promises";
4
6
  import path, { join } from "node:path";
5
7
  import { EventEmitter } from "node:events";
@@ -9,9 +11,6 @@ import process$1, { stdin, stdout } from "node:process";
9
11
  import { stripVTControlCharacters, styleText } from "node:util";
10
12
  import { fileURLToPath } from "node:url";
11
13
  import l__default, { createInterface } from "node:readline";
12
- //#region \0rolldown/runtime.js
13
- var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
14
- //#endregion
15
14
  //#region ../../node_modules/.pnpm/commander@15.0.0/node_modules/commander/lib/error.js
16
15
  /**
17
16
  * CommanderError class
@@ -2968,13 +2967,40 @@ function useColor() {
2968
2967
  new Command();
2969
2968
  //#endregion
2970
2969
  //#region agents.ts
2971
- const platformInstructions = "You are a ContextLayer app agent. The user message is the task for this run. Work in the app directory and return the task's result.";
2970
+ const platformInstructions = "You are a ContextLayer app agent. The user message is the task for this run. Work in the app directory and return the task's result. Use database_query to read and save persistent data in declared databases. Files are scratch data; schema changes belong in explicitly authored SQL migrations.";
2972
2971
  async function runLocalAgent(name, prompt) {
2973
2972
  const instructions = await readFile(join("agents", name, "instructions.md"), "utf8");
2974
- const { query } = await import("@anthropic-ai/claude-agent-sdk");
2973
+ const { createServer } = await import("vite");
2974
+ const { getPreparedLocalBindings, openLocalDatabases } = await import("./vite.mjs");
2975
+ const server = await createServer({
2976
+ logLevel: "error",
2977
+ server: {
2978
+ host: "127.0.0.1",
2979
+ port: 0
2980
+ }
2981
+ });
2982
+ await server.listen();
2983
+ const bindings = getPreparedLocalBindings(server);
2984
+ const configuration = resourcesForConsumer(bindings.configuration, {
2985
+ name,
2986
+ type: "agent"
2987
+ });
2988
+ const storage = await openLocalDatabases(bindings.workerName, configuration.databases);
2989
+ const { query, createSdkMcpServer, tool } = await import("@anthropic-ai/claude-agent-sdk");
2975
2990
  const run = query({
2976
2991
  options: {
2992
+ allowedTools: ["mcp__databases__database_query"],
2977
2993
  cwd: process.cwd(),
2994
+ mcpServers: { databases: createSdkMcpServer({
2995
+ name: "databases",
2996
+ tools: [tool("database_query", "Query or update a declared persistent SQLite database using parameterized SQL.", databaseQuerySchema.shape, async ({ slug, sql, params }) => {
2997
+ const result = await storage.databases.query(slug, sql, params);
2998
+ return { content: [{
2999
+ text: JSON.stringify(result),
3000
+ type: "text"
3001
+ }] };
3002
+ })]
3003
+ }) },
2978
3004
  settingSources: [],
2979
3005
  systemPrompt: {
2980
3006
  append: `${platformInstructions}\n\n${instructions}`,
@@ -3001,169 +3027,11 @@ async function runLocalAgent(name, prompt) {
3001
3027
  throw new Error("Claude ended without a result");
3002
3028
  } finally {
3003
3029
  run.close();
3030
+ await storage.dispose();
3031
+ await server.close();
3004
3032
  }
3005
3033
  }
3006
3034
  //#endregion
3007
- //#region ../shared/dist/app-connections.mjs
3008
- const appConnectionSchema = connectionRequirementSchema.extend({
3009
- connection_id: string().nullable(),
3010
- slug: string()
3011
- });
3012
- const appConnectionsSchema = object({ connections: array(appConnectionSchema) });
3013
- //#endregion
3014
- //#region owner.ts
3015
- const ownerOptionsSchema = object({ org: string().min(1).optional() });
3016
- async function resolveOwner(org, headers) {
3017
- if (!org) return {
3018
- id: (await getCurrentUser(headers)).id,
3019
- type: "user"
3020
- };
3021
- const response = await fetch(new URL("/api/platform/organizations", env.CTX_AUTH_ISSUER), { headers });
3022
- if (!response.ok) throw new Error(`Organization lookup failed: HTTP ${response.status}`);
3023
- const { organizations } = object({ organizations: array(object({
3024
- id: string().min(1),
3025
- slug: string()
3026
- })) }).parse(await response.json());
3027
- const organization = organizations.find((organization) => organization.slug === org);
3028
- if (!organization) throw new Error("Organization is not in your memberships");
3029
- return {
3030
- id: organization.id,
3031
- type: "organization"
3032
- };
3033
- }
3034
- function ownerPath(owner) {
3035
- return `${owner.type === "organization" ? "org" : "user"}/${encodeURIComponent(owner.id)}`;
3036
- }
3037
- //#endregion
3038
- //#region connections.ts
3039
- async function getLocalManifest(target) {
3040
- return target === "local" ? readLocalManifest() : void 0;
3041
- }
3042
- async function resolveAppTarget(target) {
3043
- if (target !== "local") return target;
3044
- return getLocalAppId();
3045
- }
3046
- async function assignAppConnection(target, slug, connectionId) {
3047
- const appTarget = await resolveAppTarget(target);
3048
- const response = await fetch(new URL(`/api/apps/${encodeURIComponent(appTarget)}/connections/assign`, env.CTX_PLATFORM_URL), {
3049
- body: JSON.stringify({
3050
- connection_id: connectionId,
3051
- manifest: await getLocalManifest(target),
3052
- slug
3053
- }),
3054
- headers: {
3055
- authorization: `Bearer ${await getAccessToken()}`,
3056
- "content-type": "application/json"
3057
- },
3058
- method: "POST"
3059
- });
3060
- if (!response.ok) throw new Error(`Connection assignment failed: HTTP ${response.status}`);
3061
- const assignment = object({
3062
- connection_id: string(),
3063
- slug: string()
3064
- }).parse(await response.json());
3065
- console.log(`${assignment.slug} → ${assignment.connection_id}`);
3066
- }
3067
- async function listAppConnections(target) {
3068
- const appTarget = await resolveAppTarget(target);
3069
- const manifest = await getLocalManifest(target);
3070
- const response = await fetch(new URL(`/api/apps/${encodeURIComponent(appTarget)}/connections/list`, env.CTX_PLATFORM_URL), {
3071
- body: JSON.stringify({ manifest }),
3072
- headers: {
3073
- authorization: `Bearer ${await getAccessToken()}`,
3074
- "content-type": "application/json"
3075
- },
3076
- method: "POST"
3077
- });
3078
- if (!response.ok) throw new Error(`App connections request failed: HTTP ${response.status}`);
3079
- const { connections } = appConnectionsSchema.parse(await response.json());
3080
- if (!connections.length) {
3081
- console.log("No connection requirements.");
3082
- return;
3083
- }
3084
- console.table(connections.map(({ connection_id, ...requirement }) => ({
3085
- ...requirement,
3086
- connection: connection_id ?? "unassigned"
3087
- })));
3088
- }
3089
- async function addConnection(toolkit, org) {
3090
- const headers = { authorization: `Bearer ${await getAccessToken()}` };
3091
- const owner = await resolveOwner(org, headers);
3092
- const response = await fetch(new URL(`/api/connections/${ownerPath(owner)}`, env.CTX_PLATFORM_URL), {
3093
- body: JSON.stringify({ toolkit }),
3094
- headers: {
3095
- ...headers,
3096
- "content-type": "application/json"
3097
- },
3098
- method: "POST"
3099
- });
3100
- if (!response.ok) throw new Error(`Connection authorization failed: HTTP ${response.status}`);
3101
- const { redirect_url } = object({ redirect_url: url() }).parse(await response.json());
3102
- console.log(`Open this link to authorize the Owner's account:\n${redirect_url}`);
3103
- console.log(`After authorization, run ctx connections${org ? ` --org ${org}` : ""} list to see the Owner's account.`);
3104
- }
3105
- async function listConnections(org) {
3106
- const headers = { authorization: `Bearer ${await getAccessToken()}` };
3107
- const owner = await resolveOwner(org, headers);
3108
- const response = await fetch(new URL(`/api/connections/${ownerPath(owner)}`, env.CTX_PLATFORM_URL), { headers });
3109
- if (!response.ok) throw new Error(`Connection list failed: HTTP ${response.status}`);
3110
- const { connections } = object({ connections: array(object({
3111
- alias: string().nullable(),
3112
- display_name: string().nullable(),
3113
- id: string(),
3114
- status: string(),
3115
- toolkit: string()
3116
- })) }).parse(await response.json());
3117
- if (connections.length === 0) {
3118
- console.log("No connected accounts.");
3119
- return;
3120
- }
3121
- console.table(connections);
3122
- }
3123
- //#endregion
3124
- //#region git-credential.ts
3125
- async function setupGit(cli) {
3126
- const key = `credential.${new URL("/git", env.CTX_PLATFORM_URL).href}.helper`;
3127
- const command = [
3128
- process.execPath,
3129
- fileURLToPath(cli),
3130
- "git-credential"
3131
- ].map((argument) => `'${argument.replaceAll("'", "'\\''")}'`).join(" ");
3132
- await execa("git", [
3133
- "config",
3134
- "--global",
3135
- "--replace-all",
3136
- key,
3137
- ""
3138
- ]);
3139
- await execa("git", [
3140
- "config",
3141
- "--global",
3142
- "--add",
3143
- key,
3144
- `!${command}`
3145
- ]);
3146
- }
3147
- async function gitCredential(operation) {
3148
- if (operation === "capability") {
3149
- process.stdout.write("version 0\ncapability authtype\n");
3150
- return;
3151
- }
3152
- if (operation !== "get") return;
3153
- const attributes = new URLSearchParams();
3154
- const lines = createInterface({ input: process.stdin });
3155
- for await (const line of lines) {
3156
- if (line === "") break;
3157
- const separator = line.indexOf("=");
3158
- attributes.append(line.slice(0, separator), line.slice(separator + 1));
3159
- }
3160
- literal("authtype").parse(attributes.getAll("capability[]").find((c) => c === "authtype"));
3161
- process.stdout.write(`capability[]=authtype\nauthtype=Bearer\ncredential=${await getAccessToken()}\n\n`);
3162
- }
3163
- //#endregion
3164
- //#region package.json
3165
- var version = "0.14.2";
3166
- //#endregion
3167
3035
  //#region ../../node_modules/.pnpm/fast-string-truncated-width@3.0.3/node_modules/fast-string-truncated-width/dist/utils.js
3168
3036
  const getCodePointsLength = (() => {
3169
3037
  const SURROGATE_PAIR_RE = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g;
@@ -3499,6 +3367,11 @@ var import_src = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
3499
3367
  beep
3500
3368
  };
3501
3369
  })))();
3370
+ function findCursor(s, o, l) {
3371
+ if (!l.some((r) => !r.disabled)) return s;
3372
+ const t = s + o, n = Math.max(l.length - 1, 0), e = t < 0 ? n : t > n ? 0 : t;
3373
+ return l[e]?.disabled ? findCursor(e, o < 0 ? -1 : 1, l) : e;
3374
+ }
3502
3375
  const settings = {
3503
3376
  actions: /* @__PURE__ */ new Set([
3504
3377
  "up",
@@ -3580,7 +3453,19 @@ function setRawMode(e, r) {
3580
3453
  const o = e;
3581
3454
  o.isTTY && o.setRawMode(r);
3582
3455
  }
3456
+ const getColumns = (e) => "columns" in e && typeof e.columns == "number" ? e.columns : 80;
3583
3457
  const getRows = (e) => "rows" in e && typeof e.rows == "number" ? e.rows : 20;
3458
+ function wrapTextWithPrefix(e, r, o, n = o, s = o, t) {
3459
+ return wrapAnsi(r, getColumns(e ?? stdout) - o.length, {
3460
+ hard: true,
3461
+ trim: false
3462
+ }).split(`
3463
+ `).map((c, i, m) => {
3464
+ const d = t ? t(c, i) : c;
3465
+ return i === 0 ? `${n}${d}` : i === m.length - 1 ? `${s}${d}` : `${o}${d}`;
3466
+ }).join(`
3467
+ `);
3468
+ }
3584
3469
  function runValidation(e, a) {
3585
3470
  if ("~standard" in e) {
3586
3471
  const n = e["~standard"].validate(a);
@@ -3797,6 +3682,32 @@ let u$1 = class u extends y {
3797
3682
  });
3798
3683
  }
3799
3684
  };
3685
+ let n$1 = class n extends y {
3686
+ options;
3687
+ cursor = 0;
3688
+ get _selectedValue() {
3689
+ return this.options[this.cursor];
3690
+ }
3691
+ changeValue() {
3692
+ const e = this._selectedValue;
3693
+ this.value = e === void 0 ? void 0 : e.value;
3694
+ }
3695
+ constructor(e) {
3696
+ super(e, false), this.options = e.options;
3697
+ const o = this.options.findIndex(({ value: s }) => s === e.initialValue), t = o === -1 ? 0 : o;
3698
+ this.cursor = this.options[t]?.disabled ? findCursor(t, 1, this.options) : t, this.changeValue(), this.on("cursor", (s) => {
3699
+ switch (s) {
3700
+ case "left":
3701
+ case "up":
3702
+ this.cursor = findCursor(this.cursor, -1, this.options);
3703
+ break;
3704
+ case "down":
3705
+ case "right": this.cursor = findCursor(this.cursor, 1, this.options);
3706
+ }
3707
+ this.changeValue();
3708
+ });
3709
+ }
3710
+ };
3800
3711
  //#endregion
3801
3712
  //#region ../../node_modules/.pnpm/@clack+prompts@1.8.1/node_modules/@clack/prompts/dist/index.mjs
3802
3713
  function isUnicodeSupported() {
@@ -3811,6 +3722,8 @@ const S_STEP_ERROR = unicodeOr("▲", "x");
3811
3722
  const S_STEP_SUBMIT = unicodeOr("◇", "o");
3812
3723
  const S_BAR = unicodeOr("│", "|");
3813
3724
  const S_BAR_END = unicodeOr("└", "—");
3725
+ const S_RADIO_ACTIVE = unicodeOr("●", ">");
3726
+ const S_RADIO_INACTIVE = unicodeOr("○", " ");
3814
3727
  const S_PASSWORD_MASK = unicodeOr("▪", "•");
3815
3728
  const symbol = (o) => {
3816
3729
  switch (o) {
@@ -3822,6 +3735,63 @@ const symbol = (o) => {
3822
3735
  case "validating": return styleText("dim", S_STEP_ACTIVE);
3823
3736
  }
3824
3737
  };
3738
+ const symbolBar = (o) => {
3739
+ switch (o) {
3740
+ case "initial":
3741
+ case "active": return styleText("cyan", S_BAR);
3742
+ case "cancel": return styleText("red", S_BAR);
3743
+ case "error": return styleText("yellow", S_BAR);
3744
+ case "submit": return styleText("green", S_BAR);
3745
+ }
3746
+ };
3747
+ function formatInstructionFooter(o, e) {
3748
+ const r = [`${e ? `${styleText("cyan", S_BAR)} ` : ""}${o.join(" • ")}`];
3749
+ return e && r.push(styleText("cyan", S_BAR_END)), r;
3750
+ }
3751
+ const I = (l, e, w, p, b, C = false) => {
3752
+ let r = e, O = 0;
3753
+ if (C) for (let i = p - 1; i >= w; i--) {
3754
+ const m = l[i];
3755
+ if (m && (r -= m.length), O++, r <= b) break;
3756
+ }
3757
+ else for (let i = w; i < p; i++) {
3758
+ const m = l[i];
3759
+ if (m && (r -= m.length), O++, r <= b) break;
3760
+ }
3761
+ return {
3762
+ lineCount: r,
3763
+ removals: O
3764
+ };
3765
+ };
3766
+ const limitOptions = ({ cursor: l, options: e, style: w, output: p = process.stdout, maxItems: b = Number.POSITIVE_INFINITY, columnPadding: C = 0, rowPadding: r = 4 }) => {
3767
+ const i = getColumns(p) - C, m = getRows(p), M = styleText("dim", "..."), v = Math.max(m - r, 0), a = Math.max(Math.min(b, v), 5);
3768
+ let f = 0;
3769
+ l >= a - 3 && (f = Math.max(Math.min(l - a + 3, e.length - a), 0));
3770
+ let d = a < e.length && f > 0, c = a < e.length && f + a < e.length;
3771
+ const W = Math.min(f + a, e.length), s = [];
3772
+ let g = 0;
3773
+ d && g++, c && g++;
3774
+ const T = f + (d ? 1 : 0), y = W - (c ? 1 : 0);
3775
+ for (let t = T; t < y; t++) {
3776
+ const n = e[t], h = wrapAnsi(n ? w(n, t === l) : "", i, {
3777
+ hard: true,
3778
+ trim: false
3779
+ }).split(`
3780
+ `);
3781
+ s.push(h), g += h.length;
3782
+ }
3783
+ if (g > v) {
3784
+ let t = 0, n = 0, o = g;
3785
+ const h = l - T;
3786
+ let u = v;
3787
+ const L = () => I(s, o, 0, h, u), E = () => I(s, o, h + 1, s.length, u, true);
3788
+ d ? ({lineCount: o, removals: t} = L(), o > u && (c || (u -= 1), {lineCount: o, removals: n} = E())) : (c || (u -= 1), {lineCount: o, removals: n} = E(), o > u && (u -= 1, {lineCount: o, removals: t} = L())), t > 0 && (d = true, s.splice(0, t)), n > 0 && (c = true, s.splice(s.length - n, n));
3789
+ }
3790
+ const x = [];
3791
+ d && x.push(M);
3792
+ for (const t of s) for (const n of t) x.push(n);
3793
+ return c && x.push(M), x;
3794
+ };
3825
3795
  `${styleText("dim", "↑/↓")}`, `${styleText("dim", "Space:")}`, `${styleText("dim", "Enter:")}`;
3826
3796
  const password = (e) => new u$1({
3827
3797
  validate: e.validate,
@@ -3850,21 +3820,269 @@ ${r ? styleText("cyan", S_BAR_END) : ""}
3850
3820
  }
3851
3821
  }
3852
3822
  }).prompt();
3853
- `${styleText("dim", "↑/↓")}`, `${styleText("dim", "Enter:")}`;
3823
+ const SELECT_INSTRUCTIONS = [`${styleText("dim", "↑/↓")} to navigate`, `${styleText("dim", "Enter:")} confirm`];
3824
+ const c = (t, o) => t.includes(`
3825
+ `) ? t.split(`
3826
+ `).map((d) => o(d)).join(`
3827
+ `) : o(t);
3828
+ const select = (t) => {
3829
+ const o = (n, m) => {
3830
+ if (n === void 0) return "";
3831
+ const s = n.label ?? String(n.value);
3832
+ switch (m) {
3833
+ case "disabled": return `${styleText("gray", S_RADIO_INACTIVE)} ${c(s, (i) => styleText("gray", i))}${n.hint ? ` ${styleText("dim", `(${n.hint ?? "disabled"})`)}` : ""}`;
3834
+ case "selected": return `${c(s, (i) => styleText("dim", i))}`;
3835
+ case "active": return `${styleText("green", S_RADIO_ACTIVE)} ${s}${n.hint ? ` ${styleText("dim", `(${n.hint})`)}` : ""}`;
3836
+ case "cancelled": return `${c(s, (i) => styleText(["strikethrough", "dim"], i))}`;
3837
+ default: return `${styleText("dim", S_RADIO_INACTIVE)} ${c(s, (i) => styleText("dim", i))}`;
3838
+ }
3839
+ }, d = t.showInstructions ?? true;
3840
+ return new n$1({
3841
+ options: t.options,
3842
+ signal: t.signal,
3843
+ input: t.input,
3844
+ output: t.output,
3845
+ initialValue: t.initialValue,
3846
+ render() {
3847
+ const n = t.withGuide ?? settings.withGuide, m = `${symbol(this.state)} `, s = `${symbolBar(this.state)} `, i = wrapTextWithPrefix(t.output, t.message, s, m), u = `${n ? `${styleText("gray", S_BAR)}
3848
+ ` : ""}${i}
3849
+ `;
3850
+ switch (this.state) {
3851
+ case "submit": {
3852
+ const r = n ? `${styleText("gray", S_BAR)} ` : "";
3853
+ return `${u}${wrapTextWithPrefix(t.output, o(this.options[this.cursor], "selected"), r)}`;
3854
+ }
3855
+ case "cancel": {
3856
+ const r = n ? `${styleText("gray", S_BAR)} ` : "";
3857
+ return `${u}${wrapTextWithPrefix(t.output, o(this.options[this.cursor], "cancelled"), r)}${n ? `
3858
+ ${styleText("gray", S_BAR)}` : ""}`;
3859
+ }
3860
+ default: {
3861
+ const r = n ? `${styleText("cyan", S_BAR)} ` : "", a = u.split(`
3862
+ `).length, p = d ? formatInstructionFooter(SELECT_INSTRUCTIONS, n) : n ? [styleText("cyan", S_BAR_END)] : [], f = p.join(`
3863
+ `), b = p.length + 1;
3864
+ return `${u}${r}${limitOptions({
3865
+ output: t.output,
3866
+ cursor: this.cursor,
3867
+ options: this.options,
3868
+ maxItems: t.maxItems,
3869
+ columnPadding: r.length,
3870
+ rowPadding: a + b,
3871
+ style: (g, x) => o(g, g.disabled ? "disabled" : x ? "active" : "inactive")
3872
+ }).join(`
3873
+ ${r}`)}
3874
+ ${f}
3875
+ `;
3876
+ }
3877
+ }
3878
+ }
3879
+ }).prompt();
3880
+ };
3854
3881
  `${styleText("gray", S_BAR)}`;
3855
3882
  //#endregion
3856
- //#region ../shared/dist/app-usage.mjs
3857
- const ownerSchema = discriminatedUnion("type", [object({
3858
- id: string().min(1),
3859
- type: literal("user")
3860
- }), object({
3861
- id: string().min(1),
3862
- type: literal("organization")
3863
- })]);
3864
- object({
3865
- id: uuid(),
3866
- owner: ownerSchema
3883
+ //#region ../shared/dist/app-connections.mjs
3884
+ const appConnectionSchema = appResourceConfigurationSchema.shape.connections.valueType.extend({ slug: string() });
3885
+ const appConnectionsSchema = object({ connections: array(appConnectionSchema) });
3886
+ //#endregion
3887
+ //#region ../shared/dist/connections.mjs
3888
+ const connectionSchema = object({
3889
+ alias: string().nullable(),
3890
+ display_name: string().nullable(),
3891
+ id: string(),
3892
+ status: literal("ACTIVE"),
3893
+ toolkit: string()
3894
+ });
3895
+ const connectionsSchema = object({ connections: array(connectionSchema) });
3896
+ //#endregion
3897
+ //#region owner.ts
3898
+ const ownerOptionsSchema = object({ org: string().min(1).optional() });
3899
+ async function resolveOwner(org, headers) {
3900
+ if (!org) return {
3901
+ id: (await getCurrentUser(env, headers)).id,
3902
+ type: "user"
3903
+ };
3904
+ const organization = (await listOrganizations(env, headers)).find((organization) => organization.slug === org);
3905
+ if (!organization) throw new Error("Organization is not in your memberships");
3906
+ return {
3907
+ id: organization.id,
3908
+ type: "organization"
3909
+ };
3910
+ }
3911
+ function ownerPath(owner) {
3912
+ return `${owner.type === "organization" ? "org" : "user"}/${encodeURIComponent(owner.id)}`;
3913
+ }
3914
+ //#endregion
3915
+ //#region connections.ts
3916
+ const connectionAssignmentSchema = object({
3917
+ connection_id: string().min(1).optional(),
3918
+ slug: string().min(1),
3919
+ target: string().min(1)
3920
+ }).superRefine((input, ctx) => {
3921
+ if (input.target !== "local" && input.connection_id === void 0) ctx.addIssue({
3922
+ code: "custom",
3923
+ message: "Hosted assignment requires a connection ID",
3924
+ path: ["connection_id"]
3925
+ });
3867
3926
  });
3927
+ async function assignAppConnection(target, slug, connectionId) {
3928
+ if (target === "local") {
3929
+ const configuration = await prepareLocalResourceConfiguration();
3930
+ const requirement = new Map(Object.entries(configuration.connections)).get(slug);
3931
+ if (!requirement) throw new Error(`No local connection requirement for "${slug}"`);
3932
+ if (connectionId === void 0 && !process.stdin.isTTY) throw new Error("Supply a connection ID when assigning without a terminal");
3933
+ const url = new URL("/api/connections", env.CTX_PLATFORM_URL);
3934
+ url.searchParams.set("toolkit", requirement.toolkit);
3935
+ const response = await fetch(url, { headers: { authorization: `Bearer ${await getAccessToken(env)}` } });
3936
+ if (!response.ok) throw new Error(`Personal connection list failed: HTTP ${response.status}`);
3937
+ const { connections } = connectionsSchema.parse(await response.json());
3938
+ if (connections.length === 0) throw new Error(`No personal ${requirement.toolkit} accounts. Run npx @usecontextlayer/cli connections add ${requirement.toolkit}, approve access in your browser, then rerun this assignment.`);
3939
+ if (connectionId === void 0) {
3940
+ const selected = await select({
3941
+ message: `Your ${requirement.toolkit} account for ${slug}`,
3942
+ options: connections.map((connection) => ({
3943
+ label: `${connection.display_name ?? connection.alias ?? connection.id} (${connection.id}; ${connection.status})`,
3944
+ value: connection.id
3945
+ })),
3946
+ output: process.stderr
3947
+ });
3948
+ if (isCancel(selected)) {
3949
+ process.exitCode = 130;
3950
+ return;
3951
+ }
3952
+ connectionId = selected;
3953
+ }
3954
+ if (!connections.some(({ id }) => id === connectionId)) throw new Error(`Account ${connectionId} is not one of your ${requirement.toolkit} accounts`);
3955
+ await saveLocalConnection(slug, connectionId);
3956
+ console.log(`${slug} → ${connectionId}`);
3957
+ console.log("Start or restart pnpm dev to use this assignment.");
3958
+ return;
3959
+ }
3960
+ const response = await fetch(new URL(`/api/apps/${encodeURIComponent(target)}/connections/assign`, env.CTX_PLATFORM_URL), {
3961
+ body: JSON.stringify({
3962
+ connection_id: connectionId,
3963
+ slug
3964
+ }),
3965
+ headers: {
3966
+ authorization: `Bearer ${await getAccessToken(env)}`,
3967
+ "content-type": "application/json"
3968
+ },
3969
+ method: "POST"
3970
+ });
3971
+ if (!response.ok) throw new Error(`Connection assignment failed: HTTP ${response.status}`);
3972
+ const assignment = object({
3973
+ connection_id: string(),
3974
+ slug: string()
3975
+ }).parse(await response.json());
3976
+ console.log(`${assignment.slug} → ${assignment.connection_id}`);
3977
+ }
3978
+ async function listAppConnections(target) {
3979
+ if (target === "local") {
3980
+ const { connections } = await prepareLocalResourceConfiguration();
3981
+ if (Object.keys(connections).length === 0) {
3982
+ console.log("No connection requirements.");
3983
+ return;
3984
+ }
3985
+ console.table(Object.entries(connections).map(([slug, { connection_id, consumers, mode, toolkit }]) => ({
3986
+ connection: connection_id ?? "unassigned",
3987
+ consumers: consumers.map((consumer) => consumer.type === "app" ? "app" : `agent:${consumer.name}`).join(", "),
3988
+ mode,
3989
+ slug,
3990
+ toolkit
3991
+ })));
3992
+ return;
3993
+ }
3994
+ const response = await fetch(new URL(`/api/apps/${encodeURIComponent(target)}/connections/list`, env.CTX_PLATFORM_URL), {
3995
+ body: JSON.stringify({}),
3996
+ headers: {
3997
+ authorization: `Bearer ${await getAccessToken(env)}`,
3998
+ "content-type": "application/json"
3999
+ },
4000
+ method: "POST"
4001
+ });
4002
+ if (!response.ok) throw new Error(`App connections request failed: HTTP ${response.status}`);
4003
+ const { connections } = appConnectionsSchema.parse(await response.json());
4004
+ if (!connections.length) {
4005
+ console.log("No connection requirements.");
4006
+ return;
4007
+ }
4008
+ console.table(connections.map(({ connection_id, ...requirement }) => ({
4009
+ ...requirement,
4010
+ connection: connection_id ?? "unassigned"
4011
+ })));
4012
+ }
4013
+ async function addConnection(toolkit, org) {
4014
+ const headers = { authorization: `Bearer ${await getAccessToken(env)}` };
4015
+ const owner = await resolveOwner(org, headers);
4016
+ const response = await fetch(new URL(`/api/connections/${ownerPath(owner)}`, env.CTX_PLATFORM_URL), {
4017
+ body: JSON.stringify({ toolkit }),
4018
+ headers: {
4019
+ ...headers,
4020
+ "content-type": "application/json"
4021
+ },
4022
+ method: "POST"
4023
+ });
4024
+ if (!response.ok) throw new Error(`Connection authorization failed: HTTP ${response.status}`);
4025
+ const { redirect_url } = object({ redirect_url: url() }).parse(await response.json());
4026
+ console.log(`Open this link to authorize the Owner's account:\n${redirect_url}`);
4027
+ console.log("After approving access in your browser, assign the account to an app's declared connection slug.");
4028
+ if (!org) console.log("From your app directory, choose your account for a slug in public/slate.json:\n npx @usecontextlayer/cli connection local assign <slug>");
4029
+ console.log(`For a hosted app, get the account ID and assign it:\n npx @usecontextlayer/cli connections${org ? ` --org '${org.replaceAll("'", "'\\''")}'` : ""} list\n npx @usecontextlayer/cli connection <AppID-or-hostname> assign <slug> <connection_id>`);
4030
+ }
4031
+ async function listConnections(org) {
4032
+ const headers = { authorization: `Bearer ${await getAccessToken(env)}` };
4033
+ const owner = await resolveOwner(org, headers);
4034
+ const response = await fetch(new URL(`/api/connections/${ownerPath(owner)}`, env.CTX_PLATFORM_URL), { headers });
4035
+ if (!response.ok) throw new Error(`Connection list failed: HTTP ${response.status}`);
4036
+ const { connections } = connectionsSchema.parse(await response.json());
4037
+ if (connections.length === 0) {
4038
+ console.log("No connected accounts.");
4039
+ return;
4040
+ }
4041
+ console.table(connections);
4042
+ }
4043
+ //#endregion
4044
+ //#region git-credential.ts
4045
+ async function setupGit(cli) {
4046
+ const key = `credential.${new URL("/git", env.CTX_PLATFORM_URL).href}.helper`;
4047
+ const command = [
4048
+ process.execPath,
4049
+ fileURLToPath(cli),
4050
+ "git-credential"
4051
+ ].map((argument) => `'${argument.replaceAll("'", "'\\''")}'`).join(" ");
4052
+ await execa("git", [
4053
+ "config",
4054
+ "--global",
4055
+ "--replace-all",
4056
+ key,
4057
+ ""
4058
+ ]);
4059
+ await execa("git", [
4060
+ "config",
4061
+ "--global",
4062
+ "--add",
4063
+ key,
4064
+ `!${command}`
4065
+ ]);
4066
+ }
4067
+ async function gitCredential(operation) {
4068
+ if (operation === "capability") {
4069
+ process.stdout.write("version 0\ncapability authtype\n");
4070
+ return;
4071
+ }
4072
+ if (operation !== "get") return;
4073
+ const attributes = new URLSearchParams();
4074
+ const lines = createInterface({ input: process.stdin });
4075
+ for await (const line of lines) {
4076
+ if (line === "") break;
4077
+ const separator = line.indexOf("=");
4078
+ attributes.append(line.slice(0, separator), line.slice(separator + 1));
4079
+ }
4080
+ literal("authtype").parse(attributes.getAll("capability[]").find((c) => c === "authtype"));
4081
+ process.stdout.write(`capability[]=authtype\nauthtype=Bearer\ncredential=${await getAccessToken(env)}\n\n`);
4082
+ }
4083
+ //#endregion
4084
+ //#region package.json
4085
+ var version = "0.15.0";
3868
4086
  //#endregion
3869
4087
  //#region secrets.ts
3870
4088
  const secretOptionsSchema = ownerOptionsSchema.extend({ stdin: boolean().default(false) });
@@ -3878,7 +4096,7 @@ async function resolveSecretUrl(name, org, headers) {
3878
4096
  return new URL(`/api/secrets/${ownerPath(owner)}/${encodeURIComponent(name)}`, env.CTX_PLATFORM_URL);
3879
4097
  }
3880
4098
  async function setSecret(name, options) {
3881
- const headers = { authorization: `Bearer ${await getAccessToken()}` };
4099
+ const headers = { authorization: `Bearer ${await getAccessToken(env)}` };
3882
4100
  const url = await resolveSecretUrl(name, options.org, headers);
3883
4101
  let value;
3884
4102
  if (options.stdin) {
@@ -3910,7 +4128,7 @@ async function setSecret(name, options) {
3910
4128
  console.log(JSON.stringify(confirmationSchema.parse(await response.json())));
3911
4129
  }
3912
4130
  async function dropSecret(name, org) {
3913
- const headers = { authorization: `Bearer ${await getAccessToken()}` };
4131
+ const headers = { authorization: `Bearer ${await getAccessToken(env)}` };
3914
4132
  const response = await fetch(await resolveSecretUrl(name, org, headers), {
3915
4133
  headers,
3916
4134
  method: "DELETE"
@@ -3921,9 +4139,31 @@ async function dropSecret(name, org) {
3921
4139
  //#endregion
3922
4140
  //#region cli.ts
3923
4141
  const program = new Command().name("ctx").description("ContextLayer CLI").version(version);
3924
- program.command("init <directory>").description("Create a Cloudflare React Router project with Tailwind and shadcn Base UI, register an app owned by you or an organization (ctx login), and set its immutable Git remote").option("--org <slug>", "Create the app under an organization you belong to").action(async (directory, options) => {
4142
+ program.addHelpText("afterAll", "\nRun without a global install: npx @usecontextlayer/cli <command>");
4143
+ async function authenticate(command) {
4144
+ try {
4145
+ return await getCurrentUser(env, { authorization: `Bearer ${await getAccessToken(env)}` });
4146
+ } catch (error) {
4147
+ if (error instanceof LoginRequiredError) command.error(error.message);
4148
+ throw error;
4149
+ }
4150
+ }
4151
+ async function requireLogin(command) {
4152
+ await authenticate(command);
4153
+ }
4154
+ program.command("init <directory>").description("Create a private ContextLayer app on main with React Router, Tailwind, and shadcn; requires login, Node.js 24+, pnpm, and Git author identity; sets the Git remote and prints how to start and publish the app").option("--org <slug>", "Create the app under an organization you belong to").hook("preAction", requireLogin).action(async (directory, options) => {
3925
4155
  const input = object({ organization_slug: string().min(1).optional() }).parse({ organization_slug: options.org });
3926
- const accessToken = await getAccessToken();
4156
+ const accessToken = await getAccessToken(env);
4157
+ const gitIdentity = await execa("git", ["var", "GIT_AUTHOR_IDENT"], {
4158
+ reject: false,
4159
+ stdio: [
4160
+ "inherit",
4161
+ "ignore",
4162
+ "inherit"
4163
+ ]
4164
+ });
4165
+ process.exitCode = gitIdentity.exitCode;
4166
+ if (gitIdentity.exitCode !== 0) return;
3927
4167
  const result = await execa("pnpm", [
3928
4168
  "create",
3929
4169
  "cloudflare@latest",
@@ -3935,6 +4175,11 @@ program.command("init <directory>").description("Create a Cloudflare React Route
3935
4175
  "--",
3936
4176
  "--yes"
3937
4177
  ], {
4178
+ env: {
4179
+ GIT_CONFIG_COUNT: "1",
4180
+ GIT_CONFIG_KEY_0: "init.defaultBranch",
4181
+ GIT_CONFIG_VALUE_0: "main"
4182
+ },
3938
4183
  reject: false,
3939
4184
  stdio: "inherit"
3940
4185
  });
@@ -3942,7 +4187,18 @@ program.command("init <directory>").description("Create a Cloudflare React Route
3942
4187
  if (result.exitCode !== 0) return;
3943
4188
  const tsconfigPath = join(directory, "tsconfig.json");
3944
4189
  const tsconfig = looseObject({ compilerOptions: record(string(), unknown()) }).parse(JSON.parse(await readFile(tsconfigPath, "utf8")));
3945
- tsconfig.compilerOptions.paths = { "~/*": ["./app/*"] };
4190
+ tsconfig.compilerOptions.paths = {
4191
+ "@/*": ["./*"],
4192
+ "~/*": ["./app/*"]
4193
+ };
4194
+ const cloudflareTsconfigPath = join(directory, "tsconfig.cloudflare.json");
4195
+ const cloudflareTsconfig = looseObject({
4196
+ compilerOptions: record(string(), unknown()),
4197
+ include: array(string())
4198
+ }).parse(JSON.parse(await readFile(cloudflareTsconfigPath, "utf8")));
4199
+ cloudflareTsconfig.compilerOptions.paths = tsconfig.compilerOptions.paths;
4200
+ cloudflareTsconfig.include.push("databases/**/schema.ts");
4201
+ await writeFile(cloudflareTsconfigPath, `${JSON.stringify(cloudflareTsconfig, null, 2)}\n`);
3946
4202
  await writeFile(tsconfigPath, `${JSON.stringify(tsconfig, null, 2)}\n`);
3947
4203
  const ui = await execa("pnpm", [
3948
4204
  "dlx",
@@ -3979,7 +4235,8 @@ program.command("init <directory>").description("Create a Cloudflare React Route
3979
4235
  const install = await execa("pnpm", [
3980
4236
  "add",
3981
4237
  `@usecontextlayer/sdk@${version}`,
3982
- "zod@^4.4.3"
4238
+ "zod@^4.4.3",
4239
+ "drizzle-orm@0.45.3"
3983
4240
  ], {
3984
4241
  cwd: directory,
3985
4242
  reject: false,
@@ -3987,6 +4244,21 @@ program.command("init <directory>").description("Create a Cloudflare React Route
3987
4244
  });
3988
4245
  process.exitCode = install.exitCode;
3989
4246
  if (install.exitCode !== 0) return;
4247
+ const migrationTool = await execa("pnpm", [
4248
+ "add",
4249
+ "-D",
4250
+ "drizzle-kit@0.31.11"
4251
+ ], {
4252
+ cwd: directory,
4253
+ reject: false,
4254
+ stdio: "inherit"
4255
+ });
4256
+ process.exitCode = migrationTool.exitCode;
4257
+ if (migrationTool.exitCode !== 0) return;
4258
+ const packagePath = join(directory, "package.json");
4259
+ const projectPackage = looseObject({ scripts: record(string(), string()) }).parse(JSON.parse(await readFile(packagePath, "utf8")));
4260
+ projectPackage.scripts["db:migration"] = "drizzle-kit generate --custom";
4261
+ await writeFile(packagePath, `${JSON.stringify(projectPackage, null, 2)}\n`);
3990
4262
  const response = await fetch(new URL("/api/apps", env.CTX_PLATFORM_URL), {
3991
4263
  body: JSON.stringify(input),
3992
4264
  headers: {
@@ -4012,27 +4284,63 @@ program.command("init <directory>").description("Create a Cloudflare React Route
4012
4284
  stdio: "inherit"
4013
4285
  });
4014
4286
  process.exitCode = git.exitCode;
4287
+ if (git.exitCode !== 0) return;
4288
+ console.log(`
4289
+ ContextLayer app created: ${app.id}
4290
+ Dashboard: ${new URL(`/dashboard/apps/${app.id}`, env.CTX_PLATFORM_URL).href}
4291
+ Hosted URL: https://${app.public_hostname}/ (available after deployment)
4292
+
4293
+ Start locally:
4294
+ cd '${directory.replaceAll("'", "'\\''")}'
4295
+ pnpm dev
4296
+ Open the local URL printed by the development server.
4297
+
4298
+ Publish your changes:
4299
+ git add .
4300
+ git commit
4301
+ git push -u origin main
4302
+
4303
+ See README.md for the development and connection workflow.
4304
+ `);
4015
4305
  });
4016
4306
  program.command("login").description("Sign in by approving a browser code and configure Git credentials for ContextLayer repositories").action(async () => {
4017
- await login();
4307
+ await login(env);
4018
4308
  await setupGit(new URL(import.meta.url));
4019
4309
  console.log("Git credentials configured for ContextLayer.");
4020
4310
  });
4021
- program.command("git-credential <operation>").description("Git credential helper using the saved login; invoked automatically by Git").action(gitCredential);
4022
- program.command("whoami").description("Show your ContextLayer user ID using the saved login").action(whoami);
4023
- const connections = program.command("connections").description("Manage Owner-owned connected accounts or list an app's requirements").option("--org <org_slug>", "Manage this organization's connections; defaults to the signed-in user").argument("[target]", "AppID, public hostname, or local (run from the app root)").addArgument(new Argument("[operation]", "App connection operation").choices(["list"])).action(async (target, operation) => {
4311
+ program.command("git-credential <operation>").description("Git credential helper using the saved login; invoked automatically by Git").hook("preAction", async (command) => {
4312
+ if (command.args[0] === "get") await requireLogin(command);
4313
+ }).action(gitCredential);
4314
+ program.command("whoami").description("Show your ContextLayer user ID verified by the auth server").action(async (_options, command) => {
4315
+ console.log((await authenticate(command)).id);
4316
+ });
4317
+ program.command("join").argument("<target>", "AppID, public hostname, or local (run from the app root)").description("Join a private app you can view using ctx login, enabling member-scoped unattended work; returns app and user IDs").hook("preAction", requireLogin).action(async (target) => {
4318
+ await joinApp(union([
4319
+ literal("local"),
4320
+ uuid(),
4321
+ hostname()
4322
+ ]).parse(target));
4323
+ });
4324
+ const connections = program.command("connections").description("Manage Owner-owned connected accounts or list app and agent connection requirements; local reads only the current folder and its saved development choices").option("--org <org_slug>", "Manage this organization's connections; defaults to the signed-in user").argument("[target]", "AppID or public hostname (pushed requirements), or local (current folder; no platform request)").addArgument(new Argument("[operation]", "App connection operation").choices(["list"])).hook("preAction", async (command, actionCommand) => {
4325
+ if (actionCommand !== command || command.args[0] !== "local") await requireLogin(actionCommand);
4326
+ }).action(async (target, operation) => {
4024
4327
  if (!target || !operation) connections.error("Expected: ctx connections <target> list");
4025
4328
  await listAppConnections(target);
4026
4329
  });
4027
4330
  connections.command("list").description("List the selected Owner's Composio accounts with their toolkit, identity, and status").action(async (_options, command) => {
4028
4331
  await listConnections(ownerOptionsSchema.parse(command.optsWithGlobals()).org);
4029
4332
  });
4030
- connections.command("add <toolkit>").description("Print a Composio authorization link for the selected Owner's account (for example, gmail)").action(async (toolkit, _options, command) => {
4333
+ connections.command("add <toolkit>").description("Authorize the selected Owner's account in your browser (for example, gmail), then assign it to an app's declared connection slug with ctx connection").action(async (toolkit, _options, command) => {
4031
4334
  const options = ownerOptionsSchema.parse(command.optsWithGlobals());
4032
4335
  await addConnection(string().min(1).parse(toolkit), options.org);
4033
4336
  });
4034
- program.command("connection").description("Assign a connection using the requirement’s viewer or owner mode; owner mode requires app management").argument("<target>", "AppID, public hostname, or local (run from the app root)").addArgument(new Argument("<operation>").choices(["assign"])).argument("<slug>", "Connection requirement slug").argument("<connection_id>", "Composio connected-account ID").action(async (target, _operation, slug, connectionId) => {
4035
- await assignAppConnection(target, slug, connectionId);
4337
+ program.command("connection").description("Bind a declared slug: local saves your personal account in .contextlayer/connections.json; AppID/hostname saves a hosted assignment under its declared Owner").argument("<target>", "AppID, public hostname, or local (run from the app root)").addArgument(new Argument("<operation>").choices(["assign"])).argument("<slug>", "Connection slug declared in your Slate (for example, work-email)").argument("[connection_id]", "Connected account ID; omit locally to choose your accounts for the declared toolkit; required for hosted targets").hook("preAction", requireLogin).action(async (target, _operation, slug, connectionId) => {
4338
+ const input = connectionAssignmentSchema.parse({
4339
+ connection_id: connectionId,
4340
+ slug,
4341
+ target
4342
+ });
4343
+ await assignAppConnection(input.target, input.slug, input.connection_id);
4036
4344
  });
4037
4345
  program.command("agent").addArgument(new Argument("<target>", "Run from the app root").choices(["local"])).addArgument(new Argument("<operation>").choices(["run"])).argument("<name>", "Agent directory under agents/").argument("<prompt>", "Task to send as the user message").description("Run agents/<name>/instructions.md locally with Claude Agent SDK and native Claude authentication; no platform server or ctx login required").action(async (_target, _operation, name, prompt) => {
4038
4346
  const input = object({
@@ -4044,7 +4352,7 @@ program.command("agent").addArgument(new Argument("<target>", "Run from the app
4044
4352
  });
4045
4353
  console.log(await runLocalAgent(input.name, input.prompt));
4046
4354
  });
4047
- const secrets = program.command("secrets").description("Save or drop Owner-owned shared secrets through the authenticated platform").option("--org <org_slug>", "Use this organization's Owner; defaults to the signed-in user");
4355
+ const secrets = program.command("secrets").description("Save or drop Owner-owned shared secrets through the authenticated platform").option("--org <org_slug>", "Use this organization's Owner; defaults to the signed-in user").hook("preAction", requireLogin);
4048
4356
  secrets.command("set").argument("<name>", "Secret name: uppercase letters, digits, and underscores; cannot start with a digit").description("Save a secret with a masked password prompt; returns only Owner and name").option("--stdin", "Read the exact UTF-8 value from stdin instead of prompting; preserves whitespace and newlines").action(async (name, _options, command) => {
4049
4357
  await setSecret(secretNameSchema.parse(name), secretOptionsSchema.parse(command.optsWithGlobals()));
4050
4358
  });