idea-manager 0.1.2 → 0.3.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 (63) hide show
  1. package/README.md +19 -10
  2. package/next.config.ts +0 -1
  3. package/package.json +2 -2
  4. package/public/favicon.svg +10 -0
  5. package/public/icon.svg +2 -11
  6. package/src/app/api/filesystem/route.ts +49 -0
  7. package/src/app/api/projects/[id]/cleanup/route.ts +32 -0
  8. package/src/app/api/projects/[id]/items/[itemId]/refine/route.ts +36 -0
  9. package/src/app/api/projects/[id]/items/[itemId]/route.ts +23 -1
  10. package/src/app/api/projects/[id]/items/route.ts +51 -1
  11. package/src/app/api/projects/[id]/scan/route.ts +73 -0
  12. package/src/app/api/projects/[id]/scan/stream/route.ts +112 -0
  13. package/src/app/api/projects/[id]/structure/route.ts +34 -3
  14. package/src/app/api/projects/[id]/structure/stream/route.ts +157 -0
  15. package/src/app/api/projects/[id]/sub-projects/[subId]/route.ts +39 -0
  16. package/src/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/chat/route.ts +60 -0
  17. package/src/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/prompt/route.ts +26 -0
  18. package/src/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/route.ts +39 -0
  19. package/src/app/api/projects/[id]/sub-projects/[subId]/tasks/route.ts +33 -0
  20. package/src/app/api/projects/[id]/sub-projects/route.ts +31 -0
  21. package/src/app/api/projects/route.ts +1 -1
  22. package/src/app/globals.css +465 -5
  23. package/src/app/layout.tsx +3 -0
  24. package/src/app/page.tsx +260 -88
  25. package/src/app/projects/[id]/page.tsx +366 -183
  26. package/src/cli.ts +44 -12
  27. package/src/components/DirectoryPicker.tsx +137 -0
  28. package/src/components/ScanPanel.tsx +743 -0
  29. package/src/components/brainstorm/Editor.tsx +20 -4
  30. package/src/components/brainstorm/MemoPin.tsx +91 -5
  31. package/src/components/dashboard/SubProjectCard.tsx +76 -0
  32. package/src/components/dashboard/TabBar.tsx +42 -0
  33. package/src/components/task/ProjectTree.tsx +223 -0
  34. package/src/components/task/PromptEditor.tsx +107 -0
  35. package/src/components/task/StatusFlow.tsx +43 -0
  36. package/src/components/task/TaskChat.tsx +134 -0
  37. package/src/components/task/TaskDetail.tsx +205 -0
  38. package/src/components/task/TaskList.tsx +119 -0
  39. package/src/components/tree/CardView.tsx +206 -0
  40. package/src/components/tree/RefinePopover.tsx +157 -0
  41. package/src/components/tree/TreeNode.tsx +147 -38
  42. package/src/components/tree/TreeView.tsx +270 -26
  43. package/src/components/ui/ConfirmDialog.tsx +88 -0
  44. package/src/lib/ai/chat-responder.ts +4 -2
  45. package/src/lib/ai/cleanup.ts +87 -0
  46. package/src/lib/ai/client.ts +175 -58
  47. package/src/lib/ai/prompter.ts +19 -24
  48. package/src/lib/ai/refiner.ts +128 -0
  49. package/src/lib/ai/structurer.ts +340 -11
  50. package/src/lib/db/queries/context.ts +76 -0
  51. package/src/lib/db/queries/items.ts +133 -12
  52. package/src/lib/db/queries/projects.ts +12 -8
  53. package/src/lib/db/queries/sub-projects.ts +122 -0
  54. package/src/lib/db/queries/task-conversations.ts +27 -0
  55. package/src/lib/db/queries/task-prompts.ts +32 -0
  56. package/src/lib/db/queries/tasks.ts +133 -0
  57. package/src/lib/db/schema.ts +75 -0
  58. package/src/lib/mcp/server.ts +38 -39
  59. package/src/lib/mcp/tools.ts +47 -45
  60. package/src/lib/scanner.ts +573 -0
  61. package/src/lib/task-store.ts +97 -0
  62. package/src/types/index.ts +65 -0
  63. package/src/app/icon.svg +0 -19
package/src/cli.ts CHANGED
@@ -3,16 +3,24 @@
3
3
  import { Command } from 'commander';
