@minions-tasks/cli 0.2.2 → 0.3.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 (2) hide show
  1. package/dist/index.js +309 -1
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -4,11 +4,38 @@
4
4
  */
5
5
  import { Command } from 'commander';
6
6
  import chalk from 'chalk';
7
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync } from 'fs';
8
+ import { join, resolve } from 'path';
9
+ import { randomUUID } from 'crypto';
10
+ import { customTypes } from '@minions-tasks/sdk';
7
11
  const program = new Command();
12
+ const STORE_DIR = resolve(process.env.MINIONS_STORE || '.minions');
13
+ function ensureStore() {
14
+ if (!existsSync(STORE_DIR)) {
15
+ mkdirSync(STORE_DIR, { recursive: true });
16
+ }
17
+ }
18
+ function getTypeDir(slug) {
19
+ const dir = join(STORE_DIR, slug);
20
+ if (!existsSync(dir)) {
21
+ mkdirSync(dir, { recursive: true });
22
+ }
23
+ return dir;
24
+ }
25
+ function findType(slug) {
26
+ const type = customTypes.find(t => t.slug === slug);
27
+ if (!type) {
28
+ console.error(chalk.red(`Unknown type: ${slug}`));
29
+ console.error(chalk.dim(`Available types: ${customTypes.map(t => t.slug).join(', ')}`));
30
+ process.exit(1);
31
+ }
32
+ return type;
33
+ }
8
34
  program
9
35
  .name('tasks')
10
36
  .description('Task and work management across agents, humans, and workflows')
11
- .version('0.2.2');
37
+ .version('0.3.0');
38
+ // ─── info ──────────────────────────────────────────────────
12
39
  program
13
40
  .command('info')
14
41
  .description('Show project info')
@@ -19,5 +46,286 @@ program
19
46
  console.log(` SDK: ${chalk.cyan('@minions-tasks/sdk')}`);
20
47
  console.log(` CLI: ${chalk.cyan('@minions-tasks/cli')}`);
21
48
  console.log(` Python: ${chalk.cyan('minions-tasks')}`);
