@xiashe/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 +31 -0
- package/bin/xiashe.mjs +928 -0
- package/package.json +18 -0
package/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# @xiashe/cli
|
|
2
|
+
|
|
3
|
+
`xiashe` exposes XiaShe core workflows from a local terminal or any shell-capable Agent.
|
|
4
|
+
|
|
5
|
+
## Local development
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
node packages/xiashe-cli/bin/xiashe.mjs --help
|
|
9
|
+
node packages/xiashe-cli/bin/xiashe.mjs config path
|
|
10
|
+
node packages/xiashe-cli/bin/xiashe.mjs env use cn
|
|
11
|
+
node packages/xiashe-cli/bin/xiashe.mjs login --email you@example.com --password '********' --convex-url https://...
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Core workflows
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
xiashe skills list
|
|
18
|
+
xiashe skills install copywriting --connection MaxClaw-1 --watch
|
|
19
|
+
xiashe agent issue --name "Local WorkBuddy"
|
|
20
|
+
xiashe agent connect --token "<token>" --name "Local WorkBuddy"
|
|
21
|
+
xiashe agent tasks watch --ack
|
|
22
|
+
xiashe chat send --chat "New Chat" --content "帮我处理这个文件" --file ./report.pdf --agent "Local WorkBuddy"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Local state is stored in `~/.xiashe/config.json`.
|
|
26
|
+
|
|
27
|
+
Production defaults:
|
|
28
|
+
|
|
29
|
+
- Convex: `https://api.xiashe.chat`
|
|
30
|
+
- Actions: `https://actions.xiashe.chat`
|
|
31
|
+
- Relay: `https://relay.xiashe.chat`
|
package/bin/xiashe.mjs
ADDED
|
@@ -0,0 +1,928 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
|
|
5
|
+
let sdk;
|
|
6
|
+
try {
|
|
7
|
+
sdk = await import('@xiashe/sdk');
|
|
8
|
+
} catch {
|
|
9
|
+
try {
|
|
10
|
+
sdk = await import('xiashe-sdk');
|
|
11
|
+
} catch {
|
|
12
|
+
sdk = await import('../../xiashe-sdk/index.mjs');
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const {
|
|
17
|
+
VERSION,
|
|
18
|
+
DEFAULTS,
|
|
19
|
+
XiasheClient,
|
|
20
|
+
configPath,
|
|
21
|
+
readConfig,
|
|
22
|
+
updateProfile,
|
|
23
|
+
useProfile,
|
|
24
|
+
useEnvironment,
|
|
25
|
+
loadProfile,
|
|
26
|
+
saveLoginSession,
|
|
27
|
+
saveAgentSession,
|
|
28
|
+
printableAgentPrompt,
|
|
29
|
+
createIdempotencyKey,
|
|
30
|
+
postJson,
|
|
31
|
+
mcpCall,
|
|
32
|
+
decodeMcpToolResult,
|
|
33
|
+
nowIso
|
|
34
|
+
} = sdk;
|
|
35
|
+
|
|
36
|
+
const EXIT_USAGE = 2;
|
|
37
|
+
const EXIT_AUTH = 3;
|
|
38
|
+
const EXIT_NETWORK = 4;
|
|
39
|
+
const EXIT_RUNTIME = 5;
|
|
40
|
+
const COMMAND_NAME = process.env.XIASHE_CLI_NAME || 'xiashe';
|
|
41
|
+
const PRODUCT_NAME = process.env.XIASHE_PRODUCT_NAME || (COMMAND_NAME === 'agentpie' ? 'AgentPie' : 'XiaShe');
|
|
42
|
+
|
|
43
|
+
function usage() {
|
|
44
|
+
const c = COMMAND_NAME;
|
|
45
|
+
return `${c} ${VERSION}
|
|
46
|
+
|
|
47
|
+
Usage:
|
|
48
|
+
${c} login --email <email> --password <password> --convex-url <url>
|
|
49
|
+
${c} signup --email <email> --password <password> --name <name>
|
|
50
|
+
${c} whoami
|
|
51
|
+
|
|
52
|
+
${c} config path|get|set <key> <value>|use <profile>
|
|
53
|
+
${c} env use cn|global
|
|
54
|
+
|
|
55
|
+
${c} connections list
|
|
56
|
+
${c} claw pair --connection <id-or-name>
|
|
57
|
+
${c} claw repair --connection <id-or-name>
|
|
58
|
+
${c} claw check --connection <id-or-name>
|
|
59
|
+
|
|
60
|
+
${c} skills list [query]
|
|
61
|
+
${c} skills detail <skill>
|
|
62
|
+
${c} skills install <skill> --connection <id-or-name> [--watch]
|
|
63
|
+
${c} skills jobs --connection <id-or-name>
|
|
64
|
+
${c} skills claim <one-time-token>
|
|
65
|
+
|
|
66
|
+
${c} agent issue [--name <name>] [--kind <kind>] [--json]
|
|
67
|
+
${c} agent connect --token <token> [--name <name>] [--kind <kind>]
|
|
68
|
+
${c} agent heartbeat
|
|
69
|
+
${c} agent list
|
|
70
|
+
${c} agent tasks list|watch|ack <taskId>|complete <taskId> [--content <text>]
|
|
71
|
+
${c} agent task create --agent <id-or-name> --chat <id-or-title> --content <text>
|
|
72
|
+
|
|
73
|
+
${c} chat list
|
|
74
|
+
${c} chat create --connection <id-or-name> --title <title>
|
|
75
|
+
${c} chat history --chat <id-or-title>
|
|
76
|
+
${c} chat send --chat <id-or-title> --content <text> [--file <path>] [--agent <id-or-name>]
|
|
77
|
+
|
|
78
|
+
${c} files upload <path>
|
|
79
|
+
${c} files list
|
|
80
|
+
|
|
81
|
+
${c} ops doctor
|
|
82
|
+
|
|
83
|
+
Global flags:
|
|
84
|
+
--profile <name> Local config profile. Default: active profile.
|
|
85
|
+
--convex-url <url> Convex deployment URL.
|
|
86
|
+
--actions-url <url> ${PRODUCT_NAME} action site URL.
|
|
87
|
+
--market <cn|intl> Skill market.
|
|
88
|
+
--locale <zh-CN|en> Catalog locale.
|
|
89
|
+
--json Print JSON output.
|
|
90
|
+
--timeout-ms <ms> HTTP timeout. Default: 20000.
|
|
91
|
+
--help Show help.
|
|
92
|
+
--version Show version.
|
|
93
|
+
`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function fail(message, code = EXIT_RUNTIME) {
|
|
97
|
+
console.error(message);
|
|
98
|
+
process.exit(code);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function parseGlobal(argv) {
|
|
102
|
+
const rest = [];
|
|
103
|
+
const global = {
|
|
104
|
+
json: false,
|
|
105
|
+
profile: '',
|
|
106
|
+
convexUrl: '',
|
|
107
|
+
actionsUrl: '',
|
|
108
|
+
market: '',
|
|
109
|
+
locale: '',
|
|
110
|
+
timeoutMs: 20_000
|
|
111
|
+
};
|
|
112
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
113
|
+
const value = argv[i];
|
|
114
|
+
const next = argv[i + 1];
|
|
115
|
+
if (value === '--help' || value === '-h') {
|
|
116
|
+
console.log(usage());
|
|
117
|
+
process.exit(0);
|
|
118
|
+
}
|
|
119
|
+
if (value === '--version' || value === '-v') {
|
|
120
|
+
console.log(VERSION);
|
|
121
|
+
process.exit(0);
|
|
122
|
+
}
|
|
123
|
+
if (value === '--json') {
|
|
124
|
+
global.json = true;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (value === '--profile') {
|
|
128
|
+
global.profile = requireValue(value, next);
|
|
129
|
+
i += 1;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (value === '--convex-url') {
|
|
133
|
+
global.convexUrl = requireValue(value, next);
|
|
134
|
+
i += 1;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (value === '--actions-url') {
|
|
138
|
+
global.actionsUrl = requireValue(value, next);
|
|
139
|
+
i += 1;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (value === '--market') {
|
|
143
|
+
global.market = requireValue(value, next);
|
|
144
|
+
i += 1;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (value === '--locale') {
|
|
148
|
+
global.locale = requireValue(value, next);
|
|
149
|
+
i += 1;
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (value === '--timeout-ms') {
|
|
153
|
+
global.timeoutMs = Number(requireValue(value, next));
|
|
154
|
+
if (!Number.isFinite(global.timeoutMs) || global.timeoutMs < 1000) {
|
|
155
|
+
fail('Invalid --timeout-ms value.', EXIT_USAGE);
|
|
156
|
+
}
|
|
157
|
+
i += 1;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
rest.push(value);
|
|
161
|
+
}
|
|
162
|
+
return { global, argv: rest };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function parseFlags(argv) {
|
|
166
|
+
const out = { _: [] };
|
|
167
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
168
|
+
const value = argv[i];
|
|
169
|
+
if (!value.startsWith('--')) {
|
|
170
|
+
out._.push(value);
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const eqIndex = value.indexOf('=');
|
|
174
|
+
if (eqIndex > 0) {
|
|
175
|
+
out[value.slice(2, eqIndex)] = value.slice(eqIndex + 1);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
const key = value.slice(2);
|
|
179
|
+
const next = argv[i + 1];
|
|
180
|
+
if (!next || next.startsWith('--')) {
|
|
181
|
+
out[key] = true;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
out[key] = next;
|
|
185
|
+
i += 1;
|
|
186
|
+
}
|
|
187
|
+
return out;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function requireValue(key, value) {
|
|
191
|
+
if (!value || String(value).startsWith('--')) {
|
|
192
|
+
fail(`Missing value for ${key}.`, EXIT_USAGE);
|
|
193
|
+
}
|
|
194
|
+
return value;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function requireArg(value, label) {
|
|
198
|
+
if (!value) {
|
|
199
|
+
fail(`Missing ${label}.\n\n${usage()}`, EXIT_USAGE);
|
|
200
|
+
}
|
|
201
|
+
return value;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function publicError(error) {
|
|
205
|
+
const message = error instanceof Error ? error.message : String(error || '');
|
|
206
|
+
const code = error?.code || message.match(/\b[A-Z][A-Z0-9_]{4,}\b/)?.[0] || '';
|
|
207
|
+
return {
|
|
208
|
+
message: message || 'XIASHE_CLI_FAILED',
|
|
209
|
+
code: code || 'XIASHE_CLI_FAILED',
|
|
210
|
+
status: error?.status
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function inferExitCode(error) {
|
|
215
|
+
const normalized = `${error?.code || ''} ${error?.message || ''}`.toUpperCase();
|
|
216
|
+
if (normalized.includes('AUTH')) return EXIT_AUTH;
|
|
217
|
+
if (normalized.includes('NETWORK') || normalized.includes('TIMEOUT') || normalized.includes('HTTP')) return EXIT_NETWORK;
|
|
218
|
+
if (normalized.includes('REQUIRED') || normalized.includes('MISSING') || normalized.includes('NOT_FOUND')) return EXIT_USAGE;
|
|
219
|
+
return EXIT_RUNTIME;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function print(value, global = {}) {
|
|
223
|
+
if (global.json) {
|
|
224
|
+
console.log(JSON.stringify(value, null, 2));
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (typeof value === 'string') {
|
|
228
|
+
console.log(value);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
console.log(JSON.stringify(value, null, 2));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function table(rows, columns) {
|
|
235
|
+
if (!rows.length) {
|
|
236
|
+
console.log('No rows.');
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const widths = columns.map((column) =>
|
|
240
|
+
Math.max(column.label.length, ...rows.map((row) => String(column.value(row) ?? '').length))
|
|
241
|
+
);
|
|
242
|
+
console.log(columns.map((column, index) => column.label.padEnd(widths[index])).join(' '));
|
|
243
|
+
console.log(widths.map((width) => '-'.repeat(width)).join(' '));
|
|
244
|
+
for (const row of rows) {
|
|
245
|
+
console.log(columns.map((column, index) => String(column.value(row) ?? '').padEnd(widths[index])).join(' '));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async function makeClient(global) {
|
|
250
|
+
const loaded = await loadProfile({
|
|
251
|
+
profile: global.profile || undefined,
|
|
252
|
+
convexUrl: global.convexUrl || undefined,
|
|
253
|
+
actionsUrl: global.actionsUrl || undefined,
|
|
254
|
+
market: global.market || undefined,
|
|
255
|
+
locale: global.locale || undefined
|
|
256
|
+
});
|
|
257
|
+
return {
|
|
258
|
+
profileName: loaded.profileName,
|
|
259
|
+
client: new XiasheClient(loaded.profile, { profileName: loaded.profileName, timeoutMs: global.timeoutMs })
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async function persistGlobalOverrides(profileName, global) {
|
|
264
|
+
const patch = {};
|
|
265
|
+
if (global.convexUrl) patch.convexUrl = global.convexUrl;
|
|
266
|
+
if (global.actionsUrl) {
|
|
267
|
+
patch.actionsUrl = global.actionsUrl.replace(/\/+$/, '');
|
|
268
|
+
patch.mcpUrl = `${patch.actionsUrl}/mcp/xiashe`;
|
|
269
|
+
patch.agentClaimUrl = `${patch.actionsUrl}/agent-gateway/claim`;
|
|
270
|
+
}
|
|
271
|
+
if (global.market) patch.market = global.market;
|
|
272
|
+
if (global.locale) patch.locale = global.locale;
|
|
273
|
+
if (Object.keys(patch).length > 0) {
|
|
274
|
+
await updateProfile(profileName, patch);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async function cmdConfig(sub, args, global) {
|
|
279
|
+
const flags = parseFlags(args);
|
|
280
|
+
if (sub === 'path') {
|
|
281
|
+
print(configPath(), global);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (sub === 'get' || !sub) {
|
|
285
|
+
const config = await readConfig();
|
|
286
|
+
print(config, global);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
if (sub === 'use') {
|
|
290
|
+
const profile = requireArg(flags._[0], 'profile name');
|
|
291
|
+
await useProfile(profile);
|
|
292
|
+
print(`Active profile: ${profile}`, global);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
if (sub === 'set') {
|
|
296
|
+
const key = requireArg(flags._[0], 'config key');
|
|
297
|
+
const value = requireArg(flags._[1], 'config value');
|
|
298
|
+
const loaded = await loadProfile({ profile: global.profile || undefined });
|
|
299
|
+
await updateProfile(loaded.profileName, { [key]: value });
|
|
300
|
+
print(`Saved ${key} for profile ${loaded.profileName}.`, global);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
fail(`Unknown config command: ${sub}`, EXIT_USAGE);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async function cmdEnv(sub, args, global) {
|
|
307
|
+
const flags = parseFlags(args);
|
|
308
|
+
if (sub !== 'use') {
|
|
309
|
+
fail('Usage: xiashe env use cn|global', EXIT_USAGE);
|
|
310
|
+
}
|
|
311
|
+
const envName = requireArg(flags._[0], 'environment');
|
|
312
|
+
const loaded = await loadProfile({ profile: global.profile || undefined });
|
|
313
|
+
const defaults = await useEnvironment(envName, loaded.profileName);
|
|
314
|
+
print({ ok: true, profile: loaded.profileName, ...defaults }, global);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async function cmdLogin(args, global) {
|
|
318
|
+
const flags = parseFlags(args);
|
|
319
|
+
const email = requireArg(flags.email, '--email');
|
|
320
|
+
const password = requireArg(flags.password, '--password');
|
|
321
|
+
const { client, profileName } = await makeClient(global);
|
|
322
|
+
const auth = await client.signInWithPassword(email, password);
|
|
323
|
+
await persistGlobalOverrides(profileName, global);
|
|
324
|
+
await saveLoginSession(profileName, auth);
|
|
325
|
+
print(
|
|
326
|
+
global.json
|
|
327
|
+
? { ok: true, profile: profileName, userId: auth.userId, email: auth.email, expiresAt: auth.sessionExpiresAt }
|
|
328
|
+
: `Signed in as ${auth.email}. Profile: ${profileName}`,
|
|
329
|
+
global
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
async function cmdSignup(args, global) {
|
|
334
|
+
const flags = parseFlags(args);
|
|
335
|
+
const email = requireArg(flags.email, '--email');
|
|
336
|
+
const password = requireArg(flags.password, '--password');
|
|
337
|
+
const name = requireArg(flags.name || flags['display-name'], '--name');
|
|
338
|
+
const { client, profileName } = await makeClient(global);
|
|
339
|
+
const auth = await client.registerWithPassword(email, password, name);
|
|
340
|
+
await persistGlobalOverrides(profileName, global);
|
|
341
|
+
await saveLoginSession(profileName, auth);
|
|
342
|
+
print(global.json ? { ok: true, profile: profileName, userId: auth.userId, email: auth.email } : `Signed up as ${auth.email}.`, global);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async function cmdAuth(sub, args, global) {
|
|
346
|
+
const flags = parseFlags(args);
|
|
347
|
+
const { client, profileName } = await makeClient(global);
|
|
348
|
+
if (sub === 'request-code') {
|
|
349
|
+
const result = await client.requestEmailCode(requireArg(flags.email, '--email'), flags.mode || 'login');
|
|
350
|
+
print(result, global);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
if (sub === 'verify-code') {
|
|
354
|
+
const result = await client.verifyEmailCode(
|
|
355
|
+
requireArg(flags.email, '--email'),
|
|
356
|
+
requireArg(flags.code, '--code'),
|
|
357
|
+
flags.mode || 'login',
|
|
358
|
+
flags.name || ''
|
|
359
|
+
);
|
|
360
|
+
await persistGlobalOverrides(profileName, global);
|
|
361
|
+
await saveLoginSession(profileName, result);
|
|
362
|
+
print(global.json ? { ok: true, profile: profileName, userId: result.userId, email: result.email } : `Signed in as ${result.email}.`, global);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
fail('Usage: xiashe auth request-code|verify-code', EXIT_USAGE);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
async function cmdWhoami(global) {
|
|
369
|
+
const { client, profileName } = await makeClient(global);
|
|
370
|
+
const user = await client.whoami();
|
|
371
|
+
print(global.json ? { profile: profileName, ...user } : `${user.email} (${user._id})\nProfile: ${profileName}`, global);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
async function cmdLogout(global) {
|
|
375
|
+
const loaded = await loadProfile({ profile: global.profile || undefined });
|
|
376
|
+
await updateProfile(loaded.profileName, { session: undefined });
|
|
377
|
+
print(`Signed out profile ${loaded.profileName}.`, global);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
async function cmdConnections(sub, _args, global) {
|
|
381
|
+
if (sub && sub !== 'list') {
|
|
382
|
+
fail(`Unknown connections command: ${sub}`, EXIT_USAGE);
|
|
383
|
+
}
|
|
384
|
+
const { client } = await makeClient(global);
|
|
385
|
+
const rows = await client.listConnections();
|
|
386
|
+
if (global.json) {
|
|
387
|
+
print(rows, global);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
table(rows, [
|
|
391
|
+
{ label: 'id', value: (row) => row._id || row.id },
|
|
392
|
+
{ label: 'name', value: (row) => row.name },
|
|
393
|
+
{ label: 'type', value: (row) => row.connectorType },
|
|
394
|
+
{ label: 'status', value: (row) => row.status },
|
|
395
|
+
{ label: 'gateway', value: (row) => row.relayGatewayId || row.gatewayUrl || '' }
|
|
396
|
+
]);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
async function cmdClaw(sub, args, global) {
|
|
400
|
+
const flags = parseFlags(args);
|
|
401
|
+
const { client } = await makeClient(global);
|
|
402
|
+
if (sub === 'list' || !sub) {
|
|
403
|
+
return cmdConnections('list', [], global);
|
|
404
|
+
}
|
|
405
|
+
const connection = await client.resolveConnection(flags.connection || flags.c || flags._[0]);
|
|
406
|
+
if (sub === 'pair' || sub === 'repair') {
|
|
407
|
+
const result = sub === 'pair'
|
|
408
|
+
? await client.issueRelayPairCode(connection._id || connection.id)
|
|
409
|
+
: await client.createRelayPairCommand(connection._id || connection.id, { forceBootstrap: flags.bootstrap === true });
|
|
410
|
+
if (global.json) {
|
|
411
|
+
print(result, global);
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
if (result.pairCode) {
|
|
415
|
+
print(`Pair code: ${result.pairCode}\nExpires: ${result.expiresAt ? nowIso(result.expiresAt) : 'unknown'}`, global);
|
|
416
|
+
} else {
|
|
417
|
+
print(result.pairCommand || JSON.stringify(result, null, 2), global);
|
|
418
|
+
}
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
if (sub === 'check') {
|
|
422
|
+
const health = await probeConnection(connection, flags.token || process.env.XIASHE_GATEWAY_TOKEN, global.timeoutMs);
|
|
423
|
+
print(health, global);
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
fail(`Unknown claw command: ${sub}`, EXIT_USAGE);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
async function probeConnection(connection, token, timeoutMs) {
|
|
430
|
+
const gatewayUrl = String(connection.gatewayUrl || '').replace(/\/+$/, '');
|
|
431
|
+
const result = {
|
|
432
|
+
id: connection._id || connection.id,
|
|
433
|
+
name: connection.name,
|
|
434
|
+
status: connection.status,
|
|
435
|
+
connectorType: connection.connectorType,
|
|
436
|
+
gatewayUrl,
|
|
437
|
+
relayGatewayId: connection.relayGatewayId || null,
|
|
438
|
+
checks: []
|
|
439
|
+
};
|
|
440
|
+
if (!gatewayUrl) {
|
|
441
|
+
result.checks.push({ name: 'gatewayUrl', ok: false, error: 'missing gatewayUrl' });
|
|
442
|
+
return result;
|
|
443
|
+
}
|
|
444
|
+
for (const pathname of ['/health', '/api/health', '/healthz']) {
|
|
445
|
+
try {
|
|
446
|
+
const response = await fetch(`${gatewayUrl}${pathname}`, {
|
|
447
|
+
headers: token ? { authorization: `Bearer ${token}` } : {},
|
|
448
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
449
|
+
});
|
|
450
|
+
result.checks.push({ name: pathname, ok: response.ok, status: response.status });
|
|
451
|
+
if (response.ok) break;
|
|
452
|
+
} catch (error) {
|
|
453
|
+
result.checks.push({ name: pathname, ok: false, error: error.message });
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return result;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
async function cmdSkills(sub, args, global) {
|
|
460
|
+
const flags = parseFlags(args);
|
|
461
|
+
const { client } = await makeClient(global);
|
|
462
|
+
if (sub === 'list' || sub === 'search' || !sub) {
|
|
463
|
+
const search = flags._.join(' ') || flags.search || '';
|
|
464
|
+
const connection = flags.connection ? await client.resolveConnection(flags.connection) : null;
|
|
465
|
+
const result = await client.listSkills({ search, connectionId: connection?._id || connection?.id });
|
|
466
|
+
if (global.json) {
|
|
467
|
+
print(result, global);
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
table(result.skills || [], [
|
|
471
|
+
{ label: 'id', value: (row) => row.id },
|
|
472
|
+
{ label: 'skill', value: (row) => row.skillId },
|
|
473
|
+
{ label: 'name', value: (row) => row.name },
|
|
474
|
+
{ label: 'free', value: (row) => (row.isFree ? 'yes' : 'no') },
|
|
475
|
+
{ label: 'ready', value: (row) => (row.installReady ? 'yes' : 'no') }
|
|
476
|
+
]);
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
if (sub === 'detail') {
|
|
480
|
+
const skill = await client.resolveSkill(requireArg(flags._[0], 'skill'), { search: flags._[0] });
|
|
481
|
+
const detail = await client.getSkillDetail(skill.id);
|
|
482
|
+
print(detail, global);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
if (sub === 'install') {
|
|
486
|
+
const selector = requireArg(flags._[0], 'skill');
|
|
487
|
+
const connection = await client.resolveConnection(flags.connection || flags.c);
|
|
488
|
+
const skill = await client.resolveSkill(selector, { search: selector, connectionId: connection._id || connection.id });
|
|
489
|
+
const result = await client.createSkillInstallJob(connection._id || connection.id, skill.id, {
|
|
490
|
+
idempotencyKey: flags.idempotencyKey || createIdempotencyKey('cli-skill-install', [connection._id || connection.id, skill.id])
|
|
491
|
+
});
|
|
492
|
+
if (flags.watch) {
|
|
493
|
+
const final = await watchSkillJob(client, connection._id || connection.id, result.jobId, global);
|
|
494
|
+
print(global.json ? { queued: result, final } : `Install ${final.status}: ${final.skillName || skill.name} (${final.phase || final.status})`, global);
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
print(result, global);
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
if (sub === 'jobs' || sub === 'status') {
|
|
501
|
+
const connection = await client.resolveConnection(flags.connection || flags.c);
|
|
502
|
+
const rows = await client.listSkillInstallJobs(connection._id || connection.id, Number(flags.limit || 20));
|
|
503
|
+
if (global.json) {
|
|
504
|
+
print(rows, global);
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
table(rows, [
|
|
508
|
+
{ label: 'job', value: (row) => row._id },
|
|
509
|
+
{ label: 'skill', value: (row) => row.skillName || row.skillId },
|
|
510
|
+
{ label: 'status', value: (row) => row.status },
|
|
511
|
+
{ label: 'phase', value: (row) => row.phase || '' },
|
|
512
|
+
{ label: 'updated', value: (row) => nowIso(row.updatedAt).slice(0, 19) }
|
|
513
|
+
]);
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
if (sub === 'claim' || sub === 'use') {
|
|
517
|
+
await claimSkill(flags, client, global);
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
fail(`Unknown skills command: ${sub}`, EXIT_USAGE);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
async function watchSkillJob(client, connectionId, jobId, global) {
|
|
524
|
+
const deadline = Date.now() + Number(global.watchTimeoutMs || 180_000);
|
|
525
|
+
let latest = null;
|
|
526
|
+
while (Date.now() < deadline) {
|
|
527
|
+
const rows = await client.listSkillInstallJobs(connectionId, 30);
|
|
528
|
+
latest = rows.find((row) => row._id === jobId) || latest || rows[0];
|
|
529
|
+
if (latest && !global.json) {
|
|
530
|
+
process.stderr.write(`\r${latest.status} ${latest.phase || ''} ${latest.phaseMessage || ''}`.slice(0, 160));
|
|
531
|
+
}
|
|
532
|
+
if (latest && ['succeeded', 'failed', 'needs_action', 'cancelled'].includes(latest.status)) {
|
|
533
|
+
if (!global.json) process.stderr.write('\n');
|
|
534
|
+
return latest;
|
|
535
|
+
}
|
|
536
|
+
await new Promise((resolve) => setTimeout(resolve, 2500));
|
|
537
|
+
}
|
|
538
|
+
if (!global.json) process.stderr.write('\n');
|
|
539
|
+
return latest || { status: 'unknown' };
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
async function claimSkill(flags, client, global) {
|
|
543
|
+
const token = requireArg(flags._[0] || flags.token, 'one-time token');
|
|
544
|
+
const claimUrl = flags['claim-url'] || client.profile.mcpUrl.replace(/\/?$/, '/claim');
|
|
545
|
+
const mcpUrl = flags['mcp-url'] || client.profile.mcpUrl;
|
|
546
|
+
const claim = await postJson(
|
|
547
|
+
claimUrl,
|
|
548
|
+
{
|
|
549
|
+
token,
|
|
550
|
+
agentName: flags.name || flags['agent-name'] || 'XiaShe CLI Agent',
|
|
551
|
+
agentKind: flags.kind || 'cli',
|
|
552
|
+
agentRef: flags.ref || `${process.platform}:${process.env.USER || 'local'}`
|
|
553
|
+
},
|
|
554
|
+
{ timeoutMs: global.timeoutMs }
|
|
555
|
+
);
|
|
556
|
+
if (!claim.ok || !claim.runtimeToken) {
|
|
557
|
+
throw new Error(claim.error || 'CLAIM_FAILED');
|
|
558
|
+
}
|
|
559
|
+
const tools = await mcpCall(mcpUrl, '', 'tools/list', {}, global.timeoutMs);
|
|
560
|
+
const prompt = decodeMcpToolResult(
|
|
561
|
+
await mcpCall(mcpUrl, claim.runtimeToken, 'tools/call', { name: 'xiashe.get_skill_prompt', arguments: {} }, global.timeoutMs)
|
|
562
|
+
);
|
|
563
|
+
const status = decodeMcpToolResult(
|
|
564
|
+
await mcpCall(mcpUrl, claim.runtimeToken, 'tools/call', { name: 'xiashe.skill_status', arguments: {} }, global.timeoutMs)
|
|
565
|
+
);
|
|
566
|
+
const receiptCallId = prompt?.receipt?.callId;
|
|
567
|
+
const ack = receiptCallId
|
|
568
|
+
? decodeMcpToolResult(
|
|
569
|
+
await mcpCall(
|
|
570
|
+
mcpUrl,
|
|
571
|
+
claim.runtimeToken,
|
|
572
|
+
'tools/call',
|
|
573
|
+
{
|
|
574
|
+
name: 'xiashe.ack_skill_call',
|
|
575
|
+
arguments: {
|
|
576
|
+
callId: receiptCallId,
|
|
577
|
+
outcome: 'received',
|
|
578
|
+
idempotencyKey: `xiashe-cli:${receiptCallId}:received`
|
|
579
|
+
}
|
|
580
|
+
},
|
|
581
|
+
global.timeoutMs
|
|
582
|
+
)
|
|
583
|
+
)
|
|
584
|
+
: null;
|
|
585
|
+
if (global.json) {
|
|
586
|
+
print({ ok: true, claim: { ...claim, runtimeToken: undefined }, tools, prompt, status, ack }, global);
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
print(
|
|
590
|
+
[
|
|
591
|
+
`Skill: ${claim.skill?.name || prompt.skill?.name || 'Unknown'}`,
|
|
592
|
+
`Connection: ${claim.connection?.name || status.connection?.name || 'Unknown'}`,
|
|
593
|
+
'',
|
|
594
|
+
prompt.prompt || prompt.instructions || prompt.text || '',
|
|
595
|
+
'',
|
|
596
|
+
`Status: ${status.status || status.session?.status || 'active'}`
|
|
597
|
+
].join('\n'),
|
|
598
|
+
global
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
async function cmdAgent(sub, args, global) {
|
|
603
|
+
const flags = parseFlags(args);
|
|
604
|
+
const { client, profileName } = await makeClient(global);
|
|
605
|
+
if (sub === 'issue' || sub === 'invite') {
|
|
606
|
+
const issue = await client.issueAgentConnectToken({
|
|
607
|
+
agentName: flags.name || flags['agent-name'] || undefined,
|
|
608
|
+
agentKind: flags.kind || undefined,
|
|
609
|
+
transport: flags.transport || undefined
|
|
610
|
+
});
|
|
611
|
+
if (global.json) {
|
|
612
|
+
print(issue, global);
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
print(printableAgentPrompt(issue), global);
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
if (sub === 'connect' || sub === 'claim') {
|
|
619
|
+
const token = requireArg(flags.token || flags._[0], '--token');
|
|
620
|
+
const claim = await client.claimAgentConnectToken(token, {
|
|
621
|
+
claimUrl: flags['claim-url'] || undefined,
|
|
622
|
+
agentName: flags.name || flags['agent-name'] || 'XiaShe CLI Agent',
|
|
623
|
+
agentKind: flags.kind || 'cli',
|
|
624
|
+
agentRef: flags.ref || `${osName()} ${process.cwd()}`,
|
|
625
|
+
transport: flags.transport || 'npx'
|
|
626
|
+
});
|
|
627
|
+
await saveAgentSession(profileName, claim);
|
|
628
|
+
print(global.json ? { ok: true, ...claim, runtimeToken: undefined } : `Connected Agent: ${claim.displayName}`, global);
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
if (sub === 'heartbeat') {
|
|
632
|
+
const result = await client.agentHeartbeat({
|
|
633
|
+
status: flags.status || 'online',
|
|
634
|
+
capabilities: parseCsv(flags.capabilities || 'chat,files,tasks,skills')
|
|
635
|
+
});
|
|
636
|
+
print(result, global);
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
if (sub === 'list' || sub === 'agents' || !sub) {
|
|
640
|
+
const rows = await client.listAgents(Number(flags.limit || 50));
|
|
641
|
+
if (global.json) {
|
|
642
|
+
print(rows, global);
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
table(rows, [
|
|
646
|
+
{ label: 'id', value: (row) => row.id },
|
|
647
|
+
{ label: 'name', value: (row) => row.displayName },
|
|
648
|
+
{ label: 'kind', value: (row) => row.agentKind },
|
|
649
|
+
{ label: 'status', value: (row) => row.status },
|
|
650
|
+
{ label: 'pending', value: (row) => row.pendingTaskCount },
|
|
651
|
+
{ label: 'lastSeen', value: (row) => (row.lastSeenAt ? nowIso(row.lastSeenAt).slice(0, 19) : '') }
|
|
652
|
+
]);
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
if (sub === 'tasks') {
|
|
656
|
+
await cmdAgentTasks(flags, client, global);
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
if (sub === 'task') {
|
|
660
|
+
const action = requireArg(flags._[0], 'agent task action');
|
|
661
|
+
if (action !== 'create') fail('Usage: xiashe agent task create --agent <id-or-name> --chat <id-or-title> --content <text>', EXIT_USAGE);
|
|
662
|
+
const agent = await resolveAgent(client, requireArg(flags.agent, '--agent'));
|
|
663
|
+
const chat = await client.resolveChat(requireArg(flags.chat, '--chat'));
|
|
664
|
+
const result = await client.createAgentChatTask({
|
|
665
|
+
chatId: chat._id || chat.id,
|
|
666
|
+
targetAgentConnectionId: agent.id,
|
|
667
|
+
content: readContent(flags),
|
|
668
|
+
idempotencyKey: flags.idempotencyKey || createIdempotencyKey('cli-agent-task', [chat._id || chat.id, agent.id, readContent(flags)])
|
|
669
|
+
});
|
|
670
|
+
print(result, global);
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
fail(`Unknown agent command: ${sub}`, EXIT_USAGE);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
async function cmdAgentTasks(flags, client, global) {
|
|
677
|
+
const action = flags._[0] || 'list';
|
|
678
|
+
if (action === 'list') {
|
|
679
|
+
const result = await client.agentListTasks({ limit: Number(flags.limit || 20) });
|
|
680
|
+
print(result, global);
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
if (action === 'watch') {
|
|
684
|
+
await client.agentHeartbeat({ capabilities: parseCsv(flags.capabilities || 'chat,files,tasks,skills') }).catch(() => undefined);
|
|
685
|
+
const seen = new Set();
|
|
686
|
+
const deadline = Date.now() + Number(flags['timeout-ms'] || 300_000);
|
|
687
|
+
while (Date.now() < deadline) {
|
|
688
|
+
const result = await client.agentListTasks({ limit: Number(flags.limit || 20) });
|
|
689
|
+
const tasks = result.tasks || result.items || [];
|
|
690
|
+
for (const task of tasks) {
|
|
691
|
+
const id = task.taskId || task.id;
|
|
692
|
+
if (!id || seen.has(id)) continue;
|
|
693
|
+
seen.add(id);
|
|
694
|
+
console.log(JSON.stringify(task, null, 2));
|
|
695
|
+
if (flags.ack) await client.agentAckTask(id, 'running');
|
|
696
|
+
}
|
|
697
|
+
await new Promise((resolve) => setTimeout(resolve, 2500));
|
|
698
|
+
}
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
if (action === 'ack') {
|
|
702
|
+
const taskId = requireArg(flags._[1], 'task id');
|
|
703
|
+
print(await client.agentAckTask(taskId, flags.state || 'running'), global);
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
if (action === 'complete' || action === 'fail') {
|
|
707
|
+
const taskId = requireArg(flags._[1], 'task id');
|
|
708
|
+
const state = action === 'fail' ? 'failed' : (flags.state || 'completed');
|
|
709
|
+
const content = readContent(flags);
|
|
710
|
+
print(await client.agentCompleteTask(taskId, { content, error: flags.error || undefined }, state), global);
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
fail(`Unknown agent tasks command: ${action}`, EXIT_USAGE);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
async function resolveAgent(client, selector) {
|
|
717
|
+
const rows = await client.listAgents(100);
|
|
718
|
+
const needle = String(selector || '').toLowerCase();
|
|
719
|
+
const found =
|
|
720
|
+
rows.find((row) => row.id === selector) ||
|
|
721
|
+
rows.find((row) => String(row.displayName || '').toLowerCase() === needle) ||
|
|
722
|
+
rows.find((row) => String(row.displayName || '').toLowerCase().includes(needle));
|
|
723
|
+
if (!found) throw new Error(`Agent not found: ${selector}`);
|
|
724
|
+
return found;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
async function cmdChat(sub, args, global) {
|
|
728
|
+
const flags = parseFlags(args);
|
|
729
|
+
const { client } = await makeClient(global);
|
|
730
|
+
if (sub === 'list' || !sub) {
|
|
731
|
+
const rows = await client.listChats();
|
|
732
|
+
if (global.json) {
|
|
733
|
+
print(rows, global);
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
table(rows, [
|
|
737
|
+
{ label: 'id', value: (row) => row._id || row.id },
|
|
738
|
+
{ label: 'title', value: (row) => row.title },
|
|
739
|
+
{ label: 'connection', value: (row) => row.connectionName || row.connection?.name || row.connectionId },
|
|
740
|
+
{ label: 'updated', value: (row) => nowIso(row.updatedAt || row.lastActivityAt).slice(0, 19) }
|
|
741
|
+
]);
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
if (sub === 'create') {
|
|
745
|
+
const connection = await client.resolveConnection(requireArg(flags.connection || flags.c, '--connection'));
|
|
746
|
+
const result = await client.createChat(connection._id || connection.id, flags.title || 'New Chat');
|
|
747
|
+
print(global.json ? { chatId: result } : `Created chat: ${result}`, global);
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
if (sub === 'history') {
|
|
751
|
+
const chat = await client.resolveChat(requireArg(flags.chat || flags._[0], '--chat'));
|
|
752
|
+
const rows = await client.listMessages(chat._id || chat.id);
|
|
753
|
+
if (global.json) {
|
|
754
|
+
print(rows, global);
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
for (const message of rows) {
|
|
758
|
+
console.log(`\n[${message.role}] ${nowIso(message.createdAt).slice(0, 19)}`);
|
|
759
|
+
console.log(message.content || '');
|
|
760
|
+
if (message.attachments?.length) console.log(`attachments: ${message.attachments.map((a) => a.name).join(', ')}`);
|
|
761
|
+
}
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
if (sub === 'send') {
|
|
765
|
+
const chat = await client.resolveChat(requireArg(flags.chat, '--chat'));
|
|
766
|
+
const content = readContent(flags);
|
|
767
|
+
const attachments = [];
|
|
768
|
+
if (flags.file) {
|
|
769
|
+
const uploaded = await client.uploadFile(flags.file, { source: 'chat_attachment' });
|
|
770
|
+
attachments.push({
|
|
771
|
+
type: String(uploaded.mime || '').startsWith('image/') ? 'photo' : 'file',
|
|
772
|
+
name: uploaded.fileName,
|
|
773
|
+
mime: uploaded.mime,
|
|
774
|
+
size: uploaded.size,
|
|
775
|
+
fileObjectId: uploaded.fileObjectId,
|
|
776
|
+
remoteUrl: uploaded.downloadUrl,
|
|
777
|
+
status: 'uploaded'
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
const messageId = await client.sendUserMessage(chat._id || chat.id, content, attachments);
|
|
781
|
+
if (flags.agent) {
|
|
782
|
+
const agent = await resolveAgent(client, flags.agent);
|
|
783
|
+
const task = await client.createAgentChatTask({
|
|
784
|
+
chatId: chat._id || chat.id,
|
|
785
|
+
targetAgentConnectionId: agent.id,
|
|
786
|
+
messageId,
|
|
787
|
+
content,
|
|
788
|
+
attachments: attachments.map((attachment) => ({
|
|
789
|
+
fileObjectId: attachment.fileObjectId,
|
|
790
|
+
name: attachment.name,
|
|
791
|
+
mime: attachment.mime,
|
|
792
|
+
size: attachment.size
|
|
793
|
+
})),
|
|
794
|
+
idempotencyKey: createIdempotencyKey('cli-chat-agent', [chat._id || chat.id, agent.id, messageId])
|
|
795
|
+
});
|
|
796
|
+
print({ messageId, agentTask: task }, global);
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
print(global.json ? { messageId } : `Message saved: ${messageId}`, global);
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
fail(`Unknown chat command: ${sub}`, EXIT_USAGE);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
async function cmdFiles(sub, args, global) {
|
|
806
|
+
const flags = parseFlags(args);
|
|
807
|
+
const { client } = await makeClient(global);
|
|
808
|
+
if (sub === 'upload') {
|
|
809
|
+
const result = await client.uploadFile(requireArg(flags._[0] || flags.path, 'file path'), {
|
|
810
|
+
fileName: flags.name || undefined,
|
|
811
|
+
mime: flags.mime || undefined,
|
|
812
|
+
source: flags.source || 'manual_upload'
|
|
813
|
+
});
|
|
814
|
+
print(result, global);
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
if (sub === 'list' || !sub) {
|
|
818
|
+
const rows = await client.listFiles();
|
|
819
|
+
if (global.json) {
|
|
820
|
+
print(rows, global);
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
table(rows, [
|
|
824
|
+
{ label: 'id', value: (row) => row.id },
|
|
825
|
+
{ label: 'name', value: (row) => row.fileName },
|
|
826
|
+
{ label: 'mime', value: (row) => row.mime },
|
|
827
|
+
{ label: 'size', value: (row) => row.size },
|
|
828
|
+
{ label: 'created', value: (row) => nowIso(row.createdAt).slice(0, 19) }
|
|
829
|
+
]);
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
fail(`Unknown files command: ${sub}`, EXIT_USAGE);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
async function cmdOps(sub, args, global) {
|
|
836
|
+
const flags = parseFlags(args);
|
|
837
|
+
const { client } = await makeClient(global);
|
|
838
|
+
if (sub !== 'doctor' && sub !== 'relay-doctor' && sub !== 'prewarm') {
|
|
839
|
+
fail('Usage: xiashe ops doctor', EXIT_USAGE);
|
|
840
|
+
}
|
|
841
|
+
if (sub === 'prewarm') {
|
|
842
|
+
const url = `${client.profile.actionsUrl.replace(/\/+$/, '')}/agent-gateway/skills/prewarm`;
|
|
843
|
+
const targets = parseCsv(flags.targets || flags._.join(','));
|
|
844
|
+
const result = await postJson(url, { targets }, { timeoutMs: global.timeoutMs });
|
|
845
|
+
print(result, global);
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
const checks = {
|
|
849
|
+
profile: client.profileName,
|
|
850
|
+
convexUrl: client.profile.convexUrl || null,
|
|
851
|
+
actionsUrl: client.profile.actionsUrl || null,
|
|
852
|
+
relayUrl: client.profile.relayUrl || null,
|
|
853
|
+
signedIn: Boolean(client.profile.session?.userId && client.profile.session?.sessionToken),
|
|
854
|
+
agentConnected: Boolean(client.profile.agent?.runtimeToken),
|
|
855
|
+
probes: []
|
|
856
|
+
};
|
|
857
|
+
for (const url of [client.profile.actionsUrl, client.profile.relayUrl].filter(Boolean)) {
|
|
858
|
+
for (const pathname of ['/health', '/healthz']) {
|
|
859
|
+
try {
|
|
860
|
+
const response = await fetch(`${url.replace(/\/+$/, '')}${pathname}`, { signal: AbortSignal.timeout(global.timeoutMs) });
|
|
861
|
+
checks.probes.push({ url: `${url}${pathname}`, ok: response.ok, status: response.status });
|
|
862
|
+
if (response.ok) break;
|
|
863
|
+
} catch (error) {
|
|
864
|
+
checks.probes.push({ url: `${url}${pathname}`, ok: false, error: error.message });
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
print(checks, global);
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
function readContent(flags) {
|
|
872
|
+
if (flags.content !== undefined) return String(flags.content);
|
|
873
|
+
if (flags.text !== undefined) return String(flags.text);
|
|
874
|
+
if (flags._?.length > 1) return flags._.slice(1).join(' ');
|
|
875
|
+
return '';
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
function parseCsv(input) {
|
|
879
|
+
return String(input || '')
|
|
880
|
+
.split(',')
|
|
881
|
+
.map((item) => item.trim())
|
|
882
|
+
.filter(Boolean);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
function osName() {
|
|
886
|
+
return `${osPlatform()}-${process.arch}`;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
function osPlatform() {
|
|
890
|
+
return process.platform;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
async function main() {
|
|
894
|
+
const parsed = parseGlobal(process.argv.slice(2));
|
|
895
|
+
const [command, sub, ...args] = parsed.argv;
|
|
896
|
+
const global = parsed.global;
|
|
897
|
+
if (!command) {
|
|
898
|
+
console.log(usage());
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
try {
|
|
902
|
+
if (command === 'config') return await cmdConfig(sub, args, global);
|
|
903
|
+
if (command === 'env') return await cmdEnv(sub, args, global);
|
|
904
|
+
if (command === 'login') return await cmdLogin([sub, ...args].filter(Boolean), global);
|
|
905
|
+
if (command === 'signup') return await cmdSignup([sub, ...args].filter(Boolean), global);
|
|
906
|
+
if (command === 'auth') return await cmdAuth(sub, args, global);
|
|
907
|
+
if (command === 'whoami') return await cmdWhoami(global);
|
|
908
|
+
if (command === 'logout') return await cmdLogout(global);
|
|
909
|
+
if (command === 'connections' || command === 'connection') return await cmdConnections(sub, args, global);
|
|
910
|
+
if (command === 'claw') return await cmdClaw(sub, args, global);
|
|
911
|
+
if (command === 'skills' || command === 'skill') return await cmdSkills(sub, args, global);
|
|
912
|
+
if (command === 'agent' || command === 'agents') return await cmdAgent(sub, args, global);
|
|
913
|
+
if (command === 'chat' || command === 'chats') return await cmdChat(sub, args, global);
|
|
914
|
+
if (command === 'files' || command === 'file') return await cmdFiles(sub, args, global);
|
|
915
|
+
if (command === 'ops') return await cmdOps(sub, args, global);
|
|
916
|
+
fail(`Unknown command: ${command}\n\n${usage()}`, EXIT_USAGE);
|
|
917
|
+
} catch (error) {
|
|
918
|
+
const normalized = publicError(error);
|
|
919
|
+
if (global.json) {
|
|
920
|
+
console.error(JSON.stringify({ ok: false, ...normalized }, null, 2));
|
|
921
|
+
} else {
|
|
922
|
+
console.error(`xiashe: ${normalized.message}`);
|
|
923
|
+
}
|
|
924
|
+
process.exit(inferExitCode(error));
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
void main();
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xiashe/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"bin": {
|
|
6
|
+
"xiashe": "bin/xiashe.mjs"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin",
|
|
10
|
+
"README.md"
|
|
11
|
+
],
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@xiashe/sdk": "0.1.0"
|
|
14
|
+
},
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=20"
|
|
17
|
+
}
|
|
18
|
+
}
|