agent-office 0.6.22 → 0.7.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 (58) hide show
  1. package/AGENTS.md +0 -0
  2. package/CONTEXT.md +77 -0
  3. package/README.md +87 -6
  4. package/dist/commands/cron-requests.d.ts +5 -5
  5. package/dist/commands/cron-requests.d.ts.map +1 -1
  6. package/dist/commands/cron-requests.js +9 -14
  7. package/dist/commands/cron-requests.js.map +1 -1
  8. package/dist/commands/crons.d.ts +9 -9
  9. package/dist/commands/crons.d.ts.map +1 -1
  10. package/dist/commands/crons.js +17 -27
  11. package/dist/commands/crons.js.map +1 -1
  12. package/dist/commands/messages.d.ts +6 -4
  13. package/dist/commands/messages.d.ts.map +1 -1
  14. package/dist/commands/messages.js +18 -13
  15. package/dist/commands/messages.js.map +1 -1
  16. package/dist/commands/sessions.d.ts +5 -5
  17. package/dist/commands/sessions.d.ts.map +1 -1
  18. package/dist/commands/sessions.js +10 -16
  19. package/dist/commands/sessions.js.map +1 -1
  20. package/dist/commands/task-columns.d.ts +1 -1
  21. package/dist/commands/task-columns.d.ts.map +1 -1
  22. package/dist/commands/task-columns.js +2 -4
  23. package/dist/commands/task-columns.js.map +1 -1
  24. package/dist/commands/tasks.d.ts +10 -10
  25. package/dist/commands/tasks.d.ts.map +1 -1
  26. package/dist/commands/tasks.js +20 -31
  27. package/dist/commands/tasks.js.map +1 -1
  28. package/dist/contracts/commands.d.ts +38 -0
  29. package/dist/contracts/commands.d.ts.map +1 -0
  30. package/dist/contracts/commands.js +469 -0
  31. package/dist/contracts/commands.js.map +1 -0
  32. package/dist/index.js +1076 -153
  33. package/dist/index.js.map +1 -1
  34. package/dist/lib/input.d.ts +7 -0
  35. package/dist/lib/input.d.ts.map +1 -0
  36. package/dist/lib/input.js +94 -0
  37. package/dist/lib/input.js.map +1 -0
  38. package/dist/lib/mcp.d.ts +36 -0
  39. package/dist/lib/mcp.d.ts.map +1 -0
  40. package/dist/lib/mcp.js +150 -0
  41. package/dist/lib/mcp.js.map +1 -0
  42. package/dist/lib/output.d.ts +9 -1
  43. package/dist/lib/output.d.ts.map +1 -1
  44. package/dist/lib/output.js +59 -4
  45. package/dist/lib/output.js.map +1 -1
  46. package/dist/lib/schema.d.ts +35 -0
  47. package/dist/lib/schema.d.ts.map +1 -0
  48. package/dist/lib/schema.js +441 -0
  49. package/dist/lib/schema.js.map +1 -0
  50. package/dist/lib/validation.d.ts +15 -0
  51. package/dist/lib/validation.d.ts.map +1 -0
  52. package/dist/lib/validation.js +140 -0
  53. package/dist/lib/validation.js.map +1 -0
  54. package/package.json +5 -2
  55. package/skills/SKILL-create-coworker.md +51 -0
  56. package/skills/SKILL-cron-jobs.md +106 -0
  57. package/skills/SKILL-send-message.md +56 -0
  58. package/skills/SKILL-task-management.md +86 -0
package/dist/index.js CHANGED
@@ -1,26 +1,77 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command, Option } from 'commander';
3
- import { readFileSync } from 'fs';
3
+ import { readFileSync, readdirSync } from 'fs';
4
4
  import { fileURLToPath } from 'url';
5
5
  import { dirname, join } from 'path';
6
6
  import { listCoworkers, getCoworkerInfo, updateCoworker, createSession, deleteCoworker } from './commands/sessions.js';
7
- import { sendMessage, checkUnreadMail, getUnreadMail, listMessagesBetween } from './commands/messages.js';
7
+ import { sendMessage, checkUnreadMail, getUnreadMail, listMessagesBetween, listMessagesToNotify, markMessagesAsNotified, } from './commands/messages.js';
8
8
  import { listCrons, deleteCron, enableCron, disableCron, cronHistory, requestCron, createCron, checkCronJobs, listActiveCronJobs, } from './commands/crons.js';
9
9
  import { listCronRequests, getCronRequest, approveCronRequest, rejectCronRequest, deleteCronRequest, } from './commands/cron-requests.js';
10
10
  import { addTask, listTasks, getTask, updateTask, deleteTask, assignTask, unassignTask, moveTask, taskStats, getTaskHistory, } from './commands/tasks.js';
11
11
  import { listTaskColumns } from './commands/task-columns.js';
12
12
  import { createSqliteStorage, createPostgresqlStorage } from './db/index.js';
13
+ import { formatOutput } from './lib/output.js';
14
+ import { getSchema, getMutatingCommands } from './lib/schema.js';
15
+ import { MCPServer } from './lib/mcp.js';
13
16
  const __filename = fileURLToPath(import.meta.url);
14
17
  const __dirname = dirname(__filename);
15
18
  const packageJson = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf-8'));
16
19
  // Global storage instance
17
20
  let storage = null;
21
+ let isDryRun = false;
18
22
  async function getStorage() {
19
23
  if (!storage) {
20
24
  throw new Error('Storage not initialized. Use --sqlite <path> or --postgresql <url> option.');
21
25
  }
22
26
  return storage;
23
27
  }
28
+ function getOutputFormat(opts) {
29
+ // Check explicit --output flag first
30
+ const outputFormat = opts.output?.toLowerCase();
31
+ if (outputFormat === 'json')
32
+ return 'json';
33
+ if (outputFormat === 'ndjson')
34
+ return 'ndjson';
35
+ if (outputFormat === 'toon')
36
+ return 'toon';
37
+ if (outputFormat === 'auto')
38
+ return 'auto';
39
+ // Check environment variable as fallback
40
+ const envFormat = process.env.AGENT_OFFICE_OUTPUT_FORMAT?.toLowerCase();
41
+ if (envFormat === 'json')
42
+ return 'json';
43
+ if (envFormat === 'ndjson')
44
+ return 'ndjson';
45
+ if (envFormat === 'toon')
46
+ return 'toon';
47
+ if (envFormat === 'auto')
48
+ return 'auto';
49
+ return 'auto';
50
+ }
51
+ function getFields(opts) {
52
+ if (!opts.fields)
53
+ return undefined;
54
+ return opts.fields
55
+ .split(',')
56
+ .map(f => f.trim())
57
+ .filter(f => f.length > 0);
58
+ }
59
+ function formatWithOptions(data, opts) {
60
+ const format = getOutputFormat(opts);
61
+ const fields = getFields(opts);
62
+ return formatOutput(data, { format, fields });
63
+ }
64
+ // Helper to check if dry-run should be applied
65
+ function shouldExecute(commandName) {
66
+ if (!isDryRun)
67
+ return true;
68
+ const mutatingCommands = getMutatingCommands();
69
+ if (mutatingCommands.includes(commandName)) {
70
+ console.log(`[DRY-RUN] Would execute: ${commandName}`);
71
+ return false;
72
+ }
73
+ return true;
74
+ }
24
75
  const program = new Command();
25
76
  program
26
77
  .name('agent-office')
@@ -28,7 +79,9 @@ program
28
79
  .version(packageJson.version)
29
80
  .addOption(new Option('--sqlite <path>', 'SQLite database file path').env('AGENT_OFFICE_SQLITE'))
30
81
  .addOption(new Option('--postgresql <url>', 'PostgreSQL connection URL').env('AGENT_OFFICE_POSTGRESQL'))
