@krapht/ork 1.0.0-alpha.2 → 1.0.0-alpha.20

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.
Files changed (71) hide show
  1. package/README.md +12 -7
  2. package/dist/__build_info__-D6mXC4cD.mjs +228 -0
  3. package/dist/apply-Mb0fNpCX.mjs +17 -0
  4. package/dist/argument-error-CihH46Le.mjs +27 -0
  5. package/dist/attach-C1OnxyMh.mjs +92 -0
  6. package/dist/attach-CEGbRg1D.mjs +1230 -0
  7. package/dist/bin.mjs +1312 -2390
  8. package/dist/brief-DH66xmj8.mjs +115 -0
  9. package/dist/{decide-CGyx9rv7.mjs → builtin-files-Cf-NZDqM.mjs} +3 -51
  10. package/dist/chips-BqcT58qr.mjs +43 -0
  11. package/dist/command-tree-CEkzWNy6.mjs +63 -0
  12. package/dist/{completion-CSU5vkEt.mjs → completion-DS0gCZRm.mjs} +200 -30
  13. package/dist/context-CGPh12HV.mjs +106 -0
  14. package/dist/detached-Dakw5RZj.mjs +38 -0
  15. package/dist/extra-arguments-BxQMXARO.mjs +13 -0
  16. package/dist/{find-D6Gj1sxg.mjs → find-BTikxc54.mjs} +2 -2
  17. package/dist/flags-C8yCq0j5.mjs +145 -0
  18. package/dist/follow-ups-CtlhHbwe.mjs +563 -0
  19. package/dist/format-BB4E-T6x.mjs +249 -0
  20. package/dist/{help-groups-BwYoAWsh.mjs → help-groups-BpRgBSTh.mjs} +42 -76
  21. package/dist/in-a-run-CbnKjO7t.mjs +27 -0
  22. package/dist/install-CWcpX5g4.mjs +211 -0
  23. package/dist/{limits-B1-DYc3d.mjs → limits-VQqsBoQk.mjs} +2 -2
  24. package/dist/{mcp-DE_EmyVm.mjs → mcp-wGfkMCVF.mjs} +6 -6
  25. package/dist/once-BHxSdZz_.mjs +112 -0
  26. package/dist/orphan-guard-BFKg8VSd.mjs +58 -0
  27. package/dist/output-B1kwo0YU.mjs +439 -0
  28. package/dist/outputs-text-CNTJ7Qq_.mjs +19 -0
  29. package/dist/{pause-CL6N_Xrj.mjs → pause-C_Wxh-KM.mjs} +7 -4
  30. package/dist/print-DNYzq4Rr.mjs +19 -0
  31. package/dist/project-records-XY3ku95x.mjs +233 -0
  32. package/dist/refusals-DQQX3AIb.mjs +116 -0
  33. package/dist/{relay-0SD0erwf.mjs → relay-BA6tsnpc.mjs} +2 -2
  34. package/dist/run-Bvvg-fiF.mjs +234 -0
  35. package/dist/run-DBUAq-Uu.mjs +1452 -0
  36. package/dist/send-nrKtak5F.mjs +105 -0
  37. package/dist/{src-Dujmvv4s.mjs → src-8cOPOgrT.mjs} +12523 -5507
  38. package/dist/{src-BbyEoMn4.mjs → src-CmEvHdju.mjs} +797 -233
  39. package/dist/src-D4JSymhK.mjs +3104 -0
  40. package/dist/{src-DOcK8Jfp.mjs → src-I_RyKu6_.mjs} +1022 -590
  41. package/dist/store-CDpk8Wtz.mjs +16 -0
  42. package/dist/ui/THIRD_PARTY_LICENSES.txt +612 -0
  43. package/dist/ui/assets/app--FijEXiJ.css +1 -0
  44. package/dist/ui/assets/app-BowhjEAe.js +112 -0
  45. package/dist/ui/assets/{index-9dFha9Qn.js → index-CrMa2oHB.js} +3 -3
  46. package/dist/ui/assets/theme-CdXvqTQ6.js +1 -0
  47. package/dist/ui/index.html +2 -2
  48. package/dist/{ui-CTmO2Xzu.mjs → ui-DY3Dnpho.mjs} +54 -25
  49. package/dist/usage-1VwSg_Qt.mjs +257 -0
  50. package/dist/validate-BxayjjkV.mjs +632 -0
  51. package/dist/verdict-CMvPCpWC.mjs +84 -0
  52. package/dist/{watch-CtbgDAcz.mjs → watch-CT0EtLPZ.mjs} +45 -17
  53. package/dist/{workflows-D1_WVukk.mjs → workflows-DHHi-APg.mjs} +30 -5
  54. package/dist/worktrees-CZvbHzbN.mjs +302 -0
  55. package/package.json +10 -8
  56. package/dist/__build_info__-fy6L1Mrd.mjs +0 -615
  57. package/dist/clean-DaHjs__2.mjs +0 -88
  58. package/dist/context-DT8MfEIR.mjs +0 -274
  59. package/dist/format-Dnfv8zMT.mjs +0 -427
  60. package/dist/in-a-run-pLAZeNys.mjs +0 -22
  61. package/dist/list-BrH8KRSJ.mjs +0 -110
  62. package/dist/output-BLQWD6MJ.mjs +0 -950
  63. package/dist/remove-BeGdqroy.mjs +0 -194
  64. package/dist/report-DK4LGqwF.mjs +0 -129
  65. package/dist/send-C8tunWhv.mjs +0 -163
  66. package/dist/src-CCUKRX51.mjs +0 -1204
  67. package/dist/ui/assets/app-BNT0SuIz.css +0 -1
  68. package/dist/ui/assets/app-T7wVAhgx.js +0 -67
  69. package/dist/ui/assets/theme-3xH5jxKs.js +0 -1
  70. package/dist/usage-DYUmP0i4.mjs +0 -419
  71. package/dist/worktrees-DdMYoNOC.mjs +0 -212
