@odla-ai/cli 0.30.1 → 0.30.3

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 CHANGED
@@ -147,7 +147,11 @@ Use `pm task ref <id>` (or the equivalent goal/decision/bug command) to print
147
147
  copy-ready structured markup for Discussion. The same markup may be pasted into
148
148
  `pm <entity> comment <id> --body "…"`; PM stores it as a structured reference,
149
149
  and `pm <entity> comments <id>` prints copy-ready markup instead of flattening
150
- the link back to a title.
150
+ the link back to a title. For ordinary chat and handoffs, `pm task link <id>`
151
+ prints a normal Markdown link to the record's durable Studio route. Successful
152
+ create and lifecycle commands print that title-first link directly. Lead with
153
+ the PM type and linked title, explain the state or next action, and keep the id
154
+ hidden in the URL; a bare id list is not a useful handoff.
151
155
 
152
156
  When diagnosing a deployed app, agents can request one parseable observability
153
157
  snapshot instead of scraping Studio or composing collector routes themselves:
package/bin/odla-ai.js ADDED
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { spawnSync } from "node:child_process";
4
+ import { dirname, join } from "node:path";
5
+ import { fileURLToPath, pathToFileURL } from "node:url";
6
+ import { workspaceBuildOrder } from "./workspace-build.js";
7
+
8
+ const packageRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
9
+ const builtEntry = join(packageRoot, "dist", "bin.js");
10
+
11
+ if (!existsSync(builtEntry)) {
12
+ const workspaceRoot = join(packageRoot, "..", "..");
13
+ const rootManifestPath = join(workspaceRoot, "package.json");
14
+ const rootManifest = existsSync(rootManifestPath)
15
+ ? JSON.parse(readFileSync(rootManifestPath, "utf8"))
16
+ : null;
17
+ if (rootManifest?.name !== "odla-ai" || rootManifest.private !== true) {
18
+ throw new Error("odla-ai CLI build is missing; reinstall @odla-ai/cli from npm");
19
+ }
20
+
21
+ console.error("odla-ai: workspace CLI is not built; building the local monorepo first");
22
+ const npm = process.platform === "win32" ? "npm.cmd" : "npm";
23
+ const workspaces = workspaceBuildOrder(workspaceRoot);
24
+ const child = spawnSync(
25
+ npm,
26
+ ["run", "build", ...workspaces.map((name) => `--workspace=${name}`)],
27
+ { cwd: workspaceRoot, stdio: "inherit" },
28
+ );
29
+ if (child.error) throw child.error;
30
+ if (child.status !== 0) process.exit(child.status ?? 1);
31
+ }
32
+
33
+ await import(pathToFileURL(builtEntry).href);
@@ -0,0 +1,2 @@
1
+ /** Return buildable internal dependencies in dependency-first order. */
2
+ export declare function workspaceBuildOrder(root: string): string[];
@@ -0,0 +1,36 @@
1
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ const manifestAt = (path) => JSON.parse(readFileSync(path, "utf8"));
5
+
6
+ /** Return buildable internal dependencies in dependency-first order. */
7
+ export function workspaceBuildOrder(root) {
8
+ const rootManifest = manifestAt(join(root, "package.json"));
9
+ const workspaces = new Map();
10
+ for (const pattern of rootManifest.workspaces ?? []) {
11
+ if (!pattern.endsWith("/*")) continue;
12
+ const parent = join(root, pattern.slice(0, -2));
13
+ if (!existsSync(parent)) continue;
14
+ for (const entry of readdirSync(parent, { withFileTypes: true })) {
15
+ if (!entry.isDirectory()) continue;
16
+ const manifestPath = join(parent, entry.name, "package.json");
17
+ if (!existsSync(manifestPath)) continue;
18
+ const manifest = manifestAt(manifestPath);
19
+ if (manifest.name) workspaces.set(manifest.name, manifest);
20
+ }
21
+ }
22
+
23
+ const visited = new Set();
24
+ const order = [];
25
+ const visit = (name) => {
26
+ if (visited.has(name)) return;
27
+ visited.add(name);
28
+ const manifest = workspaces.get(name);
29
+ if (!manifest) return;
30
+ const dependencies = { ...manifest.dependencies, ...manifest.devDependencies };
31
+ for (const dependency of Object.keys(dependencies)) visit(dependency);
32
+ if (manifest.scripts?.build) order.push(name);
33
+ };
34
+ visit("@odla-ai/cli");
35
+ return order;
36
+ }
package/dist/bin.cjs CHANGED
@@ -9608,6 +9608,7 @@ Usage:
9608
9608
  odla-ai pm bug add --app <id> --title <t> (--description <text>|--body <text>) [--status <s>] [--severity <s>] [--goal <id>] [--assignee <id>] [--decision <id>] [--mutation-id <id>] [--json]
9609
9609
  odla-ai bug report --app <id> --title <t> (--description <text>|--body <text>) [--severity <s>] [--json]