31
- .addOption(new Option('--json', 'Output in JSON format instead of TOON').default(false))
82
+ .addOption(new Option('--output <format>', 'Output format (json, ndjson, toon, auto)').env('AGENT_OFFICE_OUTPUT_FORMAT'))
83
+ .addOption(new Option('--fields <fields>', 'Comma-separated list of fields to include in output'))
84
+ .addOption(new Option('--dry-run', 'Validate commands without executing mutating operations'))
32
85
  .hook('preAction', async (thisCommand) => {
33
86
  const opts = thisCommand.opts();
34
87
  if (opts.sqlite && opts.postgresql) {
@@ -42,396 +95,1266 @@ program
42
95
  storage = createPostgresqlStorage(opts.postgresql);
43
96
  await storage.runMigrations();
44
97
  }
98
+ isDryRun = opts.dryRun || false;
99
+ });
100
+ // Schema introspection commands
101
+ program
102
+ .command('schema')
103
+ .description('Show schema for a specific command or all commands')
104
+ .argument('[command]', 'Command name to show schema for (omit for all commands)')
105
+ .action(async (commandName, _cmd) => {
106
+ const opts = program.opts();
107
+ const schema = getSchema(commandName);
108
+ if (commandName && !schema) {
109
+ console.log(formatWithOptions({ error: `Unknown command: ${commandName}` }, opts));
110
+ process.exit(1);
111
+ }
112
+ console.log(formatWithOptions(schema, opts));
113
+ });
114
+ program
115
+ .command('describe')
116
+ .description('Describe available commands (alias for schema)')
117
+ .argument('[command]', 'Command name to describe (omit for all commands)')
118
+ .action(async (commandName, _cmd) => {
119
+ const opts = program.opts();
120
+ const schema = getSchema(commandName);
121
+ if (commandName && !schema) {
122
+ console.log(formatWithOptions({ error: `Unknown command: ${commandName}` }, opts));
123
+ process.exit(1);
124
+ }
125
+ console.log(formatWithOptions(schema, opts));
45
126
  });
46
127
  // Session/Coworker commands
47
128
  program
48
129
  .command('list-coworkers')
49
130
  .description('List all coworkers (sessions)')
50
131
  .action(async (_args, command) => {
51
- const useJson = command.optsWithGlobals().json;
132
+ const opts = command.optsWithGlobals();
52
133
  const storage = await getStorage();
53
- await listCoworkers(storage, useJson);
134
+ const coworkers = await listCoworkers(storage);
135
+ console.log(formatWithOptions(coworkers, opts));
54
136
  await storage.close();
55
137
  });
56
138
  program
57
139
  .command('create-coworker')
58
140
  .description('Create a new coworker (session)')
59
- .requiredOption('-n, --name <name>', 'Coworker name')
60
- .requiredOption('-t, --coworker-type <type>', 'Coworker type (e.g., assistant, developer, manager)')
141
+ .requiredOption('--json <json>', 'Full JSON payload with name and coworkerType')
61
142
  .action(async (options, command) => {
62
- const useJson = command.optsWithGlobals().json;
143
+ const opts = command.optsWithGlobals();
144
+ // Parse JSON input
145
+ let data;
146
+ try {
147
+ data = JSON.parse(options.json);
148
+ }
149
+ catch (e) {
150
+ console.log(formatWithOptions({ error: 'Invalid JSON in --json' }, opts));
151
+ process.exit(1);
152
+ }
153
+ // Validate required fields
154
+ if (!data.name) {
155
+ console.log(formatWithOptions({ error: 'Missing required field: name' }, opts));
156
+ process.exit(1);
157
+ }
158
+ if (!data.coworkerType) {
159
+ console.log(formatWithOptions({ error: 'Missing required field: coworkerType' }, opts));
160
+ process.exit(1);
161
+ }
162
+ if (!shouldExecute('create-coworker')) {
163
+ console.log(formatWithOptions({
164
+ dryRun: true,
165
+ command: 'create-coworker',
166
+ params: { name: data.name, coworkerType: data.coworkerType },
167
+ }, opts));
168
+ return;
169
+ }
63
170
  const storage = await getStorage();
64
- await createSession(storage, options.name, options.coworkerType, useJson);
171
+ const session = await createSession(storage, data.name, data.coworkerType);
172
+ console.log(formatWithOptions(session, opts));
65
173
  await storage.close();
66
174
  });
67
175
  program
68
176
  .command('delete-coworker')
69
177
  .description('Delete a coworker (session)')
70
- .requiredOption('-n, --name <name>', 'Coworker name')
178
+ .requiredOption('--json <json>', 'Full JSON payload with name field')
71
179
  .action(async (options, command) => {
72
- const useJson = command.optsWithGlobals().json;
180
+ const opts = command.optsWithGlobals();
181
+ // Parse JSON input
182
+ let data;
183
+ try {
184
+ data = JSON.parse(options.json);
185
+ }
186
+ catch (e) {
187
+ console.log(formatWithOptions({ error: 'Invalid JSON in --json' }, opts));
188
+ process.exit(1);
189
+ }
190
+ // Validate required fields
191
+ if (!data.name) {
192
+ console.log(formatWithOptions({ error: 'Missing required field: name' }, opts));
193
+ process.exit(1);
194
+ }
195
+ if (!shouldExecute('delete-coworker')) {
196
+ console.log(formatWithOptions({
197
+ dryRun: true,
198
+ command: 'delete-coworker',
199
+ params: { name: data.name },
200
+ }, opts));
201
+ return;
202
+ }
73
203
  const storage = await getStorage();
74
- await deleteCoworker(storage, options.name, useJson);
204
+ await deleteCoworker(storage, data.name);
205
+ console.log(formatWithOptions({ success: true, message: `Coworker ${data.name} deleted` }, opts));
75
206
  await storage.close();
76
207
  });
77
208
  program
78
209
  .command('update-coworker')
79
210
  .description("Update a coworker's information (status, description, philosophy, visual description)")
80
- .requiredOption('-n, --name <name>', 'Coworker name')
81
- .option('-t, --coworker-type <type>', 'Set the coworker type')
82
- .option('-s, --status <status>', 'Set the status (omit to clear)')
83
- .option('-d, --description <description>', 'Set the description (omit to clear)')
84
- .option('-p, --philosophy <philosophy>', 'Set the philosophy (omit to clear)')
85
- .option('-v, --visual-description <visual>', 'Set the visual description (omit to clear)')
211
+ .requiredOption('--json <json>', 'Full JSON payload with name and optional fields')
86
212
  .action(async (options, command) => {
87
- const useJson = command.optsWithGlobals().json;
213
+ const opts = command.optsWithGlobals();
214
+ // Parse JSON input
215
+ let data;
216
+ try {
217
+ data = JSON.parse(options.json);
218
+ }
219
+ catch (e) {
220
+ console.log(formatWithOptions({ error: 'Invalid JSON in --json' }, opts));
221
+ process.exit(1);
222
+ }
223
+ // Validate required fields
224
+ if (!data.name) {
225
+ console.log(formatWithOptions({ error: 'Missing required field: name' }, opts));
226
+ process.exit(1);
227
+ }
228
+ if (!shouldExecute('update-coworker')) {
229
+ console.log(formatWithOptions({
230
+ dryRun: true,
231
+ command: 'update-coworker',
232
+ params: {
233
+ name: data.name,
234
+ coworkerType: data.coworkerType,
235
+ status: data.status,
236
+ description: data.description,
237
+ philosophy: data.philosophy,
238
+ visualDescription: data.visualDescription,
239
+ },
240
+ }, opts));
241
+ return;
242
+ }
88
243
  const storage = await getStorage();
89
- await updateCoworker(storage, options.name, {
90
- coworkerType: options.coworkerType ?? null,
91
- status: options.status ?? null,
92
- description: options.description ?? null,
93
- philosophy: options.philosophy ?? null,
94
- visualDescription: options.visualDescription ?? null,
95
- }, useJson);
244
+ await updateCoworker(storage, data.name, {
245
+ coworkerType: data.coworkerType ?? null,
246
+ status: data.status ?? null,
247
+ description: data.description ?? null,
248
+ philosophy: data.philosophy ?? null,
249
+ visualDescription: data.visualDescription ?? null,
250
+ });
251
+ const coworker = await getCoworkerInfo(storage, data.name);
252
+ console.log(formatWithOptions(coworker, opts));
96
253
  await storage.close();
97
254
  });
98
255
  program
99
256
  .command('get-coworker-info')
100
257
  .description('Get coworker information (name, description, philosophy)')
101
- .requiredOption('-n, --name <name>', 'Coworker name')
258
+ .requiredOption('--json <json>', 'Full JSON payload with name field')
102
259
  .action(async (options, command) => {
103
- const useJson = command.optsWithGlobals().json;
260
+ const opts = command.optsWithGlobals();
261
+ // Parse JSON input
262
+ let data;
263
+ try {
264
+ data = JSON.parse(options.json);
265
+ }
266
+ catch (e) {
267
+ console.log(formatWithOptions({ error: 'Invalid JSON in --json' }, opts));
268
+ process.exit(1);
269
+ }
270
+ // Validate required fields
271
+ if (!data.name) {
272
+ console.log(formatWithOptions({ error: 'Missing required field: name' }, opts));
273
+ process.exit(1);
274
+ }
104
275
  const storage = await getStorage();
105
- await getCoworkerInfo(storage, options.name, useJson);
276
+ const coworker = await getCoworkerInfo(storage, data.name);
277
+ console.log(formatWithOptions(coworker, opts));
106
278
  await storage.close();
107
279
  });
