@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.
Files changed (49) hide show
  1. package/README.md +23 -3
  2. package/dist/index.js +7162 -76
  3. package/package.json +6 -9
  4. package/skill/backreel/SKILL.md +171 -0
  5. package/skill/backreel/reference/crowdfunding.md +82 -0
  6. package/skill/backreel/reference/enquiries.md +80 -0
  7. package/skill/backreel/reference/episodes.md +104 -0
  8. package/skill/backreel/reference/images.md +76 -0
  9. package/skill/npc-character/SKILL.md +111 -0
  10. package/skill/npc-character/examples/nagi.character.md +117 -0
  11. package/skill/npc-character/examples/nagi.stage.md +34 -0
  12. package/skill/npc-character/examples/yuzu.character.md +110 -0
  13. package/skill/npc-character/examples/yuzu.stage.md +30 -0
  14. package/skill/npc-character/reference/character-craft.md +153 -0
  15. package/skill/npc-character/reference/document-format.md +151 -0
  16. package/skill/npc-character/reference/interview.md +44 -0
  17. package/skill/npc-character/reference/life.md +92 -0
  18. package/skill/npc-character/reference/runtime.md +95 -0
  19. package/skill/npc-character/reference/stage-craft.md +109 -0
  20. package/skill/npc-character/reference/visual-kit.md +81 -0
  21. package/skill/npc-character/reference/voice.md +101 -0
  22. package/skill/wji/SKILL.md +1 -1
  23. package/skill/wji/reference/commands.md +25 -5
  24. package/dist/commands/agent.js +0 -294
  25. package/dist/commands/asset.js +0 -241
  26. package/dist/commands/auth.js +0 -216
  27. package/dist/commands/backreel.js +0 -320
  28. package/dist/commands/canvas.js +0 -1706
  29. package/dist/commands/class.js +0 -395
  30. package/dist/commands/config.js +0 -113
  31. package/dist/commands/crew.js +0 -205
  32. package/dist/commands/fsnode.js +0 -288
  33. package/dist/commands/gen.js +0 -352
  34. package/dist/commands/model.js +0 -136
  35. package/dist/commands/npc.js +0 -413
  36. package/dist/commands/org.js +0 -85
  37. package/dist/commands/preset.js +0 -176
  38. package/dist/commands/profile.js +0 -129
  39. package/dist/commands/skill.js +0 -272
  40. package/dist/commands/workspace.js +0 -470
  41. package/dist/core/archive.js +0 -95
  42. package/dist/core/client.js +0 -252
  43. package/dist/core/config.js +0 -95
  44. package/dist/core/crew.js +0 -546
  45. package/dist/core/members.js +0 -54
  46. package/dist/core/npc.js +0 -108
  47. package/dist/core/output.js +0 -174
  48. package/dist/core/prompt.js +0 -118
  49. package/dist/core/zip.js +0 -87