9610
9610
  odla-ai pm <goal|task|decision|bug> get <id> [--json]
9611
+ odla-ai pm <goal|task|decision|bug> link <id> [--json]
9611
9612
  odla-ai pm <goal|task|decision|bug> ref <id> [--json]
9612
9613
  odla-ai pm goal set <id> [--title <t>|--status <s>|--proof <text>|--no-proof|--target <pct>|--no-target] [--mutation-id <id>] [--json]
9613
9614
  odla-ai pm task set <id> [--title <t>|--column <backlog|ready|doing|review|done>|--rank <n>|--goal <id>|--no-goal|--alignment-decision <id>|--no-alignment-decision|--execution <human|agent|either>|--assignee <id>|--no-assignee|--description <text>|--body <text>|--acceptance <text>|--no-acceptance|--due <epoch-ms>|--no-due|--expected-revision <n>] [--mutation-id <id>] [--json]
@@ -10509,16 +10510,26 @@ function referenceMarkup(entity, record10) {
10509
10510
  const label = (record10.title?.trim() || `${entity} ${record10.id}`).replaceAll("]", ")");
10510
10511
  return `@[${label}](pm:${entity}/${record10.id})`;
10511
10512
  }
10513
+ function studioRecordUrl(ctx, entity, id) {
10514
+ return new URL(
10515
+ `/studio/pm/${STUDIO_SECTION[entity]}/${encodeURIComponent(id)}`,
10516
+ ctx.platformUrl
10517
+ ).href;
10518
+ }
10519
+ function studioRecordLink(ctx, entity, record10) {
10520
+ const label = (record10.title?.trim() || `${entity} ${record10.id}`).replaceAll("]", ")");
10521
+ return `[${label}](${studioRecordUrl(ctx, entity, record10.id)})`;
10522
+ }
10512
10523
  function printRecord(ctx, entity, record10) {
10513
10524
  ctx.out.log(
10514
- `${record10.id} [${statusCol(entity, record10)}] ${record10.appId} ${record10.title ?? ""}`
10525
+ `${record10.id} [${statusCol(entity, record10)}] ${record10.appId} ${studioRecordLink(ctx, entity, record10)}`
10515
10526
  );
10516
10527
  }
10517
10528
  function emit2(ctx, value2, human) {
10518
10529
  if (ctx.json) ctx.out.log(JSON.stringify(value2, null, 2));
10519
10530
  else human();
10520
10531
  }
10521
- var DONE, writeMutationId2, FIELD_MAP;
10532
+ var DONE, writeMutationId2, FIELD_MAP, STUDIO_SECTION;
10522
10533
  var init_pm_action_core = __esm({
10523
10534
  "src/pm-action-core.ts"() {
10524
10535
  "use strict";
@@ -10551,6 +10562,12 @@ var init_pm_action_core = __esm({
10551
10562
  execution: { key: "executionMode" },
10552
10563
  "expected-revision": { key: "expectedRevision", num: true }
10553
10564
  };
10565
+ STUDIO_SECTION = {
10566
+ goal: "goals",
10567
+ task: "board",
10568
+ decision: "decisions",
10569
+ bug: "bugs"
10570
+ };
10554
10571
  }
10555
10572
  });
10556
10573
 
@@ -10596,7 +10613,8 @@ async function pmAdd(ctx, entity, parsed) {
10596
10613
  input,
10597
10614
  mutationId: writeMutationId2(parsed)
10598
10615
  });
10599
- emit2(ctx, res, () => ctx.out.log(`created ${entity} ${res.id}`));
10616
+ const record10 = { id: res.id, appId, title: String(input.title) };
10617
+ emit2(ctx, res, () => ctx.out.log(`created ${entity}: ${studioRecordLink(ctx, entity, record10)}`));
10600
10618
  }