108
280
  // Message commands
109
281
  program
110
282
  .command('send-message')
111
283
  .description('Send a message to one or more recipients')
112
- .requiredOption('-f, --from <from>', 'Sender name')
113
- .requiredOption('-t, --to <recipients...>', 'Recipient names')
114
- .requiredOption('-b, --body <body>', 'Message body')
284
+ .requiredOption('--json <json>', 'Full JSON payload with from, to, and body fields')
115
285
  .action(async (options, command) => {
116
- const useJson = command.optsWithGlobals().json;
286
+ const opts = command.optsWithGlobals();
287
+ // Parse JSON input
288
+ let data;
289
+ try {
290
+ data = JSON.parse(options.json);
291
+ }
292
+ catch (e) {
293
+ console.log(formatWithOptions({ error: 'Invalid JSON in --json' }, opts));
294
+ process.exit(1);
295
+ }
296
+ // Validate required fields
297
+ if (!data.from) {
298
+ console.log(formatWithOptions({ error: 'Missing required field: from' }, opts));
299
+ process.exit(1);
300
+ }
301
+ if (!data.to || !Array.isArray(data.to) || data.to.length === 0) {
302
+ console.log(formatWithOptions({ error: 'Missing required field: to (must be array)' }, opts));
303
+ process.exit(1);
304
+ }
305
+ if (!data.body) {
306
+ console.log(formatWithOptions({ error: 'Missing required field: body' }, opts));
307
+ process.exit(1);
308
+ }
309
+ if (!shouldExecute('send-message')) {
310
+ console.log(formatWithOptions({
311
+ dryRun: true,
312
+ command: 'send-message',
313
+ params: { from: data.from, to: data.to, body: data.body },
314
+ }, opts));
315
+ return;
316
+ }
117
317
  const storage = await getStorage();
118
- await sendMessage(storage, options.from, options.to, options.body, useJson);
318
+ await sendMessage(storage, data.from, data.to, data.body);
319
+ console.log(formatWithOptions({ success: true, message: 'Message sent' }, opts));
119
320
  await storage.close();
120
321
  });
121
322
  program
122
323
  .command('check-unread-messages')
123
324
  .description('Check if there are unread messages for a coworker')
124
- .requiredOption('-c, --coworker <name>', 'Coworker name to check')
325
+ .requiredOption('--json <json>', 'Full JSON payload with coworker field')
125
326
  .action(async (options, command) => {
126
- const useJson = command.optsWithGlobals().json;
327
+ const opts = command.optsWithGlobals();
328
+ // Parse JSON input
329
+ let data;
330
+ try {
331
+ data = JSON.parse(options.json);
332
+ }
333
+ catch (e) {
334
+ console.log(formatWithOptions({ error: 'Invalid JSON in --json' }, opts));
335
+ process.exit(1);
336
+ }
337
+ // Validate required fields
338
+ if (!data.coworker) {
339
+ console.log(formatWithOptions({ error: 'Missing required field: coworker' }, opts));
340
+ process.exit(1);
341
+ }
127
342
  const storage = await getStorage();
128
- await checkUnreadMail(storage, options.coworker, useJson);
343
+ const result = await checkUnreadMail(storage, data.coworker);
344
+ console.log(formatWithOptions(result, opts));
129
345
  await storage.close();
130
346
  });
131
347
  program
132
348
  .command('get-unread-messages')
133
349
  .description('Get all unread messages for a coworker and mark as read')
134
- .requiredOption('-c, --coworker <name>', 'Coworker name')
350
+ .requiredOption('--json <json>', 'Full JSON payload with coworker field')
135
351
  .action(async (options, command) => {
136
- const useJson = command.optsWithGlobals().json;
352
+ const opts = command.optsWithGlobals();
353
+ // Parse JSON input
354
+ let data;
355
+ try {
356
+ data = JSON.parse(options.json);
357
+ }
358
+ catch (e) {
359
+ console.log(formatWithOptions({ error: 'Invalid JSON in --json' }, opts));
360
+ process.exit(1);
361
+ }
362
+ // Validate required fields
363
+ if (!data.coworker) {
364
+ console.log(formatWithOptions({ error: 'Missing required field: coworker' }, opts));
365
+ process.exit(1);
366
+ }
367
+ if (!shouldExecute('get-unread-messages')) {
368
+ console.log(formatWithOptions({
369
+ dryRun: true,
370
+ command: 'get-unread-messages',
371
+ params: { coworker: data.coworker },
372
+ }, opts));
373
+ return;
374
+ }
137
375
  const storage = await getStorage();
138
- await getUnreadMail(storage, options.coworker, useJson);
376
+ const messages = await getUnreadMail(storage, data.coworker);
377
+ console.log(formatWithOptions(messages, opts));
139
378
  await storage.close();
140
379
  });
141
380
  program
142
381
  .command('list-messages-between')
143
382
  .description('Show all messages between two coworkers')
144
- .requiredOption('--coworker1 <name>', 'First coworker name')
145
- .requiredOption('--coworker2 <name>', 'Second coworker name')
146
- .option('--start <isoTime>', 'Start time (ISO 8601 format)')
147
- .option('--end <isoTime>', 'End time (ISO 8601 format)')
383
+ .requiredOption('--json <json>', 'Full JSON payload with coworker1, coworker2, and optional start/end fields')
384
+ .action(async (options, command) => {
385
+ const opts = command.optsWithGlobals();
386
+ // Parse JSON input
387
+ let data;
388
+ try {
389
+ data = JSON.parse(options.json);
390
+ }
391
+ catch (e) {
392
+ console.log(formatWithOptions({ error: 'Invalid JSON in --json' }, opts));
393
+ process.exit(1);
394
+ }
395
+ // Validate required fields
396
+ if (!data.coworker1) {
397
+ console.log(formatWithOptions({ error: 'Missing required field: coworker1' }, opts));
398
+ process.exit(1);
399
+ }
400
+ if (!data.coworker2) {
401
+ console.log(formatWithOptions({ error: 'Missing required field: coworker2' }, opts));
402
+ process.exit(1);
403
+ }
404
+ const storage = await getStorage();
405
+ const messages = await listMessagesBetween(storage, data.coworker1, data.coworker2, data.start, data.end);
406
+ console.log(formatWithOptions(messages, opts));
407
+ await storage.close();
408
+ });
409
+ program
410
+ .command('list-messages-to-notify')
411
+ .description('List unread messages older than specified hours that have not been notified')
412
+ .requiredOption('--json <json>', 'Full JSON payload with coworker and hours fields')
413
+ .action(async (options, command) => {
414
+ const opts = command.optsWithGlobals();
415
+ // Parse JSON input
416
+ let data;
417
+ try {
418
+ data = JSON.parse(options.json);
419
+ }
420
+ catch (e) {
421
+ console.log(formatWithOptions({ error: 'Invalid JSON in --json' }, opts));
422
+ process.exit(1);
423
+ }
424
+ // Validate required fields
425
+ if (!data.coworker) {
426
+ console.log(formatWithOptions({ error: 'Missing required field: coworker' }, opts));
427
+ process.exit(1);
428
+ }
429
+ if (data.hours === undefined || data.hours === null) {
430
+ console.log(formatWithOptions({ error: 'Missing required field: hours' }, opts));
431
+ process.exit(1);
432
+ }
433
+ const storage = await getStorage();
434
+ const messages = await listMessagesToNotify(storage, data.coworker, data.hours);
435
+ console.log(formatWithOptions(messages, opts));
436
+ await storage.close();
437
+ });
438
+ program
439
+ .command('mark-messages-as-notified')
440
+ .description('Mark specific messages as notified')
441
+ .requiredOption('--json <json>', 'Full JSON payload with ids array')
148
442
  .action(async (options, command) => {
149
- const useJson = command.optsWithGlobals().json;
443
+ const opts = command.optsWithGlobals();
444
+ // Parse JSON input
445
+ let data;
446
+ try {
447
+ data = JSON.parse(options.json);
448
+ }
449
+ catch (e) {
450
+ console.log(formatWithOptions({ error: 'Invalid JSON in --json' }, opts));
451
+ process.exit(1);
452
+ }
453
+ // Validate required fields
454
+ if (!data.ids || !Array.isArray(data.ids) || data.ids.length === 0) {
455
+ console.log(formatWithOptions({ error: 'Missing required field: ids (must be array of numbers)' }, opts));
456
+ process.exit(1);
457
+ }
458
+ if (!shouldExecute('mark-messages-as-notified')) {
459
+ console.log(formatWithOptions({
460
+ dryRun: true,
461
+ command: 'mark-messages-as-notified',
462
+ params: { ids: data.ids },
463
+ }, opts));
464
+ return;
465
+ }
150
466
  const storage = await getStorage();
151
- await listMessagesBetween(storage, options.coworker1, options.coworker2, options.start, options.end, useJson);
467
+ await markMessagesAsNotified(storage, data.ids);
468
+ console.log(formatWithOptions({ success: true, marked: data.ids.length }, opts));
152
469
  await storage.close();
153
470
  });
