olly-molly 0.1.0 → 0.1.3

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 (77) hide show
  1. package/README.md +3 -3
  2. package/bin/cli.js +137 -52
  3. package/package.json +4 -15
  4. package/app/api/agent/execute/route.ts +0 -157
  5. package/app/api/agent/status/route.ts +0 -38
  6. package/app/api/check-api-key/route.ts +0 -12
  7. package/app/api/conversations/[id]/route.ts +0 -35
  8. package/app/api/conversations/route.ts +0 -24
  9. package/app/api/members/[id]/route.ts +0 -37
  10. package/app/api/members/route.ts +0 -12
  11. package/app/api/pm/breakdown/route.ts +0 -142
  12. package/app/api/pm/tickets/route.ts +0 -147
  13. package/app/api/projects/[id]/route.ts +0 -56
  14. package/app/api/projects/active/route.ts +0 -15
  15. package/app/api/projects/route.ts +0 -53
  16. package/app/api/tickets/[id]/logs/route.ts +0 -16
  17. package/app/api/tickets/[id]/route.ts +0 -60
  18. package/app/api/tickets/[id]/work-logs/route.ts +0 -16
  19. package/app/api/tickets/route.ts +0 -37
  20. package/app/design-system/page.tsx +0 -242
  21. package/app/favicon.ico +0 -0
  22. package/app/globals.css +0 -318
  23. package/app/layout.tsx +0 -37
  24. package/app/page.tsx +0 -331
  25. package/components/ThemeProvider.tsx +0 -56
  26. package/components/ThemeToggle.tsx +0 -31
  27. package/components/activity/ActivityLog.tsx +0 -96
  28. package/components/activity/index.ts +0 -1
  29. package/components/kanban/ConversationList.tsx +0 -75
  30. package/components/kanban/ConversationView.tsx +0 -132
  31. package/components/kanban/KanbanBoard.tsx +0 -179
  32. package/components/kanban/KanbanColumn.tsx +0 -80
  33. package/components/kanban/SortableTicket.tsx +0 -58
  34. package/components/kanban/TicketCard.tsx +0 -98
  35. package/components/kanban/TicketModal.tsx +0 -510
  36. package/components/kanban/TicketSidebar.tsx +0 -448
  37. package/components/kanban/index.ts +0 -8
  38. package/components/pm/PMRequestModal.tsx +0 -196
  39. package/components/pm/index.ts +0 -1
  40. package/components/project/ProjectSelector.tsx +0 -211
  41. package/components/project/index.ts +0 -1
  42. package/components/team/MemberCard.tsx +0 -147
  43. package/components/team/TeamPanel.tsx +0 -57
  44. package/components/team/index.ts +0 -2
  45. package/components/ui/ApiKeyModal.tsx +0 -101
  46. package/components/ui/Avatar.tsx +0 -95
  47. package/components/ui/Badge.tsx +0 -59
  48. package/components/ui/Button.tsx +0 -60
  49. package/components/ui/Card.tsx +0 -64
  50. package/components/ui/Input.tsx +0 -41
  51. package/components/ui/Modal.tsx +0 -76
  52. package/components/ui/ResizablePane.tsx +0 -97
  53. package/components/ui/Select.tsx +0 -45
  54. package/components/ui/Textarea.tsx +0 -41
  55. package/components/ui/index.ts +0 -8
  56. package/db/dev.sqlite +0 -0
  57. package/db/dev.sqlite-shm +0 -0
  58. package/db/dev.sqlite-wal +0 -0
  59. package/db/schema-conversations.sql +0 -26
  60. package/db/schema-projects.sql +0 -29
  61. package/db/schema.sql +0 -94
  62. package/lib/agent-jobs.ts +0 -232
  63. package/lib/db.ts +0 -564
  64. package/next.config.ts +0 -10
  65. package/postcss.config.mjs +0 -7
  66. package/public/app-icon.png +0 -0
  67. package/public/file.svg +0 -1
  68. package/public/globe.svg +0 -1
  69. package/public/next.svg +0 -1
  70. package/public/profiles/designer.png +0 -0
  71. package/public/profiles/dev-backend.png +0 -0
  72. package/public/profiles/dev-frontend.png +0 -0
  73. package/public/profiles/pm.png +0 -0
  74. package/public/profiles/qa.png +0 -0
  75. package/public/vercel.svg +0 -1
  76. package/public/window.svg +0 -1
  77. package/tsconfig.json +0 -34
