@devang0907/agent-dev 0.1.1 → 0.1.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/dist/agent/loop.js +1 -1
- package/dist/ui/ApiKeyPrompt.d.ts +12 -0
- package/dist/ui/ApiKeyPrompt.js +42 -0
- package/dist/ui/App.js +23 -0
- package/dist/ui/ModelSelector.js +2 -3
- package/dist/ui/StartupBanner.js +37 -71
- package/package.json +1 -1
package/dist/agent/loop.js
CHANGED
|
@@ -13,7 +13,7 @@ async function collectStream(model, messages, settings, systemPrompt, signal, on
|
|
|
13
13
|
systemPrompt,
|
|
14
14
|
thinkingLevel: settings.thinkingLevel,
|
|
15
15
|
signal,
|
|
16
|
-
});
|
|
16
|
+
}, settings);
|
|
17
17
|
for await (const event of stream) {
|
|
18
18
|
if (event.type === "text_delta") {
|
|
19
19
|
content += event.delta;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { ThemeColors } from "./theme.js";
|
|
3
|
+
import type { Model, ProviderId } from "../providers/types.js";
|
|
4
|
+
interface ApiKeyPromptProps {
|
|
5
|
+
theme: ThemeColors;
|
|
6
|
+
provider: ProviderId;
|
|
7
|
+
model: Model;
|
|
8
|
+
onSubmit: (apiKey: string) => void;
|
|
9
|
+
onCancel: () => void;
|
|
10
|
+
}
|
|
11
|
+
export declare function ApiKeyPrompt({ theme, provider, model, onSubmit, onCancel }: ApiKeyPromptProps): React.JSX.Element;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect } from "react";
|
|
3
|
+
import { Box, Text, useInput } from "ink";
|
|
4
|
+
import { PROVIDER_LABELS } from "../config/models.js";
|
|
5
|
+
import { PROVIDER_ENV_VARS } from "../providers/registry.js";
|
|
6
|
+
import { LeftBorder } from "./LeftBorder.js";
|
|
7
|
+
import { Panel } from "./Panel.js";
|
|
8
|
+
function BlinkingCursor({ theme, visible }) {
|
|
9
|
+
if (!visible)
|
|
10
|
+
return null;
|
|
11
|
+
return _jsx(Text, { color: theme.primary, children: "\u258C" });
|
|
12
|
+
}
|
|
13
|
+
export function ApiKeyPrompt({ theme, provider, model, onSubmit, onCancel }) {
|
|
14
|
+
const [value, setValue] = useState("");
|
|
15
|
+
const [cursorOn, setCursorOn] = useState(true);
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
const id = setInterval(() => setCursorOn((v) => !v), 530);
|
|
18
|
+
return () => clearInterval(id);
|
|
19
|
+
}, []);
|
|
20
|
+
useInput((input, key) => {
|
|
21
|
+
if (key.escape) {
|
|
22
|
+
onCancel();
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (key.return) {
|
|
26
|
+
const trimmed = value.trim();
|
|
27
|
+
if (trimmed)
|
|
28
|
+
onSubmit(trimmed);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (key.backspace || key.delete) {
|
|
32
|
+
setValue((v) => v.slice(0, -1));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (input && !key.ctrl && !key.meta) {
|
|
36
|
+
setValue((v) => v + input);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
const envVars = PROVIDER_ENV_VARS[provider];
|
|
40
|
+
const masked = "•".repeat(value.length);
|
|
41
|
+
return (_jsx(Box, { paddingX: 2, marginTop: 1, children: _jsxs(LeftBorder, { theme: theme, borderColor: theme.borderActive, children: [_jsx(Text, { color: theme.text, bold: true, children: "API key required" }), _jsx(Text, { color: theme.textMuted, children: " Enter save \u00B7 Esc back" }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { color: theme.textMuted, children: ["Provider: ", PROVIDER_LABELS[provider]] }), _jsxs(Text, { color: theme.textMuted, children: ["Model: ", model.name] })] }), _jsx(Box, { marginTop: 1, children: _jsx(Panel, { theme: theme, borderColor: theme.primary, marginBottom: 0, children: _jsx(Box, { flexDirection: "row", children: value.length > 0 ? (_jsxs(_Fragment, { children: [_jsx(Text, { color: theme.text, children: masked }), _jsx(BlinkingCursor, { theme: theme, visible: cursorOn })] })) : (_jsxs(_Fragment, { children: [_jsx(Text, { color: theme.textMuted, children: "Paste API key\u2026" }), _jsx(BlinkingCursor, { theme: theme, visible: cursorOn })] })) }) }) }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: theme.textMuted, children: ["Or set env: ", envVars.join(" · ")] }) })] }) }));
|
|
42
|
+
}
|
package/dist/ui/App.js
CHANGED
|
@@ -5,7 +5,9 @@ import { ChatView } from "./ChatView.js";
|
|
|
5
5
|
import { Editor } from "./Editor.js";
|
|
6
6
|
import { Footer } from "./Footer.js";
|
|
7
7
|
import { ModelSelector } from "./ModelSelector.js";
|
|
8
|
+
import { ApiKeyPrompt } from "./ApiKeyPrompt.js";
|
|
8
9
|
import { SettingsView } from "./SettingsView.js";
|
|
10
|
+
import { hasProviderAuth } from "../providers/registry.js";
|
|
9
11
|
import { StartupBanner } from "./StartupBanner.js";
|
|
10
12
|
import { getTheme } from "./theme.js";
|
|
11
13
|
import { saveSettings } from "../config/settings.js";
|
|
@@ -19,6 +21,7 @@ export function App({ session, workdir, onQuit }) {
|
|
|
19
21
|
const [streamingText, setStreamingText] = useState("");
|
|
20
22
|
const [overlay, setOverlay] = useState("none");
|
|
21
23
|
const [modelFilter, setModelFilter] = useState();
|
|
24
|
+
const [pendingModel, setPendingModel] = useState(null);
|
|
22
25
|
const [settings, setSettings] = useState(session.getSettings());
|
|
23
26
|
const [model, setModel] = useState(session.getModel());
|
|
24
27
|
const [running, setRunning] = useState(false);
|
|
@@ -118,6 +121,11 @@ export function App({ session, workdir, onQuit }) {
|
|
|
118
121
|
}, [session, running, onQuit, exit]);
|
|
119
122
|
const hasChat = displayMessages.length > 0 || streamingText.length > 0;
|
|
120
123
|
return (_jsxs(Box, { flexDirection: "column", height: "100%", children: [_jsx(Box, { paddingX: 2, marginBottom: 1, children: _jsx(StartupBanner, { theme: theme, compact: hasChat }) }), _jsx(ChatView, { messages: displayMessages, theme: theme, model: model, streamingText: streamingText, running: running }), _jsx(Footer, { workdir: workdir, model: model, theme: theme }), overlay === "none" && (_jsx(Editor, { theme: theme, model: model, disabled: running, running: running, onSubmit: handleSubmit })), overlay === "model" && (_jsx(ModelSelector, { theme: theme, settings: settings, filter: modelFilter, onSelect: (m) => {
|
|
124
|
+
if (!hasProviderAuth(m.provider, settings)) {
|
|
125
|
+
setPendingModel(m);
|
|
126
|
+
setOverlay("apiKey");
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
121
129
|
session.setModel(m);
|
|
122
130
|
setModel(m);
|
|
123
131
|
setOverlay("none");
|
|
@@ -125,6 +133,21 @@ export function App({ session, workdir, onQuit }) {
|
|
|
125
133
|
}, onClose: () => {
|
|
126
134
|
setOverlay("none");
|
|
127
135
|
setModelFilter(undefined);
|
|
136
|
+
} })), overlay === "apiKey" && pendingModel && (_jsx(ApiKeyPrompt, { theme: theme, provider: pendingModel.provider, model: pendingModel, onSubmit: (apiKey) => {
|
|
137
|
+
const updated = {
|
|
138
|
+
...settings,
|
|
139
|
+
apiKeys: { ...settings.apiKeys, [pendingModel.provider]: apiKey },
|
|
140
|
+
};
|
|
141
|
+
session.updateSettings(updated);
|
|
142
|
+
setSettings(updated);
|
|
143
|
+
session.setModel(pendingModel);
|
|
144
|
+
setModel(pendingModel);
|
|
145
|
+
setPendingModel(null);
|
|
146
|
+
setOverlay("none");
|
|
147
|
+
setModelFilter(undefined);
|
|
148
|
+
}, onCancel: () => {
|
|
149
|
+
setPendingModel(null);
|
|
150
|
+
setOverlay("model");
|
|
128
151
|
} })), overlay === "settings" && (_jsx(SettingsView, { theme: theme, settings: settings, onUpdate: (s) => {
|
|
129
152
|
session.updateSettings(s);
|
|
130
153
|
setSettings(s);
|
package/dist/ui/ModelSelector.js
CHANGED
|
@@ -33,12 +33,11 @@ export function ModelSelector({ theme, settings, filter, onSelect, onClose }) {
|
|
|
33
33
|
});
|
|
34
34
|
const providers = ["openai", "groq", "gemini", "free"];
|
|
35
35
|
let lastProvider;
|
|
36
|
-
return (_jsx(Box, { paddingX: 2, marginTop: 1, children: _jsxs(LeftBorder, { theme: theme, borderColor: theme.borderActive, children: [_jsx(Text, { color: theme.text, bold: true, children: "/model" }), _jsx(Text, { color: theme.textMuted, children: " \u2191\u2193 navigate \u00B7 Enter select \u00B7 Esc close" }), filter && _jsxs(Text, { color: theme.textMuted, children: [" filter: ", filter] }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [filtered.length === 0 && _jsx(Text, { color: theme.textMuted, children: "No models match" }), filtered.map((m, i) => {
|
|
37
|
-
const hasKey = hasProviderAuth(m.provider, settings);
|
|
36
|
+
return (_jsx(Box, { paddingX: 2, marginTop: 1, children: _jsxs(LeftBorder, { theme: theme, borderColor: theme.borderActive, children: [_jsx(Text, { color: theme.text, bold: true, children: "/model" }), _jsx(Text, { color: theme.textMuted, children: " \u2191\u2193 navigate \u00B7 Enter select \u00B7 Esc close" }), _jsx(Text, { color: theme.textMuted, children: " Models without a key will prompt for an API key" }), filter && _jsxs(Text, { color: theme.textMuted, children: [" filter: ", filter] }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [filtered.length === 0 && _jsx(Text, { color: theme.textMuted, children: "No models match" }), filtered.map((m, i) => {
|
|
38
37
|
const selected = i === safeIndex;
|
|
39
38
|
const showHeader = m.provider !== lastProvider;
|
|
40
39
|
lastProvider = m.provider;
|
|
41
|
-
return (_jsxs(Box, { flexDirection: "column", children: [showHeader && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.textMuted, children: PROVIDER_LABELS[m.provider] }) })), _jsxs(Text, { color: selected ? theme.primary : theme.text, children: [selected ? "› " : " ", m.name,
|
|
40
|
+
return (_jsxs(Box, { flexDirection: "column", children: [showHeader && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.textMuted, children: PROVIDER_LABELS[m.provider] }) })), _jsxs(Text, { color: selected ? theme.primary : theme.text, children: [selected ? "› " : " ", m.name, selected && _jsxs(Text, { color: theme.textMuted, children: [" ", modelRef(m)] })] })] }, modelRef(m)));
|
|
42
41
|
})] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.textMuted, children: providers.map((p) => {
|
|
43
42
|
const ok = hasProviderAuth(p, settings);
|
|
44
43
|
return `${ok ? "●" : "○"} ${p}`;
|
package/dist/ui/StartupBanner.js
CHANGED
|
@@ -1,82 +1,48 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { memo } from "react";
|
|
3
3
|
import { Box, Text } from "ink";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
4
|
+
/**
|
|
5
|
+
* 5x7 block-letter glyphs for the "AGENT-DEV" wordmark.
|
|
6
|
+
* "█" = lit pixel, " " = empty pixel. Every glyph is exactly 5 columns
|
|
7
|
+
* wide and 7 rows tall, so glyphs line up cleanly when concatenated
|
|
8
|
+
* row-by-row to spell out a word.
|
|
9
|
+
*/
|
|
10
|
+
const GLYPH_HEIGHT = 7;
|
|
11
|
+
const GLYPH_WIDTH = 5;
|
|
12
|
+
const GLYPHS = {
|
|
13
|
+
A: [" █ ", " █ █ ", "█ █", "█████", "█ █", "█ █", "█ █"],
|
|
14
|
+
G: [" ███ ", "█ ", "█ ", "█ ███", "█ █", "█ █", " ███ "],
|
|
15
|
+
E: ["█████", "█ ", "█ ", "████ ", "█ ", "█ ", "█████"],
|
|
16
|
+
N: ["█ █", "██ █", "█ █ █", "█ ██", "█ █", "█ █", "█ █"],
|
|
17
|
+
T: ["█████", " █ ", " █ ", " █ ", " █ ", " █ ", " █ "],
|
|
18
|
+
"-": [" ", " ", " ", "█████", " ", " ", " "],
|
|
19
|
+
D: ["████ ", "█ █", "█ █", "█ █", "█ █", "█ █", "████ "],
|
|
20
|
+
V: ["█ █", "█ █", "█ █", "█ █", " █ █ ", " █ █ ", " █ "],
|
|
13
21
|
};
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return
|
|
22
|
+
const BLANK_GLYPH = Array(GLYPH_HEIGHT).fill(" ".repeat(GLYPH_WIDTH));
|
|
23
|
+
/** Wordmark color — matches the brand logo regardless of active theme. */
|
|
24
|
+
const LOGO_COLOR = "#F2A154";
|
|
25
|
+
function glyphFor(char) {
|
|
26
|
+
return GLYPHS[char.toUpperCase()] ?? BLANK_GLYPH;
|
|
19
27
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
segments.push({ text: run, color: runColor });
|
|
28
|
-
run = char;
|
|
29
|
-
runColor = color;
|
|
30
|
-
}
|
|
31
|
-
else {
|
|
32
|
-
run += char;
|
|
33
|
-
runColor = color;
|
|
34
|
-
}
|
|
28
|
+
/** Builds the word as GLYPH_HEIGHT lines of text, gap columns between letters. */
|
|
29
|
+
function buildBlockLines(word, gap = 1) {
|
|
30
|
+
const glyphs = word.split("").map(glyphFor);
|
|
31
|
+
const gapStr = " ".repeat(gap);
|
|
32
|
+
const lines = [];
|
|
33
|
+
for (let row = 0; row < GLYPH_HEIGHT; row++) {
|
|
34
|
+
lines.push(glyphs.map((g) => g[row]).join(gapStr));
|
|
35
35
|
}
|
|
36
|
-
|
|
37
|
-
segments.push({ text: run, color: runColor });
|
|
38
|
-
return (_jsx(Text, { children: segments.map((s, i) => (_jsx(Text, { color: s.color, children: s.text }, i))) }));
|
|
36
|
+
return lines;
|
|
39
37
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return text.slice(0, INNER);
|
|
43
|
-
const pad = INNER - text.length;
|
|
44
|
-
const left = Math.floor(pad / 2);
|
|
45
|
-
return " ".repeat(left) + text + " ".repeat(pad - left);
|
|
46
|
-
}
|
|
47
|
-
function row(content) {
|
|
48
|
-
const inner = content.length > INNER ? content.slice(0, INNER) : content.padEnd(INNER);
|
|
49
|
-
return "|" + inner + "|";
|
|
50
|
-
}
|
|
51
|
-
function border() {
|
|
52
|
-
return "+" + "-".repeat(INNER) + "+";
|
|
53
|
-
}
|
|
54
|
-
function buildRobotLines() {
|
|
55
|
-
// Every line in `head` is built to the same fixed width (23 chars) so
|
|
56
|
-
// center() applies identical padding to each one and the face stays
|
|
57
|
-
// aligned: antenna -> rounded head -> eyes -> mouth grille -> bolts.
|
|
58
|
-
const head = [
|
|
59
|
-
"." + "-".repeat(21) + ".", // head top, rounded corners
|
|
60
|
-
"|" + " ".repeat(6) + "@@" + " ".repeat(5) + "@@" + " ".repeat(6) + "|", // eyes
|
|
61
|
-
"|" + " ".repeat(21) + "|", // visor gap
|
|
62
|
-
"|" + " ".repeat(6) + "# # # # #" + " ".repeat(6) + "|", // mouth grille
|
|
63
|
-
"'" + "-".repeat(21) + "'", // head bottom, rounded corners
|
|
64
|
-
" ".repeat(9) + "+" + " ".repeat(3) + "+" + " ".repeat(9), // neck bolts
|
|
65
|
-
];
|
|
66
|
-
return [
|
|
67
|
-
border(),
|
|
68
|
-
row(center("#%@$^&* AGENT-DEV *&^$@#%")),
|
|
69
|
-
row(""),
|
|
70
|
-
...head.map((line) => row(center(line))),
|
|
71
|
-
row(""),
|
|
72
|
-
border(),
|
|
73
|
-
];
|
|
74
|
-
}
|
|
75
|
-
const ROBOT_LINES = buildRobotLines();
|
|
76
|
-
const ROBOT_MINI = "<@ @> AGENT-DEV <@ @>";
|
|
38
|
+
const LOGO_LINES = buildBlockLines("AGENT-DEV");
|
|
39
|
+
const LOGO_MINI = "AGENT-DEV";
|
|
77
40
|
export const StartupBanner = memo(function StartupBanner({ theme, compact, }) {
|
|
41
|
+
// Falls back to the brand orange; lets a theme override via an
|
|
42
|
+
// optional `accent` field without forcing it into ThemeColors.
|
|
43
|
+
const color = theme.accent ?? LOGO_COLOR;
|
|
78
44
|
if (compact) {
|
|
79
|
-
return (_jsx(Box, { marginBottom: 1, children: _jsx(
|
|
45
|
+
return (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: color, bold: true, children: LOGO_MINI }) }));
|
|
80
46
|
}
|
|
81
|
-
return (_jsx(Box, { flexDirection: "column", marginBottom: 1, children:
|
|
47
|
+
return (_jsx(Box, { flexDirection: "column", marginBottom: 1, children: LOGO_LINES.map((line, i) => (_jsx(Text, { color: color, bold: true, children: line }, i))) }));
|
|
82
48
|
});
|