49
+ console.log(` Store: ${chalk.cyan(STORE_DIR)}`);
50
+ console.log(` Types: ${chalk.cyan(String(customTypes.length))}`);
51
+ });
52
+ // ─── types ─────────────────────────────────────────────────
53
+ const types = program.command('types').description('Manage MinionType schemas');
54
+ types
55
+ .command('list')
56
+ .alias('ls')
57
+ .description('List all available MinionTypes')
58
+ .action(() => {
59
+ console.log(chalk.bold(`\n ${customTypes.length} MinionTypes available:\n`));
60
+ for (const type of customTypes) {
61
+ const fieldCount = type.schema.length;
62
+ console.log(` ${type.icon} ${chalk.bold(type.name)} ${chalk.dim(`(${type.slug})`)}`);
63
+ console.log(` ${chalk.dim(type.description)}`);
64
+ console.log(` ${chalk.dim(`${fieldCount} fields: ${type.schema.map(f => f.name).join(', ')}`)}`);
65
+ console.log('');
66
+ }
67
+ });
68
+ types
69
+ .command('show <slug>')
70
+ .description('Show detailed schema for a MinionType')
71
+ .action((slug) => {
72
+ const type = findType(slug);
73
+ console.log(`\n ${type.icon} ${chalk.bold(type.name)}`);
74
+ console.log(` ${chalk.dim(type.description)}`);
75
+ console.log(` ${chalk.dim(`ID: ${type.id} Slug: ${type.slug}`)}\n`);
76
+ console.log(chalk.bold(' Fields:\n'));
77
+ for (const field of type.schema) {
78
+ const typeColor = field.type === 'string' ? 'green' : field.type === 'number' ? 'yellow' : field.type === 'boolean' ? 'blue' : 'magenta';
79
+ console.log(` ${chalk.dim('•')} ${chalk.bold(field.name)} ${chalk[typeColor](field.type)}`);
80
+ }
81
+ console.log('');
82
+ });
83
+ // ─── create ────────────────────────────────────────────────
84
+ program
85
+ .command('create <type>')
86
+ .description('Create a new Minion of the specified type')
87
+ .option('-d, --data <json>', 'Field data as JSON string')
88
+ .option('-f, --file <path>', 'Read field data from a JSON file')
89
+ .option('-t, --title <title>', 'Shortcut: set the title field')
90
+ .option('-s, --status <status>', 'Shortcut: set the status field')
91
+ .option('-p, --priority <priority>', 'Shortcut: set the priority field')
92
+ .action((typeSlug, opts) => {
93
+ const type = findType(typeSlug);
94
+ ensureStore();
95
+ let fields = {};
96
+ if (opts.file) {
97
+ fields = JSON.parse(readFileSync(opts.file, 'utf-8'));
98
+ }
99
+ else if (opts.data) {
100
+ fields = JSON.parse(opts.data);
101
+ }
102
+ // Apply shortcut flags
103
+ if (opts.title)
104
+ fields.title = opts.title;
105
+ if (opts.status)
106
+ fields.status = opts.status;
107
+ if (opts.priority)
108
+ fields.priority = opts.priority;
109
+ const minion = {
110
+ id: randomUUID(),
111
+ type: type.slug,
112
+ typeName: type.name,
113
+ fields,
114
+ createdAt: new Date().toISOString(),
115
+ updatedAt: new Date().toISOString(),
116
+ };
117
+ const dir = getTypeDir(type.slug);
118
+ const filePath = join(dir, `${minion.id}.json`);
119
+ writeFileSync(filePath, JSON.stringify(minion, null, 2));
120
+ console.log(chalk.green(`\n ✔ Created ${type.icon} ${type.name}`));
121
+ console.log(` ${chalk.dim('ID:')} ${minion.id}`);
122
+ console.log(` ${chalk.dim('File:')} ${filePath}`);
123
+ if (fields.title)
124
+ console.log(` ${chalk.dim('Title:')} ${fields.title}`);
125
+ console.log('');
126
+ });
127
+ // ─── list ──────────────────────────────────────────────────
128
+ program
129
+ .command('list [type]')
130
+ .alias('ls')
131
+ .description('List all Minions, optionally filtered by type')
132
+ .option('--json', 'Output as JSON')
133
+ .action((typeSlug, opts) => {
134
+ ensureStore();
135
+ const slugs = typeSlug ? [typeSlug] : customTypes.map(t => t.slug);
136
+ const allMinions = [];
137
+ for (const slug of slugs) {
138
+ const dir = join(STORE_DIR, slug);
139
+ if (!existsSync(dir))
140
+ continue;
141
+ const files = readdirSync(dir).filter(f => f.endsWith('.json'));
142
+ for (const file of files) {
143
+ const minion = JSON.parse(readFileSync(join(dir, file), 'utf-8'));
144
+ allMinions.push(minion);
145
+ }
146
+ }
147
+ if (opts.json) {
148
+ console.log(JSON.stringify(allMinions, null, 2));
149
+ return;
150
+ }
151
+ if (allMinions.length === 0) {
152
+ console.log(chalk.dim('\n No Minions found.\n'));
153
+ return;
154
+ }
155
+ console.log(chalk.bold(`\n ${allMinions.length} Minion(s):\n`));
156
+ for (const m of allMinions) {
157
+ const type = customTypes.find(t => t.slug === m.type);
158
+ const icon = type?.icon || '?';
159
+ const title = m.fields?.title || m.fields?.name || m.fields?.label || chalk.dim('(untitled)');
160
+ const status = m.fields?.status ? chalk.dim(`[${m.fields.status}]`) : '';
161
+ console.log(` ${icon} ${chalk.bold(title)} ${status}`);
162
+ console.log(` ${chalk.dim(m.id)} ${chalk.dim(m.type)}`);
163
+ }
164
+ console.log('');
165
+ });
166
+ // ─── show ──────────────────────────────────────────────────
167
+ program
168
+ .command('show <id>')
169
+ .description('Show a Minion by ID')
170
+ .option('--json', 'Output as JSON')
171
+ .action((id, opts) => {
172
+ ensureStore();
173
+ // Search across all type directories
174
+ for (const type of customTypes) {
175
+ const filePath = join(STORE_DIR, type.slug, `${id}.json`);
176
+ if (existsSync(filePath)) {
177
+ const minion = JSON.parse(readFileSync(filePath, 'utf-8'));
178
+ if (opts.json) {
179
+ console.log(JSON.stringify(minion, null, 2));
180
+ return;
181
+ }
182
+ console.log(`\n ${type.icon} ${chalk.bold(minion.fields?.title || minion.fields?.name || type.name)}`);
183
+ console.log(` ${chalk.dim(`Type: ${minion.type} ID: ${minion.id}`)}`);
184
+ console.log(` ${chalk.dim(`Created: ${minion.createdAt}`)}\n`);
185
+ console.log(chalk.bold(' Fields:\n'));
186
+ for (const [key, value] of Object.entries(minion.fields || {})) {
187
+ console.log(` ${chalk.dim('•')} ${chalk.bold(key)}: ${value}`);
188
+ }
189
+ console.log('');
190
+ return;
191
+ }
192
+ }
193
+ console.error(chalk.red(`\n Minion not found: ${id}\n`));
194
+ process.exit(1);
195
+ });
196
+ // ─── update ────────────────────────────────────────────────
197
+ program
198
+ .command('update <id>')
199
+ .description('Update fields on an existing Minion')
200
+ .option('-d, --data <json>', 'Fields to update as JSON')
201
+ .option('-s, --status <status>', 'Shortcut: update status')
202
+ .option('-p, --priority <priority>', 'Shortcut: update priority')
203
+ .action((id, opts) => {
204
+ ensureStore();
205
+ for (const type of customTypes) {
206
+ const filePath = join(STORE_DIR, type.slug, `${id}.json`);
207
+ if (existsSync(filePath)) {
208
+ const minion = JSON.parse(readFileSync(filePath, 'utf-8'));
209
+ let updates = {};
210
+ if (opts.data)
211
+ updates = JSON.parse(opts.data);
212
+ if (opts.status)
213
+ updates.status = opts.status;
214
+ if (opts.priority)
215
+ updates.priority = opts.priority;
216
+ minion.fields = { ...minion.fields, ...updates };
217
+ minion.updatedAt = new Date().toISOString();
218
+ writeFileSync(filePath, JSON.stringify(minion, null, 2));
219
+ console.log(chalk.green(`\n ✔ Updated ${type.icon} ${minion.fields?.title || type.name}`));
220
+ for (const [key, value] of Object.entries(updates)) {
221
+ console.log(` ${chalk.dim('•')} ${key} → ${value}`);
222
+ }
223
+ console.log('');
224
+ return;
225
+ }
226
+ }
227
+ console.error(chalk.red(`\n Minion not found: ${id}\n`));
228
+ process.exit(1);
229
+ });
230
+ // ─── delete ────────────────────────────────────────────────
231
+ program
232
+ .command('delete <id>')
233
+ .description('Delete a Minion by ID (sets status to cancelled if possible)')
234
+ .option('--hard', 'Permanently delete the file')
235
+ .action((id, opts) => {
236
+ ensureStore();
237
+ const { unlinkSync } = require('fs');
238
+ for (const type of customTypes) {
239
+ const filePath = join(STORE_DIR, type.slug, `${id}.json`);
240
+ if (existsSync(filePath)) {
241
+ if (opts.hard) {
242
+ unlinkSync(filePath);
243
+ console.log(chalk.yellow(`\n 🗑 Permanently deleted ${id}\n`));
244
+ }
245
+ else {
246
+ const minion = JSON.parse(readFileSync(filePath, 'utf-8'));
247
+ minion.fields.status = 'cancelled';
248
+ minion.updatedAt = new Date().toISOString();
249
+ writeFileSync(filePath, JSON.stringify(minion, null, 2));
250
+ console.log(chalk.yellow(`\n ✔ Cancelled ${type.icon} ${minion.fields?.title || type.name}`));
251
+ console.log(chalk.dim(` Use --hard to permanently delete\n`));
252
+ }
253
+ return;
254
+ }
255
+ }
256
+ console.error(chalk.red(`\n Minion not found: ${id}\n`));
257
+ process.exit(1);
258
+ });
259
+ // ─── validate ──────────────────────────────────────────────
260
+ program
261
+ .command('validate <file>')
262
+ .description('Validate a JSON file against its MinionType schema')
263
+ .action((file) => {
264
+ const data = JSON.parse(readFileSync(file, 'utf-8'));
265
+ const type = customTypes.find(t => t.slug === data.type);
266
+ if (!type) {
267
+ console.error(chalk.red(`\n Unknown type: ${data.type}\n`));
268
+ process.exit(1);
269
+ }
270
+ const errors = [];
271
+ const schemaFields = type.schema.map(f => f.name);
272
+ const dataFields = Object.keys(data.fields || {});
273
+ // Check for missing fields
274
+ for (const f of schemaFields) {
275
+ if (!(f in (data.fields || {}))) {
276
+ errors.push(`Missing field: ${f}`);
277
+ }
278
+ }
279
+ // Check for unknown fields
280
+ for (const f of dataFields) {
281
+ if (!schemaFields.includes(f)) {
282
+ errors.push(`Unknown field: ${f}`);
283
+ }
284
+ }
285
+ // Check field types
286
+ for (const field of type.schema) {
287
+ const value = data.fields?.[field.name];
288
+ if (value === undefined)
289
+ continue;
290
+ if (field.type === 'number' && typeof value !== 'number') {
291
+ errors.push(`Field ${field.name} should be number, got ${typeof value}`);
292
+ }
293
+ if (field.type === 'boolean' && typeof value !== 'boolean') {
294
+ errors.push(`Field ${field.name} should be boolean, got ${typeof value}`);
295
+ }
296
+ }
297
+ if (errors.length === 0) {
298
+ console.log(chalk.green(`\n ✔ Valid ${type.icon} ${type.name}\n`));
299
+ }
300
+ else {
301
+ console.log(chalk.red(`\n ✘ ${errors.length} validation error(s):\n`));
302
+ for (const err of errors) {
303
+ console.log(` ${chalk.red('•')} ${err}`);
304
+ }
305
+ console.log('');
306
+ process.exit(1);
307
+ }
308
+ });
309
+ // ─── stats ─────────────────────────────────────────────────
310
+ program
311
+ .command('stats')
312
+ .description('Show statistics about stored Minions')
313
+ .action(() => {
314
+ ensureStore();
315
+ console.log(chalk.bold('\n Minion Statistics:\n'));
316
+ let total = 0;
317
+ for (const type of customTypes) {
318
+ const dir = join(STORE_DIR, type.slug);
319
+ if (!existsSync(dir)) {
320
+ console.log(` ${type.icon} ${type.name.padEnd(22)} ${chalk.dim('0')}`);
321
+ continue;
322
+ }
323
+ const count = readdirSync(dir).filter(f => f.endsWith('.json')).length;
324
+ total += count;
325
+ const bar = chalk.cyan('█'.repeat(Math.min(count, 30)));
326
+ console.log(` ${type.icon} ${type.name.padEnd(22)} ${String(count).padStart(4)} ${bar}`);
327
+ }
328
+ console.log(`\n ${chalk.bold('Total:')} ${total} Minion(s)`);
329
+ console.log(` ${chalk.dim(`Store: ${STORE_DIR}`)}\n`);
22
330
  });
23
331
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minions-tasks/cli",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "CLI for minions-tasks — Task and work management across agents, humans, and workflows",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -28,7 +28,7 @@
28
28
  "commander": "^14.0.3",
29
29
  "inquirer": "^13.2.5",
30
30
  "minions-sdk": "^0.2.2",
31
- "@minions-tasks/sdk": "0.2.2"
31
+ "@minions-tasks/sdk": "0.3.0"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@types/node": "^25.3.0",