@wji.ai/cli 0.1.0 → 0.2.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 +10 -1
- package/dist/commands/canvas.js +91 -12
- package/dist/commands/fsnode.js +30 -1
- package/dist/commands/gen.js +4 -19
- package/dist/commands/workspace.js +6 -2
- package/dist/index.js +4 -1
- package/package.json +1 -1
- package/skill/short-drama/SKILL.md +305 -0
- package/skill/short-drama/examples/night-shift/episodes/E01.json +1883 -0
- package/skill/short-drama/examples/night-shift/project.json +774 -0
- package/skill/short-drama/reference/canvas-recipes.md +199 -0
- package/skill/short-drama/reference/craft-motion.md +116 -0
- package/skill/short-drama/reference/craft-shots.md +177 -0
- package/skill/short-drama/reference/craft-story.md +188 -0
- package/skill/short-drama/reference/dialogue-craft.md +120 -0
- package/skill/short-drama/reference/schema.md +435 -0
- package/skill/short-drama/reference/stage-art.md +56 -0
- package/skill/short-drama/reference/stage-characters.md +206 -0
- package/skill/short-drama/reference/stage-outline.md +103 -0
- package/skill/short-drama/reference/stage-script.md +113 -0
- package/skill/short-drama/reference/stage-storyboard.md +465 -0
- package/skill/short-drama/reference/workspace-layout.md +118 -0
- package/skill/short-drama/scripts/selftest.mjs +1411 -0
- package/skill/short-drama/scripts/short-drama.mjs +3371 -0
- package/skill/wji/SKILL.md +97 -22
- package/skill/wji/reference/canvas-recipes.md +16 -2
- package/skill/wji/reference/commands.md +15 -6
package/README.md
CHANGED
|
@@ -165,7 +165,9 @@ wji canvas gen <node> --yes --wait # spends credits
|
|
|
165
165
|
```
|
|
166
166
|
|
|
167
167
|
Changes appear live in the open canvas. Pass `--canvas <id>` to work on a
|
|
168
|
-
canvas that is not linked (`wji canvas list`)
|
|
168
|
+
canvas that is not linked (`wji canvas list`); `wji canvas create <name>` adds
|
|
169
|
+
an empty canvas, `wji canvas add image --asset <id>` shows an existing asset
|
|
170
|
+
and `wji canvas add dataset --dataset <id>` places a dataset card.
|
|
169
171
|
|
|
170
172
|
## Skills
|
|
171
173
|
|
|
@@ -240,3 +242,10 @@ cp -r "$(npm root -g)/@wji.ai/cli/skill/wji" ~/.claude/skills/wji
|
|
|
240
242
|
|
|
241
243
|
From a source checkout: `cp -r skill/wji ~/.claude/skills/wji`. Re-copy after
|
|
242
244
|
upgrading the CLI so the skill matches the commands.
|
|
245
|
+
|
|
246
|
+
`skill/short-drama/` is a second skill built on top of it: an AI short-drama
|
|
247
|
+
pipeline (outline → characters → scenes/props → script → storyboard) whose
|
|
248
|
+
stages are checked by `skill/short-drama/scripts/short-drama.mjs validate`,
|
|
249
|
+
with the data stored as workspace datasets and the work laid out on canvases.
|
|
250
|
+
Install it the same way (`cp -r skill/short-drama ~/.claude/skills/short-drama`)
|
|
251
|
+
next to `wji`; `node skill/short-drama/scripts/selftest.mjs` runs its self-test.
|
package/dist/commands/canvas.js
CHANGED
|
@@ -23,7 +23,8 @@ const NODE_PREFIX = 'canvas/node/';
|
|
|
23
23
|
const EDGE_MIMETYPE = 'canvas/edge';
|
|
24
24
|
const CANVAS_MIMETYPE = 'workspace-v2/canvas';
|
|
25
25
|
const CANVASES_MIMETYPE = 'workspace-v2/canvases';
|
|
26
|
-
const
|
|
26
|
+
const DATASET_MIMETYPE = 'workspace/dataset';
|
|
27
|
+
const CREATABLE_KINDS = ['text', 'title', 'comment', 'image', 'video', 'audio', 'dataset'];
|
|
27
28
|
const MEDIA_KINDS = new Set(['image', 'video', 'audio']);
|
|
28
29
|
const TEXT_KINDS = new Set(['text', 'title', 'comment']);
|
|
29
30
|
/** Default names the canvas UI shows localized. */
|
|
@@ -34,6 +35,12 @@ const DEFAULT_NAMES = {
|
|
|
34
35
|
image: '图片',
|
|
35
36
|
video: '视频',
|
|
36
37
|
audio: '音频',
|
|
38
|
+
dataset: '数据集',
|
|
39
|
+
};
|
|
40
|
+
/** What the web writes into a brand-new canvas folder (CanvasAppFileSystem.createCanvas). */
|
|
41
|
+
const INITIAL_CANVAS_CONTENT = {
|
|
42
|
+
data: { viewport: { x: 0, y: 0, zoom: 1 }, backgroundMode: 'dots' },
|
|
43
|
+
metadata: {},
|
|
37
44
|
};
|
|
38
45
|
/** The canvas lays media out on a 250px short side; text nodes are wider. */
|
|
39
46
|
const MEDIA_BASE = 250;
|
|
@@ -105,6 +112,19 @@ async function loadCanvas(client, canvasId) {
|
|
|
105
112
|
assets,
|
|
106
113
|
};
|
|
107
114
|
}
|
|
115
|
+
/** The `canvases` folder of every InfiniteCanvas app in a workspace. */
|
|
116
|
+
async function canvasesFolders(client, wsId) {
|
|
117
|
+
const ws = await client.get(`/workspace/list/${wsId}`);
|
|
118
|
+
const apps = (ws.apps || []).filter((a) => a.app === 'InfiniteCanvas' || a.type === 'INFINITE_CANVAS');
|
|
119
|
+
const out = [];
|
|
120
|
+
for (const app of apps) {
|
|
121
|
+
const tree = await client.get(`/workspace/list/${wsId}/fs/${app.id}`);
|
|
122
|
+
const folder = (tree?.children || []).find((n) => n.mimetype === CANVASES_MIMETYPE);
|
|
123
|
+
if (folder)
|
|
124
|
+
out.push({ appId: String(app.id), folderId: String(folder.id) });
|
|
125
|
+
}
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
108
128
|
function requireNode(canvas, id) {
|
|
109
129
|
const node = canvas.byId.get(id);
|
|
110
130
|
if (!node)
|
|
@@ -144,6 +164,9 @@ function describe(canvas, node) {
|
|
|
144
164
|
assetId: node.file,
|
|
145
165
|
url: asset?.url || undefined,
|
|
146
166
|
assetType: asset?.type,
|
|
167
|
+
dataset: node.kind === 'dataset' && node.content.datasetId
|
|
168
|
+
? { id: String(node.content.datasetId), name: node.content.datasetName, type: node.content.datasetType, folder: node.content.datasetParentName }
|
|
169
|
+
: undefined,
|
|
147
170
|
status: generate?.historyId ? (pending ? 'generating' : asset?.error ? 'failed' : asset?.url ? 'done' : 'pending') : undefined,
|
|
148
171
|
error: asset?.error?.message || asset?.error || undefined,
|
|
149
172
|
generate,
|
|
@@ -152,12 +175,16 @@ function describe(canvas, node) {
|
|
|
152
175
|
};
|
|
153
176
|
}
|
|
154
177
|
// ── models ──────────────────────────────────────────────────────────────────
|
|
178
|
+
/** An audio node is fed by either an `audio` or a `tts` model — the backend tags voice models `tts`. */
|
|
179
|
+
const KIND_FEATURES = { audio: ['audio', 'tts'] };
|
|
155
180
|
async function findModel(client, kind, name, workspaceId) {
|
|
156
|
-
const
|
|
181
|
+
const features = KIND_FEATURES[kind] ?? [kind];
|
|
182
|
+
const rows = (await Promise.all(features.map((f) => fetchModels(client, { features: [f], workspace: workspaceId })))).flat();
|
|
157
183
|
const usable = rows.filter((m) => m.enabled !== false);
|
|
158
184
|
const model = usable.find((m) => m.nickname === name) ?? usable.find((m) => m.name === name);
|
|
159
185
|
if (!model) {
|
|
160
|
-
|
|
186
|
+
const hint = features.map((f) => `--feature ${f}`).join(' / ');
|
|
187
|
+
throw new Error(`No ${kind} model "${name}". See \`wji models list ${hint}\`.`);
|
|
161
188
|
}
|
|
162
189
|
return model;
|
|
163
190
|
}
|
|
@@ -243,6 +270,8 @@ function applyModelDefaults(model, kind, generate, opts) {
|
|
|
243
270
|
// ── writes ──────────────────────────────────────────────────────────────────
|
|
244
271
|
/** Short side 250, like the canvas; text-ish nodes use a typical card size. */
|
|
245
272
|
function estimateSize(canvas, node) {
|
|
273
|
+
if (node.kind === 'dataset')
|
|
274
|
+
return { width: 480, height: 220 };
|
|
246
275
|
if (!MEDIA_KINDS.has(node.kind))
|
|
247
276
|
return { width: 320, height: 200 };
|
|
248
277
|
const ratioText = node.content.mode === 'generate' ? node.content.generate?.ratio : undefined;
|
|
@@ -335,6 +364,8 @@ async function patchNode(client, node, body) {
|
|
|
335
364
|
function summary(n) {
|
|
336
365
|
if (n.text !== undefined)
|
|
337
366
|
return String(n.text).replace(/\s+/g, ' ');
|
|
367
|
+
if (n.dataset)
|
|
368
|
+
return [n.dataset.type, n.dataset.name, color.dim(n.dataset.id)].filter(Boolean).join(' ');
|
|
338
369
|
if (n.mode === 'generate') {
|
|
339
370
|
const g = n.generate || {};
|
|
340
371
|
const state = n.status === 'done' ? color.green('done') : n.status === 'failed' ? color.red('failed') : n.status ? color.yellow(n.status) : '';
|
|
@@ -361,6 +392,7 @@ function printNode(n) {
|
|
|
361
392
|
['name', n.name],
|
|
362
393
|
['position', n.position && `${Math.round(n.position.x)},${Math.round(n.position.y)}`],
|
|
363
394
|
['text', n.text],
|
|
395
|
+
['dataset', n.dataset && `${n.dataset.type || 'dataset'} "${n.dataset.name || ''}" ${color.dim(n.dataset.id)}${n.dataset.folder ? ` in ${n.dataset.folder}` : ''}`],
|
|
364
396
|
['asset', n.assetId],
|
|
365
397
|
['url', n.url],
|
|
366
398
|
['status', n.status],
|
|
@@ -603,17 +635,11 @@ export function canvasCommand(globals) {
|
|
|
603
635
|
const c = client();
|
|
604
636
|
const linked = opts.workspace ? null : await linkedState(c);
|
|
605
637
|
const wsId = opts.workspace || linked?.workspaceId || c.requireWorkspace();
|
|
606
|
-
const ws = await c.get(`/workspace/list/${wsId}`);
|
|
607
|
-
const apps = (ws.apps || []).filter((a) => a.app === 'InfiniteCanvas' || a.type === 'INFINITE_CANVAS');
|
|
608
638
|
const rows = [];
|
|
609
|
-
for (const
|
|
610
|
-
const
|
|
611
|
-
const folder = (tree?.children || []).find((n) => n.mimetype === CANVASES_MIMETYPE);
|
|
612
|
-
if (!folder)
|
|
613
|
-
continue;
|
|
614
|
-
const canvases = toList(await c.getFresh(`/fsnode/list/${folder.id}/children`, { limit: -1 }));
|
|
639
|
+
for (const { appId, folderId } of await canvasesFolders(c, wsId)) {
|
|
640
|
+
const canvases = toList(await c.getFresh(`/fsnode/list/${folderId}/children`, { limit: -1 }));
|
|
615
641
|
for (const it of canvases.filter((n) => n.mimetype === CANVAS_MIMETYPE)) {
|
|
616
|
-
rows.push({ id: it.id, name: it.name, app:
|
|
642
|
+
rows.push({ id: it.id, name: it.name, app: appId, linked: it.id === linked?.canvasId, updatedAt: it.updatedAt });
|
|
617
643
|
}
|
|
618
644
|
}
|
|
619
645
|
render(rows, () => table(rows, [
|
|
@@ -623,6 +649,36 @@ export function canvasCommand(globals) {
|
|
|
623
649
|
{ header: 'app', value: (r) => r.app },
|
|
624
650
|
]));
|
|
625
651
|
});
|
|
652
|
+
canvas
|
|
653
|
+
.command('create <name>')
|
|
654
|
+
.description('Create an empty canvas in a workspace (its InfiniteCanvas app)')
|
|
655
|
+
.option('-w, --workspace <id>', 'workspace (defaults to the linked one, then the profile default)')
|
|
656
|
+
.option('--app <appId>', 'InfiniteCanvas app when the workspace has several')
|
|
657
|
+
.action(async (name, opts) => {
|
|
658
|
+
const c = client();
|
|
659
|
+
const linked = opts.workspace ? null : await linkedState(c);
|
|
660
|
+
const wsId = opts.workspace || linked?.workspaceId || c.requireWorkspace();
|
|
661
|
+
const folders = await canvasesFolders(c, wsId);
|
|
662
|
+
if (!folders.length)
|
|
663
|
+
throw new Error(`Workspace ${wsId} has no InfiniteCanvas app. Add one with \`wji ws app add canvas\`.`);
|
|
664
|
+
const target = opts.app ? folders.find((f) => f.appId === opts.app) : folders[0];
|
|
665
|
+
if (!target)
|
|
666
|
+
throw new Error(`No InfiniteCanvas app ${opts.app} in workspace ${wsId}`);
|
|
667
|
+
if (!opts.app && folders.length > 1)
|
|
668
|
+
warn(`Several InfiniteCanvas apps — using ${target.appId}; pass --app to choose`);
|
|
669
|
+
const raw = await c.post('/fsnode/create', {
|
|
670
|
+
name,
|
|
671
|
+
type: 'folder',
|
|
672
|
+
mimetype: CANVAS_MIMETYPE,
|
|
673
|
+
parent: target.folderId,
|
|
674
|
+
content: INITIAL_CANVAS_CONTENT,
|
|
675
|
+
});
|
|
676
|
+
const out = { id: raw.id, name: raw.name, app: target.appId, workspaceId: wsId };
|
|
677
|
+
render(out, () => {
|
|
678
|
+
success(`Created canvas "${out.name}" ${color.dim(out.id)}`);
|
|
679
|
+
info(color.dim(`Target it with --canvas ${out.id}, or open it in the browser and press link.`));
|
|
680
|
+
});
|
|
681
|
+
});
|
|
626
682
|
canvas
|
|
627
683
|
.command('nodes')
|
|
628
684
|
.description('Nodes with references and a content summary (* = generate node)')
|
|
@@ -671,6 +727,7 @@ export function canvasCommand(globals) {
|
|
|
671
727
|
.option('-t, --text <text>', 'text for text/title/comment (- reads stdin)')
|
|
672
728
|
.option('--text-file <path>', 'read the text from a file')
|
|
673
729
|
.option('--asset <assetId>', 'show an existing asset (display node)')
|
|
730
|
+
.option('--dataset <fsnodeId>', 'for `add dataset`: the workspace dataset node to show on the canvas')
|
|
674
731
|
.option('--from <nodeIds...>', 'connect these nodes in as references, in order (@image1, @image2…)')
|
|
675
732
|
.option('--at <x,y>', 'position; default is right of the --from nodes')).action(async (kind, opts) => {
|
|
676
733
|
if (!CREATABLE_KINDS.includes(kind)) {
|
|
@@ -694,6 +751,28 @@ export function canvasCommand(globals) {
|
|
|
694
751
|
throw new Error(`${kind} nodes need --text`);
|
|
695
752
|
content.text = text;
|
|
696
753
|
}
|
|
754
|
+
else if (kind === 'dataset') {
|
|
755
|
+
// A dataset node is a card bound to a workspace dataset (CanvasAppFileSystem.createDatasetNodeFromDataset).
|
|
756
|
+
if (!opts.dataset)
|
|
757
|
+
throw new Error('dataset nodes need --dataset <fsnodeId>');
|
|
758
|
+
if (from.length)
|
|
759
|
+
throw new Error('dataset nodes take no references; connect them out to a generate node instead');
|
|
760
|
+
const ds = await c.getFresh(`/fsnode/list/${opts.dataset}`);
|
|
761
|
+
if (ds?.mimetype !== DATASET_MIMETYPE) {
|
|
762
|
+
throw new Error(`${opts.dataset} is not a dataset (mimetype ${ds?.mimetype || 'none'})`);
|
|
763
|
+
}
|
|
764
|
+
const parent = ds.parent ? await c.get(`/fsnode/list/${idOf(ds.parent)}`).catch(() => null) : null;
|
|
765
|
+
const dsName = ds.nickname || ds.name || DEFAULT_NAMES.dataset;
|
|
766
|
+
if (!opts.name)
|
|
767
|
+
body.name = dsName;
|
|
768
|
+
Object.assign(content, {
|
|
769
|
+
datasetId: String(ds.id),
|
|
770
|
+
datasetName: dsName,
|
|
771
|
+
datasetType: String(ds.content?.type || ''),
|
|
772
|
+
datasetParentName: parent ? parent.nickname || parent.name || '' : '',
|
|
773
|
+
description: String(ds.content?.description || ''),
|
|
774
|
+
});
|
|
775
|
+
}
|
|
697
776
|
else {
|
|
698
777
|
const { patch, explicit } = await generateOptions(opts);
|
|
699
778
|
const wantsGenerate = Object.keys(patch).length > 0;
|
package/dist/commands/fsnode.js
CHANGED
|
@@ -3,6 +3,15 @@ import { WjiClient } from '../core/client.js';
|
|
|
3
3
|
import { confirm, parseKeyValues, readJson, readValue } from '../core/prompt.js';
|
|
4
4
|
import { color, detail, info, relativeTime, render, success, table, truncate } from '../core/output.js';
|
|
5
5
|
import { printTree } from './workspace.js';
|
|
6
|
+
/** Nested plain objects merge; arrays and scalars are replaced. */
|
|
7
|
+
function deepMerge(base, patch) {
|
|
8
|
+
const out = { ...base };
|
|
9
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
10
|
+
const isObject = (v) => v && typeof v === 'object' && !Array.isArray(v);
|
|
11
|
+
out[key] = isObject(value) && isObject(out[key]) ? deepMerge(out[key], value) : value;
|
|
12
|
+
}
|
|
13
|
+
return out;
|
|
14
|
+
}
|
|
6
15
|
export function fsCommand(globals) {
|
|
7
16
|
const fs = new Command('fs')
|
|
8
17
|
.aliases(['fsnode'])
|
|
@@ -106,7 +115,9 @@ export function fsCommand(globals) {
|
|
|
106
115
|
.option('--icon <assetId>', 'set the icon asset')
|
|
107
116
|
.option('--asset <assetId>', 'set the attached file asset (use "null" to clear)')
|
|
108
117
|
.option('--sort <n>', 'sort order')
|
|
109
|
-
.option('--set <key=value...>', 'arbitrary field, dotted keys allowed')
|
|
118
|
+
.option('--set <key=value...>', 'arbitrary top-level field, dotted keys allowed (content.* would REPLACE content — use --merge)')
|
|
119
|
+
.option('--merge <key=value...>', 'set fields inside content, merged with the current content; values accept @file')
|
|
120
|
+
.option('--add-images <assetId...>', 'append to the image asset list instead of replacing it')
|
|
110
121
|
.action(async (id, opts) => {
|
|
111
122
|
const client = new WjiClient(globals());
|
|
112
123
|
const body = {
|
|
@@ -120,6 +131,24 @@ export function fsCommand(globals) {
|
|
|
120
131
|
sort: opts.sort != null ? Number(opts.sort) : undefined,
|
|
121
132
|
...parseKeyValues(opts.set),
|
|
122
133
|
};
|
|
134
|
+
if (opts.merge?.length || opts.addImages?.length) {
|
|
135
|
+
// PATCH replaces `content` and `images` whole, so read first and merge.
|
|
136
|
+
const node = await client.getFresh(`/fsnode/list/${id}`);
|
|
137
|
+
if (opts.merge?.length) {
|
|
138
|
+
const pairs = [];
|
|
139
|
+
for (const pair of opts.merge) {
|
|
140
|
+
const index = pair.indexOf('=');
|
|
141
|
+
if (index === -1)
|
|
142
|
+
throw new Error(`Expected key=value, got "${pair}"`);
|
|
143
|
+
pairs.push(`${pair.slice(0, index)}=${await readValue(pair.slice(index + 1))}`);
|
|
144
|
+
}
|
|
145
|
+
body.content = deepMerge(body.content ?? node.content ?? {}, parseKeyValues(pairs));
|
|
146
|
+
}
|
|
147
|
+
if (opts.addImages?.length) {
|
|
148
|
+
const current = (opts.images ?? node.images ?? []).map((it) => (typeof it === 'string' ? it : it.id));
|
|
149
|
+
body.images = [...current, ...opts.addImages.filter((it) => !current.includes(it))];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
123
152
|
const updated = await client.patch(`/fsnode/list/${id}`, body);
|
|
124
153
|
render(updated, (n) => success(`Updated ${n?.id || id}`));
|
|
125
154
|
});
|
package/dist/commands/gen.js
CHANGED
|
@@ -2,7 +2,6 @@ import { Command } from 'commander';
|
|
|
2
2
|
import { WjiClient } from '../core/client.js';
|
|
3
3
|
import { confirm, parseKeyValues, readJson, readValue } from '../core/prompt.js';
|
|
4
4
|
import { color, detail, info, isJsonMode, printJson, render, success, table, truncate, warn } from '../core/output.js';
|
|
5
|
-
const PENDING = 0;
|
|
6
5
|
const READY = 1;
|
|
7
6
|
const CANCELLED = -1;
|
|
8
7
|
/** `--ref abc123`, `--ref abc123:hero`, `--ref abc123:hero:0.6` */
|
|
@@ -81,22 +80,9 @@ export function reportHistory(history, assets) {
|
|
|
81
80
|
]);
|
|
82
81
|
}
|
|
83
82
|
async function submit(client, kind, body, opts) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const result = await client.post(path, body);
|
|
88
|
-
if (!opts.history) {
|
|
89
|
-
// Raw mode returns Asset[] directly; there is no history to poll.
|
|
90
|
-
return render(result, (assets) => {
|
|
91
|
-
if (!Array.isArray(assets))
|
|
92
|
-
return detail(Object.entries(assets || {}));
|
|
93
|
-
table(assets, [
|
|
94
|
-
{ header: 'assetId', value: (a) => a.id },
|
|
95
|
-
{ header: 'state', value: (a) => (a.status === PENDING ? 'pending' : 'ready') },
|
|
96
|
-
{ header: 'url', value: (a) => a.url },
|
|
97
|
-
]);
|
|
98
|
-
});
|
|
99
|
-
}
|
|
83
|
+
// Always the history endpoint: the raw one silently drops fsnode, scope,
|
|
84
|
+
// scopeId and tags, and returns no id to poll with.
|
|
85
|
+
const result = await client.post(`/ai/assets/history/${kind}/generations`, body);
|
|
100
86
|
if (!opts.wait) {
|
|
101
87
|
if (isJsonMode())
|
|
102
88
|
return printJson(result);
|
|
@@ -121,14 +107,13 @@ function withCommonOptions(cmd) {
|
|
|
121
107
|
.option('-p, --prompt <text>', 'prompt text (@file / - for stdin)')
|
|
122
108
|
.option('-n, --n <count>', 'number of outputs', '1')
|
|
123
109
|
.option('-w, --workspace <id>', 'workspace to bill and file the result under')
|
|
124
|
-
.option('--fsnode <id>', '
|
|
110
|
+
.option('--fsnode <id>', 'file the history entry under an fsnode (does not add the asset to its images)')
|
|
125
111
|
.option('--scope <scope>', 'history scope, e.g. workspace | canvas')
|
|
126
112
|
.option('--scope-id <id>', 'history scope id')
|
|
127
113
|
.option('-r, --ratio <ratio>', 'aspect ratio, e.g. 16:9')
|
|
128
114
|
.option('--tags <tag...>', 'tags for the history entry')
|
|
129
115
|
.option('--values <json>', 'model-specific form values (@file / - supported)')
|
|
130
116
|
.option('--set <key=value...>', 'model-specific form values as key=value')
|
|
131
|
-
.option('--no-history', 'call the raw endpoint and return assets instead of a history')
|
|
132
117
|
.option('--wait', 'block until the task finishes')
|
|
133
118
|
.option('--timeout <seconds>', 'wait timeout', '900')
|
|
134
119
|
.option('--interval <seconds>', 'poll interval', '5');
|
|
@@ -16,6 +16,8 @@ const APPS = {
|
|
|
16
16
|
batch: 'Batch',
|
|
17
17
|
digitalhost: 'DigitalHost',
|
|
18
18
|
};
|
|
19
|
+
/** Apps every new workspace gets unless `-a` or `--no-apps` says otherwise — what the drama and canvas workflows need. */
|
|
20
|
+
const DEFAULT_APPS = ['dataset', 'datasetimagegeneration', 'canvas'];
|
|
19
21
|
const APP_TYPES = {
|
|
20
22
|
Dataset: 'DATASET',
|
|
21
23
|
DatasetImageGeneration: 'DATASET_IMAGE_GENERATION',
|
|
@@ -103,10 +105,12 @@ export function workspaceCommand(globals) {
|
|
|
103
105
|
.option('-r, --aspect-ratio <ratio>', '16:9 | 9:16 | 21:9', '16:9')
|
|
104
106
|
.option('-l, --language <lang>', 'dialogue language', 'auto')
|
|
105
107
|
.option('-t, --type <type>', 'workspaceType')
|
|
106
|
-
.option('-a, --app <app...>',
|
|
108
|
+
.option('-a, --app <app...>', `apps to create (default: ${DEFAULT_APPS.join(' ')})`)
|
|
109
|
+
.option('--no-apps', 'create an empty workspace with no apps')
|
|
107
110
|
.action(async (name, opts) => {
|
|
108
111
|
const client = new WjiClient(globals());
|
|
109
|
-
const
|
|
112
|
+
const tokens = opts.app?.length ? opts.app : opts.apps ? DEFAULT_APPS : [];
|
|
113
|
+
const apps = tokens.map((token) => {
|
|
110
114
|
const { app, type } = resolveApp(token);
|
|
111
115
|
return { app, type, name: app };
|
|
112
116
|
});
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
2
3
|
import { Command } from 'commander';
|
|
3
4
|
import { ApiError } from './core/client.js';
|
|
4
5
|
import { color, setJsonMode } from './core/output.js';
|
|
@@ -15,7 +16,9 @@ import { profileCommand } from './commands/profile.js';
|
|
|
15
16
|
import { presetCommand } from './commands/preset.js';
|
|
16
17
|
import { skillCommand } from './commands/skill.js';
|
|
17
18
|
import { canvasCommand } from './commands/canvas.js';
|
|
18
|
-
|
|
19
|
+
// Read from package.json so `wji --version` cannot drift from the published
|
|
20
|
+
// version — the skill compares the two to detect a stale install.
|
|
21
|
+
const VERSION = createRequire(import.meta.url)('../package.json').version;
|
|
19
22
|
const program = new Command();
|
|
20
23
|
program
|
|
21
24
|
.name('wji')
|