@usecontextlayer/cli 0.14.2 → 0.14.3
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/dist/cli.mjs +382 -177
- package/dist/cli.mjs.map +1 -1
- package/dist/{app-CJgT3IKd.mjs → resources-DEdwfmku.mjs} +198 -20
- package/dist/resources-DEdwfmku.mjs.map +1 -0
- package/dist/vite.d.mts +13 -9
- package/dist/vite.d.mts.map +1 -1
- package/dist/vite.mjs +5 -3
- package/dist/vite.mjs.map +1 -1
- package/package.json +3 -2
- package/dist/app-CJgT3IKd.mjs.map +0 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { C as unknown, E as execa, S as union, T as uuid, _ as literal, b as record, c as getCurrentUser, d as env, g as hostname, h as boolean, i as ownerSchema, l as login, m as array, n as saveLocalConnection, o as joinApp, p as secretNameSchema, r as appResourceConfigurationSchema, s as getAccessToken, t as prepareLocalResourceConfiguration, u as whoami, v as looseObject, w as url, x as string, y as object } from "./resources-DEdwfmku.mjs";
|
|
3
3
|
import { cp, readFile, writeFile } from "node:fs/promises";
|
|
4
4
|
import path, { join } from "node:path";
|
|
5
5
|
import { EventEmitter } from "node:events";
|
|
@@ -3004,166 +3004,6 @@ async function runLocalAgent(name, prompt) {
|
|
|
3004
3004
|
}
|
|
3005
3005
|
}
|
|
3006
3006
|
//#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
3007
|
//#region ../../node_modules/.pnpm/fast-string-truncated-width@3.0.3/node_modules/fast-string-truncated-width/dist/utils.js
|
|
3168
3008
|
const getCodePointsLength = (() => {
|
|
3169
3009
|
const SURROGATE_PAIR_RE = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g;
|
|
@@ -3499,6 +3339,11 @@ var import_src = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
3499
3339
|
beep
|
|
3500
3340
|
};
|
|
3501
3341
|
})))();
|
|
3342
|
+
function findCursor(s, o, l) {
|
|
3343
|
+
if (!l.some((r) => !r.disabled)) return s;
|
|
3344
|
+
const t = s + o, n = Math.max(l.length - 1, 0), e = t < 0 ? n : t > n ? 0 : t;
|
|
3345
|
+
return l[e]?.disabled ? findCursor(e, o < 0 ? -1 : 1, l) : e;
|
|
3346
|
+
}
|
|
3502
3347
|
const settings = {
|
|
3503
3348
|
actions: /* @__PURE__ */ new Set([
|
|
3504
3349
|
"up",
|
|
@@ -3580,7 +3425,19 @@ function setRawMode(e, r) {
|
|
|
3580
3425
|
const o = e;
|
|
3581
3426
|
o.isTTY && o.setRawMode(r);
|
|
3582
3427
|
}
|
|
3428
|
+
const getColumns = (e) => "columns" in e && typeof e.columns == "number" ? e.columns : 80;
|
|
3583
3429
|
const getRows = (e) => "rows" in e && typeof e.rows == "number" ? e.rows : 20;
|
|
3430
|
+
function wrapTextWithPrefix(e, r, o, n = o, s = o, t) {
|
|
3431
|
+
return wrapAnsi(r, getColumns(e ?? stdout) - o.length, {
|
|
3432
|
+
hard: true,
|
|
3433
|
+
trim: false
|
|
3434
|
+
}).split(`
|
|
3435
|
+
`).map((c, i, m) => {
|
|
3436
|
+
const d = t ? t(c, i) : c;
|
|
3437
|
+
return i === 0 ? `${n}${d}` : i === m.length - 1 ? `${s}${d}` : `${o}${d}`;
|
|
3438
|
+
}).join(`
|
|
3439
|
+
`);
|
|
3440
|
+
}
|
|
3584
3441
|
function runValidation(e, a) {
|
|
3585
3442
|
if ("~standard" in e) {
|
|
3586
3443
|
const n = e["~standard"].validate(a);
|
|
@@ -3797,6 +3654,32 @@ let u$1 = class u extends y {
|
|
|
3797
3654
|
});
|
|
3798
3655
|
}
|
|
3799
3656
|
};
|
|
3657
|
+
let n$1 = class n extends y {
|
|
3658
|
+
options;
|
|
3659
|
+
cursor = 0;
|
|
3660
|
+
get _selectedValue() {
|
|
3661
|
+
return this.options[this.cursor];
|
|
3662
|
+
}
|
|
3663
|
+
changeValue() {
|
|
3664
|
+
const e = this._selectedValue;
|
|
3665
|
+
this.value = e === void 0 ? void 0 : e.value;
|
|
3666
|
+
}
|
|
3667
|
+
constructor(e) {
|
|
3668
|
+
super(e, false), this.options = e.options;
|
|
3669
|
+
const o = this.options.findIndex(({ value: s }) => s === e.initialValue), t = o === -1 ? 0 : o;
|
|
3670
|
+
this.cursor = this.options[t]?.disabled ? findCursor(t, 1, this.options) : t, this.changeValue(), this.on("cursor", (s) => {
|
|
3671
|
+
switch (s) {
|
|
3672
|
+
case "left":
|
|
3673
|
+
case "up":
|
|
3674
|
+
this.cursor = findCursor(this.cursor, -1, this.options);
|
|
3675
|
+
break;
|
|
3676
|
+
case "down":
|
|
3677
|
+
case "right": this.cursor = findCursor(this.cursor, 1, this.options);
|
|
3678
|
+
}
|
|
3679
|
+
this.changeValue();
|
|
3680
|
+
});
|
|
3681
|
+
}
|
|
3682
|
+
};
|
|
3800
3683
|
//#endregion
|
|
3801
3684
|
//#region ../../node_modules/.pnpm/@clack+prompts@1.8.1/node_modules/@clack/prompts/dist/index.mjs
|
|
3802
3685
|
function isUnicodeSupported() {
|
|
@@ -3811,6 +3694,8 @@ const S_STEP_ERROR = unicodeOr("▲", "x");
|
|
|
3811
3694
|
const S_STEP_SUBMIT = unicodeOr("◇", "o");
|
|
3812
3695
|
const S_BAR = unicodeOr("│", "|");
|
|
3813
3696
|
const S_BAR_END = unicodeOr("└", "—");
|
|
3697
|
+
const S_RADIO_ACTIVE = unicodeOr("●", ">");
|
|
3698
|
+
const S_RADIO_INACTIVE = unicodeOr("○", " ");
|
|
3814
3699
|
const S_PASSWORD_MASK = unicodeOr("▪", "•");
|
|
3815
3700
|
const symbol = (o) => {
|
|
3816
3701
|
switch (o) {
|
|
@@ -3822,6 +3707,63 @@ const symbol = (o) => {
|
|
|
3822
3707
|
case "validating": return styleText("dim", S_STEP_ACTIVE);
|
|
3823
3708
|
}
|
|
3824
3709
|
};
|
|
3710
|
+
const symbolBar = (o) => {
|
|
3711
|
+
switch (o) {
|
|
3712
|
+
case "initial":
|
|
3713
|
+
case "active": return styleText("cyan", S_BAR);
|
|
3714
|
+
case "cancel": return styleText("red", S_BAR);
|
|
3715
|
+
case "error": return styleText("yellow", S_BAR);
|
|
3716
|
+
case "submit": return styleText("green", S_BAR);
|
|
3717
|
+
}
|
|
3718
|
+
};
|
|
3719
|
+
function formatInstructionFooter(o, e) {
|
|
3720
|
+
const r = [`${e ? `${styleText("cyan", S_BAR)} ` : ""}${o.join(" • ")}`];
|
|
3721
|
+
return e && r.push(styleText("cyan", S_BAR_END)), r;
|
|
3722
|
+
}
|
|
3723
|
+
const I = (l, e, w, p, b, C = false) => {
|
|
3724
|
+
let r = e, O = 0;
|
|
3725
|
+
if (C) for (let i = p - 1; i >= w; i--) {
|
|
3726
|
+
const m = l[i];
|
|
3727
|
+
if (m && (r -= m.length), O++, r <= b) break;
|
|
3728
|
+
}
|
|
3729
|
+
else for (let i = w; i < p; i++) {
|
|
3730
|
+
const m = l[i];
|
|
3731
|
+
if (m && (r -= m.length), O++, r <= b) break;
|
|
3732
|
+
}
|
|
3733
|
+
return {
|
|
3734
|
+
lineCount: r,
|
|
3735
|
+
removals: O
|
|
3736
|
+
};
|
|
3737
|
+
};
|
|
3738
|
+
const limitOptions = ({ cursor: l, options: e, style: w, output: p = process.stdout, maxItems: b = Number.POSITIVE_INFINITY, columnPadding: C = 0, rowPadding: r = 4 }) => {
|
|
3739
|
+
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);
|
|
3740
|
+
let f = 0;
|
|
3741
|
+
l >= a - 3 && (f = Math.max(Math.min(l - a + 3, e.length - a), 0));
|
|
3742
|
+
let d = a < e.length && f > 0, c = a < e.length && f + a < e.length;
|
|
3743
|
+
const W = Math.min(f + a, e.length), s = [];
|
|
3744
|
+
let g = 0;
|
|
3745
|
+
d && g++, c && g++;
|
|
3746
|
+
const T = f + (d ? 1 : 0), y = W - (c ? 1 : 0);
|
|
3747
|
+
for (let t = T; t < y; t++) {
|
|
3748
|
+
const n = e[t], h = wrapAnsi(n ? w(n, t === l) : "", i, {
|
|
3749
|
+
hard: true,
|
|
3750
|
+
trim: false
|
|
3751
|
+
}).split(`
|
|
3752
|
+
`);
|
|
3753
|
+
s.push(h), g += h.length;
|
|
3754
|
+
}
|
|
3755
|
+
if (g > v) {
|
|
3756
|
+
let t = 0, n = 0, o = g;
|
|
3757
|
+
const h = l - T;
|
|
3758
|
+
let u = v;
|
|
3759
|
+
const L = () => I(s, o, 0, h, u), E = () => I(s, o, h + 1, s.length, u, true);
|
|
3760
|
+
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));
|
|
3761
|
+
}
|
|
3762
|
+
const x = [];
|
|
3763
|
+
d && x.push(M);
|
|
3764
|
+
for (const t of s) for (const n of t) x.push(n);
|
|
3765
|
+
return c && x.push(M), x;
|
|
3766
|
+
};
|
|
3825
3767
|
`${styleText("dim", "↑/↓")}`, `${styleText("dim", "Space:")}`, `${styleText("dim", "Enter:")}`;
|
|
3826
3768
|
const password = (e) => new u$1({
|
|
3827
3769
|
validate: e.validate,
|
|
@@ -3850,21 +3792,272 @@ ${r ? styleText("cyan", S_BAR_END) : ""}
|
|
|
3850
3792
|
}
|
|
3851
3793
|
}
|
|
3852
3794
|
}).prompt();
|
|
3853
|
-
`${styleText("dim", "↑/↓")}`, `${styleText("dim", "Enter:")}
|
|
3795
|
+
const SELECT_INSTRUCTIONS = [`${styleText("dim", "↑/↓")} to navigate`, `${styleText("dim", "Enter:")} confirm`];
|
|
3796
|
+
const c = (t, o) => t.includes(`
|
|
3797
|
+
`) ? t.split(`
|
|
3798
|
+
`).map((d) => o(d)).join(`
|
|
3799
|
+
`) : o(t);
|
|
3800
|
+
const select = (t) => {
|
|
3801
|
+
const o = (n, m) => {
|
|
3802
|
+
if (n === void 0) return "";
|
|
3803
|
+
const s = n.label ?? String(n.value);
|
|
3804
|
+
switch (m) {
|
|
3805
|
+
case "disabled": return `${styleText("gray", S_RADIO_INACTIVE)} ${c(s, (i) => styleText("gray", i))}${n.hint ? ` ${styleText("dim", `(${n.hint ?? "disabled"})`)}` : ""}`;
|
|
3806
|
+
case "selected": return `${c(s, (i) => styleText("dim", i))}`;
|
|
3807
|
+
case "active": return `${styleText("green", S_RADIO_ACTIVE)} ${s}${n.hint ? ` ${styleText("dim", `(${n.hint})`)}` : ""}`;
|
|
3808
|
+
case "cancelled": return `${c(s, (i) => styleText(["strikethrough", "dim"], i))}`;
|
|
3809
|
+
default: return `${styleText("dim", S_RADIO_INACTIVE)} ${c(s, (i) => styleText("dim", i))}`;
|
|
3810
|
+
}
|
|
3811
|
+
}, d = t.showInstructions ?? true;
|
|
3812
|
+
return new n$1({
|
|
3813
|
+
options: t.options,
|
|
3814
|
+
signal: t.signal,
|
|
3815
|
+
input: t.input,
|
|
3816
|
+
output: t.output,
|
|
3817
|
+
initialValue: t.initialValue,
|
|
3818
|
+
render() {
|
|
3819
|
+
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)}
|
|
3820
|
+
` : ""}${i}
|
|
3821
|
+
`;
|
|
3822
|
+
switch (this.state) {
|
|
3823
|
+
case "submit": {
|
|
3824
|
+
const r = n ? `${styleText("gray", S_BAR)} ` : "";
|
|
3825
|
+
return `${u}${wrapTextWithPrefix(t.output, o(this.options[this.cursor], "selected"), r)}`;
|
|
3826
|
+
}
|
|
3827
|
+
case "cancel": {
|
|
3828
|
+
const r = n ? `${styleText("gray", S_BAR)} ` : "";
|
|
3829
|
+
return `${u}${wrapTextWithPrefix(t.output, o(this.options[this.cursor], "cancelled"), r)}${n ? `
|
|
3830
|
+
${styleText("gray", S_BAR)}` : ""}`;
|
|
3831
|
+
}
|
|
3832
|
+
default: {
|
|
3833
|
+
const r = n ? `${styleText("cyan", S_BAR)} ` : "", a = u.split(`
|
|
3834
|
+
`).length, p = d ? formatInstructionFooter(SELECT_INSTRUCTIONS, n) : n ? [styleText("cyan", S_BAR_END)] : [], f = p.join(`
|
|
3835
|
+
`), b = p.length + 1;
|
|
3836
|
+
return `${u}${r}${limitOptions({
|
|
3837
|
+
output: t.output,
|
|
3838
|
+
cursor: this.cursor,
|
|
3839
|
+
options: this.options,
|
|
3840
|
+
maxItems: t.maxItems,
|
|
3841
|
+
columnPadding: r.length,
|
|
3842
|
+
rowPadding: a + b,
|
|
3843
|
+
style: (g, x) => o(g, g.disabled ? "disabled" : x ? "active" : "inactive")
|
|
3844
|
+
}).join(`
|
|
3845
|
+
${r}`)}
|
|
3846
|
+
${f}
|
|
3847
|
+
`;
|
|
3848
|
+
}
|
|
3849
|
+
}
|
|
3850
|
+
}
|
|
3851
|
+
}).prompt();
|
|
3852
|
+
};
|
|
3854
3853
|
`${styleText("gray", S_BAR)}`;
|
|
3855
3854
|
//#endregion
|
|
3856
|
-
//#region ../shared/dist/app-
|
|
3857
|
-
const
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
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
|
+
//#region ../shared/dist/connections.mjs
|
|
3860
|
+
const connectionSchema = object({
|
|
3861
|
+
alias: string().nullable(),
|
|
3862
|
+
display_name: string().nullable(),
|
|
3863
|
+
id: string(),
|
|
3864
|
+
status: string(),
|
|
3865
|
+
toolkit: string()
|
|
3866
|
+
});
|
|
3867
|
+
const connectionsSchema = object({ connections: array(connectionSchema) });
|
|
3868
|
+
//#endregion
|
|
3869
|
+
//#region owner.ts
|
|
3870
|
+
const ownerOptionsSchema = object({ org: string().min(1).optional() });
|
|
3871
|
+
async function resolveOwner(org, headers) {
|
|
3872
|
+
if (!org) return {
|
|
3873
|
+
id: (await getCurrentUser(headers)).id,
|
|
3874
|
+
type: "user"
|
|
3875
|
+
};
|
|
3876
|
+
const response = await fetch(new URL("/api/platform/organizations", env.CTX_AUTH_ISSUER), { headers });
|
|
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);
|
|
3883
|
+
if (!organization) throw new Error("Organization is not in your memberships");
|
|
3884
|
+
return {
|
|
3885
|
+
id: organization.id,
|
|
3886
|
+
type: "organization"
|
|
3887
|
+
};
|
|
3888
|
+
}
|
|
3889
|
+
function ownerPath(owner) {
|
|
3890
|
+
return `${owner.type === "organization" ? "org" : "user"}/${encodeURIComponent(owner.id)}`;
|
|
3891
|
+
}
|
|
3892
|
+
//#endregion
|
|
3893
|
+
//#region connections.ts
|
|
3894
|
+
const connectionAssignmentSchema = object({
|
|
3895
|
+
connection_id: string().min(1).optional(),
|
|
3896
|
+
slug: string().min(1),
|
|
3897
|
+
target: string().min(1)
|
|
3898
|
+
}).superRefine((input, ctx) => {
|
|
3899
|
+
if (input.target !== "local" && input.connection_id === void 0) ctx.addIssue({
|
|
3900
|
+
code: "custom",
|
|
3901
|
+
message: "Hosted assignment requires a connection ID",
|
|
3902
|
+
path: ["connection_id"]
|
|
3903
|
+
});
|
|
3867
3904
|
});
|
|
3905
|
+
async function assignAppConnection(target, slug, connectionId) {
|
|
3906
|
+
if (target === "local") {
|
|
3907
|
+
const configuration = await prepareLocalResourceConfiguration();
|
|
3908
|
+
const requirement = new Map(Object.entries(configuration.connections)).get(slug);
|
|
3909
|
+
if (!requirement) throw new Error(`No local connection requirement for "${slug}"`);
|
|
3910
|
+
if (connectionId === void 0 && !process.stdin.isTTY) throw new Error("Supply a connection ID when assigning without a terminal");
|
|
3911
|
+
const url = new URL("/api/connections", env.CTX_PLATFORM_URL);
|
|
3912
|
+
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 connection list failed: HTTP ${response.status}`);
|
|
3915
|
+
const { connections } = connectionsSchema.parse(await response.json());
|
|
3916
|
+
if (connections.length === 0) throw new Error(`No personal ${requirement.toolkit} accounts. Run ctx connections add ${requirement.toolkit}.`);
|
|
3917
|
+
if (connectionId === void 0) {
|
|
3918
|
+
const selected = await select({
|
|
3919
|
+
message: `Your ${requirement.toolkit} account for ${slug}`,
|
|
3920
|
+
options: connections.map((connection) => ({
|
|
3921
|
+
label: `${connection.display_name ?? connection.alias ?? connection.id} (${connection.id}; ${connection.status})`,
|
|
3922
|
+
value: connection.id
|
|
3923
|
+
})),
|
|
3924
|
+
output: process.stderr
|
|
3925
|
+
});
|
|
3926
|
+
if (isCancel(selected)) {
|
|
3927
|
+
process.exitCode = 130;
|
|
3928
|
+
return;
|
|
3929
|
+
}
|
|
3930
|
+
connectionId = selected;
|
|
3931
|
+
}
|
|
3932
|
+
if (!connections.some(({ id }) => id === connectionId)) throw new Error(`Account ${connectionId} is not one of your ${requirement.toolkit} accounts`);
|
|
3933
|
+
await saveLocalConnection(slug, connectionId);
|
|
3934
|
+
console.log(`${slug} → ${connectionId}`);
|
|
3935
|
+
return;
|
|
3936
|
+
}
|
|
3937
|
+
const response = await fetch(new URL(`/api/apps/${encodeURIComponent(target)}/connections/assign`, env.CTX_PLATFORM_URL), {
|
|
3938
|
+
body: JSON.stringify({
|
|
3939
|
+
connection_id: connectionId,
|
|
3940
|
+
slug
|
|
3941
|
+
}),
|
|
3942
|
+
headers: {
|
|
3943
|
+
authorization: `Bearer ${await getAccessToken()}`,
|
|
3944
|
+
"content-type": "application/json"
|
|
3945
|
+
},
|
|
3946
|
+
method: "POST"
|
|
3947
|
+
});
|
|
3948
|
+
if (!response.ok) throw new Error(`Connection assignment failed: HTTP ${response.status}`);
|
|
3949
|
+
const assignment = object({
|
|
3950
|
+
connection_id: string(),
|
|
3951
|
+
slug: string()
|
|
3952
|
+
}).parse(await response.json());
|
|
3953
|
+
console.log(`${assignment.slug} → ${assignment.connection_id}`);
|
|
3954
|
+
}
|
|
3955
|
+
async function listAppConnections(target) {
|
|
3956
|
+
if (target === "local") {
|
|
3957
|
+
const { connections } = await prepareLocalResourceConfiguration();
|
|
3958
|
+
if (Object.keys(connections).length === 0) {
|
|
3959
|
+
console.log("No connection requirements.");
|
|
3960
|
+
return;
|
|
3961
|
+
}
|
|
3962
|
+
console.table(Object.entries(connections).map(([slug, { connection_id, consumers, mode, toolkit }]) => ({
|
|
3963
|
+
connection: connection_id ?? "unassigned",
|
|
3964
|
+
consumers: consumers.map((consumer) => consumer.type === "app" ? "app" : `agent:${consumer.name}`).join(", "),
|
|
3965
|
+
mode,
|
|
3966
|
+
slug,
|
|
3967
|
+
toolkit
|
|
3968
|
+
})));
|
|
3969
|
+
return;
|
|
3970
|
+
}
|
|
3971
|
+
const response = await fetch(new URL(`/api/apps/${encodeURIComponent(target)}/connections/list`, env.CTX_PLATFORM_URL), {
|
|
3972
|
+
body: JSON.stringify({}),
|
|
3973
|
+
headers: {
|
|
3974
|
+
authorization: `Bearer ${await getAccessToken()}`,
|
|
3975
|
+
"content-type": "application/json"
|
|
3976
|
+
},
|
|
3977
|
+
method: "POST"
|
|
3978
|
+
});
|
|
3979
|
+
if (!response.ok) throw new Error(`App connections request failed: HTTP ${response.status}`);
|
|
3980
|
+
const { connections } = appConnectionsSchema.parse(await response.json());
|
|
3981
|
+
if (!connections.length) {
|
|
3982
|
+
console.log("No connection requirements.");
|
|
3983
|
+
return;
|
|
3984
|
+
}
|
|
3985
|
+
console.table(connections.map(({ connection_id, ...requirement }) => ({
|
|
3986
|
+
...requirement,
|
|
3987
|
+
connection: connection_id ?? "unassigned"
|
|
3988
|
+
})));
|
|
3989
|
+
}
|
|
3990
|
+
async function addConnection(toolkit, org) {
|
|
3991
|
+
const headers = { authorization: `Bearer ${await getAccessToken()}` };
|
|
3992
|
+
const owner = await resolveOwner(org, headers);
|
|
3993
|
+
const response = await fetch(new URL(`/api/connections/${ownerPath(owner)}`, env.CTX_PLATFORM_URL), {
|
|
3994
|
+
body: JSON.stringify({ toolkit }),
|
|
3995
|
+
headers: {
|
|
3996
|
+
...headers,
|
|
3997
|
+
"content-type": "application/json"
|
|
3998
|
+
},
|
|
3999
|
+
method: "POST"
|
|
4000
|
+
});
|
|
4001
|
+
if (!response.ok) throw new Error(`Connection authorization failed: HTTP ${response.status}`);
|
|
4002
|
+
const { redirect_url } = object({ redirect_url: url() }).parse(await response.json());
|
|
4003
|
+
console.log(`Open this link to authorize the Owner's account:\n${redirect_url}`);
|
|
4004
|
+
console.log(`After authorization, run ctx connections${org ? ` --org ${org}` : ""} list to see the Owner's account.`);
|
|
4005
|
+
}
|
|
4006
|
+
async function listConnections(org) {
|
|
4007
|
+
const headers = { authorization: `Bearer ${await getAccessToken()}` };
|
|
4008
|
+
const owner = await resolveOwner(org, headers);
|
|
4009
|
+
const response = await fetch(new URL(`/api/connections/${ownerPath(owner)}`, env.CTX_PLATFORM_URL), { headers });
|
|
4010
|
+
if (!response.ok) throw new Error(`Connection list failed: HTTP ${response.status}`);
|
|
4011
|
+
const { connections } = connectionsSchema.parse(await response.json());
|
|
4012
|
+
if (connections.length === 0) {
|
|
4013
|
+
console.log("No connected accounts.");
|
|
4014
|
+
return;
|
|
4015
|
+
}
|
|
4016
|
+
console.table(connections);
|
|
4017
|
+
}
|
|
4018
|
+
//#endregion
|
|
4019
|
+
//#region git-credential.ts
|
|
4020
|
+
async function setupGit(cli) {
|
|
4021
|
+
const key = `credential.${new URL("/git", env.CTX_PLATFORM_URL).href}.helper`;
|
|
4022
|
+
const command = [
|
|
4023
|
+
process.execPath,
|
|
4024
|
+
fileURLToPath(cli),
|
|
4025
|
+
"git-credential"
|
|
4026
|
+
].map((argument) => `'${argument.replaceAll("'", "'\\''")}'`).join(" ");
|
|
4027
|
+
await execa("git", [
|
|
4028
|
+
"config",
|
|
4029
|
+
"--global",
|
|
4030
|
+
"--replace-all",
|
|
4031
|
+
key,
|
|
4032
|
+
""
|
|
4033
|
+
]);
|
|
4034
|
+
await execa("git", [
|
|
4035
|
+
"config",
|
|
4036
|
+
"--global",
|
|
4037
|
+
"--add",
|
|
4038
|
+
key,
|
|
4039
|
+
`!${command}`
|
|
4040
|
+
]);
|
|
4041
|
+
}
|
|
4042
|
+
async function gitCredential(operation) {
|
|
4043
|
+
if (operation === "capability") {
|
|
4044
|
+
process.stdout.write("version 0\ncapability authtype\n");
|
|
4045
|
+
return;
|
|
4046
|
+
}
|
|
4047
|
+
if (operation !== "get") return;
|
|
4048
|
+
const attributes = new URLSearchParams();
|
|
4049
|
+
const lines = createInterface({ input: process.stdin });
|
|
4050
|
+
for await (const line of lines) {
|
|
4051
|
+
if (line === "") break;
|
|
4052
|
+
const separator = line.indexOf("=");
|
|
4053
|
+
attributes.append(line.slice(0, separator), line.slice(separator + 1));
|
|
4054
|
+
}
|
|
4055
|
+
literal("authtype").parse(attributes.getAll("capability[]").find((c) => c === "authtype"));
|
|
4056
|
+
process.stdout.write(`capability[]=authtype\nauthtype=Bearer\ncredential=${await getAccessToken()}\n\n`);
|
|
4057
|
+
}
|
|
4058
|
+
//#endregion
|
|
4059
|
+
//#region package.json
|
|
4060
|
+
var version = "0.14.3";
|
|
3868
4061
|
//#endregion
|
|
3869
4062
|
//#region secrets.ts
|
|
3870
4063
|
const secretOptionsSchema = ownerOptionsSchema.extend({ stdin: boolean().default(false) });
|
|
@@ -3921,7 +4114,7 @@ async function dropSecret(name, org) {
|
|
|
3921
4114
|
//#endregion
|
|
3922
4115
|
//#region cli.ts
|
|
3923
4116
|
const program = new Command().name("ctx").description("ContextLayer CLI").version(version);
|
|
3924
|
-
program.command("init <directory>").description("Create a Cloudflare React Router project with Tailwind and shadcn Base UI, register
|
|
4117
|
+
program.command("init <directory>").description("Create a Cloudflare React Router project with Tailwind and shadcn Base UI, register and join a private app owned by you or an organization (ctx login), and set its immutable Git remote").option("--org <slug>", "Create the app under an organization you belong to").action(async (directory, options) => {
|
|
3925
4118
|
const input = object({ organization_slug: string().min(1).optional() }).parse({ organization_slug: options.org });
|
|
3926
4119
|
const accessToken = await getAccessToken();
|
|
3927
4120
|
const result = await execa("pnpm", [
|
|
@@ -4020,7 +4213,14 @@ program.command("login").description("Sign in by approving a browser code and co
|
|
|
4020
4213
|
});
|
|
4021
4214
|
program.command("git-credential <operation>").description("Git credential helper using the saved login; invoked automatically by Git").action(gitCredential);
|
|
4022
4215
|
program.command("whoami").description("Show your ContextLayer user ID using the saved login").action(whoami);
|
|
4023
|
-
|
|
4216
|
+
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").action(async (target) => {
|
|
4217
|
+
await joinApp(union([
|
|
4218
|
+
literal("local"),
|
|
4219
|
+
uuid(),
|
|
4220
|
+
hostname()
|
|
4221
|
+
]).parse(target));
|
|
4222
|
+
});
|
|
4223
|
+
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"])).action(async (target, operation) => {
|
|
4024
4224
|
if (!target || !operation) connections.error("Expected: ctx connections <target> list");
|
|
4025
4225
|
await listAppConnections(target);
|
|
4026
4226
|
});
|
|
@@ -4031,8 +4231,13 @@ connections.command("add <toolkit>").description("Print a Composio authorization
|
|
|
4031
4231
|
const options = ownerOptionsSchema.parse(command.optsWithGlobals());
|
|
4032
4232
|
await addConnection(string().min(1).parse(toolkit), options.org);
|
|
4033
4233
|
});
|
|
4034
|
-
program.command("connection").description("
|
|
4035
|
-
|
|
4234
|
+
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 requirement slug").argument("[connection_id]", "Connected account ID; omit locally to choose your accounts for the declared toolkit; required for hosted targets").action(async (target, _operation, slug, connectionId) => {
|
|
4235
|
+
const input = connectionAssignmentSchema.parse({
|
|
4236
|
+
connection_id: connectionId,
|
|
4237
|
+
slug,
|
|
4238
|
+
target
|
|
4239
|
+
});
|
|
4240
|
+
await assignAppConnection(input.target, input.slug, input.connection_id);
|
|
4036
4241
|
});
|
|
4037
4242
|
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
4243
|
const input = object({
|