cc-hub-cli 1.0.10 → 1.0.11
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 +2 -0
- package/dist/index.js +273 -69
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/index.js
CHANGED
|
@@ -18,6 +18,38 @@ var SETTINGS_FILE = process.env.CLAUDE_SETTINGS_FILE || path.join(CLAUDE_DIR, "s
|
|
|
18
18
|
var CLAUDE_JSON = path.join(os.homedir(), ".claude.json");
|
|
19
19
|
var PROJECTS_DIR = path.join(CLAUDE_DIR, "projects");
|
|
20
20
|
var SESSIONS_DIR = path.join(CLAUDE_DIR, "sessions");
|
|
21
|
+
var DESKTOP_SUPPORT_DIR = path.join(os.homedir(), "Library/Application Support/Claude-3p");
|
|
22
|
+
var DESKTOP_CONFIG_LIBRARY = path.join(DESKTOP_SUPPORT_DIR, "configLibrary");
|
|
23
|
+
var DESKTOP_META_FILE = path.join(DESKTOP_CONFIG_LIBRARY, "_meta.json");
|
|
24
|
+
var DESKTOP_SESSIONS_DIR = path.join(DESKTOP_SUPPORT_DIR, "local-agent-mode-sessions");
|
|
25
|
+
function isDesktopAppInstalled() {
|
|
26
|
+
return fs.existsSync(DESKTOP_SUPPORT_DIR);
|
|
27
|
+
}
|
|
28
|
+
function findDesktopClaudeBinary() {
|
|
29
|
+
const claudeCodeDir = path.join(DESKTOP_SUPPORT_DIR, "claude-code");
|
|
30
|
+
if (!fs.existsSync(claudeCodeDir)) return void 0;
|
|
31
|
+
let versions;
|
|
32
|
+
try {
|
|
33
|
+
versions = fs.readdirSync(claudeCodeDir).filter(
|
|
34
|
+
(d) => fs.existsSync(path.join(claudeCodeDir, d, "claude.app", "Contents", "MacOS", "claude"))
|
|
35
|
+
);
|
|
36
|
+
} catch {
|
|
37
|
+
return void 0;
|
|
38
|
+
}
|
|
39
|
+
if (versions.length === 0) return void 0;
|
|
40
|
+
versions.sort((a, b) => {
|
|
41
|
+
const parse = (v) => v.split(".").map((n) => parseInt(n, 10));
|
|
42
|
+
const av = parse(a);
|
|
43
|
+
const bv = parse(b);
|
|
44
|
+
for (let i = 0; i < Math.max(av.length, bv.length); i++) {
|
|
45
|
+
const an = av[i] || 0;
|
|
46
|
+
const bn = bv[i] || 0;
|
|
47
|
+
if (an !== bn) return bn - an;
|
|
48
|
+
}
|
|
49
|
+
return 0;
|
|
50
|
+
});
|
|
51
|
+
return path.join(claudeCodeDir, versions[0], "claude.app", "Contents", "MacOS", "claude");
|
|
52
|
+
}
|
|
21
53
|
function ensureFile(filePath, defaultContent) {
|
|
22
54
|
if (!fs.existsSync(filePath)) {
|
|
23
55
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
@@ -428,6 +460,9 @@ function providerCommand() {
|
|
|
428
460
|
}
|
|
429
461
|
|
|
430
462
|
// src/profiles.ts
|
|
463
|
+
import { randomUUID } from "crypto";
|
|
464
|
+
import fs2 from "fs";
|
|
465
|
+
import path2 from "path";
|
|
431
466
|
function maskToken(token) {
|
|
432
467
|
if (!token) return "(unset)";
|
|
433
468
|
if (token.length <= 12) return token;
|
|
@@ -463,21 +498,114 @@ function isAnthropicModel(model) {
|
|
|
463
498
|
if (lower.startsWith("claude-")) return true;
|
|
464
499
|
return false;
|
|
465
500
|
}
|
|
501
|
+
function toDesktopProfile(p) {
|
|
502
|
+
const models = p.models || (p.model ? [p.model] : []);
|
|
503
|
+
const isAnthropic = p.provider === "anthropic" || !p.provider && !p.url;
|
|
504
|
+
if (isAnthropic && !p.url) {
|
|
505
|
+
return {
|
|
506
|
+
inferenceProvider: "1p",
|
|
507
|
+
inferenceModels: models.map((m) => ({ name: m, supports1m: true }))
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
return {
|
|
511
|
+
inferenceProvider: "gateway",
|
|
512
|
+
inferenceGatewayBaseUrl: p.url || void 0,
|
|
513
|
+
inferenceGatewayApiKey: p.token || void 0,
|
|
514
|
+
inferenceGatewayAuthScheme: "bearer",
|
|
515
|
+
inferenceModels: models.map((m) => ({ name: m, supports1m: true }))
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
function readDesktopMeta() {
|
|
519
|
+
if (!fs2.existsSync(DESKTOP_META_FILE)) return {};
|
|
520
|
+
try {
|
|
521
|
+
return readJson(DESKTOP_META_FILE);
|
|
522
|
+
} catch {
|
|
523
|
+
return {};
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
function writeDesktopMeta(meta) {
|
|
527
|
+
writeJson(DESKTOP_META_FILE, meta);
|
|
528
|
+
}
|
|
529
|
+
function writeDesktopProfile(id, data) {
|
|
530
|
+
const filePath = path2.join(DESKTOP_CONFIG_LIBRARY, `${id}.json`);
|
|
531
|
+
writeJson(filePath, data);
|
|
532
|
+
}
|
|
533
|
+
function removeDesktopProfile(id) {
|
|
534
|
+
const filePath = path2.join(DESKTOP_CONFIG_LIBRARY, `${id}.json`);
|
|
535
|
+
if (fs2.existsSync(filePath)) {
|
|
536
|
+
fs2.unlinkSync(filePath);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
function syncProfileToDesktop(name, p) {
|
|
540
|
+
if (!isDesktopAppInstalled()) return;
|
|
541
|
+
if (!fs2.existsSync(DESKTOP_CONFIG_LIBRARY)) {
|
|
542
|
+
fs2.mkdirSync(DESKTOP_CONFIG_LIBRARY, { recursive: true });
|
|
543
|
+
}
|
|
544
|
+
const meta = readDesktopMeta();
|
|
545
|
+
const entries = meta.entries || [];
|
|
546
|
+
let id = p.desktopId;
|
|
547
|
+
if (!id) {
|
|
548
|
+
const existingByName = entries.find((e) => e.name === name);
|
|
549
|
+
if (existingByName) {
|
|
550
|
+
id = existingByName.id;
|
|
551
|
+
} else {
|
|
552
|
+
id = randomUUID();
|
|
553
|
+
}
|
|
554
|
+
p.desktopId = id;
|
|
555
|
+
}
|
|
556
|
+
const existingIndex = entries.findIndex((e) => e.id === id);
|
|
557
|
+
if (existingIndex !== -1) {
|
|
558
|
+
entries[existingIndex].name = name;
|
|
559
|
+
} else {
|
|
560
|
+
entries.push({ id, name });
|
|
561
|
+
}
|
|
562
|
+
meta.entries = entries;
|
|
563
|
+
writeDesktopMeta(meta);
|
|
564
|
+
writeDesktopProfile(id, toDesktopProfile(p));
|
|
565
|
+
}
|
|
566
|
+
function removeProfileFromDesktop(name, p) {
|
|
567
|
+
if (!isDesktopAppInstalled() || !p.desktopId) return;
|
|
568
|
+
const meta = readDesktopMeta();
|
|
569
|
+
if (meta.entries) {
|
|
570
|
+
meta.entries = meta.entries.filter((e) => e.id !== p.desktopId);
|
|
571
|
+
}
|
|
572
|
+
if (meta.appliedId === p.desktopId) {
|
|
573
|
+
delete meta.appliedId;
|
|
574
|
+
}
|
|
575
|
+
writeDesktopMeta(meta);
|
|
576
|
+
removeDesktopProfile(p.desktopId);
|
|
577
|
+
}
|
|
578
|
+
function setDesktopActiveProfile(p) {
|
|
579
|
+
if (!isDesktopAppInstalled() || !p.desktopId) return;
|
|
580
|
+
const meta = readDesktopMeta();
|
|
581
|
+
meta.appliedId = p.desktopId;
|
|
582
|
+
const entries = meta.entries || [];
|
|
583
|
+
if (!entries.some((e) => e.id === p.desktopId)) {
|
|
584
|
+
entries.push({ id: p.desktopId, name: "unknown" });
|
|
585
|
+
meta.entries = entries;
|
|
586
|
+
}
|
|
587
|
+
writeDesktopMeta(meta);
|
|
588
|
+
}
|
|
589
|
+
function resolveClaudeBinary() {
|
|
590
|
+
try {
|
|
591
|
+
const result = spawnSync("which", ["claude"], { encoding: "utf-8" });
|
|
592
|
+
if (result.status === 0 && result.stdout.trim()) {
|
|
593
|
+
return "claude";
|
|
594
|
+
}
|
|
595
|
+
} catch {
|
|
596
|
+
}
|
|
597
|
+
const desktopBinary = findDesktopClaudeBinary();
|
|
598
|
+
if (desktopBinary) return desktopBinary;
|
|
599
|
+
console.error("Error: Could not find Claude Code CLI.");
|
|
600
|
+
console.error("Install it globally or install the Claude Code desktop app.");
|
|
601
|
+
process.exit(1);
|
|
602
|
+
}
|
|
466
603
|
function updateSettingsForProfile(p) {
|
|
467
604
|
ensureSettingsFile();
|
|
468
605
|
const settings = readJson(SETTINGS_FILE);
|
|
469
606
|
const models = p.models || (p.model ? [p.model] : []);
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
if (models[0]) aliases.push("sonnet");
|
|
473
|
-
if (models[1]) aliases.push("opus");
|
|
474
|
-
if (models[2]) aliases.push("haiku");
|
|
475
|
-
settings.model = aliases[0];
|
|
476
|
-
settings.availableModels = aliases;
|
|
477
|
-
} else {
|
|
478
|
-
delete settings.model;
|
|
479
|
-
delete settings.availableModels;
|
|
480
|
-
}
|
|
607
|
+
delete settings.model;
|
|
608
|
+
delete settings.availableModels;
|
|
481
609
|
const envVarsToClean = [
|
|
482
610
|
"ANTHROPIC_DEFAULT_OPUS_MODEL",
|
|
483
611
|
"ANTHROPIC_DEFAULT_OPUS_MODEL_NAME",
|
|
@@ -499,11 +627,15 @@ function updateSettingsForProfile(p) {
|
|
|
499
627
|
}
|
|
500
628
|
function profileCommand() {
|
|
501
629
|
const profile = new Command2("profile").description("Manage Claude CLI profiles");
|
|
502
|
-
profile.command("add").description("Add or update a profile").argument("<name>", "Profile name").option("-m, --model <model>", "Model ID (e.g. claude-opus-4-6) - can be used multiple times", collect, []).option("-t, --token <token>", "API key / token").option("-u, --url <url>", "Base URL (e.g. https://api.anthropic.com)").option("-p, --provider <provider>", "Provider type: anthropic (default) or openai").action((name, opts) => {
|
|
630
|
+
profile.command("add").description("Add or update a profile").argument("<name>", "Profile name").option("-m, --model <model>", "Model ID (e.g. claude-opus-4-6) - can be used multiple times (max 3)", collect, []).option("-t, --token <token>", "API key / token").option("-u, --url <url>", "Base URL (e.g. https://api.anthropic.com)").option("-p, --provider <provider>", "Provider type: anthropic (default) or openai").action((name, opts) => {
|
|
631
|
+
const models = opts.model && opts.model.length > 0 ? opts.model : void 0;
|
|
632
|
+
if (models && models.length > 3) {
|
|
633
|
+
console.error("Error: A profile can have at most 3 models.");
|
|
634
|
+
process.exit(1);
|
|
635
|
+
}
|
|
503
636
|
ensureProfilesFile();
|
|
504
637
|
const data = readJson(PROFILES_FILE);
|
|
505
638
|
const profile2 = data.profiles[name] || {};
|
|
506
|
-
const models = opts.model && opts.model.length > 0 ? opts.model : void 0;
|
|
507
639
|
if (models) {
|
|
508
640
|
profile2.models = models;
|
|
509
641
|
profile2.model = models[0];
|
|
@@ -512,10 +644,11 @@ function profileCommand() {
|
|
|
512
644
|
if (opts.url) profile2.url = opts.url;
|
|
513
645
|
if (opts.provider) profile2.provider = opts.provider;
|
|
514
646
|
data.profiles[name] = profile2;
|
|
647
|
+
syncProfileToDesktop(name, profile2);
|
|
515
648
|
writeJson(PROFILES_FILE, data);
|
|
516
649
|
console.log(`Profile '${name}' saved.`);
|
|
517
650
|
});
|
|
518
|
-
profile.command("update").description("Update fields of an existing profile").argument("<name>", "Profile name (must already exist)").option("-m, --model <model>", "Model ID - can be used multiple times to set multiple models", collect, []).option("-d, --delete-model <model>", "Remove model ID - can be used multiple times", collect, []).option("-t, --token <token>", "API key / token").option("-u, --url <url>", "Base URL").option("-p, --provider <provider>", "Provider type: anthropic (default) or openai").action((name, opts) => {
|
|
651
|
+
profile.command("update").description("Update fields of an existing profile").argument("<name>", "Profile name (must already exist)").option("-m, --model <model>", "Model ID - can be used multiple times to set multiple models (max 3)", collect, []).option("-d, --delete-model <model>", "Remove model ID - can be used multiple times", collect, []).option("-t, --token <token>", "API key / token").option("-u, --url <url>", "Base URL").option("-p, --provider <provider>", "Provider type: anthropic (default) or openai").action((name, opts) => {
|
|
519
652
|
ensureProfilesFile();
|
|
520
653
|
const data = readJson(PROFILES_FILE);
|
|
521
654
|
if (!data.profiles[name]) {
|
|
@@ -564,9 +697,15 @@ function profileCommand() {
|
|
|
564
697
|
p.model = providedModels[0];
|
|
565
698
|
}
|
|
566
699
|
}
|
|
700
|
+
const finalModels = p.models || (p.model ? [p.model] : []);
|
|
701
|
+
if (finalModels.length > 3) {
|
|
702
|
+
console.error("Error: A profile can have at most 3 models.");
|
|
703
|
+
process.exit(1);
|
|
704
|
+
}
|
|
567
705
|
if (opts.token) p.token = opts.token;
|
|
568
706
|
if (opts.url) p.url = opts.url;
|
|
569
707
|
if (opts.provider) p.provider = opts.provider;
|
|
708
|
+
syncProfileToDesktop(name, p);
|
|
570
709
|
writeJson(PROFILES_FILE, data);
|
|
571
710
|
console.log(`Profile '${name}' updated.`);
|
|
572
711
|
});
|
|
@@ -586,9 +725,11 @@ function profileCommand() {
|
|
|
586
725
|
for (const name of names) {
|
|
587
726
|
const p = profiles[name];
|
|
588
727
|
const marker = name === def ? "* " : " ";
|
|
728
|
+
const desktopMarker = p.desktopId ? " [desktop]" : "";
|
|
729
|
+
const displayName = (name + desktopMarker).padEnd(20);
|
|
589
730
|
console.log(fmt(
|
|
590
731
|
marker,
|
|
591
|
-
|
|
732
|
+
displayName,
|
|
592
733
|
formatModels(p),
|
|
593
734
|
maskToken(p.token || ""),
|
|
594
735
|
p.provider || "anthropic",
|
|
@@ -605,7 +746,8 @@ function profileCommand() {
|
|
|
605
746
|
process.exit(1);
|
|
606
747
|
}
|
|
607
748
|
if (opts.json) {
|
|
608
|
-
|
|
749
|
+
const { desktopId, ...rest } = p;
|
|
750
|
+
console.log(JSON.stringify({ name, ...rest }, null, 2));
|
|
609
751
|
} else {
|
|
610
752
|
console.log(`Name: ${name}`);
|
|
611
753
|
console.log(`Model: ${p.model || "(unset)"}`);
|
|
@@ -637,6 +779,7 @@ function profileCommand() {
|
|
|
637
779
|
console.error(`Profile '${name}' not found.`);
|
|
638
780
|
process.exit(1);
|
|
639
781
|
}
|
|
782
|
+
removeProfileFromDesktop(name, data.profiles[name]);
|
|
640
783
|
delete data.profiles[name];
|
|
641
784
|
writeJson(PROFILES_FILE, data);
|
|
642
785
|
console.log(`Profile '${name}' removed.`);
|
|
@@ -668,9 +811,32 @@ function profileCommand() {
|
|
|
668
811
|
process.exit(1);
|
|
669
812
|
}
|
|
670
813
|
data.default = name;
|
|
814
|
+
setDesktopActiveProfile(data.profiles[name]);
|
|
671
815
|
writeJson(PROFILES_FILE, data);
|
|
672
816
|
console.log(`Default profile set to '${name}'.`);
|
|
673
817
|
});
|
|
818
|
+
profile.command("sync").description("Synchronize all CLI profiles to the Claude desktop app").action(() => {
|
|
819
|
+
if (!isDesktopAppInstalled()) {
|
|
820
|
+
console.error("Claude desktop app is not installed.");
|
|
821
|
+
process.exit(1);
|
|
822
|
+
}
|
|
823
|
+
ensureProfilesFile();
|
|
824
|
+
const data = readJson(PROFILES_FILE);
|
|
825
|
+
const names = Object.keys(data.profiles);
|
|
826
|
+
if (names.length === 0) {
|
|
827
|
+
console.log("No profiles to sync.");
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
if (!fs2.existsSync(DESKTOP_CONFIG_LIBRARY)) {
|
|
831
|
+
fs2.mkdirSync(DESKTOP_CONFIG_LIBRARY, { recursive: true });
|
|
832
|
+
}
|
|
833
|
+
for (const name of names) {
|
|
834
|
+
const p = data.profiles[name];
|
|
835
|
+
syncProfileToDesktop(name, p);
|
|
836
|
+
}
|
|
837
|
+
writeJson(PROFILES_FILE, data);
|
|
838
|
+
console.log(`Synced ${names.length} profile(s) to the desktop app.`);
|
|
839
|
+
});
|
|
674
840
|
return profile;
|
|
675
841
|
}
|
|
676
842
|
function collect(value, previous) {
|
|
@@ -680,7 +846,8 @@ function execClaude(profileName, p, extraArgs) {
|
|
|
680
846
|
updateSettingsForProfile(p);
|
|
681
847
|
const models = p.models || (p.model ? [p.model] : []);
|
|
682
848
|
const firstModel = models[0];
|
|
683
|
-
const
|
|
849
|
+
const binary = resolveClaudeBinary();
|
|
850
|
+
const cmd = [binary];
|
|
684
851
|
if (firstModel) cmd.push("--model", firstModel);
|
|
685
852
|
cmd.push(...extraArgs);
|
|
686
853
|
const env = {
|
|
@@ -745,6 +912,7 @@ function useCommand() {
|
|
|
745
912
|
process.exit(1);
|
|
746
913
|
}
|
|
747
914
|
data.default = name;
|
|
915
|
+
setDesktopActiveProfile(data.profiles[name]);
|
|
748
916
|
writeJson(PROFILES_FILE, data);
|
|
749
917
|
console.log(`Default profile set to '${name}'.`);
|
|
750
918
|
});
|
|
@@ -966,8 +1134,8 @@ function hooksCommand() {
|
|
|
966
1134
|
|
|
967
1135
|
// src/sessions.ts
|
|
968
1136
|
import { Command as Command4 } from "commander";
|
|
969
|
-
import
|
|
970
|
-
import
|
|
1137
|
+
import fs3 from "fs";
|
|
1138
|
+
import path3 from "path";
|
|
971
1139
|
import { execSync } from "child_process";
|
|
972
1140
|
function encodePath(p) {
|
|
973
1141
|
return p.replace(/\./g, "DOTMARK").replace(/\//g, "-").replace(/DOTMARK/g, "-");
|
|
@@ -982,9 +1150,9 @@ function formatTimestamp(ms) {
|
|
|
982
1150
|
}
|
|
983
1151
|
function findProjectDir(query) {
|
|
984
1152
|
const encoded = encodePath(query);
|
|
985
|
-
if (
|
|
1153
|
+
if (fs3.existsSync(path3.join(PROJECTS_DIR, encoded))) return encoded;
|
|
986
1154
|
try {
|
|
987
|
-
const dirs =
|
|
1155
|
+
const dirs = fs3.readdirSync(PROJECTS_DIR);
|
|
988
1156
|
const match = dirs.find((d) => d.toLowerCase().includes(query.toLowerCase()));
|
|
989
1157
|
return match || null;
|
|
990
1158
|
} catch {
|
|
@@ -996,7 +1164,7 @@ function parseSessionMeta(filePath) {
|
|
|
996
1164
|
let slug = "";
|
|
997
1165
|
let customTitle = "";
|
|
998
1166
|
try {
|
|
999
|
-
const lines =
|
|
1167
|
+
const lines = fs3.readFileSync(filePath, "utf-8").split("\n");
|
|
1000
1168
|
for (const line of lines) {
|
|
1001
1169
|
if (!line.trim()) continue;
|
|
1002
1170
|
try {
|
|
@@ -1054,26 +1222,26 @@ function sessionCommand() {
|
|
|
1054
1222
|
const limit = parseInt(opts.limit, 10);
|
|
1055
1223
|
let dirs;
|
|
1056
1224
|
try {
|
|
1057
|
-
dirs =
|
|
1225
|
+
dirs = fs3.readdirSync(PROJECTS_DIR);
|
|
1058
1226
|
} catch {
|
|
1059
1227
|
console.log("No projects directory found.");
|
|
1060
1228
|
return;
|
|
1061
1229
|
}
|
|
1062
1230
|
dirs.sort((a, b) => {
|
|
1063
|
-
const statA =
|
|
1064
|
-
const statB =
|
|
1231
|
+
const statA = fs3.statSync(path3.join(PROJECTS_DIR, a));
|
|
1232
|
+
const statB = fs3.statSync(path3.join(PROJECTS_DIR, b));
|
|
1065
1233
|
return statB.mtimeMs - statA.mtimeMs;
|
|
1066
1234
|
});
|
|
1067
1235
|
let count = 0;
|
|
1068
1236
|
for (const projDir of dirs) {
|
|
1069
1237
|
if (count >= limit) break;
|
|
1070
|
-
const fullPath =
|
|
1238
|
+
const fullPath = path3.join(PROJECTS_DIR, projDir);
|
|
1071
1239
|
let nSessions = 0;
|
|
1072
1240
|
try {
|
|
1073
|
-
nSessions =
|
|
1241
|
+
nSessions = fs3.readdirSync(fullPath).filter((f) => f.endsWith(".jsonl")).length;
|
|
1074
1242
|
} catch {
|
|
1075
1243
|
}
|
|
1076
|
-
const stat =
|
|
1244
|
+
const stat = fs3.statSync(fullPath);
|
|
1077
1245
|
const decoded = decodePath(projDir);
|
|
1078
1246
|
if (opts.json) {
|
|
1079
1247
|
console.log(JSON.stringify({ project: decoded, sessions: nSessions, modified: Math.floor(stat.mtimeMs) }));
|
|
@@ -1091,7 +1259,7 @@ function sessionCommand() {
|
|
|
1091
1259
|
console.error(`No project matched: ${project}`);
|
|
1092
1260
|
process.exit(1);
|
|
1093
1261
|
}
|
|
1094
|
-
const fullPath =
|
|
1262
|
+
const fullPath = path3.join(PROJECTS_DIR, projDir);
|
|
1095
1263
|
console.log(`Project: ${decodePath(projDir)}`);
|
|
1096
1264
|
console.log(`Dir: ${fullPath}`);
|
|
1097
1265
|
console.log("");
|
|
@@ -1100,16 +1268,16 @@ function sessionCommand() {
|
|
|
1100
1268
|
console.log(fmt("----------", "----", "-------", "--------"));
|
|
1101
1269
|
let files;
|
|
1102
1270
|
try {
|
|
1103
|
-
files =
|
|
1271
|
+
files = fs3.readdirSync(fullPath).filter((f) => f.endsWith(".jsonl"));
|
|
1104
1272
|
} catch {
|
|
1105
1273
|
return;
|
|
1106
1274
|
}
|
|
1107
1275
|
for (const file of files) {
|
|
1108
|
-
const filePath =
|
|
1276
|
+
const filePath = path3.join(fullPath, file);
|
|
1109
1277
|
const sessionId = file.replace(/\.jsonl$/, "");
|
|
1110
1278
|
let msgCount = 0;
|
|
1111
1279
|
try {
|
|
1112
|
-
const content =
|
|
1280
|
+
const content = fs3.readFileSync(filePath, "utf-8");
|
|
1113
1281
|
msgCount = content ? content.split("\n").filter((l) => l.trim()).length : 0;
|
|
1114
1282
|
} catch {
|
|
1115
1283
|
}
|
|
@@ -1117,7 +1285,7 @@ function sessionCommand() {
|
|
|
1117
1285
|
console.log(fmt(sessionId, slug || "-", started, String(msgCount)));
|
|
1118
1286
|
if (opts.verbose) {
|
|
1119
1287
|
try {
|
|
1120
|
-
const lines =
|
|
1288
|
+
const lines = fs3.readFileSync(filePath, "utf-8").split("\n");
|
|
1121
1289
|
for (const line of lines) {
|
|
1122
1290
|
if (!line.trim()) continue;
|
|
1123
1291
|
try {
|
|
@@ -1145,33 +1313,36 @@ function sessionCommand() {
|
|
|
1145
1313
|
}
|
|
1146
1314
|
});
|
|
1147
1315
|
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) => {
|
|
1148
|
-
let
|
|
1316
|
+
let searchRoots = [{ root: PROJECTS_DIR, label: "" }];
|
|
1317
|
+
if (isDesktopAppInstalled()) {
|
|
1318
|
+
searchRoots.push({ root: DESKTOP_SESSIONS_DIR, label: "[desktop] " });
|
|
1319
|
+
}
|
|
1149
1320
|
if (opts.project) {
|
|
1150
1321
|
const projDir = findProjectDir(opts.project);
|
|
1151
1322
|
if (!projDir) {
|
|
1152
1323
|
console.error(`No project matched: ${opts.project}`);
|
|
1153
1324
|
process.exit(1);
|
|
1154
1325
|
}
|
|
1155
|
-
|
|
1326
|
+
searchRoots = [{ root: path3.join(PROJECTS_DIR, projDir), label: "" }];
|
|
1156
1327
|
}
|
|
1157
1328
|
const limit = parseInt(opts.limit, 10);
|
|
1158
1329
|
let count = 0;
|
|
1159
|
-
function searchDir(dir) {
|
|
1330
|
+
function searchDir(dir, label, baseDir) {
|
|
1160
1331
|
if (count >= limit) return;
|
|
1161
1332
|
let entries;
|
|
1162
1333
|
try {
|
|
1163
|
-
entries =
|
|
1334
|
+
entries = fs3.readdirSync(dir, { withFileTypes: true });
|
|
1164
1335
|
} catch {
|
|
1165
1336
|
return;
|
|
1166
1337
|
}
|
|
1167
1338
|
for (const entry of entries) {
|
|
1168
1339
|
if (count >= limit) break;
|
|
1169
|
-
const fullPath =
|
|
1340
|
+
const fullPath = path3.join(dir, entry.name);
|
|
1170
1341
|
if (entry.isDirectory()) {
|
|
1171
|
-
searchDir(fullPath);
|
|
1342
|
+
searchDir(fullPath, label, baseDir);
|
|
1172
1343
|
} else if (entry.name.endsWith(".jsonl")) {
|
|
1173
1344
|
try {
|
|
1174
|
-
const content =
|
|
1345
|
+
const content = fs3.readFileSync(fullPath, "utf-8");
|
|
1175
1346
|
const lines = content.split("\n");
|
|
1176
1347
|
let found = false;
|
|
1177
1348
|
for (let lineno = 0; lineno < lines.length; lineno++) {
|
|
@@ -1180,10 +1351,11 @@ function sessionCommand() {
|
|
|
1180
1351
|
const match = opts.ignoreCase ? line.toLowerCase().includes(query.toLowerCase()) : line.includes(query);
|
|
1181
1352
|
if (match) {
|
|
1182
1353
|
if (!found) {
|
|
1183
|
-
const relPath =
|
|
1354
|
+
const relPath = path3.relative(baseDir, fullPath);
|
|
1184
1355
|
const projEnc = relPath.split("/")[0];
|
|
1185
|
-
const sessionId =
|
|
1186
|
-
|
|
1356
|
+
const sessionId = path3.basename(fullPath, ".jsonl");
|
|
1357
|
+
const projName = label ? projEnc : decodePath(projEnc);
|
|
1358
|
+
console.log(`${label}[${projName} \u2192 ${sessionId}]`);
|
|
1187
1359
|
found = true;
|
|
1188
1360
|
count++;
|
|
1189
1361
|
}
|
|
@@ -1211,12 +1383,14 @@ function sessionCommand() {
|
|
|
1211
1383
|
}
|
|
1212
1384
|
}
|
|
1213
1385
|
}
|
|
1214
|
-
|
|
1386
|
+
for (const { root, label } of searchRoots) {
|
|
1387
|
+
searchDir(root, label, root);
|
|
1388
|
+
}
|
|
1215
1389
|
});
|
|
1216
1390
|
session.command("ps").description("Show active Claude Code processes").action(() => {
|
|
1217
1391
|
let files;
|
|
1218
1392
|
try {
|
|
1219
|
-
files =
|
|
1393
|
+
files = fs3.readdirSync(SESSIONS_DIR).filter((f) => f.endsWith(".json"));
|
|
1220
1394
|
} catch {
|
|
1221
1395
|
console.log("(no session files found)");
|
|
1222
1396
|
return;
|
|
@@ -1230,7 +1404,7 @@ function sessionCommand() {
|
|
|
1230
1404
|
console.log(fmt("---", "----------", "-------", "---", ""));
|
|
1231
1405
|
for (const file of files) {
|
|
1232
1406
|
try {
|
|
1233
|
-
const data = JSON.parse(
|
|
1407
|
+
const data = JSON.parse(fs3.readFileSync(path3.join(SESSIONS_DIR, file), "utf-8"));
|
|
1234
1408
|
const pid = String(data.pid || "?");
|
|
1235
1409
|
const sessionId = data.sessionId || "?";
|
|
1236
1410
|
const cwd = data.cwd || "?";
|
|
@@ -1251,49 +1425,75 @@ function sessionCommand() {
|
|
|
1251
1425
|
let nSessions = 0;
|
|
1252
1426
|
let totalMsgs = 0;
|
|
1253
1427
|
let nActive = 0;
|
|
1428
|
+
let nDesktopSessions = 0;
|
|
1429
|
+
let nDesktopMsgs = 0;
|
|
1430
|
+
const walk = (dir) => {
|
|
1431
|
+
const results = [];
|
|
1432
|
+
try {
|
|
1433
|
+
for (const entry of fs3.readdirSync(dir, { withFileTypes: true })) {
|
|
1434
|
+
const fullPath = path3.join(dir, entry.name);
|
|
1435
|
+
if (entry.isDirectory()) results.push(...walk(fullPath));
|
|
1436
|
+
else if (entry.name.endsWith(".jsonl")) results.push(fullPath);
|
|
1437
|
+
}
|
|
1438
|
+
} catch {
|
|
1439
|
+
}
|
|
1440
|
+
return results;
|
|
1441
|
+
};
|
|
1254
1442
|
try {
|
|
1255
|
-
nProjects =
|
|
1443
|
+
nProjects = fs3.readdirSync(PROJECTS_DIR).length;
|
|
1256
1444
|
} catch {
|
|
1257
1445
|
}
|
|
1258
1446
|
try {
|
|
1259
|
-
const walk = (dir) => {
|
|
1260
|
-
const results = [];
|
|
1261
|
-
try {
|
|
1262
|
-
for (const entry of fs2.readdirSync(dir, { withFileTypes: true })) {
|
|
1263
|
-
const fullPath = path2.join(dir, entry.name);
|
|
1264
|
-
if (entry.isDirectory()) results.push(...walk(fullPath));
|
|
1265
|
-
else if (entry.name.endsWith(".jsonl")) results.push(fullPath);
|
|
1266
|
-
}
|
|
1267
|
-
} catch {
|
|
1268
|
-
}
|
|
1269
|
-
return results;
|
|
1270
|
-
};
|
|
1271
1447
|
const sessionFiles = walk(PROJECTS_DIR);
|
|
1272
1448
|
nSessions = sessionFiles.length;
|
|
1273
1449
|
for (const f of sessionFiles) {
|
|
1274
1450
|
try {
|
|
1275
|
-
const content =
|
|
1451
|
+
const content = fs3.readFileSync(f, "utf-8");
|
|
1276
1452
|
totalMsgs += content ? content.split("\n").filter((l) => l.trim()).length : 0;
|
|
1277
1453
|
} catch {
|
|
1278
1454
|
}
|
|
1279
1455
|
}
|
|
1280
1456
|
} catch {
|
|
1281
1457
|
}
|
|
1458
|
+
if (isDesktopAppInstalled()) {
|
|
1459
|
+
try {
|
|
1460
|
+
const desktopFiles = walk(DESKTOP_SESSIONS_DIR);
|
|
1461
|
+
nDesktopSessions = desktopFiles.length;
|
|
1462
|
+
for (const f of desktopFiles) {
|
|
1463
|
+
try {
|
|
1464
|
+
const content = fs3.readFileSync(f, "utf-8");
|
|
1465
|
+
nDesktopMsgs += content ? content.split("\n").filter((l) => l.trim()).length : 0;
|
|
1466
|
+
} catch {
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
} catch {
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1282
1472
|
try {
|
|
1283
|
-
nActive =
|
|
1473
|
+
nActive = fs3.readdirSync(SESSIONS_DIR).filter((f) => f.endsWith(".json")).length;
|
|
1284
1474
|
} catch {
|
|
1285
1475
|
}
|
|
1286
1476
|
console.log(`Projects: ${nProjects}`);
|
|
1287
|
-
console.log(`Sessions: ${nSessions}`);
|
|
1288
|
-
|
|
1477
|
+
console.log(`Sessions: ${nSessions} (CLI)`);
|
|
1478
|
+
if (isDesktopAppInstalled()) {
|
|
1479
|
+
console.log(` ${nDesktopSessions} (desktop)`);
|
|
1480
|
+
}
|
|
1481
|
+
console.log(`Total messages: ${totalMsgs} (CLI)`);
|
|
1482
|
+
if (isDesktopAppInstalled()) {
|
|
1483
|
+
console.log(` ${nDesktopMsgs} (desktop)`);
|
|
1484
|
+
}
|
|
1289
1485
|
console.log(`Active procs: ${nActive} (in ${SESSIONS_DIR})`);
|
|
1290
1486
|
console.log("");
|
|
1291
1487
|
try {
|
|
1292
|
-
const totalSize = execSync(`du -sh "${
|
|
1488
|
+
const totalSize = execSync(`du -sh "${path3.join(process.env.CLAUDE_DIR || path3.join(process.env.HOME || "", ".claude"))}" 2>/dev/null`, { encoding: "utf-8" }).trim().split(/\s+/)[0];
|
|
1293
1489
|
const projSize = execSync(`du -sh "${PROJECTS_DIR}" 2>/dev/null`, { encoding: "utf-8" }).trim().split(/\s+/)[0];
|
|
1490
|
+
const desktopSize = isDesktopAppInstalled() ? execSync(`du -sh "${DESKTOP_SESSIONS_DIR}" 2>/dev/null`, { encoding: "utf-8" }).trim().split(/\s+/)[0] : null;
|
|
1294
1491
|
console.log("Storage:");
|
|
1295
1492
|
console.log(` Total: ${totalSize}`);
|
|
1296
1493
|
console.log(` Projects: ${projSize}`);
|
|
1494
|
+
if (desktopSize) {
|
|
1495
|
+
console.log(` Desktop: ${desktopSize}`);
|
|
1496
|
+
}
|
|
1297
1497
|
} catch {
|
|
1298
1498
|
}
|
|
1299
1499
|
});
|
|
@@ -1305,23 +1505,23 @@ function sessionCommand() {
|
|
|
1305
1505
|
const walk = (dir) => {
|
|
1306
1506
|
let entries;
|
|
1307
1507
|
try {
|
|
1308
|
-
entries =
|
|
1508
|
+
entries = fs3.readdirSync(dir, { withFileTypes: true });
|
|
1309
1509
|
} catch {
|
|
1310
1510
|
return;
|
|
1311
1511
|
}
|
|
1312
1512
|
for (const entry of entries) {
|
|
1313
|
-
const fullPath =
|
|
1513
|
+
const fullPath = path3.join(dir, entry.name);
|
|
1314
1514
|
if (entry.isDirectory()) {
|
|
1315
1515
|
walk(fullPath);
|
|
1316
1516
|
} else if (entry.name.endsWith(".jsonl")) {
|
|
1317
1517
|
try {
|
|
1318
|
-
const stat =
|
|
1518
|
+
const stat = fs3.statSync(fullPath);
|
|
1319
1519
|
if (stat.mtimeMs < cutoffMs) {
|
|
1320
1520
|
const size = stat.size;
|
|
1321
1521
|
if (opts.dryRun) {
|
|
1322
1522
|
console.log(`[dry-run] would delete: ${fullPath} (${Math.floor(size / 1024)}KB)`);
|
|
1323
1523
|
} else {
|
|
1324
|
-
|
|
1524
|
+
fs3.unlinkSync(fullPath);
|
|
1325
1525
|
console.log(`Deleted: ${fullPath}`);
|
|
1326
1526
|
}
|
|
1327
1527
|
deleted++;
|
|
@@ -1333,6 +1533,9 @@ function sessionCommand() {
|
|
|
1333
1533
|
}
|
|
1334
1534
|
};
|
|
1335
1535
|
walk(PROJECTS_DIR);
|
|
1536
|
+
if (isDesktopAppInstalled()) {
|
|
1537
|
+
walk(DESKTOP_SESSIONS_DIR);
|
|
1538
|
+
}
|
|
1336
1539
|
console.log("");
|
|
1337
1540
|
const verb = opts.dryRun ? "Would delete" : "Deleted";
|
|
1338
1541
|
console.log(`${verb} ${deleted} file(s) (~${Math.floor(freed / 1024)}KB freed)`);
|
|
@@ -1372,6 +1575,7 @@ _cc-hub() {
|
|
|
1372
1575
|
'remove:Remove a profile'
|
|
1373
1576
|
'rename:Rename a profile'
|
|
1374
1577
|
'default:Set the default profile'
|
|
1578
|
+
'sync:Synchronize all CLI profiles to the desktop app'
|
|
1375
1579
|
)
|
|
1376
1580
|
|
|
1377
1581
|
local -a hooks_subcmds
|
|
@@ -1515,7 +1719,7 @@ _cc-hub() {
|
|
|
1515
1719
|
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
1516
1720
|
commands="profile use run hook session provider complete help"
|
|
1517
1721
|
|
|
1518
|
-
local profile_subcmds="add update list view remove rename default"
|
|
1722
|
+
local profile_subcmds="add update list view remove rename default sync"
|
|
1519
1723
|
local provider_subcmds="list"
|
|
1520
1724
|
local provider_types="anthropic openai"
|
|
1521
1725
|
local hooks_subcmds="list add remove enable disable"
|