@wji.ai/cli 0.6.0 → 0.6.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.
@@ -15,6 +15,21 @@ function skillRefLabel(ref) {
15
15
  return String(ref);
16
16
  return ref.kind === 'system' ? `system:${ref.key}` : `user:${ref.id}`;
17
17
  }
18
+ /** Backend DEFAULT_MAX_STEPS / MAX_STEPS_LIMIT (src/ai/_/constant.ts). */
19
+ const DEFAULT_MAX_STEPS = 40;
20
+ const MAX_STEPS_LIMIT = 200;
21
+ /** `--max-steps`: an integer in range, or "default" to clear back to the site default. */
22
+ function parseMaxSteps(value) {
23
+ if (value === undefined)
24
+ return undefined;
25
+ if (['default', 'none', 'null'].includes(value.trim().toLowerCase()))
26
+ return null;
27
+ const n = Number(value);
28
+ if (!Number.isInteger(n) || n < 1 || n > MAX_STEPS_LIMIT) {
29
+ throw new Error(`--max-steps must be an integer from 1 to ${MAX_STEPS_LIMIT} (or "default")`);
30
+ }
31
+ return n;
32
+ }
18
33
  export function agentCommand(globals) {
19
34
  const agent = new Command('agent').description('Manage AI agents');
20
35
  agent
@@ -59,7 +74,7 @@ export function agentCommand(globals) {
59
74
  });
60
75
  agent
61
76
  .command('get <id>')
