@higherdev/cli 0.1.0 → 0.1.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.
Files changed (3) hide show
  1. package/README.md +15 -3
  2. package/dist/index.js +123 -35
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -63,11 +63,23 @@ addenda, conventions, budgets. Creating an epic fires `epic.created`, which the
63
63
  orchestrator already triggers on, so the handoff needs no glue.
64
64
 
65
65
  ```bash
66
- hd connect # ChatGPT OAuth, once per machine
67
- hd doctor # what a run would cost, before it runs
66
+ hd connect # ChatGPT OAuth, once per machine
67
+ hd doctor # what a run would cost, before it runs
68
+ hd plan # a conversation: type, it answers, keep going
69
+ ```
70
+
71
+ Bare `hd plan` on a terminal is a back-and-forth. Every turn after the first
72
+ continues the same Codex session, so it remembers what you already said. A blank
73
+ line or `/exit` leaves, and `hd plan --resume "..."` picks the thread back up
74
+ later.
75
+
76
+ Give it a request on the command line instead and it runs one turn and exits,
77
+ which is what a script wants:
78
+
79
+ ```bash
68
80
  hd plan "assess the call-intel code and plan what is missing"
69
81
  hd plan --read-only "what would you change about the agent lineup?"
70
- hd plan --resume "split the third ticket, it is too big"
82
+ hd plan --once "file the epic we discussed"
71
83
  ```
72
84
 
73
85
  ### The subscription is checked, not assumed
package/dist/index.js CHANGED
@@ -1190,11 +1190,17 @@ function str(value) {
1190
1190
  }
