@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/bin.js
CHANGED
|
@@ -5942,6 +5942,7 @@ Usage:
|
|
|
5942
5942
|
odla-ai runbook ask "<question>" [--app <id>] [--all] [--json]
|
|
5943
5943
|
odla-ai runbook search "<question>" [--app <id>] [--all] [--limit <n>] [--json]
|
|
5944
5944
|
odla-ai runbook impact [--base origin/main] [--app <id>] [--all] [--limit <n>] [--json]
|
|
5945
|
+
odla-ai runbook lint [--app <id>] [--all] [--json]
|
|
5945
5946
|
odla-ai runbook list [--app <id>] [--all] [--q <text>] [--json]
|
|
5946
5947
|
odla-ai runbook comment <slug> --body "..." [--app <id>]
|
|
5947
5948
|
odla-ai runbook get <slug> [--app <id>]
|
|
@@ -5988,7 +5989,10 @@ Commands:
|
|
|
5988
5989
|
"search" the passages behind it; "get" the whole document.
|
|
5989
5990
|
"impact" diffs your working tree against a base ref and names the
|
|
5990
5991
|
runbooks that describe what you changed \u2014 run it after touching a
|
|
5991
|
-
package's exported API or JSDoc, or a Studio surface.
|
|
5992
|
+
package's exported API or JSDoc, or a Studio surface. "lint"
|
|
5993
|
+
checks the corpus the other way: every odla-ai command the
|
|
5994
|
+
runbooks name is held against this CLI's real command surface,
|
|
5995
|
+
and against the minimum versions each runbook declares. A wrong step
|
|
5992
5996
|
is fixed with "edit", which takes effect immediately: a runbook is
|
|
5993
5997
|
a row, not a release. Runbooks describe PROCEDURE; what an export
|
|
5994
5998
|
does and what it guarantees is JSDoc, rendered per package at
|
|
@@ -6732,6 +6736,124 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
|
|
|
6732
6736
|
}
|
|
6733
6737
|
}
|
|
6734
6738
|
|
|
6739
|
+
// src/surface.ts
|
|
6740
|
+
var PM_ACTIONS = {
|
|
6741
|
+
list: {},
|
|
6742
|
+
add: {},
|
|
6743
|
+
create: {},
|
|
6744
|
+
get: {},
|
|
6745
|
+
set: {},
|
|
6746
|
+
update: {},
|
|
6747
|
+
status: {},
|
|
6748
|
+
move: {},
|
|
6749
|
+
done: {},
|
|
6750
|
+
comment: {},
|
|
6751
|
+
comments: {},
|
|
6752
|
+
rm: {},
|
|
6753
|
+
delete: {}
|
|
6754
|
+
};
|
|
6755
|
+
var PM_ENTITIES = Object.fromEntries(
|
|
6756
|
+
["goal", "conformance", "task", "kanban", "decision", "bug"].map((entity) => [entity, PM_ACTIONS])
|
|
6757
|
+
);
|
|
6758
|
+
var COMMAND_SURFACE = {
|
|
6759
|
+
admin: {
|
|
6760
|
+
ai: {
|
|
6761
|
+
show: {},
|
|
6762
|
+
set: {},
|
|
6763
|
+
credentials: {},
|
|
6764
|
+
models: {},
|
|
6765
|
+
usage: {},
|
|
6766
|
+
audit: {},
|
|
6767
|
+
credential: { set: {} }
|
|
6768
|
+
}
|
|
6769
|
+
},
|
|
6770
|
+
app: {
|
|
6771
|
+
archive: {},
|
|
6772
|
+
restore: {},
|
|
6773
|
+
export: {},
|
|
6774
|
+
import: {},
|
|
6775
|
+
rename: {},
|
|
6776
|
+
"refresh-sandbox": {},
|
|
6777
|
+
"go-live": {},
|
|
6778
|
+
promote: {},
|
|
6779
|
+
owners: { list: {}, add: {}, remove: {} }
|
|
6780
|
+
},
|
|
6781
|
+
calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
|
|
6782
|
+
capabilities: {},
|
|
6783
|
+
code: { connect: {} },
|
|
6784
|
+
doctor: {},
|
|
6785
|
+
help: {},
|
|
6786
|
+
init: {},
|
|
6787
|
+
pm: PM_ENTITIES,
|
|
6788
|
+
provision: {},
|
|
6789
|
+
runbook: {
|
|
6790
|
+
ask: {},
|
|
6791
|
+
search: {},
|
|
6792
|
+
impact: {},
|
|
6793
|
+
list: {},
|
|
6794
|
+
get: {},
|
|
6795
|
+
cat: {},
|
|
6796
|
+
new: {},
|
|
6797
|
+
edit: {},
|
|
6798
|
+
comment: {},
|
|
6799
|
+
import: {},
|
|
6800
|
+
visibility: {},
|
|
6801
|
+
publish: {},
|
|
6802
|
+
archive: {},
|
|
6803
|
+
history: {},
|
|
6804
|
+
revert: {},
|
|
6805
|
+
rm: {},
|
|
6806
|
+
lint: {}
|
|
6807
|
+
},
|
|
6808
|
+
secrets: { push: {}, set: {}, "set-clerk-key": {} },
|
|
6809
|
+
security: {
|
|
6810
|
+
plan: {},
|
|
6811
|
+
sources: {},
|
|
6812
|
+
run: {},
|
|
6813
|
+
status: {},
|
|
6814
|
+
report: {},
|
|
6815
|
+
github: { connect: {}, disconnect: {} }
|
|
6816
|
+
},
|
|
6817
|
+
setup: {},
|
|
6818
|
+
skill: { install: {} },
|
|
6819
|
+
smoke: {},
|
|
6820
|
+
version: {},
|
|
6821
|
+
whoami: {}
|
|
6822
|
+
};
|
|
6823
|
+
function acceptedAfter(path) {
|
|
6824
|
+
let node = COMMAND_SURFACE;
|
|
6825
|
+
for (const word of path) {
|
|
6826
|
+
node = node?.[word];
|
|
6827
|
+
if (!node) return [];
|
|
6828
|
+
}
|
|
6829
|
+
return Object.keys(node).sort();
|
|
6830
|
+
}
|
|
6831
|
+
function validateInvocation(words2) {
|
|
6832
|
+
let node = COMMAND_SURFACE;
|
|
6833
|
+
const walked = [];
|
|
6834
|
+
for (const word of words2) {
|
|
6835
|
+
if (Object.keys(node).length === 0) return null;
|
|
6836
|
+
const next = node[word];
|
|
6837
|
+
if (!next) return { validPrefix: walked.join(" "), word, accepted: Object.keys(node).sort() };
|
|
6838
|
+
walked.push(word);
|
|
6839
|
+
node = next;
|
|
6840
|
+
}
|
|
6841
|
+
return null;
|
|
6842
|
+
}
|
|
6843
|
+
function describeProblem(problem) {
|
|
6844
|
+
const where = problem.validPrefix ? `after "${problem.validPrefix}"` : "as a command";
|
|
6845
|
+
return `"${problem.word}" is not accepted ${where} \u2014 try: ${problem.accepted.join(", ")}`;
|
|
6846
|
+
}
|
|
6847
|
+
function surfacePaths(node = COMMAND_SURFACE, prefix = []) {
|
|
6848
|
+
const paths = [];
|
|
6849
|
+
for (const [word, child] of Object.entries(node)) {
|
|
6850
|
+
const path = [...prefix, word];
|
|
6851
|
+
paths.push(path);
|
|
6852
|
+
paths.push(...surfacePaths(child, path));
|
|
6853
|
+
}
|
|
6854
|
+
return paths;
|
|
6855
|
+
}
|
|
6856
|
+
|
|
6735
6857
|
// src/security.ts
|
|
6736
6858
|
import { isAbsolute as isAbsolute5, relative as relative5, resolve as resolve9, sep as sep4 } from "path";
|
|
6737
6859
|
import {
|
|
@@ -8052,6 +8174,55 @@ import process10 from "process";
|
|
|
8052
8174
|
|
|
8053
8175
|
// src/runbook-actions.ts
|
|
8054
8176
|
import { readFileSync as readFileSync8 } from "fs";
|
|
8177
|
+
|
|
8178
|
+
// src/runbook-requires.ts
|
|
8179
|
+
var SPEC = /^(@?[\w./-]+?)@(\d+\.\d+\.\d+(?:[\w.-]*)?)$/;
|
|
8180
|
+
function parseRequires(value) {
|
|
8181
|
+
if (!value) return [];
|
|
8182
|
+
const out = [];
|
|
8183
|
+
for (const token of value.split(/[\s,]+/).filter(Boolean)) {
|
|
8184
|
+
const match = SPEC.exec(token);
|
|
8185
|
+
if (match?.[1] && match[2]) out.push({ name: match[1], min: match[2] });
|
|
8186
|
+
}
|
|
8187
|
+
return out;
|
|
8188
|
+
}
|
|
8189
|
+
function compareVersions(a, b) {
|
|
8190
|
+
const parts = (v) => {
|
|
8191
|
+
const [core = "", pre = ""] = v.split("-", 2);
|
|
8192
|
+
return { nums: core.split(".").map((n) => Number.parseInt(n, 10) || 0), pre };
|
|
8193
|
+
};
|
|
8194
|
+
const left = parts(a);
|
|
8195
|
+
const right = parts(b);
|
|
8196
|
+
for (let i = 0; i < Math.max(left.nums.length, right.nums.length); i++) {
|
|
8197
|
+
const diff = (left.nums[i] ?? 0) - (right.nums[i] ?? 0);
|
|
8198
|
+
if (diff) return diff < 0 ? -1 : 1;
|
|
8199
|
+
}
|
|
8200
|
+
if (left.pre === right.pre) return 0;
|
|
8201
|
+
if (!left.pre) return 1;
|
|
8202
|
+
if (!right.pre) return -1;
|
|
8203
|
+
return left.pre < right.pre ? -1 : 1;
|
|
8204
|
+
}
|
|
8205
|
+
function satisfies(installed, min) {
|
|
8206
|
+
return compareVersions(installed, min) >= 0;
|
|
8207
|
+
}
|
|
8208
|
+
function unmetRequirements(requires, installedVersions) {
|
|
8209
|
+
const unmet = [];
|
|
8210
|
+
for (const requirement of parseRequires(requires)) {
|
|
8211
|
+
const installed = installedVersions[requirement.name];
|
|
8212
|
+
if (installed === void 0) {
|
|
8213
|
+
unmet.push({ ...requirement, installed: null });
|
|
8214
|
+
} else if (!satisfies(installed, requirement.min)) {
|
|
8215
|
+
unmet.push({ ...requirement, installed });
|
|
8216
|
+
}
|
|
8217
|
+
}
|
|
8218
|
+
return unmet;
|
|
8219
|
+
}
|
|
8220
|
+
function describeUnmet(slug, unmet) {
|
|
8221
|
+
const detail = unmet.map((u) => `${u.name}@${u.min}${u.installed ? ` (you have ${u.installed})` : " (not installed here)"}`).join(", ");
|
|
8222
|
+
return `note: runbook "${slug}" expects ${detail} \u2014 some steps may name commands your version does not have.`;
|
|
8223
|
+
}
|
|
8224
|
+
|
|
8225
|
+
// src/runbook-actions.ts
|
|
8055
8226
|
var PLATFORM_SCOPE = "$platform";
|
|
8056
8227
|
async function call(ctx, method, path, body) {
|
|
8057
8228
|
const res = await ctx.doFetch(`${ctx.platformUrl.replace(/\/$/, "")}/registry/pm${path}`, {
|
|
@@ -8106,6 +8277,8 @@ async function runbookList(ctx, all, query) {
|
|
|
8106
8277
|
async function runbookGet(ctx, slug) {
|
|
8107
8278
|
const runbook = await bySlug(ctx, slug);
|
|
8108
8279
|
if (ctx.json) return ctx.out.log(JSON.stringify(runbook, null, 2));
|
|
8280
|
+
const unmet = unmetRequirements(runbook.requires, { "@odla-ai/cli": cliVersion() });
|
|
8281
|
+
if (unmet.length) ctx.out.error(describeUnmet(slug, unmet));
|
|
8109
8282
|
ctx.out.log(runbook.body);
|
|
8110
8283
|
}
|
|
8111
8284
|
async function runbookNew(ctx, slug, title, body, summary) {
|
|
@@ -8525,6 +8698,61 @@ async function runbookImpact(ctx, options, deps = {}) {
|
|
|
8525
8698
|
report2(ctx, impacts);
|
|
8526
8699
|
}
|
|
8527
8700
|
|
|
8701
|
+
// src/runbook-lint.ts
|
|
8702
|
+
function invocationsIn(body) {
|
|
8703
|
+
const re = /(`|npx[^\S\n]+)?(?:@odla-ai\/cli(?:@[\w.-]+)?|odla-ai)((?:[^\S\n]+[a-z][\w-]*)+)/g;
|
|
8704
|
+
const found = [];
|
|
8705
|
+
for (const match of body.matchAll(re)) {
|
|
8706
|
+
if (!match[1]) continue;
|
|
8707
|
+
const words2 = match[2].trim().split(/[^\S\n]+/);
|
|
8708
|
+
if (words2.length) found.push(words2);
|
|
8709
|
+
}
|
|
8710
|
+
return found;
|
|
8711
|
+
}
|
|
8712
|
+
function lintRunbook(runbook, installed) {
|
|
8713
|
+
const findings = [];
|
|
8714
|
+
const at = { slug: runbook.slug, appId: runbook.appId };
|
|
8715
|
+
const declared = parseRequires(runbook.requires);
|
|
8716
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8717
|
+
for (const words2 of invocationsIn(runbook.body)) {
|
|
8718
|
+
const problem = validateInvocation(words2);
|
|
8719
|
+
if (!problem) continue;
|
|
8720
|
+
const key = `${problem.validPrefix}|${problem.word}`;
|
|
8721
|
+
if (seen.has(key)) continue;
|
|
8722
|
+
seen.add(key);
|
|
8723
|
+
findings.push({
|
|
8724
|
+
...at,
|
|
8725
|
+
kind: declared.length ? "command" : "undeclared",
|
|
8726
|
+
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.`
|
|
8727
|
+
});
|
|
8728
|
+
}
|
|
8729
|
+
const unmet = unmetRequirements(runbook.requires, installed);
|
|
8730
|
+
if (unmet.length) findings.push({ ...at, kind: "requires", detail: describeUnmet(runbook.slug, unmet) });
|
|
8731
|
+
return findings;
|
|
8732
|
+
}
|
|
8733
|
+
async function runbookLint(ctx, all) {
|
|
8734
|
+
const params = new URLSearchParams();
|
|
8735
|
+
if (!all) params.set("app", ctx.appId);
|
|
8736
|
+
const page = await call(ctx, "GET", `/runbook${params.size ? `?${params}` : ""}`);
|
|
8737
|
+
const installed = { "@odla-ai/cli": cliVersion() };
|
|
8738
|
+
const findings = page.records.flatMap((runbook) => lintRunbook(runbook, installed));
|
|
8739
|
+
if (ctx.json) return ctx.out.log(JSON.stringify({ checked: page.records.length, findings }, null, 2));
|
|
8740
|
+
if (!page.records.length) return ctx.out.log("(no runbooks in scope)");
|
|
8741
|
+
if (!findings.length) {
|
|
8742
|
+
return ctx.out.log(
|
|
8743
|
+
`${page.records.length} runbook${page.records.length === 1 ? "" : "s"} checked; every command they name is real for @odla-ai/cli ${cliVersion()}.`
|
|
8744
|
+
);
|
|
8745
|
+
}
|
|
8746
|
+
ctx.out.log(`${findings.length} finding${findings.length === 1 ? "" : "s"} across ${page.records.length} runbooks:`);
|
|
8747
|
+
for (const finding of findings) {
|
|
8748
|
+
const scope = finding.appId === PLATFORM_SCOPE ? "" : ` --app ${finding.appId}`;
|
|
8749
|
+
ctx.out.log("");
|
|
8750
|
+
ctx.out.log(`${finding.slug} [${finding.kind}]`);
|
|
8751
|
+
ctx.out.log(` ${finding.detail}`);
|
|
8752
|
+
ctx.out.log(` odla-ai runbook get ${finding.slug}${scope}`);
|
|
8753
|
+
}
|
|
8754
|
+
}
|
|
8755
|
+
|
|
8528
8756
|
// src/runbook-search-command.ts
|
|
8529
8757
|
async function runbookSearch(ctx, query, all, limit) {
|
|
8530
8758
|
const params = new URLSearchParams({ q: query });
|
|
@@ -8724,7 +8952,7 @@ function requireSlug(slug, action) {
|
|
|
8724
8952
|
return slug;
|
|
8725
8953
|
}
|
|
8726
8954
|
var WRITES = /* @__PURE__ */ new Set(["new", "edit", "publish", "archive", "visibility", "revert", "rm", "import"]);
|
|
8727
|
-
var CONFIG_FREE = /* @__PURE__ */ new Set(["list", "search", "ask", "get", "cat", "history", "impact"]);
|
|
8955
|
+
var CONFIG_FREE = /* @__PURE__ */ new Set(["list", "search", "ask", "get", "cat", "history", "impact", "lint"]);
|
|
8728
8956
|
async function loadOrDefaultConfig(configPath, action, appId) {
|
|
8729
8957
|
const projectScoped = appId !== void 0;
|
|
8730
8958
|
if (!projectScoped && CONFIG_FREE.has(action) && !existsSync10(resolve10(configPath))) {
|
|
@@ -8806,6 +9034,8 @@ async function runbookCommand(parsed, deps = {}) {
|
|
|
8806
9034
|
if (!question) throw new Error('"runbook ask" needs a question, e.g. odla-ai runbook ask "how do I roll back a publish?"');
|
|
8807
9035
|
return runbookAsk(ctx, question, parsed.options.all === true);
|
|
8808
9036
|
}
|
|
9037
|
+
case "lint":
|
|
9038
|
+
return runbookLint(ctx, parsed.options.all === true);
|
|
8809
9039
|
case "impact":
|
|
8810
9040
|
return runbookImpact(
|
|
8811
9041
|
ctx,
|
|
@@ -8874,9 +9104,7 @@ async function runbookCommand(parsed, deps = {}) {
|
|
|
8874
9104
|
case "rm":
|
|
8875
9105
|
return runbookRemove(ctx, requireSlug(slug, "rm"));
|
|
8876
9106
|
default:
|
|
8877
|
-
throw new Error(
|
|
8878
|
-
`unknown runbook action "${action}". Try ask, search, impact, list, get, new, edit, comment, import, visibility, publish, archive, history, revert, rm.`
|
|
8879
|
-
);
|
|
9107
|
+
throw new Error(`unknown runbook action "${action}". Try ${acceptedAfter(["runbook"]).join(", ")}.`);
|
|
8880
9108
|
}
|
|
8881
9109
|
}
|
|
8882
9110
|
|
|
@@ -9460,6 +9688,11 @@ export {
|
|
|
9460
9688
|
codeConnect,
|
|
9461
9689
|
runCodeRuntime,
|
|
9462
9690
|
provision,
|
|
9691
|
+
COMMAND_SURFACE,
|
|
9692
|
+
acceptedAfter,
|
|
9693
|
+
validateInvocation,
|
|
9694
|
+
describeProblem,
|
|
9695
|
+
surfacePaths,
|
|
9463
9696
|
runHostedSecurity,
|
|
9464
9697
|
getHostedSecurityIntent,
|
|
9465
9698
|
getHostedSecurityPlan,
|
|
@@ -9472,4 +9705,4 @@ export {
|
|
|
9472
9705
|
exitCodeFor,
|
|
9473
9706
|
runCli
|
|
9474
9707
|
};
|
|
9475
|
-
//# sourceMappingURL=chunk-
|
|
9708
|
+
//# sourceMappingURL=chunk-QMA5OGKQ.js.map
|