hanzi-browse 2.2.0

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 (78) hide show
  1. package/README.md +182 -0
  2. package/dist/agent/loop.d.ts +63 -0
  3. package/dist/agent/loop.js +186 -0
  4. package/dist/agent/system-prompt.d.ts +7 -0
  5. package/dist/agent/system-prompt.js +41 -0
  6. package/dist/agent/tools.d.ts +9 -0
  7. package/dist/agent/tools.js +154 -0
  8. package/dist/cli/detect-credentials.d.ts +31 -0
  9. package/dist/cli/detect-credentials.js +44 -0
  10. package/dist/cli/import-credentials-handler.d.ts +14 -0
  11. package/dist/cli/import-credentials-handler.js +22 -0
  12. package/dist/cli/session-files.d.ts +28 -0
  13. package/dist/cli/session-files.js +118 -0
  14. package/dist/cli/setup.d.ts +10 -0
  15. package/dist/cli/setup.js +915 -0
  16. package/dist/cli.d.ts +16 -0
  17. package/dist/cli.js +506 -0
  18. package/dist/dashboard/assets/index-CEFyesbT.js +46 -0
  19. package/dist/dashboard/assets/index-Dnht2kLU.css +1 -0
  20. package/dist/dashboard/index.html +13 -0
  21. package/dist/index.d.ts +2 -0
  22. package/dist/index.js +1116 -0
  23. package/dist/ipc/index.d.ts +8 -0
  24. package/dist/ipc/index.js +8 -0
  25. package/dist/ipc/native-host.d.ts +96 -0
  26. package/dist/ipc/native-host.js +223 -0
  27. package/dist/ipc/websocket-client.d.ts +73 -0
  28. package/dist/ipc/websocket-client.js +199 -0
  29. package/dist/license/manager.d.ts +20 -0
  30. package/dist/license/manager.js +15 -0
  31. package/dist/llm/client.d.ts +72 -0
  32. package/dist/llm/client.js +227 -0
  33. package/dist/llm/credentials.d.ts +61 -0
  34. package/dist/llm/credentials.js +200 -0
  35. package/dist/llm/vertex.d.ts +22 -0
  36. package/dist/llm/vertex.js +335 -0
  37. package/dist/managed/api-http.test.d.ts +7 -0
  38. package/dist/managed/api-http.test.js +623 -0
  39. package/dist/managed/api.d.ts +51 -0
  40. package/dist/managed/api.js +1448 -0
  41. package/dist/managed/api.test.d.ts +10 -0
  42. package/dist/managed/api.test.js +146 -0
  43. package/dist/managed/auth.d.ts +38 -0
  44. package/dist/managed/auth.js +192 -0
  45. package/dist/managed/billing.d.ts +70 -0
  46. package/dist/managed/billing.js +227 -0
  47. package/dist/managed/deploy.d.ts +17 -0
  48. package/dist/managed/deploy.js +385 -0
  49. package/dist/managed/e2e.test.d.ts +15 -0
  50. package/dist/managed/e2e.test.js +151 -0
  51. package/dist/managed/hardening.test.d.ts +14 -0
  52. package/dist/managed/hardening.test.js +346 -0
  53. package/dist/managed/integration.test.d.ts +8 -0
  54. package/dist/managed/integration.test.js +274 -0
  55. package/dist/managed/log.d.ts +18 -0
  56. package/dist/managed/log.js +31 -0
  57. package/dist/managed/server.d.ts +12 -0
  58. package/dist/managed/server.js +69 -0
  59. package/dist/managed/store-pg.d.ts +191 -0
  60. package/dist/managed/store-pg.js +479 -0
  61. package/dist/managed/store.d.ts +188 -0
  62. package/dist/managed/store.js +379 -0
  63. package/dist/relay/auto-start.d.ts +19 -0
  64. package/dist/relay/auto-start.js +71 -0
  65. package/dist/relay/server.d.ts +17 -0
  66. package/dist/relay/server.js +403 -0
  67. package/dist/types/index.d.ts +5 -0
  68. package/dist/types/index.js +4 -0
  69. package/dist/types/session.d.ts +134 -0
  70. package/dist/types/session.js +16 -0
  71. package/package.json +61 -0
  72. package/skills/README.md +48 -0
  73. package/skills/a11y-auditor/SKILL.md +42 -0
  74. package/skills/e2e-tester/SKILL.md +154 -0
  75. package/skills/hanzi-browse/SKILL.md +182 -0
  76. package/skills/linkedin-prospector/SKILL.md +149 -0
  77. package/skills/social-poster/SKILL.md +146 -0
  78. package/skills/x-marketer/SKILL.md +479 -0
