@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
|
@@ -138,7 +138,7 @@ import process2 from "process";
|
|
|
138
138
|
async function openUrl(url, options = {}) {
|
|
139
139
|
const command = openerFor(options.platform ?? process2.platform);
|
|
140
140
|
const doSpawn = options.spawnImpl ?? spawn;
|
|
141
|
-
await new Promise((
|
|
141
|
+
await new Promise((resolve12, reject) => {
|
|
142
142
|
const child = doSpawn(command.cmd, [...command.args, url], {
|
|
143
143
|
stdio: "ignore",
|
|
144
144
|
detached: true
|
|
@@ -146,7 +146,7 @@ async function openUrl(url, options = {}) {
|
|
|
146
146
|
child.once("error", reject);
|
|
147
147
|
child.once("spawn", () => {
|
|
148
148
|
child.unref();
|
|
149
|
-
|
|
149
|
+
resolve12();
|
|
150
150
|
});
|
|
151
151
|
});
|
|
152
152
|
}
|
|
@@ -1414,8 +1414,8 @@ function credential(value) {
|
|
|
1414
1414
|
// src/calendar-poll.ts
|
|
1415
1415
|
async function waitForCalendarPoll(milliseconds, signal) {
|
|
1416
1416
|
if (signal?.aborted) throw signal.reason ?? new Error("calendar connection aborted");
|
|
1417
|
-
await new Promise((
|
|
1418
|
-
const timer = setTimeout(
|
|
1417
|
+
await new Promise((resolve12, reject) => {
|
|
1418
|
+
const timer = setTimeout(resolve12, milliseconds);
|
|
1419
1419
|
signal?.addEventListener("abort", () => {
|
|
1420
1420
|
clearTimeout(timer);
|
|
1421
1421
|
reject(signal.reason ?? new Error("calendar connection aborted"));
|
|
@@ -1616,7 +1616,8 @@ var CAPABILITIES = {
|
|
|
1616
1616
|
"compose declared app-capability schema/rules, create guarded seeds when absent, and configure platform AI, auth, and deployment links",
|
|
1617
1617
|
"apply Google Calendar booking config, then drive state-bound consent, status, discovery, and disconnect flows (bookings run live through the platform proxy)",
|
|
1618
1618
|
"validate integration contracts offline and smoke-test a provisioned db environment plus anonymous capability routes",
|
|
1619
|
-
"
|
|
1619
|
+
"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",
|
|
1620
|
+
"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",
|
|
1620
1621
|
"inspect durable agent wakeups as a versioned JSON envelope and explicitly requeue one dead-lettered job with a scoped environment credential",
|
|
1621
1622
|
"run app-attributed hosted security discovery and independent validation without provider keys",
|
|
1622
1623
|
"connect/revoke source-read-only GitHub sources and drive commit-pinned hosted security jobs without PATs or provider keys",
|
|
@@ -2822,8 +2823,8 @@ function hostedPollTimeout(value = 10 * 6e4) {
|
|
|
2822
2823
|
}
|
|
2823
2824
|
async function waitForHostedPoll(milliseconds, signal) {
|
|
2824
2825
|
if (signal?.aborted) throw signal.reason ?? new DOMException("aborted", "AbortError");
|
|
2825
|
-
await new Promise((
|
|
2826
|
-
const timer = setTimeout(
|
|
2826
|
+
await new Promise((resolve12, reject) => {
|
|
2827
|
+
const timer = setTimeout(resolve12, milliseconds);
|
|
2827
2828
|
signal?.addEventListener("abort", () => {
|
|
2828
2829
|
clearTimeout(timer);
|
|
2829
2830
|
reject(signal.reason ?? new DOMException("aborted", "AbortError"));
|
|
@@ -4843,21 +4844,21 @@ async function verifyCodeCandidate(input) {
|
|
|
4843
4844
|
const recipes = [];
|
|
4844
4845
|
const logs = [];
|
|
4845
4846
|
for (const recipe2 of policy.recipes) {
|
|
4846
|
-
const
|
|
4847
|
+
const clean4 = await stageWorkspace(staged.workspaceDir, limits);
|
|
4847
4848
|
try {
|
|
4848
|
-
if (await digestStagedWorkspace(
|
|
4849
|
+
if (await digestStagedWorkspace(clean4.workspaceDir, limits) !== sourceDigest) {
|
|
4849
4850
|
throw new TypeError("clean verifier source changed before execution");
|
|
4850
4851
|
}
|
|
4851
4852
|
const result = checkedResult(await input.recipeExecutor.run({
|
|
4852
|
-
workspaceDir:
|
|
4853
|
+
workspaceDir: clean4.workspaceDir,
|
|
4853
4854
|
recipe: recipe2,
|
|
4854
4855
|
signal: input.signal
|
|
4855
4856
|
}), recipe2.maxOutputBytes);
|
|
4856
|
-
const artifacts = await inspectArtifacts(
|
|
4857
|
+
const artifacts = await inspectArtifacts(clean4.workspaceDir, recipe2);
|
|
4857
4858
|
recipes.push(recipeReceipt(recipe2, result, artifacts));
|
|
4858
4859
|
logs.push({ recipeId: recipe2.id, ...boundedLogs(result, recipe2.maxOutputBytes) });
|
|
4859
4860
|
} finally {
|
|
4860
|
-
await
|
|
4861
|
+
await clean4.cleanup();
|
|
4861
4862
|
}
|
|
4862
4863
|
}
|
|
4863
4864
|
const fields = {
|
|
@@ -5979,11 +5980,21 @@ Usage:
|
|
|
5979
5980
|
odla-ai app owners list [--config odla.config.mjs] [--email <odla-account>] [--json]
|
|
5980
5981
|
odla-ai app owners add <email> [--email <odla-account>] [--json]
|
|
5981
5982
|
odla-ai app owners remove <email> [--email <odla-account>] [--json]
|
|
5982
|
-
odla-ai pm
|
|
5983
|
-
odla-ai pm
|
|
5983
|
+
odla-ai pm goal list [--app <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
5984
|
+
odla-ai pm task list [--app <id>] [--column <c>] [--goal <id>] [--assignee <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
5985
|
+
odla-ai pm decision list [--app <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
5986
|
+
odla-ai pm bug list [--app <id>] [--status <s>] [--severity <s>] [--goal <id>] [--assignee <id>] [--decision <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
5987
|
+
odla-ai pm goal add --app <id> --title <t> [--status <s>] [--proof <text>] [--target <pct>] [--mutation-id <id>] [--json]
|
|
5988
|
+
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]
|
|
5989
|
+
odla-ai pm decision add --app <id> --title <t> --body <text> [--status <s>] [--mutation-id <id>] [--json]
|
|
5990
|
+
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]
|
|
5984
5991
|
odla-ai pm <goal|task|decision|bug> get <id> [--json]
|
|
5985
|
-
odla-ai pm
|
|
5986
|
-
odla-ai pm
|
|
5992
|
+
odla-ai pm goal set <id> [--title <t>|--status <s>|--proof <text>|--no-proof|--target <pct>|--no-target] [--mutation-id <id>] [--json]
|
|
5993
|
+
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]
|
|
5994
|
+
odla-ai pm decision set <id> [--title <t>|--status <s>|--body <text>] [--mutation-id <id>] [--json]
|
|
5995
|
+
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]
|
|
5996
|
+
odla-ai pm <goal|task|decision> done <id> [--mutation-id <id>]
|
|
5997
|
+
odla-ai pm bug done <id> [--decision <accepted-decision-id>] [--mutation-id <id>]
|
|
5987
5998
|
odla-ai pm <goal|task|decision|bug> comment <id> --body "..." [--mutation-id <id>]
|
|
5988
5999
|
odla-ai pm <goal|task|decision|bug> comments <id> [--json]
|
|
5989
6000
|
odla-ai pm <goal|task|decision|bug> rm <id>
|
|
@@ -5998,8 +6009,12 @@ Usage:
|
|
|
5998
6009
|
odla-ai discuss watch [<topic>] [--cursor <cursor>] [--by <authorId>] [--self <authorId>] [--interval <s>] [--timeout <s>] [--json|--jsonl]
|
|
5999
6010
|
odla-ai agent jobs [--env dev] [--state pending|running|succeeded|dead_letter] [--limit 50] [--email <email>] [--json]
|
|
6000
6011
|
odla-ai agent retry <job-id> [--env dev] [--email <email>] [--json]
|
|
6001
|
-
odla-ai
|
|
6002
|
-
odla-ai
|
|
6012
|
+
odla-ai context show [--context <name>] [--platform https://odla.ai] [--app <id>] [--env prod] [--json]
|
|
6013
|
+
odla-ai context list [--json]
|
|
6014
|
+
odla-ai context save <name> [--platform <url>] [--app <id>] [--env <name>] [--json]
|
|
6015
|
+
odla-ai context remove <name> --yes [--json]
|
|
6016
|
+
odla-ai o11y status [--app <id>] [--context <name>] [--platform https://odla.ai] [--env prod] [--minutes 60] [--json]
|
|
6017
|
+
odla-ai whoami [--context <name>] [--platform https://odla.ai] [--json]
|
|
6003
6018
|
odla-ai runbook ask "<question>" [--app <id>] [--all] [--json]
|
|
6004
6019
|
odla-ai runbook search "<question>" [--app <id>] [--all] [--limit <n>] [--json]
|
|
6005
6020
|
odla-ai runbook impact [--base origin/main] [--app <id>] [--all] [--limit <n>] [--json]
|
|
@@ -6064,6 +6079,11 @@ Commands:
|
|
|
6064
6079
|
whoami Report who this terminal is authenticated as, and whether it holds
|
|
6065
6080
|
platform admin. A handshake-minted device token is never admin,
|
|
6066
6081
|
however the human who approved it is configured.
|
|
6082
|
+
context Explain selected config, platform, app, environment, and
|
|
6083
|
+
developer-token provenance without printing credentials or
|
|
6084
|
+
starting a device handshake. Operator work can run outside a
|
|
6085
|
+
project checkout with explicit flags or ODLA_* environment
|
|
6086
|
+
variables.
|
|
6067
6087
|
setup Install offline odla runbooks for common coding-agent harnesses.
|
|
6068
6088
|
init Create a generic odla.config.mjs plus starter schema/rules files.
|
|
6069
6089
|
doctor Validate and summarize the project config without network calls.
|
|
@@ -6135,6 +6155,15 @@ Safety:
|
|
|
6135
6155
|
Provision caches the approved developer token and service credentials under
|
|
6136
6156
|
.odla/ with mode 0600, and init adds those paths to .gitignore. Secret push
|
|
6137
6157
|
preflights Wrangler before any shown-once issuance or destructive rotation.
|
|
6158
|
+
Projectless PM, Discussions, o11y, runbook, and identity commands use
|
|
6159
|
+
--platform/--app/--env, ODLA_PLATFORM_URL/ODLA_APP_ID/ODLA_ENV, and
|
|
6160
|
+
ODLA_DEV_TOKEN. Save non-secret scope metadata with "context save", then
|
|
6161
|
+
select it explicitly with --context or ODLA_CONTEXT. Each named context gets
|
|
6162
|
+
isolated developer and scoped-token caches. Override their locations with
|
|
6163
|
+
ODLA_DEV_TOKEN_FILE and ODLA_ADMIN_TOKEN_FILE; ODLA_CONTEXT_FILE relocates
|
|
6164
|
+
the metadata file. Flags and specific ODLA_* scope variables beat a selected
|
|
6165
|
+
context, which beats project config. There is no ambient current context.
|
|
6166
|
+
"context show" reports only provenance and cache state and never authenticates.
|
|
6138
6167
|
Provision opens the approval page in your browser automatically whenever the
|
|
6139
6168
|
machine can show one, including agent-driven runs; only CI, SSH, and
|
|
6140
6169
|
display-less hosts skip it. Use --open to force or --no-open to suppress.
|
|
@@ -6848,6 +6877,7 @@ var COMMAND_SURFACE = {
|
|
|
6848
6877
|
calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
|
|
6849
6878
|
capabilities: {},
|
|
6850
6879
|
code: { connect: {} },
|
|
6880
|
+
context: { show: {}, list: {}, save: {}, remove: {} },
|
|
6851
6881
|
// `watch`, `read`, `reply`, and `resolve` take a topic id from there on.
|
|
6852
6882
|
discuss: {
|
|
6853
6883
|
groups: {},
|
|
@@ -7254,8 +7284,8 @@ function parseArgv(argv) {
|
|
|
7254
7284
|
}
|
|
7255
7285
|
return { positionals, options };
|
|
7256
7286
|
}
|
|
7257
|
-
function assertArgs(parsed,
|
|
7258
|
-
const allowed = new Set(
|
|
7287
|
+
function assertArgs(parsed, allowedOptions2, maxPositionals) {
|
|
7288
|
+
const allowed = new Set(allowedOptions2);
|
|
7259
7289
|
for (const name of Object.keys(parsed.options)) {
|
|
7260
7290
|
if (!allowed.has(name)) throw new Error(`unknown option "--${name}"; run "odla-ai help" for supported options`);
|
|
7261
7291
|
}
|
|
@@ -8135,6 +8165,384 @@ async function codeCommand(parsed, dependencies) {
|
|
|
8135
8165
|
});
|
|
8136
8166
|
}
|
|
8137
8167
|
|
|
8168
|
+
// src/operator-credentials.ts
|
|
8169
|
+
import process9 from "process";
|
|
8170
|
+
function developerTokenStatus(context, parsed, now = Date.now()) {
|
|
8171
|
+
const cached = readJsonFile(context.cfg.local.tokenFile);
|
|
8172
|
+
const cacheStatus = !cached?.token ? "missing" : cached.platform !== context.platform.value ? "other-platform" : (cached.expiresAt ?? 0) <= now + 6e4 ? "expired" : "valid";
|
|
8173
|
+
const source = clean(
|
|
8174
|
+
stringOpt(parsed.options.token)
|
|
8175
|
+
) ? "flag" : clean(process9.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
|
|
8176
|
+
return {
|
|
8177
|
+
source,
|
|
8178
|
+
cacheFile: context.cfg.local.tokenFile,
|
|
8179
|
+
cacheStatus
|
|
8180
|
+
};
|
|
8181
|
+
}
|
|
8182
|
+
function clean(value) {
|
|
8183
|
+
const normalized = value?.trim();
|
|
8184
|
+
return normalized || void 0;
|
|
8185
|
+
}
|
|
8186
|
+
|
|
8187
|
+
// src/operator-context.ts
|
|
8188
|
+
import { existsSync as existsSync10 } from "fs";
|
|
8189
|
+
import { join as join10, resolve as resolve11 } from "path";
|
|
8190
|
+
import process11 from "process";
|
|
8191
|
+
|
|
8192
|
+
// src/operator-profiles.ts
|
|
8193
|
+
import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
|
|
8194
|
+
import { homedir as homedir2 } from "os";
|
|
8195
|
+
import { dirname as dirname7, join as join9, resolve as resolve10 } from "path";
|
|
8196
|
+
import process10 from "process";
|
|
8197
|
+
function operatorProfileFile() {
|
|
8198
|
+
return resolve10(
|
|
8199
|
+
clean2(process10.env.ODLA_CONTEXT_FILE) ?? join9(homedir2(), ".odla", "contexts.json")
|
|
8200
|
+
);
|
|
8201
|
+
}
|
|
8202
|
+
function resolveOperatorProfile(parsed) {
|
|
8203
|
+
const fromFlag = clean2(stringOpt(parsed.options.context));
|
|
8204
|
+
const fromEnvironment = clean2(process10.env.ODLA_CONTEXT);
|
|
8205
|
+
const name = fromFlag ?? fromEnvironment ?? null;
|
|
8206
|
+
const file = operatorProfileFile();
|
|
8207
|
+
if (!name) {
|
|
8208
|
+
return { name: null, source: "unresolved", file, value: null };
|
|
8209
|
+
}
|
|
8210
|
+
assertOperatorName(name, "context");
|
|
8211
|
+
const profiles = readOperatorProfiles(file);
|
|
8212
|
+
const value = Object.hasOwn(profiles, name) ? profiles[name] : void 0;
|
|
8213
|
+
if (!value) {
|
|
8214
|
+
throw new Error(
|
|
8215
|
+
`operator context "${name}" not found in ${file}; run "odla-ai context list" or save it first`
|
|
8216
|
+
);
|
|
8217
|
+
}
|
|
8218
|
+
return {
|
|
8219
|
+
name,
|
|
8220
|
+
source: fromFlag ? "flag" : "environment",
|
|
8221
|
+
file,
|
|
8222
|
+
value
|
|
8223
|
+
};
|
|
8224
|
+
}
|
|
8225
|
+
function listOperatorProfiles(file = operatorProfileFile()) {
|
|
8226
|
+
return Object.entries(readOperatorProfiles(file)).sort(([a], [b]) => a.localeCompare(b)).map(([name, profile]) => ({ name, ...profile }));
|
|
8227
|
+
}
|
|
8228
|
+
function saveOperatorProfile(name, profile, file = operatorProfileFile()) {
|
|
8229
|
+
assertOperatorName(name, "context");
|
|
8230
|
+
const normalized = validateProfile(profile, `operator context "${name}"`);
|
|
8231
|
+
const profiles = readOperatorProfiles(file);
|
|
8232
|
+
profiles[name] = normalized;
|
|
8233
|
+
writePrivateJson(file, { schemaVersion: 1, profiles });
|
|
8234
|
+
}
|
|
8235
|
+
function removeOperatorProfile(name, file = operatorProfileFile()) {
|
|
8236
|
+
assertOperatorName(name, "context");
|
|
8237
|
+
const profiles = readOperatorProfiles(file);
|
|
8238
|
+
if (!Object.hasOwn(profiles, name)) return false;
|
|
8239
|
+
delete profiles[name];
|
|
8240
|
+
writePrivateJson(file, { schemaVersion: 1, profiles });
|
|
8241
|
+
return true;
|
|
8242
|
+
}
|
|
8243
|
+
function operatorCredentialFiles(selection) {
|
|
8244
|
+
const base = selection.name ? join9(dirname7(selection.file), "profiles", selection.name) : join9(homedir2(), ".odla");
|
|
8245
|
+
return {
|
|
8246
|
+
developer: join9(base, "dev-token.json"),
|
|
8247
|
+
scoped: join9(base, "admin-token.local.json")
|
|
8248
|
+
};
|
|
8249
|
+
}
|
|
8250
|
+
function assertOperatorName(value, label) {
|
|
8251
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(value)) {
|
|
8252
|
+
throw new Error(
|
|
8253
|
+
`${label} must contain lowercase letters, numbers, and hyphens`
|
|
8254
|
+
);
|
|
8255
|
+
}
|
|
8256
|
+
}
|
|
8257
|
+
function readOperatorProfiles(file) {
|
|
8258
|
+
if (!existsSync9(file)) return emptyProfiles();
|
|
8259
|
+
let raw;
|
|
8260
|
+
try {
|
|
8261
|
+
raw = JSON.parse(readFileSync8(file, "utf8"));
|
|
8262
|
+
} catch {
|
|
8263
|
+
throw new Error(`operator context file ${file} is not valid JSON`);
|
|
8264
|
+
}
|
|
8265
|
+
if (!raw || typeof raw !== "object" || raw.schemaVersion !== 1 || !raw.profiles || typeof raw.profiles !== "object" || Array.isArray(raw.profiles)) {
|
|
8266
|
+
throw new Error(`operator context file ${file} has an invalid shape`);
|
|
8267
|
+
}
|
|
8268
|
+
const unknown = Object.keys(raw).filter(
|
|
8269
|
+
(key) => !["schemaVersion", "profiles"].includes(key)
|
|
8270
|
+
);
|
|
8271
|
+
if (unknown.length > 0) {
|
|
8272
|
+
throw new Error(
|
|
8273
|
+
`operator context file ${file} contains unsupported field(s): ${unknown.sort().join(", ")}`
|
|
8274
|
+
);
|
|
8275
|
+
}
|
|
8276
|
+
const profiles = emptyProfiles();
|
|
8277
|
+
for (const [name, value] of Object.entries(
|
|
8278
|
+
raw.profiles
|
|
8279
|
+
)) {
|
|
8280
|
+
assertOperatorName(name, "context");
|
|
8281
|
+
profiles[name] = validateProfile(value, `operator context "${name}"`);
|
|
8282
|
+
}
|
|
8283
|
+
return profiles;
|
|
8284
|
+
}
|
|
8285
|
+
function emptyProfiles() {
|
|
8286
|
+
return /* @__PURE__ */ Object.create(null);
|
|
8287
|
+
}
|
|
8288
|
+
function validateProfile(value, label) {
|
|
8289
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
8290
|
+
throw new Error(`${label} has an invalid shape`);
|
|
8291
|
+
}
|
|
8292
|
+
const unknown = Object.keys(value).filter(
|
|
8293
|
+
(key) => !["platform", "app", "environment"].includes(key)
|
|
8294
|
+
);
|
|
8295
|
+
if (unknown.length > 0) {
|
|
8296
|
+
throw new Error(
|
|
8297
|
+
`${label} contains unsupported field(s): ${unknown.sort().join(", ")}; contexts store scope metadata only, never credentials`
|
|
8298
|
+
);
|
|
8299
|
+
}
|
|
8300
|
+
const candidate = value;
|
|
8301
|
+
if (typeof candidate.platform !== "string") {
|
|
8302
|
+
throw new Error(`${label} needs an absolute platform URL`);
|
|
8303
|
+
}
|
|
8304
|
+
const platform = platformAudience(candidate.platform);
|
|
8305
|
+
const app = clean2(candidate.app);
|
|
8306
|
+
const environment2 = clean2(candidate.environment);
|
|
8307
|
+
if (app) assertOperatorName(app, "app");
|
|
8308
|
+
if (environment2) assertOperatorName(environment2, "environment");
|
|
8309
|
+
return {
|
|
8310
|
+
platform,
|
|
8311
|
+
...app ? { app } : {},
|
|
8312
|
+
...environment2 ? { environment: environment2 } : {}
|
|
8313
|
+
};
|
|
8314
|
+
}
|
|
8315
|
+
function clean2(value) {
|
|
8316
|
+
const normalized = value?.trim();
|
|
8317
|
+
return normalized || void 0;
|
|
8318
|
+
}
|
|
8319
|
+
|
|
8320
|
+
// src/operator-context.ts
|
|
8321
|
+
var DEFAULT_PLATFORM2 = "https://odla.ai";
|
|
8322
|
+
async function resolveOperatorContext(parsed, options = {}) {
|
|
8323
|
+
const profile = resolveOperatorProfile(parsed);
|
|
8324
|
+
const configArgument = stringOpt(parsed.options.config) ?? "odla.config.mjs";
|
|
8325
|
+
const configPath = resolve11(configArgument);
|
|
8326
|
+
const explicitConfig = parsed.options.config !== void 0;
|
|
8327
|
+
const hasConfig = existsSync10(configPath);
|
|
8328
|
+
if (!hasConfig && (!options.allowMissingConfig || explicitConfig)) {
|
|
8329
|
+
await loadProjectConfig(configArgument);
|
|
8330
|
+
}
|
|
8331
|
+
const loaded = hasConfig ? await loadProjectConfig(configArgument) : void 0;
|
|
8332
|
+
const platformFlag = clean3(stringOpt(parsed.options.platform));
|
|
8333
|
+
const platformEnvironment = clean3(process11.env.ODLA_PLATFORM_URL);
|
|
8334
|
+
const platformValue = platformAudience(
|
|
8335
|
+
platformFlag ?? platformEnvironment ?? profile.value?.platform ?? loaded?.platformUrl ?? DEFAULT_PLATFORM2
|
|
8336
|
+
);
|
|
8337
|
+
const platformSource = platformFlag ? "flag" : platformEnvironment ? "environment" : profile.value ? "profile" : loaded ? "config" : "default";
|
|
8338
|
+
const appFlag = clean3(stringOpt(parsed.options.app));
|
|
8339
|
+
const appEnvironment = clean3(process11.env.ODLA_APP_ID);
|
|
8340
|
+
const appValue = appFlag ?? appEnvironment ?? profile.value?.app ?? loaded?.app.id ?? null;
|
|
8341
|
+
const appSource = appFlag ? "flag" : appEnvironment ? "environment" : profile.value?.app ? "profile" : loaded ? "config" : "unresolved";
|
|
8342
|
+
if (appValue) assertOperatorName(appValue, "app");
|
|
8343
|
+
if (options.requireApp && !appValue) {
|
|
8344
|
+
throw new Error(
|
|
8345
|
+
"app context is unresolved; pass --app <id>, set ODLA_APP_ID, select --context <name>, or run inside a project with odla.config.mjs"
|
|
8346
|
+
);
|
|
8347
|
+
}
|
|
8348
|
+
const envFlag = clean3(stringOpt(parsed.options.env));
|
|
8349
|
+
const envEnvironment = clean3(process11.env.ODLA_ENV);
|
|
8350
|
+
const environmentValue = envFlag ?? envEnvironment ?? profile.value?.environment ?? options.defaultEnvironment ?? null;
|
|
8351
|
+
const environmentSource = envFlag ? "flag" : envEnvironment ? "environment" : profile.value?.environment ? "profile" : options.defaultEnvironment ? "default" : "unresolved";
|
|
8352
|
+
if (environmentValue) {
|
|
8353
|
+
assertOperatorName(environmentValue, "environment");
|
|
8354
|
+
}
|
|
8355
|
+
const rootDir = loaded?.rootDir ?? process11.cwd();
|
|
8356
|
+
const profileCredentials = operatorCredentialFiles(profile);
|
|
8357
|
+
const tokenFile = clean3(process11.env.ODLA_DEV_TOKEN_FILE) ? resolve11(process11.env.ODLA_DEV_TOKEN_FILE) : profile.name ? profileCredentials.developer : loaded?.local.tokenFile ?? profileCredentials.developer;
|
|
8358
|
+
const scopedTokenFile = clean3(process11.env.ODLA_ADMIN_TOKEN_FILE) ? resolve11(process11.env.ODLA_ADMIN_TOKEN_FILE) : profile.name ? profileCredentials.scoped : loaded ? join10(loaded.rootDir, ".odla", "admin-token.local.json") : profileCredentials.scoped;
|
|
8359
|
+
const cfg = loaded ? {
|
|
8360
|
+
...loaded,
|
|
8361
|
+
platformUrl: platformValue,
|
|
8362
|
+
app: appValue ? {
|
|
8363
|
+
id: appValue,
|
|
8364
|
+
name: appValue === loaded.app.id ? loaded.app.name : appValue
|
|
8365
|
+
} : loaded.app,
|
|
8366
|
+
local: { ...loaded.local, tokenFile }
|
|
8367
|
+
} : {
|
|
8368
|
+
configPath,
|
|
8369
|
+
rootDir,
|
|
8370
|
+
platformUrl: platformValue,
|
|
8371
|
+
dbEndpoint: platformValue,
|
|
8372
|
+
app: {
|
|
8373
|
+
id: appValue ?? "operator",
|
|
8374
|
+
name: appValue ?? "Operator"
|
|
8375
|
+
},
|
|
8376
|
+
envs: environmentValue ? [environmentValue] : [],
|
|
8377
|
+
services: [],
|
|
8378
|
+
local: {
|
|
8379
|
+
tokenFile,
|
|
8380
|
+
credentialsFile: join10(rootDir, ".odla", "credentials.local.json"),
|
|
8381
|
+
devVarsFile: join10(rootDir, ".dev.vars"),
|
|
8382
|
+
gitignore: true
|
|
8383
|
+
}
|
|
8384
|
+
};
|
|
8385
|
+
return {
|
|
8386
|
+
cfg,
|
|
8387
|
+
profile: {
|
|
8388
|
+
name: profile.name,
|
|
8389
|
+
source: profile.source,
|
|
8390
|
+
file: profile.file
|
|
8391
|
+
},
|
|
8392
|
+
config: {
|
|
8393
|
+
path: configPath,
|
|
8394
|
+
status: loaded ? "loaded" : "absent",
|
|
8395
|
+
explicit: explicitConfig
|
|
8396
|
+
},
|
|
8397
|
+
platform: { value: platformValue, source: platformSource },
|
|
8398
|
+
app: { value: appValue, source: appSource },
|
|
8399
|
+
environment: {
|
|
8400
|
+
value: environmentValue,
|
|
8401
|
+
source: environmentSource
|
|
8402
|
+
},
|
|
8403
|
+
credentials: {
|
|
8404
|
+
developerTokenFile: tokenFile,
|
|
8405
|
+
scopedTokenFile
|
|
8406
|
+
}
|
|
8407
|
+
};
|
|
8408
|
+
}
|
|
8409
|
+
function clean3(value) {
|
|
8410
|
+
const normalized = value?.trim();
|
|
8411
|
+
return normalized || void 0;
|
|
8412
|
+
}
|
|
8413
|
+
|
|
8414
|
+
// src/context-command.ts
|
|
8415
|
+
async function contextCommand(parsed, deps = {}) {
|
|
8416
|
+
assertArgs(
|
|
8417
|
+
parsed,
|
|
8418
|
+
[
|
|
8419
|
+
"config",
|
|
8420
|
+
"context",
|
|
8421
|
+
"platform",
|
|
8422
|
+
"app",
|
|
8423
|
+
"env",
|
|
8424
|
+
"token",
|
|
8425
|
+
"json",
|
|
8426
|
+
"yes"
|
|
8427
|
+
],
|
|
8428
|
+
3
|
|
8429
|
+
);
|
|
8430
|
+
const action = parsed.positionals[1];
|
|
8431
|
+
const out = deps.stdout ?? console;
|
|
8432
|
+
if (action === "list") {
|
|
8433
|
+
const profiles = listOperatorProfiles();
|
|
8434
|
+
if (parsed.options.json === true) {
|
|
8435
|
+
out.log(JSON.stringify({ schemaVersion: 1, profiles }, null, 2));
|
|
8436
|
+
return;
|
|
8437
|
+
}
|
|
8438
|
+
if (profiles.length === 0) {
|
|
8439
|
+
out.log("No named operator contexts.");
|
|
8440
|
+
return;
|
|
8441
|
+
}
|
|
8442
|
+
out.log("name platform app environment");
|
|
8443
|
+
for (const profile of profiles) {
|
|
8444
|
+
out.log(
|
|
8445
|
+
`${profile.name} ${profile.platform} ${profile.app ?? ""} ${profile.environment ?? ""}`
|
|
8446
|
+
);
|
|
8447
|
+
}
|
|
8448
|
+
return;
|
|
8449
|
+
}
|
|
8450
|
+
if (action === "save") {
|
|
8451
|
+
if (parsed.options.token !== void 0) {
|
|
8452
|
+
throw new Error(
|
|
8453
|
+
"context save does not accept --token; contexts store scope metadata only"
|
|
8454
|
+
);
|
|
8455
|
+
}
|
|
8456
|
+
const name = requireName(parsed);
|
|
8457
|
+
const context2 = await resolveOperatorContext(parsed, {
|
|
8458
|
+
allowMissingConfig: true
|
|
8459
|
+
});
|
|
8460
|
+
const profile = {
|
|
8461
|
+
platform: context2.platform.value,
|
|
8462
|
+
...context2.app.value ? { app: context2.app.value } : {},
|
|
8463
|
+
...context2.environment.value ? { environment: context2.environment.value } : {}
|
|
8464
|
+
};
|
|
8465
|
+
saveOperatorProfile(name, profile, context2.profile.file);
|
|
8466
|
+
if (parsed.options.json === true) {
|
|
8467
|
+
out.log(JSON.stringify({ schemaVersion: 1, name, ...profile }, null, 2));
|
|
8468
|
+
} else {
|
|
8469
|
+
out.log(`saved operator context "${name}" (${profile.platform})`);
|
|
8470
|
+
}
|
|
8471
|
+
return;
|
|
8472
|
+
}
|
|
8473
|
+
if (action === "remove") {
|
|
8474
|
+
const name = requireName(parsed);
|
|
8475
|
+
if (parsed.options.yes !== true) {
|
|
8476
|
+
throw new Error(`context remove "${name}" requires --yes`);
|
|
8477
|
+
}
|
|
8478
|
+
const removed = removeOperatorProfile(name);
|
|
8479
|
+
const result2 = {
|
|
8480
|
+
schemaVersion: 1,
|
|
8481
|
+
name,
|
|
8482
|
+
removed,
|
|
8483
|
+
credentialCachesRemoved: false,
|
|
8484
|
+
grantsRevoked: false
|
|
8485
|
+
};
|
|
8486
|
+
if (parsed.options.json === true) out.log(JSON.stringify(result2, null, 2));
|
|
8487
|
+
else {
|
|
8488
|
+
const status = removed ? `removed operator context "${name}"` : `operator context "${name}" was already absent`;
|
|
8489
|
+
out.log(`${status}; credential caches were not deleted or revoked`);
|
|
8490
|
+
}
|
|
8491
|
+
return;
|
|
8492
|
+
}
|
|
8493
|
+
if (action !== "show") {
|
|
8494
|
+
throw new Error(
|
|
8495
|
+
`unknown context action "${action ?? ""}". Try show|list|save|remove.`
|
|
8496
|
+
);
|
|
8497
|
+
}
|
|
8498
|
+
const context = await resolveOperatorContext(parsed, {
|
|
8499
|
+
allowMissingConfig: true,
|
|
8500
|
+
defaultEnvironment: "prod"
|
|
8501
|
+
});
|
|
8502
|
+
const token = developerTokenStatus(context, parsed);
|
|
8503
|
+
const result = {
|
|
8504
|
+
schemaVersion: 1,
|
|
8505
|
+
profile: context.profile,
|
|
8506
|
+
config: context.config,
|
|
8507
|
+
platform: context.platform,
|
|
8508
|
+
app: context.app,
|
|
8509
|
+
environment: context.environment,
|
|
8510
|
+
authentication: {
|
|
8511
|
+
developerToken: token,
|
|
8512
|
+
scopedTokenCacheFile: context.credentials.scopedTokenFile,
|
|
8513
|
+
handshakeStarted: false
|
|
8514
|
+
}
|
|
8515
|
+
};
|
|
8516
|
+
if (parsed.options.json === true) {
|
|
8517
|
+
out.log(JSON.stringify(result, null, 2));
|
|
8518
|
+
return;
|
|
8519
|
+
}
|
|
8520
|
+
out.log(`config: ${context.config.status} (${context.config.path})`);
|
|
8521
|
+
out.log(
|
|
8522
|
+
`context: ${context.profile.name ?? "(none)"} (${context.profile.source}; ${context.profile.file})`
|
|
8523
|
+
);
|
|
8524
|
+
out.log(
|
|
8525
|
+
`platform: ${context.platform.value} (${context.platform.source})`
|
|
8526
|
+
);
|
|
8527
|
+
out.log(
|
|
8528
|
+
`app: ${context.app.value ?? "(unresolved)"} (${context.app.source})`
|
|
8529
|
+
);
|
|
8530
|
+
out.log(
|
|
8531
|
+
`env: ${context.environment.value ?? "(unresolved)"} (${context.environment.source})`
|
|
8532
|
+
);
|
|
8533
|
+
out.log(
|
|
8534
|
+
`auth: ${token.source} (cache ${token.cacheStatus}: ${token.cacheFile})`
|
|
8535
|
+
);
|
|
8536
|
+
out.log("handshake: not started");
|
|
8537
|
+
}
|
|
8538
|
+
function requireName(parsed) {
|
|
8539
|
+
const name = parsed.positionals[2]?.trim();
|
|
8540
|
+
if (!name) {
|
|
8541
|
+
throw new Error(`context ${parsed.positionals[1]} needs a profile name`);
|
|
8542
|
+
}
|
|
8543
|
+
return name;
|
|
8544
|
+
}
|
|
8545
|
+
|
|
8138
8546
|
// src/discuss-actions.ts
|
|
8139
8547
|
var writeMutationId = (parsed) => stringOpt(parsed.options["mutation-id"]) ?? crypto.randomUUID();
|
|
8140
8548
|
async function request(ctx, method, path, body) {
|
|
@@ -8186,8 +8594,9 @@ async function discussGroups(ctx) {
|
|
|
8186
8594
|
}
|
|
8187
8595
|
async function discussList(ctx, parsed) {
|
|
8188
8596
|
const query = new URLSearchParams();
|
|
8597
|
+
const app = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
8598
|
+
if (app) query.set("app", app);
|
|
8189
8599
|
for (const [flag, param] of [
|
|
8190
|
-
["app", "app"],
|
|
8191
8600
|
["q", "q"],
|
|
8192
8601
|
["state", "state"],
|
|
8193
8602
|
["limit", "limit"],
|
|
@@ -8263,7 +8672,7 @@ function renderRead(ctx, topic, posts) {
|
|
|
8263
8672
|
}
|
|
8264
8673
|
}
|
|
8265
8674
|
async function discussPost(ctx, parsed) {
|
|
8266
|
-
const appId = stringOpt(parsed.options.app);
|
|
8675
|
+
const appId = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
8267
8676
|
if (!appId) throw new Error("discuss post needs --app <appId>");
|
|
8268
8677
|
const subject = stringOpt(parsed.options.subject);
|
|
8269
8678
|
if (!subject) throw new Error('discuss post needs --subject "\u2026"');
|
|
@@ -8295,7 +8704,7 @@ async function discussResolve(ctx, id, resolved, parsed) {
|
|
|
8295
8704
|
}
|
|
8296
8705
|
async function discussWho(ctx, parsed) {
|
|
8297
8706
|
const query = new URLSearchParams({ q: stringOpt(parsed.options.q) ?? "" });
|
|
8298
|
-
const app = stringOpt(parsed.options.app);
|
|
8707
|
+
const app = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
8299
8708
|
if (app) query.set("app", app);
|
|
8300
8709
|
const kinds = stringOpt(parsed.options.kinds);
|
|
8301
8710
|
if (kinds) query.set("kinds", kinds);
|
|
@@ -8399,14 +8808,14 @@ function jsonl(ctx, parsed, value) {
|
|
|
8399
8808
|
}
|
|
8400
8809
|
async function discussWatch(ctx, topicId, parsed) {
|
|
8401
8810
|
if (ctx.json && parsed.options.jsonl === true) throw new Error("--json and --jsonl cannot be combined");
|
|
8402
|
-
const sleep = ctx.sleep ?? ((ms) => new Promise((
|
|
8811
|
+
const sleep = ctx.sleep ?? ((ms) => new Promise((resolve12) => setTimeout(resolve12, ms)));
|
|
8403
8812
|
const now = ctx.now ?? Date.now;
|
|
8404
8813
|
const intervalMs = (numberOpt2(parsed, "interval", DEFAULT_INTERVAL_MS / 1e3) ?? DEFAULT_INTERVAL_MS / 1e3) * 1e3;
|
|
8405
8814
|
const timeoutSeconds = numberOpt2(parsed, "timeout");
|
|
8406
8815
|
const deadline = timeoutSeconds === void 0 ? void 0 : now() + timeoutSeconds * 1e3;
|
|
8407
8816
|
const by = stringOpt(parsed.options.by);
|
|
8408
8817
|
const self = stringOpt(parsed.options.self);
|
|
8409
|
-
const app = stringOpt(parsed.options.app);
|
|
8818
|
+
const app = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
8410
8819
|
let cursor = stringOpt(parsed.options.cursor);
|
|
8411
8820
|
let firstSuccess = true;
|
|
8412
8821
|
let consecutiveFailures = 0;
|
|
@@ -8551,14 +8960,19 @@ var ALLOWED = [
|
|
|
8551
8960
|
"timeout",
|
|
8552
8961
|
"cursor",
|
|
8553
8962
|
"jsonl",
|
|
8554
|
-
"mutation-id"
|
|
8963
|
+
"mutation-id",
|
|
8964
|
+
"platform",
|
|
8965
|
+
"context"
|
|
8555
8966
|
];
|
|
8556
8967
|
function requireId(id, action) {
|
|
8557
8968
|
if (!id) throw new Error(`"discuss ${action}" needs a topic id`);
|
|
8558
8969
|
return id;
|
|
8559
8970
|
}
|
|
8560
8971
|
async function buildContext(parsed, deps) {
|
|
8561
|
-
const
|
|
8972
|
+
const context = await resolveOperatorContext(parsed, {
|
|
8973
|
+
allowMissingConfig: true
|
|
8974
|
+
});
|
|
8975
|
+
const { cfg } = context;
|
|
8562
8976
|
const doFetch = deps.fetch ?? fetch;
|
|
8563
8977
|
const out = deps.stdout ?? console;
|
|
8564
8978
|
const token = await getDeveloperToken(
|
|
@@ -8572,7 +8986,16 @@ async function buildContext(parsed, deps) {
|
|
|
8572
8986
|
doFetch,
|
|
8573
8987
|
out
|
|
8574
8988
|
);
|
|
8575
|
-
return {
|
|
8989
|
+
return {
|
|
8990
|
+
platformUrl: cfg.platformUrl,
|
|
8991
|
+
token,
|
|
8992
|
+
doFetch,
|
|
8993
|
+
out,
|
|
8994
|
+
json: parsed.options.json === true,
|
|
8995
|
+
// Preserve Discussions' existing cross-project default for config-backed
|
|
8996
|
+
// use while allowing remote agents to scope via ODLA_APP_ID or a profile.
|
|
8997
|
+
appId: context.app.source === "environment" || context.app.source === "profile" ? context.app.value ?? void 0 : void 0
|
|
8998
|
+
};
|
|
8576
8999
|
}
|
|
8577
9000
|
async function discussCommand(parsed, deps = {}) {
|
|
8578
9001
|
assertArgs(parsed, ALLOWED, 3);
|
|
@@ -8684,7 +9107,7 @@ async function pmList(ctx, entity, parsed) {
|
|
|
8684
9107
|
assignee: "assigneeId",
|
|
8685
9108
|
decision: "decisionId"
|
|
8686
9109
|
};
|
|
8687
|
-
const app = stringOpt(parsed.options.app);
|
|
9110
|
+
const app = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
8688
9111
|
if (app) q.set("app", app);
|
|
8689
9112
|
for (const [flag, param] of Object.entries(filters)) {
|
|
8690
9113
|
const v = stringOpt(parsed.options[flag]);
|
|
@@ -8703,7 +9126,7 @@ async function pmList(ctx, entity, parsed) {
|
|
|
8703
9126
|
});
|
|
8704
9127
|
}
|
|
8705
9128
|
async function pmAdd(ctx, entity, parsed) {
|
|
8706
|
-
const appId = stringOpt(parsed.options.app);
|
|
9129
|
+
const appId = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
8707
9130
|
if (!appId) throw new Error("pm add needs --app <appId>");
|
|
8708
9131
|
const input = collectEntityFields(entity, parsed, false);
|
|
8709
9132
|
if (!input.title) throw new Error("pm add needs --title <title>");
|
|
@@ -8754,7 +9177,7 @@ async function allRecords(ctx, entity, appId) {
|
|
|
8754
9177
|
}
|
|
8755
9178
|
}
|
|
8756
9179
|
async function pmHandoff(ctx, parsed) {
|
|
8757
|
-
const appId = stringOpt(parsed.options.app);
|
|
9180
|
+
const appId = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
8758
9181
|
if (!appId) throw new Error("pm handoff needs --app <appId>");
|
|
8759
9182
|
const [goals, tasks, bugs] = await Promise.all([
|
|
8760
9183
|
allRecords(ctx, "goal", appId),
|
|
@@ -8826,37 +9249,62 @@ var ALIASES = {
|
|
|
8826
9249
|
decision: "decision",
|
|
8827
9250
|
bug: "bug"
|
|
8828
9251
|
};
|
|
8829
|
-
var
|
|
8830
|
-
|
|
8831
|
-
"
|
|
8832
|
-
"
|
|
8833
|
-
|
|
8834
|
-
"
|
|
8835
|
-
"
|
|
8836
|
-
"
|
|
8837
|
-
|
|
8838
|
-
|
|
8839
|
-
|
|
8840
|
-
|
|
8841
|
-
|
|
8842
|
-
|
|
8843
|
-
|
|
8844
|
-
|
|
8845
|
-
|
|
8846
|
-
|
|
8847
|
-
|
|
8848
|
-
|
|
8849
|
-
|
|
8850
|
-
|
|
8851
|
-
|
|
8852
|
-
|
|
8853
|
-
|
|
9252
|
+
var COMMON_OPTIONS = ["config", "token", "email", "json", "platform", "context"];
|
|
9253
|
+
var ACTION_OPTIONS = {
|
|
9254
|
+
list: ["app", "q", "limit", "offset"],
|
|
9255
|
+
add: ["app", "title", "mutation-id"],
|
|
9256
|
+
get: [],
|
|
9257
|
+
set: ["mutation-id"],
|
|
9258
|
+
done: ["mutation-id"],
|
|
9259
|
+
comment: ["body", "mutation-id"],
|
|
9260
|
+
comments: [],
|
|
9261
|
+
rm: []
|
|
9262
|
+
};
|
|
9263
|
+
var ENTITY_OPTIONS = {
|
|
9264
|
+
goal: {
|
|
9265
|
+
list: ["status"],
|
|
9266
|
+
add: ["status", "proof", "target"],
|
|
9267
|
+
set: ["title", "status", "proof", "target"],
|
|
9268
|
+
done: []
|
|
9269
|
+
},
|
|
9270
|
+
task: {
|
|
9271
|
+
list: ["column", "goal", "assignee"],
|
|
9272
|
+
add: ["column", "goal", "assignee", "due", "description", "desc", "body"],
|
|
9273
|
+
set: ["title", "column", "rank", "goal", "assignee", "due", "description", "desc", "body"],
|
|
9274
|
+
done: []
|
|
9275
|
+
},
|
|
9276
|
+
decision: {
|
|
9277
|
+
list: ["status"],
|
|
9278
|
+
add: ["status", "body"],
|
|
9279
|
+
set: ["title", "status", "body"],
|
|
9280
|
+
done: []
|
|
9281
|
+
},
|
|
9282
|
+
bug: {
|
|
9283
|
+
list: ["status", "severity", "goal", "assignee", "decision"],
|
|
9284
|
+
add: ["status", "severity", "goal", "assignee", "decision", "description", "desc", "body"],
|
|
9285
|
+
set: ["title", "status", "severity", "goal", "assignee", "decision", "description", "desc", "body"],
|
|
9286
|
+
done: ["decision"]
|
|
9287
|
+
}
|
|
9288
|
+
};
|
|
9289
|
+
function canonicalAction(action) {
|
|
9290
|
+
if (action === "create") return "add";
|
|
9291
|
+
if (action === "update" || action === "status" || action === "move") return "set";
|
|
9292
|
+
if (action === "delete") return "rm";
|
|
9293
|
+
return action in ACTION_OPTIONS ? action : null;
|
|
9294
|
+
}
|
|
9295
|
+
function allowedOptions(entity, action) {
|
|
9296
|
+
const entityOptions = action === "list" || action === "add" || action === "set" || action === "done" ? ENTITY_OPTIONS[entity][action] : [];
|
|
9297
|
+
return [...COMMON_OPTIONS, ...ACTION_OPTIONS[action], ...entityOptions];
|
|
9298
|
+
}
|
|
8854
9299
|
function requireId2(id, action) {
|
|
8855
9300
|
if (!id) throw new Error(`"pm ... ${action}" needs an item id`);
|
|
8856
9301
|
return id;
|
|
8857
9302
|
}
|
|
8858
9303
|
async function buildContext2(parsed, deps) {
|
|
8859
|
-
const
|
|
9304
|
+
const context = await resolveOperatorContext(parsed, {
|
|
9305
|
+
allowMissingConfig: true
|
|
9306
|
+
});
|
|
9307
|
+
const { cfg } = context;
|
|
8860
9308
|
const doFetch = deps.fetch ?? fetch;
|
|
8861
9309
|
const out = deps.stdout ?? console;
|
|
8862
9310
|
const token = await getDeveloperToken(
|
|
@@ -8865,32 +9313,39 @@ async function buildContext2(parsed, deps) {
|
|
|
8865
9313
|
doFetch,
|
|
8866
9314
|
out
|
|
8867
9315
|
);
|
|
8868
|
-
return {
|
|
9316
|
+
return {
|
|
9317
|
+
platformUrl: cfg.platformUrl,
|
|
9318
|
+
token,
|
|
9319
|
+
doFetch,
|
|
9320
|
+
out,
|
|
9321
|
+
json: parsed.options.json === true,
|
|
9322
|
+
// Preserve PM's existing cross-project default when a config is present;
|
|
9323
|
+
// only an explicit remote-agent environment or profile selects an app.
|
|
9324
|
+
appId: context.app.source === "environment" || context.app.source === "profile" ? context.app.value ?? void 0 : void 0
|
|
9325
|
+
};
|
|
8869
9326
|
}
|
|
8870
9327
|
async function pmCommand(parsed, deps = {}) {
|
|
8871
9328
|
const word = parsed.positionals[1] ?? "";
|
|
8872
9329
|
if (word === "handoff") {
|
|
8873
|
-
assertArgs(parsed,
|
|
9330
|
+
assertArgs(parsed, [...COMMON_OPTIONS, "app"], 2);
|
|
8874
9331
|
return pmHandoff(await buildContext2(parsed, deps), parsed);
|
|
8875
9332
|
}
|
|
8876
9333
|
const entity = ALIASES[word];
|
|
8877
9334
|
if (!entity) throw new Error(`unknown pm entity "${word}". Try "odla-ai pm bug list" (goal|task|decision|bug).`);
|
|
8878
|
-
const
|
|
8879
|
-
|
|
9335
|
+
const requestedAction = parsed.positionals[2] ?? "list";
|
|
9336
|
+
const action = canonicalAction(requestedAction);
|
|
9337
|
+
if (!action) throw new Error(`unknown pm action "${requestedAction}". Try list|add|get|set|done|comment|comments|rm.`);
|
|
9338
|
+
assertArgs(parsed, allowedOptions(entity, action), 4);
|
|
8880
9339
|
const ctx = await buildContext2(parsed, deps);
|
|
8881
9340
|
const id = parsed.positionals[3];
|
|
8882
9341
|
switch (action) {
|
|
8883
9342
|
case "list":
|
|
8884
9343
|
return pmList(ctx, entity, parsed);
|
|
8885
9344
|
case "add":
|
|
8886
|
-
case "create":
|
|
8887
9345
|
return pmAdd(ctx, entity, parsed);
|
|
8888
9346
|
case "get":
|
|
8889
9347
|
return pmGet(ctx, entity, requireId2(id, action));
|
|
8890
9348
|
case "set":
|
|
8891
|
-
case "update":
|
|
8892
|
-
case "status":
|
|
8893
|
-
case "move":
|
|
8894
9349
|
return pmSet(ctx, entity, requireId2(id, action), parsed);
|
|
8895
9350
|
case "done":
|
|
8896
9351
|
return pmDone(ctx, entity, requireId2(id, action), parsed);
|
|
@@ -8899,10 +9354,7 @@ async function pmCommand(parsed, deps = {}) {
|
|
|
8899
9354
|
case "comments":
|
|
8900
9355
|
return pmComments(ctx, entity, requireId2(id, action));
|
|
8901
9356
|
case "rm":
|
|
8902
|
-
case "delete":
|
|
8903
9357
|
return pmRemove(ctx, entity, requireId2(id, action));
|
|
8904
|
-
default:
|
|
8905
|
-
throw new Error(`unknown pm action "${action}". Try list|add|get|set|done|comment|comments|rm.`);
|
|
8906
9358
|
}
|
|
8907
9359
|
}
|
|
8908
9360
|
|
|
@@ -8914,6 +9366,20 @@ function statusVerdict(reads) {
|
|
|
8914
9366
|
sourceHttp(reasons, "canary", reads.canary);
|
|
8915
9367
|
sourceHttp(reasons, "collector", reads.collector);
|
|
8916
9368
|
sourceHttp(reasons, "provider", reads.provider);
|
|
9369
|
+
if (reads.providerCapacity) {
|
|
9370
|
+
sourceHttp(
|
|
9371
|
+
reasons,
|
|
9372
|
+
"provider",
|
|
9373
|
+
reads.providerCapacity,
|
|
9374
|
+
"provider_capacity_"
|
|
9375
|
+
);
|
|
9376
|
+
}
|
|
9377
|
+
sourceHttp(
|
|
9378
|
+
reasons,
|
|
9379
|
+
"provider",
|
|
9380
|
+
reads.providerReconciliation,
|
|
9381
|
+
"provider_reconciliation_"
|
|
9382
|
+
);
|
|
8917
9383
|
sourceHttp(reasons, "provider", reads.providerHistory, "provider_history_");
|
|
8918
9384
|
const liveStatus = String(reads.liveSync.body.status ?? "");
|
|
8919
9385
|
if (liveStatus === "partial") {
|
|
@@ -8986,6 +9452,17 @@ function statusVerdict(reads) {
|
|
|
8986
9452
|
severity: "degraded"
|
|
8987
9453
|
});
|
|
8988
9454
|
}
|
|
9455
|
+
const reconciliationStatus = String(
|
|
9456
|
+
reads.providerReconciliation.body.status ?? ""
|
|
9457
|
+
);
|
|
9458
|
+
if (reads.providerReconciliation.httpStatus >= 200 && reads.providerReconciliation.httpStatus < 300 && reconciliationStatus && reconciliationStatus !== "within_budget") {
|
|
9459
|
+
const reconciliationReason = reads.providerReconciliation.body.reason;
|
|
9460
|
+
reasons.push({
|
|
9461
|
+
source: "provider",
|
|
9462
|
+
code: reconciliationStatus === "outside_budget" ? typeof reconciliationReason === "string" && reconciliationReason ? reconciliationReason : "request_coverage_outside_budget" : `request_reconciliation_${reconciliationStatus}`,
|
|
9463
|
+
severity: "degraded"
|
|
9464
|
+
});
|
|
9465
|
+
}
|
|
8989
9466
|
const providerHistoryStatus = String(reads.providerHistory.body.status ?? "");
|
|
8990
9467
|
if (providerHistoryStatus && providerHistoryStatus !== "observed" && providerHistoryStatus !== "no_data") {
|
|
8991
9468
|
reasons.push({
|
|
@@ -9056,8 +9533,19 @@ function printO11yStatus(status, out) {
|
|
|
9056
9533
|
`collector ${status.collector.httpStatus} ${String(status.collector.body.status ?? status.collector.body.error ?? "unavailable")} ${numeric3(collectorStorage.affectedPoints)} affected points`
|
|
9057
9534
|
);
|
|
9058
9535
|
const providerMetrics = record6(status.provider.body.metrics) ? status.provider.body.metrics : {};
|
|
9536
|
+
const providerCapacity = record6(status.provider.body.capacity) ? status.provider.body.capacity : {};
|
|
9537
|
+
const workerMemory = record6(providerCapacity.memory) ? providerCapacity.memory : {};
|
|
9059
9538
|
out.log(
|
|
9060
|
-
`cloudflare ${status.provider.httpStatus} ${String(status.provider.body.status ?? status.provider.body.error ?? "unavailable")} ${numeric3(providerMetrics.requests)} invocations ${numeric3(providerMetrics.errors)} runtime errors`
|
|
9539
|
+
`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`
|
|
9540
|
+
);
|
|
9541
|
+
for (const line of providerCapacityLines(status.providerCapacity)) {
|
|
9542
|
+
out.log(line);
|
|
9543
|
+
}
|
|
9544
|
+
const coverage = record6(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
|
|
9545
|
+
const coverageCounts = record6(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
|
|
9546
|
+
const coverageBudget = record6(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
|
|
9547
|
+
out.log(
|
|
9548
|
+
`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`
|
|
9061
9549
|
);
|
|
9062
9550
|
const providerPoints = Array.isArray(status.providerHistory.body.points) ? status.providerHistory.body.points.length : 0;
|
|
9063
9551
|
const providerFreshness = record6(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
|
|
@@ -9068,6 +9556,27 @@ function printO11yStatus(status, out) {
|
|
|
9068
9556
|
`verdict ${status.verdict.status} ${status.verdict.reasons.map((reason) => `${reason.source}:${reason.code}`).join(", ") || "all required signals healthy"}`
|
|
9069
9557
|
);
|
|
9070
9558
|
}
|
|
9559
|
+
function providerCapacityLines(read3) {
|
|
9560
|
+
const resources = record6(read3.body.resources) ? read3.body.resources : {};
|
|
9561
|
+
const durableObjects = record6(resources.durableObjects) ? resources.durableObjects : {};
|
|
9562
|
+
const periodic = record6(durableObjects.periodic) ? durableObjects.periodic : {};
|
|
9563
|
+
const storage = record6(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
|
|
9564
|
+
const d1 = record6(resources.d1) ? resources.d1 : {};
|
|
9565
|
+
const d1Activity = record6(d1.activity) ? d1.activity : {};
|
|
9566
|
+
const d1Storage = record6(d1.storage) ? d1.storage : {};
|
|
9567
|
+
const d1Latency = record6(d1Activity.latency) ? d1Activity.latency : {};
|
|
9568
|
+
const r2 = record6(resources.r2) ? resources.r2 : {};
|
|
9569
|
+
const r2Operations = record6(r2.operations) ? r2.operations : {};
|
|
9570
|
+
const r2Storage = record6(r2.storage) ? r2.storage : {};
|
|
9571
|
+
const status = String(
|
|
9572
|
+
read3.body.status ?? read3.body.error ?? "unavailable"
|
|
9573
|
+
);
|
|
9574
|
+
return [
|
|
9575
|
+
`cloudflare-capacity ${read3.httpStatus} ${status} ${optionalBytes(storage.storedBytes)} SQLite DO storage ${optionalCount(periodic.activeWebsocketConnectionsMax, "active socket max")} ${optionalAgeSeconds(storage.ageSeconds)} storage evidence age`,
|
|
9576
|
+
`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`,
|
|
9577
|
+
`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`
|
|
9578
|
+
];
|
|
9579
|
+
}
|
|
9071
9580
|
function liveSyncLine(read3) {
|
|
9072
9581
|
const performance = record6(read3.body.performance) ? read3.body.performance : {};
|
|
9073
9582
|
const commitToSend = record6(performance.commitToSend) ? performance.commitToSend : {};
|
|
@@ -9086,12 +9595,42 @@ function optionalAge(value) {
|
|
|
9086
9595
|
if (typeof value !== "number" || !Number.isFinite(value)) return "unknown";
|
|
9087
9596
|
return `${Math.max(0, Math.round(value / 1e3))}s`;
|
|
9088
9597
|
}
|
|
9598
|
+
function optionalPercent(value) {
|
|
9599
|
+
return typeof value === "number" && Number.isFinite(value) ? `${Math.round(value * 100)}%` : "\u2014";
|
|
9600
|
+
}
|
|
9601
|
+
function optionalBytes(value) {
|
|
9602
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return "\u2014";
|
|
9603
|
+
if (value >= 1024 * 1024 * 1024) {
|
|
9604
|
+
return `${(value / (1024 * 1024 * 1024)).toFixed(2)} GiB`;
|
|
9605
|
+
}
|
|
9606
|
+
if (value >= 1024 * 1024) {
|
|
9607
|
+
return `${(value / (1024 * 1024)).toFixed(1)} MiB`;
|
|
9608
|
+
}
|
|
9609
|
+
if (value >= 1024) return `${(value / 1024).toFixed(1)} KiB`;
|
|
9610
|
+
return `${Math.round(value)} B`;
|
|
9611
|
+
}
|
|
9612
|
+
function optionalCount(value, unit) {
|
|
9613
|
+
return typeof value === "number" && Number.isFinite(value) ? `${value} ${unit}` : `\u2014 ${unit}`;
|
|
9614
|
+
}
|
|
9615
|
+
function optionalAgeSeconds(value) {
|
|
9616
|
+
return typeof value === "number" && Number.isFinite(value) ? `${Math.max(0, Math.round(value))}s` : "unknown";
|
|
9617
|
+
}
|
|
9089
9618
|
|
|
9090
9619
|
// src/o11y-command.ts
|
|
9091
9620
|
async function o11yCommand(parsed, deps = {}) {
|
|
9092
9621
|
assertArgs(
|
|
9093
9622
|
parsed,
|
|
9094
|
-
[
|
|
9623
|
+
[
|
|
9624
|
+
"config",
|
|
9625
|
+
"context",
|
|
9626
|
+
"platform",
|
|
9627
|
+
"token",
|
|
9628
|
+
"email",
|
|
9629
|
+
"json",
|
|
9630
|
+
"app",
|
|
9631
|
+
"env",
|
|
9632
|
+
"minutes"
|
|
9633
|
+
],
|
|
9095
9634
|
2
|
|
9096
9635
|
);
|
|
9097
9636
|
const action = parsed.positionals[1];
|
|
@@ -9103,9 +9642,12 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
9103
9642
|
const minutes = statusMinutes(
|
|
9104
9643
|
numberOpt(parsed.options.minutes, "--minutes") ?? 60
|
|
9105
9644
|
);
|
|
9106
|
-
const
|
|
9107
|
-
|
|
9108
|
-
|
|
9645
|
+
const context = await resolveOperatorContext(parsed, {
|
|
9646
|
+
allowMissingConfig: true,
|
|
9647
|
+
defaultEnvironment: "prod",
|
|
9648
|
+
requireApp: true
|
|
9649
|
+
});
|
|
9650
|
+
const cfg = context.cfg;
|
|
9109
9651
|
const doFetch = deps.fetch ?? fetch;
|
|
9110
9652
|
const out = deps.stdout ?? console;
|
|
9111
9653
|
const token = await getDeveloperToken(
|
|
@@ -9119,8 +9661,8 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
9119
9661
|
doFetch,
|
|
9120
9662
|
out
|
|
9121
9663
|
);
|
|
9122
|
-
const appId =
|
|
9123
|
-
const env =
|
|
9664
|
+
const appId = context.app.value;
|
|
9665
|
+
const env = context.environment.value;
|
|
9124
9666
|
const base = `${cfg.platformUrl}/o11y/${encodeURIComponent(appId)}`;
|
|
9125
9667
|
const query = new URLSearchParams({ env, minutes: String(minutes) });
|
|
9126
9668
|
const headers = { authorization: `Bearer ${token}` };
|
|
@@ -9137,6 +9679,8 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
9137
9679
|
canary,
|
|
9138
9680
|
collector,
|
|
9139
9681
|
provider,
|
|
9682
|
+
providerCapacity,
|
|
9683
|
+
providerReconciliation,
|
|
9140
9684
|
providerHistory
|
|
9141
9685
|
] = await Promise.all([
|
|
9142
9686
|
read2(`${base}/metrics/red?${query}`, headers, doFetch),
|
|
@@ -9153,6 +9697,19 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
9153
9697
|
),
|
|
9154
9698
|
read2(`${base}/self-health?${query}`, headers, doFetch),
|
|
9155
9699
|
read2(`${base}/provider/cloudflare?${query}`, headers, doFetch),
|
|
9700
|
+
appId === "odla-db" ? read2(`${base}/provider/cloudflare/capacity`, headers, doFetch) : Promise.resolve({
|
|
9701
|
+
httpStatus: 204,
|
|
9702
|
+
body: {
|
|
9703
|
+
status: "not_applicable",
|
|
9704
|
+
scope: "cloudflare_account",
|
|
9705
|
+
reason: "account-scoped Durable Object capacity is exposed only through odla-db"
|
|
9706
|
+
}
|
|
9707
|
+
}),
|
|
9708
|
+
read2(
|
|
9709
|
+
`${base}/provider/cloudflare/reconciliation?${query}`,
|
|
9710
|
+
headers,
|
|
9711
|
+
doFetch
|
|
9712
|
+
),
|
|
9156
9713
|
read2(`${base}/provider/cloudflare/history?${query}`, headers, doFetch)
|
|
9157
9714
|
]);
|
|
9158
9715
|
const verdict = statusVerdict({
|
|
@@ -9161,10 +9718,12 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
9161
9718
|
canary,
|
|
9162
9719
|
collector,
|
|
9163
9720
|
provider,
|
|
9721
|
+
providerCapacity,
|
|
9722
|
+
providerReconciliation,
|
|
9164
9723
|
providerHistory
|
|
9165
9724
|
});
|
|
9166
9725
|
const status = {
|
|
9167
|
-
schemaVersion:
|
|
9726
|
+
schemaVersion: 9,
|
|
9168
9727
|
scope: { appId, env, minutes },
|
|
9169
9728
|
observedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9170
9729
|
verdict,
|
|
@@ -9174,6 +9733,8 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
9174
9733
|
canary,
|
|
9175
9734
|
collector,
|
|
9176
9735
|
provider,
|
|
9736
|
+
providerCapacity,
|
|
9737
|
+
providerReconciliation,
|
|
9177
9738
|
providerHistory
|
|
9178
9739
|
};
|
|
9179
9740
|
if (parsed.options.json === true) {
|
|
@@ -9205,14 +9766,14 @@ async function read2(url, headers, doFetch) {
|
|
|
9205
9766
|
|
|
9206
9767
|
// src/record.ts
|
|
9207
9768
|
import { appendFileSync } from "fs";
|
|
9208
|
-
import
|
|
9769
|
+
import process12 from "process";
|
|
9209
9770
|
function recordInvocation(parsed) {
|
|
9210
|
-
const file =
|
|
9771
|
+
const file = process12.env.ODLA_CLI_RECORD;
|
|
9211
9772
|
if (!file) return;
|
|
9212
9773
|
try {
|
|
9213
9774
|
const entry = {
|
|
9214
9775
|
path: invocationPath(parsed.positionals),
|
|
9215
|
-
options: Object.
|
|
9776
|
+
options: Object.entries(parsed.options).map(([name, value]) => value === false ? `no-${name}` : name).sort()
|
|
9216
9777
|
};
|
|
9217
9778
|
if (!entry.path.length) return;
|
|
9218
9779
|
appendFileSync(file, `${JSON.stringify(entry)}
|
|
@@ -9221,13 +9782,8 @@ function recordInvocation(parsed) {
|
|
|
9221
9782
|
}
|
|
9222
9783
|
}
|
|
9223
9784
|
|
|
9224
|
-
// src/runbook-command.ts
|
|
9225
|
-
import { existsSync as existsSync10 } from "fs";
|
|
9226
|
-
import { join as join12, resolve as resolve10 } from "path";
|
|
9227
|
-
import process11 from "process";
|
|
9228
|
-
|
|
9229
9785
|
// src/runbook-actions.ts
|
|
9230
|
-
import { readFileSync as
|
|
9786
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
9231
9787
|
|
|
9232
9788
|
// src/runbook-requires.ts
|
|
9233
9789
|
var SPEC = /^(@?[\w./-]+?)@(\d+\.\d+\.\d+(?:[\w.-]*)?)$/;
|
|
@@ -9312,7 +9868,7 @@ async function bySlug(ctx, slug) {
|
|
|
9312
9868
|
function readBody(file, inline) {
|
|
9313
9869
|
if (inline !== void 0) return inline;
|
|
9314
9870
|
if (file === void 0) throw new Error("supply the new text with --file <path>, --file - (stdin), or --body");
|
|
9315
|
-
return
|
|
9871
|
+
return readFileSync9(file === "-" ? 0 : file, "utf8");
|
|
9316
9872
|
}
|
|
9317
9873
|
var stamp = (ms) => ms ? new Date(ms).toISOString().slice(0, 16).replace("T", " ") : "";
|
|
9318
9874
|
async function runbookList(ctx, all, query) {
|
|
@@ -9399,8 +9955,8 @@ async function runbookRemove(ctx, slug) {
|
|
|
9399
9955
|
}
|
|
9400
9956
|
|
|
9401
9957
|
// src/runbook-import.ts
|
|
9402
|
-
import { readFileSync as
|
|
9403
|
-
import { basename as basename2, join as
|
|
9958
|
+
import { readFileSync as readFileSync10, readdirSync as readdirSync2, statSync } from "fs";
|
|
9959
|
+
import { basename as basename2, join as join11 } from "path";
|
|
9404
9960
|
function parseRunbook(text, slug) {
|
|
9405
9961
|
let rest = text;
|
|
9406
9962
|
const meta = {};
|
|
@@ -9430,7 +9986,7 @@ function readRunbookDir(dir) {
|
|
|
9430
9986
|
if (!files.length) throw new Error(`no .md files in ${dir}`);
|
|
9431
9987
|
return files.map((file) => {
|
|
9432
9988
|
const slug = basename2(file, ".md");
|
|
9433
|
-
const parsed = parseRunbook(
|
|
9989
|
+
const parsed = parseRunbook(readFileSync10(join11(dir, file), "utf8"), slug);
|
|
9434
9990
|
return { file, slug, ...parsed, words: parsed.body.split(/\s+/).filter(Boolean).length };
|
|
9435
9991
|
});
|
|
9436
9992
|
}
|
|
@@ -9502,8 +10058,8 @@ async function upsert(ctx, r, visibility) {
|
|
|
9502
10058
|
|
|
9503
10059
|
// src/runbook-impact.ts
|
|
9504
10060
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
9505
|
-
import { existsSync as
|
|
9506
|
-
import { join as
|
|
10061
|
+
import { existsSync as existsSync11, readFileSync as readFileSync11 } from "fs";
|
|
10062
|
+
import { join as join12 } from "path";
|
|
9507
10063
|
|
|
9508
10064
|
// src/runbook-impact-scan.ts
|
|
9509
10065
|
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$]*)/;
|
|
@@ -9672,10 +10228,10 @@ ${body.split("\n").map((line) => `+${line}`).join("\n")}
|
|
|
9672
10228
|
}
|
|
9673
10229
|
function manifestLabeller(root) {
|
|
9674
10230
|
return (workspace) => {
|
|
9675
|
-
const manifest =
|
|
9676
|
-
if (!
|
|
10231
|
+
const manifest = join12(root, workspace, "package.json");
|
|
10232
|
+
if (!existsSync11(manifest)) return void 0;
|
|
9677
10233
|
try {
|
|
9678
|
-
const name = JSON.parse(
|
|
10234
|
+
const name = JSON.parse(readFileSync11(manifest, "utf8")).name;
|
|
9679
10235
|
return typeof name === "string" ? name : void 0;
|
|
9680
10236
|
} catch {
|
|
9681
10237
|
return void 0;
|
|
@@ -9742,7 +10298,7 @@ function report3(ctx, impacts) {
|
|
|
9742
10298
|
async function runbookImpact(ctx, options, deps = {}) {
|
|
9743
10299
|
const cwd = deps.cwd ?? process.cwd();
|
|
9744
10300
|
const runGit = deps.runGit ?? gitRunner(cwd);
|
|
9745
|
-
const read3 = deps.readRepoFile ?? ((path) =>
|
|
10301
|
+
const read3 = deps.readRepoFile ?? ((path) => readFileSync11(join12(cwd, path), "utf8"));
|
|
9746
10302
|
const surfaces = changedSurfaces(collectDiff(runGit, options.base, read3), manifestLabeller(cwd));
|
|
9747
10303
|
if (!surfaces.length) {
|
|
9748
10304
|
return ctx.out.log(
|
|
@@ -9875,12 +10431,12 @@ async function runbookComment(ctx, slug, body) {
|
|
|
9875
10431
|
|
|
9876
10432
|
// src/runbook-editor.ts
|
|
9877
10433
|
import { spawnSync } from "child_process";
|
|
9878
|
-
import { mkdtempSync, readFileSync as
|
|
10434
|
+
import { mkdtempSync, readFileSync as readFileSync12, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
9879
10435
|
import { tmpdir as tmpdir4 } from "os";
|
|
9880
|
-
import { join as
|
|
9881
|
-
import
|
|
10436
|
+
import { join as join13 } from "path";
|
|
10437
|
+
import process13 from "process";
|
|
9882
10438
|
var EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
|
|
9883
|
-
function resolveEditor(env =
|
|
10439
|
+
function resolveEditor(env = process13.env) {
|
|
9884
10440
|
for (const name of EDITOR_ENV) {
|
|
9885
10441
|
const value = env[name];
|
|
9886
10442
|
if (value && value.trim()) return value.trim();
|
|
@@ -9894,8 +10450,8 @@ function defaultRun(command, path) {
|
|
|
9894
10450
|
return result.status ?? 0;
|
|
9895
10451
|
}
|
|
9896
10452
|
function editText(initial, slug, deps = {}) {
|
|
9897
|
-
const env = deps.env ??
|
|
9898
|
-
const interactive = deps.interactive ?? (() => Boolean(
|
|
10453
|
+
const env = deps.env ?? process13.env;
|
|
10454
|
+
const interactive = deps.interactive ?? (() => Boolean(process13.stdin.isTTY));
|
|
9899
10455
|
const editor = resolveEditor(env);
|
|
9900
10456
|
if (!editor)
|
|
9901
10457
|
throw new Error(
|
|
@@ -9903,13 +10459,13 @@ function editText(initial, slug, deps = {}) {
|
|
|
9903
10459
|
);
|
|
9904
10460
|
if (!interactive())
|
|
9905
10461
|
throw new Error(`cannot open an editor without a terminal \u2014 pass --file <path> or --body "\u2026" instead`);
|
|
9906
|
-
const dir = mkdtempSync(
|
|
9907
|
-
const file =
|
|
10462
|
+
const dir = mkdtempSync(join13(tmpdir4(), "odla-runbook-"));
|
|
10463
|
+
const file = join13(dir, `${slug}.md`);
|
|
9908
10464
|
try {
|
|
9909
10465
|
writeFileSync4(file, initial, { mode: 384 });
|
|
9910
10466
|
const code = defaultRunOrInjected(deps)(editor, file);
|
|
9911
10467
|
if (code !== 0) throw new Error(`editor "${editor}" exited with ${code}; nothing was written`);
|
|
9912
|
-
const edited =
|
|
10468
|
+
const edited = readFileSync12(file, "utf8");
|
|
9913
10469
|
return edited === initial ? null : edited;
|
|
9914
10470
|
} finally {
|
|
9915
10471
|
rmSync2(dir, { recursive: true, force: true });
|
|
@@ -9950,10 +10506,16 @@ function credentialKind(identity) {
|
|
|
9950
10506
|
return identity.scopes.length ? "device token (scoped)" : "device token or session";
|
|
9951
10507
|
}
|
|
9952
10508
|
async function whoamiCommand(parsed, deps = {}) {
|
|
9953
|
-
assertArgs(
|
|
10509
|
+
assertArgs(
|
|
10510
|
+
parsed,
|
|
10511
|
+
["config", "context", "platform", "token", "email", "json"],
|
|
10512
|
+
1
|
|
10513
|
+
);
|
|
9954
10514
|
const out = deps.stdout ?? console;
|
|
9955
10515
|
const doFetch = deps.fetch ?? fetch;
|
|
9956
|
-
const cfg = await
|
|
10516
|
+
const { cfg } = await resolveOperatorContext(parsed, {
|
|
10517
|
+
allowMissingConfig: true
|
|
10518
|
+
});
|
|
9957
10519
|
const token = await getDeveloperToken(
|
|
9958
10520
|
cfg,
|
|
9959
10521
|
{
|
|
@@ -9984,7 +10546,7 @@ async function whoamiCommand(parsed, deps = {}) {
|
|
|
9984
10546
|
}
|
|
9985
10547
|
|
|
9986
10548
|
// src/runbook-command.ts
|
|
9987
|
-
var
|
|
10549
|
+
var ALLOWED2 = [
|
|
9988
10550
|
"config",
|
|
9989
10551
|
"token",
|
|
9990
10552
|
"email",
|
|
@@ -10002,34 +10564,24 @@ var ALLOWED3 = [
|
|
|
10002
10564
|
"dry-run",
|
|
10003
10565
|
"limit",
|
|
10004
10566
|
"base",
|
|
10005
|
-
"requires"
|
|
10567
|
+
"requires",
|
|
10568
|
+
"platform",
|
|
10569
|
+
"context"
|
|
10006
10570
|
];
|
|
10007
10571
|
function requireSlug(slug, action) {
|
|
10008
10572
|
if (!slug) throw new Error(`"runbook ${action}" needs a slug, e.g. "odla-ai runbook ${action} release"`);
|
|
10009
10573
|
return slug;
|
|
10010
10574
|
}
|
|
10011
10575
|
var WRITES = /* @__PURE__ */ new Set(["new", "edit", "publish", "archive", "visibility", "revert", "rm", "import"]);
|
|
10012
|
-
var CONFIG_FREE = /* @__PURE__ */ new Set(["list", "search", "ask", "get", "cat", "history", "impact", "lint"]);
|
|
10013
|
-
async function loadOrDefaultConfig(configPath, action, appId) {
|
|
10014
|
-
const projectScoped = appId !== void 0;
|
|
10015
|
-
if (!projectScoped && CONFIG_FREE.has(action) && !existsSync10(resolve10(configPath))) {
|
|
10016
|
-
const rootDir = process11.cwd();
|
|
10017
|
-
return {
|
|
10018
|
-
configPath,
|
|
10019
|
-
platformUrl: process11.env.ODLA_PLATFORM_URL ?? "https://odla.ai",
|
|
10020
|
-
rootDir,
|
|
10021
|
-
app: { id: "", name: "" },
|
|
10022
|
-
local: { tokenFile: join12(rootDir, ".odla/dev-token.json") }
|
|
10023
|
-
};
|
|
10024
|
-
}
|
|
10025
|
-
return loadProjectConfig(configPath);
|
|
10026
|
-
}
|
|
10027
10576
|
async function buildContext3(parsed, deps, action) {
|
|
10028
|
-
const
|
|
10029
|
-
const
|
|
10577
|
+
const appIdOption = stringOpt(parsed.options.app);
|
|
10578
|
+
const context = await resolveOperatorContext(parsed, {
|
|
10579
|
+
allowMissingConfig: true
|
|
10580
|
+
});
|
|
10581
|
+
const { cfg } = context;
|
|
10030
10582
|
const doFetch = deps.fetch ?? fetch;
|
|
10031
10583
|
const out = deps.stdout ?? console;
|
|
10032
|
-
const appId =
|
|
10584
|
+
const appId = appIdOption ?? (context.app.source === "environment" || context.app.source === "profile" ? context.app.value : null) ?? PLATFORM_SCOPE;
|
|
10033
10585
|
const dryRun = parsed.options["dry-run"] === true;
|
|
10034
10586
|
if (action === "import" && dryRun) {
|
|
10035
10587
|
return {
|
|
@@ -10050,8 +10602,10 @@ async function buildContext3(parsed, deps, action) {
|
|
|
10050
10602
|
fetch: doFetch,
|
|
10051
10603
|
stdout: out,
|
|
10052
10604
|
openApprovalUrl: deps.openUrl,
|
|
10053
|
-
//
|
|
10054
|
-
|
|
10605
|
+
// A project, named context, or the global operator context owns the
|
|
10606
|
+
// exact-scope cache; it never follows an arbitrary shell directory.
|
|
10607
|
+
rootDir: cfg.rootDir,
|
|
10608
|
+
tokenFile: context.credentials.scopedTokenFile
|
|
10055
10609
|
}) : await getDeveloperToken(
|
|
10056
10610
|
cfg,
|
|
10057
10611
|
{
|
|
@@ -10080,7 +10634,7 @@ async function buildContext3(parsed, deps, action) {
|
|
|
10080
10634
|
}
|
|
10081
10635
|
async function runbookCommand(parsed, deps = {}) {
|
|
10082
10636
|
const action = parsed.positionals[1] ?? "list";
|
|
10083
|
-
assertArgs(parsed,
|
|
10637
|
+
assertArgs(parsed, ALLOWED2, action === "ask" || action === "search" ? 64 : 4);
|
|
10084
10638
|
const ctx = await buildContext3(parsed, deps, action);
|
|
10085
10639
|
const slug = parsed.positionals[2];
|
|
10086
10640
|
switch (action) {
|
|
@@ -10634,6 +11188,10 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
|
10634
11188
|
await whoamiCommand(parsed, runtime);
|
|
10635
11189
|
return;
|
|
10636
11190
|
}
|
|
11191
|
+
if (command === "context") {
|
|
11192
|
+
await contextCommand(parsed, runtime);
|
|
11193
|
+
return;
|
|
11194
|
+
}
|
|
10637
11195
|
if (command === "runbook") {
|
|
10638
11196
|
await runbookCommand(parsed, runtime);
|
|
10639
11197
|
return;
|
|
@@ -10793,4 +11351,4 @@ export {
|
|
|
10793
11351
|
exitCodeFor,
|
|
10794
11352
|
runCli
|
|
10795
11353
|
};
|
|
10796
|
-
//# sourceMappingURL=chunk-
|
|
11354
|
+
//# sourceMappingURL=chunk-B6DISJPC.js.map
|