idea-manager 0.9.6 → 1.0.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.
- package/package.json +7 -3
- package/src/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/chat/route.ts +3 -2
- package/src/cli.ts +7 -12
- package/src/components/workspace/WorkspacePanel.tsx +18 -0
- package/src/lib/ai/agents.ts +102 -0
- package/src/lib/ai/client.ts +46 -50
- package/src/lib/db/queries/projects.ts +6 -4
- package/src/lib/db/schema.ts +3 -0
- package/src/lib/watcher.ts +2 -2
- package/src/types/index.ts +3 -0
package/package.json
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "idea-manager",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Turn free-form brainstorming into structured task trees with AI-generated prompts. Built-in MCP Server for autonomous AI agent execution. Local-first with SQLite, cross-PC sync via Git.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"brainstorm",
|
|
7
7
|
"ai",
|
|
8
8
|
"mcp",
|
|
9
9
|
"idea",
|
|
10
|
+
"task-manager",
|
|
11
|
+
"prompt",
|
|
10
12
|
"structuring",
|
|
11
|
-
"
|
|
13
|
+
"project-management",
|
|
14
|
+
"sqlite",
|
|
15
|
+
"local-first"
|
|
12
16
|
],
|
|
13
17
|
"author": "navskh",
|
|
14
18
|
"license": "MIT",
|
|
@@ -4,7 +4,7 @@ import { getTask } from '@/lib/db/queries/tasks';
|
|
|
4
4
|
import { getTaskPrompt } from '@/lib/db/queries/task-prompts';
|
|
5
5
|
import { getBrainstorm } from '@/lib/db/queries/brainstorms';
|
|
6
6
|
import { getProject } from '@/lib/db/queries/projects';
|
|
7
|
-
import {
|
|
7
|
+
import { runAgent } from '@/lib/ai/client';
|
|
8
8
|
|
|
9
9
|
export async function GET(
|
|
10
10
|
_request: NextRequest,
|
|
@@ -55,7 +55,8 @@ ${brainstorm?.content ? `\nBrainstorming context:\n${brainstorm.content.slice(0,
|
|
|
55
55
|
.join('\n');
|
|
56
56
|
|
|
57
57
|
try {
|
|
58
|
-
const
|
|
58
|
+
const agentType = project?.agent_type || 'claude';
|
|
59
|
+
const aiResponse = await runAgent(agentType, `${systemPrompt}\n\nConversation:\n${conversationText}`);
|
|
59
60
|
const trimmed = aiResponse.trim();
|
|
60
61
|
if (!trimmed) {
|
|
61
62
|
const fallbackMsg = addTaskConversation(taskId, 'assistant', '(AI 응답을 생성하지 못했습니다. 다시 시도해주세요.)');
|
package/src/cli.ts
CHANGED
|
@@ -22,7 +22,7 @@ try {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
async function openAsApp(url: string) {
|
|
25
|
-
const {
|
|
25
|
+
const { spawn: spawnChild } = await import('child_process');
|
|
26
26
|
const fs = await import('fs');
|
|
27
27
|
const platform = process.platform;
|
|
28
28
|
|
|
@@ -47,23 +47,18 @@ async function openAsApp(url: string) {
|
|
|
47
47
|
];
|
|
48
48
|
|
|
49
49
|
for (const browser of browsers) {
|
|
50
|
-
// On macOS, check binary exists before trying
|
|
51
50
|
if (platform === 'darwin' && !fs.existsSync(browser.bin)) continue;
|
|
52
51
|
|
|
53
52
|
try {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
stdio: 'ignore',
|
|
59
|
-
}, (err) => { if (err) reject(err); });
|
|
60
|
-
child.unref();
|
|
61
|
-
// Browser launched — resolve after brief delay
|
|
62
|
-
setTimeout(resolve, 500);
|
|
53
|
+
const child = spawnChild(browser.bin, browser.args, {
|
|
54
|
+
detached: true,
|
|
55
|
+
stdio: 'ignore',
|
|
56
|
+
shell: platform === 'win32',
|
|
63
57
|
});
|
|
58
|
+
child.unref();
|
|
64
59
|
return; // success
|
|
65
60
|
} catch {
|
|
66
|
-
continue;
|
|
61
|
+
continue;
|
|
67
62
|
}
|
|
68
63
|
}
|
|
69
64
|
|
|
@@ -19,6 +19,7 @@ interface IProject {
|
|
|
19
19
|
project_path: string | null;
|
|
20
20
|
ai_context: string;
|
|
21
21
|
watch_enabled: boolean;
|
|
22
|
+
agent_type: string;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
export default function WorkspacePanel({
|
|
@@ -397,6 +398,23 @@ export default function WorkspacePanel({
|
|
|
397
398
|
)}
|
|
398
399
|
</div>
|
|
399
400
|
<div className="flex items-center gap-2">
|
|
401
|
+
<select
|
|
402
|
+
value={project.agent_type || 'claude'}
|
|
403
|
+
onChange={async (e) => {
|
|
404
|
+
const res = await fetch(`/api/projects/${id}`, {
|
|
405
|
+
method: 'PUT',
|
|
406
|
+
headers: { 'Content-Type': 'application/json' },
|
|
407
|
+
body: JSON.stringify({ agent_type: e.target.value }),
|
|
408
|
+
});
|
|
409
|
+
if (res.ok) setProject(await res.json());
|
|
410
|
+
}}
|
|
411
|
+
className="px-2 py-1.5 text-xs bg-muted border border-border rounded-md text-foreground cursor-pointer hover:bg-card-hover transition-colors"
|
|
412
|
+
title="AI Agent"
|
|
413
|
+
>
|
|
414
|
+
<option value="claude">Claude</option>
|
|
415
|
+
<option value="gemini">Gemini</option>
|
|
416
|
+
<option value="codex">Codex</option>
|
|
417
|
+
</select>
|
|
400
418
|
<button onClick={handleToggleWatch}
|
|
401
419
|
className={`px-3 py-1.5 text-xs border rounded-md transition-colors flex items-center gap-1.5 ${
|
|
402
420
|
project.watch_enabled
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { AgentType } from '../../types';
|
|
2
|
+
|
|
3
|
+
export interface AgentConfig {
|
|
4
|
+
name: string;
|
|
5
|
+
binary: string;
|
|
6
|
+
buildArgs: (opts: { streaming: boolean }) => string[];
|
|
7
|
+
buildEnv: () => NodeJS.ProcessEnv;
|
|
8
|
+
parseStreamEvent: (parsed: Record<string, unknown>) => { text?: string; final?: string } | null;
|
|
9
|
+
cleanOutput?: (text: string) => string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const claudeConfig: AgentConfig = {
|
|
13
|
+
name: 'Claude',
|
|
14
|
+
binary: 'claude',
|
|
15
|
+
buildArgs: ({ streaming }) => [
|
|
16
|
+
'--dangerously-skip-permissions',
|
|
17
|
+
'--model', 'sonnet',
|
|
18
|
+
...(streaming
|
|
19
|
+
? ['--output-format', 'stream-json', '--verbose']
|
|
20
|
+
: ['--output-format', 'text']),
|
|
21
|
+
'--max-turns', '80',
|
|
22
|
+
'-p', '-',
|
|
23
|
+
],
|
|
24
|
+
buildEnv: () => {
|
|
25
|
+
const env = { ...process.env };
|
|
26
|
+
delete env.CLAUDECODE;
|
|
27
|
+
delete env.CLAUDE_CODE_ENTRYPOINT;
|
|
28
|
+
delete env.CLAUDE_CODE_MAX_OUTPUT_TOKENS;
|
|
29
|
+
for (const key of Object.keys(env)) {
|
|
30
|
+
if (key.startsWith('CLAUDE_CODE_') || key === 'ANTHROPIC_PARENT_SESSION') {
|
|
31
|
+
delete env[key];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return { ...env, FORCE_COLOR: '0' };
|
|
35
|
+
},
|
|
36
|
+
parseStreamEvent: (parsed) => {
|
|
37
|
+
if (parsed.type === 'content_block_delta' && (parsed.delta as Record<string, unknown>)?.text) {
|
|
38
|
+
return { text: (parsed.delta as Record<string, unknown>).text as string };
|
|
39
|
+
}
|
|
40
|
+
if (parsed.type === 'assistant' && (parsed.message as Record<string, unknown>)?.content) {
|
|
41
|
+
let t = '';
|
|
42
|
+
for (const b of (parsed.message as Record<string, unknown>).content as { type: string; text?: string }[]) {
|
|
43
|
+
if (b.type === 'text') t += b.text;
|
|
44
|
+
}
|
|
45
|
+
return { final: t };
|
|
46
|
+
}
|
|
47
|
+
if (parsed.type === 'result' && parsed.result) {
|
|
48
|
+
return { final: parsed.result as string };
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
},
|
|
52
|
+
cleanOutput: (text) => text.replace(/Error: Reached max turns \(\d+\)\s*/g, '').trim(),
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const geminiConfig: AgentConfig = {
|
|
56
|
+
name: 'Gemini',
|
|
57
|
+
binary: 'gemini',
|
|
58
|
+
buildArgs: ({ streaming }) => [
|
|
59
|
+
'--yolo',
|
|
60
|
+
...(streaming
|
|
61
|
+
? ['--output-format', 'stream-json']
|
|
62
|
+
: ['--output-format', 'json']),
|
|
63
|
+
'-p', '-',
|
|
64
|
+
],
|
|
65
|
+
buildEnv: () => ({ ...process.env, FORCE_COLOR: '0' }),
|
|
66
|
+
parseStreamEvent: (parsed) => {
|
|
67
|
+
if (parsed.type === 'content_block_delta' && (parsed.delta as Record<string, unknown>)?.text) {
|
|
68
|
+
return { text: (parsed.delta as Record<string, unknown>).text as string };
|
|
69
|
+
}
|
|
70
|
+
if (parsed.type === 'result') {
|
|
71
|
+
return { final: (parsed.response || parsed.text || parsed.result) as string };
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const codexConfig: AgentConfig = {
|
|
78
|
+
name: 'Codex',
|
|
79
|
+
binary: 'codex',
|
|
80
|
+
buildArgs: ({ streaming }) => [
|
|
81
|
+
'exec',
|
|
82
|
+
'--full-auto',
|
|
83
|
+
...(streaming ? ['--json'] : []),
|
|
84
|
+
'-',
|
|
85
|
+
],
|
|
86
|
+
buildEnv: () => ({ ...process.env, FORCE_COLOR: '0' }),
|
|
87
|
+
parseStreamEvent: (parsed) => {
|
|
88
|
+
if (parsed.type === 'item.completed' && (parsed.item as Record<string, unknown>)?.type === 'agent_message') {
|
|
89
|
+
return { final: (parsed.item as Record<string, unknown>).text as string };
|
|
90
|
+
}
|
|
91
|
+
if (parsed.type === 'item.updated' && (parsed.item as Record<string, unknown>)?.type === 'agent_message') {
|
|
92
|
+
return { text: (parsed.item as Record<string, unknown>).text as string };
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export const AGENTS: Record<AgentType, AgentConfig> = {
|
|
99
|
+
claude: claudeConfig,
|
|
100
|
+
gemini: geminiConfig,
|
|
101
|
+
codex: codexConfig,
|
|
102
|
+
};
|
package/src/lib/ai/client.ts
CHANGED
|
@@ -1,48 +1,41 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const DEFAULT_ARGS = ['--dangerously-skip-permissions'];
|
|
5
|
-
const MODEL = 'sonnet';
|
|
6
|
-
const MAX_TURNS = 80;
|
|
2
|
+
import { AGENTS } from './agents';
|
|
3
|
+
import type { AgentType } from '../../types';
|
|
7
4
|
|
|
8
5
|
export type OnTextChunk = (text: string) => void;
|
|
9
6
|
export type OnRawEvent = (event: Record<string, unknown>) => void;
|
|
10
7
|
|
|
11
|
-
export interface
|
|
8
|
+
export interface RunAgentOptions {
|
|
12
9
|
cwd?: string;
|
|
13
10
|
timeoutMs?: number;
|
|
14
11
|
}
|
|
15
12
|
|
|
16
13
|
/**
|
|
17
|
-
* Spawn
|
|
14
|
+
* Spawn an AI CLI agent and collect the result text.
|
|
18
15
|
* Optional onText callback receives streaming text chunks as they arrive.
|
|
19
16
|
*/
|
|
20
|
-
export function
|
|
17
|
+
export function runAgent(
|
|
18
|
+
agentType: AgentType,
|
|
19
|
+
prompt: string,
|
|
20
|
+
onText?: OnTextChunk,
|
|
21
|
+
onRawEvent?: OnRawEvent,
|
|
22
|
+
options?: RunAgentOptions,
|
|
23
|
+
): Promise<string> {
|
|
24
|
+
const config = AGENTS[agentType];
|
|
25
|
+
if (!config) {
|
|
26
|
+
return Promise.reject(new Error(`Unknown agent type: ${agentType}`));
|
|
27
|
+
}
|
|
28
|
+
|
|
21
29
|
return new Promise((resolve, reject) => {
|
|
22
30
|
const useStreamJson = !!(onText || onRawEvent);
|
|
23
|
-
const args =
|
|
24
|
-
|
|
25
|
-
'--model', MODEL,
|
|
26
|
-
...(useStreamJson ? ['--output-format', 'stream-json', '--verbose'] : ['--output-format', 'text']),
|
|
27
|
-
'--max-turns', String(MAX_TURNS),
|
|
28
|
-
'-p', '-', // read prompt from stdin
|
|
29
|
-
];
|
|
30
|
-
|
|
31
|
-
// Strip Claude Code session env vars to avoid nested session detection
|
|
32
|
-
const cleanEnv = { ...process.env };
|
|
33
|
-
delete cleanEnv.CLAUDECODE;
|
|
34
|
-
delete cleanEnv.CLAUDE_CODE_ENTRYPOINT;
|
|
35
|
-
delete cleanEnv.CLAUDE_CODE_MAX_OUTPUT_TOKENS;
|
|
36
|
-
for (const key of Object.keys(cleanEnv)) {
|
|
37
|
-
if (key.startsWith('CLAUDE_CODE_') || key === 'ANTHROPIC_PARENT_SESSION') {
|
|
38
|
-
delete cleanEnv[key];
|
|
39
|
-
}
|
|
40
|
-
}
|
|
31
|
+
const args = config.buildArgs({ streaming: useStreamJson });
|
|
32
|
+
const env = config.buildEnv();
|
|
41
33
|
|
|
42
|
-
const proc = spawn(
|
|
34
|
+
const proc = spawn(config.binary, args, {
|
|
43
35
|
cwd: options?.cwd || process.cwd(),
|
|
44
36
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
45
|
-
|
|
37
|
+
shell: process.platform === 'win32',
|
|
38
|
+
env,
|
|
46
39
|
});
|
|
47
40
|
|
|
48
41
|
// Timeout handling
|
|
@@ -62,6 +55,7 @@ export function runClaude(prompt: string, onText?: OnTextChunk, onRawEvent?: OnR
|
|
|
62
55
|
let buffer = '';
|
|
63
56
|
let resultText = '';
|
|
64
57
|
let stderrText = '';
|
|
58
|
+
let lastEmittedLength = 0;
|
|
65
59
|
|
|
66
60
|
if (useStreamJson) {
|
|
67
61
|
// stream-json mode: parse NDJSON events
|
|
@@ -77,23 +71,21 @@ export function runClaude(prompt: string, onText?: OnTextChunk, onRawEvent?: OnR
|
|
|
77
71
|
const parsed = JSON.parse(trimmed);
|
|
78
72
|
onRawEvent?.(parsed);
|
|
79
73
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
74
|
+
const event = config.parseStreamEvent(parsed);
|
|
75
|
+
if (event) {
|
|
76
|
+
if (event.final) {
|
|
77
|
+
// Some agents emit cumulative text, emit only new portion
|
|
78
|
+
if (event.final.length > lastEmittedLength) {
|
|
79
|
+
const newPart = event.final.slice(lastEmittedLength);
|
|
80
|
+
onText?.(newPart);
|
|
81
|
+
lastEmittedLength = event.final.length;
|
|
82
|
+
}
|
|
83
|
+
resultText = event.final;
|
|
84
|
+
} else if (event.text) {
|
|
85
|
+
resultText += event.text;
|
|
86
|
+
lastEmittedLength = resultText.length;
|
|
87
|
+
onText?.(event.text);
|
|
87
88
|
}
|
|
88
|
-
if (fullText.length > resultText.length) {
|
|
89
|
-
onText?.(fullText.slice(resultText.length));
|
|
90
|
-
}
|
|
91
|
-
resultText = fullText;
|
|
92
|
-
} else if (parsed.type === 'result' && parsed.result) {
|
|
93
|
-
if (parsed.result.length > resultText.length) {
|
|
94
|
-
onText?.(parsed.result.slice(resultText.length));
|
|
95
|
-
}
|
|
96
|
-
resultText = parsed.result;
|
|
97
89
|
}
|
|
98
90
|
} catch { /* ignore non-JSON */ }
|
|
99
91
|
}
|
|
@@ -110,25 +102,29 @@ export function runClaude(prompt: string, onText?: OnTextChunk, onRawEvent?: OnR
|
|
|
110
102
|
});
|
|
111
103
|
|
|
112
104
|
proc.on('error', (err) => {
|
|
113
|
-
reject(new Error(
|
|
105
|
+
reject(new Error(`${config.name} CLI error: ${err.message}`));
|
|
114
106
|
});
|
|
115
107
|
|
|
116
108
|
proc.on('exit', (code, signal) => {
|
|
117
109
|
if (timeoutTimer) clearTimeout(timeoutTimer);
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
resultText = resultText.replace(/Error: Reached max turns \(\d+\)\s*/g, '').trim();
|
|
110
|
+
if (!useStreamJson && config.cleanOutput) {
|
|
111
|
+
resultText = config.cleanOutput(resultText);
|
|
121
112
|
}
|
|
122
113
|
if (timedOut) {
|
|
123
|
-
reject(new Error(
|
|
114
|
+
reject(new Error(`${config.name} CLI timed out after ${Math.round((options?.timeoutMs || 0) / 1000)}s`));
|
|
124
115
|
return;
|
|
125
116
|
}
|
|
126
117
|
if (code !== 0 && !resultText) {
|
|
127
118
|
const detail = stderrText.slice(0, 500) || (signal ? `killed by signal ${signal}` : 'no output');
|
|
128
|
-
reject(new Error(
|
|
119
|
+
reject(new Error(`${config.name} CLI exited with code ${code}: ${detail}`));
|
|
129
120
|
return;
|
|
130
121
|
}
|
|
131
122
|
resolve(resultText);
|
|
132
123
|
});
|
|
133
124
|
});
|
|
134
125
|
}
|
|
126
|
+
|
|
127
|
+
// Backward compatibility
|
|
128
|
+
export function runClaude(prompt: string, onText?: OnTextChunk, onRawEvent?: OnRawEvent, options?: RunAgentOptions): Promise<string> {
|
|
129
|
+
return runAgent('claude', prompt, onText, onRawEvent, options);
|
|
130
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getDb } from '../index';
|
|
2
2
|
import { generateId } from '../../utils/id';
|
|
3
|
-
import type { IProject } from '../../../types';
|
|
3
|
+
import type { IProject, AgentType } from '../../../types';
|
|
4
4
|
|
|
5
5
|
interface ProjectRow {
|
|
6
6
|
id: string;
|
|
@@ -9,12 +9,13 @@ interface ProjectRow {
|
|
|
9
9
|
project_path: string | null;
|
|
10
10
|
ai_context: string;
|
|
11
11
|
watch_enabled: number;
|
|
12
|
+
agent_type: string;
|
|
12
13
|
created_at: string;
|
|
13
14
|
updated_at: string;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
function rowToProject(row: ProjectRow): IProject {
|
|
17
|
-
return { ...row, watch_enabled: row.watch_enabled === 1 };
|
|
18
|
+
return { ...row, watch_enabled: row.watch_enabled === 1, agent_type: (row.agent_type || 'claude') as AgentType };
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
export function listProjects(): IProject[] {
|
|
@@ -47,7 +48,7 @@ export function createProject(name: string, description: string = '', projectPat
|
|
|
47
48
|
return getProject(id)!;
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
export function updateProject(id: string, data: { name?: string; description?: string; project_path?: string | null; ai_context?: string; watch_enabled?: boolean }): IProject | undefined {
|
|
51
|
+
export function updateProject(id: string, data: { name?: string; description?: string; project_path?: string | null; ai_context?: string; watch_enabled?: boolean; agent_type?: AgentType }): IProject | undefined {
|
|
51
52
|
const db = getDb();
|
|
52
53
|
const project = getProject(id);
|
|
53
54
|
if (!project) return undefined;
|
|
@@ -55,13 +56,14 @@ export function updateProject(id: string, data: { name?: string; description?: s
|
|
|
55
56
|
const now = new Date().toISOString();
|
|
56
57
|
|
|
57
58
|
db.prepare(
|
|
58
|
-
'UPDATE projects SET name = ?, description = ?, project_path = ?, ai_context = ?, watch_enabled = ?, updated_at = ? WHERE id = ?'
|
|
59
|
+
'UPDATE projects SET name = ?, description = ?, project_path = ?, ai_context = ?, watch_enabled = ?, agent_type = ?, updated_at = ? WHERE id = ?'
|
|
59
60
|
).run(
|
|
60
61
|
data.name ?? project.name,
|
|
61
62
|
data.description ?? project.description,
|
|
62
63
|
data.project_path !== undefined ? data.project_path : project.project_path,
|
|
63
64
|
data.ai_context !== undefined ? data.ai_context : (project.ai_context ?? ''),
|
|
64
65
|
data.watch_enabled !== undefined ? (data.watch_enabled ? 1 : 0) : (project.watch_enabled ? 1 : 0),
|
|
66
|
+
data.agent_type ?? project.agent_type ?? 'claude',
|
|
65
67
|
now,
|
|
66
68
|
id,
|
|
67
69
|
);
|
package/src/lib/db/schema.ts
CHANGED
|
@@ -33,6 +33,9 @@ export function initSchema(db: Database.Database): void {
|
|
|
33
33
|
if (!projCols.some(c => c.name === 'watch_enabled')) {
|
|
34
34
|
db.exec("ALTER TABLE projects ADD COLUMN watch_enabled INTEGER NOT NULL DEFAULT 0");
|
|
35
35
|
}
|
|
36
|
+
if (!projCols.some(c => c.name === 'agent_type')) {
|
|
37
|
+
db.exec("ALTER TABLE projects ADD COLUMN agent_type TEXT NOT NULL DEFAULT 'claude'");
|
|
38
|
+
}
|
|
36
39
|
|
|
37
40
|
// v2 tables
|
|
38
41
|
db.exec(`
|
package/src/lib/watcher.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
|
-
import {
|
|
2
|
+
import { runAgent } from './ai/client';
|
|
3
3
|
import { listProjects, getProject } from './db/queries/projects';
|
|
4
4
|
import { getSubProject } from './db/queries/sub-projects';
|
|
5
5
|
import { getTasksByProject, getTask, updateTask } from './db/queries/tasks';
|
|
@@ -121,7 +121,7 @@ async function executeTask(task: ITask, project: IProject, options: WatcherOptio
|
|
|
121
121
|
}
|
|
122
122
|
};
|
|
123
123
|
|
|
124
|
-
const result = await
|
|
124
|
+
const result = await runAgent(project.agent_type || 'claude', fullPrompt, onText, undefined, {
|
|
125
125
|
cwd,
|
|
126
126
|
timeoutMs: options.timeoutMs,
|
|
127
127
|
});
|
package/src/types/index.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export type AgentType = 'claude' | 'gemini' | 'codex';
|
|
2
|
+
|
|
1
3
|
export interface IProject {
|
|
2
4
|
id: string;
|
|
3
5
|
name: string;
|
|
@@ -5,6 +7,7 @@ export interface IProject {
|
|
|
5
7
|
project_path: string | null;
|
|
6
8
|
ai_context: string;
|
|
7
9
|
watch_enabled: boolean;
|
|
10
|
+
agent_type: AgentType;
|
|
8
11
|
created_at: string;
|
|
9
12
|
updated_at: string;
|
|
10
13
|
}
|