@aion0/forge 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 (80) hide show
  1. package/CLAUDE.md +4 -0
  2. package/README.md +264 -0
  3. package/app/api/auth/[...nextauth]/route.ts +3 -0
  4. package/app/api/claude/[id]/route.ts +31 -0
  5. package/app/api/claude/[id]/stream/route.ts +63 -0
  6. package/app/api/claude/route.ts +28 -0
  7. package/app/api/claude-sessions/[projectName]/live/route.ts +72 -0
  8. package/app/api/claude-sessions/[projectName]/route.ts +37 -0
  9. package/app/api/claude-sessions/sync/route.ts +17 -0
  10. package/app/api/flows/route.ts +6 -0
  11. package/app/api/flows/run/route.ts +19 -0
  12. package/app/api/notify/test/route.ts +33 -0
  13. package/app/api/projects/route.ts +7 -0
  14. package/app/api/sessions/[id]/chat/route.ts +64 -0
  15. package/app/api/sessions/[id]/messages/route.ts +9 -0
  16. package/app/api/sessions/[id]/route.ts +17 -0
  17. package/app/api/sessions/route.ts +20 -0
  18. package/app/api/settings/route.ts +15 -0
  19. package/app/api/status/route.ts +12 -0
  20. package/app/api/tasks/[id]/route.ts +36 -0
  21. package/app/api/tasks/[id]/stream/route.ts +77 -0
  22. package/app/api/tasks/link/route.ts +37 -0
  23. package/app/api/tasks/route.ts +43 -0
  24. package/app/api/tasks/session/route.ts +14 -0
  25. package/app/api/templates/route.ts +6 -0
  26. package/app/api/tunnel/route.ts +20 -0
  27. package/app/api/watchers/route.ts +33 -0
  28. package/app/globals.css +26 -0
  29. package/app/icon.svg +26 -0
  30. package/app/layout.tsx +17 -0
  31. package/app/login/page.tsx +61 -0
  32. package/app/page.tsx +9 -0
  33. package/cli/mw.ts +377 -0
  34. package/components/ChatPanel.tsx +191 -0
  35. package/components/ClaudeTerminal.tsx +267 -0
  36. package/components/Dashboard.tsx +270 -0
  37. package/components/MarkdownContent.tsx +57 -0
  38. package/components/NewSessionModal.tsx +93 -0
  39. package/components/NewTaskModal.tsx +456 -0
  40. package/components/ProjectList.tsx +108 -0
  41. package/components/SessionList.tsx +74 -0
  42. package/components/SessionView.tsx +655 -0
  43. package/components/SettingsModal.tsx +366 -0
  44. package/components/StatusBar.tsx +99 -0
  45. package/components/TaskBoard.tsx +110 -0
  46. package/components/TaskDetail.tsx +351 -0
  47. package/components/TunnelToggle.tsx +163 -0
  48. package/components/WebTerminal.tsx +1069 -0
  49. package/docs/LOCAL-DEPLOY.md +144 -0
  50. package/docs/roadmap-multi-agent-workflow.md +330 -0
  51. package/instrumentation.ts +14 -0
  52. package/lib/auth.ts +47 -0
  53. package/lib/claude-process.ts +352 -0
  54. package/lib/claude-sessions.ts +267 -0
  55. package/lib/cloudflared.ts +218 -0
  56. package/lib/flows.ts +86 -0
  57. package/lib/init.ts +82 -0
  58. package/lib/notify.ts +75 -0
  59. package/lib/password.ts +77 -0
  60. package/lib/projects.ts +86 -0
  61. package/lib/session-manager.ts +156 -0
  62. package/lib/session-watcher.ts +345 -0
  63. package/lib/settings.ts +44 -0
  64. package/lib/task-manager.ts +668 -0
  65. package/lib/telegram-bot.ts +912 -0
  66. package/lib/terminal-server.ts +70 -0
  67. package/lib/terminal-standalone.ts +363 -0
  68. package/middleware.ts +33 -0
  69. package/next-env.d.ts +6 -0
  70. package/next.config.ts +16 -0
  71. package/package.json +66 -0
  72. package/postcss.config.mjs +7 -0
  73. package/src/config/index.ts +119 -0
  74. package/src/core/db/database.ts +133 -0
  75. package/src/core/memory/strategy.ts +32 -0
  76. package/src/core/providers/chat.ts +65 -0
  77. package/src/core/providers/registry.ts +60 -0
  78. package/src/core/session/manager.ts +190 -0
  79. package/src/types/index.ts +128 -0
  80. package/tsconfig.json +41 -0