154
471
  // Cron commands
155
472
  program
156
473
  .command('list-crons')
157
474
  .description('List all cron jobs for a specific coworker')
158
- .requiredOption('-c, --coworker <name>', 'Coworker name')
475
+ .requiredOption('--json <json>', 'Full JSON payload with coworker field')
159
476
  .action(async (options, command) => {
160
- const useJson = command.optsWithGlobals().json;
477
+ const opts = command.optsWithGlobals();
478
+ // Parse JSON input
479
+ let data;
480
+ try {
481
+ data = JSON.parse(options.json);
482
+ }
483
+ catch (e) {
484
+ console.log(formatWithOptions({ error: 'Invalid JSON in --json' }, opts));
485
+ process.exit(1);
486
+ }
487
+ // Validate required fields
488
+ if (!data.coworker) {
489
+ console.log(formatWithOptions({ error: 'Missing required field: coworker' }, opts));
490
+ process.exit(1);
491
+ }
161
492
  const storage = await getStorage();
162
- await listCrons(storage, options.coworker, useJson);
493
+ const crons = await listCrons(storage, data.coworker);
494
+ console.log(formatWithOptions(crons, opts));
163
495
  await storage.close();
164
496
  });
165
497
  program
166
498
  .command('delete-cron')
167
499
  .description('Delete a cron job')
168
- .requiredOption('-i, --id <id>', 'Cron job ID', parseInt)
500
+ .requiredOption('--json <json>', 'Full JSON payload with id field')
169
501
  .action(async (options, command) => {
170
- const useJson = command.optsWithGlobals().json;
502
+ const opts = command.optsWithGlobals();
503
+ // Parse JSON input
504
+ let data;
505
+ try {
506
+ data = JSON.parse(options.json);
507
+ }
508
+ catch (e) {
509
+ console.log(formatWithOptions({ error: 'Invalid JSON in --json' }, opts));
510
+ process.exit(1);
511
+ }
512
+ // Validate required fields
513
+ if (data.id === undefined || data.id === null) {
514
+ console.log(formatWithOptions({ error: 'Missing required field: id' }, opts));
515
+ process.exit(1);
516
+ }
517
+ if (!shouldExecute('delete-cron')) {
518
+ console.log(formatWithOptions({
519
+ dryRun: true,
520
+ command: 'delete-cron',
521
+ params: { id: data.id },
522
+ }, opts));
523
+ return;
524
+ }
171
525
  const storage = await getStorage();
172
- await deleteCron(storage, options.id, useJson);
526
+ await deleteCron(storage, data.id);
527
+ console.log(formatWithOptions({ success: true, message: `Cron job ${data.id} deleted` }, opts));
173
528
  await storage.close();
174
529
  });
175
530
  program
176
531
  .command('enable-cron')
177
532
  .description('Enable a cron job')
178
- .requiredOption('-i, --id <id>', 'Cron job ID', parseInt)
533
+ .requiredOption('--json <json>', 'Full JSON payload with id field')
179
534
  .action(async (options, command) => {
180
- const useJson = command.optsWithGlobals().json;
535
+ const opts = command.optsWithGlobals();
536
+ // Parse JSON input
537
+ let data;
538
+ try {
539
+ data = JSON.parse(options.json);
540
+ }
541
+ catch (e) {
542
+ console.log(formatWithOptions({ error: 'Invalid JSON in --json' }, opts));
543
+ process.exit(1);
544
+ }
545
+ // Validate required fields
546
+ if (data.id === undefined || data.id === null) {
547
+ console.log(formatWithOptions({ error: 'Missing required field: id' }, opts));
548
+ process.exit(1);
549
+ }
550
+ if (!shouldExecute('enable-cron')) {
551
+ console.log(formatWithOptions({
552
+ dryRun: true,
553
+ command: 'enable-cron',
554
+ params: { id: data.id },
555
+ }, opts));
556
+ return;
557
+ }
181
558
  const storage = await getStorage();
182
- await enableCron(storage, options.id, useJson);
559
+ await enableCron(storage, data.id);
560
+ console.log(formatWithOptions({ success: true, message: `Cron job ${data.id} enabled` }, opts));
183
561
  await storage.close();
184
562
  });
185
563
  program
186
564
  .command('disable-cron')
187
565
  .description('Disable a cron job')
188
- .requiredOption('-i, --id <id>', 'Cron job ID', parseInt)
566
+ .requiredOption('--json <json>', 'Full JSON payload with id field')
189
567
  .action(async (options, command) => {
190
- const useJson = command.optsWithGlobals().json;
568
+ const opts = command.optsWithGlobals();
569
+ // Parse JSON input
570
+ let data;
571
+ try {
572
+ data = JSON.parse(options.json);
573
+ }
574
+ catch (e) {
575
+ console.log(formatWithOptions({ error: 'Invalid JSON in --json' }, opts));
576
+ process.exit(1);
577
+ }
578
+ // Validate required fields
579
+ if (data.id === undefined || data.id === null) {
580
+ console.log(formatWithOptions({ error: 'Missing required field: id' }, opts));
581
+ process.exit(1);
582
+ }
583
+ if (!shouldExecute('disable-cron')) {
584
+ console.log(formatWithOptions({
585
+ dryRun: true,
586
+ command: 'disable-cron',
587
+ params: { id: data.id },
588
+ }, opts));
589
+ return;
590
+ }
191
591
  const storage = await getStorage();
192
- await disableCron(storage, options.id, useJson);
592
+ await disableCron(storage, data.id);
593
+ console.log(formatWithOptions({ success: true, message: `Cron job ${data.id} disabled` }, opts));
193
594
  await storage.close();
194
595
  });
195
596
  program
196
597
  .command('cron-history')
197
598
  .description('Get cron job execution history')
198
- .requiredOption('-i, --id <id>', 'Cron job ID', parseInt)
199
- .option('-l, --limit <limit>', 'Number of history entries to show', '10')
599
+ .requiredOption('--json <json>', 'Full JSON payload with id and optional limit fields')
200
600
  .action(async (options, command) => {
201
- const useJson = command.optsWithGlobals().json;
601
+ const opts = command.optsWithGlobals();
602
+ // Parse JSON input
603
+ let data;
604
+ try {
605
+ data = JSON.parse(options.json);
606
+ }
607
+ catch (e) {
608
+ console.log(formatWithOptions({ error: 'Invalid JSON in --json' }, opts));
609
+ process.exit(1);
610
+ }
611
+ // Validate required fields
612
+ if (data.id === undefined || data.id === null) {
613
+ console.log(formatWithOptions({ error: 'Missing required field: id' }, opts));
614
+ process.exit(1);
615
+ }
616
+ const limit = data.limit ?? 10;
202
617
  const storage = await getStorage();
203
- await cronHistory(storage, options.id, parseInt(options.limit), useJson);
618
+ const history = await cronHistory(storage, data.id, limit);
619
+ console.log(formatWithOptions(history, opts));
204
620
  await storage.close();
205
621
  });
206
622
  program
207
623
  .command('check-cron-jobs')
208
624
  .description('Check if there are any active cron jobs for a coworker this minute')
209
- .requiredOption('-c, --coworker <name>', 'Coworker name to check')
625
+ .requiredOption('--json <json>', 'Full JSON payload with coworker field')
210
626
  .action(async (options, command) => {
211
- const useJson = command.optsWithGlobals().json;
627
+ const opts = command.optsWithGlobals();
628
+ // Parse JSON input
629
+ let data;
630
+ try {
631
+ data = JSON.parse(options.json);
632
+ }
633
+ catch (e) {
634
+ console.log(formatWithOptions({ error: 'Invalid JSON in --json' }, opts));
635
+ process.exit(1);
636
+ }
637
+ // Validate required fields
638
+ if (!data.coworker) {
639
+ console.log(formatWithOptions({ error: 'Missing required field: coworker' }, opts));
640
+ process.exit(1);
641
+ }
212
642
  const storage = await getStorage();
213
- await checkCronJobs(storage, options.coworker, useJson);
643
+ const result = await checkCronJobs(storage, data.coworker);
644
+ console.log(formatWithOptions(result, opts));
214
645
  await storage.close();
215
646
  });
216
647
  program
217
648
  .command('list-active-cron-actions')
