@odla-ai/cli 0.30.3 → 0.31.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 +4 -1
- package/dist/bin.cjs +153 -51
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-3HFNKDZV.js → chunk-5VH4EWV7.js} +119 -36
- package/dist/chunk-5VH4EWV7.js.map +1 -0
- package/dist/{cli-BGSX5Z4C.js → cli-CCQVA7VV.js} +2 -2
- package/dist/index.cjs +130 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-3HFNKDZV.js.map +0 -1
- /package/dist/{cli-BGSX5Z4C.js.map → cli-CCQVA7VV.js.map} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
runCli
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-5VH4EWV7.js";
|
|
5
5
|
import {
|
|
6
6
|
exitCodeFor
|
|
7
7
|
} from "./chunk-UKLSRQ5J.js";
|
|
@@ -9,4 +9,4 @@ export {
|
|
|
9
9
|
exitCodeFor,
|
|
10
10
|
runCli
|
|
11
11
|
};
|
|
12
|
-
//# sourceMappingURL=cli-
|
|
12
|
+
//# sourceMappingURL=cli-CCQVA7VV.js.map
|
package/dist/index.cjs
CHANGED
|
@@ -149,7 +149,7 @@ var import_node_process = __toESM(require("process"), 1);
|
|
|
149
149
|
async function openUrl(url, options = {}) {
|
|
150
150
|
const command = openerFor(options.platform ?? import_node_process.default.platform);
|
|
151
151
|
const doSpawn = options.spawnImpl ?? import_node_child_process.spawn;
|
|
152
|
-
await new Promise((
|
|
152
|
+
await new Promise((resolve14, reject) => {
|
|
153
153
|
const child = doSpawn(command.cmd, [...command.args, url], {
|
|
154
154
|
stdio: "ignore",
|
|
155
155
|
detached: true
|
|
@@ -157,7 +157,7 @@ async function openUrl(url, options = {}) {
|
|
|
157
157
|
child.once("error", reject);
|
|
158
158
|
child.once("spawn", () => {
|
|
159
159
|
child.unref();
|
|
160
|
-
|
|
160
|
+
resolve14();
|
|
161
161
|
});
|
|
162
162
|
});
|
|
163
163
|
}
|
|
@@ -2598,8 +2598,8 @@ function credential(value2) {
|
|
|
2598
2598
|
// src/calendar-poll.ts
|
|
2599
2599
|
async function waitForCalendarPoll(milliseconds, signal) {
|
|
2600
2600
|
if (signal?.aborted) throw signal.reason ?? new Error("calendar connection aborted");
|
|
2601
|
-
await new Promise((
|
|
2602
|
-
const timer = setTimeout(
|
|
2601
|
+
await new Promise((resolve14, reject) => {
|
|
2602
|
+
const timer = setTimeout(resolve14, milliseconds);
|
|
2603
2603
|
signal?.addEventListener("abort", () => {
|
|
2604
2604
|
clearTimeout(timer);
|
|
2605
2605
|
reject(signal.reason ?? new Error("calendar connection aborted"));
|
|
@@ -3305,7 +3305,7 @@ async function configOperationWait(options) {
|
|
|
3305
3305
|
assertOperationId(options.operationId);
|
|
3306
3306
|
const cfg = await loadProjectConfig(options.configPath);
|
|
3307
3307
|
const client = await operationClient(cfg, options, "wait");
|
|
3308
|
-
const wait2 = options.pollWait ?? ((ms) => new Promise((
|
|
3308
|
+
const wait2 = options.pollWait ?? ((ms) => new Promise((resolve14) => setTimeout(resolve14, ms)));
|
|
3309
3309
|
const now = () => (options.now?.() ?? /* @__PURE__ */ new Date()).getTime();
|
|
3310
3310
|
const deadline = now() + (options.timeoutSeconds ?? DEFAULT_WAIT_SECONDS) * 1e3;
|
|
3311
3311
|
const interval = (options.intervalSeconds ?? DEFAULT_INTERVAL_SECONDS) * 1e3;
|
|
@@ -8156,8 +8156,8 @@ function hostedPollTimeout(value2 = 10 * 6e4) {
|
|
|
8156
8156
|
}
|
|
8157
8157
|
async function waitForHostedPoll(milliseconds, signal) {
|
|
8158
8158
|
if (signal?.aborted) throw signal.reason ?? new DOMException("aborted", "AbortError");
|
|
8159
|
-
await new Promise((
|
|
8160
|
-
const timer = setTimeout(
|
|
8159
|
+
await new Promise((resolve14, reject) => {
|
|
8160
|
+
const timer = setTimeout(resolve14, milliseconds);
|
|
8161
8161
|
signal?.addEventListener("abort", () => {
|
|
8162
8162
|
clearTimeout(timer);
|
|
8163
8163
|
reject(signal.reason ?? new DOMException("aborted", "AbortError"));
|
|
@@ -8904,13 +8904,16 @@ Usage:
|
|
|
8904
8904
|
odla-ai app rename <name> [continue in Studio; human session required]
|
|
8905
8905
|
odla-ai app owners <list|add|remove> [...] [continue in Studio; human session required]
|
|
8906
8906
|
odla-ai brand design unpack <bundle.html|-> [--out <dir>] [--json]
|
|
8907
|
-
odla-ai pm
|
|
8907
|
+
odla-ai pm project list [--app <product-id>] [--status <s>] [--json]
|
|
8908
|
+
odla-ai pm project add --app <product-id> --name <name> [--description <text>] [--json]
|
|
8909
|
+
odla-ai pm project use <project-id> [--json] [saved locally in this worktree]
|
|
8910
|
+
odla-ai pm goal list [--app <id>] [--project <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
8908
8911
|
odla-ai pm task list [--app <id>] [--column <backlog|ready|doing|review|done>] [--goal <id>] [--assignee <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
8909
8912
|
odla-ai pm decision list [--app <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
8910
8913
|
odla-ai pm bug list [--app <id>] [--status <s>] [--severity <s>] [--goal <id>] [--assignee <id>] [--decision <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
8911
8914
|
odla-ai pm goal add --app <id> --title <t> [--status <s>] [--proof <text>] [--target <pct>] [--mutation-id <id>] [--json]
|
|
8912
8915
|
odla-ai pm task add --app <id> --title <t> [--column <backlog|ready|doing|review|done>] [--goal <id>|--alignment-decision <id>] [--assignee <id>] [--description <text>|--body <text>] [--acceptance <text>] [--execution <human|agent|either>] [--due <epoch-ms>] [--mutation-id <id>] [--json]
|
|
8913
|
-
odla-ai pm next --app <id> [--json]
|
|
8916
|
+
odla-ai pm next --app <id> [--project <id>] [--json]
|
|
8914
8917
|
odla-ai pm watch --app <id> [--cursor <cursor>] [--entity goal|task|decision|bug] [--action created|updated|deleted|comment.created|comment.updated] [--state <state>] [--by <principalId>] [--self <principalId>] [--interval <s>] [--timeout <s>] [--json|--jsonl]
|
|
8915
8918
|
odla-ai pm task ready <id> --expected-revision <n> [--goal <id>|--alignment-decision <id>] [--description <text>|--body <text>] [--acceptance <text>] [--execution <human|agent|either>] [--mutation-id <id>] [--json]
|
|
8916
8919
|
odla-ai pm task claim <id> --expected-revision <n> [--mutation-id <id>] [--json]
|
|
@@ -8930,7 +8933,7 @@ Usage:
|
|
|
8930
8933
|
odla-ai pm <goal|task|decision|bug> comment <id> --body "..." [--mutation-id <id>]
|
|
8931
8934
|
odla-ai pm <goal|task|decision|bug> comments <id> [--json]
|
|
8932
8935
|
odla-ai pm <goal|task|decision|bug> rm <id>
|
|
8933
|
-
odla-ai pm handoff --app <id> [--json]
|
|
8936
|
+
odla-ai pm handoff --app <id> [--project <id>] [--json]
|
|
8934
8937
|
odla-ai discuss groups [--json]
|
|
8935
8938
|
odla-ai discuss list [--app <id>] [--q <text>] [--state open|resolved|all] [--json]
|
|
8936
8939
|
odla-ai discuss read <topic> [--limit <n> --offset <n>] [--json]
|
|
@@ -9063,13 +9066,12 @@ Commands:
|
|
|
9063
9066
|
code Enroll this Mac/Linux host for the current Studio-connected repository and run Pi.
|
|
9064
9067
|
admin Manage platform-funded AI routing/credentials/usage with narrow device grants.
|
|
9065
9068
|
security Connect GitHub sources and run commit-pinned hosted reviews, or scan a local snapshot.
|
|
9066
|
-
pm Project management (via @odla-ai/pm)
|
|
9067
|
-
|
|
9068
|
-
|
|
9069
|
-
|
|
9070
|
-
|
|
9071
|
-
|
|
9072
|
-
@odla-ai/chat discussion thread.
|
|
9069
|
+
pm Project management (via @odla-ai/pm): Products contain Projects;
|
|
9070
|
+
projects contain goals, kanban tasks, decisions, and bugs. Use
|
|
9071
|
+
"pm project list|add|use" to select worktree-local context, or
|
|
9072
|
+
pass --app/--project explicitly. Same device-grant auth as "app".
|
|
9073
|
+
Status changes and comments post to each item's @odla-ai/chat
|
|
9074
|
+
discussion thread.
|
|
9073
9075
|
bug Intent-first alias for PM bugs. "bug report" writes to
|
|
9074
9076
|
odla PM; odla product defects do not belong in GitHub Issues.
|
|
9075
9077
|
discuss Group discussions (via @odla-ai/chat) for the apps you co-own:
|
|
@@ -9476,7 +9478,7 @@ function jsonl(ctx, parsed, value2) {
|
|
|
9476
9478
|
}
|
|
9477
9479
|
async function discussWatch(ctx, topicId, parsed) {
|
|
9478
9480
|
if (ctx.json && parsed.options.jsonl === true) throw new Error("--json and --jsonl cannot be combined");
|
|
9479
|
-
const sleep = ctx.sleep ?? ((ms) => new Promise((
|
|
9481
|
+
const sleep = ctx.sleep ?? ((ms) => new Promise((resolve14) => setTimeout(resolve14, ms)));
|
|
9480
9482
|
const now = ctx.now ?? Date.now;
|
|
9481
9483
|
const intervalMs = (numberOpt2(parsed, "interval", DEFAULT_INTERVAL_MS / 1e3) ?? DEFAULT_INTERVAL_MS / 1e3) * 1e3;
|
|
9482
9484
|
const timeoutSeconds = numberOpt2(parsed, "timeout");
|
|
@@ -9824,6 +9826,8 @@ async function pmList(ctx, entity, parsed) {
|
|
|
9824
9826
|
};
|
|
9825
9827
|
const app = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
9826
9828
|
if (app) q.set("app", app);
|
|
9829
|
+
const project = stringOpt(parsed.options.project) ?? ctx.projectId;
|
|
9830
|
+
if (project) q.set("project", project);
|
|
9827
9831
|
for (const [flag, param] of Object.entries(filters)) {
|
|
9828
9832
|
const raw = stringOpt(parsed.options[flag]);
|
|
9829
9833
|
const v = entity === "task" && flag === "column" && raw === "ready" ? "todo" : raw;
|
|
@@ -9843,6 +9847,7 @@ async function pmList(ctx, entity, parsed) {
|
|
|
9843
9847
|
}
|
|
9844
9848
|
async function pmAdd(ctx, entity, parsed) {
|
|
9845
9849
|
const appId = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
9850
|
+
const projectId = stringOpt(parsed.options.project) ?? ctx.projectId;
|
|
9846
9851
|
if (!appId) throw new Error("pm add needs --app <appId>");
|
|
9847
9852
|
const input = collectEntityFields(entity, parsed, false);
|
|
9848
9853
|
if (!input.title) throw new Error("pm add needs --title <title>");
|
|
@@ -9850,6 +9855,7 @@ async function pmAdd(ctx, entity, parsed) {
|
|
|
9850
9855
|
throw new Error("pm bug add needs --description <context> (or --body <context>)");
|
|
9851
9856
|
const res = await pmRequest(ctx, "POST", `/${entity}`, {
|
|
9852
9857
|
appId,
|
|
9858
|
+
...projectId ? { projectId } : {},
|
|
9853
9859
|
input,
|
|
9854
9860
|
mutationId: writeMutationId2(parsed)
|
|
9855
9861
|
});
|
|
@@ -9929,7 +9935,7 @@ async function pmTaskLifecycle(ctx, id, action2, parsed) {
|
|
|
9929
9935
|
ctx.out.log(`task: ${label} \u2192 ${state2}`);
|
|
9930
9936
|
});
|
|
9931
9937
|
}
|
|
9932
|
-
async function allRecords(ctx, entity, appId) {
|
|
9938
|
+
async function allRecords(ctx, entity, appId, projectId) {
|
|
9933
9939
|
const records = [];
|
|
9934
9940
|
for (; ; ) {
|
|
9935
9941
|
const q = new URLSearchParams({
|
|
@@ -9937,6 +9943,7 @@ async function allRecords(ctx, entity, appId) {
|
|
|
9937
9943
|
limit: "100",
|
|
9938
9944
|
offset: String(records.length)
|
|
9939
9945
|
});
|
|
9946
|
+
if (projectId) q.set("project", projectId);
|
|
9940
9947
|
const page2 = await pmRequest(ctx, "GET", `/${entity}?${q}`);
|
|
9941
9948
|
records.push(...page2.records);
|
|
9942
9949
|
if (records.length >= page2.total || page2.records.length === 0) return records;
|
|
@@ -9944,13 +9951,15 @@ async function allRecords(ctx, entity, appId) {
|
|
|
9944
9951
|
}
|
|
9945
9952
|
async function pmNext(ctx, parsed) {
|
|
9946
9953
|
const appId = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
9954
|
+
const projectId = stringOpt(parsed.options.project) ?? ctx.projectId;
|
|
9947
9955
|
if (!appId) throw new Error("pm next needs --app <appId>");
|
|
9948
9956
|
const [goals, tasks] = await Promise.all([
|
|
9949
|
-
allRecords(ctx, "goal", appId),
|
|
9950
|
-
allRecords(ctx, "task", appId)
|
|
9957
|
+
allRecords(ctx, "goal", appId, projectId),
|
|
9958
|
+
allRecords(ctx, "task", appId, projectId)
|
|
9951
9959
|
]);
|
|
9952
9960
|
const result = {
|
|
9953
9961
|
appId,
|
|
9962
|
+
projectId,
|
|
9954
9963
|
openGoals: goals.filter((record10) => record10.status === "open"),
|
|
9955
9964
|
doing: tasks.filter((record10) => record10.column === "doing"),
|
|
9956
9965
|
ready: tasks.filter((record10) => record10.column === "todo")
|
|
@@ -9981,14 +9990,16 @@ async function pmNext(ctx, parsed) {
|
|
|
9981
9990
|
}
|
|
9982
9991
|
async function pmHandoff(ctx, parsed) {
|
|
9983
9992
|
const appId = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
9993
|
+
const projectId = stringOpt(parsed.options.project) ?? ctx.projectId;
|
|
9984
9994
|
if (!appId) throw new Error("pm handoff needs --app <appId>");
|
|
9985
9995
|
const [goals, tasks, bugs] = await Promise.all([
|
|
9986
|
-
allRecords(ctx, "goal", appId),
|
|
9987
|
-
allRecords(ctx, "task", appId),
|
|
9988
|
-
allRecords(ctx, "bug", appId)
|
|
9996
|
+
allRecords(ctx, "goal", appId, projectId),
|
|
9997
|
+
allRecords(ctx, "task", appId, projectId),
|
|
9998
|
+
allRecords(ctx, "bug", appId, projectId)
|
|
9989
9999
|
]);
|
|
9990
10000
|
const handoff = {
|
|
9991
10001
|
appId,
|
|
10002
|
+
projectId,
|
|
9992
10003
|
unmetGoals: goals.filter((record10) => record10.status !== "met"),
|
|
9993
10004
|
activeTasks: tasks.filter((record10) => record10.column !== "done"),
|
|
9994
10005
|
openBugs: bugs.filter((record10) => record10.status !== "fixed" && record10.status !== "wontfix")
|
|
@@ -10153,7 +10164,7 @@ async function pmWatch(ctx, parsed) {
|
|
|
10153
10164
|
}
|
|
10154
10165
|
const appId = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
10155
10166
|
if (!appId) throw new Error("pm watch needs --app <appId>");
|
|
10156
|
-
const sleep = ctx.sleep ?? ((ms) => new Promise((
|
|
10167
|
+
const sleep = ctx.sleep ?? ((ms) => new Promise((resolve14) => setTimeout(resolve14, ms)));
|
|
10157
10168
|
const now = ctx.now ?? Date.now;
|
|
10158
10169
|
const intervalMs = (positiveNumber(parsed, "interval", DEFAULT_INTERVAL_MS2 / 1e3) ?? DEFAULT_INTERVAL_MS2 / 1e3) * 1e3;
|
|
10159
10170
|
const timeoutSeconds = positiveNumber(parsed, "timeout");
|
|
@@ -10262,6 +10273,56 @@ async function pmWatch(ctx, parsed) {
|
|
|
10262
10273
|
}
|
|
10263
10274
|
}
|
|
10264
10275
|
|
|
10276
|
+
// src/pm-project-context.ts
|
|
10277
|
+
var import_node_path16 = require("path");
|
|
10278
|
+
var pmProjectContextFile = (rootDir) => (0, import_node_path16.resolve)(rootDir, ".odla", "pm-project.local.json");
|
|
10279
|
+
function readPmProjectContext(rootDir) {
|
|
10280
|
+
const value2 = readJsonFile(pmProjectContextFile(rootDir));
|
|
10281
|
+
return value2 && typeof value2.appId === "string" && typeof value2.projectId === "string" ? value2 : null;
|
|
10282
|
+
}
|
|
10283
|
+
function writePmProjectContext(rootDir, value2) {
|
|
10284
|
+
writePrivateJson(pmProjectContextFile(rootDir), { ...value2, selectedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
10285
|
+
}
|
|
10286
|
+
|
|
10287
|
+
// src/pm-project-actions.ts
|
|
10288
|
+
async function pmProjectList(ctx, parsed) {
|
|
10289
|
+
const q = new URLSearchParams();
|
|
10290
|
+
const app = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
10291
|
+
if (app) q.set("app", app);
|
|
10292
|
+
for (const key of ["status", "limit", "offset"]) {
|
|
10293
|
+
const value2 = stringOpt(parsed.options[key]);
|
|
10294
|
+
if (value2) q.set(key, value2);
|
|
10295
|
+
}
|
|
10296
|
+
const page2 = await pmRequest(ctx, "GET", `/project?${q}`);
|
|
10297
|
+
emit2(ctx, page2, () => {
|
|
10298
|
+
ctx.out.log(`projects \u2014 ${page2.records.length} of ${page2.total}`);
|
|
10299
|
+
ctx.out.log("id status product name");
|
|
10300
|
+
for (const project of page2.records) {
|
|
10301
|
+
ctx.out.log(`${project.id} ${project.status} ${project.appId} ${project.name}${project.isDefault ? " (default)" : ""}`);
|
|
10302
|
+
}
|
|
10303
|
+
});
|
|
10304
|
+
}
|
|
10305
|
+
async function pmProjectAdd(ctx, parsed) {
|
|
10306
|
+
const appId = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
10307
|
+
const name = stringOpt(parsed.options.name);
|
|
10308
|
+
if (!appId) throw new Error("pm project add needs --app <productId>");
|
|
10309
|
+
if (!name) throw new Error("pm project add needs --name <name>");
|
|
10310
|
+
const result = await pmRequest(ctx, "POST", "/project", {
|
|
10311
|
+
appId,
|
|
10312
|
+
name,
|
|
10313
|
+
description: stringOpt(parsed.options.description),
|
|
10314
|
+
mutationId: writeMutationId2(parsed)
|
|
10315
|
+
});
|
|
10316
|
+
emit2(ctx, result, () => ctx.out.log(`created project: ${result.project.name} (${result.project.id})`));
|
|
10317
|
+
}
|
|
10318
|
+
async function pmProjectUse(ctx, id) {
|
|
10319
|
+
if (!ctx.rootDir) throw new Error("pm project use needs a local project directory");
|
|
10320
|
+
const { project } = await pmRequest(ctx, "GET", `/project/${encodeURIComponent(id)}`);
|
|
10321
|
+
if (project.status !== "active") throw new Error(`project ${project.name} is ${project.status}, not active`);
|
|
10322
|
+
writePmProjectContext(ctx.rootDir, { appId: project.appId, projectId: project.id });
|
|
10323
|
+
emit2(ctx, project, () => ctx.out.log(`using ${project.appId} / ${project.name} (${project.id}) in this worktree`));
|
|
10324
|
+
}
|
|
10325
|
+
|
|
10265
10326
|
// src/pm-command.ts
|
|
10266
10327
|
var ALIASES = {
|
|
10267
10328
|
goal: "goal",
|
|
@@ -10273,8 +10334,8 @@ var ALIASES = {
|
|
|
10273
10334
|
};
|
|
10274
10335
|
var COMMON_OPTIONS = ["config", "token", "email", "json", "platform", "context", "open"];
|
|
10275
10336
|
var ACTION_OPTIONS = {
|
|
10276
|
-
list: ["app", "q", "limit", "offset"],
|
|
10277
|
-
add: ["app", "title", "mutation-id"],
|
|
10337
|
+
list: ["app", "project", "q", "limit", "offset"],
|
|
10338
|
+
add: ["app", "project", "title", "mutation-id"],
|
|
10278
10339
|
get: [],
|
|
10279
10340
|
set: ["mutation-id"],
|
|
10280
10341
|
done: ["mutation-id"],
|
|
@@ -10346,6 +10407,9 @@ async function buildContext2(parsed, deps) {
|
|
|
10346
10407
|
doFetch,
|
|
10347
10408
|
out
|
|
10348
10409
|
);
|
|
10410
|
+
const selectedProject = readPmProjectContext(cfg.rootDir);
|
|
10411
|
+
const requestedApp = stringOpt(parsed.options.app) ?? (context.app.source === "environment" || context.app.source === "profile" ? context.app.value ?? void 0 : void 0);
|
|
10412
|
+
const compatibleProject = selectedProject && (!requestedApp || selectedProject.appId === requestedApp) ? selectedProject : null;
|
|
10349
10413
|
return {
|
|
10350
10414
|
platformUrl: cfg.platformUrl,
|
|
10351
10415
|
token,
|
|
@@ -10354,15 +10418,33 @@ async function buildContext2(parsed, deps) {
|
|
|
10354
10418
|
json: parsed.options.json === true,
|
|
10355
10419
|
// Preserve PM's existing cross-project default when a config is present;
|
|
10356
10420
|
// only an explicit remote-agent environment or profile selects an app.
|
|
10357
|
-
appId: context.app.source === "environment" || context.app.source === "profile" ? context.app.value ?? void 0 :
|
|
10421
|
+
appId: context.app.source === "environment" || context.app.source === "profile" ? context.app.value ?? void 0 : compatibleProject?.appId,
|
|
10422
|
+
projectId: compatibleProject?.projectId,
|
|
10423
|
+
rootDir: cfg.rootDir,
|
|
10358
10424
|
...deps.sleep ? { sleep: deps.sleep } : {},
|
|
10359
10425
|
...deps.now ? { now: deps.now } : {}
|
|
10360
10426
|
};
|
|
10361
10427
|
}
|
|
10362
10428
|
async function pmCommand(parsed, deps = {}) {
|
|
10363
10429
|
const word = parsed.positionals[1] ?? "";
|
|
10430
|
+
if (word === "project") {
|
|
10431
|
+
const action3 = parsed.positionals[2] ?? "list";
|
|
10432
|
+
if (action3 === "list") {
|
|
10433
|
+
assertArgs(parsed, [...COMMON_OPTIONS, "app", "status", "limit", "offset"], 3);
|
|
10434
|
+
return pmProjectList(await buildContext2(parsed, deps), parsed);
|
|
10435
|
+
}
|
|
10436
|
+
if (action3 === "add" || action3 === "create") {
|
|
10437
|
+
assertArgs(parsed, [...COMMON_OPTIONS, "app", "name", "description", "mutation-id"], 3);
|
|
10438
|
+
return pmProjectAdd(await buildContext2(parsed, deps), parsed);
|
|
10439
|
+
}
|
|
10440
|
+
if (action3 === "use") {
|
|
10441
|
+
assertArgs(parsed, COMMON_OPTIONS, 4);
|
|
10442
|
+
return pmProjectUse(await buildContext2(parsed, deps), requireId2(parsed.positionals[3], action3));
|
|
10443
|
+
}
|
|
10444
|
+
throw new Error(`unknown pm project action "${action3}". Try list|add|use.`);
|
|
10445
|
+
}
|
|
10364
10446
|
if (word === "next") {
|
|
10365
|
-
assertArgs(parsed, [...COMMON_OPTIONS, "app"], 2);
|
|
10447
|
+
assertArgs(parsed, [...COMMON_OPTIONS, "app", "project"], 2);
|
|
10366
10448
|
return pmNext(await buildContext2(parsed, deps), parsed);
|
|
10367
10449
|
}
|
|
10368
10450
|
if (word === "watch") {
|
|
@@ -10382,7 +10464,7 @@ async function pmCommand(parsed, deps = {}) {
|
|
|
10382
10464
|
return pmWatch(await buildContext2(parsed, deps), parsed).then(() => void 0);
|
|
10383
10465
|
}
|
|
10384
10466
|
if (word === "handoff") {
|
|
10385
|
-
assertArgs(parsed, [...COMMON_OPTIONS, "app"], 2);
|
|
10467
|
+
assertArgs(parsed, [...COMMON_OPTIONS, "app", "project"], 2);
|
|
10386
10468
|
return pmHandoff(await buildContext2(parsed, deps), parsed);
|
|
10387
10469
|
}
|
|
10388
10470
|
const entity = ALIASES[word];
|
|
@@ -11514,6 +11596,7 @@ var COMMAND_SURFACE = {
|
|
|
11514
11596
|
},
|
|
11515
11597
|
pm: {
|
|
11516
11598
|
...PM_ENTITIES,
|
|
11599
|
+
project: { list: {}, add: {}, create: {}, use: {} },
|
|
11517
11600
|
handoff: {},
|
|
11518
11601
|
next: {},
|
|
11519
11602
|
watch: {}
|
|
@@ -11794,7 +11877,7 @@ async function runbookRemove(ctx, slug) {
|
|
|
11794
11877
|
|
|
11795
11878
|
// src/runbook-import.ts
|
|
11796
11879
|
var import_node_fs17 = require("fs");
|
|
11797
|
-
var
|
|
11880
|
+
var import_node_path17 = require("path");
|
|
11798
11881
|
function parseRunbook(text2, slug) {
|
|
11799
11882
|
let rest = text2;
|
|
11800
11883
|
const meta = {};
|
|
@@ -11823,8 +11906,8 @@ function readRunbookDir(dir) {
|
|
|
11823
11906
|
const files = (0, import_node_fs17.readdirSync)(dir).filter((f) => f.endsWith(".md")).sort();
|
|
11824
11907
|
if (!files.length) throw new Error(`no .md files in ${dir}`);
|
|
11825
11908
|
return files.map((file) => {
|
|
11826
|
-
const slug = (0,
|
|
11827
|
-
const parsed = parseRunbook((0, import_node_fs17.readFileSync)((0,
|
|
11909
|
+
const slug = (0, import_node_path17.basename)(file, ".md");
|
|
11910
|
+
const parsed = parseRunbook((0, import_node_fs17.readFileSync)((0, import_node_path17.join)(dir, file), "utf8"), slug);
|
|
11828
11911
|
return { file, slug, ...parsed, words: parsed.body.split(/\s+/).filter(Boolean).length };
|
|
11829
11912
|
});
|
|
11830
11913
|
}
|
|
@@ -11898,7 +11981,7 @@ async function upsert(ctx, r, visibility) {
|
|
|
11898
11981
|
// src/runbook-impact.ts
|
|
11899
11982
|
var import_node_child_process7 = require("child_process");
|
|
11900
11983
|
var import_node_fs18 = require("fs");
|
|
11901
|
-
var
|
|
11984
|
+
var import_node_path18 = require("path");
|
|
11902
11985
|
|
|
11903
11986
|
// src/runbook-impact-scan.ts
|
|
11904
11987
|
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$]*)/;
|
|
@@ -12067,7 +12150,7 @@ ${body.split("\n").map((line) => `+${line}`).join("\n")}
|
|
|
12067
12150
|
}
|
|
12068
12151
|
function manifestLabeller(root) {
|
|
12069
12152
|
return (workspace) => {
|
|
12070
|
-
const manifest = (0,
|
|
12153
|
+
const manifest = (0, import_node_path18.join)(root, workspace, "package.json");
|
|
12071
12154
|
if (!(0, import_node_fs18.existsSync)(manifest)) return void 0;
|
|
12072
12155
|
try {
|
|
12073
12156
|
const name = JSON.parse((0, import_node_fs18.readFileSync)(manifest, "utf8")).name;
|
|
@@ -12137,7 +12220,7 @@ function report3(ctx, impacts) {
|
|
|
12137
12220
|
async function runbookImpact(ctx, options, deps = {}) {
|
|
12138
12221
|
const cwd = deps.cwd ?? process.cwd();
|
|
12139
12222
|
const runGit = deps.runGit ?? gitRunner(cwd);
|
|
12140
|
-
const read3 = deps.readRepoFile ?? ((path) => (0, import_node_fs18.readFileSync)((0,
|
|
12223
|
+
const read3 = deps.readRepoFile ?? ((path) => (0, import_node_fs18.readFileSync)((0, import_node_path18.join)(cwd, path), "utf8"));
|
|
12141
12224
|
const surfaces = changedSurfaces(collectDiff(runGit, options.base, read3), manifestLabeller(cwd));
|
|
12142
12225
|
if (!surfaces.length) {
|
|
12143
12226
|
return ctx.out.log(
|
|
@@ -12272,7 +12355,7 @@ async function runbookComment(ctx, slug, body) {
|
|
|
12272
12355
|
var import_node_child_process8 = require("child_process");
|
|
12273
12356
|
var import_node_fs19 = require("fs");
|
|
12274
12357
|
var import_node_os5 = require("os");
|
|
12275
|
-
var
|
|
12358
|
+
var import_node_path19 = require("path");
|
|
12276
12359
|
var import_node_process14 = __toESM(require("process"), 1);
|
|
12277
12360
|
var EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
|
|
12278
12361
|
function resolveEditor(env = import_node_process14.default.env) {
|
|
@@ -12298,8 +12381,8 @@ function editText(initial, slug, deps = {}) {
|
|
|
12298
12381
|
);
|
|
12299
12382
|
if (!interactive())
|
|
12300
12383
|
throw new Error(`cannot open an editor without a terminal \u2014 pass --file <path> or --body "\u2026" instead`);
|
|
12301
|
-
const dir = (0, import_node_fs19.mkdtempSync)((0,
|
|
12302
|
-
const file = (0,
|
|
12384
|
+
const dir = (0, import_node_fs19.mkdtempSync)((0, import_node_path19.join)((0, import_node_os5.tmpdir)(), "odla-runbook-"));
|
|
12385
|
+
const file = (0, import_node_path19.join)(dir, `${slug}.md`);
|
|
12303
12386
|
try {
|
|
12304
12387
|
(0, import_node_fs19.writeFileSync)(file, initial, { mode: 384 });
|
|
12305
12388
|
const code = defaultRunOrInjected(deps)(editor, file);
|
|
@@ -12652,7 +12735,7 @@ function hostedSeverity(value2, flag) {
|
|
|
12652
12735
|
var import_security2 = require("@odla-ai/security");
|
|
12653
12736
|
|
|
12654
12737
|
// src/security.ts
|
|
12655
|
-
var
|
|
12738
|
+
var import_node_path20 = require("path");
|
|
12656
12739
|
var import_security = require("@odla-ai/security");
|
|
12657
12740
|
var import_node3 = require("@odla-ai/security/node");
|
|
12658
12741
|
async function runHostedSecurity(options) {
|
|
@@ -12664,9 +12747,9 @@ async function runHostedSecurity(options) {
|
|
|
12664
12747
|
const appId = selfAudit ? "odla-ai" : cfg.app.id;
|
|
12665
12748
|
const env = selfAudit ? "prod" : selectEnv(options.env, cfg.envs, cfg.configPath, cfg.rootDir);
|
|
12666
12749
|
const platform = options.platform ?? cfg?.platformUrl ?? "https://odla.ai";
|
|
12667
|
-
const target = (0,
|
|
12668
|
-
const output = (0,
|
|
12669
|
-
const outputRelative = (0,
|
|
12750
|
+
const target = (0, import_node_path20.resolve)(options.target ?? cfg?.rootDir ?? ".");
|
|
12751
|
+
const output = (0, import_node_path20.resolve)(options.out ?? (0, import_node_path20.resolve)(target, ".odla/security/hosted"));
|
|
12752
|
+
const outputRelative = (0, import_node_path20.relative)(target, output).split(import_node_path20.sep).join("/");
|
|
12670
12753
|
if (!outputRelative) throw new Error("Hosted security output cannot be the repository root");
|
|
12671
12754
|
const profile = profileFor(options.profile ?? "odla", options.maxHuntTasks ?? 12);
|
|
12672
12755
|
const tokenRequest = {
|
|
@@ -12678,7 +12761,7 @@ async function runHostedSecurity(options) {
|
|
|
12678
12761
|
};
|
|
12679
12762
|
const token = await injectedToken(options, tokenRequest);
|
|
12680
12763
|
const snapshot = await (0, import_node3.snapshotDirectory)(target, {
|
|
12681
|
-
exclude: !outputRelative.startsWith("../") && !(0,
|
|
12764
|
+
exclude: !outputRelative.startsWith("../") && !(0, import_node_path20.isAbsolute)(outputRelative) ? [outputRelative] : []
|
|
12682
12765
|
});
|
|
12683
12766
|
const hosted = await (0, import_security.createPlatformSecurityReasoners)({
|
|
12684
12767
|
platform,
|
|
@@ -12696,7 +12779,7 @@ async function runHostedSecurity(options) {
|
|
|
12696
12779
|
});
|
|
12697
12780
|
const harness = (0, import_security.createSecurityHarness)({
|
|
12698
12781
|
profile,
|
|
12699
|
-
store: new import_node3.FileRunStore((0,
|
|
12782
|
+
store: new import_node3.FileRunStore((0, import_node_path20.resolve)(output, "state")),
|
|
12700
12783
|
discoveryReasoner: hosted.discoveryReasoner,
|
|
12701
12784
|
validationReasoner: hosted.validationReasoner,
|
|
12702
12785
|
policy: {
|
|
@@ -12720,7 +12803,7 @@ async function runHostedSecurity(options) {
|
|
|
12720
12803
|
function selectEnv(requested, declared, configPath, rootDir) {
|
|
12721
12804
|
const env = requested ?? (declared.includes("dev") ? "dev" : declared[0]);
|
|
12722
12805
|
if (!env || !declared.includes(env)) {
|
|
12723
|
-
const shown = (0,
|
|
12806
|
+
const shown = (0, import_node_path20.relative)(rootDir, configPath) || configPath;
|
|
12724
12807
|
throw new Error(`env "${env ?? ""}" is not declared in ${shown}`);
|
|
12725
12808
|
}
|
|
12726
12809
|
return env;
|
|
@@ -12749,7 +12832,7 @@ function printSummary(out, appId, env, run, report4, output) {
|
|
|
12749
12832
|
out.log(` coverage: ${report4.coverageStatus} ${complete}/${report4.coverage.length} blocked=${report4.metrics.blockedCells} shallow=${report4.metrics.shallowCells} unscheduled=${report4.metrics.unscheduledCells} budget_exhausted=${report4.metrics.budgetExhaustedCells}`);
|
|
12750
12833
|
if (report4.callBudget) out.log(` calls: discovery=${formatBudget(report4.callBudget.discovery)} validation=${formatBudget(report4.callBudget.validation)}`);
|
|
12751
12834
|
out.log(` findings: confirmed=${report4.metrics.confirmed} needs_reproduction=${report4.metrics.needsReproduction} candidates=${report4.metrics.candidates}`);
|
|
12752
|
-
out.log(` report: ${(0,
|
|
12835
|
+
out.log(` report: ${(0, import_node_path20.resolve)(output, "REPORT.md")}`);
|
|
12753
12836
|
}
|
|
12754
12837
|
function formatBudget(usage) {
|
|
12755
12838
|
return usage ? `${usage.usedCalls}/${usage.maxCalls} skipped=${usage.skippedCalls}` : "caller-managed";
|