pennyrouter 0.2.8 → 0.2.10
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/LICENSE +21 -21
- package/README.md +225 -225
- package/bin/penny.js +9 -9
- package/bin/pennyrouter.js +7 -7
- package/package.json +30 -30
- package/src/cli.js +1774 -1727
- package/src/harnesses/aider.js +48 -48
- package/src/harnesses/claude-code.js +322 -318
- package/src/harnesses/claude-code.test.js +57 -57
- package/src/harnesses/cline.js +77 -77
- package/src/harnesses/codegpt.js +76 -76
- package/src/harnesses/codex.js +180 -180
- package/src/harnesses/codex.test.js +119 -119
- package/src/harnesses/opencode.js +151 -151
- package/src/harnesses/shared.js +51 -51
- package/src/harnesses/windsurf.js +57 -57
- package/src/harnesses/zed.js +61 -61
- package/src/launch.js +487 -467
- package/src/launch.test.js +170 -153
- package/src/mcp.js +228 -0
- package/src/mcp.test.js +182 -0
- package/src/session.js +157 -157
- package/src/session.test.js +44 -44
- package/src/state.js +103 -103
- package/src/statusline.js +202 -202
- package/src/statusline.test.js +72 -72
- package/src/thread-session.js +204 -0
- package/src/thread-session.test.js +140 -0
|
@@ -1,57 +1,57 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
disableClaudeAiConnectors,
|
|
5
|
-
restoreClaudeAiConnectors,
|
|
6
|
-
} from "./claude-code.js";
|
|
7
|
-
|
|
8
|
-
function testAbsentSettingIsRemovedOnUninstall() {
|
|
9
|
-
const settings = {};
|
|
10
|
-
const before = disableClaudeAiConnectors(settings);
|
|
11
|
-
|
|
12
|
-
assert.equal(settings.disableClaudeAiConnectors, true);
|
|
13
|
-
assert.deepEqual(before, { present: false });
|
|
14
|
-
|
|
15
|
-
restoreClaudeAiConnectors(settings, { claude_ai_connectors_before: before });
|
|
16
|
-
assert.equal("disableClaudeAiConnectors" in settings, false);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function testExistingSettingIsRestored() {
|
|
20
|
-
const settings = { disableClaudeAiConnectors: false };
|
|
21
|
-
const before = disableClaudeAiConnectors(settings);
|
|
22
|
-
|
|
23
|
-
assert.equal(settings.disableClaudeAiConnectors, true);
|
|
24
|
-
assert.deepEqual(before, { present: true, value: false });
|
|
25
|
-
|
|
26
|
-
restoreClaudeAiConnectors(settings, { claude_ai_connectors_before: before });
|
|
27
|
-
assert.equal(settings.disableClaudeAiConnectors, false);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function testReinstallKeepsOriginalValue() {
|
|
31
|
-
const settings = {};
|
|
32
|
-
const firstBefore = disableClaudeAiConnectors(settings);
|
|
33
|
-
const secondBefore = disableClaudeAiConnectors(settings, {
|
|
34
|
-
claude_ai_connectors_before: firstBefore,
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
assert.deepEqual(secondBefore, { present: false });
|
|
38
|
-
restoreClaudeAiConnectors(settings, {
|
|
39
|
-
claude_ai_connectors_before: secondBefore,
|
|
40
|
-
});
|
|
41
|
-
assert.equal("disableClaudeAiConnectors" in settings, false);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function testUninstallPreservesLaterUserEdit() {
|
|
45
|
-
const settings = {};
|
|
46
|
-
const before = disableClaudeAiConnectors(settings);
|
|
47
|
-
settings.disableClaudeAiConnectors = false;
|
|
48
|
-
|
|
49
|
-
restoreClaudeAiConnectors(settings, { claude_ai_connectors_before: before });
|
|
50
|
-
assert.equal(settings.disableClaudeAiConnectors, false);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
testAbsentSettingIsRemovedOnUninstall();
|
|
54
|
-
testExistingSettingIsRestored();
|
|
55
|
-
testReinstallKeepsOriginalValue();
|
|
56
|
-
testUninstallPreservesLaterUserEdit();
|
|
57
|
-
console.log("claude-code.test.js: all checks passed");
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
disableClaudeAiConnectors,
|
|
5
|
+
restoreClaudeAiConnectors,
|
|
6
|
+
} from "./claude-code.js";
|
|
7
|
+
|
|
8
|
+
function testAbsentSettingIsRemovedOnUninstall() {
|
|
9
|
+
const settings = {};
|
|
10
|
+
const before = disableClaudeAiConnectors(settings);
|
|
11
|
+
|
|
12
|
+
assert.equal(settings.disableClaudeAiConnectors, true);
|
|
13
|
+
assert.deepEqual(before, { present: false });
|
|
14
|
+
|
|
15
|
+
restoreClaudeAiConnectors(settings, { claude_ai_connectors_before: before });
|
|
16
|
+
assert.equal("disableClaudeAiConnectors" in settings, false);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function testExistingSettingIsRestored() {
|
|
20
|
+
const settings = { disableClaudeAiConnectors: false };
|
|
21
|
+
const before = disableClaudeAiConnectors(settings);
|
|
22
|
+
|
|
23
|
+
assert.equal(settings.disableClaudeAiConnectors, true);
|
|
24
|
+
assert.deepEqual(before, { present: true, value: false });
|
|
25
|
+
|
|
26
|
+
restoreClaudeAiConnectors(settings, { claude_ai_connectors_before: before });
|
|
27
|
+
assert.equal(settings.disableClaudeAiConnectors, false);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function testReinstallKeepsOriginalValue() {
|
|
31
|
+
const settings = {};
|
|
32
|
+
const firstBefore = disableClaudeAiConnectors(settings);
|
|
33
|
+
const secondBefore = disableClaudeAiConnectors(settings, {
|
|
34
|
+
claude_ai_connectors_before: firstBefore,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
assert.deepEqual(secondBefore, { present: false });
|
|
38
|
+
restoreClaudeAiConnectors(settings, {
|
|
39
|
+
claude_ai_connectors_before: secondBefore,
|
|
40
|
+
});
|
|
41
|
+
assert.equal("disableClaudeAiConnectors" in settings, false);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function testUninstallPreservesLaterUserEdit() {
|
|
45
|
+
const settings = {};
|
|
46
|
+
const before = disableClaudeAiConnectors(settings);
|
|
47
|
+
settings.disableClaudeAiConnectors = false;
|
|
48
|
+
|
|
49
|
+
restoreClaudeAiConnectors(settings, { claude_ai_connectors_before: before });
|
|
50
|
+
assert.equal(settings.disableClaudeAiConnectors, false);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
testAbsentSettingIsRemovedOnUninstall();
|
|
54
|
+
testExistingSettingIsRestored();
|
|
55
|
+
testReinstallKeepsOriginalValue();
|
|
56
|
+
testUninstallPreservesLaterUserEdit();
|
|
57
|
+
console.log("claude-code.test.js: all checks passed");
|
package/src/harnesses/cline.js
CHANGED
|
@@ -1,77 +1,77 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
|
-
|
|
3
|
-
export const clineHarness = {
|
|
4
|
-
id: "cline",
|
|
5
|
-
name: "Cline",
|
|
6
|
-
|
|
7
|
-
async detect() {
|
|
8
|
-
return false;
|
|
9
|
-
},
|
|
10
|
-
|
|
11
|
-
async planInstall() {
|
|
12
|
-
return {
|
|
13
|
-
configPath: null,
|
|
14
|
-
summary: [
|
|
15
|
-
"show manual OpenAI Compatible setup instructions",
|
|
16
|
-
"do not write VS Code settings or extension storage",
|
|
17
|
-
"record that manual Cline setup instructions were shown",
|
|
18
|
-
],
|
|
19
|
-
};
|
|
20
|
-
},
|
|
21
|
-
|
|
22
|
-
async install({ apiKey, gatewayBaseUrl }) {
|
|
23
|
-
const copied = copyToClipboard(apiKey);
|
|
24
|
-
|
|
25
|
-
console.log("");
|
|
26
|
-
console.log("Cline manual setup");
|
|
27
|
-
console.log(" Open Cline settings in VS Code and set:");
|
|
28
|
-
console.log(" - API Provider: OpenAI Compatible");
|
|
29
|
-
console.log(` - Base URL: ${gatewayBaseUrl}/v1`);
|
|
30
|
-
console.log(` - API Key: ${copied ? "copied to clipboard" : "copy from your PennyRouter dashboard"}`);
|
|
31
|
-
console.log(" - Model ID: pennyrouter/auto (Penny Custom)");
|
|
32
|
-
console.log("");
|
|
33
|
-
console.log("The CLI did not write VS Code settings because Cline stores provider config differently across versions.");
|
|
34
|
-
if (!copied) {
|
|
35
|
-
console.log("Clipboard copy failed, so the key was not printed. Open PennyRouter and create or reveal a key manually for Cline.");
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return {
|
|
39
|
-
harness: "cline",
|
|
40
|
-
config_path: null,
|
|
41
|
-
backup_path: null,
|
|
42
|
-
installed_at: new Date().toISOString(),
|
|
43
|
-
manual_required: true,
|
|
44
|
-
restart: "save Cline settings, then restart VS Code or reload the Cline window if it does not pick up the provider",
|
|
45
|
-
changes: {
|
|
46
|
-
provider: "OpenAI Compatible",
|
|
47
|
-
base_url: `${gatewayBaseUrl}/v1`,
|
|
48
|
-
model: "pennyrouter/auto",
|
|
49
|
-
model_name: "Penny Custom",
|
|
50
|
-
},
|
|
51
|
-
};
|
|
52
|
-
},
|
|
53
|
-
|
|
54
|
-
async uninstall() {
|
|
55
|
-
console.log("");
|
|
56
|
-
console.log("Cline manual uninstall");
|
|
57
|
-
console.log(" Open Cline settings in VS Code and remove or replace the PennyRouter OpenAI Compatible provider:");
|
|
58
|
-
console.log(" - Base URL: https://api.pennyrouter.com/v1");
|
|
59
|
-
console.log(" - API Key: pr-...");
|
|
60
|
-
console.log(" - Model ID: pennyrouter/auto");
|
|
61
|
-
},
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
function copyToClipboard(text) {
|
|
65
|
-
const commands =
|
|
66
|
-
process.platform === "darwin"
|
|
67
|
-
? [["pbcopy"]]
|
|
68
|
-
: process.platform === "win32"
|
|
69
|
-
? [["clip"]]
|
|
70
|
-
: [["xclip", "-selection", "clipboard"], ["xsel", "--clipboard", "--input"]];
|
|
71
|
-
|
|
72
|
-
for (const [command, ...args] of commands) {
|
|
73
|
-
const result = spawnSync(command, args, { input: text, stdio: ["pipe", "ignore", "ignore"] });
|
|
74
|
-
if (result.status === 0) return true;
|
|
75
|
-
}
|
|
76
|
-
return false;
|
|
77
|
-
}
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
export const clineHarness = {
|
|
4
|
+
id: "cline",
|
|
5
|
+
name: "Cline",
|
|
6
|
+
|
|
7
|
+
async detect() {
|
|
8
|
+
return false;
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
async planInstall() {
|
|
12
|
+
return {
|
|
13
|
+
configPath: null,
|
|
14
|
+
summary: [
|
|
15
|
+
"show manual OpenAI Compatible setup instructions",
|
|
16
|
+
"do not write VS Code settings or extension storage",
|
|
17
|
+
"record that manual Cline setup instructions were shown",
|
|
18
|
+
],
|
|
19
|
+
};
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
async install({ apiKey, gatewayBaseUrl }) {
|
|
23
|
+
const copied = copyToClipboard(apiKey);
|
|
24
|
+
|
|
25
|
+
console.log("");
|
|
26
|
+
console.log("Cline manual setup");
|
|
27
|
+
console.log(" Open Cline settings in VS Code and set:");
|
|
28
|
+
console.log(" - API Provider: OpenAI Compatible");
|
|
29
|
+
console.log(` - Base URL: ${gatewayBaseUrl}/v1`);
|
|
30
|
+
console.log(` - API Key: ${copied ? "copied to clipboard" : "copy from your PennyRouter dashboard"}`);
|
|
31
|
+
console.log(" - Model ID: pennyrouter/auto (Penny Custom)");
|
|
32
|
+
console.log("");
|
|
33
|
+
console.log("The CLI did not write VS Code settings because Cline stores provider config differently across versions.");
|
|
34
|
+
if (!copied) {
|
|
35
|
+
console.log("Clipboard copy failed, so the key was not printed. Open PennyRouter and create or reveal a key manually for Cline.");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
harness: "cline",
|
|
40
|
+
config_path: null,
|
|
41
|
+
backup_path: null,
|
|
42
|
+
installed_at: new Date().toISOString(),
|
|
43
|
+
manual_required: true,
|
|
44
|
+
restart: "save Cline settings, then restart VS Code or reload the Cline window if it does not pick up the provider",
|
|
45
|
+
changes: {
|
|
46
|
+
provider: "OpenAI Compatible",
|
|
47
|
+
base_url: `${gatewayBaseUrl}/v1`,
|
|
48
|
+
model: "pennyrouter/auto",
|
|
49
|
+
model_name: "Penny Custom",
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
async uninstall() {
|
|
55
|
+
console.log("");
|
|
56
|
+
console.log("Cline manual uninstall");
|
|
57
|
+
console.log(" Open Cline settings in VS Code and remove or replace the PennyRouter OpenAI Compatible provider:");
|
|
58
|
+
console.log(" - Base URL: https://api.pennyrouter.com/v1");
|
|
59
|
+
console.log(" - API Key: pr-...");
|
|
60
|
+
console.log(" - Model ID: pennyrouter/auto");
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
function copyToClipboard(text) {
|
|
65
|
+
const commands =
|
|
66
|
+
process.platform === "darwin"
|
|
67
|
+
? [["pbcopy"]]
|
|
68
|
+
: process.platform === "win32"
|
|
69
|
+
? [["clip"]]
|
|
70
|
+
: [["xclip", "-selection", "clipboard"], ["xsel", "--clipboard", "--input"]];
|
|
71
|
+
|
|
72
|
+
for (const [command, ...args] of commands) {
|
|
73
|
+
const result = spawnSync(command, args, { input: text, stdio: ["pipe", "ignore", "ignore"] });
|
|
74
|
+
if (result.status === 0) return true;
|
|
75
|
+
}
|
|
76
|
+
return false;
|
|
77
|
+
}
|
package/src/harnesses/codegpt.js
CHANGED
|
@@ -1,76 +1,76 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
|
-
|
|
3
|
-
export const codeGptHarness = {
|
|
4
|
-
id: "codegpt",
|
|
5
|
-
name: "JetBrains CodeGPT",
|
|
6
|
-
|
|
7
|
-
async detect() {
|
|
8
|
-
return false;
|
|
9
|
-
},
|
|
10
|
-
|
|
11
|
-
async planInstall() {
|
|
12
|
-
return {
|
|
13
|
-
configPath: null,
|
|
14
|
-
summary: [
|
|
15
|
-
"show manual CodeGPT OpenAI-compatible setup instructions",
|
|
16
|
-
"do not mutate JetBrains XML settings until the exact IDE/version schema is confirmed",
|
|
17
|
-
"record that manual CodeGPT setup instructions were shown",
|
|
18
|
-
],
|
|
19
|
-
};
|
|
20
|
-
},
|
|
21
|
-
|
|
22
|
-
async install({ apiKey, gatewayBaseUrl }) {
|
|
23
|
-
const copied = copyToClipboard(apiKey);
|
|
24
|
-
|
|
25
|
-
console.log("");
|
|
26
|
-
console.log("JetBrains CodeGPT manual setup");
|
|
27
|
-
console.log(" Open CodeGPT settings in your JetBrains IDE and set:");
|
|
28
|
-
console.log(` - Base URL: ${gatewayBaseUrl}/v1`);
|
|
29
|
-
console.log(` - API Key: ${copied ? "copied to clipboard" : "copy from your PennyRouter dashboard"}`);
|
|
30
|
-
console.log(" - Model: pennyrouter/auto (Penny Custom)");
|
|
31
|
-
console.log("");
|
|
32
|
-
console.log("The CLI did not write JetBrains XML settings because CodeGPT stores options under IDE/version-specific paths.");
|
|
33
|
-
if (!copied) {
|
|
34
|
-
console.log("Clipboard copy failed, so the key was not printed. Open PennyRouter and create or reveal a key manually for CodeGPT.");
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return {
|
|
38
|
-
harness: "codegpt",
|
|
39
|
-
config_path: null,
|
|
40
|
-
backup_path: null,
|
|
41
|
-
installed_at: new Date().toISOString(),
|
|
42
|
-
manual_required: true,
|
|
43
|
-
restart: "restart the JetBrains IDE after saving CodeGPT settings",
|
|
44
|
-
changes: {
|
|
45
|
-
provider: "OpenAI Compatible",
|
|
46
|
-
base_url: `${gatewayBaseUrl}/v1`,
|
|
47
|
-
model: "pennyrouter/auto",
|
|
48
|
-
model_name: "Penny Custom",
|
|
49
|
-
},
|
|
50
|
-
};
|
|
51
|
-
},
|
|
52
|
-
|
|
53
|
-
async uninstall() {
|
|
54
|
-
console.log("");
|
|
55
|
-
console.log("JetBrains CodeGPT manual uninstall");
|
|
56
|
-
console.log(" Open CodeGPT settings and remove or replace the PennyRouter provider:");
|
|
57
|
-
console.log(" - Base URL: https://api.pennyrouter.com/v1");
|
|
58
|
-
console.log(" - API Key: pr-...");
|
|
59
|
-
console.log(" - Model: pennyrouter/auto");
|
|
60
|
-
},
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
function copyToClipboard(text) {
|
|
64
|
-
const commands =
|
|
65
|
-
process.platform === "darwin"
|
|
66
|
-
? [["pbcopy"]]
|
|
67
|
-
: process.platform === "win32"
|
|
68
|
-
? [["clip"]]
|
|
69
|
-
: [["xclip", "-selection", "clipboard"], ["xsel", "--clipboard", "--input"]];
|
|
70
|
-
|
|
71
|
-
for (const [command, ...args] of commands) {
|
|
72
|
-
const result = spawnSync(command, args, { input: text, stdio: ["pipe", "ignore", "ignore"] });
|
|
73
|
-
if (result.status === 0) return true;
|
|
74
|
-
}
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
export const codeGptHarness = {
|
|
4
|
+
id: "codegpt",
|
|
5
|
+
name: "JetBrains CodeGPT",
|
|
6
|
+
|
|
7
|
+
async detect() {
|
|
8
|
+
return false;
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
async planInstall() {
|
|
12
|
+
return {
|
|
13
|
+
configPath: null,
|
|
14
|
+
summary: [
|
|
15
|
+
"show manual CodeGPT OpenAI-compatible setup instructions",
|
|
16
|
+
"do not mutate JetBrains XML settings until the exact IDE/version schema is confirmed",
|
|
17
|
+
"record that manual CodeGPT setup instructions were shown",
|
|
18
|
+
],
|
|
19
|
+
};
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
async install({ apiKey, gatewayBaseUrl }) {
|
|
23
|
+
const copied = copyToClipboard(apiKey);
|
|
24
|
+
|
|
25
|
+
console.log("");
|
|
26
|
+
console.log("JetBrains CodeGPT manual setup");
|
|
27
|
+
console.log(" Open CodeGPT settings in your JetBrains IDE and set:");
|
|
28
|
+
console.log(` - Base URL: ${gatewayBaseUrl}/v1`);
|
|
29
|
+
console.log(` - API Key: ${copied ? "copied to clipboard" : "copy from your PennyRouter dashboard"}`);
|
|
30
|
+
console.log(" - Model: pennyrouter/auto (Penny Custom)");
|
|
31
|
+
console.log("");
|
|
32
|
+
console.log("The CLI did not write JetBrains XML settings because CodeGPT stores options under IDE/version-specific paths.");
|
|
33
|
+
if (!copied) {
|
|
34
|
+
console.log("Clipboard copy failed, so the key was not printed. Open PennyRouter and create or reveal a key manually for CodeGPT.");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
harness: "codegpt",
|
|
39
|
+
config_path: null,
|
|
40
|
+
backup_path: null,
|
|
41
|
+
installed_at: new Date().toISOString(),
|
|
42
|
+
manual_required: true,
|
|
43
|
+
restart: "restart the JetBrains IDE after saving CodeGPT settings",
|
|
44
|
+
changes: {
|
|
45
|
+
provider: "OpenAI Compatible",
|
|
46
|
+
base_url: `${gatewayBaseUrl}/v1`,
|
|
47
|
+
model: "pennyrouter/auto",
|
|
48
|
+
model_name: "Penny Custom",
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
async uninstall() {
|
|
54
|
+
console.log("");
|
|
55
|
+
console.log("JetBrains CodeGPT manual uninstall");
|
|
56
|
+
console.log(" Open CodeGPT settings and remove or replace the PennyRouter provider:");
|
|
57
|
+
console.log(" - Base URL: https://api.pennyrouter.com/v1");
|
|
58
|
+
console.log(" - API Key: pr-...");
|
|
59
|
+
console.log(" - Model: pennyrouter/auto");
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
function copyToClipboard(text) {
|
|
64
|
+
const commands =
|
|
65
|
+
process.platform === "darwin"
|
|
66
|
+
? [["pbcopy"]]
|
|
67
|
+
: process.platform === "win32"
|
|
68
|
+
? [["clip"]]
|
|
69
|
+
: [["xclip", "-selection", "clipboard"], ["xsel", "--clipboard", "--input"]];
|
|
70
|
+
|
|
71
|
+
for (const [command, ...args] of commands) {
|
|
72
|
+
const result = spawnSync(command, args, { input: text, stdio: ["pipe", "ignore", "ignore"] });
|
|
73
|
+
if (result.status === 0) return true;
|
|
74
|
+
}
|
|
75
|
+
return false;
|
|
76
|
+
}
|