@myvillage/cli 2.8.2 → 2.10.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myvillage/cli",
3
- "version": "2.8.2",
3
+ "version": "2.10.0",
4
4
  "description": "MyVillageOS CLI for community developers",
5
5
  "type": "module",
6
6
  "bin": {
@@ -22,6 +22,14 @@ export const DATASET_APPEND_DESCRIPTION =
22
22
  'regression provide a numeric `target`. Data stays local; provenance ' +
23
23
  'is recorded automatically.';
24
24
 
25
+ export const DATASET_READ_DESCRIPTION =
26
+ 'Read a LOCAL dataset on this machine: its description, columns, labels, ' +
27
+ 'row counts, and a page of its rows (newest working set by default, or a ' +
28
+ 'frozen version). Filter by split or label and page with offset/limit. ' +
29
+ 'Table rows come back keyed by column name; image and sound rows come back ' +
30
+ 'as absolute file paths (which model_predict accepts). Read-only — use ' +
31
+ 'dataset_append to add rows.';
32
+
25
33
  export const MODEL_PREDICT_DESCRIPTION =
26
34
  'Run a prediction with a LOCAL trained model on this machine. `input` is ' +
27
35
  'text (words models), the values of one table row as a list in column ' +
@@ -38,6 +46,12 @@ export const BUILTIN_TOOL_DEFS = [
38
46
  tier: 'villager',
39
47
  builtin: true,
40
48
  },
49
+ {
50
+ name: 'dataset_read',
51
+ description: DATASET_READ_DESCRIPTION,
52
+ tier: 'villager',
53
+ builtin: true,
54
+ },
41
55
  {
42
56
  name: 'model_predict',
43
57
  description: MODEL_PREDICT_DESCRIPTION,
@@ -45,3 +59,25 @@ export const BUILTIN_TOOL_DEFS = [
45
59
  builtin: true,
46
60
  },
47
61
  ];
62
+
63
+ /** One line telling an agent what a local model is for and what it takes. */
64
+ export function describeLocalModel(m) {
65
+ const f = m.featurization ?? {};
66
+ const modality = f.modality ?? m.modality;
67
+ let takes;
68
+ if (modality === 'image') takes = 'a local image file path (.jpg/.png)';
69
+ else if (modality === 'audio') takes = 'a local .wav file path';
70
+ else if (modality === 'text') takes = 'some writing';
71
+ else if (Array.isArray(f.columns)) {
72
+ takes = 'a table row with ' + f.columns.map((c) => `${JSON.stringify(c.name)} (${c.type})`).join(', ');
73
+ } else if (Array.isArray(m.featureNames) && m.featureNames.length) {
74
+ takes = 'a table row of numbers: ' + m.featureNames.map((n) => JSON.stringify(n)).join(', ');
75
+ } else {
76
+ takes = `a list of ${m.inputDim ?? 'some'} numbers`;
77
+ }
78
+ const gives =
79
+ m.taskType === 'classification' && Array.isArray(m.labels) && m.labels.length
80
+ ? `sorts it into: ${m.labels.join(', ')}`
81
+ : 'gives back a number';
82
+ return `- "${m.slug}" (${m.name}): takes ${takes}; ${gives}.`;
83
+ }
@@ -10,16 +10,63 @@
10
10
  * `myvillage dataset append` command uses — so agents and humans write
11
11
  * identically. Raw data stays local; nothing leaves the machine.
12
12
  *
13
- * Tool contract mirrors @myvillage-studio/dataset-schema DATASET_APPEND_TOOL.
13
+ * `dataset_read` is its read side: it wraps readRows() so agents can look at
14
+ * what's been collected (to answer questions about it, avoid duplicate
15
+ * appends, or feed rows to model_predict). Also local-only.
16
+ *
17
+ * Tool contracts mirror @myvillage-studio/dataset-schema DATASET_APPEND_TOOL
18
+ * and DATASET_READ_TOOL.
14
19
  */
15
20
  import { tool } from 'ai';
16
21
  import { z } from 'zod';
17
- import { appendRows, datasetExists, readManifest, ingestAsset } from '../utils/dataset-workspace.js';
18
- import { DATASET_APPEND_DESCRIPTION, MODEL_PREDICT_DESCRIPTION } from './builtin-tools-registry.js';
22
+ import {
23
+ appendRows,
24
+ datasetExists,
25
+ readManifest,
26
+ readRows,
27
+ ingestAsset,
28
+ getAssetPath,
29
+ listDatasets,
30
+ } from '../utils/dataset-workspace.js';
31
+ import {
32
+ DATASET_APPEND_DESCRIPTION,
33
+ DATASET_READ_DESCRIPTION,
34
+ MODEL_PREDICT_DESCRIPTION,
35
+ } from './builtin-tools-registry.js';
19
36
  import { readToolsYaml } from '../utils/local-agent.js';
