@odla-ai/cli 0.22.1 → 0.24.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 +8 -0
- package/dist/bin.cjs +100 -16
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-DINDRUDA.js → chunk-7P7MI4WX.js} +101 -17
- package/dist/chunk-7P7MI4WX.js.map +1 -0
- package/dist/index.cjs +100 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-DINDRUDA.js.map +0 -1
package/dist/bin.js
CHANGED
|
@@ -5931,13 +5931,14 @@ Usage:
|
|
|
5931
5931
|
odla-ai app owners add <email> [--email <odla-account>] [--json]
|
|
5932
5932
|
odla-ai app owners remove <email> [--email <odla-account>] [--json]
|
|
5933
5933
|
odla-ai pm <goal|task|decision|bug> list [--app <id>] [--status <s>] [--severity <s>] [--column <c>] [--q <text>] [--json]
|
|
5934
|
-
odla-ai pm <goal|task|decision|bug> add --app <id> --title <t> [--severity high|--column doing|--goal <id>|--body <text>|--proof <text>]
|
|
5934
|
+
odla-ai pm <goal|task|decision|bug> add --app <id> --title <t> [--severity high|--column doing|--goal <id>|--description <text>|--body <text>|--proof <text>]
|
|
5935
5935
|
odla-ai pm <goal|task|decision|bug> get <id> [--json]
|
|
5936
|
-
odla-ai pm <goal|task|decision|bug> set <id> [--status <s>|--column <c>|--assignee <id>|--no-assignee|--goal <id>]
|
|
5937
|
-
odla-ai pm <goal|task|decision|bug> done <id>
|
|
5936
|
+
odla-ai pm <goal|task|decision|bug> set <id> [--status <s>|--column <c>|--assignee <id>|--no-assignee|--goal <id>|--decision <id>]
|
|
5937
|
+
odla-ai pm <goal|task|decision|bug> done <id> [--decision <accepted-decision-id>]
|
|
5938
5938
|
odla-ai pm <goal|task|decision|bug> comment <id> --body "..."
|
|
5939
5939
|
odla-ai pm <goal|task|decision|bug> comments <id> [--json]
|
|
5940
5940
|
odla-ai pm <goal|task|decision|bug> rm <id>
|
|
5941
|
+
odla-ai pm handoff --app <id> [--json]
|
|
5941
5942
|
odla-ai whoami [--json]
|
|
5942
5943
|
odla-ai runbook ask "<question>" [--app <id>] [--all] [--json]
|
|
5943
5944
|
odla-ai runbook search "<question>" [--app <id>] [--all] [--limit <n>] [--json]
|
|
@@ -6784,7 +6785,10 @@ var COMMAND_SURFACE = {
|
|
|
6784
6785
|
doctor: {},
|
|
6785
6786
|
help: {},
|
|
6786
6787
|
init: {},
|
|
6787
|
-
pm:
|
|
6788
|
+
pm: {
|
|
6789
|
+
...PM_ENTITIES,
|
|
6790
|
+
handoff: {}
|
|
6791
|
+
},
|
|
6788
6792
|
provision: {},
|
|
6789
6793
|
runbook: {
|
|
6790
6794
|
ask: {},
|
|
@@ -7367,6 +7371,7 @@ function chooseIdMode(options, rows) {
|
|
|
7367
7371
|
async function appImport(options) {
|
|
7368
7372
|
const cfg = await loadProjectConfig(options.configPath);
|
|
7369
7373
|
const out = options.stdout ?? console;
|
|
7374
|
+
const say = options.json ? (line) => out.error(line) : (line) => out.log(line);
|
|
7370
7375
|
const doFetch = options.fetch ?? fetch;
|
|
7371
7376
|
const { tenant } = resolveTenant(cfg, options.env);
|
|
7372
7377
|
const text = options.file === "-" ? (options.readStdin ?? (() => readFileSync7(0, "utf8")))() : readFileSync7(options.file, "utf8");
|
|
@@ -7388,10 +7393,10 @@ ${detail}${more}`);
|
|
|
7388
7393
|
}
|
|
7389
7394
|
const chunks = planImportChunks(ops);
|
|
7390
7395
|
const result = { tenant, rows: ops.length, chunks: chunks.length, committed: 0, duplicate: 0, txIds: [], ignored };
|
|
7391
|
-
if (ignored.length > 0)
|
|
7396
|
+
if (ignored.length > 0) say(`ignoring server-maintained field(s): ${ignored.join(", ")} \u2014 odla-db stamps these on write`);
|
|
7392
7397
|
if (options.dryRun || options.yes !== true) {
|
|
7393
7398
|
const why = options.dryRun ? "--dry-run" : "no --yes";
|
|
7394
|
-
|
|
7399
|
+
say(`${tenant}: would upsert ${ops.length} row(s) across ${chunks.length} transaction(s) \u2014 nothing written (${why})`);
|
|
7395
7400
|
if (options.json) out.log(JSON.stringify(result, null, 2));
|
|
7396
7401
|
return result;
|
|
7397
7402
|
}
|
|
@@ -7600,6 +7605,7 @@ function printPlan(out, verb, pre, opts) {
|
|
|
7600
7605
|
async function appTransfer(options) {
|
|
7601
7606
|
const cfg = await loadProjectConfig(options.configPath);
|
|
7602
7607
|
const out = options.stdout ?? console;
|
|
7608
|
+
const say = options.json ? (line) => out.error(line) : (line) => out.log(line);
|
|
7603
7609
|
const doFetch = options.fetch ?? fetch;
|
|
7604
7610
|
const tenants = bothTenants(cfg);
|
|
7605
7611
|
const route2 = endpointsFor(options.verb, tenants);
|
|
@@ -7620,7 +7626,7 @@ async function appTransfer(options) {
|
|
|
7620
7626
|
throw new Error(`pre-flight failed${pre.body.error?.code ? ` (${pre.body.error.code})` : ""}: ${pre.body.error?.message ?? pre.status}`);
|
|
7621
7627
|
}
|
|
7622
7628
|
const plan = pre.body;
|
|
7623
|
-
printPlan(
|
|
7629
|
+
printPlan({ log: say }, options.verb, plan, options);
|
|
7624
7630
|
if (options.verb === "go-live" && !plan.targetEmpty) {
|
|
7625
7631
|
throw new Error(
|
|
7626
7632
|
`${route2.target} already has data \u2014 go-live has already happened. Use \`odla-ai app promote --yes\` to push schema and rules, or \`odla-ai app refresh-sandbox --yes\` to pull live back down.`
|
|
@@ -7628,7 +7634,7 @@ async function appTransfer(options) {
|
|
|
7628
7634
|
}
|
|
7629
7635
|
if (plan.blockers.length > 0) throw new Error(`cannot ${options.verb}: ${plan.blockers.map((b) => b.message).join("; ")}`);
|
|
7630
7636
|
if (options.dryRun || options.yes !== true) {
|
|
7631
|
-
|
|
7637
|
+
say(`nothing written (${options.dryRun ? "--dry-run" : "no --yes"})`);
|
|
7632
7638
|
if (options.json) out.log(JSON.stringify(plan, null, 2));
|
|
7633
7639
|
return { ok: true, plan };
|
|
7634
7640
|
}
|
|
@@ -7642,7 +7648,7 @@ async function appTransfer(options) {
|
|
|
7642
7648
|
);
|
|
7643
7649
|
if (res2.status !== 200) throw new Error(`promote failed${res2.body.error?.code ? ` (${res2.body.error.code})` : ""}: ${res2.body.error?.message ?? res2.status}`);
|
|
7644
7650
|
if (options.json) out.log(JSON.stringify(res2.body, null, 2));
|
|
7645
|
-
else
|
|
7651
|
+
else say(`${route2.target}: promoted ${(res2.body.applied ?? []).join(", ")}`);
|
|
7646
7652
|
return { ok: true, plan, result: res2.body };
|
|
7647
7653
|
}
|
|
7648
7654
|
const res = await api(
|
|
@@ -7661,7 +7667,7 @@ async function appTransfer(options) {
|
|
|
7661
7667
|
doFetch
|
|
7662
7668
|
);
|
|
7663
7669
|
if (res.status !== 200) throw new Error(`${options.verb} failed${res.body.error?.code ? ` (${res.body.error.code})` : ""}: ${res.body.error?.message ?? res.status}`);
|
|
7664
|
-
|
|
7670
|
+
say(`${route2.target}: replaced from ${route2.source} (tx ${res.body.destination?.maxTx}, epoch ${res.body.destination?.epoch}) \u2014 connected clients resync automatically`);
|
|
7665
7671
|
if (options.includeFiles) await copyFiles(cfg, token, route2, doFetch, out);
|
|
7666
7672
|
if (options.json) out.log(JSON.stringify(res.body, null, 2));
|
|
7667
7673
|
return { ok: true, plan, result: res.body };
|
|
@@ -7980,6 +7986,7 @@ var FIELD_MAP = {
|
|
|
7980
7986
|
column: { key: "column" },
|
|
7981
7987
|
rank: { key: "rank", num: true },
|
|
7982
7988
|
goal: { key: "goalId" },
|
|
7989
|
+
decision: { key: "decisionId" },
|
|
7983
7990
|
assignee: { key: "assigneeId" },
|
|
7984
7991
|
due: { key: "dueAt", num: true },
|
|
7985
7992
|
proof: { key: "proof" },
|
|
@@ -8018,6 +8025,14 @@ function collectFields(parsed, allowClear) {
|
|
|
8018
8025
|
}
|
|
8019
8026
|
return out;
|
|
8020
8027
|
}
|
|
8028
|
+
function collectEntityFields(entity, parsed, allowClear) {
|
|
8029
|
+
const fields = collectFields(parsed, allowClear);
|
|
8030
|
+
if ((entity === "task" || entity === "bug") && fields.body !== void 0) {
|
|
8031
|
+
if (fields.description === void 0) fields.description = fields.body;
|
|
8032
|
+
delete fields.body;
|
|
8033
|
+
}
|
|
8034
|
+
return fields;
|
|
8035
|
+
}
|
|
8021
8036
|
function statusCol(entity, r) {
|
|
8022
8037
|
if (entity === "bug") return `${r.status ?? ""}/${r.severity ?? ""}`;
|
|
8023
8038
|
if (entity === "task") return String(r.column ?? "");
|
|
@@ -8032,7 +8047,14 @@ function emit(ctx, value, human) {
|
|
|
8032
8047
|
}
|
|
8033
8048
|
async function pmList(ctx, entity, parsed) {
|
|
8034
8049
|
const q = new URLSearchParams();
|
|
8035
|
-
const filters = {
|
|
8050
|
+
const filters = {
|
|
8051
|
+
status: "status",
|
|
8052
|
+
severity: "severity",
|
|
8053
|
+
column: "column",
|
|
8054
|
+
goal: "goalId",
|
|
8055
|
+
assignee: "assigneeId",
|
|
8056
|
+
decision: "decisionId"
|
|
8057
|
+
};
|
|
8036
8058
|
const app = stringOpt(parsed.options.app);
|
|
8037
8059
|
if (app) q.set("app", app);
|
|
8038
8060
|
for (const [flag, param] of Object.entries(filters)) {
|
|
@@ -8054,8 +8076,10 @@ async function pmList(ctx, entity, parsed) {
|
|
|
8054
8076
|
async function pmAdd(ctx, entity, parsed) {
|
|
8055
8077
|
const appId = stringOpt(parsed.options.app);
|
|
8056
8078
|
if (!appId) throw new Error("pm add needs --app <appId>");
|
|
8057
|
-
const input =
|
|
8079
|
+
const input = collectEntityFields(entity, parsed, false);
|
|
8058
8080
|
if (!input.title) throw new Error("pm add needs --title <title>");
|
|
8081
|
+
if (entity === "bug" && !input.description)
|
|
8082
|
+
throw new Error("pm bug add needs --description <context> (or --body <context>)");
|
|
8059
8083
|
const res = await pmRequest(ctx, "POST", `/${entity}`, { appId, input });
|
|
8060
8084
|
emit(ctx, res, () => ctx.out.log(`created ${entity} ${res.id}`));
|
|
8061
8085
|
}
|
|
@@ -8064,16 +8088,71 @@ async function pmGet(ctx, entity, id) {
|
|
|
8064
8088
|
emit(ctx, record5, () => printRecord(ctx, entity, record5));
|
|
8065
8089
|
}
|
|
8066
8090
|
async function pmSet(ctx, entity, id, parsed) {
|
|
8067
|
-
const patch2 =
|
|
8091
|
+
const patch2 = collectEntityFields(entity, parsed, true);
|
|
8068
8092
|
if (Object.keys(patch2).length === 0)
|
|
8069
8093
|
throw new Error("pm set needs at least one field flag (e.g. --status doing, --assignee me, --no-assignee)");
|
|
8070
8094
|
const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(id)}`, { patch: patch2 });
|
|
8071
8095
|
emit(ctx, res, () => ctx.out.log(res.record ? `${res.record.id} [${statusCol(entity, res.record)}] ${res.record.appId} ${res.record.title ?? ""}` : `updated ${entity} ${id}`));
|
|
8072
8096
|
}
|
|
8073
|
-
async function pmDone(ctx, entity, id) {
|
|
8074
|
-
const
|
|
8097
|
+
async function pmDone(ctx, entity, id, parsed) {
|
|
8098
|
+
const decisionId = stringOpt(parsed.options.decision);
|
|
8099
|
+
if (decisionId && entity !== "bug") throw new Error("--decision is only valid when completing a bug");
|
|
8100
|
+
const patch2 = { ...DONE[entity], ...decisionId ? { decisionId } : {} };
|
|
8101
|
+
const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(id)}`, { patch: patch2 });
|
|
8075
8102
|
emit(ctx, res, () => ctx.out.log(`${entity} ${id} \u2192 done`));
|
|
8076
8103
|
}
|
|
8104
|
+
async function allRecords(ctx, entity, appId) {
|
|
8105
|
+
const records = [];
|
|
8106
|
+
for (; ; ) {
|
|
8107
|
+
const q = new URLSearchParams({
|
|
8108
|
+
app: appId,
|
|
8109
|
+
limit: "100",
|
|
8110
|
+
offset: String(records.length)
|
|
8111
|
+
});
|
|
8112
|
+
const page = await pmRequest(ctx, "GET", `/${entity}?${q}`);
|
|
8113
|
+
records.push(...page.records);
|
|
8114
|
+
if (records.length >= page.total || page.records.length === 0) return records;
|
|
8115
|
+
}
|
|
8116
|
+
}
|
|
8117
|
+
async function pmHandoff(ctx, parsed) {
|
|
8118
|
+
const appId = stringOpt(parsed.options.app);
|
|
8119
|
+
if (!appId) throw new Error("pm handoff needs --app <appId>");
|
|
8120
|
+
const [goals, tasks, bugs] = await Promise.all([
|
|
8121
|
+
allRecords(ctx, "goal", appId),
|
|
8122
|
+
allRecords(ctx, "task", appId),
|
|
8123
|
+
allRecords(ctx, "bug", appId)
|
|
8124
|
+
]);
|
|
8125
|
+
const handoff = {
|
|
8126
|
+
appId,
|
|
8127
|
+
unmetGoals: goals.filter((record5) => record5.status !== "met"),
|
|
8128
|
+
activeTasks: tasks.filter((record5) => record5.column !== "done"),
|
|
8129
|
+
openBugs: bugs.filter((record5) => record5.status !== "fixed" && record5.status !== "wontfix")
|
|
8130
|
+
};
|
|
8131
|
+
const result = {
|
|
8132
|
+
...handoff,
|
|
8133
|
+
clean: !handoff.unmetGoals.length && !handoff.activeTasks.length && !handoff.openBugs.length
|
|
8134
|
+
};
|
|
8135
|
+
emit(ctx, result, () => {
|
|
8136
|
+
if (result.clean) {
|
|
8137
|
+
ctx.out.log(`${appId}: no unresolved PM work`);
|
|
8138
|
+
return;
|
|
8139
|
+
}
|
|
8140
|
+
ctx.out.log(`${appId}: authoritative PM handoff`);
|
|
8141
|
+
for (const [label, records] of [
|
|
8142
|
+
["unmet goals", result.unmetGoals],
|
|
8143
|
+
["active tasks", result.activeTasks],
|
|
8144
|
+
["open bugs", result.openBugs]
|
|
8145
|
+
]) {
|
|
8146
|
+
ctx.out.log(`${label}:`);
|
|
8147
|
+
if (!records.length) ctx.out.log("- (none)");
|
|
8148
|
+
else for (const record5 of records) printRecord(
|
|
8149
|
+
ctx,
|
|
8150
|
+
label === "unmet goals" ? "goal" : label === "active tasks" ? "task" : "bug",
|
|
8151
|
+
record5
|
|
8152
|
+
);
|
|
8153
|
+
}
|
|
8154
|
+
});
|
|
8155
|
+
}
|
|
8077
8156
|
async function pmComment(ctx, entity, id, parsed) {
|
|
8078
8157
|
const body = stringOpt(parsed.options.body);
|
|
8079
8158
|
if (!body) throw new Error('pm comment needs --body "..."');
|
|
@@ -8124,6 +8203,7 @@ var ALLOWED = [
|
|
|
8124
8203
|
"target",
|
|
8125
8204
|
"description",
|
|
8126
8205
|
"desc",
|
|
8206
|
+
"decision",
|
|
8127
8207
|
"limit",
|
|
8128
8208
|
"offset",
|
|
8129
8209
|
"q"
|
|
@@ -8146,6 +8226,10 @@ async function buildContext(parsed, deps) {
|
|
|
8146
8226
|
}
|
|
8147
8227
|
async function pmCommand(parsed, deps = {}) {
|
|
8148
8228
|
const word = parsed.positionals[1] ?? "";
|
|
8229
|
+
if (word === "handoff") {
|
|
8230
|
+
assertArgs(parsed, ALLOWED, 2);
|
|
8231
|
+
return pmHandoff(await buildContext(parsed, deps), parsed);
|
|
8232
|
+
}
|
|
8149
8233
|
const entity = ALIASES[word];
|
|
8150
8234
|
if (!entity) throw new Error(`unknown pm entity "${word}". Try "odla-ai pm bug list" (goal|task|decision|bug).`);
|
|
8151
8235
|
const action = parsed.positionals[2] ?? "list";
|
|
@@ -8166,7 +8250,7 @@ async function pmCommand(parsed, deps = {}) {
|
|
|
8166
8250
|
case "move":
|
|
8167
8251
|
return pmSet(ctx, entity, requireId(id, action), parsed);
|
|
8168
8252
|
case "done":
|
|
8169
|
-
return pmDone(ctx, entity, requireId(id, action));
|
|
8253
|
+
return pmDone(ctx, entity, requireId(id, action), parsed);
|
|
8170
8254
|
case "comment":
|
|
8171
8255
|
return pmComment(ctx, entity, requireId(id, action), parsed);
|
|
8172
8256
|
case "comments":
|
|
@@ -9747,4 +9831,4 @@ export {
|
|
|
9747
9831
|
exitCodeFor,
|
|
9748
9832
|
runCli
|
|
9749
9833
|
};
|
|
9750
|
-
//# sourceMappingURL=chunk-
|
|
9834
|
+
//# sourceMappingURL=chunk-7P7MI4WX.js.map
|