218
649
  .description('List all active cron actions for a specific coworker that should run this minute (for AI execution)')
219
- .requiredOption('-c, --coworker <name>', 'Coworker name to check')
650
+ .requiredOption('--json <json>', 'Full JSON payload with coworker field')
220
651
  .action(async (options, command) => {
221
- const useJson = command.optsWithGlobals().json;
652
+ const opts = command.optsWithGlobals();
653
+ // Parse JSON input
654
+ let data;
655
+ try {
656
+ data = JSON.parse(options.json);
657
+ }
658
+ catch (e) {
659
+ console.log(formatWithOptions({ error: 'Invalid JSON in --json' }, opts));
660
+ process.exit(1);
661
+ }
662
+ // Validate required fields
663
+ if (!data.coworker) {
664
+ console.log(formatWithOptions({ error: 'Missing required field: coworker' }, opts));
665
+ process.exit(1);
666
+ }
222
667
  const storage = await getStorage();
223
- await listActiveCronJobs(storage, options.coworker, useJson);
668
+ const jobs = await listActiveCronJobs(storage, data.coworker);
669
+ console.log(formatWithOptions(jobs, opts));
224
670
  await storage.close();
225
671
  });
226
672
  program
227
673
  .command('create-cron')
228
674
  .description('Create a new cron job directly')
229
- .requiredOption('-n, --name <name>', 'Cron job name')
230
- .requiredOption('-c, --coworker <coworker>', 'Coworker name')
231
- .requiredOption('-S, --schedule <schedule>', 'Cron schedule expression')
232
- .requiredOption('-t, --task <task>', 'Task to perform (action to do)')
233
- .requiredOption('-N, --notify <instructions>', 'Instructions on who to notify when complete (can be names or descriptions)')
234
- .requiredOption('-z, --timezone <timezone>', 'Timezone')
675
+ .requiredOption('--json <json>', 'Full JSON payload with name, coworker, schedule, task, notify, and timezone fields')
235
676
  .action(async (options, command) => {
236
- const useJson = command.optsWithGlobals().json;
677
+ const opts = command.optsWithGlobals();
678
+ // Parse JSON input
679
+ let data;
680
+ try {
681
+ data = JSON.parse(options.json);
682
+ }
683
+ catch (e) {
684
+ console.log(formatWithOptions({ error: 'Invalid JSON in --json' }, opts));
685
+ process.exit(1);
686
+ }
687
+ // Validate required fields
688
+ if (!data.name) {
689
+ console.log(formatWithOptions({ error: 'Missing required field: name' }, opts));
690
+ process.exit(1);
691
+ }
692
+ if (!data.coworker) {
693
+ console.log(formatWithOptions({ error: 'Missing required field: coworker' }, opts));
694
+ process.exit(1);
695
+ }
696
+ if (!data.schedule) {
697
+ console.log(formatWithOptions({ error: 'Missing required field: schedule' }, opts));
698
+ process.exit(1);
699
+ }
700
+ if (!data.task) {
701
+ console.log(formatWithOptions({ error: 'Missing required field: task' }, opts));
702
+ process.exit(1);
703
+ }
704
+ if (!data.notify) {
705
+ console.log(formatWithOptions({ error: 'Missing required field: notify' }, opts));
706
+ process.exit(1);
707
+ }
708
+ if (!data.timezone) {
709
+ console.log(formatWithOptions({ error: 'Missing required field: timezone' }, opts));
710
+ process.exit(1);
711
+ }
712
+ if (!shouldExecute('create-cron')) {
713
+ console.log(formatWithOptions({
714
+ dryRun: true,
715
+ command: 'create-cron',
716
+ params: {
717
+ name: data.name,
718
+ coworker: data.coworker,
719
+ schedule: data.schedule,
720
+ task: data.task,
721
+ notify: data.notify,
722
+ timezone: data.timezone,
723
+ },
724
+ }, opts));
725
+ return;
726
+ }
237
727
  const storage = await getStorage();
238
- // Combine task and notify into a structured message
239
- const message = `Action To Do:\n${options.task}\n\nWho To Notify When Complete:\n${options.notify}`;
240
- await createCron(storage, options.name, options.coworker, options.schedule, message, options.timezone, useJson);
728
+ const message = `Action To Do:\n${data.task}\n\nWho To Notify When Complete:\n${data.notify}`;
729
+ const cron = await createCron(storage, data.name, data.coworker, data.schedule, message, data.timezone);
730
+ console.log(formatWithOptions(cron, opts));
241
731
  await storage.close();
242
732
  });
243
- // Cron request management commands (top-level)
733
+ // Cron request management commands
244
734
  program
245
735
  .command('list-cron-requests')
246
736
  .description('List all cron job requests')
247
737
  .action(async (_args, command) => {
248
- const useJson = command.optsWithGlobals().json;
738
+ const opts = command.optsWithGlobals();
249
739
  const storage = await getStorage();
250
- await listCronRequests(storage, useJson);
740
+ const requests = await listCronRequests(storage);
741
+ console.log(formatWithOptions(requests, opts));
251
742
  await storage.close();
252
743
  });
253
744
  program
254
745
  .command('request-cron')
255
746
  .description('Create a new cron job request (requires approval)')
256
- .requiredOption('-n, --name <name>', 'Cron job name')
257
- .requiredOption('-c, --coworker <coworker>', 'Coworker name')
258
- .requiredOption('-S, --schedule <schedule>', 'Cron schedule expression')
259
- .requiredOption('-t, --task <task>', 'Task to perform (action to do)')
260
- .requiredOption('-N, --notify <instructions>', 'Instructions on who to notify when complete (can be names or descriptions)')
261
- .requiredOption('-z, --timezone <timezone>', 'Timezone')
747
+ .requiredOption('--json <json>', 'Full JSON payload with name, coworker, schedule, task, notify, and timezone fields')
262
748
  .action(async (options, command) => {
263
- const useJson = command.optsWithGlobals().json;
749
+ const opts = command.optsWithGlobals();
750
+ // Parse JSON input
751
+ let data;
752
+ try {
753
+ data = JSON.parse(options.json);
754
+ }
755
+ catch (e) {
756
+ console.log(formatWithOptions({ error: 'Invalid JSON in --json' }, opts));
757
+ process.exit(1);
758
+ }
759
+ // Validate required fields
760
+ if (!data.name) {
761
+ console.log(formatWithOptions({ error: 'Missing required field: name' }, opts));
762
+ process.exit(1);
763
+ }
764
+ if (!data.coworker) {
765
+ console.log(formatWithOptions({ error: 'Missing required field: coworker' }, opts));
766
+ process.exit(1);
767
+ }
768
+ if (!data.schedule) {
769
+ console.log(formatWithOptions({ error: 'Missing required field: schedule' }, opts));
770
+ process.exit(1);
771
+ }
772
+ if (!data.task) {
773
+ console.log(formatWithOptions({ error: 'Missing required field: task' }, opts));
774
+ process.exit(1);
775
+ }
776
+ if (!data.notify) {
777
+ console.log(formatWithOptions({ error: 'Missing required field: notify' }, opts));
778
+ process.exit(1);
779
+ }
780
+ if (!data.timezone) {
781
+ console.log(formatWithOptions({ error: 'Missing required field: timezone' }, opts));
782
+ process.exit(1);
783
+ }
784
+ if (!shouldExecute('request-cron')) {
785
+ console.log(formatWithOptions({
786
+ dryRun: true,
787
+ command: 'request-cron',
788
+ params: {
789
+ name: data.name,
790
+ coworker: data.coworker,
791
+ schedule: data.schedule,
792
+ task: data.task,
793
+ notify: data.notify,
794
+ timezone: data.timezone,
795
+ },
796
+ }, opts));
797
+ return;
798
+ }
264
799
  const storage = await getStorage();
265
- // Combine task and notify into a structured message
266
- const message = `Action To Do:\n${options.task}\n\nWho To Notify When Complete:\n${options.notify}`;
267
- await requestCron(storage, options.name, options.coworker, options.schedule, message, options.timezone, useJson);
800
+ const message = `Action To Do:\n${data.task}\n\nWho To Notify When Complete:\n${data.notify}`;
801
+ const request = await requestCron(storage, data.name, data.coworker, data.schedule, message, data.timezone);
802
+ console.log(formatWithOptions(request, opts));
268
803
  await storage.close();
269
804
  });
270
805
  program
271
806
  .command('get-cron-request')
272
807
  .description('Get details of a cron job request')
