@0xtiby/toby 1.0.1 → 1.2.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/dist/cli.js +457 -165
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.tsx
|
|
4
4
|
import meow from "meow";
|
|
5
|
-
import { render
|
|
5
|
+
import { render } from "ink";
|
|
6
6
|
|
|
7
7
|
// src/commands/plan.tsx
|
|
8
8
|
import { useState as useState3, useEffect as useEffect2, useMemo as useMemo2 } from "react";
|
|
@@ -80,6 +80,7 @@ var LOCAL_TOBY_DIR = ".toby";
|
|
|
80
80
|
var DEFAULT_SPECS_DIR = "specs";
|
|
81
81
|
var STATUS_FILE = "status.json";
|
|
82
82
|
var CONFIG_FILE = "config.json";
|
|
83
|
+
var TRANSCRIPTS_DIR = "transcripts";
|
|
83
84
|
function getGlobalDir() {
|
|
84
85
|
return path.join(os.homedir(), GLOBAL_TOBY_DIR);
|
|
85
86
|
}
|
|
@@ -111,6 +112,220 @@ function ensureLocalDir(cwd) {
|
|
|
111
112
|
return dir;
|
|
112
113
|
}
|
|
113
114
|
|
|
115
|
+
// src/lib/help.ts
|
|
116
|
+
var SPEC_FLAGS = [
|
|
117
|
+
{ name: "--spec=<query>", description: "Target spec(s) by name, slug, number, or list" },
|
|
118
|
+
{ name: "--specs=<names>", description: "Alias for --spec" },
|
|
119
|
+
{ name: "--all", description: "Process all matching specs" },
|
|
120
|
+
{ name: "--iterations=<n>", description: "Override iteration count" },
|
|
121
|
+
{ name: "--verbose", description: "Show full CLI output" },
|
|
122
|
+
{ name: "--transcript", description: "Save session transcript to file" },
|
|
123
|
+
{ name: "--cli=<name>", description: "Override AI CLI (claude, codex, opencode)" },
|
|
124
|
+
{ name: "--session=<name>", description: "Name the session for branch/PR naming" }
|
|
125
|
+
];
|
|
126
|
+
var commandHelp = {
|
|
127
|
+
plan: {
|
|
128
|
+
summary: "Plan specs with AI loop engine",
|
|
129
|
+
usage: ["$ toby plan [options]"],
|
|
130
|
+
flags: SPEC_FLAGS,
|
|
131
|
+
examples: [
|
|
132
|
+
{
|
|
133
|
+
command: "toby plan --spec=auth --cli=claude --session=auth-feature",
|
|
134
|
+
description: 'Plan the auth spec using Claude, naming the session "auth-feature"'
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
command: "toby plan --spec=auth,payments --iterations=3 --verbose",
|
|
138
|
+
description: "Plan auth and payments specs with 3 iterations, showing full output"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
command: "toby plan --all --transcript",
|
|
142
|
+
description: "Plan all pending specs and save a transcript of the session"
|
|
143
|
+
}
|
|
144
|
+
]
|
|
145
|
+
},
|
|
146
|
+
build: {
|
|
147
|
+
summary: "Build tasks one-per-spawn with AI",
|
|
148
|
+
usage: ["$ toby build [options]"],
|
|
149
|
+
flags: SPEC_FLAGS,
|
|
150
|
+
examples: [
|
|
151
|
+
{
|
|
152
|
+
command: "toby build --spec=auth --cli=claude --session=auth-feature",
|
|
153
|
+
description: 'Build the auth spec using Claude, resuming "auth-feature"'
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
command: "toby build --all --iterations=5 --transcript",
|
|
157
|
+
description: "Build all planned specs with up to 5 iterations, saving transcripts"
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
command: "toby build --spec=2 --verbose",
|
|
161
|
+
description: "Build spec #2 with full CLI output visible"
|
|
162
|
+
}
|
|
163
|
+
]
|
|
164
|
+
},
|
|
165
|
+
init: {
|
|
166
|
+
summary: "Initialize toby in current project",
|
|
167
|
+
usage: ["$ toby init [options]"],
|
|
168
|
+
flags: [
|
|
169
|
+
{
|
|
170
|
+
name: "--plan-cli=<name>",
|
|
171
|
+
description: "Set plan CLI (claude, codex, opencode)"
|
|
172
|
+
},
|
|
173
|
+
{ name: "--plan-model=<id>", description: "Set plan model" },
|
|
174
|
+
{
|
|
175
|
+
name: "--build-cli=<name>",
|
|
176
|
+
description: "Set build CLI (claude, codex, opencode)"
|
|
177
|
+
},
|
|
178
|
+
{ name: "--build-model=<id>", description: "Set build model" },
|
|
179
|
+
{ name: "--specs-dir=<path>", description: "Set specs directory" },
|
|
180
|
+
{
|
|
181
|
+
name: "--verbose",
|
|
182
|
+
description: "Enable verbose output in config"
|
|
183
|
+
}
|
|
184
|
+
],
|
|
185
|
+
examples: [
|
|
186
|
+
{
|
|
187
|
+
command: "toby init",
|
|
188
|
+
description: "Launch the interactive setup wizard"
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
command: "toby init --plan-cli=claude --build-cli=claude --specs-dir=specs",
|
|
192
|
+
description: "Non-interactive init with required flags (for CI/agents)"
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
command: "toby init --plan-cli=codex --build-cli=codex --specs-dir=specs --verbose",
|
|
196
|
+
description: "Initialize with Codex for both phases, verbose enabled"
|
|
197
|
+
}
|
|
198
|
+
]
|
|
199
|
+
},
|
|
200
|
+
status: {
|
|
201
|
+
summary: "Show project status",
|
|
202
|
+
usage: ["$ toby status [options]"],
|
|
203
|
+
flags: [
|
|
204
|
+
{
|
|
205
|
+
name: "--spec=<query>",
|
|
206
|
+
description: "Show status for a specific spec by name, slug, or number"
|
|
207
|
+
}
|
|
208
|
+
],
|
|
209
|
+
examples: [
|
|
210
|
+
{
|
|
211
|
+
command: "toby status",
|
|
212
|
+
description: "Show status overview for all specs in the project"
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
command: "toby status --spec=auth",
|
|
216
|
+
description: "Show detailed status for the auth spec"
|
|
217
|
+
}
|
|
218
|
+
]
|
|
219
|
+
},
|
|
220
|
+
config: {
|
|
221
|
+
summary: "Manage configuration",
|
|
222
|
+
usage: [
|
|
223
|
+
"$ toby config Interactive config editor",
|
|
224
|
+
"$ toby config get <key> Show a config value (dot-notation)",
|
|
225
|
+
"$ toby config set <key> <value> Set a config value",
|
|
226
|
+
"$ toby config set <k>=<v> [<k>=<v>...] Batch set values"
|
|
227
|
+
],
|
|
228
|
+
flags: [],
|
|
229
|
+
examples: [
|
|
230
|
+
{
|
|
231
|
+
command: "toby config",
|
|
232
|
+
description: "Open the interactive config editor"
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
command: "toby config get plan.cli",
|
|
236
|
+
description: "Show the configured plan CLI"
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
command: "toby config set plan.cli=claude build.iterations=5",
|
|
240
|
+
description: "Batch set plan CLI to claude and build iterations to 5"
|
|
241
|
+
}
|
|
242
|
+
]
|
|
243
|
+
},
|
|
244
|
+
clean: {
|
|
245
|
+
summary: "Delete session transcripts",
|
|
246
|
+
usage: ["$ toby clean [options]"],
|
|
247
|
+
flags: [
|
|
248
|
+
{
|
|
249
|
+
name: "--force",
|
|
250
|
+
description: "Skip confirmation prompt (required in non-TTY)"
|
|
251
|
+
}
|
|
252
|
+
],
|
|
253
|
+
examples: [
|
|
254
|
+
{
|
|
255
|
+
command: "toby clean",
|
|
256
|
+
description: "Delete all transcripts with confirmation prompt"
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
command: "toby clean --force",
|
|
260
|
+
description: "Delete all transcripts without confirmation (for CI/agents)"
|
|
261
|
+
}
|
|
262
|
+
]
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
function formatCommandHelp(command2, help) {
|
|
266
|
+
const lines = [];
|
|
267
|
+
lines.push(`toby ${command2} \u2014 ${help.summary}`);
|
|
268
|
+
lines.push("");
|
|
269
|
+
lines.push("Usage");
|
|
270
|
+
for (const usage of help.usage) {
|
|
271
|
+
lines.push(` ${usage}`);
|
|
272
|
+
}
|
|
273
|
+
if (help.flags.length > 0) {
|
|
274
|
+
lines.push("");
|
|
275
|
+
lines.push("Options");
|
|
276
|
+
const maxName = Math.max(...help.flags.map((f) => f.name.length));
|
|
277
|
+
for (const flag of help.flags) {
|
|
278
|
+
lines.push(` ${flag.name.padEnd(maxName)} ${flag.description}`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
lines.push("");
|
|
282
|
+
lines.push("Examples");
|
|
283
|
+
const exampleBlocks = help.examples.map(
|
|
284
|
+
(ex) => ` $ ${ex.command}
|
|
285
|
+
${ex.description}`
|
|
286
|
+
);
|
|
287
|
+
lines.push(exampleBlocks.join("\n\n"));
|
|
288
|
+
lines.push("");
|
|
289
|
+
return lines.join("\n");
|
|
290
|
+
}
|
|
291
|
+
function formatErrorWithHint(message, validValues, example) {
|
|
292
|
+
const lines = [];
|
|
293
|
+
if (validValues) {
|
|
294
|
+
lines.push(`\u2717 ${message}. Valid options: ${validValues.join(", ")}`);
|
|
295
|
+
} else {
|
|
296
|
+
lines.push(`\u2717 ${message}`);
|
|
297
|
+
}
|
|
298
|
+
if (example) {
|
|
299
|
+
lines.push("");
|
|
300
|
+
lines.push("Example:");
|
|
301
|
+
lines.push(` $ ${example}`);
|
|
302
|
+
}
|
|
303
|
+
lines.push("");
|
|
304
|
+
return lines.join("\n");
|
|
305
|
+
}
|
|
306
|
+
function formatGlobalHelp(version2) {
|
|
307
|
+
const maxCmd = Math.max(
|
|
308
|
+
...Object.keys(commandHelp).map((c) => c.length)
|
|
309
|
+
);
|
|
310
|
+
const cmdLines = Object.entries(commandHelp).map(([name, h]) => ` ${name.padEnd(maxCmd)} ${h.summary}`).join("\n");
|
|
311
|
+
return [
|
|
312
|
+
`toby v${version2} \u2014 AI-assisted development loop engine`,
|
|
313
|
+
"",
|
|
314
|
+
"Usage",
|
|
315
|
+
" $ toby <command> [options]",
|
|
316
|
+
"",
|
|
317
|
+
"Commands",
|
|
318
|
+
cmdLines,
|
|
319
|
+
"",
|
|
320
|
+
"Options",
|
|
321
|
+
" --help Show help (use with a command for details)",
|
|
322
|
+
" --version Show version",
|
|
323
|
+
"",
|
|
324
|
+
"Run toby <command> --help for command-specific options and examples.",
|
|
325
|
+
""
|
|
326
|
+
].join("\n");
|
|
327
|
+
}
|
|
328
|
+
|
|
114
329
|
// src/lib/config.ts
|
|
115
330
|
function readConfigFile(filePath) {
|
|
116
331
|
try {
|
|
@@ -153,7 +368,13 @@ function writeConfig(config, filePath) {
|
|
|
153
368
|
}
|
|
154
369
|
function validateCliName(cli2) {
|
|
155
370
|
if (cli2 && !CLI_NAMES.includes(cli2)) {
|
|
156
|
-
throw new Error(
|
|
371
|
+
throw new Error(
|
|
372
|
+
formatErrorWithHint(
|
|
373
|
+
`Unknown CLI: ${cli2}`,
|
|
374
|
+
[...CLI_NAMES],
|
|
375
|
+
"toby plan --cli=claude --spec=auth"
|
|
376
|
+
)
|
|
377
|
+
);
|
|
157
378
|
}
|
|
158
379
|
}
|
|
159
380
|
function resolveCommandConfig(config, command2, flags2 = {}) {
|
|
@@ -567,7 +788,6 @@ var AbortError = class extends Error {
|
|
|
567
788
|
// src/lib/transcript.ts
|
|
568
789
|
import fs6 from "fs";
|
|
569
790
|
import path6 from "path";
|
|
570
|
-
var TRANSCRIPTS_DIR = "transcripts";
|
|
571
791
|
function formatTimestamp() {
|
|
572
792
|
const now = /* @__PURE__ */ new Date();
|
|
573
793
|
const pad2 = (n, len = 2) => String(n).padStart(len, "0");
|
|
@@ -2582,13 +2802,127 @@ Usage: toby config set <key> <value>` });
|
|
|
2582
2802
|
] });
|
|
2583
2803
|
}
|
|
2584
2804
|
|
|
2805
|
+
// src/commands/clean.tsx
|
|
2806
|
+
import { useState as useState8, useEffect as useEffect7, useMemo as useMemo4 } from "react";
|
|
2807
|
+
import { Text as Text9, useApp as useApp4, useInput as useInput2 } from "ink";
|
|
2808
|
+
|
|
2809
|
+
// src/lib/clean.ts
|
|
2810
|
+
import path9 from "path";
|
|
2811
|
+
import fs10 from "fs";
|
|
2812
|
+
function listTranscripts(cwd) {
|
|
2813
|
+
const dir = path9.join(getLocalDir(cwd), TRANSCRIPTS_DIR);
|
|
2814
|
+
let entries;
|
|
2815
|
+
try {
|
|
2816
|
+
entries = fs10.readdirSync(dir, { withFileTypes: true });
|
|
2817
|
+
} catch {
|
|
2818
|
+
return [];
|
|
2819
|
+
}
|
|
2820
|
+
return entries.filter((e) => e.isFile()).map((e) => path9.join(dir, e.name));
|
|
2821
|
+
}
|
|
2822
|
+
function deleteTranscripts(files) {
|
|
2823
|
+
let deleted = 0;
|
|
2824
|
+
for (const file of files) {
|
|
2825
|
+
try {
|
|
2826
|
+
fs10.unlinkSync(file);
|
|
2827
|
+
deleted++;
|
|
2828
|
+
} catch {
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2831
|
+
return deleted;
|
|
2832
|
+
}
|
|
2833
|
+
function executeClean(cwd) {
|
|
2834
|
+
const files = listTranscripts(cwd);
|
|
2835
|
+
if (files.length === 0) {
|
|
2836
|
+
return { deleted: 0, failed: 0, total: 0 };
|
|
2837
|
+
}
|
|
2838
|
+
const deleted = deleteTranscripts(files);
|
|
2839
|
+
return { deleted, failed: files.length - deleted, total: files.length };
|
|
2840
|
+
}
|
|
2841
|
+
|
|
2842
|
+
// src/commands/clean.tsx
|
|
2843
|
+
import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2844
|
+
function computeInitial(force) {
|
|
2845
|
+
const files = listTranscripts();
|
|
2846
|
+
if (files.length === 0) {
|
|
2847
|
+
return { phase: "empty", fileCount: 0, result: null };
|
|
2848
|
+
}
|
|
2849
|
+
if (!process.stdin.isTTY && !force) {
|
|
2850
|
+
return { phase: "error", fileCount: files.length, result: null };
|
|
2851
|
+
}
|
|
2852
|
+
if (force) {
|
|
2853
|
+
const r = executeClean();
|
|
2854
|
+
return { phase: "done", fileCount: files.length, result: r };
|
|
2855
|
+
}
|
|
2856
|
+
return { phase: "confirming", fileCount: files.length, result: null };
|
|
2857
|
+
}
|
|
2858
|
+
function Clean({ force }) {
|
|
2859
|
+
const { exit } = useApp4();
|
|
2860
|
+
const initial = useMemo4(() => computeInitial(force), [force]);
|
|
2861
|
+
useEffect7(() => {
|
|
2862
|
+
if (initial.phase === "error") {
|
|
2863
|
+
process.exitCode = 1;
|
|
2864
|
+
}
|
|
2865
|
+
}, [initial.phase]);
|
|
2866
|
+
const [phase, setPhase] = useState8(initial.phase);
|
|
2867
|
+
const [result, setResult] = useState8(initial.result);
|
|
2868
|
+
useEffect7(() => {
|
|
2869
|
+
if (phase === "empty" || phase === "done" || phase === "cancelled" || phase === "error") {
|
|
2870
|
+
const timer = setTimeout(() => exit(), 100);
|
|
2871
|
+
return () => clearTimeout(timer);
|
|
2872
|
+
}
|
|
2873
|
+
}, [phase, exit]);
|
|
2874
|
+
useInput2((input, key) => {
|
|
2875
|
+
if (phase !== "confirming") return;
|
|
2876
|
+
if (input === "y" || key.return) {
|
|
2877
|
+
const r = executeClean();
|
|
2878
|
+
setResult(r);
|
|
2879
|
+
setPhase("done");
|
|
2880
|
+
} else if (input === "n" || key.escape) {
|
|
2881
|
+
setPhase("cancelled");
|
|
2882
|
+
}
|
|
2883
|
+
});
|
|
2884
|
+
if (phase === "error") {
|
|
2885
|
+
return /* @__PURE__ */ jsx9(Text9, { color: "red", children: "Error: Use --force to clean transcripts in non-interactive mode." });
|
|
2886
|
+
}
|
|
2887
|
+
if (phase === "empty") {
|
|
2888
|
+
return /* @__PURE__ */ jsx9(Text9, { children: "No transcripts to clean." });
|
|
2889
|
+
}
|
|
2890
|
+
if (phase === "confirming") {
|
|
2891
|
+
return /* @__PURE__ */ jsxs8(Text9, { children: [
|
|
2892
|
+
"Found ",
|
|
2893
|
+
initial.fileCount,
|
|
2894
|
+
" transcript files. Delete all? [Y/n]"
|
|
2895
|
+
] });
|
|
2896
|
+
}
|
|
2897
|
+
if (phase === "done" && result) {
|
|
2898
|
+
if (result.failed > 0) {
|
|
2899
|
+
return /* @__PURE__ */ jsxs8(Text9, { children: [
|
|
2900
|
+
"Deleted ",
|
|
2901
|
+
result.deleted,
|
|
2902
|
+
" transcript files. Failed to delete ",
|
|
2903
|
+
result.failed,
|
|
2904
|
+
" files."
|
|
2905
|
+
] });
|
|
2906
|
+
}
|
|
2907
|
+
return /* @__PURE__ */ jsxs8(Text9, { children: [
|
|
2908
|
+
"Deleted ",
|
|
2909
|
+
result.deleted,
|
|
2910
|
+
" transcript files."
|
|
2911
|
+
] });
|
|
2912
|
+
}
|
|
2913
|
+
if (phase === "cancelled") {
|
|
2914
|
+
return /* @__PURE__ */ jsx9(Text9, { children: "Clean cancelled." });
|
|
2915
|
+
}
|
|
2916
|
+
return null;
|
|
2917
|
+
}
|
|
2918
|
+
|
|
2585
2919
|
// src/components/Welcome.tsx
|
|
2586
|
-
import { useState as
|
|
2587
|
-
import { Box as Box12, Text as
|
|
2920
|
+
import { useState as useState10, useEffect as useEffect9, useMemo as useMemo6 } from "react";
|
|
2921
|
+
import { Box as Box12, Text as Text13, useApp as useApp5, useStdout as useStdout2 } from "ink";
|
|
2588
2922
|
|
|
2589
2923
|
// src/components/hamster/HamsterWheel.tsx
|
|
2590
|
-
import { useState as
|
|
2591
|
-
import { Box as Box9, Text as
|
|
2924
|
+
import { useState as useState9, useEffect as useEffect8, useMemo as useMemo5 } from "react";
|
|
2925
|
+
import { Box as Box9, Text as Text10, useStdout } from "ink";
|
|
2592
2926
|
|
|
2593
2927
|
// src/components/hamster/palette.ts
|
|
2594
2928
|
var PALETTE = {
|
|
@@ -2747,7 +3081,7 @@ function generateWheelPixels(cx, cy, outerRadius, innerRadius, spokeAngle, aspec
|
|
|
2747
3081
|
}
|
|
2748
3082
|
|
|
2749
3083
|
// src/components/hamster/HamsterWheel.tsx
|
|
2750
|
-
import { jsx as
|
|
3084
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
2751
3085
|
function buildGrid(width, height) {
|
|
2752
3086
|
return Array.from(
|
|
2753
3087
|
{ length: height },
|
|
@@ -2830,9 +3164,9 @@ function HamsterWheel({
|
|
|
2830
3164
|
heightProp,
|
|
2831
3165
|
columns
|
|
2832
3166
|
);
|
|
2833
|
-
const [frame, setFrame] =
|
|
2834
|
-
const [spokeAngle, setSpokeAngle] =
|
|
2835
|
-
|
|
3167
|
+
const [frame, setFrame] = useState9(0);
|
|
3168
|
+
const [spokeAngle, setSpokeAngle] = useState9(0);
|
|
3169
|
+
useEffect8(() => {
|
|
2836
3170
|
if (speed === 0 || isStatic) return;
|
|
2837
3171
|
const interval = computeInterval(HAMSTER_BASE_INTERVAL, speed);
|
|
2838
3172
|
const id = setInterval(() => {
|
|
@@ -2840,7 +3174,7 @@ function HamsterWheel({
|
|
|
2840
3174
|
}, interval);
|
|
2841
3175
|
return () => clearInterval(id);
|
|
2842
3176
|
}, [speed, isStatic]);
|
|
2843
|
-
|
|
3177
|
+
useEffect8(() => {
|
|
2844
3178
|
if (speed === 0 || isStatic) return;
|
|
2845
3179
|
const interval = computeInterval(WHEEL_BASE_INTERVAL, speed);
|
|
2846
3180
|
const id = setInterval(() => {
|
|
@@ -2848,7 +3182,7 @@ function HamsterWheel({
|
|
|
2848
3182
|
}, interval);
|
|
2849
3183
|
return () => clearInterval(id);
|
|
2850
3184
|
}, [speed, isStatic]);
|
|
2851
|
-
const renderedRows =
|
|
3185
|
+
const renderedRows = useMemo5(() => {
|
|
2852
3186
|
if (isStatic) return [];
|
|
2853
3187
|
const grid = buildGrid(resolvedWidth, resolvedHeight);
|
|
2854
3188
|
const { cx, cy, outerRadius, innerRadius } = computeWheelGeometry(
|
|
@@ -2880,43 +3214,43 @@ function HamsterWheel({
|
|
|
2880
3214
|
return buildColorRuns(grid, resolvedWidth, resolvedHeight);
|
|
2881
3215
|
}, [resolvedWidth, resolvedHeight, frame, spokeAngle, isStatic]);
|
|
2882
3216
|
if (isStatic) {
|
|
2883
|
-
return /* @__PURE__ */
|
|
3217
|
+
return /* @__PURE__ */ jsx10(Text10, { children: " \u{1F439} toby" });
|
|
2884
3218
|
}
|
|
2885
|
-
return /* @__PURE__ */
|
|
3219
|
+
return /* @__PURE__ */ jsx10(Box9, { flexDirection: "column", children: renderedRows.map((runs, y) => /* @__PURE__ */ jsx10(Text10, { children: runs.map((run, i) => /* @__PURE__ */ jsx10(Text10, { color: run.fg, backgroundColor: run.bg, children: run.char.repeat(run.length) }, i)) }, y)) });
|
|
2886
3220
|
}
|
|
2887
3221
|
|
|
2888
3222
|
// src/components/InfoPanel.tsx
|
|
2889
|
-
import { Box as Box10, Text as
|
|
2890
|
-
import { jsx as
|
|
3223
|
+
import { Box as Box10, Text as Text11 } from "ink";
|
|
3224
|
+
import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2891
3225
|
var formatTokens = (n) => new Intl.NumberFormat().format(n);
|
|
2892
3226
|
function StatRow({ label, value }) {
|
|
2893
|
-
return /* @__PURE__ */
|
|
2894
|
-
/* @__PURE__ */
|
|
3227
|
+
return /* @__PURE__ */ jsxs9(Box10, { children: [
|
|
3228
|
+
/* @__PURE__ */ jsxs9(Text11, { dimColor: true, children: [
|
|
2895
3229
|
String(label).padStart(9),
|
|
2896
3230
|
" "
|
|
2897
3231
|
] }),
|
|
2898
|
-
/* @__PURE__ */
|
|
3232
|
+
/* @__PURE__ */ jsx11(Text11, { children: value })
|
|
2899
3233
|
] });
|
|
2900
3234
|
}
|
|
2901
3235
|
function InfoPanel({ version: version2, stats }) {
|
|
2902
|
-
return /* @__PURE__ */
|
|
2903
|
-
/* @__PURE__ */
|
|
3236
|
+
return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", children: [
|
|
3237
|
+
/* @__PURE__ */ jsxs9(Text11, { bold: true, color: "#f0a030", children: [
|
|
2904
3238
|
"toby v",
|
|
2905
3239
|
version2
|
|
2906
3240
|
] }),
|
|
2907
|
-
stats !== null && /* @__PURE__ */
|
|
2908
|
-
/* @__PURE__ */
|
|
2909
|
-
/* @__PURE__ */
|
|
2910
|
-
/* @__PURE__ */
|
|
2911
|
-
/* @__PURE__ */
|
|
3241
|
+
stats !== null && /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", marginTop: 1, children: [
|
|
3242
|
+
/* @__PURE__ */ jsx11(StatRow, { label: "Specs", value: stats.totalSpecs }),
|
|
3243
|
+
/* @__PURE__ */ jsx11(StatRow, { label: "Planned", value: stats.planned }),
|
|
3244
|
+
/* @__PURE__ */ jsx11(StatRow, { label: "Done", value: stats.done }),
|
|
3245
|
+
/* @__PURE__ */ jsx11(StatRow, { label: "Tokens", value: formatTokens(stats.totalTokens) })
|
|
2912
3246
|
] })
|
|
2913
3247
|
] });
|
|
2914
3248
|
}
|
|
2915
3249
|
|
|
2916
3250
|
// src/components/MainMenu.tsx
|
|
2917
|
-
import { Text as
|
|
3251
|
+
import { Text as Text12, Box as Box11 } from "ink";
|
|
2918
3252
|
import SelectInput3 from "ink-select-input";
|
|
2919
|
-
import { jsx as
|
|
3253
|
+
import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
2920
3254
|
var MENU_ITEMS = [
|
|
2921
3255
|
{ label: "plan", value: "plan", description: "Plan specs with AI loop engine" },
|
|
2922
3256
|
{ label: "build", value: "build", description: "Build tasks one-per-spawn with AI" },
|
|
@@ -2924,16 +3258,16 @@ var MENU_ITEMS = [
|
|
|
2924
3258
|
{ label: "config", value: "config", description: "Manage configuration" }
|
|
2925
3259
|
];
|
|
2926
3260
|
function MenuItem({ isSelected = false, label, description }) {
|
|
2927
|
-
return /* @__PURE__ */
|
|
2928
|
-
/* @__PURE__ */
|
|
2929
|
-
description && /* @__PURE__ */
|
|
3261
|
+
return /* @__PURE__ */ jsxs10(Box11, { children: [
|
|
3262
|
+
/* @__PURE__ */ jsx12(Text12, { color: isSelected ? "blue" : void 0, children: label.padEnd(10) }),
|
|
3263
|
+
description && /* @__PURE__ */ jsxs10(Text12, { dimColor: true, children: [
|
|
2930
3264
|
"\u2014 ",
|
|
2931
3265
|
description
|
|
2932
3266
|
] })
|
|
2933
3267
|
] });
|
|
2934
3268
|
}
|
|
2935
3269
|
function MainMenu({ onSelect }) {
|
|
2936
|
-
return /* @__PURE__ */
|
|
3270
|
+
return /* @__PURE__ */ jsx12(Box11, { flexDirection: "column", children: /* @__PURE__ */ jsx12(
|
|
2937
3271
|
SelectInput3,
|
|
2938
3272
|
{
|
|
2939
3273
|
items: MENU_ITEMS,
|
|
@@ -2944,9 +3278,9 @@ function MainMenu({ onSelect }) {
|
|
|
2944
3278
|
}
|
|
2945
3279
|
|
|
2946
3280
|
// src/lib/stats.ts
|
|
2947
|
-
import
|
|
3281
|
+
import fs11 from "fs";
|
|
2948
3282
|
function computeProjectStats(cwd) {
|
|
2949
|
-
if (!
|
|
3283
|
+
if (!fs11.existsSync(getLocalDir(cwd))) {
|
|
2950
3284
|
return null;
|
|
2951
3285
|
}
|
|
2952
3286
|
let statusData;
|
|
@@ -2988,157 +3322,99 @@ function computeProjectStats(cwd) {
|
|
|
2988
3322
|
}
|
|
2989
3323
|
|
|
2990
3324
|
// src/components/Welcome.tsx
|
|
2991
|
-
import { jsx as
|
|
3325
|
+
import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
2992
3326
|
var NARROW_THRESHOLD = 60;
|
|
2993
3327
|
function Welcome({ version: version2 }) {
|
|
2994
|
-
const { exit } =
|
|
3328
|
+
const { exit } = useApp5();
|
|
2995
3329
|
const { stdout } = useStdout2();
|
|
2996
|
-
const [selectedCommand, setSelectedCommand] =
|
|
2997
|
-
const stats =
|
|
3330
|
+
const [selectedCommand, setSelectedCommand] = useState10(null);
|
|
3331
|
+
const stats = useMemo6(() => computeProjectStats(), []);
|
|
2998
3332
|
const isNarrow = (stdout.columns ?? 80) < NARROW_THRESHOLD;
|
|
2999
|
-
|
|
3333
|
+
useEffect9(() => {
|
|
3000
3334
|
if (selectedCommand === "status") {
|
|
3001
3335
|
const timer = setTimeout(() => exit(), 0);
|
|
3002
3336
|
return () => clearTimeout(timer);
|
|
3003
3337
|
}
|
|
3004
3338
|
}, [selectedCommand, exit]);
|
|
3005
3339
|
if (selectedCommand === "plan") {
|
|
3006
|
-
return /* @__PURE__ */
|
|
3340
|
+
return /* @__PURE__ */ jsx13(Plan, {});
|
|
3007
3341
|
}
|
|
3008
3342
|
if (selectedCommand === "build") {
|
|
3009
|
-
return /* @__PURE__ */
|
|
3343
|
+
return /* @__PURE__ */ jsx13(Build, {});
|
|
3010
3344
|
}
|
|
3011
3345
|
if (selectedCommand === "status") {
|
|
3012
|
-
return /* @__PURE__ */
|
|
3346
|
+
return /* @__PURE__ */ jsx13(Status, { version: version2 });
|
|
3013
3347
|
}
|
|
3014
3348
|
if (selectedCommand === "config") {
|
|
3015
|
-
return /* @__PURE__ */
|
|
3349
|
+
return /* @__PURE__ */ jsx13(ConfigEditor, { version: version2 });
|
|
3016
3350
|
}
|
|
3017
|
-
return /* @__PURE__ */
|
|
3018
|
-
isNarrow ? /* @__PURE__ */
|
|
3019
|
-
/* @__PURE__ */
|
|
3351
|
+
return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", gap: 1, children: [
|
|
3352
|
+
isNarrow ? /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", children: [
|
|
3353
|
+
/* @__PURE__ */ jsxs11(Text13, { bold: true, color: "#f0a030", children: [
|
|
3020
3354
|
"\u{1F439} toby v",
|
|
3021
3355
|
version2
|
|
3022
3356
|
] }),
|
|
3023
|
-
stats !== null && /* @__PURE__ */
|
|
3024
|
-
/* @__PURE__ */
|
|
3025
|
-
/* @__PURE__ */
|
|
3026
|
-
/* @__PURE__ */
|
|
3027
|
-
/* @__PURE__ */
|
|
3028
|
-
/* @__PURE__ */
|
|
3029
|
-
/* @__PURE__ */
|
|
3030
|
-
/* @__PURE__ */
|
|
3031
|
-
/* @__PURE__ */
|
|
3357
|
+
stats !== null && /* @__PURE__ */ jsxs11(Text13, { children: [
|
|
3358
|
+
/* @__PURE__ */ jsx13(Text13, { dimColor: true, children: "Specs: " }),
|
|
3359
|
+
/* @__PURE__ */ jsx13(Text13, { children: stats.totalSpecs }),
|
|
3360
|
+
/* @__PURE__ */ jsx13(Text13, { dimColor: true, children: " \xB7 Planned: " }),
|
|
3361
|
+
/* @__PURE__ */ jsx13(Text13, { children: stats.planned }),
|
|
3362
|
+
/* @__PURE__ */ jsx13(Text13, { dimColor: true, children: " \xB7 Done: " }),
|
|
3363
|
+
/* @__PURE__ */ jsx13(Text13, { children: stats.done }),
|
|
3364
|
+
/* @__PURE__ */ jsx13(Text13, { dimColor: true, children: " \xB7 Tokens: " }),
|
|
3365
|
+
/* @__PURE__ */ jsx13(Text13, { children: formatTokens(stats.totalTokens) })
|
|
3032
3366
|
] })
|
|
3033
|
-
] }) : /* @__PURE__ */
|
|
3034
|
-
/* @__PURE__ */
|
|
3035
|
-
/* @__PURE__ */
|
|
3367
|
+
] }) : /* @__PURE__ */ jsxs11(Box12, { flexDirection: "row", gap: 2, children: [
|
|
3368
|
+
/* @__PURE__ */ jsx13(HamsterWheel, {}),
|
|
3369
|
+
/* @__PURE__ */ jsx13(InfoPanel, { version: version2, stats })
|
|
3036
3370
|
] }),
|
|
3037
|
-
/* @__PURE__ */
|
|
3371
|
+
/* @__PURE__ */ jsx13(MainMenu, { onSelect: setSelectedCommand })
|
|
3038
3372
|
] });
|
|
3039
3373
|
}
|
|
3040
3374
|
|
|
3041
|
-
// src/cli.
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
--spec=<name> Single spec or comma-separated (e.g. --spec=auth,payments)
|
|
3062
|
-
--specs=<names> Alias for --spec` });
|
|
3063
|
-
}
|
|
3064
|
-
function UnknownCommand({ command: command2 }) {
|
|
3065
|
-
return /* @__PURE__ */ jsx13(Text13, { color: "red", children: `Unknown command: ${command2}
|
|
3066
|
-
Run "toby --help" for available commands.` });
|
|
3067
|
-
}
|
|
3068
|
-
var cli = meow(
|
|
3069
|
-
`
|
|
3070
|
-
Usage
|
|
3071
|
-
$ toby <command> [options]
|
|
3072
|
-
|
|
3073
|
-
Commands
|
|
3074
|
-
plan Plan specs with AI loop engine
|
|
3075
|
-
build Build tasks one-per-spawn with AI
|
|
3076
|
-
init Initialize toby in current project
|
|
3077
|
-
status Show project status
|
|
3078
|
-
config Manage configuration
|
|
3079
|
-
|
|
3080
|
-
Plan Options
|
|
3081
|
-
--spec=<query> Target spec(s) by name, slug, number, or comma-separated list
|
|
3082
|
-
--specs=<names> Alias for --spec with comma-separated specs
|
|
3083
|
-
--all Plan all pending specs
|
|
3084
|
-
--iterations=<n> Override iteration count
|
|
3085
|
-
--verbose Show full CLI output
|
|
3086
|
-
--transcript Save session transcript to file
|
|
3087
|
-
--cli=<name> Override AI CLI (claude, codex, opencode)
|
|
3088
|
-
--session=<name> Name the session for branch/PR naming
|
|
3089
|
-
|
|
3090
|
-
Build Options
|
|
3091
|
-
--spec=<query> Target spec(s) by name, slug, number, or comma-separated list
|
|
3092
|
-
--specs=<names> Alias for --spec with comma-separated specs
|
|
3093
|
-
--all Build all planned specs in order
|
|
3094
|
-
--iterations=<n> Override max iteration count
|
|
3095
|
-
--verbose Show full CLI output
|
|
3096
|
-
--transcript Save session transcript to file
|
|
3097
|
-
--cli=<name> Override AI CLI (claude, codex, opencode)
|
|
3098
|
-
--session=<name> Name the session for branch/PR naming
|
|
3099
|
-
|
|
3100
|
-
Status Options
|
|
3101
|
-
--spec=<query> Show status for a spec by name, slug, or number
|
|
3102
|
-
|
|
3103
|
-
Init Options
|
|
3104
|
-
--plan-cli=<name> Set plan CLI (claude, codex, opencode)
|
|
3105
|
-
--plan-model=<id> Set plan model
|
|
3106
|
-
--build-cli=<name> Set build CLI (claude, codex, opencode)
|
|
3107
|
-
--build-model=<id> Set build model
|
|
3108
|
-
--specs-dir=<path> Set specs directory
|
|
3109
|
-
--verbose Enable verbose output in config
|
|
3375
|
+
// src/lib/cli-meta.ts
|
|
3376
|
+
var COMMAND_NAMES = Object.keys(commandHelp);
|
|
3377
|
+
var MEOW_FLAGS = {
|
|
3378
|
+
help: { type: "boolean", default: false },
|
|
3379
|
+
spec: { type: "string" },
|
|
3380
|
+
specs: { type: "string" },
|
|
3381
|
+
all: { type: "boolean", default: false },
|
|
3382
|
+
iterations: { type: "number" },
|
|
3383
|
+
verbose: { type: "boolean", default: false },
|
|
3384
|
+
transcript: { type: "boolean" },
|
|
3385
|
+
cli: { type: "string" },
|
|
3386
|
+
planCli: { type: "string" },
|
|
3387
|
+
planModel: { type: "string" },
|
|
3388
|
+
buildCli: { type: "string" },
|
|
3389
|
+
buildModel: { type: "string" },
|
|
3390
|
+
specsDir: { type: "string" },
|
|
3391
|
+
session: { type: "string" },
|
|
3392
|
+
force: { type: "boolean", default: false }
|
|
3393
|
+
};
|
|
3394
|
+
var MEOW_FLAG_NAMES = Object.keys(MEOW_FLAGS);
|
|
3110
3395
|
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
`,
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
planCli: { type: "string" },
|
|
3128
|
-
planModel: { type: "string" },
|
|
3129
|
-
buildCli: { type: "string" },
|
|
3130
|
-
buildModel: { type: "string" },
|
|
3131
|
-
specsDir: { type: "string" },
|
|
3132
|
-
session: { type: "string" }
|
|
3133
|
-
}
|
|
3134
|
-
}
|
|
3135
|
-
);
|
|
3396
|
+
// src/cli.tsx
|
|
3397
|
+
import { jsx as jsx14 } from "react/jsx-runtime";
|
|
3398
|
+
function writeUnknownCommandError(command2) {
|
|
3399
|
+
process.stderr.write(
|
|
3400
|
+
formatErrorWithHint(
|
|
3401
|
+
`Unknown command: ${command2}`,
|
|
3402
|
+
COMMAND_NAMES,
|
|
3403
|
+
"toby --help"
|
|
3404
|
+
)
|
|
3405
|
+
);
|
|
3406
|
+
}
|
|
3407
|
+
var cli = meow("", {
|
|
3408
|
+
importMeta: import.meta,
|
|
3409
|
+
autoHelp: false,
|
|
3410
|
+
flags: MEOW_FLAGS
|
|
3411
|
+
});
|
|
3136
3412
|
ensureGlobalDir();
|
|
3137
3413
|
var resolvedSpec = cli.flags.specs ?? cli.flags.spec;
|
|
3138
3414
|
var flags = { ...cli.flags, spec: resolvedSpec };
|
|
3139
3415
|
var commands = {
|
|
3140
3416
|
plan: {
|
|
3141
|
-
render: (flags2) => /* @__PURE__ */
|
|
3417
|
+
render: (flags2) => /* @__PURE__ */ jsx14(
|
|
3142
3418
|
Plan,
|
|
3143
3419
|
{
|
|
3144
3420
|
spec: flags2.spec,
|
|
@@ -3153,7 +3429,7 @@ var commands = {
|
|
|
3153
3429
|
waitForExit: true
|
|
3154
3430
|
},
|
|
3155
3431
|
build: {
|
|
3156
|
-
render: (flags2) => /* @__PURE__ */
|
|
3432
|
+
render: (flags2) => /* @__PURE__ */ jsx14(
|
|
3157
3433
|
Build,
|
|
3158
3434
|
{
|
|
3159
3435
|
spec: flags2.spec,
|
|
@@ -3168,7 +3444,7 @@ var commands = {
|
|
|
3168
3444
|
waitForExit: true
|
|
3169
3445
|
},
|
|
3170
3446
|
init: {
|
|
3171
|
-
render: (flags2, _input, version2) => /* @__PURE__ */
|
|
3447
|
+
render: (flags2, _input, version2) => /* @__PURE__ */ jsx14(
|
|
3172
3448
|
Init,
|
|
3173
3449
|
{
|
|
3174
3450
|
version: version2,
|
|
@@ -3183,17 +3459,21 @@ var commands = {
|
|
|
3183
3459
|
waitForExit: true
|
|
3184
3460
|
},
|
|
3185
3461
|
status: {
|
|
3186
|
-
render: (flags2, _input, version2) => /* @__PURE__ */
|
|
3462
|
+
render: (flags2, _input, version2) => /* @__PURE__ */ jsx14(Status, { spec: flags2.spec, version: version2 })
|
|
3463
|
+
},
|
|
3464
|
+
clean: {
|
|
3465
|
+
render: (flags2) => /* @__PURE__ */ jsx14(Clean, { force: flags2.force }),
|
|
3466
|
+
waitForExit: true
|
|
3187
3467
|
},
|
|
3188
3468
|
config: {
|
|
3189
3469
|
render: (_flags, input, version2) => {
|
|
3190
3470
|
const [, subcommand, ...rest] = input;
|
|
3191
|
-
if (!subcommand) return /* @__PURE__ */
|
|
3471
|
+
if (!subcommand) return /* @__PURE__ */ jsx14(ConfigEditor, { version: version2 });
|
|
3192
3472
|
if (subcommand === "set" && rest.some((arg) => arg.includes("="))) {
|
|
3193
|
-
return /* @__PURE__ */
|
|
3473
|
+
return /* @__PURE__ */ jsx14(ConfigSetBatch, { pairs: rest.filter((arg) => arg.includes("=")) });
|
|
3194
3474
|
}
|
|
3195
3475
|
const [configKey, value] = rest;
|
|
3196
|
-
return /* @__PURE__ */
|
|
3476
|
+
return /* @__PURE__ */ jsx14(
|
|
3197
3477
|
Config,
|
|
3198
3478
|
{
|
|
3199
3479
|
subcommand,
|
|
@@ -3208,12 +3488,24 @@ var commands = {
|
|
|
3208
3488
|
};
|
|
3209
3489
|
var version = cli.pkg.version ?? "0.0.0";
|
|
3210
3490
|
var [command] = cli.input;
|
|
3211
|
-
if (
|
|
3491
|
+
if (cli.flags.help) {
|
|
3492
|
+
if (!command || command in commands) {
|
|
3493
|
+
if (command && command in commandHelp) {
|
|
3494
|
+
process.stdout.write(formatCommandHelp(command, commandHelp[command]));
|
|
3495
|
+
} else {
|
|
3496
|
+
process.stdout.write(formatGlobalHelp(version));
|
|
3497
|
+
}
|
|
3498
|
+
process.exitCode = 0;
|
|
3499
|
+
} else {
|
|
3500
|
+
writeUnknownCommandError(command);
|
|
3501
|
+
process.exitCode = 1;
|
|
3502
|
+
}
|
|
3503
|
+
} else if (!command) {
|
|
3212
3504
|
if (process.stdin.isTTY) {
|
|
3213
|
-
const app = render(/* @__PURE__ */
|
|
3505
|
+
const app = render(/* @__PURE__ */ jsx14(Welcome, { version }));
|
|
3214
3506
|
await app.waitUntilExit();
|
|
3215
3507
|
} else {
|
|
3216
|
-
|
|
3508
|
+
process.stdout.write(formatGlobalHelp(version));
|
|
3217
3509
|
}
|
|
3218
3510
|
} else if (command in commands) {
|
|
3219
3511
|
const entry = commands[command];
|
|
@@ -3224,6 +3516,6 @@ if (!command) {
|
|
|
3224
3516
|
app.unmount();
|
|
3225
3517
|
}
|
|
3226
3518
|
} else {
|
|
3227
|
-
|
|
3519
|
+
writeUnknownCommandError(command);
|
|
3228
3520
|
process.exitCode = 1;
|
|
3229
3521
|
}
|