@myvillage/cli 1.3.0 → 1.5.1

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.
@@ -2,8 +2,9 @@ import { existsSync, readFileSync } from 'fs';
2
2
  import { join, resolve } from 'path';
3
3
  import chalk from 'chalk';
4
4
  import ora from 'ora';
5
+ import { villageSpinner, brand } from '../utils/brand.js';
5
6
  import { isAuthenticated } from '../utils/auth.js';
6
- import { getMyGames, getGameStats } from '../utils/api.js';
7
+ import { getMyGames, getGameStatus } from '../utils/api.js';
7
8
 
8
9
  function readPackageJson(dir) {
9
10
  const pkgPath = join(dir, 'package.json');
@@ -48,33 +49,49 @@ export async function statusCommand() {
48
49
  }
49
50
 
50
51
  async function showGameStatus(gameId) {
51
- const spinner = ora('Fetching game status...').start();
52
+ const spinner = villageSpinner('Fetching game status...').start();
52
53
 
53
54
  try {
54
- const stats = await getGameStats(gameId);
55
+ const data = await getGameStatus(gameId);
56
+ const game = data.game || data;
55
57
 
56
58
  spinner.stop();
57
59
 
58
60
  console.log();
59
- console.log(chalk.bold(` ${stats.name || gameId}`));
61
+ console.log(chalk.bold(` ${game.title || game.name || gameId}`));
60
62
  console.log();
61
- console.log(` Status: ${formatStatus(stats.status)}`);
62
- console.log(` Play Count: ${chalk.cyan(stats.playCount ?? 0)}`);
63
- console.log(` MVT Earned: ${chalk.yellow(stats.mvtEarned ?? 0)} tokens`);
64
- console.log(` Last Updated: ${chalk.dim(formatDate(stats.lastUpdated))}`);
63
+ console.log(` Status: ${formatStatus(game.status)}`);
64
+ console.log(` Play Count: ${brand.gold(game.playCount ?? 0)}`);
65
+ console.log(` MVT Earned: ${chalk.yellow(game.mvtAwarded ?? 0)} tokens`);
66
+ console.log(` Last Updated: ${brand.teal(formatDate(game.updatedAt || game.lastUpdated))}`);
65
67
 
66
- if (stats.url) {
67
- console.log(` URL: ${chalk.cyan(stats.url)}`);
68
+ if (game.slug) {
69
+ console.log(` Slug: ${brand.teal(game.slug)}`);
68
70
  }
71
+
72
+ // Show review history if available
73
+ if (game.reviews?.length > 0) {
74
+ console.log();
75
+ console.log(chalk.bold(' Review History:'));
76
+ for (const review of game.reviews) {
77
+ const action = formatReviewAction(review.action);
78
+ const date = formatDate(review.createdAt);
79
+ console.log(` ${action} - ${brand.teal(date)}`);
80
+ if (review.notes) {
81
+ console.log(` ${brand.teal(review.notes)}`);
82
+ }
83
+ }
84
+ }
85
+
69
86
  console.log();
70
87
  } catch (err) {
71
- const message = err.response?.data?.message || err.message;
88
+ const message = err.response?.data?.error || err.response?.data?.message || err.message;
72
89
  spinner.fail(`Failed to fetch game status: ${message}`);
73
90
  }
74
91
  }
75
92
 
76
93
  async function showAllGames() {
77
- const spinner = ora('Fetching your games...').start();
94
+ const spinner = villageSpinner('Fetching your games...').start();
78
95
 
79
96
  try {
80
97
  const data = await getMyGames();
@@ -84,8 +101,8 @@ async function showAllGames() {
84
101
 
85
102
  if (!games.length) {
86
103
  console.log();
87
- console.log(chalk.dim(' No deployed games found.'));
88
- console.log(chalk.dim(' Create a game with "myvillage create-game" and deploy it!'));
104
+ console.log(brand.teal(' No deployed games found.'));
105
+ console.log(brand.teal(' Create a game with "myvillage create-game" and deploy it!'));
89
106
  console.log();
90
107
  return;
91
108
  }
@@ -95,32 +112,51 @@ async function showAllGames() {
95
112
  console.log();
96
113
 
97
114
  for (const game of games) {
98
- console.log(` ${chalk.yellow(game.name || game.gameId)}`);
99
- console.log(` Status: ${formatStatus(game.status)} | Plays: ${chalk.cyan(game.playCount ?? 0)} | MVT: ${chalk.yellow(game.mvtEarned ?? 0)}`);
115
+ console.log(` ${chalk.yellow(game.title || game.name || game.id)}`);
116
+ console.log(` Status: ${formatStatus(game.status)} | Plays: ${brand.gold(game.playCount ?? 0)} | MVT: ${chalk.yellow(game.mvtAwarded ?? 0)}`);
100
117
 
101
- if (game.url) {
102
- console.log(` ${chalk.dim(game.url)}`);
118
+ if (game.slug) {
119
+ console.log(` ${brand.teal(game.slug)}`);
103
120
  }
104
121
 
105
122
  console.log();
106
123
  }
107
124
  } catch (err) {
108
- const message = err.response?.data?.message || err.message;
125
+ const message = err.response?.data?.error || err.response?.data?.message || err.message;
109
126
  spinner.fail(`Failed to fetch games: ${message}`);
110
127
  }
111
128
  }
112
129
 
113
130
  function formatStatus(status) {
114
131
  switch (status) {
115
- case 'live':
116
- return chalk.green('Live');
117
- case 'building':
118
- return chalk.yellow('Building');
119
- case 'failed':
120
- return chalk.red('Failed');
121
- case 'pending':
122
- return chalk.dim('Pending');
132
+ case 'DRAFT':
133
+ return brand.teal('Draft');
134
+ case 'SUBMITTED':
135
+ return chalk.yellow('Submitted');
136
+ case 'UNDER_REVIEW':
137
+ return chalk.yellow('Under Review');
138
+ case 'APPROVED':
139
+ return chalk.green('Approved');
140
+ case 'REJECTED':
141
+ return chalk.red('Rejected');
142
+ case 'PUBLISHED':
143
+ return chalk.green('Published');
144
+ case 'ARCHIVED':
145
+ return brand.teal('Archived');
146
+ default:
147
+ return brand.teal(status || 'Unknown');
148
+ }
149
+ }
150
+
151
+ function formatReviewAction(action) {
152
+ switch (action) {
153
+ case 'APPROVE':
154
+ return chalk.green('Approved');
155
+ case 'REJECT':
156
+ return chalk.red('Rejected');
157
+ case 'REQUEST_CHANGES':
158
+ return chalk.yellow('Changes Requested');
123
159
  default:
124
- return chalk.dim(status || 'Unknown');
160
+ return brand.teal(action || 'Unknown');
125
161
  }
126
162
  }
@@ -1,25 +1,35 @@
1
1
  import chalk from 'chalk';
2
2
  import ora from 'ora';
3
+ import { villageSpinner, brand } from '../utils/brand.js';
3
4
  import { isAuthenticated } from '../utils/auth.js';
4
- import { castVote, removeVote } from '../utils/api.js';
5
+ import { castVote, listMyAgents } from '../utils/api.js';
5
6
 
6
7
  export async function voteCommand(options) {
7
8
  if (!isAuthenticated()) {
8
- console.log(chalk.red(' Authentication required. Run \'myvillage login\' first.'));
9
+ console.log(chalk.red(' \u2717 Authentication required. Run \'myvillage login\' first.'));
9
10
  return;
10
11
  }
11
12
 
12
- // Handle undo
13
- if (options.undo) {
14
- const spinner = ora('Removing vote...').start();
13
+ // Resolve agent identity if --as flag provided
14
+ let agentProfileId = null;
15
+
16
+ if (options.as) {
15
17
  try {
16
- await removeVote(options.undo);
17
- spinner.succeed('Vote removed.');
18
+ const agentsResult = await listMyAgents();
19
+ const agents = agentsResult.data || agentsResult;
20
+ const agent = Array.isArray(agents) ? agents.find(a => a.handle === options.as) : null;
21
+
22
+ if (!agent) {
23
+ console.log(chalk.red(` \u2717 Agent @${options.as} not found. Run 'myvillage agent' to see your agents.\n`));
24
+ return;
25
+ }
26
+ agentProfileId = agent.id;
27
+ console.log(brand.teal(` Voting as agent @${agent.handle}\n`));
18
28
  } catch (err) {
19
29
  const message = err.response?.data?.error || err.response?.data?.message || err.message;
20
- spinner.fail(`Failed to remove vote: ${message}`);
30
+ console.log(chalk.red(` \u2717 Failed to resolve agent: ${message}\n`));
31
+ return;
21
32
  }
22
- return;
23
33
  }
24
34
 
25
35
  // Determine target
@@ -27,21 +37,39 @@ export async function voteCommand(options) {
27
37
  const targetId = options.post || options.comment;
28
38
 
29
39
  if (!targetType || !targetId) {
30
- console.log(chalk.red(' Specify a target: --post <id> or --comment <id>'));
31
- console.log(chalk.dim(' Example: myvillage vote --post abc123'));
32
- console.log(chalk.dim(' Example: myvillage vote --comment xyz789 --down\n'));
40
+ console.log(chalk.red(' \u2717 Specify a target: --post <id> or --comment <id>'));
41
+ console.log(brand.teal(' Example: myvillage vote --post abc123'));
42
+ console.log(brand.teal(' Example: myvillage vote --comment xyz789 --down\n'));
33
43
  return;
34
44
  }
35
45
 
36
46
  const value = options.down ? -1 : 1;
37
- const action = value === 1 ? 'Upvoting' : 'Downvoting';
38
- const spinner = ora(`${action}...`).start();
47
+ const action = options.undo ? 'Removing vote from' : (value === 1 ? 'Upvoting' : 'Downvoting');
48
+ const spinner = villageSpinner(`${action} ${targetType.toLowerCase()}...`).start();
39
49
 
40
50
  try {
41
- await castVote({ targetType, targetId, value });
42
- const emoji = value === 1 ? chalk.green('▲') : chalk.red('▼');
43
- const word = value === 1 ? 'Upvoted' : 'Downvoted';
44
- spinner.succeed(`${word} ${targetType.toLowerCase()} ${targetId} ${emoji}`);
51
+ const data = { targetType, targetId, value };
52
+ if (agentProfileId) {
53
+ data.agentProfileId = agentProfileId;
54
+ }
55
+ const result = await castVote(data);
56
+ const resultAction = result?.data?.action;
57
+
58
+ if (options.undo) {
59
+ if (resultAction === 'removed') {
60
+ spinner.succeed(`Vote removed from ${targetType.toLowerCase()} ${targetId}`);
61
+ } else {
62
+ spinner.warn(`No existing vote to remove (${resultAction || 'created'}). A new vote was cast instead.`);
63
+ }
64
+ } else {
65
+ const emoji = value === 1 ? chalk.green('\u25b2') : chalk.red('\u25bc');
66
+ if (resultAction === 'removed') {
67
+ spinner.succeed(`Vote toggled off for ${targetType.toLowerCase()} ${targetId}`);
68
+ } else {
69
+ const word = value === 1 ? 'Upvoted' : 'Downvoted';
70
+ spinner.succeed(`${word} ${targetType.toLowerCase()} ${targetId} ${emoji}`);
71
+ }
72
+ }
45
73
  } catch (err) {
46
74
  const message = err.response?.data?.error || err.response?.data?.message || err.message;
47
75
  spinner.fail(`Failed to vote: ${message}`);
package/src/index.js CHANGED
@@ -4,6 +4,9 @@ import updateNotifier from 'update-notifier';
4
4
  import { loginCommand } from './commands/login.js';
5
5
  import { logoutCommand } from './commands/logout.js';
6
6
  import { createGameCommand } from './commands/create-game.js';
7
+ import { createCommand } from './commands/create-app.js';
8
+ import { deployCommand } from './commands/deploy.js';
9
+ import { statusCommand } from './commands/status.js';
7
10
  import { feedCommand } from './commands/feed.js';
8
11
  import {
9
12
  communityCommand,
@@ -15,6 +18,7 @@ import {
15
18
  } from './commands/community.js';
16
19
  import {
17
20
  postCommand,
21
+ postViewCommand,
18
22
  postCreateCommand,
19
23
  postListCommand,
20
24
  postEditCommand,
@@ -32,7 +36,35 @@ import {
32
36
  agentDeleteCommand,
33
37
  agentJoinCommand,
34
38
  agentLeaveCommand,
39
+ agentRunCommand,
35
40
  } from './commands/agent.js';
41
+ import {
42
+ agentStartCommand,
43
+ agentStopCommand,
44
+ agentStatusCommand,
45
+ agentLogsCommand,
46
+ agentAddToolCommand,
47
+ agentRemoveToolCommand,
48
+ } from './commands/agent-local.js';
49
+ import {
50
+ bizreqsNewCommand,
51
+ bizreqsSpecCommand,
52
+ bizreqsListCommand,
53
+ bizreqsStatusCommand,
54
+ bizreqsImportCommand,
55
+ } from './commands/bizreqs.js';
56
+ import {
57
+ soulprintInitCommand,
58
+ soulprintIngestCommand,
59
+ soulprintDatasetListCommand,
60
+ soulprintDatasetPullCommand,
61
+ soulprintTrainCommand,
62
+ soulprintJobsCommand,
63
+ soulprintJobDetailCommand,
64
+ soulprintPushCommand,
65
+ soulprintPublishCommand,
66
+ soulprintModelsCommand,
67
+ } from './commands/soulprint.js';
36
68
 
37
69
  const require = createRequire(import.meta.url);
38
70
  const pkg = require('../package.json');
@@ -53,6 +85,7 @@ export function run() {
53
85
  program
54
86
  .command('login')
55
87
  .description('Authenticate with MyVillageOS')
88
+ .option('--no-browser', 'Manual login for headless environments (servers, SSH, etc.)')
56
89
  .action(loginCommand);
57
90
 
58
91
  program
@@ -65,6 +98,21 @@ export function run() {
65
98
  .description('Create a new game project with interactive wizard')
66
99
  .action(createGameCommand);
67
100
 
101
+ program
102
+ .command('create')
103
+ .description('Create a new project (portal, data labeling app, or game)')
104
+ .action(createCommand);
105
+
106
+ program
107
+ .command('deploy')
108
+ .description('Build and deploy your game to MyVillageOS')
109
+ .action(deployCommand);
110
+
111
+ program
112
+ .command('status')
113
+ .description('Check deployment status of your games')
114
+ .action(statusCommand);
115
+
68
116
  // ── Network: Feed ───────────────────────────────────
69
117
 
70
118
  program
@@ -85,6 +133,11 @@ export function run() {
85
133
  .description('View a post by ID, or create one interactively')
86
134
  .action(postCommand);
87
135
 
136
+ postCmd
137
+ .command('view <id>')
138
+ .description('View a post by ID')
139
+ .action(postViewCommand);
140
+
88
141
  postCmd
89
142
  .command('create')
90
143
  .description('Create a new post (interactive)')
@@ -168,7 +221,8 @@ export function run() {
168
221
  .option('--post <id>', 'Vote on a post')
169
222
  .option('--comment <id>', 'Vote on a comment')
170
223
  .option('-d, --down', 'Downvote instead of upvote')
171
- .option('--undo <voteId>', 'Remove a vote')
224
+ .option('--undo', 'Remove your existing vote (re-posts to toggle off)')
225
+ .option('--as <agent-handle>', 'Vote as one of your agents')
172
226
  .action(voteCommand);
173
227
 
174
228
  // ── Network: Search ─────────────────────────────────
@@ -227,5 +281,194 @@ export function run() {
227
281
  .description('Leave a community as an agent')
228
282
  .action(agentLeaveCommand);
229
283
 
284
+ agentCmd
285
+ .command('run <handle>')
286
+ .description('Run a workflow agent (WORKFLOW or HYBRID only)')
287
+ .option('--input <text>', 'Input data for the agent')
288
+ .action(agentRunCommand);
289
+
290
+ // Local agent lifecycle commands
291
+ agentCmd
292
+ .command('start <name>')
293
+ .description('Start a local agent daemon')
294
+ .action(agentStartCommand);
295
+
296
+ agentCmd
297
+ .command('stop <name>')
298
+ .description('Stop a local agent daemon')
299
+ .action(agentStopCommand);
300
+
301
+ agentCmd
302
+ .command('status [name]')
303
+ .description('Show local agent status')
304
+ .option('--remote', 'Include server-side activity from MAN')
305
+ .action(agentStatusCommand);
306
+
307
+ agentCmd
308
+ .command('logs <name>')
309
+ .description('View agent activity logs')
310
+ .option('--follow', 'Follow log output in real-time')
311
+ .option('--since <time>', 'Show logs since timestamp')
312
+ .action(agentLogsCommand);
313
+
314
+ agentCmd
315
+ .command('add-tool <name> <tool>')
316
+ .description('Add an MCP server tool to a local agent')
317
+ .action(agentAddToolCommand);
318
+
319
+ agentCmd
320
+ .command('remove-tool <name> <tool>')
321
+ .description('Remove an MCP server tool from a local agent')
322
+ .action(agentRemoveToolCommand);
323
+
324
+ // ── BizReqs: Business Requirements Pipeline ───────────
325
+
326
+ const bizreqsCmd = program
327
+ .command('bizreqs')
328
+ .description('Business requirements intake and project pipeline');
329
+
330
+ bizreqsCmd
331
+ .command('new')
332
+ .description('Start a new AI-guided business intake session')
333
+ .option('--org <name>', 'Organization name')
334
+ .option('--contact <name>', 'Contact name')
335
+ .option('--from-file <path>', 'Path to a text file with initial context')
336
+ .option('--from-url <url>', 'URL to the organization\'s website')
337
+ .option('--quick', 'Shortened flow (skip dream state, fewer exchanges)')
338
+ .action(bizreqsNewCommand);
339
+
340
+ bizreqsCmd
341
+ .command('spec <id>')
342
+ .description('Generate full project specification from an intake')
343
+ .option('--detail <level>', 'Detail level: brief, standard, comprehensive', 'standard')
344
+ .option('--output <dir>', 'Output directory for spec file', './specs')
345
+ .action(bizreqsSpecCommand);
346
+
347
+ bizreqsCmd
348
+ .command('list')
349
+ .description('List pipeline submissions')
350
+ .option('--status <status>', 'Filter: new, in-review, spec-ready, assigned, in-progress, delivered')
351
+ .option('--city <city>', 'Filter by city')
352
+ .option('--search <query>', 'Search by org name, solution, or ID')
353
+ .option('--sort <sort>', 'Sort: newest, oldest, priority', 'newest')
354
+ .option('-n, --limit <number>', 'Number of results', '50')
355
+ .option('--offset <number>', 'Pagination offset', '0')
356
+ .option('--json', 'Output raw JSON')
357
+ .action(bizreqsListCommand);
358
+
359
+ bizreqsCmd
360
+ .command('status <id>')
361
+ .description('Check project status')
362
+ .action(bizreqsStatusCommand);
363
+
364
+ bizreqsCmd
365
+ .command('import')
366
+ .description('Import requirements from a file or URL')
367
+ .option('--file <path>', 'Path to .txt or .md file')
368
+ .option('--url <url>', 'URL to fetch content from')
369
+ .option('--org <name>', 'Organization name')
370
+ .option('--contact <name>', 'Contact name')
371
+ .action(bizreqsImportCommand);
372
+
373
+ // ── SoulPrint Studio: Model Training Pipeline ───────────
374
+
375
+ const soulprintCmd = program
376
+ .command('soulprint')
377
+ .description('SoulPrint Studio \u2014 datasets, training, and model publishing');
378
+
379
+ soulprintCmd
380
+ .command('init')
381
+ .description('Initialize local training workspace')
382
+ .option('--skip-python', 'Skip Python venv setup')
383
+ .option('--skip-scripts', 'Skip training script download')
384
+ .action(soulprintInitCommand);
385
+
386
+ soulprintCmd
387
+ .command('ingest <path>')
388
+ .description('Ingest training data into a dataset')
389
+ .requiredOption('--dataset <slug>', 'Target dataset slug')
390
+ .requiredOption('--type <type>', 'Data type: text, image, audio, structured, multimodal')
391
+ .option('--source <source>', 'Ingestion source label', 'CLI')
392
+ .option('--captions <path>', 'Path to captions file (image/multimodal)')
393
+ .option('--transcriptions <path>', 'Path to transcriptions file (audio)')
394
+ .option('--schema <path>', 'Path to schema JSON file (structured)')
395
+ .option('--recursive', 'Recurse into subdirectories')
396
+ .option('--glob <pattern>', 'File glob pattern to filter')
397
+ .option('--split <split>', 'Assign all items to a split: train, validation, test')
398
+ .option('--dry-run', 'Show what would be ingested without uploading')
399
+ .option('--concurrency <n>', 'Parallel upload limit', '5')
400
+ .action(soulprintIngestCommand);
401
+
402
+ const soulprintDatasetCmd = soulprintCmd
403
+ .command('datasets')
404
+ .description('Manage training datasets');
405
+
406
+ soulprintDatasetCmd
407
+ .command('list')
408
+ .description('List available datasets on SoulPrint Studio')
409
+ .option('--type <type>', 'Filter by type: text, image, audio, structured, multimodal')
410
+ .option('--status <status>', 'Filter by status: collecting, ready, training, archived')
411
+ .option('--json', 'Output raw JSON')
412
+ .action(soulprintDatasetListCommand);
413
+
414
+ soulprintDatasetCmd
415
+ .command('pull <slug>')
416
+ .description('Download a dataset to your local machine')
417
+ .option('--version <number>', 'Specific version (default: latest frozen)')
418
+ .option('--split <split>', 'Download only a specific split: train, validation, test')
419
+ .option('--force', 'Re-download even if already present locally')
420
+ .action(soulprintDatasetPullCommand);
421
+
422
+ soulprintCmd
423
+ .command('train')
424
+ .description('Run model training locally')
425
+ .requiredOption('--type <type>', 'Training type: text, image, audio, structured, multimodal')
426
+ .requiredOption('--dataset <slug>', 'Dataset slug to train on')
427
+ .option('--version <number>', 'Dataset version (default: latest)')
428
+ .option('--base <model>', 'Base model identifier')
429
+ .option('--method <method>', 'Training method')
430
+ .option('--config <path>', 'Path to training config YAML file')
431
+ .option('--name <name>', 'Name for the resulting model')
432
+ .option('--dry-run', 'Validate config and dataset without starting training')
433
+ .action(soulprintTrainCommand);
434
+
435
+ soulprintCmd
436
+ .command('jobs [jobId]')
437
+ .description('View training job status')
438
+ .option('--status <status>', 'Filter by status')
439
+ .option('--json', 'Output raw JSON')
440
+ .action((jobId, options) => {
441
+ if (jobId) return soulprintJobDetailCommand(jobId, options);
442
+ return soulprintJobsCommand(options);
443
+ });
444
+
445
+ soulprintCmd
446
+ .command('models')
447
+ .description('List models in the registry')
448
+ .option('--status <status>', 'Filter: draft, validated, published, rejected')
449
+ .option('--type <type>', 'Filter: text, image, audio, structured, multimodal')
450
+ .option('--json', 'Output raw JSON')
451
+ .action(soulprintModelsCommand);
452
+
453
+ soulprintCmd
454
+ .command('push <path>')
455
+ .description('Upload locally trained model artifacts to SoulPrint Studio')
456
+ .requiredOption('--name <name>', 'Model name')
457
+ .requiredOption('--type <type>', 'Model type: text, image, audio, structured, multimodal')
458
+ .option('--base <model>', 'Base model it was trained from')
459
+ .option('--method <method>', 'Training method used')
460
+ .option('--job <jobId>', 'Link to an existing training job')
461
+ .option('--description <text>', 'Model description')
462
+ .action(soulprintPushCommand);
463
+
464
+ soulprintCmd
465
+ .command('publish <modelSlug>')
466
+ .description('Publish a validated model to the MyVillage platform')
467
+ .option('--villager <id>', 'Target villager ID to own the model')
468
+ .option('--villages <ids>', 'Comma-separated village IDs to associate')
469
+ .option('--tier <tier>', 'Model tier: FREE, BASIC, PRO, ENTERPRISE', 'FREE')
470
+ .option('--public', 'Make the model publicly available')
471
+ .action(soulprintPublishCommand);
472
+
230
473
  program.parse();
231
474
  }