273
- .requiredOption('-i, --id <id>', 'Cron request ID', parseInt)
808
+ .requiredOption('--json <json>', 'Full JSON payload with id field')
274
809
  .action(async (options, command) => {
275
- const useJson = command.optsWithGlobals().json;
810
+ const opts = command.optsWithGlobals();
811
+ // Parse JSON input
812
+ let data;
813
+ try {
814
+ data = JSON.parse(options.json);
815
+ }
816
+ catch (e) {
817
+ console.log(formatWithOptions({ error: 'Invalid JSON in --json' }, opts));
818
+ process.exit(1);
819
+ }
820
+ // Validate required fields
821
+ if (data.id === undefined || data.id === null) {
822
+ console.log(formatWithOptions({ error: 'Missing required field: id' }, opts));
823
+ process.exit(1);
824
+ }
276
825
  const storage = await getStorage();
277
- await getCronRequest(storage, options.id, useJson);
826
+ const request = await getCronRequest(storage, data.id);
827
+ console.log(formatWithOptions(request, opts));
278
828
  await storage.close();
279
829
  });
280
830
  program
281
831
  .command('approve-cron-request')
282
832
  .description('Approve a pending cron job request')
283
- .requiredOption('-i, --id <id>', 'Cron request ID', parseInt)
284
- .requiredOption('-r, --reviewer <name>', 'Name of the reviewer')
285
- .option('-n, --notes <notes>', 'Optional reviewer notes')
833
+ .requiredOption('--json <json>', 'Full JSON payload with id, reviewer, and optional notes fields')
286
834
  .action(async (options, command) => {
287
- const useJson = command.optsWithGlobals().json;
835
+ const opts = command.optsWithGlobals();
836
+ // Parse JSON input
837
+ let data;
838
+ try {
839
+ data = JSON.parse(options.json);
840
+ }
841
+ catch (e) {
842
+ console.log(formatWithOptions({ error: 'Invalid JSON in --json' }, opts));
843
+ process.exit(1);
844
+ }
845
+ // Validate required fields
846
+ if (data.id === undefined || data.id === null) {
847
+ console.log(formatWithOptions({ error: 'Missing required field: id' }, opts));
848
+ process.exit(1);
849
+ }
850
+ if (!data.reviewer) {
851
+ console.log(formatWithOptions({ error: 'Missing required field: reviewer' }, opts));
852
+ process.exit(1);
853
+ }
854
+ if (!shouldExecute('approve-cron-request')) {
855
+ console.log(formatWithOptions({
856
+ dryRun: true,
857
+ command: 'approve-cron-request',
858
+ params: { id: data.id, reviewer: data.reviewer, notes: data.notes },
859
+ }, opts));
860
+ return;
861
+ }
288
862
  const storage = await getStorage();
289
- await approveCronRequest(storage, options.id, options.reviewer, options.notes, useJson);
863
+ const result = await approveCronRequest(storage, data.id, data.reviewer, data.notes);
864
+ console.log(formatWithOptions(result, opts));
290
865
  await storage.close();
291
866
  });
292
867
  program
293
868
  .command('reject-cron-request')
294
869
  .description('Reject a pending cron job request')
295
- .requiredOption('-i, --id <id>', 'Cron request ID', parseInt)
296
- .requiredOption('-r, --reviewer <name>', 'Name of the reviewer')
297
- .option('-n, --notes <notes>', 'Optional reviewer notes')
870
+ .requiredOption('--json <json>', 'Full JSON payload with id, reviewer, and optional notes fields')
298
871
  .action(async (options, command) => {
299
- const useJson = command.optsWithGlobals().json;
872
+ const opts = command.optsWithGlobals();
873
+ // Parse JSON input
874
+ let data;
875
+ try {
876
+ data = JSON.parse(options.json);
877
+ }
878
+ catch (e) {
879
+ console.log(formatWithOptions({ error: 'Invalid JSON in --json' }, opts));
880
+ process.exit(1);
881
+ }
882
+ // Validate required fields
883
+ if (data.id === undefined || data.id === null) {
884
+ console.log(formatWithOptions({ error: 'Missing required field: id' }, opts));
885
+ process.exit(1);
886
+ }
887
+ if (!data.reviewer) {
888
+ console.log(formatWithOptions({ error: 'Missing required field: reviewer' }, opts));
889
+ process.exit(1);
890
+ }
891
+ if (!shouldExecute('reject-cron-request')) {
892
+ console.log(formatWithOptions({
893
+ dryRun: true,
894
+ command: 'reject-cron-request',
895
+ params: { id: data.id, reviewer: data.reviewer, notes: data.notes },
896
+ }, opts));
897
+ return;
898
+ }
300
899
  const storage = await getStorage();
301
- await rejectCronRequest(storage, options.id, options.reviewer, options.notes, useJson);
900
+ const result = await rejectCronRequest(storage, data.id, data.reviewer, data.notes);
901
+ console.log(formatWithOptions(result, opts));
302
902
  await storage.close();
303
903
  });
304
904
  program
305
905
  .command('delete-cron-request')
306
906
  .description('Delete a cron job request')
307
- .requiredOption('-i, --id <id>', 'Cron request ID', parseInt)
907
+ .requiredOption('--json <json>', 'Full JSON payload with id field')
308
908
  .action(async (options, command) => {
309
- const useJson = command.optsWithGlobals().json;
909
+ const opts = command.optsWithGlobals();
910
+ // Parse JSON input
911
+ let data;
912
+ try {
913
+ data = JSON.parse(options.json);
914
+ }
915
+ catch (e) {
916
+ console.log(formatWithOptions({ error: 'Invalid JSON in --json' }, opts));
917
+ process.exit(1);
918
+ }
919
+ // Validate required fields
920
+ if (data.id === undefined || data.id === null) {
921
+ console.log(formatWithOptions({ error: 'Missing required field: id' }, opts));
922
+ process.exit(1);
923
+ }
924
+ if (!shouldExecute('delete-cron-request')) {
925
+ console.log(formatWithOptions({
926
+ dryRun: true,
927
+ command: 'delete-cron-request',
928
+ params: { id: data.id },
929
+ }, opts));
930
+ return;
931
+ }
310
932
  const storage = await getStorage();
311
- await deleteCronRequest(storage, options.id, useJson);
933
+ await deleteCronRequest(storage, data.id);
934
+ console.log(formatWithOptions({ success: true, message: `Cron request ${data.id} deleted` }, opts));
312
935
  await storage.close();
313
936
  });
314
937
  // Task board commands
315
938
  program
316
939
  .command('add-task')
317
940
  .description('Create a new task')
318
- .requiredOption('-t, --title <title>', 'Task title')
319
- .option('-d, --description <desc>', 'Task description', '')
320
- .option('-a, --assignee <assignee>', 'Task assignee')
321
- .requiredOption('-c, --column <column>', 'Initial column (idea, approved idea, working on, blocked, ready for review, done)')
322
- .option('--dependencies <deps...>', 'Task dependency IDs', [])
941
+ .requiredOption('--json <json>', 'Full JSON payload with title, column, and optional fields')
323
942
  .action(async (options, command) => {
324
- const useJson = command.optsWithGlobals().json;
943
+ const opts = command.optsWithGlobals();
944
+ // Parse JSON input
945
+ let data;
946
+ try {
947
+ data = JSON.parse(options.json);
948
+ }
949
+ catch (e) {
950
+ console.log(formatWithOptions({ error: 'Invalid JSON in --json' }, opts));
951
+ process.exit(1);
952
+ }
953
+ // Validate required fields
954
+ if (!data.title) {
955
+ console.log(formatWithOptions({ error: 'Missing required field: title' }, opts));
956
+ process.exit(1);
957
+ }
958
+ if (!data.column) {
959
+ console.log(formatWithOptions({ error: 'Missing required field: column' }, opts));
960
+ process.exit(1);
961
+ }
962
+ if (!shouldExecute('add-task')) {
963
+ console.log(formatWithOptions({
964
+ dryRun: true,
965
+ command: 'add-task',
966
+ params: {
967
+ title: data.title,
968
+ description: data.description,
969
+ assignee: data.assignee,
970
+ column: data.column,
971
+ dependencies: data.dependencies,
972
+ },
973
+ }, opts));
974
+ return;
975
+ }
325
976
  const storage = await getStorage();
326
- const deps = options.dependencies?.map((id) => parseInt(id)) || [];
327
- await addTask(storage, options.title, options.description, options.assignee || null, options.column, deps, useJson);
977
+ const deps = data.dependencies || [];
978
+ const task = await addTask(storage, data.title, data.description || '', data.assignee || null, data.column, deps);
979
+ console.log(formatWithOptions(task, opts));
328
980
  await storage.close();
329
981
  });
330
982
  program
331
983
  .command('list-tasks')
332
984
  .description('List all tasks')
