cli-copilot-worker 0.1.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 (72) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +30 -0
  3. package/bin/cli-copilot-worker.mjs +3 -0
  4. package/dist/src/cli.d.ts +2 -0
  5. package/dist/src/cli.js +682 -0
  6. package/dist/src/cli.js.map +1 -0
  7. package/dist/src/core/copilot.d.ts +13 -0
  8. package/dist/src/core/copilot.js +56 -0
  9. package/dist/src/core/copilot.js.map +1 -0
  10. package/dist/src/core/failure-classifier.d.ts +8 -0
  11. package/dist/src/core/failure-classifier.js +148 -0
  12. package/dist/src/core/failure-classifier.js.map +1 -0
  13. package/dist/src/core/ids.d.ts +1 -0
  14. package/dist/src/core/ids.js +11 -0
  15. package/dist/src/core/ids.js.map +1 -0
  16. package/dist/src/core/markdown.d.ts +5 -0
  17. package/dist/src/core/markdown.js +14 -0
  18. package/dist/src/core/markdown.js.map +1 -0
  19. package/dist/src/core/paths.d.ts +18 -0
  20. package/dist/src/core/paths.js +42 -0
  21. package/dist/src/core/paths.js.map +1 -0
  22. package/dist/src/core/profile-faults.d.ts +15 -0
  23. package/dist/src/core/profile-faults.js +110 -0
  24. package/dist/src/core/profile-faults.js.map +1 -0
  25. package/dist/src/core/profile-manager.d.ts +25 -0
  26. package/dist/src/core/profile-manager.js +162 -0
  27. package/dist/src/core/profile-manager.js.map +1 -0
  28. package/dist/src/core/question-registry.d.ts +25 -0
  29. package/dist/src/core/question-registry.js +154 -0
  30. package/dist/src/core/question-registry.js.map +1 -0
  31. package/dist/src/core/store.d.ts +39 -0
  32. package/dist/src/core/store.js +206 -0
  33. package/dist/src/core/store.js.map +1 -0
  34. package/dist/src/core/types.d.ts +152 -0
  35. package/dist/src/core/types.js +2 -0
  36. package/dist/src/core/types.js.map +1 -0
  37. package/dist/src/daemon/client.d.ts +6 -0
  38. package/dist/src/daemon/client.js +117 -0
  39. package/dist/src/daemon/client.js.map +1 -0
  40. package/dist/src/daemon/server.d.ts +1 -0
  41. package/dist/src/daemon/server.js +149 -0
  42. package/dist/src/daemon/server.js.map +1 -0
  43. package/dist/src/daemon/service.d.ts +69 -0
  44. package/dist/src/daemon/service.js +800 -0
  45. package/dist/src/daemon/service.js.map +1 -0
  46. package/dist/src/doctor.d.ts +1 -0
  47. package/dist/src/doctor.js +74 -0
  48. package/dist/src/doctor.js.map +1 -0
  49. package/dist/src/index.d.ts +3 -0
  50. package/dist/src/index.js +4 -0
  51. package/dist/src/index.js.map +1 -0
  52. package/dist/src/output.d.ts +28 -0
  53. package/dist/src/output.js +307 -0
  54. package/dist/src/output.js.map +1 -0
  55. package/package.json +59 -0
  56. package/src/cli.ts +881 -0
  57. package/src/core/copilot.ts +75 -0
  58. package/src/core/failure-classifier.ts +202 -0
  59. package/src/core/ids.ts +11 -0
  60. package/src/core/markdown.ts +19 -0
  61. package/src/core/paths.ts +56 -0
  62. package/src/core/profile-faults.ts +140 -0
  63. package/src/core/profile-manager.ts +220 -0
  64. package/src/core/question-registry.ts +191 -0
  65. package/src/core/store.ts +273 -0
  66. package/src/core/types.ts +211 -0
  67. package/src/daemon/client.ts +137 -0
  68. package/src/daemon/server.ts +167 -0
  69. package/src/daemon/service.ts +968 -0
  70. package/src/doctor.ts +82 -0
  71. package/src/index.ts +3 -0
  72. package/src/output.ts +391 -0
