cc-hub-cli 1.0.11 → 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.
Files changed (3) hide show
  1. package/README.md +10 -4
  2. package/dist/index.js +622 -337
  3. package/package.json +4 -3
package/dist/index.js CHANGED
@@ -4,63 +4,326 @@
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"))
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
+ _buildCandidates() {
62
+ const candidates = [
63
+ path.join(process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming"), "Claude"),
64
+ path.join(process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming"), "Claude-3p"),
65
+ path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local"), "Claude"),
66
+ path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local"), "Claude-3p")
67
+ ];
68
+ const packagesDir = path.join(
69
+ process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local"),
70
+ "Packages"
35
71
  );
36
- } catch {
72
+ if (fs.existsSync(packagesDir)) {
73
+ try {
74
+ const entries = fs.readdirSync(packagesDir);
75
+ for (const entry of entries) {
76
+ if (entry.startsWith("Claude_")) {
77
+ candidates.push(path.join(packagesDir, entry, "LocalCache", "Roaming", "Claude"));
78
+ candidates.push(path.join(packagesDir, entry, "LocalCache", "Roaming", "Claude-3p"));
79
+ }
80
+ }
81
+ } catch {
82
+ }
83
+ }
84
+ return candidates;
85
+ }
86
+ _findSupportDir() {
87
+ for (const dir of this._buildCandidates()) {
88
+ if (fs.existsSync(dir)) return dir;
89
+ }
37
90
  return void 0;
38
91
  }
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");
92
+ isInstalled() {
93
+ return this._findSupportDir() !== void 0;
94
+ }
95
+ getSupportDir() {
96
+ return this._findSupportDir();
97
+ }
98
+ getSessionsDir() {
99
+ const dir = this._findSupportDir();
100
+ return dir ? path.join(dir, "local-agent-mode-sessions") : void 0;
101
+ }
102
+ getConfigLibrary() {
103
+ const dir = this._findSupportDir();
104
+ return dir ? path.join(dir, "configLibrary") : void 0;
105
+ }
106
+ findBinary() {
107
+ const win32Binary = path.join(process.env.LOCALAPPDATA || "", "Programs", "Claude", "Claude.exe");
108
+ if (fs.existsSync(win32Binary)) return win32Binary;
109
+ return void 0;
110
+ }
111
+ };
112
+ var NoOpDesktopApp = class {
113
+ isInstalled() {
114
+ return false;
115
+ }
116
+ getSupportDir() {
117
+ return void 0;
118
+ }
119
+ getSessionsDir() {
120
+ return void 0;
121
+ }
122
+ getConfigLibrary() {
123
+ return void 0;
124
+ }
125
+ findBinary() {
126
+ return void 0;
127
+ }
128
+ };
129
+
130
+ // src/platform/profile-syncer.ts
131
+ import fs2 from "fs";
132
+ import path2 from "path";
133
+ import { randomUUID } from "crypto";
134
+ function toDesktopProfile(p) {
135
+ const models = p.models || (p.model ? [p.model] : []);
136
+ const isAnthropic = p.provider === "anthropic" || !p.provider && !p.url;
137
+ if (isAnthropic && !p.url) {
138
+ return {
139
+ inferenceProvider: "1p",
140
+ inferenceModels: models.map((m) => ({ name: m, supports1m: true }))
141
+ };
142
+ }
143
+ return {
144
+ inferenceProvider: "gateway",
145
+ inferenceGatewayBaseUrl: p.url || void 0,
146
+ inferenceGatewayApiKey: p.token || void 0,
147
+ inferenceGatewayAuthScheme: "bearer",
148
+ inferenceModels: models.map((m) => ({ name: m, supports1m: true }))
149
+ };
150
+ }
151
+ var DesktopProfileSyncer = class {
152
+ constructor(app) {
153
+ this.app = app;
154
+ }
155
+ app;
156
+ isSupported() {
157
+ return this.app.isInstalled();
158
+ }
159
+ sync(name, p) {
160
+ const configLib = this.app.getConfigLibrary();
161
+ if (!configLib) return;
162
+ if (!fs2.existsSync(configLib)) {
163
+ fs2.mkdirSync(configLib, { recursive: true });
164
+ }
165
+ const meta = this.readMeta();
166
+ const entries = meta.entries || [];
167
+ let id = p.desktopId;
168
+ if (!id) {
169
+ const existingByName = entries.find((e) => e.name === name);
170
+ if (existingByName) {
171
+ id = existingByName.id;
172
+ } else {
173
+ id = randomUUID();
174
+ }
175
+ p.desktopId = id;
176
+ }
177
+ const existingIndex = entries.findIndex((e) => e.id === id);
178
+ if (existingIndex !== -1) {
179
+ entries[existingIndex].name = name;
180
+ } else {
181
+ entries.push({ id, name });
182
+ }
183
+ meta.entries = entries;
184
+ this.writeMeta(meta);
185
+ this.writeProfile(id, configLib, toDesktopProfile(p));
186
+ }
187
+ remove(name, p) {
188
+ const configLib = this.app.getConfigLibrary();
189
+ if (!configLib || !p.desktopId) return;
190
+ const meta = this.readMeta();
191
+ if (meta.entries) {
192
+ meta.entries = meta.entries.filter((e) => e.id !== p.desktopId);
193
+ }
194
+ if (meta.appliedId === p.desktopId) {
195
+ delete meta.appliedId;
196
+ }
197
+ this.writeMeta(meta);
198
+ const filePath = path2.join(configLib, `${p.desktopId}.json`);
199
+ if (fs2.existsSync(filePath)) {
200
+ fs2.unlinkSync(filePath);
201
+ }
202
+ }
203
+ setActive(p) {
204
+ const configLib = this.app.getConfigLibrary();
205
+ if (!configLib || !p.desktopId) return;
206
+ const meta = this.readMeta();
207
+ meta.appliedId = p.desktopId;
208
+ const entries = meta.entries || [];
209
+ if (!entries.some((e) => e.id === p.desktopId)) {
210
+ entries.push({ id: p.desktopId, name: "unknown" });
211
+ meta.entries = entries;
212
+ }
213
+ this.writeMeta(meta);
214
+ }
215
+ metaFile() {
216
+ const configLib = this.app.getConfigLibrary();
217
+ return configLib ? path2.join(configLib, "_meta.json") : void 0;
218
+ }
219
+ readMeta() {
220
+ const file = this.metaFile();
221
+ if (!file || !fs2.existsSync(file)) return {};
222
+ try {
223
+ return readJson(file);
224
+ } catch {
225
+ return {};
226
+ }
227
+ }
228
+ writeMeta(meta) {
229
+ const file = this.metaFile();
230
+ if (file) writeJson(file, meta);
231
+ }
232
+ writeProfile(id, configLib, data) {
233
+ writeJson(path2.join(configLib, `${id}.json`), data);
234
+ }
235
+ };
236
+
237
+ // src/platform/binary-resolver.ts
238
+ import { spawnSync } from "child_process";
239
+ var SystemBinaryResolver = class {
240
+ constructor(app) {
241
+ this.app = app;
242
+ }
243
+ app;
244
+ resolve() {
245
+ try {
246
+ const result = spawnSync("claude", ["--version"], {
247
+ shell: process.platform === "win32",
248
+ encoding: "utf-8"
249
+ });
250
+ if (result.status === 0) {
251
+ return "claude";
252
+ }
253
+ } catch {
254
+ }
255
+ const desktopBinary = this.app.findBinary();
256
+ if (desktopBinary) return desktopBinary;
257
+ console.error("Error: Could not find Claude Code CLI.");
258
+ console.error("Install it globally or install the Claude Code desktop app.");
259
+ process.exit(1);
260
+ }
261
+ };
262
+
263
+ // src/platform/path-codec.ts
264
+ var UnixPathCodec = class {
265
+ encode(p) {
266
+ return p.replace(/[\\/]/g, "-").replace(/\./g, "-").replace(/:/g, "");
267
+ }
268
+ decode(encoded) {
269
+ return encoded.replace(/--/g, "/.").replace(/-/g, "/");
270
+ }
271
+ };
272
+ var WindowsPathCodec = class {
273
+ encode(p) {
274
+ return p.replace(/[\\/]/g, "-").replace(/\./g, "-").replace(/:/g, "");
275
+ }
276
+ decode(encoded) {
277
+ const decoded = encoded.replace(/--/g, "\\.").replace(/-/g, "\\");
278
+ if (/^[A-Za-z]\\/.test(decoded)) {
279
+ return decoded[0] + ":" + decoded.slice(1);
280
+ }
281
+ return decoded;
282
+ }
283
+ };
284
+
285
+ // src/platform/index.ts
286
+ function createDesktopApp() {
287
+ if (process.platform === "darwin") return new MacOSDesktopApp();
288
+ if (process.platform === "win32") return new WindowsDesktopApp();
289
+ return new NoOpDesktopApp();
290
+ }
291
+ function createProfileSyncer() {
292
+ return new DesktopProfileSyncer(createDesktopApp());
293
+ }
294
+ function createBinaryResolver() {
295
+ return new SystemBinaryResolver(createDesktopApp());
296
+ }
297
+ function createPathCodec() {
298
+ if (process.platform === "win32") return new WindowsPathCodec();
299
+ return new UnixPathCodec();
300
+ }
301
+
302
+ // src/config.ts
303
+ var CLAUDE_DIR = process.env.CLAUDE_DIR || path3.join(os2.homedir(), ".claude");
304
+ var PROFILES_FILE = process.env.CLAUDE_PROFILES_FILE || path3.join(CLAUDE_DIR, "profiles.json");
305
+ var SETTINGS_FILE = process.env.CLAUDE_SETTINGS_FILE || path3.join(CLAUDE_DIR, "settings.json");
306
+ var CLAUDE_JSON = path3.join(os2.homedir(), ".claude.json");
307
+ var PROJECTS_DIR = path3.join(CLAUDE_DIR, "projects");
308
+ var SESSIONS_DIR = path3.join(CLAUDE_DIR, "sessions");
309
+ var desktopApp = createDesktopApp();
310
+ var DESKTOP_CONFIG_LIBRARY = desktopApp.getConfigLibrary() || "";
311
+ var DESKTOP_META_FILE = DESKTOP_CONFIG_LIBRARY ? path3.join(DESKTOP_CONFIG_LIBRARY, "_meta.json") : "";
312
+ var DESKTOP_SESSIONS_DIR = desktopApp.getSessionsDir() || "";
313
+ function isDesktopAppInstalled() {
314
+ return desktopApp.isInstalled();
52
315
  }
53
316
  function ensureFile(filePath, defaultContent) {
54
- if (!fs.existsSync(filePath)) {
55
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
56
- fs.writeFileSync(filePath, defaultContent, "utf-8");
317
+ if (!fs3.existsSync(filePath)) {
318
+ fs3.mkdirSync(path3.dirname(filePath), { recursive: true });
319
+ fs3.writeFileSync(filePath, defaultContent, "utf-8");
57
320
  }
58
321
  }
59
322
  function readJson(filePath) {
60
- return JSON.parse(fs.readFileSync(filePath, "utf-8"));
323
+ return JSON.parse(fs3.readFileSync(filePath, "utf-8"));
61
324
  }
62
325
  function writeJson(filePath, data) {
63
- fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
326
+ fs3.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
64
327
  }
65
328
  function ensureProfilesFile() {
66
329
  ensureFile(PROFILES_FILE, '{"profiles":{}}\n');
@@ -69,12 +332,12 @@ function ensureSettingsFile() {
69
332
  ensureFile(SETTINGS_FILE, "{}\n");
70
333
  }
71
334
  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");
335
+ if (!fs3.existsSync(filePath)) return;
336
+ const backupPath = path3.join(CLAUDE_DIR, path3.basename(filePath) + ".backup");
337
+ const raw = fs3.readFileSync(filePath, "utf-8");
75
338
  try {
76
339
  JSON.parse(raw);
77
- fs.copyFileSync(filePath, backupPath);
340
+ fs3.copyFileSync(filePath, backupPath);
78
341
  return;
79
342
  } catch {
80
343
  }
@@ -99,22 +362,26 @@ function fixJsonFile(filePath, fallback = {}) {
99
362
  if (openCurly > 0) text += "}".repeat(openCurly);
100
363
  try {
101
364
  JSON.parse(text);
102
- fs.writeFileSync(filePath, text + "\n", "utf-8");
103
- console.error(`Fixed invalid JSON in ${path.basename(filePath)}.`);
365
+ fs3.writeFileSync(filePath, text + "\n", "utf-8");
366
+ console.error(`Fixed invalid JSON in ${path3.basename(filePath)}.`);
104
367
  } catch {
105
- if (fs.existsSync(backupPath)) {
106
- fs.copyFileSync(backupPath, filePath);
107
- console.error(`Restored ${path.basename(filePath)} from backup.`);
368
+ if (fs3.existsSync(backupPath)) {
369
+ fs3.copyFileSync(backupPath, filePath);
370
+ console.error(`Restored ${path3.basename(filePath)} from backup.`);
108
371
  } else {
109
372
  writeJson(filePath, fallback);
110
- console.error(`Could not fix ${path.basename(filePath)}, no backup found, reset to default.`);
373
+ console.error(`Could not fix ${path3.basename(filePath)}, no backup found, reset to default.`);
111
374
  }
112
375
  }
113
376
  }
114
377
 
115
- // src/provider.ts
116
- import http from "http";
378
+ // src/profiles/runner.ts
379
+ import { spawnSync as spawnSync2, spawn } from "child_process";
380
+
381
+ // src/provider/index.ts
117
382
  import { Command } from "commander";
383
+
384
+ // src/provider/transform.ts
118
385
  function sanitizeToolId(id) {
119
386
  let sanitized = id.replace(/[^a-zA-Z0-9_-]/g, "_");
120
387
  if (!/^[a-zA-Z]/.test(sanitized)) {
@@ -325,6 +592,9 @@ data: ${JSON.stringify(data)}
325
592
  });
326
593
  yield sse("message_stop", { type: "message_stop" });
327
594
  }
595
+
596
+ // src/provider/server.ts
597
+ import http from "http";
328
598
  async function startOpenAIProxy(targetUrl, apiKey, model, models = []) {
329
599
  const base = targetUrl.replace(/\/+$/, "");
330
600
  const server = http.createServer(async (req, res) => {
@@ -436,6 +706,8 @@ function readBody(req) {
436
706
  req.on("error", reject);
437
707
  });
438
708
  }
709
+
710
+ // src/provider/index.ts
439
711
  var PROVIDERS = [
440
712
  {
441
713
  name: "anthropic",
@@ -459,10 +731,100 @@ function providerCommand() {
459
731
  return cmd;
460
732
  }
461
733
 
462
- // src/profiles.ts
463
- import { randomUUID } from "crypto";
464
- import fs2 from "fs";
465
- import path2 from "path";
734
+ // src/profiles/runner.ts
735
+ function resolveClaudeBinary() {
736
+ return createBinaryResolver().resolve();
737
+ }
738
+ function updateSettingsForProfile(p) {
739
+ ensureSettingsFile();
740
+ const settings = readJson(SETTINGS_FILE);
741
+ const models = p.models || (p.model ? [p.model] : []);
742
+ delete settings.model;
743
+ delete settings.availableModels;
744
+ const envVarsToClean = [
745
+ "ANTHROPIC_DEFAULT_OPUS_MODEL",
746
+ "ANTHROPIC_DEFAULT_OPUS_MODEL_NAME",
747
+ "ANTHROPIC_DEFAULT_OPUS_MODEL_DESCRIPTION",
748
+ "ANTHROPIC_DEFAULT_SONNET_MODEL",
749
+ "ANTHROPIC_DEFAULT_SONNET_MODEL_NAME",
750
+ "ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION",
751
+ "ANTHROPIC_DEFAULT_HAIKU_MODEL",
752
+ "ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME",
753
+ "ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION",
754
+ "ANTHROPIC_CUSTOM_MODEL_OPTION"
755
+ ];
756
+ if (settings.env) {
757
+ const env = settings.env;
758
+ for (const key of envVarsToClean) {
759
+ delete env[key];
760
+ }
761
+ }
762
+ writeJson(SETTINGS_FILE, settings);
763
+ }
764
+ function execClaude(profileName, p, extraArgs) {
765
+ updateSettingsForProfile(p);
766
+ const models = p.models || (p.model ? [p.model] : []);
767
+ const firstModel = models[0];
768
+ const binary = resolveClaudeBinary();
769
+ const cmd = [binary];
770
+ if (firstModel) cmd.push("--model", firstModel);
771
+ cmd.push(...extraArgs);
772
+ const env = {
773
+ ...process.env,
774
+ ANTHROPIC_AUTH_TOKEN: p.token || void 0,
775
+ ANTHROPIC_BASE_URL: p.url || void 0,
776
+ CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS: "1",
777
+ CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: "1"
778
+ };
779
+ if (models.length > 0) {
780
+ if (models[0]) {
781
+ env.ANTHROPIC_DEFAULT_SONNET_MODEL = models[0];
782
+ env.ANTHROPIC_DEFAULT_SONNET_MODEL_NAME = models[0];
783
+ env.ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION = `Custom: ${models[0]}`;
784
+ }
785
+ if (models[1]) {
786
+ env.ANTHROPIC_DEFAULT_OPUS_MODEL = models[1];
787
+ env.ANTHROPIC_DEFAULT_OPUS_MODEL_NAME = models[1];
788
+ env.ANTHROPIC_DEFAULT_OPUS_MODEL_DESCRIPTION = `Custom: ${models[1]}`;
789
+ }
790
+ if (models[2]) {
791
+ env.ANTHROPIC_DEFAULT_HAIKU_MODEL = models[2];
792
+ env.ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME = models[2];
793
+ env.ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION = `Custom: ${models[2]}`;
794
+ }
795
+ env.ANTHROPIC_CUSTOM_MODEL_OPTION = models[0];
796
+ }
797
+ delete env.ANTHROPIC_API_KEY;
798
+ console.error(`Using profile '${profileName}': model=${firstModel || "(default)"} url=${p.url || "(default)"} provider=${p.provider || "anthropic"}`);
799
+ if (p.provider === "openai") {
800
+ const allModels = p.models || (p.model ? [p.model] : []);
801
+ startOpenAIProxy(
802
+ p.url || "https://api.openai.com",
803
+ p.token || "",
804
+ firstModel || "gpt-4o",
805
+ allModels
806
+ ).then(({ baseUrl, stop }) => {
807
+ env.ANTHROPIC_BASE_URL = baseUrl;
808
+ const child = spawn(cmd[0], cmd.slice(1), { stdio: "inherit", env, shell: process.platform === "win32" });
809
+ child.on("close", (code) => {
810
+ stop();
811
+ process.exit(code ?? 1);
812
+ });
813
+ }).catch((err) => {
814
+ console.error("Failed to start OpenAI proxy:", err);
815
+ process.exit(1);
816
+ });
817
+ } else {
818
+ const result = spawnSync2(cmd[0], cmd.slice(1), {
819
+ stdio: "inherit",
820
+ env,
821
+ shell: process.platform === "win32"
822
+ });
823
+ process.exit(result.status ?? 1);
824
+ }
825
+ }
826
+
827
+ // src/profiles/commands.ts
466
828
  function maskToken(token) {
467
829
  if (!token) return "(unset)";
468
830
  if (token.length <= 12) return token;
@@ -498,136 +860,13 @@ function isAnthropicModel(model) {
498
860
  if (lower.startsWith("claude-")) return true;
499
861
  return false;
500
862
  }
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);
863
+ function collect(value, previous) {
864
+ return previous.concat([value]);
627
865
  }
628
866
  function profileCommand() {
629
867
  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) => {
868
+ const syncer = createProfileSyncer();
869
+ 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
870
  const models = opts.model && opts.model.length > 0 ? opts.model : void 0;
632
871
  if (models && models.length > 3) {
633
872
  console.error("Error: A profile can have at most 3 models.");
@@ -644,11 +883,11 @@ function profileCommand() {
644
883
  if (opts.url) profile2.url = opts.url;
645
884
  if (opts.provider) profile2.provider = opts.provider;
646
885
  data.profiles[name] = profile2;
647
- syncProfileToDesktop(name, profile2);
886
+ syncer.sync(name, profile2);
648
887
  writeJson(PROFILES_FILE, data);
649
888
  console.log(`Profile '${name}' saved.`);
650
889
  });
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) => {
890
+ 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
891
  ensureProfilesFile();
653
892
  const data = readJson(PROFILES_FILE);
654
893
  if (!data.profiles[name]) {
@@ -705,7 +944,7 @@ function profileCommand() {
705
944
  if (opts.token) p.token = opts.token;
706
945
  if (opts.url) p.url = opts.url;
707
946
  if (opts.provider) p.provider = opts.provider;
708
- syncProfileToDesktop(name, p);
947
+ syncer.sync(name, p);
709
948
  writeJson(PROFILES_FILE, data);
710
949
  console.log(`Profile '${name}' updated.`);
711
950
  });
@@ -779,7 +1018,7 @@ function profileCommand() {
779
1018
  console.error(`Profile '${name}' not found.`);
780
1019
  process.exit(1);
781
1020
  }
782
- removeProfileFromDesktop(name, data.profiles[name]);
1021
+ syncer.remove(name, data.profiles[name]);
783
1022
  delete data.profiles[name];
784
1023
  writeJson(PROFILES_FILE, data);
785
1024
  console.log(`Profile '${name}' removed.`);
@@ -811,12 +1050,12 @@ function profileCommand() {
811
1050
  process.exit(1);
812
1051
  }
813
1052
  data.default = name;
814
- setDesktopActiveProfile(data.profiles[name]);
1053
+ syncer.setActive(data.profiles[name]);
815
1054
  writeJson(PROFILES_FILE, data);
816
1055
  console.log(`Default profile set to '${name}'.`);
817
1056
  });
818
1057
  profile.command("sync").description("Synchronize all CLI profiles to the Claude desktop app").action(() => {
819
- if (!isDesktopAppInstalled()) {
1058
+ if (!syncer.isSupported()) {
820
1059
  console.error("Claude desktop app is not installed.");
821
1060
  process.exit(1);
822
1061
  }
@@ -827,83 +1066,17 @@ function profileCommand() {
827
1066
  console.log("No profiles to sync.");
828
1067
  return;
829
1068
  }
830
- if (!fs2.existsSync(DESKTOP_CONFIG_LIBRARY)) {
831
- fs2.mkdirSync(DESKTOP_CONFIG_LIBRARY, { recursive: true });
832
- }
833
1069
  for (const name of names) {
834
1070
  const p = data.profiles[name];
835
- syncProfileToDesktop(name, p);
1071
+ syncer.sync(name, p);
836
1072
  }
837
1073
  writeJson(PROFILES_FILE, data);
838
1074
  console.log(`Synced ${names.length} profile(s) to the desktop app.`);
839
1075
  });
840
1076
  return profile;
841
1077
  }
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
1078
  function useCommand() {
1079
+ const syncer = createProfileSyncer();
907
1080
  return new Command2("use").description("Set a profile as the default").argument("<name>", "Profile name").action((name) => {
908
1081
  ensureProfilesFile();
909
1082
  const data = readJson(PROFILES_FILE);
@@ -912,7 +1085,7 @@ function useCommand() {
912
1085
  process.exit(1);
913
1086
  }
914
1087
  data.default = name;
915
- setDesktopActiveProfile(data.profiles[name]);
1088
+ syncer.setActive(data.profiles[name]);
916
1089
  writeJson(PROFILES_FILE, data);
917
1090
  console.log(`Default profile set to '${name}'.`);
918
1091
  });
@@ -940,7 +1113,7 @@ function runCommand() {
940
1113
  });
941
1114
  }
942
1115
 
943
- // src/hooks.ts
1116
+ // src/hooks/commands.ts
944
1117
  import { Command as Command3 } from "commander";
945
1118
  function buildFlat(data) {
946
1119
  const rows = [];
@@ -980,26 +1153,29 @@ function buildFlat(data) {
980
1153
  rows.sort((a, b) => a.seq - b.seq);
981
1154
  return rows;
982
1155
  }
1156
+ function displayHookList(data) {
1157
+ const rows = buildFlat(data);
1158
+ if (rows.length === 0) {
1159
+ console.log("No hooks defined.");
1160
+ return;
1161
+ }
1162
+ const fmt = (idx, active, event, matcher, cmd) => `${String(idx).padEnd(4)} ${active.padEnd(2)} ${event.padEnd(22)} ${matcher.padEnd(25)} ${cmd}`;
1163
+ console.log(fmt(0, "", "EVENT", "MATCHER", "COMMAND").replace(/^IDX/, "IDX").replace(/^0/, "IDX"));
1164
+ console.log(fmt(0, "", "-----", "-------", "-------").replace(/^0/, "---"));
1165
+ for (let idx = 0; idx < rows.length; idx++) {
1166
+ const r = rows[idx];
1167
+ const marker = r.active ? " " : "\u2717";
1168
+ const matcher = r.matcher || "(any)";
1169
+ const cmd = r.command.length > 60 ? r.command.slice(0, 60) + "\u2026" : r.command;
1170
+ console.log(fmt(idx, marker, r.event, matcher, cmd));
1171
+ }
1172
+ }
983
1173
  function hooksCommand() {
984
1174
  const hooks = new Command3("hook").description("Manage Claude Code hooks in settings.json");
985
1175
  hooks.command("list").description("List all hooks").action(() => {
986
1176
  ensureSettingsFile();
987
1177
  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
- }
1178
+ displayHookList(data);
1003
1179
  });
1004
1180
  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
1181
  ensureSettingsFile();
@@ -1092,6 +1268,8 @@ function hooksCommand() {
1092
1268
  data._cc_hub_disabled = remaining;
1093
1269
  if (remaining.length === 0) delete data._cc_hub_disabled;
1094
1270
  writeJson(SETTINGS_FILE, data);
1271
+ console.log("");
1272
+ displayHookList(data);
1095
1273
  });
1096
1274
  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
1275
  prev = prev || [];
@@ -1128,21 +1306,52 @@ function hooksCommand() {
1128
1306
  console.log(`Hook ${t} (${r.event}) disabled.`);
1129
1307
  }
1130
1308
  writeJson(SETTINGS_FILE, data);
1309
+ console.log("");
1310
+ displayHookList(data);
1131
1311
  });
1132
1312
  return hooks;
1133
1313
  }
1134
1314
 
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";
1315
+ // src/sessions/codec.ts
1140
1316
  function encodePath(p) {
1141
- return p.replace(/\./g, "DOTMARK").replace(/\//g, "-").replace(/DOTMARK/g, "-");
1317
+ return createPathCodec().encode(p);
1142
1318
  }
1143
1319
  function decodePath(encoded) {
1144
- return encoded.replace(/--/g, "/.").replace(/-/g, "/");
1320
+ return createPathCodec().decode(encoded);
1321
+ }
1322
+
1323
+ // src/sessions/stats.ts
1324
+ import fs4 from "fs";
1325
+ import path4 from "path";
1326
+ function getDirSize(dir) {
1327
+ let total = 0;
1328
+ try {
1329
+ for (const entry of fs4.readdirSync(dir, { withFileTypes: true })) {
1330
+ const fullPath = path4.join(dir, entry.name);
1331
+ if (entry.isDirectory()) {
1332
+ total += getDirSize(fullPath);
1333
+ } else {
1334
+ total += fs4.statSync(fullPath).size;
1335
+ }
1336
+ }
1337
+ } catch {
1338
+ }
1339
+ return total;
1145
1340
  }
1341
+ function formatSize(bytes) {
1342
+ const units = ["B", "KB", "MB", "GB"];
1343
+ let size = bytes;
1344
+ let unitIndex = 0;
1345
+ while (size >= 1024 && unitIndex < units.length - 1) {
1346
+ size /= 1024;
1347
+ unitIndex++;
1348
+ }
1349
+ return `${Math.round(size * 10) / 10}${units[unitIndex]}`;
1350
+ }
1351
+
1352
+ // src/sessions/utils.ts
1353
+ import fs5 from "fs";
1354
+ import path5 from "path";
1146
1355
  function formatTimestamp(ms) {
1147
1356
  const d = new Date(ms);
1148
1357
  const pad = (n) => String(n).padStart(2, "0");
@@ -1150,9 +1359,9 @@ function formatTimestamp(ms) {
1150
1359
  }
1151
1360
  function findProjectDir(query) {
1152
1361
  const encoded = encodePath(query);
1153
- if (fs3.existsSync(path3.join(PROJECTS_DIR, encoded))) return encoded;
1362
+ if (fs5.existsSync(path5.join(PROJECTS_DIR, encoded))) return encoded;
1154
1363
  try {
1155
- const dirs = fs3.readdirSync(PROJECTS_DIR);
1364
+ const dirs = fs5.readdirSync(PROJECTS_DIR);
1156
1365
  const match = dirs.find((d) => d.toLowerCase().includes(query.toLowerCase()));
1157
1366
  return match || null;
1158
1367
  } catch {
@@ -1164,7 +1373,7 @@ function parseSessionMeta(filePath) {
1164
1373
  let slug = "";
1165
1374
  let customTitle = "";
1166
1375
  try {
1167
- const lines = fs3.readFileSync(filePath, "utf-8").split("\n");
1376
+ const lines = fs5.readFileSync(filePath, "utf-8").split("\n");
1168
1377
  for (const line of lines) {
1169
1378
  if (!line.trim()) continue;
1170
1379
  try {
@@ -1216,32 +1425,39 @@ function snippet(text, query, width = 150) {
1216
1425
  const suffix = end < text.length ? "..." : "";
1217
1426
  return prefix + text.slice(start, end) + suffix;
1218
1427
  }
1428
+
1429
+ // src/sessions/commands.ts
1430
+ import { Command as Command4 } from "commander";
1431
+ import fs6 from "fs";
1432
+ import path6 from "path";
1219
1433
  function sessionCommand() {
1220
1434
  const session = new Command4("session").description("Manage Claude Code sessions");
1435
+ const desktopApp2 = createDesktopApp();
1436
+ const DESKTOP_SESSIONS_DIR2 = desktopApp2.getSessionsDir() || "";
1221
1437
  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
1438
  const limit = parseInt(opts.limit, 10);
1223
1439
  let dirs;
1224
1440
  try {
1225
- dirs = fs3.readdirSync(PROJECTS_DIR);
1441
+ dirs = fs6.readdirSync(PROJECTS_DIR);
1226
1442
  } catch {
1227
1443
  console.log("No projects directory found.");
1228
1444
  return;
1229
1445
  }
1230
1446
  dirs.sort((a, b) => {
1231
- const statA = fs3.statSync(path3.join(PROJECTS_DIR, a));
1232
- const statB = fs3.statSync(path3.join(PROJECTS_DIR, b));
1447
+ const statA = fs6.statSync(path6.join(PROJECTS_DIR, a));
1448
+ const statB = fs6.statSync(path6.join(PROJECTS_DIR, b));
1233
1449
  return statB.mtimeMs - statA.mtimeMs;
1234
1450
  });
1235
1451
  let count = 0;
1236
1452
  for (const projDir of dirs) {
1237
1453
  if (count >= limit) break;
1238
- const fullPath = path3.join(PROJECTS_DIR, projDir);
1454
+ const fullPath = path6.join(PROJECTS_DIR, projDir);
1239
1455
  let nSessions = 0;
1240
1456
  try {
1241
- nSessions = fs3.readdirSync(fullPath).filter((f) => f.endsWith(".jsonl")).length;
1457
+ nSessions = fs6.readdirSync(fullPath).filter((f) => f.endsWith(".jsonl")).length;
1242
1458
  } catch {
1243
1459
  }
1244
- const stat = fs3.statSync(fullPath);
1460
+ const stat = fs6.statSync(fullPath);
1245
1461
  const decoded = decodePath(projDir);
1246
1462
  if (opts.json) {
1247
1463
  console.log(JSON.stringify({ project: decoded, sessions: nSessions, modified: Math.floor(stat.mtimeMs) }));
@@ -1259,7 +1475,7 @@ function sessionCommand() {
1259
1475
  console.error(`No project matched: ${project}`);
1260
1476
  process.exit(1);
1261
1477
  }
1262
- const fullPath = path3.join(PROJECTS_DIR, projDir);
1478
+ const fullPath = path6.join(PROJECTS_DIR, projDir);
1263
1479
  console.log(`Project: ${decodePath(projDir)}`);
1264
1480
  console.log(`Dir: ${fullPath}`);
1265
1481
  console.log("");
@@ -1268,16 +1484,16 @@ function sessionCommand() {
1268
1484
  console.log(fmt("----------", "----", "-------", "--------"));
1269
1485
  let files;
1270
1486
  try {
1271
- files = fs3.readdirSync(fullPath).filter((f) => f.endsWith(".jsonl"));
1487
+ files = fs6.readdirSync(fullPath).filter((f) => f.endsWith(".jsonl"));
1272
1488
  } catch {
1273
1489
  return;
1274
1490
  }
1275
1491
  for (const file of files) {
1276
- const filePath = path3.join(fullPath, file);
1492
+ const filePath = path6.join(fullPath, file);
1277
1493
  const sessionId = file.replace(/\.jsonl$/, "");
1278
1494
  let msgCount = 0;
1279
1495
  try {
1280
- const content = fs3.readFileSync(filePath, "utf-8");
1496
+ const content = fs6.readFileSync(filePath, "utf-8");
1281
1497
  msgCount = content ? content.split("\n").filter((l) => l.trim()).length : 0;
1282
1498
  } catch {
1283
1499
  }
@@ -1285,7 +1501,7 @@ function sessionCommand() {
1285
1501
  console.log(fmt(sessionId, slug || "-", started, String(msgCount)));
1286
1502
  if (opts.verbose) {
1287
1503
  try {
1288
- const lines = fs3.readFileSync(filePath, "utf-8").split("\n");
1504
+ const lines = fs6.readFileSync(filePath, "utf-8").split("\n");
1289
1505
  for (const line of lines) {
1290
1506
  if (!line.trim()) continue;
1291
1507
  try {
@@ -1315,7 +1531,7 @@ function sessionCommand() {
1315
1531
  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
1532
  let searchRoots = [{ root: PROJECTS_DIR, label: "" }];
1317
1533
  if (isDesktopAppInstalled()) {
1318
- searchRoots.push({ root: DESKTOP_SESSIONS_DIR, label: "[desktop] " });
1534
+ searchRoots.push({ root: DESKTOP_SESSIONS_DIR2, label: "[desktop] " });
1319
1535
  }
1320
1536
  if (opts.project) {
1321
1537
  const projDir = findProjectDir(opts.project);
@@ -1323,7 +1539,7 @@ function sessionCommand() {
1323
1539
  console.error(`No project matched: ${opts.project}`);
1324
1540
  process.exit(1);
1325
1541
  }
1326
- searchRoots = [{ root: path3.join(PROJECTS_DIR, projDir), label: "" }];
1542
+ searchRoots = [{ root: path6.join(PROJECTS_DIR, projDir), label: "" }];
1327
1543
  }
1328
1544
  const limit = parseInt(opts.limit, 10);
1329
1545
  let count = 0;
@@ -1331,18 +1547,18 @@ function sessionCommand() {
1331
1547
  if (count >= limit) return;
1332
1548
  let entries;
1333
1549
  try {
1334
- entries = fs3.readdirSync(dir, { withFileTypes: true });
1550
+ entries = fs6.readdirSync(dir, { withFileTypes: true });
1335
1551
  } catch {
1336
1552
  return;
1337
1553
  }
1338
1554
  for (const entry of entries) {
1339
1555
  if (count >= limit) break;
1340
- const fullPath = path3.join(dir, entry.name);
1556
+ const fullPath = path6.join(dir, entry.name);
1341
1557
  if (entry.isDirectory()) {
1342
1558
  searchDir(fullPath, label, baseDir);
1343
1559
  } else if (entry.name.endsWith(".jsonl")) {
1344
1560
  try {
1345
- const content = fs3.readFileSync(fullPath, "utf-8");
1561
+ const content = fs6.readFileSync(fullPath, "utf-8");
1346
1562
  const lines = content.split("\n");
1347
1563
  let found = false;
1348
1564
  for (let lineno = 0; lineno < lines.length; lineno++) {
@@ -1351,9 +1567,9 @@ function sessionCommand() {
1351
1567
  const match = opts.ignoreCase ? line.toLowerCase().includes(query.toLowerCase()) : line.includes(query);
1352
1568
  if (match) {
1353
1569
  if (!found) {
1354
- const relPath = path3.relative(baseDir, fullPath);
1355
- const projEnc = relPath.split("/")[0];
1356
- const sessionId = path3.basename(fullPath, ".jsonl");
1570
+ const relPath = path6.relative(baseDir, fullPath);
1571
+ const projEnc = relPath.split(path6.sep)[0];
1572
+ const sessionId = path6.basename(fullPath, ".jsonl");
1357
1573
  const projName = label ? projEnc : decodePath(projEnc);
1358
1574
  console.log(`${label}[${projName} \u2192 ${sessionId}]`);
1359
1575
  found = true;
@@ -1390,7 +1606,7 @@ function sessionCommand() {
1390
1606
  session.command("ps").description("Show active Claude Code processes").action(() => {
1391
1607
  let files;
1392
1608
  try {
1393
- files = fs3.readdirSync(SESSIONS_DIR).filter((f) => f.endsWith(".json"));
1609
+ files = fs6.readdirSync(SESSIONS_DIR).filter((f) => f.endsWith(".json"));
1394
1610
  } catch {
1395
1611
  console.log("(no session files found)");
1396
1612
  return;
@@ -1404,7 +1620,7 @@ function sessionCommand() {
1404
1620
  console.log(fmt("---", "----------", "-------", "---", ""));
1405
1621
  for (const file of files) {
1406
1622
  try {
1407
- const data = JSON.parse(fs3.readFileSync(path3.join(SESSIONS_DIR, file), "utf-8"));
1623
+ const data = JSON.parse(fs6.readFileSync(path6.join(SESSIONS_DIR, file), "utf-8"));
1408
1624
  const pid = String(data.pid || "?");
1409
1625
  const sessionId = data.sessionId || "?";
1410
1626
  const cwd = data.cwd || "?";
@@ -1430,8 +1646,8 @@ function sessionCommand() {
1430
1646
  const walk = (dir) => {
1431
1647
  const results = [];
1432
1648
  try {
1433
- for (const entry of fs3.readdirSync(dir, { withFileTypes: true })) {
1434
- const fullPath = path3.join(dir, entry.name);
1649
+ for (const entry of fs6.readdirSync(dir, { withFileTypes: true })) {
1650
+ const fullPath = path6.join(dir, entry.name);
1435
1651
  if (entry.isDirectory()) results.push(...walk(fullPath));
1436
1652
  else if (entry.name.endsWith(".jsonl")) results.push(fullPath);
1437
1653
  }
@@ -1440,7 +1656,7 @@ function sessionCommand() {
1440
1656
  return results;
1441
1657
  };
1442
1658
  try {
1443
- nProjects = fs3.readdirSync(PROJECTS_DIR).length;
1659
+ nProjects = fs6.readdirSync(PROJECTS_DIR).length;
1444
1660
  } catch {
1445
1661
  }
1446
1662
  try {
@@ -1448,7 +1664,7 @@ function sessionCommand() {
1448
1664
  nSessions = sessionFiles.length;
1449
1665
  for (const f of sessionFiles) {
1450
1666
  try {
1451
- const content = fs3.readFileSync(f, "utf-8");
1667
+ const content = fs6.readFileSync(f, "utf-8");
1452
1668
  totalMsgs += content ? content.split("\n").filter((l) => l.trim()).length : 0;
1453
1669
  } catch {
1454
1670
  }
@@ -1457,11 +1673,11 @@ function sessionCommand() {
1457
1673
  }
1458
1674
  if (isDesktopAppInstalled()) {
1459
1675
  try {
1460
- const desktopFiles = walk(DESKTOP_SESSIONS_DIR);
1676
+ const desktopFiles = walk(DESKTOP_SESSIONS_DIR2);
1461
1677
  nDesktopSessions = desktopFiles.length;
1462
1678
  for (const f of desktopFiles) {
1463
1679
  try {
1464
- const content = fs3.readFileSync(f, "utf-8");
1680
+ const content = fs6.readFileSync(f, "utf-8");
1465
1681
  nDesktopMsgs += content ? content.split("\n").filter((l) => l.trim()).length : 0;
1466
1682
  } catch {
1467
1683
  }
@@ -1470,7 +1686,7 @@ function sessionCommand() {
1470
1686
  }
1471
1687
  }
1472
1688
  try {
1473
- nActive = fs3.readdirSync(SESSIONS_DIR).filter((f) => f.endsWith(".json")).length;
1689
+ nActive = fs6.readdirSync(SESSIONS_DIR).filter((f) => f.endsWith(".json")).length;
1474
1690
  } catch {
1475
1691
  }
1476
1692
  console.log(`Projects: ${nProjects}`);
@@ -1484,17 +1700,14 @@ function sessionCommand() {
1484
1700
  }
1485
1701
  console.log(`Active procs: ${nActive} (in ${SESSIONS_DIR})`);
1486
1702
  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 {
1703
+ const totalSize = formatSize(getDirSize(CLAUDE_DIR));
1704
+ const projSize = formatSize(getDirSize(PROJECTS_DIR));
1705
+ const desktopSize = isDesktopAppInstalled() ? formatSize(getDirSize(DESKTOP_SESSIONS_DIR2)) : null;
1706
+ console.log("Storage:");
1707
+ console.log(` Total: ${totalSize}`);
1708
+ console.log(` Projects: ${projSize}`);
1709
+ if (desktopSize) {
1710
+ console.log(` Desktop: ${desktopSize}`);
1498
1711
  }
1499
1712
  });
1500
1713
  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 +1718,23 @@ function sessionCommand() {
1505
1718
  const walk = (dir) => {
1506
1719
  let entries;
1507
1720
  try {
1508
- entries = fs3.readdirSync(dir, { withFileTypes: true });
1721
+ entries = fs6.readdirSync(dir, { withFileTypes: true });
1509
1722
  } catch {
1510
1723
  return;
1511
1724
  }
1512
1725
  for (const entry of entries) {
1513
- const fullPath = path3.join(dir, entry.name);
1726
+ const fullPath = path6.join(dir, entry.name);
1514
1727
  if (entry.isDirectory()) {
1515
1728
  walk(fullPath);
1516
1729
  } else if (entry.name.endsWith(".jsonl")) {
1517
1730
  try {
1518
- const stat = fs3.statSync(fullPath);
1731
+ const stat = fs6.statSync(fullPath);
1519
1732
  if (stat.mtimeMs < cutoffMs) {
1520
1733
  const size = stat.size;
1521
1734
  if (opts.dryRun) {
1522
1735
  console.log(`[dry-run] would delete: ${fullPath} (${Math.floor(size / 1024)}KB)`);
1523
1736
  } else {
1524
- fs3.unlinkSync(fullPath);
1737
+ fs6.unlinkSync(fullPath);
1525
1738
  console.log(`Deleted: ${fullPath}`);
1526
1739
  }
1527
1740
  deleted++;
@@ -1534,7 +1747,7 @@ function sessionCommand() {
1534
1747
  };
1535
1748
  walk(PROJECTS_DIR);
1536
1749
  if (isDesktopAppInstalled()) {
1537
- walk(DESKTOP_SESSIONS_DIR);
1750
+ walk(DESKTOP_SESSIONS_DIR2);
1538
1751
  }
1539
1752
  console.log("");
1540
1753
  const verb = opts.dryRun ? "Would delete" : "Deleted";
@@ -1543,8 +1756,10 @@ function sessionCommand() {
1543
1756
  return session;
1544
1757
  }
1545
1758
 
1546
- // src/complete.ts
1547
- import { Command as Command5 } from "commander";
1759
+ // src/complete/index.ts
1760
+ import { Command as Command5, Argument } from "commander";
1761
+
1762
+ // src/complete/zsh.ts
1548
1763
  var ZSH_COMPLETION = `#compdef cc-hub
1549
1764
 
1550
1765
  _cc-hub() {
@@ -1556,7 +1771,7 @@ _cc-hub() {
1556
1771
  'hook:Manage Claude Code hooks in settings.json'
1557
1772
  'session:Manage Claude Code sessions'
1558
1773
  'provider:Manage provider types'
1559
- 'complete:Print shell completion functions'
1774
+ 'completion:Print shell completion functions'
1560
1775
  'help:Display help for a command'
1561
1776
  )
1562
1777
 
@@ -1641,7 +1856,13 @@ _cc-hub() {
1641
1856
  else
1642
1857
  words=("stub" $words[3,-1])
1643
1858
  (( 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)'
1859
+ _arguments -C -S \\
1860
+ '1:profile:_cc_hub_profiles' \\
1861
+ '(-m --model)*'{-m,--model}'[Model ID]:model:->profileModel' \\
1862
+ '(-d --delete-model)*'{-d,--delete-model}'[Remove model ID]:model:->profileModel' \\
1863
+ '(-t --token)'{-t,--token}'[API key / token]:token:' \\
1864
+ '(-u --url)'{-u,--url}'[Base URL]:url:' \\
1865
+ '(-p --provider)'{-p,--provider}'[Provider type]:provider:(anthropic openai)'
1645
1866
  case $state in
1646
1867
  profileModel)
1647
1868
  _cc_hub_models_for_profile $line[1]
@@ -1675,6 +1896,8 @@ _cc-hub() {
1675
1896
 
1676
1897
  compdef _cc-hub cc-hub
1677
1898
  `;
1899
+
1900
+ // src/complete/bash.ts
1678
1901
  var BASH_COMPLETION = `_cc-hub_profiles() {
1679
1902
  local profiles_file="\${CLAUDE_PROFILES_FILE:-$HOME/.claude/profiles.json}"
1680
1903
  if [[ -f "$profiles_file" ]]; then
@@ -1717,7 +1940,7 @@ _cc-hub() {
1717
1940
  COMPREPLY=()
1718
1941
  cur="\${COMP_WORDS[COMP_CWORD]}"
1719
1942
  prev="\${COMP_WORDS[COMP_CWORD-1]}"
1720
- commands="profile use run hook session provider complete help"
1943
+ commands="profile use run hook session provider completion help"
1721
1944
 
1722
1945
  local profile_subcmds="add update list view remove rename default sync"
1723
1946
  local provider_subcmds="list"
@@ -1783,8 +2006,67 @@ _cc-hub() {
1783
2006
 
1784
2007
  complete -F _cc-hub cc-hub
1785
2008
  `;
1786
- function completeCommand() {
1787
- return new Command5("complete").description("Print shell completion script").argument("<shell>", "Shell type: bash or zsh").action((shell) => {
2009
+
2010
+ // src/complete/powershell.ts
2011
+ var POWERSHELL_COMPLETION = `Register-ArgumentCompleter -Native -CommandName cc-hub -ScriptBlock {
2012
+ param($wordToComplete, $commandAst, $cursorPosition)
2013
+
2014
+ $commands = @(
2015
+ 'profile:Manage Claude CLI profiles'
2016
+ 'use:Set a profile as the default'
2017
+ 'run:Launch Claude Code using the default or a specified profile'
2018
+ 'hook:Manage Claude Code hooks in settings.json'
2019
+ 'session:Manage Claude Code sessions'
2020
+ 'provider:Manage provider types'
2021
+ 'completion:Print shell completion functions'
2022
+ 'help:Display help for a command'
2023
+ )
2024
+
2025
+ $profileSubcmds = @('add', 'update', 'list', 'view', 'remove', 'rename', 'default', 'sync')
2026
+ $hookSubcmds = @('list', 'add', 'remove', 'enable', 'disable')
2027
+ $sessionSubcmds = @('list', 'show', 'search', 'ps', 'stats', 'clean')
2028
+ $providerSubcmds = @('list')
2029
+
2030
+ $tokens = $commandAst.CommandElements | ForEach-Object { $_.ToString() }
2031
+
2032
+ if ($tokens.Count -eq 1 -or ($tokens.Count -eq 2 -and $wordToComplete -ne '')) {
2033
+ $commands | ForEach-Object { if ($_ -like "$wordToComplete*") { $_ } }
2034
+ return
2035
+ }
2036
+
2037
+ $cmd = $tokens[1]
2038
+
2039
+ switch ($cmd) {
2040
+ 'profile' {
2041
+ if ($tokens.Count -eq 2 -or ($tokens.Count -eq 3 -and $wordToComplete -ne '')) {
2042
+ $profileSubcmds | ForEach-Object { if ($_ -like "$wordToComplete*") { $_ } }
2043
+ return
2044
+ }
2045
+ }
2046
+ 'hook' {
2047
+ if ($tokens.Count -eq 2 -or ($tokens.Count -eq 3 -and $wordToComplete -ne '')) {
2048
+ $hookSubcmds | ForEach-Object { if ($_ -like "$wordToComplete*") { $_ } }
2049
+ return
2050
+ }
2051
+ }
2052
+ 'session' {
2053
+ if ($tokens.Count -eq 2 -or ($tokens.Count -eq 3 -and $wordToComplete -ne '')) {
2054
+ $sessionSubcmds | ForEach-Object { if ($_ -like "$wordToComplete*") { $_ } }
2055
+ return
2056
+ }
2057
+ }
2058
+ 'provider' {
2059
+ if ($tokens.Count -eq 2 -or ($tokens.Count -eq 3 -and $wordToComplete -ne '')) {
2060
+ $providerSubcmds | ForEach-Object { if ($_ -like "$wordToComplete*") { $_ } }
2061
+ return
2062
+ }
2063
+ }
2064
+ }
2065
+ }`;
2066
+
2067
+ // src/complete/index.ts
2068
+ function completionCommand() {
2069
+ return new Command5("completion").description("Print shell completion script").addArgument(new Argument("<shell>", "Shell type: bash, zsh, or powershell").choices(["bash", "zsh", "powershell"])).action((shell) => {
1788
2070
  switch (shell) {
1789
2071
  case "zsh":
1790
2072
  process.stdout.write(ZSH_COMPLETION);
@@ -1792,8 +2074,11 @@ function completeCommand() {
1792
2074
  case "bash":
1793
2075
  process.stdout.write(BASH_COMPLETION);
1794
2076
  break;
2077
+ case "powershell":
2078
+ process.stdout.write(POWERSHELL_COMPLETION);
2079
+ break;
1795
2080
  default:
1796
- console.error(`Unsupported shell: ${shell}. Use 'bash' or 'zsh'.`);
2081
+ console.error(`Unsupported shell: ${shell}. Use 'bash', 'zsh', or 'powershell'.`);
1797
2082
  process.exit(1);
1798
2083
  }
1799
2084
  });
@@ -1809,6 +2094,6 @@ program.addCommand(useCommand());
1809
2094
  program.addCommand(runCommand());
1810
2095
  program.addCommand(hooksCommand());
1811
2096
  program.addCommand(sessionCommand());
1812
- program.addCommand(completeCommand());
2097
+ program.addCommand(completionCommand());
1813
2098
  program.addCommand(providerCommand());
1814
2099
  program.parse();