@devang0907/agent-dev 0.1.0 → 0.1.1
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/config/settings.d.ts +1 -2
- package/dist/config/settings.js +8 -2
- package/dist/providers/types.d.ts +0 -1
- package/dist/ui/App.js +5 -3
- package/dist/ui/ChatView.d.ts +4 -1
- package/dist/ui/ChatView.js +21 -2
- package/dist/ui/Editor.d.ts +4 -2
- package/dist/ui/Editor.js +56 -13
- package/dist/ui/Footer.d.ts +1 -2
- package/dist/ui/Footer.js +8 -3
- package/dist/ui/LeftBorder.d.ts +11 -0
- package/dist/ui/LeftBorder.js +6 -0
- package/dist/ui/ModelSelector.js +14 -10
- package/dist/ui/Panel.d.ts +11 -0
- package/dist/ui/Panel.js +5 -0
- package/dist/ui/SettingsView.js +7 -12
- package/dist/ui/StartupBanner.d.ts +8 -0
- package/dist/ui/StartupBanner.js +82 -0
- package/dist/ui/slash-commands.d.ts +16 -0
- package/dist/ui/slash-commands.js +31 -0
- package/dist/ui/theme.d.ts +13 -10
- package/dist/ui/theme.js +20 -23
- package/package.json +13 -4
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import type { ProviderId } from "../providers/types.js";
|
|
2
|
-
import type { ThinkingLevel
|
|
2
|
+
import type { ThinkingLevel } from "../providers/types.js";
|
|
3
3
|
export interface Settings {
|
|
4
4
|
defaultProvider: ProviderId;
|
|
5
5
|
defaultModel: string;
|
|
6
6
|
thinkingLevel: ThinkingLevel;
|
|
7
|
-
theme: Theme;
|
|
8
7
|
apiKeys?: Partial<Record<ProviderId, string>>;
|
|
9
8
|
}
|
|
10
9
|
export declare function loadSettings(): Settings;
|
package/dist/config/settings.js
CHANGED
|
@@ -4,7 +4,6 @@ const DEFAULT_SETTINGS = {
|
|
|
4
4
|
defaultProvider: "free",
|
|
5
5
|
defaultModel: "meta-llama/llama-3.3-70b-instruct:free",
|
|
6
6
|
thinkingLevel: "off",
|
|
7
|
-
theme: "dark",
|
|
8
7
|
};
|
|
9
8
|
export function loadSettings() {
|
|
10
9
|
if (!existsSync(SETTINGS_PATH)) {
|
|
@@ -12,7 +11,14 @@ export function loadSettings() {
|
|
|
12
11
|
}
|
|
13
12
|
try {
|
|
14
13
|
const raw = readFileSync(SETTINGS_PATH, "utf-8");
|
|
15
|
-
|
|
14
|
+
const parsed = JSON.parse(raw);
|
|
15
|
+
return {
|
|
16
|
+
...DEFAULT_SETTINGS,
|
|
17
|
+
defaultProvider: parsed.defaultProvider ?? DEFAULT_SETTINGS.defaultProvider,
|
|
18
|
+
defaultModel: parsed.defaultModel ?? DEFAULT_SETTINGS.defaultModel,
|
|
19
|
+
thinkingLevel: parsed.thinkingLevel ?? DEFAULT_SETTINGS.thinkingLevel,
|
|
20
|
+
apiKeys: parsed.apiKeys,
|
|
21
|
+
};
|
|
16
22
|
}
|
|
17
23
|
catch {
|
|
18
24
|
return { ...DEFAULT_SETTINGS };
|
package/dist/ui/App.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState, useEffect, useCallback, useRef } from "react";
|
|
3
|
-
import { Box,
|
|
3
|
+
import { Box, useInput, useApp } from "ink";
|
|
4
4
|
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
8
|
import { SettingsView } from "./SettingsView.js";
|
|
9
|
+
import { StartupBanner } from "./StartupBanner.js";
|
|
9
10
|
import { getTheme } from "./theme.js";
|
|
10
11
|
import { saveSettings } from "../config/settings.js";
|
|
11
12
|
export function App({ session, workdir, onQuit }) {
|
|
@@ -22,7 +23,7 @@ export function App({ session, workdir, onQuit }) {
|
|
|
22
23
|
const [model, setModel] = useState(session.getModel());
|
|
23
24
|
const [running, setRunning] = useState(false);
|
|
24
25
|
const streamingRef = useRef("");
|
|
25
|
-
const theme = getTheme(
|
|
26
|
+
const theme = getTheme();
|
|
26
27
|
useEffect(() => {
|
|
27
28
|
const handler = (event) => {
|
|
28
29
|
switch (event.type) {
|
|
@@ -115,7 +116,8 @@ export function App({ session, workdir, onQuit }) {
|
|
|
115
116
|
return;
|
|
116
117
|
await session.prompt(value);
|
|
117
118
|
}, [session, running, onQuit, exit]);
|
|
118
|
-
|
|
119
|
+
const hasChat = displayMessages.length > 0 || streamingText.length > 0;
|
|
120
|
+
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) => {
|
|
119
121
|
session.setModel(m);
|
|
120
122
|
setModel(m);
|
|
121
123
|
setOverlay("none");
|
package/dist/ui/ChatView.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import type { DisplayMessage } from "./App.js";
|
|
3
3
|
import type { ThemeColors } from "./theme.js";
|
|
4
|
+
import type { Model } from "../providers/types.js";
|
|
4
5
|
interface ChatViewProps {
|
|
5
6
|
messages: DisplayMessage[];
|
|
6
7
|
theme: ThemeColors;
|
|
8
|
+
model: Model;
|
|
7
9
|
streamingText?: string;
|
|
10
|
+
running?: boolean;
|
|
8
11
|
}
|
|
9
|
-
export declare function ChatView({ messages, theme, streamingText }: ChatViewProps): React.JSX.Element;
|
|
12
|
+
export declare function ChatView({ messages, theme, model, streamingText, running }: ChatViewProps): React.JSX.Element | null;
|
|
10
13
|
export {};
|
package/dist/ui/ChatView.js
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect } from "react";
|
|
2
3
|
import { Box, Text } from "ink";
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
import { SPINNER_FRAMES, TOOL_ICONS } from "./theme.js";
|
|
5
|
+
import { LeftBorder } from "./LeftBorder.js";
|
|
6
|
+
import { Panel } from "./Panel.js";
|
|
7
|
+
import { modelRef } from "../config/models.js";
|
|
8
|
+
function truncate(text, max) {
|
|
9
|
+
return text.length > max ? text.slice(0, max) + "…" : text;
|
|
10
|
+
}
|
|
11
|
+
export function ChatView({ messages, theme, model, streamingText, running }) {
|
|
12
|
+
const [spinIdx, setSpinIdx] = useState(0);
|
|
13
|
+
const hasContent = messages.length > 0 || (streamingText?.length ?? 0) > 0;
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (!running && !streamingText)
|
|
16
|
+
return;
|
|
17
|
+
const id = setInterval(() => setSpinIdx((i) => (i + 1) % SPINNER_FRAMES.length), 80);
|
|
18
|
+
return () => clearInterval(id);
|
|
19
|
+
}, [running, streamingText]);
|
|
20
|
+
if (!hasContent) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
return (_jsxs(Panel, { theme: theme, flexGrow: 1, borderColor: theme.border, children: [messages.map((msg, i) => (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [msg.role === "user" && (_jsx(LeftBorder, { theme: theme, borderColor: theme.primary, marginBottom: 0, children: _jsx(Text, { color: theme.text, children: msg.content }) })), msg.role === "assistant" && (_jsxs(Box, { flexDirection: "column", paddingLeft: 1, children: [_jsx(Text, { color: theme.text, children: msg.content || "" }), _jsxs(Text, { color: theme.textMuted, children: [_jsx(Text, { color: theme.primary, children: "\u25A3 " }), modelRef(model)] })] })), msg.role === "tool" && (_jsx(Box, { paddingLeft: 1, children: _jsxs(Text, { color: theme.text, children: [_jsx(Text, { color: theme.textMuted, children: TOOL_ICONS[msg.toolName ?? ""] ?? "⚙" }), " ", _jsx(Text, { bold: true, children: msg.toolName }), _jsxs(Text, { color: theme.textMuted, children: [" ", truncate(msg.content, 200)] })] }) }))] }, i))), streamingText && (_jsxs(Box, { flexDirection: "column", paddingLeft: 1, children: [_jsx(Text, { color: theme.text, children: streamingText }), _jsxs(Text, { color: theme.textMuted, children: [_jsxs(Text, { color: theme.primary, children: [SPINNER_FRAMES[spinIdx], " "] }), "responding\u2026"] })] })), running && !streamingText && messages.length > 0 && (_jsx(Box, { paddingLeft: 1, children: _jsxs(Text, { color: theme.textMuted, children: [_jsx(Text, { color: theme.primary, children: SPINNER_FRAMES[spinIdx] }), " working\u2026"] }) }))] }));
|
|
5
24
|
}
|
package/dist/ui/Editor.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import type { ThemeColors } from "./theme.js";
|
|
3
|
+
import type { Model } from "../providers/types.js";
|
|
3
4
|
interface EditorProps {
|
|
4
5
|
theme: ThemeColors;
|
|
6
|
+
model: Model;
|
|
5
7
|
disabled?: boolean;
|
|
8
|
+
running?: boolean;
|
|
6
9
|
onSubmit: (value: string) => void;
|
|
7
|
-
filterHint?: string;
|
|
8
10
|
}
|
|
9
|
-
export declare function Editor({ theme, disabled,
|
|
11
|
+
export declare function Editor({ theme, model, disabled, running, onSubmit }: EditorProps): React.JSX.Element;
|
|
10
12
|
export {};
|
package/dist/ui/Editor.js
CHANGED
|
@@ -1,10 +1,40 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState } from "react";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect } from "react";
|
|
3
3
|
import { Box, Text, useInput } from "ink";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
import { modelRef } from "../config/models.js";
|
|
5
|
+
import { matchSlashCommands, completeSlashInput, } from "./slash-commands.js";
|
|
6
|
+
import { Panel } from "./Panel.js";
|
|
7
|
+
import { SPINNER_FRAMES } from "./theme.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 Editor({ theme, model, disabled, running, onSubmit }) {
|
|
14
|
+
const [value, setValue] = useState("");
|
|
7
15
|
const [suggestions, setSuggestions] = useState([]);
|
|
16
|
+
const [spinIdx, setSpinIdx] = useState(0);
|
|
17
|
+
const [cursorOn, setCursorOn] = useState(true);
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
if (!running)
|
|
20
|
+
return;
|
|
21
|
+
const id = setInterval(() => setSpinIdx((i) => (i + 1) % SPINNER_FRAMES.length), 80);
|
|
22
|
+
return () => clearInterval(id);
|
|
23
|
+
}, [running]);
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (disabled)
|
|
26
|
+
return;
|
|
27
|
+
const id = setInterval(() => setCursorOn((v) => !v), 530);
|
|
28
|
+
return () => clearInterval(id);
|
|
29
|
+
}, [disabled]);
|
|
30
|
+
const updateSuggestions = (text) => {
|
|
31
|
+
if (text.startsWith("/")) {
|
|
32
|
+
setSuggestions(matchSlashCommands(text));
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
setSuggestions([]);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
8
38
|
useInput((input, key) => {
|
|
9
39
|
if (disabled)
|
|
10
40
|
return;
|
|
@@ -16,20 +46,33 @@ export function Editor({ theme, disabled, onSubmit, filterHint }) {
|
|
|
16
46
|
setSuggestions([]);
|
|
17
47
|
return;
|
|
18
48
|
}
|
|
49
|
+
if (key.tab) {
|
|
50
|
+
if (value.startsWith("/")) {
|
|
51
|
+
const completed = completeSlashInput(value);
|
|
52
|
+
if (completed) {
|
|
53
|
+
setValue(completed);
|
|
54
|
+
setSuggestions(matchSlashCommands(completed));
|
|
55
|
+
}
|
|
56
|
+
else if (suggestions.length > 0) {
|
|
57
|
+
setValue(suggestions[0].cmd);
|
|
58
|
+
setSuggestions(matchSlashCommands(suggestions[0].cmd));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
19
63
|
if (key.backspace || key.delete) {
|
|
20
|
-
|
|
64
|
+
const newVal = value.slice(0, -1);
|
|
65
|
+
setValue(newVal);
|
|
66
|
+
updateSuggestions(newVal);
|
|
21
67
|
return;
|
|
22
68
|
}
|
|
23
69
|
if (input && !key.ctrl && !key.meta) {
|
|
24
70
|
const newVal = value + input;
|
|
25
71
|
setValue(newVal);
|
|
26
|
-
|
|
27
|
-
setSuggestions(SLASH_COMMANDS.filter((c) => c.startsWith(newVal)));
|
|
28
|
-
}
|
|
29
|
-
else {
|
|
30
|
-
setSuggestions([]);
|
|
31
|
-
}
|
|
72
|
+
updateSuggestions(newVal);
|
|
32
73
|
}
|
|
33
74
|
});
|
|
34
|
-
|
|
75
|
+
const placeholder = "Ask anything…";
|
|
76
|
+
const showCursor = !disabled && cursorOn;
|
|
77
|
+
return (_jsxs(Box, { flexDirection: "column", marginX: 2, children: [suggestions.length > 0 && (_jsx(Box, { flexDirection: "column", borderStyle: "round", borderColor: theme.border, paddingX: 1, marginBottom: 1, children: suggestions.map((s) => (_jsxs(Text, { children: [_jsx(Text, { color: theme.primary, children: s.cmd }), _jsxs(Text, { color: theme.textMuted, children: [" \u2014 ", s.desc] })] }, s.cmd))) })), _jsxs(Panel, { theme: theme, borderColor: disabled ? theme.border : theme.primary, marginBottom: 0, children: [_jsx(Box, { flexDirection: "row", children: value.length > 0 ? (_jsxs(_Fragment, { children: [_jsx(Text, { color: theme.text, children: value }), _jsx(BlinkingCursor, { theme: theme, visible: showCursor })] })) : showCursor ? (_jsx(BlinkingCursor, { theme: theme, visible: true })) : (_jsx(Text, { color: theme.textMuted, children: placeholder })) }), _jsxs(Text, { color: theme.textMuted, children: ["agent-dev \u00B7 ", _jsx(Text, { color: theme.text, children: modelRef(model) })] })] }), _jsx(Box, { marginTop: 1, marginBottom: 1, children: running ? (_jsxs(Text, { color: theme.textMuted, children: [_jsx(Text, { color: theme.primary, children: SPINNER_FRAMES[spinIdx] }), " ", "esc interrupt"] })) : (_jsx(Text, { color: theme.textMuted, children: "Tab completes /commands" })) })] }));
|
|
35
78
|
}
|
package/dist/ui/Footer.d.ts
CHANGED
|
@@ -5,7 +5,6 @@ interface FooterProps {
|
|
|
5
5
|
workdir: string;
|
|
6
6
|
model: Model;
|
|
7
7
|
theme: ThemeColors;
|
|
8
|
-
running: boolean;
|
|
9
8
|
}
|
|
10
|
-
export declare function Footer({ workdir, model, theme
|
|
9
|
+
export declare function Footer({ workdir, model, theme }: FooterProps): React.JSX.Element;
|
|
11
10
|
export {};
|
package/dist/ui/Footer.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
3
|
import { modelRef } from "../config/models.js";
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
function shortPath(path, max = 56) {
|
|
5
|
+
if (path.length <= max)
|
|
6
|
+
return path;
|
|
7
|
+
return "…" + path.slice(-(max - 1));
|
|
8
|
+
}
|
|
9
|
+
export function Footer({ workdir, model, theme }) {
|
|
10
|
+
return (_jsx(Box, { borderStyle: "single", borderColor: theme.border, borderLeft: false, borderRight: false, borderBottom: false, paddingX: 2, marginBottom: 1, children: _jsxs(Text, { color: theme.textMuted, children: [_jsx(Text, { color: theme.primary, children: "\u2302 " }), shortPath(workdir), " ", _jsx(Text, { color: theme.text, children: modelRef(model) })] }) }));
|
|
6
11
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { ThemeColors } from "./theme.js";
|
|
3
|
+
interface LeftBorderProps {
|
|
4
|
+
theme: ThemeColors;
|
|
5
|
+
borderColor?: string;
|
|
6
|
+
marginBottom?: number;
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
}
|
|
9
|
+
/** OpenCode-style left ┃ accent border */
|
|
10
|
+
export declare function LeftBorder({ theme, borderColor, marginBottom, children, }: LeftBorderProps): React.JSX.Element;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Box } from "ink";
|
|
3
|
+
/** OpenCode-style left ┃ accent border */
|
|
4
|
+
export function LeftBorder({ theme, borderColor = theme.primary, marginBottom = 0, children, }) {
|
|
5
|
+
return (_jsx(Box, { flexDirection: "column", borderStyle: "single", borderColor: borderColor, borderLeft: true, borderTop: false, borderRight: false, borderBottom: false, paddingLeft: 1, marginBottom: marginBottom, children: children }));
|
|
6
|
+
}
|
package/dist/ui/ModelSelector.js
CHANGED
|
@@ -3,12 +3,13 @@ import { useState } from "react";
|
|
|
3
3
|
import { Box, Text, useInput } from "ink";
|
|
4
4
|
import { ALL_MODELS, modelRef, PROVIDER_LABELS } from "../config/models.js";
|
|
5
5
|
import { hasProviderAuth } from "../providers/registry.js";
|
|
6
|
+
import { LeftBorder } from "./LeftBorder.js";
|
|
6
7
|
function fuzzyMatch(text, query) {
|
|
7
8
|
if (!query)
|
|
8
9
|
return true;
|
|
9
10
|
const lower = text.toLowerCase();
|
|
10
11
|
const q = query.toLowerCase();
|
|
11
|
-
return lower.includes(q)
|
|
12
|
+
return lower.includes(q);
|
|
12
13
|
}
|
|
13
14
|
export function ModelSelector({ theme, settings, filter, onSelect, onClose }) {
|
|
14
15
|
const filtered = ALL_MODELS.filter((m) => {
|
|
@@ -17,7 +18,7 @@ export function ModelSelector({ theme, settings, filter, onSelect, onClose }) {
|
|
|
17
18
|
});
|
|
18
19
|
const [index, setIndex] = useState(0);
|
|
19
20
|
const safeIndex = Math.min(index, Math.max(0, filtered.length - 1));
|
|
20
|
-
useInput((
|
|
21
|
+
useInput((_, key) => {
|
|
21
22
|
if (key.escape) {
|
|
22
23
|
onClose();
|
|
23
24
|
return;
|
|
@@ -29,14 +30,17 @@ export function ModelSelector({ theme, settings, filter, onSelect, onClose }) {
|
|
|
29
30
|
if (key.return && filtered[safeIndex]) {
|
|
30
31
|
onSelect(filtered[safeIndex]);
|
|
31
32
|
}
|
|
32
|
-
if (input && !key.ctrl) {
|
|
33
|
-
// typing not used in overlay mode
|
|
34
|
-
}
|
|
35
33
|
});
|
|
36
34
|
const providers = ["openai", "groq", "gemini", "free"];
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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);
|
|
38
|
+
const selected = i === safeIndex;
|
|
39
|
+
const showHeader = m.provider !== lastProvider;
|
|
40
|
+
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, !hasKey && _jsx(Text, { color: theme.warning, children: " (no key)" }), selected && _jsxs(Text, { color: theme.textMuted, children: [" ", modelRef(m)] })] })] }, modelRef(m)));
|
|
42
|
+
})] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.textMuted, children: providers.map((p) => {
|
|
43
|
+
const ok = hasProviderAuth(p, settings);
|
|
44
|
+
return `${ok ? "●" : "○"} ${p}`;
|
|
45
|
+
}).join(" ") }) })] }) }));
|
|
42
46
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { ThemeColors } from "./theme.js";
|
|
3
|
+
interface PanelProps {
|
|
4
|
+
theme: ThemeColors;
|
|
5
|
+
marginBottom?: number;
|
|
6
|
+
flexGrow?: number;
|
|
7
|
+
borderColor?: string;
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
}
|
|
10
|
+
export declare function Panel({ theme, marginBottom, flexGrow, borderColor, children, }: PanelProps): React.JSX.Element;
|
|
11
|
+
export {};
|
package/dist/ui/Panel.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Box } from "ink";
|
|
3
|
+
export function Panel({ theme, marginBottom = 1, flexGrow, borderColor = theme.border, children, }) {
|
|
4
|
+
return (_jsx(Box, { flexDirection: "column", flexGrow: flexGrow, borderStyle: "round", borderColor: borderColor, paddingX: 1, paddingY: 1, marginX: 2, marginBottom: marginBottom, children: children }));
|
|
5
|
+
}
|
package/dist/ui/SettingsView.js
CHANGED
|
@@ -2,11 +2,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useState } from "react";
|
|
3
3
|
import { Box, Text, useInput } from "ink";
|
|
4
4
|
import { PROVIDER_ENV_VARS } from "../providers/registry.js";
|
|
5
|
+
import { LeftBorder } from "./LeftBorder.js";
|
|
5
6
|
const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high"];
|
|
6
|
-
const THEMES = ["dark", "light"];
|
|
7
7
|
export function SettingsView({ theme, settings, onUpdate, onClose }) {
|
|
8
8
|
const [index, setIndex] = useState(0);
|
|
9
|
-
const items = ["thinkingLevel", "
|
|
9
|
+
const items = ["thinkingLevel", "envKeys"];
|
|
10
10
|
useInput((_, key) => {
|
|
11
11
|
if (key.escape)
|
|
12
12
|
onClose();
|
|
@@ -21,17 +21,12 @@ export function SettingsView({ theme, settings, onUpdate, onClose }) {
|
|
|
21
21
|
const next = THINKING_LEVELS[(cur + 1) % THINKING_LEVELS.length];
|
|
22
22
|
onUpdate({ ...settings, thinkingLevel: next });
|
|
23
23
|
}
|
|
24
|
-
else if (item === "theme") {
|
|
25
|
-
const cur = THEMES.indexOf(settings.theme);
|
|
26
|
-
const next = THEMES[(cur + 1) % THEMES.length];
|
|
27
|
-
onUpdate({ ...settings, theme: next });
|
|
28
|
-
}
|
|
29
24
|
}
|
|
30
25
|
});
|
|
31
26
|
const providers = ["openai", "groq", "gemini", "free"];
|
|
32
|
-
return (
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
27
|
+
return (_jsx(Box, { paddingX: 2, marginTop: 1, children: _jsxs(LeftBorder, { theme: theme, borderColor: theme.borderActive, children: [_jsx(Text, { color: theme.text, bold: true, children: "/settings" }), _jsx(Text, { color: theme.textMuted, children: " Enter cycle \u00B7 Esc close" }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { color: index === 0 ? theme.primary : theme.text, children: [index === 0 ? "› " : " ", "Thinking:", " ", _jsx(Text, { bold: true, children: settings.thinkingLevel })] }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: index === 1 ? theme.primary : theme.textMuted, children: [index === 1 ? "› " : " ", "API keys"] }) }), providers.map((p) => {
|
|
28
|
+
const vars = PROVIDER_ENV_VARS[p];
|
|
29
|
+
const set = vars.some((v) => process.env[v]);
|
|
30
|
+
return (_jsxs(Text, { color: set ? theme.success : theme.textMuted, children: [" ", set ? "✓" : "○", " ", p, ": ", vars.join(" · ")] }, p));
|
|
31
|
+
})] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.textMuted, children: "OPENAI_API_KEY \u00B7 GROQ_API_KEY \u00B7 GEMINI_API_KEY \u00B7 OPENROUTER_API_KEY" }) })] }) }));
|
|
37
32
|
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { memo } from "react";
|
|
3
|
+
import { Box, Text } from "ink";
|
|
4
|
+
const INNER = 42;
|
|
5
|
+
const CHAR_COLORS = {
|
|
6
|
+
"#": "gray",
|
|
7
|
+
"@": "cyan",
|
|
8
|
+
"$": "green",
|
|
9
|
+
"%": "yellow",
|
|
10
|
+
"^": "blue",
|
|
11
|
+
"&": "magenta",
|
|
12
|
+
"*": "white",
|
|
13
|
+
};
|
|
14
|
+
const BOX_CHARS = "+-|";
|
|
15
|
+
function colorForChar(char, theme) {
|
|
16
|
+
if (BOX_CHARS.includes(char) || char === " ")
|
|
17
|
+
return theme.border;
|
|
18
|
+
return CHAR_COLORS[char] ?? theme.text;
|
|
19
|
+
}
|
|
20
|
+
function ColoredLine({ line, theme }) {
|
|
21
|
+
const segments = [];
|
|
22
|
+
let run = "";
|
|
23
|
+
let runColor = "";
|
|
24
|
+
for (const char of line) {
|
|
25
|
+
const color = colorForChar(char, theme);
|
|
26
|
+
if (run && color !== runColor) {
|
|
27
|
+
segments.push({ text: run, color: runColor });
|
|
28
|
+
run = char;
|
|
29
|
+
runColor = color;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
run += char;
|
|
33
|
+
runColor = color;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (run)
|
|
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))) }));
|
|
39
|
+
}
|
|
40
|
+
function center(text) {
|
|
41
|
+
if (text.length >= INNER)
|
|
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 <@ @>";
|
|
77
|
+
export const StartupBanner = memo(function StartupBanner({ theme, compact, }) {
|
|
78
|
+
if (compact) {
|
|
79
|
+
return (_jsx(Box, { marginBottom: 1, children: _jsx(ColoredLine, { line: ROBOT_MINI, theme: theme }) }));
|
|
80
|
+
}
|
|
81
|
+
return (_jsx(Box, { flexDirection: "column", marginBottom: 1, children: ROBOT_LINES.map((line, i) => (_jsx(ColoredLine, { line: line, theme: theme }, i))) }));
|
|
82
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const SLASH_COMMANDS: readonly [{
|
|
2
|
+
readonly cmd: "/model";
|
|
3
|
+
readonly desc: "Select provider & model";
|
|
4
|
+
}, {
|
|
5
|
+
readonly cmd: "/settings";
|
|
6
|
+
readonly desc: "Thinking level & API keys";
|
|
7
|
+
}, {
|
|
8
|
+
readonly cmd: "/new";
|
|
9
|
+
readonly desc: "New session";
|
|
10
|
+
}, {
|
|
11
|
+
readonly cmd: "/quit";
|
|
12
|
+
readonly desc: "Exit";
|
|
13
|
+
}];
|
|
14
|
+
export declare function matchSlashCommands(input: string): typeof SLASH_COMMANDS[number][];
|
|
15
|
+
export declare function longestCommonPrefix(strings: string[]): string;
|
|
16
|
+
export declare function completeSlashInput(input: string): string | null;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export const SLASH_COMMANDS = [
|
|
2
|
+
{ cmd: "/model", desc: "Select provider & model" },
|
|
3
|
+
{ cmd: "/settings", desc: "Thinking level & API keys" },
|
|
4
|
+
{ cmd: "/new", desc: "New session" },
|
|
5
|
+
{ cmd: "/quit", desc: "Exit" },
|
|
6
|
+
];
|
|
7
|
+
export function matchSlashCommands(input) {
|
|
8
|
+
if (!input.startsWith("/"))
|
|
9
|
+
return [];
|
|
10
|
+
return SLASH_COMMANDS.filter((c) => c.cmd.startsWith(input));
|
|
11
|
+
}
|
|
12
|
+
export function longestCommonPrefix(strings) {
|
|
13
|
+
if (strings.length === 0)
|
|
14
|
+
return "";
|
|
15
|
+
let prefix = strings[0];
|
|
16
|
+
for (let i = 1; i < strings.length; i++) {
|
|
17
|
+
while (prefix && !strings[i].startsWith(prefix)) {
|
|
18
|
+
prefix = prefix.slice(0, -1);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return prefix;
|
|
22
|
+
}
|
|
23
|
+
export function completeSlashInput(input) {
|
|
24
|
+
const matches = matchSlashCommands(input).map((c) => c.cmd);
|
|
25
|
+
if (matches.length === 0)
|
|
26
|
+
return null;
|
|
27
|
+
if (matches.length === 1)
|
|
28
|
+
return matches[0];
|
|
29
|
+
const common = longestCommonPrefix(matches);
|
|
30
|
+
return common.length > input.length ? common : null;
|
|
31
|
+
}
|
package/dist/ui/theme.d.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import type { Theme } from "../providers/types.js";
|
|
2
1
|
export interface ThemeColors {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
text: string;
|
|
3
|
+
textMuted: string;
|
|
4
|
+
primary: string;
|
|
5
|
+
secondary: string;
|
|
6
|
+
success: string;
|
|
7
|
+
warning: string;
|
|
9
8
|
error: string;
|
|
10
|
-
|
|
9
|
+
border: string;
|
|
10
|
+
borderActive: string;
|
|
11
11
|
}
|
|
12
|
-
|
|
13
|
-
export declare
|
|
12
|
+
/** OpenCode-inspired palette — standard terminal ANSI colors */
|
|
13
|
+
export declare const theme: ThemeColors;
|
|
14
|
+
export declare function getTheme(): ThemeColors;
|
|
15
|
+
export declare const SPINNER_FRAMES: string[];
|
|
16
|
+
export declare const TOOL_ICONS: Record<string, string>;
|
package/dist/ui/theme.js
CHANGED
|
@@ -1,25 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
light: {
|
|
13
|
-
user: "blue",
|
|
14
|
-
assistant: "black",
|
|
15
|
-
tool: "yellow",
|
|
16
|
-
border: "gray",
|
|
17
|
-
muted: "gray",
|
|
18
|
-
accent: "green",
|
|
19
|
-
error: "red",
|
|
20
|
-
header: "blue",
|
|
21
|
-
},
|
|
1
|
+
/** OpenCode-inspired palette — standard terminal ANSI colors */
|
|
2
|
+
export const theme = {
|
|
3
|
+
text: "white",
|
|
4
|
+
textMuted: "gray",
|
|
5
|
+
primary: "cyan",
|
|
6
|
+
secondary: "blue",
|
|
7
|
+
success: "green",
|
|
8
|
+
warning: "yellow",
|
|
9
|
+
error: "red",
|
|
10
|
+
border: "gray",
|
|
11
|
+
borderActive: "white",
|
|
22
12
|
};
|
|
23
|
-
export function getTheme(
|
|
24
|
-
return
|
|
13
|
+
export function getTheme() {
|
|
14
|
+
return theme;
|
|
25
15
|
}
|
|
16
|
+
export const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
17
|
+
export const TOOL_ICONS = {
|
|
18
|
+
bash: "$",
|
|
19
|
+
read: "→",
|
|
20
|
+
write: "⚙",
|
|
21
|
+
edit: "%",
|
|
22
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devang0907/agent-dev",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Minimal
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Minimal terminal coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Devang Rakholiya <rakholiyadevang@gmail.com>",
|
|
@@ -9,8 +9,17 @@
|
|
|
9
9
|
"type": "git",
|
|
10
10
|
"url": "git+https://github.com/Devang0907/agent-dev.git"
|
|
11
11
|
},
|
|
12
|
-
"keywords": [
|
|
13
|
-
|
|
12
|
+
"keywords": [
|
|
13
|
+
"ai",
|
|
14
|
+
"agent",
|
|
15
|
+
"cli",
|
|
16
|
+
"ink",
|
|
17
|
+
"coding-agent"
|
|
18
|
+
],
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
14
23
|
"bin": {
|
|
15
24
|
"agent": "dist/cli.js"
|
|
16
25
|
},
|