@odla-ai/cli 0.25.12 → 0.25.18
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 +705 -147
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-MFC4TX6W.js → chunk-B6DISJPC.js} +688 -130
- package/dist/chunk-B6DISJPC.js.map +1 -0
- package/dist/index.cjs +705 -147
- 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
|
}
|
|
@@ -975,8 +975,8 @@ function parseArgv(argv) {
|
|
|
975
975
|
}
|
|
976
976
|
return { positionals, options };
|
|
977
977
|
}
|
|
978
|
-
function assertArgs(parsed,
|
|
979
|
-
const allowed = new Set(
|
|
978
|
+
function assertArgs(parsed, allowedOptions2, maxPositionals) {
|
|
979
|
+
const allowed = new Set(allowedOptions2);
|
|
980
980
|
for (const name of Object.keys(parsed.options)) {
|
|
981
981
|
if (!allowed.has(name)) throw new Error(`unknown option "--${name}"; run "odla-ai help" for supported options`);
|
|
982
982
|
}
|
|
@@ -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 = {
|
|
@@ -6612,11 +6613,21 @@ Usage:
|
|
|
6612
6613
|
odla-ai app owners list [--config odla.config.mjs] [--email <odla-account>] [--json]
|
|
6613
6614
|
odla-ai app owners add <email> [--email <odla-account>] [--json]
|
|
6614
6615
|
odla-ai app owners remove <email> [--email <odla-account>] [--json]
|
|
6615
|
-
odla-ai pm
|
|
6616
|
-
odla-ai pm
|
|
6616
|
+
odla-ai pm goal list [--app <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
6617
|
+
odla-ai pm task list [--app <id>] [--column <c>] [--goal <id>] [--assignee <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
6618
|
+
odla-ai pm decision list [--app <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
6619
|
+
odla-ai pm bug list [--app <id>] [--status <s>] [--severity <s>] [--goal <id>] [--assignee <id>] [--decision <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
6620
|
+
odla-ai pm goal add --app <id> --title <t> [--status <s>] [--proof <text>] [--target <pct>] [--mutation-id <id>] [--json]
|
|
6621
|
+
odla-ai pm task add --app <id> --title <t> [--column <c>] [--goal <id>] [--assignee <id>] [--description <text>|--body <text>] [--due <epoch-ms>] [--mutation-id <id>] [--json]
|
|
6622
|
+
odla-ai pm decision add --app <id> --title <t> --body <text> [--status <s>] [--mutation-id <id>] [--json]
|
|
6623
|
+
odla-ai pm bug add --app <id> --title <t> (--description <text>|--body <text>) [--status <s>] [--severity <s>] [--goal <id>] [--assignee <id>] [--decision <id>] [--mutation-id <id>] [--json]
|
|
6617
6624
|
odla-ai pm <goal|task|decision|bug> get <id> [--json]
|
|
6618
|
-
odla-ai pm
|
|
6619
|
-
odla-ai pm
|
|
6625
|
+
odla-ai pm goal set <id> [--title <t>|--status <s>|--proof <text>|--no-proof|--target <pct>|--no-target] [--mutation-id <id>] [--json]
|
|
6626
|
+
odla-ai pm task set <id> [--title <t>|--column <c>|--rank <n>|--goal <id>|--no-goal|--assignee <id>|--no-assignee|--description <text>|--body <text>|--due <epoch-ms>|--no-due] [--mutation-id <id>] [--json]
|
|
6627
|
+
odla-ai pm decision set <id> [--title <t>|--status <s>|--body <text>] [--mutation-id <id>] [--json]
|
|
6628
|
+
odla-ai pm bug set <id> [--title <t>|--status <s>|--severity <s>|--goal <id>|--no-goal|--assignee <id>|--no-assignee|--decision <id>|--no-decision|--description <text>|--body <text>] [--mutation-id <id>] [--json]
|
|
6629
|
+
odla-ai pm <goal|task|decision> done <id> [--mutation-id <id>]
|
|
6630
|
+
odla-ai pm bug done <id> [--decision <accepted-decision-id>] [--mutation-id <id>]
|
|
6620
6631
|
odla-ai pm <goal|task|decision|bug> comment <id> --body "..." [--mutation-id <id>]
|
|
6621
6632
|
odla-ai pm <goal|task|decision|bug> comments <id> [--json]
|
|
6622
6633
|
odla-ai pm <goal|task|decision|bug> rm <id>
|
|
@@ -6631,8 +6642,12 @@ Usage:
|
|
|
6631
6642
|
odla-ai discuss watch [<topic>] [--cursor <cursor>] [--by <authorId>] [--self <authorId>] [--interval <s>] [--timeout <s>] [--json|--jsonl]
|
|
6632
6643
|
odla-ai agent jobs [--env dev] [--state pending|running|succeeded|dead_letter] [--limit 50] [--email <email>] [--json]
|
|
6633
6644
|
odla-ai agent retry <job-id> [--env dev] [--email <email>] [--json]
|
|
6634
|
-
odla-ai
|
|
6635
|
-
odla-ai
|
|
6645
|
+
odla-ai context show [--context <name>] [--platform https://odla.ai] [--app <id>] [--env prod] [--json]
|
|
6646
|
+
odla-ai context list [--json]
|
|
6647
|
+
odla-ai context save <name> [--platform <url>] [--app <id>] [--env <name>] [--json]
|
|
6648
|
+
odla-ai context remove <name> --yes [--json]
|
|
6649
|
+
odla-ai o11y status [--app <id>] [--context <name>] [--platform https://odla.ai] [--env prod] [--minutes 60] [--json]
|
|
6650
|
+
odla-ai whoami [--context <name>] [--platform https://odla.ai] [--json]
|
|
6636
6651
|
odla-ai runbook ask "<question>" [--app <id>] [--all] [--json]
|
|
6637
6652
|
odla-ai runbook search "<question>" [--app <id>] [--all] [--limit <n>] [--json]
|
|
6638
6653
|
odla-ai runbook impact [--base origin/main] [--app <id>] [--all] [--limit <n>] [--json]
|
|
@@ -6697,6 +6712,11 @@ Commands:
|
|
|
6697
6712
|
whoami Report who this terminal is authenticated as, and whether it holds
|
|
6698
6713
|
platform admin. A handshake-minted device token is never admin,
|
|
6699
6714
|
however the human who approved it is configured.
|
|
6715
|
+
context Explain selected config, platform, app, environment, and
|
|
6716
|
+
developer-token provenance without printing credentials or
|
|
6717
|
+
starting a device handshake. Operator work can run outside a
|
|
6718
|
+
project checkout with explicit flags or ODLA_* environment
|
|
6719
|
+
variables.
|
|
6700
6720
|
setup Install offline odla runbooks for common coding-agent harnesses.
|
|
6701
6721
|
init Create a generic odla.config.mjs plus starter schema/rules files.
|
|
6702
6722
|
doctor Validate and summarize the project config without network calls.
|
|
@@ -6768,6 +6788,15 @@ Safety:
|
|
|
6768
6788
|
Provision caches the approved developer token and service credentials under
|
|
6769
6789
|
.odla/ with mode 0600, and init adds those paths to .gitignore. Secret push
|
|
6770
6790
|
preflights Wrangler before any shown-once issuance or destructive rotation.
|
|
6791
|
+
Projectless PM, Discussions, o11y, runbook, and identity commands use
|
|
6792
|
+
--platform/--app/--env, ODLA_PLATFORM_URL/ODLA_APP_ID/ODLA_ENV, and
|
|
6793
|
+
ODLA_DEV_TOKEN. Save non-secret scope metadata with "context save", then
|
|
6794
|
+
select it explicitly with --context or ODLA_CONTEXT. Each named context gets
|
|
6795
|
+
isolated developer and scoped-token caches. Override their locations with
|
|
6796
|
+
ODLA_DEV_TOKEN_FILE and ODLA_ADMIN_TOKEN_FILE; ODLA_CONTEXT_FILE relocates
|
|
6797
|
+
the metadata file. Flags and specific ODLA_* scope variables beat a selected
|
|
6798
|
+
context, which beats project config. There is no ambient current context.
|
|
6799
|
+
"context show" reports only provenance and cache state and never authenticates.
|
|
6771
6800
|
Provision opens the approval page in your browser automatically whenever the
|
|
6772
6801
|
machine can show one, including agent-driven runs; only CI, SSH, and
|
|
6773
6802
|
display-less hosts skip it. Use --open to force or --no-open to suppress.
|
|
@@ -6885,8 +6914,8 @@ function hostedPollTimeout(value = 10 * 6e4) {
|
|
|
6885
6914
|
}
|
|
6886
6915
|
async function waitForHostedPoll(milliseconds, signal) {
|
|
6887
6916
|
if (signal?.aborted) throw signal.reason ?? new DOMException("aborted", "AbortError");
|
|
6888
|
-
await new Promise((
|
|
6889
|
-
const timer = setTimeout(
|
|
6917
|
+
await new Promise((resolve12, reject) => {
|
|
6918
|
+
const timer = setTimeout(resolve12, milliseconds);
|
|
6890
6919
|
signal?.addEventListener("abort", () => {
|
|
6891
6920
|
clearTimeout(timer);
|
|
6892
6921
|
reject(signal.reason ?? new DOMException("aborted", "AbortError"));
|
|
@@ -7396,6 +7425,384 @@ async function codeCommand(parsed, dependencies) {
|
|
|
7396
7425
|
});
|
|
7397
7426
|
}
|
|
7398
7427
|
|
|
7428
|
+
// src/operator-credentials.ts
|
|
7429
|
+
var import_node_process7 = __toESM(require("process"), 1);
|
|
7430
|
+
function developerTokenStatus(context, parsed, now = Date.now()) {
|
|
7431
|
+
const cached = readJsonFile(context.cfg.local.tokenFile);
|
|
7432
|
+
const cacheStatus = !cached?.token ? "missing" : cached.platform !== context.platform.value ? "other-platform" : (cached.expiresAt ?? 0) <= now + 6e4 ? "expired" : "valid";
|
|
7433
|
+
const source = clean(
|
|
7434
|
+
stringOpt(parsed.options.token)
|
|
7435
|
+
) ? "flag" : clean(import_node_process7.default.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
|
|
7436
|
+
return {
|
|
7437
|
+
source,
|
|
7438
|
+
cacheFile: context.cfg.local.tokenFile,
|
|
7439
|
+
cacheStatus
|
|
7440
|
+
};
|
|
7441
|
+
}
|
|
7442
|
+
function clean(value) {
|
|
7443
|
+
const normalized = value?.trim();
|
|
7444
|
+
return normalized || void 0;
|
|
7445
|
+
}
|
|
7446
|
+
|
|
7447
|
+
// src/operator-context.ts
|
|
7448
|
+
var import_node_fs14 = require("fs");
|
|
7449
|
+
var import_node_path12 = require("path");
|
|
7450
|
+
var import_node_process9 = __toESM(require("process"), 1);
|
|
7451
|
+
|
|
7452
|
+
// src/operator-profiles.ts
|
|
7453
|
+
var import_node_fs13 = require("fs");
|
|
7454
|
+
var import_node_os4 = require("os");
|
|
7455
|
+
var import_node_path11 = require("path");
|
|
7456
|
+
var import_node_process8 = __toESM(require("process"), 1);
|
|
7457
|
+
function operatorProfileFile() {
|
|
7458
|
+
return (0, import_node_path11.resolve)(
|
|
7459
|
+
clean2(import_node_process8.default.env.ODLA_CONTEXT_FILE) ?? (0, import_node_path11.join)((0, import_node_os4.homedir)(), ".odla", "contexts.json")
|
|
7460
|
+
);
|
|
7461
|
+
}
|
|
7462
|
+
function resolveOperatorProfile(parsed) {
|
|
7463
|
+
const fromFlag = clean2(stringOpt(parsed.options.context));
|
|
7464
|
+
const fromEnvironment = clean2(import_node_process8.default.env.ODLA_CONTEXT);
|
|
7465
|
+
const name = fromFlag ?? fromEnvironment ?? null;
|
|
7466
|
+
const file = operatorProfileFile();
|
|
7467
|
+
if (!name) {
|
|
7468
|
+
return { name: null, source: "unresolved", file, value: null };
|
|
7469
|
+
}
|
|
7470
|
+
assertOperatorName(name, "context");
|
|
7471
|
+
const profiles = readOperatorProfiles(file);
|
|
7472
|
+
const value = Object.hasOwn(profiles, name) ? profiles[name] : void 0;
|
|
7473
|
+
if (!value) {
|
|
7474
|
+
throw new Error(
|
|
7475
|
+
`operator context "${name}" not found in ${file}; run "odla-ai context list" or save it first`
|
|
7476
|
+
);
|
|
7477
|
+
}
|
|
7478
|
+
return {
|
|
7479
|
+
name,
|
|
7480
|
+
source: fromFlag ? "flag" : "environment",
|
|
7481
|
+
file,
|
|
7482
|
+
value
|
|
7483
|
+
};
|
|
7484
|
+
}
|
|
7485
|
+
function listOperatorProfiles(file = operatorProfileFile()) {
|
|
7486
|
+
return Object.entries(readOperatorProfiles(file)).sort(([a], [b]) => a.localeCompare(b)).map(([name, profile]) => ({ name, ...profile }));
|
|
7487
|
+
}
|
|
7488
|
+
function saveOperatorProfile(name, profile, file = operatorProfileFile()) {
|
|
7489
|
+
assertOperatorName(name, "context");
|
|
7490
|
+
const normalized = validateProfile(profile, `operator context "${name}"`);
|
|
7491
|
+
const profiles = readOperatorProfiles(file);
|
|
7492
|
+
profiles[name] = normalized;
|
|
7493
|
+
writePrivateJson(file, { schemaVersion: 1, profiles });
|
|
7494
|
+
}
|
|
7495
|
+
function removeOperatorProfile(name, file = operatorProfileFile()) {
|
|
7496
|
+
assertOperatorName(name, "context");
|
|
7497
|
+
const profiles = readOperatorProfiles(file);
|
|
7498
|
+
if (!Object.hasOwn(profiles, name)) return false;
|
|
7499
|
+
delete profiles[name];
|
|
7500
|
+
writePrivateJson(file, { schemaVersion: 1, profiles });
|
|
7501
|
+
return true;
|
|
7502
|
+
}
|
|
7503
|
+
function operatorCredentialFiles(selection) {
|
|
7504
|
+
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");
|
|
7505
|
+
return {
|
|
7506
|
+
developer: (0, import_node_path11.join)(base, "dev-token.json"),
|
|
7507
|
+
scoped: (0, import_node_path11.join)(base, "admin-token.local.json")
|
|
7508
|
+
};
|
|
7509
|
+
}
|
|
7510
|
+
function assertOperatorName(value, label) {
|
|
7511
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(value)) {
|
|
7512
|
+
throw new Error(
|
|
7513
|
+
`${label} must contain lowercase letters, numbers, and hyphens`
|
|
7514
|
+
);
|
|
7515
|
+
}
|
|
7516
|
+
}
|
|
7517
|
+
function readOperatorProfiles(file) {
|
|
7518
|
+
if (!(0, import_node_fs13.existsSync)(file)) return emptyProfiles();
|
|
7519
|
+
let raw;
|
|
7520
|
+
try {
|
|
7521
|
+
raw = JSON.parse((0, import_node_fs13.readFileSync)(file, "utf8"));
|
|
7522
|
+
} catch {
|
|
7523
|
+
throw new Error(`operator context file ${file} is not valid JSON`);
|
|
7524
|
+
}
|
|
7525
|
+
if (!raw || typeof raw !== "object" || raw.schemaVersion !== 1 || !raw.profiles || typeof raw.profiles !== "object" || Array.isArray(raw.profiles)) {
|
|
7526
|
+
throw new Error(`operator context file ${file} has an invalid shape`);
|
|
7527
|
+
}
|
|
7528
|
+
const unknown = Object.keys(raw).filter(
|
|
7529
|
+
(key) => !["schemaVersion", "profiles"].includes(key)
|
|
7530
|
+
);
|
|
7531
|
+
if (unknown.length > 0) {
|
|
7532
|
+
throw new Error(
|
|
7533
|
+
`operator context file ${file} contains unsupported field(s): ${unknown.sort().join(", ")}`
|
|
7534
|
+
);
|
|
7535
|
+
}
|
|
7536
|
+
const profiles = emptyProfiles();
|
|
7537
|
+
for (const [name, value] of Object.entries(
|
|
7538
|
+
raw.profiles
|
|
7539
|
+
)) {
|
|
7540
|
+
assertOperatorName(name, "context");
|
|
7541
|
+
profiles[name] = validateProfile(value, `operator context "${name}"`);
|
|
7542
|
+
}
|
|
7543
|
+
return profiles;
|
|
7544
|
+
}
|
|
7545
|
+
function emptyProfiles() {
|
|
7546
|
+
return /* @__PURE__ */ Object.create(null);
|
|
7547
|
+
}
|
|
7548
|
+
function validateProfile(value, label) {
|
|
7549
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
7550
|
+
throw new Error(`${label} has an invalid shape`);
|
|
7551
|
+
}
|
|
7552
|
+
const unknown = Object.keys(value).filter(
|
|
7553
|
+
(key) => !["platform", "app", "environment"].includes(key)
|
|
7554
|
+
);
|
|
7555
|
+
if (unknown.length > 0) {
|
|
7556
|
+
throw new Error(
|
|
7557
|
+
`${label} contains unsupported field(s): ${unknown.sort().join(", ")}; contexts store scope metadata only, never credentials`
|
|
7558
|
+
);
|
|
7559
|
+
}
|
|
7560
|
+
const candidate = value;
|
|
7561
|
+
if (typeof candidate.platform !== "string") {
|
|
7562
|
+
throw new Error(`${label} needs an absolute platform URL`);
|
|
7563
|
+
}
|
|
7564
|
+
const platform = platformAudience(candidate.platform);
|
|
7565
|
+
const app = clean2(candidate.app);
|
|
7566
|
+
const environment2 = clean2(candidate.environment);
|
|
7567
|
+
if (app) assertOperatorName(app, "app");
|
|
7568
|
+
if (environment2) assertOperatorName(environment2, "environment");
|
|
7569
|
+
return {
|
|
7570
|
+
platform,
|
|
7571
|
+
...app ? { app } : {},
|
|
7572
|
+
...environment2 ? { environment: environment2 } : {}
|
|
7573
|
+
};
|
|
7574
|
+
}
|
|
7575
|
+
function clean2(value) {
|
|
7576
|
+
const normalized = value?.trim();
|
|
7577
|
+
return normalized || void 0;
|
|
7578
|
+
}
|
|
7579
|
+
|
|
7580
|
+
// src/operator-context.ts
|
|
7581
|
+
var DEFAULT_PLATFORM2 = "https://odla.ai";
|
|
7582
|
+
async function resolveOperatorContext(parsed, options = {}) {
|
|
7583
|
+
const profile = resolveOperatorProfile(parsed);
|
|
7584
|
+
const configArgument = stringOpt(parsed.options.config) ?? "odla.config.mjs";
|
|
7585
|
+
const configPath = (0, import_node_path12.resolve)(configArgument);
|
|
7586
|
+
const explicitConfig = parsed.options.config !== void 0;
|
|
7587
|
+
const hasConfig = (0, import_node_fs14.existsSync)(configPath);
|
|
7588
|
+
if (!hasConfig && (!options.allowMissingConfig || explicitConfig)) {
|
|
7589
|
+
await loadProjectConfig(configArgument);
|
|
7590
|
+
}
|
|
7591
|
+
const loaded = hasConfig ? await loadProjectConfig(configArgument) : void 0;
|
|
7592
|
+
const platformFlag = clean3(stringOpt(parsed.options.platform));
|
|
7593
|
+
const platformEnvironment = clean3(import_node_process9.default.env.ODLA_PLATFORM_URL);
|
|
7594
|
+
const platformValue = platformAudience(
|
|
7595
|
+
platformFlag ?? platformEnvironment ?? profile.value?.platform ?? loaded?.platformUrl ?? DEFAULT_PLATFORM2
|
|
7596
|
+
);
|
|
7597
|
+
const platformSource = platformFlag ? "flag" : platformEnvironment ? "environment" : profile.value ? "profile" : loaded ? "config" : "default";
|
|
7598
|
+
const appFlag = clean3(stringOpt(parsed.options.app));
|
|
7599
|
+
const appEnvironment = clean3(import_node_process9.default.env.ODLA_APP_ID);
|
|
7600
|
+
const appValue = appFlag ?? appEnvironment ?? profile.value?.app ?? loaded?.app.id ?? null;
|
|
7601
|
+
const appSource = appFlag ? "flag" : appEnvironment ? "environment" : profile.value?.app ? "profile" : loaded ? "config" : "unresolved";
|
|
7602
|
+
if (appValue) assertOperatorName(appValue, "app");
|
|
7603
|
+
if (options.requireApp && !appValue) {
|
|
7604
|
+
throw new Error(
|
|
7605
|
+
"app context is unresolved; pass --app <id>, set ODLA_APP_ID, select --context <name>, or run inside a project with odla.config.mjs"
|
|
7606
|
+
);
|
|
7607
|
+
}
|
|
7608
|
+
const envFlag = clean3(stringOpt(parsed.options.env));
|
|
7609
|
+
const envEnvironment = clean3(import_node_process9.default.env.ODLA_ENV);
|
|
7610
|
+
const environmentValue = envFlag ?? envEnvironment ?? profile.value?.environment ?? options.defaultEnvironment ?? null;
|
|
7611
|
+
const environmentSource = envFlag ? "flag" : envEnvironment ? "environment" : profile.value?.environment ? "profile" : options.defaultEnvironment ? "default" : "unresolved";
|
|
7612
|
+
if (environmentValue) {
|
|
7613
|
+
assertOperatorName(environmentValue, "environment");
|
|
7614
|
+
}
|
|
7615
|
+
const rootDir = loaded?.rootDir ?? import_node_process9.default.cwd();
|
|
7616
|
+
const profileCredentials = operatorCredentialFiles(profile);
|
|
7617
|
+
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;
|
|
7618
|
+
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;
|
|
7619
|
+
const cfg = loaded ? {
|
|
7620
|
+
...loaded,
|
|
7621
|
+
platformUrl: platformValue,
|
|
7622
|
+
app: appValue ? {
|
|
7623
|
+
id: appValue,
|
|
7624
|
+
name: appValue === loaded.app.id ? loaded.app.name : appValue
|
|
7625
|
+
} : loaded.app,
|
|
7626
|
+
local: { ...loaded.local, tokenFile }
|
|
7627
|
+
} : {
|
|
7628
|
+
configPath,
|
|
7629
|
+
rootDir,
|
|
7630
|
+
platformUrl: platformValue,
|
|
7631
|
+
dbEndpoint: platformValue,
|
|
7632
|
+
app: {
|
|
7633
|
+
id: appValue ?? "operator",
|
|
7634
|
+
name: appValue ?? "Operator"
|
|
7635
|
+
},
|
|
7636
|
+
envs: environmentValue ? [environmentValue] : [],
|
|
7637
|
+
services: [],
|
|
7638
|
+
local: {
|
|
7639
|
+
tokenFile,
|
|
7640
|
+
credentialsFile: (0, import_node_path12.join)(rootDir, ".odla", "credentials.local.json"),
|
|
7641
|
+
devVarsFile: (0, import_node_path12.join)(rootDir, ".dev.vars"),
|
|
7642
|
+
gitignore: true
|
|
7643
|
+
}
|
|
7644
|
+
};
|
|
7645
|
+
return {
|
|
7646
|
+
cfg,
|
|
7647
|
+
profile: {
|
|
7648
|
+
name: profile.name,
|
|
7649
|
+
source: profile.source,
|
|
7650
|
+
file: profile.file
|
|
7651
|
+
},
|
|
7652
|
+
config: {
|
|
7653
|
+
path: configPath,
|
|
7654
|
+
status: loaded ? "loaded" : "absent",
|
|
7655
|
+
explicit: explicitConfig
|
|
7656
|
+
},
|
|
7657
|
+
platform: { value: platformValue, source: platformSource },
|
|
7658
|
+
app: { value: appValue, source: appSource },
|
|
7659
|
+
environment: {
|
|
7660
|
+
value: environmentValue,
|
|
7661
|
+
source: environmentSource
|
|
7662
|
+
},
|
|
7663
|
+
credentials: {
|
|
7664
|
+
developerTokenFile: tokenFile,
|
|
7665
|
+
scopedTokenFile
|
|
7666
|
+
}
|
|
7667
|
+
};
|
|
7668
|
+
}
|
|
7669
|
+
function clean3(value) {
|
|
7670
|
+
const normalized = value?.trim();
|
|
7671
|
+
return normalized || void 0;
|
|
7672
|
+
}
|
|
7673
|
+
|
|
7674
|
+
// src/context-command.ts
|
|
7675
|
+
async function contextCommand(parsed, deps = {}) {
|
|
7676
|
+
assertArgs(
|
|
7677
|
+
parsed,
|
|
7678
|
+
[
|
|
7679
|
+
"config",
|
|
7680
|
+
"context",
|
|
7681
|
+
"platform",
|
|
7682
|
+
"app",
|
|
7683
|
+
"env",
|
|
7684
|
+
"token",
|
|
7685
|
+
"json",
|
|
7686
|
+
"yes"
|
|
7687
|
+
],
|
|
7688
|
+
3
|
|
7689
|
+
);
|
|
7690
|
+
const action = parsed.positionals[1];
|
|
7691
|
+
const out = deps.stdout ?? console;
|
|
7692
|
+
if (action === "list") {
|
|
7693
|
+
const profiles = listOperatorProfiles();
|
|
7694
|
+
if (parsed.options.json === true) {
|
|
7695
|
+
out.log(JSON.stringify({ schemaVersion: 1, profiles }, null, 2));
|
|
7696
|
+
return;
|
|
7697
|
+
}
|
|
7698
|
+
if (profiles.length === 0) {
|
|
7699
|
+
out.log("No named operator contexts.");
|
|
7700
|
+
return;
|
|
7701
|
+
}
|
|
7702
|
+
out.log("name platform app environment");
|
|
7703
|
+
for (const profile of profiles) {
|
|
7704
|
+
out.log(
|
|
7705
|
+
`${profile.name} ${profile.platform} ${profile.app ?? ""} ${profile.environment ?? ""}`
|
|
7706
|
+
);
|
|
7707
|
+
}
|
|
7708
|
+
return;
|
|
7709
|
+
}
|
|
7710
|
+
if (action === "save") {
|
|
7711
|
+
if (parsed.options.token !== void 0) {
|
|
7712
|
+
throw new Error(
|
|
7713
|
+
"context save does not accept --token; contexts store scope metadata only"
|
|
7714
|
+
);
|
|
7715
|
+
}
|
|
7716
|
+
const name = requireName(parsed);
|
|
7717
|
+
const context2 = await resolveOperatorContext(parsed, {
|
|
7718
|
+
allowMissingConfig: true
|
|
7719
|
+
});
|
|
7720
|
+
const profile = {
|
|
7721
|
+
platform: context2.platform.value,
|
|
7722
|
+
...context2.app.value ? { app: context2.app.value } : {},
|
|
7723
|
+
...context2.environment.value ? { environment: context2.environment.value } : {}
|
|
7724
|
+
};
|
|
7725
|
+
saveOperatorProfile(name, profile, context2.profile.file);
|
|
7726
|
+
if (parsed.options.json === true) {
|
|
7727
|
+
out.log(JSON.stringify({ schemaVersion: 1, name, ...profile }, null, 2));
|
|
7728
|
+
} else {
|
|
7729
|
+
out.log(`saved operator context "${name}" (${profile.platform})`);
|
|
7730
|
+
}
|
|
7731
|
+
return;
|
|
7732
|
+
}
|
|
7733
|
+
if (action === "remove") {
|
|
7734
|
+
const name = requireName(parsed);
|
|
7735
|
+
if (parsed.options.yes !== true) {
|
|
7736
|
+
throw new Error(`context remove "${name}" requires --yes`);
|
|
7737
|
+
}
|
|
7738
|
+
const removed = removeOperatorProfile(name);
|
|
7739
|
+
const result2 = {
|
|
7740
|
+
schemaVersion: 1,
|
|
7741
|
+
name,
|
|
7742
|
+
removed,
|
|
7743
|
+
credentialCachesRemoved: false,
|
|
7744
|
+
grantsRevoked: false
|
|
7745
|
+
};
|
|
7746
|
+
if (parsed.options.json === true) out.log(JSON.stringify(result2, null, 2));
|
|
7747
|
+
else {
|
|
7748
|
+
const status = removed ? `removed operator context "${name}"` : `operator context "${name}" was already absent`;
|
|
7749
|
+
out.log(`${status}; credential caches were not deleted or revoked`);
|
|
7750
|
+
}
|
|
7751
|
+
return;
|
|
7752
|
+
}
|
|
7753
|
+
if (action !== "show") {
|
|
7754
|
+
throw new Error(
|
|
7755
|
+
`unknown context action "${action ?? ""}". Try show|list|save|remove.`
|
|
7756
|
+
);
|
|
7757
|
+
}
|
|
7758
|
+
const context = await resolveOperatorContext(parsed, {
|
|
7759
|
+
allowMissingConfig: true,
|
|
7760
|
+
defaultEnvironment: "prod"
|
|
7761
|
+
});
|
|
7762
|
+
const token = developerTokenStatus(context, parsed);
|
|
7763
|
+
const result = {
|
|
7764
|
+
schemaVersion: 1,
|
|
7765
|
+
profile: context.profile,
|
|
7766
|
+
config: context.config,
|
|
7767
|
+
platform: context.platform,
|
|
7768
|
+
app: context.app,
|
|
7769
|
+
environment: context.environment,
|
|
7770
|
+
authentication: {
|
|
7771
|
+
developerToken: token,
|
|
7772
|
+
scopedTokenCacheFile: context.credentials.scopedTokenFile,
|
|
7773
|
+
handshakeStarted: false
|
|
7774
|
+
}
|
|
7775
|
+
};
|
|
7776
|
+
if (parsed.options.json === true) {
|
|
7777
|
+
out.log(JSON.stringify(result, null, 2));
|
|
7778
|
+
return;
|
|
7779
|
+
}
|
|
7780
|
+
out.log(`config: ${context.config.status} (${context.config.path})`);
|
|
7781
|
+
out.log(
|
|
7782
|
+
`context: ${context.profile.name ?? "(none)"} (${context.profile.source}; ${context.profile.file})`
|
|
7783
|
+
);
|
|
7784
|
+
out.log(
|
|
7785
|
+
`platform: ${context.platform.value} (${context.platform.source})`
|
|
7786
|
+
);
|
|
7787
|
+
out.log(
|
|
7788
|
+
`app: ${context.app.value ?? "(unresolved)"} (${context.app.source})`
|
|
7789
|
+
);
|
|
7790
|
+
out.log(
|
|
7791
|
+
`env: ${context.environment.value ?? "(unresolved)"} (${context.environment.source})`
|
|
7792
|
+
);
|
|
7793
|
+
out.log(
|
|
7794
|
+
`auth: ${token.source} (cache ${token.cacheStatus}: ${token.cacheFile})`
|
|
7795
|
+
);
|
|
7796
|
+
out.log("handshake: not started");
|
|
7797
|
+
}
|
|
7798
|
+
function requireName(parsed) {
|
|
7799
|
+
const name = parsed.positionals[2]?.trim();
|
|
7800
|
+
if (!name) {
|
|
7801
|
+
throw new Error(`context ${parsed.positionals[1]} needs a profile name`);
|
|
7802
|
+
}
|
|
7803
|
+
return name;
|
|
7804
|
+
}
|
|
7805
|
+
|
|
7399
7806
|
// src/discuss-actions.ts
|
|
7400
7807
|
var writeMutationId = (parsed) => stringOpt(parsed.options["mutation-id"]) ?? crypto.randomUUID();
|
|
7401
7808
|
async function request(ctx, method, path, body) {
|
|
@@ -7447,8 +7854,9 @@ async function discussGroups(ctx) {
|
|
|
7447
7854
|
}
|
|
7448
7855
|
async function discussList(ctx, parsed) {
|
|
7449
7856
|
const query = new URLSearchParams();
|
|
7857
|
+
const app = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
7858
|
+
if (app) query.set("app", app);
|
|
7450
7859
|
for (const [flag, param] of [
|
|
7451
|
-
["app", "app"],
|
|
7452
7860
|
["q", "q"],
|
|
7453
7861
|
["state", "state"],
|
|
7454
7862
|
["limit", "limit"],
|
|
@@ -7524,7 +7932,7 @@ function renderRead(ctx, topic, posts) {
|
|
|
7524
7932
|
}
|
|
7525
7933
|
}
|
|
7526
7934
|
async function discussPost(ctx, parsed) {
|
|
7527
|
-
const appId = stringOpt(parsed.options.app);
|
|
7935
|
+
const appId = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
7528
7936
|
if (!appId) throw new Error("discuss post needs --app <appId>");
|
|
7529
7937
|
const subject = stringOpt(parsed.options.subject);
|
|
7530
7938
|
if (!subject) throw new Error('discuss post needs --subject "\u2026"');
|
|
@@ -7556,7 +7964,7 @@ async function discussResolve(ctx, id, resolved, parsed) {
|
|
|
7556
7964
|
}
|
|
7557
7965
|
async function discussWho(ctx, parsed) {
|
|
7558
7966
|
const query = new URLSearchParams({ q: stringOpt(parsed.options.q) ?? "" });
|
|
7559
|
-
const app = stringOpt(parsed.options.app);
|
|
7967
|
+
const app = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
7560
7968
|
if (app) query.set("app", app);
|
|
7561
7969
|
const kinds = stringOpt(parsed.options.kinds);
|
|
7562
7970
|
if (kinds) query.set("kinds", kinds);
|
|
@@ -7660,14 +8068,14 @@ function jsonl(ctx, parsed, value) {
|
|
|
7660
8068
|
}
|
|
7661
8069
|
async function discussWatch(ctx, topicId, parsed) {
|
|
7662
8070
|
if (ctx.json && parsed.options.jsonl === true) throw new Error("--json and --jsonl cannot be combined");
|
|
7663
|
-
const sleep = ctx.sleep ?? ((ms) => new Promise((
|
|
8071
|
+
const sleep = ctx.sleep ?? ((ms) => new Promise((resolve12) => setTimeout(resolve12, ms)));
|
|
7664
8072
|
const now = ctx.now ?? Date.now;
|
|
7665
8073
|
const intervalMs = (numberOpt2(parsed, "interval", DEFAULT_INTERVAL_MS / 1e3) ?? DEFAULT_INTERVAL_MS / 1e3) * 1e3;
|
|
7666
8074
|
const timeoutSeconds = numberOpt2(parsed, "timeout");
|
|
7667
8075
|
const deadline = timeoutSeconds === void 0 ? void 0 : now() + timeoutSeconds * 1e3;
|
|
7668
8076
|
const by = stringOpt(parsed.options.by);
|
|
7669
8077
|
const self = stringOpt(parsed.options.self);
|
|
7670
|
-
const app = stringOpt(parsed.options.app);
|
|
8078
|
+
const app = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
7671
8079
|
let cursor = stringOpt(parsed.options.cursor);
|
|
7672
8080
|
let firstSuccess = true;
|
|
7673
8081
|
let consecutiveFailures = 0;
|
|
@@ -7812,14 +8220,19 @@ var ALLOWED = [
|
|
|
7812
8220
|
"timeout",
|
|
7813
8221
|
"cursor",
|
|
7814
8222
|
"jsonl",
|
|
7815
|
-
"mutation-id"
|
|
8223
|
+
"mutation-id",
|
|
8224
|
+
"platform",
|
|
8225
|
+
"context"
|
|
7816
8226
|
];
|
|
7817
8227
|
function requireId(id, action) {
|
|
7818
8228
|
if (!id) throw new Error(`"discuss ${action}" needs a topic id`);
|
|
7819
8229
|
return id;
|
|
7820
8230
|
}
|
|
7821
8231
|
async function buildContext(parsed, deps) {
|
|
7822
|
-
const
|
|
8232
|
+
const context = await resolveOperatorContext(parsed, {
|
|
8233
|
+
allowMissingConfig: true
|
|
8234
|
+
});
|
|
8235
|
+
const { cfg } = context;
|
|
7823
8236
|
const doFetch = deps.fetch ?? fetch;
|
|
7824
8237
|
const out = deps.stdout ?? console;
|
|
7825
8238
|
const token = await getDeveloperToken(
|
|
@@ -7833,7 +8246,16 @@ async function buildContext(parsed, deps) {
|
|
|
7833
8246
|
doFetch,
|
|
7834
8247
|
out
|
|
7835
8248
|
);
|
|
7836
|
-
return {
|
|
8249
|
+
return {
|
|
8250
|
+
platformUrl: cfg.platformUrl,
|
|
8251
|
+
token,
|
|
8252
|
+
doFetch,
|
|
8253
|
+
out,
|
|
8254
|
+
json: parsed.options.json === true,
|
|
8255
|
+
// Preserve Discussions' existing cross-project default for config-backed
|
|
8256
|
+
// use while allowing remote agents to scope via ODLA_APP_ID or a profile.
|
|
8257
|
+
appId: context.app.source === "environment" || context.app.source === "profile" ? context.app.value ?? void 0 : void 0
|
|
8258
|
+
};
|
|
7837
8259
|
}
|
|
7838
8260
|
async function discussCommand(parsed, deps = {}) {
|
|
7839
8261
|
assertArgs(parsed, ALLOWED, 3);
|
|
@@ -7945,7 +8367,7 @@ async function pmList(ctx, entity, parsed) {
|
|
|
7945
8367
|
assignee: "assigneeId",
|
|
7946
8368
|
decision: "decisionId"
|
|
7947
8369
|
};
|
|
7948
|
-
const app = stringOpt(parsed.options.app);
|
|
8370
|
+
const app = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
7949
8371
|
if (app) q.set("app", app);
|
|
7950
8372
|
for (const [flag, param] of Object.entries(filters)) {
|
|
7951
8373
|
const v = stringOpt(parsed.options[flag]);
|
|
@@ -7964,7 +8386,7 @@ async function pmList(ctx, entity, parsed) {
|
|
|
7964
8386
|
});
|
|
7965
8387
|
}
|
|
7966
8388
|
async function pmAdd(ctx, entity, parsed) {
|
|
7967
|
-
const appId = stringOpt(parsed.options.app);
|
|
8389
|
+
const appId = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
7968
8390
|
if (!appId) throw new Error("pm add needs --app <appId>");
|
|
7969
8391
|
const input = collectEntityFields(entity, parsed, false);
|
|
7970
8392
|
if (!input.title) throw new Error("pm add needs --title <title>");
|
|
@@ -8015,7 +8437,7 @@ async function allRecords(ctx, entity, appId) {
|
|
|
8015
8437
|
}
|
|
8016
8438
|
}
|
|
8017
8439
|
async function pmHandoff(ctx, parsed) {
|
|
8018
|
-
const appId = stringOpt(parsed.options.app);
|
|
8440
|
+
const appId = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
8019
8441
|
if (!appId) throw new Error("pm handoff needs --app <appId>");
|
|
8020
8442
|
const [goals, tasks, bugs] = await Promise.all([
|
|
8021
8443
|
allRecords(ctx, "goal", appId),
|
|
@@ -8087,37 +8509,62 @@ var ALIASES = {
|
|
|
8087
8509
|
decision: "decision",
|
|
8088
8510
|
bug: "bug"
|
|
8089
8511
|
};
|
|
8090
|
-
var
|
|
8091
|
-
|
|
8092
|
-
"
|
|
8093
|
-
"
|
|
8094
|
-
|
|
8095
|
-
"
|
|
8096
|
-
"
|
|
8097
|
-
"
|
|
8098
|
-
|
|
8099
|
-
|
|
8100
|
-
|
|
8101
|
-
|
|
8102
|
-
|
|
8103
|
-
|
|
8104
|
-
|
|
8105
|
-
|
|
8106
|
-
|
|
8107
|
-
|
|
8108
|
-
|
|
8109
|
-
|
|
8110
|
-
|
|
8111
|
-
|
|
8112
|
-
|
|
8113
|
-
|
|
8114
|
-
|
|
8512
|
+
var COMMON_OPTIONS = ["config", "token", "email", "json", "platform", "context"];
|
|
8513
|
+
var ACTION_OPTIONS = {
|
|
8514
|
+
list: ["app", "q", "limit", "offset"],
|
|
8515
|
+
add: ["app", "title", "mutation-id"],
|
|
8516
|
+
get: [],
|
|
8517
|
+
set: ["mutation-id"],
|
|
8518
|
+
done: ["mutation-id"],
|
|
8519
|
+
comment: ["body", "mutation-id"],
|
|
8520
|
+
comments: [],
|
|
8521
|
+
rm: []
|
|
8522
|
+
};
|
|
8523
|
+
var ENTITY_OPTIONS = {
|
|
8524
|
+
goal: {
|
|
8525
|
+
list: ["status"],
|
|
8526
|
+
add: ["status", "proof", "target"],
|
|
8527
|
+
set: ["title", "status", "proof", "target"],
|
|
8528
|
+
done: []
|
|
8529
|
+
},
|
|
8530
|
+
task: {
|
|
8531
|
+
list: ["column", "goal", "assignee"],
|
|
8532
|
+
add: ["column", "goal", "assignee", "due", "description", "desc", "body"],
|
|
8533
|
+
set: ["title", "column", "rank", "goal", "assignee", "due", "description", "desc", "body"],
|
|
8534
|
+
done: []
|
|
8535
|
+
},
|
|
8536
|
+
decision: {
|
|
8537
|
+
list: ["status"],
|
|
8538
|
+
add: ["status", "body"],
|
|
8539
|
+
set: ["title", "status", "body"],
|
|
8540
|
+
done: []
|
|
8541
|
+
},
|
|
8542
|
+
bug: {
|
|
8543
|
+
list: ["status", "severity", "goal", "assignee", "decision"],
|
|
8544
|
+
add: ["status", "severity", "goal", "assignee", "decision", "description", "desc", "body"],
|
|
8545
|
+
set: ["title", "status", "severity", "goal", "assignee", "decision", "description", "desc", "body"],
|
|
8546
|
+
done: ["decision"]
|
|
8547
|
+
}
|
|
8548
|
+
};
|
|
8549
|
+
function canonicalAction(action) {
|
|
8550
|
+
if (action === "create") return "add";
|
|
8551
|
+
if (action === "update" || action === "status" || action === "move") return "set";
|
|
8552
|
+
if (action === "delete") return "rm";
|
|
8553
|
+
return action in ACTION_OPTIONS ? action : null;
|
|
8554
|
+
}
|
|
8555
|
+
function allowedOptions(entity, action) {
|
|
8556
|
+
const entityOptions = action === "list" || action === "add" || action === "set" || action === "done" ? ENTITY_OPTIONS[entity][action] : [];
|
|
8557
|
+
return [...COMMON_OPTIONS, ...ACTION_OPTIONS[action], ...entityOptions];
|
|
8558
|
+
}
|
|
8115
8559
|
function requireId2(id, action) {
|
|
8116
8560
|
if (!id) throw new Error(`"pm ... ${action}" needs an item id`);
|
|
8117
8561
|
return id;
|
|
8118
8562
|
}
|
|
8119
8563
|
async function buildContext2(parsed, deps) {
|
|
8120
|
-
const
|
|
8564
|
+
const context = await resolveOperatorContext(parsed, {
|
|
8565
|
+
allowMissingConfig: true
|
|
8566
|
+
});
|
|
8567
|
+
const { cfg } = context;
|
|
8121
8568
|
const doFetch = deps.fetch ?? fetch;
|
|
8122
8569
|
const out = deps.stdout ?? console;
|
|
8123
8570
|
const token = await getDeveloperToken(
|
|
@@ -8126,32 +8573,39 @@ async function buildContext2(parsed, deps) {
|
|
|
8126
8573
|
doFetch,
|
|
8127
8574
|
out
|
|
8128
8575
|
);
|
|
8129
|
-
return {
|
|
8576
|
+
return {
|
|
8577
|
+
platformUrl: cfg.platformUrl,
|
|
8578
|
+
token,
|
|
8579
|
+
doFetch,
|
|
8580
|
+
out,
|
|
8581
|
+
json: parsed.options.json === true,
|
|
8582
|
+
// Preserve PM's existing cross-project default when a config is present;
|
|
8583
|
+
// only an explicit remote-agent environment or profile selects an app.
|
|
8584
|
+
appId: context.app.source === "environment" || context.app.source === "profile" ? context.app.value ?? void 0 : void 0
|
|
8585
|
+
};
|
|
8130
8586
|
}
|
|
8131
8587
|
async function pmCommand(parsed, deps = {}) {
|
|
8132
8588
|
const word = parsed.positionals[1] ?? "";
|
|
8133
8589
|
if (word === "handoff") {
|
|
8134
|
-
assertArgs(parsed,
|
|
8590
|
+
assertArgs(parsed, [...COMMON_OPTIONS, "app"], 2);
|
|
8135
8591
|
return pmHandoff(await buildContext2(parsed, deps), parsed);
|
|
8136
8592
|
}
|
|
8137
8593
|
const entity = ALIASES[word];
|
|
8138
8594
|
if (!entity) throw new Error(`unknown pm entity "${word}". Try "odla-ai pm bug list" (goal|task|decision|bug).`);
|
|
8139
|
-
const
|
|
8140
|
-
|
|
8595
|
+
const requestedAction = parsed.positionals[2] ?? "list";
|
|
8596
|
+
const action = canonicalAction(requestedAction);
|
|
8597
|
+
if (!action) throw new Error(`unknown pm action "${requestedAction}". Try list|add|get|set|done|comment|comments|rm.`);
|
|
8598
|
+
assertArgs(parsed, allowedOptions(entity, action), 4);
|
|
8141
8599
|
const ctx = await buildContext2(parsed, deps);
|
|
8142
8600
|
const id = parsed.positionals[3];
|
|
8143
8601
|
switch (action) {
|
|
8144
8602
|
case "list":
|
|
8145
8603
|
return pmList(ctx, entity, parsed);
|
|
8146
8604
|
case "add":
|
|
8147
|
-
case "create":
|
|
8148
8605
|
return pmAdd(ctx, entity, parsed);
|
|
8149
8606
|
case "get":
|
|
8150
8607
|
return pmGet(ctx, entity, requireId2(id, action));
|
|
8151
8608
|
case "set":
|
|
8152
|
-
case "update":
|
|
8153
|
-
case "status":
|
|
8154
|
-
case "move":
|
|
8155
8609
|
return pmSet(ctx, entity, requireId2(id, action), parsed);
|
|
8156
8610
|
case "done":
|
|
8157
8611
|
return pmDone(ctx, entity, requireId2(id, action), parsed);
|
|
@@ -8160,10 +8614,7 @@ async function pmCommand(parsed, deps = {}) {
|
|
|
8160
8614
|
case "comments":
|
|
8161
8615
|
return pmComments(ctx, entity, requireId2(id, action));
|
|
8162
8616
|
case "rm":
|
|
8163
|
-
case "delete":
|
|
8164
8617
|
return pmRemove(ctx, entity, requireId2(id, action));
|
|
8165
|
-
default:
|
|
8166
|
-
throw new Error(`unknown pm action "${action}". Try list|add|get|set|done|comment|comments|rm.`);
|
|
8167
8618
|
}
|
|
8168
8619
|
}
|
|
8169
8620
|
|
|
@@ -8175,6 +8626,20 @@ function statusVerdict(reads) {
|
|
|
8175
8626
|
sourceHttp(reasons, "canary", reads.canary);
|
|
8176
8627
|
sourceHttp(reasons, "collector", reads.collector);
|
|
8177
8628
|
sourceHttp(reasons, "provider", reads.provider);
|
|
8629
|
+
if (reads.providerCapacity) {
|
|
8630
|
+
sourceHttp(
|
|
8631
|
+
reasons,
|
|
8632
|
+
"provider",
|
|
8633
|
+
reads.providerCapacity,
|
|
8634
|
+
"provider_capacity_"
|
|
8635
|
+
);
|
|
8636
|
+
}
|
|
8637
|
+
sourceHttp(
|
|
8638
|
+
reasons,
|
|
8639
|
+
"provider",
|
|
8640
|
+
reads.providerReconciliation,
|
|
8641
|
+
"provider_reconciliation_"
|
|
8642
|
+
);
|
|
8178
8643
|
sourceHttp(reasons, "provider", reads.providerHistory, "provider_history_");
|
|
8179
8644
|
const liveStatus = String(reads.liveSync.body.status ?? "");
|
|
8180
8645
|
if (liveStatus === "partial") {
|
|
@@ -8247,6 +8712,17 @@ function statusVerdict(reads) {
|
|
|
8247
8712
|
severity: "degraded"
|
|
8248
8713
|
});
|
|
8249
8714
|
}
|
|
8715
|
+
const reconciliationStatus = String(
|
|
8716
|
+
reads.providerReconciliation.body.status ?? ""
|
|
8717
|
+
);
|
|
8718
|
+
if (reads.providerReconciliation.httpStatus >= 200 && reads.providerReconciliation.httpStatus < 300 && reconciliationStatus && reconciliationStatus !== "within_budget") {
|
|
8719
|
+
const reconciliationReason = reads.providerReconciliation.body.reason;
|
|
8720
|
+
reasons.push({
|
|
8721
|
+
source: "provider",
|
|
8722
|
+
code: reconciliationStatus === "outside_budget" ? typeof reconciliationReason === "string" && reconciliationReason ? reconciliationReason : "request_coverage_outside_budget" : `request_reconciliation_${reconciliationStatus}`,
|
|
8723
|
+
severity: "degraded"
|
|
8724
|
+
});
|
|
8725
|
+
}
|
|
8250
8726
|
const providerHistoryStatus = String(reads.providerHistory.body.status ?? "");
|
|
8251
8727
|
if (providerHistoryStatus && providerHistoryStatus !== "observed" && providerHistoryStatus !== "no_data") {
|
|
8252
8728
|
reasons.push({
|
|
@@ -8317,8 +8793,19 @@ function printO11yStatus(status, out) {
|
|
|
8317
8793
|
`collector ${status.collector.httpStatus} ${String(status.collector.body.status ?? status.collector.body.error ?? "unavailable")} ${numeric3(collectorStorage.affectedPoints)} affected points`
|
|
8318
8794
|
);
|
|
8319
8795
|
const providerMetrics = record6(status.provider.body.metrics) ? status.provider.body.metrics : {};
|
|
8796
|
+
const providerCapacity = record6(status.provider.body.capacity) ? status.provider.body.capacity : {};
|
|
8797
|
+
const workerMemory = record6(providerCapacity.memory) ? providerCapacity.memory : {};
|
|
8320
8798
|
out.log(
|
|
8321
|
-
`cloudflare ${status.provider.httpStatus} ${String(status.provider.body.status ?? status.provider.body.error ?? "unavailable")} ${numeric3(providerMetrics.requests)} invocations ${numeric3(providerMetrics.errors)} runtime errors`
|
|
8799
|
+
`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`
|
|
8800
|
+
);
|
|
8801
|
+
for (const line of providerCapacityLines(status.providerCapacity)) {
|
|
8802
|
+
out.log(line);
|
|
8803
|
+
}
|
|
8804
|
+
const coverage = record6(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
|
|
8805
|
+
const coverageCounts = record6(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
|
|
8806
|
+
const coverageBudget = record6(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
|
|
8807
|
+
out.log(
|
|
8808
|
+
`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
8809
|
);
|
|
8323
8810
|
const providerPoints = Array.isArray(status.providerHistory.body.points) ? status.providerHistory.body.points.length : 0;
|
|
8324
8811
|
const providerFreshness = record6(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
|
|
@@ -8329,6 +8816,27 @@ function printO11yStatus(status, out) {
|
|
|
8329
8816
|
`verdict ${status.verdict.status} ${status.verdict.reasons.map((reason) => `${reason.source}:${reason.code}`).join(", ") || "all required signals healthy"}`
|
|
8330
8817
|
);
|
|
8331
8818
|
}
|
|
8819
|
+
function providerCapacityLines(read3) {
|
|
8820
|
+
const resources = record6(read3.body.resources) ? read3.body.resources : {};
|
|
8821
|
+
const durableObjects = record6(resources.durableObjects) ? resources.durableObjects : {};
|
|
8822
|
+
const periodic = record6(durableObjects.periodic) ? durableObjects.periodic : {};
|
|
8823
|
+
const storage = record6(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
|
|
8824
|
+
const d1 = record6(resources.d1) ? resources.d1 : {};
|
|
8825
|
+
const d1Activity = record6(d1.activity) ? d1.activity : {};
|
|
8826
|
+
const d1Storage = record6(d1.storage) ? d1.storage : {};
|
|
8827
|
+
const d1Latency = record6(d1Activity.latency) ? d1Activity.latency : {};
|
|
8828
|
+
const r2 = record6(resources.r2) ? resources.r2 : {};
|
|
8829
|
+
const r2Operations = record6(r2.operations) ? r2.operations : {};
|
|
8830
|
+
const r2Storage = record6(r2.storage) ? r2.storage : {};
|
|
8831
|
+
const status = String(
|
|
8832
|
+
read3.body.status ?? read3.body.error ?? "unavailable"
|
|
8833
|
+
);
|
|
8834
|
+
return [
|
|
8835
|
+
`cloudflare-capacity ${read3.httpStatus} ${status} ${optionalBytes(storage.storedBytes)} SQLite DO storage ${optionalCount(periodic.activeWebsocketConnectionsMax, "active socket max")} ${optionalAgeSeconds(storage.ageSeconds)} storage evidence age`,
|
|
8836
|
+
`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`,
|
|
8837
|
+
`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`
|
|
8838
|
+
];
|
|
8839
|
+
}
|
|
8332
8840
|
function liveSyncLine(read3) {
|
|
8333
8841
|
const performance = record6(read3.body.performance) ? read3.body.performance : {};
|
|
8334
8842
|
const commitToSend = record6(performance.commitToSend) ? performance.commitToSend : {};
|
|
@@ -8347,12 +8855,42 @@ function optionalAge(value) {
|
|
|
8347
8855
|
if (typeof value !== "number" || !Number.isFinite(value)) return "unknown";
|
|
8348
8856
|
return `${Math.max(0, Math.round(value / 1e3))}s`;
|
|
8349
8857
|
}
|
|
8858
|
+
function optionalPercent(value) {
|
|
8859
|
+
return typeof value === "number" && Number.isFinite(value) ? `${Math.round(value * 100)}%` : "\u2014";
|
|
8860
|
+
}
|
|
8861
|
+
function optionalBytes(value) {
|
|
8862
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return "\u2014";
|
|
8863
|
+
if (value >= 1024 * 1024 * 1024) {
|
|
8864
|
+
return `${(value / (1024 * 1024 * 1024)).toFixed(2)} GiB`;
|
|
8865
|
+
}
|
|
8866
|
+
if (value >= 1024 * 1024) {
|
|
8867
|
+
return `${(value / (1024 * 1024)).toFixed(1)} MiB`;
|
|
8868
|
+
}
|
|
8869
|
+
if (value >= 1024) return `${(value / 1024).toFixed(1)} KiB`;
|
|
8870
|
+
return `${Math.round(value)} B`;
|
|
8871
|
+
}
|
|
8872
|
+
function optionalCount(value, unit) {
|
|
8873
|
+
return typeof value === "number" && Number.isFinite(value) ? `${value} ${unit}` : `\u2014 ${unit}`;
|
|
8874
|
+
}
|
|
8875
|
+
function optionalAgeSeconds(value) {
|
|
8876
|
+
return typeof value === "number" && Number.isFinite(value) ? `${Math.max(0, Math.round(value))}s` : "unknown";
|
|
8877
|
+
}
|
|
8350
8878
|
|
|
8351
8879
|
// src/o11y-command.ts
|
|
8352
8880
|
async function o11yCommand(parsed, deps = {}) {
|
|
8353
8881
|
assertArgs(
|
|
8354
8882
|
parsed,
|
|
8355
|
-
[
|
|
8883
|
+
[
|
|
8884
|
+
"config",
|
|
8885
|
+
"context",
|
|
8886
|
+
"platform",
|
|
8887
|
+
"token",
|
|
8888
|
+
"email",
|
|
8889
|
+
"json",
|
|
8890
|
+
"app",
|
|
8891
|
+
"env",
|
|
8892
|
+
"minutes"
|
|
8893
|
+
],
|
|
8356
8894
|
2
|
|
8357
8895
|
);
|
|
8358
8896
|
const action = parsed.positionals[1];
|
|
@@ -8364,9 +8902,12 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
8364
8902
|
const minutes = statusMinutes(
|
|
8365
8903
|
numberOpt(parsed.options.minutes, "--minutes") ?? 60
|
|
8366
8904
|
);
|
|
8367
|
-
const
|
|
8368
|
-
|
|
8369
|
-
|
|
8905
|
+
const context = await resolveOperatorContext(parsed, {
|
|
8906
|
+
allowMissingConfig: true,
|
|
8907
|
+
defaultEnvironment: "prod",
|
|
8908
|
+
requireApp: true
|
|
8909
|
+
});
|
|
8910
|
+
const cfg = context.cfg;
|
|
8370
8911
|
const doFetch = deps.fetch ?? fetch;
|
|
8371
8912
|
const out = deps.stdout ?? console;
|
|
8372
8913
|
const token = await getDeveloperToken(
|
|
@@ -8380,8 +8921,8 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
8380
8921
|
doFetch,
|
|
8381
8922
|
out
|
|
8382
8923
|
);
|
|
8383
|
-
const appId =
|
|
8384
|
-
const env =
|
|
8924
|
+
const appId = context.app.value;
|
|
8925
|
+
const env = context.environment.value;
|
|
8385
8926
|
const base = `${cfg.platformUrl}/o11y/${encodeURIComponent(appId)}`;
|
|
8386
8927
|
const query = new URLSearchParams({ env, minutes: String(minutes) });
|
|
8387
8928
|
const headers = { authorization: `Bearer ${token}` };
|
|
@@ -8398,6 +8939,8 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
8398
8939
|
canary,
|
|
8399
8940
|
collector,
|
|
8400
8941
|
provider,
|
|
8942
|
+
providerCapacity,
|
|
8943
|
+
providerReconciliation,
|
|
8401
8944
|
providerHistory
|
|
8402
8945
|
] = await Promise.all([
|
|
8403
8946
|
read2(`${base}/metrics/red?${query}`, headers, doFetch),
|
|
@@ -8414,6 +8957,19 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
8414
8957
|
),
|
|
8415
8958
|
read2(`${base}/self-health?${query}`, headers, doFetch),
|
|
8416
8959
|
read2(`${base}/provider/cloudflare?${query}`, headers, doFetch),
|
|
8960
|
+
appId === "odla-db" ? read2(`${base}/provider/cloudflare/capacity`, headers, doFetch) : Promise.resolve({
|
|
8961
|
+
httpStatus: 204,
|
|
8962
|
+
body: {
|
|
8963
|
+
status: "not_applicable",
|
|
8964
|
+
scope: "cloudflare_account",
|
|
8965
|
+
reason: "account-scoped Durable Object capacity is exposed only through odla-db"
|
|
8966
|
+
}
|
|
8967
|
+
}),
|
|
8968
|
+
read2(
|
|
8969
|
+
`${base}/provider/cloudflare/reconciliation?${query}`,
|
|
8970
|
+
headers,
|
|
8971
|
+
doFetch
|
|
8972
|
+
),
|
|
8417
8973
|
read2(`${base}/provider/cloudflare/history?${query}`, headers, doFetch)
|
|
8418
8974
|
]);
|
|
8419
8975
|
const verdict = statusVerdict({
|
|
@@ -8422,10 +8978,12 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
8422
8978
|
canary,
|
|
8423
8979
|
collector,
|
|
8424
8980
|
provider,
|
|
8981
|
+
providerCapacity,
|
|
8982
|
+
providerReconciliation,
|
|
8425
8983
|
providerHistory
|
|
8426
8984
|
});
|
|
8427
8985
|
const status = {
|
|
8428
|
-
schemaVersion:
|
|
8986
|
+
schemaVersion: 9,
|
|
8429
8987
|
scope: { appId, env, minutes },
|
|
8430
8988
|
observedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8431
8989
|
verdict,
|
|
@@ -8435,6 +8993,8 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
8435
8993
|
canary,
|
|
8436
8994
|
collector,
|
|
8437
8995
|
provider,
|
|
8996
|
+
providerCapacity,
|
|
8997
|
+
providerReconciliation,
|
|
8438
8998
|
providerHistory
|
|
8439
8999
|
};
|
|
8440
9000
|
if (parsed.options.json === true) {
|
|
@@ -8467,7 +9027,7 @@ async function read2(url, headers, doFetch) {
|
|
|
8467
9027
|
// src/provision.ts
|
|
8468
9028
|
var import_apps5 = require("@odla-ai/apps");
|
|
8469
9029
|
var import_ai3 = require("@odla-ai/ai");
|
|
8470
|
-
var
|
|
9030
|
+
var import_node_process10 = __toESM(require("process"), 1);
|
|
8471
9031
|
|
|
8472
9032
|
// src/integration-provision.ts
|
|
8473
9033
|
var import_db3 = require("@odla-ai/db");
|
|
@@ -8826,7 +9386,7 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
|
|
|
8826
9386
|
await provisionIntegrationSeeds(doFetch, cfg.dbEndpoint, tenantId, dbKey, database.integrations, env, out);
|
|
8827
9387
|
}
|
|
8828
9388
|
if (cfg.services.includes("ai") && cfg.ai?.provider && cfg.ai.keyEnv) {
|
|
8829
|
-
const key =
|
|
9389
|
+
const key = import_node_process10.default.env[cfg.ai.keyEnv];
|
|
8830
9390
|
if (key) {
|
|
8831
9391
|
const secretName = cfg.ai.secretName ?? defaultSecretName(cfg.ai.provider);
|
|
8832
9392
|
await (0, import_ai3.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, secretName, key);
|
|
@@ -8867,8 +9427,8 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
|
|
|
8867
9427
|
}
|
|
8868
9428
|
|
|
8869
9429
|
// src/record.ts
|
|
8870
|
-
var
|
|
8871
|
-
var
|
|
9430
|
+
var import_node_fs15 = require("fs");
|
|
9431
|
+
var import_node_process11 = __toESM(require("process"), 1);
|
|
8872
9432
|
|
|
8873
9433
|
// src/surface.ts
|
|
8874
9434
|
var PM_ACTIONS = {
|
|
@@ -8916,6 +9476,7 @@ var COMMAND_SURFACE = {
|
|
|
8916
9476
|
calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
|
|
8917
9477
|
capabilities: {},
|
|
8918
9478
|
code: { connect: {} },
|
|
9479
|
+
context: { show: {}, list: {}, save: {}, remove: {} },
|
|
8919
9480
|
// `watch`, `read`, `reply`, and `resolve` take a topic id from there on.
|
|
8920
9481
|
discuss: {
|
|
8921
9482
|
groups: {},
|
|
@@ -9019,27 +9580,22 @@ function surfacePaths(node = COMMAND_SURFACE, prefix = []) {
|
|
|
9019
9580
|
|
|
9020
9581
|
// src/record.ts
|
|
9021
9582
|
function recordInvocation(parsed) {
|
|
9022
|
-
const file =
|
|
9583
|
+
const file = import_node_process11.default.env.ODLA_CLI_RECORD;
|
|
9023
9584
|
if (!file) return;
|
|
9024
9585
|
try {
|
|
9025
9586
|
const entry = {
|
|
9026
9587
|
path: invocationPath(parsed.positionals),
|
|
9027
|
-
options: Object.
|
|
9588
|
+
options: Object.entries(parsed.options).map(([name, value]) => value === false ? `no-${name}` : name).sort()
|
|
9028
9589
|
};
|
|
9029
9590
|
if (!entry.path.length) return;
|
|
9030
|
-
(0,
|
|
9591
|
+
(0, import_node_fs15.appendFileSync)(file, `${JSON.stringify(entry)}
|
|
9031
9592
|
`);
|
|
9032
9593
|
} catch {
|
|
9033
9594
|
}
|
|
9034
9595
|
}
|
|
9035
9596
|
|
|
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
9597
|
// src/runbook-actions.ts
|
|
9042
|
-
var
|
|
9598
|
+
var import_node_fs16 = require("fs");
|
|
9043
9599
|
|
|
9044
9600
|
// src/runbook-requires.ts
|
|
9045
9601
|
var SPEC = /^(@?[\w./-]+?)@(\d+\.\d+\.\d+(?:[\w.-]*)?)$/;
|
|
@@ -9124,7 +9680,7 @@ async function bySlug(ctx, slug) {
|
|
|
9124
9680
|
function readBody(file, inline) {
|
|
9125
9681
|
if (inline !== void 0) return inline;
|
|
9126
9682
|
if (file === void 0) throw new Error("supply the new text with --file <path>, --file - (stdin), or --body");
|
|
9127
|
-
return (0,
|
|
9683
|
+
return (0, import_node_fs16.readFileSync)(file === "-" ? 0 : file, "utf8");
|
|
9128
9684
|
}
|
|
9129
9685
|
var stamp = (ms) => ms ? new Date(ms).toISOString().slice(0, 16).replace("T", " ") : "";
|
|
9130
9686
|
async function runbookList(ctx, all, query) {
|
|
@@ -9211,8 +9767,8 @@ async function runbookRemove(ctx, slug) {
|
|
|
9211
9767
|
}
|
|
9212
9768
|
|
|
9213
9769
|
// src/runbook-import.ts
|
|
9214
|
-
var
|
|
9215
|
-
var
|
|
9770
|
+
var import_node_fs17 = require("fs");
|
|
9771
|
+
var import_node_path13 = require("path");
|
|
9216
9772
|
function parseRunbook(text, slug) {
|
|
9217
9773
|
let rest = text;
|
|
9218
9774
|
const meta = {};
|
|
@@ -9237,12 +9793,12 @@ function parseRunbook(text, slug) {
|
|
|
9237
9793
|
};
|
|
9238
9794
|
}
|
|
9239
9795
|
function readRunbookDir(dir) {
|
|
9240
|
-
if (!(0,
|
|
9241
|
-
const files = (0,
|
|
9796
|
+
if (!(0, import_node_fs17.statSync)(dir, { throwIfNoEntry: false })?.isDirectory()) throw new Error(`not a directory: ${dir}`);
|
|
9797
|
+
const files = (0, import_node_fs17.readdirSync)(dir).filter((f) => f.endsWith(".md")).sort();
|
|
9242
9798
|
if (!files.length) throw new Error(`no .md files in ${dir}`);
|
|
9243
9799
|
return files.map((file) => {
|
|
9244
|
-
const slug = (0,
|
|
9245
|
-
const parsed = parseRunbook((0,
|
|
9800
|
+
const slug = (0, import_node_path13.basename)(file, ".md");
|
|
9801
|
+
const parsed = parseRunbook((0, import_node_fs17.readFileSync)((0, import_node_path13.join)(dir, file), "utf8"), slug);
|
|
9246
9802
|
return { file, slug, ...parsed, words: parsed.body.split(/\s+/).filter(Boolean).length };
|
|
9247
9803
|
});
|
|
9248
9804
|
}
|
|
@@ -9314,8 +9870,8 @@ async function upsert(ctx, r, visibility) {
|
|
|
9314
9870
|
|
|
9315
9871
|
// src/runbook-impact.ts
|
|
9316
9872
|
var import_node_child_process7 = require("child_process");
|
|
9317
|
-
var
|
|
9318
|
-
var
|
|
9873
|
+
var import_node_fs18 = require("fs");
|
|
9874
|
+
var import_node_path14 = require("path");
|
|
9319
9875
|
|
|
9320
9876
|
// src/runbook-impact-scan.ts
|
|
9321
9877
|
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 +10040,10 @@ ${body.split("\n").map((line) => `+${line}`).join("\n")}
|
|
|
9484
10040
|
}
|
|
9485
10041
|
function manifestLabeller(root) {
|
|
9486
10042
|
return (workspace) => {
|
|
9487
|
-
const manifest = (0,
|
|
9488
|
-
if (!(0,
|
|
10043
|
+
const manifest = (0, import_node_path14.join)(root, workspace, "package.json");
|
|
10044
|
+
if (!(0, import_node_fs18.existsSync)(manifest)) return void 0;
|
|
9489
10045
|
try {
|
|
9490
|
-
const name = JSON.parse((0,
|
|
10046
|
+
const name = JSON.parse((0, import_node_fs18.readFileSync)(manifest, "utf8")).name;
|
|
9491
10047
|
return typeof name === "string" ? name : void 0;
|
|
9492
10048
|
} catch {
|
|
9493
10049
|
return void 0;
|
|
@@ -9554,7 +10110,7 @@ function report3(ctx, impacts) {
|
|
|
9554
10110
|
async function runbookImpact(ctx, options, deps = {}) {
|
|
9555
10111
|
const cwd = deps.cwd ?? process.cwd();
|
|
9556
10112
|
const runGit = deps.runGit ?? gitRunner(cwd);
|
|
9557
|
-
const read3 = deps.readRepoFile ?? ((path) => (0,
|
|
10113
|
+
const read3 = deps.readRepoFile ?? ((path) => (0, import_node_fs18.readFileSync)((0, import_node_path14.join)(cwd, path), "utf8"));
|
|
9558
10114
|
const surfaces = changedSurfaces(collectDiff(runGit, options.base, read3), manifestLabeller(cwd));
|
|
9559
10115
|
if (!surfaces.length) {
|
|
9560
10116
|
return ctx.out.log(
|
|
@@ -9687,12 +10243,12 @@ async function runbookComment(ctx, slug, body) {
|
|
|
9687
10243
|
|
|
9688
10244
|
// src/runbook-editor.ts
|
|
9689
10245
|
var import_node_child_process8 = require("child_process");
|
|
9690
|
-
var
|
|
9691
|
-
var
|
|
9692
|
-
var
|
|
9693
|
-
var
|
|
10246
|
+
var import_node_fs19 = require("fs");
|
|
10247
|
+
var import_node_os5 = require("os");
|
|
10248
|
+
var import_node_path15 = require("path");
|
|
10249
|
+
var import_node_process12 = __toESM(require("process"), 1);
|
|
9694
10250
|
var EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
|
|
9695
|
-
function resolveEditor(env =
|
|
10251
|
+
function resolveEditor(env = import_node_process12.default.env) {
|
|
9696
10252
|
for (const name of EDITOR_ENV) {
|
|
9697
10253
|
const value = env[name];
|
|
9698
10254
|
if (value && value.trim()) return value.trim();
|
|
@@ -9706,8 +10262,8 @@ function defaultRun(command, path) {
|
|
|
9706
10262
|
return result.status ?? 0;
|
|
9707
10263
|
}
|
|
9708
10264
|
function editText(initial, slug, deps = {}) {
|
|
9709
|
-
const env = deps.env ??
|
|
9710
|
-
const interactive = deps.interactive ?? (() => Boolean(
|
|
10265
|
+
const env = deps.env ?? import_node_process12.default.env;
|
|
10266
|
+
const interactive = deps.interactive ?? (() => Boolean(import_node_process12.default.stdin.isTTY));
|
|
9711
10267
|
const editor = resolveEditor(env);
|
|
9712
10268
|
if (!editor)
|
|
9713
10269
|
throw new Error(
|
|
@@ -9715,16 +10271,16 @@ function editText(initial, slug, deps = {}) {
|
|
|
9715
10271
|
);
|
|
9716
10272
|
if (!interactive())
|
|
9717
10273
|
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,
|
|
10274
|
+
const dir = (0, import_node_fs19.mkdtempSync)((0, import_node_path15.join)((0, import_node_os5.tmpdir)(), "odla-runbook-"));
|
|
10275
|
+
const file = (0, import_node_path15.join)(dir, `${slug}.md`);
|
|
9720
10276
|
try {
|
|
9721
|
-
(0,
|
|
10277
|
+
(0, import_node_fs19.writeFileSync)(file, initial, { mode: 384 });
|
|
9722
10278
|
const code = defaultRunOrInjected(deps)(editor, file);
|
|
9723
10279
|
if (code !== 0) throw new Error(`editor "${editor}" exited with ${code}; nothing was written`);
|
|
9724
|
-
const edited = (0,
|
|
10280
|
+
const edited = (0, import_node_fs19.readFileSync)(file, "utf8");
|
|
9725
10281
|
return edited === initial ? null : edited;
|
|
9726
10282
|
} finally {
|
|
9727
|
-
(0,
|
|
10283
|
+
(0, import_node_fs19.rmSync)(dir, { recursive: true, force: true });
|
|
9728
10284
|
}
|
|
9729
10285
|
}
|
|
9730
10286
|
var defaultRunOrInjected = (deps) => deps.run ?? defaultRun;
|
|
@@ -9762,10 +10318,16 @@ function credentialKind(identity) {
|
|
|
9762
10318
|
return identity.scopes.length ? "device token (scoped)" : "device token or session";
|
|
9763
10319
|
}
|
|
9764
10320
|
async function whoamiCommand(parsed, deps = {}) {
|
|
9765
|
-
assertArgs(
|
|
10321
|
+
assertArgs(
|
|
10322
|
+
parsed,
|
|
10323
|
+
["config", "context", "platform", "token", "email", "json"],
|
|
10324
|
+
1
|
|
10325
|
+
);
|
|
9766
10326
|
const out = deps.stdout ?? console;
|
|
9767
10327
|
const doFetch = deps.fetch ?? fetch;
|
|
9768
|
-
const cfg = await
|
|
10328
|
+
const { cfg } = await resolveOperatorContext(parsed, {
|
|
10329
|
+
allowMissingConfig: true
|
|
10330
|
+
});
|
|
9769
10331
|
const token = await getDeveloperToken(
|
|
9770
10332
|
cfg,
|
|
9771
10333
|
{
|
|
@@ -9796,7 +10358,7 @@ async function whoamiCommand(parsed, deps = {}) {
|
|
|
9796
10358
|
}
|
|
9797
10359
|
|
|
9798
10360
|
// src/runbook-command.ts
|
|
9799
|
-
var
|
|
10361
|
+
var ALLOWED2 = [
|
|
9800
10362
|
"config",
|
|
9801
10363
|
"token",
|
|
9802
10364
|
"email",
|
|
@@ -9814,34 +10376,24 @@ var ALLOWED3 = [
|
|
|
9814
10376
|
"dry-run",
|
|
9815
10377
|
"limit",
|
|
9816
10378
|
"base",
|
|
9817
|
-
"requires"
|
|
10379
|
+
"requires",
|
|
10380
|
+
"platform",
|
|
10381
|
+
"context"
|
|
9818
10382
|
];
|
|
9819
10383
|
function requireSlug(slug, action) {
|
|
9820
10384
|
if (!slug) throw new Error(`"runbook ${action}" needs a slug, e.g. "odla-ai runbook ${action} release"`);
|
|
9821
10385
|
return slug;
|
|
9822
10386
|
}
|
|
9823
10387
|
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
10388
|
async function buildContext3(parsed, deps, action) {
|
|
9840
|
-
const
|
|
9841
|
-
const
|
|
10389
|
+
const appIdOption = stringOpt(parsed.options.app);
|
|
10390
|
+
const context = await resolveOperatorContext(parsed, {
|
|
10391
|
+
allowMissingConfig: true
|
|
10392
|
+
});
|
|
10393
|
+
const { cfg } = context;
|
|
9842
10394
|
const doFetch = deps.fetch ?? fetch;
|
|
9843
10395
|
const out = deps.stdout ?? console;
|
|
9844
|
-
const appId =
|
|
10396
|
+
const appId = appIdOption ?? (context.app.source === "environment" || context.app.source === "profile" ? context.app.value : null) ?? PLATFORM_SCOPE;
|
|
9845
10397
|
const dryRun = parsed.options["dry-run"] === true;
|
|
9846
10398
|
if (action === "import" && dryRun) {
|
|
9847
10399
|
return {
|
|
@@ -9862,8 +10414,10 @@ async function buildContext3(parsed, deps, action) {
|
|
|
9862
10414
|
fetch: doFetch,
|
|
9863
10415
|
stdout: out,
|
|
9864
10416
|
openApprovalUrl: deps.openUrl,
|
|
9865
|
-
//
|
|
9866
|
-
|
|
10417
|
+
// A project, named context, or the global operator context owns the
|
|
10418
|
+
// exact-scope cache; it never follows an arbitrary shell directory.
|
|
10419
|
+
rootDir: cfg.rootDir,
|
|
10420
|
+
tokenFile: context.credentials.scopedTokenFile
|
|
9867
10421
|
}) : await getDeveloperToken(
|
|
9868
10422
|
cfg,
|
|
9869
10423
|
{
|
|
@@ -9892,7 +10446,7 @@ async function buildContext3(parsed, deps, action) {
|
|
|
9892
10446
|
}
|
|
9893
10447
|
async function runbookCommand(parsed, deps = {}) {
|
|
9894
10448
|
const action = parsed.positionals[1] ?? "list";
|
|
9895
|
-
assertArgs(parsed,
|
|
10449
|
+
assertArgs(parsed, ALLOWED2, action === "ask" || action === "search" ? 64 : 4);
|
|
9896
10450
|
const ctx = await buildContext3(parsed, deps, action);
|
|
9897
10451
|
const slug = parsed.positionals[2];
|
|
9898
10452
|
switch (action) {
|
|
@@ -10138,7 +10692,7 @@ function hostedSeverity(value, flag) {
|
|
|
10138
10692
|
var import_security2 = require("@odla-ai/security");
|
|
10139
10693
|
|
|
10140
10694
|
// src/security.ts
|
|
10141
|
-
var
|
|
10695
|
+
var import_node_path16 = require("path");
|
|
10142
10696
|
var import_security = require("@odla-ai/security");
|
|
10143
10697
|
var import_node3 = require("@odla-ai/security/node");
|
|
10144
10698
|
async function runHostedSecurity(options) {
|
|
@@ -10150,9 +10704,9 @@ async function runHostedSecurity(options) {
|
|
|
10150
10704
|
const appId = selfAudit ? "odla-ai" : cfg.app.id;
|
|
10151
10705
|
const env = selfAudit ? "prod" : selectEnv(options.env, cfg.envs, cfg.configPath, cfg.rootDir);
|
|
10152
10706
|
const platform = options.platform ?? cfg?.platformUrl ?? "https://odla.ai";
|
|
10153
|
-
const target = (0,
|
|
10154
|
-
const output = (0,
|
|
10155
|
-
const outputRelative = (0,
|
|
10707
|
+
const target = (0, import_node_path16.resolve)(options.target ?? cfg?.rootDir ?? ".");
|
|
10708
|
+
const output = (0, import_node_path16.resolve)(options.out ?? (0, import_node_path16.resolve)(target, ".odla/security/hosted"));
|
|
10709
|
+
const outputRelative = (0, import_node_path16.relative)(target, output).split(import_node_path16.sep).join("/");
|
|
10156
10710
|
if (!outputRelative) throw new Error("Hosted security output cannot be the repository root");
|
|
10157
10711
|
const profile = profileFor(options.profile ?? "odla", options.maxHuntTasks ?? 12);
|
|
10158
10712
|
const tokenRequest = {
|
|
@@ -10164,7 +10718,7 @@ async function runHostedSecurity(options) {
|
|
|
10164
10718
|
};
|
|
10165
10719
|
const token = await injectedToken(options, tokenRequest);
|
|
10166
10720
|
const snapshot = await (0, import_node3.snapshotDirectory)(target, {
|
|
10167
|
-
exclude: !outputRelative.startsWith("../") && !(0,
|
|
10721
|
+
exclude: !outputRelative.startsWith("../") && !(0, import_node_path16.isAbsolute)(outputRelative) ? [outputRelative] : []
|
|
10168
10722
|
});
|
|
10169
10723
|
const hosted = await (0, import_security.createPlatformSecurityReasoners)({
|
|
10170
10724
|
platform,
|
|
@@ -10182,7 +10736,7 @@ async function runHostedSecurity(options) {
|
|
|
10182
10736
|
});
|
|
10183
10737
|
const harness = (0, import_security.createSecurityHarness)({
|
|
10184
10738
|
profile,
|
|
10185
|
-
store: new import_node3.FileRunStore((0,
|
|
10739
|
+
store: new import_node3.FileRunStore((0, import_node_path16.resolve)(output, "state")),
|
|
10186
10740
|
discoveryReasoner: hosted.discoveryReasoner,
|
|
10187
10741
|
validationReasoner: hosted.validationReasoner,
|
|
10188
10742
|
policy: {
|
|
@@ -10206,7 +10760,7 @@ async function runHostedSecurity(options) {
|
|
|
10206
10760
|
function selectEnv(requested, declared, configPath, rootDir) {
|
|
10207
10761
|
const env = requested ?? (declared.includes("dev") ? "dev" : declared[0]);
|
|
10208
10762
|
if (!env || !declared.includes(env)) {
|
|
10209
|
-
const shown = (0,
|
|
10763
|
+
const shown = (0, import_node_path16.relative)(rootDir, configPath) || configPath;
|
|
10210
10764
|
throw new Error(`env "${env ?? ""}" is not declared in ${shown}`);
|
|
10211
10765
|
}
|
|
10212
10766
|
return env;
|
|
@@ -10235,7 +10789,7 @@ function printSummary(out, appId, env, run, report4, output) {
|
|
|
10235
10789
|
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
10790
|
if (report4.callBudget) out.log(` calls: discovery=${formatBudget(report4.callBudget.discovery)} validation=${formatBudget(report4.callBudget.validation)}`);
|
|
10237
10791
|
out.log(` findings: confirmed=${report4.metrics.confirmed} needs_reproduction=${report4.metrics.needsReproduction} candidates=${report4.metrics.candidates}`);
|
|
10238
|
-
out.log(` report: ${(0,
|
|
10792
|
+
out.log(` report: ${(0, import_node_path16.resolve)(output, "REPORT.md")}`);
|
|
10239
10793
|
}
|
|
10240
10794
|
function formatBudget(usage) {
|
|
10241
10795
|
return usage ? `${usage.usedCalls}/${usage.maxCalls} skipped=${usage.skippedCalls}` : "caller-managed";
|
|
@@ -10715,6 +11269,10 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
|
10715
11269
|
await whoamiCommand(parsed, runtime);
|
|
10716
11270
|
return;
|
|
10717
11271
|
}
|
|
11272
|
+
if (command === "context") {
|
|
11273
|
+
await contextCommand(parsed, runtime);
|
|
11274
|
+
return;
|
|
11275
|
+
}
|
|
10718
11276
|
if (command === "runbook") {
|
|
10719
11277
|
await runbookCommand(parsed, runtime);
|
|
10720
11278
|
return;
|