@wji.ai/cli 0.6.1 → 0.7.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 +23 -3
- package/dist/index.js +7162 -76
- package/package.json +6 -9
- package/skill/backreel/SKILL.md +171 -0
- package/skill/backreel/reference/crowdfunding.md +82 -0
- package/skill/backreel/reference/enquiries.md +80 -0
- package/skill/backreel/reference/episodes.md +104 -0
- package/skill/backreel/reference/images.md +76 -0
- package/skill/npc-character/SKILL.md +111 -0
- package/skill/npc-character/examples/nagi.character.md +117 -0
- package/skill/npc-character/examples/nagi.stage.md +34 -0
- package/skill/npc-character/examples/yuzu.character.md +110 -0
- package/skill/npc-character/examples/yuzu.stage.md +30 -0
- package/skill/npc-character/reference/character-craft.md +153 -0
- package/skill/npc-character/reference/document-format.md +151 -0
- package/skill/npc-character/reference/interview.md +44 -0
- package/skill/npc-character/reference/life.md +92 -0
- package/skill/npc-character/reference/runtime.md +95 -0
- package/skill/npc-character/reference/stage-craft.md +109 -0
- package/skill/npc-character/reference/visual-kit.md +81 -0
- package/skill/npc-character/reference/voice.md +101 -0
- package/skill/wji/SKILL.md +1 -1
- package/skill/wji/reference/commands.md +25 -5
- package/dist/commands/agent.js +0 -294
- package/dist/commands/asset.js +0 -241
- package/dist/commands/auth.js +0 -216
- package/dist/commands/backreel.js +0 -320
- package/dist/commands/canvas.js +0 -1706
- package/dist/commands/class.js +0 -395
- package/dist/commands/config.js +0 -113
- package/dist/commands/crew.js +0 -205
- package/dist/commands/fsnode.js +0 -288
- package/dist/commands/gen.js +0 -352
- package/dist/commands/model.js +0 -136
- package/dist/commands/npc.js +0 -413
- package/dist/commands/org.js +0 -85
- package/dist/commands/preset.js +0 -176
- package/dist/commands/profile.js +0 -129
- package/dist/commands/skill.js +0 -272
- package/dist/commands/workspace.js +0 -470
- package/dist/core/archive.js +0 -95
- package/dist/core/client.js +0 -252
- package/dist/core/config.js +0 -95
- package/dist/core/crew.js +0 -546
- package/dist/core/members.js +0 -54
- package/dist/core/npc.js +0 -108
- package/dist/core/output.js +0 -174
- package/dist/core/prompt.js +0 -118
- package/dist/core/zip.js +0 -87
package/dist/commands/npc.js
DELETED
|
@@ -1,413 +0,0 @@
|
|
|
1
|
-
import { writeFile } from 'node:fs/promises';
|
|
2
|
-
import { Command } from 'commander';
|
|
3
|
-
import { isObjectId, WjiClient } from '../core/client.js';
|
|
4
|
-
import { confirm, parseKeyValues, readJson, readValue } from '../core/prompt.js';
|
|
5
|
-
import { color, detail, info, isJsonMode, printJson, render, relativeTime, success, table, truncate, warn } from '../core/output.js';
|
|
6
|
-
import { NPC_JOB_TYPES, NPC_STATUSES, NPC_VISIBILITIES, RESIDENTS, npcLabel, parseJob, parsePositions, resolveNpc, updateJobs, } from '../core/npc.js';
|
|
7
|
-
const TRANSFERS = '/npc-city/transfers';
|
|
8
|
-
export function npcCommand(globals) {
|
|
9
|
-
const npc = new Command('npc')
|
|
10
|
-
.aliases(['npc-city'])
|
|
11
|
-
.description('Manage your NPC City residents (NPCs): profile, jobs, soul, character doc, transfers');
|
|
12
|
-
// ── residents ──────────────────────────────────────────────────────────
|
|
13
|
-
npc
|
|
14
|
-
.command('list', { isDefault: true })
|
|
15
|
-
.description('Your NPCs (org NPCs with --org); --public for the public directory')
|
|
16
|
-
.option('--public', 'list the public directory instead of yours')
|
|
17
|
-
.option('-k, --keyword <text>', 'search text')
|
|
18
|
-
.option('--status <status>', NPC_STATUSES.join(' | '))
|
|
19
|
-
.option('--visibility <v>', NPC_VISIBILITIES.join(' | '))
|
|
20
|
-
.option('--page <n>', 'page number', '1')
|
|
21
|
-
.option('--limit <n>', 'page size', '20')
|
|
22
|
-
.action(async (opts) => {
|
|
23
|
-
const client = new WjiClient(globals());
|
|
24
|
-
const result = await client.get(opts.public ? RESIDENTS : `${RESIDENTS}/mine`, {
|
|
25
|
-
keyword: opts.keyword,
|
|
26
|
-
status: opts.status,
|
|
27
|
-
visibility: opts.visibility,
|
|
28
|
-
page: Number(opts.page),
|
|
29
|
-
limit: Number(opts.limit),
|
|
30
|
-
});
|
|
31
|
-
render(result, (r) => {
|
|
32
|
-
table(r?.data || [], [
|
|
33
|
-
{ header: 'id', value: (n) => n.id },
|
|
34
|
-
{ header: 'name', value: (n) => truncate(n.name, 20) },
|
|
35
|
-
{ header: 'handle', value: (n) => n.handle },
|
|
36
|
-
{ header: 'role', value: (n) => truncate(n.role, 16) },
|
|
37
|
-
{ header: 'genre', value: (n) => truncate(n.genre, 16) },
|
|
38
|
-
{ header: 'status', value: (n) => n.status },
|
|
39
|
-
{ header: 'vis', value: (n) => n.visibility },
|
|
40
|
-
{ header: 'jobs', value: (n) => (n.jobs || []).length },
|
|
41
|
-
{ header: 'updated', value: (n) => relativeTime(n.updatedAt) },
|
|
42
|
-
]);
|
|
43
|
-
pageFooter(r);
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
npc
|
|
47
|
-
.command('get <npc>')
|
|
48
|
-
.description('Show an NPC and its jobs (id, @handle or name)')
|
|
49
|
-
.action(async (token) => {
|
|
50
|
-
const client = new WjiClient(globals());
|
|
51
|
-
const r = await resolveNpc(client, token);
|
|
52
|
-
render(r, () => printNpc(r));
|
|
53
|
-
});
|
|
54
|
-
residentOptions(npc
|
|
55
|
-
.command('create <name>')
|
|
56
|
-
.description('Create an NPC (private until you publish it). Needs NPC City spawn permission.')
|
|
57
|
-
.requiredOption('--role <role>', 'role, e.g. Singer')
|
|
58
|
-
.requiredOption('--genre <genre>', 'genre, e.g. City pop')).action(async (name, opts) => {
|
|
59
|
-
const client = new WjiClient(globals());
|
|
60
|
-
const body = await residentBody(client, opts);
|
|
61
|
-
if (body.handle)
|
|
62
|
-
await assertHandle(client, body.handle);
|
|
63
|
-
const created = await client.post(RESIDENTS, { name, ...body });
|
|
64
|
-
render(created, (r) => success(`Created ${npcLabel(r)} — ${r.id} (${r.visibility})`));
|
|
65
|
-
});
|
|
66
|
-
residentOptions(npc
|
|
67
|
-
.command('set <npc>')
|
|
68
|
-
.description('Update NPC fields; only the flags you pass change')
|
|
69
|
-
.option('-n, --name <name>', 'rename')
|
|
70
|
-
.option('--role <role>', 'role')
|
|
71
|
-
.option('--genre <genre>', 'genre')).action(async (token, opts) => {
|
|
72
|
-
const client = new WjiClient(globals());
|
|
73
|
-
const current = await resolveNpc(client, token);
|
|
74
|
-
const body = await residentBody(client, opts, current);
|
|
75
|
-
if (opts.name)
|
|
76
|
-
body.name = opts.name;
|
|
77
|
-
if (!Object.keys(body).length)
|
|
78
|
-
throw new Error('Nothing to change — pass at least one field flag');
|
|
79
|
-
if (body.handle)
|
|
80
|
-
await assertHandle(client, body.handle, current.id);
|
|
81
|
-
const updated = await client.patch(`${RESIDENTS}/${current.id}`, body);
|
|
82
|
-
render(updated, (r) => success(`Updated ${npcLabel(r)}: ${Object.keys(body).join(', ')}`));
|
|
83
|
-
});
|
|
84
|
-
for (const [cmd, visibility] of [['publish', 'public'], ['unpublish', 'private']]) {
|
|
85
|
-
npc
|
|
86
|
-
.command(`${cmd} <npc>`)
|
|
87
|
-
.description(`Make an NPC ${visibility}`)
|
|
88
|
-
.action(async (token) => {
|
|
89
|
-
const client = new WjiClient(globals());
|
|
90
|
-
const r = await resolveNpc(client, token);
|
|
91
|
-
const updated = await client.patch(`${RESIDENTS}/${r.id}`, { visibility });
|
|
92
|
-
render(updated, (u) => success(`${npcLabel(u)} is now ${u.visibility}`));
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
npc
|
|
96
|
-
.command('rm <npc>')
|
|
97
|
-
.description('Delete an NPC')
|
|
98
|
-
.option('-y, --yes', 'skip confirmation')
|
|
99
|
-
.action(async (token, opts) => {
|
|
100
|
-
const client = new WjiClient(globals());
|
|
101
|
-
const r = await resolveNpc(client, token);
|
|
102
|
-
if (!(await confirm(`Delete NPC ${npcLabel(r)} (${r.id})?`, opts.yes)))
|
|
103
|
-
return info('Aborted');
|
|
104
|
-
const result = await client.delete(`${RESIDENTS}/${r.id}`);
|
|
105
|
-
render(result, () => success(`Deleted ${npcLabel(r)}`));
|
|
106
|
-
});
|
|
107
|
-
npc
|
|
108
|
-
.command('handle <handle>')
|
|
109
|
-
.description('Check whether a @handle is free')
|
|
110
|
-
.option('--exclude <npc>', "ignore this NPC's own handle")
|
|
111
|
-
.action(async (handle, opts) => {
|
|
112
|
-
const client = new WjiClient(globals());
|
|
113
|
-
const excludeId = opts.exclude ? (await resolveNpc(client, opts.exclude)).id : undefined;
|
|
114
|
-
const result = await client.get(`${RESIDENTS}/handle-available`, { handle, excludeId });
|
|
115
|
-
render(result, (r) => r.available ? success(`${r.handle} is available`) : warn(`${r.handle} is not available (${r.reason})`));
|
|
116
|
-
});
|
|
117
|
-
// ── jobs (work history) ────────────────────────────────────────────────
|
|
118
|
-
const job = npc.command('job').aliases(['jobs']).description("An NPC's work history: concerts, releases, ads…");
|
|
119
|
-
job
|
|
120
|
-
.command('list <npc>', { isDefault: true })
|
|
121
|
-
.description('Numbered work history')
|
|
122
|
-
.action(async (token) => {
|
|
123
|
-
const client = new WjiClient(globals());
|
|
124
|
-
const r = await resolveNpc(client, token);
|
|
125
|
-
render(r.jobs || [], (jobs) => printJobs(jobs));
|
|
126
|
-
});
|
|
127
|
-
jobOptions(job
|
|
128
|
-
.command('add <npc>')
|
|
129
|
-
.description('Add a job')
|
|
130
|
-
.requiredOption('-t, --type <type>', NPC_JOB_TYPES.join(' | '))
|
|
131
|
-
.requiredOption('--title <text>', 'title')
|
|
132
|
-
.requiredOption('--date <YYYY-MM-DD>', 'date')).action(async (token, opts) => {
|
|
133
|
-
const client = new WjiClient(globals());
|
|
134
|
-
const r = await resolveNpc(client, token);
|
|
135
|
-
const entry = parseJob(opts);
|
|
136
|
-
const updated = await updateJobs(client, r.id, (jobs) => [...jobs, entry]);
|
|
137
|
-
render(updated.jobs, () => success(`Added "${entry.title}" to ${npcLabel(r)} (#${updated.jobs.length})`));
|
|
138
|
-
});
|
|
139
|
-
jobOptions(job
|
|
140
|
-
.command('set <npc> <n>')
|
|
141
|
-
.description('Change job #n (see `npc job list`)')
|
|
142
|
-
.option('-t, --type <type>', NPC_JOB_TYPES.join(' | '))
|
|
143
|
-
.option('--title <text>', 'title')
|
|
144
|
-
.option('--date <YYYY-MM-DD>', 'date')).action(async (token, n, opts) => {
|
|
145
|
-
const client = new WjiClient(globals());
|
|
146
|
-
const r = await resolveNpc(client, token);
|
|
147
|
-
const updated = await updateJobs(client, r.id, (jobs) => {
|
|
148
|
-
const [i] = parsePositions([n], jobs.length);
|
|
149
|
-
jobs[i] = parseJob(opts, jobs[i]);
|
|
150
|
-
return jobs;
|
|
151
|
-
});
|
|
152
|
-
render(updated.jobs, () => success(`Updated job #${n} of ${npcLabel(r)}`));
|
|
153
|
-
});
|
|
154
|
-
job
|
|
155
|
-
.command('rm <npc> <n...>')
|
|
156
|
-
.description('Remove jobs by number')
|
|
157
|
-
.option('-y, --yes', 'skip confirmation')
|
|
158
|
-
.action(async (token, ns, opts) => {
|
|
159
|
-
const client = new WjiClient(globals());
|
|
160
|
-
const r = await resolveNpc(client, token);
|
|
161
|
-
const positions = parsePositions(ns, (r.jobs || []).length);
|
|
162
|
-
const titles = positions.map((i) => `#${i + 1} ${r.jobs[i].title}`).join(', ');
|
|
163
|
-
if (!(await confirm(`Remove ${titles} from ${npcLabel(r)}?`, opts.yes)))
|
|
164
|
-
return info('Aborted');
|
|
165
|
-
// Re-check against a fresh read so a concurrent edit can't shift the numbers.
|
|
166
|
-
const updated = await updateJobs(client, r.id, (jobs) => {
|
|
167
|
-
if (JSON.stringify(jobs) !== JSON.stringify(r.jobs)) {
|
|
168
|
-
throw new Error('The job list changed since it was read — list it again and retry');
|
|
169
|
-
}
|
|
170
|
-
return jobs.filter((_, i) => !positions.includes(i));
|
|
171
|
-
});
|
|
172
|
-
render(updated.jobs, () => success(`Removed ${positions.length} job(s); ${updated.jobs.length} left`));
|
|
173
|
-
});
|
|
174
|
-
// ── soul ───────────────────────────────────────────────────────────────
|
|
175
|
-
const soul = npc.command('soul').description("An NPC's soul config (persona, protocols, voice, assets)");
|
|
176
|
-
soul
|
|
177
|
-
.command('get <npc>', { isDefault: true })
|
|
178
|
-
.description('Print the soul config as JSON')
|
|
179
|
-
.action(async (token) => {
|
|
180
|
-
const client = new WjiClient(globals());
|
|
181
|
-
const r = await resolveNpc(client, token);
|
|
182
|
-
printJson(await client.getFresh(`${RESIDENTS}/${r.id}/soul`));
|
|
183
|
-
});
|
|
184
|
-
soul
|
|
185
|
-
.command('set <npc> <json>')
|
|
186
|
-
.description('Merge top-level soul keys (inline JSON, @file or -); nested objects are replaced whole')
|
|
187
|
-
.action(async (token, json) => {
|
|
188
|
-
const client = new WjiClient(globals());
|
|
189
|
-
const r = await resolveNpc(client, token);
|
|
190
|
-
const soulConfig = await readJson(json);
|
|
191
|
-
if (!soulConfig || typeof soulConfig !== 'object' || Array.isArray(soulConfig)) {
|
|
192
|
-
throw new Error('Soul config must be a JSON object');
|
|
193
|
-
}
|
|
194
|
-
const updated = await client.patch(`${RESIDENTS}/${r.id}/soul`, { soulConfig });
|
|
195
|
-
render(updated, () => success(`Updated soul of ${npcLabel(r)}: ${Object.keys(soulConfig).join(', ')}`));
|
|
196
|
-
});
|
|
197
|
-
// ── character document ────────────────────────────────────────────────
|
|
198
|
-
const doc = npc.command('doc').description('Export / apply the character document (profile + soul in one file)');
|
|
199
|
-
doc
|
|
200
|
-
.command('export <npc>')
|
|
201
|
-
.description('Print or save the character document')
|
|
202
|
-
.option('--format <format>', 'md | json', 'md')
|
|
203
|
-
.option('-o, --out <file>', 'write to a file instead of stdout')
|
|
204
|
-
.action(async (token, opts) => {
|
|
205
|
-
const client = new WjiClient(globals());
|
|
206
|
-
const r = await resolveNpc(client, token);
|
|
207
|
-
const result = await client.getFresh(`${RESIDENTS}/${r.id}/document`, { format: opts.format });
|
|
208
|
-
if (opts.out) {
|
|
209
|
-
await writeFile(opts.out, result.text);
|
|
210
|
-
return render({ file: opts.out, format: result.format }, () => success(`Wrote ${opts.out}`));
|
|
211
|
-
}
|
|
212
|
-
render(result, () => process.stdout.write(result.text.endsWith('\n') ? result.text : `${result.text}\n`));
|
|
213
|
-
});
|
|
214
|
-
doc
|
|
215
|
-
.command('apply <npc> <text>')
|
|
216
|
-
.description('Preview the changes a document makes (@file or -), confirm, then apply')
|
|
217
|
-
.option('--dry-run', 'only show the changes')
|
|
218
|
-
.option('-y, --yes', 'skip confirmation')
|
|
219
|
-
.action(async (token, source, opts) => {
|
|
220
|
-
const client = new WjiClient(globals());
|
|
221
|
-
const r = await resolveNpc(client, token);
|
|
222
|
-
const text = await readValue(source);
|
|
223
|
-
const base = `${RESIDENTS}/${r.id}/document`;
|
|
224
|
-
const preview = await client.post(`${base}/preview`, { text });
|
|
225
|
-
const changes = preview.changes || [];
|
|
226
|
-
if (opts.dryRun || !changes.length) {
|
|
227
|
-
return render(preview, () => {
|
|
228
|
-
printChanges(changes, preview.warnings);
|
|
229
|
-
if (!changes.length)
|
|
230
|
-
info('Nothing to apply');
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
if (!isJsonMode())
|
|
234
|
-
printChanges(changes, preview.warnings);
|
|
235
|
-
if (!(await confirm(`Apply ${changes.length} change(s) to ${npcLabel(r)}?`, opts.yes)))
|
|
236
|
-
return info('Aborted');
|
|
237
|
-
const applied = await client.post(base, { text });
|
|
238
|
-
render(applied, (a) => {
|
|
239
|
-
for (const w of a.warnings || [])
|
|
240
|
-
warn(w);
|
|
241
|
-
success(`Applied ${(a.applied || []).length} change(s) to ${npcLabel(r)}`);
|
|
242
|
-
});
|
|
243
|
-
});
|
|
244
|
-
// ── transfers ─────────────────────────────────────────────────────────
|
|
245
|
-
const transfer = npc
|
|
246
|
-
.command('transfer')
|
|
247
|
-
.description('Hand an NPC to another user; they accept within 7 days');
|
|
248
|
-
transfer
|
|
249
|
-
.command('send <npc>')
|
|
250
|
-
.description('Offer an NPC to another user')
|
|
251
|
-
.requiredOption('--to <user>', 'recipient email or user id')
|
|
252
|
-
.option('-y, --yes', 'skip confirmation')
|
|
253
|
-
.action(async (token, opts) => {
|
|
254
|
-
const client = new WjiClient(globals());
|
|
255
|
-
const r = await resolveNpc(client, token);
|
|
256
|
-
if (!(await confirm(`Offer ${npcLabel(r)} to ${opts.to}? You lose it once they accept.`, opts.yes))) {
|
|
257
|
-
return info('Aborted');
|
|
258
|
-
}
|
|
259
|
-
const target = isObjectId(opts.to) ? { toUserId: opts.to } : { email: opts.to };
|
|
260
|
-
const result = await client.post(`${RESIDENTS}/${r.id}/transfer`, target);
|
|
261
|
-
render(result, (t) => success(`Transfer ${t.id} to ${t.toOwner?.name || opts.to} pending until ${t.expiresAt}`));
|
|
262
|
-
});
|
|
263
|
-
transfer
|
|
264
|
-
.command('list', { isDefault: true })
|
|
265
|
-
.description('Pending transfers, incoming and outgoing')
|
|
266
|
-
.action(async () => {
|
|
267
|
-
const client = new WjiClient(globals());
|
|
268
|
-
const [incoming, outgoing] = await Promise.all([
|
|
269
|
-
client.getFresh(`${TRANSFERS}/incoming`),
|
|
270
|
-
client.getFresh(`${TRANSFERS}/outgoing`),
|
|
271
|
-
]);
|
|
272
|
-
const rows = [
|
|
273
|
-
...(incoming || []).map((t) => ({ ...t, direction: 'in' })),
|
|
274
|
-
...(outgoing || []).map((t) => ({ ...t, direction: 'out' })),
|
|
275
|
-
];
|
|
276
|
-
render(rows, () => table(rows, [
|
|
277
|
-
{ header: 'id', value: (t) => t.id },
|
|
278
|
-
{ header: 'dir', value: (t) => t.direction },
|
|
279
|
-
{ header: 'npc', value: (t) => (t.resident ? npcLabel(t.resident) : null) },
|
|
280
|
-
{ header: 'from', value: (t) => t.fromOwner?.name },
|
|
281
|
-
{ header: 'to', value: (t) => t.toOwner?.name },
|
|
282
|
-
{ header: 'expires', value: (t) => relativeTime(t.expiresAt) },
|
|
283
|
-
]));
|
|
284
|
-
});
|
|
285
|
-
for (const action of ['accept', 'reject', 'cancel']) {
|
|
286
|
-
transfer
|
|
287
|
-
.command(`${action} <transferId>`)
|
|
288
|
-
.description(action === 'cancel' ? 'Withdraw a transfer you sent' : `${action[0].toUpperCase()}${action.slice(1)} a transfer sent to you`)
|
|
289
|
-
.action(async (id) => {
|
|
290
|
-
const client = new WjiClient(globals());
|
|
291
|
-
const result = await client.post(`${TRANSFERS}/${id}/${action}`);
|
|
292
|
-
render(result, (t) => success(`Transfer ${id}: ${t.status}${t.resident ? ` (${npcLabel(t.resident)})` : ''}`));
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
return npc;
|
|
296
|
-
}
|
|
297
|
-
// ── helpers ──────────────────────────────────────────────────────────────
|
|
298
|
-
function residentOptions(cmd) {
|
|
299
|
-
return cmd
|
|
300
|
-
.option('--handle <handle>', '@handle (a-z 0-9 . _ -, 2–32)')
|
|
301
|
-
.option('--tagline <text>', 'one-line tagline')
|
|
302
|
-
.option('--bio <text>', 'bio (@file / - supported)')
|
|
303
|
-
.option('--image <url|assetId>', 'avatar image')
|
|
304
|
-
.option('--hero-image <url|assetId>', 'hero image')
|
|
305
|
-
.option('--color <hex>', 'accent color')
|
|
306
|
-
.option('--status <status>', NPC_STATUSES.join(' | '))
|
|
307
|
-
.option('--visibility <v>', NPC_VISIBILITIES.join(' | '))
|
|
308
|
-
.option('--social <key=url...>', 'ig / yt / tw / web')
|
|
309
|
-
.option('--tag <key=value...>', 'card tags: rating, type, role, gender, tier, language…')
|
|
310
|
-
.option('--set <key=value...>', 'any other field, e.g. liveEnabled=true');
|
|
311
|
-
}
|
|
312
|
-
function jobOptions(cmd) {
|
|
313
|
-
return cmd
|
|
314
|
-
.option('--detail <text>', 'what happened')
|
|
315
|
-
.option('--location <text>', 'where')
|
|
316
|
-
.option('--url <url>', 'link');
|
|
317
|
-
}
|
|
318
|
-
/** The resident fields a create / set carries, from flags. `current` merges socials / tags. */
|
|
319
|
-
async function residentBody(client, opts, current) {
|
|
320
|
-
const body = {};
|
|
321
|
-
if (opts.role)
|
|
322
|
-
body.role = opts.role;
|
|
323
|
-
if (opts.genre)
|
|
324
|
-
body.genre = opts.genre;
|
|
325
|
-
if (opts.handle)
|
|
326
|
-
body.handle = opts.handle;
|
|
327
|
-
if (opts.tagline !== undefined)
|
|
328
|
-
body.tagline = opts.tagline;
|
|
329
|
-
if (opts.bio !== undefined)
|
|
330
|
-
body.bio = await readValue(opts.bio);
|
|
331
|
-
if (opts.image)
|
|
332
|
-
body.image = await imageUrl(client, opts.image);
|
|
333
|
-
if (opts.heroImage)
|
|
334
|
-
body.heroImage = await imageUrl(client, opts.heroImage);
|
|
335
|
-
if (opts.color)
|
|
336
|
-
body.color = opts.color;
|
|
337
|
-
if (opts.status)
|
|
338
|
-
body.status = oneOf(opts.status, NPC_STATUSES, '--status');
|
|
339
|
-
if (opts.visibility)
|
|
340
|
-
body.visibility = oneOf(opts.visibility, NPC_VISIBILITIES, '--visibility');
|
|
341
|
-
if (opts.social)
|
|
342
|
-
body.socials = { ...(current?.socials || {}), ...parseKeyValues(opts.social) };
|
|
343
|
-
if (opts.tag)
|
|
344
|
-
body.tags = { ...(current?.tags || {}), ...parseKeyValues(opts.tag) };
|
|
345
|
-
Object.assign(body, parseKeyValues(opts.set));
|
|
346
|
-
return body;
|
|
347
|
-
}
|
|
348
|
-
/** Residents store image URLs; an asset id is swapped for its ready URL. */
|
|
349
|
-
async function imageUrl(client, value) {
|
|
350
|
-
if (!isObjectId(value))
|
|
351
|
-
return value;
|
|
352
|
-
const [asset] = await client.checkAssetStatus([value]);
|
|
353
|
-
if (!asset?.url)
|
|
354
|
-
throw new Error(`Asset ${value} has no URL yet (missing, not yours, or still pending)`);
|
|
355
|
-
return asset.url;
|
|
356
|
-
}
|
|
357
|
-
async function assertHandle(client, handle, excludeId) {
|
|
358
|
-
const r = await client.get(`${RESIDENTS}/handle-available`, { handle, excludeId });
|
|
359
|
-
if (!r.available)
|
|
360
|
-
throw new Error(`Handle ${r.handle || handle} is not available (${r.reason})`);
|
|
361
|
-
}
|
|
362
|
-
function oneOf(value, allowed, flag) {
|
|
363
|
-
if (!allowed.includes(value))
|
|
364
|
-
throw new Error(`${flag} must be one of ${allowed.join(', ')}`);
|
|
365
|
-
return value;
|
|
366
|
-
}
|
|
367
|
-
function pageFooter(r) {
|
|
368
|
-
const p = r?.pagination;
|
|
369
|
-
if (!p)
|
|
370
|
-
return;
|
|
371
|
-
info(color.dim(`${p.total} total · page ${p.page}${p.hasNext ? ` · more: --page ${p.page + 1}` : ''}`));
|
|
372
|
-
}
|
|
373
|
-
function printNpc(r) {
|
|
374
|
-
detail([
|
|
375
|
-
['id', r.id],
|
|
376
|
-
['name', r.name],
|
|
377
|
-
['handle', r.handle],
|
|
378
|
-
['role', r.role],
|
|
379
|
-
['genre', r.genre],
|
|
380
|
-
['status', r.status],
|
|
381
|
-
['visibility', r.visibility],
|
|
382
|
-
['tagline', r.tagline],
|
|
383
|
-
['bio', truncate(r.bio, 120)],
|
|
384
|
-
['image', r.image],
|
|
385
|
-
['owner', r.owner?.name || r.owner],
|
|
386
|
-
['organization', r.organization?.name || r.organization],
|
|
387
|
-
['tags', r.tags && Object.keys(r.tags).length ? r.tags : null],
|
|
388
|
-
['socials', r.socials && Object.keys(r.socials).length ? r.socials : null],
|
|
389
|
-
['live', r.liveEnabled ? (r.livePublic ? 'enabled, public' : 'enabled') : null],
|
|
390
|
-
['updated', relativeTime(r.updatedAt)],
|
|
391
|
-
]);
|
|
392
|
-
process.stdout.write('\n');
|
|
393
|
-
printJobs(r.jobs || []);
|
|
394
|
-
}
|
|
395
|
-
function printJobs(jobs) {
|
|
396
|
-
table(jobs.map((j, i) => ({ ...j, n: i + 1 })), [
|
|
397
|
-
{ header: '#', value: (j) => j.n },
|
|
398
|
-
{ header: 'date', value: (j) => j.date },
|
|
399
|
-
{ header: 'type', value: (j) => j.type },
|
|
400
|
-
{ header: 'title', value: (j) => truncate(j.title, 32) },
|
|
401
|
-
{ header: 'detail', value: (j) => truncate(j.detail, 32) },
|
|
402
|
-
{ header: 'location', value: (j) => truncate(j.location, 20) },
|
|
403
|
-
]);
|
|
404
|
-
}
|
|
405
|
-
function printChanges(changes, warnings = []) {
|
|
406
|
-
for (const w of warnings)
|
|
407
|
-
warn(w);
|
|
408
|
-
table(changes, [
|
|
409
|
-
{ header: 'path', value: (c) => c.path },
|
|
410
|
-
{ header: 'from', value: (c) => truncate(JSON.stringify(c.from ?? null), 40) },
|
|
411
|
-
{ header: 'to', value: (c) => truncate(JSON.stringify(c.to ?? null), 40) },
|
|
412
|
-
]);
|
|
413
|
-
}
|
package/dist/commands/org.js
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import { WjiClient } from '../core/client.js';
|
|
3
|
-
import { updateProfile } from '../core/config.js';
|
|
4
|
-
import { color, detail, info, render, success, table } from '../core/output.js';
|
|
5
|
-
/**
|
|
6
|
-
* Organization context is a request header (X-Org-Id), not a login. Selecting
|
|
7
|
-
* one here just stores it in the profile; every later call carries it.
|
|
8
|
-
*/
|
|
9
|
-
export function orgCommand(globals) {
|
|
10
|
-
const org = new Command('org').description('Switch organization context (X-Org-Id)');
|
|
11
|
-
org
|
|
12
|
-
.command('list', { isDefault: true })
|
|
13
|
-
.description('List organizations you belong to')
|
|
14
|
-
.action(async () => {
|
|
15
|
-
const client = new WjiClient(globals());
|
|
16
|
-
const memberships = await client.get('/organization/mine');
|
|
17
|
-
render(memberships, (rows) => table(rows || [], [
|
|
18
|
-
{
|
|
19
|
-
header: '',
|
|
20
|
-
value: (m) => m.organization?.id === client.orgId ? color.green('*') : ' ',
|
|
21
|
-
},
|
|
22
|
-
{ header: 'id', value: (m) => m.organization?.id },
|
|
23
|
-
{ header: 'name', value: (m) => m.organization?.name },
|
|
24
|
-
{ header: 'role', value: (m) => m.role },
|
|
25
|
-
{ header: 'status', value: (m) => m.status },
|
|
26
|
-
{ header: 'org status', value: (m) => m.organization?.status },
|
|
27
|
-
]));
|
|
28
|
-
});
|
|
29
|
-
org
|
|
30
|
-
.command('use <orgId>')
|
|
31
|
-
.description('Make this organization the default context')
|
|
32
|
-
.action(async (orgId) => {
|
|
33
|
-
updateProfile(globals().profile, { orgId });
|
|
34
|
-
success(`Org context set to ${orgId}`);
|
|
35
|
-
info(color.dim('Use `wji org personal` to go back to your personal context.'));
|
|
36
|
-
});
|
|
37
|
-
org
|
|
38
|
-
.command('personal')
|
|
39
|
-
.description('Clear the organization context')
|
|
40
|
-
.action(() => {
|
|
41
|
-
updateProfile(globals().profile, { orgId: null });
|
|
42
|
-
success('Switched to personal context');
|
|
43
|
-
});
|
|
44
|
-
org
|
|
45
|
-
.command('current')
|
|
46
|
-
.description('Show the member record for the active org context')
|
|
47
|
-
.action(async () => {
|
|
48
|
-
const client = new WjiClient(globals());
|
|
49
|
-
if (!client.orgId)
|
|
50
|
-
return info('Personal context (no organization)');
|
|
51
|
-
const me = await client.get('/organization/me');
|
|
52
|
-
render(me, (m) => detail([
|
|
53
|
-
['org', m.organization?.name],
|
|
54
|
-
['orgId', m.organization?.id],
|
|
55
|
-
['role', m.role],
|
|
56
|
-
['status', m.status],
|
|
57
|
-
['monthlyLimit', m.monthlyLimit],
|
|
58
|
-
['permanentLimit', m.permanentLimit],
|
|
59
|
-
]));
|
|
60
|
-
});
|
|
61
|
-
org
|
|
62
|
-
.command('members')
|
|
63
|
-
.description('List members of the active org context')
|
|
64
|
-
.action(async () => {
|
|
65
|
-
const client = new WjiClient(globals());
|
|
66
|
-
const members = await client.get('/organization/members');
|
|
67
|
-
render(members, (rows) => table(rows || [], [
|
|
68
|
-
{ header: 'id', value: (m) => m.id },
|
|
69
|
-
{ header: 'user', value: (m) => m.user?.id },
|
|
70
|
-
{ header: 'name', value: (m) => m.user?.name },
|
|
71
|
-
{ header: 'email', value: (m) => m.user?.email },
|
|
72
|
-
{ header: 'role', value: (m) => m.role },
|
|
73
|
-
{ header: 'status', value: (m) => m.status },
|
|
74
|
-
]));
|
|
75
|
-
});
|
|
76
|
-
org
|
|
77
|
-
.command('create <name>')
|
|
78
|
-
.description('Create an organization')
|
|
79
|
-
.action(async (name) => {
|
|
80
|
-
const client = new WjiClient(globals());
|
|
81
|
-
const created = await client.post('/organization/create', { name });
|
|
82
|
-
render(created, (o) => success(`Created organization ${o.name} (${o.id})`));
|
|
83
|
-
});
|
|
84
|
-
return org;
|
|
85
|
-
}
|
package/dist/commands/preset.js
DELETED
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import { WjiClient } from '../core/client.js';
|
|
3
|
-
import { confirm, parseKeyValues, readValue } from '../core/prompt.js';
|
|
4
|
-
import { color, detail, info, render, success, table, truncate } from '../core/output.js';
|
|
5
|
-
const CATEGORIES = ['character', 'storyboard', 'multi_angle', 'composition', 'utility'];
|
|
6
|
-
const SORTS = ['weight', 'popular', 'latest', 'updated'];
|
|
7
|
-
export function presetCommand(globals) {
|
|
8
|
-
const preset = new Command('preset')
|
|
9
|
-
.aliases(['prompt'])
|
|
10
|
-
.description('Manage prompt presets');
|
|
11
|
-
const listOptions = (cmd) => cmd
|
|
12
|
-
.option('-k, --keyword <text>', 'search text')
|
|
13
|
-
.option('-t, --tag <tags>', 'comma-separated tags, all must match')
|
|
14
|
-
.option('-s, --sort <sort>', SORTS.join(' | '))
|
|
15
|
-
.option('--page <n>', 'page number', '1')
|
|
16
|
-
.option('--limit <n>', 'page size', '20');
|
|
17
|
-
const query = (client, opts, workspace) => ({
|
|
18
|
-
keyword: opts.keyword,
|
|
19
|
-
tag: opts.tag,
|
|
20
|
-
sortBy: opts.sort,
|
|
21
|
-
workspaceId: workspace,
|
|
22
|
-
page: Number(opts.page),
|
|
23
|
-
limit: Number(opts.limit),
|
|
24
|
-
});
|
|
25
|
-
listOptions(preset
|
|
26
|
-
.command('list', { isDefault: true })
|
|
27
|
-
.description('Presets you can use here (yours + public + workspace)')
|
|
28
|
-
.option('-w, --workspace <id>', 'include workspace presets')).action(async (opts) => {
|
|
29
|
-
const client = new WjiClient(globals());
|
|
30
|
-
const result = await client.get('/prompt-preset/search/usable', query(client, opts, client.workspace(opts.workspace)));
|
|
31
|
-
render(result, (r) => printPresets(r));
|
|
32
|
-
});
|
|
33
|
-
listOptions(preset.command('mine').description('Presets you created')).action(async (opts) => {
|
|
34
|
-
const client = new WjiClient(globals());
|
|
35
|
-
const result = await client.get('/prompt-preset/search/my', query(client, opts));
|
|
36
|
-
render(result, (r) => printPresets(r));
|
|
37
|
-
});
|
|
38
|
-
listOptions(preset.command('public').description('Public preset library')).action(async (opts) => {
|
|
39
|
-
const client = new WjiClient(globals());
|
|
40
|
-
const result = await client.get('/prompt-preset/search/public', query(client, opts));
|
|
41
|
-
render(result, (r) => printPresets(r));
|
|
42
|
-
});
|
|
43
|
-
listOptions(preset
|
|
44
|
-
.command('workspace [id]')
|
|
45
|
-
.description('Presets belonging to a workspace')).action(async (id, opts) => {
|
|
46
|
-
const client = new WjiClient(globals());
|
|
47
|
-
const result = await client.get('/prompt-preset/search/workspace', query(client, opts, client.requireWorkspace(id)));
|
|
48
|
-
render(result, (r) => printPresets(r));
|
|
49
|
-
});
|
|
50
|
-
preset
|
|
51
|
-
.command('get <id>')
|
|
52
|
-
.description('Show a preset with its full prompt')
|
|
53
|
-
.action(async (id) => {
|
|
54
|
-
const client = new WjiClient(globals());
|
|
55
|
-
const p = await client.get(`/prompt-preset/list/${id}`);
|
|
56
|
-
render(p, () => detail([
|
|
57
|
-
['id', p.id],
|
|
58
|
-
['name', p.name],
|
|
59
|
-
['description', p.description],
|
|
60
|
-
['category', p.category],
|
|
61
|
-
['visibility', p.visibility],
|
|
62
|
-
['tags', (p.tags || []).join(', ')],
|
|
63
|
-
['workspace', p.workspace?.id || p.workspace],
|
|
64
|
-
['forkedFrom', p.forkedFrom?.id || p.forkedFrom],
|
|
65
|
-
['forkCount', p.forkCount],
|
|
66
|
-
['prompt', p.prompt],
|
|
67
|
-
]));
|
|
68
|
-
});
|
|
69
|
-
preset
|
|
70
|
-
.command('create <name>')
|
|
71
|
-
.description('Create a preset')
|
|
72
|
-
.requiredOption('-p, --prompt <text>', 'prompt body (@file / - supported)')
|
|
73
|
-
.option('-d, --description <text>', 'description')
|
|
74
|
-
.option('-c, --category <category>', CATEGORIES.join(' | '), 'utility')
|
|
75
|
-
.option('-t, --tag <tag...>', 'tags')
|
|
76
|
-
.option('--visibility <v>', 'private | public', 'private')
|
|
77
|
-
.option('-w, --workspace <id>', 'attach to a workspace')
|
|
78
|
-
.option('--set <key=value...>', 'arbitrary field')
|
|
79
|
-
.action(async (name, opts) => {
|
|
80
|
-
const client = new WjiClient(globals());
|
|
81
|
-
const created = await client.post('/prompt-preset/create', {
|
|
82
|
-
name,
|
|
83
|
-
prompt: await readValue(opts.prompt),
|
|
84
|
-
description: opts.description,
|
|
85
|
-
category: opts.category,
|
|
86
|
-
tags: opts.tag,
|
|
87
|
-
visibility: opts.visibility,
|
|
88
|
-
workspaceId: client.workspace(opts.workspace),
|
|
89
|
-
...parseKeyValues(opts.set),
|
|
90
|
-
});
|
|
91
|
-
render(created, (p) => success(`Created preset ${p.name} (${p.id})`));
|
|
92
|
-
});
|
|
93
|
-
preset
|
|
94
|
-
.command('update <id>')
|
|
95
|
-
.description('Update a preset')
|
|
96
|
-
.option('-n, --name <name>', 'new name')
|
|
97
|
-
.option('-p, --prompt <text>', 'new prompt body (@file / - supported)')
|
|
98
|
-
.option('-d, --description <text>', 'new description')
|
|
99
|
-
.option('-c, --category <category>', CATEGORIES.join(' | '))
|
|
100
|
-
.option('-t, --tag <tag...>', 'replace tags')
|
|
101
|
-
.option('--visibility <v>', 'private | public')
|
|
102
|
-
.option('--set <key=value...>', 'arbitrary field')
|
|
103
|
-
.action(async (id, opts) => {
|
|
104
|
-
const client = new WjiClient(globals());
|
|
105
|
-
// The endpoint rebuilds every field from the body, so an omitted field
|
|
106
|
-
// arrives as undefined and is rejected. Merge onto the current preset to
|
|
107
|
-
// keep `update` partial from the caller's point of view.
|
|
108
|
-
const current = await client.getFresh(`/prompt-preset/list/${id}`);
|
|
109
|
-
const updated = await client.put(`/prompt-preset/list/${id}`, {
|
|
110
|
-
name: opts.name ?? current.name,
|
|
111
|
-
prompt: opts.prompt ? await readValue(opts.prompt) : current.prompt,
|
|
112
|
-
description: opts.description ?? current.description,
|
|
113
|
-
category: opts.category ?? current.category,
|
|
114
|
-
tags: opts.tag ?? current.tags,
|
|
115
|
-
visibility: opts.visibility ?? current.visibility,
|
|
116
|
-
...parseKeyValues(opts.set),
|
|
117
|
-
});
|
|
118
|
-
render(updated, () => success(`Updated ${id}`));
|
|
119
|
-
});
|
|
120
|
-
preset
|
|
121
|
-
.command('rm <id>')
|
|
122
|
-
.description('Delete a preset')
|
|
123
|
-
.option('-y, --yes', 'skip confirmation')
|
|
124
|
-
.action(async (id, opts) => {
|
|
125
|
-
if (!(await confirm(`Delete preset ${id}?`, opts.yes)))
|
|
126
|
-
return info('Aborted');
|
|
127
|
-
const client = new WjiClient(globals());
|
|
128
|
-
const result = await client.delete(`/prompt-preset/list/${id}`);
|
|
129
|
-
render(result, () => success(`Deleted ${id}`));
|
|
130
|
-
});
|
|
131
|
-
preset
|
|
132
|
-
.command('fork <id>')
|
|
133
|
-
.description('Fork a preset into your own library')
|
|
134
|
-
.action(async (id) => {
|
|
135
|
-
const client = new WjiClient(globals());
|
|
136
|
-
const forked = await client.post(`/prompt-preset/fork/${id}`);
|
|
137
|
-
render(forked, (p) => success(`Forked to ${p.id}`));
|
|
138
|
-
});
|
|
139
|
-
preset
|
|
140
|
-
.command('copy <id> [workspaceId]')
|
|
141
|
-
.description('Copy a preset into a workspace')
|
|
142
|
-
.action(async (id, workspaceId) => {
|
|
143
|
-
const client = new WjiClient(globals());
|
|
144
|
-
const copied = await client.post(`/prompt-preset/copy-to-workspace/${id}`, {
|
|
145
|
-
workspaceId: client.requireWorkspace(workspaceId),
|
|
146
|
-
});
|
|
147
|
-
render(copied, (p) => success(`Copied to workspace as ${p.id}`));
|
|
148
|
-
});
|
|
149
|
-
preset
|
|
150
|
-
.command('tags')
|
|
151
|
-
.description('List preset tags in a scope')
|
|
152
|
-
.option('-s, --scope <scope>', 'my | public | workspace', 'public')
|
|
153
|
-
.option('-w, --workspace <id>', 'workspace when scope=workspace')
|
|
154
|
-
.action(async (opts) => {
|
|
155
|
-
const client = new WjiClient(globals());
|
|
156
|
-
const tags = await client.get('/prompt-preset/tags', {
|
|
157
|
-
scope: opts.scope,
|
|
158
|
-
workspaceId: opts.scope === 'workspace' ? client.requireWorkspace(opts.workspace) : undefined,
|
|
159
|
-
});
|
|
160
|
-
render(tags, (rows) => rows.forEach((t) => process.stdout.write(`${t?.tag ?? t}\n`)));
|
|
161
|
-
});
|
|
162
|
-
return preset;
|
|
163
|
-
}
|
|
164
|
-
function printPresets(result) {
|
|
165
|
-
const rows = result?.data || result || [];
|
|
166
|
-
table(rows, [
|
|
167
|
-
{ header: 'id', value: (p) => p.id },
|
|
168
|
-
{ header: 'name', value: (p) => truncate(p.name, 28) },
|
|
169
|
-
{ header: 'category', value: (p) => p.category },
|
|
170
|
-
{ header: 'vis', value: (p) => p.visibility },
|
|
171
|
-
{ header: 'tags', value: (p) => (p.tags || []).join(',') },
|
|
172
|
-
{ header: 'prompt', value: (p) => truncate(p.prompt, 40) },
|
|
173
|
-
]);
|
|
174
|
-
if (result?.pagination)
|
|
175
|
-
info(color.dim(`${result.pagination.total} total`));
|
|
176
|
-
}
|