@wji.ai/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +242 -0
- package/dist/commands/agent.js +274 -0
- package/dist/commands/asset.js +241 -0
- package/dist/commands/auth.js +216 -0
- package/dist/commands/canvas.js +915 -0
- package/dist/commands/config.js +113 -0
- package/dist/commands/fsnode.js +252 -0
- package/dist/commands/gen.js +366 -0
- package/dist/commands/model.js +134 -0
- package/dist/commands/org.js +85 -0
- package/dist/commands/preset.js +176 -0
- package/dist/commands/profile.js +129 -0
- package/dist/commands/skill.js +272 -0
- package/dist/commands/workspace.js +305 -0
- package/dist/core/client.js +252 -0
- package/dist/core/config.js +95 -0
- package/dist/core/output.js +143 -0
- package/dist/core/prompt.js +118 -0
- package/dist/core/zip.js +87 -0
- package/dist/index.js +94 -0
- package/package.json +49 -0
- package/skill/wji/SKILL.md +398 -0
- package/skill/wji/reference/canvas-recipes.md +179 -0
- package/skill/wji/reference/commands.md +381 -0
package/README.md
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# wji-cli
|
|
2
|
+
|
|
3
|
+
Command line interface for the WJI AI platform (`wji-backend`).
|
|
4
|
+
|
|
5
|
+
Covers auth, workspaces, the fsnode tree (ideas / datasets / files), asset
|
|
6
|
+
uploads, image / video / audio generation, model discovery, agents, prompt
|
|
7
|
+
presets and your profile.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g @wji.ai/cli # or: pnpm add -g @wji.ai/cli / yarn global add @wji.ai/cli
|
|
13
|
+
wji --version
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Requires Node 20+ (uses built-in `fetch`, `FormData` and `Blob`).
|
|
17
|
+
|
|
18
|
+
From source:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
git clone https://github.com/ultra-instinct-ai/wji-cli.git && cd wji-cli
|
|
22
|
+
pnpm install && pnpm build
|
|
23
|
+
pnpm link --global # puts `wji` on your PATH
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quick start
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
wji auth login --email you@example.com
|
|
30
|
+
wji ws list
|
|
31
|
+
wji ws use <workspaceId> # remembered for later commands
|
|
32
|
+
wji models list --feature image # pass the MODEL column to -m
|
|
33
|
+
wji gen image -m nano-banana -p "a rainy alley at dusk" --ratio 16:9 --wait
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Against a local backend, point at the API port (the Nuxt dev server on `:3000`
|
|
37
|
+
is *not* the API):
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
wji config set endpoint http://localhost:3002
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Authentication
|
|
44
|
+
|
|
45
|
+
`wji auth login` stores a JWT pair in `~/.wji/config.json` (mode `0600`) and
|
|
46
|
+
refreshes it automatically when the access token expires.
|
|
47
|
+
|
|
48
|
+
Each refresh rotates the refresh token, and the server revokes **every**
|
|
49
|
+
session if a used refresh token is presented again. Parallel `wji` processes
|
|
50
|
+
therefore share one refresh through a lock file beside the config
|
|
51
|
+
(`.refresh-<profile>.lock`) and re-read the config before refreshing. `wji auth
|
|
52
|
+
logout` revokes the refresh token server-side; an issued access token stays
|
|
53
|
+
valid until it expires.
|
|
54
|
+
|
|
55
|
+
For automation, prefer a long-lived API key:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
wji auth key create "ci" --save # secret is printed once
|
|
59
|
+
wji auth key list
|
|
60
|
+
wji auth key revoke <id>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Credential precedence:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
--token > $WJI_TOKEN / $WJI_API_KEY > profile.apiKey > profile.accessToken
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`sk_…` keys are never refreshed; a JWT is.
|
|
70
|
+
|
|
71
|
+
## Profiles, endpoints and orgs
|
|
72
|
+
|
|
73
|
+
A profile bundles an endpoint, credentials, an org context and a default
|
|
74
|
+
workspace. The default endpoint is `https://api.wji.ai`.
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
wji config use staging
|
|
78
|
+
wji config set endpoint http://localhost:3002
|
|
79
|
+
wji config profiles
|
|
80
|
+
wji --profile staging ws list # one-off override
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Organization context is the `X-Org-Id` header, not a separate login:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
wji org list
|
|
87
|
+
wji org use <orgId>
|
|
88
|
+
wji org personal # back to your personal space
|
|
89
|
+
wji --no-org ws list # one-off personal call
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Commands
|
|
93
|
+
|
|
94
|
+
| Group | What it does |
|
|
95
|
+
| --- | --- |
|
|
96
|
+
| `wji auth` | login / signup / token / whoami / refresh / logout, `auth key` for API keys |
|
|
97
|
+
| `wji config` | profiles, endpoint, defaults |
|
|
98
|
+
| `wji org` | organization context, members |
|
|
99
|
+
| `wji ws` | workspaces, apps, members, quota, stats, templates |
|
|
100
|
+
| `wji fs` | fsnode tree: ls, tree, get, mkdir, touch, set, describe, rm, sort, batch |
|
|
101
|
+
| `wji dataset` | dataset nodes (character / location / prop / …) and dataset records |
|
|
102
|
+
| `wji asset` | upload, import from URL, search, status, download, summary |
|
|
103
|
+
| `wji canvas` | Infinite Canvas: linked canvas + selection, nodes, references, generate nodes, submit |
|
|
104
|
+
| `wji gen` | `image` / `video` / `audio` generation plus `cost` estimation |
|
|
105
|
+
| `wji history` | generation history: search, poll, bookmark, download |
|
|
106
|
+
| `wji models` | list models and their form parameters |
|
|
107
|
+
| `wji agent` | browse and manage AI agents |
|
|
108
|
+
| `wji preset` | prompt presets: yours, public, workspace; fork and copy |
|
|
109
|
+
| `wji skill` | agent skills: system list, push/pull SKILL.md bundles, import zip, update, rm |
|
|
110
|
+
| `wji profile` | account profile, password, wallet, login history |
|
|
111
|
+
|
|
112
|
+
Run `wji <group> --help` for the full option list.
|
|
113
|
+
|
|
114
|
+
## Conventions
|
|
115
|
+
|
|
116
|
+
- **`--json`** on any command prints the raw API response and nothing else, so
|
|
117
|
+
everything composes with `jq`.
|
|
118
|
+
- **`@file` and `-`** work anywhere a long text or JSON value is expected:
|
|
119
|
+
`-p @prompt.txt`, `--content @node.json`, `-p -` to read stdin.
|
|
120
|
+
- **`--set key=value`** sends arbitrary fields; dotted keys nest
|
|
121
|
+
(`--set values.quality=high`), and `true` / `false` / `null` / numbers /
|
|
122
|
+
JSON literals are parsed.
|
|
123
|
+
- **References** are `assetId[:label[:weight]]`, e.g.
|
|
124
|
+
`--ref 65f0…:hero:0.6`.
|
|
125
|
+
- **Destructive commands** confirm interactively; `--yes` skips, and they refuse
|
|
126
|
+
to run unconfirmed on a non-TTY.
|
|
127
|
+
- Server-side filters are only offered where the API actually implements them.
|
|
128
|
+
Agent `--name` / `--category` / `--tag` are exact matches, and `wji asset
|
|
129
|
+
list` filters only by `--store`; for anything else, pipe `--json` into `jq`.
|
|
130
|
+
|
|
131
|
+
## Generation
|
|
132
|
+
|
|
133
|
+
Generation is asynchronous. By default `wji gen` writes a *history* entry you
|
|
134
|
+
can poll:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
wji gen video -m wan-3.0 -p "slow pan across the rooftop" -d 5 \
|
|
138
|
+
--start-image <assetId> --wait --timeout 1200
|
|
139
|
+
|
|
140
|
+
wji history get <historyId> --wait
|
|
141
|
+
wji history download <historyId> -o ./out
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Check the price first with `wji gen cost -t video -m <model> -d 5`.
|
|
145
|
+
|
|
146
|
+
Model-specific parameters come from the model's own form schema — see
|
|
147
|
+
`wji models get <model>` — and are passed with `--set` or `--values`.
|
|
148
|
+
|
|
149
|
+
`-m` takes the value in the `MODEL` column, which is the model's `nickname`.
|
|
150
|
+
That is the unique key; plain names collide across providers.
|
|
151
|
+
|
|
152
|
+
## Canvas
|
|
153
|
+
|
|
154
|
+
Open a canvas in the browser and press the **link** button in its top bar. The
|
|
155
|
+
open workspace, canvas and selected nodes are then available to the CLI for
|
|
156
|
+
5 minutes (renewed while the tab is open), and `wji canvas` defaults to that
|
|
157
|
+
canvas:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
wji canvas status # what is linked and selected
|
|
161
|
+
wji canvas add video --from <img1> <img2> -m seedance-2.0-fast-ref -r 16:9 -d 8 \
|
|
162
|
+
-p "@image1 paddles across the lake while @image2 waits on the shore"
|
|
163
|
+
wji canvas set <node> --resolution 720p --set seed=42
|
|
164
|
+
wji canvas gen <node> --yes --wait # spends credits
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Changes appear live in the open canvas. Pass `--canvas <id>` to work on a
|
|
168
|
+
canvas that is not linked (`wji canvas list`).
|
|
169
|
+
|
|
170
|
+
## Skills
|
|
171
|
+
|
|
172
|
+
Skills are SKILL.md bundles (markdown only — scripts don't persist server-side).
|
|
173
|
+
System skills are built-in; user skills are yours to create and attach to agents.
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
wji skill system # built-in skills (image, document, …)
|
|
177
|
+
wji skill push ./my-skill # zip a local folder (needs SKILL.md at root) and upload
|
|
178
|
+
wji skill list
|
|
179
|
+
wji skill pull <id> ./my-skill # write the bundle back to disk
|
|
180
|
+
wji skill cat <id> # print its SKILL.md
|
|
181
|
+
wji skill rm <id>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Attach skills to an agent, and use agent version history:
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
wji agent skill-add <agentId> system:image user:<skillId>
|
|
188
|
+
wji agent skills <agentId>
|
|
189
|
+
wji agent versions <agentId> # every edit auto-snapshots a version
|
|
190
|
+
wji agent restore <agentId> <versionId>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Skill refs are `system:<key>` or `user:<skillId>` (a bare id is treated as `user:`).
|
|
194
|
+
|
|
195
|
+
## Server limits worth knowing
|
|
196
|
+
|
|
197
|
+
| Area | Limit |
|
|
198
|
+
| --- | --- |
|
|
199
|
+
| Generation submissions | image 30 / video 10 / audio 20 per minute, per user or key |
|
|
200
|
+
| Generation queue | queued + running jobs: FREE 3 / VIP 10 / SVIP 25 → HTTP 429 |
|
|
201
|
+
| Pagination | `--limit` ≤100; `-1` returns at most 500 except on opt-in endpoints |
|
|
202
|
+
| Batches | `asset status` is sent 100 ids at a time; `asset summary` ≤100 ids |
|
|
203
|
+
| Skills | zip ≤5MB, ≤200 files, ≤2MB per `.md`, ≤20MB total, 50 per account |
|
|
204
|
+
| Creation caps | 50 workspaces, 3 organizations per account |
|
|
205
|
+
| Remote import | private / internal hosts are refused, including via redirect |
|
|
206
|
+
|
|
207
|
+
Rejected requests (429 / 400) are refused before any credit is charged.
|
|
208
|
+
|
|
209
|
+
## Exit codes
|
|
210
|
+
|
|
211
|
+
| Code | Meaning |
|
|
212
|
+
| --- | --- |
|
|
213
|
+
| `0` | success |
|
|
214
|
+
| `1` | error (validation, network, API 4xx/5xx) |
|
|
215
|
+
| `77` | authentication or permission failure (401 / 403) |
|
|
216
|
+
|
|
217
|
+
## Environment variables
|
|
218
|
+
|
|
219
|
+
| Variable | Effect |
|
|
220
|
+
| --- | --- |
|
|
221
|
+
| `WJI_ENDPOINT` | API base URL |
|
|
222
|
+
| `WJI_TOKEN` / `WJI_API_KEY` | credential for this process |
|
|
223
|
+
| `WJI_PROFILE` | profile to use |
|
|
224
|
+
| `WJI_ORG_ID` | organization context |
|
|
225
|
+
| `WJI_WORKSPACE` | default workspace |
|
|
226
|
+
| `WJI_LOCALE` | response language (`cht`, `chs`, `en`) |
|
|
227
|
+
| `WJI_CONFIG_FILE` / `WJI_CONFIG_DIR` | relocate the config |
|
|
228
|
+
| `NO_COLOR` | disable colour |
|
|
229
|
+
|
|
230
|
+
## Claude Code skill
|
|
231
|
+
|
|
232
|
+
`skill/wji/` is an Agent Skill that teaches Claude Code to drive this CLI:
|
|
233
|
+
|
|
234
|
+
The npm package ships it. After a global install:
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
mkdir -p ~/.claude/skills
|
|
238
|
+
cp -r "$(npm root -g)/@wji.ai/cli/skill/wji" ~/.claude/skills/wji
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
From a source checkout: `cp -r skill/wji ~/.claude/skills/wji`. Re-copy after
|
|
242
|
+
upgrading the CLI so the skill matches the commands.
|
|
@@ -0,0 +1,274 @@
|
|
|
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, relativeTime, render, success, table, truncate } from '../core/output.js';
|
|
5
|
+
/** Parse a skill ref token: `system:<key>` or `user:<id>` (bare = user id). */
|
|
6
|
+
function parseSkillRef(token) {
|
|
7
|
+
const [kindRaw, rest] = token.includes(':') ? token.split(/:(.+)/) : ['user', token];
|
|
8
|
+
const kind = kindRaw === 'system' ? 'system' : 'user';
|
|
9
|
+
if (!rest)
|
|
10
|
+
throw new Error(`Invalid skill ref "${token}" — use system:<key> or user:<id>`);
|
|
11
|
+
return kind === 'system' ? { kind, key: rest } : { kind, id: rest };
|
|
12
|
+
}
|
|
13
|
+
function skillRefLabel(ref) {
|
|
14
|
+
if (!ref?.kind)
|
|
15
|
+
return String(ref);
|
|
16
|
+
return ref.kind === 'system' ? `system:${ref.key}` : `user:${ref.id}`;
|
|
17
|
+
}
|
|
18
|
+
export function agentCommand(globals) {
|
|
19
|
+
const agent = new Command('agent').description('Manage AI agents');
|
|
20
|
+
agent
|
|
21
|
+
.command('list', { isDefault: true })
|
|
22
|
+
.description('Browse the public agent marketplace')
|
|
23
|
+
.option('-n, --name <name>', 'exact agent name')
|
|
24
|
+
.option('-c, --category <name>', 'exact category')
|
|
25
|
+
.option('-t, --tag <tag>', 'agents carrying this tag')
|
|
26
|
+
.option('--include-unlisted', 'also show unlisted agents')
|
|
27
|
+
.option('--page <n>', 'page number', '1')
|
|
28
|
+
.option('--limit <n>', 'page size', '20')
|
|
29
|
+
.action(async (opts) => {
|
|
30
|
+
const client = new WjiClient(globals());
|
|
31
|
+
const result = await client.get('/ai/agent/search', {
|
|
32
|
+
name: opts.name,
|
|
33
|
+
category: opts.category,
|
|
34
|
+
tags: opts.tag,
|
|
35
|
+
includeUnlisted: opts.includeUnlisted ? 'true' : undefined,
|
|
36
|
+
page: Number(opts.page),
|
|
37
|
+
limit: Number(opts.limit),
|
|
38
|
+
});
|
|
39
|
+
render(result, (r) => printAgentTable(r));
|
|
40
|
+
});
|
|
41
|
+
agent
|
|
42
|
+
.command('mine')
|
|
43
|
+
.description('List agents you created')
|
|
44
|
+
.option('-n, --name <name>', 'exact agent name')
|
|
45
|
+
.option('-c, --category <name>', 'exact category')
|
|
46
|
+
.option('--visibility <v>', 'public | private | unlist')
|
|
47
|
+
.option('--page <n>', 'page number', '1')
|
|
48
|
+
.option('--limit <n>', 'page size', '20')
|
|
49
|
+
.action(async (opts) => {
|
|
50
|
+
const client = new WjiClient(globals());
|
|
51
|
+
const result = await client.get('/ai/agent/search/my', {
|
|
52
|
+
name: opts.name,
|
|
53
|
+
category: opts.category,
|
|
54
|
+
visibility: opts.visibility,
|
|
55
|
+
page: Number(opts.page),
|
|
56
|
+
limit: Number(opts.limit),
|
|
57
|
+
});
|
|
58
|
+
render(result, (r) => printAgentTable(r));
|
|
59
|
+
});
|
|
60
|
+
agent
|
|
61
|
+
.command('get <id>')
|
|
62
|
+
.description('Show an agent (instructions included when you own it)')
|
|
63
|
+
.action(async (id) => {
|
|
64
|
+
const client = new WjiClient(globals());
|
|
65
|
+
const a = await client.get(`/ai/agent/list/${id}`);
|
|
66
|
+
render(a, () => detail([
|
|
67
|
+
['id', a.id],
|
|
68
|
+
['name', a.name],
|
|
69
|
+
['description', a.description],
|
|
70
|
+
['model', a.model],
|
|
71
|
+
['visibility', a.visibility],
|
|
72
|
+
['category', a.category],
|
|
73
|
+
['tags', (a.tags || []).join(', ')],
|
|
74
|
+
['greeting', a.greeting],
|
|
75
|
+
['creator', a.creator?.name || a.creator?.id || a.creator],
|
|
76
|
+
['organization', a.organization?.id || a.organization || 'personal'],
|
|
77
|
+
['skills', (a.skills || []).map(skillRefLabel).join(', ')],
|
|
78
|
+
['instructions', a.instructions],
|
|
79
|
+
]));
|
|
80
|
+
});
|
|
81
|
+
agent
|
|
82
|
+
.command('create <name>')
|
|
83
|
+
.description('Create an agent')
|
|
84
|
+
.requiredOption('-d, --description <text>', 'short description')
|
|
85
|
+
.option('-i, --instructions <text>', 'system prompt (@file / - supported)')
|
|
86
|
+
.option('-m, --model <name>', 'backing model')
|
|
87
|
+
.option('--visibility <v>', 'private | unlist | public', 'private')
|
|
88
|
+
.option('-c, --category <name>', 'category')
|
|
89
|
+
.option('-t, --tag <tag...>', 'tags')
|
|
90
|
+
.option('--greeting <text>', 'opening message')
|
|
91
|
+
.option('--skill <ref...>', 'attach skills: system:<key> or user:<skillId>')
|
|
92
|
+
.option('--set <key=value...>', 'arbitrary field')
|
|
93
|
+
.action(async (name, opts) => {
|
|
94
|
+
const client = new WjiClient(globals());
|
|
95
|
+
const created = await client.post('/ai/agent/create', {
|
|
96
|
+
name,
|
|
97
|
+
description: opts.description,
|
|
98
|
+
instructions: opts.instructions ? await readValue(opts.instructions) : undefined,
|
|
99
|
+
model: opts.model,
|
|
100
|
+
visibility: opts.visibility,
|
|
101
|
+
category: opts.category,
|
|
102
|
+
tags: opts.tag,
|
|
103
|
+
greeting: opts.greeting,
|
|
104
|
+
skills: opts.skill?.length ? opts.skill.map(parseSkillRef) : undefined,
|
|
105
|
+
...parseKeyValues(opts.set),
|
|
106
|
+
});
|
|
107
|
+
render(created, (a) => success(`Created agent ${a.name} (${a.id})`));
|
|
108
|
+
});
|
|
109
|
+
agent
|
|
110
|
+
.command('update <id>')
|
|
111
|
+
.description('Update an agent')
|
|
112
|
+
.option('-n, --name <name>', 'new name')
|
|
113
|
+
.option('-d, --description <text>', 'new description')
|
|
114
|
+
.option('-i, --instructions <text>', 'new system prompt (@file / - supported)')
|
|
115
|
+
.option('-m, --model <name>', 'new model')
|
|
116
|
+
.option('--visibility <v>', 'private | unlist | public')
|
|
117
|
+
.option('-c, --category <name>', 'category')
|
|
118
|
+
.option('-t, --tag <tag...>', 'replace tags')
|
|
119
|
+
.option('--greeting <text>', 'opening message')
|
|
120
|
+
.option('--set <key=value...>', 'arbitrary field')
|
|
121
|
+
.action(async (id, opts) => {
|
|
122
|
+
const client = new WjiClient(globals());
|
|
123
|
+
const updated = await client.put(`/ai/agent/list/${id}`, {
|
|
124
|
+
name: opts.name,
|
|
125
|
+
description: opts.description,
|
|
126
|
+
instructions: opts.instructions ? await readValue(opts.instructions) : undefined,
|
|
127
|
+
model: opts.model,
|
|
128
|
+
visibility: opts.visibility,
|
|
129
|
+
category: opts.category,
|
|
130
|
+
tags: opts.tag,
|
|
131
|
+
greeting: opts.greeting,
|
|
132
|
+
...parseKeyValues(opts.set),
|
|
133
|
+
});
|
|
134
|
+
render(updated, () => success(`Updated ${id}`));
|
|
135
|
+
});
|
|
136
|
+
agent
|
|
137
|
+
.command('rm <id>')
|
|
138
|
+
.description('Delete an agent')
|
|
139
|
+
.option('-y, --yes', 'skip confirmation')
|
|
140
|
+
.action(async (id, opts) => {
|
|
141
|
+
const client = new WjiClient(globals());
|
|
142
|
+
const a = await client.get(`/ai/agent/list/${id}`).catch(() => ({ name: id }));
|
|
143
|
+
if (!(await confirm(`Delete agent "${a.name}" (${id})?`, opts.yes)))
|
|
144
|
+
return info('Aborted');
|
|
145
|
+
const result = await client.delete(`/ai/agent/list/${id}`);
|
|
146
|
+
render(result, () => success(`Deleted ${id}`));
|
|
147
|
+
});
|
|
148
|
+
agent
|
|
149
|
+
.command('files <id>')
|
|
150
|
+
.description('List files attached to an agent')
|
|
151
|
+
.action(async (id) => {
|
|
152
|
+
const client = new WjiClient(globals());
|
|
153
|
+
const files = await client.get(`/ai/agent/list/${id}/files`);
|
|
154
|
+
render(files, (rows) => table(rows || [], [
|
|
155
|
+
{ header: 'id', value: (f) => f.id },
|
|
156
|
+
{ header: 'name', value: (f) => f.name },
|
|
157
|
+
{ header: 'type', value: (f) => f.type },
|
|
158
|
+
]));
|
|
159
|
+
});
|
|
160
|
+
agent
|
|
161
|
+
.command('categories')
|
|
162
|
+
.description('List agent categories')
|
|
163
|
+
.option('--mine', 'only categories used by your agents')
|
|
164
|
+
.action(async (opts) => {
|
|
165
|
+
const client = new WjiClient(globals());
|
|
166
|
+
const list = await client.get(opts.mine ? '/ai/agent/categories/my' : '/ai/agent/categories');
|
|
167
|
+
render(list, (rows) => rows.filter(Boolean).forEach((c) => process.stdout.write(c + '\n')));
|
|
168
|
+
});
|
|
169
|
+
agent
|
|
170
|
+
.command('tags')
|
|
171
|
+
.description('List agent tags')
|
|
172
|
+
.option('--mine', 'only tags used by your agents')
|
|
173
|
+
.action(async (opts) => {
|
|
174
|
+
const client = new WjiClient(globals());
|
|
175
|
+
const list = await client.get(opts.mine ? '/ai/agent/tags/my' : '/ai/agent/tags');
|
|
176
|
+
render(list, (rows) => rows.filter(Boolean).forEach((t) => process.stdout.write(t + '\n')));
|
|
177
|
+
});
|
|
178
|
+
agent
|
|
179
|
+
.command('recommend')
|
|
180
|
+
.description('Show recommended agents')
|
|
181
|
+
.action(async () => {
|
|
182
|
+
const client = new WjiClient(globals());
|
|
183
|
+
const rows = await client.get('/ai/agent/recommend');
|
|
184
|
+
render(rows, (list) => table(list || [], [
|
|
185
|
+
{ header: 'id', value: (a) => a.id },
|
|
186
|
+
{ header: 'name', value: (a) => a.name },
|
|
187
|
+
{ header: 'title', value: (a) => truncate(a.recommendTitle, 40) },
|
|
188
|
+
]));
|
|
189
|
+
});
|
|
190
|
+
agent
|
|
191
|
+
.command('skills <id>')
|
|
192
|
+
.description('Show the skills attached to an agent')
|
|
193
|
+
.action(async (id) => {
|
|
194
|
+
const client = new WjiClient(globals());
|
|
195
|
+
const a = await client.get(`/ai/agent/list/${id}`);
|
|
196
|
+
render(a.skills || [], (rows) => {
|
|
197
|
+
if (!rows.length)
|
|
198
|
+
return info(color.dim('(no skills attached)'));
|
|
199
|
+
table(rows, [
|
|
200
|
+
{ header: 'kind', value: (r) => r.kind },
|
|
201
|
+
{ header: 'ref', value: (r) => (r.kind === 'system' ? r.key : r.id) },
|
|
202
|
+
{ header: 'name', value: (r) => r.name },
|
|
203
|
+
]);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
agent
|
|
207
|
+
.command('skill-add <id> <ref...>')
|
|
208
|
+
.description('Attach skills to an agent (refs: system:<key> or user:<skillId>)')
|
|
209
|
+
.action(async (id, refs) => {
|
|
210
|
+
const client = new WjiClient(globals());
|
|
211
|
+
const a = await client.get(`/ai/agent/list/${id}`);
|
|
212
|
+
const existing = (a.skills || []);
|
|
213
|
+
const seen = new Set(existing.map(skillRefLabel));
|
|
214
|
+
const added = [];
|
|
215
|
+
for (const token of refs) {
|
|
216
|
+
const ref = parseSkillRef(token);
|
|
217
|
+
if (seen.has(skillRefLabel(ref)))
|
|
218
|
+
continue;
|
|
219
|
+
existing.push(ref);
|
|
220
|
+
seen.add(skillRefLabel(ref));
|
|
221
|
+
added.push(skillRefLabel(ref));
|
|
222
|
+
}
|
|
223
|
+
const updated = await client.put(`/ai/agent/list/${id}`, { skills: existing });
|
|
224
|
+
render(updated, () => success(added.length ? `Attached ${added.join(', ')}` : 'No new skills to attach'));
|
|
225
|
+
});
|
|
226
|
+
agent
|
|
227
|
+
.command('skill-remove <id> <ref...>')
|
|
228
|
+
.description('Detach skills from an agent (refs: system:<key> or user:<skillId>)')
|
|
229
|
+
.action(async (id, refs) => {
|
|
230
|
+
const client = new WjiClient(globals());
|
|
231
|
+
const a = await client.get(`/ai/agent/list/${id}`);
|
|
232
|
+
const drop = new Set(refs.map((t) => skillRefLabel(parseSkillRef(t))));
|
|
233
|
+
const kept = (a.skills || []).filter((r) => !drop.has(skillRefLabel(r)));
|
|
234
|
+
const updated = await client.put(`/ai/agent/list/${id}`, { skills: kept });
|
|
235
|
+
render(updated, () => success(`Removed ${refs.length} skill ref(s)`));
|
|
236
|
+
});
|
|
237
|
+
agent
|
|
238
|
+
.command('versions <id>')
|
|
239
|
+
.description('List an agent\'s version history')
|
|
240
|
+
.action(async (id) => {
|
|
241
|
+
const client = new WjiClient(globals());
|
|
242
|
+
const versions = await client.get(`/ai/agent/list/${id}/versions`);
|
|
243
|
+
render(versions, (rows) => table(rows || [], [
|
|
244
|
+
{ header: 'versionId', value: (v) => v.id },
|
|
245
|
+
{ header: 'v', value: (v) => v.version },
|
|
246
|
+
{ header: 'name', value: (v) => truncate(v.snapshot?.name, 24) },
|
|
247
|
+
{ header: 'model', value: (v) => v.snapshot?.model },
|
|
248
|
+
{ header: 'comment', value: (v) => truncate(v.comment, 30) },
|
|
249
|
+
{ header: 'when', value: (v) => relativeTime(v.createdAt) },
|
|
250
|
+
]));
|
|
251
|
+
});
|
|
252
|
+
agent
|
|
253
|
+
.command('restore <id> <versionId>')
|
|
254
|
+
.description('Restore an agent to a previous version')
|
|
255
|
+
.action(async (id, versionId) => {
|
|
256
|
+
const client = new WjiClient(globals());
|
|
257
|
+
const result = await client.post(`/ai/agent/list/${id}/versions/${versionId}/restore`);
|
|
258
|
+
render(result, () => success(`Restored ${id} to version ${versionId}`));
|
|
259
|
+
});
|
|
260
|
+
return agent;
|
|
261
|
+
}
|
|
262
|
+
function printAgentTable(result) {
|
|
263
|
+
const rows = result?.data || result || [];
|
|
264
|
+
table(rows, [
|
|
265
|
+
{ header: 'id', value: (a) => a.id },
|
|
266
|
+
{ header: 'name', value: (a) => truncate(a.name, 28) },
|
|
267
|
+
{ header: 'model', value: (a) => a.model },
|
|
268
|
+
{ header: 'visibility', value: (a) => a.visibility },
|
|
269
|
+
{ header: 'category', value: (a) => a.category },
|
|
270
|
+
{ header: 'updated', value: (a) => relativeTime(a.updatedAt) },
|
|
271
|
+
]);
|
|
272
|
+
if (result?.pagination)
|
|
273
|
+
info(color.dim(`${result.pagination.total} total`));
|
|
274
|
+
}
|