@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.
- package/README.md +15 -3
- package/dist/index.js +123 -35
- 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
|
|
67
|
-
hd doctor
|
|
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 --
|
|
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((
|
|
1197
|
-
const row = asRecord(
|
|
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.
|
|
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
|
|
5370
|
+
const shared = [
|
|
5317
5371
|
"--json",
|
|
5318
5372
|
"-c",
|
|
5319
5373
|
`model_reasoning_effort="${opts.effort}"`,
|
|
5320
|
-
"
|
|
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, ...
|
|
5380
|
+
return ["exec", "resume", opts.resumeSessionId, ...shared, opts.prompt];
|
|
5327
5381
|
}
|
|
5328
|
-
return ["exec", "--model", opts.model, ...
|
|
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
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
const
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
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
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
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
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
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() {
|