@wji.ai/cli 0.7.0 → 0.8.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.
Files changed (49) hide show
  1. package/README.md +4 -1
  2. package/dist/index.js +7162 -76
  3. package/package.json +6 -9
  4. package/skill/short-drama/SKILL.md +22 -10
  5. package/skill/short-drama/reference/bgm-sfx.md +124 -0
  6. package/skill/short-drama/reference/canvas-recipes.md +19 -2
  7. package/skill/short-drama/reference/film-mixing-guideline.md +447 -0
  8. package/skill/short-drama/reference/mix-over-video.md +89 -0
  9. package/skill/short-drama/reference/operations-cli.md +1 -1
  10. package/skill/short-drama/reference/schema.md +4 -2
  11. package/skill/short-drama/reference/stage-characters.md +2 -2
  12. package/skill/short-drama/reference/stage-storyboard.md +29 -6
  13. package/skill/short-drama/reference/workspace-layout.md +2 -2
  14. package/skill/short-drama/scripts/selftest.mjs +54 -9
  15. package/skill/short-drama/scripts/short-drama.mjs +150 -12
  16. package/dist/commands/agent.js +0 -294
  17. package/dist/commands/asset.js +0 -241
  18. package/dist/commands/auth.js +0 -216
  19. package/dist/commands/backreel-enquiry.js +0 -171
  20. package/dist/commands/backreel-episode.js +0 -243
  21. package/dist/commands/backreel-fund.js +0 -299
  22. package/dist/commands/backreel.js +0 -443
  23. package/dist/commands/canvas.js +0 -1706
  24. package/dist/commands/class.js +0 -395
  25. package/dist/commands/config.js +0 -113
  26. package/dist/commands/crew.js +0 -205
  27. package/dist/commands/fsnode.js +0 -288
  28. package/dist/commands/gen.js +0 -356
  29. package/dist/commands/model.js +0 -136
  30. package/dist/commands/npc-album.js +0 -201
  31. package/dist/commands/npc-live.js +0 -316
  32. package/dist/commands/npc.js +0 -437
  33. package/dist/commands/org.js +0 -85
  34. package/dist/commands/preset.js +0 -176
  35. package/dist/commands/profile.js +0 -129
  36. package/dist/commands/skill.js +0 -272
  37. package/dist/commands/workspace.js +0 -470
  38. package/dist/core/archive.js +0 -95
  39. package/dist/core/backreel.js +0 -23
  40. package/dist/core/client.js +0 -269
  41. package/dist/core/config.js +0 -95
  42. package/dist/core/crew.js +0 -546
  43. package/dist/core/image.js +0 -106
  44. package/dist/core/members.js +0 -54
  45. package/dist/core/npc.js +0 -108
  46. package/dist/core/output.js +0 -174
  47. package/dist/core/prompt.js +0 -118
  48. package/dist/core/vod.js +0 -149
  49. package/dist/core/zip.js +0 -87
