cc-hub-cli 1.1.15 → 1.1.17

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.
Files changed (2) hide show
  1. package/dist/index.js +301 -38
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import { Command as Command2 } from "commander";
10
10
  // src/config.ts
11
11
  import fs4 from "fs";
12
12
  import path4 from "path";
13
- import os3 from "os";
13
+ import os4 from "os";
14
14
 
15
15
  // src/platform/desktop-app.ts
16
16
  import fs2 from "fs";
@@ -266,16 +266,55 @@ function sanitizeToolId(id) {
266
266
  }
267
267
  return sanitized;
268
268
  }
269
+ function buildSystemMessage(system) {
270
+ if (typeof system === "string") {
271
+ return { role: "system", content: system };
272
+ }
273
+ if (Array.isArray(system)) {
274
+ const textBlocks = system.filter((b) => b.type === "text" && b.text);
275
+ const hasCacheControl = textBlocks.some((b) => b.cache_control);
276
+ if (hasCacheControl) {
277
+ const parts = textBlocks.map((b) => {
278
+ const part = { type: "text", text: b.text };
279
+ if (b.cache_control) part.cache_control = b.cache_control;
280
+ return part;
281
+ });
282
+ if (parts.length > 0) return { role: "system", content: parts };
283
+ } else {
284
+ const text = textBlocks.map((b) => b.text).join("\n");
285
+ if (text) return { role: "system", content: text };
286
+ }
287
+ }
288
+ return null;
289
+ }
290
+ function convertAnthropicContentPart(part) {
291
+ let convertedPart = null;
292
+ if (part.type === "image") {
293
+ if (part.source?.type === "base64" && part.source.media_type && part.source.data) {
294
+ const url = `data:${part.source.media_type};base64,${part.source.data}`;
295
+ debug(`transform: converting base64 image (${part.source.media_type}, ${part.source.data.length} chars)`);
296
+ convertedPart = { type: "image_url", image_url: { url } };
297
+ } else if (part.source?.type === "url" && part.source.url) {
298
+ debug(`transform: converting image url (${part.source.url.slice(0, 80)}...)`);
299
+ convertedPart = { type: "image_url", image_url: { url: part.source.url } };
300
+ } else {
301
+ warn(`transform: skipping invalid image block (missing source fields)`);
302
+ return null;
303
+ }
304
+ } else if (part.type === "text") {
305
+ convertedPart = { type: "text", text: part.text };
306
+ }
307
+ if (convertedPart && part.cache_control) {
308
+ convertedPart.cache_control = part.cache_control;
309
+ }
310
+ return convertedPart;
311
+ }
269
312
  function transformAnthropicToOpenAI(body) {
270
313
  debug(`transform: anthropic -> openai model=${body.model} messages=${(body.messages ?? []).length}`);
271
314
  const messages = [];
272
315
  if (body.system) {
273
- if (typeof body.system === "string") {
274
- messages.push({ role: "system", content: body.system });
275
- } else if (Array.isArray(body.system)) {
276
- const text = body.system.filter((b) => b.type === "text" && b.text).map((b) => b.text).join("\n");
277
- if (text) messages.push({ role: "system", content: text });
278
- }
316
+ const systemMsg = buildSystemMessage(body.system);
317
+ if (systemMsg) messages.push(systemMsg);
279
318
  }
280
319
  for (const msg of body.messages ?? []) {
281
320
  if (typeof msg.content === "string") {
@@ -301,23 +340,9 @@ function transformAnthropicToOpenAI(body) {
301
340
  (b) => b.type === "text" && b.text || b.type === "image" && (b.source?.type === "base64" && b.source.media_type && b.source.data || b.source?.type === "url" && b.source.url)
302
341
  );
303
342
  if (contentParts.length > 0) {
304
- const converted = contentParts.map((part) => {
305
- if (part.type === "image") {
306
- if (part.source?.type === "base64" && part.source.media_type && part.source.data) {
307
- const url = `data:${part.source.media_type};base64,${part.source.data}`;
308
- debug(`transform: converting base64 image (${part.source.media_type}, ${part.source.data.length} chars)`);
309
- return { type: "image_url", image_url: { url } };
310
- } else if (part.source?.type === "url" && part.source.url) {
311
- debug(`transform: converting image url (${part.source.url.slice(0, 80)}...)`);
312
- return { type: "image_url", image_url: { url: part.source.url } };
313
- }
314
- warn(`transform: skipping invalid image block (missing source fields)`);
315
- return null;
316
- }
317
- return { type: "text", text: part.text };
318
- }).filter(Boolean);
343
+ const converted = contentParts.map(convertAnthropicContentPart).filter(Boolean);
319
344
  if (converted.length === 0) {
320
- } else if (converted.every((p) => p.type === "text")) {
345
+ } else if (converted.every((p) => p.type === "text") && !converted.some((p) => p.cache_control)) {
321
346
  messages.push({
322
347
  role: "user",
323
348
  content: converted.map((p) => p.text).join("")
@@ -334,6 +359,12 @@ function transformAnthropicToOpenAI(body) {
334
359
  if (textParts.length > 0) {
335
360
  assistantMsg.content = textParts.map((b) => b.text).join("\n");
336
361
  }
362
+ const thinkingParts = msg.content.filter(
363
+ (b) => b.type === "thinking" && b.thinking
364
+ );
365
+ if (thinkingParts.length > 0) {
366
+ assistantMsg.reasoning_content = thinkingParts.map((b) => b.thinking).join("\n");
367
+ }
337
368
  const toolUseParts = msg.content.filter(
338
369
  (b) => b.type === "tool_use" && b.id
339
370
  );
@@ -386,6 +417,13 @@ function transformOpenAIResponseToAnthropic(openaiResponse, originalModel) {
386
417
  const choice = openaiResponse.choices?.[0];
387
418
  if (!choice) throw new Error("No choices in OpenAI response");
388
419
  const content = [];
420
+ if (choice.message?.reasoning_content) {
421
+ content.push({
422
+ type: "thinking",
423
+ thinking: choice.message.reasoning_content,
424
+ signature: ""
425
+ });
426
+ }
389
427
  if (choice.message?.content) {
390
428
  content.push({ type: "text", text: choice.message.content });
391
429
  }
@@ -422,7 +460,8 @@ function transformOpenAIResponseToAnthropic(openaiResponse, originalModel) {
422
460
  usage: {
423
461
  input_tokens: (openaiResponse.usage?.prompt_tokens ?? 0) - (openaiResponse.usage?.prompt_tokens_details?.cached_tokens ?? 0),
424
462
  output_tokens: openaiResponse.usage?.completion_tokens ?? 0,
425
- cache_read_input_tokens: openaiResponse.usage?.prompt_tokens_details?.cached_tokens ?? 0
463
+ cache_read_input_tokens: openaiResponse.usage?.prompt_tokens_details?.cached_tokens ?? 0,
464
+ cache_creation_input_tokens: openaiResponse.usage?.prompt_tokens_details?.cache_creation_tokens ?? 0
426
465
  }
427
466
  };
428
467
  }
@@ -445,7 +484,8 @@ data: ${JSON.stringify(data)}
445
484
  usage: {
446
485
  input_tokens: usage.input_tokens ?? 0,
447
486
  output_tokens: 0,
448
- cache_read_input_tokens: usage.cache_read_input_tokens ?? 0
487
+ cache_read_input_tokens: usage.cache_read_input_tokens ?? 0,
488
+ cache_creation_input_tokens: usage.cache_creation_input_tokens ?? 0
449
489
  }
450
490
  }
451
491
  });
@@ -454,7 +494,7 @@ data: ${JSON.stringify(data)}
454
494
  yield sse("content_block_start", {
455
495
  type: "content_block_start",
456
496
  index: i,
457
- content_block: block.type === "tool_use" ? { type: "tool_use", id: block.id, name: block.name, input: {} } : { type: "text", text: "" }
497
+ content_block: block.type === "tool_use" ? { type: "tool_use", id: block.id, name: block.name, input: {} } : block.type === "thinking" ? { type: "thinking", thinking: "", signature: block.signature ?? "" } : { type: "text", text: "" }
458
498
  });
459
499
  if (block.type === "text" && block.text) {
460
500
  yield sse("content_block_delta", {
@@ -462,6 +502,12 @@ data: ${JSON.stringify(data)}
462
502
  index: i,
463
503
  delta: { type: "text_delta", text: block.text }
464
504
  });
505
+ } else if (block.type === "thinking" && block.thinking) {
506
+ yield sse("content_block_delta", {
507
+ type: "content_block_delta",
508
+ index: i,
509
+ delta: { type: "thinking_delta", thinking: block.thinking }
510
+ });
465
511
  } else if (block.type === "tool_use" && block.input) {
466
512
  yield sse("content_block_delta", {
467
513
  type: "content_block_delta",
@@ -482,9 +528,184 @@ data: ${JSON.stringify(data)}
482
528
  yield sse("message_stop", { type: "message_stop" });
483
529
  }
484
530
 
531
+ // src/provider/kimi.ts
532
+ function transformAnthropicToKimi(body) {
533
+ debug(`transform: anthropic -> kimi model=${body.model} messages=${(body.messages ?? []).length}`);
534
+ const messages = [];
535
+ debug(`transform: anthropic -> kimi messageCount=${(body.messages ?? []).length}`);
536
+ if (body.system) {
537
+ const systemMsg = buildSystemMessage(body.system);
538
+ if (systemMsg) messages.push(systemMsg);
539
+ }
540
+ for (const msg of body.messages ?? []) {
541
+ if (typeof msg.content === "string") {
542
+ messages.push({ role: msg.role, content: msg.content });
543
+ continue;
544
+ }
545
+ if (!Array.isArray(msg.content)) {
546
+ messages.push({ role: msg.role, content: JSON.stringify(msg.content) });
547
+ continue;
548
+ }
549
+ if (msg.role === "user") {
550
+ const toolResults = msg.content.filter(
551
+ (b) => b.type === "tool_result" && b.tool_use_id
552
+ );
553
+ for (const tr of toolResults) {
554
+ messages.push({
555
+ role: "tool",
556
+ tool_call_id: sanitizeToolId(tr.tool_use_id),
557
+ content: typeof tr.content === "string" ? tr.content : Array.isArray(tr.content) ? tr.content.filter((b) => b.type === "text").map((b) => b.text).join("") : JSON.stringify(tr.content)
558
+ });
559
+ }
560
+ const contentParts = msg.content.filter(
561
+ (b) => b.type === "text" && b.text || b.type === "image" && (b.source?.type === "base64" && b.source.media_type && b.source.data || b.source?.type === "url" && b.source.url)
562
+ );
563
+ if (contentParts.length > 0) {
564
+ const converted = contentParts.map(convertAnthropicContentPart).filter(Boolean);
565
+ if (converted.length === 0) {
566
+ } else if (converted.every((p) => p.type === "text") && !converted.some((p) => p.cache_control)) {
567
+ messages.push({
568
+ role: "user",
569
+ content: converted.map((p) => p.text).join("")
570
+ });
571
+ } else {
572
+ messages.push({ role: "user", content: converted });
573
+ }
574
+ }
575
+ } else if (msg.role === "assistant") {
576
+ const assistantMsg = { role: "assistant", content: null };
577
+ const textParts = msg.content.filter(
578
+ (b) => b.type === "text" && b.text
579
+ );
580
+ if (textParts.length > 0) {
581
+ assistantMsg.content = textParts.map((b) => b.text).join("\n");
582
+ }
583
+ const thinkingParts = msg.content.filter(
584
+ (b) => b.type === "thinking" && b.thinking
585
+ );
586
+ const toolUseParts = msg.content.filter(
587
+ (b) => b.type === "tool_use" && b.id
588
+ );
589
+ if (toolUseParts.length > 0) {
590
+ assistantMsg.tool_calls = toolUseParts.map((b) => ({
591
+ id: sanitizeToolId(b.id),
592
+ type: "function",
593
+ function: {
594
+ name: b.name,
595
+ arguments: JSON.stringify(b.input ?? {})
596
+ }
597
+ }));
598
+ if (thinkingParts.length > 0) {
599
+ assistantMsg.reasoning_content = thinkingParts.map((b) => b.thinking).join("\n");
600
+ } else {
601
+ assistantMsg.reasoning_content = " ";
602
+ }
603
+ } else if (thinkingParts.length > 0) {
604
+ assistantMsg.reasoning_content = thinkingParts.map((b) => b.thinking).join("\n");
605
+ }
606
+ messages.push(assistantMsg);
607
+ }
608
+ }
609
+ const result = {
610
+ model: body.model,
611
+ messages,
612
+ stream: body.stream ?? false
613
+ };
614
+ if (body.max_tokens != null) result.max_tokens = body.max_tokens;
615
+ if (body.temperature != null) result.temperature = body.temperature;
616
+ if (body.tools?.length) {
617
+ debug(`transform: mapping ${body.tools.length} tool(s)`);
618
+ result.tools = body.tools.map((t) => ({
619
+ type: "function",
620
+ function: {
621
+ name: t.name,
622
+ description: t.description ?? "",
623
+ parameters: t.input_schema
624
+ }
625
+ }));
626
+ if (body.tool_choice) {
627
+ const tc = body.tool_choice;
628
+ if (tc.type === "auto" || tc.type === "none" || tc.type === "required") {
629
+ result.tool_choice = tc.type;
630
+ } else if (tc.type === "tool") {
631
+ result.tool_choice = {
632
+ type: "function",
633
+ function: { name: tc.name }
634
+ };
635
+ }
636
+ }
637
+ }
638
+ return result;
639
+ }
640
+ function transformKimiResponseToAnthropic(kimiResponse, originalModel) {
641
+ debug(`transform: kimi -> anthropic model=${kimiResponse.model ?? originalModel} choices=${kimiResponse.choices?.length ?? 0}`);
642
+ const choice = kimiResponse.choices?.[0];
643
+ if (!choice) throw new Error("No choices in Kimi response");
644
+ const content = [];
645
+ if (choice.message?.reasoning_content) {
646
+ content.push({
647
+ type: "thinking",
648
+ thinking: choice.message.reasoning_content,
649
+ signature: ""
650
+ });
651
+ }
652
+ if (choice.message?.content) {
653
+ content.push({ type: "text", text: choice.message.content });
654
+ }
655
+ if (choice.message?.tool_calls?.length) {
656
+ for (const tc of choice.message.tool_calls) {
657
+ let input = {};
658
+ try {
659
+ input = typeof tc.function.arguments === "string" ? JSON.parse(tc.function.arguments) : tc.function.arguments;
660
+ } catch {
661
+ input = { text: tc.function.arguments ?? "" };
662
+ }
663
+ content.push({
664
+ type: "tool_use",
665
+ id: tc.id,
666
+ name: tc.function.name,
667
+ input
668
+ });
669
+ }
670
+ }
671
+ const finishMap = {
672
+ stop: "end_turn",
673
+ length: "max_tokens",
674
+ tool_calls: "tool_use",
675
+ content_filter: "stop_sequence"
676
+ };
677
+ const cachedTokens = kimiResponse.usage?.prompt_tokens_details?.cached_tokens ?? kimiResponse.usage?.cached_tokens ?? 0;
678
+ return {
679
+ id: kimiResponse.id ?? `msg_${Date.now()}`,
680
+ type: "message",
681
+ role: "assistant",
682
+ model: originalModel,
683
+ content,
684
+ stop_reason: finishMap[choice.finish_reason] ?? "end_turn",
685
+ stop_sequence: null,
686
+ usage: {
687
+ input_tokens: (kimiResponse.usage?.prompt_tokens ?? 0) - cachedTokens,
688
+ output_tokens: kimiResponse.usage?.completion_tokens ?? 0,
689
+ cache_read_input_tokens: cachedTokens,
690
+ cache_creation_input_tokens: kimiResponse.usage?.prompt_tokens_details?.cache_creation_tokens ?? 0
691
+ }
692
+ };
693
+ }
694
+
485
695
  // src/provider/server.ts
486
696
  import http from "http";
487
- async function startOpenAIProxy(targetUrl, apiKey, model, models = [], modelMappings = {}) {
697
+ import os3 from "os";
698
+ function getKimiHeaders(claudeVersion) {
699
+ const platform = os3.platform();
700
+ const stainlessOS = platform === "darwin" ? "MacOS" : platform === "win32" ? "Windows" : "Linux";
701
+ return {
702
+ "X-Stainless-OS": stainlessOS,
703
+ "X-Stainless-Package-Version": "1.1.17",
704
+ "X-Stainless-Runtime": "node",
705
+ "User-Agent": `claude-code/${claudeVersion}`
706
+ };
707
+ }
708
+ async function startOpenAIProxy(targetUrl, apiKey, model, models = [], modelMappings = {}, provider = "openai", claudeVersion = "0.0.0") {
488
709
  const base = targetUrl.replace(/\/+$/, "");
489
710
  const server = http.createServer(async (req, res) => {
490
711
  debug(`Proxy request: ${req.method} ${req.url}`);
@@ -512,7 +733,8 @@ async function startOpenAIProxy(targetUrl, apiKey, model, models = [], modelMapp
512
733
  const isStream = !!parsed.stream;
513
734
  const requestModel = parsed.model ?? model;
514
735
  const actualModel = modelMappings[requestModel] || requestModel;
515
- const openaiBody = transformAnthropicToOpenAI({ ...parsed, model: actualModel, stream: false });
736
+ const transformBody = provider === "kimi" ? transformAnthropicToKimi : transformAnthropicToOpenAI;
737
+ const openaiBody = transformBody({ ...parsed, model: actualModel, stream: false });
516
738
  if (isStream) {
517
739
  res.writeHead(200, {
518
740
  "Content-Type": "text/event-stream",
@@ -525,7 +747,8 @@ async function startOpenAIProxy(targetUrl, apiKey, model, models = [], modelMapp
525
747
  method: "POST",
526
748
  headers: {
527
749
  "Content-Type": "application/json",
528
- "Authorization": `Bearer ${apiKey}`
750
+ "Authorization": `Bearer ${apiKey}`,
751
+ ...provider === "kimi" ? getKimiHeaders(claudeVersion) : {}
529
752
  },
530
753
  body: JSON.stringify(openaiBody)
531
754
  });
@@ -540,7 +763,8 @@ data: ${errText}
540
763
  return;
541
764
  }
542
765
  const data2 = await upstream2.json();
543
- const anthropicResponse2 = transformOpenAIResponseToAnthropic(data2, parsed.model ?? model);
766
+ const transformResponse2 = provider === "kimi" ? transformKimiResponseToAnthropic : transformOpenAIResponseToAnthropic;
767
+ const anthropicResponse2 = transformResponse2(data2, parsed.model ?? model);
544
768
  for (const chunk of synthesizeAnthropicSSE(anthropicResponse2)) {
545
769
  res.write(chunk);
546
770
  }
@@ -554,7 +778,8 @@ data: ${errText}
554
778
  method: "POST",
555
779
  headers: {
556
780
  "Content-Type": "application/json",
557
- "Authorization": `Bearer ${apiKey}`
781
+ "Authorization": `Bearer ${apiKey}`,
782
+ ...provider === "kimi" ? getKimiHeaders(claudeVersion) : {}
558
783
  },
559
784
  body: JSON.stringify(openaiBody)
560
785
  });
@@ -566,7 +791,8 @@ data: ${errText}
566
791
  return;
567
792
  }
568
793
  const data = await upstream.json();
569
- const anthropicResponse = transformOpenAIResponseToAnthropic(data, parsed.model ?? model);
794
+ const transformResponse = provider === "kimi" ? transformKimiResponseToAnthropic : transformOpenAIResponseToAnthropic;
795
+ const anthropicResponse = transformResponse(data, parsed.model ?? model);
570
796
  res.writeHead(200, { "Content-Type": "application/json" });
571
797
  res.end(JSON.stringify(anthropicResponse));
572
798
  return;
@@ -615,6 +841,10 @@ var PROVIDERS = [
615
841
  {
616
842
  name: "openai",
617
843
  description: "Embedded proxy \u2014 translates Anthropic requests to OpenAI Chat Completions format"
844
+ },
845
+ {
846
+ name: "kimi",
847
+ description: "Embedded proxy \u2014 translates Anthropic requests to Kimi API format (handles reasoning_content compatibility)"
618
848
  }
619
849
  ];
620
850
  var ANTHROPIC_ALIASES = ["claude-sonnet-4-6", "claude-opus-4-7", "claude-haiku-4-5"];
@@ -763,6 +993,29 @@ var DesktopProfileSyncer = class {
763
993
 
764
994
  // src/platform/binary-resolver.ts
765
995
  import { spawnSync } from "child_process";
996
+ var cachedVersion;
997
+ function getClaudeVersion() {
998
+ if (cachedVersion) return cachedVersion;
999
+ debug("binary-resolver: detecting Claude version");
1000
+ try {
1001
+ const result = spawnSync("claude", ["--version"], {
1002
+ shell: process.platform === "win32",
1003
+ encoding: "utf-8"
1004
+ });
1005
+ if (result.status === 0 && result.stdout) {
1006
+ const match = result.stdout.trim().match(/^(\d+\.\d+\.\d+)/);
1007
+ if (match) {
1008
+ cachedVersion = match[1];
1009
+ debug(`binary-resolver: detected Claude version ${cachedVersion}`);
1010
+ return cachedVersion;
1011
+ }
1012
+ }
1013
+ } catch {
1014
+ }
1015
+ cachedVersion = "0.0.0";
1016
+ debug("binary-resolver: could not detect Claude version, using default");
1017
+ return cachedVersion;
1018
+ }
766
1019
  var SystemBinaryResolver = class {
767
1020
  constructor(app) {
768
1021
  this.app = app;
@@ -831,10 +1084,10 @@ function createPathCodec() {
831
1084
  }
832
1085
 
833
1086
  // src/config.ts
834
- var CLAUDE_DIR = process.env.CLAUDE_DIR || path4.join(os3.homedir(), ".claude");
1087
+ var CLAUDE_DIR = process.env.CLAUDE_DIR || path4.join(os4.homedir(), ".claude");
835
1088
  var PROFILES_FILE = process.env.CLAUDE_PROFILES_FILE || path4.join(CLAUDE_DIR, "profiles.json");
836
1089
  var SETTINGS_FILE = process.env.CLAUDE_SETTINGS_FILE || path4.join(CLAUDE_DIR, "settings.json");
837
- var CLAUDE_JSON = path4.join(os3.homedir(), ".claude.json");
1090
+ var CLAUDE_JSON = path4.join(os4.homedir(), ".claude.json");
838
1091
  var PROJECTS_DIR = path4.join(CLAUDE_DIR, "projects");
839
1092
  var SESSIONS_DIR = path4.join(CLAUDE_DIR, "sessions");
840
1093
  var desktopApp = createDesktopApp();
@@ -996,15 +1249,17 @@ function execClaude(profileName, p, extraArgs) {
996
1249
  }
997
1250
  delete env.ANTHROPIC_API_KEY;
998
1251
  info(`Launching Claude with profile '${profileName}': model=${firstModel || "(default)"} url=${p.url || "(default)"} provider=${p.provider || "anthropic"} binary=${binary}`);
999
- if (p.provider === "openai") {
1252
+ if (p.provider === "openai" || p.provider === "kimi") {
1000
1253
  const allModels = p.models || (p.model ? [p.model] : []);
1001
- debug(`execClaude: starting OpenAI proxy for ${allModels.length} model(s)`);
1254
+ debug(`execClaude: starting ${p.provider} proxy for ${allModels.length} model(s)`);
1002
1255
  startOpenAIProxy(
1003
1256
  p.url || "https://api.openai.com",
1004
1257
  p.token || "",
1005
1258
  firstModel || "gpt-4o",
1006
1259
  allModels,
1007
- {}
1260
+ {},
1261
+ p.provider,
1262
+ getClaudeVersion()
1008
1263
  ).then(({ baseUrl, stop }) => {
1009
1264
  env.ANTHROPIC_BASE_URL = baseUrl;
1010
1265
  debug(`execClaude: proxy running at ${baseUrl}`);
@@ -2566,7 +2821,15 @@ function proxyCommand() {
2566
2821
  }
2567
2822
  models = [defaultModel];
2568
2823
  }
2569
- const { baseUrl, stop } = await startOpenAIProxy(targetUrl, apiKey, defaultModel, models, modelMappings);
2824
+ const { baseUrl, stop } = await startOpenAIProxy(
2825
+ targetUrl,
2826
+ apiKey,
2827
+ defaultModel,
2828
+ models,
2829
+ modelMappings,
2830
+ "openai",
2831
+ getClaudeVersion()
2832
+ );
2570
2833
  console.log(`Proxy running at ${baseUrl}`);
2571
2834
  console.log("Press Ctrl+C to stop");
2572
2835
  process.on("SIGINT", () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-hub-cli",
3
- "version": "1.1.15",
3
+ "version": "1.1.17",
4
4
  "description": "Manage Claude CLI profiles, hooks, and sessions",
5
5
  "type": "module",
6
6
  "bin": {