claskit 1.0.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.
Files changed (3) hide show
  1. package/README.md +133 -0
  2. package/bin/claskit.js +523 -0
  3. package/package.json +43 -0
package/README.md ADDED
@@ -0,0 +1,133 @@
1
+ # claskit
2
+
3
+ Autonomous Claude Code task runner. Write task specs as Markdown, run `claskit`, watch Claude implement them.
4
+
5
+ ```
6
+ ┌──────────────────────────────────────────────────────────────────┐
7
+ │ │
8
+ │ .claude/tasks/todo/ .claude/tasks/done/ │
9
+ │ ┌──────────────────┐ ┌──────────────────┐ │
10
+ │ │ feature-a.md │ │ feature-a.md │ │
11
+ │ │ feature-b.md │ │ │ │
12
+ │ └────────┬─────────┘ └────────▲─────────┘ │
13
+ │ │ │ │
14
+ │ ▼ │ │
15
+ │ ┌───────────┐ │ │
16
+ │ │ claskit │ │ │
17
+ │ └─────┬─────┘ │ │
18
+ │ │ │ │
19
+ │ ┌──────┴──────┐ │ │
20
+ │ │ │ │ │
21
+ │ --now --schedule implements │
22
+ │ │ HH:MM ⏳ │ │
23
+ │ │ │ │ │
24
+ │ └──────┬───────┘ │ │
25
+ │ ▼ │ │
26
+ │ claude --dangerously-skip-permissions │ │
27
+ │ reads spec → verifies → ──────────────┘ │
28
+ │ │
29
+ └──────────────────────────────────────────────────────────────────┘
30
+ ```
31
+
32
+ ## Requirements
33
+
34
+ - Node.js 18+
35
+ - [Claude Code CLI](https://claude.ai/code) installed and authenticated
36
+
37
+ ## Install
38
+
39
+ ```bash
40
+ pnpm add -g claskit
41
+ ```
42
+
43
+ ## Quick start
44
+
45
+ ```bash
46
+ pnpm add -g claskit
47
+ ```
48
+
49
+ ```bash
50
+ claskit
51
+ ```
52
+
53
+ claskit detects if the project is not set up and walks you through initialization interactively.
54
+
55
+ ## Usage
56
+
57
+ ```
58
+ claskit [flag]
59
+ ```
60
+
61
+ | Flag | What it does |
62
+ |------|-------------|
63
+ | _(none)_ | Interactive menu: pick tasks, choose when to run |
64
+ | `--init` | Set up `.claude/tasks/` folder structure in current project |
65
+ | `--now` | Skip menu, run immediately (still shows task picker + confirm) |
66
+ | `--test` | Create 2 sample task files in `todo/` to test the runner |
67
+ | `--clean-test` | Remove all test-generated files |
68
+
69
+ ### Interactive menu options
70
+
71
+ - **Now** — pick tasks and launch immediately
72
+ - **Schedule (HH:MM)** — live countdown until target time, then launch
73
+ - **Exit**
74
+
75
+ ### Task picker
76
+
77
+ When 2+ tasks are queued, claskit asks which to run:
78
+
79
+ ```
80
+ Select tasks:
81
+ Enter numbers separated by commas, or "all"
82
+ [all]: 1,3
83
+ ```
84
+
85
+ ## How it works
86
+
87
+ 1. Claude reads each selected `.md` spec
88
+ 2. Decides execution order based on dependencies noted in specs
89
+ 3. Implements each task fully
90
+ 4. Verifies acceptance criteria
91
+ 5. Moves the spec from `todo/` to `done/` on success
92
+ 6. Stops and reports if anything fails
93
+
94
+ Claude runs with `--dangerously-skip-permissions` — it will read, write, and execute commands without prompting. Only use in projects you trust.
95
+
96
+ ## Task spec format
97
+
98
+ ```markdown
99
+ # Feature Title
100
+
101
+ ## Task
102
+
103
+ What needs to be built.
104
+
105
+ ## Acceptance Criteria
106
+
107
+ - [ ] Criterion 1
108
+ - [ ] Criterion 2
109
+
110
+ ## Files Affected
111
+
112
+ | File | Change |
113
+ |------|--------|
114
+ | `src/foo.ts` | New component |
115
+
116
+ ## Notes
117
+
118
+ Dependencies, order hints, constraints.
119
+ ```
120
+
121
+ Save as `.claude/tasks/todo/my-feature.md`.
122
+
123
+ ## Project integration
124
+
125
+ ```bash
126
+ npm pkg set scripts.claskit="claskit"
127
+ ```
128
+
129
+ Then run with `npm run claskit` / `pnpm claskit` / `yarn claskit`.
130
+
131
+ ## License
132
+
133
+ MIT
package/bin/claskit.js ADDED
@@ -0,0 +1,523 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const readline = require('readline');
7
+ const { spawn } = require('child_process');
8
+
9
+ // ANSI colors
10
+ const C = {
11
+ red: '\x1b[31m',
12
+ green: '\x1b[32m',
13
+ yellow: '\x1b[33m',
14
+ cyan: '\x1b[36m',
15
+ bold: '\x1b[1m',
16
+ dim: '\x1b[2m',
17
+ reset: '\x1b[0m',
18
+ };
19
+
20
+ const { name, version } = JSON.parse(
21
+ fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8')
22
+ );
23
+
24
+ const TODO_DIR = '.claude/tasks/todo';
25
+ const DONE_DIR = '.claude/tasks/done';
26
+ const TASKS_DIR = '.claude/tasks';
27
+ const PROJECT_NAME = path.basename(process.cwd());
28
+
29
+ const TASKS_README = `# .claude/tasks
30
+
31
+ This folder is managed by [claskit](https://github.com/phucbm/claskit) — an autonomous Claude Code task runner.
32
+
33
+ ## How it works
34
+
35
+ Each \`.md\` file in \`todo/\` is a task spec. When you run \`claskit\`, Claude reads the specs,
36
+ implements them in order, then moves each file to \`done/\` on success.
37
+
38
+ ## Folder structure
39
+
40
+ \`\`\`
41
+ .claude/tasks/
42
+ todo/ ← task specs waiting to be implemented
43
+ done/ ← completed task specs (moved here by claskit after success)
44
+ README.md ← this file
45
+ \`\`\`
46
+
47
+ ## Task spec format
48
+
49
+ Each \`.md\` file should contain:
50
+ - A \`# Title\` heading
51
+ - **Task** section: what to implement
52
+ - **Acceptance Criteria** section: checklist Claude verifies before marking done
53
+ - **Files Affected** section (optional): which files will change
54
+ - **Notes** section (optional): dependencies, order hints, constraints
55
+
56
+ ## For AI assistants reading this
57
+
58
+ If you see \`.md\` files in \`todo/\`, they are pending implementation specs.
59
+ Do not modify them unless asked. Do not move them to \`done/\` manually.
60
+ claskit handles the lifecycle: read spec → implement → verify → move to done.
61
+ `;
62
+
63
+ function runInit() {
64
+ const readmePath = path.join(TASKS_DIR, 'README.md');
65
+ const alreadyExists = fs.existsSync(TODO_DIR) && fs.existsSync(DONE_DIR);
66
+
67
+ fs.mkdirSync(TODO_DIR, { recursive: true });
68
+ fs.mkdirSync(DONE_DIR, { recursive: true });
69
+ fs.writeFileSync(readmePath, TASKS_README);
70
+
71
+ if (alreadyExists) {
72
+ console.log(`${C.yellow}Already initialized.${C.reset} Updated README.md.`);
73
+ } else {
74
+ console.log(`${C.green}Initialized claskit for ${C.bold}${PROJECT_NAME}${C.reset}`);
75
+ console.log(` ${C.dim}Created: ${TODO_DIR}/${C.reset}`);
76
+ console.log(` ${C.dim}Created: ${DONE_DIR}/${C.reset}`);
77
+ console.log(` ${C.dim}Created: ${TASKS_DIR}/README.md${C.reset}`);
78
+ console.log('');
79
+ console.log(`Next: add task specs to ${C.cyan}${TODO_DIR}/${C.reset}`);
80
+ console.log(`Then run: ${C.cyan}claskit --now${C.reset}`);
81
+ console.log('');
82
+ console.log(`${C.dim}To try with sample tasks: claskit --test${C.reset}`);
83
+ }
84
+ }
85
+
86
+ const TEST_TASK_1 = `# claskit Test Task 1: Create output file
87
+
88
+ **Status:** Planned — auto-generated by claskit --test
89
+
90
+ ## Task
91
+
92
+ Create the file \`.claude/tasks/claskit-test-1.txt\` with this exact content:
93
+
94
+ \`\`\`
95
+ claskit test task 1 complete
96
+ \`\`\`
97
+
98
+ ## Acceptance Criteria
99
+
100
+ - [ ] File \`.claude/tasks/claskit-test-1.txt\` exists
101
+ - [ ] File contains "claskit test task 1 complete"
102
+
103
+ ## Notes
104
+
105
+ Do NOT run any build or lint commands. Just create the file.
106
+ This task must complete before task 2 runs (task 2 reads this file).
107
+ `;
108
+
109
+ const TEST_TASK_2 = `# claskit Test Task 2: Chain verification
110
+
111
+ **Status:** Planned — auto-generated by claskit --test
112
+
113
+ ## Task
114
+
115
+ Read \`.claude/tasks/claskit-test-1.txt\` and confirm it contains "claskit test task 1 complete".
116
+ Then create \`.claude/tasks/claskit-test-2.txt\` with this exact content:
117
+
118
+ \`\`\`
119
+ claskit test task 2 complete
120
+ task 1 output confirmed
121
+ \`\`\`
122
+
123
+ ## Acceptance Criteria
124
+
125
+ - [ ] \`.claude/tasks/claskit-test-1.txt\` exists and contains expected text (from task 1)
126
+ - [ ] \`.claude/tasks/claskit-test-2.txt\` created with correct content
127
+
128
+ ## Notes
129
+
130
+ Do NOT run any build or lint commands. Just read task 1 output and create task 2 file.
131
+ Depends on: claskit-test-task-1.md
132
+ `;
133
+
134
+ // ─── helpers ────────────────────────────────────────────────────────────────
135
+
136
+ function pad(str, len) {
137
+ const s = String(str);
138
+ return s.length >= len ? s : s + ' '.repeat(len - s.length);
139
+ }
140
+
141
+ function box(...lines) {
142
+ const width = 44;
143
+ const inner = width - 2;
144
+ const top = `╔${'═'.repeat(inner)}╗`;
145
+ const bottom = `╚${'═'.repeat(inner)}╝`;
146
+ const rows = lines.map(l => `║ ${pad(l, inner - 2)}║`);
147
+ return [top, ...rows, bottom].join('\n');
148
+ }
149
+
150
+ function taskTitle(filepath) {
151
+ try {
152
+ const content = fs.readFileSync(filepath, 'utf8');
153
+ const m = content.match(/^#\s+(.+)/m);
154
+ return m ? m[1].trim() : path.basename(filepath, '.md');
155
+ } catch {
156
+ return path.basename(filepath, '.md');
157
+ }
158
+ }
159
+
160
+ function fmtDuration(secs) {
161
+ const h = Math.floor(secs / 3600);
162
+ const m = Math.floor((secs % 3600) / 60);
163
+ const s = secs % 60;
164
+ if (h > 0) return `${h}h ${String(m).padStart(2,'0')}m ${String(s).padStart(2,'0')}s`;
165
+ if (m > 0) return `${m}m ${String(s).padStart(2,'0')}s`;
166
+ return `${s}s`;
167
+ }
168
+
169
+ // ─── countdown ──────────────────────────────────────────────────────────────
170
+
171
+ function countdown(totalSecs, label) {
172
+ return new Promise((resolve) => {
173
+ let remaining = totalSecs;
174
+
175
+ const tick = () => {
176
+ process.stdout.write(
177
+ `\r ${C.yellow}⏳${C.reset} ${label} — ${C.bold}${fmtDuration(remaining)}${C.reset} remaining `
178
+ );
179
+ };
180
+
181
+ tick();
182
+ const timer = setInterval(() => {
183
+ remaining--;
184
+ if (remaining <= 0) {
185
+ clearInterval(timer);
186
+ process.stdout.write('\r' + ' '.repeat(60) + '\r');
187
+ resolve();
188
+ } else {
189
+ tick();
190
+ }
191
+ }, 1000);
192
+
193
+ process.on('SIGINT', () => {
194
+ clearInterval(timer);
195
+ process.stdout.write('\r' + ' '.repeat(60) + '\r');
196
+ console.log(`\n${C.red}Cancelled.${C.reset}`);
197
+ exit(0);
198
+ });
199
+ });
200
+ }
201
+
202
+ // ─── readline prompt helpers ─────────────────────────────────────────────────
203
+ // Single rl instance with an event-driven queue so piped stdin lines
204
+ // are buffered and consumed in order — even when lines arrive before ask() registers.
205
+
206
+ const _lineQueue = [];
207
+ let _lineWaiter = null;
208
+ let _rl = null;
209
+
210
+ function initRL() {
211
+ if (_rl) return;
212
+ _rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: false });
213
+ _rl.on('line', line => {
214
+ const trimmed = line.trim();
215
+ if (_lineWaiter) {
216
+ const resolve = _lineWaiter;
217
+ _lineWaiter = null;
218
+ resolve(trimmed);
219
+ } else {
220
+ _lineQueue.push(trimmed);
221
+ }
222
+ });
223
+ }
224
+
225
+ function closeRL() {
226
+ if (_rl) { _rl.close(); _rl = null; }
227
+ }
228
+
229
+ function exit(code = 0) {
230
+ closeRL();
231
+ process.exit(code);
232
+ }
233
+
234
+ function ask(question) {
235
+ initRL();
236
+ process.stdout.write(question);
237
+ return new Promise(resolve => {
238
+ if (_lineQueue.length > 0) {
239
+ const line = _lineQueue.shift();
240
+ process.stdout.write(line + '\n');
241
+ resolve(line);
242
+ } else {
243
+ _lineWaiter = resolve;
244
+ }
245
+ });
246
+ }
247
+
248
+ function menu(options) {
249
+ options.forEach((opt, i) => {
250
+ console.log(` ${C.cyan}[${i + 1}]${C.reset} ${opt}`);
251
+ });
252
+ return ask(`\n${C.bold}>${C.reset} `);
253
+ }
254
+
255
+ // ─── launch ──────────────────────────────────────────────────────────────────
256
+
257
+ async function pickTasks(tasks) {
258
+ if (tasks.length === 1) return tasks; // nothing to pick
259
+
260
+ console.log(`${C.bold}Select tasks:${C.reset}`);
261
+ console.log(` ${C.dim}Enter numbers separated by commas, or "all"${C.reset}`);
262
+ const ans = await ask(` ${C.dim}[all]${C.reset}: `);
263
+ const input = ans.trim().toLowerCase();
264
+
265
+ if (!input || input === 'all') return tasks;
266
+
267
+ const indices = input.split(',').map(s => parseInt(s.trim(), 10) - 1);
268
+ const selected = indices.filter(i => i >= 0 && i < tasks.length).map(i => tasks[i]);
269
+
270
+ if (selected.length === 0) {
271
+ console.log(`${C.red}No valid tasks selected.${C.reset}`);
272
+ exit(1);
273
+ }
274
+
275
+ console.log('');
276
+ console.log(`${C.dim}Running ${selected.length} of ${tasks.length} task(s):${C.reset}`);
277
+ selected.forEach((f, i) => console.log(` ${C.cyan}${i + 1}.${C.reset} ${taskTitle(f)}`));
278
+ return selected;
279
+ }
280
+
281
+ function buildPrompt(selectedTasks) {
282
+ const fileList = selectedTasks.map(f => `- ${f.replace(/\\/g, '/')}`).join('\n');
283
+ return `You are an autonomous task runner for this project.
284
+
285
+ Your job:
286
+ 1. Read ONLY the following task files (ignore other files in .claude/tasks/todo/):
287
+ ${fileList}
288
+ 2. Analyze each spec and decide the best execution order based on dependencies, complexity, and notes in each file
289
+ 3. Tell me the order you chose and why (briefly)
290
+ 4. For each task in order:
291
+ a. Implement it fully according to the spec
292
+ b. Verify all acceptance criteria listed in the spec are met
293
+ c. Run the project's build/lint command (check package.json for the right command)
294
+ d. If build/lint passes → move the .md file from .claude/tasks/todo/ to .claude/tasks/done/
295
+ e. If something fails → stop, report clearly what failed and why, do not proceed to next task
296
+ 5. After all tasks are done, print a summary: what was completed, what was skipped, any issues
297
+
298
+ Do not ask for confirmation between tasks. Work autonomously until your assigned tasks are done or a blocker is hit.`;
299
+ }
300
+
301
+ async function confirmLaunch(count) {
302
+ console.log('');
303
+ console.log(` ${C.bold}Ready to run ${count} task(s) autonomously.${C.reset}`);
304
+ console.log('');
305
+ console.log(` ${C.yellow}${C.bold}⚠ WARNING: --dangerously-skip-permissions${C.reset}`);
306
+ console.log(` ${C.yellow}Claude will read, write, and run commands WITHOUT asking.${C.reset}`);
307
+ console.log(` ${C.yellow}Only use this in a project you trust and have backed up.${C.reset}`);
308
+ console.log('');
309
+ const ans = await ask(` Confirm? ${C.dim}[y/N]${C.reset}: `);
310
+ return ans.toLowerCase() === 'y';
311
+ }
312
+
313
+ function launchClaude(prompt, label) {
314
+ console.log('');
315
+ console.log(` ${C.green}${C.bold}Launching Claude Code${label ? ` — ${label}` : ''}${C.reset}`);
316
+ console.log(` ${C.dim}Prompt: "${prompt.split('\n')[0]}${prompt.includes('\n') ? '...' : ''}"${C.reset}`);
317
+ console.log(`\n${'─'.repeat(50)}\n`);
318
+
319
+ // Pipe prompt via stdin so Claude runs interactively (tool calls visible)
320
+ const bin = process.platform === 'win32' ? 'claude.cmd' : 'claude';
321
+ const child = spawn(bin, ['--dangerously-skip-permissions'], {
322
+ stdio: ['pipe', 'inherit', 'inherit'],
323
+ shell: false,
324
+ });
325
+
326
+ child.stdin.write(prompt + '\n');
327
+ child.stdin.end();
328
+
329
+ child.on('exit', code => {
330
+ console.log(`\n${'─'.repeat(50)}`);
331
+ if (code !== 0) {
332
+ console.log(`${C.red}Claude exited with code ${code}${C.reset}`);
333
+ process.exit(code);
334
+ } else {
335
+ console.log(`${C.green}Claude finished successfully.${C.reset}`);
336
+ }
337
+ });
338
+ }
339
+
340
+ async function launchReal(tasks) {
341
+ const selected = await pickTasks(tasks);
342
+ const confirmed = await confirmLaunch(selected.length);
343
+ if (!confirmed) {
344
+ console.log(`\n${C.dim}Aborted.${C.reset}`);
345
+ exit(0);
346
+ }
347
+ launchClaude(buildPrompt(selected), 'autonomous mode');
348
+ }
349
+
350
+ // ─── schedule ────────────────────────────────────────────────────────────────
351
+
352
+ async function scheduleRun(tasks) {
353
+ const selected = await pickTasks(tasks);
354
+ const input = await ask(` Enter time ${C.dim}(HH:MM, 24h)${C.reset}: `);
355
+ if (!/^([01]\d|2[0-3]):[0-5]\d$/.test(input)) {
356
+ console.log(`${C.red}Invalid format. Use HH:MM (e.g. 22:30)${C.reset}`);
357
+ exit(1);
358
+ }
359
+
360
+ const [hh, mm] = input.split(':').map(Number);
361
+ const now = new Date();
362
+ const target = new Date();
363
+ target.setHours(hh, mm, 0, 0);
364
+ if (target <= now) target.setDate(target.getDate() + 1);
365
+
366
+ const waitSecs = Math.round((target - now) / 1000);
367
+ console.log(`\n ${C.cyan}Scheduled for ${input}${C.reset} — ${fmtDuration(waitSecs)} from now`);
368
+ console.log(` ${C.dim}Press Ctrl+C to cancel${C.reset}\n`);
369
+
370
+ await countdown(waitSecs, `Launching at ${input}`);
371
+
372
+ const confirmed = await confirmLaunch(selected.length);
373
+ if (!confirmed) {
374
+ console.log(`\n${C.dim}Aborted.${C.reset}`);
375
+ exit(0);
376
+ }
377
+ launchClaude(buildPrompt(selected), 'autonomous mode');
378
+ }
379
+
380
+ // ─── main ────────────────────────────────────────────────────────────────────
381
+
382
+ async function main() {
383
+ const args = process.argv.slice(2);
384
+
385
+ // Guard: warn if not in a project directory
386
+ const looksLikeProject = fs.existsSync('.git') || fs.existsSync('package.json') || fs.existsSync('.claude');
387
+ if (!looksLikeProject) {
388
+ console.log('');
389
+ console.log(`${C.yellow}⚠ No project detected in ${process.cwd()}${C.reset}`);
390
+ console.log(`${C.dim}claskit should be run from your project root, not ~/ or /${C.reset}`);
391
+ const ans = await ask(` Continue anyway? ${C.dim}[y/N]${C.reset}: `);
392
+ if (ans.toLowerCase() !== 'y') { exit(0); }
393
+ console.log('');
394
+ }
395
+
396
+ if (args.includes('--init')) {
397
+ runInit();
398
+ return;
399
+ }
400
+
401
+ // Onboarding — interactive setup if not initialized
402
+ if (!fs.existsSync(TODO_DIR)) {
403
+ console.log('');
404
+ console.log(`${C.yellow}claskit not set up in this project.${C.reset}`);
405
+ const doInit = await ask(` Initialize now? ${C.dim}[Y/n]${C.reset}: `);
406
+ if (doInit.toLowerCase() === 'n') {
407
+ console.log(`\n${C.dim}Run: claskit --init${C.reset}`);
408
+ exit(0);
409
+ }
410
+ runInit();
411
+ console.log('');
412
+ const doTest = await ask(` Add sample tasks to try claskit? ${C.dim}[Y/n]${C.reset}: `);
413
+ if (doTest.toLowerCase() !== 'n') {
414
+ const f1 = path.join(TODO_DIR, 'claskit-test-task-1.md');
415
+ const f2 = path.join(TODO_DIR, 'claskit-test-task-2.md');
416
+ fs.writeFileSync(f1, TEST_TASK_1);
417
+ fs.writeFileSync(f2, TEST_TASK_2);
418
+ console.log(` ${C.green}✓${C.reset} Created 2 sample tasks in ${TODO_DIR}/`);
419
+ }
420
+ console.log('');
421
+ console.log(`${'─'.repeat(42)}`);
422
+ console.log(` ${C.bold}Ready.${C.reset} Run: ${C.cyan}claskit --now${C.reset}`);
423
+ console.log(`${'─'.repeat(42)}`);
424
+ exit(0);
425
+ }
426
+
427
+ // Scan tasks
428
+ const tasks = fs.readdirSync(TODO_DIR)
429
+ .filter(f => f.endsWith('.md'))
430
+ .sort()
431
+ .map(f => path.join(TODO_DIR, f));
432
+
433
+ const taskCount = tasks.length;
434
+
435
+ // Header
436
+ console.log('');
437
+ console.log(C.cyan + box(
438
+ `${name} v${version}`,
439
+ `Project: ${PROJECT_NAME}`,
440
+ `Tasks: ${taskCount} found`
441
+ ) + C.reset);
442
+ console.log('');
443
+
444
+ if (args.includes('--test')) {
445
+ const f1 = path.join(TODO_DIR, 'claskit-test-task-1.md');
446
+ const f2 = path.join(TODO_DIR, 'claskit-test-task-2.md');
447
+ fs.writeFileSync(f1, TEST_TASK_1);
448
+ fs.writeFileSync(f2, TEST_TASK_2);
449
+ console.log(`${C.green}Test tasks created:${C.reset}`);
450
+ console.log(` ${C.dim}${f1}${C.reset}`);
451
+ console.log(` ${C.dim}${f2}${C.reset}`);
452
+ console.log('');
453
+ console.log(`${C.bold}Now run:${C.reset} ${C.cyan}claskit --now${C.reset} (or ${C.cyan}pnpm claskit --now${C.reset})`);
454
+ console.log(`${C.dim}Select both test tasks → confirm → watch Claude create the output files.${C.reset}`);
455
+ console.log('');
456
+ console.log(`${C.dim}When done, run: claskit --clean-test${C.reset}`);
457
+ return;
458
+ }
459
+
460
+ if (args.includes('--clean-test')) {
461
+ const toDelete = [
462
+ path.join(TODO_DIR, 'claskit-test-task-1.md'),
463
+ path.join(TODO_DIR, 'claskit-test-task-2.md'),
464
+ path.join(DONE_DIR, 'claskit-test-task-1.md'),
465
+ path.join(DONE_DIR, 'claskit-test-task-2.md'),
466
+ '.claude/tasks/claskit-test-1.txt',
467
+ '.claude/tasks/claskit-test-2.txt',
468
+ ];
469
+ let removed = 0;
470
+ toDelete.forEach(f => {
471
+ if (fs.existsSync(f)) { fs.unlinkSync(f); removed++; }
472
+ });
473
+ console.log(`${C.green}Cleaned up ${removed} test file(s).${C.reset}`);
474
+ return;
475
+ }
476
+
477
+ if (taskCount === 0) {
478
+ console.log(`${C.yellow}No tasks found in ${TODO_DIR}${C.reset}`);
479
+ exit(0);
480
+ }
481
+
482
+ // List tasks
483
+ console.log(`${C.bold}Queued tasks:${C.reset}`);
484
+ tasks.forEach((f, i) => {
485
+ const title = taskTitle(f);
486
+ console.log(` ${C.dim}${i + 1}.${C.reset} ${title}`);
487
+ console.log(` ${C.dim}${path.basename(f)}${C.reset}`);
488
+ });
489
+ console.log('');
490
+
491
+ if (args.includes('--now')) {
492
+ await launchReal(tasks);
493
+ return;
494
+ }
495
+
496
+ // Interactive menu
497
+ console.log(`${C.bold}When to run?${C.reset}`);
498
+ const choice = await menu(['Now', 'Schedule (HH:MM)', 'Exit']);
499
+
500
+ switch (choice) {
501
+ case '1':
502
+ await launchReal(tasks);
503
+ break;
504
+
505
+ case '2':
506
+ await scheduleRun(tasks);
507
+ break;
508
+
509
+ case '3':
510
+ default:
511
+ console.log(`\n${C.dim}Bye.${C.reset}`);
512
+ exit(0);
513
+ }
514
+ }
515
+
516
+ if (require.main === module) {
517
+ main().catch(err => {
518
+ console.error(`${C.red}${err.message}${C.reset}`);
519
+ exit(1);
520
+ });
521
+ }
522
+
523
+ module.exports = { pad, box, fmtDuration, taskTitle, buildPrompt };
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "claskit",
3
+ "version": "1.0.0",
4
+ "description": "Autonomous Claude Code task runner for spec-driven development",
5
+ "author": {
6
+ "name": "phucbm",
7
+ "url": "https://github.com/phucbm"
8
+ },
9
+ "license": "MIT",
10
+ "homepage": "https://github.com/phucbm/claskit#readme",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/phucbm/claskit.git"
14
+ },
15
+ "bugs": {
16
+ "url": "https://github.com/phucbm/claskit/issues"
17
+ },
18
+ "bin": {
19
+ "claskit": "bin/claskit.js"
20
+ },
21
+ "main": "./bin/claskit.js",
22
+ "files": [
23
+ "bin/",
24
+ "README.md",
25
+ "LICENSE"
26
+ ],
27
+ "engines": {
28
+ "node": ">=18"
29
+ },
30
+ "scripts": {
31
+ "test": "node --test test/*.test.js"
32
+ },
33
+ "keywords": [
34
+ "claude",
35
+ "claude-code",
36
+ "ai",
37
+ "task-runner",
38
+ "automation",
39
+ "cli",
40
+ "spec-driven",
41
+ "autonomous"
42
+ ]
43
+ }