package/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # @krapht/ork
2
2
 
3
- The `ork` command: orchestrate Claude agents on your machine with hierarchical,
4
- serializable workflows.
3
+ The `ork` command: orchestrate coding agents (Claude Code, OpenCode, Command
4
+ Code) on your machine with hierarchical, serializable workflows.
5
5
 
6
- > **Alpha.** Workflows of Claude agents and tools, run from the terminal or the
6
+ > **Alpha.** Workflows of coding agents and tools, run from the terminal or the
7
7
  > browser (`ork ui`). Expect changes until 1.0.0. See the
8
8
  > [project README](https://github.com/mimshins/ork#readme), the
9
9
  > [guides](https://github.com/mimshins/ork/tree/main/docs#readme) and
@@ -26,10 +26,15 @@ ork worktrees clean # removes the merged ones; asks first
26
26
  ork upgrade # a newer version? upgrade the way you installed Ork
27
27
  ```
28
28
 
29
- A workflow that names no Claude driver runs on the one that's ready: the CLI
30
- driver uses your own logged-in `claude`; the SDK driver needs
31
- `ANTHROPIC_API_KEY` or a cloud provider. `--driver cli` or `--driver sdk` picks
32
- one.
29
+ A workflow that names no agent driver runs on the one that's ready. Today that
30
+ is `claude-code`: your own `claude`, logged in or with `ANTHROPIC_API_KEY` or a
31
+ cloud provider set. Your own `opencode` (`open-code`) and Command Code's `cmd`
32
+ (`command-code`) work too: `--driver <id>`, or a driver per node
33
+ ([Drivers](https://github.com/mimshins/ork/blob/main/docs/guides/drivers.md)).
34
+
35
+ Ork is built with Ork: its own workflows plan, build, review and document its
36
+ features, and each run is measured against a manual session in
37
+ [Benchmarks: Ork building Ork](https://github.com/mimshins/ork/blob/main/benchmarks/README.md).
33
38
 
34
39
  Ork never stores or asks for credentials: it uses the tools you already have,
35
40
  signed in with your own accounts. Once a day it asks the npm registry whether a
@@ -0,0 +1,228 @@
1
+ import { E as parseVersion, T as isNewer, w as compareVersions } from "./src-8cOPOgrT.mjs";
2
+ import path from "node:path";
3
+ import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
4
+ import { homedir } from "node:os";
5
+ // ../core/src/remote/session.ts
6
+ const chattable = (type) => type === "agent.leader" || type === "agent.worker";
7
+ /** Reads `node`'s session from a run's events, and every node's last output. */
8
+ const nodeSession = (events, node) => {
9
+ const session = {
10
+ known: false,
11
+ chattable: false,
12
+ running: false
13
+ };
14
+ const outputs = {};
15
+ for (const { event } of events) {
16
+ if (event.type === "run.started") {
17
+ const declared = event.nodes.find((n) => n.id === node);
18
+ if (declared !== void 0) {
19
+ session.known = true;
20
+ session.chattable = chattable(declared.type);
21
+ }
22
+ }
23
+ if (event.type === "node.finished") outputs[event.id] = event.output;
24
+ if (!("id" in event) || event.id !== node) continue;
25
+ if (event.type === "node.started") {
26
+ session.known = true;
27
+ session.chattable = chattable(event.nodeType);
28
+ session.running = true;
29
+ }
30
+ if (event.type === "node.finished") session.running = false;
31
+ if (event.type === "worktree" && event.action === "created") session.checkout = {
32
+ path: event.path,
33
+ ...event.branch === void 0 ? {} : { branch: event.branch },
34
+ own: true,
35
+ owner: node
36
+ };
37
+ if (event.type !== "node.event") continue;
38
+ const inner = event.event;
39
+ if (inner.fork === true) continue;
40
+ if (inner.type === "session.started") {
41
+ session.sessionId = inner.sessionId;
42
+ if (inner.cwd !== void 0) session.cwd = inner.cwd;
43
+ if (inner.driver !== void 0) session.driver = inner.driver;
44
+ if (inner.model !== "") session.model = inner.model;
45
+ }
46
+ if (inner.type === "session.finished" && inner.outcome.sessionId !== "") session.sessionId = inner.outcome.sessionId;
47
+ }
48
+ return {
49
+ session,
50
+ outputs
51
+ };
52
+ };
53
+ /** The dist-tags every install follows, toward and after 1.0.0. */
54
+ const CHANNEL_TAGS = [
55
+ "latest",
56
+ "alpha",
57
+ "beta",
58
+ "rc"
59
+ ];
60
+ /** The dist-tag of snapshots from `main`, followed only by a canary. */
61
+ const CANARY_TAG = "canary";
62
+ const updateCachePath = (home = homedir()) => path.join(home, ".ork", "update-check.json");
63
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
64
+ const text = (record, key) => {
65
+ const value = record[key];
66
+ return typeof value === "string" ? value : void 0;
67
+ };
68
+ const notifiedOf = (value) => {
69
+ if (!isRecord(value)) return void 0;
70
+ const version = text(value, "version");
71
+ const at = text(value, "at");
72
+ return version === void 0 || at === void 0 ? void 0 : {
73
+ version,
74
+ at
75
+ };
76
+ };
77
+ /** The cache, or `null` when there is none or it cannot be read. */
78
+ const readUpdateCache = (file) => {
79
+ let parsed;
80
+ try {
81
+ parsed = JSON.parse(readFileSync(file, "utf8"));
82
+ } catch {
83
+ return null;
84
+ }
85
+ if (!isRecord(parsed)) return null;
86
+ const checkedAt = text(parsed, "checkedAt");
87
+ if (checkedAt === void 0) return null;
88
+ const latest = text(parsed, "latest");
89
+ const notified = notifiedOf(parsed["notified"]);
90
+ return {
91
+ checkedAt,
92
+ ...latest === void 0 ? {} : { latest },
93
+ ...notified === void 0 ? {} : { notified }
94
+ };
95
+ };
96
+ /** Writes the cache whole (a temporary file, then a rename). */
97
+ const writeUpdateCache = (file, cache) => {
98
+ mkdirSync(path.dirname(file), { recursive: true });
99
+ const temporary = `${file}.${process.pid}.tmp`;
100
+ writeFileSync(temporary, `${JSON.stringify(cache, null, 2)}\n`);
101
+ renameSync(temporary, file);
102
+ };
103
+ const age = (iso, now) => {
104
+ const time = Date.parse(iso);
105
+ return Number.isNaN(time) ? Infinity : now.getTime() - time;
106
+ };
107
+ /** Whether it is time to ask the registry again. */
108
+ const checkDue = (cache, now) => cache === null || age(cache.checkedAt, now) >= 864e5;
109
+ /** Whether `version` is a canary snapshot: its pre-release starts `canary`. */
110
+ const isCanary = (version) => parseVersion(version)?.pre[0]?.startsWith(CANARY_TAG) === true;
111
+ /**
112
+ * The newest version, by semver precedence, among the dist-tags `current`
113
+ * follows: the channel tags, and `canary` too when `current` is a canary. A tag
114
+ * whose value doesn't parse is skipped; `null` when none does.
115
+ */
116
+ const newestTagged = (tags, current) => {
117
+ return (current !== void 0 && isCanary(current) ? [...CHANNEL_TAGS, "canary"] : CHANNEL_TAGS).flatMap((tag) => {
118
+ const value = tags[tag];
119
+ const version = typeof value === "string" ? parseVersion(value) : null;
120
+ return typeof value === "string" && version !== null ? [{
121
+ value,
122
+ version
123
+ }] : [];
124
+ }).reduce((best, candidate) => best === null || compareVersions(candidate.version, best.version) > 0 ? candidate : best, null)?.value ?? null;
125
+ };
126
+ /**
127
+ * The newest version on the registry that the running version follows ({@link
128
+ * newestTagged}), or `null` on any failure.
129
+ */
130
+ const fetchLatest = async (name, options = {}) => {
131
+ const registry = (options.registry ?? "https://registry.npmjs.org").replace(/\/+$/u, "");
132
+ const get = options.fetch ?? globalThis.fetch;
133
+ try {
134
+ const response = await get(`${registry}/-/package/${name}/dist-tags`, {
135
+ headers: { accept: "application/json" },
136
+ signal: AbortSignal.timeout(options.timeoutMs ?? 5e3)
137
+ });
138
+ if (!response.ok) return null;
139
+ const tags = await response.json();
140
+ return isRecord(tags) ? newestTagged(tags, options.current) : null;
141
+ } catch {
142
+ return null;
143
+ }
144
+ };
145
+ /**
146
+ * Asks the registry and records the answer. A failed check is recorded too, so
147
+ * an offline machine is not asked again until the interval passes; the last
148
+ * known version is kept. `options.current` is the running version.
149
+ */
150
+ const refreshUpdateCache = async (file, name, now, options = {}) => {
151
+ const before = readUpdateCache(file);
152
+ const latest = await fetchLatest(name, options) ?? before?.latest;
153
+ const cache = {
154
+ ...before,
155
+ checkedAt: now.toISOString(),
156
+ ...latest === void 0 ? {} : { latest }
157
+ };
158
+ writeUpdateCache(file, cache);
159
+ return cache;
160
+ };
161
+ /** The newer version the cache knows of, if any. */
162
+ const newerVersion = (cache, current) => cache?.latest !== void 0 && isNewer(cache.latest, current) ? cache.latest : null;
163
+ /**
164
+ * The version to tell the user about now: newer than theirs, and not already
165
+ * told today. `null` when there is nothing to say.
166
+ */
167
+ const pendingNotice = (cache, current, now) => {
168
+ const newer = newerVersion(cache, current);
169
+ if (newer === null) return null;
170
+ const told = cache?.notified;
171
+ return told?.version === newer && age(told.at, now) < 864e5 ? null : newer;
172
+ };
173
+ // ../core/src/update/switches.ts
174
+ /**
175
+ * @file The environment's say over the update check (ADR-038): the variables
176
+ * that turn it off (`ORK_NO_UPDATE_CHECK`, the common `NO_UPDATE_NOTIFIER`,
177
+ * and `CI`), and the registry to ask instead of npm's. The CLI adds its own
178
+ * rules about where a notice may go; the server behind `ork ui` uses only
179
+ * these.
180
+ */
181
+ /** Turns the automatic check and its notice off. */
182
+ const NO_UPDATE_ENV = "ORK_NO_UPDATE_CHECK";
183
+ /** A registry to ask instead of npm's, e.g. a company mirror. */
184
+ const UPDATE_REGISTRY_ENV = "ORK_UPDATE_REGISTRY";
185
+ /** The variables that turn the check off, in the order they are looked at. */
186
+ const UPDATE_SWITCHES = [
187
+ NO_UPDATE_ENV,
188
+ "NO_UPDATE_NOTIFIER",
189
+ "CI"
190
+ ];
191
+ /** Set to anything but empty, `0` or `false`. */
192
+ const isSwitchedOn = (value) => value !== void 0 && value !== "" && value !== "0" && value.toLowerCase() !== "false";
193
+ /** The variable that turns the check off in `env`, or `null` when none does. */
194
+ const updateSwitchOff = (env) => UPDATE_SWITCHES.find((name) => isSwitchedOn(env[name])) ?? null;
195
+ /** The registry `ORK_UPDATE_REGISTRY` names, if it names one. */
196
+ const registryOf = (env) => {
197
+ const registry = env[UPDATE_REGISTRY_ENV];
198
+ return registry === void 0 || registry === "" ? void 0 : registry;
199
+ };
200
+ /** The host of a registry that isn't an http(s) URL, past any `user:pass@`. */
201
+ const hostOf = (registry) => registry.replace(/^[a-z][\w+.-]*:\/\//iu, "").replace(/^[^/?#]*@/u, "").split(/[/?#]/u)[0] ?? "";
202
+ /**
203
+ * The registry as it may be shown (on a page, in a message): an http(s) URL
204
+ * without its user name and password, which a mirror's URL may carry; anything
205
+ * else only as its host.
206
+ */
207
+ const registryShown = (registry) => {
208
+ try {
209
+ const url = new URL(registry);
210
+ if (url.protocol === "http:" || url.protocol === "https:") {
211
+ if (url.username === "" && url.password === "") return registry;
212
+ url.username = "";
213
+ url.password = "";
214
+ return url.href;
215
+ }
216
+ } catch {}
217
+ return hostOf(registry);
218
+ };
219
+ // src/__build_info__.ts
220
+ /**
221
+ * @file The published package's name and version, bound to
222
+ * `packages/cli/package.json`. Do not edit by hand: `pnpm changesets:version`
223
+ * and `pnpm build` rewrite it (`tools/release/build-info.ts`), and a test
224
+ * fails when it is stale.
225
+ */
226
+ const ORK_NAME = "@krapht/ork";
227
+ const ORK_VERSION = "1.0.0-alpha.20";
228
+ export { registryShown as a, fetchLatest as c, readUpdateCache as d, refreshUpdateCache as f, nodeSession as h, registryOf as i, newerVersion as l, writeUpdateCache as m, ORK_VERSION as n, updateSwitchOff as o, updateCachePath as p, isSwitchedOn as r, checkDue as s, ORK_NAME as t, pendingNotice as u };
@@ -0,0 +1,17 @@
1
+ // ../core/src/list-filters/apply.ts
2
+ /**
3
+ * @file Keeps the rows a list's filter matches (RFC-017). The list's own order
4
+ * and `--limit` stay the caller's: filter, then order, then limit.
5
+ */
6
+ const fieldMatches = (field, row, wanted) => {
7
+ if (wanted === void 0 || wanted === "all" || field.get === void 0) return true;
8
+ const got = field.get(row);
9
+ if (got === void 0) return false;
10
+ const values = typeof got === "string" ? [got] : got;
11
+ if (field.type === "since") return values.some((value) => Date.parse(value) >= Date.parse(wanted));
12
+ if (field.type === "until") return values.some((value) => Date.parse(value) < Date.parse(wanted));
13
+ return values.includes(wanted);
14
+ };
15
+ /** The rows of `rows` every field of `filter` matches, in their order. */
16
+ const applyFilters = (table, rows, filter) => rows.filter((row) => table.fields.every((field) => fieldMatches(field, row, filter[field.name])));
17
+ export { applyFilters as t };
@@ -0,0 +1,27 @@
1
+ import { parseArgs } from "citty";
2
+ // src/argument-error.ts
3
+ /**
4
+ * @file The error citty's `runMain` takes for an argument error: the plugins
5
+ * that refuse a flag (`list/known-flags.ts`) or an extra word
6
+ * (`extra-arguments.ts`) throw one, so the message is one line and the exit
7
+ * code is 1, as for a wrong enum value.
8
+ */
9
+ /**
10
+ * An error citty's `runMain` takes for an argument error (its `CLIError`, which
11
+ * it does not export): a failed parse gives one, and it takes `message`.
12
+ */
13
+ const argumentError = (message) => {
14
+ try {
15
+ parseArgs([], { x: {
16
+ type: "string",
17
+ required: true
18
+ } });
19
+ } catch (error) {
20
+ if (error instanceof Error) {
21
+ error.message = message;
22
+ return error;
23
+ }
24
+ }
25
+ return new Error(message);
26
+ };
27
+ export { argumentError as t };
@@ -0,0 +1,92 @@
1
+ import "./src-8cOPOgrT.mjs";
2
+ import { t as findRun } from "./find-BTikxc54.mjs";
3
+ import { c as write, l as writeErr, s as style } from "./output-B1kwo0YU.mjs";
4
+ import { m as createTerminalHuman, t as attachRun } from "./attach-CEGbRg1D.mjs";
5
+ import { n as attachRefusal } from "./in-a-run-CbnKjO7t.mjs";
6
+ import { t as refuseExtraArguments } from "./extra-arguments-BxQMXARO.mjs";
7
+ import { t as withStore } from "./store-CDpk8Wtz.mjs";
8
+ import { defineCommand } from "citty";
9
+ // src/commands/attach.ts
10
+ /**
11
+ * @file `ork attach`: follow a run and the runs it called on this terminal, and
12
+ * answer their open decisions here (specs/cli.md). Ctrl-C detaches: the run
13
+ * keeps going and its decisions stay open (specs/run-control.md).
14
+ */
15
+ /**
16
+ * Follows `runId` and what it called until it ends or the person detaches, and
17
+ * returns the exit code: 0 when the run succeeded or they detached, else 1.
18
+ */
19
+ const attachSession = async (store, runId, session) => {
20
+ const detach = new AbortController();
21
+ session.signal?.addEventListener("abort", () => {
22
+ detach.abort();
23
+ });
24
+ const interactive = session.terminal?.interactive === true;
25
+ const human = session.terminal === void 0 || !interactive ? void 0 : createTerminalHuman({
26
+ ...session.terminal,
27
+ interrupt: () => {
28
+ detach.abort();
29
+ }
30
+ });
31
+ try {
32
+ const attached = await attachRun(store, runId, session.out, {
33
+ signal: detach.signal,
34
+ hintNoTerminal: human === void 0,
35
+ ...human === void 0 ? {} : { human },
36
+ ...session.home === void 0 ? {} : { home: session.home },
37
+ ...session.intervalMs === void 0 ? {} : { intervalMs: session.intervalMs }
38
+ });
39
+ if (detach.signal.aborted) {
40
+ session.out(style("dim", `detached; run ${runId} keeps going · ork attach ${runId} · ork stop ${runId}`));
41
+ return 0;
42
+ }
43
+ return attached.exitCode;
44
+ } finally {
45
+ human?.close();
46
+ }
47
+ };
48
+ const attach = defineCommand({
49
+ meta: {
50
+ name: "attach",
51
+ description: "Follow a run and the runs it called, and answer their questions here; Ctrl-C detaches"
52
+ },
53
+ plugins: [refuseExtraArguments],
54
+ args: { run: {
55
+ type: "positional",
56
+ description: "A run id or a prefix of one",
57
+ required: true
58
+ } },
59
+ run: ({ args }) => withStore(async (store) => {
60
+ const refusal = process.stdin.isTTY ? attachRefusal() : null;
61
+ if (refusal !== null) {
62
+ writeErr(refusal);
63
+ process.exitCode = 1;
64
+ return;
65
+ }
66
+ const found = findRun(store, args.run, process.cwd());
67
+ if (!found.ok) {
68
+ writeErr(found.error);
69
+ process.exitCode = 1;
70
+ return;
71
+ }
72
+ const abort = new AbortController();
73
+ const onSigint = () => {
74
+ abort.abort();
75
+ };
76
+ process.once("SIGINT", onSigint);
77
+ try {
78
+ process.exitCode = await attachSession(store, found.run.id, {
79
+ out: write,
80
+ signal: abort.signal,
81
+ terminal: {
82
+ input: process.stdin,
83
+ output: process.stdout,
84
+ interactive: process.stdin.isTTY
85
+ }
86
+ });
87
+ } finally {
88
+ process.off("SIGINT", onSigint);
89
+ }
90
+ })
91
+ });
92
+ export { attach };