@odla-ai/cli 0.25.11 → 0.25.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -7
- package/dist/bin.cjs +720 -165
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-FYSV7POZ.js → chunk-5MRY3V2K.js} +703 -148
- package/dist/chunk-5MRY3V2K.js.map +1 -0
- package/dist/index.cjs +720 -165
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/skills/odla-o11y-debug/SKILL.md +30 -10
- package/dist/chunk-FYSV7POZ.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",
|
|
@@ -1638,7 +1639,7 @@ var CAPABILITIES = {
|
|
|
1638
1639
|
"approve GitHub App repository access separately from redacted-snippet disclosure"
|
|
1639
1640
|
],
|
|
1640
1641
|
studio: [
|
|
1641
|
-
"view application telemetry, reconciled live-sync load/freshness, commit-to-visible canary health, provider-owned Worker runtime metrics, and environment state",
|
|
1642
|
+
"view application telemetry grouped by exact Worker version, reconciled live-sync load/freshness, commit-to-visible canary health, provider-owned Worker runtime metrics, and environment state",
|
|
1642
1643
|
"let signed-in users inventory/revoke their own agent grants and admins audit/global-revoke them",
|
|
1643
1644
|
"review calendar connection, granted read scope, selected calendars, and sync health without exposing provider tokens",
|
|
1644
1645
|
"perform manual credential recovery \u2014 for the primary owner or any co-owner \u2014 when the CLI's local shown-once copy is unavailable",
|
|
@@ -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 = {
|
|
@@ -5998,8 +5999,12 @@ Usage:
|
|
|
5998
5999
|
odla-ai discuss watch [<topic>] [--cursor <cursor>] [--by <authorId>] [--self <authorId>] [--interval <s>] [--timeout <s>] [--json|--jsonl]
|
|
5999
6000
|
odla-ai agent jobs [--env dev] [--state pending|running|succeeded|dead_letter] [--limit 50] [--email <email>] [--json]
|
|
6000
6001
|
odla-ai agent retry <job-id> [--env dev] [--email <email>] [--json]
|
|
6001
|
-
odla-ai
|
|
6002
|
-
odla-ai
|
|
6002
|
+
odla-ai context show [--context <name>] [--platform https://odla.ai] [--app <id>] [--env prod] [--json]
|
|
6003
|
+
odla-ai context list [--json]
|
|
6004
|
+
odla-ai context save <name> [--platform <url>] [--app <id>] [--env <name>] [--json]
|
|
6005
|
+
odla-ai context remove <name> --yes [--json]
|
|
6006
|
+
odla-ai o11y status [--app <id>] [--context <name>] [--platform https://odla.ai] [--env prod] [--minutes 60] [--json]
|
|
6007
|
+
odla-ai whoami [--context <name>] [--platform https://odla.ai] [--json]
|
|
6003
6008
|
odla-ai runbook ask "<question>" [--app <id>] [--all] [--json]
|
|
6004
6009
|
odla-ai runbook search "<question>" [--app <id>] [--all] [--limit <n>] [--json]
|
|
6005
6010
|
odla-ai runbook impact [--base origin/main] [--app <id>] [--all] [--limit <n>] [--json]
|
|
@@ -6064,6 +6069,11 @@ Commands:
|
|
|
6064
6069
|
whoami Report who this terminal is authenticated as, and whether it holds
|
|
6065
6070
|
platform admin. A handshake-minted device token is never admin,
|
|
6066
6071
|
however the human who approved it is configured.
|
|
6072
|
+
context Explain selected config, platform, app, environment, and
|
|
6073
|
+
developer-token provenance without printing credentials or
|
|
6074
|
+
starting a device handshake. Operator work can run outside a
|
|
6075
|
+
project checkout with explicit flags or ODLA_* environment
|
|
6076
|
+
variables.
|
|
6067
6077
|
setup Install offline odla runbooks for common coding-agent harnesses.
|
|
6068
6078
|
init Create a generic odla.config.mjs plus starter schema/rules files.
|
|
6069
6079
|
doctor Validate and summarize the project config without network calls.
|
|
@@ -6135,6 +6145,15 @@ Safety:
|
|
|
6135
6145
|
Provision caches the approved developer token and service credentials under
|
|
6136
6146
|
.odla/ with mode 0600, and init adds those paths to .gitignore. Secret push
|
|
6137
6147
|
preflights Wrangler before any shown-once issuance or destructive rotation.
|
|
6148
|
+
Projectless PM, Discussions, o11y, runbook, and identity commands use
|
|
6149
|
+
--platform/--app/--env, ODLA_PLATFORM_URL/ODLA_APP_ID/ODLA_ENV, and
|
|
6150
|
+
ODLA_DEV_TOKEN. Save non-secret scope metadata with "context save", then
|
|
6151
|
+
select it explicitly with --context or ODLA_CONTEXT. Each named context gets
|
|
6152
|
+
isolated developer and scoped-token caches. Override their locations with
|
|
6153
|
+
ODLA_DEV_TOKEN_FILE and ODLA_ADMIN_TOKEN_FILE; ODLA_CONTEXT_FILE relocates
|
|
6154
|
+
the metadata file. Flags and specific ODLA_* scope variables beat a selected
|
|
6155
|
+
context, which beats project config. There is no ambient current context.
|
|
6156
|
+
"context show" reports only provenance and cache state and never authenticates.
|
|
6138
6157
|
Provision opens the approval page in your browser automatically whenever the
|
|
6139
6158
|
machine can show one, including agent-driven runs; only CI, SSH, and
|
|
6140
6159
|
display-less hosts skip it. Use --open to force or --no-open to suppress.
|
|
@@ -6848,6 +6867,7 @@ var COMMAND_SURFACE = {
|
|
|
6848
6867
|
calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
|
|
6849
6868
|
capabilities: {},
|
|
6850
6869
|
code: { connect: {} },
|
|
6870
|
+
context: { show: {}, list: {}, save: {}, remove: {} },
|
|
6851
6871
|
// `watch`, `read`, `reply`, and `resolve` take a topic id from there on.
|
|
6852
6872
|
discuss: {
|
|
6853
6873
|
groups: {},
|
|
@@ -8135,6 +8155,384 @@ async function codeCommand(parsed, dependencies) {
|
|
|
8135
8155
|
});
|
|
8136
8156
|
}
|
|
8137
8157
|
|
|
8158
|
+
// src/operator-credentials.ts
|
|
8159
|
+
import process9 from "process";
|
|
8160
|
+
function developerTokenStatus(context, parsed, now = Date.now()) {
|
|
8161
|
+
const cached = readJsonFile(context.cfg.local.tokenFile);
|
|
8162
|
+
const cacheStatus = !cached?.token ? "missing" : cached.platform !== context.platform.value ? "other-platform" : (cached.expiresAt ?? 0) <= now + 6e4 ? "expired" : "valid";
|
|
8163
|
+
const source = clean(
|
|
8164
|
+
stringOpt(parsed.options.token)
|
|
8165
|
+
) ? "flag" : clean(process9.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
|
|
8166
|
+
return {
|
|
8167
|
+
source,
|
|
8168
|
+
cacheFile: context.cfg.local.tokenFile,
|
|
8169
|
+
cacheStatus
|
|
8170
|
+
};
|
|
8171
|
+
}
|
|
8172
|
+
function clean(value) {
|
|
8173
|
+
const normalized = value?.trim();
|
|
8174
|
+
return normalized || void 0;
|
|
8175
|
+
}
|
|
8176
|
+
|
|
8177
|
+
// src/operator-context.ts
|
|
8178
|
+
import { existsSync as existsSync10 } from "fs";
|
|
8179
|
+
import { join as join10, resolve as resolve11 } from "path";
|
|
8180
|
+
import process11 from "process";
|
|
8181
|
+
|
|
8182
|
+
// src/operator-profiles.ts
|
|
8183
|
+
import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
|
|
8184
|
+
import { homedir as homedir2 } from "os";
|
|
8185
|
+
import { dirname as dirname7, join as join9, resolve as resolve10 } from "path";
|
|
8186
|
+
import process10 from "process";
|
|
8187
|
+
function operatorProfileFile() {
|
|
8188
|
+
return resolve10(
|
|
8189
|
+
clean2(process10.env.ODLA_CONTEXT_FILE) ?? join9(homedir2(), ".odla", "contexts.json")
|
|
8190
|
+
);
|
|
8191
|
+
}
|
|
8192
|
+
function resolveOperatorProfile(parsed) {
|
|
8193
|
+
const fromFlag = clean2(stringOpt(parsed.options.context));
|
|
8194
|
+
const fromEnvironment = clean2(process10.env.ODLA_CONTEXT);
|
|
8195
|
+
const name = fromFlag ?? fromEnvironment ?? null;
|
|
8196
|
+
const file = operatorProfileFile();
|
|
8197
|
+
if (!name) {
|
|
8198
|
+
return { name: null, source: "unresolved", file, value: null };
|
|
8199
|
+
}
|
|
8200
|
+
assertOperatorName(name, "context");
|
|
8201
|
+
const profiles = readOperatorProfiles(file);
|
|
8202
|
+
const value = Object.hasOwn(profiles, name) ? profiles[name] : void 0;
|
|
8203
|
+
if (!value) {
|
|
8204
|
+
throw new Error(
|
|
8205
|
+
`operator context "${name}" not found in ${file}; run "odla-ai context list" or save it first`
|
|
8206
|
+
);
|
|
8207
|
+
}
|
|
8208
|
+
return {
|
|
8209
|
+
name,
|
|
8210
|
+
source: fromFlag ? "flag" : "environment",
|
|
8211
|
+
file,
|
|
8212
|
+
value
|
|
8213
|
+
};
|
|
8214
|
+
}
|
|
8215
|
+
function listOperatorProfiles(file = operatorProfileFile()) {
|
|
8216
|
+
return Object.entries(readOperatorProfiles(file)).sort(([a], [b]) => a.localeCompare(b)).map(([name, profile]) => ({ name, ...profile }));
|
|
8217
|
+
}
|
|
8218
|
+
function saveOperatorProfile(name, profile, file = operatorProfileFile()) {
|
|
8219
|
+
assertOperatorName(name, "context");
|
|
8220
|
+
const normalized = validateProfile(profile, `operator context "${name}"`);
|
|
8221
|
+
const profiles = readOperatorProfiles(file);
|
|
8222
|
+
profiles[name] = normalized;
|
|
8223
|
+
writePrivateJson(file, { schemaVersion: 1, profiles });
|
|
8224
|
+
}
|
|
8225
|
+
function removeOperatorProfile(name, file = operatorProfileFile()) {
|
|
8226
|
+
assertOperatorName(name, "context");
|
|
8227
|
+
const profiles = readOperatorProfiles(file);
|
|
8228
|
+
if (!Object.hasOwn(profiles, name)) return false;
|
|
8229
|
+
delete profiles[name];
|
|
8230
|
+
writePrivateJson(file, { schemaVersion: 1, profiles });
|
|
8231
|
+
return true;
|
|
8232
|
+
}
|
|
8233
|
+
function operatorCredentialFiles(selection) {
|
|
8234
|
+
const base = selection.name ? join9(dirname7(selection.file), "profiles", selection.name) : join9(homedir2(), ".odla");
|
|
8235
|
+
return {
|
|
8236
|
+
developer: join9(base, "dev-token.json"),
|
|
8237
|
+
scoped: join9(base, "admin-token.local.json")
|
|
8238
|
+
};
|
|
8239
|
+
}
|
|
8240
|
+
function assertOperatorName(value, label) {
|
|
8241
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(value)) {
|
|
8242
|
+
throw new Error(
|
|
8243
|
+
`${label} must contain lowercase letters, numbers, and hyphens`
|
|
8244
|
+
);
|
|
8245
|
+
}
|
|
8246
|
+
}
|
|
8247
|
+
function readOperatorProfiles(file) {
|
|
8248
|
+
if (!existsSync9(file)) return emptyProfiles();
|
|
8249
|
+
let raw;
|
|
8250
|
+
try {
|
|
8251
|
+
raw = JSON.parse(readFileSync8(file, "utf8"));
|
|
8252
|
+
} catch {
|
|
8253
|
+
throw new Error(`operator context file ${file} is not valid JSON`);
|
|
8254
|
+
}
|
|
8255
|
+
if (!raw || typeof raw !== "object" || raw.schemaVersion !== 1 || !raw.profiles || typeof raw.profiles !== "object" || Array.isArray(raw.profiles)) {
|
|
8256
|
+
throw new Error(`operator context file ${file} has an invalid shape`);
|
|
8257
|
+
}
|
|
8258
|
+
const unknown = Object.keys(raw).filter(
|
|
8259
|
+
(key) => !["schemaVersion", "profiles"].includes(key)
|
|
8260
|
+
);
|
|
8261
|
+
if (unknown.length > 0) {
|
|
8262
|
+
throw new Error(
|
|
8263
|
+
`operator context file ${file} contains unsupported field(s): ${unknown.sort().join(", ")}`
|
|
8264
|
+
);
|
|
8265
|
+
}
|
|
8266
|
+
const profiles = emptyProfiles();
|
|
8267
|
+
for (const [name, value] of Object.entries(
|
|
8268
|
+
raw.profiles
|
|
8269
|
+
)) {
|
|
8270
|
+
assertOperatorName(name, "context");
|
|
8271
|
+
profiles[name] = validateProfile(value, `operator context "${name}"`);
|
|
8272
|
+
}
|
|
8273
|
+
return profiles;
|
|
8274
|
+
}
|
|
8275
|
+
function emptyProfiles() {
|
|
8276
|
+
return /* @__PURE__ */ Object.create(null);
|
|
8277
|
+
}
|
|
8278
|
+
function validateProfile(value, label) {
|
|
8279
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
8280
|
+
throw new Error(`${label} has an invalid shape`);
|
|
8281
|
+
}
|
|
8282
|
+
const unknown = Object.keys(value).filter(
|
|
8283
|
+
(key) => !["platform", "app", "environment"].includes(key)
|
|
8284
|
+
);
|
|
8285
|
+
if (unknown.length > 0) {
|
|
8286
|
+
throw new Error(
|
|
8287
|
+
`${label} contains unsupported field(s): ${unknown.sort().join(", ")}; contexts store scope metadata only, never credentials`
|
|
8288
|
+
);
|
|
8289
|
+
}
|
|
8290
|
+
const candidate = value;
|
|
8291
|
+
if (typeof candidate.platform !== "string") {
|
|
8292
|
+
throw new Error(`${label} needs an absolute platform URL`);
|
|
8293
|
+
}
|
|
8294
|
+
const platform = platformAudience(candidate.platform);
|
|
8295
|
+
const app = clean2(candidate.app);
|
|
8296
|
+
const environment2 = clean2(candidate.environment);
|
|
8297
|
+
if (app) assertOperatorName(app, "app");
|
|
8298
|
+
if (environment2) assertOperatorName(environment2, "environment");
|
|
8299
|
+
return {
|
|
8300
|
+
platform,
|
|
8301
|
+
...app ? { app } : {},
|
|
8302
|
+
...environment2 ? { environment: environment2 } : {}
|
|
8303
|
+
};
|
|
8304
|
+
}
|
|
8305
|
+
function clean2(value) {
|
|
8306
|
+
const normalized = value?.trim();
|
|
8307
|
+
return normalized || void 0;
|
|
8308
|
+
}
|
|
8309
|
+
|
|
8310
|
+
// src/operator-context.ts
|
|
8311
|
+
var DEFAULT_PLATFORM2 = "https://odla.ai";
|
|
8312
|
+
async function resolveOperatorContext(parsed, options = {}) {
|
|
8313
|
+
const profile = resolveOperatorProfile(parsed);
|
|
8314
|
+
const configArgument = stringOpt(parsed.options.config) ?? "odla.config.mjs";
|
|
8315
|
+
const configPath = resolve11(configArgument);
|
|
8316
|
+
const explicitConfig = parsed.options.config !== void 0;
|
|
8317
|
+
const hasConfig = existsSync10(configPath);
|
|
8318
|
+
if (!hasConfig && (!options.allowMissingConfig || explicitConfig)) {
|
|
8319
|
+
await loadProjectConfig(configArgument);
|
|
8320
|
+
}
|
|
8321
|
+
const loaded = hasConfig ? await loadProjectConfig(configArgument) : void 0;
|
|
8322
|
+
const platformFlag = clean3(stringOpt(parsed.options.platform));
|
|
8323
|
+
const platformEnvironment = clean3(process11.env.ODLA_PLATFORM_URL);
|
|
8324
|
+
const platformValue = platformAudience(
|
|
8325
|
+
platformFlag ?? platformEnvironment ?? profile.value?.platform ?? loaded?.platformUrl ?? DEFAULT_PLATFORM2
|
|
8326
|
+
);
|
|
8327
|
+
const platformSource = platformFlag ? "flag" : platformEnvironment ? "environment" : profile.value ? "profile" : loaded ? "config" : "default";
|
|
8328
|
+
const appFlag = clean3(stringOpt(parsed.options.app));
|
|
8329
|
+
const appEnvironment = clean3(process11.env.ODLA_APP_ID);
|
|
8330
|
+
const appValue = appFlag ?? appEnvironment ?? profile.value?.app ?? loaded?.app.id ?? null;
|
|
8331
|
+
const appSource = appFlag ? "flag" : appEnvironment ? "environment" : profile.value?.app ? "profile" : loaded ? "config" : "unresolved";
|
|
8332
|
+
if (appValue) assertOperatorName(appValue, "app");
|
|
8333
|
+
if (options.requireApp && !appValue) {
|
|
8334
|
+
throw new Error(
|
|
8335
|
+
"app context is unresolved; pass --app <id>, set ODLA_APP_ID, select --context <name>, or run inside a project with odla.config.mjs"
|
|
8336
|
+
);
|
|
8337
|
+
}
|
|
8338
|
+
const envFlag = clean3(stringOpt(parsed.options.env));
|
|
8339
|
+
const envEnvironment = clean3(process11.env.ODLA_ENV);
|
|
8340
|
+
const environmentValue = envFlag ?? envEnvironment ?? profile.value?.environment ?? options.defaultEnvironment ?? null;
|
|
8341
|
+
const environmentSource = envFlag ? "flag" : envEnvironment ? "environment" : profile.value?.environment ? "profile" : options.defaultEnvironment ? "default" : "unresolved";
|
|
8342
|
+
if (environmentValue) {
|
|
8343
|
+
assertOperatorName(environmentValue, "environment");
|
|
8344
|
+
}
|
|
8345
|
+
const rootDir = loaded?.rootDir ?? process11.cwd();
|
|
8346
|
+
const profileCredentials = operatorCredentialFiles(profile);
|
|
8347
|
+
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;
|
|
8348
|
+
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;
|
|
8349
|
+
const cfg = loaded ? {
|
|
8350
|
+
...loaded,
|
|
8351
|
+
platformUrl: platformValue,
|
|
8352
|
+
app: appValue ? {
|
|
8353
|
+
id: appValue,
|
|
8354
|
+
name: appValue === loaded.app.id ? loaded.app.name : appValue
|
|
8355
|
+
} : loaded.app,
|
|
8356
|
+
local: { ...loaded.local, tokenFile }
|
|
8357
|
+
} : {
|
|
8358
|
+
configPath,
|
|
8359
|
+
rootDir,
|
|
8360
|
+
platformUrl: platformValue,
|
|
8361
|
+
dbEndpoint: platformValue,
|
|
8362
|
+
app: {
|
|
8363
|
+
id: appValue ?? "operator",
|
|
8364
|
+
name: appValue ?? "Operator"
|
|
8365
|
+
},
|
|
8366
|
+
envs: environmentValue ? [environmentValue] : [],
|
|
8367
|
+
services: [],
|
|
8368
|
+
local: {
|
|
8369
|
+
tokenFile,
|
|
8370
|
+
credentialsFile: join10(rootDir, ".odla", "credentials.local.json"),
|
|
8371
|
+
devVarsFile: join10(rootDir, ".dev.vars"),
|
|
8372
|
+
gitignore: true
|
|
8373
|
+
}
|
|
8374
|
+
};
|
|
8375
|
+
return {
|
|
8376
|
+
cfg,
|
|
8377
|
+
profile: {
|
|
8378
|
+
name: profile.name,
|
|
8379
|
+
source: profile.source,
|
|
8380
|
+
file: profile.file
|
|
8381
|
+
},
|
|
8382
|
+
config: {
|
|
8383
|
+
path: configPath,
|
|
8384
|
+
status: loaded ? "loaded" : "absent",
|
|
8385
|
+
explicit: explicitConfig
|
|
8386
|
+
},
|
|
8387
|
+
platform: { value: platformValue, source: platformSource },
|
|
8388
|
+
app: { value: appValue, source: appSource },
|
|
8389
|
+
environment: {
|
|
8390
|
+
value: environmentValue,
|
|
8391
|
+
source: environmentSource
|
|
8392
|
+
},
|
|
8393
|
+
credentials: {
|
|
8394
|
+
developerTokenFile: tokenFile,
|
|
8395
|
+
scopedTokenFile
|
|
8396
|
+
}
|
|
8397
|
+
};
|
|
8398
|
+
}
|
|
8399
|
+
function clean3(value) {
|
|
8400
|
+
const normalized = value?.trim();
|
|
8401
|
+
return normalized || void 0;
|
|
8402
|
+
}
|
|
8403
|
+
|
|
8404
|
+
// src/context-command.ts
|
|
8405
|
+
async function contextCommand(parsed, deps = {}) {
|
|
8406
|
+
assertArgs(
|
|
8407
|
+
parsed,
|
|
8408
|
+
[
|
|
8409
|
+
"config",
|
|
8410
|
+
"context",
|
|
8411
|
+
"platform",
|
|
8412
|
+
"app",
|
|
8413
|
+
"env",
|
|
8414
|
+
"token",
|
|
8415
|
+
"json",
|
|
8416
|
+
"yes"
|
|
8417
|
+
],
|
|
8418
|
+
3
|
|
8419
|
+
);
|
|
8420
|
+
const action = parsed.positionals[1];
|
|
8421
|
+
const out = deps.stdout ?? console;
|
|
8422
|
+
if (action === "list") {
|
|
8423
|
+
const profiles = listOperatorProfiles();
|
|
8424
|
+
if (parsed.options.json === true) {
|
|
8425
|
+
out.log(JSON.stringify({ schemaVersion: 1, profiles }, null, 2));
|
|
8426
|
+
return;
|
|
8427
|
+
}
|
|
8428
|
+
if (profiles.length === 0) {
|
|
8429
|
+
out.log("No named operator contexts.");
|
|
8430
|
+
return;
|
|
8431
|
+
}
|
|
8432
|
+
out.log("name platform app environment");
|
|
8433
|
+
for (const profile of profiles) {
|
|
8434
|
+
out.log(
|
|
8435
|
+
`${profile.name} ${profile.platform} ${profile.app ?? ""} ${profile.environment ?? ""}`
|
|
8436
|
+
);
|
|
8437
|
+
}
|
|
8438
|
+
return;
|
|
8439
|
+
}
|
|
8440
|
+
if (action === "save") {
|
|
8441
|
+
if (parsed.options.token !== void 0) {
|
|
8442
|
+
throw new Error(
|
|
8443
|
+
"context save does not accept --token; contexts store scope metadata only"
|
|
8444
|
+
);
|
|
8445
|
+
}
|
|
8446
|
+
const name = requireName(parsed);
|
|
8447
|
+
const context2 = await resolveOperatorContext(parsed, {
|
|
8448
|
+
allowMissingConfig: true
|
|
8449
|
+
});
|
|
8450
|
+
const profile = {
|
|
8451
|
+
platform: context2.platform.value,
|
|
8452
|
+
...context2.app.value ? { app: context2.app.value } : {},
|
|
8453
|
+
...context2.environment.value ? { environment: context2.environment.value } : {}
|
|
8454
|
+
};
|
|
8455
|
+
saveOperatorProfile(name, profile, context2.profile.file);
|
|
8456
|
+
if (parsed.options.json === true) {
|
|
8457
|
+
out.log(JSON.stringify({ schemaVersion: 1, name, ...profile }, null, 2));
|
|
8458
|
+
} else {
|
|
8459
|
+
out.log(`saved operator context "${name}" (${profile.platform})`);
|
|
8460
|
+
}
|
|
8461
|
+
return;
|
|
8462
|
+
}
|
|
8463
|
+
if (action === "remove") {
|
|
8464
|
+
const name = requireName(parsed);
|
|
8465
|
+
if (parsed.options.yes !== true) {
|
|
8466
|
+
throw new Error(`context remove "${name}" requires --yes`);
|
|
8467
|
+
}
|
|
8468
|
+
const removed = removeOperatorProfile(name);
|
|
8469
|
+
const result2 = {
|
|
8470
|
+
schemaVersion: 1,
|
|
8471
|
+
name,
|
|
8472
|
+
removed,
|
|
8473
|
+
credentialCachesRemoved: false,
|
|
8474
|
+
grantsRevoked: false
|
|
8475
|
+
};
|
|
8476
|
+
if (parsed.options.json === true) out.log(JSON.stringify(result2, null, 2));
|
|
8477
|
+
else {
|
|
8478
|
+
const status = removed ? `removed operator context "${name}"` : `operator context "${name}" was already absent`;
|
|
8479
|
+
out.log(`${status}; credential caches were not deleted or revoked`);
|
|
8480
|
+
}
|
|
8481
|
+
return;
|
|
8482
|
+
}
|
|
8483
|
+
if (action !== "show") {
|
|
8484
|
+
throw new Error(
|
|
8485
|
+
`unknown context action "${action ?? ""}". Try show|list|save|remove.`
|
|
8486
|
+
);
|
|
8487
|
+
}
|
|
8488
|
+
const context = await resolveOperatorContext(parsed, {
|
|
8489
|
+
allowMissingConfig: true,
|
|
8490
|
+
defaultEnvironment: "prod"
|
|
8491
|
+
});
|
|
8492
|
+
const token = developerTokenStatus(context, parsed);
|
|
8493
|
+
const result = {
|
|
8494
|
+
schemaVersion: 1,
|
|
8495
|
+
profile: context.profile,
|
|
8496
|
+
config: context.config,
|
|
8497
|
+
platform: context.platform,
|
|
8498
|
+
app: context.app,
|
|
8499
|
+
environment: context.environment,
|
|
8500
|
+
authentication: {
|
|
8501
|
+
developerToken: token,
|
|
8502
|
+
scopedTokenCacheFile: context.credentials.scopedTokenFile,
|
|
8503
|
+
handshakeStarted: false
|
|
8504
|
+
}
|
|
8505
|
+
};
|
|
8506
|
+
if (parsed.options.json === true) {
|
|
8507
|
+
out.log(JSON.stringify(result, null, 2));
|
|
8508
|
+
return;
|
|
8509
|
+
}
|
|
8510
|
+
out.log(`config: ${context.config.status} (${context.config.path})`);
|
|
8511
|
+
out.log(
|
|
8512
|
+
`context: ${context.profile.name ?? "(none)"} (${context.profile.source}; ${context.profile.file})`
|
|
8513
|
+
);
|
|
8514
|
+
out.log(
|
|
8515
|
+
`platform: ${context.platform.value} (${context.platform.source})`
|
|
8516
|
+
);
|
|
8517
|
+
out.log(
|
|
8518
|
+
`app: ${context.app.value ?? "(unresolved)"} (${context.app.source})`
|
|
8519
|
+
);
|
|
8520
|
+
out.log(
|
|
8521
|
+
`env: ${context.environment.value ?? "(unresolved)"} (${context.environment.source})`
|
|
8522
|
+
);
|
|
8523
|
+
out.log(
|
|
8524
|
+
`auth: ${token.source} (cache ${token.cacheStatus}: ${token.cacheFile})`
|
|
8525
|
+
);
|
|
8526
|
+
out.log("handshake: not started");
|
|
8527
|
+
}
|
|
8528
|
+
function requireName(parsed) {
|
|
8529
|
+
const name = parsed.positionals[2]?.trim();
|
|
8530
|
+
if (!name) {
|
|
8531
|
+
throw new Error(`context ${parsed.positionals[1]} needs a profile name`);
|
|
8532
|
+
}
|
|
8533
|
+
return name;
|
|
8534
|
+
}
|
|
8535
|
+
|
|
8138
8536
|
// src/discuss-actions.ts
|
|
8139
8537
|
var writeMutationId = (parsed) => stringOpt(parsed.options["mutation-id"]) ?? crypto.randomUUID();
|
|
8140
8538
|
async function request(ctx, method, path, body) {
|
|
@@ -8186,8 +8584,9 @@ async function discussGroups(ctx) {
|
|
|
8186
8584
|
}
|
|
8187
8585
|
async function discussList(ctx, parsed) {
|
|
8188
8586
|
const query = new URLSearchParams();
|
|
8587
|
+
const app = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
8588
|
+
if (app) query.set("app", app);
|
|
8189
8589
|
for (const [flag, param] of [
|
|
8190
|
-
["app", "app"],
|
|
8191
8590
|
["q", "q"],
|
|
8192
8591
|
["state", "state"],
|
|
8193
8592
|
["limit", "limit"],
|
|
@@ -8263,7 +8662,7 @@ function renderRead(ctx, topic, posts) {
|
|
|
8263
8662
|
}
|
|
8264
8663
|
}
|
|
8265
8664
|
async function discussPost(ctx, parsed) {
|
|
8266
|
-
const appId = stringOpt(parsed.options.app);
|
|
8665
|
+
const appId = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
8267
8666
|
if (!appId) throw new Error("discuss post needs --app <appId>");
|
|
8268
8667
|
const subject = stringOpt(parsed.options.subject);
|
|
8269
8668
|
if (!subject) throw new Error('discuss post needs --subject "\u2026"');
|
|
@@ -8295,7 +8694,7 @@ async function discussResolve(ctx, id, resolved, parsed) {
|
|
|
8295
8694
|
}
|
|
8296
8695
|
async function discussWho(ctx, parsed) {
|
|
8297
8696
|
const query = new URLSearchParams({ q: stringOpt(parsed.options.q) ?? "" });
|
|
8298
|
-
const app = stringOpt(parsed.options.app);
|
|
8697
|
+
const app = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
8299
8698
|
if (app) query.set("app", app);
|
|
8300
8699
|
const kinds = stringOpt(parsed.options.kinds);
|
|
8301
8700
|
if (kinds) query.set("kinds", kinds);
|
|
@@ -8399,14 +8798,14 @@ function jsonl(ctx, parsed, value) {
|
|
|
8399
8798
|
}
|
|
8400
8799
|
async function discussWatch(ctx, topicId, parsed) {
|
|
8401
8800
|
if (ctx.json && parsed.options.jsonl === true) throw new Error("--json and --jsonl cannot be combined");
|
|
8402
|
-
const sleep = ctx.sleep ?? ((ms) => new Promise((
|
|
8801
|
+
const sleep = ctx.sleep ?? ((ms) => new Promise((resolve12) => setTimeout(resolve12, ms)));
|
|
8403
8802
|
const now = ctx.now ?? Date.now;
|
|
8404
8803
|
const intervalMs = (numberOpt2(parsed, "interval", DEFAULT_INTERVAL_MS / 1e3) ?? DEFAULT_INTERVAL_MS / 1e3) * 1e3;
|
|
8405
8804
|
const timeoutSeconds = numberOpt2(parsed, "timeout");
|
|
8406
8805
|
const deadline = timeoutSeconds === void 0 ? void 0 : now() + timeoutSeconds * 1e3;
|
|
8407
8806
|
const by = stringOpt(parsed.options.by);
|
|
8408
8807
|
const self = stringOpt(parsed.options.self);
|
|
8409
|
-
const app = stringOpt(parsed.options.app);
|
|
8808
|
+
const app = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
8410
8809
|
let cursor = stringOpt(parsed.options.cursor);
|
|
8411
8810
|
let firstSuccess = true;
|
|
8412
8811
|
let consecutiveFailures = 0;
|
|
@@ -8551,14 +8950,19 @@ var ALLOWED = [
|
|
|
8551
8950
|
"timeout",
|
|
8552
8951
|
"cursor",
|
|
8553
8952
|
"jsonl",
|
|
8554
|
-
"mutation-id"
|
|
8953
|
+
"mutation-id",
|
|
8954
|
+
"platform",
|
|
8955
|
+
"context"
|
|
8555
8956
|
];
|
|
8556
8957
|
function requireId(id, action) {
|
|
8557
8958
|
if (!id) throw new Error(`"discuss ${action}" needs a topic id`);
|
|
8558
8959
|
return id;
|
|
8559
8960
|
}
|
|
8560
8961
|
async function buildContext(parsed, deps) {
|
|
8561
|
-
const
|
|
8962
|
+
const context = await resolveOperatorContext(parsed, {
|
|
8963
|
+
allowMissingConfig: true
|
|
8964
|
+
});
|
|
8965
|
+
const { cfg } = context;
|
|
8562
8966
|
const doFetch = deps.fetch ?? fetch;
|
|
8563
8967
|
const out = deps.stdout ?? console;
|
|
8564
8968
|
const token = await getDeveloperToken(
|
|
@@ -8572,7 +8976,16 @@ async function buildContext(parsed, deps) {
|
|
|
8572
8976
|
doFetch,
|
|
8573
8977
|
out
|
|
8574
8978
|
);
|
|
8575
|
-
return {
|
|
8979
|
+
return {
|
|
8980
|
+
platformUrl: cfg.platformUrl,
|
|
8981
|
+
token,
|
|
8982
|
+
doFetch,
|
|
8983
|
+
out,
|
|
8984
|
+
json: parsed.options.json === true,
|
|
8985
|
+
// Preserve Discussions' existing cross-project default for config-backed
|
|
8986
|
+
// use while allowing remote agents to scope via ODLA_APP_ID or a profile.
|
|
8987
|
+
appId: context.app.source === "environment" || context.app.source === "profile" ? context.app.value ?? void 0 : void 0
|
|
8988
|
+
};
|
|
8576
8989
|
}
|
|
8577
8990
|
async function discussCommand(parsed, deps = {}) {
|
|
8578
8991
|
assertArgs(parsed, ALLOWED, 3);
|
|
@@ -8684,7 +9097,7 @@ async function pmList(ctx, entity, parsed) {
|
|
|
8684
9097
|
assignee: "assigneeId",
|
|
8685
9098
|
decision: "decisionId"
|
|
8686
9099
|
};
|
|
8687
|
-
const app = stringOpt(parsed.options.app);
|
|
9100
|
+
const app = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
8688
9101
|
if (app) q.set("app", app);
|
|
8689
9102
|
for (const [flag, param] of Object.entries(filters)) {
|
|
8690
9103
|
const v = stringOpt(parsed.options[flag]);
|
|
@@ -8703,7 +9116,7 @@ async function pmList(ctx, entity, parsed) {
|
|
|
8703
9116
|
});
|
|
8704
9117
|
}
|
|
8705
9118
|
async function pmAdd(ctx, entity, parsed) {
|
|
8706
|
-
const appId = stringOpt(parsed.options.app);
|
|
9119
|
+
const appId = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
8707
9120
|
if (!appId) throw new Error("pm add needs --app <appId>");
|
|
8708
9121
|
const input = collectEntityFields(entity, parsed, false);
|
|
8709
9122
|
if (!input.title) throw new Error("pm add needs --title <title>");
|
|
@@ -8754,7 +9167,7 @@ async function allRecords(ctx, entity, appId) {
|
|
|
8754
9167
|
}
|
|
8755
9168
|
}
|
|
8756
9169
|
async function pmHandoff(ctx, parsed) {
|
|
8757
|
-
const appId = stringOpt(parsed.options.app);
|
|
9170
|
+
const appId = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
8758
9171
|
if (!appId) throw new Error("pm handoff needs --app <appId>");
|
|
8759
9172
|
const [goals, tasks, bugs] = await Promise.all([
|
|
8760
9173
|
allRecords(ctx, "goal", appId),
|
|
@@ -8849,14 +9262,19 @@ var ALLOWED2 = [
|
|
|
8849
9262
|
"limit",
|
|
8850
9263
|
"offset",
|
|
8851
9264
|
"q",
|
|
8852
|
-
"mutation-id"
|
|
9265
|
+
"mutation-id",
|
|
9266
|
+
"platform",
|
|
9267
|
+
"context"
|
|
8853
9268
|
];
|
|
8854
9269
|
function requireId2(id, action) {
|
|
8855
9270
|
if (!id) throw new Error(`"pm ... ${action}" needs an item id`);
|
|
8856
9271
|
return id;
|
|
8857
9272
|
}
|
|
8858
9273
|
async function buildContext2(parsed, deps) {
|
|
8859
|
-
const
|
|
9274
|
+
const context = await resolveOperatorContext(parsed, {
|
|
9275
|
+
allowMissingConfig: true
|
|
9276
|
+
});
|
|
9277
|
+
const { cfg } = context;
|
|
8860
9278
|
const doFetch = deps.fetch ?? fetch;
|
|
8861
9279
|
const out = deps.stdout ?? console;
|
|
8862
9280
|
const token = await getDeveloperToken(
|
|
@@ -8865,7 +9283,16 @@ async function buildContext2(parsed, deps) {
|
|
|
8865
9283
|
doFetch,
|
|
8866
9284
|
out
|
|
8867
9285
|
);
|
|
8868
|
-
return {
|
|
9286
|
+
return {
|
|
9287
|
+
platformUrl: cfg.platformUrl,
|
|
9288
|
+
token,
|
|
9289
|
+
doFetch,
|
|
9290
|
+
out,
|
|
9291
|
+
json: parsed.options.json === true,
|
|
9292
|
+
// Preserve PM's existing cross-project default when a config is present;
|
|
9293
|
+
// only an explicit remote-agent environment or profile selects an app.
|
|
9294
|
+
appId: context.app.source === "environment" || context.app.source === "profile" ? context.app.value ?? void 0 : void 0
|
|
9295
|
+
};
|
|
8869
9296
|
}
|
|
8870
9297
|
async function pmCommand(parsed, deps = {}) {
|
|
8871
9298
|
const word = parsed.positionals[1] ?? "";
|
|
@@ -8914,6 +9341,20 @@ function statusVerdict(reads) {
|
|
|
8914
9341
|
sourceHttp(reasons, "canary", reads.canary);
|
|
8915
9342
|
sourceHttp(reasons, "collector", reads.collector);
|
|
8916
9343
|
sourceHttp(reasons, "provider", reads.provider);
|
|
9344
|
+
if (reads.providerCapacity) {
|
|
9345
|
+
sourceHttp(
|
|
9346
|
+
reasons,
|
|
9347
|
+
"provider",
|
|
9348
|
+
reads.providerCapacity,
|
|
9349
|
+
"provider_capacity_"
|
|
9350
|
+
);
|
|
9351
|
+
}
|
|
9352
|
+
sourceHttp(
|
|
9353
|
+
reasons,
|
|
9354
|
+
"provider",
|
|
9355
|
+
reads.providerReconciliation,
|
|
9356
|
+
"provider_reconciliation_"
|
|
9357
|
+
);
|
|
8917
9358
|
sourceHttp(reasons, "provider", reads.providerHistory, "provider_history_");
|
|
8918
9359
|
const liveStatus = String(reads.liveSync.body.status ?? "");
|
|
8919
9360
|
if (liveStatus === "partial") {
|
|
@@ -8986,6 +9427,17 @@ function statusVerdict(reads) {
|
|
|
8986
9427
|
severity: "degraded"
|
|
8987
9428
|
});
|
|
8988
9429
|
}
|
|
9430
|
+
const reconciliationStatus = String(
|
|
9431
|
+
reads.providerReconciliation.body.status ?? ""
|
|
9432
|
+
);
|
|
9433
|
+
if (reads.providerReconciliation.httpStatus >= 200 && reads.providerReconciliation.httpStatus < 300 && reconciliationStatus && reconciliationStatus !== "within_budget") {
|
|
9434
|
+
const reconciliationReason = reads.providerReconciliation.body.reason;
|
|
9435
|
+
reasons.push({
|
|
9436
|
+
source: "provider",
|
|
9437
|
+
code: reconciliationStatus === "outside_budget" ? typeof reconciliationReason === "string" && reconciliationReason ? reconciliationReason : "request_coverage_outside_budget" : `request_reconciliation_${reconciliationStatus}`,
|
|
9438
|
+
severity: "degraded"
|
|
9439
|
+
});
|
|
9440
|
+
}
|
|
8989
9441
|
const providerHistoryStatus = String(reads.providerHistory.body.status ?? "");
|
|
8990
9442
|
if (providerHistoryStatus && providerHistoryStatus !== "observed" && providerHistoryStatus !== "no_data") {
|
|
8991
9443
|
reasons.push({
|
|
@@ -9022,11 +9474,138 @@ function sourceHttp(reasons, source, read3, prefix = "") {
|
|
|
9022
9474
|
});
|
|
9023
9475
|
}
|
|
9024
9476
|
|
|
9477
|
+
// src/o11y-output.ts
|
|
9478
|
+
function printO11yStatus(status, out) {
|
|
9479
|
+
out.log(
|
|
9480
|
+
`o11y status ${status.scope.appId}/${status.scope.env} (${status.scope.minutes}m)`
|
|
9481
|
+
);
|
|
9482
|
+
const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record6) : [];
|
|
9483
|
+
const requests = routes.reduce(
|
|
9484
|
+
(total, row) => total + numeric3(row.requests),
|
|
9485
|
+
0
|
|
9486
|
+
);
|
|
9487
|
+
const errors = routes.reduce(
|
|
9488
|
+
(total, row) => total + numeric3(row.errors),
|
|
9489
|
+
0
|
|
9490
|
+
);
|
|
9491
|
+
out.log(
|
|
9492
|
+
`application ${status.application.httpStatus} ${requests} requests ${errors} errors`
|
|
9493
|
+
);
|
|
9494
|
+
const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(record6) : [];
|
|
9495
|
+
out.log(
|
|
9496
|
+
`application-versions ${status.applicationVersions.httpStatus} ${versions.length ? versions.slice(0, 5).map(
|
|
9497
|
+
(row) => `${String(row.value || "(unattributed)")}:${numeric3(row.requests)}`
|
|
9498
|
+
).join(", ") : "none observed"}`
|
|
9499
|
+
);
|
|
9500
|
+
out.log(liveSyncLine(status.liveSync));
|
|
9501
|
+
const canaryDurations = record6(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
|
|
9502
|
+
out.log(
|
|
9503
|
+
`canary ${status.canary.httpStatus} ${String(status.canary.body.status ?? status.canary.body.error ?? "unavailable")} ${optionalNumeric(canaryDurations.publishToVisibleMs)} publish-to-visible`
|
|
9504
|
+
);
|
|
9505
|
+
const collectorIngest = record6(status.collector.body.ingest) ? status.collector.body.ingest : {};
|
|
9506
|
+
const collectorStorage = record6(collectorIngest.storage) ? collectorIngest.storage : {};
|
|
9507
|
+
out.log(
|
|
9508
|
+
`collector ${status.collector.httpStatus} ${String(status.collector.body.status ?? status.collector.body.error ?? "unavailable")} ${numeric3(collectorStorage.affectedPoints)} affected points`
|
|
9509
|
+
);
|
|
9510
|
+
const providerMetrics = record6(status.provider.body.metrics) ? status.provider.body.metrics : {};
|
|
9511
|
+
const providerCapacity = record6(status.provider.body.capacity) ? status.provider.body.capacity : {};
|
|
9512
|
+
const workerMemory = record6(providerCapacity.memory) ? providerCapacity.memory : {};
|
|
9513
|
+
out.log(
|
|
9514
|
+
`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`
|
|
9515
|
+
);
|
|
9516
|
+
for (const line of providerCapacityLines(status.providerCapacity)) {
|
|
9517
|
+
out.log(line);
|
|
9518
|
+
}
|
|
9519
|
+
const coverage = record6(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
|
|
9520
|
+
const coverageCounts = record6(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
|
|
9521
|
+
const coverageBudget = record6(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
|
|
9522
|
+
out.log(
|
|
9523
|
+
`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`
|
|
9524
|
+
);
|
|
9525
|
+
const providerPoints = Array.isArray(status.providerHistory.body.points) ? status.providerHistory.body.points.length : 0;
|
|
9526
|
+
const providerFreshness = record6(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
|
|
9527
|
+
out.log(
|
|
9528
|
+
`cloudflare-history ${status.providerHistory.httpStatus} ${String(status.providerHistory.body.status ?? status.providerHistory.body.error ?? "unavailable")} ${providerPoints} snapshots ${optionalAge(providerFreshness.ageMs)} old`
|
|
9529
|
+
);
|
|
9530
|
+
out.log(
|
|
9531
|
+
`verdict ${status.verdict.status} ${status.verdict.reasons.map((reason) => `${reason.source}:${reason.code}`).join(", ") || "all required signals healthy"}`
|
|
9532
|
+
);
|
|
9533
|
+
}
|
|
9534
|
+
function providerCapacityLines(read3) {
|
|
9535
|
+
const resources = record6(read3.body.resources) ? read3.body.resources : {};
|
|
9536
|
+
const durableObjects = record6(resources.durableObjects) ? resources.durableObjects : {};
|
|
9537
|
+
const periodic = record6(durableObjects.periodic) ? durableObjects.periodic : {};
|
|
9538
|
+
const storage = record6(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
|
|
9539
|
+
const d1 = record6(resources.d1) ? resources.d1 : {};
|
|
9540
|
+
const d1Activity = record6(d1.activity) ? d1.activity : {};
|
|
9541
|
+
const d1Storage = record6(d1.storage) ? d1.storage : {};
|
|
9542
|
+
const d1Latency = record6(d1Activity.latency) ? d1Activity.latency : {};
|
|
9543
|
+
const r2 = record6(resources.r2) ? resources.r2 : {};
|
|
9544
|
+
const r2Operations = record6(r2.operations) ? r2.operations : {};
|
|
9545
|
+
const r2Storage = record6(r2.storage) ? r2.storage : {};
|
|
9546
|
+
const status = String(
|
|
9547
|
+
read3.body.status ?? read3.body.error ?? "unavailable"
|
|
9548
|
+
);
|
|
9549
|
+
return [
|
|
9550
|
+
`cloudflare-capacity ${read3.httpStatus} ${status} ${optionalBytes(storage.storedBytes)} SQLite DO storage ${optionalCount(periodic.activeWebsocketConnectionsMax, "active socket max")} ${optionalAgeSeconds(storage.ageSeconds)} storage evidence age`,
|
|
9551
|
+
`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`,
|
|
9552
|
+
`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`
|
|
9553
|
+
];
|
|
9554
|
+
}
|
|
9555
|
+
function liveSyncLine(read3) {
|
|
9556
|
+
const performance = record6(read3.body.performance) ? read3.body.performance : {};
|
|
9557
|
+
const commitToSend = record6(performance.commitToSend) ? performance.commitToSend : {};
|
|
9558
|
+
return `live-sync ${read3.httpStatus} ${String(read3.body.status ?? read3.body.error ?? "unavailable")} ${numeric3(read3.body.activeConnections)} active ${optionalNumeric(commitToSend.p95)} commit-to-send p95 ${numeric3(performance.sendFailures)} send failures`;
|
|
9559
|
+
}
|
|
9560
|
+
function record6(value) {
|
|
9561
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
9562
|
+
}
|
|
9563
|
+
function numeric3(value) {
|
|
9564
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
9565
|
+
}
|
|
9566
|
+
function optionalNumeric(value) {
|
|
9567
|
+
return typeof value === "number" && Number.isFinite(value) ? `${value} ms` : "\u2014";
|
|
9568
|
+
}
|
|
9569
|
+
function optionalAge(value) {
|
|
9570
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return "unknown";
|
|
9571
|
+
return `${Math.max(0, Math.round(value / 1e3))}s`;
|
|
9572
|
+
}
|
|
9573
|
+
function optionalPercent(value) {
|
|
9574
|
+
return typeof value === "number" && Number.isFinite(value) ? `${Math.round(value * 100)}%` : "\u2014";
|
|
9575
|
+
}
|
|
9576
|
+
function optionalBytes(value) {
|
|
9577
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return "\u2014";
|
|
9578
|
+
if (value >= 1024 * 1024 * 1024) {
|
|
9579
|
+
return `${(value / (1024 * 1024 * 1024)).toFixed(2)} GiB`;
|
|
9580
|
+
}
|
|
9581
|
+
if (value >= 1024 * 1024) {
|
|
9582
|
+
return `${(value / (1024 * 1024)).toFixed(1)} MiB`;
|
|
9583
|
+
}
|
|
9584
|
+
if (value >= 1024) return `${(value / 1024).toFixed(1)} KiB`;
|
|
9585
|
+
return `${Math.round(value)} B`;
|
|
9586
|
+
}
|
|
9587
|
+
function optionalCount(value, unit) {
|
|
9588
|
+
return typeof value === "number" && Number.isFinite(value) ? `${value} ${unit}` : `\u2014 ${unit}`;
|
|
9589
|
+
}
|
|
9590
|
+
function optionalAgeSeconds(value) {
|
|
9591
|
+
return typeof value === "number" && Number.isFinite(value) ? `${Math.max(0, Math.round(value))}s` : "unknown";
|
|
9592
|
+
}
|
|
9593
|
+
|
|
9025
9594
|
// src/o11y-command.ts
|
|
9026
9595
|
async function o11yCommand(parsed, deps = {}) {
|
|
9027
9596
|
assertArgs(
|
|
9028
9597
|
parsed,
|
|
9029
|
-
[
|
|
9598
|
+
[
|
|
9599
|
+
"config",
|
|
9600
|
+
"context",
|
|
9601
|
+
"platform",
|
|
9602
|
+
"token",
|
|
9603
|
+
"email",
|
|
9604
|
+
"json",
|
|
9605
|
+
"app",
|
|
9606
|
+
"env",
|
|
9607
|
+
"minutes"
|
|
9608
|
+
],
|
|
9030
9609
|
2
|
|
9031
9610
|
);
|
|
9032
9611
|
const action = parsed.positionals[1];
|
|
@@ -9038,9 +9617,12 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
9038
9617
|
const minutes = statusMinutes(
|
|
9039
9618
|
numberOpt(parsed.options.minutes, "--minutes") ?? 60
|
|
9040
9619
|
);
|
|
9041
|
-
const
|
|
9042
|
-
|
|
9043
|
-
|
|
9620
|
+
const context = await resolveOperatorContext(parsed, {
|
|
9621
|
+
allowMissingConfig: true,
|
|
9622
|
+
defaultEnvironment: "prod",
|
|
9623
|
+
requireApp: true
|
|
9624
|
+
});
|
|
9625
|
+
const cfg = context.cfg;
|
|
9044
9626
|
const doFetch = deps.fetch ?? fetch;
|
|
9045
9627
|
const out = deps.stdout ?? console;
|
|
9046
9628
|
const token = await getDeveloperToken(
|
|
@@ -9054,13 +9636,30 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
9054
9636
|
doFetch,
|
|
9055
9637
|
out
|
|
9056
9638
|
);
|
|
9057
|
-
const appId =
|
|
9058
|
-
const env =
|
|
9639
|
+
const appId = context.app.value;
|
|
9640
|
+
const env = context.environment.value;
|
|
9059
9641
|
const base = `${cfg.platformUrl}/o11y/${encodeURIComponent(appId)}`;
|
|
9060
9642
|
const query = new URLSearchParams({ env, minutes: String(minutes) });
|
|
9061
9643
|
const headers = { authorization: `Bearer ${token}` };
|
|
9062
|
-
const
|
|
9644
|
+
const versionsQuery = new URLSearchParams({
|
|
9645
|
+
env,
|
|
9646
|
+
minutes: String(minutes),
|
|
9647
|
+
dimension: "version",
|
|
9648
|
+
metric: "requests"
|
|
9649
|
+
});
|
|
9650
|
+
const [
|
|
9651
|
+
application,
|
|
9652
|
+
applicationVersions,
|
|
9653
|
+
liveSync,
|
|
9654
|
+
canary,
|
|
9655
|
+
collector,
|
|
9656
|
+
provider,
|
|
9657
|
+
providerCapacity,
|
|
9658
|
+
providerReconciliation,
|
|
9659
|
+
providerHistory
|
|
9660
|
+
] = await Promise.all([
|
|
9063
9661
|
read2(`${base}/metrics/red?${query}`, headers, doFetch),
|
|
9662
|
+
read2(`${base}/explore?${versionsQuery}`, headers, doFetch),
|
|
9064
9663
|
read2(
|
|
9065
9664
|
`${base}/live-sync/health?${query}`,
|
|
9066
9665
|
headers,
|
|
@@ -9073,6 +9672,19 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
9073
9672
|
),
|
|
9074
9673
|
read2(`${base}/self-health?${query}`, headers, doFetch),
|
|
9075
9674
|
read2(`${base}/provider/cloudflare?${query}`, headers, doFetch),
|
|
9675
|
+
appId === "odla-db" ? read2(`${base}/provider/cloudflare/capacity`, headers, doFetch) : Promise.resolve({
|
|
9676
|
+
httpStatus: 204,
|
|
9677
|
+
body: {
|
|
9678
|
+
status: "not_applicable",
|
|
9679
|
+
scope: "cloudflare_account",
|
|
9680
|
+
reason: "account-scoped Durable Object capacity is exposed only through odla-db"
|
|
9681
|
+
}
|
|
9682
|
+
}),
|
|
9683
|
+
read2(
|
|
9684
|
+
`${base}/provider/cloudflare/reconciliation?${query}`,
|
|
9685
|
+
headers,
|
|
9686
|
+
doFetch
|
|
9687
|
+
),
|
|
9076
9688
|
read2(`${base}/provider/cloudflare/history?${query}`, headers, doFetch)
|
|
9077
9689
|
]);
|
|
9078
9690
|
const verdict = statusVerdict({
|
|
@@ -9081,25 +9693,30 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
9081
9693
|
canary,
|
|
9082
9694
|
collector,
|
|
9083
9695
|
provider,
|
|
9696
|
+
providerCapacity,
|
|
9697
|
+
providerReconciliation,
|
|
9084
9698
|
providerHistory
|
|
9085
9699
|
});
|
|
9086
9700
|
const status = {
|
|
9087
|
-
schemaVersion:
|
|
9701
|
+
schemaVersion: 9,
|
|
9088
9702
|
scope: { appId, env, minutes },
|
|
9089
9703
|
observedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9090
9704
|
verdict,
|
|
9091
9705
|
application,
|
|
9706
|
+
applicationVersions,
|
|
9092
9707
|
liveSync,
|
|
9093
9708
|
canary,
|
|
9094
9709
|
collector,
|
|
9095
9710
|
provider,
|
|
9711
|
+
providerCapacity,
|
|
9712
|
+
providerReconciliation,
|
|
9096
9713
|
providerHistory
|
|
9097
9714
|
};
|
|
9098
9715
|
if (parsed.options.json === true) {
|
|
9099
9716
|
out.log(JSON.stringify(status, null, 2));
|
|
9100
9717
|
return;
|
|
9101
9718
|
}
|
|
9102
|
-
|
|
9719
|
+
printO11yStatus(status, out);
|
|
9103
9720
|
}
|
|
9104
9721
|
function statusMinutes(value) {
|
|
9105
9722
|
if (!Number.isSafeInteger(value) || value < 1 || value > 7 * 24 * 60) {
|
|
@@ -9121,71 +9738,12 @@ async function read2(url, headers, doFetch) {
|
|
|
9121
9738
|
}
|
|
9122
9739
|
return { httpStatus: response2.status, body };
|
|
9123
9740
|
}
|
|
9124
|
-
function printStatus2(status, out) {
|
|
9125
|
-
out.log(
|
|
9126
|
-
`o11y status ${status.scope.appId}/${status.scope.env} (${status.scope.minutes}m)`
|
|
9127
|
-
);
|
|
9128
|
-
const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record6) : [];
|
|
9129
|
-
const requests = routes.reduce(
|
|
9130
|
-
(total, row) => total + numeric3(row.requests),
|
|
9131
|
-
0
|
|
9132
|
-
);
|
|
9133
|
-
const errors = routes.reduce(
|
|
9134
|
-
(total, row) => total + numeric3(row.errors),
|
|
9135
|
-
0
|
|
9136
|
-
);
|
|
9137
|
-
out.log(
|
|
9138
|
-
`application ${status.application.httpStatus} ${requests} requests ${errors} errors`
|
|
9139
|
-
);
|
|
9140
|
-
out.log(
|
|
9141
|
-
liveSyncLine(status.liveSync)
|
|
9142
|
-
);
|
|
9143
|
-
const canaryDurations = record6(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
|
|
9144
|
-
out.log(
|
|
9145
|
-
`canary ${status.canary.httpStatus} ${String(status.canary.body.status ?? status.canary.body.error ?? "unavailable")} ${optionalNumeric(canaryDurations.publishToVisibleMs)} publish-to-visible`
|
|
9146
|
-
);
|
|
9147
|
-
const collectorIngest = record6(status.collector.body.ingest) ? status.collector.body.ingest : {};
|
|
9148
|
-
const collectorStorage = record6(collectorIngest.storage) ? collectorIngest.storage : {};
|
|
9149
|
-
out.log(
|
|
9150
|
-
`collector ${status.collector.httpStatus} ${String(status.collector.body.status ?? status.collector.body.error ?? "unavailable")} ${numeric3(collectorStorage.affectedPoints)} affected points`
|
|
9151
|
-
);
|
|
9152
|
-
const providerMetrics = record6(status.provider.body.metrics) ? status.provider.body.metrics : {};
|
|
9153
|
-
out.log(
|
|
9154
|
-
`cloudflare ${status.provider.httpStatus} ${String(status.provider.body.status ?? status.provider.body.error ?? "unavailable")} ${numeric3(providerMetrics.requests)} invocations ${numeric3(providerMetrics.errors)} runtime errors`
|
|
9155
|
-
);
|
|
9156
|
-
const providerPoints = Array.isArray(status.providerHistory.body.points) ? status.providerHistory.body.points.length : 0;
|
|
9157
|
-
const providerFreshness = record6(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
|
|
9158
|
-
out.log(
|
|
9159
|
-
`cloudflare-history ${status.providerHistory.httpStatus} ${String(status.providerHistory.body.status ?? status.providerHistory.body.error ?? "unavailable")} ${providerPoints} snapshots ${optionalAge(providerFreshness.ageMs)} old`
|
|
9160
|
-
);
|
|
9161
|
-
out.log(
|
|
9162
|
-
`verdict ${status.verdict.status} ${status.verdict.reasons.map((reason) => `${reason.source}:${reason.code}`).join(", ") || "all required signals healthy"}`
|
|
9163
|
-
);
|
|
9164
|
-
}
|
|
9165
|
-
function liveSyncLine(read3) {
|
|
9166
|
-
const performance = record6(read3.body.performance) ? read3.body.performance : {};
|
|
9167
|
-
const commitToSend = record6(performance.commitToSend) ? performance.commitToSend : {};
|
|
9168
|
-
return `live-sync ${read3.httpStatus} ${String(read3.body.status ?? read3.body.error ?? "unavailable")} ${numeric3(read3.body.activeConnections)} active ${optionalNumeric(commitToSend.p95)} commit-to-send p95 ${numeric3(performance.sendFailures)} send failures`;
|
|
9169
|
-
}
|
|
9170
|
-
function record6(value) {
|
|
9171
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
9172
|
-
}
|
|
9173
|
-
function numeric3(value) {
|
|
9174
|
-
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
9175
|
-
}
|
|
9176
|
-
function optionalNumeric(value) {
|
|
9177
|
-
return typeof value === "number" && Number.isFinite(value) ? `${value} ms` : "\u2014";
|
|
9178
|
-
}
|
|
9179
|
-
function optionalAge(value) {
|
|
9180
|
-
if (typeof value !== "number" || !Number.isFinite(value)) return "unknown";
|
|
9181
|
-
return `${Math.max(0, Math.round(value / 1e3))}s`;
|
|
9182
|
-
}
|
|
9183
9741
|
|
|
9184
9742
|
// src/record.ts
|
|
9185
9743
|
import { appendFileSync } from "fs";
|
|
9186
|
-
import
|
|
9744
|
+
import process12 from "process";
|
|
9187
9745
|
function recordInvocation(parsed) {
|
|
9188
|
-
const file =
|
|
9746
|
+
const file = process12.env.ODLA_CLI_RECORD;
|
|
9189
9747
|
if (!file) return;
|
|
9190
9748
|
try {
|
|
9191
9749
|
const entry = {
|
|
@@ -9199,13 +9757,8 @@ function recordInvocation(parsed) {
|
|
|
9199
9757
|
}
|
|
9200
9758
|
}
|
|
9201
9759
|
|
|
9202
|
-
// src/runbook-command.ts
|
|
9203
|
-
import { existsSync as existsSync10 } from "fs";
|
|
9204
|
-
import { join as join12, resolve as resolve10 } from "path";
|
|
9205
|
-
import process11 from "process";
|
|
9206
|
-
|
|
9207
9760
|
// src/runbook-actions.ts
|
|
9208
|
-
import { readFileSync as
|
|
9761
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
9209
9762
|
|
|
9210
9763
|
// src/runbook-requires.ts
|
|
9211
9764
|
var SPEC = /^(@?[\w./-]+?)@(\d+\.\d+\.\d+(?:[\w.-]*)?)$/;
|
|
@@ -9290,7 +9843,7 @@ async function bySlug(ctx, slug) {
|
|
|
9290
9843
|
function readBody(file, inline) {
|
|
9291
9844
|
if (inline !== void 0) return inline;
|
|
9292
9845
|
if (file === void 0) throw new Error("supply the new text with --file <path>, --file - (stdin), or --body");
|
|
9293
|
-
return
|
|
9846
|
+
return readFileSync9(file === "-" ? 0 : file, "utf8");
|
|
9294
9847
|
}
|
|
9295
9848
|
var stamp = (ms) => ms ? new Date(ms).toISOString().slice(0, 16).replace("T", " ") : "";
|
|
9296
9849
|
async function runbookList(ctx, all, query) {
|
|
@@ -9377,8 +9930,8 @@ async function runbookRemove(ctx, slug) {
|
|
|
9377
9930
|
}
|
|
9378
9931
|
|
|
9379
9932
|
// src/runbook-import.ts
|
|
9380
|
-
import { readFileSync as
|
|
9381
|
-
import { basename as basename2, join as
|
|
9933
|
+
import { readFileSync as readFileSync10, readdirSync as readdirSync2, statSync } from "fs";
|
|
9934
|
+
import { basename as basename2, join as join11 } from "path";
|
|
9382
9935
|
function parseRunbook(text, slug) {
|
|
9383
9936
|
let rest = text;
|
|
9384
9937
|
const meta = {};
|
|
@@ -9408,7 +9961,7 @@ function readRunbookDir(dir) {
|
|
|
9408
9961
|
if (!files.length) throw new Error(`no .md files in ${dir}`);
|
|
9409
9962
|
return files.map((file) => {
|
|
9410
9963
|
const slug = basename2(file, ".md");
|
|
9411
|
-
const parsed = parseRunbook(
|
|
9964
|
+
const parsed = parseRunbook(readFileSync10(join11(dir, file), "utf8"), slug);
|
|
9412
9965
|
return { file, slug, ...parsed, words: parsed.body.split(/\s+/).filter(Boolean).length };
|
|
9413
9966
|
});
|
|
9414
9967
|
}
|
|
@@ -9480,8 +10033,8 @@ async function upsert(ctx, r, visibility) {
|
|
|
9480
10033
|
|
|
9481
10034
|
// src/runbook-impact.ts
|
|
9482
10035
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
9483
|
-
import { existsSync as
|
|
9484
|
-
import { join as
|
|
10036
|
+
import { existsSync as existsSync11, readFileSync as readFileSync11 } from "fs";
|
|
10037
|
+
import { join as join12 } from "path";
|
|
9485
10038
|
|
|
9486
10039
|
// src/runbook-impact-scan.ts
|
|
9487
10040
|
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$]*)/;
|
|
@@ -9650,10 +10203,10 @@ ${body.split("\n").map((line) => `+${line}`).join("\n")}
|
|
|
9650
10203
|
}
|
|
9651
10204
|
function manifestLabeller(root) {
|
|
9652
10205
|
return (workspace) => {
|
|
9653
|
-
const manifest =
|
|
9654
|
-
if (!
|
|
10206
|
+
const manifest = join12(root, workspace, "package.json");
|
|
10207
|
+
if (!existsSync11(manifest)) return void 0;
|
|
9655
10208
|
try {
|
|
9656
|
-
const name = JSON.parse(
|
|
10209
|
+
const name = JSON.parse(readFileSync11(manifest, "utf8")).name;
|
|
9657
10210
|
return typeof name === "string" ? name : void 0;
|
|
9658
10211
|
} catch {
|
|
9659
10212
|
return void 0;
|
|
@@ -9720,7 +10273,7 @@ function report3(ctx, impacts) {
|
|
|
9720
10273
|
async function runbookImpact(ctx, options, deps = {}) {
|
|
9721
10274
|
const cwd = deps.cwd ?? process.cwd();
|
|
9722
10275
|
const runGit = deps.runGit ?? gitRunner(cwd);
|
|
9723
|
-
const read3 = deps.readRepoFile ?? ((path) =>
|
|
10276
|
+
const read3 = deps.readRepoFile ?? ((path) => readFileSync11(join12(cwd, path), "utf8"));
|
|
9724
10277
|
const surfaces = changedSurfaces(collectDiff(runGit, options.base, read3), manifestLabeller(cwd));
|
|
9725
10278
|
if (!surfaces.length) {
|
|
9726
10279
|
return ctx.out.log(
|
|
@@ -9853,12 +10406,12 @@ async function runbookComment(ctx, slug, body) {
|
|
|
9853
10406
|
|
|
9854
10407
|
// src/runbook-editor.ts
|
|
9855
10408
|
import { spawnSync } from "child_process";
|
|
9856
|
-
import { mkdtempSync, readFileSync as
|
|
10409
|
+
import { mkdtempSync, readFileSync as readFileSync12, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
9857
10410
|
import { tmpdir as tmpdir4 } from "os";
|
|
9858
|
-
import { join as
|
|
9859
|
-
import
|
|
10411
|
+
import { join as join13 } from "path";
|
|
10412
|
+
import process13 from "process";
|
|
9860
10413
|
var EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
|
|
9861
|
-
function resolveEditor(env =
|
|
10414
|
+
function resolveEditor(env = process13.env) {
|
|
9862
10415
|
for (const name of EDITOR_ENV) {
|
|
9863
10416
|
const value = env[name];
|
|
9864
10417
|
if (value && value.trim()) return value.trim();
|
|
@@ -9872,8 +10425,8 @@ function defaultRun(command, path) {
|
|
|
9872
10425
|
return result.status ?? 0;
|
|
9873
10426
|
}
|
|
9874
10427
|
function editText(initial, slug, deps = {}) {
|
|
9875
|
-
const env = deps.env ??
|
|
9876
|
-
const interactive = deps.interactive ?? (() => Boolean(
|
|
10428
|
+
const env = deps.env ?? process13.env;
|
|
10429
|
+
const interactive = deps.interactive ?? (() => Boolean(process13.stdin.isTTY));
|
|
9877
10430
|
const editor = resolveEditor(env);
|
|
9878
10431
|
if (!editor)
|
|
9879
10432
|
throw new Error(
|
|
@@ -9881,13 +10434,13 @@ function editText(initial, slug, deps = {}) {
|
|
|
9881
10434
|
);
|
|
9882
10435
|
if (!interactive())
|
|
9883
10436
|
throw new Error(`cannot open an editor without a terminal \u2014 pass --file <path> or --body "\u2026" instead`);
|
|
9884
|
-
const dir = mkdtempSync(
|
|
9885
|
-
const file =
|
|
10437
|
+
const dir = mkdtempSync(join13(tmpdir4(), "odla-runbook-"));
|
|
10438
|
+
const file = join13(dir, `${slug}.md`);
|
|
9886
10439
|
try {
|
|
9887
10440
|
writeFileSync4(file, initial, { mode: 384 });
|
|
9888
10441
|
const code = defaultRunOrInjected(deps)(editor, file);
|
|
9889
10442
|
if (code !== 0) throw new Error(`editor "${editor}" exited with ${code}; nothing was written`);
|
|
9890
|
-
const edited =
|
|
10443
|
+
const edited = readFileSync12(file, "utf8");
|
|
9891
10444
|
return edited === initial ? null : edited;
|
|
9892
10445
|
} finally {
|
|
9893
10446
|
rmSync2(dir, { recursive: true, force: true });
|
|
@@ -9928,10 +10481,16 @@ function credentialKind(identity) {
|
|
|
9928
10481
|
return identity.scopes.length ? "device token (scoped)" : "device token or session";
|
|
9929
10482
|
}
|
|
9930
10483
|
async function whoamiCommand(parsed, deps = {}) {
|
|
9931
|
-
assertArgs(
|
|
10484
|
+
assertArgs(
|
|
10485
|
+
parsed,
|
|
10486
|
+
["config", "context", "platform", "token", "email", "json"],
|
|
10487
|
+
1
|
|
10488
|
+
);
|
|
9932
10489
|
const out = deps.stdout ?? console;
|
|
9933
10490
|
const doFetch = deps.fetch ?? fetch;
|
|
9934
|
-
const cfg = await
|
|
10491
|
+
const { cfg } = await resolveOperatorContext(parsed, {
|
|
10492
|
+
allowMissingConfig: true
|
|
10493
|
+
});
|
|
9935
10494
|
const token = await getDeveloperToken(
|
|
9936
10495
|
cfg,
|
|
9937
10496
|
{
|
|
@@ -9980,34 +10539,24 @@ var ALLOWED3 = [
|
|
|
9980
10539
|
"dry-run",
|
|
9981
10540
|
"limit",
|
|
9982
10541
|
"base",
|
|
9983
|
-
"requires"
|
|
10542
|
+
"requires",
|
|
10543
|
+
"platform",
|
|
10544
|
+
"context"
|
|
9984
10545
|
];
|
|
9985
10546
|
function requireSlug(slug, action) {
|
|
9986
10547
|
if (!slug) throw new Error(`"runbook ${action}" needs a slug, e.g. "odla-ai runbook ${action} release"`);
|
|
9987
10548
|
return slug;
|
|
9988
10549
|
}
|
|
9989
10550
|
var WRITES = /* @__PURE__ */ new Set(["new", "edit", "publish", "archive", "visibility", "revert", "rm", "import"]);
|
|
9990
|
-
var CONFIG_FREE = /* @__PURE__ */ new Set(["list", "search", "ask", "get", "cat", "history", "impact", "lint"]);
|
|
9991
|
-
async function loadOrDefaultConfig(configPath, action, appId) {
|
|
9992
|
-
const projectScoped = appId !== void 0;
|
|
9993
|
-
if (!projectScoped && CONFIG_FREE.has(action) && !existsSync10(resolve10(configPath))) {
|
|
9994
|
-
const rootDir = process11.cwd();
|
|
9995
|
-
return {
|
|
9996
|
-
configPath,
|
|
9997
|
-
platformUrl: process11.env.ODLA_PLATFORM_URL ?? "https://odla.ai",
|
|
9998
|
-
rootDir,
|
|
9999
|
-
app: { id: "", name: "" },
|
|
10000
|
-
local: { tokenFile: join12(rootDir, ".odla/dev-token.json") }
|
|
10001
|
-
};
|
|
10002
|
-
}
|
|
10003
|
-
return loadProjectConfig(configPath);
|
|
10004
|
-
}
|
|
10005
10551
|
async function buildContext3(parsed, deps, action) {
|
|
10006
|
-
const
|
|
10007
|
-
const
|
|
10552
|
+
const appIdOption = stringOpt(parsed.options.app);
|
|
10553
|
+
const context = await resolveOperatorContext(parsed, {
|
|
10554
|
+
allowMissingConfig: true
|
|
10555
|
+
});
|
|
10556
|
+
const { cfg } = context;
|
|
10008
10557
|
const doFetch = deps.fetch ?? fetch;
|
|
10009
10558
|
const out = deps.stdout ?? console;
|
|
10010
|
-
const appId =
|
|
10559
|
+
const appId = appIdOption ?? (context.app.source === "environment" || context.app.source === "profile" ? context.app.value : null) ?? PLATFORM_SCOPE;
|
|
10011
10560
|
const dryRun = parsed.options["dry-run"] === true;
|
|
10012
10561
|
if (action === "import" && dryRun) {
|
|
10013
10562
|
return {
|
|
@@ -10028,8 +10577,10 @@ async function buildContext3(parsed, deps, action) {
|
|
|
10028
10577
|
fetch: doFetch,
|
|
10029
10578
|
stdout: out,
|
|
10030
10579
|
openApprovalUrl: deps.openUrl,
|
|
10031
|
-
//
|
|
10032
|
-
|
|
10580
|
+
// A project, named context, or the global operator context owns the
|
|
10581
|
+
// exact-scope cache; it never follows an arbitrary shell directory.
|
|
10582
|
+
rootDir: cfg.rootDir,
|
|
10583
|
+
tokenFile: context.credentials.scopedTokenFile
|
|
10033
10584
|
}) : await getDeveloperToken(
|
|
10034
10585
|
cfg,
|
|
10035
10586
|
{
|
|
@@ -10612,6 +11163,10 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
|
10612
11163
|
await whoamiCommand(parsed, runtime);
|
|
10613
11164
|
return;
|
|
10614
11165
|
}
|
|
11166
|
+
if (command === "context") {
|
|
11167
|
+
await contextCommand(parsed, runtime);
|
|
11168
|
+
return;
|
|
11169
|
+
}
|
|
10615
11170
|
if (command === "runbook") {
|
|
10616
11171
|
await runbookCommand(parsed, runtime);
|
|
10617
11172
|
return;
|
|
@@ -10771,4 +11326,4 @@ export {
|
|
|
10771
11326
|
exitCodeFor,
|
|
10772
11327
|
runCli
|
|
10773
11328
|
};
|
|
10774
|
-
//# sourceMappingURL=chunk-
|
|
11329
|
+
//# sourceMappingURL=chunk-5MRY3V2K.js.map
|