cc-hub-cli 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +160 -1
  2. package/dist/index.js +211 -53
  3. package/package.json +1 -2
package/README.md CHANGED
@@ -1 +1,160 @@
1
- # cc-hub
1
+ # cc-hub
2
+
3
+ Manage Claude CLI profiles, hooks, and sessions — one tool, all in one place.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g cc-hub-cli
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```bash
14
+ # Add a profile
15
+ cc-hub profile add flow -m anthropic.claude-4-6-sonnet -t eyJ... -u https://example.com/api
16
+
17
+ # Set as default
18
+ cc-hub profile default flow
19
+
20
+ # Launch Claude Code with it
21
+ cc-hub run
22
+
23
+ # Or launch with a specific profile
24
+ cc-hub run flow
25
+ ```
26
+
27
+ ## Commands
28
+
29
+ ### Profiles
30
+
31
+ Manage multiple Claude API configurations (model, token, URL).
32
+
33
+ ```bash
34
+ cc-hub profile add <name> -m <model> -t <token> -u <url> # Add or update
35
+ cc-hub profile update <name> -m <model> # Update fields
36
+ cc-hub profile list # List all (tokens masked)
37
+ cc-hub profile view <name> # View details (token visible)
38
+ cc-hub profile view <name> -j # View as JSON
39
+ cc-hub profile remove <name> # Remove
40
+ cc-hub profile default <name> # Set default
41
+ ```
42
+
43
+ ### Run / Use
44
+
45
+ Launch Claude Code with a profile's credentials injected as environment variables.
46
+
47
+ ```bash
48
+ # Set a profile as default (no launch)
49
+ cc-hub use <name>
50
+
51
+ # Launch Claude Code with a profile
52
+ cc-hub use <name> [extra args...]
53
+
54
+ # Launch using the default profile
55
+ cc-hub run [extra args...]
56
+
57
+ # Launch with a specific profile
58
+ cc-hub run <name> [extra args...]
59
+ ```
60
+
61
+ `run` and `use` exec into the `claude` CLI with `ANTHROPIC_AUTH_TOKEN`, `ANTHROPIC_BASE_URL`, and `--model` set from the profile.
62
+
63
+ ### Hook
64
+
65
+ Manage Claude Code hooks in `~/.claude/settings.json`.
66
+
67
+ ```bash
68
+ cc-hub hook list # List all hooks
69
+ cc-hub hook add -e <event> -c <command> [-m <matcher>] [-a] # Add a hook
70
+ cc-hub hook remove -i <index> # Remove by index
71
+ cc-hub hook enable -i <index> [-i <index>] # Enable disabled hooks
72
+ cc-hub hook disable -i <index> [-i <index>] # Disable active hooks
73
+ ```
74
+
75
+ **Events:** `PreToolUse`, `PostToolUse`, `Notification`, `Stop`, `UserPromptSubmit`, `PermissionRequest`
76
+
77
+ **Examples:**
78
+
79
+ ```bash
80
+ # Desktop notification when Claude finishes
81
+ cc-hub hook add -e Stop -c 'osascript -e "display notification \"Done\""'
82
+
83
+ # Hook only for Bash tool usage
84
+ cc-hub hook add -e PreToolUse -m Bash -c 'echo "Running bash..."'
85
+
86
+ # Async hook
87
+ cc-hub hook add -e PostToolUse -c 'my-logger.sh' -a
88
+ ```
89
+
90
+ Disabled hooks are kept in a pool and can be re-enabled later with `hook enable`.
91
+
92
+ ### Session
93
+
94
+ Browse and search Claude Code session history from `~/.claude/projects/`.
95
+
96
+ ```bash
97
+ cc-hub session list [-n <limit>] [-s] [-j] # List projects
98
+ cc-hub session show <project> [-v] # Show sessions for a project
99
+ cc-hub session search <query> [-p <project>] [-n <n>] [-i] # Search conversation history
100
+ cc-hub session ps # Show active processes
101
+ cc-hub session stats # Summary statistics
102
+ cc-hub session clean [-d <days>] [--dry-run] # Delete old sessions
103
+ ```
104
+
105
+ **Examples:**
106
+
107
+ ```bash
108
+ # List recent projects
109
+ cc-hub session list -n 10
110
+
111
+ # Show sessions with first message preview
112
+ cc-hub session show cc-hub -v
113
+
114
+ # Case-insensitive search
115
+ cc-hub session search "authentication" -i
116
+
117
+ # Preview what would be cleaned up
118
+ cc-hub session clean -d 60 --dry-run
119
+ ```
120
+
121
+ ### Shell Completion
122
+
123
+ ```bash
124
+ # zsh — add to ~/.zshrc
125
+ eval "$(cc-hub complete zsh)"
126
+
127
+ # bash — add to ~/.bashrc
128
+ eval "$(cc-hub complete bash)"
129
+ ```
130
+
131
+ Completes subcommands, profile names, and event types.
132
+
133
+ ## Configuration
134
+
135
+ cc-hub reads from these paths (overridable via environment variables):
136
+
137
+ | File | Default | Env Override |
138
+ |---|---|---|
139
+ | Profiles | `~/.claude/profiles.json` | `CLAUDE_PROFILES_FILE` |
140
+ | Settings | `~/.claude/settings.json` | `CLAUDE_SETTINGS_FILE` |
141
+ | Claude dir | `~/.claude` | `CLAUDE_DIR` |
142
+
143
+ ### Profile storage format
144
+
145
+ ```json
146
+ {
147
+ "profiles": {
148
+ "flow": {
149
+ "model": "anthropic.claude-4-6-sonnet",
150
+ "token": "eyJ...",
151
+ "url": "https://example.com/api"
152
+ }
153
+ },
154
+ "default": "flow"
155
+ }
156
+ ```
157
+
158
+ ## License
159
+
160
+ MIT
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command4 } from "commander";
4
+ import { Command as Command5 } from "commander";
5
5
 
