ralphctl 0.5.0 → 0.6.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 +29 -16
- package/dist/absolute-path-WUTZQ37D.mjs +8 -0
- package/dist/chunk-6RDMCLWU.mjs +108 -0
- package/dist/chunk-HIU74KTO.mjs +1046 -0
- package/dist/chunk-S3PTDH57.mjs +78 -0
- package/dist/chunk-WV4D2CPG.mjs +26 -0
- package/dist/cli.mjs +22413 -717
- package/dist/manifest.json +24 -0
- package/dist/prompt-adapter-JQICGVX7.mjs +7 -0
- package/dist/prompts/ideate.md +3 -1
- package/dist/prompts/plan-auto.md +23 -8
- package/dist/prompts/plan-common-examples.md +3 -3
- package/dist/prompts/plan-common.md +6 -5
- package/dist/prompts/plan-interactive.md +30 -7
- package/dist/prompts/repo-onboard.md +154 -64
- package/dist/prompts/signals-task.md +3 -0
- package/dist/prompts/sprint-feedback.md +3 -0
- package/dist/prompts/task-evaluation.md +74 -53
- package/dist/prompts/task-execution.md +65 -21
- package/dist/prompts/ticket-refine.md +11 -8
- package/dist/prompts/validation-checklist.md +3 -2
- package/dist/skills/default/abstraction-first/SKILL.md +45 -0
- package/dist/skills/default/alignment/SKILL.md +46 -0
- package/dist/skills/default/iterative-review/SKILL.md +48 -0
- package/dist/skills/exec/.gitkeep +0 -0
- package/dist/skills/plan/.gitkeep +0 -0
- package/dist/skills/refine/.gitkeep +0 -0
- package/dist/storage-paths-IPNZZM5D.mjs +15 -0
- package/dist/validation-error-QT6Q7FYU.mjs +7 -0
- package/package.json +9 -4
- package/dist/add-67UFUI54.mjs +0 -17
- package/dist/add-DVPVHENV.mjs +0 -18
- package/dist/bootstrap-FMHG6DRY.mjs +0 -11
- package/dist/chunk-62HYDA7L.mjs +0 -1128
- package/dist/chunk-747KW2RW.mjs +0 -24
- package/dist/chunk-BSB4EDGR.mjs +0 -260
- package/dist/chunk-BT5FKIZX.mjs +0 -787
- package/dist/chunk-CBMFRQ4Y.mjs +0 -441
- package/dist/chunk-CFUVE2BP.mjs +0 -16
- package/dist/chunk-D6QZNEYN.mjs +0 -5520
- package/dist/chunk-FNAAA32W.mjs +0 -103
- package/dist/chunk-GQ2WFKBN.mjs +0 -269
- package/dist/chunk-IWXBJD2D.mjs +0 -27
- package/dist/chunk-OGEXYSFS.mjs +0 -228
- package/dist/chunk-VAZ3LJBI.mjs +0 -179
- package/dist/chunk-WDMLPXOD.mjs +0 -363
- package/dist/chunk-XN2UIHBY.mjs +0 -589
- package/dist/chunk-ZE2BRQA2.mjs +0 -5542
- package/dist/create-Z635FQKO.mjs +0 -15
- package/dist/handle-23EFF3BE.mjs +0 -22
- package/dist/mount-NCYR22SN.mjs +0 -7434
- package/dist/project-DQHF4ISP.mjs +0 -34
- package/dist/prompts/check-script-discover.md +0 -69
- package/dist/prompts/ideate-auto.md +0 -195
- package/dist/prompts/task-evaluation-resume.md +0 -41
- package/dist/resolver-OVPYVW6Q.mjs +0 -163
- package/dist/sprint-4E26AB5F.mjs +0 -38
- package/dist/start-T34NI3LF.mjs +0 -19
|
@@ -0,0 +1,1046 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/integration/ui/prompts/auto-mount.tsx
|
|
4
|
+
import "react";
|
|
5
|
+
import { render } from "ink";
|
|
6
|
+
|
|
7
|
+
// src/business/ports/prompt-port.ts
|
|
8
|
+
var PromptCancelledError = class extends Error {
|
|
9
|
+
constructor(message = "Prompt cancelled by user") {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = "PromptCancelledError";
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// src/integration/ui/prompts/prompt-host.tsx
|
|
16
|
+
import "react";
|
|
17
|
+
import { Box as Box8 } from "ink";
|
|
18
|
+
|
|
19
|
+
// src/integration/ui/prompts/hooks.ts
|
|
20
|
+
import { useEffect, useState } from "react";
|
|
21
|
+
|
|
22
|
+
// src/integration/ui/prompts/prompt-queue.ts
|
|
23
|
+
var SEQUENCE_IDLE_MS = 250;
|
|
24
|
+
var PromptQueue = class {
|
|
25
|
+
queue = [];
|
|
26
|
+
history = [];
|
|
27
|
+
lastResolveAt = 0;
|
|
28
|
+
idleClearTimer = null;
|
|
29
|
+
listeners = /* @__PURE__ */ new Set();
|
|
30
|
+
enqueue(prompt) {
|
|
31
|
+
if (this.queue.length === 0 && Date.now() - this.lastResolveAt > SEQUENCE_IDLE_MS) {
|
|
32
|
+
this.history = [];
|
|
33
|
+
}
|
|
34
|
+
if (this.idleClearTimer !== null) {
|
|
35
|
+
clearTimeout(this.idleClearTimer);
|
|
36
|
+
this.idleClearTimer = null;
|
|
37
|
+
}
|
|
38
|
+
this.queue.push(prompt);
|
|
39
|
+
this.notify();
|
|
40
|
+
}
|
|
41
|
+
current() {
|
|
42
|
+
return this.queue[0] ?? null;
|
|
43
|
+
}
|
|
44
|
+
resolveCurrent(value) {
|
|
45
|
+
const head = this.queue.shift();
|
|
46
|
+
if (!head) return;
|
|
47
|
+
this.history.push(buildResolved(head, value));
|
|
48
|
+
this.lastResolveAt = Date.now();
|
|
49
|
+
head.resolve(value);
|
|
50
|
+
this.scheduleIdleClear();
|
|
51
|
+
this.notify();
|
|
52
|
+
}
|
|
53
|
+
cancelCurrent(err) {
|
|
54
|
+
const head = this.queue.shift();
|
|
55
|
+
if (!head) return;
|
|
56
|
+
this.lastResolveAt = Date.now();
|
|
57
|
+
head.reject(err);
|
|
58
|
+
this.scheduleIdleClear();
|
|
59
|
+
this.notify();
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* After the queue empties, schedule a clear of the visible transcript.
|
|
63
|
+
* If a new prompt arrives within the idle window the timer is cancelled
|
|
64
|
+
* (see `enqueue`) and the transcript continues to accumulate. Without
|
|
65
|
+
* this, completed workflows leave their transcript on screen indefinitely
|
|
66
|
+
* (e.g. project-add answers persisting on the home view after pop).
|
|
67
|
+
*/
|
|
68
|
+
scheduleIdleClear() {
|
|
69
|
+
if (this.queue.length > 0) return;
|
|
70
|
+
if (this.idleClearTimer !== null) clearTimeout(this.idleClearTimer);
|
|
71
|
+
this.idleClearTimer = setTimeout(() => {
|
|
72
|
+
this.idleClearTimer = null;
|
|
73
|
+
if (this.queue.length === 0) {
|
|
74
|
+
this.history = [];
|
|
75
|
+
this.notify();
|
|
76
|
+
}
|
|
77
|
+
}, SEQUENCE_IDLE_MS);
|
|
78
|
+
}
|
|
79
|
+
/** Drop all pending prompts without resolving. Used on app shutdown. */
|
|
80
|
+
clear(reason) {
|
|
81
|
+
while (this.queue.length > 0) {
|
|
82
|
+
const p = this.queue.shift();
|
|
83
|
+
p?.reject(reason);
|
|
84
|
+
}
|
|
85
|
+
this.history = [];
|
|
86
|
+
this.notify();
|
|
87
|
+
}
|
|
88
|
+
/** Discard the visible transcript without affecting pending prompts. */
|
|
89
|
+
clearHistory() {
|
|
90
|
+
this.history = [];
|
|
91
|
+
this.notify();
|
|
92
|
+
}
|
|
93
|
+
subscribe(listener) {
|
|
94
|
+
this.listeners.add(listener);
|
|
95
|
+
try {
|
|
96
|
+
listener(this.snapshot());
|
|
97
|
+
} catch {
|
|
98
|
+
}
|
|
99
|
+
return () => {
|
|
100
|
+
this.listeners.delete(listener);
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
size() {
|
|
104
|
+
return this.queue.length;
|
|
105
|
+
}
|
|
106
|
+
historySnapshot() {
|
|
107
|
+
return this.history;
|
|
108
|
+
}
|
|
109
|
+
snapshot() {
|
|
110
|
+
return { current: this.current(), history: this.history };
|
|
111
|
+
}
|
|
112
|
+
notify() {
|
|
113
|
+
const snap = this.snapshot();
|
|
114
|
+
for (const l of this.listeners) {
|
|
115
|
+
try {
|
|
116
|
+
l(snap);
|
|
117
|
+
} catch {
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
function buildResolved(prompt, value) {
|
|
123
|
+
switch (prompt.kind) {
|
|
124
|
+
case "confirm":
|
|
125
|
+
return { kind: "confirm", options: prompt.options, value };
|
|
126
|
+
case "input":
|
|
127
|
+
return { kind: "input", options: prompt.options, value };
|
|
128
|
+
case "select":
|
|
129
|
+
return { kind: "select", options: prompt.options, value };
|
|
130
|
+
case "checkbox":
|
|
131
|
+
return { kind: "checkbox", options: prompt.options, value };
|
|
132
|
+
case "editor":
|
|
133
|
+
return { kind: "editor", options: prompt.options, value };
|
|
134
|
+
case "fileBrowser":
|
|
135
|
+
return { kind: "fileBrowser", options: prompt.options, value };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
var promptQueue = new PromptQueue();
|
|
139
|
+
|
|
140
|
+
// src/integration/ui/prompts/hooks.ts
|
|
141
|
+
function useCurrentPrompt() {
|
|
142
|
+
const [current, setCurrent] = useState(null);
|
|
143
|
+
useEffect(() => {
|
|
144
|
+
const unsubscribe = promptQueue.subscribe((state) => {
|
|
145
|
+
setCurrent(state.current);
|
|
146
|
+
});
|
|
147
|
+
return unsubscribe;
|
|
148
|
+
}, []);
|
|
149
|
+
return current;
|
|
150
|
+
}
|
|
151
|
+
function usePromptState() {
|
|
152
|
+
const [state, setState] = useState({ current: null, history: [] });
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
const unsubscribe = promptQueue.subscribe(setState);
|
|
155
|
+
return unsubscribe;
|
|
156
|
+
}, []);
|
|
157
|
+
return state;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// src/integration/ui/prompts/prompt-transcript.tsx
|
|
161
|
+
import "react";
|
|
162
|
+
import { Box, Text } from "ink";
|
|
163
|
+
|
|
164
|
+
// src/integration/ui/theme/tokens.ts
|
|
165
|
+
var inkColors = {
|
|
166
|
+
// Semantic state
|
|
167
|
+
success: "#7FB069",
|
|
168
|
+
// sage — not neon green, easier on the eyes
|
|
169
|
+
error: "#E76F51",
|
|
170
|
+
// warm coral — not a pure red klaxon
|
|
171
|
+
warning: "#E8A13B",
|
|
172
|
+
// amber
|
|
173
|
+
info: "#6CA6B0",
|
|
174
|
+
// dusty cyan
|
|
175
|
+
// UI state
|
|
176
|
+
muted: "#8B8680",
|
|
177
|
+
// warm gray (hint of yellow, matches the mustard brand)
|
|
178
|
+
highlight: "#E8C547",
|
|
179
|
+
// brand mustard — focus / active
|
|
180
|
+
// Brand
|
|
181
|
+
primary: "#E8C547",
|
|
182
|
+
// mustard — section stamps, accents
|
|
183
|
+
secondary: "#D98880"
|
|
184
|
+
// muted rose — Ralph personality pull-quotes
|
|
185
|
+
};
|
|
186
|
+
var glyphs = {
|
|
187
|
+
// Phase / status
|
|
188
|
+
phaseDone: "\u25A0",
|
|
189
|
+
phaseActive: "\u25C6",
|
|
190
|
+
phasePending: "\u25C7",
|
|
191
|
+
phaseDisabled: "\u25CC",
|
|
192
|
+
// Action cursors / bullets
|
|
193
|
+
actionCursor: "\u25B8",
|
|
194
|
+
selectMarker: "\u203A",
|
|
195
|
+
bulletListItem: "\xB7",
|
|
196
|
+
arrowRight: "\u2192",
|
|
197
|
+
activityArrow: "\u21B3",
|
|
198
|
+
// Section markers
|
|
199
|
+
badge: "\u25A3",
|
|
200
|
+
sectionRule: "\u2501",
|
|
201
|
+
// State confirmation
|
|
202
|
+
check: "\u2713",
|
|
203
|
+
cross: "\u2717",
|
|
204
|
+
warningGlyph: "\u26A0",
|
|
205
|
+
infoGlyph: "i",
|
|
206
|
+
// Loading (braille spinner frames)
|
|
207
|
+
spinner: ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"],
|
|
208
|
+
// Personality rail
|
|
209
|
+
quoteRail: "\u2503",
|
|
210
|
+
// Separators
|
|
211
|
+
inlineDot: "\xB7",
|
|
212
|
+
emDash: "\u2014",
|
|
213
|
+
separatorVertical: "\u2502"
|
|
214
|
+
};
|
|
215
|
+
var spacing = {
|
|
216
|
+
/** Between top-level sections (blank line). */
|
|
217
|
+
section: 1,
|
|
218
|
+
/** Before a final CTA row — a beat of breath before a decision. */
|
|
219
|
+
actionBreak: 2,
|
|
220
|
+
/** Card internal x-padding. */
|
|
221
|
+
cardPadX: 1,
|
|
222
|
+
/** Left-indent for nested content (steps, bullets, children). */
|
|
223
|
+
indent: 2,
|
|
224
|
+
/** Internal gutter inside card-like boxes. */
|
|
225
|
+
gutter: 1
|
|
226
|
+
};
|
|
227
|
+
var FIELD_LABEL_WIDTH = 12;
|
|
228
|
+
var DONUT_EMOJI = "\u{1F369}";
|
|
229
|
+
|
|
230
|
+
// src/integration/ui/prompts/prompt-transcript.tsx
|
|
231
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
232
|
+
function PromptTranscript({ history }) {
|
|
233
|
+
if (history.length === 0) return null;
|
|
234
|
+
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: history.map((entry, i) => /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
235
|
+
DONUT_EMOJI,
|
|
236
|
+
" ",
|
|
237
|
+
entry.options.message,
|
|
238
|
+
": ",
|
|
239
|
+
renderValue(entry)
|
|
240
|
+
] }) }, i)) });
|
|
241
|
+
}
|
|
242
|
+
function renderValue(entry) {
|
|
243
|
+
switch (entry.kind) {
|
|
244
|
+
case "confirm":
|
|
245
|
+
return entry.value ? "yes" : "no";
|
|
246
|
+
case "input":
|
|
247
|
+
return entry.value === "" ? "(empty)" : entry.value;
|
|
248
|
+
case "select": {
|
|
249
|
+
const choice = entry.options.choices.find((c) => c.value === entry.value);
|
|
250
|
+
return choice?.label ?? String(entry.value);
|
|
251
|
+
}
|
|
252
|
+
case "checkbox": {
|
|
253
|
+
if (entry.value.length === 0) return "(none)";
|
|
254
|
+
const labels = entry.value.map((v) => {
|
|
255
|
+
const choice = entry.options.choices.find((c) => c.value === v);
|
|
256
|
+
return choice?.label ?? String(v);
|
|
257
|
+
});
|
|
258
|
+
return labels.join(", ");
|
|
259
|
+
}
|
|
260
|
+
case "editor":
|
|
261
|
+
if (entry.value === null) return "(cancelled)";
|
|
262
|
+
if (entry.value === "") return "(empty)";
|
|
263
|
+
return summariseMultiline(entry.value);
|
|
264
|
+
case "fileBrowser":
|
|
265
|
+
return entry.value ?? "(cancelled)";
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function summariseMultiline(text) {
|
|
269
|
+
const lines = text.split("\n");
|
|
270
|
+
if (lines.length === 1) {
|
|
271
|
+
const first = lines[0] ?? "";
|
|
272
|
+
return first.length > 60 ? `${first.slice(0, 57)}\u2026` : first;
|
|
273
|
+
}
|
|
274
|
+
return `(${String(lines.length)} lines)`;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// src/integration/ui/prompts/confirm-prompt.tsx
|
|
278
|
+
import { useEffect as useEffect2, useMemo, useState as useState2 } from "react";
|
|
279
|
+
import { Box as Box2, Text as Text2, useInput, useStdout } from "ink";
|
|
280
|
+
import { ConfirmInput } from "@inkjs/ui";
|
|
281
|
+
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
282
|
+
var RESERVED_ROWS = 10;
|
|
283
|
+
var MIN_VIEWPORT = 6;
|
|
284
|
+
var MAX_VIEWPORT = 40;
|
|
285
|
+
function useTerminalRows() {
|
|
286
|
+
const { stdout } = useStdout();
|
|
287
|
+
const [rows, setRows] = useState2(stdout.rows);
|
|
288
|
+
useEffect2(() => {
|
|
289
|
+
const onResize = () => {
|
|
290
|
+
setRows(stdout.rows);
|
|
291
|
+
};
|
|
292
|
+
stdout.on("resize", onResize);
|
|
293
|
+
return () => {
|
|
294
|
+
stdout.off("resize", onResize);
|
|
295
|
+
};
|
|
296
|
+
}, [stdout]);
|
|
297
|
+
return rows;
|
|
298
|
+
}
|
|
299
|
+
function ConfirmPrompt({ options, onSubmit }) {
|
|
300
|
+
const details = options.details?.trim();
|
|
301
|
+
const lines = useMemo(() => details ? details.split("\n") : [], [details]);
|
|
302
|
+
const terminalRows = useTerminalRows();
|
|
303
|
+
const viewport = Math.max(MIN_VIEWPORT, Math.min(MAX_VIEWPORT, terminalRows - RESERVED_ROWS));
|
|
304
|
+
const total = lines.length;
|
|
305
|
+
const maxOffset = Math.max(0, total - viewport);
|
|
306
|
+
const scrollable = total > viewport;
|
|
307
|
+
const [offset, setOffset] = useState2(0);
|
|
308
|
+
useInput((_input, key) => {
|
|
309
|
+
if (!scrollable) return;
|
|
310
|
+
if (key.upArrow) setOffset((o) => Math.max(0, o - 1));
|
|
311
|
+
else if (key.downArrow) setOffset((o) => Math.min(maxOffset, o + 1));
|
|
312
|
+
else if (key.pageUp) setOffset((o) => Math.max(0, o - viewport));
|
|
313
|
+
else if (key.pageDown) setOffset((o) => Math.min(maxOffset, o + viewport));
|
|
314
|
+
});
|
|
315
|
+
const visibleLines = scrollable ? lines.slice(offset, offset + viewport) : lines;
|
|
316
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
317
|
+
details ? /* @__PURE__ */ jsxs2(
|
|
318
|
+
Box2,
|
|
319
|
+
{
|
|
320
|
+
flexDirection: "column",
|
|
321
|
+
borderStyle: "round",
|
|
322
|
+
borderColor: inkColors.muted,
|
|
323
|
+
paddingX: spacing.gutter,
|
|
324
|
+
marginBottom: spacing.section,
|
|
325
|
+
children: [
|
|
326
|
+
visibleLines.map((line, idx) => /* @__PURE__ */ jsx2(Text2, { children: line.length > 0 ? /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
327
|
+
/* @__PURE__ */ jsxs2(Text2, { color: inkColors.muted, children: [
|
|
328
|
+
glyphs.quoteRail,
|
|
329
|
+
" "
|
|
330
|
+
] }),
|
|
331
|
+
line
|
|
332
|
+
] }) : " " }, idx)),
|
|
333
|
+
scrollable ? /* @__PURE__ */ jsxs2(Text2, { color: inkColors.muted, children: [
|
|
334
|
+
glyphs.inlineDot,
|
|
335
|
+
" lines ",
|
|
336
|
+
String(offset + 1),
|
|
337
|
+
"\u2013",
|
|
338
|
+
String(Math.min(offset + viewport, total)),
|
|
339
|
+
" of",
|
|
340
|
+
" ",
|
|
341
|
+
String(total),
|
|
342
|
+
" ",
|
|
343
|
+
glyphs.inlineDot,
|
|
344
|
+
" \u2191/\u2193 scroll ",
|
|
345
|
+
glyphs.inlineDot,
|
|
346
|
+
" PgUp/PgDn page"
|
|
347
|
+
] }) : null
|
|
348
|
+
]
|
|
349
|
+
}
|
|
350
|
+
) : null,
|
|
351
|
+
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
352
|
+
/* @__PURE__ */ jsxs2(Text2, { children: [
|
|
353
|
+
DONUT_EMOJI,
|
|
354
|
+
" ",
|
|
355
|
+
options.message,
|
|
356
|
+
" "
|
|
357
|
+
] }),
|
|
358
|
+
/* @__PURE__ */ jsx2(
|
|
359
|
+
ConfirmInput,
|
|
360
|
+
{
|
|
361
|
+
defaultChoice: options.default === false ? "cancel" : "confirm",
|
|
362
|
+
onConfirm: () => {
|
|
363
|
+
onSubmit(true);
|
|
364
|
+
},
|
|
365
|
+
onCancel: () => {
|
|
366
|
+
onSubmit(false);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
)
|
|
370
|
+
] })
|
|
371
|
+
] });
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// src/integration/ui/prompts/input-prompt.tsx
|
|
375
|
+
import { useState as useState3 } from "react";
|
|
376
|
+
import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
|
|
377
|
+
import { TextInput } from "@inkjs/ui";
|
|
378
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
379
|
+
function InputPrompt({ options, onSubmit, onCancel }) {
|
|
380
|
+
const [error, setError] = useState3(null);
|
|
381
|
+
useInput2((_input, key) => {
|
|
382
|
+
if (key.escape) onCancel();
|
|
383
|
+
});
|
|
384
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
385
|
+
/* @__PURE__ */ jsxs3(Box3, { children: [
|
|
386
|
+
/* @__PURE__ */ jsxs3(Text3, { children: [
|
|
387
|
+
DONUT_EMOJI,
|
|
388
|
+
" ",
|
|
389
|
+
options.message,
|
|
390
|
+
":",
|
|
391
|
+
" "
|
|
392
|
+
] }),
|
|
393
|
+
/* @__PURE__ */ jsx3(
|
|
394
|
+
TextInput,
|
|
395
|
+
{
|
|
396
|
+
defaultValue: options.default,
|
|
397
|
+
placeholder: options.default,
|
|
398
|
+
onSubmit: (value) => {
|
|
399
|
+
const validation = options.validate?.(value);
|
|
400
|
+
if (validation !== void 0 && validation !== true) {
|
|
401
|
+
if (typeof validation === "string") {
|
|
402
|
+
setError(validation);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
setError(null);
|
|
407
|
+
onSubmit(value);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
)
|
|
411
|
+
] }),
|
|
412
|
+
error !== null && /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsxs3(Text3, { color: inkColors.error, children: [
|
|
413
|
+
" \u2717 ",
|
|
414
|
+
error
|
|
415
|
+
] }) })
|
|
416
|
+
] });
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// src/integration/ui/prompts/select-prompt.tsx
|
|
420
|
+
import { useState as useState4 } from "react";
|
|
421
|
+
import { Box as Box4, Text as Text4, useInput as useInput3 } from "ink";
|
|
422
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
423
|
+
function SelectPrompt({ options, onSubmit, onCancel }) {
|
|
424
|
+
const initialIdx = findInitialIdx(options);
|
|
425
|
+
const [focusedIdx, setFocusedIdx] = useState4(initialIdx);
|
|
426
|
+
useInput3((_input, key) => {
|
|
427
|
+
if (key.escape) {
|
|
428
|
+
onCancel();
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
if (key.upArrow) {
|
|
432
|
+
setFocusedIdx((i) => stepFocus(options.choices, i, -1));
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
if (key.downArrow) {
|
|
436
|
+
setFocusedIdx((i) => stepFocus(options.choices, i, 1));
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
if (key.return) {
|
|
440
|
+
const picked = options.choices[focusedIdx];
|
|
441
|
+
if (picked && !isDisabled(picked)) {
|
|
442
|
+
onSubmit(picked.value);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
|
|
447
|
+
/* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsxs4(Text4, { children: [
|
|
448
|
+
DONUT_EMOJI,
|
|
449
|
+
" ",
|
|
450
|
+
options.message,
|
|
451
|
+
":"
|
|
452
|
+
] }) }),
|
|
453
|
+
/* @__PURE__ */ jsx4(Box4, { marginLeft: 2, flexDirection: "column", children: options.choices.map((choice, i) => {
|
|
454
|
+
const isFocused = i === focusedIdx;
|
|
455
|
+
const disabled = isDisabled(choice);
|
|
456
|
+
const color = disabled ? inkColors.muted : isFocused ? inkColors.highlight : void 0;
|
|
457
|
+
const prefix = isFocused ? glyphs.actionCursor : " ";
|
|
458
|
+
return /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
459
|
+
/* @__PURE__ */ jsx4(Text4, { color, bold: isFocused, children: `${prefix} ${choice.label}` }),
|
|
460
|
+
typeof choice.disabled === "string" ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: ` (${choice.disabled})` }) : null
|
|
461
|
+
] }, `${String(i)}-${choice.label}`);
|
|
462
|
+
}) })
|
|
463
|
+
] });
|
|
464
|
+
}
|
|
465
|
+
function isDisabled(choice) {
|
|
466
|
+
return choice.disabled === true || typeof choice.disabled === "string";
|
|
467
|
+
}
|
|
468
|
+
function findInitialIdx(options) {
|
|
469
|
+
if (options.default !== void 0) {
|
|
470
|
+
const idx = options.choices.findIndex((c) => c.value === options.default);
|
|
471
|
+
const chosen = options.choices[idx];
|
|
472
|
+
if (idx >= 0 && chosen && !isDisabled(chosen)) return idx;
|
|
473
|
+
}
|
|
474
|
+
const firstEnabled = options.choices.findIndex((c) => !isDisabled(c));
|
|
475
|
+
return firstEnabled >= 0 ? firstEnabled : 0;
|
|
476
|
+
}
|
|
477
|
+
function stepFocus(choices, from, delta) {
|
|
478
|
+
const len = choices.length;
|
|
479
|
+
if (len === 0) return from;
|
|
480
|
+
let next = from;
|
|
481
|
+
for (let i = 0; i < len; i++) {
|
|
482
|
+
next = (next + delta + len) % len;
|
|
483
|
+
const candidate = choices[next];
|
|
484
|
+
if (candidate && !isDisabled(candidate)) return next;
|
|
485
|
+
}
|
|
486
|
+
return from;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// src/integration/ui/prompts/checkbox-prompt.tsx
|
|
490
|
+
import { useState as useState5 } from "react";
|
|
491
|
+
import { Box as Box5, Text as Text5, useInput as useInput4 } from "ink";
|
|
492
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
493
|
+
function CheckboxPrompt({ options, onSubmit, onCancel }) {
|
|
494
|
+
const initialFocus = options.choices.findIndex((c) => !isDisabled2(c));
|
|
495
|
+
const [focusedIdx, setFocusedIdx] = useState5(initialFocus >= 0 ? initialFocus : 0);
|
|
496
|
+
const [checked, setChecked] = useState5(() => seedCheckedSet(options));
|
|
497
|
+
useInput4((input, key) => {
|
|
498
|
+
if (key.escape) {
|
|
499
|
+
onCancel();
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
if (key.upArrow) {
|
|
503
|
+
setFocusedIdx((i) => stepFocus2(options.choices, i, -1));
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
if (key.downArrow) {
|
|
507
|
+
setFocusedIdx((i) => stepFocus2(options.choices, i, 1));
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
if (input === " ") {
|
|
511
|
+
const choice = options.choices[focusedIdx];
|
|
512
|
+
if (choice && !isDisabled2(choice)) {
|
|
513
|
+
setChecked((prev) => {
|
|
514
|
+
const next = new Set(prev);
|
|
515
|
+
if (next.has(focusedIdx)) next.delete(focusedIdx);
|
|
516
|
+
else next.add(focusedIdx);
|
|
517
|
+
return next;
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
if (key.return) {
|
|
523
|
+
const picked = [...checked].sort((a, b) => a - b).map((i) => options.choices[i]?.value).filter((v) => v !== void 0);
|
|
524
|
+
onSubmit(picked);
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
|
|
528
|
+
/* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsxs5(Text5, { children: [
|
|
529
|
+
DONUT_EMOJI,
|
|
530
|
+
" ",
|
|
531
|
+
options.message,
|
|
532
|
+
": ",
|
|
533
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "(space toggles, enter submits)" })
|
|
534
|
+
] }) }),
|
|
535
|
+
/* @__PURE__ */ jsx5(Box5, { marginLeft: 2, flexDirection: "column", children: options.choices.map((choice, i) => {
|
|
536
|
+
const isFocused = i === focusedIdx;
|
|
537
|
+
const disabled = isDisabled2(choice);
|
|
538
|
+
const color = disabled ? inkColors.muted : isFocused ? inkColors.highlight : void 0;
|
|
539
|
+
const cursor = isFocused ? glyphs.actionCursor : " ";
|
|
540
|
+
const mark = checked.has(i) ? glyphs.check : glyphs.phasePending;
|
|
541
|
+
return /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
542
|
+
/* @__PURE__ */ jsx5(Text5, { color, bold: isFocused, children: `${cursor} ${mark} ${choice.label}` }),
|
|
543
|
+
typeof choice.disabled === "string" ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: ` (${choice.disabled})` }) : null
|
|
544
|
+
] }, `${String(i)}-${choice.label}`);
|
|
545
|
+
}) })
|
|
546
|
+
] });
|
|
547
|
+
}
|
|
548
|
+
function isDisabled2(choice) {
|
|
549
|
+
return choice.disabled === true || typeof choice.disabled === "string";
|
|
550
|
+
}
|
|
551
|
+
function seedCheckedSet(options) {
|
|
552
|
+
const defaults = options.defaults ?? [];
|
|
553
|
+
const set = /* @__PURE__ */ new Set();
|
|
554
|
+
for (const v of defaults) {
|
|
555
|
+
const idx = options.choices.findIndex((c) => c.value === v);
|
|
556
|
+
if (idx >= 0) set.add(idx);
|
|
557
|
+
}
|
|
558
|
+
return set;
|
|
559
|
+
}
|
|
560
|
+
function stepFocus2(choices, from, delta) {
|
|
561
|
+
const len = choices.length;
|
|
562
|
+
if (len === 0) return from;
|
|
563
|
+
let next = from;
|
|
564
|
+
for (let i = 0; i < len; i++) {
|
|
565
|
+
next = (next + delta + len) % len;
|
|
566
|
+
const candidate = choices[next];
|
|
567
|
+
if (candidate && !isDisabled2(candidate)) return next;
|
|
568
|
+
}
|
|
569
|
+
return from;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// src/integration/ui/prompts/editor-prompt.tsx
|
|
573
|
+
import { useState as useState6, useMemo as useMemo2 } from "react";
|
|
574
|
+
import { Box as Box6, Text as Text6, useInput as useInput5 } from "ink";
|
|
575
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
576
|
+
var MIN_EDIT_ROWS = 8;
|
|
577
|
+
function splitLines(text) {
|
|
578
|
+
return text.split("\n");
|
|
579
|
+
}
|
|
580
|
+
function joinLines(lines) {
|
|
581
|
+
return lines.join("\n");
|
|
582
|
+
}
|
|
583
|
+
function clampCursor(lines, cursor) {
|
|
584
|
+
const row = Math.max(0, Math.min(cursor.row, lines.length - 1));
|
|
585
|
+
const line = lines[row] ?? "";
|
|
586
|
+
const col = Math.max(0, Math.min(cursor.col, line.length));
|
|
587
|
+
return { row, col };
|
|
588
|
+
}
|
|
589
|
+
function EditorPrompt({ options, onSubmit, onCancel }) {
|
|
590
|
+
const [lines, setLines] = useState6(() => splitLines(options.default ?? ""));
|
|
591
|
+
const [cursor, setCursor] = useState6(() => {
|
|
592
|
+
const init = splitLines(options.default ?? "");
|
|
593
|
+
const lastRow = init.length - 1;
|
|
594
|
+
return { row: lastRow, col: (init[lastRow] ?? "").length };
|
|
595
|
+
});
|
|
596
|
+
useInput5((input, key) => {
|
|
597
|
+
if (key.escape || key.ctrl && input === "c") {
|
|
598
|
+
onCancel();
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
if (key.ctrl && input === "d") {
|
|
602
|
+
onSubmit(joinLines(lines));
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
if (key.leftArrow) {
|
|
606
|
+
setCursor((prev) => {
|
|
607
|
+
if (prev.col > 0) return { row: prev.row, col: prev.col - 1 };
|
|
608
|
+
if (prev.row > 0) {
|
|
609
|
+
const up = lines[prev.row - 1] ?? "";
|
|
610
|
+
return { row: prev.row - 1, col: up.length };
|
|
611
|
+
}
|
|
612
|
+
return prev;
|
|
613
|
+
});
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
if (key.rightArrow) {
|
|
617
|
+
setCursor((prev) => {
|
|
618
|
+
const curLine = lines[prev.row] ?? "";
|
|
619
|
+
if (prev.col < curLine.length) return { row: prev.row, col: prev.col + 1 };
|
|
620
|
+
if (prev.row < lines.length - 1) return { row: prev.row + 1, col: 0 };
|
|
621
|
+
return prev;
|
|
622
|
+
});
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
if (key.upArrow) {
|
|
626
|
+
setCursor((prev) => clampCursor(lines, { row: prev.row - 1, col: prev.col }));
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
if (key.downArrow) {
|
|
630
|
+
setCursor((prev) => clampCursor(lines, { row: prev.row + 1, col: prev.col }));
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
if (key.ctrl && input === "a") {
|
|
634
|
+
setCursor((prev) => ({ row: prev.row, col: 0 }));
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
if (key.ctrl && input === "e") {
|
|
638
|
+
setCursor((prev) => {
|
|
639
|
+
const curLine = lines[prev.row] ?? "";
|
|
640
|
+
return { row: prev.row, col: curLine.length };
|
|
641
|
+
});
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
if (key.backspace || key.delete) {
|
|
645
|
+
setLines((prev) => {
|
|
646
|
+
const next = [...prev];
|
|
647
|
+
const line = next[cursor.row] ?? "";
|
|
648
|
+
if (cursor.col > 0) {
|
|
649
|
+
next[cursor.row] = line.slice(0, cursor.col - 1) + line.slice(cursor.col);
|
|
650
|
+
setCursor({ row: cursor.row, col: cursor.col - 1 });
|
|
651
|
+
} else if (cursor.row > 0) {
|
|
652
|
+
const prevLine = next[cursor.row - 1] ?? "";
|
|
653
|
+
const mergedCol = prevLine.length;
|
|
654
|
+
next[cursor.row - 1] = prevLine + line;
|
|
655
|
+
next.splice(cursor.row, 1);
|
|
656
|
+
setCursor({ row: cursor.row - 1, col: mergedCol });
|
|
657
|
+
}
|
|
658
|
+
return next;
|
|
659
|
+
});
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
if (key.return) {
|
|
663
|
+
setLines((prev) => {
|
|
664
|
+
const next = [...prev];
|
|
665
|
+
const line = next[cursor.row] ?? "";
|
|
666
|
+
const before = line.slice(0, cursor.col);
|
|
667
|
+
const after = line.slice(cursor.col);
|
|
668
|
+
next[cursor.row] = before;
|
|
669
|
+
next.splice(cursor.row + 1, 0, after);
|
|
670
|
+
return next;
|
|
671
|
+
});
|
|
672
|
+
setCursor({ row: cursor.row + 1, col: 0 });
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
if (input && !key.ctrl && !key.meta) {
|
|
676
|
+
const chunk = input;
|
|
677
|
+
setLines((prev) => {
|
|
678
|
+
const next = [...prev];
|
|
679
|
+
const line = next[cursor.row] ?? "";
|
|
680
|
+
const before = line.slice(0, cursor.col);
|
|
681
|
+
const after = line.slice(cursor.col);
|
|
682
|
+
const parts = (before + chunk + after).split("\n");
|
|
683
|
+
next.splice(cursor.row, 1, ...parts);
|
|
684
|
+
const insertedLines = chunk.split("\n");
|
|
685
|
+
const insertedRow = cursor.row + insertedLines.length - 1;
|
|
686
|
+
const insertedCol = insertedLines.length === 1 ? before.length + chunk.length : (insertedLines[insertedLines.length - 1] ?? "").length;
|
|
687
|
+
setCursor({ row: insertedRow, col: insertedCol });
|
|
688
|
+
return next;
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
const renderedLines = useMemo2(() => {
|
|
693
|
+
const padCount = Math.max(0, MIN_EDIT_ROWS - lines.length);
|
|
694
|
+
const padded = lines.map((line, i) => {
|
|
695
|
+
if (i !== cursor.row) return line.length > 0 ? line : " ";
|
|
696
|
+
const before = line.slice(0, cursor.col);
|
|
697
|
+
const at = line[cursor.col] ?? " ";
|
|
698
|
+
const after = line.slice(cursor.col + 1);
|
|
699
|
+
return { before, at, after };
|
|
700
|
+
});
|
|
701
|
+
for (let i = 0; i < padCount; i++) padded.push(" ");
|
|
702
|
+
return padded;
|
|
703
|
+
}, [lines, cursor]);
|
|
704
|
+
const charCount = lines.reduce((sum, l) => sum + l.length, 0);
|
|
705
|
+
const lineCount = lines.length;
|
|
706
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", borderStyle: "round", borderColor: inkColors.muted, paddingX: 1, width: "100%", children: [
|
|
707
|
+
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
708
|
+
/* @__PURE__ */ jsx6(Text6, { color: inkColors.primary, bold: true, children: glyphs.badge }),
|
|
709
|
+
/* @__PURE__ */ jsx6(Text6, { color: inkColors.primary, bold: true, children: ` ${options.message.toUpperCase()}` })
|
|
710
|
+
] }),
|
|
711
|
+
/* @__PURE__ */ jsx6(Box6, { marginTop: 1, marginLeft: 1, flexDirection: "column", children: renderedLines.map((item, i) => {
|
|
712
|
+
if (typeof item === "string") {
|
|
713
|
+
return /* @__PURE__ */ jsx6(Text6, { dimColor: i >= lines.length, children: item }, i);
|
|
714
|
+
}
|
|
715
|
+
return /* @__PURE__ */ jsxs6(Text6, { children: [
|
|
716
|
+
item.before,
|
|
717
|
+
/* @__PURE__ */ jsx6(Text6, { inverse: true, children: item.at }),
|
|
718
|
+
item.after
|
|
719
|
+
] }, i);
|
|
720
|
+
}) }),
|
|
721
|
+
/* @__PURE__ */ jsxs6(Box6, { marginTop: 1, justifyContent: "space-between", children: [
|
|
722
|
+
/* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
723
|
+
"Ctrl+D submit ",
|
|
724
|
+
glyphs.inlineDot,
|
|
725
|
+
" Esc cancel ",
|
|
726
|
+
glyphs.inlineDot,
|
|
727
|
+
" Enter newline"
|
|
728
|
+
] }),
|
|
729
|
+
/* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
730
|
+
String(lineCount),
|
|
731
|
+
" ",
|
|
732
|
+
lineCount === 1 ? "line" : "lines",
|
|
733
|
+
" ",
|
|
734
|
+
glyphs.inlineDot,
|
|
735
|
+
" ",
|
|
736
|
+
String(charCount),
|
|
737
|
+
" ",
|
|
738
|
+
charCount === 1 ? "char" : "chars"
|
|
739
|
+
] })
|
|
740
|
+
] })
|
|
741
|
+
] });
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// src/integration/ui/prompts/file-browser-prompt.tsx
|
|
745
|
+
import { useEffect as useEffect3, useMemo as useMemo3, useState as useState7 } from "react";
|
|
746
|
+
import { readdirSync, statSync } from "fs";
|
|
747
|
+
import { homedir } from "os";
|
|
748
|
+
import { dirname, join, resolve } from "path";
|
|
749
|
+
import { Box as Box7, Text as Text7, useInput as useInput6 } from "ink";
|
|
750
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
751
|
+
function listDirectories(dirPath) {
|
|
752
|
+
try {
|
|
753
|
+
return readdirSync(dirPath, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name).sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
|
754
|
+
} catch {
|
|
755
|
+
return [];
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
function isGitRepo(dirPath) {
|
|
759
|
+
try {
|
|
760
|
+
return statSync(join(dirPath, ".git")).isDirectory();
|
|
761
|
+
} catch {
|
|
762
|
+
return false;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
var PAGE_SIZE = 12;
|
|
766
|
+
function FileBrowserPrompt({ options, onSubmit, onCancel }) {
|
|
767
|
+
const [currentPath, setCurrentPath] = useState7(
|
|
768
|
+
() => options.startPath ? resolve(options.startPath) : homedir()
|
|
769
|
+
);
|
|
770
|
+
const [dirs, setDirs] = useState7([]);
|
|
771
|
+
const [cursor, setCursor] = useState7(0);
|
|
772
|
+
const [offset, setOffset] = useState7(0);
|
|
773
|
+
useEffect3(() => {
|
|
774
|
+
setDirs(listDirectories(currentPath));
|
|
775
|
+
setCursor(0);
|
|
776
|
+
setOffset(0);
|
|
777
|
+
}, [currentPath]);
|
|
778
|
+
const message = options.message ?? "Browse to directory:";
|
|
779
|
+
const parent = dirname(currentPath);
|
|
780
|
+
useInput6((input, key) => {
|
|
781
|
+
if (key.escape) {
|
|
782
|
+
onCancel();
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
if (key.upArrow) {
|
|
786
|
+
setCursor((c) => Math.max(0, c - 1));
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
if (key.downArrow) {
|
|
790
|
+
setCursor((c) => Math.min(dirs.length - 1, c + 1));
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
if (key.return) {
|
|
794
|
+
const name = dirs[cursor];
|
|
795
|
+
if (name) setCurrentPath(join(currentPath, name));
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
if (input === ".") {
|
|
799
|
+
if (options.mustBeGitRepo && !isGitRepo(currentPath)) return;
|
|
800
|
+
onSubmit(currentPath);
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
if (key.backspace || input === "u") {
|
|
804
|
+
if (parent !== currentPath) setCurrentPath(parent);
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
if (input === "h") {
|
|
808
|
+
setCurrentPath(homedir());
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
});
|
|
812
|
+
useEffect3(() => {
|
|
813
|
+
if (cursor < offset) setOffset(cursor);
|
|
814
|
+
else if (cursor >= offset + PAGE_SIZE) setOffset(cursor - PAGE_SIZE + 1);
|
|
815
|
+
}, [cursor, offset]);
|
|
816
|
+
const visible = useMemo3(() => dirs.slice(offset, offset + PAGE_SIZE), [dirs, offset]);
|
|
817
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", borderStyle: "round", borderColor: inkColors.muted, paddingX: 1, children: [
|
|
818
|
+
/* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsxs7(Text7, { children: [
|
|
819
|
+
DONUT_EMOJI,
|
|
820
|
+
" ",
|
|
821
|
+
message
|
|
822
|
+
] }) }),
|
|
823
|
+
/* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: currentPath }) }),
|
|
824
|
+
/* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
|
|
825
|
+
visible.length === 0 && /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "(no subdirectories)" }),
|
|
826
|
+
visible.map((name, i) => {
|
|
827
|
+
const absoluteIdx = offset + i;
|
|
828
|
+
const isSelected = absoluteIdx === cursor;
|
|
829
|
+
const full = join(currentPath, name);
|
|
830
|
+
const repo = isGitRepo(full);
|
|
831
|
+
const icon = repo ? "\u2699 " : "\u25B8 ";
|
|
832
|
+
return /* @__PURE__ */ jsxs7(Text7, { color: isSelected ? inkColors.highlight : void 0, children: [
|
|
833
|
+
isSelected ? "\u203A " : " ",
|
|
834
|
+
icon,
|
|
835
|
+
name,
|
|
836
|
+
repo && /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " (git repo)" })
|
|
837
|
+
] }, name);
|
|
838
|
+
})
|
|
839
|
+
] }),
|
|
840
|
+
/* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "\u2191/\u2193 move \xB7 Enter descend \xB7 Backspace up \xB7 h home \xB7 . select \xB7 Esc cancel" }) })
|
|
841
|
+
] });
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// src/integration/ui/prompts/prompt-host.tsx
|
|
845
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
846
|
+
function cancel() {
|
|
847
|
+
promptQueue.cancelCurrent(new PromptCancelledError());
|
|
848
|
+
}
|
|
849
|
+
function PromptHost() {
|
|
850
|
+
const { current, history } = usePromptState();
|
|
851
|
+
if (!current && history.length === 0) return null;
|
|
852
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
853
|
+
/* @__PURE__ */ jsx8(PromptTranscript, { history }),
|
|
854
|
+
current ? /* @__PURE__ */ jsx8(ActivePrompt, { prompt: current }) : null
|
|
855
|
+
] });
|
|
856
|
+
}
|
|
857
|
+
function ActivePrompt({
|
|
858
|
+
prompt: current
|
|
859
|
+
}) {
|
|
860
|
+
switch (current.kind) {
|
|
861
|
+
case "confirm":
|
|
862
|
+
return /* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsx8(
|
|
863
|
+
ConfirmPrompt,
|
|
864
|
+
{
|
|
865
|
+
options: current.options,
|
|
866
|
+
onSubmit: (v) => {
|
|
867
|
+
promptQueue.resolveCurrent(v);
|
|
868
|
+
},
|
|
869
|
+
onCancel: cancel
|
|
870
|
+
}
|
|
871
|
+
) });
|
|
872
|
+
case "input":
|
|
873
|
+
return /* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsx8(
|
|
874
|
+
InputPrompt,
|
|
875
|
+
{
|
|
876
|
+
options: current.options,
|
|
877
|
+
onSubmit: (v) => {
|
|
878
|
+
promptQueue.resolveCurrent(v);
|
|
879
|
+
},
|
|
880
|
+
onCancel: cancel
|
|
881
|
+
}
|
|
882
|
+
) });
|
|
883
|
+
case "select":
|
|
884
|
+
return /* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsx8(
|
|
885
|
+
SelectPrompt,
|
|
886
|
+
{
|
|
887
|
+
options: current.options,
|
|
888
|
+
onSubmit: (v) => {
|
|
889
|
+
promptQueue.resolveCurrent(v);
|
|
890
|
+
},
|
|
891
|
+
onCancel: cancel
|
|
892
|
+
}
|
|
893
|
+
) });
|
|
894
|
+
case "checkbox":
|
|
895
|
+
return /* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsx8(
|
|
896
|
+
CheckboxPrompt,
|
|
897
|
+
{
|
|
898
|
+
options: current.options,
|
|
899
|
+
onSubmit: (v) => {
|
|
900
|
+
promptQueue.resolveCurrent(v);
|
|
901
|
+
},
|
|
902
|
+
onCancel: cancel
|
|
903
|
+
}
|
|
904
|
+
) });
|
|
905
|
+
case "editor":
|
|
906
|
+
return /* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsx8(
|
|
907
|
+
EditorPrompt,
|
|
908
|
+
{
|
|
909
|
+
options: current.options,
|
|
910
|
+
onSubmit: (v) => {
|
|
911
|
+
promptQueue.resolveCurrent(v);
|
|
912
|
+
},
|
|
913
|
+
onCancel: () => {
|
|
914
|
+
promptQueue.resolveCurrent(null);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
) });
|
|
918
|
+
case "fileBrowser":
|
|
919
|
+
return /* @__PURE__ */ jsx8(Box8, { children: /* @__PURE__ */ jsx8(
|
|
920
|
+
FileBrowserPrompt,
|
|
921
|
+
{
|
|
922
|
+
options: current.options,
|
|
923
|
+
onSubmit: (v) => {
|
|
924
|
+
promptQueue.resolveCurrent(v);
|
|
925
|
+
},
|
|
926
|
+
onCancel: () => {
|
|
927
|
+
promptQueue.resolveCurrent(null);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
) });
|
|
931
|
+
default: {
|
|
932
|
+
const _exhaustive = current;
|
|
933
|
+
void _exhaustive;
|
|
934
|
+
return null;
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
// src/integration/ui/prompts/auto-mount.tsx
|
|
940
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
941
|
+
var hostState = "none";
|
|
942
|
+
var autoInstance = null;
|
|
943
|
+
var drainUnsubscribe = null;
|
|
944
|
+
function registerExternalHost() {
|
|
945
|
+
hostState = "external";
|
|
946
|
+
return () => {
|
|
947
|
+
hostState = "none";
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
function canInteract() {
|
|
951
|
+
if (process.env["RALPHCTL_NO_TUI"]) return false;
|
|
952
|
+
if (process.env["CI"]) return false;
|
|
953
|
+
if (process.env["RALPHCTL_JSON"]) return false;
|
|
954
|
+
if (!process.stdout.isTTY) return false;
|
|
955
|
+
if (!process.stdin.isTTY) return false;
|
|
956
|
+
return true;
|
|
957
|
+
}
|
|
958
|
+
function ensurePromptHost() {
|
|
959
|
+
if (hostState === "external" || hostState === "auto") return;
|
|
960
|
+
if (!canInteract()) {
|
|
961
|
+
throw new PromptCancelledError(
|
|
962
|
+
"Interactive prompt requested in non-interactive environment. Pass the value as a CLI flag."
|
|
963
|
+
);
|
|
964
|
+
}
|
|
965
|
+
hostState = "auto";
|
|
966
|
+
autoInstance = render(/* @__PURE__ */ jsx9(PromptHost, {}), { exitOnCtrlC: false });
|
|
967
|
+
drainUnsubscribe = promptQueue.subscribe((state) => {
|
|
968
|
+
if (state.current === null) {
|
|
969
|
+
setImmediate(() => {
|
|
970
|
+
if (promptQueue.size() === 0) teardownAutoHost();
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
function teardownAutoHost() {
|
|
976
|
+
if (hostState !== "auto") return;
|
|
977
|
+
drainUnsubscribe?.();
|
|
978
|
+
drainUnsubscribe = null;
|
|
979
|
+
autoInstance?.unmount();
|
|
980
|
+
autoInstance = null;
|
|
981
|
+
hostState = "none";
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
// src/integration/ui/prompts/prompt-adapter.ts
|
|
985
|
+
var InkPromptAdapter = class {
|
|
986
|
+
select(options) {
|
|
987
|
+
ensurePromptHost();
|
|
988
|
+
return new Promise((resolve2, reject) => {
|
|
989
|
+
promptQueue.enqueue({
|
|
990
|
+
kind: "select",
|
|
991
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
|
992
|
+
options,
|
|
993
|
+
resolve: resolve2,
|
|
994
|
+
reject
|
|
995
|
+
});
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
confirm(options) {
|
|
999
|
+
ensurePromptHost();
|
|
1000
|
+
return new Promise((resolve2, reject) => {
|
|
1001
|
+
promptQueue.enqueue({ kind: "confirm", options, resolve: resolve2, reject });
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
input(options) {
|
|
1005
|
+
ensurePromptHost();
|
|
1006
|
+
return new Promise((resolve2, reject) => {
|
|
1007
|
+
promptQueue.enqueue({ kind: "input", options, resolve: resolve2, reject });
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
checkbox(options) {
|
|
1011
|
+
ensurePromptHost();
|
|
1012
|
+
return new Promise((resolve2, reject) => {
|
|
1013
|
+
promptQueue.enqueue({
|
|
1014
|
+
kind: "checkbox",
|
|
1015
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
|
1016
|
+
options,
|
|
1017
|
+
resolve: resolve2,
|
|
1018
|
+
reject
|
|
1019
|
+
});
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
editor(options) {
|
|
1023
|
+
ensurePromptHost();
|
|
1024
|
+
return new Promise((resolve2, reject) => {
|
|
1025
|
+
promptQueue.enqueue({ kind: "editor", options, resolve: resolve2, reject });
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
fileBrowser(options) {
|
|
1029
|
+
ensurePromptHost();
|
|
1030
|
+
return new Promise((resolve2, reject) => {
|
|
1031
|
+
promptQueue.enqueue({ kind: "fileBrowser", options, resolve: resolve2, reject });
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
};
|
|
1035
|
+
|
|
1036
|
+
export {
|
|
1037
|
+
PromptCancelledError,
|
|
1038
|
+
useCurrentPrompt,
|
|
1039
|
+
inkColors,
|
|
1040
|
+
glyphs,
|
|
1041
|
+
spacing,
|
|
1042
|
+
FIELD_LABEL_WIDTH,
|
|
1043
|
+
PromptHost,
|
|
1044
|
+
registerExternalHost,
|
|
1045
|
+
InkPromptAdapter
|
|
1046
|
+
};
|