@codewithdan/zingit 0.16.1 → 0.17.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/AGENTS.md +13 -13
- package/CHANGELOG.md +2 -0
- package/README.md +17 -17
- package/bin/cli.js +2 -2
- package/client/dist/zingit-client.js +46 -46
- package/package.json +3 -3
- package/server/dist/agents/base.d.ts +2 -2
- package/server/dist/agents/base.js +13 -13
- package/server/dist/agents/claude.js +1 -1
- package/server/dist/agents/codex.js +1 -1
- package/server/dist/agents/copilot.js +1 -1
- package/server/dist/handlers/messageHandlers.js +9 -9
- package/server/dist/services/git-manager.d.ts +5 -5
- package/server/dist/services/git-manager.js +6 -6
- package/server/dist/services/index.d.ts +1 -1
- package/server/dist/types.d.ts +2 -2
- package/server/dist/validation/payload.d.ts +1 -1
- package/server/dist/validation/payload.js +25 -25
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codewithdan/zingit",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "AI-powered UI
|
|
3
|
+
"version": "0.17.1",
|
|
4
|
+
"description": "AI-powered UI marker tool - point, mark, and let AI fix it",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=22.0.0"
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
},
|
|
35
35
|
"keywords": [
|
|
36
36
|
"ui",
|
|
37
|
-
"
|
|
37
|
+
"marker",
|
|
38
38
|
"ai",
|
|
39
39
|
"agent",
|
|
40
40
|
"claude",
|
|
@@ -11,8 +11,8 @@ export declare abstract class BaseAgent implements Agent {
|
|
|
11
11
|
*/
|
|
12
12
|
protected formatPromptWithImageMetadata(prompt: string, images?: ImageContent[]): string;
|
|
13
13
|
/**
|
|
14
|
-
* Extract images from batch data
|
|
15
|
-
* Returns an array of ImageContent objects for
|
|
14
|
+
* Extract images from batch data markers
|
|
15
|
+
* Returns an array of ImageContent objects for markers that have screenshots
|
|
16
16
|
*/
|
|
17
17
|
extractImages(data: BatchData): ImageContent[];
|
|
18
18
|
formatPrompt(data: BatchData, projectDir: string): string;
|
|
@@ -18,12 +18,12 @@ export class BaseAgent {
|
|
|
18
18
|
return `${header}---\n\n${prompt}`;
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
|
-
* Extract images from batch data
|
|
22
|
-
* Returns an array of ImageContent objects for
|
|
21
|
+
* Extract images from batch data markers
|
|
22
|
+
* Returns an array of ImageContent objects for markers that have screenshots
|
|
23
23
|
*/
|
|
24
24
|
extractImages(data) {
|
|
25
25
|
const images = [];
|
|
26
|
-
data.
|
|
26
|
+
data.markers.forEach((ann, i) => {
|
|
27
27
|
if (ann.screenshot) {
|
|
28
28
|
let base64Data = ann.screenshot;
|
|
29
29
|
let mediaType = 'image/png'; // Default
|
|
@@ -48,13 +48,13 @@ export class BaseAgent {
|
|
|
48
48
|
// 3. Check padding is correct
|
|
49
49
|
const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;
|
|
50
50
|
if (!base64Data || !base64Regex.test(base64Data) || base64Data.length % 4 !== 0) {
|
|
51
|
-
console.warn(`ZingIt: Invalid base64 data in
|
|
52
|
-
return; // Skip this
|
|
51
|
+
console.warn(`ZingIt: Invalid base64 data in marker ${i + 1}, skipping screenshot`);
|
|
52
|
+
return; // Skip this marker's screenshot
|
|
53
53
|
}
|
|
54
54
|
// Check image size limit (base64 is ~33% larger than binary)
|
|
55
55
|
const estimatedBinarySize = Math.ceil(base64Data.length * 0.75);
|
|
56
56
|
if (estimatedBinarySize > MAX_IMAGE_SIZE_BYTES) {
|
|
57
|
-
console.warn(`ZingIt: Image in
|
|
57
|
+
console.warn(`ZingIt: Image in marker ${i + 1} exceeds ${MAX_IMAGE_SIZE_BYTES / 1024 / 1024}MB limit, skipping`);
|
|
58
58
|
return; // Skip oversized image
|
|
59
59
|
}
|
|
60
60
|
// Validate that base64 can be decoded (catches corrupted data)
|
|
@@ -62,13 +62,13 @@ export class BaseAgent {
|
|
|
62
62
|
Buffer.from(base64Data, 'base64');
|
|
63
63
|
}
|
|
64
64
|
catch (err) {
|
|
65
|
-
console.warn(`ZingIt: Failed to decode base64 in
|
|
66
|
-
return; // Skip this
|
|
65
|
+
console.warn(`ZingIt: Failed to decode base64 in marker ${i + 1}, skipping screenshot:`, err);
|
|
66
|
+
return; // Skip this marker's screenshot
|
|
67
67
|
}
|
|
68
68
|
images.push({
|
|
69
69
|
base64: base64Data,
|
|
70
70
|
mediaType,
|
|
71
|
-
label: `Screenshot of
|
|
71
|
+
label: `Screenshot of Marker ${i + 1}: ${ann.identifier}`
|
|
72
72
|
});
|
|
73
73
|
}
|
|
74
74
|
});
|
|
@@ -81,10 +81,10 @@ Page: ${data.pageTitle}
|
|
|
81
81
|
URL: ${data.pageUrl}
|
|
82
82
|
|
|
83
83
|
`;
|
|
84
|
-
data.
|
|
84
|
+
data.markers.forEach((ann, i) => {
|
|
85
85
|
prompt += `---
|
|
86
86
|
|
|
87
|
-
##
|
|
87
|
+
## Marker ${i + 1}: ${ann.identifier}
|
|
88
88
|
|
|
89
89
|
**Requested Change:** ${ann.notes}
|
|
90
90
|
|
|
@@ -108,8 +108,8 @@ ${ann.parentContext ? `**Parent Path:** \`${ann.parentContext}\`` : ''}
|
|
|
108
108
|
|
|
109
109
|
`;
|
|
110
110
|
});
|
|
111
|
-
// Check if any
|
|
112
|
-
const hasScreenshots = data.
|
|
111
|
+
// Check if any markers have screenshots
|
|
112
|
+
const hasScreenshots = data.markers.some(ann => ann.screenshot);
|
|
113
113
|
prompt += `
|
|
114
114
|
CRITICAL INSTRUCTIONS:
|
|
115
115
|
1. CAREFULLY identify the CORRECT element to modify:
|
|
@@ -84,7 +84,7 @@ CRITICAL EFFICIENCY RULES:
|
|
|
84
84
|
2. Make the requested change immediately - don't explore or explain unless there's ambiguity
|
|
85
85
|
3. For simple changes (text, styles, attributes), be concise - just do it and confirm
|
|
86
86
|
4. Only search/explore if the selector doesn't match or you need to understand complex context
|
|
87
|
-
5. Avoid explaining what
|
|
87
|
+
5. Avoid explaining what markers are or describing the codebase unnecessarily
|
|
88
88
|
|
|
89
89
|
WHEN TO BE BRIEF (90% of cases):
|
|
90
90
|
- Text changes: Find, change, confirm (1-2 sentences)
|
|
@@ -79,7 +79,7 @@ export class CodexAgent extends BaseAgent {
|
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
// Add system instructions and main prompt
|
|
82
|
-
const systemInstructions = `You are a UI debugging assistant. When given
|
|
82
|
+
const systemInstructions = `You are a UI debugging assistant. When given markers about UI elements, search for the corresponding code using the selectors and HTML context provided, then make the requested changes.
|
|
83
83
|
|
|
84
84
|
When screenshots are provided, use them to:
|
|
85
85
|
- Better understand the visual context and styling of the elements
|
|
@@ -49,7 +49,7 @@ export class CopilotAgent extends BaseAgent {
|
|
|
49
49
|
<context>
|
|
50
50
|
You are a UI debugging assistant working in the project directory: ${projectDir}
|
|
51
51
|
|
|
52
|
-
When given
|
|
52
|
+
When given markers about UI elements:
|
|
53
53
|
1. Search for the corresponding code using the selectors and HTML context provided
|
|
54
54
|
2. Make the requested changes in the project at ${projectDir}
|
|
55
55
|
3. Be thorough in finding the right files and making precise edits
|
|
@@ -116,7 +116,7 @@ export async function handleBatch(ws, state, msg, deps) {
|
|
|
116
116
|
try {
|
|
117
117
|
console.log('[Batch] Creating checkpoint...');
|
|
118
118
|
const checkpoint = await state.gitManager.createCheckpoint({
|
|
119
|
-
|
|
119
|
+
markers: batchData.markers,
|
|
120
120
|
pageUrl: batchData.pageUrl,
|
|
121
121
|
pageTitle: batchData.pageTitle,
|
|
122
122
|
agentName: state.agentName,
|
|
@@ -128,7 +128,7 @@ export async function handleBatch(ws, state, msg, deps) {
|
|
|
128
128
|
checkpoint: {
|
|
129
129
|
id: checkpoint.id,
|
|
130
130
|
timestamp: checkpoint.timestamp,
|
|
131
|
-
|
|
131
|
+
markers: checkpoint.markers,
|
|
132
132
|
filesModified: 0,
|
|
133
133
|
linesChanged: 0,
|
|
134
134
|
agentName: checkpoint.agentName,
|
|
@@ -169,12 +169,12 @@ export async function handleBatch(ws, state, msg, deps) {
|
|
|
169
169
|
else {
|
|
170
170
|
console.log('[Batch] Reusing existing session');
|
|
171
171
|
}
|
|
172
|
-
// Log user's
|
|
173
|
-
console.log('[Batch]
|
|
174
|
-
if (batchData.
|
|
175
|
-
batchData.
|
|
176
|
-
const notePreview =
|
|
177
|
-
console.log(`[Batch]
|
|
172
|
+
// Log user's markers before formatting
|
|
173
|
+
console.log('[Batch] Marker count:', batchData.markers?.length || 0);
|
|
174
|
+
if (batchData.markers && batchData.markers.length > 0) {
|
|
175
|
+
batchData.markers.forEach((m, idx) => {
|
|
176
|
+
const notePreview = m.notes?.substring(0, 200) || '(no notes)';
|
|
177
|
+
console.log(`[Batch] Marker ${idx + 1}: ${notePreview}`);
|
|
178
178
|
});
|
|
179
179
|
}
|
|
180
180
|
if (batchData.pageUrl) {
|
|
@@ -229,7 +229,7 @@ export async function handleMessage(ws, state, msg, deps) {
|
|
|
229
229
|
return;
|
|
230
230
|
}
|
|
231
231
|
console.log('[Message] ===== Request started =====');
|
|
232
|
-
// Create session if it doesn't exist (allows direct messaging without
|
|
232
|
+
// Create session if it doesn't exist (allows direct messaging without markers)
|
|
233
233
|
if (!state.session) {
|
|
234
234
|
if (!state.agent) {
|
|
235
235
|
console.warn('[ZingIt] No agent selected for message');
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Marker } from '../types.js';
|
|
2
2
|
export interface Checkpoint {
|
|
3
3
|
id: string;
|
|
4
4
|
timestamp: string;
|
|
5
5
|
commitHash: string;
|
|
6
6
|
branchName: string;
|
|
7
|
-
|
|
7
|
+
markers: MarkerSummary[];
|
|
8
8
|
pageUrl: string;
|
|
9
9
|
pageTitle: string;
|
|
10
10
|
agentName: string;
|
|
@@ -12,7 +12,7 @@ export interface Checkpoint {
|
|
|
12
12
|
filesModified: number;
|
|
13
13
|
linesChanged: number;
|
|
14
14
|
}
|
|
15
|
-
export interface
|
|
15
|
+
export interface MarkerSummary {
|
|
16
16
|
id: string;
|
|
17
17
|
identifier: string;
|
|
18
18
|
notes: string;
|
|
@@ -32,7 +32,7 @@ export interface ChangeHistory {
|
|
|
32
32
|
export interface CheckpointInfo {
|
|
33
33
|
id: string;
|
|
34
34
|
timestamp: string;
|
|
35
|
-
|
|
35
|
+
markers: MarkerSummary[];
|
|
36
36
|
filesModified: number;
|
|
37
37
|
linesChanged: number;
|
|
38
38
|
agentName: string;
|
|
@@ -62,7 +62,7 @@ export declare class GitManager {
|
|
|
62
62
|
* Create a checkpoint before AI modifications
|
|
63
63
|
*/
|
|
64
64
|
createCheckpoint(metadata: {
|
|
65
|
-
|
|
65
|
+
markers: Marker[];
|
|
66
66
|
pageUrl: string;
|
|
67
67
|
pageTitle: string;
|
|
68
68
|
agentName: string;
|
|
@@ -114,10 +114,10 @@ export class GitManager {
|
|
|
114
114
|
timestamp: new Date().toISOString(),
|
|
115
115
|
commitHash: commitHash.trim(),
|
|
116
116
|
branchName: status.branch,
|
|
117
|
-
|
|
118
|
-
id:
|
|
119
|
-
identifier:
|
|
120
|
-
notes:
|
|
117
|
+
markers: metadata.markers.map((m) => ({
|
|
118
|
+
id: m.id,
|
|
119
|
+
identifier: m.identifier,
|
|
120
|
+
notes: m.notes,
|
|
121
121
|
})),
|
|
122
122
|
pageUrl: metadata.pageUrl,
|
|
123
123
|
pageTitle: metadata.pageTitle,
|
|
@@ -188,7 +188,7 @@ export class GitManager {
|
|
|
188
188
|
if (fileChanges.length > 0) {
|
|
189
189
|
try {
|
|
190
190
|
await execFileAsync('git', ['add', '-A'], { cwd: this.projectDir });
|
|
191
|
-
const identifiers = checkpoint.
|
|
191
|
+
const identifiers = checkpoint.markers.map((a) => sanitizeForGit(a.identifier)).join(', ');
|
|
192
192
|
const commitMsg = `[ZingIt] ${identifiers}`;
|
|
193
193
|
// Use execFile with array args to avoid shell injection
|
|
194
194
|
await execFileAsync('git', ['commit', '-m', commitMsg], { cwd: this.projectDir });
|
|
@@ -266,7 +266,7 @@ export class GitManager {
|
|
|
266
266
|
return history.checkpoints.map((cp, index) => ({
|
|
267
267
|
id: cp.id,
|
|
268
268
|
timestamp: cp.timestamp,
|
|
269
|
-
|
|
269
|
+
markers: cp.markers,
|
|
270
270
|
filesModified: cp.filesModified,
|
|
271
271
|
linesChanged: cp.linesChanged,
|
|
272
272
|
agentName: cp.agentName,
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { GitManager, GitManagerError } from './git-manager.js';
|
|
2
|
-
export type { Checkpoint, CheckpointInfo, ChangeHistory, FileChange,
|
|
2
|
+
export type { Checkpoint, CheckpointInfo, ChangeHistory, FileChange, MarkerSummary } from './git-manager.js';
|
package/server/dist/types.d.ts
CHANGED
|
@@ -29,7 +29,7 @@ export interface AgentSession {
|
|
|
29
29
|
destroy(): Promise<void>;
|
|
30
30
|
getSessionId?(): string | null;
|
|
31
31
|
}
|
|
32
|
-
export interface
|
|
32
|
+
export interface Marker {
|
|
33
33
|
id: string;
|
|
34
34
|
selector: string;
|
|
35
35
|
identifier: string;
|
|
@@ -46,7 +46,7 @@ export interface Annotation {
|
|
|
46
46
|
export interface BatchData {
|
|
47
47
|
pageUrl: string;
|
|
48
48
|
pageTitle: string;
|
|
49
|
-
|
|
49
|
+
markers: Marker[];
|
|
50
50
|
projectDir?: string;
|
|
51
51
|
}
|
|
52
52
|
export type WSIncomingType = 'batch' | 'message' | 'reset' | 'stop' | 'get_agents' | 'select_agent' | 'get_history' | 'undo' | 'revert_to' | 'clear_history';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BatchData } from '../types.js';
|
|
2
|
-
export declare const
|
|
2
|
+
export declare const MAX_MARKERS = 50;
|
|
3
3
|
export declare const MAX_HTML_LENGTH = 50000;
|
|
4
4
|
export declare const MAX_NOTES_LENGTH = 5000;
|
|
5
5
|
export declare const MAX_SELECTOR_LENGTH = 1000;
|
|
@@ -2,33 +2,33 @@
|
|
|
2
2
|
// ============================================
|
|
3
3
|
// Payload Validation
|
|
4
4
|
// ============================================
|
|
5
|
-
export const
|
|
5
|
+
export const MAX_MARKERS = 50;
|
|
6
6
|
export const MAX_HTML_LENGTH = 50000;
|
|
7
7
|
export const MAX_NOTES_LENGTH = 5000;
|
|
8
8
|
export const MAX_SELECTOR_LENGTH = 1000;
|
|
9
9
|
export const MAX_SCREENSHOT_SIZE = 5000000; // ~5MB base64 (matches Claude API limit)
|
|
10
10
|
const VALID_STATUSES = ['pending', 'processing', 'completed'];
|
|
11
|
-
function
|
|
12
|
-
if (!
|
|
13
|
-
return { valid: false, error: `
|
|
11
|
+
function validateMarker(marker, index) {
|
|
12
|
+
if (!marker.id || typeof marker.id !== 'string') {
|
|
13
|
+
return { valid: false, error: `Marker ${index}: missing or invalid id` };
|
|
14
14
|
}
|
|
15
|
-
if (!
|
|
16
|
-
return { valid: false, error: `
|
|
15
|
+
if (!marker.identifier || typeof marker.identifier !== 'string') {
|
|
16
|
+
return { valid: false, error: `Marker ${index}: missing or invalid identifier` };
|
|
17
17
|
}
|
|
18
|
-
if (
|
|
19
|
-
return { valid: false, error: `
|
|
18
|
+
if (marker.status && !VALID_STATUSES.includes(marker.status)) {
|
|
19
|
+
return { valid: false, error: `Marker ${index}: invalid status '${marker.status}'` };
|
|
20
20
|
}
|
|
21
|
-
if (
|
|
22
|
-
return { valid: false, error: `
|
|
21
|
+
if (marker.selector && marker.selector.length > MAX_SELECTOR_LENGTH) {
|
|
22
|
+
return { valid: false, error: `Marker ${index}: selector too long (max ${MAX_SELECTOR_LENGTH})` };
|
|
23
23
|
}
|
|
24
|
-
if (
|
|
25
|
-
return { valid: false, error: `
|
|
24
|
+
if (marker.html && marker.html.length > MAX_HTML_LENGTH) {
|
|
25
|
+
return { valid: false, error: `Marker ${index}: html too long (max ${MAX_HTML_LENGTH})` };
|
|
26
26
|
}
|
|
27
|
-
if (
|
|
28
|
-
return { valid: false, error: `
|
|
27
|
+
if (marker.notes && marker.notes.length > MAX_NOTES_LENGTH) {
|
|
28
|
+
return { valid: false, error: `Marker ${index}: notes too long (max ${MAX_NOTES_LENGTH})` };
|
|
29
29
|
}
|
|
30
|
-
if (
|
|
31
|
-
return { valid: false, error: `
|
|
30
|
+
if (marker.screenshot && marker.screenshot.length > MAX_SCREENSHOT_SIZE) {
|
|
31
|
+
return { valid: false, error: `Marker ${index}: screenshot too large (max ${MAX_SCREENSHOT_SIZE / 1000}KB)` };
|
|
32
32
|
}
|
|
33
33
|
return { valid: true };
|
|
34
34
|
}
|
|
@@ -36,18 +36,18 @@ export function validateBatchData(data) {
|
|
|
36
36
|
if (!data) {
|
|
37
37
|
return { valid: false, error: 'Missing batch data' };
|
|
38
38
|
}
|
|
39
|
-
if (!data.
|
|
40
|
-
return { valid: false, error: 'Missing or invalid
|
|
39
|
+
if (!data.markers || !Array.isArray(data.markers)) {
|
|
40
|
+
return { valid: false, error: 'Missing or invalid markers array' };
|
|
41
41
|
}
|
|
42
|
-
if (data.
|
|
43
|
-
return { valid: false, error: 'No
|
|
42
|
+
if (data.markers.length === 0) {
|
|
43
|
+
return { valid: false, error: 'No markers provided' };
|
|
44
44
|
}
|
|
45
|
-
if (data.
|
|
46
|
-
return { valid: false, error: `Too many
|
|
45
|
+
if (data.markers.length > MAX_MARKERS) {
|
|
46
|
+
return { valid: false, error: `Too many markers (max ${MAX_MARKERS})` };
|
|
47
47
|
}
|
|
48
|
-
// Validate each
|
|
49
|
-
for (let i = 0; i < data.
|
|
50
|
-
const result =
|
|
48
|
+
// Validate each marker
|
|
49
|
+
for (let i = 0; i < data.markers.length; i++) {
|
|
50
|
+
const result = validateMarker(data.markers[i], i);
|
|
51
51
|
if (!result.valid) {
|
|
52
52
|
return { valid: false, error: result.error };
|
|
53
53
|
}
|