10601
10619
  async function pmGet(ctx, entity, id) {
10602
10620
  const { record: record10 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id)}`);
@@ -10621,7 +10639,10 @@ async function pmSet(ctx, entity, id, parsed) {
10621
10639
  patch: patch2,
10622
10640
  mutationId: writeMutationId2(parsed)
10623
10641
  });
10624
- emit2(ctx, res, () => ctx.out.log(res.record ? `${res.record.id} [${statusCol(entity, res.record)}] ${res.record.appId} ${res.record.title ?? ""}` : `updated ${entity} ${id}`));
10642
+ emit2(ctx, res, () => {
10643
+ if (!res.record) return ctx.out.log(`updated ${entity} ${id}`);
10644
+ ctx.out.log(`${entity}: ${studioRecordLink(ctx, entity, res.record)} \u2192 ${statusCol(entity, res.record)}`);
10645
+ });
10625
10646
  }
10626
10647
  async function pmDone(ctx, entity, id, parsed) {
10627
10648
  const decisionId = stringOpt(parsed.options.decision);
@@ -10631,7 +10652,11 @@ async function pmDone(ctx, entity, id, parsed) {
10631
10652
  patch: patch2,
10632
10653
  mutationId: writeMutationId2(parsed)
10633
10654
  });
10634
- emit2(ctx, res, () => ctx.out.log(`${entity} ${id} \u2192 done`));
10655
+ emit2(ctx, res, () => {
10656
+ const label = res.record ? studioRecordLink(ctx, entity, res.record) : id;
10657
+ const state2 = res.record ? statusCol(entity, res.record) : "done";
10658
+ ctx.out.log(`${entity}: ${label} \u2192 ${state2}`);
10659
+ });
10635
10660
  }
10636
10661
  async function pmTaskLifecycle(ctx, id, action2, parsed) {
10637
10662
  const rawRevision = stringOpt(parsed.options["expected-revision"]);
@@ -10660,7 +10685,8 @@ async function pmTaskLifecycle(ctx, id, action2, parsed) {
10660
10685
  );
10661
10686
  emit2(ctx, res, () => {
10662
10687
  const state2 = res.record ? statusCol("task", res.record) : action2;
10663
- ctx.out.log(`task ${id} \u2192 ${state2}`);
10688
+ const label = res.record ? studioRecordLink(ctx, "task", res.record) : id;
10689
+ ctx.out.log(`task: ${label} \u2192 ${state2}`);
10664
10690
  });
10665
10691
  }
10666
10692
  async function allRecords(ctx, entity, appId) {
@@ -10765,6 +10791,27 @@ var init_pm_actions = __esm({
10765
10791
  }
10766
10792
  });
10767
10793
 
10794
+ // src/pm-links.ts
10795
+ async function pmLink(ctx, entity, id) {
10796
+ const { record: record10 } = await pmRequest(
10797
+ ctx,
10798
+ "GET",
10799
+ `/${entity}/${encodeURIComponent(id)}`
10800
+ );
10801
+ const url = studioRecordUrl(ctx, entity, record10.id);
10802
+ const markdown = studioRecordLink(ctx, entity, record10);
10803
+ emit2(ctx, { kind: entity, id: record10.id, label: record10.title ?? "", url, markdown }, () => {
10804
+ ctx.out.log(markdown);
10805
+ });
10806
+ }
10807
+ var init_pm_links = __esm({
10808
+ "src/pm-links.ts"() {
10809
+ "use strict";
10810
+ init_cjs_shims();
10811
+ init_pm_action_core();
10812
+ }
10813
+ });
10814
+
10768
10815
  // src/pm-comments.ts
10769
10816
  async function pmComment(ctx, entity, id, parsed) {
10770
10817
  const body = stringOpt(parsed.options.body);
@@ -11091,7 +11138,7 @@ async function pmCommand(parsed, deps = {}) {
11091
11138
  if (!entity) throw new Error(`unknown pm entity "${word}". Try "odla-ai pm bug list" (goal|task|decision|bug).`);
11092
11139
  const requestedAction = parsed.positionals[2] ?? "list";
11093
11140
  const action2 = canonicalAction(requestedAction);
11094
- if (!action2) throw new Error(`unknown pm action "${requestedAction}". Try list|add|get|set|done|comment|comments|rm.`);
11141
+ if (!action2) throw new Error(`unknown pm action "${requestedAction}". Try list|add|get|set|done|link|ref|comment|comments|rm.`);
11095
11142
  assertArgs(parsed, allowedOptions(entity, action2), 4);
11096
11143
  if ((action2 === "ready" || action2 === "claim" || action2 === "release") && entity !== "task") {
11097
11144
  throw new Error(`pm ${action2} is only valid for tasks`);
@@ -11115,6 +11162,8 @@ async function pmCommand(parsed, deps = {}) {
11115
11162
  return pmComments(ctx, entity, requireId2(id, action2));
11116
11163
  case "rm":
11117
11164
  return pmRemove(ctx, entity, requireId2(id, action2));
11165
+ case "link":
11166
+ return pmLink(ctx, entity, requireId2(id, action2));
11118
11167
  case "ref":
11119
11168
  return pmReference(ctx, entity, requireId2(id, action2));
11120
11169
  case "ready":
@@ -11131,6 +11180,7 @@ var init_pm_command = __esm({
11131
11180
  init_argv();
11132
11181
  init_operator_context();
11133
11182
  init_pm_actions();
11183
+ init_pm_links();
11134
11184
  init_pm_comments();
11135
11185
  init_token();
11136
11186
  init_pm_watch();
@@ -11155,6 +11205,7 @@ var init_pm_command = __esm({
11155
11205
  ready: ["goal", "alignment-decision", "execution", "description", "desc", "body", "acceptance", "expected-revision", "mutation-id"],
11156
11206
  claim: ["expected-revision", "mutation-id"],
11157
11207
  release: ["expected-revision", "mutation-id"],
11208
+ link: [],
11158
11209
  ref: []
11159
11210
  };
11160
11211
  ENTITY_OPTIONS = {