@usecontextlayer/cli 0.16.2 → 0.18.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 (53) hide show
  1. package/README.md +4 -4
  2. package/dist/auth-BL9AxZGb.mjs +1214 -0
  3. package/dist/auth-BL9AxZGb.mjs.map +1 -0
  4. package/dist/auth.d.mts +17 -2
  5. package/dist/auth.d.mts.map +1 -1
  6. package/dist/auth.mjs +1 -2
  7. package/dist/cli.mjs +173 -217
  8. package/dist/cli.mjs.map +1 -1
  9. package/dist/database-codegen-CxDtyvam.mjs +24 -0
  10. package/dist/database-codegen-CxDtyvam.mjs.map +1 -0
  11. package/dist/database-dialect-BLWWgYGo.mjs +56 -0
  12. package/dist/database-dialect-BLWWgYGo.mjs.map +1 -0
  13. package/dist/database.d.mts +35 -0
  14. package/dist/database.d.mts.map +1 -0
  15. package/dist/database.mjs +32 -0
  16. package/dist/database.mjs.map +1 -0
  17. package/dist/databases-SvX3IgqC.mjs +23 -0
  18. package/dist/databases-SvX3IgqC.mjs.map +1 -0
  19. package/dist/env-C_tllJ4u.mjs +98 -0
  20. package/dist/env-C_tllJ4u.mjs.map +1 -0
  21. package/dist/local-databases-BeXOeSs-.mjs +53 -0
  22. package/dist/local-databases-BeXOeSs-.mjs.map +1 -0
  23. package/dist/local-databases-C5qUZcUH.mjs +1 -0
  24. package/dist/{resources--XCCMeP6.mjs → resources-0GJALfam.mjs} +28 -180
  25. package/dist/resources-0GJALfam.mjs.map +1 -0
  26. package/dist/{auth-DOsE8GwJ.mjs → schemas-D9UReYiT.mjs} +37 -1212
  27. package/dist/schemas-D9UReYiT.mjs.map +1 -0
  28. package/dist/schemas-DeaNWrS-.d.mts +2213 -0
  29. package/dist/schemas-DeaNWrS-.d.mts.map +1 -0
  30. package/dist/template.tar.gz +0 -0
  31. package/dist/vite.d.mts +8 -4333
  32. package/dist/vite.d.mts.map +1 -1
  33. package/dist/vite.mjs +16 -67
  34. package/dist/vite.mjs.map +1 -1
  35. package/package.json +12 -7
  36. package/dist/auth-DOsE8GwJ.mjs.map +0 -1
  37. package/dist/resources--XCCMeP6.mjs.map +0 -1
  38. package/dist/rolldown-runtime-EAsCxrDo.mjs +0 -14
  39. package/dist/template/AGENTS.md +0 -13
  40. package/dist/template/README.md +0 -46
  41. package/dist/template/app/root.tsx +0 -66
  42. package/dist/template/app/routes/home.tsx +0 -25
  43. package/dist/template/databases/updates/drizzle.config.ts +0 -8
  44. package/dist/template/databases/updates/migrations/0000_create_updates.sql +0 -6
  45. package/dist/template/databases/updates/migrations/meta/0000_snapshot.json +0 -14
  46. package/dist/template/databases/updates/migrations/meta/_journal.json +0 -13
  47. package/dist/template/databases/updates/migrations/migrations.js +0 -10
  48. package/dist/template/databases/updates/schema.ts +0 -8
  49. package/dist/template/public/slate.json +0 -4
  50. package/dist/template/vite.config.ts +0 -9
  51. package/dist/template/workers/app.ts +0 -8
  52. package/dist/template/workers/local-resources/index.ts +0 -6
  53. package/dist/template/workers/local-resources/wrangler.jsonc +0 -19
package/dist/cli.mjs CHANGED
@@ -1,16 +1,21 @@
1
1
  #!/usr/bin/env node
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-DOsE8GwJ.mjs";
4
- import { a as env, c as ownerSchema, d as databaseQuerySchema, i as joinSlate, l as resourcesForConsumer, n as saveLocalConnection, o as noCommitSchema, p as execa, s as slateResourceConfigurationSchema, t as prepareLocalResourceConfiguration, u as secretNameSchema } from "./resources--XCCMeP6.mjs";
5
- import { cp, readFile, writeFile } from "node:fs/promises";
6
- import path, { join } from "node:path";
2
+ import { a as login, i as listOrganizations, n as getAccessToken, r as getCurrentUser, t as LoginRequiredError } from "./auth-BL9AxZGb.mjs";
3
+ import { a as boolean, c as literal, g as uuid, h as url, i as array, o as discriminatedUnion, p as string, r as _undefined, s as hostname, u as object } from "./schemas-D9UReYiT.mjs";
4
+ import { a as databaseSlugSchema, l as secretNameSchema, n as env, s as resourcesForConsumer } from "./env-C_tllJ4u.mjs";
5
+ import { a as noCommitSchema, c as execa, n as prepareLocalResourceConfiguration, o as slateResourceConfigurationSchema, r as saveLocalConnection, s as ownerSchema } from "./resources-0GJALfam.mjs";
6
+ import { t as databaseQuerySchema } from "./databases-SvX3IgqC.mjs";
7
+ import { readFile } from "node:fs/promises";
8
+ import path, { join, resolve } from "node:path";
9
+ import { fileURLToPath } from "node:url";
7
10
  import { EventEmitter } from "node:events";
