cc-hub-cli 1.0.11 → 1.1.1

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 (3) hide show
  1. package/README.md +8 -2
  2. package/dist/index.js +607 -333
  3. package/package.json +4 -3
package/dist/index.js CHANGED
@@ -4,63 +4,315 @@
4
4
  import { Command as Command6 } from "commander";
5
5
  import { createRequire } from "module";
6
6
 
7
- // src/profiles.ts
7
+ // src/profiles/commands.ts
8
8
  import { Command as Command2 } from "commander";
9
- import { spawnSync, spawn } from "child_process";
10
9
 
11
10
  // src/config.ts
11
+ import fs3 from "fs";
12
+ import path3 from "path";
13
+ import os2 from "os";
14
+
15
+ // src/platform/desktop-app.ts
12
16
  import fs from "fs";
13
17
  import path from "path";
14
18
  import os from "os";
15
- var CLAUDE_DIR = process.env.CLAUDE_DIR || path.join(os.homedir(), ".claude");
16
- var PROFILES_FILE = process.env.CLAUDE_PROFILES_FILE || path.join(CLAUDE_DIR, "profiles.json");
17
- var SETTINGS_FILE = process.env.CLAUDE_SETTINGS_FILE || path.join(CLAUDE_DIR, "settings.json");
18
- var CLAUDE_JSON = path.join(os.homedir(), ".claude.json");
19
- var PROJECTS_DIR = path.join(CLAUDE_DIR, "projects");
20
- var SESSIONS_DIR = path.join(CLAUDE_DIR, "sessions");
21
- var DESKTOP_SUPPORT_DIR = path.join(os.homedir(), "Library/Application Support/Claude-3p");
22
- var DESKTOP_CONFIG_LIBRARY = path.join(DESKTOP_SUPPORT_DIR, "configLibrary");
23
- var DESKTOP_META_FILE = path.join(DESKTOP_CONFIG_LIBRARY, "_meta.json");
24
- var DESKTOP_SESSIONS_DIR = path.join(DESKTOP_SUPPORT_DIR, "local-agent-mode-sessions");
25
- function isDesktopAppInstalled() {
26
- return fs.existsSync(DESKTOP_SUPPORT_DIR);
19
+ function sortSemverDesc(a, b) {
20
+ const parse = (v) => v.split(".").map((n) => parseInt(n, 10));
21
+ const av = parse(a);
22
+ const bv = parse(b);
23
+ for (let i = 0; i < Math.max(av.length, bv.length); i++) {
24
+ const an = av[i] || 0;
25
+ const bn = bv[i] || 0;
26
+ if (an !== bn) return bn - an;
27
+ }
28
+ return 0;
27
29
  }
28
- function findDesktopClaudeBinary() {
29
- const claudeCodeDir = path.join(DESKTOP_SUPPORT_DIR, "claude-code");
30
- if (!fs.existsSync(claudeCodeDir)) return void 0;
31
- let versions;
32
- try {
33
- versions = fs.readdirSync(claudeCodeDir).filter(
34
- (d) => fs.existsSync(path.join(claudeCodeDir, d, "claude.app", "Contents", "MacOS", "claude"))
35
- );
36
- } catch {
30
+ var MacOSDesktopApp = class {
31
+ supportDir = path.join(os.homedir(), "Library/Application Support/Claude-3p");
32
+ isInstalled() {
33
+ return fs.existsSync(this.supportDir);
34
+ }
35
+ getSupportDir() {
36
+ return this.isInstalled() ? this.supportDir : void 0;
37
+ }
38
+ getSessionsDir() {
39
+ return this.isInstalled() ? path.join(this.supportDir, "local-agent-mode-sessions") : void 0;
40
+ }
41
+ getConfigLibrary() {
42
+ return this.isInstalled() ? path.join(this.supportDir, "configLibrary") : void 0;
43
+ }
44
+ findBinary() {
45
+ const claudeCodeDir = path.join(this.supportDir, "claude-code");
46
+ if (!fs.existsSync(claudeCodeDir)) return void 0;
47
+ let versions;
48
+ try {
49
+ versions = fs.readdirSync(claudeCodeDir).filter(
50
+ (d) => fs.existsSync(path.join(claudeCodeDir, d, "claude.app", "Contents", "MacOS", "claude"))
51
+ );
52
+ } catch {
53
+ return void 0;
54
+ }
55
+ if (versions.length === 0) return void 0;
56
+ versions.sort(sortSemverDesc);
57
+ return path.join(claudeCodeDir, versions[0], "claude.app", "Contents", "MacOS", "claude");
58
+ }
59
+ };
60
+ var WindowsDesktopApp = class {
61
+ candidates = [
62
+ path.join(process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming"), "Claude"),
63
+ path.join(process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming"), "Claude-3p"),
64
+ path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local"), "Claude"),
65
+ path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local"), "Claude-3p"),
66
+ path.join(
67
+ process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local"),
68
+ "Packages",
69
+ "Claude_pzs8sxrjxfjjc",
70
+ "LocalCache",
71
+ "Roaming",
72
+ "Claude"
73
+ )
74
+ ];
75
+ _findSupportDir() {
76
+ for (const dir of this.candidates) {
77
+ if (fs.existsSync(dir)) return dir;
78
+ }
37
79
  return void 0;
38
80
  }
39
- if (versions.length === 0) return void 0;
40
- versions.sort((a, b) => {
41
- const parse = (v) => v.split(".").map((n) => parseInt(n, 10));
42
- const av = parse(a);
43
- const bv = parse(b);
44
- for (let i = 0; i < Math.max(av.length, bv.length); i++) {
45
- const an = av[i] || 0;
46
- const bn = bv[i] || 0;
47
- if (an !== bn) return bn - an;
48
- }
49
- return 0;
50
- });
51
- return path.join(claudeCodeDir, versions[0], "claude.app", "Contents", "MacOS", "claude");
81
+ isInstalled() {
82
+ return this._findSupportDir() !== void 0;
83
+ }
84
+ getSupportDir() {
85
+ return this._findSupportDir();
86
+ }
87
+ getSessionsDir() {
88
+ const dir = this._findSupportDir();
89
+ return dir ? path.join(dir, "local-agent-mode-sessions") : void 0;
90
+ }
91
+ getConfigLibrary() {
92
+ const dir = this._findSupportDir();
93
+ return dir ? path.join(dir, "configLibrary") : void 0;
94
+ }
95
+ findBinary() {
96
+ const win32Binary = path.join(process.env.LOCALAPPDATA || "", "Programs", "Claude", "Claude.exe");
97
+ if (fs.existsSync(win32Binary)) return win32Binary;
98
+ return void 0;
99
+ }
100
+ };
101
+ var NoOpDesktopApp = class {
102
+ isInstalled() {
103
+ return false;
104
+ }
105
+ getSupportDir() {
106
+ return void 0;
107
+ }
108
+ getSessionsDir() {
109
+ return void 0;
110
+ }
111
+ getConfigLibrary() {
112
+ return void 0;
113
+ }
114
+ findBinary() {
115
+ return void 0;
116
+ }
117
+ };
118
+
119
+ // src/platform/profile-syncer.ts
120
+ import fs2 from "fs";
121
+ import path2 from "path";
122
+ import { randomUUID } from "crypto";
123
+ function toDesktopProfile(p) {
124
+ const models = p.models || (p.model ? [p.model] : []);
125
+ const isAnthropic = p.provider === "anthropic" || !p.provider && !p.url;
126
+ if (isAnthropic && !p.url) {
127
+ return {
128
+ inferenceProvider: "1p",
129
+ inferenceModels: models.map((m) => ({ name: m, supports1m: true }))
130
+ };
131
+ }
132
+ return {
133
+ inferenceProvider: "gateway",
134
+ inferenceGatewayBaseUrl: p.url || void 0,
135
+ inferenceGatewayApiKey: p.token || void 0,
136
+ inferenceGatewayAuthScheme: "bearer",
137
+ inferenceModels: models.map((m) => ({ name: m, supports1m: true }))
138
+ };
139
+ }
140
+ var DesktopProfileSyncer = class {
141
+ constructor(app) {
142
+ this.app = app;
143
+ }
144
+ app;
145
+ isSupported() {
146
+ return this.app.isInstalled();
147
+ }
148
+ sync(name, p) {
149
+ const configLib = this.app.getConfigLibrary();
150
+ if (!configLib) return;
151
+ if (!fs2.existsSync(configLib)) {
152
+ fs2.mkdirSync(configLib, { recursive: true });
153
+ }
154
+ const meta = this.readMeta();
155
+ const entries = meta.entries || [];
156
+ let id = p.desktopId;
157
+ if (!id) {
158
+ const existingByName = entries.find((e) => e.name === name);
159
+ if (existingByName) {
160
+ id = existingByName.id;
161
+ } else {
162
+ id = randomUUID();
163
+ }
164
+ p.desktopId = id;
165
+ }
166
+ const existingIndex = entries.findIndex((e) => e.id === id);
167
+ if (existingIndex !== -1) {
168
+ entries[existingIndex].name = name;
169
+ } else {
170
+ entries.push({ id, name });
171
+ }
172
+ meta.entries = entries;
173
+ this.writeMeta(meta);
174
+ this.writeProfile(id, configLib, toDesktopProfile(p));
175
+ }
176
+ remove(name, p) {
177
+ const configLib = this.app.getConfigLibrary();
178
+ if (!configLib || !p.desktopId) return;
179
+ const meta = this.readMeta();
180
+ if (meta.entries) {
181
+ meta.entries = meta.entries.filter((e) => e.id !== p.desktopId);
182
+ }
183
+ if (meta.appliedId === p.desktopId) {
184
+ delete meta.appliedId;
185
+ }
186
+ this.writeMeta(meta);
187
+ const filePath = path2.join(configLib, `${p.desktopId}.json`);
188
+ if (fs2.existsSync(filePath)) {
189
+ fs2.unlinkSync(filePath);
190
+ }
191
+ }
192
+ setActive(p) {
193
+ const configLib = this.app.getConfigLibrary();
194
+ if (!configLib || !p.desktopId) return;
195
+ const meta = this.readMeta();
196
+ meta.appliedId = p.desktopId;
197
+ const entries = meta.entries || [];
198
+ if (!entries.some((e) => e.id === p.desktopId)) {
199
+ entries.push({ id: p.desktopId, name: "unknown" });
200
+ meta.entries = entries;
201
+ }
202
+ this.writeMeta(meta);
203
+ }
204
+ metaFile() {
205
+ const configLib = this.app.getConfigLibrary();
206
+ return configLib ? path2.join(configLib, "_meta.json") : void 0;
207
+ }
208
+ readMeta() {
209
+ const file = this.metaFile();
210
+ if (!file || !fs2.existsSync(file)) return {};
211
+ try {
212
+ return readJson(file);
213
+ } catch {
214
+ return {};
215
+ }
216
+ }
217
+ writeMeta(meta) {
218
+ const file = this.metaFile();
219
+ if (file) writeJson(file, meta);
220
+ }
221
+ writeProfile(id, configLib, data) {
222
+ writeJson(path2.join(configLib, `${id}.json`), data);
223
+ }
224
+ };
225
+
226
+ // src/platform/binary-resolver.ts
227
+ import { spawnSync } from "child_process";
228
+ var SystemBinaryResolver = class {
229
+ constructor(app) {
230
+ this.app = app;
231
+ }
232
+ app;
233
+ resolve() {
234
+ try {
235
+ const result = spawnSync("claude", ["--version"], {
236
+ shell: process.platform === "win32",
237
+ encoding: "utf-8"
238
+ });
239
+ if (result.status === 0) {
240
+ return "claude";
241
+ }
242
+ } catch {
243
+ }
244
+ const desktopBinary = this.app.findBinary();
245
+ if (desktopBinary) return desktopBinary;
246
+ console.error("Error: Could not find Claude Code CLI.");
247
+ console.error("Install it globally or install the Claude Code desktop app.");
248
+ process.exit(1);
249
+ }
250
+ };
251
+
252
+ // src/platform/path-codec.ts
253
+ var UnixPathCodec = class {
254
+ encode(p) {
255
+ return p.replace(/[\\/]/g, "-").replace(/\./g, "-").replace(/:/g, "");
256
+ }
257
+ decode(encoded) {
258
+ return encoded.replace(/--/g, "/.").replace(/-/g, "/");
259
+ }
260
+ };
261
+ var WindowsPathCodec = class {
262
+ encode(p) {
263
+ return p.replace(/[\\/]/g, "-").replace(/\./g, "-").replace(/:/g, "");
264
+ }
265
+ decode(encoded) {
266
+ const decoded = encoded.replace(/--/g, "\\.").replace(/-/g, "\\");
267
+ if (/^[A-Za-z]\\/.test(decoded)) {
268
+ return decoded[0] + ":" + decoded.slice(1);
269
+ }
270
+ return decoded;
271
+ }
272
+ };
273
+
274
+ // src/platform/index.ts
275
+ function createDesktopApp() {
276
+ if (process.platform === "darwin") return new MacOSDesktopApp();
277
+ if (process.platform === "win32") return new WindowsDesktopApp();
278
+ return new NoOpDesktopApp();
279
+ }
280
+ function createProfileSyncer() {
281
+ return new DesktopProfileSyncer(createDesktopApp());
282
+ }
283
+ function createBinaryResolver() {
284
+ return new SystemBinaryResolver(createDesktopApp());
285
+ }
286
+ function createPathCodec() {
287
+ if (process.platform === "win32") return new WindowsPathCodec();
288
+ return new UnixPathCodec();
289
+ }
290
+
291
+ // src/config.ts
292
+ var CLAUDE_DIR = process.env.CLAUDE_DIR || path3.join(os2.homedir(), ".claude");
293
+ var PROFILES_FILE = process.env.CLAUDE_PROFILES_FILE || path3.join(CLAUDE_DIR, "profiles.json");
294
+ var SETTINGS_FILE = process.env.CLAUDE_SETTINGS_FILE || path3.join(CLAUDE_DIR, "settings.json");
295
+ var CLAUDE_JSON = path3.join(os2.homedir(), ".claude.json");
296
+ var PROJECTS_DIR = path3.join(CLAUDE_DIR, "projects");
297
+ var SESSIONS_DIR = path3.join(CLAUDE_DIR, "sessions");
298
+ var desktopApp = createDesktopApp();
299
+ var DESKTOP_CONFIG_LIBRARY = desktopApp.getConfigLibrary() || "";
300
+ var DESKTOP_META_FILE = DESKTOP_CONFIG_LIBRARY ? path3.join(DESKTOP_CONFIG_LIBRARY, "_meta.json") : "";
301
+ var DESKTOP_SESSIONS_DIR = desktopApp.getSessionsDir() || "";
302
+ function isDesktopAppInstalled() {
303
+ return desktopApp.isInstalled();
52
304
  }
53
305
  function ensureFile(filePath, defaultContent) {
54
- if (!fs.existsSync(filePath)) {
55
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
56
- fs.writeFileSync(filePath, defaultContent, "utf-8");
306
+ if (!fs3.existsSync(filePath)) {
307
+ fs3.mkdirSync(path3.dirname(filePath), { recursive: true });
308
+ fs3.writeFileSync(filePath, defaultContent, "utf-8");
57
309
  }
58
310
  }
59
311
  function readJson(filePath) {
60
- return JSON.parse(fs.readFileSync(filePath, "utf-8"));
312
+ return JSON.parse(fs3.readFileSync(filePath, "utf-8"));
61
313
  }
62
314
  function writeJson(filePath, data) {
63
- fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
315
+ fs3.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
64
316
  }
65
317
  function ensureProfilesFile() {
66
318
  ensureFile(PROFILES_FILE, '{"profiles":{}}\n');
@@ -69,12 +321,12 @@ function ensureSettingsFile() {
69
321
  ensureFile(SETTINGS_FILE, "{}\n");
70
322
  }
71
323
  function fixJsonFile(filePath, fallback = {}) {
72
- if (!fs.existsSync(filePath)) return;
73
- const backupPath = path.join(CLAUDE_DIR, path.basename(filePath) + ".backup");
74
- const raw = fs.readFileSync(filePath, "utf-8");
324
+ if (!fs3.existsSync(filePath)) return;
325
+ const backupPath = path3.join(CLAUDE_DIR, path3.basename(filePath) + ".backup");
326
+ const raw = fs3.readFileSync(filePath, "utf-8");
75
327
  try {
76
328
  JSON.parse(raw);
77
- fs.copyFileSync(filePath, backupPath);
329
+ fs3.copyFileSync(filePath, backupPath);
78
330
  return;
79
331
  } catch {
80
332
  }
@@ -99,22 +351,26 @@ function fixJsonFile(filePath, fallback = {}) {
99
351
  if (openCurly > 0) text += "}".repeat(openCurly);
100
352
  try {
101
353
  JSON.parse(text);
102
- fs.writeFileSync(filePath, text + "\n", "utf-8");
103
- console.error(`Fixed invalid JSON in ${path.basename(filePath)}.`);
354
+ fs3.writeFileSync(filePath, text + "\n", "utf-8");
355
+ console.error(`Fixed invalid JSON in ${path3.basename(filePath)}.`);
104
356
  } catch {
105
- if (fs.existsSync(backupPath)) {
106
- fs.copyFileSync(backupPath, filePath);
107
- console.error(`Restored ${path.basename(filePath)} from backup.`);
357
+ if (fs3.existsSync(backupPath)) {
358
+ fs3.copyFileSync(backupPath, filePath);
359
+ console.error(`Restored ${path3.basename(filePath)} from backup.`);
108
360
  } else {
109
361
  writeJson(filePath, fallback);
110
- console.error(`Could not fix ${path.basename(filePath)}, no backup found, reset to default.`);
362
+ console.error(`Could not fix ${path3.basename(filePath)}, no backup found, reset to default.`);
111
363
  }
112
364
  }
113
365
  }
114
366
 
115
- // src/provider.ts
116
- import http from "http";
367
+ // src/profiles/runner.ts
368
+ import { spawnSync as spawnSync2, spawn } from "child_process";
369
+
370
+ // src/provider/index.ts
117
371
  import { Command } from "commander";
372
+
373
+ // src/provider/transform.ts
118
374
  function sanitizeToolId(id) {
119
375
  let sanitized = id.replace(/[^a-zA-Z0-9_-]/g, "_");
120
376
  if (!/^[a-zA-Z]/.test(sanitized)) {
@@ -325,6 +581,9 @@ data: ${JSON.stringify(data)}
325
581
  });
326
582
  yield sse("message_stop", { type: "message_stop" });
327
583
  }
584
+
585
+ // src/provider/server.ts
586
+ import http from "http";
328
587
  async function startOpenAIProxy(targetUrl, apiKey, model, models = []) {
329
588
  const base = targetUrl.replace(/\/+$/, "");
330
589
  const server = http.createServer(async (req, res) => {
@@ -436,6 +695,8 @@ function readBody(req) {
436
695
  req.on("error", reject);
437
696
  });
438
697
  }
698
+
699
+ // src/provider/index.ts
439
700
  var PROVIDERS = [
440
701
  {
441
702
  name: "anthropic",
@@ -459,10 +720,100 @@ function providerCommand() {
459
720
  return cmd;
460
721
  }
461
722
 
462
- // src/profiles.ts
463
- import { randomUUID } from "crypto";
464
- import fs2 from "fs";
465
- import path2 from "path";
723
+ // src/profiles/runner.ts
724
+ function resolveClaudeBinary() {
725
+ return createBinaryResolver().resolve();
726
+ }
727
+ function updateSettingsForProfile(p) {
728
+ ensureSettingsFile();
729
+ const settings = readJson(SETTINGS_FILE);
730
+ const models = p.models || (p.model ? [p.model] : []);
731
+ delete settings.model;
732
+ delete settings.availableModels;
733
+ const envVarsToClean = [
734
+ "ANTHROPIC_DEFAULT_OPUS_MODEL",
735
+ "ANTHROPIC_DEFAULT_OPUS_MODEL_NAME",
736
+ "ANTHROPIC_DEFAULT_OPUS_MODEL_DESCRIPTION",
737
+ "ANTHROPIC_DEFAULT_SONNET_MODEL",
738
+ "ANTHROPIC_DEFAULT_SONNET_MODEL_NAME",
739
+ "ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION",
740
+ "ANTHROPIC_DEFAULT_HAIKU_MODEL",
741
+ "ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME",
742
+ "ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION",
743
+ "ANTHROPIC_CUSTOM_MODEL_OPTION"
744
+ ];
745
+ if (settings.env) {
746
+ const env = settings.env;
747
+ for (const key of envVarsToClean) {
748
+ delete env[key];
749
+ }
750
+ }
751
+ writeJson(SETTINGS_FILE, settings);
752
+ }
753
+ function execClaude(profileName, p, extraArgs) {
754
+ updateSettingsForProfile(p);
755
+ const models = p.models || (p.model ? [p.model] : []);
756
+ const firstModel = models[0];
757
+ const binary = resolveClaudeBinary();
758
+ const cmd = [binary];
759
+ if (firstModel) cmd.push("--model", firstModel);
760
+ cmd.push(...extraArgs);
761
+ const env = {
762
+ ...process.env,
763
+ ANTHROPIC_AUTH_TOKEN: p.token || void 0,
764
+ ANTHROPIC_BASE_URL: p.url || void 0,
765
+ CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS: "1",
766
+ CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: "1"
767
+ };
768
+ if (models.length > 0) {
769
+ if (models[0]) {
770
+ env.ANTHROPIC_DEFAULT_SONNET_MODEL = models[0];
771
+ env.ANTHROPIC_DEFAULT_SONNET_MODEL_NAME = models[0];
772
+ env.ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION = `Custom: ${models[0]}`;
773
+ }
774
+ if (models[1]) {
775
+ env.ANTHROPIC_DEFAULT_OPUS_MODEL = models[1];
776
+ env.ANTHROPIC_DEFAULT_OPUS_MODEL_NAME = models[1];
777
+ env.ANTHROPIC_DEFAULT_OPUS_MODEL_DESCRIPTION = `Custom: ${models[1]}`;
778
+ }
779
+ if (models[2]) {
780
+ env.ANTHROPIC_DEFAULT_HAIKU_MODEL = models[2];
781
+ env.ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME = models[2];
782
+ env.ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION = `Custom: ${models[2]}`;
783
+ }
784
+ env.ANTHROPIC_CUSTOM_MODEL_OPTION = models[0];
785
+ }
786
+ delete env.ANTHROPIC_API_KEY;
787
+ console.error(`Using profile '${profileName}': model=${firstModel || "(default)"} url=${p.url || "(default)"} provider=${p.provider || "anthropic"}`);
788
+ if (p.provider === "openai") {
789
+ const allModels = p.models || (p.model ? [p.model] : []);
790
+ startOpenAIProxy(
791
+ p.url || "https://api.openai.com",
792
+ p.token || "",
793
+ firstModel || "gpt-4o",
794
+ allModels
795
+ ).then(({ baseUrl, stop }) => {
796
+ env.ANTHROPIC_BASE_URL = baseUrl;
797
+ const child = spawn(cmd[0], cmd.slice(1), { stdio: "inherit", env, shell: process.platform === "win32" });
798
+ child.on("close", (code) => {
799
+ stop();
800
+ process.exit(code ?? 1);
801
+ });
802
+ }).catch((err) => {
803
+ console.error("Failed to start OpenAI proxy:", err);
804
+ process.exit(1);
805
+ });
806
+ } else {
807
+ const result = spawnSync2(cmd[0], cmd.slice(1), {
808
+ stdio: "inherit",
809
+ env,
810
+ shell: process.platform === "win32"
811
+ });
812
+ process.exit(result.status ?? 1);
813
+ }
814
+ }
815
+
816
+ // src/profiles/commands.ts
466
817
  function maskToken(token) {
467
818
  if (!token) return "(unset)";
468
819
  if (token.length <= 12) return token;
@@ -498,136 +849,13 @@ function isAnthropicModel(model) {
498
849
  if (lower.startsWith("claude-")) return true;
499
850
  return false;
500
851
  }
501
- function toDesktopProfile(p) {
502
- const models = p.models || (p.model ? [p.model] : []);
503
- const isAnthropic = p.provider === "anthropic" || !p.provider && !p.url;
504
- if (isAnthropic && !p.url) {
505
- return {
506
- inferenceProvider: "1p",
507
- inferenceModels: models.map((m) => ({ name: m, supports1m: true }))
508
- };
509
- }
510
- return {
511
- inferenceProvider: "gateway",
512
- inferenceGatewayBaseUrl: p.url || void 0,
513
- inferenceGatewayApiKey: p.token || void 0,
514
- inferenceGatewayAuthScheme: "bearer",
515
- inferenceModels: models.map((m) => ({ name: m, supports1m: true }))
516
- };
517
- }
518
- function readDesktopMeta() {
519
- if (!fs2.existsSync(DESKTOP_META_FILE)) return {};
520
- try {
521
- return readJson(DESKTOP_META_FILE);
522
- } catch {
523
- return {};
524
- }
525
- }
526
- function writeDesktopMeta(meta) {
527
- writeJson(DESKTOP_META_FILE, meta);
528
- }
529
- function writeDesktopProfile(id, data) {
530
- const filePath = path2.join(DESKTOP_CONFIG_LIBRARY, `${id}.json`);
531
- writeJson(filePath, data);
532
- }
533
- function removeDesktopProfile(id) {
534
- const filePath = path2.join(DESKTOP_CONFIG_LIBRARY, `${id}.json`);
535
- if (fs2.existsSync(filePath)) {
536
- fs2.unlinkSync(filePath);
537
- }
538
- }
539
- function syncProfileToDesktop(name, p) {
540
- if (!isDesktopAppInstalled()) return;
541
- if (!fs2.existsSync(DESKTOP_CONFIG_LIBRARY)) {
542
- fs2.mkdirSync(DESKTOP_CONFIG_LIBRARY, { recursive: true });
543
- }
544
- const meta = readDesktopMeta();
545
- const entries = meta.entries || [];
546
- let id = p.desktopId;
547
- if (!id) {
548
- const existingByName = entries.find((e) => e.name === name);
549
- if (existingByName) {
550
- id = existingByName.id;
551
- } else {
552
- id = randomUUID();
553
- }
554
- p.desktopId = id;
555
- }
556
- const existingIndex = entries.findIndex((e) => e.id === id);
557
- if (existingIndex !== -1) {
558
- entries[existingIndex].name = name;
559
- } else {
560
- entries.push({ id, name });
561
- }
562
- meta.entries = entries;
563
- writeDesktopMeta(meta);
564
- writeDesktopProfile(id, toDesktopProfile(p));
565
- }
566
- function removeProfileFromDesktop(name, p) {
567
- if (!isDesktopAppInstalled() || !p.desktopId) return;
568
- const meta = readDesktopMeta();
569
- if (meta.entries) {
570
- meta.entries = meta.entries.filter((e) => e.id !== p.desktopId);
571
- }
572
- if (meta.appliedId === p.desktopId) {
573
- delete meta.appliedId;
574
- }
575
- writeDesktopMeta(meta);
576
- removeDesktopProfile(p.desktopId);
577
- }
578
- function setDesktopActiveProfile(p) {
579
- if (!isDesktopAppInstalled() || !p.desktopId) return;
580
- const meta = readDesktopMeta();
581
- meta.appliedId = p.desktopId;
582
- const entries = meta.entries || [];
583
- if (!entries.some((e) => e.id === p.desktopId)) {
584
- entries.push({ id: p.desktopId, name: "unknown" });
585
- meta.entries = entries;
586
- }
587
- writeDesktopMeta(meta);
588
- }
589
- function resolveClaudeBinary() {
590
- try {
591
- const result = spawnSync("which", ["claude"], { encoding: "utf-8" });
592
- if (result.status === 0 && result.stdout.trim()) {
593
- return "claude";
594
- }
595
- } catch {
596
- }
597
- const desktopBinary = findDesktopClaudeBinary();
598
- if (desktopBinary) return desktopBinary;
599
- console.error("Error: Could not find Claude Code CLI.");
600
- console.error("Install it globally or install the Claude Code desktop app.");
601
- process.exit(1);
602
- }
603
- function updateSettingsForProfile(p) {
604
- ensureSettingsFile();
605
- const settings = readJson(SETTINGS_FILE);
606
- const models = p.models || (p.model ? [p.model] : []);
607
- delete settings.model;
608
- delete settings.availableModels;
609
- const envVarsToClean = [
610
- "ANTHROPIC_DEFAULT_OPUS_MODEL",
611
- "ANTHROPIC_DEFAULT_OPUS_MODEL_NAME",
612
- "ANTHROPIC_DEFAULT_OPUS_MODEL_DESCRIPTION",
613
- "ANTHROPIC_DEFAULT_SONNET_MODEL",
614
- "ANTHROPIC_DEFAULT_SONNET_MODEL_NAME",
615
- "ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION",
616
- "ANTHROPIC_DEFAULT_HAIKU_MODEL",
617
- "ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME",
618
- "ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION",
619
- "ANTHROPIC_CUSTOM_MODEL_OPTION"
620
- ];
621
- if (settings.env) {
622
- for (const key of envVarsToClean) {
623
- delete settings.env[key];
624
- }
625
- }
626
- writeJson(SETTINGS_FILE, settings);
852
+ function collect(value, previous) {
853
+ return previous.concat([value]);
627
854
  }
628
855
  function profileCommand() {
629
856
  const profile = new Command2("profile").description("Manage Claude CLI profiles");
630
- profile.command("add").description("Add or update a profile").argument("<name>", "Profile name").option("-m, --model <model>", "Model ID (e.g. claude-opus-4-6) - can be used multiple times (max 3)", collect, []).option("-t, --token <token>", "API key / token").option("-u, --url <url>", "Base URL (e.g. https://api.anthropic.com)").option("-p, --provider <provider>", "Provider type: anthropic (default) or openai").action((name, opts) => {
857
+ const syncer = createProfileSyncer();
858
+ profile.command("add").description("Add or update a profile").argument("<name>", "Profile name").option("-m, --model <model>", "Model ID - can be used multiple times (max 3)", collect, []).option("-t, --token <token>", "API key / token").option("-u, --url <url>", "Base URL").option("-p, --provider <provider>", "Provider type: anthropic (default) or openai").action((name, opts) => {
631
859
  const models = opts.model && opts.model.length > 0 ? opts.model : void 0;
632
860
  if (models && models.length > 3) {
633
861
  console.error("Error: A profile can have at most 3 models.");
@@ -644,11 +872,11 @@ function profileCommand() {
644
872
  if (opts.url) profile2.url = opts.url;
645
873
  if (opts.provider) profile2.provider = opts.provider;
646
874
  data.profiles[name] = profile2;
647
- syncProfileToDesktop(name, profile2);
875
+ syncer.sync(name, profile2);
648
876
  writeJson(PROFILES_FILE, data);
649
877
  console.log(`Profile '${name}' saved.`);
650
878
  });
651
- profile.command("update").description("Update fields of an existing profile").argument("<name>", "Profile name (must already exist)").option("-m, --model <model>", "Model ID - can be used multiple times to set multiple models (max 3)", collect, []).option("-d, --delete-model <model>", "Remove model ID - can be used multiple times", collect, []).option("-t, --token <token>", "API key / token").option("-u, --url <url>", "Base URL").option("-p, --provider <provider>", "Provider type: anthropic (default) or openai").action((name, opts) => {
879
+ profile.command("update").description("Update fields of an existing profile").argument("<name>", "Profile name (must already exist)").option("-m, --model <model>", "Model ID - can be used multiple times", collect, []).option("-d, --delete-model <model>", "Remove model ID - can be used multiple times", collect, []).option("-t, --token <token>", "API key / token").option("-u, --url <url>", "Base URL").option("-p, --provider <provider>", "Provider type").action((name, opts) => {
652
880
  ensureProfilesFile();
653
881
  const data = readJson(PROFILES_FILE);
654
882
  if (!data.profiles[name]) {
@@ -705,7 +933,7 @@ function profileCommand() {
705
933
  if (opts.token) p.token = opts.token;
706
934
  if (opts.url) p.url = opts.url;
707
935
  if (opts.provider) p.provider = opts.provider;
708
- syncProfileToDesktop(name, p);
936
+ syncer.sync(name, p);
709
937
  writeJson(PROFILES_FILE, data);
710
938
  console.log(`Profile '${name}' updated.`);
711
939
  });
@@ -779,7 +1007,7 @@ function profileCommand() {
779
1007
  console.error(`Profile '${name}' not found.`);
780
1008
  process.exit(1);
781
1009
  }
782
- removeProfileFromDesktop(name, data.profiles[name]);
1010
+ syncer.remove(name, data.profiles[name]);
783
1011
  delete data.profiles[name];
784
1012
  writeJson(PROFILES_FILE, data);
785
1013
  console.log(`Profile '${name}' removed.`);
@@ -811,12 +1039,12 @@ function profileCommand() {
811
1039
  process.exit(1);
812
1040
  }
813
1041
  data.default = name;
814
- setDesktopActiveProfile(data.profiles[name]);
1042
+ syncer.setActive(data.profiles[name]);
815
1043
  writeJson(PROFILES_FILE, data);
816
1044
  console.log(`Default profile set to '${name}'.`);
817
1045
  });
818
1046
  profile.command("sync").description("Synchronize all CLI profiles to the Claude desktop app").action(() => {
819
- if (!isDesktopAppInstalled()) {
1047
+ if (!syncer.isSupported()) {
820
1048
  console.error("Claude desktop app is not installed.");
821
1049
  process.exit(1);
822
1050
  }
@@ -827,83 +1055,17 @@ function profileCommand() {
827
1055
  console.log("No profiles to sync.");
828
1056
  return;
829
1057
  }
830
- if (!fs2.existsSync(DESKTOP_CONFIG_LIBRARY)) {
831
- fs2.mkdirSync(DESKTOP_CONFIG_LIBRARY, { recursive: true });
832
- }
833
1058
  for (const name of names) {
834
1059
  const p = data.profiles[name];
835
- syncProfileToDesktop(name, p);
1060
+ syncer.sync(name, p);
836
1061
  }
837
1062
  writeJson(PROFILES_FILE, data);
838
1063
  console.log(`Synced ${names.length} profile(s) to the desktop app.`);
839
1064
  });
840
1065
  return profile;
841
1066
  }
842
- function collect(value, previous) {
843
- return previous.concat([value]);
844
- }
845
- function execClaude(profileName, p, extraArgs) {
846
- updateSettingsForProfile(p);
847
- const models = p.models || (p.model ? [p.model] : []);
848
- const firstModel = models[0];
849
- const binary = resolveClaudeBinary();
850
- const cmd = [binary];
851
- if (firstModel) cmd.push("--model", firstModel);
852
- cmd.push(...extraArgs);
853
- const env = {
854
- ...process.env,
855
- ANTHROPIC_AUTH_TOKEN: p.token || void 0,
856
- ANTHROPIC_BASE_URL: p.url || void 0,
857
- CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS: "1",
858
- CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: "1"
859
- };
860
- if (models.length > 0) {
861
- if (models[0]) {
862
- env.ANTHROPIC_DEFAULT_SONNET_MODEL = models[0];
863
- env.ANTHROPIC_DEFAULT_SONNET_MODEL_NAME = models[0];
864
- env.ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION = `Custom: ${models[0]}`;
865
- }
866
- if (models[1]) {
867
- env.ANTHROPIC_DEFAULT_OPUS_MODEL = models[1];
868
- env.ANTHROPIC_DEFAULT_OPUS_MODEL_NAME = models[1];
869
- env.ANTHROPIC_DEFAULT_OPUS_MODEL_DESCRIPTION = `Custom: ${models[1]}`;
870
- }
871
- if (models[2]) {
872
- env.ANTHROPIC_DEFAULT_HAIKU_MODEL = models[2];
873
- env.ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME = models[2];
874
- env.ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION = `Custom: ${models[2]}`;
875
- }
876
- env.ANTHROPIC_CUSTOM_MODEL_OPTION = models[0];
877
- }
878
- delete env.ANTHROPIC_API_KEY;
879
- console.error(`Using profile '${profileName}': model=${firstModel || "(default)"} url=${p.url || "(default)"} provider=${p.provider || "anthropic"}`);
880
- if (p.provider === "openai") {
881
- const allModels = p.models || (p.model ? [p.model] : []);
882
- startOpenAIProxy(
883
- p.url || "https://api.openai.com",
884
- p.token || "",
885
- firstModel || "gpt-4o",
886
- allModels
887
- ).then(({ baseUrl, stop }) => {
888
- env.ANTHROPIC_BASE_URL = baseUrl;
889
- const child = spawn(cmd[0], cmd.slice(1), { stdio: "inherit", env });
890
- child.on("close", (code) => {
891
- stop();
892
- process.exit(code ?? 1);
893
- });
894
- }).catch((err) => {
895
- console.error("Failed to start OpenAI proxy:", err);
896
- process.exit(1);
897
- });
898
- } else {
899
- const result = spawnSync(cmd[0], cmd.slice(1), {
900
- stdio: "inherit",
901
- env
902
- });
903
- process.exit(result.status ?? 1);
904
- }
905
- }
906
1067
  function useCommand() {
1068
+ const syncer = createProfileSyncer();
907
1069
  return new Command2("use").description("Set a profile as the default").argument("<name>", "Profile name").action((name) => {
908
1070
  ensureProfilesFile();
909
1071
  const data = readJson(PROFILES_FILE);
@@ -912,7 +1074,7 @@ function useCommand() {
912
1074
  process.exit(1);
913
1075
  }
914
1076
  data.default = name;
915
- setDesktopActiveProfile(data.profiles[name]);
1077
+ syncer.setActive(data.profiles[name]);
916
1078
  writeJson(PROFILES_FILE, data);
917
1079
  console.log(`Default profile set to '${name}'.`);
918
1080
  });
@@ -940,7 +1102,7 @@ function runCommand() {
940
1102
  });
941
1103
  }
942
1104
 
943
- // src/hooks.ts
1105
+ // src/hooks/commands.ts
944
1106
  import { Command as Command3 } from "commander";
945
1107
  function buildFlat(data) {
946
1108
  const rows = [];
@@ -980,26 +1142,29 @@ function buildFlat(data) {
980
1142
  rows.sort((a, b) => a.seq - b.seq);
981
1143
  return rows;
982
1144
  }
1145
+ function displayHookList(data) {
1146
+ const rows = buildFlat(data);
1147
+ if (rows.length === 0) {
1148
+ console.log("No hooks defined.");
1149
+ return;
1150
+ }
1151
+ const fmt = (idx, active, event, matcher, cmd) => `${String(idx).padEnd(4)} ${active.padEnd(2)} ${event.padEnd(22)} ${matcher.padEnd(25)} ${cmd}`;
1152
+ console.log(fmt(0, "", "EVENT", "MATCHER", "COMMAND").replace(/^IDX/, "IDX").replace(/^0/, "IDX"));
1153
+ console.log(fmt(0, "", "-----", "-------", "-------").replace(/^0/, "---"));
1154
+ for (let idx = 0; idx < rows.length; idx++) {
1155
+ const r = rows[idx];
1156
+ const marker = r.active ? " " : "\u2717";
1157
+ const matcher = r.matcher || "(any)";
1158
+ const cmd = r.command.length > 60 ? r.command.slice(0, 60) + "\u2026" : r.command;
1159
+ console.log(fmt(idx, marker, r.event, matcher, cmd));
1160
+ }
1161
+ }
983
1162
  function hooksCommand() {
984
1163
  const hooks = new Command3("hook").description("Manage Claude Code hooks in settings.json");
985
1164
  hooks.command("list").description("List all hooks").action(() => {
986
1165
  ensureSettingsFile();
987
1166
  const data = readJson(SETTINGS_FILE);
988
- const rows = buildFlat(data);
989
- if (rows.length === 0) {
990
- console.log("No hooks defined.");
991
- return;
992
- }
993
- const fmt = (idx, active, event, matcher, cmd) => `${String(idx).padEnd(4)} ${active.padEnd(2)} ${event.padEnd(22)} ${matcher.padEnd(25)} ${cmd}`;
994
- console.log(fmt(0, "", "EVENT", "MATCHER", "COMMAND").replace(/^IDX/, "IDX").replace(/^0/, "IDX"));
995
- console.log(fmt(0, "", "-----", "-------", "-------").replace(/^0/, "---"));
996
- for (let idx = 0; idx < rows.length; idx++) {
997
- const r = rows[idx];
998
- const marker = r.active ? " " : "\u2717";
999
- const matcher = r.matcher || "(any)";
1000
- const cmd = r.command.length > 60 ? r.command.slice(0, 60) + "\u2026" : r.command;
1001
- console.log(fmt(idx, marker, r.event, matcher, cmd));
1002
- }
1167
+ displayHookList(data);
1003
1168
  });
1004
1169
  hooks.command("add").description("Add a hook to settings.json").requiredOption("-e, --event <event>", "Hook event (PreToolUse|PostToolUse|Notification|Stop|UserPromptSubmit|PermissionRequest)").option("-m, --matcher <matcher>", "Tool name matcher (omit for catch-all)").requiredOption("-c, --command <command>", "Shell command to run").option("-a, --async", "Run the hook asynchronously").action((opts) => {
1005
1170
  ensureSettingsFile();
@@ -1092,6 +1257,8 @@ function hooksCommand() {
1092
1257
  data._cc_hub_disabled = remaining;
1093
1258
  if (remaining.length === 0) delete data._cc_hub_disabled;
1094
1259
  writeJson(SETTINGS_FILE, data);
1260
+ console.log("");
1261
+ displayHookList(data);
1095
1262
  });
1096
1263
  hooks.command("disable").description("Disable one or more hooks (removes from active)").requiredOption("-i, --index <indexes...>", "Global index from 'hooks list' (repeatable)", (v, prev) => {
1097
1264
  prev = prev || [];
@@ -1128,21 +1295,52 @@ function hooksCommand() {
1128
1295
  console.log(`Hook ${t} (${r.event}) disabled.`);
1129
1296
  }
1130
1297
  writeJson(SETTINGS_FILE, data);
1298
+ console.log("");
1299
+ displayHookList(data);
1131
1300
  });
1132
1301
  return hooks;
1133
1302
  }
1134
1303
 
1135
- // src/sessions.ts
1136
- import { Command as Command4 } from "commander";
1137
- import fs3 from "fs";
1138
- import path3 from "path";
1139
- import { execSync } from "child_process";
1304
+ // src/sessions/codec.ts
1140
1305
  function encodePath(p) {
1141
- return p.replace(/\./g, "DOTMARK").replace(/\//g, "-").replace(/DOTMARK/g, "-");
1306
+ return createPathCodec().encode(p);
1142
1307
  }
1143
1308
  function decodePath(encoded) {
1144
- return encoded.replace(/--/g, "/.").replace(/-/g, "/");
1309
+ return createPathCodec().decode(encoded);
1310
+ }
1311
+
1312
+ // src/sessions/stats.ts
1313
+ import fs4 from "fs";
1314
+ import path4 from "path";
1315
+ function getDirSize(dir) {
1316
+ let total = 0;
1317
+ try {
1318
+ for (const entry of fs4.readdirSync(dir, { withFileTypes: true })) {
1319
+ const fullPath = path4.join(dir, entry.name);
1320
+ if (entry.isDirectory()) {
1321
+ total += getDirSize(fullPath);
1322
+ } else {
1323
+ total += fs4.statSync(fullPath).size;
1324
+ }
1325
+ }
1326
+ } catch {
1327
+ }
1328
+ return total;
1329
+ }
1330
+ function formatSize(bytes) {
1331
+ const units = ["B", "KB", "MB", "GB"];
1332
+ let size = bytes;
1333
+ let unitIndex = 0;
1334
+ while (size >= 1024 && unitIndex < units.length - 1) {
1335
+ size /= 1024;
1336
+ unitIndex++;
1337
+ }
1338
+ return `${Math.round(size * 10) / 10}${units[unitIndex]}`;
1145
1339
  }
1340
+
1341
+ // src/sessions/utils.ts
1342
+ import fs5 from "fs";
1343
+ import path5 from "path";
1146
1344
  function formatTimestamp(ms) {
1147
1345
  const d = new Date(ms);
1148
1346
  const pad = (n) => String(n).padStart(2, "0");
@@ -1150,9 +1348,9 @@ function formatTimestamp(ms) {
1150
1348
  }
1151
1349
  function findProjectDir(query) {
1152
1350
  const encoded = encodePath(query);
1153
- if (fs3.existsSync(path3.join(PROJECTS_DIR, encoded))) return encoded;
1351
+ if (fs5.existsSync(path5.join(PROJECTS_DIR, encoded))) return encoded;
1154
1352
  try {
1155
- const dirs = fs3.readdirSync(PROJECTS_DIR);
1353
+ const dirs = fs5.readdirSync(PROJECTS_DIR);
1156
1354
  const match = dirs.find((d) => d.toLowerCase().includes(query.toLowerCase()));
1157
1355
  return match || null;
1158
1356
  } catch {
@@ -1164,7 +1362,7 @@ function parseSessionMeta(filePath) {
1164
1362
  let slug = "";
1165
1363
  let customTitle = "";
1166
1364
  try {
1167
- const lines = fs3.readFileSync(filePath, "utf-8").split("\n");
1365
+ const lines = fs5.readFileSync(filePath, "utf-8").split("\n");
1168
1366
  for (const line of lines) {
1169
1367
  if (!line.trim()) continue;
1170
1368
  try {
@@ -1216,32 +1414,39 @@ function snippet(text, query, width = 150) {
1216
1414
  const suffix = end < text.length ? "..." : "";
1217
1415
  return prefix + text.slice(start, end) + suffix;
1218
1416
  }
1417
+
1418
+ // src/sessions/commands.ts
1419
+ import { Command as Command4 } from "commander";
1420
+ import fs6 from "fs";
1421
+ import path6 from "path";
1219
1422
  function sessionCommand() {
1220
1423
  const session = new Command4("session").description("Manage Claude Code sessions");
1424
+ const desktopApp2 = createDesktopApp();
1425
+ const DESKTOP_SESSIONS_DIR2 = desktopApp2.getSessionsDir() || "";
1221
1426
  session.command("list").description("List all Claude Code project sessions").option("-n, --limit <n>", "Max number of projects to show", "30").option("-s, --short", "Show encoded names only (no decoding)").option("-j, --json", "Output as JSON lines").action((opts) => {
1222
1427
  const limit = parseInt(opts.limit, 10);
1223
1428
  let dirs;
1224
1429
  try {
1225
- dirs = fs3.readdirSync(PROJECTS_DIR);
1430
+ dirs = fs6.readdirSync(PROJECTS_DIR);
1226
1431
  } catch {
1227
1432
  console.log("No projects directory found.");
1228
1433
  return;
1229
1434
  }
1230
1435
  dirs.sort((a, b) => {
1231
- const statA = fs3.statSync(path3.join(PROJECTS_DIR, a));
1232
- const statB = fs3.statSync(path3.join(PROJECTS_DIR, b));
1436
+ const statA = fs6.statSync(path6.join(PROJECTS_DIR, a));
1437
+ const statB = fs6.statSync(path6.join(PROJECTS_DIR, b));
1233
1438
  return statB.mtimeMs - statA.mtimeMs;
1234
1439
  });
1235
1440
  let count = 0;
1236
1441
  for (const projDir of dirs) {
1237
1442
  if (count >= limit) break;
1238
- const fullPath = path3.join(PROJECTS_DIR, projDir);
1443
+ const fullPath = path6.join(PROJECTS_DIR, projDir);
1239
1444
  let nSessions = 0;
1240
1445
  try {
1241
- nSessions = fs3.readdirSync(fullPath).filter((f) => f.endsWith(".jsonl")).length;
1446
+ nSessions = fs6.readdirSync(fullPath).filter((f) => f.endsWith(".jsonl")).length;
1242
1447
  } catch {
1243
1448
  }
1244
- const stat = fs3.statSync(fullPath);
1449
+ const stat = fs6.statSync(fullPath);
1245
1450
  const decoded = decodePath(projDir);
1246
1451
  if (opts.json) {
1247
1452
  console.log(JSON.stringify({ project: decoded, sessions: nSessions, modified: Math.floor(stat.mtimeMs) }));
@@ -1259,7 +1464,7 @@ function sessionCommand() {
1259
1464
  console.error(`No project matched: ${project}`);
1260
1465
  process.exit(1);
1261
1466
  }
1262
- const fullPath = path3.join(PROJECTS_DIR, projDir);
1467
+ const fullPath = path6.join(PROJECTS_DIR, projDir);
1263
1468
  console.log(`Project: ${decodePath(projDir)}`);
1264
1469
  console.log(`Dir: ${fullPath}`);
1265
1470
  console.log("");
@@ -1268,16 +1473,16 @@ function sessionCommand() {
1268
1473
  console.log(fmt("----------", "----", "-------", "--------"));
1269
1474
  let files;
1270
1475
  try {
1271
- files = fs3.readdirSync(fullPath).filter((f) => f.endsWith(".jsonl"));
1476
+ files = fs6.readdirSync(fullPath).filter((f) => f.endsWith(".jsonl"));
1272
1477
  } catch {
1273
1478
  return;
1274
1479
  }
1275
1480
  for (const file of files) {
1276
- const filePath = path3.join(fullPath, file);
1481
+ const filePath = path6.join(fullPath, file);
1277
1482
  const sessionId = file.replace(/\.jsonl$/, "");
1278
1483
  let msgCount = 0;
1279
1484
  try {
1280
- const content = fs3.readFileSync(filePath, "utf-8");
1485
+ const content = fs6.readFileSync(filePath, "utf-8");
1281
1486
  msgCount = content ? content.split("\n").filter((l) => l.trim()).length : 0;
1282
1487
  } catch {
1283
1488
  }
@@ -1285,7 +1490,7 @@ function sessionCommand() {
1285
1490
  console.log(fmt(sessionId, slug || "-", started, String(msgCount)));
1286
1491
  if (opts.verbose) {
1287
1492
  try {
1288
- const lines = fs3.readFileSync(filePath, "utf-8").split("\n");
1493
+ const lines = fs6.readFileSync(filePath, "utf-8").split("\n");
1289
1494
  for (const line of lines) {
1290
1495
  if (!line.trim()) continue;
1291
1496
  try {
@@ -1315,7 +1520,7 @@ function sessionCommand() {
1315
1520
  session.command("search").description("Search conversation history across all projects").argument("<query>", "Text to search for").option("-p, --project <project>", "Filter to a specific project (partial match)").option("-n, --limit <n>", "Max number of matching files to show", "20").option("-i, --ignore-case", "Case-insensitive search").action((query, opts) => {
1316
1521
  let searchRoots = [{ root: PROJECTS_DIR, label: "" }];
1317
1522
  if (isDesktopAppInstalled()) {
1318
- searchRoots.push({ root: DESKTOP_SESSIONS_DIR, label: "[desktop] " });
1523
+ searchRoots.push({ root: DESKTOP_SESSIONS_DIR2, label: "[desktop] " });
1319
1524
  }
1320
1525
  if (opts.project) {
1321
1526
  const projDir = findProjectDir(opts.project);
@@ -1323,7 +1528,7 @@ function sessionCommand() {
1323
1528
  console.error(`No project matched: ${opts.project}`);
1324
1529
  process.exit(1);
1325
1530
  }
1326
- searchRoots = [{ root: path3.join(PROJECTS_DIR, projDir), label: "" }];
1531
+ searchRoots = [{ root: path6.join(PROJECTS_DIR, projDir), label: "" }];
1327
1532
  }
1328
1533
  const limit = parseInt(opts.limit, 10);
1329
1534
  let count = 0;
@@ -1331,18 +1536,18 @@ function sessionCommand() {
1331
1536
  if (count >= limit) return;
1332
1537
  let entries;
1333
1538
  try {
1334
- entries = fs3.readdirSync(dir, { withFileTypes: true });
1539
+ entries = fs6.readdirSync(dir, { withFileTypes: true });
1335
1540
  } catch {
1336
1541
  return;
1337
1542
  }
1338
1543
  for (const entry of entries) {
1339
1544
  if (count >= limit) break;
1340
- const fullPath = path3.join(dir, entry.name);
1545
+ const fullPath = path6.join(dir, entry.name);
1341
1546
  if (entry.isDirectory()) {
1342
1547
  searchDir(fullPath, label, baseDir);
1343
1548
  } else if (entry.name.endsWith(".jsonl")) {
1344
1549
  try {
1345
- const content = fs3.readFileSync(fullPath, "utf-8");
1550
+ const content = fs6.readFileSync(fullPath, "utf-8");
1346
1551
  const lines = content.split("\n");
1347
1552
  let found = false;
1348
1553
  for (let lineno = 0; lineno < lines.length; lineno++) {
@@ -1351,9 +1556,9 @@ function sessionCommand() {
1351
1556
  const match = opts.ignoreCase ? line.toLowerCase().includes(query.toLowerCase()) : line.includes(query);
1352
1557
  if (match) {
1353
1558
  if (!found) {
1354
- const relPath = path3.relative(baseDir, fullPath);
1355
- const projEnc = relPath.split("/")[0];
1356
- const sessionId = path3.basename(fullPath, ".jsonl");
1559
+ const relPath = path6.relative(baseDir, fullPath);
1560
+ const projEnc = relPath.split(path6.sep)[0];
1561
+ const sessionId = path6.basename(fullPath, ".jsonl");
1357
1562
  const projName = label ? projEnc : decodePath(projEnc);
1358
1563
  console.log(`${label}[${projName} \u2192 ${sessionId}]`);
1359
1564
  found = true;
@@ -1390,7 +1595,7 @@ function sessionCommand() {
1390
1595
  session.command("ps").description("Show active Claude Code processes").action(() => {
1391
1596
  let files;
1392
1597
  try {
1393
- files = fs3.readdirSync(SESSIONS_DIR).filter((f) => f.endsWith(".json"));
1598
+ files = fs6.readdirSync(SESSIONS_DIR).filter((f) => f.endsWith(".json"));
1394
1599
  } catch {
1395
1600
  console.log("(no session files found)");
1396
1601
  return;
@@ -1404,7 +1609,7 @@ function sessionCommand() {
1404
1609
  console.log(fmt("---", "----------", "-------", "---", ""));
1405
1610
  for (const file of files) {
1406
1611
  try {
1407
- const data = JSON.parse(fs3.readFileSync(path3.join(SESSIONS_DIR, file), "utf-8"));
1612
+ const data = JSON.parse(fs6.readFileSync(path6.join(SESSIONS_DIR, file), "utf-8"));
1408
1613
  const pid = String(data.pid || "?");
1409
1614
  const sessionId = data.sessionId || "?";
1410
1615
  const cwd = data.cwd || "?";
@@ -1430,8 +1635,8 @@ function sessionCommand() {
1430
1635
  const walk = (dir) => {
1431
1636
  const results = [];
1432
1637
  try {
1433
- for (const entry of fs3.readdirSync(dir, { withFileTypes: true })) {
1434
- const fullPath = path3.join(dir, entry.name);
1638
+ for (const entry of fs6.readdirSync(dir, { withFileTypes: true })) {
1639
+ const fullPath = path6.join(dir, entry.name);
1435
1640
  if (entry.isDirectory()) results.push(...walk(fullPath));
1436
1641
  else if (entry.name.endsWith(".jsonl")) results.push(fullPath);
1437
1642
  }
@@ -1440,7 +1645,7 @@ function sessionCommand() {
1440
1645
  return results;
1441
1646
  };
1442
1647
  try {
1443
- nProjects = fs3.readdirSync(PROJECTS_DIR).length;
1648
+ nProjects = fs6.readdirSync(PROJECTS_DIR).length;
1444
1649
  } catch {
1445
1650
  }
1446
1651
  try {
@@ -1448,7 +1653,7 @@ function sessionCommand() {
1448
1653
  nSessions = sessionFiles.length;
1449
1654
  for (const f of sessionFiles) {
1450
1655
  try {
1451
- const content = fs3.readFileSync(f, "utf-8");
1656
+ const content = fs6.readFileSync(f, "utf-8");
1452
1657
  totalMsgs += content ? content.split("\n").filter((l) => l.trim()).length : 0;
1453
1658
  } catch {
1454
1659
  }
@@ -1457,11 +1662,11 @@ function sessionCommand() {
1457
1662
  }
1458
1663
  if (isDesktopAppInstalled()) {
1459
1664
  try {
1460
- const desktopFiles = walk(DESKTOP_SESSIONS_DIR);
1665
+ const desktopFiles = walk(DESKTOP_SESSIONS_DIR2);
1461
1666
  nDesktopSessions = desktopFiles.length;
1462
1667
  for (const f of desktopFiles) {
1463
1668
  try {
1464
- const content = fs3.readFileSync(f, "utf-8");
1669
+ const content = fs6.readFileSync(f, "utf-8");
1465
1670
  nDesktopMsgs += content ? content.split("\n").filter((l) => l.trim()).length : 0;
1466
1671
  } catch {
1467
1672
  }
@@ -1470,7 +1675,7 @@ function sessionCommand() {
1470
1675
  }
1471
1676
  }
1472
1677
  try {
1473
- nActive = fs3.readdirSync(SESSIONS_DIR).filter((f) => f.endsWith(".json")).length;
1678
+ nActive = fs6.readdirSync(SESSIONS_DIR).filter((f) => f.endsWith(".json")).length;
1474
1679
  } catch {
1475
1680
  }
1476
1681
  console.log(`Projects: ${nProjects}`);
@@ -1484,17 +1689,14 @@ function sessionCommand() {
1484
1689
  }
1485
1690
  console.log(`Active procs: ${nActive} (in ${SESSIONS_DIR})`);
1486
1691
  console.log("");
1487
- try {
1488
- const totalSize = execSync(`du -sh "${path3.join(process.env.CLAUDE_DIR || path3.join(process.env.HOME || "", ".claude"))}" 2>/dev/null`, { encoding: "utf-8" }).trim().split(/\s+/)[0];
1489
- const projSize = execSync(`du -sh "${PROJECTS_DIR}" 2>/dev/null`, { encoding: "utf-8" }).trim().split(/\s+/)[0];
1490
- const desktopSize = isDesktopAppInstalled() ? execSync(`du -sh "${DESKTOP_SESSIONS_DIR}" 2>/dev/null`, { encoding: "utf-8" }).trim().split(/\s+/)[0] : null;
1491
- console.log("Storage:");
1492
- console.log(` Total: ${totalSize}`);
1493
- console.log(` Projects: ${projSize}`);
1494
- if (desktopSize) {
1495
- console.log(` Desktop: ${desktopSize}`);
1496
- }
1497
- } catch {
1692
+ const totalSize = formatSize(getDirSize(CLAUDE_DIR));
1693
+ const projSize = formatSize(getDirSize(PROJECTS_DIR));
1694
+ const desktopSize = isDesktopAppInstalled() ? formatSize(getDirSize(DESKTOP_SESSIONS_DIR2)) : null;
1695
+ console.log("Storage:");
1696
+ console.log(` Total: ${totalSize}`);
1697
+ console.log(` Projects: ${projSize}`);
1698
+ if (desktopSize) {
1699
+ console.log(` Desktop: ${desktopSize}`);
1498
1700
  }
1499
1701
  });
1500
1702
  session.command("clean").description("Delete session JSONL files older than N days").option("-d, --days <n>", "Delete files older than this many days", "30").option("--dry-run", "Show what would be deleted without deleting").action((opts) => {
@@ -1505,23 +1707,23 @@ function sessionCommand() {
1505
1707
  const walk = (dir) => {
1506
1708
  let entries;
1507
1709
  try {
1508
- entries = fs3.readdirSync(dir, { withFileTypes: true });
1710
+ entries = fs6.readdirSync(dir, { withFileTypes: true });
1509
1711
  } catch {
1510
1712
  return;
1511
1713
  }
1512
1714
  for (const entry of entries) {
1513
- const fullPath = path3.join(dir, entry.name);
1715
+ const fullPath = path6.join(dir, entry.name);
1514
1716
  if (entry.isDirectory()) {
1515
1717
  walk(fullPath);
1516
1718
  } else if (entry.name.endsWith(".jsonl")) {
1517
1719
  try {
1518
- const stat = fs3.statSync(fullPath);
1720
+ const stat = fs6.statSync(fullPath);
1519
1721
  if (stat.mtimeMs < cutoffMs) {
1520
1722
  const size = stat.size;
1521
1723
  if (opts.dryRun) {
1522
1724
  console.log(`[dry-run] would delete: ${fullPath} (${Math.floor(size / 1024)}KB)`);
1523
1725
  } else {
1524
- fs3.unlinkSync(fullPath);
1726
+ fs6.unlinkSync(fullPath);
1525
1727
  console.log(`Deleted: ${fullPath}`);
1526
1728
  }
1527
1729
  deleted++;
@@ -1534,7 +1736,7 @@ function sessionCommand() {
1534
1736
  };
1535
1737
  walk(PROJECTS_DIR);
1536
1738
  if (isDesktopAppInstalled()) {
1537
- walk(DESKTOP_SESSIONS_DIR);
1739
+ walk(DESKTOP_SESSIONS_DIR2);
1538
1740
  }
1539
1741
  console.log("");
1540
1742
  const verb = opts.dryRun ? "Would delete" : "Deleted";
@@ -1543,8 +1745,10 @@ function sessionCommand() {
1543
1745
  return session;
1544
1746
  }
1545
1747
 
1546
- // src/complete.ts
1748
+ // src/complete/index.ts
1547
1749
  import { Command as Command5 } from "commander";
1750
+
1751
+ // src/complete/zsh.ts
1548
1752
  var ZSH_COMPLETION = `#compdef cc-hub
1549
1753
 
1550
1754
  _cc-hub() {
@@ -1641,7 +1845,13 @@ _cc-hub() {
1641
1845
  else
1642
1846
  words=("stub" $words[3,-1])
1643
1847
  (( CURRENT-- ))
1644
- _arguments -C -S '1:profile:_cc_hub_profiles' '(-m --model)*'{-m,--model}'[Model ID]:model:->profileModel' '(-d --delete-model)*'{-d,--delete-model}'[Remove model ID]:model:->profileModel' '(-t --token)'{-t,--token}'[API key / token]:token:' '(-u --url)'{-u,--url}'[Base URL]:url:' '(-p --provider)'{-p,--provider}'[Provider type]:provider:(anthropic openai)'
1848
+ _arguments -C -S \\
1849
+ '1:profile:_cc_hub_profiles' \\
1850
+ '(-m --model)*'{-m,--model}'[Model ID]:model:->profileModel' \\
1851
+ '(-d --delete-model)*'{-d,--delete-model}'[Remove model ID]:model:->profileModel' \\
1852
+ '(-t --token)'{-t,--token}'[API key / token]:token:' \\
1853
+ '(-u --url)'{-u,--url}'[Base URL]:url:' \\
1854
+ '(-p --provider)'{-p,--provider}'[Provider type]:provider:(anthropic openai)'
1645
1855
  case $state in
1646
1856
  profileModel)
1647
1857
  _cc_hub_models_for_profile $line[1]
@@ -1675,6 +1885,8 @@ _cc-hub() {
1675
1885
 
1676
1886
  compdef _cc-hub cc-hub
1677
1887
  `;
1888
+
1889
+ // src/complete/bash.ts
1678
1890
  var BASH_COMPLETION = `_cc-hub_profiles() {
1679
1891
  local profiles_file="\${CLAUDE_PROFILES_FILE:-$HOME/.claude/profiles.json}"
1680
1892
  if [[ -f "$profiles_file" ]]; then
@@ -1783,8 +1995,67 @@ _cc-hub() {
1783
1995
 
1784
1996
  complete -F _cc-hub cc-hub
1785
1997
  `;
1998
+
1999
+ // src/complete/powershell.ts
2000
+ var POWERSHELL_COMPLETION = `Register-ArgumentCompleter -Native -CommandName cc-hub -ScriptBlock {
2001
+ param($wordToComplete, $commandAst, $cursorPosition)
2002
+
2003
+ $commands = @(
2004
+ 'profile:Manage Claude CLI profiles'
2005
+ 'use:Set a profile as the default'
2006
+ 'run:Launch Claude Code using the default or a specified profile'
2007
+ 'hook:Manage Claude Code hooks in settings.json'
2008
+ 'session:Manage Claude Code sessions'
2009
+ 'provider:Manage provider types'
2010
+ 'complete:Print shell completion functions'
2011
+ 'help:Display help for a command'
2012
+ )
2013
+
2014
+ $profileSubcmds = @('add', 'update', 'list', 'view', 'remove', 'rename', 'default', 'sync')
2015
+ $hookSubcmds = @('list', 'add', 'remove', 'enable', 'disable')
2016
+ $sessionSubcmds = @('list', 'show', 'search', 'ps', 'stats', 'clean')
2017
+ $providerSubcmds = @('list')
2018
+
2019
+ $tokens = $commandAst.CommandElements | ForEach-Object { $_.ToString() }
2020
+
2021
+ if ($tokens.Count -eq 1 -or ($tokens.Count -eq 2 -and $wordToComplete -ne '')) {
2022
+ $commands | ForEach-Object { if ($_ -like "$wordToComplete*") { $_ } }
2023
+ return
2024
+ }
2025
+
2026
+ $cmd = $tokens[1]
2027
+
2028
+ switch ($cmd) {
2029
+ 'profile' {
2030
+ if ($tokens.Count -eq 2 -or ($tokens.Count -eq 3 -and $wordToComplete -ne '')) {
2031
+ $profileSubcmds | ForEach-Object { if ($_ -like "$wordToComplete*") { $_ } }
2032
+ return
2033
+ }
2034
+ }
2035
+ 'hook' {
2036
+ if ($tokens.Count -eq 2 -or ($tokens.Count -eq 3 -and $wordToComplete -ne '')) {
2037
+ $hookSubcmds | ForEach-Object { if ($_ -like "$wordToComplete*") { $_ } }
2038
+ return
2039
+ }
2040
+ }
2041
+ 'session' {
2042
+ if ($tokens.Count -eq 2 -or ($tokens.Count -eq 3 -and $wordToComplete -ne '')) {
2043
+ $sessionSubcmds | ForEach-Object { if ($_ -like "$wordToComplete*") { $_ } }
2044
+ return
2045
+ }
2046
+ }
2047
+ 'provider' {
2048
+ if ($tokens.Count -eq 2 -or ($tokens.Count -eq 3 -and $wordToComplete -ne '')) {
2049
+ $providerSubcmds | ForEach-Object { if ($_ -like "$wordToComplete*") { $_ } }
2050
+ return
2051
+ }
2052
+ }
2053
+ }
2054
+ }`;
2055
+
2056
+ // src/complete/index.ts
1786
2057
  function completeCommand() {
1787
- return new Command5("complete").description("Print shell completion script").argument("<shell>", "Shell type: bash or zsh").action((shell) => {
2058
+ return new Command5("complete").description("Print shell completion script").argument("<shell>", "Shell type: bash, zsh, or powershell").action((shell) => {
1788
2059
  switch (shell) {
1789
2060
  case "zsh":
1790
2061
  process.stdout.write(ZSH_COMPLETION);
@@ -1792,8 +2063,11 @@ function completeCommand() {
1792
2063
  case "bash":
1793
2064
  process.stdout.write(BASH_COMPLETION);
1794
2065
  break;
2066
+ case "powershell":
2067
+ process.stdout.write(POWERSHELL_COMPLETION);
2068
+ break;
1795
2069
  default:
1796
- console.error(`Unsupported shell: ${shell}. Use 'bash' or 'zsh'.`);
2070
+ console.error(`Unsupported shell: ${shell}. Use 'bash', 'zsh', or 'powershell'.`);
1797
2071
  process.exit(1);
1798
2072
  }
1799
2073
  });