@higherdev/cli 0.16.0 → 0.18.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/README.md +9 -2
- package/dist/api.js +8 -3
- package/dist/host.js +119 -0
- package/dist/index.js +57 -29
- package/dist/out.js +3 -3
- package/dist/tui/App.js +186 -52
- package/dist/tui/Dashboard.js +29 -11
- package/dist/tui/Decision.js +18 -31
- package/dist/tui/Help.js +4 -4
- package/dist/tui/Panels.js +48 -30
- package/dist/tui/TextInput.js +5 -1
- package/dist/tui/agent-rows.js +14 -2
- package/dist/tui/data.js +20 -6
- package/dist/tui/decide-nav.js +62 -0
- package/dist/tui/narrate.js +97 -0
- package/dist/tui/parse.js +5 -2
- package/dist/tui/stream.js +51 -34
- package/dist/tui/ticket-view.js +226 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ workspace-map config shapes are migrated automatically when they are read.
|
|
|
25
25
|
| `hd --help` | Show the HigherDEV banner and usage |
|
|
26
26
|
| `hd status` | Show workspace, ticket, run, and decision status |
|
|
27
27
|
| `hd ticket list` | List tickets |
|
|
28
|
-
| `hd ticket show KEY` | Show one ticket |
|
|
28
|
+
| `hd ticket show KEY [--json]` | Show one ticket (body, acceptance, timeline, messages, PR, runs, decisions) |
|
|
29
29
|
| `hd ticket new [PATH.md]` | Create a ticket from the guided form or a Markdown spec |
|
|
30
30
|
| `hd ticket new --title TITLE [--acceptance TEXT] [options]` | Create a ticket non-interactively |
|
|
31
31
|
| `hd ticket queue KEY` | Queue a complete ticket now |
|
|
@@ -49,6 +49,7 @@ workspace-map config shapes are migrated automatically when they are read.
|
|
|
49
49
|
| `hd env ls` | List workspace environment variable names |
|
|
50
50
|
| `hd env set NAME=VALUE [NAME=VALUE...]` | Set workspace environment variables |
|
|
51
51
|
| `hd env rm NAME` | Remove a workspace environment variable |
|
|
52
|
+
| `hd host roll [--host HOST] [--address ADDR] [--checkout DIR]` | Pull, install, and restart a runner over SSH, then watch drain |
|
|
52
53
|
| `hd host env ls` | List environment variable names on the workspace's default host |
|
|
53
54
|
| `hd host env set NAME=VALUE [NAME=VALUE...]` | Set host-only environment variables |
|
|
54
55
|
| `hd host env rm NAME` | Remove a host-only environment variable |
|
|
@@ -64,9 +65,15 @@ workspace-map config shapes are migrated automatically when they are read.
|
|
|
64
65
|
| `hd upgrade --host HOST [--checkout DIR] [options]` | Refresh this host configuration |
|
|
65
66
|
|
|
66
67
|
`hd init` uses `~/hdx` as its checkout by default. Pass `--checkout DIR` when the runner source is
|
|
67
|
-
elsewhere.
|
|
68
|
+
elsewhere. Pass `--address HOST` to store `RUNNER_SSH_ADDRESS` so `hd host roll` can SSH to that
|
|
69
|
+
host. If the runner environment or service unit already exists, inspect the paths it prints and
|
|
68
70
|
pass `--force` only when replacing that host configuration is intentional.
|
|
69
71
|
|
|
72
|
+
`hd host roll` SSHes to the host address (`--address`, or `RUNNER_SSH_ADDRESS` from the host
|
|
73
|
+
heartbeat), pulls the checkout, installs, and restarts the systemd unit. The runner drains live
|
|
74
|
+
runs for up to 15 minutes (`RUNNER_DRAIN_TIMEOUT_MS`) before force-closing leftovers. The systemd
|
|
75
|
+
unit `TimeoutStopSec` is one minute above that drain timeout.
|
|
76
|
+
|
|
70
77
|
`hd workspace new` uses the operator's authenticated `gh`, defaults to the repository's real default
|
|
71
78
|
branch, bootstraps an empty repository unless `--no-bootstrap` is set, and invites `mel-ilotus` with push
|
|
72
79
|
access for user-owned repositories or admin for organization-owned repositories unless `--runner-user USER`
|
package/dist/api.js
CHANGED
|
@@ -58,14 +58,19 @@ 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
|
|
61
|
+
export async function listTicketRunEvents(key, afterAt, afterId, config = loadConfig()) {
|
|
62
62
|
const query = new URLSearchParams();
|
|
63
63
|
if (afterAt)
|
|
64
64
|
query.set("after_at", afterAt);
|
|
65
65
|
if (afterId)
|
|
66
66
|
query.set("after_id", afterId);
|
|
67
|
-
const suffix = query.size ?
|
|
68
|
-
|
|
67
|
+
const suffix = query.size ? `&${query.toString()}` : "";
|
|
68
|
+
const result = await request(config, "GET", `/api/w/${config.slug}/tickets/${encodeURIComponent(key)}/events?stream=1${suffix}`);
|
|
69
|
+
const runs = new Map((result.runs ?? []).map((run) => [run.id, run]));
|
|
70
|
+
return { ...result, events: result.events.map((event) => ({ ...event, run: runs.get(event.run_id) })) };
|
|
71
|
+
}
|
|
72
|
+
export async function listTicketStory(key, config = loadConfig()) {
|
|
73
|
+
return request(config, "GET", `/api/w/${config.slug}/tickets/${encodeURIComponent(key)}/events`);
|
|
69
74
|
}
|
|
70
75
|
export async function postMessage(fields, config = loadConfig()) {
|
|
71
76
|
return request(config, "POST", `/api/w/${config.slug}/messages`, fields);
|
package/dist/host.js
CHANGED
|
@@ -53,6 +53,35 @@ export function launchdPlist(opts) {
|
|
|
53
53
|
</plist>
|
|
54
54
|
`;
|
|
55
55
|
}
|
|
56
|
+
export const DEFAULT_DRAIN_TIMEOUT_MS = 15 * 60_000;
|
|
57
|
+
export const HOST_ROLL_USAGE = "usage: hd host roll [--host HOST] [--address ADDR] [--checkout DIR]";
|
|
58
|
+
export function systemdStopTimeoutSec(drainTimeoutMs = DEFAULT_DRAIN_TIMEOUT_MS) {
|
|
59
|
+
return Math.ceil(drainTimeoutMs / 1_000) + 60;
|
|
60
|
+
}
|
|
61
|
+
export function formatDrainStatus(live, until, now = Date.now()) {
|
|
62
|
+
const remaining = Date.parse(until) - now;
|
|
63
|
+
const minutes = Number.isFinite(remaining) ? Math.max(0, Math.ceil(remaining / 60_000)) : 0;
|
|
64
|
+
const runs = live === 1 ? "1 run" : `${live} runs`;
|
|
65
|
+
return `draining: ${runs}, up to ${minutes}m`;
|
|
66
|
+
}
|
|
67
|
+
export function hostDrainFromStats(stats) {
|
|
68
|
+
if (!stats || typeof stats !== "object" || Array.isArray(stats))
|
|
69
|
+
return null;
|
|
70
|
+
const draining = stats.draining;
|
|
71
|
+
if (!draining || typeof draining !== "object" || Array.isArray(draining))
|
|
72
|
+
return null;
|
|
73
|
+
const live = Number(draining.live);
|
|
74
|
+
const until = draining.until;
|
|
75
|
+
if (!Number.isFinite(live) || live < 0 || typeof until !== "string")
|
|
76
|
+
return null;
|
|
77
|
+
return { live, until };
|
|
78
|
+
}
|
|
79
|
+
export function sshAddressFromStats(stats) {
|
|
80
|
+
if (!stats || typeof stats !== "object" || Array.isArray(stats))
|
|
81
|
+
return null;
|
|
82
|
+
const value = stats.ssh_address;
|
|
83
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
84
|
+
}
|
|
56
85
|
export function systemdUnit(opts) {
|
|
57
86
|
return `[Unit]
|
|
58
87
|
Description=HDX runner
|
|
@@ -65,6 +94,7 @@ WorkingDirectory=${opts.workDir}
|
|
|
65
94
|
ExecStart=${opts.nodePath} --experimental-strip-types ${opts.runnerPath}
|
|
66
95
|
Restart=always
|
|
67
96
|
RestartSec=5
|
|
97
|
+
TimeoutStopSec=${systemdStopTimeoutSec(opts.drainTimeoutMs)}
|
|
68
98
|
|
|
69
99
|
[Install]
|
|
70
100
|
WantedBy=default.target
|
|
@@ -85,6 +115,8 @@ export function parseHostFlags(argv) {
|
|
|
85
115
|
flags.host = next();
|
|
86
116
|
else if (arg === "--checkout")
|
|
87
117
|
flags.checkout = next();
|
|
118
|
+
else if (arg === "--address")
|
|
119
|
+
flags.address = next();
|
|
88
120
|
else if (arg === "--force")
|
|
89
121
|
flags.force = true;
|
|
90
122
|
else if (arg === "--env-out")
|
|
@@ -104,6 +136,45 @@ export function parseHostFlags(argv) {
|
|
|
104
136
|
}
|
|
105
137
|
return flags;
|
|
106
138
|
}
|
|
139
|
+
export function parseHostRollFlags(argv) {
|
|
140
|
+
const flags = {};
|
|
141
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
142
|
+
const arg = argv[i];
|
|
143
|
+
const next = () => {
|
|
144
|
+
const value = argv[i + 1];
|
|
145
|
+
if (!value || value.startsWith("--"))
|
|
146
|
+
throw new Error(`Missing value for ${arg}`);
|
|
147
|
+
i += 1;
|
|
148
|
+
return value;
|
|
149
|
+
};
|
|
150
|
+
if (arg === "--host")
|
|
151
|
+
flags.host = next();
|
|
152
|
+
else if (arg === "--address")
|
|
153
|
+
flags.address = next();
|
|
154
|
+
else if (arg === "--checkout")
|
|
155
|
+
flags.checkout = next();
|
|
156
|
+
else if (!arg.startsWith("-") && !flags.host)
|
|
157
|
+
flags.host = arg;
|
|
158
|
+
else
|
|
159
|
+
throw new Error(arg.startsWith("-") ? `Unknown option: ${arg}` : HOST_ROLL_USAGE);
|
|
160
|
+
}
|
|
161
|
+
return flags;
|
|
162
|
+
}
|
|
163
|
+
function shellQuote(value) {
|
|
164
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
165
|
+
}
|
|
166
|
+
export function hostRollCommand(checkout) {
|
|
167
|
+
return [
|
|
168
|
+
"set -e",
|
|
169
|
+
`cd ${shellQuote(checkout)}`,
|
|
170
|
+
"git pull --ff-only",
|
|
171
|
+
"pnpm install",
|
|
172
|
+
"systemctl --user restart hdx-runner.service",
|
|
173
|
+
].join(" && ");
|
|
174
|
+
}
|
|
175
|
+
async function defaultSsh(address, command) {
|
|
176
|
+
return execFileAsync("ssh", ["-o", "BatchMode=yes", address, "bash", "-lc", command], { encoding: "utf8" });
|
|
177
|
+
}
|
|
107
178
|
async function exists(file) {
|
|
108
179
|
try {
|
|
109
180
|
await access(file);
|
|
@@ -159,6 +230,9 @@ export async function initHost(flags, env = process.env) {
|
|
|
159
230
|
RUNNER_HOST: host,
|
|
160
231
|
WORKTREE_ROOT: worktreeRoot,
|
|
161
232
|
GH_TOKEN: env.GH_TOKEN || existing.GH_TOKEN || "",
|
|
233
|
+
...(flags.address || env.RUNNER_SSH_ADDRESS || existing.RUNNER_SSH_ADDRESS
|
|
234
|
+
? { RUNNER_SSH_ADDRESS: flags.address || env.RUNNER_SSH_ADDRESS || existing.RUNNER_SSH_ADDRESS }
|
|
235
|
+
: {}),
|
|
162
236
|
};
|
|
163
237
|
await mkdir(path.dirname(envFile), { recursive: true });
|
|
164
238
|
await writeFile(envFile, formatEnvFile(merged), { encoding: "utf8", mode: 0o600 });
|
|
@@ -198,3 +272,48 @@ export async function initHost(flags, env = process.env) {
|
|
|
198
272
|
lines.push("Start the runner service to register this host.");
|
|
199
273
|
return lines.join("\n");
|
|
200
274
|
}
|
|
275
|
+
const ROLL_WAIT_MS = DEFAULT_DRAIN_TIMEOUT_MS + 2 * 60_000;
|
|
276
|
+
export async function hostRoll(flags, deps = {}) {
|
|
277
|
+
const log = deps.log ?? ((line) => console.log(line));
|
|
278
|
+
const now = deps.now ?? Date.now;
|
|
279
|
+
const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
280
|
+
const ssh = deps.ssh ?? defaultSsh;
|
|
281
|
+
const loadStatus = deps.getStatus ?? (await import("./api.js")).getStatus;
|
|
282
|
+
const snapshot = await loadStatus();
|
|
283
|
+
const host = flags.host ?? snapshot.workspace.default_host;
|
|
284
|
+
const address = flags.address ?? snapshot.host?.ssh_address ?? null;
|
|
285
|
+
if (!address) {
|
|
286
|
+
throw new Error(`hd host roll needs an SSH address for ${host}. Pass --address or set RUNNER_SSH_ADDRESS on the host.`);
|
|
287
|
+
}
|
|
288
|
+
const checkout = expandHome(flags.checkout ?? "~/hdx", homedir());
|
|
289
|
+
const command = hostRollCommand(checkout);
|
|
290
|
+
const lines = [`rolling ${host} at ${address}`];
|
|
291
|
+
log(lines[0]);
|
|
292
|
+
await ssh(address, command);
|
|
293
|
+
const restarted = `restarted ${host}; waiting for drain`;
|
|
294
|
+
lines.push(restarted);
|
|
295
|
+
log(restarted);
|
|
296
|
+
const before = snapshot.host?.last_seen_at ?? null;
|
|
297
|
+
const deadline = now() + (deps.waitMs ?? ROLL_WAIT_MS);
|
|
298
|
+
let lastDrain = "";
|
|
299
|
+
while (now() < deadline) {
|
|
300
|
+
const status = await loadStatus();
|
|
301
|
+
const drain = status.host?.draining ?? null;
|
|
302
|
+
if (drain) {
|
|
303
|
+
const line = formatDrainStatus(drain.live, drain.until, now());
|
|
304
|
+
if (line !== lastDrain) {
|
|
305
|
+
lastDrain = line;
|
|
306
|
+
lines.push(line);
|
|
307
|
+
log(line);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
else if (status.host?.last_seen_at && status.host.last_seen_at !== before && status.host.runner_version) {
|
|
311
|
+
const line = `${host} runner ${status.host.runner_version}`;
|
|
312
|
+
lines.push(line);
|
|
313
|
+
log(line);
|
|
314
|
+
return lines;
|
|
315
|
+
}
|
|
316
|
+
await sleep(2_000);
|
|
317
|
+
}
|
|
318
|
+
throw new Error(`timed out waiting for ${host} to finish draining`);
|
|
319
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
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, getWorkspace, listAgents, listEpics, listHostEnv, listMessages, listWorkspaceEnv,
|
|
5
|
-
import { initHost, parseHostFlags } from "./host.js";
|
|
4
|
+
import { approveEpic, answerDecision, cancelTicket, createEpic, deleteAgent, deleteEpic, getStatus, getWorkspace, listAgents, listEpics, listHostEnv, listMessages, listWorkspaceEnv, listTicketRunEvents, listTickets, listWorkspaces, postMessage, queueTicket, removeWorkspaceEnv, removeHostEnv, setPaused, setWorkspaceEnv, setHostEnv, showTicket, updateAgent, updateCaps, } from "./api.js";
|
|
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";
|
|
8
8
|
import { epicProgressRows, readEpicSpec } from "./epics.js";
|
|
9
9
|
import { banner, c, statusChip, table, truncate, usage } from "./out.js";
|
|
10
10
|
import { ticketNew } from "./ticket-commands.js";
|
|
11
|
+
import { ticketViewLines } from "./tui/ticket-view.js";
|
|
11
12
|
import { agentAdd, AGENT_ADD_USAGE } from "./agent-commands.js";
|
|
12
13
|
import { WORKSPACE_USAGE, workspaceGrantRunnerAccess, workspaceNew, workspaceRotateKey, workspaceSet, workspaceUse } from "./workspace-commands.js";
|
|
13
14
|
import { defaultGh, hasWriteRepoPermission, HDX_RUNNER_GH_USER, requireAdminRepo, runnerRepoPermission } from "./workspace-preflight.js";
|
|
@@ -54,13 +55,17 @@ function printTickets(tickets) {
|
|
|
54
55
|
}
|
|
55
56
|
console.log(table(["KEY", "STATUS", "PROVIDER", "TITLE", "WHY"], tickets.map((ticket) => [
|
|
56
57
|
c.bold(ticket.key), statusChip(ticket.status), ticket.provider ?? c.dim("-"),
|
|
57
|
-
truncate(ticket.title, 48), ticket.
|
|
58
|
+
truncate(ticket.title, 48), ticket.latest_headline ? c.yellow(truncate(ticket.latest_headline, 40))
|
|
59
|
+
: ticket.stuck_reason ? c.yellow(truncate(ticket.stuck_reason, 40)) : "",
|
|
58
60
|
])));
|
|
59
61
|
}
|
|
60
62
|
async function cmdStatus() {
|
|
61
63
|
const data = await getStatus();
|
|
62
64
|
const state = data.workspace.paused ? c.yellow("off") : c.green("on");
|
|
63
65
|
console.log(`${c.bold(data.workspace.name)} ${c.dim(data.workspace.repo)} ${state}\n`);
|
|
66
|
+
if (data.host?.draining) {
|
|
67
|
+
console.log(`${c.yellow(formatDrainStatus(data.host.draining.live, data.host.draining.until))}\n`);
|
|
68
|
+
}
|
|
64
69
|
const waiting = [...new Set(data.tickets.map((ticket) => ticket.stuck_reason
|
|
65
70
|
?.match(/^Waiting on (\w+) until (\d{1,2}:\d{2})\.$/)).filter(Boolean)
|
|
66
71
|
.map((match) => `${match?.[1]} waiting until ${match?.[2]}`))];
|
|
@@ -85,23 +90,31 @@ async function cmdTicket(argv, deps = {}) {
|
|
|
85
90
|
return;
|
|
86
91
|
}
|
|
87
92
|
if (action === "show") {
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (
|
|
94
|
-
console.log(
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
93
|
+
const parsed = flags(rest);
|
|
94
|
+
const key = parsed.rest[0];
|
|
95
|
+
if (!key || parsed.rest.length !== 1)
|
|
96
|
+
fail("usage: hd ticket show KEY [--json]");
|
|
97
|
+
const data = await showTicket(key.toUpperCase());
|
|
98
|
+
if (parsed.bools.has("json")) {
|
|
99
|
+
console.log(JSON.stringify(data));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const { ticket, pr, events, runs, messages, decisions } = data;
|
|
103
|
+
const width = process.stdout.columns && process.stdout.columns > 0 ? process.stdout.columns : 80;
|
|
104
|
+
for (const line of ticketViewLines({
|
|
105
|
+
...ticket,
|
|
106
|
+
stuck: ticket.stuck_reason,
|
|
107
|
+
pr,
|
|
108
|
+
timeline: events,
|
|
109
|
+
messages,
|
|
110
|
+
runs: runs.map((run) => ({
|
|
111
|
+
id: run.id, kind: run.kind, provider: run.provider, status: run.status,
|
|
112
|
+
summary: run.summary, elapsed_ms: run.elapsed_ms,
|
|
113
|
+
})),
|
|
114
|
+
decisions,
|
|
115
|
+
}, width)) {
|
|
116
|
+
console.log(line.text);
|
|
117
|
+
}
|
|
105
118
|
return;
|
|
106
119
|
}
|
|
107
120
|
if (action === "new") {
|
|
@@ -182,7 +195,7 @@ async function cmdLogs(argv) {
|
|
|
182
195
|
let afterId;
|
|
183
196
|
const follow = bools.has("follow");
|
|
184
197
|
async function tick() {
|
|
185
|
-
const { events } = await
|
|
198
|
+
const { events } = await listTicketRunEvents(key, afterAt, afterId);
|
|
186
199
|
let printed = false;
|
|
187
200
|
for (const event of events) {
|
|
188
201
|
if (seen.has(event.id))
|
|
@@ -413,12 +426,23 @@ function githubSecretMissing(error) {
|
|
|
413
426
|
return /404|not found|does not exist/i.test(message);
|
|
414
427
|
}
|
|
415
428
|
export async function cmdEnv(argv, deps = {}) {
|
|
416
|
-
const
|
|
417
|
-
|
|
418
|
-
|
|
429
|
+
const parsed = flags(argv);
|
|
430
|
+
const [rawAction, ...args] = parsed.rest;
|
|
431
|
+
const action = !rawAction && parsed.bools.has("vercel") ? "ls" : rawAction;
|
|
432
|
+
if (action === "ls" && !args.length && !Object.keys(parsed.opts).length
|
|
433
|
+
&& [...parsed.bools].every((name) => name === "vercel")) {
|
|
434
|
+
const showVercel = parsed.bools.has("vercel");
|
|
435
|
+
const [{ env }, detail] = await Promise.all([listWorkspaceEnv(), showVercel ? getWorkspace() : null]);
|
|
419
436
|
if (!env.length)
|
|
420
437
|
return console.log(c.dim("No workspace environment variables."));
|
|
421
|
-
|
|
438
|
+
if (!showVercel)
|
|
439
|
+
console.log(table(["NAME", "UPDATED"], env.map((row) => [row.name, row.updated_at])));
|
|
440
|
+
else {
|
|
441
|
+
const production = new Set(detail?.workspace.settings?.vercel?.production_env ?? []);
|
|
442
|
+
console.log(table(["NAME", "UPDATED", "VERCEL"], env.map((row) => [
|
|
443
|
+
row.name, row.updated_at, production.has(row.name) ? "yes" : "no",
|
|
444
|
+
])));
|
|
445
|
+
}
|
|
422
446
|
return;
|
|
423
447
|
}
|
|
424
448
|
if (action === "set" && args.length) {
|
|
@@ -461,11 +485,15 @@ export async function cmdEnv(argv, deps = {}) {
|
|
|
461
485
|
console.log(`synced removal of ${name} to ${repo}`);
|
|
462
486
|
return;
|
|
463
487
|
}
|
|
464
|
-
fail("usage: hd env ls | set NAME=VALUE [NAME=VALUE...] | rm NAME");
|
|
488
|
+
fail("usage: hd env ls [--vercel] | set NAME=VALUE [NAME=VALUE...] | rm NAME");
|
|
465
489
|
}
|
|
466
|
-
async function cmdHost(argv) {
|
|
490
|
+
async function cmdHost(argv, deps = {}) {
|
|
467
491
|
const [scope, action, ...args] = argv;
|
|
468
|
-
|
|
492
|
+
if (scope === "roll") {
|
|
493
|
+
await hostRoll(parseHostRollFlags([action, ...args].filter((value) => Boolean(value))), deps);
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
const usage = "usage: hd host roll [--host HOST] [--address ADDR] [--checkout DIR] | env ls | set NAME=VALUE [NAME=VALUE...] | rm NAME";
|
|
469
497
|
if (scope !== "env")
|
|
470
498
|
fail(usage);
|
|
471
499
|
const host = (await getStatus()).workspace.default_host;
|
|
@@ -563,7 +591,7 @@ export async function main(argv = process.argv.slice(2), deps = {}) {
|
|
|
563
591
|
return;
|
|
564
592
|
}
|
|
565
593
|
if (cmd === "host") {
|
|
566
|
-
await cmdHost(rest);
|
|
594
|
+
await cmdHost(rest, deps);
|
|
567
595
|
return;
|
|
568
596
|
}
|
|
569
597
|
if (cmd === "pause" || cmd === "off") {
|
package/dist/out.js
CHANGED
|
@@ -64,14 +64,14 @@ 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 | new [PATH] | queue | cancel")} ticket operations`,
|
|
67
|
+
` ${c.blue("hd ticket list | show KEY [--json] | new [PATH] | queue | cancel")} ticket operations`,
|
|
68
68
|
` ${c.blue("hd epic new PATH | list | approve | rm")} epic operations`,
|
|
69
69
|
` ${c.blue("hd plan")} use /architect in the TUI`,
|
|
70
70
|
` ${c.blue("hd workspace ls | use | new | set | rotate-key | grant-runner-access")} workspace operations`,
|
|
71
71
|
` ${c.blue("hd agents [add | rm | set]")} manage agents`,
|
|
72
72
|
` ${c.blue("hd caps [set PROVIDER N]")} provider concurrency`,
|
|
73
|
-
` ${c.blue("hd env ls | set | rm")}
|
|
74
|
-
` ${c.blue("hd host env ls | set | rm")}
|
|
73
|
+
` ${c.blue("hd env ls [--vercel] | set | rm")} workspace environment`,
|
|
74
|
+
` ${c.blue("hd host roll | env ls | set | rm")} roll a host or edit host env`,
|
|
75
75
|
` ${c.blue("hd logs KEY [-f]")} run events`,
|
|
76
76
|
` ${c.blue("hd msg KEY TEXT")} message a builder`,
|
|
77
77
|
` ${c.blue("hd inbox [--all] [--limit N] [--json]")} inbox messages`,
|