1191
1191
  function assistantText(payload) {
1192
1192
  if (typeof payload.text === "string") return payload.text;
1193
+ const item = asRecord(payload.item);
1194
+ if (typeof item.text === "string") return item.text;
1195
+ if (Array.isArray(item.content)) {
1196
+ const joined = item.content.map((entry) => str(asRecord(entry).text)).filter(Boolean).join("\n");
1197
+ if (joined) return joined;
1198
+ }
1193
1199
  const message = asRecord(payload.message);
1194
1200
  const content = message.content;
1195
1201
  if (Array.isArray(content)) {
1196
- return content.map((item) => {
1197
- const row = asRecord(item);
1202
+ return content.map((item2) => {
1203
+ const row = asRecord(item2);
1198
1204
  return str(row.text);
1199
1205
  }).filter(Boolean).join("\n");
1200
1206
  }
@@ -1224,10 +1230,56 @@ function resultTail(payload) {
1224
1230
  ${tail}`;
1225
1231
  return prefix || tail;
1226
1232
  }
1233
+ function codexItemLine(event, item) {
1234
+ const kind = str(item.type);
1235
+ if (!kind || kind === "agent_message") return null;
1236
+ if (kind === "mcp_tool_call") {
1237
+ const server = str(item.server);
1238
+ const tool = str(item.tool);
1239
+ const status = str(item.status);
1240
+ if (status === "in_progress") return SKIP;
1241
+ const failed = status === "failed" || Boolean(item.error);
1242
+ const name = [server, tool].filter(Boolean).join("/") || "tool";
1243
+ const args = item.arguments && Object.keys(asRecord(item.arguments)).length > 0 ? ` ${JSON.stringify(item.arguments)}` : "";
1244
+ return {
1245
+ id: event.id,
1246
+ kind: failed ? "error" : "tool",
1247
+ title: `${name}${clip2(args, 120)}${status && status !== "completed" ? ` (${status})` : ""}`,
1248
+ body: failed ? clip2(str(asRecord(item.error).message ?? item.error), 400) || void 0 : void 0
1249
+ };
1250
+ }
1251
+ if (kind === "command_execution") {
1252
+ const command = clip2(str(item.command ?? item.cmd), 120);
1253
+ const code = item.exit_code ?? item.exitCode;
1254
+ return {
1255
+ id: event.id,
1256
+ kind: code != null && Number(code) !== 0 ? "error" : "tool",
1257
+ title: `${command || "command"}${code == null ? "" : ` (exit ${code})`}`
1258
+ };
1259
+ }
1260
+ if (kind === "file_change" || kind === "patch_apply") {
1261
+ const files = Array.isArray(item.changes) ? item.changes.map((change) => str(asRecord(change).path)).filter(Boolean) : [str(item.path)].filter(Boolean);
1262
+ return { id: event.id, kind: "tool", title: `edited ${files.join(", ") || "a file"}` };
1263
+ }
1264
+ if (kind === "reasoning" || kind === "todo_list") return SKIP;
1265
+ return { id: event.id, kind: "status", title: kind.replaceAll("_", " ") };
1266
+ }
1267
+ function clip2(text, max) {
1268
+ const flat = String(text ?? "").trim();
1269
+ return flat.length <= max ? flat : `${flat.slice(0, Math.max(0, max - 3))}...`;
1270
+ }
1227
1271
  function transcriptLines(events) {
1228
1272
  const lines = [];
1229
1273
  for (const event of events) {
1230
1274
  const payload = asRecord(event.payload);
1275
+ if (payload.item) {
1276
+ const line = codexItemLine(event, asRecord(payload.item));
1277
+ if (line === SKIP) continue;
1278
+ if (line) {
1279
+ lines.push(line);
1280
+ continue;
1281
+ }
1282
+ }
1231
1283
  if (event.type === "error") {
1232
1284
  lines.push({
1233
1285
  id: event.id,
@@ -1277,9 +1329,11 @@ function transcriptLines(events) {
1277
1329
  }
1278
1330
  return lines;
1279
1331
  }
1332
+ var SKIP;
1280
1333
  var init_transcript = __esm({
1281
1334
  "../../packages/db/src/transcript.ts"() {
1282
1335
  "use strict";
1336
+ SKIP = "skip";
1283
1337
  }
1284
1338
  });
1285
1339
 
@@ -4533,7 +4587,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
4533
4587
  import { z as z3 } from "zod";
4534
4588
 
4535
4589
  // src/version.ts
4536
- var VERSION = "0.1.0";
4590
+ var VERSION = "0.1.1";
4537
4591
 
4538
4592
  // src/architect/tools.ts
4539
4593
  init_src();
@@ -5313,19 +5367,19 @@ function mcpConfigArgs(endpoint, extraArgs = []) {
5313
5367
  ];
5314
5368
  }
5315
5369
  function buildCodexArgs(opts) {
5316
- const base = [
5370
+ const shared = [
5317
5371
  "--json",
5318
5372
  "-c",
5319
5373
  `model_reasoning_effort="${opts.effort}"`,
5320
- "--sandbox",
5321
- "read-only",
5374
+ "-c",
5375
+ 'sandbox_mode="read-only"',
5322
5376
  "--skip-git-repo-check",
5323
5377
  ...opts.mcp
5324
5378
  ];
5325
5379
  if (opts.resumeSessionId) {
5326
- return ["exec", "resume", opts.resumeSessionId, ...base, opts.prompt];
5380
+ return ["exec", "resume", opts.resumeSessionId, ...shared, opts.prompt];
5327
5381
  }
5328
- return ["exec", "--model", opts.model, ...base, "-C", opts.cwd, opts.prompt];
5382
+ return ["exec", "--model", opts.model, "--sandbox", "read-only", ...shared, "-C", opts.cwd, opts.prompt];
5329
5383
  }
5330
5384
  function spawnArchitect(opts) {
5331
5385
  const args = buildCodexArgs(opts);
@@ -5536,9 +5590,11 @@ ${howToFix(connection) ?? ""}`.trim());
5536
5590
  pending.push({ run_id: runRow.id, seq: seq++, type: event.type, payload: event.payload });
5537
5591
  const [line] = transcriptLines([{ id: String(seq), type: event.type, payload: event.payload }]);
5538
5592
  if (!line) continue;
5593
+ if (line.kind === "status" && line.title === "init") continue;
5539
5594
  if (line.kind === "text") opts.onLine(line.title);
5540
5595
  else if (line.kind === "error") opts.onLine(c.red(line.title));
5541
5596
  else opts.onLine(c.dim(` ${line.title}`));
5597
+ if (line.body) opts.onLine(c.dim(` ${line.body}`));
5542
5598
  }
5543
5599
  clearInterval(ticker);
5544
5600
  await flush();
@@ -5563,6 +5619,20 @@ ${howToFix(connection) ?? ""}`.trim());
5563
5619
  function slugOf6(command) {
5564
5620
  return command.optsWithGlobals().workspace;
5565
5621
  }
5622
+ async function askLine(prompt2) {
5623
+ const rl = createInterface3({ input: process.stdin, output: process.stderr });
5624
+ try {
5625
+ return await new Promise((resolve) => {
5626
+ rl.once("close", () => resolve(""));
5627
+ rl.question(prompt2).then(
5628
+ (answer) => resolve(answer.trim()),
5629
+ () => resolve("")
5630
+ );
5631
+ });
5632
+ } finally {
5633
+ rl.close();
5634
+ }
5635
+ }
5566
5636
  function report(connection) {
5567
5637
  emit(
5568
5638
  { codex: connection },
@@ -5571,39 +5641,57 @@ ${howToFix(connection) ?? ""}`.trim()
5571
5641
  );
5572
5642
  }
