@higherdev/cli 0.21.0 → 0.23.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/dist/api.js CHANGED
@@ -58,6 +58,9 @@ export async function queueTicket(key, config = loadConfig()) {
58
58
  export async function cancelTicket(key, config = loadConfig()) {
59
59
  return request(config, "POST", `/api/w/${config.slug}/tickets/${encodeURIComponent(key)}/cancel`);
60
60
  }
61
+ export async function mergeTicket(key, reason, config = loadConfig()) {
62
+ return request(config, "POST", `/api/w/${config.slug}/tickets/${encodeURIComponent(key)}/merge`, { reason: reason ?? "", by: "owner" }, true);
63
+ }
61
64
  export async function listTicketRunEvents(key, afterAt, afterId, config = loadConfig()) {
62
65
  const query = new URLSearchParams();
63
66
  if (afterAt)
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { realpathSync } from "node:fs";
3
3
  import { pathToFileURL } from "node:url";
4
- import { approveEpic, answerDecision, cancelTicket, createEpic, deleteAgent, deleteEpic, getStatus, getRoadmap, getWorkspace, listAgents, listEpics, listHostEnv, listMessages, listWorkspaceEnv, listTicketRunEvents, listTickets, listWorkspaces, postMessage, queueTicket, removeWorkspaceEnv, removeHostEnv, setPaused, setWorkspaceEnv, setHostEnv, showTicket, updateAgent, updateEpic, updateCaps, } from "./api.js";
4
+ import { approveEpic, answerDecision, cancelTicket, mergeTicket, createEpic, deleteAgent, deleteEpic, getStatus, getRoadmap, getWorkspace, listAgents, listEpics, listHostEnv, listMessages, listWorkspaceEnv, listTicketRunEvents, listTickets, listWorkspaces, postMessage, queueTicket, removeWorkspaceEnv, removeHostEnv, setPaused, setWorkspaceEnv, setHostEnv, showTicket, updateAgent, updateEpic, updateCaps, } from "./api.js";
5
5
  import { formatDrainStatus, hostRoll, initHost, parseHostFlags, parseHostRollFlags } from "./host.js";
6
6
  import { login, parseLoginFlags } from "./login.js";
7
7
  import { loadConfig } from "./config.js";
@@ -139,7 +139,16 @@ async function cmdTicket(argv, deps = {}) {
139
139
  console.log(`${c.bold(ticket.key)} ${statusChip(ticket.status)}`);
140
140
  return;
141
141
  }
142
- fail("usage: hd ticket list | show KEY | new --title TITLE | queue KEY | cancel KEY");
142
+ if (action === "merge") {
143
+ const parsed = flags(rest);
144
+ const key = parsed.rest[0];
145
+ if (!key || parsed.rest.length !== 1)
146
+ fail("usage: hd ticket merge KEY [--reason TEXT]");
147
+ const { ticket } = await mergeTicket(key.toUpperCase(), parsed.opts.reason);
148
+ console.log(`${c.bold(ticket.key)} ${statusChip(ticket.status)}`);
149
+ return;
150
+ }
151
+ fail("usage: hd ticket list | show KEY | new --title TITLE | queue KEY | cancel KEY | merge KEY");
143
152
  }
144
153
  async function cmdEpic(argv) {
145
154
  const [action, ...rest] = argv;
package/dist/out.js CHANGED
@@ -64,7 +64,7 @@ export function usage() {
64
64
  return [
65
65
  c.bold("Usage"),
66
66
  ` ${c.blue("hd status")} workspace overview`,
67
- ` ${c.blue("hd ticket list | show KEY [--json] | new [PATH] | queue | cancel")} ticket operations`,
67
+ ` ${c.blue("hd ticket list | show KEY [--json] | new [PATH] | queue | cancel | merge")} ticket operations`,
68
68
  ` ${c.blue("hd epic new PATH | list | approve | rm")} epic operations`,
69
69
  ` ${c.blue("hd roadmap [--json]")} ordered workspace roadmap`,
70
70
  ` ${c.blue("hd plan")} use /architect in the TUI`,
package/dist/tui/App.js CHANGED
@@ -24,7 +24,7 @@ import { alertOnce } from "./alert.js";
24
24
  import { bubbleRows } from "./height.js";
25
25
  import { planLayout, splitPanels } from "./layout.js";
26
26
  import { parseLine } from "./parse.js";
27
- import { configuredSlugs, acknowledgeInbox, approveEpic, cancelTicket, createEpicFromFile, decisionOptions, deleteAgent, deleteEpic, followChat, loadChatMessages, loadLiveEvents, listWorkspaceEnv, loadTicketDetail, POLL_MS, pollSnapshot, postAgentMessage, postTicketMessage, queueTicket, resolveDecision, selectDecision, setWorkspacePaused, switchWorkspace, updateAgent, updateProviderCap, updateWorkspace, } from "./data.js";
27
+ import { configuredSlugs, acknowledgeInbox, approveEpic, cancelTicket, mergeTicket, createEpicFromFile, decisionOptions, deleteAgent, deleteEpic, followChat, loadChatMessages, loadLiveEvents, listWorkspaceEnv, loadTicketDetail, POLL_MS, pollSnapshot, postAgentMessage, postTicketMessage, queueTicket, resolveDecision, selectDecision, setWorkspacePaused, switchWorkspace, updateAgent, updateProviderCap, updateWorkspace, } from "./data.js";
28
28
  import { inputActive, NO_REPLY_NOTE, promptPlaceholder, REPLY_WAIT_MS } from "./chat-wait.js";
29
29
  import { answeredLine, decisionHeaderIndex, decisionIdAt, moveDecisionFocus, nextUnanswered, resolveDecisionAnswer, } from "./decide-nav.js";
30
30
  import { EARLIER_PAGE } from "./inbox.js";
@@ -693,6 +693,20 @@ export function App({ initial }) {
693
693
  setBusy(false);
694
694
  }
695
695
  return;
696
+ case "merge":
697
+ setBusy(true);
698
+ try {
699
+ const { ticket: merged } = await mergeTicket(config, action.key);
700
+ say("system", `${merged.key} approved over review.`);
701
+ await refresh();
702
+ }
703
+ catch (error) {
704
+ setNotice(error instanceof Error ? error.message : String(error));
705
+ }
706
+ finally {
707
+ setBusy(false);
708
+ }
709
+ return;
696
710
  case "agent-add":
697
711
  setBusy(true);
698
712
  try {
package/dist/tui/Help.js CHANGED
@@ -11,6 +11,7 @@ export const COMMANDS = [
11
11
  { name: "/ticket", args: "HD-12 | new [PATH.md]", help: "open a full ticket view or create one" },
12
12
  { name: "/queue", args: "HD-12", help: "queue a complete ticket now" },
13
13
  { name: "/cancel", args: "HD-12", help: "cancel a ticket" },
14
+ { name: "/merge", args: "HD-12", help: "approve a reviewed PR over the reviewer's objections" },
14
15
  { name: "/msg", args: "HD-12 TEXT", help: "message a ticket's builder" },
15
16
  { name: "/logs", args: "[raw] [HD-12]", help: "filter activity; raw reveals event JSON" },
16
17
  { name: "/epic", args: "new PATH | approve ID | rm ID", help: "create, approve, or remove a draft epic" },
@@ -1,5 +1,18 @@
1
1
  import { narrateEvents } from "./narrate.js";
2
2
  import { wrapLines } from "./ticket-view.js";
3
+ export function chatInstructionEffects(body) {
4
+ const parts = [];
5
+ const answered = body.match(/Answered decision[^\n.;]*/i);
6
+ const queued = body.match(/HD-\d+ is queued/i);
7
+ const cap = body.match(/attempt cap is \d+/i);
8
+ if (answered)
9
+ parts.push(answered[0].trim().replace(/[.;]+$/, ""));
10
+ if (queued)
11
+ parts.push(queued[0].trim().replace(/[.;]+$/, ""));
12
+ if (cap)
13
+ parts.push(cap[0].trim().replace(/[.;]+$/, ""));
14
+ return parts.length ? parts.join("; ") : null;
15
+ }
3
16
  /** Last 50 exchanges (a human turn and an agent turn) when rehydrating. */
4
17
  export const CHAT_EXCHANGE_LIMIT = 50;
5
18
  export const CHAT_MESSAGE_LIMIT = CHAT_EXCHANGE_LIMIT * 2;
@@ -96,6 +109,11 @@ export function chatViewLines(thread, width, label, now = Date.now()) {
96
109
  lines.push({ key: `${turn.id}:body:${index}`, kind: "body", text });
97
110
  });
98
111
  }
112
+ if (turn.speaker !== "you") {
113
+ const effects = chatInstructionEffects(turn.body);
114
+ if (effects)
115
+ lines.push({ key: `${turn.id}:effects`, kind: "status", text: effects });
116
+ }
99
117
  }
100
118
  if (thread.pending) {
101
119
  const live = pendingActivity([], { started_at: thread.pending.startedAt, status: thread.pending.status }, label, now);
package/dist/tui/data.js CHANGED
@@ -1,4 +1,4 @@
1
- import { approveEpic as approveEpicNow, answerDecision, cancelTicket as cancelTicketNow, createAgent as postAgent, createEpic as postEpic, deleteEpic as removeEpic, getStatus, getRoadmap, getRun, getWorkspace, listAgents, listFeed, listMessages, markMessagesDelivered, listWorkspaceEnv as getWorkspaceEnv, listTicketRunEvents, listTickets, listWorkspaces, postMessage as sendMessage, queueTicket as queueTicketNow, setPaused as setPausedNow, showTicket, updateAgent as patchAgent, updateCaps, updateWorkspace as patchWorkspace, deleteAgent as removeAgent, } from "../api.js";
1
+ import { approveEpic as approveEpicNow, answerDecision, cancelTicket as cancelTicketNow, createAgent as postAgent, createEpic as postEpic, deleteEpic as removeEpic, getStatus, getRoadmap, getRun, getWorkspace, listAgents, listFeed, listMessages, markMessagesDelivered, listWorkspaceEnv as getWorkspaceEnv, listTicketRunEvents, listTickets, listWorkspaces, postMessage as sendMessage, mergeTicket as mergeTicketNow, queueTicket as queueTicketNow, setPaused as setPausedNow, showTicket, updateAgent as patchAgent, updateCaps, updateWorkspace as patchWorkspace, deleteAgent as removeAgent, } from "../api.js";
2
2
  import { loadConfig, switchWorkspace as selectWorkspace, } from "../config.js";
3
3
  import { readEpicSpec } from "../epics.js";
4
4
  import { EARLIER_PAGE } from "./inbox.js";
@@ -188,6 +188,9 @@ export async function queueTicket(config, key) {
188
188
  export async function cancelTicket(config, key) {
189
189
  return cancelTicketNow(key, config);
190
190
  }
191
+ export async function mergeTicket(config, key, reason) {
192
+ return mergeTicketNow(key, reason, config);
193
+ }
191
194
  export async function setWorkspacePaused(config, paused) {
192
195
  return setPausedNow(paused, config);
193
196
  }
package/dist/tui/parse.js CHANGED
@@ -80,6 +80,9 @@ export function parseLine(raw) {
80
80
  case "cancel":
81
81
  return argument ? { kind: "cancel", key: argument.toUpperCase() }
82
82
  : { kind: "unknown", command: "cancel needs a key" };
83
+ case "merge":
84
+ return argument ? { kind: "merge", key: argument.toUpperCase() }
85
+ : { kind: "unknown", command: "merge needs a key" };
83
86
  case "decide": {
84
87
  const dismiss = /(^|\s)--(skip|dismiss)(\s|$)/.test(argument);
85
88
  const words = argument.replace(/(^|\s)--(skip|dismiss)(\s|$)/g, " ").trim().split(/\s+/).filter(Boolean);
@@ -130,7 +130,7 @@ export function ticketViewLines(ticket, width, collapsed = []) {
130
130
  pushWrapped(lines, "timeline", " ", event.headline, width);
131
131
  lines.push({
132
132
  key: `${event.id}:meta`, kind: "line", section: "timeline",
133
- text: ` ${event.outcome} · ${event.kind}/${event.provider} · ${elapsedLabel(event.elapsed_ms)}`,
133
+ text: ` ${event.outcome} · ${event.kind}/${event.provider ?? "owner"} · ${elapsedLabel(event.elapsed_ms)}`,
134
134
  });
135
135
  if (event.send_back_reason) {
136
136
  pushWrapped(lines, "timeline", " sent back because: ", event.send_back_reason, width);
package/package.json CHANGED
@@ -1,7 +1,12 @@
1
1
  {
2
2
  "name": "@higherdev/cli",
3
- "version": "0.21.0",
3
+ "version": "0.23.0",
4
4
  "type": "module",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/craig-higherops/hdx.git",
8
+ "directory": "apps/cli"
9
+ },
5
10
  "bin": {
6
11
  "hd": "dist/index.js"
7
12
  },