@wji.ai/cli 0.5.1 → 0.6.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 +7 -2
- 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 +6 -3
- package/dist/core/output.js +29 -0
- package/dist/index.js +5 -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 +30 -2
- package/skill/wji/reference/commands.md +27 -12
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/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)
|
|
@@ -2,7 +2,7 @@ import { Command } from 'commander';
|
|
|
2
2
|
import { WjiClient } from '../core/client.js';
|
|
3
3
|
import { 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
7
|
/** App tokens accepted by `--app`; mirrors WorkspaceApplication on the backend. */
|
|
8
8
|
const APPS = {
|
|
@@ -54,6 +54,7 @@ export function workspaceCommand(globals) {
|
|
|
54
54
|
scope: opts.scope,
|
|
55
55
|
page: Number(opts.page),
|
|
56
56
|
limit: Number(opts.limit),
|
|
57
|
+
detail: detailLevel(),
|
|
57
58
|
});
|
|
58
59
|
const current = client.workspace();
|
|
59
60
|
render(result, (r) => {
|
|
@@ -77,7 +78,9 @@ export function workspaceCommand(globals) {
|
|
|
77
78
|
.description('Show a workspace (defaults to the selected one)')
|
|
78
79
|
.action(async (id) => {
|
|
79
80
|
const client = new WjiClient(globals());
|
|
80
|
-
const w = await client.get(`/workspace/list/${client.requireWorkspace(id)}
|
|
81
|
+
const w = await client.get(`/workspace/list/${client.requireWorkspace(id)}`, {
|
|
82
|
+
detail: detailLevel(),
|
|
83
|
+
});
|
|
81
84
|
render(w, () => detail([
|
|
82
85
|
['id', w.id],
|
|
83
86
|
['name', w.name],
|
|
@@ -173,7 +176,7 @@ export function workspaceCommand(globals) {
|
|
|
173
176
|
const path = opts.app
|
|
174
177
|
? `/workspace/list/${wsId}/fs/${opts.app}`
|
|
175
178
|
: `/workspace/list/${wsId}/fs`;
|
|
176
|
-
const root = await client.get(path);
|
|
179
|
+
const root = await client.get(path, { detail: detailLevel() });
|
|
177
180
|
render(root, (node) => printTree(node));
|
|
178
181
|
});
|
|
179
182
|
// ── apps ───────────────────────────────────────────────────────────────────
|
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';
|
|
@@ -34,7 +34,8 @@ program
|
|
|
34
34
|
.option('--org <orgId>', 'organization context (X-Org-Id)')
|
|
35
35
|
.option('--no-org', 'force personal context')
|
|
36
36
|
.option('--locale <locale>', 'response language (cht, chs, en)')
|
|
37
|
-
.option('--json', 'print
|
|
37
|
+
.option('--json', 'print JSON instead of tables (standard detail unless --detail is given)')
|
|
38
|
+
.option('--detail <level>', 'how much to return: brief | standard | full (default standard for --json; env WJI_DETAIL)')
|
|
38
39
|
.configureHelp({ sortSubcommands: true });
|
|
39
40
|
/**
|
|
40
41
|
* Global flags live on the root command, but subcommand actions run before
|
|
@@ -51,10 +52,12 @@ const globals = () => {
|
|
|
51
52
|
// commander turns `--no-org` into org === false
|
|
52
53
|
personal: opts.org === false,
|
|
53
54
|
locale: opts.locale,
|
|
55
|
+
detail: opts.detail,
|
|
54
56
|
};
|
|
55
57
|
};
|
|
56
58
|
program.hook('preAction', () => {
|
|
57
59
|
setJsonMode(!!program.opts().json);
|
|
60
|
+
setDetailMode(program.opts().detail ?? process.env.WJI_DETAIL);
|
|
58
61
|
});
|
|
59
62
|
program.addCommand(authCommand(globals));
|
|
60
63
|
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
|
|
@@ -94,7 +100,7 @@ wji ws fs # whole workspace tree
|
|
|
94
100
|
wji ws fs --app <appId> # one app's tree
|
|
95
101
|
wji fs tree <nodeId> --depth 2
|
|
96
102
|
wji fs ls <folderId>
|
|
97
|
-
wji fs get <nodeId> --json | jq .content
|
|
103
|
+
wji fs get <nodeId> --json --detail full | jq .content # --detail full: the summary is not the content
|
|
98
104
|
|
|
99
105
|
wji fs mkdir <parentId> characters --nickname 角色
|
|
100
106
|
wji dataset new <folderId> "Ada" -t character -d "a tired detective"
|
|
@@ -211,6 +217,25 @@ balanced default; **Seedance 2.0 Fast / MiniMax H3 Max** for drafts, tests and
|
|
|
211
217
|
large batches. The ranking is a guide — quote the real number from
|
|
212
218
|
`wji gen cost` / `wji canvas gen` before spending.
|
|
213
219
|
|
|
220
|
+
**Uncensored (無審核) models.** Use these only when the user asks for
|
|
221
|
+
uncensored output or a censored model refused the content. They carry the
|
|
222
|
+
`uncensored` tag in `wji models list --json`, and their names end in
|
|
223
|
+
`-uncensored`. The Wan 3.0 family has a Ref and a first/last-frame variant of
|
|
224
|
+
each tier, like the censored Wan 3.0.
|
|
225
|
+
|
|
226
|
+
| Job | Model (`name`) | Inputs |
|
|
227
|
+
| --- | --- | --- |
|
|
228
|
+
| 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) |
|
|
229
|
+
| 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 |
|
|
230
|
+
| 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 |
|
|
231
|
+
| Animate a character | `wan-animate-uncensored` (`--set mode=animate\|replace`) | exactly 1 image + 1 video; output is as long as the video |
|
|
232
|
+
| Text → image | `z-image-uncensored` (long edge ≤ 1536), `z-image-pro-uncensored` (≤ 2560, `--set long_edge=`) | prompt + ratio |
|
|
233
|
+
| Edit an image | `qwen-image-edit-uncensored` | 1–3 images: the **first** is the image to edit |
|
|
234
|
+
| 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 |
|
|
235
|
+
| Video upscale | `flashvsr-uncensored` (`--set resolution=720p\|1080p\|2k\|4k`) | 1 MP4 ≤ 120 s; billed per input second |
|
|
236
|
+
|
|
237
|
+
"Prime" variants are faster and cost more; "Pro" variants output 1080p–4K.
|
|
238
|
+
|
|
214
239
|
### Limits (per account)
|
|
215
240
|
|
|
216
241
|
| Limit | Value | When exceeded |
|
|
@@ -248,7 +273,7 @@ headshot and sheet as image references and the voice as an audio reference to
|
|
|
248
273
|
a Ref model.
|
|
249
274
|
|
|
250
275
|
```bash
|
|
251
|
-
wji fs get <datasetId> --
|
|
276
|
+
wji fs get <datasetId> --json --detail full | jq '{images: [.images[]? | {id, type}], icon, primaryAudio}'
|
|
252
277
|
wji fs set <datasetId> --add-images <headshot> <sheet> <voice> --icon <headshot> --set primaryAudio=<voice>
|
|
253
278
|
```
|
|
254
279
|
|
|
@@ -549,6 +574,9 @@ wji org list && wji org use <orgId>
|
|
|
549
574
|
- **Lists are paginated.** `--limit` is capped at 100; `--limit -1` returns at
|
|
550
575
|
most 500 on most endpoints. Page with `--page` instead of assuming you got
|
|
551
576
|
everything.
|
|
577
|
+
- **Lists are trimmed.** `--json` is the `standard` level; read `content`,
|
|
578
|
+
`form` or `settings` with `--detail full`, and never write back a `content`
|
|
579
|
+
that carries `truncated: true`.
|
|
552
580
|
- **Report failures honestly.** A failed generation surfaces as a non-zero exit
|
|
553
581
|
with the provider's message — pass it on rather than retrying silently.
|
|
554
582
|
|
|
@@ -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,8 +65,8 @@ 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` |
|
|
@@ -96,10 +97,10 @@ Restriction from the backend: `Dataset`, `Draw`, `DatasetImageGeneration` and
|
|
|
96
97
|
|
|
97
98
|
| Command | API |
|
|
98
99
|
| --- | --- |
|
|
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) |
|
|
100
|
+
| `fs root [key] [-d depth]` | `GET /fsnode/root[/:key]` — key defaults to your drive; pass a workspace id for its root; sends `detail` |
|
|
101
|
+
| `fs ls <id> [--limit] [--scope]` | `GET /fsnode/list/:id/children` — sends `detail`; at `standard` each `content` is a summary |
|
|
102
|
+
| `fs tree <id> [-d depth]` | `GET /fsnode/list/:id/tree` — sends `detail` |
|
|
103
|
+
| `fs get <id> [--scope]` | `GET /fsnode/list/:id` (comma-separated ids allowed) — sends `detail`; `--detail full` for the whole `content` |
|
|
103
104
|
| `fs path <id>` | `GET /fsnode/list/:id/path` |
|
|
104
105
|
| `fs mkdir <parent> <name> [--nickname] [--content]` | `POST /fsnode/create` type=folder |
|
|
105
106
|
| `fs touch <parent> <name> [--mimetype] [--asset] [--content]` | `POST /fsnode/create` type=file |
|
|
@@ -118,7 +119,7 @@ characters, nicknames at 200, and `fs batch` at 100 items per call.
|
|
|
118
119
|
| Command | Notes |
|
|
119
120
|
| --- | --- |
|
|
120
121
|
| `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 |
|
|
122
|
+
| `dataset list <folderId>` | dataset nodes in a folder (sends `detail`) |
|
|
122
123
|
| `dataset get <key>` | `GET /dataset/list/:key` (vector-store record) |
|
|
123
124
|
| `dataset rm <key> [-y]` | `DELETE /dataset/list/:key` |
|
|
124
125
|
|
|
@@ -150,6 +151,11 @@ Model parameters worth knowing (`--set`, see `wji models get`):
|
|
|
150
151
|
`gpt-image-2.5-*` `background=transparent` returns a PNG with alpha;
|
|
151
152
|
`bytedance-seedream-5-pro` `layer_decomposition=true` splits one reference
|
|
152
153
|
image into separate PNG layer assets (exactly 1 reference, `size=1K|2K`).
|
|
154
|
+
`face-swap-uncensored` / `head-swap-uncensored` take exactly 2 references in
|
|
155
|
+
order (target picture, then the face / head); `qwen-image-edit-uncensored`
|
|
156
|
+
edits the first reference; `z-image-*-uncensored` `long_edge=<px>`;
|
|
157
|
+
`wan-animate-uncensored` `mode=animate|replace`; `flashvsr-uncensored`
|
|
158
|
+
`resolution=720p|1080p|2k|4k`.
|
|
153
159
|
|
|
154
160
|
Uploads of `.html`, `.xhtml`, `.svg`, `.js` and `.xml` are stored as
|
|
155
161
|
`application/octet-stream` with `Content-Disposition: attachment`.
|
|
@@ -171,7 +177,7 @@ updates live.
|
|
|
171
177
|
| `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
178
|
| `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
179
|
| `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 |
|
|
180
|
+
| `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
181
|
| `canvas edges [-c] [--node]` | the `canvas/edge` children |
|
|
176
182
|
| `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
183
|
| `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`) |
|
|
@@ -320,7 +326,7 @@ parameters are range-checked against the model's `min`/`max`.
|
|
|
320
326
|
## history
|
|
321
327
|
|
|
322
328
|
`history list [-w] [--mode image|video|audio] [--fsnode] [--scope] [--scope-id] [--tags] [--bookmarked]` →
|
|
323
|
-
`GET /ai/assets/history/search`
|
|
329
|
+
`GET /ai/assets/history/search` (sends `detail`; `standard` clips `prompt` and summarises `form`)
|
|
324
330
|
|
|
325
331
|
`history get <id> [--wait] [--timeout] [--interval]` ·
|
|
326
332
|
`history bookmark <id> [--off]` · `history rm <id> [-y]` ·
|
|
@@ -329,12 +335,15 @@ parameters are range-checked against the model's `min`/`max`.
|
|
|
329
335
|
## models
|
|
330
336
|
|
|
331
337
|
`models list [-f feature...] [-w] [-k keyword] [--all]` →
|
|
332
|
-
`GET /ai/model/list/:features`
|
|
338
|
+
`GET /ai/model/list/:features` (sends `detail`; at `standard` the `form` keeps
|
|
339
|
+
only ratios, batch, reference caps and `other` options — `--detail full` for the
|
|
340
|
+
whole schema)
|
|
333
341
|
|
|
334
342
|
The endpoint matches with `features.some(...)`, so an empty feature list returns
|
|
335
343
|
nothing; with no `-f` the CLI asks for every feature instead.
|
|
336
344
|
|
|
337
|
-
The response is a curated view — no `id`, `rate
|
|
345
|
+
The response is a curated view — no `id`, `rate` or `protocol` (`tags` is
|
|
346
|
+
included; uncensored models carry `uncensored`) — and it
|
|
338
347
|
is de-duplicated by **`nickname`**, which is therefore the unique selector.
|
|
339
348
|
`name` is not unique (`seedance-2.0` exists twice under different providers).
|
|
340
349
|
Pass the `MODEL` column (the nickname) to `-m`.
|
|
@@ -467,6 +476,12 @@ Pass `null` to clear a nullable field. Password minimum is 6 characters.
|
|
|
467
476
|
asset carrying `error` is also a failure.
|
|
468
477
|
- Some GETs are cached per user for 10s, keyed on the full URL including the
|
|
469
478
|
query string.
|
|
479
|
+
- Workspace, fsnode, model and history reads accept `?detail=brief|standard|full`.
|
|
480
|
+
The API defaults to `full` (the website relies on it); the CLI sends the
|
|
481
|
+
`--detail` value, or `standard` under `--json`. `standard` clips long text to
|
|
482
|
+
200 chars, turns media into `{id, url, thumbnail, status, …}` and reduces an
|
|
483
|
+
fsnode `content` to a summary carrying `truncated: true`; never PATCH such a
|
|
484
|
+
summary back.
|
|
470
485
|
- Paginated endpoints return `{ data, pagination: { total, page, limit, next,
|
|
471
486
|
hasNext, hasPrev, totalPages } }`. An explicit `limit` is capped at **100**.
|
|
472
487
|
`limit: -1` returns everything only on endpoints that opt in (`fs ls` does);
|