@agentskit/cli 0.5.3 → 0.7.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 +6 -1
- package/dist/bin.cjs +1746 -441
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-CCPJYGHP.js → chunk-72XFU2X2.js} +1605 -292
- package/dist/chunk-72XFU2X2.js.map +1 -0
- package/dist/index.cjs +1781 -441
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +480 -7
- package/dist/index.d.ts +480 -7
- package/dist/index.js +1 -1
- package/package.json +9 -8
- package/dist/chunk-CCPJYGHP.js.map +0 -1
package/dist/bin.cjs
CHANGED
|
@@ -1,42 +1,46 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
var React3 = require('react');
|
|
5
|
-
var ink = require('ink');
|
|
6
4
|
var commander = require('commander');
|
|
7
|
-
var
|
|
5
|
+
var React2 = require('react');
|
|
6
|
+
var ink = require('ink');
|
|
8
7
|
var promises = require('fs/promises');
|
|
8
|
+
var os = require('os');
|
|
9
|
+
var path = require('path');
|
|
9
10
|
var ink$1 = require('@agentskit/ink');
|
|
10
11
|
var adapters = require('@agentskit/adapters');
|
|
12
|
+
var crypto = require('crypto');
|
|
13
|
+
var fs = require('fs');
|
|
11
14
|
var tools = require('@agentskit/tools');
|
|
12
15
|
var skills = require('@agentskit/skills');
|
|
13
16
|
var memory = require('@agentskit/memory');
|
|
17
|
+
var child_process = require('child_process');
|
|
14
18
|
var jsxRuntime = require('react/jsx-runtime');
|
|
19
|
+
var url = require('url');
|
|
20
|
+
var runtime = require('@agentskit/runtime');
|
|
15
21
|
var prompts = require('@inquirer/prompts');
|
|
16
22
|
var kleur = require('kleur');
|
|
17
|
-
var fs = require('fs');
|
|
18
|
-
var runtime = require('@agentskit/runtime');
|
|
19
|
-
var child_process = require('child_process');
|
|
20
23
|
var chokidar = require('chokidar');
|
|
24
|
+
var rag = require('@agentskit/rag');
|
|
21
25
|
|
|
22
26
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
23
27
|
|
|
24
|
-
var
|
|
28
|
+
var React2__default = /*#__PURE__*/_interopDefault(React2);
|
|
25
29
|
var path__default = /*#__PURE__*/_interopDefault(path);
|
|
26
30
|
var kleur__default = /*#__PURE__*/_interopDefault(kleur);
|
|
27
31
|
var chokidar__default = /*#__PURE__*/_interopDefault(chokidar);
|
|
28
32
|
|
|
29
|
-
async function loadJsonConfig(
|
|
33
|
+
async function loadJsonConfig(path5) {
|
|
30
34
|
try {
|
|
31
|
-
const raw = await promises.readFile(
|
|
35
|
+
const raw = await promises.readFile(path5, "utf8");
|
|
32
36
|
return JSON.parse(raw);
|
|
33
37
|
} catch {
|
|
34
38
|
return void 0;
|
|
35
39
|
}
|
|
36
40
|
}
|
|
37
|
-
async function loadTsConfig(
|
|
41
|
+
async function loadTsConfig(path5) {
|
|
38
42
|
try {
|
|
39
|
-
const mod = await import(
|
|
43
|
+
const mod = await import(path5);
|
|
40
44
|
return mod.default ?? mod;
|
|
41
45
|
} catch {
|
|
42
46
|
return void 0;
|
|
@@ -54,16 +58,38 @@ async function loadPackageJsonConfig(dir) {
|
|
|
54
58
|
return void 0;
|
|
55
59
|
}
|
|
56
60
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
+
function mergeConfigs(base, override) {
|
|
62
|
+
if (!base && !override) return void 0;
|
|
63
|
+
if (!base) return override;
|
|
64
|
+
if (!override) return base;
|
|
65
|
+
return {
|
|
66
|
+
...base,
|
|
67
|
+
...override,
|
|
68
|
+
tools: { ...base.tools, ...override.tools },
|
|
69
|
+
defaults: { ...base.defaults, ...override.defaults },
|
|
70
|
+
runtime: { ...base.runtime, ...override.runtime },
|
|
71
|
+
observability: { ...base.observability, ...override.observability }
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
async function loadLocalConfig(cwd) {
|
|
75
|
+
const tsConfig = await loadTsConfig(path.join(cwd, ".agentskit.config.ts"));
|
|
61
76
|
if (tsConfig) return tsConfig;
|
|
62
|
-
const
|
|
63
|
-
const jsonConfig = await loadJsonConfig(jsonPath);
|
|
77
|
+
const jsonConfig = await loadJsonConfig(path.join(cwd, ".agentskit.config.json"));
|
|
64
78
|
if (jsonConfig) return jsonConfig;
|
|
65
79
|
return await loadPackageJsonConfig(cwd);
|
|
66
80
|
}
|
|
81
|
+
async function loadGlobalConfig(home) {
|
|
82
|
+
const globalDir = path.join(os.homedir(), ".agentskit");
|
|
83
|
+
const tsConfig = await loadTsConfig(path.join(globalDir, "config.ts"));
|
|
84
|
+
if (tsConfig) return tsConfig;
|
|
85
|
+
return await loadJsonConfig(path.join(globalDir, "config.json"));
|
|
86
|
+
}
|
|
87
|
+
async function loadConfig(options) {
|
|
88
|
+
const cwd = path.resolve(process.cwd());
|
|
89
|
+
const global = await loadGlobalConfig();
|
|
90
|
+
const local = await loadLocalConfig(cwd);
|
|
91
|
+
return mergeConfigs(global, local);
|
|
92
|
+
}
|
|
67
93
|
var providers = {
|
|
68
94
|
openai: {
|
|
69
95
|
label: "OpenAI",
|
|
@@ -123,7 +149,7 @@ function createDemoAdapter(provider, model) {
|
|
|
123
149
|
].join(" ");
|
|
124
150
|
for (const chunk of reply.match(/.{1,18}/g) ?? []) {
|
|
125
151
|
if (cancelled) return;
|
|
126
|
-
await new Promise((
|
|
152
|
+
await new Promise((resolve4) => setTimeout(resolve4, 35));
|
|
127
153
|
yield { type: "text", content: chunk };
|
|
128
154
|
}
|
|
129
155
|
yield { type: "done" };
|
|
@@ -174,159 +200,1461 @@ function resolveChatProvider(options) {
|
|
|
174
200
|
summary: `${entry.label} live adapter`
|
|
175
201
|
};
|
|
176
202
|
}
|
|
177
|
-
var
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
203
|
+
var ROOT = path.join(os.homedir(), ".agentskit", "sessions");
|
|
204
|
+
var META_SUFFIX = ".meta.json";
|
|
205
|
+
function cwdHash(cwd = process.cwd()) {
|
|
206
|
+
return crypto.createHash("sha256").update(cwd).digest("hex").slice(0, 12);
|
|
207
|
+
}
|
|
208
|
+
function dirFor(cwd = process.cwd()) {
|
|
209
|
+
return path.join(ROOT, cwdHash(cwd));
|
|
210
|
+
}
|
|
211
|
+
function ensureDir(dir) {
|
|
212
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
213
|
+
}
|
|
214
|
+
function generateSessionId() {
|
|
215
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
216
|
+
const suffix = crypto.randomBytes(3).toString("hex");
|
|
217
|
+
return `${ts}-${suffix}`;
|
|
218
|
+
}
|
|
219
|
+
function sessionFilePath(id, cwd = process.cwd()) {
|
|
220
|
+
ensureDir(dirFor(cwd));
|
|
221
|
+
return path.join(dirFor(cwd), `${id}.json`);
|
|
222
|
+
}
|
|
223
|
+
function metaPath(id, cwd = process.cwd()) {
|
|
224
|
+
return path.join(dirFor(cwd), `${id}${META_SUFFIX}`);
|
|
225
|
+
}
|
|
226
|
+
function readMeta(id, cwd = process.cwd()) {
|
|
227
|
+
const path5 = metaPath(id, cwd);
|
|
228
|
+
if (!fs.existsSync(path5)) return null;
|
|
229
|
+
try {
|
|
230
|
+
return JSON.parse(fs.readFileSync(path5, "utf8"));
|
|
231
|
+
} catch {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function writeSessionMeta(meta, cwd = process.cwd()) {
|
|
236
|
+
ensureDir(dirFor(cwd));
|
|
237
|
+
fs.writeFileSync(metaPath(meta.id, cwd), JSON.stringify(meta, null, 2));
|
|
238
|
+
}
|
|
239
|
+
function derivePreview(messages) {
|
|
240
|
+
const firstUser = messages.find((m) => m.role === "user" && m.content.trim());
|
|
241
|
+
if (!firstUser) return "(empty)";
|
|
242
|
+
const single = firstUser.content.replace(/\s+/g, " ").trim();
|
|
243
|
+
return single.length > 80 ? `${single.slice(0, 80)}\u2026` : single;
|
|
244
|
+
}
|
|
245
|
+
function listSessions(cwd = process.cwd()) {
|
|
246
|
+
const dir = dirFor(cwd);
|
|
247
|
+
if (!fs.existsSync(dir)) return [];
|
|
248
|
+
const entries = fs.readdirSync(dir);
|
|
249
|
+
const records = [];
|
|
250
|
+
for (const entry of entries) {
|
|
251
|
+
if (!entry.endsWith(".json") || entry.endsWith(META_SUFFIX)) continue;
|
|
252
|
+
const id = entry.replace(/\.json$/, "");
|
|
253
|
+
const meta = readMeta(id, cwd);
|
|
254
|
+
const file = path.join(dir, entry);
|
|
255
|
+
if (meta) {
|
|
256
|
+
records.push({ metadata: meta, file });
|
|
257
|
+
} else {
|
|
258
|
+
const stats = fs.statSync(file);
|
|
259
|
+
records.push({
|
|
260
|
+
metadata: {
|
|
261
|
+
id,
|
|
262
|
+
cwd,
|
|
263
|
+
createdAt: stats.birthtime.toISOString(),
|
|
264
|
+
updatedAt: stats.mtime.toISOString(),
|
|
265
|
+
messageCount: 0,
|
|
266
|
+
preview: "(legacy session)"
|
|
267
|
+
},
|
|
268
|
+
file
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
records.sort((a, b) => b.metadata.updatedAt.localeCompare(a.metadata.updatedAt));
|
|
273
|
+
return records;
|
|
274
|
+
}
|
|
275
|
+
function findLatestSession(cwd = process.cwd()) {
|
|
276
|
+
const all = listSessions(cwd);
|
|
277
|
+
return all[0] ?? null;
|
|
278
|
+
}
|
|
279
|
+
function findSession(id, cwd = process.cwd()) {
|
|
280
|
+
const all = listSessions(cwd);
|
|
281
|
+
const exact = all.find((s) => s.metadata.id === id || s.metadata.label === id);
|
|
282
|
+
if (exact) return exact;
|
|
283
|
+
const prefix = all.find((s) => s.metadata.id.startsWith(id));
|
|
284
|
+
return prefix ?? null;
|
|
285
|
+
}
|
|
286
|
+
function renameSession(id, label, cwd = process.cwd()) {
|
|
287
|
+
const record = findSession(id, cwd);
|
|
288
|
+
if (!record) throw new Error(`No session matching "${id}".`);
|
|
289
|
+
const next = { ...record.metadata, label, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
290
|
+
writeSessionMeta(next, cwd);
|
|
291
|
+
return next;
|
|
292
|
+
}
|
|
293
|
+
function forkSession(id, cwd = process.cwd()) {
|
|
294
|
+
const record = findSession(id, cwd);
|
|
295
|
+
if (!record) throw new Error(`No session matching "${id}".`);
|
|
296
|
+
const newId = generateSessionId();
|
|
297
|
+
const newFile = sessionFilePath(newId, cwd);
|
|
298
|
+
if (fs.existsSync(record.file)) {
|
|
299
|
+
fs.writeFileSync(newFile, fs.readFileSync(record.file, "utf8"));
|
|
300
|
+
}
|
|
301
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
302
|
+
writeSessionMeta(
|
|
303
|
+
{
|
|
304
|
+
...record.metadata,
|
|
305
|
+
id: newId,
|
|
306
|
+
createdAt: now,
|
|
307
|
+
updatedAt: now,
|
|
308
|
+
forkedFrom: record.metadata.id,
|
|
309
|
+
label: void 0
|
|
310
|
+
},
|
|
311
|
+
cwd
|
|
312
|
+
);
|
|
313
|
+
return { id: newId, file: newFile, isNew: true };
|
|
314
|
+
}
|
|
315
|
+
function resolveSession(input2) {
|
|
316
|
+
const cwd = input2.cwd ?? process.cwd();
|
|
317
|
+
if (input2.explicitPath) {
|
|
318
|
+
return { id: "custom", file: input2.explicitPath, isNew: !fs.existsSync(input2.explicitPath) };
|
|
319
|
+
}
|
|
320
|
+
if (input2.forceNew) {
|
|
321
|
+
const id2 = generateSessionId();
|
|
322
|
+
return { id: id2, file: sessionFilePath(id2, cwd), isNew: true };
|
|
323
|
+
}
|
|
324
|
+
if (input2.resumeId) {
|
|
325
|
+
const target = input2.resumeId === true ? findLatestSession(cwd) : findSession(input2.resumeId, cwd);
|
|
326
|
+
if (target) {
|
|
327
|
+
return { id: target.metadata.id, file: target.file, isNew: false };
|
|
328
|
+
}
|
|
329
|
+
process.stderr.write(
|
|
330
|
+
`No session matching "${String(input2.resumeId)}" \u2014 starting a new one.
|
|
331
|
+
`
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
const latest = findLatestSession(cwd);
|
|
335
|
+
if (latest) return { id: latest.metadata.id, file: latest.file, isNew: false };
|
|
336
|
+
const id = generateSessionId();
|
|
337
|
+
return { id, file: sessionFilePath(id, cwd), isNew: true };
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// src/extensibility/telemetry/pricing.ts
|
|
341
|
+
var builtinPricing = {
|
|
342
|
+
"gpt-4o": { inputPerM: 2.5, outputPerM: 10 },
|
|
343
|
+
"gpt-4o-mini": { inputPerM: 0.15, outputPerM: 0.6 },
|
|
344
|
+
"gpt-4.1": { inputPerM: 2, outputPerM: 8 },
|
|
345
|
+
"gpt-4.1-mini": { inputPerM: 0.4, outputPerM: 1.6 },
|
|
346
|
+
"claude-opus-4": { inputPerM: 15, outputPerM: 75 },
|
|
347
|
+
"claude-sonnet-4": { inputPerM: 3, outputPerM: 15 },
|
|
348
|
+
"claude-haiku-4": { inputPerM: 0.8, outputPerM: 4 },
|
|
349
|
+
"gemini-2.5-pro": { inputPerM: 1.25, outputPerM: 10 },
|
|
350
|
+
"gemini-2.5-flash": { inputPerM: 0.3, outputPerM: 2.5 }
|
|
351
|
+
};
|
|
352
|
+
var customPricing = {};
|
|
353
|
+
function getPricing(model) {
|
|
354
|
+
if (!model) return void 0;
|
|
355
|
+
if (customPricing[model]) return customPricing[model];
|
|
356
|
+
if (builtinPricing[model]) return builtinPricing[model];
|
|
357
|
+
const short = model.includes("/") ? model.split("/").pop() : model;
|
|
358
|
+
return customPricing[short] ?? builtinPricing[short];
|
|
359
|
+
}
|
|
360
|
+
function computeCost(model, usage) {
|
|
361
|
+
if (!model) return void 0;
|
|
362
|
+
const pricing = getPricing(model);
|
|
363
|
+
if (!pricing) return void 0;
|
|
364
|
+
const inputUsd = usage.promptTokens / 1e6 * pricing.inputPerM;
|
|
365
|
+
const outputUsd = usage.completionTokens / 1e6 * pricing.outputPerM;
|
|
366
|
+
return {
|
|
367
|
+
model,
|
|
368
|
+
inputUsd,
|
|
369
|
+
outputUsd,
|
|
370
|
+
totalUsd: inputUsd + outputUsd
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// src/slash-commands.ts
|
|
375
|
+
function parseSlashCommand(input2) {
|
|
376
|
+
if (!input2.startsWith("/")) return null;
|
|
377
|
+
const match = input2.slice(1).match(/^(\S+)\s*([\s\S]*)$/);
|
|
378
|
+
if (!match) return null;
|
|
379
|
+
return { name: match[1], args: match[2] ?? "" };
|
|
380
|
+
}
|
|
381
|
+
function createSlashRegistry(commands) {
|
|
382
|
+
const map = /* @__PURE__ */ new Map();
|
|
383
|
+
for (const cmd of commands) {
|
|
384
|
+
map.set(cmd.name, cmd);
|
|
385
|
+
for (const alias of cmd.aliases ?? []) map.set(alias, cmd);
|
|
386
|
+
}
|
|
387
|
+
return map;
|
|
388
|
+
}
|
|
389
|
+
var builtinSlashCommands = [
|
|
390
|
+
{
|
|
391
|
+
name: "help",
|
|
392
|
+
aliases: ["?"],
|
|
393
|
+
description: "List available slash commands.",
|
|
394
|
+
run(ctx) {
|
|
395
|
+
const seen = /* @__PURE__ */ new Set();
|
|
396
|
+
const lines = [];
|
|
397
|
+
for (const cmd of ctx.commands) {
|
|
398
|
+
if (seen.has(cmd.name)) continue;
|
|
399
|
+
seen.add(cmd.name);
|
|
400
|
+
const suffix = cmd.usage ? ` (${cmd.usage})` : "";
|
|
401
|
+
lines.push(` /${cmd.name.padEnd(10)} ${cmd.description}${suffix}`);
|
|
402
|
+
}
|
|
403
|
+
ctx.feedback(`Slash commands:
|
|
404
|
+
${lines.join("\n")}`, "info");
|
|
405
|
+
}
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
name: "model",
|
|
409
|
+
description: "Switch the active model.",
|
|
410
|
+
usage: "/model <name>",
|
|
411
|
+
run(ctx, args) {
|
|
412
|
+
const value = args.trim();
|
|
413
|
+
if (!value) {
|
|
414
|
+
ctx.feedback(
|
|
415
|
+
`Current model: ${ctx.runtime.model ?? "unset"}. Usage: /model <name>`,
|
|
416
|
+
"warn"
|
|
417
|
+
);
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
ctx.setModel(value);
|
|
421
|
+
ctx.feedback(`Model \u2192 ${value}`, "success");
|
|
422
|
+
}
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
name: "provider",
|
|
426
|
+
description: "Switch the adapter provider.",
|
|
427
|
+
usage: "/provider openai|anthropic|gemini|ollama|deepseek|grok|kimi|demo",
|
|
428
|
+
run(ctx, args) {
|
|
429
|
+
const value = args.trim();
|
|
430
|
+
if (!value) {
|
|
431
|
+
ctx.feedback(
|
|
432
|
+
`Current provider: ${ctx.runtime.provider}. Usage: /provider <name>`,
|
|
433
|
+
"warn"
|
|
434
|
+
);
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
ctx.setProvider(value);
|
|
438
|
+
ctx.feedback(`Provider \u2192 ${value}`, "success");
|
|
439
|
+
}
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
name: "base-url",
|
|
443
|
+
aliases: ["baseurl"],
|
|
444
|
+
description: "Override provider base URL.",
|
|
445
|
+
usage: "/base-url <url|clear>",
|
|
446
|
+
run(ctx, args) {
|
|
447
|
+
const value = args.trim();
|
|
448
|
+
if (!value || value === "clear") {
|
|
449
|
+
ctx.setBaseUrl(void 0);
|
|
450
|
+
ctx.feedback("Base URL cleared.", "success");
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
ctx.setBaseUrl(value);
|
|
454
|
+
ctx.feedback(`Base URL \u2192 ${value}`, "success");
|
|
455
|
+
}
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
name: "tools",
|
|
459
|
+
description: "Set active tools (comma-separated) or clear them.",
|
|
460
|
+
usage: "/tools web_search,fetch_url | /tools clear",
|
|
461
|
+
run(ctx, args) {
|
|
462
|
+
const value = args.trim();
|
|
463
|
+
if (!value || value === "clear") {
|
|
464
|
+
ctx.setTools(void 0);
|
|
465
|
+
ctx.feedback("Tools reset to defaults.", "success");
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
ctx.setTools(value);
|
|
469
|
+
ctx.feedback(`Tools \u2192 ${value}`, "success");
|
|
470
|
+
}
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
name: "skill",
|
|
474
|
+
description: "Set active skill(s) (comma-separated) or clear them.",
|
|
475
|
+
usage: "/skill researcher,coder | /skill clear",
|
|
476
|
+
run(ctx, args) {
|
|
477
|
+
const value = args.trim();
|
|
478
|
+
if (!value || value === "clear") {
|
|
479
|
+
ctx.setSkill(void 0);
|
|
480
|
+
ctx.feedback("Skills cleared.", "success");
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
ctx.setSkill(value);
|
|
484
|
+
ctx.feedback(`Skills \u2192 ${value}`, "success");
|
|
485
|
+
}
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
name: "clear",
|
|
489
|
+
aliases: ["reset"],
|
|
490
|
+
description: "Clear the conversation history in this session.",
|
|
491
|
+
async run(ctx) {
|
|
492
|
+
await ctx.chat.clear();
|
|
493
|
+
ctx.feedback("History cleared.", "success");
|
|
494
|
+
}
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
name: "usage",
|
|
498
|
+
description: "Show the cumulative token usage for this session.",
|
|
499
|
+
run(ctx) {
|
|
500
|
+
const usage = ctx.chat.usage;
|
|
501
|
+
if (!usage || usage.totalTokens === 0) {
|
|
502
|
+
ctx.feedback("No usage reported yet for this session.", "info");
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
ctx.feedback(
|
|
506
|
+
`Tokens \u2014 prompt=${usage.promptTokens} completion=${usage.completionTokens} total=${usage.totalTokens}`,
|
|
507
|
+
"info"
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
name: "cost",
|
|
513
|
+
description: "Estimate the cost so far for the current model.",
|
|
514
|
+
run(ctx) {
|
|
515
|
+
const usage = ctx.chat.usage;
|
|
516
|
+
const model = ctx.runtime.model;
|
|
517
|
+
if (!usage || usage.totalTokens === 0) {
|
|
518
|
+
ctx.feedback("No usage reported yet for this session.", "info");
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
const cost = computeCost(model, usage);
|
|
522
|
+
if (!cost) {
|
|
523
|
+
ctx.feedback(
|
|
524
|
+
`No pricing registered for model "${model ?? "unset"}". Register with registerPricing() or provide a known model name.`,
|
|
525
|
+
"warn"
|
|
526
|
+
);
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
ctx.feedback(
|
|
530
|
+
`$${cost.totalUsd.toFixed(4)} total (in=$${cost.inputUsd.toFixed(4)} out=$${cost.outputUsd.toFixed(4)} model=${cost.model})`,
|
|
531
|
+
"info"
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
},
|
|
535
|
+
{
|
|
536
|
+
name: "rename",
|
|
537
|
+
description: "Attach a human-readable label to the current session.",
|
|
538
|
+
usage: "/rename <label>",
|
|
539
|
+
run(ctx, args) {
|
|
540
|
+
const label = args.trim();
|
|
541
|
+
const sessionId = ctx.runtime.sessionId;
|
|
542
|
+
if (!sessionId || sessionId === "custom") {
|
|
543
|
+
ctx.feedback("Rename is only available for managed sessions.", "warn");
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
if (!label) {
|
|
547
|
+
ctx.feedback("Usage: /rename <label>", "warn");
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
try {
|
|
551
|
+
renameSession(sessionId, label);
|
|
552
|
+
ctx.feedback(`Session labeled "${label}".`, "success");
|
|
553
|
+
} catch (err) {
|
|
554
|
+
ctx.feedback(`/rename failed: ${err instanceof Error ? err.message : String(err)}`, "error");
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
},
|
|
558
|
+
{
|
|
559
|
+
name: "fork",
|
|
560
|
+
description: "Branch a copy of the current session. Does not switch to it.",
|
|
561
|
+
run(ctx) {
|
|
562
|
+
const sessionId = ctx.runtime.sessionId;
|
|
563
|
+
if (!sessionId || sessionId === "custom") {
|
|
564
|
+
ctx.feedback("Fork is only available for managed sessions.", "warn");
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
try {
|
|
568
|
+
const result = forkSession(sessionId);
|
|
569
|
+
ctx.feedback(
|
|
570
|
+
`Forked into ${result.id}. Resume with:
|
|
571
|
+
agentskit chat --resume ${result.id}`,
|
|
572
|
+
"success"
|
|
573
|
+
);
|
|
574
|
+
} catch (err) {
|
|
575
|
+
ctx.feedback(`/fork failed: ${err instanceof Error ? err.message : String(err)}`, "error");
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
},
|
|
579
|
+
{
|
|
580
|
+
name: "exit",
|
|
581
|
+
aliases: ["quit", "q"],
|
|
582
|
+
description: "Exit the chat.",
|
|
583
|
+
run() {
|
|
584
|
+
process.exit(0);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
];
|
|
588
|
+
var skillRegistry = {
|
|
589
|
+
researcher: skills.researcher,
|
|
590
|
+
coder: skills.coder,
|
|
591
|
+
planner: skills.planner,
|
|
592
|
+
critic: skills.critic,
|
|
593
|
+
summarizer: skills.summarizer
|
|
594
|
+
};
|
|
595
|
+
function instantiate(kind) {
|
|
596
|
+
switch (kind) {
|
|
597
|
+
case "web_search":
|
|
598
|
+
return [tools.webSearch()];
|
|
599
|
+
case "fetch_url":
|
|
600
|
+
return [tools.fetchUrl()];
|
|
601
|
+
case "filesystem":
|
|
602
|
+
return tools.filesystem({ basePath: process.cwd() });
|
|
603
|
+
case "shell":
|
|
604
|
+
return [tools.shell({ timeout: 3e4 })];
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
function gateTool(tool) {
|
|
608
|
+
if (tool.requiresConfirmation === false) return tool;
|
|
609
|
+
return { ...tool, requiresConfirmation: true };
|
|
610
|
+
}
|
|
611
|
+
function resolveTools(toolNames) {
|
|
612
|
+
if (!toolNames) {
|
|
613
|
+
return [...instantiate("web_search"), ...instantiate("fetch_url")].map(gateTool);
|
|
614
|
+
}
|
|
615
|
+
const tools = [];
|
|
616
|
+
for (const name of toolNames.split(",").map((s) => s.trim()).filter(Boolean)) {
|
|
617
|
+
switch (name) {
|
|
618
|
+
case "web_search":
|
|
619
|
+
case "fetch_url":
|
|
620
|
+
case "filesystem":
|
|
621
|
+
case "shell":
|
|
622
|
+
tools.push(...instantiate(name));
|
|
623
|
+
break;
|
|
624
|
+
default:
|
|
625
|
+
process.stderr.write(`Unknown tool: ${name}
|
|
626
|
+
`);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
return tools;
|
|
630
|
+
}
|
|
631
|
+
function resolveSkill(skillName) {
|
|
632
|
+
if (!skillName) return void 0;
|
|
633
|
+
const skill = skillRegistry[skillName.trim()];
|
|
634
|
+
if (!skill) {
|
|
635
|
+
process.stderr.write(`Unknown skill: ${skillName}
|
|
636
|
+
`);
|
|
637
|
+
return void 0;
|
|
638
|
+
}
|
|
639
|
+
return skill;
|
|
640
|
+
}
|
|
641
|
+
function resolveSkills(skillNames) {
|
|
642
|
+
if (!skillNames) return void 0;
|
|
643
|
+
const names = skillNames.split(",").map((s) => s.trim());
|
|
644
|
+
const resolved = names.map((n) => skillRegistry[n]).filter(Boolean);
|
|
645
|
+
if (resolved.length === 0) {
|
|
646
|
+
process.stderr.write(`No valid skills found in: ${skillNames}
|
|
647
|
+
`);
|
|
648
|
+
return void 0;
|
|
649
|
+
}
|
|
650
|
+
if (resolved.length === 1) return resolved[0];
|
|
651
|
+
return skills.composeSkills(...resolved);
|
|
652
|
+
}
|
|
653
|
+
function resolveMemory(backend, memoryPath) {
|
|
654
|
+
switch (backend) {
|
|
655
|
+
case "sqlite":
|
|
656
|
+
return memory.sqliteChatMemory({ path: memoryPath.replace(/\.json$/, ".db") });
|
|
657
|
+
case "file":
|
|
658
|
+
default:
|
|
659
|
+
return memory.fileChatMemory(memoryPath);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// src/extensibility/permissions/policy.ts
|
|
664
|
+
function evaluatePolicy(policy, toolName) {
|
|
665
|
+
if (policy.mode === "bypassPermissions") return "allow";
|
|
666
|
+
if (policy.mode === "plan") return "ask";
|
|
667
|
+
for (const rule of policy.rules) {
|
|
668
|
+
if (matchesRule(rule, toolName)) return rule.action;
|
|
669
|
+
}
|
|
670
|
+
if (policy.mode === "acceptEdits" && /^(fs_write|edit|write_file)/.test(toolName)) {
|
|
671
|
+
return "allow";
|
|
672
|
+
}
|
|
673
|
+
return "ask";
|
|
674
|
+
}
|
|
675
|
+
function matchesRule(rule, toolName) {
|
|
676
|
+
if (rule.tool instanceof RegExp) return rule.tool.test(toolName);
|
|
677
|
+
const str = rule.tool;
|
|
678
|
+
if (str.startsWith("re:")) return new RegExp(str.slice(3)).test(toolName);
|
|
679
|
+
return str === toolName;
|
|
680
|
+
}
|
|
681
|
+
function applyPolicyToTool(policy, tool) {
|
|
682
|
+
const action = evaluatePolicy(policy, tool.name);
|
|
683
|
+
if (action === "deny") return null;
|
|
684
|
+
if (action === "allow") return { ...tool, requiresConfirmation: false };
|
|
685
|
+
return { ...tool, requiresConfirmation: true };
|
|
686
|
+
}
|
|
687
|
+
function applyPolicyToTools(policy, tools) {
|
|
688
|
+
const out = [];
|
|
689
|
+
for (const tool of tools) {
|
|
690
|
+
const gated = applyPolicyToTool(policy, tool);
|
|
691
|
+
if (gated) out.push(gated);
|
|
692
|
+
}
|
|
693
|
+
return out;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// src/runtime/use-runtime.ts
|
|
697
|
+
function useRuntime(options) {
|
|
698
|
+
const [provider, setProvider] = React2.useState(options.provider);
|
|
699
|
+
const [model, setModel] = React2.useState(options.model);
|
|
700
|
+
const [apiKey, setApiKey] = React2.useState(options.apiKey);
|
|
701
|
+
const [baseUrl, setBaseUrl] = React2.useState(options.baseUrl);
|
|
702
|
+
const [toolsFlag, setToolsFlag] = React2.useState(options.tools);
|
|
703
|
+
const [skillFlag, setSkillFlag] = React2.useState(options.skill);
|
|
704
|
+
const runtime = React2.useMemo(
|
|
705
|
+
() => resolveChatProvider({ provider, model, apiKey, baseUrl }),
|
|
706
|
+
[provider, model, apiKey, baseUrl]
|
|
707
|
+
);
|
|
708
|
+
const memory = React2.useMemo(
|
|
709
|
+
() => resolveMemory(options.memoryBackend, options.memoryPath ?? ".agentskit-history.json"),
|
|
710
|
+
[options.memoryPath, options.memoryBackend]
|
|
711
|
+
);
|
|
712
|
+
const tools = React2.useMemo(() => {
|
|
713
|
+
const resolved = resolveTools(toolsFlag);
|
|
714
|
+
if (!options.permissionPolicy) return resolved;
|
|
715
|
+
return applyPolicyToTools(options.permissionPolicy, resolved);
|
|
716
|
+
}, [toolsFlag, options.permissionPolicy]);
|
|
717
|
+
const skills = React2.useMemo(() => {
|
|
718
|
+
if (!skillFlag) return void 0;
|
|
719
|
+
const names = skillFlag.split(",").map((s) => s.trim());
|
|
720
|
+
const resolved = names.map((n) => skillRegistry[n]).filter(Boolean);
|
|
721
|
+
if (resolved.length === 0) return void 0;
|
|
722
|
+
return resolved;
|
|
723
|
+
}, [skillFlag]);
|
|
724
|
+
return {
|
|
725
|
+
runtime,
|
|
726
|
+
memory,
|
|
727
|
+
tools,
|
|
728
|
+
skills,
|
|
729
|
+
state: { provider, model, apiKey, baseUrl, toolsFlag, skillFlag },
|
|
730
|
+
setProvider,
|
|
731
|
+
setModel,
|
|
732
|
+
setApiKey,
|
|
733
|
+
setBaseUrl,
|
|
734
|
+
setToolsFlag,
|
|
735
|
+
setSkillFlag
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
function useToolPermissions(chat) {
|
|
739
|
+
const [sessionAllowed, setSessionAllowed] = React2.useState(/* @__PURE__ */ new Set());
|
|
740
|
+
const autoApprovedRef = React2.useRef(/* @__PURE__ */ new Set());
|
|
741
|
+
React2.useEffect(() => {
|
|
742
|
+
if (sessionAllowed.size === 0) return;
|
|
743
|
+
for (const message of chat.messages) {
|
|
744
|
+
for (const call of message.toolCalls ?? []) {
|
|
745
|
+
if (call.status === "requires_confirmation" && sessionAllowed.has(call.name) && !autoApprovedRef.current.has(call.id)) {
|
|
746
|
+
autoApprovedRef.current.add(call.id);
|
|
747
|
+
void chat.approve(call.id);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}, [chat.messages, sessionAllowed, chat.approve]);
|
|
752
|
+
const handleApproveAlways = (toolCallId, toolName) => {
|
|
753
|
+
setSessionAllowed((prev) => {
|
|
754
|
+
if (prev.has(toolName)) return prev;
|
|
755
|
+
const next = new Set(prev);
|
|
756
|
+
next.add(toolName);
|
|
757
|
+
return next;
|
|
758
|
+
});
|
|
759
|
+
autoApprovedRef.current.add(toolCallId);
|
|
760
|
+
void chat.approve(toolCallId);
|
|
761
|
+
};
|
|
762
|
+
const awaitingConfirmation = React2.useMemo(
|
|
763
|
+
() => chat.messages.some(
|
|
764
|
+
(message) => message.toolCalls?.some(
|
|
765
|
+
(call) => call.status === "requires_confirmation" && !sessionAllowed.has(call.name)
|
|
766
|
+
)
|
|
767
|
+
),
|
|
768
|
+
[chat.messages, sessionAllowed]
|
|
769
|
+
);
|
|
770
|
+
return { sessionAllowed, handleApproveAlways, awaitingConfirmation };
|
|
771
|
+
}
|
|
772
|
+
function useSessionMeta(options) {
|
|
773
|
+
const sessionCreatedAtRef = React2.useRef(void 0);
|
|
774
|
+
const messageCount = options.messages.length;
|
|
775
|
+
const firstUserContent = options.messages.find((m) => m.role === "user")?.content ?? "";
|
|
776
|
+
React2.useEffect(() => {
|
|
777
|
+
const sessionId = options.sessionId;
|
|
778
|
+
if (!sessionId || sessionId === "custom") return;
|
|
779
|
+
if (!sessionCreatedAtRef.current) {
|
|
780
|
+
sessionCreatedAtRef.current = (/* @__PURE__ */ new Date()).toISOString();
|
|
781
|
+
}
|
|
782
|
+
try {
|
|
783
|
+
writeSessionMeta({
|
|
784
|
+
id: sessionId,
|
|
785
|
+
cwd: process.cwd(),
|
|
786
|
+
createdAt: sessionCreatedAtRef.current,
|
|
787
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
788
|
+
messageCount,
|
|
789
|
+
preview: derivePreview(options.messages),
|
|
790
|
+
provider: options.provider,
|
|
791
|
+
model: options.model
|
|
792
|
+
});
|
|
793
|
+
} catch {
|
|
794
|
+
}
|
|
795
|
+
}, [options.sessionId, messageCount, firstUserContent, options.provider, options.model]);
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// src/extensibility/hooks/runner.ts
|
|
799
|
+
var HookDispatcher = class {
|
|
800
|
+
constructor(handlers = [], onError = (_h, err) => process.stderr.write(
|
|
801
|
+
`[agentskit] hook error: ${err instanceof Error ? err.message : String(err)}
|
|
802
|
+
`
|
|
803
|
+
)) {
|
|
804
|
+
this.onError = onError;
|
|
805
|
+
this.handlers = /* @__PURE__ */ new Map();
|
|
806
|
+
for (const handler of handlers) this.register(handler);
|
|
807
|
+
}
|
|
808
|
+
register(handler) {
|
|
809
|
+
const list = this.handlers.get(handler.event) ?? [];
|
|
810
|
+
list.push(handler);
|
|
811
|
+
this.handlers.set(handler.event, list);
|
|
812
|
+
}
|
|
813
|
+
async dispatch(event, payload) {
|
|
814
|
+
const list = this.handlers.get(event) ?? [];
|
|
815
|
+
let current = { ...payload, event };
|
|
816
|
+
for (const handler of list) {
|
|
817
|
+
if (!this.matches(handler, current)) continue;
|
|
818
|
+
let result;
|
|
819
|
+
try {
|
|
820
|
+
result = await handler.run(current);
|
|
821
|
+
} catch (err) {
|
|
822
|
+
this.onError(handler, err);
|
|
823
|
+
continue;
|
|
824
|
+
}
|
|
825
|
+
if (result.decision === "block") {
|
|
826
|
+
return { payload: current, blocked: true, reason: result.reason };
|
|
827
|
+
}
|
|
828
|
+
if (result.decision === "modify") {
|
|
829
|
+
current = { ...result.payload, event };
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
return { payload: current, blocked: false };
|
|
833
|
+
}
|
|
834
|
+
matches(handler, payload) {
|
|
835
|
+
if (!handler.matcher) return true;
|
|
836
|
+
if (typeof handler.matcher === "function") return handler.matcher(payload);
|
|
837
|
+
return handler.matcher.test(String(payload.tool ?? payload.prompt ?? ""));
|
|
838
|
+
}
|
|
839
|
+
};
|
|
840
|
+
function configHooksToHandlers(config) {
|
|
841
|
+
if (!config) return [];
|
|
842
|
+
const handlers = [];
|
|
843
|
+
for (const [event, entries] of Object.entries(config)) {
|
|
844
|
+
for (const entry of entries) {
|
|
845
|
+
handlers.push({
|
|
846
|
+
event,
|
|
847
|
+
matcher: entry.matcher ? new RegExp(entry.matcher) : void 0,
|
|
848
|
+
run: (payload) => runShellHook(entry, payload)
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
return handlers;
|
|
853
|
+
}
|
|
854
|
+
function runShellHook(entry, payload) {
|
|
855
|
+
return new Promise((resolvePromise) => {
|
|
856
|
+
const timeoutMs = entry.timeout ?? 5e3;
|
|
857
|
+
const child = child_process.spawn("sh", ["-c", entry.run], {
|
|
858
|
+
stdio: ["pipe", "pipe", "inherit"]
|
|
859
|
+
});
|
|
860
|
+
let stdout = "";
|
|
861
|
+
child.stdout.on("data", (chunk) => {
|
|
862
|
+
stdout += chunk.toString();
|
|
863
|
+
});
|
|
864
|
+
const timer = setTimeout(() => {
|
|
865
|
+
child.kill("SIGTERM");
|
|
866
|
+
}, timeoutMs);
|
|
867
|
+
child.on("close", (code) => {
|
|
868
|
+
clearTimeout(timer);
|
|
869
|
+
if (code !== 0) {
|
|
870
|
+
resolvePromise({
|
|
871
|
+
decision: "block",
|
|
872
|
+
reason: `shell hook exited with code ${code}`
|
|
873
|
+
});
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
const trimmed = stdout.trim();
|
|
877
|
+
if (!trimmed) {
|
|
878
|
+
resolvePromise({ decision: "continue" });
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
try {
|
|
882
|
+
const parsed = JSON.parse(trimmed);
|
|
883
|
+
resolvePromise(parsed);
|
|
884
|
+
} catch {
|
|
885
|
+
resolvePromise({ decision: "continue" });
|
|
886
|
+
}
|
|
887
|
+
});
|
|
888
|
+
child.on("error", (err) => {
|
|
889
|
+
clearTimeout(timer);
|
|
890
|
+
resolvePromise({ decision: "block", reason: err.message });
|
|
891
|
+
});
|
|
892
|
+
try {
|
|
893
|
+
child.stdin.write(JSON.stringify(payload));
|
|
894
|
+
child.stdin.end();
|
|
895
|
+
} catch {
|
|
896
|
+
}
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
function groupIntoTurns(messages) {
|
|
900
|
+
const turns = [];
|
|
901
|
+
let current = [];
|
|
902
|
+
for (const message of messages) {
|
|
903
|
+
if (message.role === "user") {
|
|
904
|
+
if (current.length > 0) turns.push(current);
|
|
905
|
+
current = [message];
|
|
906
|
+
} else if (message.role === "system") {
|
|
907
|
+
if (current.length > 0) turns.push(current);
|
|
908
|
+
turns.push([message]);
|
|
909
|
+
current = [];
|
|
910
|
+
} else {
|
|
911
|
+
current.push(message);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
if (current.length > 0) turns.push(current);
|
|
915
|
+
return turns;
|
|
916
|
+
}
|
|
917
|
+
function ChatApp(options) {
|
|
918
|
+
const {
|
|
919
|
+
runtime,
|
|
920
|
+
memory,
|
|
921
|
+
tools,
|
|
922
|
+
skills,
|
|
923
|
+
state: { baseUrl, toolsFlag, skillFlag },
|
|
924
|
+
setProvider,
|
|
925
|
+
setModel,
|
|
926
|
+
setApiKey,
|
|
927
|
+
setBaseUrl,
|
|
928
|
+
setToolsFlag,
|
|
929
|
+
setSkillFlag
|
|
930
|
+
} = useRuntime(options);
|
|
931
|
+
const mergedTools = React2.useMemo(() => {
|
|
932
|
+
const extra = options.extraTools ?? [];
|
|
933
|
+
return [...tools, ...extra];
|
|
934
|
+
}, [tools, options.extraTools]);
|
|
935
|
+
const mergedSkills = React2.useMemo(() => {
|
|
936
|
+
const extra = options.extraSkills ?? [];
|
|
937
|
+
if (!skills && extra.length === 0) return void 0;
|
|
938
|
+
return [...skills ?? [], ...extra];
|
|
939
|
+
}, [skills, options.extraSkills]);
|
|
940
|
+
const chat = ink$1.useChat({
|
|
941
|
+
adapter: runtime.adapter,
|
|
942
|
+
memory,
|
|
943
|
+
systemPrompt: options.system,
|
|
944
|
+
tools: mergedTools.length > 0 ? mergedTools : void 0,
|
|
945
|
+
skills: mergedSkills
|
|
946
|
+
});
|
|
947
|
+
const { sessionAllowed, handleApproveAlways, awaitingConfirmation } = useToolPermissions(chat);
|
|
948
|
+
useSessionMeta({
|
|
949
|
+
sessionId: options.sessionId,
|
|
950
|
+
messages: chat.messages,
|
|
951
|
+
provider: runtime.provider,
|
|
952
|
+
model: runtime.model
|
|
953
|
+
});
|
|
954
|
+
const turns = React2.useMemo(() => groupIntoTurns(chat.messages), [chat.messages]);
|
|
955
|
+
const toolNames = toolsFlag ? toolsFlag.split(",").map((s) => s.trim()).filter(Boolean) : [];
|
|
956
|
+
const [feedback, setFeedback] = React2.useState(null);
|
|
957
|
+
const hookDispatcher = React2.useMemo(
|
|
958
|
+
() => new HookDispatcher(options.hookHandlers ?? []),
|
|
959
|
+
[options.hookHandlers]
|
|
960
|
+
);
|
|
961
|
+
React2.useEffect(() => {
|
|
962
|
+
void hookDispatcher.dispatch("SessionStart", {
|
|
963
|
+
event: "SessionStart",
|
|
964
|
+
sessionId: options.sessionId,
|
|
965
|
+
provider: runtime.provider,
|
|
966
|
+
model: runtime.model
|
|
967
|
+
});
|
|
968
|
+
return () => {
|
|
969
|
+
void hookDispatcher.dispatch("SessionEnd", {
|
|
970
|
+
event: "SessionEnd",
|
|
971
|
+
sessionId: options.sessionId
|
|
972
|
+
});
|
|
973
|
+
};
|
|
974
|
+
}, [hookDispatcher]);
|
|
975
|
+
const slashCommands = React2.useMemo(
|
|
976
|
+
() => [...builtinSlashCommands, ...options.slashCommands ?? []],
|
|
977
|
+
[options.slashCommands]
|
|
978
|
+
);
|
|
979
|
+
const slashRegistry = React2.useMemo(() => createSlashRegistry(slashCommands), [slashCommands]);
|
|
980
|
+
const handleSubmitInput = async (raw) => {
|
|
981
|
+
const parsed = parseSlashCommand(raw);
|
|
982
|
+
if (!parsed) {
|
|
983
|
+
const hookResult = await hookDispatcher.dispatch("UserPromptSubmit", {
|
|
984
|
+
event: "UserPromptSubmit",
|
|
985
|
+
prompt: raw
|
|
986
|
+
});
|
|
987
|
+
if (hookResult.blocked) {
|
|
988
|
+
setFeedback({
|
|
989
|
+
message: `Prompt blocked: ${hookResult.reason ?? "hook refused"}`,
|
|
990
|
+
kind: "warn"
|
|
991
|
+
});
|
|
992
|
+
return true;
|
|
993
|
+
}
|
|
994
|
+
setFeedback(null);
|
|
995
|
+
return false;
|
|
996
|
+
}
|
|
997
|
+
const cmd = slashRegistry.get(parsed.name);
|
|
998
|
+
if (!cmd) {
|
|
999
|
+
setFeedback({
|
|
1000
|
+
message: `Unknown command: /${parsed.name}. Type /help for the list.`,
|
|
1001
|
+
kind: "error"
|
|
1002
|
+
});
|
|
1003
|
+
return true;
|
|
1004
|
+
}
|
|
1005
|
+
const ctx = {
|
|
1006
|
+
chat,
|
|
1007
|
+
runtime: {
|
|
1008
|
+
provider: runtime.provider,
|
|
1009
|
+
model: runtime.model,
|
|
1010
|
+
mode: runtime.mode,
|
|
1011
|
+
baseUrl,
|
|
1012
|
+
tools: toolsFlag,
|
|
1013
|
+
skill: skillFlag,
|
|
1014
|
+
sessionId: options.sessionId
|
|
1015
|
+
},
|
|
1016
|
+
setProvider,
|
|
1017
|
+
setModel,
|
|
1018
|
+
setApiKey,
|
|
1019
|
+
setBaseUrl,
|
|
1020
|
+
setTools: setToolsFlag,
|
|
1021
|
+
setSkill: setSkillFlag,
|
|
1022
|
+
feedback: (message, kind = "info") => setFeedback({ message, kind }),
|
|
1023
|
+
commands: slashCommands
|
|
1024
|
+
};
|
|
1025
|
+
try {
|
|
1026
|
+
await cmd.run(ctx, parsed.args);
|
|
1027
|
+
} catch (err) {
|
|
1028
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1029
|
+
setFeedback({ message: `/${parsed.name} failed: ${message}`, kind: "error" });
|
|
1030
|
+
}
|
|
1031
|
+
return true;
|
|
1032
|
+
};
|
|
1033
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", gap: 1, children: [
|
|
1034
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1035
|
+
ink$1.StatusHeader,
|
|
1036
|
+
{
|
|
1037
|
+
provider: runtime.provider,
|
|
1038
|
+
model: runtime.model,
|
|
1039
|
+
mode: runtime.mode,
|
|
1040
|
+
tools: toolNames,
|
|
1041
|
+
messageCount: chat.messages.length,
|
|
1042
|
+
sessionId: options.sessionId
|
|
1043
|
+
}
|
|
1044
|
+
),
|
|
1045
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink$1.ChatContainer, { children: turns.map((turn, turnIdx) => {
|
|
1046
|
+
const assistantSteps = turn.filter((m) => m.role === "assistant").length;
|
|
1047
|
+
let stepIndex = 0;
|
|
1048
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { flexDirection: "column", gap: 1, children: turn.map((message) => {
|
|
1049
|
+
const showStep = message.role === "assistant" && assistantSteps > 1;
|
|
1050
|
+
if (showStep) stepIndex++;
|
|
1051
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
|
|
1052
|
+
showStep ? /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
|
|
1053
|
+
"\u21BB step ",
|
|
1054
|
+
stepIndex,
|
|
1055
|
+
"/",
|
|
1056
|
+
assistantSteps
|
|
1057
|
+
] }) : null,
|
|
1058
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink$1.Message, { message }),
|
|
1059
|
+
message.toolCalls?.map((toolCall) => /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
|
|
1060
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink$1.ToolCallView, { toolCall, expanded: true }),
|
|
1061
|
+
toolCall.status === "requires_confirmation" && !sessionAllowed.has(toolCall.name) ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1062
|
+
ink$1.ToolConfirmation,
|
|
1063
|
+
{
|
|
1064
|
+
toolCall,
|
|
1065
|
+
onApprove: chat.approve,
|
|
1066
|
+
onDeny: chat.deny,
|
|
1067
|
+
onApproveAlways: handleApproveAlways
|
|
1068
|
+
}
|
|
1069
|
+
) : null
|
|
1070
|
+
] }, toolCall.id))
|
|
1071
|
+
] }, message.id);
|
|
1072
|
+
}) }, `turn-${turnIdx}`);
|
|
1073
|
+
}) }),
|
|
1074
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1075
|
+
ink$1.ThinkingIndicator,
|
|
1076
|
+
{
|
|
1077
|
+
visible: chat.status === "streaming",
|
|
1078
|
+
label: toolNames.length > 0 ? "agent working" : "thinking"
|
|
1079
|
+
}
|
|
1080
|
+
),
|
|
1081
|
+
chat.error ? /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { borderStyle: "round", borderColor: "red", paddingX: 1, flexDirection: "column", children: [
|
|
1082
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: "red", bold: true, children: [
|
|
1083
|
+
"\u2717 ",
|
|
1084
|
+
chat.error.name || "Error"
|
|
1085
|
+
] }),
|
|
1086
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "red", children: chat.error.message })
|
|
1087
|
+
] }) : null,
|
|
1088
|
+
feedback ? /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { borderStyle: "round", borderColor: feedbackBorder(feedback.kind), paddingX: 1, children: /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: feedbackBorder(feedback.kind), children: feedback.message }) }) : null,
|
|
1089
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1090
|
+
ink$1.InputBar,
|
|
1091
|
+
{
|
|
1092
|
+
chat,
|
|
1093
|
+
placeholder: "Type a message or /help for commands",
|
|
1094
|
+
disabled: awaitingConfirmation,
|
|
1095
|
+
onSubmitInput: handleSubmitInput
|
|
1096
|
+
}
|
|
1097
|
+
)
|
|
1098
|
+
] });
|
|
1099
|
+
}
|
|
1100
|
+
function feedbackBorder(kind) {
|
|
1101
|
+
switch (kind) {
|
|
1102
|
+
case "error":
|
|
1103
|
+
return "red";
|
|
1104
|
+
case "warn":
|
|
1105
|
+
return "yellow";
|
|
1106
|
+
case "success":
|
|
1107
|
+
return "green";
|
|
1108
|
+
case "info":
|
|
1109
|
+
default:
|
|
1110
|
+
return "cyan";
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
function renderChatHeader(options) {
|
|
1114
|
+
const runtime = resolveChatProvider(options);
|
|
1115
|
+
const parts = [`provider=${runtime.provider}`];
|
|
1116
|
+
if (runtime.model) parts.push(`model=${runtime.model}`);
|
|
1117
|
+
parts.push(`mode=${runtime.mode}`);
|
|
1118
|
+
if (options.tools) parts.push(`tools=${options.tools}`);
|
|
1119
|
+
if (options.skill) parts.push(`skill=${options.skill}`);
|
|
1120
|
+
if (options.memoryBackend) parts.push(`memory=${options.memoryBackend}`);
|
|
1121
|
+
return parts.join(" ");
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
// src/commands/shared.ts
|
|
1125
|
+
function mergeWithConfig(options, config) {
|
|
1126
|
+
if (!config) return options;
|
|
1127
|
+
const d = config.defaults ?? {};
|
|
1128
|
+
const resolvedApiKey = options.apiKey ?? (d.apiKeyEnv ? process.env[d.apiKeyEnv] : void 0) ?? d.apiKey;
|
|
1129
|
+
return {
|
|
1130
|
+
...options,
|
|
1131
|
+
provider: options.provider !== "demo" ? options.provider : d.provider ?? options.provider,
|
|
1132
|
+
model: options.model ?? d.model,
|
|
1133
|
+
apiKey: resolvedApiKey,
|
|
1134
|
+
baseUrl: options.baseUrl ?? d.baseUrl,
|
|
1135
|
+
tools: options.tools ?? d.tools,
|
|
1136
|
+
skill: options.skill ?? d.skill,
|
|
1137
|
+
system: options.system ?? d.system,
|
|
1138
|
+
memoryBackend: options.memoryBackend ?? d.memoryBackend
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
async function loadPlugins(options = {}) {
|
|
1142
|
+
const {
|
|
1143
|
+
specs = [],
|
|
1144
|
+
pluginDirs = [],
|
|
1145
|
+
cwd = process.cwd(),
|
|
1146
|
+
autoDiscoverUserDir = true,
|
|
1147
|
+
onError = (spec, err) => process.stderr.write(
|
|
1148
|
+
`[agentskit] plugin "${spec}" failed to load: ${err instanceof Error ? err.message : String(err)}
|
|
1149
|
+
`
|
|
1150
|
+
),
|
|
1151
|
+
log = () => {
|
|
1152
|
+
}
|
|
1153
|
+
} = options;
|
|
1154
|
+
const resolvedSpecs = [...specs];
|
|
1155
|
+
const discoveryDirs = [...pluginDirs];
|
|
1156
|
+
if (autoDiscoverUserDir) discoveryDirs.push(path.join(os.homedir(), ".agentskit", "plugins"));
|
|
1157
|
+
for (const dir of discoveryDirs) {
|
|
1158
|
+
const discovered = await discoverPluginsInDir(dir);
|
|
1159
|
+
resolvedSpecs.push(...discovered);
|
|
1160
|
+
}
|
|
1161
|
+
const plugins = [];
|
|
1162
|
+
for (const spec of resolvedSpecs) {
|
|
1163
|
+
try {
|
|
1164
|
+
const plugin = await loadPluginFromSpec(spec, cwd, log);
|
|
1165
|
+
if (plugin) plugins.push(plugin);
|
|
1166
|
+
} catch (err) {
|
|
1167
|
+
onError(spec, err);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
return mergePluginsIntoBundle(plugins);
|
|
1171
|
+
}
|
|
1172
|
+
async function discoverPluginsInDir(dir) {
|
|
1173
|
+
try {
|
|
1174
|
+
const entries = await promises.readdir(dir);
|
|
1175
|
+
const absolutes = entries.filter((name) => /\.(m?js|ts)$/i.test(name)).map((name) => path.join(dir, name));
|
|
1176
|
+
const validated = [];
|
|
1177
|
+
for (const p of absolutes) {
|
|
1178
|
+
try {
|
|
1179
|
+
const s = await promises.stat(p);
|
|
1180
|
+
if (s.isFile()) validated.push(p);
|
|
1181
|
+
} catch {
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
return validated;
|
|
1185
|
+
} catch {
|
|
1186
|
+
return [];
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
async function loadPluginFromSpec(spec, cwd, log) {
|
|
1190
|
+
const isPath = spec.startsWith("./") || spec.startsWith("../") || path.isAbsolute(spec);
|
|
1191
|
+
const importTarget = isPath ? url.pathToFileURL(path.resolve(cwd, spec)).href : spec;
|
|
1192
|
+
const mod = await import(importTarget);
|
|
1193
|
+
const exported = mod.default ?? mod.plugin ?? mod;
|
|
1194
|
+
const sourcePath = isPath ? path.resolve(cwd, spec) : void 0;
|
|
1195
|
+
const ctx = {
|
|
1196
|
+
cwd,
|
|
1197
|
+
sourcePath,
|
|
1198
|
+
log: (msg) => log(`[${spec}] ${msg}`)
|
|
1199
|
+
};
|
|
1200
|
+
if (typeof exported === "function") {
|
|
1201
|
+
const factory = exported;
|
|
1202
|
+
return await factory(ctx);
|
|
1203
|
+
}
|
|
1204
|
+
if (exported && typeof exported === "object" && "name" in exported) {
|
|
1205
|
+
const plugin = exported;
|
|
1206
|
+
if (plugin.init) await plugin.init(ctx);
|
|
1207
|
+
return plugin;
|
|
1208
|
+
}
|
|
1209
|
+
throw new Error(
|
|
1210
|
+
"Module did not export a Plugin \u2014 expected default export to be a Plugin object or a PluginFactory function."
|
|
1211
|
+
);
|
|
1212
|
+
}
|
|
1213
|
+
function mergePluginsIntoBundle(plugins) {
|
|
1214
|
+
const bundle = {
|
|
1215
|
+
plugins,
|
|
1216
|
+
slashCommands: [],
|
|
1217
|
+
tools: [],
|
|
1218
|
+
skills: [],
|
|
1219
|
+
providers: {},
|
|
1220
|
+
hooks: [],
|
|
1221
|
+
mcpServers: []
|
|
1222
|
+
};
|
|
1223
|
+
for (const plugin of plugins) {
|
|
1224
|
+
if (plugin.slashCommands) bundle.slashCommands.push(...plugin.slashCommands);
|
|
1225
|
+
if (plugin.tools) bundle.tools.push(...plugin.tools);
|
|
1226
|
+
if (plugin.skills) bundle.skills.push(...plugin.skills);
|
|
1227
|
+
if (plugin.hooks) bundle.hooks.push(...plugin.hooks);
|
|
1228
|
+
if (plugin.mcpServers) bundle.mcpServers.push(...plugin.mcpServers);
|
|
1229
|
+
if (plugin.providers) {
|
|
1230
|
+
for (const [name, factory] of Object.entries(plugin.providers)) {
|
|
1231
|
+
bundle.providers[name] = factory;
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
return bundle;
|
|
1236
|
+
}
|
|
1237
|
+
var McpClient = class {
|
|
1238
|
+
constructor(spec, onError = (err) => process.stderr.write(
|
|
1239
|
+
`[agentskit] mcp[${spec.name}] error: ${err instanceof Error ? err.message : String(err)}
|
|
1240
|
+
`
|
|
1241
|
+
)) {
|
|
1242
|
+
this.spec = spec;
|
|
1243
|
+
this.onError = onError;
|
|
1244
|
+
this.buffer = "";
|
|
1245
|
+
this.nextId = 1;
|
|
1246
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
1247
|
+
this.disposed = false;
|
|
1248
|
+
}
|
|
1249
|
+
async start() {
|
|
1250
|
+
if (this.child) return;
|
|
1251
|
+
const child = child_process.spawn(this.spec.command, this.spec.args ?? [], {
|
|
1252
|
+
env: { ...process.env, ...this.spec.env ?? {} },
|
|
1253
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1254
|
+
});
|
|
1255
|
+
this.child = child;
|
|
1256
|
+
child.stdout.on("data", (chunk) => this.onStdout(chunk.toString()));
|
|
1257
|
+
child.stderr.on("data", (chunk) => {
|
|
1258
|
+
process.stderr.write(`[mcp ${this.spec.name}] ${chunk}`);
|
|
1259
|
+
});
|
|
1260
|
+
child.on("error", (err) => this.onError(err));
|
|
1261
|
+
child.on("close", () => {
|
|
1262
|
+
this.disposed = true;
|
|
1263
|
+
for (const pending of this.pending.values()) {
|
|
1264
|
+
pending({ jsonrpc: "2.0", id: 0, error: { code: -1, message: "server closed" } });
|
|
1265
|
+
}
|
|
1266
|
+
this.pending.clear();
|
|
1267
|
+
});
|
|
1268
|
+
await this.request("initialize", {
|
|
1269
|
+
protocolVersion: "2024-11-05",
|
|
1270
|
+
capabilities: {},
|
|
1271
|
+
clientInfo: { name: "agentskit", version: "0" }
|
|
1272
|
+
});
|
|
1273
|
+
}
|
|
1274
|
+
async listTools() {
|
|
1275
|
+
const res = await this.request("tools/list", {});
|
|
1276
|
+
const tools = res.tools ?? [];
|
|
1277
|
+
return tools;
|
|
1278
|
+
}
|
|
1279
|
+
async callTool(name, args) {
|
|
1280
|
+
return await this.request("tools/call", { name, arguments: args });
|
|
1281
|
+
}
|
|
1282
|
+
dispose() {
|
|
1283
|
+
if (this.disposed) return;
|
|
1284
|
+
this.disposed = true;
|
|
1285
|
+
this.child?.kill("SIGTERM");
|
|
1286
|
+
}
|
|
1287
|
+
request(method, params) {
|
|
1288
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
1289
|
+
if (!this.child || this.disposed) {
|
|
1290
|
+
rejectPromise(new Error(`mcp server ${this.spec.name} not running`));
|
|
1291
|
+
return;
|
|
1292
|
+
}
|
|
1293
|
+
const id = this.nextId++;
|
|
1294
|
+
const timeoutMs = this.spec.timeout ?? 1e4;
|
|
1295
|
+
const timer = setTimeout(() => {
|
|
1296
|
+
this.pending.delete(id);
|
|
1297
|
+
rejectPromise(new Error(`mcp ${this.spec.name}.${method} timed out`));
|
|
1298
|
+
}, timeoutMs);
|
|
1299
|
+
this.pending.set(id, (res) => {
|
|
1300
|
+
clearTimeout(timer);
|
|
1301
|
+
if (res.error) {
|
|
1302
|
+
rejectPromise(new Error(`mcp ${method} failed: ${res.error.message}`));
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
resolvePromise(res.result);
|
|
1306
|
+
});
|
|
1307
|
+
const message = JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n";
|
|
1308
|
+
this.child.stdin.write(message);
|
|
1309
|
+
});
|
|
1310
|
+
}
|
|
1311
|
+
onStdout(chunk) {
|
|
1312
|
+
this.buffer += chunk;
|
|
1313
|
+
let newlineIndex = this.buffer.indexOf("\n");
|
|
1314
|
+
while (newlineIndex !== -1) {
|
|
1315
|
+
const line = this.buffer.slice(0, newlineIndex).trim();
|
|
1316
|
+
this.buffer = this.buffer.slice(newlineIndex + 1);
|
|
1317
|
+
if (line) {
|
|
1318
|
+
try {
|
|
1319
|
+
const parsed = JSON.parse(line);
|
|
1320
|
+
const pending = this.pending.get(Number(parsed.id));
|
|
1321
|
+
if (pending) {
|
|
1322
|
+
this.pending.delete(Number(parsed.id));
|
|
1323
|
+
pending(parsed);
|
|
1324
|
+
}
|
|
1325
|
+
} catch (err) {
|
|
1326
|
+
this.onError(err);
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
newlineIndex = this.buffer.indexOf("\n");
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
};
|
|
1333
|
+
|
|
1334
|
+
// src/extensibility/mcp/bridge.ts
|
|
1335
|
+
async function bridgeMcpServers(specs) {
|
|
1336
|
+
const clients = [];
|
|
1337
|
+
const tools = [];
|
|
1338
|
+
for (const spec of specs) {
|
|
1339
|
+
const client = new McpClient(spec);
|
|
1340
|
+
try {
|
|
1341
|
+
await client.start();
|
|
1342
|
+
const mcpTools = await client.listTools();
|
|
1343
|
+
for (const mcpTool of mcpTools) {
|
|
1344
|
+
tools.push(mcpToolToDefinition(spec.name, client, mcpTool));
|
|
1345
|
+
}
|
|
1346
|
+
clients.push(client);
|
|
1347
|
+
} catch (err) {
|
|
1348
|
+
process.stderr.write(
|
|
1349
|
+
`[agentskit] mcp server "${spec.name}" failed: ${err instanceof Error ? err.message : String(err)}
|
|
1350
|
+
`
|
|
1351
|
+
);
|
|
1352
|
+
client.dispose();
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
return { clients, tools };
|
|
1356
|
+
}
|
|
1357
|
+
function mcpToolToDefinition(serverName, client, tool) {
|
|
1358
|
+
return {
|
|
1359
|
+
name: `${serverName}__${tool.name}`,
|
|
1360
|
+
description: tool.description ?? `MCP tool ${tool.name} from ${serverName}`,
|
|
1361
|
+
schema: tool.inputSchema ?? { type: "object", properties: {} },
|
|
1362
|
+
execute: async (args) => {
|
|
1363
|
+
return await client.callTool(tool.name, args);
|
|
1364
|
+
}
|
|
1365
|
+
};
|
|
1366
|
+
}
|
|
1367
|
+
function disposeMcpClients(clients) {
|
|
1368
|
+
for (const client of clients) client.dispose();
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
// src/commands/chat.ts
|
|
1372
|
+
function registerChatCommand(program) {
|
|
1373
|
+
program.command("chat").description("Start a terminal chat session.").option("--provider <provider>", "Provider to use", "demo").option("--model <model>", "Model name").option("--api-key <key>", "API key for the selected provider").option("--base-url <url>", "Override provider base URL").option("--system <prompt>", "System prompt").option("--memory <path>", "Explicit memory file path (overrides session management)").option("--tools <tools>", "Comma-separated tools: web_search,fetch_url,filesystem,shell").option("--skill <skills>", "Comma-separated skills: researcher,coder,planner,critic,summarizer").option("--memory-backend <backend>", "Memory backend: file (default), sqlite").option("--new", "Start a fresh chat session (ignore previous conversations in this directory)").option("--resume [id]", "Resume a prior session by id; omit id to resume the latest").option("--list-sessions", "List saved sessions for this directory and exit").option("--no-config", "Skip loading .agentskit.config.json").option(
|
|
1374
|
+
"--plugin-dir <dir>",
|
|
1375
|
+
"Extra directory to auto-discover plugin modules from (repeatable)",
|
|
1376
|
+
(value, prev = []) => [...prev, value],
|
|
1377
|
+
[]
|
|
1378
|
+
).option(
|
|
1379
|
+
"--mode <mode>",
|
|
1380
|
+
"Permission mode: default | plan | acceptEdits | bypassPermissions"
|
|
1381
|
+
).action(async (options) => {
|
|
1382
|
+
if (options.listSessions) {
|
|
1383
|
+
const sessions = listSessions();
|
|
1384
|
+
if (sessions.length === 0) {
|
|
1385
|
+
process.stdout.write("No saved sessions for this directory.\n");
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1388
|
+
for (const s of sessions) {
|
|
1389
|
+
const { id, updatedAt, messageCount, preview, model, label, forkedFrom } = s.metadata;
|
|
1390
|
+
const display = label ? `${label} (${id})` : id;
|
|
1391
|
+
const forkNote = forkedFrom ? ` \u2190 fork ${forkedFrom}` : "";
|
|
1392
|
+
process.stdout.write(
|
|
1393
|
+
`${display} ${updatedAt} msgs=${messageCount}${model ? ` model=${model}` : ""}${forkNote}
|
|
1394
|
+
${preview}
|
|
1395
|
+
`
|
|
1396
|
+
);
|
|
1397
|
+
}
|
|
1398
|
+
return;
|
|
1399
|
+
}
|
|
1400
|
+
const config = options.config !== false ? await loadConfig() : void 0;
|
|
1401
|
+
const merged = mergeWithConfig(options, config);
|
|
1402
|
+
const session = resolveSession({
|
|
1403
|
+
explicitPath: options.memory,
|
|
1404
|
+
forceNew: Boolean(options.new),
|
|
1405
|
+
resumeId: options.resume
|
|
1406
|
+
});
|
|
1407
|
+
if (!session.isNew && !options.memory) {
|
|
1408
|
+
process.stdout.write(
|
|
1409
|
+
`Resuming session ${session.id}. Start fresh with --new or list with --list-sessions.
|
|
1410
|
+
`
|
|
1411
|
+
);
|
|
1412
|
+
}
|
|
1413
|
+
const pluginBundle = await loadPlugins({
|
|
1414
|
+
specs: config?.plugins ?? [],
|
|
1415
|
+
pluginDirs: options.pluginDir ?? []
|
|
1416
|
+
});
|
|
1417
|
+
const configHooks = configHooksToHandlers(config?.hooks);
|
|
1418
|
+
const hookHandlers = [...configHooks, ...pluginBundle.hooks];
|
|
1419
|
+
const configMcpSpecs = Object.entries(config?.mcp?.servers ?? {}).map(([name, spec]) => ({
|
|
1420
|
+
name,
|
|
1421
|
+
command: spec.command,
|
|
1422
|
+
args: spec.args,
|
|
1423
|
+
env: spec.env,
|
|
1424
|
+
timeout: spec.timeout
|
|
1425
|
+
}));
|
|
1426
|
+
const allMcpSpecs = [...configMcpSpecs, ...pluginBundle.mcpServers];
|
|
1427
|
+
const { clients: mcpClients, tools: mcpTools } = await bridgeMcpServers(allMcpSpecs);
|
|
1428
|
+
const policyMode = options.mode ?? config?.permissions?.mode ?? "default";
|
|
1429
|
+
const permissionPolicy = {
|
|
1430
|
+
mode: policyMode,
|
|
1431
|
+
rules: (config?.permissions?.rules ?? []).map((r) => ({
|
|
1432
|
+
tool: r.tool,
|
|
1433
|
+
action: r.action,
|
|
1434
|
+
scope: r.scope
|
|
1435
|
+
}))
|
|
1436
|
+
};
|
|
1437
|
+
const chatOptions = {
|
|
1438
|
+
apiKey: merged.apiKey ?? options.apiKey,
|
|
1439
|
+
baseUrl: merged.baseUrl ?? options.baseUrl,
|
|
1440
|
+
provider: merged.provider,
|
|
1441
|
+
model: merged.model,
|
|
1442
|
+
system: merged.system ?? options.system,
|
|
1443
|
+
memoryPath: session.file,
|
|
1444
|
+
sessionId: session.id,
|
|
1445
|
+
tools: merged.tools ?? options.tools,
|
|
1446
|
+
skill: merged.skill ?? options.skill,
|
|
1447
|
+
memoryBackend: merged.memoryBackend ?? options.memoryBackend,
|
|
1448
|
+
agentsKitConfig: config,
|
|
1449
|
+
slashCommands: pluginBundle.slashCommands,
|
|
1450
|
+
extraTools: [...pluginBundle.tools, ...mcpTools],
|
|
1451
|
+
extraSkills: pluginBundle.skills,
|
|
1452
|
+
hookHandlers,
|
|
1453
|
+
permissionPolicy
|
|
1454
|
+
};
|
|
1455
|
+
process.stdout.write(`${renderChatHeader(chatOptions)}
|
|
1456
|
+
`);
|
|
1457
|
+
const instance = ink.render(React2__default.default.createElement(ChatApp, chatOptions));
|
|
1458
|
+
try {
|
|
1459
|
+
await instance.waitUntilExit();
|
|
1460
|
+
} finally {
|
|
1461
|
+
disposeMcpClients(mcpClients);
|
|
1462
|
+
}
|
|
1463
|
+
if (options.memory) {
|
|
1464
|
+
process.stdout.write(
|
|
1465
|
+
`
|
|
1466
|
+
Session saved to ${session.file}. Resume with --memory ${session.file}
|
|
1467
|
+
`
|
|
1468
|
+
);
|
|
1469
|
+
} else {
|
|
1470
|
+
process.stdout.write(
|
|
1471
|
+
`
|
|
1472
|
+
Session saved. Resume with:
|
|
1473
|
+
agentskit chat --resume ${session.id}
|
|
1474
|
+
Or start fresh with:
|
|
1475
|
+
agentskit chat --new
|
|
1476
|
+
`
|
|
1477
|
+
);
|
|
1478
|
+
}
|
|
1479
|
+
});
|
|
1480
|
+
}
|
|
1481
|
+
function formatEvent(event) {
|
|
1482
|
+
switch (event.type) {
|
|
1483
|
+
case "agent:step":
|
|
1484
|
+
return `[step ${event.step}] ${event.action}`;
|
|
1485
|
+
case "llm:start":
|
|
1486
|
+
return `[llm] start (${event.messageCount} messages)`;
|
|
1487
|
+
case "llm:end": {
|
|
1488
|
+
const preview = event.content.length > 100 ? event.content.slice(0, 100) + "..." : event.content;
|
|
1489
|
+
return `[llm] done (${event.durationMs}ms) "${preview}"`;
|
|
1490
|
+
}
|
|
1491
|
+
case "tool:start":
|
|
1492
|
+
return `[tool] ${event.name} ${JSON.stringify(event.args)}`;
|
|
1493
|
+
case "tool:end":
|
|
1494
|
+
return `[tool] ${event.name} done (${event.durationMs}ms)`;
|
|
1495
|
+
case "error":
|
|
1496
|
+
return `[error] ${event.error.message}`;
|
|
1497
|
+
default:
|
|
1498
|
+
return `[${event.type}]`;
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
async function runAgent(task, options) {
|
|
1502
|
+
if (options.skill && options.skills) {
|
|
1503
|
+
process.stderr.write("Error: --skill and --skills are mutually exclusive. Use one or the other.\n");
|
|
1504
|
+
process.exit(1);
|
|
1505
|
+
}
|
|
1506
|
+
const { adapter } = resolveChatProvider({
|
|
1507
|
+
provider: options.provider,
|
|
1508
|
+
model: options.model,
|
|
1509
|
+
apiKey: options.apiKey,
|
|
1510
|
+
baseUrl: options.baseUrl
|
|
1511
|
+
});
|
|
1512
|
+
const tools = resolveTools(options.tools);
|
|
1513
|
+
const skill = options.skills ? resolveSkills(options.skills) : resolveSkill(options.skill);
|
|
1514
|
+
const memory = options.memory ? resolveMemory(options.memoryBackend, options.memory) : void 0;
|
|
1515
|
+
const observers = [];
|
|
1516
|
+
if (options.verbose) {
|
|
1517
|
+
observers.push({
|
|
1518
|
+
name: "cli-verbose",
|
|
1519
|
+
on(event) {
|
|
1520
|
+
process.stderr.write(formatEvent(event) + "\n");
|
|
1521
|
+
}
|
|
1522
|
+
});
|
|
1523
|
+
}
|
|
1524
|
+
const runtime$1 = runtime.createRuntime({
|
|
1525
|
+
adapter,
|
|
1526
|
+
tools,
|
|
1527
|
+
memory,
|
|
1528
|
+
systemPrompt: options.systemPrompt,
|
|
1529
|
+
maxSteps: options.maxSteps ? parseInt(options.maxSteps, 10) : void 0,
|
|
1530
|
+
observers
|
|
1531
|
+
});
|
|
1532
|
+
const result = await runtime$1.run(task, {
|
|
1533
|
+
skill: skill ?? void 0
|
|
1534
|
+
});
|
|
1535
|
+
process.stdout.write(result.content + "\n");
|
|
1536
|
+
}
|
|
1537
|
+
function RunApp({ task, options }) {
|
|
1538
|
+
const [status, setStatus] = React2.useState("running");
|
|
1539
|
+
const [currentStep, setCurrentStep] = React2.useState(0);
|
|
1540
|
+
const [toolCalls, setToolCalls] = React2.useState([]);
|
|
1541
|
+
const [result, setResult] = React2.useState("");
|
|
1542
|
+
const [error, setError] = React2.useState("");
|
|
1543
|
+
const [durationMs, setDurationMs] = React2.useState(0);
|
|
1544
|
+
React2.useEffect(() => {
|
|
1545
|
+
async function execute() {
|
|
1546
|
+
if (options.skill && options.skills) {
|
|
1547
|
+
setError("--skill and --skills are mutually exclusive.");
|
|
1548
|
+
setStatus("error");
|
|
1549
|
+
return;
|
|
1550
|
+
}
|
|
1551
|
+
const { adapter } = resolveChatProvider({
|
|
1552
|
+
provider: options.provider,
|
|
1553
|
+
model: options.model,
|
|
1554
|
+
apiKey: options.apiKey,
|
|
1555
|
+
baseUrl: options.baseUrl
|
|
1556
|
+
});
|
|
1557
|
+
const tools = resolveTools(options.tools);
|
|
1558
|
+
const skill = options.skills ? resolveSkills(options.skills) : resolveSkill(options.skill);
|
|
1559
|
+
const memory = options.memory ? resolveMemory(options.memoryBackend, options.memory) : void 0;
|
|
1560
|
+
const observers = [{
|
|
1561
|
+
name: "run-ui",
|
|
1562
|
+
on(event) {
|
|
1563
|
+
switch (event.type) {
|
|
1564
|
+
case "agent:step":
|
|
1565
|
+
setCurrentStep(event.step);
|
|
1566
|
+
break;
|
|
1567
|
+
case "tool:start":
|
|
1568
|
+
setToolCalls((prev) => [...prev, { name: event.name, status: "running" }]);
|
|
1569
|
+
break;
|
|
1570
|
+
case "tool:end":
|
|
1571
|
+
setToolCalls((prev) => prev.map(
|
|
1572
|
+
(tc) => tc.name === event.name && tc.status === "running" ? { ...tc, status: "done", durationMs: event.durationMs } : tc
|
|
1573
|
+
));
|
|
1574
|
+
break;
|
|
1575
|
+
case "error":
|
|
1576
|
+
setToolCalls((prev) => prev.map(
|
|
1577
|
+
(tc) => tc.status === "running" ? { ...tc, status: "error" } : tc
|
|
1578
|
+
));
|
|
1579
|
+
break;
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
}];
|
|
1583
|
+
const runtime$1 = runtime.createRuntime({
|
|
1584
|
+
adapter,
|
|
1585
|
+
tools,
|
|
1586
|
+
memory,
|
|
1587
|
+
systemPrompt: options.systemPrompt,
|
|
1588
|
+
maxSteps: options.maxSteps ? parseInt(options.maxSteps, 10) : void 0,
|
|
1589
|
+
observers
|
|
1590
|
+
});
|
|
1591
|
+
try {
|
|
1592
|
+
const runResult = await runtime$1.run(task, { skill: skill ?? void 0 });
|
|
1593
|
+
setResult(runResult.content);
|
|
1594
|
+
setDurationMs(runResult.durationMs);
|
|
1595
|
+
setStatus("done");
|
|
1596
|
+
} catch (err) {
|
|
1597
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
1598
|
+
setStatus("error");
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
void execute();
|
|
1602
|
+
}, []);
|
|
1603
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", gap: 1, children: [
|
|
1604
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { bold: true, color: "cyan", children: "agentskit run" }),
|
|
1605
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
|
|
1606
|
+
"Task: ",
|
|
1607
|
+
task
|
|
1608
|
+
] }),
|
|
1609
|
+
status === "running" && currentStep > 0 && /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: "yellow", children: [
|
|
1610
|
+
"\u27F3",
|
|
1611
|
+
" Step ",
|
|
1612
|
+
currentStep
|
|
1613
|
+
] }),
|
|
1614
|
+
toolCalls.map((tc, i) => /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { marginLeft: 2, children: /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: tc.status === "running" ? "yellow" : tc.status === "done" ? "green" : "red", children: [
|
|
1615
|
+
tc.status === "running" ? "\u27F3" : tc.status === "done" ? "\u2713" : "\u2717",
|
|
1616
|
+
" ",
|
|
1617
|
+
tc.name,
|
|
1618
|
+
tc.durationMs !== void 0 ? ` (${tc.durationMs}ms)` : ""
|
|
1619
|
+
] }) }, i)),
|
|
1620
|
+
status === "running" && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "yellow", children: "Running..." }),
|
|
1621
|
+
status === "done" && /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
|
|
1622
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: "green", bold: true, children: [
|
|
1623
|
+
"Done (",
|
|
1624
|
+
durationMs,
|
|
1625
|
+
"ms)"
|
|
1626
|
+
] }),
|
|
1627
|
+
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: result })
|
|
1628
|
+
] }),
|
|
1629
|
+
status === "error" && /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: "red", bold: true, children: [
|
|
1630
|
+
"Error: ",
|
|
1631
|
+
error
|
|
1632
|
+
] })
|
|
1633
|
+
] });
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
// src/commands/run.ts
|
|
1637
|
+
function registerRunCommand(program) {
|
|
1638
|
+
program.command("run [task]").description("Execute an agent task and output the result.").option("--task <task>", "Task string (alternative to positional argument)").option("--provider <provider>", "Provider to use", "demo").option("--model <model>", "Model name").option("--api-key <key>", "API key for the selected provider").option("--base-url <url>", "Override provider base URL").option("--skill <skill>", "Single skill to use").option("--skills <skills>", "Comma-separated skills (composed together)").option("--tools <tools>", "Comma-separated tools: web_search,filesystem,shell").option("--memory <path>", "Path for memory persistence").option("--memory-backend <backend>", "Memory backend: file (default), sqlite").option("--system-prompt <prompt>", "System prompt").option("--max-steps <steps>", "Maximum agent steps", "10").option("--verbose", "Stream agent steps to stderr").option("--pretty", "Use rich Ink-based output").option("--no-config", "Skip loading .agentskit.config.json").action(async (positionalTask, options) => {
|
|
1639
|
+
const task = options.task ?? positionalTask;
|
|
1640
|
+
if (!task) {
|
|
1641
|
+
process.stderr.write("Error: task is required. Pass as argument or use --task.\n");
|
|
1642
|
+
process.exit(1);
|
|
201
1643
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
if (!skillName) return void 0;
|
|
207
|
-
const skill = skillRegistry[skillName.trim()];
|
|
208
|
-
if (!skill) {
|
|
209
|
-
process.stderr.write(`Unknown skill: ${skillName}
|
|
210
|
-
`);
|
|
211
|
-
return void 0;
|
|
212
|
-
}
|
|
213
|
-
return skill;
|
|
214
|
-
}
|
|
215
|
-
function resolveSkills(skillNames) {
|
|
216
|
-
if (!skillNames) return void 0;
|
|
217
|
-
const names = skillNames.split(",").map((s) => s.trim());
|
|
218
|
-
const resolved = names.map((n) => skillRegistry[n]).filter(Boolean);
|
|
219
|
-
if (resolved.length === 0) {
|
|
220
|
-
process.stderr.write(`No valid skills found in: ${skillNames}
|
|
221
|
-
`);
|
|
222
|
-
return void 0;
|
|
223
|
-
}
|
|
224
|
-
if (resolved.length === 1) return resolved[0];
|
|
225
|
-
return skills.composeSkills(...resolved);
|
|
226
|
-
}
|
|
227
|
-
function resolveMemory(backend, memoryPath) {
|
|
228
|
-
switch (backend) {
|
|
229
|
-
case "sqlite":
|
|
230
|
-
return memory.sqliteChatMemory({ path: memoryPath.replace(/\.json$/, ".db") });
|
|
231
|
-
case "file":
|
|
232
|
-
default:
|
|
233
|
-
return memory.fileChatMemory(memoryPath);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
function groupIntoTurns(messages) {
|
|
237
|
-
const turns = [];
|
|
238
|
-
let current = [];
|
|
239
|
-
for (const message of messages) {
|
|
240
|
-
if (message.role === "user") {
|
|
241
|
-
if (current.length > 0) turns.push(current);
|
|
242
|
-
current = [message];
|
|
243
|
-
} else if (message.role === "system") {
|
|
244
|
-
if (current.length > 0) turns.push(current);
|
|
245
|
-
turns.push([message]);
|
|
246
|
-
current = [];
|
|
1644
|
+
const config = options.config !== false ? await loadConfig() : void 0;
|
|
1645
|
+
const merged = mergeWithConfig(options, config);
|
|
1646
|
+
if (options.pretty) {
|
|
1647
|
+
ink.render(React2__default.default.createElement(RunApp, { task, options }));
|
|
247
1648
|
} else {
|
|
248
|
-
|
|
1649
|
+
try {
|
|
1650
|
+
await runAgent(task, { ...options, provider: merged.provider, model: merged.model });
|
|
1651
|
+
} catch (err) {
|
|
1652
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
|
|
1653
|
+
`);
|
|
1654
|
+
process.exit(1);
|
|
1655
|
+
}
|
|
249
1656
|
}
|
|
250
|
-
}
|
|
251
|
-
if (current.length > 0) turns.push(current);
|
|
252
|
-
return turns;
|
|
253
|
-
}
|
|
254
|
-
function ChatApp(options) {
|
|
255
|
-
const runtime = React3.useMemo(() => resolveChatProvider(options), [
|
|
256
|
-
options.apiKey,
|
|
257
|
-
options.baseUrl,
|
|
258
|
-
options.model,
|
|
259
|
-
options.provider
|
|
260
|
-
]);
|
|
261
|
-
const memory = React3.useMemo(
|
|
262
|
-
() => resolveMemory(options.memoryBackend, options.memoryPath ?? ".agentskit-history.json"),
|
|
263
|
-
[options.memoryPath, options.memoryBackend]
|
|
264
|
-
);
|
|
265
|
-
const tools = React3.useMemo(() => resolveTools(options.tools), [options.tools]);
|
|
266
|
-
const skills = React3.useMemo(() => {
|
|
267
|
-
if (!options.skill) return void 0;
|
|
268
|
-
const names = options.skill.split(",").map((s) => s.trim());
|
|
269
|
-
const resolved = names.map((n) => skillRegistry[n]).filter(Boolean);
|
|
270
|
-
if (resolved.length === 0) return void 0;
|
|
271
|
-
return resolved;
|
|
272
|
-
}, [options.skill]);
|
|
273
|
-
const chat = ink$1.useChat({
|
|
274
|
-
adapter: runtime.adapter,
|
|
275
|
-
memory,
|
|
276
|
-
systemPrompt: options.system,
|
|
277
|
-
tools: tools.length > 0 ? tools : void 0,
|
|
278
|
-
skills
|
|
279
1657
|
});
|
|
280
|
-
const turns = React3.useMemo(() => groupIntoTurns(chat.messages), [chat.messages]);
|
|
281
|
-
const toolNames = options.tools ? options.tools.split(",").map((s) => s.trim()).filter(Boolean) : [];
|
|
282
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", gap: 1, children: [
|
|
283
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
284
|
-
ink$1.StatusHeader,
|
|
285
|
-
{
|
|
286
|
-
provider: runtime.provider,
|
|
287
|
-
model: runtime.model,
|
|
288
|
-
mode: runtime.mode,
|
|
289
|
-
tools: toolNames,
|
|
290
|
-
messageCount: chat.messages.length
|
|
291
|
-
}
|
|
292
|
-
),
|
|
293
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink$1.ChatContainer, { children: turns.map((turn, turnIdx) => {
|
|
294
|
-
const assistantSteps = turn.filter((m) => m.role === "assistant").length;
|
|
295
|
-
let stepIndex = 0;
|
|
296
|
-
return /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { flexDirection: "column", gap: 1, children: turn.map((message) => {
|
|
297
|
-
const showStep = message.role === "assistant" && assistantSteps > 1;
|
|
298
|
-
if (showStep) stepIndex++;
|
|
299
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
|
|
300
|
-
showStep ? /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { dimColor: true, children: [
|
|
301
|
-
"\u21BB step ",
|
|
302
|
-
stepIndex,
|
|
303
|
-
"/",
|
|
304
|
-
assistantSteps
|
|
305
|
-
] }) : null,
|
|
306
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink$1.Message, { message }),
|
|
307
|
-
message.toolCalls?.map((toolCall) => /* @__PURE__ */ jsxRuntime.jsx(ink$1.ToolCallView, { toolCall, expanded: true }, toolCall.id))
|
|
308
|
-
] }, message.id);
|
|
309
|
-
}) }, `turn-${turnIdx}`);
|
|
310
|
-
}) }),
|
|
311
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
312
|
-
ink$1.ThinkingIndicator,
|
|
313
|
-
{
|
|
314
|
-
visible: chat.status === "streaming",
|
|
315
|
-
label: toolNames.length > 0 ? "agent working" : "thinking"
|
|
316
|
-
}
|
|
317
|
-
),
|
|
318
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink$1.InputBar, { chat, placeholder: "Type a message and press Enter\u2026" })
|
|
319
|
-
] });
|
|
320
|
-
}
|
|
321
|
-
function renderChatHeader(options) {
|
|
322
|
-
const runtime = resolveChatProvider(options);
|
|
323
|
-
const parts = [`provider=${runtime.provider}`];
|
|
324
|
-
if (runtime.model) parts.push(`model=${runtime.model}`);
|
|
325
|
-
parts.push(`mode=${runtime.mode}`);
|
|
326
|
-
if (options.tools) parts.push(`tools=${options.tools}`);
|
|
327
|
-
if (options.skill) parts.push(`skill=${options.skill}`);
|
|
328
|
-
if (options.memoryBackend) parts.push(`memory=${options.memoryBackend}`);
|
|
329
|
-
return parts.join(" ");
|
|
330
1658
|
}
|
|
331
1659
|
var PROVIDER_IMPORT = {
|
|
332
1660
|
openai: "openai",
|
|
@@ -928,159 +2256,42 @@ function printNextSteps(options) {
|
|
|
928
2256
|
`);
|
|
929
2257
|
process.stdout.write(kleur__default.default.dim(" Docs: https://www.agentskit.io/docs\n\n"));
|
|
930
2258
|
}
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
const
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
async function runAgent(task, options) {
|
|
952
|
-
if (options.skill && options.skills) {
|
|
953
|
-
process.stderr.write("Error: --skill and --skills are mutually exclusive. Use one or the other.\n");
|
|
954
|
-
process.exit(1);
|
|
955
|
-
}
|
|
956
|
-
const { adapter } = resolveChatProvider({
|
|
957
|
-
provider: options.provider,
|
|
958
|
-
model: options.model,
|
|
959
|
-
apiKey: options.apiKey,
|
|
960
|
-
baseUrl: options.baseUrl
|
|
961
|
-
});
|
|
962
|
-
const tools = resolveTools(options.tools);
|
|
963
|
-
const skill = options.skills ? resolveSkills(options.skills) : resolveSkill(options.skill);
|
|
964
|
-
const memory = options.memory ? resolveMemory(options.memoryBackend, options.memory) : void 0;
|
|
965
|
-
const observers = [];
|
|
966
|
-
if (options.verbose) {
|
|
967
|
-
observers.push({
|
|
968
|
-
name: "cli-verbose",
|
|
969
|
-
on(event) {
|
|
970
|
-
process.stderr.write(formatEvent(event) + "\n");
|
|
971
|
-
}
|
|
972
|
-
});
|
|
973
|
-
}
|
|
974
|
-
const runtime$1 = runtime.createRuntime({
|
|
975
|
-
adapter,
|
|
976
|
-
tools,
|
|
977
|
-
memory,
|
|
978
|
-
systemPrompt: options.systemPrompt,
|
|
979
|
-
maxSteps: options.maxSteps ? parseInt(options.maxSteps, 10) : void 0,
|
|
980
|
-
observers
|
|
981
|
-
});
|
|
982
|
-
const result = await runtime$1.run(task, {
|
|
983
|
-
skill: skill ?? void 0
|
|
984
|
-
});
|
|
985
|
-
process.stdout.write(result.content + "\n");
|
|
986
|
-
}
|
|
987
|
-
function RunApp({ task, options }) {
|
|
988
|
-
const [status, setStatus] = React3.useState("running");
|
|
989
|
-
const [currentStep, setCurrentStep] = React3.useState(0);
|
|
990
|
-
const [toolCalls, setToolCalls] = React3.useState([]);
|
|
991
|
-
const [result, setResult] = React3.useState("");
|
|
992
|
-
const [error, setError] = React3.useState("");
|
|
993
|
-
const [durationMs, setDurationMs] = React3.useState(0);
|
|
994
|
-
React3.useEffect(() => {
|
|
995
|
-
async function execute() {
|
|
996
|
-
if (options.skill && options.skills) {
|
|
997
|
-
setError("--skill and --skills are mutually exclusive.");
|
|
998
|
-
setStatus("error");
|
|
999
|
-
return;
|
|
1000
|
-
}
|
|
1001
|
-
const { adapter } = resolveChatProvider({
|
|
1002
|
-
provider: options.provider,
|
|
1003
|
-
model: options.model,
|
|
1004
|
-
apiKey: options.apiKey,
|
|
1005
|
-
baseUrl: options.baseUrl
|
|
1006
|
-
});
|
|
1007
|
-
const tools = resolveTools(options.tools);
|
|
1008
|
-
const skill = options.skills ? resolveSkills(options.skills) : resolveSkill(options.skill);
|
|
1009
|
-
const memory = options.memory ? resolveMemory(options.memoryBackend, options.memory) : void 0;
|
|
1010
|
-
const observers = [{
|
|
1011
|
-
name: "run-ui",
|
|
1012
|
-
on(event) {
|
|
1013
|
-
switch (event.type) {
|
|
1014
|
-
case "agent:step":
|
|
1015
|
-
setCurrentStep(event.step);
|
|
1016
|
-
break;
|
|
1017
|
-
case "tool:start":
|
|
1018
|
-
setToolCalls((prev) => [...prev, { name: event.name, status: "running" }]);
|
|
1019
|
-
break;
|
|
1020
|
-
case "tool:end":
|
|
1021
|
-
setToolCalls((prev) => prev.map(
|
|
1022
|
-
(tc) => tc.name === event.name && tc.status === "running" ? { ...tc, status: "done", durationMs: event.durationMs } : tc
|
|
1023
|
-
));
|
|
1024
|
-
break;
|
|
1025
|
-
case "error":
|
|
1026
|
-
setToolCalls((prev) => prev.map(
|
|
1027
|
-
(tc) => tc.status === "running" ? { ...tc, status: "error" } : tc
|
|
1028
|
-
));
|
|
1029
|
-
break;
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1032
|
-
}];
|
|
1033
|
-
const runtime$1 = runtime.createRuntime({
|
|
1034
|
-
adapter,
|
|
1035
|
-
tools,
|
|
1036
|
-
memory,
|
|
1037
|
-
systemPrompt: options.systemPrompt,
|
|
1038
|
-
maxSteps: options.maxSteps ? parseInt(options.maxSteps, 10) : void 0,
|
|
1039
|
-
observers
|
|
2259
|
+
|
|
2260
|
+
// src/commands/init.ts
|
|
2261
|
+
function registerInitCommand(program) {
|
|
2262
|
+
program.command("init").description("Generate a starter project. Run with no flags for interactive mode.").option("--template <template>", "Starter template (react|ink|runtime|multi-agent)").option("--dir <directory>", "Target directory", "agentskit-app").option("--provider <provider>", "LLM provider (openai|anthropic|gemini|ollama|demo)").option("--tools <tools>", "Comma-separated tools (web_search,filesystem,shell)").option("--memory <backend>", "Memory backend (none|file|sqlite)").option("--pm <packageManager>", "Package manager (pnpm|npm|yarn|bun)").option("-y, --yes", "Skip interactive prompts; use flag values + defaults").action(async (rawOptions) => {
|
|
2263
|
+
const isCi = !process.stdout.isTTY || rawOptions.yes || rawOptions.template;
|
|
2264
|
+
let resolved;
|
|
2265
|
+
if (isCi) {
|
|
2266
|
+
const template = rawOptions.template ?? "react";
|
|
2267
|
+
resolved = {
|
|
2268
|
+
targetDir: path__default.default.resolve(process.cwd(), rawOptions.dir),
|
|
2269
|
+
template,
|
|
2270
|
+
provider: rawOptions.provider ?? "demo",
|
|
2271
|
+
tools: rawOptions.tools ? rawOptions.tools.split(",").map((t) => t.trim()) : [],
|
|
2272
|
+
memory: rawOptions.memory ?? "none",
|
|
2273
|
+
packageManager: rawOptions.pm ?? "pnpm"
|
|
2274
|
+
};
|
|
2275
|
+
} else {
|
|
2276
|
+
const result = await runInteractiveInit({
|
|
2277
|
+
dir: rawOptions.dir,
|
|
2278
|
+
template: rawOptions.template
|
|
1040
2279
|
});
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
setResult(runResult.content);
|
|
1044
|
-
setDurationMs(runResult.durationMs);
|
|
1045
|
-
setStatus("done");
|
|
1046
|
-
} catch (err) {
|
|
1047
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
1048
|
-
setStatus("error");
|
|
2280
|
+
if (result.cancelled) {
|
|
2281
|
+
process.exit(0);
|
|
1049
2282
|
}
|
|
2283
|
+
resolved = result.options;
|
|
1050
2284
|
}
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
" Step ",
|
|
1062
|
-
currentStep
|
|
1063
|
-
] }),
|
|
1064
|
-
toolCalls.map((tc, i) => /* @__PURE__ */ jsxRuntime.jsx(ink.Box, { marginLeft: 2, children: /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: tc.status === "running" ? "yellow" : tc.status === "done" ? "green" : "red", children: [
|
|
1065
|
-
tc.status === "running" ? "\u27F3" : tc.status === "done" ? "\u2713" : "\u2717",
|
|
1066
|
-
" ",
|
|
1067
|
-
tc.name,
|
|
1068
|
-
tc.durationMs !== void 0 ? ` (${tc.durationMs}ms)` : ""
|
|
1069
|
-
] }) }, i)),
|
|
1070
|
-
status === "running" && /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { color: "yellow", children: "Running..." }),
|
|
1071
|
-
status === "done" && /* @__PURE__ */ jsxRuntime.jsxs(ink.Box, { flexDirection: "column", children: [
|
|
1072
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: "green", bold: true, children: [
|
|
1073
|
-
"Done (",
|
|
1074
|
-
durationMs,
|
|
1075
|
-
"ms)"
|
|
1076
|
-
] }),
|
|
1077
|
-
/* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: result })
|
|
1078
|
-
] }),
|
|
1079
|
-
status === "error" && /* @__PURE__ */ jsxRuntime.jsxs(ink.Text, { color: "red", bold: true, children: [
|
|
1080
|
-
"Error: ",
|
|
1081
|
-
error
|
|
1082
|
-
] })
|
|
1083
|
-
] });
|
|
2285
|
+
await writeStarterProject(resolved);
|
|
2286
|
+
if (isCi) {
|
|
2287
|
+
process.stdout.write(
|
|
2288
|
+
`Created ${resolved.template} starter in ${path__default.default.relative(process.cwd(), resolved.targetDir) || "."}
|
|
2289
|
+
`
|
|
2290
|
+
);
|
|
2291
|
+
} else {
|
|
2292
|
+
printNextSteps(resolved);
|
|
2293
|
+
}
|
|
2294
|
+
});
|
|
1084
2295
|
}
|
|
1085
2296
|
var PROVIDER_ENV_KEYS = {
|
|
1086
2297
|
openai: "OPENAI_API_KEY",
|
|
@@ -1141,8 +2352,8 @@ async function checkPnpm() {
|
|
|
1141
2352
|
};
|
|
1142
2353
|
}
|
|
1143
2354
|
async function checkPackageJson() {
|
|
1144
|
-
const
|
|
1145
|
-
if (!fs.existsSync(
|
|
2355
|
+
const path5 = path.join(process.cwd(), "package.json");
|
|
2356
|
+
if (!fs.existsSync(path5)) {
|
|
1146
2357
|
return {
|
|
1147
2358
|
status: "warn",
|
|
1148
2359
|
name: "package.json",
|
|
@@ -1151,7 +2362,7 @@ async function checkPackageJson() {
|
|
|
1151
2362
|
};
|
|
1152
2363
|
}
|
|
1153
2364
|
try {
|
|
1154
|
-
const pkg = JSON.parse(await promises.readFile(
|
|
2365
|
+
const pkg = JSON.parse(await promises.readFile(path5, "utf8"));
|
|
1155
2366
|
const deps = {
|
|
1156
2367
|
...pkg.dependencies ?? {},
|
|
1157
2368
|
...pkg.devDependencies ?? {}
|
|
@@ -1347,6 +2558,26 @@ function renderReport(report, opts = {}) {
|
|
|
1347
2558
|
lines.push("");
|
|
1348
2559
|
return lines.join("\n");
|
|
1349
2560
|
}
|
|
2561
|
+
|
|
2562
|
+
// src/commands/doctor.ts
|
|
2563
|
+
function registerDoctorCommand(program) {
|
|
2564
|
+
program.command("doctor").description("Diagnose your AgentsKit environment.").option("--no-network", "Skip provider reachability checks").option(
|
|
2565
|
+
"--providers <providers>",
|
|
2566
|
+
"Comma-separated providers to check (default: openai,anthropic,gemini,ollama)"
|
|
2567
|
+
).option("--json", "Emit JSON instead of formatted output").action(async (options) => {
|
|
2568
|
+
const providers2 = options.providers ? options.providers.split(",").map((p) => p.trim()).filter(Boolean) : void 0;
|
|
2569
|
+
const report = await runDoctor({
|
|
2570
|
+
providers: providers2,
|
|
2571
|
+
noNetwork: options.network === false
|
|
2572
|
+
});
|
|
2573
|
+
if (options.json) {
|
|
2574
|
+
process.stdout.write(JSON.stringify(report, null, 2) + "\n");
|
|
2575
|
+
} else {
|
|
2576
|
+
process.stdout.write(renderReport(report, { color: process.stdout.isTTY }));
|
|
2577
|
+
}
|
|
2578
|
+
if (report.fail > 0) process.exit(1);
|
|
2579
|
+
});
|
|
2580
|
+
}
|
|
1350
2581
|
var DEFAULT_WATCH = [
|
|
1351
2582
|
"**/*.ts",
|
|
1352
2583
|
"**/*.tsx",
|
|
@@ -1413,11 +2644,11 @@ function startDev(options) {
|
|
|
1413
2644
|
}
|
|
1414
2645
|
});
|
|
1415
2646
|
};
|
|
1416
|
-
const restart = (
|
|
2647
|
+
const restart = (path5) => {
|
|
1417
2648
|
if (restartTimer) clearTimeout(restartTimer);
|
|
1418
2649
|
restartTimer = setTimeout(() => {
|
|
1419
2650
|
restartTimer = void 0;
|
|
1420
|
-
banner(`\u21BB change detected \u2014 ${
|
|
2651
|
+
banner(`\u21BB change detected \u2014 ${path5}`, "yellow");
|
|
1421
2652
|
if (child && !child.killed && child.exitCode === null) {
|
|
1422
2653
|
child.kill("SIGTERM");
|
|
1423
2654
|
}
|
|
@@ -1454,6 +2685,73 @@ function startDev(options) {
|
|
|
1454
2685
|
restarts: () => restartCount
|
|
1455
2686
|
};
|
|
1456
2687
|
}
|
|
2688
|
+
|
|
2689
|
+
// src/commands/dev.ts
|
|
2690
|
+
function registerDevCommand(program) {
|
|
2691
|
+
program.command("dev [entry]").description("Run an entry file with hot-reload on file changes.").option("--watch <globs>", "Comma-separated glob patterns to watch").option("--ignore <globs>", "Comma-separated glob patterns to ignore").option("--debounce <ms>", "Debounce window before restart", "200").action(async (positional, options) => {
|
|
2692
|
+
const entry = positional ?? "src/index.ts";
|
|
2693
|
+
const watch = options.watch ? options.watch.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
2694
|
+
const ignore = options.ignore ? options.ignore.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
2695
|
+
try {
|
|
2696
|
+
const controller = startDev({
|
|
2697
|
+
entry,
|
|
2698
|
+
watch,
|
|
2699
|
+
ignore,
|
|
2700
|
+
debounceMs: Number(options.debounce) || 200
|
|
2701
|
+
});
|
|
2702
|
+
await controller.done;
|
|
2703
|
+
} catch (err) {
|
|
2704
|
+
process.stderr.write(`Error: ${err.message}
|
|
2705
|
+
`);
|
|
2706
|
+
process.exit(1);
|
|
2707
|
+
}
|
|
2708
|
+
});
|
|
2709
|
+
}
|
|
2710
|
+
function registerConfigCommand(program) {
|
|
2711
|
+
program.command("config").description("Show or scaffold the AgentsKit config.").argument(
|
|
2712
|
+
"[action]",
|
|
2713
|
+
'Action: "init" to create a template, "show" to print the merged config.',
|
|
2714
|
+
"show"
|
|
2715
|
+
).option("--global", "Write/read the global config at ~/.agentskit/config.json (default)").option("--local", "Write/read a project-level .agentskit.config.json in the current directory").option("--force", "Overwrite an existing config file").action(async (action, options) => {
|
|
2716
|
+
const isLocal = Boolean(options.local);
|
|
2717
|
+
const targetPath = isLocal ? path__default.default.join(process.cwd(), ".agentskit.config.json") : path__default.default.join(os.homedir(), ".agentskit", "config.json");
|
|
2718
|
+
if (action === "show") {
|
|
2719
|
+
const config = await loadConfig();
|
|
2720
|
+
process.stdout.write(JSON.stringify(config ?? {}, null, 2) + "\n");
|
|
2721
|
+
return;
|
|
2722
|
+
}
|
|
2723
|
+
if (action !== "init") {
|
|
2724
|
+
process.stderr.write(`Unknown action: ${action}. Use "init" or "show".
|
|
2725
|
+
`);
|
|
2726
|
+
process.exit(2);
|
|
2727
|
+
}
|
|
2728
|
+
if (fs.existsSync(targetPath) && !options.force) {
|
|
2729
|
+
process.stderr.write(
|
|
2730
|
+
`Config already exists at ${targetPath}. Re-run with --force to overwrite.
|
|
2731
|
+
`
|
|
2732
|
+
);
|
|
2733
|
+
process.exit(1);
|
|
2734
|
+
}
|
|
2735
|
+
const template = {
|
|
2736
|
+
defaults: {
|
|
2737
|
+
provider: "openai",
|
|
2738
|
+
baseUrl: "https://openrouter.ai/api",
|
|
2739
|
+
apiKeyEnv: "OPENROUTER_API_KEY",
|
|
2740
|
+
model: "openai/gpt-oss-120b:free",
|
|
2741
|
+
tools: "web_search,fetch_url"
|
|
2742
|
+
}
|
|
2743
|
+
};
|
|
2744
|
+
fs.mkdirSync(path__default.default.dirname(targetPath), { recursive: true });
|
|
2745
|
+
fs.writeFileSync(targetPath, JSON.stringify(template, null, 2) + "\n");
|
|
2746
|
+
process.stdout.write(
|
|
2747
|
+
`Wrote ${targetPath}
|
|
2748
|
+
Edit it to taste, then run:
|
|
2749
|
+
agentskit chat
|
|
2750
|
+
(flags on the CLI still win over config values.)
|
|
2751
|
+
`
|
|
2752
|
+
);
|
|
2753
|
+
});
|
|
2754
|
+
}
|
|
1457
2755
|
async function startTunnel(options) {
|
|
1458
2756
|
const stdout = options.stdout ?? process.stdout;
|
|
1459
2757
|
const open = options.open ?? (async (opts) => {
|
|
@@ -1519,125 +2817,8 @@ async function startTunnel(options) {
|
|
|
1519
2817
|
};
|
|
1520
2818
|
}
|
|
1521
2819
|
|
|
1522
|
-
// src/commands.ts
|
|
1523
|
-
function
|
|
1524
|
-
if (!config) return options;
|
|
1525
|
-
return {
|
|
1526
|
-
...options,
|
|
1527
|
-
// Config defaults — only apply if CLI flag wasn't set
|
|
1528
|
-
provider: options.provider !== "demo" ? options.provider : config.defaults?.provider ?? options.provider,
|
|
1529
|
-
model: options.model ?? config.defaults?.model
|
|
1530
|
-
};
|
|
1531
|
-
}
|
|
1532
|
-
function createCli() {
|
|
1533
|
-
const program = new commander.Command();
|
|
1534
|
-
program.name("agentskit").description("AgentsKit CLI for chat demos and project bootstrapping.");
|
|
1535
|
-
program.command("chat").description("Start a terminal chat session.").option("--provider <provider>", "Provider to use", "demo").option("--model <model>", "Model name").option("--api-key <key>", "API key for the selected provider").option("--base-url <url>", "Override provider base URL").option("--system <prompt>", "System prompt").option("--memory <path>", "Path for file-based memory", ".agentskit-history.json").option("--tools <tools>", "Comma-separated tools: web_search,filesystem,shell").option("--skill <skills>", "Comma-separated skills: researcher,coder,planner,critic,summarizer").option("--memory-backend <backend>", "Memory backend: file (default), sqlite").option("--no-config", "Skip loading .agentskit.config.json").action(async (options) => {
|
|
1536
|
-
const config = options.config !== false ? await loadConfig() : void 0;
|
|
1537
|
-
const merged = mergeWithConfig(options, config);
|
|
1538
|
-
const chatOptions = {
|
|
1539
|
-
apiKey: merged.apiKey ?? options.apiKey,
|
|
1540
|
-
baseUrl: merged.baseUrl ?? options.baseUrl,
|
|
1541
|
-
provider: merged.provider,
|
|
1542
|
-
model: merged.model,
|
|
1543
|
-
system: options.system,
|
|
1544
|
-
memoryPath: options.memory,
|
|
1545
|
-
tools: options.tools,
|
|
1546
|
-
skill: options.skill,
|
|
1547
|
-
memoryBackend: options.memoryBackend,
|
|
1548
|
-
agentsKitConfig: config
|
|
1549
|
-
};
|
|
1550
|
-
process.stdout.write(`${renderChatHeader(chatOptions)}
|
|
1551
|
-
`);
|
|
1552
|
-
ink.render(React3__default.default.createElement(ChatApp, chatOptions));
|
|
1553
|
-
});
|
|
1554
|
-
program.command("run [task]").description("Execute an agent task and output the result.").option("--task <task>", "Task string (alternative to positional argument)").option("--provider <provider>", "Provider to use", "demo").option("--model <model>", "Model name").option("--api-key <key>", "API key for the selected provider").option("--base-url <url>", "Override provider base URL").option("--skill <skill>", "Single skill to use").option("--skills <skills>", "Comma-separated skills (composed together)").option("--tools <tools>", "Comma-separated tools: web_search,filesystem,shell").option("--memory <path>", "Path for memory persistence").option("--memory-backend <backend>", "Memory backend: file (default), sqlite").option("--system-prompt <prompt>", "System prompt").option("--max-steps <steps>", "Maximum agent steps", "10").option("--verbose", "Stream agent steps to stderr").option("--pretty", "Use rich Ink-based output").option("--no-config", "Skip loading .agentskit.config.json").action(async (positionalTask, options) => {
|
|
1555
|
-
const task = options.task ?? positionalTask;
|
|
1556
|
-
if (!task) {
|
|
1557
|
-
process.stderr.write("Error: task is required. Pass as argument or use --task.\n");
|
|
1558
|
-
process.exit(1);
|
|
1559
|
-
}
|
|
1560
|
-
const config = options.config !== false ? await loadConfig() : void 0;
|
|
1561
|
-
const merged = mergeWithConfig(options, config);
|
|
1562
|
-
if (options.pretty) {
|
|
1563
|
-
ink.render(React3__default.default.createElement(RunApp, { task, options }));
|
|
1564
|
-
} else {
|
|
1565
|
-
try {
|
|
1566
|
-
await runAgent(task, { ...options, provider: merged.provider, model: merged.model });
|
|
1567
|
-
} catch (err) {
|
|
1568
|
-
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
|
|
1569
|
-
`);
|
|
1570
|
-
process.exit(1);
|
|
1571
|
-
}
|
|
1572
|
-
}
|
|
1573
|
-
});
|
|
1574
|
-
program.command("init").description("Generate a starter project. Run with no flags for interactive mode.").option("--template <template>", "Starter template (react|ink|runtime|multi-agent)").option("--dir <directory>", "Target directory", "agentskit-app").option("--provider <provider>", "LLM provider (openai|anthropic|gemini|ollama|demo)").option("--tools <tools>", "Comma-separated tools (web_search,filesystem,shell)").option("--memory <backend>", "Memory backend (none|file|sqlite)").option("--pm <packageManager>", "Package manager (pnpm|npm|yarn|bun)").option("-y, --yes", "Skip interactive prompts; use flag values + defaults").action(async (rawOptions) => {
|
|
1575
|
-
const isCi = !process.stdout.isTTY || rawOptions.yes || rawOptions.template;
|
|
1576
|
-
let resolved;
|
|
1577
|
-
if (isCi) {
|
|
1578
|
-
const template = rawOptions.template ?? "react";
|
|
1579
|
-
resolved = {
|
|
1580
|
-
targetDir: path__default.default.resolve(process.cwd(), rawOptions.dir),
|
|
1581
|
-
template,
|
|
1582
|
-
provider: rawOptions.provider ?? "demo",
|
|
1583
|
-
tools: rawOptions.tools ? rawOptions.tools.split(",").map((t) => t.trim()) : [],
|
|
1584
|
-
memory: rawOptions.memory ?? "none",
|
|
1585
|
-
packageManager: rawOptions.pm ?? "pnpm"
|
|
1586
|
-
};
|
|
1587
|
-
} else {
|
|
1588
|
-
const result = await runInteractiveInit({
|
|
1589
|
-
dir: rawOptions.dir,
|
|
1590
|
-
template: rawOptions.template
|
|
1591
|
-
});
|
|
1592
|
-
if (result.cancelled) {
|
|
1593
|
-
process.exit(0);
|
|
1594
|
-
}
|
|
1595
|
-
resolved = result.options;
|
|
1596
|
-
}
|
|
1597
|
-
await writeStarterProject(resolved);
|
|
1598
|
-
if (isCi) {
|
|
1599
|
-
process.stdout.write(
|
|
1600
|
-
`Created ${resolved.template} starter in ${path__default.default.relative(process.cwd(), resolved.targetDir) || "."}
|
|
1601
|
-
`
|
|
1602
|
-
);
|
|
1603
|
-
} else {
|
|
1604
|
-
printNextSteps(resolved);
|
|
1605
|
-
}
|
|
1606
|
-
});
|
|
1607
|
-
program.command("doctor").description("Diagnose your AgentsKit environment.").option("--no-network", "Skip provider reachability checks").option(
|
|
1608
|
-
"--providers <providers>",
|
|
1609
|
-
"Comma-separated providers to check (default: openai,anthropic,gemini,ollama)"
|
|
1610
|
-
).option("--json", "Emit JSON instead of formatted output").action(async (options) => {
|
|
1611
|
-
const providers2 = options.providers ? options.providers.split(",").map((p) => p.trim()).filter(Boolean) : void 0;
|
|
1612
|
-
const report = await runDoctor({
|
|
1613
|
-
providers: providers2,
|
|
1614
|
-
noNetwork: options.network === false
|
|
1615
|
-
});
|
|
1616
|
-
if (options.json) {
|
|
1617
|
-
process.stdout.write(JSON.stringify(report, null, 2) + "\n");
|
|
1618
|
-
} else {
|
|
1619
|
-
process.stdout.write(renderReport(report, { color: process.stdout.isTTY }));
|
|
1620
|
-
}
|
|
1621
|
-
if (report.fail > 0) process.exit(1);
|
|
1622
|
-
});
|
|
1623
|
-
program.command("dev [entry]").description("Run an entry file with hot-reload on file changes.").option("--watch <globs>", "Comma-separated glob patterns to watch").option("--ignore <globs>", "Comma-separated glob patterns to ignore").option("--debounce <ms>", "Debounce window before restart", "200").action(async (positional, options) => {
|
|
1624
|
-
const entry = positional ?? "src/index.ts";
|
|
1625
|
-
const watch = options.watch ? options.watch.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
1626
|
-
const ignore = options.ignore ? options.ignore.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
1627
|
-
try {
|
|
1628
|
-
const controller = startDev({
|
|
1629
|
-
entry,
|
|
1630
|
-
watch,
|
|
1631
|
-
ignore,
|
|
1632
|
-
debounceMs: Number(options.debounce) || 200
|
|
1633
|
-
});
|
|
1634
|
-
await controller.done;
|
|
1635
|
-
} catch (err) {
|
|
1636
|
-
process.stderr.write(`Error: ${err.message}
|
|
1637
|
-
`);
|
|
1638
|
-
process.exit(1);
|
|
1639
|
-
}
|
|
1640
|
-
});
|
|
2820
|
+
// src/commands/tunnel.ts
|
|
2821
|
+
function registerTunnelCommand(program) {
|
|
1641
2822
|
program.command("tunnel <port>").description("Open a public URL pointing to a local port (great for webhooks).").option("--subdomain <name>", "Hint for a stable subdomain (provider may decline)").option("--host <host>", "Local hostname", "localhost").action(async (port, options) => {
|
|
1642
2823
|
const portNum = Number(port);
|
|
1643
2824
|
if (Number.isNaN(portNum) || portNum < 1 || portNum > 65535) {
|
|
@@ -1658,6 +2839,130 @@ function createCli() {
|
|
|
1658
2839
|
process.exit(1);
|
|
1659
2840
|
}
|
|
1660
2841
|
});
|
|
2842
|
+
}
|
|
2843
|
+
|
|
2844
|
+
// src/extensibility/rag/embedders.ts
|
|
2845
|
+
function createOpenAiEmbedder(config) {
|
|
2846
|
+
const model = config.model ?? "text-embedding-3-small";
|
|
2847
|
+
const baseUrl = (config.baseUrl ?? "https://api.openai.com").replace(/\/$/, "");
|
|
2848
|
+
return async (text) => {
|
|
2849
|
+
const res = await fetch(`${baseUrl}/v1/embeddings`, {
|
|
2850
|
+
method: "POST",
|
|
2851
|
+
headers: {
|
|
2852
|
+
"content-type": "application/json",
|
|
2853
|
+
authorization: `Bearer ${config.apiKey}`
|
|
2854
|
+
},
|
|
2855
|
+
body: JSON.stringify({ model, input: text })
|
|
2856
|
+
});
|
|
2857
|
+
if (!res.ok) {
|
|
2858
|
+
const body = await res.text().catch(() => "");
|
|
2859
|
+
throw new Error(`embedder ${model} HTTP ${res.status}: ${body}`);
|
|
2860
|
+
}
|
|
2861
|
+
const json = await res.json();
|
|
2862
|
+
const first = json.data?.[0]?.embedding;
|
|
2863
|
+
if (!first) throw new Error(`embedder ${model}: response missing data[0].embedding`);
|
|
2864
|
+
return first;
|
|
2865
|
+
};
|
|
2866
|
+
}
|
|
2867
|
+
function resolveEmbedder(config) {
|
|
2868
|
+
const embedder = config.embedder;
|
|
2869
|
+
const provider = embedder?.provider ?? "openai";
|
|
2870
|
+
if (provider !== "openai") {
|
|
2871
|
+
throw new Error(`Unsupported RAG embedder provider: ${provider}. Only "openai" is built-in.`);
|
|
2872
|
+
}
|
|
2873
|
+
const apiKey = embedder?.apiKey ?? process.env.OPENAI_API_KEY ?? process.env.OPENROUTER_API_KEY;
|
|
2874
|
+
if (!apiKey) {
|
|
2875
|
+
throw new Error("RAG embedder needs an API key (config.rag.embedder.apiKey or OPENAI_API_KEY env).");
|
|
2876
|
+
}
|
|
2877
|
+
return createOpenAiEmbedder({
|
|
2878
|
+
apiKey,
|
|
2879
|
+
model: embedder?.model,
|
|
2880
|
+
baseUrl: embedder?.baseUrl
|
|
2881
|
+
});
|
|
2882
|
+
}
|
|
2883
|
+
function buildRagFromConfig(options) {
|
|
2884
|
+
const cwd = options.cwd ?? process.cwd();
|
|
2885
|
+
const dir = path.resolve(cwd, options.config.dir ?? "./.agentskit-rag");
|
|
2886
|
+
const store = memory.fileVectorMemory({ path: `${dir}/store.json` });
|
|
2887
|
+
const embed = options.embedder ?? resolveEmbedder(options.config);
|
|
2888
|
+
return rag.createRAG({
|
|
2889
|
+
embed,
|
|
2890
|
+
store,
|
|
2891
|
+
chunkSize: options.config.chunkSize,
|
|
2892
|
+
topK: options.config.topK
|
|
2893
|
+
});
|
|
2894
|
+
}
|
|
2895
|
+
async function indexSources(rag, config, cwd) {
|
|
2896
|
+
const root = process.cwd();
|
|
2897
|
+
const sources = config.sources ?? [];
|
|
2898
|
+
const absolutePaths = [];
|
|
2899
|
+
for (const pattern of sources) {
|
|
2900
|
+
for await (const match of promises.glob(pattern, { cwd: root })) {
|
|
2901
|
+
absolutePaths.push(path.resolve(root, match));
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2904
|
+
const documents = await Promise.all(
|
|
2905
|
+
absolutePaths.map(async (path5) => ({
|
|
2906
|
+
id: path5,
|
|
2907
|
+
content: await promises.readFile(path5, "utf8"),
|
|
2908
|
+
source: path5
|
|
2909
|
+
}))
|
|
2910
|
+
);
|
|
2911
|
+
if (documents.length > 0) await rag.ingest(documents);
|
|
2912
|
+
return { documentCount: documents.length, sources: absolutePaths };
|
|
2913
|
+
}
|
|
2914
|
+
|
|
2915
|
+
// src/commands/rag.ts
|
|
2916
|
+
function registerRagCommand(program) {
|
|
2917
|
+
const rag = program.command("rag").description("Retrieval-augmented generation utilities.");
|
|
2918
|
+
rag.command("index").description("Index files referenced by config.rag.sources into the vector store.").option(
|
|
2919
|
+
"--source <glob>",
|
|
2920
|
+
"Glob to index (overrides config.rag.sources; repeatable)",
|
|
2921
|
+
(value, prev = []) => [...prev, value],
|
|
2922
|
+
[]
|
|
2923
|
+
).action(async (options) => {
|
|
2924
|
+
const config = await loadConfig();
|
|
2925
|
+
const rawConfig = config?.rag;
|
|
2926
|
+
const overrideSources = options.source;
|
|
2927
|
+
const ragConfig = {
|
|
2928
|
+
...rawConfig ?? {},
|
|
2929
|
+
sources: overrideSources.length > 0 ? overrideSources : rawConfig?.sources ?? []
|
|
2930
|
+
};
|
|
2931
|
+
if (!ragConfig.sources || ragConfig.sources.length === 0) {
|
|
2932
|
+
process.stderr.write("No RAG sources configured. Set config.rag.sources or pass --source <glob>.\n");
|
|
2933
|
+
process.exit(1);
|
|
2934
|
+
}
|
|
2935
|
+
try {
|
|
2936
|
+
const instance = buildRagFromConfig({ config: ragConfig });
|
|
2937
|
+
const result = await indexSources(instance, ragConfig);
|
|
2938
|
+
process.stdout.write(
|
|
2939
|
+
`Indexed ${result.documentCount} document${result.documentCount === 1 ? "" : "s"}.
|
|
2940
|
+
`
|
|
2941
|
+
);
|
|
2942
|
+
for (const source of result.sources) {
|
|
2943
|
+
process.stdout.write(` \u2022 ${source}
|
|
2944
|
+
`);
|
|
2945
|
+
}
|
|
2946
|
+
} catch (err) {
|
|
2947
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
|
|
2948
|
+
`);
|
|
2949
|
+
process.exit(1);
|
|
2950
|
+
}
|
|
2951
|
+
});
|
|
2952
|
+
}
|
|
2953
|
+
|
|
2954
|
+
// src/commands/index.ts
|
|
2955
|
+
function createCli() {
|
|
2956
|
+
const program = new commander.Command();
|
|
2957
|
+
program.name("agentskit").description("AgentsKit CLI for chat demos and project bootstrapping.");
|
|
2958
|
+
registerChatCommand(program);
|
|
2959
|
+
registerRunCommand(program);
|
|
2960
|
+
registerInitCommand(program);
|
|
2961
|
+
registerDoctorCommand(program);
|
|
2962
|
+
registerDevCommand(program);
|
|
2963
|
+
registerConfigCommand(program);
|
|
2964
|
+
registerTunnelCommand(program);
|
|
2965
|
+
registerRagCommand(program);
|
|
1661
2966
|
return program;
|
|
1662
2967
|
}
|
|
1663
2968
|
|