8
11
  import childProcess from "node:child_process";
9
12
  import fs from "node:fs";
10
13
  import process$1, { stdin, stdout } from "node:process";
11
14
  import { stripVTControlCharacters, styleText } from "node:util";
12
- import { fileURLToPath } from "node:url";
13
15
  import l__default, { createInterface } from "node:readline";
16
+ //#region \0rolldown/runtime.js
17
+ var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
18
+ //#endregion
14
19
  //#region ../../node_modules/.pnpm/commander@15.0.0/node_modules/commander/lib/error.js
15
20
  /**
16
21
  * CommanderError class
@@ -2966,12 +2971,72 @@ function useColor() {
2966
2971
  }
2967
2972
  new Command();
2968
2973
  //#endregion
2974
+ //#region ../shared/dist/accounts.mjs
2975
+ const accountSchema = object({
2976
+ alias: string().nullable(),
2977
+ display_name: string().nullable(),
2978
+ id: string(),
2979
+ status: literal("ACTIVE"),
2980
+ toolkit: string()
2981
+ });
2982
+ const accountsSchema = object({ accounts: array(accountSchema) });
2983
+ //#endregion
2984
+ //#region owner.ts
2985
+ const ownerOptionsSchema = object({ org: string().min(1).optional() });
2986
+ async function resolveOwner(org, headers) {
2987
+ if (!org) return {
2988
+ id: (await getCurrentUser(env, headers)).id,
2989
+ type: "personal"
2990
+ };
2991
+ const organization = (await listOrganizations(env, headers)).find((organization) => organization.slug === org);
2992
+ if (!organization) throw new Error("Organization is not in your memberships");
2993
+ return {
2994
+ id: organization.id,
2995
+ type: "organization"
2996
+ };
2997
+ }
2998
+ function ownerPath(owner) {
2999
+ return `${owner.type === "organization" ? "org" : "personal"}/${encodeURIComponent(owner.id)}`;
3000
+ }
3001
+ //#endregion
3002
+ //#region accounts.ts
3003
+ async function connectAccount(toolkit, org) {
3004
+ const headers = { authorization: `Bearer ${await getAccessToken(env)}` };
3005
+ const owner = await resolveOwner(org, headers);
3006
+ const response = await fetch(new URL(`/api/accounts/${ownerPath(owner)}`, env.CTX_PLATFORM_URL), {
3007
+ body: JSON.stringify({ toolkit }),
3008
+ headers: {
3009
+ ...headers,
3010
+ "content-type": "application/json"
3011
+ },
3012
+ method: "POST"
3013
+ });
3014
+ if (!response.ok) throw new Error(`Connecting account failed: HTTP ${response.status}`);
3015
+ const { redirect_url } = object({ redirect_url: url() }).parse(await response.json());
3016
+ console.log(`Open this link to connect the Owner's account:\n${redirect_url}`);
3017
+ console.log("After connecting in your browser, assign the connected account to a slate's declared connection.");
3018
+ if (!org) console.log("From your checkout, assign your connected account to a connection declared in slate.json:\n npx @usecontextlayer/cli connections local assign <slug>");
3019
+ console.log(`For a hosted slate, get the connected account ID and assign it:\n npx @usecontextlayer/cli accounts${org ? ` --org '${org.replaceAll("'", "'\\''")}'` : ""} list\n npx @usecontextlayer/cli connections <SlateID-or-hostname> assign <slug> <account_id>`);
3020
+ }
3021
+ async function listAccounts(org) {
3022
+ const headers = { authorization: `Bearer ${await getAccessToken(env)}` };
3023
+ const owner = await resolveOwner(org, headers);
3024
+ const response = await fetch(new URL(`/api/accounts/${ownerPath(owner)}`, env.CTX_PLATFORM_URL), { headers });
3025
+ if (!response.ok) throw new Error(`Connected account list failed: HTTP ${response.status}`);
3026
+ const { accounts } = accountsSchema.parse(await response.json());
3027
+ if (accounts.length === 0) {
3028
+ console.log("No connected accounts.");
3029
+ return;
3030
+ }
3031
+ console.table(accounts);
3032
+ }
3033
+ //#endregion
2969
3034
  //#region agents.ts
2970
- const platformInstructions = "You are an agent of a ContextLayer slate.The user message is the task for this run. Work in the checkout 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.";
3035
+ const platformInstructions = "You are an agent of a ContextLayer slate. The user message is the task for this run. Work in the checkout 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 Kysely migrations.";
2971
3036
  async function runLocalAgent(name, prompt) {
2972
3037
  const instructions = await readFile(join("agents", name, "instructions.md"), "utf8");
2973
3038
  const { createServer } = await import("vite");
2974
- const { getPreparedLocalBindings, openLocalDatabases } = await import("./vite.mjs");
3039
+ const { getPreparedLocalBindings } = await import("./vite.mjs");
2975
3040
  const server = await createServer({
2976
3041
  logLevel: "error",
2977
3042
  server: {
@@ -2985,6 +3050,7 @@ async function runLocalAgent(name, prompt) {
2985
3050
  name,
2986
3051
  type: "agent"
2987
3052
  });
3053
+ const { openLocalDatabases } = await import("./local-databases-C5qUZcUH.mjs");
2988
3054
  const storage = await openLocalDatabases(bindings.workerName, configuration.databases);
2989
3055
  const { query, createSdkMcpServer, tool } = await import("@anthropic-ai/claude-agent-sdk");
2990
3056
  const run = query({
@@ -3880,86 +3946,64 @@ ${f}
3880
3946
  };
3881
3947
  `${styleText("gray", S_BAR)}`;
3882
3948
  //#endregion
3883
- //#region ../shared/dist/connections.mjs
3884
- const connectionSchema = object({
3885
- alias: string().nullable(),
3886
- display_name: string().nullable(),
3887
- id: string(),
3888
- status: literal("ACTIVE"),
3889
- toolkit: string()
3890
- });
3891
- const connectionsSchema = object({ connections: array(connectionSchema) });
3892
- //#endregion
3893
3949
  //#region ../shared/dist/slate-connections.mjs
3894
3950
  const slateConnectionSchema = slateResourceConfigurationSchema.shape.connections.valueType.extend({ slug: string() });
3895
3951
  const slateConnectionsSchema = object({ connections: array(slateConnectionSchema) });
3896
3952
  //#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: "personal"
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" : "personal"}/${encodeURIComponent(owner.id)}`;
3913
- }
3914
- //#endregion
3915
3953
  //#region connections.ts
3916
- const connectionAssignmentSchema = object({
3917
- connection_id: string().min(1).optional(),
3954
+ const connectionsCommandSchema = discriminatedUnion("operation", [object({
3955
+ account_id: _undefined(),
3956
+ operation: literal("list"),
3957
+ slug: _undefined(),
3958
+ target: string().min(1)
3959
+ }), object({
3960
+ account_id: string().min(1).optional(),
3961
+ operation: literal("assign"),
3918
3962
  slug: string().min(1),
3919
3963
  target: string().min(1)
3920
3964
  }).superRefine((input, ctx) => {
3921
- if (input.target !== "local" && input.connection_id === void 0) ctx.addIssue({
3965
+ if (input.target !== "local" && input.account_id === void 0) ctx.addIssue({
3922
3966
  code: "custom",
3923
- message: "Hosted assignment requires a connection ID",
3924
- path: ["connection_id"]
3967
+ message: "Hosted assignment requires an account ID",
3968
+ path: ["account_id"]
3925
3969
  });
3926
- });
3927
- async function assignSlateConnection(target, slug, connectionId) {
3970
+ })]);
3971
+ async function assignSlateConnection(target, slug, accountId) {
3928
3972
  if (target === "local") {
3929
3973
  const configuration = await prepareLocalResourceConfiguration();
3930
3974
  const requirement = new Map(Object.entries(configuration.connections)).get(slug);
3931
3975
  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);
3976
+ if (accountId === void 0 && !process.stdin.isTTY) throw new Error("Supply an account ID when assigning without a terminal");
3977
+ const url = new URL("/api/accounts", env.CTX_PLATFORM_URL);
3934
3978
  url.searchParams.set("toolkit", requirement.toolkit);
3935
3979
  const response = await fetch(url, { headers: { authorization: `Bearer ${await getAccessToken(env)}` } });
3936
3980
  if (!response.ok) throw new Error(`Personal connected account list failed: HTTP ${response.status}`);
3937
- const { connections } = connectionsSchema.parse(await response.json());
3938
- if (connections.length === 0) throw new Error(`No personal connected accounts for ${requirement.toolkit}. Run npx @usecontextlayer/cli connections add ${requirement.toolkit} to connect an account in your browser, then rerun this assignment.`);
3939
- if (connectionId === void 0) {
3940
- const accountId = await select({
3981
+ const { accounts } = accountsSchema.parse(await response.json());
3982
+ if (accounts.length === 0) throw new Error(`No personal connected accounts for ${requirement.toolkit}. Run npx @usecontextlayer/cli accounts add ${requirement.toolkit} to connect an account in your browser, then rerun this assignment.`);
3983
+ if (accountId === void 0) {
3984
+ const selectedAccountId = await select({
3941
3985
  message: `Assign a connected ${requirement.toolkit} account to ${slug}`,
3942
- options: connections.map((connection) => ({
3943
- label: `${connection.display_name ?? connection.alias ?? connection.id} (${connection.id}; ${connection.status})`,
3944
- value: connection.id
3986
+ options: accounts.map((account) => ({
3987
+ label: `${account.display_name ?? account.alias ?? account.id} (${account.id}; ${account.status})`,
3988
+ value: account.id
3945
3989
  })),
3946
3990
  output: process.stderr
3947
3991
  });
3948
- if (isCancel(accountId)) {
3992
+ if (isCancel(selectedAccountId)) {
3949
3993
  process.exitCode = 130;
3950
3994
  return;
3951
3995
  }
3952
- connectionId = accountId;
3996
+ accountId = selectedAccountId;
3953
3997
  }
3954
- if (!connections.some(({ id }) => id === connectionId)) throw new Error(`Connected account ${connectionId} is not one of your connected ${requirement.toolkit} accounts`);
3955
- await saveLocalConnection(slug, connectionId);
3956
- console.log(`${slug} → ${connectionId}`);
3998
+ if (!accounts.some(({ id }) => id === accountId)) throw new Error(`Connected account ${accountId} is not one of your connected ${requirement.toolkit} accounts`);
3999
+ await saveLocalConnection(slug, accountId);
4000
+ console.log(`${slug} → ${accountId}`);
3957
4001
  console.log("Start or restart pnpm dev to use this assignment.");
3958
4002
  return;
3959
4003
  }
3960
4004
  const response = await fetch(new URL(`/api/slates/${encodeURIComponent(target)}/connections/assign`, env.CTX_PLATFORM_URL), {
3961
4005
  body: JSON.stringify({
3962
- connection_id: connectionId,
4006
+ account_id: accountId,
3963
4007
  slug
3964
4008
  }),
3965
4009
  headers: {
@@ -3971,26 +4015,18 @@ async function assignSlateConnection(target, slug, connectionId) {
3971
4015
  if (response.status === 409 && noCommitSchema.safeParse(await response.json()).success) throw new Error("Nothing has been pushed to this slate yet. Push to main before configuring hosted resources.");
3972
4016
  if (!response.ok) throw new Error(`Connection assignment failed: HTTP ${response.status}`);
3973
4017
  const assignment = object({
3974
- connection_id: string(),
4018
+ account_id: string(),
3975
4019
  slug: string()
3976
4020
  }).parse(await response.json());
3977
- console.log(`${assignment.slug} → ${assignment.connection_id}`);
4021
+ console.log(`${assignment.slug} → ${assignment.account_id}`);
3978
4022
  }
3979
- async function listSlateConnections(target) {
4023
+ async function prepareSlateConnections(target) {
3980
4024
  if (target === "local") {
3981
4025
  const { connections } = await prepareLocalResourceConfiguration();
3982
- if (Object.keys(connections).length === 0) {
3983
- console.log("No connection requirements.");
3984
- return;
3985
- }
3986
- console.table(Object.entries(connections).map(([slug, { connection_id, consumers, mode, toolkit }]) => ({
3987
- connected_account: connection_id ?? "unassigned",
3988
- consumers: consumers.map((consumer) => consumer.type === "app" ? "app" : `agent:${consumer.name}`).join(", "),
3989
- mode,
3990
- slug,
3991
- toolkit
3992
- })));
3993
- return;
4026
+ return Object.entries(connections).map(([slug, connection]) => ({
4027
+ ...connection,
4028
+ slug
4029
+ }));
3994
4030
  }
3995
4031
  const response = await fetch(new URL(`/api/slates/${encodeURIComponent(target)}/connections/list`, env.CTX_PLATFORM_URL), {
3996
4032
  body: JSON.stringify({}),
@@ -4002,46 +4038,20 @@ async function listSlateConnections(target) {
4002
4038
  });
4003
4039
  if (response.status === 409 && noCommitSchema.safeParse(await response.json()).success) throw new Error("Nothing has been pushed to this slate yet. Push to main before configuring hosted resources.");
4004
4040
  if (!response.ok) throw new Error(`Slate connections request failed: HTTP ${response.status}`);
4005
- const { connections } = slateConnectionsSchema.parse(await response.json());
4041
+ return slateConnectionsSchema.parse(await response.json()).connections;
4042
+ }
4043
+ async function listSlateConnections(target) {
4044
+ const connections = await prepareSlateConnections(target);
4006
4045
  if (!connections.length) {
4007
4046
  console.log("No connection requirements.");
4008
4047
  return;
4009
4048
  }
4010
- console.table(connections.map(({ connection_id, ...requirement }) => ({
4049
+ console.table(connections.map(({ account_id, consumers, ...requirement }) => ({
4011
4050
  ...requirement,
4012
- connected_account: connection_id ?? "unassigned"
4051
+ connected_account: account_id ?? "unassigned",
4052
+ consumers: consumers.map((consumer) => consumer.type === "app" ? "app" : `agent:${consumer.name}`).join(", ")
4013
4053
  })));
4014
4054
  }
4015
- async function connectAccount(toolkit, org) {
4016
- const headers = { authorization: `Bearer ${await getAccessToken(env)}` };
4017
- const owner = await resolveOwner(org, headers);
4018
- const response = await fetch(new URL(`/api/connections/${ownerPath(owner)}`, env.CTX_PLATFORM_URL), {
4019
- body: JSON.stringify({ toolkit }),
4020
- headers: {
4021
- ...headers,
4022
- "content-type": "application/json"
4023
- },
4024
- method: "POST"
4025
- });
4026
- if (!response.ok) throw new Error(`Connecting account failed: HTTP ${response.status}`);
4027
- const { redirect_url } = object({ redirect_url: url() }).parse(await response.json());
4028
- console.log(`Open this link to connect the Owner's account:\n${redirect_url}`);
4029
- console.log("After connecting in your browser, assign the connected account to a slate's declared connection.");
4030
- if (!org) console.log("From your checkout, assign your connected account to a connection declared in slate.json:\n npx @usecontextlayer/cli connection local assign <slug>");
4031
- console.log(`For a hosted slate, get the connected account ID and assign it:\n npx @usecontextlayer/cli connections${org ? ` --org '${org.replaceAll("'", "'\\''")}'` : ""} list\n npx @usecontextlayer/cli connection <SlateID-or-hostname> assign <slug> <connection_id>`);
4032
- }
4033
- async function listConnections(org) {
4034
- const headers = { authorization: `Bearer ${await getAccessToken(env)}` };
4035
- const owner = await resolveOwner(org, headers);
4036
- const response = await fetch(new URL(`/api/connections/${ownerPath(owner)}`, env.CTX_PLATFORM_URL), { headers });
4037
- if (!response.ok) throw new Error(`Connected account list failed: HTTP ${response.status}`);
4038
- const { connections } = connectionsSchema.parse(await response.json());
4039
- if (connections.length === 0) {
4040
- console.log("No connected accounts.");
4041
- return;
4042
- }
4043
- console.table(connections);
4044
- }
4045
4055
  //#endregion
4046
4056
  //#region git-credential.ts
4047
4057
  async function configureGit(cli) {
@@ -4084,7 +4094,7 @@ async function gitCredential(operation) {
4084
4094
  }
4085
4095
  //#endregion
4086
4096
  //#region package.json
4087
- var version = "0.16.2";
4097
+ var version = "0.18.0";
4088
4098
  //#endregion
4089
4099
  //#region secrets.ts
4090
4100
  const secretOptionsSchema = ownerOptionsSchema.extend({ stdin: boolean().default(false) });
@@ -4141,19 +4151,29 @@ async function dropSecret(name, org) {
4141
4151
  //#endregion
4142
4152
  //#region cli.ts
4143
4153
  const program = new Command().name("ctx").description("ContextLayer CLI").version(version);
4144
- program.addHelpText("afterAll", "\nRun without a global install: npx @usecontextlayer/cli <command>");
4154
+ program.addHelpText("afterAll", `\nEnvironment (current values):\n CTX_PLATFORM_URL ${env.CTX_PLATFORM_URL}\n CTX_AUTH_ISSUER ${env.CTX_AUTH_ISSUER}\n CTX_AUTH_CLIENT_ID ${env.CTX_AUTH_CLIENT_ID}\n CTX_CONFIG_DIR ${env.CTX_CONFIG_DIR} Saved-login directory; one login per directory.\n\nRun without a global install: npx @usecontextlayer/cli <command>`);
4145
4155
  async function authenticate(command) {
4146
4156
  try {
4147
4157
  return await getCurrentUser(env, { authorization: `Bearer ${await getAccessToken(env)}` });
4148
4158
  } catch (error) {
4149
- if (error instanceof LoginRequiredError) command.error(error.message);
4159
+ if (error instanceof LoginRequiredError) {
4160
+ const lines = [error.message];
4161
+ if (error.mismatch) {
4162
+ const { saved, expected } = error.mismatch;
4163
+ lines.push(`Saved issuer: ${saved.issuer}`, `Expected issuer: ${expected.issuer}`);
4164
+ if (saved.clientId !== expected.clientId) lines.push(`Saved client ID: ${saved.clientId}`, `Expected client ID: ${expected.clientId}`);
4165
+ }
4166
+ const settings = Object.entries(env).map(([key, value]) => `${key}='${value.replaceAll("'", "'\\''")}'`).join(" ");
4167
+ lines.push(`Run: ${settings} npx @usecontextlayer/cli login`);
4168
+ command.error(lines.join("\n"));
4169
+ }
4150
4170
  throw error;
4151
4171
  }
4152
4172
  }
4153
4173
  async function requireLogin(command) {
4154
4174
  await authenticate(command);
4155
4175
  }
4156
- program.command("init <directory>").description("Create a private ContextLayer slate 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 deploy the slate").option("--org <slug>", "Create the slate under an organization you belong to").hook("preAction", requireLogin).action(async (directory, options) => {
4176
+ program.command("init <directory>").description("Create a private ContextLayer slate on main with React Router, Tailwind, and shadcn; requires login, Node.js 22.22.0+, pnpm, and Git author identity; sets the Git remote and prints how to start and deploy the slate").option("--org <slug>", "Create the slate under an organization you belong to").hook("preAction", requireLogin).action(async (directory, options) => {
4157
4177
  const input = object({ organization_slug: string().min(1).optional() }).parse({ organization_slug: options.org });
4158
4178
  const accessToken = await getAccessToken(env);
4159
4179
  const gitIdentity = await execa("git", ["var", "GIT_AUTHOR_IDENT"], {
@@ -4166,15 +4186,15 @@ program.command("init <directory>").description("Create a private ContextLayer s
4166
4186
  });
4167
4187
  process.exitCode = gitIdentity.exitCode;
4168
4188
  if (gitIdentity.exitCode !== 0) return;
4169
- const result = await execa("pnpm", [
4170
- "create",
4171
- "cloudflare@latest",
4189
+ const result = await execa(process.execPath, [
4190
+ fileURLToPath(new URL("./dist/cli.js", import.meta.resolve("create-react-router/package.json"))),
4172
4191
  directory,
4173
- "--framework=react-router",
4174
- "--accept-defaults",
4175
- "--git",
4176
- "--no-deploy",
4177
- "--",
4192
+ "--template",
4193
+ fileURLToPath(new URL("./template.tar.gz", import.meta.url)),
4194
+ "--package-manager",
4195
+ "pnpm",
4196
+ "--install",
4197
+ "--git-init",
4178
4198
  "--yes"
4179
4199
  ], {
4180
4200
  env: {
@@ -4187,80 +4207,6 @@ program.command("init <directory>").description("Create a private ContextLayer s
4187
4207
  });
4188
4208
  process.exitCode = result.exitCode;
4189
4209
  if (result.exitCode !== 0) return;
4190
- const tsconfigPath = join(directory, "tsconfig.json");
4191
- const tsconfig = looseObject({ compilerOptions: record(string(), unknown()) }).parse(JSON.parse(await readFile(tsconfigPath, "utf8")));
4192
- tsconfig.compilerOptions.paths = {
4193
- "@/*": ["./*"],
4194
- "~/*": ["./app/*"]
4195
- };
4196
- const cloudflareTsconfigPath = join(directory, "tsconfig.cloudflare.json");
4197
- const cloudflareTsconfig = looseObject({
4198
- compilerOptions: record(string(), unknown()),
4199
- include: array(string())
4200
- }).parse(JSON.parse(await readFile(cloudflareTsconfigPath, "utf8")));
4201
- cloudflareTsconfig.compilerOptions.paths = tsconfig.compilerOptions.paths;
4202
- cloudflareTsconfig.include.push("databases/**/schema.ts");
4203
- await writeFile(cloudflareTsconfigPath, `${JSON.stringify(cloudflareTsconfig, null, 2)}\n`);
4204
- await writeFile(tsconfigPath, `${JSON.stringify(tsconfig, null, 2)}\n`);
4205
- const ui = await execa("pnpm", [
4206
- "dlx",
4207
- "shadcn@latest",
4208
- "init",
4209
- "--preset",
4210
- "nova",
4211
- "--base",
4212
- "base",
4213
- "--yes",
4214
- "--no-monorepo"
4215
- ], {
4216
- cwd: directory,
4217
- reject: false,
4218
- stdio: "inherit"
4219
- });
4220
- process.exitCode = ui.exitCode;
4221
- if (ui.exitCode !== 0) return;
4222
- await cp(new URL("./template/", import.meta.url), directory, { recursive: true });
4223
- const configure = await execa("pnpm", [
4224
- "config",
4225
- "set",
4226
- "--location=project",
4227
- "--json",
4228
- "minimumReleaseAgeExclude",
4229
- "[\"@usecontextlayer/*\"]"
4230
- ], {
4231
- cwd: directory,
4232
- reject: false,
4233
- stdio: "inherit"
4234
- });
4235
- process.exitCode = configure.exitCode;
4236
- if (configure.exitCode !== 0) return;
4237
- const install = await execa("pnpm", [
4238
- "add",
4239
- `@usecontextlayer/sdk@${version}`,
4240
- "zod@^4.4.3",
4241
- "drizzle-orm@0.45.3"
4242
- ], {
4243
- cwd: directory,
4244
- reject: false,
4245
- stdio: "inherit"
4246
- });
4247
- process.exitCode = install.exitCode;
4248
- if (install.exitCode !== 0) return;
4249
- const migrationTool = await execa("pnpm", [
4250
- "add",
4251
- "-D",
4252
- "drizzle-kit@0.31.11"
4253
- ], {
4254
- cwd: directory,
4255
- reject: false,
4256
- stdio: "inherit"
4257
- });
4258
- process.exitCode = migrationTool.exitCode;
4259
- if (migrationTool.exitCode !== 0) return;
4260
- const packagePath = join(directory, "package.json");
4261
- const checkoutPackage = looseObject({ scripts: record(string(), string()) }).parse(JSON.parse(await readFile(packagePath, "utf8")));
4262
- checkoutPackage.scripts["db:migration"] = "drizzle-kit generate --custom";
4263
- await writeFile(packagePath, `${JSON.stringify(checkoutPackage, null, 2)}\n`);
4264
4210
  const response = await fetch(new URL("/api/slates", env.CTX_PLATFORM_URL), {
4265
4211
  body: JSON.stringify(input),
4266
4212
  headers: {
@@ -4297,11 +4243,11 @@ Start locally:
4297
4243
  pnpm dev
4298
4244
  Open the local URL printed by the development server.
4299
4245
 
4300
- Deploy your changes:
4301
- git add .
4302
- git commit
4246
+ Deploy the committed starter:
4303
4247
  git push -u origin main
4304
4248
 
4249
+ Commit your own changes before subsequent pushes.
4250
+
4305
4251
  See README.md for the development and connection workflow.
4306
4252
  `);
4307
4253
  });
@@ -4313,36 +4259,37 @@ program.command("login").description("Sign in by approving a browser code and co
4313
4259
  program.command("git-credential <operation>").description("Git credential helper using the saved login; invoked automatically by Git").hook("preAction", async (command) => {
4314
4260
  if (command.args[0] === "get") await requireLogin(command);
4315
4261
  }).action(gitCredential);
4316
- program.command("whoami").description("Show your ContextLayer user ID verified by the auth server").action(async (_options, command) => {
4317
- console.log((await authenticate(command)).id);
4262
+ program.command("whoami").description("Show your ContextLayer user ID verified by the auth server, with the issuer and platform URL in use").action(async (_options, command) => {
4263
+ const { id } = await authenticate(command);
4264
+ console.log(`User: ${id}\nIssuer: ${env.CTX_AUTH_ISSUER}\nPlatform: ${env.CTX_PLATFORM_URL}`);
4318
4265
  });
4319
- program.command("join").argument("<target>", "SlateID, public hostname, or local (run from the checkout root)").description("Join a private slate you can view using ctx login, enabling member-scoped unattended work; returns slate and user IDs").hook("preAction", requireLogin).action(async (target) => {
4320
- await joinSlate(union([
4321
- literal("local"),
4322
- uuid(),
4323
- hostname()
4324
- ]).parse(target));
4266
+ const accounts = program.command("accounts").description("Connect and list the selected Owner's connected accounts").option("--org <slug>", "Manage this organization's connected accounts; defaults to the signed-in user").hook("preAction", requireLogin);
4267
+ accounts.command("list").description("List the selected Owner's connected accounts with their toolkit, identity, and status").action(async (_options, command) => {
4268
+ await listAccounts(ownerOptionsSchema.parse(command.optsWithGlobals()).org);
4325
4269
  });
4326
- const connections = program.command("connections").description("Manage Owner-owned connected accounts or list app and agent connections and assignments; local reads only the current checkout and its saved development assignments").option("--org <slug>", "Manage this organization's connected accounts; defaults to the signed-in user").argument("[target]", "SlateID or public hostname (pushed requirements), or local (current checkout; no platform request)").addArgument(new Argument("[operation]", "Slate connection operation").choices(["list"])).hook("preAction", async (command, actionCommand) => {
4327
- if (actionCommand !== command || command.args[0] !== "local") await requireLogin(actionCommand);
4328
- }).action(async (target, operation) => {
4329
- if (!target || !operation) connections.error("Expected: ctx connections <target> list");
4330
- await listSlateConnections(target);
4331
- });
4332
- connections.command("list").description("List the selected Owner's connected accounts with their toolkit, identity, and status").action(async (_options, command) => {
4333
- await listConnections(ownerOptionsSchema.parse(command.optsWithGlobals()).org);
4334
- });
4335
- connections.command("add <toolkit>").description("Connect the selected Owner's account in your browser (for example, gmail), then assign the connected account to a slate's declared connection with ctx connection").action(async (toolkit, _options, command) => {
4270
+ accounts.command("add <toolkit>").description("Connect the selected Owner's account in your browser (for example, gmail), then assign it with ctx connections <target> assign <slug> [account_id]").action(async (toolkit, _options, command) => {
4336
4271
  const options = ownerOptionsSchema.parse(command.optsWithGlobals());
4337
4272
  await connectAccount(string().min(1).parse(toolkit), options.org);
4338
4273
  });
4339
- program.command("connection").description("Assign a connected account to a declared connection: local saves a personal connected account assignment in .contextlayer/connections.json; SlateID/hostname saves a hosted assignment under its declared Owner").argument("<target>", "SlateID, public hostname, or local (run from the checkout root)").addArgument(new Argument("<operation>").choices(["assign"])).argument("<slug>", "Connection slug declared in slate.json (for example, work-email)").argument("[connection_id]", "Connected account ID; omit locally to assign a connected account for the declared toolkit interactively; required for hosted targets").hook("preAction", requireLogin).action(async (target, _operation, slug, connectionId) => {
4340
- const input = connectionAssignmentSchema.parse({
4341
- connection_id: connectionId,
4274
+ program.command("connections").description("List declared app and agent connections or assign connected accounts; local reads and saves checkout configuration, SlateID/hostname uses pushed declarations and hosted assignments").argument("<target>", "SlateID, public hostname, or local (run from the checkout root)").addArgument(new Argument("<operation>").choices(["list", "assign"])).argument("[slug]", "Required for assign: connection slug declared in slate.json (for example, work-email)").argument("[account_id]", "For assign: connected account ID; omit locally to choose interactively; required for hosted targets").action(async (target, operation, slug, accountId, _options, command) => {
4275
+ const parsed = connectionsCommandSchema.safeParse({
4276
+ account_id: accountId,
4277
+ operation,
4342
4278
  slug,
4343
4279
  target
4344
4280
  });
4345
- await assignSlateConnection(input.target, input.slug, input.connection_id);
4281
+ if (!parsed.success) command.error(parsed.error.issues.map((issue) => issue.message).join("\n"));
4282
+ const input = parsed.data;
4283
+ if (input.operation !== "list" || input.target !== "local") await requireLogin(command);
4284
+ switch (input.operation) {
4285
+ case "list":
4286
+ await listSlateConnections(input.target);
4287
+ break;
4288
+ case "assign":
4289
+ await assignSlateConnection(input.target, input.slug, input.account_id);
4290
+ break;
4291
+ default: throw new Error(`Unhandled connections operation: ${JSON.stringify(input)}`);
4292
+ }
4346
4293
  });
4347
4294
  program.command("agent").addArgument(new Argument("<target>", "Run from the checkout 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; Vite startup also requires ctx login when the app declares connections").action(async (_target, _operation, name, prompt) => {
4348
4295
  const input = object({
@@ -4361,6 +4308,15 @@ secrets.command("set").argument("<name>", "Secret name: uppercase letters, digit
4361
4308
  secrets.command("drop").argument("<name>", "Secret name: uppercase letters, digits, and underscores; cannot start with a digit").description("Delete the selected Owner's shared secret; returns only Owner and name").action(async (name, _options, command) => {
4362
4309
  await dropSecret(secretNameSchema.parse(name), secretOptionsSchema.parse(command.optsWithGlobals()).org);
4363
4310
  });
4311
+ program.command("db.codegen").argument("<slug>", "Database declared in public/slate.json or agents/<name>/slate.json").description("Generate databases/<slug>/types.ts from the migrated local database").addHelpText("after", "\nRun from the slate root with pnpm dev running.\nApply migrations with pnpm exec kysely before generating types.\n\nExample:\n pnpm db.codegen notes").action(async (slug, _options, command) => {
4312
+ const { generateDatabaseTypes } = await import("./database-codegen-CxDtyvam.mjs");
4313
+ try {
4314
+ await generateDatabaseTypes(databaseSlugSchema.parse(slug));
4315
+ } catch (error) {
4316
+ if (error instanceof Error && "code" in error && error.code === "ENOENT" && "path" in error && error.path === resolve("public/slate.json")) command.error(`Missing ${error.path}. Run pnpm db.codegen from the slate root containing public/slate.json.`);
4317
+ throw error;
4318
+ }
4319
+ });
4364
4320
  await program.parseAsync();
4365
4321
  //#endregion
4366
4322
  export {};