cliskill 1.0.4 → 1.0.6
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 +66 -0
- package/dist/bootstrap/cli.js +1 -1
- package/dist/{chunk-UJMUL64T.js → chunk-S4ZZPUPF.js} +1595 -159
- package/dist/chunk-S4ZZPUPF.js.map +1 -0
- package/dist/index.d.ts +249 -63
- package/dist/index.js +46 -313
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-UJMUL64T.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -10,11 +10,14 @@ import {
|
|
|
10
10
|
GenericCompatAdapter,
|
|
11
11
|
InkApp,
|
|
12
12
|
LspTool,
|
|
13
|
+
MCPClient,
|
|
14
|
+
MCPConnectionManager,
|
|
13
15
|
MessageList,
|
|
14
16
|
QueryEngine,
|
|
15
17
|
Spinner,
|
|
16
18
|
StatusBar,
|
|
17
19
|
StreamingToolExecutor,
|
|
20
|
+
TaskManager,
|
|
18
21
|
ToolRegistry,
|
|
19
22
|
WebSearchTool,
|
|
20
23
|
analyzeTokenBudget,
|
|
@@ -25,7 +28,7 @@ import {
|
|
|
25
28
|
renderInkApp,
|
|
26
29
|
runAgentLoop,
|
|
27
30
|
runCli
|
|
28
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-S4ZZPUPF.js";
|
|
29
32
|
import {
|
|
30
33
|
getGlobalSkillsDir,
|
|
31
34
|
getHistoryPath
|
|
@@ -325,9 +328,31 @@ var PermissionManager = class {
|
|
|
325
328
|
mode;
|
|
326
329
|
rules = [];
|
|
327
330
|
sessionDecisions = /* @__PURE__ */ new Map();
|
|
328
|
-
|
|
331
|
+
autoApprovePatterns = [];
|
|
332
|
+
decisionCache = /* @__PURE__ */ new Map();
|
|
333
|
+
cacheTtl = 6e4;
|
|
334
|
+
constructor(mode, rules, autoApprovePatterns) {
|
|
329
335
|
this.mode = mode;
|
|
330
336
|
this.rules = rules ?? [];
|
|
337
|
+
this.autoApprovePatterns = autoApprovePatterns ?? [];
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* P3.13: Dynamic permission check — evaluates tool + input together.
|
|
341
|
+
* Uses auto-approve patterns for regex-based matching.
|
|
342
|
+
*/
|
|
343
|
+
canUseTool(toolName, input, riskLevel) {
|
|
344
|
+
const inputStr = JSON.stringify(input);
|
|
345
|
+
for (const pattern of this.autoApprovePatterns) {
|
|
346
|
+
if (this.matchTool(toolName, pattern.tool)) {
|
|
347
|
+
if (pattern.maxRiskLevel && this.riskLevelOrdinal(riskLevel) > this.riskLevelOrdinal(pattern.maxRiskLevel)) {
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
if (!pattern.inputPattern || pattern.inputPattern.test(inputStr)) {
|
|
351
|
+
return { allowed: true, reason: `Auto-approved by pattern: ${pattern.tool}` };
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return this.check(toolName, riskLevel, inputStr);
|
|
331
356
|
}
|
|
332
357
|
/**
|
|
333
358
|
* Check if a tool operation is allowed.
|
|
@@ -357,14 +382,22 @@ var PermissionManager = class {
|
|
|
357
382
|
if (sessionDecision !== void 0) {
|
|
358
383
|
return { allowed: sessionDecision, reason: "session decision" };
|
|
359
384
|
}
|
|
385
|
+
const cached = this.decisionCache.get(sessionKey);
|
|
386
|
+
if (cached && Date.now() - cached.timestamp < this.cacheTtl) {
|
|
387
|
+
return cached.result;
|
|
388
|
+
}
|
|
360
389
|
return { allowed: false, reason: "No rule matched \u2014 requires user approval" };
|
|
361
390
|
}
|
|
362
391
|
/**
|
|
363
|
-
* Record a user decision for the session.
|
|
392
|
+
* Record a user decision for the session with caching.
|
|
364
393
|
*/
|
|
365
394
|
recordDecision(toolName, content, allowed) {
|
|
366
395
|
const key = `${toolName}:${content}`;
|
|
367
396
|
this.sessionDecisions.set(key, allowed);
|
|
397
|
+
this.decisionCache.set(key, {
|
|
398
|
+
result: { allowed, reason: "session decision" },
|
|
399
|
+
timestamp: Date.now()
|
|
400
|
+
});
|
|
368
401
|
}
|
|
369
402
|
/**
|
|
370
403
|
* Add a permission rule.
|
|
@@ -372,6 +405,12 @@ var PermissionManager = class {
|
|
|
372
405
|
addRule(rule) {
|
|
373
406
|
this.rules.push(rule);
|
|
374
407
|
}
|
|
408
|
+
/**
|
|
409
|
+
* P3.13: Add an auto-approve pattern.
|
|
410
|
+
*/
|
|
411
|
+
addAutoApprovePattern(pattern) {
|
|
412
|
+
this.autoApprovePatterns.push(pattern);
|
|
413
|
+
}
|
|
375
414
|
/**
|
|
376
415
|
* Set the permission mode.
|
|
377
416
|
*/
|
|
@@ -395,6 +434,10 @@ var PermissionManager = class {
|
|
|
395
434
|
}
|
|
396
435
|
return content.includes(pattern);
|
|
397
436
|
}
|
|
437
|
+
riskLevelOrdinal(level) {
|
|
438
|
+
const order = ["readonly", "safe", "destructive", "critical"];
|
|
439
|
+
return order.indexOf(level);
|
|
440
|
+
}
|
|
398
441
|
};
|
|
399
442
|
|
|
400
443
|
// src/safety/command-inspector.ts
|
|
@@ -1040,316 +1083,6 @@ function tokenize(segment) {
|
|
|
1040
1083
|
return tokens;
|
|
1041
1084
|
}
|
|
1042
1085
|
|
|
1043
|
-
// src/mcp/client.ts
|
|
1044
|
-
import { spawn } from "child_process";
|
|
1045
|
-
import { createInterface } from "readline";
|
|
1046
|
-
var REQUEST_TIMEOUT = 3e4;
|
|
1047
|
-
var MCPClient = class {
|
|
1048
|
-
config;
|
|
1049
|
-
process = null;
|
|
1050
|
-
rl = null;
|
|
1051
|
-
connected = false;
|
|
1052
|
-
nextId = 1;
|
|
1053
|
-
pendingRequests = /* @__PURE__ */ new Map();
|
|
1054
|
-
buffer = "";
|
|
1055
|
-
constructor(config) {
|
|
1056
|
-
this.config = config;
|
|
1057
|
-
}
|
|
1058
|
-
async connect() {
|
|
1059
|
-
if (this.connected) return;
|
|
1060
|
-
const env = {
|
|
1061
|
-
...process.env,
|
|
1062
|
-
...this.config.env
|
|
1063
|
-
};
|
|
1064
|
-
this.process = spawn(this.config.command, this.config.args, {
|
|
1065
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
1066
|
-
env
|
|
1067
|
-
});
|
|
1068
|
-
this.process.on("error", (err) => {
|
|
1069
|
-
this.cleanup();
|
|
1070
|
-
throw err;
|
|
1071
|
-
});
|
|
1072
|
-
this.process.on("exit", () => {
|
|
1073
|
-
this.cleanup();
|
|
1074
|
-
});
|
|
1075
|
-
if (!this.process.stdout) {
|
|
1076
|
-
throw new Error("MCP server stdout is not available");
|
|
1077
|
-
}
|
|
1078
|
-
this.rl = createInterface({ input: this.process.stdout });
|
|
1079
|
-
this.rl.on("line", (line) => {
|
|
1080
|
-
this.handleLine(line);
|
|
1081
|
-
});
|
|
1082
|
-
if (this.process.stderr) {
|
|
1083
|
-
this.process.stderr.on("data", () => {
|
|
1084
|
-
});
|
|
1085
|
-
}
|
|
1086
|
-
await this.sendRequest("initialize", {
|
|
1087
|
-
protocolVersion: "2024-11-05",
|
|
1088
|
-
capabilities: {},
|
|
1089
|
-
clientInfo: { name: "cliskill", version: "1.0.0" }
|
|
1090
|
-
});
|
|
1091
|
-
this.sendNotification("notifications/initialized", {});
|
|
1092
|
-
this.connected = true;
|
|
1093
|
-
}
|
|
1094
|
-
async disconnect() {
|
|
1095
|
-
if (!this.process) return;
|
|
1096
|
-
for (const [, pending] of this.pendingRequests) {
|
|
1097
|
-
clearTimeout(pending.timer);
|
|
1098
|
-
pending.reject(new Error("Connection closed"));
|
|
1099
|
-
}
|
|
1100
|
-
this.pendingRequests.clear();
|
|
1101
|
-
this.process.kill("SIGTERM");
|
|
1102
|
-
this.cleanup();
|
|
1103
|
-
}
|
|
1104
|
-
async listTools() {
|
|
1105
|
-
const response = await this.sendRequest("tools/list", {});
|
|
1106
|
-
const result = response.result;
|
|
1107
|
-
return result?.tools ?? [];
|
|
1108
|
-
}
|
|
1109
|
-
async callTool(name, args) {
|
|
1110
|
-
const response = await this.sendRequest("tools/call", { name, arguments: args });
|
|
1111
|
-
return response.result;
|
|
1112
|
-
}
|
|
1113
|
-
async listResources() {
|
|
1114
|
-
const response = await this.sendRequest("resources/list", {});
|
|
1115
|
-
const result = response.result;
|
|
1116
|
-
return result?.resources ?? [];
|
|
1117
|
-
}
|
|
1118
|
-
async readResource(uri) {
|
|
1119
|
-
const response = await this.sendRequest("resources/read", { uri });
|
|
1120
|
-
return response.result;
|
|
1121
|
-
}
|
|
1122
|
-
async listPrompts() {
|
|
1123
|
-
const response = await this.sendRequest("prompts/list", {});
|
|
1124
|
-
const result = response.result;
|
|
1125
|
-
return result?.prompts ?? [];
|
|
1126
|
-
}
|
|
1127
|
-
isConnected() {
|
|
1128
|
-
return this.connected;
|
|
1129
|
-
}
|
|
1130
|
-
sendRequest(method, params) {
|
|
1131
|
-
return new Promise((resolve2, reject) => {
|
|
1132
|
-
if (!this.process?.stdin) {
|
|
1133
|
-
reject(new Error("MCP server not connected"));
|
|
1134
|
-
return;
|
|
1135
|
-
}
|
|
1136
|
-
const id = this.nextId++;
|
|
1137
|
-
const request = { jsonrpc: "2.0", id, method, params };
|
|
1138
|
-
const timer = setTimeout(() => {
|
|
1139
|
-
this.pendingRequests.delete(id);
|
|
1140
|
-
reject(new Error(`Request timeout: ${method} (id=${id})`));
|
|
1141
|
-
}, REQUEST_TIMEOUT);
|
|
1142
|
-
this.pendingRequests.set(id, { resolve: resolve2, reject, timer });
|
|
1143
|
-
const data = JSON.stringify(request) + "\n";
|
|
1144
|
-
this.process.stdin.write(data, (err) => {
|
|
1145
|
-
if (err) {
|
|
1146
|
-
clearTimeout(timer);
|
|
1147
|
-
this.pendingRequests.delete(id);
|
|
1148
|
-
reject(err);
|
|
1149
|
-
}
|
|
1150
|
-
});
|
|
1151
|
-
});
|
|
1152
|
-
}
|
|
1153
|
-
sendNotification(method, params) {
|
|
1154
|
-
if (!this.process?.stdin) return;
|
|
1155
|
-
const notification = { jsonrpc: "2.0", method, params };
|
|
1156
|
-
const data = JSON.stringify(notification) + "\n";
|
|
1157
|
-
this.process.stdin.write(data);
|
|
1158
|
-
}
|
|
1159
|
-
handleLine(line) {
|
|
1160
|
-
this.buffer += line;
|
|
1161
|
-
let response;
|
|
1162
|
-
try {
|
|
1163
|
-
response = JSON.parse(this.buffer);
|
|
1164
|
-
} catch {
|
|
1165
|
-
return;
|
|
1166
|
-
} finally {
|
|
1167
|
-
this.buffer = "";
|
|
1168
|
-
}
|
|
1169
|
-
if (response.id !== void 0 && response.id !== null) {
|
|
1170
|
-
const pending = this.pendingRequests.get(response.id);
|
|
1171
|
-
if (pending) {
|
|
1172
|
-
clearTimeout(pending.timer);
|
|
1173
|
-
this.pendingRequests.delete(response.id);
|
|
1174
|
-
pending.resolve(response);
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
}
|
|
1178
|
-
cleanup() {
|
|
1179
|
-
this.connected = false;
|
|
1180
|
-
this.rl?.close();
|
|
1181
|
-
this.rl = null;
|
|
1182
|
-
this.process = null;
|
|
1183
|
-
this.buffer = "";
|
|
1184
|
-
}
|
|
1185
|
-
};
|
|
1186
|
-
|
|
1187
|
-
// src/mcp/manager.ts
|
|
1188
|
-
var MCPConnectionManager = class {
|
|
1189
|
-
clients = /* @__PURE__ */ new Map();
|
|
1190
|
-
async addServer(config) {
|
|
1191
|
-
const client = new MCPClient(config);
|
|
1192
|
-
await client.connect();
|
|
1193
|
-
this.clients.set(config.name, client);
|
|
1194
|
-
}
|
|
1195
|
-
async removeServer(name) {
|
|
1196
|
-
const client = this.clients.get(name);
|
|
1197
|
-
if (client) {
|
|
1198
|
-
await client.disconnect();
|
|
1199
|
-
this.clients.delete(name);
|
|
1200
|
-
}
|
|
1201
|
-
}
|
|
1202
|
-
getConnectedServers() {
|
|
1203
|
-
return Array.from(this.clients.entries()).filter(([, client]) => client.isConnected()).map(([name]) => name);
|
|
1204
|
-
}
|
|
1205
|
-
async getAllTools() {
|
|
1206
|
-
const allTools = [];
|
|
1207
|
-
for (const [, client] of this.clients) {
|
|
1208
|
-
if (!client.isConnected()) continue;
|
|
1209
|
-
try {
|
|
1210
|
-
const tools = await client.listTools();
|
|
1211
|
-
allTools.push(...tools);
|
|
1212
|
-
} catch {
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1215
|
-
return allTools;
|
|
1216
|
-
}
|
|
1217
|
-
async callTool(serverName, toolName, args) {
|
|
1218
|
-
const client = this.clients.get(serverName);
|
|
1219
|
-
if (!client) {
|
|
1220
|
-
throw new Error(`MCP server "${serverName}" not found`);
|
|
1221
|
-
}
|
|
1222
|
-
if (!client.isConnected()) {
|
|
1223
|
-
throw new Error(`MCP server "${serverName}" is not connected`);
|
|
1224
|
-
}
|
|
1225
|
-
return client.callTool(toolName, args);
|
|
1226
|
-
}
|
|
1227
|
-
async getToolsForServer(serverName) {
|
|
1228
|
-
const client = this.clients.get(serverName);
|
|
1229
|
-
if (!client || !client.isConnected()) {
|
|
1230
|
-
return [];
|
|
1231
|
-
}
|
|
1232
|
-
return client.listTools();
|
|
1233
|
-
}
|
|
1234
|
-
async disconnectAll() {
|
|
1235
|
-
for (const [, client] of this.clients) {
|
|
1236
|
-
try {
|
|
1237
|
-
await client.disconnect();
|
|
1238
|
-
} catch {
|
|
1239
|
-
}
|
|
1240
|
-
}
|
|
1241
|
-
this.clients.clear();
|
|
1242
|
-
}
|
|
1243
|
-
};
|
|
1244
|
-
|
|
1245
|
-
// src/tasks/manager.ts
|
|
1246
|
-
var TaskManager = class {
|
|
1247
|
-
tasks = /* @__PURE__ */ new Map();
|
|
1248
|
-
listeners = /* @__PURE__ */ new Map();
|
|
1249
|
-
idCounter = 0;
|
|
1250
|
-
createTask(definition) {
|
|
1251
|
-
const id = `task_${++this.idCounter}_${Date.now()}`;
|
|
1252
|
-
const task = {
|
|
1253
|
-
...definition,
|
|
1254
|
-
id,
|
|
1255
|
-
status: "pending",
|
|
1256
|
-
createdAt: Date.now(),
|
|
1257
|
-
progress: 0
|
|
1258
|
-
};
|
|
1259
|
-
this.tasks.set(id, task);
|
|
1260
|
-
return id;
|
|
1261
|
-
}
|
|
1262
|
-
cancelTask(id) {
|
|
1263
|
-
const task = this.tasks.get(id);
|
|
1264
|
-
if (!task) return;
|
|
1265
|
-
if (task.status !== "running" && task.status !== "pending") return;
|
|
1266
|
-
task.status = "cancelled";
|
|
1267
|
-
task.completedAt = Date.now();
|
|
1268
|
-
this.emit("onStatusChange", id, "cancelled");
|
|
1269
|
-
}
|
|
1270
|
-
getTask(id) {
|
|
1271
|
-
return this.tasks.get(id);
|
|
1272
|
-
}
|
|
1273
|
-
listTasks(filter) {
|
|
1274
|
-
const all = Array.from(this.tasks.values());
|
|
1275
|
-
if (!filter?.status) return all;
|
|
1276
|
-
return all.filter((t) => t.status === filter.status);
|
|
1277
|
-
}
|
|
1278
|
-
getActiveCount() {
|
|
1279
|
-
let count = 0;
|
|
1280
|
-
for (const task of this.tasks.values()) {
|
|
1281
|
-
if (task.status === "running" || task.status === "pending") count++;
|
|
1282
|
-
}
|
|
1283
|
-
return count;
|
|
1284
|
-
}
|
|
1285
|
-
removeTask(id) {
|
|
1286
|
-
const task = this.tasks.get(id);
|
|
1287
|
-
if (!task) return;
|
|
1288
|
-
if (task.status === "running") {
|
|
1289
|
-
throw new Error(`Cannot remove running task "${id}". Cancel it first.`);
|
|
1290
|
-
}
|
|
1291
|
-
this.tasks.delete(id);
|
|
1292
|
-
}
|
|
1293
|
-
clearCompleted() {
|
|
1294
|
-
for (const [id, task] of this.tasks) {
|
|
1295
|
-
if (task.status === "completed" || task.status === "failed" || task.status === "cancelled") {
|
|
1296
|
-
this.tasks.delete(id);
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1299
|
-
}
|
|
1300
|
-
on(event, handler) {
|
|
1301
|
-
if (!this.listeners.has(event)) {
|
|
1302
|
-
this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
1303
|
-
}
|
|
1304
|
-
this.listeners.get(event).add(handler);
|
|
1305
|
-
}
|
|
1306
|
-
off(event, handler) {
|
|
1307
|
-
this.listeners.get(event)?.delete(handler);
|
|
1308
|
-
}
|
|
1309
|
-
/** @internal Update task status */
|
|
1310
|
-
updateStatus(id, status) {
|
|
1311
|
-
const task = this.tasks.get(id);
|
|
1312
|
-
if (!task) return;
|
|
1313
|
-
task.status = status;
|
|
1314
|
-
if (status === "running") task.startedAt = Date.now();
|
|
1315
|
-
if (status === "completed" || status === "failed" || status === "cancelled") {
|
|
1316
|
-
task.completedAt = Date.now();
|
|
1317
|
-
}
|
|
1318
|
-
this.emit("onStatusChange", id, status);
|
|
1319
|
-
}
|
|
1320
|
-
/** @internal Update task progress */
|
|
1321
|
-
updateProgress(id, progress, message) {
|
|
1322
|
-
const task = this.tasks.get(id);
|
|
1323
|
-
if (!task) return;
|
|
1324
|
-
task.progress = Math.min(100, Math.max(0, progress));
|
|
1325
|
-
this.emit("onProgress", id, task.progress, message);
|
|
1326
|
-
}
|
|
1327
|
-
/** @internal Set task result */
|
|
1328
|
-
setResult(id, result) {
|
|
1329
|
-
const task = this.tasks.get(id);
|
|
1330
|
-
if (!task) return;
|
|
1331
|
-
task.result = result;
|
|
1332
|
-
task.status = "completed";
|
|
1333
|
-
task.completedAt = Date.now();
|
|
1334
|
-
task.progress = 100;
|
|
1335
|
-
this.emit("onComplete", id, result);
|
|
1336
|
-
this.emit("onStatusChange", id, "completed");
|
|
1337
|
-
}
|
|
1338
|
-
/** @internal Set task error */
|
|
1339
|
-
setError(id, error) {
|
|
1340
|
-
const task = this.tasks.get(id);
|
|
1341
|
-
if (!task) return;
|
|
1342
|
-
task.error = error;
|
|
1343
|
-
task.status = "failed";
|
|
1344
|
-
task.completedAt = Date.now();
|
|
1345
|
-
this.emit("onError", id, error);
|
|
1346
|
-
this.emit("onStatusChange", id, "failed");
|
|
1347
|
-
}
|
|
1348
|
-
emit(event, ...args) {
|
|
1349
|
-
this.listeners.get(event)?.forEach((fn) => fn(...args));
|
|
1350
|
-
}
|
|
1351
|
-
};
|
|
1352
|
-
|
|
1353
1086
|
// src/tasks/executor.ts
|
|
1354
1087
|
import { exec as exec2 } from "child_process";
|
|
1355
1088
|
async function executeAgentTask(taskId, manager, adapter, tools, prompt, signal) {
|