@myvillage/cli 1.5.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.
package/src/index.js CHANGED
@@ -53,6 +53,18 @@ import {
53
53
  bizreqsStatusCommand,
54
54
  bizreqsImportCommand,
55
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';
56
68
 
57
69
  const require = createRequire(import.meta.url);
58
70
  const pkg = require('../package.json');
@@ -73,6 +85,7 @@ export function run() {
73
85
  program
74
86
  .command('login')
75
87
  .description('Authenticate with MyVillageOS')
88
+ .option('--no-browser', 'Manual login for headless environments (servers, SSH, etc.)')
76
89
  .action(loginCommand);
77
90
 
78
91
  program
@@ -357,5 +370,105 @@ export function run() {
357
370
  .option('--contact <name>', 'Contact name')
358
371
  .action(bizreqsImportCommand);
359
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
+
360
473
  program.parse();
361
474
  }
@@ -5,9 +5,9 @@ import { stringify as stringifyYaml } from 'yaml';
5
5
  // ── MCP Tool Catalog ────────────────────────────────────
6
6
 
7
7
  const TOOL_CATALOG = {
8
- 'man-feed': {
9
- command: 'internal',
10
- description: 'MAN feed access (post, read, react)',
8
+ myvillage: {
9
+ url: 'https://mcp.myvillageproject.ai',
10
+ description: 'MyVillageOS platform access (feed, posts, communities, wallet, knowledge)',
11
11
  always_enabled: true,
12
12
  },
13
13
  filesystem: {
@@ -151,11 +151,11 @@ ${description}
151
151
  function generateToolsYaml(selectedTools) {
152
152
  const servers = {};
153
153
 
154
- // man-feed is always included
155
- servers['man-feed'] = TOOL_CATALOG['man-feed'];
154
+ // myvillage is always included
155
+ servers['myvillage'] = TOOL_CATALOG['myvillage'];
156
156
 
157
157
  for (const toolId of selectedTools) {
158
- if (toolId === 'man-feed') continue;
158
+ if (toolId === 'myvillage') continue;
159
159
  if (TOOL_CATALOG[toolId]) {
160
160
  servers[toolId] = { ...TOOL_CATALOG[toolId] };
161
161
  }
package/src/utils/api.js CHANGED
@@ -7,7 +7,7 @@ const require = createRequire(import.meta.url);
7
7
  const { version } = require('../../package.json');
8
8
  const USER_AGENT = `MyVillageOS-CLI/${version}`;
9
9
 
10
- function createClient(baseURL) {
10
+ export function createClient(baseURL) {
11
11
  const client = axios.create({
12
12
  baseURL,
13
13
  headers: {
@@ -10,6 +10,7 @@ const DEFAULT_CONFIG = {
10
10
  networkBaseUrl: 'https://portal.myvillageproject.ai/api/network',
11
11
  bizreqsBaseUrl: 'https://portal.myvillageproject.ai/api/bizreqs',
12
12
  oauthBaseUrl: 'https://portal.myvillageproject.ai/api/oauth',
13
+ soulprintBaseUrl: 'https://soulprint-studio.myvillageproject.ai/api',
13
14
  clientId: 'mvos_aG_c729fuQxvvqYHOnkgTQ',
14
15
  callbackPort: 3737,
15
16
  anthropicApiKey: null,
@@ -0,0 +1,136 @@
1
+ import { createClient } from './api.js';
2
+ import { getConfig } from './config.js';
3
+
4
+ // ── Client Factory ─────────────────────────────────────
5
+
6
+ export function getSoulprintClient() {
7
+ const config = getConfig();
8
+ return createClient(config.soulprintBaseUrl);
9
+ }
10
+
11
+ // ── Datasets ───────────────────────────────────────────
12
+
13
+ export async function listDatasets(params = {}) {
14
+ const client = getSoulprintClient();
15
+ const response = await client.get('/datasets', { params });
16
+ return response.data;
17
+ }
18
+
19
+ export async function getDataset(slug) {
20
+ const client = getSoulprintClient();
21
+ const response = await client.get(`/datasets/${slug}`);
22
+ return response.data;
23
+ }
24
+
25
+ export async function getDatasetDownload(slug, version) {
26
+ const client = getSoulprintClient();
27
+ const params = version ? { version } : {};
28
+ const response = await client.get(`/datasets/${slug}/download`, { params });
29
+ return response.data;
30
+ }
31
+
32
+ // ── Training Jobs ──────────────────────────────────────
33
+
34
+ export async function createJob(data) {
35
+ const client = getSoulprintClient();
36
+ const response = await client.post('/jobs', data);
37
+ return response.data;
38
+ }
39
+
40
+ export async function listJobs(params = {}) {
41
+ const client = getSoulprintClient();
42
+ const response = await client.get('/jobs', { params });
43
+ return response.data;
44
+ }
45
+
46
+ export async function getJob(id) {
47
+ const client = getSoulprintClient();
48
+ const response = await client.get(`/jobs/${id}`);
49
+ return response.data;
50
+ }
51
+
52
+ export async function updateJobStatus(id, data) {
53
+ const client = getSoulprintClient();
54
+ const response = await client.patch(`/jobs/${id}/status`, data);
55
+ return response.data;
56
+ }
57
+
58
+ export async function completeJob(id, data) {
59
+ const client = getSoulprintClient();
60
+ const response = await client.post(`/jobs/${id}/complete`, data);
61
+ return response.data;
62
+ }
63
+
64
+ export async function failJob(id, data) {
65
+ const client = getSoulprintClient();
66
+ const response = await client.post(`/jobs/${id}/fail`, data);
67
+ return response.data;
68
+ }
69
+
70
+ // ── Models ─────────────────────────────────────────────
71
+
72
+ export async function listModels(params = {}) {
73
+ const client = getSoulprintClient();
74
+ const response = await client.get('/models', { params });
75
+ return response.data;
76
+ }
77
+
78
+ export async function getModel(slug) {
79
+ const client = getSoulprintClient();
80
+ const response = await client.get(`/models/${slug}`);
81
+ return response.data;
82
+ }
83
+
84
+ export async function publishModel(slug, data) {
85
+ const client = getSoulprintClient();
86
+ const response = await client.post(`/models/${slug}/publish`, data);
87
+ return response.data;
88
+ }
89
+
90
+ // ── Ingestion ──────────────────────────────────────────
91
+
92
+ export async function ingestText(data) {
93
+ const client = getSoulprintClient();
94
+ const response = await client.post('/ingest/text', data);
95
+ return response.data;
96
+ }
97
+
98
+ export async function ingestStructured(data) {
99
+ const client = getSoulprintClient();
100
+ const response = await client.post('/ingest/structured', data);
101
+ return response.data;
102
+ }
103
+
104
+ export async function prepareIngestion(data) {
105
+ const client = getSoulprintClient();
106
+ const response = await client.post('/ingest/prepare', data);
107
+ return response.data;
108
+ }
109
+
110
+ export async function completeIngestion(ingestionId, data) {
111
+ const client = getSoulprintClient();
112
+ const response = await client.post(`/ingest/${ingestionId}/complete`, data);
113
+ return response.data;
114
+ }
115
+
116
+ export async function abortIngestion(ingestionId) {
117
+ const client = getSoulprintClient();
118
+ const response = await client.post(`/ingest/${ingestionId}/abort`);
119
+ return response.data;
120
+ }
121
+
122
+ // ── Upload ─────────────────────────────────────────────
123
+
124
+ export async function getUploadUrls(data) {
125
+ const client = getSoulprintClient();
126
+ const response = await client.post('/upload/presign', data);
127
+ return response.data;
128
+ }
129
+
130
+ // ── Scripts ────────────────────────────────────────────
131
+
132
+ export async function getScriptsManifest() {
133
+ const client = getSoulprintClient();
134
+ const response = await client.get('/scripts/manifest');
135
+ return response.data;
136
+ }
@@ -0,0 +1,158 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { execSync } from 'child_process';
4
+ import { getConfigDir } from './config.js';
5
+ import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
6
+
7
+ const SOULPRINT_DIR_NAME = 'soulprint';
8
+
9
+ // ── Directory Helpers ──────────────────────────────────
10
+
11
+ export function getSoulprintDir() {
12
+ return join(getConfigDir(), SOULPRINT_DIR_NAME);
13
+ }
14
+
15
+ export function getDatasetsDir() {
16
+ return join(getSoulprintDir(), 'datasets');
17
+ }
18
+
19
+ export function getModelsDir() {
20
+ return join(getSoulprintDir(), 'models');
21
+ }
22
+
23
+ export function getConfigsDir() {
24
+ return join(getSoulprintDir(), 'configs');
25
+ }
26
+
27
+ export function getLogsDir() {
28
+ return join(getSoulprintDir(), 'logs');
29
+ }
30
+
31
+ export function getVenvDir() {
32
+ return join(getSoulprintDir(), 'venv');
33
+ }
34
+
35
+ export function getScriptsDir() {
36
+ return join(getSoulprintDir(), 'scripts');
37
+ }
38
+
39
+ // ── Workspace State ────────────────────────────────────
40
+
41
+ export function isWorkspaceInitialized() {
42
+ const dir = getSoulprintDir();
43
+ return existsSync(dir) && existsSync(join(dir, 'workspace.yaml'));
44
+ }
45
+
46
+ export function ensureWorkspace() {
47
+ return isWorkspaceInitialized();
48
+ }
49
+
50
+ export function readWorkspaceConfig() {
51
+ const configPath = join(getSoulprintDir(), 'workspace.yaml');
52
+ if (!existsSync(configPath)) return null;
53
+ try {
54
+ return parseYaml(readFileSync(configPath, 'utf-8'));
55
+ } catch {
56
+ return null;
57
+ }
58
+ }
59
+
60
+ export function writeWorkspaceConfig(config) {
61
+ const configPath = join(getSoulprintDir(), 'workspace.yaml');
62
+ writeFileSync(configPath, stringifyYaml(config, { lineWidth: 0 }));
63
+ }
64
+
65
+ // ── Dataset Helpers ────────────────────────────────────
66
+
67
+ export function getLocalDatasetDir(slug, version) {
68
+ return join(getDatasetsDir(), slug, `v${version}`);
69
+ }
70
+
71
+ export function isDatasetDownloaded(slug, version) {
72
+ const dir = getLocalDatasetDir(slug, version);
73
+ return existsSync(dir) && existsSync(join(dir, 'manifest.json'));
74
+ }
75
+
76
+ export function listLocalDatasets() {
77
+ const dir = getDatasetsDir();
78
+ if (!existsSync(dir)) return [];
79
+ return readdirSync(dir, { withFileTypes: true })
80
+ .filter(e => e.isDirectory())
81
+ .map(e => e.name);
82
+ }
83
+
84
+ // ── Python Environment ─────────────────────────────────
85
+
86
+ export function getPythonPath() {
87
+ const venvPython = join(getVenvDir(), 'bin', 'python');
88
+ if (existsSync(venvPython)) return venvPython;
89
+ return null;
90
+ }
91
+
92
+ export function isPythonAvailable() {
93
+ try {
94
+ execSync('python3 --version', { stdio: 'pipe' });
95
+ return true;
96
+ } catch {
97
+ return false;
98
+ }
99
+ }
100
+
101
+ export function getPythonVersion() {
102
+ try {
103
+ return execSync('python3 --version', { stdio: 'pipe' }).toString().trim();
104
+ } catch {
105
+ return null;
106
+ }
107
+ }
108
+
109
+ export function detectGPU() {
110
+ try {
111
+ const nvidiaSmi = execSync('nvidia-smi --query-gpu=name,memory.total --format=csv,noheader', { stdio: 'pipe' }).toString().trim();
112
+ if (nvidiaSmi) {
113
+ const [name, vram] = nvidiaSmi.split(', ');
114
+ return { type: 'cuda', name, vram };
115
+ }
116
+ } catch { /* no CUDA */ }
117
+
118
+ try {
119
+ const sysctl = execSync('sysctl -n machdep.cpu.brand_string', { stdio: 'pipe' }).toString().trim();
120
+ if (sysctl.includes('Apple')) {
121
+ const mem = execSync('sysctl -n hw.memsize', { stdio: 'pipe' }).toString().trim();
122
+ const memGB = Math.round(parseInt(mem) / 1073741824);
123
+ return { type: 'mps', name: sysctl, vram: `${memGB}GB unified` };
124
+ }
125
+ } catch { /* not Apple Silicon */ }
126
+
127
+ return { type: 'cpu', name: 'CPU only', vram: null };
128
+ }
129
+
130
+ export function getMachineInfo() {
131
+ const gpu = detectGPU();
132
+ return {
133
+ gpu: gpu.name,
134
+ gpuType: gpu.type,
135
+ vram: gpu.vram,
136
+ os: `${process.platform} ${process.arch}`,
137
+ python: getPythonVersion(),
138
+ nodeVersion: process.version,
139
+ };
140
+ }
141
+
142
+ // ── Training Job Local State ───────────────────────────
143
+
144
+ export function getJobOutputDir(jobId) {
145
+ return join(getModelsDir(), jobId);
146
+ }
147
+
148
+ export function getJobLogFile(jobId) {
149
+ const dir = getLogsDir();
150
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
151
+ return join(dir, `${jobId}.jsonl`);
152
+ }
153
+
154
+ export function appendJobLog(jobId, entry) {
155
+ const logFile = getJobLogFile(jobId);
156
+ const line = JSON.stringify({ ...entry, ts: new Date().toISOString() }) + '\n';
157
+ writeFileSync(logFile, line, { flag: 'a' });
158
+ }