@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
package/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # agent-dev
2
+
3
+ A minimal pi-like terminal coding agent with an Ink UI. Chat with an AI that can read, write, edit files, and run bash commands.
4
+
5
+ ## Quick start
6
+
7
+ ```bash
8
+ npm install
9
+ npm run dev
10
+ ```
11
+
12
+ Set at least one API key:
13
+
14
+ ```bash
15
+ export OPENROUTER_API_KEY=sk-or-... # Free models (default)
16
+ export OPENAI_API_KEY=sk-... # ChatGPT
17
+ export GROQ_API_KEY=gsk_... # Groq
18
+ export GEMINI_API_KEY=... # Google Gemini
19
+ ```
20
+
21
+ ## Providers
22
+
23
+ | Provider | Env var | Example models |
24
+ |----------|---------|----------------|
25
+ | OpenAI (ChatGPT) | `OPENAI_API_KEY` | `gpt-4o`, `gpt-4o-mini` |
26
+ | Groq | `GROQ_API_KEY` | `llama-3.3-70b-versatile` |
27
+ | Google Gemini | `GEMINI_API_KEY` or `GOOGLE_API_KEY` | `gemini-2.0-flash` |
28
+ | Free (OpenRouter) | `OPENROUTER_API_KEY` | `meta-llama/llama-3.3-70b-instruct:free` |
29
+
30
+ ## Interactive commands
31
+
32
+ | Command | Description |
33
+ |---------|-------------|
34
+ | `/model` | Open model selector (grouped by provider) |
35
+ | `/model groq` | Open selector filtered by search |
36
+ | `/settings` | Thinking level, theme, API key status |
37
+ | `/new` | Clear session |
38
+ | `/quit` | Exit |
39
+
40
+ ## CLI
41
+
42
+ ```bash
43
+ npm run dev # Interactive
44
+ npm run dev -- -p "List files" # Print mode
45
+ npm run dev -- -c # Continue last session
46
+ npm run dev -- --model groq/llama-3.3-70b-versatile "hello"
47
+ npm run build && npm start
48
+ ```
49
+
50
+ Config and sessions are stored in `~/.agent-dev/`.
51
+
52
+ ## Tools
53
+
54
+ The agent has four built-in tools: `read`, `write`, `edit`, `bash`. File operations are restricted to the current working directory.
55
+
56
+ ## License
57
+
58
+ MIT
@@ -0,0 +1,32 @@
1
+ import type { ChatMessage, Model, ToolCall } from "../providers/types.js";
2
+ import type { Settings } from "../config/settings.js";
3
+ export type AgentEvent = {
4
+ type: "message_start";
5
+ role: "assistant";
6
+ } | {
7
+ type: "text_delta";
8
+ delta: string;
9
+ } | {
10
+ type: "tool_call";
11
+ toolCall: ToolCall;
12
+ } | {
13
+ type: "tool_result";
14
+ toolCallId: string;
15
+ name: string;
16
+ result: string;
17
+ } | {
18
+ type: "turn_end";
19
+ } | {
20
+ type: "error";
21
+ message: string;
22
+ };
23
+ export interface AgentLoopOptions {
24
+ model: Model;
25
+ messages: ChatMessage[];
26
+ settings: Settings;
27
+ workdir: string;
28
+ systemPrompt?: string;
29
+ signal?: AbortSignal;
30
+ onEvent: (event: AgentEvent) => void;
31
+ }
32
+ export declare function runAgentLoop(options: AgentLoopOptions): Promise<ChatMessage[]>;
@@ -0,0 +1,83 @@
1
+ import { streamChat } from "../providers/registry.js";
2
+ import { getToolDefinitions, executeTool } from "./tools/index.js";
3
+ const DEFAULT_SYSTEM_PROMPT = `You are a helpful coding assistant with access to tools: read, write, edit, and bash.
4
+ Use tools to inspect and modify the codebase. Be concise and accurate.
5
+ Working directory: ${process.cwd()}`;
6
+ async function collectStream(model, messages, settings, systemPrompt, signal, onEvent) {
7
+ const tools = getToolDefinitions();
8
+ let content = "";
9
+ const toolCallMap = new Map();
10
+ const stream = streamChat(model, {
11
+ messages,
12
+ tools,
13
+ systemPrompt,
14
+ thinkingLevel: settings.thinkingLevel,
15
+ signal,
16
+ });
17
+ for await (const event of stream) {
18
+ if (event.type === "text_delta") {
19
+ content += event.delta;
20
+ onEvent?.({ type: "text_delta", delta: event.delta });
21
+ }
22
+ else if (event.type === "tool_call_delta") {
23
+ if (!toolCallMap.has(event.index)) {
24
+ toolCallMap.set(event.index, { id: event.id ?? "", name: event.name ?? "", arguments: "" });
25
+ }
26
+ const tc = toolCallMap.get(event.index);
27
+ if (event.id)
28
+ tc.id = event.id;
29
+ if (event.name)
30
+ tc.name = event.name;
31
+ if (event.argumentsDelta)
32
+ tc.arguments += event.argumentsDelta;
33
+ }
34
+ else if (event.type === "error") {
35
+ return { content, toolCalls: [], error: event.message };
36
+ }
37
+ }
38
+ const toolCalls = Array.from(toolCallMap.values()).filter((tc) => tc.name);
39
+ return { content, toolCalls };
40
+ }
41
+ export async function runAgentLoop(options) {
42
+ const { model, messages, settings, workdir, systemPrompt = DEFAULT_SYSTEM_PROMPT, signal, onEvent, } = options;
43
+ const context = [...messages];
44
+ while (true) {
45
+ if (signal?.aborted)
46
+ break;
47
+ onEvent({ type: "message_start", role: "assistant" });
48
+ const { content, toolCalls, error } = await collectStream(model, context, settings, systemPrompt, signal, onEvent);
49
+ if (error) {
50
+ onEvent({ type: "error", message: error });
51
+ break;
52
+ }
53
+ const assistantMsg = {
54
+ role: "assistant",
55
+ content,
56
+ toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
57
+ };
58
+ context.push(assistantMsg);
59
+ if (toolCalls.length === 0) {
60
+ onEvent({ type: "turn_end" });
61
+ break;
62
+ }
63
+ for (const tc of toolCalls) {
64
+ onEvent({ type: "tool_call", toolCall: tc });
65
+ let args = {};
66
+ try {
67
+ args = JSON.parse(tc.arguments || "{}");
68
+ }
69
+ catch {
70
+ args = {};
71
+ }
72
+ const result = await executeTool(tc.name, args, workdir);
73
+ onEvent({ type: "tool_result", toolCallId: tc.id, name: tc.name, result });
74
+ context.push({
75
+ role: "tool",
76
+ content: result,
77
+ toolCallId: tc.id,
78
+ name: tc.name,
79
+ });
80
+ }
81
+ }
82
+ return context.slice(messages.length);
83
+ }
@@ -0,0 +1,33 @@
1
+ import { EventEmitter } from "node:events";
2
+ import type { ChatMessage, Model } from "../providers/types.js";
3
+ import type { Settings } from "../config/settings.js";
4
+ import { type AgentEvent } from "./loop.js";
5
+ import { SessionManager } from "../session/manager.js";
6
+ export type SessionEvent = AgentEvent | {
7
+ type: "user_message";
8
+ content: string;
9
+ } | {
10
+ type: "model_changed";
11
+ model: Model;
12
+ };
13
+ export declare class AgentSession extends EventEmitter {
14
+ private messages;
15
+ private model;
16
+ private settings;
17
+ private workdir;
18
+ private sessionManager;
19
+ private abortController?;
20
+ private running;
21
+ constructor(settings: Settings, sessionManager: SessionManager, workdir: string, initialModel?: Model);
22
+ getModel(): Model;
23
+ getSettings(): Settings;
24
+ getMessages(): ChatMessage[];
25
+ isRunning(): boolean;
26
+ setModel(model: Model): void;
27
+ updateSettings(settings: Settings): void;
28
+ abort(): void;
29
+ prompt(content: string): Promise<void>;
30
+ newSession(): void;
31
+ getAvailableModels(): Model[];
32
+ static resolveInitialModel(settings: Settings, modelRef?: string): Model | undefined;
33
+ }
@@ -0,0 +1,107 @@
1
+ import { EventEmitter } from "node:events";
2
+ import { findModel } from "../config/models.js";
3
+ import { setDefaultModel, saveSettings } from "../config/settings.js";
4
+ import { getAvailableModels, getDefaultModelForProvider } from "../providers/registry.js";
5
+ import { runAgentLoop } from "./loop.js";
6
+ export class AgentSession extends EventEmitter {
7
+ messages = [];
8
+ model;
9
+ settings;
10
+ workdir;
11
+ sessionManager;
12
+ abortController;
13
+ running = false;
14
+ constructor(settings, sessionManager, workdir, initialModel) {
15
+ super();
16
+ this.settings = settings;
17
+ this.sessionManager = sessionManager;
18
+ this.workdir = workdir;
19
+ this.messages = sessionManager.getMessages();
20
+ const available = getAvailableModels(settings);
21
+ const fromSettings = findModel(settings.defaultProvider, settings.defaultModel);
22
+ this.model =
23
+ initialModel ??
24
+ (fromSettings && available.some((m) => m.provider === fromSettings.provider && m.id === fromSettings.id)
25
+ ? fromSettings
26
+ : available[0] ?? findModel("free", "meta-llama/llama-3.3-70b-instruct:free"));
27
+ }
28
+ getModel() {
29
+ return this.model;
30
+ }
31
+ getSettings() {
32
+ return this.settings;
33
+ }
34
+ getMessages() {
35
+ return [...this.messages];
36
+ }
37
+ isRunning() {
38
+ return this.running;
39
+ }
40
+ setModel(model) {
41
+ this.model = model;
42
+ this.settings = setDefaultModel(this.settings, model.provider, model.id);
43
+ this.sessionManager.appendModelChange(model);
44
+ this.emit("event", { type: "model_changed", model });
45
+ }
46
+ updateSettings(settings) {
47
+ this.settings = settings;
48
+ saveSettings(settings);
49
+ }
50
+ abort() {
51
+ this.abortController?.abort();
52
+ }
53
+ async prompt(content) {
54
+ if (this.running)
55
+ return;
56
+ this.running = true;
57
+ const userMsg = { role: "user", content };
58
+ this.messages.push(userMsg);
59
+ this.sessionManager.appendMessage(userMsg);
60
+ this.emit("event", { type: "user_message", content });
61
+ this.abortController = new AbortController();
62
+ try {
63
+ const newMessages = await runAgentLoop({
64
+ model: this.model,
65
+ messages: [...this.messages],
66
+ settings: this.settings,
67
+ workdir: this.workdir,
68
+ signal: this.abortController.signal,
69
+ onEvent: (event) => this.emit("event", event),
70
+ });
71
+ for (const msg of newMessages) {
72
+ if (msg.role !== "user") {
73
+ this.messages.push(msg);
74
+ this.sessionManager.appendMessage(msg);
75
+ }
76
+ }
77
+ }
78
+ finally {
79
+ this.running = false;
80
+ this.abortController = undefined;
81
+ this.sessionManager.saveAsLast();
82
+ }
83
+ }
84
+ newSession() {
85
+ this.messages = [];
86
+ this.sessionManager.clear();
87
+ }
88
+ getAvailableModels() {
89
+ return getAvailableModels(this.settings);
90
+ }
91
+ static resolveInitialModel(settings, modelRef) {
92
+ if (modelRef) {
93
+ const slash = modelRef.indexOf("/");
94
+ if (slash > 0) {
95
+ const provider = modelRef.slice(0, slash);
96
+ const id = modelRef.slice(slash + 1);
97
+ return findModel(provider, id);
98
+ }
99
+ }
100
+ const available = getAvailableModels(settings);
101
+ const fromSettings = findModel(settings.defaultProvider, settings.defaultModel);
102
+ if (fromSettings && available.some((m) => m.provider === fromSettings.provider && m.id === fromSettings.id)) {
103
+ return fromSettings;
104
+ }
105
+ return available[0] ?? getDefaultModelForProvider(settings.defaultProvider);
106
+ }
107
+ }
@@ -0,0 +1,8 @@
1
+ import type { ToolDefinition } from "../../providers/types.js";
2
+ export interface AgentTool {
3
+ definition: ToolDefinition;
4
+ execute: (args: Record<string, unknown>, workdir: string) => Promise<string>;
5
+ }
6
+ export declare const BUILTIN_TOOLS: AgentTool[];
7
+ export declare function getToolDefinitions(): ToolDefinition[];
8
+ export declare function executeTool(name: string, args: Record<string, unknown>, workdir: string): Promise<string>;
@@ -0,0 +1,21 @@
1
+ import { readTool, writeTool, editTool, bashTool, executeRead, executeWrite, executeEdit, executeBash, } from "./read.js";
2
+ export const BUILTIN_TOOLS = [
3
+ { definition: readTool, execute: (args, wd) => executeRead(args, wd) },
4
+ { definition: writeTool, execute: (args, wd) => executeWrite(args, wd) },
5
+ { definition: editTool, execute: (args, wd) => executeEdit(args, wd) },
6
+ { definition: bashTool, execute: (args, wd) => executeBash(args, wd) },
7
+ ];
8
+ export function getToolDefinitions() {
9
+ return BUILTIN_TOOLS.map((t) => t.definition);
10
+ }
11
+ export async function executeTool(name, args, workdir) {
12
+ const tool = BUILTIN_TOOLS.find((t) => t.definition.name === name);
13
+ if (!tool)
14
+ return `Error: unknown tool ${name}`;
15
+ try {
16
+ return await tool.execute(args, workdir);
17
+ }
18
+ catch (err) {
19
+ return `Error: ${err instanceof Error ? err.message : String(err)}`;
20
+ }
21
+ }
@@ -0,0 +1,20 @@
1
+ import type { ToolDefinition } from "../../providers/types.js";
2
+ export declare const readTool: ToolDefinition;
3
+ export declare function executeRead(args: {
4
+ path: string;
5
+ }, workdir?: string): Promise<string>;
6
+ export declare const writeTool: ToolDefinition;
7
+ export declare function executeWrite(args: {
8
+ path: string;
9
+ content: string;
10
+ }, workdir?: string): Promise<string>;
11
+ export declare const editTool: ToolDefinition;
12
+ export declare function executeEdit(args: {
13
+ path: string;
14
+ old_string: string;
15
+ new_string: string;
16
+ }, workdir?: string): Promise<string>;
17
+ export declare const bashTool: ToolDefinition;
18
+ export declare function executeBash(args: {
19
+ command: string;
20
+ }, workdir?: string): Promise<string>;
@@ -0,0 +1,108 @@
1
+ import { readFileSync, writeFileSync, existsSync } from "node:fs";
2
+ import { resolve, isAbsolute } from "node:path";
3
+ const DEFAULT_WORKDIR = process.cwd();
4
+ function resolvePath(path, workdir = DEFAULT_WORKDIR) {
5
+ return isAbsolute(path) ? path : resolve(workdir, path);
6
+ }
7
+ function assertWithinWorkdir(path, workdir = DEFAULT_WORKDIR) {
8
+ const resolved = resolve(path);
9
+ const root = resolve(workdir);
10
+ if (!resolved.startsWith(root)) {
11
+ throw new Error(`Path outside working directory: ${path}`);
12
+ }
13
+ }
14
+ export const readTool = {
15
+ name: "read",
16
+ description: "Read the contents of a file",
17
+ parameters: {
18
+ type: "object",
19
+ properties: {
20
+ path: { type: "string", description: "File path relative to project root" },
21
+ },
22
+ required: ["path"],
23
+ },
24
+ };
25
+ export async function executeRead(args, workdir = DEFAULT_WORKDIR) {
26
+ const filePath = resolvePath(args.path, workdir);
27
+ assertWithinWorkdir(filePath, workdir);
28
+ if (!existsSync(filePath)) {
29
+ return `Error: file not found: ${args.path}`;
30
+ }
31
+ const content = readFileSync(filePath, "utf-8");
32
+ return content.length > 50000 ? content.slice(0, 50000) + "\n... (truncated)" : content;
33
+ }
34
+ export const writeTool = {
35
+ name: "write",
36
+ description: "Write content to a file (creates or overwrites)",
37
+ parameters: {
38
+ type: "object",
39
+ properties: {
40
+ path: { type: "string", description: "File path relative to project root" },
41
+ content: { type: "string", description: "Content to write" },
42
+ },
43
+ required: ["path", "content"],
44
+ },
45
+ };
46
+ export async function executeWrite(args, workdir = DEFAULT_WORKDIR) {
47
+ const filePath = resolvePath(args.path, workdir);
48
+ assertWithinWorkdir(filePath, workdir);
49
+ writeFileSync(filePath, args.content, "utf-8");
50
+ return `Written ${args.content.length} bytes to ${args.path}`;
51
+ }
52
+ export const editTool = {
53
+ name: "edit",
54
+ description: "Replace old_string with new_string in a file",
55
+ parameters: {
56
+ type: "object",
57
+ properties: {
58
+ path: { type: "string", description: "File path relative to project root" },
59
+ old_string: { type: "string", description: "String to find" },
60
+ new_string: { type: "string", description: "Replacement string" },
61
+ },
62
+ required: ["path", "old_string", "new_string"],
63
+ },
64
+ };
65
+ export async function executeEdit(args, workdir = DEFAULT_WORKDIR) {
66
+ const filePath = resolvePath(args.path, workdir);
67
+ assertWithinWorkdir(filePath, workdir);
68
+ if (!existsSync(filePath)) {
69
+ return `Error: file not found: ${args.path}`;
70
+ }
71
+ const content = readFileSync(filePath, "utf-8");
72
+ if (!content.includes(args.old_string)) {
73
+ return `Error: old_string not found in ${args.path}`;
74
+ }
75
+ const newContent = content.replace(args.old_string, args.new_string);
76
+ writeFileSync(filePath, newContent, "utf-8");
77
+ return `Edited ${args.path}`;
78
+ }
79
+ export const bashTool = {
80
+ name: "bash",
81
+ description: "Run a shell command in the project directory",
82
+ parameters: {
83
+ type: "object",
84
+ properties: {
85
+ command: { type: "string", description: "Shell command to execute" },
86
+ },
87
+ required: ["command"],
88
+ },
89
+ };
90
+ export async function executeBash(args, workdir = DEFAULT_WORKDIR) {
91
+ const { exec } = await import("node:child_process");
92
+ const { promisify } = await import("node:util");
93
+ const execAsync = promisify(exec);
94
+ try {
95
+ const { stdout, stderr } = await execAsync(args.command, {
96
+ cwd: workdir,
97
+ timeout: 120000,
98
+ maxBuffer: 1024 * 1024,
99
+ });
100
+ const out = stdout + (stderr ? `\n${stderr}` : "");
101
+ return out.trim() || "(no output)";
102
+ }
103
+ catch (err) {
104
+ const e = err;
105
+ const out = (e.stdout ?? "") + (e.stderr ? `\n${e.stderr}` : "");
106
+ return out.trim() || (e.message ?? "Command failed");
107
+ }
108
+ }
@@ -0,0 +1,9 @@
1
+ export interface CliArgs {
2
+ print: boolean;
3
+ continueSession: boolean;
4
+ model?: string;
5
+ prompt?: string;
6
+ help: boolean;
7
+ }
8
+ export declare function parseArgs(argv: string[]): CliArgs;
9
+ export declare function printHelp(): void;
@@ -0,0 +1,60 @@
1
+ export function parseArgs(argv) {
2
+ const args = argv.slice(2);
3
+ const result = {
4
+ print: false,
5
+ continueSession: false,
6
+ help: false,
7
+ };
8
+ const positional = [];
9
+ for (let i = 0; i < args.length; i++) {
10
+ const arg = args[i];
11
+ if (arg === "-h" || arg === "--help") {
12
+ result.help = true;
13
+ }
14
+ else if (arg === "-p" || arg === "--print") {
15
+ result.print = true;
16
+ }
17
+ else if (arg === "-c" || arg === "--continue") {
18
+ result.continueSession = true;
19
+ }
20
+ else if (arg === "--model" && args[i + 1]) {
21
+ result.model = args[++i];
22
+ }
23
+ else if (!arg.startsWith("-")) {
24
+ positional.push(arg);
25
+ }
26
+ }
27
+ if (positional.length > 0) {
28
+ result.prompt = positional.join(" ");
29
+ }
30
+ return result;
31
+ }
32
+ export function printHelp() {
33
+ console.log(`
34
+ agent-dev — minimal pi-like coding agent
35
+
36
+ Usage:
37
+ agent Interactive mode
38
+ agent -p "prompt" Print mode (no TUI)
39
+ agent -c Continue last session
40
+ agent --model groq/llama-3.3-70b-versatile "prompt"
41
+
42
+ Options:
43
+ -p, --print Print response and exit
44
+ -c, --continue Continue most recent session
45
+ --model <ref> Provider/model (e.g. openai/gpt-4o)
46
+ -h, --help Show help
47
+
48
+ Commands (interactive):
49
+ /model [search] Select model
50
+ /settings Settings menu
51
+ /new New session
52
+ /quit Quit
53
+
54
+ Environment:
55
+ OPENAI_API_KEY OpenAI (ChatGPT)
56
+ GROQ_API_KEY Groq
57
+ GEMINI_API_KEY Google Gemini
58
+ OPENROUTER_API_KEY Free models via OpenRouter
59
+ `);
60
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ import { main } from "./main.js";
3
+ main().catch((err) => {
4
+ console.error(err);
5
+ process.exit(1);
6
+ });
@@ -0,0 +1,6 @@
1
+ import type { Model, ProviderId } from "../providers/types.js";
2
+ export declare const PROVIDER_LABELS: Record<ProviderId, string>;
3
+ export declare const ALL_MODELS: Model[];
4
+ export declare function findModel(provider: ProviderId, id: string): Model | undefined;
5
+ export declare function parseModelRef(ref: string): Model | undefined;
6
+ export declare function modelRef(model: Model): string;
@@ -0,0 +1,43 @@
1
+ export const PROVIDER_LABELS = {
2
+ openai: "OpenAI (ChatGPT)",
3
+ groq: "Groq",
4
+ gemini: "Google Gemini",
5
+ free: "Free (OpenRouter)",
6
+ };
7
+ export const ALL_MODELS = [
8
+ { provider: "openai", id: "gpt-4o", name: "GPT-4o" },
9
+ { provider: "openai", id: "gpt-4o-mini", name: "GPT-4o Mini" },
10
+ { provider: "groq", id: "llama-3.3-70b-versatile", name: "Llama 3.3 70B" },
11
+ { provider: "groq", id: "openai/gpt-oss-120b", name: "GPT-OSS 120B" },
12
+ { provider: "gemini", id: "gemini-2.0-flash", name: "Gemini 2.0 Flash" },
13
+ { provider: "gemini", id: "gemini-2.5-flash", name: "Gemini 2.5 Flash" },
14
+ {
15
+ provider: "free",
16
+ id: "meta-llama/llama-3.3-70b-instruct:free",
17
+ name: "Llama 3.3 70B (free)",
18
+ },
19
+ {
20
+ provider: "free",
21
+ id: "google/gemini-2.0-flash-exp:free",
22
+ name: "Gemini 2.0 Flash (free)",
23
+ },
24
+ {
25
+ provider: "free",
26
+ id: "qwen/qwen-2.5-72b-instruct:free",
27
+ name: "Qwen 2.5 72B (free)",
28
+ },
29
+ ];
30
+ export function findModel(provider, id) {
31
+ return ALL_MODELS.find((m) => m.provider === provider && m.id === id);
32
+ }
33
+ export function parseModelRef(ref) {
34
+ const slash = ref.indexOf("/");
35
+ if (slash === -1)
36
+ return undefined;
37
+ const provider = ref.slice(0, slash);
38
+ const id = ref.slice(slash + 1);
39
+ return findModel(provider, id);
40
+ }
41
+ export function modelRef(model) {
42
+ return `${model.provider}/${model.id}`;
43
+ }
@@ -0,0 +1,4 @@
1
+ export declare const CONFIG_DIR: string;
2
+ export declare const SETTINGS_PATH: string;
3
+ export declare const SESSIONS_DIR: string;
4
+ export declare const LAST_SESSION_PATH: string;
@@ -0,0 +1,6 @@
1
+ import { homedir } from "node:os";
2
+ import { join } from "node:path";
3
+ export const CONFIG_DIR = process.env.AGENT_DEV_DIR ?? join(homedir(), ".agent-dev");
4
+ export const SETTINGS_PATH = join(CONFIG_DIR, "settings.json");
5
+ export const SESSIONS_DIR = join(CONFIG_DIR, "sessions");
6
+ export const LAST_SESSION_PATH = join(CONFIG_DIR, "last-session.json");
@@ -0,0 +1,12 @@
1
+ import type { ProviderId } from "../providers/types.js";
2
+ import type { ThinkingLevel, Theme } from "../providers/types.js";
3
+ export interface Settings {
4
+ defaultProvider: ProviderId;
5
+ defaultModel: string;
6
+ thinkingLevel: ThinkingLevel;
7
+ theme: Theme;
8
+ apiKeys?: Partial<Record<ProviderId, string>>;
9
+ }
10
+ export declare function loadSettings(): Settings;
11
+ export declare function saveSettings(settings: Settings): void;
12
+ export declare function setDefaultModel(settings: Settings, provider: ProviderId, modelId: string): Settings;
@@ -0,0 +1,29 @@
1
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
2
+ import { CONFIG_DIR, SETTINGS_PATH } from "./paths.js";
3
+ const DEFAULT_SETTINGS = {
4
+ defaultProvider: "free",
5
+ defaultModel: "meta-llama/llama-3.3-70b-instruct:free",
6
+ thinkingLevel: "off",
7
+ theme: "dark",
8
+ };
9
+ export function loadSettings() {
10
+ if (!existsSync(SETTINGS_PATH)) {
11
+ return { ...DEFAULT_SETTINGS };
12
+ }
13
+ try {
14
+ const raw = readFileSync(SETTINGS_PATH, "utf-8");
15
+ return { ...DEFAULT_SETTINGS, ...JSON.parse(raw) };
16
+ }
17
+ catch {
18
+ return { ...DEFAULT_SETTINGS };
19
+ }
20
+ }
21
+ export function saveSettings(settings) {
22
+ mkdirSync(CONFIG_DIR, { recursive: true });
23
+ writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2), "utf-8");
24
+ }
25
+ export function setDefaultModel(settings, provider, modelId) {
26
+ const updated = { ...settings, defaultProvider: provider, defaultModel: modelId };
27
+ saveSettings(updated);
28
+ return updated;
29
+ }
package/dist/main.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function main(): Promise<void>;