olly-molly 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 (78) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +182 -0
  3. package/app/api/agent/execute/route.ts +157 -0
  4. package/app/api/agent/status/route.ts +38 -0
  5. package/app/api/check-api-key/route.ts +12 -0
  6. package/app/api/conversations/[id]/route.ts +35 -0
  7. package/app/api/conversations/route.ts +24 -0
  8. package/app/api/members/[id]/route.ts +37 -0
  9. package/app/api/members/route.ts +12 -0
  10. package/app/api/pm/breakdown/route.ts +142 -0
  11. package/app/api/pm/tickets/route.ts +147 -0
  12. package/app/api/projects/[id]/route.ts +56 -0
  13. package/app/api/projects/active/route.ts +15 -0
  14. package/app/api/projects/route.ts +53 -0
  15. package/app/api/tickets/[id]/logs/route.ts +16 -0
  16. package/app/api/tickets/[id]/route.ts +60 -0
  17. package/app/api/tickets/[id]/work-logs/route.ts +16 -0
  18. package/app/api/tickets/route.ts +37 -0
  19. package/app/design-system/page.tsx +242 -0
  20. package/app/favicon.ico +0 -0
  21. package/app/globals.css +318 -0
  22. package/app/layout.tsx +37 -0
  23. package/app/page.tsx +331 -0
  24. package/bin/cli.js +66 -0
  25. package/components/ThemeProvider.tsx +56 -0
  26. package/components/ThemeToggle.tsx +31 -0
  27. package/components/activity/ActivityLog.tsx +96 -0
  28. package/components/activity/index.ts +1 -0
  29. package/components/kanban/ConversationList.tsx +75 -0
  30. package/components/kanban/ConversationView.tsx +132 -0
  31. package/components/kanban/KanbanBoard.tsx +179 -0
  32. package/components/kanban/KanbanColumn.tsx +80 -0
  33. package/components/kanban/SortableTicket.tsx +58 -0
  34. package/components/kanban/TicketCard.tsx +98 -0
  35. package/components/kanban/TicketModal.tsx +510 -0
  36. package/components/kanban/TicketSidebar.tsx +448 -0
  37. package/components/kanban/index.ts +8 -0
  38. package/components/pm/PMRequestModal.tsx +196 -0
  39. package/components/pm/index.ts +1 -0
  40. package/components/project/ProjectSelector.tsx +211 -0
  41. package/components/project/index.ts +1 -0
  42. package/components/team/MemberCard.tsx +147 -0
  43. package/components/team/TeamPanel.tsx +57 -0
  44. package/components/team/index.ts +2 -0
  45. package/components/ui/ApiKeyModal.tsx +101 -0
  46. package/components/ui/Avatar.tsx +95 -0
  47. package/components/ui/Badge.tsx +59 -0
  48. package/components/ui/Button.tsx +60 -0
  49. package/components/ui/Card.tsx +64 -0
  50. package/components/ui/Input.tsx +41 -0
  51. package/components/ui/Modal.tsx +76 -0
  52. package/components/ui/ResizablePane.tsx +97 -0
  53. package/components/ui/Select.tsx +45 -0
  54. package/components/ui/Textarea.tsx +41 -0
  55. package/components/ui/index.ts +8 -0
  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 +26 -0
  60. package/db/schema-projects.sql +29 -0
  61. package/db/schema.sql +94 -0
  62. package/lib/agent-jobs.ts +232 -0
  63. package/lib/db.ts +564 -0
  64. package/next.config.ts +10 -0
  65. package/package.json +80 -0
  66. package/postcss.config.mjs +7 -0
  67. package/public/app-icon.png +0 -0
  68. package/public/file.svg +1 -0
  69. package/public/globe.svg +1 -0
  70. package/public/next.svg +1 -0
  71. package/public/profiles/designer.png +0 -0
  72. package/public/profiles/dev-backend.png +0 -0
  73. package/public/profiles/dev-frontend.png +0 -0
  74. package/public/profiles/pm.png +0 -0
  75. package/public/profiles/qa.png +0 -0
  76. package/public/vercel.svg +1 -0
  77. package/public/window.svg +1 -0
  78. package/tsconfig.json +34 -0
