@devang0907/agent-dev 0.1.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 (52) hide show
  1. package/README.md +58 -0
  2. package/dist/agent/loop.d.ts +32 -0
  3. package/dist/agent/loop.js +83 -0
  4. package/dist/agent/session.d.ts +33 -0
  5. package/dist/agent/session.js +107 -0
  6. package/dist/agent/tools/index.d.ts +8 -0
  7. package/dist/agent/tools/index.js +21 -0
  8. package/dist/agent/tools/read.d.ts +20 -0
  9. package/dist/agent/tools/read.js +108 -0
  10. package/dist/cli/args.d.ts +9 -0
  11. package/dist/cli/args.js +60 -0
  12. package/dist/cli.d.ts +2 -0
  13. package/dist/cli.js +6 -0
  14. package/dist/config/models.d.ts +6 -0
  15. package/dist/config/models.js +43 -0
  16. package/dist/config/paths.d.ts +4 -0
  17. package/dist/config/paths.js +6 -0
  18. package/dist/config/settings.d.ts +12 -0
  19. package/dist/config/settings.js +29 -0
  20. package/dist/main.d.ts +1 -0
  21. package/dist/main.js +45 -0
  22. package/dist/modes/print-mode.d.ts +2 -0
  23. package/dist/modes/print-mode.js +32 -0
  24. package/dist/providers/gemini.d.ts +10 -0
  25. package/dist/providers/gemini.js +116 -0
  26. package/dist/providers/groq.d.ts +10 -0
  27. package/dist/providers/groq.js +113 -0
  28. package/dist/providers/openai.d.ts +10 -0
  29. package/dist/providers/openai.js +121 -0
  30. package/dist/providers/openrouter-free.d.ts +10 -0
  31. package/dist/providers/openrouter-free.js +133 -0
  32. package/dist/providers/registry.d.ts +7 -0
  33. package/dist/providers/registry.js +39 -0
  34. package/dist/providers/types.d.ts +60 -0
  35. package/dist/providers/types.js +1 -0
  36. package/dist/session/manager.d.ts +25 -0
  37. package/dist/session/manager.js +85 -0
  38. package/dist/ui/App.d.ts +14 -0
  39. package/dist/ui/App.js +131 -0
  40. package/dist/ui/ChatView.d.ts +10 -0
  41. package/dist/ui/ChatView.js +5 -0
  42. package/dist/ui/Editor.d.ts +10 -0
  43. package/dist/ui/Editor.js +35 -0
  44. package/dist/ui/Footer.d.ts +11 -0
  45. package/dist/ui/Footer.js +6 -0
  46. package/dist/ui/ModelSelector.d.ts +13 -0
  47. package/dist/ui/ModelSelector.js +42 -0
  48. package/dist/ui/SettingsView.d.ts +11 -0
  49. package/dist/ui/SettingsView.js +37 -0
  50. package/dist/ui/theme.d.ts +13 -0
  51. package/dist/ui/theme.js +25 -0
  52. package/package.json +42 -0
