codemaxxing 0.1.3 → 0.1.5
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/dist/auth-cli.d.ts +16 -0
- package/dist/auth-cli.js +268 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +32 -0
- package/dist/config.d.ts +9 -0
- package/dist/config.js +27 -0
- package/dist/index.js +98 -1
- package/dist/utils/auth.d.ts +59 -0
- package/dist/utils/auth.js +495 -0
- package/package.json +3 -2
- package/src/auth-cli.ts +289 -0
- package/src/cli.ts +39 -0
- package/src/config.ts +33 -0
- package/src/index.tsx +93 -1
- package/src/utils/auth.ts +606 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* codemaxxing auth CLI
|
|
4
|
+
*
|
|
5
|
+
* Commands:
|
|
6
|
+
* codemaxxing login — Interactive auth setup
|
|
7
|
+
* codemaxxing auth list — List saved credentials
|
|
8
|
+
* codemaxxing auth remove <name> — Remove a credential
|
|
9
|
+
* codemaxxing auth openrouter — Start OpenRouter OAuth
|
|
10
|
+
* codemaxxing auth anthropic — Get Anthropic via Claude Code
|
|
11
|
+
* codemaxxing auth openai — Import Codex CLI credentials
|
|
12
|
+
* codemaxxing auth qwen — Import Qwen CLI credentials
|
|
13
|
+
* codemaxxing auth copilot — GitHub Copilot device flow
|
|
14
|
+
* codemaxxing auth api-key <name> <key> — Save API key directly
|
|
15
|
+
*/
|
|
16
|
+
export declare function main(): Promise<void>;
|
package/dist/auth-cli.js
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* codemaxxing auth CLI
|
|
4
|
+
*
|
|
5
|
+
* Commands:
|
|
6
|
+
* codemaxxing login — Interactive auth setup
|
|
7
|
+
* codemaxxing auth list — List saved credentials
|
|
8
|
+
* codemaxxing auth remove <name> — Remove a credential
|
|
9
|
+
* codemaxxing auth openrouter — Start OpenRouter OAuth
|
|
10
|
+
* codemaxxing auth anthropic — Get Anthropic via Claude Code
|
|
11
|
+
* codemaxxing auth openai — Import Codex CLI credentials
|
|
12
|
+
* codemaxxing auth qwen — Import Qwen CLI credentials
|
|
13
|
+
* codemaxxing auth copilot — GitHub Copilot device flow
|
|
14
|
+
* codemaxxing auth api-key <name> <key> — Save API key directly
|
|
15
|
+
*/
|
|
16
|
+
import { PROVIDERS, getCredentials, removeCredential, openRouterOAuth, anthropicSetupToken, importCodexToken, importQwenToken, copilotDeviceFlow, saveApiKey, detectAvailableAuth, } from "./utils/auth.js";
|
|
17
|
+
export async function main() {
|
|
18
|
+
const command = process.argv[2] ?? "login";
|
|
19
|
+
switch (command) {
|
|
20
|
+
case "login": {
|
|
21
|
+
console.log("\n💪 Codemaxxing Authentication\n");
|
|
22
|
+
console.log("Available providers:\n");
|
|
23
|
+
PROVIDERS.forEach((p, i) => {
|
|
24
|
+
const methods = p.methods.filter((m) => m !== "none").join(", ");
|
|
25
|
+
console.log(` ${i + 1}. ${p.name}`);
|
|
26
|
+
console.log(` ${p.description}`);
|
|
27
|
+
console.log(` Methods: ${methods}\n`);
|
|
28
|
+
});
|
|
29
|
+
console.log("Detected on this machine:");
|
|
30
|
+
const detected = detectAvailableAuth();
|
|
31
|
+
if (detected.length === 0) {
|
|
32
|
+
console.log(" — No existing CLI credentials found\n");
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
detected.forEach((d) => {
|
|
36
|
+
console.log(` ⚡ ${d.provider} — ${d.description}`);
|
|
37
|
+
});
|
|
38
|
+
console.log("");
|
|
39
|
+
}
|
|
40
|
+
const readline = await import("readline");
|
|
41
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
42
|
+
const ask = (q) => new Promise((resolve) => rl.question(q, resolve));
|
|
43
|
+
const choice = await ask("Select a provider (1-" + PROVIDERS.length + ") or name: ");
|
|
44
|
+
const providerId = PROVIDERS[parseInt(choice) - 1]?.id ?? choice.toLowerCase();
|
|
45
|
+
const provider = PROVIDERS.find((p) => p.id === providerId);
|
|
46
|
+
if (!provider) {
|
|
47
|
+
console.log(`Unknown provider: ${providerId}`);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
console.log(`\nSetting up ${provider.name}...\n`);
|
|
51
|
+
try {
|
|
52
|
+
if (providerId === "openrouter") {
|
|
53
|
+
const cred = await openRouterOAuth((msg) => console.log(` ${msg}`));
|
|
54
|
+
console.log(`\n✅ OpenRouter authenticated! (${cred.label})`);
|
|
55
|
+
}
|
|
56
|
+
else if (providerId === "anthropic") {
|
|
57
|
+
if (provider.methods.includes("setup-token")) {
|
|
58
|
+
const cred = await anthropicSetupToken((msg) => console.log(` ${msg}`));
|
|
59
|
+
console.log(`\n✅ Anthropic authenticated! (${cred.label})`);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
const apiKey = await ask("Enter your Anthropic API key: ");
|
|
63
|
+
const cred = saveApiKey(providerId, apiKey);
|
|
64
|
+
console.log(`\n✅ Saved API key for ${provider.name}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
else if (providerId === "openai") {
|
|
68
|
+
const imported = importCodexToken((msg) => console.log(` ${msg}`));
|
|
69
|
+
if (imported) {
|
|
70
|
+
console.log(`\n✅ Imported Codex credentials! (${imported.label})`);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
const apiKey = await ask("Enter your OpenAI API key: ");
|
|
74
|
+
const cred = saveApiKey(providerId, apiKey);
|
|
75
|
+
console.log(`\n✅ Saved API key for ${provider.name}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
else if (providerId === "qwen") {
|
|
79
|
+
const imported = importQwenToken((msg) => console.log(` ${msg}`));
|
|
80
|
+
if (imported) {
|
|
81
|
+
console.log(`\n✅ Imported Qwen credentials! (${imported.label})`);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
const apiKey = await ask("Enter your Qwen/DashScope API key: ");
|
|
85
|
+
const cred = saveApiKey(providerId, apiKey);
|
|
86
|
+
console.log(`\n✅ Saved API key for ${provider.name}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
else if (providerId === "copilot") {
|
|
90
|
+
const cred = await copilotDeviceFlow((msg) => console.log(` ${msg}`));
|
|
91
|
+
console.log(`\n✅ GitHub Copilot authenticated!`);
|
|
92
|
+
}
|
|
93
|
+
else if (providerId === "custom") {
|
|
94
|
+
const baseUrl = await ask("Enter the base URL: ");
|
|
95
|
+
const apiKey = await ask("Enter your API key: ");
|
|
96
|
+
const cred = saveApiKey(providerId, apiKey, baseUrl, "Custom provider");
|
|
97
|
+
console.log(`\n✅ Saved custom provider`);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
const apiKey = await ask(`Enter your ${provider.name} API key (${provider.consoleUrl}): `);
|
|
101
|
+
const cred = saveApiKey(providerId, apiKey);
|
|
102
|
+
console.log(`\n✅ Saved API key for ${provider.name}`);
|
|
103
|
+
}
|
|
104
|
+
console.log("\nRun 'codemaxxing' to start coding!");
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
console.error(`\n❌ Error: ${err.message}`);
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
rl.close();
|
|
112
|
+
}
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
case "list":
|
|
116
|
+
case "ls": {
|
|
117
|
+
const creds = getCredentials();
|
|
118
|
+
console.log("\n💪 Saved Credentials\n");
|
|
119
|
+
if (creds.length === 0) {
|
|
120
|
+
console.log(" No credentials saved.\n");
|
|
121
|
+
console.log(" Run 'codemaxxing login' to set up authentication.\n");
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
creds.forEach((c) => {
|
|
125
|
+
console.log(` ${c.provider}`);
|
|
126
|
+
console.log(` Method: ${c.method}`);
|
|
127
|
+
console.log(` Label: ${c.label ?? "—"}`);
|
|
128
|
+
const maskedKey = c.apiKey.length > 12
|
|
129
|
+
? c.apiKey.slice(0, 4) + "•".repeat(8) + c.apiKey.slice(-4)
|
|
130
|
+
: "••••••••";
|
|
131
|
+
console.log(` Key: ${maskedKey}`);
|
|
132
|
+
console.log(` Base: ${c.baseUrl}`);
|
|
133
|
+
console.log("");
|
|
134
|
+
});
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
case "remove":
|
|
138
|
+
case "rm":
|
|
139
|
+
case "delete": {
|
|
140
|
+
const target = process.argv[3];
|
|
141
|
+
if (!target) {
|
|
142
|
+
console.log("Usage: codemaxxing auth remove <provider-name>");
|
|
143
|
+
console.log("\nSaved providers:");
|
|
144
|
+
getCredentials().forEach((c) => console.log(` ${c.provider}`));
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
const removed = removeCredential(target);
|
|
148
|
+
if (removed) {
|
|
149
|
+
console.log(`✅ Removed ${target}`);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
console.log(`❌ No credential found for: ${target}`);
|
|
153
|
+
console.log("\nSaved providers:");
|
|
154
|
+
getCredentials().forEach((c) => console.log(` ${c.provider}`));
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
case "openrouter": {
|
|
160
|
+
console.log("Starting OpenRouter OAuth flow...\n");
|
|
161
|
+
try {
|
|
162
|
+
const cred = await openRouterOAuth((msg) => console.log(` ${msg}`));
|
|
163
|
+
console.log(`\n✅ OpenRouter authenticated!`);
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
console.error(`\n❌ ${err.message}`);
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
case "anthropic": {
|
|
172
|
+
console.log("Starting Anthropic setup-token flow...\n");
|
|
173
|
+
try {
|
|
174
|
+
const cred = await anthropicSetupToken((msg) => console.log(` ${msg}`));
|
|
175
|
+
console.log(`\n✅ Anthropic authenticated!`);
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
console.error(`\n❌ ${err.message}`);
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
case "openai": {
|
|
184
|
+
console.log("Checking for Codex CLI credentials...\n");
|
|
185
|
+
const imported = importCodexToken((msg) => console.log(` ${msg}`));
|
|
186
|
+
if (imported) {
|
|
187
|
+
console.log(`\n✅ Imported Codex credentials!`);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
console.log("\n❌ No Codex CLI credentials found.");
|
|
191
|
+
console.log("Make sure Codex CLI is installed and you've logged in.");
|
|
192
|
+
}
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
case "qwen": {
|
|
196
|
+
console.log("Checking for Qwen CLI credentials...\n");
|
|
197
|
+
const imported = importQwenToken((msg) => console.log(` ${msg}`));
|
|
198
|
+
if (imported) {
|
|
199
|
+
console.log(`\n✅ Imported Qwen credentials!`);
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
console.log("\n❌ No Qwen CLI credentials found.");
|
|
203
|
+
console.log("Make sure Qwen CLI is installed and you've logged in.");
|
|
204
|
+
}
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
case "copilot": {
|
|
208
|
+
console.log("Starting GitHub Copilot device flow...\n");
|
|
209
|
+
try {
|
|
210
|
+
const cred = await copilotDeviceFlow((msg) => console.log(` ${msg}`));
|
|
211
|
+
console.log(`\n✅ GitHub Copilot authenticated!`);
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
console.error(`\n❌ ${err.message}`);
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
case "api-key": {
|
|
220
|
+
const providerId = process.argv[3];
|
|
221
|
+
const apiKey = process.argv[4];
|
|
222
|
+
if (!providerId || !apiKey) {
|
|
223
|
+
console.log("Usage: codemaxxing auth api-key <provider-id> <api-key>");
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
const cred = saveApiKey(providerId, apiKey);
|
|
227
|
+
console.log(`\n✅ Saved API key for ${cred.provider}`);
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
case "help":
|
|
231
|
+
case "--help":
|
|
232
|
+
case "-h": {
|
|
233
|
+
console.log(`
|
|
234
|
+
💪 Codemaxxing Auth
|
|
235
|
+
|
|
236
|
+
Commands:
|
|
237
|
+
codemaxxing login Interactive authentication setup
|
|
238
|
+
codemaxxing auth list List saved credentials
|
|
239
|
+
codemaxxing auth remove <name> Remove a credential
|
|
240
|
+
codemaxxing auth openrouter Start OpenRouter OAuth flow
|
|
241
|
+
codemaxxing auth anthropic Get Anthropic via Claude Code CLI
|
|
242
|
+
codemaxxing auth openai Import Codex CLI credentials
|
|
243
|
+
codemaxxing auth qwen Import Qwen CLI credentials
|
|
244
|
+
codemaxxing auth copilot GitHub Copilot device flow
|
|
245
|
+
codemaxxing auth api-key <id> <key> Save API key directly
|
|
246
|
+
codemaxxing auth help Show this help
|
|
247
|
+
|
|
248
|
+
Examples:
|
|
249
|
+
codemaxxing login # Interactive provider picker
|
|
250
|
+
codemaxxing auth openrouter # One browser login, access to 200+ models
|
|
251
|
+
codemaxxing auth anthropic # Use your Claude subscription via Claude Code
|
|
252
|
+
codemaxxing auth list # See what's saved
|
|
253
|
+
`);
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
default:
|
|
257
|
+
console.log(`Unknown command: ${command}`);
|
|
258
|
+
console.log("Run 'codemaxxing auth help' for available commands.");
|
|
259
|
+
process.exit(1);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
// Run main if this is the main module
|
|
263
|
+
if (typeof require !== "undefined" && require.main === module) {
|
|
264
|
+
main().catch((err) => {
|
|
265
|
+
console.error(`Error: ${err.message}`);
|
|
266
|
+
process.exit(1);
|
|
267
|
+
});
|
|
268
|
+
}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Codemaxxing CLI entry point
|
|
4
|
+
* Routes subcommands (login, auth) to auth-cli, everything else to the TUI
|
|
5
|
+
*/
|
|
6
|
+
import { spawn } from "node:child_process";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { dirname, join } from "node:path";
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = dirname(__filename);
|
|
11
|
+
const subcmd = process.argv[2];
|
|
12
|
+
if (subcmd === "login" || subcmd === "auth") {
|
|
13
|
+
// Route to auth CLI
|
|
14
|
+
const authScript = join(__dirname, "auth-cli.js");
|
|
15
|
+
const args = subcmd === "login"
|
|
16
|
+
? [authScript, "login", ...process.argv.slice(3)]
|
|
17
|
+
: [authScript, ...process.argv.slice(3)];
|
|
18
|
+
const child = spawn(process.execPath, args, {
|
|
19
|
+
stdio: "inherit",
|
|
20
|
+
cwd: process.cwd(),
|
|
21
|
+
});
|
|
22
|
+
child.on("exit", (code) => process.exit(code ?? 0));
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
// Route to TUI
|
|
26
|
+
const tuiScript = join(__dirname, "index.js");
|
|
27
|
+
const child = spawn(process.execPath, [tuiScript, ...process.argv.slice(2)], {
|
|
28
|
+
stdio: "inherit",
|
|
29
|
+
cwd: process.cwd(),
|
|
30
|
+
});
|
|
31
|
+
child.on("exit", (code) => process.exit(code ?? 0));
|
|
32
|
+
}
|
package/dist/config.d.ts
CHANGED
|
@@ -39,3 +39,12 @@ export declare function detectLocalProvider(): Promise<ProviderConfig | null>;
|
|
|
39
39
|
* List available models from a provider endpoint
|
|
40
40
|
*/
|
|
41
41
|
export declare function listModels(baseUrl: string, apiKey: string): Promise<string[]>;
|
|
42
|
+
/**
|
|
43
|
+
* Resolve provider configuration from auth store or config file
|
|
44
|
+
* Priority: CLI args > auth store > config file > auto-detect
|
|
45
|
+
*/
|
|
46
|
+
export declare function resolveProvider(providerId: string, cliArgs: CLIArgs): {
|
|
47
|
+
baseUrl: string;
|
|
48
|
+
apiKey: string;
|
|
49
|
+
model: string;
|
|
50
|
+
} | null;
|
package/dist/config.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync, existsSync, mkdirSync, writeFileSync } from "fs";
|
|
2
2
|
import { homedir } from "os";
|
|
3
3
|
import { join } from "path";
|
|
4
|
+
import { getCredential } from "./utils/auth.js";
|
|
4
5
|
const CONFIG_DIR = join(homedir(), ".codemaxxing");
|
|
5
6
|
const CONFIG_FILE = join(CONFIG_DIR, "settings.json");
|
|
6
7
|
const DEFAULT_CONFIG = {
|
|
@@ -172,3 +173,29 @@ export async function listModels(baseUrl, apiKey) {
|
|
|
172
173
|
catch { /* ignore */ }
|
|
173
174
|
return [];
|
|
174
175
|
}
|
|
176
|
+
/**
|
|
177
|
+
* Resolve provider configuration from auth store or config file
|
|
178
|
+
* Priority: CLI args > auth store > config file > auto-detect
|
|
179
|
+
*/
|
|
180
|
+
export function resolveProvider(providerId, cliArgs) {
|
|
181
|
+
// Check auth store first
|
|
182
|
+
const authCred = getCredential(providerId);
|
|
183
|
+
if (authCred) {
|
|
184
|
+
return {
|
|
185
|
+
baseUrl: authCred.baseUrl,
|
|
186
|
+
apiKey: authCred.apiKey,
|
|
187
|
+
model: cliArgs.model || "auto",
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
// Fall back to config file
|
|
191
|
+
const config = loadConfig();
|
|
192
|
+
const provider = config.providers?.[providerId];
|
|
193
|
+
if (provider) {
|
|
194
|
+
return {
|
|
195
|
+
baseUrl: provider.baseUrl,
|
|
196
|
+
apiKey: cliArgs.apiKey || provider.apiKey,
|
|
197
|
+
model: cliArgs.model || provider.model,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
return null;
|
|
201
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,8 @@ import { listSessions, getSession, loadMessages } from "./utils/sessions.js";
|
|
|
10
10
|
import { execSync } from "child_process";
|
|
11
11
|
import { isGitRepo, getBranch, getStatus, getDiff, undoLastCommit } from "./utils/git.js";
|
|
12
12
|
import { getTheme, listThemes, THEMES, DEFAULT_THEME } from "./themes.js";
|
|
13
|
-
|
|
13
|
+
import { PROVIDERS, openRouterOAuth, anthropicSetupToken, importCodexToken, importQwenToken, copilotDeviceFlow } from "./utils/auth.js";
|
|
14
|
+
const VERSION = "0.1.5";
|
|
14
15
|
// ── Helpers ──
|
|
15
16
|
function formatTimeAgo(date) {
|
|
16
17
|
const secs = Math.floor((Date.now() - date.getTime()) / 1000);
|
|
@@ -28,6 +29,8 @@ function formatTimeAgo(date) {
|
|
|
28
29
|
// ── Slash Commands ──
|
|
29
30
|
const SLASH_COMMANDS = [
|
|
30
31
|
{ cmd: "/help", desc: "show commands" },
|
|
32
|
+
{ cmd: "/login", desc: "set up authentication" },
|
|
33
|
+
{ cmd: "/login", desc: "set up authentication" },
|
|
31
34
|
{ cmd: "/map", desc: "show repository map" },
|
|
32
35
|
{ cmd: "/reset", desc: "clear conversation" },
|
|
33
36
|
{ cmd: "/context", desc: "show message count" },
|
|
@@ -104,6 +107,8 @@ function App() {
|
|
|
104
107
|
const [sessionPickerIndex, setSessionPickerIndex] = useState(0);
|
|
105
108
|
const [themePicker, setThemePicker] = useState(false);
|
|
106
109
|
const [themePickerIndex, setThemePickerIndex] = useState(0);
|
|
110
|
+
const [loginPicker, setLoginPicker] = useState(false);
|
|
111
|
+
const [loginPickerIndex, setLoginPickerIndex] = useState(0);
|
|
107
112
|
const [approval, setApproval] = useState(null);
|
|
108
113
|
// Listen for paste events from stdin interceptor
|
|
109
114
|
useEffect(() => {
|
|
@@ -275,10 +280,16 @@ function App() {
|
|
|
275
280
|
exit();
|
|
276
281
|
return;
|
|
277
282
|
}
|
|
283
|
+
if (trimmed === "/login" || trimmed === "/auth") {
|
|
284
|
+
setLoginPicker(true);
|
|
285
|
+
setLoginPickerIndex(0);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
278
288
|
if (trimmed === "/help") {
|
|
279
289
|
addMsg("info", [
|
|
280
290
|
"Commands:",
|
|
281
291
|
" /help — show this",
|
|
292
|
+
" /login — authentication setup (run codemaxxing login in terminal)",
|
|
282
293
|
" /model — switch model mid-session",
|
|
283
294
|
" /models — list available models",
|
|
284
295
|
" /map — show repository map",
|
|
@@ -487,6 +498,92 @@ function App() {
|
|
|
487
498
|
return;
|
|
488
499
|
}
|
|
489
500
|
}
|
|
501
|
+
// Login picker navigation
|
|
502
|
+
if (loginPicker) {
|
|
503
|
+
const loginProviders = PROVIDERS.filter((p) => p.id !== "local");
|
|
504
|
+
if (key.upArrow) {
|
|
505
|
+
setLoginPickerIndex((prev) => (prev - 1 + loginProviders.length) % loginProviders.length);
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
if (key.downArrow) {
|
|
509
|
+
setLoginPickerIndex((prev) => (prev + 1) % loginProviders.length);
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
if (key.return) {
|
|
513
|
+
const selected = loginProviders[loginPickerIndex];
|
|
514
|
+
setLoginPicker(false);
|
|
515
|
+
if (selected.id === "openrouter") {
|
|
516
|
+
addMsg("info", "Starting OpenRouter OAuth — opening browser...");
|
|
517
|
+
setLoading(true);
|
|
518
|
+
setSpinnerMsg("Waiting for authorization...");
|
|
519
|
+
openRouterOAuth((msg) => addMsg("info", msg))
|
|
520
|
+
.then((cred) => {
|
|
521
|
+
addMsg("info", `✅ OpenRouter authenticated! You now have access to 200+ models.`);
|
|
522
|
+
addMsg("info", `Switch with: /model anthropic/claude-sonnet-4`);
|
|
523
|
+
setLoading(false);
|
|
524
|
+
})
|
|
525
|
+
.catch((err) => {
|
|
526
|
+
addMsg("error", `OAuth failed: ${err.message}`);
|
|
527
|
+
setLoading(false);
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
else if (selected.id === "anthropic") {
|
|
531
|
+
addMsg("info", "Starting Anthropic setup-token flow...");
|
|
532
|
+
setLoading(true);
|
|
533
|
+
setSpinnerMsg("Waiting for Claude Code auth...");
|
|
534
|
+
anthropicSetupToken((msg) => addMsg("info", msg))
|
|
535
|
+
.then((cred) => {
|
|
536
|
+
addMsg("info", `✅ Anthropic authenticated! (${cred.label})`);
|
|
537
|
+
setLoading(false);
|
|
538
|
+
})
|
|
539
|
+
.catch((err) => {
|
|
540
|
+
addMsg("error", `Anthropic auth failed: ${err.message}`);
|
|
541
|
+
setLoading(false);
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
else if (selected.id === "openai") {
|
|
545
|
+
const imported = importCodexToken((msg) => addMsg("info", msg));
|
|
546
|
+
if (imported) {
|
|
547
|
+
addMsg("info", `✅ Imported Codex credentials! (${imported.label})`);
|
|
548
|
+
}
|
|
549
|
+
else {
|
|
550
|
+
addMsg("info", "No Codex CLI found. Run: codemaxxing auth api-key openai <your-key>");
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
else if (selected.id === "qwen") {
|
|
554
|
+
const imported = importQwenToken((msg) => addMsg("info", msg));
|
|
555
|
+
if (imported) {
|
|
556
|
+
addMsg("info", `✅ Imported Qwen credentials! (${imported.label})`);
|
|
557
|
+
}
|
|
558
|
+
else {
|
|
559
|
+
addMsg("info", "No Qwen CLI found. Run: codemaxxing auth api-key qwen <your-key>");
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
else if (selected.id === "copilot") {
|
|
563
|
+
addMsg("info", "Starting GitHub Copilot device flow...");
|
|
564
|
+
setLoading(true);
|
|
565
|
+
setSpinnerMsg("Waiting for GitHub authorization...");
|
|
566
|
+
copilotDeviceFlow((msg) => addMsg("info", msg))
|
|
567
|
+
.then(() => {
|
|
568
|
+
addMsg("info", `✅ GitHub Copilot authenticated!`);
|
|
569
|
+
setLoading(false);
|
|
570
|
+
})
|
|
571
|
+
.catch((err) => {
|
|
572
|
+
addMsg("error", `Copilot auth failed: ${err.message}`);
|
|
573
|
+
setLoading(false);
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
else {
|
|
577
|
+
addMsg("info", `Run: codemaxxing auth api-key ${selected.id} <your-key>\n Get key at: ${selected.consoleUrl ?? selected.baseUrl}`);
|
|
578
|
+
}
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
if (key.escape) {
|
|
582
|
+
setLoginPicker(false);
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
490
587
|
// Theme picker navigation
|
|
491
588
|
if (themePicker) {
|
|
492
589
|
const themeKeys = listThemes();
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth management for Codemaxxing
|
|
3
|
+
*
|
|
4
|
+
* Supports:
|
|
5
|
+
* - OpenRouter OAuth PKCE (browser login, no API key needed)
|
|
6
|
+
* - Anthropic setup-token (via Claude Code CLI)
|
|
7
|
+
* - OpenAI/ChatGPT (via Codex CLI cached token)
|
|
8
|
+
* - Qwen (via Qwen CLI cached credentials)
|
|
9
|
+
* - GitHub Copilot (device flow)
|
|
10
|
+
* - Manual API key entry (any provider)
|
|
11
|
+
*
|
|
12
|
+
* Credentials stored in ~/.codemaxxing/auth.json with 0o600 permissions.
|
|
13
|
+
*/
|
|
14
|
+
export interface AuthCredential {
|
|
15
|
+
provider: string;
|
|
16
|
+
method: "api-key" | "oauth" | "setup-token" | "cached-token";
|
|
17
|
+
apiKey: string;
|
|
18
|
+
baseUrl: string;
|
|
19
|
+
label?: string;
|
|
20
|
+
expiresAt?: string;
|
|
21
|
+
createdAt: string;
|
|
22
|
+
}
|
|
23
|
+
export interface ProviderDef {
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
methods: string[];
|
|
27
|
+
baseUrl: string;
|
|
28
|
+
consoleUrl?: string;
|
|
29
|
+
description: string;
|
|
30
|
+
}
|
|
31
|
+
export declare const PROVIDERS: ProviderDef[];
|
|
32
|
+
export declare function getCredentials(): AuthCredential[];
|
|
33
|
+
export declare function getCredential(providerId: string): AuthCredential | undefined;
|
|
34
|
+
export declare function saveCredential(cred: AuthCredential): void;
|
|
35
|
+
export declare function removeCredential(providerId: string): boolean;
|
|
36
|
+
export declare function openRouterOAuth(onStatus?: (msg: string) => void): Promise<AuthCredential>;
|
|
37
|
+
export declare function detectClaudeCLI(): boolean;
|
|
38
|
+
export declare function anthropicSetupToken(onStatus?: (msg: string) => void): Promise<AuthCredential>;
|
|
39
|
+
export declare function detectCodexToken(): string | null;
|
|
40
|
+
export declare function importCodexToken(onStatus?: (msg: string) => void): AuthCredential | null;
|
|
41
|
+
export declare function detectQwenToken(): string | null;
|
|
42
|
+
export declare function importQwenToken(onStatus?: (msg: string) => void): AuthCredential | null;
|
|
43
|
+
export declare function copilotDeviceFlow(onStatus?: (msg: string) => void): Promise<AuthCredential>;
|
|
44
|
+
export declare function saveApiKey(providerId: string, apiKey: string, baseUrl?: string, label?: string): AuthCredential;
|
|
45
|
+
/**
|
|
46
|
+
* Check which provider CLIs / cached tokens are available on this machine
|
|
47
|
+
*/
|
|
48
|
+
export declare function detectAvailableAuth(): Array<{
|
|
49
|
+
provider: string;
|
|
50
|
+
method: string;
|
|
51
|
+
description: string;
|
|
52
|
+
}>;
|
|
53
|
+
/**
|
|
54
|
+
* Resolve provider config: check auth store first, then fall back to config
|
|
55
|
+
*/
|
|
56
|
+
export declare function resolveProviderAuth(providerId: string): {
|
|
57
|
+
apiKey: string;
|
|
58
|
+
baseUrl: string;
|
|
59
|
+
} | null;
|