create-claude-pipeline 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.
- package/bin/cli.js +359 -0
- package/package.json +32 -0
- package/template/.claude/agents/be-developer.md +218 -0
- package/template/.claude/agents/designer.md +192 -0
- package/template/.claude/agents/fe-developer.md +175 -0
- package/template/.claude/agents/infra-developer.md +270 -0
- package/template/.claude/agents/planner.md +126 -0
- package/template/.claude/agents/pm.md +130 -0
- package/template/.claude/agents/qa-engineer.md +270 -0
- package/template/.claude/agents/security-reviewer.md +281 -0
- package/template/.claude/settings.json +5 -0
- package/template/.claude/skills/analyze-requirements/SKILL.md +166 -0
- package/template/.claude/skills/api-integration/SKILL.md +354 -0
- package/template/.claude/skills/assemble-context/SKILL.md +192 -0
- package/template/.claude/skills/db-migration/SKILL.md +228 -0
- package/template/.claude/skills/explore-be-codebase/SKILL.md +260 -0
- package/template/.claude/skills/explore-codebase/SKILL.md +190 -0
- package/template/.claude/skills/explore-design-system/SKILL.md +150 -0
- package/template/.claude/skills/explore-fe-codebase/SKILL.md +209 -0
- package/template/.claude/skills/explore-implementation/SKILL.md +147 -0
- package/template/.claude/skills/explore-infra/SKILL.md +242 -0
- package/template/.claude/skills/implement-api/SKILL.md +477 -0
- package/template/.claude/skills/implement-components/SKILL.md +217 -0
- package/template/.claude/skills/review-auth/SKILL.md +175 -0
- package/template/.claude/skills/scan-vulnerabilities/SKILL.md +200 -0
- package/template/.claude/skills/write-cicd/SKILL.md +293 -0
- package/template/.claude/skills/write-design-spec/SKILL.md +363 -0
- package/template/.claude/skills/write-dockerfile/SKILL.md +269 -0
- package/template/.claude/skills/write-plan-doc/SKILL.md +164 -0
- package/template/.claude/skills/write-plan-doc/assets/plan_template.html +251 -0
- package/template/.claude/skills/write-qa-report/SKILL.md +151 -0
- package/template/.claude/skills/write-security-report/SKILL.md +185 -0
- package/template/.claude/skills/write-test-cases/SKILL.md +234 -0
- package/template/.claude-pipeline/dashboard/.env.example +1 -0
- package/template/.claude-pipeline/dashboard/.eslintrc.json +3 -0
- package/template/.claude-pipeline/dashboard/README.md +36 -0
- package/template/.claude-pipeline/dashboard/next.config.mjs +6 -0
- package/template/.claude-pipeline/dashboard/package-lock.json +8148 -0
- package/template/.claude-pipeline/dashboard/package.json +36 -0
- package/template/.claude-pipeline/dashboard/postcss.config.mjs +8 -0
- package/template/.claude-pipeline/dashboard/server.ts +24 -0
- package/template/.claude-pipeline/dashboard/src/app/api/pipelines/[id]/checkpoint/route.ts +23 -0
- package/template/.claude-pipeline/dashboard/src/app/api/pipelines/[id]/outputs/[...filepath]/route.ts +18 -0
- package/template/.claude-pipeline/dashboard/src/app/api/pipelines/[id]/route.ts +10 -0
- package/template/.claude-pipeline/dashboard/src/app/api/pipelines/route.ts +64 -0
- package/template/.claude-pipeline/dashboard/src/app/favicon.ico +0 -0
- package/template/.claude-pipeline/dashboard/src/app/fonts/GeistMonoVF.woff +0 -0
- package/template/.claude-pipeline/dashboard/src/app/fonts/GeistVF.woff +0 -0
- package/template/.claude-pipeline/dashboard/src/app/globals.css +52 -0
- package/template/.claude-pipeline/dashboard/src/app/layout.tsx +33 -0
- package/template/.claude-pipeline/dashboard/src/app/page.tsx +49 -0
- package/template/.claude-pipeline/dashboard/src/app/pipeline/[id]/page.tsx +84 -0
- package/template/.claude-pipeline/dashboard/src/components/agent-card.tsx +40 -0
- package/template/.claude-pipeline/dashboard/src/components/agent-logs.tsx +65 -0
- package/template/.claude-pipeline/dashboard/src/components/artifact-viewer.tsx +130 -0
- package/template/.claude-pipeline/dashboard/src/components/checkpoint-banner.tsx +59 -0
- package/template/.claude-pipeline/dashboard/src/components/new-pipeline-modal.tsx +63 -0
- package/template/.claude-pipeline/dashboard/src/components/output-list.tsx +57 -0
- package/template/.claude-pipeline/dashboard/src/components/phase-dots.tsx +37 -0
- package/template/.claude-pipeline/dashboard/src/components/pipeline-card.tsx +53 -0
- package/template/.claude-pipeline/dashboard/src/components/resizable-panels.tsx +91 -0
- package/template/.claude-pipeline/dashboard/src/hooks/use-pipeline-detail.ts +65 -0
- package/template/.claude-pipeline/dashboard/src/hooks/use-pipelines.ts +60 -0
- package/template/.claude-pipeline/dashboard/src/hooks/use-websocket.ts +58 -0
- package/template/.claude-pipeline/dashboard/src/lib/agents.ts +30 -0
- package/template/.claude-pipeline/dashboard/src/lib/checkpoint.ts +37 -0
- package/template/.claude-pipeline/dashboard/src/lib/pipelines.ts +91 -0
- package/template/.claude-pipeline/dashboard/src/lib/watcher.ts +90 -0
- package/template/.claude-pipeline/dashboard/src/lib/ws-server.ts +123 -0
- package/template/.claude-pipeline/dashboard/src/types/pipeline.ts +61 -0
- package/template/.claude-pipeline/dashboard/tailwind.config.ts +31 -0
- package/template/.claude-pipeline/dashboard/tsconfig.json +26 -0
- package/template/CLAUDE.md +301 -0
- package/template/references/context-structure.md +34 -0
- package/template/references/pm-context-assembly.md +34 -0
- package/template/references/task-context-template.md +65 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import type { PipelineState, PipelineSummary } from "@/types/pipeline";
|
|
4
|
+
|
|
5
|
+
const PIPELINES_DIR = process.env.PIPELINES_DIR;
|
|
6
|
+
if (!PIPELINES_DIR) {
|
|
7
|
+
throw new Error("PIPELINES_DIR 환경변수가 설정되지 않았습니다. npx create-claude-pipeline으로 실행해주세요.");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function getPipelinesDir(): string {
|
|
11
|
+
return path.resolve(PIPELINES_DIR);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function getPipelineDir(id: string): string {
|
|
15
|
+
return path.join(getPipelinesDir(), id);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function readPipelineState(id: string): PipelineState | null {
|
|
19
|
+
const filePath = path.join(getPipelineDir(id), "state.json");
|
|
20
|
+
try {
|
|
21
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
22
|
+
return JSON.parse(raw) as PipelineState;
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function listPipelines(): PipelineSummary[] {
|
|
29
|
+
const dir = getPipelinesDir();
|
|
30
|
+
if (!fs.existsSync(dir)) return [];
|
|
31
|
+
|
|
32
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
33
|
+
const summaries: PipelineSummary[] = [];
|
|
34
|
+
|
|
35
|
+
for (const entry of entries) {
|
|
36
|
+
if (!entry.isDirectory()) continue;
|
|
37
|
+
const state = readPipelineState(entry.name);
|
|
38
|
+
if (!state) continue;
|
|
39
|
+
summaries.push({
|
|
40
|
+
id: state.id,
|
|
41
|
+
requirements: state.requirements,
|
|
42
|
+
status: state.status,
|
|
43
|
+
currentPhase: state.currentPhase,
|
|
44
|
+
createdAt: state.createdAt,
|
|
45
|
+
agents: state.agents,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return summaries.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function readOutputFile(pipelineId: string, filepath: string): { content: string; contentType: string } | { error: "forbidden" } | { error: "not_found" } {
|
|
53
|
+
const pipelineDir = getPipelineDir(pipelineId);
|
|
54
|
+
const resolved = path.resolve(pipelineDir, filepath);
|
|
55
|
+
|
|
56
|
+
if (!resolved.startsWith(path.resolve(pipelineDir))) {
|
|
57
|
+
return { error: "forbidden" };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const state = readPipelineState(pipelineId);
|
|
61
|
+
if (!state) return { error: "not_found" };
|
|
62
|
+
|
|
63
|
+
const isRegistered = state.outputs.some((o) => o.filename === filepath);
|
|
64
|
+
if (!isRegistered) return { error: "forbidden" };
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const content = fs.readFileSync(resolved, "utf-8");
|
|
68
|
+
const ext = path.extname(filepath).toLowerCase();
|
|
69
|
+
const contentType =
|
|
70
|
+
ext === ".md" ? "text/markdown; charset=utf-8" :
|
|
71
|
+
ext === ".html" ? "text/html; charset=utf-8" :
|
|
72
|
+
"text/plain; charset=utf-8";
|
|
73
|
+
return { content, contentType };
|
|
74
|
+
} catch {
|
|
75
|
+
return { error: "not_found" };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function writeCheckpointResponse(pipelineId: string, action: string, message?: string): boolean {
|
|
80
|
+
const filePath = path.join(getPipelineDir(pipelineId), "checkpoint_response.json");
|
|
81
|
+
try {
|
|
82
|
+
fs.writeFileSync(filePath, JSON.stringify({
|
|
83
|
+
action,
|
|
84
|
+
message: message || "",
|
|
85
|
+
timestamp: new Date().toISOString(),
|
|
86
|
+
}));
|
|
87
|
+
return true;
|
|
88
|
+
} catch {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { getPipelinesDir, readPipelineState } from "./pipelines";
|
|
4
|
+
import type { PipelineState } from "@/types/pipeline";
|
|
5
|
+
|
|
6
|
+
type WatchCallback = (id: string, state: PipelineState | null) => void;
|
|
7
|
+
|
|
8
|
+
export class PipelineWatcher {
|
|
9
|
+
private watchers = new Map<string, fs.FSWatcher>();
|
|
10
|
+
private debounceTimers = new Map<string, NodeJS.Timeout>();
|
|
11
|
+
private dirWatcher: fs.FSWatcher | null = null;
|
|
12
|
+
private callback: WatchCallback;
|
|
13
|
+
|
|
14
|
+
constructor(callback: WatchCallback) {
|
|
15
|
+
this.callback = callback;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
start() {
|
|
19
|
+
const dir = getPipelinesDir();
|
|
20
|
+
if (!fs.existsSync(dir)) {
|
|
21
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
this.dirWatcher = fs.watch(dir, (_, filename) => {
|
|
25
|
+
if (!filename) return;
|
|
26
|
+
const fullPath = path.join(dir, filename);
|
|
27
|
+
if (fs.existsSync(fullPath) && fs.statSync(fullPath).isDirectory()) {
|
|
28
|
+
this.watchPipeline(filename);
|
|
29
|
+
} else {
|
|
30
|
+
this.unwatchPipeline(filename);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
35
|
+
for (const entry of entries) {
|
|
36
|
+
if (entry.isDirectory()) {
|
|
37
|
+
this.watchPipeline(entry.name);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private watchPipeline(id: string) {
|
|
43
|
+
if (this.watchers.has(id)) return;
|
|
44
|
+
|
|
45
|
+
const stateFile = path.join(getPipelinesDir(), id, "state.json");
|
|
46
|
+
if (!fs.existsSync(stateFile)) return;
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const watcher = fs.watch(stateFile, () => {
|
|
50
|
+
const existing = this.debounceTimers.get(id);
|
|
51
|
+
if (existing) clearTimeout(existing);
|
|
52
|
+
|
|
53
|
+
this.debounceTimers.set(id, setTimeout(() => {
|
|
54
|
+
this.debounceTimers.delete(id);
|
|
55
|
+
const state = readPipelineState(id);
|
|
56
|
+
this.callback(id, state);
|
|
57
|
+
}, 100));
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
watcher.on("error", () => {
|
|
61
|
+
this.unwatchPipeline(id);
|
|
62
|
+
this.callback(id, null);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
this.watchers.set(id, watcher);
|
|
66
|
+
} catch {
|
|
67
|
+
// Ignore watch errors
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private unwatchPipeline(id: string) {
|
|
72
|
+
const watcher = this.watchers.get(id);
|
|
73
|
+
if (watcher) {
|
|
74
|
+
watcher.close();
|
|
75
|
+
this.watchers.delete(id);
|
|
76
|
+
}
|
|
77
|
+
const timer = this.debounceTimers.get(id);
|
|
78
|
+
if (timer) {
|
|
79
|
+
clearTimeout(timer);
|
|
80
|
+
this.debounceTimers.delete(id);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
stop() {
|
|
85
|
+
this.dirWatcher?.close();
|
|
86
|
+
this.watchers.forEach((_, id) => {
|
|
87
|
+
this.unwatchPipeline(id);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { WebSocketServer, WebSocket } from "ws";
|
|
2
|
+
import type { Server } from "http";
|
|
3
|
+
import type { PipelineState, ServerMessage, ClientMessage } from "@/types/pipeline";
|
|
4
|
+
import { PipelineWatcher } from "./watcher";
|
|
5
|
+
import { readPipelineState, writeCheckpointResponse, getPipelineDir } from "./pipelines";
|
|
6
|
+
import { detectCheckpoint } from "./checkpoint";
|
|
7
|
+
import fs from "fs";
|
|
8
|
+
|
|
9
|
+
interface ClientState {
|
|
10
|
+
ws: WebSocket;
|
|
11
|
+
mode: "none" | "all" | "single";
|
|
12
|
+
pipelineId?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function createWSServer(server: Server) {
|
|
16
|
+
const wss = new WebSocketServer({ server, path: "/ws" });
|
|
17
|
+
const clients = new Map<WebSocket, ClientState>();
|
|
18
|
+
const prevActivitiesCount = new Map<string, number>();
|
|
19
|
+
|
|
20
|
+
const watcher = new PipelineWatcher((id, state) => {
|
|
21
|
+
if (state === null) {
|
|
22
|
+
broadcast({ type: "pipeline:removed", id }, (c) => c.mode !== "none");
|
|
23
|
+
prevActivitiesCount.delete(id);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
broadcastToAll({ type: "pipeline:updated", id, state });
|
|
28
|
+
|
|
29
|
+
const prevCount = prevActivitiesCount.get(id) || 0;
|
|
30
|
+
const newActivities = state.activities.slice(prevCount);
|
|
31
|
+
prevActivitiesCount.set(id, state.activities.length);
|
|
32
|
+
|
|
33
|
+
for (const activity of newActivities) {
|
|
34
|
+
broadcastToSingle(id, { type: "pipeline:activity", id, activity });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const checkpoint = detectCheckpoint(state.activities);
|
|
38
|
+
if (checkpoint) {
|
|
39
|
+
broadcastToSingle(id, { type: "pipeline:checkpoint", id, checkpoint });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
broadcastToSingle(id, { type: "pipeline:updated", id, state });
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
watcher.start();
|
|
46
|
+
|
|
47
|
+
function broadcast(msg: ServerMessage, filter: (c: ClientState) => boolean) {
|
|
48
|
+
const data = JSON.stringify(msg);
|
|
49
|
+
clients.forEach((client) => {
|
|
50
|
+
if (client.ws.readyState === WebSocket.OPEN && filter(client)) {
|
|
51
|
+
client.ws.send(data);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function broadcastToAll(msg: ServerMessage) {
|
|
57
|
+
broadcast(msg, (c) => c.mode === "all");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function broadcastToSingle(pipelineId: string, msg: ServerMessage) {
|
|
61
|
+
broadcast(msg, (c) => c.mode === "single" && c.pipelineId === pipelineId);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
wss.on("connection", (ws) => {
|
|
65
|
+
const clientState: ClientState = { ws, mode: "none" };
|
|
66
|
+
clients.set(ws, clientState);
|
|
67
|
+
|
|
68
|
+
ws.on("message", (raw) => {
|
|
69
|
+
try {
|
|
70
|
+
const msg = JSON.parse(raw.toString()) as ClientMessage;
|
|
71
|
+
handleMessage(clientState, msg);
|
|
72
|
+
} catch {
|
|
73
|
+
ws.send(JSON.stringify({ type: "error", message: "Invalid message" }));
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
ws.on("close", () => {
|
|
78
|
+
clients.delete(ws);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
function handleMessage(client: ClientState, msg: ClientMessage) {
|
|
83
|
+
switch (msg.type) {
|
|
84
|
+
case "subscribe:all":
|
|
85
|
+
client.mode = "all";
|
|
86
|
+
client.pipelineId = undefined;
|
|
87
|
+
break;
|
|
88
|
+
|
|
89
|
+
case "subscribe": {
|
|
90
|
+
const dir = getPipelineDir(msg.pipelineId);
|
|
91
|
+
if (!fs.existsSync(dir)) {
|
|
92
|
+
client.ws.send(JSON.stringify({ type: "error", message: "Pipeline not found" }));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
client.mode = "single";
|
|
96
|
+
client.pipelineId = msg.pipelineId;
|
|
97
|
+
|
|
98
|
+
const state = readPipelineState(msg.pipelineId);
|
|
99
|
+
if (state) {
|
|
100
|
+
prevActivitiesCount.set(msg.pipelineId, state.activities.length);
|
|
101
|
+
client.ws.send(JSON.stringify({ type: "pipeline:updated", id: msg.pipelineId, state }));
|
|
102
|
+
const checkpoint = detectCheckpoint(state.activities);
|
|
103
|
+
if (checkpoint) {
|
|
104
|
+
client.ws.send(JSON.stringify({ type: "pipeline:checkpoint", id: msg.pipelineId, checkpoint }));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
case "unsubscribe":
|
|
111
|
+
client.mode = "none";
|
|
112
|
+
client.pipelineId = undefined;
|
|
113
|
+
break;
|
|
114
|
+
|
|
115
|
+
case "checkpoint:respond": {
|
|
116
|
+
writeCheckpointResponse(msg.pipelineId, msg.action, msg.message);
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return { wss, watcher };
|
|
123
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export interface PipelineState {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
requirements: string;
|
|
5
|
+
status: "running" | "completed" | "failed" | "paused";
|
|
6
|
+
currentPhase: number;
|
|
7
|
+
agents: Record<string, AgentState>;
|
|
8
|
+
outputs: OutputEntry[];
|
|
9
|
+
activities: Activity[];
|
|
10
|
+
createdAt: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface AgentState {
|
|
14
|
+
id: string;
|
|
15
|
+
status: "idle" | "working" | "done";
|
|
16
|
+
currentTask?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface OutputEntry {
|
|
20
|
+
filename: string;
|
|
21
|
+
status: "complete";
|
|
22
|
+
phase: number;
|
|
23
|
+
updatedAt: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface Activity {
|
|
27
|
+
id: string;
|
|
28
|
+
agentId: string;
|
|
29
|
+
message: string;
|
|
30
|
+
timestamp: string;
|
|
31
|
+
type: "info" | "success" | "error" | "progress";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface CheckpointInfo {
|
|
35
|
+
phase: number;
|
|
36
|
+
description: string;
|
|
37
|
+
status: "pending" | "approved" | "rejected";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface PipelineSummary {
|
|
41
|
+
id: string;
|
|
42
|
+
requirements: string;
|
|
43
|
+
status: PipelineState["status"];
|
|
44
|
+
currentPhase: number;
|
|
45
|
+
createdAt: string;
|
|
46
|
+
agents: Record<string, AgentState>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// WebSocket messages
|
|
50
|
+
export type ServerMessage =
|
|
51
|
+
| { type: "pipeline:updated"; id: string; state: PipelineState }
|
|
52
|
+
| { type: "pipeline:activity"; id: string; activity: Activity }
|
|
53
|
+
| { type: "pipeline:checkpoint"; id: string; checkpoint: CheckpointInfo }
|
|
54
|
+
| { type: "pipeline:removed"; id: string }
|
|
55
|
+
| { type: "error"; message: string };
|
|
56
|
+
|
|
57
|
+
export type ClientMessage =
|
|
58
|
+
| { type: "subscribe:all" }
|
|
59
|
+
| { type: "subscribe"; pipelineId: string }
|
|
60
|
+
| { type: "unsubscribe"; pipelineId: string }
|
|
61
|
+
| { type: "checkpoint:respond"; pipelineId: string; action: "approve" | "reject"; message?: string };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Config } from "tailwindcss";
|
|
2
|
+
|
|
3
|
+
const config: Config = {
|
|
4
|
+
content: [
|
|
5
|
+
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
|
6
|
+
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
|
7
|
+
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
|
8
|
+
],
|
|
9
|
+
theme: {
|
|
10
|
+
extend: {
|
|
11
|
+
colors: {
|
|
12
|
+
background: "var(--background)",
|
|
13
|
+
foreground: "var(--foreground)",
|
|
14
|
+
panel: "#1f2937",
|
|
15
|
+
border: "#374151",
|
|
16
|
+
accent: {
|
|
17
|
+
purple: "#6366f1",
|
|
18
|
+
"purple-light": "#8b5cf6",
|
|
19
|
+
green: "#34d399",
|
|
20
|
+
},
|
|
21
|
+
text: {
|
|
22
|
+
primary: "#f3f4f6",
|
|
23
|
+
secondary: "#9ca3af",
|
|
24
|
+
muted: "#6b7280",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
plugins: [require("@tailwindcss/typography")],
|
|
30
|
+
};
|
|
31
|
+
export default config;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
4
|
+
"allowJs": true,
|
|
5
|
+
"skipLibCheck": true,
|
|
6
|
+
"strict": true,
|
|
7
|
+
"noEmit": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"module": "esnext",
|
|
10
|
+
"moduleResolution": "bundler",
|
|
11
|
+
"resolveJsonModule": true,
|
|
12
|
+
"isolatedModules": true,
|
|
13
|
+
"jsx": "preserve",
|
|
14
|
+
"incremental": true,
|
|
15
|
+
"plugins": [
|
|
16
|
+
{
|
|
17
|
+
"name": "next"
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"paths": {
|
|
21
|
+
"@/*": ["./src/*"]
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
25
|
+
"exclude": ["node_modules"]
|
|
26
|
+
}
|