agent-office 0.6.23 → 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 -6
  13. package/dist/commands/messages.d.ts.map +1 -1
  14. package/dist/commands/messages.js +11 -18
  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 +1062 -160
  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, listMessagesToNotify, markMessagesAsNotified } 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,417 +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')
148
384
  .action(async (options, command) => {
149
- const useJson = command.optsWithGlobals().json;
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
+ }
150
404
  const storage = await getStorage();
151
- await listMessagesBetween(storage, options.coworker1, options.coworker2, options.start, options.end, useJson);
405
+ const messages = await listMessagesBetween(storage, data.coworker1, data.coworker2, data.start, data.end);
406
+ console.log(formatWithOptions(messages, opts));
152
407
  await storage.close();
153
408
  });
154
409
  program
155
410
  .command('list-messages-to-notify')
156
411
  .description('List unread messages older than specified hours that have not been notified')
157
- .requiredOption('-c, --coworker <name>', 'Coworker name to check')
158
- .requiredOption('-H, --hours <hours>', 'Hours threshold for message age', parseFloat)
412
+ .requiredOption('--json <json>', 'Full JSON payload with coworker and hours fields')
159
413
  .action(async (options, command) => {
160
- const useJson = command.optsWithGlobals().json;
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
+ }
161
433
  const storage = await getStorage();
162
- await listMessagesToNotify(storage, options.coworker, options.hours, useJson);
434
+ const messages = await listMessagesToNotify(storage, data.coworker, data.hours);
435
+ console.log(formatWithOptions(messages, opts));
163
436
  await storage.close();
164
437
  });
165
438
  program
166
439
  .command('mark-messages-as-notified')
167
440
  .description('Mark specific messages as notified')
168
- .requiredOption('-i, --ids <ids...>', 'Message IDs to mark', (value) => value.split(',').map(Number))
441
+ .requiredOption('--json <json>', 'Full JSON payload with ids array')
169
442
  .action(async (options, command) => {
170
- 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
+ }
171
466
  const storage = await getStorage();
172
- await markMessagesAsNotified(storage, options.ids, useJson);
467
+ await markMessagesAsNotified(storage, data.ids);
468
+ console.log(formatWithOptions({ success: true, marked: data.ids.length }, opts));
173
469
  await storage.close();
174
470
  });
175
471
  // Cron commands
176
472
  program
177
473
  .command('list-crons')
178
474
  .description('List all cron jobs for a specific coworker')
179
- .requiredOption('-c, --coworker <name>', 'Coworker name')
475
+ .requiredOption('--json <json>', 'Full JSON payload with coworker field')
180
476
  .action(async (options, command) => {
181
- 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
+ }
182
492
  const storage = await getStorage();
183
- await listCrons(storage, options.coworker, useJson);
493
+ const crons = await listCrons(storage, data.coworker);
494
+ console.log(formatWithOptions(crons, opts));
184
495
  await storage.close();
185
496
  });
186
497
  program
187
498
  .command('delete-cron')
188
499
  .description('Delete a cron job')
189
- .requiredOption('-i, --id <id>', 'Cron job ID', parseInt)
500
+ .requiredOption('--json <json>', 'Full JSON payload with id field')
190
501
  .action(async (options, command) => {
191
- 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
+ }
192
525
  const storage = await getStorage();
193
- 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));
194
528
  await storage.close();
195
529
  });
196
530
  program
197
531
  .command('enable-cron')
198
532
  .description('Enable a cron job')
199
- .requiredOption('-i, --id <id>', 'Cron job ID', parseInt)
533
+ .requiredOption('--json <json>', 'Full JSON payload with id field')
200
534
  .action(async (options, command) => {
201
- 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
+ }
202
558
  const storage = await getStorage();
203
- 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));
204
561
  await storage.close();
205
562
  });
206
563
  program
207
564
  .command('disable-cron')
208
565
  .description('Disable a cron job')
209
- .requiredOption('-i, --id <id>', 'Cron job ID', parseInt)
566
+ .requiredOption('--json <json>', 'Full JSON payload with id field')
210
567
  .action(async (options, command) => {
211
- 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
+ }
212
591
  const storage = await getStorage();
213
- 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));
214
594
  await storage.close();
215
595
  });
216
596
  program
217
597
  .command('cron-history')
218
598
  .description('Get cron job execution history')
