@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.
- package/README.md +4 -1
- package/dist/index.js +7162 -76
- package/package.json +6 -9
- package/skill/short-drama/SKILL.md +22 -10
- package/skill/short-drama/reference/bgm-sfx.md +124 -0
- package/skill/short-drama/reference/canvas-recipes.md +19 -2
- package/skill/short-drama/reference/film-mixing-guideline.md +447 -0
- package/skill/short-drama/reference/mix-over-video.md +89 -0
- package/skill/short-drama/reference/operations-cli.md +1 -1
- package/skill/short-drama/reference/schema.md +4 -2
- package/skill/short-drama/reference/stage-characters.md +2 -2
- package/skill/short-drama/reference/stage-storyboard.md +29 -6
- package/skill/short-drama/reference/workspace-layout.md +2 -2
- package/skill/short-drama/scripts/selftest.mjs +54 -9
- package/skill/short-drama/scripts/short-drama.mjs +150 -12
- package/dist/commands/agent.js +0 -294
- package/dist/commands/asset.js +0 -241
- package/dist/commands/auth.js +0 -216
- package/dist/commands/backreel-enquiry.js +0 -171
- package/dist/commands/backreel-episode.js +0 -243
- package/dist/commands/backreel-fund.js +0 -299
- package/dist/commands/backreel.js +0 -443
- package/dist/commands/canvas.js +0 -1706
- package/dist/commands/class.js +0 -395
- package/dist/commands/config.js +0 -113
- package/dist/commands/crew.js +0 -205
- package/dist/commands/fsnode.js +0 -288
- package/dist/commands/gen.js +0 -356
- package/dist/commands/model.js +0 -136
- package/dist/commands/npc-album.js +0 -201
- package/dist/commands/npc-live.js +0 -316
- package/dist/commands/npc.js +0 -437
- package/dist/commands/org.js +0 -85
- package/dist/commands/preset.js +0 -176
- package/dist/commands/profile.js +0 -129
- package/dist/commands/skill.js +0 -272
- package/dist/commands/workspace.js +0 -470
- package/dist/core/archive.js +0 -95
- package/dist/core/backreel.js +0 -23
- package/dist/core/client.js +0 -269
- package/dist/core/config.js +0 -95
- package/dist/core/crew.js +0 -546
- package/dist/core/image.js +0 -106
- package/dist/core/members.js +0 -54
- package/dist/core/npc.js +0 -108
- package/dist/core/output.js +0 -174
- package/dist/core/prompt.js +0 -118
- package/dist/core/vod.js +0 -149
- package/dist/core/zip.js +0 -87
package/dist/commands/agent.js
DELETED
|
@@ -1,294 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import { WjiClient } from '../core/client.js';
|
|
3
|
-
import { confirm, parseKeyValues, readValue } from '../core/prompt.js';
|
|
4
|
-
import { color, detail, info, relativeTime, render, success, table, truncate } from '../core/output.js';
|
|
5
|
-
/** Parse a skill ref token: `system:<key>` or `user:<id>` (bare = user id). */
|
|
6
|
-
function parseSkillRef(token) {
|
|
7
|
-
const [kindRaw, rest] = token.includes(':') ? token.split(/:(.+)/) : ['user', token];
|
|
8
|
-
const kind = kindRaw === 'system' ? 'system' : 'user';
|
|
9
|
-
if (!rest)
|
|
10
|
-
throw new Error(`Invalid skill ref "${token}" — use system:<key> or user:<id>`);
|
|
11
|
-
return kind === 'system' ? { kind, key: rest } : { kind, id: rest };
|
|
12
|
-
}
|
|
13
|
-
function skillRefLabel(ref) {
|
|
14
|
-
if (!ref?.kind)
|
|
15
|
-
return String(ref);
|
|
16
|
-
return ref.kind === 'system' ? `system:${ref.key}` : `user:${ref.id}`;
|
|
17
|
-
}
|
|
18
|
-
/** Backend DEFAULT_MAX_STEPS / MAX_STEPS_LIMIT (src/ai/_/constant.ts). */
|
|
19
|
-
const DEFAULT_MAX_STEPS = 40;
|
|
20
|
-
const MAX_STEPS_LIMIT = 200;
|
|
21
|
-
/** `--max-steps`: an integer in range, or "default" to clear back to the site default. */
|
|
22
|
-
function parseMaxSteps(value) {
|
|
23
|
-
if (value === undefined)
|
|
24
|
-
return undefined;
|
|
25
|
-
if (['default', 'none', 'null'].includes(value.trim().toLowerCase()))
|
|
26
|
-
return null;
|
|
27
|
-
const n = Number(value);
|
|
28
|
-
if (!Number.isInteger(n) || n < 1 || n > MAX_STEPS_LIMIT) {
|
|
29
|
-
throw new Error(`--max-steps must be an integer from 1 to ${MAX_STEPS_LIMIT} (or "default")`);
|
|
30
|
-
}
|
|
31
|
-
return n;
|
|
32
|
-
}
|
|
33
|
-
export function agentCommand(globals) {
|
|
34
|
-
const agent = new Command('agent').description('Manage AI agents');
|
|
35
|
-
agent
|
|
36
|
-
.command('list', { isDefault: true })
|
|
37
|
-
.description('Browse the public agent marketplace')
|
|
38
|
-
.option('-n, --name <name>', 'exact agent name')
|
|
39
|
-
.option('-c, --category <name>', 'exact category')
|
|
40
|
-
.option('-t, --tag <tag>', 'agents carrying this tag')
|
|
41
|
-
.option('--include-unlisted', 'also show unlisted agents')
|
|
42
|
-
.option('--page <n>', 'page number', '1')
|
|
43
|
-
.option('--limit <n>', 'page size', '20')
|
|
44
|
-
.action(async (opts) => {
|
|
45
|
-
const client = new WjiClient(globals());
|
|
46
|
-
const result = await client.get('/ai/agent/search', {
|
|
47
|
-
name: opts.name,
|
|
48
|
-
category: opts.category,
|
|
49
|
-
tags: opts.tag,
|
|
50
|
-
includeUnlisted: opts.includeUnlisted ? 'true' : undefined,
|
|
51
|
-
page: Number(opts.page),
|
|
52
|
-
limit: Number(opts.limit),
|
|
53
|
-
});
|
|
54
|
-
render(result, (r) => printAgentTable(r));
|
|
55
|
-
});
|
|
56
|
-
agent
|
|
57
|
-
.command('mine')
|
|
58
|
-
.description('List agents you created')
|
|
59
|
-
.option('-n, --name <name>', 'exact agent name')
|
|
60
|
-
.option('-c, --category <name>', 'exact category')
|
|
61
|
-
.option('--visibility <v>', 'public | private | unlist')
|
|
62
|
-
.option('--page <n>', 'page number', '1')
|
|
63
|
-
.option('--limit <n>', 'page size', '20')
|
|
64
|
-
.action(async (opts) => {
|
|
65
|
-
const client = new WjiClient(globals());
|
|
66
|
-
const result = await client.get('/ai/agent/search/my', {
|
|
67
|
-
name: opts.name,
|
|
68
|
-
category: opts.category,
|
|
69
|
-
visibility: opts.visibility,
|
|
70
|
-
page: Number(opts.page),
|
|
71
|
-
limit: Number(opts.limit),
|
|
72
|
-
});
|
|
73
|
-
render(result, (r) => printAgentTable(r));
|
|
74
|
-
});
|
|
75
|
-
agent
|
|
76
|
-
.command('get <id>')
|
|
77
|
-
.description('Show an agent (private agents: creator only; instructions only when you own it)')
|
|
78
|
-
.action(async (id) => {
|
|
79
|
-
const client = new WjiClient(globals());
|
|
80
|
-
const a = await client.get(`/ai/agent/list/${id}`);
|
|
81
|
-
render(a, () => detail([
|
|
82
|
-
['id', a.id],
|
|
83
|
-
['name', a.name],
|
|
84
|
-
['description', a.description],
|
|
85
|
-
['model', a.model],
|
|
86
|
-
['visibility', a.visibility],
|
|
87
|
-
['category', a.category],
|
|
88
|
-
['tags', (a.tags || []).join(', ')],
|
|
89
|
-
['greeting', a.greeting],
|
|
90
|
-
['maxSteps', a.maxSteps ?? color.dim(`default (${DEFAULT_MAX_STEPS})`)],
|
|
91
|
-
['creator', a.creator?.name || a.creator?.id || a.creator],
|
|
92
|
-
['organization', a.organization?.id || a.organization || 'personal'],
|
|
93
|
-
['skills', (a.skills || []).map(skillRefLabel).join(', ')],
|
|
94
|
-
['instructions', a.instructions],
|
|
95
|
-
]));
|
|
96
|
-
});
|
|
97
|
-
agent
|
|
98
|
-
.command('create <name>')
|
|
99
|
-
.description('Create an agent (needs the agent-create permission on your account)')
|
|
100
|
-
.requiredOption('-d, --description <text>', 'short description')
|
|
101
|
-
.option('-i, --instructions <text>', 'system prompt (@file / - supported)')
|
|
102
|
-
.option('-m, --model <name>', 'backing model')
|
|
103
|
-
.option('--visibility <v>', 'private | unlist | public', 'private')
|
|
104
|
-
.option('-c, --category <name>', 'category')
|
|
105
|
-
.option('-t, --tag <tag...>', 'tags')
|
|
106
|
-
.option('--greeting <text>', 'opening message')
|
|
107
|
-
.option('--skill <ref...>', 'attach skills: system:<key> or user:<skillId>')
|
|
108
|
-
.option('--max-steps <n>', `model calls allowed per chat turn, 1-${MAX_STEPS_LIMIT} (default ${DEFAULT_MAX_STEPS})`)
|
|
109
|
-
.option('--set <key=value...>', 'arbitrary field (admin-only fields are dropped by the server)')
|
|
110
|
-
.action(async (name, opts) => {
|
|
111
|
-
const client = new WjiClient(globals());
|
|
112
|
-
const created = await client.post('/ai/agent/create', {
|
|
113
|
-
name,
|
|
114
|
-
description: opts.description,
|
|
115
|
-
instructions: opts.instructions ? await readValue(opts.instructions) : undefined,
|
|
116
|
-
model: opts.model,
|
|
117
|
-
visibility: opts.visibility,
|
|
118
|
-
category: opts.category,
|
|
119
|
-
tags: opts.tag,
|
|
120
|
-
greeting: opts.greeting,
|
|
121
|
-
skills: opts.skill?.length ? opts.skill.map(parseSkillRef) : undefined,
|
|
122
|
-
maxSteps: parseMaxSteps(opts.maxSteps),
|
|
123
|
-
...parseKeyValues(opts.set),
|
|
124
|
-
});
|
|
125
|
-
render(created, (a) => success(`Created agent ${a.name} (${a.id})`));
|
|
126
|
-
});
|
|
127
|
-
agent
|
|
128
|
-
.command('update <id>')
|
|
129
|
-
.description('Update an agent (creator only)')
|
|
130
|
-
.option('-n, --name <name>', 'new name')
|
|
131
|
-
.option('-d, --description <text>', 'new description')
|
|
132
|
-
.option('-i, --instructions <text>', 'new system prompt (@file / - supported)')
|
|
133
|
-
.option('-m, --model <name>', 'new model')
|
|
134
|
-
.option('--visibility <v>', 'private | unlist | public')
|
|
135
|
-
.option('-c, --category <name>', 'category')
|
|
136
|
-
.option('-t, --tag <tag...>', 'replace tags')
|
|
137
|
-
.option('--greeting <text>', 'opening message')
|
|
138
|
-
.option('--max-steps <n>', `model calls allowed per chat turn, 1-${MAX_STEPS_LIMIT}; "default" clears it`)
|
|
139
|
-
.option('--set <key=value...>', 'arbitrary field (admin-only fields are dropped by the server)')
|
|
140
|
-
.action(async (id, opts) => {
|
|
141
|
-
const client = new WjiClient(globals());
|
|
142
|
-
const updated = await client.put(`/ai/agent/list/${id}`, {
|
|
143
|
-
name: opts.name,
|
|
144
|
-
description: opts.description,
|
|
145
|
-
instructions: opts.instructions ? await readValue(opts.instructions) : undefined,
|
|
146
|
-
model: opts.model,
|
|
147
|
-
visibility: opts.visibility,
|
|
148
|
-
category: opts.category,
|
|
149
|
-
tags: opts.tag,
|
|
150
|
-
greeting: opts.greeting,
|
|
151
|
-
maxSteps: parseMaxSteps(opts.maxSteps),
|
|
152
|
-
...parseKeyValues(opts.set),
|
|
153
|
-
});
|
|
154
|
-
render(updated, () => success(`Updated ${id}`));
|
|
155
|
-
});
|
|
156
|
-
agent
|
|
157
|
-
.command('rm <id>')
|
|
158
|
-
.description('Delete an agent')
|
|
159
|
-
.option('-y, --yes', 'skip confirmation')
|
|
160
|
-
.action(async (id, opts) => {
|
|
161
|
-
const client = new WjiClient(globals());
|
|
162
|
-
const a = await client.get(`/ai/agent/list/${id}`).catch(() => ({ name: id }));
|
|
163
|
-
if (!(await confirm(`Delete agent "${a.name}" (${id})?`, opts.yes)))
|
|
164
|
-
return info('Aborted');
|
|
165
|
-
const result = await client.delete(`/ai/agent/list/${id}`);
|
|
166
|
-
render(result, () => success(`Deleted ${id}`));
|
|
167
|
-
});
|
|
168
|
-
agent
|
|
169
|
-
.command('files <id>')
|
|
170
|
-
.description('List files attached to an agent (creator only)')
|
|
171
|
-
.action(async (id) => {
|
|
172
|
-
const client = new WjiClient(globals());
|
|
173
|
-
const files = await client.get(`/ai/agent/list/${id}/files`);
|
|
174
|
-
render(files, (rows) => table(rows || [], [
|
|
175
|
-
{ header: 'id', value: (f) => f.id },
|
|
176
|
-
{ header: 'name', value: (f) => f.name },
|
|
177
|
-
{ header: 'type', value: (f) => f.type },
|
|
178
|
-
]));
|
|
179
|
-
});
|
|
180
|
-
agent
|
|
181
|
-
.command('categories')
|
|
182
|
-
.description('List agent categories')
|
|
183
|
-
.option('--mine', 'only categories used by your agents')
|
|
184
|
-
.action(async (opts) => {
|
|
185
|
-
const client = new WjiClient(globals());
|
|
186
|
-
const list = await client.get(opts.mine ? '/ai/agent/categories/my' : '/ai/agent/categories');
|
|
187
|
-
render(list, (rows) => rows.filter(Boolean).forEach((c) => process.stdout.write(c + '\n')));
|
|
188
|
-
});
|
|
189
|
-
agent
|
|
190
|
-
.command('tags')
|
|
191
|
-
.description('List agent tags')
|
|
192
|
-
.option('--mine', 'only tags used by your agents')
|
|
193
|
-
.action(async (opts) => {
|
|
194
|
-
const client = new WjiClient(globals());
|
|
195
|
-
const list = await client.get(opts.mine ? '/ai/agent/tags/my' : '/ai/agent/tags');
|
|
196
|
-
render(list, (rows) => rows.filter(Boolean).forEach((t) => process.stdout.write(t + '\n')));
|
|
197
|
-
});
|
|
198
|
-
agent
|
|
199
|
-
.command('recommend')
|
|
200
|
-
.description('Show recommended agents')
|
|
201
|
-
.action(async () => {
|
|
202
|
-
const client = new WjiClient(globals());
|
|
203
|
-
const rows = await client.get('/ai/agent/recommend');
|
|
204
|
-
render(rows, (list) => table(list || [], [
|
|
205
|
-
{ header: 'id', value: (a) => a.id },
|
|
206
|
-
{ header: 'name', value: (a) => a.name },
|
|
207
|
-
{ header: 'title', value: (a) => truncate(a.recommendTitle, 40) },
|
|
208
|
-
]));
|
|
209
|
-
});
|
|
210
|
-
agent
|
|
211
|
-
.command('skills <id>')
|
|
212
|
-
.description('Show the skills attached to an agent')
|
|
213
|
-
.action(async (id) => {
|
|
214
|
-
const client = new WjiClient(globals());
|
|
215
|
-
const a = await client.get(`/ai/agent/list/${id}`);
|
|
216
|
-
render(a.skills || [], (rows) => {
|
|
217
|
-
if (!rows.length)
|
|
218
|
-
return info(color.dim('(no skills attached)'));
|
|
219
|
-
table(rows, [
|
|
220
|
-
{ header: 'kind', value: (r) => r.kind },
|
|
221
|
-
{ header: 'ref', value: (r) => (r.kind === 'system' ? r.key : r.id) },
|
|
222
|
-
{ header: 'name', value: (r) => r.name },
|
|
223
|
-
]);
|
|
224
|
-
});
|
|
225
|
-
});
|
|
226
|
-
agent
|
|
227
|
-
.command('skill-add <id> <ref...>')
|
|
228
|
-
.description('Attach skills to an agent (refs: system:<key> or user:<skillId>)')
|
|
229
|
-
.action(async (id, refs) => {
|
|
230
|
-
const client = new WjiClient(globals());
|
|
231
|
-
const a = await client.get(`/ai/agent/list/${id}`);
|
|
232
|
-
const existing = (a.skills || []);
|
|
233
|
-
const seen = new Set(existing.map(skillRefLabel));
|
|
234
|
-
const added = [];
|
|
235
|
-
for (const token of refs) {
|
|
236
|
-
const ref = parseSkillRef(token);
|
|
237
|
-
if (seen.has(skillRefLabel(ref)))
|
|
238
|
-
continue;
|
|
239
|
-
existing.push(ref);
|
|
240
|
-
seen.add(skillRefLabel(ref));
|
|
241
|
-
added.push(skillRefLabel(ref));
|
|
242
|
-
}
|
|
243
|
-
const updated = await client.put(`/ai/agent/list/${id}`, { skills: existing });
|
|
244
|
-
render(updated, () => success(added.length ? `Attached ${added.join(', ')}` : 'No new skills to attach'));
|
|
245
|
-
});
|
|
246
|
-
agent
|
|
247
|
-
.command('skill-remove <id> <ref...>')
|
|
248
|
-
.description('Detach skills from an agent (refs: system:<key> or user:<skillId>)')
|
|
249
|
-
.action(async (id, refs) => {
|
|
250
|
-
const client = new WjiClient(globals());
|
|
251
|
-
const a = await client.get(`/ai/agent/list/${id}`);
|
|
252
|
-
const drop = new Set(refs.map((t) => skillRefLabel(parseSkillRef(t))));
|
|
253
|
-
const kept = (a.skills || []).filter((r) => !drop.has(skillRefLabel(r)));
|
|
254
|
-
const updated = await client.put(`/ai/agent/list/${id}`, { skills: kept });
|
|
255
|
-
render(updated, () => success(`Removed ${refs.length} skill ref(s)`));
|
|
256
|
-
});
|
|
257
|
-
agent
|
|
258
|
-
.command('versions <id>')
|
|
259
|
-
.description('List an agent\'s version history (creator only — snapshots hold instructions)')
|
|
260
|
-
.action(async (id) => {
|
|
261
|
-
const client = new WjiClient(globals());
|
|
262
|
-
const versions = await client.get(`/ai/agent/list/${id}/versions`);
|
|
263
|
-
render(versions, (rows) => table(rows || [], [
|
|
264
|
-
{ header: 'versionId', value: (v) => v.id },
|
|
265
|
-
{ header: 'v', value: (v) => v.version },
|
|
266
|
-
{ header: 'name', value: (v) => truncate(v.snapshot?.name, 24) },
|
|
267
|
-
{ header: 'model', value: (v) => v.snapshot?.model },
|
|
268
|
-
{ header: 'comment', value: (v) => truncate(v.comment, 30) },
|
|
269
|
-
{ header: 'when', value: (v) => relativeTime(v.createdAt) },
|
|
270
|
-
]));
|
|
271
|
-
});
|
|
272
|
-
agent
|
|
273
|
-
.command('restore <id> <versionId>')
|
|
274
|
-
.description('Restore an agent to a previous version (creator only)')
|
|
275
|
-
.action(async (id, versionId) => {
|
|
276
|
-
const client = new WjiClient(globals());
|
|
277
|
-
const result = await client.post(`/ai/agent/list/${id}/versions/${versionId}/restore`);
|
|
278
|
-
render(result, () => success(`Restored ${id} to version ${versionId}`));
|
|
279
|
-
});
|
|
280
|
-
return agent;
|
|
281
|
-
}
|
|
282
|
-
function printAgentTable(result) {
|
|
283
|
-
const rows = result?.data || result || [];
|
|
284
|
-
table(rows, [
|
|
285
|
-
{ header: 'id', value: (a) => a.id },
|
|
286
|
-
{ header: 'name', value: (a) => truncate(a.name, 28) },
|
|
287
|
-
{ header: 'model', value: (a) => a.model },
|
|
288
|
-
{ header: 'visibility', value: (a) => a.visibility },
|
|
289
|
-
{ header: 'category', value: (a) => a.category },
|
|
290
|
-
{ header: 'updated', value: (a) => relativeTime(a.updatedAt) },
|
|
291
|
-
]);
|
|
292
|
-
if (result?.pagination)
|
|
293
|
-
info(color.dim(`${result.pagination.total} total`));
|
|
294
|
-
}
|
package/dist/commands/asset.js
DELETED
|
@@ -1,241 +0,0 @@
|
|
|
1
|
-
import { createWriteStream } from 'node:fs';
|
|
2
|
-
import { stat } from 'node:fs/promises';
|
|
3
|
-
import { basename, join } from 'node:path';
|
|
4
|
-
import { Readable } from 'node:stream';
|
|
5
|
-
import { pipeline } from 'node:stream/promises';
|
|
6
|
-
import { Command } from 'commander';
|
|
7
|
-
import { ASSET_BATCH_MAX_IDS, WjiClient, isObjectId } from '../core/client.js';
|
|
8
|
-
import { confirm } from '../core/prompt.js';
|
|
9
|
-
import { color, detail, info, isJsonMode, printJson, relativeTime, render, success, table, truncate, warn } from '../core/output.js';
|
|
10
|
-
const STATE = { 0: 'pending', 1: 'ready', [-1]: 'cancelled' };
|
|
11
|
-
/** The server rejects the whole batch if any id is malformed — fail fast and say which. */
|
|
12
|
-
function assertAssetIds(ids) {
|
|
13
|
-
const bad = ids.filter((id) => !isObjectId(id));
|
|
14
|
-
if (bad.length)
|
|
15
|
-
throw new Error(`Not asset ids (expected 24-char ObjectIds): ${bad.join(', ')}`);
|
|
16
|
-
}
|
|
17
|
-
function humanSize(bytes) {
|
|
18
|
-
if (bytes == null)
|
|
19
|
-
return null;
|
|
20
|
-
const units = ['B', 'KB', 'MB', 'GB'];
|
|
21
|
-
let value = bytes;
|
|
22
|
-
let unit = 0;
|
|
23
|
-
while (value >= 1024 && unit < units.length - 1) {
|
|
24
|
-
value /= 1024;
|
|
25
|
-
unit++;
|
|
26
|
-
}
|
|
27
|
-
return `${value.toFixed(unit === 0 ? 0 : 1)}${units[unit]}`;
|
|
28
|
-
}
|
|
29
|
-
export function assetCommand(globals) {
|
|
30
|
-
const asset = new Command('asset').description('Upload and manage assets');
|
|
31
|
-
asset
|
|
32
|
-
.command('upload <file...>')
|
|
33
|
-
.description('Upload one or more local files')
|
|
34
|
-
.option('-w, --workspace <id>', 'attach to a workspace')
|
|
35
|
-
.option('--store <id>', 'vector store id')
|
|
36
|
-
.option('--vectorize', 'index the file for retrieval')
|
|
37
|
-
.option('--width <n>', 'source width hint')
|
|
38
|
-
.option('--height <n>', 'source height hint')
|
|
39
|
-
.option('--duration <n>', 'media duration hint in seconds')
|
|
40
|
-
.action(async (files, opts) => {
|
|
41
|
-
const client = new WjiClient(globals());
|
|
42
|
-
const workspace = client.workspace(opts.workspace);
|
|
43
|
-
const uploaded = [];
|
|
44
|
-
for (const file of files) {
|
|
45
|
-
const size = (await stat(file)).size;
|
|
46
|
-
info(color.dim(`Uploading ${basename(file)} (${humanSize(size)})…`));
|
|
47
|
-
const result = await client.upload('/assets/v2/upload', file, {
|
|
48
|
-
workspace,
|
|
49
|
-
store: opts.store,
|
|
50
|
-
vectorize: opts.vectorize ? 'true' : undefined,
|
|
51
|
-
width: opts.width,
|
|
52
|
-
height: opts.height,
|
|
53
|
-
duration: opts.duration,
|
|
54
|
-
});
|
|
55
|
-
uploaded.push(result);
|
|
56
|
-
if (!isJsonMode())
|
|
57
|
-
success(`${basename(file)} → ${result.id}`);
|
|
58
|
-
}
|
|
59
|
-
if (isJsonMode())
|
|
60
|
-
render(files.length === 1 ? uploaded[0] : uploaded, () => { });
|
|
61
|
-
});
|
|
62
|
-
asset
|
|
63
|
-
.command('import <url>')
|
|
64
|
-
.description('Import an asset from a URL')
|
|
65
|
-
.option('-w, --workspace <id>', 'attach to a workspace')
|
|
66
|
-
.option('-n, --name <name>', 'asset name')
|
|
67
|
-
.option('--type <type>', 'asset type override')
|
|
68
|
-
.option('--raw', 'use /assets/import-from-url instead of create-from-url')
|
|
69
|
-
.action(async (url, opts) => {
|
|
70
|
-
const client = new WjiClient(globals());
|
|
71
|
-
const created = opts.raw
|
|
72
|
-
? await client.post('/assets/import-from-url', { url })
|
|
73
|
-
: await client.post('/assets/create-from-url', {
|
|
74
|
-
url,
|
|
75
|
-
workspace: client.workspace(opts.workspace),
|
|
76
|
-
name: opts.name,
|
|
77
|
-
type: opts.type,
|
|
78
|
-
});
|
|
79
|
-
render(created, (a) => success(`Imported ${a.id}`));
|
|
80
|
-
});
|
|
81
|
-
asset
|
|
82
|
-
.command('list')
|
|
83
|
-
.description('Search your assets')
|
|
84
|
-
.option('--store <id>', 'filter by vector store')
|
|
85
|
-
.option('--page <n>', 'page number', '1')
|
|
86
|
-
.option('--limit <n>', 'page size', '20')
|
|
87
|
-
.action(async (opts) => {
|
|
88
|
-
const client = new WjiClient(globals());
|
|
89
|
-
const result = await client.get('/assets/search', {
|
|
90
|
-
store: opts.store,
|
|
91
|
-
page: Number(opts.page),
|
|
92
|
-
limit: Number(opts.limit),
|
|
93
|
-
});
|
|
94
|
-
render(result, (r) => {
|
|
95
|
-
table(r.data || [], [
|
|
96
|
-
{ header: 'id', value: (a) => a.id },
|
|
97
|
-
{ header: 'type', value: (a) => a.type },
|
|
98
|
-
{ header: 'name', value: (a) => truncate(a.name, 30) },
|
|
99
|
-
{ header: 'size', value: (a) => humanSize(a.size) },
|
|
100
|
-
{ header: 'state', value: (a) => STATE[a.status] ?? a.status },
|
|
101
|
-
{ header: 'created', value: (a) => relativeTime(a.createdAt) },
|
|
102
|
-
]);
|
|
103
|
-
info(color.dim(`${r.pagination?.total ?? 0} total`));
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
asset
|
|
107
|
-
.command('get <id>')
|
|
108
|
-
.description('Show one asset')
|
|
109
|
-
.action(async (id) => {
|
|
110
|
-
const client = new WjiClient(globals());
|
|
111
|
-
const a = await client.get(`/assets/list/${id}`);
|
|
112
|
-
render(a, () => printAsset(a));
|
|
113
|
-
});
|
|
114
|
-
asset
|
|
115
|
-
.command('status <ids...>')
|
|
116
|
-
.description(`Check processing state of several assets (sent ${ASSET_BATCH_MAX_IDS} per request)`)
|
|
117
|
-
.action(async (ids) => {
|
|
118
|
-
assertAssetIds(ids);
|
|
119
|
-
const unique = [...new Set(ids)];
|
|
120
|
-
const client = new WjiClient(globals());
|
|
121
|
-
const rows = await client.checkAssetStatus(unique);
|
|
122
|
-
render(rows, (list) => {
|
|
123
|
-
table(list || [], [
|
|
124
|
-
{ header: 'id', value: (a) => a.id },
|
|
125
|
-
{ header: 'state', value: (a) => STATE[a.status] ?? a.status },
|
|
126
|
-
{ header: 'error', value: (a) => truncate(a.error?.message || a.error, 40) },
|
|
127
|
-
{ header: 'url', value: (a) => truncate(a.url, 50) },
|
|
128
|
-
]);
|
|
129
|
-
// The server silently drops ids you may not read instead of erroring.
|
|
130
|
-
const missing = unique.length - (list?.length ?? 0);
|
|
131
|
-
if (missing > 0) {
|
|
132
|
-
warn(`${missing} id(s) not returned: not found, or neither yours nor in a workspace you belong to`);
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
asset
|
|
137
|
-
.command('update <id>')
|
|
138
|
-
.description('Rename or retag an asset')
|
|
139
|
-
.option('-n, --name <name>', 'new name')
|
|
140
|
-
.option('--tags <tag...>', 'replace tags')
|
|
141
|
-
.action(async (id, opts) => {
|
|
142
|
-
const client = new WjiClient(globals());
|
|
143
|
-
const updated = await client.patch(`/assets/list/${id}`, {
|
|
144
|
-
name: opts.name,
|
|
145
|
-
tags: opts.tags,
|
|
146
|
-
});
|
|
147
|
-
render(updated, () => success(`Updated ${id}`));
|
|
148
|
-
});
|
|
149
|
-
asset
|
|
150
|
-
.command('rm <id>')
|
|
151
|
-
.description('Delete an asset')
|
|
152
|
-
.option('-y, --yes', 'skip confirmation')
|
|
153
|
-
.action(async (id, opts) => {
|
|
154
|
-
if (!(await confirm(`Delete asset ${id}?`, opts.yes)))
|
|
155
|
-
return info('Aborted');
|
|
156
|
-
const client = new WjiClient(globals());
|
|
157
|
-
const result = await client.delete(`/assets/list/${id}`);
|
|
158
|
-
render(result, () => success(`Deleted ${id}`));
|
|
159
|
-
});
|
|
160
|
-
asset
|
|
161
|
-
.command('download <ids...>')
|
|
162
|
-
.description('Download assets to disk (a directory keeps each asset name plus its file extension)')
|
|
163
|
-
.option('-o, --out <path>', 'output file (one id) or directory', '.')
|
|
164
|
-
.action(async (ids, opts) => {
|
|
165
|
-
const client = new WjiClient(globals());
|
|
166
|
-
const isDir = await stat(opts.out).then((s) => s.isDirectory()).catch(() => false);
|
|
167
|
-
if (ids.length > 1 && !isDir)
|
|
168
|
-
throw new Error('With several ids, --out must be an existing directory');
|
|
169
|
-
const saved = [];
|
|
170
|
-
for (const id of ids) {
|
|
171
|
-
const a = await client.get(`/assets/list/${id}`);
|
|
172
|
-
if (!a?.url)
|
|
173
|
-
throw new Error(`Asset ${id} has no URL yet (state: ${STATE[a?.status] ?? a?.status})`);
|
|
174
|
-
const res = await fetch(a.url);
|
|
175
|
-
if (!res.ok || !res.body)
|
|
176
|
-
throw new Error(`Download failed: ${res.status} ${res.statusText}`);
|
|
177
|
-
let target = opts.out;
|
|
178
|
-
if (isDir) {
|
|
179
|
-
// Asset names are titles ("boat"), not file names: keep the extension so
|
|
180
|
-
// image viewers and agents can open the file.
|
|
181
|
-
const base = String(a.name || id).replace(/[\\/]/g, '_');
|
|
182
|
-
target = join(opts.out, /\.[a-z0-9]{2,5}$/i.test(base) ? base : `${base}${extensionOf(a.url)}`);
|
|
183
|
-
}
|
|
184
|
-
await pipeline(Readable.fromWeb(res.body), createWriteStream(target));
|
|
185
|
-
saved.push({ id, path: target });
|
|
186
|
-
if (!isJsonMode())
|
|
187
|
-
success(`Saved ${target}`);
|
|
188
|
-
}
|
|
189
|
-
if (isJsonMode())
|
|
190
|
-
printJson(ids.length === 1 ? saved[0] : saved);
|
|
191
|
-
});
|
|
192
|
-
asset
|
|
193
|
-
.command('resolve <url>')
|
|
194
|
-
.description('Find an existing asset by its URL')
|
|
195
|
-
.option('-w, --workspace <id>', 'workspace scope')
|
|
196
|
-
.action(async (url, opts) => {
|
|
197
|
-
const client = new WjiClient(globals());
|
|
198
|
-
const a = await client.get('/assets/resolve-by-url', {
|
|
199
|
-
url,
|
|
200
|
-
workspace: client.workspace(opts.workspace),
|
|
201
|
-
});
|
|
202
|
-
render(a, (found) => (found ? printAsset(found) : info('No matching asset')));
|
|
203
|
-
});
|
|
204
|
-
asset
|
|
205
|
-
.command('summary <id...>')
|
|
206
|
-
.description(`Generate AI summaries for your own assets (at most ${ASSET_BATCH_MAX_IDS} per call)`)
|
|
207
|
-
.action(async (ids) => {
|
|
208
|
-
assertAssetIds(ids);
|
|
209
|
-
// Not batched automatically: every id is a separate vectorize + LLM job.
|
|
210
|
-
if (ids.length > ASSET_BATCH_MAX_IDS) {
|
|
211
|
-
throw new Error(`asset summary takes at most ${ASSET_BATCH_MAX_IDS} ids per call`);
|
|
212
|
-
}
|
|
213
|
-
const client = new WjiClient(globals());
|
|
214
|
-
const result = ids.length === 1
|
|
215
|
-
? await client.post(`/assets/list/${ids[0]}/summary`)
|
|
216
|
-
: await client.post('/assets/summary/batch', { ids });
|
|
217
|
-
render(result, (r) => detail(Object.entries(r || {})));
|
|
218
|
-
});
|
|
219
|
-
return asset;
|
|
220
|
-
}
|
|
221
|
-
function extensionOf(url) {
|
|
222
|
-
const match = /\.([a-z0-9]{2,5})(?:\?|$)/i.exec(url);
|
|
223
|
-
return match ? `.${match[1]}` : '';
|
|
224
|
-
}
|
|
225
|
-
function printAsset(a) {
|
|
226
|
-
detail([
|
|
227
|
-
['id', a.id],
|
|
228
|
-
['name', a.name],
|
|
229
|
-
['type', a.type],
|
|
230
|
-
['mimetype', a.mimetype],
|
|
231
|
-
['state', STATE[a.status] ?? a.status],
|
|
232
|
-
['size', humanSize(a.size)],
|
|
233
|
-
['aspectRatio', a.aspectRatio],
|
|
234
|
-
['duration', a.duration],
|
|
235
|
-
['url', a.url],
|
|
236
|
-
['thumbnail', a.thumbnail],
|
|
237
|
-
['tags', (a.tags || []).join(', ')],
|
|
238
|
-
['error', a.error?.message || a.error],
|
|
239
|
-
['createdAt', a.createdAt],
|
|
240
|
-
]);
|
|
241
|
-
}
|