@@ -1,142 +0,0 @@
1
- import { NextRequest, NextResponse } from 'next/server';
2
- import { ticketService, memberService } from '@/lib/db';
3
- import OpenAI from 'openai';
4
-
5
- const openai = new OpenAI({
6
- apiKey: process.env.OPENAI_API_KEY,
7
- });
8
-
9
- /**
10
- * PM Agent - AI-powered feature breakdown
11
- *
12
- * Uses OpenAI to analyze feature requests and create appropriate tasks
13
- * with intelligent assignment to team members.
14
- */
15
-
16
- interface TaskFromAI {
17
- title: string;
18
- description: string;
19
- priority: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
20
- assignee_role: 'FE_DEV' | 'BACKEND_DEV' | 'QA' | 'DEVOPS';
21
- }
22
-
23
- const SYSTEM_PROMPT = `You are a PM (Project Manager) AI agent for a software development team.
24
-
25
- Your team consists of:
26
- - FE_DEV (Frontend Developer): Handles UI/UX, React, Next.js, CSS, components, user interfaces
27
- - BACKEND_DEV (Backend Developer): Handles APIs, databases, server logic, authentication, business logic
28
- - QA (QA Engineer): Handles testing, quality assurance, bug verification, E2E tests
29
- - DEVOPS (DevOps Engineer): Handles deployment, CI/CD, infrastructure, monitoring
30
-
31
- When given a feature request, you must:
32
- 1. Break it down into specific, actionable tasks
33
- 2. Assign each task to the appropriate team member based on their expertise
34
- 3. Set priorities (CRITICAL > HIGH > MEDIUM > LOW)
35
-
36
- IMPORTANT RULES:
37
- - Create focused, single-responsibility tasks
38
- - Backend tasks should come before frontend integration tasks
39
- - Always include a QA task for testing
40
- - Be specific in task descriptions
41
- - Use Korean for titles and descriptions
42
-
43
- Respond in JSON format:
44
- {
45
- "tasks": [
46
- {
47
- "title": "Task title in Korean",
48
- "description": "Detailed task description in Korean",
49
- "priority": "HIGH",
50
- "assignee_role": "BACKEND_DEV"
51
- }
52
- ],
53
- "summary": "Brief summary of the breakdown in Korean"
54
- }`;
55
-
56
- async function breakdownWithAI(request: string): Promise<{ tasks: TaskFromAI[]; summary: string }> {
57
- const completion = await openai.chat.completions.create({
58
- model: 'gpt-4o-mini',
59
- messages: [
60
- { role: 'system', content: SYSTEM_PROMPT },
61
- { role: 'user', content: `Feature Request: ${request}` }
62
- ],
63
- response_format: { type: 'json_object' },
64
- temperature: 0.7,
65
- max_tokens: 2000,
66
- });
67
-
68
- const content = completion.choices[0].message.content;
69
- if (!content) {
70
- throw new Error('No response from AI');
71
- }
72
-
73
- return JSON.parse(content);
74
- }
75
-
76
- function getAssigneeByRole(role: string): string | null {
77
- const member = memberService.getByRole(role);
78
- return member?.id || null;
79
- }
80
-
81
- export async function POST(request: NextRequest) {
82
- try {
83
- const body = await request.json();
84
-
85
- if (!body.request) {
86
- return NextResponse.json(
87
- { error: 'Feature request is required' },
88
- { status: 400 }
89
- );
90
- }
91
-
92
- if (!process.env.OPENAI_API_KEY) {
93
- return NextResponse.json(
94
- { error: 'OpenAI API key not configured' },
95
- { status: 500 }
96
- );
97
- }
98
-
99
- const pmMember = memberService.getByRole('PM');
100
-
101
- // Use AI to break down the request
102
- const aiResponse = await breakdownWithAI(body.request);
103
-
104
- const createdTickets = [];
105
-
106
- for (const task of aiResponse.tasks) {
107
- const assigneeId = getAssigneeByRole(task.assignee_role);
108
-
109
- const ticket = ticketService.create({
110
- title: task.title,
111
- description: task.description,
112
- priority: task.priority,
113
- assignee_id: assigneeId || undefined,
114
- project_id: body.project_id,
115
- created_by: pmMember?.id,
116
- });
117
-
118
- const assignee = assigneeId ? memberService.getById(assigneeId) : null;
119
- createdTickets.push({
120
- ...ticket,
121
- assignee,
122
- assigned_role: task.assignee_role,
123
- });
124
- }
125
-
126
- return NextResponse.json({
127
- success: true,
128
- original_request: body.request,
129
- created_by: pmMember,
130
- tickets_created: createdTickets.length,
131
- tickets: createdTickets,
132
- ai_summary: aiResponse.summary,
133
- message: `PM이 AI를 사용해 "${body.request}" 요청을 분석하여 ${createdTickets.length}개의 태스크를 생성했습니다.`,
134
- }, { status: 201 });
135
- } catch (error) {
136
- console.error('Error in PM breakdown:', error);
137
- return NextResponse.json(
138
- { error: 'Failed to process feature request', details: String(error) },
139
- { status: 500 }
140
- );
141
- }
142
- }
@@ -1,147 +0,0 @@
1
- import { NextRequest, NextResponse } from 'next/server';
2
- import { ticketService, memberService } from '@/lib/db';
3
-
4
- /**
5
- * PM Agent API - Create tickets with automatic assignment
6
- *
7
- * The PM Agent analyzes the ticket content and automatically assigns
8
- * it to the most appropriate team member based on keywords and task type.
9
- */
10
-
11
- // Keywords mapping for auto-assignment
12
- const ROLE_KEYWORDS: Record<string, string[]> = {
13
- FE_DEV: [
14
- 'frontend', 'ui', 'ux', 'react', 'component', 'css', 'style', 'design',
15
- 'button', 'form', 'page', 'layout', 'responsive', 'animation', 'tailwind',
16
- '프론트엔드', 'UI', '컴포넌트', '디자인', '스타일', '화면', '페이지'
17
- ],
18
- BACKEND_DEV: [
19
- 'backend', 'api', 'database', 'db', 'server', 'endpoint', 'rest',
20
- 'authentication', 'auth', 'sql', 'query', 'migration', 'model',
21
- '백엔드', 'API', '데이터베이스', '서버', '인증'
22
- ],
23
- QA: [
24
- 'test', 'testing', 'qa', 'quality', 'bug', 'fix', 'verify', 'validation',
25
- 'e2e', 'integration', 'unit test', 'playwright', 'automation',
26
- '테스트', 'QA', '버그', '검증', '품질'
27
- ],
28
- DEVOPS: [
29
- 'deploy', 'deployment', 'ci', 'cd', 'pipeline', 'docker', 'kubernetes',
30
- 'infrastructure', 'monitoring', 'logging', 'aws', 'cloud', 'server',
31
- '배포', '인프라', '파이프라인', '모니터링'
32
- ],
33
- };
34
-
35
- function analyzeAndAssign(title: string, description?: string): string | null {
36
- const text = `${title} ${description || ''}`.toLowerCase();
37
-
38
- // Count keyword matches for each role
39
- const scores: Record<string, number> = {
40
- FE_DEV: 0,
41
- BACKEND_DEV: 0,
42
- QA: 0,
43
- DEVOPS: 0,
44
- };
45
-
46
- for (const [role, keywords] of Object.entries(ROLE_KEYWORDS)) {
47
- for (const keyword of keywords) {
48
- if (text.includes(keyword.toLowerCase())) {
49
- scores[role] += 1;
50
- }
51
- }
52
- }
53
-
54
- // Find the role with highest score
55
- let maxScore = 0;
56
- let assignedRole: string | null = null;
57
-
58
- for (const [role, score] of Object.entries(scores)) {
59
- if (score > maxScore) {
60
- maxScore = score;
61
- assignedRole = role;
62
- }
63
- }
64
-
65
- // If no clear match, default to FE_DEV for general tasks
66
- if (maxScore === 0) {
67
- assignedRole = 'FE_DEV';
68
- }
69
-
70
- // Get the member with this role
71
- const member = memberService.getByRole(assignedRole!);
72
- return member?.id || null;
73
- }
74
-
75
- export async function POST(request: NextRequest) {
76
- try {
77
- const body = await request.json();
78
-
79
- if (!body.title) {
80
- return NextResponse.json(
81
- { error: 'Title is required' },
82
- { status: 400 }
83
- );
84
- }
85
-
86
- // Get PM member
87
- const pmMember = memberService.getByRole('PM');
88
-
89
- // Auto-assign based on content analysis
90
- let assigneeId = body.assignee_id;
91
- let autoAssigned = false;
92
-
93
- if (!assigneeId && body.auto_assign !== false) {
94
- assigneeId = analyzeAndAssign(body.title, body.description);
95
- autoAssigned = true;
96
- }
97
-
98
- // Create ticket with PM as creator
99
- const ticket = ticketService.create({
100
- title: body.title,
101
- description: body.description,
102
- priority: body.priority || 'MEDIUM',
103
- assignee_id: assigneeId,
104
- created_by: pmMember?.id,
105
- });
106
-
107
- // Get assignee info for response
108
- const assignee = assigneeId ? memberService.getById(assigneeId) : null;
109
-
110
- return NextResponse.json({
111
- ...ticket,
112
- assignee,
113
- auto_assigned: autoAssigned,
114
- created_by_pm: true,
115
- }, { status: 201 });
116
- } catch (error) {
117
- console.error('Error in PM create ticket:', error);
118
- return NextResponse.json(
119
- { error: 'Failed to create ticket' },
120
- { status: 500 }
121
- );
122
- }
123
- }
124
-
125
- // GET endpoint to get PM agent info and capabilities
126
- export async function GET() {
127
- try {
128
- const pmMember = memberService.getByRole('PM');
129
- const allMembers = memberService.getAll();
130
-
131
- return NextResponse.json({
132
- pm: pmMember,
133
- team: allMembers.filter(m => m.role !== 'PM'),
134
- capabilities: {
135
- auto_assignment: true,
136
- supported_roles: Object.keys(ROLE_KEYWORDS),
137
- keywords: ROLE_KEYWORDS,
138
- },
139
- });
140
- } catch (error) {
141
- console.error('Error fetching PM info:', error);
142
- return NextResponse.json(
143
- { error: 'Failed to fetch PM info' },
144
- { status: 500 }
145
- );
146
- }
147
- }
@@ -1,56 +0,0 @@
1
- import { NextRequest, NextResponse } from 'next/server';
2
- import { projectService } from '@/lib/db';
3
-
4
- export async function GET(
5
- request: NextRequest,
6
- { params }: { params: Promise<{ id: string }> }
7
- ) {
8
- try {
9
- const { id } = await params;
10
- const project = projectService.getById(id);
11
- if (!project) {
12
- return NextResponse.json({ error: 'Project not found' }, { status: 404 });
13
- }
14
- return NextResponse.json(project);
15
- } catch (error) {
16
- console.error('Error fetching project:', error);
17
- return NextResponse.json({ error: 'Failed to fetch project' }, { status: 500 });
18
- }
19
- }
20
-
21
- export async function DELETE(
22
- request: NextRequest,
23
- { params }: { params: Promise<{ id: string }> }
24
- ) {
25
- try {
26
- const { id } = await params;
27
- const deleted = projectService.delete(id);
28
- if (!deleted) {
29
- return NextResponse.json({ error: 'Project not found' }, { status: 404 });
30
- }
31
- return NextResponse.json({ success: true });
32
- } catch (error) {
33
- console.error('Error deleting project:', error);
34
- return NextResponse.json({ error: 'Failed to delete project' }, { status: 500 });
35
- }
36
- }
37
-
38
- export async function PATCH(
39
- request: NextRequest,
40
- { params }: { params: Promise<{ id: string }> }
41
- ) {
42
- try {
43
- const { id } = await params;
44
- const body = await request.json();
45
-
46
- if (body.is_active) {
47
- const project = projectService.setActive(id);
48
- return NextResponse.json(project);
49
- }
50
-
51
- return NextResponse.json({ error: 'Invalid update' }, { status: 400 });
52
- } catch (error) {
53
- console.error('Error updating project:', error);
54
- return NextResponse.json({ error: 'Failed to update project' }, { status: 500 });
55
- }
56
- }
@@ -1,15 +0,0 @@
1
- import { NextRequest, NextResponse } from 'next/server';
2
- import { projectService } from '@/lib/db';
3
-
4
- export async function GET() {
5
- try {
6
- const project = projectService.getActive();
7
- if (!project) {
8
- return NextResponse.json({ error: 'No active project' }, { status: 404 });
9
- }
10
- return NextResponse.json(project);
11
- } catch (error) {
12
- console.error('Error fetching active project:', error);
13
- return NextResponse.json({ error: 'Failed to fetch active project' }, { status: 500 });
14
- }
15
- }
@@ -1,53 +0,0 @@
1
- import { NextRequest, NextResponse } from 'next/server';
2
- import { projectService } from '@/lib/db';
3
- import fs from 'fs';
4
- import path from 'path';
5
-
6
- export async function GET() {
7
- try {
8
- const projects = projectService.getAll();
9
- return NextResponse.json(projects);
10
- } catch (error) {
11
- console.error('Error fetching projects:', error);
12
- return NextResponse.json({ error: 'Failed to fetch projects' }, { status: 500 });
13
- }
14
- }
15
-
16
- export async function POST(request: NextRequest) {
17
- try {
18
- const body = await request.json();
19
-
20
- if (!body.path) {
21
- return NextResponse.json({ error: 'Project path is required' }, { status: 400 });
22
- }
23
-
24
- // Validate that the path exists and is a directory
25
- const projectPath = body.path;
26
- if (!fs.existsSync(projectPath)) {
27
- return NextResponse.json({ error: 'Path does not exist' }, { status: 400 });
28
- }
29
-
30
- const stats = fs.statSync(projectPath);
31
- if (!stats.isDirectory()) {
32
- return NextResponse.json({ error: 'Path is not a directory' }, { status: 400 });
33
- }
34
-
35
- // Check if it's a git repository
36
- const gitPath = path.join(projectPath, '.git');
37
- const isGitRepo = fs.existsSync(gitPath);
38
-
39
- // Extract project name from path if not provided
40
- const name = body.name || path.basename(projectPath);
41
-
42
- const project = projectService.create({
43
- name,
44
- path: projectPath,
45
- description: body.description || (isGitRepo ? 'Git repository' : 'Local project'),
46
- });
47
-
48
- return NextResponse.json({ ...project, is_git_repo: isGitRepo }, { status: 201 });
49
- } catch (error) {
50
- console.error('Error creating project:', error);
51
- return NextResponse.json({ error: 'Failed to create project' }, { status: 500 });
52
- }
53
- }
@@ -1,16 +0,0 @@
1
- import { NextRequest, NextResponse } from 'next/server';
2
- import { activityService } from '@/lib/db';
3
-
4
- export async function GET(
5
- request: NextRequest,
6
- { params }: { params: Promise<{ id: string }> }
7
- ) {
8
- try {
9
- const { id } = await params;
10
- const logs = activityService.getByTicketId(id);
11
- return NextResponse.json(logs);
12
- } catch (error) {
13
- console.error('Error fetching activity logs:', error);
14
- return NextResponse.json({ error: 'Failed to fetch activity logs' }, { status: 500 });
15
- }
16
- }
@@ -1,60 +0,0 @@
1
- import { NextRequest, NextResponse } from 'next/server';
2
- import { ticketService, activityService } from '@/lib/db';
3
-
4
- export async function GET(
5
- request: NextRequest,
6
- { params }: { params: Promise<{ id: string }> }
7
- ) {
8
- try {
9
- const { id } = await params;
10
- const ticket = ticketService.getById(id);
11
- if (!ticket) {
12
- return NextResponse.json({ error: 'Ticket not found' }, { status: 404 });
13
- }
14
-
15
- // Include activity logs
16
- const logs = activityService.getByTicketId(id);
17
-
18
- return NextResponse.json({ ...ticket, logs });
19
- } catch (error) {
20
- console.error('Error fetching ticket:', error);
21
- return NextResponse.json({ error: 'Failed to fetch ticket' }, { status: 500 });
22
- }
23
- }
24
-
25
- export async function PATCH(
26
- request: NextRequest,
27
- { params }: { params: Promise<{ id: string }> }
28
- ) {
29
- try {
30
- const { id } = await params;
31
- const body = await request.json();
32
- const { updated_by, ...data } = body;
33
-
34
- const ticket = ticketService.update(id, data, updated_by);
35
- if (!ticket) {
36
- return NextResponse.json({ error: 'Ticket not found' }, { status: 404 });
37
- }
38
- return NextResponse.json(ticket);
39
- } catch (error) {
40
- console.error('Error updating ticket:', error);
41
- return NextResponse.json({ error: 'Failed to update ticket' }, { status: 500 });
42
- }
43
- }
44
-
45
- export async function DELETE(
46
- request: NextRequest,
47
- { params }: { params: Promise<{ id: string }> }
48
- ) {
49
- try {
50
- const { id } = await params;
51
- const deleted = ticketService.delete(id);
52
- if (!deleted) {
53
- return NextResponse.json({ error: 'Ticket not found' }, { status: 404 });
54
- }
55
- return NextResponse.json({ success: true });
56
- } catch (error) {
57
- console.error('Error deleting ticket:', error);
58
- return NextResponse.json({ error: 'Failed to delete ticket' }, { status: 500 });
59
- }
60
- }
@@ -1,16 +0,0 @@
1
- import { NextRequest, NextResponse } from 'next/server';
2
- import { agentWorkLogService } from '@/lib/db';
3
-
4
- export async function GET(
5
- request: NextRequest,
6
- { params }: { params: Promise<{ id: string }> }
7
- ) {
8
- try {
9
- const { id } = await params;
10
- const logs = agentWorkLogService.getByTicketId(id);
11
- return NextResponse.json(logs);
12
- } catch (error) {
13
- console.error('Error fetching work logs:', error);
14
- return NextResponse.json({ error: 'Failed to fetch work logs' }, { status: 500 });
15
- }
16
- }
@@ -1,37 +0,0 @@
1
- import { NextRequest, NextResponse } from 'next/server';
2
- import { ticketService } from '@/lib/db';
3
-
4
- export async function GET(request: NextRequest) {
5
- try {
6
- const { searchParams } = new URL(request.url);
7
- const status = searchParams.get('status') || undefined;
8
- const projectId = searchParams.get('projectId') || undefined;
9
- const tickets = ticketService.getAll(status, projectId);
10
- return NextResponse.json(tickets);
11
- } catch (error) {
12
- console.error('Error fetching tickets:', error);
13
- return NextResponse.json({ error: 'Failed to fetch tickets' }, { status: 500 });
14
- }
15
- }
16
-
17
- export async function POST(request: NextRequest) {
18
- try {
19
- const body = await request.json();
20
- if (!body.title) {
21
- return NextResponse.json({ error: 'Title is required' }, { status: 400 });
22
- }
23
- const ticket = ticketService.create({
24
- title: body.title,
25
- description: body.description,
26
- priority: body.priority,
27
- assignee_id: body.assignee_id,
28
- project_id: body.project_id,
29
- created_by: body.created_by,
30
- });
31
- return NextResponse.json(ticket, { status: 201 });
32
- } catch (error) {
33
- console.error('Error creating ticket:', error);
34
- return NextResponse.json({ error: 'Failed to create ticket' }, { status: 500 });
35
- }
36
- }
37
-