219
- .requiredOption('-i, --id <id>', 'Cron job ID', parseInt)
220
- .option('-l, --limit <limit>', 'Number of history entries to show', '10')
599
+ .requiredOption('--json <json>', 'Full JSON payload with id and optional limit fields')
221
600
  .action(async (options, command) => {
222
- 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;
223
617
  const storage = await getStorage();
224
- await cronHistory(storage, options.id, parseInt(options.limit), useJson);
618
+ const history = await cronHistory(storage, data.id, limit);
619
+ console.log(formatWithOptions(history, opts));
225
620
  await storage.close();
226
621
  });
227
622
  program
228
623
  .command('check-cron-jobs')
229
624
  .description('Check if there are any active cron jobs for a coworker this minute')
230
- .requiredOption('-c, --coworker <name>', 'Coworker name to check')
625
+ .requiredOption('--json <json>', 'Full JSON payload with coworker field')
231
626
  .action(async (options, command) => {
232
- 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
+ }
233
642
  const storage = await getStorage();
234
- await checkCronJobs(storage, options.coworker, useJson);
643
+ const result = await checkCronJobs(storage, data.coworker);
644
+ console.log(formatWithOptions(result, opts));
235
645
  await storage.close();
236
646
  });
237
647
  program
238
648
  .command('list-active-cron-actions')
239
649
  .description('List all active cron actions for a specific coworker that should run this minute (for AI execution)')
240
- .requiredOption('-c, --coworker <name>', 'Coworker name to check')
650
+ .requiredOption('--json <json>', 'Full JSON payload with coworker field')
241
651
  .action(async (options, command) => {
242
- 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
+ }
243
667
  const storage = await getStorage();
244
- await listActiveCronJobs(storage, options.coworker, useJson);
668
+ const jobs = await listActiveCronJobs(storage, data.coworker);
669
+ console.log(formatWithOptions(jobs, opts));
245
670
  await storage.close();
246
671
  });
247
672
  program
248
673
  .command('create-cron')
249
674
  .description('Create a new cron job directly')
250
- .requiredOption('-n, --name <name>', 'Cron job name')
251
- .requiredOption('-c, --coworker <coworker>', 'Coworker name')
252
- .requiredOption('-S, --schedule <schedule>', 'Cron schedule expression')
253
- .requiredOption('-t, --task <task>', 'Task to perform (action to do)')
254
- .requiredOption('-N, --notify <instructions>', 'Instructions on who to notify when complete (can be names or descriptions)')
255
- .requiredOption('-z, --timezone <timezone>', 'Timezone')
675
+ .requiredOption('--json <json>', 'Full JSON payload with name, coworker, schedule, task, notify, and timezone fields')
256
676
  .action(async (options, command) => {
257
- 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
+ }
258
727
  const storage = await getStorage();
259
- // Combine task and notify into a structured message
260
- const message = `Action To Do:\n${options.task}\n\nWho To Notify When Complete:\n${options.notify}`;
261
- 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));
262
731
  await storage.close();
263
732
  });
264
- // Cron request management commands (top-level)
733
+ // Cron request management commands
265
734
  program
266
735
  .command('list-cron-requests')
267
736
  .description('List all cron job requests')
268
737
  .action(async (_args, command) => {
269
- const useJson = command.optsWithGlobals().json;
738
+ const opts = command.optsWithGlobals();
270
739
  const storage = await getStorage();
271
- await listCronRequests(storage, useJson);
740
+ const requests = await listCronRequests(storage);
741
+ console.log(formatWithOptions(requests, opts));
272
742
  await storage.close();
273
743
  });
274
744
  program
275
745
  .command('request-cron')
276
746
  .description('Create a new cron job request (requires approval)')
277
- .requiredOption('-n, --name <name>', 'Cron job name')
278
- .requiredOption('-c, --coworker <coworker>', 'Coworker name')
279
- .requiredOption('-S, --schedule <schedule>', 'Cron schedule expression')
280
- .requiredOption('-t, --task <task>', 'Task to perform (action to do)')
281
- .requiredOption('-N, --notify <instructions>', 'Instructions on who to notify when complete (can be names or descriptions)')
282
- .requiredOption('-z, --timezone <timezone>', 'Timezone')
747
+ .requiredOption('--json <json>', 'Full JSON payload with name, coworker, schedule, task, notify, and timezone fields')
283
748
  .action(async (options, command) => {
284
- 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
+ }
285
799
  const storage = await getStorage();
286
- // Combine task and notify into a structured message
287
- const message = `Action To Do:\n${options.task}\n\nWho To Notify When Complete:\n${options.notify}`;
288
- 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));
289
803
  await storage.close();
290
804
  });
291
805
  program
292
806
  .command('get-cron-request')
293
807
  .description('Get details of a cron job request')
