cliskill 1.1.1 → 1.1.2

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.
@@ -28,8 +28,8 @@ var providerSchema = z.object({
28
28
  apiKey: z.string().optional(),
29
29
  /** Default model to use */
30
30
  model: z.string().min(1),
31
- /** API format: "openai-compatible" | "messages-compatible" | "glm-compatible" | "custom" */
32
- format: z.enum(["openai-compatible", "messages-compatible", "glm-compatible", "custom"]),
31
+ /** API format: "openai-compatible" | "messages-compatible" | "glm-compatible" | "anthropic-compatible" | "custom" */
32
+ format: z.enum(["openai-compatible", "messages-compatible", "glm-compatible", "anthropic-compatible", "custom"]),
33
33
  /** Custom headers to send with each request */
34
34
  headers: z.record(z.string()).optional(),
35
35
  /** Request timeout in milliseconds */
@@ -269,6 +269,36 @@ function applyEnvironmentVariables(config) {
269
269
  }];
270
270
  }
271
271
  }
272
+ const anthropicApiKey = process.env.ANTHROPIC_API_KEY || process.env.CLISKILL_ANTHROPIC_API_KEY;
273
+ const anthropicBaseUrl = process.env.ANTHROPIC_BASE_URL || process.env.CLISKILL_ANTHROPIC_BASE_URL;
274
+ const anthropicModel = process.env.CLISKILL_ANTHROPIC_MODEL;
275
+ if (anthropicApiKey || anthropicBaseUrl) {
276
+ const existingAnthropic = result.providers.find((p) => p.format === "anthropic-compatible");
277
+ if (existingAnthropic) {
278
+ const updated = { ...existingAnthropic };
279
+ if (anthropicApiKey) updated.apiKey = anthropicApiKey;
280
+ if (anthropicBaseUrl) updated.baseUrl = anthropicBaseUrl;
281
+ if (anthropicModel) updated.model = anthropicModel;
282
+ result.providers = [
283
+ updated,
284
+ ...result.providers.filter((p) => p.format !== "anthropic-compatible")
285
+ ];
286
+ } else {
287
+ const anthropicProvider = {
288
+ name: "anthropic",
289
+ baseUrl: anthropicBaseUrl ?? "https://api.anthropic.com",
290
+ model: anthropicModel ?? "claude-sonnet-4-20250514",
291
+ format: "anthropic-compatible",
292
+ apiKey: anthropicApiKey,
293
+ timeout: 18e4,
294
+ maxTokens: 65536
295
+ };
296
+ result.providers = [...result.providers, anthropicProvider];
297
+ if (!result.defaultProvider) {
298
+ result.defaultProvider = "anthropic";
299
+ }
300
+ }
301
+ }
272
302
  const envAutoMode = process.env.CLISKILL_AUTO_MODE;