4
4
  import { startMcpServer } from '@/lib/mcp/server';
5
5
  import { listProjects, getProject } from '@/lib/db/queries/projects';
6
- import { getItemTree, getItems, updateItem } from '@/lib/db/queries/items';
7
- import { getPrompt } from '@/lib/db/queries/prompts';
6
+ import { getSubProjects } from '@/lib/db/queries/sub-projects';
7
+ import { getTasksByProject, updateTask } from '@/lib/db/queries/tasks';
8
+ import { getTaskPrompt } from '@/lib/db/queries/task-prompts';
8
9
  import type { McpToolContext } from '@/lib/mcp/tools';
10
+ import { spawn } from 'child_process';
11
+ import path from 'path';
12
+ import { fileURLToPath } from 'url';
13
+
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = path.dirname(__filename);
16
+ const PKG_ROOT = path.resolve(__dirname, '..');
9
17
 
10
18
  const program = new Command();
11
19
 
12
20
  program
13
21
  .name('im')
14
- .description('Idea Manager CLI')
15
- .version('1.0.0');
22
+ .description('Idea Manager v2 - Brainstorming to structured tasks with prompts')
23
+ .version('0.2.0');
16
24
 
17
25
  program
18
26
  .command('mcp')
@@ -21,10 +29,10 @@ program
21
29
  const ctx: McpToolContext = {
22
30
  listProjects,
23
31
  getProject,
24
- getItemTree,
25
- getItems,
26
- getPrompt,
27
- updateItem: (id, data) => updateItem(id, data as Parameters<typeof updateItem>[1]),
32
+ getSubProjects,
33
+ getTasksByProject,
34
+ getTaskPrompt,
35
+ updateTask: (id, data) => updateTask(id, data as Parameters<typeof updateTask>[1]),
28
36
  };
29
37
 
30
38
  await startMcpServer(ctx);
@@ -32,10 +40,34 @@ program
32
40
 
33
41
  program
34
42
  .command('start')