294
- .requiredOption('-i, --id <id>', 'Cron request ID', parseInt)
808
+ .requiredOption('--json <json>', 'Full JSON payload with id field')
295
809
  .action(async (options, command) => {
296
- 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
+ }
297
825
  const storage = await getStorage();
298
- await getCronRequest(storage, options.id, useJson);
826
+ const request = await getCronRequest(storage, data.id);
827
+ console.log(formatWithOptions(request, opts));
299
828
  await storage.close();
300
829
  });
301
830
  program
302
831
  .command('approve-cron-request')
303
832
  .description('Approve a pending cron job request')
304
- .requiredOption('-i, --id <id>', 'Cron request ID', parseInt)
305
- .requiredOption('-r, --reviewer <name>', 'Name of the reviewer')
306
- .option('-n, --notes <notes>', 'Optional reviewer notes')
833
+ .requiredOption('--json <json>', 'Full JSON payload with id, reviewer, and optional notes fields')
307
834
  .action(async (options, command) => {
308
- 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
+ }
309
862
  const storage = await getStorage();
310
- 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));
311
865
  await storage.close();
312
866
  });
313
867
  program
314
868
  .command('reject-cron-request')
315
869
  .description('Reject a pending cron job request')
316
- .requiredOption('-i, --id <id>', 'Cron request ID', parseInt)
317
- .requiredOption('-r, --reviewer <name>', 'Name of the reviewer')
318
- .option('-n, --notes <notes>', 'Optional reviewer notes')
870
+ .requiredOption('--json <json>', 'Full JSON payload with id, reviewer, and optional notes fields')
319
871
  .action(async (options, command) => {
320
- 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
+ }
321
899
  const storage = await getStorage();
322
- 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));
323
902
  await storage.close();
324
903
  });
325
904
  program
326
905
  .command('delete-cron-request')
327
906
  .description('Delete a cron job request')
328
- .requiredOption('-i, --id <id>', 'Cron request ID', parseInt)
907
+ .requiredOption('--json <json>', 'Full JSON payload with id field')
329
908
  .action(async (options, command) => {
330
- 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
+ }
331
932
  const storage = await getStorage();
332
- 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));
333
935
  await storage.close();
334
936
  });
335
937
  // Task board commands
336
938
  program
337
939
  .command('add-task')
338
940
  .description('Create a new task')
339
- .requiredOption('-t, --title <title>', 'Task title')
340
- .option('-d, --description <desc>', 'Task description', '')
341
- .option('-a, --assignee <assignee>', 'Task assignee')
342
- .requiredOption('-c, --column <column>', 'Initial column (idea, approved idea, working on, blocked, ready for review, done)')
343
- .option('--dependencies <deps...>', 'Task dependency IDs', [])
941
+ .requiredOption('--json <json>', 'Full JSON payload with title, column, and optional fields')
344
942
  .action(async (options, command) => {
345
- 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
+ }
346
976
  const storage = await getStorage();
347
- const deps = options.dependencies?.map((id) => parseInt(id)) || [];
348
- 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));
349
980
  await storage.close();
350
981
  });
351
982
  program
352
983
  .command('list-tasks')
353
984
  .description('List all tasks')
354
- .option('-a, --assignee <assignee>', 'Filter by assignee')
355
- .option('-c, --column <column>', 'Filter by column')
356
- .option('-s, --search <query>', 'Search in title and description')
985
+ .option('--json <json>', 'Full JSON payload with optional assignee, column, and search fields')
357
986
  .action(async (options, command) => {
358
- 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
+ }
359
999
  const storage = await getStorage();
360
- 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));
361
1002
  await storage.close();
362
1003
  });
363
1004
  program
364
1005
  .command('get-task')
365
1006
  .description('Get a task by ID')
366
- .requiredOption('-i, --id <id>', 'Task ID', parseInt)
1007
+ .requiredOption('--json <json>', 'Full JSON payload with id field')
367
1008
  .action(async (options, command) => {
368
- 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
+ }
369
1024
  const storage = await getStorage();
370
- await getTask(storage, options.id, useJson);
1025
+ const task = await getTask(storage, data.id);
1026
+ console.log(formatWithOptions(task, opts));
371
1027
  await storage.close();
372
1028
  });
373
1029
  program
374
1030
  .command('update-task')
375
1031
  .description('Update a task')
