@crewx/wbs 0.1.6 → 0.1.8

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 ADDED
@@ -0,0 +1,718 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.main = main;
38
+ const child_process_1 = require("child_process");
39
+ const path = __importStar(require("path"));
40
+ const fs = __importStar(require("fs"));
41
+ const engine_1 = require("./src/engine");
42
+ let PKG_VERSION = 'unknown';
43
+ try {
44
+ const _pkgPath = fs.existsSync(path.join(__dirname, 'package.json'))
45
+ ? path.join(__dirname, 'package.json')
46
+ : path.join(__dirname, '..', 'package.json');
47
+ const _pkg = JSON.parse(fs.readFileSync(_pkgPath, 'utf-8'));
48
+ if (_pkg?.version)
49
+ PKG_VERSION = String(_pkg.version);
50
+ }
51
+ catch { }
52
+ async function loadTracer() {
53
+ try {
54
+ return await Promise.resolve().then(() => __importStar(require('@crewx/shared/skill-tracer')));
55
+ }
56
+ catch {
57
+ return null;
58
+ }
59
+ }
60
+ const STATUS_ICONS = {
61
+ pending: '⬜️',
62
+ running: '🟡',
63
+ completed: '✅',
64
+ failed: '❌',
65
+ on_hold: '⏸️',
66
+ planning: '📝',
67
+ partial: '⚠️',
68
+ };
69
+ function icon(status) {
70
+ return STATUS_ICONS[status] ?? '❓';
71
+ }
72
+ function getDisplayWidth(str) {
73
+ let width = 0;
74
+ for (const char of String(str)) {
75
+ const code = char.codePointAt(0) ?? 0;
76
+ if ((code >= 0x1100 && code <= 0x11FF) || (code >= 0x3000 && code <= 0x303F) ||
77
+ (code >= 0x3040 && code <= 0x309F) || (code >= 0x30A0 && code <= 0x30FF) ||
78
+ (code >= 0x3130 && code <= 0x318F) || (code >= 0x3200 && code <= 0x32FF) ||
79
+ (code >= 0x4E00 && code <= 0x9FFF) || (code >= 0xAC00 && code <= 0xD7AF) ||
80
+ (code >= 0xF900 && code <= 0xFAFF) || (code >= 0xFF00 && code <= 0xFFEF) ||
81
+ (code >= 0x1F300 && code <= 0x1F9FF) || (code >= 0x2600 && code <= 0x26FF) ||
82
+ (code >= 0x2700 && code <= 0x27BF)) {
83
+ width += 2;
84
+ }
85
+ else {
86
+ width += 1;
87
+ }
88
+ }
89
+ return width;
90
+ }
91
+ function pad(str, targetWidth) {
92
+ const s = String(str);
93
+ const diff = targetWidth - getDisplayWidth(s);
94
+ return diff > 0 ? s + ' '.repeat(diff) : s;
95
+ }
96
+ function colWidths(headers, rows) {
97
+ const widths = headers.map(h => getDisplayWidth(h));
98
+ rows.forEach(row => row.forEach((cell, i) => {
99
+ const w = getDisplayWidth(String(cell));
100
+ if (w > (widths[i] ?? 0))
101
+ widths[i] = w;
102
+ }));
103
+ return widths;
104
+ }
105
+ function fmtRow(cells, widths) {
106
+ return '| ' + cells.map((c, i) => pad(String(c), widths[i] ?? 0)).join(' | ') + ' |';
107
+ }
108
+ function fmtSep(widths) {
109
+ return '|' + widths.map(w => '-'.repeat(w + 2)).join('|') + '|';
110
+ }
111
+ function fmtTable(headers, rows) {
112
+ const w = colWidths(headers, rows);
113
+ return [fmtRow(headers, w), fmtSep(w), ...rows.map(r => fmtRow(r, w))].join('\n');
114
+ }
115
+ function fmtProjectList(projects) {
116
+ if (!projects || projects.length === 0)
117
+ return '📋 WBS Projects (0)\n\nNo projects found.';
118
+ const rows = projects.map(p => {
119
+ const jobs = engine_1.engine.listJobs(p.id);
120
+ const done = jobs.filter(j => j.status === 'completed').length;
121
+ const total = jobs.length;
122
+ const progress = total > 0 ? Math.round((done / total) * 100) : 0;
123
+ const statusIcon = p.status === 'completed' ? icon('completed')
124
+ : (p.status === 'running' || jobs.some(j => j.status === 'running')) ? icon('running')
125
+ : done > 0 ? icon('running') : icon('pending');
126
+ return [statusIcon, p.id, p.title, String(total), `${progress}%`];
127
+ });
128
+ return `📋 WBS Projects (${projects.length})\n\n${fmtTable(['상태', 'ID', '프로젝트명', 'Jobs', 'Progress'], rows)}`;
129
+ }
130
+ function fmtStatus(result) {
131
+ if (!result.project)
132
+ return '❌ Project not found';
133
+ const { project, jobs, summary } = result;
134
+ const lines = [];
135
+ lines.push(`📋 ${project.title} (${project.id})`);
136
+ if (summary)
137
+ lines.push(` Status: ${project.status} | Progress: ${summary.progress}% (${summary.completed}/${summary.total})`);
138
+ if (project.detail_path)
139
+ lines.push(` Detail: ${project.detail_path}`);
140
+ lines.push('');
141
+ if (jobs.length === 0) {
142
+ lines.push(' Jobs: None');
143
+ }
144
+ else {
145
+ lines.push(' Jobs:');
146
+ const table = fmtTable(['상태', '#', 'Slug', 'ID', '작업명', '담당'], jobs.map((j, i) => [icon(j.status), String(i + 1), (0, engine_1.getJobSlug)(j), j.id, j.title, j.agent]));
147
+ table.split('\n').forEach(l => lines.push(' ' + l));
148
+ }
149
+ return lines.join('\n');
150
+ }
151
+ function fmtJobList(jobs, wbsId) {
152
+ if (!jobs || jobs.length === 0)
153
+ return `📋 Jobs for ${wbsId} (0)\n\nNo jobs found.`;
154
+ const done = jobs.filter(j => j.status === 'completed').length;
155
+ const progress = jobs.length > 0 ? Math.round((done / jobs.length) * 100) : 0;
156
+ const table = fmtTable(['상태', '#', 'Slug', 'ID', '작업명', '담당', 'Status'], jobs.map((j, i) => [icon(j.status), String(i + 1), (0, engine_1.getJobSlug)(j), j.id, j.title, j.agent, j.status]));
157
+ return `📋 Jobs for ${wbsId} (${jobs.length})\n Progress: ${progress}% (${done}/${jobs.length})\n\n${table}`;
158
+ }
159
+ function fmtExecList(execs, jobId) {
160
+ if (execs.length === 0)
161
+ return `No executions found for job: ${jobId}`;
162
+ const headers = ['상태', 'ID', 'PID', 'Task ID', '시작', '종료', '코드'];
163
+ const rows = execs.map(e => [
164
+ icon(e.status), e.id, String(e.pid ?? '-'), e.task_id ?? '-',
165
+ e.started_at ? new Date(e.started_at).toLocaleString('ko-KR') : '-',
166
+ e.ended_at ? new Date(e.ended_at).toLocaleString('ko-KR') : '-',
167
+ e.exit_code !== null ? String(e.exit_code) : '-',
168
+ ]);
169
+ return `📋 Executions for ${jobId}\n\n${fmtTable(headers, rows)}`;
170
+ }
171
+ function fmtExecStatus(exec) {
172
+ return `\n📋 Execution: ${exec.id}\n\nStatus: ${icon(exec.status)} ${exec.status}\nJob ID: ${exec.job_id}\nPID: ${exec.pid ?? '-'}\nTask ID: ${exec.task_id ?? '-'}\nStarted: ${exec.started_at ? new Date(exec.started_at).toLocaleString('ko-KR') : '-'}\nEnded: ${exec.ended_at ? new Date(exec.ended_at).toLocaleString('ko-KR') : '-'}\nExit Code: ${exec.exit_code !== null ? exec.exit_code : '-'}\nError: ${exec.error ?? '-'}\n`;
173
+ }
174
+ function fmtRunning(execs) {
175
+ if (execs.length === 0)
176
+ return 'No running executions';
177
+ const headers = ['상태', 'Exec ID', 'Job ID', 'PID', 'Task ID', '시작'];
178
+ const rows = execs.map(e => [
179
+ icon(e.status), e.id, e.job_id, String(e.pid ?? '-'), e.task_id ?? '-',
180
+ e.started_at ? new Date(e.started_at).toLocaleString('ko-KR') : '-',
181
+ ]);
182
+ return `🟡 Running Executions (${execs.length})\n\n${fmtTable(headers, rows)}`;
183
+ }
184
+ function hasJson(args) { return args.includes('--json'); }
185
+ function hasFlag(args, name) { return args.includes(`--${name}`); }
186
+ function getBooleanOpt(args, opts, name) {
187
+ const inlinePrefix = `--${name}=`;
188
+ const inlineArg = args.find(arg => arg.startsWith(inlinePrefix));
189
+ if (inlineArg) {
190
+ const rawInline = inlineArg.slice(inlinePrefix.length);
191
+ if (rawInline === 'true' || rawInline === '1')
192
+ return true;
193
+ if (rawInline === 'false' || rawInline === '0')
194
+ return false;
195
+ console.error(`Invalid boolean for --${name}: ${rawInline} (use true/false)`);
196
+ process.exit(1);
197
+ }
198
+ if (hasFlag(args, name))
199
+ return true;
200
+ const raw = opts[name];
201
+ if (raw === undefined)
202
+ return undefined;
203
+ if (raw === 'true' || raw === '1')
204
+ return true;
205
+ if (raw === 'false' || raw === '0')
206
+ return false;
207
+ console.error(`Invalid boolean for --${name}: ${raw} (use true/false)`);
208
+ process.exit(1);
209
+ }
210
+ function parseOpts(args, start = 0) {
211
+ const opts = {};
212
+ for (let i = start; i < args.length; i++) {
213
+ const current = args[i];
214
+ if (!current)
215
+ continue;
216
+ const next = args[i + 1];
217
+ if (current.startsWith('--') && next && !next.startsWith('--')) {
218
+ opts[current.slice(2)] = next;
219
+ i++;
220
+ }
221
+ }
222
+ return opts;
223
+ }
224
+ function handleJob(args) {
225
+ const sub = args[0];
226
+ switch (sub) {
227
+ case 'list': {
228
+ const wbsId = args[1];
229
+ if (!wbsId) {
230
+ console.error('Usage: wbs job list <wbs-id> [--json]');
231
+ process.exit(1);
232
+ }
233
+ const jobs = engine_1.engine.listJobs(wbsId);
234
+ console.log(hasJson(args) ? JSON.stringify(jobs, null, 2) : fmtJobList(jobs, wbsId));
235
+ break;
236
+ }
237
+ case 'add': {
238
+ const wbsId = args[1];
239
+ const opts = parseOpts(args, 2);
240
+ const { title, agent } = opts;
241
+ const description = opts.desc || opts.description || null;
242
+ const seq = opts.seq ? parseInt(opts.seq, 10) : 0;
243
+ const issue_number = opts.issue ? parseInt(opts.issue, 10) : null;
244
+ const no_git = getBooleanOpt(args, opts, 'no-git') ?? false;
245
+ if (!wbsId || !title || !agent) {
246
+ console.error('Usage: wbs job add <wbs-id> --title "Job Title" --agent "@agent" [--desc "..."] [--seq N] [--issue N] [--no-git]');
247
+ process.exit(1);
248
+ }
249
+ const jobId = engine_1.engine.addJob(wbsId, { title, description, agent, seq, issue_number, no_git });
250
+ (0, engine_1.generateDetailFile)(wbsId);
251
+ console.log(JSON.stringify({ jobId, wbsId, title, description, agent, seq, issue_number, no_git }, null, 2));
252
+ break;
253
+ }
254
+ case 'update': {
255
+ const jobIdArg = args[1];
256
+ if (!jobIdArg) {
257
+ console.error('Usage: wbs job update <job-id|slug> [--status ...] [--title ...] [--agent ...] [--seq N] [--desc ...] [--issue N] [--no-git[=true|false]]');
258
+ process.exit(1);
259
+ }
260
+ const opts = parseOpts(args, 2);
261
+ const job = (0, engine_1.resolveJobId)(jobIdArg);
262
+ if (!job) {
263
+ console.error(`Job not found: ${jobIdArg}`);
264
+ process.exit(1);
265
+ }
266
+ const jobId = job.id;
267
+ const fields = {};
268
+ if (opts.status) {
269
+ if (!['pending', 'running', 'completed', 'failed'].includes(opts.status)) {
270
+ console.error('Invalid status. Must be: pending, running, completed, or failed');
271
+ process.exit(1);
272
+ }
273
+ fields.status = opts.status;
274
+ }
275
+ if (opts.title)
276
+ fields.title = opts.title;
277
+ if (opts.agent)
278
+ fields.agent = opts.agent;
279
+ if (opts.seq !== undefined)
280
+ fields.seq = parseInt(opts.seq, 10);
281
+ if (opts.desc || opts.description)
282
+ fields.description = opts.desc || opts.description;
283
+ if (opts.issue !== undefined)
284
+ fields.issue_number = opts.issue ? parseInt(opts.issue, 10) : null;
285
+ const noGitOpt = getBooleanOpt(args, opts, 'no-git');
286
+ if (noGitOpt !== undefined)
287
+ fields.no_git = noGitOpt;
288
+ if (Object.keys(fields).length === 0) {
289
+ console.error('No update options provided.');
290
+ process.exit(1);
291
+ }
292
+ engine_1.engine.updateJob(jobId, fields);
293
+ const updated = engine_1.engine.getJob(jobId);
294
+ console.log(JSON.stringify(updated, null, 2));
295
+ break;
296
+ }
297
+ case 'next': {
298
+ const wbsId = args[1];
299
+ if (!wbsId) {
300
+ console.error('Usage: wbs job next <wbs-id>');
301
+ process.exit(1);
302
+ }
303
+ const job = engine_1.engine.getNextJob(wbsId);
304
+ if (job) {
305
+ engine_1.engine.runWorker(job).then(r => console.log(JSON.stringify(r, null, 2)));
306
+ }
307
+ else {
308
+ console.log('No pending jobs');
309
+ }
310
+ break;
311
+ }
312
+ case 'run': {
313
+ const wbsId = args[1];
314
+ if (!wbsId) {
315
+ console.error('Usage: wbs job run <wbs-id>');
316
+ process.exit(1);
317
+ }
318
+ engine_1.engine.run(wbsId);
319
+ break;
320
+ }
321
+ case 'retry': {
322
+ const jobIdArg = args[1];
323
+ if (!jobIdArg) {
324
+ console.error('Usage: wbs job retry <job-id|slug>');
325
+ process.exit(1);
326
+ }
327
+ const job = (0, engine_1.resolveJobId)(jobIdArg);
328
+ if (!job) {
329
+ console.error(`Job not found: ${jobIdArg}`);
330
+ process.exit(1);
331
+ }
332
+ engine_1.engine.updateJob(job.id, { status: 'pending' });
333
+ console.log(`[WBS] Retrying job: ${job.title}`);
334
+ engine_1.engine.runWorker(job).then(r => console.log(JSON.stringify(r, null, 2)));
335
+ break;
336
+ }
337
+ default:
338
+ console.log(`
339
+ Job Commands - Manage jobs within a WBS project
340
+
341
+ Usage:
342
+ wbs job list <wbs-id> [--json] List all jobs in a project
343
+ wbs job add <wbs-id> [options] Add a new job to the project
344
+ wbs job update <job-id> [options] Update an existing job
345
+ wbs job next <wbs-id> Run the next pending job
346
+ wbs job run <wbs-id> [--parallel] Run all pending jobs (default: sequential)
347
+ wbs job retry <job-id> Retry a failed/completed job
348
+
349
+ Options for 'job add':
350
+ --title "Job Title" Job title (required)
351
+ --agent "@agent" Worker agent (required)
352
+ --seq N Execution sequence (default: 0)
353
+ --desc "..." Detailed instructions
354
+ --issue N Related GitHub issue number
355
+ --no-git Skip git worktree/PR template for non-code jobs
356
+
357
+ Options for 'job update':
358
+ --no-git[=true|false] Set/clear no_git flag
359
+
360
+ Options for 'job run':
361
+ --parallel Run same-seq pending jobs concurrently (job run only)
362
+ `);
363
+ }
364
+ }
365
+ function handleExec(args) {
366
+ const sub = args[0];
367
+ switch (sub) {
368
+ case 'list': {
369
+ const jobIdArg = args[1];
370
+ if (!jobIdArg) {
371
+ console.error('Usage: wbs exec list <job-id|slug> [--json]');
372
+ process.exit(1);
373
+ }
374
+ const resolvedJob = (0, engine_1.resolveJobId)(jobIdArg);
375
+ const jobId = resolvedJob?.id ?? jobIdArg;
376
+ const execs = engine_1.engine.listExecutions(jobId);
377
+ console.log(hasJson(args) ? JSON.stringify(execs, null, 2) : fmtExecList(execs, jobId));
378
+ break;
379
+ }
380
+ case 'status': {
381
+ const execId = args[1];
382
+ if (!execId) {
383
+ console.error('Usage: wbs exec status <exec-id> [--json]');
384
+ process.exit(1);
385
+ }
386
+ const exec = engine_1.engine.getExecution(execId);
387
+ if (!exec) {
388
+ console.error(`Execution not found: ${execId}`);
389
+ process.exit(1);
390
+ }
391
+ console.log(hasJson(args) ? JSON.stringify(exec, null, 2) : fmtExecStatus(exec));
392
+ break;
393
+ }
394
+ case 'kill': {
395
+ const execId = args[1];
396
+ if (!execId) {
397
+ console.error('Usage: wbs exec kill <exec-id>');
398
+ process.exit(1);
399
+ }
400
+ const ok = engine_1.engine.killExecution(execId);
401
+ if (ok) {
402
+ console.log(`Killed execution: ${execId}`);
403
+ }
404
+ else {
405
+ console.error(`Could not kill execution: ${execId} (not running or not found)`);
406
+ process.exit(1);
407
+ }
408
+ break;
409
+ }
410
+ case 'running': {
411
+ const execs = engine_1.engine.getRunningExecutions();
412
+ console.log(hasJson(args) ? JSON.stringify(execs, null, 2) : fmtRunning(execs));
413
+ break;
414
+ }
415
+ default:
416
+ console.log(`
417
+ Execution Commands - Manage job executions
418
+
419
+ Usage:
420
+ wbs exec list <job-id> [--json] List execution history for a job
421
+ wbs exec status <exec-id> [--json] Get execution details
422
+ wbs exec kill <exec-id> Kill a running execution
423
+ wbs exec running [--json] List all running executions
424
+ `);
425
+ }
426
+ }
427
+ function getLocalTimestamp() {
428
+ const now = new Date();
429
+ return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`;
430
+ }
431
+ function daemonTick() {
432
+ const ts = getLocalTimestamp();
433
+ console.log(`[${ts}] Daemon tick`);
434
+ const zombies = engine_1.engine.checkAndFixZombies();
435
+ if (zombies.length > 0) {
436
+ console.log(`[${ts}] Fixed ${zombies.length} zombie(s):`);
437
+ zombies.forEach(z => console.log(` - ${z.jobId}: ${z.reason}`));
438
+ }
439
+ const activeProjects = engine_1.engine.getActiveProjects();
440
+ if (activeProjects.length === 0) {
441
+ console.log(`[${ts}] No active projects`);
442
+ return;
443
+ }
444
+ for (const project of activeProjects) {
445
+ const nextSeq = engine_1.engine.getNextSeq(project.id);
446
+ if (nextSeq === null) {
447
+ console.log(`[${ts}] ${project.id}: No pending jobs`);
448
+ continue;
449
+ }
450
+ const running = engine_1.engine.getJobsBySeq(project.id, nextSeq, 'running');
451
+ const pending = engine_1.engine.getJobsBySeq(project.id, nextSeq, 'pending');
452
+ if (running.length > 0) {
453
+ console.log(`[${ts}] ${project.id}: ${running.length} job(s) running in seq ${nextSeq}`);
454
+ continue;
455
+ }
456
+ if (pending.length > 0) {
457
+ console.log(`[${ts}] ${project.id}: Starting next seq ${nextSeq} (${pending.length} pending)`);
458
+ try {
459
+ (0, child_process_1.execSync)(`crewx x "@wbs_coordinator ${project.id} 다음 작업 진행해줘"`, { stdio: 'inherit', cwd: process.cwd() });
460
+ }
461
+ catch (e) {
462
+ console.error(`[${ts}] Failed to call coordinator: ${e.message}`);
463
+ }
464
+ }
465
+ }
466
+ }
467
+ function startDaemon() {
468
+ const pidFile = engine_1.engine.daemonPidFile;
469
+ if (fs.existsSync(pidFile)) {
470
+ const pid = parseInt(fs.readFileSync(pidFile, 'utf8').trim(), 10);
471
+ if (engine_1.engine.isProcessAlive(pid)) {
472
+ console.error(`[WBS Daemon] Already running (PID: ${pid})`);
473
+ return false;
474
+ }
475
+ fs.unlinkSync(pidFile);
476
+ }
477
+ const logFile = engine_1.engine.daemonLogFile;
478
+ const out = fs.openSync(logFile, 'a');
479
+ const child = (0, child_process_1.spawn)(process.execPath, [__filename, 'daemon', '--foreground'], {
480
+ detached: true,
481
+ stdio: ['ignore', out, out],
482
+ cwd: process.cwd(),
483
+ });
484
+ fs.writeFileSync(pidFile, String(child.pid));
485
+ child.unref();
486
+ console.log(`[WBS Daemon] Started (PID: ${child.pid})`);
487
+ console.log(`[WBS Daemon] Log: ${logFile}`);
488
+ return true;
489
+ }
490
+ function stopDaemon() {
491
+ const pidFile = engine_1.engine.daemonPidFile;
492
+ if (!fs.existsSync(pidFile)) {
493
+ console.error('[WBS Daemon] Not running (no PID file)');
494
+ return false;
495
+ }
496
+ const pid = parseInt(fs.readFileSync(pidFile, 'utf8').trim(), 10);
497
+ if (!engine_1.engine.isProcessAlive(pid)) {
498
+ console.log('[WBS Daemon] Process not found, cleaning up PID file');
499
+ fs.unlinkSync(pidFile);
500
+ return true;
501
+ }
502
+ try {
503
+ process.kill(pid, 'SIGTERM');
504
+ fs.unlinkSync(pidFile);
505
+ console.log(`[WBS Daemon] Stopped (PID: ${pid})`);
506
+ return true;
507
+ }
508
+ catch (e) {
509
+ console.error(`[WBS Daemon] Failed to stop: ${e.message}`);
510
+ return false;
511
+ }
512
+ }
513
+ function handleDaemon(args) {
514
+ const sub = args[0];
515
+ switch (sub) {
516
+ case 'start':
517
+ startDaemon();
518
+ break;
519
+ case 'stop':
520
+ stopDaemon();
521
+ break;
522
+ case 'status': {
523
+ const pidFile = engine_1.engine.daemonPidFile;
524
+ if (!fs.existsSync(pidFile)) {
525
+ const msg = { running: false, message: 'Not running' };
526
+ console.log(hasJson(args) ? JSON.stringify(msg) : `[WBS Daemon] Not running`);
527
+ }
528
+ else {
529
+ const pid = parseInt(fs.readFileSync(pidFile, 'utf8').trim(), 10);
530
+ const alive = engine_1.engine.isProcessAlive(pid);
531
+ const msg = alive ? { running: true, pid, message: `Running (PID: ${pid})` } : { running: false, pid, message: 'Stale PID file (process dead)' };
532
+ console.log(hasJson(args) ? JSON.stringify(msg, null, 2) : `[WBS Daemon] ${msg.message}`);
533
+ }
534
+ break;
535
+ }
536
+ case 'restart':
537
+ stopDaemon();
538
+ setTimeout(() => startDaemon(), 1000);
539
+ break;
540
+ case '--foreground': {
541
+ console.log(`[WBS Daemon] Starting in foreground mode`);
542
+ console.log(`[WBS Daemon] Interval: ${engine_1.DAEMON_INTERVAL_MS / 1000 / 60} minutes`);
543
+ console.log(`[WBS Daemon] Zombie timeout: ${engine_1.ZOMBIE_TIMEOUT_MS / 1000 / 60} minutes`);
544
+ console.log(`[WBS Daemon] Press Ctrl+C to stop\n`);
545
+ daemonTick();
546
+ setInterval(daemonTick, engine_1.DAEMON_INTERVAL_MS);
547
+ break;
548
+ }
549
+ case 'tick':
550
+ daemonTick();
551
+ break;
552
+ default:
553
+ console.log(`
554
+ Daemon Commands - Background scheduler for WBS
555
+
556
+ Usage:
557
+ wbs daemon start Start daemon in background
558
+ wbs daemon stop Stop running daemon
559
+ wbs daemon status Check daemon status
560
+ wbs daemon restart Restart daemon
561
+ wbs daemon tick Manual tick (for testing)
562
+ `);
563
+ }
564
+ }
565
+ function showDetailedUsage() {
566
+ const skillMd = path.join(__dirname, '..', 'SKILL.md');
567
+ if (fs.existsSync(skillMd)) {
568
+ process.stdout.write(fs.readFileSync(skillMd, 'utf-8'));
569
+ }
570
+ else {
571
+ showHelp();
572
+ }
573
+ }
574
+ function showHelp() {
575
+ console.log(`
576
+ @crewx/wbs@${PKG_VERSION} - Work Breakdown Structure for AI agent teams
577
+
578
+ Usage:
579
+ wbs <command> [options]
580
+
581
+ Project Commands:
582
+ wbs create "Title" [--detail "path"] Create a new WBS project
583
+ wbs list [--json] List all projects
584
+ wbs status <wbs-id> [--json] Get project status with jobs summary
585
+ wbs delete <wbs-id> Delete a project and its jobs
586
+
587
+ Job Commands:
588
+ wbs job list <wbs-id> [--json] List all jobs in a project
589
+ wbs job add <wbs-id> [opts] Add a job to the project
590
+ wbs job update <job-id> [opts] Update a job
591
+ wbs job next <wbs-id> Run the next pending job
592
+ wbs job run <wbs-id> Run all pending jobs
593
+ wbs job retry <job-id> Retry a failed/completed job
594
+
595
+ Execution Commands:
596
+ wbs exec list <job-id> List execution history for a job
597
+ wbs exec status <exec-id> Get execution details
598
+ wbs exec kill <exec-id> Kill a running execution
599
+ wbs exec running List all running executions
600
+
601
+ Daemon Commands:
602
+ wbs daemon start Start background scheduler
603
+ wbs daemon stop Stop daemon
604
+ wbs daemon status Check daemon status
605
+
606
+ wbs usage Show detailed guide
607
+
608
+ Options:
609
+ --json Output raw JSON
610
+ --version Show version
611
+ --help, -h Show this help
612
+
613
+ Examples:
614
+ wbs create "User Authentication"
615
+ wbs job add wbs-1 --title "Design API" --agent "@backend" --seq 1
616
+ wbs status wbs-1
617
+ wbs job run wbs-1
618
+
619
+ For detailed usage guide: wbs usage
620
+ `);
621
+ }
622
+ async function main(inputArgs) {
623
+ const args = inputArgs ?? process.argv.slice(2);
624
+ const command = args[0];
625
+ if (command === '--version') {
626
+ console.log(PKG_VERSION);
627
+ return;
628
+ }
629
+ switch (command) {
630
+ case 'create': {
631
+ const title = args[1];
632
+ if (!title) {
633
+ console.error('Usage: wbs create "Project Title" [--detail "path"] [--source "branch"] [--target "branch"]');
634
+ process.exit(1);
635
+ }
636
+ const opts = parseOpts(args, 2);
637
+ const detailOpt = opts.detail ?? null;
638
+ const sourceBranchOpt = opts.source || undefined;
639
+ const targetBranchOpt = opts.target || undefined;
640
+ const id = engine_1.engine.create(title, detailOpt, sourceBranchOpt, targetBranchOpt);
641
+ let detailPath = detailOpt;
642
+ if (!detailPath) {
643
+ const detailsDir = path.join(process.cwd(), 'wbs-details');
644
+ if (!fs.existsSync(detailsDir))
645
+ fs.mkdirSync(detailsDir, { recursive: true });
646
+ detailPath = `wbs-details/${id}-detail.md`;
647
+ const detailFile = path.join(process.cwd(), detailPath);
648
+ fs.writeFileSync(detailFile, `# ${title}\n\n## 개요\n\n(프로젝트 개요를 작성하세요)\n\n## 요구사항\n\n(요구사항을 작성하세요)\n\n## 참고 자료\n\n(참고 자료 링크나 설명을 추가하세요)\n`);
649
+ engine_1.engine.updateDetailPath(id, detailPath);
650
+ }
651
+ console.log(JSON.stringify({ id, title, detailPath }, null, 2));
652
+ break;
653
+ }
654
+ case 'list': {
655
+ const projects = engine_1.engine.list();
656
+ console.log(hasJson(args) ? JSON.stringify(projects, null, 2) : fmtProjectList(projects));
657
+ break;
658
+ }
659
+ case 'status': {
660
+ const wbsId = args[1];
661
+ if (!wbsId) {
662
+ console.error('Usage: wbs status <wbs-id> [--json]');
663
+ process.exit(1);
664
+ }
665
+ const result = engine_1.engine.status(wbsId);
666
+ if (result.project) {
667
+ const pending = result.jobs.filter(j => j.status === 'pending').length;
668
+ const running = result.jobs.filter(j => j.status === 'running').length;
669
+ const completed = result.jobs.filter(j => j.status === 'completed').length;
670
+ const failed = result.jobs.filter(j => j.status === 'failed').length;
671
+ result.summary = {
672
+ total: result.jobs.length, pending, running, completed, failed,
673
+ progress: result.jobs.length > 0 ? Math.round((completed / result.jobs.length) * 100) : 0,
674
+ };
675
+ }
676
+ console.log(hasJson(args) ? JSON.stringify(result, null, 2) : fmtStatus(result));
677
+ break;
678
+ }
679
+ case 'delete': {
680
+ const wbsId = args[1];
681
+ if (!wbsId) {
682
+ console.error('Usage: wbs delete <wbs-id>');
683
+ process.exit(1);
684
+ }
685
+ engine_1.engine.delete(wbsId);
686
+ console.log(`Deleted: ${wbsId}`);
687
+ break;
688
+ }
689
+ case 'help':
690
+ showHelp();
691
+ break;
692
+ case 'usage':
693
+ showDetailedUsage();
694
+ break;
695
+ case 'job':
696
+ handleJob(args.slice(1));
697
+ break;
698
+ case 'exec':
699
+ handleExec(args.slice(1));
700
+ break;
701
+ case 'daemon':
702
+ handleDaemon(args.slice(1));
703
+ break;
704
+ default: showHelp();
705
+ }
706
+ }
707
+ if (require.main === module) {
708
+ const _args = process.argv.slice(2);
709
+ const _isDaemonForeground = _args[0] === 'daemon' && _args.includes('--foreground');
710
+ (_isDaemonForeground ? Promise.resolve(null) : loadTracer()).then(tracer => {
711
+ const opts = tracer ? { skillVersion: tracer.getOwnSkillVersion(__dirname) } : {};
712
+ return tracer ? tracer.run('crewx/wbs', () => main(), opts) : main();
713
+ }).catch((e) => {
714
+ console.error(e.message);
715
+ process.exit(1);
716
+ });
717
+ }
718
+ //# sourceMappingURL=cli.js.map