@cowliss/cli 0.11.0 → 0.12.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/dist/index.js +232 -166
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -173,6 +173,8 @@ const SYSTEM_EVENT_PREFIX = "system.";
|
|
|
173
173
|
* system.profile_deleted is the provider-recorded fact that a provider
|
|
174
174
|
* (e.g. Clerk `user.deleted`) deleted the user upstream — a marker event
|
|
175
175
|
* only, with no deletion semantics (hard-delete belongs to GDPR erasure);
|
|
176
|
+
* system.signed_up and system.logged_in are the same shape for the other end
|
|
177
|
+
* of that lifecycle (Clerk `user.created` and `session.created`);
|
|
176
178
|
* system.email_clicked is a link click reported by SES feedback.
|
|
177
179
|
*
|
|
178
180
|
* Rows carrying one of these names get `system: true` so the usage meter
|
|
@@ -187,6 +189,17 @@ const SYSTEM_EVENTS = {
|
|
|
187
189
|
consentRevoked: "system.consent_revoked",
|
|
188
190
|
profileDeleted: "system.profile_deleted",
|
|
189
191
|
/**
|
|
192
|
+
* The provider's account lifecycle, as facts rather than as profile state:
|
|
193
|
+
* a profile can predate its `user.created` (an SDK identify or a
|
|
194
|
+
* membership delivery may have created it first), so these name what the
|
|
195
|
+
* provider reported, not what Cowliss did. `signedUp` is the trigger for a
|
|
196
|
+
* welcome journey fed by a provider source, which `emailRegistered` only
|
|
197
|
+
* approximates (it fires on an address change too, and never for a
|
|
198
|
+
* profile with no address). `loggedIn` is one row per session start.
|
|
199
|
+
*/
|
|
200
|
+
signedUp: "system.signed_up",
|
|
201
|
+
loggedIn: "system.logged_in",
|
|
202
|
+
/**
|
|
190
203
|
* A write's identifiers named more than one profile and Cowliss merged
|
|
191
204
|
* them (ticket 69). Lands on the survivor's timeline carrying
|
|
192
205
|
* `mergedProfileIds`, the ids folded into it.
|
|
@@ -7873,6 +7886,36 @@ const updateWebhookBodySchema = z.object({ data: z.object({
|
|
|
7873
7886
|
/** `q` is a substring search over the webhook's name. */
|
|
7874
7887
|
const listWebhooksQuerySchema = paginationQuerySchema.extend({ q: searchQuerySchema });
|
|
7875
7888
|
|
|
7889
|
+
//#endregion
|
|
7890
|
+
//#region ../../packages/shared/src/whoami.ts
|
|
7891
|
+
/**
|
|
7892
|
+
* What an ingestion key plus one source id write into. An org API key is
|
|
7893
|
+
* org-scoped and carries no app of its own (`ApiKeyClaims`), so any live
|
|
7894
|
+
* `src_` in the organization is accepted by the write path: a well-formed id
|
|
7895
|
+
* belonging to another app lands profiles there with a 200. The id the
|
|
7896
|
+
* caller is about to send is therefore the input, and this answers where it
|
|
7897
|
+
* goes before anything is written.
|
|
7898
|
+
*
|
|
7899
|
+
* Scoped to that one source's app on purpose: the key stays a write
|
|
7900
|
+
* credential, and a leaked one must not enumerate the org's other apps
|
|
7901
|
+
* (.scratch/24-onboarding-dx/spec.md).
|
|
7902
|
+
*/
|
|
7903
|
+
const whoamiQuerySchema = z.object({ sourceId: sourceIdSchema });
|
|
7904
|
+
const whoamiSchema = z.object({
|
|
7905
|
+
orgId: z.string(),
|
|
7906
|
+
app: z.object({
|
|
7907
|
+
id: z.string(),
|
|
7908
|
+
name: z.string()
|
|
7909
|
+
}),
|
|
7910
|
+
/** Every live pipe into that app, so "a Clerk source already feeds this" is visible. */
|
|
7911
|
+
sources: z.array(sourceSchema.pick({
|
|
7912
|
+
id: true,
|
|
7913
|
+
kind: true,
|
|
7914
|
+
configured: true,
|
|
7915
|
+
lastReceivedAt: true
|
|
7916
|
+
}))
|
|
7917
|
+
});
|
|
7918
|
+
|
|
7876
7919
|
//#endregion
|
|
7877
7920
|
//#region ../../packages/api-client/src/client.ts
|
|
7878
7921
|
var ApiError = class extends Error {
|
|
@@ -7897,7 +7940,7 @@ async function send(baseUrl, init) {
|
|
|
7897
7940
|
body: typeof init.body === "string" || init.body === null ? init.body : Uint8Array.from(init.body)
|
|
7898
7941
|
});
|
|
7899
7942
|
} catch {
|
|
7900
|
-
throw new ApiError("network_error", `Could not reach the API at ${baseUrl}.
|
|
7943
|
+
throw new ApiError("network_error", `Could not reach the API at ${baseUrl}. Check the address, or set COW_API_URL to the one you meant.`, 0);
|
|
7901
7944
|
}
|
|
7902
7945
|
const requestId = response.headers.get("X-Request-Id") ?? void 0;
|
|
7903
7946
|
if (init.binary && response.ok) return {
|
|
@@ -9211,170 +9254,6 @@ function registerAdd(program, io) {
|
|
|
9211
9254
|
});
|
|
9212
9255
|
}
|
|
9213
9256
|
|
|
9214
|
-
//#endregion
|
|
9215
|
-
//#region src/lib/login.ts
|
|
9216
|
-
/**
|
|
9217
|
-
* Login via the dashboard's /cli-auth bridge: start a loopback server, open
|
|
9218
|
-
* the web app there, and the signed-in browser POSTs the Clerk session token
|
|
9219
|
-
* back. `--token <jwt>` bypasses the browser entirely (agents, CI).
|
|
9220
|
-
*/
|
|
9221
|
-
async function login(env, options) {
|
|
9222
|
-
let token;
|
|
9223
|
-
if (options.token !== void 0) {
|
|
9224
|
-
token = options.token.trim();
|
|
9225
|
-
if (decodeTokenPayload(token) === null) throw new Error("The provided --token value is not a JWT");
|
|
9226
|
-
} else token = await collectTokenViaBrowser(resolveWebUrl(env, (await readCowConfig(process.cwd()))?.webUrl, options.webUrl), 3e5, options.noOpen === true);
|
|
9227
|
-
const claims = decodeSessionToken(token);
|
|
9228
|
-
if (claims === null) throw new Error("Token is not a decodable JWT");
|
|
9229
|
-
if (claims.userId === null) throw new Error("Token has no subject claim. Is it a Clerk session token?");
|
|
9230
|
-
const apiUrl = options.apiFlag ?? resolveApiUrl(env, null, void 0, (await readCowConfig(process.cwd()))?.apiUrl);
|
|
9231
|
-
return {
|
|
9232
|
-
credentialsPath: await writeCredentials(env, {
|
|
9233
|
-
token,
|
|
9234
|
-
apiUrl
|
|
9235
|
-
}),
|
|
9236
|
-
apiUrl,
|
|
9237
|
-
userId: claims.userId,
|
|
9238
|
-
orgId: claims.orgId,
|
|
9239
|
-
role: claims.role,
|
|
9240
|
-
hasOrg: claims.orgId !== null
|
|
9241
|
-
};
|
|
9242
|
-
}
|
|
9243
|
-
/** Open the dashboard bridge and await one POST of { token }. */
|
|
9244
|
-
function collectTokenViaBrowser(webUrl, timeoutMs = 18e4, noOpen = false) {
|
|
9245
|
-
return new Promise((resolve, reject) => {
|
|
9246
|
-
let server = null;
|
|
9247
|
-
const timer = setTimeout(() => {
|
|
9248
|
-
server?.close();
|
|
9249
|
-
reject(/* @__PURE__ */ new Error(`Timed out waiting for the browser to sign in (${Math.round(timeoutMs / 1e3)}s). Or paste a token: \`cow login --token <jwt>\`.`));
|
|
9250
|
-
}, timeoutMs);
|
|
9251
|
-
server = createServer((req, res) => {
|
|
9252
|
-
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
9253
|
-
res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
|
|
9254
|
-
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
9255
|
-
if (req.method === "OPTIONS") {
|
|
9256
|
-
res.writeHead(204).end();
|
|
9257
|
-
return;
|
|
9258
|
-
}
|
|
9259
|
-
if (req.method !== "POST") {
|
|
9260
|
-
res.writeHead(405).end();
|
|
9261
|
-
return;
|
|
9262
|
-
}
|
|
9263
|
-
const chunks = [];
|
|
9264
|
-
req.on("data", (chunk) => chunks.push(chunk));
|
|
9265
|
-
req.on("end", () => {
|
|
9266
|
-
clearTimeout(timer);
|
|
9267
|
-
try {
|
|
9268
|
-
const body = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
9269
|
-
const field = (name) => typeof body === "object" && body !== null && name in body ? body[name] : void 0;
|
|
9270
|
-
if (field("denied") === true) {
|
|
9271
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
9272
|
-
res.end(JSON.stringify({ ok: true }));
|
|
9273
|
-
reject(/* @__PURE__ */ new Error("Sign-in was denied in the browser. No access was granted."));
|
|
9274
|
-
return;
|
|
9275
|
-
}
|
|
9276
|
-
const token = field("token");
|
|
9277
|
-
if (typeof token !== "string" || token.length === 0) {
|
|
9278
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
9279
|
-
res.end(JSON.stringify({ error: "missing token" }));
|
|
9280
|
-
reject(/* @__PURE__ */ new Error("The browser bridge sent no token"));
|
|
9281
|
-
return;
|
|
9282
|
-
}
|
|
9283
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
9284
|
-
res.end(JSON.stringify({ ok: true }));
|
|
9285
|
-
resolve(token);
|
|
9286
|
-
} catch (error) {
|
|
9287
|
-
reject(error instanceof Error ? error : new Error(String(error)));
|
|
9288
|
-
} finally {
|
|
9289
|
-
server?.close();
|
|
9290
|
-
}
|
|
9291
|
-
});
|
|
9292
|
-
});
|
|
9293
|
-
server.listen(0, "127.0.0.1", () => {
|
|
9294
|
-
const address = server?.address();
|
|
9295
|
-
if (address === null || typeof address !== "object") {
|
|
9296
|
-
reject(/* @__PURE__ */ new Error("Could not bind a loopback port"));
|
|
9297
|
-
return;
|
|
9298
|
-
}
|
|
9299
|
-
const callbackUrl = `http://127.0.0.1:${address.port}/cb`;
|
|
9300
|
-
const target = `${webUrl}/cli-auth?cb=${encodeURIComponent(callbackUrl)}`;
|
|
9301
|
-
if (!noOpen) openBrowser(target);
|
|
9302
|
-
process.stderr.write(`Waiting for sign-in at:\n ${target}\n`);
|
|
9303
|
-
});
|
|
9304
|
-
server.on("error", (error) => {
|
|
9305
|
-
clearTimeout(timer);
|
|
9306
|
-
reject(error);
|
|
9307
|
-
});
|
|
9308
|
-
});
|
|
9309
|
-
}
|
|
9310
|
-
function openBrowser(url) {
|
|
9311
|
-
const cmd = process.platform === "darwin" ? "open" : process.platform === "linux" ? "xdg-open" : null;
|
|
9312
|
-
if (cmd === null) return;
|
|
9313
|
-
spawn(cmd, [url], {
|
|
9314
|
-
stdio: "ignore",
|
|
9315
|
-
detached: true
|
|
9316
|
-
}).unref();
|
|
9317
|
-
}
|
|
9318
|
-
|
|
9319
|
-
//#endregion
|
|
9320
|
-
//#region src/commands/auth.ts
|
|
9321
|
-
/** login/logout/whoami: session-token management, no contract route. */
|
|
9322
|
-
function registerAuth(program, env, io) {
|
|
9323
|
-
program.command("login").description("authenticate via the dashboard browser flow (or --token) and cache the session token").option("--token <jwt>", "paste a session token instead of the browser flow").option("--web <url>", `dashboard URL; overrides COW_WEB_URL and webUrl in cow.json (default ${DEFAULT_WEB_URL})`).option("--no-open", "do not open the browser; print the URL and wait (headless/agent use)").action(async (opts) => {
|
|
9324
|
-
const outcome = await login(env, {
|
|
9325
|
-
token: opts.token,
|
|
9326
|
-
webUrl: opts.web,
|
|
9327
|
-
apiFlag: opts.api,
|
|
9328
|
-
noOpen: opts.open === false
|
|
9329
|
-
});
|
|
9330
|
-
emit({ data: {
|
|
9331
|
-
...outcome,
|
|
9332
|
-
warnings: outcome.hasOrg ? void 0 : ["The token carries no active organization. Select one in the dashboard and log in again."]
|
|
9333
|
-
} }, io, opts.json);
|
|
9334
|
-
});
|
|
9335
|
-
program.command("logout").description("delete the cached session token").action(async (opts) => {
|
|
9336
|
-
const removed = await clearCredentials(env);
|
|
9337
|
-
emit({ data: { removed } }, io, opts.json);
|
|
9338
|
-
});
|
|
9339
|
-
program.command("whoami").description("show which credential commands will use, and for a session its subject, org, role, and expiry").action(async (opts) => {
|
|
9340
|
-
const credentials = await readCredentials(env);
|
|
9341
|
-
const { kind } = resolveCredential(env, credentials);
|
|
9342
|
-
if (kind === "none") throw new Error("Not logged in. Run `cow login` first, or set COW_PIPELINE_KEY.");
|
|
9343
|
-
if (kind === "pipelineKey") {
|
|
9344
|
-
emit({ data: {
|
|
9345
|
-
credential: kind,
|
|
9346
|
-
source: "COW_PIPELINE_KEY",
|
|
9347
|
-
note: "A pipeline key may push, enable, disable, and read executions."
|
|
9348
|
-
} }, io, opts.json);
|
|
9349
|
-
return;
|
|
9350
|
-
}
|
|
9351
|
-
const claims = decodeSessionToken(credentials?.token ?? "");
|
|
9352
|
-
if (!claims) throw new Error("Cached token is not decodable. Run `cow login` again.");
|
|
9353
|
-
const projectApiUrl = (await readCowConfig(process.cwd()))?.apiUrl;
|
|
9354
|
-
const apiUrl = resolveApiUrl(env, credentials, opts.api, projectApiUrl);
|
|
9355
|
-
const mintedFor = credentials?.apiUrl;
|
|
9356
|
-
emit({ data: {
|
|
9357
|
-
credential: kind,
|
|
9358
|
-
apiUrl,
|
|
9359
|
-
mintedFor: mintedFor ?? null,
|
|
9360
|
-
...mintedFor !== void 0 && mintedFor !== apiUrl ? { warning: `This token was minted for ${mintedFor}, but commands here talk to ${apiUrl}. Run \`cow login\` again from this directory.` } : {},
|
|
9361
|
-
...claims
|
|
9362
|
-
} }, io, opts.json);
|
|
9363
|
-
});
|
|
9364
|
-
}
|
|
9365
|
-
|
|
9366
|
-
//#endregion
|
|
9367
|
-
//#region src/commands/build.ts
|
|
9368
|
-
/**
|
|
9369
|
-
* `cow build`: typecheck, bundle, and write `.cow/build` for the project in
|
|
9370
|
-
* the current directory. Local only, no API call and no network.
|
|
9371
|
-
*/
|
|
9372
|
-
function registerBuild(program, io) {
|
|
9373
|
-
program.command("build").description("typecheck and bundle the repo in the current directory, writing .cow/build (local: no API call)").action(async (opts) => {
|
|
9374
|
-
emit({ data: await buildProject(process.cwd()) }, io, opts.json);
|
|
9375
|
-
});
|
|
9376
|
-
}
|
|
9377
|
-
|
|
9378
9257
|
//#endregion
|
|
9379
9258
|
//#region ../../node_modules/.pnpm/@asteasolutions+zod-to-openapi@9.1.0_zod@4.4.3/node_modules/@asteasolutions/zod-to-openapi/dist/index.cjs
|
|
9380
9259
|
var require_dist = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
@@ -10659,6 +10538,19 @@ const ingestion = defineModule(defineRoute({
|
|
|
10659
10538
|
200: envelope(batchResultDtoSchema, "Per-item outcomes, positionally aligned"),
|
|
10660
10539
|
...ingestionErrors
|
|
10661
10540
|
}
|
|
10541
|
+
}), defineRoute({
|
|
10542
|
+
method: "get",
|
|
10543
|
+
path: "/v1/whoami",
|
|
10544
|
+
operationId: "ingestion.whoami",
|
|
10545
|
+
tags: ["ingestion"],
|
|
10546
|
+
summary: "Which app a source id writes into",
|
|
10547
|
+
security: API_KEY_AUTH,
|
|
10548
|
+
surfaces: HIDDEN_FROM_TOOLS,
|
|
10549
|
+
request: { query: whoamiQuerySchema },
|
|
10550
|
+
responses: {
|
|
10551
|
+
200: envelope(whoamiSchema, "The source's org, app, and sibling sources"),
|
|
10552
|
+
...errors("invalid_key", "not_found", "validation_failed", "rate_limited")
|
|
10553
|
+
}
|
|
10662
10554
|
}));
|
|
10663
10555
|
|
|
10664
10556
|
//#endregion
|
|
@@ -11887,6 +11779,180 @@ const allRoutes = Object.values(contract).flatMap((module) => Object.values(modu
|
|
|
11887
11779
|
/** The tags in the order their modules are declared. */
|
|
11888
11780
|
const tags = [...new Set(allRoutes.flatMap((route) => route.tags ?? []))];
|
|
11889
11781
|
|
|
11782
|
+
//#endregion
|
|
11783
|
+
//#region src/lib/login.ts
|
|
11784
|
+
/**
|
|
11785
|
+
* Login via the dashboard's /cli-auth bridge: start a loopback server, open
|
|
11786
|
+
* the web app there, and the signed-in browser POSTs the Clerk session token
|
|
11787
|
+
* back. `--token <jwt>` bypasses the browser entirely (agents, CI).
|
|
11788
|
+
*/
|
|
11789
|
+
async function login(env, options) {
|
|
11790
|
+
let token;
|
|
11791
|
+
if (options.token !== void 0) {
|
|
11792
|
+
token = options.token.trim();
|
|
11793
|
+
if (decodeTokenPayload(token) === null) throw new Error("The provided --token value is not a JWT");
|
|
11794
|
+
} else token = await collectTokenViaBrowser(resolveWebUrl(env, (await readCowConfig(process.cwd()))?.webUrl, options.webUrl), 3e5, options.noOpen === true);
|
|
11795
|
+
const claims = decodeSessionToken(token);
|
|
11796
|
+
if (claims === null) throw new Error("Token is not a decodable JWT");
|
|
11797
|
+
if (claims.userId === null) throw new Error("Token has no subject claim. Is it a Clerk session token?");
|
|
11798
|
+
const apiUrl = options.apiFlag ?? resolveApiUrl(env, null, void 0, (await readCowConfig(process.cwd()))?.apiUrl);
|
|
11799
|
+
return {
|
|
11800
|
+
credentialsPath: await writeCredentials(env, {
|
|
11801
|
+
token,
|
|
11802
|
+
apiUrl
|
|
11803
|
+
}),
|
|
11804
|
+
apiUrl,
|
|
11805
|
+
userId: claims.userId,
|
|
11806
|
+
orgId: claims.orgId,
|
|
11807
|
+
role: claims.role,
|
|
11808
|
+
hasOrg: claims.orgId !== null
|
|
11809
|
+
};
|
|
11810
|
+
}
|
|
11811
|
+
/** Open the dashboard bridge and await one POST of { token }. */
|
|
11812
|
+
function collectTokenViaBrowser(webUrl, timeoutMs = 18e4, noOpen = false) {
|
|
11813
|
+
return new Promise((resolve, reject) => {
|
|
11814
|
+
let server = null;
|
|
11815
|
+
const timer = setTimeout(() => {
|
|
11816
|
+
server?.close();
|
|
11817
|
+
reject(/* @__PURE__ */ new Error(`Timed out waiting for the browser to sign in (${Math.round(timeoutMs / 1e3)}s). Or paste a token: \`cow login --token <jwt>\`.`));
|
|
11818
|
+
}, timeoutMs);
|
|
11819
|
+
server = createServer((req, res) => {
|
|
11820
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
11821
|
+
res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
|
|
11822
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
11823
|
+
if (req.method === "OPTIONS") {
|
|
11824
|
+
res.writeHead(204).end();
|
|
11825
|
+
return;
|
|
11826
|
+
}
|
|
11827
|
+
if (req.method !== "POST") {
|
|
11828
|
+
res.writeHead(405).end();
|
|
11829
|
+
return;
|
|
11830
|
+
}
|
|
11831
|
+
const chunks = [];
|
|
11832
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
11833
|
+
req.on("end", () => {
|
|
11834
|
+
clearTimeout(timer);
|
|
11835
|
+
try {
|
|
11836
|
+
const body = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
11837
|
+
const field = (name) => typeof body === "object" && body !== null && name in body ? body[name] : void 0;
|
|
11838
|
+
if (field("denied") === true) {
|
|
11839
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
11840
|
+
res.end(JSON.stringify({ ok: true }));
|
|
11841
|
+
reject(/* @__PURE__ */ new Error("Sign-in was denied in the browser. No access was granted."));
|
|
11842
|
+
return;
|
|
11843
|
+
}
|
|
11844
|
+
const token = field("token");
|
|
11845
|
+
if (typeof token !== "string" || token.length === 0) {
|
|
11846
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
11847
|
+
res.end(JSON.stringify({ error: "missing token" }));
|
|
11848
|
+
reject(/* @__PURE__ */ new Error("The browser bridge sent no token"));
|
|
11849
|
+
return;
|
|
11850
|
+
}
|
|
11851
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
11852
|
+
res.end(JSON.stringify({ ok: true }));
|
|
11853
|
+
resolve(token);
|
|
11854
|
+
} catch (error) {
|
|
11855
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
11856
|
+
} finally {
|
|
11857
|
+
server?.close();
|
|
11858
|
+
}
|
|
11859
|
+
});
|
|
11860
|
+
});
|
|
11861
|
+
server.listen(0, "127.0.0.1", () => {
|
|
11862
|
+
const address = server?.address();
|
|
11863
|
+
if (address === null || typeof address !== "object") {
|
|
11864
|
+
reject(/* @__PURE__ */ new Error("Could not bind a loopback port"));
|
|
11865
|
+
return;
|
|
11866
|
+
}
|
|
11867
|
+
const callbackUrl = `http://127.0.0.1:${address.port}/cb`;
|
|
11868
|
+
const target = `${webUrl}/cli-auth?cb=${encodeURIComponent(callbackUrl)}`;
|
|
11869
|
+
if (!noOpen) openBrowser(target);
|
|
11870
|
+
process.stderr.write(`Waiting for sign-in at:\n ${target}\n`);
|
|
11871
|
+
});
|
|
11872
|
+
server.on("error", (error) => {
|
|
11873
|
+
clearTimeout(timer);
|
|
11874
|
+
reject(error);
|
|
11875
|
+
});
|
|
11876
|
+
});
|
|
11877
|
+
}
|
|
11878
|
+
function openBrowser(url) {
|
|
11879
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "linux" ? "xdg-open" : null;
|
|
11880
|
+
if (cmd === null) return;
|
|
11881
|
+
spawn(cmd, [url], {
|
|
11882
|
+
stdio: "ignore",
|
|
11883
|
+
detached: true
|
|
11884
|
+
}).unref();
|
|
11885
|
+
}
|
|
11886
|
+
|
|
11887
|
+
//#endregion
|
|
11888
|
+
//#region src/commands/auth.ts
|
|
11889
|
+
/** login/logout/whoami: session-token management, no contract route. */
|
|
11890
|
+
function registerAuth(program, env, io) {
|
|
11891
|
+
program.command("login").description("authenticate via the dashboard browser flow (or --token) and cache the session token").option("--token <jwt>", "paste a session token instead of the browser flow").option("--web <url>", `dashboard URL; overrides COW_WEB_URL and webUrl in cow.json (default ${DEFAULT_WEB_URL})`).option("--no-open", "do not open the browser; print the URL and wait (headless/agent use)").action(async (opts) => {
|
|
11892
|
+
const outcome = await login(env, {
|
|
11893
|
+
token: opts.token,
|
|
11894
|
+
webUrl: opts.web,
|
|
11895
|
+
apiFlag: opts.api,
|
|
11896
|
+
noOpen: opts.open === false
|
|
11897
|
+
});
|
|
11898
|
+
emit({ data: {
|
|
11899
|
+
...outcome,
|
|
11900
|
+
warnings: outcome.hasOrg ? void 0 : ["The token carries no active organization. Select one in the dashboard and log in again."]
|
|
11901
|
+
} }, io, opts.json);
|
|
11902
|
+
});
|
|
11903
|
+
program.command("logout").description("delete the cached session token").action(async (opts) => {
|
|
11904
|
+
const removed = await clearCredentials(env);
|
|
11905
|
+
emit({ data: { removed } }, io, opts.json);
|
|
11906
|
+
});
|
|
11907
|
+
program.command("whoami").description("show which credential commands will use, and for a session its subject, org, role, and expiry").option("--key <ak_...>", "ask the API what an ingestion key writes into, instead of reading the cached session").option("--source <src_...>", "the source id that key is about to send to (required with --key)").action(async (opts) => {
|
|
11908
|
+
if (opts.key) {
|
|
11909
|
+
if (!opts.source) throw new Error("`--key` needs `--source <src_...>`: the id you are about to send to is what this checks.");
|
|
11910
|
+
const client = createClient({
|
|
11911
|
+
baseUrl: resolveApiUrl(env, await readCredentials(env), opts.api, (await readCowConfig(process.cwd()))?.apiUrl),
|
|
11912
|
+
auth: () => opts.key ?? null,
|
|
11913
|
+
headers: { [CLIENT_HEADER]: `@cowliss/cli/${(await readCliPackage()).version}` }
|
|
11914
|
+
});
|
|
11915
|
+
emit(await client.request(contract.ingestion["ingestion.whoami"], { query: { sourceId: opts.source } }), io, opts.json);
|
|
11916
|
+
return;
|
|
11917
|
+
}
|
|
11918
|
+
const credentials = await readCredentials(env);
|
|
11919
|
+
const { kind } = resolveCredential(env, credentials);
|
|
11920
|
+
if (kind === "none") throw new Error("Not logged in. Run `cow login` first, or set COW_PIPELINE_KEY.");
|
|
11921
|
+
if (kind === "pipelineKey") {
|
|
11922
|
+
emit({ data: {
|
|
11923
|
+
credential: kind,
|
|
11924
|
+
source: "COW_PIPELINE_KEY",
|
|
11925
|
+
note: "A pipeline key may push, enable, disable, and read executions."
|
|
11926
|
+
} }, io, opts.json);
|
|
11927
|
+
return;
|
|
11928
|
+
}
|
|
11929
|
+
const claims = decodeSessionToken(credentials?.token ?? "");
|
|
11930
|
+
if (!claims) throw new Error("Cached token is not decodable. Run `cow login` again.");
|
|
11931
|
+
const projectApiUrl = (await readCowConfig(process.cwd()))?.apiUrl;
|
|
11932
|
+
const apiUrl = resolveApiUrl(env, credentials, opts.api, projectApiUrl);
|
|
11933
|
+
const mintedFor = credentials?.apiUrl;
|
|
11934
|
+
emit({ data: {
|
|
11935
|
+
credential: kind,
|
|
11936
|
+
apiUrl,
|
|
11937
|
+
mintedFor: mintedFor ?? null,
|
|
11938
|
+
...mintedFor !== void 0 && mintedFor !== apiUrl ? { warning: `This token was minted for ${mintedFor}, but commands here talk to ${apiUrl}. Run \`cow login\` again from this directory.` } : {},
|
|
11939
|
+
...claims
|
|
11940
|
+
} }, io, opts.json);
|
|
11941
|
+
});
|
|
11942
|
+
}
|
|
11943
|
+
|
|
11944
|
+
//#endregion
|
|
11945
|
+
//#region src/commands/build.ts
|
|
11946
|
+
/**
|
|
11947
|
+
* `cow build`: typecheck, bundle, and write `.cow/build` for the project in
|
|
11948
|
+
* the current directory. Local only, no API call and no network.
|
|
11949
|
+
*/
|
|
11950
|
+
function registerBuild(program, io) {
|
|
11951
|
+
program.command("build").description("typecheck and bundle the repo in the current directory, writing .cow/build (local: no API call)").action(async (opts) => {
|
|
11952
|
+
emit({ data: await buildProject(process.cwd()) }, io, opts.json);
|
|
11953
|
+
});
|
|
11954
|
+
}
|
|
11955
|
+
|
|
11890
11956
|
//#endregion
|
|
11891
11957
|
//#region ../../packages/shared/src/contract/surfaces.ts
|
|
11892
11958
|
/** camelCase field name → kebab-case flag body (`sourceId` → `source-id`). */
|
|
@@ -13591,7 +13657,7 @@ function errorMessage(error) {
|
|
|
13591
13657
|
code: error.code,
|
|
13592
13658
|
message: error.message
|
|
13593
13659
|
} }, null, 2);
|
|
13594
|
-
if (error.code === "invalid_key") return `${envelope}\n(hint: run \`cow login\` to refresh the session token)`;
|
|
13660
|
+
if (error.code === "invalid_key" && !process.argv.includes("--key")) return `${envelope}\n(hint: run \`cow login\` to refresh the session token)`;
|
|
13595
13661
|
return envelope;
|
|
13596
13662
|
}
|
|
13597
13663
|
if (error instanceof Error) return `error: ${error.message}`;
|