@@ -0,0 +1,60 @@
1
+ export type ProviderId = "openai" | "groq" | "gemini" | "free";
2
+ export type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high";
3
+ export type Theme = "dark" | "light";
4
+ export interface Model {
5
+ provider: ProviderId;
6
+ id: string;
7
+ name: string;
8
+ }
9
+ export type MessageRole = "user" | "assistant" | "tool" | "system";
10
+ export interface ToolCall {
11
+ id: string;
12
+ name: string;
13
+ arguments: string;
14
+ }
15
+ export interface ChatMessage {
16
+ role: MessageRole;
17
+ content: string;
18
+ toolCalls?: ToolCall[];
19
+ toolCallId?: string;
20
+ name?: string;
21
+ }
22
+ export interface ToolDefinition {
23
+ name: string;
24
+ description: string;
25
+ parameters: Record<string, unknown>;
26
+ }
27
+ export interface ChatContext {
28
+ messages: ChatMessage[];
29
+ tools: ToolDefinition[];
30
+ systemPrompt?: string;
31
+ thinkingLevel?: ThinkingLevel;
32
+ signal?: AbortSignal;
33
+ }
34
+ export type StreamEvent = {
35
+ type: "text_delta";
36
+ delta: string;
37
+ } | {
38
+ type: "tool_call_delta";
39
+ index: number;
40
+ id?: string;
41
+ name?: string;
42
+ argumentsDelta?: string;
43
+ } | {
44
+ type: "done";
45
+ usage?: {
46
+ inputTokens?: number;
47
+ outputTokens?: number;
48
+ };
49
+ } | {
50
+ type: "error";
51
+ message: string;
52
+ };
53
+ export interface StreamResult {
54
+ content: string;
55
+ toolCalls: ToolCall[];
56
+ usage?: {
57
+ inputTokens?: number;
58
+ outputTokens?: number;
59
+ };
60
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,25 @@
1
+ import type { ChatMessage, Model } from "../providers/types.js";
2
+ export interface SessionEntry {
3
+ type: "message" | "model_change";
4
+ id: string;
5
+ timestamp: string;
6
+ data: ChatMessage | {
7
+ provider: string;
8
+ modelId: string;
9
+ };
10
+ }
11
+ export declare class SessionManager {
12
+ readonly sessionId: string;
13
+ readonly sessionPath: string;
14
+ private messages;
15
+ constructor(sessionId?: string, cwd?: string);
16
+ private load;
17
+ getMessages(): ChatMessage[];
18
+ appendMessage(msg: ChatMessage): void;
19
+ appendMessages(msgs: ChatMessage[]): void;
20
+ appendModelChange(model: Model): void;
21
+ private appendEntry;
22
+ clear(): void;
23
+ saveAsLast(): void;
24
+ static loadLast(): SessionManager | undefined;
25
+ }
@@ -0,0 +1,85 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { mkdirSync, appendFileSync, readFileSync, writeFileSync, existsSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { createHash } from "node:crypto";
5
+ import { SESSIONS_DIR, LAST_SESSION_PATH } from "../config/paths.js";
6
+ export class SessionManager {
7
+ sessionId;
8
+ sessionPath;
9
+ messages = [];
10
+ constructor(sessionId, cwd) {
11
+ mkdirSync(SESSIONS_DIR, { recursive: true });
12
+ if (sessionId) {
13
+ this.sessionId = sessionId;
14
+ this.sessionPath = join(SESSIONS_DIR, `${sessionId}.jsonl`);
15
+ this.load();
16
+ }
17
+ else {
18
+ const hash = createHash("sha256").update(cwd ?? process.cwd()).digest("hex").slice(0, 12);
19
+ this.sessionId = `${hash}-${Date.now()}`;
20
+ this.sessionPath = join(SESSIONS_DIR, `${this.sessionId}.jsonl`);
21
+ }
22
+ }
23
+ load() {
24
+ if (!existsSync(this.sessionPath))
25
+ return;
26
+ const lines = readFileSync(this.sessionPath, "utf-8").split("\n").filter(Boolean);
27
+ for (const line of lines) {
28
+ try {
29
+ const entry = JSON.parse(line);
30
+ if (entry.type === "message") {
31
+ this.messages.push(entry.data);
32
+ }
33
+ }
34
+ catch {
35
+ // skip bad lines
36
+ }
37
+ }
38
+ }
39
+ getMessages() {
40
+ return [...this.messages];
41
+ }
42
+ appendMessage(msg) {
43
+ this.messages.push(msg);
44
+ this.appendEntry({
45
+ type: "message",
46
+ id: randomUUID(),
47
+ timestamp: new Date().toISOString(),
48
+ data: msg,
49
+ });
50
+ }
51
+ appendMessages(msgs) {
52
+ for (const msg of msgs) {
53
+ this.appendMessage(msg);
54
+ }
55
+ }
56
+ appendModelChange(model) {
57
+ this.appendEntry({
58
+ type: "model_change",
59
+ id: randomUUID(),
60
+ timestamp: new Date().toISOString(),
61
+ data: { provider: model.provider, modelId: model.id },
62
+ });
63
+ }
64
+ appendEntry(entry) {
65
+ appendFileSync(this.sessionPath, JSON.stringify(entry) + "\n", "utf-8");
66
+ }
67
+ clear() {
68
+ this.messages = [];
69
+ writeFileSync(this.sessionPath, "", "utf-8");
70
+ }
71
+ saveAsLast() {
72
+ writeFileSync(LAST_SESSION_PATH, JSON.stringify({ sessionId: this.sessionId, path: this.sessionPath }), "utf-8");
73
+ }
74
+ static loadLast() {
75
+ if (!existsSync(LAST_SESSION_PATH))
76
+ return undefined;
77
+ try {
78
+ const { sessionId } = JSON.parse(readFileSync(LAST_SESSION_PATH, "utf-8"));
79
+ return new SessionManager(sessionId);
80
+ }
81
+ catch {
82
+ return undefined;
83
+ }
84
+ }
85
+ }
@@ -0,0 +1,14 @@
1
+ import React from "react";
2
+ import type { AgentSession } from "../agent/session.js";
3
+ export interface DisplayMessage {
4
+ role: "user" | "assistant" | "tool";
5
+ content: string;
6
+ toolName?: string;
7
+ }
8
+ interface AppProps {
9
+ session: AgentSession;
10
+ workdir: string;
11
+ onQuit: () => void;
12
+ }
13
+ export declare function App({ session, workdir, onQuit }: AppProps): React.JSX.Element;
14
+ export {};
package/dist/ui/App.js ADDED
@@ -0,0 +1,131 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect, useCallback, useRef } from "react";
3
+ import { Box, Text, useInput, useApp } from "ink";
4
+ import { ChatView } from "./ChatView.js";
5
+ import { Editor } from "./Editor.js";
6
+ import { Footer } from "./Footer.js";
7
+ import { ModelSelector } from "./ModelSelector.js";
8
+ import { SettingsView } from "./SettingsView.js";
9
+ import { getTheme } from "./theme.js";
10
+ import { saveSettings } from "../config/settings.js";
11
+ export function App({ session, workdir, onQuit }) {
12
+ const { exit } = useApp();
13
+ const [displayMessages, setDisplayMessages] = useState(() => session.getMessages().map((m) => ({
14
+ role: m.role === "tool" ? "tool" : m.role === "user" ? "user" : "assistant",
15
+ content: m.content,
16
+ toolName: m.name,
17
+ })));
18
+ const [streamingText, setStreamingText] = useState("");
19
+ const [overlay, setOverlay] = useState("none");
20
+ const [modelFilter, setModelFilter] = useState();
21
+ const [settings, setSettings] = useState(session.getSettings());
22
+ const [model, setModel] = useState(session.getModel());
23
+ const [running, setRunning] = useState(false);
24
+ const streamingRef = useRef("");
25
+ const theme = getTheme(settings.theme);
26
+ useEffect(() => {
27
+ const handler = (event) => {
28
+ switch (event.type) {
29
+ case "user_message":
30
+ setDisplayMessages((prev) => [...prev, { role: "user", content: event.content }]);
31
+ setRunning(true);
32
+ streamingRef.current = "";
33
+ setStreamingText("");
34
+ break;
35
+ case "message_start":
36
+ streamingRef.current = "";
37
+ setStreamingText("");
38
+ break;
39
+ case "text_delta":
40
+ streamingRef.current += event.delta;
41
+ setStreamingText(streamingRef.current);
42
+ break;
43
+ case "tool_call":
44
+ const partial = streamingRef.current;
45
+ if (partial) {
46
+ setDisplayMessages((prev) => [...prev, { role: "assistant", content: partial }]);
47
+ streamingRef.current = "";
48
+ setStreamingText("");
49
+ }
50
+ break;
51
+ case "tool_result":
52
+ setDisplayMessages((prev) => [
53
+ ...prev,
54
+ { role: "tool", content: event.result, toolName: event.name },
55
+ ]);
56
+ break;
57
+ case "turn_end":
58
+ const final = streamingRef.current;
59
+ if (final) {
60
+ setDisplayMessages((prev) => [...prev, { role: "assistant", content: final }]);
61
+ }
62
+ streamingRef.current = "";
63
+ setStreamingText("");
64
+ setRunning(false);
65
+ break;
66
+ case "error":
67
+ setDisplayMessages((prev) => [
68
+ ...prev,
69
+ { role: "assistant", content: `Error: ${event.message}` },
70
+ ]);
71
+ streamingRef.current = "";
72
+ setStreamingText("");
73
+ setRunning(false);
74
+ break;
75
+ case "model_changed":
76
+ setModel(event.model);
77
+ break;
78
+ }
79
+ };
80
+ session.on("event", handler);
81
+ return () => {
82
+ session.off("event", handler);
83
+ };
84
+ }, [session]);
85
+ useInput((_, key) => {
86
+ if (overlay !== "none")
87
+ return;
88
+ if (key.escape && running) {
89
+ session.abort();
90
+ }
91
+ });
92
+ const handleSubmit = useCallback(async (value) => {
93
+ if (value === "/quit") {
94
+ onQuit();
95
+ exit();
96
+ return;
97
+ }
98
+ if (value === "/new") {
99
+ session.newSession();
100
+ setDisplayMessages([]);
101
+ setStreamingText("");
102
+ return;
103
+ }
104
+ if (value === "/settings") {
105
+ setOverlay("settings");
106
+ return;
107
+ }
108
+ if (value.startsWith("/model")) {
109
+ const parts = value.split(/\s+/);
110
+ setModelFilter(parts[1] ?? undefined);
111
+ setOverlay("model");
112
+ return;
113
+ }
114
+ if (running)
115
+ return;
116
+ await session.prompt(value);
117
+ }, [session, running, onQuit, exit]);
118
+ return (_jsxs(Box, { flexDirection: "column", height: "100%", children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { color: theme.header, bold: true, children: "agent-dev" }), _jsx(Text, { color: theme.muted, children: " \u2014 /model /settings /new /quit | Esc abort" })] }), _jsx(ChatView, { messages: displayMessages, theme: theme, streamingText: streamingText }), _jsx(Footer, { workdir: workdir, model: model, theme: theme, running: running }), overlay === "none" && (_jsx(Editor, { theme: theme, disabled: running, onSubmit: handleSubmit })), overlay === "model" && (_jsx(ModelSelector, { theme: theme, settings: settings, filter: modelFilter, onSelect: (m) => {
119
+ session.setModel(m);
120
+ setModel(m);
121
+ setOverlay("none");
122
+ setModelFilter(undefined);
123
+ }, onClose: () => {
124
+ setOverlay("none");
125
+ setModelFilter(undefined);
126
+ } })), overlay === "settings" && (_jsx(SettingsView, { theme: theme, settings: settings, onUpdate: (s) => {
127
+ session.updateSettings(s);
128
+ setSettings(s);
129
+ saveSettings(s);
130
+ }, onClose: () => setOverlay("none") }))] }));
131
+ }
@@ -0,0 +1,10 @@
1
+ import React from "react";
2
+ import type { DisplayMessage } from "./App.js";
3
+ import type { ThemeColors } from "./theme.js";
4
+ interface ChatViewProps {
5
+ messages: DisplayMessage[];
6
+ theme: ThemeColors;
7
+ streamingText?: string;
8
+ }
9
+ export declare function ChatView({ messages, theme, streamingText }: ChatViewProps): React.JSX.Element;
10
+ export {};
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from "ink";
3
+ export function ChatView({ messages, theme, streamingText }) {
4
+ return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, marginBottom: 1, children: [messages.length === 0 && !streamingText && (_jsx(Text, { color: theme.muted, children: "Type a message or /model to select a provider. /settings for options." })), messages.map((msg, i) => (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [msg.role === "user" && (_jsxs(Text, { color: theme.user, bold: true, children: ["You: ", msg.content] })), msg.role === "assistant" && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: theme.assistant, bold: true, children: "Assistant:" }), _jsx(Text, { color: theme.assistant, children: msg.content || "" })] })), msg.role === "tool" && (_jsx(Box, { flexDirection: "column", children: _jsxs(Text, { color: theme.tool, children: ["[", msg.toolName, "] ", msg.content.slice(0, 200), msg.content.length > 200 ? "..." : ""] }) }))] }, i))), streamingText && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: theme.assistant, bold: true, children: "Assistant:" }), _jsx(Text, { color: theme.assistant, children: streamingText })] }))] }));
5
+ }
@@ -0,0 +1,10 @@
1
+ import React from "react";
2
+ import type { ThemeColors } from "./theme.js";
3
+ interface EditorProps {
4
+ theme: ThemeColors;
5
+ disabled?: boolean;
6
+ onSubmit: (value: string) => void;
7
+ filterHint?: string;
8
+ }
9
+ export declare function Editor({ theme, disabled, onSubmit, filterHint }: EditorProps): React.JSX.Element;
10
+ export {};
@@ -0,0 +1,35 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { Box, Text, useInput } from "ink";
4
+ const SLASH_COMMANDS = ["/model", "/settings", "/new", "/quit"];
5
+ export function Editor({ theme, disabled, onSubmit, filterHint }) {
6
+ const [value, setValue] = useState(filterHint ?? "");
7
+ const [suggestions, setSuggestions] = useState([]);
8
+ useInput((input, key) => {
9
+ if (disabled)
10
+ return;
11
+ if (key.return) {
12
+ const trimmed = value.trim();
13
+ if (trimmed)
14
+ onSubmit(trimmed);
15
+ setValue("");
16
+ setSuggestions([]);
17
+ return;
18
+ }
19
+ if (key.backspace || key.delete) {
20
+ setValue((v) => v.slice(0, -1));
21
+ return;
22
+ }
23
+ if (input && !key.ctrl && !key.meta) {
24
+ const newVal = value + input;
25
+ setValue(newVal);
26
+ if (newVal.startsWith("/")) {
27
+ setSuggestions(SLASH_COMMANDS.filter((c) => c.startsWith(newVal)));
28
+ }
29
+ else {
30
+ setSuggestions([]);
31
+ }
32
+ }
33
+ });
34
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: theme.accent, paddingX: 1, children: [suggestions.length > 0 && (_jsx(Text, { color: theme.muted, children: suggestions.join(" ") })), _jsxs(Text, { children: [_jsx(Text, { color: theme.accent, children: "> " }), _jsx(Text, { children: value }), _jsx(Text, { color: theme.muted, children: "\u2588" })] })] }));
35
+ }
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ import type { Model } from "../providers/types.js";
3
+ import type { ThemeColors } from "./theme.js";
4
+ interface FooterProps {
5
+ workdir: string;
6
+ model: Model;
7
+ theme: ThemeColors;
8
+ running: boolean;
9
+ }
10
+ export declare function Footer({ workdir, model, theme, running }: FooterProps): React.JSX.Element;
11
+ export {};
@@ -0,0 +1,6 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box, Text } from "ink";
3
+ import { modelRef } from "../config/models.js";
4
+ export function Footer({ workdir, model, theme, running }) {
5
+ return (_jsx(Box, { borderStyle: "single", borderColor: theme.border, paddingX: 1, children: _jsxs(Text, { color: theme.muted, children: [workdir, " | ", modelRef(model), " ", running ? "| working..." : ""] }) }));
6
+ }
@@ -0,0 +1,13 @@
1
+ import React from "react";
2
+ import type { Model } from "../providers/types.js";
3
+ import type { ThemeColors } from "./theme.js";
4
+ import type { Settings } from "../config/settings.js";
5
+ interface ModelSelectorProps {
6
+ theme: ThemeColors;
7
+ settings: Settings;
8
+ filter?: string;
9
+ onSelect: (model: Model) => void;
10
+ onClose: () => void;
11
+ }
12
+ export declare function ModelSelector({ theme, settings, filter, onSelect, onClose }: ModelSelectorProps): React.JSX.Element;
13
+ export {};
@@ -0,0 +1,42 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { Box, Text, useInput } from "ink";
4
+ import { ALL_MODELS, modelRef, PROVIDER_LABELS } from "../config/models.js";
5
+ import { hasProviderAuth } from "../providers/registry.js";
6
+ function fuzzyMatch(text, query) {
7
+ if (!query)
8
+ return true;
9
+ const lower = text.toLowerCase();
10
+ const q = query.toLowerCase();
11
+ return lower.includes(q) || modelRef({ provider: "openai", id: text, name: text }).includes(q);
12
+ }
13
+ export function ModelSelector({ theme, settings, filter, onSelect, onClose }) {
14
+ const filtered = ALL_MODELS.filter((m) => {
15
+ const label = `${PROVIDER_LABELS[m.provider]} ${m.name} ${modelRef(m)}`;
16
+ return fuzzyMatch(label, filter ?? "");
17
+ });
18
+ const [index, setIndex] = useState(0);
19
+ const safeIndex = Math.min(index, Math.max(0, filtered.length - 1));
20
+ useInput((input, key) => {
21
+ if (key.escape) {
22
+ onClose();
23
+ return;
24
+ }
25
+ if (key.upArrow)
26
+ setIndex((i) => Math.max(0, i - 1));
27
+ if (key.downArrow)
28
+ setIndex((i) => Math.min(filtered.length - 1, i + 1));
29
+ if (key.return && filtered[safeIndex]) {
30
+ onSelect(filtered[safeIndex]);
31
+ }
32
+ if (input && !key.ctrl) {
33
+ // typing not used in overlay mode
34
+ }
35
+ });
36
+ const providers = ["openai", "groq", "gemini", "free"];
37
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "double", borderColor: theme.accent, padding: 1, children: [_jsx(Text, { color: theme.header, bold: true, children: "/model \u2014 select provider & model (Esc to close)" }), filter && _jsxs(Text, { color: theme.muted, children: ["Filter: ", filter] }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [filtered.length === 0 && _jsx(Text, { color: theme.muted, children: "No models match" }), filtered.map((m, i) => {
38
+ const hasKey = hasProviderAuth(m.provider, settings);
39
+ const selected = i === safeIndex;
40
+ return (_jsxs(Text, { color: selected ? theme.accent : theme.assistant, children: [selected ? "> " : " ", "[", PROVIDER_LABELS[m.provider], "] ", m.name, !hasKey ? " (no key)" : ""] }, modelRef(m)));
41
+ })] }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: theme.muted, children: ["Providers: ", providers.map((p) => `${PROVIDER_LABELS[p]}${hasProviderAuth(p, settings) ? "" : " (no key)"}`).join(" | ")] }) })] }));
42
+ }
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ import type { ThemeColors } from "./theme.js";
3
+ import type { Settings } from "../config/settings.js";
4
+ interface SettingsViewProps {
5
+ theme: ThemeColors;
6
+ settings: Settings;
7
+ onUpdate: (settings: Settings) => void;
8
+ onClose: () => void;
9
+ }
10
+ export declare function SettingsView({ theme, settings, onUpdate, onClose }: SettingsViewProps): React.JSX.Element;
11
+ export {};
@@ -0,0 +1,37 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { Box, Text, useInput } from "ink";
4
+ import { PROVIDER_ENV_VARS } from "../providers/registry.js";
5
+ const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high"];
6
+ const THEMES = ["dark", "light"];
7
+ export function SettingsView({ theme, settings, onUpdate, onClose }) {
8
+ const [index, setIndex] = useState(0);
9
+ const items = ["thinkingLevel", "theme", "envKeys"];
10
+ useInput((_, key) => {
11
+ if (key.escape)
12
+ onClose();
13
+ if (key.upArrow)
14
+ setIndex((i) => Math.max(0, i - 1));
15
+ if (key.downArrow)
16
+ setIndex((i) => Math.min(items.length - 1, i + 1));
17
+ if (key.return) {
18
+ const item = items[index];
19
+ if (item === "thinkingLevel") {
20
+ const cur = THINKING_LEVELS.indexOf(settings.thinkingLevel);
21
+ const next = THINKING_LEVELS[(cur + 1) % THINKING_LEVELS.length];
22
+ onUpdate({ ...settings, thinkingLevel: next });
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
+ }
30
+ });
31
+ const providers = ["openai", "groq", "gemini", "free"];
32
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "double", borderColor: theme.accent, padding: 1, children: [_jsx(Text, { color: theme.header, bold: true, children: "/settings (Enter to cycle, Esc to close)" }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { color: index === 0 ? theme.accent : theme.assistant, children: [index === 0 ? "> " : " ", "Thinking level: ", settings.thinkingLevel] }), _jsxs(Text, { color: index === 1 ? theme.accent : theme.assistant, children: [index === 1 ? "> " : " ", "Theme: ", settings.theme] }), _jsxs(Text, { color: index === 2 ? theme.accent : theme.muted, children: [index === 2 ? "> " : " ", "API keys (env vars):"] }), providers.map((p) => {
33
+ const vars = PROVIDER_ENV_VARS[p];
34
+ const set = vars.some((v) => process.env[v]);
35
+ return (_jsxs(Text, { color: theme.muted, children: [" ", p, ": ", vars.join(" or "), " \u2014 ", set ? "set" : "not set"] }, p));
36
+ })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.muted, children: "Set OPENAI_API_KEY, GROQ_API_KEY, GEMINI_API_KEY, OPENROUTER_API_KEY" }) })] }));
37
+ }
@@ -0,0 +1,13 @@
1
+ import type { Theme } from "../providers/types.js";
2
+ export interface ThemeColors {
3
+ user: string;
4
+ assistant: string;
5
+ tool: string;
6
+ border: string;
7
+ muted: string;
8
+ accent: string;
9
+ error: string;
10
+ header: string;
11
+ }
12
+ export declare const themes: Record<Theme, ThemeColors>;
13
+ export declare function getTheme(theme: Theme): ThemeColors;
@@ -0,0 +1,25 @@
1
+ export const themes = {
2
+ dark: {
3
+ user: "cyan",
4
+ assistant: "white",
5
+ tool: "yellow",
6
+ border: "gray",
7
+ muted: "gray",
8
+ accent: "green",
9
+ error: "red",
10
+ header: "blue",
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
+ },
22
+ };
23
+ export function getTheme(theme) {
24
+ return themes[theme];
25
+ }
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@devang0907/agent-dev",
3
+ "version": "0.1.0",
4
+ "description": "Minimal pi-like terminal coding agent with Ink UI",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "Devang Rakholiya <rakholiyadevang@gmail.com>",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/Devang0907/agent-dev.git"
11
+ },
12
+ "keywords": ["ai", "agent", "cli", "ink", "coding-agent"],
13
+ "files": ["dist", "README.md"],
14
+ "bin": {
15
+ "agent": "dist/cli.js"
16
+ },
17
+ "scripts": {
18
+ "dev": "tsx src/cli.ts",
19
+ "build": "tsc",
20
+ "start": "node dist/cli.js",
21
+ "typecheck": "tsc --noEmit",
22
+ "prepublishOnly": "npm run build",
23
+ "publish:public": "npm publish --access public"
24
+ },
25
+ "engines": {
26
+ "node": ">=20"
27
+ },
28
+ "dependencies": {
29
+ "@google/genai": "^1.0.0",
30
+ "chalk": "^5.4.1",
31
+ "ink": "^5.2.0",
32
+ "openai": "^4.85.0",
33
+ "react": "^18.3.1",
34
+ "typebox": "^1.0.27"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^22.13.0",
38
+ "@types/react": "^18.3.18",
39
+ "tsx": "^4.19.2",
40
+ "typescript": "^5.7.3"
41
+ }
42
+ }