@@ -1,129 +0,0 @@
1
- import { Command } from 'commander';
2
- import { WjiClient } from '../core/client.js';
3
- import { askSecret, parseKeyValues, readValue } from '../core/prompt.js';
4
- import { color, detail, info, relativeTime, render, success, table, truncate } from '../core/output.js';
5
- export function profileCommand(globals) {
6
- const profile = new Command('profile').description('Your account profile, wallet and login history');
7
- profile
8
- .command('show', { isDefault: true })
9
- .description('Show your profile')
10
- .action(async () => {
11
- const client = new WjiClient(globals());
12
- const me = await client.get('/user/me');
13
- render(me, (u) => detail([
14
- ['id', u.id],
15
- ['name', u.name],
16
- ['handle', u.handle],
17
- ['email', u.email],
18
- ['bio', u.bio],
19
- ['website', u.website],
20
- ['contactEmail', u.contactEmail],
21
- ['phone', u.phone],
22
- ['balance', u.balance],
23
- ['vip', u.vip?.level],
24
- ['avatar', u.avatar?.id || u.avatar],
25
- ['banner', u.banner?.id || u.banner],
26
- ['socialLinks', (u.socialLinks || []).map((s) => `${s.platform}:${s.url}`).join(', ')],
27
- ['createdAt', u.createdAt],
28
- ]));
29
- });
30
- profile
31
- .command('update')
32
- .description('Update your profile')
33
- .option('-n, --name <name>', 'display name')
34
- .option('--handle <handle>', '3-24 chars, letter first, lowercase/digits/underscore')
35
- .option('--bio <text>', 'bio, max 500 chars (@file / - supported)')
36
- .option('--email <email>', 'account email')
37
- .option('--contact-email <email>', 'public contact email')
38
- .option('--phone <phone>', 'contact phone')
39
- .option('--website <url>', 'website')
40
- .option('--avatar <assetId>', 'avatar asset id ("null" to clear)')
41
- .option('--banner <assetId>', 'banner asset id ("null" to clear)')
42
- .option('--set <key=value...>', 'arbitrary field')
43
- .action(async (opts) => {
44
- const client = new WjiClient(globals());
45
- const nullable = (v) => (v === 'null' ? null : v);
46
- const updated = await client.patch('/user/me', {
47
- name: opts.name,
48
- handle: nullable(opts.handle),
49
- bio: opts.bio ? await readValue(opts.bio) : undefined,
50
- email: opts.email,
51
- contactEmail: nullable(opts.contactEmail),
52
- phone: nullable(opts.phone),
53
- website: nullable(opts.website),
54
- avatar: nullable(opts.avatar),
55
- banner: nullable(opts.banner),
56
- ...parseKeyValues(opts.set),
57
- });
58
- render(updated, () => success('Profile updated'));
59
- });
60
- profile
61
- .command('password')
62
- .description('Change your password')
63
- .option('-p, --password <password>', 'new password (prompted when omitted)')
64
- .action(async (opts) => {
65
- const client = new WjiClient(globals());
66
- const password = opts.password || (await askSecret('New password: '));
67
- if (password.length < 6)
68
- throw new Error('Password must be at least 6 characters');
69
- const result = await client.patch('/user/me/password', { password });
70
- render(result, () => success('Password changed'));
71
- });
72
- profile
73
- .command('logins')
74
- .description('Show your last 10 successful logins')
75
- .action(async () => {
76
- const client = new WjiClient(globals());
77
- const rows = await client.get('/user/loginHistory');
78
- render(rows, (list) => table(list || [], [
79
- { header: 'when', value: (l) => relativeTime(l.createdAt) },
80
- { header: 'state', value: (l) => l.state },
81
- { header: 'ip', value: (l) => l.ip },
82
- { header: 'agent', value: (l) => truncate(l.userAgent, 40) },
83
- ]));
84
- });
85
- profile
86
- .command('wallet')
87
- .description('Show your credit balance and recent ledger entries')
88
- .option('--limit <n>', 'ledger entries to show', '10')
89
- .action(async (opts) => {
90
- const client = new WjiClient(globals());
91
- const me = await client.get('/user/me');
92
- const ledgers = await client
93
- .get('/wallet/ledgers/search', { limit: Number(opts.limit) })
94
- .catch(() => null);
95
- render({ balance: me.balance, vip: me.vip, ledgers }, () => {
96
- detail([
97
- ['balance', me.balance],
98
- ['vip', me.vip?.level],
99
- ]);
100
- if (ledgers?.data?.length) {
101
- info('');
102
- table(ledgers.data, [
103
- { header: 'when', value: (l) => relativeTime(l.createdAt) },
104
- { header: 'amount', value: (l) => l.amount },
105
- { header: 'type', value: (l) => l.type },
106
- { header: 'note', value: (l) => truncate(l.description || l.remark, 40) },
107
- ]);
108
- }
109
- });
110
- });
111
- profile
112
- .command('search <keyword>')
113
- .description('Find other users by name or handle (an exact email address also matches)')
114
- .option('--limit <n>', 'max results (1-20)', '10')
115
- .action(async (keyword, opts) => {
116
- const client = new WjiClient(globals());
117
- // Public search returns a bare array of {id, name, handle, avatar} — never email.
118
- const result = await client.get('/user/search', { keyword, limit: Number(opts.limit) });
119
- render(result, (users) => {
120
- table(users || [], [
121
- { header: 'id', value: (u) => u.id },
122
- { header: 'name', value: (u) => u.name },
123
- { header: 'handle', value: (u) => u.handle },
124
- ]);
125
- info(color.dim(`${users?.length ?? 0} found`));
126
- });
127
- });
128
- return profile;
129
- }
@@ -1,272 +0,0 @@
1
- import { existsSync, statSync } from 'node:fs';
2
- import { mkdir, readFile, readdir, writeFile } from 'node:fs/promises';
3
- import { basename, dirname, join, relative, resolve } from 'node:path';
4
- import { Command } from 'commander';
5
- import { WjiClient } from '../core/client.js';
6
- import { confirm } from '../core/prompt.js';
7
- import { createZip } from '../core/zip.js';
8
- import { color, detail, info, isJsonMode, printJson, render, success, table, truncate, warn } from '../core/output.js';
9
- /**
10
- * Import limits enforced by wji-backend (src/ai/skill/zip.ts, shared.service.ts).
11
- * Checked locally so a push fails with a clear message instead of a 400 / 413.
12
- */
13
- const SKILL_ZIP_MAX_UPLOAD_BYTES = 5 * 1024 * 1024;
14
- const SKILL_ZIP_MAX_ENTRIES = 200;
15
- const SKILL_ZIP_MAX_FILE_BYTES = 2 * 1024 * 1024;
16
- const SKILL_ZIP_MAX_TOTAL_BYTES = 20 * 1024 * 1024;
17
- const mb = (bytes) => `${bytes / (1024 * 1024)}MB`;
18
- function assertUploadSize(bytes, what) {
19
- if (bytes > SKILL_ZIP_MAX_UPLOAD_BYTES) {
20
- throw new Error(`${what} is ${(bytes / (1024 * 1024)).toFixed(1)}MB; the server accepts at most ${mb(SKILL_ZIP_MAX_UPLOAD_BYTES)}.`);
21
- }
22
- }
23
- /** Recursively collect files under a dir as forward-slash relative paths. */
24
- async function collectFiles(root) {
25
- const out = [];
26
- async function walk(dir) {
27
- for (const entry of await readdir(dir, { withFileTypes: true })) {
28
- if (entry.name.startsWith('.'))
29
- continue;
30
- const full = join(dir, entry.name);
31
- if (entry.isDirectory())
32
- await walk(full);
33
- else if (entry.isFile())
34
- out.push(full);
35
- }
36
- }
37
- await walk(root);
38
- return out;
39
- }
40
- /** Read a skill folder into zip entries. The backend keeps only .md files. */
41
- async function zipSkillDir(dir) {
42
- const root = resolve(dir);
43
- if (!existsSync(root) || !statSync(root).isDirectory()) {
44
- throw new Error(`Not a directory: ${dir}`);
45
- }
46
- const files = await collectFiles(root);
47
- const rel = (f) => relative(root, f).split('\\').join('/');
48
- const hasSkillMd = files.some((f) => {
49
- const r = rel(f);
50
- return r === 'SKILL.md' || r.endsWith('/SKILL.md');
51
- });
52
- if (!hasSkillMd) {
53
- throw new Error(`No SKILL.md found in ${dir} — a skill must contain SKILL.md at its root.`);
54
- }
55
- const entries = [];
56
- const kept = [];
57
- const skipped = [];
58
- for (const f of files) {
59
- const path = rel(f);
60
- if (path.toLowerCase().endsWith('.md')) {
61
- entries.push({ path, content: await readFile(f) });
62
- kept.push(path);
63
- }
64
- else {
65
- skipped.push(path);
66
- }
67
- }
68
- if (entries.length > SKILL_ZIP_MAX_ENTRIES) {
69
- throw new Error(`${entries.length} markdown files; a skill holds at most ${SKILL_ZIP_MAX_ENTRIES}.`);
70
- }
71
- let total = 0;
72
- for (const entry of entries) {
73
- const bytes = Buffer.byteLength(entry.content);
74
- if (bytes > SKILL_ZIP_MAX_FILE_BYTES) {
75
- throw new Error(`${entry.path} is larger than ${mb(SKILL_ZIP_MAX_FILE_BYTES)}; the server rejects it.`);
76
- }
77
- total += bytes;
78
- }
79
- if (total > SKILL_ZIP_MAX_TOTAL_BYTES) {
80
- throw new Error(`Markdown totals ${(total / (1024 * 1024)).toFixed(1)}MB; the server accepts at most ${mb(SKILL_ZIP_MAX_TOTAL_BYTES)}.`);
81
- }
82
- const zip = createZip(entries);
83
- assertUploadSize(zip.length, 'The bundled zip');
84
- return { zip, kept, skipped };
85
- }
86
- export function skillCommand(globals) {
87
- const skill = new Command('skill').description('Manage agent skills (SKILL.md bundles)');
88
- // ── system skills ────────────────────────────────────────────────────────
89
- skill
90
- .command('system')
91
- .description('List built-in system skills you can attach to an agent')
92
- .action(async () => {
93
- const client = new WjiClient(globals());
94
- const list = await client.get('/ai/skill/system');
95
- render(list, (rows) => table(rows || [], [
96
- { header: 'key', value: (s) => s.key },
97
- { header: 'name', value: (s) => s.name },
98
- { header: 'runtime', value: (s) => s.runtime },
99
- { header: 'script', value: (s) => (s.script ? 'yes' : '') },
100
- { header: 'description', value: (s) => truncate(s.description, 46) },
101
- ]));
102
- });
103
- // ── user skills ──────────────────────────────────────────────────────────
104
- skill
105
- .command('list', { isDefault: true })
106
- .description('List your skills')
107
- .option('--page <n>', 'page number', '1')
108
- .option('--limit <n>', 'page size', '20')
109
- .action(async (opts) => {
110
- const client = new WjiClient(globals());
111
- const result = await client.get('/ai/skill/search', {
112
- page: Number(opts.page),
113
- limit: Number(opts.limit),
114
- });
115
- render(result, (r) => {
116
- table(r.data || [], [
117
- { header: 'id', value: (s) => s.id },
118
- { header: 'name', value: (s) => truncate(s.name, 28) },
119
- { header: 'files', value: (s) => (s.files || []).length },
120
- { header: 'visibility', value: (s) => s.visibility },
121
- { header: 'description', value: (s) => truncate(s.description, 40) },
122
- ]);
123
- info(color.dim(`${r.pagination?.total ?? 0} total`));
124
- });
125
- });
126
- skill
127
- .command('get <id>')
128
- .description('Show a skill and its files')
129
- .action(async (id) => {
130
- const client = new WjiClient(globals());
131
- const s = await client.get(`/ai/skill/${id}`);
132
- render(s, () => {
133
- detail([
134
- ['id', s.id],
135
- ['name', s.name],
136
- ['description', s.description],
137
- ['visibility', s.visibility],
138
- ['files', (s.files || []).length],
139
- ]);
140
- for (const f of s.files || []) {
141
- info(` ${color.cyan(f.path)} ${color.dim(`(${(f.content || '').length} chars)`)}`);
142
- }
143
- });
144
- });
145
- skill
146
- .command('cat <id> [path]')
147
- .description('Print the content of a skill file (defaults to SKILL.md)')
148
- .action(async (id, path) => {
149
- const client = new WjiClient(globals());
150
- const s = await client.get(`/ai/skill/${id}`);
151
- const files = s.files || [];
152
- const target = (path && files.find((f) => f.path === path)) ||
153
- files.find((f) => f.path === 'SKILL.md' || f.path.endsWith('/SKILL.md')) ||
154
- files[0];
155
- if (!target)
156
- throw new Error('Skill has no files');
157
- process.stdout.write(target.content + '\n');
158
- });
159
- skill
160
- .command('import <zip>')
161
- .description('Import a skill from a .zip whose root contains SKILL.md')
162
- .action(async (zipPath) => {
163
- const client = new WjiClient(globals());
164
- if (!existsSync(zipPath))
165
- throw new Error(`No such file: ${zipPath}`);
166
- const buffer = await readFile(zipPath);
167
- assertUploadSize(buffer.length, basename(zipPath));
168
- const created = await client.uploadBuffer('/ai/skill/import', buffer, basename(zipPath));
169
- render(created, (s) => success(`Imported skill ${s.name} (${s.id})`));
170
- });
171
- skill
172
- .command('push <dir>')
173
- .description('Bundle a local skill folder and import it (only .md files are stored)')
174
- .action(async (dir) => {
175
- const client = new WjiClient(globals());
176
- const { zip, kept, skipped } = await zipSkillDir(dir);
177
- if (skipped.length) {
178
- warn(`Skipping ${skipped.length} non-markdown file(s): ${skipped.slice(0, 5).join(', ')}${skipped.length > 5 ? '…' : ''}`);
179
- }
180
- const created = await client.uploadBuffer('/ai/skill/import', zip, `${basename(resolve(dir))}.zip`);
181
- if (isJsonMode())
182
- return printJson(created);
183
- success(`Imported skill ${created.name} (${created.id})`);
184
- info(color.dim(`${kept.length} markdown file(s): ${kept.join(', ')}`));
185
- });
186
- skill
187
- .command('pull <id> [dir]')
188
- .description('Write a skill\'s files to disk (defaults to ./<skill-name>)')
189
- .option('-f, --force', 'overwrite existing files')
190
- .action(async (id, dir, opts) => {
191
- const client = new WjiClient(globals());
192
- const s = await client.get(`/ai/skill/${id}`);
193
- const target = resolve(dir || slugify(s.name) || id);
194
- const written = [];
195
- for (const f of s.files || []) {
196
- const dest = join(target, f.path);
197
- if (existsSync(dest) && !opts.force) {
198
- throw new Error(`${dest} exists — pass --force to overwrite`);
199
- }
200
- await mkdir(dirname(dest), { recursive: true });
201
- await writeFile(dest, f.content);
202
- written.push(relative(process.cwd(), dest));
203
- }
204
- render({ id, dir: target, files: written }, () => {
205
- success(`Pulled ${written.length} file(s) to ${relative(process.cwd(), target) || '.'}`);
206
- for (const w of written)
207
- info(color.dim(` ${w}`));
208
- });
209
- });
210
- skill
211
- .command('update <id>')
212
- .description('Update a skill\'s metadata, or replace its files from a folder')
213
- .option('-n, --name <name>', 'new name')
214
- .option('-d, --description <text>', 'new description')
215
- .option('--visibility <v>', 'private | unlist')
216
- .option('--dir <dir>', 'replace files from this skill folder')
217
- .option('-y, --yes', 'skip the confirmation before --dir replaces files')
218
- .action(async (id, opts) => {
219
- const client = new WjiClient(globals());
220
- const body = {
221
- name: opts.name,
222
- description: opts.description,
223
- visibility: opts.visibility,
224
- };
225
- if (opts.dir) {
226
- const root = resolve(opts.dir);
227
- const files = await collectFiles(root);
228
- const rel = (f) => relative(root, f).split('\\').join('/');
229
- body.files = [];
230
- for (const f of files) {
231
- const path = rel(f);
232
- if (path.toLowerCase().endsWith('.md')) {
233
- body.files.push({ path, content: await readFile(f, 'utf8') });
234
- }
235
- }
236
- if (!body.files.some((f) => f.path === 'SKILL.md' || f.path.endsWith('/SKILL.md'))) {
237
- throw new Error(`No SKILL.md in ${opts.dir}`);
238
- }
239
- // --dir swaps the whole file set; files missing from the folder are gone for good.
240
- const current = await client.get(`/ai/skill/${id}`).catch(() => null);
241
- const label = current?.name ? `"${current.name}" (${id})` : id;
242
- const had = Array.isArray(current?.files) ? `its ${current.files.length} file(s)` : 'all of its files';
243
- const prompt = `Replace ${had} of skill ${label} with ${body.files.length} file(s) from ${opts.dir}?`;
244
- if (!(await confirm(prompt, opts.yes)))
245
- return info('Aborted');
246
- }
247
- const updated = await client.put(`/ai/skill/${id}`, body);
248
- render(updated, () => success(`Updated skill ${id}`));
249
- });
250
- skill
251
- .command('rm <id>')
252
- .description('Delete a skill')
253
- .option('-y, --yes', 'skip confirmation')
254
- .action(async (id, opts) => {
255
- const client = new WjiClient(globals());
256
- const s = await client.get(`/ai/skill/${id}`).catch(() => ({ name: id }));
257
- if (!(await confirm(`Delete skill "${s.name}" (${id})?`, opts.yes)))
258
- return info('Aborted');
259
- const result = await client.delete(`/ai/skill/${id}`);
260
- render(result, () => success(`Deleted ${id}`));
261
- });
262
- return skill;
263
- }
264
- function slugify(name) {
265
- if (!name)
266
- return '';
267
- return name
268
- .trim()
269
- .toLowerCase()
270
- .replace(/[^a-z0-9]+/g, '-')
271
- .replace(/^-+|-+$/g, '');
272
- }