@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.
@@ -0,0 +1,168 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, unlinkSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { getConfigDir } from './config.js';
4
+ import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
5
+
6
+ // ── Directory Helpers ───────────────────────────────────
7
+
8
+ export function getAgentsDir() {
9
+ const dir = join(getConfigDir(), 'agents');
10
+ if (!existsSync(dir)) {
11
+ mkdirSync(dir, { recursive: true });
12
+ }
13
+ return dir;
14
+ }
15
+
16
+ export function getAgentDir(name) {
17
+ return join(getAgentsDir(), name);
18
+ }
19
+
20
+ export function agentExists(name) {
21
+ const dir = getAgentDir(name);
22
+ return existsSync(dir) && existsSync(join(dir, 'agent.config.yaml'));
23
+ }
24
+
25
+ // ── Config Read/Write ───────────────────────────────────
26
+
27
+ export function readAgentConfig(name) {
28
+ const configPath = join(getAgentDir(name), 'agent.config.yaml');
29
+ if (!existsSync(configPath)) return null;
30
+ try {
31
+ const raw = readFileSync(configPath, 'utf-8');
32
+ return parseYaml(raw);
33
+ } catch {
34
+ return null;
35
+ }
36
+ }
37
+
38
+ export function writeAgentConfig(name, config) {
39
+ const configPath = join(getAgentDir(name), 'agent.config.yaml');
40
+ writeFileSync(configPath, stringifyYaml(config, { lineWidth: 0 }));
41
+ }
42
+
43
+ // ── Tools YAML Read/Write ───────────────────────────────
44
+
45
+ export function readToolsYaml(name) {
46
+ const toolsPath = join(getAgentDir(name), 'tools.yaml');
47
+ if (!existsSync(toolsPath)) return { servers: {} };
48
+ try {
49
+ const raw = readFileSync(toolsPath, 'utf-8');
50
+ return parseYaml(raw) || { servers: {} };
51
+ } catch {
52
+ return { servers: {} };
53
+ }
54
+ }
55
+
56
+ export function writeToolsYaml(name, toolsConfig) {
57
+ const toolsPath = join(getAgentDir(name), 'tools.yaml');
58
+ writeFileSync(toolsPath, stringifyYaml(toolsConfig, { lineWidth: 0 }));
59
+ }
60
+
61
+ // ── Daemon Status ───────────────────────────────────────
62
+
63
+ export function getDaemonInfo(name) {
64
+ const pidFile = join(getAgentDir(name), 'daemon.pid');
65
+ if (!existsSync(pidFile)) return null;
66
+ try {
67
+ return JSON.parse(readFileSync(pidFile, 'utf-8'));
68
+ } catch {
69
+ return null;
70
+ }
71
+ }
72
+
73
+ export function isDaemonRunning(name) {
74
+ const info = getDaemonInfo(name);
75
+ if (!info) return false;
76
+ try {
77
+ process.kill(info.pid, 0);
78
+ return true;
79
+ } catch {
80
+ // Process dead, clean up stale PID file
81
+ cleanupPidFile(name);
82
+ return false;
83
+ }
84
+ }
85
+
86
+ export function cleanupPidFile(name) {
87
+ const pidFile = join(getAgentDir(name), 'daemon.pid');
88
+ try {
89
+ if (existsSync(pidFile)) unlinkSync(pidFile);
90
+ } catch {
91
+ // ignore
92
+ }
93
+ }
94
+
95
+ // ── List Local Agents ───────────────────────────────────
96
+
97
+ export function listLocalAgents() {
98
+ const agentsDir = getAgentsDir();
99
+ if (!existsSync(agentsDir)) return [];
100
+
101
+ const entries = readdirSync(agentsDir, { withFileTypes: true });
102
+ const agents = [];
103
+
104
+ for (const entry of entries) {
105
+ if (!entry.isDirectory()) continue;
106
+ const configPath = join(agentsDir, entry.name, 'agent.config.yaml');
107
+ if (!existsSync(configPath)) continue;
108
+
109
+ try {
110
+ const config = parseYaml(readFileSync(configPath, 'utf-8'));
111
+ const running = isDaemonRunning(entry.name);
112
+ const daemonInfo = getDaemonInfo(entry.name);
113
+
114
+ agents.push({
115
+ name: entry.name,
116
+ config,
117
+ isRunning: running,
118
+ lastHeartbeat: daemonInfo?.lastHeartbeat || null,
119
+ });
120
+ } catch {
121
+ // Skip malformed agent directories
122
+ }
123
+ }
124
+
125
+ return agents;
126
+ }
127
+
128
+ // ── Log Reading ─────────────────────────────────────────
129
+
130
+ export function readAgentLogs(name, options = {}) {
131
+ const logsDir = join(getAgentDir(name), 'logs');
132
+ if (!existsSync(logsDir)) return [];
133
+
134
+ const { since, limit = 50 } = options;
135
+
136
+ // Find log files, sorted newest first
137
+ const files = readdirSync(logsDir)
138
+ .filter(f => f.endsWith('.jsonl'))
139
+ .sort()
140
+ .reverse();
141
+
142
+ const entries = [];
143
+
144
+ for (const file of files) {
145
+ const lines = readFileSync(join(logsDir, file), 'utf-8')
146
+ .split('\n')
147
+ .filter(Boolean);
148
+
149
+ for (const line of lines.reverse()) {
150
+ try {
151
+ const entry = JSON.parse(line);
152
+ if (since && new Date(entry.ts) < new Date(since)) continue;
153
+ entries.push(entry);
154
+ if (entries.length >= limit) return entries.reverse();
155
+ } catch {
156
+ // skip malformed lines
157
+ }
158
+ }
159
+ }
160
+
161
+ return entries.reverse();
162
+ }
163
+
164
+ export function getLogFilePath(name) {
165
+ const logsDir = join(getAgentDir(name), 'logs');
166
+ const today = new Date().toISOString().slice(0, 10);
167
+ return join(logsDir, `${today}.jsonl`);
168
+ }
@@ -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
+ }