idea-manager 1.7.0 → 1.9.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/.next/build-manifest.json +2 -2
- package/.next/routes-manifest.json +8 -0
- package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_global-error.html +2 -2
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +2 -2
- package/.next/server/app/_not-found.rsc +2 -2
- package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/api/archive/route.js +12 -3
- package/.next/server/app/api/archive/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/filesystem/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/filesystem/tree/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/global-memo/route.js +9 -0
- package/.next/server/app/api/global-memo/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/[id]/advisor/route.js +34 -0
- package/.next/server/app/api/projects/[id]/advisor/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/projects/[id]/apply-distribute/route.js +5 -5
- package/.next/server/app/api/projects/[id]/apply-distribute/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/[id]/auto-distribute/route.js +3 -3
- package/.next/server/app/api/projects/[id]/auto-distribute/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/[id]/brainstorm/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/[id]/git-sync/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/[id]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/[id]/sub-projects/[subId]/route.js +12 -3
- package/.next/server/app/api/projects/[id]/sub-projects/[subId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/chat/route.js +3 -3
- package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/chat/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/prompt/route.js +9 -0
- package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/prompt/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/refine/route.js +3 -3
- package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/refine/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/route.js +12 -3
- package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/route.js +12 -3
- package/.next/server/app/api/projects/[id]/sub-projects/[subId]/tasks/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/[id]/sub-projects/route.js +12 -3
- package/.next/server/app/api/projects/[id]/sub-projects/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/search/route.js +9 -0
- package/.next/server/app/api/search/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/sync/route.js +9 -0
- package/.next/server/app/api/sync/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/version/route_client-reference-manifest.js +1 -1
- package/.next/server/app/index.html +2 -2
- package/.next/server/app/index.rsc +3 -3
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/index.segments/_full.segment.rsc +3 -3
- package/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/page.js +6 -6
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/projects/[id]/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +12 -11
- package/.next/server/chunks/117.js +9 -0
- package/.next/server/pages/404.html +2 -2
- package/.next/server/pages/500.html +2 -2
- package/.next/static/chunks/374-769431701aab500f.js +1 -0
- package/.next/static/chunks/app/_global-error/page-3ff8f59aaa75b8f8.js +1 -0
- package/.next/static/chunks/app/api/archive/route-3ff8f59aaa75b8f8.js +1 -0
- package/.next/static/chunks/app/api/filesystem/route-3ff8f59aaa75b8f8.js +1 -0
- package/.next/static/chunks/app/api/filesystem/tree/route-3ff8f59aaa75b8f8.js +1 -0
- package/.next/static/chunks/app/api/global-memo/route-3ff8f59aaa75b8f8.js +1 -0
- package/.next/static/chunks/app/api/health/route-3ff8f59aaa75b8f8.js +1 -0
- package/.next/static/chunks/app/api/projects/[id]/advisor/route-3ff8f59aaa75b8f8.js +1 -0
- package/.next/static/chunks/app/api/projects/[id]/apply-distribute/route-3ff8f59aaa75b8f8.js +1 -0
- package/.next/static/chunks/app/api/projects/[id]/auto-distribute/route-3ff8f59aaa75b8f8.js +1 -0
- package/.next/static/chunks/app/api/projects/[id]/brainstorm/route-3ff8f59aaa75b8f8.js +1 -0
- package/.next/static/chunks/app/api/projects/[id]/git-sync/route-3ff8f59aaa75b8f8.js +1 -0
- package/.next/static/chunks/app/api/projects/[id]/route-3ff8f59aaa75b8f8.js +1 -0
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/route-3ff8f59aaa75b8f8.js +1 -0
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/chat/route-3ff8f59aaa75b8f8.js +1 -0
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/prompt/route-3ff8f59aaa75b8f8.js +1 -0
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/refine/route-3ff8f59aaa75b8f8.js +1 -0
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/route-3ff8f59aaa75b8f8.js +1 -0
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/route-3ff8f59aaa75b8f8.js +1 -0
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/route-3ff8f59aaa75b8f8.js +1 -0
- package/.next/static/chunks/app/api/projects/route-3ff8f59aaa75b8f8.js +1 -0
- package/.next/static/chunks/app/api/search/route-3ff8f59aaa75b8f8.js +1 -0
- package/.next/static/chunks/app/api/sync/route-3ff8f59aaa75b8f8.js +1 -0
- package/.next/static/chunks/app/api/update/route-3ff8f59aaa75b8f8.js +1 -0
- package/.next/static/chunks/app/api/version/route-3ff8f59aaa75b8f8.js +1 -0
- package/.next/static/chunks/app/{page-9a1dc101e82c397c.js → page-e935ee928da68ca2.js} +7 -7
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-3ff8f59aaa75b8f8.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-3ff8f59aaa75b8f8.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-3ff8f59aaa75b8f8.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-3ff8f59aaa75b8f8.js +1 -0
- package/.next/static/css/e4c7cd5a570312d9.css +3 -0
- package/README.ja.md +2 -0
- package/README.ko.md +3 -1
- package/README.md +3 -1
- package/README.zh.md +2 -0
- package/package.json +1 -1
- package/src/app/api/projects/[id]/advisor/route.ts +71 -0
- package/src/cli.ts +10 -0
- package/src/components/workspace/ProjectAdvisor.tsx +194 -0
- package/src/components/workspace/WorkspacePanel.tsx +23 -0
- package/src/lib/ai/project-context.ts +111 -0
- package/src/lib/auto-update.ts +136 -0
- package/src/lib/db/queries/project-conversations.ts +29 -0
- package/src/lib/db/schema.ts +11 -0
- package/src/types/index.ts +8 -0
- package/.next/static/chunks/374-23189d7e246ad164.js +0 -1
- package/.next/static/chunks/app/_global-error/page-e6a77f238d2cdbb9.js +0 -1
- package/.next/static/chunks/app/api/archive/route-e6a77f238d2cdbb9.js +0 -1
- package/.next/static/chunks/app/api/filesystem/route-e6a77f238d2cdbb9.js +0 -1
- package/.next/static/chunks/app/api/filesystem/tree/route-e6a77f238d2cdbb9.js +0 -1
- package/.next/static/chunks/app/api/global-memo/route-e6a77f238d2cdbb9.js +0 -1
- package/.next/static/chunks/app/api/health/route-e6a77f238d2cdbb9.js +0 -1
- package/.next/static/chunks/app/api/projects/[id]/apply-distribute/route-e6a77f238d2cdbb9.js +0 -1
- package/.next/static/chunks/app/api/projects/[id]/auto-distribute/route-e6a77f238d2cdbb9.js +0 -1
- package/.next/static/chunks/app/api/projects/[id]/brainstorm/route-e6a77f238d2cdbb9.js +0 -1
- package/.next/static/chunks/app/api/projects/[id]/git-sync/route-e6a77f238d2cdbb9.js +0 -1
- package/.next/static/chunks/app/api/projects/[id]/route-e6a77f238d2cdbb9.js +0 -1
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/route-e6a77f238d2cdbb9.js +0 -1
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/chat/route-e6a77f238d2cdbb9.js +0 -1
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/prompt/route-e6a77f238d2cdbb9.js +0 -1
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/refine/route-e6a77f238d2cdbb9.js +0 -1
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/[taskId]/route-e6a77f238d2cdbb9.js +0 -1
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/[subId]/tasks/route-e6a77f238d2cdbb9.js +0 -1
- package/.next/static/chunks/app/api/projects/[id]/sub-projects/route-e6a77f238d2cdbb9.js +0 -1
- package/.next/static/chunks/app/api/projects/route-e6a77f238d2cdbb9.js +0 -1
- package/.next/static/chunks/app/api/search/route-e6a77f238d2cdbb9.js +0 -1
- package/.next/static/chunks/app/api/sync/route-e6a77f238d2cdbb9.js +0 -1
- package/.next/static/chunks/app/api/update/route-e6a77f238d2cdbb9.js +0 -1
- package/.next/static/chunks/app/api/version/route-e6a77f238d2cdbb9.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-e6a77f238d2cdbb9.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-e6a77f238d2cdbb9.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-e6a77f238d2cdbb9.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-e6a77f238d2cdbb9.js +0 -1
- package/.next/static/css/eab748b03f49c43a.css +0 -3
- /package/.next/static/{mxrEVQX3r5YlDPZgpDvSp → pxqzEiwniZAUDOUTb5SnX}/_buildManifest.js +0 -0
- /package/.next/static/{mxrEVQX3r5YlDPZgpDvSp → pxqzEiwniZAUDOUTb5SnX}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { getProject } from '../db/queries/projects';
|
|
2
|
+
import { getSubProjects } from '../db/queries/sub-projects';
|
|
3
|
+
import { getTasksByProject } from '../db/queries/tasks';
|
|
4
|
+
import { getBrainstorm } from '../db/queries/brainstorms';
|
|
5
|
+
import type { ITask, TaskStatus } from '../../types';
|
|
6
|
+
|
|
7
|
+
const MAX_BRAINSTORM = 4000;
|
|
8
|
+
const NOTE_LIMIT_ACTIVE = 500;
|
|
9
|
+
const NOTE_LIMIT_DEFAULT = 200;
|
|
10
|
+
const MAX_HISTORY_MESSAGES = 20;
|
|
11
|
+
|
|
12
|
+
function truncate(s: string | null | undefined, max: number): string {
|
|
13
|
+
if (!s) return '';
|
|
14
|
+
if (s.length <= max) return s;
|
|
15
|
+
return s.slice(0, max) + '…';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function isActive(task: ITask): boolean {
|
|
19
|
+
return (['doing', 'problem', 'testing'] as TaskStatus[]).includes(task.status) || task.is_today;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const STATUS_ICON: Record<string, string> = {
|
|
23
|
+
idea: 'idea', doing: 'DOING', writing: 'writing', submitted: 'submitted',
|
|
24
|
+
testing: 'testing', done: 'done', problem: 'PROBLEM',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export function buildProjectAdvisorPrompt(projectId: string): string {
|
|
28
|
+
const project = getProject(projectId);
|
|
29
|
+
if (!project) return '';
|
|
30
|
+
|
|
31
|
+
const brainstorm = getBrainstorm(projectId);
|
|
32
|
+
const subs = getSubProjects(projectId);
|
|
33
|
+
const allTasks = getTasksByProject(projectId);
|
|
34
|
+
|
|
35
|
+
// Group tasks by sub-project
|
|
36
|
+
const tasksBySub = new Map<string, ITask[]>();
|
|
37
|
+
for (const t of allTasks) {
|
|
38
|
+
const list = tasksBySub.get(t.sub_project_id) ?? [];
|
|
39
|
+
list.push(t);
|
|
40
|
+
tasksBySub.set(t.sub_project_id, list);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Build task summary per sub-project
|
|
44
|
+
const subSections: string[] = [];
|
|
45
|
+
for (const sub of subs) {
|
|
46
|
+
const tasks = tasksBySub.get(sub.id) ?? [];
|
|
47
|
+
if (tasks.length === 0) {
|
|
48
|
+
subSections.push(`### ${sub.name}\n${sub.description || '(설명 없음)'}\n태스크 없음.`);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const lines: string[] = [];
|
|
52
|
+
lines.push(`### ${sub.name}`);
|
|
53
|
+
if (sub.description) lines.push(sub.description);
|
|
54
|
+
lines.push(`태스크 ${tasks.length}개:`);
|
|
55
|
+
for (const t of tasks) {
|
|
56
|
+
const noteLimit = isActive(t) ? NOTE_LIMIT_ACTIVE : NOTE_LIMIT_DEFAULT;
|
|
57
|
+
const note = truncate(t.description, noteLimit);
|
|
58
|
+
const flags = [t.priority === 'high' ? 'HIGH' : null, t.is_today ? 'today' : null].filter(Boolean).join(', ');
|
|
59
|
+
const flagStr = flags ? ` (${flags})` : '';
|
|
60
|
+
const noteStr = note ? ` — ${note}` : '';
|
|
61
|
+
lines.push(`- [${STATUS_ICON[t.status] ?? t.status}] **${t.title}**${flagStr}${noteStr}`);
|
|
62
|
+
}
|
|
63
|
+
subSections.push(lines.join('\n'));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Stats
|
|
67
|
+
const counts: Record<string, number> = {};
|
|
68
|
+
let todayCount = 0;
|
|
69
|
+
const problemTasks: string[] = [];
|
|
70
|
+
for (const t of allTasks) {
|
|
71
|
+
counts[t.status] = (counts[t.status] ?? 0) + 1;
|
|
72
|
+
if (t.is_today) todayCount++;
|
|
73
|
+
if (t.status === 'problem') problemTasks.push(t.title);
|
|
74
|
+
}
|
|
75
|
+
const statsLines = [
|
|
76
|
+
`- 전체: ${allTasks.length}개`,
|
|
77
|
+
...Object.entries(counts).map(([k, v]) => ` - ${k}: ${v}`),
|
|
78
|
+
`- Today 표시: ${todayCount}개`,
|
|
79
|
+
];
|
|
80
|
+
if (problemTasks.length > 0) {
|
|
81
|
+
statsLines.push(`- 문제 태스크: ${problemTasks.join(', ')}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Assemble
|
|
85
|
+
const parts: string[] = [];
|
|
86
|
+
parts.push(`당신은 프로젝트 "${project.name}"의 어드바이저입니다.`);
|
|
87
|
+
parts.push(`사용자가 프로젝트 방향, 우선순위, 빠진 부분, 다음 단계 등을 논의하면 프로젝트 전체 맥락을 바탕으로 간결하게 답합니다.`);
|
|
88
|
+
parts.push(`태스크를 언급할 때는 정확한 제목을 쓰세요. 한국어로 답하세요. 긴 설교는 금지.`);
|
|
89
|
+
|
|
90
|
+
if (project.ai_context) {
|
|
91
|
+
parts.push(`\nProject AI Policy:\n${project.ai_context}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
parts.push('\n=== PROJECT CONTEXT ===');
|
|
95
|
+
|
|
96
|
+
if (brainstorm?.content) {
|
|
97
|
+
parts.push(`\n## 브레인스토밍\n${truncate(brainstorm.content, MAX_BRAINSTORM)}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
parts.push(`\n## 프로젝트 & 태스크\n${subSections.join('\n\n')}`);
|
|
101
|
+
parts.push(`\n## 통계\n${statsLines.join('\n')}`);
|
|
102
|
+
|
|
103
|
+
return parts.join('\n');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function trimConversationHistory(
|
|
107
|
+
messages: { role: string; content: string }[],
|
|
108
|
+
): { role: string; content: string }[] {
|
|
109
|
+
if (messages.length <= MAX_HISTORY_MESSAGES) return messages;
|
|
110
|
+
return messages.slice(-MAX_HISTORY_MESSAGES);
|
|
111
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import { readFileSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
|
|
5
|
+
function readInstalledVersion(pkgRoot: string): string {
|
|
6
|
+
try {
|
|
7
|
+
const raw = readFileSync(join(pkgRoot, 'package.json'), 'utf8');
|
|
8
|
+
const parsed = JSON.parse(raw) as { version?: string };
|
|
9
|
+
return parsed.version ?? '0.0.0';
|
|
10
|
+
} catch {
|
|
11
|
+
return '0.0.0';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function cmpVersion(a: string, b: string): number {
|
|
16
|
+
const as = a.split('.').map(n => parseInt(n, 10) || 0);
|
|
17
|
+
const bs = b.split('.').map(n => parseInt(n, 10) || 0);
|
|
18
|
+
for (let i = 0; i < Math.max(as.length, bs.length); i++) {
|
|
19
|
+
const av = as[i] ?? 0;
|
|
20
|
+
const bv = bs[i] ?? 0;
|
|
21
|
+
if (av !== bv) return av - bv;
|
|
22
|
+
}
|
|
23
|
+
return 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function fetchLatestVersion(): Promise<string | null> {
|
|
27
|
+
try {
|
|
28
|
+
const controller = new AbortController();
|
|
29
|
+
const timer = setTimeout(() => controller.abort(), 3000);
|
|
30
|
+
const res = await fetch('https://registry.npmjs.org/idea-manager/latest', {
|
|
31
|
+
signal: controller.signal,
|
|
32
|
+
});
|
|
33
|
+
clearTimeout(timer);
|
|
34
|
+
if (!res.ok) return null;
|
|
35
|
+
const data = await res.json() as { version?: string };
|
|
36
|
+
return typeof data.version === 'string' ? data.version : null;
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function runNpmInstall(): Promise<boolean> {
|
|
43
|
+
return new Promise((resolve) => {
|
|
44
|
+
const child = spawn('npm', ['install', '-g', 'idea-manager@latest', '--no-fund', '--no-audit'], {
|
|
45
|
+
stdio: 'inherit',
|
|
46
|
+
shell: process.platform === 'win32',
|
|
47
|
+
env: process.env,
|
|
48
|
+
});
|
|
49
|
+
const timer = setTimeout(() => child.kill('SIGTERM'), 3 * 60 * 1000);
|
|
50
|
+
child.on('exit', (code) => {
|
|
51
|
+
clearTimeout(timer);
|
|
52
|
+
resolve(code === 0);
|
|
53
|
+
});
|
|
54
|
+
child.on('error', () => {
|
|
55
|
+
clearTimeout(timer);
|
|
56
|
+
resolve(false);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface AutoUpdateResult {
|
|
62
|
+
current: string;
|
|
63
|
+
latest?: string;
|
|
64
|
+
upgraded: boolean;
|
|
65
|
+
skipped: boolean;
|
|
66
|
+
reason?: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Check npm registry for a newer version and install it in place. Designed
|
|
71
|
+
* for `im start` only — `im mcp` / `im watch` should NOT auto-update because
|
|
72
|
+
* they may be long-running integrations where a mid-session restart is
|
|
73
|
+
* disruptive.
|
|
74
|
+
*
|
|
75
|
+
* Opt-outs:
|
|
76
|
+
* - `IM_NO_AUTO_UPDATE=1` env var
|
|
77
|
+
* - `CI` env var (any truthy value)
|
|
78
|
+
* - Network timeout / registry unreachable → silently skipped
|
|
79
|
+
* - Failed `npm install` → logged, falls back to current version
|
|
80
|
+
*/
|
|
81
|
+
export async function maybeAutoUpdate(pkgRoot: string): Promise<AutoUpdateResult> {
|
|
82
|
+
const current = readInstalledVersion(pkgRoot);
|
|
83
|
+
|
|
84
|
+
if (process.env.IM_NO_AUTO_UPDATE === '1') {
|
|
85
|
+
return { current, upgraded: false, skipped: true, reason: 'IM_NO_AUTO_UPDATE=1' };
|
|
86
|
+
}
|
|
87
|
+
if (process.env.CI) {
|
|
88
|
+
return { current, upgraded: false, skipped: true, reason: 'CI environment' };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const latest = await fetchLatestVersion();
|
|
92
|
+
if (!latest) {
|
|
93
|
+
return { current, upgraded: false, skipped: true, reason: 'registry unreachable' };
|
|
94
|
+
}
|
|
95
|
+
if (cmpVersion(latest, current) <= 0) {
|
|
96
|
+
return { current, latest, upgraded: false, skipped: false };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
console.log('');
|
|
100
|
+
console.log(` IM — 새 버전 감지: v${current} → v${latest}`);
|
|
101
|
+
console.log(` 업데이트 중... (IM_NO_AUTO_UPDATE=1로 건너뛸 수 있음)`);
|
|
102
|
+
console.log('');
|
|
103
|
+
|
|
104
|
+
const ok = await runNpmInstall();
|
|
105
|
+
if (!ok) {
|
|
106
|
+
console.log('');
|
|
107
|
+
console.log(` ⚠ 업데이트 실패 — 기존 v${current}로 계속 진행합니다.`);
|
|
108
|
+
console.log('');
|
|
109
|
+
return { current, latest, upgraded: false, skipped: false, reason: 'install failed' };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
console.log('');
|
|
113
|
+
console.log(` ✓ v${latest} 설치 완료. 재시작합니다...`);
|
|
114
|
+
console.log('');
|
|
115
|
+
return { current, latest, upgraded: true, skipped: false };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Re-exec the `im` CLI with the same arguments so the freshly installed code
|
|
120
|
+
* replaces the old copy loaded in the current Node process. Sets
|
|
121
|
+
* IM_NO_AUTO_UPDATE=1 on the child to prevent an update-respawn loop.
|
|
122
|
+
*/
|
|
123
|
+
export function respawnSelf(): void {
|
|
124
|
+
const args = process.argv.slice(2);
|
|
125
|
+
const child = spawn('im', args, {
|
|
126
|
+
stdio: 'inherit',
|
|
127
|
+
shell: process.platform === 'win32',
|
|
128
|
+
env: { ...process.env, IM_NO_AUTO_UPDATE: '1' },
|
|
129
|
+
});
|
|
130
|
+
child.on('exit', (code) => process.exit(code ?? 0));
|
|
131
|
+
child.on('error', (err) => {
|
|
132
|
+
console.error(` ⚠ 재시작 실패: ${err.message}`);
|
|
133
|
+
console.error(` 직접 다시 실행해주세요: im ${args.join(' ')}`);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { getDb } from '../index';
|
|
2
|
+
import { generateId } from '../../utils/id';
|
|
3
|
+
import type { IProjectConversation } from '../../../types';
|
|
4
|
+
|
|
5
|
+
export function getProjectConversations(projectId: string, limit = 50): IProjectConversation[] {
|
|
6
|
+
const db = getDb();
|
|
7
|
+
return db.prepare(
|
|
8
|
+
`SELECT * FROM project_conversations WHERE project_id = ? ORDER BY created_at DESC LIMIT ?`
|
|
9
|
+
).all(projectId, limit).reverse() as IProjectConversation[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function addProjectConversation(
|
|
13
|
+
projectId: string,
|
|
14
|
+
role: 'assistant' | 'user' | 'system',
|
|
15
|
+
content: string,
|
|
16
|
+
): IProjectConversation {
|
|
17
|
+
const db = getDb();
|
|
18
|
+
const id = generateId();
|
|
19
|
+
const now = new Date().toISOString();
|
|
20
|
+
db.prepare(
|
|
21
|
+
'INSERT INTO project_conversations (id, project_id, role, content, created_at) VALUES (?, ?, ?, ?, ?)'
|
|
22
|
+
).run(id, projectId, role, content, now);
|
|
23
|
+
return db.prepare('SELECT * FROM project_conversations WHERE id = ?').get(id) as IProjectConversation;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function clearProjectConversations(projectId: string): void {
|
|
27
|
+
const db = getDb();
|
|
28
|
+
db.prepare('DELETE FROM project_conversations WHERE project_id = ?').run(projectId);
|
|
29
|
+
}
|
package/src/lib/db/schema.ts
CHANGED
|
@@ -95,6 +95,17 @@ export function initSchema(db: any): void {
|
|
|
95
95
|
);
|
|
96
96
|
`);
|
|
97
97
|
|
|
98
|
+
db.exec(`
|
|
99
|
+
CREATE TABLE IF NOT EXISTS project_conversations (
|
|
100
|
+
id TEXT PRIMARY KEY,
|
|
101
|
+
project_id TEXT NOT NULL,
|
|
102
|
+
role TEXT NOT NULL CHECK(role IN ('assistant','user','system')),
|
|
103
|
+
content TEXT NOT NULL,
|
|
104
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
105
|
+
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
|
|
106
|
+
);
|
|
107
|
+
`);
|
|
108
|
+
|
|
98
109
|
// tasks archive migration
|
|
99
110
|
const taskCols = db.prepare("PRAGMA table_info(tasks)").all() as { name: string }[];
|
|
100
111
|
if (!taskCols.some(c => c.name === 'is_archived')) {
|
package/src/types/index.ts
CHANGED
|
@@ -70,6 +70,14 @@ export interface ITaskConversation {
|
|
|
70
70
|
created_at: string;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
export interface IProjectConversation {
|
|
74
|
+
id: string;
|
|
75
|
+
project_id: string;
|
|
76
|
+
role: 'assistant' | 'user' | 'system';
|
|
77
|
+
content: string;
|
|
78
|
+
created_at: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
73
81
|
export interface IGitSyncResult {
|
|
74
82
|
projectId: string;
|
|
75
83
|
projectName: string;
|