@higherdev/cli 0.10.0 → 0.11.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 +5 -2
- package/dist/index.js +14 -58
- package/dist/tui/App.js +34 -3
- package/dist/tui/Help.js +1 -1
- package/dist/tui/parse.js +4 -0
- package/dist/workspace-commands.js +170 -0
- package/dist/workspace-preflight.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -56,9 +56,12 @@ workspace-map config shapes are migrated automatically when they are read.
|
|
|
56
56
|
|
|
57
57
|
`hd workspace new` uses the operator's authenticated `gh`, defaults to the repository's real default
|
|
58
58
|
branch, bootstraps an empty repository unless `--no-bootstrap` is set, and invites `mel-ilotus` unless
|
|
59
|
-
`--runner-user USER` overrides it.
|
|
59
|
+
`--runner-user USER` overrides it. On a terminal, missing name or repo flags start a guided wizard. If
|
|
60
|
+
the repository is missing, approve private creation interactively, use `--create` to force it, or
|
|
61
|
+
`--no-create` to fail. Fully flagged calls remain non-interactive for scripts and Mel.
|
|
60
62
|
|
|
61
63
|
Inside the TUI, use `/board`, `/inbox`, `/ticket`, `/queue`, `/cancel`, `/epic new`,
|
|
62
|
-
`/epics`, `/plan`, `/decide`, `/agents add`, `/agents rm`, `/settings`, `/workspace`,
|
|
64
|
+
`/epics`, `/plan`, `/decide`, `/agents add`, `/agents rm`, `/settings`, `/workspace`,
|
|
65
|
+
`/workspace new`, `/workspace set`, `/workspace rotate-key`, `/feed`,
|
|
63
66
|
`/orchestrator`, `/refresh`, `/help`, or `/exit`. The display refreshes from
|
|
64
67
|
the HDX API every five seconds.
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { realpathSync } from "node:fs";
|
|
3
3
|
import { pathToFileURL } from "node:url";
|
|
4
|
-
import { answerDecision, cancelTicket, createAgent, createEpic, createTicket,
|
|
4
|
+
import { answerDecision, cancelTicket, createAgent, createEpic, createTicket, deleteAgent, getStatus, listAgents, listEpics, listTicketEvents, listTickets, listWorkspaces, postMessage, queueTicket, setPaused, showTicket, updateAgent, updateCaps, } from "./api.js";
|
|
5
5
|
import { initHost, parseHostFlags } from "./host.js";
|
|
6
|
-
import { loadConfig
|
|
6
|
+
import { loadConfig } from "./config.js";
|
|
7
7
|
import { epicProgressRows, readEpicSpec } from "./epics.js";
|
|
8
8
|
import { banner, c, statusChip, table, truncate, usage } from "./out.js";
|
|
9
9
|
import { launchArchitect } from "./plan.js";
|
|
10
|
-
import {
|
|
10
|
+
import { WORKSPACE_USAGE, workspaceNew, workspaceRotateKey, workspaceSet } from "./workspace-commands.js";
|
|
11
11
|
function fail(message) {
|
|
12
12
|
console.error(message);
|
|
13
13
|
process.exit(1);
|
|
@@ -241,71 +241,27 @@ async function cmdWorkspace(argv, deps = {}) {
|
|
|
241
241
|
])));
|
|
242
242
|
return;
|
|
243
243
|
}
|
|
244
|
-
const workspaceUsage = "usage: hd workspace ls | new ... | set [flags] | rotate-key";
|
|
245
244
|
if (action === "rotate-key") {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
console.log(`api key: ${
|
|
245
|
+
if (rest.length)
|
|
246
|
+
fail(WORKSPACE_USAGE);
|
|
247
|
+
const result = await workspaceRotateKey();
|
|
248
|
+
console.log(`api key: ${result.apiKey}`);
|
|
250
249
|
console.log("saved locally; update Mel's hd init configuration on the box.");
|
|
251
250
|
return;
|
|
252
251
|
}
|
|
253
252
|
if (action === "set") {
|
|
254
|
-
const
|
|
255
|
-
if (args.length || bools.size)
|
|
256
|
-
fail(workspaceUsage);
|
|
257
|
-
const fields = {};
|
|
258
|
-
for (const [flag, field] of [["name", "name"], ["repo", "repo"], ["branch", "default_branch"], ["host", "default_host"]]) {
|
|
259
|
-
if (opts[flag])
|
|
260
|
-
fields[field] = opts[flag];
|
|
261
|
-
}
|
|
262
|
-
if (opts["auto-merge"]) {
|
|
263
|
-
if (!["on", "off"].includes(opts["auto-merge"]))
|
|
264
|
-
fail("--auto-merge must be on or off");
|
|
265
|
-
fields.auto_merge = opts["auto-merge"] === "on";
|
|
266
|
-
}
|
|
267
|
-
if (opts["max-turns"]) {
|
|
268
|
-
const maxTurns = {};
|
|
269
|
-
for (const entry of opts["max-turns"].split(",")) {
|
|
270
|
-
const [kind, raw, ...extra] = entry.split("=");
|
|
271
|
-
const value = Number(raw);
|
|
272
|
-
if (extra.length || !["build", "followup", "review", "orchestrate"].includes(kind)
|
|
273
|
-
|| !Number.isInteger(value) || value < 1) {
|
|
274
|
-
fail("--max-turns must be KIND=N[,KIND=N...] for build, followup, review, or orchestrate");
|
|
275
|
-
}
|
|
276
|
-
maxTurns[kind] = value;
|
|
277
|
-
}
|
|
278
|
-
fields.max_turns = maxTurns;
|
|
279
|
-
}
|
|
280
|
-
if (opts["max-attempts"]) {
|
|
281
|
-
const value = Number(opts["max-attempts"]);
|
|
282
|
-
if (!Number.isInteger(value) || value < 1)
|
|
283
|
-
fail("--max-attempts must be an integer >= 1");
|
|
284
|
-
fields.max_attempts = value;
|
|
285
|
-
}
|
|
286
|
-
if (!Object.keys(fields).length)
|
|
287
|
-
fail(workspaceUsage);
|
|
288
|
-
const { workspace } = await updateWorkspace(fields);
|
|
253
|
+
const workspace = await workspaceSet(rest);
|
|
289
254
|
console.log(`${workspace.slug} ${workspace.name} ${workspace.repo}`);
|
|
290
255
|
return;
|
|
291
256
|
}
|
|
292
257
|
if (action !== "new")
|
|
293
|
-
fail(
|
|
294
|
-
const
|
|
295
|
-
if (
|
|
296
|
-
|
|
297
|
-
}
|
|
298
|
-
const preflight = await (deps.preflightWorkspace ?? preflightWorkspace)({ name: opts.name, repo: opts.repo,
|
|
299
|
-
branch: opts.branch, noBootstrap: bools.has("no-bootstrap"),
|
|
300
|
-
runnerUser: opts["runner-user"] ?? HDX_RUNNER_GH_USER });
|
|
301
|
-
if (preflight.invitationPending)
|
|
302
|
-
console.log(`invitation pending for ${opts["runner-user"] ?? HDX_RUNNER_GH_USER}`);
|
|
303
|
-
const result = await createWorkspace({ name: opts.name, repo: opts.repo, slug: opts.slug,
|
|
304
|
-
default_branch: preflight.branch, default_host: opts.host ?? "box" });
|
|
305
|
-
console.log(`workspace: ${result.workspace.slug}`);
|
|
306
|
-
writeHdConfig({ url: result.url, api_key: result.api_key, slug: result.workspace.slug });
|
|
258
|
+
fail(WORKSPACE_USAGE);
|
|
259
|
+
const result = await workspaceNew(rest, deps);
|
|
260
|
+
if (result.invitationPending)
|
|
261
|
+
console.log(`invitation pending for ${result.runnerUser}`);
|
|
262
|
+
console.log(`workspace: ${result.slug}`);
|
|
307
263
|
console.log("saved and switched; configure another machine with:");
|
|
308
|
-
console.log(
|
|
264
|
+
console.log(result.initCommand);
|
|
309
265
|
}
|
|
310
266
|
function selectAgent(agents, target, provider) {
|
|
311
267
|
const exact = agents.find((agent) => agent.id === target);
|
package/dist/tui/App.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
3
|
import { Box, Static, Text, useApp, useInput, useStdout } from "ink";
|
|
4
|
+
import { loadConfig } from "../config.js";
|
|
4
5
|
import { epicProgressRows } from "../epics.js";
|
|
5
6
|
import { launchArchitect } from "../plan.js";
|
|
7
|
+
import { workspaceNew, workspaceRotateKey, workspaceSet } from "../workspace-commands.js";
|
|
6
8
|
import { Banner } from "./Banner.js";
|
|
7
9
|
import { Bubble } from "./Bubble.js";
|
|
8
10
|
import { Cockpit, StreamPanel, boardTicketIds, nextCursor } from "./Dashboard.js";
|
|
@@ -165,10 +167,10 @@ export function App({ initial }) {
|
|
|
165
167
|
setBusy(false);
|
|
166
168
|
}
|
|
167
169
|
}, [settings, config, refresh]);
|
|
168
|
-
const changeWorkspace = useCallback(async (slug) => {
|
|
170
|
+
const changeWorkspace = useCallback(async (slug, source = config) => {
|
|
169
171
|
setBusy(true);
|
|
170
172
|
try {
|
|
171
|
-
const snapshot = await switchWorkspace(slug,
|
|
173
|
+
const snapshot = await switchWorkspace(slug, source);
|
|
172
174
|
loads.current.switchTo(snapshot.workspace.id);
|
|
173
175
|
setConfig(snapshot.config);
|
|
174
176
|
setWorkspace(snapshot.workspace);
|
|
@@ -188,7 +190,7 @@ export function App({ initial }) {
|
|
|
188
190
|
finally {
|
|
189
191
|
setBusy(false);
|
|
190
192
|
}
|
|
191
|
-
}, [say]);
|
|
193
|
+
}, [config, say]);
|
|
192
194
|
const askOrchestrator = useCallback(async (text) => {
|
|
193
195
|
setBusy(true);
|
|
194
196
|
const id = nextId();
|
|
@@ -298,6 +300,35 @@ export function App({ initial }) {
|
|
|
298
300
|
}
|
|
299
301
|
}
|
|
300
302
|
return;
|
|
303
|
+
case "workspace-command":
|
|
304
|
+
setBusy(true);
|
|
305
|
+
try {
|
|
306
|
+
if (action.command === "new") {
|
|
307
|
+
let created;
|
|
308
|
+
await suspendTerminal(async () => { created = await workspaceNew(action.args); });
|
|
309
|
+
if (!created)
|
|
310
|
+
throw new Error("Workspace wizard did not finish.");
|
|
311
|
+
await changeWorkspace(created.slug, loadConfig());
|
|
312
|
+
say("system", `Created ${created.slug}. Configure the box with: ${created.initCommand}`);
|
|
313
|
+
}
|
|
314
|
+
else if (action.command === "set") {
|
|
315
|
+
const updated = await workspaceSet(action.args);
|
|
316
|
+
say("system", `Updated ${updated.slug}: ${updated.name} (${updated.repo}).`);
|
|
317
|
+
await refresh();
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
const rotated = await workspaceRotateKey();
|
|
321
|
+
setConfig(loadConfig());
|
|
322
|
+
say("system", `API key: ${rotated.apiKey}\nSaved locally. Update Mel's hd init configuration on the box.`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
catch (error) {
|
|
326
|
+
setNotice(error instanceof Error ? error.message : String(error));
|
|
327
|
+
}
|
|
328
|
+
finally {
|
|
329
|
+
setBusy(false);
|
|
330
|
+
}
|
|
331
|
+
return;
|
|
301
332
|
case "ticket":
|
|
302
333
|
setTicketKey(action.key);
|
|
303
334
|
setView("ticket");
|
package/dist/tui/Help.js
CHANGED
|
@@ -16,7 +16,7 @@ export const COMMANDS = [
|
|
|
16
16
|
{ name: "/decide", args: "2 | text", help: "answer the decision on screen" },
|
|
17
17
|
{ name: "/agents", args: "[add ... | rm ROLE|ID]", help: "view or manage agents" },
|
|
18
18
|
{ name: "/settings", help: "change provider caps and agent settings" },
|
|
19
|
-
{ name: "/workspace", args: "[slug]", help: "switch
|
|
19
|
+
{ name: "/workspace", args: "[slug | new | set | rotate-key]", help: "list, switch, create, or configure" },
|
|
20
20
|
{ name: "/feed", help: "what just happened" },
|
|
21
21
|
{ name: "/orchestrator", help: "talk to the agent that gets work in flight finished" },
|
|
22
22
|
{ name: "/refresh", help: "reload the board now" },
|
package/dist/tui/parse.js
CHANGED
|
@@ -37,6 +37,10 @@ export function parseLine(raw) {
|
|
|
37
37
|
return { kind: "help" };
|
|
38
38
|
case "workspace":
|
|
39
39
|
case "ws": {
|
|
40
|
+
const command = rest[0]?.toLowerCase();
|
|
41
|
+
if (command && ["new", "set", "rotate-key"].includes(command)) {
|
|
42
|
+
return { kind: "workspace-command", command: command, args: rest.slice(1) };
|
|
43
|
+
}
|
|
40
44
|
return { kind: "workspace", slug: argument || null };
|
|
41
45
|
}
|
|
42
46
|
case "ticket":
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { createInterface } from "node:readline/promises";
|
|
2
|
+
import { createWorkspace, rotateWorkspaceApiKey, updateWorkspace } from "./api.js";
|
|
3
|
+
import { loadConfig, writeHdConfig } from "./config.js";
|
|
4
|
+
import { defaultGh, HDX_RUNNER_GH_USER, preflightWorkspace } from "./workspace-preflight.js";
|
|
5
|
+
export const WORKSPACE_USAGE = "usage: hd workspace ls | new --name NAME --repo OWNER/NAME [--create|--no-create] [flags] | set [flags] | rotate-key";
|
|
6
|
+
function flags(argv) {
|
|
7
|
+
const opts = {};
|
|
8
|
+
const bools = new Set();
|
|
9
|
+
const rest = [];
|
|
10
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
11
|
+
const arg = argv[i];
|
|
12
|
+
if (!arg.startsWith("--")) {
|
|
13
|
+
rest.push(arg);
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
const next = argv[i + 1];
|
|
17
|
+
if (next && !next.startsWith("-")) {
|
|
18
|
+
opts[arg.slice(2)] = next;
|
|
19
|
+
i += 1;
|
|
20
|
+
}
|
|
21
|
+
else
|
|
22
|
+
bools.add(arg.slice(2));
|
|
23
|
+
}
|
|
24
|
+
return { opts, bools, rest };
|
|
25
|
+
}
|
|
26
|
+
export function workspaceSlug(name) {
|
|
27
|
+
return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "workspace";
|
|
28
|
+
}
|
|
29
|
+
function yes(answer) {
|
|
30
|
+
return !["n", "no"].includes(answer.trim().toLowerCase());
|
|
31
|
+
}
|
|
32
|
+
function message(error) {
|
|
33
|
+
return error instanceof Error ? error.message : String(error);
|
|
34
|
+
}
|
|
35
|
+
async function repoState(repo, gh) {
|
|
36
|
+
try {
|
|
37
|
+
const value = JSON.parse(await gh(["repo", "view", repo, "--json", "defaultBranchRef"]));
|
|
38
|
+
return { found: true, branch: value.defaultBranchRef?.name };
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
if (/404|not found|could not resolve/i.test(message(error)))
|
|
42
|
+
return { found: false };
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function lazyPrompt(deps) {
|
|
47
|
+
let readline;
|
|
48
|
+
const ask = deps.prompt ?? (async (question) => {
|
|
49
|
+
readline ??= createInterface({ input: process.stdin, output: process.stdout });
|
|
50
|
+
return readline.question(question);
|
|
51
|
+
});
|
|
52
|
+
return { ask, close: () => readline?.close() };
|
|
53
|
+
}
|
|
54
|
+
async function askDefault(ask, label, fallback = "") {
|
|
55
|
+
const answer = (await ask(`${label}${fallback ? ` [${fallback}]` : ""}: `)).trim();
|
|
56
|
+
return answer || fallback;
|
|
57
|
+
}
|
|
58
|
+
export async function workspaceNew(argv, deps = {}) {
|
|
59
|
+
const { opts, bools, rest } = flags(argv);
|
|
60
|
+
if (rest.length || (bools.has("create") && bools.has("no-create")))
|
|
61
|
+
throw new Error(WORKSPACE_USAGE);
|
|
62
|
+
const interactive = deps.isTTY ?? Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
63
|
+
const guided = !opts.name || !opts.repo;
|
|
64
|
+
if (guided && !interactive)
|
|
65
|
+
throw new Error(WORKSPACE_USAGE);
|
|
66
|
+
const gh = deps.gh ?? defaultGh;
|
|
67
|
+
const prompt = lazyPrompt(deps);
|
|
68
|
+
try {
|
|
69
|
+
let name = opts.name ?? "";
|
|
70
|
+
let repo = opts.repo ?? "";
|
|
71
|
+
let branch = opts.branch;
|
|
72
|
+
let host = opts.host ?? "box";
|
|
73
|
+
if (guided || interactive || bools.has("create") || bools.has("no-create")) {
|
|
74
|
+
try {
|
|
75
|
+
await gh(["--version"]);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
throw new Error("GitHub CLI is unavailable. Install it if needed, then run `gh auth login`.");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (guided) {
|
|
82
|
+
name = await askDefault(prompt.ask, "Name", name);
|
|
83
|
+
if (!name)
|
|
84
|
+
throw new Error("Name is required.");
|
|
85
|
+
let defaultRepo = repo;
|
|
86
|
+
if (!defaultRepo) {
|
|
87
|
+
const owner = (await gh(["api", "user", "--jq", ".login"])).trim();
|
|
88
|
+
defaultRepo = `${owner}/${workspaceSlug(name)}`;
|
|
89
|
+
}
|
|
90
|
+
repo = await askDefault(prompt.ask, "Repo", defaultRepo);
|
|
91
|
+
if (!repo)
|
|
92
|
+
throw new Error("Repo is required.");
|
|
93
|
+
}
|
|
94
|
+
let state;
|
|
95
|
+
if (guided || interactive || bools.has("create") || bools.has("no-create"))
|
|
96
|
+
state = await repoState(repo, gh);
|
|
97
|
+
if (state && !state.found) {
|
|
98
|
+
if (bools.has("no-create"))
|
|
99
|
+
throw new Error(`GitHub repository ${repo} does not exist and --no-create was set.`);
|
|
100
|
+
const create = bools.has("create") || (interactive && yes(await prompt.ask(`Create ${repo} as a private GitHub repo? [Y/n] `)));
|
|
101
|
+
if (!create)
|
|
102
|
+
throw new Error(`GitHub repository ${repo} was not created.`);
|
|
103
|
+
await gh(["repo", "create", repo, "--private"]);
|
|
104
|
+
state = { found: true, branch: "main" };
|
|
105
|
+
}
|
|
106
|
+
if (guided) {
|
|
107
|
+
branch = await askDefault(prompt.ask, "Branch", branch ?? state?.branch ?? "main");
|
|
108
|
+
host = await askDefault(prompt.ask, "Host", host);
|
|
109
|
+
const summary = `${name} | ${repo} | ${branch} | host ${host}`;
|
|
110
|
+
if (!yes(await prompt.ask(`Create ${summary}. Proceed? [Y/n] `)))
|
|
111
|
+
throw new Error("Workspace creation cancelled.");
|
|
112
|
+
}
|
|
113
|
+
const preflight = await (deps.preflightWorkspace ?? preflightWorkspace)({
|
|
114
|
+
name, repo, branch, noBootstrap: bools.has("no-bootstrap"),
|
|
115
|
+
runnerUser: opts["runner-user"] ?? HDX_RUNNER_GH_USER,
|
|
116
|
+
}, gh);
|
|
117
|
+
const result = await createWorkspace({ name, repo, slug: opts.slug,
|
|
118
|
+
default_branch: preflight.branch, default_host: host });
|
|
119
|
+
writeHdConfig({ url: result.url, api_key: result.api_key, slug: result.workspace.slug });
|
|
120
|
+
return { slug: result.workspace.slug, repo: result.workspace.repo,
|
|
121
|
+
invitationPending: preflight.invitationPending,
|
|
122
|
+
runnerUser: opts["runner-user"] ?? HDX_RUNNER_GH_USER,
|
|
123
|
+
initCommand: `hd init --url ${result.url} --api-key ${result.api_key} --slug ${result.workspace.slug}` };
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
prompt.close();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
export async function workspaceSet(argv) {
|
|
130
|
+
const { opts, bools, rest } = flags(argv);
|
|
131
|
+
if (rest.length || bools.size)
|
|
132
|
+
throw new Error(WORKSPACE_USAGE);
|
|
133
|
+
const fields = {};
|
|
134
|
+
for (const [flag, field] of [["name", "name"], ["repo", "repo"], ["branch", "default_branch"], ["host", "default_host"]]) {
|
|
135
|
+
if (opts[flag])
|
|
136
|
+
fields[field] = opts[flag];
|
|
137
|
+
}
|
|
138
|
+
if (opts["auto-merge"]) {
|
|
139
|
+
if (!["on", "off"].includes(opts["auto-merge"]))
|
|
140
|
+
throw new Error("--auto-merge must be on or off");
|
|
141
|
+
fields.auto_merge = opts["auto-merge"] === "on";
|
|
142
|
+
}
|
|
143
|
+
if (opts["max-turns"]) {
|
|
144
|
+
const turns = {};
|
|
145
|
+
for (const entry of opts["max-turns"].split(",")) {
|
|
146
|
+
const [kind, raw, ...extra] = entry.split("=");
|
|
147
|
+
const value = Number(raw);
|
|
148
|
+
if (extra.length || !["build", "followup", "review", "orchestrate"].includes(kind)
|
|
149
|
+
|| !Number.isInteger(value) || value < 1)
|
|
150
|
+
throw new Error("--max-turns must be KIND=N[,KIND=N...]");
|
|
151
|
+
turns[kind] = value;
|
|
152
|
+
}
|
|
153
|
+
fields.max_turns = turns;
|
|
154
|
+
}
|
|
155
|
+
if (opts["max-attempts"]) {
|
|
156
|
+
const value = Number(opts["max-attempts"]);
|
|
157
|
+
if (!Number.isInteger(value) || value < 1)
|
|
158
|
+
throw new Error("--max-attempts must be an integer >= 1");
|
|
159
|
+
fields.max_attempts = value;
|
|
160
|
+
}
|
|
161
|
+
if (!Object.keys(fields).length)
|
|
162
|
+
throw new Error(WORKSPACE_USAGE);
|
|
163
|
+
return (await updateWorkspace(fields)).workspace;
|
|
164
|
+
}
|
|
165
|
+
export async function workspaceRotateKey() {
|
|
166
|
+
const config = loadConfig();
|
|
167
|
+
const { api_key } = await rotateWorkspaceApiKey(config);
|
|
168
|
+
writeHdConfig({ ...config, api_key });
|
|
169
|
+
return { apiKey: api_key, slug: config.slug };
|
|
170
|
+
}
|
|
@@ -19,7 +19,7 @@ function errorMessage(error) {
|
|
|
19
19
|
const value = error;
|
|
20
20
|
return value.stderr?.trim() || value.message || String(error);
|
|
21
21
|
}
|
|
22
|
-
async function defaultGh(args) {
|
|
22
|
+
export async function defaultGh(args) {
|
|
23
23
|
try {
|
|
24
24
|
return (await exec("gh", args, { encoding: "utf8", maxBuffer: 1024 * 1024 })).stdout;
|
|
25
25
|
}
|