@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/bin.cjs
CHANGED
|
@@ -244,7 +244,7 @@ var import_node_process2 = __toESM(require("process"), 1);
|
|
|
244
244
|
async function openUrl(url, options = {}) {
|
|
245
245
|
const command = openerFor(options.platform ?? import_node_process2.default.platform);
|
|
246
246
|
const doSpawn = options.spawnImpl ?? import_node_child_process.spawn;
|
|
247
|
-
await new Promise((
|
|
247
|
+
await new Promise((resolve12, reject) => {
|
|
248
248
|
const child = doSpawn(command.cmd, [...command.args, url], {
|
|
249
249
|
stdio: "ignore",
|
|
250
250
|
detached: true
|
|
@@ -252,7 +252,7 @@ async function openUrl(url, options = {}) {
|
|
|
252
252
|
child.once("error", reject);
|
|
253
253
|
child.once("spawn", () => {
|
|
254
254
|
child.unref();
|
|
255
|
-
|
|
255
|
+
resolve12();
|
|
256
256
|
});
|
|
257
257
|
});
|
|
258
258
|
}
|
|
@@ -915,8 +915,8 @@ function parseArgv(argv) {
|
|
|
915
915
|
}
|
|
916
916
|
return { positionals, options };
|
|
917
917
|
}
|
|
918
|
-
function assertArgs(parsed,
|
|
919
|
-
const allowed = new Set(
|
|
918
|
+
function assertArgs(parsed, allowedOptions2, maxPositionals) {
|
|
919
|
+
const allowed = new Set(allowedOptions2);
|
|
920
920
|
for (const name of Object.keys(parsed.options)) {
|
|
921
921
|
if (!allowed.has(name)) throw new Error(`unknown option "--${name}"; run "odla-ai help" for supported options`);
|
|
922
922
|
}
|
|
@@ -2186,8 +2186,8 @@ function credential(value) {
|
|
|
2186
2186
|
// src/calendar-poll.ts
|
|
2187
2187
|
async function waitForCalendarPoll(milliseconds, signal) {
|
|
2188
2188
|
if (signal?.aborted) throw signal.reason ?? new Error("calendar connection aborted");
|
|
2189
|
-
await new Promise((
|
|
2190
|
-
const timer = setTimeout(
|
|
2189
|
+
await new Promise((resolve12, reject) => {
|
|
2190
|
+
const timer = setTimeout(resolve12, milliseconds);
|
|
2191
2191
|
signal?.addEventListener("abort", () => {
|
|
2192
2192
|
clearTimeout(timer);
|
|
2193
2193
|
reject(signal.reason ?? new Error("calendar connection aborted"));
|
|
@@ -2388,7 +2388,8 @@ var CAPABILITIES = {
|
|
|
2388
2388
|
"compose declared app-capability schema/rules, create guarded seeds when absent, and configure platform AI, auth, and deployment links",
|
|
2389
2389
|
"apply Google Calendar booking config, then drive state-bound consent, status, discovery, and disconnect flows (bookings run live through the platform proxy)",
|
|
2390
2390
|
"validate integration contracts offline and smoke-test a provisioned db environment plus anonymous capability routes",
|
|
2391
|
-
"
|
|
2391
|
+
"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",
|
|
2392
|
+
"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",
|
|
2392
2393
|
"inspect durable agent wakeups as a versioned JSON envelope and explicitly requeue one dead-lettered job with a scoped environment credential",
|
|
2393
2394
|
"run app-attributed hosted security discovery and independent validation without provider keys",
|
|
2394
2395
|
"connect/revoke source-read-only GitHub sources and drive commit-pinned hosted security jobs without PATs or provider keys",
|
|
@@ -5416,21 +5417,21 @@ async function verifyCodeCandidate(input) {
|
|
|
5416
5417
|
const recipes = [];
|
|
5417
5418
|
const logs = [];
|
|
5418
5419
|
for (const recipe2 of policy.recipes) {
|
|
5419
|
-
const
|
|
5420
|
+
const clean4 = await stageWorkspace(staged.workspaceDir, limits);
|
|
5420
5421
|
try {
|
|
5421
|
-
if (await digestStagedWorkspace(
|
|
5422
|
+
if (await digestStagedWorkspace(clean4.workspaceDir, limits) !== sourceDigest) {
|
|
5422
5423
|
throw new TypeError("clean verifier source changed before execution");
|
|
5423
5424
|
}
|
|
5424
5425
|
const result = checkedResult(await input.recipeExecutor.run({
|
|
5425
|
-
workspaceDir:
|
|
5426
|
+
workspaceDir: clean4.workspaceDir,
|
|
5426
5427
|
recipe: recipe2,
|
|
5427
5428
|
signal: input.signal
|
|
5428
5429
|
}), recipe2.maxOutputBytes);
|
|
5429
|
-
const artifacts = await inspectArtifacts(
|
|
5430
|
+
const artifacts = await inspectArtifacts(clean4.workspaceDir, recipe2);
|
|
5430
5431
|
recipes.push(recipeReceipt(recipe2, result, artifacts));
|
|
5431
5432
|
logs.push({ recipeId: recipe2.id, ...boundedLogs(result, recipe2.maxOutputBytes) });
|
|
5432
5433
|
} finally {
|
|
5433
|
-
await
|
|
5434
|
+
await clean4.cleanup();
|
|
5434
5435
|
}
|
|
5435
5436
|
}
|
|
5436
5437
|
const fields = {
|
|
@@ -6552,11 +6553,21 @@ Usage:
|
|
|
6552
6553
|
odla-ai app owners list [--config odla.config.mjs] [--email <odla-account>] [--json]
|
|
6553
6554
|
odla-ai app owners add <email> [--email <odla-account>] [--json]
|
|
6554
6555
|
odla-ai app owners remove <email> [--email <odla-account>] [--json]
|
|
6555
|
-
odla-ai pm
|
|
6556
|
-
odla-ai pm
|
|
6556
|
+
odla-ai pm goal list [--app <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
6557
|
+
odla-ai pm task list [--app <id>] [--column <c>] [--goal <id>] [--assignee <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
6558
|
+
odla-ai pm decision list [--app <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
6559
|
+
odla-ai pm bug list [--app <id>] [--status <s>] [--severity <s>] [--goal <id>] [--assignee <id>] [--decision <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
6560
|
+
odla-ai pm goal add --app <id> --title <t> [--status <s>] [--proof <text>] [--target <pct>] [--mutation-id <id>] [--json]
|
|
6561
|
+
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]
|
|
6562
|
+
odla-ai pm decision add --app <id> --title <t> --body <text> [--status <s>] [--mutation-id <id>] [--json]
|
|
6563
|
+
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]
|
|
6557
6564
|
odla-ai pm <goal|task|decision|bug> get <id> [--json]
|
|
6558
|
-
odla-ai pm
|
|
6559
|
-
odla-ai pm
|
|
6565
|
+
odla-ai pm goal set <id> [--title <t>|--status <s>|--proof <text>|--no-proof|--target <pct>|--no-target] [--mutation-id <id>] [--json]
|
|
6566
|
+
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]
|
|
6567
|
+
odla-ai pm decision set <id> [--title <t>|--status <s>|--body <text>] [--mutation-id <id>] [--json]
|
|
6568
|
+
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]
|
|
6569
|
+
odla-ai pm <goal|task|decision> done <id> [--mutation-id <id>]
|
|
6570
|
+
odla-ai pm bug done <id> [--decision <accepted-decision-id>] [--mutation-id <id>]
|
|
6560
6571
|
odla-ai pm <goal|task|decision|bug> comment <id> --body "..." [--mutation-id <id>]
|
|
6561
6572
|
odla-ai pm <goal|task|decision|bug> comments <id> [--json]
|
|
6562
6573
|
odla-ai pm <goal|task|decision|bug> rm <id>
|
|
@@ -6571,8 +6582,12 @@ Usage:
|
|
|
6571
6582
|
odla-ai discuss watch [<topic>] [--cursor <cursor>] [--by <authorId>] [--self <authorId>] [--interval <s>] [--timeout <s>] [--json|--jsonl]
|
|
6572
6583
|
odla-ai agent jobs [--env dev] [--state pending|running|succeeded|dead_letter] [--limit 50] [--email <email>] [--json]
|
|
6573
6584
|
odla-ai agent retry <job-id> [--env dev] [--email <email>] [--json]
|
|
6574
|
-
odla-ai
|
|
6575
|
-
odla-ai
|
|
6585
|
+
odla-ai context show [--context <name>] [--platform https://odla.ai] [--app <id>] [--env prod] [--json]
|
|
6586
|
+
odla-ai context list [--json]
|
|
6587
|
+
odla-ai context save <name> [--platform <url>] [--app <id>] [--env <name>] [--json]
|
|
6588
|
+
odla-ai context remove <name> --yes [--json]
|
|
6589
|
+
odla-ai o11y status [--app <id>] [--context <name>] [--platform https://odla.ai] [--env prod] [--minutes 60] [--json]
|
|
6590
|
+
odla-ai whoami [--context <name>] [--platform https://odla.ai] [--json]
|
|
6576
6591
|
odla-ai runbook ask "<question>" [--app <id>] [--all] [--json]
|
|
6577
6592
|
odla-ai runbook search "<question>" [--app <id>] [--all] [--limit <n>] [--json]
|
|
6578
6593
|
odla-ai runbook impact [--base origin/main] [--app <id>] [--all] [--limit <n>] [--json]
|
|
@@ -6637,6 +6652,11 @@ Commands:
|
|
|
6637
6652
|
whoami Report who this terminal is authenticated as, and whether it holds
|
|
6638
6653
|
platform admin. A handshake-minted device token is never admin,
|
|
6639
6654
|
however the human who approved it is configured.
|
|
6655
|
+
context Explain selected config, platform, app, environment, and
|
|
6656
|
+
developer-token provenance without printing credentials or
|
|
6657
|
+
starting a device handshake. Operator work can run outside a
|
|
6658
|
+
project checkout with explicit flags or ODLA_* environment
|
|
6659
|
+
variables.
|
|
6640
6660
|
setup Install offline odla runbooks for common coding-agent harnesses.
|
|
6641
6661
|
init Create a generic odla.config.mjs plus starter schema/rules files.
|
|
6642
6662
|
doctor Validate and summarize the project config without network calls.
|
|
@@ -6708,6 +6728,15 @@ Safety:
|
|
|
6708
6728
|
Provision caches the approved developer token and service credentials under
|
|
6709
6729
|
.odla/ with mode 0600, and init adds those paths to .gitignore. Secret push
|
|
6710
6730
|
preflights Wrangler before any shown-once issuance or destructive rotation.
|
|
6731
|
+
Projectless PM, Discussions, o11y, runbook, and identity commands use
|
|
6732
|
+
--platform/--app/--env, ODLA_PLATFORM_URL/ODLA_APP_ID/ODLA_ENV, and
|
|
6733
|
+
ODLA_DEV_TOKEN. Save non-secret scope metadata with "context save", then
|
|
6734
|
+
select it explicitly with --context or ODLA_CONTEXT. Each named context gets
|
|
6735
|
+
isolated developer and scoped-token caches. Override their locations with
|
|
6736
|
+
ODLA_DEV_TOKEN_FILE and ODLA_ADMIN_TOKEN_FILE; ODLA_CONTEXT_FILE relocates
|
|
6737
|
+
the metadata file. Flags and specific ODLA_* scope variables beat a selected
|
|
6738
|
+
context, which beats project config. There is no ambient current context.
|
|
6739
|
+
"context show" reports only provenance and cache state and never authenticates.
|
|
6711
6740
|
Provision opens the approval page in your browser automatically whenever the
|
|
6712
6741
|
machine can show one, including agent-driven runs; only CI, SSH, and
|
|
6713
6742
|
display-less hosts skip it. Use --open to force or --no-open to suppress.
|
|
@@ -6825,8 +6854,8 @@ function hostedPollTimeout(value = 10 * 6e4) {
|
|
|
6825
6854
|
}
|
|
6826
6855
|
async function waitForHostedPoll(milliseconds, signal) {
|
|
6827
6856
|
if (signal?.aborted) throw signal.reason ?? new DOMException("aborted", "AbortError");
|
|
6828
|
-
await new Promise((
|
|
6829
|
-
const timer = setTimeout(
|
|
6857
|
+
await new Promise((resolve12, reject) => {
|
|
6858
|
+
const timer = setTimeout(resolve12, milliseconds);
|
|
6830
6859
|
signal?.addEventListener("abort", () => {
|
|
6831
6860
|
clearTimeout(timer);
|
|
6832
6861
|
reject(signal.reason ?? new DOMException("aborted", "AbortError"));
|
|
@@ -7336,6 +7365,384 @@ async function codeCommand(parsed, dependencies) {
|
|
|
7336
7365
|
});
|
|
7337
7366
|
}
|
|
7338
7367
|
|
|
7368
|
+
// src/operator-credentials.ts
|
|
7369
|
+
var import_node_process7 = __toESM(require("process"), 1);
|
|
7370
|
+
function developerTokenStatus(context, parsed, now = Date.now()) {
|
|
7371
|
+
const cached = readJsonFile(context.cfg.local.tokenFile);
|
|
7372
|
+
const cacheStatus = !cached?.token ? "missing" : cached.platform !== context.platform.value ? "other-platform" : (cached.expiresAt ?? 0) <= now + 6e4 ? "expired" : "valid";
|
|
7373
|
+
const source = clean(
|
|
7374
|
+
stringOpt(parsed.options.token)
|
|
7375
|
+
) ? "flag" : clean(import_node_process7.default.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
|
|
7376
|
+
return {
|
|
7377
|
+
source,
|
|
7378
|
+
cacheFile: context.cfg.local.tokenFile,
|
|
7379
|
+
cacheStatus
|
|
7380
|
+
};
|
|
7381
|
+
}
|
|
7382
|
+
function clean(value) {
|
|
7383
|
+
const normalized = value?.trim();
|
|
7384
|
+
return normalized || void 0;
|
|
7385
|
+
}
|
|
7386
|
+
|
|
7387
|
+
// src/operator-context.ts
|
|
7388
|
+
var import_node_fs14 = require("fs");
|
|
7389
|
+
var import_node_path12 = require("path");
|
|
7390
|
+
var import_node_process9 = __toESM(require("process"), 1);
|
|
7391
|
+
|
|
7392
|
+
// src/operator-profiles.ts
|
|
7393
|
+
var import_node_fs13 = require("fs");
|
|
7394
|
+
var import_node_os4 = require("os");
|
|
7395
|
+
var import_node_path11 = require("path");
|
|
7396
|
+
var import_node_process8 = __toESM(require("process"), 1);
|
|
7397
|
+
function operatorProfileFile() {
|
|
7398
|
+
return (0, import_node_path11.resolve)(
|
|
7399
|
+
clean2(import_node_process8.default.env.ODLA_CONTEXT_FILE) ?? (0, import_node_path11.join)((0, import_node_os4.homedir)(), ".odla", "contexts.json")
|
|
7400
|
+
);
|
|
7401
|
+
}
|
|
7402
|
+
function resolveOperatorProfile(parsed) {
|
|
7403
|
+
const fromFlag = clean2(stringOpt(parsed.options.context));
|
|
7404
|
+
const fromEnvironment = clean2(import_node_process8.default.env.ODLA_CONTEXT);
|
|
7405
|
+
const name = fromFlag ?? fromEnvironment ?? null;
|
|
7406
|
+
const file = operatorProfileFile();
|
|
7407
|
+
if (!name) {
|
|
7408
|
+
return { name: null, source: "unresolved", file, value: null };
|
|
7409
|
+
}
|
|
7410
|
+
assertOperatorName(name, "context");
|
|
7411
|
+
const profiles = readOperatorProfiles(file);
|
|
7412
|
+
const value = Object.hasOwn(profiles, name) ? profiles[name] : void 0;
|
|
7413
|
+
if (!value) {
|
|
7414
|
+
throw new Error(
|
|
7415
|
+
`operator context "${name}" not found in ${file}; run "odla-ai context list" or save it first`
|
|
7416
|
+
);
|
|
7417
|
+
}
|
|
7418
|
+
return {
|
|
7419
|
+
name,
|
|
7420
|
+
source: fromFlag ? "flag" : "environment",
|
|
7421
|
+
file,
|
|
7422
|
+
value
|
|
7423
|
+
};
|
|
7424
|
+
}
|
|
7425
|
+
function listOperatorProfiles(file = operatorProfileFile()) {
|
|
7426
|
+
return Object.entries(readOperatorProfiles(file)).sort(([a], [b]) => a.localeCompare(b)).map(([name, profile]) => ({ name, ...profile }));
|
|
7427
|
+
}
|
|
7428
|
+
function saveOperatorProfile(name, profile, file = operatorProfileFile()) {
|
|
7429
|
+
assertOperatorName(name, "context");
|
|
7430
|
+
const normalized = validateProfile(profile, `operator context "${name}"`);
|
|
7431
|
+
const profiles = readOperatorProfiles(file);
|
|
7432
|
+
profiles[name] = normalized;
|
|
7433
|
+
writePrivateJson(file, { schemaVersion: 1, profiles });
|
|
7434
|
+
}
|
|
7435
|
+
function removeOperatorProfile(name, file = operatorProfileFile()) {
|
|
7436
|
+
assertOperatorName(name, "context");
|
|
7437
|
+
const profiles = readOperatorProfiles(file);
|
|
7438
|
+
if (!Object.hasOwn(profiles, name)) return false;
|
|
7439
|
+
delete profiles[name];
|
|
7440
|
+
writePrivateJson(file, { schemaVersion: 1, profiles });
|
|
7441
|
+
return true;
|
|
7442
|
+
}
|
|
7443
|
+
function operatorCredentialFiles(selection) {
|
|
7444
|
+
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");
|
|
7445
|
+
return {
|
|
7446
|
+
developer: (0, import_node_path11.join)(base, "dev-token.json"),
|
|
7447
|
+
scoped: (0, import_node_path11.join)(base, "admin-token.local.json")
|
|
7448
|
+
};
|
|
7449
|
+
}
|
|
7450
|
+
function assertOperatorName(value, label) {
|
|
7451
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(value)) {
|
|
7452
|
+
throw new Error(
|
|
7453
|
+
`${label} must contain lowercase letters, numbers, and hyphens`
|
|
7454
|
+
);
|
|
7455
|
+
}
|
|
7456
|
+
}
|
|
7457
|
+
function readOperatorProfiles(file) {
|
|
7458
|
+
if (!(0, import_node_fs13.existsSync)(file)) return emptyProfiles();
|
|
7459
|
+
let raw;
|
|
7460
|
+
try {
|
|
7461
|
+
raw = JSON.parse((0, import_node_fs13.readFileSync)(file, "utf8"));
|
|
7462
|
+
} catch {
|
|
7463
|
+
throw new Error(`operator context file ${file} is not valid JSON`);
|
|
7464
|
+
}
|
|
7465
|
+
if (!raw || typeof raw !== "object" || raw.schemaVersion !== 1 || !raw.profiles || typeof raw.profiles !== "object" || Array.isArray(raw.profiles)) {
|
|
7466
|
+
throw new Error(`operator context file ${file} has an invalid shape`);
|
|
7467
|
+
}
|
|
7468
|
+
const unknown = Object.keys(raw).filter(
|
|
7469
|
+
(key) => !["schemaVersion", "profiles"].includes(key)
|
|
7470
|
+
);
|
|
7471
|
+
if (unknown.length > 0) {
|
|
7472
|
+
throw new Error(
|
|
7473
|
+
`operator context file ${file} contains unsupported field(s): ${unknown.sort().join(", ")}`
|
|
7474
|
+
);
|
|
7475
|
+
}
|
|
7476
|
+
const profiles = emptyProfiles();
|
|
7477
|
+
for (const [name, value] of Object.entries(
|
|
7478
|
+
raw.profiles
|
|
7479
|
+
)) {
|
|
7480
|
+
assertOperatorName(name, "context");
|
|
7481
|
+
profiles[name] = validateProfile(value, `operator context "${name}"`);
|
|
7482
|
+
}
|
|
7483
|
+
return profiles;
|
|
7484
|
+
}
|
|
7485
|
+
function emptyProfiles() {
|
|
7486
|
+
return /* @__PURE__ */ Object.create(null);
|
|
7487
|
+
}
|
|
7488
|
+
function validateProfile(value, label) {
|
|
7489
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
7490
|
+
throw new Error(`${label} has an invalid shape`);
|
|
7491
|
+
}
|
|
7492
|
+
const unknown = Object.keys(value).filter(
|
|
7493
|
+
(key) => !["platform", "app", "environment"].includes(key)
|
|
7494
|
+
);
|
|
7495
|
+
if (unknown.length > 0) {
|
|
7496
|
+
throw new Error(
|
|
7497
|
+
`${label} contains unsupported field(s): ${unknown.sort().join(", ")}; contexts store scope metadata only, never credentials`
|
|
7498
|
+
);
|
|
7499
|
+
}
|
|
7500
|
+
const candidate = value;
|
|
7501
|
+
if (typeof candidate.platform !== "string") {
|
|
7502
|
+
throw new Error(`${label} needs an absolute platform URL`);
|
|
7503
|
+
}
|
|
7504
|
+
const platform = platformAudience(candidate.platform);
|
|
7505
|
+
const app = clean2(candidate.app);
|
|
7506
|
+
const environment2 = clean2(candidate.environment);
|
|
7507
|
+
if (app) assertOperatorName(app, "app");
|
|
7508
|
+
if (environment2) assertOperatorName(environment2, "environment");
|
|
7509
|
+
return {
|
|
7510
|
+
platform,
|
|
7511
|
+
...app ? { app } : {},
|
|
7512
|
+
...environment2 ? { environment: environment2 } : {}
|
|
7513
|
+
};
|
|
7514
|
+
}
|
|
7515
|
+
function clean2(value) {
|
|
7516
|
+
const normalized = value?.trim();
|
|
7517
|
+
return normalized || void 0;
|
|
7518
|
+
}
|
|
7519
|
+
|
|
7520
|
+
// src/operator-context.ts
|
|
7521
|
+
var DEFAULT_PLATFORM2 = "https://odla.ai";
|
|
7522
|
+
async function resolveOperatorContext(parsed, options = {}) {
|
|
7523
|
+
const profile = resolveOperatorProfile(parsed);
|
|
7524
|
+
const configArgument = stringOpt(parsed.options.config) ?? "odla.config.mjs";
|
|
7525
|
+
const configPath = (0, import_node_path12.resolve)(configArgument);
|
|
7526
|
+
const explicitConfig = parsed.options.config !== void 0;
|
|
7527
|
+
const hasConfig = (0, import_node_fs14.existsSync)(configPath);
|
|
7528
|
+
if (!hasConfig && (!options.allowMissingConfig || explicitConfig)) {
|
|
7529
|
+
await loadProjectConfig(configArgument);
|
|
7530
|
+
}
|
|
7531
|
+
const loaded = hasConfig ? await loadProjectConfig(configArgument) : void 0;
|
|
7532
|
+
const platformFlag = clean3(stringOpt(parsed.options.platform));
|
|
7533
|
+
const platformEnvironment = clean3(import_node_process9.default.env.ODLA_PLATFORM_URL);
|
|
7534
|
+
const platformValue = platformAudience(
|
|
7535
|
+
platformFlag ?? platformEnvironment ?? profile.value?.platform ?? loaded?.platformUrl ?? DEFAULT_PLATFORM2
|
|
7536
|
+
);
|
|
7537
|
+
const platformSource = platformFlag ? "flag" : platformEnvironment ? "environment" : profile.value ? "profile" : loaded ? "config" : "default";
|
|
7538
|
+
const appFlag = clean3(stringOpt(parsed.options.app));
|
|
7539
|
+
const appEnvironment = clean3(import_node_process9.default.env.ODLA_APP_ID);
|
|
7540
|
+
const appValue = appFlag ?? appEnvironment ?? profile.value?.app ?? loaded?.app.id ?? null;
|
|
7541
|
+
const appSource = appFlag ? "flag" : appEnvironment ? "environment" : profile.value?.app ? "profile" : loaded ? "config" : "unresolved";
|
|
7542
|
+
if (appValue) assertOperatorName(appValue, "app");
|
|
7543
|
+
if (options.requireApp && !appValue) {
|
|
7544
|
+
throw new Error(
|
|
7545
|
+
"app context is unresolved; pass --app <id>, set ODLA_APP_ID, select --context <name>, or run inside a project with odla.config.mjs"
|
|
7546
|
+
);
|
|
7547
|
+
}
|
|
7548
|
+
const envFlag = clean3(stringOpt(parsed.options.env));
|
|
7549
|
+
const envEnvironment = clean3(import_node_process9.default.env.ODLA_ENV);
|
|
7550
|
+
const environmentValue = envFlag ?? envEnvironment ?? profile.value?.environment ?? options.defaultEnvironment ?? null;
|
|
7551
|
+
const environmentSource = envFlag ? "flag" : envEnvironment ? "environment" : profile.value?.environment ? "profile" : options.defaultEnvironment ? "default" : "unresolved";
|
|
7552
|
+
if (environmentValue) {
|
|
7553
|
+
assertOperatorName(environmentValue, "environment");
|
|
7554
|
+
}
|
|
7555
|
+
const rootDir = loaded?.rootDir ?? import_node_process9.default.cwd();
|
|
7556
|
+
const profileCredentials = operatorCredentialFiles(profile);
|
|
7557
|
+
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;
|
|
7558
|
+
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;
|
|
7559
|
+
const cfg = loaded ? {
|
|
7560
|
+
...loaded,
|
|
7561
|
+
platformUrl: platformValue,
|
|
7562
|
+
app: appValue ? {
|
|
7563
|
+
id: appValue,
|
|
7564
|
+
name: appValue === loaded.app.id ? loaded.app.name : appValue
|
|
7565
|
+
} : loaded.app,
|
|
7566
|
+
local: { ...loaded.local, tokenFile }
|
|
7567
|
+
} : {
|
|
7568
|
+
configPath,
|
|
7569
|
+
rootDir,
|
|
7570
|
+
platformUrl: platformValue,
|
|
7571
|
+
dbEndpoint: platformValue,
|
|
7572
|
+
app: {
|
|
7573
|
+
id: appValue ?? "operator",
|
|
7574
|
+
name: appValue ?? "Operator"
|
|
7575
|
+
},
|
|
7576
|
+
envs: environmentValue ? [environmentValue] : [],
|
|
7577
|
+
services: [],
|
|
7578
|
+
local: {
|
|
7579
|
+
tokenFile,
|
|
7580
|
+
credentialsFile: (0, import_node_path12.join)(rootDir, ".odla", "credentials.local.json"),
|
|
7581
|
+
devVarsFile: (0, import_node_path12.join)(rootDir, ".dev.vars"),
|
|
7582
|
+
gitignore: true
|
|
7583
|
+
}
|
|
7584
|
+
};
|
|
7585
|
+
return {
|
|
7586
|
+
cfg,
|
|
7587
|
+
profile: {
|
|
7588
|
+
name: profile.name,
|
|
7589
|
+
source: profile.source,
|
|
7590
|
+
file: profile.file
|
|
7591
|
+
},
|
|
7592
|
+
config: {
|
|
7593
|
+
path: configPath,
|
|
7594
|
+
status: loaded ? "loaded" : "absent",
|
|
7595
|
+
explicit: explicitConfig
|
|
7596
|
+
},
|
|
7597
|
+
platform: { value: platformValue, source: platformSource },
|
|
7598
|
+
app: { value: appValue, source: appSource },
|
|
7599
|
+
environment: {
|
|
7600
|
+
value: environmentValue,
|
|
7601
|
+
source: environmentSource
|
|
7602
|
+
},
|
|
7603
|
+
credentials: {
|
|
7604
|
+
developerTokenFile: tokenFile,
|
|
7605
|
+
scopedTokenFile
|
|
7606
|
+
}
|
|
7607
|
+
};
|
|
7608
|
+
}
|
|
7609
|
+
function clean3(value) {
|
|
7610
|
+
const normalized = value?.trim();
|
|
7611
|
+
return normalized || void 0;
|
|
7612
|
+
}
|
|
7613
|
+
|
|
7614
|
+
// src/context-command.ts
|
|
7615
|
+
async function contextCommand(parsed, deps = {}) {
|
|
7616
|
+
assertArgs(
|
|
7617
|
+
parsed,
|
|
7618
|
+
[
|
|
7619
|
+
"config",
|
|
7620
|
+
"context",
|
|
7621
|
+
"platform",
|
|
7622
|
+
"app",
|
|
7623
|
+
"env",
|
|
7624
|
+
"token",
|
|
7625
|
+
"json",
|
|
7626
|
+
"yes"
|
|
7627
|
+
],
|
|
7628
|
+
3
|
|
7629
|
+
);
|
|
7630
|
+
const action = parsed.positionals[1];
|
|
7631
|
+
const out = deps.stdout ?? console;
|
|
7632
|
+
if (action === "list") {
|
|
7633
|
+
const profiles = listOperatorProfiles();
|
|
7634
|
+
if (parsed.options.json === true) {
|
|
7635
|
+
out.log(JSON.stringify({ schemaVersion: 1, profiles }, null, 2));
|
|
7636
|
+
return;
|
|
7637
|
+
}
|
|
7638
|
+
if (profiles.length === 0) {
|
|
7639
|
+
out.log("No named operator contexts.");
|
|
7640
|
+
return;
|
|
7641
|
+
}
|
|
7642
|
+
out.log("name platform app environment");
|
|
7643
|
+
for (const profile of profiles) {
|
|
7644
|
+
out.log(
|
|
7645
|
+
`${profile.name} ${profile.platform} ${profile.app ?? ""} ${profile.environment ?? ""}`
|
|
7646
|
+
);
|
|
7647
|
+
}
|
|
7648
|
+
return;
|
|
7649
|
+
}
|
|
7650
|
+
if (action === "save") {
|
|
7651
|
+
if (parsed.options.token !== void 0) {
|
|
7652
|
+
throw new Error(
|
|
7653
|
+
"context save does not accept --token; contexts store scope metadata only"
|
|
7654
|
+
);
|
|
7655
|
+
}
|
|
7656
|
+
const name = requireName(parsed);
|
|
7657
|
+
const context2 = await resolveOperatorContext(parsed, {
|
|
7658
|
+
allowMissingConfig: true
|
|
7659
|
+
});
|
|
7660
|
+
const profile = {
|
|
7661
|
+
platform: context2.platform.value,
|
|
7662
|
+
...context2.app.value ? { app: context2.app.value } : {},
|
|
7663
|
+
...context2.environment.value ? { environment: context2.environment.value } : {}
|
|
7664
|
+
};
|
|
7665
|
+
saveOperatorProfile(name, profile, context2.profile.file);
|
|
7666
|
+
if (parsed.options.json === true) {
|
|
7667
|
+
out.log(JSON.stringify({ schemaVersion: 1, name, ...profile }, null, 2));
|
|
7668
|
+
} else {
|
|
7669
|
+
out.log(`saved operator context "${name}" (${profile.platform})`);
|
|
7670
|
+
}
|
|
7671
|
+
return;
|
|
7672
|
+
}
|
|
7673
|
+
if (action === "remove") {
|
|
7674
|
+
const name = requireName(parsed);
|
|
7675
|
+
if (parsed.options.yes !== true) {
|
|
7676
|
+
throw new Error(`context remove "${name}" requires --yes`);
|
|
7677
|
+
}
|
|
7678
|
+
const removed = removeOperatorProfile(name);
|
|
7679
|
+
const result2 = {
|
|
7680
|
+
schemaVersion: 1,
|
|
7681
|
+
name,
|
|
7682
|
+
removed,
|
|
7683
|
+
credentialCachesRemoved: false,
|
|
7684
|
+
grantsRevoked: false
|
|
7685
|
+
};
|
|
7686
|
+
if (parsed.options.json === true) out.log(JSON.stringify(result2, null, 2));
|
|
7687
|
+
else {
|
|
7688
|
+
const status = removed ? `removed operator context "${name}"` : `operator context "${name}" was already absent`;
|
|
7689
|
+
out.log(`${status}; credential caches were not deleted or revoked`);
|
|
7690
|
+
}
|
|
7691
|
+
return;
|
|
7692
|
+
}
|
|
7693
|
+
if (action !== "show") {
|
|
7694
|
+
throw new Error(
|
|
7695
|
+
`unknown context action "${action ?? ""}". Try show|list|save|remove.`
|
|
7696
|
+
);
|
|
7697
|
+
}
|
|
7698
|
+
const context = await resolveOperatorContext(parsed, {
|
|
7699
|
+
allowMissingConfig: true,
|
|
7700
|
+
defaultEnvironment: "prod"
|
|
7701
|
+
});
|
|
7702
|
+
const token = developerTokenStatus(context, parsed);
|
|
7703
|
+
const result = {
|
|
7704
|
+
schemaVersion: 1,
|
|
7705
|
+
profile: context.profile,
|
|
7706
|
+
config: context.config,
|
|
7707
|
+
platform: context.platform,
|
|
7708
|
+
app: context.app,
|
|
7709
|
+
environment: context.environment,
|
|
7710
|
+
authentication: {
|
|
7711
|
+
developerToken: token,
|
|
7712
|
+
scopedTokenCacheFile: context.credentials.scopedTokenFile,
|
|
7713
|
+
handshakeStarted: false
|
|
7714
|
+
}
|
|
7715
|
+
};
|
|
7716
|
+
if (parsed.options.json === true) {
|
|
7717
|
+
out.log(JSON.stringify(result, null, 2));
|
|
7718
|
+
return;
|
|
7719
|
+
}
|
|
7720
|
+
out.log(`config: ${context.config.status} (${context.config.path})`);
|
|
7721
|
+
out.log(
|
|
7722
|
+
`context: ${context.profile.name ?? "(none)"} (${context.profile.source}; ${context.profile.file})`
|
|
7723
|
+
);
|
|
7724
|
+
out.log(
|
|
7725
|
+
`platform: ${context.platform.value} (${context.platform.source})`
|
|
7726
|
+
);
|
|
7727
|
+
out.log(
|
|
7728
|
+
`app: ${context.app.value ?? "(unresolved)"} (${context.app.source})`
|
|
7729
|
+
);
|
|
7730
|
+
out.log(
|
|
7731
|
+
`env: ${context.environment.value ?? "(unresolved)"} (${context.environment.source})`
|
|
7732
|
+
);
|
|
7733
|
+
out.log(
|
|
7734
|
+
`auth: ${token.source} (cache ${token.cacheStatus}: ${token.cacheFile})`
|
|
7735
|
+
);
|
|
7736
|
+
out.log("handshake: not started");
|
|
7737
|
+
}
|
|
7738
|
+
function requireName(parsed) {
|
|
7739
|
+
const name = parsed.positionals[2]?.trim();
|
|
7740
|
+
if (!name) {
|
|
7741
|
+
throw new Error(`context ${parsed.positionals[1]} needs a profile name`);
|
|
7742
|
+
}
|
|
7743
|
+
return name;
|
|
7744
|
+
}
|
|
7745
|
+
|
|
7339
7746
|
// src/discuss-actions.ts
|
|
7340
7747
|
var writeMutationId = (parsed) => stringOpt(parsed.options["mutation-id"]) ?? crypto.randomUUID();
|
|
7341
7748
|
async function request(ctx, method, path, body) {
|
|
@@ -7387,8 +7794,9 @@ async function discussGroups(ctx) {
|
|
|
7387
7794
|
}
|
|
7388
7795
|
async function discussList(ctx, parsed) {
|
|
7389
7796
|
const query = new URLSearchParams();
|
|
7797
|
+
const app = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
7798
|
+
if (app) query.set("app", app);
|
|
7390
7799
|
for (const [flag, param] of [
|
|
7391
|
-
["app", "app"],
|
|
7392
7800
|
["q", "q"],
|
|
7393
7801
|
["state", "state"],
|
|
7394
7802
|
["limit", "limit"],
|
|
@@ -7464,7 +7872,7 @@ function renderRead(ctx, topic, posts) {
|
|
|
7464
7872
|
}
|
|
7465
7873
|
}
|
|
7466
7874
|
async function discussPost(ctx, parsed) {
|
|
7467
|
-
const appId = stringOpt(parsed.options.app);
|
|
7875
|
+
const appId = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
7468
7876
|
if (!appId) throw new Error("discuss post needs --app <appId>");
|
|
7469
7877
|
const subject = stringOpt(parsed.options.subject);
|
|
7470
7878
|
if (!subject) throw new Error('discuss post needs --subject "\u2026"');
|
|
@@ -7496,7 +7904,7 @@ async function discussResolve(ctx, id, resolved, parsed) {
|
|
|
7496
7904
|
}
|
|
7497
7905
|
async function discussWho(ctx, parsed) {
|
|
7498
7906
|
const query = new URLSearchParams({ q: stringOpt(parsed.options.q) ?? "" });
|
|
7499
|
-
const app = stringOpt(parsed.options.app);
|
|
7907
|
+
const app = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
7500
7908
|
if (app) query.set("app", app);
|
|
7501
7909
|
const kinds = stringOpt(parsed.options.kinds);
|
|
7502
7910
|
if (kinds) query.set("kinds", kinds);
|
|
@@ -7600,14 +8008,14 @@ function jsonl(ctx, parsed, value) {
|
|
|
7600
8008
|
}
|
|
7601
8009
|
async function discussWatch(ctx, topicId, parsed) {
|
|
7602
8010
|
if (ctx.json && parsed.options.jsonl === true) throw new Error("--json and --jsonl cannot be combined");
|
|
7603
|
-
const sleep = ctx.sleep ?? ((ms) => new Promise((
|
|
8011
|
+
const sleep = ctx.sleep ?? ((ms) => new Promise((resolve12) => setTimeout(resolve12, ms)));
|
|
7604
8012
|
const now = ctx.now ?? Date.now;
|
|
7605
8013
|
const intervalMs = (numberOpt2(parsed, "interval", DEFAULT_INTERVAL_MS / 1e3) ?? DEFAULT_INTERVAL_MS / 1e3) * 1e3;
|
|
7606
8014
|
const timeoutSeconds = numberOpt2(parsed, "timeout");
|
|
7607
8015
|
const deadline = timeoutSeconds === void 0 ? void 0 : now() + timeoutSeconds * 1e3;
|
|
7608
8016
|
const by = stringOpt(parsed.options.by);
|
|
7609
8017
|
const self = stringOpt(parsed.options.self);
|
|
7610
|
-
const app = stringOpt(parsed.options.app);
|
|
8018
|
+
const app = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
7611
8019
|
let cursor = stringOpt(parsed.options.cursor);
|
|
7612
8020
|
let firstSuccess = true;
|
|
7613
8021
|
let consecutiveFailures = 0;
|
|
@@ -7752,14 +8160,19 @@ var ALLOWED = [
|
|
|
7752
8160
|
"timeout",
|
|
7753
8161
|
"cursor",
|
|
7754
8162
|
"jsonl",
|
|
7755
|
-
"mutation-id"
|
|
8163
|
+
"mutation-id",
|
|
8164
|
+
"platform",
|
|
8165
|
+
"context"
|
|
7756
8166
|
];
|
|
7757
8167
|
function requireId(id, action) {
|
|
7758
8168
|
if (!id) throw new Error(`"discuss ${action}" needs a topic id`);
|
|
7759
8169
|
return id;
|
|
7760
8170
|
}
|
|
7761
8171
|
async function buildContext(parsed, deps) {
|
|
7762
|
-
const
|
|
8172
|
+
const context = await resolveOperatorContext(parsed, {
|
|
8173
|
+
allowMissingConfig: true
|
|
8174
|
+
});
|
|
8175
|
+
const { cfg } = context;
|
|
7763
8176
|
const doFetch = deps.fetch ?? fetch;
|
|
7764
8177
|
const out = deps.stdout ?? console;
|
|
7765
8178
|
const token = await getDeveloperToken(
|
|
@@ -7773,7 +8186,16 @@ async function buildContext(parsed, deps) {
|
|
|
7773
8186
|
doFetch,
|
|
7774
8187
|
out
|
|
7775
8188
|
);
|
|
7776
|
-
return {
|
|
8189
|
+
return {
|
|
8190
|
+
platformUrl: cfg.platformUrl,
|
|
8191
|
+
token,
|
|
8192
|
+
doFetch,
|
|
8193
|
+
out,
|
|
8194
|
+
json: parsed.options.json === true,
|
|
8195
|
+
// Preserve Discussions' existing cross-project default for config-backed
|
|
8196
|
+
// use while allowing remote agents to scope via ODLA_APP_ID or a profile.
|
|
8197
|
+
appId: context.app.source === "environment" || context.app.source === "profile" ? context.app.value ?? void 0 : void 0
|
|
8198
|
+
};
|
|
7777
8199
|
}
|
|
7778
8200
|
async function discussCommand(parsed, deps = {}) {
|
|
7779
8201
|
assertArgs(parsed, ALLOWED, 3);
|
|
@@ -7885,7 +8307,7 @@ async function pmList(ctx, entity, parsed) {
|
|
|
7885
8307
|
assignee: "assigneeId",
|
|
7886
8308
|
decision: "decisionId"
|
|
7887
8309
|
};
|
|
7888
|
-
const app = stringOpt(parsed.options.app);
|
|
8310
|
+
const app = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
7889
8311
|
if (app) q.set("app", app);
|
|
7890
8312
|
for (const [flag, param] of Object.entries(filters)) {
|
|
7891
8313
|
const v = stringOpt(parsed.options[flag]);
|
|
@@ -7904,7 +8326,7 @@ async function pmList(ctx, entity, parsed) {
|
|
|
7904
8326
|
});
|
|
7905
8327
|
}
|
|
7906
8328
|
async function pmAdd(ctx, entity, parsed) {
|
|
7907
|
-
const appId = stringOpt(parsed.options.app);
|
|
8329
|
+
const appId = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
7908
8330
|
if (!appId) throw new Error("pm add needs --app <appId>");
|
|
7909
8331
|
const input = collectEntityFields(entity, parsed, false);
|
|
7910
8332
|
if (!input.title) throw new Error("pm add needs --title <title>");
|
|
@@ -7955,7 +8377,7 @@ async function allRecords(ctx, entity, appId) {
|
|
|
7955
8377
|
}
|
|
7956
8378
|
}
|
|
7957
8379
|
async function pmHandoff(ctx, parsed) {
|
|
7958
|
-
const appId = stringOpt(parsed.options.app);
|
|
8380
|
+
const appId = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
7959
8381
|
if (!appId) throw new Error("pm handoff needs --app <appId>");
|
|
7960
8382
|
const [goals, tasks, bugs] = await Promise.all([
|
|
7961
8383
|
allRecords(ctx, "goal", appId),
|
|
@@ -8027,37 +8449,62 @@ var ALIASES = {
|
|
|
8027
8449
|
decision: "decision",
|
|
8028
8450
|
bug: "bug"
|
|
8029
8451
|
};
|
|
8030
|
-
var
|
|
8031
|
-
|
|
8032
|
-
"
|
|
8033
|
-
"
|
|
8034
|
-
|
|
8035
|
-
"
|
|
8036
|
-
"
|
|
8037
|
-
"
|
|
8038
|
-
|
|
8039
|
-
|
|
8040
|
-
|
|
8041
|
-
|
|
8042
|
-
|
|
8043
|
-
|
|
8044
|
-
|
|
8045
|
-
|
|
8046
|
-
|
|
8047
|
-
|
|
8048
|
-
|
|
8049
|
-
|
|
8050
|
-
|
|
8051
|
-
|
|
8052
|
-
|
|
8053
|
-
|
|
8054
|
-
|
|
8452
|
+
var COMMON_OPTIONS = ["config", "token", "email", "json", "platform", "context"];
|
|
8453
|
+
var ACTION_OPTIONS = {
|
|
8454
|
+
list: ["app", "q", "limit", "offset"],
|
|
8455
|
+
add: ["app", "title", "mutation-id"],
|
|
8456
|
+
get: [],
|
|
8457
|
+
set: ["mutation-id"],
|
|
8458
|
+
done: ["mutation-id"],
|
|
8459
|
+
comment: ["body", "mutation-id"],
|
|
8460
|
+
comments: [],
|
|
8461
|
+
rm: []
|
|
8462
|
+
};
|
|
8463
|
+
var ENTITY_OPTIONS = {
|
|
8464
|
+
goal: {
|
|
8465
|
+
list: ["status"],
|
|
8466
|
+
add: ["status", "proof", "target"],
|
|
8467
|
+
set: ["title", "status", "proof", "target"],
|
|
8468
|
+
done: []
|
|
8469
|
+
},
|
|
8470
|
+
task: {
|
|
8471
|
+
list: ["column", "goal", "assignee"],
|
|
8472
|
+
add: ["column", "goal", "assignee", "due", "description", "desc", "body"],
|
|
8473
|
+
set: ["title", "column", "rank", "goal", "assignee", "due", "description", "desc", "body"],
|
|
8474
|
+
done: []
|
|
8475
|
+
},
|
|
8476
|
+
decision: {
|
|
8477
|
+
list: ["status"],
|
|
8478
|
+
add: ["status", "body"],
|
|
8479
|
+
set: ["title", "status", "body"],
|
|
8480
|
+
done: []
|
|
8481
|
+
},
|
|
8482
|
+
bug: {
|
|
8483
|
+
list: ["status", "severity", "goal", "assignee", "decision"],
|
|
8484
|
+
add: ["status", "severity", "goal", "assignee", "decision", "description", "desc", "body"],
|
|
8485
|
+
set: ["title", "status", "severity", "goal", "assignee", "decision", "description", "desc", "body"],
|
|
8486
|
+
done: ["decision"]
|
|
8487
|
+
}
|
|
8488
|
+
};
|
|
8489
|
+
function canonicalAction(action) {
|
|
8490
|
+
if (action === "create") return "add";
|
|
8491
|
+
if (action === "update" || action === "status" || action === "move") return "set";
|
|
8492
|
+
if (action === "delete") return "rm";
|
|
8493
|
+
return action in ACTION_OPTIONS ? action : null;
|
|
8494
|
+
}
|
|
8495
|
+
function allowedOptions(entity, action) {
|
|
8496
|
+
const entityOptions = action === "list" || action === "add" || action === "set" || action === "done" ? ENTITY_OPTIONS[entity][action] : [];
|
|
8497
|
+
return [...COMMON_OPTIONS, ...ACTION_OPTIONS[action], ...entityOptions];
|
|
8498
|
+
}
|
|
8055
8499
|
function requireId2(id, action) {
|
|
8056
8500
|
if (!id) throw new Error(`"pm ... ${action}" needs an item id`);
|
|
8057
8501
|
return id;
|
|
8058
8502
|
}
|
|
8059
8503
|
async function buildContext2(parsed, deps) {
|
|
8060
|
-
const
|
|
8504
|
+
const context = await resolveOperatorContext(parsed, {
|
|
8505
|
+
allowMissingConfig: true
|
|
8506
|
+
});
|
|
8507
|
+
const { cfg } = context;
|
|
8061
8508
|
const doFetch = deps.fetch ?? fetch;
|
|
8062
8509
|
const out = deps.stdout ?? console;
|
|
8063
8510
|
const token = await getDeveloperToken(
|
|
@@ -8066,32 +8513,39 @@ async function buildContext2(parsed, deps) {
|
|
|
8066
8513
|
doFetch,
|
|
8067
8514
|
out
|
|
8068
8515
|
);
|
|
8069
|
-
return {
|
|
8516
|
+
return {
|
|
8517
|
+
platformUrl: cfg.platformUrl,
|
|
8518
|
+
token,
|
|
8519
|
+
doFetch,
|
|
8520
|
+
out,
|
|
8521
|
+
json: parsed.options.json === true,
|
|
8522
|
+
// Preserve PM's existing cross-project default when a config is present;
|
|
8523
|
+
// only an explicit remote-agent environment or profile selects an app.
|
|
8524
|
+
appId: context.app.source === "environment" || context.app.source === "profile" ? context.app.value ?? void 0 : void 0
|
|
8525
|
+
};
|
|
8070
8526
|
}
|
|
8071
8527
|
async function pmCommand(parsed, deps = {}) {
|
|
8072
8528
|
const word = parsed.positionals[1] ?? "";
|
|
8073
8529
|
if (word === "handoff") {
|
|
8074
|
-
assertArgs(parsed,
|
|
8530
|
+
assertArgs(parsed, [...COMMON_OPTIONS, "app"], 2);
|
|
8075
8531
|
return pmHandoff(await buildContext2(parsed, deps), parsed);
|
|
8076
8532
|
}
|
|
8077
8533
|
const entity = ALIASES[word];
|
|
8078
8534
|
if (!entity) throw new Error(`unknown pm entity "${word}". Try "odla-ai pm bug list" (goal|task|decision|bug).`);
|
|
8079
|
-
const
|
|
8080
|
-
|
|
8535
|
+
const requestedAction = parsed.positionals[2] ?? "list";
|
|
8536
|
+
const action = canonicalAction(requestedAction);
|
|
8537
|
+
if (!action) throw new Error(`unknown pm action "${requestedAction}". Try list|add|get|set|done|comment|comments|rm.`);
|
|
8538
|
+
assertArgs(parsed, allowedOptions(entity, action), 4);
|
|
8081
8539
|
const ctx = await buildContext2(parsed, deps);
|
|
8082
8540
|
const id = parsed.positionals[3];
|
|
8083
8541
|
switch (action) {
|
|
8084
8542
|
case "list":
|
|
8085
8543
|
return pmList(ctx, entity, parsed);
|
|
8086
8544
|
case "add":
|
|
8087
|
-
case "create":
|
|
8088
8545
|
return pmAdd(ctx, entity, parsed);
|
|
8089
8546
|
case "get":
|
|
8090
8547
|
return pmGet(ctx, entity, requireId2(id, action));
|
|
8091
8548
|
case "set":
|
|
8092
|
-
case "update":
|
|
8093
|
-
case "status":
|
|
8094
|
-
case "move":
|
|
8095
8549
|
return pmSet(ctx, entity, requireId2(id, action), parsed);
|
|
8096
8550
|
case "done":
|
|
8097
8551
|
return pmDone(ctx, entity, requireId2(id, action), parsed);
|
|
@@ -8100,10 +8554,7 @@ async function pmCommand(parsed, deps = {}) {
|
|
|
8100
8554
|
case "comments":
|
|
8101
8555
|
return pmComments(ctx, entity, requireId2(id, action));
|
|
8102
8556
|
case "rm":
|
|
8103
|
-
case "delete":
|
|
8104
8557
|
return pmRemove(ctx, entity, requireId2(id, action));
|
|
8105
|
-
default:
|
|
8106
|
-
throw new Error(`unknown pm action "${action}". Try list|add|get|set|done|comment|comments|rm.`);
|
|
8107
8558
|
}
|
|
8108
8559
|
}
|
|
8109
8560
|
|
|
@@ -8115,6 +8566,20 @@ function statusVerdict(reads) {
|
|
|
8115
8566
|
sourceHttp(reasons, "canary", reads.canary);
|
|
8116
8567
|
sourceHttp(reasons, "collector", reads.collector);
|
|
8117
8568
|
sourceHttp(reasons, "provider", reads.provider);
|
|
8569
|
+
if (reads.providerCapacity) {
|
|
8570
|
+
sourceHttp(
|
|
8571
|
+
reasons,
|
|
8572
|
+
"provider",
|
|
8573
|
+
reads.providerCapacity,
|
|
8574
|
+
"provider_capacity_"
|
|
8575
|
+
);
|
|
8576
|
+
}
|
|
8577
|
+
sourceHttp(
|
|
8578
|
+
reasons,
|
|
8579
|
+
"provider",
|
|
8580
|
+
reads.providerReconciliation,
|
|
8581
|
+
"provider_reconciliation_"
|
|
8582
|
+
);
|
|
8118
8583
|
sourceHttp(reasons, "provider", reads.providerHistory, "provider_history_");
|
|
8119
8584
|
const liveStatus = String(reads.liveSync.body.status ?? "");
|
|
8120
8585
|
if (liveStatus === "partial") {
|
|
@@ -8187,6 +8652,17 @@ function statusVerdict(reads) {
|
|
|
8187
8652
|
severity: "degraded"
|
|
8188
8653
|
});
|
|
8189
8654
|
}
|
|
8655
|
+
const reconciliationStatus = String(
|
|
8656
|
+
reads.providerReconciliation.body.status ?? ""
|
|
8657
|
+
);
|
|
8658
|
+
if (reads.providerReconciliation.httpStatus >= 200 && reads.providerReconciliation.httpStatus < 300 && reconciliationStatus && reconciliationStatus !== "within_budget") {
|
|
8659
|
+
const reconciliationReason = reads.providerReconciliation.body.reason;
|
|
8660
|
+
reasons.push({
|
|
8661
|
+
source: "provider",
|
|
8662
|
+
code: reconciliationStatus === "outside_budget" ? typeof reconciliationReason === "string" && reconciliationReason ? reconciliationReason : "request_coverage_outside_budget" : `request_reconciliation_${reconciliationStatus}`,
|
|
8663
|
+
severity: "degraded"
|
|
8664
|
+
});
|
|
8665
|
+
}
|
|
8190
8666
|
const providerHistoryStatus = String(reads.providerHistory.body.status ?? "");
|
|
8191
8667
|
if (providerHistoryStatus && providerHistoryStatus !== "observed" && providerHistoryStatus !== "no_data") {
|
|
8192
8668
|
reasons.push({
|
|
@@ -8257,8 +8733,19 @@ function printO11yStatus(status, out) {
|
|
|
8257
8733
|
`collector ${status.collector.httpStatus} ${String(status.collector.body.status ?? status.collector.body.error ?? "unavailable")} ${numeric3(collectorStorage.affectedPoints)} affected points`
|
|
8258
8734
|
);
|
|
8259
8735
|
const providerMetrics = record6(status.provider.body.metrics) ? status.provider.body.metrics : {};
|
|
8736
|
+
const providerCapacity = record6(status.provider.body.capacity) ? status.provider.body.capacity : {};
|
|
8737
|
+
const workerMemory = record6(providerCapacity.memory) ? providerCapacity.memory : {};
|
|
8260
8738
|
out.log(
|
|
8261
|
-
`cloudflare ${status.provider.httpStatus} ${String(status.provider.body.status ?? status.provider.body.error ?? "unavailable")} ${numeric3(providerMetrics.requests)} invocations ${numeric3(providerMetrics.errors)} runtime errors`
|
|
8739
|
+
`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`
|
|
8740
|
+
);
|
|
8741
|
+
for (const line of providerCapacityLines(status.providerCapacity)) {
|
|
8742
|
+
out.log(line);
|
|
8743
|
+
}
|
|
8744
|
+
const coverage = record6(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
|
|
8745
|
+
const coverageCounts = record6(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
|
|
8746
|
+
const coverageBudget = record6(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
|
|
8747
|
+
out.log(
|
|
8748
|
+
`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`
|
|
8262
8749
|
);
|
|
8263
8750
|
const providerPoints = Array.isArray(status.providerHistory.body.points) ? status.providerHistory.body.points.length : 0;
|
|
8264
8751
|
const providerFreshness = record6(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
|
|
@@ -8269,6 +8756,27 @@ function printO11yStatus(status, out) {
|
|
|
8269
8756
|
`verdict ${status.verdict.status} ${status.verdict.reasons.map((reason) => `${reason.source}:${reason.code}`).join(", ") || "all required signals healthy"}`
|
|
8270
8757
|
);
|
|
8271
8758
|
}
|
|
8759
|
+
function providerCapacityLines(read3) {
|
|
8760
|
+
const resources = record6(read3.body.resources) ? read3.body.resources : {};
|
|
8761
|
+
const durableObjects = record6(resources.durableObjects) ? resources.durableObjects : {};
|
|
8762
|
+
const periodic = record6(durableObjects.periodic) ? durableObjects.periodic : {};
|
|
8763
|
+
const storage = record6(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
|
|
8764
|
+
const d1 = record6(resources.d1) ? resources.d1 : {};
|
|
8765
|
+
const d1Activity = record6(d1.activity) ? d1.activity : {};
|
|
8766
|
+
const d1Storage = record6(d1.storage) ? d1.storage : {};
|
|
8767
|
+
const d1Latency = record6(d1Activity.latency) ? d1Activity.latency : {};
|
|
8768
|
+
const r2 = record6(resources.r2) ? resources.r2 : {};
|
|
8769
|
+
const r2Operations = record6(r2.operations) ? r2.operations : {};
|
|
8770
|
+
const r2Storage = record6(r2.storage) ? r2.storage : {};
|
|
8771
|
+
const status = String(
|
|
8772
|
+
read3.body.status ?? read3.body.error ?? "unavailable"
|
|
8773
|
+
);
|
|
8774
|
+
return [
|
|
8775
|
+
`cloudflare-capacity ${read3.httpStatus} ${status} ${optionalBytes(storage.storedBytes)} SQLite DO storage ${optionalCount(periodic.activeWebsocketConnectionsMax, "active socket max")} ${optionalAgeSeconds(storage.ageSeconds)} storage evidence age`,
|
|
8776
|
+
`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`,
|
|
8777
|
+
`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`
|
|
8778
|
+
];
|
|
8779
|
+
}
|
|
8272
8780
|
function liveSyncLine(read3) {
|
|
8273
8781
|
const performance = record6(read3.body.performance) ? read3.body.performance : {};
|
|
8274
8782
|
const commitToSend = record6(performance.commitToSend) ? performance.commitToSend : {};
|
|
@@ -8287,12 +8795,42 @@ function optionalAge(value) {
|
|
|
8287
8795
|
if (typeof value !== "number" || !Number.isFinite(value)) return "unknown";
|
|
8288
8796
|
return `${Math.max(0, Math.round(value / 1e3))}s`;
|
|
8289
8797
|
}
|
|
8798
|
+
function optionalPercent(value) {
|
|
8799
|
+
return typeof value === "number" && Number.isFinite(value) ? `${Math.round(value * 100)}%` : "\u2014";
|
|
8800
|
+
}
|
|
8801
|
+
function optionalBytes(value) {
|
|
8802
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return "\u2014";
|
|
8803
|
+
if (value >= 1024 * 1024 * 1024) {
|
|
8804
|
+
return `${(value / (1024 * 1024 * 1024)).toFixed(2)} GiB`;
|
|
8805
|
+
}
|
|
8806
|
+
if (value >= 1024 * 1024) {
|
|
8807
|
+
return `${(value / (1024 * 1024)).toFixed(1)} MiB`;
|
|
8808
|
+
}
|
|
8809
|
+
if (value >= 1024) return `${(value / 1024).toFixed(1)} KiB`;
|
|
8810
|
+
return `${Math.round(value)} B`;
|
|
8811
|
+
}
|
|
8812
|
+
function optionalCount(value, unit) {
|
|
8813
|
+
return typeof value === "number" && Number.isFinite(value) ? `${value} ${unit}` : `\u2014 ${unit}`;
|
|
8814
|
+
}
|
|
8815
|
+
function optionalAgeSeconds(value) {
|
|
8816
|
+
return typeof value === "number" && Number.isFinite(value) ? `${Math.max(0, Math.round(value))}s` : "unknown";
|
|
8817
|
+
}
|
|
8290
8818
|
|
|
8291
8819
|
// src/o11y-command.ts
|
|
8292
8820
|
async function o11yCommand(parsed, deps = {}) {
|
|
8293
8821
|
assertArgs(
|
|
8294
8822
|
parsed,
|
|
8295
|
-
[
|
|
8823
|
+
[
|
|
8824
|
+
"config",
|
|
8825
|
+
"context",
|
|
8826
|
+
"platform",
|
|
8827
|
+
"token",
|
|
8828
|
+
"email",
|
|
8829
|
+
"json",
|
|
8830
|
+
"app",
|
|
8831
|
+
"env",
|
|
8832
|
+
"minutes"
|
|
8833
|
+
],
|
|
8296
8834
|
2
|
|
8297
8835
|
);
|
|
8298
8836
|
const action = parsed.positionals[1];
|
|
@@ -8304,9 +8842,12 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
8304
8842
|
const minutes = statusMinutes(
|
|
8305
8843
|
numberOpt(parsed.options.minutes, "--minutes") ?? 60
|
|
8306
8844
|
);
|
|
8307
|
-
const
|
|
8308
|
-
|
|
8309
|
-
|
|
8845
|
+
const context = await resolveOperatorContext(parsed, {
|
|
8846
|
+
allowMissingConfig: true,
|
|
8847
|
+
defaultEnvironment: "prod",
|
|
8848
|
+
requireApp: true
|
|
8849
|
+
});
|
|
8850
|
+
const cfg = context.cfg;
|
|
8310
8851
|
const doFetch = deps.fetch ?? fetch;
|
|
8311
8852
|
const out = deps.stdout ?? console;
|
|
8312
8853
|
const token = await getDeveloperToken(
|
|
@@ -8320,8 +8861,8 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
8320
8861
|
doFetch,
|
|
8321
8862
|
out
|
|
8322
8863
|
);
|
|
8323
|
-
const appId =
|
|
8324
|
-
const env =
|
|
8864
|
+
const appId = context.app.value;
|
|
8865
|
+
const env = context.environment.value;
|
|
8325
8866
|
const base = `${cfg.platformUrl}/o11y/${encodeURIComponent(appId)}`;
|
|
8326
8867
|
const query = new URLSearchParams({ env, minutes: String(minutes) });
|
|
8327
8868
|
const headers = { authorization: `Bearer ${token}` };
|
|
@@ -8338,6 +8879,8 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
8338
8879
|
canary,
|
|
8339
8880
|
collector,
|
|
8340
8881
|
provider,
|
|
8882
|
+
providerCapacity,
|
|
8883
|
+
providerReconciliation,
|
|
8341
8884
|
providerHistory
|
|
8342
8885
|
] = await Promise.all([
|
|
8343
8886
|
read2(`${base}/metrics/red?${query}`, headers, doFetch),
|
|
@@ -8354,6 +8897,19 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
8354
8897
|
),
|
|
8355
8898
|
read2(`${base}/self-health?${query}`, headers, doFetch),
|
|
8356
8899
|
read2(`${base}/provider/cloudflare?${query}`, headers, doFetch),
|
|
8900
|
+
appId === "odla-db" ? read2(`${base}/provider/cloudflare/capacity`, headers, doFetch) : Promise.resolve({
|
|
8901
|
+
httpStatus: 204,
|
|
8902
|
+
body: {
|
|
8903
|
+
status: "not_applicable",
|
|
8904
|
+
scope: "cloudflare_account",
|
|
8905
|
+
reason: "account-scoped Durable Object capacity is exposed only through odla-db"
|
|
8906
|
+
}
|
|
8907
|
+
}),
|
|
8908
|
+
read2(
|
|
8909
|
+
`${base}/provider/cloudflare/reconciliation?${query}`,
|
|
8910
|
+
headers,
|
|
8911
|
+
doFetch
|
|
8912
|
+
),
|
|
8357
8913
|
read2(`${base}/provider/cloudflare/history?${query}`, headers, doFetch)
|
|
8358
8914
|
]);
|
|
8359
8915
|
const verdict = statusVerdict({
|
|
@@ -8362,10 +8918,12 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
8362
8918
|
canary,
|
|
8363
8919
|
collector,
|
|
8364
8920
|
provider,
|
|
8921
|
+
providerCapacity,
|
|
8922
|
+
providerReconciliation,
|
|
8365
8923
|
providerHistory
|
|
8366
8924
|
});
|
|
8367
8925
|
const status = {
|
|
8368
|
-
schemaVersion:
|
|
8926
|
+
schemaVersion: 9,
|
|
8369
8927
|
scope: { appId, env, minutes },
|
|
8370
8928
|
observedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8371
8929
|
verdict,
|
|
@@ -8375,6 +8933,8 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
8375
8933
|
canary,
|
|
8376
8934
|
collector,
|
|
8377
8935
|
provider,
|
|
8936
|
+
providerCapacity,
|
|
8937
|
+
providerReconciliation,
|
|
8378
8938
|
providerHistory
|
|
8379
8939
|
};
|
|
8380
8940
|
if (parsed.options.json === true) {
|
|
@@ -8407,7 +8967,7 @@ async function read2(url, headers, doFetch) {
|
|
|
8407
8967
|
// src/provision.ts
|
|
8408
8968
|
var import_apps5 = require("@odla-ai/apps");
|
|
8409
8969
|
var import_ai3 = require("@odla-ai/ai");
|
|
8410
|
-
var
|
|
8970
|
+
var import_node_process10 = __toESM(require("process"), 1);
|
|
8411
8971
|
|
|
8412
8972
|
// src/integration-provision.ts
|
|
8413
8973
|
var import_db3 = require("@odla-ai/db");
|
|
@@ -8766,7 +9326,7 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
|
|
|
8766
9326
|
await provisionIntegrationSeeds(doFetch, cfg.dbEndpoint, tenantId, dbKey, database.integrations, env, out);
|
|
8767
9327
|
}
|
|
8768
9328
|
if (cfg.services.includes("ai") && cfg.ai?.provider && cfg.ai.keyEnv) {
|
|
8769
|
-
const key =
|
|
9329
|
+
const key = import_node_process10.default.env[cfg.ai.keyEnv];
|
|
8770
9330
|
if (key) {
|
|
8771
9331
|
const secretName = cfg.ai.secretName ?? defaultSecretName(cfg.ai.provider);
|
|
8772
9332
|
await (0, import_ai3.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, secretName, key);
|
|
@@ -8807,8 +9367,8 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
|
|
|
8807
9367
|
}
|
|
8808
9368
|
|
|
8809
9369
|
// src/record.ts
|
|
8810
|
-
var
|
|
8811
|
-
var
|
|
9370
|
+
var import_node_fs15 = require("fs");
|
|
9371
|
+
var import_node_process11 = __toESM(require("process"), 1);
|
|
8812
9372
|
|
|
8813
9373
|
// src/surface.ts
|
|
8814
9374
|
var PM_ACTIONS = {
|
|
@@ -8856,6 +9416,7 @@ var COMMAND_SURFACE = {
|
|
|
8856
9416
|
calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
|
|
8857
9417
|
capabilities: {},
|
|
8858
9418
|
code: { connect: {} },
|
|
9419
|
+
context: { show: {}, list: {}, save: {}, remove: {} },
|
|
8859
9420
|
// `watch`, `read`, `reply`, and `resolve` take a topic id from there on.
|
|
8860
9421
|
discuss: {
|
|
8861
9422
|
groups: {},
|
|
@@ -8950,27 +9511,22 @@ function invocationPath(words2) {
|
|
|
8950
9511
|
|
|
8951
9512
|
// src/record.ts
|
|
8952
9513
|
function recordInvocation(parsed) {
|
|
8953
|
-
const file =
|
|
9514
|
+
const file = import_node_process11.default.env.ODLA_CLI_RECORD;
|
|
8954
9515
|
if (!file) return;
|
|
8955
9516
|
try {
|
|
8956
9517
|
const entry = {
|
|
8957
9518
|
path: invocationPath(parsed.positionals),
|
|
8958
|
-
options: Object.
|
|
9519
|
+
options: Object.entries(parsed.options).map(([name, value]) => value === false ? `no-${name}` : name).sort()
|
|
8959
9520
|
};
|
|
8960
9521
|
if (!entry.path.length) return;
|
|
8961
|
-
(0,
|
|
9522
|
+
(0, import_node_fs15.appendFileSync)(file, `${JSON.stringify(entry)}
|
|
8962
9523
|
`);
|
|
8963
9524
|
} catch {
|
|
8964
9525
|
}
|
|
8965
9526
|
}
|
|
8966
9527
|
|
|
8967
|
-
// src/runbook-command.ts
|
|
8968
|
-
var import_node_fs18 = require("fs");
|
|
8969
|
-
var import_node_path14 = require("path");
|
|
8970
|
-
var import_node_process10 = __toESM(require("process"), 1);
|
|
8971
|
-
|
|
8972
9528
|
// src/runbook-actions.ts
|
|
8973
|
-
var
|
|
9529
|
+
var import_node_fs16 = require("fs");
|
|
8974
9530
|
|
|
8975
9531
|
// src/runbook-requires.ts
|
|
8976
9532
|
var SPEC = /^(@?[\w./-]+?)@(\d+\.\d+\.\d+(?:[\w.-]*)?)$/;
|
|
@@ -9055,7 +9611,7 @@ async function bySlug(ctx, slug) {
|
|
|
9055
9611
|
function readBody(file, inline) {
|
|
9056
9612
|
if (inline !== void 0) return inline;
|
|
9057
9613
|
if (file === void 0) throw new Error("supply the new text with --file <path>, --file - (stdin), or --body");
|
|
9058
|
-
return (0,
|
|
9614
|
+
return (0, import_node_fs16.readFileSync)(file === "-" ? 0 : file, "utf8");
|
|
9059
9615
|
}
|
|
9060
9616
|
var stamp = (ms) => ms ? new Date(ms).toISOString().slice(0, 16).replace("T", " ") : "";
|
|
9061
9617
|
async function runbookList(ctx, all, query) {
|
|
@@ -9142,8 +9698,8 @@ async function runbookRemove(ctx, slug) {
|
|
|
9142
9698
|
}
|
|
9143
9699
|
|
|
9144
9700
|
// src/runbook-import.ts
|
|
9145
|
-
var
|
|
9146
|
-
var
|
|
9701
|
+
var import_node_fs17 = require("fs");
|
|
9702
|
+
var import_node_path13 = require("path");
|
|
9147
9703
|
function parseRunbook(text, slug) {
|
|
9148
9704
|
let rest = text;
|
|
9149
9705
|
const meta = {};
|
|
@@ -9168,12 +9724,12 @@ function parseRunbook(text, slug) {
|
|
|
9168
9724
|
};
|
|
9169
9725
|
}
|
|
9170
9726
|
function readRunbookDir(dir) {
|
|
9171
|
-
if (!(0,
|
|
9172
|
-
const files = (0,
|
|
9727
|
+
if (!(0, import_node_fs17.statSync)(dir, { throwIfNoEntry: false })?.isDirectory()) throw new Error(`not a directory: ${dir}`);
|
|
9728
|
+
const files = (0, import_node_fs17.readdirSync)(dir).filter((f) => f.endsWith(".md")).sort();
|
|
9173
9729
|
if (!files.length) throw new Error(`no .md files in ${dir}`);
|
|
9174
9730
|
return files.map((file) => {
|
|
9175
|
-
const slug = (0,
|
|
9176
|
-
const parsed = parseRunbook((0,
|
|
9731
|
+
const slug = (0, import_node_path13.basename)(file, ".md");
|
|
9732
|
+
const parsed = parseRunbook((0, import_node_fs17.readFileSync)((0, import_node_path13.join)(dir, file), "utf8"), slug);
|
|
9177
9733
|
return { file, slug, ...parsed, words: parsed.body.split(/\s+/).filter(Boolean).length };
|
|
9178
9734
|
});
|
|
9179
9735
|
}
|
|
@@ -9245,8 +9801,8 @@ async function upsert(ctx, r, visibility) {
|
|
|
9245
9801
|
|
|
9246
9802
|
// src/runbook-impact.ts
|
|
9247
9803
|
var import_node_child_process7 = require("child_process");
|
|
9248
|
-
var
|
|
9249
|
-
var
|
|
9804
|
+
var import_node_fs18 = require("fs");
|
|
9805
|
+
var import_node_path14 = require("path");
|
|
9250
9806
|
|
|
9251
9807
|
// src/runbook-impact-scan.ts
|
|
9252
9808
|
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$]*)/;
|
|
@@ -9415,10 +9971,10 @@ ${body.split("\n").map((line) => `+${line}`).join("\n")}
|
|
|
9415
9971
|
}
|
|
9416
9972
|
function manifestLabeller(root) {
|
|
9417
9973
|
return (workspace) => {
|
|
9418
|
-
const manifest = (0,
|
|
9419
|
-
if (!(0,
|
|
9974
|
+
const manifest = (0, import_node_path14.join)(root, workspace, "package.json");
|
|
9975
|
+
if (!(0, import_node_fs18.existsSync)(manifest)) return void 0;
|
|
9420
9976
|
try {
|
|
9421
|
-
const name = JSON.parse((0,
|
|
9977
|
+
const name = JSON.parse((0, import_node_fs18.readFileSync)(manifest, "utf8")).name;
|
|
9422
9978
|
return typeof name === "string" ? name : void 0;
|
|
9423
9979
|
} catch {
|
|
9424
9980
|
return void 0;
|
|
@@ -9485,7 +10041,7 @@ function report3(ctx, impacts) {
|
|
|
9485
10041
|
async function runbookImpact(ctx, options, deps = {}) {
|
|
9486
10042
|
const cwd = deps.cwd ?? process.cwd();
|
|
9487
10043
|
const runGit = deps.runGit ?? gitRunner(cwd);
|
|
9488
|
-
const read3 = deps.readRepoFile ?? ((path) => (0,
|
|
10044
|
+
const read3 = deps.readRepoFile ?? ((path) => (0, import_node_fs18.readFileSync)((0, import_node_path14.join)(cwd, path), "utf8"));
|
|
9489
10045
|
const surfaces = changedSurfaces(collectDiff(runGit, options.base, read3), manifestLabeller(cwd));
|
|
9490
10046
|
if (!surfaces.length) {
|
|
9491
10047
|
return ctx.out.log(
|
|
@@ -9618,12 +10174,12 @@ async function runbookComment(ctx, slug, body) {
|
|
|
9618
10174
|
|
|
9619
10175
|
// src/runbook-editor.ts
|
|
9620
10176
|
var import_node_child_process8 = require("child_process");
|
|
9621
|
-
var
|
|
9622
|
-
var
|
|
9623
|
-
var
|
|
9624
|
-
var
|
|
10177
|
+
var import_node_fs19 = require("fs");
|
|
10178
|
+
var import_node_os5 = require("os");
|
|
10179
|
+
var import_node_path15 = require("path");
|
|
10180
|
+
var import_node_process12 = __toESM(require("process"), 1);
|
|
9625
10181
|
var EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
|
|
9626
|
-
function resolveEditor(env =
|
|
10182
|
+
function resolveEditor(env = import_node_process12.default.env) {
|
|
9627
10183
|
for (const name of EDITOR_ENV) {
|
|
9628
10184
|
const value = env[name];
|
|
9629
10185
|
if (value && value.trim()) return value.trim();
|
|
@@ -9637,8 +10193,8 @@ function defaultRun(command, path) {
|
|
|
9637
10193
|
return result.status ?? 0;
|
|
9638
10194
|
}
|
|
9639
10195
|
function editText(initial, slug, deps = {}) {
|
|
9640
|
-
const env = deps.env ??
|
|
9641
|
-
const interactive = deps.interactive ?? (() => Boolean(
|
|
10196
|
+
const env = deps.env ?? import_node_process12.default.env;
|
|
10197
|
+
const interactive = deps.interactive ?? (() => Boolean(import_node_process12.default.stdin.isTTY));
|
|
9642
10198
|
const editor = resolveEditor(env);
|
|
9643
10199
|
if (!editor)
|
|
9644
10200
|
throw new Error(
|
|
@@ -9646,16 +10202,16 @@ function editText(initial, slug, deps = {}) {
|
|
|
9646
10202
|
);
|
|
9647
10203
|
if (!interactive())
|
|
9648
10204
|
throw new Error(`cannot open an editor without a terminal \u2014 pass --file <path> or --body "\u2026" instead`);
|
|
9649
|
-
const dir = (0,
|
|
9650
|
-
const file = (0,
|
|
10205
|
+
const dir = (0, import_node_fs19.mkdtempSync)((0, import_node_path15.join)((0, import_node_os5.tmpdir)(), "odla-runbook-"));
|
|
10206
|
+
const file = (0, import_node_path15.join)(dir, `${slug}.md`);
|
|
9651
10207
|
try {
|
|
9652
|
-
(0,
|
|
10208
|
+
(0, import_node_fs19.writeFileSync)(file, initial, { mode: 384 });
|
|
9653
10209
|
const code = defaultRunOrInjected(deps)(editor, file);
|
|
9654
10210
|
if (code !== 0) throw new Error(`editor "${editor}" exited with ${code}; nothing was written`);
|
|
9655
|
-
const edited = (0,
|
|
10211
|
+
const edited = (0, import_node_fs19.readFileSync)(file, "utf8");
|
|
9656
10212
|
return edited === initial ? null : edited;
|
|
9657
10213
|
} finally {
|
|
9658
|
-
(0,
|
|
10214
|
+
(0, import_node_fs19.rmSync)(dir, { recursive: true, force: true });
|
|
9659
10215
|
}
|
|
9660
10216
|
}
|
|
9661
10217
|
var defaultRunOrInjected = (deps) => deps.run ?? defaultRun;
|
|
@@ -9693,10 +10249,16 @@ function credentialKind(identity) {
|
|
|
9693
10249
|
return identity.scopes.length ? "device token (scoped)" : "device token or session";
|
|
9694
10250
|
}
|
|
9695
10251
|
async function whoamiCommand(parsed, deps = {}) {
|
|
9696
|
-
assertArgs(
|
|
10252
|
+
assertArgs(
|
|
10253
|
+
parsed,
|
|
10254
|
+
["config", "context", "platform", "token", "email", "json"],
|
|
10255
|
+
1
|
|
10256
|
+
);
|
|
9697
10257
|
const out = deps.stdout ?? console;
|
|
9698
10258
|
const doFetch = deps.fetch ?? fetch;
|
|
9699
|
-
const cfg = await
|
|
10259
|
+
const { cfg } = await resolveOperatorContext(parsed, {
|
|
10260
|
+
allowMissingConfig: true
|
|
10261
|
+
});
|
|
9700
10262
|
const token = await getDeveloperToken(
|
|
9701
10263
|
cfg,
|
|
9702
10264
|
{
|
|
@@ -9727,7 +10289,7 @@ async function whoamiCommand(parsed, deps = {}) {
|
|
|
9727
10289
|
}
|
|
9728
10290
|
|
|
9729
10291
|
// src/runbook-command.ts
|
|
9730
|
-
var
|
|
10292
|
+
var ALLOWED2 = [
|
|
9731
10293
|
"config",
|
|
9732
10294
|
"token",
|
|
9733
10295
|
"email",
|
|
@@ -9745,34 +10307,24 @@ var ALLOWED3 = [
|
|
|
9745
10307
|
"dry-run",
|
|
9746
10308
|
"limit",
|
|
9747
10309
|
"base",
|
|
9748
|
-
"requires"
|
|
10310
|
+
"requires",
|
|
10311
|
+
"platform",
|
|
10312
|
+
"context"
|
|
9749
10313
|
];
|
|
9750
10314
|
function requireSlug(slug, action) {
|
|
9751
10315
|
if (!slug) throw new Error(`"runbook ${action}" needs a slug, e.g. "odla-ai runbook ${action} release"`);
|
|
9752
10316
|
return slug;
|
|
9753
10317
|
}
|
|
9754
10318
|
var WRITES = /* @__PURE__ */ new Set(["new", "edit", "publish", "archive", "visibility", "revert", "rm", "import"]);
|
|
9755
|
-
var CONFIG_FREE = /* @__PURE__ */ new Set(["list", "search", "ask", "get", "cat", "history", "impact", "lint"]);
|
|
9756
|
-
async function loadOrDefaultConfig(configPath, action, appId) {
|
|
9757
|
-
const projectScoped = appId !== void 0;
|
|
9758
|
-
if (!projectScoped && CONFIG_FREE.has(action) && !(0, import_node_fs18.existsSync)((0, import_node_path14.resolve)(configPath))) {
|
|
9759
|
-
const rootDir = import_node_process10.default.cwd();
|
|
9760
|
-
return {
|
|
9761
|
-
configPath,
|
|
9762
|
-
platformUrl: import_node_process10.default.env.ODLA_PLATFORM_URL ?? "https://odla.ai",
|
|
9763
|
-
rootDir,
|
|
9764
|
-
app: { id: "", name: "" },
|
|
9765
|
-
local: { tokenFile: (0, import_node_path14.join)(rootDir, ".odla/dev-token.json") }
|
|
9766
|
-
};
|
|
9767
|
-
}
|
|
9768
|
-
return loadProjectConfig(configPath);
|
|
9769
|
-
}
|
|
9770
10319
|
async function buildContext3(parsed, deps, action) {
|
|
9771
|
-
const
|
|
9772
|
-
const
|
|
10320
|
+
const appIdOption = stringOpt(parsed.options.app);
|
|
10321
|
+
const context = await resolveOperatorContext(parsed, {
|
|
10322
|
+
allowMissingConfig: true
|
|
10323
|
+
});
|
|
10324
|
+
const { cfg } = context;
|
|
9773
10325
|
const doFetch = deps.fetch ?? fetch;
|
|
9774
10326
|
const out = deps.stdout ?? console;
|
|
9775
|
-
const appId =
|
|
10327
|
+
const appId = appIdOption ?? (context.app.source === "environment" || context.app.source === "profile" ? context.app.value : null) ?? PLATFORM_SCOPE;
|
|
9776
10328
|
const dryRun = parsed.options["dry-run"] === true;
|
|
9777
10329
|
if (action === "import" && dryRun) {
|
|
9778
10330
|
return {
|
|
@@ -9793,8 +10345,10 @@ async function buildContext3(parsed, deps, action) {
|
|
|
9793
10345
|
fetch: doFetch,
|
|
9794
10346
|
stdout: out,
|
|
9795
10347
|
openApprovalUrl: deps.openUrl,
|
|
9796
|
-
//
|
|
9797
|
-
|
|
10348
|
+
// A project, named context, or the global operator context owns the
|
|
10349
|
+
// exact-scope cache; it never follows an arbitrary shell directory.
|
|
10350
|
+
rootDir: cfg.rootDir,
|
|
10351
|
+
tokenFile: context.credentials.scopedTokenFile
|
|
9798
10352
|
}) : await getDeveloperToken(
|
|
9799
10353
|
cfg,
|
|
9800
10354
|
{
|
|
@@ -9823,7 +10377,7 @@ async function buildContext3(parsed, deps, action) {
|
|
|
9823
10377
|
}
|
|
9824
10378
|
async function runbookCommand(parsed, deps = {}) {
|
|
9825
10379
|
const action = parsed.positionals[1] ?? "list";
|
|
9826
|
-
assertArgs(parsed,
|
|
10380
|
+
assertArgs(parsed, ALLOWED2, action === "ask" || action === "search" ? 64 : 4);
|
|
9827
10381
|
const ctx = await buildContext3(parsed, deps, action);
|
|
9828
10382
|
const slug = parsed.positionals[2];
|
|
9829
10383
|
switch (action) {
|
|
@@ -10069,7 +10623,7 @@ function hostedSeverity(value, flag) {
|
|
|
10069
10623
|
var import_security2 = require("@odla-ai/security");
|
|
10070
10624
|
|
|
10071
10625
|
// src/security.ts
|
|
10072
|
-
var
|
|
10626
|
+
var import_node_path16 = require("path");
|
|
10073
10627
|
var import_security = require("@odla-ai/security");
|
|
10074
10628
|
var import_node3 = require("@odla-ai/security/node");
|
|
10075
10629
|
async function runHostedSecurity(options) {
|
|
@@ -10081,9 +10635,9 @@ async function runHostedSecurity(options) {
|
|
|
10081
10635
|
const appId = selfAudit ? "odla-ai" : cfg.app.id;
|
|
10082
10636
|
const env = selfAudit ? "prod" : selectEnv(options.env, cfg.envs, cfg.configPath, cfg.rootDir);
|
|
10083
10637
|
const platform = options.platform ?? cfg?.platformUrl ?? "https://odla.ai";
|
|
10084
|
-
const target = (0,
|
|
10085
|
-
const output = (0,
|
|
10086
|
-
const outputRelative = (0,
|
|
10638
|
+
const target = (0, import_node_path16.resolve)(options.target ?? cfg?.rootDir ?? ".");
|
|
10639
|
+
const output = (0, import_node_path16.resolve)(options.out ?? (0, import_node_path16.resolve)(target, ".odla/security/hosted"));
|
|
10640
|
+
const outputRelative = (0, import_node_path16.relative)(target, output).split(import_node_path16.sep).join("/");
|
|
10087
10641
|
if (!outputRelative) throw new Error("Hosted security output cannot be the repository root");
|
|
10088
10642
|
const profile = profileFor(options.profile ?? "odla", options.maxHuntTasks ?? 12);
|
|
10089
10643
|
const tokenRequest = {
|
|
@@ -10095,7 +10649,7 @@ async function runHostedSecurity(options) {
|
|
|
10095
10649
|
};
|
|
10096
10650
|
const token = await injectedToken(options, tokenRequest);
|
|
10097
10651
|
const snapshot = await (0, import_node3.snapshotDirectory)(target, {
|
|
10098
|
-
exclude: !outputRelative.startsWith("../") && !(0,
|
|
10652
|
+
exclude: !outputRelative.startsWith("../") && !(0, import_node_path16.isAbsolute)(outputRelative) ? [outputRelative] : []
|
|
10099
10653
|
});
|
|
10100
10654
|
const hosted = await (0, import_security.createPlatformSecurityReasoners)({
|
|
10101
10655
|
platform,
|
|
@@ -10113,7 +10667,7 @@ async function runHostedSecurity(options) {
|
|
|
10113
10667
|
});
|
|
10114
10668
|
const harness = (0, import_security.createSecurityHarness)({
|
|
10115
10669
|
profile,
|
|
10116
|
-
store: new import_node3.FileRunStore((0,
|
|
10670
|
+
store: new import_node3.FileRunStore((0, import_node_path16.resolve)(output, "state")),
|
|
10117
10671
|
discoveryReasoner: hosted.discoveryReasoner,
|
|
10118
10672
|
validationReasoner: hosted.validationReasoner,
|
|
10119
10673
|
policy: {
|
|
@@ -10137,7 +10691,7 @@ async function runHostedSecurity(options) {
|
|
|
10137
10691
|
function selectEnv(requested, declared, configPath, rootDir) {
|
|
10138
10692
|
const env = requested ?? (declared.includes("dev") ? "dev" : declared[0]);
|
|
10139
10693
|
if (!env || !declared.includes(env)) {
|
|
10140
|
-
const shown = (0,
|
|
10694
|
+
const shown = (0, import_node_path16.relative)(rootDir, configPath) || configPath;
|
|
10141
10695
|
throw new Error(`env "${env ?? ""}" is not declared in ${shown}`);
|
|
10142
10696
|
}
|
|
10143
10697
|
return env;
|
|
@@ -10166,7 +10720,7 @@ function printSummary(out, appId, env, run, report4, output) {
|
|
|
10166
10720
|
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}`);
|
|
10167
10721
|
if (report4.callBudget) out.log(` calls: discovery=${formatBudget(report4.callBudget.discovery)} validation=${formatBudget(report4.callBudget.validation)}`);
|
|
10168
10722
|
out.log(` findings: confirmed=${report4.metrics.confirmed} needs_reproduction=${report4.metrics.needsReproduction} candidates=${report4.metrics.candidates}`);
|
|
10169
|
-
out.log(` report: ${(0,
|
|
10723
|
+
out.log(` report: ${(0, import_node_path16.resolve)(output, "REPORT.md")}`);
|
|
10170
10724
|
}
|
|
10171
10725
|
function formatBudget(usage) {
|
|
10172
10726
|
return usage ? `${usage.usedCalls}/${usage.maxCalls} skipped=${usage.skippedCalls}` : "caller-managed";
|
|
@@ -10635,6 +11189,10 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
|
10635
11189
|
await whoamiCommand(parsed, runtime);
|
|
10636
11190
|
return;
|
|
10637
11191
|
}
|
|
11192
|
+
if (command === "context") {
|
|
11193
|
+
await contextCommand(parsed, runtime);
|
|
11194
|
+
return;
|
|
11195
|
+
}
|
|
10638
11196
|
if (command === "runbook") {
|
|
10639
11197
|
await runbookCommand(parsed, runtime);
|
|
10640
11198
|
return;
|