@@ -0,0 +1,682 @@
1
+ #!/usr/bin/env node
2
+ import { resolve as resolvePath } from 'node:path';
3
+ import process from 'node:process';
4
+ import { Command } from 'commander';
5
+ import pkg from '../package.json' with { type: 'json' };
6
+ import { readMarkdownFile } from './core/markdown.js';
7
+ import { daemonIsRunning, ensureDaemonMeta, sendDaemonRequest } from './daemon/client.js';
8
+ import { runDaemonServer } from './daemon/server.js';
9
+ import { inspectDoctor } from './doctor.js';
10
+ import { createEventPrinter, formatActions, formatAttemptHistory, formatEntries, formatPendingQuestion, formatProfileSummary, formatProfilesSection, printJson, resolveOutputFormat, shortenPath, } from './output.js';
11
+ function getOutputFormat(program) {
12
+ return resolveOutputFormat(program.opts().output);
13
+ }
14
+ function parseInteger(value, label) {
15
+ if (value === undefined) {
16
+ return undefined;
17
+ }
18
+ const parsed = Number.parseInt(value, 10);
19
+ if (Number.isNaN(parsed)) {
20
+ throw new Error(`Invalid ${label}: ${value}`);
21
+ }
22
+ return parsed;
23
+ }
24
+ function isRecord(value) {
25
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
26
+ }
27
+ function stringValue(value) {
28
+ return typeof value === 'string' && value.length > 0 ? value : undefined;
29
+ }
30
+ function extractPendingQuestion(result) {
31
+ const candidates = [
32
+ result.pendingQuestion,
33
+ isRecord(result.conversation) ? result.conversation.pendingQuestion : undefined,
34
+ ];
35
+ for (const candidate of candidates) {
36
+ if (!isRecord(candidate)) {
37
+ continue;
38
+ }
39
+ if (typeof candidate.question === 'string' && typeof candidate.allowFreeform === 'boolean' && typeof candidate.askedAt === 'string' && typeof candidate.sessionId === 'string') {
40
+ return candidate;
41
+ }
42
+ }
43
+ return undefined;
44
+ }
45
+ function extractAttempts(result) {
46
+ if (!isRecord(result)) {
47
+ return undefined;
48
+ }
49
+ if (Array.isArray(result.attempts)) {
50
+ return result.attempts;
51
+ }
52
+ if (isRecord(result.job) && Array.isArray(result.job.attempts)) {
53
+ return result.job.attempts;
54
+ }
55
+ if (isRecord(result.conversation) && Array.isArray(result.conversation.attempts)) {
56
+ return result.conversation.attempts;
57
+ }
58
+ return undefined;
59
+ }
60
+ function formatProfileIdentity(result) {
61
+ const sources = [
62
+ result,
63
+ isRecord(result.conversation) ? result.conversation : undefined,
64
+ isRecord(result.job) ? result.job : undefined,
65
+ ].filter((source) => source !== undefined);
66
+ for (const source of sources) {
67
+ const hasProfileSignals = source.activeProfileId !== undefined ||
68
+ source.currentProfileId !== undefined ||
69
+ source.profileId !== undefined ||
70
+ source.activeProfileConfigDir !== undefined ||
71
+ source.currentProfileConfigDir !== undefined ||
72
+ source.profileConfigDir !== undefined ||
73
+ source.cooldownUntil !== undefined ||
74
+ source.failureCount !== undefined ||
75
+ source.lastFailureCategory !== undefined ||
76
+ source.lastFailureReason !== undefined ||
77
+ source.authenticated !== undefined ||
78
+ source.login !== undefined ||
79
+ source.models !== undefined;
80
+ if (!hasProfileSignals) {
81
+ continue;
82
+ }
83
+ const id = stringValue(source.activeProfileId)
84
+ ?? stringValue(source.currentProfileId)
85
+ ?? stringValue(source.profileId)
86
+ ?? stringValue(source.id);
87
+ const configDir = stringValue(source.activeProfileConfigDir)
88
+ ?? stringValue(source.currentProfileConfigDir)
89
+ ?? stringValue(source.profileConfigDir)
90
+ ?? stringValue(source.configDir);
91
+ if (id || configDir) {
92
+ return [
93
+ id ?? 'unknown',
94
+ configDir ? shortenPath(configDir) : undefined,
95
+ ].filter((part) => part !== undefined).join(' ');
96
+ }
97
+ }
98
+ return undefined;
99
+ }
100
+ function formatPrimitiveFields(result, excludeKeys) {
101
+ return Object.entries(result)
102
+ .filter(([key, value]) => !excludeKeys.includes(key) && (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean'))
103
+ .map(([key, value]) => `${key}: ${String(value)}`);
104
+ }
105
+ function renderAttemptsSection(result) {
106
+ return formatAttemptHistory(extractAttempts(result));
107
+ }
108
+ function renderProfilesSection(result, options) {
109
+ return formatProfilesSection(result.profiles, options);
110
+ }
111
+ function indentBlock(text, prefix = ' ') {
112
+ return text.split('\n').map((line) => `${prefix}${line}`).join('\n');
113
+ }
114
+ function renderRunLikeResult(result) {
115
+ const lines = [
116
+ `Conversation: ${String(result.conversationId ?? 'unknown')}`,
117
+ `Job: ${String(result.jobId ?? 'unknown')}`,
118
+ `Status: ${String(result.status ?? 'unknown')}`,
119
+ ];
120
+ const profile = formatProfileIdentity(result);
121
+ if (profile) {
122
+ lines.push('');
123
+ lines.push(`Profile: ${profile}`);
124
+ }
125
+ const attempts = renderAttemptsSection(result);
126
+ if (attempts) {
127
+ lines.push('');
128
+ lines.push(attempts);
129
+ }
130
+ const pendingQuestion = extractPendingQuestion(result);
131
+ if (pendingQuestion) {
132
+ lines.push('');
133
+ lines.push('Pending Question:');
134
+ lines.push(formatPendingQuestion(pendingQuestion));
135
+ }
136
+ lines.push('');
137
+ lines.push('Actions:');
138
+ lines.push(formatActions(result.actions));
139
+ return lines.join('\n');
140
+ }
141
+ function renderListResult(result) {
142
+ const conversations = Array.isArray(result.conversations)
143
+ ? result.conversations
144
+ : [];
145
+ if (conversations.length === 0) {
146
+ return 'No conversations.';
147
+ }
148
+ return conversations
149
+ .map((conversation) => {
150
+ const pending = typeof conversation.pendingQuestion === 'string' ? ' pending-question' : '';
151
+ return [
152
+ `${String(conversation.id)} ${String(conversation.status)}${pending}`,
153
+ ` cwd: ${shortenPath(String(conversation.cwd ?? ''))}`,
154
+ ` model: ${String(conversation.model ?? '')}`,
155
+ ` updated: ${String(conversation.updatedAt ?? '')}`,
156
+ ].join('\n');
157
+ })
158
+ .join('\n\n');
159
+ }
160
+ function renderInfoResult(result) {
161
+ const conversation = result.conversation;
162
+ const jobs = Array.isArray(result.jobs) ? result.jobs : [];
163
+ const lines = [
164
+ `Conversation: ${String(conversation.id ?? 'unknown')}`,
165
+ `Status: ${String(conversation.status ?? 'unknown')}`,
166
+ `Model: ${String(conversation.model ?? 'unknown')}`,
167
+ `cwd: ${shortenPath(String(conversation.cwd ?? ''))}`,
168
+ `Created: ${String(conversation.createdAt ?? '')}`,
169
+ `Updated: ${String(conversation.updatedAt ?? '')}`,
170
+ ];
171
+ const profile = formatProfileIdentity(conversation);
172
+ if (profile) {
173
+ lines.push('');
174
+ lines.push(`Profile: ${profile}`);
175
+ }
176
+ const pendingQuestion = extractPendingQuestion(conversation);
177
+ if (pendingQuestion) {
178
+ lines.push('');
179
+ lines.push('Pending Question:');
180
+ lines.push(formatPendingQuestion(pendingQuestion));
181
+ }
182
+ lines.push('');
183
+ lines.push('Jobs:');
184
+ if (jobs.length === 0) {
185
+ lines.push('- none');
186
+ }
187
+ else {
188
+ jobs.forEach((job) => {
189
+ const jobProfile = formatProfileIdentity(job);
190
+ const attempts = renderAttemptsSection(job);
191
+ lines.push([
192
+ `- ${String(job.id)} ${String(job.kind)} ${String(job.status)} started=${String(job.startedAt ?? '')}`,
193
+ jobProfile ? `profile=${jobProfile}` : undefined,
194
+ Array.isArray(job.attempts) ? `attempts=${job.attempts.length}` : undefined,
195
+ job.error ? `error=${String(job.error)}` : undefined,
196
+ ].filter((part) => part !== undefined).join(' '));
197
+ if (attempts) {
198
+ lines.push(indentBlock(attempts));
199
+ }
200
+ });
201
+ }
202
+ return lines.join('\n');
203
+ }
204
+ function renderJobResult(result) {
205
+ const job = result.job;
206
+ const lines = [
207
+ `Job: ${String(job.id ?? 'unknown')}`,
208
+ `Conversation: ${String(job.conversationId ?? 'unknown')}`,
209
+ `Kind: ${String(job.kind ?? 'unknown')}`,
210
+ `Status: ${String(job.status ?? 'unknown')}`,
211
+ `Input: ${shortenPath(String(job.inputFilePath ?? ''))}`,
212
+ `Started: ${String(job.startedAt ?? '')}`,
213
+ ];
214
+ if (job.endedAt) {
215
+ lines.push(`Ended: ${String(job.endedAt)}`);
216
+ }
217
+ if (job.error) {
218
+ lines.push(`Error: ${String(job.error)}`);
219
+ }
220
+ const attempts = renderAttemptsSection(result);
221
+ if (attempts) {
222
+ lines.push('');
223
+ lines.push(attempts);
224
+ }
225
+ const pendingQuestion = extractPendingQuestion(result);
226
+ if (pendingQuestion) {
227
+ lines.push('');
228
+ lines.push('Pending Question:');
229
+ lines.push(formatPendingQuestion(pendingQuestion));
230
+ }
231
+ lines.push('');
232
+ lines.push('Actions:');
233
+ lines.push(formatActions({
234
+ ...result.actions,
235
+ answer: job.status === 'waiting_answer'
236
+ ? `cli-copilot-worker answer ${String(job.conversationId)} answer.md`
237
+ : null,
238
+ }));
239
+ return lines.join('\n');
240
+ }
241
+ function renderReadResult(result) {
242
+ const lines = [
243
+ `Conversation: ${result.conversation.id}`,
244
+ `Status: ${result.conversation.status}`,
245
+ `cwd: ${shortenPath(result.conversation.cwd)}`,
246
+ `Model: ${result.conversation.model}`,
247
+ `Entries: ${result.meta.returnedEntries}/${result.meta.totalEntries}`,
248
+ `Approx Tokens: ${result.meta.approxTokens}/${result.meta.tokenBudget}`,
249
+ ];
250
+ const profile = formatProfileIdentity(result.conversation);
251
+ if (profile) {
252
+ lines.push('');
253
+ lines.push(`Profile: ${profile}`);
254
+ }
255
+ const pendingQuestion = extractPendingQuestion(result.conversation);
256
+ if (pendingQuestion) {
257
+ lines.push('');
258
+ lines.push('Pending Question:');
259
+ lines.push(formatPendingQuestion(pendingQuestion));
260
+ }
261
+ const attempts = renderAttemptsSection(result);
262
+ if (attempts) {
263
+ lines.push('');
264
+ lines.push(attempts);
265
+ }
266
+ lines.push('');
267
+ lines.push(formatEntries(result.entries));
268
+ lines.push('');
269
+ lines.push('Actions:');
270
+ lines.push(formatActions(result.meta.nextActions));
271
+ return lines.join('\n');
272
+ }
273
+ function renderDaemonResult(result) {
274
+ const lines = formatPrimitiveFields(result, ['profiles', 'attempts', 'jobs']);
275
+ const profiles = renderProfilesSection(result, { includeHealth: true });
276
+ if (profiles) {
277
+ if (lines.length > 0) {
278
+ lines.push('');
279
+ }
280
+ lines.push(profiles);
281
+ }
282
+ const attempts = renderAttemptsSection(result);
283
+ if (attempts) {
284
+ if (lines.length > 0) {
285
+ lines.push('');
286
+ }
287
+ lines.push(attempts);
288
+ }
289
+ return lines.join('\n');
290
+ }
291
+ async function readRequestPayload(filePath) {
292
+ return await readMarkdownFile(filePath, process.cwd());
293
+ }
294
+ async function handleRun(taskPath, options, program) {
295
+ const payload = await readRequestPayload(taskPath);
296
+ const output = getOutputFormat(program);
297
+ const eventPrinter = createEventPrinter(output === 'text' && !options.async && (process.stdout.isTTY ?? false));
298
+ const result = await sendDaemonRequest('run', {
299
+ inputFilePath: payload.path,
300
+ content: payload.content,
301
+ cwd: resolvePath(options.cwd ?? process.cwd()),
302
+ model: options.model,
303
+ timeoutMs: parseInteger(options.timeout, 'timeout'),
304
+ async: options.async ?? false,
305
+ }, { onEvent: eventPrinter.onEvent });
306
+ eventPrinter.finish();
307
+ if (output === 'json') {
308
+ printJson(result);
309
+ return;
310
+ }
311
+ process.stdout.write(`${renderRunLikeResult(result)}\n`);
312
+ }
313
+ async function handleSend(conversationId, messagePath, options, program) {
314
+ const payload = await readRequestPayload(messagePath);
315
+ const output = getOutputFormat(program);
316
+ const eventPrinter = createEventPrinter(output === 'text' && !options.async && (process.stdout.isTTY ?? false));
317
+ const result = await sendDaemonRequest('send', {
318
+ conversationId,
319
+ inputFilePath: payload.path,
320
+ content: payload.content,
321
+ timeoutMs: parseInteger(options.timeout, 'timeout'),
322
+ async: options.async ?? false,
323
+ }, { onEvent: eventPrinter.onEvent });
324
+ eventPrinter.finish();
325
+ if (output === 'json') {
326
+ printJson(result);
327
+ return;
328
+ }
329
+ process.stdout.write(`${renderRunLikeResult(result)}\n`);
330
+ }
331
+ async function handleAnswer(conversationId, answerPath, program) {
332
+ const payload = await readRequestPayload(answerPath);
333
+ const result = await sendDaemonRequest('answer', {
334
+ conversationId,
335
+ inputFilePath: payload.path,
336
+ content: payload.content,
337
+ });
338
+ if (getOutputFormat(program) === 'json') {
339
+ printJson(result);
340
+ return;
341
+ }
342
+ process.stdout.write([
343
+ `Conversation: ${String(result.conversationId ?? conversationId)}`,
344
+ `Status: ${String(result.status ?? 'submitted')}`,
345
+ `Answer: ${String(result.answer ?? '')}`,
346
+ '',
347
+ 'Actions:',
348
+ `- read: cli-copilot-worker read ${String(result.conversationId ?? conversationId)} --follow`,
349
+ `- info: cli-copilot-worker info ${String(result.conversationId ?? conversationId)}`,
350
+ ].join('\n') + '\n');
351
+ }
352
+ async function handleRead(conversationId, options, program) {
353
+ const output = getOutputFormat(program);
354
+ if (options.follow && output === 'json') {
355
+ throw new Error('--follow only supports text output');
356
+ }
357
+ let after = parseInteger(options.after, 'after');
358
+ let firstPass = true;
359
+ while (true) {
360
+ const result = await sendDaemonRequest('read', {
361
+ conversationId,
362
+ from: firstPass ? parseInteger(options.from, 'from') : undefined,
363
+ to: firstPass ? parseInteger(options.to, 'to') : undefined,
364
+ after,
365
+ before: firstPass ? parseInteger(options.before, 'before') : undefined,
366
+ tokens: parseInteger(options.tokens, 'tokens'),
367
+ detail: options.detail,
368
+ });
369
+ if (output === 'json') {
370
+ printJson(result);
371
+ return;
372
+ }
373
+ if (firstPass || result.entries.length > 0) {
374
+ process.stdout.write(`${renderReadResult(result)}\n`);
375
+ }
376
+ if (!options.follow) {
377
+ return;
378
+ }
379
+ const lastEntry = result.entries[result.entries.length - 1];
380
+ after = lastEntry?.index ?? after;
381
+ firstPass = false;
382
+ if (result.conversation.status !== 'running') {
383
+ return;
384
+ }
385
+ await new Promise((resolve) => setTimeout(resolve, 2000));
386
+ }
387
+ }
388
+ async function handleList(program) {
389
+ const result = await sendDaemonRequest('list');
390
+ if (getOutputFormat(program) === 'json') {
391
+ printJson(result);
392
+ return;
393
+ }
394
+ process.stdout.write(`${renderListResult(result)}\n`);
395
+ }
396
+ async function handleInfo(conversationId, program) {
397
+ const result = await sendDaemonRequest('info', { conversationId });
398
+ if (getOutputFormat(program) === 'json') {
399
+ printJson(result);
400
+ return;
401
+ }
402
+ process.stdout.write(`${renderInfoResult(result)}\n`);
403
+ }
404
+ async function handleJobStatus(jobId, program) {
405
+ const result = await sendDaemonRequest('job.status', { jobId });
406
+ if (getOutputFormat(program) === 'json') {
407
+ printJson(result);
408
+ return;
409
+ }
410
+ process.stdout.write(`${renderJobResult(result)}\n`);
411
+ }
412
+ async function handleJobList(program) {
413
+ const result = await sendDaemonRequest('job.list');
414
+ if (getOutputFormat(program) === 'json') {
415
+ printJson(result);
416
+ return;
417
+ }
418
+ const jobs = Array.isArray(result.jobs) ? result.jobs : [];
419
+ if (jobs.length === 0) {
420
+ process.stdout.write('No jobs.\n');
421
+ return;
422
+ }
423
+ const lines = jobs.map((job) => `${String(job.id)} ${String(job.kind)} ${String(job.status)} conversation=${String(job.conversationId)}`);
424
+ process.stdout.write(`${lines.join('\n')}\n`);
425
+ }
426
+ async function handleJobWait(jobId, options, program) {
427
+ const result = await sendDaemonRequest('job.wait', {
428
+ jobId,
429
+ timeoutSeconds: parseInteger(options.timeout, 'timeout'),
430
+ intervalSeconds: parseInteger(options.interval, 'interval'),
431
+ });
432
+ if (getOutputFormat(program) === 'json') {
433
+ printJson(result);
434
+ return;
435
+ }
436
+ process.stdout.write(`${renderJobResult(result)}\n`);
437
+ }
438
+ async function handleJobRead(jobId, program) {
439
+ const result = await sendDaemonRequest('job.read', { jobId });
440
+ if (getOutputFormat(program) === 'json') {
441
+ printJson(result);
442
+ return;
443
+ }
444
+ const job = result.job;
445
+ const entries = Array.isArray(result.entries) ? result.entries : [];
446
+ const attempts = renderAttemptsSection(result);
447
+ const pendingQuestion = extractPendingQuestion(result);
448
+ const profile = formatProfileIdentity(job);
449
+ process.stdout.write([
450
+ `Job: ${String(job.id ?? jobId)}`,
451
+ `Conversation: ${String(job.conversationId ?? 'unknown')}`,
452
+ `Status: ${String(job.status ?? 'unknown')}`,
453
+ `Kind: ${String(job.kind ?? 'unknown')}`,
454
+ `Started: ${String(job.startedAt ?? '')}`,
455
+ ...(job.endedAt ? [`Ended: ${String(job.endedAt)}`] : []),
456
+ ...(profile ? [`Profile: ${profile}`] : []),
457
+ '',
458
+ ...(attempts ? [attempts, ''] : []),
459
+ ...(pendingQuestion
460
+ ? ['Pending Question:', formatPendingQuestion(pendingQuestion), '']
461
+ : []),
462
+ formatEntries(entries),
463
+ ].join('\n') + '\n');
464
+ }
465
+ async function handleJobCancel(jobId, program) {
466
+ const result = await sendDaemonRequest('job.cancel', { jobId });
467
+ if (getOutputFormat(program) === 'json') {
468
+ printJson(result);
469
+ return;
470
+ }
471
+ process.stdout.write(`${renderJobResult(result)}\n`);
472
+ }
473
+ async function handleDaemonStart(program) {
474
+ const meta = await ensureDaemonMeta();
475
+ const result = await sendDaemonRequest('daemon.status');
476
+ const payload = { ...result, pid: meta.pid, startedAt: meta.startedAt };
477
+ if (getOutputFormat(program) === 'json') {
478
+ printJson(payload);
479
+ return;
480
+ }
481
+ process.stdout.write(`${renderDaemonResult(payload)}\n`);
482
+ }
483
+ async function handleDaemonStatus(program) {
484
+ if (!await daemonIsRunning()) {
485
+ const result = { status: 'stopped' };
486
+ if (getOutputFormat(program) === 'json') {
487
+ printJson(result);
488
+ return;
489
+ }
490
+ process.stdout.write('status: stopped\n');
491
+ return;
492
+ }
493
+ const result = await sendDaemonRequest('daemon.status');
494
+ if (getOutputFormat(program) === 'json') {
495
+ printJson(result);
496
+ return;
497
+ }
498
+ process.stdout.write(`${renderDaemonResult(result)}\n`);
499
+ }
500
+ async function handleDaemonStop(program) {
501
+ if (!await daemonIsRunning()) {
502
+ const result = { status: 'stopped' };
503
+ if (getOutputFormat(program) === 'json') {
504
+ printJson(result);
505
+ return;
506
+ }
507
+ process.stdout.write('status: stopped\n');
508
+ return;
509
+ }
510
+ const result = await sendDaemonRequest('daemon.stop');
511
+ if (getOutputFormat(program) === 'json') {
512
+ printJson(result);
513
+ return;
514
+ }
515
+ process.stdout.write(`${renderDaemonResult(result)}\n`);
516
+ }
517
+ async function handleDoctor(program) {
518
+ const result = await inspectDoctor();
519
+ if (getOutputFormat(program) === 'json') {
520
+ printJson(result);
521
+ return;
522
+ }
523
+ const profiles = Array.isArray(result.profiles) ? result.profiles : [];
524
+ const now = Date.now();
525
+ process.stdout.write([
526
+ `Node: ${String(result.node ?? '')}`,
527
+ `Copilot CLI: ${String(result.copilot ?? 'not found')}`,
528
+ `mcpc: ${String(result.mcpc ?? 'not found')}`,
529
+ `Daemon running: ${String(result.daemonRunning ?? false)}`,
530
+ `State root: ${shortenPath(String(result.stateRoot ?? ''))}`,
531
+ '',
532
+ 'Profiles:',
533
+ ...(profiles.length > 0
534
+ ? profiles.map((profile) => formatProfileSummary(profile, {
535
+ includeAuth: true,
536
+ includeModels: true,
537
+ includeHealth: true,
538
+ now,
539
+ }))
540
+ : ['- none']),
541
+ ].join('\n') + '\n');
542
+ }
543
+ const program = new Command();
544
+ program
545
+ .name('cli-copilot-worker')
546
+ .description('Copilot-only worker CLI')
547
+ .version(pkg.version)
548
+ .option('--output <format>', 'Output format: text or json');
549
+ program
550
+ .command('run')
551
+ .argument('<task.md>')
552
+ .description('Run a Markdown task file')
553
+ .option('--cwd <dir>', 'Working directory for the Copilot session')
554
+ .option('--model <id>', 'Copilot model id')
555
+ .option('--timeout <ms>', 'Timeout in milliseconds')
556
+ .option('--async', 'Return immediately and keep the job in the daemon')
557
+ .action(async (taskPath, options) => {
558
+ await handleRun(taskPath, options, program);
559
+ });
560
+ program
561
+ .command('send')
562
+ .argument('<conversation-id>')
563
+ .argument('<message.md>')
564
+ .description('Send a Markdown follow-up message to an existing conversation')
565
+ .option('--timeout <ms>', 'Timeout in milliseconds')
566
+ .option('--async', 'Return immediately and keep the job in the daemon')
567
+ .action(async (conversationId, messagePath, options) => {
568
+ await handleSend(conversationId, messagePath, options, program);
569
+ });
570
+ program
571
+ .command('answer')
572
+ .argument('<conversation-id>')
573
+ .argument('<answer.md>')
574
+ .description('Answer a pending Copilot question from a Markdown file')
575
+ .action(async (conversationId, answerPath) => {
576
+ await handleAnswer(conversationId, answerPath, program);
577
+ });
578
+ program
579
+ .command('read')
580
+ .argument('<conversation-id>')
581
+ .description('Read a conversation transcript')
582
+ .option('--from <n>', 'First transcript entry index')
583
+ .option('--to <n>', 'Last transcript entry index')
584
+ .option('--after <n>', 'Only show entries after index N')
585
+ .option('--before <n>', 'Only show entries before index N')
586
+ .option('--tokens <n>', 'Approx token budget for the returned slice')
587
+ .option('--detail <level>', 'Detail level: standard, verbose, full, meta')
588
+ .option('--follow', 'Poll for new transcript entries while the conversation is running')
589
+ .action(async (conversationId, options) => {
590
+ await handleRead(conversationId, options, program);
591
+ });
592
+ program
593
+ .command('list')
594
+ .description('List conversations')
595
+ .action(async () => {
596
+ await handleList(program);
597
+ });
598
+ program
599
+ .command('info')
600
+ .argument('<conversation-id>')
601
+ .description('Show conversation metadata')
602
+ .action(async (conversationId) => {
603
+ await handleInfo(conversationId, program);
604
+ });
605
+ const job = program
606
+ .command('job')
607
+ .description('Inspect background jobs');
608
+ job
609
+ .command('list')
610
+ .description('List jobs')
611
+ .action(async () => {
612
+ await handleJobList(program);
613
+ });
614
+ job
615
+ .command('status')
616
+ .argument('<job-id>')
617
+ .description('Show job status')
618
+ .action(async (jobId) => {
619
+ await handleJobStatus(jobId, program);
620
+ });
621
+ job
622
+ .command('wait')
623
+ .argument('<job-id>')
624
+ .description('Wait for an async job to finish')
625
+ .option('--timeout <seconds>', 'Timeout in seconds')
626
+ .option('--interval <seconds>', 'Polling interval in seconds')
627
+ .action(async (jobId, options) => {
628
+ await handleJobWait(jobId, options, program);
629
+ });
630
+ job
631
+ .command('read')
632
+ .argument('<job-id>')
633
+ .description('Read transcript entries for a single job')
634
+ .action(async (jobId) => {
635
+ await handleJobRead(jobId, program);
636
+ });
637
+ job
638
+ .command('cancel')
639
+ .argument('<job-id>')
640
+ .description('Cancel a running job')
641
+ .action(async (jobId) => {
642
+ await handleJobCancel(jobId, program);
643
+ });
644
+ const daemon = program
645
+ .command('daemon')
646
+ .description('Manage the local cli-copilot-worker daemon');
647
+ daemon
648
+ .command('start')
649
+ .description('Start the daemon if it is not already running')
650
+ .action(async () => {
651
+ await handleDaemonStart(program);
652
+ });
653
+ daemon
654
+ .command('status')
655
+ .description('Show daemon status')
656
+ .action(async () => {
657
+ await handleDaemonStatus(program);
658
+ });
659
+ daemon
660
+ .command('stop')
661
+ .description('Stop the daemon')
662
+ .action(async () => {
663
+ await handleDaemonStop(program);
664
+ });
665
+ program
666
+ .command('doctor')
667
+ .description('Check local environment and Copilot auth')
668
+ .action(async () => {
669
+ await handleDoctor(program);
670
+ });
671
+ program
672
+ .command('daemon-run', { hidden: true })
673
+ .description('Internal daemon entrypoint')
674
+ .action(async () => {
675
+ await runDaemonServer();
676
+ });
677
+ await program.parseAsync(process.argv).catch((error) => {
678
+ const message = error instanceof Error ? error.message : String(error);
679
+ process.stderr.write(`${message}\n`);
680
+ process.exit(1);
681
+ });
682
+ //# sourceMappingURL=cli.js.map