333
- .option('-a, --assignee <assignee>', 'Filter by assignee')
334
- .option('-c, --column <column>', 'Filter by column')
335
- .option('-s, --search <query>', 'Search in title and description')
985
+ .option('--json <json>', 'Full JSON payload with optional assignee, column, and search fields')
336
986
  .action(async (options, command) => {
337
- const useJson = command.optsWithGlobals().json;
987
+ const opts = command.optsWithGlobals();
988
+ // Parse JSON input if provided
989
+ let data = {};
990
+ if (options.json) {
991
+ try {
992
+ data = JSON.parse(options.json);
993
+ }
994
+ catch (e) {
995
+ console.log(formatWithOptions({ error: 'Invalid JSON in --json' }, opts));
996
+ process.exit(1);
997
+ }
998
+ }
338
999
  const storage = await getStorage();
339
- await listTasks(storage, options.assignee, options.column, options.search, useJson);
1000
+ const tasks = await listTasks(storage, data.assignee, data.column, data.search);
1001
+ console.log(formatWithOptions(tasks, opts));
340
1002
  await storage.close();
341
1003
  });
342
1004
  program
343
1005
  .command('get-task')
344
1006
  .description('Get a task by ID')
345
- .requiredOption('-i, --id <id>', 'Task ID', parseInt)
1007
+ .requiredOption('--json <json>', 'Full JSON payload with id field')
346
1008
  .action(async (options, command) => {
347
- const useJson = command.optsWithGlobals().json;
1009
+ const opts = command.optsWithGlobals();
1010
+ // Parse JSON input
1011
+ let data;
1012
+ try {
1013
+ data = JSON.parse(options.json);
1014
+ }
1015
+ catch (e) {
1016
+ console.log(formatWithOptions({ error: 'Invalid JSON in --json' }, opts));
1017
+ process.exit(1);
1018
+ }
1019
+ // Validate required fields
1020
+ if (data.id === undefined || data.id === null) {
1021
+ console.log(formatWithOptions({ error: 'Missing required field: id' }, opts));
1022
+ process.exit(1);
1023
+ }
348
1024
  const storage = await getStorage();
349
- await getTask(storage, options.id, useJson);
1025
+ const task = await getTask(storage, data.id);
1026
+ console.log(formatWithOptions(task, opts));
350
1027
  await storage.close();
351
1028
  });
352
1029
  program
353
1030
  .command('update-task')
354
1031
  .description('Update a task')
355
- .requiredOption('-i, --id <id>', 'Task ID', parseInt)
356
- .option('-t, --title <title>', 'New title')
357
- .option('-d, --description <desc>', 'New description')
358
- .option('-a, --assignee <assignee>', 'New assignee')
359
- .option('-c, --column <column>', 'New column')
360
- .option('--dependencies <deps...>', 'New dependency IDs')
1032
+ .requiredOption('--json <json>', 'Full JSON payload with id and optional fields')
361
1033
  .action(async (options, command) => {
362
- const useJson = command.optsWithGlobals().json;
1034
+ const opts = command.optsWithGlobals();
1035
+ // Parse JSON input
1036
+ let data;
1037
+ try {
1038
+ data = JSON.parse(options.json);
1039
+ }
1040
+ catch (e) {
1041
+ console.log(formatWithOptions({ error: 'Invalid JSON in --json' }, opts));
1042
+ process.exit(1);
1043
+ }
1044
+ // Validate required fields
1045
+ if (data.id === undefined || data.id === null) {
1046
+ console.log(formatWithOptions({ error: 'Missing required field: id' }, opts));
1047
+ process.exit(1);
1048
+ }
1049
+ if (!shouldExecute('update-task')) {
1050
+ console.log(formatWithOptions({
1051
+ dryRun: true,
1052
+ command: 'update-task',
1053
+ params: {
1054
+ id: data.id,
1055
+ title: data.title,
1056
+ description: data.description,
1057
+ assignee: data.assignee,
1058
+ column: data.column,
1059
+ dependencies: data.dependencies,
1060
+ },
1061
+ }, opts));
1062
+ return;
1063
+ }
363
1064
  const storage = await getStorage();
364
- const deps = options.dependencies?.map((id) => parseInt(id));
365
- await updateTask(storage, options.id, options.title, options.description, options.assignee, options.column, deps, useJson);
1065
+ const deps = data.dependencies;
1066
+ const task = await updateTask(storage, data.id, data.title, data.description, data.assignee, data.column, deps);
1067
+ console.log(formatWithOptions(task, opts));
366
1068
  await storage.close();
367
1069
  });
368
1070
  program
369
1071
  .command('delete-task')
370
1072
  .description('Delete a task')
371
- .requiredOption('-i, --id <id>', 'Task ID', parseInt)
1073
+ .requiredOption('--json <json>', 'Full JSON payload with id field')
372
1074
  .action(async (options, command) => {
373
- const useJson = command.optsWithGlobals().json;
1075
+ const opts = command.optsWithGlobals();
1076
+ // Parse JSON input
1077
+ let data;
1078
+ try {
1079
+ data = JSON.parse(options.json);
1080
+ }
1081
+ catch (e) {
1082
+ console.log(formatWithOptions({ error: 'Invalid JSON in --json' }, opts));
1083
+ process.exit(1);
1084
+ }
1085
+ // Validate required fields
1086
+ if (data.id === undefined || data.id === null) {
1087
+ console.log(formatWithOptions({ error: 'Missing required field: id' }, opts));
1088
+ process.exit(1);
1089
+ }
1090
+ if (!shouldExecute('delete-task')) {
1091
+ console.log(formatWithOptions({
1092
+ dryRun: true,
1093
+ command: 'delete-task',
1094
+ params: { id: data.id },
1095
+ }, opts));
1096
+ return;
1097
+ }
374
1098
  const storage = await getStorage();
375
- await deleteTask(storage, options.id, useJson);
1099
+ await deleteTask(storage, data.id);
1100
+ console.log(formatWithOptions({ success: true, message: `Task ${data.id} deleted` }, opts));
376
1101
  await storage.close();
377
1102
  });
378
1103
  program
379
1104
  .command('assign-task')
380
1105
  .description('Assign a task to someone')
381
- .requiredOption('-i, --id <id>', 'Task ID', parseInt)
382
- .requiredOption('-a, --assignee <assignee>', 'Assignee name')
1106
+ .requiredOption('--json <json>', 'Full JSON payload with id and assignee fields')
383
1107
  .action(async (options, command) => {
384
- const useJson = command.optsWithGlobals().json;
1108
+ const opts = command.optsWithGlobals();
1109
+ // Parse JSON input
1110
+ let data;
1111
+ try {
1112
+ data = JSON.parse(options.json);
1113
+ }
1114
+ catch (e) {
1115
+ console.log(formatWithOptions({ error: 'Invalid JSON in --json' }, opts));
1116
+ process.exit(1);
1117
+ }
1118
+ // Validate required fields
1119
+ if (data.id === undefined || data.id === null) {
1120
+ console.log(formatWithOptions({ error: 'Missing required field: id' }, opts));
1121
+ process.exit(1);
1122
+ }
1123
+ if (!data.assignee) {
1124
+ console.log(formatWithOptions({ error: 'Missing required field: assignee' }, opts));
1125
+ process.exit(1);
1126
+ }
1127
+ if (!shouldExecute('assign-task')) {
1128
+ console.log(formatWithOptions({
1129
+ dryRun: true,
1130
+ command: 'assign-task',
1131
+ params: { id: data.id, assignee: data.assignee },
1132
+ }, opts));
1133
+ return;
1134
+ }
385
1135
  const storage = await getStorage();
386
- await assignTask(storage, options.id, options.assignee, useJson);
1136
+ const task = await assignTask(storage, data.id, data.assignee);
1137
+ console.log(formatWithOptions(task, opts));
387
1138
  await storage.close();
388
1139
  });
389
1140
  program
390
1141
  .command('unassign-task')
391
1142
  .description('Remove assignment from a task')
392
- .requiredOption('-i, --id <id>', 'Task ID', parseInt)
1143
+ .requiredOption('--json <json>', 'Full JSON payload with id field')
393
1144
  .action(async (options, command) => {
394
- const useJson = command.optsWithGlobals().json;
1145
+ const opts = command.optsWithGlobals();
1146
+ // Parse JSON input
1147
+ let data;
1148
+ try {
1149
+ data = JSON.parse(options.json);
1150
+ }
1151
+ catch (e) {
1152
+ console.log(formatWithOptions({ error: 'Invalid JSON in --json' }, opts));
1153
+ process.exit(1);
1154
+ }
1155
+ // Validate required fields
1156
+ if (data.id === undefined || data.id === null) {
1157
+ console.log(formatWithOptions({ error: 'Missing required field: id' }, opts));
1158
+ process.exit(1);
1159
+ }
1160
+ if (!shouldExecute('unassign-task')) {
1161
+ console.log(formatWithOptions({
1162
+ dryRun: true,
1163
+ command: 'unassign-task',
1164
+ params: { id: data.id },
1165
+ }, opts));
1166
+ return;
1167
+ }
395
1168
  const storage = await getStorage();
396
- await unassignTask(storage, options.id, useJson);
1169
+ const task = await unassignTask(storage, data.id);
1170
+ console.log(formatWithOptions(task, opts));
397
1171
  await storage.close();
398
1172
  });
