@claude-canvas/bridge 0.2.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/dist/index.d.ts +178 -0
- package/dist/index.js +425 -0
- package/dist/index.js.map +1 -0
- package/package.json +50 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { ChatMessage, AnnotationSet, FileChange } from '@claude-canvas/core';
|
|
2
|
+
export { Annotation, AnnotationSet, ChatMessage, FileChange, SourceInfo } from '@claude-canvas/core';
|
|
3
|
+
|
|
4
|
+
type SessionStatus = 'active' | 'qa-running' | 'qa-passed' | 'qa-failed' | 'merged' | 'discarded';
|
|
5
|
+
type QAStepName = 'tsc' | 'eslint' | 'build' | 'test' | 'skill';
|
|
6
|
+
interface CanvasSession {
|
|
7
|
+
id: string;
|
|
8
|
+
branch: string;
|
|
9
|
+
baseBranch: string;
|
|
10
|
+
status: SessionStatus;
|
|
11
|
+
messages: ChatMessage[];
|
|
12
|
+
changedFiles: string[];
|
|
13
|
+
commitCount: number;
|
|
14
|
+
startedAt: number;
|
|
15
|
+
stashRef?: string;
|
|
16
|
+
}
|
|
17
|
+
interface QAStep {
|
|
18
|
+
name: QAStepName;
|
|
19
|
+
skillName?: string;
|
|
20
|
+
pass: boolean;
|
|
21
|
+
output: string;
|
|
22
|
+
duration: number;
|
|
23
|
+
}
|
|
24
|
+
interface QAResult {
|
|
25
|
+
steps: QAStep[];
|
|
26
|
+
overallPass: boolean;
|
|
27
|
+
duration: number;
|
|
28
|
+
}
|
|
29
|
+
interface MergeResult {
|
|
30
|
+
success: boolean;
|
|
31
|
+
commitHash?: string;
|
|
32
|
+
error?: string;
|
|
33
|
+
}
|
|
34
|
+
type ClientMessage = {
|
|
35
|
+
type: 'canvas:session-start';
|
|
36
|
+
} | {
|
|
37
|
+
type: 'canvas:session-end';
|
|
38
|
+
payload: {
|
|
39
|
+
action: 'merge' | 'discard';
|
|
40
|
+
};
|
|
41
|
+
} | {
|
|
42
|
+
type: 'canvas:annotate';
|
|
43
|
+
payload: {
|
|
44
|
+
annotations: AnnotationSet;
|
|
45
|
+
memo: string;
|
|
46
|
+
};
|
|
47
|
+
} | {
|
|
48
|
+
type: 'canvas:chat';
|
|
49
|
+
payload: {
|
|
50
|
+
message: string;
|
|
51
|
+
};
|
|
52
|
+
} | {
|
|
53
|
+
type: 'canvas:qa-retry';
|
|
54
|
+
};
|
|
55
|
+
type ServerMessage = {
|
|
56
|
+
type: 'canvas:session-status';
|
|
57
|
+
payload: Partial<CanvasSession>;
|
|
58
|
+
} | {
|
|
59
|
+
type: 'canvas:stream';
|
|
60
|
+
payload: {
|
|
61
|
+
delta: string;
|
|
62
|
+
done: boolean;
|
|
63
|
+
};
|
|
64
|
+
} | {
|
|
65
|
+
type: 'canvas:file-changed';
|
|
66
|
+
payload: FileChange;
|
|
67
|
+
} | {
|
|
68
|
+
type: 'canvas:qa-progress';
|
|
69
|
+
payload: {
|
|
70
|
+
step: string;
|
|
71
|
+
status: 'running' | 'pass' | 'fail';
|
|
72
|
+
};
|
|
73
|
+
} | {
|
|
74
|
+
type: 'canvas:qa-result';
|
|
75
|
+
payload: QAResult;
|
|
76
|
+
} | {
|
|
77
|
+
type: 'canvas:merge-result';
|
|
78
|
+
payload: MergeResult;
|
|
79
|
+
} | {
|
|
80
|
+
type: 'canvas:error';
|
|
81
|
+
payload: {
|
|
82
|
+
message: string;
|
|
83
|
+
recoverable: boolean;
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
interface ClaudeCanvasOptions {
|
|
87
|
+
permissionMode?: 'acceptEdits' | 'default';
|
|
88
|
+
sessionTimeout?: number;
|
|
89
|
+
shortcut?: string;
|
|
90
|
+
git?: {
|
|
91
|
+
branchPrefix?: string;
|
|
92
|
+
autoCommit?: boolean;
|
|
93
|
+
squashMerge?: boolean;
|
|
94
|
+
};
|
|
95
|
+
qa?: {
|
|
96
|
+
tsc?: boolean;
|
|
97
|
+
eslint?: boolean | string;
|
|
98
|
+
build?: boolean;
|
|
99
|
+
test?: boolean | string;
|
|
100
|
+
skills?: string[];
|
|
101
|
+
};
|
|
102
|
+
ui?: {
|
|
103
|
+
library?: 'auto' | 'carbon' | 'mui' | 'shadcn' | 'antd';
|
|
104
|
+
wrapperPaths?: string[];
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
declare const CLIENT_MESSAGE_TYPES: readonly ["canvas:session-start", "canvas:session-end", "canvas:annotate", "canvas:chat", "canvas:qa-retry"];
|
|
109
|
+
declare const SERVER_MESSAGE_TYPES: readonly ["canvas:session-status", "canvas:stream", "canvas:file-changed", "canvas:qa-progress", "canvas:qa-result", "canvas:merge-result", "canvas:error"];
|
|
110
|
+
declare class WSParseError extends Error {
|
|
111
|
+
constructor(message: string);
|
|
112
|
+
}
|
|
113
|
+
declare class WSProtocol {
|
|
114
|
+
/** Parse raw JSON string to ClientMessage. Throws on invalid. */
|
|
115
|
+
static parseClientMessage(raw: string): ClientMessage;
|
|
116
|
+
/** Serialize ServerMessage to JSON string */
|
|
117
|
+
static serializeServerMessage(msg: ServerMessage): string;
|
|
118
|
+
/** Check if a type string is valid */
|
|
119
|
+
static isValidMessageType(type: string): boolean;
|
|
120
|
+
/** Create an error server message */
|
|
121
|
+
static createErrorMessage(message: string, recoverable: boolean): ServerMessage;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
declare class PromptBuilder {
|
|
125
|
+
/**
|
|
126
|
+
* Convert annotations + user memo into a Claude prompt string
|
|
127
|
+
* Format per spec (04-data-model.md Section 4):
|
|
128
|
+
* 파일: {file}:{line}
|
|
129
|
+
* 컴포넌트: <{componentName}>
|
|
130
|
+
* 라이브러리: {library} (if present)
|
|
131
|
+
* 메모: {ann.memo} (if present)
|
|
132
|
+
*
|
|
133
|
+
* 요청: {memo}
|
|
134
|
+
*/
|
|
135
|
+
static buildPrompt(annotations: AnnotationSet, memo: string): string;
|
|
136
|
+
/**
|
|
137
|
+
* Build prompt with conversation history context
|
|
138
|
+
*/
|
|
139
|
+
static buildContextualPrompt(annotations: AnnotationSet, memo: string, history: ChatMessage[]): string;
|
|
140
|
+
/**
|
|
141
|
+
* Extract file paths referenced in annotations
|
|
142
|
+
*/
|
|
143
|
+
static extractFileReferences(annotations: AnnotationSet): string[];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
declare class ClaudeBridge {
|
|
147
|
+
private process;
|
|
148
|
+
private fileChangeListeners;
|
|
149
|
+
private options;
|
|
150
|
+
constructor(options: ClaudeCanvasOptions);
|
|
151
|
+
spawn(): Promise<void>;
|
|
152
|
+
sendPrompt(prompt: string): AsyncGenerator<string>;
|
|
153
|
+
onFileChanged(callback: (change: FileChange) => void): void;
|
|
154
|
+
shutdown(): Promise<void>;
|
|
155
|
+
restart(): Promise<void>;
|
|
156
|
+
private detectFileChanges;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
declare class GitManager {
|
|
160
|
+
private session;
|
|
161
|
+
startSession(options?: {
|
|
162
|
+
branchPrefix?: string;
|
|
163
|
+
}): Promise<CanvasSession>;
|
|
164
|
+
endSession(action: 'merge' | 'discard'): Promise<MergeResult>;
|
|
165
|
+
getChangedFiles(): Promise<string[]>;
|
|
166
|
+
autoCommit(message: string): Promise<string>;
|
|
167
|
+
getSession(): CanvasSession | null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
declare class QARunner {
|
|
171
|
+
private options;
|
|
172
|
+
constructor(options?: ClaudeCanvasOptions['qa']);
|
|
173
|
+
run(): AsyncGenerator<QAStep>;
|
|
174
|
+
getResult(steps: QAStep[]): QAResult;
|
|
175
|
+
private runStep;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export { CLIENT_MESSAGE_TYPES, type CanvasSession, ClaudeBridge, type ClaudeCanvasOptions, type ClientMessage, GitManager, type MergeResult, PromptBuilder, type QAResult, QARunner, type QAStep, type QAStepName, SERVER_MESSAGE_TYPES, type ServerMessage, type SessionStatus, WSParseError, WSProtocol };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
// src/ws-protocol.ts
|
|
2
|
+
var CLIENT_MESSAGE_TYPES = [
|
|
3
|
+
"canvas:session-start",
|
|
4
|
+
"canvas:session-end",
|
|
5
|
+
"canvas:annotate",
|
|
6
|
+
"canvas:chat",
|
|
7
|
+
"canvas:qa-retry"
|
|
8
|
+
];
|
|
9
|
+
var SERVER_MESSAGE_TYPES = [
|
|
10
|
+
"canvas:session-status",
|
|
11
|
+
"canvas:stream",
|
|
12
|
+
"canvas:file-changed",
|
|
13
|
+
"canvas:qa-progress",
|
|
14
|
+
"canvas:qa-result",
|
|
15
|
+
"canvas:merge-result",
|
|
16
|
+
"canvas:error"
|
|
17
|
+
];
|
|
18
|
+
var WSParseError = class extends Error {
|
|
19
|
+
constructor(message) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.name = "WSParseError";
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
var WSProtocol = class {
|
|
25
|
+
/** Parse raw JSON string to ClientMessage. Throws on invalid. */
|
|
26
|
+
static parseClientMessage(raw) {
|
|
27
|
+
let parsed;
|
|
28
|
+
try {
|
|
29
|
+
parsed = JSON.parse(raw);
|
|
30
|
+
} catch {
|
|
31
|
+
throw new WSParseError("Invalid JSON");
|
|
32
|
+
}
|
|
33
|
+
if (!parsed || typeof parsed !== "object") {
|
|
34
|
+
throw new WSParseError("Message must be an object");
|
|
35
|
+
}
|
|
36
|
+
const msg = parsed;
|
|
37
|
+
const type = msg.type;
|
|
38
|
+
if (!type || typeof type !== "string") {
|
|
39
|
+
throw new WSParseError("Missing or invalid type");
|
|
40
|
+
}
|
|
41
|
+
if (!CLIENT_MESSAGE_TYPES.includes(type)) {
|
|
42
|
+
throw new WSParseError(`Unknown client message type: ${type}`);
|
|
43
|
+
}
|
|
44
|
+
switch (type) {
|
|
45
|
+
case "canvas:session-start":
|
|
46
|
+
return { type: "canvas:session-start" };
|
|
47
|
+
case "canvas:session-end": {
|
|
48
|
+
const payload = msg.payload;
|
|
49
|
+
if (!payload || typeof payload !== "object") {
|
|
50
|
+
throw new WSParseError("canvas:session-end requires payload");
|
|
51
|
+
}
|
|
52
|
+
const p = payload;
|
|
53
|
+
if (!p.action || typeof p.action !== "string") {
|
|
54
|
+
throw new WSParseError("canvas:session-end payload must have action field");
|
|
55
|
+
}
|
|
56
|
+
if (p.action !== "merge" && p.action !== "discard") {
|
|
57
|
+
throw new WSParseError('action must be "merge" or "discard"');
|
|
58
|
+
}
|
|
59
|
+
return { type: "canvas:session-end", payload: { action: p.action } };
|
|
60
|
+
}
|
|
61
|
+
case "canvas:annotate": {
|
|
62
|
+
const payload = msg.payload;
|
|
63
|
+
if (!payload || typeof payload !== "object") {
|
|
64
|
+
throw new WSParseError("canvas:annotate requires payload");
|
|
65
|
+
}
|
|
66
|
+
const p = payload;
|
|
67
|
+
if (!p.annotations) {
|
|
68
|
+
throw new WSParseError("canvas:annotate payload must have annotations");
|
|
69
|
+
}
|
|
70
|
+
if (typeof p.memo !== "string") {
|
|
71
|
+
throw new WSParseError("canvas:annotate payload must have memo string");
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
type: "canvas:annotate",
|
|
75
|
+
payload: { annotations: p.annotations, memo: p.memo }
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
case "canvas:chat": {
|
|
79
|
+
const payload = msg.payload;
|
|
80
|
+
if (!payload || typeof payload !== "object") {
|
|
81
|
+
throw new WSParseError("canvas:chat requires payload");
|
|
82
|
+
}
|
|
83
|
+
const p = payload;
|
|
84
|
+
if (typeof p.message !== "string") {
|
|
85
|
+
throw new WSParseError("canvas:chat payload must have message string");
|
|
86
|
+
}
|
|
87
|
+
return { type: "canvas:chat", payload: { message: p.message } };
|
|
88
|
+
}
|
|
89
|
+
case "canvas:qa-retry":
|
|
90
|
+
return { type: "canvas:qa-retry" };
|
|
91
|
+
default:
|
|
92
|
+
throw new WSParseError(`Unhandled message type: ${type}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/** Serialize ServerMessage to JSON string */
|
|
96
|
+
static serializeServerMessage(msg) {
|
|
97
|
+
return JSON.stringify(msg);
|
|
98
|
+
}
|
|
99
|
+
/** Check if a type string is valid */
|
|
100
|
+
static isValidMessageType(type) {
|
|
101
|
+
return CLIENT_MESSAGE_TYPES.includes(type) || SERVER_MESSAGE_TYPES.includes(type);
|
|
102
|
+
}
|
|
103
|
+
/** Create an error server message */
|
|
104
|
+
static createErrorMessage(message, recoverable) {
|
|
105
|
+
return { type: "canvas:error", payload: { message, recoverable } };
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// src/prompt-builder.ts
|
|
110
|
+
var PromptBuilder = class {
|
|
111
|
+
/**
|
|
112
|
+
* Convert annotations + user memo into a Claude prompt string
|
|
113
|
+
* Format per spec (04-data-model.md Section 4):
|
|
114
|
+
* 파일: {file}:{line}
|
|
115
|
+
* 컴포넌트: <{componentName}>
|
|
116
|
+
* 라이브러리: {library} (if present)
|
|
117
|
+
* 메모: {ann.memo} (if present)
|
|
118
|
+
*
|
|
119
|
+
* 요청: {memo}
|
|
120
|
+
*/
|
|
121
|
+
static buildPrompt(annotations, memo) {
|
|
122
|
+
const parts = [];
|
|
123
|
+
for (const ann of annotations.annotations) {
|
|
124
|
+
if (ann.source) {
|
|
125
|
+
parts.push(`\uD30C\uC77C: ${ann.source.file}:${ann.source.line}`);
|
|
126
|
+
parts.push(`\uCEF4\uD3EC\uB10C\uD2B8: <${ann.source.componentName}>`);
|
|
127
|
+
if (ann.source.library) {
|
|
128
|
+
parts.push(`\uB77C\uC774\uBE0C\uB7EC\uB9AC: ${ann.source.library}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (ann.memo) {
|
|
132
|
+
parts.push(`\uBA54\uBAA8: ${ann.memo}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (parts.length > 0) {
|
|
136
|
+
parts.push(`
|
|
137
|
+
\uC694\uCCAD: ${memo}`);
|
|
138
|
+
return parts.join("\n");
|
|
139
|
+
} else {
|
|
140
|
+
return `\uC694\uCCAD: ${memo}`;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Build prompt with conversation history context
|
|
145
|
+
*/
|
|
146
|
+
static buildContextualPrompt(annotations, memo, history) {
|
|
147
|
+
const basePrompt = this.buildPrompt(annotations, memo);
|
|
148
|
+
if (history.length === 0) {
|
|
149
|
+
return basePrompt;
|
|
150
|
+
}
|
|
151
|
+
const recentHistory = history.slice(-3);
|
|
152
|
+
const historyLines = [];
|
|
153
|
+
for (const msg of recentHistory) {
|
|
154
|
+
const role = msg.role === "user" ? "\uC0AC\uC6A9\uC790" : "\uC5B4\uC2DC\uC2A4\uD134\uD2B8";
|
|
155
|
+
historyLines.push(`${role}: ${msg.content}`);
|
|
156
|
+
}
|
|
157
|
+
const contextPrefix = "\uC774\uC804 \uB300\uD654:\n" + historyLines.join("\n") + "\n\n";
|
|
158
|
+
return contextPrefix + basePrompt;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Extract file paths referenced in annotations
|
|
162
|
+
*/
|
|
163
|
+
static extractFileReferences(annotations) {
|
|
164
|
+
const files = /* @__PURE__ */ new Set();
|
|
165
|
+
for (const ann of annotations.annotations) {
|
|
166
|
+
if (ann.source?.file) {
|
|
167
|
+
files.add(ann.source.file);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return Array.from(files).sort();
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
// src/claude-bridge.ts
|
|
175
|
+
import { spawn as cpSpawn } from "child_process";
|
|
176
|
+
import { createInterface } from "readline";
|
|
177
|
+
var WRITE_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit"]);
|
|
178
|
+
var SHUTDOWN_TIMEOUT = 5e3;
|
|
179
|
+
var ClaudeBridge = class {
|
|
180
|
+
process = null;
|
|
181
|
+
fileChangeListeners = [];
|
|
182
|
+
options;
|
|
183
|
+
constructor(options) {
|
|
184
|
+
this.options = options;
|
|
185
|
+
}
|
|
186
|
+
async spawn() {
|
|
187
|
+
if (this.process) {
|
|
188
|
+
throw new Error("Claude subprocess already running");
|
|
189
|
+
}
|
|
190
|
+
const args = [
|
|
191
|
+
"--print",
|
|
192
|
+
"-",
|
|
193
|
+
"--output-format",
|
|
194
|
+
"stream-json"
|
|
195
|
+
];
|
|
196
|
+
if (this.options.permissionMode === "acceptEdits") {
|
|
197
|
+
args.push("--allowedTools", "Edit,Write,Bash(read-only)");
|
|
198
|
+
}
|
|
199
|
+
this.process = cpSpawn("claude", args, {
|
|
200
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
201
|
+
env: { ...process.env }
|
|
202
|
+
});
|
|
203
|
+
this.process.on("error", (err) => {
|
|
204
|
+
console.error("[ClaudeBridge] subprocess error:", err.message);
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
async *sendPrompt(prompt) {
|
|
208
|
+
if (!this.process?.stdin || !this.process?.stdout) {
|
|
209
|
+
throw new Error("Claude subprocess not running");
|
|
210
|
+
}
|
|
211
|
+
this.process.stdin.write(prompt + "\n");
|
|
212
|
+
const rl = createInterface({ input: this.process.stdout });
|
|
213
|
+
for await (const line of rl) {
|
|
214
|
+
if (!line.trim()) continue;
|
|
215
|
+
let msg;
|
|
216
|
+
try {
|
|
217
|
+
msg = JSON.parse(line);
|
|
218
|
+
} catch {
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
this.detectFileChanges(msg);
|
|
222
|
+
if (msg.content) {
|
|
223
|
+
for (const block of msg.content) {
|
|
224
|
+
if (block.type === "text" && block.text) {
|
|
225
|
+
yield block.text;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (msg.type === "result") {
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
onFileChanged(callback) {
|
|
235
|
+
this.fileChangeListeners.push(callback);
|
|
236
|
+
}
|
|
237
|
+
async shutdown() {
|
|
238
|
+
if (!this.process) return;
|
|
239
|
+
const proc = this.process;
|
|
240
|
+
this.process = null;
|
|
241
|
+
proc.stdin?.end();
|
|
242
|
+
proc.kill("SIGTERM");
|
|
243
|
+
await new Promise((resolve) => {
|
|
244
|
+
const timer = setTimeout(() => {
|
|
245
|
+
proc.kill("SIGKILL");
|
|
246
|
+
resolve();
|
|
247
|
+
}, SHUTDOWN_TIMEOUT);
|
|
248
|
+
proc.on("exit", () => {
|
|
249
|
+
clearTimeout(timer);
|
|
250
|
+
resolve();
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
async restart() {
|
|
255
|
+
await this.shutdown();
|
|
256
|
+
await this.spawn();
|
|
257
|
+
}
|
|
258
|
+
detectFileChanges(msg) {
|
|
259
|
+
if (!msg.content || this.fileChangeListeners.length === 0) return;
|
|
260
|
+
for (const block of msg.content) {
|
|
261
|
+
if (block.type === "tool_use" && block.name && WRITE_TOOLS.has(block.name)) {
|
|
262
|
+
const filePath = block.input?.file_path;
|
|
263
|
+
if (filePath) {
|
|
264
|
+
const change = {
|
|
265
|
+
file: filePath,
|
|
266
|
+
action: block.name === "Write" ? "create" : "edit",
|
|
267
|
+
linesAdded: 0,
|
|
268
|
+
linesRemoved: 0
|
|
269
|
+
};
|
|
270
|
+
for (const listener of this.fileChangeListeners) {
|
|
271
|
+
listener(change);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
// src/git-manager.ts
|
|
280
|
+
import { execFile } from "child_process";
|
|
281
|
+
function git(...args) {
|
|
282
|
+
return new Promise((resolve, reject) => {
|
|
283
|
+
execFile("git", args, (err, stdout) => {
|
|
284
|
+
if (err) reject(err);
|
|
285
|
+
else resolve(stdout.trim());
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
var GitManager = class {
|
|
290
|
+
session = null;
|
|
291
|
+
async startSession(options = {}) {
|
|
292
|
+
const prefix = options.branchPrefix ?? "canvas/session";
|
|
293
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:T]/g, "").slice(0, 13);
|
|
294
|
+
const branch = `${prefix}-${timestamp}`;
|
|
295
|
+
const baseBranch = await git("branch", "--show-current");
|
|
296
|
+
const status = await git("status", "--porcelain");
|
|
297
|
+
let stashRef;
|
|
298
|
+
if (status) {
|
|
299
|
+
await git("stash", "push", "-m", "claude-canvas-session");
|
|
300
|
+
stashRef = (await git("stash", "list", "--max-count=1")).split(":")[0];
|
|
301
|
+
}
|
|
302
|
+
await git("checkout", "-b", branch);
|
|
303
|
+
this.session = {
|
|
304
|
+
id: crypto.randomUUID(),
|
|
305
|
+
branch,
|
|
306
|
+
baseBranch,
|
|
307
|
+
status: "active",
|
|
308
|
+
messages: [],
|
|
309
|
+
changedFiles: [],
|
|
310
|
+
commitCount: 0,
|
|
311
|
+
startedAt: Date.now(),
|
|
312
|
+
stashRef
|
|
313
|
+
};
|
|
314
|
+
return this.session;
|
|
315
|
+
}
|
|
316
|
+
async endSession(action) {
|
|
317
|
+
if (!this.session) throw new Error("No active session");
|
|
318
|
+
const { baseBranch, branch, stashRef, id } = this.session;
|
|
319
|
+
if (action === "merge") {
|
|
320
|
+
await git("checkout", baseBranch);
|
|
321
|
+
await git("merge", "--squash", branch);
|
|
322
|
+
await git("commit", "-m", `feat: claude canvas session ${id}`);
|
|
323
|
+
const hash = await git("rev-parse", "HEAD");
|
|
324
|
+
await git("branch", "-D", branch);
|
|
325
|
+
if (stashRef) await git("stash", "pop");
|
|
326
|
+
this.session.status = "merged";
|
|
327
|
+
this.session = null;
|
|
328
|
+
return { success: true, commitHash: hash };
|
|
329
|
+
} else {
|
|
330
|
+
await git("checkout", baseBranch);
|
|
331
|
+
await git("branch", "-D", branch);
|
|
332
|
+
if (stashRef) await git("stash", "pop");
|
|
333
|
+
this.session.status = "discarded";
|
|
334
|
+
this.session = null;
|
|
335
|
+
return { success: true };
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
async getChangedFiles() {
|
|
339
|
+
if (!this.session) return [];
|
|
340
|
+
const diff = await git("diff", "--name-only", this.session.baseBranch);
|
|
341
|
+
return diff ? diff.split("\n") : [];
|
|
342
|
+
}
|
|
343
|
+
async autoCommit(message) {
|
|
344
|
+
await git("add", "-A");
|
|
345
|
+
await git("commit", "-m", message);
|
|
346
|
+
if (this.session) this.session.commitCount++;
|
|
347
|
+
return git("rev-parse", "HEAD");
|
|
348
|
+
}
|
|
349
|
+
getSession() {
|
|
350
|
+
return this.session;
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
// src/qa-runner.ts
|
|
355
|
+
import { execFile as execFile2 } from "child_process";
|
|
356
|
+
function execAsync(cmd, args) {
|
|
357
|
+
return new Promise((resolve, reject) => {
|
|
358
|
+
execFile2(cmd, args, (err, stdout) => {
|
|
359
|
+
if (err) reject(err);
|
|
360
|
+
else resolve(stdout);
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
var QARunner = class {
|
|
365
|
+
options;
|
|
366
|
+
constructor(options = {}) {
|
|
367
|
+
this.options = options ?? {};
|
|
368
|
+
}
|
|
369
|
+
async *run() {
|
|
370
|
+
if (this.options.tsc !== false) {
|
|
371
|
+
const step = await this.runStep("tsc", "npx tsc --noEmit");
|
|
372
|
+
yield step;
|
|
373
|
+
}
|
|
374
|
+
if (this.options.eslint !== false) {
|
|
375
|
+
const cmd = typeof this.options.eslint === "string" ? this.options.eslint : "npx eslint .";
|
|
376
|
+
const step = await this.runStep("eslint", cmd);
|
|
377
|
+
yield step;
|
|
378
|
+
}
|
|
379
|
+
if (this.options.build !== false) {
|
|
380
|
+
const step = await this.runStep("build", "npm run build");
|
|
381
|
+
yield step;
|
|
382
|
+
}
|
|
383
|
+
if (this.options.test !== false) {
|
|
384
|
+
const cmd = typeof this.options.test === "string" ? this.options.test : "npx vitest run";
|
|
385
|
+
const step = await this.runStep("test", cmd);
|
|
386
|
+
yield step;
|
|
387
|
+
}
|
|
388
|
+
if (this.options.skills) {
|
|
389
|
+
for (const skill of this.options.skills) {
|
|
390
|
+
const step = await this.runStep("skill", skill);
|
|
391
|
+
step.skillName = skill;
|
|
392
|
+
yield step;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
getResult(steps) {
|
|
397
|
+
return {
|
|
398
|
+
steps,
|
|
399
|
+
overallPass: steps.every((s) => s.pass),
|
|
400
|
+
duration: steps.reduce((sum, s) => sum + s.duration, 0)
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
async runStep(name, command) {
|
|
404
|
+
const start = Date.now();
|
|
405
|
+
try {
|
|
406
|
+
const [cmd, ...args] = command.split(" ");
|
|
407
|
+
const output = await execAsync(cmd, args);
|
|
408
|
+
return { name, pass: true, output, duration: Date.now() - start };
|
|
409
|
+
} catch (err) {
|
|
410
|
+
const output = err instanceof Error ? err.message : String(err);
|
|
411
|
+
return { name, pass: false, output, duration: Date.now() - start };
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
export {
|
|
416
|
+
CLIENT_MESSAGE_TYPES,
|
|
417
|
+
ClaudeBridge,
|
|
418
|
+
GitManager,
|
|
419
|
+
PromptBuilder,
|
|
420
|
+
QARunner,
|
|
421
|
+
SERVER_MESSAGE_TYPES,
|
|
422
|
+
WSParseError,
|
|
423
|
+
WSProtocol
|
|
424
|
+
};
|
|
425
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ws-protocol.ts","../src/prompt-builder.ts","../src/claude-bridge.ts","../src/git-manager.ts","../src/qa-runner.ts"],"sourcesContent":["import type { AnnotationSet } from '@claude-canvas/core'\r\nimport type { ClientMessage, ServerMessage } from './types.js'\r\n\r\n// All valid message types\r\nexport const CLIENT_MESSAGE_TYPES = [\r\n 'canvas:session-start',\r\n 'canvas:session-end',\r\n 'canvas:annotate',\r\n 'canvas:chat',\r\n 'canvas:qa-retry',\r\n] as const\r\n\r\nexport const SERVER_MESSAGE_TYPES = [\r\n 'canvas:session-status',\r\n 'canvas:stream',\r\n 'canvas:file-changed',\r\n 'canvas:qa-progress',\r\n 'canvas:qa-result',\r\n 'canvas:merge-result',\r\n 'canvas:error',\r\n] as const\r\n\r\nexport class WSParseError extends Error {\r\n constructor(message: string) {\r\n super(message)\r\n this.name = 'WSParseError'\r\n }\r\n}\r\n\r\nexport class WSProtocol {\r\n /** Parse raw JSON string to ClientMessage. Throws on invalid. */\r\n static parseClientMessage(raw: string): ClientMessage {\r\n let parsed: unknown\r\n try {\r\n parsed = JSON.parse(raw)\r\n } catch {\r\n throw new WSParseError('Invalid JSON')\r\n }\r\n\r\n if (!parsed || typeof parsed !== 'object') {\r\n throw new WSParseError('Message must be an object')\r\n }\r\n\r\n const msg = parsed as Record<string, unknown>\r\n const type = msg.type\r\n\r\n if (!type || typeof type !== 'string') {\r\n throw new WSParseError('Missing or invalid type')\r\n }\r\n\r\n if (!(CLIENT_MESSAGE_TYPES as readonly string[]).includes(type)) {\r\n throw new WSParseError(`Unknown client message type: ${type}`)\r\n }\r\n\r\n // Validate payload structure based on type\r\n switch (type) {\r\n case 'canvas:session-start':\r\n return { type: 'canvas:session-start' }\r\n\r\n case 'canvas:session-end': {\r\n const payload = msg.payload\r\n if (!payload || typeof payload !== 'object') {\r\n throw new WSParseError('canvas:session-end requires payload')\r\n }\r\n const p = payload as Record<string, unknown>\r\n if (!p.action || typeof p.action !== 'string') {\r\n throw new WSParseError('canvas:session-end payload must have action field')\r\n }\r\n if (p.action !== 'merge' && p.action !== 'discard') {\r\n throw new WSParseError('action must be \"merge\" or \"discard\"')\r\n }\r\n return { type: 'canvas:session-end', payload: { action: p.action as 'merge' | 'discard' } }\r\n }\r\n\r\n case 'canvas:annotate': {\r\n const payload = msg.payload\r\n if (!payload || typeof payload !== 'object') {\r\n throw new WSParseError('canvas:annotate requires payload')\r\n }\r\n const p = payload as Record<string, unknown>\r\n if (!p.annotations) {\r\n throw new WSParseError('canvas:annotate payload must have annotations')\r\n }\r\n if (typeof p.memo !== 'string') {\r\n throw new WSParseError('canvas:annotate payload must have memo string')\r\n }\r\n return {\r\n type: 'canvas:annotate',\r\n payload: { annotations: p.annotations as AnnotationSet, memo: p.memo },\r\n }\r\n }\r\n\r\n case 'canvas:chat': {\r\n const payload = msg.payload\r\n if (!payload || typeof payload !== 'object') {\r\n throw new WSParseError('canvas:chat requires payload')\r\n }\r\n const p = payload as Record<string, unknown>\r\n if (typeof p.message !== 'string') {\r\n throw new WSParseError('canvas:chat payload must have message string')\r\n }\r\n return { type: 'canvas:chat', payload: { message: p.message } }\r\n }\r\n\r\n case 'canvas:qa-retry':\r\n return { type: 'canvas:qa-retry' }\r\n\r\n default:\r\n // Type guard ensures this is unreachable\r\n throw new WSParseError(`Unhandled message type: ${type}`)\r\n }\r\n }\r\n\r\n /** Serialize ServerMessage to JSON string */\r\n static serializeServerMessage(msg: ServerMessage): string {\r\n return JSON.stringify(msg)\r\n }\r\n\r\n /** Check if a type string is valid */\r\n static isValidMessageType(type: string): boolean {\r\n return (CLIENT_MESSAGE_TYPES as readonly string[]).includes(type) || (SERVER_MESSAGE_TYPES as readonly string[]).includes(type)\r\n }\r\n\r\n /** Create an error server message */\r\n static createErrorMessage(message: string, recoverable: boolean): ServerMessage {\r\n return { type: 'canvas:error', payload: { message, recoverable } }\r\n }\r\n}\r\n","import type { AnnotationSet, ChatMessage } from '@claude-canvas/core'\r\n\r\nexport class PromptBuilder {\r\n /**\r\n * Convert annotations + user memo into a Claude prompt string\r\n * Format per spec (04-data-model.md Section 4):\r\n * 파일: {file}:{line}\r\n * 컴포넌트: <{componentName}>\r\n * 라이브러리: {library} (if present)\r\n * 메모: {ann.memo} (if present)\r\n *\r\n * 요청: {memo}\r\n */\r\n static buildPrompt(annotations: AnnotationSet, memo: string): string {\r\n const parts: string[] = []\r\n\r\n // Process each annotation\r\n for (const ann of annotations.annotations) {\r\n if (ann.source) {\r\n parts.push(`파일: ${ann.source.file}:${ann.source.line}`)\r\n parts.push(`컴포넌트: <${ann.source.componentName}>`)\r\n if (ann.source.library) {\r\n parts.push(`라이브러리: ${ann.source.library}`)\r\n }\r\n }\r\n if (ann.memo) {\r\n parts.push(`메모: ${ann.memo}`)\r\n }\r\n }\r\n\r\n // Add the main request\r\n if (parts.length > 0) {\r\n parts.push(`\\n요청: ${memo}`)\r\n return parts.join('\\n')\r\n } else {\r\n return `요청: ${memo}`\r\n }\r\n }\r\n\r\n /**\r\n * Build prompt with conversation history context\r\n */\r\n static buildContextualPrompt(\r\n annotations: AnnotationSet,\r\n memo: string,\r\n history: ChatMessage[]\r\n ): string {\r\n const basePrompt = this.buildPrompt(annotations, memo)\r\n\r\n if (history.length === 0) {\r\n return basePrompt\r\n }\r\n\r\n // Create history summary (last 3 messages for context)\r\n const recentHistory = history.slice(-3)\r\n const historyLines: string[] = []\r\n\r\n for (const msg of recentHistory) {\r\n const role = msg.role === 'user' ? '사용자' : '어시스턴트'\r\n historyLines.push(`${role}: ${msg.content}`)\r\n }\r\n\r\n // Combine history and current prompt\r\n const contextPrefix = '이전 대화:\\n' + historyLines.join('\\n') + '\\n\\n'\r\n return contextPrefix + basePrompt\r\n }\r\n\r\n /**\r\n * Extract file paths referenced in annotations\r\n */\r\n static extractFileReferences(annotations: AnnotationSet): string[] {\r\n const files = new Set<string>()\r\n\r\n for (const ann of annotations.annotations) {\r\n if (ann.source?.file) {\r\n files.add(ann.source.file)\r\n }\r\n }\r\n\r\n return Array.from(files).sort()\r\n }\r\n}\r\n","import { spawn as cpSpawn, type ChildProcess } from 'node:child_process'\r\nimport { createInterface } from 'node:readline'\r\nimport type { FileChange } from '@claude-canvas/core'\r\nimport type { ClaudeCanvasOptions } from './types.js'\r\n\r\ninterface StreamJsonMessage {\r\n type: string\r\n content?: Array<{\r\n type: string\r\n text?: string\r\n name?: string\r\n input?: Record<string, unknown>\r\n }>\r\n}\r\n\r\nconst WRITE_TOOLS = new Set(['Write', 'Edit'])\r\nconst SHUTDOWN_TIMEOUT = 5000\r\n\r\nexport class ClaudeBridge {\r\n private process: ChildProcess | null = null\r\n private fileChangeListeners: Array<(change: FileChange) => void> = []\r\n private options: ClaudeCanvasOptions\r\n\r\n constructor(options: ClaudeCanvasOptions) {\r\n this.options = options\r\n }\r\n\r\n async spawn(): Promise<void> {\r\n if (this.process) {\r\n throw new Error('Claude subprocess already running')\r\n }\r\n\r\n const args = [\r\n '--print', '-',\r\n '--output-format', 'stream-json',\r\n ]\r\n\r\n if (this.options.permissionMode === 'acceptEdits') {\r\n args.push('--allowedTools', 'Edit,Write,Bash(read-only)')\r\n }\r\n\r\n this.process = cpSpawn('claude', args, {\r\n stdio: ['pipe', 'pipe', 'pipe'],\r\n env: { ...process.env },\r\n })\r\n\r\n this.process.on('error', (err) => {\r\n console.error('[ClaudeBridge] subprocess error:', err.message)\r\n })\r\n }\r\n\r\n async *sendPrompt(prompt: string): AsyncGenerator<string> {\r\n if (!this.process?.stdin || !this.process?.stdout) {\r\n throw new Error('Claude subprocess not running')\r\n }\r\n\r\n // Write prompt to stdin\r\n this.process.stdin.write(prompt + '\\n')\r\n\r\n const rl = createInterface({ input: this.process.stdout })\r\n\r\n for await (const line of rl) {\r\n if (!line.trim()) continue\r\n\r\n let msg: StreamJsonMessage\r\n try {\r\n msg = JSON.parse(line) as StreamJsonMessage\r\n } catch {\r\n continue\r\n }\r\n\r\n // Detect file changes from tool_use events\r\n this.detectFileChanges(msg)\r\n\r\n // Extract text content\r\n if (msg.content) {\r\n for (const block of msg.content) {\r\n if (block.type === 'text' && block.text) {\r\n yield block.text\r\n }\r\n }\r\n }\r\n\r\n // Result message signals stream end\r\n if (msg.type === 'result') {\r\n break\r\n }\r\n }\r\n }\r\n\r\n onFileChanged(callback: (change: FileChange) => void): void {\r\n this.fileChangeListeners.push(callback)\r\n }\r\n\r\n async shutdown(): Promise<void> {\r\n if (!this.process) return\r\n\r\n const proc = this.process\r\n this.process = null\r\n\r\n proc.stdin?.end()\r\n proc.kill('SIGTERM')\r\n\r\n await new Promise<void>((resolve) => {\r\n const timer = setTimeout(() => {\r\n proc.kill('SIGKILL')\r\n resolve()\r\n }, SHUTDOWN_TIMEOUT)\r\n\r\n proc.on('exit', () => {\r\n clearTimeout(timer)\r\n resolve()\r\n })\r\n })\r\n }\r\n\r\n async restart(): Promise<void> {\r\n await this.shutdown()\r\n await this.spawn()\r\n }\r\n\r\n private detectFileChanges(msg: StreamJsonMessage): void {\r\n if (!msg.content || this.fileChangeListeners.length === 0) return\r\n\r\n for (const block of msg.content) {\r\n if (block.type === 'tool_use' && block.name && WRITE_TOOLS.has(block.name)) {\r\n const filePath = block.input?.file_path as string | undefined\r\n if (filePath) {\r\n const change: FileChange = {\r\n file: filePath,\r\n action: block.name === 'Write' ? 'create' : 'edit',\r\n linesAdded: 0,\r\n linesRemoved: 0,\r\n }\r\n for (const listener of this.fileChangeListeners) {\r\n listener(change)\r\n }\r\n }\r\n }\r\n }\r\n }\r\n}\r\n","import { execFile } from 'node:child_process'\r\nimport type { CanvasSession, MergeResult } from './types.js'\r\n\r\nfunction git(...args: string[]): Promise<string> {\r\n return new Promise((resolve, reject) => {\r\n execFile('git', args, (err, stdout) => {\r\n if (err) reject(err)\r\n else resolve(stdout.trim())\r\n })\r\n })\r\n}\r\n\r\nexport class GitManager {\r\n private session: CanvasSession | null = null\r\n\r\n async startSession(options: { branchPrefix?: string } = {}): Promise<CanvasSession> {\r\n const prefix = options.branchPrefix ?? 'canvas/session'\r\n const timestamp = new Date().toISOString().replace(/[-:T]/g, '').slice(0, 13)\r\n const branch = `${prefix}-${timestamp}`\r\n const baseBranch = await git('branch', '--show-current')\r\n\r\n // Stash uncommitted changes if working directory is dirty\r\n const status = await git('status', '--porcelain')\r\n let stashRef: string | undefined\r\n if (status) {\r\n await git('stash', 'push', '-m', 'claude-canvas-session')\r\n stashRef = (await git('stash', 'list', '--max-count=1')).split(':')[0]\r\n }\r\n\r\n await git('checkout', '-b', branch)\r\n\r\n this.session = {\r\n id: crypto.randomUUID(),\r\n branch,\r\n baseBranch,\r\n status: 'active',\r\n messages: [],\r\n changedFiles: [],\r\n commitCount: 0,\r\n startedAt: Date.now(),\r\n stashRef,\r\n }\r\n return this.session\r\n }\r\n\r\n async endSession(action: 'merge' | 'discard'): Promise<MergeResult> {\r\n if (!this.session) throw new Error('No active session')\r\n\r\n const { baseBranch, branch, stashRef, id } = this.session\r\n\r\n if (action === 'merge') {\r\n await git('checkout', baseBranch)\r\n await git('merge', '--squash', branch)\r\n await git('commit', '-m', `feat: claude canvas session ${id}`)\r\n const hash = await git('rev-parse', 'HEAD')\r\n await git('branch', '-D', branch)\r\n if (stashRef) await git('stash', 'pop')\r\n this.session.status = 'merged'\r\n this.session = null\r\n return { success: true, commitHash: hash }\r\n } else {\r\n await git('checkout', baseBranch)\r\n await git('branch', '-D', branch)\r\n if (stashRef) await git('stash', 'pop')\r\n this.session.status = 'discarded'\r\n this.session = null\r\n return { success: true }\r\n }\r\n }\r\n\r\n async getChangedFiles(): Promise<string[]> {\r\n if (!this.session) return []\r\n const diff = await git('diff', '--name-only', this.session.baseBranch)\r\n return diff ? diff.split('\\n') : []\r\n }\r\n\r\n async autoCommit(message: string): Promise<string> {\r\n await git('add', '-A')\r\n await git('commit', '-m', message)\r\n if (this.session) this.session.commitCount++\r\n return git('rev-parse', 'HEAD')\r\n }\r\n\r\n getSession(): CanvasSession | null {\r\n return this.session\r\n }\r\n}\r\n","import { execFile } from 'node:child_process'\r\nimport type { QAResult, QAStep, QAStepName, ClaudeCanvasOptions } from './types.js'\r\n\r\nfunction execAsync(cmd: string, args: string[]): Promise<string> {\r\n return new Promise((resolve, reject) => {\r\n execFile(cmd, args, (err, stdout) => {\r\n if (err) reject(err)\r\n else resolve(stdout)\r\n })\r\n })\r\n}\r\n\r\nexport class QARunner {\r\n private options: NonNullable<ClaudeCanvasOptions['qa']>\r\n\r\n constructor(options: ClaudeCanvasOptions['qa'] = {}) {\r\n this.options = options ?? {}\r\n }\r\n\r\n async *run(): AsyncGenerator<QAStep> {\r\n if (this.options.tsc !== false) {\r\n const step = await this.runStep('tsc', 'npx tsc --noEmit')\r\n yield step\r\n }\r\n\r\n if (this.options.eslint !== false) {\r\n const cmd = typeof this.options.eslint === 'string' ? this.options.eslint : 'npx eslint .'\r\n const step = await this.runStep('eslint', cmd)\r\n yield step\r\n }\r\n\r\n if (this.options.build !== false) {\r\n const step = await this.runStep('build', 'npm run build')\r\n yield step\r\n }\r\n\r\n if (this.options.test !== false) {\r\n const cmd = typeof this.options.test === 'string' ? this.options.test : 'npx vitest run'\r\n const step = await this.runStep('test', cmd)\r\n yield step\r\n }\r\n\r\n if (this.options.skills) {\r\n for (const skill of this.options.skills) {\r\n const step = await this.runStep('skill', skill)\r\n step.skillName = skill\r\n yield step\r\n }\r\n }\r\n }\r\n\r\n getResult(steps: QAStep[]): QAResult {\r\n return {\r\n steps,\r\n overallPass: steps.every(s => s.pass),\r\n duration: steps.reduce((sum, s) => sum + s.duration, 0),\r\n }\r\n }\r\n\r\n private async runStep(name: QAStepName, command: string): Promise<QAStep> {\r\n const start = Date.now()\r\n try {\r\n const [cmd, ...args] = command.split(' ')\r\n const output = await execAsync(cmd, args)\r\n return { name, pass: true, output, duration: Date.now() - start }\r\n } catch (err: unknown) {\r\n const output = err instanceof Error ? err.message : String(err)\r\n return { name, pass: false, output, duration: Date.now() - start }\r\n }\r\n }\r\n}\r\n"],"mappings":";AAIO,IAAM,uBAAuB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,uBAAuB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,eAAN,cAA2B,MAAM;AAAA,EACtC,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,aAAN,MAAiB;AAAA;AAAA,EAEtB,OAAO,mBAAmB,KAA4B;AACpD,QAAI;AACJ,QAAI;AACF,eAAS,KAAK,MAAM,GAAG;AAAA,IACzB,QAAQ;AACN,YAAM,IAAI,aAAa,cAAc;AAAA,IACvC;AAEA,QAAI,CAAC,UAAU,OAAO,WAAW,UAAU;AACzC,YAAM,IAAI,aAAa,2BAA2B;AAAA,IACpD;AAEA,UAAM,MAAM;AACZ,UAAM,OAAO,IAAI;AAEjB,QAAI,CAAC,QAAQ,OAAO,SAAS,UAAU;AACrC,YAAM,IAAI,aAAa,yBAAyB;AAAA,IAClD;AAEA,QAAI,CAAE,qBAA2C,SAAS,IAAI,GAAG;AAC/D,YAAM,IAAI,aAAa,gCAAgC,IAAI,EAAE;AAAA,IAC/D;AAGA,YAAQ,MAAM;AAAA,MACZ,KAAK;AACH,eAAO,EAAE,MAAM,uBAAuB;AAAA,MAExC,KAAK,sBAAsB;AACzB,cAAM,UAAU,IAAI;AACpB,YAAI,CAAC,WAAW,OAAO,YAAY,UAAU;AAC3C,gBAAM,IAAI,aAAa,qCAAqC;AAAA,QAC9D;AACA,cAAM,IAAI;AACV,YAAI,CAAC,EAAE,UAAU,OAAO,EAAE,WAAW,UAAU;AAC7C,gBAAM,IAAI,aAAa,mDAAmD;AAAA,QAC5E;AACA,YAAI,EAAE,WAAW,WAAW,EAAE,WAAW,WAAW;AAClD,gBAAM,IAAI,aAAa,qCAAqC;AAAA,QAC9D;AACA,eAAO,EAAE,MAAM,sBAAsB,SAAS,EAAE,QAAQ,EAAE,OAA8B,EAAE;AAAA,MAC5F;AAAA,MAEA,KAAK,mBAAmB;AACtB,cAAM,UAAU,IAAI;AACpB,YAAI,CAAC,WAAW,OAAO,YAAY,UAAU;AAC3C,gBAAM,IAAI,aAAa,kCAAkC;AAAA,QAC3D;AACA,cAAM,IAAI;AACV,YAAI,CAAC,EAAE,aAAa;AAClB,gBAAM,IAAI,aAAa,+CAA+C;AAAA,QACxE;AACA,YAAI,OAAO,EAAE,SAAS,UAAU;AAC9B,gBAAM,IAAI,aAAa,+CAA+C;AAAA,QACxE;AACA,eAAO;AAAA,UACL,MAAM;AAAA,UACN,SAAS,EAAE,aAAa,EAAE,aAA8B,MAAM,EAAE,KAAK;AAAA,QACvE;AAAA,MACF;AAAA,MAEA,KAAK,eAAe;AAClB,cAAM,UAAU,IAAI;AACpB,YAAI,CAAC,WAAW,OAAO,YAAY,UAAU;AAC3C,gBAAM,IAAI,aAAa,8BAA8B;AAAA,QACvD;AACA,cAAM,IAAI;AACV,YAAI,OAAO,EAAE,YAAY,UAAU;AACjC,gBAAM,IAAI,aAAa,8CAA8C;AAAA,QACvE;AACA,eAAO,EAAE,MAAM,eAAe,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE;AAAA,MAChE;AAAA,MAEA,KAAK;AACH,eAAO,EAAE,MAAM,kBAAkB;AAAA,MAEnC;AAEE,cAAM,IAAI,aAAa,2BAA2B,IAAI,EAAE;AAAA,IAC5D;AAAA,EACF;AAAA;AAAA,EAGA,OAAO,uBAAuB,KAA4B;AACxD,WAAO,KAAK,UAAU,GAAG;AAAA,EAC3B;AAAA;AAAA,EAGA,OAAO,mBAAmB,MAAuB;AAC/C,WAAQ,qBAA2C,SAAS,IAAI,KAAM,qBAA2C,SAAS,IAAI;AAAA,EAChI;AAAA;AAAA,EAGA,OAAO,mBAAmB,SAAiB,aAAqC;AAC9E,WAAO,EAAE,MAAM,gBAAgB,SAAS,EAAE,SAAS,YAAY,EAAE;AAAA,EACnE;AACF;;;AC7HO,IAAM,gBAAN,MAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWzB,OAAO,YAAY,aAA4B,MAAsB;AACnE,UAAM,QAAkB,CAAC;AAGzB,eAAW,OAAO,YAAY,aAAa;AACzC,UAAI,IAAI,QAAQ;AACd,cAAM,KAAK,iBAAO,IAAI,OAAO,IAAI,IAAI,IAAI,OAAO,IAAI,EAAE;AACtD,cAAM,KAAK,8BAAU,IAAI,OAAO,aAAa,GAAG;AAChD,YAAI,IAAI,OAAO,SAAS;AACtB,gBAAM,KAAK,mCAAU,IAAI,OAAO,OAAO,EAAE;AAAA,QAC3C;AAAA,MACF;AACA,UAAI,IAAI,MAAM;AACZ,cAAM,KAAK,iBAAO,IAAI,IAAI,EAAE;AAAA,MAC9B;AAAA,IACF;AAGA,QAAI,MAAM,SAAS,GAAG;AACpB,YAAM,KAAK;AAAA,gBAAS,IAAI,EAAE;AAC1B,aAAO,MAAM,KAAK,IAAI;AAAA,IACxB,OAAO;AACL,aAAO,iBAAO,IAAI;AAAA,IACpB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,sBACL,aACA,MACA,SACQ;AACR,UAAM,aAAa,KAAK,YAAY,aAAa,IAAI;AAErD,QAAI,QAAQ,WAAW,GAAG;AACxB,aAAO;AAAA,IACT;AAGA,UAAM,gBAAgB,QAAQ,MAAM,EAAE;AACtC,UAAM,eAAyB,CAAC;AAEhC,eAAW,OAAO,eAAe;AAC/B,YAAM,OAAO,IAAI,SAAS,SAAS,uBAAQ;AAC3C,mBAAa,KAAK,GAAG,IAAI,KAAK,IAAI,OAAO,EAAE;AAAA,IAC7C;AAGA,UAAM,gBAAgB,iCAAa,aAAa,KAAK,IAAI,IAAI;AAC7D,WAAO,gBAAgB;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,sBAAsB,aAAsC;AACjE,UAAM,QAAQ,oBAAI,IAAY;AAE9B,eAAW,OAAO,YAAY,aAAa;AACzC,UAAI,IAAI,QAAQ,MAAM;AACpB,cAAM,IAAI,IAAI,OAAO,IAAI;AAAA,MAC3B;AAAA,IACF;AAEA,WAAO,MAAM,KAAK,KAAK,EAAE,KAAK;AAAA,EAChC;AACF;;;ACjFA,SAAS,SAAS,eAAkC;AACpD,SAAS,uBAAuB;AAchC,IAAM,cAAc,oBAAI,IAAI,CAAC,SAAS,MAAM,CAAC;AAC7C,IAAM,mBAAmB;AAElB,IAAM,eAAN,MAAmB;AAAA,EAChB,UAA+B;AAAA,EAC/B,sBAA2D,CAAC;AAAA,EAC5D;AAAA,EAER,YAAY,SAA8B;AACxC,SAAK,UAAU;AAAA,EACjB;AAAA,EAEA,MAAM,QAAuB;AAC3B,QAAI,KAAK,SAAS;AAChB,YAAM,IAAI,MAAM,mCAAmC;AAAA,IACrD;AAEA,UAAM,OAAO;AAAA,MACX;AAAA,MAAW;AAAA,MACX;AAAA,MAAmB;AAAA,IACrB;AAEA,QAAI,KAAK,QAAQ,mBAAmB,eAAe;AACjD,WAAK,KAAK,kBAAkB,4BAA4B;AAAA,IAC1D;AAEA,SAAK,UAAU,QAAQ,UAAU,MAAM;AAAA,MACrC,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA,MAC9B,KAAK,EAAE,GAAG,QAAQ,IAAI;AAAA,IACxB,CAAC;AAED,SAAK,QAAQ,GAAG,SAAS,CAAC,QAAQ;AAChC,cAAQ,MAAM,oCAAoC,IAAI,OAAO;AAAA,IAC/D,CAAC;AAAA,EACH;AAAA,EAEA,OAAO,WAAW,QAAwC;AACxD,QAAI,CAAC,KAAK,SAAS,SAAS,CAAC,KAAK,SAAS,QAAQ;AACjD,YAAM,IAAI,MAAM,+BAA+B;AAAA,IACjD;AAGA,SAAK,QAAQ,MAAM,MAAM,SAAS,IAAI;AAEtC,UAAM,KAAK,gBAAgB,EAAE,OAAO,KAAK,QAAQ,OAAO,CAAC;AAEzD,qBAAiB,QAAQ,IAAI;AAC3B,UAAI,CAAC,KAAK,KAAK,EAAG;AAElB,UAAI;AACJ,UAAI;AACF,cAAM,KAAK,MAAM,IAAI;AAAA,MACvB,QAAQ;AACN;AAAA,MACF;AAGA,WAAK,kBAAkB,GAAG;AAG1B,UAAI,IAAI,SAAS;AACf,mBAAW,SAAS,IAAI,SAAS;AAC/B,cAAI,MAAM,SAAS,UAAU,MAAM,MAAM;AACvC,kBAAM,MAAM;AAAA,UACd;AAAA,QACF;AAAA,MACF;AAGA,UAAI,IAAI,SAAS,UAAU;AACzB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,cAAc,UAA8C;AAC1D,SAAK,oBAAoB,KAAK,QAAQ;AAAA,EACxC;AAAA,EAEA,MAAM,WAA0B;AAC9B,QAAI,CAAC,KAAK,QAAS;AAEnB,UAAM,OAAO,KAAK;AAClB,SAAK,UAAU;AAEf,SAAK,OAAO,IAAI;AAChB,SAAK,KAAK,SAAS;AAEnB,UAAM,IAAI,QAAc,CAAC,YAAY;AACnC,YAAM,QAAQ,WAAW,MAAM;AAC7B,aAAK,KAAK,SAAS;AACnB,gBAAQ;AAAA,MACV,GAAG,gBAAgB;AAEnB,WAAK,GAAG,QAAQ,MAAM;AACpB,qBAAa,KAAK;AAClB,gBAAQ;AAAA,MACV,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,UAAyB;AAC7B,UAAM,KAAK,SAAS;AACpB,UAAM,KAAK,MAAM;AAAA,EACnB;AAAA,EAEQ,kBAAkB,KAA8B;AACtD,QAAI,CAAC,IAAI,WAAW,KAAK,oBAAoB,WAAW,EAAG;AAE3D,eAAW,SAAS,IAAI,SAAS;AAC/B,UAAI,MAAM,SAAS,cAAc,MAAM,QAAQ,YAAY,IAAI,MAAM,IAAI,GAAG;AAC1E,cAAM,WAAW,MAAM,OAAO;AAC9B,YAAI,UAAU;AACZ,gBAAM,SAAqB;AAAA,YACzB,MAAM;AAAA,YACN,QAAQ,MAAM,SAAS,UAAU,WAAW;AAAA,YAC5C,YAAY;AAAA,YACZ,cAAc;AAAA,UAChB;AACA,qBAAW,YAAY,KAAK,qBAAqB;AAC/C,qBAAS,MAAM;AAAA,UACjB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;AC7IA,SAAS,gBAAgB;AAGzB,SAAS,OAAO,MAAiC;AAC/C,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,aAAS,OAAO,MAAM,CAAC,KAAK,WAAW;AACrC,UAAI,IAAK,QAAO,GAAG;AAAA,UACd,SAAQ,OAAO,KAAK,CAAC;AAAA,IAC5B,CAAC;AAAA,EACH,CAAC;AACH;AAEO,IAAM,aAAN,MAAiB;AAAA,EACd,UAAgC;AAAA,EAExC,MAAM,aAAa,UAAqC,CAAC,GAA2B;AAClF,UAAM,SAAS,QAAQ,gBAAgB;AACvC,UAAM,aAAY,oBAAI,KAAK,GAAE,YAAY,EAAE,QAAQ,UAAU,EAAE,EAAE,MAAM,GAAG,EAAE;AAC5E,UAAM,SAAS,GAAG,MAAM,IAAI,SAAS;AACrC,UAAM,aAAa,MAAM,IAAI,UAAU,gBAAgB;AAGvD,UAAM,SAAS,MAAM,IAAI,UAAU,aAAa;AAChD,QAAI;AACJ,QAAI,QAAQ;AACV,YAAM,IAAI,SAAS,QAAQ,MAAM,uBAAuB;AACxD,kBAAY,MAAM,IAAI,SAAS,QAAQ,eAAe,GAAG,MAAM,GAAG,EAAE,CAAC;AAAA,IACvE;AAEA,UAAM,IAAI,YAAY,MAAM,MAAM;AAElC,SAAK,UAAU;AAAA,MACb,IAAI,OAAO,WAAW;AAAA,MACtB;AAAA,MACA;AAAA,MACA,QAAQ;AAAA,MACR,UAAU,CAAC;AAAA,MACX,cAAc,CAAC;AAAA,MACf,aAAa;AAAA,MACb,WAAW,KAAK,IAAI;AAAA,MACpB;AAAA,IACF;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,WAAW,QAAmD;AAClE,QAAI,CAAC,KAAK,QAAS,OAAM,IAAI,MAAM,mBAAmB;AAEtD,UAAM,EAAE,YAAY,QAAQ,UAAU,GAAG,IAAI,KAAK;AAElD,QAAI,WAAW,SAAS;AACtB,YAAM,IAAI,YAAY,UAAU;AAChC,YAAM,IAAI,SAAS,YAAY,MAAM;AACrC,YAAM,IAAI,UAAU,MAAM,+BAA+B,EAAE,EAAE;AAC7D,YAAM,OAAO,MAAM,IAAI,aAAa,MAAM;AAC1C,YAAM,IAAI,UAAU,MAAM,MAAM;AAChC,UAAI,SAAU,OAAM,IAAI,SAAS,KAAK;AACtC,WAAK,QAAQ,SAAS;AACtB,WAAK,UAAU;AACf,aAAO,EAAE,SAAS,MAAM,YAAY,KAAK;AAAA,IAC3C,OAAO;AACL,YAAM,IAAI,YAAY,UAAU;AAChC,YAAM,IAAI,UAAU,MAAM,MAAM;AAChC,UAAI,SAAU,OAAM,IAAI,SAAS,KAAK;AACtC,WAAK,QAAQ,SAAS;AACtB,WAAK,UAAU;AACf,aAAO,EAAE,SAAS,KAAK;AAAA,IACzB;AAAA,EACF;AAAA,EAEA,MAAM,kBAAqC;AACzC,QAAI,CAAC,KAAK,QAAS,QAAO,CAAC;AAC3B,UAAM,OAAO,MAAM,IAAI,QAAQ,eAAe,KAAK,QAAQ,UAAU;AACrE,WAAO,OAAO,KAAK,MAAM,IAAI,IAAI,CAAC;AAAA,EACpC;AAAA,EAEA,MAAM,WAAW,SAAkC;AACjD,UAAM,IAAI,OAAO,IAAI;AACrB,UAAM,IAAI,UAAU,MAAM,OAAO;AACjC,QAAI,KAAK,QAAS,MAAK,QAAQ;AAC/B,WAAO,IAAI,aAAa,MAAM;AAAA,EAChC;AAAA,EAEA,aAAmC;AACjC,WAAO,KAAK;AAAA,EACd;AACF;;;ACtFA,SAAS,YAAAA,iBAAgB;AAGzB,SAAS,UAAU,KAAa,MAAiC;AAC/D,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,IAAAA,UAAS,KAAK,MAAM,CAAC,KAAK,WAAW;AACnC,UAAI,IAAK,QAAO,GAAG;AAAA,UACd,SAAQ,MAAM;AAAA,IACrB,CAAC;AAAA,EACH,CAAC;AACH;AAEO,IAAM,WAAN,MAAe;AAAA,EACZ;AAAA,EAER,YAAY,UAAqC,CAAC,GAAG;AACnD,SAAK,UAAU,WAAW,CAAC;AAAA,EAC7B;AAAA,EAEA,OAAO,MAA8B;AACnC,QAAI,KAAK,QAAQ,QAAQ,OAAO;AAC9B,YAAM,OAAO,MAAM,KAAK,QAAQ,OAAO,kBAAkB;AACzD,YAAM;AAAA,IACR;AAEA,QAAI,KAAK,QAAQ,WAAW,OAAO;AACjC,YAAM,MAAM,OAAO,KAAK,QAAQ,WAAW,WAAW,KAAK,QAAQ,SAAS;AAC5E,YAAM,OAAO,MAAM,KAAK,QAAQ,UAAU,GAAG;AAC7C,YAAM;AAAA,IACR;AAEA,QAAI,KAAK,QAAQ,UAAU,OAAO;AAChC,YAAM,OAAO,MAAM,KAAK,QAAQ,SAAS,eAAe;AACxD,YAAM;AAAA,IACR;AAEA,QAAI,KAAK,QAAQ,SAAS,OAAO;AAC/B,YAAM,MAAM,OAAO,KAAK,QAAQ,SAAS,WAAW,KAAK,QAAQ,OAAO;AACxE,YAAM,OAAO,MAAM,KAAK,QAAQ,QAAQ,GAAG;AAC3C,YAAM;AAAA,IACR;AAEA,QAAI,KAAK,QAAQ,QAAQ;AACvB,iBAAW,SAAS,KAAK,QAAQ,QAAQ;AACvC,cAAM,OAAO,MAAM,KAAK,QAAQ,SAAS,KAAK;AAC9C,aAAK,YAAY;AACjB,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAAA,EAEA,UAAU,OAA2B;AACnC,WAAO;AAAA,MACL;AAAA,MACA,aAAa,MAAM,MAAM,OAAK,EAAE,IAAI;AAAA,MACpC,UAAU,MAAM,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,UAAU,CAAC;AAAA,IACxD;AAAA,EACF;AAAA,EAEA,MAAc,QAAQ,MAAkB,SAAkC;AACxE,UAAM,QAAQ,KAAK,IAAI;AACvB,QAAI;AACF,YAAM,CAAC,KAAK,GAAG,IAAI,IAAI,QAAQ,MAAM,GAAG;AACxC,YAAM,SAAS,MAAM,UAAU,KAAK,IAAI;AACxC,aAAO,EAAE,MAAM,MAAM,MAAM,QAAQ,UAAU,KAAK,IAAI,IAAI,MAAM;AAAA,IAClE,SAAS,KAAc;AACrB,YAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC9D,aAAO,EAAE,MAAM,MAAM,OAAO,QAAQ,UAAU,KAAK,IAAI,IAAI,MAAM;AAAA,IACnE;AAAA,EACF;AACF;","names":["execFile"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@claude-canvas/bridge",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Claude Canvas server-side bridge (Claude Agent SDK, Git, QA, WebSocket)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "https://github.com/claude-canvas/claude-canvas.git",
|
|
23
|
+
"directory": "packages/bridge"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@claude-canvas/core": "0.2.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"tsup": "^8.4.0",
|
|
30
|
+
"vitest": "^3.0.0",
|
|
31
|
+
"typescript": "^5.7.0",
|
|
32
|
+
"@types/node": "^22.0.0"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@anthropic-ai/claude-agent-sdk": ">=0.1.0"
|
|
36
|
+
},
|
|
37
|
+
"peerDependenciesMeta": {
|
|
38
|
+
"@anthropic-ai/claude-agent-sdk": {
|
|
39
|
+
"optional": true
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsup",
|
|
44
|
+
"test": "vitest run",
|
|
45
|
+
"test:watch": "vitest",
|
|
46
|
+
"lint": "eslint src/",
|
|
47
|
+
"typecheck": "tsc --noEmit",
|
|
48
|
+
"clean": "rm -rf dist"
|
|
49
|
+
}
|
|
50
|
+
}
|