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/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-UJMUL64T.js";
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
- constructor(mode, rules) {
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) {