@higherdev/cli 0.11.0 → 0.11.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 CHANGED
@@ -14,7 +14,8 @@ Create `~/.config/hd/config.json` with an existing workspace API key:
14
14
  {
15
15
  "url": "https://hdx-higher-ops.vercel.app",
16
16
  "api_key": "hdx_...",
17
- "current": "workspace"
17
+ "current": "workspace",
18
+ "repos": {}
18
19
  }
19
20
  ```
20
21
 
@@ -35,7 +36,7 @@ workspace-map config shapes are migrated automatically when they are read.
35
36
  | `hd ticket cancel KEY` | Cancel a ticket |
36
37
  | `hd epic new PATH [--title TITLE]` | Create an epic from a Markdown spec |
37
38
  | `hd epic list` | List epics and ticket progress |
38
- | `hd plan [--repo DIR]` | Hand the terminal to Codex to author an epic spec |
39
+ | `hd plan [--repo DIR] [--clone-root DIR]` | Hand the terminal to Codex to author an epic spec |
39
40
  | `hd workspace ls` | List every workspace available to the configured key |
40
41
  | `hd workspace new --name NAME --repo OWNER/NAME [options]` | Preflight GitHub, wire the runner, and create a paused workspace |
41
42
  | `hd workspace set [options]` | Update settings; max turns uses `--max-turns KIND=N[,KIND=N...]` |
@@ -60,8 +61,12 @@ branch, bootstraps an empty repository unless `--no-bootstrap` is set, and invit
60
61
  the repository is missing, approve private creation interactively, use `--create` to force it, or
61
62
  `--no-create` to fail. Fully flagged calls remain non-interactive for scripts and Mel.
62
63
 
64
+ `hd plan` remembers a matching checkout per workspace. If none exists, it offers to clone into
65
+ `~/HigherDEV/<slug>`; change the parent with `--clone-root DIR`. An explicit `--repo DIR` is saved
66
+ after its Git origin is verified.
67
+
63
68
  Inside the TUI, use `/board`, `/inbox`, `/ticket`, `/queue`, `/cancel`, `/epic new`,
64
- `/epics`, `/plan`, `/decide`, `/agents add`, `/agents rm`, `/settings`, `/workspace`,
69
+ `/epics`, `/plan [--repo DIR]`, `/decide`, `/agents add`, `/agents rm`, `/settings`, `/workspace`,
65
70
  `/workspace new`, `/workspace set`, `/workspace rotate-key`, `/feed`,
66
71
  `/orchestrator`, `/refresh`, `/help`, or `/exit`. The display refreshes from
67
72
  the HDX API every five seconds.
package/dist/config.js CHANGED
@@ -16,6 +16,11 @@ function normalizeConnection(value) {
16
16
  function missing(path) {
17
17
  return new Error(`missing ${path}\nWrite { "url": "https://hdx-higher-ops.vercel.app", "api_key": "hdx_...", "current": "workspace" }`);
18
18
  }
19
+ function normalizeRepos(value) {
20
+ if (!value || typeof value !== "object" || Array.isArray(value))
21
+ return {};
22
+ return Object.fromEntries(Object.entries(value).filter((entry) => Boolean(entry[0]) && typeof entry[1] === "string" && Boolean(entry[1])));
23
+ }
19
24
  export function loadStoredConfig(path = configPath()) {
20
25
  let raw;
21
26
  try {
@@ -27,13 +32,14 @@ export function loadStoredConfig(path = configPath()) {
27
32
  const parsed = JSON.parse(raw);
28
33
  const connection = normalizeConnection(parsed);
29
34
  const current = typeof parsed.current === "string" ? parsed.current : "";
35
+ const repos = normalizeRepos(parsed.repos);
30
36
  if (connection && current)
31
- return { ...connection, current };
37
+ return { ...connection, current, repos };
32
38
  // 0.4 stored one workspace at the top level with `slug` rather than
33
39
  // `current`. One key can now reach every workspace, so only the selected
34
40
  // slug needs to survive.
35
41
  if (connection && typeof parsed.slug === "string" && parsed.slug) {
36
- const migrated = { ...connection, current: parsed.slug };
42
+ const migrated = { ...connection, current: parsed.slug, repos };
37
43
  writeStoredConfig(migrated, path);
38
44
  return migrated;
39
45
  }
@@ -46,7 +52,7 @@ export function loadStoredConfig(path = configPath()) {
46
52
  const selected = normalizeConnection(source[current]);
47
53
  if (!selected)
48
54
  throw new Error(`${path} current workspace ${current} is not configured`);
49
- const migrated = { ...selected, current };
55
+ const migrated = { ...selected, current, repos };
50
56
  writeStoredConfig(migrated, path);
51
57
  return migrated;
52
58
  }
@@ -62,10 +68,16 @@ export function writeStoredConfig(config, path = configPath()) {
62
68
  }
63
69
  /** Store one operator connection and make its selected workspace current. */
64
70
  export function writeHdConfig(config, path = configPath()) {
71
+ let repos = {};
72
+ try {
73
+ repos = loadStoredConfig(path).repos;
74
+ }
75
+ catch { }
65
76
  writeStoredConfig({
66
77
  url: config.url.replace(/\/$/, ""),
67
78
  api_key: config.api_key,
68
79
  current: config.slug,
80
+ repos,
69
81
  }, path);
70
82
  }
71
83
  export function switchWorkspace(slug, path = configPath()) {
@@ -73,3 +85,7 @@ export function switchWorkspace(slug, path = configPath()) {
73
85
  writeStoredConfig({ ...stored, current: slug }, path);
74
86
  return { url: stored.url, api_key: stored.api_key, slug };
75
87
  }
88
+ export function rememberWorkspaceRepo(slug, directory, path = configPath()) {
89
+ const stored = loadStoredConfig(path);
90
+ writeStoredConfig({ ...stored, repos: { ...stored.repos, [slug]: directory } }, path);
91
+ }
package/dist/index.js CHANGED
@@ -157,11 +157,14 @@ async function cmdEpic(argv) {
157
157
  }
158
158
  async function cmdPlan(argv) {
159
159
  const { rest, opts, bools } = flags(argv);
160
- if (rest.length || bools.size || Object.keys(opts).some((key) => key !== "repo")) {
161
- fail("usage: hd plan [--repo DIR]");
160
+ if (rest.length || bools.size || Object.keys(opts).some((key) => !["repo", "clone-root"].includes(key))) {
161
+ fail("usage: hd plan [--repo DIR] [--clone-root DIR]");
162
162
  }
163
163
  const { workspace } = await getStatus();
164
- await launchArchitect({ workspaceRepo: workspace.repo, repo: opts.repo });
164
+ const result = await launchArchitect({ workspaceRepo: workspace.repo, workspaceSlug: workspace.slug,
165
+ repo: opts.repo, cloneRoot: opts["clone-root"] });
166
+ if (result.cloneCommand)
167
+ console.log(`Clone it with: ${result.cloneCommand}`);
165
168
  }
166
169
  async function cmdLogs(argv) {
167
170
  const { rest, bools } = flags(argv);
package/dist/out.js CHANGED
@@ -66,7 +66,7 @@ export function usage() {
66
66
  ` ${c.blue("hd status")} workspace overview`,
67
67
  ` ${c.blue("hd ticket list | show | new | queue | cancel")} ticket operations`,
68
68
  ` ${c.blue("hd epic new PATH | list")} epic operations`,
69
- ` ${c.blue("hd plan [--repo DIR]")} author an epic with Codex`,
69
+ ` ${c.blue("hd plan [--repo DIR] [--clone-root DIR]")} author an epic with Codex`,
70
70
  ` ${c.blue("hd workspace ls | new | set | rotate-key")} workspace operations`,
71
71
  ` ${c.blue("hd agents [add | rm | set]")} manage agents`,
72
72
  ` ${c.blue("hd caps [set PROVIDER N]")} provider concurrency`,
package/dist/plan.js CHANGED
@@ -1,8 +1,11 @@
1
1
  import { spawn, spawnSync } from "node:child_process";
2
+ import { mkdirSync } from "node:fs";
2
3
  import { readFile } from "node:fs/promises";
3
4
  import { homedir } from "node:os";
4
5
  import { isAbsolute, join, resolve } from "node:path";
5
- import { createInterface } from "node:readline/promises";
6
+ import { loadStoredConfig, rememberWorkspaceRepo } from "./config.js";
7
+ import { promptOnStdin } from "./prompt.js";
8
+ import { defaultGh } from "./workspace-preflight.js";
6
9
  const ARCHITECT_PROMPT = new URL("../prompts/architect.md", import.meta.url);
7
10
  function gitOutput(cwd, args) {
8
11
  const result = spawnSync("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
@@ -21,13 +24,13 @@ export function remoteRepo(remote) {
21
24
  return trimmed.includes("/") ? trimmed : null;
22
25
  }
23
26
  }
24
- function expanded(path) {
27
+ function expanded(path, home = homedir(), cwd = process.cwd()) {
25
28
  const value = path.trim();
26
29
  if (value === "~")
27
- return homedir();
30
+ return home;
28
31
  if (value.startsWith("~/"))
29
- return join(homedir(), value.slice(2));
30
- return isAbsolute(value) ? value : resolve(value);
32
+ return join(home, value.slice(2));
33
+ return isAbsolute(value) ? value : resolve(cwd, value);
31
34
  }
32
35
  export function matchingWorkspaceRepo(directory, workspaceRepo) {
33
36
  const root = gitOutput(expanded(directory), ["rev-parse", "--show-toplevel"]);
@@ -36,36 +39,6 @@ export function matchingWorkspaceRepo(directory, workspaceRepo) {
36
39
  const origin = gitOutput(root, ["remote", "get-url", "origin"]);
37
40
  return origin && remoteRepo(origin)?.toLowerCase() === workspaceRepo.toLowerCase() ? root : null;
38
41
  }
39
- async function askForRepo(workspaceRepo) {
40
- if (!process.stdin.isTTY || !process.stdout.isTTY) {
41
- throw new Error(`Current directory is not ${workspaceRepo}. Pass --repo DIR.`);
42
- }
43
- const prompt = createInterface({ input: process.stdin, output: process.stdout });
44
- try {
45
- return (await prompt.question(`Repository directory for ${workspaceRepo}: `)).trim();
46
- }
47
- finally {
48
- prompt.close();
49
- }
50
- }
51
- export async function resolveWorkspaceRepo(workspaceRepo, requested) {
52
- if (requested) {
53
- const match = matchingWorkspaceRepo(requested, workspaceRepo);
54
- if (!match)
55
- throw new Error(`${requested} is not a checkout of ${workspaceRepo}.`);
56
- return match;
57
- }
58
- const current = matchingWorkspaceRepo(process.cwd(), workspaceRepo);
59
- if (current)
60
- return current;
61
- const answer = await askForRepo(workspaceRepo);
62
- if (!answer)
63
- throw new Error("A repository directory is required.");
64
- const match = matchingWorkspaceRepo(answer, workspaceRepo);
65
- if (!match)
66
- throw new Error(`${answer} is not a checkout of ${workspaceRepo}.`);
67
- return match;
68
- }
69
42
  export function verifyCodex(checkVersion = () => spawnSync("codex", ["--version"], { stdio: "ignore" })) {
70
43
  const check = checkVersion();
71
44
  if (check.error || check.status !== 0) {
@@ -84,11 +57,62 @@ function runCodex(cwd, prompt) {
84
57
  });
85
58
  });
86
59
  }
60
+ function shellPath(path) {
61
+ return /^[A-Za-z0-9_./~-]+$/.test(path) ? path : `'${path.replaceAll("'", "'\\''")}'`;
62
+ }
63
+ export async function resolveWorkspaceRepo(options, runtime = {}) {
64
+ const matchRepo = runtime.matchRepo ?? matchingWorkspaceRepo;
65
+ const cwd = runtime.cwd?.() ?? process.cwd();
66
+ const home = runtime.home?.() ?? homedir();
67
+ const saveRepo = runtime.saveRepo ?? rememberWorkspaceRepo;
68
+ const accept = (directory) => {
69
+ saveRepo(options.workspaceSlug, directory);
70
+ return { directory };
71
+ };
72
+ if (options.repo) {
73
+ const match = matchRepo(options.repo, options.workspaceRepo);
74
+ if (!match)
75
+ throw new Error(`${options.repo} is not a checkout of ${options.workspaceRepo}.`);
76
+ return accept(match);
77
+ }
78
+ const saved = (runtime.savedRepo ?? ((slug) => loadStoredConfig().repos[slug]))(options.workspaceSlug);
79
+ if (saved) {
80
+ const match = matchRepo(saved, options.workspaceRepo);
81
+ if (match)
82
+ return { directory: match };
83
+ }
84
+ const current = matchRepo(cwd, options.workspaceRepo);
85
+ if (current)
86
+ return accept(current);
87
+ const defaultRoot = "~/HigherDEV";
88
+ const rootInput = options.cloneRoot ?? defaultRoot;
89
+ const root = expanded(rootInput, home, cwd);
90
+ const destination = join(root, options.workspaceSlug);
91
+ const shownDestination = join(rootInput, options.workspaceSlug);
92
+ const cloneCommand = `gh repo clone ${options.workspaceRepo} ${shellPath(shownDestination)}`;
93
+ const interactive = runtime.isTTY ?? Boolean(process.stdin.isTTY && process.stdout.isTTY);
94
+ if (!interactive || ["n", "no"].includes((await (runtime.prompt ?? promptOnStdin)(`Clone ${options.workspaceRepo} to ${shownDestination}? [Y/n] `)).trim().toLowerCase()))
95
+ return { cloneCommand };
96
+ const gh = runtime.gh ?? defaultGh;
97
+ try {
98
+ await gh(["--version"]);
99
+ }
100
+ catch {
101
+ throw new Error("GitHub CLI is unavailable. Install it if needed, then run `gh auth login`.");
102
+ }
103
+ (runtime.mkdir ?? ((path) => mkdirSync(path, { recursive: true })))(root);
104
+ await gh(["repo", "clone", options.workspaceRepo, destination]);
105
+ const cloned = matchRepo(destination, options.workspaceRepo);
106
+ if (!cloned)
107
+ throw new Error(`Clone completed but ${destination} is not a checkout of ${options.workspaceRepo}.`);
108
+ return accept(cloned);
109
+ }
87
110
  export async function launchArchitect(options, runtime = {}) {
111
+ const resolved = await resolveWorkspaceRepo(options, runtime);
112
+ if ("cloneCommand" in resolved)
113
+ return { launched: false, cloneCommand: resolved.cloneCommand };
88
114
  (runtime.verifyCodex ?? verifyCodex)();
89
- const [cwd, prompt] = await Promise.all([
90
- resolveWorkspaceRepo(options.workspaceRepo, options.repo),
91
- readFile(ARCHITECT_PROMPT, "utf8"),
92
- ]);
93
- await (runtime.invokeCodex ?? runCodex)(cwd, prompt);
115
+ const prompt = await readFile(ARCHITECT_PROMPT, "utf8");
116
+ await (runtime.invokeCodex ?? runCodex)(resolved.directory, prompt);
117
+ return { launched: true };
94
118
  }
package/dist/prompt.js ADDED
@@ -0,0 +1,13 @@
1
+ import { createInterface } from "node:readline/promises";
2
+ export async function promptOnStdin(question, resumeStdin = false) {
3
+ const prompt = createInterface({ input: process.stdin, output: process.stdout });
4
+ try {
5
+ return await prompt.question(question);
6
+ }
7
+ finally {
8
+ prompt.close();
9
+ // Ink restores raw mode and listeners, but it cannot read a paused stream.
10
+ if (resumeStdin)
11
+ process.stdin.resume();
12
+ }
13
+ }
package/dist/tui/App.js CHANGED
@@ -4,6 +4,7 @@ import { Box, Static, Text, useApp, useInput, useStdout } from "ink";
4
4
  import { loadConfig } from "../config.js";
5
5
  import { epicProgressRows } from "../epics.js";
6
6
  import { launchArchitect } from "../plan.js";
7
+ import { promptOnStdin } from "../prompt.js";
7
8
  import { workspaceNew, workspaceRotateKey, workspaceSet } from "../workspace-commands.js";
8
9
  import { Banner } from "./Banner.js";
9
10
  import { Bubble } from "./Bubble.js";
@@ -25,6 +26,7 @@ import { UI } from "./theme.js";
25
26
  import { WorkspaceLoads } from "./workspace-load.js";
26
27
  let messageSeq = 0;
27
28
  const nextId = () => `m${messageSeq++}`;
29
+ const tuiPrompt = (question) => promptOnStdin(question, true);
28
30
  export function App({ initial }) {
29
31
  const { exit, suspendTerminal } = useApp();
30
32
  const { stdout } = useStdout();
@@ -305,7 +307,7 @@ export function App({ initial }) {
305
307
  try {
306
308
  if (action.command === "new") {
307
309
  let created;
308
- await suspendTerminal(async () => { created = await workspaceNew(action.args); });
310
+ await suspendTerminal(async () => { created = await workspaceNew(action.args, { prompt: tuiPrompt }); });
309
311
  if (!created)
310
312
  throw new Error("Workspace wizard did not finish.");
311
313
  await changeWorkspace(created.slug, loadConfig());
@@ -434,9 +436,21 @@ export function App({ initial }) {
434
436
  case "plan":
435
437
  setBusy(true);
436
438
  try {
437
- await suspendTerminal(() => launchArchitect({ workspaceRepo: workspace.repo }));
438
- say("system", "Architect session ended. Review the spec, then run /epic new PATH.");
439
- await refresh();
439
+ let result;
440
+ await suspendTerminal(async () => {
441
+ result = await launchArchitect({
442
+ workspaceRepo: workspace.repo, workspaceSlug: workspace.slug,
443
+ repo: action.repo, cloneRoot: action.cloneRoot,
444
+ }, { prompt: tuiPrompt });
445
+ });
446
+ if (!result)
447
+ throw new Error("Architect did not finish.");
448
+ if (result.cloneCommand)
449
+ say("system", `Clone it with: ${result.cloneCommand}`);
450
+ else {
451
+ say("system", "Architect session ended. Review the spec, then run /epic new PATH.");
452
+ await refresh();
453
+ }
440
454
  }
441
455
  catch (error) {
442
456
  setNotice(error instanceof Error ? error.message : String(error));
package/dist/tui/Help.js CHANGED
@@ -12,7 +12,7 @@ export const COMMANDS = [
12
12
  { name: "/cancel", args: "HD-12", help: "cancel a ticket" },
13
13
  { name: "/epic", args: "new PATH", help: "create an epic from a Markdown spec" },
14
14
  { name: "/epics", help: "list epics and ticket progress" },
15
- { name: "/plan", help: "author an epic with your Codex CLI" },
15
+ { name: "/plan", args: "[--repo DIR]", help: "author an epic with your Codex CLI" },
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" },
package/dist/tui/parse.js CHANGED
@@ -60,8 +60,20 @@ export function parseLine(raw) {
60
60
  case "cancel":
61
61
  return argument ? { kind: "cancel", key: argument.toUpperCase() }
62
62
  : { kind: "unknown", command: "cancel needs a key" };
63
- case "plan":
64
- return argument ? { kind: "unknown", command: "plan takes no arguments" } : { kind: "plan" };
63
+ case "plan": {
64
+ const opts = {};
65
+ for (let i = 0; i < rest.length; i += 2) {
66
+ if (!rest[i]?.startsWith("--") || !rest[i + 1]) {
67
+ return { kind: "unknown", command: "plan accepts --repo DIR and --clone-root DIR" };
68
+ }
69
+ opts[rest[i].slice(2)] = rest[i + 1];
70
+ }
71
+ if (Object.keys(opts).some((key) => !["repo", "clone-root"].includes(key))) {
72
+ return { kind: "unknown", command: "plan accepts --repo DIR and --clone-root DIR" };
73
+ }
74
+ return { kind: "plan", ...(opts.repo ? { repo: opts.repo } : {}),
75
+ ...(opts["clone-root"] ? { cloneRoot: opts["clone-root"] } : {}) };
76
+ }
65
77
  case "decide": {
66
78
  const dismiss = /(^|\s)--(skip|dismiss)(\s|$)/.test(argument);
67
79
  return {
@@ -1,6 +1,6 @@
1
- import { createInterface } from "node:readline/promises";
2
1
  import { createWorkspace, rotateWorkspaceApiKey, updateWorkspace } from "./api.js";
3
2
  import { loadConfig, writeHdConfig } from "./config.js";
3
+ import { promptOnStdin } from "./prompt.js";
4
4
  import { defaultGh, HDX_RUNNER_GH_USER, preflightWorkspace } from "./workspace-preflight.js";
5
5
  export const WORKSPACE_USAGE = "usage: hd workspace ls | new --name NAME --repo OWNER/NAME [--create|--no-create] [flags] | set [flags] | rotate-key";
6
6
  function flags(argv) {
@@ -44,12 +44,7 @@ async function repoState(repo, gh) {
44
44
  }
45
45
  }
46
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() };
47
+ return deps.prompt ?? promptOnStdin;
53
48
  }
54
49
  async function askDefault(ask, label, fallback = "") {
55
50
  const answer = (await ask(`${label}${fallback ? ` [${fallback}]` : ""}: `)).trim();
@@ -65,7 +60,7 @@ export async function workspaceNew(argv, deps = {}) {
65
60
  throw new Error(WORKSPACE_USAGE);
66
61
  const gh = deps.gh ?? defaultGh;
67
62
  const prompt = lazyPrompt(deps);
68
- try {
63
+ {
69
64
  let name = opts.name ?? "";
70
65
  let repo = opts.repo ?? "";
71
66
  let branch = opts.branch;
@@ -79,7 +74,7 @@ export async function workspaceNew(argv, deps = {}) {
79
74
  }
80
75
  }
81
76
  if (guided) {
82
- name = await askDefault(prompt.ask, "Name", name);
77
+ name = await askDefault(prompt, "Name", name);
83
78
  if (!name)
84
79
  throw new Error("Name is required.");
85
80
  let defaultRepo = repo;
@@ -87,7 +82,7 @@ export async function workspaceNew(argv, deps = {}) {
87
82
  const owner = (await gh(["api", "user", "--jq", ".login"])).trim();
88
83
  defaultRepo = `${owner}/${workspaceSlug(name)}`;
89
84
  }
90
- repo = await askDefault(prompt.ask, "Repo", defaultRepo);
85
+ repo = await askDefault(prompt, "Repo", defaultRepo);
91
86
  if (!repo)
92
87
  throw new Error("Repo is required.");
93
88
  }
@@ -97,17 +92,17 @@ export async function workspaceNew(argv, deps = {}) {
97
92
  if (state && !state.found) {
98
93
  if (bools.has("no-create"))
99
94
  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] `)));
