@odla-ai/cli 0.25.12 → 0.25.17
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 +61 -7
- package/dist/bin.cjs +637 -104
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-MFC4TX6W.js → chunk-5MRY3V2K.js} +620 -87
- package/dist/chunk-5MRY3V2K.js.map +1 -0
- package/dist/index.cjs +637 -104
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/skills/odla-o11y-debug/SKILL.md +18 -10
- package/dist/chunk-MFC4TX6W.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -304,7 +304,7 @@ var import_node_process2 = __toESM(require("process"), 1);
|
|
|
304
304
|
async function openUrl(url, options = {}) {
|
|
305
305
|
const command = openerFor(options.platform ?? import_node_process2.default.platform);
|
|
306
306
|
const doSpawn = options.spawnImpl ?? import_node_child_process.spawn;
|
|
307
|
-
await new Promise((
|
|
307
|
+
await new Promise((resolve12, reject) => {
|
|
308
308
|
const child = doSpawn(command.cmd, [...command.args, url], {
|
|
309
309
|
stdio: "ignore",
|
|
310
310
|
detached: true
|
|
@@ -312,7 +312,7 @@ async function openUrl(url, options = {}) {
|
|
|
312
312
|
child.once("error", reject);
|
|
313
313
|
child.once("spawn", () => {
|
|
314
314
|
child.unref();
|
|
315
|
-
|
|
315
|
+
resolve12();
|
|
316
316
|
});
|
|
317
317
|
});
|
|
318
318
|
}
|
|
@@ -2246,8 +2246,8 @@ function credential(value) {
|
|
|
2246
2246
|
// src/calendar-poll.ts
|
|
2247
2247
|
async function waitForCalendarPoll(milliseconds, signal) {
|
|
2248
2248
|
if (signal?.aborted) throw signal.reason ?? new Error("calendar connection aborted");
|
|
2249
|
-
await new Promise((
|
|
2250
|
-
const timer = setTimeout(
|
|
2249
|
+
await new Promise((resolve12, reject) => {
|
|
2250
|
+
const timer = setTimeout(resolve12, milliseconds);
|
|
2251
2251
|
signal?.addEventListener("abort", () => {
|
|
2252
2252
|
clearTimeout(timer);
|
|
2253
2253
|
reject(signal.reason ?? new Error("calendar connection aborted"));
|
|
@@ -2448,7 +2448,8 @@ var CAPABILITIES = {
|
|
|
2448
2448
|
"compose declared app-capability schema/rules, create guarded seeds when absent, and configure platform AI, auth, and deployment links",
|
|
2449
2449
|
"apply Google Calendar booking config, then drive state-bound consent, status, discovery, and disconnect flows (bookings run live through the platform proxy)",
|
|
2450
2450
|
"validate integration contracts offline and smoke-test a provisioned db environment plus anonymous capability routes",
|
|
2451
|
-
"
|
|
2451
|
+
"save and explicitly select non-secret named operator contexts with isolated credential caches; resolve and explain platform, app, environment, and credential provenance without authenticating; then run PM, Discussions, o11y, runbook, and identity operations outside a project checkout",
|
|
2452
|
+
"read one versioned o11y status envelope spanning application RED, exact Worker versions and Cloudflare colos observed in traffic, current live-sync freshness/load, the protected commit-to-visible canary, collector ingest/scheduler trust, provider-owned runtime metrics, account-scoped Durable Object, D1, and R2 evidence under odla-db, and a bounded machine verdict",
|
|
2452
2453
|
"inspect durable agent wakeups as a versioned JSON envelope and explicitly requeue one dead-lettered job with a scoped environment credential",
|
|
2453
2454
|
"run app-attributed hosted security discovery and independent validation without provider keys",
|
|
2454
2455
|
"connect/revoke source-read-only GitHub sources and drive commit-pinned hosted security jobs without PATs or provider keys",
|
|
@@ -5476,21 +5477,21 @@ async function verifyCodeCandidate(input) {
|
|
|
5476
5477
|
const recipes = [];
|
|
5477
5478
|
const logs = [];
|
|
5478
5479
|
for (const recipe2 of policy.recipes) {
|
|
5479
|
-
const
|
|
5480
|
+
const clean4 = await stageWorkspace(staged.workspaceDir, limits);
|
|
5480
5481
|
try {
|
|
5481
|
-
if (await digestStagedWorkspace(
|
|
5482
|
+
if (await digestStagedWorkspace(clean4.workspaceDir, limits) !== sourceDigest) {
|
|
5482
5483
|
throw new TypeError("clean verifier source changed before execution");
|
|
5483
5484
|
}
|
|
5484
5485
|
const result = checkedResult(await input.recipeExecutor.run({
|
|
5485
|
-
workspaceDir:
|
|
5486
|
+
workspaceDir: clean4.workspaceDir,
|
|
5486
5487
|
recipe: recipe2,
|
|
5487
5488
|
signal: input.signal
|
|
5488
5489
|
}), recipe2.maxOutputBytes);
|
|
5489
|
-
const artifacts = await inspectArtifacts(
|
|
5490
|
+
const artifacts = await inspectArtifacts(clean4.workspaceDir, recipe2);
|
|
5490
5491
|
recipes.push(recipeReceipt(recipe2, result, artifacts));
|
|
5491
5492
|
logs.push({ recipeId: recipe2.id, ...boundedLogs(result, recipe2.maxOutputBytes) });
|
|
5492
5493
|
} finally {
|
|
5493
|
-
await
|
|
5494
|
+
await clean4.cleanup();
|
|
5494
5495
|
}
|
|
5495
5496
|
}
|
|
5496
5497
|
const fields = {
|
|
@@ -6631,8 +6632,12 @@ Usage:
|
|
|
6631
6632
|
odla-ai discuss watch [<topic>] [--cursor <cursor>] [--by <authorId>] [--self <authorId>] [--interval <s>] [--timeout <s>] [--json|--jsonl]
|
|
6632
6633
|
odla-ai agent jobs [--env dev] [--state pending|running|succeeded|dead_letter] [--limit 50] [--email <email>] [--json]
|
|
6633
6634
|
odla-ai agent retry <job-id> [--env dev] [--email <email>] [--json]
|
|
6634
|
-
odla-ai
|
|
6635
|
-
odla-ai
|
|
6635
|
+
odla-ai context show [--context <name>] [--platform https://odla.ai] [--app <id>] [--env prod] [--json]
|
|
6636
|
+
odla-ai context list [--json]
|
|
6637
|
+
odla-ai context save <name> [--platform <url>] [--app <id>] [--env <name>] [--json]
|
|
6638
|
+
odla-ai context remove <name> --yes [--json]
|
|
6639
|
+
odla-ai o11y status [--app <id>] [--context <name>] [--platform https://odla.ai] [--env prod] [--minutes 60] [--json]
|
|
6640
|
+
odla-ai whoami [--context <name>] [--platform https://odla.ai] [--json]
|
|
6636
6641
|
odla-ai runbook ask "<question>" [--app <id>] [--all] [--json]
|
|
6637
6642
|
odla-ai runbook search "<question>" [--app <id>] [--all] [--limit <n>] [--json]
|
|
6638
6643
|
odla-ai runbook impact [--base origin/main] [--app <id>] [--all] [--limit <n>] [--json]
|
|
@@ -6697,6 +6702,11 @@ Commands:
|
|
|
6697
6702
|
whoami Report who this terminal is authenticated as, and whether it holds
|
|
6698
6703
|
platform admin. A handshake-minted device token is never admin,
|
|
6699
6704
|
however the human who approved it is configured.
|
|
6705
|
+
context Explain selected config, platform, app, environment, and
|
|
6706
|
+
developer-token provenance without printing credentials or
|
|
6707
|
+
starting a device handshake. Operator work can run outside a
|
|
6708
|
+
project checkout with explicit flags or ODLA_* environment
|
|
6709
|
+
variables.
|
|
6700
6710
|
setup Install offline odla runbooks for common coding-agent harnesses.
|
|
6701
6711
|
init Create a generic odla.config.mjs plus starter schema/rules files.
|
|
6702
6712
|
doctor Validate and summarize the project config without network calls.
|
|
@@ -6768,6 +6778,15 @@ Safety:
|
|
|
6768
6778
|
Provision caches the approved developer token and service credentials under
|
|
6769
6779
|
.odla/ with mode 0600, and init adds those paths to .gitignore. Secret push
|
|
6770
6780
|
preflights Wrangler before any shown-once issuance or destructive rotation.
|
|
6781
|
+
Projectless PM, Discussions, o11y, runbook, and identity commands use
|
|
6782
|
+
--platform/--app/--env, ODLA_PLATFORM_URL/ODLA_APP_ID/ODLA_ENV, and
|
|
6783
|
+
ODLA_DEV_TOKEN. Save non-secret scope metadata with "context save", then
|
|
6784
|
+
select it explicitly with --context or ODLA_CONTEXT. Each named context gets
|
|
6785
|
+
isolated developer and scoped-token caches. Override their locations with
|
|
6786
|
+
ODLA_DEV_TOKEN_FILE and ODLA_ADMIN_TOKEN_FILE; ODLA_CONTEXT_FILE relocates
|
|
6787
|
+
the metadata file. Flags and specific ODLA_* scope variables beat a selected
|
|
6788
|
+
context, which beats project config. There is no ambient current context.
|
|
6789
|
+
"context show" reports only provenance and cache state and never authenticates.
|
|
6771
6790
|
Provision opens the approval page in your browser automatically whenever the
|
|
6772
6791
|
machine can show one, including agent-driven runs; only CI, SSH, and
|
|
6773
6792
|
display-less hosts skip it. Use --open to force or --no-open to suppress.
|
|
@@ -6885,8 +6904,8 @@ function hostedPollTimeout(value = 10 * 6e4) {
|
|
|
6885
6904
|
}
|
|
6886
6905
|
async function waitForHostedPoll(milliseconds, signal) {
|
|
6887
6906
|
if (signal?.aborted) throw signal.reason ?? new DOMException("aborted", "AbortError");
|
|
6888
|
-
await new Promise((
|
|
6889
|
-
const timer = setTimeout(
|
|
6907
|
+
await new Promise((resolve12, reject) => {
|
|
6908
|
+
const timer = setTimeout(resolve12, milliseconds);
|
|
6890
6909
|
signal?.addEventListener("abort", () => {
|
|
6891
6910
|
clearTimeout(timer);
|
|
6892
6911
|
reject(signal.reason ?? new DOMException("aborted", "AbortError"));
|
|
@@ -7396,6 +7415,384 @@ async function codeCommand(parsed, dependencies) {
|
|
|
7396
7415
|
});
|
|
7397
7416
|
}
|
|
7398
7417
|
|
|
7418
|
+
// src/operator-credentials.ts
|
|
7419
|
+
var import_node_process7 = __toESM(require("process"), 1);
|
|
7420
|
+
function developerTokenStatus(context, parsed, now = Date.now()) {
|
|
7421
|
+
const cached = readJsonFile(context.cfg.local.tokenFile);
|
|
7422
|
+
const cacheStatus = !cached?.token ? "missing" : cached.platform !== context.platform.value ? "other-platform" : (cached.expiresAt ?? 0) <= now + 6e4 ? "expired" : "valid";
|
|
7423
|
+
const source = clean(
|
|
7424
|
+
stringOpt(parsed.options.token)
|
|
7425
|
+
) ? "flag" : clean(import_node_process7.default.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
|
|
7426
|
+
return {
|
|
7427
|
+
source,
|
|
7428
|
+
cacheFile: context.cfg.local.tokenFile,
|
|
7429
|
+
cacheStatus
|
|
7430
|
+
};
|
|
7431
|
+
}
|
|
7432
|
+
function clean(value) {
|
|
7433
|
+
const normalized = value?.trim();
|
|
7434
|
+
return normalized || void 0;
|
|
7435
|
+
}
|
|
7436
|
+
|
|
7437
|
+
// src/operator-context.ts
|
|
7438
|
+
var import_node_fs14 = require("fs");
|
|
7439
|
+
var import_node_path12 = require("path");
|
|
7440
|
+
var import_node_process9 = __toESM(require("process"), 1);
|
|
7441
|
+
|
|
7442
|
+
// src/operator-profiles.ts
|
|
7443
|
+
var import_node_fs13 = require("fs");
|
|
7444
|
+
var import_node_os4 = require("os");
|
|
7445
|
+
var import_node_path11 = require("path");
|
|
7446
|
+
var import_node_process8 = __toESM(require("process"), 1);
|
|
7447
|
+
function operatorProfileFile() {
|
|
7448
|
+
return (0, import_node_path11.resolve)(
|
|
7449
|
+
clean2(import_node_process8.default.env.ODLA_CONTEXT_FILE) ?? (0, import_node_path11.join)((0, import_node_os4.homedir)(), ".odla", "contexts.json")
|
|
7450
|
+
);
|
|
7451
|
+
}
|
|
7452
|
+
function resolveOperatorProfile(parsed) {
|
|
7453
|
+
const fromFlag = clean2(stringOpt(parsed.options.context));
|
|
7454
|
+
const fromEnvironment = clean2(import_node_process8.default.env.ODLA_CONTEXT);
|
|
7455
|
+
const name = fromFlag ?? fromEnvironment ?? null;
|
|
7456
|
+
const file = operatorProfileFile();
|
|
7457
|
+
if (!name) {
|
|
7458
|
+
return { name: null, source: "unresolved", file, value: null };
|
|
7459
|
+
}
|
|
7460
|
+
assertOperatorName(name, "context");
|
|
7461
|
+
const profiles = readOperatorProfiles(file);
|
|
7462
|
+
const value = Object.hasOwn(profiles, name) ? profiles[name] : void 0;
|
|
7463
|
+
if (!value) {
|
|
7464
|
+
throw new Error(
|
|
7465
|
+
`operator context "${name}" not found in ${file}; run "odla-ai context list" or save it first`
|
|
7466
|
+
);
|
|
7467
|
+
}
|
|
7468
|
+
return {
|
|
7469
|
+
name,
|
|
7470
|
+
source: fromFlag ? "flag" : "environment",
|
|
7471
|
+
file,
|
|
7472
|
+
value
|
|
7473
|
+
};
|
|
7474
|
+
}
|
|
7475
|
+
function listOperatorProfiles(file = operatorProfileFile()) {
|
|
7476
|
+
return Object.entries(readOperatorProfiles(file)).sort(([a], [b]) => a.localeCompare(b)).map(([name, profile]) => ({ name, ...profile }));
|
|
7477
|
+
}
|
|
7478
|
+
function saveOperatorProfile(name, profile, file = operatorProfileFile()) {
|
|
7479
|
+
assertOperatorName(name, "context");
|
|
7480
|
+
const normalized = validateProfile(profile, `operator context "${name}"`);
|
|
7481
|
+
const profiles = readOperatorProfiles(file);
|
|
7482
|
+
profiles[name] = normalized;
|
|
7483
|
+
writePrivateJson(file, { schemaVersion: 1, profiles });
|
|
7484
|
+
}
|
|
7485
|
+
function removeOperatorProfile(name, file = operatorProfileFile()) {
|
|
7486
|
+
assertOperatorName(name, "context");
|
|
7487
|
+
const profiles = readOperatorProfiles(file);
|
|
7488
|
+
if (!Object.hasOwn(profiles, name)) return false;
|
|
7489
|
+
delete profiles[name];
|
|
7490
|
+
writePrivateJson(file, { schemaVersion: 1, profiles });
|
|
7491
|
+
return true;
|
|
7492
|
+
}
|
|
7493
|
+
function operatorCredentialFiles(selection) {
|
|
7494
|
+
const base = selection.name ? (0, import_node_path11.join)((0, import_node_path11.dirname)(selection.file), "profiles", selection.name) : (0, import_node_path11.join)((0, import_node_os4.homedir)(), ".odla");
|
|
7495
|
+
return {
|
|
7496
|
+
developer: (0, import_node_path11.join)(base, "dev-token.json"),
|
|
7497
|
+
scoped: (0, import_node_path11.join)(base, "admin-token.local.json")
|
|
7498
|
+
};
|
|
7499
|
+
}
|
|
7500
|
+
function assertOperatorName(value, label) {
|
|
7501
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(value)) {
|
|
7502
|
+
throw new Error(
|
|
7503
|
+
`${label} must contain lowercase letters, numbers, and hyphens`
|
|
7504
|
+
);
|
|
7505
|
+
}
|
|
7506
|
+
}
|
|
7507
|
+
function readOperatorProfiles(file) {
|
|
7508
|
+
if (!(0, import_node_fs13.existsSync)(file)) return emptyProfiles();
|
|
7509
|
+
let raw;
|
|
7510
|
+
try {
|
|
7511
|
+
raw = JSON.parse((0, import_node_fs13.readFileSync)(file, "utf8"));
|
|
7512
|
+
} catch {
|
|
7513
|
+
throw new Error(`operator context file ${file} is not valid JSON`);
|
|
7514
|
+
}
|
|
7515
|
+
if (!raw || typeof raw !== "object" || raw.schemaVersion !== 1 || !raw.profiles || typeof raw.profiles !== "object" || Array.isArray(raw.profiles)) {
|
|
7516
|
+
throw new Error(`operator context file ${file} has an invalid shape`);
|
|
7517
|
+
}
|
|
7518
|
+
const unknown = Object.keys(raw).filter(
|
|
7519
|
+
(key) => !["schemaVersion", "profiles"].includes(key)
|
|
7520
|
+
);
|
|
7521
|
+
if (unknown.length > 0) {
|
|
7522
|
+
throw new Error(
|
|
7523
|
+
`operator context file ${file} contains unsupported field(s): ${unknown.sort().join(", ")}`
|
|
7524
|
+
);
|
|
7525
|
+
}
|
|
7526
|
+
const profiles = emptyProfiles();
|
|
7527
|
+
for (const [name, value] of Object.entries(
|
|
7528
|
+
raw.profiles
|
|
7529
|
+
)) {
|
|
7530
|
+
assertOperatorName(name, "context");
|
|
7531
|
+
profiles[name] = validateProfile(value, `operator context "${name}"`);
|
|
7532
|
+
}
|
|
7533
|
+
return profiles;
|
|
7534
|
+
}
|
|
7535
|
+
function emptyProfiles() {
|
|
7536
|
+
return /* @__PURE__ */ Object.create(null);
|
|
7537
|
+
}
|
|
7538
|
+
function validateProfile(value, label) {
|
|
7539
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
7540
|
+
throw new Error(`${label} has an invalid shape`);
|
|
7541
|
+
}
|
|
7542
|
+
const unknown = Object.keys(value).filter(
|
|
7543
|
+
(key) => !["platform", "app", "environment"].includes(key)
|
|
7544
|
+
);
|
|
7545
|
+
if (unknown.length > 0) {
|
|
7546
|
+
throw new Error(
|
|
7547
|
+
`${label} contains unsupported field(s): ${unknown.sort().join(", ")}; contexts store scope metadata only, never credentials`
|
|
7548
|
+
);
|
|
7549
|
+
}
|
|
7550
|
+
const candidate = value;
|
|
7551
|
+
if (typeof candidate.platform !== "string") {
|
|
7552
|
+
throw new Error(`${label} needs an absolute platform URL`);
|
|
7553
|
+
}
|
|
7554
|
+
const platform = platformAudience(candidate.platform);
|
|
7555
|
+
const app = clean2(candidate.app);
|
|
7556
|
+
const environment2 = clean2(candidate.environment);
|
|
7557
|
+
if (app) assertOperatorName(app, "app");
|
|
7558
|
+
if (environment2) assertOperatorName(environment2, "environment");
|
|
7559
|
+
return {
|
|
7560
|
+
platform,
|
|
7561
|
+
...app ? { app } : {},
|
|
7562
|
+
...environment2 ? { environment: environment2 } : {}
|
|
7563
|
+
};
|
|
7564
|
+
}
|
|
7565
|
+
function clean2(value) {
|
|
7566
|
+
const normalized = value?.trim();
|
|
7567
|
+
return normalized || void 0;
|
|
7568
|
+
}
|
|
7569
|
+
|
|
7570
|
+
// src/operator-context.ts
|
|
7571
|
+
var DEFAULT_PLATFORM2 = "https://odla.ai";
|
|
7572
|
+
async function resolveOperatorContext(parsed, options = {}) {
|
|
7573
|
+
const profile = resolveOperatorProfile(parsed);
|
|
7574
|
+
const configArgument = stringOpt(parsed.options.config) ?? "odla.config.mjs";
|
|
7575
|
+
const configPath = (0, import_node_path12.resolve)(configArgument);
|
|
7576
|
+
const explicitConfig = parsed.options.config !== void 0;
|
|
7577
|
+
const hasConfig = (0, import_node_fs14.existsSync)(configPath);
|
|
7578
|
+
if (!hasConfig && (!options.allowMissingConfig || explicitConfig)) {
|
|
7579
|
+
await loadProjectConfig(configArgument);
|
|
7580
|
+
}
|
|
7581
|
+
const loaded = hasConfig ? await loadProjectConfig(configArgument) : void 0;
|
|
7582
|
+
const platformFlag = clean3(stringOpt(parsed.options.platform));
|
|
7583
|
+
const platformEnvironment = clean3(import_node_process9.default.env.ODLA_PLATFORM_URL);
|
|
7584
|
+
const platformValue = platformAudience(
|
|
7585
|
+
platformFlag ?? platformEnvironment ?? profile.value?.platform ?? loaded?.platformUrl ?? DEFAULT_PLATFORM2
|
|
7586
|
+
);
|
|
7587
|
+
const platformSource = platformFlag ? "flag" : platformEnvironment ? "environment" : profile.value ? "profile" : loaded ? "config" : "default";
|
|
7588
|
+
const appFlag = clean3(stringOpt(parsed.options.app));
|
|
7589
|
+
const appEnvironment = clean3(import_node_process9.default.env.ODLA_APP_ID);
|
|
7590
|
+
const appValue = appFlag ?? appEnvironment ?? profile.value?.app ?? loaded?.app.id ?? null;
|
|
7591
|
+
const appSource = appFlag ? "flag" : appEnvironment ? "environment" : profile.value?.app ? "profile" : loaded ? "config" : "unresolved";
|
|
7592
|
+
if (appValue) assertOperatorName(appValue, "app");
|
|
7593
|
+
if (options.requireApp && !appValue) {
|
|
7594
|
+
throw new Error(
|
|
7595
|
+
"app context is unresolved; pass --app <id>, set ODLA_APP_ID, select --context <name>, or run inside a project with odla.config.mjs"
|
|
7596
|
+
);
|
|
7597
|
+
}
|
|
7598
|
+
const envFlag = clean3(stringOpt(parsed.options.env));
|
|
7599
|
+
const envEnvironment = clean3(import_node_process9.default.env.ODLA_ENV);
|
|
7600
|
+
const environmentValue = envFlag ?? envEnvironment ?? profile.value?.environment ?? options.defaultEnvironment ?? null;
|
|
7601
|
+
const environmentSource = envFlag ? "flag" : envEnvironment ? "environment" : profile.value?.environment ? "profile" : options.defaultEnvironment ? "default" : "unresolved";
|
|
7602
|
+
if (environmentValue) {
|
|
7603
|
+
assertOperatorName(environmentValue, "environment");
|
|
7604
|
+
}
|
|
7605
|
+
const rootDir = loaded?.rootDir ?? import_node_process9.default.cwd();
|
|
7606
|
+
const profileCredentials = operatorCredentialFiles(profile);
|
|
7607
|
+
const tokenFile = clean3(import_node_process9.default.env.ODLA_DEV_TOKEN_FILE) ? (0, import_node_path12.resolve)(import_node_process9.default.env.ODLA_DEV_TOKEN_FILE) : profile.name ? profileCredentials.developer : loaded?.local.tokenFile ?? profileCredentials.developer;
|
|
7608
|
+
const scopedTokenFile = clean3(import_node_process9.default.env.ODLA_ADMIN_TOKEN_FILE) ? (0, import_node_path12.resolve)(import_node_process9.default.env.ODLA_ADMIN_TOKEN_FILE) : profile.name ? profileCredentials.scoped : loaded ? (0, import_node_path12.join)(loaded.rootDir, ".odla", "admin-token.local.json") : profileCredentials.scoped;
|
|
7609
|
+
const cfg = loaded ? {
|
|
7610
|
+
...loaded,
|
|
7611
|
+
platformUrl: platformValue,
|
|
7612
|
+
app: appValue ? {
|
|
7613
|
+
id: appValue,
|
|
7614
|
+
name: appValue === loaded.app.id ? loaded.app.name : appValue
|
|
7615
|
+
} : loaded.app,
|
|
7616
|
+
local: { ...loaded.local, tokenFile }
|
|
7617
|
+
} : {
|
|
7618
|
+
configPath,
|
|
7619
|
+
rootDir,
|
|
7620
|
+
platformUrl: platformValue,
|
|
7621
|
+
dbEndpoint: platformValue,
|
|
7622
|
+
app: {
|
|
7623
|
+
id: appValue ?? "operator",
|
|
7624
|
+
name: appValue ?? "Operator"
|
|
7625
|
+
},
|
|
7626
|
+
envs: environmentValue ? [environmentValue] : [],
|
|
7627
|
+
services: [],
|
|
7628
|
+
local: {
|
|
7629
|
+
tokenFile,
|
|
7630
|
+
credentialsFile: (0, import_node_path12.join)(rootDir, ".odla", "credentials.local.json"),
|
|
7631
|
+
devVarsFile: (0, import_node_path12.join)(rootDir, ".dev.vars"),
|
|
7632
|
+
gitignore: true
|
|
7633
|
+
}
|
|
7634
|
+
};
|
|
7635
|
+
return {
|
|
7636
|
+
cfg,
|
|
7637
|
+
profile: {
|
|
7638
|
+
name: profile.name,
|
|
7639
|
+
source: profile.source,
|
|
7640
|
+
file: profile.file
|
|
7641
|
+
},
|
|
7642
|
+
config: {
|
|
7643
|
+
path: configPath,
|
|
7644
|
+
status: loaded ? "loaded" : "absent",
|
|
7645
|
+
explicit: explicitConfig
|
|
7646
|
+
},
|
|
7647
|
+
platform: { value: platformValue, source: platformSource },
|
|
7648
|
+
app: { value: appValue, source: appSource },
|
|
7649
|
+
environment: {
|
|
7650
|
+
value: environmentValue,
|
|
7651
|
+
source: environmentSource
|
|
7652
|
+
},
|
|
7653
|
+
credentials: {
|
|
7654
|
+
developerTokenFile: tokenFile,
|
|
7655
|
+
scopedTokenFile
|
|
7656
|
+
}
|
|
7657
|
+
};
|
|
7658
|
+
}
|
|
7659
|
+
function clean3(value) {
|
|
7660
|
+
const normalized = value?.trim();
|
|
7661
|
+
return normalized || void 0;
|
|
7662
|
+
}
|
|
7663
|
+
|
|
7664
|
+
// src/context-command.ts
|
|
7665
|
+
async function contextCommand(parsed, deps = {}) {
|
|
7666
|
+
assertArgs(
|
|
7667
|
+
parsed,
|
|
7668
|
+
[
|
|
7669
|
+
"config",
|
|
7670
|
+
"context",
|
|
7671
|
+
"platform",
|
|
7672
|
+
"app",
|
|
7673
|
+
"env",
|
|
7674
|
+
"token",
|
|
7675
|
+
"json",
|
|
7676
|
+
"yes"
|
|
7677
|
+
],
|
|
7678
|
+
3
|
|
7679
|
+
);
|
|
7680
|
+
const action = parsed.positionals[1];
|
|
7681
|
+
const out = deps.stdout ?? console;
|
|
7682
|
+
if (action === "list") {
|
|
7683
|
+
const profiles = listOperatorProfiles();
|
|
7684
|
+
if (parsed.options.json === true) {
|
|
7685
|
+
out.log(JSON.stringify({ schemaVersion: 1, profiles }, null, 2));
|
|
7686
|
+
return;
|
|
7687
|
+
}
|
|
7688
|
+
if (profiles.length === 0) {
|
|
7689
|
+
out.log("No named operator contexts.");
|
|
7690
|
+
return;
|
|
7691
|
+
}
|
|
7692
|
+
out.log("name platform app environment");
|
|
7693
|
+
for (const profile of profiles) {
|
|
7694
|
+
out.log(
|
|
7695
|
+
`${profile.name} ${profile.platform} ${profile.app ?? ""} ${profile.environment ?? ""}`
|
|
7696
|
+
);
|
|
7697
|
+
}
|
|
7698
|
+
return;
|
|
7699
|
+
}
|
|
7700
|
+
if (action === "save") {
|
|
7701
|
+
if (parsed.options.token !== void 0) {
|
|
7702
|
+
throw new Error(
|
|
7703
|
+
"context save does not accept --token; contexts store scope metadata only"
|
|
7704
|
+
);
|
|
7705
|
+
}
|
|
7706
|
+
const name = requireName(parsed);
|
|
7707
|
+
const context2 = await resolveOperatorContext(parsed, {
|
|
7708
|
+
allowMissingConfig: true
|
|
7709
|
+
});
|
|
7710
|
+
const profile = {
|
|
7711
|
+
platform: context2.platform.value,
|
|
7712
|
+
...context2.app.value ? { app: context2.app.value } : {},
|
|
7713
|
+
...context2.environment.value ? { environment: context2.environment.value } : {}
|
|
7714
|
+
};
|
|
7715
|
+
saveOperatorProfile(name, profile, context2.profile.file);
|
|
7716
|
+
if (parsed.options.json === true) {
|
|
7717
|
+
out.log(JSON.stringify({ schemaVersion: 1, name, ...profile }, null, 2));
|
|
7718
|
+
} else {
|
|
7719
|
+
out.log(`saved operator context "${name}" (${profile.platform})`);
|
|
7720
|
+
}
|
|
7721
|
+
return;
|
|
7722
|
+
}
|
|
7723
|
+
if (action === "remove") {
|
|
7724
|
+
const name = requireName(parsed);
|
|
7725
|
+
if (parsed.options.yes !== true) {
|
|
7726
|
+
throw new Error(`context remove "${name}" requires --yes`);
|
|
7727
|
+
}
|
|
7728
|
+
const removed = removeOperatorProfile(name);
|
|
7729
|
+
const result2 = {
|
|
7730
|
+
schemaVersion: 1,
|
|
7731
|
+
name,
|
|
7732
|
+
removed,
|
|
7733
|
+
credentialCachesRemoved: false,
|
|
7734
|
+
grantsRevoked: false
|
|
7735
|
+
};
|
|
7736
|
+
if (parsed.options.json === true) out.log(JSON.stringify(result2, null, 2));
|
|
7737
|
+
else {
|
|
7738
|
+
const status = removed ? `removed operator context "${name}"` : `operator context "${name}" was already absent`;
|
|
7739
|
+
out.log(`${status}; credential caches were not deleted or revoked`);
|
|
7740
|
+
}
|
|
7741
|
+
return;
|
|
7742
|
+
}
|
|
7743
|
+
if (action !== "show") {
|
|
7744
|
+
throw new Error(
|
|
7745
|
+
`unknown context action "${action ?? ""}". Try show|list|save|remove.`
|
|
7746
|
+
);
|
|
7747
|
+
}
|
|
7748
|
+
const context = await resolveOperatorContext(parsed, {
|
|
7749
|
+
allowMissingConfig: true,
|
|
7750
|
+
defaultEnvironment: "prod"
|
|
7751
|
+
});
|
|
7752
|
+
const token = developerTokenStatus(context, parsed);
|
|
7753
|
+
const result = {
|
|
7754
|
+
schemaVersion: 1,
|
|
7755
|
+
profile: context.profile,
|
|
7756
|
+
config: context.config,
|
|
7757
|
+
platform: context.platform,
|
|
7758
|
+
app: context.app,
|
|
7759
|
+
environment: context.environment,
|
|
7760
|
+
authentication: {
|
|
7761
|
+
developerToken: token,
|
|
7762
|
+
scopedTokenCacheFile: context.credentials.scopedTokenFile,
|
|
7763
|
+
handshakeStarted: false
|
|
7764
|
+
}
|
|
7765
|
+
};
|
|
7766
|
+
if (parsed.options.json === true) {
|
|
7767
|
+
out.log(JSON.stringify(result, null, 2));
|
|
7768
|
+
return;
|
|
7769
|
+
}
|
|
7770
|
+
out.log(`config: ${context.config.status} (${context.config.path})`);
|
|
7771
|
+
out.log(
|
|
7772
|
+
`context: ${context.profile.name ?? "(none)"} (${context.profile.source}; ${context.profile.file})`
|
|
7773
|
+
);
|
|
7774
|
+
out.log(
|
|
7775
|
+
`platform: ${context.platform.value} (${context.platform.source})`
|
|
7776
|
+
);
|
|
7777
|
+
out.log(
|
|
7778
|
+
`app: ${context.app.value ?? "(unresolved)"} (${context.app.source})`
|
|
7779
|
+
);
|
|
7780
|
+
out.log(
|
|
7781
|
+
`env: ${context.environment.value ?? "(unresolved)"} (${context.environment.source})`
|
|
7782
|
+
);
|
|
7783
|
+
out.log(
|
|
7784
|
+
`auth: ${token.source} (cache ${token.cacheStatus}: ${token.cacheFile})`
|
|
7785
|
+
);
|
|
7786
|
+
out.log("handshake: not started");
|
|
7787
|
+
}
|
|
7788
|
+
function requireName(parsed) {
|
|
7789
|
+
const name = parsed.positionals[2]?.trim();
|
|
7790
|
+
if (!name) {
|
|
7791
|
+
throw new Error(`context ${parsed.positionals[1]} needs a profile name`);
|
|
7792
|
+
}
|
|
7793
|
+
return name;
|
|
7794
|
+
}
|
|
7795
|
+
|
|
7399
7796
|
// src/discuss-actions.ts
|
|
7400
7797
|
var writeMutationId = (parsed) => stringOpt(parsed.options["mutation-id"]) ?? crypto.randomUUID();
|
|
7401
7798
|
async function request(ctx, method, path, body) {
|
|
@@ -7447,8 +7844,9 @@ async function discussGroups(ctx) {
|
|
|
7447
7844
|
}
|
|
7448
7845
|
async function discussList(ctx, parsed) {
|
|
7449
7846
|
const query = new URLSearchParams();
|
|
7847
|
+
const app = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
7848
|
+
if (app) query.set("app", app);
|
|
7450
7849
|
for (const [flag, param] of [
|
|
7451
|
-
["app", "app"],
|
|
7452
7850
|
["q", "q"],
|
|
7453
7851
|
["state", "state"],
|
|
7454
7852
|
["limit", "limit"],
|
|
@@ -7524,7 +7922,7 @@ function renderRead(ctx, topic, posts) {
|
|
|
7524
7922
|
}
|
|
7525
7923
|
}
|
|
7526
7924
|
async function discussPost(ctx, parsed) {
|
|
7527
|
-
const appId = stringOpt(parsed.options.app);
|
|
7925
|
+
const appId = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
7528
7926
|
if (!appId) throw new Error("discuss post needs --app <appId>");
|
|
7529
7927
|
const subject = stringOpt(parsed.options.subject);
|
|
7530
7928
|
if (!subject) throw new Error('discuss post needs --subject "\u2026"');
|
|
@@ -7556,7 +7954,7 @@ async function discussResolve(ctx, id, resolved, parsed) {
|
|
|
7556
7954
|
}
|
|
7557
7955
|
async function discussWho(ctx, parsed) {
|
|
7558
7956
|
const query = new URLSearchParams({ q: stringOpt(parsed.options.q) ?? "" });
|
|
7559
|
-
const app = stringOpt(parsed.options.app);
|
|
7957
|
+
const app = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
7560
7958
|
if (app) query.set("app", app);
|
|
7561
7959
|
const kinds = stringOpt(parsed.options.kinds);
|
|
7562
7960
|
if (kinds) query.set("kinds", kinds);
|
|
@@ -7660,14 +8058,14 @@ function jsonl(ctx, parsed, value) {
|
|
|
7660
8058
|
}
|
|
7661
8059
|
async function discussWatch(ctx, topicId, parsed) {
|
|
7662
8060
|
if (ctx.json && parsed.options.jsonl === true) throw new Error("--json and --jsonl cannot be combined");
|
|
7663
|
-
const sleep = ctx.sleep ?? ((ms) => new Promise((
|
|
8061
|
+
const sleep = ctx.sleep ?? ((ms) => new Promise((resolve12) => setTimeout(resolve12, ms)));
|
|
7664
8062
|
const now = ctx.now ?? Date.now;
|
|
7665
8063
|
const intervalMs = (numberOpt2(parsed, "interval", DEFAULT_INTERVAL_MS / 1e3) ?? DEFAULT_INTERVAL_MS / 1e3) * 1e3;
|
|
7666
8064
|
const timeoutSeconds = numberOpt2(parsed, "timeout");
|
|
7667
8065
|
const deadline = timeoutSeconds === void 0 ? void 0 : now() + timeoutSeconds * 1e3;
|
|
7668
8066
|
const by = stringOpt(parsed.options.by);
|
|
7669
8067
|
const self = stringOpt(parsed.options.self);
|
|
7670
|
-
const app = stringOpt(parsed.options.app);
|
|
8068
|
+
const app = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
7671
8069
|
let cursor = stringOpt(parsed.options.cursor);
|
|
7672
8070
|
let firstSuccess = true;
|
|
7673
8071
|
let consecutiveFailures = 0;
|
|
@@ -7812,14 +8210,19 @@ var ALLOWED = [
|
|
|
7812
8210
|
"timeout",
|
|
7813
8211
|
"cursor",
|
|
7814
8212
|
"jsonl",
|
|
7815
|
-
"mutation-id"
|
|
8213
|
+
"mutation-id",
|
|
8214
|
+
"platform",
|
|
8215
|
+
"context"
|
|
7816
8216
|
];
|
|
7817
8217
|
function requireId(id, action) {
|
|
7818
8218
|
if (!id) throw new Error(`"discuss ${action}" needs a topic id`);
|
|
7819
8219
|
return id;
|
|
7820
8220
|
}
|
|
7821
8221
|
async function buildContext(parsed, deps) {
|
|
7822
|
-
const
|
|
8222
|
+
const context = await resolveOperatorContext(parsed, {
|
|
8223
|
+
allowMissingConfig: true
|
|
8224
|
+
});
|
|
8225
|
+
const { cfg } = context;
|
|
7823
8226
|
const doFetch = deps.fetch ?? fetch;
|
|
7824
8227
|
const out = deps.stdout ?? console;
|
|
7825
8228
|
const token = await getDeveloperToken(
|
|
@@ -7833,7 +8236,16 @@ async function buildContext(parsed, deps) {
|
|
|
7833
8236
|
doFetch,
|
|
7834
8237
|
out
|
|
7835
8238
|
);
|
|
7836
|
-
return {
|
|
8239
|
+
return {
|
|
8240
|
+
platformUrl: cfg.platformUrl,
|
|
8241
|
+
token,
|
|
8242
|
+
doFetch,
|
|
8243
|
+
out,
|
|
8244
|
+
json: parsed.options.json === true,
|
|
8245
|
+
// Preserve Discussions' existing cross-project default for config-backed
|
|
8246
|
+
// use while allowing remote agents to scope via ODLA_APP_ID or a profile.
|
|
8247
|
+
appId: context.app.source === "environment" || context.app.source === "profile" ? context.app.value ?? void 0 : void 0
|
|
8248
|
+
};
|
|
7837
8249
|
}
|
|
7838
8250
|
async function discussCommand(parsed, deps = {}) {
|
|
7839
8251
|
assertArgs(parsed, ALLOWED, 3);
|
|
@@ -7945,7 +8357,7 @@ async function pmList(ctx, entity, parsed) {
|
|
|
7945
8357
|
assignee: "assigneeId",
|
|
7946
8358
|
decision: "decisionId"
|
|
7947
8359
|
};
|
|
7948
|
-
const app = stringOpt(parsed.options.app);
|
|
8360
|
+
const app = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
7949
8361
|
if (app) q.set("app", app);
|
|
7950
8362
|
for (const [flag, param] of Object.entries(filters)) {
|
|
7951
8363
|
const v = stringOpt(parsed.options[flag]);
|
|
@@ -7964,7 +8376,7 @@ async function pmList(ctx, entity, parsed) {
|
|
|
7964
8376
|
});
|
|
7965
8377
|
}
|
|
7966
8378
|
async function pmAdd(ctx, entity, parsed) {
|
|
7967
|
-
const appId = stringOpt(parsed.options.app);
|
|
8379
|
+
const appId = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
7968
8380
|
if (!appId) throw new Error("pm add needs --app <appId>");
|
|
7969
8381
|
const input = collectEntityFields(entity, parsed, false);
|
|
7970
8382
|
if (!input.title) throw new Error("pm add needs --title <title>");
|
|
@@ -8015,7 +8427,7 @@ async function allRecords(ctx, entity, appId) {
|
|
|
8015
8427
|
}
|
|
8016
8428
|
}
|
|
8017
8429
|
async function pmHandoff(ctx, parsed) {
|
|
8018
|
-
const appId = stringOpt(parsed.options.app);
|
|
8430
|
+
const appId = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
8019
8431
|
if (!appId) throw new Error("pm handoff needs --app <appId>");
|
|
8020
8432
|
const [goals, tasks, bugs] = await Promise.all([
|
|
8021
8433
|
allRecords(ctx, "goal", appId),
|
|
@@ -8110,14 +8522,19 @@ var ALLOWED2 = [
|
|
|
8110
8522
|
"limit",
|
|
8111
8523
|
"offset",
|
|
8112
8524
|
"q",
|
|
8113
|
-
"mutation-id"
|
|
8525
|
+
"mutation-id",
|
|
8526
|
+
"platform",
|
|
8527
|
+
"context"
|
|
8114
8528
|
];
|
|
8115
8529
|
function requireId2(id, action) {
|
|
8116
8530
|
if (!id) throw new Error(`"pm ... ${action}" needs an item id`);
|
|
8117
8531
|
return id;
|
|
8118
8532
|
}
|
|
8119
8533
|
async function buildContext2(parsed, deps) {
|
|
8120
|
-
const
|
|
8534
|
+
const context = await resolveOperatorContext(parsed, {
|
|
8535
|
+
allowMissingConfig: true
|
|
8536
|
+
});
|
|
8537
|
+
const { cfg } = context;
|
|
8121
8538
|
const doFetch = deps.fetch ?? fetch;
|
|
8122
8539
|
const out = deps.stdout ?? console;
|
|
8123
8540
|
const token = await getDeveloperToken(
|
|
@@ -8126,7 +8543,16 @@ async function buildContext2(parsed, deps) {
|
|
|
8126
8543
|
doFetch,
|
|
8127
8544
|
out
|
|
8128
8545
|
);
|
|
8129
|
-
return {
|
|
8546
|
+
return {
|
|
8547
|
+
platformUrl: cfg.platformUrl,
|
|
8548
|
+
token,
|
|
8549
|
+
doFetch,
|
|
8550
|
+
out,
|
|
8551
|
+
json: parsed.options.json === true,
|
|
8552
|
+
// Preserve PM's existing cross-project default when a config is present;
|
|
8553
|
+
// only an explicit remote-agent environment or profile selects an app.
|
|
8554
|
+
appId: context.app.source === "environment" || context.app.source === "profile" ? context.app.value ?? void 0 : void 0
|
|
8555
|
+
};
|
|
8130
8556
|
}
|
|
8131
8557
|
async function pmCommand(parsed, deps = {}) {
|
|
8132
8558
|
const word = parsed.positionals[1] ?? "";
|
|
@@ -8175,6 +8601,20 @@ function statusVerdict(reads) {
|
|
|
8175
8601
|
sourceHttp(reasons, "canary", reads.canary);
|
|
8176
8602
|
sourceHttp(reasons, "collector", reads.collector);
|
|
8177
8603
|
sourceHttp(reasons, "provider", reads.provider);
|
|
8604
|
+
if (reads.providerCapacity) {
|
|
8605
|
+
sourceHttp(
|
|
8606
|
+
reasons,
|
|
8607
|
+
"provider",
|
|
8608
|
+
reads.providerCapacity,
|
|
8609
|
+
"provider_capacity_"
|
|
8610
|
+
);
|
|
8611
|
+
}
|
|
8612
|
+
sourceHttp(
|
|
8613
|
+
reasons,
|
|
8614
|
+
"provider",
|
|
8615
|
+
reads.providerReconciliation,
|
|
8616
|
+
"provider_reconciliation_"
|
|
8617
|
+
);
|
|
8178
8618
|
sourceHttp(reasons, "provider", reads.providerHistory, "provider_history_");
|
|
8179
8619
|
const liveStatus = String(reads.liveSync.body.status ?? "");
|
|
8180
8620
|
if (liveStatus === "partial") {
|
|
@@ -8247,6 +8687,17 @@ function statusVerdict(reads) {
|
|
|
8247
8687
|
severity: "degraded"
|
|
8248
8688
|
});
|
|
8249
8689
|
}
|
|
8690
|
+
const reconciliationStatus = String(
|
|
8691
|
+
reads.providerReconciliation.body.status ?? ""
|
|
8692
|
+
);
|
|
8693
|
+
if (reads.providerReconciliation.httpStatus >= 200 && reads.providerReconciliation.httpStatus < 300 && reconciliationStatus && reconciliationStatus !== "within_budget") {
|
|
8694
|
+
const reconciliationReason = reads.providerReconciliation.body.reason;
|
|
8695
|
+
reasons.push({
|
|
8696
|
+
source: "provider",
|
|
8697
|
+
code: reconciliationStatus === "outside_budget" ? typeof reconciliationReason === "string" && reconciliationReason ? reconciliationReason : "request_coverage_outside_budget" : `request_reconciliation_${reconciliationStatus}`,
|
|
8698
|
+
severity: "degraded"
|
|
8699
|
+
});
|
|
8700
|
+
}
|
|
8250
8701
|
const providerHistoryStatus = String(reads.providerHistory.body.status ?? "");
|
|
8251
8702
|
if (providerHistoryStatus && providerHistoryStatus !== "observed" && providerHistoryStatus !== "no_data") {
|
|
8252
8703
|
reasons.push({
|
|
@@ -8317,8 +8768,19 @@ function printO11yStatus(status, out) {
|
|
|
8317
8768
|
`collector ${status.collector.httpStatus} ${String(status.collector.body.status ?? status.collector.body.error ?? "unavailable")} ${numeric3(collectorStorage.affectedPoints)} affected points`
|
|
8318
8769
|
);
|
|
8319
8770
|
const providerMetrics = record6(status.provider.body.metrics) ? status.provider.body.metrics : {};
|
|
8771
|
+
const providerCapacity = record6(status.provider.body.capacity) ? status.provider.body.capacity : {};
|
|
8772
|
+
const workerMemory = record6(providerCapacity.memory) ? providerCapacity.memory : {};
|
|
8773
|
+
out.log(
|
|
8774
|
+
`cloudflare ${status.provider.httpStatus} ${String(status.provider.body.status ?? status.provider.body.error ?? "unavailable")} ${numeric3(providerMetrics.requests)} invocations ${numeric3(providerMetrics.errors)} runtime errors ${optionalBytes(workerMemory.headroomBytes)} isolate memory headroom`
|
|
8775
|
+
);
|
|
8776
|
+
for (const line of providerCapacityLines(status.providerCapacity)) {
|
|
8777
|
+
out.log(line);
|
|
8778
|
+
}
|
|
8779
|
+
const coverage = record6(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
|
|
8780
|
+
const coverageCounts = record6(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
|
|
8781
|
+
const coverageBudget = record6(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
|
|
8320
8782
|
out.log(
|
|
8321
|
-
`
|
|
8783
|
+
`request-coverage ${status.providerReconciliation.httpStatus} ${String(status.providerReconciliation.body.status ?? status.providerReconciliation.body.error ?? "unavailable")} ${optionalPercent(coverage.applicationCoverage)} application/provider ${numeric3(coverageCounts.applicationRequests)}/${numeric3(coverageCounts.providerRequests)} requests \xB1${optionalPercent(coverageBudget.maxRelativeError)} budget`
|
|
8322
8784
|
);
|
|
8323
8785
|
const providerPoints = Array.isArray(status.providerHistory.body.points) ? status.providerHistory.body.points.length : 0;
|
|
8324
8786
|
const providerFreshness = record6(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
|
|
@@ -8329,6 +8791,27 @@ function printO11yStatus(status, out) {
|
|
|
8329
8791
|
`verdict ${status.verdict.status} ${status.verdict.reasons.map((reason) => `${reason.source}:${reason.code}`).join(", ") || "all required signals healthy"}`
|
|
8330
8792
|
);
|
|
8331
8793
|
}
|
|
8794
|
+
function providerCapacityLines(read3) {
|
|
8795
|
+
const resources = record6(read3.body.resources) ? read3.body.resources : {};
|
|
8796
|
+
const durableObjects = record6(resources.durableObjects) ? resources.durableObjects : {};
|
|
8797
|
+
const periodic = record6(durableObjects.periodic) ? durableObjects.periodic : {};
|
|
8798
|
+
const storage = record6(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
|
|
8799
|
+
const d1 = record6(resources.d1) ? resources.d1 : {};
|
|
8800
|
+
const d1Activity = record6(d1.activity) ? d1.activity : {};
|
|
8801
|
+
const d1Storage = record6(d1.storage) ? d1.storage : {};
|
|
8802
|
+
const d1Latency = record6(d1Activity.latency) ? d1Activity.latency : {};
|
|
8803
|
+
const r2 = record6(resources.r2) ? resources.r2 : {};
|
|
8804
|
+
const r2Operations = record6(r2.operations) ? r2.operations : {};
|
|
8805
|
+
const r2Storage = record6(r2.storage) ? r2.storage : {};
|
|
8806
|
+
const status = String(
|
|
8807
|
+
read3.body.status ?? read3.body.error ?? "unavailable"
|
|
8808
|
+
);
|
|
8809
|
+
return [
|
|
8810
|
+
`cloudflare-capacity ${read3.httpStatus} ${status} ${optionalBytes(storage.storedBytes)} SQLite DO storage ${optionalCount(periodic.activeWebsocketConnectionsMax, "active socket max")} ${optionalAgeSeconds(storage.ageSeconds)} storage evidence age`,
|
|
8811
|
+
`cloudflare-d1 ${read3.httpStatus} ${status} ${optionalBytes(d1Storage.databaseSizeBytes)} storage ${numeric3(d1Activity.readQueries) + numeric3(d1Activity.writeQueries)} queries ${numeric3(d1Activity.rowsRead)}/${numeric3(d1Activity.rowsWritten)} rows read/written ${optionalNumeric(d1Latency.worstDatabaseHourP99Ms)} worst database-hour p99 ${optionalAgeSeconds(d1Activity.ageSeconds)} activity evidence age`,
|
|
8812
|
+
`cloudflare-r2 ${read3.httpStatus} ${status} ${optionalBytes(r2Storage.storedBytes)} storage ${numeric3(r2Storage.objectCount)} objects ${numeric3(r2Operations.requests)} operations ${numeric3(r2Operations.userErrorRequests) + numeric3(r2Operations.internalErrorRequests)} provider errors ${optionalAgeSeconds(r2Operations.ageSeconds)} operation evidence age`
|
|
8813
|
+
];
|
|
8814
|
+
}
|
|
8332
8815
|
function liveSyncLine(read3) {
|
|
8333
8816
|
const performance = record6(read3.body.performance) ? read3.body.performance : {};
|
|
8334
8817
|
const commitToSend = record6(performance.commitToSend) ? performance.commitToSend : {};
|
|
@@ -8347,12 +8830,42 @@ function optionalAge(value) {
|
|
|
8347
8830
|
if (typeof value !== "number" || !Number.isFinite(value)) return "unknown";
|
|
8348
8831
|
return `${Math.max(0, Math.round(value / 1e3))}s`;
|
|
8349
8832
|
}
|
|
8833
|
+
function optionalPercent(value) {
|
|
8834
|
+
return typeof value === "number" && Number.isFinite(value) ? `${Math.round(value * 100)}%` : "\u2014";
|
|
8835
|
+
}
|
|
8836
|
+
function optionalBytes(value) {
|
|
8837
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return "\u2014";
|
|
8838
|
+
if (value >= 1024 * 1024 * 1024) {
|
|
8839
|
+
return `${(value / (1024 * 1024 * 1024)).toFixed(2)} GiB`;
|
|
8840
|
+
}
|
|
8841
|
+
if (value >= 1024 * 1024) {
|
|
8842
|
+
return `${(value / (1024 * 1024)).toFixed(1)} MiB`;
|
|
8843
|
+
}
|
|
8844
|
+
if (value >= 1024) return `${(value / 1024).toFixed(1)} KiB`;
|
|
8845
|
+
return `${Math.round(value)} B`;
|
|
8846
|
+
}
|
|
8847
|
+
function optionalCount(value, unit) {
|
|
8848
|
+
return typeof value === "number" && Number.isFinite(value) ? `${value} ${unit}` : `\u2014 ${unit}`;
|
|
8849
|
+
}
|
|
8850
|
+
function optionalAgeSeconds(value) {
|
|
8851
|
+
return typeof value === "number" && Number.isFinite(value) ? `${Math.max(0, Math.round(value))}s` : "unknown";
|
|
8852
|
+
}
|
|
8350
8853
|
|
|
8351
8854
|
// src/o11y-command.ts
|
|
8352
8855
|
async function o11yCommand(parsed, deps = {}) {
|
|
8353
8856
|
assertArgs(
|
|
8354
8857
|
parsed,
|
|
8355
|
-
[
|
|
8858
|
+
[
|
|
8859
|
+
"config",
|
|
8860
|
+
"context",
|
|
8861
|
+
"platform",
|
|
8862
|
+
"token",
|
|
8863
|
+
"email",
|
|
8864
|
+
"json",
|
|
8865
|
+
"app",
|
|
8866
|
+
"env",
|
|
8867
|
+
"minutes"
|
|
8868
|
+
],
|
|
8356
8869
|
2
|
|
8357
8870
|
);
|
|
8358
8871
|
const action = parsed.positionals[1];
|
|
@@ -8364,9 +8877,12 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
8364
8877
|
const minutes = statusMinutes(
|
|
8365
8878
|
numberOpt(parsed.options.minutes, "--minutes") ?? 60
|
|
8366
8879
|
);
|
|
8367
|
-
const
|
|
8368
|
-
|
|
8369
|
-
|
|
8880
|
+
const context = await resolveOperatorContext(parsed, {
|
|
8881
|
+
allowMissingConfig: true,
|
|
8882
|
+
defaultEnvironment: "prod",
|
|
8883
|
+
requireApp: true
|
|
8884
|
+
});
|
|
8885
|
+
const cfg = context.cfg;
|
|
8370
8886
|
const doFetch = deps.fetch ?? fetch;
|
|
8371
8887
|
const out = deps.stdout ?? console;
|
|
8372
8888
|
const token = await getDeveloperToken(
|
|
@@ -8380,8 +8896,8 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
8380
8896
|
doFetch,
|
|
8381
8897
|
out
|
|
8382
8898
|
);
|
|
8383
|
-
const appId =
|
|
8384
|
-
const env =
|
|
8899
|
+
const appId = context.app.value;
|
|
8900
|
+
const env = context.environment.value;
|
|
8385
8901
|
const base = `${cfg.platformUrl}/o11y/${encodeURIComponent(appId)}`;
|
|
8386
8902
|
const query = new URLSearchParams({ env, minutes: String(minutes) });
|
|
8387
8903
|
const headers = { authorization: `Bearer ${token}` };
|
|
@@ -8398,6 +8914,8 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
8398
8914
|
canary,
|
|
8399
8915
|
collector,
|
|
8400
8916
|
provider,
|
|
8917
|
+
providerCapacity,
|
|
8918
|
+
providerReconciliation,
|
|
8401
8919
|
providerHistory
|
|
8402
8920
|
] = await Promise.all([
|
|
8403
8921
|
read2(`${base}/metrics/red?${query}`, headers, doFetch),
|
|
@@ -8414,6 +8932,19 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
8414
8932
|
),
|
|
8415
8933
|
read2(`${base}/self-health?${query}`, headers, doFetch),
|
|
8416
8934
|
read2(`${base}/provider/cloudflare?${query}`, headers, doFetch),
|
|
8935
|
+
appId === "odla-db" ? read2(`${base}/provider/cloudflare/capacity`, headers, doFetch) : Promise.resolve({
|
|
8936
|
+
httpStatus: 204,
|
|
8937
|
+
body: {
|
|
8938
|
+
status: "not_applicable",
|
|
8939
|
+
scope: "cloudflare_account",
|
|
8940
|
+
reason: "account-scoped Durable Object capacity is exposed only through odla-db"
|
|
8941
|
+
}
|
|
8942
|
+
}),
|
|
8943
|
+
read2(
|
|
8944
|
+
`${base}/provider/cloudflare/reconciliation?${query}`,
|
|
8945
|
+
headers,
|
|
8946
|
+
doFetch
|
|
8947
|
+
),
|
|
8417
8948
|
read2(`${base}/provider/cloudflare/history?${query}`, headers, doFetch)
|
|
8418
8949
|
]);
|
|
8419
8950
|
const verdict = statusVerdict({
|
|
@@ -8422,10 +8953,12 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
8422
8953
|
canary,
|
|
8423
8954
|
collector,
|
|
8424
8955
|
provider,
|
|
8956
|
+
providerCapacity,
|
|
8957
|
+
providerReconciliation,
|
|
8425
8958
|
providerHistory
|
|
8426
8959
|
});
|
|
8427
8960
|
const status = {
|
|
8428
|
-
schemaVersion:
|
|
8961
|
+
schemaVersion: 9,
|
|
8429
8962
|
scope: { appId, env, minutes },
|
|
8430
8963
|
observedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8431
8964
|
verdict,
|
|
@@ -8435,6 +8968,8 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
8435
8968
|
canary,
|
|
8436
8969
|
collector,
|
|
8437
8970
|
provider,
|
|
8971
|
+
providerCapacity,
|
|
8972
|
+
providerReconciliation,
|
|
8438
8973
|
providerHistory
|
|
8439
8974
|
};
|
|
8440
8975
|
if (parsed.options.json === true) {
|
|
@@ -8467,7 +9002,7 @@ async function read2(url, headers, doFetch) {
|
|
|
8467
9002
|
// src/provision.ts
|
|
8468
9003
|
var import_apps5 = require("@odla-ai/apps");
|
|
8469
9004
|
var import_ai3 = require("@odla-ai/ai");
|
|
8470
|
-
var
|
|
9005
|
+
var import_node_process10 = __toESM(require("process"), 1);
|
|
8471
9006
|
|
|
8472
9007
|
// src/integration-provision.ts
|
|
8473
9008
|
var import_db3 = require("@odla-ai/db");
|
|
@@ -8826,7 +9361,7 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
|
|
|
8826
9361
|
await provisionIntegrationSeeds(doFetch, cfg.dbEndpoint, tenantId, dbKey, database.integrations, env, out);
|
|
8827
9362
|
}
|
|
8828
9363
|
if (cfg.services.includes("ai") && cfg.ai?.provider && cfg.ai.keyEnv) {
|
|
8829
|
-
const key =
|
|
9364
|
+
const key = import_node_process10.default.env[cfg.ai.keyEnv];
|
|
8830
9365
|
if (key) {
|
|
8831
9366
|
const secretName = cfg.ai.secretName ?? defaultSecretName(cfg.ai.provider);
|
|
8832
9367
|
await (0, import_ai3.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, secretName, key);
|
|
@@ -8867,8 +9402,8 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
|
|
|
8867
9402
|
}
|
|
8868
9403
|
|
|
8869
9404
|
// src/record.ts
|
|
8870
|
-
var
|
|
8871
|
-
var
|
|
9405
|
+
var import_node_fs15 = require("fs");
|
|
9406
|
+
var import_node_process11 = __toESM(require("process"), 1);
|
|
8872
9407
|
|
|
8873
9408
|
// src/surface.ts
|
|
8874
9409
|
var PM_ACTIONS = {
|
|
@@ -8916,6 +9451,7 @@ var COMMAND_SURFACE = {
|
|
|
8916
9451
|
calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
|
|
8917
9452
|
capabilities: {},
|
|
8918
9453
|
code: { connect: {} },
|
|
9454
|
+
context: { show: {}, list: {}, save: {}, remove: {} },
|
|
8919
9455
|
// `watch`, `read`, `reply`, and `resolve` take a topic id from there on.
|
|
8920
9456
|
discuss: {
|
|
8921
9457
|
groups: {},
|
|
@@ -9019,7 +9555,7 @@ function surfacePaths(node = COMMAND_SURFACE, prefix = []) {
|
|
|
9019
9555
|
|
|
9020
9556
|
// src/record.ts
|
|
9021
9557
|
function recordInvocation(parsed) {
|
|
9022
|
-
const file =
|
|
9558
|
+
const file = import_node_process11.default.env.ODLA_CLI_RECORD;
|
|
9023
9559
|
if (!file) return;
|
|
9024
9560
|
try {
|
|
9025
9561
|
const entry = {
|
|
@@ -9027,19 +9563,14 @@ function recordInvocation(parsed) {
|
|
|
9027
9563
|
options: Object.keys(parsed.options).sort()
|
|
9028
9564
|
};
|
|
9029
9565
|
if (!entry.path.length) return;
|
|
9030
|
-
(0,
|
|
9566
|
+
(0, import_node_fs15.appendFileSync)(file, `${JSON.stringify(entry)}
|
|
9031
9567
|
`);
|
|
9032
9568
|
} catch {
|
|
9033
9569
|
}
|
|
9034
9570
|
}
|
|
9035
9571
|
|
|
9036
|
-
// src/runbook-command.ts
|
|
9037
|
-
var import_node_fs18 = require("fs");
|
|
9038
|
-
var import_node_path14 = require("path");
|
|
9039
|
-
var import_node_process10 = __toESM(require("process"), 1);
|
|
9040
|
-
|
|
9041
9572
|
// src/runbook-actions.ts
|
|
9042
|
-
var
|
|
9573
|
+
var import_node_fs16 = require("fs");
|
|
9043
9574
|
|
|
9044
9575
|
// src/runbook-requires.ts
|
|
9045
9576
|
var SPEC = /^(@?[\w./-]+?)@(\d+\.\d+\.\d+(?:[\w.-]*)?)$/;
|
|
@@ -9124,7 +9655,7 @@ async function bySlug(ctx, slug) {
|
|
|
9124
9655
|
function readBody(file, inline) {
|
|
9125
9656
|
if (inline !== void 0) return inline;
|
|
9126
9657
|
if (file === void 0) throw new Error("supply the new text with --file <path>, --file - (stdin), or --body");
|
|
9127
|
-
return (0,
|
|
9658
|
+
return (0, import_node_fs16.readFileSync)(file === "-" ? 0 : file, "utf8");
|
|
9128
9659
|
}
|
|
9129
9660
|
var stamp = (ms) => ms ? new Date(ms).toISOString().slice(0, 16).replace("T", " ") : "";
|
|
9130
9661
|
async function runbookList(ctx, all, query) {
|
|
@@ -9211,8 +9742,8 @@ async function runbookRemove(ctx, slug) {
|
|
|
9211
9742
|
}
|
|
9212
9743
|
|
|
9213
9744
|
// src/runbook-import.ts
|
|
9214
|
-
var
|
|
9215
|
-
var
|
|
9745
|
+
var import_node_fs17 = require("fs");
|
|
9746
|
+
var import_node_path13 = require("path");
|
|
9216
9747
|
function parseRunbook(text, slug) {
|
|
9217
9748
|
let rest = text;
|
|
9218
9749
|
const meta = {};
|
|
@@ -9237,12 +9768,12 @@ function parseRunbook(text, slug) {
|
|
|
9237
9768
|
};
|
|
9238
9769
|
}
|
|
9239
9770
|
function readRunbookDir(dir) {
|
|
9240
|
-
if (!(0,
|
|
9241
|
-
const files = (0,
|
|
9771
|
+
if (!(0, import_node_fs17.statSync)(dir, { throwIfNoEntry: false })?.isDirectory()) throw new Error(`not a directory: ${dir}`);
|
|
9772
|
+
const files = (0, import_node_fs17.readdirSync)(dir).filter((f) => f.endsWith(".md")).sort();
|
|
9242
9773
|
if (!files.length) throw new Error(`no .md files in ${dir}`);
|
|
9243
9774
|
return files.map((file) => {
|
|
9244
|
-
const slug = (0,
|
|
9245
|
-
const parsed = parseRunbook((0,
|
|
9775
|
+
const slug = (0, import_node_path13.basename)(file, ".md");
|
|
9776
|
+
const parsed = parseRunbook((0, import_node_fs17.readFileSync)((0, import_node_path13.join)(dir, file), "utf8"), slug);
|
|
9246
9777
|
return { file, slug, ...parsed, words: parsed.body.split(/\s+/).filter(Boolean).length };
|
|
9247
9778
|
});
|
|
9248
9779
|
}
|
|
@@ -9314,8 +9845,8 @@ async function upsert(ctx, r, visibility) {
|
|
|
9314
9845
|
|
|
9315
9846
|
// src/runbook-impact.ts
|
|
9316
9847
|
var import_node_child_process7 = require("child_process");
|
|
9317
|
-
var
|
|
9318
|
-
var
|
|
9848
|
+
var import_node_fs18 = require("fs");
|
|
9849
|
+
var import_node_path14 = require("path");
|
|
9319
9850
|
|
|
9320
9851
|
// src/runbook-impact-scan.ts
|
|
9321
9852
|
var DECL = /^[+-]\s*export\s+(?:declare\s+)?(?:default\s+)?(?:abstract\s+)?(?:async\s+)?(?:const|let|var|function|class|interface|type|enum)\s+([A-Za-z_$][\w$]*)/;
|
|
@@ -9484,10 +10015,10 @@ ${body.split("\n").map((line) => `+${line}`).join("\n")}
|
|
|
9484
10015
|
}
|
|
9485
10016
|
function manifestLabeller(root) {
|
|
9486
10017
|
return (workspace) => {
|
|
9487
|
-
const manifest = (0,
|
|
9488
|
-
if (!(0,
|
|
10018
|
+
const manifest = (0, import_node_path14.join)(root, workspace, "package.json");
|
|
10019
|
+
if (!(0, import_node_fs18.existsSync)(manifest)) return void 0;
|
|
9489
10020
|
try {
|
|
9490
|
-
const name = JSON.parse((0,
|
|
10021
|
+
const name = JSON.parse((0, import_node_fs18.readFileSync)(manifest, "utf8")).name;
|
|
9491
10022
|
return typeof name === "string" ? name : void 0;
|
|
9492
10023
|
} catch {
|
|
9493
10024
|
return void 0;
|
|
@@ -9554,7 +10085,7 @@ function report3(ctx, impacts) {
|
|
|
9554
10085
|
async function runbookImpact(ctx, options, deps = {}) {
|
|
9555
10086
|
const cwd = deps.cwd ?? process.cwd();
|
|
9556
10087
|
const runGit = deps.runGit ?? gitRunner(cwd);
|
|
9557
|
-
const read3 = deps.readRepoFile ?? ((path) => (0,
|
|
10088
|
+
const read3 = deps.readRepoFile ?? ((path) => (0, import_node_fs18.readFileSync)((0, import_node_path14.join)(cwd, path), "utf8"));
|
|
9558
10089
|
const surfaces = changedSurfaces(collectDiff(runGit, options.base, read3), manifestLabeller(cwd));
|
|
9559
10090
|
if (!surfaces.length) {
|
|
9560
10091
|
return ctx.out.log(
|
|
@@ -9687,12 +10218,12 @@ async function runbookComment(ctx, slug, body) {
|
|
|
9687
10218
|
|
|
9688
10219
|
// src/runbook-editor.ts
|
|
9689
10220
|
var import_node_child_process8 = require("child_process");
|
|
9690
|
-
var
|
|
9691
|
-
var
|
|
9692
|
-
var
|
|
9693
|
-
var
|
|
10221
|
+
var import_node_fs19 = require("fs");
|
|
10222
|
+
var import_node_os5 = require("os");
|
|
10223
|
+
var import_node_path15 = require("path");
|
|
10224
|
+
var import_node_process12 = __toESM(require("process"), 1);
|
|
9694
10225
|
var EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
|
|
9695
|
-
function resolveEditor(env =
|
|
10226
|
+
function resolveEditor(env = import_node_process12.default.env) {
|
|
9696
10227
|
for (const name of EDITOR_ENV) {
|
|
9697
10228
|
const value = env[name];
|
|
9698
10229
|
if (value && value.trim()) return value.trim();
|
|
@@ -9706,8 +10237,8 @@ function defaultRun(command, path) {
|
|
|
9706
10237
|
return result.status ?? 0;
|
|
9707
10238
|
}
|
|
9708
10239
|
function editText(initial, slug, deps = {}) {
|
|
9709
|
-
const env = deps.env ??
|
|
9710
|
-
const interactive = deps.interactive ?? (() => Boolean(
|
|
10240
|
+
const env = deps.env ?? import_node_process12.default.env;
|
|
10241
|
+
const interactive = deps.interactive ?? (() => Boolean(import_node_process12.default.stdin.isTTY));
|
|
9711
10242
|
const editor = resolveEditor(env);
|
|
9712
10243
|
if (!editor)
|
|
9713
10244
|
throw new Error(
|
|
@@ -9715,16 +10246,16 @@ function editText(initial, slug, deps = {}) {
|
|
|
9715
10246
|
);
|
|
9716
10247
|
if (!interactive())
|
|
9717
10248
|
throw new Error(`cannot open an editor without a terminal \u2014 pass --file <path> or --body "\u2026" instead`);
|
|
9718
|
-
const dir = (0,
|
|
9719
|
-
const file = (0,
|
|
10249
|
+
const dir = (0, import_node_fs19.mkdtempSync)((0, import_node_path15.join)((0, import_node_os5.tmpdir)(), "odla-runbook-"));
|
|
10250
|
+
const file = (0, import_node_path15.join)(dir, `${slug}.md`);
|
|
9720
10251
|
try {
|
|
9721
|
-
(0,
|
|
10252
|
+
(0, import_node_fs19.writeFileSync)(file, initial, { mode: 384 });
|
|
9722
10253
|
const code = defaultRunOrInjected(deps)(editor, file);
|
|
9723
10254
|
if (code !== 0) throw new Error(`editor "${editor}" exited with ${code}; nothing was written`);
|
|
9724
|
-
const edited = (0,
|
|
10255
|
+
const edited = (0, import_node_fs19.readFileSync)(file, "utf8");
|
|
9725
10256
|
return edited === initial ? null : edited;
|
|
9726
10257
|
} finally {
|
|
9727
|
-
(0,
|
|
10258
|
+
(0, import_node_fs19.rmSync)(dir, { recursive: true, force: true });
|
|
9728
10259
|
}
|
|
9729
10260
|
}
|
|
9730
10261
|
var defaultRunOrInjected = (deps) => deps.run ?? defaultRun;
|
|
@@ -9762,10 +10293,16 @@ function credentialKind(identity) {
|
|
|
9762
10293
|
return identity.scopes.length ? "device token (scoped)" : "device token or session";
|
|
9763
10294
|
}
|
|
9764
10295
|
async function whoamiCommand(parsed, deps = {}) {
|
|
9765
|
-
assertArgs(
|
|
10296
|
+
assertArgs(
|
|
10297
|
+
parsed,
|
|
10298
|
+
["config", "context", "platform", "token", "email", "json"],
|
|
10299
|
+
1
|
|
10300
|
+
);
|
|
9766
10301
|
const out = deps.stdout ?? console;
|
|
9767
10302
|
const doFetch = deps.fetch ?? fetch;
|
|
9768
|
-
const cfg = await
|
|
10303
|
+
const { cfg } = await resolveOperatorContext(parsed, {
|
|
10304
|
+
allowMissingConfig: true
|
|
10305
|
+
});
|
|
9769
10306
|
const token = await getDeveloperToken(
|
|
9770
10307
|
cfg,
|
|
9771
10308
|
{
|
|
@@ -9814,34 +10351,24 @@ var ALLOWED3 = [
|
|
|
9814
10351
|
"dry-run",
|
|
9815
10352
|
"limit",
|
|
9816
10353
|
"base",
|
|
9817
|
-
"requires"
|
|
10354
|
+
"requires",
|
|
10355
|
+
"platform",
|
|
10356
|
+
"context"
|
|
9818
10357
|
];
|
|
9819
10358
|
function requireSlug(slug, action) {
|
|
9820
10359
|
if (!slug) throw new Error(`"runbook ${action}" needs a slug, e.g. "odla-ai runbook ${action} release"`);
|
|
9821
10360
|
return slug;
|
|
9822
10361
|
}
|
|
9823
10362
|
var WRITES = /* @__PURE__ */ new Set(["new", "edit", "publish", "archive", "visibility", "revert", "rm", "import"]);
|
|
9824
|
-
var CONFIG_FREE = /* @__PURE__ */ new Set(["list", "search", "ask", "get", "cat", "history", "impact", "lint"]);
|
|
9825
|
-
async function loadOrDefaultConfig(configPath, action, appId) {
|
|
9826
|
-
const projectScoped = appId !== void 0;
|
|
9827
|
-
if (!projectScoped && CONFIG_FREE.has(action) && !(0, import_node_fs18.existsSync)((0, import_node_path14.resolve)(configPath))) {
|
|
9828
|
-
const rootDir = import_node_process10.default.cwd();
|
|
9829
|
-
return {
|
|
9830
|
-
configPath,
|
|
9831
|
-
platformUrl: import_node_process10.default.env.ODLA_PLATFORM_URL ?? "https://odla.ai",
|
|
9832
|
-
rootDir,
|
|
9833
|
-
app: { id: "", name: "" },
|
|
9834
|
-
local: { tokenFile: (0, import_node_path14.join)(rootDir, ".odla/dev-token.json") }
|
|
9835
|
-
};
|
|
9836
|
-
}
|
|
9837
|
-
return loadProjectConfig(configPath);
|
|
9838
|
-
}
|
|
9839
10363
|
async function buildContext3(parsed, deps, action) {
|
|
9840
|
-
const
|
|
9841
|
-
const
|
|
10364
|
+
const appIdOption = stringOpt(parsed.options.app);
|
|
10365
|
+
const context = await resolveOperatorContext(parsed, {
|
|
10366
|
+
allowMissingConfig: true
|
|
10367
|
+
});
|
|
10368
|
+
const { cfg } = context;
|
|
9842
10369
|
const doFetch = deps.fetch ?? fetch;
|
|
9843
10370
|
const out = deps.stdout ?? console;
|
|
9844
|
-
const appId =
|
|
10371
|
+
const appId = appIdOption ?? (context.app.source === "environment" || context.app.source === "profile" ? context.app.value : null) ?? PLATFORM_SCOPE;
|
|
9845
10372
|
const dryRun = parsed.options["dry-run"] === true;
|
|
9846
10373
|
if (action === "import" && dryRun) {
|
|
9847
10374
|
return {
|
|
@@ -9862,8 +10389,10 @@ async function buildContext3(parsed, deps, action) {
|
|
|
9862
10389
|
fetch: doFetch,
|
|
9863
10390
|
stdout: out,
|
|
9864
10391
|
openApprovalUrl: deps.openUrl,
|
|
9865
|
-
//
|
|
9866
|
-
|
|
10392
|
+
// A project, named context, or the global operator context owns the
|
|
10393
|
+
// exact-scope cache; it never follows an arbitrary shell directory.
|
|
10394
|
+
rootDir: cfg.rootDir,
|
|
10395
|
+
tokenFile: context.credentials.scopedTokenFile
|
|
9867
10396
|
}) : await getDeveloperToken(
|
|
9868
10397
|
cfg,
|
|
9869
10398
|
{
|
|
@@ -10138,7 +10667,7 @@ function hostedSeverity(value, flag) {
|
|
|
10138
10667
|
var import_security2 = require("@odla-ai/security");
|
|
10139
10668
|
|
|
10140
10669
|
// src/security.ts
|
|
10141
|
-
var
|
|
10670
|
+
var import_node_path16 = require("path");
|
|
10142
10671
|
var import_security = require("@odla-ai/security");
|
|
10143
10672
|
var import_node3 = require("@odla-ai/security/node");
|
|
10144
10673
|
async function runHostedSecurity(options) {
|
|
@@ -10150,9 +10679,9 @@ async function runHostedSecurity(options) {
|
|
|
10150
10679
|
const appId = selfAudit ? "odla-ai" : cfg.app.id;
|
|
10151
10680
|
const env = selfAudit ? "prod" : selectEnv(options.env, cfg.envs, cfg.configPath, cfg.rootDir);
|
|
10152
10681
|
const platform = options.platform ?? cfg?.platformUrl ?? "https://odla.ai";
|
|
10153
|
-
const target = (0,
|
|
10154
|
-
const output = (0,
|
|
10155
|
-
const outputRelative = (0,
|
|
10682
|
+
const target = (0, import_node_path16.resolve)(options.target ?? cfg?.rootDir ?? ".");
|
|
10683
|
+
const output = (0, import_node_path16.resolve)(options.out ?? (0, import_node_path16.resolve)(target, ".odla/security/hosted"));
|
|
10684
|
+
const outputRelative = (0, import_node_path16.relative)(target, output).split(import_node_path16.sep).join("/");
|
|
10156
10685
|
if (!outputRelative) throw new Error("Hosted security output cannot be the repository root");
|
|
10157
10686
|
const profile = profileFor(options.profile ?? "odla", options.maxHuntTasks ?? 12);
|
|
10158
10687
|
const tokenRequest = {
|
|
@@ -10164,7 +10693,7 @@ async function runHostedSecurity(options) {
|
|
|
10164
10693
|
};
|
|
10165
10694
|
const token = await injectedToken(options, tokenRequest);
|
|
10166
10695
|
const snapshot = await (0, import_node3.snapshotDirectory)(target, {
|
|
10167
|
-
exclude: !outputRelative.startsWith("../") && !(0,
|
|
10696
|
+
exclude: !outputRelative.startsWith("../") && !(0, import_node_path16.isAbsolute)(outputRelative) ? [outputRelative] : []
|
|
10168
10697
|
});
|
|
10169
10698
|
const hosted = await (0, import_security.createPlatformSecurityReasoners)({
|
|
10170
10699
|
platform,
|
|
@@ -10182,7 +10711,7 @@ async function runHostedSecurity(options) {
|
|
|
10182
10711
|
});
|
|
10183
10712
|
const harness = (0, import_security.createSecurityHarness)({
|
|
10184
10713
|
profile,
|
|
10185
|
-
store: new import_node3.FileRunStore((0,
|
|
10714
|
+
store: new import_node3.FileRunStore((0, import_node_path16.resolve)(output, "state")),
|
|
10186
10715
|
discoveryReasoner: hosted.discoveryReasoner,
|
|
10187
10716
|
validationReasoner: hosted.validationReasoner,
|
|
10188
10717
|
policy: {
|
|
@@ -10206,7 +10735,7 @@ async function runHostedSecurity(options) {
|
|
|
10206
10735
|
function selectEnv(requested, declared, configPath, rootDir) {
|
|
10207
10736
|
const env = requested ?? (declared.includes("dev") ? "dev" : declared[0]);
|
|
10208
10737
|
if (!env || !declared.includes(env)) {
|
|
10209
|
-
const shown = (0,
|
|
10738
|
+
const shown = (0, import_node_path16.relative)(rootDir, configPath) || configPath;
|
|
10210
10739
|
throw new Error(`env "${env ?? ""}" is not declared in ${shown}`);
|
|
10211
10740
|
}
|
|
10212
10741
|
return env;
|
|
@@ -10235,7 +10764,7 @@ function printSummary(out, appId, env, run, report4, output) {
|
|
|
10235
10764
|
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}`);
|
|
10236
10765
|
if (report4.callBudget) out.log(` calls: discovery=${formatBudget(report4.callBudget.discovery)} validation=${formatBudget(report4.callBudget.validation)}`);
|
|
10237
10766
|
out.log(` findings: confirmed=${report4.metrics.confirmed} needs_reproduction=${report4.metrics.needsReproduction} candidates=${report4.metrics.candidates}`);
|
|
10238
|
-
out.log(` report: ${(0,
|
|
10767
|
+
out.log(` report: ${(0, import_node_path16.resolve)(output, "REPORT.md")}`);
|
|
10239
10768
|
}
|
|
10240
10769
|
function formatBudget(usage) {
|
|
10241
10770
|
return usage ? `${usage.usedCalls}/${usage.maxCalls} skipped=${usage.skippedCalls}` : "caller-managed";
|
|
@@ -10715,6 +11244,10 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
|
10715
11244
|
await whoamiCommand(parsed, runtime);
|
|
10716
11245
|
return;
|
|
10717
11246
|
}
|
|
11247
|
+
if (command === "context") {
|
|
11248
|
+
await contextCommand(parsed, runtime);
|
|
11249
|
+
return;
|
|
11250
|
+
}
|
|
10718
11251
|
if (command === "runbook") {
|
|
10719
11252
|
await runbookCommand(parsed, runtime);
|
|
10720
11253
|
return;
|