@agentskit/cli 0.6.0 → 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 +2063 -1271
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-V7E4HWTG.js → chunk-72XFU2X2.js} +1232 -433
- package/dist/chunk-72XFU2X2.js.map +1 -0
- package/dist/index.cjs +2096 -1270
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +420 -2
- package/dist/index.d.ts +420 -2
- package/dist/index.js +1 -1
- package/package.json +4 -3
- package/dist/chunk-V7E4HWTG.js.map +0 -1
|
@@ -1,35 +1,37 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { mkdir, writeFile, readFile } from 'fs/promises';
|
|
2
|
+
import { readdir, stat, mkdir, writeFile, glob, readFile } from 'fs/promises';
|
|
3
3
|
import { homedir } from 'os';
|
|
4
|
-
import
|
|
4
|
+
import path, { join, resolve, isAbsolute, basename } from 'path';
|
|
5
5
|
import { kimi, grok, deepseek, ollama, gemini, anthropic, openai } from '@agentskit/adapters';
|
|
6
|
-
import
|
|
6
|
+
import { randomBytes, createHash } from 'crypto';
|
|
7
|
+
import { writeFileSync, existsSync, readdirSync, statSync, readFileSync, mkdirSync } from 'fs';
|
|
8
|
+
import { spawn } from 'child_process';
|
|
9
|
+
import React2, { useMemo, useState, useEffect, useRef } from 'react';
|
|
7
10
|
import { Box, Text, render } from 'ink';
|
|
8
11
|
import { useChat, StatusHeader, ChatContainer, Message, ToolCallView, ToolConfirmation, ThinkingIndicator, InputBar } from '@agentskit/ink';
|
|
9
12
|
import { shell, filesystem, fetchUrl, webSearch } from '@agentskit/tools';
|
|
10
13
|
import { summarizer, critic, planner, coder, researcher, composeSkills } from '@agentskit/skills';
|
|
11
|
-
import { fileChatMemory, sqliteChatMemory } from '@agentskit/memory';
|
|
12
|
-
import { randomBytes, createHash } from 'crypto';
|
|
13
|
-
import { existsSync, mkdirSync, writeFileSync, readdirSync, statSync, readFileSync } from 'fs';
|
|
14
|
+
import { fileVectorMemory, fileChatMemory, sqliteChatMemory } from '@agentskit/memory';
|
|
14
15
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
16
|
+
import { pathToFileURL } from 'url';
|
|
15
17
|
import { createRuntime } from '@agentskit/runtime';
|
|
16
|
-
import { spawn } from 'child_process';
|
|
17
18
|
import chokidar from 'chokidar';
|
|
18
19
|
import kleur3 from 'kleur';
|
|
20
|
+
import { createRAG } from '@agentskit/rag';
|
|
19
21
|
import { Command } from 'commander';
|
|
20
22
|
import { input, select, checkbox, confirm } from '@inquirer/prompts';
|
|
21
23
|
|
|
22
|
-
async function loadJsonConfig(
|
|
24
|
+
async function loadJsonConfig(path5) {
|
|
23
25
|
try {
|
|
24
|
-
const raw = await readFile(
|
|
26
|
+
const raw = await readFile(path5, "utf8");
|
|
25
27
|
return JSON.parse(raw);
|
|
26
28
|
} catch {
|
|
27
29
|
return void 0;
|
|
28
30
|
}
|
|
29
31
|
}
|
|
30
|
-
async function loadTsConfig(
|
|
32
|
+
async function loadTsConfig(path5) {
|
|
31
33
|
try {
|
|
32
|
-
const mod = await import(
|
|
34
|
+
const mod = await import(path5);
|
|
33
35
|
return mod.default ?? mod;
|
|
34
36
|
} catch {
|
|
35
37
|
return void 0;
|
|
@@ -139,7 +141,7 @@ function createDemoAdapter(provider, model) {
|
|
|
139
141
|
].join(" ");
|
|
140
142
|
for (const chunk of reply.match(/.{1,18}/g) ?? []) {
|
|
141
143
|
if (cancelled) return;
|
|
142
|
-
await new Promise((
|
|
144
|
+
await new Promise((resolve4) => setTimeout(resolve4, 35));
|
|
143
145
|
yield { type: "text", content: chunk };
|
|
144
146
|
}
|
|
145
147
|
yield { type: "done" };
|
|
@@ -190,80 +192,6 @@ function resolveChatProvider(options) {
|
|
|
190
192
|
summary: `${entry.label} live adapter`
|
|
191
193
|
};
|
|
192
194
|
}
|
|
193
|
-
var skillRegistry = {
|
|
194
|
-
researcher,
|
|
195
|
-
coder,
|
|
196
|
-
planner,
|
|
197
|
-
critic,
|
|
198
|
-
summarizer
|
|
199
|
-
};
|
|
200
|
-
function instantiate(kind) {
|
|
201
|
-
switch (kind) {
|
|
202
|
-
case "web_search":
|
|
203
|
-
return [webSearch()];
|
|
204
|
-
case "fetch_url":
|
|
205
|
-
return [fetchUrl()];
|
|
206
|
-
case "filesystem":
|
|
207
|
-
return filesystem({ basePath: process.cwd() });
|
|
208
|
-
case "shell":
|
|
209
|
-
return [shell({ timeout: 3e4 })];
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
function gateTool(tool) {
|
|
213
|
-
if (tool.requiresConfirmation === false) return tool;
|
|
214
|
-
return { ...tool, requiresConfirmation: true };
|
|
215
|
-
}
|
|
216
|
-
function resolveTools(toolNames) {
|
|
217
|
-
if (!toolNames) {
|
|
218
|
-
return [...instantiate("web_search"), ...instantiate("fetch_url")].map(gateTool);
|
|
219
|
-
}
|
|
220
|
-
const tools = [];
|
|
221
|
-
for (const name of toolNames.split(",").map((s) => s.trim()).filter(Boolean)) {
|
|
222
|
-
switch (name) {
|
|
223
|
-
case "web_search":
|
|
224
|
-
case "fetch_url":
|
|
225
|
-
case "filesystem":
|
|
226
|
-
case "shell":
|
|
227
|
-
tools.push(...instantiate(name));
|
|
228
|
-
break;
|
|
229
|
-
default:
|
|
230
|
-
process.stderr.write(`Unknown tool: ${name}
|
|
231
|
-
`);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
return tools;
|
|
235
|
-
}
|
|
236
|
-
function resolveSkill(skillName) {
|
|
237
|
-
if (!skillName) return void 0;
|
|
238
|
-
const skill = skillRegistry[skillName.trim()];
|
|
239
|
-
if (!skill) {
|
|
240
|
-
process.stderr.write(`Unknown skill: ${skillName}
|
|
241
|
-
`);
|
|
242
|
-
return void 0;
|
|
243
|
-
}
|
|
244
|
-
return skill;
|
|
245
|
-
}
|
|
246
|
-
function resolveSkills(skillNames) {
|
|
247
|
-
if (!skillNames) return void 0;
|
|
248
|
-
const names = skillNames.split(",").map((s) => s.trim());
|
|
249
|
-
const resolved = names.map((n) => skillRegistry[n]).filter(Boolean);
|
|
250
|
-
if (resolved.length === 0) {
|
|
251
|
-
process.stderr.write(`No valid skills found in: ${skillNames}
|
|
252
|
-
`);
|
|
253
|
-
return void 0;
|
|
254
|
-
}
|
|
255
|
-
if (resolved.length === 1) return resolved[0];
|
|
256
|
-
return composeSkills(...resolved);
|
|
257
|
-
}
|
|
258
|
-
function resolveMemory(backend, memoryPath) {
|
|
259
|
-
switch (backend) {
|
|
260
|
-
case "sqlite":
|
|
261
|
-
return sqliteChatMemory({ path: memoryPath.replace(/\.json$/, ".db") });
|
|
262
|
-
case "file":
|
|
263
|
-
default:
|
|
264
|
-
return fileChatMemory(memoryPath);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
195
|
var ROOT = join(homedir(), ".agentskit", "sessions");
|
|
268
196
|
var META_SUFFIX = ".meta.json";
|
|
269
197
|
function cwdHash(cwd = process.cwd()) {
|
|
@@ -288,10 +216,10 @@ function metaPath(id, cwd = process.cwd()) {
|
|
|
288
216
|
return join(dirFor(cwd), `${id}${META_SUFFIX}`);
|
|
289
217
|
}
|
|
290
218
|
function readMeta(id, cwd = process.cwd()) {
|
|
291
|
-
const
|
|
292
|
-
if (!existsSync(
|
|
219
|
+
const path5 = metaPath(id, cwd);
|
|
220
|
+
if (!existsSync(path5)) return null;
|
|
293
221
|
try {
|
|
294
|
-
return JSON.parse(readFileSync(
|
|
222
|
+
return JSON.parse(readFileSync(path5, "utf8"));
|
|
295
223
|
} catch {
|
|
296
224
|
return null;
|
|
297
225
|
}
|
|
@@ -341,11 +269,41 @@ function findLatestSession(cwd = process.cwd()) {
|
|
|
341
269
|
return all[0] ?? null;
|
|
342
270
|
}
|
|
343
271
|
function findSession(id, cwd = process.cwd()) {
|
|
344
|
-
const
|
|
272
|
+
const all = listSessions(cwd);
|
|
273
|
+
const exact = all.find((s) => s.metadata.id === id || s.metadata.label === id);
|
|
345
274
|
if (exact) return exact;
|
|
346
|
-
const prefix =
|
|
275
|
+
const prefix = all.find((s) => s.metadata.id.startsWith(id));
|
|
347
276
|
return prefix ?? null;
|
|
348
277
|
}
|
|
278
|
+
function renameSession(id, label, cwd = process.cwd()) {
|
|
279
|
+
const record = findSession(id, cwd);
|
|
280
|
+
if (!record) throw new Error(`No session matching "${id}".`);
|
|
281
|
+
const next = { ...record.metadata, label, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
282
|
+
writeSessionMeta(next, cwd);
|
|
283
|
+
return next;
|
|
284
|
+
}
|
|
285
|
+
function forkSession(id, cwd = process.cwd()) {
|
|
286
|
+
const record = findSession(id, cwd);
|
|
287
|
+
if (!record) throw new Error(`No session matching "${id}".`);
|
|
288
|
+
const newId = generateSessionId();
|
|
289
|
+
const newFile = sessionFilePath(newId, cwd);
|
|
290
|
+
if (existsSync(record.file)) {
|
|
291
|
+
writeFileSync(newFile, readFileSync(record.file, "utf8"));
|
|
292
|
+
}
|
|
293
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
294
|
+
writeSessionMeta(
|
|
295
|
+
{
|
|
296
|
+
...record.metadata,
|
|
297
|
+
id: newId,
|
|
298
|
+
createdAt: now,
|
|
299
|
+
updatedAt: now,
|
|
300
|
+
forkedFrom: record.metadata.id,
|
|
301
|
+
label: void 0
|
|
302
|
+
},
|
|
303
|
+
cwd
|
|
304
|
+
);
|
|
305
|
+
return { id: newId, file: newFile, isNew: true };
|
|
306
|
+
}
|
|
349
307
|
function resolveSession(input2) {
|
|
350
308
|
const cwd = input2.cwd ?? process.cwd();
|
|
351
309
|
if (input2.explicitPath) {
|
|
@@ -371,6 +329,182 @@ function resolveSession(input2) {
|
|
|
371
329
|
return { id, file: sessionFilePath(id, cwd), isNew: true };
|
|
372
330
|
}
|
|
373
331
|
|
|
332
|
+
// src/extensibility/telemetry/pricing.ts
|
|
333
|
+
var builtinPricing = {
|
|
334
|
+
"gpt-4o": { inputPerM: 2.5, outputPerM: 10 },
|
|
335
|
+
"gpt-4o-mini": { inputPerM: 0.15, outputPerM: 0.6 },
|
|
336
|
+
"gpt-4.1": { inputPerM: 2, outputPerM: 8 },
|
|
337
|
+
"gpt-4.1-mini": { inputPerM: 0.4, outputPerM: 1.6 },
|
|
338
|
+
"claude-opus-4": { inputPerM: 15, outputPerM: 75 },
|
|
339
|
+
"claude-sonnet-4": { inputPerM: 3, outputPerM: 15 },
|
|
340
|
+
"claude-haiku-4": { inputPerM: 0.8, outputPerM: 4 },
|
|
341
|
+
"gemini-2.5-pro": { inputPerM: 1.25, outputPerM: 10 },
|
|
342
|
+
"gemini-2.5-flash": { inputPerM: 0.3, outputPerM: 2.5 }
|
|
343
|
+
};
|
|
344
|
+
var customPricing = {};
|
|
345
|
+
function registerPricing(model, pricing) {
|
|
346
|
+
customPricing[model] = pricing;
|
|
347
|
+
}
|
|
348
|
+
function getPricing(model) {
|
|
349
|
+
if (!model) return void 0;
|
|
350
|
+
if (customPricing[model]) return customPricing[model];
|
|
351
|
+
if (builtinPricing[model]) return builtinPricing[model];
|
|
352
|
+
const short = model.includes("/") ? model.split("/").pop() : model;
|
|
353
|
+
return customPricing[short] ?? builtinPricing[short];
|
|
354
|
+
}
|
|
355
|
+
function computeCost(model, usage) {
|
|
356
|
+
if (!model) return void 0;
|
|
357
|
+
const pricing = getPricing(model);
|
|
358
|
+
if (!pricing) return void 0;
|
|
359
|
+
const inputUsd = usage.promptTokens / 1e6 * pricing.inputPerM;
|
|
360
|
+
const outputUsd = usage.completionTokens / 1e6 * pricing.outputPerM;
|
|
361
|
+
return {
|
|
362
|
+
model,
|
|
363
|
+
inputUsd,
|
|
364
|
+
outputUsd,
|
|
365
|
+
totalUsd: inputUsd + outputUsd
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// src/extensibility/permissions/policy.ts
|
|
370
|
+
var defaultPolicy = {
|
|
371
|
+
mode: "default",
|
|
372
|
+
rules: []
|
|
373
|
+
};
|
|
374
|
+
function evaluatePolicy(policy, toolName) {
|
|
375
|
+
if (policy.mode === "bypassPermissions") return "allow";
|
|
376
|
+
if (policy.mode === "plan") return "ask";
|
|
377
|
+
for (const rule of policy.rules) {
|
|
378
|
+
if (matchesRule(rule, toolName)) return rule.action;
|
|
379
|
+
}
|
|
380
|
+
if (policy.mode === "acceptEdits" && /^(fs_write|edit|write_file)/.test(toolName)) {
|
|
381
|
+
return "allow";
|
|
382
|
+
}
|
|
383
|
+
return "ask";
|
|
384
|
+
}
|
|
385
|
+
function matchesRule(rule, toolName) {
|
|
386
|
+
if (rule.tool instanceof RegExp) return rule.tool.test(toolName);
|
|
387
|
+
const str = rule.tool;
|
|
388
|
+
if (str.startsWith("re:")) return new RegExp(str.slice(3)).test(toolName);
|
|
389
|
+
return str === toolName;
|
|
390
|
+
}
|
|
391
|
+
function applyPolicyToTool(policy, tool) {
|
|
392
|
+
const action = evaluatePolicy(policy, tool.name);
|
|
393
|
+
if (action === "deny") return null;
|
|
394
|
+
if (action === "allow") return { ...tool, requiresConfirmation: false };
|
|
395
|
+
return { ...tool, requiresConfirmation: true };
|
|
396
|
+
}
|
|
397
|
+
function applyPolicyToTools(policy, tools) {
|
|
398
|
+
const out = [];
|
|
399
|
+
for (const tool of tools) {
|
|
400
|
+
const gated = applyPolicyToTool(policy, tool);
|
|
401
|
+
if (gated) out.push(gated);
|
|
402
|
+
}
|
|
403
|
+
return out;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// src/extensibility/hooks/runner.ts
|
|
407
|
+
var HookDispatcher = class {
|
|
408
|
+
constructor(handlers = [], onError = (_h, err) => process.stderr.write(
|
|
409
|
+
`[agentskit] hook error: ${err instanceof Error ? err.message : String(err)}
|
|
410
|
+
`
|
|
411
|
+
)) {
|
|
412
|
+
this.onError = onError;
|
|
413
|
+
this.handlers = /* @__PURE__ */ new Map();
|
|
414
|
+
for (const handler of handlers) this.register(handler);
|
|
415
|
+
}
|
|
416
|
+
register(handler) {
|
|
417
|
+
const list = this.handlers.get(handler.event) ?? [];
|
|
418
|
+
list.push(handler);
|
|
419
|
+
this.handlers.set(handler.event, list);
|
|
420
|
+
}
|
|
421
|
+
async dispatch(event, payload) {
|
|
422
|
+
const list = this.handlers.get(event) ?? [];
|
|
423
|
+
let current = { ...payload, event };
|
|
424
|
+
for (const handler of list) {
|
|
425
|
+
if (!this.matches(handler, current)) continue;
|
|
426
|
+
let result;
|
|
427
|
+
try {
|
|
428
|
+
result = await handler.run(current);
|
|
429
|
+
} catch (err) {
|
|
430
|
+
this.onError(handler, err);
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
if (result.decision === "block") {
|
|
434
|
+
return { payload: current, blocked: true, reason: result.reason };
|
|
435
|
+
}
|
|
436
|
+
if (result.decision === "modify") {
|
|
437
|
+
current = { ...result.payload, event };
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return { payload: current, blocked: false };
|
|
441
|
+
}
|
|
442
|
+
matches(handler, payload) {
|
|
443
|
+
if (!handler.matcher) return true;
|
|
444
|
+
if (typeof handler.matcher === "function") return handler.matcher(payload);
|
|
445
|
+
return handler.matcher.test(String(payload.tool ?? payload.prompt ?? ""));
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
function configHooksToHandlers(config) {
|
|
449
|
+
if (!config) return [];
|
|
450
|
+
const handlers = [];
|
|
451
|
+
for (const [event, entries] of Object.entries(config)) {
|
|
452
|
+
for (const entry of entries) {
|
|
453
|
+
handlers.push({
|
|
454
|
+
event,
|
|
455
|
+
matcher: entry.matcher ? new RegExp(entry.matcher) : void 0,
|
|
456
|
+
run: (payload) => runShellHook(entry, payload)
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
return handlers;
|
|
461
|
+
}
|
|
462
|
+
function runShellHook(entry, payload) {
|
|
463
|
+
return new Promise((resolvePromise) => {
|
|
464
|
+
const timeoutMs = entry.timeout ?? 5e3;
|
|
465
|
+
const child = spawn("sh", ["-c", entry.run], {
|
|
466
|
+
stdio: ["pipe", "pipe", "inherit"]
|
|
467
|
+
});
|
|
468
|
+
let stdout = "";
|
|
469
|
+
child.stdout.on("data", (chunk) => {
|
|
470
|
+
stdout += chunk.toString();
|
|
471
|
+
});
|
|
472
|
+
const timer = setTimeout(() => {
|
|
473
|
+
child.kill("SIGTERM");
|
|
474
|
+
}, timeoutMs);
|
|
475
|
+
child.on("close", (code) => {
|
|
476
|
+
clearTimeout(timer);
|
|
477
|
+
if (code !== 0) {
|
|
478
|
+
resolvePromise({
|
|
479
|
+
decision: "block",
|
|
480
|
+
reason: `shell hook exited with code ${code}`
|
|
481
|
+
});
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
const trimmed = stdout.trim();
|
|
485
|
+
if (!trimmed) {
|
|
486
|
+
resolvePromise({ decision: "continue" });
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
try {
|
|
490
|
+
const parsed = JSON.parse(trimmed);
|
|
491
|
+
resolvePromise(parsed);
|
|
492
|
+
} catch {
|
|
493
|
+
resolvePromise({ decision: "continue" });
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
child.on("error", (err) => {
|
|
497
|
+
clearTimeout(timer);
|
|
498
|
+
resolvePromise({ decision: "block", reason: err.message });
|
|
499
|
+
});
|
|
500
|
+
try {
|
|
501
|
+
child.stdin.write(JSON.stringify(payload));
|
|
502
|
+
child.stdin.end();
|
|
503
|
+
} catch {
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
|
|
374
508
|
// src/slash-commands.ts
|
|
375
509
|
function parseSlashCommand(input2) {
|
|
376
510
|
if (!input2.startsWith("/")) return null;
|
|
@@ -493,6 +627,89 @@ ${lines.join("\n")}`, "info");
|
|
|
493
627
|
ctx.feedback("History cleared.", "success");
|
|
494
628
|
}
|
|
495
629
|
},
|
|
630
|
+
{
|
|
631
|
+
name: "usage",
|
|
632
|
+
description: "Show the cumulative token usage for this session.",
|
|
633
|
+
run(ctx) {
|
|
634
|
+
const usage = ctx.chat.usage;
|
|
635
|
+
if (!usage || usage.totalTokens === 0) {
|
|
636
|
+
ctx.feedback("No usage reported yet for this session.", "info");
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
ctx.feedback(
|
|
640
|
+
`Tokens \u2014 prompt=${usage.promptTokens} completion=${usage.completionTokens} total=${usage.totalTokens}`,
|
|
641
|
+
"info"
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
},
|
|
645
|
+
{
|
|
646
|
+
name: "cost",
|
|
647
|
+
description: "Estimate the cost so far for the current model.",
|
|
648
|
+
run(ctx) {
|
|
649
|
+
const usage = ctx.chat.usage;
|
|
650
|
+
const model = ctx.runtime.model;
|
|
651
|
+
if (!usage || usage.totalTokens === 0) {
|
|
652
|
+
ctx.feedback("No usage reported yet for this session.", "info");
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
const cost = computeCost(model, usage);
|
|
656
|
+
if (!cost) {
|
|
657
|
+
ctx.feedback(
|
|
658
|
+
`No pricing registered for model "${model ?? "unset"}". Register with registerPricing() or provide a known model name.`,
|
|
659
|
+
"warn"
|
|
660
|
+
);
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
ctx.feedback(
|
|
664
|
+
`$${cost.totalUsd.toFixed(4)} total (in=$${cost.inputUsd.toFixed(4)} out=$${cost.outputUsd.toFixed(4)} model=${cost.model})`,
|
|
665
|
+
"info"
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
},
|
|
669
|
+
{
|
|
670
|
+
name: "rename",
|
|
671
|
+
description: "Attach a human-readable label to the current session.",
|
|
672
|
+
usage: "/rename <label>",
|
|
673
|
+
run(ctx, args) {
|
|
674
|
+
const label = args.trim();
|
|
675
|
+
const sessionId = ctx.runtime.sessionId;
|
|
676
|
+
if (!sessionId || sessionId === "custom") {
|
|
677
|
+
ctx.feedback("Rename is only available for managed sessions.", "warn");
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
if (!label) {
|
|
681
|
+
ctx.feedback("Usage: /rename <label>", "warn");
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
try {
|
|
685
|
+
renameSession(sessionId, label);
|
|
686
|
+
ctx.feedback(`Session labeled "${label}".`, "success");
|
|
687
|
+
} catch (err) {
|
|
688
|
+
ctx.feedback(`/rename failed: ${err instanceof Error ? err.message : String(err)}`, "error");
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
},
|
|
692
|
+
{
|
|
693
|
+
name: "fork",
|
|
694
|
+
description: "Branch a copy of the current session. Does not switch to it.",
|
|
695
|
+
run(ctx) {
|
|
696
|
+
const sessionId = ctx.runtime.sessionId;
|
|
697
|
+
if (!sessionId || sessionId === "custom") {
|
|
698
|
+
ctx.feedback("Fork is only available for managed sessions.", "warn");
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
try {
|
|
702
|
+
const result = forkSession(sessionId);
|
|
703
|
+
ctx.feedback(
|
|
704
|
+
`Forked into ${result.id}. Resume with:
|
|
705
|
+
agentskit chat --resume ${result.id}`,
|
|
706
|
+
"success"
|
|
707
|
+
);
|
|
708
|
+
} catch (err) {
|
|
709
|
+
ctx.feedback(`/fork failed: ${err instanceof Error ? err.message : String(err)}`, "error");
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
},
|
|
496
713
|
{
|
|
497
714
|
name: "exit",
|
|
498
715
|
aliases: ["quit", "q"],
|
|
@@ -502,57 +719,127 @@ ${lines.join("\n")}`, "info");
|
|
|
502
719
|
}
|
|
503
720
|
}
|
|
504
721
|
];
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
722
|
+
var skillRegistry = {
|
|
723
|
+
researcher,
|
|
724
|
+
coder,
|
|
725
|
+
planner,
|
|
726
|
+
critic,
|
|
727
|
+
summarizer
|
|
728
|
+
};
|
|
729
|
+
function instantiate(kind) {
|
|
730
|
+
switch (kind) {
|
|
731
|
+
case "web_search":
|
|
732
|
+
return [webSearch()];
|
|
733
|
+
case "fetch_url":
|
|
734
|
+
return [fetchUrl()];
|
|
735
|
+
case "filesystem":
|
|
736
|
+
return filesystem({ basePath: process.cwd() });
|
|
737
|
+
case "shell":
|
|
738
|
+
return [shell({ timeout: 3e4 })];
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
function gateTool(tool) {
|
|
742
|
+
if (tool.requiresConfirmation === false) return tool;
|
|
743
|
+
return { ...tool, requiresConfirmation: true };
|
|
744
|
+
}
|
|
745
|
+
function resolveTools(toolNames) {
|
|
746
|
+
if (!toolNames) {
|
|
747
|
+
return [...instantiate("web_search"), ...instantiate("fetch_url")].map(gateTool);
|
|
748
|
+
}
|
|
749
|
+
const tools = [];
|
|
750
|
+
for (const name of toolNames.split(",").map((s) => s.trim()).filter(Boolean)) {
|
|
751
|
+
switch (name) {
|
|
752
|
+
case "web_search":
|
|
753
|
+
case "fetch_url":
|
|
754
|
+
case "filesystem":
|
|
755
|
+
case "shell":
|
|
756
|
+
tools.push(...instantiate(name));
|
|
757
|
+
break;
|
|
758
|
+
default:
|
|
759
|
+
process.stderr.write(`Unknown tool: ${name}
|
|
760
|
+
`);
|
|
518
761
|
}
|
|
519
762
|
}
|
|
520
|
-
|
|
521
|
-
return turns;
|
|
763
|
+
return tools;
|
|
522
764
|
}
|
|
523
|
-
function
|
|
524
|
-
|
|
525
|
-
const
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
)
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
765
|
+
function resolveSkill(skillName) {
|
|
766
|
+
if (!skillName) return void 0;
|
|
767
|
+
const skill = skillRegistry[skillName.trim()];
|
|
768
|
+
if (!skill) {
|
|
769
|
+
process.stderr.write(`Unknown skill: ${skillName}
|
|
770
|
+
`);
|
|
771
|
+
return void 0;
|
|
772
|
+
}
|
|
773
|
+
return skill;
|
|
774
|
+
}
|
|
775
|
+
function resolveSkills(skillNames) {
|
|
776
|
+
if (!skillNames) return void 0;
|
|
777
|
+
const names = skillNames.split(",").map((s) => s.trim());
|
|
778
|
+
const resolved = names.map((n) => skillRegistry[n]).filter(Boolean);
|
|
779
|
+
if (resolved.length === 0) {
|
|
780
|
+
process.stderr.write(`No valid skills found in: ${skillNames}
|
|
781
|
+
`);
|
|
782
|
+
return void 0;
|
|
783
|
+
}
|
|
784
|
+
if (resolved.length === 1) return resolved[0];
|
|
785
|
+
return composeSkills(...resolved);
|
|
786
|
+
}
|
|
787
|
+
function resolveMemory(backend, memoryPath) {
|
|
788
|
+
switch (backend) {
|
|
789
|
+
case "sqlite":
|
|
790
|
+
return sqliteChatMemory({ path: memoryPath.replace(/\.json$/, ".db") });
|
|
791
|
+
case "file":
|
|
792
|
+
default:
|
|
793
|
+
return fileChatMemory(memoryPath);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// src/runtime/use-runtime.ts
|
|
798
|
+
function useRuntime(options) {
|
|
799
|
+
const [provider, setProvider] = useState(options.provider);
|
|
800
|
+
const [model, setModel] = useState(options.model);
|
|
801
|
+
const [apiKey, setApiKey] = useState(options.apiKey);
|
|
802
|
+
const [baseUrl, setBaseUrl] = useState(options.baseUrl);
|
|
803
|
+
const [toolsFlag, setToolsFlag] = useState(options.tools);
|
|
804
|
+
const [skillFlag, setSkillFlag] = useState(options.skill);
|
|
805
|
+
const runtime = useMemo(
|
|
806
|
+
() => resolveChatProvider({ provider, model, apiKey, baseUrl }),
|
|
807
|
+
[provider, model, apiKey, baseUrl]
|
|
808
|
+
);
|
|
809
|
+
const memory = useMemo(
|
|
810
|
+
() => resolveMemory(options.memoryBackend, options.memoryPath ?? ".agentskit-history.json"),
|
|
811
|
+
[options.memoryPath, options.memoryBackend]
|
|
812
|
+
);
|
|
813
|
+
const tools = useMemo(() => {
|
|
814
|
+
const resolved = resolveTools(toolsFlag);
|
|
815
|
+
if (!options.permissionPolicy) return resolved;
|
|
816
|
+
return applyPolicyToTools(options.permissionPolicy, resolved);
|
|
817
|
+
}, [toolsFlag, options.permissionPolicy]);
|
|
818
|
+
const skills = useMemo(() => {
|
|
819
|
+
if (!skillFlag) return void 0;
|
|
820
|
+
const names = skillFlag.split(",").map((s) => s.trim());
|
|
821
|
+
const resolved = names.map((n) => skillRegistry[n]).filter(Boolean);
|
|
822
|
+
if (resolved.length === 0) return void 0;
|
|
823
|
+
return resolved;
|
|
824
|
+
}, [skillFlag]);
|
|
825
|
+
return {
|
|
826
|
+
runtime,
|
|
827
|
+
memory,
|
|
828
|
+
tools,
|
|
829
|
+
skills,
|
|
830
|
+
state: { provider, model, apiKey, baseUrl, toolsFlag, skillFlag },
|
|
831
|
+
setProvider,
|
|
832
|
+
setModel,
|
|
833
|
+
setApiKey,
|
|
834
|
+
setBaseUrl,
|
|
835
|
+
setToolsFlag,
|
|
836
|
+
setSkillFlag
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
function useToolPermissions(chat) {
|
|
840
|
+
const [sessionAllowed, setSessionAllowed] = useState(/* @__PURE__ */ new Set());
|
|
841
|
+
const autoApprovedRef = useRef(/* @__PURE__ */ new Set());
|
|
842
|
+
useEffect(() => {
|
|
556
843
|
if (sessionAllowed.size === 0) return;
|
|
557
844
|
for (const message of chat.messages) {
|
|
558
845
|
for (const call of message.toolCalls ?? []) {
|
|
@@ -573,9 +860,20 @@ function ChatApp(options) {
|
|
|
573
860
|
autoApprovedRef.current.add(toolCallId);
|
|
574
861
|
void chat.approve(toolCallId);
|
|
575
862
|
};
|
|
863
|
+
const awaitingConfirmation = useMemo(
|
|
864
|
+
() => chat.messages.some(
|
|
865
|
+
(message) => message.toolCalls?.some(
|
|
866
|
+
(call) => call.status === "requires_confirmation" && !sessionAllowed.has(call.name)
|
|
867
|
+
)
|
|
868
|
+
),
|
|
869
|
+
[chat.messages, sessionAllowed]
|
|
870
|
+
);
|
|
871
|
+
return { sessionAllowed, handleApproveAlways, awaitingConfirmation };
|
|
872
|
+
}
|
|
873
|
+
function useSessionMeta(options) {
|
|
576
874
|
const sessionCreatedAtRef = useRef(void 0);
|
|
577
|
-
const messageCount =
|
|
578
|
-
const firstUserContent =
|
|
875
|
+
const messageCount = options.messages.length;
|
|
876
|
+
const firstUserContent = options.messages.find((m) => m.role === "user")?.content ?? "";
|
|
579
877
|
useEffect(() => {
|
|
580
878
|
const sessionId = options.sessionId;
|
|
581
879
|
if (!sessionId || sessionId === "custom") return;
|
|
@@ -589,16 +887,90 @@ function ChatApp(options) {
|
|
|
589
887
|
createdAt: sessionCreatedAtRef.current,
|
|
590
888
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
591
889
|
messageCount,
|
|
592
|
-
preview: derivePreview(
|
|
593
|
-
provider:
|
|
594
|
-
model:
|
|
890
|
+
preview: derivePreview(options.messages),
|
|
891
|
+
provider: options.provider,
|
|
892
|
+
model: options.model
|
|
595
893
|
});
|
|
596
894
|
} catch {
|
|
597
895
|
}
|
|
598
|
-
}, [options.sessionId, messageCount, firstUserContent,
|
|
896
|
+
}, [options.sessionId, messageCount, firstUserContent, options.provider, options.model]);
|
|
897
|
+
}
|
|
898
|
+
function groupIntoTurns(messages) {
|
|
899
|
+
const turns = [];
|
|
900
|
+
let current = [];
|
|
901
|
+
for (const message of messages) {
|
|
902
|
+
if (message.role === "user") {
|
|
903
|
+
if (current.length > 0) turns.push(current);
|
|
904
|
+
current = [message];
|
|
905
|
+
} else if (message.role === "system") {
|
|
906
|
+
if (current.length > 0) turns.push(current);
|
|
907
|
+
turns.push([message]);
|
|
908
|
+
current = [];
|
|
909
|
+
} else {
|
|
910
|
+
current.push(message);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
if (current.length > 0) turns.push(current);
|
|
914
|
+
return turns;
|
|
915
|
+
}
|
|
916
|
+
function ChatApp(options) {
|
|
917
|
+
const {
|
|
918
|
+
runtime,
|
|
919
|
+
memory,
|
|
920
|
+
tools,
|
|
921
|
+
skills,
|
|
922
|
+
state: { baseUrl, toolsFlag, skillFlag },
|
|
923
|
+
setProvider,
|
|
924
|
+
setModel,
|
|
925
|
+
setApiKey,
|
|
926
|
+
setBaseUrl,
|
|
927
|
+
setToolsFlag,
|
|
928
|
+
setSkillFlag
|
|
929
|
+
} = useRuntime(options);
|
|
930
|
+
const mergedTools = useMemo(() => {
|
|
931
|
+
const extra = options.extraTools ?? [];
|
|
932
|
+
return [...tools, ...extra];
|
|
933
|
+
}, [tools, options.extraTools]);
|
|
934
|
+
const mergedSkills = useMemo(() => {
|
|
935
|
+
const extra = options.extraSkills ?? [];
|
|
936
|
+
if (!skills && extra.length === 0) return void 0;
|
|
937
|
+
return [...skills ?? [], ...extra];
|
|
938
|
+
}, [skills, options.extraSkills]);
|
|
939
|
+
const chat = useChat({
|
|
940
|
+
adapter: runtime.adapter,
|
|
941
|
+
memory,
|
|
942
|
+
systemPrompt: options.system,
|
|
943
|
+
tools: mergedTools.length > 0 ? mergedTools : void 0,
|
|
944
|
+
skills: mergedSkills
|
|
945
|
+
});
|
|
946
|
+
const { sessionAllowed, handleApproveAlways, awaitingConfirmation } = useToolPermissions(chat);
|
|
947
|
+
useSessionMeta({
|
|
948
|
+
sessionId: options.sessionId,
|
|
949
|
+
messages: chat.messages,
|
|
950
|
+
provider: runtime.provider,
|
|
951
|
+
model: runtime.model
|
|
952
|
+
});
|
|
599
953
|
const turns = useMemo(() => groupIntoTurns(chat.messages), [chat.messages]);
|
|
600
954
|
const toolNames = toolsFlag ? toolsFlag.split(",").map((s) => s.trim()).filter(Boolean) : [];
|
|
601
955
|
const [feedback, setFeedback] = useState(null);
|
|
956
|
+
const hookDispatcher = useMemo(
|
|
957
|
+
() => new HookDispatcher(options.hookHandlers ?? []),
|
|
958
|
+
[options.hookHandlers]
|
|
959
|
+
);
|
|
960
|
+
useEffect(() => {
|
|
961
|
+
void hookDispatcher.dispatch("SessionStart", {
|
|
962
|
+
event: "SessionStart",
|
|
963
|
+
sessionId: options.sessionId,
|
|
964
|
+
provider: runtime.provider,
|
|
965
|
+
model: runtime.model
|
|
966
|
+
});
|
|
967
|
+
return () => {
|
|
968
|
+
void hookDispatcher.dispatch("SessionEnd", {
|
|
969
|
+
event: "SessionEnd",
|
|
970
|
+
sessionId: options.sessionId
|
|
971
|
+
});
|
|
972
|
+
};
|
|
973
|
+
}, [hookDispatcher]);
|
|
602
974
|
const slashCommands = useMemo(
|
|
603
975
|
() => [...builtinSlashCommands, ...options.slashCommands ?? []],
|
|
604
976
|
[options.slashCommands]
|
|
@@ -607,6 +979,17 @@ function ChatApp(options) {
|
|
|
607
979
|
const handleSubmitInput = async (raw) => {
|
|
608
980
|
const parsed = parseSlashCommand(raw);
|
|
609
981
|
if (!parsed) {
|
|
982
|
+
const hookResult = await hookDispatcher.dispatch("UserPromptSubmit", {
|
|
983
|
+
event: "UserPromptSubmit",
|
|
984
|
+
prompt: raw
|
|
985
|
+
});
|
|
986
|
+
if (hookResult.blocked) {
|
|
987
|
+
setFeedback({
|
|
988
|
+
message: `Prompt blocked: ${hookResult.reason ?? "hook refused"}`,
|
|
989
|
+
kind: "warn"
|
|
990
|
+
});
|
|
991
|
+
return true;
|
|
992
|
+
}
|
|
610
993
|
setFeedback(null);
|
|
611
994
|
return false;
|
|
612
995
|
}
|
|
@@ -626,7 +1009,8 @@ function ChatApp(options) {
|
|
|
626
1009
|
mode: runtime.mode,
|
|
627
1010
|
baseUrl,
|
|
628
1011
|
tools: toolsFlag,
|
|
629
|
-
skill: skillFlag
|
|
1012
|
+
skill: skillFlag,
|
|
1013
|
+
sessionId: options.sessionId
|
|
630
1014
|
},
|
|
631
1015
|
setProvider,
|
|
632
1016
|
setModel,
|
|
@@ -645,14 +1029,6 @@ function ChatApp(options) {
|
|
|
645
1029
|
}
|
|
646
1030
|
return true;
|
|
647
1031
|
};
|
|
648
|
-
const awaitingConfirmation = useMemo(
|
|
649
|
-
() => chat.messages.some(
|
|
650
|
-
(message) => message.toolCalls?.some(
|
|
651
|
-
(call) => call.status === "requires_confirmation" && !sessionAllowed.has(call.name)
|
|
652
|
-
)
|
|
653
|
-
),
|
|
654
|
-
[chat.messages, sessionAllowed]
|
|
655
|
-
);
|
|
656
1032
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 1, children: [
|
|
657
1033
|
/* @__PURE__ */ jsx(
|
|
658
1034
|
StatusHeader,
|
|
@@ -743,6 +1119,291 @@ function renderChatHeader(options) {
|
|
|
743
1119
|
if (options.memoryBackend) parts.push(`memory=${options.memoryBackend}`);
|
|
744
1120
|
return parts.join(" ");
|
|
745
1121
|
}
|
|
1122
|
+
async function loadPlugins(options = {}) {
|
|
1123
|
+
const {
|
|
1124
|
+
specs = [],
|
|
1125
|
+
pluginDirs = [],
|
|
1126
|
+
cwd = process.cwd(),
|
|
1127
|
+
autoDiscoverUserDir = true,
|
|
1128
|
+
onError = (spec, err) => process.stderr.write(
|
|
1129
|
+
`[agentskit] plugin "${spec}" failed to load: ${err instanceof Error ? err.message : String(err)}
|
|
1130
|
+
`
|
|
1131
|
+
),
|
|
1132
|
+
log = () => {
|
|
1133
|
+
}
|
|
1134
|
+
} = options;
|
|
1135
|
+
const resolvedSpecs = [...specs];
|
|
1136
|
+
const discoveryDirs = [...pluginDirs];
|
|
1137
|
+
if (autoDiscoverUserDir) discoveryDirs.push(join(homedir(), ".agentskit", "plugins"));
|
|
1138
|
+
for (const dir of discoveryDirs) {
|
|
1139
|
+
const discovered = await discoverPluginsInDir(dir);
|
|
1140
|
+
resolvedSpecs.push(...discovered);
|
|
1141
|
+
}
|
|
1142
|
+
const plugins = [];
|
|
1143
|
+
for (const spec of resolvedSpecs) {
|
|
1144
|
+
try {
|
|
1145
|
+
const plugin = await loadPluginFromSpec(spec, cwd, log);
|
|
1146
|
+
if (plugin) plugins.push(plugin);
|
|
1147
|
+
} catch (err) {
|
|
1148
|
+
onError(spec, err);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
return mergePluginsIntoBundle(plugins);
|
|
1152
|
+
}
|
|
1153
|
+
async function discoverPluginsInDir(dir) {
|
|
1154
|
+
try {
|
|
1155
|
+
const entries = await readdir(dir);
|
|
1156
|
+
const absolutes = entries.filter((name) => /\.(m?js|ts)$/i.test(name)).map((name) => join(dir, name));
|
|
1157
|
+
const validated = [];
|
|
1158
|
+
for (const p of absolutes) {
|
|
1159
|
+
try {
|
|
1160
|
+
const s = await stat(p);
|
|
1161
|
+
if (s.isFile()) validated.push(p);
|
|
1162
|
+
} catch {
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
return validated;
|
|
1166
|
+
} catch {
|
|
1167
|
+
return [];
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
async function loadPluginFromSpec(spec, cwd, log) {
|
|
1171
|
+
const isPath = spec.startsWith("./") || spec.startsWith("../") || isAbsolute(spec);
|
|
1172
|
+
const importTarget = isPath ? pathToFileURL(resolve(cwd, spec)).href : spec;
|
|
1173
|
+
const mod = await import(importTarget);
|
|
1174
|
+
const exported = mod.default ?? mod.plugin ?? mod;
|
|
1175
|
+
const sourcePath = isPath ? resolve(cwd, spec) : void 0;
|
|
1176
|
+
const ctx = {
|
|
1177
|
+
cwd,
|
|
1178
|
+
sourcePath,
|
|
1179
|
+
log: (msg) => log(`[${spec}] ${msg}`)
|
|
1180
|
+
};
|
|
1181
|
+
if (typeof exported === "function") {
|
|
1182
|
+
const factory = exported;
|
|
1183
|
+
return await factory(ctx);
|
|
1184
|
+
}
|
|
1185
|
+
if (exported && typeof exported === "object" && "name" in exported) {
|
|
1186
|
+
const plugin = exported;
|
|
1187
|
+
if (plugin.init) await plugin.init(ctx);
|
|
1188
|
+
return plugin;
|
|
1189
|
+
}
|
|
1190
|
+
throw new Error(
|
|
1191
|
+
"Module did not export a Plugin \u2014 expected default export to be a Plugin object or a PluginFactory function."
|
|
1192
|
+
);
|
|
1193
|
+
}
|
|
1194
|
+
function mergePluginsIntoBundle(plugins) {
|
|
1195
|
+
const bundle = {
|
|
1196
|
+
plugins,
|
|
1197
|
+
slashCommands: [],
|
|
1198
|
+
tools: [],
|
|
1199
|
+
skills: [],
|
|
1200
|
+
providers: {},
|
|
1201
|
+
hooks: [],
|
|
1202
|
+
mcpServers: []
|
|
1203
|
+
};
|
|
1204
|
+
for (const plugin of plugins) {
|
|
1205
|
+
if (plugin.slashCommands) bundle.slashCommands.push(...plugin.slashCommands);
|
|
1206
|
+
if (plugin.tools) bundle.tools.push(...plugin.tools);
|
|
1207
|
+
if (plugin.skills) bundle.skills.push(...plugin.skills);
|
|
1208
|
+
if (plugin.hooks) bundle.hooks.push(...plugin.hooks);
|
|
1209
|
+
if (plugin.mcpServers) bundle.mcpServers.push(...plugin.mcpServers);
|
|
1210
|
+
if (plugin.providers) {
|
|
1211
|
+
for (const [name, factory] of Object.entries(plugin.providers)) {
|
|
1212
|
+
bundle.providers[name] = factory;
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
return bundle;
|
|
1217
|
+
}
|
|
1218
|
+
var McpClient = class {
|
|
1219
|
+
constructor(spec, onError = (err) => process.stderr.write(
|
|
1220
|
+
`[agentskit] mcp[${spec.name}] error: ${err instanceof Error ? err.message : String(err)}
|
|
1221
|
+
`
|
|
1222
|
+
)) {
|
|
1223
|
+
this.spec = spec;
|
|
1224
|
+
this.onError = onError;
|
|
1225
|
+
this.buffer = "";
|
|
1226
|
+
this.nextId = 1;
|
|
1227
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
1228
|
+
this.disposed = false;
|
|
1229
|
+
}
|
|
1230
|
+
async start() {
|
|
1231
|
+
if (this.child) return;
|
|
1232
|
+
const child = spawn(this.spec.command, this.spec.args ?? [], {
|
|
1233
|
+
env: { ...process.env, ...this.spec.env ?? {} },
|
|
1234
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1235
|
+
});
|
|
1236
|
+
this.child = child;
|
|
1237
|
+
child.stdout.on("data", (chunk) => this.onStdout(chunk.toString()));
|
|
1238
|
+
child.stderr.on("data", (chunk) => {
|
|
1239
|
+
process.stderr.write(`[mcp ${this.spec.name}] ${chunk}`);
|
|
1240
|
+
});
|
|
1241
|
+
child.on("error", (err) => this.onError(err));
|
|
1242
|
+
child.on("close", () => {
|
|
1243
|
+
this.disposed = true;
|
|
1244
|
+
for (const pending of this.pending.values()) {
|
|
1245
|
+
pending({ jsonrpc: "2.0", id: 0, error: { code: -1, message: "server closed" } });
|
|
1246
|
+
}
|
|
1247
|
+
this.pending.clear();
|
|
1248
|
+
});
|
|
1249
|
+
await this.request("initialize", {
|
|
1250
|
+
protocolVersion: "2024-11-05",
|
|
1251
|
+
capabilities: {},
|
|
1252
|
+
clientInfo: { name: "agentskit", version: "0" }
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1255
|
+
async listTools() {
|
|
1256
|
+
const res = await this.request("tools/list", {});
|
|
1257
|
+
const tools = res.tools ?? [];
|
|
1258
|
+
return tools;
|
|
1259
|
+
}
|
|
1260
|
+
async callTool(name, args) {
|
|
1261
|
+
return await this.request("tools/call", { name, arguments: args });
|
|
1262
|
+
}
|
|
1263
|
+
dispose() {
|
|
1264
|
+
if (this.disposed) return;
|
|
1265
|
+
this.disposed = true;
|
|
1266
|
+
this.child?.kill("SIGTERM");
|
|
1267
|
+
}
|
|
1268
|
+
request(method, params) {
|
|
1269
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
1270
|
+
if (!this.child || this.disposed) {
|
|
1271
|
+
rejectPromise(new Error(`mcp server ${this.spec.name} not running`));
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1274
|
+
const id = this.nextId++;
|
|
1275
|
+
const timeoutMs = this.spec.timeout ?? 1e4;
|
|
1276
|
+
const timer = setTimeout(() => {
|
|
1277
|
+
this.pending.delete(id);
|
|
1278
|
+
rejectPromise(new Error(`mcp ${this.spec.name}.${method} timed out`));
|
|
1279
|
+
}, timeoutMs);
|
|
1280
|
+
this.pending.set(id, (res) => {
|
|
1281
|
+
clearTimeout(timer);
|
|
1282
|
+
if (res.error) {
|
|
1283
|
+
rejectPromise(new Error(`mcp ${method} failed: ${res.error.message}`));
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
resolvePromise(res.result);
|
|
1287
|
+
});
|
|
1288
|
+
const message = JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n";
|
|
1289
|
+
this.child.stdin.write(message);
|
|
1290
|
+
});
|
|
1291
|
+
}
|
|
1292
|
+
onStdout(chunk) {
|
|
1293
|
+
this.buffer += chunk;
|
|
1294
|
+
let newlineIndex = this.buffer.indexOf("\n");
|
|
1295
|
+
while (newlineIndex !== -1) {
|
|
1296
|
+
const line = this.buffer.slice(0, newlineIndex).trim();
|
|
1297
|
+
this.buffer = this.buffer.slice(newlineIndex + 1);
|
|
1298
|
+
if (line) {
|
|
1299
|
+
try {
|
|
1300
|
+
const parsed = JSON.parse(line);
|
|
1301
|
+
const pending = this.pending.get(Number(parsed.id));
|
|
1302
|
+
if (pending) {
|
|
1303
|
+
this.pending.delete(Number(parsed.id));
|
|
1304
|
+
pending(parsed);
|
|
1305
|
+
}
|
|
1306
|
+
} catch (err) {
|
|
1307
|
+
this.onError(err);
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
newlineIndex = this.buffer.indexOf("\n");
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
};
|
|
1314
|
+
|
|
1315
|
+
// src/extensibility/mcp/bridge.ts
|
|
1316
|
+
async function bridgeMcpServers(specs) {
|
|
1317
|
+
const clients = [];
|
|
1318
|
+
const tools = [];
|
|
1319
|
+
for (const spec of specs) {
|
|
1320
|
+
const client = new McpClient(spec);
|
|
1321
|
+
try {
|
|
1322
|
+
await client.start();
|
|
1323
|
+
const mcpTools = await client.listTools();
|
|
1324
|
+
for (const mcpTool of mcpTools) {
|
|
1325
|
+
tools.push(mcpToolToDefinition(spec.name, client, mcpTool));
|
|
1326
|
+
}
|
|
1327
|
+
clients.push(client);
|
|
1328
|
+
} catch (err) {
|
|
1329
|
+
process.stderr.write(
|
|
1330
|
+
`[agentskit] mcp server "${spec.name}" failed: ${err instanceof Error ? err.message : String(err)}
|
|
1331
|
+
`
|
|
1332
|
+
);
|
|
1333
|
+
client.dispose();
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
return { clients, tools };
|
|
1337
|
+
}
|
|
1338
|
+
function mcpToolToDefinition(serverName, client, tool) {
|
|
1339
|
+
return {
|
|
1340
|
+
name: `${serverName}__${tool.name}`,
|
|
1341
|
+
description: tool.description ?? `MCP tool ${tool.name} from ${serverName}`,
|
|
1342
|
+
schema: tool.inputSchema ?? { type: "object", properties: {} },
|
|
1343
|
+
execute: async (args) => {
|
|
1344
|
+
return await client.callTool(tool.name, args);
|
|
1345
|
+
}
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
function disposeMcpClients(clients) {
|
|
1349
|
+
for (const client of clients) client.dispose();
|
|
1350
|
+
}
|
|
1351
|
+
function formatEvent(event) {
|
|
1352
|
+
switch (event.type) {
|
|
1353
|
+
case "agent:step":
|
|
1354
|
+
return `[step ${event.step}] ${event.action}`;
|
|
1355
|
+
case "llm:start":
|
|
1356
|
+
return `[llm] start (${event.messageCount} messages)`;
|
|
1357
|
+
case "llm:end": {
|
|
1358
|
+
const preview = event.content.length > 100 ? event.content.slice(0, 100) + "..." : event.content;
|
|
1359
|
+
return `[llm] done (${event.durationMs}ms) "${preview}"`;
|
|
1360
|
+
}
|
|
1361
|
+
case "tool:start":
|
|
1362
|
+
return `[tool] ${event.name} ${JSON.stringify(event.args)}`;
|
|
1363
|
+
case "tool:end":
|
|
1364
|
+
return `[tool] ${event.name} done (${event.durationMs}ms)`;
|
|
1365
|
+
case "error":
|
|
1366
|
+
return `[error] ${event.error.message}`;
|
|
1367
|
+
default:
|
|
1368
|
+
return `[${event.type}]`;
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
async function runAgent(task, options) {
|
|
1372
|
+
if (options.skill && options.skills) {
|
|
1373
|
+
process.stderr.write("Error: --skill and --skills are mutually exclusive. Use one or the other.\n");
|
|
1374
|
+
process.exit(1);
|
|
1375
|
+
}
|
|
1376
|
+
const { adapter } = resolveChatProvider({
|
|
1377
|
+
provider: options.provider,
|
|
1378
|
+
model: options.model,
|
|
1379
|
+
apiKey: options.apiKey,
|
|
1380
|
+
baseUrl: options.baseUrl
|
|
1381
|
+
});
|
|
1382
|
+
const tools = resolveTools(options.tools);
|
|
1383
|
+
const skill = options.skills ? resolveSkills(options.skills) : resolveSkill(options.skill);
|
|
1384
|
+
const memory = options.memory ? resolveMemory(options.memoryBackend, options.memory) : void 0;
|
|
1385
|
+
const observers = [];
|
|
1386
|
+
if (options.verbose) {
|
|
1387
|
+
observers.push({
|
|
1388
|
+
name: "cli-verbose",
|
|
1389
|
+
on(event) {
|
|
1390
|
+
process.stderr.write(formatEvent(event) + "\n");
|
|
1391
|
+
}
|
|
1392
|
+
});
|
|
1393
|
+
}
|
|
1394
|
+
const runtime = createRuntime({
|
|
1395
|
+
adapter,
|
|
1396
|
+
tools,
|
|
1397
|
+
memory,
|
|
1398
|
+
systemPrompt: options.systemPrompt,
|
|
1399
|
+
maxSteps: options.maxSteps ? parseInt(options.maxSteps, 10) : void 0,
|
|
1400
|
+
observers
|
|
1401
|
+
});
|
|
1402
|
+
const result = await runtime.run(task, {
|
|
1403
|
+
skill: skill ?? void 0
|
|
1404
|
+
});
|
|
1405
|
+
process.stdout.write(result.content + "\n");
|
|
1406
|
+
}
|
|
746
1407
|
var PROVIDER_IMPORT = {
|
|
747
1408
|
openai: "openai",
|
|
748
1409
|
anthropic: "anthropic",
|
|
@@ -841,7 +1502,7 @@ function reactStarter(ctx) {
|
|
|
841
1502
|
return {
|
|
842
1503
|
"package.json": JSON.stringify(
|
|
843
1504
|
{
|
|
844
|
-
name:
|
|
1505
|
+
name: path.basename(ctx.template === "react" ? "agentskit-react-app" : "agentskit-app"),
|
|
845
1506
|
private: true,
|
|
846
1507
|
type: "module",
|
|
847
1508
|
scripts: {
|
|
@@ -1210,68 +1871,12 @@ async function writeStarterProject(options) {
|
|
|
1210
1871
|
await mkdir(options.targetDir, { recursive: true });
|
|
1211
1872
|
await Promise.all(
|
|
1212
1873
|
Object.entries(files).map(async ([relativePath, content]) => {
|
|
1213
|
-
const absolutePath =
|
|
1214
|
-
await mkdir(
|
|
1874
|
+
const absolutePath = path.join(options.targetDir, relativePath);
|
|
1875
|
+
await mkdir(path.dirname(absolutePath), { recursive: true });
|
|
1215
1876
|
await writeFile(absolutePath, content, "utf8");
|
|
1216
1877
|
})
|
|
1217
1878
|
);
|
|
1218
1879
|
}
|
|
1219
|
-
function formatEvent(event) {
|
|
1220
|
-
switch (event.type) {
|
|
1221
|
-
case "agent:step":
|
|
1222
|
-
return `[step ${event.step}] ${event.action}`;
|
|
1223
|
-
case "llm:start":
|
|
1224
|
-
return `[llm] start (${event.messageCount} messages)`;
|
|
1225
|
-
case "llm:end": {
|
|
1226
|
-
const preview = event.content.length > 100 ? event.content.slice(0, 100) + "..." : event.content;
|
|
1227
|
-
return `[llm] done (${event.durationMs}ms) "${preview}"`;
|
|
1228
|
-
}
|
|
1229
|
-
case "tool:start":
|
|
1230
|
-
return `[tool] ${event.name} ${JSON.stringify(event.args)}`;
|
|
1231
|
-
case "tool:end":
|
|
1232
|
-
return `[tool] ${event.name} done (${event.durationMs}ms)`;
|
|
1233
|
-
case "error":
|
|
1234
|
-
return `[error] ${event.error.message}`;
|
|
1235
|
-
default:
|
|
1236
|
-
return `[${event.type}]`;
|
|
1237
|
-
}
|
|
1238
|
-
}
|
|
1239
|
-
async function runAgent(task, options) {
|
|
1240
|
-
if (options.skill && options.skills) {
|
|
1241
|
-
process.stderr.write("Error: --skill and --skills are mutually exclusive. Use one or the other.\n");
|
|
1242
|
-
process.exit(1);
|
|
1243
|
-
}
|
|
1244
|
-
const { adapter } = resolveChatProvider({
|
|
1245
|
-
provider: options.provider,
|
|
1246
|
-
model: options.model,
|
|
1247
|
-
apiKey: options.apiKey,
|
|
1248
|
-
baseUrl: options.baseUrl
|
|
1249
|
-
});
|
|
1250
|
-
const tools = resolveTools(options.tools);
|
|
1251
|
-
const skill = options.skills ? resolveSkills(options.skills) : resolveSkill(options.skill);
|
|
1252
|
-
const memory = options.memory ? resolveMemory(options.memoryBackend, options.memory) : void 0;
|
|
1253
|
-
const observers = [];
|
|
1254
|
-
if (options.verbose) {
|
|
1255
|
-
observers.push({
|
|
1256
|
-
name: "cli-verbose",
|
|
1257
|
-
on(event) {
|
|
1258
|
-
process.stderr.write(formatEvent(event) + "\n");
|
|
1259
|
-
}
|
|
1260
|
-
});
|
|
1261
|
-
}
|
|
1262
|
-
const runtime = createRuntime({
|
|
1263
|
-
adapter,
|
|
1264
|
-
tools,
|
|
1265
|
-
memory,
|
|
1266
|
-
systemPrompt: options.systemPrompt,
|
|
1267
|
-
maxSteps: options.maxSteps ? parseInt(options.maxSteps, 10) : void 0,
|
|
1268
|
-
observers
|
|
1269
|
-
});
|
|
1270
|
-
const result = await runtime.run(task, {
|
|
1271
|
-
skill: skill ?? void 0
|
|
1272
|
-
});
|
|
1273
|
-
process.stdout.write(result.content + "\n");
|
|
1274
|
-
}
|
|
1275
1880
|
var PROVIDER_ENV_KEYS = {
|
|
1276
1881
|
openai: "OPENAI_API_KEY",
|
|
1277
1882
|
anthropic: "ANTHROPIC_API_KEY",
|
|
@@ -1331,8 +1936,8 @@ async function checkPnpm() {
|
|
|
1331
1936
|
};
|
|
1332
1937
|
}
|
|
1333
1938
|
async function checkPackageJson() {
|
|
1334
|
-
const
|
|
1335
|
-
if (!existsSync(
|
|
1939
|
+
const path5 = join(process.cwd(), "package.json");
|
|
1940
|
+
if (!existsSync(path5)) {
|
|
1336
1941
|
return {
|
|
1337
1942
|
status: "warn",
|
|
1338
1943
|
name: "package.json",
|
|
@@ -1341,7 +1946,7 @@ async function checkPackageJson() {
|
|
|
1341
1946
|
};
|
|
1342
1947
|
}
|
|
1343
1948
|
try {
|
|
1344
|
-
const pkg = JSON.parse(await readFile(
|
|
1949
|
+
const pkg = JSON.parse(await readFile(path5, "utf8"));
|
|
1345
1950
|
const deps = {
|
|
1346
1951
|
...pkg.dependencies ?? {},
|
|
1347
1952
|
...pkg.devDependencies ?? {}
|
|
@@ -1603,11 +2208,11 @@ function startDev(options) {
|
|
|
1603
2208
|
}
|
|
1604
2209
|
});
|
|
1605
2210
|
};
|
|
1606
|
-
const restart = (
|
|
2211
|
+
const restart = (path5) => {
|
|
1607
2212
|
if (restartTimer) clearTimeout(restartTimer);
|
|
1608
2213
|
restartTimer = setTimeout(() => {
|
|
1609
2214
|
restartTimer = void 0;
|
|
1610
|
-
banner(`\u21BB change detected \u2014 ${
|
|
2215
|
+
banner(`\u21BB change detected \u2014 ${path5}`, "yellow");
|
|
1611
2216
|
if (child && !child.killed && child.exitCode === null) {
|
|
1612
2217
|
child.kill("SIGTERM");
|
|
1613
2218
|
}
|
|
@@ -1708,132 +2313,205 @@ async function startTunnel(options) {
|
|
|
1708
2313
|
requests: () => requests
|
|
1709
2314
|
};
|
|
1710
2315
|
}
|
|
1711
|
-
async function runInteractiveInit(defaults = {}) {
|
|
1712
|
-
process.stdout.write(`
|
|
1713
|
-
${kleur3.bold().green("\u25B2")} ${kleur3.bold("agentskit init")}
|
|
1714
|
-
`);
|
|
1715
|
-
process.stdout.write(kleur3.dim(" Generate a starter project \u2014 answer five questions.\n\n"));
|
|
1716
|
-
try {
|
|
1717
|
-
const targetDir = await input({
|
|
1718
|
-
message: "Project directory:",
|
|
1719
|
-
default: defaults.dir ?? "agentskit-app",
|
|
1720
|
-
validate: (value) => {
|
|
1721
|
-
if (!value.trim()) return "A directory name is required.";
|
|
1722
|
-
const abs = path3.resolve(process.cwd(), value);
|
|
1723
|
-
if (existsSync(abs)) return `${value} already exists. Pick a different name.`;
|
|
1724
|
-
return true;
|
|
1725
|
-
}
|
|
1726
|
-
});
|
|
1727
|
-
const template = await select({
|
|
1728
|
-
message: "Template:",
|
|
1729
|
-
default: defaults.template ?? "react",
|
|
1730
|
-
choices: [
|
|
1731
|
-
{ name: "React chat (Vite + browser)", value: "react", description: "Streaming UI with @agentskit/react" },
|
|
1732
|
-
{ name: "Ink chat (terminal UI)", value: "ink", description: "Same chat but in your terminal" },
|
|
1733
|
-
{ name: "Runtime (headless agent, no UI)", value: "runtime", description: "Autonomous task \u2192 result" },
|
|
1734
|
-
{ name: "Multi-agent (planner + delegates)", value: "multi-agent", description: "Supervisor pattern, ready to extend" }
|
|
1735
|
-
]
|
|
1736
|
-
});
|
|
1737
|
-
const provider = await select({
|
|
1738
|
-
message: "LLM provider:",
|
|
1739
|
-
default: "demo",
|
|
1740
|
-
choices: [
|
|
1741
|
-
{ name: "Demo (no API key \u2014 deterministic stub)", value: "demo" },
|
|
1742
|
-
{ name: "OpenAI", value: "openai" },
|
|
1743
|
-
{ name: "Anthropic", value: "anthropic" },
|
|
1744
|
-
{ name: "Gemini", value: "gemini" },
|
|
1745
|
-
{ name: "Ollama (local, no key)", value: "ollama" }
|
|
1746
|
-
]
|
|
1747
|
-
});
|
|
1748
|
-
let tools = [];
|
|
1749
|
-
if (template !== "react") {
|
|
1750
|
-
tools = await checkbox({
|
|
1751
|
-
message: "Tools (space to toggle, enter to confirm):",
|
|
1752
|
-
choices: [
|
|
1753
|
-
{ name: "web_search", value: "web_search" },
|
|
1754
|
-
{ name: "filesystem", value: "filesystem" },
|
|
1755
|
-
{ name: "shell", value: "shell" }
|
|
1756
|
-
]
|
|
1757
|
-
});
|
|
1758
|
-
}
|
|
1759
|
-
const memory = await select({
|
|
1760
|
-
message: "Memory backend:",
|
|
1761
|
-
default: "none",
|
|
1762
|
-
choices: [
|
|
1763
|
-
{ name: "None (stateless)", value: "none" },
|
|
1764
|
-
{ name: "File (JSON on disk)", value: "file" },
|
|
1765
|
-
{ name: "SQLite (better-sqlite3)", value: "sqlite" }
|
|
1766
|
-
]
|
|
1767
|
-
});
|
|
1768
|
-
const packageManager = await select({
|
|
1769
|
-
message: "Package manager:",
|
|
1770
|
-
default: "pnpm",
|
|
1771
|
-
choices: [
|
|
1772
|
-
{ name: "pnpm", value: "pnpm" },
|
|
1773
|
-
{ name: "npm", value: "npm" },
|
|
1774
|
-
{ name: "yarn", value: "yarn" },
|
|
1775
|
-
{ name: "bun", value: "bun" }
|
|
1776
|
-
]
|
|
1777
|
-
});
|
|
1778
|
-
process.stdout.write("\n" + kleur3.dim(" Summary:\n"));
|
|
1779
|
-
process.stdout.write(kleur3.dim(` dir ${targetDir}
|
|
1780
|
-
`));
|
|
1781
|
-
process.stdout.write(kleur3.dim(` template ${template}
|
|
1782
|
-
`));
|
|
1783
|
-
process.stdout.write(kleur3.dim(` provider ${provider}
|
|
1784
|
-
`));
|
|
1785
|
-
if (tools.length) process.stdout.write(kleur3.dim(` tools ${tools.join(", ")}
|
|
1786
|
-
`));
|
|
1787
|
-
process.stdout.write(kleur3.dim(` memory ${memory}
|
|
1788
|
-
`));
|
|
1789
|
-
process.stdout.write(kleur3.dim(` pm ${packageManager}
|
|
1790
2316
|
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
}
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
}
|
|
1807
|
-
};
|
|
1808
|
-
} catch (err) {
|
|
1809
|
-
if (err.name === "ExitPromptError") {
|
|
1810
|
-
process.stdout.write(kleur3.yellow("\nCancelled.\n"));
|
|
1811
|
-
return { cancelled: true, options: { targetDir: "", template: "react" } };
|
|
2317
|
+
// src/extensibility/rag/embedders.ts
|
|
2318
|
+
function createOpenAiEmbedder(config) {
|
|
2319
|
+
const model = config.model ?? "text-embedding-3-small";
|
|
2320
|
+
const baseUrl = (config.baseUrl ?? "https://api.openai.com").replace(/\/$/, "");
|
|
2321
|
+
return async (text) => {
|
|
2322
|
+
const res = await fetch(`${baseUrl}/v1/embeddings`, {
|
|
2323
|
+
method: "POST",
|
|
2324
|
+
headers: {
|
|
2325
|
+
"content-type": "application/json",
|
|
2326
|
+
authorization: `Bearer ${config.apiKey}`
|
|
2327
|
+
},
|
|
2328
|
+
body: JSON.stringify({ model, input: text })
|
|
2329
|
+
});
|
|
2330
|
+
if (!res.ok) {
|
|
2331
|
+
const body = await res.text().catch(() => "");
|
|
2332
|
+
throw new Error(`embedder ${model} HTTP ${res.status}: ${body}`);
|
|
1812
2333
|
}
|
|
1813
|
-
|
|
2334
|
+
const json = await res.json();
|
|
2335
|
+
const first = json.data?.[0]?.embedding;
|
|
2336
|
+
if (!first) throw new Error(`embedder ${model}: response missing data[0].embedding`);
|
|
2337
|
+
return first;
|
|
2338
|
+
};
|
|
2339
|
+
}
|
|
2340
|
+
function resolveEmbedder(config) {
|
|
2341
|
+
const embedder = config.embedder;
|
|
2342
|
+
const provider = embedder?.provider ?? "openai";
|
|
2343
|
+
if (provider !== "openai") {
|
|
2344
|
+
throw new Error(`Unsupported RAG embedder provider: ${provider}. Only "openai" is built-in.`);
|
|
1814
2345
|
}
|
|
2346
|
+
const apiKey = embedder?.apiKey ?? process.env.OPENAI_API_KEY ?? process.env.OPENROUTER_API_KEY;
|
|
2347
|
+
if (!apiKey) {
|
|
2348
|
+
throw new Error("RAG embedder needs an API key (config.rag.embedder.apiKey or OPENAI_API_KEY env).");
|
|
2349
|
+
}
|
|
2350
|
+
return createOpenAiEmbedder({
|
|
2351
|
+
apiKey,
|
|
2352
|
+
model: embedder?.model,
|
|
2353
|
+
baseUrl: embedder?.baseUrl
|
|
2354
|
+
});
|
|
1815
2355
|
}
|
|
1816
|
-
function
|
|
1817
|
-
const
|
|
1818
|
-
const
|
|
1819
|
-
const
|
|
1820
|
-
const
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
2356
|
+
function buildRagFromConfig(options) {
|
|
2357
|
+
const cwd = options.cwd ?? process.cwd();
|
|
2358
|
+
const dir = resolve(cwd, options.config.dir ?? "./.agentskit-rag");
|
|
2359
|
+
const store = fileVectorMemory({ path: `${dir}/store.json` });
|
|
2360
|
+
const embed = options.embedder ?? resolveEmbedder(options.config);
|
|
2361
|
+
return createRAG({
|
|
2362
|
+
embed,
|
|
2363
|
+
store,
|
|
2364
|
+
chunkSize: options.config.chunkSize,
|
|
2365
|
+
topK: options.config.topK
|
|
2366
|
+
});
|
|
2367
|
+
}
|
|
2368
|
+
async function indexSources(rag, config, cwd) {
|
|
2369
|
+
const root = cwd ?? process.cwd();
|
|
2370
|
+
const sources = config.sources ?? [];
|
|
2371
|
+
const absolutePaths = [];
|
|
2372
|
+
for (const pattern of sources) {
|
|
2373
|
+
for await (const match of glob(pattern, { cwd: root })) {
|
|
2374
|
+
absolutePaths.push(resolve(root, match));
|
|
2375
|
+
}
|
|
1832
2376
|
}
|
|
1833
|
-
|
|
2377
|
+
const documents = await Promise.all(
|
|
2378
|
+
absolutePaths.map(async (path5) => ({
|
|
2379
|
+
id: path5,
|
|
2380
|
+
content: await readFile(path5, "utf8"),
|
|
2381
|
+
source: path5
|
|
2382
|
+
}))
|
|
2383
|
+
);
|
|
2384
|
+
if (documents.length > 0) await rag.ingest(documents);
|
|
2385
|
+
return { documentCount: documents.length, sources: absolutePaths };
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2388
|
+
// src/commands/shared.ts
|
|
2389
|
+
function mergeWithConfig(options, config) {
|
|
2390
|
+
if (!config) return options;
|
|
2391
|
+
const d = config.defaults ?? {};
|
|
2392
|
+
const resolvedApiKey = options.apiKey ?? (d.apiKeyEnv ? process.env[d.apiKeyEnv] : void 0) ?? d.apiKey;
|
|
2393
|
+
return {
|
|
2394
|
+
...options,
|
|
2395
|
+
provider: options.provider !== "demo" ? options.provider : d.provider ?? options.provider,
|
|
2396
|
+
model: options.model ?? d.model,
|
|
2397
|
+
apiKey: resolvedApiKey,
|
|
2398
|
+
baseUrl: options.baseUrl ?? d.baseUrl,
|
|
2399
|
+
tools: options.tools ?? d.tools,
|
|
2400
|
+
skill: options.skill ?? d.skill,
|
|
2401
|
+
system: options.system ?? d.system,
|
|
2402
|
+
memoryBackend: options.memoryBackend ?? d.memoryBackend
|
|
2403
|
+
};
|
|
2404
|
+
}
|
|
1834
2405
|
|
|
2406
|
+
// src/commands/chat.ts
|
|
2407
|
+
function registerChatCommand(program) {
|
|
2408
|
+
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(
|
|
2409
|
+
"--plugin-dir <dir>",
|
|
2410
|
+
"Extra directory to auto-discover plugin modules from (repeatable)",
|
|
2411
|
+
(value, prev = []) => [...prev, value],
|
|
2412
|
+
[]
|
|
2413
|
+
).option(
|
|
2414
|
+
"--mode <mode>",
|
|
2415
|
+
"Permission mode: default | plan | acceptEdits | bypassPermissions"
|
|
2416
|
+
).action(async (options) => {
|
|
2417
|
+
if (options.listSessions) {
|
|
2418
|
+
const sessions = listSessions();
|
|
2419
|
+
if (sessions.length === 0) {
|
|
2420
|
+
process.stdout.write("No saved sessions for this directory.\n");
|
|
2421
|
+
return;
|
|
2422
|
+
}
|
|
2423
|
+
for (const s of sessions) {
|
|
2424
|
+
const { id, updatedAt, messageCount, preview, model, label, forkedFrom } = s.metadata;
|
|
2425
|
+
const display = label ? `${label} (${id})` : id;
|
|
2426
|
+
const forkNote = forkedFrom ? ` \u2190 fork ${forkedFrom}` : "";
|
|
2427
|
+
process.stdout.write(
|
|
2428
|
+
`${display} ${updatedAt} msgs=${messageCount}${model ? ` model=${model}` : ""}${forkNote}
|
|
2429
|
+
${preview}
|
|
2430
|
+
`
|
|
2431
|
+
);
|
|
2432
|
+
}
|
|
2433
|
+
return;
|
|
2434
|
+
}
|
|
2435
|
+
const config = options.config !== false ? await loadConfig() : void 0;
|
|
2436
|
+
const merged = mergeWithConfig(options, config);
|
|
2437
|
+
const session = resolveSession({
|
|
2438
|
+
explicitPath: options.memory,
|
|
2439
|
+
forceNew: Boolean(options.new),
|
|
2440
|
+
resumeId: options.resume
|
|
2441
|
+
});
|
|
2442
|
+
if (!session.isNew && !options.memory) {
|
|
2443
|
+
process.stdout.write(
|
|
2444
|
+
`Resuming session ${session.id}. Start fresh with --new or list with --list-sessions.
|
|
2445
|
+
`
|
|
2446
|
+
);
|
|
2447
|
+
}
|
|
2448
|
+
const pluginBundle = await loadPlugins({
|
|
2449
|
+
specs: config?.plugins ?? [],
|
|
2450
|
+
pluginDirs: options.pluginDir ?? []
|
|
2451
|
+
});
|
|
2452
|
+
const configHooks = configHooksToHandlers(config?.hooks);
|
|
2453
|
+
const hookHandlers = [...configHooks, ...pluginBundle.hooks];
|
|
2454
|
+
const configMcpSpecs = Object.entries(config?.mcp?.servers ?? {}).map(([name, spec]) => ({
|
|
2455
|
+
name,
|
|
2456
|
+
command: spec.command,
|
|
2457
|
+
args: spec.args,
|
|
2458
|
+
env: spec.env,
|
|
2459
|
+
timeout: spec.timeout
|
|
2460
|
+
}));
|
|
2461
|
+
const allMcpSpecs = [...configMcpSpecs, ...pluginBundle.mcpServers];
|
|
2462
|
+
const { clients: mcpClients, tools: mcpTools } = await bridgeMcpServers(allMcpSpecs);
|
|
2463
|
+
const policyMode = options.mode ?? config?.permissions?.mode ?? "default";
|
|
2464
|
+
const permissionPolicy = {
|
|
2465
|
+
mode: policyMode,
|
|
2466
|
+
rules: (config?.permissions?.rules ?? []).map((r) => ({
|
|
2467
|
+
tool: r.tool,
|
|
2468
|
+
action: r.action,
|
|
2469
|
+
scope: r.scope
|
|
2470
|
+
}))
|
|
2471
|
+
};
|
|
2472
|
+
const chatOptions = {
|
|
2473
|
+
apiKey: merged.apiKey ?? options.apiKey,
|
|
2474
|
+
baseUrl: merged.baseUrl ?? options.baseUrl,
|
|
2475
|
+
provider: merged.provider,
|
|
2476
|
+
model: merged.model,
|
|
2477
|
+
system: merged.system ?? options.system,
|
|
2478
|
+
memoryPath: session.file,
|
|
2479
|
+
sessionId: session.id,
|
|
2480
|
+
tools: merged.tools ?? options.tools,
|
|
2481
|
+
skill: merged.skill ?? options.skill,
|
|
2482
|
+
memoryBackend: merged.memoryBackend ?? options.memoryBackend,
|
|
2483
|
+
agentsKitConfig: config,
|
|
2484
|
+
slashCommands: pluginBundle.slashCommands,
|
|
2485
|
+
extraTools: [...pluginBundle.tools, ...mcpTools],
|
|
2486
|
+
extraSkills: pluginBundle.skills,
|
|
2487
|
+
hookHandlers,
|
|
2488
|
+
permissionPolicy
|
|
2489
|
+
};
|
|
2490
|
+
process.stdout.write(`${renderChatHeader(chatOptions)}
|
|
1835
2491
|
`);
|
|
1836
|
-
|
|
2492
|
+
const instance = render(React2.createElement(ChatApp, chatOptions));
|
|
2493
|
+
try {
|
|
2494
|
+
await instance.waitUntilExit();
|
|
2495
|
+
} finally {
|
|
2496
|
+
disposeMcpClients(mcpClients);
|
|
2497
|
+
}
|
|
2498
|
+
if (options.memory) {
|
|
2499
|
+
process.stdout.write(
|
|
2500
|
+
`
|
|
2501
|
+
Session saved to ${session.file}. Resume with --memory ${session.file}
|
|
2502
|
+
`
|
|
2503
|
+
);
|
|
2504
|
+
} else {
|
|
2505
|
+
process.stdout.write(
|
|
2506
|
+
`
|
|
2507
|
+
Session saved. Resume with:
|
|
2508
|
+
agentskit chat --resume ${session.id}
|
|
2509
|
+
Or start fresh with:
|
|
2510
|
+
agentskit chat --new
|
|
2511
|
+
`
|
|
2512
|
+
);
|
|
2513
|
+
}
|
|
2514
|
+
});
|
|
1837
2515
|
}
|
|
1838
2516
|
function RunApp({ task, options }) {
|
|
1839
2517
|
const [status, setStatus] = useState("running");
|
|
@@ -1934,91 +2612,8 @@ function RunApp({ task, options }) {
|
|
|
1934
2612
|
] });
|
|
1935
2613
|
}
|
|
1936
2614
|
|
|
1937
|
-
// src/commands.ts
|
|
1938
|
-
function
|
|
1939
|
-
if (!config) return options;
|
|
1940
|
-
const d = config.defaults ?? {};
|
|
1941
|
-
const resolvedApiKey = options.apiKey ?? (d.apiKeyEnv ? process.env[d.apiKeyEnv] : void 0) ?? d.apiKey;
|
|
1942
|
-
return {
|
|
1943
|
-
...options,
|
|
1944
|
-
// Config defaults — only apply if CLI flag wasn't set
|
|
1945
|
-
provider: options.provider !== "demo" ? options.provider : d.provider ?? options.provider,
|
|
1946
|
-
model: options.model ?? d.model,
|
|
1947
|
-
apiKey: resolvedApiKey,
|
|
1948
|
-
baseUrl: options.baseUrl ?? d.baseUrl,
|
|
1949
|
-
tools: options.tools ?? d.tools,
|
|
1950
|
-
skill: options.skill ?? d.skill,
|
|
1951
|
-
system: options.system ?? d.system,
|
|
1952
|
-
memoryBackend: options.memoryBackend ?? d.memoryBackend
|
|
1953
|
-
};
|
|
1954
|
-
}
|
|
1955
|
-
function createCli() {
|
|
1956
|
-
const program = new Command();
|
|
1957
|
-
program.name("agentskit").description("AgentsKit CLI for chat demos and project bootstrapping.");
|
|
1958
|
-
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").action(async (options) => {
|
|
1959
|
-
if (options.listSessions) {
|
|
1960
|
-
const sessions = listSessions();
|
|
1961
|
-
if (sessions.length === 0) {
|
|
1962
|
-
process.stdout.write("No saved sessions for this directory.\n");
|
|
1963
|
-
return;
|
|
1964
|
-
}
|
|
1965
|
-
for (const s of sessions) {
|
|
1966
|
-
const { id, updatedAt, messageCount, preview, model } = s.metadata;
|
|
1967
|
-
process.stdout.write(
|
|
1968
|
-
`${id} ${updatedAt} msgs=${messageCount}${model ? ` model=${model}` : ""}
|
|
1969
|
-
${preview}
|
|
1970
|
-
`
|
|
1971
|
-
);
|
|
1972
|
-
}
|
|
1973
|
-
return;
|
|
1974
|
-
}
|
|
1975
|
-
const config = options.config !== false ? await loadConfig() : void 0;
|
|
1976
|
-
const merged = mergeWithConfig(options, config);
|
|
1977
|
-
const session = resolveSession({
|
|
1978
|
-
explicitPath: options.memory,
|
|
1979
|
-
forceNew: Boolean(options.new),
|
|
1980
|
-
resumeId: options.resume
|
|
1981
|
-
});
|
|
1982
|
-
if (!session.isNew && !options.memory) {
|
|
1983
|
-
process.stdout.write(
|
|
1984
|
-
`Resuming session ${session.id}. Start fresh with --new or list with --list-sessions.
|
|
1985
|
-
`
|
|
1986
|
-
);
|
|
1987
|
-
}
|
|
1988
|
-
const chatOptions = {
|
|
1989
|
-
apiKey: merged.apiKey ?? options.apiKey,
|
|
1990
|
-
baseUrl: merged.baseUrl ?? options.baseUrl,
|
|
1991
|
-
provider: merged.provider,
|
|
1992
|
-
model: merged.model,
|
|
1993
|
-
system: merged.system ?? options.system,
|
|
1994
|
-
memoryPath: session.file,
|
|
1995
|
-
sessionId: session.id,
|
|
1996
|
-
tools: merged.tools ?? options.tools,
|
|
1997
|
-
skill: merged.skill ?? options.skill,
|
|
1998
|
-
memoryBackend: merged.memoryBackend ?? options.memoryBackend,
|
|
1999
|
-
agentsKitConfig: config
|
|
2000
|
-
};
|
|
2001
|
-
process.stdout.write(`${renderChatHeader(chatOptions)}
|
|
2002
|
-
`);
|
|
2003
|
-
const instance = render(React3.createElement(ChatApp, chatOptions));
|
|
2004
|
-
await instance.waitUntilExit();
|
|
2005
|
-
if (options.memory) {
|
|
2006
|
-
process.stdout.write(
|
|
2007
|
-
`
|
|
2008
|
-
Session saved to ${session.file}. Resume with --memory ${session.file}
|
|
2009
|
-
`
|
|
2010
|
-
);
|
|
2011
|
-
} else {
|
|
2012
|
-
process.stdout.write(
|
|
2013
|
-
`
|
|
2014
|
-
Session saved. Resume with:
|
|
2015
|
-
agentskit chat --resume ${session.id}
|
|
2016
|
-
Or start fresh with:
|
|
2017
|
-
agentskit chat --new
|
|
2018
|
-
`
|
|
2019
|
-
);
|
|
2020
|
-
}
|
|
2021
|
-
});
|
|
2615
|
+
// src/commands/run.ts
|
|
2616
|
+
function registerRunCommand(program) {
|
|
2022
2617
|
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) => {
|
|
2023
2618
|
const task = options.task ?? positionalTask;
|
|
2024
2619
|
if (!task) {
|
|
@@ -2028,7 +2623,7 @@ Or start fresh with:
|
|
|
2028
2623
|
const config = options.config !== false ? await loadConfig() : void 0;
|
|
2029
2624
|
const merged = mergeWithConfig(options, config);
|
|
2030
2625
|
if (options.pretty) {
|
|
2031
|
-
render(
|
|
2626
|
+
render(React2.createElement(RunApp, { task, options }));
|
|
2032
2627
|
} else {
|
|
2033
2628
|
try {
|
|
2034
2629
|
await runAgent(task, { ...options, provider: merged.provider, model: merged.model });
|
|
@@ -2039,13 +2634,144 @@ Or start fresh with:
|
|
|
2039
2634
|
}
|
|
2040
2635
|
}
|
|
2041
2636
|
});
|
|
2637
|
+
}
|
|
2638
|
+
async function runInteractiveInit(defaults = {}) {
|
|
2639
|
+
process.stdout.write(`
|
|
2640
|
+
${kleur3.bold().green("\u25B2")} ${kleur3.bold("agentskit init")}
|
|
2641
|
+
`);
|
|
2642
|
+
process.stdout.write(kleur3.dim(" Generate a starter project \u2014 answer five questions.\n\n"));
|
|
2643
|
+
try {
|
|
2644
|
+
const targetDir = await input({
|
|
2645
|
+
message: "Project directory:",
|
|
2646
|
+
default: defaults.dir ?? "agentskit-app",
|
|
2647
|
+
validate: (value) => {
|
|
2648
|
+
if (!value.trim()) return "A directory name is required.";
|
|
2649
|
+
const abs = path.resolve(process.cwd(), value);
|
|
2650
|
+
if (existsSync(abs)) return `${value} already exists. Pick a different name.`;
|
|
2651
|
+
return true;
|
|
2652
|
+
}
|
|
2653
|
+
});
|
|
2654
|
+
const template = await select({
|
|
2655
|
+
message: "Template:",
|
|
2656
|
+
default: defaults.template ?? "react",
|
|
2657
|
+
choices: [
|
|
2658
|
+
{ name: "React chat (Vite + browser)", value: "react", description: "Streaming UI with @agentskit/react" },
|
|
2659
|
+
{ name: "Ink chat (terminal UI)", value: "ink", description: "Same chat but in your terminal" },
|
|
2660
|
+
{ name: "Runtime (headless agent, no UI)", value: "runtime", description: "Autonomous task \u2192 result" },
|
|
2661
|
+
{ name: "Multi-agent (planner + delegates)", value: "multi-agent", description: "Supervisor pattern, ready to extend" }
|
|
2662
|
+
]
|
|
2663
|
+
});
|
|
2664
|
+
const provider = await select({
|
|
2665
|
+
message: "LLM provider:",
|
|
2666
|
+
default: "demo",
|
|
2667
|
+
choices: [
|
|
2668
|
+
{ name: "Demo (no API key \u2014 deterministic stub)", value: "demo" },
|
|
2669
|
+
{ name: "OpenAI", value: "openai" },
|
|
2670
|
+
{ name: "Anthropic", value: "anthropic" },
|
|
2671
|
+
{ name: "Gemini", value: "gemini" },
|
|
2672
|
+
{ name: "Ollama (local, no key)", value: "ollama" }
|
|
2673
|
+
]
|
|
2674
|
+
});
|
|
2675
|
+
let tools = [];
|
|
2676
|
+
if (template !== "react") {
|
|
2677
|
+
tools = await checkbox({
|
|
2678
|
+
message: "Tools (space to toggle, enter to confirm):",
|
|
2679
|
+
choices: [
|
|
2680
|
+
{ name: "web_search", value: "web_search" },
|
|
2681
|
+
{ name: "filesystem", value: "filesystem" },
|
|
2682
|
+
{ name: "shell", value: "shell" }
|
|
2683
|
+
]
|
|
2684
|
+
});
|
|
2685
|
+
}
|
|
2686
|
+
const memory = await select({
|
|
2687
|
+
message: "Memory backend:",
|
|
2688
|
+
default: "none",
|
|
2689
|
+
choices: [
|
|
2690
|
+
{ name: "None (stateless)", value: "none" },
|
|
2691
|
+
{ name: "File (JSON on disk)", value: "file" },
|
|
2692
|
+
{ name: "SQLite (better-sqlite3)", value: "sqlite" }
|
|
2693
|
+
]
|
|
2694
|
+
});
|
|
2695
|
+
const packageManager = await select({
|
|
2696
|
+
message: "Package manager:",
|
|
2697
|
+
default: "pnpm",
|
|
2698
|
+
choices: [
|
|
2699
|
+
{ name: "pnpm", value: "pnpm" },
|
|
2700
|
+
{ name: "npm", value: "npm" },
|
|
2701
|
+
{ name: "yarn", value: "yarn" },
|
|
2702
|
+
{ name: "bun", value: "bun" }
|
|
2703
|
+
]
|
|
2704
|
+
});
|
|
2705
|
+
process.stdout.write("\n" + kleur3.dim(" Summary:\n"));
|
|
2706
|
+
process.stdout.write(kleur3.dim(` dir ${targetDir}
|
|
2707
|
+
`));
|
|
2708
|
+
process.stdout.write(kleur3.dim(` template ${template}
|
|
2709
|
+
`));
|
|
2710
|
+
process.stdout.write(kleur3.dim(` provider ${provider}
|
|
2711
|
+
`));
|
|
2712
|
+
if (tools.length) process.stdout.write(kleur3.dim(` tools ${tools.join(", ")}
|
|
2713
|
+
`));
|
|
2714
|
+
process.stdout.write(kleur3.dim(` memory ${memory}
|
|
2715
|
+
`));
|
|
2716
|
+
process.stdout.write(kleur3.dim(` pm ${packageManager}
|
|
2717
|
+
|
|
2718
|
+
`));
|
|
2719
|
+
const proceed = await confirm({ message: "Generate?", default: true });
|
|
2720
|
+
if (!proceed) {
|
|
2721
|
+
process.stdout.write(kleur3.yellow("Cancelled.\n"));
|
|
2722
|
+
return { cancelled: true, options: { targetDir, template, provider, tools, memory, packageManager } };
|
|
2723
|
+
}
|
|
2724
|
+
return {
|
|
2725
|
+
cancelled: false,
|
|
2726
|
+
options: {
|
|
2727
|
+
targetDir: path.resolve(process.cwd(), targetDir),
|
|
2728
|
+
template,
|
|
2729
|
+
provider,
|
|
2730
|
+
tools,
|
|
2731
|
+
memory,
|
|
2732
|
+
packageManager
|
|
2733
|
+
}
|
|
2734
|
+
};
|
|
2735
|
+
} catch (err) {
|
|
2736
|
+
if (err.name === "ExitPromptError") {
|
|
2737
|
+
process.stdout.write(kleur3.yellow("\nCancelled.\n"));
|
|
2738
|
+
return { cancelled: true, options: { targetDir: "", template: "react" } };
|
|
2739
|
+
}
|
|
2740
|
+
throw err;
|
|
2741
|
+
}
|
|
2742
|
+
}
|
|
2743
|
+
function printNextSteps(options) {
|
|
2744
|
+
const dir = path.relative(process.cwd(), options.targetDir) || ".";
|
|
2745
|
+
const pm = options.packageManager ?? "pnpm";
|
|
2746
|
+
const installCmd = pm === "npm" ? "npm install" : `${pm} install`;
|
|
2747
|
+
const runCmd = pm === "npm" ? "npm run dev" : `${pm} dev`;
|
|
2748
|
+
process.stdout.write("\n" + kleur3.green("\u2713 Created starter at ") + kleur3.bold(dir) + "\n\n");
|
|
2749
|
+
process.stdout.write(kleur3.bold("Next steps:\n\n"));
|
|
2750
|
+
process.stdout.write(` ${kleur3.cyan("cd")} ${dir}
|
|
2751
|
+
`);
|
|
2752
|
+
process.stdout.write(` ${kleur3.cyan(installCmd)}
|
|
2753
|
+
`);
|
|
2754
|
+
if (options.provider && options.provider !== "demo" && options.provider !== "ollama") {
|
|
2755
|
+
process.stdout.write(
|
|
2756
|
+
` ${kleur3.cyan("cp")} .env.example .env ${kleur3.dim("# add your API key")}
|
|
2757
|
+
`
|
|
2758
|
+
);
|
|
2759
|
+
}
|
|
2760
|
+
process.stdout.write(` ${kleur3.cyan(runCmd)}
|
|
2761
|
+
|
|
2762
|
+
`);
|
|
2763
|
+
process.stdout.write(kleur3.dim(" Docs: https://www.agentskit.io/docs\n\n"));
|
|
2764
|
+
}
|
|
2765
|
+
|
|
2766
|
+
// src/commands/init.ts
|
|
2767
|
+
function registerInitCommand(program) {
|
|
2042
2768
|
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) => {
|
|
2043
2769
|
const isCi = !process.stdout.isTTY || rawOptions.yes || rawOptions.template;
|
|
2044
2770
|
let resolved;
|
|
2045
2771
|
if (isCi) {
|
|
2046
2772
|
const template = rawOptions.template ?? "react";
|
|
2047
2773
|
resolved = {
|
|
2048
|
-
targetDir:
|
|
2774
|
+
targetDir: path.resolve(process.cwd(), rawOptions.dir),
|
|
2049
2775
|
template,
|
|
2050
2776
|
provider: rawOptions.provider ?? "demo",
|
|
2051
2777
|
tools: rawOptions.tools ? rawOptions.tools.split(",").map((t) => t.trim()) : [],
|
|
@@ -2065,13 +2791,17 @@ Or start fresh with:
|
|
|
2065
2791
|
await writeStarterProject(resolved);
|
|
2066
2792
|
if (isCi) {
|
|
2067
2793
|
process.stdout.write(
|
|
2068
|
-
`Created ${resolved.template} starter in ${
|
|
2794
|
+
`Created ${resolved.template} starter in ${path.relative(process.cwd(), resolved.targetDir) || "."}
|
|
2069
2795
|
`
|
|
2070
2796
|
);
|
|
2071
2797
|
} else {
|
|
2072
2798
|
printNextSteps(resolved);
|
|
2073
2799
|
}
|
|
2074
2800
|
});
|
|
2801
|
+
}
|
|
2802
|
+
|
|
2803
|
+
// src/commands/doctor.ts
|
|
2804
|
+
function registerDoctorCommand(program) {
|
|
2075
2805
|
program.command("doctor").description("Diagnose your AgentsKit environment.").option("--no-network", "Skip provider reachability checks").option(
|
|
2076
2806
|
"--providers <providers>",
|
|
2077
2807
|
"Comma-separated providers to check (default: openai,anthropic,gemini,ollama)"
|
|
@@ -2088,6 +2818,10 @@ Or start fresh with:
|
|
|
2088
2818
|
}
|
|
2089
2819
|
if (report.fail > 0) process.exit(1);
|
|
2090
2820
|
});
|
|
2821
|
+
}
|
|
2822
|
+
|
|
2823
|
+
// src/commands/dev.ts
|
|
2824
|
+
function registerDevCommand(program) {
|
|
2091
2825
|
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) => {
|
|
2092
2826
|
const entry = positional ?? "src/index.ts";
|
|
2093
2827
|
const watch = options.watch ? options.watch.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
@@ -2106,9 +2840,15 @@ Or start fresh with:
|
|
|
2106
2840
|
process.exit(1);
|
|
2107
2841
|
}
|
|
2108
2842
|
});
|
|
2109
|
-
|
|
2843
|
+
}
|
|
2844
|
+
function registerConfigCommand(program) {
|
|
2845
|
+
program.command("config").description("Show or scaffold the AgentsKit config.").argument(
|
|
2846
|
+
"[action]",
|
|
2847
|
+
'Action: "init" to create a template, "show" to print the merged config.',
|
|
2848
|
+
"show"
|
|
2849
|
+
).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) => {
|
|
2110
2850
|
const isLocal = Boolean(options.local);
|
|
2111
|
-
const targetPath = isLocal ?
|
|
2851
|
+
const targetPath = isLocal ? path.join(process.cwd(), ".agentskit.config.json") : path.join(homedir(), ".agentskit", "config.json");
|
|
2112
2852
|
if (action === "show") {
|
|
2113
2853
|
const config = await loadConfig();
|
|
2114
2854
|
process.stdout.write(JSON.stringify(config ?? {}, null, 2) + "\n");
|
|
@@ -2120,8 +2860,10 @@ Or start fresh with:
|
|
|
2120
2860
|
process.exit(2);
|
|
2121
2861
|
}
|
|
2122
2862
|
if (existsSync(targetPath) && !options.force) {
|
|
2123
|
-
process.stderr.write(
|
|
2124
|
-
`
|
|
2863
|
+
process.stderr.write(
|
|
2864
|
+
`Config already exists at ${targetPath}. Re-run with --force to overwrite.
|
|
2865
|
+
`
|
|
2866
|
+
);
|
|
2125
2867
|
process.exit(1);
|
|
2126
2868
|
}
|
|
2127
2869
|
const template = {
|
|
@@ -2133,7 +2875,7 @@ Or start fresh with:
|
|
|
2133
2875
|
tools: "web_search,fetch_url"
|
|
2134
2876
|
}
|
|
2135
2877
|
};
|
|
2136
|
-
mkdirSync(
|
|
2878
|
+
mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
2137
2879
|
writeFileSync(targetPath, JSON.stringify(template, null, 2) + "\n");
|
|
2138
2880
|
process.stdout.write(
|
|
2139
2881
|
`Wrote ${targetPath}
|
|
@@ -2143,6 +2885,10 @@ Edit it to taste, then run:
|
|
|
2143
2885
|
`
|
|
2144
2886
|
);
|
|
2145
2887
|
});
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
// src/commands/tunnel.ts
|
|
2891
|
+
function registerTunnelCommand(program) {
|
|
2146
2892
|
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) => {
|
|
2147
2893
|
const portNum = Number(port);
|
|
2148
2894
|
if (Number.isNaN(portNum) || portNum < 1 || portNum > 65535) {
|
|
@@ -2163,9 +2909,62 @@ Edit it to taste, then run:
|
|
|
2163
2909
|
process.exit(1);
|
|
2164
2910
|
}
|
|
2165
2911
|
});
|
|
2912
|
+
}
|
|
2913
|
+
|
|
2914
|
+
// src/commands/rag.ts
|
|
2915
|
+
function registerRagCommand(program) {
|
|
2916
|
+
const rag = program.command("rag").description("Retrieval-augmented generation utilities.");
|
|
2917
|
+
rag.command("index").description("Index files referenced by config.rag.sources into the vector store.").option(
|
|
2918
|
+
"--source <glob>",
|
|
2919
|
+
"Glob to index (overrides config.rag.sources; repeatable)",
|
|
2920
|
+
(value, prev = []) => [...prev, value],
|
|
2921
|
+
[]
|
|
2922
|
+
).action(async (options) => {
|
|
2923
|
+
const config = await loadConfig();
|
|
2924
|
+
const rawConfig = config?.rag;
|
|
2925
|
+
const overrideSources = options.source;
|
|
2926
|
+
const ragConfig = {
|
|
2927
|
+
...rawConfig ?? {},
|
|
2928
|
+
sources: overrideSources.length > 0 ? overrideSources : rawConfig?.sources ?? []
|
|
2929
|
+
};
|
|
2930
|
+
if (!ragConfig.sources || ragConfig.sources.length === 0) {
|
|
2931
|
+
process.stderr.write("No RAG sources configured. Set config.rag.sources or pass --source <glob>.\n");
|
|
2932
|
+
process.exit(1);
|
|
2933
|
+
}
|
|
2934
|
+
try {
|
|
2935
|
+
const instance = buildRagFromConfig({ config: ragConfig });
|
|
2936
|
+
const result = await indexSources(instance, ragConfig);
|
|
2937
|
+
process.stdout.write(
|
|
2938
|
+
`Indexed ${result.documentCount} document${result.documentCount === 1 ? "" : "s"}.
|
|
2939
|
+
`
|
|
2940
|
+
);
|
|
2941
|
+
for (const source of result.sources) {
|
|
2942
|
+
process.stdout.write(` \u2022 ${source}
|
|
2943
|
+
`);
|
|
2944
|
+
}
|
|
2945
|
+
} catch (err) {
|
|
2946
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
|
|
2947
|
+
`);
|
|
2948
|
+
process.exit(1);
|
|
2949
|
+
}
|
|
2950
|
+
});
|
|
2951
|
+
}
|
|
2952
|
+
|
|
2953
|
+
// src/commands/index.ts
|
|
2954
|
+
function createCli() {
|
|
2955
|
+
const program = new Command();
|
|
2956
|
+
program.name("agentskit").description("AgentsKit CLI for chat demos and project bootstrapping.");
|
|
2957
|
+
registerChatCommand(program);
|
|
2958
|
+
registerRunCommand(program);
|
|
2959
|
+
registerInitCommand(program);
|
|
2960
|
+
registerDoctorCommand(program);
|
|
2961
|
+
registerDevCommand(program);
|
|
2962
|
+
registerConfigCommand(program);
|
|
2963
|
+
registerTunnelCommand(program);
|
|
2964
|
+
registerRagCommand(program);
|
|
2166
2965
|
return program;
|
|
2167
2966
|
}
|
|
2168
2967
|
|
|
2169
|
-
export { ChatApp, createCli, loadConfig, renderChatHeader, renderReport, resolveChatProvider, runAgent, runDoctor, startDev, startTunnel, writeStarterProject };
|
|
2170
|
-
//# sourceMappingURL=chunk-
|
|
2171
|
-
//# sourceMappingURL=chunk-
|
|
2968
|
+
export { ChatApp, HookDispatcher, McpClient, applyPolicyToTool, applyPolicyToTools, bridgeMcpServers, buildRagFromConfig, computeCost, configHooksToHandlers, createCli, createOpenAiEmbedder, defaultPolicy, derivePreview, disposeMcpClients, evaluatePolicy, findLatestSession, findSession, forkSession, generateSessionId, getPricing, indexSources, listSessions, loadConfig, loadPlugins, mergePluginsIntoBundle, registerPricing, renameSession, renderChatHeader, renderReport, resolveChatProvider, resolveSession, runAgent, runDoctor, sessionFilePath, startDev, startTunnel, writeSessionMeta, writeStarterProject };
|
|
2969
|
+
//# sourceMappingURL=chunk-72XFU2X2.js.map
|
|
2970
|
+
//# sourceMappingURL=chunk-72XFU2X2.js.map
|