@odla-ai/cli 0.18.0 → 0.19.0
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 +30 -14
- package/dist/bin.cjs +224 -5
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-TOSMNGGQ.js → chunk-QMA5OGKQ.js} +239 -6
- package/dist/chunk-QMA5OGKQ.js.map +1 -0
- package/dist/index.cjs +245 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +41 -1
- package/dist/index.d.ts +41 -1
- package/dist/index.js +13 -3
- package/package.json +1 -1
- package/skills/odla/SKILL.md +14 -0
- package/dist/chunk-TOSMNGGQ.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -35,8 +35,10 @@ __export(index_exports, {
|
|
|
35
35
|
CAPABILITIES: () => CAPABILITIES,
|
|
36
36
|
CODE_BUILD_RECIPES: () => CODE_BUILD_RECIPES,
|
|
37
37
|
CODE_PI_IMAGE: () => CODE_PI_IMAGE,
|
|
38
|
+
COMMAND_SURFACE: () => COMMAND_SURFACE,
|
|
38
39
|
GOOGLE_CALENDAR_EVENTS_SCOPE: () => GOOGLE_CALENDAR_EVENTS_SCOPE,
|
|
39
40
|
SYSTEM_AI_PURPOSES: () => SYSTEM_AI_PURPOSES,
|
|
41
|
+
acceptedAfter: () => acceptedAfter,
|
|
40
42
|
adminAi: () => adminAi,
|
|
41
43
|
calendarBookingPageUrl: () => calendarBookingPageUrl,
|
|
42
44
|
calendarCalendars: () => calendarCalendars,
|
|
@@ -46,6 +48,7 @@ __export(index_exports, {
|
|
|
46
48
|
calendarStatus: () => calendarStatus,
|
|
47
49
|
codeConnect: () => codeConnect,
|
|
48
50
|
connectGitHubSecuritySource: () => connectGitHubSecuritySource,
|
|
51
|
+
describeProblem: () => describeProblem,
|
|
49
52
|
disconnectGitHubSecuritySource: () => disconnectGitHubSecuritySource,
|
|
50
53
|
doctor: () => doctor,
|
|
51
54
|
exitCodeFor: () => exitCodeFor,
|
|
@@ -73,7 +76,9 @@ __export(index_exports, {
|
|
|
73
76
|
secretsSet: () => secretsSet,
|
|
74
77
|
secretsSetClerkKey: () => secretsSetClerkKey,
|
|
75
78
|
smoke: () => smoke,
|
|
76
|
-
startHostedSecurityJob: () => startHostedSecurityJob
|
|
79
|
+
startHostedSecurityJob: () => startHostedSecurityJob,
|
|
80
|
+
surfacePaths: () => surfacePaths,
|
|
81
|
+
validateInvocation: () => validateInvocation
|
|
77
82
|
});
|
|
78
83
|
module.exports = __toCommonJS(index_exports);
|
|
79
84
|
|
|
@@ -6489,6 +6494,7 @@ Usage:
|
|
|
6489
6494
|
odla-ai runbook ask "<question>" [--app <id>] [--all] [--json]
|
|
6490
6495
|
odla-ai runbook search "<question>" [--app <id>] [--all] [--limit <n>] [--json]
|
|
6491
6496
|
odla-ai runbook impact [--base origin/main] [--app <id>] [--all] [--limit <n>] [--json]
|
|
6497
|
+
odla-ai runbook lint [--app <id>] [--all] [--json]
|
|
6492
6498
|
odla-ai runbook list [--app <id>] [--all] [--q <text>] [--json]
|
|
6493
6499
|
odla-ai runbook comment <slug> --body "..." [--app <id>]
|
|
6494
6500
|
odla-ai runbook get <slug> [--app <id>]
|
|
@@ -6535,7 +6541,10 @@ Commands:
|
|
|
6535
6541
|
"search" the passages behind it; "get" the whole document.
|
|
6536
6542
|
"impact" diffs your working tree against a base ref and names the
|
|
6537
6543
|
runbooks that describe what you changed \u2014 run it after touching a
|
|
6538
|
-
package's exported API or JSDoc, or a Studio surface.
|
|
6544
|
+
package's exported API or JSDoc, or a Studio surface. "lint"
|
|
6545
|
+
checks the corpus the other way: every odla-ai command the
|
|
6546
|
+
runbooks name is held against this CLI's real command surface,
|
|
6547
|
+
and against the minimum versions each runbook declares. A wrong step
|
|
6539
6548
|
is fixed with "edit", which takes effect immediately: a runbook is
|
|
6540
6549
|
a row, not a release. Runbooks describe PROCEDURE; what an export
|
|
6541
6550
|
does and what it guarantees is JSDoc, rendered per package at
|
|
@@ -7856,6 +7865,55 @@ var import_node_process9 = __toESM(require("process"), 1);
|
|
|
7856
7865
|
|
|
7857
7866
|
// src/runbook-actions.ts
|
|
7858
7867
|
var import_node_fs13 = require("fs");
|
|
7868
|
+
|
|
7869
|
+
// src/runbook-requires.ts
|
|
7870
|
+
var SPEC = /^(@?[\w./-]+?)@(\d+\.\d+\.\d+(?:[\w.-]*)?)$/;
|
|
7871
|
+
function parseRequires(value) {
|
|
7872
|
+
if (!value) return [];
|
|
7873
|
+
const out = [];
|
|
7874
|
+
for (const token of value.split(/[\s,]+/).filter(Boolean)) {
|
|
7875
|
+
const match = SPEC.exec(token);
|
|
7876
|
+
if (match?.[1] && match[2]) out.push({ name: match[1], min: match[2] });
|
|
7877
|
+
}
|
|
7878
|
+
return out;
|
|
7879
|
+
}
|
|
7880
|
+
function compareVersions(a, b) {
|
|
7881
|
+
const parts = (v) => {
|
|
7882
|
+
const [core = "", pre = ""] = v.split("-", 2);
|
|
7883
|
+
return { nums: core.split(".").map((n) => Number.parseInt(n, 10) || 0), pre };
|
|
7884
|
+
};
|
|
7885
|
+
const left = parts(a);
|
|
7886
|
+
const right = parts(b);
|
|
7887
|
+
for (let i = 0; i < Math.max(left.nums.length, right.nums.length); i++) {
|
|
7888
|
+
const diff = (left.nums[i] ?? 0) - (right.nums[i] ?? 0);
|
|
7889
|
+
if (diff) return diff < 0 ? -1 : 1;
|
|
7890
|
+
}
|
|
7891
|
+
if (left.pre === right.pre) return 0;
|
|
7892
|
+
if (!left.pre) return 1;
|
|
7893
|
+
if (!right.pre) return -1;
|
|
7894
|
+
return left.pre < right.pre ? -1 : 1;
|
|
7895
|
+
}
|
|
7896
|
+
function satisfies(installed, min) {
|
|
7897
|
+
return compareVersions(installed, min) >= 0;
|
|
7898
|
+
}
|
|
7899
|
+
function unmetRequirements(requires, installedVersions) {
|
|
7900
|
+
const unmet = [];
|
|
7901
|
+
for (const requirement of parseRequires(requires)) {
|
|
7902
|
+
const installed = installedVersions[requirement.name];
|
|
7903
|
+
if (installed === void 0) {
|
|
7904
|
+
unmet.push({ ...requirement, installed: null });
|
|
7905
|
+
} else if (!satisfies(installed, requirement.min)) {
|
|
7906
|
+
unmet.push({ ...requirement, installed });
|
|
7907
|
+
}
|
|
7908
|
+
}
|
|
7909
|
+
return unmet;
|
|
7910
|
+
}
|
|
7911
|
+
function describeUnmet(slug, unmet) {
|
|
7912
|
+
const detail = unmet.map((u) => `${u.name}@${u.min}${u.installed ? ` (you have ${u.installed})` : " (not installed here)"}`).join(", ");
|
|
7913
|
+
return `note: runbook "${slug}" expects ${detail} \u2014 some steps may name commands your version does not have.`;
|
|
7914
|
+
}
|
|
7915
|
+
|
|
7916
|
+
// src/runbook-actions.ts
|
|
7859
7917
|
var PLATFORM_SCOPE = "$platform";
|
|
7860
7918
|
async function call(ctx, method, path, body) {
|
|
7861
7919
|
const res = await ctx.doFetch(`${ctx.platformUrl.replace(/\/$/, "")}/registry/pm${path}`, {
|
|
@@ -7910,6 +7968,8 @@ async function runbookList(ctx, all, query) {
|
|
|
7910
7968
|
async function runbookGet(ctx, slug) {
|
|
7911
7969
|
const runbook = await bySlug(ctx, slug);
|
|
7912
7970
|
if (ctx.json) return ctx.out.log(JSON.stringify(runbook, null, 2));
|
|
7971
|
+
const unmet = unmetRequirements(runbook.requires, { "@odla-ai/cli": cliVersion() });
|
|
7972
|
+
if (unmet.length) ctx.out.error(describeUnmet(slug, unmet));
|
|
7913
7973
|
ctx.out.log(runbook.body);
|
|
7914
7974
|
}
|
|
7915
7975
|
async function runbookNew(ctx, slug, title, body, summary) {
|
|
@@ -8329,6 +8389,179 @@ async function runbookImpact(ctx, options, deps = {}) {
|
|
|
8329
8389
|
report2(ctx, impacts);
|
|
8330
8390
|
}
|
|
8331
8391
|
|
|
8392
|
+
// src/surface.ts
|
|
8393
|
+
var PM_ACTIONS = {
|
|
8394
|
+
list: {},
|
|
8395
|
+
add: {},
|
|
8396
|
+
create: {},
|
|
8397
|
+
get: {},
|
|
8398
|
+
set: {},
|
|
8399
|
+
update: {},
|
|
8400
|
+
status: {},
|
|
8401
|
+
move: {},
|
|
8402
|
+
done: {},
|
|
8403
|
+
comment: {},
|
|
8404
|
+
comments: {},
|
|
8405
|
+
rm: {},
|
|
8406
|
+
delete: {}
|
|
8407
|
+
};
|
|
8408
|
+
var PM_ENTITIES = Object.fromEntries(
|
|
8409
|
+
["goal", "conformance", "task", "kanban", "decision", "bug"].map((entity) => [entity, PM_ACTIONS])
|
|
8410
|
+
);
|
|
8411
|
+
var COMMAND_SURFACE = {
|
|
8412
|
+
admin: {
|
|
8413
|
+
ai: {
|
|
8414
|
+
show: {},
|
|
8415
|
+
set: {},
|
|
8416
|
+
credentials: {},
|
|
8417
|
+
models: {},
|
|
8418
|
+
usage: {},
|
|
8419
|
+
audit: {},
|
|
8420
|
+
credential: { set: {} }
|
|
8421
|
+
}
|
|
8422
|
+
},
|
|
8423
|
+
app: {
|
|
8424
|
+
archive: {},
|
|
8425
|
+
restore: {},
|
|
8426
|
+
export: {},
|
|
8427
|
+
import: {},
|
|
8428
|
+
rename: {},
|
|
8429
|
+
"refresh-sandbox": {},
|
|
8430
|
+
"go-live": {},
|
|
8431
|
+
promote: {},
|
|
8432
|
+
owners: { list: {}, add: {}, remove: {} }
|
|
8433
|
+
},
|
|
8434
|
+
calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
|
|
8435
|
+
capabilities: {},
|
|
8436
|
+
code: { connect: {} },
|
|
8437
|
+
doctor: {},
|
|
8438
|
+
help: {},
|
|
8439
|
+
init: {},
|
|
8440
|
+
pm: PM_ENTITIES,
|
|
8441
|
+
provision: {},
|
|
8442
|
+
runbook: {
|
|
8443
|
+
ask: {},
|
|
8444
|
+
search: {},
|
|
8445
|
+
impact: {},
|
|
8446
|
+
list: {},
|
|
8447
|
+
get: {},
|
|
8448
|
+
cat: {},
|
|
8449
|
+
new: {},
|
|
8450
|
+
edit: {},
|
|
8451
|
+
comment: {},
|
|
8452
|
+
import: {},
|
|
8453
|
+
visibility: {},
|
|
8454
|
+
publish: {},
|
|
8455
|
+
archive: {},
|
|
8456
|
+
history: {},
|
|
8457
|
+
revert: {},
|
|
8458
|
+
rm: {},
|
|
8459
|
+
lint: {}
|
|
8460
|
+
},
|
|
8461
|
+
secrets: { push: {}, set: {}, "set-clerk-key": {} },
|
|
8462
|
+
security: {
|
|
8463
|
+
plan: {},
|
|
8464
|
+
sources: {},
|
|
8465
|
+
run: {},
|
|
8466
|
+
status: {},
|
|
8467
|
+
report: {},
|
|
8468
|
+
github: { connect: {}, disconnect: {} }
|
|
8469
|
+
},
|
|
8470
|
+
setup: {},
|
|
8471
|
+
skill: { install: {} },
|
|
8472
|
+
smoke: {},
|
|
8473
|
+
version: {},
|
|
8474
|
+
whoami: {}
|
|
8475
|
+
};
|
|
8476
|
+
function acceptedAfter(path) {
|
|
8477
|
+
let node = COMMAND_SURFACE;
|
|
8478
|
+
for (const word of path) {
|
|
8479
|
+
node = node?.[word];
|
|
8480
|
+
if (!node) return [];
|
|
8481
|
+
}
|
|
8482
|
+
return Object.keys(node).sort();
|
|
8483
|
+
}
|
|
8484
|
+
function validateInvocation(words2) {
|
|
8485
|
+
let node = COMMAND_SURFACE;
|
|
8486
|
+
const walked = [];
|
|
8487
|
+
for (const word of words2) {
|
|
8488
|
+
if (Object.keys(node).length === 0) return null;
|
|
8489
|
+
const next = node[word];
|
|
8490
|
+
if (!next) return { validPrefix: walked.join(" "), word, accepted: Object.keys(node).sort() };
|
|
8491
|
+
walked.push(word);
|
|
8492
|
+
node = next;
|
|
8493
|
+
}
|
|
8494
|
+
return null;
|
|
8495
|
+
}
|
|
8496
|
+
function describeProblem(problem) {
|
|
8497
|
+
const where = problem.validPrefix ? `after "${problem.validPrefix}"` : "as a command";
|
|
8498
|
+
return `"${problem.word}" is not accepted ${where} \u2014 try: ${problem.accepted.join(", ")}`;
|
|
8499
|
+
}
|
|
8500
|
+
function surfacePaths(node = COMMAND_SURFACE, prefix = []) {
|
|
8501
|
+
const paths = [];
|
|
8502
|
+
for (const [word, child] of Object.entries(node)) {
|
|
8503
|
+
const path = [...prefix, word];
|
|
8504
|
+
paths.push(path);
|
|
8505
|
+
paths.push(...surfacePaths(child, path));
|
|
8506
|
+
}
|
|
8507
|
+
return paths;
|
|
8508
|
+
}
|
|
8509
|
+
|
|
8510
|
+
// src/runbook-lint.ts
|
|
8511
|
+
function invocationsIn(body) {
|
|
8512
|
+
const re = /(`|npx[^\S\n]+)?(?:@odla-ai\/cli(?:@[\w.-]+)?|odla-ai)((?:[^\S\n]+[a-z][\w-]*)+)/g;
|
|
8513
|
+
const found = [];
|
|
8514
|
+
for (const match of body.matchAll(re)) {
|
|
8515
|
+
if (!match[1]) continue;
|
|
8516
|
+
const words2 = match[2].trim().split(/[^\S\n]+/);
|
|
8517
|
+
if (words2.length) found.push(words2);
|
|
8518
|
+
}
|
|
8519
|
+
return found;
|
|
8520
|
+
}
|
|
8521
|
+
function lintRunbook(runbook, installed) {
|
|
8522
|
+
const findings = [];
|
|
8523
|
+
const at = { slug: runbook.slug, appId: runbook.appId };
|
|
8524
|
+
const declared = parseRequires(runbook.requires);
|
|
8525
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8526
|
+
for (const words2 of invocationsIn(runbook.body)) {
|
|
8527
|
+
const problem = validateInvocation(words2);
|
|
8528
|
+
if (!problem) continue;
|
|
8529
|
+
const key = `${problem.validPrefix}|${problem.word}`;
|
|
8530
|
+
if (seen.has(key)) continue;
|
|
8531
|
+
seen.add(key);
|
|
8532
|
+
findings.push({
|
|
8533
|
+
...at,
|
|
8534
|
+
kind: declared.length ? "command" : "undeclared",
|
|
8535
|
+
detail: declared.length ? `\`odla-ai ${words2.join(" ")}\`: ${describeProblem(problem)}` : `\`odla-ai ${words2.join(" ")}\`: ${describeProblem(problem)}. If a newer package added it, declare that with requires.`
|
|
8536
|
+
});
|
|
8537
|
+
}
|
|
8538
|
+
const unmet = unmetRequirements(runbook.requires, installed);
|
|
8539
|
+
if (unmet.length) findings.push({ ...at, kind: "requires", detail: describeUnmet(runbook.slug, unmet) });
|
|
8540
|
+
return findings;
|
|
8541
|
+
}
|
|
8542
|
+
async function runbookLint(ctx, all) {
|
|
8543
|
+
const params = new URLSearchParams();
|
|
8544
|
+
if (!all) params.set("app", ctx.appId);
|
|
8545
|
+
const page = await call(ctx, "GET", `/runbook${params.size ? `?${params}` : ""}`);
|
|
8546
|
+
const installed = { "@odla-ai/cli": cliVersion() };
|
|
8547
|
+
const findings = page.records.flatMap((runbook) => lintRunbook(runbook, installed));
|
|
8548
|
+
if (ctx.json) return ctx.out.log(JSON.stringify({ checked: page.records.length, findings }, null, 2));
|
|
8549
|
+
if (!page.records.length) return ctx.out.log("(no runbooks in scope)");
|
|
8550
|
+
if (!findings.length) {
|
|
8551
|
+
return ctx.out.log(
|
|
8552
|
+
`${page.records.length} runbook${page.records.length === 1 ? "" : "s"} checked; every command they name is real for @odla-ai/cli ${cliVersion()}.`
|
|
8553
|
+
);
|
|
8554
|
+
}
|
|
8555
|
+
ctx.out.log(`${findings.length} finding${findings.length === 1 ? "" : "s"} across ${page.records.length} runbooks:`);
|
|
8556
|
+
for (const finding of findings) {
|
|
8557
|
+
const scope = finding.appId === PLATFORM_SCOPE ? "" : ` --app ${finding.appId}`;
|
|
8558
|
+
ctx.out.log("");
|
|
8559
|
+
ctx.out.log(`${finding.slug} [${finding.kind}]`);
|
|
8560
|
+
ctx.out.log(` ${finding.detail}`);
|
|
8561
|
+
ctx.out.log(` odla-ai runbook get ${finding.slug}${scope}`);
|
|
8562
|
+
}
|
|
8563
|
+
}
|
|
8564
|
+
|
|
8332
8565
|
// src/runbook-search-command.ts
|
|
8333
8566
|
async function runbookSearch(ctx, query, all, limit) {
|
|
8334
8567
|
const params = new URLSearchParams({ q: query });
|
|
@@ -8528,7 +8761,7 @@ function requireSlug(slug, action) {
|
|
|
8528
8761
|
return slug;
|
|
8529
8762
|
}
|
|
8530
8763
|
var WRITES = /* @__PURE__ */ new Set(["new", "edit", "publish", "archive", "visibility", "revert", "rm", "import"]);
|
|
8531
|
-
var CONFIG_FREE = /* @__PURE__ */ new Set(["list", "search", "ask", "get", "cat", "history", "impact"]);
|
|
8764
|
+
var CONFIG_FREE = /* @__PURE__ */ new Set(["list", "search", "ask", "get", "cat", "history", "impact", "lint"]);
|
|
8532
8765
|
async function loadOrDefaultConfig(configPath, action, appId) {
|
|
8533
8766
|
const projectScoped = appId !== void 0;
|
|
8534
8767
|
if (!projectScoped && CONFIG_FREE.has(action) && !(0, import_node_fs17.existsSync)((0, import_node_path14.resolve)(configPath))) {
|
|
@@ -8610,6 +8843,8 @@ async function runbookCommand(parsed, deps = {}) {
|
|
|
8610
8843
|
if (!question) throw new Error('"runbook ask" needs a question, e.g. odla-ai runbook ask "how do I roll back a publish?"');
|
|
8611
8844
|
return runbookAsk(ctx, question, parsed.options.all === true);
|
|
8612
8845
|
}
|
|
8846
|
+
case "lint":
|
|
8847
|
+
return runbookLint(ctx, parsed.options.all === true);
|
|
8613
8848
|
case "impact":
|
|
8614
8849
|
return runbookImpact(
|
|
8615
8850
|
ctx,
|
|
@@ -8678,9 +8913,7 @@ async function runbookCommand(parsed, deps = {}) {
|
|
|
8678
8913
|
case "rm":
|
|
8679
8914
|
return runbookRemove(ctx, requireSlug(slug, "rm"));
|
|
8680
8915
|
default:
|
|
8681
|
-
throw new Error(
|
|
8682
|
-
`unknown runbook action "${action}". Try ask, search, impact, list, get, new, edit, comment, import, visibility, publish, archive, history, revert, rm.`
|
|
8683
|
-
);
|
|
8916
|
+
throw new Error(`unknown runbook action "${action}". Try ${acceptedAfter(["runbook"]).join(", ")}.`);
|
|
8684
8917
|
}
|
|
8685
8918
|
}
|
|
8686
8919
|
|
|
@@ -9505,8 +9738,10 @@ async function calendarCommand(parsed, dependencies) {
|
|
|
9505
9738
|
CAPABILITIES,
|
|
9506
9739
|
CODE_BUILD_RECIPES,
|
|
9507
9740
|
CODE_PI_IMAGE,
|
|
9741
|
+
COMMAND_SURFACE,
|
|
9508
9742
|
GOOGLE_CALENDAR_EVENTS_SCOPE,
|
|
9509
9743
|
SYSTEM_AI_PURPOSES,
|
|
9744
|
+
acceptedAfter,
|
|
9510
9745
|
adminAi,
|
|
9511
9746
|
calendarBookingPageUrl,
|
|
9512
9747
|
calendarCalendars,
|
|
@@ -9516,6 +9751,7 @@ async function calendarCommand(parsed, dependencies) {
|
|
|
9516
9751
|
calendarStatus,
|
|
9517
9752
|
codeConnect,
|
|
9518
9753
|
connectGitHubSecuritySource,
|
|
9754
|
+
describeProblem,
|
|
9519
9755
|
disconnectGitHubSecuritySource,
|
|
9520
9756
|
doctor,
|
|
9521
9757
|
exitCodeFor,
|
|
@@ -9543,6 +9779,8 @@ async function calendarCommand(parsed, dependencies) {
|
|
|
9543
9779
|
secretsSet,
|
|
9544
9780
|
secretsSetClerkKey,
|
|
9545
9781
|
smoke,
|
|
9546
|
-
startHostedSecurityJob
|
|
9782
|
+
startHostedSecurityJob,
|
|
9783
|
+
surfacePaths,
|
|
9784
|
+
validateInvocation
|
|
9547
9785
|
});
|
|
9548
9786
|
//# sourceMappingURL=index.cjs.map
|