273
303
  if (envAutoMode) {
274
304
  result.autoMode = {
@@ -457,6 +487,81 @@ var BaseAdapter = class {
457
487
 
458
488
  // src/connect/generic-adapter.ts
459
489
  import { randomUUID } from "crypto";
490
+
491
+ // src/connect/network-errors.ts
492
+ var NETWORK_ERROR_PATTERNS = [
493
+ "ECONNRESET",
494
+ "ECONNABORTED",
495
+ "ETIMEDOUT",
496
+ "EHOSTUNREACH",
497
+ "ENETUNREACH",
498
+ "WSAETIMEDOUT",
499
+ "WSAECONNRESET",
500
+ "WSAECONNABORTED",
501
+ "fetch failed",
502
+ "network"
503
+ ];
504
+ var ABORT_PATTERNS = [
505
+ "AbortError",
506
+ "abort",
507
+ "Abort",
508
+ "timeout",
509
+ "Timeout"
510
+ ];
511
+ function isAbortOrNetworkError(err) {
512
+ const name = err.name ?? "";
513
+ const msg = err.message ?? "";
514
+ const code = err.code ?? "";
515
+ if (name === "AbortError") return true;
516
+ const combined = `${name} ${msg} ${code}`;
517
+ for (const pattern of ABORT_PATTERNS) {
518
+ if (combined.includes(pattern)) return true;
519
+ }
520
+ for (const pattern of NETWORK_ERROR_PATTERNS) {
521
+ if (combined.includes(pattern)) return true;
522
+ }
523
+ return false;
524
+ }
525
+
526
+ // src/connect/sse-parser.ts
527
+ async function* readStreamLines(body) {
528
+ const reader = body.getReader();
529
+ const decoder = new TextDecoder();
530
+ let buffer = "";
531
+ let scanFrom = 0;
532
+ try {
533
+ while (true) {
534
+ let done;
535
+ let value;
536
+ try {
537
+ const result = await reader.read();
538
+ done = result.done;
539
+ value = result.value;
540
+ } catch (err) {
541
+ if (isAbortOrNetworkError(err)) break;
542
+ throw err;
543
+ }
544
+ if (done) break;
545
+ buffer += decoder.decode(value, { stream: true });
546
+ let nlIdx;
547
+ while ((nlIdx = buffer.indexOf("\n", scanFrom)) !== -1) {
548
+ yield buffer.substring(scanFrom, nlIdx);
549
+ scanFrom = nlIdx + 1;
550
+ }
551
+ if (scanFrom > 0) {
552
+ buffer = buffer.substring(scanFrom);
553
+ scanFrom = 0;
554
+ }
555
+ }
556
+ if (buffer.length > 0) {
557
+ yield buffer;
558
+ }
559
+ } finally {
560
+ reader.releaseLock();
561
+ }
562
+ }
563
+
564
+ // src/connect/generic-adapter.ts
460
565
  var GenericCompatAdapter = class extends BaseAdapter {
461
566
  constructor(config, name) {
462
567
  super();
@@ -593,38 +698,15 @@ var GenericCompatAdapter = class extends BaseAdapter {
593
698
  };
594
699
  }
595
700
  async *parseSSEStream(body) {
596
- const reader = body.getReader();
597
- const decoder = new TextDecoder();
598
- let buffer = "";
599
- try {
600
- while (true) {
601
- let done;
602
- let value;
603
- try {
604
- const result = await reader.read();
605
- done = result.done;
606
- value = result.value;
607
- } catch (err) {
608
- if (isAbortOrNetworkError(err)) break;
609
- throw err;
610
- }
611
- if (done) break;
612
- buffer += decoder.decode(value, { stream: true });
613
- const lines = buffer.split("\n");
614
- buffer = lines.pop() ?? "";
615
- for (const line of lines) {
616
- const trimmed = line.trim();
617
- if (!trimmed || trimmed === "data: [DONE]") continue;
618
- if (!trimmed.startsWith("data: ")) continue;
619
- try {
620
- const json = JSON.parse(trimmed.slice(6));
621
- yield* this.processChunk(json);
622
- } catch {
623
- }
624
- }
701
+ for await (const line of readStreamLines(body)) {
702
+ const trimmed = line.trim();
703
+ if (!trimmed || trimmed === "data: [DONE]") continue;
704
+ if (!trimmed.startsWith("data: ")) continue;
705
+ try {
706
+ const json = JSON.parse(trimmed.slice(6));
707
+ yield* this.processChunk(json);
708
+ } catch {
625
709
  }
626
- } finally {
627
- reader.releaseLock();
628
710
  }
629
711
  }
630
712
  /** Track active tool calls: index → { id, name, args } for emitting tool_use_end */
@@ -672,11 +754,252 @@ var GenericCompatAdapter = class extends BaseAdapter {
672
754
  }
673
755
  }
674
756
  };
675
- function isAbortOrNetworkError(err) {
676
- const name = err.name ?? "";
677
- const msg = err.message ?? "";
678
- return name === "AbortError" || msg.includes("abort") || msg.includes("Abort") || msg.includes("timeout") || msg.includes("Timeout") || msg.includes("fetch failed") || msg.includes("ECONNRESET") || msg.includes("network");
679
- }
757
+
758
+ // src/connect/anthropic-adapter.ts
759
+ var ANTHROPIC_VERSION = "2023-06-01";
760
+ var MESSAGES_PATH = "/v1/messages";
761
+ var AnthropicAdapter = class extends BaseAdapter {
762
+ constructor(config, name) {
763
+ super();
764
+ this.config = config;
765
+ this.name = name ?? `anthropic:${config.model}`;
766
+ }
767
+ config;
768
+ name;
769
+ async complete(request) {
770
+ const url = this.buildUrl(MESSAGES_PATH);
771
+ const body = this.buildRequestBody(request, false);
772
+ const response = await this.fetchWithRetry(url, () => ({
773
+ method: "POST",
774
+ headers: this.buildHeaders(),
775
+ body: JSON.stringify(body),
776
+ signal: this.buildSignal(request.signal)
777
+ }));
778
+ if (!response.ok) {
779
+ const text = await response.text().catch(() => "unknown error");
780
+ throw new Error(`Anthropic error (${response.status}): ${text}`);
781
+ }
782
+ const data = await response.json();
783
+ return this.mapResponse(data);
784
+ }
785
+ async *stream(request) {
786
+ const url = this.buildUrl(MESSAGES_PATH);
787
+ const body = this.buildRequestBody(request, true);
788
+ const response = await this.fetchWithRetry(url, () => ({
789
+ method: "POST",
790
+ headers: this.buildHeaders(),
791
+ body: JSON.stringify(body),
792
+ signal: this.buildStreamingSignal(request.signal)
793
+ }));
794
+ if (!response.ok) {
795
+ const text = await response.text().catch(() => "unknown error");
796
+ throw new Error(`Anthropic error (${response.status}): ${text}`);
797
+ }
798
+ if (!response.body) throw new Error("No response body for streaming");
799
+ yield* this.parseSSEStream(response.body);
800
+ }
801
+ async validate() {
802
+ try {
803
+ const url = this.buildUrl("/v1/models");
804
+ const response = await fetch(url, {
805
+ headers: this.buildHeaders(),
806
+ signal: AbortSignal.timeout(1e4)
807
+ });
808
+ if (!response.ok) return { ok: false, error: `HTTP ${response.status}` };
809
+ return { ok: true };
810
+ } catch (err) {
811
+ return { ok: false, error: err.message };
812
+ }
813
+ }
814
+ dispose() {
815
+ }
816
+ buildHeaders() {
817
+ const headers = {
818
+ "Content-Type": "application/json",
819
+ "anthropic-version": ANTHROPIC_VERSION,
820
+ ...this.config.headers
821
+ };
822
+ if (this.config.apiKey) {
823
+ headers["x-api-key"] = this.config.apiKey;
824
+ }
825
+ return headers;
826
+ }
827
+ buildRequestBody(request, stream) {
828
+ const messages = [];
829
+ for (const msg of request.messages) {
830
+ const mapped = this.mapMessage(msg);
831
+ if (mapped) messages.push(...mapped);
832
+ }
833
+ const body = {
834
+ model: this.config.model,
835
+ messages,
836
+ max_tokens: request.maxTokens ?? this.config.maxTokens,
837
+ temperature: request.temperature,
838
+ stream
839
+ };
840
+ if (request.systemPrompt) {
841
+ body.system = request.systemPrompt;
842
+ }
843
+ if (request.stopSequences && request.stopSequences.length > 0) {
844
+ body.stop_sequences = request.stopSequences;
845
+ }
846
+ if (request.tools && request.tools.length > 0) {
847
+ body.tools = request.tools.map(this.mapTool);
848
+ }
849
+ return body;
850
+ }
851
+ mapMessage(msg) {
852
+ const textParts = msg.content.filter((b) => b.type === "text");
853
+ const toolUseParts = msg.content.filter((b) => b.type === "tool_use");
854
+ const toolResultParts = msg.content.filter((b) => b.type === "tool_result");
855
+ if (msg.role === "system") {
856
+ return null;
857
+ }
858
+ if (msg.role === "assistant") {
859
+ const blocks = [];
860
+ const textContent2 = textParts.map((t) => t.text).join("");
861
+ if (textContent2) {
862
+ blocks.push({ type: "text", text: textContent2 });
863
+ }
864
+ for (const tc of toolUseParts) {
865
+ blocks.push({ type: "tool_use", id: tc.id, name: tc.name, input: tc.input });
866
+ }
867
+ return [{ role: "assistant", content: blocks.length > 0 ? blocks : "" }];
868
+ }
869
+ if (toolResultParts.length > 0) {
870
+ const blocks = [];
871
+ for (const result of toolResultParts) {
872
+ const sanitizedContent = result.content.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "") || "(empty output)";
873
+ blocks.push({ type: "tool_result", tool_use_id: result.toolUseId, content: sanitizedContent });
874
+ }
875
+ const textContent2 = textParts.map((t) => t.text).join("");
876
+ if (textContent2) {
877
+ blocks.unshift({ type: "text", text: textContent2 });
878
+ }
879
+ return [{ role: "user", content: blocks }];
880
+ }
881
+ const textContent = textParts.map((t) => t.text).join("");
882
+ return [{ role: msg.role, content: textContent || "" }];
883
+ }
884
+ mapTool(tool) {
885
+ return {
886
+ name: tool.name,
887
+ description: tool.description,
888
+ input_schema: tool.inputSchema
889
+ };
890
+ }
891
+ mapResponse(data) {
892
+ const contentBlocks = [];
893
+ for (const block of data.content) {
894
+ if (block.type === "text") {
895
+ contentBlocks.push({ type: "text", text: block.text });
896
+ } else if (block.type === "tool_use") {
897
+ contentBlocks.push({
898
+ type: "tool_use",
899
+ id: block.id,
900
+ name: block.name,
901
+ input: block.input
902
+ });
903
+ }
904
+ }
905
+ return {
906
+ message: { role: "assistant", content: contentBlocks },
907
+ stopReason: data.stop_reason ?? "end_turn",
908
+ usage: {
909
+ inputTokens: data.usage?.input_tokens ?? 0,
910
+ outputTokens: data.usage?.output_tokens ?? 0,
911
+ cacheReadTokens: data.usage?.cache_read_input_tokens,
912
+ cacheWriteTokens: data.usage?.cache_creation_input_tokens
913
+ },
914
+ model: data.model
915
+ };
916
+ }
917
+ async *parseSSEStream(body) {
918
+ let currentModel = "";
919
+ let currentToolId = "";
920
+ let currentToolName = "";
921
+ let toolInputBuffer = "";
922
+ for await (const line of readStreamLines(body)) {
923
+ const trimmed = line.trim();
924
+ if (!trimmed || trimmed.startsWith("event:")) continue;
925
+ if (!trimmed.startsWith("data:")) continue;
926
+ const jsonStr = trimmed.slice(6).trim();
927
+ if (!jsonStr) continue;
928
+ let event;
929
+ try {
930
+ event = JSON.parse(jsonStr);
931
+ } catch {
932
+ continue;
933
+ }
934
+ switch (event.type) {
935
+ case "message_start": {
936
+ if (event.message) {
937
+ currentModel = event.message.model ?? "";
938
+ yield { type: "message_start", model: currentModel };
939
+ }
940
+ break;
941
+ }
942
+ case "content_block_start": {
943
+ const block = event.content_block;
944
+ if (block?.type === "tool_use") {
945
+ currentToolId = block.id ?? "";
946
+ currentToolName = block.name ?? "";
947
+ toolInputBuffer = "";
948
+ yield { type: "tool_use_start", id: currentToolId, name: currentToolName };
949
+ }
950
+ break;
951
+ }
952
+ case "content_block_delta": {
953
+ const delta = event.delta;
954
+ if (!delta) break;
955
+ if (delta.type === "text_delta" && delta.text) {
956
+ yield { type: "text_delta", text: delta.text };
957
+ } else if (delta.type === "input_json_delta" && delta.partial_json) {
958
+ toolInputBuffer += delta.partial_json;
959
+ yield { type: "tool_use_delta", id: currentToolId, inputDelta: delta.partial_json };
960
+ }
961
+ break;
962
+ }
963
+ case "content_block_stop": {
964
+ if (currentToolId && currentToolName) {
965
+ let input = {};
966
+ try {
967
+ input = JSON.parse(toolInputBuffer || "{}");
968
+ } catch {
969
+ }
970
+ yield { type: "tool_use_end", id: currentToolId, name: currentToolName, input };
971
+ currentToolId = "";
972
+ currentToolName = "";
973
+ toolInputBuffer = "";
974
+ }
975
+ break;
976
+ }
977
+ case "message_delta": {
978
+ const delta = event.delta;
979
+ if (delta?.stop_reason) {
980
+ const usage = event.usage ?? { output_tokens: 0 };
981
+ yield {
982
+ type: "message_end",
983
+ stopReason: delta.stop_reason,
984
+ usage: { inputTokens: 0, outputTokens: usage.output_tokens }
985
+ };
986
+ }
987
+ break;
988
+ }
989
+ case "message_stop": {
990
+ break;
991
+ }
992
+ case "ping": {
993
+ break;
994
+ }
995
+ case "error": {
996
+ const errorData = event;
997
+ throw new Error(`Anthropic stream error: ${errorData.error?.message ?? "unknown"}`);
998
+ }
999
+ }
1000
+ }
1001
+ }
1002
+ };
680
1003
 
681
1004
  // src/connect/glm-adapter.ts
682
1005
  import { randomUUID as randomUUID2 } from "crypto";
@@ -831,38 +1154,15 @@ var GLMAdapter = class extends BaseAdapter {
831
1154
  };
832
1155
  }
833
1156
  async *parseSSEStream(body) {
834
- const reader = body.getReader();
835
- const decoder = new TextDecoder();
836
- let buffer = "";
837
- try {
838
- while (true) {
839
- let done;
840
- let value;
841
- try {
842
- const result = await reader.read();
843
- done = result.done;
844
- value = result.value;
845
- } catch (err) {
846
- if (isAbortOrNetworkError2(err)) break;
847
- throw err;
848
- }
849
- if (done) break;
850
- buffer += decoder.decode(value, { stream: true });
851
- const lines = buffer.split("\n");
852
- buffer = lines.pop() ?? "";
853
- for (const line of lines) {
854
- const trimmed = line.trim();
855
- if (!trimmed || trimmed === "data: [DONE]") continue;
856
- if (!trimmed.startsWith("data: ")) continue;
857
- try {
858
- const json = JSON.parse(trimmed.slice(6));
859
- yield* this.processChunk(json);
860
- } catch {
861
- }
862
- }
1157
+ for await (const line of readStreamLines(body)) {
1158
+ const trimmed = line.trim();
1159
+ if (!trimmed || trimmed === "data: [DONE]") continue;
1160
+ if (!trimmed.startsWith("data: ")) continue;
1161
+ try {
1162
+ const json = JSON.parse(trimmed.slice(6));
1163
+ yield* this.processChunk(json);
1164
+ } catch {
863
1165
  }
864
- } finally {
865
- reader.releaseLock();
866
1166
  }
867
1167
  }
868
1168
  /** Track active tool calls: index → { id, name, args } for emitting tool_use_end */
@@ -917,11 +1217,6 @@ var GLMAdapter = class extends BaseAdapter {
917
1217
  }
918
1218
  }
919
1219
  };
920
- function isAbortOrNetworkError2(err) {
921
- const name = err.name ?? "";
922
- const msg = err.message ?? "";
923
- return name === "AbortError" || msg.includes("abort") || msg.includes("Abort") || msg.includes("timeout") || msg.includes("Timeout") || msg.includes("fetch failed") || msg.includes("ECONNRESET") || msg.includes("network");
924
- }
925
1220
 
926
1221
  // src/connect/registry.ts
927
1222
  var AdapterRegistry = class {
@@ -955,6 +1250,8 @@ var AdapterRegistry = class {
955
1250
  return new GenericCompatAdapter(config);
956
1251
  case "glm-compatible":
957
1252
  return new GLMAdapter(config);
1253
+ case "anthropic-compatible":
1254
+ return new AnthropicAdapter(config);
958
1255
  case "custom":
959
1256
  throw new Error(`Custom provider format requires a custom adapter for "${config.name}".`);
960
1257
  default:
@@ -8031,109 +8328,208 @@ function buildSystemPrompt() {
8031
8328
  // src/mcp/client.ts
8032
8329
  import { spawn as spawn2 } from "child_process";
8033
8330
  import { createInterface } from "readline";
8034
- var REQUEST_TIMEOUT = 3e4;
8331
+ var DEFAULT_REQUEST_TIMEOUT = 3e4;
8332
+ var CONNECT_TIMEOUT = 3e4;
8333
+ var MCP_PROTOCOL_VERSION = "2024-11-05";
8035
8334
  var MCPClient = class {
8036
8335
  config;
8037
8336
  process = null;
8038
8337
  rl = null;
8039
- connected = false;
8338
+ connectionState = "disconnected";
8040
8339
  nextId = 1;
8041
8340
  pendingRequests = /* @__PURE__ */ new Map();
8042
- buffer = "";
8341
+ serverCapabilities = {};
8342
+ serverInfo;
8343
+ serverInstructions;
8344
+ connectReject = null;
8043
8345
  constructor(config) {
8044
8346
  this.config = config;
8045
8347
  }
8046
8348
  async connect() {
8047
- if (this.connected) return;
8048
- const env = {
8049
- ...process.env,
8050
- ...this.config.env
8051
- };
8052
- this.process = spawn2(this.config.command, this.config.args, {
8053
- stdio: ["pipe", "pipe", "pipe"],
8054
- env
8055
- });
8056
- this.process.on("error", (err) => {
8057
- this.cleanup();
8058
- throw err;
8059
- });
8060
- this.process.on("exit", () => {
8061
- this.cleanup();
8062
- });
8063
- if (!this.process.stdout) {
8064
- throw new Error("MCP server stdout is not available");
8349
+ if (this.connectionState === "connected") return;
8350
+ if (this.connectionState === "connecting") {
8351
+ throw new Error(`MCP server "${this.config.name}" is already connecting`);
8065
8352
  }
8066
- this.rl = createInterface({ input: this.process.stdout });
8067
- this.rl.on("line", (line) => {
8068
- this.handleLine(line);
8069
- });
8070
- if (this.process.stderr) {
8071
- this.process.stderr.on("data", () => {
8072
- });
8353
+ this.connectionState = "connecting";
8354
+ try {
8355
+ await this.spawnProcess();
8356
+ await this.performHandshake();
8357
+ this.connectionState = "connected";
8358
+ } catch (error) {
8359
+ this.cleanup();
8360
+ this.connectionState = "failed";
8361
+ throw error;
8073
8362
  }
8074
- await this.sendRequest("initialize", {
8075
- protocolVersion: "2024-11-05",
8076
- capabilities: {},
8077
- clientInfo: { name: "cliskill", version: "1.0.0" }
8078
- });
8079
- this.sendNotification("notifications/initialized", {});
8080
- this.connected = true;
8081
8363
  }
8082
8364
  async disconnect() {
8083
- if (!this.process) return;
8365
+ if (this.connectionState === "disconnected") return;
8084
8366
  for (const [, pending] of this.pendingRequests) {
8085
8367
  clearTimeout(pending.timer);
8086
8368
  pending.reject(new Error("Connection closed"));
8087
8369
  }
8088
8370
  this.pendingRequests.clear();
8089
- this.process.kill("SIGTERM");
8371
+ if (this.process != null && !this.process.killed) {
8372
+ try {
8373
+ this.process.kill("SIGTERM");
8374
+ await new Promise((resolve10) => {
8375
+ if (!this.process) {
8376
+ resolve10();
8377
+ return;
8378
+ }
8379
+ const forceKillTimer = setTimeout(() => {
8380
+ try {
8381
+ this.process?.kill("SIGKILL");
8382
+ } catch {
8383
+ }
8384
+ resolve10();
8385
+ }, 5e3);
8386
+ this.process.once("exit", () => {
8387
+ clearTimeout(forceKillTimer);
8388
+ resolve10();
8389
+ });
8390
+ });
8391
+ } catch {
8392
+ }
8393
+ }
8090
8394
  this.cleanup();
8091
8395
  }
8092
8396
  async listTools() {
8093
8397
  const response = await this.sendRequest("tools/list", {});
8398
+ this.assertNoError(response);
8094
8399
  const result = response.result;
8095
8400
  return result?.tools ?? [];
8096
8401
  }
8097
8402
  async callTool(name, args) {
8098
8403
  const response = await this.sendRequest("tools/call", { name, arguments: args });
8099
- return response.result;
8404
+ this.assertNoError(response);
8405
+ const result = response.result;
8406
+ return result ?? { content: [{ type: "text", text: "No result from MCP server" }] };
8100
8407
  }
8101
8408
  async listResources() {
8102
8409
  const response = await this.sendRequest("resources/list", {});
8410
+ this.assertNoError(response);
8103
8411
  const result = response.result;
8104
8412
  return result?.resources ?? [];
8105
8413
  }
8106
8414
  async readResource(uri) {
8107
8415
  const response = await this.sendRequest("resources/read", { uri });
8416
+ this.assertNoError(response);
8108
8417
  return response.result;
8109
8418
  }
8110
8419
  async listPrompts() {
8111
8420
  const response = await this.sendRequest("prompts/list", {});
8421
+ this.assertNoError(response);
8112
8422
  const result = response.result;
8113
8423
  return result?.prompts ?? [];
8114
8424
  }
8115
8425
  isConnected() {
8116
- return this.connected;
8426
+ return this.connectionState === "connected";
8427
+ }
8428
+ getState() {
8429
+ return this.connectionState;
8430
+ }
8431
+ getServerInfo() {
8432
+ return this.serverInfo;
8433
+ }
8434
+ getServerInstructions() {
8435
+ return this.serverInstructions;
8436
+ }
8437
+ getCapabilities() {
8438
+ return this.serverCapabilities;
8439
+ }
8440
+ getConfig() {
8441
+ return this.config;
8442
+ }
8443
+ async spawnProcess() {
8444
+ const env = {
8445
+ ...process.env,
8446
+ ...this.config.env
8447
+ };
8448
+ this.process = spawn2(this.config.command, this.config.args, {
8449
+ stdio: ["pipe", "pipe", "pipe"],
8450
+ env
8451
+ });
8452
+ this.process.on("error", (err) => {
8453
+ this.handleProcessError(err);
8454
+ });
8455
+ this.process.on("exit", (code, signal) => {
8456
+ if (this.connectionState === "connected" || this.connectionState === "connecting") {
8457
+ this.handleProcessExit(code, signal);
8458
+ }
8459
+ });
8460
+ if (!this.process.stdout) {
8461
+ throw new Error(`MCP server "${this.config.name}": stdout is not available`);
8462
+ }
8463
+ if (!this.process.stdin) {
8464
+ throw new Error(`MCP server "${this.config.name}": stdin is not available`);
8465
+ }
8466
+ this.rl = createInterface({ input: this.process.stdout });
8467
+ this.rl.on("line", (line) => {
8468
+ this.handleLine(line);
8469
+ });
8470
+ if (this.process.stderr) {
8471
+ this.process.stderr.on("data", (data) => {
8472
+ const text = data.toString().trim();
8473
+ if (text) {
8474
+ console.error(`[MCP:${this.config.name}:stderr] ${text}`);
8475
+ }
8476
+ });
8477
+ }
8478
+ }
8479
+ async performHandshake() {
8480
+ const connectPromise = this.sendRequest("initialize", {
8481
+ protocolVersion: MCP_PROTOCOL_VERSION,
8482
+ capabilities: {},
8483
+ clientInfo: { name: "cliskill", version: "1.0.0" }
8484
+ });
8485
+ const timeoutPromise = new Promise((_, reject) => {
8486
+ const timer = setTimeout(() => {
8487
+ reject(new Error(
8488
+ `MCP server "${this.config.name}" connection timed out after ${CONNECT_TIMEOUT}ms`
8489
+ ));
8490
+ }, CONNECT_TIMEOUT);
8491
+ timer.unref();
8492
+ });
8493
+ const response = await Promise.race([connectPromise, timeoutPromise]);
8494
+ this.assertNoError(response);
8495
+ const initResult = response.result;
8496
+ if (!initResult) {
8497
+ throw new Error(`MCP server "${this.config.name}" returned empty initialize result`);
8498
+ }
8499
+ if (initResult.protocolVersion !== MCP_PROTOCOL_VERSION) {
8500
+ throw new Error(
8501
+ `MCP server "${this.config.name}" uses incompatible protocol version: ${initResult.protocolVersion} (expected ${MCP_PROTOCOL_VERSION})`
8502
+ );
8503
+ }
8504
+ this.serverCapabilities = initResult.capabilities ?? {};
8505
+ this.serverInfo = initResult.serverInfo;
8506
+ this.serverInstructions = initResult.instructions;
8507
+ this.sendNotification("notifications/initialized", {});
8117
8508
  }
8118
8509
  sendRequest(method, params) {
8119
8510
  return new Promise((resolve10, reject) => {
8120
8511
  if (!this.process?.stdin) {
8121
- reject(new Error("MCP server not connected"));
8512
+ reject(new Error(`MCP server "${this.config.name}" is not connected`));
8122
8513
  return;
8123
8514
  }
8124
8515
  const id = this.nextId++;
8125
8516
  const request = { jsonrpc: "2.0", id, method, params };
8126
8517
  const timer = setTimeout(() => {
8127
8518
  this.pendingRequests.delete(id);
8128
- reject(new Error(`Request timeout: ${method} (id=${id})`));
8129
- }, REQUEST_TIMEOUT);
8519
+ reject(new Error(
8520
+ `Request timeout: ${method} (id=${id}) on server "${this.config.name}"`
8521
+ ));
8522
+ }, DEFAULT_REQUEST_TIMEOUT);
8523
+ timer.unref();
8130
8524
  this.pendingRequests.set(id, { resolve: resolve10, reject, timer });
8131
8525
  const data = JSON.stringify(request) + "\n";
8132
8526
  this.process.stdin.write(data, (err) => {
8133
8527
  if (err) {
8134
8528
  clearTimeout(timer);
8135
8529
  this.pendingRequests.delete(id);
8136
- reject(err);
8530
+ reject(new Error(
8531
+ `Failed to send request to MCP server "${this.config.name}": ${err.message}`
8532
+ ));
8137
8533
  }
8138
8534
  });
8139
8535
  });
@@ -8142,19 +8538,23 @@ var MCPClient = class {
8142
8538
  if (!this.process?.stdin) return;
8143
8539
  const notification = { jsonrpc: "2.0", method, params };
8144
8540
  const data = JSON.stringify(notification) + "\n";
8145
- this.process.stdin.write(data);
8541
+ this.process.stdin.write(data, (err) => {
8542
+ if (err) {
8543
+ console.error(
8544
+ `[MCP:${this.config.name}] Failed to send notification "${method}": ${err.message}`
8545
+ );
8546
+ }
8547
+ });
8146
8548
  }
8147
8549
  handleLine(line) {
8148
- this.buffer += line;
8550
+ if (!line.trim()) return;
8149
8551
  let response;
8150
8552
  try {
8151
- response = JSON.parse(this.buffer);
8553
+ response = JSON.parse(line);
8152
8554
  } catch {
8153
8555
  return;
8154
- } finally {
8155
- this.buffer = "";
8156
8556
  }
8157
- if (response.id !== void 0 && response.id !== null) {
8557
+ if (response.id != null) {
8158
8558
  const pending = this.pendingRequests.get(response.id);
8159
8559
  if (pending) {
8160
8560
  clearTimeout(pending.timer);
@@ -8163,12 +8563,47 @@ var MCPClient = class {
8163
8563
  }
8164
8564
  }
8165
8565
  }
8566
+ handleProcessError(err) {
8567
+ const message = `MCP server "${this.config.name}" process error: ${err.message}`;
8568
+ console.error(`[MCP] ${message}`);
8569
+ for (const [, pending] of this.pendingRequests) {
8570
+ clearTimeout(pending.timer);
8571
+ pending.reject(new Error(message));
8572
+ }
8573
+ this.pendingRequests.clear();
8574
+ if (this.connectReject) {
8575
+ this.connectReject(new Error(message));
8576
+ this.connectReject = null;
8577
+ }
8578
+ this.cleanup();
8579
+ }
8580
+ handleProcessExit(code, signal) {
8581
+ const reason = signal ? `terminated by signal ${signal}` : `exited with code ${code}`;
8582
+ const message = `MCP server "${this.config.name}" ${reason}`;
8583
+ for (const [, pending] of this.pendingRequests) {
8584
+ clearTimeout(pending.timer);
8585
+ pending.reject(new Error(message));
8586
+ }
8587
+ this.pendingRequests.clear();
8588
+ this.cleanup();
8589
+ }
8590
+ assertNoError(response) {
8591
+ if (response.error) {
8592
+ const { code, message, data } = response.error;
8593
+ const detail = data ? ` | data: ${JSON.stringify(data)}` : "";
8594
+ throw new Error(
8595
+ `MCP server "${this.config.name}" returned error [${code}]: ${message}${detail}`
8596
+ );
8597
+ }
8598
+ }
8166
8599
  cleanup() {
8167
- this.connected = false;
8600
+ this.connectionState = "disconnected";
8168
8601
  this.rl?.close();
8169
8602
  this.rl = null;
8170
8603
  this.process = null;
8171
- this.buffer = "";
8604
+ this.serverCapabilities = {};
8605
+ this.serverInfo = void 0;
8606
+ this.serverInstructions = void 0;
8172
8607
  }
8173
8608
  };
8174
8609
 
@@ -8176,28 +8611,51 @@ var MCPClient = class {
8176
8611
  var MCPConnectionManager = class {
8177
8612
  clients = /* @__PURE__ */ new Map();
8178
8613
  async addServer(config) {
8614
+ if (this.clients.has(config.name)) {
8615
+ await this.removeServer(config.name);
8616
+ }
8179
8617
  const client = new MCPClient(config);
8180
- await client.connect();
8181
- this.clients.set(config.name, client);
8618
+ try {
8619
+ await client.connect();
8620
+ this.clients.set(config.name, client);
8621
+ } catch (error) {
8622
+ const message = error instanceof Error ? error.message : String(error);
8623
+ console.error(`[MCP] Failed to connect server "${config.name}": ${message}`);
8624
+ try {
8625
+ await client.disconnect();
8626
+ } catch {
8627
+ }
8628
+ throw error;
8629
+ }
8182
8630
  }
8183
8631
  async removeServer(name) {
8184
8632
  const client = this.clients.get(name);
8185
- if (client) {
8633
+ if (!client) return;
8634
+ try {
8186
8635
  await client.disconnect();
8636
+ } catch (error) {
8637
+ const message = error instanceof Error ? error.message : String(error);
8638
+ console.error(`[MCP] Error disconnecting server "${name}": ${message}`);
8639
+ } finally {
8187
8640
  this.clients.delete(name);
8188
8641
  }
8189
8642
  }
8190
8643
  getConnectedServers() {
8191
8644
  return Array.from(this.clients.entries()).filter(([, client]) => client.isConnected()).map(([name]) => name);
8192
8645
  }
8646
+ getClient(name) {
8647
+ return this.clients.get(name);
8648
+ }
8193
8649
  async getAllTools() {
8194
8650
  const allTools = [];
8195
- for (const [, client] of this.clients) {
8651
+ for (const [name, client] of this.clients) {
8196
8652
  if (!client.isConnected()) continue;
8197
8653
  try {
8198
8654
  const tools = await client.listTools();
8199
8655
  allTools.push(...tools);
8200
- } catch {
8656
+ } catch (error) {
8657
+ const message = error instanceof Error ? error.message : String(error);
8658
+ console.error(`[MCP] Failed to list tools from "${name}": ${message}`);
8201
8659
  }
8202
8660
  }
8203
8661
  return allTools;
@@ -8217,17 +8675,40 @@ var MCPConnectionManager = class {
8217
8675
  if (!client || !client.isConnected()) {
8218
8676
  return [];
8219
8677
  }
8220
- return client.listTools();
8678
+ try {
8679
+ return await client.listTools();
8680
+ } catch (error) {
8681
+ const message = error instanceof Error ? error.message : String(error);
8682
+ console.error(`[MCP] Failed to list tools from "${serverName}": ${message}`);
8683
+ return [];
8684
+ }
8221
8685
  }
8222
8686
  async disconnectAll() {
8223
- for (const [, client] of this.clients) {
8224
- try {
8225
- await client.disconnect();
8226
- } catch {
8687
+ const disconnectPromises = Array.from(this.clients.entries()).map(
8688
+ async ([name, client]) => {
8689
+ try {
8690
+ await client.disconnect();
8691
+ } catch (error) {
8692
+ const message = error instanceof Error ? error.message : String(error);
8693
+ console.error(`[MCP] Error disconnecting server "${name}": ${message}`);
8694
+ }
8227
8695
  }
8228
- }
8696
+ );
8697
+ await Promise.allSettled(disconnectPromises);
8229
8698
  this.clients.clear();
8230
8699
  }
8700
+ /** Register graceful shutdown handlers for SIGINT/SIGTERM. */
8701
+ registerShutdownHandlers() {
8702
+ const handler = async () => {
8703
+ await this.disconnectAll();
8704
+ };
8705
+ process.on("SIGINT", handler);
8706
+ process.on("SIGTERM", handler);
8707
+ return () => {
8708
+ process.off("SIGINT", handler);
8709
+ process.off("SIGTERM", handler);
8710
+ };
8711
+ }
8231
8712
  };
8232
8713
 
8233
8714
  // src/mcp/mcp-tool-adapter.ts
@@ -8236,19 +8717,23 @@ var MCPToolAdapter = class {
8236
8717
  name;
8237
8718
  description;
8238
8719
  inputSchema;
8239
- riskLevel = "safe";
8720
+ riskLevel;
8240
8721
  concurrencySafe = true;
8241
- readOnly = true;
8722
+ readOnly;
8242
8723
  serverName;
8724
+ originalToolName;
8243
8725
  mcpManager;
8244
8726
  rawInputSchema;
8245
- constructor(serverName, toolName, description, inputSchema2, mcpManager) {
8727
+ constructor(serverName, toolName, description, inputSchema2, mcpManager, annotations) {
8728
+ this.serverName = serverName;
8729
+ this.originalToolName = toolName;
8246
8730
  this.name = `mcp_${serverName}_${toolName}`;
8247
8731
  this.description = description;
8248
- this.serverName = serverName;
8249
8732
  this.mcpManager = mcpManager;
8250
8733
  this.rawInputSchema = inputSchema2;
8251
8734
  this.inputSchema = this.buildZodSchema(inputSchema2);
8735
+ this.readOnly = annotations?.readOnlyHint === true;
8736
+ this.riskLevel = this.deriveRiskLevel(annotations);
8252
8737
  }
8253
8738
  async execute(input, context) {
8254
8739
  const allowed = await context.checkPermission(this.name, JSON.stringify(input));
@@ -8256,8 +8741,16 @@ var MCPToolAdapter = class {
8256
8741
  return `Error: Permission denied for MCP tool: ${this.name}`;
8257
8742
  }
8258
8743
  try {
8259
- const result = await this.mcpManager.callTool(this.serverName, this.getOriginalName(), input);
8260
- return typeof result === "string" ? result : JSON.stringify(result, null, 2);
8744
+ const result = await this.mcpManager.callTool(
8745
+ this.serverName,
8746
+ this.originalToolName,
8747
+ input
8748
+ );
8749
+ if (result.isError) {
8750
+ const errorText = result.content.filter((block) => block.type === "text").map((block) => block.text ?? "").join("\n");
8751
+ return `Error from MCP tool ${this.name}: ${errorText || "Unknown error"}`;
8752
+ }
8753
+ return this.formatResult(result);
8261
8754
  } catch (err) {
8262
8755
  return `Error calling MCP tool ${this.name}: ${err.message}`;
8263
8756
  }
@@ -8270,12 +8763,29 @@ var MCPToolAdapter = class {
8270
8763
  };
8271
8764
  }
8272
8765
  getOriginalName() {
8273
- const parts = this.name.split("_");
8274
- return parts.slice(2).join("_");
8766
+ return this.originalToolName;
8275
8767
  }
8276
8768
  getServerName() {
8277
8769
  return this.serverName;
8278
8770
  }
8771
+ deriveRiskLevel(annotations) {
8772
+ if (annotations?.destructiveHint === true) return "destructive";
8773
+ if (annotations?.readOnlyHint === true) return "readonly";
8774
+ return "safe";
8775
+ }
8776
+ formatResult(result) {
8777
+ const parts = [];
8778
+ for (const block of result.content) {
8779
+ if (block.type === "text" && block.text != null) {
8780
+ parts.push(block.text);
8781
+ } else if (block.type === "image" && block.data != null) {
8782
+ parts.push(`[Image: ${block.mimeType ?? "unknown"}, ${block.data.length} bytes base64]`);
8783
+ } else if (block.type === "resource" && block.text != null) {
8784
+ parts.push(block.text);
8785
+ }
8786
+ }
8787
+ return parts.length > 0 ? parts.join("\n") : JSON.stringify(result, null, 2);
8788
+ }
8279
8789
  buildZodSchema(jsonSchema) {
8280
8790
  const properties = jsonSchema.properties ?? {};
8281
8791
  const required = new Set(jsonSchema.required ?? []);
@@ -8291,10 +8801,21 @@ var MCPToolAdapter = class {
8291
8801
  switch (type) {
8292
8802
  case "string":
8293
8803
  if (prop.enum) return z25.enum(prop.enum);
8804
+ if (prop.format === "date-time") return z25.string().datetime();
8805
+ if (typeof prop.minLength === "number" || typeof prop.maxLength === "number") {
8806
+ let schema = z25.string();
8807
+ if (typeof prop.minLength === "number") schema = schema.min(prop.minLength);
8808
+ if (typeof prop.maxLength === "number") schema = schema.max(prop.maxLength);
8809
+ return schema;
8810
+ }
8294
8811
  return z25.string();
8295
8812
  case "number":
8296
- case "integer":
8297
- return z25.number();
8813
+ case "integer": {
8814
+ let schema = z25.number();
8815
+ if (typeof prop.minimum === "number") schema = schema.min(prop.minimum);
8816
+ if (typeof prop.maximum === "number") schema = schema.max(prop.maximum);
8817
+ return schema;
8818
+ }
8298
8819
  case "boolean":
8299
8820
  return z25.boolean();
8300
8821
  case "array":
@@ -8323,12 +8844,15 @@ async function registerMCPTools(mcpManager, registerFn) {
8323
8844
  tool.name,
8324
8845
  tool.description ?? `MCP tool: ${tool.name}`,
8325
8846
  tool.inputSchema,
8326
- mcpManager
8847
+ mcpManager,
8848
+ tool.annotations
8327
8849
  );
8328
8850
  registerFn(adapter);
8329
8851
  registered.push(adapter.name);
8330
8852
  }
8331
- } catch {
8853
+ } catch (error) {
8854
+ const message = error instanceof Error ? error.message : String(error);
8855
+ console.error(`[MCP] Failed to register tools from "${serverName}": ${message}`);
8332
8856
  }
8333
8857
  }
8334
8858
  return registered;
@@ -8398,7 +8922,7 @@ async function connectMcpServers(config, toolRegistry) {
8398
8922
  console.error(`MCP server "${serverConfig.name}" connection failed: ${err.message}`);
8399
8923
  }
8400
8924
  }
8401
- if (connectedCount === 0) return mcpManager;
8925
+ if (connectedCount === 0) return null;
8402
8926
  const registeredTools = await registerMCPTools(mcpManager, (tool) => {
8403
8927
  try {
8404
8928
  toolRegistry.register(tool);
@@ -8408,6 +8932,7 @@ async function connectMcpServers(config, toolRegistry) {
8408
8932
  if (registeredTools.length > 0) {
8409
8933
  console.log(` MCP: ${connectedCount} server(s) connected, ${registeredTools.length} tool(s) loaded`);
8410
8934
  }
8935
+ mcpManager.registerShutdownHandlers();
8411
8936
  return mcpManager;
8412
8937
  }
8413
8938
  async function runTuiRepl(config) {
@@ -10651,6 +11176,13 @@ var PROVIDER_OPTIONS = [
10651
11176
  requiresApiKey: true,
10652
11177
  format: "openai-compatible"
10653
11178
  },
11179
+ {
11180
+ id: "anthropic",
11181
+ label: "Anthropic Claude",
11182
+ defaultBaseUrl: "https://api.anthropic.com",
11183
+ requiresApiKey: true,
11184
+ format: "anthropic-compatible"
11185
+ },
10654
11186
  {
10655
11187
  id: "ollama",
10656
11188
  label: "Ollama (local)",
@@ -10850,13 +11382,18 @@ import { useState as useState7, useEffect as useEffect2 } from "react";
10850
11382
  import { Box as Box10, Text as Text10, useInput as useInput6 } from "ink";
10851
11383
 
10852
11384
  // src/connect/fetch-models.ts
10853
- async function fetchModelsFromApi(baseUrl, apiKey) {
11385
+ async function fetchModelsFromApi(baseUrl, apiKey, format) {
10854
11386
  const url = baseUrl.replace(/\/$/, "") + "/models";
10855
11387
  const headers = {
10856
11388
  "Content-Type": "application/json"
10857
11389
  };
10858
11390
  if (apiKey) {
10859
- headers["Authorization"] = `Bearer ${apiKey}`;
11391
+ if (format === "anthropic-compatible") {
11392
+ headers["x-api-key"] = apiKey;
11393
+ headers["anthropic-version"] = "2023-06-01";
11394
+ } else {
11395
+ headers["Authorization"] = `Bearer ${apiKey}`;
11396
+ }
10860
11397
  }
10861
11398
  const response = await fetch(url, {
10862
11399
  method: "GET",
@@ -10874,9 +11411,12 @@ async function fetchModelsFromApi(baseUrl, apiKey) {
10874
11411
  }
10875
11412
  var CATEGORY_PATTERNS = [
10876
11413
  { pattern: /o[1-4](?:-|$|-mini)/i, category: "reasoning" },
11414
+ { pattern: /claude-opus|claude-[34]\.(\d)+/i, category: "reasoning" },
10877
11415
  { pattern: /reason|think|deep/i, category: "reasoning" },
11416
+ { pattern: /claude-sonnet/i, category: "code" },
10878
11417
  { pattern: /code|codestral|deepseek-coder|qwen.*coder|starcoder/i, category: "code" },
10879
11418
  { pattern: /mini|flash|fast|turbo|haiku|lite/i, category: "fast" },
11419
+ { pattern: /claude-haiku/i, category: "fast" },
10880
11420
  { pattern: /creative|dall/i, category: "creative" }
10881
11421
  ];
10882
11422
  function classifyModelName(modelName) {
@@ -10952,7 +11492,7 @@ function ModelStep() {
10952
11492
  setPhase("fallback");
10953
11493
  return;
10954
11494
  }
10955
- fetchModelsFromApi(state.baseUrl, state.apiKey || void 0).then((fetched) => {
11495
+ fetchModelsFromApi(state.baseUrl, state.apiKey || void 0, state.provider?.format).then((fetched) => {
10956
11496
  if (fetched.length === 0) {
10957
11497
  setError("No models found at this endpoint");
10958
11498
  setPhase("fallback");
@@ -11640,4 +12180,4 @@ export {
11640
12180
  MCPConnectionManager,
11641
12181
  runCli
11642
12182
  };
11643
- //# sourceMappingURL=chunk-PRO7DR3G.js.map
12183
+ //# sourceMappingURL=chunk-SDHXIBKZ.js.map