@odla-ai/cli 0.30.3 → 0.31.1
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 +154 -52
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-3HFNKDZV.js → chunk-Y3W7YKWI.js} +120 -37
- package/dist/chunk-Y3W7YKWI.js.map +1 -0
- package/dist/{cli-BGSX5Z4C.js → cli-Q2U7TCMV.js} +2 -2
- package/dist/index.cjs +131 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-3HFNKDZV.js.map +0 -1
- /package/dist/{cli-BGSX5Z4C.js.map → cli-Q2U7TCMV.js.map} +0 -0
package/README.md
CHANGED
|
@@ -96,7 +96,10 @@ app is registered, start each session with the read-only aligned-work intake,
|
|
|
96
96
|
then read open bugs and recent decisions:
|
|
97
97
|
|
|
98
98
|
```bash
|
|
99
|
-
npx odla-ai pm
|
|
99
|
+
npx odla-ai pm project list --app <productId>
|
|
100
|
+
npx odla-ai pm project add --app <productId> --name "My project"
|
|
101
|
+
npx odla-ai pm project use <projectId>
|
|
102
|
+
npx odla-ai pm next --app <appId> --project <projectId>
|
|
100
103
|
npx odla-ai pm handoff --app <appId>
|
|
101
104
|
npx odla-ai pm watch --app <appId> --entity task --jsonl
|
|
102
105
|
npx odla-ai pm bug list --app <appId> --status open
|
package/dist/bin.cjs
CHANGED
|
@@ -233,7 +233,7 @@ var init_approval_prompt = __esm({
|
|
|
233
233
|
async function openUrl(url, options = {}) {
|
|
234
234
|
const command = openerFor(options.platform ?? import_node_process.default.platform);
|
|
235
235
|
const doSpawn = options.spawnImpl ?? import_node_child_process.spawn;
|
|
236
|
-
await new Promise((
|
|
236
|
+
await new Promise((resolve15, reject) => {
|
|
237
237
|
const child = doSpawn(command.cmd, [...command.args, url], {
|
|
238
238
|
stdio: "ignore",
|
|
239
239
|
detached: true
|
|
@@ -241,7 +241,7 @@ async function openUrl(url, options = {}) {
|
|
|
241
241
|
child.once("error", reject);
|
|
242
242
|
child.once("spawn", () => {
|
|
243
243
|
child.unref();
|
|
244
|
-
|
|
244
|
+
resolve15();
|
|
245
245
|
});
|
|
246
246
|
});
|
|
247
247
|
}
|
|
@@ -570,7 +570,7 @@ async function freshHandshake(ctx, waitMs) {
|
|
|
570
570
|
} catch (err) {
|
|
571
571
|
const code = err instanceof import_db.OdlaError ? err.code : void 0;
|
|
572
572
|
if (code === "handshake_pending" && started) throw stillPending(started, ctx.email);
|
|
573
|
-
if (code === "handshake_denied" || code === "handshake_expired" || code === "handshake_timeout") {
|
|
573
|
+
if (code === "handshake_duplicate" || code === "handshake_denied" || code === "handshake_expired" || code === "handshake_timeout") {
|
|
574
574
|
clearPendingHandshake(ctx.pendingFile);
|
|
575
575
|
}
|
|
576
576
|
throw err;
|
|
@@ -2921,8 +2921,8 @@ var init_calendar_http = __esm({
|
|
|
2921
2921
|
// src/calendar-poll.ts
|
|
2922
2922
|
async function waitForCalendarPoll(milliseconds, signal) {
|
|
2923
2923
|
if (signal?.aborted) throw signal.reason ?? new Error("calendar connection aborted");
|
|
2924
|
-
await new Promise((
|
|
2925
|
-
const timer = setTimeout(
|
|
2924
|
+
await new Promise((resolve15, reject) => {
|
|
2925
|
+
const timer = setTimeout(resolve15, milliseconds);
|
|
2926
2926
|
signal?.addEventListener("abort", () => {
|
|
2927
2927
|
clearTimeout(timer);
|
|
2928
2928
|
reject(signal.reason ?? new Error("calendar connection aborted"));
|
|
@@ -3694,7 +3694,7 @@ async function configOperationWait(options) {
|
|
|
3694
3694
|
assertOperationId(options.operationId);
|
|
3695
3695
|
const cfg = await loadProjectConfig(options.configPath);
|
|
3696
3696
|
const client = await operationClient(cfg, options, "wait");
|
|
3697
|
-
const wait2 = options.pollWait ?? ((ms) => new Promise((
|
|
3697
|
+
const wait2 = options.pollWait ?? ((ms) => new Promise((resolve15) => setTimeout(resolve15, ms)));
|
|
3698
3698
|
const now = () => (options.now?.() ?? /* @__PURE__ */ new Date()).getTime();
|
|
3699
3699
|
const deadline = now() + (options.timeoutSeconds ?? DEFAULT_WAIT_SECONDS) * 1e3;
|
|
3700
3700
|
const interval = (options.intervalSeconds ?? DEFAULT_INTERVAL_SECONDS) * 1e3;
|
|
@@ -8802,8 +8802,8 @@ function hostedPollTimeout(value2 = 10 * 6e4) {
|
|
|
8802
8802
|
}
|
|
8803
8803
|
async function waitForHostedPoll(milliseconds, signal) {
|
|
8804
8804
|
if (signal?.aborted) throw signal.reason ?? new DOMException("aborted", "AbortError");
|
|
8805
|
-
await new Promise((
|
|
8806
|
-
const timer = setTimeout(
|
|
8805
|
+
await new Promise((resolve15, reject) => {
|
|
8806
|
+
const timer = setTimeout(resolve15, milliseconds);
|
|
8807
8807
|
signal?.addEventListener("abort", () => {
|
|
8808
8808
|
clearTimeout(timer);
|
|
8809
8809
|
reject(signal.reason ?? new DOMException("aborted", "AbortError"));
|
|
@@ -9593,13 +9593,16 @@ Usage:
|
|
|
9593
9593
|
odla-ai app rename <name> [continue in Studio; human session required]
|
|
9594
9594
|
odla-ai app owners <list|add|remove> [...] [continue in Studio; human session required]
|
|
9595
9595
|
odla-ai brand design unpack <bundle.html|-> [--out <dir>] [--json]
|
|
9596
|
-
odla-ai pm
|
|
9596
|
+
odla-ai pm project list [--app <product-id>] [--status <s>] [--json]
|
|
9597
|
+
odla-ai pm project add --app <product-id> --name <name> [--description <text>] [--json]
|
|
9598
|
+
odla-ai pm project use <project-id> [--json] [saved locally in this worktree]
|
|
9599
|
+
odla-ai pm goal list [--app <id>] [--project <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
9597
9600
|
odla-ai pm task list [--app <id>] [--column <backlog|ready|doing|review|done>] [--goal <id>] [--assignee <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
9598
9601
|
odla-ai pm decision list [--app <id>] [--status <s>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
9599
9602
|
odla-ai pm bug list [--app <id>] [--status <s>] [--severity <s>] [--goal <id>] [--assignee <id>] [--decision <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
9600
9603
|
odla-ai pm goal add --app <id> --title <t> [--status <s>] [--proof <text>] [--target <pct>] [--mutation-id <id>] [--json]
|
|
9601
9604
|
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]
|
|
9602
|
-
odla-ai pm next --app <id> [--json]
|
|
9605
|
+
odla-ai pm next --app <id> [--project <id>] [--json]
|
|
9603
9606
|
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]
|
|
9604
9607
|
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]
|
|
9605
9608
|
odla-ai pm task claim <id> --expected-revision <n> [--mutation-id <id>] [--json]
|
|
@@ -9619,7 +9622,7 @@ Usage:
|
|
|
9619
9622
|
odla-ai pm <goal|task|decision|bug> comment <id> --body "..." [--mutation-id <id>]
|
|
9620
9623
|
odla-ai pm <goal|task|decision|bug> comments <id> [--json]
|
|
9621
9624
|
odla-ai pm <goal|task|decision|bug> rm <id>
|
|
9622
|
-
odla-ai pm handoff --app <id> [--json]
|
|
9625
|
+
odla-ai pm handoff --app <id> [--project <id>] [--json]
|
|
9623
9626
|
odla-ai discuss groups [--json]
|
|
9624
9627
|
odla-ai discuss list [--app <id>] [--q <text>] [--state open|resolved|all] [--json]
|
|
9625
9628
|
odla-ai discuss read <topic> [--limit <n> --offset <n>] [--json]
|
|
@@ -9754,13 +9757,12 @@ Commands:
|
|
|
9754
9757
|
code Enroll this Mac/Linux host for the current Studio-connected repository and run Pi.
|
|
9755
9758
|
admin Manage platform-funded AI routing/credentials/usage with narrow device grants.
|
|
9756
9759
|
security Connect GitHub sources and run commit-pinned hosted reviews, or scan a local snapshot.
|
|
9757
|
-
pm Project management (via @odla-ai/pm)
|
|
9758
|
-
|
|
9759
|
-
|
|
9760
|
-
|
|
9761
|
-
|
|
9762
|
-
|
|
9763
|
-
@odla-ai/chat discussion thread.
|
|
9760
|
+
pm Project management (via @odla-ai/pm): Products contain Projects;
|
|
9761
|
+
projects contain goals, kanban tasks, decisions, and bugs. Use
|
|
9762
|
+
"pm project list|add|use" to select worktree-local context, or
|
|
9763
|
+
pass --app/--project explicitly. Same device-grant auth as "app".
|
|
9764
|
+
Status changes and comments post to each item's @odla-ai/chat
|
|
9765
|
+
discussion thread.
|
|
9764
9766
|
bug Intent-first alias for PM bugs. "bug report" writes to
|
|
9765
9767
|
odla PM; odla product defects do not belong in GitHub Issues.
|
|
9766
9768
|
discuss Group discussions (via @odla-ai/chat) for the apps you co-own:
|
|
@@ -10202,7 +10204,7 @@ function jsonl(ctx, parsed, value2) {
|
|
|
10202
10204
|
}
|
|
10203
10205
|
async function discussWatch(ctx, topicId, parsed) {
|
|
10204
10206
|
if (ctx.json && parsed.options.jsonl === true) throw new Error("--json and --jsonl cannot be combined");
|
|
10205
|
-
const sleep = ctx.sleep ?? ((ms) => new Promise((
|
|
10207
|
+
const sleep = ctx.sleep ?? ((ms) => new Promise((resolve15) => setTimeout(resolve15, ms)));
|
|
10206
10208
|
const now = ctx.now ?? Date.now;
|
|
10207
10209
|
const intervalMs = (numberOpt2(parsed, "interval", DEFAULT_INTERVAL_MS / 1e3) ?? DEFAULT_INTERVAL_MS / 1e3) * 1e3;
|
|
10208
10210
|
const timeoutSeconds = numberOpt2(parsed, "timeout");
|
|
@@ -10584,6 +10586,8 @@ async function pmList(ctx, entity, parsed) {
|
|
|
10584
10586
|
};
|
|
10585
10587
|
const app = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
10586
10588
|
if (app) q.set("app", app);
|
|
10589
|
+
const project = stringOpt(parsed.options.project) ?? ctx.projectId;
|
|
10590
|
+
if (project) q.set("project", project);
|
|
10587
10591
|
for (const [flag, param] of Object.entries(filters)) {
|
|
10588
10592
|
const raw = stringOpt(parsed.options[flag]);
|
|
10589
10593
|
const v = entity === "task" && flag === "column" && raw === "ready" ? "todo" : raw;
|
|
@@ -10603,6 +10607,7 @@ async function pmList(ctx, entity, parsed) {
|
|
|
10603
10607
|
}
|
|
10604
10608
|
async function pmAdd(ctx, entity, parsed) {
|
|
10605
10609
|
const appId = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
10610
|
+
const projectId = stringOpt(parsed.options.project) ?? ctx.projectId;
|
|
10606
10611
|
if (!appId) throw new Error("pm add needs --app <appId>");
|
|
10607
10612
|
const input = collectEntityFields(entity, parsed, false);
|
|
10608
10613
|
if (!input.title) throw new Error("pm add needs --title <title>");
|
|
@@ -10610,6 +10615,7 @@ async function pmAdd(ctx, entity, parsed) {
|
|
|
10610
10615
|
throw new Error("pm bug add needs --description <context> (or --body <context>)");
|
|
10611
10616
|
const res = await pmRequest(ctx, "POST", `/${entity}`, {
|
|
10612
10617
|
appId,
|
|
10618
|
+
...projectId ? { projectId } : {},
|
|
10613
10619
|
input,
|
|
10614
10620
|
mutationId: writeMutationId2(parsed)
|
|
10615
10621
|
});
|
|
@@ -10689,7 +10695,7 @@ async function pmTaskLifecycle(ctx, id, action2, parsed) {
|
|
|
10689
10695
|
ctx.out.log(`task: ${label} \u2192 ${state2}`);
|
|
10690
10696
|
});
|
|
10691
10697
|
}
|
|
10692
|
-
async function allRecords(ctx, entity, appId) {
|
|
10698
|
+
async function allRecords(ctx, entity, appId, projectId) {
|
|
10693
10699
|
const records = [];
|
|
10694
10700
|
for (; ; ) {
|
|
10695
10701
|
const q = new URLSearchParams({
|
|
@@ -10697,6 +10703,7 @@ async function allRecords(ctx, entity, appId) {
|
|
|
10697
10703
|
limit: "100",
|
|
10698
10704
|
offset: String(records.length)
|
|
10699
10705
|
});
|
|
10706
|
+
if (projectId) q.set("project", projectId);
|
|
10700
10707
|
const page2 = await pmRequest(ctx, "GET", `/${entity}?${q}`);
|
|
10701
10708
|
records.push(...page2.records);
|
|
10702
10709
|
if (records.length >= page2.total || page2.records.length === 0) return records;
|
|
@@ -10704,13 +10711,15 @@ async function allRecords(ctx, entity, appId) {
|
|
|
10704
10711
|
}
|
|
10705
10712
|
async function pmNext(ctx, parsed) {
|
|
10706
10713
|
const appId = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
10714
|
+
const projectId = stringOpt(parsed.options.project) ?? ctx.projectId;
|
|
10707
10715
|
if (!appId) throw new Error("pm next needs --app <appId>");
|
|
10708
10716
|
const [goals, tasks] = await Promise.all([
|
|
10709
|
-
allRecords(ctx, "goal", appId),
|
|
10710
|
-
allRecords(ctx, "task", appId)
|
|
10717
|
+
allRecords(ctx, "goal", appId, projectId),
|
|
10718
|
+
allRecords(ctx, "task", appId, projectId)
|
|
10711
10719
|
]);
|
|
10712
10720
|
const result = {
|
|
10713
10721
|
appId,
|
|
10722
|
+
projectId,
|
|
10714
10723
|
openGoals: goals.filter((record10) => record10.status === "open"),
|
|
10715
10724
|
doing: tasks.filter((record10) => record10.column === "doing"),
|
|
10716
10725
|
ready: tasks.filter((record10) => record10.column === "todo")
|
|
@@ -10741,14 +10750,16 @@ async function pmNext(ctx, parsed) {
|
|
|
10741
10750
|
}
|
|
10742
10751
|
async function pmHandoff(ctx, parsed) {
|
|
10743
10752
|
const appId = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
10753
|
+
const projectId = stringOpt(parsed.options.project) ?? ctx.projectId;
|
|
10744
10754
|
if (!appId) throw new Error("pm handoff needs --app <appId>");
|
|
10745
10755
|
const [goals, tasks, bugs] = await Promise.all([
|
|
10746
|
-
allRecords(ctx, "goal", appId),
|
|
10747
|
-
allRecords(ctx, "task", appId),
|
|
10748
|
-
allRecords(ctx, "bug", appId)
|
|
10756
|
+
allRecords(ctx, "goal", appId, projectId),
|
|
10757
|
+
allRecords(ctx, "task", appId, projectId),
|
|
10758
|
+
allRecords(ctx, "bug", appId, projectId)
|
|
10749
10759
|
]);
|
|
10750
10760
|
const handoff = {
|
|
10751
10761
|
appId,
|
|
10762
|
+
projectId,
|
|
10752
10763
|
unmetGoals: goals.filter((record10) => record10.status !== "met"),
|
|
10753
10764
|
activeTasks: tasks.filter((record10) => record10.column !== "done"),
|
|
10754
10765
|
openBugs: bugs.filter((record10) => record10.status !== "fixed" && record10.status !== "wontfix")
|
|
@@ -10940,7 +10951,7 @@ async function pmWatch(ctx, parsed) {
|
|
|
10940
10951
|
}
|
|
10941
10952
|
const appId = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
10942
10953
|
if (!appId) throw new Error("pm watch needs --app <appId>");
|
|
10943
|
-
const sleep = ctx.sleep ?? ((ms) => new Promise((
|
|
10954
|
+
const sleep = ctx.sleep ?? ((ms) => new Promise((resolve15) => setTimeout(resolve15, ms)));
|
|
10944
10955
|
const now = ctx.now ?? Date.now;
|
|
10945
10956
|
const intervalMs = (positiveNumber(parsed, "interval", DEFAULT_INTERVAL_MS2 / 1e3) ?? DEFAULT_INTERVAL_MS2 / 1e3) * 1e3;
|
|
10946
10957
|
const timeoutSeconds = positiveNumber(parsed, "timeout");
|
|
@@ -11061,6 +11072,73 @@ var init_pm_watch = __esm({
|
|
|
11061
11072
|
}
|
|
11062
11073
|
});
|
|
11063
11074
|
|
|
11075
|
+
// src/pm-project-context.ts
|
|
11076
|
+
function readPmProjectContext(rootDir) {
|
|
11077
|
+
const value2 = readJsonFile(pmProjectContextFile(rootDir));
|
|
11078
|
+
return value2 && typeof value2.appId === "string" && typeof value2.projectId === "string" ? value2 : null;
|
|
11079
|
+
}
|
|
11080
|
+
function writePmProjectContext(rootDir, value2) {
|
|
11081
|
+
writePrivateJson(pmProjectContextFile(rootDir), { ...value2, selectedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
11082
|
+
}
|
|
11083
|
+
var import_node_path17, pmProjectContextFile;
|
|
11084
|
+
var init_pm_project_context = __esm({
|
|
11085
|
+
"src/pm-project-context.ts"() {
|
|
11086
|
+
"use strict";
|
|
11087
|
+
init_cjs_shims();
|
|
11088
|
+
import_node_path17 = require("path");
|
|
11089
|
+
init_local();
|
|
11090
|
+
pmProjectContextFile = (rootDir) => (0, import_node_path17.resolve)(rootDir, ".odla", "pm-project.local.json");
|
|
11091
|
+
}
|
|
11092
|
+
});
|
|
11093
|
+
|
|
11094
|
+
// src/pm-project-actions.ts
|
|
11095
|
+
async function pmProjectList(ctx, parsed) {
|
|
11096
|
+
const q = new URLSearchParams();
|
|
11097
|
+
const app = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
11098
|
+
if (app) q.set("app", app);
|
|
11099
|
+
for (const key of ["status", "limit", "offset"]) {
|
|
11100
|
+
const value2 = stringOpt(parsed.options[key]);
|
|
11101
|
+
if (value2) q.set(key, value2);
|
|
11102
|
+
}
|
|
11103
|
+
const page2 = await pmRequest(ctx, "GET", `/project?${q}`);
|
|
11104
|
+
emit2(ctx, page2, () => {
|
|
11105
|
+
ctx.out.log(`projects \u2014 ${page2.records.length} of ${page2.total}`);
|
|
11106
|
+
ctx.out.log("id status product name");
|
|
11107
|
+
for (const project of page2.records) {
|
|
11108
|
+
ctx.out.log(`${project.id} ${project.status} ${project.appId} ${project.name}${project.isDefault ? " (default)" : ""}`);
|
|
11109
|
+
}
|
|
11110
|
+
});
|
|
11111
|
+
}
|
|
11112
|
+
async function pmProjectAdd(ctx, parsed) {
|
|
11113
|
+
const appId = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
11114
|
+
const name = stringOpt(parsed.options.name);
|
|
11115
|
+
if (!appId) throw new Error("pm project add needs --app <productId>");
|
|
11116
|
+
if (!name) throw new Error("pm project add needs --name <name>");
|
|
11117
|
+
const result = await pmRequest(ctx, "POST", "/project", {
|
|
11118
|
+
appId,
|
|
11119
|
+
name,
|
|
11120
|
+
description: stringOpt(parsed.options.description),
|
|
11121
|
+
mutationId: writeMutationId2(parsed)
|
|
11122
|
+
});
|
|
11123
|
+
emit2(ctx, result, () => ctx.out.log(`created project: ${result.project.name} (${result.project.id})`));
|
|
11124
|
+
}
|
|
11125
|
+
async function pmProjectUse(ctx, id) {
|
|
11126
|
+
if (!ctx.rootDir) throw new Error("pm project use needs a local project directory");
|
|
11127
|
+
const { project } = await pmRequest(ctx, "GET", `/project/${encodeURIComponent(id)}`);
|
|
11128
|
+
if (project.status !== "active") throw new Error(`project ${project.name} is ${project.status}, not active`);
|
|
11129
|
+
writePmProjectContext(ctx.rootDir, { appId: project.appId, projectId: project.id });
|
|
11130
|
+
emit2(ctx, project, () => ctx.out.log(`using ${project.appId} / ${project.name} (${project.id}) in this worktree`));
|
|
11131
|
+
}
|
|
11132
|
+
var init_pm_project_actions = __esm({
|
|
11133
|
+
"src/pm-project-actions.ts"() {
|
|
11134
|
+
"use strict";
|
|
11135
|
+
init_cjs_shims();
|
|
11136
|
+
init_argv();
|
|
11137
|
+
init_pm_action_core();
|
|
11138
|
+
init_pm_project_context();
|
|
11139
|
+
}
|
|
11140
|
+
});
|
|
11141
|
+
|
|
11064
11142
|
// src/pm-command.ts
|
|
11065
11143
|
function canonicalAction(action2) {
|
|
11066
11144
|
if (action2 === "create") return "add";
|
|
@@ -11095,6 +11173,9 @@ async function buildContext2(parsed, deps) {
|
|
|
11095
11173
|
doFetch,
|
|
11096
11174
|
out
|
|
11097
11175
|
);
|
|
11176
|
+
const selectedProject = readPmProjectContext(cfg.rootDir);
|
|
11177
|
+
const requestedApp = stringOpt(parsed.options.app) ?? (context.app.source === "environment" || context.app.source === "profile" ? context.app.value ?? void 0 : void 0);
|
|
11178
|
+
const compatibleProject = selectedProject && (!requestedApp || selectedProject.appId === requestedApp) ? selectedProject : null;
|
|
11098
11179
|
return {
|
|
11099
11180
|
platformUrl: cfg.platformUrl,
|
|
11100
11181
|
token,
|
|
@@ -11103,15 +11184,33 @@ async function buildContext2(parsed, deps) {
|
|
|
11103
11184
|
json: parsed.options.json === true,
|
|
11104
11185
|
// Preserve PM's existing cross-project default when a config is present;
|
|
11105
11186
|
// only an explicit remote-agent environment or profile selects an app.
|
|
11106
|
-
appId: context.app.source === "environment" || context.app.source === "profile" ? context.app.value ?? void 0 :
|
|
11187
|
+
appId: context.app.source === "environment" || context.app.source === "profile" ? context.app.value ?? void 0 : compatibleProject?.appId,
|
|
11188
|
+
projectId: compatibleProject?.projectId,
|
|
11189
|
+
rootDir: cfg.rootDir,
|
|
11107
11190
|
...deps.sleep ? { sleep: deps.sleep } : {},
|
|
11108
11191
|
...deps.now ? { now: deps.now } : {}
|
|
11109
11192
|
};
|
|
11110
11193
|
}
|
|
11111
11194
|
async function pmCommand(parsed, deps = {}) {
|
|
11112
11195
|
const word = parsed.positionals[1] ?? "";
|
|
11196
|
+
if (word === "project") {
|
|
11197
|
+
const action3 = parsed.positionals[2] ?? "list";
|
|
11198
|
+
if (action3 === "list") {
|
|
11199
|
+
assertArgs(parsed, [...COMMON_OPTIONS, "app", "status", "limit", "offset"], 3);
|
|
11200
|
+
return pmProjectList(await buildContext2(parsed, deps), parsed);
|
|
11201
|
+
}
|
|
11202
|
+
if (action3 === "add" || action3 === "create") {
|
|
11203
|
+
assertArgs(parsed, [...COMMON_OPTIONS, "app", "name", "description", "mutation-id"], 3);
|
|
11204
|
+
return pmProjectAdd(await buildContext2(parsed, deps), parsed);
|
|
11205
|
+
}
|
|
11206
|
+
if (action3 === "use") {
|
|
11207
|
+
assertArgs(parsed, COMMON_OPTIONS, 4);
|
|
11208
|
+
return pmProjectUse(await buildContext2(parsed, deps), requireId2(parsed.positionals[3], action3));
|
|
11209
|
+
}
|
|
11210
|
+
throw new Error(`unknown pm project action "${action3}". Try list|add|use.`);
|
|
11211
|
+
}
|
|
11113
11212
|
if (word === "next") {
|
|
11114
|
-
assertArgs(parsed, [...COMMON_OPTIONS, "app"], 2);
|
|
11213
|
+
assertArgs(parsed, [...COMMON_OPTIONS, "app", "project"], 2);
|
|
11115
11214
|
return pmNext(await buildContext2(parsed, deps), parsed);
|
|
11116
11215
|
}
|
|
11117
11216
|
if (word === "watch") {
|
|
@@ -11131,7 +11230,7 @@ async function pmCommand(parsed, deps = {}) {
|
|
|
11131
11230
|
return pmWatch(await buildContext2(parsed, deps), parsed).then(() => void 0);
|
|
11132
11231
|
}
|
|
11133
11232
|
if (word === "handoff") {
|
|
11134
|
-
assertArgs(parsed, [...COMMON_OPTIONS, "app"], 2);
|
|
11233
|
+
assertArgs(parsed, [...COMMON_OPTIONS, "app", "project"], 2);
|
|
11135
11234
|
return pmHandoff(await buildContext2(parsed, deps), parsed);
|
|
11136
11235
|
}
|
|
11137
11236
|
const entity = ALIASES[word];
|
|
@@ -11184,6 +11283,8 @@ var init_pm_command = __esm({
|
|
|
11184
11283
|
init_pm_comments();
|
|
11185
11284
|
init_token();
|
|
11186
11285
|
init_pm_watch();
|
|
11286
|
+
init_pm_project_actions();
|
|
11287
|
+
init_pm_project_context();
|
|
11187
11288
|
ALIASES = {
|
|
11188
11289
|
goal: "goal",
|
|
11189
11290
|
conformance: "goal",
|
|
@@ -11194,8 +11295,8 @@ var init_pm_command = __esm({
|
|
|
11194
11295
|
};
|
|
11195
11296
|
COMMON_OPTIONS = ["config", "token", "email", "json", "platform", "context", "open"];
|
|
11196
11297
|
ACTION_OPTIONS = {
|
|
11197
|
-
list: ["app", "q", "limit", "offset"],
|
|
11198
|
-
add: ["app", "title", "mutation-id"],
|
|
11298
|
+
list: ["app", "project", "q", "limit", "offset"],
|
|
11299
|
+
add: ["app", "project", "title", "mutation-id"],
|
|
11199
11300
|
get: [],
|
|
11200
11301
|
set: ["mutation-id"],
|
|
11201
11302
|
done: ["mutation-id"],
|
|
@@ -12453,6 +12554,7 @@ var init_surface = __esm({
|
|
|
12453
12554
|
},
|
|
12454
12555
|
pm: {
|
|
12455
12556
|
...PM_ENTITIES,
|
|
12557
|
+
project: { list: {}, add: {}, create: {}, use: {} },
|
|
12456
12558
|
handoff: {},
|
|
12457
12559
|
next: {},
|
|
12458
12560
|
watch: {}
|
|
@@ -12687,8 +12789,8 @@ function readRunbookDir(dir) {
|
|
|
12687
12789
|
const files = (0, import_node_fs19.readdirSync)(dir).filter((f) => f.endsWith(".md")).sort();
|
|
12688
12790
|
if (!files.length) throw new Error(`no .md files in ${dir}`);
|
|
12689
12791
|
return files.map((file) => {
|
|
12690
|
-
const slug = (0,
|
|
12691
|
-
const parsed = parseRunbook((0, import_node_fs19.readFileSync)((0,
|
|
12792
|
+
const slug = (0, import_node_path18.basename)(file, ".md");
|
|
12793
|
+
const parsed = parseRunbook((0, import_node_fs19.readFileSync)((0, import_node_path18.join)(dir, file), "utf8"), slug);
|
|
12692
12794
|
return { file, slug, ...parsed, words: parsed.body.split(/\s+/).filter(Boolean).length };
|
|
12693
12795
|
});
|
|
12694
12796
|
}
|
|
@@ -12758,13 +12860,13 @@ async function upsert(ctx, r, visibility) {
|
|
|
12758
12860
|
);
|
|
12759
12861
|
return "updated";
|
|
12760
12862
|
}
|
|
12761
|
-
var import_node_fs19,
|
|
12863
|
+
var import_node_fs19, import_node_path18;
|
|
12762
12864
|
var init_runbook_import = __esm({
|
|
12763
12865
|
"src/runbook-import.ts"() {
|
|
12764
12866
|
"use strict";
|
|
12765
12867
|
init_cjs_shims();
|
|
12766
12868
|
import_node_fs19 = require("fs");
|
|
12767
|
-
|
|
12869
|
+
import_node_path18 = require("path");
|
|
12768
12870
|
init_runbook_actions();
|
|
12769
12871
|
}
|
|
12770
12872
|
});
|
|
@@ -12942,7 +13044,7 @@ ${body.split("\n").map((line) => `+${line}`).join("\n")}
|
|
|
12942
13044
|
}
|
|
12943
13045
|
function manifestLabeller(root) {
|
|
12944
13046
|
return (workspace) => {
|
|
12945
|
-
const manifest = (0,
|
|
13047
|
+
const manifest = (0, import_node_path19.join)(root, workspace, "package.json");
|
|
12946
13048
|
if (!(0, import_node_fs20.existsSync)(manifest)) return void 0;
|
|
12947
13049
|
try {
|
|
12948
13050
|
const name = JSON.parse((0, import_node_fs20.readFileSync)(manifest, "utf8")).name;
|
|
@@ -13011,7 +13113,7 @@ function report3(ctx, impacts) {
|
|
|
13011
13113
|
async function runbookImpact(ctx, options, deps = {}) {
|
|
13012
13114
|
const cwd = deps.cwd ?? process.cwd();
|
|
13013
13115
|
const runGit = deps.runGit ?? gitRunner(cwd);
|
|
13014
|
-
const read3 = deps.readRepoFile ?? ((path) => (0, import_node_fs20.readFileSync)((0,
|
|
13116
|
+
const read3 = deps.readRepoFile ?? ((path) => (0, import_node_fs20.readFileSync)((0, import_node_path19.join)(cwd, path), "utf8"));
|
|
13015
13117
|
const surfaces = changedSurfaces(collectDiff(runGit, options.base, read3), manifestLabeller(cwd));
|
|
13016
13118
|
if (!surfaces.length) {
|
|
13017
13119
|
return ctx.out.log(
|
|
@@ -13022,14 +13124,14 @@ async function runbookImpact(ctx, options, deps = {}) {
|
|
|
13022
13124
|
if (ctx.json) return ctx.out.log(JSON.stringify({ base: options.base, impacts }, null, 2));
|
|
13023
13125
|
report3(ctx, impacts);
|
|
13024
13126
|
}
|
|
13025
|
-
var import_node_child_process7, import_node_fs20,
|
|
13127
|
+
var import_node_child_process7, import_node_fs20, import_node_path19, SOURCE2, editHint;
|
|
13026
13128
|
var init_runbook_impact = __esm({
|
|
13027
13129
|
"src/runbook-impact.ts"() {
|
|
13028
13130
|
"use strict";
|
|
13029
13131
|
init_cjs_shims();
|
|
13030
13132
|
import_node_child_process7 = require("child_process");
|
|
13031
13133
|
import_node_fs20 = require("fs");
|
|
13032
|
-
|
|
13134
|
+
import_node_path19 = require("path");
|
|
13033
13135
|
init_runbook_impact_scan();
|
|
13034
13136
|
init_runbook_actions();
|
|
13035
13137
|
SOURCE2 = /\.(ts|tsx|js|jsx|mts|cts)$/;
|
|
@@ -13198,8 +13300,8 @@ function editText(initial, slug, deps = {}) {
|
|
|
13198
13300
|
);
|
|
13199
13301
|
if (!interactive())
|
|
13200
13302
|
throw new Error(`cannot open an editor without a terminal \u2014 pass --file <path> or --body "\u2026" instead`);
|
|
13201
|
-
const dir = (0, import_node_fs21.mkdtempSync)((0,
|
|
13202
|
-
const file = (0,
|
|
13303
|
+
const dir = (0, import_node_fs21.mkdtempSync)((0, import_node_path20.join)((0, import_node_os5.tmpdir)(), "odla-runbook-"));
|
|
13304
|
+
const file = (0, import_node_path20.join)(dir, `${slug}.md`);
|
|
13203
13305
|
try {
|
|
13204
13306
|
(0, import_node_fs21.writeFileSync)(file, initial, { mode: 384 });
|
|
13205
13307
|
const code = defaultRunOrInjected(deps)(editor, file);
|
|
@@ -13210,7 +13312,7 @@ function editText(initial, slug, deps = {}) {
|
|
|
13210
13312
|
(0, import_node_fs21.rmSync)(dir, { recursive: true, force: true });
|
|
13211
13313
|
}
|
|
13212
13314
|
}
|
|
13213
|
-
var import_node_child_process8, import_node_fs21, import_node_os5,
|
|
13315
|
+
var import_node_child_process8, import_node_fs21, import_node_os5, import_node_path20, import_node_process14, EDITOR_ENV, defaultRunOrInjected;
|
|
13214
13316
|
var init_runbook_editor = __esm({
|
|
13215
13317
|
"src/runbook-editor.ts"() {
|
|
13216
13318
|
"use strict";
|
|
@@ -13218,7 +13320,7 @@ var init_runbook_editor = __esm({
|
|
|
13218
13320
|
import_node_child_process8 = require("child_process");
|
|
13219
13321
|
import_node_fs21 = require("fs");
|
|
13220
13322
|
import_node_os5 = require("os");
|
|
13221
|
-
|
|
13323
|
+
import_node_path20 = require("path");
|
|
13222
13324
|
import_node_process14 = __toESM(require("process"), 1);
|
|
13223
13325
|
EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
|
|
13224
13326
|
defaultRunOrInjected = (deps) => deps.run ?? defaultRun;
|
|
@@ -13615,9 +13717,9 @@ async function runHostedSecurity(options) {
|
|
|
13615
13717
|
const appId = selfAudit ? "odla-ai" : cfg.app.id;
|
|
13616
13718
|
const env = selfAudit ? "prod" : selectEnv(options.env, cfg.envs, cfg.configPath, cfg.rootDir);
|
|
13617
13719
|
const platform = options.platform ?? cfg?.platformUrl ?? "https://odla.ai";
|
|
13618
|
-
const target = (0,
|
|
13619
|
-
const output = (0,
|
|
13620
|
-
const outputRelative = (0,
|
|
13720
|
+
const target = (0, import_node_path21.resolve)(options.target ?? cfg?.rootDir ?? ".");
|
|
13721
|
+
const output = (0, import_node_path21.resolve)(options.out ?? (0, import_node_path21.resolve)(target, ".odla/security/hosted"));
|
|
13722
|
+
const outputRelative = (0, import_node_path21.relative)(target, output).split(import_node_path21.sep).join("/");
|
|
13621
13723
|
if (!outputRelative) throw new Error("Hosted security output cannot be the repository root");
|
|
13622
13724
|
const profile = profileFor(options.profile ?? "odla", options.maxHuntTasks ?? 12);
|
|
13623
13725
|
const tokenRequest = {
|
|
@@ -13629,7 +13731,7 @@ async function runHostedSecurity(options) {
|
|
|
13629
13731
|
};
|
|
13630
13732
|
const token = await injectedToken(options, tokenRequest);
|
|
13631
13733
|
const snapshot = await (0, import_node3.snapshotDirectory)(target, {
|
|
13632
|
-
exclude: !outputRelative.startsWith("../") && !(0,
|
|
13734
|
+
exclude: !outputRelative.startsWith("../") && !(0, import_node_path21.isAbsolute)(outputRelative) ? [outputRelative] : []
|
|
13633
13735
|
});
|
|
13634
13736
|
const hosted = await (0, import_security.createPlatformSecurityReasoners)({
|
|
13635
13737
|
platform,
|
|
@@ -13647,7 +13749,7 @@ async function runHostedSecurity(options) {
|
|
|
13647
13749
|
});
|
|
13648
13750
|
const harness = (0, import_security.createSecurityHarness)({
|
|
13649
13751
|
profile,
|
|
13650
|
-
store: new import_node3.FileRunStore((0,
|
|
13752
|
+
store: new import_node3.FileRunStore((0, import_node_path21.resolve)(output, "state")),
|
|
13651
13753
|
discoveryReasoner: hosted.discoveryReasoner,
|
|
13652
13754
|
validationReasoner: hosted.validationReasoner,
|
|
13653
13755
|
policy: {
|
|
@@ -13671,7 +13773,7 @@ async function runHostedSecurity(options) {
|
|
|
13671
13773
|
function selectEnv(requested, declared, configPath, rootDir) {
|
|
13672
13774
|
const env = requested ?? (declared.includes("dev") ? "dev" : declared[0]);
|
|
13673
13775
|
if (!env || !declared.includes(env)) {
|
|
13674
|
-
const shown = (0,
|
|
13776
|
+
const shown = (0, import_node_path21.relative)(rootDir, configPath) || configPath;
|
|
13675
13777
|
throw new Error(`env "${env ?? ""}" is not declared in ${shown}`);
|
|
13676
13778
|
}
|
|
13677
13779
|
return env;
|
|
@@ -13700,17 +13802,17 @@ function printSummary(out, appId, env, run, report4, output) {
|
|
|
13700
13802
|
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}`);
|
|
13701
13803
|
if (report4.callBudget) out.log(` calls: discovery=${formatBudget(report4.callBudget.discovery)} validation=${formatBudget(report4.callBudget.validation)}`);
|
|
13702
13804
|
out.log(` findings: confirmed=${report4.metrics.confirmed} needs_reproduction=${report4.metrics.needsReproduction} candidates=${report4.metrics.candidates}`);
|
|
13703
|
-
out.log(` report: ${(0,
|
|
13805
|
+
out.log(` report: ${(0, import_node_path21.resolve)(output, "REPORT.md")}`);
|
|
13704
13806
|
}
|
|
13705
13807
|
function formatBudget(usage) {
|
|
13706
13808
|
return usage ? `${usage.usedCalls}/${usage.maxCalls} skipped=${usage.skippedCalls}` : "caller-managed";
|
|
13707
13809
|
}
|
|
13708
|
-
var
|
|
13810
|
+
var import_node_path21, import_security, import_node3;
|
|
13709
13811
|
var init_security = __esm({
|
|
13710
13812
|
"src/security.ts"() {
|
|
13711
13813
|
"use strict";
|
|
13712
13814
|
init_cjs_shims();
|
|
13713
|
-
|
|
13815
|
+
import_node_path21 = require("path");
|
|
13714
13816
|
import_security = require("@odla-ai/security");
|
|
13715
13817
|
import_node3 = require("@odla-ai/security/node");
|
|
13716
13818
|
init_config();
|