6
6
  // src/profiles.ts
7
7
  import { Command } from "commander";
8
- import Anthropic from "@anthropic-ai/sdk";
8
+ import { spawnSync } from "child_process";
9
9
 
10
10
  // src/config.ts
11
11
  import fs from "fs";
@@ -134,78 +134,60 @@ function profileCommand() {
134
134
  });
135
135
  return profile;
136
136
  }
137
- async function runWithProfile(profileName, prompt) {
138
- ensureProfilesFile();
139
- const data = readJson(PROFILES_FILE);
140
- const p = data.profiles[profileName];
141
- if (!p) {
142
- console.error(`Profile '${profileName}' not found.`);
143
- process.exit(1);
144
- }
145
- if (!p.token) {
146
- console.error(`Profile '${profileName}' has no token configured.`);
147
- process.exit(1);
148
- }
149
- const client = new Anthropic({
150
- apiKey: p.token,
151
- ...p.url ? { baseURL: p.url } : {}
152
- });
137
+ function execClaude(profileName, p, extraArgs) {
138
+ const cmd = ["claude"];
139
+ if (p.model) cmd.push("--model", p.model);
140
+ cmd.push(...extraArgs);
141
+ const env = {
142
+ ...process.env,
143
+ ANTHROPIC_AUTH_TOKEN: p.token || void 0,
144
+ ANTHROPIC_BASE_URL: p.url || void 0
145
+ };
146
+ delete env.ANTHROPIC_API_KEY;
153
147
  console.error(`Using profile '${profileName}': model=${p.model || "(default)"} url=${p.url || "(default)"}`);
154
- const stream = client.messages.stream(
155
- {
156
- model: p.model || "claude-sonnet-4-20250514",
157
- max_tokens: 4096,
158
- messages: [{ role: "user", content: prompt }]
159
- }
160
- );
161
- for await (const event of stream) {
162
- if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
163
- process.stdout.write(event.delta.text);
164
- }
165
- }
166
- process.stdout.write("\n");
148
+ const result = spawnSync(cmd[0], cmd.slice(1), {
149
+ stdio: "inherit",
150
+ env
151
+ });
152
+ process.exit(result.status ?? 1);
167
153
  }