376
- .requiredOption('-i, --id <id>', 'Task ID', parseInt)
377
- .option('-t, --title <title>', 'New title')
378
- .option('-d, --description <desc>', 'New description')
379
- .option('-a, --assignee <assignee>', 'New assignee')
380
- .option('-c, --column <column>', 'New column')
381
- .option('--dependencies <deps...>', 'New dependency IDs')
1032
+ .requiredOption('--json <json>', 'Full JSON payload with id and optional fields')
382
1033
  .action(async (options, command) => {
383
- 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
+ }
384
1064
  const storage = await getStorage();
385
- const deps = options.dependencies?.map((id) => parseInt(id));
386
- 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));
387
1068
  await storage.close();
388
1069
  });
389
1070
  program
390
1071
  .command('delete-task')
391
1072
  .description('Delete a task')
392
- .requiredOption('-i, --id <id>', 'Task ID', parseInt)
1073
+ .requiredOption('--json <json>', 'Full JSON payload with id field')
393
1074
  .action(async (options, command) => {
394
- 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
+ }
395
1098
  const storage = await getStorage();
396
- await deleteTask(storage, options.id, useJson);
1099
+ await deleteTask(storage, data.id);
1100
+ console.log(formatWithOptions({ success: true, message: `Task ${data.id} deleted` }, opts));
397
1101
  await storage.close();
398
1102
  });
399
1103
  program
400
1104
  .command('assign-task')
401
1105
  .description('Assign a task to someone')
402
- .requiredOption('-i, --id <id>', 'Task ID', parseInt)
403
- .requiredOption('-a, --assignee <assignee>', 'Assignee name')
1106
+ .requiredOption('--json <json>', 'Full JSON payload with id and assignee fields')
404
1107
  .action(async (options, command) => {
405
- 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
+ }
406
1135
  const storage = await getStorage();
407
- await assignTask(storage, options.id, options.assignee, useJson);
1136
+ const task = await assignTask(storage, data.id, data.assignee);
1137
+ console.log(formatWithOptions(task, opts));
408
1138
  await storage.close();
409
1139
  });
410
1140
  program
411
1141
  .command('unassign-task')
412
1142
  .description('Remove assignment from a task')
413
- .requiredOption('-i, --id <id>', 'Task ID', parseInt)
1143
+ .requiredOption('--json <json>', 'Full JSON payload with id field')
414
1144
  .action(async (options, command) => {
415
- 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
+ }
416
1168
  const storage = await getStorage();
417
- await unassignTask(storage, options.id, useJson);
1169
+ const task = await unassignTask(storage, data.id);
1170
+ console.log(formatWithOptions(task, opts));
418
1171
  await storage.close();
419
1172
  });
420
1173
  program
421
1174
  .command('move-task')
422
1175
  .description('Move a task to a different column')
423
- .requiredOption('-i, --id <id>', 'Task ID', parseInt)
424
- .requiredOption('-c, --column <column>', 'Target column')
1176
+ .requiredOption('--json <json>', 'Full JSON payload with id and column fields')
425
1177
  .action(async (options, command) => {
426
- 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
+ }
427
1205
  const storage = await getStorage();
428
- await moveTask(storage, options.id, options.column, useJson);
1206
+ const task = await moveTask(storage, data.id, data.column);
1207
+ console.log(formatWithOptions(task, opts));
429
1208
  await storage.close();
430
1209
  });
431
1210
  program
432
1211
  .command('task-stats')
433
1212
  .description('Show task statistics by column')
434
1213
  .action(async (_args, command) => {
435
- const useJson = command.optsWithGlobals().json;
1214
+ const opts = command.optsWithGlobals();
436
1215
  const storage = await getStorage();
437
- await taskStats(storage, useJson);
1216
+ const stats = await taskStats(storage);
1217
+ console.log(formatWithOptions(stats, opts));
438
1218
  await storage.close();
439
1219
  });
440
1220
  program
441
1221
  .command('task-history')
442
1222
  .description('Show column transition history for a task with durations')
443
- .requiredOption('-i, --id <id>', 'Task ID', parseInt)
1223
+ .requiredOption('--json <json>', 'Full JSON payload with id field')
444
1224
  .action(async (options, command) => {
445
- 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
+ }
446
1240
  const storage = await getStorage();
447
- await getTaskHistory(storage, options.id, useJson);
1241
+ const history = await getTaskHistory(storage, data.id);
1242
+ console.log(formatWithOptions(history, opts));
448
1243
  await storage.close();
449
1244
  });
450
1245
  program
451
1246
  .command('list-task-columns')
452
1247
  .description('List all valid task board columns')
453
1248
  .action(async (_args, command) => {
454
- const useJson = command.optsWithGlobals().json;
455
- 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();
456
1358
  });
457
1359
  program.parse();
458
1360
  //# sourceMappingURL=index.js.map