codmir 0.4.0 → 0.6.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.
@@ -1,15 +1,10 @@
1
- var __getOwnPropNames = Object.getOwnPropertyNames;
2
1
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
2
  get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
3
  }) : x)(function(x) {
5
4
  if (typeof require !== "undefined") return require.apply(this, arguments);
6
5
  throw Error('Dynamic require of "' + x + '" is not supported');
7
6
  });
8
- var __commonJS = (cb, mod) => function __require2() {
9
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
10
- };
11
7
 
12
8
  export {
13
- __require,
14
- __commonJS
9
+ __require
15
10
  };
@@ -0,0 +1,231 @@
1
+ // src/sync/cloud-sync.ts
2
+ import WebSocket from "ws";
3
+ var WebSocketCloudSync = class {
4
+ config;
5
+ ws = null;
6
+ messageHandlers = /* @__PURE__ */ new Set();
7
+ pendingMessages = [];
8
+ reconnectAttempts = 0;
9
+ maxReconnectAttempts = 5;
10
+ heartbeatInterval = null;
11
+ constructor(config) {
12
+ this.config = config;
13
+ }
14
+ async connect() {
15
+ if (!this.config.enabled || !this.config.apiUrl) {
16
+ return;
17
+ }
18
+ return new Promise((resolve, reject) => {
19
+ const wsUrl = this.config.apiUrl.replace("https://", "wss://").replace("http://", "ws://");
20
+ const url = `${wsUrl}/api/agent/ws?apiKey=${this.config.apiKey || ""}`;
21
+ this.ws = new WebSocket(url);
22
+ this.ws.on("open", () => {
23
+ console.log("[CloudSync] Connected to Codmir cloud");
24
+ this.reconnectAttempts = 0;
25
+ for (const msg of this.pendingMessages) {
26
+ this.emitEvent(msg);
27
+ }
28
+ this.pendingMessages = [];
29
+ this.startHeartbeat();
30
+ resolve();
31
+ });
32
+ this.ws.on("message", (data) => {
33
+ try {
34
+ const message = JSON.parse(data.toString());
35
+ for (const handler of this.messageHandlers) {
36
+ handler(message);
37
+ }
38
+ } catch (error) {
39
+ console.error("[CloudSync] Failed to parse message:", error);
40
+ }
41
+ });
42
+ this.ws.on("close", () => {
43
+ console.log("[CloudSync] Disconnected from cloud");
44
+ this.stopHeartbeat();
45
+ this.attemptReconnect();
46
+ });
47
+ this.ws.on("error", (error) => {
48
+ console.error("[CloudSync] WebSocket error:", error.message);
49
+ reject(error);
50
+ });
51
+ setTimeout(() => {
52
+ if (this.ws?.readyState !== WebSocket.OPEN) {
53
+ reject(new Error("Connection timeout"));
54
+ }
55
+ }, 1e4);
56
+ });
57
+ }
58
+ startHeartbeat() {
59
+ this.heartbeatInterval = setInterval(() => {
60
+ if (this.ws?.readyState === WebSocket.OPEN) {
61
+ this.ws.ping();
62
+ }
63
+ }, 3e4);
64
+ }
65
+ stopHeartbeat() {
66
+ if (this.heartbeatInterval) {
67
+ clearInterval(this.heartbeatInterval);
68
+ this.heartbeatInterval = null;
69
+ }
70
+ }
71
+ attemptReconnect() {
72
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) {
73
+ console.log("[CloudSync] Max reconnect attempts reached");
74
+ return;
75
+ }
76
+ this.reconnectAttempts++;
77
+ const delay = Math.min(1e3 * Math.pow(2, this.reconnectAttempts), 3e4);
78
+ console.log(`[CloudSync] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
79
+ setTimeout(() => {
80
+ this.connect().catch(() => {
81
+ });
82
+ }, delay);
83
+ }
84
+ disconnect() {
85
+ this.stopHeartbeat();
86
+ if (this.ws) {
87
+ this.ws.close();
88
+ this.ws = null;
89
+ }
90
+ }
91
+ async createTask(session) {
92
+ const task = {
93
+ id: session.taskId || session.id,
94
+ userId: this.config.userId || "anonymous",
95
+ projectId: this.config.projectId,
96
+ title: session.prompt.slice(0, 100),
97
+ description: session.prompt,
98
+ status: session.status,
99
+ source: "cli",
100
+ workspaceRoot: session.workspaceRoot,
101
+ steps: session.steps,
102
+ files: session.files,
103
+ createdAt: new Date(session.createdAt).toISOString(),
104
+ updatedAt: new Date(session.updatedAt).toISOString()
105
+ };
106
+ if (this.ws?.readyState !== WebSocket.OPEN) {
107
+ return this.createTaskREST(task);
108
+ }
109
+ this.ws.send(JSON.stringify({
110
+ type: "task.create",
111
+ task
112
+ }));
113
+ return task;
114
+ }
115
+ async createTaskREST(task) {
116
+ if (!this.config.apiUrl) {
117
+ return task;
118
+ }
119
+ try {
120
+ const response = await fetch(`${this.config.apiUrl}/api/tasks`, {
121
+ method: "POST",
122
+ headers: {
123
+ "Content-Type": "application/json",
124
+ "Authorization": `Bearer ${this.config.apiKey}`
125
+ },
126
+ body: JSON.stringify(task)
127
+ });
128
+ if (response.ok) {
129
+ return await response.json();
130
+ }
131
+ } catch (error) {
132
+ console.error("[CloudSync] Failed to create task via REST:", error);
133
+ }
134
+ return task;
135
+ }
136
+ async updateTask(taskId, updates) {
137
+ const payload = {
138
+ type: "task.update",
139
+ taskId,
140
+ updates: {
141
+ status: updates.status,
142
+ steps: updates.steps,
143
+ files: updates.files,
144
+ error: updates.error,
145
+ result: updates.result,
146
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
147
+ completedAt: updates.completedAt ? new Date(updates.completedAt).toISOString() : void 0
148
+ }
149
+ };
150
+ if (this.ws?.readyState === WebSocket.OPEN) {
151
+ this.ws.send(JSON.stringify(payload));
152
+ } else {
153
+ this.updateTaskREST(taskId, payload.updates).catch(() => {
154
+ });
155
+ }
156
+ }
157
+ async updateTaskREST(taskId, updates) {
158
+ if (!this.config.apiUrl) return;
159
+ try {
160
+ await fetch(`${this.config.apiUrl}/api/tasks/${taskId}`, {
161
+ method: "PATCH",
162
+ headers: {
163
+ "Content-Type": "application/json",
164
+ "Authorization": `Bearer ${this.config.apiKey}`
165
+ },
166
+ body: JSON.stringify(updates)
167
+ });
168
+ } catch (error) {
169
+ console.error("[CloudSync] Failed to update task via REST:", error);
170
+ }
171
+ }
172
+ emitEvent(event) {
173
+ if (this.ws?.readyState === WebSocket.OPEN) {
174
+ this.ws.send(JSON.stringify({
175
+ type: "event",
176
+ event
177
+ }));
178
+ } else {
179
+ this.pendingMessages.push(event);
180
+ }
181
+ }
182
+ onMessage(handler) {
183
+ this.messageHandlers.add(handler);
184
+ return () => this.messageHandlers.delete(handler);
185
+ }
186
+ isConnected() {
187
+ return this.ws?.readyState === WebSocket.OPEN;
188
+ }
189
+ };
190
+ var NoopCloudSync = class {
191
+ async connect() {
192
+ }
193
+ disconnect() {
194
+ }
195
+ async createTask(session) {
196
+ return {
197
+ id: session.id,
198
+ userId: "local",
199
+ title: session.prompt.slice(0, 100),
200
+ description: session.prompt,
201
+ status: session.status,
202
+ source: "cli",
203
+ workspaceRoot: session.workspaceRoot,
204
+ steps: session.steps,
205
+ files: session.files,
206
+ createdAt: new Date(session.createdAt).toISOString(),
207
+ updatedAt: new Date(session.updatedAt).toISOString()
208
+ };
209
+ }
210
+ async updateTask() {
211
+ }
212
+ emitEvent() {
213
+ }
214
+ onMessage() {
215
+ return () => {
216
+ };
217
+ }
218
+ isConnected() {
219
+ return false;
220
+ }
221
+ };
222
+ function createCloudSync(config) {
223
+ if (!config?.enabled) {
224
+ return new NoopCloudSync();
225
+ }
226
+ return new WebSocketCloudSync(config);
227
+ }
228
+
229
+ export {
230
+ createCloudSync
231
+ };
@@ -0,0 +1,295 @@
1
+ // src/context/local-context-provider.ts
2
+ import * as fs from "fs/promises";
3
+ import * as path from "path";
4
+ import { spawn, exec } from "child_process";
5
+ import { promisify } from "util";
6
+ import { glob } from "glob";
7
+ var execPromise = promisify(exec);
8
+ var LocalContextProvider = class {
9
+ workspaceRoot;
10
+ askUserHandler;
11
+ notificationHandler;
12
+ constructor(workspaceRoot, options) {
13
+ this.workspaceRoot = path.resolve(workspaceRoot);
14
+ this.askUserHandler = options?.askUserHandler;
15
+ this.notificationHandler = options?.notificationHandler;
16
+ }
17
+ getWorkspaceRoot() {
18
+ return this.workspaceRoot;
19
+ }
20
+ resolvePath(relativePath) {
21
+ const resolved = path.resolve(this.workspaceRoot, relativePath);
22
+ if (!resolved.startsWith(this.workspaceRoot)) {
23
+ throw new Error(`Path escapes workspace: ${relativePath}`);
24
+ }
25
+ return resolved;
26
+ }
27
+ async readFile(filePath, options) {
28
+ const fullPath = this.resolvePath(filePath);
29
+ try {
30
+ const content = await fs.readFile(fullPath, "utf-8");
31
+ const lines = content.split("\n");
32
+ const lineCount = lines.length;
33
+ let resultContent = content;
34
+ let truncated = false;
35
+ if (options?.startLine || options?.endLine) {
36
+ const start = (options.startLine || 1) - 1;
37
+ const end = options.endLine || lines.length;
38
+ resultContent = lines.slice(start, end).join("\n");
39
+ truncated = start > 0 || end < lines.length;
40
+ }
41
+ if (options?.maxLength && resultContent.length > options.maxLength) {
42
+ resultContent = resultContent.slice(0, options.maxLength);
43
+ truncated = true;
44
+ }
45
+ const ext = path.extname(fullPath).slice(1);
46
+ const languageMap = {
47
+ ts: "typescript",
48
+ tsx: "typescript",
49
+ js: "javascript",
50
+ jsx: "javascript",
51
+ py: "python",
52
+ rb: "ruby",
53
+ go: "go",
54
+ rs: "rust",
55
+ java: "java",
56
+ c: "c",
57
+ cpp: "cpp",
58
+ h: "c",
59
+ hpp: "cpp",
60
+ cs: "csharp",
61
+ php: "php",
62
+ swift: "swift",
63
+ kt: "kotlin",
64
+ scala: "scala",
65
+ sh: "bash",
66
+ bash: "bash",
67
+ zsh: "bash",
68
+ fish: "fish",
69
+ ps1: "powershell",
70
+ sql: "sql",
71
+ json: "json",
72
+ yaml: "yaml",
73
+ yml: "yaml",
74
+ xml: "xml",
75
+ html: "html",
76
+ css: "css",
77
+ scss: "scss",
78
+ less: "less",
79
+ md: "markdown",
80
+ txt: "plaintext"
81
+ };
82
+ return {
83
+ content: resultContent,
84
+ path: filePath,
85
+ language: languageMap[ext] || "plaintext",
86
+ lineCount,
87
+ truncated
88
+ };
89
+ } catch (error) {
90
+ if (error.code === "ENOENT") {
91
+ throw new Error(`File not found: ${filePath}`);
92
+ }
93
+ throw error;
94
+ }
95
+ }
96
+ async writeFile(filePath, content) {
97
+ const fullPath = this.resolvePath(filePath);
98
+ const dir = path.dirname(fullPath);
99
+ await fs.mkdir(dir, { recursive: true });
100
+ await fs.writeFile(fullPath, content, "utf-8");
101
+ }
102
+ async deleteFile(filePath) {
103
+ const fullPath = this.resolvePath(filePath);
104
+ await fs.unlink(fullPath);
105
+ }
106
+ async listDirectory(dirPath) {
107
+ const fullPath = this.resolvePath(dirPath);
108
+ const entries = await fs.readdir(fullPath, { withFileTypes: true });
109
+ const result = [];
110
+ for (const entry of entries) {
111
+ const entryPath = path.join(dirPath, entry.name);
112
+ const stat2 = await fs.stat(path.join(fullPath, entry.name)).catch(() => null);
113
+ result.push({
114
+ name: entry.name,
115
+ path: entryPath,
116
+ type: entry.isDirectory() ? "directory" : "file",
117
+ size: stat2?.size
118
+ });
119
+ }
120
+ return result;
121
+ }
122
+ async fileExists(filePath) {
123
+ const fullPath = this.resolvePath(filePath);
124
+ try {
125
+ await fs.access(fullPath);
126
+ return true;
127
+ } catch {
128
+ return false;
129
+ }
130
+ }
131
+ async searchFiles(query, options) {
132
+ const results = [];
133
+ const maxResults = options?.maxResults || 100;
134
+ const includePatterns = options?.include || ["**/*"];
135
+ const excludePatterns = options?.exclude || ["**/node_modules/**", "**/.git/**", "**/dist/**"];
136
+ for (const pattern of includePatterns) {
137
+ const files = await glob(pattern, {
138
+ cwd: this.workspaceRoot,
139
+ ignore: excludePatterns,
140
+ nodir: true,
141
+ absolute: false
142
+ });
143
+ for (const file of files) {
144
+ if (results.length >= maxResults) break;
145
+ try {
146
+ const content = await this.readFile(file);
147
+ const lines = content.content.split("\n");
148
+ const regex = new RegExp(query, options?.caseSensitive ? "g" : "gi");
149
+ for (let i = 0; i < lines.length; i++) {
150
+ const line = lines[i];
151
+ const matches = line.matchAll(regex);
152
+ for (const match of matches) {
153
+ if (results.length >= maxResults) break;
154
+ results.push({
155
+ path: file,
156
+ line: i + 1,
157
+ column: (match.index || 0) + 1,
158
+ match: match[0],
159
+ context: line.trim()
160
+ });
161
+ }
162
+ }
163
+ } catch {
164
+ }
165
+ }
166
+ }
167
+ return results;
168
+ }
169
+ async runCommand(command, options) {
170
+ const cwd = options?.cwd ? this.resolvePath(options.cwd) : this.workspaceRoot;
171
+ const timeout = options?.timeout || 3e4;
172
+ const startTime = Date.now();
173
+ return new Promise((resolve2, reject) => {
174
+ const proc = spawn("bash", ["-c", command], {
175
+ cwd,
176
+ env: { ...process.env, ...options?.env },
177
+ timeout
178
+ });
179
+ let stdout = "";
180
+ let stderr = "";
181
+ proc.stdout.on("data", (data) => {
182
+ stdout += data.toString();
183
+ });
184
+ proc.stderr.on("data", (data) => {
185
+ stderr += data.toString();
186
+ });
187
+ proc.on("close", (code) => {
188
+ resolve2({
189
+ exitCode: code ?? 0,
190
+ stdout,
191
+ stderr,
192
+ duration: Date.now() - startTime
193
+ });
194
+ });
195
+ proc.on("error", (error) => {
196
+ reject(error);
197
+ });
198
+ setTimeout(() => {
199
+ proc.kill("SIGTERM");
200
+ reject(new Error(`Command timed out after ${timeout}ms`));
201
+ }, timeout);
202
+ });
203
+ }
204
+ async getGitStatus() {
205
+ try {
206
+ const branchResult = await execPromise("git rev-parse --abbrev-ref HEAD", {
207
+ cwd: this.workspaceRoot
208
+ });
209
+ const branch = branchResult.stdout.trim();
210
+ const statusResult = await execPromise("git status --porcelain", {
211
+ cwd: this.workspaceRoot
212
+ });
213
+ const staged = [];
214
+ const modified = [];
215
+ const untracked = [];
216
+ const deleted = [];
217
+ for (const line of statusResult.stdout.split("\n")) {
218
+ if (!line) continue;
219
+ const status = line.substring(0, 2);
220
+ const file = line.substring(3);
221
+ if (status.includes("?")) {
222
+ untracked.push(file);
223
+ } else if (status.includes("D")) {
224
+ deleted.push(file);
225
+ } else if (status[0] !== " ") {
226
+ staged.push(file);
227
+ } else if (status[1] !== " ") {
228
+ modified.push(file);
229
+ }
230
+ }
231
+ let ahead = 0;
232
+ let behind = 0;
233
+ try {
234
+ const trackingResult = await execPromise(
235
+ "git rev-list --left-right --count HEAD...@{upstream}",
236
+ { cwd: this.workspaceRoot }
237
+ );
238
+ const [a, b] = trackingResult.stdout.trim().split(" ").map(Number);
239
+ ahead = a || 0;
240
+ behind = b || 0;
241
+ } catch {
242
+ }
243
+ return { branch, ahead, behind, staged, modified, untracked, deleted };
244
+ } catch (error) {
245
+ return {
246
+ branch: "unknown",
247
+ ahead: 0,
248
+ behind: 0,
249
+ staged: [],
250
+ modified: [],
251
+ untracked: [],
252
+ deleted: []
253
+ };
254
+ }
255
+ }
256
+ async getGitDiff(options) {
257
+ let cmd = "git diff";
258
+ if (options?.staged) {
259
+ cmd += " --staged";
260
+ }
261
+ if (options?.commit) {
262
+ cmd += ` ${options.commit}`;
263
+ }
264
+ if (options?.path) {
265
+ cmd += ` -- ${options.path}`;
266
+ }
267
+ try {
268
+ const result = await execPromise(cmd, { cwd: this.workspaceRoot });
269
+ return result.stdout;
270
+ } catch {
271
+ return "";
272
+ }
273
+ }
274
+ async askUser(question, options) {
275
+ if (this.askUserHandler) {
276
+ return this.askUserHandler(question, options);
277
+ }
278
+ throw new Error("No askUser handler configured");
279
+ }
280
+ showNotification(message, type) {
281
+ if (this.notificationHandler) {
282
+ this.notificationHandler(message, type);
283
+ } else {
284
+ console.log(`[${type || "info"}] ${message}`);
285
+ }
286
+ }
287
+ };
288
+ function createLocalContextProvider(workspaceRoot, options) {
289
+ return new LocalContextProvider(workspaceRoot, options);
290
+ }
291
+
292
+ export {
293
+ LocalContextProvider,
294
+ createLocalContextProvider
295
+ };
@@ -0,0 +1,9 @@
1
+ import {
2
+ LocalContextProvider,
3
+ createLocalContextProvider
4
+ } from "../chunk-BQMZNWYV.js";
5
+ import "../chunk-3RG5ZIWI.js";
6
+ export {
7
+ LocalContextProvider,
8
+ createLocalContextProvider
9
+ };