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.
- package/README.md +10 -4
- package/dist/index.js +622 -337
- 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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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 (!
|
|
55
|
-
|
|
56
|
-
|
|
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(
|
|
323
|
+
return JSON.parse(fs3.readFileSync(filePath, "utf-8"));
|
|
61
324
|
}
|
|
62
325
|
function writeJson(filePath, data) {
|
|
63
|
-
|
|
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 (!
|
|
73
|
-
const backupPath =
|
|
74
|
-
const raw =
|
|
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
|
-
|
|
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
|
-
|
|
103
|
-
console.error(`Fixed invalid JSON in ${
|
|
365
|
+
fs3.writeFileSync(filePath, text + "\n", "utf-8");
|
|
366
|
+
console.error(`Fixed invalid JSON in ${path3.basename(filePath)}.`);
|
|
104
367
|
} catch {
|
|
105
|
-
if (
|
|
106
|
-
|
|
107
|
-
console.error(`Restored ${
|
|
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 ${
|
|
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/
|
|
116
|
-
import
|
|
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
|
-
|
|
464
|
-
|
|
465
|
-
|
|
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
|
|
502
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (!
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1317
|
+
return createPathCodec().encode(p);
|
|
1142
1318
|
}
|
|
1143
1319
|
function decodePath(encoded) {
|
|
1144
|
-
return
|
|
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 (
|
|
1362
|
+
if (fs5.existsSync(path5.join(PROJECTS_DIR, encoded))) return encoded;
|
|
1154
1363
|
try {
|
|
1155
|
-
const dirs =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1232
|
-
const statB =
|
|
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 =
|
|
1454
|
+
const fullPath = path6.join(PROJECTS_DIR, projDir);
|
|
1239
1455
|
let nSessions = 0;
|
|
1240
1456
|
try {
|
|
1241
|
-
nSessions =
|
|
1457
|
+
nSessions = fs6.readdirSync(fullPath).filter((f) => f.endsWith(".jsonl")).length;
|
|
1242
1458
|
} catch {
|
|
1243
1459
|
}
|
|
1244
|
-
const stat =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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:
|
|
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:
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1355
|
-
const projEnc = relPath.split(
|
|
1356
|
-
const sessionId =
|
|
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 =
|
|
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(
|
|
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
|
|
1434
|
-
const fullPath =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
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
|
-
'
|
|
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
|
|
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
|
|
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
|
-
|
|
1787
|
-
|
|
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 '
|
|
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(
|
|
2097
|
+
program.addCommand(completionCommand());
|
|
1813
2098
|
program.addCommand(providerCommand());
|
|
1814
2099
|
program.parse();
|