tickmarkr 1.90.5 → 1.90.7
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/dist/cli/commands/doctor.js +25 -3
- package/dist/cli/commands/fleet.d.ts +18 -0
- package/dist/cli/commands/fleet.js +88 -56
- package/dist/cli/commands/init.d.ts +6 -1
- package/dist/cli/commands/init.js +73 -47
- package/dist/cli/commands/run.js +3 -2
- package/dist/cli/commands/verify.d.ts +1 -0
- package/dist/cli/commands/verify.js +12 -2
- package/dist/config/fleet-overlay.d.ts +4 -0
- package/dist/config/fleet-overlay.js +4 -0
- package/dist/gates/baseline.d.ts +15 -0
- package/dist/gates/baseline.js +88 -5
- package/dist/run/consult.js +17 -2
- package/dist/run/daemon.js +25 -0
- package/dist/tui/ink/components.d.ts +3 -1
- package/dist/tui/ink/components.js +2 -2
- package/dist/tui/ink/fleet-app.d.ts +15 -2
- package/dist/tui/ink/fleet-app.js +169 -41
- package/dist/tui/ink/init-app.d.ts +34 -0
- package/dist/tui/ink/init-app.js +272 -0
- package/package.json +1 -1
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { emitKeypressEvents } from "node:readline";
|
|
3
|
+
import { PassThrough } from "node:stream";
|
|
4
|
+
import { Box, render, Text, useApp, useInput } from "ink";
|
|
5
|
+
import { useRef, useState } from "react";
|
|
6
|
+
import { GLYPHS } from "../../brand.js";
|
|
7
|
+
import { ToggleMark } from "./components.js";
|
|
8
|
+
const DRIVERS = ["auto", "herdr", "subprocess"];
|
|
9
|
+
const VISIBILITY = ["pane", "headless"];
|
|
10
|
+
// inkOutput/inkInput replicate the compatibility bridges in fleet-app.tsx — they are
|
|
11
|
+
// not exported there and a sibling change owns that file, so importing was not an option when
|
|
12
|
+
// this act landed. If a third Ink act appears, hoist the pair into a shared module.
|
|
13
|
+
const escape = String.fromCharCode(27);
|
|
14
|
+
const inkBookkeepingWrites = {
|
|
15
|
+
"": true,
|
|
16
|
+
[`${escape}[?25l`]: true,
|
|
17
|
+
[`${escape}[?25h`]: true,
|
|
18
|
+
[`${escape}[?2026h`]: true,
|
|
19
|
+
[`${escape}[?2026l`]: true,
|
|
20
|
+
};
|
|
21
|
+
function inkOutput(output) {
|
|
22
|
+
if (typeof output.on === "function" && typeof output.off === "function")
|
|
23
|
+
return output;
|
|
24
|
+
const facade = Object.create(output);
|
|
25
|
+
const write = output.write.bind(output);
|
|
26
|
+
facade.write = ((chunk, ...args) => {
|
|
27
|
+
const text = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
|
|
28
|
+
if (inkBookkeepingWrites[text] === true)
|
|
29
|
+
return true;
|
|
30
|
+
return Reflect.apply(write, output, [chunk, ...args]);
|
|
31
|
+
});
|
|
32
|
+
facade.on = () => facade;
|
|
33
|
+
facade.off = () => facade;
|
|
34
|
+
return facade;
|
|
35
|
+
}
|
|
36
|
+
function inkInput(input, initialInput) {
|
|
37
|
+
const productionInput = typeof input.ref === "function" && typeof input.unref === "function";
|
|
38
|
+
// Isolate Ink's listeners on a bridge so every editor exit can detach the one listener it
|
|
39
|
+
// owns from the operator's terminal (see fleet-app.tsx for the full provenance).
|
|
40
|
+
const stream = new PassThrough();
|
|
41
|
+
stream.isTTY = input.isTTY;
|
|
42
|
+
stream.setRawMode = input.setRawMode?.bind(input);
|
|
43
|
+
stream.ref = () => {
|
|
44
|
+
if (productionInput)
|
|
45
|
+
input.ref();
|
|
46
|
+
return stream;
|
|
47
|
+
};
|
|
48
|
+
stream.unref = () => {
|
|
49
|
+
if (productionInput)
|
|
50
|
+
input.unref();
|
|
51
|
+
return stream;
|
|
52
|
+
};
|
|
53
|
+
const queued = [...initialInput];
|
|
54
|
+
let active = true;
|
|
55
|
+
let scheduled;
|
|
56
|
+
const pump = () => {
|
|
57
|
+
scheduled = undefined;
|
|
58
|
+
if (!active)
|
|
59
|
+
return;
|
|
60
|
+
const next = queued.shift();
|
|
61
|
+
if (next === undefined)
|
|
62
|
+
return;
|
|
63
|
+
stream.write(next);
|
|
64
|
+
scheduled = setTimeout(pump, 0);
|
|
65
|
+
};
|
|
66
|
+
const onData = (chunk) => {
|
|
67
|
+
queued.push(typeof chunk === "string" ? chunk : chunk.toString("utf8"));
|
|
68
|
+
scheduled ??= setTimeout(pump, 0);
|
|
69
|
+
};
|
|
70
|
+
const onKeypress = (sequence, key) => {
|
|
71
|
+
const token = key?.sequence ?? sequence;
|
|
72
|
+
if (token === undefined)
|
|
73
|
+
return;
|
|
74
|
+
queued.push(token);
|
|
75
|
+
scheduled ??= setTimeout(pump, 0);
|
|
76
|
+
};
|
|
77
|
+
if (productionInput) {
|
|
78
|
+
input.on("data", onData);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
emitKeypressEvents(input);
|
|
82
|
+
input.on("keypress", onKeypress);
|
|
83
|
+
}
|
|
84
|
+
input.resume();
|
|
85
|
+
if (queued.length > 0)
|
|
86
|
+
scheduled = setTimeout(pump, 0);
|
|
87
|
+
return {
|
|
88
|
+
stream: stream,
|
|
89
|
+
stop() {
|
|
90
|
+
active = false;
|
|
91
|
+
clearTimeout(scheduled);
|
|
92
|
+
if (productionInput)
|
|
93
|
+
input.off("data", onData);
|
|
94
|
+
else
|
|
95
|
+
input.off("keypress", onKeypress);
|
|
96
|
+
input.pause();
|
|
97
|
+
stream.end();
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
// Three fields, one toggle, one action — a descriptor array, deliberately not a forms framework.
|
|
102
|
+
function buildRows(offerSkills) {
|
|
103
|
+
const rows = [
|
|
104
|
+
{ id: "driver", section: "Run", label: "Driver", desc: "auto: pick herdr when HERDR_ENV=1, else subprocess" },
|
|
105
|
+
{ id: "concurrency", section: "Run", label: "Concurrency", desc: "parallel task batteries per run" },
|
|
106
|
+
{
|
|
107
|
+
id: "visibility",
|
|
108
|
+
section: "Run",
|
|
109
|
+
label: "Visibility (gate LLMs)",
|
|
110
|
+
desc: "headless: judge/review/consult run silently · pane: visible agents",
|
|
111
|
+
},
|
|
112
|
+
];
|
|
113
|
+
if (offerSkills) {
|
|
114
|
+
rows.push({
|
|
115
|
+
id: "skills",
|
|
116
|
+
section: "Skills",
|
|
117
|
+
label: "Install agent skills",
|
|
118
|
+
desc: "copy the packaged /tkr skills + AGENTS.md guidance into this repo",
|
|
119
|
+
});
|
|
120
|
+
rows.push({
|
|
121
|
+
id: "docs",
|
|
122
|
+
section: "Skills",
|
|
123
|
+
label: "Append agent docs",
|
|
124
|
+
desc: "append the marked tickmarkr guidance block to AGENTS.md (and CLAUDE.md where applicable)",
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
rows.push({ id: "continue", label: "Continue →", desc: "save these preferences and continue to discovery" });
|
|
128
|
+
return rows;
|
|
129
|
+
}
|
|
130
|
+
const HINT_BAR = "Enter/Space to change · Tab to jump sections · ↑/↓ to move · Enter on Continue · Esc to quit";
|
|
131
|
+
export function InitWizardApp({ fields }) {
|
|
132
|
+
const { exit } = useApp();
|
|
133
|
+
const rows = buildRows(fields.offerSkills);
|
|
134
|
+
const sections = fields.offerSkills ? ["Run", "Skills"] : ["Run"];
|
|
135
|
+
const [cursor, setCursor] = useState(0);
|
|
136
|
+
const cursorRef = useRef(0);
|
|
137
|
+
const driverRef = useRef(fields.driver);
|
|
138
|
+
const visibilityRef = useRef(fields.visibilityLlm);
|
|
139
|
+
const skillsRef = useRef(fields.skillsDefault);
|
|
140
|
+
const docsRef = useRef(false);
|
|
141
|
+
const concurrencyTextRef = useRef(String(fields.concurrency));
|
|
142
|
+
const lastValidConcurrencyRef = useRef(fields.concurrency);
|
|
143
|
+
const typedRef = useRef(false);
|
|
144
|
+
const doneRef = useRef(false);
|
|
145
|
+
const [, setRevision] = useState(0);
|
|
146
|
+
const bump = () => setRevision((n) => n + 1);
|
|
147
|
+
// Leaving the row (or pressing Enter on it) is the commit point: a parse ≥ 1 becomes the new
|
|
148
|
+
// value, anything else — empty buffer, "0" — snaps back to the last valid integer, so the
|
|
149
|
+
// wizard can never emit a concurrency the config schema (positive int) would reject.
|
|
150
|
+
const commitConcurrency = () => {
|
|
151
|
+
typedRef.current = false;
|
|
152
|
+
const parsed = Number.parseInt(concurrencyTextRef.current, 10);
|
|
153
|
+
if (Number.isInteger(parsed) && parsed >= 1)
|
|
154
|
+
lastValidConcurrencyRef.current = parsed;
|
|
155
|
+
concurrencyTextRef.current = String(lastValidConcurrencyRef.current);
|
|
156
|
+
};
|
|
157
|
+
const finish = (result) => {
|
|
158
|
+
if (doneRef.current)
|
|
159
|
+
return;
|
|
160
|
+
doneRef.current = true;
|
|
161
|
+
exit(result);
|
|
162
|
+
};
|
|
163
|
+
useInput((input, key) => {
|
|
164
|
+
if (doneRef.current)
|
|
165
|
+
return;
|
|
166
|
+
if (key.escape || (key.ctrl && input === "c")) {
|
|
167
|
+
finish({ kind: "quit" });
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const moveTo = (next) => {
|
|
171
|
+
commitConcurrency();
|
|
172
|
+
cursorRef.current = next;
|
|
173
|
+
setCursor(next);
|
|
174
|
+
};
|
|
175
|
+
if (key.downArrow) {
|
|
176
|
+
moveTo(Math.min(cursorRef.current + 1, rows.length - 1));
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (key.upArrow) {
|
|
180
|
+
moveTo(Math.max(cursorRef.current - 1, 0));
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (key.tab) {
|
|
184
|
+
// Jump to the first row of the next section, wrapping — with a single section this lands home.
|
|
185
|
+
const starts = rows.flatMap((row, index) => row.section && rows[index - 1]?.section !== row.section ? [index] : []);
|
|
186
|
+
moveTo(starts.find((start) => start > cursorRef.current) ?? starts[0] ?? 0);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const row = rows[cursorRef.current];
|
|
190
|
+
if (row.id === "concurrency") {
|
|
191
|
+
if (!key.ctrl && !key.meta && /^[0-9]$/.test(input)) {
|
|
192
|
+
// First digit replaces the seeded value, later digits append — inline integer entry.
|
|
193
|
+
concurrencyTextRef.current = typedRef.current ? concurrencyTextRef.current + input : input;
|
|
194
|
+
typedRef.current = true;
|
|
195
|
+
bump();
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (key.backspace || key.delete) {
|
|
199
|
+
concurrencyTextRef.current = concurrencyTextRef.current.slice(0, -1);
|
|
200
|
+
typedRef.current = true;
|
|
201
|
+
bump();
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (key.return && row.id === "continue") {
|
|
206
|
+
commitConcurrency();
|
|
207
|
+
finish({
|
|
208
|
+
kind: "continue",
|
|
209
|
+
overlay: {
|
|
210
|
+
driver: driverRef.current,
|
|
211
|
+
concurrency: lastValidConcurrencyRef.current,
|
|
212
|
+
visibility: { llm: visibilityRef.current },
|
|
213
|
+
},
|
|
214
|
+
installSkills: fields.offerSkills ? skillsRef.current : false,
|
|
215
|
+
installDocs: fields.offerSkills ? docsRef.current : false,
|
|
216
|
+
});
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (key.return || input === " ") {
|
|
220
|
+
if (row.id === "driver")
|
|
221
|
+
driverRef.current = DRIVERS[(DRIVERS.indexOf(driverRef.current) + 1) % DRIVERS.length];
|
|
222
|
+
else if (row.id === "visibility") {
|
|
223
|
+
visibilityRef.current = VISIBILITY[(VISIBILITY.indexOf(visibilityRef.current) + 1) % VISIBILITY.length];
|
|
224
|
+
}
|
|
225
|
+
else if (row.id === "skills")
|
|
226
|
+
skillsRef.current = !skillsRef.current;
|
|
227
|
+
else if (row.id === "docs")
|
|
228
|
+
docsRef.current = !docsRef.current;
|
|
229
|
+
else if (row.id === "concurrency")
|
|
230
|
+
commitConcurrency();
|
|
231
|
+
bump();
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
const valueOf = (row) => {
|
|
235
|
+
switch (row.id) {
|
|
236
|
+
case "driver":
|
|
237
|
+
return driverRef.current;
|
|
238
|
+
case "concurrency":
|
|
239
|
+
return concurrencyTextRef.current;
|
|
240
|
+
case "visibility":
|
|
241
|
+
return visibilityRef.current;
|
|
242
|
+
case "skills":
|
|
243
|
+
return _jsx(ToggleMark, { active: skillsRef.current });
|
|
244
|
+
case "docs":
|
|
245
|
+
return _jsx(ToggleMark, { active: docsRef.current });
|
|
246
|
+
case "continue":
|
|
247
|
+
return "";
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
const currentSection = rows.slice(0, cursor + 1).reduce((section, row) => row.section ?? section, rows[0].section);
|
|
251
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "tickmarkr init" }), _jsxs(Text, { children: [_jsx(Text, { bold: true, children: "Preferences" }), _jsx(Text, { dimColor: true, children: " \u00B7 Discovery \u00B7 Fleet" })] }), _jsx(Text, { dimColor: true, children: "act 1 of 3 \u2014 run preferences for this repo" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Box, { flexDirection: "column", width: 10, marginRight: 2, children: sections.map((section) => (_jsx(Text, { bold: section === currentSection, dimColor: section !== currentSection, children: section }, section))) }), _jsx(Box, { flexDirection: "column", children: rows.map((row, index) => (_jsxs(Text, { bold: index === cursor, children: [index === cursor ? `${GLYPHS.pointer} ` : " ", row.id === "continue" ? row.label : row.label.padEnd(24), valueOf(row)] }, row.id))) })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: rows[cursor].desc }), _jsx(Text, { dimColor: true, children: HINT_BAR })] })] }));
|
|
252
|
+
}
|
|
253
|
+
export async function runInitWizardApp(opts) {
|
|
254
|
+
const bridgedInput = inkInput(opts.input, opts.initialInput ?? []);
|
|
255
|
+
const legacyOutput = typeof opts.output.on !== "function" || typeof opts.output.off !== "function";
|
|
256
|
+
const app = render(_jsx(InitWizardApp, { fields: opts.fields }), {
|
|
257
|
+
stdin: bridgedInput.stream,
|
|
258
|
+
stdout: inkOutput(opts.output),
|
|
259
|
+
exitOnCtrlC: false,
|
|
260
|
+
patchConsole: false,
|
|
261
|
+
// Injected test outputs collect one complete frame per keypress (same rationale as
|
|
262
|
+
// fleet-app.tsx): disable Ink's render throttling only for that facade.
|
|
263
|
+
debug: legacyOutput,
|
|
264
|
+
});
|
|
265
|
+
try {
|
|
266
|
+
return await app.waitUntilExit();
|
|
267
|
+
}
|
|
268
|
+
finally {
|
|
269
|
+
app.unmount();
|
|
270
|
+
bridgedInput.stop();
|
|
271
|
+
}
|
|
272
|
+
}
|