@@ -0,0 +1,133 @@
1
+ import Database from 'better-sqlite3';
2
+ import { existsSync, mkdirSync } from 'node:fs';
3
+ import { dirname } from 'node:path';
4
+
5
+ let db: Database.Database | null = null;
6
+
7
+ export function getDb(dbPath: string): Database.Database {
8
+ if (db) return db;
9
+
10
+ const dir = dirname(dbPath);
11
+ if (!existsSync(dir)) {
12
+ mkdirSync(dir, { recursive: true });
13
+ }
14
+
15
+ db = new Database(dbPath);
16
+ db.pragma('journal_mode = WAL');
17
+ db.pragma('foreign_keys = ON');
18
+ initSchema(db);
19
+ return db;
20
+ }
21
+
22
+ function initSchema(db: Database.Database) {
23
+ // Migrations for existing tables
24
+ try { db.exec('ALTER TABLE tasks ADD COLUMN scheduled_at TEXT'); } catch {}
25
+ try { db.exec('ALTER TABLE tasks ADD COLUMN mode TEXT NOT NULL DEFAULT \'prompt\''); } catch {}
26
+ try { db.exec('ALTER TABLE tasks ADD COLUMN watch_config TEXT'); } catch {}
27
+
28
+ db.exec(`
29
+ CREATE TABLE IF NOT EXISTS sessions (
30
+ id TEXT PRIMARY KEY,
31
+ name TEXT NOT NULL,
32
+ template_id TEXT NOT NULL,
33
+ provider TEXT NOT NULL,
34
+ model TEXT NOT NULL,
35
+ status TEXT NOT NULL DEFAULT 'idle',
36
+ memory_config TEXT NOT NULL,
37
+ system_prompt TEXT NOT NULL DEFAULT '',
38
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
39
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
40
+ );
41
+
42
+ CREATE TABLE IF NOT EXISTS messages (
43
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
44
+ session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
45
+ role TEXT NOT NULL,
46
+ content TEXT NOT NULL,
47
+ provider TEXT NOT NULL,
48
+ model TEXT NOT NULL,
49
+ token_count INTEGER DEFAULT 0,
50
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
51
+ );
52
+
53
+ CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_id, created_at);
54
+
55
+ CREATE TABLE IF NOT EXISTS usage (
56
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
57
+ provider TEXT NOT NULL,
58
+ model TEXT NOT NULL,
59
+ session_id TEXT REFERENCES sessions(id),
60
+ input_tokens INTEGER NOT NULL DEFAULT 0,
61
+ output_tokens INTEGER NOT NULL DEFAULT 0,
62
+ cost REAL NOT NULL DEFAULT 0,
63
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
64
+ );
65
+
66
+ CREATE INDEX IF NOT EXISTS idx_usage_provider ON usage(provider, created_at);
67
+
68
+ CREATE TABLE IF NOT EXISTS tasks (
69
+ id TEXT PRIMARY KEY,
70
+ project_name TEXT NOT NULL,
71
+ project_path TEXT NOT NULL,
72
+ prompt TEXT NOT NULL,
73
+ status TEXT NOT NULL DEFAULT 'queued',
74
+ priority INTEGER NOT NULL DEFAULT 0,
75
+ conversation_id TEXT,
76
+ log TEXT NOT NULL DEFAULT '[]',
77
+ result_summary TEXT,
78
+ git_diff TEXT,
79
+ git_branch TEXT,
80
+ cost_usd REAL,
81
+ error TEXT,
82
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
83
+ started_at TEXT,
84
+ completed_at TEXT,
85
+ scheduled_at TEXT,
86
+ mode TEXT NOT NULL DEFAULT 'prompt',
87
+ watch_config TEXT
88
+ );
89
+
90
+ CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status, created_at);
91
+
92
+ -- Cached Claude CLI sessions for tree view
93
+ CREATE TABLE IF NOT EXISTS cached_sessions (
94
+ project_name TEXT NOT NULL,
95
+ session_id TEXT NOT NULL,
96
+ summary TEXT,
97
+ first_prompt TEXT,
98
+ message_count INTEGER DEFAULT 0,
99
+ entry_count INTEGER DEFAULT 0,
100
+ created TEXT,
101
+ modified TEXT,
102
+ git_branch TEXT,
103
+ file_size INTEGER DEFAULT 0,
104
+ last_synced TEXT NOT NULL DEFAULT (datetime('now')),
105
+ PRIMARY KEY (project_name, session_id)
106
+ );
107
+
108
+ CREATE INDEX IF NOT EXISTS idx_cached_sessions_project ON cached_sessions(project_name, modified);
109
+
110
+ -- Session watchers — monitor sessions and notify via Telegram
111
+ CREATE TABLE IF NOT EXISTS session_watchers (
112
+ id TEXT PRIMARY KEY,
113
+ project_name TEXT NOT NULL,
114
+ session_id TEXT,
115
+ label TEXT,
116
+ check_interval INTEGER NOT NULL DEFAULT 60,
117
+ last_entry_count INTEGER DEFAULT 0,
118
+ last_checked TEXT,
119
+ notify_on_change INTEGER NOT NULL DEFAULT 1,
120
+ notify_on_idle INTEGER NOT NULL DEFAULT 1,
121
+ idle_threshold INTEGER NOT NULL DEFAULT 300,
122
+ active INTEGER NOT NULL DEFAULT 1,
123
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
124
+ );
125
+ `);
126
+ }
127
+
128
+ export function closeDb() {
129
+ if (db) {
130
+ db.close();
131
+ db = null;
132
+ }
133
+ }
@@ -0,0 +1,32 @@
1
+ import type { Message, MemoryConfig } from '@/src/types';
2
+
3
+ /**
4
+ * Apply memory strategy to filter/transform messages before sending to AI.
5
+ */
6
+ export function getMemoryMessages(messages: Message[], config: MemoryConfig): Message[] {
7
+ switch (config.strategy) {
8
+ case 'none':
9
+ // Only return the last user message
10
+ return messages.length > 0 ? [messages[messages.length - 1]] : [];
11
+
12
+ case 'sliding_window': {
13
+ const windowSize = config.windowSize || 20;
14
+ return messages.slice(-windowSize);
15
+ }
16
+
17
+ case 'full':
18
+ return messages;
19
+
20
+ case 'full_with_summary':
21
+ // TODO: Implement compression — for now, same as full
22
+ // Future: compress older messages into a summary using summaryModel
23
+ return messages;
24
+
25
+ case 'external':
26
+ // Only return the last user message; full history is in Obsidian
27
+ return messages.length > 0 ? [messages[messages.length - 1]] : [];
28
+
29
+ default:
30
+ return messages;
31
+ }
32
+ }
@@ -0,0 +1,65 @@
1
+ import { streamText, generateText, type ModelMessage } from 'ai';
2
+ import { getModel } from '@/src/core/providers/registry';
3
+ import type { ProviderName } from '@/src/types';
4
+
5
+ export interface ChatOptions {
6
+ provider: ProviderName;
7
+ model?: string;
8
+ systemPrompt?: string;
9
+ messages: ModelMessage[];
10
+ onToken?: (token: string) => void;
11
+ }
12
+
13
+ export interface ChatResult {
14
+ content: string;
15
+ inputTokens: number;
16
+ outputTokens: number;
17
+ model: string;
18
+ provider: ProviderName;
19
+ }
20
+
21
+ export async function chat(options: ChatOptions): Promise<ChatResult> {
22
+ const model = getModel(options.provider, options.model);
23
+ const modelId = options.model || (model as any).modelId || options.model || 'unknown';
24
+
25
+ const result = await generateText({
26
+ model,
27
+ system: options.systemPrompt,
28
+ messages: options.messages,
29
+ });
30
+
31
+ return {
32
+ content: result.text,
33
+ inputTokens: result.usage?.inputTokens ?? 0,
34
+ outputTokens: result.usage?.outputTokens ?? 0,
35
+ model: modelId,
36
+ provider: options.provider,
37
+ };
38
+ }
39
+
40
+ export async function chatStream(options: ChatOptions): Promise<ChatResult> {
41
+ const model = getModel(options.provider, options.model);
42
+ const modelId = options.model || (model as any).modelId || options.model || 'unknown';
43
+
44
+ const result = streamText({
45
+ model,
46
+ system: options.systemPrompt,
47
+ messages: options.messages,
48
+ });
49
+
50
+ let content = '';
51
+ for await (const chunk of result.textStream) {
52
+ content += chunk;
53
+ options.onToken?.(chunk);
54
+ }
55
+
56
+ const usage = await result.usage;
57
+
58
+ return {
59
+ content,
60
+ inputTokens: usage?.inputTokens ?? 0,
61
+ outputTokens: usage?.outputTokens ?? 0,
62
+ model: modelId,
63
+ provider: options.provider,
64
+ };
65
+ }
@@ -0,0 +1,60 @@
1
+ import { createAnthropic } from '@ai-sdk/anthropic';
2
+ import { createGoogleGenerativeAI } from '@ai-sdk/google';
3
+ import { createOpenAI } from '@ai-sdk/openai';
4
+ import type { LanguageModel } from 'ai';
5
+ import { loadConfig, getProviderApiKey } from '@/src/config';
6
+ import type { ProviderName } from '@/src/types';
7
+
8
+ const providerInstances = new Map<string, LanguageModel>();
9
+
10
+ export function getModel(provider: ProviderName, model?: string): LanguageModel {
11
+ const config = loadConfig();
12
+ const providerConfig = config.providers[provider];
13
+ const modelId = model || providerConfig.defaultModel;
14
+ const cacheKey = `${provider}:${modelId}`;
15
+
16
+ const cached = providerInstances.get(cacheKey);
17
+ if (cached) return cached;
18
+
19
+ const apiKey = providerConfig.apiKey || getProviderApiKey(provider);
20
+ const instance = createModel(provider, modelId, apiKey);
21
+ providerInstances.set(cacheKey, instance);
22
+ return instance;
23
+ }
24
+
25
+ function createModel(provider: ProviderName, modelId: string, apiKey?: string): LanguageModel {
26
+ switch (provider) {
27
+ case 'anthropic': {
28
+ const anthropic = createAnthropic({ apiKey });
29
+ return anthropic(modelId);
30
+ }
31
+ case 'google': {
32
+ const google = createGoogleGenerativeAI({ apiKey });
33
+ return google(modelId);
34
+ }
35
+ case 'openai': {
36
+ const openai = createOpenAI({ apiKey });
37
+ return openai(modelId);
38
+ }
39
+ case 'grok': {
40
+ // Grok uses OpenAI-compatible API
41
+ const grok = createOpenAI({
42
+ apiKey,
43
+ baseURL: 'https://api.x.ai/v1',
44
+ });
45
+ return grok(modelId);
46
+ }
47
+ default:
48
+ throw new Error(`Unknown provider: ${provider}`);
49
+ }
50
+ }
51
+
52
+ export function listAvailableProviders(): { name: ProviderName; displayName: string; hasKey: boolean; enabled: boolean }[] {
53
+ const config = loadConfig();
54
+ return Object.values(config.providers).map(p => ({
55
+ name: p.name,
56
+ displayName: p.displayName,
57
+ hasKey: !!(p.apiKey || getProviderApiKey(p.name)),
58
+ enabled: p.enabled,
59
+ }));
60
+ }
@@ -0,0 +1,190 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import type { ModelMessage } from 'ai';
3
+ import { getDb } from '@/src/core/db/database';
4
+ import { getDbPath, loadTemplate } from '@/src/config';
5
+ import { chatStream, type ChatResult } from '@/src/core/providers/chat';
6
+ import { getMemoryMessages } from '@/src/core/memory/strategy';
7
+ import type { Session, SessionStatus, Message, ProviderName, MemoryConfig } from '@/src/types';
8
+
9
+ export class SessionManager {
10
+ private db;
11
+
12
+ constructor() {
13
+ this.db = getDb(getDbPath());
14
+ }
15
+
16
+ create(opts: { name: string; templateId: string; provider?: ProviderName; model?: string }): Session {
17
+ const template = loadTemplate(opts.templateId);
18
+ if (!template) throw new Error(`Template not found: ${opts.templateId}`);
19
+
20
+ const id = randomUUID().slice(0, 8);
21
+ const provider = opts.provider || template.provider;
22
+ const model = opts.model || template.model || '';
23
+ const memoryConfig = JSON.stringify(template.memory);
24
+
25
+ this.db.prepare(`
26
+ INSERT INTO sessions (id, name, template_id, provider, model, status, memory_config, system_prompt)
27
+ VALUES (?, ?, ?, ?, ?, 'idle', ?, ?)
28
+ `).run(id, opts.name, opts.templateId, provider, model, memoryConfig, template.systemPrompt);
29
+
30
+ return this.get(id)!;
31
+ }
32
+
33
+ get(id: string): Session | null {
34
+ const row = this.db.prepare(`
35
+ SELECT s.*, COUNT(m.id) as message_count,
36
+ (SELECT content FROM messages WHERE session_id = s.id ORDER BY created_at DESC LIMIT 1) as last_message
37
+ FROM sessions s
38
+ LEFT JOIN messages m ON m.session_id = s.id
39
+ WHERE s.id = ?
40
+ GROUP BY s.id
41
+ `).get(id) as any;
42
+
43
+ if (!row) return null;
44
+ return this.rowToSession(row);
45
+ }
46
+
47
+ getByName(name: string): Session | null {
48
+ const row = this.db.prepare(`
49
+ SELECT s.*, COUNT(m.id) as message_count,
50
+ (SELECT content FROM messages WHERE session_id = s.id ORDER BY created_at DESC LIMIT 1) as last_message
51
+ FROM sessions s
52
+ LEFT JOIN messages m ON m.session_id = s.id
53
+ WHERE s.name = ?
54
+ GROUP BY s.id
55
+ `).get(name) as any;
56
+
57
+ if (!row) return null;
58
+ return this.rowToSession(row);
59
+ }
60
+
61
+ list(status?: SessionStatus): Session[] {
62
+ let query = `
63
+ SELECT s.*, COUNT(m.id) as message_count,
64
+ (SELECT content FROM messages WHERE session_id = s.id ORDER BY created_at DESC LIMIT 1) as last_message
65
+ FROM sessions s
66
+ LEFT JOIN messages m ON m.session_id = s.id
67
+ `;
68
+ const params: string[] = [];
69
+ if (status) {
70
+ query += ' WHERE s.status = ?';
71
+ params.push(status);
72
+ }
73
+ query += ' GROUP BY s.id ORDER BY s.updated_at DESC';
74
+
75
+ const rows = this.db.prepare(query).all(...params) as any[];
76
+ return rows.map(r => this.rowToSession(r));
77
+ }
78
+
79
+ updateStatus(id: string, status: SessionStatus) {
80
+ this.db.prepare(`UPDATE sessions SET status = ?, updated_at = datetime('now') WHERE id = ?`).run(status, id);
81
+ }
82
+
83
+ async sendMessage(
84
+ sessionId: string,
85
+ userMessage: string,
86
+ onToken?: (token: string) => void
87
+ ): Promise<ChatResult> {
88
+ const session = this.get(sessionId);
89
+ if (!session) throw new Error(`Session not found: ${sessionId}`);
90
+
91
+ // Save user message
92
+ this.addMessage(sessionId, 'user', userMessage, session.provider, session.model);
93
+
94
+ // Get messages based on memory strategy
95
+ const allMessages = this.getMessages(sessionId);
96
+ const memoryMessages = getMemoryMessages(allMessages, session.memory);
97
+
98
+ // Convert to ModelMessage format
99
+ const coreMessages: ModelMessage[] = memoryMessages.map(m => ({
100
+ role: m.role as 'user' | 'assistant',
101
+ content: m.content,
102
+ }));
103
+
104
+ this.updateStatus(sessionId, 'running');
105
+
106
+ try {
107
+ const result = await chatStream({
108
+ provider: session.provider,
109
+ model: session.model || undefined,
110
+ systemPrompt: session.systemPrompt,
111
+ messages: coreMessages,
112
+ onToken,
113
+ });
114
+
115
+ // Save assistant message
116
+ this.addMessage(sessionId, 'assistant', result.content, result.provider, result.model);
117
+
118
+ // Record usage
119
+ this.recordUsage(sessionId, result);
120
+
121
+ this.updateStatus(sessionId, 'idle');
122
+ return result;
123
+ } catch (err) {
124
+ this.updateStatus(sessionId, 'error');
125
+ throw err;
126
+ }
127
+ }
128
+
129
+ addMessage(sessionId: string, role: string, content: string, provider: string, model: string) {
130
+ this.db.prepare(`
131
+ INSERT INTO messages (session_id, role, content, provider, model)
132
+ VALUES (?, ?, ?, ?, ?)
133
+ `).run(sessionId, role, content, provider, model);
134
+
135
+ this.db.prepare(`UPDATE sessions SET updated_at = datetime('now') WHERE id = ?`).run(sessionId);
136
+ }
137
+
138
+ getMessages(sessionId: string, limit?: number): Message[] {
139
+ let query = 'SELECT * FROM messages WHERE session_id = ? ORDER BY created_at ASC';
140
+ const params: (string | number)[] = [sessionId];
141
+ if (limit) {
142
+ // Get last N messages
143
+ query = `SELECT * FROM (
144
+ SELECT * FROM messages WHERE session_id = ? ORDER BY created_at DESC LIMIT ?
145
+ ) ORDER BY created_at ASC`;
146
+ params.push(limit);
147
+ }
148
+ return this.db.prepare(query).all(...params) as Message[];
149
+ }
150
+
151
+ delete(id: string) {
152
+ this.db.prepare('DELETE FROM sessions WHERE id = ?').run(id);
153
+ }
154
+
155
+ getUsageSummary(): { provider: string; totalInput: number; totalOutput: number; totalCost: number }[] {
156
+ return this.db.prepare(`
157
+ SELECT provider,
158
+ SUM(input_tokens) as totalInput,
159
+ SUM(output_tokens) as totalOutput,
160
+ SUM(cost) as totalCost
161
+ FROM usage
162
+ WHERE created_at >= date('now', '-30 days')
163
+ GROUP BY provider
164
+ `).all() as any[];
165
+ }
166
+
167
+ private recordUsage(sessionId: string, result: ChatResult) {
168
+ this.db.prepare(`
169
+ INSERT INTO usage (provider, model, session_id, input_tokens, output_tokens, cost)
170
+ VALUES (?, ?, ?, ?, ?, ?)
171
+ `).run(result.provider, result.model, sessionId, result.inputTokens, result.outputTokens, 0);
172
+ }
173
+
174
+ private rowToSession(row: any): Session {
175
+ return {
176
+ id: row.id,
177
+ name: row.name,
178
+ templateId: row.template_id,
179
+ provider: row.provider,
180
+ model: row.model,
181
+ status: row.status,
182
+ memory: JSON.parse(row.memory_config),
183
+ systemPrompt: row.system_prompt,
184
+ messageCount: row.message_count || 0,
185
+ createdAt: row.created_at,
186
+ updatedAt: row.updated_at,
187
+ lastMessage: row.last_message || undefined,
188
+ };
189
+ }
190
+ }
@@ -0,0 +1,128 @@
1
+ export type MemoryStrategy = 'none' | 'sliding_window' | 'full' | 'full_with_summary' | 'external';
2
+
3
+ export type SessionStatus = 'running' | 'idle' | 'paused' | 'archived' | 'error';
4
+
5
+ export type ProviderName = 'anthropic' | 'google' | 'openai' | 'grok';
6
+
7
+ export interface ProviderConfig {
8
+ name: ProviderName;
9
+ displayName: string;
10
+ apiKey?: string;
11
+ defaultModel: string;
12
+ models: string[];
13
+ enabled: boolean;
14
+ }
15
+
16
+ export interface MemoryConfig {
17
+ strategy: MemoryStrategy;
18
+ windowSize?: number; // for sliding_window
19
+ compressAfter?: number; // for full — compress after N messages
20
+ summaryModel?: string; // model to use for summarization
21
+ }
22
+
23
+ export interface SessionTemplate {
24
+ id: string;
25
+ name: string;
26
+ description: string;
27
+ provider: ProviderName;
28
+ model?: string;
29
+ fallbackProvider?: ProviderName;
30
+ memory: MemoryConfig;
31
+ systemPrompt: string;
32
+ context?: {
33
+ files?: string[];
34
+ };
35
+ commands?: Record<string, { description: string; action: string; prompt?: string }>;
36
+ ui?: {
37
+ icon?: string;
38
+ color?: string;
39
+ pinned?: boolean;
40
+ };
41
+ }
42
+
43
+ export interface Message {
44
+ id: number;
45
+ sessionId: string;
46
+ role: 'user' | 'assistant' | 'system';
47
+ content: string;
48
+ provider: ProviderName;
49
+ model: string;
50
+ tokenCount?: number;
51
+ createdAt: string;
52
+ }
53
+
54
+ export interface Session {
55
+ id: string;
56
+ name: string;
57
+ templateId: string;
58
+ provider: ProviderName;
59
+ model: string;
60
+ status: SessionStatus;
61
+ memory: MemoryConfig;
62
+ systemPrompt: string;
63
+ messageCount: number;
64
+ createdAt: string;
65
+ updatedAt: string;
66
+ lastMessage?: string;
67
+ }
68
+
69
+ export interface UsageRecord {
70
+ provider: ProviderName;
71
+ model: string;
72
+ inputTokens: number;
73
+ outputTokens: number;
74
+ cost: number;
75
+ date: string;
76
+ }
77
+
78
+ export type TaskStatus = 'queued' | 'running' | 'done' | 'failed' | 'cancelled';
79
+ export type TaskMode = 'prompt' | 'monitor';
80
+
81
+ export interface WatchConfig {
82
+ condition: 'change' | 'idle' | 'complete' | 'error' | 'keyword';
83
+ keyword?: string; // for 'keyword' condition
84
+ idleMinutes?: number; // for 'idle' condition (default 10)
85
+ action: 'notify' | 'message' | 'task';
86
+ actionPrompt?: string; // message to send or task prompt
87
+ actionProject?: string; // for 'task' action
88
+ repeat?: boolean; // keep watching after trigger (default false)
89
+ }
90
+
91
+ export interface Task {
92
+ id: string;
93
+ projectName: string;
94
+ projectPath: string;
95
+ prompt: string;
96
+ mode: TaskMode;
97
+ status: TaskStatus;
98
+ priority: number;
99
+ conversationId?: string;
100
+ watchConfig?: WatchConfig;
101
+ log: TaskLogEntry[];
102
+ resultSummary?: string;
103
+ gitDiff?: string;
104
+ gitBranch?: string;
105
+ costUSD?: number;
106
+ error?: string;
107
+ createdAt: string;
108
+ startedAt?: string;
109
+ completedAt?: string;
110
+ scheduledAt?: string;
111
+ }
112
+
113
+ export interface TaskLogEntry {
114
+ type: 'system' | 'assistant' | 'result';
115
+ subtype?: string;
116
+ content: string;
117
+ tool?: string;
118
+ timestamp: string;
119
+ }
120
+
121
+ export interface AppConfig {
122
+ dataDir: string;
123
+ providers: Record<ProviderName, ProviderConfig>;
124
+ server: {
125
+ host: string;
126
+ port: number;
127
+ };
128
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": [
5
+ "dom",
6
+ "dom.iterable",
7
+ "esnext"
8
+ ],
9
+ "allowJs": true,
10
+ "skipLibCheck": true,
11
+ "strict": true,
12
+ "noEmit": true,
13
+ "esModuleInterop": true,
14
+ "module": "esnext",
15
+ "moduleResolution": "bundler",
16
+ "resolveJsonModule": true,
17
+ "isolatedModules": true,
18
+ "jsx": "react-jsx",
19
+ "incremental": true,
20
+ "plugins": [
21
+ {
22
+ "name": "next"
23
+ }
24
+ ],
25
+ "paths": {
26
+ "@/*": [
27
+ "./*"
28
+ ]
29
+ }
30
+ },
31
+ "include": [
32
+ "next-env.d.ts",
33
+ "**/*.ts",
34
+ "**/*.tsx",
35
+ ".next/types/**/*.ts",
36
+ ".next/dev/types/**/*.ts"
37
+ ],
38
+ "exclude": [
39
+ "node_modules"
40
+ ]
41
+ }