399
1173
  program
400
1174
  .command('move-task')
401
1175
  .description('Move a task to a different column')
402
- .requiredOption('-i, --id <id>', 'Task ID', parseInt)
403
- .requiredOption('-c, --column <column>', 'Target column')
1176
+ .requiredOption('--json <json>', 'Full JSON payload with id and column fields')
404
1177
  .action(async (options, command) => {
405
- const useJson = command.optsWithGlobals().json;
1178
+ const opts = command.optsWithGlobals();
1179
+ // Parse JSON input
1180
+ let data;
1181
+ try {
1182
+ data = JSON.parse(options.json);
1183
+ }
1184
+ catch (e) {
1185
+ console.log(formatWithOptions({ error: 'Invalid JSON in --json' }, opts));
1186
+ process.exit(1);
1187
+ }
1188
+ // Validate required fields
1189
+ if (data.id === undefined || data.id === null) {
1190
+ console.log(formatWithOptions({ error: 'Missing required field: id' }, opts));
1191
+ process.exit(1);
1192
+ }
1193
+ if (!data.column) {
1194
+ console.log(formatWithOptions({ error: 'Missing required field: column' }, opts));
1195
+ process.exit(1);
1196
+ }
1197
+ if (!shouldExecute('move-task')) {
1198
+ console.log(formatWithOptions({
1199
+ dryRun: true,
1200
+ command: 'move-task',
1201
+ params: { id: data.id, column: data.column },
1202
+ }, opts));
1203
+ return;
1204
+ }
406
1205
  const storage = await getStorage();
407
- await moveTask(storage, options.id, options.column, useJson);
1206
+ const task = await moveTask(storage, data.id, data.column);
1207
+ console.log(formatWithOptions(task, opts));
408
1208
  await storage.close();
409
1209
  });
410
1210
  program
411
1211
  .command('task-stats')
412
1212
  .description('Show task statistics by column')
413
1213
  .action(async (_args, command) => {
414
- const useJson = command.optsWithGlobals().json;
1214
+ const opts = command.optsWithGlobals();
415
1215
  const storage = await getStorage();
416
- await taskStats(storage, useJson);
1216
+ const stats = await taskStats(storage);
1217
+ console.log(formatWithOptions(stats, opts));
417
1218
  await storage.close();
418
1219
  });
419
1220
  program
420
1221
  .command('task-history')
421
1222
  .description('Show column transition history for a task with durations')
422
- .requiredOption('-i, --id <id>', 'Task ID', parseInt)
1223
+ .requiredOption('--json <json>', 'Full JSON payload with id field')
423
1224
  .action(async (options, command) => {
424
- const useJson = command.optsWithGlobals().json;
1225
+ const opts = command.optsWithGlobals();
1226
+ // Parse JSON input
1227
+ let data;
1228
+ try {
1229
+ data = JSON.parse(options.json);
1230
+ }
1231
+ catch (e) {
1232
+ console.log(formatWithOptions({ error: 'Invalid JSON in --json' }, opts));
1233
+ process.exit(1);
1234
+ }
1235
+ // Validate required fields
1236
+ if (data.id === undefined || data.id === null) {
1237
+ console.log(formatWithOptions({ error: 'Missing required field: id' }, opts));
1238
+ process.exit(1);
1239
+ }
425
1240
  const storage = await getStorage();
426
- await getTaskHistory(storage, options.id, useJson);
1241
+ const history = await getTaskHistory(storage, data.id);
1242
+ console.log(formatWithOptions(history, opts));
427
1243
  await storage.close();
428
1244
  });
429
1245
  program
430
1246
  .command('list-task-columns')
431
1247
  .description('List all valid task board columns')
432
1248
  .action(async (_args, command) => {
433
- const useJson = command.optsWithGlobals().json;
434
- await listTaskColumns(useJson);
1249
+ const opts = command.optsWithGlobals();
1250
+ const columns = listTaskColumns();
1251
+ console.log(formatWithOptions(columns, opts));
1252
+ });
1253
+ // Skill and context commands
1254
+ function listSkills() {
1255
+ try {
1256
+ const skillsDir = join(__dirname, '../skills');
1257
+ return readdirSync(skillsDir)
1258
+ .filter(f => f.startsWith('SKILL-') && f.endsWith('.md'))
1259
+ .map(f => f.replace('SKILL-', '').replace('.md', ''));
1260
+ }
1261
+ catch {
1262
+ return [];
1263
+ }
1264
+ }
1265
+ function getSkillContent(skillName) {
1266
+ try {
1267
+ const skillPath = join(__dirname, `../skills/SKILL-${skillName}.md`);
1268
+ return readFileSync(skillPath, 'utf-8');
1269
+ }
1270
+ catch {
1271
+ return null;
1272
+ }
1273
+ }
1274
+ function getContextDoc() {
1275
+ try {
1276
+ const contextPath = join(__dirname, '../CONTEXT.md');
1277
+ return readFileSync(contextPath, 'utf-8');
1278
+ }
1279
+ catch {
1280
+ return null;
1281
+ }
1282
+ }
1283
+ function getAgentsDoc() {
1284
+ try {
1285
+ const agentsPath = join(__dirname, '../AGENTS.md');
1286
+ return readFileSync(agentsPath, 'utf-8');
1287
+ }
1288
+ catch {
1289
+ return null;
1290
+ }
1291
+ }
1292
+ program
1293
+ .command('list-skills')
1294
+ .description('List available agent skills')
1295
+ .action(async (_args, command) => {
1296
+ const opts = command.optsWithGlobals();
1297
+ const skills = listSkills();
1298
+ console.log(formatWithOptions(skills, opts));
1299
+ });
1300
+ program
1301
+ .command('get-skill')
1302
+ .description('Get the content of a specific agent skill')
1303
+ .requiredOption('--json <json>', 'Full JSON payload with name field')
1304
+ .action(async (options, command) => {
1305
+ const opts = command.optsWithGlobals();
1306
+ // Parse JSON input
1307
+ let data;
1308
+ try {
1309
+ data = JSON.parse(options.json);
1310
+ }
1311
+ catch (e) {
1312
+ console.log(formatWithOptions({ error: 'Invalid JSON in --json' }, opts));
1313
+ process.exit(1);
1314
+ }
1315
+ // Validate required fields
1316
+ if (!data.name) {
1317
+ console.log(formatWithOptions({ error: 'Missing required field: name' }, opts));
1318
+ process.exit(1);
1319
+ }
1320
+ const content = getSkillContent(data.name);
1321
+ if (!content) {
1322
+ console.log(formatWithOptions({ error: `Skill not found: ${data.name}` }, opts));
1323
+ process.exit(1);
1324
+ }
1325
+ console.log(content); // Output raw markdown content
1326
+ });
1327
+ program
1328
+ .command('context')
1329
+ .description('Show context window discipline guidelines')
1330
+ .action(async (_args, command) => {
1331
+ const opts = command.optsWithGlobals();
1332
+ const content = getContextDoc();
1333
+ if (!content) {
1334
+ console.log(formatWithOptions({ error: 'CONTEXT.md not found' }, opts));
1335
+ process.exit(1);
1336
+ }
1337
+ console.log(content); // Output raw markdown content
1338
+ });
1339
+ program
1340
+ .command('agents')
1341
+ .description('Show agent security guidelines')
1342
+ .action(async (_args, command) => {
1343
+ const opts = command.optsWithGlobals();
1344
+ const content = getAgentsDoc();
1345
+ if (!content) {
1346
+ console.log(formatWithOptions({ error: 'AGENTS.md not found' }, opts));
1347
+ process.exit(1);
1348
+ }
1349
+ console.log(content); // Output raw markdown content
1350
+ });
1351
+ // MCP Server command
1352
+ program
1353
+ .command('mcp')
1354
+ .description('Run as an MCP (Model Context Protocol) server')
1355
+ .action(async () => {
1356
+ const server = new MCPServer();
1357
+ await server.processStdio();
435
1358
  });
436
1359
  program.parse();
437
1360
  //# sourceMappingURL=index.js.map