20
37
 
21
38
  export { BUILTIN_TOOL_DEFS } from './builtin-tools-registry.js';
22
39
 
40
+ const READ_DEFAULT_LIMIT = 50;
41
+ const READ_MAX_LIMIT = 200;
42
+ // Long text rows are clipped so one page can't blow out the model's context.
43
+ const READ_MAX_TEXT_CHARS = 2000;
44
+
45
+ /** Shape a stored row for the model: readable features, no internal noise. */
46
+ function presentRow(slug, manifest, row) {
47
+ let features = row.features;
48
+ let truncated = false;
49
+ if (features && typeof features === 'object' && !Array.isArray(features) && features.path) {
50
+ // AssetRef → absolute path, the same form model_predict / dataset_append take
51
+ features = getAssetPath(slug, features.path);
52
+ } else if (
53
+ Array.isArray(features) &&
54
+ manifest.featureNames?.length === features.length
55
+ ) {
56
+ features = Object.fromEntries(manifest.featureNames.map((name, i) => [name, features[i]]));
57
+ } else if (typeof features === 'string' && features.length > READ_MAX_TEXT_CHARS) {
58
+ features = features.slice(0, READ_MAX_TEXT_CHARS);
59
+ truncated = true;
60
+ }
61
+ const out = { id: row.id, features, split: row.split };
62
+ if (truncated) out.truncated = true;
63
+ if (row.label != null) out.label = row.label;
64
+ if (row.target != null) out.target = row.target;
65
+ if (row.provenance?.source) out.source = row.provenance.source;
66
+ if (row.provenance?.ts) out.addedAt = row.provenance.ts;
67
+ return out;
68
+ }
69
+
23
70
  /**
24
71
  * Gate built-ins by the SAME allowlist as the MyVillageOS platform server
25
72
  * (`allowed_tools` on whichever tools.yaml server points at a myvillageproject.ai
@@ -128,6 +175,77 @@ export function getBuiltinTools(agentName, config) {
128
175
  },
129
176
  }),
130
177
 
178
+ dataset_read: tool({
179
+ description: DATASET_READ_DESCRIPTION,
180
+ inputSchema: z.object({
181
+ datasetId: z.string().describe('Slug of the local dataset to read'),
182
+ version: z
183
+ .number()
184
+ .int()
185
+ .positive()
186
+ .optional()
187
+ .describe('Frozen version number to read; omit for the current working set'),
188
+ split: z.enum(['train', 'val']).optional().describe('Only rows in this split'),
189
+ label: z.string().optional().describe('Only rows with this class label (classification datasets)'),
190
+ offset: z.number().int().min(0).optional().describe('Rows to skip (default 0)'),
191
+ limit: z
192
+ .number()
193
+ .int()
194
+ .min(0)
195
+ .max(READ_MAX_LIMIT)
196
+ .optional()
197
+ .describe(`Rows to return (default ${READ_DEFAULT_LIMIT}, max ${READ_MAX_LIMIT}; 0 = summary only)`),
198
+ }),
199
+ execute: async ({ datasetId, version, split, label, offset = 0, limit = READ_DEFAULT_LIMIT }) => {
200
+ const manifest = readManifest(datasetId);
201
+ if (!manifest) {
202
+ let available = [];
203
+ try {
204
+ available = listDatasets().map((d) => d.slug);
205
+ } catch { /* store unavailable */ }
206
+ return {
207
+ error:
208
+ `Dataset "${datasetId}" not found locally.` +
209
+ (available.length ? ` Available: ${available.join(', ')}.` : ' No local datasets exist yet.'),
210
+ };
211
+ }
212
+ if (version && !manifest.versions?.some((v) => v.version === version)) {
213
+ const known = (manifest.versions ?? []).map((v) => v.version);
214
+ return {
215
+ error:
216
+ `Dataset "${datasetId}" has no version ${version}.` +
217
+ (known.length ? ` Frozen versions: ${known.join(', ')}.` : ' It has no frozen versions yet.'),
218
+ };
219
+ }
220
+ try {
221
+ let rows = readRows(datasetId, { version, split });
222
+ if (label != null) rows = rows.filter((r) => r.label === label);
223
+ const page = rows.slice(offset, offset + limit);
224
+ return {
225
+ ok: true,
226
+ datasetId: manifest.slug,
227
+ name: manifest.name,
228
+ ...(manifest.description ? { description: manifest.description } : {}),
229
+ modality: manifest.modality,
230
+ taskType: manifest.taskType,
231
+ labels: manifest.labels ?? [],
232
+ ...(manifest.featureNames ? { columns: manifest.featureNames } : {}),
233
+ ...(manifest.featureTypes ? { columnTypes: manifest.featureTypes } : {}),
234
+ stats: manifest.stats,
235
+ versions: (manifest.versions ?? []).map((v) => ({ version: v.version, rowCount: v.rowCount, frozenAt: v.frozenAt })),
236
+ reading: version ? `version ${version}` : 'working set',
237
+ matchingRows: rows.length,
238
+ offset,
239
+ returned: page.length,
240
+ hasMore: offset + page.length < rows.length,
241
+ rows: page.map((r) => presentRow(datasetId, manifest, r)),
242
+ };
243
+ } catch (e) {
244
+ return { error: e.message };
245
+ }
246
+ },
247
+ }),
248
+
131
249
  model_predict: tool({
132
250
  description: MODEL_PREDICT_DESCRIPTION,
133
251
  inputSchema: z.object({
@@ -13,6 +13,7 @@
13
13
 
14
14
  import { listDatasets } from '../utils/dataset-workspace.js';
15
15
  import { listModels } from '../utils/model-workspace.js';
16
+ import { describeLocalModel } from './builtin-tools-registry.js';
16
17
 
17
18
  export async function gatherContext(agentConfig, lastCheckIn, recentActions = []) {
18
19
  const parts = [];
@@ -34,13 +35,14 @@ export async function gatherContext(agentConfig, lastCheckIn, recentActions = []
34
35
  }
35
36
  }
36
37
 
37
- // Local Lab datasets & models, so the agent knows what it can append to /
38
- // predict with (and the exact slugs to pass to dataset_append / model_predict).
38
+ // Local Lab datasets & models, so the agent knows what it can read / append
39
+ // to / predict with (and the exact slugs to pass to dataset_read /
40
+ // dataset_append / model_predict).
39
41
  // Local-only, synchronous file reads; tfjs is NOT loaded by listing models.
40
42
  try {
41
43
  const datasets = listDatasets();
42
44
  if (datasets.length > 0) {
43
- parts.push('\nLocal datasets you can add to with dataset_append (use the slug as datasetId):');
45
+ parts.push('\nLocal datasets you can read with dataset_read or add to with dataset_append (use the slug as datasetId):');
44
46
  for (const d of datasets.slice(0, 25)) {
45
47
  parts.push(`- ${d.slug} — ${d.modality}/${d.taskType}, ${d.stats?.rowCount ?? 0} rows`);
46
48
  }
@@ -51,11 +53,8 @@ export async function gatherContext(agentConfig, lastCheckIn, recentActions = []
51
53
  try {
52
54
  const models = listModels();
53
55
  if (models.length > 0) {
54
- parts.push('\nLocal models you can run with model_predict (use the slug as modelId):');
55
- for (const m of models.slice(0, 25)) {
56
- const labels = m.labels?.length ? ` → ${m.labels.join('/')}` : '';
57
- parts.push(`- ${m.slug} — ${m.modality}/${m.taskType}${labels}`);
58
- }
56
+ parts.push('\nLocal models you can run with model_predict (the quoted slug is the modelId). Use one whenever its job matches what you are doing:');
57
+ for (const m of models.slice(0, 25)) parts.push(describeLocalModel(m));
59
58
  }
60
59
  } catch {
61
60
  /* no local models / store unavailable — skip */
@@ -25,7 +25,7 @@ const TOOL_GROUPS = {
25
25
  villages: ['village_search', 'village_view', 'village_create', 'village_update'],
26
26
  moments: ['moment_create', 'moment_get', 'pulse_create', 'pulse_list'],
27
27
  villagers: ['villager_me', 'villager_search', 'villager_update', 'agent_list'],
28
- meetings: ['recall_send_bot', 'recall_meeting_attendance'],
28
+ meetings: ['recall_send_bot', 'recall_list_sessions', 'recall_meeting_attendance'],
29
29
  };
30
30
 
31
31
  // ─── Agentic App Template (Next.js) ─────────────────────────────────────────
@@ -756,7 +756,7 @@ const TOOL_GROUPS = {
756
756
  villages: ['village_search', 'village_view', 'village_create', 'village_update'],
757
757
  moments: ['moment_create', 'moment_get', 'pulse_create', 'pulse_list'],
758
758
  villagers: ['villager_me', 'villager_search', 'villager_update', 'agent_list'],
759
- meetings: ['recall_send_bot', 'recall_meeting_attendance'],
759
+ meetings: ['recall_send_bot', 'recall_list_sessions', 'recall_meeting_attendance'],
760
760
  };
761
761
 
762
762
  // Tool groups selected during project creation