prjct-cli 0.11.0 → 0.11.1
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 +11 -1
- package/packages/shared/dist/index.d.ts +615 -0
- package/packages/shared/dist/index.js +204 -0
- package/packages/shared/package.json +29 -0
- package/packages/shared/src/index.ts +9 -0
- package/packages/shared/src/schemas.ts +124 -0
- package/packages/shared/src/types.ts +187 -0
- package/packages/shared/src/utils.ts +148 -0
- package/packages/shared/tsconfig.json +18 -0
- package/packages/web/README.md +36 -0
- package/packages/web/app/api/claude/sessions/route.ts +44 -0
- package/packages/web/app/api/claude/status/route.ts +34 -0
- package/packages/web/app/api/projects/[id]/delete/route.ts +21 -0
- package/packages/web/app/api/projects/[id]/icon/route.ts +33 -0
- package/packages/web/app/api/projects/[id]/route.ts +29 -0
- package/packages/web/app/api/projects/[id]/stats/route.ts +36 -0
- package/packages/web/app/api/projects/[id]/status/route.ts +21 -0
- package/packages/web/app/api/projects/route.ts +16 -0
- package/packages/web/app/api/sessions/history/route.ts +122 -0
- package/packages/web/app/api/stats/route.ts +38 -0
- package/packages/web/app/error.tsx +34 -0
- package/packages/web/app/favicon.ico +0 -0
- package/packages/web/app/globals.css +155 -0
- package/packages/web/app/layout.tsx +43 -0
- package/packages/web/app/loading.tsx +7 -0
- package/packages/web/app/not-found.tsx +25 -0
- package/packages/web/app/page.tsx +227 -0
- package/packages/web/app/project/[id]/error.tsx +41 -0
- package/packages/web/app/project/[id]/loading.tsx +9 -0
- package/packages/web/app/project/[id]/not-found.tsx +27 -0
- package/packages/web/app/project/[id]/page.tsx +253 -0
- package/packages/web/app/project/[id]/stats/page.tsx +447 -0
- package/packages/web/app/sessions/page.tsx +165 -0
- package/packages/web/app/settings/page.tsx +150 -0
- package/packages/web/components/AppSidebar.tsx +113 -0
- package/packages/web/components/CommandButton.tsx +39 -0
- package/packages/web/components/ConnectionStatus.tsx +29 -0
- package/packages/web/components/Logo.tsx +65 -0
- package/packages/web/components/MarkdownContent.tsx +123 -0
- package/packages/web/components/ProjectAvatar.tsx +54 -0
- package/packages/web/components/TechStackBadges.tsx +20 -0
- package/packages/web/components/TerminalTab.tsx +84 -0
- package/packages/web/components/TerminalTabs.tsx +210 -0
- package/packages/web/components/charts/SessionsChart.tsx +172 -0
- package/packages/web/components/providers.tsx +45 -0
- package/packages/web/components/ui/alert-dialog.tsx +157 -0
- package/packages/web/components/ui/badge.tsx +46 -0
- package/packages/web/components/ui/button.tsx +60 -0
- package/packages/web/components/ui/card.tsx +92 -0
- package/packages/web/components/ui/chart.tsx +385 -0
- package/packages/web/components/ui/dropdown-menu.tsx +257 -0
- package/packages/web/components/ui/scroll-area.tsx +58 -0
- package/packages/web/components/ui/sheet.tsx +139 -0
- package/packages/web/components/ui/tabs.tsx +66 -0
- package/packages/web/components/ui/tooltip.tsx +61 -0
- package/packages/web/components.json +22 -0
- package/packages/web/context/TerminalContext.tsx +45 -0
- package/packages/web/context/TerminalTabsContext.tsx +136 -0
- package/packages/web/eslint.config.mjs +18 -0
- package/packages/web/hooks/useClaudeTerminal.ts +375 -0
- package/packages/web/hooks/useProjectStats.ts +38 -0
- package/packages/web/hooks/useProjects.ts +73 -0
- package/packages/web/hooks/useStats.ts +28 -0
- package/packages/web/lib/format.ts +23 -0
- package/packages/web/lib/parse-prjct-files.ts +1122 -0
- package/packages/web/lib/projects.ts +452 -0
- package/packages/web/lib/pty.ts +101 -0
- package/packages/web/lib/query-config.ts +44 -0
- package/packages/web/lib/utils.ts +6 -0
- package/packages/web/next-env.d.ts +6 -0
- package/packages/web/next.config.ts +7 -0
- package/packages/web/package.json +53 -0
- package/packages/web/postcss.config.mjs +7 -0
- package/packages/web/public/file.svg +1 -0
- package/packages/web/public/globe.svg +1 -0
- package/packages/web/public/next.svg +1 -0
- package/packages/web/public/vercel.svg +1 -0
- package/packages/web/public/window.svg +1 -0
- package/packages/web/server.ts +262 -0
- package/packages/web/tsconfig.json +34 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
// src/schemas.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
var SessionMetricsSchema = z.object({
|
|
4
|
+
filesChanged: z.number().default(0),
|
|
5
|
+
linesAdded: z.number().default(0),
|
|
6
|
+
linesRemoved: z.number().default(0),
|
|
7
|
+
commits: z.number().default(0),
|
|
8
|
+
snapshots: z.array(z.string()).default([])
|
|
9
|
+
});
|
|
10
|
+
var TimelineEventSchema = z.object({
|
|
11
|
+
type: z.enum(["start", "pause", "resume", "complete", "snapshot"]),
|
|
12
|
+
at: z.string().datetime(),
|
|
13
|
+
data: z.record(z.unknown()).optional()
|
|
14
|
+
});
|
|
15
|
+
var SessionSchema = z.object({
|
|
16
|
+
id: z.string().regex(/^sess_[a-z0-9]{8}$/),
|
|
17
|
+
projectId: z.string(),
|
|
18
|
+
task: z.string().min(1),
|
|
19
|
+
status: z.enum(["active", "paused", "completed"]),
|
|
20
|
+
startedAt: z.string().datetime(),
|
|
21
|
+
pausedAt: z.string().datetime().nullable(),
|
|
22
|
+
completedAt: z.string().datetime().nullable(),
|
|
23
|
+
duration: z.number().min(0),
|
|
24
|
+
metrics: SessionMetricsSchema,
|
|
25
|
+
timeline: z.array(TimelineEventSchema)
|
|
26
|
+
});
|
|
27
|
+
var TaskSchema = z.object({
|
|
28
|
+
id: z.string(),
|
|
29
|
+
title: z.string().min(1),
|
|
30
|
+
description: z.string().optional(),
|
|
31
|
+
status: z.enum(["pending", "in_progress", "completed", "blocked"]),
|
|
32
|
+
priority: z.enum(["low", "medium", "high", "critical"]),
|
|
33
|
+
createdAt: z.string().datetime(),
|
|
34
|
+
completedAt: z.string().datetime().optional(),
|
|
35
|
+
duration: z.number().optional(),
|
|
36
|
+
tags: z.array(z.string()).optional()
|
|
37
|
+
});
|
|
38
|
+
var IdeaSchema = z.object({
|
|
39
|
+
id: z.string(),
|
|
40
|
+
content: z.string().min(1),
|
|
41
|
+
capturedAt: z.string().datetime(),
|
|
42
|
+
source: z.string().optional(),
|
|
43
|
+
promoted: z.boolean().optional(),
|
|
44
|
+
promotedTo: z.string().optional()
|
|
45
|
+
});
|
|
46
|
+
var FeatureSchema = z.object({
|
|
47
|
+
id: z.string(),
|
|
48
|
+
title: z.string().min(1),
|
|
49
|
+
description: z.string().optional(),
|
|
50
|
+
status: z.enum(["planned", "in_progress", "shipped", "cancelled"]),
|
|
51
|
+
priority: z.number().min(1),
|
|
52
|
+
createdAt: z.string().datetime(),
|
|
53
|
+
shippedAt: z.string().datetime().optional(),
|
|
54
|
+
tasks: z.array(TaskSchema).optional(),
|
|
55
|
+
version: z.string().optional()
|
|
56
|
+
});
|
|
57
|
+
var ProjectConfigSchema = z.object({
|
|
58
|
+
projectId: z.string(),
|
|
59
|
+
name: z.string().optional(),
|
|
60
|
+
plugins: z.array(z.string()).optional()
|
|
61
|
+
}).passthrough();
|
|
62
|
+
var WSInputMessageSchema = z.object({
|
|
63
|
+
type: z.literal("input"),
|
|
64
|
+
payload: z.object({
|
|
65
|
+
data: z.string()
|
|
66
|
+
}),
|
|
67
|
+
timestamp: z.string().datetime()
|
|
68
|
+
});
|
|
69
|
+
var WSResizeMessageSchema = z.object({
|
|
70
|
+
type: z.literal("resize"),
|
|
71
|
+
payload: z.object({
|
|
72
|
+
cols: z.number().min(1),
|
|
73
|
+
rows: z.number().min(1)
|
|
74
|
+
}),
|
|
75
|
+
timestamp: z.string().datetime()
|
|
76
|
+
});
|
|
77
|
+
var WSMessageSchema = z.discriminatedUnion("type", [
|
|
78
|
+
WSInputMessageSchema,
|
|
79
|
+
WSResizeMessageSchema
|
|
80
|
+
]);
|
|
81
|
+
var CreateSessionRequestSchema = z.object({
|
|
82
|
+
task: z.string().min(1).max(200),
|
|
83
|
+
projectId: z.string()
|
|
84
|
+
});
|
|
85
|
+
var CreateTaskRequestSchema = z.object({
|
|
86
|
+
title: z.string().min(1).max(200),
|
|
87
|
+
description: z.string().max(1e3).optional(),
|
|
88
|
+
priority: z.enum(["low", "medium", "high", "critical"]).default("medium")
|
|
89
|
+
});
|
|
90
|
+
var CaptureIdeaRequestSchema = z.object({
|
|
91
|
+
content: z.string().min(1).max(500),
|
|
92
|
+
source: z.string().optional()
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// src/utils.ts
|
|
96
|
+
function generateSessionId() {
|
|
97
|
+
const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
98
|
+
let id = "sess_";
|
|
99
|
+
for (let i = 0; i < 8; i++) {
|
|
100
|
+
id += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
101
|
+
}
|
|
102
|
+
return id;
|
|
103
|
+
}
|
|
104
|
+
function generateId(prefix = "id") {
|
|
105
|
+
const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
106
|
+
let id = `${prefix}_`;
|
|
107
|
+
for (let i = 0; i < 8; i++) {
|
|
108
|
+
id += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
109
|
+
}
|
|
110
|
+
return id;
|
|
111
|
+
}
|
|
112
|
+
function formatDuration(seconds) {
|
|
113
|
+
if (seconds < 60) return `${seconds}s`;
|
|
114
|
+
if (seconds < 3600) return `${Math.round(seconds / 60)}m`;
|
|
115
|
+
const hours = Math.floor(seconds / 3600);
|
|
116
|
+
const minutes = Math.round(seconds % 3600 / 60);
|
|
117
|
+
if (minutes === 0) return `${hours}h`;
|
|
118
|
+
return `${hours}h ${minutes}m`;
|
|
119
|
+
}
|
|
120
|
+
function parseDuration(duration) {
|
|
121
|
+
const match = duration.match(/(?:(\d+)h)?\s*(?:(\d+)m)?\s*(?:(\d+)s)?/);
|
|
122
|
+
if (!match) return 0;
|
|
123
|
+
const hours = parseInt(match[1] || "0", 10);
|
|
124
|
+
const minutes = parseInt(match[2] || "0", 10);
|
|
125
|
+
const secs = parseInt(match[3] || "0", 10);
|
|
126
|
+
return hours * 3600 + minutes * 60 + secs;
|
|
127
|
+
}
|
|
128
|
+
function getRelativeTime(date) {
|
|
129
|
+
const now = /* @__PURE__ */ new Date();
|
|
130
|
+
const then = typeof date === "string" ? new Date(date) : date;
|
|
131
|
+
const diffMs = now.getTime() - then.getTime();
|
|
132
|
+
const diffSecs = Math.floor(diffMs / 1e3);
|
|
133
|
+
const diffMins = Math.floor(diffSecs / 60);
|
|
134
|
+
const diffHours = Math.floor(diffMins / 60);
|
|
135
|
+
const diffDays = Math.floor(diffHours / 24);
|
|
136
|
+
if (diffSecs < 60) return "just now";
|
|
137
|
+
if (diffMins < 60) return `${diffMins}m ago`;
|
|
138
|
+
if (diffHours < 24) return `${diffHours}h ago`;
|
|
139
|
+
if (diffDays === 1) return "yesterday";
|
|
140
|
+
if (diffDays < 7) return `${diffDays}d ago`;
|
|
141
|
+
return then.toLocaleDateString();
|
|
142
|
+
}
|
|
143
|
+
function getTimestamp() {
|
|
144
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
145
|
+
}
|
|
146
|
+
function getDate() {
|
|
147
|
+
return (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
148
|
+
}
|
|
149
|
+
function getYearMonth() {
|
|
150
|
+
const now = /* @__PURE__ */ new Date();
|
|
151
|
+
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
|
|
152
|
+
}
|
|
153
|
+
function safeJsonParse(json, fallback) {
|
|
154
|
+
try {
|
|
155
|
+
return JSON.parse(json);
|
|
156
|
+
} catch {
|
|
157
|
+
return fallback;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function truncate(str, maxLength) {
|
|
161
|
+
if (str.length <= maxLength) return str;
|
|
162
|
+
return str.slice(0, maxLength - 3) + "...";
|
|
163
|
+
}
|
|
164
|
+
function slugify(str) {
|
|
165
|
+
return str.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
166
|
+
}
|
|
167
|
+
function deepClone(obj) {
|
|
168
|
+
return JSON.parse(JSON.stringify(obj));
|
|
169
|
+
}
|
|
170
|
+
function isNode() {
|
|
171
|
+
return typeof process !== "undefined" && process.versions?.node != null;
|
|
172
|
+
}
|
|
173
|
+
function isBrowser() {
|
|
174
|
+
return typeof window !== "undefined";
|
|
175
|
+
}
|
|
176
|
+
export {
|
|
177
|
+
CaptureIdeaRequestSchema,
|
|
178
|
+
CreateSessionRequestSchema,
|
|
179
|
+
CreateTaskRequestSchema,
|
|
180
|
+
FeatureSchema,
|
|
181
|
+
IdeaSchema,
|
|
182
|
+
ProjectConfigSchema,
|
|
183
|
+
SessionMetricsSchema,
|
|
184
|
+
SessionSchema,
|
|
185
|
+
TaskSchema,
|
|
186
|
+
TimelineEventSchema,
|
|
187
|
+
WSInputMessageSchema,
|
|
188
|
+
WSMessageSchema,
|
|
189
|
+
WSResizeMessageSchema,
|
|
190
|
+
deepClone,
|
|
191
|
+
formatDuration,
|
|
192
|
+
generateId,
|
|
193
|
+
generateSessionId,
|
|
194
|
+
getDate,
|
|
195
|
+
getRelativeTime,
|
|
196
|
+
getTimestamp,
|
|
197
|
+
getYearMonth,
|
|
198
|
+
isBrowser,
|
|
199
|
+
isNode,
|
|
200
|
+
parseDuration,
|
|
201
|
+
safeJsonParse,
|
|
202
|
+
slugify,
|
|
203
|
+
truncate
|
|
204
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@prjct/shared",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Shared types and utilities for prjct",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsup src/index.ts --format esm --dts",
|
|
17
|
+
"dev": "tsup src/index.ts --format esm --dts --watch",
|
|
18
|
+
"typecheck": "tsc --noEmit",
|
|
19
|
+
"lint": "eslint src"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"zod": "^3.23.8"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"esbuild": "^0.25.0",
|
|
26
|
+
"tsup": "^8.0.2",
|
|
27
|
+
"typescript": "^5.4.5"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod Schemas for validation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from 'zod'
|
|
6
|
+
|
|
7
|
+
// Session Schemas
|
|
8
|
+
export const SessionMetricsSchema = z.object({
|
|
9
|
+
filesChanged: z.number().default(0),
|
|
10
|
+
linesAdded: z.number().default(0),
|
|
11
|
+
linesRemoved: z.number().default(0),
|
|
12
|
+
commits: z.number().default(0),
|
|
13
|
+
snapshots: z.array(z.string()).default([])
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
export const TimelineEventSchema = z.object({
|
|
17
|
+
type: z.enum(['start', 'pause', 'resume', 'complete', 'snapshot']),
|
|
18
|
+
at: z.string().datetime(),
|
|
19
|
+
data: z.record(z.unknown()).optional()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
export const SessionSchema = z.object({
|
|
23
|
+
id: z.string().regex(/^sess_[a-z0-9]{8}$/),
|
|
24
|
+
projectId: z.string(),
|
|
25
|
+
task: z.string().min(1),
|
|
26
|
+
status: z.enum(['active', 'paused', 'completed']),
|
|
27
|
+
startedAt: z.string().datetime(),
|
|
28
|
+
pausedAt: z.string().datetime().nullable(),
|
|
29
|
+
completedAt: z.string().datetime().nullable(),
|
|
30
|
+
duration: z.number().min(0),
|
|
31
|
+
metrics: SessionMetricsSchema,
|
|
32
|
+
timeline: z.array(TimelineEventSchema)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
// Task Schemas
|
|
36
|
+
export const TaskSchema = z.object({
|
|
37
|
+
id: z.string(),
|
|
38
|
+
title: z.string().min(1),
|
|
39
|
+
description: z.string().optional(),
|
|
40
|
+
status: z.enum(['pending', 'in_progress', 'completed', 'blocked']),
|
|
41
|
+
priority: z.enum(['low', 'medium', 'high', 'critical']),
|
|
42
|
+
createdAt: z.string().datetime(),
|
|
43
|
+
completedAt: z.string().datetime().optional(),
|
|
44
|
+
duration: z.number().optional(),
|
|
45
|
+
tags: z.array(z.string()).optional()
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
// Idea Schemas
|
|
49
|
+
export const IdeaSchema = z.object({
|
|
50
|
+
id: z.string(),
|
|
51
|
+
content: z.string().min(1),
|
|
52
|
+
capturedAt: z.string().datetime(),
|
|
53
|
+
source: z.string().optional(),
|
|
54
|
+
promoted: z.boolean().optional(),
|
|
55
|
+
promotedTo: z.string().optional()
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
// Feature Schemas
|
|
59
|
+
export const FeatureSchema = z.object({
|
|
60
|
+
id: z.string(),
|
|
61
|
+
title: z.string().min(1),
|
|
62
|
+
description: z.string().optional(),
|
|
63
|
+
status: z.enum(['planned', 'in_progress', 'shipped', 'cancelled']),
|
|
64
|
+
priority: z.number().min(1),
|
|
65
|
+
createdAt: z.string().datetime(),
|
|
66
|
+
shippedAt: z.string().datetime().optional(),
|
|
67
|
+
tasks: z.array(TaskSchema).optional(),
|
|
68
|
+
version: z.string().optional()
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
// Project Config Schema
|
|
72
|
+
export const ProjectConfigSchema = z.object({
|
|
73
|
+
projectId: z.string(),
|
|
74
|
+
name: z.string().optional(),
|
|
75
|
+
plugins: z.array(z.string()).optional()
|
|
76
|
+
}).passthrough()
|
|
77
|
+
|
|
78
|
+
// WebSocket Message Schemas
|
|
79
|
+
export const WSInputMessageSchema = z.object({
|
|
80
|
+
type: z.literal('input'),
|
|
81
|
+
payload: z.object({
|
|
82
|
+
data: z.string()
|
|
83
|
+
}),
|
|
84
|
+
timestamp: z.string().datetime()
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
export const WSResizeMessageSchema = z.object({
|
|
88
|
+
type: z.literal('resize'),
|
|
89
|
+
payload: z.object({
|
|
90
|
+
cols: z.number().min(1),
|
|
91
|
+
rows: z.number().min(1)
|
|
92
|
+
}),
|
|
93
|
+
timestamp: z.string().datetime()
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
export const WSMessageSchema = z.discriminatedUnion('type', [
|
|
97
|
+
WSInputMessageSchema,
|
|
98
|
+
WSResizeMessageSchema
|
|
99
|
+
])
|
|
100
|
+
|
|
101
|
+
// API Request Schemas
|
|
102
|
+
export const CreateSessionRequestSchema = z.object({
|
|
103
|
+
task: z.string().min(1).max(200),
|
|
104
|
+
projectId: z.string()
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
export const CreateTaskRequestSchema = z.object({
|
|
108
|
+
title: z.string().min(1).max(200),
|
|
109
|
+
description: z.string().max(1000).optional(),
|
|
110
|
+
priority: z.enum(['low', 'medium', 'high', 'critical']).default('medium')
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
export const CaptureIdeaRequestSchema = z.object({
|
|
114
|
+
content: z.string().min(1).max(500),
|
|
115
|
+
source: z.string().optional()
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
// Inferred Types
|
|
119
|
+
export type SessionInput = z.infer<typeof SessionSchema>
|
|
120
|
+
export type TaskInput = z.infer<typeof TaskSchema>
|
|
121
|
+
export type IdeaInput = z.infer<typeof IdeaSchema>
|
|
122
|
+
export type FeatureInput = z.infer<typeof FeatureSchema>
|
|
123
|
+
export type ProjectConfigInput = z.infer<typeof ProjectConfigSchema>
|
|
124
|
+
export type WSMessageInput = z.infer<typeof WSMessageSchema>
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core Types for prjct
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Session Types
|
|
6
|
+
export interface Session {
|
|
7
|
+
id: string
|
|
8
|
+
projectId: string
|
|
9
|
+
task: string
|
|
10
|
+
status: 'active' | 'paused' | 'completed'
|
|
11
|
+
startedAt: string
|
|
12
|
+
pausedAt: string | null
|
|
13
|
+
completedAt: string | null
|
|
14
|
+
duration: number
|
|
15
|
+
metrics: SessionMetrics
|
|
16
|
+
timeline: TimelineEvent[]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface SessionMetrics {
|
|
20
|
+
filesChanged: number
|
|
21
|
+
linesAdded: number
|
|
22
|
+
linesRemoved: number
|
|
23
|
+
commits: number
|
|
24
|
+
snapshots: string[]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface TimelineEvent {
|
|
28
|
+
type: 'start' | 'pause' | 'resume' | 'complete' | 'snapshot'
|
|
29
|
+
at: string
|
|
30
|
+
data?: Record<string, unknown>
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Snapshot Types
|
|
34
|
+
export interface Snapshot {
|
|
35
|
+
hash: string
|
|
36
|
+
shortHash: string
|
|
37
|
+
message: string
|
|
38
|
+
timestamp: string
|
|
39
|
+
files: string[]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Task Types
|
|
43
|
+
export interface Task {
|
|
44
|
+
id: string
|
|
45
|
+
title: string
|
|
46
|
+
description?: string
|
|
47
|
+
status: 'pending' | 'in_progress' | 'completed' | 'blocked'
|
|
48
|
+
priority: 'low' | 'medium' | 'high' | 'critical'
|
|
49
|
+
createdAt: string
|
|
50
|
+
completedAt?: string
|
|
51
|
+
duration?: number
|
|
52
|
+
tags?: string[]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Idea Types
|
|
56
|
+
export interface Idea {
|
|
57
|
+
id: string
|
|
58
|
+
content: string
|
|
59
|
+
capturedAt: string
|
|
60
|
+
source?: string
|
|
61
|
+
promoted?: boolean
|
|
62
|
+
promotedTo?: string
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Feature Types
|
|
66
|
+
export interface Feature {
|
|
67
|
+
id: string
|
|
68
|
+
title: string
|
|
69
|
+
description?: string
|
|
70
|
+
status: 'planned' | 'in_progress' | 'shipped' | 'cancelled'
|
|
71
|
+
priority: number
|
|
72
|
+
createdAt: string
|
|
73
|
+
shippedAt?: string
|
|
74
|
+
tasks?: Task[]
|
|
75
|
+
version?: string
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Project Types
|
|
79
|
+
export interface Project {
|
|
80
|
+
id: string
|
|
81
|
+
name: string
|
|
82
|
+
path: string
|
|
83
|
+
createdAt: string
|
|
84
|
+
lastActiveAt: string
|
|
85
|
+
config: ProjectConfig
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface ProjectConfig {
|
|
89
|
+
projectId: string
|
|
90
|
+
name?: string
|
|
91
|
+
plugins?: string[]
|
|
92
|
+
[key: string]: unknown
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Metrics Types
|
|
96
|
+
export interface DailyMetrics {
|
|
97
|
+
date: string
|
|
98
|
+
sessions: number
|
|
99
|
+
duration: number
|
|
100
|
+
commits: number
|
|
101
|
+
filesChanged: number
|
|
102
|
+
linesAdded: number
|
|
103
|
+
linesRemoved: number
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface WeeklyMetrics {
|
|
107
|
+
weekStart: string
|
|
108
|
+
weekEnd: string
|
|
109
|
+
totalSessions: number
|
|
110
|
+
totalDuration: number
|
|
111
|
+
averageDuration: number
|
|
112
|
+
tasksCompleted: number
|
|
113
|
+
featuresShipped: number
|
|
114
|
+
productivityScore: number
|
|
115
|
+
streak: number
|
|
116
|
+
byDay: Record<string, DailyMetrics>
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// WebSocket Message Types
|
|
120
|
+
export interface WSMessage {
|
|
121
|
+
type: string
|
|
122
|
+
payload?: unknown
|
|
123
|
+
timestamp: string
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface WSInputMessage extends WSMessage {
|
|
127
|
+
type: 'input'
|
|
128
|
+
payload: {
|
|
129
|
+
data: string
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface WSOutputMessage extends WSMessage {
|
|
134
|
+
type: 'output'
|
|
135
|
+
payload: {
|
|
136
|
+
data: string
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface WSResizeMessage extends WSMessage {
|
|
141
|
+
type: 'resize'
|
|
142
|
+
payload: {
|
|
143
|
+
cols: number
|
|
144
|
+
rows: number
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface WSStatusMessage extends WSMessage {
|
|
149
|
+
type: 'status'
|
|
150
|
+
payload: {
|
|
151
|
+
status: 'connected' | 'disconnected' | 'error'
|
|
152
|
+
message?: string
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// API Response Types
|
|
157
|
+
export interface ApiResponse<T = unknown> {
|
|
158
|
+
success: boolean
|
|
159
|
+
data?: T
|
|
160
|
+
error?: string
|
|
161
|
+
timestamp: string
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Event Types (for event bus)
|
|
165
|
+
export type EventType =
|
|
166
|
+
| 'session.started'
|
|
167
|
+
| 'session.paused'
|
|
168
|
+
| 'session.resumed'
|
|
169
|
+
| 'session.completed'
|
|
170
|
+
| 'task.created'
|
|
171
|
+
| 'task.completed'
|
|
172
|
+
| 'feature.added'
|
|
173
|
+
| 'feature.shipped'
|
|
174
|
+
| 'idea.captured'
|
|
175
|
+
| 'snapshot.created'
|
|
176
|
+
| 'snapshot.restored'
|
|
177
|
+
| 'git.commit'
|
|
178
|
+
| 'git.push'
|
|
179
|
+
| 'project.init'
|
|
180
|
+
| 'project.sync'
|
|
181
|
+
|
|
182
|
+
export interface EventPayload {
|
|
183
|
+
type: EventType
|
|
184
|
+
timestamp: string
|
|
185
|
+
projectId: string
|
|
186
|
+
data: Record<string, unknown>
|
|
187
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Generate a unique session ID
|
|
7
|
+
*/
|
|
8
|
+
export function generateSessionId(): string {
|
|
9
|
+
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
|
|
10
|
+
let id = 'sess_'
|
|
11
|
+
for (let i = 0; i < 8; i++) {
|
|
12
|
+
id += chars.charAt(Math.floor(Math.random() * chars.length))
|
|
13
|
+
}
|
|
14
|
+
return id
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Generate a unique ID with prefix
|
|
19
|
+
*/
|
|
20
|
+
export function generateId(prefix: string = 'id'): string {
|
|
21
|
+
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
|
|
22
|
+
let id = `${prefix}_`
|
|
23
|
+
for (let i = 0; i < 8; i++) {
|
|
24
|
+
id += chars.charAt(Math.floor(Math.random() * chars.length))
|
|
25
|
+
}
|
|
26
|
+
return id
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Format duration in seconds to human readable
|
|
31
|
+
*/
|
|
32
|
+
export function formatDuration(seconds: number): string {
|
|
33
|
+
if (seconds < 60) return `${seconds}s`
|
|
34
|
+
if (seconds < 3600) return `${Math.round(seconds / 60)}m`
|
|
35
|
+
|
|
36
|
+
const hours = Math.floor(seconds / 3600)
|
|
37
|
+
const minutes = Math.round((seconds % 3600) / 60)
|
|
38
|
+
|
|
39
|
+
if (minutes === 0) return `${hours}h`
|
|
40
|
+
return `${hours}h ${minutes}m`
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Parse duration string to seconds
|
|
45
|
+
*/
|
|
46
|
+
export function parseDuration(duration: string): number {
|
|
47
|
+
const match = duration.match(/(?:(\d+)h)?\s*(?:(\d+)m)?\s*(?:(\d+)s)?/)
|
|
48
|
+
if (!match) return 0
|
|
49
|
+
|
|
50
|
+
const hours = parseInt(match[1] || '0', 10)
|
|
51
|
+
const minutes = parseInt(match[2] || '0', 10)
|
|
52
|
+
const secs = parseInt(match[3] || '0', 10)
|
|
53
|
+
|
|
54
|
+
return hours * 3600 + minutes * 60 + secs
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get relative time string
|
|
59
|
+
*/
|
|
60
|
+
export function getRelativeTime(date: Date | string): string {
|
|
61
|
+
const now = new Date()
|
|
62
|
+
const then = typeof date === 'string' ? new Date(date) : date
|
|
63
|
+
const diffMs = now.getTime() - then.getTime()
|
|
64
|
+
const diffSecs = Math.floor(diffMs / 1000)
|
|
65
|
+
const diffMins = Math.floor(diffSecs / 60)
|
|
66
|
+
const diffHours = Math.floor(diffMins / 60)
|
|
67
|
+
const diffDays = Math.floor(diffHours / 24)
|
|
68
|
+
|
|
69
|
+
if (diffSecs < 60) return 'just now'
|
|
70
|
+
if (diffMins < 60) return `${diffMins}m ago`
|
|
71
|
+
if (diffHours < 24) return `${diffHours}h ago`
|
|
72
|
+
if (diffDays === 1) return 'yesterday'
|
|
73
|
+
if (diffDays < 7) return `${diffDays}d ago`
|
|
74
|
+
|
|
75
|
+
return then.toLocaleDateString()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Get ISO timestamp
|
|
80
|
+
*/
|
|
81
|
+
export function getTimestamp(): string {
|
|
82
|
+
return new Date().toISOString()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get date in YYYY-MM-DD format
|
|
87
|
+
*/
|
|
88
|
+
export function getDate(): string {
|
|
89
|
+
return new Date().toISOString().split('T')[0]
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get year-month in YYYY-MM format
|
|
94
|
+
*/
|
|
95
|
+
export function getYearMonth(): string {
|
|
96
|
+
const now = new Date()
|
|
97
|
+
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Safely parse JSON
|
|
102
|
+
*/
|
|
103
|
+
export function safeJsonParse<T>(json: string, fallback: T): T {
|
|
104
|
+
try {
|
|
105
|
+
return JSON.parse(json) as T
|
|
106
|
+
} catch {
|
|
107
|
+
return fallback
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Truncate string with ellipsis
|
|
113
|
+
*/
|
|
114
|
+
export function truncate(str: string, maxLength: number): string {
|
|
115
|
+
if (str.length <= maxLength) return str
|
|
116
|
+
return str.slice(0, maxLength - 3) + '...'
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Slugify string
|
|
121
|
+
*/
|
|
122
|
+
export function slugify(str: string): string {
|
|
123
|
+
return str
|
|
124
|
+
.toLowerCase()
|
|
125
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
126
|
+
.replace(/^-|-$/g, '')
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Deep clone object
|
|
131
|
+
*/
|
|
132
|
+
export function deepClone<T>(obj: T): T {
|
|
133
|
+
return JSON.parse(JSON.stringify(obj))
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Check if running in Node.js
|
|
138
|
+
*/
|
|
139
|
+
export function isNode(): boolean {
|
|
140
|
+
return typeof process !== 'undefined' && process.versions?.node != null
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Check if running in browser
|
|
145
|
+
*/
|
|
146
|
+
export function isBrowser(): boolean {
|
|
147
|
+
return typeof window !== 'undefined'
|
|
148
|
+
}
|