35
- .description('Start the web UI')
36
- .action(async () => {
37
- const open = (await import('open')).default;
38
- await open('http://localhost:3456');
43
+ .description('Start the web UI (Next.js dev server on port 3456)')
44
+ .option('-p, --port <port>', 'Port number', '3456')
45
+ .action(async (opts) => {
46
+ const port = opts.port;
47
+ console.log(`\n IM - Idea Manager v2`);
48
+ console.log(` Starting on http://localhost:${port}\n`);
49
+
50
+ const nextBin = path.join(PKG_ROOT, 'node_modules', '.bin', 'next');
51
+ const child = spawn(nextBin, ['dev', '-p', port], {
52
+ cwd: PKG_ROOT,
53
+ stdio: 'inherit',
54
+ env: { ...process.env, NODE_ENV: 'development' },
55
+ });
56
+
57
+ child.on('error', (err) => {
58
+ console.error('Failed to start server:', err.message);
59
+ process.exit(1);
60
+ });
61
+
62
+ setTimeout(async () => {
63
+ try {
64
+ const open = (await import('open')).default;
65
+ await open(`http://localhost:${port}`);
66
+ } catch { /* ignore */ }
67
+ }, 3000);
68
+
69
+ process.on('SIGINT', () => { child.kill(); process.exit(0); });
70
+ process.on('SIGTERM', () => { child.kill(); process.exit(0); });
39
71
  });
40
72
 
41
73
  program.parse();
@@ -0,0 +1,137 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect, useCallback } from 'react';
4
+
5
+ interface DirEntry {
6
+ name: string;
7
+ path: string;
8
+ }
9
+
10
+ interface DirInfo {
11
+ current: string;
12
+ parent: string | null;
13
+ dirs: DirEntry[];
14
+ isProject: boolean;
15
+ }
16
+
17
+ interface DirectoryPickerProps {
18
+ onSelect: (path: string) => void;
19
+ onCancel: () => void;
20
+ initialPath?: string;
21
+ }
22
+
23
+ export default function DirectoryPicker({ onSelect, onCancel, initialPath }: DirectoryPickerProps) {
24
+ const [dirInfo, setDirInfo] = useState<DirInfo | null>(null);
25
+ const [loading, setLoading] = useState(true);
26
+ const [error, setError] = useState<string | null>(null);
27
+
28
+ const loadDir = useCallback(async (dirPath?: string) => {
29
+ setLoading(true);
30
+ setError(null);
31
+ try {
32
+ const params = dirPath ? `?path=${encodeURIComponent(dirPath)}` : '';
33
+ const res = await fetch(`/api/filesystem${params}`);
34
+ if (res.ok) {
35
+ setDirInfo(await res.json());
36
+ } else {
37
+ const data = await res.json();
38
+ setError(data.error || '불러오기 실패');
39
+ }
40
+ } catch {
41
+ setError('불러오기 실패');
42
+ } finally {
43
+ setLoading(false);
44
+ }
45
+ }, []);
46
+
47
+ useEffect(() => {
48
+ loadDir(initialPath);
49
+ }, [loadDir, initialPath]);
50
+
51
+ return (
52
+ <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50" onClick={onCancel}>
53
+ <div
54
+ className="bg-card border border-border rounded-lg shadow-xl w-[520px] max-h-[70vh] flex flex-col"
55
+ onClick={e => e.stopPropagation()}
56
+ >
57
+ {/* Header */}
58
+ <div className="flex items-center justify-between px-4 py-3 border-b border-border">
59
+ <h3 className="text-sm font-semibold">프로젝트 폴더 선택</h3>
60
+ <button onClick={onCancel} className="text-muted-foreground hover:text-foreground text-lg leading-none">&times;</button>
61
+ </div>
62
+
63
+ {/* Current path */}
64
+ <div className="px-4 py-2 border-b border-border bg-muted">
65
+ <div className="flex items-center gap-2">
66
+ <span className="text-xs text-muted-foreground shrink-0">경로:</span>
67
+ <span className="text-xs font-mono truncate flex-1" title={dirInfo?.current}>
68
+ {dirInfo?.current || '...'}
69
+ </span>
70
+ {dirInfo?.isProject && (
71
+ <span className="text-xs text-success shrink-0 font-medium">프로젝트 감지</span>
72
+ )}
73
+ </div>
74
+ </div>
75
+
76
+ {/* Directory list */}
77
+ <div className="flex-1 overflow-y-auto min-h-0">
78
+ {loading ? (
79
+ <div className="p-8 text-center text-muted-foreground text-sm">불러오는 중...</div>
80
+ ) : error ? (
81
+ <div className="p-8 text-center text-destructive text-sm">{error}</div>
82
+ ) : (
83
+ <div className="py-1">
84
+ {/* Parent directory */}
85
+ {dirInfo?.parent && (
86
+ <button
87
+ onClick={() => loadDir(dirInfo.parent!)}
88
+ className="w-full text-left px-4 py-2 text-sm hover:bg-muted transition-colors
89
+ flex items-center gap-2 text-muted-foreground"
90
+ >
91
+ <span>↑</span>
92
+ <span>..</span>
93
+ </button>
94
+ )}
95
+
96
+ {/* Subdirectories */}
97
+ {dirInfo?.dirs.length === 0 && (
98
+ <div className="px-4 py-6 text-center text-muted-foreground text-xs">
99
+ 하위 폴더가 없습니다
100
+ </div>
101
+ )}
102
+ {dirInfo?.dirs.map(dir => (
103
+ <button
104
+ key={dir.path}
105
+ onClick={() => loadDir(dir.path)}
106
+ className="w-full text-left px-4 py-2 text-sm hover:bg-muted transition-colors
107
+ flex items-center gap-2"
108
+ >
109
+ <span className="text-muted-foreground">📁</span>
110
+ <span>{dir.name}</span>
111
+ </button>
112
+ ))}
113
+ </div>
114
+ )}
115
+ </div>
116
+
117
+ {/* Footer */}
118
+ <div className="flex items-center justify-end gap-2 px-4 py-3 border-t border-border">
119
+ <button
120
+ onClick={onCancel}
121
+ className="px-3 py-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors"
122
+ >
123
+ 취소
124
+ </button>
125
+ <button
126
+ onClick={() => dirInfo && onSelect(dirInfo.current)}
127
+ disabled={!dirInfo}
128
+ className="px-4 py-1.5 text-xs bg-primary hover:bg-primary-hover text-white
129
+ rounded-md transition-colors disabled:opacity-50"
130
+ >
131
+ 이 폴더 선택
132
+ </button>
133
+ </div>
134
+ </div>
135
+ </div>
136
+ );
137
+ }