cc-hub-cli 1.1.4 → 1.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +362 -202
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8,14 +8,110 @@ import { createRequire } from "module";
|
|
|
8
8
|
import { Command as Command2 } from "commander";
|
|
9
9
|
|
|
10
10
|
// src/config.ts
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import
|
|
11
|
+
import fs4 from "fs";
|
|
12
|
+
import path4 from "path";
|
|
13
|
+
import os3 from "os";
|
|
14
14
|
|
|
15
15
|
// src/platform/desktop-app.ts
|
|
16
|
+
import fs2 from "fs";
|
|
17
|
+
import path2 from "path";
|
|
18
|
+
import os2 from "os";
|
|
19
|
+
|
|
20
|
+
// src/logger.ts
|
|
16
21
|
import fs from "fs";
|
|
17
22
|
import path from "path";
|
|
18
23
|
import os from "os";
|
|
24
|
+
var LOG_DIR = path.join(os.homedir(), ".claude", "cc-hub", "logs");
|
|
25
|
+
var LEVEL_PRIORITY = {
|
|
26
|
+
DEBUG: 0,
|
|
27
|
+
INFO: 1,
|
|
28
|
+
WARN: 2,
|
|
29
|
+
ERROR: 3
|
|
30
|
+
};
|
|
31
|
+
var currentLevel = "INFO";
|
|
32
|
+
function setLogLevel(level) {
|
|
33
|
+
const upper = level.toUpperCase();
|
|
34
|
+
if (upper in LEVEL_PRIORITY) {
|
|
35
|
+
currentLevel = upper;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function shouldLog(level) {
|
|
39
|
+
return LEVEL_PRIORITY[level] >= LEVEL_PRIORITY[currentLevel];
|
|
40
|
+
}
|
|
41
|
+
function ensureLogDir() {
|
|
42
|
+
if (!fs.existsSync(LOG_DIR)) {
|
|
43
|
+
fs.mkdirSync(LOG_DIR, { recursive: true });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function logFilePath() {
|
|
47
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
48
|
+
return path.join(LOG_DIR, `cc-hub-${date}.log`);
|
|
49
|
+
}
|
|
50
|
+
function formatLine(level, message) {
|
|
51
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
52
|
+
return `[${ts}] [${level}] ${message}
|
|
53
|
+
`;
|
|
54
|
+
}
|
|
55
|
+
function write(level, message, err) {
|
|
56
|
+
if (!shouldLog(level)) return;
|
|
57
|
+
ensureLogDir();
|
|
58
|
+
let line = formatLine(level, message);
|
|
59
|
+
if (err instanceof Error) {
|
|
60
|
+
line += formatLine(level, err.stack || err.message);
|
|
61
|
+
} else if (err !== void 0) {
|
|
62
|
+
line += formatLine(level, String(err));
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
fs.appendFileSync(logFilePath(), line, "utf-8");
|
|
66
|
+
} catch {
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function error(message, err) {
|
|
70
|
+
write("ERROR", message, err);
|
|
71
|
+
}
|
|
72
|
+
function warn(message, err) {
|
|
73
|
+
write("WARN", message, err);
|
|
74
|
+
}
|
|
75
|
+
function info(message) {
|
|
76
|
+
write("INFO", message);
|
|
77
|
+
}
|
|
78
|
+
function debug(message) {
|
|
79
|
+
write("DEBUG", message);
|
|
80
|
+
}
|
|
81
|
+
function installGlobalExceptionHandlers() {
|
|
82
|
+
process.on("uncaughtException", (err) => {
|
|
83
|
+
error("Uncaught exception", err);
|
|
84
|
+
console.error("Unexpected error:", err.message);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
});
|
|
87
|
+
process.on("unhandledRejection", (reason) => {
|
|
88
|
+
error("Unhandled rejection", reason instanceof Error ? reason : new Error(String(reason)));
|
|
89
|
+
console.error("Unhandled promise rejection:", reason);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
function safeAction(fn) {
|
|
94
|
+
return ((...args) => {
|
|
95
|
+
info(`Executing: ${process.argv.slice(2).join(" ")}`);
|
|
96
|
+
try {
|
|
97
|
+
const result = fn(...args);
|
|
98
|
+
if (result && typeof result.then === "function") {
|
|
99
|
+
return result.catch((err) => {
|
|
100
|
+
error(`Command failed: ${err instanceof Error ? err.message : String(err)}`, err);
|
|
101
|
+
console.error("Error:", err instanceof Error ? err.message : String(err));
|
|
102
|
+
process.exit(1);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return result;
|
|
106
|
+
} catch (err) {
|
|
107
|
+
error(`Command failed: ${err instanceof Error ? err.message : String(err)}`, err);
|
|
108
|
+
console.error("Error:", err instanceof Error ? err.message : String(err));
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// src/platform/desktop-app.ts
|
|
19
115
|
function sortSemverDesc(a, b) {
|
|
20
116
|
const parse = (v) => v.split(".").map((n) => parseInt(n, 10));
|
|
21
117
|
const av = parse(a);
|
|
@@ -28,58 +124,61 @@ function sortSemverDesc(a, b) {
|
|
|
28
124
|
return 0;
|
|
29
125
|
}
|
|
30
126
|
var MacOSDesktopApp = class {
|
|
31
|
-
supportDir =
|
|
127
|
+
supportDir = path2.join(os2.homedir(), "Library/Application Support/Claude-3p");
|
|
32
128
|
isInstalled() {
|
|
33
|
-
return
|
|
129
|
+
return fs2.existsSync(this.supportDir);
|
|
34
130
|
}
|
|
35
131
|
getSupportDir() {
|
|
36
132
|
return this.isInstalled() ? this.supportDir : void 0;
|
|
37
133
|
}
|
|
38
134
|
getSessionsDir() {
|
|
39
|
-
return this.isInstalled() ?
|
|
135
|
+
return this.isInstalled() ? path2.join(this.supportDir, "local-agent-mode-sessions") : void 0;
|
|
40
136
|
}
|
|
41
137
|
getConfigLibrary() {
|
|
42
138
|
if (!this.isInstalled()) return void 0;
|
|
43
|
-
const configLib =
|
|
44
|
-
if (
|
|
45
|
-
if (
|
|
139
|
+
const configLib = path2.join(this.supportDir, "configLibrary");
|
|
140
|
+
if (fs2.existsSync(path2.join(configLib, "_meta.json"))) return configLib;
|
|
141
|
+
if (fs2.existsSync(configLib)) return configLib;
|
|
46
142
|
return configLib;
|
|
47
143
|
}
|
|
48
144
|
findBinary() {
|
|
49
|
-
|
|
50
|
-
|
|
145
|
+
debug(`desktop-app: searching for binary in ${this.supportDir}`);
|
|
146
|
+
const claudeCodeDir = path2.join(this.supportDir, "claude-code");
|
|
147
|
+
if (!fs2.existsSync(claudeCodeDir)) return void 0;
|
|
51
148
|
let versions;
|
|
52
149
|
try {
|
|
53
|
-
versions =
|
|
54
|
-
(d) =>
|
|
150
|
+
versions = fs2.readdirSync(claudeCodeDir).filter(
|
|
151
|
+
(d) => fs2.existsSync(path2.join(claudeCodeDir, d, "claude.app", "Contents", "MacOS", "claude"))
|
|
55
152
|
);
|
|
56
153
|
} catch {
|
|
57
154
|
return void 0;
|
|
58
155
|
}
|
|
59
156
|
if (versions.length === 0) return void 0;
|
|
60
157
|
versions.sort(sortSemverDesc);
|
|
61
|
-
|
|
158
|
+
const binary = path2.join(claudeCodeDir, versions[0], "claude.app", "Contents", "MacOS", "claude");
|
|
159
|
+
debug(`desktop-app: found macOS binary ${binary}`);
|
|
160
|
+
return binary;
|
|
62
161
|
}
|
|
63
162
|
};
|
|
64
163
|
var WindowsDesktopApp = class {
|
|
65
164
|
_buildCandidates() {
|
|
66
165
|
const candidates = [
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
166
|
+
path2.join(process.env.APPDATA || path2.join(os2.homedir(), "AppData", "Roaming"), "Claude-3p"),
|
|
167
|
+
path2.join(process.env.APPDATA || path2.join(os2.homedir(), "AppData", "Roaming"), "Claude"),
|
|
168
|
+
path2.join(process.env.LOCALAPPDATA || path2.join(os2.homedir(), "AppData", "Local"), "Claude-3p"),
|
|
169
|
+
path2.join(process.env.LOCALAPPDATA || path2.join(os2.homedir(), "AppData", "Local"), "Claude")
|
|
71
170
|
];
|
|
72
|
-
const packagesDir =
|
|
73
|
-
process.env.LOCALAPPDATA ||
|
|
171
|
+
const packagesDir = path2.join(
|
|
172
|
+
process.env.LOCALAPPDATA || path2.join(os2.homedir(), "AppData", "Local"),
|
|
74
173
|
"Packages"
|
|
75
174
|
);
|
|
76
|
-
if (
|
|
175
|
+
if (fs2.existsSync(packagesDir)) {
|
|
77
176
|
try {
|
|
78
|
-
const entries =
|
|
177
|
+
const entries = fs2.readdirSync(packagesDir);
|
|
79
178
|
for (const entry of entries) {
|
|
80
179
|
if (entry.startsWith("Claude_")) {
|
|
81
|
-
candidates.push(
|
|
82
|
-
candidates.push(
|
|
180
|
+
candidates.push(path2.join(packagesDir, entry, "LocalCache", "Roaming", "Claude-3p"));
|
|
181
|
+
candidates.push(path2.join(packagesDir, entry, "LocalCache", "Roaming", "Claude"));
|
|
83
182
|
}
|
|
84
183
|
}
|
|
85
184
|
} catch {
|
|
@@ -89,7 +188,7 @@ var WindowsDesktopApp = class {
|
|
|
89
188
|
}
|
|
90
189
|
_findSupportDir() {
|
|
91
190
|
for (const dir of this._buildCandidates()) {
|
|
92
|
-
if (
|
|
191
|
+
if (fs2.existsSync(dir)) return dir;
|
|
93
192
|
}
|
|
94
193
|
return void 0;
|
|
95
194
|
}
|
|
@@ -102,34 +201,34 @@ var WindowsDesktopApp = class {
|
|
|
102
201
|
getConfigLibrary() {
|
|
103
202
|
const candidates = this._buildCandidates();
|
|
104
203
|
for (const dir2 of candidates) {
|
|
105
|
-
const configLib =
|
|
106
|
-
if (
|
|
204
|
+
const configLib = path2.join(dir2, "configLibrary");
|
|
205
|
+
if (fs2.existsSync(path2.join(configLib, "_meta.json"))) {
|
|
107
206
|
return configLib;
|
|
108
207
|
}
|
|
109
208
|
}
|
|
110
209
|
for (const dir2 of candidates) {
|
|
111
|
-
const configLib =
|
|
112
|
-
if (
|
|
210
|
+
const configLib = path2.join(dir2, "configLibrary");
|
|
211
|
+
if (fs2.existsSync(configLib)) {
|
|
113
212
|
return configLib;
|
|
114
213
|
}
|
|
115
214
|
}
|
|
116
215
|
const dir = this._findSupportDir();
|
|
117
|
-
return dir ?
|
|
216
|
+
return dir ? path2.join(dir, "configLibrary") : void 0;
|
|
118
217
|
}
|
|
119
218
|
getSessionsDir() {
|
|
120
219
|
const candidates = this._buildCandidates();
|
|
121
220
|
for (const dir2 of candidates) {
|
|
122
|
-
const sessionsDir =
|
|
123
|
-
if (
|
|
221
|
+
const sessionsDir = path2.join(dir2, "local-agent-mode-sessions");
|
|
222
|
+
if (fs2.existsSync(sessionsDir)) {
|
|
124
223
|
return sessionsDir;
|
|
125
224
|
}
|
|
126
225
|
}
|
|
127
226
|
const dir = this._findSupportDir();
|
|
128
|
-
return dir ?
|
|
227
|
+
return dir ? path2.join(dir, "local-agent-mode-sessions") : void 0;
|
|
129
228
|
}
|
|
130
229
|
findBinary() {
|
|
131
|
-
const win32Binary =
|
|
132
|
-
if (
|
|
230
|
+
const win32Binary = path2.join(process.env.LOCALAPPDATA || "", "Programs", "Claude", "Claude.exe");
|
|
231
|
+
if (fs2.existsSync(win32Binary)) return win32Binary;
|
|
133
232
|
return void 0;
|
|
134
233
|
}
|
|
135
234
|
};
|
|
@@ -152,8 +251,8 @@ var NoOpDesktopApp = class {
|
|
|
152
251
|
};
|
|
153
252
|
|
|
154
253
|
// src/platform/profile-syncer.ts
|
|
155
|
-
import
|
|
156
|
-
import
|
|
254
|
+
import fs3 from "fs";
|
|
255
|
+
import path3 from "path";
|
|
157
256
|
import { randomUUID } from "crypto";
|
|
158
257
|
function toDesktopProfile(p) {
|
|
159
258
|
const models = p.models || (p.model ? [p.model] : []);
|
|
@@ -182,9 +281,10 @@ var DesktopProfileSyncer = class {
|
|
|
182
281
|
}
|
|
183
282
|
sync(name, p) {
|
|
184
283
|
const configLib = this.app.getConfigLibrary();
|
|
284
|
+
debug(`profile-syncer: sync '${name}' to ${configLib || "(none)"}`);
|
|
185
285
|
if (!configLib) return;
|
|
186
|
-
if (!
|
|
187
|
-
|
|
286
|
+
if (!fs3.existsSync(configLib)) {
|
|
287
|
+
fs3.mkdirSync(configLib, { recursive: true });
|
|
188
288
|
}
|
|
189
289
|
const meta = this.readMeta();
|
|
190
290
|
const entries = meta.entries || [];
|
|
@@ -207,9 +307,11 @@ var DesktopProfileSyncer = class {
|
|
|
207
307
|
meta.entries = entries;
|
|
208
308
|
this.writeMeta(meta);
|
|
209
309
|
this.writeProfile(id, configLib, toDesktopProfile(p));
|
|
310
|
+
debug(`profile-syncer: synced '${name}' id=${id}`);
|
|
210
311
|
}
|
|
211
312
|
remove(name, p) {
|
|
212
313
|
const configLib = this.app.getConfigLibrary();
|
|
314
|
+
debug(`profile-syncer: remove '${name}' id=${p.desktopId || "(none)"} from ${configLib || "(none)"}`);
|
|
213
315
|
if (!configLib || !p.desktopId) return;
|
|
214
316
|
const meta = this.readMeta();
|
|
215
317
|
if (meta.entries) {
|
|
@@ -219,13 +321,14 @@ var DesktopProfileSyncer = class {
|
|
|
219
321
|
delete meta.appliedId;
|
|
220
322
|
}
|
|
221
323
|
this.writeMeta(meta);
|
|
222
|
-
const filePath =
|
|
223
|
-
if (
|
|
224
|
-
|
|
324
|
+
const filePath = path3.join(configLib, `${p.desktopId}.json`);
|
|
325
|
+
if (fs3.existsSync(filePath)) {
|
|
326
|
+
fs3.unlinkSync(filePath);
|
|
225
327
|
}
|
|
226
328
|
}
|
|
227
329
|
setActive(p) {
|
|
228
330
|
const configLib = this.app.getConfigLibrary();
|
|
331
|
+
debug(`profile-syncer: setActive id=${p.desktopId || "(none)"} in ${configLib || "(none)"}`);
|
|
229
332
|
if (!configLib || !p.desktopId) return;
|
|
230
333
|
const meta = this.readMeta();
|
|
231
334
|
meta.appliedId = p.desktopId;
|
|
@@ -238,11 +341,11 @@ var DesktopProfileSyncer = class {
|
|
|
238
341
|
}
|
|
239
342
|
metaFile() {
|
|
240
343
|
const configLib = this.app.getConfigLibrary();
|
|
241
|
-
return configLib ?
|
|
344
|
+
return configLib ? path3.join(configLib, "_meta.json") : void 0;
|
|
242
345
|
}
|
|
243
346
|
readMeta() {
|
|
244
347
|
const file = this.metaFile();
|
|
245
|
-
if (!file || !
|
|
348
|
+
if (!file || !fs3.existsSync(file)) return {};
|
|
246
349
|
try {
|
|
247
350
|
return readJson(file);
|
|
248
351
|
} catch {
|
|
@@ -254,7 +357,7 @@ var DesktopProfileSyncer = class {
|
|
|
254
357
|
if (file) writeJson(file, meta);
|
|
255
358
|
}
|
|
256
359
|
writeProfile(id, configLib, data) {
|
|
257
|
-
writeJson(
|
|
360
|
+
writeJson(path3.join(configLib, `${id}.json`), data);
|
|
258
361
|
}
|
|
259
362
|
};
|
|
260
363
|
|
|
@@ -266,21 +369,25 @@ var SystemBinaryResolver = class {
|
|
|
266
369
|
}
|
|
267
370
|
app;
|
|
268
371
|
resolve() {
|
|
372
|
+
debug("binary-resolver: trying global 'claude' command");
|
|
269
373
|
try {
|
|
270
374
|
const result = spawnSync("claude", ["--version"], {
|
|
271
375
|
shell: process.platform === "win32",
|
|
272
376
|
encoding: "utf-8"
|
|
273
377
|
});
|
|
274
378
|
if (result.status === 0) {
|
|
379
|
+
debug("binary-resolver: found global 'claude'");
|
|
275
380
|
return "claude";
|
|
276
381
|
}
|
|
277
382
|
} catch {
|
|
278
383
|
}
|
|
384
|
+
debug("binary-resolver: trying desktop app binary");
|
|
279
385
|
const desktopBinary = this.app.findBinary();
|
|
280
|
-
if (desktopBinary)
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
386
|
+
if (desktopBinary) {
|
|
387
|
+
debug(`binary-resolver: found desktop binary at ${desktopBinary}`);
|
|
388
|
+
return desktopBinary;
|
|
389
|
+
}
|
|
390
|
+
throw new Error("Could not find Claude Code CLI. Install it globally or install the Claude Code desktop app.");
|
|
284
391
|
}
|
|
285
392
|
};
|
|
286
393
|
|
|
@@ -324,30 +431,33 @@ function createPathCodec() {
|
|
|
324
431
|
}
|
|
325
432
|
|
|
326
433
|
// src/config.ts
|
|
327
|
-
var CLAUDE_DIR = process.env.CLAUDE_DIR ||
|
|
328
|
-
var PROFILES_FILE = process.env.CLAUDE_PROFILES_FILE ||
|
|
329
|
-
var SETTINGS_FILE = process.env.CLAUDE_SETTINGS_FILE ||
|
|
330
|
-
var CLAUDE_JSON =
|
|
331
|
-
var PROJECTS_DIR =
|
|
332
|
-
var SESSIONS_DIR =
|
|
434
|
+
var CLAUDE_DIR = process.env.CLAUDE_DIR || path4.join(os3.homedir(), ".claude");
|
|
435
|
+
var PROFILES_FILE = process.env.CLAUDE_PROFILES_FILE || path4.join(CLAUDE_DIR, "profiles.json");
|
|
436
|
+
var SETTINGS_FILE = process.env.CLAUDE_SETTINGS_FILE || path4.join(CLAUDE_DIR, "settings.json");
|
|
437
|
+
var CLAUDE_JSON = path4.join(os3.homedir(), ".claude.json");
|
|
438
|
+
var PROJECTS_DIR = path4.join(CLAUDE_DIR, "projects");
|
|
439
|
+
var SESSIONS_DIR = path4.join(CLAUDE_DIR, "sessions");
|
|
333
440
|
var desktopApp = createDesktopApp();
|
|
334
441
|
var DESKTOP_CONFIG_LIBRARY = desktopApp.getConfigLibrary() || "";
|
|
335
|
-
var DESKTOP_META_FILE = DESKTOP_CONFIG_LIBRARY ?
|
|
442
|
+
var DESKTOP_META_FILE = DESKTOP_CONFIG_LIBRARY ? path4.join(DESKTOP_CONFIG_LIBRARY, "_meta.json") : "";
|
|
336
443
|
var DESKTOP_SESSIONS_DIR = desktopApp.getSessionsDir() || "";
|
|
337
444
|
function isDesktopAppInstalled() {
|
|
338
445
|
return desktopApp.isInstalled();
|
|
339
446
|
}
|
|
340
447
|
function ensureFile(filePath, defaultContent) {
|
|
341
|
-
if (!
|
|
342
|
-
|
|
343
|
-
|
|
448
|
+
if (!fs4.existsSync(filePath)) {
|
|
449
|
+
debug(`ensureFile: creating ${filePath}`);
|
|
450
|
+
fs4.mkdirSync(path4.dirname(filePath), { recursive: true });
|
|
451
|
+
fs4.writeFileSync(filePath, defaultContent, "utf-8");
|
|
344
452
|
}
|
|
345
453
|
}
|
|
346
454
|
function readJson(filePath) {
|
|
347
|
-
|
|
455
|
+
debug(`readJson: ${filePath}`);
|
|
456
|
+
return JSON.parse(fs4.readFileSync(filePath, "utf-8"));
|
|
348
457
|
}
|
|
349
458
|
function writeJson(filePath, data) {
|
|
350
|
-
|
|
459
|
+
debug(`writeJson: ${filePath}`);
|
|
460
|
+
fs4.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
351
461
|
}
|
|
352
462
|
function ensureProfilesFile() {
|
|
353
463
|
ensureFile(PROFILES_FILE, '{"profiles":{}}\n');
|
|
@@ -356,12 +466,12 @@ function ensureSettingsFile() {
|
|
|
356
466
|
ensureFile(SETTINGS_FILE, "{}\n");
|
|
357
467
|
}
|
|
358
468
|
function fixJsonFile(filePath, fallback = {}) {
|
|
359
|
-
if (!
|
|
360
|
-
const backupPath =
|
|
361
|
-
const raw =
|
|
469
|
+
if (!fs4.existsSync(filePath)) return;
|
|
470
|
+
const backupPath = path4.join(CLAUDE_DIR, path4.basename(filePath) + ".backup");
|
|
471
|
+
const raw = fs4.readFileSync(filePath, "utf-8");
|
|
362
472
|
try {
|
|
363
473
|
JSON.parse(raw);
|
|
364
|
-
|
|
474
|
+
fs4.copyFileSync(filePath, backupPath);
|
|
365
475
|
return;
|
|
366
476
|
} catch {
|
|
367
477
|
}
|
|
@@ -386,15 +496,18 @@ function fixJsonFile(filePath, fallback = {}) {
|
|
|
386
496
|
if (openCurly > 0) text += "}".repeat(openCurly);
|
|
387
497
|
try {
|
|
388
498
|
JSON.parse(text);
|
|
389
|
-
|
|
390
|
-
|
|
499
|
+
fs4.writeFileSync(filePath, text + "\n", "utf-8");
|
|
500
|
+
warn(`Fixed invalid JSON in ${path4.basename(filePath)}.`);
|
|
501
|
+
console.error(`Fixed invalid JSON in ${path4.basename(filePath)}.`);
|
|
391
502
|
} catch {
|
|
392
|
-
if (
|
|
393
|
-
|
|
394
|
-
|
|
503
|
+
if (fs4.existsSync(backupPath)) {
|
|
504
|
+
fs4.copyFileSync(backupPath, filePath);
|
|
505
|
+
warn(`Restored ${path4.basename(filePath)} from backup.`);
|
|
506
|
+
console.error(`Restored ${path4.basename(filePath)} from backup.`);
|
|
395
507
|
} else {
|
|
396
508
|
writeJson(filePath, fallback);
|
|
397
|
-
|
|
509
|
+
error(`Could not fix ${path4.basename(filePath)}, no backup found, reset to default.`);
|
|
510
|
+
console.error(`Could not fix ${path4.basename(filePath)}, no backup found, reset to default.`);
|
|
398
511
|
}
|
|
399
512
|
}
|
|
400
513
|
}
|
|
@@ -414,6 +527,7 @@ function sanitizeToolId(id) {
|
|
|
414
527
|
return sanitized;
|
|
415
528
|
}
|
|
416
529
|
function transformAnthropicToOpenAI(body) {
|
|
530
|
+
debug(`transform: anthropic -> openai model=${body.model} messages=${(body.messages ?? []).length}`);
|
|
417
531
|
const messages = [];
|
|
418
532
|
if (body.system) {
|
|
419
533
|
if (typeof body.system === "string") {
|
|
@@ -495,6 +609,7 @@ function transformAnthropicToOpenAI(body) {
|
|
|
495
609
|
if (body.max_tokens != null) result.max_tokens = body.max_tokens;
|
|
496
610
|
if (body.temperature != null) result.temperature = body.temperature;
|
|
497
611
|
if (body.tools?.length) {
|
|
612
|
+
debug(`transform: mapping ${body.tools.length} tool(s)`);
|
|
498
613
|
result.tools = body.tools.map((t) => ({
|
|
499
614
|
type: "function",
|
|
500
615
|
function: {
|
|
@@ -518,6 +633,7 @@ function transformAnthropicToOpenAI(body) {
|
|
|
518
633
|
return result;
|
|
519
634
|
}
|
|
520
635
|
function transformOpenAIResponseToAnthropic(openaiResponse, originalModel) {
|
|
636
|
+
debug(`transform: openai -> anthropic model=${openaiResponse.model ?? originalModel} choices=${openaiResponse.choices?.length ?? 0}`);
|
|
521
637
|
const choice = openaiResponse.choices?.[0];
|
|
522
638
|
if (!choice) throw new Error("No choices in OpenAI response");
|
|
523
639
|
const content = [];
|
|
@@ -622,6 +738,7 @@ import http from "http";
|
|
|
622
738
|
async function startOpenAIProxy(targetUrl, apiKey, model, models = []) {
|
|
623
739
|
const base = targetUrl.replace(/\/+$/, "");
|
|
624
740
|
const server = http.createServer(async (req, res) => {
|
|
741
|
+
debug(`Proxy request: ${req.method} ${req.url}`);
|
|
625
742
|
try {
|
|
626
743
|
if (req.method === "GET" && req.url === "/v1/models") {
|
|
627
744
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
@@ -663,6 +780,7 @@ async function startOpenAIProxy(targetUrl, apiKey, model, models = []) {
|
|
|
663
780
|
});
|
|
664
781
|
if (!upstream2.ok) {
|
|
665
782
|
const errText = await upstream2.text();
|
|
783
|
+
error(`Upstream streaming error: ${upstream2.status} ${errText}`);
|
|
666
784
|
res.write(`event: error
|
|
667
785
|
data: ${errText}
|
|
668
786
|
|
|
@@ -691,6 +809,7 @@ data: ${errText}
|
|
|
691
809
|
});
|
|
692
810
|
if (!upstream.ok) {
|
|
693
811
|
const errText = await upstream.text();
|
|
812
|
+
error(`Upstream error: ${upstream.status} ${errText}`);
|
|
694
813
|
res.writeHead(upstream.status, { "Content-Type": "application/json" });
|
|
695
814
|
res.end(errText);
|
|
696
815
|
return;
|
|
@@ -704,6 +823,7 @@ data: ${errText}
|
|
|
704
823
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
705
824
|
res.end(JSON.stringify({ error: { type: "not_found", message: "endpoint not found" } }));
|
|
706
825
|
} catch (err) {
|
|
826
|
+
error("Proxy request handler error", err);
|
|
707
827
|
if (!res.headersSent) {
|
|
708
828
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
709
829
|
res.end(JSON.stringify({ error: { type: "internal_error", message: String(err) } }));
|
|
@@ -714,9 +834,13 @@ data: ${errText}
|
|
|
714
834
|
server.listen(0, "127.0.0.1", () => {
|
|
715
835
|
const addr = server.address();
|
|
716
836
|
const baseUrl = `http://127.0.0.1:${addr.port}`;
|
|
837
|
+
debug(`OpenAI proxy listening on ${baseUrl}`);
|
|
717
838
|
resolve({
|
|
718
839
|
baseUrl,
|
|
719
|
-
stop: () =>
|
|
840
|
+
stop: () => {
|
|
841
|
+
debug("OpenAI proxy stopped");
|
|
842
|
+
server.close();
|
|
843
|
+
}
|
|
720
844
|
});
|
|
721
845
|
});
|
|
722
846
|
server.on("error", reject);
|
|
@@ -744,14 +868,14 @@ var PROVIDERS = [
|
|
|
744
868
|
];
|
|
745
869
|
function providerCommand() {
|
|
746
870
|
const cmd = new Command("provider").description("Manage provider types");
|
|
747
|
-
cmd.command("list").description("List available provider types").action(() => {
|
|
871
|
+
cmd.command("list").description("List available provider types").action(safeAction(() => {
|
|
748
872
|
const fmt = (name, desc) => `${name.padEnd(12)} ${desc}`;
|
|
749
873
|
console.log(fmt("NAME", "DESCRIPTION"));
|
|
750
874
|
console.log(fmt("----", "-----------"));
|
|
751
875
|
for (const p of PROVIDERS) {
|
|
752
876
|
console.log(fmt(p.name, p.description));
|
|
753
877
|
}
|
|
754
|
-
});
|
|
878
|
+
}));
|
|
755
879
|
return cmd;
|
|
756
880
|
}
|
|
757
881
|
|
|
@@ -760,11 +884,12 @@ function resolveClaudeBinary() {
|
|
|
760
884
|
return createBinaryResolver().resolve();
|
|
761
885
|
}
|
|
762
886
|
function updateSettingsForProfile(p) {
|
|
887
|
+
debug(`updateSettingsForProfile: reading ${SETTINGS_FILE}`);
|
|
763
888
|
ensureSettingsFile();
|
|
764
|
-
const
|
|
889
|
+
const settings2 = readJson(SETTINGS_FILE);
|
|
765
890
|
const models = p.models || (p.model ? [p.model] : []);
|
|
766
|
-
delete
|
|
767
|
-
delete
|
|
891
|
+
delete settings2.model;
|
|
892
|
+
delete settings2.availableModels;
|
|
768
893
|
const envVarsToClean = [
|
|
769
894
|
"ANTHROPIC_DEFAULT_OPUS_MODEL",
|
|
770
895
|
"ANTHROPIC_DEFAULT_OPUS_MODEL_NAME",
|
|
@@ -777,13 +902,14 @@ function updateSettingsForProfile(p) {
|
|
|
777
902
|
"ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION",
|
|
778
903
|
"ANTHROPIC_CUSTOM_MODEL_OPTION"
|
|
779
904
|
];
|
|
780
|
-
if (
|
|
781
|
-
const env =
|
|
905
|
+
if (settings2.env) {
|
|
906
|
+
const env = settings2.env;
|
|
782
907
|
for (const key of envVarsToClean) {
|
|
783
908
|
delete env[key];
|
|
784
909
|
}
|
|
785
910
|
}
|
|
786
|
-
writeJson(SETTINGS_FILE,
|
|
911
|
+
writeJson(SETTINGS_FILE, settings2);
|
|
912
|
+
debug(`updateSettingsForProfile: wrote ${SETTINGS_FILE}`);
|
|
787
913
|
}
|
|
788
914
|
function execClaude(profileName, p, extraArgs) {
|
|
789
915
|
updateSettingsForProfile(p);
|
|
@@ -819,9 +945,10 @@ function execClaude(profileName, p, extraArgs) {
|
|
|
819
945
|
env.ANTHROPIC_CUSTOM_MODEL_OPTION = models[0];
|
|
820
946
|
}
|
|
821
947
|
delete env.ANTHROPIC_API_KEY;
|
|
822
|
-
|
|
948
|
+
info(`Launching Claude with profile '${profileName}': model=${firstModel || "(default)"} url=${p.url || "(default)"} provider=${p.provider || "anthropic"} binary=${binary}`);
|
|
823
949
|
if (p.provider === "openai") {
|
|
824
950
|
const allModels = p.models || (p.model ? [p.model] : []);
|
|
951
|
+
debug(`execClaude: starting OpenAI proxy for ${allModels.length} model(s)`);
|
|
825
952
|
startOpenAIProxy(
|
|
826
953
|
p.url || "https://api.openai.com",
|
|
827
954
|
p.token || "",
|
|
@@ -829,12 +956,14 @@ function execClaude(profileName, p, extraArgs) {
|
|
|
829
956
|
allModels
|
|
830
957
|
).then(({ baseUrl, stop }) => {
|
|
831
958
|
env.ANTHROPIC_BASE_URL = baseUrl;
|
|
959
|
+
debug(`execClaude: proxy running at ${baseUrl}`);
|
|
832
960
|
const child = spawn(cmd[0], cmd.slice(1), { stdio: "inherit", env, shell: process.platform === "win32" });
|
|
833
961
|
child.on("close", (code) => {
|
|
834
962
|
stop();
|
|
835
963
|
process.exit(code ?? 1);
|
|
836
964
|
});
|
|
837
965
|
}).catch((err) => {
|
|
966
|
+
error("Failed to start OpenAI proxy", err);
|
|
838
967
|
console.error("Failed to start OpenAI proxy:", err);
|
|
839
968
|
process.exit(1);
|
|
840
969
|
});
|
|
@@ -890,11 +1019,10 @@ function collect(value, previous) {
|
|
|
890
1019
|
function profileCommand() {
|
|
891
1020
|
const profile = new Command2("profile").description("Manage Claude CLI profiles");
|
|
892
1021
|
const syncer = createProfileSyncer();
|
|
893
|
-
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) => {
|
|
1022
|
+
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(safeAction((name, opts) => {
|
|
894
1023
|
const models = opts.model && opts.model.length > 0 ? opts.model : void 0;
|
|
895
1024
|
if (models && models.length > 3) {
|
|
896
|
-
|
|
897
|
-
process.exit(1);
|
|
1025
|
+
throw new Error("Error: A profile can have at most 3 models.");
|
|
898
1026
|
}
|
|
899
1027
|
ensureProfilesFile();
|
|
900
1028
|
const data = readJson(PROFILES_FILE);
|
|
@@ -907,16 +1035,17 @@ function profileCommand() {
|
|
|
907
1035
|
if (opts.url) profile2.url = opts.url;
|
|
908
1036
|
if (opts.provider) profile2.provider = opts.provider;
|
|
909
1037
|
data.profiles[name] = profile2;
|
|
1038
|
+
debug(`profile add: syncing profile '${name}' to desktop`);
|
|
910
1039
|
syncer.sync(name, profile2);
|
|
911
1040
|
writeJson(PROFILES_FILE, data);
|
|
1041
|
+
debug(`profile add: wrote ${PROFILES_FILE}`);
|
|
912
1042
|
console.log(`Profile '${name}' saved.`);
|
|
913
|
-
});
|
|
914
|
-
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) => {
|
|
1043
|
+
}));
|
|
1044
|
+
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(safeAction((name, opts) => {
|
|
915
1045
|
ensureProfilesFile();
|
|
916
1046
|
const data = readJson(PROFILES_FILE);
|
|
917
1047
|
if (!data.profiles[name]) {
|
|
918
|
-
|
|
919
|
-
process.exit(1);
|
|
1048
|
+
throw new Error(`Profile '${name}' not found. Use 'profile add' to create it.`);
|
|
920
1049
|
}
|
|
921
1050
|
const p = data.profiles[name];
|
|
922
1051
|
const providedModels = opts.model && opts.model.length > 0 ? opts.model : void 0;
|
|
@@ -962,17 +1091,18 @@ function profileCommand() {
|
|
|
962
1091
|
}
|
|
963
1092
|
const finalModels = p.models || (p.model ? [p.model] : []);
|
|
964
1093
|
if (finalModels.length > 3) {
|
|
965
|
-
|
|
966
|
-
process.exit(1);
|
|
1094
|
+
throw new Error("Error: A profile can have at most 3 models.");
|
|
967
1095
|
}
|
|
968
1096
|
if (opts.token) p.token = opts.token;
|
|
969
1097
|
if (opts.url) p.url = opts.url;
|
|
970
1098
|
if (opts.provider) p.provider = opts.provider;
|
|
1099
|
+
debug(`profile update: syncing profile '${name}' to desktop`);
|
|
971
1100
|
syncer.sync(name, p);
|
|
972
1101
|
writeJson(PROFILES_FILE, data);
|
|
1102
|
+
debug(`profile update: wrote ${PROFILES_FILE}`);
|
|
973
1103
|
console.log(`Profile '${name}' updated.`);
|
|
974
|
-
});
|
|
975
|
-
profile.command("list").description("List all profiles").action(() => {
|
|
1104
|
+
}));
|
|
1105
|
+
profile.command("list").description("List all profiles").action(safeAction(() => {
|
|
976
1106
|
ensureProfilesFile();
|
|
977
1107
|
const data = readJson(PROFILES_FILE);
|
|
978
1108
|
const profiles = data.profiles;
|
|
@@ -999,14 +1129,13 @@ function profileCommand() {
|
|
|
999
1129
|
p.url || "(default)"
|
|
1000
1130
|
));
|
|
1001
1131
|
}
|
|
1002
|
-
});
|
|
1003
|
-
profile.command("view").description("View full details of a profile (token unmasked)").argument("<name>", "Profile name").option("-j, --json", "Output as JSON").action((name, opts) => {
|
|
1132
|
+
}));
|
|
1133
|
+
profile.command("view").description("View full details of a profile (token unmasked)").argument("<name>", "Profile name").option("-j, --json", "Output as JSON").action(safeAction((name, opts) => {
|
|
1004
1134
|
ensureProfilesFile();
|
|
1005
1135
|
const data = readJson(PROFILES_FILE);
|
|
1006
1136
|
const p = data.profiles[name];
|
|
1007
1137
|
if (!p) {
|
|
1008
|
-
|
|
1009
|
-
process.exit(1);
|
|
1138
|
+
throw new Error(`Profile '${name}' not found.`);
|
|
1010
1139
|
}
|
|
1011
1140
|
if (opts.json) {
|
|
1012
1141
|
const { desktopId, ...rest } = p;
|
|
@@ -1034,29 +1163,28 @@ function profileCommand() {
|
|
|
1034
1163
|
console.log(`URL: ${p.url || "(default)"}`);
|
|
1035
1164
|
console.log(`Provider: ${p.provider || "anthropic"}`);
|
|
1036
1165
|
}
|
|
1037
|
-
});
|
|
1038
|
-
profile.command("remove").description("Remove a profile").argument("<name>", "Profile name").action((name) => {
|
|
1166
|
+
}));
|
|
1167
|
+
profile.command("remove").description("Remove a profile").argument("<name>", "Profile name").action(safeAction((name) => {
|
|
1039
1168
|
ensureProfilesFile();
|
|
1040
1169
|
const data = readJson(PROFILES_FILE);
|
|
1041
1170
|
if (!data.profiles[name]) {
|
|
1042
|
-
|
|
1043
|
-
process.exit(1);
|
|
1171
|
+
throw new Error(`Profile '${name}' not found.`);
|
|
1044
1172
|
}
|
|
1173
|
+
debug(`profile remove: removing profile '${name}' from desktop sync`);
|
|
1045
1174
|
syncer.remove(name, data.profiles[name]);
|
|
1046
1175
|
delete data.profiles[name];
|
|
1047
1176
|
writeJson(PROFILES_FILE, data);
|
|
1177
|
+
debug(`profile remove: wrote ${PROFILES_FILE}`);
|
|
1048
1178
|
console.log(`Profile '${name}' removed.`);
|
|
1049
|
-
});
|
|
1050
|
-
profile.command("rename").description("Rename a profile").argument("<oldName>", "Current profile name").argument("<newName>", "New profile name").action((oldName, newName) => {
|
|
1179
|
+
}));
|
|
1180
|
+
profile.command("rename").description("Rename a profile").argument("<oldName>", "Current profile name").argument("<newName>", "New profile name").action(safeAction((oldName, newName) => {
|
|
1051
1181
|
ensureProfilesFile();
|
|
1052
1182
|
const data = readJson(PROFILES_FILE);
|
|
1053
1183
|
if (!data.profiles[oldName]) {
|
|
1054
|
-
|
|
1055
|
-
process.exit(1);
|
|
1184
|
+
throw new Error(`Profile '${oldName}' not found.`);
|
|
1056
1185
|
}
|
|
1057
1186
|
if (data.profiles[newName]) {
|
|
1058
|
-
|
|
1059
|
-
process.exit(1);
|
|
1187
|
+
throw new Error(`Profile '${newName}' already exists. Choose a different name.`);
|
|
1060
1188
|
}
|
|
1061
1189
|
data.profiles[newName] = data.profiles[oldName];
|
|
1062
1190
|
delete data.profiles[oldName];
|
|
@@ -1065,23 +1193,23 @@ function profileCommand() {
|
|
|
1065
1193
|
}
|
|
1066
1194
|
writeJson(PROFILES_FILE, data);
|
|
1067
1195
|
console.log(`Profile '${oldName}' renamed to '${newName}'.`);
|
|
1068
|
-
});
|
|
1069
|
-
profile.command("default").description("Set the default profile").argument("<name>", "Profile name to set as default").action((name) => {
|
|
1196
|
+
}));
|
|
1197
|
+
profile.command("default").description("Set the default profile").argument("<name>", "Profile name to set as default").action(safeAction((name) => {
|
|
1070
1198
|
ensureProfilesFile();
|
|
1071
1199
|
const data = readJson(PROFILES_FILE);
|
|
1072
1200
|
if (!data.profiles[name]) {
|
|
1073
|
-
|
|
1074
|
-
process.exit(1);
|
|
1201
|
+
throw new Error(`Profile '${name}' not found.`);
|
|
1075
1202
|
}
|
|
1076
1203
|
data.default = name;
|
|
1204
|
+
debug(`profile default: setting active desktop profile to '${name}'`);
|
|
1077
1205
|
syncer.setActive(data.profiles[name]);
|
|
1078
1206
|
writeJson(PROFILES_FILE, data);
|
|
1207
|
+
debug(`profile default: wrote ${PROFILES_FILE}`);
|
|
1079
1208
|
console.log(`Default profile set to '${name}'.`);
|
|
1080
|
-
});
|
|
1081
|
-
profile.command("sync").description("Synchronize all CLI profiles to the Claude desktop app").action(() => {
|
|
1209
|
+
}));
|
|
1210
|
+
profile.command("sync").description("Synchronize all CLI profiles to the Claude desktop app").action(safeAction(() => {
|
|
1082
1211
|
if (!syncer.isSupported()) {
|
|
1083
|
-
|
|
1084
|
-
process.exit(1);
|
|
1212
|
+
throw new Error("Claude desktop app is not installed.");
|
|
1085
1213
|
}
|
|
1086
1214
|
ensureProfilesFile();
|
|
1087
1215
|
const data = readJson(PROFILES_FILE);
|
|
@@ -1092,30 +1220,33 @@ function profileCommand() {
|
|
|
1092
1220
|
}
|
|
1093
1221
|
for (const name of names) {
|
|
1094
1222
|
const p = data.profiles[name];
|
|
1223
|
+
debug(`profile sync: syncing '${name}' to desktop`);
|
|
1095
1224
|
syncer.sync(name, p);
|
|
1096
1225
|
}
|
|
1097
1226
|
writeJson(PROFILES_FILE, data);
|
|
1227
|
+
debug(`profile sync: wrote ${PROFILES_FILE}`);
|
|
1098
1228
|
console.log(`Synced ${names.length} profile(s) to the desktop app.`);
|
|
1099
|
-
});
|
|
1229
|
+
}));
|
|
1100
1230
|
return profile;
|
|
1101
1231
|
}
|
|
1102
1232
|
function useCommand() {
|
|
1103
1233
|
const syncer = createProfileSyncer();
|
|
1104
|
-
return new Command2("use").description("Set a profile as the default").argument("<name>", "Profile name").action((name) => {
|
|
1234
|
+
return new Command2("use").description("Set a profile as the default").argument("<name>", "Profile name").action(safeAction((name) => {
|
|
1105
1235
|
ensureProfilesFile();
|
|
1106
1236
|
const data = readJson(PROFILES_FILE);
|
|
1107
1237
|
if (!data.profiles[name]) {
|
|
1108
|
-
|
|
1109
|
-
process.exit(1);
|
|
1238
|
+
throw new Error(`Profile '${name}' not found.`);
|
|
1110
1239
|
}
|
|
1111
1240
|
data.default = name;
|
|
1241
|
+
debug(`use: setting active desktop profile to '${name}'`);
|
|
1112
1242
|
syncer.setActive(data.profiles[name]);
|
|
1113
1243
|
writeJson(PROFILES_FILE, data);
|
|
1244
|
+
debug(`use: wrote ${PROFILES_FILE}`);
|
|
1114
1245
|
console.log(`Default profile set to '${name}'.`);
|
|
1115
|
-
});
|
|
1246
|
+
}));
|
|
1116
1247
|
}
|
|
1117
1248
|
function runCommand() {
|
|
1118
|
-
return new Command2("run").description("Launch Claude Code using the default or a specified profile").allowUnknownOption().argument("[args...]", "Optional profile name followed by extra arguments").action((args) => {
|
|
1249
|
+
return new Command2("run").description("Launch Claude Code using the default or a specified profile").allowUnknownOption().argument("[args...]", "Optional profile name followed by extra arguments").action(safeAction((args) => {
|
|
1119
1250
|
fixJsonFile(CLAUDE_JSON);
|
|
1120
1251
|
ensureProfilesFile();
|
|
1121
1252
|
const data = readJson(PROFILES_FILE);
|
|
@@ -1129,12 +1260,12 @@ function runCommand() {
|
|
|
1129
1260
|
claudeArgs = args;
|
|
1130
1261
|
}
|
|
1131
1262
|
if (!profileName) {
|
|
1132
|
-
|
|
1133
|
-
process.exit(1);
|
|
1263
|
+
throw new Error("No default profile set. Use 'cc-hub use <name>' first.");
|
|
1134
1264
|
}
|
|
1135
1265
|
const p = data.profiles[profileName];
|
|
1266
|
+
debug(`run: launching claude with profile '${profileName}', args=[${claudeArgs.join(", ")}]`);
|
|
1136
1267
|
execClaude(profileName, p, claudeArgs);
|
|
1137
|
-
});
|
|
1268
|
+
}));
|
|
1138
1269
|
}
|
|
1139
1270
|
|
|
1140
1271
|
// src/hooks/commands.ts
|
|
@@ -1196,12 +1327,15 @@ function displayHookList(data) {
|
|
|
1196
1327
|
}
|
|
1197
1328
|
function hooksCommand() {
|
|
1198
1329
|
const hooks = new Command3("hook").description("Manage Claude Code hooks in settings.json");
|
|
1199
|
-
hooks.command("list").description("List all hooks").action(() => {
|
|
1330
|
+
hooks.command("list").description("List all hooks").action(safeAction(() => {
|
|
1331
|
+
debug("hook list: reading settings");
|
|
1200
1332
|
ensureSettingsFile();
|
|
1201
1333
|
const data = readJson(SETTINGS_FILE);
|
|
1334
|
+
debug(`hook list: found ${Object.keys(data.hooks || {}).length} event types`);
|
|
1202
1335
|
displayHookList(data);
|
|
1203
|
-
});
|
|
1204
|
-
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) => {
|
|
1336
|
+
}));
|
|
1337
|
+
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(safeAction((opts) => {
|
|
1338
|
+
debug(`hook add: event=${opts.event} matcher=${opts.matcher || "(any)"}`);
|
|
1205
1339
|
ensureSettingsFile();
|
|
1206
1340
|
const data = readJson(SETTINGS_FILE);
|
|
1207
1341
|
const hooksRoot = data.hooks || (data.hooks = {});
|
|
@@ -1219,16 +1353,17 @@ function hooksCommand() {
|
|
|
1219
1353
|
if (opts.async) newHook.async = true;
|
|
1220
1354
|
targetGroup.hooks.push(newHook);
|
|
1221
1355
|
writeJson(SETTINGS_FILE, data);
|
|
1356
|
+
debug(`hook add: wrote settings with new hook seq=${seq}`);
|
|
1222
1357
|
console.log(`Hook added to event '${opts.event}'${matcher ? ` matcher='${matcher}'` : ""}.`);
|
|
1223
|
-
});
|
|
1224
|
-
hooks.command("remove").description("Remove a hook by its global index (see 'hooks list')").requiredOption("-i, --index <index>", "Global index from 'hooks list'", parseInt).action((opts) => {
|
|
1358
|
+
}));
|
|
1359
|
+
hooks.command("remove").description("Remove a hook by its global index (see 'hooks list')").requiredOption("-i, --index <index>", "Global index from 'hooks list'", parseInt).action(safeAction((opts) => {
|
|
1360
|
+
debug(`hook remove: index=${opts.index}`);
|
|
1225
1361
|
ensureSettingsFile();
|
|
1226
1362
|
const data = readJson(SETTINGS_FILE);
|
|
1227
1363
|
const rows = buildFlat(data);
|
|
1228
1364
|
const target = opts.index;
|
|
1229
1365
|
if (target < 0 || target >= rows.length) {
|
|
1230
|
-
|
|
1231
|
-
process.exit(1);
|
|
1366
|
+
throw new Error(`Index ${target} out of range (0-${rows.length - 1}).`);
|
|
1232
1367
|
}
|
|
1233
1368
|
const r = rows[target];
|
|
1234
1369
|
if (r.active) {
|
|
@@ -1242,13 +1377,15 @@ function hooksCommand() {
|
|
|
1242
1377
|
if (pool.length === 0) delete data._cc_hub_disabled;
|
|
1243
1378
|
}
|
|
1244
1379
|
writeJson(SETTINGS_FILE, data);
|
|
1380
|
+
debug(`hook remove: wrote settings after removing hook ${target}`);
|
|
1245
1381
|
console.log(`Hook ${target} removed.`);
|
|
1246
|
-
});
|
|
1382
|
+
}));
|
|
1247
1383
|
hooks.command("enable").description("Enable one or more disabled hooks").requiredOption("-i, --index <indexes...>", "Global index from 'hooks list' (repeatable)", (v, prev) => {
|
|
1248
1384
|
prev = prev || [];
|
|
1249
1385
|
prev.push(parseInt(v));
|
|
1250
1386
|
return prev;
|
|
1251
|
-
}).action((opts) => {
|
|
1387
|
+
}).action(safeAction((opts) => {
|
|
1388
|
+
debug(`hook enable: indexes=[${opts.index.join(", ")}]`);
|
|
1252
1389
|
ensureSettingsFile();
|
|
1253
1390
|
const data = readJson(SETTINGS_FILE);
|
|
1254
1391
|
const rows = buildFlat(data);
|
|
@@ -1259,8 +1396,7 @@ function hooksCommand() {
|
|
|
1259
1396
|
else if (rows[t].active) errors.push(`Index ${t} is already active.`);
|
|
1260
1397
|
}
|
|
1261
1398
|
if (errors.length > 0) {
|
|
1262
|
-
|
|
1263
|
-
process.exit(1);
|
|
1399
|
+
throw new Error(errors.join("\n"));
|
|
1264
1400
|
}
|
|
1265
1401
|
const hooksRoot = data.hooks || (data.hooks = {});
|
|
1266
1402
|
const pool = data._cc_hub_disabled;
|
|
@@ -1292,14 +1428,16 @@ function hooksCommand() {
|
|
|
1292
1428
|
data._cc_hub_disabled = remaining;
|
|
1293
1429
|
if (remaining.length === 0) delete data._cc_hub_disabled;
|
|
1294
1430
|
writeJson(SETTINGS_FILE, data);
|
|
1431
|
+
debug(`hook enable: wrote settings after restoring ${toRestore.length} hook(s)`);
|
|
1295
1432
|
console.log("");
|
|
1296
1433
|
displayHookList(data);
|
|
1297
|
-
});
|
|
1434
|
+
}));
|
|
1298
1435
|
hooks.command("disable").description("Disable one or more hooks (removes from active)").requiredOption("-i, --index <indexes...>", "Global index from 'hooks list' (repeatable)", (v, prev) => {
|
|
1299
1436
|
prev = prev || [];
|
|
1300
1437
|
prev.push(parseInt(v));
|
|
1301
1438
|
return prev;
|
|
1302
|
-
}).action((opts) => {
|
|
1439
|
+
}).action(safeAction((opts) => {
|
|
1440
|
+
debug(`hook disable: indexes=[${opts.index.join(", ")}]`);
|
|
1303
1441
|
ensureSettingsFile();
|
|
1304
1442
|
const data = readJson(SETTINGS_FILE);
|
|
1305
1443
|
const rows = buildFlat(data);
|
|
@@ -1310,8 +1448,7 @@ function hooksCommand() {
|
|
|
1310
1448
|
else if (!rows[t].active) errors.push(`Index ${t} is already disabled.`);
|
|
1311
1449
|
}
|
|
1312
1450
|
if (errors.length > 0) {
|
|
1313
|
-
|
|
1314
|
-
process.exit(1);
|
|
1451
|
+
throw new Error(errors.join("\n"));
|
|
1315
1452
|
}
|
|
1316
1453
|
const hooksRoot = data.hooks;
|
|
1317
1454
|
const pool = data._cc_hub_disabled || (data._cc_hub_disabled = []);
|
|
@@ -1330,32 +1467,37 @@ function hooksCommand() {
|
|
|
1330
1467
|
console.log(`Hook ${t} (${r.event}) disabled.`);
|
|
1331
1468
|
}
|
|
1332
1469
|
writeJson(SETTINGS_FILE, data);
|
|
1470
|
+
debug(`hook disable: wrote settings after disabling ${targets.length} hook(s)`);
|
|
1333
1471
|
console.log("");
|
|
1334
1472
|
displayHookList(data);
|
|
1335
|
-
});
|
|
1473
|
+
}));
|
|
1336
1474
|
return hooks;
|
|
1337
1475
|
}
|
|
1338
1476
|
|
|
1339
1477
|
// src/sessions/codec.ts
|
|
1340
1478
|
function encodePath(p) {
|
|
1341
|
-
|
|
1479
|
+
const encoded = createPathCodec().encode(p);
|
|
1480
|
+
debug(`codec: encode "${p}" -> "${encoded}"`);
|
|
1481
|
+
return encoded;
|
|
1342
1482
|
}
|
|
1343
1483
|
function decodePath(encoded) {
|
|
1344
|
-
|
|
1484
|
+
const decoded = createPathCodec().decode(encoded);
|
|
1485
|
+
debug(`codec: decode "${encoded}" -> "${decoded}"`);
|
|
1486
|
+
return decoded;
|
|
1345
1487
|
}
|
|
1346
1488
|
|
|
1347
1489
|
// src/sessions/stats.ts
|
|
1348
|
-
import
|
|
1349
|
-
import
|
|
1490
|
+
import fs5 from "fs";
|
|
1491
|
+
import path5 from "path";
|
|
1350
1492
|
function getDirSize(dir) {
|
|
1351
1493
|
let total = 0;
|
|
1352
1494
|
try {
|
|
1353
|
-
for (const entry of
|
|
1354
|
-
const fullPath =
|
|
1495
|
+
for (const entry of fs5.readdirSync(dir, { withFileTypes: true })) {
|
|
1496
|
+
const fullPath = path5.join(dir, entry.name);
|
|
1355
1497
|
if (entry.isDirectory()) {
|
|
1356
1498
|
total += getDirSize(fullPath);
|
|
1357
1499
|
} else {
|
|
1358
|
-
total +=
|
|
1500
|
+
total += fs5.statSync(fullPath).size;
|
|
1359
1501
|
}
|
|
1360
1502
|
}
|
|
1361
1503
|
} catch {
|
|
@@ -1374,19 +1516,24 @@ function formatSize(bytes) {
|
|
|
1374
1516
|
}
|
|
1375
1517
|
|
|
1376
1518
|
// src/sessions/utils.ts
|
|
1377
|
-
import
|
|
1378
|
-
import
|
|
1519
|
+
import fs6 from "fs";
|
|
1520
|
+
import path6 from "path";
|
|
1379
1521
|
function formatTimestamp(ms) {
|
|
1380
1522
|
const d = new Date(ms);
|
|
1381
1523
|
const pad = (n) => String(n).padStart(2, "0");
|
|
1382
1524
|
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
1383
1525
|
}
|
|
1384
1526
|
function findProjectDir(query) {
|
|
1527
|
+
debug(`sessions: findProjectDir query="${query}"`);
|
|
1385
1528
|
const encoded = encodePath(query);
|
|
1386
|
-
if (
|
|
1529
|
+
if (fs6.existsSync(path6.join(PROJECTS_DIR, encoded))) {
|
|
1530
|
+
debug(`sessions: findProjectDir exact match ${encoded}`);
|
|
1531
|
+
return encoded;
|
|
1532
|
+
}
|
|
1387
1533
|
try {
|
|
1388
|
-
const dirs =
|
|
1534
|
+
const dirs = fs6.readdirSync(PROJECTS_DIR);
|
|
1389
1535
|
const match = dirs.find((d) => d.toLowerCase().includes(query.toLowerCase()));
|
|
1536
|
+
if (match) debug(`sessions: findProjectDir partial match ${match}`);
|
|
1390
1537
|
return match || null;
|
|
1391
1538
|
} catch {
|
|
1392
1539
|
return null;
|
|
@@ -1397,7 +1544,7 @@ function parseSessionMeta(filePath) {
|
|
|
1397
1544
|
let slug = "";
|
|
1398
1545
|
let customTitle = "";
|
|
1399
1546
|
try {
|
|
1400
|
-
const lines =
|
|
1547
|
+
const lines = fs6.readFileSync(filePath, "utf-8").split("\n");
|
|
1401
1548
|
for (const line of lines) {
|
|
1402
1549
|
if (!line.trim()) continue;
|
|
1403
1550
|
try {
|
|
@@ -1452,36 +1599,37 @@ function snippet(text, query, width = 150) {
|
|
|
1452
1599
|
|
|
1453
1600
|
// src/sessions/commands.ts
|
|
1454
1601
|
import { Command as Command4 } from "commander";
|
|
1455
|
-
import
|
|
1456
|
-
import
|
|
1602
|
+
import fs7 from "fs";
|
|
1603
|
+
import path7 from "path";
|
|
1457
1604
|
function sessionCommand() {
|
|
1458
1605
|
const session = new Command4("session").description("Manage Claude Code sessions");
|
|
1459
1606
|
const desktopApp2 = createDesktopApp();
|
|
1460
1607
|
const DESKTOP_SESSIONS_DIR2 = desktopApp2.getSessionsDir() || "";
|
|
1461
|
-
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) => {
|
|
1608
|
+
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(safeAction((opts) => {
|
|
1609
|
+
debug(`session list: reading projects from ${PROJECTS_DIR}, limit=${opts.limit}`);
|
|
1462
1610
|
const limit = parseInt(opts.limit, 10);
|
|
1463
1611
|
let dirs;
|
|
1464
1612
|
try {
|
|
1465
|
-
dirs =
|
|
1613
|
+
dirs = fs7.readdirSync(PROJECTS_DIR);
|
|
1466
1614
|
} catch {
|
|
1467
1615
|
console.log("No projects directory found.");
|
|
1468
1616
|
return;
|
|
1469
1617
|
}
|
|
1470
1618
|
dirs.sort((a, b) => {
|
|
1471
|
-
const statA =
|
|
1472
|
-
const statB =
|
|
1619
|
+
const statA = fs7.statSync(path7.join(PROJECTS_DIR, a));
|
|
1620
|
+
const statB = fs7.statSync(path7.join(PROJECTS_DIR, b));
|
|
1473
1621
|
return statB.mtimeMs - statA.mtimeMs;
|
|
1474
1622
|
});
|
|
1475
1623
|
let count = 0;
|
|
1476
1624
|
for (const projDir of dirs) {
|
|
1477
1625
|
if (count >= limit) break;
|
|
1478
|
-
const fullPath =
|
|
1626
|
+
const fullPath = path7.join(PROJECTS_DIR, projDir);
|
|
1479
1627
|
let nSessions = 0;
|
|
1480
1628
|
try {
|
|
1481
|
-
nSessions =
|
|
1629
|
+
nSessions = fs7.readdirSync(fullPath).filter((f) => f.endsWith(".jsonl")).length;
|
|
1482
1630
|
} catch {
|
|
1483
1631
|
}
|
|
1484
|
-
const stat =
|
|
1632
|
+
const stat = fs7.statSync(fullPath);
|
|
1485
1633
|
const decoded = decodePath(projDir);
|
|
1486
1634
|
if (opts.json) {
|
|
1487
1635
|
console.log(JSON.stringify({ project: decoded, sessions: nSessions, modified: Math.floor(stat.mtimeMs) }));
|
|
@@ -1492,14 +1640,14 @@ function sessionCommand() {
|
|
|
1492
1640
|
}
|
|
1493
1641
|
count++;
|
|
1494
1642
|
}
|
|
1495
|
-
});
|
|
1496
|
-
session.command("show").description("Show session files for a project").argument("<project>", "Project path or encoded name (partial match ok)").option("-v, --verbose", "Show first user message of each session").action((project, opts) => {
|
|
1643
|
+
}));
|
|
1644
|
+
session.command("show").description("Show session files for a project").argument("<project>", "Project path or encoded name (partial match ok)").option("-v, --verbose", "Show first user message of each session").action(safeAction((project, opts) => {
|
|
1645
|
+
debug(`session show: project=${project} verbose=${!!opts.verbose}`);
|
|
1497
1646
|
const projDir = findProjectDir(project);
|
|
1498
1647
|
if (!projDir) {
|
|
1499
|
-
|
|
1500
|
-
process.exit(1);
|
|
1648
|
+
throw new Error(`No project matched: ${project}`);
|
|
1501
1649
|
}
|
|
1502
|
-
const fullPath =
|
|
1650
|
+
const fullPath = path7.join(PROJECTS_DIR, projDir);
|
|
1503
1651
|
console.log(`Project: ${decodePath(projDir)}`);
|
|
1504
1652
|
console.log(`Dir: ${fullPath}`);
|
|
1505
1653
|
console.log("");
|
|
@@ -1508,16 +1656,16 @@ function sessionCommand() {
|
|
|
1508
1656
|
console.log(fmt("----------", "----", "-------", "--------"));
|
|
1509
1657
|
let files;
|
|
1510
1658
|
try {
|
|
1511
|
-
files =
|
|
1659
|
+
files = fs7.readdirSync(fullPath).filter((f) => f.endsWith(".jsonl"));
|
|
1512
1660
|
} catch {
|
|
1513
1661
|
return;
|
|
1514
1662
|
}
|
|
1515
1663
|
for (const file of files) {
|
|
1516
|
-
const filePath =
|
|
1664
|
+
const filePath = path7.join(fullPath, file);
|
|
1517
1665
|
const sessionId = file.replace(/\.jsonl$/, "");
|
|
1518
1666
|
let msgCount = 0;
|
|
1519
1667
|
try {
|
|
1520
|
-
const content =
|
|
1668
|
+
const content = fs7.readFileSync(filePath, "utf-8");
|
|
1521
1669
|
msgCount = content ? content.split("\n").filter((l) => l.trim()).length : 0;
|
|
1522
1670
|
} catch {
|
|
1523
1671
|
}
|
|
@@ -1525,7 +1673,7 @@ function sessionCommand() {
|
|
|
1525
1673
|
console.log(fmt(sessionId, slug || "-", started, String(msgCount)));
|
|
1526
1674
|
if (opts.verbose) {
|
|
1527
1675
|
try {
|
|
1528
|
-
const lines =
|
|
1676
|
+
const lines = fs7.readFileSync(filePath, "utf-8").split("\n");
|
|
1529
1677
|
for (const line of lines) {
|
|
1530
1678
|
if (!line.trim()) continue;
|
|
1531
1679
|
try {
|
|
@@ -1551,8 +1699,9 @@ function sessionCommand() {
|
|
|
1551
1699
|
}
|
|
1552
1700
|
}
|
|
1553
1701
|
}
|
|
1554
|
-
});
|
|
1555
|
-
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) => {
|
|
1702
|
+
}));
|
|
1703
|
+
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(safeAction((query, opts) => {
|
|
1704
|
+
debug(`session search: query="${query}" project=${opts.project || "(all)"} limit=${opts.limit} ignoreCase=${!!opts.ignoreCase}`);
|
|
1556
1705
|
let searchRoots = [{ root: PROJECTS_DIR, label: "" }];
|
|
1557
1706
|
if (isDesktopAppInstalled()) {
|
|
1558
1707
|
searchRoots.push({ root: DESKTOP_SESSIONS_DIR2, label: "[desktop] " });
|
|
@@ -1560,10 +1709,9 @@ function sessionCommand() {
|
|
|
1560
1709
|
if (opts.project) {
|
|
1561
1710
|
const projDir = findProjectDir(opts.project);
|
|
1562
1711
|
if (!projDir) {
|
|
1563
|
-
|
|
1564
|
-
process.exit(1);
|
|
1712
|
+
throw new Error(`No project matched: ${opts.project}`);
|
|
1565
1713
|
}
|
|
1566
|
-
searchRoots = [{ root:
|
|
1714
|
+
searchRoots = [{ root: path7.join(PROJECTS_DIR, projDir), label: "" }];
|
|
1567
1715
|
}
|
|
1568
1716
|
const limit = parseInt(opts.limit, 10);
|
|
1569
1717
|
let count = 0;
|
|
@@ -1571,18 +1719,18 @@ function sessionCommand() {
|
|
|
1571
1719
|
if (count >= limit) return;
|
|
1572
1720
|
let entries;
|
|
1573
1721
|
try {
|
|
1574
|
-
entries =
|
|
1722
|
+
entries = fs7.readdirSync(dir, { withFileTypes: true });
|
|
1575
1723
|
} catch {
|
|
1576
1724
|
return;
|
|
1577
1725
|
}
|
|
1578
1726
|
for (const entry of entries) {
|
|
1579
1727
|
if (count >= limit) break;
|
|
1580
|
-
const fullPath =
|
|
1728
|
+
const fullPath = path7.join(dir, entry.name);
|
|
1581
1729
|
if (entry.isDirectory()) {
|
|
1582
1730
|
searchDir(fullPath, label, baseDir);
|
|
1583
1731
|
} else if (entry.name.endsWith(".jsonl")) {
|
|
1584
1732
|
try {
|
|
1585
|
-
const content =
|
|
1733
|
+
const content = fs7.readFileSync(fullPath, "utf-8");
|
|
1586
1734
|
const lines = content.split("\n");
|
|
1587
1735
|
let found = false;
|
|
1588
1736
|
for (let lineno = 0; lineno < lines.length; lineno++) {
|
|
@@ -1591,9 +1739,9 @@ function sessionCommand() {
|
|
|
1591
1739
|
const match = opts.ignoreCase ? line.toLowerCase().includes(query.toLowerCase()) : line.includes(query);
|
|
1592
1740
|
if (match) {
|
|
1593
1741
|
if (!found) {
|
|
1594
|
-
const relPath =
|
|
1595
|
-
const projEnc = relPath.split(
|
|
1596
|
-
const sessionId =
|
|
1742
|
+
const relPath = path7.relative(baseDir, fullPath);
|
|
1743
|
+
const projEnc = relPath.split(path7.sep)[0];
|
|
1744
|
+
const sessionId = path7.basename(fullPath, ".jsonl");
|
|
1597
1745
|
const projName = label ? projEnc : decodePath(projEnc);
|
|
1598
1746
|
console.log(`${label}[${projName} \u2192 ${sessionId}]`);
|
|
1599
1747
|
found = true;
|
|
@@ -1626,11 +1774,12 @@ function sessionCommand() {
|
|
|
1626
1774
|
for (const { root, label } of searchRoots) {
|
|
1627
1775
|
searchDir(root, label, root);
|
|
1628
1776
|
}
|
|
1629
|
-
});
|
|
1630
|
-
session.command("ps").description("Show active Claude Code processes").action(() => {
|
|
1777
|
+
}));
|
|
1778
|
+
session.command("ps").description("Show active Claude Code processes").action(safeAction(() => {
|
|
1779
|
+
debug(`session ps: reading sessions from ${SESSIONS_DIR}`);
|
|
1631
1780
|
let files;
|
|
1632
1781
|
try {
|
|
1633
|
-
files =
|
|
1782
|
+
files = fs7.readdirSync(SESSIONS_DIR).filter((f) => f.endsWith(".json"));
|
|
1634
1783
|
} catch {
|
|
1635
1784
|
console.log("(no session files found)");
|
|
1636
1785
|
return;
|
|
@@ -1644,7 +1793,7 @@ function sessionCommand() {
|
|
|
1644
1793
|
console.log(fmt("---", "----------", "-------", "---", ""));
|
|
1645
1794
|
for (const file of files) {
|
|
1646
1795
|
try {
|
|
1647
|
-
const data = JSON.parse(
|
|
1796
|
+
const data = JSON.parse(fs7.readFileSync(path7.join(SESSIONS_DIR, file), "utf-8"));
|
|
1648
1797
|
const pid = String(data.pid || "?");
|
|
1649
1798
|
const sessionId = data.sessionId || "?";
|
|
1650
1799
|
const cwd = data.cwd || "?";
|
|
@@ -1659,8 +1808,9 @@ function sessionCommand() {
|
|
|
1659
1808
|
} catch {
|
|
1660
1809
|
}
|
|
1661
1810
|
}
|
|
1662
|
-
});
|
|
1663
|
-
session.command("stats").description("Show summary statistics across all Claude Code sessions").action(() => {
|
|
1811
|
+
}));
|
|
1812
|
+
session.command("stats").description("Show summary statistics across all Claude Code sessions").action(safeAction(() => {
|
|
1813
|
+
debug(`session stats: scanning ${PROJECTS_DIR} and ${DESKTOP_SESSIONS_DIR2 || "(no desktop)"}`);
|
|
1664
1814
|
let nProjects = 0;
|
|
1665
1815
|
let nSessions = 0;
|
|
1666
1816
|
let totalMsgs = 0;
|
|
@@ -1670,8 +1820,8 @@ function sessionCommand() {
|
|
|
1670
1820
|
const walk = (dir) => {
|
|
1671
1821
|
const results = [];
|
|
1672
1822
|
try {
|
|
1673
|
-
for (const entry of
|
|
1674
|
-
const fullPath =
|
|
1823
|
+
for (const entry of fs7.readdirSync(dir, { withFileTypes: true })) {
|
|
1824
|
+
const fullPath = path7.join(dir, entry.name);
|
|
1675
1825
|
if (entry.isDirectory()) results.push(...walk(fullPath));
|
|
1676
1826
|
else if (entry.name.endsWith(".jsonl")) results.push(fullPath);
|
|
1677
1827
|
}
|
|
@@ -1680,7 +1830,7 @@ function sessionCommand() {
|
|
|
1680
1830
|
return results;
|
|
1681
1831
|
};
|
|
1682
1832
|
try {
|
|
1683
|
-
nProjects =
|
|
1833
|
+
nProjects = fs7.readdirSync(PROJECTS_DIR).length;
|
|
1684
1834
|
} catch {
|
|
1685
1835
|
}
|
|
1686
1836
|
try {
|
|
@@ -1688,7 +1838,7 @@ function sessionCommand() {
|
|
|
1688
1838
|
nSessions = sessionFiles.length;
|
|
1689
1839
|
for (const f of sessionFiles) {
|
|
1690
1840
|
try {
|
|
1691
|
-
const content =
|
|
1841
|
+
const content = fs7.readFileSync(f, "utf-8");
|
|
1692
1842
|
totalMsgs += content ? content.split("\n").filter((l) => l.trim()).length : 0;
|
|
1693
1843
|
} catch {
|
|
1694
1844
|
}
|
|
@@ -1701,7 +1851,7 @@ function sessionCommand() {
|
|
|
1701
1851
|
nDesktopSessions = desktopFiles.length;
|
|
1702
1852
|
for (const f of desktopFiles) {
|
|
1703
1853
|
try {
|
|
1704
|
-
const content =
|
|
1854
|
+
const content = fs7.readFileSync(f, "utf-8");
|
|
1705
1855
|
nDesktopMsgs += content ? content.split("\n").filter((l) => l.trim()).length : 0;
|
|
1706
1856
|
} catch {
|
|
1707
1857
|
}
|
|
@@ -1710,7 +1860,7 @@ function sessionCommand() {
|
|
|
1710
1860
|
}
|
|
1711
1861
|
}
|
|
1712
1862
|
try {
|
|
1713
|
-
nActive =
|
|
1863
|
+
nActive = fs7.readdirSync(SESSIONS_DIR).filter((f) => f.endsWith(".json")).length;
|
|
1714
1864
|
} catch {
|
|
1715
1865
|
}
|
|
1716
1866
|
console.log(`Projects: ${nProjects}`);
|
|
@@ -1733,8 +1883,9 @@ function sessionCommand() {
|
|
|
1733
1883
|
if (desktopSize) {
|
|
1734
1884
|
console.log(` Desktop: ${desktopSize}`);
|
|
1735
1885
|
}
|
|
1736
|
-
});
|
|
1737
|
-
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) => {
|
|
1886
|
+
}));
|
|
1887
|
+
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(safeAction((opts) => {
|
|
1888
|
+
debug(`session clean: days=${opts.days} dryRun=${!!opts.dryRun}`);
|
|
1738
1889
|
const days = parseInt(opts.days, 10);
|
|
1739
1890
|
const cutoffMs = Date.now() - days * 24 * 60 * 60 * 1e3;
|
|
1740
1891
|
let deleted = 0;
|
|
@@ -1742,23 +1893,23 @@ function sessionCommand() {
|
|
|
1742
1893
|
const walk = (dir) => {
|
|
1743
1894
|
let entries;
|
|
1744
1895
|
try {
|
|
1745
|
-
entries =
|
|
1896
|
+
entries = fs7.readdirSync(dir, { withFileTypes: true });
|
|
1746
1897
|
} catch {
|
|
1747
1898
|
return;
|
|
1748
1899
|
}
|
|
1749
1900
|
for (const entry of entries) {
|
|
1750
|
-
const fullPath =
|
|
1901
|
+
const fullPath = path7.join(dir, entry.name);
|
|
1751
1902
|
if (entry.isDirectory()) {
|
|
1752
1903
|
walk(fullPath);
|
|
1753
1904
|
} else if (entry.name.endsWith(".jsonl")) {
|
|
1754
1905
|
try {
|
|
1755
|
-
const stat =
|
|
1906
|
+
const stat = fs7.statSync(fullPath);
|
|
1756
1907
|
if (stat.mtimeMs < cutoffMs) {
|
|
1757
1908
|
const size = stat.size;
|
|
1758
1909
|
if (opts.dryRun) {
|
|
1759
1910
|
console.log(`[dry-run] would delete: ${fullPath} (${Math.floor(size / 1024)}KB)`);
|
|
1760
1911
|
} else {
|
|
1761
|
-
|
|
1912
|
+
fs7.unlinkSync(fullPath);
|
|
1762
1913
|
console.log(`Deleted: ${fullPath}`);
|
|
1763
1914
|
}
|
|
1764
1915
|
deleted++;
|
|
@@ -1776,7 +1927,7 @@ function sessionCommand() {
|
|
|
1776
1927
|
console.log("");
|
|
1777
1928
|
const verb = opts.dryRun ? "Would delete" : "Deleted";
|
|
1778
1929
|
console.log(`${verb} ${deleted} file(s) (~${Math.floor(freed / 1024)}KB freed)`);
|
|
1779
|
-
});
|
|
1930
|
+
}));
|
|
1780
1931
|
return session;
|
|
1781
1932
|
}
|
|
1782
1933
|
|
|
@@ -2090,7 +2241,7 @@ var POWERSHELL_COMPLETION = `Register-ArgumentCompleter -Native -CommandName cc-
|
|
|
2090
2241
|
|
|
2091
2242
|
// src/complete/index.ts
|
|
2092
2243
|
function completionCommand() {
|
|
2093
|
-
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) => {
|
|
2244
|
+
return new Command5("completion").description("Print shell completion script").addArgument(new Argument("<shell>", "Shell type: bash, zsh, or powershell").choices(["bash", "zsh", "powershell"])).action(safeAction((shell) => {
|
|
2094
2245
|
switch (shell) {
|
|
2095
2246
|
case "zsh":
|
|
2096
2247
|
process.stdout.write(ZSH_COMPLETION);
|
|
@@ -2105,12 +2256,16 @@ function completionCommand() {
|
|
|
2105
2256
|
console.error(`Unsupported shell: ${shell}. Use 'bash', 'zsh', or 'powershell'.`);
|
|
2106
2257
|
process.exit(1);
|
|
2107
2258
|
}
|
|
2108
|
-
});
|
|
2259
|
+
}));
|
|
2109
2260
|
}
|
|
2110
2261
|
|
|
2111
2262
|
// src/index.ts
|
|
2112
2263
|
var _require = createRequire(import.meta.url);
|
|
2113
2264
|
var { version } = _require("../package.json");
|
|
2265
|
+
ensureSettingsFile();
|
|
2266
|
+
var settings = readJson(SETTINGS_FILE);
|
|
2267
|
+
setLogLevel(settings._cc_hub_logLevel || "INFO");
|
|
2268
|
+
installGlobalExceptionHandlers();
|
|
2114
2269
|
var program = new Command6();
|
|
2115
2270
|
program.name("cc-hub").description("Manage Claude CLI profiles, hooks, and sessions").version(version);
|
|
2116
2271
|
program.addCommand(profileCommand());
|
|
@@ -2120,4 +2275,9 @@ program.addCommand(hooksCommand());
|
|
|
2120
2275
|
program.addCommand(sessionCommand());
|
|
2121
2276
|
program.addCommand(completionCommand());
|
|
2122
2277
|
program.addCommand(providerCommand());
|
|
2123
|
-
|
|
2278
|
+
try {
|
|
2279
|
+
program.parse();
|
|
2280
|
+
} catch (err) {
|
|
2281
|
+
console.error("Unexpected error:", err instanceof Error ? err.message : String(err));
|
|
2282
|
+
process.exit(1);
|
|
2283
|
+
}
|