@usecontextlayer/cli 0.14.3 → 0.16.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.
- package/README.md +38 -0
- package/dist/auth-DOsE8GwJ.mjs +6847 -0
- package/dist/auth-DOsE8GwJ.mjs.map +1 -0
- package/dist/auth.d.mts +23 -0
- package/dist/auth.d.mts.map +1 -0
- package/dist/auth.mjs +2 -0
- package/dist/cli.mjs +177 -74
- package/dist/cli.mjs.map +1 -1
- package/dist/{resources-DEdwfmku.mjs → resources-B-EYHG1j.mjs} +198 -5665
- package/dist/resources-B-EYHG1j.mjs.map +1 -0
- package/dist/rolldown-runtime-EAsCxrDo.mjs +14 -0
- package/dist/template/AGENTS.md +13 -0
- package/dist/template/README.md +46 -0
- package/dist/template/app/root.tsx +28 -0
- package/dist/template/app/routes/home.tsx +17 -3
- package/dist/template/databases/updates/drizzle.config.ts +8 -0
- package/dist/template/databases/updates/migrations/0000_create_updates.sql +6 -0
- package/dist/template/databases/updates/migrations/meta/0000_snapshot.json +14 -0
- package/dist/template/databases/updates/migrations/meta/_journal.json +13 -0
- package/dist/template/databases/updates/migrations/migrations.js +10 -0
- package/dist/template/databases/updates/schema.ts +8 -0
- package/dist/template/workers/app.ts +3 -2
- package/dist/template/workers/local-resources/index.ts +6 -0
- package/dist/template/workers/local-resources/wrangler.jsonc +19 -0
- package/dist/vite.d.mts +4427 -3
- package/dist/vite.d.mts.map +1 -1
- package/dist/vite.mjs +94 -10
- package/dist/vite.mjs.map +1 -1
- package/package.json +19 -3
- package/dist/resources-DEdwfmku.mjs.map +0 -1
- package/dist/template/workers/local-tools/index.ts +0 -1
- package/dist/template/workers/local-tools/wrangler.jsonc +0 -5
package/dist/auth.d.mts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region env.d.ts
|
|
2
|
+
declare const env: {
|
|
3
|
+
CTX_AUTH_CLIENT_ID: string;
|
|
4
|
+
CTX_AUTH_ISSUER: string;
|
|
5
|
+
CTX_PLATFORM_URL: string;
|
|
6
|
+
};
|
|
7
|
+
//#endregion
|
|
8
|
+
//#region auth.d.ts
|
|
9
|
+
type AuthEnv = Pick<typeof env, "CTX_AUTH_ISSUER" | "CTX_AUTH_CLIENT_ID">;
|
|
10
|
+
export declare class LoginRequiredError extends Error {
|
|
11
|
+
constructor(reason: string, options?: ErrorOptions);
|
|
12
|
+
}
|
|
13
|
+
export declare function getAccessToken(env: AuthEnv): Promise<string>;
|
|
14
|
+
export declare function getCurrentUser(env: AuthEnv, headers: HeadersInit): Promise<{
|
|
15
|
+
id: string;
|
|
16
|
+
}>;
|
|
17
|
+
export declare function listOrganizations(env: AuthEnv, headers: HeadersInit): Promise<{
|
|
18
|
+
id: string;
|
|
19
|
+
slug: string;
|
|
20
|
+
}[]>;
|
|
21
|
+
//#endregion
|
|
22
|
+
export type { AuthEnv };
|
|
23
|
+
//# sourceMappingURL=auth.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.mts","names":[],"sources":["../env.ts","../auth.ts"],"mappings":";cAMa;;;;;;;KCCD,UAAU,YAAY;qBAYrB,2BAA2B;EACvC,YAAY,gBAAgB,UAAU;;wBAiDjB,eAAe,KAAK,UAAO;wBAqC3B,eAAe,KAAK,SAAS,SAAS,cAAW;;;wBAOjD,kBAAkB,KAAK,SAAS,SAAS,cAAW"}
|
package/dist/auth.mjs
ADDED
package/dist/cli.mjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
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 resourcesForConsumer, f as execa, i as joinSlate, l as secretNameSchema, n as saveLocalConnection, o as slateResourceConfigurationSchema, s as ownerSchema, t as prepareLocalResourceConfiguration, u as databaseQuerySchema } from "./resources-B-EYHG1j.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
|
|
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.";
|
|
2972
2971
|
async function runLocalAgent(name, prompt) {
|
|
2973
2972
|
const instructions = await readFile(join("agents", name, "instructions.md"), "utf8");
|
|
2974
|
-
const {
|
|
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,6 +3027,8 @@ 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
|
|
@@ -3852,34 +3880,28 @@ ${f}
|
|
|
3852
3880
|
};
|
|
3853
3881
|
`${styleText("gray", S_BAR)}`;
|
|
3854
3882
|
//#endregion
|
|
3855
|
-
//#region ../shared/dist/app-connections.mjs
|
|
3856
|
-
const appConnectionSchema = appResourceConfigurationSchema.shape.connections.valueType.extend({ slug: string() });
|
|
3857
|
-
const appConnectionsSchema = object({ connections: array(appConnectionSchema) });
|
|
3858
|
-
//#endregion
|
|
3859
3883
|
//#region ../shared/dist/connections.mjs
|
|
3860
3884
|
const connectionSchema = object({
|
|
3861
3885
|
alias: string().nullable(),
|
|
3862
3886
|
display_name: string().nullable(),
|
|
3863
3887
|
id: string(),
|
|
3864
|
-
status:
|
|
3888
|
+
status: literal("ACTIVE"),
|
|
3865
3889
|
toolkit: string()
|
|
3866
3890
|
});
|
|
3867
3891
|
const connectionsSchema = object({ connections: array(connectionSchema) });
|
|
3868
3892
|
//#endregion
|
|
3893
|
+
//#region ../shared/dist/slate-connections.mjs
|
|
3894
|
+
const slateConnectionSchema = slateResourceConfigurationSchema.shape.connections.valueType.extend({ slug: string() });
|
|
3895
|
+
const slateConnectionsSchema = object({ connections: array(slateConnectionSchema) });
|
|
3896
|
+
//#endregion
|
|
3869
3897
|
//#region owner.ts
|
|
3870
3898
|
const ownerOptionsSchema = object({ org: string().min(1).optional() });
|
|
3871
3899
|
async function resolveOwner(org, headers) {
|
|
3872
3900
|
if (!org) return {
|
|
3873
|
-
id: (await getCurrentUser(headers)).id,
|
|
3874
|
-
type: "
|
|
3901
|
+
id: (await getCurrentUser(env, headers)).id,
|
|
3902
|
+
type: "personal"
|
|
3875
3903
|
};
|
|
3876
|
-
const
|
|
3877
|
-
if (!response.ok) throw new Error(`Organization lookup failed: HTTP ${response.status}`);
|
|
3878
|
-
const { organizations } = object({ organizations: array(object({
|
|
3879
|
-
id: string().min(1),
|
|
3880
|
-
slug: string()
|
|
3881
|
-
})) }).parse(await response.json());
|
|
3882
|
-
const organization = organizations.find((organization) => organization.slug === org);
|
|
3904
|
+
const organization = (await listOrganizations(env, headers)).find((organization) => organization.slug === org);
|
|
3883
3905
|
if (!organization) throw new Error("Organization is not in your memberships");
|
|
3884
3906
|
return {
|
|
3885
3907
|
id: organization.id,
|
|
@@ -3887,7 +3909,7 @@ async function resolveOwner(org, headers) {
|
|
|
3887
3909
|
};
|
|
3888
3910
|
}
|
|
3889
3911
|
function ownerPath(owner) {
|
|
3890
|
-
return `${owner.type === "organization" ? "org" : "
|
|
3912
|
+
return `${owner.type === "organization" ? "org" : "personal"}/${encodeURIComponent(owner.id)}`;
|
|
3891
3913
|
}
|
|
3892
3914
|
//#endregion
|
|
3893
3915
|
//#region connections.ts
|
|
@@ -3902,7 +3924,7 @@ const connectionAssignmentSchema = object({
|
|
|
3902
3924
|
path: ["connection_id"]
|
|
3903
3925
|
});
|
|
3904
3926
|
});
|
|
3905
|
-
async function
|
|
3927
|
+
async function assignSlateConnection(target, slug, connectionId) {
|
|
3906
3928
|
if (target === "local") {
|
|
3907
3929
|
const configuration = await prepareLocalResourceConfiguration();
|
|
3908
3930
|
const requirement = new Map(Object.entries(configuration.connections)).get(slug);
|
|
@@ -3910,37 +3932,38 @@ async function assignAppConnection(target, slug, connectionId) {
|
|
|
3910
3932
|
if (connectionId === void 0 && !process.stdin.isTTY) throw new Error("Supply a connection ID when assigning without a terminal");
|
|
3911
3933
|
const url = new URL("/api/connections", env.CTX_PLATFORM_URL);
|
|
3912
3934
|
url.searchParams.set("toolkit", requirement.toolkit);
|
|
3913
|
-
const response = await fetch(url, { headers: { authorization: `Bearer ${await getAccessToken()}` } });
|
|
3914
|
-
if (!response.ok) throw new Error(`Personal
|
|
3935
|
+
const response = await fetch(url, { headers: { authorization: `Bearer ${await getAccessToken(env)}` } });
|
|
3936
|
+
if (!response.ok) throw new Error(`Personal connected account list failed: HTTP ${response.status}`);
|
|
3915
3937
|
const { connections } = connectionsSchema.parse(await response.json());
|
|
3916
|
-
if (connections.length === 0) throw new Error(`No personal ${requirement.toolkit}
|
|
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.`);
|
|
3917
3939
|
if (connectionId === void 0) {
|
|
3918
|
-
const
|
|
3919
|
-
message: `
|
|
3940
|
+
const accountId = await select({
|
|
3941
|
+
message: `Assign a connected ${requirement.toolkit} account to ${slug}`,
|
|
3920
3942
|
options: connections.map((connection) => ({
|
|
3921
3943
|
label: `${connection.display_name ?? connection.alias ?? connection.id} (${connection.id}; ${connection.status})`,
|
|
3922
3944
|
value: connection.id
|
|
3923
3945
|
})),
|
|
3924
3946
|
output: process.stderr
|
|
3925
3947
|
});
|
|
3926
|
-
if (isCancel(
|
|
3948
|
+
if (isCancel(accountId)) {
|
|
3927
3949
|
process.exitCode = 130;
|
|
3928
3950
|
return;
|
|
3929
3951
|
}
|
|
3930
|
-
connectionId =
|
|
3952
|
+
connectionId = accountId;
|
|
3931
3953
|
}
|
|
3932
|
-
if (!connections.some(({ id }) => id === connectionId)) throw new Error(`
|
|
3954
|
+
if (!connections.some(({ id }) => id === connectionId)) throw new Error(`Connected account ${connectionId} is not one of your connected ${requirement.toolkit} accounts`);
|
|
3933
3955
|
await saveLocalConnection(slug, connectionId);
|
|
3934
3956
|
console.log(`${slug} → ${connectionId}`);
|
|
3957
|
+
console.log("Start or restart pnpm dev to use this assignment.");
|
|
3935
3958
|
return;
|
|
3936
3959
|
}
|
|
3937
|
-
const response = await fetch(new URL(`/api/
|
|
3960
|
+
const response = await fetch(new URL(`/api/slates/${encodeURIComponent(target)}/connections/assign`, env.CTX_PLATFORM_URL), {
|
|
3938
3961
|
body: JSON.stringify({
|
|
3939
3962
|
connection_id: connectionId,
|
|
3940
3963
|
slug
|
|
3941
3964
|
}),
|
|
3942
3965
|
headers: {
|
|
3943
|
-
authorization: `Bearer ${await getAccessToken()}`,
|
|
3966
|
+
authorization: `Bearer ${await getAccessToken(env)}`,
|
|
3944
3967
|
"content-type": "application/json"
|
|
3945
3968
|
},
|
|
3946
3969
|
method: "POST"
|
|
@@ -3952,7 +3975,7 @@ async function assignAppConnection(target, slug, connectionId) {
|
|
|
3952
3975
|
}).parse(await response.json());
|
|
3953
3976
|
console.log(`${assignment.slug} → ${assignment.connection_id}`);
|
|
3954
3977
|
}
|
|
3955
|
-
async function
|
|
3978
|
+
async function listSlateConnections(target) {
|
|
3956
3979
|
if (target === "local") {
|
|
3957
3980
|
const { connections } = await prepareLocalResourceConfiguration();
|
|
3958
3981
|
if (Object.keys(connections).length === 0) {
|
|
@@ -3960,7 +3983,7 @@ async function listAppConnections(target) {
|
|
|
3960
3983
|
return;
|
|
3961
3984
|
}
|
|
3962
3985
|
console.table(Object.entries(connections).map(([slug, { connection_id, consumers, mode, toolkit }]) => ({
|
|
3963
|
-
|
|
3986
|
+
connected_account: connection_id ?? "unassigned",
|
|
3964
3987
|
consumers: consumers.map((consumer) => consumer.type === "app" ? "app" : `agent:${consumer.name}`).join(", "),
|
|
3965
3988
|
mode,
|
|
3966
3989
|
slug,
|
|
@@ -3968,27 +3991,27 @@ async function listAppConnections(target) {
|
|
|
3968
3991
|
})));
|
|
3969
3992
|
return;
|
|
3970
3993
|
}
|
|
3971
|
-
const response = await fetch(new URL(`/api/
|
|
3994
|
+
const response = await fetch(new URL(`/api/slates/${encodeURIComponent(target)}/connections/list`, env.CTX_PLATFORM_URL), {
|
|
3972
3995
|
body: JSON.stringify({}),
|
|
3973
3996
|
headers: {
|
|
3974
|
-
authorization: `Bearer ${await getAccessToken()}`,
|
|
3997
|
+
authorization: `Bearer ${await getAccessToken(env)}`,
|
|
3975
3998
|
"content-type": "application/json"
|
|
3976
3999
|
},
|
|
3977
4000
|
method: "POST"
|
|
3978
4001
|
});
|
|
3979
|
-
if (!response.ok) throw new Error(`
|
|
3980
|
-
const { connections } =
|
|
4002
|
+
if (!response.ok) throw new Error(`Slate connections request failed: HTTP ${response.status}`);
|
|
4003
|
+
const { connections } = slateConnectionsSchema.parse(await response.json());
|
|
3981
4004
|
if (!connections.length) {
|
|
3982
4005
|
console.log("No connection requirements.");
|
|
3983
4006
|
return;
|
|
3984
4007
|
}
|
|
3985
4008
|
console.table(connections.map(({ connection_id, ...requirement }) => ({
|
|
3986
4009
|
...requirement,
|
|
3987
|
-
|
|
4010
|
+
connected_account: connection_id ?? "unassigned"
|
|
3988
4011
|
})));
|
|
3989
4012
|
}
|
|
3990
|
-
async function
|
|
3991
|
-
const headers = { authorization: `Bearer ${await getAccessToken()}` };
|
|
4013
|
+
async function connectAccount(toolkit, org) {
|
|
4014
|
+
const headers = { authorization: `Bearer ${await getAccessToken(env)}` };
|
|
3992
4015
|
const owner = await resolveOwner(org, headers);
|
|
3993
4016
|
const response = await fetch(new URL(`/api/connections/${ownerPath(owner)}`, env.CTX_PLATFORM_URL), {
|
|
3994
4017
|
body: JSON.stringify({ toolkit }),
|
|
@@ -3998,16 +4021,18 @@ async function addConnection(toolkit, org) {
|
|
|
3998
4021
|
},
|
|
3999
4022
|
method: "POST"
|
|
4000
4023
|
});
|
|
4001
|
-
if (!response.ok) throw new Error(`
|
|
4024
|
+
if (!response.ok) throw new Error(`Connecting account failed: HTTP ${response.status}`);
|
|
4002
4025
|
const { redirect_url } = object({ redirect_url: url() }).parse(await response.json());
|
|
4003
|
-
console.log(`Open this link to
|
|
4004
|
-
console.log(
|
|
4026
|
+
console.log(`Open this link to connect the Owner's account:\n${redirect_url}`);
|
|
4027
|
+
console.log("After connecting in your browser, assign the connected account to a slate's declared connection.");
|
|
4028
|
+
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>");
|
|
4029
|
+
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>`);
|
|
4005
4030
|
}
|
|
4006
4031
|
async function listConnections(org) {
|
|
4007
|
-
const headers = { authorization: `Bearer ${await getAccessToken()}` };
|
|
4032
|
+
const headers = { authorization: `Bearer ${await getAccessToken(env)}` };
|
|
4008
4033
|
const owner = await resolveOwner(org, headers);
|
|
4009
4034
|
const response = await fetch(new URL(`/api/connections/${ownerPath(owner)}`, env.CTX_PLATFORM_URL), { headers });
|
|
4010
|
-
if (!response.ok) throw new Error(`
|
|
4035
|
+
if (!response.ok) throw new Error(`Connected account list failed: HTTP ${response.status}`);
|
|
4011
4036
|
const { connections } = connectionsSchema.parse(await response.json());
|
|
4012
4037
|
if (connections.length === 0) {
|
|
4013
4038
|
console.log("No connected accounts.");
|
|
@@ -4017,7 +4042,7 @@ async function listConnections(org) {
|
|
|
4017
4042
|
}
|
|
4018
4043
|
//#endregion
|
|
4019
4044
|
//#region git-credential.ts
|
|
4020
|
-
async function
|
|
4045
|
+
async function configureGit(cli) {
|
|
4021
4046
|
const key = `credential.${new URL("/git", env.CTX_PLATFORM_URL).href}.helper`;
|
|
4022
4047
|
const command = [
|
|
4023
4048
|
process.execPath,
|
|
@@ -4053,11 +4078,11 @@ async function gitCredential(operation) {
|
|
|
4053
4078
|
attributes.append(line.slice(0, separator), line.slice(separator + 1));
|
|
4054
4079
|
}
|
|
4055
4080
|
literal("authtype").parse(attributes.getAll("capability[]").find((c) => c === "authtype"));
|
|
4056
|
-
process.stdout.write(`capability[]=authtype\nauthtype=Bearer\ncredential=${await getAccessToken()}\n\n`);
|
|
4081
|
+
process.stdout.write(`capability[]=authtype\nauthtype=Bearer\ncredential=${await getAccessToken(env)}\n\n`);
|
|
4057
4082
|
}
|
|
4058
4083
|
//#endregion
|
|
4059
4084
|
//#region package.json
|
|
4060
|
-
var version = "0.
|
|
4085
|
+
var version = "0.16.0";
|
|
4061
4086
|
//#endregion
|
|
4062
4087
|
//#region secrets.ts
|
|
4063
4088
|
const secretOptionsSchema = ownerOptionsSchema.extend({ stdin: boolean().default(false) });
|
|
@@ -4071,7 +4096,7 @@ async function resolveSecretUrl(name, org, headers) {
|
|
|
4071
4096
|
return new URL(`/api/secrets/${ownerPath(owner)}/${encodeURIComponent(name)}`, env.CTX_PLATFORM_URL);
|
|
4072
4097
|
}
|
|
4073
4098
|
async function setSecret(name, options) {
|
|
4074
|
-
const headers = { authorization: `Bearer ${await getAccessToken()}` };
|
|
4099
|
+
const headers = { authorization: `Bearer ${await getAccessToken(env)}` };
|
|
4075
4100
|
const url = await resolveSecretUrl(name, options.org, headers);
|
|
4076
4101
|
let value;
|
|
4077
4102
|
if (options.stdin) {
|
|
@@ -4103,7 +4128,7 @@ async function setSecret(name, options) {
|
|
|
4103
4128
|
console.log(JSON.stringify(confirmationSchema.parse(await response.json())));
|
|
4104
4129
|
}
|
|
4105
4130
|
async function dropSecret(name, org) {
|
|
4106
|
-
const headers = { authorization: `Bearer ${await getAccessToken()}` };
|
|
4131
|
+
const headers = { authorization: `Bearer ${await getAccessToken(env)}` };
|
|
4107
4132
|
const response = await fetch(await resolveSecretUrl(name, org, headers), {
|
|
4108
4133
|
headers,
|
|
4109
4134
|
method: "DELETE"
|
|
@@ -4114,9 +4139,31 @@ async function dropSecret(name, org) {
|
|
|
4114
4139
|
//#endregion
|
|
4115
4140
|
//#region cli.ts
|
|
4116
4141
|
const program = new Command().name("ctx").description("ContextLayer CLI").version(version);
|
|
4117
|
-
program.
|
|
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 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) => {
|
|
4118
4155
|
const input = object({ organization_slug: string().min(1).optional() }).parse({ organization_slug: options.org });
|
|
4119
|
-
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;
|
|
4120
4167
|
const result = await execa("pnpm", [
|
|
4121
4168
|
"create",
|
|
4122
4169
|
"cloudflare@latest",
|
|
@@ -4128,6 +4175,11 @@ program.command("init <directory>").description("Create a Cloudflare React Route
|
|
|
4128
4175
|
"--",
|
|
4129
4176
|
"--yes"
|
|
4130
4177
|
], {
|
|
4178
|
+
env: {
|
|
4179
|
+
GIT_CONFIG_COUNT: "1",
|
|
4180
|
+
GIT_CONFIG_KEY_0: "init.defaultBranch",
|
|
4181
|
+
GIT_CONFIG_VALUE_0: "main"
|
|
4182
|
+
},
|
|
4131
4183
|
reject: false,
|
|
4132
4184
|
stdio: "inherit"
|
|
4133
4185
|
});
|
|
@@ -4135,7 +4187,18 @@ program.command("init <directory>").description("Create a Cloudflare React Route
|
|
|
4135
4187
|
if (result.exitCode !== 0) return;
|
|
4136
4188
|
const tsconfigPath = join(directory, "tsconfig.json");
|
|
4137
4189
|
const tsconfig = looseObject({ compilerOptions: record(string(), unknown()) }).parse(JSON.parse(await readFile(tsconfigPath, "utf8")));
|
|
4138
|
-
tsconfig.compilerOptions.paths = {
|
|
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`);
|
|
4139
4202
|
await writeFile(tsconfigPath, `${JSON.stringify(tsconfig, null, 2)}\n`);
|
|
4140
4203
|
const ui = await execa("pnpm", [
|
|
4141
4204
|
"dlx",
|
|
@@ -4172,7 +4235,8 @@ program.command("init <directory>").description("Create a Cloudflare React Route
|
|
|
4172
4235
|
const install = await execa("pnpm", [
|
|
4173
4236
|
"add",
|
|
4174
4237
|
`@usecontextlayer/sdk@${version}`,
|
|
4175
|
-
"zod@^4.4.3"
|
|
4238
|
+
"zod@^4.4.3",
|
|
4239
|
+
"drizzle-orm@0.45.3"
|
|
4176
4240
|
], {
|
|
4177
4241
|
cwd: directory,
|
|
4178
4242
|
reject: false,
|
|
@@ -4180,7 +4244,22 @@ program.command("init <directory>").description("Create a Cloudflare React Route
|
|
|
4180
4244
|
});
|
|
4181
4245
|
process.exitCode = install.exitCode;
|
|
4182
4246
|
if (install.exitCode !== 0) return;
|
|
4183
|
-
const
|
|
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 checkoutPackage = looseObject({ scripts: record(string(), string()) }).parse(JSON.parse(await readFile(packagePath, "utf8")));
|
|
4260
|
+
checkoutPackage.scripts["db:migration"] = "drizzle-kit generate --custom";
|
|
4261
|
+
await writeFile(packagePath, `${JSON.stringify(checkoutPackage, null, 2)}\n`);
|
|
4262
|
+
const response = await fetch(new URL("/api/slates", env.CTX_PLATFORM_URL), {
|
|
4184
4263
|
body: JSON.stringify(input),
|
|
4185
4264
|
headers: {
|
|
4186
4265
|
authorization: `Bearer ${accessToken}`,
|
|
@@ -4188,12 +4267,12 @@ program.command("init <directory>").description("Create a Cloudflare React Route
|
|
|
4188
4267
|
},
|
|
4189
4268
|
method: "POST"
|
|
4190
4269
|
});
|
|
4191
|
-
if (!response.ok) throw new Error(`
|
|
4192
|
-
const
|
|
4270
|
+
if (!response.ok) throw new Error(`Slate creation failed: HTTP ${response.status}`);
|
|
4271
|
+
const slate = object({
|
|
4193
4272
|
id: uuid(),
|
|
4194
4273
|
public_hostname: hostname()
|
|
4195
4274
|
}).parse(await response.json());
|
|
4196
|
-
const remote = new URL(`/git/${
|
|
4275
|
+
const remote = new URL(`/git/${slate.id}`, env.CTX_PLATFORM_URL);
|
|
4197
4276
|
const git = await execa("git", [
|
|
4198
4277
|
"remote",
|
|
4199
4278
|
"add",
|
|
@@ -4205,41 +4284,65 @@ program.command("init <directory>").description("Create a Cloudflare React Route
|
|
|
4205
4284
|
stdio: "inherit"
|
|
4206
4285
|
});
|
|
4207
4286
|
process.exitCode = git.exitCode;
|
|
4287
|
+
if (git.exitCode !== 0) return;
|
|
4288
|
+
console.log(`
|
|
4289
|
+
ContextLayer slate created: ${slate.id}
|
|
4290
|
+
Dashboard: ${new URL(`/dashboard/slates/${slate.id}`, env.CTX_PLATFORM_URL).href}
|
|
4291
|
+
Hosted URL: https://${slate.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
|
+
Deploy 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
|
+
`);
|
|
4208
4305
|
});
|
|
4209
4306
|
program.command("login").description("Sign in by approving a browser code and configure Git credentials for ContextLayer repositories").action(async () => {
|
|
4210
|
-
await login();
|
|
4211
|
-
await
|
|
4307
|
+
await login(env);
|
|
4308
|
+
await configureGit(new URL(import.meta.url));
|
|
4212
4309
|
console.log("Git credentials configured for ContextLayer.");
|
|
4213
4310
|
});
|
|
4214
|
-
program.command("git-credential <operation>").description("Git credential helper using the saved login; invoked automatically by Git").
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
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>", "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) => {
|
|
4318
|
+
await joinSlate(union([
|
|
4218
4319
|
literal("local"),
|
|
4219
4320
|
uuid(),
|
|
4220
4321
|
hostname()
|
|
4221
4322
|
]).parse(target));
|
|
4222
4323
|
});
|
|
4223
|
-
const connections = program.command("connections").description("Manage Owner-owned connected accounts or list app and agent
|
|
4324
|
+
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) => {
|
|
4325
|
+
if (actionCommand !== command || command.args[0] !== "local") await requireLogin(actionCommand);
|
|
4326
|
+
}).action(async (target, operation) => {
|
|
4224
4327
|
if (!target || !operation) connections.error("Expected: ctx connections <target> list");
|
|
4225
|
-
await
|
|
4328
|
+
await listSlateConnections(target);
|
|
4226
4329
|
});
|
|
4227
|
-
connections.command("list").description("List the selected Owner's
|
|
4330
|
+
connections.command("list").description("List the selected Owner's connected accounts with their toolkit, identity, and status").action(async (_options, command) => {
|
|
4228
4331
|
await listConnections(ownerOptionsSchema.parse(command.optsWithGlobals()).org);
|
|
4229
4332
|
});
|
|
4230
|
-
connections.command("add <toolkit>").description("
|
|
4333
|
+
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) => {
|
|
4231
4334
|
const options = ownerOptionsSchema.parse(command.optsWithGlobals());
|
|
4232
|
-
await
|
|
4335
|
+
await connectAccount(string().min(1).parse(toolkit), options.org);
|
|
4233
4336
|
});
|
|
4234
|
-
program.command("connection").description("
|
|
4337
|
+
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) => {
|
|
4235
4338
|
const input = connectionAssignmentSchema.parse({
|
|
4236
4339
|
connection_id: connectionId,
|
|
4237
4340
|
slug,
|
|
4238
4341
|
target
|
|
4239
4342
|
});
|
|
4240
|
-
await
|
|
4343
|
+
await assignSlateConnection(input.target, input.slug, input.connection_id);
|
|
4241
4344
|
});
|
|
4242
|
-
program.command("agent").addArgument(new Argument("<target>", "Run from the
|
|
4345
|
+
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) => {
|
|
4243
4346
|
const input = object({
|
|
4244
4347
|
name: string().min(1),
|
|
4245
4348
|
prompt: string().min(1)
|
|
@@ -4249,7 +4352,7 @@ program.command("agent").addArgument(new Argument("<target>", "Run from the app
|
|
|
4249
4352
|
});
|
|
4250
4353
|
console.log(await runLocalAgent(input.name, input.prompt));
|
|
4251
4354
|
});
|
|
4252
|
-
const secrets = program.command("secrets").description("Save or drop Owner-owned shared secrets through the authenticated platform").option("--org <
|
|
4355
|
+
const secrets = program.command("secrets").description("Save or drop Owner-owned shared secrets through the authenticated platform").option("--org <slug>", "Use this organization's Owner; defaults to the signed-in user").hook("preAction", requireLogin);
|
|
4253
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) => {
|
|
4254
4357
|
await setSecret(secretNameSchema.parse(name), secretOptionsSchema.parse(command.optsWithGlobals()));
|
|
4255
4358
|
});
|