168
154
  function useCommand() {
169
- return new Command("use").description("Launch a chat session using a saved profile").argument("<name>", "Profile name").argument("[prompt...]", "Prompt text").action(async (name, promptParts) => {
170
- const prompt = promptParts.join(" ");
171
- if (!prompt) {
172
- ensureProfilesFile();
173
- const data = readJson(PROFILES_FILE);
174
- if (!data.profiles[name]) {
175
- console.error(`Profile '${name}' not found.`);
176
- process.exit(1);
177
- }
155
+ return new Command("use").description("Launch Claude Code with a saved profile (or set default if no args)").allowUnknownOption().argument("<name>", "Profile name").argument("[args...]", "Extra arguments passed to claude").action((name, args) => {
156
+ ensureProfilesFile();
157
+ const data = readJson(PROFILES_FILE);
158
+ const p = data.profiles[name];
159
+ if (!p) {
160
+ console.error(`Profile '${name}' not found.`);
161
+ process.exit(1);
162
+ }
163
+ if (!args || args.length === 0) {
178
164
  data.default = name;
179
165
  writeJson(PROFILES_FILE, data);
180
166
  console.log(`Default profile set to '${name}'.`);
181
167
  return;
182
168
  }
183
- await runWithProfile(name, prompt);
169
+ execClaude(name, p, args);
184
170
  });
185
171
  }
186
172
  function runCommand() {
187
- return new Command("run").description("Run a prompt using the default or a specified profile").argument("[args...]", "Optional profile name followed by prompt").action(async (args) => {
173
+ return new Command("run").description("Launch Claude Code using the default or a specified profile").allowUnknownOption().argument("[args...]", "Optional profile name followed by extra arguments").action((args) => {
188
174
  ensureProfilesFile();
189
175
  const data = readJson(PROFILES_FILE);
190
176
  let profileName = "";
191
- let promptParts;
177
+ let claudeArgs;
192
178
  if (args.length > 0 && data.profiles[args[0]]) {
193
179
  profileName = args[0];
194
- promptParts = args.slice(1);
180
+ claudeArgs = args.slice(1);
195
181
  } else {
196
182
  profileName = data.default || "";
197
- promptParts = args;
183
+ claudeArgs = args;
198
184
  }
199
185
  if (!profileName) {
200
186
  console.error("No default profile set. Use 'cc-hub use <name>' first.");
201
187
  process.exit(1);
202
188
  }
203
- const prompt = promptParts.join(" ");
204
- if (!prompt) {
205
- console.error("No prompt provided.");
206
- process.exit(1);
207
- }
208
- await runWithProfile(profileName, prompt);
189
+ const p = data.profiles[profileName];
190
+ execClaude(profileName, p, claudeArgs);
209
191
  });
210
192
  }
211
193
 
@@ -250,7 +232,7 @@ function buildFlat(data) {
250
232
  return rows;
251
233
  }
252
234
  function hooksCommand() {
253
- const hooks = new Command2("hooks").alias("h").description("Manage Claude Code hooks in settings.json");
235
+ const hooks = new Command2("hook").description("Manage Claude Code hooks in settings.json");
254
236
  hooks.command("list").description("List all hooks").action(() => {
255
237
  ensureSettingsFile();
256
238
  const data = readJson(SETTINGS_FILE);
@@ -777,12 +759,188 @@ function sessionCommand() {
777
759
  return session;
778
760
  }
779
761
 
762
+ // src/complete.ts
763
+ import { Command as Command4 } from "commander";
764
+ var ZSH_COMPLETION = `#compdef cc-hub
765
+
766
+ _cc-hub() {
767
+ local -a commands
768
+ commands=(
769
+ 'profile:Manage Claude CLI profiles'
770
+ 'use:Launch Claude Code with a saved profile'
771
+ 'run:Launch Claude Code using the default or a specified profile'
772
+ 'hook:Manage Claude Code hooks in settings.json'
773
+ 'session:Manage Claude Code sessions'
774
+ 'complete:Print shell completion functions'
775
+ 'help:Display help for a command'
776
+ )
777
+
778
+ local -a profile_subcmds
779
+ profile_subcmds=(
780
+ 'add:Add or update a profile'
781
+ 'update:Update fields of an existing profile'
782
+ 'list:List all profiles'
783
+ 'view:View full details of a profile'
784
+ 'remove:Remove a profile'
785
+ 'default:Set the default profile'
786
+ )
787
+
788
+ local -a hooks_subcmds
789
+ hooks_subcmds=(
790
+ 'list:List all hooks'
791
+ 'add:Add a hook to settings.json'
792
+ 'remove:Remove a hook by its global index'
793
+ 'enable:Enable one or more disabled hooks'
794
+ 'disable:Disable one or more hooks'
795
+ )
796
+
797
+ local -a session_subcmds
798
+ session_subcmds=(
799
+ 'list:List all Claude Code project sessions'
800
+ 'show:Show session files for a project'
801
+ 'search:Search conversation history across all projects'
802
+ 'ps:Show active Claude Code processes'
803
+ 'stats:Show summary statistics'
804
+ 'clean:Delete session JSONL files older than N days'
805
+ )
806
+
807
+ _cc_hub_profiles() {
808
+ local profiles_file="\${CLAUDE_PROFILES_FILE:-$HOME/.claude/profiles.json}"
809
+ if [[ -f "$profiles_file" ]]; then
810
+ local -a names
811
+ names=(\${(f)"$(command python3 -c "
812
+ import json
813
+ data = json.load(open('$profiles_file'))
814
+ for name in data.get('profiles', {}):
815
+ print(name)
816
+ " 2>/dev/null)"})
817
+ _describe -t profiles 'profile' names
818
+ fi
819
+ }
820
+
821
+ _arguments -C \\
822
+ '1: :->command' \\
823
+ '*::arg:->args'
824
+
825
+ case $state in
826
+ command)
827
+ _describe -t commands 'cc-hub command' commands
828
+ ;;
829
+ args)
830
+ case $words[1] in
831
+ profile)
832
+ if (( CURRENT == 2 )); then
833
+ _describe -t profile-subcmds 'profile subcommand' profile_subcmds
834
+ elif [[ $words[2] == "view" || $words[2] == "remove" || $words[2] == "default" || $words[2] == "update" ]]; then
835
+ _cc_hub_profiles
836
+ fi
837
+ ;;
838
+ use|run)
839
+ _cc_hub_profiles
840
+ ;;
841
+ hook)
842
+ if (( CURRENT == 2 )); then
843
+ _describe -t hooks-subcmds 'hook subcommand' hooks_subcmds
844
+ fi
845
+ ;;
846
+ session)
847
+ if (( CURRENT == 2 )); then
848
+ _describe -t session-subcmds 'session subcommand' session_subcmds
849
+ fi
850
+ ;;
851
+ esac
852
+ ;;
853
+ esac
854
+ }
855
+
856
+ _cc-hub "$@"
857
+ `;
858
+ var BASH_COMPLETION = `_cc-hub_profiles() {
859
+ local profiles_file="\${CLAUDE_PROFILES_FILE:-$HOME/.claude/profiles.json}"
860
+ if [[ -f "$profiles_file" ]]; then
861
+ local names
862
+ names=$(command python3 -c "
863
+ import json
864
+ data = json.load(open('$profiles_file'))
865
+ for name in data.get('profiles', {}):
866
+ print(name)
867
+ " 2>/dev/null)
868
+ COMPREPLY=($(compgen -W "$names" -- "\${cur}"))
869
+ fi
870
+ }
871
+
872
+ _cc-hub() {
873
+ local cur prev commands
874
+ COMPREPLY=()
875
+ cur="\${COMP_WORDS[COMP_CWORD]}"
876
+ prev="\${COMP_WORDS[COMP_CWORD-1]}"
877
+ commands="profile use run hook session complete help"
878
+
879
+ local profile_subcmds="add update list view remove default"
880
+ local hooks_subcmds="list add remove enable disable"
881
+ local session_subcmds="list show search ps stats clean"
882
+
883
+ # Top-level command
884
+ if [[ \${COMP_CWORD} -eq 1 ]]; then
885
+ COMPREPLY=($(compgen -W "$commands" -- "$cur"))
886
+ return 0
887
+ fi
888
+
889
+ local cmd="\${COMP_WORDS[1]}"
890
+
891
+ case "$cmd" in
892
+ profile)
893
+ if [[ \${COMP_CWORD} -eq 2 ]]; then
894
+ COMPREPLY=($(compgen -W "$profile_subcmds" -- "$cur"))
895
+ elif [[ "$prev" == "view" || "$prev" == "remove" || "$prev" == "default" || "$prev" == "update" ]]; then
896
+ _cc-hub_profiles
897
+ elif [[ "$prev" == "profile" ]]; then
898
+ COMPREPLY=($(compgen -W "$profile_subcmds" -- "$cur"))
899
+ fi
900
+ ;;
901
+ use|run)
902
+ _cc-hub_profiles
903
+ ;;
904
+ hook)
905
+ if [[ \${COMP_CWORD} -eq 2 ]]; then
906
+ COMPREPLY=($(compgen -W "$hooks_subcmds" -- "$cur"))
907
+ fi
908
+ ;;
909
+ session)
910
+ if [[ \${COMP_CWORD} -eq 2 ]]; then
911
+ COMPREPLY=($(compgen -W "$session_subcmds" -- "$cur"))
912
+ fi
913
+ ;;
914
+ esac
915
+
916
+ return 0
917
+ }
918
+
919
+ complete -F _cc-hub cc-hub
920
+ `;
921
+ function completeCommand() {
922
+ return new Command4("complete").description("Print shell completion script").argument("<shell>", "Shell type: bash or zsh").action((shell) => {
923
+ switch (shell) {
924
+ case "zsh":
925
+ process.stdout.write(ZSH_COMPLETION);
926
+ break;
927
+ case "bash":
928
+ process.stdout.write(BASH_COMPLETION);
929
+ break;
930
+ default:
931
+ console.error(`Unsupported shell: ${shell}. Use 'bash' or 'zsh'.`);
932
+ process.exit(1);
933
+ }
934
+ });
935
+ }
936
+
780
937
  // src/index.ts
781
- var program = new Command4();
938
+ var program = new Command5();
782
939
  program.name("cc-hub").description("Manage Claude CLI profiles, hooks, and sessions").version("1.0.0");
783
940
  program.addCommand(profileCommand());
784
941
  program.addCommand(useCommand());
785
942
  program.addCommand(runCommand());
786
943
  program.addCommand(hooksCommand());
787
944
  program.addCommand(sessionCommand());
945
+ program.addCommand(completeCommand());
788
946
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-hub-cli",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Manage Claude CLI profiles, hooks, and sessions",
5
5
  "type": "module",
6
6
  "bin": {
@@ -25,7 +25,6 @@
25
25
  "anthropic"
26
26
  ],
27
27
  "dependencies": {
28
- "@anthropic-ai/sdk": "^0.39.0",
29
28
  "commander": "^13.1.0"
30
29
  },
31
30
  "devDependencies": {