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