5573
5643
  function registerArchitectCommands(program) {
5574
- program.command("plan").argument("[request...]", "what to plan, assess, or change").description("the architect: assess the repo, shape the work, tune the platform").option("--read-only", "let it look and propose, but change nothing").option("--resume", "continue the last architect session in this workspace").option("--allow-api-billing", "run even when Codex would bill per token instead of the subscription").action(async function(request) {
5644
+ program.command("plan").argument("[request...]", "what to plan, assess, or change; omit for a conversation").description("the architect: assess the repo, shape the work, tune the platform").option("--read-only", "let it look and propose, but change nothing").option("--resume", "continue the last architect session in this workspace").option("--once", "run a single turn and exit, even without a request").option("--allow-api-billing", "run even when Codex would bill per token instead of the subscription").action(async function(request) {
5575
5645
  const opts = this.opts();
5576
5646
  const ctx = await requireWorkspace(slugOf6(this));
5577
- let ask = request.join(" ").trim();
5578
- if (!ask) {
5579
- if (!process.stdin.isTTY) fail("Give the architect something to do.");
5580
- const rl = createInterface3({ input: process.stdin, output: process.stderr });
5581
- try {
5582
- ask = (await rl.question(c.blue("plan> "))).trim();
5583
- } finally {
5584
- rl.close();
5647
+ const first = request.join(" ").trim();
5648
+ const conversational = !first && !opts.once && process.stdin.isTTY && !isJsonMode();
5649
+ const turn = async (ask, resume2) => {
5650
+ const lines = [];
5651
+ const result = await runArchitect({
5652
+ ctx,
5653
+ request: ask,
5654
+ readOnly: Boolean(opts.readOnly),
5655
+ resume: resume2,
5656
+ allowApiBilling: Boolean(opts.allowApiBilling),
5657
+ onLine: (text) => {
5658
+ lines.push(text);
5659
+ if (!isJsonMode()) out(text);
5660
+ }
5661
+ });
5662
+ return { result, lines };
5663
+ };
5664
+ if (!conversational) {
5665
+ let ask = first;
5666
+ if (!ask) {
5667
+ if (!process.stdin.isTTY) fail("Give the architect something to do.");
5668
+ ask = await askLine("plan> ");
5669
+ if (!ask) fail("Nothing asked.");
5585
5670
  }
5586
- if (!ask) fail("Nothing asked.");
5587
- }
5588
- const lines = [];
5589
- const result = await runArchitect({
5590
- ctx,
5591
- request: ask,
5592
- readOnly: Boolean(opts.readOnly),
5593
- resume: Boolean(opts.resume),
5594
- allowApiBilling: Boolean(opts.allowApiBilling),
5595
- onLine: (text) => {
5596
- lines.push(text);
5597
- if (!isJsonMode()) out(text);
5671
+ const { result, lines } = await turn(ask, Boolean(opts.resume));
5672
+ if (isJsonMode()) {
5673
+ emit(
5674
+ { ok: result.exitCode === 0, run_id: result.runId, session_id: result.sessionId, output: lines },
5675
+ () => ""
5676
+ );
5677
+ return;
5678
+ }
5679
+ if (result.sessionId) {
5680
+ out(c.dim(`
5681
+ Continue with \`hd plan --resume "..."\`. Run ${result.runId.slice(0, 8)}.`));
5598
5682
  }
5599
- });
5600
- if (isJsonMode()) {
5601
- emit({ ok: result.exitCode === 0, run_id: result.runId, session_id: result.sessionId, output: lines }, () => "");
5602
5683
  return;
5603
5684
  }
5604
- if (result.sessionId) {
5605
- out(c.dim(`
5606
- Continue with \`hd plan --resume "..."\`. Run ${result.runId.slice(0, 8)}.`));
5685
+ out(c.dim("Talking to the architect. Enter sends, blank line or /exit leaves, ctrl-c stops a turn."));
5686
+ let resume = Boolean(opts.resume);
5687
+ for (; ; ) {
5688
+ const ask = await askLine("\nplan> ");
5689
+ if (!ask || ask === "/exit" || ask === "/quit") {
5690
+ out(c.dim('Left the architect. `hd plan --resume "..."` picks the thread back up.'));
5691
+ return;
5692
+ }
5693
+ const { result } = await turn(ask, resume);
5694
+ if (result.sessionId) resume = true;
5607
5695
  }
5608
5696
  });
5609
5697
  program.command("mcp").description("serve the platform as MCP tools on stdio, for Codex, Claude Code, or Mel").option("--read-only", "expose only the tools that change nothing").option("--list", "print the tools instead of serving them").action(async function() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@higherdev/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
5
  "description": "HigherDEV in your terminal. Full control of the board, live.",
6
6
  "bin": {