@@ -0,0 +1,80 @@
1
+ 'use client';
2
+
3
+ import { useDroppable } from '@dnd-kit/core';
4
+ import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
5
+ import { SortableTicket } from './SortableTicket';
6
+
7
+ interface Ticket {
8
+ id: string;
9
+ title: string;
10
+ description?: string | null;
11
+ status: string;
12
+ priority: string;
13
+ assignee?: {
14
+ id: string;
15
+ name: string;
16
+ avatar?: string | null;
17
+ role: string;
18
+ system_prompt: string;
19
+ } | null;
20
+ }
21
+
22
+ interface KanbanColumnProps {
23
+ id: string;
24
+ title: string;
25
+ tickets: Ticket[];
26
+ color: string;
27
+ icon: string;
28
+ onTicketClick: (ticket: Ticket) => void;
29
+ runningTicketIds?: string[];
30
+ }
31
+
32
+ export function KanbanColumn({ id, title, tickets, color, icon, onTicketClick, runningTicketIds = [] }: KanbanColumnProps) {
33
+ const { setNodeRef, isOver } = useDroppable({ id });
34
+
35
+ return (
36
+ <div
37
+ ref={setNodeRef}
38
+ className={`
39
+ flex-1 min-w-[260px]
40
+ flex flex-col max-h-[calc(100vh-180px)]
41
+ border-r border-[var(--border-primary)] last:border-r-0
42
+ transition-colors duration-150
43
+ ${isOver ? 'bg-[var(--bg-secondary)]' : 'bg-transparent'}
44
+ `}
45
+ >
46
+ {/* Column Header */}
47
+ <div className="px-4 py-3 border-b border-[var(--border-primary)]">
48
+ <div className="flex items-center gap-2">
49
+ <span className="text-sm">{icon}</span>
50
+ <h3 className={`text-xs font-medium uppercase tracking-wider ${color}`}>
51
+ {title}
52
+ </h3>
53
+ <span className="ml-auto text-xs text-[var(--text-muted)]">
54
+ {tickets.length}
55
+ </span>
56
+ </div>
57
+ </div>
58
+
59
+ {/* Tickets */}
60
+ <div className="flex-1 overflow-y-auto">
61
+ <SortableContext items={tickets.map(t => t.id)} strategy={verticalListSortingStrategy}>
62
+ {tickets.map((ticket) => (
63
+ <SortableTicket
64
+ key={ticket.id}
65
+ ticket={ticket}
66
+ onTicketClick={onTicketClick}
67
+ isRunning={runningTicketIds.includes(ticket.id)}
68
+ />
69
+ ))}
70
+ </SortableContext>
71
+
72
+ {tickets.length === 0 && (
73
+ <div className="flex items-center justify-center py-12 text-[var(--text-muted)] text-xs">
74
+ No tickets
75
+ </div>
76
+ )}
77
+ </div>
78
+ </div>
79
+ );
80
+ }
@@ -0,0 +1,58 @@
1
+ 'use client';
2
+
3
+ import { useSortable } from '@dnd-kit/sortable';
4
+ import { CSS } from '@dnd-kit/utilities';
5
+ import { TicketCard } from './TicketCard';
6
+
7
+ interface Ticket {
8
+ id: string;
9
+ title: string;
10
+ description?: string | null;
11
+ status: string;
12
+ priority: string;
13
+ assignee?: {
14
+ id: string;
15
+ name: string;
16
+ avatar?: string | null;
17
+ role: string;
18
+ system_prompt: string;
19
+ } | null;
20
+ }
21
+
22
+ interface SortableTicketProps {
23
+ ticket: Ticket;
24
+ onTicketClick: (ticket: Ticket) => void;
25
+ isRunning?: boolean;
26
+ }
27
+
28
+ export function SortableTicket({ ticket, onTicketClick, isRunning }: SortableTicketProps) {
29
+ const {
30
+ attributes,
31
+ listeners,
32
+ setNodeRef,
33
+ transform,
34
+ transition,
35
+ isDragging,
36
+ } = useSortable({ id: ticket.id });
37
+
38
+ const style = {
39
+ transform: CSS.Transform.toString(transform),
40
+ transition,
41
+ };
42
+
43
+ return (
44
+ <div
45
+ ref={setNodeRef}
46
+ style={style}
47
+ {...attributes}
48
+ {...listeners}
49
+ >
50
+ <TicketCard
51
+ ticket={ticket}
52
+ onClick={() => !isDragging && onTicketClick(ticket)}
53
+ isDragging={isDragging}
54
+ isRunning={isRunning}
55
+ />
56
+ </div>
57
+ );
58
+ }
@@ -0,0 +1,98 @@
1
+ 'use client';
2
+
3
+ import { Avatar } from '@/components/ui/Avatar';
4
+ import { PriorityBadge } from '@/components/ui/Badge';
5
+
6
+ interface Ticket {
7
+ id: string;
8
+ title: string;
9
+ description?: string | null;
10
+ status: string;
11
+ priority: string;
12
+ assignee?: {
13
+ id: string;
14
+ name: string;
15
+ avatar?: string | null;
16
+ role: string;
17
+ system_prompt: string;
18
+ } | null;
19
+ }
20
+
21
+ interface TicketCardProps {
22
+ ticket: Ticket;
23
+ onClick: () => void;
24
+ isDragging?: boolean;
25
+ isRunning?: boolean;
26
+ }
27
+
28
+ export function TicketCard({ ticket, onClick, isDragging, isRunning }: TicketCardProps) {
29
+ const roleImages: Record<string, string> = {
30
+ PM: '/profiles/pm.png',
31
+ FE_DEV: '/profiles/dev-frontend.png',
32
+ BACKEND_DEV: '/profiles/dev-backend.png',
33
+ QA: '/profiles/qa.png',
34
+ };
35
+
36
+ const profileImage = ticket.assignee ? roleImages[ticket.assignee.role] : undefined;
37
+
38
+ return (
39
+ <div
40
+ onClick={onClick}
41
+ className={`
42
+ px-4 py-3 border-b border-[var(--border-primary)] cursor-pointer
43
+ transition-colors duration-150
44
+ hover:bg-[var(--bg-secondary)]
45
+ ${isDragging ? 'opacity-50 bg-[var(--bg-secondary)]' : ''}
46
+ ${isRunning ? 'bg-[var(--status-progress)]/30' : ''}
47
+ `}
48
+ >
49
+ <div className="flex items-start gap-3">
50
+ {/* Left: Assignee Avatar */}
51
+ <div className="flex-shrink-0 pt-0.5">
52
+ {ticket.assignee ? (
53
+ <Avatar
54
+ name={ticket.assignee.name}
55
+ src={profileImage}
56
+ emoji={!profileImage ? ticket.assignee.avatar : undefined}
57
+ badge={profileImage ? ticket.assignee.avatar : undefined}
58
+ size="sm"
59
+ />
60
+ ) : (
61
+ <div className="w-6 h-6 border border-dashed border-[var(--border-secondary)] flex items-center justify-center text-[var(--text-muted)] text-xs">
62
+ ?
63
+ </div>
64
+ )}
65
+ </div>
66
+
67
+ {/* Right: Content */}
68
+ <div className="flex-1 min-w-0">
69
+ <div className="flex items-start justify-between gap-2">
70
+ <h4 className="text-sm text-[var(--text-primary)] line-clamp-2 leading-snug">
71
+ {isRunning && (
72
+ <span className="inline-block w-1.5 h-1.5 bg-[var(--status-progress-text)] rounded-full gentle-pulse mr-1.5 align-middle" />
73
+ )}
74
+ {ticket.title}
75
+ </h4>
76
+ <PriorityBadge priority={ticket.priority} />
77
+ </div>
78
+
79
+ {ticket.description && (
80
+ <p className="text-xs text-[var(--text-muted)] mt-1 line-clamp-1">
81
+ {ticket.description}
82
+ </p>
83
+ )}
84
+
85
+ {(ticket.assignee || isRunning) && (
86
+ <p className="text-xs text-[var(--text-muted)] mt-1.5">
87
+ {isRunning ? (
88
+ <span className="text-[var(--status-progress-text)]">Working...</span>
89
+ ) : (
90
+ ticket.assignee?.name
91
+ )}
92
+ </p>
93
+ )}
94
+ </div>
95
+ </div>
96
+ </div>
97
+ );
98
+ }