@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.
- package/README.md +36 -0
- package/dist/auth-CMrKVwsH.mjs +6847 -0
- package/dist/auth-CMrKVwsH.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 +500 -192
- package/dist/cli.mjs.map +1 -1
- package/dist/{app-CJgT3IKd.mjs → resources-5FTDu19i.mjs} +263 -5552
- package/dist/resources-5FTDu19i.mjs.map +1 -0
- package/dist/rolldown-runtime-EAsCxrDo.mjs +14 -0
- package/dist/template/AGENTS.md +13 -0
- package/dist/template/README.md +44 -0
- package/dist/template/app/root.tsx +28 -0
- package/dist/template/app/routes/home.tsx +16 -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 +2 -1
- package/dist/template/workers/local-resources/index.ts +1 -0
- package/dist/template/workers/local-resources/wrangler.jsonc +19 -0
- package/dist/vite.d.mts +4439 -11
- package/dist/vite.d.mts.map +1 -1
- package/dist/vite.mjs +95 -9
- package/dist/vite.mjs.map +1 -1
- package/package.json +20 -3
- package/dist/app-CJgT3IKd.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/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-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 {
|
|
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-
|
|
3857
|
-
const
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
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.
|
|
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 = {
|
|
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").
|
|
4022
|
-
|
|
4023
|
-
|
|
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("
|
|
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("
|
|
4035
|
-
|
|
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
|
});
|