@myvillage/cli 1.73.0 → 1.74.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 +1 -1
- package/src/commands/dataset.js +51 -0
- package/src/index.js +23 -0
- package/src/utils/dataset-workspace.js +87 -0
package/package.json
CHANGED
package/src/commands/dataset.js
CHANGED
|
@@ -19,6 +19,8 @@ import {
|
|
|
19
19
|
ingestAsset,
|
|
20
20
|
readRows,
|
|
21
21
|
freezeVersion,
|
|
22
|
+
deleteRow,
|
|
23
|
+
updateRow,
|
|
22
24
|
} from '../utils/dataset-workspace.js';
|
|
23
25
|
|
|
24
26
|
// ── create ─────────────────────────────────────────────
|
|
@@ -176,6 +178,55 @@ export async function datasetFreezeCommand(slug, options) {
|
|
|
176
178
|
}
|
|
177
179
|
}
|
|
178
180
|
|
|
181
|
+
// ── delete a row ───────────────────────────────────────
|
|
182
|
+
|
|
183
|
+
export async function datasetRowDeleteCommand(options) {
|
|
184
|
+
try {
|
|
185
|
+
const slug = options.dataset;
|
|
186
|
+
if (!slug) throw new Error('--dataset <slug> is required.');
|
|
187
|
+
if (!options.id) throw new Error('--id <rowId> is required.');
|
|
188
|
+
const result = deleteRow(slug, options.id);
|
|
189
|
+
if (options.json) {
|
|
190
|
+
console.log(JSON.stringify(result, null, 2));
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
success(`Deleted row ${options.id} from ${slug}`);
|
|
194
|
+
info(` total: ${result.stats.rowCount} (train ${result.stats.bySplit.train} / val ${result.stats.bySplit.val})`);
|
|
195
|
+
} catch (e) {
|
|
196
|
+
error(e.message);
|
|
197
|
+
process.exitCode = 1;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ── update a row ───────────────────────────────────────
|
|
202
|
+
|
|
203
|
+
export async function datasetRowUpdateCommand(options) {
|
|
204
|
+
try {
|
|
205
|
+
const slug = options.dataset;
|
|
206
|
+
if (!slug) throw new Error('--dataset <slug> is required.');
|
|
207
|
+
if (!options.id) throw new Error('--id <rowId> is required.');
|
|
208
|
+
const updates = {};
|
|
209
|
+
if (options.features !== undefined) updates.features = parseFeatures(options.features);
|
|
210
|
+
else if (options.text !== undefined) updates.features = options.text;
|
|
211
|
+
if (options.label !== undefined) updates.label = options.label;
|
|
212
|
+
if (options.target !== undefined) updates.target = Number(options.target);
|
|
213
|
+
if (options.split !== undefined) updates.split = options.split;
|
|
214
|
+
if (Object.keys(updates).length === 0) {
|
|
215
|
+
throw new Error('Nothing to update. Pass --text/--features, --label, --target, or --split.');
|
|
216
|
+
}
|
|
217
|
+
const result = updateRow(slug, options.id, updates);
|
|
218
|
+
if (options.json) {
|
|
219
|
+
console.log(JSON.stringify(result, null, 2));
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
success(`Updated row ${options.id} in ${slug}`);
|
|
223
|
+
info(` total: ${result.stats.rowCount} (train ${result.stats.bySplit.train} / val ${result.stats.bySplit.val})`);
|
|
224
|
+
} catch (e) {
|
|
225
|
+
error(e.message);
|
|
226
|
+
process.exitCode = 1;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
179
230
|
// ── helpers ────────────────────────────────────────────
|
|
180
231
|
|
|
181
232
|
function readRowsFromFile(path) {
|
package/src/index.js
CHANGED
|
@@ -124,6 +124,8 @@ import {
|
|
|
124
124
|
datasetAppendCommand,
|
|
125
125
|
datasetShowCommand,
|
|
126
126
|
datasetFreezeCommand,
|
|
127
|
+
datasetRowDeleteCommand,
|
|
128
|
+
datasetRowUpdateCommand,
|
|
127
129
|
} from './commands/dataset.js';
|
|
128
130
|
import {
|
|
129
131
|
modelTrainCommand,
|
|
@@ -900,6 +902,27 @@ export function run() {
|
|
|
900
902
|
.option('--json', 'Output raw JSON')
|
|
901
903
|
.action(datasetFreezeCommand);
|
|
902
904
|
|
|
905
|
+
datasetCmd
|
|
906
|
+
.command('rm-row')
|
|
907
|
+
.description('Delete a row from the working set (by id)')
|
|
908
|
+
.requiredOption('--dataset <slug>', 'Target dataset slug')
|
|
909
|
+
.requiredOption('--id <rowId>', 'Row id to delete')
|
|
910
|
+
.option('--json', 'Output raw JSON')
|
|
911
|
+
.action(datasetRowDeleteCommand);
|
|
912
|
+
|
|
913
|
+
datasetCmd
|
|
914
|
+
.command('update-row')
|
|
915
|
+
.description('Update a working-set row (by id)')
|
|
916
|
+
.requiredOption('--dataset <slug>', 'Target dataset slug')
|
|
917
|
+
.requiredOption('--id <rowId>', 'Row id to update')
|
|
918
|
+
.option('--text <text>', 'New text features')
|
|
919
|
+
.option('--features <value>', 'New features (string, or numeric vector for tabular)')
|
|
920
|
+
.option('--label <label>', 'New class label (classification)')
|
|
921
|
+
.option('--target <number>', 'New numeric target (regression)')
|
|
922
|
+
.option('--split <split>', 'train | val')
|
|
923
|
+
.option('--json', 'Output raw JSON')
|
|
924
|
+
.action(datasetRowUpdateCommand);
|
|
925
|
+
|
|
903
926
|
// ── Studio Lab: model training (local, TF.js CPU engine) ───────────────
|
|
904
927
|
const modelCmd = program
|
|
905
928
|
.command('model')
|
|
@@ -308,6 +308,93 @@ export function readRows(slug, { version, split, limit } = {}) {
|
|
|
308
308
|
return out;
|
|
309
309
|
}
|
|
310
310
|
|
|
311
|
+
// ── Edit / delete working-set rows ─────────────────────
|
|
312
|
+
//
|
|
313
|
+
// Only the working set (current.jsonl) is mutable; frozen versions (v*.jsonl)
|
|
314
|
+
// are immutable snapshots and are never touched here. Both ops rewrite the
|
|
315
|
+
// whole working-set file and recompute manifest stats from the result, so the
|
|
316
|
+
// manifest can never drift from the rows on disk.
|
|
317
|
+
|
|
318
|
+
/** Recompute manifest stats (rowCount / bySplit / byLabel) from a row array. */
|
|
319
|
+
function recomputeStats(rows) {
|
|
320
|
+
const stats = emptyStats();
|
|
321
|
+
for (const row of rows) {
|
|
322
|
+
stats.rowCount += 1;
|
|
323
|
+
if (stats.bySplit[row.split] != null) stats.bySplit[row.split] += 1;
|
|
324
|
+
if (row.label != null) {
|
|
325
|
+
stats.byLabel[row.label] = (stats.byLabel[row.label] || 0) + 1;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return stats;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** Overwrite the working set with `rows` and refresh the manifest's stats. */
|
|
332
|
+
function writeWorkingSet(slug, manifest, rows) {
|
|
333
|
+
const body = rows.map((r) => JSON.stringify(r)).join('\n');
|
|
334
|
+
writeFileSync(getCurrentDataFile(slug), rows.length ? body + '\n' : '');
|
|
335
|
+
manifest.stats = recomputeStats(rows);
|
|
336
|
+
return writeManifest(slug, manifest);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Delete one working-set row by id. Throws if the id isn't in current.jsonl
|
|
341
|
+
* (e.g. it only exists in a frozen version, which can't be edited).
|
|
342
|
+
* @returns {{datasetId:string, deleted:string, stats:object}}
|
|
343
|
+
*/
|
|
344
|
+
export function deleteRow(slug, rowId) {
|
|
345
|
+
const manifest = readManifest(slug);
|
|
346
|
+
if (!manifest) throw new Error(`Dataset "${slug}" not found.`);
|
|
347
|
+
if (!rowId) throw new Error('A row id is required.');
|
|
348
|
+
const rows = readRows(slug);
|
|
349
|
+
const idx = rows.findIndex((r) => r.id === rowId);
|
|
350
|
+
if (idx === -1) {
|
|
351
|
+
throw new Error(`Row "${rowId}" not found in the working set of "${slug}".`);
|
|
352
|
+
}
|
|
353
|
+
const [removed] = rows.splice(idx, 1);
|
|
354
|
+
writeWorkingSet(slug, manifest, rows);
|
|
355
|
+
return { datasetId: slug, deleted: removed.id, stats: manifest.stats };
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Update one working-set row by id. `updates` is a partial
|
|
360
|
+
* {features, label, target, split}; the merged row is re-validated against the
|
|
361
|
+
* dataset's modality/task (same rules as append). The row id is preserved and
|
|
362
|
+
* provenance.editedAt is stamped. Image/audio asset refs can't be edited here —
|
|
363
|
+
* delete the row and re-add the asset instead.
|
|
364
|
+
* @returns {{datasetId:string, updated:string, stats:object}}
|
|
365
|
+
*/
|
|
366
|
+
export function updateRow(slug, rowId, updates = {}) {
|
|
367
|
+
const manifest = readManifest(slug);
|
|
368
|
+
if (!manifest) throw new Error(`Dataset "${slug}" not found.`);
|
|
369
|
+
if (!rowId) throw new Error('A row id is required.');
|
|
370
|
+
const rows = readRows(slug);
|
|
371
|
+
const idx = rows.findIndex((r) => r.id === rowId);
|
|
372
|
+
if (idx === -1) {
|
|
373
|
+
throw new Error(`Row "${rowId}" not found in the working set of "${slug}".`);
|
|
374
|
+
}
|
|
375
|
+
const existing = rows[idx];
|
|
376
|
+
if ((manifest.modality === 'image' || manifest.modality === 'audio') && updates.features !== undefined) {
|
|
377
|
+
throw new Error(`Can't edit the asset of an ${manifest.modality} row — delete it and re-add.`);
|
|
378
|
+
}
|
|
379
|
+
const merged = normalizeRow(
|
|
380
|
+
{
|
|
381
|
+
features: updates.features !== undefined ? updates.features : existing.features,
|
|
382
|
+
split: updates.split !== undefined ? updates.split : existing.split,
|
|
383
|
+
label: updates.label !== undefined ? updates.label : existing.label,
|
|
384
|
+
target: updates.target !== undefined ? updates.target : existing.target,
|
|
385
|
+
},
|
|
386
|
+
manifest,
|
|
387
|
+
);
|
|
388
|
+
merged.id = existing.id;
|
|
389
|
+
merged.provenance = { ...(existing.provenance || {}), editedAt: nowISO() };
|
|
390
|
+
if (merged.label != null && !manifest.labels.includes(merged.label)) {
|
|
391
|
+
manifest.labels.push(merged.label);
|
|
392
|
+
}
|
|
393
|
+
rows[idx] = merged;
|
|
394
|
+
writeWorkingSet(slug, manifest, rows);
|
|
395
|
+
return { datasetId: slug, updated: merged.id, stats: manifest.stats };
|
|
396
|
+
}
|
|
397
|
+
|
|
311
398
|
// ── Freeze a version ───────────────────────────────────
|
|
312
399
|
|
|
313
400
|
/**
|