@odla-ai/cli 0.27.11 → 0.27.13
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 +7 -1
- package/dist/bin.cjs +272 -630
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-Y7WFLSTN.js → chunk-3YSCRXPF.js} +157 -449
- package/dist/chunk-3YSCRXPF.js.map +1 -0
- package/dist/index.cjs +272 -563
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/skills/odla/references/build.md +8 -0
- package/dist/chunk-Y7WFLSTN.js.map +0 -1
|
@@ -292,23 +292,30 @@ function handshakeWaitMs(waitSeconds, interactive = process4.stdout.isTTY === tr
|
|
|
292
292
|
|
|
293
293
|
// src/token.ts
|
|
294
294
|
async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {}) {
|
|
295
|
-
if (options.token) return options.token;
|
|
296
295
|
const audience = platformAudience(cfg.platformUrl);
|
|
297
|
-
if (process5.env.ODLA_DEV_TOKEN) {
|
|
298
|
-
const declared = process5.env.ODLA_DEV_TOKEN_AUDIENCE;
|
|
299
|
-
if (declared) {
|
|
300
|
-
if (platformAudience(declared) !== audience) throw new Error("ODLA_DEV_TOKEN_AUDIENCE does not match the configured platform");
|
|
301
|
-
} else if (audience !== "https://odla.ai") {
|
|
302
|
-
throw new Error("ODLA_DEV_TOKEN_AUDIENCE is required for a non-default platform");
|
|
303
|
-
}
|
|
304
|
-
return process5.env.ODLA_DEV_TOKEN;
|
|
305
|
-
}
|
|
306
296
|
const optionalProjectCapabilities = grantRequest.optionalProjectCapabilities ?? [];
|
|
307
297
|
const grantIntent = { projectIds: [cfg.app.id], optionalProjectCapabilities };
|
|
308
298
|
const cached = readJsonFile(cfg.local.tokenFile);
|
|
309
|
-
if (
|
|
310
|
-
|
|
311
|
-
|
|
299
|
+
if (!grantRequest.forceReview) {
|
|
300
|
+
if (options.token) return options.token;
|
|
301
|
+
if (process5.env.ODLA_DEV_TOKEN) {
|
|
302
|
+
const declared = process5.env.ODLA_DEV_TOKEN_AUDIENCE;
|
|
303
|
+
if (declared) {
|
|
304
|
+
if (platformAudience(declared) !== audience) throw new Error("ODLA_DEV_TOKEN_AUDIENCE does not match the configured platform");
|
|
305
|
+
} else if (audience !== "https://odla.ai") {
|
|
306
|
+
throw new Error("ODLA_DEV_TOKEN_AUDIENCE is required for a non-default platform");
|
|
307
|
+
}
|
|
308
|
+
return process5.env.ODLA_DEV_TOKEN;
|
|
309
|
+
}
|
|
310
|
+
if (cached?.token && cached.platform === audience && (cached.expiresAt ?? 0) > Date.now() + 6e4 && cachedGrantCovers(cached, grantIntent)) {
|
|
311
|
+
out.error(`auth: using cached developer token (${displayPath(cfg.local.tokenFile, cfg.rootDir)})`);
|
|
312
|
+
return cached.token;
|
|
313
|
+
}
|
|
314
|
+
} else {
|
|
315
|
+
if (options.token) {
|
|
316
|
+
throw new Error("--request-grant cannot be combined with --token; remove --token so the approved replacement credential can be collected and cached");
|
|
317
|
+
}
|
|
318
|
+
out.error(`auth: requesting fresh owner review for app.manage on exact project "${cfg.app.id}"`);
|
|
312
319
|
}
|
|
313
320
|
const ctx = {
|
|
314
321
|
cfg,
|
|
@@ -1372,12 +1379,6 @@ async function pollCalendarConnection(ctx, attemptId) {
|
|
|
1372
1379
|
ctx.env
|
|
1373
1380
|
);
|
|
1374
1381
|
}
|
|
1375
|
-
async function requestCalendarDisconnect(ctx) {
|
|
1376
|
-
return parseCalendarStatus(
|
|
1377
|
-
await calendarJson(ctx, "/disconnect", { method: "POST", body: JSON.stringify({ purge: false }) }),
|
|
1378
|
-
ctx.env
|
|
1379
|
-
);
|
|
1380
|
-
}
|
|
1381
1382
|
function parseCalendarStatus(raw, env) {
|
|
1382
1383
|
const outer = wrapped(raw, "calendar");
|
|
1383
1384
|
const value2 = record(outer.attempt) ?? record(outer.status) ?? outer;
|
|
@@ -1528,6 +1529,16 @@ async function waitForCalendarPoll(milliseconds, signal) {
|
|
|
1528
1529
|
});
|
|
1529
1530
|
}
|
|
1530
1531
|
|
|
1532
|
+
// src/human-session.ts
|
|
1533
|
+
async function requireStudioHuman(configPath, action2, destination = "app", env) {
|
|
1534
|
+
const cfg = await loadProjectConfig(configPath);
|
|
1535
|
+
const appEnv = env && cfg.envs.includes(env) ? env : cfg.envs.includes("dev") ? "dev" : cfg.envs[0] ?? "prod";
|
|
1536
|
+
const path = destination === "app" ? `/studio/apps/${encodeURIComponent(cfg.app.id)}/${encodeURIComponent(appEnv)}/settings/app` : `/studio/apps/${encodeURIComponent(cfg.app.id)}/${encodeURIComponent(appEnv)}/${destination}`;
|
|
1537
|
+
throw new Error(
|
|
1538
|
+
`human_session_required: ${action2} requires the signed-in owner in Studio. A CLI device token is an agent credential, so another approval or retry cannot work. ${new URL(path, cfg.platformUrl).href}`
|
|
1539
|
+
);
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1531
1542
|
// src/calendar.ts
|
|
1532
1543
|
async function calendarStatus(options) {
|
|
1533
1544
|
const { ctx, out } = await lifecycleContext(options);
|
|
@@ -1561,10 +1572,7 @@ async function applyCalendarBookingPage(ctx, bookingPageUrl, out) {
|
|
|
1561
1572
|
}
|
|
1562
1573
|
async function calendarDisconnect(options) {
|
|
1563
1574
|
if (!options.yes) throw new Error("calendar disconnect requires --yes");
|
|
1564
|
-
|
|
1565
|
-
const status = await requestCalendarDisconnect(ctx);
|
|
1566
|
-
out.log(`${ctx.env}: calendar disconnected; no calendar data was stored`);
|
|
1567
|
-
return status;
|
|
1575
|
+
return requireStudioHuman(options.configPath, "calendar disconnect", "calendar", options.env);
|
|
1568
1576
|
}
|
|
1569
1577
|
async function ensureCalendarConnected(ctx, options) {
|
|
1570
1578
|
const out = options.stdout ?? console;
|
|
@@ -1809,11 +1817,11 @@ async function assertTenantAdminAccess(doFetch, cfg, env, token) {
|
|
|
1809
1817
|
}
|
|
1810
1818
|
if (code === "provision_approval_required") {
|
|
1811
1819
|
throw new Error(
|
|
1812
|
-
`${env}: the agent credential does not carry the owner-reviewed app.manage grant required to provision "${cfg.app.id}" (tenant ${tenantId}). The human owner id on the token is accountability, not agent authority.
|
|
1820
|
+
`${env}: the agent credential does not carry the owner-reviewed app.manage grant required to provision "${cfg.app.id}" (tenant ${tenantId}). The human owner id on the token is accountability, not agent authority. Run "odla-ai provision --request-grant --email <odla-account>" to open one fresh exact-project owner review; do not change app ownership unless the human account itself is not an owner`
|
|
1813
1821
|
);
|
|
1814
1822
|
}
|
|
1815
1823
|
throw new Error(
|
|
1816
|
-
`${env}: this credential lacks live app.manage authority for "${cfg.app.id}" (tenant ${tenantId}) \u2014 nothing was minted or written;
|
|
1824
|
+
`${env}: this credential lacks live app.manage authority for "${cfg.app.id}" (tenant ${tenantId}) \u2014 nothing was minted or written; run "odla-ai provision --request-grant --email <odla-account>" to open a fresh owner review. If the human account is not an owner, an existing owner must add it in signed-in Studio; an agent token cannot repair ownership`
|
|
1817
1825
|
);
|
|
1818
1826
|
}
|
|
1819
1827
|
throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText4(res)}`);
|
|
@@ -3345,7 +3353,9 @@ async function secretsSetClerkKey(options) {
|
|
|
3345
3353
|
if (value2.startsWith("sk_live_") && !PROD_ENV_NAMES2.has(options.env) && !options.yes) {
|
|
3346
3354
|
throw new Error(`refusing to store an sk_live_ Clerk key for "${options.env}" without --yes (live users would sync into a non-prod tenant)`);
|
|
3347
3355
|
}
|
|
3348
|
-
const token = await getDeveloperToken(cfg, options, doFetch, out
|
|
3356
|
+
const token = await getDeveloperToken(cfg, options, doFetch, out, {
|
|
3357
|
+
optionalProjectCapabilities: ["app.manage"]
|
|
3358
|
+
});
|
|
3349
3359
|
const res = await doFetch(`${cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenantId)}/clerk-secret`, {
|
|
3350
3360
|
method: "POST",
|
|
3351
3361
|
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
@@ -7238,7 +7248,7 @@ function record6(value2) {
|
|
|
7238
7248
|
}
|
|
7239
7249
|
|
|
7240
7250
|
// src/provision.ts
|
|
7241
|
-
import { createAppsClient as createAppsClient3, orderAppServices as orderAppServices3, tenantIdFor as tenantIdFor4 } from "@odla-ai/apps";
|
|
7251
|
+
import { AppsError as AppsError2, createAppsClient as createAppsClient3, orderAppServices as orderAppServices3, tenantIdFor as tenantIdFor4 } from "@odla-ai/apps";
|
|
7242
7252
|
import { putSecret as putSecret2 } from "@odla-ai/ai";
|
|
7243
7253
|
import process9 from "process";
|
|
7244
7254
|
|
|
@@ -7459,14 +7469,25 @@ async function provision(options) {
|
|
|
7459
7469
|
}
|
|
7460
7470
|
const doFetch = options.fetch ?? fetch;
|
|
7461
7471
|
const token = await getDeveloperToken(cfg, options, doFetch, out, {
|
|
7462
|
-
optionalProjectCapabilities: ["app.manage"]
|
|
7472
|
+
optionalProjectCapabilities: ["app.manage"],
|
|
7473
|
+
forceReview: options.requestGrant
|
|
7463
7474
|
});
|
|
7464
7475
|
const apps = createAppsClient3({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
|
|
7465
7476
|
const existing = await apps.resolveApp(cfg.app.id);
|
|
7466
7477
|
if (existing) {
|
|
7467
7478
|
out.log(`app: ${cfg.app.id} already exists`);
|
|
7468
7479
|
} else {
|
|
7469
|
-
|
|
7480
|
+
try {
|
|
7481
|
+
await apps.createApp({ name: cfg.app.name, appId: cfg.app.id });
|
|
7482
|
+
} catch (error) {
|
|
7483
|
+
if (error instanceof AppsError2 && error.status === 403) {
|
|
7484
|
+
throw new Error(
|
|
7485
|
+
`app "${cfg.app.id}" does not exist, and this authenticated agent credential has no owner-reviewed app.manage bootstrap grant for that exact id. Run "odla-ai provision --request-grant --email <odla-account>" to open the review URL and continue; developer ownership alone is not agent authority`,
|
|
7486
|
+
{ cause: error }
|
|
7487
|
+
);
|
|
7488
|
+
}
|
|
7489
|
+
throw error;
|
|
7490
|
+
}
|
|
7470
7491
|
out.log(`app: created ${cfg.app.id}`);
|
|
7471
7492
|
}
|
|
7472
7493
|
for (const env of cfg.envs) {
|
|
@@ -7817,17 +7838,17 @@ async function runHostedSecurity(options) {
|
|
|
7817
7838
|
allowNetwork: false
|
|
7818
7839
|
}
|
|
7819
7840
|
});
|
|
7820
|
-
const
|
|
7821
|
-
await writeSecurityArtifacts(output,
|
|
7822
|
-
const reportDigest = await securityFingerprint(
|
|
7841
|
+
const report4 = await harness.run(snapshot, { runId: hosted.run.runId, signal: options.signal });
|
|
7842
|
+
await writeSecurityArtifacts(output, report4);
|
|
7843
|
+
const reportDigest = await securityFingerprint(report4);
|
|
7823
7844
|
await hosted.complete({
|
|
7824
7845
|
reportDigest,
|
|
7825
|
-
coverageStatus:
|
|
7826
|
-
confirmed:
|
|
7827
|
-
candidates:
|
|
7846
|
+
coverageStatus: report4.coverageStatus,
|
|
7847
|
+
confirmed: report4.metrics.confirmed,
|
|
7848
|
+
candidates: report4.metrics.candidates
|
|
7828
7849
|
}, { signal: options.signal });
|
|
7829
|
-
printSummary(options.stdout ?? console, appId, env, hosted.run,
|
|
7830
|
-
return Object.freeze({ report:
|
|
7850
|
+
printSummary(options.stdout ?? console, appId, env, hosted.run, report4, output);
|
|
7851
|
+
return Object.freeze({ report: report4, run: hosted.run, output });
|
|
7831
7852
|
}
|
|
7832
7853
|
function selectEnv(requested, declared, configPath, rootDir) {
|
|
7833
7854
|
const env = requested ?? (declared.includes("dev") ? "dev" : declared[0]);
|
|
@@ -7853,14 +7874,14 @@ function profileFor(name, maxHuntTasks) {
|
|
|
7853
7874
|
if (!Number.isSafeInteger(maxHuntTasks) || maxHuntTasks < 1) throw new Error("maxHuntTasks must be a positive integer");
|
|
7854
7875
|
return { ...profile, maxHuntTasks };
|
|
7855
7876
|
}
|
|
7856
|
-
function printSummary(out, appId, env, run,
|
|
7857
|
-
const complete =
|
|
7877
|
+
function printSummary(out, appId, env, run, report4, output) {
|
|
7878
|
+
const complete = report4.coverage.filter((cell) => cell.state === "complete").length;
|
|
7858
7879
|
out.log(`security: ${appId}/${env} run=${run.runId} profile=${run.profileVersion}`);
|
|
7859
7880
|
out.log(` discovery: ${run.discovery.identity.provider}/${run.discovery.identity.model}`);
|
|
7860
7881
|
out.log(` validation: ${run.validation.identity.provider}/${run.validation.identity.model}`);
|
|
7861
|
-
out.log(` coverage: ${
|
|
7862
|
-
if (
|
|
7863
|
-
out.log(` findings: confirmed=${
|
|
7882
|
+
out.log(` coverage: ${report4.coverageStatus} ${complete}/${report4.coverage.length} blocked=${report4.metrics.blockedCells} shallow=${report4.metrics.shallowCells} unscheduled=${report4.metrics.unscheduledCells} budget_exhausted=${report4.metrics.budgetExhaustedCells}`);
|
|
7883
|
+
if (report4.callBudget) out.log(` calls: discovery=${formatBudget(report4.callBudget.discovery)} validation=${formatBudget(report4.callBudget.validation)}`);
|
|
7884
|
+
out.log(` findings: confirmed=${report4.metrics.confirmed} needs_reproduction=${report4.metrics.needsReproduction} candidates=${report4.metrics.candidates}`);
|
|
7864
7885
|
out.log(` report: ${resolve9(output, "REPORT.md")}`);
|
|
7865
7886
|
}
|
|
7866
7887
|
function formatBudget(usage) {
|
|
@@ -8425,7 +8446,7 @@ async function agentCommand(parsed, deps = {}) {
|
|
|
8425
8446
|
if (action2 !== "jobs" && action2 !== "retry") {
|
|
8426
8447
|
throw new Error(`unknown agent action "${action2 ?? ""}". Try "odla-ai agent jobs --json".`);
|
|
8427
8448
|
}
|
|
8428
|
-
assertArgs(parsed, ["config", "env", "state", "limit", "json", "token"
|
|
8449
|
+
assertArgs(parsed, ["config", "env", "state", "limit", "json", "token"], action2 === "jobs" ? 2 : 3);
|
|
8429
8450
|
if (action2 === "retry" && (parsed.options.state !== void 0 || parsed.options.limit !== void 0)) {
|
|
8430
8451
|
throw new Error('--state and --limit are supported only by "agent jobs"');
|
|
8431
8452
|
}
|
|
@@ -8433,17 +8454,17 @@ async function agentCommand(parsed, deps = {}) {
|
|
|
8433
8454
|
const { env, tenant } = resolveTenant(cfg, stringOpt(parsed.options.env));
|
|
8434
8455
|
const doFetch = deps.fetch ?? fetch;
|
|
8435
8456
|
const out = deps.stdout ?? console;
|
|
8436
|
-
const credential2 =
|
|
8437
|
-
|
|
8438
|
-
|
|
8439
|
-
|
|
8440
|
-
|
|
8441
|
-
|
|
8442
|
-
|
|
8443
|
-
|
|
8444
|
-
|
|
8445
|
-
|
|
8446
|
-
|
|
8457
|
+
const credential2 = stringOpt(parsed.options.token) ?? readCredentials(cfg.local.credentialsFile)?.envs[env]?.dbKey;
|
|
8458
|
+
if (!credential2) {
|
|
8459
|
+
throw new Error(
|
|
8460
|
+
`no ${env} app credential found; run \`odla-ai provision --write-dev-vars --yes\` or pass --token <ODLA_API_KEY>`
|
|
8461
|
+
);
|
|
8462
|
+
}
|
|
8463
|
+
if (credential2.startsWith("odla_dev_")) {
|
|
8464
|
+
throw new Error(
|
|
8465
|
+
"agent job administration requires an app credential (ODLA_API_KEY / odla_sk_\u2026), not a developer device token"
|
|
8466
|
+
);
|
|
8467
|
+
}
|
|
8447
8468
|
const base = `${cfg.dbEndpoint}/app/${encodeURIComponent(tenant)}/admin/agent-jobs`;
|
|
8448
8469
|
const headers = { authorization: `Bearer ${credential2}` };
|
|
8449
8470
|
if (action2 === "retry") {
|
|
@@ -8500,53 +8521,14 @@ function errorMessage(body) {
|
|
|
8500
8521
|
}
|
|
8501
8522
|
|
|
8502
8523
|
// src/app-export.ts
|
|
8503
|
-
import { createWriteStream } from "fs";
|
|
8504
|
-
import { Readable } from "stream";
|
|
8505
|
-
import { pipeline } from "stream/promises";
|
|
8506
8524
|
async function appExport(options) {
|
|
8507
|
-
|
|
8508
|
-
const out = options.stdout ?? console;
|
|
8509
|
-
const doFetch = options.fetch ?? fetch;
|
|
8510
|
-
const { tenant } = resolveTenant(cfg, options.env);
|
|
8511
|
-
const token = await getDeveloperToken(
|
|
8512
|
-
cfg,
|
|
8513
|
-
{ configPath: cfg.configPath, token: options.token, email: options.email, open: false },
|
|
8514
|
-
doFetch,
|
|
8515
|
-
out
|
|
8516
|
-
);
|
|
8517
|
-
const auth = { authorization: `Bearer ${token}` };
|
|
8518
|
-
const base = `${cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenant)}`;
|
|
8519
|
-
if (options.fresh) {
|
|
8520
|
-
const res = await doFetch(`${base}/export`, { method: "POST", headers: auth });
|
|
8521
|
-
const body = await res.json().catch(() => ({}));
|
|
8522
|
-
if (!res.ok) throw new Error(`export failed${body.error?.code ? ` (${body.error.code})` : ""}: ${body.error?.message ?? res.status}`);
|
|
8523
|
-
}
|
|
8524
|
-
const list = await doFetch(`${base}/backups`, { headers: auth });
|
|
8525
|
-
if (!list.ok) throw new Error(`couldn't list backups (${list.status})`);
|
|
8526
|
-
const { backups } = await list.json();
|
|
8527
|
-
const newest = backups[0];
|
|
8528
|
-
if (!newest) {
|
|
8529
|
-
throw new Error(
|
|
8530
|
-
`${tenant} has no backups yet \u2014 run \`odla-ai app export --fresh\` to take one now (nightly snapshots appear after the first day with writes)`
|
|
8531
|
-
);
|
|
8532
|
-
}
|
|
8533
|
-
const download = await doFetch(`${base}/backups/${newest.id}/download`, { headers: auth });
|
|
8534
|
-
if (!download.ok || !download.body) throw new Error(`download failed (${download.status})`);
|
|
8535
|
-
const file = options.out ?? `${tenant}-${new Date(newest.created_at).toISOString().slice(0, 10)}-tx${newest.max_tx}.jsonl.gz`;
|
|
8536
|
-
await pipeline(Readable.fromWeb(download.body), createWriteStream(file));
|
|
8537
|
-
if (options.json) out.log(JSON.stringify({ file, backup: newest }, null, 2));
|
|
8538
|
-
else {
|
|
8539
|
-
out.log(`${tenant}: wrote ${file} (${newest.bytes} bytes, ${newest.kind} snapshot at tx ${newest.max_tx})`);
|
|
8540
|
-
out.log(`sha256 ${download.headers.get("x-odla-sha256") ?? newest.sha256}`);
|
|
8541
|
-
}
|
|
8542
|
-
return { file, backup: newest };
|
|
8525
|
+
return requireStudioHuman(options.configPath, "database export", "database", options.env);
|
|
8543
8526
|
}
|
|
8544
8527
|
|
|
8545
8528
|
// src/app-import.ts
|
|
8546
8529
|
import { readFileSync as readFileSync9 } from "fs";
|
|
8547
8530
|
import {
|
|
8548
8531
|
buildImportOps,
|
|
8549
|
-
importMutationId,
|
|
8550
8532
|
parseImport,
|
|
8551
8533
|
planImportChunks
|
|
8552
8534
|
} from "@odla-ai/db/import";
|
|
@@ -8565,7 +8547,6 @@ async function appImport(options) {
|
|
|
8565
8547
|
const cfg = await loadProjectConfig(options.configPath);
|
|
8566
8548
|
const out = options.stdout ?? console;
|
|
8567
8549
|
const say = options.json ? (line) => out.error(line) : (line) => out.log(line);
|
|
8568
|
-
const doFetch = options.fetch ?? fetch;
|
|
8569
8550
|
const { tenant } = resolveTenant(cfg, options.env);
|
|
8570
8551
|
const text2 = options.file === "-" ? (options.readStdin ?? (() => readFileSync9(0, "utf8")))() : readFileSync9(options.file, "utf8");
|
|
8571
8552
|
const { format, sources } = parseImport(text2, options.ns);
|
|
@@ -8593,100 +8574,18 @@ ${detail}${more}`);
|
|
|
8593
8574
|
if (options.json) out.log(JSON.stringify(result, null, 2));
|
|
8594
8575
|
return result;
|
|
8595
8576
|
}
|
|
8596
|
-
|
|
8597
|
-
cfg,
|
|
8598
|
-
{ configPath: cfg.configPath, token: options.token, email: options.email, open: false },
|
|
8599
|
-
doFetch,
|
|
8600
|
-
out
|
|
8601
|
-
);
|
|
8602
|
-
const runId = crypto.randomUUID();
|
|
8603
|
-
const url = `${cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenant)}/transact`;
|
|
8604
|
-
for (const chunk of chunks) {
|
|
8605
|
-
const res = await doFetch(url, {
|
|
8606
|
-
method: "POST",
|
|
8607
|
-
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
8608
|
-
body: JSON.stringify({ mutationId: importMutationId(runId, chunk.index), ops: chunk.ops })
|
|
8609
|
-
});
|
|
8610
|
-
const body = await res.json().catch(() => ({}));
|
|
8611
|
-
if (!res.ok) {
|
|
8612
|
-
const code = body.error?.code ? ` (${body.error.code})` : "";
|
|
8613
|
-
throw new Error(
|
|
8614
|
-
`chunk ${chunk.index + 1} of ${chunks.length} failed${code}: ${body.error?.message ?? res.status}. ${result.committed} row(s) already committed; fix the input and re-run to import the rest.`
|
|
8615
|
-
);
|
|
8616
|
-
}
|
|
8617
|
-
result.committed += chunk.ops.length;
|
|
8618
|
-
if (typeof body.txId === "number") result.txIds.push(body.txId);
|
|
8619
|
-
if (body.duplicate) result.duplicate++;
|
|
8620
|
-
}
|
|
8621
|
-
if (options.json) out.log(JSON.stringify(result, null, 2));
|
|
8622
|
-
else {
|
|
8623
|
-
const dup = result.duplicate > 0 ? ` (${result.duplicate} chunk(s) were already applied)` : "";
|
|
8624
|
-
out.log(`${tenant}: upserted ${result.committed} row(s) in ${chunks.length} transaction(s)${dup}`);
|
|
8625
|
-
}
|
|
8626
|
-
return result;
|
|
8577
|
+
return requireStudioHuman(options.configPath, "database import", "database", options.env);
|
|
8627
8578
|
}
|
|
8628
8579
|
|
|
8629
8580
|
// src/app-owners.ts
|
|
8630
|
-
var sink = (options) => options.stdout ?? console;
|
|
8631
|
-
async function ownersRequest(method, suffix, options, body) {
|
|
8632
|
-
const cfg = await loadProjectConfig(options.configPath);
|
|
8633
|
-
const doFetch = options.fetch ?? fetch;
|
|
8634
|
-
const token = await getDeveloperToken(
|
|
8635
|
-
cfg,
|
|
8636
|
-
{ configPath: cfg.configPath, token: options.token, email: options.email, open: false },
|
|
8637
|
-
doFetch,
|
|
8638
|
-
sink(options)
|
|
8639
|
-
);
|
|
8640
|
-
const res = await doFetch(`${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/owners${suffix}`, {
|
|
8641
|
-
method,
|
|
8642
|
-
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
8643
|
-
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
8644
|
-
});
|
|
8645
|
-
const data = await res.json().catch(() => ({}));
|
|
8646
|
-
if (!res.ok) {
|
|
8647
|
-
throw new Error(
|
|
8648
|
-
`owners ${method} failed${data.error?.code ? ` (${data.error.code})` : ""}: ` + (data.error?.message ?? `registry returned ${res.status}`)
|
|
8649
|
-
);
|
|
8650
|
-
}
|
|
8651
|
-
return data.owners ?? [];
|
|
8652
|
-
}
|
|
8653
|
-
function report(options, owners, headline) {
|
|
8654
|
-
const out = sink(options);
|
|
8655
|
-
if (options.json === true) {
|
|
8656
|
-
out.log(JSON.stringify(owners, null, 2));
|
|
8657
|
-
return;
|
|
8658
|
-
}
|
|
8659
|
-
if (headline) out.log(headline);
|
|
8660
|
-
out.log(`owners (${owners.length}):`);
|
|
8661
|
-
for (const o of owners) {
|
|
8662
|
-
const name = o.email?.trim() || "Unnamed member";
|
|
8663
|
-
out.log(
|
|
8664
|
-
` ${o.primary ? "\u2605" : "\xB7"} ${name} [${o.ownerId}]${o.primary ? " (primary)" : ""}`
|
|
8665
|
-
);
|
|
8666
|
-
}
|
|
8667
|
-
}
|
|
8668
8581
|
async function ownersList(options) {
|
|
8669
|
-
|
|
8582
|
+
await requireStudioHuman(options.configPath, "listing app owners", "app");
|
|
8670
8583
|
}
|
|
8671
8584
|
async function ownersAdd(email, options) {
|
|
8672
|
-
|
|
8673
|
-
report(
|
|
8674
|
-
options,
|
|
8675
|
-
owners,
|
|
8676
|
-
`added ${email} as a co-owner \u2014 they share full access. They can now run \`odla-ai provision\` to mint their own credentials (no secret sharing).`
|
|
8677
|
-
);
|
|
8585
|
+
await requireStudioHuman(options.configPath, `adding ${email} as an app owner`, "app");
|
|
8678
8586
|
}
|
|
8679
8587
|
async function ownersRemove(target, options) {
|
|
8680
|
-
|
|
8681
|
-
if (target.includes("@")) {
|
|
8682
|
-
const owners2 = await ownersRequest("GET", "", options);
|
|
8683
|
-
const match = owners2.find((o) => o.email?.toLowerCase() === target.toLowerCase());
|
|
8684
|
-
if (!match) throw new Error(`no co-owner with email ${target}`);
|
|
8685
|
-
if (match.primary) throw new Error("can't remove the primary owner");
|
|
8686
|
-
ownerId = match.ownerId;
|
|
8687
|
-
}
|
|
8688
|
-
const owners = await ownersRequest("DELETE", `/${encodeURIComponent(ownerId)}`, options);
|
|
8689
|
-
report(options, owners, `removed ${target}`);
|
|
8588
|
+
await requireStudioHuman(options.configPath, `removing ${target} as an app owner`, "app");
|
|
8690
8589
|
}
|
|
8691
8590
|
async function appOwnersCommand(parsed, dependencies = {}) {
|
|
8692
8591
|
const sub = parsed.positionals[2] ?? "list";
|
|
@@ -8718,35 +8617,9 @@ async function appOwnersCommand(parsed, dependencies = {}) {
|
|
|
8718
8617
|
|
|
8719
8618
|
// src/app-rename.ts
|
|
8720
8619
|
async function appRename(name, options) {
|
|
8721
|
-
const out = options.stdout ?? console;
|
|
8722
8620
|
const trimmed = name.trim();
|
|
8723
8621
|
if (!trimmed) throw new Error('"app rename" needs a name \u2014 try `odla-ai app rename "Acme Storefront"`.');
|
|
8724
|
-
|
|
8725
|
-
const doFetch = options.fetch ?? fetch;
|
|
8726
|
-
const token = await getDeveloperToken(
|
|
8727
|
-
cfg,
|
|
8728
|
-
{ configPath: cfg.configPath, token: options.token, email: options.email, open: false },
|
|
8729
|
-
doFetch,
|
|
8730
|
-
out
|
|
8731
|
-
);
|
|
8732
|
-
const res = await doFetch(`${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/name`, {
|
|
8733
|
-
method: "PUT",
|
|
8734
|
-
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
8735
|
-
body: JSON.stringify({ name: trimmed })
|
|
8736
|
-
});
|
|
8737
|
-
const data = await res.json().catch(() => ({}));
|
|
8738
|
-
if (!res.ok || !data.app) {
|
|
8739
|
-
throw new Error(
|
|
8740
|
-
`rename failed${data.error?.code ? ` (${data.error.code})` : ""}: ` + (data.error?.message ?? `registry returned ${res.status}`)
|
|
8741
|
-
);
|
|
8742
|
-
}
|
|
8743
|
-
if (options.json === true) {
|
|
8744
|
-
out.log(JSON.stringify(data.app, null, 2));
|
|
8745
|
-
return;
|
|
8746
|
-
}
|
|
8747
|
-
out.log(`renamed ${data.app.appId} \u2192 "${data.app.name}"`);
|
|
8748
|
-
out.log("The app id is unchanged, so credentials, tenants, and URLs keep working.");
|
|
8749
|
-
out.log(`Update the "name" in ${cfg.configPath} to match.`);
|
|
8622
|
+
await requireStudioHuman(options.configPath, `renaming the app to "${trimmed}"`, "app");
|
|
8750
8623
|
}
|
|
8751
8624
|
async function appRenameCommand(parsed, dependencies = {}) {
|
|
8752
8625
|
assertArgs(parsed, ["config", "token", "email", "json"], parsed.positionals.length);
|
|
@@ -8762,172 +8635,23 @@ async function appRenameCommand(parsed, dependencies = {}) {
|
|
|
8762
8635
|
}
|
|
8763
8636
|
|
|
8764
8637
|
// src/app-transfer.ts
|
|
8765
|
-
function endpointsFor(verb, tenants) {
|
|
8766
|
-
const up = { source: tenants.sandbox, target: tenants.live, from: "dev" };
|
|
8767
|
-
const down = { source: tenants.live, target: tenants.sandbox, from: "prod" };
|
|
8768
|
-
if (verb === "refresh-sandbox") return { ...down, mode: "refresh" };
|
|
8769
|
-
return { ...up, mode: "cutover" };
|
|
8770
|
-
}
|
|
8771
|
-
async function api(cfg, token, path, init, doFetch) {
|
|
8772
|
-
const res = await doFetch(`${cfg.dbEndpoint}${path}`, {
|
|
8773
|
-
...init,
|
|
8774
|
-
headers: { authorization: `Bearer ${token}`, "content-type": "application/json", ...init?.headers }
|
|
8775
|
-
});
|
|
8776
|
-
return { status: res.status, body: await res.json().catch(() => ({})) };
|
|
8777
|
-
}
|
|
8778
|
-
function describe(side) {
|
|
8779
|
-
if (!side.exists) return "not provisioned";
|
|
8780
|
-
if (side.maxTx === 0) return "empty \u2014 never written";
|
|
8781
|
-
const ns = side.namespaces.length === 1 ? "1 namespace" : `${side.namespaces.length} namespaces`;
|
|
8782
|
-
const identity = side.identityRows > 0 ? `, ${side.identityRows} identity row(s)` : "";
|
|
8783
|
-
return `${ns} \xB7 ${side.triples} value(s) \xB7 tx ${side.maxTx}${identity}`;
|
|
8784
|
-
}
|
|
8785
|
-
function printPlan2(out, verb, pre, opts) {
|
|
8786
|
-
const arrow = verb === "refresh-sandbox" ? "live \u2192 sandbox" : "sandbox \u2192 live";
|
|
8787
|
-
out.log(`${verb} (${arrow})`);
|
|
8788
|
-
out.log(` from ${pre.source.tenant} ${describe(pre.source)}`);
|
|
8789
|
-
out.log(` to ${pre.target.tenant} ${describe(pre.target)}`);
|
|
8790
|
-
if (verb === "promote") {
|
|
8791
|
-
out.log(" moves schema, rules and gates only \u2014 no rows are copied or removed");
|
|
8792
|
-
} else {
|
|
8793
|
-
out.log(` ${pre.target.tenant} is REPLACED by ${pre.source.tenant}`);
|
|
8794
|
-
out.log(` stays put: ${pre.staysPut.join(", ")}`);
|
|
8795
|
-
const identity = opts.includeIdentity ? "INCLUDED (--include-identity)" : `left behind: ${pre.excluded.join(", ")}`;
|
|
8796
|
-
out.log(` identity: ${identity}`);
|
|
8797
|
-
out.log(` files: ${opts.includeFiles ? "copied (--include-files)" : "not copied"}`);
|
|
8798
|
-
}
|
|
8799
|
-
for (const blocker of pre.blockers) out.log(` \u2716 ${blocker.code}: ${blocker.message}`);
|
|
8800
|
-
}
|
|
8801
8638
|
async function appTransfer(options) {
|
|
8802
8639
|
const cfg = await loadProjectConfig(options.configPath);
|
|
8803
|
-
|
|
8804
|
-
|
|
8805
|
-
const doFetch = options.fetch ?? fetch;
|
|
8806
|
-
const tenants = bothTenants(cfg);
|
|
8807
|
-
const route2 = endpointsFor(options.verb, tenants);
|
|
8808
|
-
const token = await getDeveloperToken(
|
|
8809
|
-
cfg,
|
|
8810
|
-
{ configPath: cfg.configPath, token: options.token, email: options.email, open: false },
|
|
8811
|
-
doFetch,
|
|
8812
|
-
out
|
|
8813
|
-
);
|
|
8814
|
-
const pre = await api(
|
|
8815
|
-
cfg,
|
|
8816
|
-
token,
|
|
8817
|
-
`/admin/apps/${encodeURIComponent(route2.target)}/copy-preflight?from=${route2.from}`,
|
|
8818
|
-
void 0,
|
|
8819
|
-
doFetch
|
|
8820
|
-
);
|
|
8821
|
-
if (pre.status !== 200) {
|
|
8822
|
-
throw new Error(`pre-flight failed${pre.body.error?.code ? ` (${pre.body.error.code})` : ""}: ${pre.body.error?.message ?? pre.status}`);
|
|
8823
|
-
}
|
|
8824
|
-
const plan = pre.body;
|
|
8825
|
-
printPlan2({ log: say }, options.verb, plan, options);
|
|
8826
|
-
if (options.verb === "go-live" && !plan.targetEmpty) {
|
|
8827
|
-
throw new Error(
|
|
8828
|
-
`${route2.target} already has data \u2014 go-live has already happened. Use \`odla-ai app promote --yes\` to push schema and rules, or \`odla-ai app refresh-sandbox --yes\` to pull live back down.`
|
|
8829
|
-
);
|
|
8830
|
-
}
|
|
8831
|
-
if (plan.blockers.length > 0) throw new Error(`cannot ${options.verb}: ${plan.blockers.map((b) => b.message).join("; ")}`);
|
|
8832
|
-
if (options.dryRun || options.yes !== true) {
|
|
8833
|
-
say(`nothing written (${options.dryRun ? "--dry-run" : "no --yes"})`);
|
|
8834
|
-
if (options.json) out.log(JSON.stringify(plan, null, 2));
|
|
8835
|
-
return { ok: true, plan };
|
|
8836
|
-
}
|
|
8837
|
-
if (options.verb === "promote") {
|
|
8838
|
-
const res2 = await api(
|
|
8839
|
-
cfg,
|
|
8840
|
-
token,
|
|
8841
|
-
`/admin/apps/${encodeURIComponent(route2.target)}/promote-definitions`,
|
|
8842
|
-
{ method: "POST", body: JSON.stringify({ from: "dev" }) },
|
|
8843
|
-
doFetch
|
|
8844
|
-
);
|
|
8845
|
-
if (res2.status !== 200) throw new Error(`promote failed${res2.body.error?.code ? ` (${res2.body.error.code})` : ""}: ${res2.body.error?.message ?? res2.status}`);
|
|
8846
|
-
if (options.json) out.log(JSON.stringify(res2.body, null, 2));
|
|
8847
|
-
else say(`${route2.target}: promoted ${(res2.body.applied ?? []).join(", ")}`);
|
|
8848
|
-
return { ok: true, plan, result: res2.body };
|
|
8849
|
-
}
|
|
8850
|
-
const res = await api(
|
|
8851
|
-
cfg,
|
|
8852
|
-
token,
|
|
8853
|
-
`/admin/apps/${encodeURIComponent(route2.target)}/copy-db`,
|
|
8854
|
-
{
|
|
8855
|
-
method: "POST",
|
|
8856
|
-
body: JSON.stringify({
|
|
8857
|
-
from: route2.from,
|
|
8858
|
-
mode: route2.mode,
|
|
8859
|
-
...options.includeIdentity ? { includeUsers: true } : {},
|
|
8860
|
-
...options.includeFiles ? { includeFiles: true } : {}
|
|
8861
|
-
})
|
|
8862
|
-
},
|
|
8863
|
-
doFetch
|
|
8864
|
-
);
|
|
8865
|
-
if (res.status !== 200) throw new Error(`${options.verb} failed${res.body.error?.code ? ` (${res.body.error.code})` : ""}: ${res.body.error?.message ?? res.status}`);
|
|
8866
|
-
say(`${route2.target}: replaced from ${route2.source} (tx ${res.body.destination?.maxTx}, epoch ${res.body.destination?.epoch}) \u2014 connected clients resync automatically`);
|
|
8867
|
-
if (options.includeFiles) await copyFiles(cfg, token, route2, doFetch, out);
|
|
8868
|
-
if (options.json) out.log(JSON.stringify(res.body, null, 2));
|
|
8869
|
-
return { ok: true, plan, result: res.body };
|
|
8870
|
-
}
|
|
8871
|
-
async function copyFiles(cfg, token, route2, doFetch, out) {
|
|
8872
|
-
for (let attempt = 1; attempt <= 20; attempt++) {
|
|
8873
|
-
const res = await api(
|
|
8874
|
-
cfg,
|
|
8875
|
-
token,
|
|
8876
|
-
`/admin/apps/${encodeURIComponent(route2.target)}/copy-files`,
|
|
8877
|
-
{ method: "POST", body: JSON.stringify({ from: route2.from }) },
|
|
8878
|
-
doFetch
|
|
8879
|
-
);
|
|
8880
|
-
if (res.status === 200) {
|
|
8881
|
-
out.log(`${route2.target}: files copied`);
|
|
8882
|
-
return;
|
|
8883
|
-
}
|
|
8884
|
-
if (!res.body.error?.retry) {
|
|
8885
|
-
throw new Error(`file copy failed${res.body.error?.code ? ` (${res.body.error.code})` : ""}: ${res.body.error?.message ?? res.status}`);
|
|
8886
|
-
}
|
|
8887
|
-
out.log(` files: bounded at attempt ${attempt}, resuming\u2026`);
|
|
8888
|
-
}
|
|
8889
|
-
throw new Error("file copy did not finish within 20 rounds \u2014 re-run to continue where it left off");
|
|
8640
|
+
bothTenants(cfg);
|
|
8641
|
+
return requireStudioHuman(options.configPath, `app ${options.verb}`, "database");
|
|
8890
8642
|
}
|
|
8891
8643
|
|
|
8892
8644
|
// src/app-lifecycle.ts
|
|
8893
|
-
async function lifecycleCall(action2, options) {
|
|
8894
|
-
const cfg = await loadProjectConfig(options.configPath);
|
|
8895
|
-
const out = options.stdout ?? console;
|
|
8896
|
-
const doFetch = options.fetch ?? fetch;
|
|
8897
|
-
const token = await getDeveloperToken(
|
|
8898
|
-
cfg,
|
|
8899
|
-
{ configPath: cfg.configPath, token: options.token, email: options.email, open: false },
|
|
8900
|
-
doFetch,
|
|
8901
|
-
out
|
|
8902
|
-
);
|
|
8903
|
-
const res = await doFetch(`${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/${action2}`, {
|
|
8904
|
-
method: "POST",
|
|
8905
|
-
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" }
|
|
8906
|
-
});
|
|
8907
|
-
const body = await res.json().catch(() => ({}));
|
|
8908
|
-
if (!res.ok || !body.ok) {
|
|
8909
|
-
throw new Error(`${action2} failed${body.error?.code ? ` (${body.error.code})` : ""}: ${body.error?.message ?? `registry returned ${res.status}`}`);
|
|
8910
|
-
}
|
|
8911
|
-
return body;
|
|
8912
|
-
}
|
|
8913
8645
|
async function appArchive(options) {
|
|
8914
8646
|
if (options.yes !== true) {
|
|
8915
8647
|
throw new Error(
|
|
8916
8648
|
"app archive suspends EVERY environment: API keys stop working and all services refuse requests until restored. All data is retained. Pass --yes to proceed."
|
|
8917
8649
|
);
|
|
8918
8650
|
}
|
|
8919
|
-
|
|
8920
|
-
const body = await lifecycleCall("archive", options);
|
|
8921
|
-
if (options.json) out.log(JSON.stringify(body, null, 2));
|
|
8922
|
-
else if (body.operation?.state === "noop") out.log(`${body.app?.appId}: already archived`);
|
|
8923
|
-
else out.log(`${body.app?.appId}: archived \u2014 data retained; run \`odla-ai app restore\` (or use Studio) to bring it back`);
|
|
8651
|
+
await requireStudioHuman(options.configPath, "app archive", "app");
|
|
8924
8652
|
}
|
|
8925
8653
|
async function appRestore(options) {
|
|
8926
|
-
|
|
8927
|
-
const body = await lifecycleCall("restore", options);
|
|
8928
|
-
if (options.json) out.log(JSON.stringify(body, null, 2));
|
|
8929
|
-
else if (body.operation?.state === "noop") out.log(`${body.app?.appId}: already active`);
|
|
8930
|
-
else out.log(`${body.app?.appId}: restored \u2014 every service's data plane is live again`);
|
|
8654
|
+
await requireStudioHuman(options.configPath, "app restore", "app");
|
|
8931
8655
|
}
|
|
8932
8656
|
async function appCommand(parsed, dependencies = {}) {
|
|
8933
8657
|
const sub = parsed.positionals[1];
|
|
@@ -9563,18 +9287,17 @@ Usage:
|
|
|
9563
9287
|
odla-ai calendar status [--env dev] [--email <odla-account>] [--json]
|
|
9564
9288
|
odla-ai calendar calendars [--env dev] [--email <odla-account>] [--json]
|
|
9565
9289
|
odla-ai calendar connect [--env dev] [--email <odla-account>] [--no-open] [--yes]
|
|
9566
|
-
odla-ai calendar disconnect [--env dev] [
|
|
9567
|
-
odla-ai app archive [--config odla.config.mjs] [
|
|
9568
|
-
odla-ai app restore [--config odla.config.mjs]
|
|
9569
|
-
odla-ai app export [--env dev]
|
|
9290
|
+
odla-ai calendar disconnect [--env dev] --yes [continue in Studio; human session required]
|
|
9291
|
+
odla-ai app archive [--config odla.config.mjs] --yes [continue in Studio; human session required]
|
|
9292
|
+
odla-ai app restore [--config odla.config.mjs] [continue in Studio; human session required]
|
|
9293
|
+
odla-ai app export [--env dev] [continue in Studio; human session required]
|
|
9570
9294
|
odla-ai app import <file|-> [--env dev] [--ns <namespace>] [--id-field <f>|--key <attr>|--generate-ids] [--dry-run] [--json] --yes
|
|
9571
|
-
|
|
9572
|
-
odla-ai app
|
|
9573
|
-
odla-ai app
|
|
9574
|
-
odla-ai app
|
|
9575
|
-
odla-ai app
|
|
9576
|
-
odla-ai app owners add
|
|
9577
|
-
odla-ai app owners remove <email> [--email <odla-account>] [--json]
|
|
9295
|
+
[dry-run is local; writes continue in Studio]
|
|
9296
|
+
odla-ai app refresh-sandbox [continue in Studio; human session required]
|
|
9297
|
+
odla-ai app go-live [continue in Studio; human session required]
|
|
9298
|
+
odla-ai app promote [continue in Studio; human session required]
|
|
9299
|
+
odla-ai app rename <name> [continue in Studio; human session required]
|
|
9300
|
+
odla-ai app owners <list|add|remove> [...] [continue in Studio; human session required]
|
|
9578
9301
|
odla-ai brand design unpack <bundle.html|-> [--out <dir>] [--json]
|
|
9579
9302
|
odla-ai pm goal list [--app <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
9580
9303
|
odla-ai pm task list [--app <id>] [--column <backlog|ready|doing|review|done>] [--goal <id>] [--assignee <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
@@ -9609,8 +9332,8 @@ Usage:
|
|
|
9609
9332
|
odla-ai discuss resolve <topic> [--reopen] [--mutation-id <id>]
|
|
9610
9333
|
odla-ai discuss who --q <text> [--app <id>] [--kinds user,pm:task] [--json]
|
|
9611
9334
|
odla-ai discuss watch [<topic>] [--cursor <cursor>] [--by <authorId>] [--self <authorId>] [--interval <s>] [--timeout <s>] [--json|--jsonl]
|
|
9612
|
-
odla-ai agent jobs [--env dev] [--state pending|running|succeeded|dead_letter] [--limit 50] [--
|
|
9613
|
-
odla-ai agent retry <job-id> [--env dev] [--
|
|
9335
|
+
odla-ai agent jobs [--env dev] [--state pending|running|succeeded|dead_letter] [--limit 50] [--token <ODLA_API_KEY>] [--json]
|
|
9336
|
+
odla-ai agent retry <job-id> [--env dev] [--token <ODLA_API_KEY>] [--json]
|
|
9614
9337
|
odla-ai context show [--context <name>] [--platform https://odla.ai] [--app <id>] [--env prod] [--json]
|
|
9615
9338
|
odla-ai context list [--json]
|
|
9616
9339
|
odla-ai context save <name> [--platform <url>] [--app <id>] [--env <name>] [--json]
|
|
@@ -9646,8 +9369,8 @@ Usage:
|
|
|
9646
9369
|
odla-ai admin ai credential set <provider> [--context <name>] (--from-env <NAME>|--stdin)
|
|
9647
9370
|
odla-ai admin ai usage [--context <name>] [--app-id <id>] [--env <env>] [--run-id <id>] [--limit <1-500>] [--json]
|
|
9648
9371
|
odla-ai admin ai audit [--context <name>] [--limit <1-200>] [--json]
|
|
9649
|
-
odla-ai security github connect [--repo owner/name] [--env dev]
|
|
9650
|
-
odla-ai security github disconnect --source <id> [--env dev]
|
|
9372
|
+
odla-ai security github connect [--repo owner/name] [--env dev] [continue in Studio; human session required]
|
|
9373
|
+
odla-ai security github disconnect --source <id> [--env dev] [continue in Studio; human session required]
|
|
9651
9374
|
odla-ai security plan [--env dev] [--json]
|
|
9652
9375
|
odla-ai security sources [--env dev] [--json]
|
|
9653
9376
|
odla-ai security run --source <id> --plan-digest <sha256:...> --ack-redacted-source [--ref <branch|tag|sha>] [--env dev] [--no-follow]
|
|
@@ -9655,7 +9378,7 @@ Usage:
|
|
|
9655
9378
|
odla-ai security report <job-id> [--json]
|
|
9656
9379
|
odla-ai security run [target] --ack-redacted-source [--env dev] [--profile odla] [--fail-on high]
|
|
9657
9380
|
odla-ai security run [target] --self --ack-redacted-source
|
|
9658
|
-
odla-ai provision [--config odla.config.mjs] [--email <odla-account>] [--wait <seconds>] [--dry-run] [--push-secrets] [--rotate-o11y-token] [--write-dev-vars[=path]] [--yes]
|
|
9381
|
+
odla-ai provision [--config odla.config.mjs] [--email <odla-account>] [--request-grant] [--wait <seconds>] [--dry-run] [--push-secrets] [--rotate-o11y-token] [--write-dev-vars[=path]] [--yes]
|
|
9659
9382
|
odla-ai smoke [--config odla.config.mjs] [--env dev] [--email <odla-account>] [--no-open]
|
|
9660
9383
|
odla-ai skill install [--dir <project>] [--agent <name>] [--global] [--force]
|
|
9661
9384
|
odla-ai secrets push --env <env> [--config odla.config.mjs] [--dry-run] [--yes]
|
|
@@ -9790,6 +9513,10 @@ Safety:
|
|
|
9790
9513
|
The email is a non-secret identity hint: never provide a password or session
|
|
9791
9514
|
token. The matching account must already exist, be signed in, explicitly
|
|
9792
9515
|
review the exact code, and finish any current request before claiming another.
|
|
9516
|
+
If provision reports that the current agent principal has no live app.manage
|
|
9517
|
+
grant, run it once with --request-grant. That flag ignores ODLA_DEV_TOKEN and
|
|
9518
|
+
the local cache, prints and opens a fresh exact-project owner-review URL, then
|
|
9519
|
+
continues provisioning with the approved replacement credential.
|
|
9793
9520
|
Run Code from a GitHub checkout already connected to an app in Studio; an
|
|
9794
9521
|
odla.config.mjs may select the app explicitly but is not required. Code host
|
|
9795
9522
|
approval and credential hashes live in odla-ai/db. The host
|
|
@@ -10158,7 +9885,7 @@ async function discussWatch(ctx, topicId, parsed) {
|
|
|
10158
9885
|
throw new WatchRemoteError(cursor, error);
|
|
10159
9886
|
}
|
|
10160
9887
|
if (deadline !== void 0 && now() >= deadline) {
|
|
10161
|
-
const result =
|
|
9888
|
+
const result = report(ctx, parsed, { found: false, cursor: cursor ?? "" });
|
|
10162
9889
|
throw new WatchTimeoutError(result.cursor);
|
|
10163
9890
|
}
|
|
10164
9891
|
const base = Math.min(intervalMs, 1e3);
|
|
@@ -10199,7 +9926,7 @@ async function discussWatch(ctx, topicId, parsed) {
|
|
|
10199
9926
|
});
|
|
10200
9927
|
const posts = topicId ? matching.filter((event) => event.type === "message").map((event) => event.payload) : void 0;
|
|
10201
9928
|
const topics = topicId ? void 0 : matching.filter((event) => event.type === "activity").map((event) => event.payload);
|
|
10202
|
-
return
|
|
9929
|
+
return report(ctx, parsed, {
|
|
10203
9930
|
found: true,
|
|
10204
9931
|
cursor,
|
|
10205
9932
|
events: matching,
|
|
@@ -10226,13 +9953,13 @@ async function discussWatch(ctx, topicId, parsed) {
|
|
|
10226
9953
|
}
|
|
10227
9954
|
if (page2.hasMore) continue;
|
|
10228
9955
|
if (deadline !== void 0 && now() >= deadline) {
|
|
10229
|
-
return
|
|
9956
|
+
return report(ctx, parsed, { found: false, cursor });
|
|
10230
9957
|
}
|
|
10231
9958
|
const wait2 = deadline === void 0 ? intervalMs : Math.min(intervalMs, Math.max(0, deadline - now()));
|
|
10232
9959
|
await sleep(wait2);
|
|
10233
9960
|
}
|
|
10234
9961
|
}
|
|
10235
|
-
function
|
|
9962
|
+
function report(ctx, parsed, result) {
|
|
10236
9963
|
if (ctx.json) {
|
|
10237
9964
|
ctx.out.log(JSON.stringify(result, null, 2));
|
|
10238
9965
|
} else if (parsed.options.jsonl !== true && result.found) {
|
|
@@ -10733,7 +10460,7 @@ function eventLabel(event) {
|
|
|
10733
10460
|
const body = event.payload.message?.body?.replace(/\s+/g, " ").trim();
|
|
10734
10461
|
return body || event.payload.entityId;
|
|
10735
10462
|
}
|
|
10736
|
-
function
|
|
10463
|
+
function report2(ctx, parsed, result) {
|
|
10737
10464
|
if (ctx.json) ctx.out.log(JSON.stringify(result, null, 2));
|
|
10738
10465
|
else if (parsed.options.jsonl !== true && result.found) {
|
|
10739
10466
|
for (const event of result.events ?? []) {
|
|
@@ -10793,7 +10520,7 @@ async function pmWatch(ctx, parsed) {
|
|
|
10793
10520
|
});
|
|
10794
10521
|
if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES2) throw error;
|
|
10795
10522
|
if (deadline !== void 0 && now() >= deadline) {
|
|
10796
|
-
return
|
|
10523
|
+
return report2(ctx, parsed, { found: false, cursor: cursor ?? "" });
|
|
10797
10524
|
}
|
|
10798
10525
|
const backoff = Math.min(
|
|
10799
10526
|
MAX_BACKOFF_MS2,
|
|
@@ -10834,7 +10561,7 @@ async function pmWatch(ctx, parsed) {
|
|
|
10834
10561
|
cursor,
|
|
10835
10562
|
serverTime: current.serverTime
|
|
10836
10563
|
});
|
|
10837
|
-
return
|
|
10564
|
+
return report2(ctx, parsed, { found: true, cursor, events: matching });
|
|
10838
10565
|
}
|
|
10839
10566
|
if (current.events.length > 0) {
|
|
10840
10567
|
jsonl2(ctx, parsed, {
|
|
@@ -10853,7 +10580,7 @@ async function pmWatch(ctx, parsed) {
|
|
|
10853
10580
|
}
|
|
10854
10581
|
if (current.hasMore) continue;
|
|
10855
10582
|
if (deadline !== void 0 && now() >= deadline) {
|
|
10856
|
-
return
|
|
10583
|
+
return report2(ctx, parsed, { found: false, cursor });
|
|
10857
10584
|
}
|
|
10858
10585
|
await sleep(
|
|
10859
10586
|
deadline === void 0 ? intervalMs : Math.min(intervalMs, Math.max(0, deadline - now()))
|
|
@@ -12044,7 +11771,7 @@ async function assessImpact(ctx, surfaces, all, limit) {
|
|
|
12044
11771
|
return out;
|
|
12045
11772
|
}
|
|
12046
11773
|
var editHint = (slug, appId) => `odla-ai runbook edit ${slug}${appId === PLATFORM_SCOPE ? "" : ` --app ${appId}`} --note "<what changed>"`;
|
|
12047
|
-
function
|
|
11774
|
+
function report3(ctx, impacts) {
|
|
12048
11775
|
const covered = impacts.filter((i) => i.runbooks.length);
|
|
12049
11776
|
ctx.out.log(
|
|
12050
11777
|
`${impacts.length} changed surface${impacts.length === 1 ? "" : "s"}; ${covered.length} covered by a runbook. Reread each one and fix any step this change made wrong.`
|
|
@@ -12082,7 +11809,7 @@ async function runbookImpact(ctx, options, deps = {}) {
|
|
|
12082
11809
|
}
|
|
12083
11810
|
const impacts = await assessImpact(ctx, surfaces, options.all, options.limit ?? 4);
|
|
12084
11811
|
if (ctx.json) return ctx.out.log(JSON.stringify({ base: options.base, impacts }, null, 2));
|
|
12085
|
-
|
|
11812
|
+
report3(ctx, impacts);
|
|
12086
11813
|
}
|
|
12087
11814
|
|
|
12088
11815
|
// src/runbook-lint.ts
|
|
@@ -12582,7 +12309,6 @@ async function runbookCommand(parsed, deps = {}) {
|
|
|
12582
12309
|
}
|
|
12583
12310
|
|
|
12584
12311
|
// src/security-command-context.ts
|
|
12585
|
-
import { createInterface } from "readline/promises";
|
|
12586
12312
|
async function hostedSecurityContext(parsed, dependencies) {
|
|
12587
12313
|
const configPath = stringOpt(parsed.options.config) ?? "odla.config.mjs";
|
|
12588
12314
|
const cfg = await loadProjectConfig(configPath);
|
|
@@ -12601,21 +12327,11 @@ async function hostedSecurityContext(parsed, dependencies) {
|
|
|
12601
12327
|
cfg,
|
|
12602
12328
|
{ configPath, email: stringOpt(parsed.options.email), open, openApprovalUrl: dependencies.openUrl },
|
|
12603
12329
|
doFetch,
|
|
12604
|
-
stdout
|
|
12330
|
+
stdout,
|
|
12331
|
+
{ optionalProjectCapabilities: ["app.manage"] }
|
|
12605
12332
|
);
|
|
12606
12333
|
return { platform, token, appId: cfg.app.id, env, fetch: doFetch, stdout };
|
|
12607
12334
|
}
|
|
12608
|
-
async function interactiveConfirmation(message2, dependencies) {
|
|
12609
|
-
if (dependencies.confirm) return dependencies.confirm(message2);
|
|
12610
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
|
|
12611
|
-
const prompt = createInterface({ input: process.stdin, output: process.stdout });
|
|
12612
|
-
try {
|
|
12613
|
-
const answer = await prompt.question(`${message2} [y/N] `);
|
|
12614
|
-
return /^y(?:es)?$/i.test(answer.trim());
|
|
12615
|
-
} finally {
|
|
12616
|
-
prompt.close();
|
|
12617
|
-
}
|
|
12618
|
-
}
|
|
12619
12335
|
function requiredSecurityPositional(parsed, index, label) {
|
|
12620
12336
|
const value2 = parsed.positionals[index];
|
|
12621
12337
|
if (!value2) throw new Error(`${label} is required`);
|
|
@@ -12681,31 +12397,31 @@ function printHostedJob(out, job, platform, appId) {
|
|
|
12681
12397
|
url.searchParams.set("job", job.jobId);
|
|
12682
12398
|
out.log(` Studio: ${url.toString()}`);
|
|
12683
12399
|
}
|
|
12684
|
-
function printHostedReport(out,
|
|
12685
|
-
out.log(`security report ${
|
|
12686
|
-
out.log(` coverage: ${
|
|
12687
|
-
out.log(` findings: confirmed=${
|
|
12688
|
-
out.log(` discovery: ${
|
|
12689
|
-
out.log(` validation: ${
|
|
12690
|
-
for (const finding of
|
|
12400
|
+
function printHostedReport(out, report4) {
|
|
12401
|
+
out.log(`security report ${report4.jobId}: ${report4.repository}@${report4.revision}`);
|
|
12402
|
+
out.log(` coverage: ${report4.coverageStatus} cells=${report4.metrics.coverageCells} shallow=${report4.metrics.shallowCells} blocked=${report4.metrics.blockedCells} unscheduled=${report4.metrics.unscheduledCells} budget_exhausted=${report4.metrics.budgetExhaustedCells}`);
|
|
12403
|
+
out.log(` findings: confirmed=${report4.metrics.confirmed} needs_reproduction=${report4.metrics.needsReproduction} candidates=${report4.metrics.candidates} rejected=${report4.metrics.rejected}`);
|
|
12404
|
+
out.log(` discovery: ${report4.provenance.discovery?.provider ?? "unknown"}/${report4.provenance.discovery?.model ?? "unknown"}`);
|
|
12405
|
+
out.log(` validation: ${report4.provenance.validation?.provider ?? "unknown"}/${report4.provenance.validation?.model ?? "unknown"} independent=${String(report4.provenance.independentValidation)}`);
|
|
12406
|
+
for (const finding of report4.findings) {
|
|
12691
12407
|
const location = finding.locations[0];
|
|
12692
12408
|
out.log(` [${finding.severity}] ${finding.title}${location ? ` (${location.path}:${location.line})` : ""} \xB7 ${finding.disposition}`);
|
|
12693
12409
|
}
|
|
12694
|
-
for (const limitation of
|
|
12410
|
+
for (const limitation of report4.limitations) out.log(` limitation: ${limitation}`);
|
|
12695
12411
|
}
|
|
12696
|
-
function enforceHostedReportGate(
|
|
12412
|
+
function enforceHostedReportGate(report4, parsed, out, emitSuccess) {
|
|
12697
12413
|
const failOn = hostedSeverity(stringOpt(parsed.options["fail-on"]) ?? "high", "--fail-on");
|
|
12698
12414
|
const candidateValue = parsed.options["fail-on-candidates"];
|
|
12699
12415
|
const failOnCandidates = candidateValue === false ? void 0 : hostedSeverity(stringOpt(candidateValue) ?? "critical", "--fail-on-candidates");
|
|
12700
12416
|
const atOrAbove = (severity, threshold) => HOSTED_SEVERITIES.indexOf(severity) >= HOSTED_SEVERITIES.indexOf(threshold);
|
|
12701
|
-
const confirmed =
|
|
12702
|
-
const leads = failOnCandidates ?
|
|
12703
|
-
const incomplete =
|
|
12417
|
+
const confirmed = report4.findings.filter((finding) => finding.disposition === "confirmed" && atOrAbove(finding.severity, failOn));
|
|
12418
|
+
const leads = failOnCandidates ? report4.findings.filter((finding) => finding.disposition !== "confirmed" && atOrAbove(finding.severity, failOnCandidates)) : [];
|
|
12419
|
+
const incomplete = report4.coverageStatus !== "complete" && parsed.options["allow-incomplete"] !== true;
|
|
12704
12420
|
if (confirmed.length || leads.length || incomplete) {
|
|
12705
|
-
throw new Error(`hosted security gate failed: ${confirmed.length} confirmed >= ${failOn}; ${leads.length} leads >= ${failOnCandidates ?? "disabled"}${incomplete ? `; coverage ${
|
|
12421
|
+
throw new Error(`hosted security gate failed: ${confirmed.length} confirmed >= ${failOn}; ${leads.length} leads >= ${failOnCandidates ?? "disabled"}${incomplete ? `; coverage ${report4.coverageStatus}` : ""}`);
|
|
12706
12422
|
}
|
|
12707
12423
|
if (emitSuccess) {
|
|
12708
|
-
out.log(`security gate passed: 0 confirmed >= ${failOn}; 0 leads >= ${failOnCandidates ?? "disabled"}; coverage ${
|
|
12424
|
+
out.log(`security gate passed: 0 confirmed >= ${failOn}; 0 leads >= ${failOnCandidates ?? "disabled"}; coverage ${report4.coverageStatus}. This is not proof that the application is secure.`);
|
|
12709
12425
|
}
|
|
12710
12426
|
}
|
|
12711
12427
|
function printHostedSecurityPlanRoute(out, label, route2) {
|
|
@@ -12809,13 +12525,13 @@ async function runSourceSecurityCommand(parsed, dependencies, sourceId) {
|
|
|
12809
12525
|
}
|
|
12810
12526
|
throw new Error(`hosted security job ${result.jobId} ended ${result.status}${result.errorCode ? `: ${result.errorCode}` : ""}`);
|
|
12811
12527
|
}
|
|
12812
|
-
const
|
|
12528
|
+
const report4 = await getHostedSecurityReport({ ...context, jobId: result.jobId });
|
|
12813
12529
|
if (parsed.options.json === true) {
|
|
12814
|
-
context.stdout.log(JSON.stringify({ plan, intent: preview.intent, job: result, report:
|
|
12530
|
+
context.stdout.log(JSON.stringify({ plan, intent: preview.intent, job: result, report: report4 }, null, 2));
|
|
12815
12531
|
} else {
|
|
12816
|
-
printHostedReport(context.stdout,
|
|
12532
|
+
printHostedReport(context.stdout, report4);
|
|
12817
12533
|
}
|
|
12818
|
-
enforceHostedReportGate(
|
|
12534
|
+
enforceHostedReportGate(report4, parsed, context.stdout, parsed.options.json !== true);
|
|
12819
12535
|
}
|
|
12820
12536
|
async function runLocalSecurityCommand(parsed, dependencies) {
|
|
12821
12537
|
if (parsed.options.source === true) {
|
|
@@ -12876,19 +12592,20 @@ async function runLocalSecurityCommand(parsed, dependencies) {
|
|
|
12876
12592
|
cfg,
|
|
12877
12593
|
{ configPath, email: stringOpt(parsed.options.email), open, openApprovalUrl: dependencies.openUrl },
|
|
12878
12594
|
doFetch,
|
|
12879
|
-
out
|
|
12595
|
+
out,
|
|
12596
|
+
{ optionalProjectCapabilities: ["app.manage"] }
|
|
12880
12597
|
);
|
|
12881
12598
|
}
|
|
12882
12599
|
});
|
|
12883
12600
|
enforceLocalGate(result.report, parsed);
|
|
12884
12601
|
}
|
|
12885
|
-
function enforceLocalGate(
|
|
12602
|
+
function enforceLocalGate(report4, parsed) {
|
|
12886
12603
|
const failOn = severityOpt(stringOpt(parsed.options["fail-on"]) ?? "high", "--fail-on");
|
|
12887
12604
|
const candidateValue = parsed.options["fail-on-candidates"];
|
|
12888
12605
|
const failOnCandidates = candidateValue === false ? void 0 : severityOpt(stringOpt(candidateValue) ?? "critical", "--fail-on-candidates");
|
|
12889
|
-
const confirmed = findingsAtOrAbove(
|
|
12890
|
-
const leads = failOnCandidates ? findingsAtOrAbove(
|
|
12891
|
-
const incomplete =
|
|
12606
|
+
const confirmed = findingsAtOrAbove(report4, failOn);
|
|
12607
|
+
const leads = failOnCandidates ? findingsAtOrAbove(report4, failOnCandidates, true).filter((finding) => finding.disposition !== "confirmed") : [];
|
|
12608
|
+
const incomplete = report4.coverageStatus === "incomplete" && parsed.options["allow-incomplete"] !== true;
|
|
12892
12609
|
if (confirmed.length || leads.length || incomplete) {
|
|
12893
12610
|
throw new Error(`hosted security gate failed: ${confirmed.length} confirmed >= ${failOn}; ${leads.length} leads >= ${failOnCandidates ?? "disabled"}${incomplete ? "; coverage incomplete" : ""}`);
|
|
12894
12611
|
}
|
|
@@ -12927,9 +12644,9 @@ async function securityCommand(parsed, dependencies) {
|
|
|
12927
12644
|
assertArgs(parsed, ["config", "env", "platform", "email", "open", "json"], 3);
|
|
12928
12645
|
const jobId = requiredSecurityPositional(parsed, 2, "job id");
|
|
12929
12646
|
const context = await hostedSecurityContext(parsed, dependencies);
|
|
12930
|
-
const
|
|
12931
|
-
if (parsed.options.json === true) context.stdout.log(JSON.stringify(
|
|
12932
|
-
else printHostedReport(context.stdout,
|
|
12647
|
+
const report4 = await getHostedSecurityReport({ ...context, jobId });
|
|
12648
|
+
if (parsed.options.json === true) context.stdout.log(JSON.stringify(report4, null, 2));
|
|
12649
|
+
else printHostedReport(context.stdout, report4);
|
|
12933
12650
|
return;
|
|
12934
12651
|
}
|
|
12935
12652
|
if (sub !== "run") {
|
|
@@ -12943,35 +12660,24 @@ async function githubSecurityCommand(parsed, dependencies) {
|
|
|
12943
12660
|
const action2 = parsed.positionals[2];
|
|
12944
12661
|
if (action2 === "disconnect") {
|
|
12945
12662
|
assertArgs(parsed, ["config", "env", "platform", "source", "email", "open", "yes"], 3);
|
|
12946
|
-
const context2 = await hostedSecurityContext(parsed, dependencies);
|
|
12947
12663
|
const sourceId = requiredString(parsed.options.source, "--source");
|
|
12948
|
-
|
|
12949
|
-
|
|
12950
|
-
|
|
12664
|
+
return requireStudioHuman(
|
|
12665
|
+
stringOpt(parsed.options.config) ?? "odla.config.mjs",
|
|
12666
|
+
`disconnecting GitHub security source ${sourceId}`,
|
|
12667
|
+
"security",
|
|
12668
|
+
stringOpt(parsed.options.env)
|
|
12951
12669
|
);
|
|
12952
|
-
if (!confirmed) {
|
|
12953
|
-
throw new Error("GitHub source disconnect cancelled; pass --yes in a non-interactive shell");
|
|
12954
|
-
}
|
|
12955
|
-
await disconnectGitHubSecuritySource({ ...context2, sourceId });
|
|
12956
|
-
context2.stdout.log(`github: disconnected ${sourceId} from ${context2.appId}/${context2.env}`);
|
|
12957
|
-
return;
|
|
12958
12670
|
}
|
|
12959
12671
|
if (action2 !== "connect") {
|
|
12960
12672
|
throw new Error('unknown security github command. Try "odla-ai security github connect".');
|
|
12961
12673
|
}
|
|
12962
12674
|
assertArgs(parsed, ["config", "env", "platform", "repo", "email", "open"], 3);
|
|
12963
|
-
|
|
12964
|
-
|
|
12965
|
-
|
|
12966
|
-
|
|
12967
|
-
|
|
12968
|
-
|
|
12969
|
-
openInstallUrl: dependencies.openUrl ?? openUrl,
|
|
12970
|
-
wait: dependencies.pollWait,
|
|
12971
|
-
stdout: context.stdout
|
|
12972
|
-
});
|
|
12973
|
-
context.stdout.log(`github: connected ${connection.repository ?? repository ?? "app repository"} (${connection.sourceId ?? "source pending"})`);
|
|
12974
|
-
context.stdout.log("github: odla.ai stores the installation; no PAT or GitHub token is written locally");
|
|
12675
|
+
await requireStudioHuman(
|
|
12676
|
+
stringOpt(parsed.options.config) ?? "odla.config.mjs",
|
|
12677
|
+
"connecting the GitHub security repository",
|
|
12678
|
+
"security",
|
|
12679
|
+
stringOpt(parsed.options.env)
|
|
12680
|
+
);
|
|
12975
12681
|
}
|
|
12976
12682
|
async function listSecuritySources(parsed, dependencies) {
|
|
12977
12683
|
assertArgs(parsed, ["config", "env", "platform", "email", "open", "json"], 2);
|
|
@@ -13112,6 +12818,7 @@ async function provisionCommand(parsed, dependencies) {
|
|
|
13112
12818
|
"write-credentials",
|
|
13113
12819
|
"write-dev-vars",
|
|
13114
12820
|
"token",
|
|
12821
|
+
"request-grant",
|
|
13115
12822
|
"email",
|
|
13116
12823
|
"open",
|
|
13117
12824
|
"wait",
|
|
@@ -13127,6 +12834,7 @@ async function provisionCommand(parsed, dependencies) {
|
|
|
13127
12834
|
writeCredentials: parsed.options["write-credentials"] !== false,
|
|
13128
12835
|
writeDevVars: typeof writeDevVars2 === "string" ? writeDevVars2 : writeDevVars2 === true,
|
|
13129
12836
|
token: stringOpt(parsed.options.token),
|
|
12837
|
+
requestGrant: parsed.options["request-grant"] === true,
|
|
13130
12838
|
email: stringOpt(parsed.options.email),
|
|
13131
12839
|
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
13132
12840
|
wait: numberOpt(parsed.options.wait, "--wait"),
|
|
@@ -13222,4 +12930,4 @@ export {
|
|
|
13222
12930
|
exitCodeFor,
|
|
13223
12931
|
runCli
|
|
13224
12932
|
};
|
|
13225
|
-
//# sourceMappingURL=chunk-
|
|
12933
|
+
//# sourceMappingURL=chunk-3YSCRXPF.js.map
|