@@ -0,0 +1,72 @@
1
+ /**
2
+ * LLM Client for MCP Server
3
+ *
4
+ * Routes between providers:
5
+ * - Vertex AI (Gemini) — managed mode, server-side agent loop
6
+ * - Anthropic — legacy local mode, Claude Code OAuth
7
+ *
8
+ * Canonical internal format is Anthropic content blocks.
9
+ * Vertex provider converts at the API boundary.
10
+ */
11
+ export interface ContentBlockText {
12
+ type: "text";
13
+ text: string;
14
+ }
15
+ export interface ContentBlockImage {
16
+ type: "image";
17
+ source: {
18
+ type: "base64";
19
+ media_type: string;
20
+ data: string;
21
+ };
22
+ }
23
+ export interface ContentBlockToolUse {
24
+ type: "tool_use";
25
+ id: string;
26
+ name: string;
27
+ input: Record<string, any>;
28
+ }
29
+ export interface ContentBlockToolResult {
30
+ type: "tool_result";
31
+ tool_use_id: string;
32
+ content: string | Array<ContentBlockText | ContentBlockImage>;
33
+ }
34
+ export type ContentBlock = ContentBlockText | ContentBlockImage | ContentBlockToolUse | ContentBlockToolResult;
35
+ export interface Message {
36
+ role: "user" | "assistant";
37
+ content: string | ContentBlock[];
38
+ }
39
+ export interface Tool {
40
+ name: string;
41
+ description: string;
42
+ input_schema: Record<string, any>;
43
+ }
44
+ export interface LLMResponse {
45
+ content: ContentBlock[];
46
+ stop_reason: string;
47
+ usage: {
48
+ input_tokens: number;
49
+ output_tokens: number;
50
+ };
51
+ /** The model that produced this response (for billing attribution) */
52
+ model?: string;
53
+ }
54
+ export interface CallLLMParams {
55
+ messages: Message[];
56
+ system: ContentBlockText[];
57
+ tools: Tool[];
58
+ model?: string;
59
+ maxTokens?: number;
60
+ signal?: AbortSignal;
61
+ onText?: (chunk: string) => void;
62
+ }
63
+ /**
64
+ * Call the LLM. Routes to Vertex AI (Gemini) if configured, otherwise Anthropic.
65
+ *
66
+ * Handles streaming, auto-refresh on 401, and credential resolution.
67
+ */
68
+ export declare function callLLM(params: CallLLMParams): Promise<LLMResponse>;
69
+ /**
70
+ * Reset cached credentials (e.g., after manual credential update).
71
+ */
72
+ export declare function resetCredentialCache(): void;
@@ -0,0 +1,227 @@
1
+ /**
2
+ * LLM Client for MCP Server
3
+ *
4
+ * Routes between providers:
5
+ * - Vertex AI (Gemini) — managed mode, server-side agent loop
6
+ * - Anthropic — legacy local mode, Claude Code OAuth
7
+ *
8
+ * Canonical internal format is Anthropic content blocks.
9
+ * Vertex provider converts at the API boundary.
10
+ */
11
+ import { resolveCredentials, refreshClaudeToken, saveClaudeCredentials, } from "./credentials.js";
12
+ import { callVertexLLM, isVertexConfigured } from "./vertex.js";
13
+ const ANTHROPIC_API_URL = "https://api.anthropic.com/v1/messages";
14
+ const DEFAULT_MODEL = "claude-haiku-4-5-20251001";
15
+ const DEFAULT_MAX_TOKENS = 16384;
16
+ // Cached credential source — refreshed on 401
17
+ let cachedSource = null;
18
+ function getSource() {
19
+ if (!cachedSource) {
20
+ cachedSource = resolveCredentials();
21
+ }
22
+ if (!cachedSource) {
23
+ throw new Error("No credentials found. Set ANTHROPIC_API_KEY or run `claude login`");
24
+ }
25
+ return cachedSource;
26
+ }
27
+ /**
28
+ * Build request headers based on credential type.
29
+ */
30
+ function buildHeaders(source) {
31
+ if (source.type === "api_key") {
32
+ return {
33
+ "Content-Type": "application/json",
34
+ "x-api-key": source.apiKey,
35
+ "anthropic-version": "2023-06-01",
36
+ };
37
+ }
38
+ if (source.type === "claude_oauth") {
39
+ return {
40
+ "Content-Type": "application/json",
41
+ Authorization: `Bearer ${source.credentials.accessToken}`,
42
+ "anthropic-version": "2023-06-01",
43
+ "anthropic-dangerous-direct-browser-access": "true",
44
+ "anthropic-beta": "claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14",
45
+ "x-app": "cli",
46
+ "user-agent": "claude-code/2.1.29 (Darwin; arm64)",
47
+ };
48
+ }
49
+ throw new Error("Codex credentials are not supported for direct LLM calls from MCP server");
50
+ }
51
+ /**
52
+ * Parse SSE stream and extract the final response.
53
+ */
54
+ async function parseSSEStream(response, onText, signal) {
55
+ const reader = response.body.getReader();
56
+ const decoder = new TextDecoder();
57
+ let buffer = "";
58
+ // Accumulate content blocks from streaming events
59
+ const contentBlocks = [];
60
+ let currentBlockIndex = -1;
61
+ let stopReason = "";
62
+ let usage = { input_tokens: 0, output_tokens: 0 };
63
+ try {
64
+ while (true) {
65
+ if (signal?.aborted) {
66
+ reader.cancel();
67
+ throw new DOMException("Aborted", "AbortError");
68
+ }
69
+ const { done, value } = await reader.read();
70
+ if (done)
71
+ break;
72
+ buffer += decoder.decode(value, { stream: true });
73
+ const lines = buffer.split("\n");
74
+ buffer = lines.pop();
75
+ for (const line of lines) {
76
+ if (!line.startsWith("data: "))
77
+ continue;
78
+ const data = line.slice(6);
79
+ if (data === "[DONE]")
80
+ continue;
81
+ let event;
82
+ try {
83
+ event = JSON.parse(data);
84
+ }
85
+ catch {
86
+ continue;
87
+ }
88
+ switch (event.type) {
89
+ case "content_block_start":
90
+ currentBlockIndex = event.index;
91
+ if (event.content_block.type === "tool_use") {
92
+ contentBlocks[currentBlockIndex] = {
93
+ type: "tool_use",
94
+ id: event.content_block.id,
95
+ name: event.content_block.name,
96
+ input: {},
97
+ };
98
+ }
99
+ else if (event.content_block.type === "text") {
100
+ contentBlocks[currentBlockIndex] = {
101
+ type: "text",
102
+ text: "",
103
+ };
104
+ }
105
+ break;
106
+ case "content_block_delta":
107
+ if (event.delta.type === "text_delta") {
108
+ const block = contentBlocks[event.index];
109
+ if (block) {
110
+ block.text += event.delta.text;
111
+ onText?.(event.delta.text);
112
+ }
113
+ }
114
+ else if (event.delta.type === "input_json_delta") {
115
+ // Accumulate JSON string for tool input — parse on content_block_stop
116
+ const block = contentBlocks[event.index];
117
+ if (block) {
118
+ block._rawInput = (block._rawInput || "") + event.delta.partial_json;
119
+ }
120
+ }
121
+ break;
122
+ case "content_block_stop": {
123
+ const block = contentBlocks[event.index];
124
+ if (block?.type === "tool_use") {
125
+ if (block._rawInput) {
126
+ try {
127
+ block.input = JSON.parse(block._rawInput);
128
+ }
129
+ catch {
130
+ block.input = {};
131
+ }
132
+ }
133
+ delete block._rawInput;
134
+ }
135
+ break;
136
+ }
137
+ case "message_delta":
138
+ if (event.delta.stop_reason) {
139
+ stopReason = event.delta.stop_reason;
140
+ }
141
+ if (event.usage) {
142
+ usage.output_tokens = event.usage.output_tokens || usage.output_tokens;
143
+ }
144
+ break;
145
+ case "message_start":
146
+ if (event.message?.usage) {
147
+ usage.input_tokens = event.message.usage.input_tokens || 0;
148
+ }
149
+ break;
150
+ }
151
+ }
152
+ }
153
+ }
154
+ finally {
155
+ reader.releaseLock();
156
+ }
157
+ // Safety: strip any leftover _rawInput from tool_use blocks
158
+ for (const block of contentBlocks) {
159
+ if (block._rawInput !== undefined) {
160
+ delete block._rawInput;
161
+ }
162
+ }
163
+ return { content: contentBlocks, stop_reason: stopReason, usage };
164
+ }
165
+ /**
166
+ * Call the LLM. Routes to Vertex AI (Gemini) if configured, otherwise Anthropic.
167
+ *
168
+ * Handles streaming, auto-refresh on 401, and credential resolution.
169
+ */
170
+ export async function callLLM(params) {
171
+ // Route to Vertex AI if configured
172
+ if (isVertexConfigured()) {
173
+ return callVertexLLM(params);
174
+ }
175
+ const { messages, system, tools, model = DEFAULT_MODEL, maxTokens = DEFAULT_MAX_TOKENS, signal, onText, } = params;
176
+ const source = getSource();
177
+ const headers = buildHeaders(source);
178
+ const body = JSON.stringify({
179
+ model,
180
+ max_tokens: maxTokens,
181
+ system,
182
+ messages,
183
+ tools: tools.length > 0 ? tools : undefined,
184
+ stream: true,
185
+ });
186
+ let response = await fetch(ANTHROPIC_API_URL, {
187
+ method: "POST",
188
+ headers,
189
+ body,
190
+ signal,
191
+ });
192
+ // Auto-refresh on 401/403 for OAuth credentials
193
+ if ((response.status === 401 || response.status === 403) &&
194
+ source.type === "claude_oauth" &&
195
+ source.credentials.refreshToken) {
196
+ console.error("[LLM] Got 401/403, refreshing OAuth token...");
197
+ try {
198
+ const newCreds = await refreshClaudeToken(source.credentials.refreshToken);
199
+ saveClaudeCredentials(newCreds);
200
+ // Update cached source
201
+ cachedSource = { type: "claude_oauth", credentials: newCreds };
202
+ const newHeaders = buildHeaders(cachedSource);
203
+ response = await fetch(ANTHROPIC_API_URL, {
204
+ method: "POST",
205
+ headers: newHeaders,
206
+ body,
207
+ signal,
208
+ });
209
+ }
210
+ catch (refreshErr) {
211
+ throw new Error(`Token refresh failed: ${refreshErr.message}`);
212
+ }
213
+ }
214
+ if (!response.ok) {
215
+ const errorText = await response.text().catch(() => "");
216
+ throw new Error(`Anthropic API error ${response.status}: ${errorText.slice(0, 300)}`);
217
+ }
218
+ const result = await parseSSEStream(response, onText, signal);
219
+ result.model = model;
220
+ return result;
221
+ }
222
+ /**
223
+ * Reset cached credentials (e.g., after manual credential update).
224
+ */
225
+ export function resetCredentialCache() {
226
+ cachedSource = null;
227
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Credential Reader for MCP Server
3
+ *
4
+ * Reads Claude/Codex credentials directly from the user's machine,
5
+ * eliminating the need for the native host bridge in MCP mode.
6
+ *
7
+ * Resolution order:
8
+ * 1. ANTHROPIC_API_KEY env var → direct API key auth
9
+ * 2. ~/.claude/.credentials.json → Claude Code OAuth tokens
10
+ * 3. macOS Keychain "Claude Code-credentials" → Claude Code OAuth tokens
11
+ * 4. ~/.codex/auth.json → Codex/OpenAI tokens
12
+ */
13
+ export interface ClaudeCredentials {
14
+ accessToken: string;
15
+ refreshToken: string;
16
+ expiresAt: number;
17
+ }
18
+ export interface CodexCredentials {
19
+ accessToken: string;
20
+ refreshToken: string;
21
+ accountId: string;
22
+ }
23
+ export type CredentialSource = {
24
+ type: "api_key";
25
+ apiKey: string;
26
+ } | {
27
+ type: "claude_oauth";
28
+ credentials: ClaudeCredentials;
29
+ } | {
30
+ type: "codex_oauth";
31
+ credentials: CodexCredentials;
32
+ };
33
+ /**
34
+ * Read Claude Code OAuth credentials from ~/.claude/.credentials.json
35
+ */
36
+ export declare function getClaudeCredentials(): ClaudeCredentials | null;
37
+ /**
38
+ * Read Claude Code credentials from macOS Keychain
39
+ */
40
+ export declare function getClaudeKeychainCredentials(): ClaudeCredentials | null;
41
+ /**
42
+ * Read Codex CLI credentials from ~/.codex/auth.json
43
+ */
44
+ export declare function getCodexCredentials(): CodexCredentials | null;
45
+ /**
46
+ * Save refreshed Claude credentials back to ~/.claude/.credentials.json
47
+ */
48
+ export declare function saveClaudeCredentials(newCreds: ClaudeCredentials): boolean;
49
+ /**
50
+ * Refresh Claude OAuth token using refresh token
51
+ */
52
+ export declare function refreshClaudeToken(refreshToken: string): Promise<ClaudeCredentials>;
53
+ /**
54
+ * Resolve credentials in priority order.
55
+ * Returns the first valid credential source found.
56
+ */
57
+ export declare function resolveCredentials(): CredentialSource | null;
58
+ /**
59
+ * Get a human-readable description of the credential source found.
60
+ */
61
+ export declare function describeCredentials(): string;
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Credential Reader for MCP Server
3
+ *
4
+ * Reads Claude/Codex credentials directly from the user's machine,
5
+ * eliminating the need for the native host bridge in MCP mode.
6
+ *
7
+ * Resolution order:
8
+ * 1. ANTHROPIC_API_KEY env var → direct API key auth
9
+ * 2. ~/.claude/.credentials.json → Claude Code OAuth tokens
10
+ * 3. macOS Keychain "Claude Code-credentials" → Claude Code OAuth tokens
11
+ * 4. ~/.codex/auth.json → Codex/OpenAI tokens
12
+ */
13
+ import fs from "fs";
14
+ import os from "os";
15
+ import path from "path";
16
+ import { execSync } from "child_process";
17
+ // OAuth configuration (same as native-bridge.cjs)
18
+ const OAUTH_CONFIG = {
19
+ tokenUrl: "https://console.anthropic.com/v1/oauth/token",
20
+ clientId: "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
21
+ };
22
+ /**
23
+ * Read Claude Code OAuth credentials from ~/.claude/.credentials.json
24
+ */
25
+ export function getClaudeCredentials() {
26
+ const credPath = path.join(os.homedir(), ".claude", ".credentials.json");
27
+ if (!fs.existsSync(credPath))
28
+ return null;
29
+ try {
30
+ const content = fs.readFileSync(credPath, "utf8");
31
+ const creds = JSON.parse(content);
32
+ if (creds.claudeAiOauth?.accessToken) {
33
+ return creds.claudeAiOauth;
34
+ }
35
+ }
36
+ catch {
37
+ // File unreadable or invalid JSON
38
+ }
39
+ return null;
40
+ }
41
+ /**
42
+ * Read Claude Code credentials from macOS Keychain
43
+ */
44
+ export function getClaudeKeychainCredentials() {
45
+ if (process.platform !== "darwin")
46
+ return null;
47
+ try {
48
+ const result = execSync('security find-generic-password -s "Claude Code-credentials" -w', { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
49
+ if (result?.trim()) {
50
+ const creds = JSON.parse(result.trim());
51
+ if (creds.claudeAiOauth?.accessToken) {
52
+ return creds.claudeAiOauth;
53
+ }
54
+ }
55
+ }
56
+ catch {
57
+ // Keychain entry not found or not on macOS
58
+ }
59
+ return null;
60
+ }
61
+ /**
62
+ * Read Codex CLI credentials from ~/.codex/auth.json
63
+ */
64
+ export function getCodexCredentials() {
65
+ const credPath = path.join(os.homedir(), ".codex", "auth.json");
66
+ if (!fs.existsSync(credPath))
67
+ return null;
68
+ try {
69
+ const content = fs.readFileSync(credPath, "utf8");
70
+ const creds = JSON.parse(content);
71
+ if (creds.tokens?.access_token) {
72
+ return {
73
+ accessToken: creds.tokens.access_token,
74
+ refreshToken: creds.tokens.refresh_token,
75
+ accountId: creds.tokens.account_id,
76
+ };
77
+ }
78
+ }
79
+ catch {
80
+ // File unreadable or invalid JSON
81
+ }
82
+ return null;
83
+ }
84
+ /**
85
+ * Save refreshed Claude credentials back to ~/.claude/.credentials.json
86
+ */
87
+ export function saveClaudeCredentials(newCreds) {
88
+ const credPath = path.join(os.homedir(), ".claude", ".credentials.json");
89
+ try {
90
+ let existingData = {};
91
+ if (fs.existsSync(credPath)) {
92
+ existingData = JSON.parse(fs.readFileSync(credPath, "utf8"));
93
+ }
94
+ existingData.claudeAiOauth = {
95
+ ...existingData.claudeAiOauth,
96
+ accessToken: newCreds.accessToken,
97
+ refreshToken: newCreds.refreshToken || existingData.claudeAiOauth?.refreshToken,
98
+ expiresAt: newCreds.expiresAt,
99
+ };
100
+ fs.writeFileSync(credPath, JSON.stringify(existingData, null, 2));
101
+ return true;
102
+ }
103
+ catch {
104
+ return false;
105
+ }
106
+ }
107
+ /**
108
+ * Refresh Claude OAuth token using refresh token
109
+ */
110
+ export async function refreshClaudeToken(refreshToken) {
111
+ const body = JSON.stringify({
112
+ grant_type: "refresh_token",
113
+ refresh_token: refreshToken,
114
+ client_id: OAUTH_CONFIG.clientId,
115
+ });
116
+ const response = await fetch(OAUTH_CONFIG.tokenUrl, {
117
+ method: "POST",
118
+ headers: {
119
+ "Content-Type": "application/json",
120
+ "Content-Length": String(Buffer.byteLength(body)),
121
+ },
122
+ body,
123
+ });
124
+ if (response.ok) {
125
+ const data = await response.json();
126
+ const expiresAt = Date.now() + data.expires_in * 1000;
127
+ return {
128
+ accessToken: data.access_token,
129
+ refreshToken: data.refresh_token || refreshToken,
130
+ expiresAt,
131
+ };
132
+ }
133
+ // Parse error for actionable message
134
+ let errorMessage = `Token refresh failed (${response.status})`;
135
+ try {
136
+ const errorData = await response.json();
137
+ const oauthError = errorData.error || errorData.error_code;
138
+ switch (oauthError) {
139
+ case "invalid_grant":
140
+ errorMessage = "Refresh token expired or revoked. Run: claude login";
141
+ break;
142
+ case "invalid_client":
143
+ errorMessage = "OAuth client configuration error.";
144
+ break;
145
+ default:
146
+ errorMessage = errorData.error_description || `OAuth error: ${oauthError || response.status}`;
147
+ }
148
+ }
149
+ catch {
150
+ if (response.status === 400)
151
+ errorMessage = "Refresh token invalid. Run: claude login";
152
+ else if (response.status >= 500)
153
+ errorMessage = "Anthropic API error. Try again later.";
154
+ }
155
+ throw new Error(errorMessage);
156
+ }
157
+ /**
158
+ * Resolve credentials in priority order.
159
+ * Returns the first valid credential source found.
160
+ */
161
+ export function resolveCredentials() {
162
+ // 1. ANTHROPIC_API_KEY env var
163
+ const apiKey = process.env.ANTHROPIC_API_KEY;
164
+ if (apiKey) {
165
+ return { type: "api_key", apiKey };
166
+ }
167
+ // 2. Claude Code credentials file
168
+ const fileCreds = getClaudeCredentials();
169
+ if (fileCreds) {
170
+ return { type: "claude_oauth", credentials: fileCreds };
171
+ }
172
+ // 3. macOS Keychain
173
+ const keychainCreds = getClaudeKeychainCredentials();
174
+ if (keychainCreds) {
175
+ return { type: "claude_oauth", credentials: keychainCreds };
176
+ }
177
+ // 4. Codex credentials
178
+ const codexCreds = getCodexCredentials();
179
+ if (codexCreds) {
180
+ return { type: "codex_oauth", credentials: codexCreds };
181
+ }
182
+ return null;
183
+ }
184
+ /**
185
+ * Get a human-readable description of the credential source found.
186
+ */
187
+ export function describeCredentials() {
188
+ const source = resolveCredentials();
189
+ if (!source) {
190
+ return "No credentials found. Set ANTHROPIC_API_KEY or run `claude login`";
191
+ }
192
+ switch (source.type) {
193
+ case "api_key":
194
+ return "Found ANTHROPIC_API_KEY";
195
+ case "claude_oauth":
196
+ return "Found Claude Code credentials";
197
+ case "codex_oauth":
198
+ return "Found Codex credentials";
199
+ }
200
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Vertex AI Gemini Provider for Server-Side LLM Client
3
+ *
4
+ * Handles:
5
+ * - Service account JWT → OAuth token exchange
6
+ * - Gemini API request/response format
7
+ * - Streaming SSE parsing
8
+ * - Anthropic ↔ Gemini format conversion (canonical internal format is Anthropic)
9
+ *
10
+ * The server uses Anthropic's content block format internally.
11
+ * This module converts to/from Gemini's format at the API boundary.
12
+ */
13
+ import type { CallLLMParams, LLMResponse } from "./client.js";
14
+ /**
15
+ * Initialize Vertex AI with a service account JSON file path or object.
16
+ */
17
+ export declare function initVertex(serviceAccountPathOrJson: string | object, region?: string): void;
18
+ /**
19
+ * Check if Vertex AI is configured.
20
+ */
21
+ export declare function isVertexConfigured(): boolean;
22
+ export declare function callVertexLLM(params: CallLLMParams): Promise<LLMResponse>;