@wji.ai/cli 0.5.1 → 0.6.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.
- package/README.md +7 -2
- package/dist/commands/agent.js +28 -8
- package/dist/commands/backreel.js +61 -1
- package/dist/commands/canvas.js +52 -5
- package/dist/commands/fsnode.js +13 -6
- package/dist/commands/gen.js +3 -2
- package/dist/commands/model.js +4 -2
- package/dist/commands/workspace.js +80 -4
- package/dist/core/output.js +29 -0
- package/dist/index.js +11 -2
- package/package.json +1 -1
- package/skill/ads/reference/models.md +5 -0
- package/skill/short-drama/reference/canvas-recipes.md +18 -0
- package/skill/short-drama/reference/operations-cli.md +1 -0
- package/skill/short-drama/reference/operations-wji.md +27 -4
- package/skill/short-drama/reference/stage-storyboard.md +6 -0
- package/skill/wji/SKILL.md +39 -2
- package/skill/wji/reference/commands.md +47 -15
package/README.md
CHANGED
|
@@ -117,8 +117,12 @@ Run `wji <group> --help` for the full option list.
|
|
|
117
117
|
|
|
118
118
|
## Conventions
|
|
119
119
|
|
|
120
|
-
- **`--json`** on any command prints
|
|
121
|
-
|
|
120
|
+
- **`--json`** on any command prints JSON and nothing else, so everything
|
|
121
|
+
composes with `jq`. List and get commands print the **`standard`** level by
|
|
122
|
+
default (ids, names, status, media urls, long text clipped to 200 chars,
|
|
123
|
+
`content` summarised with `truncated: true`). Add **`--detail full`** (or set
|
|
124
|
+
`WJI_DETAIL=full`) for the raw API response, `--detail brief` for id lists.
|
|
125
|
+
Human table output is never affected.
|
|
122
126
|
- **`@file` and `-`** work anywhere a long text or JSON value is expected:
|
|
123
127
|
`-p @prompt.txt`, `--content @node.json`, `-p -` to read stdin.
|
|
124
128
|
- **`--set key=value`** sends arbitrary fields; dotted keys nest
|
|
@@ -244,6 +248,7 @@ Skill refs are `system:<key>` or `user:<skillId>` (a bare id is treated as `user
|
|
|
244
248
|
| Generation submissions | image 30 / video 10 / audio 20 per minute, per user or key |
|
|
245
249
|
| Generation queue | queued + running jobs: FREE 3 / VIP 10 / SVIP 25 → HTTP 429 |
|
|
246
250
|
| Pagination | `--limit` ≤100; `-1` returns at most 500 except on opt-in endpoints |
|
|
251
|
+
| Detail levels | `?detail=brief\|standard\|full` on workspace, fsnode, model and history reads; the API defaults to `full`, the CLI's `--json` to `standard` |
|
|
247
252
|
| Batches | `asset status` is sent 100 ids at a time; `asset summary` ≤100 ids |
|
|
248
253
|
| Skills | zip ≤5MB, ≤200 files, ≤2MB per `.md`, ≤20MB total, 50 per account |
|
|
249
254
|
| Creation caps | 50 workspaces, 3 organizations per account |
|
package/dist/commands/agent.js
CHANGED
|
@@ -15,6 +15,21 @@ function skillRefLabel(ref) {
|
|
|
15
15
|
return String(ref);
|
|
16
16
|
return ref.kind === 'system' ? `system:${ref.key}` : `user:${ref.id}`;
|
|
17
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
|
+
}
|
|
18
33
|
export function agentCommand(globals) {
|
|
19
34
|
const agent = new Command('agent').description('Manage AI agents');
|
|
20
35
|
agent
|
|
@@ -59,7 +74,7 @@ export function agentCommand(globals) {
|
|
|
59
74
|
});
|
|
60
75
|
agent
|
|
61
76
|
.command('get <id>')
|
|
62
|
-
.description('Show an agent (instructions
|
|
77
|
+
.description('Show an agent (private agents: creator only; instructions only when you own it)')
|
|
63
78
|
.action(async (id) => {
|
|
64
79
|
const client = new WjiClient(globals());
|
|
65
80
|
const a = await client.get(`/ai/agent/list/${id}`);
|
|
@@ -72,6 +87,7 @@ export function agentCommand(globals) {
|
|
|
72
87
|
['category', a.category],
|
|
73
88
|
['tags', (a.tags || []).join(', ')],
|
|
74
89
|
['greeting', a.greeting],
|
|
90
|
+
['maxSteps', a.maxSteps ?? color.dim(`default (${DEFAULT_MAX_STEPS})`)],
|
|
75
91
|
['creator', a.creator?.name || a.creator?.id || a.creator],
|
|
76
92
|
['organization', a.organization?.id || a.organization || 'personal'],
|
|
77
93
|
['skills', (a.skills || []).map(skillRefLabel).join(', ')],
|
|
@@ -80,7 +96,7 @@ export function agentCommand(globals) {
|
|
|
80
96
|
});
|
|
81
97
|
agent
|
|
82
98
|
.command('create <name>')
|
|
83
|
-
.description('Create an agent')
|
|
99
|
+
.description('Create an agent (needs the agent-create permission on your account)')
|
|
84
100
|
.requiredOption('-d, --description <text>', 'short description')
|
|
85
101
|
.option('-i, --instructions <text>', 'system prompt (@file / - supported)')
|
|
86
102
|
.option('-m, --model <name>', 'backing model')
|
|
@@ -89,7 +105,8 @@ export function agentCommand(globals) {
|
|
|
89
105
|
.option('-t, --tag <tag...>', 'tags')
|
|
90
106
|
.option('--greeting <text>', 'opening message')
|
|
91
107
|
.option('--skill <ref...>', 'attach skills: system:<key> or user:<skillId>')
|
|
92
|
-
.option('--
|
|
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)')
|
|
93
110
|
.action(async (name, opts) => {
|
|
94
111
|
const client = new WjiClient(globals());
|
|
95
112
|
const created = await client.post('/ai/agent/create', {
|
|
@@ -102,13 +119,14 @@ export function agentCommand(globals) {
|
|
|
102
119
|
tags: opts.tag,
|
|
103
120
|
greeting: opts.greeting,
|
|
104
121
|
skills: opts.skill?.length ? opts.skill.map(parseSkillRef) : undefined,
|
|
122
|
+
maxSteps: parseMaxSteps(opts.maxSteps),
|
|
105
123
|
...parseKeyValues(opts.set),
|
|
106
124
|
});
|
|
107
125
|
render(created, (a) => success(`Created agent ${a.name} (${a.id})`));
|
|
108
126
|
});
|
|
109
127
|
agent
|
|
110
128
|
.command('update <id>')
|
|
111
|
-
.description('Update an agent')
|
|
129
|
+
.description('Update an agent (creator only)')
|
|
112
130
|
.option('-n, --name <name>', 'new name')
|
|
113
131
|
.option('-d, --description <text>', 'new description')
|
|
114
132
|
.option('-i, --instructions <text>', 'new system prompt (@file / - supported)')
|
|
@@ -117,7 +135,8 @@ export function agentCommand(globals) {
|
|
|
117
135
|
.option('-c, --category <name>', 'category')
|
|
118
136
|
.option('-t, --tag <tag...>', 'replace tags')
|
|
119
137
|
.option('--greeting <text>', 'opening message')
|
|
120
|
-
.option('--
|
|
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)')
|
|
121
140
|
.action(async (id, opts) => {
|
|
122
141
|
const client = new WjiClient(globals());
|
|
123
142
|
const updated = await client.put(`/ai/agent/list/${id}`, {
|
|
@@ -129,6 +148,7 @@ export function agentCommand(globals) {
|
|
|
129
148
|
category: opts.category,
|
|
130
149
|
tags: opts.tag,
|
|
131
150
|
greeting: opts.greeting,
|
|
151
|
+
maxSteps: parseMaxSteps(opts.maxSteps),
|
|
132
152
|
...parseKeyValues(opts.set),
|
|
133
153
|
});
|
|
134
154
|
render(updated, () => success(`Updated ${id}`));
|
|
@@ -147,7 +167,7 @@ export function agentCommand(globals) {
|
|
|
147
167
|
});
|
|
148
168
|
agent
|
|
149
169
|
.command('files <id>')
|
|
150
|
-
.description('List files attached to an agent')
|
|
170
|
+
.description('List files attached to an agent (creator only)')
|
|
151
171
|
.action(async (id) => {
|
|
152
172
|
const client = new WjiClient(globals());
|
|
153
173
|
const files = await client.get(`/ai/agent/list/${id}/files`);
|
|
@@ -236,7 +256,7 @@ export function agentCommand(globals) {
|
|
|
236
256
|
});
|
|
237
257
|
agent
|
|
238
258
|
.command('versions <id>')
|
|
239
|
-
.description('List an agent\'s version history')
|
|
259
|
+
.description('List an agent\'s version history (creator only — snapshots hold instructions)')
|
|
240
260
|
.action(async (id) => {
|
|
241
261
|
const client = new WjiClient(globals());
|
|
242
262
|
const versions = await client.get(`/ai/agent/list/${id}/versions`);
|
|
@@ -251,7 +271,7 @@ export function agentCommand(globals) {
|
|
|
251
271
|
});
|
|
252
272
|
agent
|
|
253
273
|
.command('restore <id> <versionId>')
|
|
254
|
-
.description('Restore an agent to a previous version')
|
|
274
|
+
.description('Restore an agent to a previous version (creator only)')
|
|
255
275
|
.action(async (id, versionId) => {
|
|
256
276
|
const client = new WjiClient(globals());
|
|
257
277
|
const result = await client.post(`/ai/agent/list/${id}/versions/${versionId}/restore`);
|
|
@@ -7,6 +7,10 @@ const POSTS = '/backreel/post';
|
|
|
7
7
|
const KINDS = ['showcase', 'crowdfunding'];
|
|
8
8
|
const PROJECT_STATUSES = ['planning', 'pre_production', 'in_production', 'post_production', 'completed'];
|
|
9
9
|
const CREDIT_KINDS = ['creative', 'crew', 'cast'];
|
|
10
|
+
/** Backend LOGLINE_MAX (src/backreel/post/descriptionHtml.ts). */
|
|
11
|
+
const LOGLINE_MAX = 200;
|
|
12
|
+
/** BackreelVerificationKind — what `creators --role` filters on. */
|
|
13
|
+
const VERIFICATION_KINDS = ['investor', 'production_company', 'brand', 'distributor', 'sales_agent'];
|
|
10
14
|
/** Credit fields the create / update DTO accepts; the rest of a read-back credit is dropped. */
|
|
11
15
|
const CREDIT_FIELDS = ['kind', 'name', 'role', 'user', 'photo', 'avatarUrl', 'link', 'source', 'npcId', 'order'];
|
|
12
16
|
export function backreelCommand(globals) {
|
|
@@ -87,6 +91,50 @@ export function backreelCommand(globals) {
|
|
|
87
91
|
const result = await client.delete(`${POSTS}/list/${id}`);
|
|
88
92
|
render(result, () => success(`Deleted "${post.title}"`));
|
|
89
93
|
});
|
|
94
|
+
br.command('creators')
|
|
95
|
+
.description('Browse Backreel creators: editor picks, then ranked by recent likes / favorites')
|
|
96
|
+
.option('-k, --keyword <text>', 'match name or @handle')
|
|
97
|
+
.option('--role <kind>', `only creators verified as: ${VERIFICATION_KINDS.join(' | ')}`)
|
|
98
|
+
.option('--verified', 'only verified creators')
|
|
99
|
+
.option('--no-featured', 'skip the editor-picked creators')
|
|
100
|
+
.option('--page <n>', 'page number', '1')
|
|
101
|
+
.option('--limit <n>', 'page size (max 48)', '20')
|
|
102
|
+
.action(async (opts) => {
|
|
103
|
+
const client = new WjiClient(globals());
|
|
104
|
+
if (opts.role)
|
|
105
|
+
oneOf(opts.role, VERIFICATION_KINDS, '--role');
|
|
106
|
+
const result = await client.get('/backreel/creator/list', {
|
|
107
|
+
keyword: opts.keyword,
|
|
108
|
+
role: opts.role,
|
|
109
|
+
verifiedOnly: opts.verified ? 'true' : undefined,
|
|
110
|
+
withFeatured: opts.featured ? undefined : 'false',
|
|
111
|
+
page: Number(opts.page),
|
|
112
|
+
limit: Number(opts.limit),
|
|
113
|
+
});
|
|
114
|
+
render(result, (r) => {
|
|
115
|
+
const columns = [
|
|
116
|
+
{ header: 'id', value: (c) => c.id },
|
|
117
|
+
{ header: 'name', value: (c) => truncate(c.name, 24) },
|
|
118
|
+
{ header: 'handle', value: (c) => (c.handle ? `@${c.handle}` : '') },
|
|
119
|
+
{ header: 'reels', value: (c) => c.reelCount },
|
|
120
|
+
{ header: 'followers', value: (c) => c.followerCount },
|
|
121
|
+
{ header: 'likes', value: (c) => c.likeCount },
|
|
122
|
+
{ header: 'verified', value: (c) => (c.verifiedKinds || []).join(', ') },
|
|
123
|
+
];
|
|
124
|
+
if (r?.featured?.length) {
|
|
125
|
+
info(color.bold('Featured'));
|
|
126
|
+
table(r.featured, [...columns, { header: 'note', value: (c) => truncate(c.featuredNote, 30) }]);
|
|
127
|
+
process.stdout.write('\n');
|
|
128
|
+
info(color.bold('Hot'));
|
|
129
|
+
}
|
|
130
|
+
table(r?.data || [], columns);
|
|
131
|
+
const p = r?.pagination;
|
|
132
|
+
if (p) {
|
|
133
|
+
const more = p.page * p.limit < p.total ? ` · more: --page ${p.page + 1}` : '';
|
|
134
|
+
info(color.dim(`${p.total} ranked · page ${p.page}${more}`));
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
});
|
|
90
138
|
// ── credits ────────────────────────────────────────────────────────────
|
|
91
139
|
const credit = br.command('credit').aliases(['credits']).description('Cast and crew of a post (max 30)');
|
|
92
140
|
credit
|
|
@@ -176,6 +224,7 @@ export function backreelCommand(globals) {
|
|
|
176
224
|
function postOptions(cmd) {
|
|
177
225
|
return cmd
|
|
178
226
|
.option('--kind <kind>', KINDS.join(' | '))
|
|
227
|
+
.option('--logline <text>', `one-line hook, max ${LOGLINE_MAX} chars; public even when the description is locked ("" clears it)`)
|
|
179
228
|
.option('--category <category>', 'category')
|
|
180
229
|
.option('--project-status <status>', PROJECT_STATUSES.join(' | '))
|
|
181
230
|
.option('--tags <tags>', 'comma-separated tags (replaces the list)')
|
|
@@ -189,6 +238,13 @@ async function postBody(opts) {
|
|
|
189
238
|
const body = { ...((await readJson(opts.body)) || {}) };
|
|
190
239
|
if (opts.kind)
|
|
191
240
|
body.kind = oneOf(opts.kind, KINDS, '--kind');
|
|
241
|
+
if (opts.logline !== undefined) {
|
|
242
|
+
// Same normalisation the server applies: plain text, one line.
|
|
243
|
+
const logline = String(await readValue(opts.logline)).replace(/<[^>]*>/g, '').replace(/\s+/g, ' ').trim();
|
|
244
|
+
if (logline.length > LOGLINE_MAX)
|
|
245
|
+
throw new Error(`--logline is ${logline.length} chars; the limit is ${LOGLINE_MAX}`);
|
|
246
|
+
body.logline = logline;
|
|
247
|
+
}
|
|
192
248
|
if (opts.category)
|
|
193
249
|
body.category = opts.category;
|
|
194
250
|
if (opts.projectStatus)
|
|
@@ -225,6 +281,7 @@ async function updateCredits(client, id, fn) {
|
|
|
225
281
|
throw new Error('A post can have at most 30 credits');
|
|
226
282
|
return client.put(`${POSTS}/list/${id}`, { credits: next });
|
|
227
283
|
}
|
|
284
|
+
const assetLabel = (a) => (a ? a.url || a.id || a : undefined);
|
|
228
285
|
function oneOf(value, allowed, flag) {
|
|
229
286
|
if (!allowed.includes(value))
|
|
230
287
|
throw new Error(`${flag} must be one of ${allowed.join(', ')}`);
|
|
@@ -239,8 +296,11 @@ function printPost(p) {
|
|
|
239
296
|
['project', p.projectStatus],
|
|
240
297
|
['category', p.category],
|
|
241
298
|
['tags', (p.tags || []).join(', ')],
|
|
299
|
+
['logline', p.logline],
|
|
242
300
|
['description', truncate(p.descriptionText || p.description, 120)],
|
|
243
|
-
['cover', p.cover
|
|
301
|
+
['cover', assetLabel(p.cover)],
|
|
302
|
+
['coverPortrait', assetLabel(p.coverPortrait)],
|
|
303
|
+
['trailer', p.trailer ? `${assetLabel(p.trailer)}${p.trailerDurationSec ? ` (${p.trailerDurationSec}s)` : ''}` : undefined],
|
|
244
304
|
['creator', p.creator?.name],
|
|
245
305
|
['likes', p.likeCount],
|
|
246
306
|
['updated', relativeTime(p.updatedAt)],
|
package/dist/commands/canvas.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { WjiClient } from '../core/client.js';
|
|
3
3
|
import { confirm, parseKeyValues, readValue } from '../core/prompt.js';
|
|
4
|
-
import { color, detail, info, isJsonMode, printJson, relativeTime, render, success, table, truncate, warn } from '../core/output.js';
|
|
4
|
+
import { clipText, color, detail, detailLevel, info, isJsonMode, printJson, relativeTime, render, success, table, truncate, warn } from '../core/output.js';
|
|
5
5
|
import { pollHistory, reportHistory, resolveAssets } from './gen.js';
|
|
6
6
|
import { loadMembers, memberLabel, resolveMember } from '../core/members.js';
|
|
7
7
|
import { fetchModels } from './model.js';
|
|
@@ -315,6 +315,53 @@ export function describe(canvas, node) {
|
|
|
315
315
|
outgoing: canvas.edges.filter((e) => e.fromNodeId === node.id).map((e) => e.toNodeId),
|
|
316
316
|
};
|
|
317
317
|
}
|
|
318
|
+
/**
|
|
319
|
+
* The canvas commands always load the full canvas (edits and generation need
|
|
320
|
+
* every field), so --detail is applied here, to the JSON they print. Mirrors
|
|
321
|
+
* the backend's canvas_list_nodes levels.
|
|
322
|
+
*/
|
|
323
|
+
export function trimDescribed(row, level = detailLevel()) {
|
|
324
|
+
if (!level || level === 'full')
|
|
325
|
+
return row;
|
|
326
|
+
if (level === 'brief') {
|
|
327
|
+
return compactRow({
|
|
328
|
+
id: row.id,
|
|
329
|
+
kind: row.kind,
|
|
330
|
+
name: row.name,
|
|
331
|
+
tag: row.tag,
|
|
332
|
+
mode: row.mode,
|
|
333
|
+
status: row.status,
|
|
334
|
+
assetId: row.assetId,
|
|
335
|
+
position: row.position,
|
|
336
|
+
references: row.references.map((r) => ({ nodeId: r.nodeId, label: r.label })),
|
|
337
|
+
outgoing: row.outgoing,
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
const g = row.generate;
|
|
341
|
+
return compactRow({
|
|
342
|
+
...row,
|
|
343
|
+
text: row.text === undefined ? undefined : clipText(row.text) ?? '',
|
|
344
|
+
error: typeof row.error === 'string' ? clipText(row.error) : row.error,
|
|
345
|
+
generate: g
|
|
346
|
+
? compactRow({
|
|
347
|
+
prompt: clipText(g.prompt),
|
|
348
|
+
model: g.model,
|
|
349
|
+
ratio: g.ratio,
|
|
350
|
+
resolution: g.resolution,
|
|
351
|
+
duration: g.duration,
|
|
352
|
+
historyId: g.historyId,
|
|
353
|
+
pendingAssetId: g.pendingAssetId,
|
|
354
|
+
})
|
|
355
|
+
: undefined,
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
function compactRow(value) {
|
|
359
|
+
const out = {};
|
|
360
|
+
for (const key of Object.keys(value))
|
|
361
|
+
if (value[key] !== undefined)
|
|
362
|
+
out[key] = value[key];
|
|
363
|
+
return out;
|
|
364
|
+
}
|
|
318
365
|
// ── models ──────────────────────────────────────────────────────────────────
|
|
319
366
|
/** An audio node is fed by either an `audio` or a `tts` model — the backend tags voice models `tts`. */
|
|
320
367
|
const KIND_FEATURES = { audio: ['audio', 'tts'] };
|
|
@@ -1022,7 +1069,7 @@ export function canvasCommand(globals) {
|
|
|
1022
1069
|
.filter((id) => loaded.byId.has(id))
|
|
1023
1070
|
.map((id) => describe(loaded, loaded.byId.get(id)));
|
|
1024
1071
|
}
|
|
1025
|
-
const out = { ...res, state: { ...state, canvasName }, selected };
|
|
1072
|
+
const out = { ...res, state: { ...state, canvasName }, selected: selected.map((n) => trimDescribed(n)) };
|
|
1026
1073
|
render(out, () => {
|
|
1027
1074
|
const seconds = Math.max(0, Math.round((new Date(state.expiresAt).getTime() - Date.now()) / 1000));
|
|
1028
1075
|
detail([
|
|
@@ -1249,7 +1296,7 @@ export function canvasCommand(globals) {
|
|
|
1249
1296
|
.filter((n) => !nameRe || nameRe.test(n.name))
|
|
1250
1297
|
.map((n) => describe(cv, n))
|
|
1251
1298
|
.filter((n) => (colors ? !!n.tag && colors.has(n.tag.color) : opts.untagged ? !n.tag : true));
|
|
1252
|
-
render({ canvas: { id: cv.id, name: cv.name, workspaceId: cv.workspaceId }, nodes: rows }, () => {
|
|
1299
|
+
render({ canvas: { id: cv.id, name: cv.name, workspaceId: cv.workspaceId }, nodes: rows.map((n) => trimDescribed(n)) }, () => {
|
|
1253
1300
|
info(color.dim(`${cv.name} (${cv.id}) · ${rows.length} node(s) · ${cv.edges.length} edge(s)`));
|
|
1254
1301
|
nodeTable(rows);
|
|
1255
1302
|
});
|
|
@@ -1283,13 +1330,13 @@ export function canvasCommand(globals) {
|
|
|
1283
1330
|
});
|
|
1284
1331
|
canvas
|
|
1285
1332
|
.command('get <nodeId>')
|
|
1286
|
-
.description('One node
|
|
1333
|
+
.description('One node with its references (--detail full for the whole generate / text)')
|
|
1287
1334
|
.option('-c, --canvas <id>', 'canvas id (defaults to the linked canvas)')
|
|
1288
1335
|
.action(async (id, opts) => {
|
|
1289
1336
|
const c = client();
|
|
1290
1337
|
const cv = await loadCanvas(c, await resolveCanvasId(c, opts.canvas));
|
|
1291
1338
|
const n = describe(cv, requireNode(cv, id));
|
|
1292
|
-
render(n, () => printNode(n));
|
|
1339
|
+
render(trimDescribed(n), () => printNode(n));
|
|
1293
1340
|
});
|
|
1294
1341
|
canvas
|
|
1295
1342
|
.command('edges')
|
package/dist/commands/fsnode.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { WjiClient } from '../core/client.js';
|
|
3
3
|
import { confirm, parseKeyValues, readJson, readValue } from '../core/prompt.js';
|
|
4
|
-
import { color, detail, info, relativeTime, render, success, table, truncate } from '../core/output.js';
|
|
4
|
+
import { color, detail, detailLevel, info, relativeTime, render, success, table, truncate } from '../core/output.js';
|
|
5
5
|
import { printTree } from './workspace.js';
|
|
6
6
|
/** Nested plain objects merge; arrays and scalars are replaced. */
|
|
7
7
|
function deepMerge(base, patch) {
|
|
@@ -22,7 +22,10 @@ export function fsCommand(globals) {
|
|
|
22
22
|
.action(async (key, opts) => {
|
|
23
23
|
const client = new WjiClient(globals());
|
|
24
24
|
const path = key ? `/fsnode/root/${key}` : '/fsnode/root';
|
|
25
|
-
const node = await client.get(path,
|
|
25
|
+
const node = await client.get(path, {
|
|
26
|
+
depth: opts.depth ? Number(opts.depth) : undefined,
|
|
27
|
+
detail: detailLevel(),
|
|
28
|
+
});
|
|
26
29
|
render(node, (n) => (opts.depth ? printTree(n) : printNode(n)));
|
|
27
30
|
});
|
|
28
31
|
fs.command('ls <id>')
|
|
@@ -34,6 +37,7 @@ export function fsCommand(globals) {
|
|
|
34
37
|
const result = await client.get(`/fsnode/list/${id}/children`, {
|
|
35
38
|
limit: opts.limit ? Number(opts.limit) : undefined,
|
|
36
39
|
scope: opts.scope,
|
|
40
|
+
detail: detailLevel(),
|
|
37
41
|
});
|
|
38
42
|
const rows = Array.isArray(result) ? result : result?.data || [];
|
|
39
43
|
render(result, () => table(rows, [
|
|
@@ -50,15 +54,18 @@ export function fsCommand(globals) {
|
|
|
50
54
|
.option('-d, --depth <n>', 'levels to expand')
|
|
51
55
|
.action(async (id, opts) => {
|
|
52
56
|
const client = new WjiClient(globals());
|
|
53
|
-
const node = await client.get(`/fsnode/list/${id}/tree`,
|
|
57
|
+
const node = await client.get(`/fsnode/list/${id}/tree`, {
|
|
58
|
+
depth: opts.depth ? Number(opts.depth) : undefined,
|
|
59
|
+
detail: detailLevel(),
|
|
60
|
+
});
|
|
54
61
|
render(node, (n) => printTree(n));
|
|
55
62
|
});
|
|
56
63
|
fs.command('get <id>')
|
|
57
|
-
.description('Show one node (comma-separate ids for a batch)')
|
|
64
|
+
.description('Show one node (comma-separate ids for a batch; --detail full for the whole content)')
|
|
58
65
|
.option('--scope <scope>', 'server-side scope filter')
|
|
59
66
|
.action(async (id, opts) => {
|
|
60
67
|
const client = new WjiClient(globals());
|
|
61
|
-
const node = await client.get(`/fsnode/list/${id}`, { scope: opts.scope });
|
|
68
|
+
const node = await client.get(`/fsnode/list/${id}`, { scope: opts.scope, detail: detailLevel() });
|
|
62
69
|
render(node, (n) => (Array.isArray(n) ? n.forEach(printNode) : printNode(n)));
|
|
63
70
|
});
|
|
64
71
|
fs.command('path <id>')
|
|
@@ -232,7 +239,7 @@ export function datasetCommand(globals) {
|
|
|
232
239
|
.description('List dataset nodes in a folder')
|
|
233
240
|
.action(async (folderId) => {
|
|
234
241
|
const client = new WjiClient(globals());
|
|
235
|
-
const result = await client.get(`/fsnode/list/${folderId}/children
|
|
242
|
+
const result = await client.get(`/fsnode/list/${folderId}/children`, { detail: detailLevel() });
|
|
236
243
|
const rows = (Array.isArray(result) ? result : result?.data || []).filter((n) => n.mimetype === 'workspace/dataset' || n.type === 'file');
|
|
237
244
|
render(rows, () => table(rows, [
|
|
238
245
|
{ header: 'id', value: (n) => n.id },
|
package/dist/commands/gen.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { WjiClient } from '../core/client.js';
|
|
3
3
|
import { confirm, parseKeyValues, readJson, readValue } from '../core/prompt.js';
|
|
4
|
-
import { color, detail, info, isJsonMode, printJson, render, success, table, truncate, warn } from '../core/output.js';
|
|
4
|
+
import { color, detail, detailLevel, info, isJsonMode, printJson, render, success, table, truncate, warn } from '../core/output.js';
|
|
5
5
|
const READY = 1;
|
|
6
6
|
const CANCELLED = -1;
|
|
7
7
|
/** `--ref abc123`, `--ref abc123:hero`, `--ref abc123:hero:0.6` */
|
|
@@ -254,6 +254,7 @@ export function historyCommand(globals) {
|
|
|
254
254
|
bookmarked: opts.bookmarked ? true : undefined,
|
|
255
255
|
page: Number(opts.page),
|
|
256
256
|
limit: Number(opts.limit),
|
|
257
|
+
detail: detailLevel(),
|
|
257
258
|
});
|
|
258
259
|
render(result, (r) => {
|
|
259
260
|
table(r.data || [], [
|
|
@@ -284,7 +285,7 @@ export function historyCommand(globals) {
|
|
|
284
285
|
}));
|
|
285
286
|
}
|
|
286
287
|
else {
|
|
287
|
-
history = await client.get(`/ai/assets/history/list/${id}
|
|
288
|
+
history = await client.get(`/ai/assets/history/list/${id}`, { detail: detailLevel() });
|
|
288
289
|
assets = await resolveAssets(client, history);
|
|
289
290
|
}
|
|
290
291
|
render({ ...history, images: assets }, () => reportHistory(history, assets));
|
package/dist/commands/model.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { WjiClient } from '../core/client.js';
|
|
3
|
-
import { color, detail, info, render, table, truncate, warn } from '../core/output.js';
|
|
3
|
+
import { color, detail, detailLevel, info, render, table, truncate, warn } from '../core/output.js';
|
|
4
4
|
const FEATURES = [
|
|
5
5
|
'text',
|
|
6
6
|
'image',
|
|
@@ -24,6 +24,7 @@ export async function fetchModels(client, opts) {
|
|
|
24
24
|
const features = opts.features?.length ? opts.features : FEATURES;
|
|
25
25
|
const list = await client.get(`/ai/model/list/${features.join(',')}`, {
|
|
26
26
|
workspaceId: client.workspace(opts.workspace),
|
|
27
|
+
detail: opts.detail,
|
|
27
28
|
});
|
|
28
29
|
return Array.isArray(list) ? list : list?.data || [];
|
|
29
30
|
}
|
|
@@ -41,6 +42,7 @@ export function modelCommand(globals) {
|
|
|
41
42
|
let rows = await fetchModels(client, {
|
|
42
43
|
features: opts.feature,
|
|
43
44
|
workspace: opts.workspace,
|
|
45
|
+
detail: detailLevel(),
|
|
44
46
|
});
|
|
45
47
|
if (!opts.all)
|
|
46
48
|
rows = rows.filter((m) => m.enabled !== false && !m.hidden);
|
|
@@ -70,7 +72,7 @@ export function modelCommand(globals) {
|
|
|
70
72
|
.option('-w, --workspace <id>', 'resolve pricing/access for a workspace')
|
|
71
73
|
.action(async (name, opts) => {
|
|
72
74
|
const client = new WjiClient(globals());
|
|
73
|
-
const rows = await fetchModels(client, { workspace: opts.workspace });
|
|
75
|
+
const rows = await fetchModels(client, { workspace: opts.workspace, detail: detailLevel() });
|
|
74
76
|
// `name` is not unique across providers; `nickname` is, so prefer it.
|
|
75
77
|
const matches = rows.filter((m) => m.nickname === name || m.name === name);
|
|
76
78
|
if (!matches.length)
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { WjiClient } from '../core/client.js';
|
|
3
|
-
import { updateProfile } from '../core/config.js';
|
|
3
|
+
import { getProfile, updateProfile } from '../core/config.js';
|
|
4
4
|
import { confirm, parseKeyValues } from '../core/prompt.js';
|
|
5
|
-
import { color, detail, info, relativeTime, render, success, table, truncate } from '../core/output.js';
|
|
5
|
+
import { color, detail, detailLevel, info, relativeTime, render, success, table, truncate } from '../core/output.js';
|
|
6
6
|
import { loadMembers, memberLabel, resolveMember } from '../core/members.js';
|
|
7
|
+
import { fetchModels } from './model.js';
|
|
7
8
|
/** App tokens accepted by `--app`; mirrors WorkspaceApplication on the backend. */
|
|
8
9
|
const APPS = {
|
|
9
10
|
dataset: 'Dataset',
|
|
@@ -54,6 +55,7 @@ export function workspaceCommand(globals) {
|
|
|
54
55
|
scope: opts.scope,
|
|
55
56
|
page: Number(opts.page),
|
|
56
57
|
limit: Number(opts.limit),
|
|
58
|
+
detail: detailLevel(),
|
|
57
59
|
});
|
|
58
60
|
const current = client.workspace();
|
|
59
61
|
render(result, (r) => {
|
|
@@ -77,7 +79,9 @@ export function workspaceCommand(globals) {
|
|
|
77
79
|
.description('Show a workspace (defaults to the selected one)')
|
|
78
80
|
.action(async (id) => {
|
|
79
81
|
const client = new WjiClient(globals());
|
|
80
|
-
const w = await client.get(`/workspace/list/${client.requireWorkspace(id)}
|
|
82
|
+
const w = await client.get(`/workspace/list/${client.requireWorkspace(id)}`, {
|
|
83
|
+
detail: detailLevel(),
|
|
84
|
+
});
|
|
81
85
|
render(w, () => detail([
|
|
82
86
|
['id', w.id],
|
|
83
87
|
['name', w.name],
|
|
@@ -173,7 +177,7 @@ export function workspaceCommand(globals) {
|
|
|
173
177
|
const path = opts.app
|
|
174
178
|
? `/workspace/list/${wsId}/fs/${opts.app}`
|
|
175
179
|
: `/workspace/list/${wsId}/fs`;
|
|
176
|
-
const root = await client.get(path);
|
|
180
|
+
const root = await client.get(path, { detail: detailLevel() });
|
|
177
181
|
render(root, (node) => printTree(node));
|
|
178
182
|
});
|
|
179
183
|
// ── apps ───────────────────────────────────────────────────────────────────
|
|
@@ -339,6 +343,56 @@ export function workspaceCommand(globals) {
|
|
|
339
343
|
await client.delete(`/workspace/list/${wsId}/member/${m.memberId}`);
|
|
340
344
|
render({ removed: targets.map((m) => m.userId) }, () => success(`Removed ${names}`));
|
|
341
345
|
});
|
|
346
|
+
ws.command('leave <id>')
|
|
347
|
+
.description('Leave a workspace you were invited to (owners cannot leave)')
|
|
348
|
+
.option('-y, --yes', 'skip confirmation')
|
|
349
|
+
.action(async (id, opts) => {
|
|
350
|
+
const g = globals();
|
|
351
|
+
const client = new WjiClient(g);
|
|
352
|
+
const w = await client.get(`/workspace/list/${id}/basic`).catch(() => ({ name: id }));
|
|
353
|
+
if (!(await confirm(`Leave workspace "${w.name}" (${id})? You need a new invite to come back.`, opts.yes))) {
|
|
354
|
+
return info('Aborted');
|
|
355
|
+
}
|
|
356
|
+
const result = await client.post(`/workspace/list/${id}/leave`);
|
|
357
|
+
// A left workspace 403s from now on, so stop pointing the profile at it.
|
|
358
|
+
if (getProfile(g.profile).profile.workspace === id)
|
|
359
|
+
updateProfile(g.profile, { workspace: undefined });
|
|
360
|
+
render(result, () => success(`Left ${w.name} (${id})`));
|
|
361
|
+
});
|
|
362
|
+
// ── default models ─────────────────────────────────────────────────────────
|
|
363
|
+
ws.command('defaults [id]')
|
|
364
|
+
.description('Show or set the workspace default models (pass "none" to clear one)')
|
|
365
|
+
.option('--chat <model>', 'default chat model for new conversations')
|
|
366
|
+
.option('--image <model>', 'default image model')
|
|
367
|
+
.option('--video <model>', 'default video model')
|
|
368
|
+
.option('--storyboard <model>', 'default storyboard model')
|
|
369
|
+
.option('--audio <model>', 'default audio model')
|
|
370
|
+
.option('--image-resolution <value>', 'default image resolution, e.g. 2K')
|
|
371
|
+
.option('--video-resolution <value>', 'default video resolution, e.g. 720p')
|
|
372
|
+
.action(async (id, opts) => {
|
|
373
|
+
const client = new WjiClient(globals());
|
|
374
|
+
const wsId = client.requireWorkspace(id);
|
|
375
|
+
// settings only comes back at full detail, and PUT replaces settings whole —
|
|
376
|
+
// read it fresh so prompts / defaultApp / improvePromptDefault survive.
|
|
377
|
+
const w = await client.getFresh(`/workspace/list/${wsId}`, { detail: 'full' });
|
|
378
|
+
const current = { ...(w.settings?.defaultModels || {}) };
|
|
379
|
+
const changes = DEFAULT_MODEL_KEYS.filter((key) => opts[key] !== undefined);
|
|
380
|
+
if (!changes.length) {
|
|
381
|
+
return render(current, () => detail(DEFAULT_MODEL_KEYS.map((key) => [key, current[key] ?? color.dim('—')])));
|
|
382
|
+
}
|
|
383
|
+
const next = { ...current };
|
|
384
|
+
const needsModels = changes.some((key) => !key.endsWith('Resolution') && !isNone(opts[key]));
|
|
385
|
+
const models = needsModels ? await fetchModels(client, { workspace: wsId }) : [];
|
|
386
|
+
for (const key of changes) {
|
|
387
|
+
const raw = String(opts[key]).trim();
|
|
388
|
+
next[key] = isNone(raw) ? null : key.endsWith('Resolution') ? raw : resolveDefaultModel(models, key, raw);
|
|
389
|
+
}
|
|
390
|
+
await client.put(`/workspace/list/${wsId}`, { settings: { ...(w.settings || {}), defaultModels: next } });
|
|
391
|
+
render(next, () => {
|
|
392
|
+
success('Default models updated');
|
|
393
|
+
detail(DEFAULT_MODEL_KEYS.map((key) => [key, next[key] ?? color.dim('—')]));
|
|
394
|
+
});
|
|
395
|
+
});
|
|
342
396
|
// ── quota / stats ──────────────────────────────────────────────────────────
|
|
343
397
|
ws.command('quota [id]')
|
|
344
398
|
.description('Show credit quota usage')
|
|
@@ -381,6 +435,28 @@ export function workspaceCommand(globals) {
|
|
|
381
435
|
});
|
|
382
436
|
return ws;
|
|
383
437
|
}
|
|
438
|
+
const DEFAULT_MODEL_KEYS = [
|
|
439
|
+
'chat',
|
|
440
|
+
'image',
|
|
441
|
+
'video',
|
|
442
|
+
'storyboard',
|
|
443
|
+
'audio',
|
|
444
|
+
'imageResolution',
|
|
445
|
+
'videoResolution',
|
|
446
|
+
];
|
|
447
|
+
const isNone = (value) => ['none', 'null', ''].includes(String(value ?? '').trim().toLowerCase());
|
|
448
|
+
/**
|
|
449
|
+
* The web stores the chat default as the model `name` (what conversations carry)
|
|
450
|
+
* and the media defaults as the `nickname` its pickers emit — the value `-m` takes.
|
|
451
|
+
* Accept either spelling and store what the web would.
|
|
452
|
+
*/
|
|
453
|
+
function resolveDefaultModel(models, key, value) {
|
|
454
|
+
const model = models.find((m) => m.nickname === value || m.name === value);
|
|
455
|
+
if (!model) {
|
|
456
|
+
throw new Error(`Unknown model "${value}" for --${key}. See \`wji models list\`.`);
|
|
457
|
+
}
|
|
458
|
+
return key === 'chat' ? model.name : model.nickname || model.name;
|
|
459
|
+
}
|
|
384
460
|
export function printTree(node, prefix = '', connector = '') {
|
|
385
461
|
if (!node)
|
|
386
462
|
return;
|
package/dist/core/output.js
CHANGED
|
@@ -17,6 +17,35 @@ export function setJsonMode(on) {
|
|
|
17
17
|
export function isJsonMode() {
|
|
18
18
|
return jsonMode;
|
|
19
19
|
}
|
|
20
|
+
const DETAIL_LEVELS = ['brief', 'standard', 'full'];
|
|
21
|
+
/**
|
|
22
|
+
* Global --detail (or WJI_DETAIL). Unset means "standard when printing JSON,
|
|
23
|
+
* nothing (server default = full) for human output", so tables are unchanged.
|
|
24
|
+
*/
|
|
25
|
+
let detailMode;
|
|
26
|
+
export function setDetailMode(raw) {
|
|
27
|
+
if (raw === undefined || raw === '') {
|
|
28
|
+
detailMode = undefined;
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const level = String(raw).toLowerCase();
|
|
32
|
+
if (!DETAIL_LEVELS.includes(level)) {
|
|
33
|
+
throw new Error(`--detail must be one of ${DETAIL_LEVELS.join(', ')} (got "${raw}")`);
|
|
34
|
+
}
|
|
35
|
+
detailMode = level;
|
|
36
|
+
}
|
|
37
|
+
export function detailLevel() {
|
|
38
|
+
if (detailMode)
|
|
39
|
+
return detailMode;
|
|
40
|
+
return jsonMode ? 'standard' : undefined;
|
|
41
|
+
}
|
|
42
|
+
/** Clip long text the way the server does at `standard` (200 chars + …). */
|
|
43
|
+
export function clipText(value, max = 200) {
|
|
44
|
+
if (value === undefined || value === null || value === '')
|
|
45
|
+
return undefined;
|
|
46
|
+
const s = String(value);
|
|
47
|
+
return s.length <= max ? s : `${s.slice(0, max)}…`;
|
|
48
|
+
}
|
|
20
49
|
export function printJson(value) {
|
|
21
50
|
process.stdout.write(JSON.stringify(value, null, 2) + '\n');
|
|
22
51
|
}
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
3
|
import { Command } from 'commander';
|
|
4
4
|
import { ApiError } from './core/client.js';
|
|
5
|
-
import { color, setJsonMode } from './core/output.js';
|
|
5
|
+
import { color, setDetailMode, setJsonMode } from './core/output.js';
|
|
6
6
|
import { authCommand } from './commands/auth.js';
|
|
7
7
|
import { configCommand } from './commands/config.js';
|
|
8
8
|
import { orgCommand } from './commands/org.js';
|
|
@@ -23,6 +23,12 @@ import { backreelCommand } from './commands/backreel.js';
|
|
|
23
23
|
// Read from package.json so `wji --version` cannot drift from the published
|
|
24
24
|
// version — the skill compares the two to detect a stale install.
|
|
25
25
|
const VERSION = createRequire(import.meta.url)('../package.json').version;
|
|
26
|
+
// `wji … | head` closes the pipe early; that is the reader being done, not a crash.
|
|
27
|
+
process.stdout.on('error', (err) => {
|
|
28
|
+
if (err.code === 'EPIPE')
|
|
29
|
+
process.exit(0);
|
|
30
|
+
throw err;
|
|
31
|
+
});
|
|
26
32
|
const program = new Command();
|
|
27
33
|
program
|
|
28
34
|
.name('wji')
|
|
@@ -34,7 +40,8 @@ program
|
|
|
34
40
|
.option('--org <orgId>', 'organization context (X-Org-Id)')
|
|
35
41
|
.option('--no-org', 'force personal context')
|
|
36
42
|
.option('--locale <locale>', 'response language (cht, chs, en)')
|
|
37
|
-
.option('--json', 'print
|
|
43
|
+
.option('--json', 'print JSON instead of tables (standard detail unless --detail is given)')
|
|
44
|
+
.option('--detail <level>', 'how much to return: brief | standard | full (default standard for --json; env WJI_DETAIL)')
|
|
38
45
|
.configureHelp({ sortSubcommands: true });
|
|
39
46
|
/**
|
|
40
47
|
* Global flags live on the root command, but subcommand actions run before
|
|
@@ -51,10 +58,12 @@ const globals = () => {
|
|
|
51
58
|
// commander turns `--no-org` into org === false
|
|
52
59
|
personal: opts.org === false,
|
|
53
60
|
locale: opts.locale,
|
|
61
|
+
detail: opts.detail,
|
|
54
62
|
};
|
|
55
63
|
};
|
|
56
64
|
program.hook('preAction', () => {
|
|
57
65
|
setJsonMode(!!program.opts().json);
|
|
66
|
+
setDetailMode(program.opts().detail ?? process.env.WJI_DETAIL);
|
|
58
67
|
});
|
|
59
68
|
program.addCommand(authCommand(globals));
|
|
60
69
|
program.addCommand(configCommand(globals));
|
package/package.json
CHANGED
|
@@ -24,6 +24,9 @@ strengths, not platform guarantees.
|
|
|
24
24
|
| `marketing-studio-image` | auto · 1:1 · 3:2 · 2:3 · 4:3 · 3:4 · 16:9 · 9:16 · 21:9 | 1k · 2k · 4k | 8 | fair | tuned for a commercial "marketing" look; cheapest 4K |
|
|
25
25
|
| `runninghub-midjourney-v7` | 1:1 · 4:3 · 3:2 · 16:9 · 3:4 · 2:3 · 9:16 | fixed; quality 1 / 2 / 4 | 5 image + 5 omni + 5 style, weighted | weak; do not rely on it for labels | mood boards, style frames; negative prompt |
|
|
26
26
|
| `runninghub-midjourney-v8.1` / `v8.2` | same 7 | native 2K | 8.1: 5; 8.2: 2 (first = image ref, second = style ref) | weak | style exploration only; never the packshot |
|
|
27
|
+
| `z-image-uncensored` / `z-image-pro-uncensored` | 1:1 · 2:3 · 3:2 · 3:4 · 4:3 · 9:16 · 16:9 | long edge ≤ 1536 (pro ≤ 2560) | none | fair | uncensored (無審核); only when the brief needs it |
|
|
28
|
+
| `qwen-image-edit-uncensored` | follows the first reference | follows the first reference | 1–3 (first = image to edit) | fair | uncensored instruction edit |
|
|
29
|
+
| `face-swap-uncensored` / `head-swap-uncensored` | follows the target | follows the target | exactly 2: target, then face / head | — | uncensored; swap a talent's face / head onto a shot |
|
|
27
30
|
|
|
28
31
|
Bold ratios are the only sources of 4:5 and 2:3 on the platform. No image model outputs 1.91:1,
|
|
29
32
|
6:7 or any banner strip.
|
|
@@ -40,6 +43,8 @@ Bold ratios are the only sources of 4:5 and 2:3 on the platform. No image model
|
|
|
40
43
|
| `seedance-2.0` family | same six | 4–15 s, or auto | 480p · 720p · 1080p · 4k | ref variants: images + video + audio | `generate_audio` | the 4K option of the family |
|
|
41
44
|
| `wan-3.0` / `-prime` | 16:9 · 4:3 · 1:1 · 3:4 · 9:16 | 2–30 s, or `autoDuration` | 480P · 720P · 1080P | first / last frame | `audio` on by default | optional "AI Generated" watermark; seed for repeatability |
|
|
42
45
|
| `wan-3.0-ref` / `-prime-ref` | same five | 2–30 s, or auto | as above | 10 images + 5 videos + 5 audios (≤ 15 s total) | `audio` | |
|
|
46
|
+
| `wan-3.0-*-uncensored` (+ `-pro`, `-prime`, `-prime-pro`; each with a `-ref-` twin) | adaptive + the five | 2–30 s, or auto | 480p–1080p; pro: 1080p · 2k · 4k | as `wan-3.0` / `wan-3.0-ref` | `audio` | uncensored (無審核); Ref twins billed on input video + output seconds |
|
|
47
|
+
| `wan-2.7-i2v-uncensored` / `wan-2.2-i2v-uncensored` | follows the input image | 2.7: 2–15 s; 2.2: 5 or 8 s | 2.7: 720p · 1080p; 2.2: 480p · 720p | first frame (2.2 also last frame); 2.7: 1 driving audio | 2.7 `audio` | uncensored; 2.2 is the cheapest video here |
|
|
43
48
|
| `veo-3.1` / `-fast` | 16:9 · 9:16 · auto | 4–8 s | 720p · 1080p · 4k | first / last frame | `generate_audio` | negative prompt available |
|
|
44
49
|
| `veo-3.1-ref` | 16:9 · 9:16 | 8 s fixed | 720p · 1080p · 4k | 1–10 images | `generate_audio` | |
|
|
45
50
|
| `kling-3` / `-pro` | 16:9 · 9:16 · 1:1 | 3–15 s | model default | 7 images, plus first / last frame | `generate_audio` (off by default) | pro = higher quality |
|
|
@@ -4,6 +4,24 @@
|
|
|
4
4
|
which row, and in what order the references are connected. Read them as written and substitute the
|
|
5
5
|
tool calls from `operations-wji.md`; `--from A B C` is `fromNodeIds: ['A','B','C']` and the array
|
|
6
6
|
order is the reference order. Everything about rows, `@imageN` order and prompts is identical.
|
|
7
|
+
Build each row with **one `canvas_create_nodes` call**: its `nodes` array takes the fields of
|
|
8
|
+
`canvas_create_node` (minus `canvas`), and `"$i"` in `fromNodeIds` or `nextTo` means item i of the
|
|
9
|
+
same batch (0-based, must come earlier), so a headshot → sheet → voice chain is one call and a
|
|
10
|
+
`characters` canvas of twelve characters is one or two calls instead of fifty. Items are created in
|
|
11
|
+
order; if one fails the result says `failedAt` and the earlier nodes stay on the canvas — fix that
|
|
12
|
+
item and resend from it with real ids (up to 60 nodes a call).
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
canvas_create_nodes { canvas: CV, nodes: [
|
|
16
|
+
{ kind: "title", text: "C01 林小夏", position: {x: 0, y: 0} }, # $0
|
|
17
|
+
{ kind: "image", name: "C01 headshot", position: {x: 400, y: 0},
|
|
18
|
+
generate: { model: "<image model>", ratio: "1:1", prompt: "<portrait prompt>" } }, # $1
|
|
19
|
+
{ kind: "image", name: "C01 sheet", fromNodeIds: ["$1"],
|
|
20
|
+
generate: { model: "<image model>", ratio: "16:9", prompt: "@image1 is the face to match … " } }, # $2
|
|
21
|
+
{ kind: "audio", name: "C01 voice", fromNodeIds: ["$1"],
|
|
22
|
+
generate: { model: "seed-audio-1.0", prompt: <voice prompt> } } # $3
|
|
23
|
+
] } → created[0..3].id = T1 H1 S1 V1
|
|
24
|
+
```
|
|
7
25
|
|
|
8
26
|
Command sequences for the three canvas types. All ids are placeholders — take real ones from the
|
|
9
27
|
command output (`wji --json canvas add … | jq -r .id`) and `bind` them at once. `SD` below is
|
|
@@ -36,6 +36,7 @@ The `wji` skill governs auth, confirmations and spending. The calls this skill m
|
|
|
36
36
|
| preflight | `wji auth whoami --json`, `wji profile wallet` |
|
|
37
37
|
| workspace | `wji ws create "<title>" -r <ratio>` → `wji ws use <id>` |
|
|
38
38
|
| datasets | `wji dataset new … --app <appId>`, `wji fs set <id> --merge` (never `--content`), `--add-images` (never `--images` on an existing node) |
|
|
39
|
+
| reads | `wji fs get <id> --json` is the `standard` summary; `--detail full` whenever you need the whole `content` (`--merge` reads full for you) |
|
|
39
40
|
| canvases | `wji canvas create <name>` · `wji canvas add <kind> -c <cv> …` · `wji canvas connect <a> <b>` · `wji canvas get <node>` · `wji canvas gen <node> [--yes --wait]` · `wji canvas rm` |
|
|
40
41
|
| assets | `wji asset download <id> -o <dir>` — **look at what you generated** before building on it |
|
|
41
42
|
| history | `wji history get <id> --wait` |
|
|
@@ -43,9 +43,9 @@ write a whole document at a time rather than field by field.
|
|
|
43
43
|
|
|
44
44
|
| Purpose | Tool |
|
|
45
45
|
| --- | --- |
|
|
46
|
-
| workspace | `
|
|
46
|
+
| workspace | `workspace_get { id }` for the workspace the chat is in, `workspace_create`, `workspace_add_app`; `workspace_search` **only with `limit`** (≤ 10) and a `keyword` — without them it returns every workspace the user can see. Both default to `detail: 'standard'`; `detail: 'full'` only when you need `settings` |
|
|
47
47
|
| semantic folders | `resolve_dataset_folder { id, key: 'story' \| 'character' \| 'location' \| 'prop' \| 'shot' }` |
|
|
48
|
-
| datasets | `fsnode_create_file { parent, name, dataset: true, datasetType, description, content, tags }`, `fsnode_update`, `fsnode_upsert_segment` |
|
|
48
|
+
| datasets | `fsnode_create_file { parent, name, dataset: true, datasetType, description, content, tags }`, `fsnode_update`, `fsnode_upsert_segment`; `fsnode_get { id, detail: 'full' }` before replacing a `content` (the default `standard` read is a summary with `truncated: true`, and writing it back is rejected) |
|
|
49
49
|
| assets | `asset_get`, `asset_check_status`, `asset_create_from_url` |
|
|
50
50
|
| canvases | the `canvas_*` tools below |
|
|
51
51
|
|
|
@@ -59,16 +59,21 @@ substitute:
|
|
|
59
59
|
| `canvas-recipes.md` shows | call |
|
|
60
60
|
| --- | --- |
|
|
61
61
|
| `wji canvas create <name>` | `canvas_create { workspace, name }` → canvas id |
|
|
62
|
-
| `wji canvas add <kind> -c CV -n "<name>" --at x,y …` | `canvas_create_node { canvas, kind, name, position: {x, y}, … }` |
|
|
62
|
+
| `wji canvas add <kind> -c CV -n "<name>" --at x,y …` | `canvas_create_node { canvas, kind, name, position: {x, y}, … }` — one node |
|
|
63
|
+
| a whole row of `wji canvas add …` lines | `canvas_create_nodes { canvas, nodes: [ … ] }` — up to 60 nodes in one call, each item the fields of `canvas_create_node` without `canvas`; `"$i"` in `fromNodeIds` / `nextTo` is item i of the same batch (0-based, must come earlier). Built in order; on a failure the result has `failedAt` and the earlier nodes stay |
|
|
63
64
|
| `--prompt-file /tmp/x.txt` / `-p "<prompt>"` | `generate: { prompt, model, ratio, resolution, duration }` on `canvas_create_node` |
|
|
64
65
|
| `--asset <id>` (a display node) | `assetId` on `canvas_create_node` |
|
|
65
66
|
| `--from A B C` | `fromNodeIds: ['A','B','C']` — **the array order is the reference order** |
|
|
66
67
|
| `wji canvas connect <a> <b>` | `canvas_connect_nodes { canvas, fromId, toId }` |
|
|
67
|
-
| `wji canvas get <node>` | `canvas_get_node { canvas, id }` — references, label, status, asset |
|
|
68
|
+
| `wji canvas get <node>` | `canvas_get_node { canvas, id }` — references, label, status, asset; `detail: 'full'` for the whole `generate` / `text` before editing them |
|
|
68
69
|
| `wji canvas gen <node>` (cost preview) | `canvas_generate { canvas, nodeIds, estimateOnly: true }` |
|
|
69
70
|
| `wji canvas gen <node> --yes --wait` | `canvas_generate { canvas, nodeIds, wait: true }` |
|
|
70
71
|
| `wji canvas rm` | `canvas_delete_nodes { canvas, ids }` |
|
|
71
72
|
|
|
73
|
+
Build each row with **one** `canvas_create_nodes` call (title, headshot, sheet `fromNodeIds: ["$1"]`,
|
|
74
|
+
voice `fromNodeIds: ["$1"]`), not four `canvas_create_node` calls — see the batch example at the top
|
|
75
|
+
of `canvas-recipes.md`.
|
|
76
|
+
|
|
72
77
|
Reference labels (`@image1`, `@audio1` …) are numbered per type in connection order, exactly as the
|
|
73
78
|
canvas UI numbers them. After connecting, call `canvas_get_node` and check the labels match what the
|
|
74
79
|
prompt names **before** generating — a mislabelled reference is a wasted generation.
|
|
@@ -77,6 +82,24 @@ prompt names **before** generating — a mislabelled reference is a wasted gener
|
|
|
77
82
|
first, tell the user the cost, and only generate for real once they say yes. The `wji` skill's
|
|
78
83
|
spending policy applies here in full.
|
|
79
84
|
|
|
85
|
+
## Steps and context
|
|
86
|
+
|
|
87
|
+
**A turn is capped at 40 steps, not 40 tool calls** (an Agent can set its own cap). One step is one
|
|
88
|
+
model call; every tool call you emit in the same step runs together and counts once. So emit
|
|
89
|
+
independent calls together — all the `short_drama_render` / `fsnode_create_file` of a stage in one
|
|
90
|
+
step, the five `resolve_dataset_folder` calls in one step — and write a whole document per
|
|
91
|
+
`short_drama_put` rather than field by field. When two steps are left you get a system note: stop
|
|
92
|
+
calling tools, report what was written (with ids) and what is still open; the stream ends at the cap
|
|
93
|
+
without letting you reply.
|
|
94
|
+
|
|
95
|
+
**Tool results stay in the context for the rest of the turn.** Read what you need, once: `ai_list_models`
|
|
96
|
+
once per project with `features: 'image'` (it is large); `fsnode_get` on the one dataset you need
|
|
97
|
+
instead of `fsnode_children` on a folder of them; `short_drama_get { section }` for one branch rather
|
|
98
|
+
than the whole project; never `workspace_search` without `limit`. Every list / get tool takes
|
|
99
|
+
`detail: 'brief' | 'standard' | 'full'` and defaults to `standard` (text clipped to 200 chars, media
|
|
100
|
+
as `{ id, url, status }`, `content` summarised); ask for `brief` when you only need ids and for `full`
|
|
101
|
+
only when you need the complete `content` / `form` / `settings`.
|
|
102
|
+
|
|
80
103
|
**In a canvas chat** (the chat panel beside an open canvas) the browser supplies its own `canvas_*`
|
|
81
104
|
tools, which always act on the canvas the user is looking at and take no `canvas` argument. A short
|
|
82
105
|
drama needs several canvases, so do that work in a workspace chat; in a canvas chat, say so.
|
|
@@ -345,6 +345,12 @@ segment is rejected, retry, then trim — do not go hunting for a forbidden word
|
|
|
345
345
|
To bisect anyway, use a scratch video node at `-d 2`, 480P: free when rejected, ~30 credits when it
|
|
346
346
|
passes, and sample each fragment several times because one pass proves nothing.
|
|
347
347
|
|
|
348
|
+
**Last resort: the uncensored twin.** `wan-3.0-ref-uncensored` (MuleRouter, no green-net) takes the
|
|
349
|
+
same prompt and the same 10 images + 5 videos + 5 audios, at ~1.3× the price; note that it bills the
|
|
350
|
+
reference-video seconds as well as the output. Switch a stuck segment to it only with the
|
|
351
|
+
Producer's approval, and only after retry-then-trim has failed. `short-drama.mjs` does not know it
|
|
352
|
+
as a mode, so set it as the node's model directly.
|
|
353
|
+
|
|
348
354
|
## Sheets mode — no storyboard images (the default)
|
|
349
355
|
|
|
350
356
|
`params.references: sheets` drops stage 5's image generation entirely: the video node references
|
package/skill/wji/SKILL.md
CHANGED
|
@@ -9,6 +9,12 @@ description: Drive the WJI AI platform from the terminal with the `wji` CLI —
|
|
|
9
9
|
`--json`, so prefer `--json` piped through `jq` when you need to read a value,
|
|
10
10
|
and the human table output when you are showing the user a result.
|
|
11
11
|
|
|
12
|
+
`--json` prints the **`standard`** detail level: ids, names, status, media urls,
|
|
13
|
+
long text clipped to 200 chars and `content` reduced to a summary marked
|
|
14
|
+
`truncated: true`. Add `--detail full` whenever you need the whole `content`,
|
|
15
|
+
`form` or `settings` (always before a read-modify-write), and `--detail brief`
|
|
16
|
+
when you only want ids. `WJI_DETAIL=full` sets it for a whole session.
|
|
17
|
+
|
|
12
18
|
## Before anything else
|
|
13
19
|
|
|
14
20
|
```bash
|
|
@@ -78,6 +84,9 @@ Most commands take `--workspace`, but setting a default is less error-prone:
|
|
|
78
84
|
```bash
|
|
79
85
|
wji ws list
|
|
80
86
|
wji ws use <workspaceId>
|
|
87
|
+
wji ws defaults # default chat / image / video / audio models
|
|
88
|
+
wji ws defaults --chat <model> --video <nickname> # "none" clears one
|
|
89
|
+
wji ws leave <workspaceId> # leave a workspace you were invited to
|
|
81
90
|
```
|
|
82
91
|
|
|
83
92
|
After that, `wji ws get`, `wji fs`, `wji gen` and `wji preset list` all default
|
|
@@ -94,7 +103,7 @@ wji ws fs # whole workspace tree
|
|
|
94
103
|
wji ws fs --app <appId> # one app's tree
|
|
95
104
|
wji fs tree <nodeId> --depth 2
|
|
96
105
|
wji fs ls <folderId>
|
|
97
|
-
wji fs get <nodeId> --json | jq .content
|
|
106
|
+
wji fs get <nodeId> --json --detail full | jq .content # --detail full: the summary is not the content
|
|
98
107
|
|
|
99
108
|
wji fs mkdir <parentId> characters --nickname 角色
|
|
100
109
|
wji dataset new <folderId> "Ada" -t character -d "a tired detective"
|
|
@@ -211,6 +220,25 @@ balanced default; **Seedance 2.0 Fast / MiniMax H3 Max** for drafts, tests and
|
|
|
211
220
|
large batches. The ranking is a guide — quote the real number from
|
|
212
221
|
`wji gen cost` / `wji canvas gen` before spending.
|
|
213
222
|
|
|
223
|
+
**Uncensored (無審核) models.** Use these only when the user asks for
|
|
224
|
+
uncensored output or a censored model refused the content. They carry the
|
|
225
|
+
`uncensored` tag in `wji models list --json`, and their names end in
|
|
226
|
+
`-uncensored`. The Wan 3.0 family has a Ref and a first/last-frame variant of
|
|
227
|
+
each tier, like the censored Wan 3.0.
|
|
228
|
+
|
|
229
|
+
| Job | Model (`name`) | Inputs |
|
|
230
|
+
| --- | --- | --- |
|
|
231
|
+
| Video, Ref | `wan-3.0-ref-uncensored`, `wan-3.0-prime-ref-uncensored` (480p–1080p); `wan-3.0-pro-ref-uncensored`, `wan-3.0-prime-pro-ref-uncensored` (1080p / 2k / 4k) | ≤ 10 images + ≤ 5 videos + ≤ 5 audios, freely mixed (video/audio ≤ 15 s total) |
|
|
232
|
+
| Video, first/last frame | `wan-3.0-uncensored`, `wan-3.0-prime-uncensored`, `wan-3.0-pro-uncensored`, `wan-3.0-prime-pro-uncensored` | text only, or start and/or end image; 2–30 s |
|
|
233
|
+
| Image → video | `wan-2.7-i2v-uncensored` (720p/1080p, 2–15 s; optional 1 driving audio for lip-sync via `--ref-audio`), `wan-2.2-i2v-uncensored` (cheapest; 480p/720p, 5 or 8 s, optional end frame) | start image |
|
|
234
|
+
| Animate a character | `wan-animate-uncensored` (`--set mode=animate\|replace`) | exactly 1 image + 1 video; output is as long as the video |
|
|
235
|
+
| Text → image | `z-image-uncensored` (long edge ≤ 1536), `z-image-pro-uncensored` (≤ 2560, `--set long_edge=`) | prompt + ratio |
|
|
236
|
+
| Edit an image | `qwen-image-edit-uncensored` | 1–3 images: the **first** is the image to edit |
|
|
237
|
+
| Face / head swap | `face-swap-uncensored`, `head-swap-uncensored` | exactly 2 images, **order matters**: 1st = target picture, 2nd = the face / head to put on it |
|
|
238
|
+
| Video upscale | `flashvsr-uncensored` (`--set resolution=720p\|1080p\|2k\|4k`) | 1 MP4 ≤ 120 s; billed per input second |
|
|
239
|
+
|
|
240
|
+
"Prime" variants are faster and cost more; "Pro" variants output 1080p–4K.
|
|
241
|
+
|
|
214
242
|
### Limits (per account)
|
|
215
243
|
|
|
216
244
|
| Limit | Value | When exceeded |
|
|
@@ -248,7 +276,7 @@ headshot and sheet as image references and the voice as an audio reference to
|
|
|
248
276
|
a Ref model.
|
|
249
277
|
|
|
250
278
|
```bash
|
|
251
|
-
wji fs get <datasetId> --
|
|
279
|
+
wji fs get <datasetId> --json --detail full | jq '{images: [.images[]? | {id, type}], icon, primaryAudio}'
|
|
252
280
|
wji fs set <datasetId> --add-images <headshot> <sheet> <voice> --icon <headshot> --set primaryAudio=<voice>
|
|
253
281
|
```
|
|
254
282
|
|
|
@@ -294,8 +322,12 @@ wji agent skill-add <agentId> system:image user:<skillId>
|
|
|
294
322
|
wji agent skills <agentId>
|
|
295
323
|
wji agent versions <agentId> # every edit auto-snapshots a version
|
|
296
324
|
wji agent restore <agentId> <versionId>
|
|
325
|
+
wji agent update <agentId> --max-steps 80 # model calls per chat turn (default 40, max 200)
|
|
297
326
|
```
|
|
298
327
|
|
|
328
|
+
Versions, files, update and restore are creator-only — a 403 `無權操作此 Agent`
|
|
329
|
+
on someone else's agent is expected.
|
|
330
|
+
|
|
299
331
|
Skill refs are `system:<key>` or `user:<skillId>` (a bare id means `user:`).
|
|
300
332
|
System keys currently include `image`, `document`, `workspace-dataset`,
|
|
301
333
|
`canvas` and `seedance-25` — confirm with `wji skill system`.
|
|
@@ -468,6 +500,8 @@ wji backreel set <postId> --project-status in_production
|
|
|
468
500
|
wji backreel credit add <postId> --kind creative --name "Wayne" --role 導演
|
|
469
501
|
wji backreel credit add <postId> --npc @mika --role "飾 阿明" --job appearance
|
|
470
502
|
wji backreel publish <postId> # unpublish takes it offline
|
|
503
|
+
wji backreel set <postId> --logline "一句話賣點" # always public, ≤200 chars
|
|
504
|
+
wji backreel creators --role investor # browse creators
|
|
471
505
|
```
|
|
472
506
|
|
|
473
507
|
- Credits (max 30) are rewritten whole in order; `credit rm` goes by number.
|
|
@@ -549,6 +583,9 @@ wji org list && wji org use <orgId>
|
|
|
549
583
|
- **Lists are paginated.** `--limit` is capped at 100; `--limit -1` returns at
|
|
550
584
|
most 500 on most endpoints. Page with `--page` instead of assuming you got
|
|
551
585
|
everything.
|
|
586
|
+
- **Lists are trimmed.** `--json` is the `standard` level; read `content`,
|
|
587
|
+
`form` or `settings` with `--detail full`, and never write back a `content`
|
|
588
|
+
that carries `truncated: true`.
|
|
552
589
|
- **Report failures honestly.** A failed generation surfaces as a non-zero exit
|
|
553
590
|
with the provider's message — pass it on rather than retrying silently.
|
|
554
591
|
|
|
@@ -10,7 +10,8 @@ Global flags (accepted before any subcommand):
|
|
|
10
10
|
| `--org <orgId>` | organization context (`X-Org-Id`) |
|
|
11
11
|
| `--no-org` | force personal context for this call |
|
|
12
12
|
| `--locale <locale>` | response language: `cht`, `chs`, `en` |
|
|
13
|
-
| `--json` | print
|
|
13
|
+
| `--json` | print JSON instead of tables (`standard` detail unless `--detail` is given) |
|
|
14
|
+
| `--detail <level>` | `brief` / `standard` / `full` for list and get reads; `full` is the raw API response. Env `WJI_DETAIL` |
|
|
14
15
|
|
|
15
16
|
Value conventions: `@file` reads a file, `-` reads stdin, `--set key=value`
|
|
16
17
|
repeats and nests on dots, references are `assetId[:label[:weight]]`.
|
|
@@ -64,13 +65,15 @@ switch endpoint.
|
|
|
64
65
|
|
|
65
66
|
| Command | API |
|
|
66
67
|
| --- | --- |
|
|
67
|
-
| `ws list [-k keyword] [-s all\|me\|shared\|favorite] [--page] [--limit]` | `GET /workspace/search` — `--limit` max 100, `-1` returns at most 500 |
|
|
68
|
-
| `ws get [id]` | `GET /workspace/list/:id` |
|
|
68
|
+
| `ws list [-k keyword] [-s all\|me\|shared\|favorite] [--page] [--limit]` | `GET /workspace/search` — `--limit` max 100, `-1` returns at most 500; sends `detail` |
|
|
69
|
+
| `ws get [id]` | `GET /workspace/list/:id` — sends `detail` (`settings` and full members need `--detail full`) |
|
|
69
70
|
| `ws use <id>` | stores the default workspace locally |
|
|
70
71
|
| `ws create <name> [-d] [-r ratio] [-l lang] [-t type] [-a app...] [--no-apps]` | `POST /workspace/create` — without `-a` the workspace gets the default template `dataset datasetimagegeneration canvas`; `--no-apps` creates it empty (the API itself adds nothing) |
|
|
71
72
|
| `ws update [id] [-n] [-d] [-r] [-l] [--set]` | `PUT /workspace/list/:id` |
|
|
72
73
|
| `ws rm <id> [-y]` | `DELETE /workspace/list/:id` (owner only) |
|
|
73
74
|
| `ws favorite <id> [--off]` | `PUT /workspace/list/:id/favorite` |
|
|
75
|
+
| `ws leave <id> [-y]` | `POST /workspace/list/:id/leave` — any member except the owner; coming back needs a new invite. Clears the profile default if it pointed there |
|
|
76
|
+
| `ws defaults [id] [--chat] [--image] [--video] [--storyboard] [--audio] [--image-resolution] [--video-resolution]` | no flags: shows `settings.defaultModels`. With flags: reads the workspace at `detail=full`, merges, `PUT /workspace/list/:id {settings}` (settings is replaced whole, so never send a partial `settings` via `ws update --set`). `none` clears one. Chat is stored as the model **name** (what conversations carry), media as the **nickname**; either spelling is accepted. The chat default is used for new conversations in the workspace |
|
|
74
77
|
| `ws fs [id] [-a appId]` | `GET /workspace/list/:id/fs[/:appid]` |
|
|
75
78
|
| `ws app list\|add <app> [-n]\|rm <appId> [-y]` | `POST/DELETE /workspace/list/:id/apps` |
|
|
76
79
|
| `ws member list [id] [-r role]` | members from `GET /workspace/list/:id` (there is no member-list endpoint): name, email, role, user id, membership id |
|
|
@@ -96,10 +99,10 @@ Restriction from the backend: `Dataset`, `Draw`, `DatasetImageGeneration` and
|
|
|
96
99
|
|
|
97
100
|
| Command | API |
|
|
98
101
|
| --- | --- |
|
|
99
|
-
| `fs root [key] [-d depth]` | `GET /fsnode/root[/:key]` — key defaults to your drive; pass a workspace id for its root |
|
|
100
|
-
| `fs ls <id> [--limit] [--scope]` | `GET /fsnode/list/:id/children` |
|
|
101
|
-
| `fs tree <id> [-d depth]` | `GET /fsnode/list/:id/tree` |
|
|
102
|
-
| `fs get <id> [--scope]` | `GET /fsnode/list/:id` (comma-separated ids allowed) |
|
|
102
|
+
| `fs root [key] [-d depth]` | `GET /fsnode/root[/:key]` — key defaults to your drive; pass a workspace id for its root; sends `detail` |
|
|
103
|
+
| `fs ls <id> [--limit] [--scope]` | `GET /fsnode/list/:id/children` — sends `detail`; at `standard` each `content` is a summary |
|
|
104
|
+
| `fs tree <id> [-d depth]` | `GET /fsnode/list/:id/tree` — sends `detail` |
|
|
105
|
+
| `fs get <id> [--scope]` | `GET /fsnode/list/:id` (comma-separated ids allowed) — sends `detail`; `--detail full` for the whole `content` |
|
|
103
106
|
| `fs path <id>` | `GET /fsnode/list/:id/path` |
|
|
104
107
|
| `fs mkdir <parent> <name> [--nickname] [--content]` | `POST /fsnode/create` type=folder |
|
|
105
108
|
| `fs touch <parent> <name> [--mimetype] [--asset] [--content]` | `POST /fsnode/create` type=file |
|
|
@@ -118,7 +121,7 @@ characters, nicknames at 200, and `fs batch` at 100 items per call.
|
|
|
118
121
|
| Command | Notes |
|
|
119
122
|
| --- | --- |
|
|
120
123
|
| `dataset new <parent> <name> [-t type] [-d description] [--images] [--nickname]` | creates an fsnode with mimetype `workspace/dataset` |
|
|
121
|
-
| `dataset list <folderId>` | dataset nodes in a folder |
|
|
124
|
+
| `dataset list <folderId>` | dataset nodes in a folder (sends `detail`) |
|
|
122
125
|
| `dataset get <key>` | `GET /dataset/list/:key` (vector-store record) |
|
|
123
126
|
| `dataset rm <key> [-y]` | `DELETE /dataset/list/:key` |
|
|
124
127
|
|
|
@@ -150,6 +153,11 @@ Model parameters worth knowing (`--set`, see `wji models get`):
|
|
|
150
153
|
`gpt-image-2.5-*` `background=transparent` returns a PNG with alpha;
|
|
151
154
|
`bytedance-seedream-5-pro` `layer_decomposition=true` splits one reference
|
|
152
155
|
image into separate PNG layer assets (exactly 1 reference, `size=1K|2K`).
|
|
156
|
+
`face-swap-uncensored` / `head-swap-uncensored` take exactly 2 references in
|
|
157
|
+
order (target picture, then the face / head); `qwen-image-edit-uncensored`
|
|
158
|
+
edits the first reference; `z-image-*-uncensored` `long_edge=<px>`;
|
|
159
|
+
`wan-animate-uncensored` `mode=animate|replace`; `flashvsr-uncensored`
|
|
160
|
+
`resolution=720p|1080p|2k|4k`.
|
|
153
161
|
|
|
154
162
|
Uploads of `.html`, `.xhtml`, `.svg`, `.js` and `.xml` are stored as
|
|
155
163
|
`application/octet-stream` with `Content-Disposition: attachment`.
|
|
@@ -171,7 +179,7 @@ updates live.
|
|
|
171
179
|
| `canvas copy [nodeIds...] --to <canvasIds...> [-c] [--selected] [--at x,y] [--same-position] [-y]` | the canvas's own copy-paste: new nodes (no position, `generate.historyId/pendingAssetId` dropped, finished asset shared via `file`), edges among them re-created oldest first so `@image1…` numbering holds, color tags kept and a missing label carried to the target. Edges from nodes outside the copy are left out. Default placement: right of what is already on the target |
|
|
172
180
|
| `canvas nodes [-c] [-k kind] [-n regex] [--tag tags…\|--untagged]` | `GET /fsnode/list/:canvasId/children?limit=-1` + `POST /assets/check-status`. `-n` matches the node name as a case-insensitive regex (`/re/flags` to set flags yourself). `--tag` takes a label (`C01`, case-insensitive), a color (`red`) or a color id (`rose`); several values match any. Filters combine (AND) |
|
|
173
181
|
| `canvas tags [-c] [-a]` | the canvas's color tags: color, label, node count (`-a` also unused, unlabelled colors) |
|
|
174
|
-
| `canvas get <nodeId> [-c]` | same load; one node with references (`@image1 ← id`) and frame slots |
|
|
182
|
+
| `canvas get <nodeId> [-c]` | same load; one node with references (`@image1 ← id`) and frame slots. Canvas commands always load the full canvas; `--detail` trims only the JSON they print (`standard` clips `text` / `generate.prompt` and drops `generate.values`) |
|
|
175
183
|
| `canvas edges [-c] [--node]` | the `canvas/edge` children |
|
|
176
184
|
| `canvas add <kind> [-c] [-n] [-t\|--text-file] [--plain] [--size w,h] [--asset] [--dataset] [-p\|--prompt-file] [-m] [-r] [--resolution] [-d] [--set] [--from ids…] [--at x,y]` | `POST /fsnode/create` (`canvas/node/<kind>`), then one `canvas/edge` per `--from`. Kinds: text, title, comment, image, video, audio, dataset. `add dataset --dataset <fsnodeId>` writes `content {datasetId, datasetName, datasetType, datasetParentName, description}` from the dataset node (must be `workspace/dataset`); it takes no `--from`. Text kinds are Markdown on the web; `--plain` turns every single line break into a Markdown hard break (`" \n"`, fenced code untouched), and without it the CLI warns when a single break would be joined. `--size` writes `content.width/height` |
|
|
177
185
|
| `canvas set <nodeId> [-c] [-n] [-t\|--text-file] [--plain] [--move x,y] [--size w,h] [-p] [-m] [-r] [--resolution] [-d] [--set]` | read → merge → `PATCH /fsnode/list/:id` with `contentPatch` of only the top-level keys that changed, so a concurrent browser edit of another key survives (falls back to a whole-content write on a backend without `contentPatch`) |
|
|
@@ -269,16 +277,20 @@ the CLI rejects both up front.
|
|
|
269
277
|
| --- | --- |
|
|
270
278
|
| `backreel list [--all] [-k] [--page] [--limit]` | `GET /backreel/post/search/my` (`--all`: `GET /backreel/post/search`) |
|
|
271
279
|
| `backreel get <postId>` | `GET /backreel/post/list/:id` |
|
|
272
|
-
| `backreel create <title> [--kind showcase\|crowdfunding] [--category] [--project-status] [--tags a,b] [-d text\|@file] [--cover assetId] [--cover-portrait] [--trailer] [--body json]` | `POST /backreel/post/create` (always a draft) |
|
|
280
|
+
| `backreel create <title> [--kind showcase\|crowdfunding] [--logline] [--category] [--project-status] [--tags a,b] [-d text\|@file] [--cover assetId] [--cover-portrait] [--trailer] [--body json]` | `POST /backreel/post/create` (always a draft) |
|
|
273
281
|
| `backreel set <postId> [--title] [same flags]` | `PUT /backreel/post/list/:id` with only the given fields |
|
|
274
282
|
| `backreel publish\|unpublish <postId>` | `POST /backreel/post/list/:id/publish\|unpublish` (publish needs a cover, media or an episode) |
|
|
275
283
|
| `backreel rm <postId> [-y]` | `DELETE /backreel/post/list/:id` |
|
|
284
|
+
| `backreel creators [-k] [--role kind] [--verified] [--no-featured] [--page] [--limit ≤48]` | `GET /backreel/creator/list` — editor-picked `featured` plus a heat-ranked page (recent likes, favorites ×2); creators with no recent activity are not listed. `--role`: investor, production_company, brand, distributor, sales_agent |
|
|
276
285
|
| `backreel credit list <postId>` | the post's `credits[]`, numbered |
|
|
277
286
|
| `backreel credit add <postId> --role [--name] [--kind cast\|crew\|creative] [--npc] [--user] [--link] [--job <type>]` | read, append, `PUT {credits}`; `--job` then appends to the NPC's `jobs` (own NPCs only, checked first) |
|
|
278
287
|
| `backreel credit rm <postId> <n...> [-y]` | read, drop, `PUT {credits}` |
|
|
279
288
|
|
|
280
289
|
Project status: planning, pre_production, in_production, post_production,
|
|
281
|
-
completed.
|
|
290
|
+
completed. `--logline` is a one-line hook (≤200 chars, HTML stripped, `""`
|
|
291
|
+
clears it) shown large on the post page and **always public**, even when the
|
|
292
|
+
description is gated. The post detail no longer carries playable URLs for VOD
|
|
293
|
+
episodes — playback is gated per episode on the web. `-d` plain text becomes `<p>` paragraphs; HTML passes through and
|
|
282
294
|
is sanitized server-side. Admin routes (`/api/backreel/*`) reject `sk_` keys.
|
|
283
295
|
|
|
284
296
|
## gen
|
|
@@ -320,7 +332,7 @@ parameters are range-checked against the model's `min`/`max`.
|
|
|
320
332
|
## history
|
|
321
333
|
|
|
322
334
|
`history list [-w] [--mode image|video|audio] [--fsnode] [--scope] [--scope-id] [--tags] [--bookmarked]` →
|
|
323
|
-
`GET /ai/assets/history/search`
|
|
335
|
+
`GET /ai/assets/history/search` (sends `detail`; `standard` clips `prompt` and summarises `form`)
|
|
324
336
|
|
|
325
337
|
`history get <id> [--wait] [--timeout] [--interval]` ·
|
|
326
338
|
`history bookmark <id> [--off]` · `history rm <id> [-y]` ·
|
|
@@ -329,12 +341,15 @@ parameters are range-checked against the model's `min`/`max`.
|
|
|
329
341
|
## models
|
|
330
342
|
|
|
331
343
|
`models list [-f feature...] [-w] [-k keyword] [--all]` →
|
|
332
|
-
`GET /ai/model/list/:features`
|
|
344
|
+
`GET /ai/model/list/:features` (sends `detail`; at `standard` the `form` keeps
|
|
345
|
+
only ratios, batch, reference caps and `other` options — `--detail full` for the
|
|
346
|
+
whole schema)
|
|
333
347
|
|
|
334
348
|
The endpoint matches with `features.some(...)`, so an empty feature list returns
|
|
335
349
|
nothing; with no `-f` the CLI asks for every feature instead.
|
|
336
350
|
|
|
337
|
-
The response is a curated view — no `id`, `rate
|
|
351
|
+
The response is a curated view — no `id`, `rate` or `protocol` (`tags` is
|
|
352
|
+
included; uncensored models carry `uncensored`) — and it
|
|
338
353
|
is de-duplicated by **`nickname`**, which is therefore the unique selector.
|
|
339
354
|
`name` is not unique (`seedance-2.0` exists twice under different providers).
|
|
340
355
|
Pass the `MODEL` column (the nickname) to `-m`.
|
|
@@ -352,7 +367,7 @@ Features: `text`, `image`, `video`, `audio`, `tts`, `web`, `thinking`,
|
|
|
352
367
|
## agent
|
|
353
368
|
|
|
354
369
|
`agent list` (public marketplace) · `agent mine` · `agent get <id>` ·
|
|
355
|
-
`agent create <name> -d [-i instructions] [-m model] [--visibility] [-c] [-t] [--greeting]` ·
|
|
370
|
+
`agent create <name> -d [-i instructions] [-m model] [--visibility] [-c] [-t] [--greeting] [--max-steps n]` ·
|
|
356
371
|
`agent update <id> …` · `agent rm <id> [-y]` · `agent files <id>` ·
|
|
357
372
|
`agent categories [--mine]` · `agent tags [--mine]` · `agent recommend`
|
|
358
373
|
|
|
@@ -371,6 +386,17 @@ takes `--skill <ref...>`.
|
|
|
371
386
|
Visibility: `private`, `unlist`, `public`. Instructions are only returned to
|
|
372
387
|
the agent's creator.
|
|
373
388
|
|
|
389
|
+
Ownership (backend since Oct 2026): a private agent is readable only by its
|
|
390
|
+
creator; `update`, `rm`, `files`, `versions` and `restore` are creator-only on
|
|
391
|
+
every agent (403 `無權操作此 Agent` otherwise — not a bug). `agent create`
|
|
392
|
+
needs the account's agent-create permission (without it: 400 `您沒有此功能的訪問權限`). `--set` cannot change `creator` or
|
|
393
|
+
`organization`, and non-admins cannot set `key`, `isSystem`, `isHighligted`,
|
|
394
|
+
`recommendTitle`, `weight` or `store` — the server drops them silently.
|
|
395
|
+
|
|
396
|
+
`--max-steps n` caps model calls per chat turn (1–200; parallel tool calls in
|
|
397
|
+
one step count once). Unset means the site default, 40. `update --max-steps
|
|
398
|
+
default` clears it.
|
|
399
|
+
|
|
374
400
|
Field caps: name ≤200, model ≤100, description and greeting ≤5,000,
|
|
375
401
|
instructions ≤200,000 characters.
|
|
376
402
|
|
|
@@ -467,6 +493,12 @@ Pass `null` to clear a nullable field. Password minimum is 6 characters.
|
|
|
467
493
|
asset carrying `error` is also a failure.
|
|
468
494
|
- Some GETs are cached per user for 10s, keyed on the full URL including the
|
|
469
495
|
query string.
|
|
496
|
+
- Workspace, fsnode, model and history reads accept `?detail=brief|standard|full`.
|
|
497
|
+
The API defaults to `full` (the website relies on it); the CLI sends the
|
|
498
|
+
`--detail` value, or `standard` under `--json`. `standard` clips long text to
|
|
499
|
+
200 chars, turns media into `{id, url, thumbnail, status, …}` and reduces an
|
|
500
|
+
fsnode `content` to a summary carrying `truncated: true`; never PATCH such a
|
|
501
|
+
summary back.
|
|
470
502
|
- Paginated endpoints return `{ data, pagination: { total, page, limit, next,
|
|
471
503
|
hasNext, hasPrev, totalPages } }`. An explicit `limit` is capped at **100**.
|
|
472
504
|
`limit: -1` returns everything only on endpoints that opt in (`fs ls` does);
|