95
+ const create = bools.has("create") || (interactive && yes(await prompt(`Create ${repo} as a private GitHub repo? [Y/n] `)));
101
96
  if (!create)
102
97
  throw new Error(`GitHub repository ${repo} was not created.`);
103
98
  await gh(["repo", "create", repo, "--private"]);
104
99
  state = { found: true, branch: "main" };
105
100
  }
106
101
  if (guided) {
107
- branch = await askDefault(prompt.ask, "Branch", branch ?? state?.branch ?? "main");
108
- host = await askDefault(prompt.ask, "Host", host);
102
+ branch = await askDefault(prompt, "Branch", branch ?? state?.branch ?? "main");
103
+ host = await askDefault(prompt, "Host", host);
109
104
  const summary = `${name} | ${repo} | ${branch} | host ${host}`;
110
- if (!yes(await prompt.ask(`Create ${summary}. Proceed? [Y/n] `)))
105
+ if (!yes(await prompt(`Create ${summary}. Proceed? [Y/n] `)))
111
106
  throw new Error("Workspace creation cancelled.");
112
107
  }
113
108
  const preflight = await (deps.preflightWorkspace ?? preflightWorkspace)({
@@ -122,9 +117,6 @@ export async function workspaceNew(argv, deps = {}) {
122
117
  runnerUser: opts["runner-user"] ?? HDX_RUNNER_GH_USER,
123
118
  initCommand: `hd init --url ${result.url} --api-key ${result.api_key} --slug ${result.workspace.slug}` };
124
119
  }
125
- finally {
126
- prompt.close();
127
- }
128
120
  }
129
121
  export async function workspaceSet(argv) {
130
122
  const { opts, bools, rest } = flags(argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@higherdev/cli",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "hd": "dist/index.js"
@@ -13,6 +13,7 @@
13
13
  "scripts": {
14
14
  "build": "tsc",
15
15
  "test": "pnpm build && node --experimental-strip-types --test test/*.test.ts",
16
+ "test:pty": "pnpm build && expect test/tui-prompt.expect",
16
17
  "prepublishOnly": "npm run build"
17
18
  },
18
19
  "dependencies": {