cc-hub-cli 1.1.5 → 1.1.7
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 +38 -0
- package/dist/index.js +302 -46
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -159,6 +159,44 @@ Invoke-Expression (& cc-hub completion powershell | Out-String)
|
|
|
159
159
|
|
|
160
160
|
Completes subcommands, profile names, and event types.
|
|
161
161
|
|
|
162
|
+
## Logging
|
|
163
|
+
|
|
164
|
+
cc-hub writes structured logs to `~/.claude/cc-hub/logs/cc-hub-YYYY-MM-DD.log`.
|
|
165
|
+
|
|
166
|
+
### Log levels
|
|
167
|
+
|
|
168
|
+
Levels are ordered from most to least verbose:
|
|
169
|
+
|
|
170
|
+
| Level | Description |
|
|
171
|
+
|---|---|
|
|
172
|
+
| `DEBUG` | All service calls, file reads/writes, path encode/decode, proxy requests |
|
|
173
|
+
| `INFO` | Command executions, profile launches, proxy start/stop |
|
|
174
|
+
| `WARN` | JSON auto-fix events, backup restores |
|
|
175
|
+
| `ERROR` | Thrown exceptions, upstream errors, uncaught exceptions/rejections |
|
|
176
|
+
|
|
177
|
+
The default level is `INFO`. To change it, add `_cc_hub_logLevel` to `~/.claude/settings.json`:
|
|
178
|
+
|
|
179
|
+
```json
|
|
180
|
+
{
|
|
181
|
+
"_cc_hub_logLevel": "DEBUG"
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Valid values: `DEBUG`, `INFO`, `WARN`, `ERROR`.
|
|
186
|
+
|
|
187
|
+
### Viewing logs
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
# Tail today's log
|
|
191
|
+
tail -f ~/.claude/cc-hub/logs/cc-hub-$(date +%Y-%m-%d).log
|
|
192
|
+
|
|
193
|
+
# View all logs
|
|
194
|
+
ls -lt ~/.claude/cc-hub/logs/
|
|
195
|
+
|
|
196
|
+
# Search for errors
|
|
197
|
+
grep ERROR ~/.claude/cc-hub/logs/*.log
|
|
198
|
+
```
|
|
199
|
+
|
|
162
200
|
## Configuration
|
|
163
201
|
|
|
164
202
|
cc-hub reads from these paths (overridable via environment variables):
|
package/dist/index.js
CHANGED
|
@@ -254,6 +254,14 @@ var NoOpDesktopApp = class {
|
|
|
254
254
|
import fs3 from "fs";
|
|
255
255
|
import path3 from "path";
|
|
256
256
|
import { randomUUID } from "crypto";
|
|
257
|
+
var ANTHROPIC_ALIASES = ["claude-sonnet-4-5", "claude-opus-4-7", "claude-haiku-4-5-20251001"];
|
|
258
|
+
function isAnthropicModel(model) {
|
|
259
|
+
const anthropicAliases = ["opus", "sonnet", "haiku", "best", "default", "opusplan", "opus[1m]", "sonnet[1m]"];
|
|
260
|
+
const lower = model.toLowerCase();
|
|
261
|
+
if (anthropicAliases.includes(lower)) return true;
|
|
262
|
+
if (lower.startsWith("claude-")) return true;
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
257
265
|
function toDesktopProfile(p) {
|
|
258
266
|
const models = p.models || (p.model ? [p.model] : []);
|
|
259
267
|
const isAnthropic = p.provider === "anthropic" || !p.provider && !p.url;
|
|
@@ -263,13 +271,24 @@ function toDesktopProfile(p) {
|
|
|
263
271
|
inferenceModels: models.map((m) => ({ name: m, supports1m: true }))
|
|
264
272
|
};
|
|
265
273
|
}
|
|
266
|
-
|
|
274
|
+
const mappings = [];
|
|
275
|
+
const mappedModels = models.map((m, index) => {
|
|
276
|
+
if (isAnthropicModel(m)) return m;
|
|
277
|
+
const alias = ANTHROPIC_ALIASES[Math.min(index, ANTHROPIC_ALIASES.length - 1)];
|
|
278
|
+
mappings.push({ alias, actual: m });
|
|
279
|
+
return alias;
|
|
280
|
+
});
|
|
281
|
+
const result = {
|
|
267
282
|
inferenceProvider: "gateway",
|
|
268
283
|
inferenceGatewayBaseUrl: p.url || void 0,
|
|
269
284
|
inferenceGatewayApiKey: p.token || void 0,
|
|
270
285
|
inferenceGatewayAuthScheme: "bearer",
|
|
271
|
-
inferenceModels:
|
|
286
|
+
inferenceModels: mappedModels.map((m) => ({ name: m, supports1m: true }))
|
|
272
287
|
};
|
|
288
|
+
if (mappings.length > 0) {
|
|
289
|
+
result.inferenceModelMappings = mappings;
|
|
290
|
+
}
|
|
291
|
+
return result;
|
|
273
292
|
}
|
|
274
293
|
var DesktopProfileSyncer = class {
|
|
275
294
|
constructor(app) {
|
|
@@ -471,6 +490,7 @@ function fixJsonFile(filePath, fallback = {}) {
|
|
|
471
490
|
const raw = fs4.readFileSync(filePath, "utf-8");
|
|
472
491
|
try {
|
|
473
492
|
JSON.parse(raw);
|
|
493
|
+
fs4.mkdirSync(CLAUDE_DIR, { recursive: true });
|
|
474
494
|
fs4.copyFileSync(filePath, backupPath);
|
|
475
495
|
return;
|
|
476
496
|
} catch {
|
|
@@ -500,14 +520,24 @@ function fixJsonFile(filePath, fallback = {}) {
|
|
|
500
520
|
warn(`Fixed invalid JSON in ${path4.basename(filePath)}.`);
|
|
501
521
|
console.error(`Fixed invalid JSON in ${path4.basename(filePath)}.`);
|
|
502
522
|
} catch {
|
|
523
|
+
let restored = false;
|
|
503
524
|
if (fs4.existsSync(backupPath)) {
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
525
|
+
try {
|
|
526
|
+
const backupRaw = fs4.readFileSync(backupPath, "utf-8");
|
|
527
|
+
JSON.parse(backupRaw);
|
|
528
|
+
fs4.copyFileSync(backupPath, filePath);
|
|
529
|
+
restored = true;
|
|
530
|
+
warn(`Restored ${path4.basename(filePath)} from backup.`);
|
|
531
|
+
console.error(`Restored ${path4.basename(filePath)} from backup.`);
|
|
532
|
+
} catch {
|
|
533
|
+
error(`Backup ${path4.basename(backupPath)} is also corrupt; using fallback.`);
|
|
534
|
+
console.error(`Backup ${path4.basename(backupPath)} is also corrupt; using fallback.`);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
if (!restored) {
|
|
508
538
|
writeJson(filePath, fallback);
|
|
509
|
-
error(`Could not fix ${path4.basename(filePath)}, no backup found, reset to default.`);
|
|
510
|
-
console.error(`Could not fix ${path4.basename(filePath)}, no backup found, reset to default.`);
|
|
539
|
+
error(`Could not fix ${path4.basename(filePath)}, no valid backup found, reset to default.`);
|
|
540
|
+
console.error(`Could not fix ${path4.basename(filePath)}, no valid backup found, reset to default.`);
|
|
511
541
|
}
|
|
512
542
|
}
|
|
513
543
|
}
|
|
@@ -518,6 +548,9 @@ import { spawnSync as spawnSync2, spawn } from "child_process";
|
|
|
518
548
|
// src/provider/index.ts
|
|
519
549
|
import { Command } from "commander";
|
|
520
550
|
|
|
551
|
+
// src/provider/server.ts
|
|
552
|
+
import http from "http";
|
|
553
|
+
|
|
521
554
|
// src/provider/transform.ts
|
|
522
555
|
function sanitizeToolId(id) {
|
|
523
556
|
let sanitized = id.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
@@ -558,17 +591,26 @@ function transformAnthropicToOpenAI(body) {
|
|
|
558
591
|
});
|
|
559
592
|
}
|
|
560
593
|
const contentParts = msg.content.filter(
|
|
561
|
-
(b) => b.type === "text" && b.text || b.type === "image" && b.source
|
|
594
|
+
(b) => b.type === "text" && b.text || b.type === "image" && (b.source?.type === "base64" && b.source.media_type && b.source.data || b.source?.type === "url" && b.source.url)
|
|
562
595
|
);
|
|
563
596
|
if (contentParts.length > 0) {
|
|
564
597
|
const converted = contentParts.map((part) => {
|
|
565
598
|
if (part.type === "image") {
|
|
566
|
-
|
|
567
|
-
|
|
599
|
+
if (part.source?.type === "base64" && part.source.media_type && part.source.data) {
|
|
600
|
+
const url = `data:${part.source.media_type};base64,${part.source.data}`;
|
|
601
|
+
debug(`transform: converting base64 image (${part.source.media_type}, ${part.source.data.length} chars)`);
|
|
602
|
+
return { type: "image_url", image_url: { url } };
|
|
603
|
+
} else if (part.source?.type === "url" && part.source.url) {
|
|
604
|
+
debug(`transform: converting image url (${part.source.url.slice(0, 80)}...)`);
|
|
605
|
+
return { type: "image_url", image_url: { url: part.source.url } };
|
|
606
|
+
}
|
|
607
|
+
warn(`transform: skipping invalid image block (missing source fields)`);
|
|
608
|
+
return null;
|
|
568
609
|
}
|
|
569
610
|
return { type: "text", text: part.text };
|
|
570
|
-
});
|
|
571
|
-
if (converted.
|
|
611
|
+
}).filter(Boolean);
|
|
612
|
+
if (converted.length === 0) {
|
|
613
|
+
} else if (converted.every((p) => p.type === "text")) {
|
|
572
614
|
messages.push({
|
|
573
615
|
role: "user",
|
|
574
616
|
content: converted.map((p) => p.text).join("")
|
|
@@ -666,7 +708,7 @@ function transformOpenAIResponseToAnthropic(openaiResponse, originalModel) {
|
|
|
666
708
|
id: openaiResponse.id ?? `msg_${Date.now()}`,
|
|
667
709
|
type: "message",
|
|
668
710
|
role: "assistant",
|
|
669
|
-
model:
|
|
711
|
+
model: originalModel,
|
|
670
712
|
content,
|
|
671
713
|
stop_reason: finishMap[choice.finish_reason] ?? "end_turn",
|
|
672
714
|
stop_sequence: null,
|
|
@@ -734,8 +776,7 @@ data: ${JSON.stringify(data)}
|
|
|
734
776
|
}
|
|
735
777
|
|
|
736
778
|
// src/provider/server.ts
|
|
737
|
-
|
|
738
|
-
async function startOpenAIProxy(targetUrl, apiKey, model, models = []) {
|
|
779
|
+
async function startOpenAIProxy(targetUrl, apiKey, model, models = [], modelMappings = {}) {
|
|
739
780
|
const base = targetUrl.replace(/\/+$/, "");
|
|
740
781
|
const server = http.createServer(async (req, res) => {
|
|
741
782
|
debug(`Proxy request: ${req.method} ${req.url}`);
|
|
@@ -761,7 +802,9 @@ async function startOpenAIProxy(targetUrl, apiKey, model, models = []) {
|
|
|
761
802
|
return;
|
|
762
803
|
}
|
|
763
804
|
const isStream = !!parsed.stream;
|
|
764
|
-
const
|
|
805
|
+
const requestModel = parsed.model ?? model;
|
|
806
|
+
const actualModel = modelMappings[requestModel] || requestModel;
|
|
807
|
+
const openaiBody = transformAnthropicToOpenAI({ ...parsed, model: actualModel, stream: false });
|
|
765
808
|
if (isStream) {
|
|
766
809
|
res.writeHead(200, {
|
|
767
810
|
"Content-Type": "text/event-stream",
|
|
@@ -866,6 +909,17 @@ var PROVIDERS = [
|
|
|
866
909
|
description: "Embedded proxy \u2014 translates Anthropic requests to OpenAI Chat Completions format"
|
|
867
910
|
}
|
|
868
911
|
];
|
|
912
|
+
var ANTHROPIC_ALIASES2 = ["claude-sonnet-4-5", "claude-opus-4-7", "claude-haiku-4-5-20251001"];
|
|
913
|
+
function isAnthropicModel2(model) {
|
|
914
|
+
const anthropicAliases = ["opus", "sonnet", "haiku", "best", "default", "opusplan", "opus[1m]", "sonnet[1m]"];
|
|
915
|
+
const lower = model.toLowerCase();
|
|
916
|
+
if (anthropicAliases.includes(lower)) return true;
|
|
917
|
+
if (lower.startsWith("claude-")) return true;
|
|
918
|
+
return false;
|
|
919
|
+
}
|
|
920
|
+
function collect(value, previous) {
|
|
921
|
+
return previous.concat([value]);
|
|
922
|
+
}
|
|
869
923
|
function providerCommand() {
|
|
870
924
|
const cmd = new Command("provider").description("Manage provider types");
|
|
871
925
|
cmd.command("list").description("List available provider types").action(safeAction(() => {
|
|
@@ -878,8 +932,51 @@ function providerCommand() {
|
|
|
878
932
|
}));
|
|
879
933
|
return cmd;
|
|
880
934
|
}
|
|
935
|
+
function proxyCommand() {
|
|
936
|
+
return new Command("proxy").description("Start a standalone OpenAI proxy for the desktop app").option("--profile <name>", "Use configuration from a saved profile").option("-u, --url <url>", "Upstream base URL (e.g., https://api.openai.com)").option("-k, --api-key <key>", "Upstream API key").option("-m, --model <model>", "Default model", "gpt-4o").option("--mapping <mapping>", "Model alias mapping (format: alias:actual, can be used multiple times)", collect, []).action(safeAction(async (opts) => {
|
|
937
|
+
let targetUrl = opts.url || "https://api.openai.com";
|
|
938
|
+
let apiKey = opts.apiKey || "";
|
|
939
|
+
let defaultModel = opts.model || "gpt-4o";
|
|
940
|
+
let models = [];
|
|
941
|
+
const modelMappings = {};
|
|
942
|
+
if (opts.profile) {
|
|
943
|
+
ensureProfilesFile();
|
|
944
|
+
const data = readJson(PROFILES_FILE);
|
|
945
|
+
const p = data.profiles[opts.profile];
|
|
946
|
+
if (!p) {
|
|
947
|
+
throw new Error(`Profile '${opts.profile}' not found.`);
|
|
948
|
+
}
|
|
949
|
+
targetUrl = p.url || targetUrl;
|
|
950
|
+
apiKey = p.token || apiKey;
|
|
951
|
+
models = p.models || (p.model ? [p.model] : []);
|
|
952
|
+
defaultModel = models[0] || defaultModel;
|
|
953
|
+
models.forEach((m, i) => {
|
|
954
|
+
if (!isAnthropicModel2(m)) {
|
|
955
|
+
const alias = ANTHROPIC_ALIASES2[Math.min(i, ANTHROPIC_ALIASES2.length - 1)];
|
|
956
|
+
modelMappings[alias] = m;
|
|
957
|
+
}
|
|
958
|
+
});
|
|
959
|
+
} else {
|
|
960
|
+
for (const m of opts.mapping) {
|
|
961
|
+
const [alias, actual] = m.split(":");
|
|
962
|
+
if (alias && actual) {
|
|
963
|
+
modelMappings[alias] = actual;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
models = [defaultModel];
|
|
967
|
+
}
|
|
968
|
+
const { baseUrl, stop } = await startOpenAIProxy(targetUrl, apiKey, defaultModel, models, modelMappings);
|
|
969
|
+
console.log(`Proxy running at ${baseUrl}`);
|
|
970
|
+
console.log("Press Ctrl+C to stop");
|
|
971
|
+
process.on("SIGINT", () => {
|
|
972
|
+
stop();
|
|
973
|
+
process.exit(0);
|
|
974
|
+
});
|
|
975
|
+
}));
|
|
976
|
+
}
|
|
881
977
|
|
|
882
978
|
// src/profiles/runner.ts
|
|
979
|
+
var BUILT_IN_DEFAULT = "__builtin__";
|
|
883
980
|
function resolveClaudeBinary() {
|
|
884
981
|
return createBinaryResolver().resolve();
|
|
885
982
|
}
|
|
@@ -953,7 +1050,8 @@ function execClaude(profileName, p, extraArgs) {
|
|
|
953
1050
|
p.url || "https://api.openai.com",
|
|
954
1051
|
p.token || "",
|
|
955
1052
|
firstModel || "gpt-4o",
|
|
956
|
-
allModels
|
|
1053
|
+
allModels,
|
|
1054
|
+
{}
|
|
957
1055
|
).then(({ baseUrl, stop }) => {
|
|
958
1056
|
env.ANTHROPIC_BASE_URL = baseUrl;
|
|
959
1057
|
debug(`execClaude: proxy running at ${baseUrl}`);
|
|
@@ -976,6 +1074,22 @@ function execClaude(profileName, p, extraArgs) {
|
|
|
976
1074
|
process.exit(result.status ?? 1);
|
|
977
1075
|
}
|
|
978
1076
|
}
|
|
1077
|
+
function execClaudeBuiltIn(extraArgs) {
|
|
1078
|
+
const binary = resolveClaudeBinary();
|
|
1079
|
+
const cmd = [binary, ...extraArgs];
|
|
1080
|
+
const env = {
|
|
1081
|
+
...process.env,
|
|
1082
|
+
CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS: "1",
|
|
1083
|
+
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: "1"
|
|
1084
|
+
};
|
|
1085
|
+
info(`Launching Claude with built-in official models: binary=${binary}`);
|
|
1086
|
+
const result = spawnSync2(cmd[0], cmd.slice(1), {
|
|
1087
|
+
stdio: "inherit",
|
|
1088
|
+
env,
|
|
1089
|
+
shell: process.platform === "win32"
|
|
1090
|
+
});
|
|
1091
|
+
process.exit(result.status ?? 1);
|
|
1092
|
+
}
|
|
979
1093
|
|
|
980
1094
|
// src/profiles/commands.ts
|
|
981
1095
|
function maskToken(token) {
|
|
@@ -985,10 +1099,10 @@ function maskToken(token) {
|
|
|
985
1099
|
}
|
|
986
1100
|
function formatModels(p) {
|
|
987
1101
|
if (p.models && p.models.length > 0) {
|
|
988
|
-
const nonAnthropicModels = p.models.filter((m) => !
|
|
1102
|
+
const nonAnthropicModels = p.models.filter((m) => !isAnthropicModel3(m));
|
|
989
1103
|
const parts = [];
|
|
990
1104
|
p.models.forEach((m, i) => {
|
|
991
|
-
if (!
|
|
1105
|
+
if (!isAnthropicModel3(m)) {
|
|
992
1106
|
const aliasIndex = nonAnthropicModels.indexOf(m);
|
|
993
1107
|
if (aliasIndex === 0) parts.push(`${m} (sonnet)`);
|
|
994
1108
|
else if (aliasIndex === 1) parts.push(`${m} (opus)`);
|
|
@@ -1006,20 +1120,20 @@ function formatModels(p) {
|
|
|
1006
1120
|
}
|
|
1007
1121
|
return p.model || "(unset)";
|
|
1008
1122
|
}
|
|
1009
|
-
function
|
|
1123
|
+
function isAnthropicModel3(model) {
|
|
1010
1124
|
const anthropicAliases = ["opus", "sonnet", "haiku", "best", "default", "opusplan", "opus[1m]", "sonnet[1m]"];
|
|
1011
1125
|
const lower = model.toLowerCase();
|
|
1012
1126
|
if (anthropicAliases.includes(lower)) return true;
|
|
1013
1127
|
if (lower.startsWith("claude-")) return true;
|
|
1014
1128
|
return false;
|
|
1015
1129
|
}
|
|
1016
|
-
function
|
|
1130
|
+
function collect2(value, previous) {
|
|
1017
1131
|
return previous.concat([value]);
|
|
1018
1132
|
}
|
|
1019
1133
|
function profileCommand() {
|
|
1020
1134
|
const profile = new Command2("profile").description("Manage Claude CLI profiles");
|
|
1021
1135
|
const syncer = createProfileSyncer();
|
|
1022
|
-
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)",
|
|
1136
|
+
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)", collect2, []).option("-t, --token <token>", "API key / token").option("-u, --url <url>", "Base URL").option("-p, --provider <provider>", "Provider type: anthropic (default) or openai").action(safeAction((name, opts) => {
|
|
1023
1137
|
const models = opts.model && opts.model.length > 0 ? opts.model : void 0;
|
|
1024
1138
|
if (models && models.length > 3) {
|
|
1025
1139
|
throw new Error("Error: A profile can have at most 3 models.");
|
|
@@ -1041,7 +1155,7 @@ function profileCommand() {
|
|
|
1041
1155
|
debug(`profile add: wrote ${PROFILES_FILE}`);
|
|
1042
1156
|
console.log(`Profile '${name}' saved.`);
|
|
1043
1157
|
}));
|
|
1044
|
-
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",
|
|
1158
|
+
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", collect2, []).option("-d, --delete-model <model>", "Remove model ID - can be used multiple times", collect2, []).option("-t, --token <token>", "API key / token").option("-u, --url <url>", "Base URL").option("-p, --provider <provider>", "Provider type").action(safeAction((name, opts) => {
|
|
1045
1159
|
ensureProfilesFile();
|
|
1046
1160
|
const data = readJson(PROFILES_FILE);
|
|
1047
1161
|
if (!data.profiles[name]) {
|
|
@@ -1133,6 +1247,9 @@ function profileCommand() {
|
|
|
1133
1247
|
profile.command("view").description("View full details of a profile (token unmasked)").argument("<name>", "Profile name").option("-j, --json", "Output as JSON").action(safeAction((name, opts) => {
|
|
1134
1248
|
ensureProfilesFile();
|
|
1135
1249
|
const data = readJson(PROFILES_FILE);
|
|
1250
|
+
if (name === BUILT_IN_DEFAULT) {
|
|
1251
|
+
throw new Error(`'${BUILT_IN_DEFAULT}' is not a stored profile. Use 'cc-hub run --built-in' or 'cc-hub use --built-in' for official Anthropic models.`);
|
|
1252
|
+
}
|
|
1136
1253
|
const p = data.profiles[name];
|
|
1137
1254
|
if (!p) {
|
|
1138
1255
|
throw new Error(`Profile '${name}' not found.`);
|
|
@@ -1144,10 +1261,10 @@ function profileCommand() {
|
|
|
1144
1261
|
console.log(`Name: ${name}`);
|
|
1145
1262
|
console.log(`Model: ${p.model || "(unset)"}`);
|
|
1146
1263
|
if (p.models && p.models.length > 0) {
|
|
1147
|
-
const nonAnthropicModels = p.models.filter((m) => !
|
|
1264
|
+
const nonAnthropicModels = p.models.filter((m) => !isAnthropicModel3(m));
|
|
1148
1265
|
console.log(`Models:`);
|
|
1149
1266
|
for (const m of p.models) {
|
|
1150
|
-
if (!
|
|
1267
|
+
if (!isAnthropicModel3(m)) {
|
|
1151
1268
|
const aliasIndex = nonAnthropicModels.indexOf(m);
|
|
1152
1269
|
let alias = "";
|
|
1153
1270
|
if (aliasIndex === 0) alias = " (sonnet)";
|
|
@@ -1194,9 +1311,19 @@ function profileCommand() {
|
|
|
1194
1311
|
writeJson(PROFILES_FILE, data);
|
|
1195
1312
|
console.log(`Profile '${oldName}' renamed to '${newName}'.`);
|
|
1196
1313
|
}));
|
|
1197
|
-
profile.command("default").description("Set the default profile").argument("
|
|
1314
|
+
profile.command("default").description("Set the default profile").option("--built-in", "Use official Anthropic models as default").argument("[name]", "Profile name to set as default (required unless --built-in)").action(safeAction((name, opts) => {
|
|
1198
1315
|
ensureProfilesFile();
|
|
1199
1316
|
const data = readJson(PROFILES_FILE);
|
|
1317
|
+
if (opts.builtIn) {
|
|
1318
|
+
data.default = BUILT_IN_DEFAULT;
|
|
1319
|
+
writeJson(PROFILES_FILE, data);
|
|
1320
|
+
debug(`profile default: wrote ${PROFILES_FILE}`);
|
|
1321
|
+
console.log("Default set to built-in official Anthropic models.");
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1324
|
+
if (!name) {
|
|
1325
|
+
throw new Error("Profile name is required. Use --built-in for official Anthropic models.");
|
|
1326
|
+
}
|
|
1200
1327
|
if (!data.profiles[name]) {
|
|
1201
1328
|
throw new Error(`Profile '${name}' not found.`);
|
|
1202
1329
|
}
|
|
@@ -1219,6 +1346,7 @@ function profileCommand() {
|
|
|
1219
1346
|
return;
|
|
1220
1347
|
}
|
|
1221
1348
|
for (const name of names) {
|
|
1349
|
+
if (name === BUILT_IN_DEFAULT) continue;
|
|
1222
1350
|
const p = data.profiles[name];
|
|
1223
1351
|
debug(`profile sync: syncing '${name}' to desktop`);
|
|
1224
1352
|
syncer.sync(name, p);
|
|
@@ -1231,9 +1359,19 @@ function profileCommand() {
|
|
|
1231
1359
|
}
|
|
1232
1360
|
function useCommand() {
|
|
1233
1361
|
const syncer = createProfileSyncer();
|
|
1234
|
-
return new Command2("use").description("Set a profile as the default").argument("
|
|
1362
|
+
return new Command2("use").description("Set a profile as the default").option("--built-in", "Use official Anthropic models as default").argument("[name]", "Profile name (required unless --built-in)").action(safeAction((name, opts) => {
|
|
1235
1363
|
ensureProfilesFile();
|
|
1236
1364
|
const data = readJson(PROFILES_FILE);
|
|
1365
|
+
if (opts.builtIn) {
|
|
1366
|
+
data.default = BUILT_IN_DEFAULT;
|
|
1367
|
+
writeJson(PROFILES_FILE, data);
|
|
1368
|
+
debug(`use: wrote ${PROFILES_FILE}`);
|
|
1369
|
+
console.log("Default set to built-in official Anthropic models.");
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
1372
|
+
if (!name) {
|
|
1373
|
+
throw new Error("Profile name is required. Use --built-in for official Anthropic models.");
|
|
1374
|
+
}
|
|
1237
1375
|
if (!data.profiles[name]) {
|
|
1238
1376
|
throw new Error(`Profile '${name}' not found.`);
|
|
1239
1377
|
}
|
|
@@ -1246,10 +1384,14 @@ function useCommand() {
|
|
|
1246
1384
|
}));
|
|
1247
1385
|
}
|
|
1248
1386
|
function runCommand() {
|
|
1249
|
-
return new Command2("run").description("Launch Claude Code using the default or a specified profile").allowUnknownOption().argument("[args...]", "Optional profile name followed by extra arguments").action(safeAction((args) => {
|
|
1387
|
+
return new Command2("run").description("Launch Claude Code using the default or a specified profile").option("--built-in", "Use official Anthropic models (no custom profile)").allowUnknownOption().argument("[args...]", "Optional profile name followed by extra arguments").action(safeAction((args, opts) => {
|
|
1250
1388
|
fixJsonFile(CLAUDE_JSON);
|
|
1251
1389
|
ensureProfilesFile();
|
|
1252
1390
|
const data = readJson(PROFILES_FILE);
|
|
1391
|
+
if (opts.builtIn) {
|
|
1392
|
+
execClaudeBuiltIn(args);
|
|
1393
|
+
return;
|
|
1394
|
+
}
|
|
1253
1395
|
let profileName = "";
|
|
1254
1396
|
let claudeArgs;
|
|
1255
1397
|
if (args.length > 0 && data.profiles[args[0]]) {
|
|
@@ -1259,8 +1401,12 @@ function runCommand() {
|
|
|
1259
1401
|
profileName = data.default || "";
|
|
1260
1402
|
claudeArgs = args;
|
|
1261
1403
|
}
|
|
1404
|
+
if (profileName === BUILT_IN_DEFAULT) {
|
|
1405
|
+
execClaudeBuiltIn(claudeArgs);
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1262
1408
|
if (!profileName) {
|
|
1263
|
-
throw new Error("No default profile set. Use 'cc-hub use <name>' first.");
|
|
1409
|
+
throw new Error("No default profile set. Use 'cc-hub use <name>' or 'cc-hub use --built-in' first.");
|
|
1264
1410
|
}
|
|
1265
1411
|
const p = data.profiles[profileName];
|
|
1266
1412
|
debug(`run: launching claude with profile '${profileName}', args=[${claudeArgs.join(", ")}]`);
|
|
@@ -1480,7 +1626,7 @@ function encodePath(p) {
|
|
|
1480
1626
|
debug(`codec: encode "${p}" -> "${encoded}"`);
|
|
1481
1627
|
return encoded;
|
|
1482
1628
|
}
|
|
1483
|
-
function
|
|
1629
|
+
function decodePath2(encoded) {
|
|
1484
1630
|
const decoded = createPathCodec().decode(encoded);
|
|
1485
1631
|
debug(`codec: decode "${encoded}" -> "${decoded}"`);
|
|
1486
1632
|
return decoded;
|
|
@@ -1596,11 +1742,54 @@ function snippet(text, query, width = 150) {
|
|
|
1596
1742
|
const suffix = end < text.length ? "..." : "";
|
|
1597
1743
|
return prefix + text.slice(start, end) + suffix;
|
|
1598
1744
|
}
|
|
1745
|
+
function findSessionFile(sessionQuery, projectQuery) {
|
|
1746
|
+
debug(`sessions: findSessionFile query="${sessionQuery}" project=${projectQuery || "(any)"}`);
|
|
1747
|
+
let searchDirs = [];
|
|
1748
|
+
if (projectQuery) {
|
|
1749
|
+
const projDir = findProjectDir(projectQuery);
|
|
1750
|
+
if (!projDir) {
|
|
1751
|
+
throw new Error(`No project matched: ${projectQuery}`);
|
|
1752
|
+
}
|
|
1753
|
+
searchDirs.push(path6.join(PROJECTS_DIR, projDir));
|
|
1754
|
+
} else {
|
|
1755
|
+
try {
|
|
1756
|
+
searchDirs = fs6.readdirSync(PROJECTS_DIR).map((d) => path6.join(PROJECTS_DIR, d));
|
|
1757
|
+
} catch {
|
|
1758
|
+
throw new Error(`No projects directory found at ${PROJECTS_DIR}`);
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
const matches = [];
|
|
1762
|
+
for (const dir of searchDirs) {
|
|
1763
|
+
let files;
|
|
1764
|
+
try {
|
|
1765
|
+
files = fs6.readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
|
|
1766
|
+
} catch {
|
|
1767
|
+
continue;
|
|
1768
|
+
}
|
|
1769
|
+
for (const file of files) {
|
|
1770
|
+
const sessionId = file.replace(/\.jsonl$/, "");
|
|
1771
|
+
if (sessionId.toLowerCase().includes(sessionQuery.toLowerCase())) {
|
|
1772
|
+
matches.push({ filePath: path6.join(dir, file), project: path6.basename(dir) });
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
if (matches.length === 0) {
|
|
1777
|
+
return null;
|
|
1778
|
+
}
|
|
1779
|
+
if (matches.length > 1) {
|
|
1780
|
+
const lines = matches.map((m) => ` ${path6.basename(m.filePath)} in ${decodePath(m.project)}`).join("\n");
|
|
1781
|
+
throw new Error(`Multiple sessions matched '${sessionQuery}':
|
|
1782
|
+
${lines}
|
|
1783
|
+
Use --project to disambiguate.`);
|
|
1784
|
+
}
|
|
1785
|
+
return matches[0];
|
|
1786
|
+
}
|
|
1599
1787
|
|
|
1600
1788
|
// src/sessions/commands.ts
|
|
1601
1789
|
import { Command as Command4 } from "commander";
|
|
1602
1790
|
import fs7 from "fs";
|
|
1603
1791
|
import path7 from "path";
|
|
1792
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
1604
1793
|
function sessionCommand() {
|
|
1605
1794
|
const session = new Command4("session").description("Manage Claude Code sessions");
|
|
1606
1795
|
const desktopApp2 = createDesktopApp();
|
|
@@ -1630,7 +1819,7 @@ function sessionCommand() {
|
|
|
1630
1819
|
} catch {
|
|
1631
1820
|
}
|
|
1632
1821
|
const stat = fs7.statSync(fullPath);
|
|
1633
|
-
const decoded =
|
|
1822
|
+
const decoded = decodePath2(projDir);
|
|
1634
1823
|
if (opts.json) {
|
|
1635
1824
|
console.log(JSON.stringify({ project: decoded, sessions: nSessions, modified: Math.floor(stat.mtimeMs) }));
|
|
1636
1825
|
} else if (opts.short) {
|
|
@@ -1648,7 +1837,7 @@ function sessionCommand() {
|
|
|
1648
1837
|
throw new Error(`No project matched: ${project}`);
|
|
1649
1838
|
}
|
|
1650
1839
|
const fullPath = path7.join(PROJECTS_DIR, projDir);
|
|
1651
|
-
console.log(`Project: ${
|
|
1840
|
+
console.log(`Project: ${decodePath2(projDir)}`);
|
|
1652
1841
|
console.log(`Dir: ${fullPath}`);
|
|
1653
1842
|
console.log("");
|
|
1654
1843
|
const fmt = (sid, name, started, msgs) => `${sid.padEnd(36)} ${name.padEnd(30)} ${started.padEnd(17)} ${msgs}`;
|
|
@@ -1742,7 +1931,7 @@ function sessionCommand() {
|
|
|
1742
1931
|
const relPath = path7.relative(baseDir, fullPath);
|
|
1743
1932
|
const projEnc = relPath.split(path7.sep)[0];
|
|
1744
1933
|
const sessionId = path7.basename(fullPath, ".jsonl");
|
|
1745
|
-
const projName = label ? projEnc :
|
|
1934
|
+
const projName = label ? projEnc : decodePath2(projEnc);
|
|
1746
1935
|
console.log(`${label}[${projName} \u2192 ${sessionId}]`);
|
|
1747
1936
|
found = true;
|
|
1748
1937
|
count++;
|
|
@@ -1928,6 +2117,38 @@ function sessionCommand() {
|
|
|
1928
2117
|
const verb = opts.dryRun ? "Would delete" : "Deleted";
|
|
1929
2118
|
console.log(`${verb} ${deleted} file(s) (~${Math.floor(freed / 1024)}KB freed)`);
|
|
1930
2119
|
}));
|
|
2120
|
+
session.command("troubleshoot").description("Launch Claude Code to troubleshoot a session file").argument("<session>", "Session ID or partial match").option("-i, --interactive", "Open an interactive Claude Code window instead of a one-shot prompt").action(safeAction((sessionId, opts) => {
|
|
2121
|
+
debug(`session troubleshoot: session=${sessionId} interactive=${!!opts.interactive}`);
|
|
2122
|
+
console.log(`Searching for session '${sessionId}'...`);
|
|
2123
|
+
const match = findSessionFile(sessionId);
|
|
2124
|
+
if (!match) {
|
|
2125
|
+
throw new Error(`Session '${sessionId}' not found.`);
|
|
2126
|
+
}
|
|
2127
|
+
if (!fs7.existsSync(match.filePath)) {
|
|
2128
|
+
throw new Error(`Session file no longer exists: ${match.filePath}`);
|
|
2129
|
+
}
|
|
2130
|
+
console.log(`Found session file: ${match.filePath}`);
|
|
2131
|
+
const nodeBinary = process.argv[0];
|
|
2132
|
+
const scriptPath = process.argv[1];
|
|
2133
|
+
let args;
|
|
2134
|
+
const promptText = `Please analyze this Claude Code session file: ${match.filePath}
|
|
2135
|
+
|
|
2136
|
+
The file contains a JSONL conversation history. Review it for any errors, anomalies, or issues (truncated responses, failed tool calls, error messages, corrupted data, etc.). Summarize what happened in the session and identify any problems that need attention. If the file is very large, focus on the most recent turns and any lines containing "error", "exception", "failed", or non-JSON content.`;
|
|
2137
|
+
if (opts.interactive) {
|
|
2138
|
+
console.log("Launching Claude Code (interactive)...");
|
|
2139
|
+
info(`session troubleshoot: launching cc-hub run (interactive) for ${match.filePath}`);
|
|
2140
|
+
args = ["run", promptText];
|
|
2141
|
+
} else {
|
|
2142
|
+
console.log("Launching Claude Code with prompt...");
|
|
2143
|
+
info(`session troubleshoot: launching cc-hub run -p "${promptText}"`);
|
|
2144
|
+
args = ["run", "-p", promptText];
|
|
2145
|
+
}
|
|
2146
|
+
const result = spawnSync3(nodeBinary, [scriptPath, ...args], {
|
|
2147
|
+
stdio: "inherit",
|
|
2148
|
+
shell: process.platform === "win32"
|
|
2149
|
+
});
|
|
2150
|
+
process.exit(result.status ?? 1);
|
|
2151
|
+
}));
|
|
1931
2152
|
return session;
|
|
1932
2153
|
}
|
|
1933
2154
|
|
|
@@ -1941,7 +2162,7 @@ _cc-hub() {
|
|
|
1941
2162
|
local -a commands
|
|
1942
2163
|
commands=(
|
|
1943
2164
|
'profile:Manage Claude CLI profiles'
|
|
1944
|
-
'use:
|
|
2165
|
+
'use:Set a profile as the default'
|
|
1945
2166
|
'run:Launch Claude Code using the default or a specified profile'
|
|
1946
2167
|
'hook:Manage Claude Code hooks in settings.json'
|
|
1947
2168
|
'session:Manage Claude Code sessions'
|
|
@@ -1985,6 +2206,7 @@ _cc-hub() {
|
|
|
1985
2206
|
'ps:Show active Claude Code processes'
|
|
1986
2207
|
'stats:Show summary statistics'
|
|
1987
2208
|
'clean:Delete session JSONL files older than N days'
|
|
2209
|
+
'troubleshoot:Launch Claude Code to troubleshoot a session file'
|
|
1988
2210
|
)
|
|
1989
2211
|
|
|
1990
2212
|
_cc_hub_profiles() {
|
|
@@ -2019,8 +2241,10 @@ _cc-hub() {
|
|
|
2019
2241
|
profile)
|
|
2020
2242
|
if (( CURRENT == 2 )); then
|
|
2021
2243
|
_describe -t profile-subcmds 'profile subcommand' profile_subcmds
|
|
2022
|
-
elif [[ $words[2] == "view" || $words[2] == "remove"
|
|
2244
|
+
elif [[ $words[2] == "view" || $words[2] == "remove" ]]; then
|
|
2023
2245
|
_cc_hub_profiles
|
|
2246
|
+
elif [[ $words[2] == "default" ]]; then
|
|
2247
|
+
_arguments -C -S '--built-in[Use official Anthropic models as default]' '*:profile:_cc_hub_profiles'
|
|
2024
2248
|
elif [[ $words[2] == "rename" ]]; then
|
|
2025
2249
|
if (( CURRENT == 3 )); then
|
|
2026
2250
|
_cc_hub_profiles
|
|
@@ -2052,7 +2276,7 @@ _cc-hub() {
|
|
|
2052
2276
|
fi
|
|
2053
2277
|
;;
|
|
2054
2278
|
use|run)
|
|
2055
|
-
_cc_hub_profiles
|
|
2279
|
+
_arguments -C -S '--built-in[Use official Anthropic models (no custom profile)]' '*:profile:_cc_hub_profiles'
|
|
2056
2280
|
;;
|
|
2057
2281
|
hook)
|
|
2058
2282
|
if (( CURRENT == 2 )); then
|
|
@@ -2062,6 +2286,8 @@ _cc-hub() {
|
|
|
2062
2286
|
session)
|
|
2063
2287
|
if (( CURRENT == 2 )); then
|
|
2064
2288
|
_describe -t session-subcmds 'session subcommand' session_subcmds
|
|
2289
|
+
elif [[ $words[2] == "troubleshoot" ]]; then
|
|
2290
|
+
_arguments -C -S '(-i --interactive)'{-i,--interactive}'[Open an interactive Claude Code window instead of a one-shot prompt]'
|
|
2065
2291
|
fi
|
|
2066
2292
|
;;
|
|
2067
2293
|
esac
|
|
@@ -2073,20 +2299,22 @@ compdef _cc-hub cc-hub
|
|
|
2073
2299
|
`;
|
|
2074
2300
|
|
|
2075
2301
|
// src/complete/bash.ts
|
|
2076
|
-
var BASH_COMPLETION = `_cc-
|
|
2302
|
+
var BASH_COMPLETION = `_cc-hub_profile_names() {
|
|
2077
2303
|
local profiles_file="\${CLAUDE_PROFILES_FILE:-$HOME/.claude/profiles.json}"
|
|
2078
2304
|
if [[ -f "$profiles_file" ]]; then
|
|
2079
|
-
|
|
2080
|
-
names=$(command python3 -c "
|
|
2305
|
+
command python3 -c "
|
|
2081
2306
|
import json
|
|
2082
2307
|
data = json.load(open('$profiles_file'))
|
|
2083
2308
|
for name in data.get('profiles', {}):
|
|
2084
2309
|
print(name)
|
|
2085
|
-
" 2>/dev/null
|
|
2086
|
-
COMPREPLY=($(compgen -W "$names" -- "\${cur}"))
|
|
2310
|
+
" 2>/dev/null
|
|
2087
2311
|
fi
|
|
2088
2312
|
}
|
|
2089
2313
|
|
|
2314
|
+
_cc-hub_profiles() {
|
|
2315
|
+
COMPREPLY=($(compgen -W "$(_cc-hub_profile_names)" -- "\${cur}"))
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2090
2318
|
_cc-hub_models_for_profile() {
|
|
2091
2319
|
local profile_name="$1"
|
|
2092
2320
|
local profiles_file="\${CLAUDE_PROFILES_FILE:-$HOME/.claude/profiles.json}"
|
|
@@ -2121,7 +2349,7 @@ _cc-hub() {
|
|
|
2121
2349
|
local provider_subcmds="list"
|
|
2122
2350
|
local provider_types="anthropic openai"
|
|
2123
2351
|
local hooks_subcmds="list add remove enable disable"
|
|
2124
|
-
local session_subcmds="list show search ps stats clean"
|
|
2352
|
+
local session_subcmds="list show search ps stats clean troubleshoot"
|
|
2125
2353
|
|
|
2126
2354
|
# Top-level command
|
|
2127
2355
|
if [[ \${COMP_CWORD} -eq 1 ]]; then
|
|
@@ -2135,8 +2363,10 @@ _cc-hub() {
|
|
|
2135
2363
|
profile)
|
|
2136
2364
|
if [[ \${COMP_CWORD} -eq 2 ]]; then
|
|
2137
2365
|
COMPREPLY=($(compgen -W "$profile_subcmds" -- "$cur"))
|
|
2138
|
-
elif [[ "$prev" == "view" || "$prev" == "remove"
|
|
2366
|
+
elif [[ "$prev" == "view" || "$prev" == "remove" ]]; then
|
|
2139
2367
|
_cc-hub_profiles
|
|
2368
|
+
elif [[ "$prev" == "default" ]]; then
|
|
2369
|
+
COMPREPLY=($(compgen -W "--built-in $(_cc-hub_profile_names)" -- "$cur"))
|
|
2140
2370
|
elif [[ "$prev" == "rename" ]]; then
|
|
2141
2371
|
_cc-hub_profiles
|
|
2142
2372
|
elif [[ "$prev" == "profile" ]]; then
|
|
@@ -2162,7 +2392,11 @@ _cc-hub() {
|
|
|
2162
2392
|
fi
|
|
2163
2393
|
;;
|
|
2164
2394
|
use|run)
|
|
2165
|
-
|
|
2395
|
+
if [[ "$prev" == "--built-in" ]]; then
|
|
2396
|
+
:
|
|
2397
|
+
else
|
|
2398
|
+
COMPREPLY=($(compgen -W "--built-in $(_cc-hub_profile_names)" -- "$cur"))
|
|
2399
|
+
fi
|
|
2166
2400
|
;;
|
|
2167
2401
|
hook)
|
|
2168
2402
|
if [[ \${COMP_CWORD} -eq 2 ]]; then
|
|
@@ -2172,6 +2406,13 @@ _cc-hub() {
|
|
|
2172
2406
|
session)
|
|
2173
2407
|
if [[ \${COMP_CWORD} -eq 2 ]]; then
|
|
2174
2408
|
COMPREPLY=($(compgen -W "$session_subcmds" -- "$cur"))
|
|
2409
|
+
elif [[ "\${COMP_WORDS[2]}" == "troubleshoot" ]]; then
|
|
2410
|
+
if [[ "$prev" == "--interactive" || "$prev" == "-i" ]]; then
|
|
2411
|
+
:
|
|
2412
|
+
else
|
|
2413
|
+
local troubleshoot_opts="--interactive -i"
|
|
2414
|
+
COMPREPLY=($(compgen -W "$troubleshoot_opts" -- "$cur"))
|
|
2415
|
+
fi
|
|
2175
2416
|
fi
|
|
2176
2417
|
;;
|
|
2177
2418
|
esac
|
|
@@ -2199,7 +2440,7 @@ var POWERSHELL_COMPLETION = `Register-ArgumentCompleter -Native -CommandName cc-
|
|
|
2199
2440
|
|
|
2200
2441
|
$profileSubcmds = @('add', 'update', 'list', 'view', 'remove', 'rename', 'default', 'sync')
|
|
2201
2442
|
$hookSubcmds = @('list', 'add', 'remove', 'enable', 'disable')
|
|
2202
|
-
$sessionSubcmds = @('list', 'show', 'search', 'ps', 'stats', 'clean')
|
|
2443
|
+
$sessionSubcmds = @('list', 'show', 'search', 'ps', 'stats', 'clean', 'troubleshoot')
|
|
2203
2444
|
$providerSubcmds = @('list')
|
|
2204
2445
|
|
|
2205
2446
|
$tokens = $commandAst.CommandElements | ForEach-Object { $_.ToString() }
|
|
@@ -2217,6 +2458,10 @@ var POWERSHELL_COMPLETION = `Register-ArgumentCompleter -Native -CommandName cc-
|
|
|
2217
2458
|
$profileSubcmds | ForEach-Object { if ($_ -like "$wordToComplete*") { $_ } }
|
|
2218
2459
|
return
|
|
2219
2460
|
}
|
|
2461
|
+
if ($tokens[2] -eq 'default' -and $tokens.Count -ge 3) {
|
|
2462
|
+
$opts = @('--built-in')
|
|
2463
|
+
$opts | ForEach-Object { if ($_ -like "$wordToComplete*") { $_ } }
|
|
2464
|
+
}
|
|
2220
2465
|
}
|
|
2221
2466
|
'hook' {
|
|
2222
2467
|
if ($tokens.Count -eq 2 -or ($tokens.Count -eq 3 -and $wordToComplete -ne '')) {
|
|
@@ -2236,6 +2481,16 @@ var POWERSHELL_COMPLETION = `Register-ArgumentCompleter -Native -CommandName cc-
|
|
|
2236
2481
|
return
|
|
2237
2482
|
}
|
|
2238
2483
|
}
|
|
2484
|
+
'use' {
|
|
2485
|
+
$opts = @('--built-in')
|
|
2486
|
+
$opts | ForEach-Object { if ($_ -like "$wordToComplete*") { $_ } }
|
|
2487
|
+
return
|
|
2488
|
+
}
|
|
2489
|
+
'run' {
|
|
2490
|
+
$opts = @('--built-in')
|
|
2491
|
+
$opts | ForEach-Object { if ($_ -like "$wordToComplete*") { $_ } }
|
|
2492
|
+
return
|
|
2493
|
+
}
|
|
2239
2494
|
}
|
|
2240
2495
|
}`;
|
|
2241
2496
|
|
|
@@ -2275,6 +2530,7 @@ program.addCommand(hooksCommand());
|
|
|
2275
2530
|
program.addCommand(sessionCommand());
|
|
2276
2531
|
program.addCommand(completionCommand());
|
|
2277
2532
|
program.addCommand(providerCommand());
|
|
2533
|
+
program.addCommand(proxyCommand());
|
|
2278
2534
|
try {
|
|
2279
2535
|
program.parse();
|
|
2280
2536
|
} catch (err) {
|