deepagentsdk 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +159 -0
- package/package.json +95 -0
- package/src/agent.ts +1230 -0
- package/src/backends/composite.ts +273 -0
- package/src/backends/filesystem.ts +692 -0
- package/src/backends/index.ts +22 -0
- package/src/backends/local-sandbox.ts +175 -0
- package/src/backends/persistent.ts +593 -0
- package/src/backends/sandbox.ts +510 -0
- package/src/backends/state.ts +244 -0
- package/src/backends/utils.ts +287 -0
- package/src/checkpointer/file-saver.ts +98 -0
- package/src/checkpointer/index.ts +5 -0
- package/src/checkpointer/kv-saver.ts +82 -0
- package/src/checkpointer/memory-saver.ts +82 -0
- package/src/checkpointer/types.ts +125 -0
- package/src/cli/components/ApiKeyInput.tsx +300 -0
- package/src/cli/components/FilePreview.tsx +237 -0
- package/src/cli/components/Input.tsx +277 -0
- package/src/cli/components/Message.tsx +93 -0
- package/src/cli/components/ModelSelection.tsx +338 -0
- package/src/cli/components/SlashMenu.tsx +101 -0
- package/src/cli/components/StatusBar.tsx +89 -0
- package/src/cli/components/Subagent.tsx +91 -0
- package/src/cli/components/TodoList.tsx +133 -0
- package/src/cli/components/ToolApproval.tsx +70 -0
- package/src/cli/components/ToolCall.tsx +144 -0
- package/src/cli/components/ToolCallSummary.tsx +175 -0
- package/src/cli/components/Welcome.tsx +75 -0
- package/src/cli/components/index.ts +24 -0
- package/src/cli/hooks/index.ts +12 -0
- package/src/cli/hooks/useAgent.ts +933 -0
- package/src/cli/index.tsx +1066 -0
- package/src/cli/theme.ts +205 -0
- package/src/cli/utils/model-list.ts +365 -0
- package/src/constants/errors.ts +29 -0
- package/src/constants/limits.ts +195 -0
- package/src/index.ts +176 -0
- package/src/middleware/agent-memory.ts +330 -0
- package/src/prompts.ts +196 -0
- package/src/skills/index.ts +2 -0
- package/src/skills/load.ts +191 -0
- package/src/skills/types.ts +53 -0
- package/src/tools/execute.ts +167 -0
- package/src/tools/filesystem.ts +418 -0
- package/src/tools/index.ts +39 -0
- package/src/tools/subagent.ts +443 -0
- package/src/tools/todos.ts +101 -0
- package/src/tools/web.ts +567 -0
- package/src/types/backend.ts +177 -0
- package/src/types/core.ts +220 -0
- package/src/types/events.ts +429 -0
- package/src/types/index.ts +94 -0
- package/src/types/structured-output.ts +43 -0
- package/src/types/subagent.ts +96 -0
- package/src/types.ts +22 -0
- package/src/utils/approval.ts +213 -0
- package/src/utils/events.ts +416 -0
- package/src/utils/eviction.ts +181 -0
- package/src/utils/index.ts +34 -0
- package/src/utils/model-parser.ts +38 -0
- package/src/utils/patch-tool-calls.ts +233 -0
- package/src/utils/project-detection.ts +32 -0
- package/src/utils/summarization.ts +254 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory checkpoint saver for testing and single-session use.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { Checkpoint, BaseCheckpointSaver, CheckpointSaverOptions } from "./types";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* In-memory checkpoint saver.
|
|
9
|
+
*
|
|
10
|
+
* Stores checkpoints in a Map. Data is lost when the process exits.
|
|
11
|
+
* Useful for testing or single-session applications.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* const saver = new MemorySaver();
|
|
16
|
+
* const agent = createDeepAgent({
|
|
17
|
+
* model: anthropic('claude-sonnet-4-20250514'),
|
|
18
|
+
* checkpointer: saver,
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export class MemorySaver implements BaseCheckpointSaver {
|
|
23
|
+
private checkpoints = new Map<string, Checkpoint>();
|
|
24
|
+
private namespace: string;
|
|
25
|
+
|
|
26
|
+
constructor(options: CheckpointSaverOptions = {}) {
|
|
27
|
+
this.namespace = options.namespace || "default";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private getKey(threadId: string): string {
|
|
31
|
+
return `${this.namespace}:${threadId}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async save(checkpoint: Checkpoint): Promise<void> {
|
|
35
|
+
const key = this.getKey(checkpoint.threadId);
|
|
36
|
+
this.checkpoints.set(key, {
|
|
37
|
+
...checkpoint,
|
|
38
|
+
updatedAt: new Date().toISOString(),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async load(threadId: string): Promise<Checkpoint | undefined> {
|
|
43
|
+
const key = this.getKey(threadId);
|
|
44
|
+
return this.checkpoints.get(key);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async list(): Promise<string[]> {
|
|
48
|
+
const prefix = `${this.namespace}:`;
|
|
49
|
+
const threadIds: string[] = [];
|
|
50
|
+
for (const key of this.checkpoints.keys()) {
|
|
51
|
+
if (key.startsWith(prefix)) {
|
|
52
|
+
threadIds.push(key.substring(prefix.length));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return threadIds;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async delete(threadId: string): Promise<void> {
|
|
59
|
+
const key = this.getKey(threadId);
|
|
60
|
+
this.checkpoints.delete(key);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async exists(threadId: string): Promise<boolean> {
|
|
64
|
+
const key = this.getKey(threadId);
|
|
65
|
+
return this.checkpoints.has(key);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Clear all checkpoints (useful for testing).
|
|
70
|
+
*/
|
|
71
|
+
clear(): void {
|
|
72
|
+
this.checkpoints.clear();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get the number of stored checkpoints.
|
|
77
|
+
*/
|
|
78
|
+
size(): number {
|
|
79
|
+
return this.checkpoints.size;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for checkpointer support.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { DeepAgentState, ModelMessage } from "../types";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Data stored in a checkpoint.
|
|
9
|
+
* Contains everything needed to resume an agent session.
|
|
10
|
+
*/
|
|
11
|
+
export interface Checkpoint {
|
|
12
|
+
/** Unique identifier for the conversation thread */
|
|
13
|
+
threadId: string;
|
|
14
|
+
|
|
15
|
+
/** Step number when this checkpoint was created */
|
|
16
|
+
step: number;
|
|
17
|
+
|
|
18
|
+
/** Conversation history (serialized messages) */
|
|
19
|
+
messages: ModelMessage[];
|
|
20
|
+
|
|
21
|
+
/** Agent state (todos and StateBackend files) */
|
|
22
|
+
state: DeepAgentState;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Interrupt data if the agent was paused mid-execution.
|
|
26
|
+
* Present when waiting for tool approval.
|
|
27
|
+
*/
|
|
28
|
+
interrupt?: InterruptData;
|
|
29
|
+
|
|
30
|
+
/** ISO 8601 timestamp when checkpoint was created */
|
|
31
|
+
createdAt: string;
|
|
32
|
+
|
|
33
|
+
/** ISO 8601 timestamp when checkpoint was last updated */
|
|
34
|
+
updatedAt: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Data about an interrupted tool execution.
|
|
39
|
+
* Used to resume from approval requests.
|
|
40
|
+
*/
|
|
41
|
+
export interface InterruptData {
|
|
42
|
+
/** The tool call that requires approval */
|
|
43
|
+
toolCall: {
|
|
44
|
+
toolCallId: string;
|
|
45
|
+
toolName: string;
|
|
46
|
+
args: unknown;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/** Step number where interrupt occurred */
|
|
50
|
+
step: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Decision to resume from an interrupt.
|
|
55
|
+
*/
|
|
56
|
+
export interface ResumeDecision {
|
|
57
|
+
/** Type of decision */
|
|
58
|
+
type: 'approve' | 'deny';
|
|
59
|
+
|
|
60
|
+
/** Optional modified arguments (for future "edit" feature) */
|
|
61
|
+
modifiedArgs?: unknown;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Options for resuming from a checkpoint.
|
|
66
|
+
*/
|
|
67
|
+
export interface ResumeOptions {
|
|
68
|
+
/** Decisions for pending tool approvals */
|
|
69
|
+
decisions: ResumeDecision[];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Interface for checkpoint storage implementations.
|
|
74
|
+
*
|
|
75
|
+
* Implement this interface to use any storage backend (memory, files,
|
|
76
|
+
* Redis, database, etc.) for persisting checkpoints.
|
|
77
|
+
*/
|
|
78
|
+
export interface BaseCheckpointSaver {
|
|
79
|
+
/**
|
|
80
|
+
* Save a checkpoint.
|
|
81
|
+
* If a checkpoint for the threadId already exists, it is overwritten.
|
|
82
|
+
*
|
|
83
|
+
* @param checkpoint - The checkpoint data to save
|
|
84
|
+
*/
|
|
85
|
+
save(checkpoint: Checkpoint): Promise<void>;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Load the latest checkpoint for a thread.
|
|
89
|
+
*
|
|
90
|
+
* @param threadId - The thread identifier
|
|
91
|
+
* @returns The checkpoint, or undefined if not found
|
|
92
|
+
*/
|
|
93
|
+
load(threadId: string): Promise<Checkpoint | undefined>;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* List all thread IDs with saved checkpoints.
|
|
97
|
+
*
|
|
98
|
+
* @returns Array of thread IDs
|
|
99
|
+
*/
|
|
100
|
+
list(): Promise<string[]>;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Delete a checkpoint.
|
|
104
|
+
*
|
|
105
|
+
* @param threadId - The thread identifier to delete
|
|
106
|
+
*/
|
|
107
|
+
delete(threadId: string): Promise<void>;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Check if a checkpoint exists for a thread.
|
|
111
|
+
*
|
|
112
|
+
* @param threadId - The thread identifier
|
|
113
|
+
* @returns True if checkpoint exists
|
|
114
|
+
*/
|
|
115
|
+
exists(threadId: string): Promise<boolean>;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Options for creating a checkpoint saver.
|
|
120
|
+
*/
|
|
121
|
+
export interface CheckpointSaverOptions {
|
|
122
|
+
/** Optional namespace prefix for isolation */
|
|
123
|
+
namespace?: string;
|
|
124
|
+
}
|
|
125
|
+
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Key Input Panel - Interactive provider selection and key input.
|
|
3
|
+
* Shows current status and allows adding/updating keys.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useState } from "react";
|
|
7
|
+
import { Box, Text, useInput } from "ink";
|
|
8
|
+
import { colors, emoji } from "../theme.js";
|
|
9
|
+
|
|
10
|
+
type Provider = "anthropic" | "openai";
|
|
11
|
+
|
|
12
|
+
interface ApiKeyInputPanelProps {
|
|
13
|
+
/** Callback when API key is saved */
|
|
14
|
+
onKeySaved?: (provider: Provider, key: string) => void;
|
|
15
|
+
/** Callback to close the panel */
|
|
16
|
+
onClose?: () => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type Step = "select-provider" | "enter-key" | "success";
|
|
20
|
+
|
|
21
|
+
export function ApiKeyInputPanel({
|
|
22
|
+
onKeySaved,
|
|
23
|
+
onClose,
|
|
24
|
+
}: ApiKeyInputPanelProps): React.ReactElement {
|
|
25
|
+
const [step, setStep] = useState<Step>("select-provider");
|
|
26
|
+
const [selectedProvider, setSelectedProvider] = useState<Provider | null>(null);
|
|
27
|
+
const [apiKey, setApiKey] = useState("");
|
|
28
|
+
const [error, setError] = useState<string | null>(null);
|
|
29
|
+
|
|
30
|
+
// Get current API keys
|
|
31
|
+
const anthropicKey = process.env.ANTHROPIC_API_KEY;
|
|
32
|
+
const openaiKey = process.env.OPENAI_API_KEY;
|
|
33
|
+
|
|
34
|
+
const maskKey = (key: string | undefined) => {
|
|
35
|
+
if (!key) return null;
|
|
36
|
+
if (key.length <= 8) return "•".repeat(key.length);
|
|
37
|
+
return key.substring(0, 10) + "..." + key.substring(key.length - 4);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Handle keyboard input
|
|
41
|
+
useInput((input, key) => {
|
|
42
|
+
if (step === "select-provider") {
|
|
43
|
+
if (input === "1" || input.toLowerCase() === "a") {
|
|
44
|
+
setSelectedProvider("anthropic");
|
|
45
|
+
setStep("enter-key");
|
|
46
|
+
setError(null);
|
|
47
|
+
// Pre-fill existing key if available
|
|
48
|
+
if (anthropicKey) {
|
|
49
|
+
setApiKey(anthropicKey);
|
|
50
|
+
}
|
|
51
|
+
} else if (input === "2" || input.toLowerCase() === "o") {
|
|
52
|
+
setSelectedProvider("openai");
|
|
53
|
+
setStep("enter-key");
|
|
54
|
+
setError(null);
|
|
55
|
+
// Pre-fill existing key if available
|
|
56
|
+
if (openaiKey) {
|
|
57
|
+
setApiKey(openaiKey);
|
|
58
|
+
}
|
|
59
|
+
} else if (key.escape) {
|
|
60
|
+
onClose?.();
|
|
61
|
+
}
|
|
62
|
+
} else if (step === "enter-key") {
|
|
63
|
+
if (key.escape) {
|
|
64
|
+
// Go back to provider selection
|
|
65
|
+
setStep("select-provider");
|
|
66
|
+
setApiKey("");
|
|
67
|
+
setSelectedProvider(null);
|
|
68
|
+
setError(null);
|
|
69
|
+
} else if (key.return) {
|
|
70
|
+
// Validate and save
|
|
71
|
+
if (!apiKey.trim()) {
|
|
72
|
+
setError("API key cannot be empty");
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Basic validation
|
|
77
|
+
if (selectedProvider === "anthropic" && !apiKey.startsWith("sk-ant-")) {
|
|
78
|
+
setError("Anthropic API keys typically start with 'sk-ant-'");
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (selectedProvider === "openai" && !apiKey.startsWith("sk-")) {
|
|
82
|
+
setError("OpenAI API keys typically start with 'sk-'");
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Save to environment
|
|
87
|
+
if (selectedProvider === "anthropic") {
|
|
88
|
+
process.env.ANTHROPIC_API_KEY = apiKey.trim();
|
|
89
|
+
} else if (selectedProvider === "openai") {
|
|
90
|
+
process.env.OPENAI_API_KEY = apiKey.trim();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
setStep("success");
|
|
94
|
+
onKeySaved?.(selectedProvider!, apiKey.trim());
|
|
95
|
+
|
|
96
|
+
// Auto-return to provider selection after success
|
|
97
|
+
setTimeout(() => {
|
|
98
|
+
setStep("select-provider");
|
|
99
|
+
setApiKey("");
|
|
100
|
+
setSelectedProvider(null);
|
|
101
|
+
}, 1500);
|
|
102
|
+
} else if (key.backspace || key.delete) {
|
|
103
|
+
setApiKey((prev) => prev.slice(0, -1));
|
|
104
|
+
setError(null);
|
|
105
|
+
} else if (input && !key.ctrl && !key.meta) {
|
|
106
|
+
setApiKey((prev) => prev + input);
|
|
107
|
+
setError(null);
|
|
108
|
+
}
|
|
109
|
+
} else if (step === "success") {
|
|
110
|
+
if (key.return || key.escape) {
|
|
111
|
+
// Return to provider selection
|
|
112
|
+
setStep("select-provider");
|
|
113
|
+
setApiKey("");
|
|
114
|
+
setSelectedProvider(null);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const maskKeyForInput = (key: string): string => {
|
|
120
|
+
if (key.length <= 8) return "•".repeat(key.length);
|
|
121
|
+
return key.substring(0, 7) + "•".repeat(Math.min(key.length - 11, 20)) + key.substring(key.length - 4);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<Box
|
|
126
|
+
flexDirection="column"
|
|
127
|
+
borderStyle="single"
|
|
128
|
+
borderColor={colors.primary}
|
|
129
|
+
paddingX={2}
|
|
130
|
+
paddingY={1}
|
|
131
|
+
marginY={1}
|
|
132
|
+
>
|
|
133
|
+
<Text bold color={colors.info}>
|
|
134
|
+
{emoji.key} API Key Management
|
|
135
|
+
</Text>
|
|
136
|
+
<Box height={1} />
|
|
137
|
+
|
|
138
|
+
{/* Always show current status */}
|
|
139
|
+
<Text bold>Current Status:</Text>
|
|
140
|
+
<Box height={1} />
|
|
141
|
+
<Box marginLeft={2}>
|
|
142
|
+
{anthropicKey ? (
|
|
143
|
+
<>
|
|
144
|
+
<Text color={colors.success}>✓ </Text>
|
|
145
|
+
<Text>Anthropic: </Text>
|
|
146
|
+
<Text dimColor>{maskKey(anthropicKey)}</Text>
|
|
147
|
+
</>
|
|
148
|
+
) : (
|
|
149
|
+
<>
|
|
150
|
+
<Text color={colors.warning}>✗ </Text>
|
|
151
|
+
<Text>Anthropic: </Text>
|
|
152
|
+
<Text dimColor>not set</Text>
|
|
153
|
+
</>
|
|
154
|
+
)}
|
|
155
|
+
</Box>
|
|
156
|
+
<Box marginLeft={2}>
|
|
157
|
+
{openaiKey ? (
|
|
158
|
+
<>
|
|
159
|
+
<Text color={colors.success}>✓ </Text>
|
|
160
|
+
<Text>OpenAI: </Text>
|
|
161
|
+
<Text dimColor>{maskKey(openaiKey)}</Text>
|
|
162
|
+
</>
|
|
163
|
+
) : (
|
|
164
|
+
<>
|
|
165
|
+
<Text color={colors.warning}>✗ </Text>
|
|
166
|
+
<Text>OpenAI: </Text>
|
|
167
|
+
<Text dimColor>not set</Text>
|
|
168
|
+
</>
|
|
169
|
+
)}
|
|
170
|
+
</Box>
|
|
171
|
+
<Box height={1} />
|
|
172
|
+
|
|
173
|
+
{step === "select-provider" && (
|
|
174
|
+
<>
|
|
175
|
+
<Text bold>Add or Update Key:</Text>
|
|
176
|
+
<Box height={1} />
|
|
177
|
+
<Box marginLeft={2}>
|
|
178
|
+
<Text color={colors.primary}>[1]</Text>
|
|
179
|
+
<Text> Anthropic (Claude)</Text>
|
|
180
|
+
{anthropicKey && <Text dimColor> (overwrite)</Text>}
|
|
181
|
+
</Box>
|
|
182
|
+
<Box marginLeft={2}>
|
|
183
|
+
<Text color={colors.primary}>[2]</Text>
|
|
184
|
+
<Text> OpenAI (GPT)</Text>
|
|
185
|
+
{openaiKey && <Text dimColor> (overwrite)</Text>}
|
|
186
|
+
</Box>
|
|
187
|
+
<Box height={1} />
|
|
188
|
+
<Text dimColor>Press 1 or 2 to select, Esc to close</Text>
|
|
189
|
+
</>
|
|
190
|
+
)}
|
|
191
|
+
|
|
192
|
+
{step === "enter-key" && selectedProvider && (
|
|
193
|
+
<>
|
|
194
|
+
<Text>
|
|
195
|
+
Enter your{" "}
|
|
196
|
+
<Text color={colors.primary}>
|
|
197
|
+
{selectedProvider === "anthropic" ? "Anthropic" : "OpenAI"}
|
|
198
|
+
</Text>{" "}
|
|
199
|
+
API key:
|
|
200
|
+
{selectedProvider === "anthropic" && anthropicKey && (
|
|
201
|
+
<Text dimColor> (current: {maskKey(anthropicKey)})</Text>
|
|
202
|
+
)}
|
|
203
|
+
{selectedProvider === "openai" && openaiKey && (
|
|
204
|
+
<Text dimColor> (current: {maskKey(openaiKey)})</Text>
|
|
205
|
+
)}
|
|
206
|
+
</Text>
|
|
207
|
+
<Box height={1} />
|
|
208
|
+
<Box>
|
|
209
|
+
<Text dimColor>{">"} </Text>
|
|
210
|
+
<Text>{apiKey ? maskKeyForInput(apiKey) : <Text dimColor>Paste your API key here...</Text>}</Text>
|
|
211
|
+
<Text color={colors.primary}>█</Text>
|
|
212
|
+
</Box>
|
|
213
|
+
{error && (
|
|
214
|
+
<>
|
|
215
|
+
<Box height={1} />
|
|
216
|
+
<Text color={colors.error}>{emoji.warning} {error}</Text>
|
|
217
|
+
</>
|
|
218
|
+
)}
|
|
219
|
+
<Box height={1} />
|
|
220
|
+
<Text dimColor>Press Enter to save, Esc to go back</Text>
|
|
221
|
+
</>
|
|
222
|
+
)}
|
|
223
|
+
|
|
224
|
+
{step === "success" && selectedProvider && (
|
|
225
|
+
<>
|
|
226
|
+
<Text color={colors.success}>
|
|
227
|
+
{emoji.completed} API key saved for{" "}
|
|
228
|
+
{selectedProvider === "anthropic" ? "Anthropic" : "OpenAI"}!
|
|
229
|
+
</Text>
|
|
230
|
+
<Box height={1} />
|
|
231
|
+
<Text dimColor>Press Enter or Esc to return to menu</Text>
|
|
232
|
+
</>
|
|
233
|
+
)}
|
|
234
|
+
</Box>
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Simple API Key Status display (read-only).
|
|
240
|
+
*/
|
|
241
|
+
export function ApiKeyStatus(): React.ReactElement {
|
|
242
|
+
const anthropicKey = process.env.ANTHROPIC_API_KEY;
|
|
243
|
+
const openaiKey = process.env.OPENAI_API_KEY;
|
|
244
|
+
|
|
245
|
+
const maskKey = (key: string | undefined) => {
|
|
246
|
+
if (!key) return null;
|
|
247
|
+
return key.substring(0, 10) + "..." + key.substring(key.length - 4);
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
return (
|
|
251
|
+
<Box
|
|
252
|
+
flexDirection="column"
|
|
253
|
+
borderStyle="single"
|
|
254
|
+
borderColor={colors.muted}
|
|
255
|
+
paddingX={2}
|
|
256
|
+
paddingY={1}
|
|
257
|
+
marginY={1}
|
|
258
|
+
>
|
|
259
|
+
<Text bold color={colors.info}>
|
|
260
|
+
{emoji.key} API Keys
|
|
261
|
+
</Text>
|
|
262
|
+
<Box height={1} />
|
|
263
|
+
<Box>
|
|
264
|
+
{anthropicKey ? (
|
|
265
|
+
<>
|
|
266
|
+
<Text color={colors.success}>✓ </Text>
|
|
267
|
+
<Text>Anthropic: </Text>
|
|
268
|
+
<Text dimColor>{maskKey(anthropicKey)}</Text>
|
|
269
|
+
</>
|
|
270
|
+
) : (
|
|
271
|
+
<>
|
|
272
|
+
<Text color={colors.warning}>✗ </Text>
|
|
273
|
+
<Text>Anthropic: </Text>
|
|
274
|
+
<Text dimColor>not set</Text>
|
|
275
|
+
</>
|
|
276
|
+
)}
|
|
277
|
+
</Box>
|
|
278
|
+
<Box>
|
|
279
|
+
{openaiKey ? (
|
|
280
|
+
<>
|
|
281
|
+
<Text color={colors.success}>✓ </Text>
|
|
282
|
+
<Text>OpenAI: </Text>
|
|
283
|
+
<Text dimColor>{maskKey(openaiKey)}</Text>
|
|
284
|
+
</>
|
|
285
|
+
) : (
|
|
286
|
+
<>
|
|
287
|
+
<Text color={colors.warning}>✗ </Text>
|
|
288
|
+
<Text>OpenAI: </Text>
|
|
289
|
+
<Text dimColor>not set</Text>
|
|
290
|
+
</>
|
|
291
|
+
)}
|
|
292
|
+
</Box>
|
|
293
|
+
<Box height={1} />
|
|
294
|
+
<Text dimColor>Use /apikey set to add or update keys</Text>
|
|
295
|
+
</Box>
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
|