@codewithdan/zingit 0.0.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.
@@ -0,0 +1,74 @@
1
+ import type { WebSocket } from 'ws';
2
+ import type { CheckpointInfo } from './services/git-manager.js';
3
+ export interface Agent {
4
+ name: string;
5
+ model: string;
6
+ start(): Promise<void>;
7
+ stop(): Promise<void>;
8
+ createSession(ws: WebSocket, projectDir: string): Promise<AgentSession>;
9
+ formatPrompt(data: BatchData, projectDir: string): string;
10
+ extractImages(data: BatchData): ImageContent[];
11
+ }
12
+ export interface ImageContent {
13
+ base64: string;
14
+ mediaType: string;
15
+ label?: string;
16
+ }
17
+ export interface AgentSession {
18
+ send(msg: {
19
+ prompt: string;
20
+ images?: ImageContent[];
21
+ }): Promise<void>;
22
+ destroy(): Promise<void>;
23
+ }
24
+ export interface Annotation {
25
+ id: string;
26
+ selector: string;
27
+ identifier: string;
28
+ html: string;
29
+ notes: string;
30
+ status?: 'pending' | 'processing' | 'completed';
31
+ selectedText?: string;
32
+ parentContext?: string;
33
+ textContent?: string;
34
+ siblingContext?: string;
35
+ parentHtml?: string;
36
+ screenshot?: string;
37
+ }
38
+ export interface BatchData {
39
+ pageUrl: string;
40
+ pageTitle: string;
41
+ annotations: Annotation[];
42
+ projectDir?: string;
43
+ }
44
+ export type WSIncomingType = 'batch' | 'message' | 'reset' | 'stop' | 'get_agents' | 'select_agent' | 'get_history' | 'undo' | 'revert_to' | 'clear_history';
45
+ export interface WSIncomingMessage {
46
+ type: WSIncomingType;
47
+ data?: BatchData;
48
+ content?: string;
49
+ agent?: string;
50
+ checkpointId?: string;
51
+ }
52
+ export type WSOutgoingType = 'connected' | 'processing' | 'response' | 'delta' | 'tool_start' | 'tool_end' | 'idle' | 'error' | 'reset_complete' | 'agents' | 'agent_selected' | 'agent_error' | 'checkpoint_created' | 'history' | 'undo_complete' | 'revert_complete' | 'history_cleared';
53
+ export interface AgentInfoMessage {
54
+ name: string;
55
+ displayName: string;
56
+ available: boolean;
57
+ version?: string;
58
+ reason?: string;
59
+ installCommand: string;
60
+ }
61
+ export interface WSOutgoingMessage {
62
+ type: WSOutgoingType;
63
+ content?: string;
64
+ message?: string;
65
+ tool?: string;
66
+ agent?: string;
67
+ model?: string;
68
+ projectDir?: string;
69
+ agents?: AgentInfoMessage[];
70
+ checkpoint?: CheckpointInfo;
71
+ checkpoints?: CheckpointInfo[];
72
+ checkpointId?: string;
73
+ filesReverted?: string[];
74
+ }
@@ -0,0 +1,2 @@
1
+ // server/src/types.ts
2
+ export {};
@@ -0,0 +1,17 @@
1
+ export interface AgentInfo {
2
+ name: string;
3
+ displayName: string;
4
+ available: boolean;
5
+ version?: string;
6
+ reason?: string;
7
+ installCommand: string;
8
+ }
9
+ /**
10
+ * Detect all available agents and their status
11
+ * Uses async operations to avoid blocking the event loop
12
+ */
13
+ export declare function detectAgents(): Promise<AgentInfo[]>;
14
+ /**
15
+ * Check if a specific agent is available
16
+ */
17
+ export declare function isAgentAvailable(agentName: string): Promise<AgentInfo | undefined>;
@@ -0,0 +1,91 @@
1
+ // server/src/utils/agent-detection.ts
2
+ // Cross-platform agent CLI detection
3
+ import { exec } from 'child_process';
4
+ import { promisify } from 'util';
5
+ import { existsSync } from 'fs';
6
+ import { homedir } from 'os';
7
+ import { join } from 'path';
8
+ const execAsync = promisify(exec);
9
+ /**
10
+ * Check if a CLI command is available (cross-platform)
11
+ * Uses async exec to avoid blocking the event loop
12
+ */
13
+ async function checkCLI(command) {
14
+ try {
15
+ const { stdout } = await execAsync(`${command} --version`, {
16
+ encoding: 'utf-8',
17
+ timeout: 5000
18
+ });
19
+ return { installed: true, version: stdout.split('\n')[0].trim() };
20
+ }
21
+ catch {
22
+ return { installed: false };
23
+ }
24
+ }
25
+ /**
26
+ * Check if Codex auth file exists (cross-platform using os.homedir())
27
+ */
28
+ function checkCodexAuth() {
29
+ const authPath = join(homedir(), '.codex', 'auth.json');
30
+ return existsSync(authPath);
31
+ }
32
+ /**
33
+ * Detect all available agents and their status
34
+ * Uses async operations to avoid blocking the event loop
35
+ */
36
+ export async function detectAgents() {
37
+ // Run all CLI checks in parallel for better performance
38
+ const [claudeCheck, copilotCheck, codexCheck] = await Promise.all([
39
+ checkCLI('claude'),
40
+ checkCLI('copilot'),
41
+ checkCLI('codex')
42
+ ]);
43
+ const agents = [];
44
+ // Claude Code
45
+ agents.push({
46
+ name: 'claude',
47
+ displayName: 'Claude Code',
48
+ available: claudeCheck.installed,
49
+ version: claudeCheck.version,
50
+ reason: claudeCheck.installed ? undefined : 'Claude Code CLI not found',
51
+ installCommand: 'npm install -g @anthropic-ai/claude-code'
52
+ });
53
+ // GitHub Copilot
54
+ agents.push({
55
+ name: 'copilot',
56
+ displayName: 'GitHub Copilot CLI',
57
+ available: copilotCheck.installed,
58
+ version: copilotCheck.version,
59
+ reason: copilotCheck.installed ? undefined : 'Copilot CLI not found',
60
+ installCommand: 'npm install -g @github/copilot'
61
+ });
62
+ // OpenAI Codex
63
+ if (codexCheck.installed) {
64
+ const hasAuth = checkCodexAuth();
65
+ agents.push({
66
+ name: 'codex',
67
+ displayName: 'OpenAI Codex',
68
+ available: hasAuth,
69
+ version: codexCheck.version,
70
+ reason: hasAuth ? undefined : 'Codex CLI installed but not logged in. Run: codex',
71
+ installCommand: 'npm install -g @openai/codex'
72
+ });
73
+ }
74
+ else {
75
+ agents.push({
76
+ name: 'codex',
77
+ displayName: 'OpenAI Codex',
78
+ available: false,
79
+ reason: 'Codex CLI not found',
80
+ installCommand: 'npm install -g @openai/codex'
81
+ });
82
+ }
83
+ return agents;
84
+ }
85
+ /**
86
+ * Check if a specific agent is available
87
+ */
88
+ export async function isAgentAvailable(agentName) {
89
+ const agents = await detectAgents();
90
+ return agents.find(a => a.name === agentName);
91
+ }
@@ -0,0 +1,12 @@
1
+ import type { BatchData } from '../types.js';
2
+ export declare const MAX_ANNOTATIONS = 50;
3
+ export declare const MAX_HTML_LENGTH = 50000;
4
+ export declare const MAX_NOTES_LENGTH = 5000;
5
+ export declare const MAX_SELECTOR_LENGTH = 1000;
6
+ export declare const MAX_SCREENSHOT_SIZE = 5000000;
7
+ export interface ValidationResult {
8
+ valid: boolean;
9
+ error?: string;
10
+ sanitizedData?: BatchData;
11
+ }
12
+ export declare function validateBatchData(data: BatchData): ValidationResult;
@@ -0,0 +1,64 @@
1
+ // server/src/validation/payload.ts
2
+ // ============================================
3
+ // Payload Validation
4
+ // ============================================
5
+ export const MAX_ANNOTATIONS = 50;
6
+ export const MAX_HTML_LENGTH = 50000;
7
+ export const MAX_NOTES_LENGTH = 5000;
8
+ export const MAX_SELECTOR_LENGTH = 1000;
9
+ export const MAX_SCREENSHOT_SIZE = 5000000; // ~5MB base64 (matches Claude API limit)
10
+ const VALID_STATUSES = ['pending', 'processing', 'completed'];
11
+ function validateAnnotation(annotation, index) {
12
+ if (!annotation.id || typeof annotation.id !== 'string') {
13
+ return { valid: false, error: `Annotation ${index}: missing or invalid id` };
14
+ }
15
+ if (!annotation.identifier || typeof annotation.identifier !== 'string') {
16
+ return { valid: false, error: `Annotation ${index}: missing or invalid identifier` };
17
+ }
18
+ if (annotation.status && !VALID_STATUSES.includes(annotation.status)) {
19
+ return { valid: false, error: `Annotation ${index}: invalid status '${annotation.status}'` };
20
+ }
21
+ if (annotation.selector && annotation.selector.length > MAX_SELECTOR_LENGTH) {
22
+ return { valid: false, error: `Annotation ${index}: selector too long (max ${MAX_SELECTOR_LENGTH})` };
23
+ }
24
+ if (annotation.html && annotation.html.length > MAX_HTML_LENGTH) {
25
+ return { valid: false, error: `Annotation ${index}: html too long (max ${MAX_HTML_LENGTH})` };
26
+ }
27
+ if (annotation.notes && annotation.notes.length > MAX_NOTES_LENGTH) {
28
+ return { valid: false, error: `Annotation ${index}: notes too long (max ${MAX_NOTES_LENGTH})` };
29
+ }
30
+ if (annotation.screenshot && annotation.screenshot.length > MAX_SCREENSHOT_SIZE) {
31
+ return { valid: false, error: `Annotation ${index}: screenshot too large (max ${MAX_SCREENSHOT_SIZE / 1000}KB)` };
32
+ }
33
+ return { valid: true };
34
+ }
35
+ export function validateBatchData(data) {
36
+ if (!data) {
37
+ return { valid: false, error: 'Missing batch data' };
38
+ }
39
+ if (!data.annotations || !Array.isArray(data.annotations)) {
40
+ return { valid: false, error: 'Missing or invalid annotations array' };
41
+ }
42
+ if (data.annotations.length === 0) {
43
+ return { valid: false, error: 'No annotations provided' };
44
+ }
45
+ if (data.annotations.length > MAX_ANNOTATIONS) {
46
+ return { valid: false, error: `Too many annotations (max ${MAX_ANNOTATIONS})` };
47
+ }
48
+ // Validate each annotation
49
+ for (let i = 0; i < data.annotations.length; i++) {
50
+ const result = validateAnnotation(data.annotations[i], i);
51
+ if (!result.valid) {
52
+ return { valid: false, error: result.error };
53
+ }
54
+ }
55
+ // Sanitize and return
56
+ return {
57
+ valid: true,
58
+ sanitizedData: {
59
+ ...data,
60
+ pageUrl: data.pageUrl ? data.pageUrl.slice(0, 2000) : data.pageUrl,
61
+ pageTitle: data.pageTitle ? data.pageTitle.slice(0, 500) : data.pageTitle,
62
+ }
63
+ };
64
+ }