fastclaw-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 +117 -0
- package/dist/index.js +1413 -0
- package/dist/sdk.d.ts +312 -0
- package/dist/sdk.js +409 -0
- package/package.json +50 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1413 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/commands/config-cmd.ts
|
|
7
|
+
import chalk3 from "chalk";
|
|
8
|
+
|
|
9
|
+
// src/config.ts
|
|
10
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
11
|
+
import { join } from "path";
|
|
12
|
+
import { homedir } from "os";
|
|
13
|
+
|
|
14
|
+
// src/constants.ts
|
|
15
|
+
var DEFAULT_URL = "https://claw.yesy.dev";
|
|
16
|
+
var API_BASE = "/bot/api/v1";
|
|
17
|
+
var MODEL_PRESETS = {
|
|
18
|
+
google: {
|
|
19
|
+
name: "google",
|
|
20
|
+
baseUrl: "https://generativelanguage.googleapis.com/v1beta",
|
|
21
|
+
models: [
|
|
22
|
+
{
|
|
23
|
+
id: "gemini-2.5-flash",
|
|
24
|
+
name: "Gemini 2.5 Flash",
|
|
25
|
+
reasoning: true,
|
|
26
|
+
input: ["text", "image"],
|
|
27
|
+
context_window: 1048576,
|
|
28
|
+
max_tokens: 65536
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: "gemini-2.5-pro",
|
|
32
|
+
name: "Gemini 2.5 Pro",
|
|
33
|
+
reasoning: true,
|
|
34
|
+
input: ["text", "image"],
|
|
35
|
+
context_window: 1048576,
|
|
36
|
+
max_tokens: 65536
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
anthropic: {
|
|
41
|
+
name: "anthropic",
|
|
42
|
+
baseUrl: "https://api.anthropic.com",
|
|
43
|
+
auth: "api-key",
|
|
44
|
+
api: "anthropic-messages",
|
|
45
|
+
models: [
|
|
46
|
+
{
|
|
47
|
+
id: "claude-sonnet-4-20250514",
|
|
48
|
+
name: "Claude Sonnet 4",
|
|
49
|
+
reasoning: false,
|
|
50
|
+
input: ["text", "image"],
|
|
51
|
+
context_window: 2e5,
|
|
52
|
+
max_tokens: 8192
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
openai: {
|
|
57
|
+
name: "openai",
|
|
58
|
+
baseUrl: "https://api.openai.com/v1",
|
|
59
|
+
auth: "api-key",
|
|
60
|
+
api: "openai-completions",
|
|
61
|
+
models: [
|
|
62
|
+
{
|
|
63
|
+
id: "gpt-5.1",
|
|
64
|
+
name: "GPT-5.1",
|
|
65
|
+
reasoning: false,
|
|
66
|
+
input: ["text", "image"],
|
|
67
|
+
context_window: 1048576,
|
|
68
|
+
max_tokens: 32768
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
},
|
|
72
|
+
minimax: {
|
|
73
|
+
name: "minimax",
|
|
74
|
+
baseUrl: "https://api.minimax.chat/v1",
|
|
75
|
+
auth: "api-key",
|
|
76
|
+
api: "openai-completions",
|
|
77
|
+
models: [
|
|
78
|
+
{
|
|
79
|
+
id: "MiniMax-Text-01",
|
|
80
|
+
name: "MiniMax Text 01",
|
|
81
|
+
reasoning: false,
|
|
82
|
+
input: ["text"],
|
|
83
|
+
context_window: 1e6,
|
|
84
|
+
max_tokens: 65536
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
var PROVIDER_DEFAULTS = {
|
|
90
|
+
google: "google/gemini-2.5-flash",
|
|
91
|
+
anthropic: "anthropic/claude-sonnet-4-20250514",
|
|
92
|
+
openai: "openai/gpt-5.1",
|
|
93
|
+
minimax: "minimax/MiniMax-Text-01"
|
|
94
|
+
};
|
|
95
|
+
var CHANNEL_FIELDS = {
|
|
96
|
+
feishu: {
|
|
97
|
+
required: ["appId", "appSecret"],
|
|
98
|
+
optional: ["account", "dmPolicy", "groupPolicy"]
|
|
99
|
+
},
|
|
100
|
+
telegram: {
|
|
101
|
+
required: ["botToken"],
|
|
102
|
+
optional: ["account", "dmPolicy", "groupPolicy"]
|
|
103
|
+
},
|
|
104
|
+
slack: {
|
|
105
|
+
required: ["botToken", "appToken"],
|
|
106
|
+
optional: ["account", "dmPolicy", "groupPolicy"]
|
|
107
|
+
},
|
|
108
|
+
discord: {
|
|
109
|
+
required: ["botToken"],
|
|
110
|
+
optional: ["account", "dmPolicy", "groupPolicy"]
|
|
111
|
+
},
|
|
112
|
+
whatsapp: {
|
|
113
|
+
required: ["botToken"],
|
|
114
|
+
optional: ["account", "dmPolicy", "groupPolicy"]
|
|
115
|
+
},
|
|
116
|
+
line: {
|
|
117
|
+
required: ["botToken", "channelSecret"],
|
|
118
|
+
optional: ["account", "dmPolicy", "groupPolicy"]
|
|
119
|
+
},
|
|
120
|
+
teams: {
|
|
121
|
+
required: ["appId", "appPassword"],
|
|
122
|
+
optional: ["account", "dmPolicy", "groupPolicy"]
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// src/config.ts
|
|
127
|
+
var CONFIG_DIR = join(homedir(), ".fastclaw");
|
|
128
|
+
var CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
129
|
+
function loadConfig() {
|
|
130
|
+
try {
|
|
131
|
+
if (!existsSync(CONFIG_FILE)) return {};
|
|
132
|
+
const raw = readFileSync(CONFIG_FILE, "utf-8");
|
|
133
|
+
return JSON.parse(raw);
|
|
134
|
+
} catch {
|
|
135
|
+
return {};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function saveConfig(config) {
|
|
139
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
140
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n");
|
|
141
|
+
}
|
|
142
|
+
function updateConfig(updates) {
|
|
143
|
+
const config = loadConfig();
|
|
144
|
+
Object.assign(config, updates);
|
|
145
|
+
saveConfig(config);
|
|
146
|
+
return config;
|
|
147
|
+
}
|
|
148
|
+
function resolveConfig(opts) {
|
|
149
|
+
const file = loadConfig();
|
|
150
|
+
return {
|
|
151
|
+
url: opts.url || process.env.FASTCLAW_URL || file.url || DEFAULT_URL,
|
|
152
|
+
adminToken: opts.adminToken || process.env.FASTCLAW_ADMIN_TOKEN || file.admin_token,
|
|
153
|
+
appToken: opts.appToken || process.env.FASTCLAW_APP_TOKEN || file.app_token,
|
|
154
|
+
userId: opts.userId || process.env.FASTCLAW_USER_ID || file.default_user_id || "default",
|
|
155
|
+
botId: opts.botId || process.env.FASTCLAW_BOT_ID || file.default_bot_id,
|
|
156
|
+
json: opts.json ?? false
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function getConfigPath() {
|
|
160
|
+
return CONFIG_FILE;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// src/utils/error.ts
|
|
164
|
+
import chalk from "chalk";
|
|
165
|
+
var ApiError = class extends Error {
|
|
166
|
+
constructor(statusCode, apiCode, message) {
|
|
167
|
+
super(message);
|
|
168
|
+
this.statusCode = statusCode;
|
|
169
|
+
this.apiCode = apiCode;
|
|
170
|
+
this.name = "ApiError";
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
function withErrorHandler(fn) {
|
|
174
|
+
return async (...args) => {
|
|
175
|
+
try {
|
|
176
|
+
await fn(...args);
|
|
177
|
+
} catch (err) {
|
|
178
|
+
if (err instanceof ApiError) {
|
|
179
|
+
console.error(chalk.red(`Error [${err.statusCode}]: ${err.message}`));
|
|
180
|
+
} else if (err instanceof Error) {
|
|
181
|
+
if (err.name === "ExitPromptError") {
|
|
182
|
+
process.exit(0);
|
|
183
|
+
}
|
|
184
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
185
|
+
} else {
|
|
186
|
+
console.error(chalk.red("Unknown error occurred"));
|
|
187
|
+
}
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
function requireOption(value, name) {
|
|
193
|
+
if (!value) {
|
|
194
|
+
console.error(chalk.red(`Missing required option: ${name}`));
|
|
195
|
+
console.error(chalk.dim(`Set via --${name}, env FASTCLAW_${name.toUpperCase().replace(/-/g, "_")}, or 'fastclaw config set ${name} <value>'`));
|
|
196
|
+
process.exit(1);
|
|
197
|
+
}
|
|
198
|
+
return value;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// src/output.ts
|
|
202
|
+
import chalk2 from "chalk";
|
|
203
|
+
import Table from "cli-table3";
|
|
204
|
+
import ora from "ora";
|
|
205
|
+
import boxen from "boxen";
|
|
206
|
+
function printJson(data) {
|
|
207
|
+
console.log(JSON.stringify(data, null, 2));
|
|
208
|
+
}
|
|
209
|
+
function printTable(headers, rows) {
|
|
210
|
+
const table = new Table({
|
|
211
|
+
head: headers.map((h) => chalk2.cyan(h)),
|
|
212
|
+
style: { head: [], border: [] }
|
|
213
|
+
});
|
|
214
|
+
rows.forEach((r) => table.push(r));
|
|
215
|
+
console.log(table.toString());
|
|
216
|
+
}
|
|
217
|
+
function printKeyValue(pairs) {
|
|
218
|
+
const maxLen = Math.max(...Object.keys(pairs).map((k) => k.length));
|
|
219
|
+
for (const [key, value] of Object.entries(pairs)) {
|
|
220
|
+
if (value !== void 0) {
|
|
221
|
+
console.log(` ${chalk2.dim(key.padEnd(maxLen))} ${value}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function printSuccess(msg) {
|
|
226
|
+
console.log(chalk2.green("\u2713") + " " + msg);
|
|
227
|
+
}
|
|
228
|
+
function printBox(lines, title) {
|
|
229
|
+
const content = lines.join("\n");
|
|
230
|
+
console.log(
|
|
231
|
+
boxen(content, {
|
|
232
|
+
padding: 1,
|
|
233
|
+
margin: { top: 1, bottom: 1, left: 0, right: 0 },
|
|
234
|
+
borderStyle: "round",
|
|
235
|
+
title,
|
|
236
|
+
titleAlignment: "left"
|
|
237
|
+
})
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
function startSpinner(text) {
|
|
241
|
+
return ora({ text, color: "cyan" }).start();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// src/commands/config-cmd.ts
|
|
245
|
+
function registerConfigCommand(program2) {
|
|
246
|
+
const cmd = program2.command("config").description("Manage local configuration");
|
|
247
|
+
cmd.command("show").description("Show current configuration").action(
|
|
248
|
+
withErrorHandler(async () => {
|
|
249
|
+
const config = loadConfig();
|
|
250
|
+
const parent = program2.opts();
|
|
251
|
+
if (parent.json) {
|
|
252
|
+
printJson(config);
|
|
253
|
+
} else {
|
|
254
|
+
console.log(chalk3.dim(`Config file: ${getConfigPath()}
|
|
255
|
+
`));
|
|
256
|
+
if (Object.keys(config).length === 0) {
|
|
257
|
+
console.log(chalk3.dim(" (empty \u2014 run 'fastclaw setup' to configure)"));
|
|
258
|
+
} else {
|
|
259
|
+
const display = {};
|
|
260
|
+
for (const [k, v] of Object.entries(config)) {
|
|
261
|
+
display[k] = k.includes("token") && v ? maskToken(v) : v;
|
|
262
|
+
}
|
|
263
|
+
printKeyValue(display);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
})
|
|
267
|
+
);
|
|
268
|
+
cmd.command("get <key>").description("Get a config value").action(
|
|
269
|
+
withErrorHandler(async (key) => {
|
|
270
|
+
const config = loadConfig();
|
|
271
|
+
const value = config[key];
|
|
272
|
+
if (value === void 0) {
|
|
273
|
+
console.log(chalk3.dim("(not set)"));
|
|
274
|
+
} else {
|
|
275
|
+
console.log(String(value));
|
|
276
|
+
}
|
|
277
|
+
})
|
|
278
|
+
);
|
|
279
|
+
cmd.command("set <key> <value>").description("Set a config value").action(
|
|
280
|
+
withErrorHandler(async (key, value) => {
|
|
281
|
+
const config = loadConfig();
|
|
282
|
+
config[key] = value;
|
|
283
|
+
saveConfig(config);
|
|
284
|
+
console.log(chalk3.green("\u2713") + ` ${key} = ${key.includes("token") ? maskToken(value) : value}`);
|
|
285
|
+
})
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
function maskToken(token) {
|
|
289
|
+
if (token.length <= 8) return "\u2022\u2022\u2022\u2022";
|
|
290
|
+
return token.slice(0, 4) + "\u2022\u2022\u2022\u2022" + token.slice(-4);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// src/commands/admin/apps.ts
|
|
294
|
+
import chalk4 from "chalk";
|
|
295
|
+
|
|
296
|
+
// src/client.ts
|
|
297
|
+
var FastClawClient = class {
|
|
298
|
+
baseUrl;
|
|
299
|
+
adminToken;
|
|
300
|
+
appToken;
|
|
301
|
+
constructor(opts) {
|
|
302
|
+
this.baseUrl = opts.url.replace(/\/+$/, "") + API_BASE;
|
|
303
|
+
this.adminToken = opts.adminToken;
|
|
304
|
+
this.appToken = opts.appToken;
|
|
305
|
+
}
|
|
306
|
+
// ── Helpers ──
|
|
307
|
+
async request(method, path, opts) {
|
|
308
|
+
const authType = opts?.auth ?? "app";
|
|
309
|
+
const token = authType === "admin" ? this.adminToken : this.appToken;
|
|
310
|
+
if (!token) {
|
|
311
|
+
throw new Error(`No ${authType} token configured. Set via --${authType}-token, env, or 'fastclaw config set'.`);
|
|
312
|
+
}
|
|
313
|
+
let url = this.baseUrl + path;
|
|
314
|
+
if (opts?.query) {
|
|
315
|
+
const params = new URLSearchParams();
|
|
316
|
+
for (const [k, v] of Object.entries(opts.query)) {
|
|
317
|
+
if (v !== void 0 && v !== "") params.set(k, v);
|
|
318
|
+
}
|
|
319
|
+
const qs = params.toString();
|
|
320
|
+
if (qs) url += "?" + qs;
|
|
321
|
+
}
|
|
322
|
+
const headers = {
|
|
323
|
+
Authorization: `Bearer ${token}`
|
|
324
|
+
};
|
|
325
|
+
let bodyStr;
|
|
326
|
+
if (opts?.body !== void 0) {
|
|
327
|
+
headers["Content-Type"] = "application/json";
|
|
328
|
+
bodyStr = JSON.stringify(opts.body);
|
|
329
|
+
}
|
|
330
|
+
const res = await fetch(url, { method, headers, body: bodyStr });
|
|
331
|
+
if (!res.ok) {
|
|
332
|
+
let msg = res.statusText;
|
|
333
|
+
try {
|
|
334
|
+
const j = await res.json();
|
|
335
|
+
if (j.message) msg = j.message;
|
|
336
|
+
} catch {
|
|
337
|
+
}
|
|
338
|
+
throw new ApiError(res.status, res.status, msg);
|
|
339
|
+
}
|
|
340
|
+
const text = await res.text();
|
|
341
|
+
if (!text) return void 0;
|
|
342
|
+
const json = JSON.parse(text);
|
|
343
|
+
if (json && typeof json === "object" && "code" in json && json.code === 0 && "data" in json) {
|
|
344
|
+
return json.data;
|
|
345
|
+
}
|
|
346
|
+
return json;
|
|
347
|
+
}
|
|
348
|
+
// ── Admin: Apps ──
|
|
349
|
+
async createApp(req) {
|
|
350
|
+
return this.request("POST", "/admin/apps", { body: req, auth: "admin" });
|
|
351
|
+
}
|
|
352
|
+
async listApps() {
|
|
353
|
+
return this.request("GET", "/admin/apps", { auth: "admin" });
|
|
354
|
+
}
|
|
355
|
+
async getApp(id) {
|
|
356
|
+
return this.request("GET", `/admin/apps/${id}`, { auth: "admin" });
|
|
357
|
+
}
|
|
358
|
+
async updateApp(id, req) {
|
|
359
|
+
return this.request("PUT", `/admin/apps/${id}`, { body: req, auth: "admin" });
|
|
360
|
+
}
|
|
361
|
+
async deleteApp(id) {
|
|
362
|
+
return this.request("DELETE", `/admin/apps/${id}`, { auth: "admin" });
|
|
363
|
+
}
|
|
364
|
+
async rotateAppToken(id) {
|
|
365
|
+
return this.request("POST", `/admin/apps/${id}/reset-token`, { auth: "admin" });
|
|
366
|
+
}
|
|
367
|
+
// ── Admin: Bot Upgrade ──
|
|
368
|
+
async upgradeBot(id, req) {
|
|
369
|
+
return this.request("POST", `/admin/bots/${id}/upgrade`, { body: req ?? {}, auth: "admin" });
|
|
370
|
+
}
|
|
371
|
+
async upgradeAllBots(req) {
|
|
372
|
+
return this.request("POST", "/admin/bots/upgrade", { body: req ?? {}, auth: "admin" });
|
|
373
|
+
}
|
|
374
|
+
// ── Bots ──
|
|
375
|
+
async createBot(req) {
|
|
376
|
+
return this.request("POST", "/bots", { body: req });
|
|
377
|
+
}
|
|
378
|
+
async listBots(userId) {
|
|
379
|
+
return this.request("GET", "/bots", { query: { user_id: userId } });
|
|
380
|
+
}
|
|
381
|
+
async getBot(id) {
|
|
382
|
+
return this.request("GET", `/bots/${id}`);
|
|
383
|
+
}
|
|
384
|
+
async updateBot(id, req) {
|
|
385
|
+
return this.request("PUT", `/bots/${id}`, { body: req });
|
|
386
|
+
}
|
|
387
|
+
async deleteBot(id) {
|
|
388
|
+
return this.request("DELETE", `/bots/${id}`);
|
|
389
|
+
}
|
|
390
|
+
// ── Bot Lifecycle ──
|
|
391
|
+
async startBot(id) {
|
|
392
|
+
return this.request("POST", `/bots/${id}/start`);
|
|
393
|
+
}
|
|
394
|
+
async stopBot(id) {
|
|
395
|
+
return this.request("POST", `/bots/${id}/stop`);
|
|
396
|
+
}
|
|
397
|
+
async restartBot(id) {
|
|
398
|
+
return this.request("POST", `/bots/${id}/restart`);
|
|
399
|
+
}
|
|
400
|
+
async getBotStatus(id) {
|
|
401
|
+
return this.request("GET", `/bots/${id}/status`);
|
|
402
|
+
}
|
|
403
|
+
async getBotConnect(id) {
|
|
404
|
+
return this.request("GET", `/bots/${id}/connect`);
|
|
405
|
+
}
|
|
406
|
+
async resetBotToken(id) {
|
|
407
|
+
return this.request("POST", `/bots/${id}/reset-token`);
|
|
408
|
+
}
|
|
409
|
+
// ── Model Providers ──
|
|
410
|
+
async listProviders(botId) {
|
|
411
|
+
return this.request("GET", `/bots/${botId}/config/models`);
|
|
412
|
+
}
|
|
413
|
+
async addProvider(botId, provider) {
|
|
414
|
+
return this.request("POST", `/bots/${botId}/config/models`, { body: provider });
|
|
415
|
+
}
|
|
416
|
+
async getProvider(botId, name) {
|
|
417
|
+
return this.request("GET", `/bots/${botId}/config/models/${name}`);
|
|
418
|
+
}
|
|
419
|
+
async updateProvider(botId, name, provider) {
|
|
420
|
+
return this.request("PUT", `/bots/${botId}/config/models/${name}`, { body: provider });
|
|
421
|
+
}
|
|
422
|
+
async deleteProvider(botId, name) {
|
|
423
|
+
return this.request("DELETE", `/bots/${botId}/config/models/${name}`);
|
|
424
|
+
}
|
|
425
|
+
// ── Model Defaults ──
|
|
426
|
+
async getDefaults(botId) {
|
|
427
|
+
return this.request("GET", `/bots/${botId}/config/defaults`);
|
|
428
|
+
}
|
|
429
|
+
async setDefaults(botId, defaults) {
|
|
430
|
+
return this.request("PUT", `/bots/${botId}/config/defaults`, { body: defaults });
|
|
431
|
+
}
|
|
432
|
+
// ── Channels ──
|
|
433
|
+
async addChannel(botId, req) {
|
|
434
|
+
return this.request("POST", `/bots/${botId}/channels`, { body: req });
|
|
435
|
+
}
|
|
436
|
+
async listChannels(botId) {
|
|
437
|
+
return this.request("GET", `/bots/${botId}/channels`);
|
|
438
|
+
}
|
|
439
|
+
async removeChannel(botId, channel, account) {
|
|
440
|
+
const query = {};
|
|
441
|
+
if (account) query.account = account;
|
|
442
|
+
return this.request("DELETE", `/bots/${botId}/channels/${channel}`, { query });
|
|
443
|
+
}
|
|
444
|
+
// ── Channel Pairing ──
|
|
445
|
+
async listPairing(botId, channel) {
|
|
446
|
+
return this.request("GET", `/bots/${botId}/channels/${channel}/pairing`);
|
|
447
|
+
}
|
|
448
|
+
async approvePairing(botId, channel, req) {
|
|
449
|
+
return this.request("POST", `/bots/${botId}/channels/${channel}/pairing/approve`, { body: req });
|
|
450
|
+
}
|
|
451
|
+
async revokePairing(botId, channel, req) {
|
|
452
|
+
return this.request("POST", `/bots/${botId}/channels/${channel}/pairing/revoke`, { body: req });
|
|
453
|
+
}
|
|
454
|
+
async listPairedUsers(botId, channel) {
|
|
455
|
+
return this.request("GET", `/bots/${botId}/channels/${channel}/pairing/users`);
|
|
456
|
+
}
|
|
457
|
+
// ── Devices ──
|
|
458
|
+
async listDevices(botId, opts) {
|
|
459
|
+
const query = {};
|
|
460
|
+
if (opts?.status) query.status = opts.status;
|
|
461
|
+
if (opts?.client_mode) query.client_mode = opts.client_mode;
|
|
462
|
+
return this.request("GET", `/bots/${botId}/devices`, { query });
|
|
463
|
+
}
|
|
464
|
+
async approveDevice(botId, requestId) {
|
|
465
|
+
return this.request("POST", `/bots/${botId}/devices/${requestId}/approve`);
|
|
466
|
+
}
|
|
467
|
+
async revokeDevice(botId, deviceId, role) {
|
|
468
|
+
const query = {};
|
|
469
|
+
if (role) query.role = role;
|
|
470
|
+
return this.request("DELETE", `/bots/${botId}/devices/${deviceId}`, { query });
|
|
471
|
+
}
|
|
472
|
+
// ── Skills ──
|
|
473
|
+
async listSkills(botId) {
|
|
474
|
+
return this.request("GET", `/bots/${botId}/skills`);
|
|
475
|
+
}
|
|
476
|
+
async setSkill(botId, name, req) {
|
|
477
|
+
return this.request("PUT", `/bots/${botId}/skills/${name}`, { body: req });
|
|
478
|
+
}
|
|
479
|
+
async deleteSkill(botId, name) {
|
|
480
|
+
return this.request("DELETE", `/bots/${botId}/skills/${name}`);
|
|
481
|
+
}
|
|
482
|
+
// ── Utility: wait for bot ready ──
|
|
483
|
+
async waitForReady(botId, timeoutMs = 12e4, intervalMs = 3e3) {
|
|
484
|
+
const start = Date.now();
|
|
485
|
+
while (Date.now() - start < timeoutMs) {
|
|
486
|
+
const status = await this.getBotStatus(botId);
|
|
487
|
+
if (status.ready) return status;
|
|
488
|
+
if (status.status === "error") throw new Error("Bot entered error state");
|
|
489
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
490
|
+
}
|
|
491
|
+
throw new Error(`Bot not ready after ${timeoutMs / 1e3}s`);
|
|
492
|
+
}
|
|
493
|
+
// ── Health check ──
|
|
494
|
+
async health() {
|
|
495
|
+
try {
|
|
496
|
+
const url = this.baseUrl.replace(API_BASE, "") + "/health";
|
|
497
|
+
const res = await fetch(url);
|
|
498
|
+
return res.ok;
|
|
499
|
+
} catch {
|
|
500
|
+
return false;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
// src/commands/admin/apps.ts
|
|
506
|
+
function getClient(program2) {
|
|
507
|
+
const opts = program2.parent.parent.opts();
|
|
508
|
+
const cfg = resolveConfig(opts);
|
|
509
|
+
requireOption(cfg.adminToken, "admin-token");
|
|
510
|
+
return { client: new FastClawClient({ url: cfg.url, adminToken: cfg.adminToken }), cfg };
|
|
511
|
+
}
|
|
512
|
+
function registerAppsCommand(admin) {
|
|
513
|
+
const apps = admin.command("apps").description("Manage apps");
|
|
514
|
+
apps.command("create").description("Create a new app").requiredOption("-n, --name <name>", "App name").option("--url <url>", "App website URL").option("--description <desc>", "App description").option("--owner-email <email>", "Owner email").option("--bot-domain-template <tpl>", "Bot domain template").action(
|
|
515
|
+
withErrorHandler(async (cmdOpts) => {
|
|
516
|
+
const { client, cfg } = getClient(apps);
|
|
517
|
+
const app = await client.createApp({
|
|
518
|
+
name: cmdOpts.name,
|
|
519
|
+
url: cmdOpts.url,
|
|
520
|
+
description: cmdOpts.description,
|
|
521
|
+
owner_email: cmdOpts.ownerEmail,
|
|
522
|
+
bot_domain_template: cmdOpts.botDomainTemplate
|
|
523
|
+
});
|
|
524
|
+
if (cfg.json) {
|
|
525
|
+
printJson(app);
|
|
526
|
+
} else {
|
|
527
|
+
printSuccess(`App created: ${app.id}`);
|
|
528
|
+
printKeyValue({
|
|
529
|
+
ID: app.id,
|
|
530
|
+
Name: app.name,
|
|
531
|
+
Token: app.api_token,
|
|
532
|
+
Status: app.status
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
})
|
|
536
|
+
);
|
|
537
|
+
apps.command("list").description("List all apps").action(
|
|
538
|
+
withErrorHandler(async () => {
|
|
539
|
+
const { client, cfg } = getClient(apps);
|
|
540
|
+
const list = await client.listApps();
|
|
541
|
+
if (cfg.json) {
|
|
542
|
+
printJson(list);
|
|
543
|
+
} else if (list.length === 0) {
|
|
544
|
+
console.log(chalk4.dim("No apps found."));
|
|
545
|
+
} else {
|
|
546
|
+
printTable(
|
|
547
|
+
["ID", "Name", "Status", "Created"],
|
|
548
|
+
list.map((a) => [a.id, a.name, a.status, a.created_at])
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
})
|
|
552
|
+
);
|
|
553
|
+
apps.command("delete <id>").description("Delete an app").action(
|
|
554
|
+
withErrorHandler(async (id) => {
|
|
555
|
+
const { client, cfg } = getClient(apps);
|
|
556
|
+
const res = await client.deleteApp(id);
|
|
557
|
+
if (cfg.json) {
|
|
558
|
+
printJson(res);
|
|
559
|
+
} else {
|
|
560
|
+
printSuccess(`App ${id} deleted`);
|
|
561
|
+
}
|
|
562
|
+
})
|
|
563
|
+
);
|
|
564
|
+
apps.command("rotate-token <id>").description("Reset an app's API token").action(
|
|
565
|
+
withErrorHandler(async (id) => {
|
|
566
|
+
const { client, cfg } = getClient(apps);
|
|
567
|
+
const res = await client.rotateAppToken(id);
|
|
568
|
+
if (cfg.json) {
|
|
569
|
+
printJson(res);
|
|
570
|
+
} else {
|
|
571
|
+
printSuccess(`Token rotated for app ${id}`);
|
|
572
|
+
console.log(` New token: ${res.api_token}`);
|
|
573
|
+
}
|
|
574
|
+
})
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// src/commands/admin/index.ts
|
|
579
|
+
function registerAdminCommand(program2) {
|
|
580
|
+
const admin = program2.command("admin").description("Admin operations");
|
|
581
|
+
registerAppsCommand(admin);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// src/commands/bot/crud.ts
|
|
585
|
+
import chalk5 from "chalk";
|
|
586
|
+
function getClient2(bot) {
|
|
587
|
+
const opts = { ...bot.parent.opts(), ...bot.opts() };
|
|
588
|
+
const cfg = resolveConfig(opts);
|
|
589
|
+
requireOption(cfg.appToken, "app-token");
|
|
590
|
+
return { client: new FastClawClient({ url: cfg.url, appToken: cfg.appToken }), cfg };
|
|
591
|
+
}
|
|
592
|
+
function registerBotCrudCommands(bot) {
|
|
593
|
+
bot.command("create").description("Create a new bot").requiredOption("-n, --name <name>", "Bot name").option("--slug <slug>", "URL slug").option("--user-id <userId>", "User ID").option("--expires-at <date>", "Expiration date (ISO-8601)").action(
|
|
594
|
+
withErrorHandler(async (cmdOpts) => {
|
|
595
|
+
const { client, cfg } = getClient2(bot);
|
|
596
|
+
const userId = cmdOpts.userId || cfg.userId;
|
|
597
|
+
const result = await client.createBot({
|
|
598
|
+
user_id: userId,
|
|
599
|
+
name: cmdOpts.name,
|
|
600
|
+
slug: cmdOpts.slug,
|
|
601
|
+
expires_at: cmdOpts.expiresAt
|
|
602
|
+
});
|
|
603
|
+
if (cfg.json) {
|
|
604
|
+
printJson(result);
|
|
605
|
+
} else {
|
|
606
|
+
printSuccess(`Bot created: ${result.id}`);
|
|
607
|
+
printKeyValue({
|
|
608
|
+
ID: result.id,
|
|
609
|
+
Name: result.name,
|
|
610
|
+
Slug: result.slug,
|
|
611
|
+
Status: result.status,
|
|
612
|
+
"Access Token": result.access_token,
|
|
613
|
+
"Access URL": result.access_url || ""
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
})
|
|
617
|
+
);
|
|
618
|
+
bot.command("list").description("List bots").option("--user-id <userId>", "User ID").action(
|
|
619
|
+
withErrorHandler(async (cmdOpts) => {
|
|
620
|
+
const { client, cfg } = getClient2(bot);
|
|
621
|
+
const userId = cmdOpts.userId || cfg.userId;
|
|
622
|
+
const list = await client.listBots(userId);
|
|
623
|
+
if (cfg.json) {
|
|
624
|
+
printJson(list);
|
|
625
|
+
} else if (list.length === 0) {
|
|
626
|
+
console.log(chalk5.dim("No bots found."));
|
|
627
|
+
} else {
|
|
628
|
+
printTable(
|
|
629
|
+
["ID", "Name", "Slug", "Status", "Created"],
|
|
630
|
+
list.map((b) => [b.id, b.name, b.slug, b.status, b.created_at])
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
})
|
|
634
|
+
);
|
|
635
|
+
bot.command("get <id>").description("Get bot details").action(
|
|
636
|
+
withErrorHandler(async (id) => {
|
|
637
|
+
const { client, cfg } = getClient2(bot);
|
|
638
|
+
const b = await client.getBot(id);
|
|
639
|
+
if (cfg.json) {
|
|
640
|
+
printJson(b);
|
|
641
|
+
} else {
|
|
642
|
+
printKeyValue({
|
|
643
|
+
ID: b.id,
|
|
644
|
+
Name: b.name,
|
|
645
|
+
Slug: b.slug,
|
|
646
|
+
Status: b.status,
|
|
647
|
+
"User ID": b.user_id,
|
|
648
|
+
Endpoint: b.endpoint || "(none)",
|
|
649
|
+
"Access Token": b.access_token,
|
|
650
|
+
Image: b.image || "",
|
|
651
|
+
"Up to date": b.image_up_to_date !== void 0 ? String(b.image_up_to_date) : "",
|
|
652
|
+
"Expires at": b.expires_at || "(never)",
|
|
653
|
+
Created: b.created_at,
|
|
654
|
+
Updated: b.updated_at
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
})
|
|
658
|
+
);
|
|
659
|
+
bot.command("update <id>").description("Update a bot").option("-n, --name <name>", "New name").option("--slug <slug>", "New slug").option("--expires-at <date>", "New expiration date").action(
|
|
660
|
+
withErrorHandler(async (id, cmdOpts) => {
|
|
661
|
+
const { client, cfg } = getClient2(bot);
|
|
662
|
+
const result = await client.updateBot(id, {
|
|
663
|
+
name: cmdOpts.name,
|
|
664
|
+
slug: cmdOpts.slug,
|
|
665
|
+
expires_at: cmdOpts.expiresAt
|
|
666
|
+
});
|
|
667
|
+
if (cfg.json) {
|
|
668
|
+
printJson(result);
|
|
669
|
+
} else {
|
|
670
|
+
printSuccess(`Bot ${id} updated`);
|
|
671
|
+
}
|
|
672
|
+
})
|
|
673
|
+
);
|
|
674
|
+
bot.command("delete <id>").description("Delete a bot").action(
|
|
675
|
+
withErrorHandler(async (id) => {
|
|
676
|
+
const { client, cfg } = getClient2(bot);
|
|
677
|
+
const res = await client.deleteBot(id);
|
|
678
|
+
if (cfg.json) {
|
|
679
|
+
printJson(res);
|
|
680
|
+
} else {
|
|
681
|
+
printSuccess(`Bot ${id} deleted`);
|
|
682
|
+
}
|
|
683
|
+
})
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// src/commands/bot/lifecycle.ts
|
|
688
|
+
import chalk6 from "chalk";
|
|
689
|
+
function getClient3(bot) {
|
|
690
|
+
const opts = { ...bot.parent.opts(), ...bot.opts() };
|
|
691
|
+
const cfg = resolveConfig(opts);
|
|
692
|
+
requireOption(cfg.appToken, "app-token");
|
|
693
|
+
return { client: new FastClawClient({ url: cfg.url, appToken: cfg.appToken }), cfg };
|
|
694
|
+
}
|
|
695
|
+
function buildBotUrls(baseUrl, slug, accessToken) {
|
|
696
|
+
const { hostname } = new URL(baseUrl);
|
|
697
|
+
const webchatUrl = `https://${slug}.${hostname}#token=${accessToken}`;
|
|
698
|
+
const wsUrl = `wss://${slug}.${hostname}`;
|
|
699
|
+
return { webchatUrl, wsUrl };
|
|
700
|
+
}
|
|
701
|
+
function registerBotLifecycleCommands(bot) {
|
|
702
|
+
bot.command("start <id>").description("Start a bot").option("--wait", "Wait until bot is ready", true).action(
|
|
703
|
+
withErrorHandler(async (id, cmdOpts) => {
|
|
704
|
+
const { client, cfg } = getClient3(bot);
|
|
705
|
+
await client.startBot(id);
|
|
706
|
+
if (cmdOpts.wait) {
|
|
707
|
+
const spinner = startSpinner("Waiting for bot to be ready...");
|
|
708
|
+
try {
|
|
709
|
+
const status = await client.waitForReady(id);
|
|
710
|
+
spinner.succeed("Bot is ready");
|
|
711
|
+
if (cfg.json) {
|
|
712
|
+
printJson(status);
|
|
713
|
+
}
|
|
714
|
+
} catch (err) {
|
|
715
|
+
spinner.fail("Bot failed to become ready");
|
|
716
|
+
throw err;
|
|
717
|
+
}
|
|
718
|
+
} else {
|
|
719
|
+
printSuccess(`Bot ${id} starting`);
|
|
720
|
+
}
|
|
721
|
+
})
|
|
722
|
+
);
|
|
723
|
+
bot.command("stop <id>").description("Stop a bot").action(
|
|
724
|
+
withErrorHandler(async (id) => {
|
|
725
|
+
const { client, cfg } = getClient3(bot);
|
|
726
|
+
const res = await client.stopBot(id);
|
|
727
|
+
if (cfg.json) {
|
|
728
|
+
printJson(res);
|
|
729
|
+
} else {
|
|
730
|
+
printSuccess(`Bot ${id} stopped`);
|
|
731
|
+
}
|
|
732
|
+
})
|
|
733
|
+
);
|
|
734
|
+
bot.command("restart <id>").description("Restart a bot").action(
|
|
735
|
+
withErrorHandler(async (id) => {
|
|
736
|
+
const { client, cfg } = getClient3(bot);
|
|
737
|
+
const res = await client.restartBot(id);
|
|
738
|
+
if (cfg.json) {
|
|
739
|
+
printJson(res);
|
|
740
|
+
} else {
|
|
741
|
+
printSuccess(`Bot ${id} restarting`);
|
|
742
|
+
}
|
|
743
|
+
})
|
|
744
|
+
);
|
|
745
|
+
bot.command("status <id>").description("Get bot status").action(
|
|
746
|
+
withErrorHandler(async (id) => {
|
|
747
|
+
const { client, cfg } = getClient3(bot);
|
|
748
|
+
const s = await client.getBotStatus(id);
|
|
749
|
+
const urls = s.status === "running" && s.ready ? await client.getBot(id).then((b) => buildBotUrls(cfg.url, b.slug, b.access_token)) : null;
|
|
750
|
+
if (cfg.json) {
|
|
751
|
+
if (urls) {
|
|
752
|
+
printJson({ ...s, webchat_url: urls.webchatUrl, ws_url: urls.wsUrl });
|
|
753
|
+
} else {
|
|
754
|
+
printJson(s);
|
|
755
|
+
}
|
|
756
|
+
} else {
|
|
757
|
+
const readyLabel = s.ready ? chalk6.green("ready") : chalk6.yellow("not ready");
|
|
758
|
+
const kv = {
|
|
759
|
+
ID: s.id,
|
|
760
|
+
Name: s.name,
|
|
761
|
+
Status: s.status,
|
|
762
|
+
Ready: readyLabel,
|
|
763
|
+
Endpoint: s.endpoint || "(none)"
|
|
764
|
+
};
|
|
765
|
+
if (urls) {
|
|
766
|
+
kv["Webchat"] = urls.webchatUrl;
|
|
767
|
+
kv["WebSocket"] = urls.wsUrl;
|
|
768
|
+
}
|
|
769
|
+
printKeyValue(kv);
|
|
770
|
+
}
|
|
771
|
+
})
|
|
772
|
+
);
|
|
773
|
+
bot.command("connect <id>").description("Get bot connection info").action(
|
|
774
|
+
withErrorHandler(async (id) => {
|
|
775
|
+
const { client, cfg } = getClient3(bot);
|
|
776
|
+
const [c, b] = await Promise.all([client.getBotConnect(id), client.getBot(id)]);
|
|
777
|
+
const urls = buildBotUrls(cfg.url, b.slug, b.access_token);
|
|
778
|
+
if (cfg.json) {
|
|
779
|
+
printJson({ ...c, webchat_url: urls.webchatUrl, ws_url: urls.wsUrl });
|
|
780
|
+
} else {
|
|
781
|
+
printBox(
|
|
782
|
+
[
|
|
783
|
+
`${chalk6.dim("Bot ID:")} ${c.id}`,
|
|
784
|
+
`${chalk6.dim("Status:")} ${c.status}`,
|
|
785
|
+
`${chalk6.dim("Ready:")} ${c.ready ? chalk6.green("yes") : chalk6.yellow("no")}`,
|
|
786
|
+
`${chalk6.dim("Webchat:")} ${urls.webchatUrl}`,
|
|
787
|
+
`${chalk6.dim("WebSocket:")} ${urls.wsUrl}`
|
|
788
|
+
],
|
|
789
|
+
"Connection Info"
|
|
790
|
+
);
|
|
791
|
+
}
|
|
792
|
+
})
|
|
793
|
+
);
|
|
794
|
+
bot.command("reset-token <id>").description("Reset bot access token").action(
|
|
795
|
+
withErrorHandler(async (id) => {
|
|
796
|
+
const { client, cfg } = getClient3(bot);
|
|
797
|
+
const res = await client.resetBotToken(id);
|
|
798
|
+
if (cfg.json) {
|
|
799
|
+
printJson(res);
|
|
800
|
+
} else {
|
|
801
|
+
printSuccess("Access token reset");
|
|
802
|
+
printKeyValue({
|
|
803
|
+
"New Token": res.access_token,
|
|
804
|
+
"Access URL": res.access_url
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
})
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// src/commands/bot/index.ts
|
|
812
|
+
function registerBotCommand(program2) {
|
|
813
|
+
const bot = program2.command("bot").description("Manage bots").option("--user-id <userId>", "User ID for bot operations");
|
|
814
|
+
registerBotCrudCommands(bot);
|
|
815
|
+
registerBotLifecycleCommands(bot);
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// src/commands/model/provider.ts
|
|
819
|
+
import chalk7 from "chalk";
|
|
820
|
+
|
|
821
|
+
// src/utils/prompt.ts
|
|
822
|
+
import { input, select, password, confirm } from "@inquirer/prompts";
|
|
823
|
+
async function promptText(message, defaultValue) {
|
|
824
|
+
return input({ message, default: defaultValue });
|
|
825
|
+
}
|
|
826
|
+
async function promptPassword(message) {
|
|
827
|
+
return password({ message, mask: "\u2022" });
|
|
828
|
+
}
|
|
829
|
+
async function promptSelect(message, choices) {
|
|
830
|
+
return select({ message, choices });
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// src/commands/model/provider.ts
|
|
834
|
+
function getClient4(model) {
|
|
835
|
+
const opts = model.parent.opts();
|
|
836
|
+
const cfg = resolveConfig(opts);
|
|
837
|
+
requireOption(cfg.appToken, "app-token");
|
|
838
|
+
return { client: new FastClawClient({ url: cfg.url, appToken: cfg.appToken }), cfg };
|
|
839
|
+
}
|
|
840
|
+
function registerModelProviderCommands(model) {
|
|
841
|
+
model.command("add <bot-id>").description("Add a model provider (interactive or preset)").option("-p, --provider <name>", "Provider name or preset (google/anthropic/openai)").option("-k, --api-key <key>", "API key").action(
|
|
842
|
+
withErrorHandler(async (botId, cmdOpts) => {
|
|
843
|
+
const { client, cfg } = getClient4(model);
|
|
844
|
+
let providerName = cmdOpts.provider;
|
|
845
|
+
let apiKey = cmdOpts.apiKey;
|
|
846
|
+
if (!providerName) {
|
|
847
|
+
providerName = await promptSelect("Model Provider:", [
|
|
848
|
+
{ name: "Google (Gemini)", value: "google" },
|
|
849
|
+
{ name: "Anthropic (Claude)", value: "anthropic" },
|
|
850
|
+
{ name: "OpenAI (GPT)", value: "openai" },
|
|
851
|
+
{ name: "MiniMax", value: "minimax" },
|
|
852
|
+
{ name: "Custom", value: "custom" }
|
|
853
|
+
]);
|
|
854
|
+
}
|
|
855
|
+
let provider;
|
|
856
|
+
if (providerName === "custom") {
|
|
857
|
+
const name = await promptText("Provider name:");
|
|
858
|
+
const baseUrl = await promptText("Base URL:");
|
|
859
|
+
apiKey = apiKey || await promptPassword("API Key:");
|
|
860
|
+
const modelId = await promptText("Model ID:");
|
|
861
|
+
const modelName = await promptText("Model display name:", modelId);
|
|
862
|
+
provider = {
|
|
863
|
+
name,
|
|
864
|
+
baseUrl,
|
|
865
|
+
apiKey,
|
|
866
|
+
models: [{ id: modelId, name: modelName, input: ["text"] }]
|
|
867
|
+
};
|
|
868
|
+
} else if (MODEL_PRESETS[providerName]) {
|
|
869
|
+
const preset = MODEL_PRESETS[providerName];
|
|
870
|
+
apiKey = apiKey || await promptPassword(`${providerName} API Key:`);
|
|
871
|
+
provider = { ...preset, apiKey };
|
|
872
|
+
} else {
|
|
873
|
+
apiKey = apiKey || await promptPassword("API Key:");
|
|
874
|
+
provider = { name: providerName, apiKey, models: [] };
|
|
875
|
+
}
|
|
876
|
+
const result = await client.addProvider(botId, provider);
|
|
877
|
+
if (cfg.json) {
|
|
878
|
+
printJson(result);
|
|
879
|
+
} else {
|
|
880
|
+
printSuccess(`Provider '${provider.name}' added to bot ${botId}`);
|
|
881
|
+
}
|
|
882
|
+
})
|
|
883
|
+
);
|
|
884
|
+
model.command("list <bot-id>").description("List model providers").action(
|
|
885
|
+
withErrorHandler(async (botId) => {
|
|
886
|
+
const { client, cfg } = getClient4(model);
|
|
887
|
+
const providers = await client.listProviders(botId);
|
|
888
|
+
if (cfg.json) {
|
|
889
|
+
printJson(providers);
|
|
890
|
+
} else if (!providers || Object.keys(providers).length === 0) {
|
|
891
|
+
console.log(chalk7.dim("No model providers configured."));
|
|
892
|
+
} else {
|
|
893
|
+
const rows = [];
|
|
894
|
+
for (const [name, p] of Object.entries(providers)) {
|
|
895
|
+
const models = (p.models || []).map((m) => m.id).join(", ");
|
|
896
|
+
rows.push([name, p.baseUrl || "(default)", models]);
|
|
897
|
+
}
|
|
898
|
+
printTable(["Provider", "Base URL", "Models"], rows);
|
|
899
|
+
}
|
|
900
|
+
})
|
|
901
|
+
);
|
|
902
|
+
model.command("get <bot-id> <provider>").description("Get provider details").action(
|
|
903
|
+
withErrorHandler(async (botId, provider) => {
|
|
904
|
+
const { client, cfg } = getClient4(model);
|
|
905
|
+
const p = await client.getProvider(botId, provider);
|
|
906
|
+
if (cfg.json) {
|
|
907
|
+
printJson(p);
|
|
908
|
+
} else {
|
|
909
|
+
printKeyValue({
|
|
910
|
+
Name: p.name,
|
|
911
|
+
"Base URL": p.baseUrl || "(default)",
|
|
912
|
+
"API Key": p.apiKey ? "\u2022\u2022\u2022\u2022" + p.apiKey.slice(-4) : "(not set)",
|
|
913
|
+
Models: (p.models || []).map((m) => m.id).join(", ")
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
})
|
|
917
|
+
);
|
|
918
|
+
model.command("delete <bot-id> <provider>").description("Delete a model provider").action(
|
|
919
|
+
withErrorHandler(async (botId, provider) => {
|
|
920
|
+
const { client, cfg } = getClient4(model);
|
|
921
|
+
await client.deleteProvider(botId, provider);
|
|
922
|
+
if (cfg.json) {
|
|
923
|
+
printJson({ message: "deleted" });
|
|
924
|
+
} else {
|
|
925
|
+
printSuccess(`Provider '${provider}' deleted from bot ${botId}`);
|
|
926
|
+
}
|
|
927
|
+
})
|
|
928
|
+
);
|
|
929
|
+
const defaults = model.command("defaults").description("Manage default model settings");
|
|
930
|
+
defaults.command("get <bot-id>").description("Get default model settings").action(
|
|
931
|
+
withErrorHandler(async (botId) => {
|
|
932
|
+
const { client, cfg } = getClient4(model);
|
|
933
|
+
const d = await client.getDefaults(botId);
|
|
934
|
+
if (cfg.json) {
|
|
935
|
+
printJson(d);
|
|
936
|
+
} else {
|
|
937
|
+
printKeyValue({
|
|
938
|
+
"Primary Model": d.primary_model || "(not set)",
|
|
939
|
+
"Fallback Model": d.fallback_model || "(not set)"
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
})
|
|
943
|
+
);
|
|
944
|
+
defaults.command("set <bot-id>").description("Set default model").option("--primary <model>", "Primary model (provider/model_id)").option("--fallback <model>", "Fallback model").action(
|
|
945
|
+
withErrorHandler(async (botId, cmdOpts) => {
|
|
946
|
+
const { client, cfg } = getClient4(model);
|
|
947
|
+
const body = {};
|
|
948
|
+
if (cmdOpts.primary) body.primary_model = cmdOpts.primary;
|
|
949
|
+
if (cmdOpts.fallback) body.fallback_model = cmdOpts.fallback;
|
|
950
|
+
const d = await client.setDefaults(botId, body);
|
|
951
|
+
if (cfg.json) {
|
|
952
|
+
printJson(d);
|
|
953
|
+
} else {
|
|
954
|
+
printSuccess("Defaults updated");
|
|
955
|
+
printKeyValue({
|
|
956
|
+
"Primary Model": d.primary_model || "(not set)",
|
|
957
|
+
"Fallback Model": d.fallback_model || "(not set)"
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
})
|
|
961
|
+
);
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
// src/commands/model/index.ts
|
|
965
|
+
function registerModelCommand(program2) {
|
|
966
|
+
const model = program2.command("model").description("Manage model providers");
|
|
967
|
+
registerModelProviderCommands(model);
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// src/commands/channel/crud.ts
|
|
971
|
+
import chalk8 from "chalk";
|
|
972
|
+
function getClient5(parent) {
|
|
973
|
+
const opts = parent.parent.opts();
|
|
974
|
+
const cfg = resolveConfig(opts);
|
|
975
|
+
requireOption(cfg.appToken, "app-token");
|
|
976
|
+
return { client: new FastClawClient({ url: cfg.url, appToken: cfg.appToken }), cfg };
|
|
977
|
+
}
|
|
978
|
+
function registerChannelCrudCommands(channel) {
|
|
979
|
+
channel.command("add <bot-id>").description("Add an IM channel").option("-c, --channel <type>", "Channel type (feishu/telegram/slack/discord)").option("--account <name>", "Account name", "default").option("--dm-policy <policy>", "DM policy (open/pairing/allowlist/disabled)").option("--group-policy <policy>", "Group policy (open/allowlist/disabled)").action(
|
|
980
|
+
withErrorHandler(async (botId, cmdOpts) => {
|
|
981
|
+
const { client, cfg } = getClient5(channel);
|
|
982
|
+
let channelType = cmdOpts.channel;
|
|
983
|
+
if (!channelType) {
|
|
984
|
+
channelType = await promptSelect("Channel type:", [
|
|
985
|
+
{ name: "Feishu (\u98DE\u4E66)", value: "feishu" },
|
|
986
|
+
{ name: "Telegram", value: "telegram" },
|
|
987
|
+
{ name: "Slack", value: "slack" },
|
|
988
|
+
{ name: "Discord", value: "discord" }
|
|
989
|
+
]);
|
|
990
|
+
}
|
|
991
|
+
const req = {
|
|
992
|
+
channel: channelType,
|
|
993
|
+
account: cmdOpts.account,
|
|
994
|
+
dmPolicy: cmdOpts.dmPolicy,
|
|
995
|
+
groupPolicy: cmdOpts.groupPolicy
|
|
996
|
+
};
|
|
997
|
+
const fields = CHANNEL_FIELDS[channelType];
|
|
998
|
+
if (fields) {
|
|
999
|
+
for (const field of fields.required) {
|
|
1000
|
+
const isSecret = field.toLowerCase().includes("secret") || field.toLowerCase().includes("token") || field.toLowerCase().includes("password");
|
|
1001
|
+
const value = isSecret ? await promptPassword(`${field}:`) : await promptText(`${field}:`);
|
|
1002
|
+
req[field] = value;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
if (!req.dmPolicy) {
|
|
1006
|
+
req.dmPolicy = await promptSelect("DM Policy:", [
|
|
1007
|
+
{ name: "Open (anyone can message)", value: "open" },
|
|
1008
|
+
{ name: "Pairing (require pairing code)", value: "pairing" },
|
|
1009
|
+
{ name: "Disabled", value: "disabled" }
|
|
1010
|
+
]);
|
|
1011
|
+
}
|
|
1012
|
+
const result = await client.addChannel(botId, req);
|
|
1013
|
+
if (cfg.json) {
|
|
1014
|
+
printJson(result);
|
|
1015
|
+
} else {
|
|
1016
|
+
printSuccess(`Channel '${channelType}' added to bot ${botId}`);
|
|
1017
|
+
}
|
|
1018
|
+
})
|
|
1019
|
+
);
|
|
1020
|
+
channel.command("list <bot-id>").description("List channels").action(
|
|
1021
|
+
withErrorHandler(async (botId) => {
|
|
1022
|
+
const { client, cfg } = getClient5(channel);
|
|
1023
|
+
const channels = await client.listChannels(botId);
|
|
1024
|
+
if (cfg.json) {
|
|
1025
|
+
printJson(channels);
|
|
1026
|
+
} else if (!channels || Object.keys(channels).length === 0) {
|
|
1027
|
+
console.log(chalk8.dim("No channels configured."));
|
|
1028
|
+
} else {
|
|
1029
|
+
const rows = [];
|
|
1030
|
+
for (const [type, accounts] of Object.entries(channels)) {
|
|
1031
|
+
for (const [account, config] of Object.entries(accounts)) {
|
|
1032
|
+
rows.push([type, account, config.dmPolicy || "", config.groupPolicy || ""]);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
printTable(["Channel", "Account", "DM Policy", "Group Policy"], rows);
|
|
1036
|
+
}
|
|
1037
|
+
})
|
|
1038
|
+
);
|
|
1039
|
+
channel.command("remove <bot-id> <channel>").description("Remove a channel").option("--account <name>", "Specific account to remove").action(
|
|
1040
|
+
withErrorHandler(async (botId, ch, cmdOpts) => {
|
|
1041
|
+
const { client, cfg } = getClient5(channel);
|
|
1042
|
+
const result = await client.removeChannel(botId, ch, cmdOpts.account);
|
|
1043
|
+
if (cfg.json) {
|
|
1044
|
+
printJson(result);
|
|
1045
|
+
} else {
|
|
1046
|
+
printSuccess(`Channel '${ch}' removed from bot ${botId}`);
|
|
1047
|
+
}
|
|
1048
|
+
})
|
|
1049
|
+
);
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
// src/commands/channel/pairing.ts
|
|
1053
|
+
function getClient6(parent) {
|
|
1054
|
+
const root = parent.parent.parent;
|
|
1055
|
+
const opts = root.opts();
|
|
1056
|
+
const cfg = resolveConfig(opts);
|
|
1057
|
+
requireOption(cfg.appToken, "app-token");
|
|
1058
|
+
return { client: new FastClawClient({ url: cfg.url, appToken: cfg.appToken }), cfg };
|
|
1059
|
+
}
|
|
1060
|
+
function registerPairingCommands(channel) {
|
|
1061
|
+
const pairing = channel.command("pairing").description("Manage channel pairing");
|
|
1062
|
+
pairing.command("list <bot-id> <channel>").description("List pairing requests").action(
|
|
1063
|
+
withErrorHandler(async (botId, ch) => {
|
|
1064
|
+
const { client, cfg } = getClient6(pairing);
|
|
1065
|
+
const list = await client.listPairing(botId, ch);
|
|
1066
|
+
if (cfg.json) {
|
|
1067
|
+
printJson(list);
|
|
1068
|
+
} else {
|
|
1069
|
+
console.log(JSON.stringify(list, null, 2));
|
|
1070
|
+
}
|
|
1071
|
+
})
|
|
1072
|
+
);
|
|
1073
|
+
pairing.command("approve <bot-id> <channel>").description("Approve a pairing request").requiredOption("--code <code>", "Pairing code").action(
|
|
1074
|
+
withErrorHandler(async (botId, ch, cmdOpts) => {
|
|
1075
|
+
const { client, cfg } = getClient6(pairing);
|
|
1076
|
+
const result = await client.approvePairing(botId, ch, { code: cmdOpts.code });
|
|
1077
|
+
if (cfg.json) {
|
|
1078
|
+
printJson(result);
|
|
1079
|
+
} else {
|
|
1080
|
+
printSuccess(`Pairing approved for channel '${ch}'`);
|
|
1081
|
+
}
|
|
1082
|
+
})
|
|
1083
|
+
);
|
|
1084
|
+
pairing.command("revoke <bot-id> <channel>").description("Revoke a user's pairing").requiredOption("--user-id <userId>", "User ID to revoke").action(
|
|
1085
|
+
withErrorHandler(async (botId, ch, cmdOpts) => {
|
|
1086
|
+
const { client, cfg } = getClient6(pairing);
|
|
1087
|
+
const result = await client.revokePairing(botId, ch, { user_id: cmdOpts.userId });
|
|
1088
|
+
if (cfg.json) {
|
|
1089
|
+
printJson(result);
|
|
1090
|
+
} else {
|
|
1091
|
+
printSuccess(`Pairing revoked for user '${cmdOpts.userId}' on channel '${ch}'`);
|
|
1092
|
+
}
|
|
1093
|
+
})
|
|
1094
|
+
);
|
|
1095
|
+
pairing.command("users <bot-id> <channel>").description("List paired users").action(
|
|
1096
|
+
withErrorHandler(async (botId, ch) => {
|
|
1097
|
+
const { client, cfg } = getClient6(pairing);
|
|
1098
|
+
const result = await client.listPairedUsers(botId, ch);
|
|
1099
|
+
if (cfg.json) {
|
|
1100
|
+
printJson(result);
|
|
1101
|
+
} else {
|
|
1102
|
+
printTable(["User ID"], (result.users || []).map((u) => [u]));
|
|
1103
|
+
}
|
|
1104
|
+
})
|
|
1105
|
+
);
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
// src/commands/channel/index.ts
|
|
1109
|
+
function registerChannelCommand(program2) {
|
|
1110
|
+
const channel = program2.command("channel").description("Manage IM channels");
|
|
1111
|
+
registerChannelCrudCommands(channel);
|
|
1112
|
+
registerPairingCommands(channel);
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
// src/commands/device/crud.ts
|
|
1116
|
+
import chalk9 from "chalk";
|
|
1117
|
+
function getClient7(device) {
|
|
1118
|
+
const opts = device.parent.opts();
|
|
1119
|
+
const cfg = resolveConfig(opts);
|
|
1120
|
+
requireOption(cfg.appToken, "app-token");
|
|
1121
|
+
return { client: new FastClawClient({ url: cfg.url, appToken: cfg.appToken }), cfg };
|
|
1122
|
+
}
|
|
1123
|
+
function registerDeviceCrudCommands(device) {
|
|
1124
|
+
device.command("list <bot-id>").description("List devices").option("--status <status>", "Filter by status (pending/paired)").option("--client-mode <mode>", "Filter by client mode (web/cli/desktop)").action(
|
|
1125
|
+
withErrorHandler(async (botId, cmdOpts) => {
|
|
1126
|
+
const { client, cfg } = getClient7(device);
|
|
1127
|
+
const result = await client.listDevices(botId, {
|
|
1128
|
+
status: cmdOpts.status,
|
|
1129
|
+
client_mode: cmdOpts.clientMode
|
|
1130
|
+
});
|
|
1131
|
+
if (cfg.json) {
|
|
1132
|
+
printJson(result);
|
|
1133
|
+
} else if (!result.devices || result.devices.length === 0) {
|
|
1134
|
+
console.log(chalk9.dim("No devices found."));
|
|
1135
|
+
} else {
|
|
1136
|
+
printTable(
|
|
1137
|
+
["Request ID", "Device ID", "Platform", "Mode", "Status", "Connected", "Age"],
|
|
1138
|
+
result.devices.map((d) => [
|
|
1139
|
+
d.request_id,
|
|
1140
|
+
d.device_id,
|
|
1141
|
+
d.platform,
|
|
1142
|
+
d.client_mode,
|
|
1143
|
+
d.status,
|
|
1144
|
+
d.connected ? chalk9.green("yes") : "no",
|
|
1145
|
+
d.age
|
|
1146
|
+
])
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1149
|
+
})
|
|
1150
|
+
);
|
|
1151
|
+
device.command("approve <bot-id> <request-id>").description("Approve a device pairing request").action(
|
|
1152
|
+
withErrorHandler(async (botId, requestId) => {
|
|
1153
|
+
const { client, cfg } = getClient7(device);
|
|
1154
|
+
const result = await client.approveDevice(botId, requestId);
|
|
1155
|
+
if (cfg.json) {
|
|
1156
|
+
printJson(result);
|
|
1157
|
+
} else {
|
|
1158
|
+
printSuccess(`Device ${requestId} approved`);
|
|
1159
|
+
}
|
|
1160
|
+
})
|
|
1161
|
+
);
|
|
1162
|
+
device.command("revoke <bot-id> <device-id>").description("Revoke a paired device").option("--role <role>", "Role to revoke", "operator").action(
|
|
1163
|
+
withErrorHandler(async (botId, deviceId, cmdOpts) => {
|
|
1164
|
+
const { client, cfg } = getClient7(device);
|
|
1165
|
+
const result = await client.revokeDevice(botId, deviceId, cmdOpts.role);
|
|
1166
|
+
if (cfg.json) {
|
|
1167
|
+
printJson(result);
|
|
1168
|
+
} else {
|
|
1169
|
+
printSuccess(`Device ${deviceId} revoked`);
|
|
1170
|
+
}
|
|
1171
|
+
})
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
// src/commands/device/index.ts
|
|
1176
|
+
function registerDeviceCommand(program2) {
|
|
1177
|
+
const device = program2.command("device").description("Manage device pairing");
|
|
1178
|
+
registerDeviceCrudCommands(device);
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
// src/commands/skill/crud.ts
|
|
1182
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
1183
|
+
import chalk10 from "chalk";
|
|
1184
|
+
function getClient8(skill) {
|
|
1185
|
+
const opts = skill.parent.opts();
|
|
1186
|
+
const cfg = resolveConfig(opts);
|
|
1187
|
+
requireOption(cfg.appToken, "app-token");
|
|
1188
|
+
return { client: new FastClawClient({ url: cfg.url, appToken: cfg.appToken }), cfg };
|
|
1189
|
+
}
|
|
1190
|
+
function registerSkillCrudCommands(skill) {
|
|
1191
|
+
skill.command("list <bot-id>").description("List skills").action(
|
|
1192
|
+
withErrorHandler(async (botId) => {
|
|
1193
|
+
const { client, cfg } = getClient8(skill);
|
|
1194
|
+
const list = await client.listSkills(botId);
|
|
1195
|
+
if (cfg.json) {
|
|
1196
|
+
printJson(list);
|
|
1197
|
+
} else if (list.length === 0) {
|
|
1198
|
+
console.log(chalk10.dim("No skills configured."));
|
|
1199
|
+
} else {
|
|
1200
|
+
printTable(["Name"], list.map((s) => [s.name]));
|
|
1201
|
+
}
|
|
1202
|
+
})
|
|
1203
|
+
);
|
|
1204
|
+
skill.command("set <bot-id> <name>").description("Create or update a skill").option("-c, --content <content>", "Skill content (markdown)").option("-f, --file <path>", "Read content from file").action(
|
|
1205
|
+
withErrorHandler(async (botId, name, cmdOpts) => {
|
|
1206
|
+
const { client, cfg } = getClient8(skill);
|
|
1207
|
+
let content;
|
|
1208
|
+
if (cmdOpts.file) {
|
|
1209
|
+
content = readFileSync2(cmdOpts.file, "utf-8");
|
|
1210
|
+
} else if (cmdOpts.content) {
|
|
1211
|
+
content = cmdOpts.content;
|
|
1212
|
+
} else {
|
|
1213
|
+
throw new Error("Either --content or --file is required");
|
|
1214
|
+
}
|
|
1215
|
+
const result = await client.setSkill(botId, name, { content });
|
|
1216
|
+
if (cfg.json) {
|
|
1217
|
+
printJson(result);
|
|
1218
|
+
} else {
|
|
1219
|
+
printSuccess(`Skill '${name}' set on bot ${botId}`);
|
|
1220
|
+
}
|
|
1221
|
+
})
|
|
1222
|
+
);
|
|
1223
|
+
skill.command("delete <bot-id> <name>").description("Delete a skill").action(
|
|
1224
|
+
withErrorHandler(async (botId, name) => {
|
|
1225
|
+
const { client, cfg } = getClient8(skill);
|
|
1226
|
+
const result = await client.deleteSkill(botId, name);
|
|
1227
|
+
if (cfg.json) {
|
|
1228
|
+
printJson(result);
|
|
1229
|
+
} else {
|
|
1230
|
+
printSuccess(`Skill '${name}' deleted from bot ${botId}`);
|
|
1231
|
+
}
|
|
1232
|
+
})
|
|
1233
|
+
);
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
// src/commands/skill/index.ts
|
|
1237
|
+
function registerSkillCommand(program2) {
|
|
1238
|
+
const skill = program2.command("skill").description("Manage bot skills");
|
|
1239
|
+
registerSkillCrudCommands(skill);
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
// src/commands/setup.ts
|
|
1243
|
+
import chalk11 from "chalk";
|
|
1244
|
+
|
|
1245
|
+
// src/utils/validators.ts
|
|
1246
|
+
function slugify(name) {
|
|
1247
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 50);
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
// src/commands/setup.ts
|
|
1251
|
+
function registerSetupCommand(program2) {
|
|
1252
|
+
program2.command("setup").description("Interactive setup wizard \u2014 create and start a bot in one step").action(
|
|
1253
|
+
withErrorHandler(async () => {
|
|
1254
|
+
console.log("");
|
|
1255
|
+
console.log(chalk11.bold.cyan(" FastClaw CLI Setup"));
|
|
1256
|
+
console.log(chalk11.dim(" \u2500".repeat(20)));
|
|
1257
|
+
console.log("");
|
|
1258
|
+
const existing = loadConfig();
|
|
1259
|
+
const url = await promptText("FastClaw Server URL:", existing.url || "https://claw.yesy.dev");
|
|
1260
|
+
const adminToken = await promptPassword("Admin Token:");
|
|
1261
|
+
const adminClient = new FastClawClient({ url, adminToken });
|
|
1262
|
+
const spinner1 = startSpinner("Connecting to server...");
|
|
1263
|
+
const healthy = await adminClient.health();
|
|
1264
|
+
if (!healthy) {
|
|
1265
|
+
spinner1.fail("Cannot connect to server");
|
|
1266
|
+
throw new Error(`Failed to connect to ${url}`);
|
|
1267
|
+
}
|
|
1268
|
+
spinner1.succeed("Connected to " + url);
|
|
1269
|
+
updateConfig({ url, admin_token: adminToken });
|
|
1270
|
+
let appToken;
|
|
1271
|
+
const appAction = await promptSelect("App:", [
|
|
1272
|
+
{ name: "Create new app", value: "create" },
|
|
1273
|
+
{ name: "Use existing app token", value: "existing" }
|
|
1274
|
+
]);
|
|
1275
|
+
if (appAction === "create") {
|
|
1276
|
+
const appName = await promptText("App Name:", "default");
|
|
1277
|
+
const app = await adminClient.createApp({ name: appName });
|
|
1278
|
+
appToken = app.api_token;
|
|
1279
|
+
printSuccess(`App created: ${app.id}`);
|
|
1280
|
+
console.log(chalk11.dim(` Token: ${appToken}`));
|
|
1281
|
+
} else {
|
|
1282
|
+
appToken = await promptPassword("App Token:");
|
|
1283
|
+
}
|
|
1284
|
+
updateConfig({ app_token: appToken });
|
|
1285
|
+
const client = new FastClawClient({ url, appToken });
|
|
1286
|
+
const botName = await promptText("Bot Name:");
|
|
1287
|
+
const userId = await promptText("User ID:", existing.default_user_id || "default");
|
|
1288
|
+
const defaultSlug = slugify(botName);
|
|
1289
|
+
const slug = await promptText("Bot Slug:", defaultSlug);
|
|
1290
|
+
const bot = await client.createBot({
|
|
1291
|
+
user_id: userId,
|
|
1292
|
+
name: botName,
|
|
1293
|
+
slug
|
|
1294
|
+
});
|
|
1295
|
+
printSuccess(`Bot created: ${bot.id}`);
|
|
1296
|
+
updateConfig({ default_user_id: userId, default_bot_id: bot.id });
|
|
1297
|
+
const providerChoice = await promptSelect("Model Provider:", [
|
|
1298
|
+
{ name: "Google (Gemini)", value: "google" },
|
|
1299
|
+
{ name: "Anthropic (Claude)", value: "anthropic" },
|
|
1300
|
+
{ name: "OpenAI (GPT)", value: "openai" },
|
|
1301
|
+
{ name: "MiniMax", value: "minimax" },
|
|
1302
|
+
{ name: "Custom", value: "custom" },
|
|
1303
|
+
{ name: "Skip (configure later)", value: "skip" }
|
|
1304
|
+
]);
|
|
1305
|
+
if (providerChoice !== "skip") {
|
|
1306
|
+
let providerConfig;
|
|
1307
|
+
if (providerChoice === "custom") {
|
|
1308
|
+
const pName = await promptText("Provider name:");
|
|
1309
|
+
const baseUrl = await promptText("Base URL:");
|
|
1310
|
+
const apiKey = await promptPassword("API Key:");
|
|
1311
|
+
const modelId = await promptText("Model ID:");
|
|
1312
|
+
const modelName = await promptText("Model display name:", modelId);
|
|
1313
|
+
providerConfig = {
|
|
1314
|
+
name: pName,
|
|
1315
|
+
baseUrl,
|
|
1316
|
+
apiKey,
|
|
1317
|
+
models: [{ id: modelId, name: modelName, input: ["text"] }]
|
|
1318
|
+
};
|
|
1319
|
+
} else {
|
|
1320
|
+
const preset = MODEL_PRESETS[providerChoice];
|
|
1321
|
+
const apiKey = await promptPassword(`${providerChoice} API Key:`);
|
|
1322
|
+
providerConfig = { ...preset, apiKey };
|
|
1323
|
+
}
|
|
1324
|
+
await client.addProvider(bot.id, providerConfig);
|
|
1325
|
+
printSuccess("Model provider configured");
|
|
1326
|
+
const defaultModel = providerChoice !== "custom" ? PROVIDER_DEFAULTS[providerChoice] : `${providerConfig.name}/${providerConfig.models[0].id}`;
|
|
1327
|
+
await client.setDefaults(bot.id, { primary_model: defaultModel });
|
|
1328
|
+
printSuccess(`Default model: ${defaultModel}`);
|
|
1329
|
+
}
|
|
1330
|
+
const spinnerStart = startSpinner("Starting bot...");
|
|
1331
|
+
await client.startBot(bot.id);
|
|
1332
|
+
spinnerStart.text = "Waiting for bot to be ready...";
|
|
1333
|
+
try {
|
|
1334
|
+
await client.waitForReady(bot.id, 12e4, 3e3);
|
|
1335
|
+
spinnerStart.succeed("Bot is ready");
|
|
1336
|
+
} catch {
|
|
1337
|
+
spinnerStart.warn("Bot started but not yet ready \u2014 it may need more time");
|
|
1338
|
+
}
|
|
1339
|
+
const addChannel = await promptSelect("Add a channel?", [
|
|
1340
|
+
{ name: "Skip", value: "skip" },
|
|
1341
|
+
{ name: "Feishu (\u98DE\u4E66)", value: "feishu" },
|
|
1342
|
+
{ name: "Telegram", value: "telegram" },
|
|
1343
|
+
{ name: "Slack", value: "slack" },
|
|
1344
|
+
{ name: "Discord", value: "discord" }
|
|
1345
|
+
]);
|
|
1346
|
+
if (addChannel !== "skip") {
|
|
1347
|
+
const channelReq = {
|
|
1348
|
+
channel: addChannel,
|
|
1349
|
+
account: "default"
|
|
1350
|
+
};
|
|
1351
|
+
const fields = CHANNEL_FIELDS[addChannel];
|
|
1352
|
+
if (fields) {
|
|
1353
|
+
for (const field of fields.required) {
|
|
1354
|
+
const isSecret = field.toLowerCase().includes("secret") || field.toLowerCase().includes("token") || field.toLowerCase().includes("password");
|
|
1355
|
+
channelReq[field] = isSecret ? await promptPassword(`${field}:`) : await promptText(`${field}:`);
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
const dmPolicy = await promptSelect("DM Policy:", [
|
|
1359
|
+
{ name: "Open (anyone can message)", value: "open" },
|
|
1360
|
+
{ name: "Pairing (require pairing code)", value: "pairing" },
|
|
1361
|
+
{ name: "Disabled", value: "disabled" }
|
|
1362
|
+
]);
|
|
1363
|
+
channelReq.dmPolicy = dmPolicy;
|
|
1364
|
+
try {
|
|
1365
|
+
await client.addChannel(bot.id, channelReq);
|
|
1366
|
+
printSuccess(`Channel '${addChannel}' added`);
|
|
1367
|
+
} catch (err) {
|
|
1368
|
+
console.log(chalk11.yellow(` ! Channel add failed: ${err instanceof Error ? err.message : err}`));
|
|
1369
|
+
console.log(chalk11.dim(` You can add it later: fastclaw channel add ${bot.id}`));
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
let connectInfo;
|
|
1373
|
+
try {
|
|
1374
|
+
connectInfo = await client.getBotConnect(bot.id);
|
|
1375
|
+
} catch {
|
|
1376
|
+
}
|
|
1377
|
+
const lines = [
|
|
1378
|
+
`${chalk11.dim("Bot ID:")} ${bot.id}`,
|
|
1379
|
+
`${chalk11.dim("Bot Name:")} ${bot.name}`,
|
|
1380
|
+
`${chalk11.dim("Slug:")} ${bot.slug}`,
|
|
1381
|
+
`${chalk11.dim("Token:")} ${bot.access_token}`
|
|
1382
|
+
];
|
|
1383
|
+
if (connectInfo) {
|
|
1384
|
+
lines.push(
|
|
1385
|
+
`${chalk11.dim("Webchat:")} ${connectInfo.webchat_url}`,
|
|
1386
|
+
`${chalk11.dim("WebSocket:")} ${connectInfo.ws_url}`
|
|
1387
|
+
);
|
|
1388
|
+
if (bot.access_url) {
|
|
1389
|
+
lines.push(`${chalk11.dim("Quick URL:")} ${bot.access_url}`);
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
printBox(lines, "Setup Complete");
|
|
1393
|
+
console.log(chalk11.dim("\nUseful commands:"));
|
|
1394
|
+
console.log(chalk11.dim(` fastclaw bot status ${bot.id}`));
|
|
1395
|
+
console.log(chalk11.dim(` fastclaw bot connect ${bot.id}`));
|
|
1396
|
+
console.log(chalk11.dim(` fastclaw model list ${bot.id}`));
|
|
1397
|
+
console.log(chalk11.dim(` fastclaw channel add ${bot.id}`));
|
|
1398
|
+
console.log("");
|
|
1399
|
+
})
|
|
1400
|
+
);
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
// src/index.ts
|
|
1404
|
+
var program = new Command().name("fastclaw").description("CLI for managing FastClaw bots").version("0.1.0").option("--url <url>", "FastClaw server URL").option("--admin-token <token>", "Admin API token").option("--app-token <token>", "App API token").option("--json", "Output as JSON");
|
|
1405
|
+
registerSetupCommand(program);
|
|
1406
|
+
registerConfigCommand(program);
|
|
1407
|
+
registerAdminCommand(program);
|
|
1408
|
+
registerBotCommand(program);
|
|
1409
|
+
registerModelCommand(program);
|
|
1410
|
+
registerChannelCommand(program);
|
|
1411
|
+
registerDeviceCommand(program);
|
|
1412
|
+
registerSkillCommand(program);
|
|
1413
|
+
program.parse();
|