cc-hub-cli 1.0.9 → 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 +301 -100
- 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,40 +697,18 @@ 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
|
});
|
|
573
|
-
profile.command("remove-model").description("Remove specific models from a profile").argument("<name>", "Profile name").option("-m, --model <model>", "Model ID to remove - can be used multiple times", collect, []).action((name, opts) => {
|
|
574
|
-
ensureProfilesFile();
|
|
575
|
-
const data = readJson(PROFILES_FILE);
|
|
576
|
-
if (!data.profiles[name]) {
|
|
577
|
-
console.error(`Profile '${name}' not found.`);
|
|
578
|
-
process.exit(1);
|
|
579
|
-
}
|
|
580
|
-
const p = data.profiles[name];
|
|
581
|
-
const toRemove = new Set(opts.model);
|
|
582
|
-
if (toRemove.size === 0) {
|
|
583
|
-
console.error("No models specified to remove. Use -m <model> to specify models.");
|
|
584
|
-
process.exit(1);
|
|
585
|
-
}
|
|
586
|
-
const currentModels = p.models || (p.model ? [p.model] : []);
|
|
587
|
-
const newModels = currentModels.filter((m) => !toRemove.has(m));
|
|
588
|
-
if (newModels.length === 0) {
|
|
589
|
-
delete p.models;
|
|
590
|
-
delete p.model;
|
|
591
|
-
console.log(`Removed all models from profile '${name}'.`);
|
|
592
|
-
} else {
|
|
593
|
-
const removedCount = currentModels.length - newModels.length;
|
|
594
|
-
p.models = newModels;
|
|
595
|
-
p.model = newModels[0];
|
|
596
|
-
console.log(`Removed ${removedCount} model(s) from profile '${name}'.`);
|
|
597
|
-
console.log(`Remaining models: ${newModels.join(", ")}`);
|
|
598
|
-
}
|
|
599
|
-
writeJson(PROFILES_FILE, data);
|
|
600
|
-
});
|
|
601
712
|
profile.command("list").description("List all profiles").action(() => {
|
|
602
713
|
ensureProfilesFile();
|
|
603
714
|
const data = readJson(PROFILES_FILE);
|
|
@@ -614,9 +725,11 @@ function profileCommand() {
|
|
|
614
725
|
for (const name of names) {
|
|
615
726
|
const p = profiles[name];
|
|
616
727
|
const marker = name === def ? "* " : " ";
|
|
728
|
+
const desktopMarker = p.desktopId ? " [desktop]" : "";
|
|
729
|
+
const displayName = (name + desktopMarker).padEnd(20);
|
|
617
730
|
console.log(fmt(
|
|
618
731
|
marker,
|
|
619
|
-
|
|
732
|
+
displayName,
|
|
620
733
|
formatModels(p),
|
|
621
734
|
maskToken(p.token || ""),
|
|
622
735
|
p.provider || "anthropic",
|
|
@@ -633,7 +746,8 @@ function profileCommand() {
|
|
|
633
746
|
process.exit(1);
|
|
634
747
|
}
|
|
635
748
|
if (opts.json) {
|
|
636
|
-
|
|
749
|
+
const { desktopId, ...rest } = p;
|
|
750
|
+
console.log(JSON.stringify({ name, ...rest }, null, 2));
|
|
637
751
|
} else {
|
|
638
752
|
console.log(`Name: ${name}`);
|
|
639
753
|
console.log(`Model: ${p.model || "(unset)"}`);
|
|
@@ -665,10 +779,30 @@ function profileCommand() {
|
|
|
665
779
|
console.error(`Profile '${name}' not found.`);
|
|
666
780
|
process.exit(1);
|
|
667
781
|
}
|
|
782
|
+
removeProfileFromDesktop(name, data.profiles[name]);
|
|
668
783
|
delete data.profiles[name];
|
|
669
784
|
writeJson(PROFILES_FILE, data);
|
|
670
785
|
console.log(`Profile '${name}' removed.`);
|
|
671
786
|
});
|
|
787
|
+
profile.command("rename").description("Rename a profile").argument("<oldName>", "Current profile name").argument("<newName>", "New profile name").action((oldName, newName) => {
|
|
788
|
+
ensureProfilesFile();
|
|
789
|
+
const data = readJson(PROFILES_FILE);
|
|
790
|
+
if (!data.profiles[oldName]) {
|
|
791
|
+
console.error(`Profile '${oldName}' not found.`);
|
|
792
|
+
process.exit(1);
|
|
793
|
+
}
|
|
794
|
+
if (data.profiles[newName]) {
|
|
795
|
+
console.error(`Profile '${newName}' already exists. Choose a different name.`);
|
|
796
|
+
process.exit(1);
|
|
797
|
+
}
|
|
798
|
+
data.profiles[newName] = data.profiles[oldName];
|
|
799
|
+
delete data.profiles[oldName];
|
|
800
|
+
if (data.default === oldName) {
|
|
801
|
+
data.default = newName;
|
|
802
|
+
}
|
|
803
|
+
writeJson(PROFILES_FILE, data);
|
|
804
|
+
console.log(`Profile '${oldName}' renamed to '${newName}'.`);
|
|
805
|
+
});
|
|
672
806
|
profile.command("default").description("Set the default profile").argument("<name>", "Profile name to set as default").action((name) => {
|
|
673
807
|
ensureProfilesFile();
|
|
674
808
|
const data = readJson(PROFILES_FILE);
|
|
@@ -677,9 +811,32 @@ function profileCommand() {
|
|
|
677
811
|
process.exit(1);
|
|
678
812
|
}
|
|
679
813
|
data.default = name;
|
|
814
|
+
setDesktopActiveProfile(data.profiles[name]);
|
|
680
815
|
writeJson(PROFILES_FILE, data);
|
|
681
816
|
console.log(`Default profile set to '${name}'.`);
|
|
682
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
|
+
});
|
|
683
840
|
return profile;
|
|
684
841
|
}
|
|
685
842
|
function collect(value, previous) {
|
|
@@ -689,7 +846,8 @@ function execClaude(profileName, p, extraArgs) {
|
|
|
689
846
|
updateSettingsForProfile(p);
|
|
690
847
|
const models = p.models || (p.model ? [p.model] : []);
|
|
691
848
|
const firstModel = models[0];
|
|
692
|
-
const
|
|
849
|
+
const binary = resolveClaudeBinary();
|
|
850
|
+
const cmd = [binary];
|
|
693
851
|
if (firstModel) cmd.push("--model", firstModel);
|
|
694
852
|
cmd.push(...extraArgs);
|
|
695
853
|
const env = {
|
|
@@ -754,6 +912,7 @@ function useCommand() {
|
|
|
754
912
|
process.exit(1);
|
|
755
913
|
}
|
|
756
914
|
data.default = name;
|
|
915
|
+
setDesktopActiveProfile(data.profiles[name]);
|
|
757
916
|
writeJson(PROFILES_FILE, data);
|
|
758
917
|
console.log(`Default profile set to '${name}'.`);
|
|
759
918
|
});
|
|
@@ -975,8 +1134,8 @@ function hooksCommand() {
|
|
|
975
1134
|
|
|
976
1135
|
// src/sessions.ts
|
|
977
1136
|
import { Command as Command4 } from "commander";
|
|
978
|
-
import
|
|
979
|
-
import
|
|
1137
|
+
import fs3 from "fs";
|
|
1138
|
+
import path3 from "path";
|
|
980
1139
|
import { execSync } from "child_process";
|
|
981
1140
|
function encodePath(p) {
|
|
982
1141
|
return p.replace(/\./g, "DOTMARK").replace(/\//g, "-").replace(/DOTMARK/g, "-");
|
|
@@ -991,9 +1150,9 @@ function formatTimestamp(ms) {
|
|
|
991
1150
|
}
|
|
992
1151
|
function findProjectDir(query) {
|
|
993
1152
|
const encoded = encodePath(query);
|
|
994
|
-
if (
|
|
1153
|
+
if (fs3.existsSync(path3.join(PROJECTS_DIR, encoded))) return encoded;
|
|
995
1154
|
try {
|
|
996
|
-
const dirs =
|
|
1155
|
+
const dirs = fs3.readdirSync(PROJECTS_DIR);
|
|
997
1156
|
const match = dirs.find((d) => d.toLowerCase().includes(query.toLowerCase()));
|
|
998
1157
|
return match || null;
|
|
999
1158
|
} catch {
|
|
@@ -1005,7 +1164,7 @@ function parseSessionMeta(filePath) {
|
|
|
1005
1164
|
let slug = "";
|
|
1006
1165
|
let customTitle = "";
|
|
1007
1166
|
try {
|
|
1008
|
-
const lines =
|
|
1167
|
+
const lines = fs3.readFileSync(filePath, "utf-8").split("\n");
|
|
1009
1168
|
for (const line of lines) {
|
|
1010
1169
|
if (!line.trim()) continue;
|
|
1011
1170
|
try {
|
|
@@ -1063,26 +1222,26 @@ function sessionCommand() {
|
|
|
1063
1222
|
const limit = parseInt(opts.limit, 10);
|
|
1064
1223
|
let dirs;
|
|
1065
1224
|
try {
|
|
1066
|
-
dirs =
|
|
1225
|
+
dirs = fs3.readdirSync(PROJECTS_DIR);
|
|
1067
1226
|
} catch {
|
|
1068
1227
|
console.log("No projects directory found.");
|
|
1069
1228
|
return;
|
|
1070
1229
|
}
|
|
1071
1230
|
dirs.sort((a, b) => {
|
|
1072
|
-
const statA =
|
|
1073
|
-
const statB =
|
|
1231
|
+
const statA = fs3.statSync(path3.join(PROJECTS_DIR, a));
|
|
1232
|
+
const statB = fs3.statSync(path3.join(PROJECTS_DIR, b));
|
|
1074
1233
|
return statB.mtimeMs - statA.mtimeMs;
|
|
1075
1234
|
});
|
|
1076
1235
|
let count = 0;
|
|
1077
1236
|
for (const projDir of dirs) {
|
|
1078
1237
|
if (count >= limit) break;
|
|
1079
|
-
const fullPath =
|
|
1238
|
+
const fullPath = path3.join(PROJECTS_DIR, projDir);
|
|
1080
1239
|
let nSessions = 0;
|
|
1081
1240
|
try {
|
|
1082
|
-
nSessions =
|
|
1241
|
+
nSessions = fs3.readdirSync(fullPath).filter((f) => f.endsWith(".jsonl")).length;
|
|
1083
1242
|
} catch {
|
|
1084
1243
|
}
|
|
1085
|
-
const stat =
|
|
1244
|
+
const stat = fs3.statSync(fullPath);
|
|
1086
1245
|
const decoded = decodePath(projDir);
|
|
1087
1246
|
if (opts.json) {
|
|
1088
1247
|
console.log(JSON.stringify({ project: decoded, sessions: nSessions, modified: Math.floor(stat.mtimeMs) }));
|
|
@@ -1100,7 +1259,7 @@ function sessionCommand() {
|
|
|
1100
1259
|
console.error(`No project matched: ${project}`);
|
|
1101
1260
|
process.exit(1);
|
|
1102
1261
|
}
|
|
1103
|
-
const fullPath =
|
|
1262
|
+
const fullPath = path3.join(PROJECTS_DIR, projDir);
|
|
1104
1263
|
console.log(`Project: ${decodePath(projDir)}`);
|
|
1105
1264
|
console.log(`Dir: ${fullPath}`);
|
|
1106
1265
|
console.log("");
|
|
@@ -1109,16 +1268,16 @@ function sessionCommand() {
|
|
|
1109
1268
|
console.log(fmt("----------", "----", "-------", "--------"));
|
|
1110
1269
|
let files;
|
|
1111
1270
|
try {
|
|
1112
|
-
files =
|
|
1271
|
+
files = fs3.readdirSync(fullPath).filter((f) => f.endsWith(".jsonl"));
|
|
1113
1272
|
} catch {
|
|
1114
1273
|
return;
|
|
1115
1274
|
}
|
|
1116
1275
|
for (const file of files) {
|
|
1117
|
-
const filePath =
|
|
1276
|
+
const filePath = path3.join(fullPath, file);
|
|
1118
1277
|
const sessionId = file.replace(/\.jsonl$/, "");
|
|
1119
1278
|
let msgCount = 0;
|
|
1120
1279
|
try {
|
|
1121
|
-
const content =
|
|
1280
|
+
const content = fs3.readFileSync(filePath, "utf-8");
|
|
1122
1281
|
msgCount = content ? content.split("\n").filter((l) => l.trim()).length : 0;
|
|
1123
1282
|
} catch {
|
|
1124
1283
|
}
|
|
@@ -1126,7 +1285,7 @@ function sessionCommand() {
|
|
|
1126
1285
|
console.log(fmt(sessionId, slug || "-", started, String(msgCount)));
|
|
1127
1286
|
if (opts.verbose) {
|
|
1128
1287
|
try {
|
|
1129
|
-
const lines =
|
|
1288
|
+
const lines = fs3.readFileSync(filePath, "utf-8").split("\n");
|
|
1130
1289
|
for (const line of lines) {
|
|
1131
1290
|
if (!line.trim()) continue;
|
|
1132
1291
|
try {
|
|
@@ -1154,33 +1313,36 @@ function sessionCommand() {
|
|
|
1154
1313
|
}
|
|
1155
1314
|
});
|
|
1156
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) => {
|
|
1157
|
-
let
|
|
1316
|
+
let searchRoots = [{ root: PROJECTS_DIR, label: "" }];
|
|
1317
|
+
if (isDesktopAppInstalled()) {
|
|
1318
|
+
searchRoots.push({ root: DESKTOP_SESSIONS_DIR, label: "[desktop] " });
|
|
1319
|
+
}
|
|
1158
1320
|
if (opts.project) {
|
|
1159
1321
|
const projDir = findProjectDir(opts.project);
|
|
1160
1322
|
if (!projDir) {
|
|
1161
1323
|
console.error(`No project matched: ${opts.project}`);
|
|
1162
1324
|
process.exit(1);
|
|
1163
1325
|
}
|
|
1164
|
-
|
|
1326
|
+
searchRoots = [{ root: path3.join(PROJECTS_DIR, projDir), label: "" }];
|
|
1165
1327
|
}
|
|
1166
1328
|
const limit = parseInt(opts.limit, 10);
|
|
1167
1329
|
let count = 0;
|
|
1168
|
-
function searchDir(dir) {
|
|
1330
|
+
function searchDir(dir, label, baseDir) {
|
|
1169
1331
|
if (count >= limit) return;
|
|
1170
1332
|
let entries;
|
|
1171
1333
|
try {
|
|
1172
|
-
entries =
|
|
1334
|
+
entries = fs3.readdirSync(dir, { withFileTypes: true });
|
|
1173
1335
|
} catch {
|
|
1174
1336
|
return;
|
|
1175
1337
|
}
|
|
1176
1338
|
for (const entry of entries) {
|
|
1177
1339
|
if (count >= limit) break;
|
|
1178
|
-
const fullPath =
|
|
1340
|
+
const fullPath = path3.join(dir, entry.name);
|
|
1179
1341
|
if (entry.isDirectory()) {
|
|
1180
|
-
searchDir(fullPath);
|
|
1342
|
+
searchDir(fullPath, label, baseDir);
|
|
1181
1343
|
} else if (entry.name.endsWith(".jsonl")) {
|
|
1182
1344
|
try {
|
|
1183
|
-
const content =
|
|
1345
|
+
const content = fs3.readFileSync(fullPath, "utf-8");
|
|
1184
1346
|
const lines = content.split("\n");
|
|
1185
1347
|
let found = false;
|
|
1186
1348
|
for (let lineno = 0; lineno < lines.length; lineno++) {
|
|
@@ -1189,10 +1351,11 @@ function sessionCommand() {
|
|
|
1189
1351
|
const match = opts.ignoreCase ? line.toLowerCase().includes(query.toLowerCase()) : line.includes(query);
|
|
1190
1352
|
if (match) {
|
|
1191
1353
|
if (!found) {
|
|
1192
|
-
const relPath =
|
|
1354
|
+
const relPath = path3.relative(baseDir, fullPath);
|
|
1193
1355
|
const projEnc = relPath.split("/")[0];
|
|
1194
|
-
const sessionId =
|
|
1195
|
-
|
|
1356
|
+
const sessionId = path3.basename(fullPath, ".jsonl");
|
|
1357
|
+
const projName = label ? projEnc : decodePath(projEnc);
|
|
1358
|
+
console.log(`${label}[${projName} \u2192 ${sessionId}]`);
|
|
1196
1359
|
found = true;
|
|
1197
1360
|
count++;
|
|
1198
1361
|
}
|
|
@@ -1220,12 +1383,14 @@ function sessionCommand() {
|
|
|
1220
1383
|
}
|
|
1221
1384
|
}
|
|
1222
1385
|
}
|
|
1223
|
-
|
|
1386
|
+
for (const { root, label } of searchRoots) {
|
|
1387
|
+
searchDir(root, label, root);
|
|
1388
|
+
}
|
|
1224
1389
|
});
|
|
1225
1390
|
session.command("ps").description("Show active Claude Code processes").action(() => {
|
|
1226
1391
|
let files;
|
|
1227
1392
|
try {
|
|
1228
|
-
files =
|
|
1393
|
+
files = fs3.readdirSync(SESSIONS_DIR).filter((f) => f.endsWith(".json"));
|
|
1229
1394
|
} catch {
|
|
1230
1395
|
console.log("(no session files found)");
|
|
1231
1396
|
return;
|
|
@@ -1239,7 +1404,7 @@ function sessionCommand() {
|
|
|
1239
1404
|
console.log(fmt("---", "----------", "-------", "---", ""));
|
|
1240
1405
|
for (const file of files) {
|
|
1241
1406
|
try {
|
|
1242
|
-
const data = JSON.parse(
|
|
1407
|
+
const data = JSON.parse(fs3.readFileSync(path3.join(SESSIONS_DIR, file), "utf-8"));
|
|
1243
1408
|
const pid = String(data.pid || "?");
|
|
1244
1409
|
const sessionId = data.sessionId || "?";
|
|
1245
1410
|
const cwd = data.cwd || "?";
|
|
@@ -1260,49 +1425,75 @@ function sessionCommand() {
|
|
|
1260
1425
|
let nSessions = 0;
|
|
1261
1426
|
let totalMsgs = 0;
|
|
1262
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
|
+
};
|
|
1263
1442
|
try {
|
|
1264
|
-
nProjects =
|
|
1443
|
+
nProjects = fs3.readdirSync(PROJECTS_DIR).length;
|
|
1265
1444
|
} catch {
|
|
1266
1445
|
}
|
|
1267
1446
|
try {
|
|
1268
|
-
const walk = (dir) => {
|
|
1269
|
-
const results = [];
|
|
1270
|
-
try {
|
|
1271
|
-
for (const entry of fs2.readdirSync(dir, { withFileTypes: true })) {
|
|
1272
|
-
const fullPath = path2.join(dir, entry.name);
|
|
1273
|
-
if (entry.isDirectory()) results.push(...walk(fullPath));
|
|
1274
|
-
else if (entry.name.endsWith(".jsonl")) results.push(fullPath);
|
|
1275
|
-
}
|
|
1276
|
-
} catch {
|
|
1277
|
-
}
|
|
1278
|
-
return results;
|
|
1279
|
-
};
|
|
1280
1447
|
const sessionFiles = walk(PROJECTS_DIR);
|
|
1281
1448
|
nSessions = sessionFiles.length;
|
|
1282
1449
|
for (const f of sessionFiles) {
|
|
1283
1450
|
try {
|
|
1284
|
-
const content =
|
|
1451
|
+
const content = fs3.readFileSync(f, "utf-8");
|
|
1285
1452
|
totalMsgs += content ? content.split("\n").filter((l) => l.trim()).length : 0;
|
|
1286
1453
|
} catch {
|
|
1287
1454
|
}
|
|
1288
1455
|
}
|
|
1289
1456
|
} catch {
|
|
1290
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
|
+
}
|
|
1291
1472
|
try {
|
|
1292
|
-
nActive =
|
|
1473
|
+
nActive = fs3.readdirSync(SESSIONS_DIR).filter((f) => f.endsWith(".json")).length;
|
|
1293
1474
|
} catch {
|
|
1294
1475
|
}
|
|
1295
1476
|
console.log(`Projects: ${nProjects}`);
|
|
1296
|
-
console.log(`Sessions: ${nSessions}`);
|
|
1297
|
-
|
|
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
|
+
}
|
|
1298
1485
|
console.log(`Active procs: ${nActive} (in ${SESSIONS_DIR})`);
|
|
1299
1486
|
console.log("");
|
|
1300
1487
|
try {
|
|
1301
|
-
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];
|
|
1302
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;
|
|
1303
1491
|
console.log("Storage:");
|
|
1304
1492
|
console.log(` Total: ${totalSize}`);
|
|
1305
1493
|
console.log(` Projects: ${projSize}`);
|
|
1494
|
+
if (desktopSize) {
|
|
1495
|
+
console.log(` Desktop: ${desktopSize}`);
|
|
1496
|
+
}
|
|
1306
1497
|
} catch {
|
|
1307
1498
|
}
|
|
1308
1499
|
});
|
|
@@ -1314,23 +1505,23 @@ function sessionCommand() {
|
|
|
1314
1505
|
const walk = (dir) => {
|
|
1315
1506
|
let entries;
|
|
1316
1507
|
try {
|
|
1317
|
-
entries =
|
|
1508
|
+
entries = fs3.readdirSync(dir, { withFileTypes: true });
|
|
1318
1509
|
} catch {
|
|
1319
1510
|
return;
|
|
1320
1511
|
}
|
|
1321
1512
|
for (const entry of entries) {
|
|
1322
|
-
const fullPath =
|
|
1513
|
+
const fullPath = path3.join(dir, entry.name);
|
|
1323
1514
|
if (entry.isDirectory()) {
|
|
1324
1515
|
walk(fullPath);
|
|
1325
1516
|
} else if (entry.name.endsWith(".jsonl")) {
|
|
1326
1517
|
try {
|
|
1327
|
-
const stat =
|
|
1518
|
+
const stat = fs3.statSync(fullPath);
|
|
1328
1519
|
if (stat.mtimeMs < cutoffMs) {
|
|
1329
1520
|
const size = stat.size;
|
|
1330
1521
|
if (opts.dryRun) {
|
|
1331
1522
|
console.log(`[dry-run] would delete: ${fullPath} (${Math.floor(size / 1024)}KB)`);
|
|
1332
1523
|
} else {
|
|
1333
|
-
|
|
1524
|
+
fs3.unlinkSync(fullPath);
|
|
1334
1525
|
console.log(`Deleted: ${fullPath}`);
|
|
1335
1526
|
}
|
|
1336
1527
|
deleted++;
|
|
@@ -1342,6 +1533,9 @@ function sessionCommand() {
|
|
|
1342
1533
|
}
|
|
1343
1534
|
};
|
|
1344
1535
|
walk(PROJECTS_DIR);
|
|
1536
|
+
if (isDesktopAppInstalled()) {
|
|
1537
|
+
walk(DESKTOP_SESSIONS_DIR);
|
|
1538
|
+
}
|
|
1345
1539
|
console.log("");
|
|
1346
1540
|
const verb = opts.dryRun ? "Would delete" : "Deleted";
|
|
1347
1541
|
console.log(`${verb} ${deleted} file(s) (~${Math.floor(freed / 1024)}KB freed)`);
|
|
@@ -1376,11 +1570,12 @@ _cc-hub() {
|
|
|
1376
1570
|
profile_subcmds=(
|
|
1377
1571
|
'add:Add or update a profile'
|
|
1378
1572
|
'update:Update fields of an existing profile'
|
|
1379
|
-
'remove-model:Remove specific models from a profile'
|
|
1380
1573
|
'list:List all profiles'
|
|
1381
1574
|
'view:View full details of a profile'
|
|
1382
1575
|
'remove:Remove a profile'
|
|
1576
|
+
'rename:Rename a profile'
|
|
1383
1577
|
'default:Set the default profile'
|
|
1578
|
+
'sync:Synchronize all CLI profiles to the desktop app'
|
|
1384
1579
|
)
|
|
1385
1580
|
|
|
1386
1581
|
local -a hooks_subcmds
|
|
@@ -1434,8 +1629,12 @@ _cc-hub() {
|
|
|
1434
1629
|
profile)
|
|
1435
1630
|
if (( CURRENT == 2 )); then
|
|
1436
1631
|
_describe -t profile-subcmds 'profile subcommand' profile_subcmds
|
|
1437
|
-
elif [[ $words[2] == "view" || $words[2] == "remove" || $words[2] == "default"
|
|
1632
|
+
elif [[ $words[2] == "view" || $words[2] == "remove" || $words[2] == "default" ]]; then
|
|
1438
1633
|
_cc_hub_profiles
|
|
1634
|
+
elif [[ $words[2] == "rename" ]]; then
|
|
1635
|
+
if (( CURRENT == 3 )); then
|
|
1636
|
+
_cc_hub_profiles
|
|
1637
|
+
fi
|
|
1439
1638
|
elif [[ $words[2] == "update" ]]; then
|
|
1440
1639
|
if (( CURRENT == 3 )); then
|
|
1441
1640
|
_cc_hub_profiles
|
|
@@ -1520,7 +1719,7 @@ _cc-hub() {
|
|
|
1520
1719
|
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
1521
1720
|
commands="profile use run hook session provider complete help"
|
|
1522
1721
|
|
|
1523
|
-
local profile_subcmds="add update
|
|
1722
|
+
local profile_subcmds="add update list view remove rename default sync"
|
|
1524
1723
|
local provider_subcmds="list"
|
|
1525
1724
|
local provider_types="anthropic openai"
|
|
1526
1725
|
local hooks_subcmds="list add remove enable disable"
|
|
@@ -1538,7 +1737,9 @@ _cc-hub() {
|
|
|
1538
1737
|
profile)
|
|
1539
1738
|
if [[ \${COMP_CWORD} -eq 2 ]]; then
|
|
1540
1739
|
COMPREPLY=($(compgen -W "$profile_subcmds" -- "$cur"))
|
|
1541
|
-
elif [[ "$prev" == "view" || "$prev" == "remove" || "$prev" == "default"
|
|
1740
|
+
elif [[ "$prev" == "view" || "$prev" == "remove" || "$prev" == "default" ]]; then
|
|
1741
|
+
_cc-hub_profiles
|
|
1742
|
+
elif [[ "$prev" == "rename" ]]; then
|
|
1542
1743
|
_cc-hub_profiles
|
|
1543
1744
|
elif [[ "$prev" == "profile" ]]; then
|
|
1544
1745
|
COMPREPLY=($(compgen -W "$profile_subcmds" -- "$cur"))
|