@wji.ai/cli 0.7.0 → 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 +4 -1
- package/dist/index.js +7162 -76
- package/package.json +6 -9
- 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-enquiry.js +0 -171
- package/dist/commands/backreel-episode.js +0 -243
- package/dist/commands/backreel-fund.js +0 -299
- package/dist/commands/backreel.js +0 -443
- 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 -356
- package/dist/commands/model.js +0 -136
- package/dist/commands/npc-album.js +0 -201
- package/dist/commands/npc-live.js +0 -316
- package/dist/commands/npc.js +0 -437
- 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/backreel.js +0 -23
- package/dist/core/client.js +0 -269
- package/dist/core/config.js +0 -95
- package/dist/core/crew.js +0 -546
- package/dist/core/image.js +0 -106
- 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/vod.js +0 -149
- package/dist/core/zip.js +0 -87
|
@@ -1,299 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import { POSTS, backreelClient, idOf, loadPost, oneOf } from '../core/backreel.js';
|
|
3
|
-
import { color, detail, info, render, relativeTime, success, table, truncate } from '../core/output.js';
|
|
4
|
-
import { confirm } from '../core/prompt.js';
|
|
5
|
-
const CURRENCIES = ['hkd', 'usd', 'twd', 'sgd', 'eur', 'gbp'];
|
|
6
|
-
const MAX_TIERS = 10;
|
|
7
|
-
const num = (value, flag, min = 0) => {
|
|
8
|
-
const n = Number(value);
|
|
9
|
-
if (!Number.isFinite(n) || n < min)
|
|
10
|
-
throw new Error(`${flag} must be a number ≥ ${min}`);
|
|
11
|
-
return n;
|
|
12
|
-
};
|
|
13
|
-
/** The server rejects a tier below the minimum donation (and below 1) with a bare 500. */
|
|
14
|
-
function tierAmount(value, minAmount) {
|
|
15
|
-
const floor = Math.max(1, minAmount || 0);
|
|
16
|
-
const n = num(value, '--amount');
|
|
17
|
-
if (n < floor)
|
|
18
|
-
throw new Error(`--amount must be at least ${floor} (the minimum donation)`);
|
|
19
|
-
return Math.round(n);
|
|
20
|
-
}
|
|
21
|
-
function itemIndex(n, count, what) {
|
|
22
|
-
const i = Number(n);
|
|
23
|
-
if (!Number.isInteger(i) || i < 1 || i > count)
|
|
24
|
-
throw new Error(`${what} ${n} does not exist — there are ${count}`);
|
|
25
|
-
return i - 1;
|
|
26
|
-
}
|
|
27
|
-
/** campaign as the update DTO takes it: the owner view plus avatar ids, minus derived totals. */
|
|
28
|
-
function campaignDto(c) {
|
|
29
|
-
return {
|
|
30
|
-
goalAmount: c?.goalAmount ?? 0,
|
|
31
|
-
offlineRaisedAmount: c?.offlineRaisedAmount ?? 0,
|
|
32
|
-
deadline: c?.deadline ?? null,
|
|
33
|
-
offlineSupporters: (c?.offlineSupporters || []).map((s, i) => ({
|
|
34
|
-
name: s.name,
|
|
35
|
-
amount: s.amount ?? 0,
|
|
36
|
-
sort: i,
|
|
37
|
-
avatar: idOf(s.avatar) ?? null,
|
|
38
|
-
avatarUrl: s.avatarUrl ?? null,
|
|
39
|
-
})),
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
/** donation config as the update DTO takes it (the read view adds `remaining` per tier). */
|
|
43
|
-
function donationDto(d) {
|
|
44
|
-
return {
|
|
45
|
-
enabled: !!d?.enabled,
|
|
46
|
-
currency: d?.currency || 'hkd',
|
|
47
|
-
minAmount: d?.minAmount ?? 0,
|
|
48
|
-
tiers: (d?.tiers || []).map((t) => ({ id: t.id, title: t.title, amount: t.amount, perks: t.perks || '', limit: t.limit ?? null })),
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
function requireCampaign(post) {
|
|
52
|
-
if (post.kind !== 'crowdfunding') {
|
|
53
|
-
throw new Error(`"${post.title}" is a ${post.kind} post — campaign fields need --kind crowdfunding (which drops its episodes)`);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
const put = (client, id, body) => client.put(`${POSTS}/list/${id}`, body);
|
|
57
|
-
function printFund(post) {
|
|
58
|
-
const c = post.campaign;
|
|
59
|
-
const d = post.donation || {};
|
|
60
|
-
// The campaign view reports the stats currency, which reads HKD until the first donation;
|
|
61
|
-
// the configured donation currency is the real one (it cannot change after that).
|
|
62
|
-
const cur = (d.currency || c?.currency || 'hkd').toUpperCase();
|
|
63
|
-
if (c) {
|
|
64
|
-
const pct = c.goalAmount ? ` (${Math.round(((c.raisedAmount || 0) / c.goalAmount) * 100)}%)` : '';
|
|
65
|
-
detail([
|
|
66
|
-
['goal', `${c.goalAmount} ${cur}`],
|
|
67
|
-
['raised', `${c.raisedAmount} ${cur}${pct} = offline ${c.offlineRaisedAmount ?? 0} + online ${(c.raisedAmount || 0) - (c.offlineRaisedAmount || 0)}`],
|
|
68
|
-
['supporters', c.supporterCount],
|
|
69
|
-
['deadline', c.deadline || color.dim('none')],
|
|
70
|
-
]);
|
|
71
|
-
process.stdout.write('\n');
|
|
72
|
-
}
|
|
73
|
-
else {
|
|
74
|
-
info(color.dim(`${post.kind} post — no campaign (goal / offline supporters are crowdfunding-only); donations still work`));
|
|
75
|
-
}
|
|
76
|
-
detail([
|
|
77
|
-
['donations', d.enabled ? color.green('on') : 'off'],
|
|
78
|
-
['currency', (d.currency || 'hkd').toUpperCase()],
|
|
79
|
-
['minimum', d.minAmount ?? 0],
|
|
80
|
-
]);
|
|
81
|
-
if ((d.tiers || []).length) {
|
|
82
|
-
process.stdout.write('\n');
|
|
83
|
-
table(d.tiers.map((t, i) => ({ ...t, n: i + 1 })), [
|
|
84
|
-
{ header: '#', value: (t) => t.n },
|
|
85
|
-
{ header: 'tier', value: (t) => truncate(t.title, 24) },
|
|
86
|
-
{ header: 'amount', value: (t) => t.amount },
|
|
87
|
-
{ header: 'left', value: (t) => (t.limit ? `${t.remaining}/${t.limit}` : '∞') },
|
|
88
|
-
{ header: 'perks', value: (t) => truncate(t.perks, 40) },
|
|
89
|
-
]);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
function printSupporters(post) {
|
|
93
|
-
table((post.campaign?.offlineSupporters || []).map((s, i) => ({ ...s, n: i + 1 })), [
|
|
94
|
-
{ header: '#', value: (s) => s.n },
|
|
95
|
-
{ header: 'name', value: (s) => s.name },
|
|
96
|
-
{ header: 'amount', value: (s) => s.amount },
|
|
97
|
-
{ header: 'avatar', value: (s) => idOf(s.avatar) || '' },
|
|
98
|
-
]);
|
|
99
|
-
}
|
|
100
|
-
export function fundCommand(globals) {
|
|
101
|
-
const fund = new Command('fund')
|
|
102
|
-
.aliases(['funding', 'campaign'])
|
|
103
|
-
.description('Crowdfunding campaign, donation settings and tiers, offline supporters, donation records');
|
|
104
|
-
fund.command('show <postId>', { isDefault: true })
|
|
105
|
-
.description('Campaign progress, donation settings and tiers')
|
|
106
|
-
.action(async (postId) => {
|
|
107
|
-
const client = backreelClient(globals);
|
|
108
|
-
const post = await loadPost(client, postId);
|
|
109
|
-
render({ campaign: post.campaign, donation: post.donation, kind: post.kind }, () => printFund(post));
|
|
110
|
-
});
|
|
111
|
-
fund.command('set <postId>')
|
|
112
|
-
.description('Campaign goal / offline total / deadline and donation switches; only what you pass changes')
|
|
113
|
-
.option('--goal <amount>', 'campaign goal (≥1 to publish a crowdfunding post)')
|
|
114
|
-
.option('--offline-raised <amount>', 'money raised outside Backreel, added to the progress bar')
|
|
115
|
-
.option('--deadline <date>', 'ISO date (e.g. 2026-12-31); "none" clears — donations stop after it')
|
|
116
|
-
.option('--donations <on|off>', 'accept online donations (Stripe)')
|
|
117
|
-
.option('--currency <code>', `${CURRENCIES.join(' | ')} — locked once a donation exists`)
|
|
118
|
-
.option('--min <amount>', 'minimum donation')
|
|
119
|
-
.action(async (postId, opts) => {
|
|
120
|
-
const client = backreelClient(globals);
|
|
121
|
-
const post = await loadPost(client, postId);
|
|
122
|
-
const body = {};
|
|
123
|
-
if (opts.goal !== undefined || opts.offlineRaised !== undefined || opts.deadline !== undefined) {
|
|
124
|
-
requireCampaign(post);
|
|
125
|
-
const campaign = campaignDto(post.campaign);
|
|
126
|
-
if (opts.goal !== undefined)
|
|
127
|
-
campaign.goalAmount = num(opts.goal, '--goal');
|
|
128
|
-
if (opts.offlineRaised !== undefined)
|
|
129
|
-
campaign.offlineRaisedAmount = num(opts.offlineRaised, '--offline-raised');
|
|
130
|
-
if (opts.deadline !== undefined) {
|
|
131
|
-
if (['none', ''].includes(String(opts.deadline).toLowerCase()))
|
|
132
|
-
campaign.deadline = null;
|
|
133
|
-
else {
|
|
134
|
-
const date = new Date(opts.deadline);
|
|
135
|
-
if (Number.isNaN(date.getTime()))
|
|
136
|
-
throw new Error('--deadline must be a date like 2026-12-31');
|
|
137
|
-
campaign.deadline = date.toISOString();
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
body.campaign = campaign;
|
|
141
|
-
}
|
|
142
|
-
if (opts.donations !== undefined || opts.currency !== undefined || opts.min !== undefined) {
|
|
143
|
-
const donation = donationDto(post.donation);
|
|
144
|
-
if (opts.donations !== undefined)
|
|
145
|
-
donation.enabled = oneOf(String(opts.donations).toLowerCase(), ['on', 'off'], '--donations') === 'on';
|
|
146
|
-
if (opts.currency !== undefined)
|
|
147
|
-
donation.currency = oneOf(String(opts.currency).toLowerCase(), CURRENCIES, '--currency');
|
|
148
|
-
if (opts.min !== undefined)
|
|
149
|
-
donation.minAmount = Math.round(num(opts.min, '--min'));
|
|
150
|
-
body.donation = donation;
|
|
151
|
-
}
|
|
152
|
-
if (!Object.keys(body).length)
|
|
153
|
-
throw new Error('Nothing to change — see `wji backreel fund set --help`');
|
|
154
|
-
const updated = await put(client, postId, body);
|
|
155
|
-
render(updated, (p) => {
|
|
156
|
-
success(`Updated ${Object.keys(body).join(' and ')} of "${p.title}"`);
|
|
157
|
-
printFund(p);
|
|
158
|
-
});
|
|
159
|
-
});
|
|
160
|
-
// ── donation tiers ─────────────────────────────────────────────────────────
|
|
161
|
-
const tier = fund.command('tier').description(`Donation tiers (≤${MAX_TIERS}): a title, an amount, perks, an optional cap`);
|
|
162
|
-
tier.command('add <postId>')
|
|
163
|
-
.requiredOption('--title <text>', 'tier name (≤60)')
|
|
164
|
-
.requiredOption('--amount <n>', 'price, ≥ the minimum donation')
|
|
165
|
-
.option('--perks <text>', 'what supporters get (≤1000)')
|
|
166
|
-
.option('--limit <n>', 'number of slots (default unlimited)')
|
|
167
|
-
.action(async (postId, opts) => {
|
|
168
|
-
const client = backreelClient(globals);
|
|
169
|
-
const post = await loadPost(client, postId);
|
|
170
|
-
const donation = donationDto(post.donation);
|
|
171
|
-
if (donation.tiers.length >= MAX_TIERS)
|
|
172
|
-
throw new Error(`Already ${MAX_TIERS} tiers`);
|
|
173
|
-
donation.tiers.push({ id: undefined, title: opts.title, amount: tierAmount(opts.amount, donation.minAmount), perks: opts.perks || '', limit: opts.limit ? num(opts.limit, '--limit', 1) : null });
|
|
174
|
-
const updated = await put(client, postId, { donation });
|
|
175
|
-
render(updated.donation, () => {
|
|
176
|
-
success(`Added tier "${opts.title}"${donation.enabled ? '' : color.dim(' — donations are off; `fund set --donations on`')}`);
|
|
177
|
-
printFund(updated);
|
|
178
|
-
});
|
|
179
|
-
});
|
|
180
|
-
tier.command('set <postId> <n>')
|
|
181
|
-
.option('--title <text>', 'tier name')
|
|
182
|
-
.option('--amount <n>', 'price')
|
|
183
|
-
.option('--perks <text>', 'perks')
|
|
184
|
-
.option('--limit <n>', 'slots; 0 = unlimited')
|
|
185
|
-
.action(async (postId, n, opts) => {
|
|
186
|
-
const client = backreelClient(globals);
|
|
187
|
-
const post = await loadPost(client, postId);
|
|
188
|
-
const donation = donationDto(post.donation);
|
|
189
|
-
const t = donation.tiers[itemIndex(n, donation.tiers.length, 'Tier')];
|
|
190
|
-
if (opts.title !== undefined)
|
|
191
|
-
t.title = opts.title;
|
|
192
|
-
if (opts.amount !== undefined)
|
|
193
|
-
t.amount = tierAmount(opts.amount, donation.minAmount);
|
|
194
|
-
if (opts.perks !== undefined)
|
|
195
|
-
t.perks = opts.perks;
|
|
196
|
-
if (opts.limit !== undefined)
|
|
197
|
-
t.limit = Number(opts.limit) > 0 ? num(opts.limit, '--limit', 1) : null;
|
|
198
|
-
const updated = await put(client, postId, { donation });
|
|
199
|
-
render(updated.donation, () => {
|
|
200
|
-
success(`Updated tier ${n}`);
|
|
201
|
-
printFund(updated);
|
|
202
|
-
});
|
|
203
|
-
});
|
|
204
|
-
tier.command('rm <postId> <n...>')
|
|
205
|
-
.option('-y, --yes', 'skip confirmation')
|
|
206
|
-
.action(async (postId, ns, opts) => {
|
|
207
|
-
const client = backreelClient(globals);
|
|
208
|
-
const post = await loadPost(client, postId);
|
|
209
|
-
const donation = donationDto(post.donation);
|
|
210
|
-
const drop = new Set(ns.map((n) => itemIndex(n, donation.tiers.length, 'Tier')));
|
|
211
|
-
const names = [...drop].map((i) => donation.tiers[i].title).join(', ');
|
|
212
|
-
if (!(await confirm(`Remove tier(s) ${names}? Past donations keep their snapshot.`, opts.yes)))
|
|
213
|
-
return info('Aborted');
|
|
214
|
-
donation.tiers = donation.tiers.filter((_, i) => !drop.has(i));
|
|
215
|
-
const updated = await put(client, postId, { donation });
|
|
216
|
-
render(updated.donation, () => {
|
|
217
|
-
success(`Removed ${drop.size} tier(s)`);
|
|
218
|
-
printFund(updated);
|
|
219
|
-
});
|
|
220
|
-
});
|
|
221
|
-
// ── offline supporters ─────────────────────────────────────────────────────
|
|
222
|
-
const sup = fund.command('supporter').aliases(['supporters']).description('Supporters who paid outside Backreel (crowdfunding posts)');
|
|
223
|
-
sup.command('list <postId>', { isDefault: true }).action(async (postId) => {
|
|
224
|
-
const client = backreelClient(globals);
|
|
225
|
-
const post = await loadPost(client, postId);
|
|
226
|
-
requireCampaign(post);
|
|
227
|
-
render(post.campaign?.offlineSupporters || [], () => printSupporters(post));
|
|
228
|
-
});
|
|
229
|
-
sup.command('add <postId>')
|
|
230
|
-
.requiredOption('--name <name>', 'display name')
|
|
231
|
-
.option('--amount <n>', 'amount (shown next to the name)', '0')
|
|
232
|
-
.option('--avatar <assetId>', 'square image asset (see `backreel image --slot photo`)')
|
|
233
|
-
.action(async (postId, opts) => {
|
|
234
|
-
const client = backreelClient(globals);
|
|
235
|
-
const post = await loadPost(client, postId);
|
|
236
|
-
requireCampaign(post);
|
|
237
|
-
const campaign = campaignDto(post.campaign);
|
|
238
|
-
campaign.offlineSupporters.push({ name: opts.name, amount: num(opts.amount, '--amount'), sort: campaign.offlineSupporters.length, avatar: opts.avatar ?? null, avatarUrl: null });
|
|
239
|
-
const updated = await put(client, postId, { campaign });
|
|
240
|
-
render(updated.campaign, () => {
|
|
241
|
-
success(`Added supporter ${opts.name}`);
|
|
242
|
-
info(color.dim('The supporter count rises; offline money only counts once you raise --offline-raised'));
|
|
243
|
-
printSupporters(updated);
|
|
244
|
-
});
|
|
245
|
-
});
|
|
246
|
-
sup.command('rm <postId> <n...>')
|
|
247
|
-
.option('-y, --yes', 'skip confirmation')
|
|
248
|
-
.action(async (postId, ns, opts) => {
|
|
249
|
-
const client = backreelClient(globals);
|
|
250
|
-
const post = await loadPost(client, postId);
|
|
251
|
-
requireCampaign(post);
|
|
252
|
-
const campaign = campaignDto(post.campaign);
|
|
253
|
-
const drop = new Set(ns.map((n) => itemIndex(n, campaign.offlineSupporters.length, 'Supporter')));
|
|
254
|
-
const names = [...drop].map((i) => campaign.offlineSupporters[i].name).join(', ');
|
|
255
|
-
if (!(await confirm(`Remove ${names}?`, opts.yes)))
|
|
256
|
-
return info('Aborted');
|
|
257
|
-
campaign.offlineSupporters = campaign.offlineSupporters.filter((_, i) => !drop.has(i));
|
|
258
|
-
const updated = await put(client, postId, { campaign });
|
|
259
|
-
render(updated.campaign, () => {
|
|
260
|
-
success(`Removed ${drop.size} supporter(s)`);
|
|
261
|
-
printSupporters(updated);
|
|
262
|
-
});
|
|
263
|
-
});
|
|
264
|
-
// ── donation records ───────────────────────────────────────────────────────
|
|
265
|
-
fund.command('donations <postId>')
|
|
266
|
-
.description('Online donations, balance and payouts (anonymous donors stay hidden)')
|
|
267
|
-
.option('--page <n>', 'page number', '1')
|
|
268
|
-
.option('--limit <n>', 'page size (≤200)', '50')
|
|
269
|
-
.action(async (postId, opts) => {
|
|
270
|
-
const client = backreelClient(globals);
|
|
271
|
-
const r = await client.get(`/backreel/donation/post/${postId}/manage`, { page: Number(opts.page), limit: Number(opts.limit) });
|
|
272
|
-
render(r, () => {
|
|
273
|
-
const s = r.summary || {};
|
|
274
|
-
const cur = (s.currency || '').toUpperCase();
|
|
275
|
-
detail([
|
|
276
|
-
['raised', `${s.raised} ${cur}`],
|
|
277
|
-
['paid out', `${s.paidOut} ${cur}`],
|
|
278
|
-
['balance', `${s.balance} ${cur}`],
|
|
279
|
-
['supporters', r.stats?.supporterCount ?? 0],
|
|
280
|
-
['donations', r.stats?.donationCount ?? 0],
|
|
281
|
-
]);
|
|
282
|
-
process.stdout.write('\n');
|
|
283
|
-
table(r.data || [], [
|
|
284
|
-
{ header: 'when', value: (d) => relativeTime(d.paidAt || d.createdAt) },
|
|
285
|
-
{ header: 'donor', value: (d) => (d.isAnonymous ? color.dim('anonymous') : truncate(d.donor?.name, 20)) },
|
|
286
|
-
{ header: 'amount', value: (d) => `${d.amount} ${(d.currency || '').toUpperCase()}` },
|
|
287
|
-
{ header: 'tier', value: (d) => truncate(d.tier?.title, 16) },
|
|
288
|
-
{ header: 'status', value: (d) => d.status + (d.refundedAmount ? ` (-${d.refundedAmount})` : '') + (d.disputed ? color.red(' disputed') : '') },
|
|
289
|
-
{ header: 'message', value: (d) => truncate(d.message, 30) },
|
|
290
|
-
]);
|
|
291
|
-
const p = r.pagination;
|
|
292
|
-
if (p)
|
|
293
|
-
info(color.dim(`${p.total} donations · page ${p.page}${p.hasNext ? ` · more: --page ${p.page + 1}` : ''}`));
|
|
294
|
-
if ((r.payouts || []).length)
|
|
295
|
-
info(color.dim(`${r.payouts.length} payout(s) — payouts and refunds are handled by Backreel staff`));
|
|
296
|
-
});
|
|
297
|
-
});
|
|
298
|
-
return fund;
|
|
299
|
-
}
|