@@ -1,205 +0,0 @@
1
- import { Command } from 'commander';
2
- import { mkdirSync, writeFileSync } from 'node:fs';
3
- import { dirname, resolve } from 'node:path';
4
- import { confirm } from '../core/prompt.js';
5
- import { color, detail, info, isJsonMode, printJson, render, success, table, warn } from '../core/output.js';
6
- import { CREW_REGISTRY, TARGETS, applyOps, describeOp, fetchPack, findOpenclaw, grokZip, loadPackFromDir, pathState, planClaude, planCodex, planOpenclaw, readInstalled, relOrAbs, uninstallPaths, validatePack, writeInstalled, } from '../core/crew.js';
7
- /**
8
- * `wji crew` — install a crew pack (skills + agent seats) into the hosts that
9
- * run them: Claude Code, OpenAI Codex, OpenClaw, and the Grok bot platform
10
- * (as an import zip). One pack, one command per host.
11
- */
12
- function parseTargets(value) {
13
- if (!value || value === 'all')
14
- return ['claude', 'codex', 'openclaw', 'grok'];
15
- const out = [];
16
- for (const raw of value.split(',').map((s) => s.trim().toLowerCase()).filter(Boolean)) {
17
- if (!TARGETS.includes(raw))
18
- throw new Error(`Unknown target "${raw}". Use: ${TARGETS.join(', ')} or all`);
19
- out.push(raw);
20
- }
21
- return out;
22
- }
23
- function requireValid(pack) {
24
- const errors = validatePack(pack);
25
- if (errors.length)
26
- throw new Error(`Pack ${pack.crew.name} is not valid:\n - ${errors.join('\n - ')}`);
27
- }
28
- export function crewCommand(_globals) {
29
- const crew = new Command('crew').description('Install crew packs (skills + agent seats) into Claude Code, Codex, OpenClaw or a Grok bot export');
30
- crew
31
- .command('install [name]')
32
- .description(`Install a crew for one or more hosts (known crews: ${Object.keys(CREW_REGISTRY).join(', ')})`)
33
- .option('--for <targets>', 'comma-separated: claude, codex, openclaw, grok, or all', 'all')
34
- .option('--from <source>', 'a local pack folder, a .zip / .tar.gz, or an http(s) URL instead of the registry')
35
- .option('--ref <ref>', 'git branch or tag when fetching from the registry')
36
- .option('--project <dir>', 'install into this project (.claude/, .agents/, .codex/, openclaw/) instead of your home directory')
37
- .option('--link', 'symlink skills and Claude agents to a local --from folder instead of copying')
38
- .option('--out <zip>', 'where to write the Grok export zip (default ./<crew>-export.zip)')
39
- .option('--no-register', 'openclaw: only write the workspaces and config fragment; do not run `openclaw agents add`')
40
- .option('--dry-run', 'print what would be written and stop')
41
- .option('-y, --yes', 'skip the confirmation')
42
- .action(async (name, opts) => {
43
- const targets = parseTargets(opts.for);
44
- const pack = await fetchPack(name || 'short-drama', { from: opts.from, ref: opts.ref });
45
- requireValid(pack);
46
- if (opts.link && (!opts.from || /^https?:/.test(opts.from) || !pack.root.startsWith(resolve(opts.from)))) {
47
- throw new Error('--link needs a local --from <folder>');
48
- }
49
- const io = { project: opts.project, link: !!opts.link, out: opts.out };
50
- const plans = {};
51
- for (const t of targets) {
52
- if (t === 'claude')
53
- plans.claude = planClaude(pack, io);
54
- if (t === 'codex')
55
- plans.codex = planCodex(pack, io);
56
- if (t === 'openclaw')
57
- plans.openclaw = planOpenclaw(pack, io, { register: opts.register !== false });
58
- }
59
- // codex and openclaw share ~/.agents/skills — write each skill once
60
- if (plans.codex && plans.openclaw) {
61
- const seen = new Set(plans.codex.filter((o) => o.kind !== 'exec').map((o) => o.path));
62
- plans.openclaw = plans.openclaw.filter((o) => o.kind === 'exec' || !seen.has(o.path));
63
- }
64
- const grokOut = targets.includes('grok') ? resolve(opts.out || `${pack.crew.name}-export.zip`) : undefined;
65
- info(`${color.bold(pack.crew.title || pack.crew.name)} — ${Object.keys(pack.agents).length} seat(s), ${pack.skills.size} skill(s) from ${pack.source}${pack.ref ? ` @ ${pack.ref}` : ''}`);
66
- for (const t of targets) {
67
- info('');
68
- info(color.bold(`[${t}]`));
69
- if (t === 'grok')
70
- info(` zip ${relOrAbs(grokOut)}`);
71
- for (const op of plans[t] || [])
72
- info(` ${describeOp(op).replace(/^(\w+)/, (m) => color.dim(m))}`);
73
- }
74
- if (opts.dryRun)
75
- return info(color.dim('\n--dry-run: nothing written'));
76
- info('');
77
- if (!(await confirm('Write these?', opts.yes)))
78
- return info('Aborted');
79
- const installed = readInstalled();
80
- const record = installed[pack.crew.name] || { source: pack.source, ref: pack.ref, installedAt: '', targets: {} };
81
- record.source = pack.source;
82
- record.ref = pack.ref;
83
- record.installedAt = new Date().toISOString();
84
- const summary = {};
85
- for (const t of targets) {
86
- info(color.bold(`[${t}]`));
87
- if (t === 'grok') {
88
- const { zip, entries } = grokZip(pack);
89
- mkdirSync(dirname(grokOut), { recursive: true });
90
- writeFileSync(grokOut, zip);
91
- info(` ✓ ${entries} entries → ${relOrAbs(grokOut)}`);
92
- record.targets.grok = { paths: [grokOut], notes: [] };
93
- summary.grok = { zip: grokOut, entries };
94
- continue;
95
- }
96
- const previous = record.targets[t]?.paths || [];
97
- const result = applyOps(plans[t] || [], info);
98
- // paths from an earlier install of this crew that this run did not touch are stale — drop them
99
- const stale = previous.filter((p) => !result.paths.includes(p));
100
- if (stale.length)
101
- uninstallPaths(stale);
102
- record.targets[t] = result;
103
- summary[t] = { written: result.paths.length, notes: result.notes };
104
- }
105
- installed[pack.crew.name] = record;
106
- writeInstalled(installed);
107
- if (targets.includes('openclaw')) {
108
- const frag = (record.targets.openclaw?.paths || []).find((p) => p.endsWith('openclaw.agents.json5'));
109
- warn(`openclaw: merge ${frag} into ~/.openclaw/openclaw.json (skills allowlists, tools, subagents, channel bindings) — the CLI never rewrites that file.`);
110
- if (!findOpenclaw())
111
- info(color.dim(' openclaw binary not found; the agents were not registered — run `openclaw agents add <seat> --workspace <dir>` for each seat.'));
112
- }
113
- if (targets.includes('codex')) {
114
- info(color.dim('codex: the wji-operator seat needs network — set [sandbox_workspace_write] network_access = true in ~/.codex/config.toml.'));
115
- }
116
- if (isJsonMode())
117
- return printJson({ crew: pack.crew.name, targets: summary });
118
- success(`Installed ${pack.crew.name} for ${targets.join(', ')}`);
119
- });
120
- crew
121
- .command('status [name]')
122
- .description('What `wji crew install` wrote, per host, and whether it is still there')
123
- .action((name) => {
124
- const installed = readInstalled();
125
- const names = name ? [name] : Object.keys(installed);
126
- const rows = [];
127
- for (const n of names) {
128
- const rec = installed[n];
129
- if (!rec) {
130
- warn(`${n}: not installed by wji crew`);
131
- continue;
132
- }
133
- for (const [target, data] of Object.entries(rec.targets)) {
134
- for (const p of data?.paths || [])
135
- rows.push({ crew: n, target, path: p, state: pathState(p) });
136
- }
137
- }
138
- render({ installed, rows }, () => {
139
- if (!rows.length)
140
- return info('Nothing installed by `wji crew`. Try `wji crew install short-drama --for claude`.');
141
- for (const n of names) {
142
- const rec = installed[n];
143
- if (!rec)
144
- continue;
145
- detail([
146
- ['crew', n],
147
- ['source', `${rec.source}${rec.ref ? ` @ ${rec.ref}` : ''}`],
148
- ['installed', rec.installedAt],
149
- ]);
150
- }
151
- info('');
152
- table(rows, [
153
- { header: 'target', value: (r) => r.target },
154
- { header: '', value: (r) => (r.state === 'missing' ? color.red('✗') : r.state === 'link' ? color.dim('↗') : color.green('✓')) },
155
- { header: 'path', value: (r) => relOrAbs(r.path) },
156
- ]);
157
- });
158
- });
159
- crew
160
- .command('uninstall <name>')
161
- .description('Remove what `wji crew install` wrote for a crew (only the recorded paths)')
162
- .option('--for <targets>', 'comma-separated targets, default all recorded', 'all')
163
- .option('-y, --yes', 'skip the confirmation')
164
- .action(async (name, opts) => {
165
- const installed = readInstalled();
166
- const rec = installed[name];
167
- if (!rec)
168
- throw new Error(`${name} was not installed by wji crew`);
169
- const targets = parseTargets(opts.for).filter((t) => rec.targets[t]);
170
- const paths = targets.flatMap((t) => rec.targets[t]?.paths || []);
171
- if (!paths.length)
172
- return info('Nothing to remove');
173
- for (const p of paths)
174
- info(` rm ${relOrAbs(p)}`);
175
- if (!(await confirm(`Remove ${paths.length} path(s)?`, opts.yes)))
176
- return info('Aborted');
177
- uninstallPaths(paths);
178
- for (const t of targets)
179
- delete rec.targets[t];
180
- if (!Object.keys(rec.targets).length)
181
- delete installed[name];
182
- writeInstalled(installed);
183
- if (targets.includes('openclaw'))
184
- warn('openclaw: remove the agents.entries / bindings you merged into ~/.openclaw/openclaw.json, and `openclaw agents delete <seat>` for each seat.');
185
- success(`Removed ${paths.length} path(s)`);
186
- });
187
- crew
188
- .command('check <dir>')
189
- .description('Validate a local crew pack (crew.json, agents/*.md, skills/*/SKILL.md, links)')
190
- .action((dir) => {
191
- const pack = loadPackFromDir(dir);
192
- const errors = validatePack(pack);
193
- render({ crew: pack.crew.name, agents: Object.keys(pack.agents), skills: [...pack.skills.keys()], errors }, () => {
194
- if (errors.length) {
195
- for (const e of errors)
196
- info(` ✗ ${e}`);
197
- throw new Error(`${errors.length} problem(s) in ${dir}`);
198
- }
199
- success(`${pack.crew.name}: ${Object.keys(pack.agents).length} seat(s), ${pack.skills.size} skill(s), all links resolve`);
200
- });
201
- if (errors.length)
202
- process.exitCode = 1;
203
- });
204
- return crew;
205
- }
@@ -1,288 +0,0 @@
1
- import { Command } from 'commander';
2
- import { WjiClient } from '../core/client.js';
3
- import { confirm, parseKeyValues, readJson, readValue } from '../core/prompt.js';
4
- import { color, detail, detailLevel, info, relativeTime, render, success, table, truncate } from '../core/output.js';
5
- import { printTree } from './workspace.js';
6
- /** Nested plain objects merge; arrays and scalars are replaced. */
7
- function deepMerge(base, patch) {
8
- const out = { ...base };
9
- for (const [key, value] of Object.entries(patch)) {
10
- const isObject = (v) => v && typeof v === 'object' && !Array.isArray(v);
11
- out[key] = isObject(value) && isObject(out[key]) ? deepMerge(out[key], value) : value;
12
- }
13
- return out;
14
- }
15
- export function fsCommand(globals) {
16
- const fs = new Command('fs')
17
- .aliases(['fsnode'])
18
- .description('Browse and edit the fsnode tree (ideas, datasets, files)');
19
- fs.command('root [key]')
20
- .description('Get a root node (defaults to your personal drive; pass a workspace id for its root)')
21
- .option('-d, --depth <n>', 'expand this many levels')
22
- .action(async (key, opts) => {
23
- const client = new WjiClient(globals());
24
- const path = key ? `/fsnode/root/${key}` : '/fsnode/root';
25
- const node = await client.get(path, {
26
- depth: opts.depth ? Number(opts.depth) : undefined,
27
- detail: detailLevel(),
28
- });
29
- render(node, (n) => (opts.depth ? printTree(n) : printNode(n)));
30
- });
31
- fs.command('ls <id>')
32
- .description('List child nodes')
33
- .option('--limit <n>', 'page size')
34
- .option('--scope <scope>', 'server-side scope filter')
35
- .action(async (id, opts) => {
36
- const client = new WjiClient(globals());
37
- const result = await client.get(`/fsnode/list/${id}/children`, {
38
- limit: opts.limit ? Number(opts.limit) : undefined,
39
- scope: opts.scope,
40
- detail: detailLevel(),
41
- });
42
- const rows = Array.isArray(result) ? result : result?.data || [];
43
- render(result, () => table(rows, [
44
- { header: 'id', value: (n) => n.id },
45
- { header: 'type', value: (n) => (n.type === 'folder' ? color.blue('dir') : 'file') },
46
- { header: 'name', value: (n) => truncate(n.name, 32) },
47
- { header: 'nickname', value: (n) => truncate(n.nickname, 24) },
48
- { header: 'mimetype', value: (n) => n.mimetype },
49
- { header: 'updated', value: (n) => relativeTime(n.updatedAt) },
50
- ]));
51
- });
52
- fs.command('tree <id>')
53
- .description('Print the subtree')
54
- .option('-d, --depth <n>', 'levels to expand')
55
- .action(async (id, opts) => {
56
- const client = new WjiClient(globals());
57
- const node = await client.get(`/fsnode/list/${id}/tree`, {
58
- depth: opts.depth ? Number(opts.depth) : undefined,
59
- detail: detailLevel(),
60
- });
61
- render(node, (n) => printTree(n));
62
- });
63
- fs.command('get <id>')
64
- .description('Show one node (comma-separate ids for a batch; --detail full for the whole content)')
65
- .option('--scope <scope>', 'server-side scope filter')
66
- .action(async (id, opts) => {
67
- const client = new WjiClient(globals());
68
- const node = await client.get(`/fsnode/list/${id}`, { scope: opts.scope, detail: detailLevel() });
69
- render(node, (n) => (Array.isArray(n) ? n.forEach(printNode) : printNode(n)));
70
- });
71
- fs.command('path <id>')
72
- .description('Print the ancestor chain from root to this node')
73
- .action(async (id) => {
74
- const client = new WjiClient(globals());
75
- const nodes = await client.get(`/fsnode/list/${id}/path`);
76
- render(nodes, (rows) => {
77
- process.stdout.write(rows.map((n) => n.nickname || n.name).join(' / ') + '\n');
78
- info(color.dim(rows.map((n) => n.id).join(' / ')));
79
- });
80
- });
81
- fs.command('mkdir <parent> <name>')
82
- .description('Create a folder')
83
- .option('--nickname <text>', 'display name')
84
- .option('--content <json>', 'content JSON, @file or - for stdin')
85
- .action(async (parent, name, opts) => {
86
- const client = new WjiClient(globals());
87
- const created = await client.post('/fsnode/create', {
88
- parent,
89
- name,
90
- type: 'folder',
91
- nickname: opts.nickname,
92
- content: await readJson(opts.content),
93
- });
94
- render(created, (n) => success(`Created folder ${n.name} (${n.id})`));
95
- });
96
- fs.command('touch <parent> <name>')
97
- .description('Create a file node')
98
- .option('--mimetype <type>', 'e.g. workspace/dataset')
99
- .option('--nickname <text>', 'display name')
100
- .option('--asset <assetId>', 'attach an asset as the file')
101
- .option('--content <json>', 'content JSON, @file or - for stdin')
102
- .action(async (parent, name, opts) => {
103
- const client = new WjiClient(globals());
104
- const created = await client.post('/fsnode/create', {
105
- parent,
106
- name,
107
- type: 'file',
108
- mimetype: opts.mimetype,
109
- nickname: opts.nickname,
110
- file: opts.asset,
111
- content: await readJson(opts.content),
112
- });
113
- render(created, (n) => success(`Created file ${n.name} (${n.id})`));
114
- });
115
- fs.command('set <id>')
116
- .description('Update a node')
117
- .option('-n, --name <name>', 'new name')
118
- .option('--nickname <text>', 'new display name')
119
- .option('--content <json>', 'replace content JSON (@file / - supported)')
120
- .option('--tags <tag...>', 'replace tags')
121
- .option('--images <assetId...>', 'replace the image asset list')
122
- .option('--icon <assetId>', 'set the icon asset')
123
- .option('--asset <assetId>', 'set the attached file asset (use "null" to clear)')
124
- .option('--sort <n>', 'sort order')
125
- .option('--set <key=value...>', 'arbitrary top-level field, dotted keys allowed (content.* would REPLACE content — use --merge)')
126
- .option('--merge <key=value...>', 'set fields inside content, merged with the current content; values accept @file')
127
- .option('--add-images <assetId...>', 'append to the image asset list instead of replacing it')
128
- .action(async (id, opts) => {
129
- const client = new WjiClient(globals());
130
- const body = {
131
- name: opts.name,
132
- nickname: opts.nickname,
133
- content: await readJson(opts.content),
134
- tags: opts.tags,
135
- images: opts.images,
136
- icon: opts.icon,
137
- file: opts.asset === 'null' ? null : opts.asset,
138
- sort: opts.sort != null ? Number(opts.sort) : undefined,
139
- ...parseKeyValues(opts.set),
140
- };
141
- if (opts.merge?.length || opts.addImages?.length) {
142
- // PATCH replaces `content` and `images` whole, so read first and merge.
143
- const node = await client.getFresh(`/fsnode/list/${id}`);
144
- if (opts.merge?.length) {
145
- const pairs = [];
146
- for (const pair of opts.merge) {
147
- const index = pair.indexOf('=');
148
- if (index === -1)
149
- throw new Error(`Expected key=value, got "${pair}"`);
150
- pairs.push(`${pair.slice(0, index)}=${await readValue(pair.slice(index + 1))}`);
151
- }
152
- body.content = deepMerge(body.content ?? node.content ?? {}, parseKeyValues(pairs));
153
- }
154
- if (opts.addImages?.length) {
155
- const current = (opts.images ?? node.images ?? []).map((it) => (typeof it === 'string' ? it : it.id));
156
- body.images = [...current, ...opts.addImages.filter((it) => !current.includes(it))];
157
- }
158
- }
159
- const updated = await client.patch(`/fsnode/list/${id}`, body);
160
- render(updated, (n) => success(`Updated ${n?.id || id}`));
161
- });
162
- fs.command('describe <id> <text>')
163
- .description('Set content.description (accepts @file or - for stdin)')
164
- .action(async (id, text) => {
165
- const client = new WjiClient(globals());
166
- const node = await client.getFresh(`/fsnode/list/${id}`);
167
- const description = await readValue(text);
168
- const updated = await client.patch(`/fsnode/list/${id}`, {
169
- content: { ...(node.content || {}), description },
170
- });
171
- render(updated, () => success(`Updated description of ${id}`));
172
- });
173
- fs.command('rm <id>')
174
- .description('Delete a node and its subtree (soft delete)')
175
- .option('-y, --yes', 'skip confirmation')
176
- .action(async (id, opts) => {
177
- const client = new WjiClient(globals());
178
- const node = await client.get(`/fsnode/list/${id}`).catch(() => ({ name: id }));
179
- if (!(await confirm(`Delete "${node.name}" (${id}) and everything under it?`, opts.yes))) {
180
- return info('Aborted');
181
- }
182
- const result = await client.delete(`/fsnode/list/${id}`);
183
- render(result, () => success(`Deleted ${id}`));
184
- });
185
- fs.command('sort <parent> <ids...>')
186
- .description('Reorder children of a folder')
187
- .action(async (parent, ids) => {
188
- const client = new WjiClient(globals());
189
- const result = await client.patch(`/fsnode/list/${parent}/children/sort`, { ids });
190
- render(result, () => success(`Reordered ${ids.length} children`));
191
- });
192
- fs.command('batch <json>')
193
- .description('Batch update nodes: [{id, name?, content?, sort?, file?}] (@file / - supported)')
194
- .action(async (json) => {
195
- const client = new WjiClient(globals());
196
- const items = await readJson(json);
197
- const result = await client.patch('/fsnode/batch', {
198
- items: Array.isArray(items) ? items : items.items,
199
- });
200
- render(result, () => success('Batch update applied'));
201
- });
202
- fs.command('used-space')
203
- .description('Show storage used by your nodes')
204
- .action(async () => {
205
- const client = new WjiClient(globals());
206
- const usage = await client.get('/fsnode/drive/used-space');
207
- render(usage, (u) => typeof u === 'object' ? detail(Object.entries(u)) : process.stdout.write(`${u}\n`));
208
- });
209
- return fs;
210
- }
211
- /** Dataset nodes are fsnodes with a `workspace/dataset` mimetype. */
212
- export function datasetCommand(globals) {
213
- const dataset = new Command('dataset').description('Dataset nodes (ideas: character, location, prop, …)');
214
- dataset
215
- .command('new <parent> <name>')
216
- .description('Create a dataset node under a folder')
217
- .option('-t, --type <type>', 'outline | screenplay | character | location | prop | outfit | scene | shot | other', 'other')
218
- .option('-d, --description <text>', 'description (@file / - supported)')
219
- .option('--images <assetId...>', 'attach image assets')
220
- .option('--nickname <text>', 'display name')
221
- .action(async (parent, name, opts) => {
222
- const client = new WjiClient(globals());
223
- const created = await client.post('/fsnode/create', {
224
- parent,
225
- name,
226
- type: 'file',
227
- mimetype: 'workspace/dataset',
228
- nickname: opts.nickname,
229
- images: opts.images,
230
- content: {
231
- type: opts.type,
232
- description: opts.description ? await readValue(opts.description) : undefined,
233
- },
234
- });
235
- render(created, (n) => success(`Created dataset ${n.name} (${n.id})`));
236
- });
237
- dataset
238
- .command('list <folderId>')
239
- .description('List dataset nodes in a folder')
240
- .action(async (folderId) => {
241
- const client = new WjiClient(globals());
242
- const result = await client.get(`/fsnode/list/${folderId}/children`, { detail: detailLevel() });
243
- const rows = (Array.isArray(result) ? result : result?.data || []).filter((n) => n.mimetype === 'workspace/dataset' || n.type === 'file');
244
- render(rows, () => table(rows, [
245
- { header: 'id', value: (n) => n.id },
246
- { header: 'name', value: (n) => truncate(n.name, 28) },
247
- { header: 'kind', value: (n) => n.content?.type },
248
- { header: 'images', value: (n) => (n.images || []).length },
249
- { header: 'description', value: (n) => truncate(n.content?.description, 40) },
250
- ]));
251
- });
252
- dataset
253
- .command('get <key>')
254
- .description('Read a vector-store dataset record by key')
255
- .action(async (key) => {
256
- const client = new WjiClient(globals());
257
- const result = await client.get(`/dataset/list/${key}`);
258
- render(result, (d) => (d ? detail(Object.entries(d)) : info('Not found')));
259
- });
260
- dataset
261
- .command('rm <key>')
262
- .description('Delete a vector-store dataset record by key')
263
- .option('-y, --yes', 'skip confirmation')
264
- .action(async (key, opts) => {
265
- if (!(await confirm(`Delete dataset record ${key}?`, opts.yes)))
266
- return info('Aborted');
267
- const client = new WjiClient(globals());
268
- const result = await client.delete(`/dataset/list/${key}`);
269
- render(result, () => success(`Deleted ${key}`));
270
- });
271
- return dataset;
272
- }
273
- function printNode(n) {
274
- detail([
275
- ['id', n.id],
276
- ['name', n.name],
277
- ['nickname', n.nickname],
278
- ['type', n.type],
279
- ['mimetype', n.mimetype],
280
- ['parent', n.parent?.id || n.parent],
281
- ['workspace', n.workspace?.id || n.workspace],
282
- ['tags', (n.tags || []).join(', ')],
283
- ['images', (n.images || []).map((a) => a?.id || a).join(', ')],
284
- ['file', n.file?.id || n.file],
285
- ['content', n.content],
286
- ['updatedAt', n.updatedAt],
287
- ]);
288
- }