62
- .description('Show an agent (instructions included when you own it)')
77
+ .description('Show an agent (private agents: creator only; instructions only when you own it)')
63
78
  .action(async (id) => {
64
79
  const client = new WjiClient(globals());
65
80
  const a = await client.get(`/ai/agent/list/${id}`);
@@ -72,6 +87,7 @@ export function agentCommand(globals) {
72
87
  ['category', a.category],
73
88
  ['tags', (a.tags || []).join(', ')],
74
89
  ['greeting', a.greeting],
90
+ ['maxSteps', a.maxSteps ?? color.dim(`default (${DEFAULT_MAX_STEPS})`)],
75
91
  ['creator', a.creator?.name || a.creator?.id || a.creator],
76
92
  ['organization', a.organization?.id || a.organization || 'personal'],
77
93
  ['skills', (a.skills || []).map(skillRefLabel).join(', ')],
@@ -80,7 +96,7 @@ export function agentCommand(globals) {
80
96
  });
81
97
  agent
82
98
  .command('create <name>')
83
- .description('Create an agent')
99
+ .description('Create an agent (needs the agent-create permission on your account)')
84
100
  .requiredOption('-d, --description <text>', 'short description')
85
101
  .option('-i, --instructions <text>', 'system prompt (@file / - supported)')
86
102
  .option('-m, --model <name>', 'backing model')
@@ -89,7 +105,8 @@ export function agentCommand(globals) {
89
105
  .option('-t, --tag <tag...>', 'tags')
90
106
  .option('--greeting <text>', 'opening message')
91
107
  .option('--skill <ref...>', 'attach skills: system:<key> or user:<skillId>')
92
- .option('--set <key=value...>', 'arbitrary field')
108
+ .option('--max-steps <n>', `model calls allowed per chat turn, 1-${MAX_STEPS_LIMIT} (default ${DEFAULT_MAX_STEPS})`)
109
+ .option('--set <key=value...>', 'arbitrary field (admin-only fields are dropped by the server)')
93
110
  .action(async (name, opts) => {
94
111
  const client = new WjiClient(globals());
95
112
  const created = await client.post('/ai/agent/create', {
@@ -102,13 +119,14 @@ export function agentCommand(globals) {
102
119
  tags: opts.tag,
103
120
  greeting: opts.greeting,
104
121
  skills: opts.skill?.length ? opts.skill.map(parseSkillRef) : undefined,
122
+ maxSteps: parseMaxSteps(opts.maxSteps),
105
123
  ...parseKeyValues(opts.set),
106
124
  });
107
125
  render(created, (a) => success(`Created agent ${a.name} (${a.id})`));
108
126
  });
109
127
  agent
110
128
  .command('update <id>')
111
- .description('Update an agent')
129
+ .description('Update an agent (creator only)')
112
130
  .option('-n, --name <name>', 'new name')
113
131
  .option('-d, --description <text>', 'new description')
114
132
  .option('-i, --instructions <text>', 'new system prompt (@file / - supported)')
@@ -117,7 +135,8 @@ export function agentCommand(globals) {
117
135
  .option('-c, --category <name>', 'category')
118
136
  .option('-t, --tag <tag...>', 'replace tags')
119
137
  .option('--greeting <text>', 'opening message')
120
- .option('--set <key=value...>', 'arbitrary field')
138
+ .option('--max-steps <n>', `model calls allowed per chat turn, 1-${MAX_STEPS_LIMIT}; "default" clears it`)
139
+ .option('--set <key=value...>', 'arbitrary field (admin-only fields are dropped by the server)')
121
140
  .action(async (id, opts) => {
122
141
  const client = new WjiClient(globals());
123
142
  const updated = await client.put(`/ai/agent/list/${id}`, {
@@ -129,6 +148,7 @@ export function agentCommand(globals) {
129
148
  category: opts.category,
130
149
  tags: opts.tag,
131
150
  greeting: opts.greeting,
151
+ maxSteps: parseMaxSteps(opts.maxSteps),
132
152
  ...parseKeyValues(opts.set),
133
153
  });
134
154
  render(updated, () => success(`Updated ${id}`));
@@ -147,7 +167,7 @@ export function agentCommand(globals) {
147
167
  });
148
168
  agent
149
169
  .command('files <id>')
150
- .description('List files attached to an agent')
170
+ .description('List files attached to an agent (creator only)')
151
171
  .action(async (id) => {
152
172
  const client = new WjiClient(globals());
153
173
  const files = await client.get(`/ai/agent/list/${id}/files`);
@@ -236,7 +256,7 @@ export function agentCommand(globals) {
236
256
  });
237
257
  agent
238
258
  .command('versions <id>')
239
- .description('List an agent\'s version history')
259
+ .description('List an agent\'s version history (creator only — snapshots hold instructions)')
240
260
  .action(async (id) => {
241
261
  const client = new WjiClient(globals());
242
262
  const versions = await client.get(`/ai/agent/list/${id}/versions`);
@@ -251,7 +271,7 @@ export function agentCommand(globals) {
251
271
  });
252
272
  agent
253
273
  .command('restore <id> <versionId>')
254
- .description('Restore an agent to a previous version')
274
+ .description('Restore an agent to a previous version (creator only)')
255
275
  .action(async (id, versionId) => {
256
276
  const client = new WjiClient(globals());
257
277
  const result = await client.post(`/ai/agent/list/${id}/versions/${versionId}/restore`);
@@ -7,6 +7,10 @@ const POSTS = '/backreel/post';
7
7
  const KINDS = ['showcase', 'crowdfunding'];
8
8
  const PROJECT_STATUSES = ['planning', 'pre_production', 'in_production', 'post_production', 'completed'];
9
9
  const CREDIT_KINDS = ['creative', 'crew', 'cast'];
10
+ /** Backend LOGLINE_MAX (src/backreel/post/descriptionHtml.ts). */
11
+ const LOGLINE_MAX = 200;
12
+ /** BackreelVerificationKind — what `creators --role` filters on. */
13
+ const VERIFICATION_KINDS = ['investor', 'production_company', 'brand', 'distributor', 'sales_agent'];
10
14
  /** Credit fields the create / update DTO accepts; the rest of a read-back credit is dropped. */
11
15
  const CREDIT_FIELDS = ['kind', 'name', 'role', 'user', 'photo', 'avatarUrl', 'link', 'source', 'npcId', 'order'];
12
16
  export function backreelCommand(globals) {
@@ -87,6 +91,50 @@ export function backreelCommand(globals) {
87
91
  const result = await client.delete(`${POSTS}/list/${id}`);
88
92
  render(result, () => success(`Deleted "${post.title}"`));
89
93
  });
94
+ br.command('creators')
95
+ .description('Browse Backreel creators: editor picks, then ranked by recent likes / favorites')
96
+ .option('-k, --keyword <text>', 'match name or @handle')
97
+ .option('--role <kind>', `only creators verified as: ${VERIFICATION_KINDS.join(' | ')}`)
98
+ .option('--verified', 'only verified creators')
99
+ .option('--no-featured', 'skip the editor-picked creators')
100
+ .option('--page <n>', 'page number', '1')
101
+ .option('--limit <n>', 'page size (max 48)', '20')
102
+ .action(async (opts) => {
103
+ const client = new WjiClient(globals());
104
+ if (opts.role)
105
+ oneOf(opts.role, VERIFICATION_KINDS, '--role');
106
+ const result = await client.get('/backreel/creator/list', {
107
+ keyword: opts.keyword,
108
+ role: opts.role,
109
+ verifiedOnly: opts.verified ? 'true' : undefined,
110
+ withFeatured: opts.featured ? undefined : 'false',
111
+ page: Number(opts.page),
112
+ limit: Number(opts.limit),
113
+ });
114
+ render(result, (r) => {
115
+ const columns = [
116
+ { header: 'id', value: (c) => c.id },
117
+ { header: 'name', value: (c) => truncate(c.name, 24) },
118
+ { header: 'handle', value: (c) => (c.handle ? `@${c.handle}` : '') },
119
+ { header: 'reels', value: (c) => c.reelCount },
120
+ { header: 'followers', value: (c) => c.followerCount },
121
+ { header: 'likes', value: (c) => c.likeCount },
122
+ { header: 'verified', value: (c) => (c.verifiedKinds || []).join(', ') },
123
+ ];
124
+ if (r?.featured?.length) {
125
+ info(color.bold('Featured'));
126
+ table(r.featured, [...columns, { header: 'note', value: (c) => truncate(c.featuredNote, 30) }]);
127
+ process.stdout.write('\n');
128
+ info(color.bold('Hot'));
129
+ }
130
+ table(r?.data || [], columns);
131
+ const p = r?.pagination;
132
+ if (p) {
133
+ const more = p.page * p.limit < p.total ? ` · more: --page ${p.page + 1}` : '';
134
+ info(color.dim(`${p.total} ranked · page ${p.page}${more}`));
135
+ }
136
+ });
137
+ });
90
138
  // ── credits ────────────────────────────────────────────────────────────
91
139
  const credit = br.command('credit').aliases(['credits']).description('Cast and crew of a post (max 30)');
92
140
  credit
@@ -176,6 +224,7 @@ export function backreelCommand(globals) {
176
224
  function postOptions(cmd) {
177
225
  return cmd
178
226
  .option('--kind <kind>', KINDS.join(' | '))
227
+ .option('--logline <text>', `one-line hook, max ${LOGLINE_MAX} chars; public even when the description is locked ("" clears it)`)
179
228
  .option('--category <category>', 'category')
180
229
  .option('--project-status <status>', PROJECT_STATUSES.join(' | '))
181
230
  .option('--tags <tags>', 'comma-separated tags (replaces the list)')
@@ -189,6 +238,13 @@ async function postBody(opts) {
189
238
  const body = { ...((await readJson(opts.body)) || {}) };
190
239
  if (opts.kind)
191
240
  body.kind = oneOf(opts.kind, KINDS, '--kind');
241
+ if (opts.logline !== undefined) {
242
+ // Same normalisation the server applies: plain text, one line.
243
+ const logline = String(await readValue(opts.logline)).replace(/<[^>]*>/g, '').replace(/\s+/g, ' ').trim();
244
+ if (logline.length > LOGLINE_MAX)
245
+ throw new Error(`--logline is ${logline.length} chars; the limit is ${LOGLINE_MAX}`);
246
+ body.logline = logline;
247
+ }
192
248
  if (opts.category)
193
249
  body.category = opts.category;
194
250
  if (opts.projectStatus)
@@ -225,6 +281,7 @@ async function updateCredits(client, id, fn) {
225
281
  throw new Error('A post can have at most 30 credits');
226
282
  return client.put(`${POSTS}/list/${id}`, { credits: next });
227
283
  }
284
+ const assetLabel = (a) => (a ? a.url || a.id || a : undefined);
228
285
  function oneOf(value, allowed, flag) {
229
286
  if (!allowed.includes(value))
230
287
  throw new Error(`${flag} must be one of ${allowed.join(', ')}`);
@@ -239,8 +296,11 @@ function printPost(p) {
239
296
  ['project', p.projectStatus],
240
297
  ['category', p.category],
241
298
  ['tags', (p.tags || []).join(', ')],
299
+ ['logline', p.logline],
242
300
  ['description', truncate(p.descriptionText || p.description, 120)],
243
- ['cover', p.cover?.url || p.cover?.id || p.cover],
301
+ ['cover', assetLabel(p.cover)],
302
+ ['coverPortrait', assetLabel(p.coverPortrait)],
303
+ ['trailer', p.trailer ? `${assetLabel(p.trailer)}${p.trailerDurationSec ? ` (${p.trailerDurationSec}s)` : ''}` : undefined],
244
304
  ['creator', p.creator?.name],
245
305
  ['likes', p.likeCount],
246
306
  ['updated', relativeTime(p.updatedAt)],
@@ -1,9 +1,10 @@
1
1
  import { Command } from 'commander';
2
2
  import { WjiClient } from '../core/client.js';
3
- import { updateProfile } from '../core/config.js';
3
+ import { getProfile, updateProfile } from '../core/config.js';
4
4
  import { confirm, parseKeyValues } from '../core/prompt.js';
5
5
  import { color, detail, detailLevel, info, relativeTime, render, success, table, truncate } from '../core/output.js';
6
6
  import { loadMembers, memberLabel, resolveMember } from '../core/members.js';
7
+ import { fetchModels } from './model.js';
7
8
  /** App tokens accepted by `--app`; mirrors WorkspaceApplication on the backend. */
8
9
  const APPS = {
9
10
  dataset: 'Dataset',
@@ -342,6 +343,56 @@ export function workspaceCommand(globals) {
342
343
  await client.delete(`/workspace/list/${wsId}/member/${m.memberId}`);
343
344
  render({ removed: targets.map((m) => m.userId) }, () => success(`Removed ${names}`));
344
345
  });
346
+ ws.command('leave <id>')
347
+ .description('Leave a workspace you were invited to (owners cannot leave)')
348
+ .option('-y, --yes', 'skip confirmation')
349
+ .action(async (id, opts) => {
350
+ const g = globals();
351
+ const client = new WjiClient(g);
352
+ const w = await client.get(`/workspace/list/${id}/basic`).catch(() => ({ name: id }));
353
+ if (!(await confirm(`Leave workspace "${w.name}" (${id})? You need a new invite to come back.`, opts.yes))) {
354
+ return info('Aborted');
355
+ }
356
+ const result = await client.post(`/workspace/list/${id}/leave`);
357
+ // A left workspace 403s from now on, so stop pointing the profile at it.
358
+ if (getProfile(g.profile).profile.workspace === id)
359
+ updateProfile(g.profile, { workspace: undefined });
360
+ render(result, () => success(`Left ${w.name} (${id})`));
361
+ });
362
+ // ── default models ─────────────────────────────────────────────────────────
363
+ ws.command('defaults [id]')
364
+ .description('Show or set the workspace default models (pass "none" to clear one)')
365
+ .option('--chat <model>', 'default chat model for new conversations')
366
+ .option('--image <model>', 'default image model')
367
+ .option('--video <model>', 'default video model')
368
+ .option('--storyboard <model>', 'default storyboard model')
369
+ .option('--audio <model>', 'default audio model')
370
+ .option('--image-resolution <value>', 'default image resolution, e.g. 2K')
371
+ .option('--video-resolution <value>', 'default video resolution, e.g. 720p')
372
+ .action(async (id, opts) => {
373
+ const client = new WjiClient(globals());
374
+ const wsId = client.requireWorkspace(id);
375
+ // settings only comes back at full detail, and PUT replaces settings whole —
376
+ // read it fresh so prompts / defaultApp / improvePromptDefault survive.
377
+ const w = await client.getFresh(`/workspace/list/${wsId}`, { detail: 'full' });
378
+ const current = { ...(w.settings?.defaultModels || {}) };
379
+ const changes = DEFAULT_MODEL_KEYS.filter((key) => opts[key] !== undefined);
380
+ if (!changes.length) {
381
+ return render(current, () => detail(DEFAULT_MODEL_KEYS.map((key) => [key, current[key] ?? color.dim('—')])));
382
+ }
383
+ const next = { ...current };
384
+ const needsModels = changes.some((key) => !key.endsWith('Resolution') && !isNone(opts[key]));
385
+ const models = needsModels ? await fetchModels(client, { workspace: wsId }) : [];
386
+ for (const key of changes) {
387
+ const raw = String(opts[key]).trim();
388
+ next[key] = isNone(raw) ? null : key.endsWith('Resolution') ? raw : resolveDefaultModel(models, key, raw);
389
+ }
390
+ await client.put(`/workspace/list/${wsId}`, { settings: { ...(w.settings || {}), defaultModels: next } });
391
+ render(next, () => {
392
+ success('Default models updated');
393
+ detail(DEFAULT_MODEL_KEYS.map((key) => [key, next[key] ?? color.dim('—')]));
394
+ });
395
+ });
345
396
  // ── quota / stats ──────────────────────────────────────────────────────────
346
397
  ws.command('quota [id]')
347
398
  .description('Show credit quota usage')
@@ -384,6 +435,28 @@ export function workspaceCommand(globals) {
384
435
  });
385
436
  return ws;
386
437
  }
438
+ const DEFAULT_MODEL_KEYS = [
439
+ 'chat',
440
+ 'image',
441
+ 'video',
442
+ 'storyboard',
443
+ 'audio',
444
+ 'imageResolution',
445
+ 'videoResolution',
446
+ ];
447
+ const isNone = (value) => ['none', 'null', ''].includes(String(value ?? '').trim().toLowerCase());
448
+ /**
449
+ * The web stores the chat default as the model `name` (what conversations carry)
450
+ * and the media defaults as the `nickname` its pickers emit — the value `-m` takes.
451
+ * Accept either spelling and store what the web would.
452
+ */
453
+ function resolveDefaultModel(models, key, value) {
454
+ const model = models.find((m) => m.nickname === value || m.name === value);
455
+ if (!model) {
456
+ throw new Error(`Unknown model "${value}" for --${key}. See \`wji models list\`.`);
457
+ }
458
+ return key === 'chat' ? model.name : model.nickname || model.name;
459
+ }
387
460
  export function printTree(node, prefix = '', connector = '') {
388
461
  if (!node)
389
462
  return;
package/dist/index.js CHANGED
@@ -23,6 +23,12 @@ import { backreelCommand } from './commands/backreel.js';
23
23
  // Read from package.json so `wji --version` cannot drift from the published
24
24
  // version — the skill compares the two to detect a stale install.
25
25
  const VERSION = createRequire(import.meta.url)('../package.json').version;
26
+ // `wji … | head` closes the pipe early; that is the reader being done, not a crash.
27
+ process.stdout.on('error', (err) => {
28
+ if (err.code === 'EPIPE')
29
+ process.exit(0);
30
+ throw err;
31
+ });
26
32
  const program = new Command();
27
33
  program
28
34
  .name('wji')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wji.ai/cli",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "Command line interface for the WJI AI platform",
5
5
  "license": "UNLICENSED",
6
6
  "author": "WJI AI",
@@ -84,6 +84,9 @@ Most commands take `--workspace`, but setting a default is less error-prone:
84
84
  ```bash
85
85
  wji ws list
86
86
  wji ws use <workspaceId>
87
+ wji ws defaults # default chat / image / video / audio models
88
+ wji ws defaults --chat <model> --video <nickname> # "none" clears one
89
+ wji ws leave <workspaceId> # leave a workspace you were invited to
87
90
  ```
88
91
 
89
92
  After that, `wji ws get`, `wji fs`, `wji gen` and `wji preset list` all default
@@ -319,8 +322,12 @@ wji agent skill-add <agentId> system:image user:<skillId>
319
322
  wji agent skills <agentId>
320
323
  wji agent versions <agentId> # every edit auto-snapshots a version
321
324
  wji agent restore <agentId> <versionId>
325
+ wji agent update <agentId> --max-steps 80 # model calls per chat turn (default 40, max 200)
322
326
  ```
323
327
 
328
+ Versions, files, update and restore are creator-only — a 403 `無權操作此 Agent`
329
+ on someone else's agent is expected.
330
+
324
331
  Skill refs are `system:<key>` or `user:<skillId>` (a bare id means `user:`).
325
332
  System keys currently include `image`, `document`, `workspace-dataset`,
326
333
  `canvas` and `seedance-25` — confirm with `wji skill system`.
@@ -493,6 +500,8 @@ wji backreel set <postId> --project-status in_production
493
500
  wji backreel credit add <postId> --kind creative --name "Wayne" --role 導演
494
501
  wji backreel credit add <postId> --npc @mika --role "飾 阿明" --job appearance
495
502
  wji backreel publish <postId> # unpublish takes it offline
503
+ wji backreel set <postId> --logline "一句話賣點" # always public, ≤200 chars
504
+ wji backreel creators --role investor # browse creators
496
505
  ```
497
506
 
498
507
  - Credits (max 30) are rewritten whole in order; `credit rm` goes by number.
@@ -72,6 +72,8 @@ switch endpoint.
72
72
  | `ws update [id] [-n] [-d] [-r] [-l] [--set]` | `PUT /workspace/list/:id` |
73
73
  | `ws rm <id> [-y]` | `DELETE /workspace/list/:id` (owner only) |
74
74
  | `ws favorite <id> [--off]` | `PUT /workspace/list/:id/favorite` |
75
+ | `ws leave <id> [-y]` | `POST /workspace/list/:id/leave` — any member except the owner; coming back needs a new invite. Clears the profile default if it pointed there |
76
+ | `ws defaults [id] [--chat] [--image] [--video] [--storyboard] [--audio] [--image-resolution] [--video-resolution]` | no flags: shows `settings.defaultModels`. With flags: reads the workspace at `detail=full`, merges, `PUT /workspace/list/:id {settings}` (settings is replaced whole, so never send a partial `settings` via `ws update --set`). `none` clears one. Chat is stored as the model **name** (what conversations carry), media as the **nickname**; either spelling is accepted. The chat default is used for new conversations in the workspace |
75
77
  | `ws fs [id] [-a appId]` | `GET /workspace/list/:id/fs[/:appid]` |
76
78
  | `ws app list\|add <app> [-n]\|rm <appId> [-y]` | `POST/DELETE /workspace/list/:id/apps` |
77
79
  | `ws member list [id] [-r role]` | members from `GET /workspace/list/:id` (there is no member-list endpoint): name, email, role, user id, membership id |
@@ -275,16 +277,20 @@ the CLI rejects both up front.
275
277
  | --- | --- |
276
278
  | `backreel list [--all] [-k] [--page] [--limit]` | `GET /backreel/post/search/my` (`--all`: `GET /backreel/post/search`) |
277
279
  | `backreel get <postId>` | `GET /backreel/post/list/:id` |
278
- | `backreel create <title> [--kind showcase\|crowdfunding] [--category] [--project-status] [--tags a,b] [-d text\|@file] [--cover assetId] [--cover-portrait] [--trailer] [--body json]` | `POST /backreel/post/create` (always a draft) |
280
+ | `backreel create <title> [--kind showcase\|crowdfunding] [--logline] [--category] [--project-status] [--tags a,b] [-d text\|@file] [--cover assetId] [--cover-portrait] [--trailer] [--body json]` | `POST /backreel/post/create` (always a draft) |
279
281
  | `backreel set <postId> [--title] [same flags]` | `PUT /backreel/post/list/:id` with only the given fields |
280
282
  | `backreel publish\|unpublish <postId>` | `POST /backreel/post/list/:id/publish\|unpublish` (publish needs a cover, media or an episode) |
281
283
  | `backreel rm <postId> [-y]` | `DELETE /backreel/post/list/:id` |
284
+ | `backreel creators [-k] [--role kind] [--verified] [--no-featured] [--page] [--limit ≤48]` | `GET /backreel/creator/list` — editor-picked `featured` plus a heat-ranked page (recent likes, favorites ×2); creators with no recent activity are not listed. `--role`: investor, production_company, brand, distributor, sales_agent |
282
285
  | `backreel credit list <postId>` | the post's `credits[]`, numbered |
283
286
  | `backreel credit add <postId> --role [--name] [--kind cast\|crew\|creative] [--npc] [--user] [--link] [--job <type>]` | read, append, `PUT {credits}`; `--job` then appends to the NPC's `jobs` (own NPCs only, checked first) |
284
287
  | `backreel credit rm <postId> <n...> [-y]` | read, drop, `PUT {credits}` |
285
288
 
286
289
  Project status: planning, pre_production, in_production, post_production,
287
- completed. `-d` plain text becomes `<p>` paragraphs; HTML passes through and
290
+ completed. `--logline` is a one-line hook (≤200 chars, HTML stripped, `""`
291
+ clears it) shown large on the post page and **always public**, even when the
292
+ description is gated. The post detail no longer carries playable URLs for VOD
293
+ episodes — playback is gated per episode on the web. `-d` plain text becomes `<p>` paragraphs; HTML passes through and
288
294
  is sanitized server-side. Admin routes (`/api/backreel/*`) reject `sk_` keys.
289
295
 
290
296
  ## gen
@@ -361,7 +367,7 @@ Features: `text`, `image`, `video`, `audio`, `tts`, `web`, `thinking`,
361
367
  ## agent
362
368
 
363
369
  `agent list` (public marketplace) · `agent mine` · `agent get <id>` ·
364
- `agent create <name> -d [-i instructions] [-m model] [--visibility] [-c] [-t] [--greeting]` ·
370
+ `agent create <name> -d [-i instructions] [-m model] [--visibility] [-c] [-t] [--greeting] [--max-steps n]` ·
365
371
  `agent update <id> …` · `agent rm <id> [-y]` · `agent files <id>` ·
366
372
  `agent categories [--mine]` · `agent tags [--mine]` · `agent recommend`
367
373
 
@@ -380,6 +386,17 @@ takes `--skill <ref...>`.
380
386
  Visibility: `private`, `unlist`, `public`. Instructions are only returned to
381
387
  the agent's creator.
382
388
 
389
+ Ownership (backend since Oct 2026): a private agent is readable only by its
390
+ creator; `update`, `rm`, `files`, `versions` and `restore` are creator-only on
391
+ every agent (403 `無權操作此 Agent` otherwise — not a bug). `agent create`
392
+ needs the account's agent-create permission (without it: 400 `您沒有此功能的訪問權限`). `--set` cannot change `creator` or
393
+ `organization`, and non-admins cannot set `key`, `isSystem`, `isHighligted`,
394
+ `recommendTitle`, `weight` or `store` — the server drops them silently.
395
+
396
+ `--max-steps n` caps model calls per chat turn (1–200; parallel tool calls in
397
+ one step count once). Unset means the site default, 40. `update --max-steps
398
+ default` clears it.
399
+
383
400
  Field caps: name ≤200, model ≤100, description and greeting ≤5,000,
384
401
  instructions ≤200,000 characters.
385
402