clippy-test 1.0.8 → 2.0.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,13 +1,17 @@
1
1
  import { useCallback, useEffect, useRef, useState } from "react";
2
+ import WebSocket from "ws";
2
3
  import { getContextLines } from "./utils.js";
3
4
  import { toolFunctionCall } from "./api.js";
4
5
  import fs from "fs";
5
6
  import os from "os";
6
7
  import path from "path";
7
- // Load Clippy config from ~/.clippy/config
8
+ import { mapChatMessagesToStoredMessages, mapStoredMessagesToChatMessages, SystemMessage, } from "@langchain/core/messages";
9
+ import { ripgrepSearch } from "./helpers/ripgrep-tool.js";
10
+ import { loadProjectMetadata, logd } from "./helpers/cli-helpers.js";
11
+ // Load OnCall config from ~/.oncall/config
8
12
  const HOME_DIR = os.homedir();
9
- const CLIPPY_DIR = path.join(HOME_DIR, ".clippy");
10
- const CONFIG_PATH = path.join(CLIPPY_DIR, "config");
13
+ const ONCALL_DIR = path.join(HOME_DIR, ".oncall");
14
+ const CONFIG_PATH = path.join(ONCALL_DIR, "config");
11
15
  let API_KEY = "";
12
16
  try {
13
17
  const configText = fs.readFileSync(CONFIG_PATH, "utf8");
@@ -16,34 +20,54 @@ try {
16
20
  API_KEY = match[1].trim();
17
21
  }
18
22
  else {
19
- console.log("No API_KEY found in ~/.clippy/config");
23
+ console.log("No API_KEY found in ~/.oncall/config");
20
24
  }
21
25
  }
22
26
  catch (err) {
23
- console.log("Failed to read Clippy config:", err);
27
+ console.log("Failed to read OnCall config:", err);
24
28
  }
25
- export function useWebSocket(url) {
29
+ export function useWebSocket(url, rawLogData) {
26
30
  //refs
27
31
  const socketRef = useRef(null);
28
32
  const [socketId, setSocketId] = useState(null);
29
33
  const [chatResponseMessages, setChatResponseMessages] = useState([]);
30
34
  const [trimmedChats, setTrimmedChats] = useState([]);
31
- const [visibleChats, setVisibleChats] = useState([]);
35
+ const initialAssistantMessage = new SystemMessage("I’m watching your app run locally. You can ask me about errors, logs, performance,or anything else related to this run.");
36
+ const [visibleChats, setVisibleChats] = useState([
37
+ initialAssistantMessage,
38
+ ]);
32
39
  const [isConnected, setIsConnected] = useState(false);
33
40
  const [connectionError, setConnectionError] = useState(null);
34
41
  const [isLoading, setIsLoading] = useState(false);
35
- const [isControlRPressed, setIsControlRPressed] = useState(false);
36
42
  const [showControlR, setShowControlR] = useState(false);
43
+ const [customMessage, setCustomMessage] = useState(null);
44
+ const [graphState, setGraphState] = useState(null);
37
45
  const authKey = API_KEY;
46
+ const initialProjectMetadata = loadProjectMetadata();
47
+ const getProjectMetadata = () => loadProjectMetadata() || initialProjectMetadata;
48
+ const getServiceId = () => getProjectMetadata()?.window_id;
49
+ const hasLogsAccess = () => {
50
+ const value = getProjectMetadata()?.logs_available;
51
+ return value === undefined ? true : value;
52
+ };
53
+ const hasCodeAccess = () => {
54
+ const value = getProjectMetadata()?.code_available;
55
+ return value === undefined ? true : value;
56
+ };
38
57
  useEffect(() => {
39
58
  if (API_KEY === "") {
40
- setConnectionError("No API_KEY found in ~/.clippy/config");
41
- console.log("No API_KEY found in ~/.clippy/config");
59
+ setConnectionError("No API_KEY found in ~/.oncall/config");
60
+ console.log("No API_KEY found in ~/.oncall/config");
42
61
  process.exit();
43
62
  }
44
63
  }, [API_KEY]);
45
64
  //web socket connection
46
65
  const connectWebSocket = useCallback(() => {
66
+ // const d = fs.readFileSync('logs')
67
+ // fs.writeFileSync('logs', `${d} inside connectWS \n message: ${url} ${authKey} ${serviceId}`)
68
+ // if (!url || !authKey || serviceId.trim() === "") {
69
+ // return;
70
+ // }
47
71
  if (socketRef.current) {
48
72
  socketRef.current.close();
49
73
  }
@@ -53,14 +77,24 @@ export function useWebSocket(url) {
53
77
  setIsConnected(false);
54
78
  socket.onopen = () => {
55
79
  try {
56
- socket.send(JSON.stringify({ type: "recurring_connection", authKey }));
80
+ const md = getProjectMetadata();
81
+ if (!md?.window_id) {
82
+ setConnectionError("Missing serviceId (window_id) in oncall.yaml");
83
+ console.log("Missing serviceId (window_id) in oncall.yaml");
84
+ return;
85
+ }
86
+ socket.send(JSON.stringify({
87
+ type: "recurring_connection",
88
+ authKey,
89
+ serviceId: md.window_id,
90
+ }));
57
91
  }
58
92
  catch (error) {
59
93
  setConnectionError("error");
60
94
  }
61
95
  };
62
96
  socket.onmessage = async (event) => {
63
- setShowControlR(false);
97
+ // setShowControlR(false);
64
98
  try {
65
99
  const raw = event.data;
66
100
  let text = "";
@@ -84,7 +118,8 @@ export function useWebSocket(url) {
84
118
  return; // ignore non-JSON frames
85
119
  }
86
120
  const data = JSON.parse(text);
87
- if (data.type === "user_assigned" && data.socketId) {
121
+ // if (data.type === "user_assigned" && data.socketId) {
122
+ if (data.type === "user_assigned") {
88
123
  setIsConnected(true);
89
124
  setSocketId(data.socketId);
90
125
  }
@@ -100,46 +135,126 @@ export function useWebSocket(url) {
100
135
  return;
101
136
  }
102
137
  if (data.type === "tool_function_call") {
103
- if (data.function_name === "get_context_lines") {
138
+ if (data.function_name === "read_file") {
139
+ logd(`[read_file] 📥 Tool call received: tool_call_id=${data.tool_call_id}, args=${JSON.stringify(data.args)}`);
140
+ if (!hasCodeAccess()) {
141
+ logd(`[read_file] ⚠️ No code access, denying tool`);
142
+ await denyToolAccess("read_file", data);
143
+ return;
144
+ }
104
145
  const argsData = {
105
- fileName: data.args.fileName,
146
+ filePath: data.args.filePath,
106
147
  lineNumber: data.args.lineNumber,
107
- before: data.args.before,
108
- after: data.args.after,
148
+ before: data.args.before || 30,
149
+ after: data.args.after || 30,
109
150
  };
110
- const result = getContextLines(argsData.fileName, argsData.lineNumber, argsData.before, argsData.after);
111
- try {
112
- await toolFunctionCall(data?.tool_call_id, result, data?.args, "tool_function_call");
151
+ logd(`[read_file] 📋 Extracted args: filePath=${argsData.filePath}, lineNumber=${argsData.lineNumber}, before=${argsData.before}, after=${argsData.after}`);
152
+ logd(`[read_file] 🔍 Calling getContextLines...`);
153
+ const result = getContextLines(argsData.filePath, argsData.lineNumber, argsData.before, argsData.after);
154
+ logd(`[read_file] ✅ File content extracted: resultLength=${result.length}, resultLines=${result.split("\n").length}, isEmpty=${result.trim().length === 0}`);
155
+ logd(`[read_file] 📄 Result preview (first 300 chars): ${result.substring(0, 300)}${result.length > 300 ? "..." : ""}`);
156
+ await postToolCallResult(data, result, setChatResponseMessages);
157
+ logd(`[read_file] 📤 Result sent to backend successfully`);
158
+ // await redisClient.set(data.tool_call_id, result, { EX: 120 });
159
+ return;
160
+ }
161
+ if (data.function_name === "grep_search") {
162
+ if (!hasCodeAccess()) {
163
+ await denyToolAccess("grep_search", data);
164
+ return;
113
165
  }
114
- catch (error) {
115
- setChatResponseMessages((prev) => [
116
- ...prev,
117
- error?.response?.message || "Error, please try again",
118
- ]);
166
+ await grepSearch(data.args.searchTerm, data);
167
+ return;
168
+ }
169
+ if (data.function_name === "read_logs") {
170
+ if (!hasLogsAccess()) {
171
+ await denyToolAccess("read_logs", data);
172
+ return;
119
173
  }
120
- // await redisClient.set(data.tool_call_id, result, { EX: 120 });
174
+ await readLogs(data.args.pageNumber, data);
175
+ return;
176
+ }
177
+ if (data.function_name === "tail_logs") {
178
+ if (!hasLogsAccess()) {
179
+ await denyToolAccess("tail_logs", data);
180
+ return;
181
+ }
182
+ await tailLogs(data.args.n, data);
183
+ return;
184
+ }
185
+ if (data.function_name === "grep_logs") {
186
+ if (!hasLogsAccess()) {
187
+ await denyToolAccess("grep_logs", data);
188
+ return;
189
+ }
190
+ await grepLogs(data.args.pattern, data.args.before, data.args.after, data);
191
+ return;
192
+ }
193
+ if (data.function_name === "get_recent_errors") {
194
+ if (!hasLogsAccess()) {
195
+ await denyToolAccess("get_recent_errors", data);
196
+ return;
197
+ }
198
+ await getRecentErrors(data.args.n, data);
199
+ return;
121
200
  }
122
- return;
123
201
  }
124
- setChatResponseMessages((prev) => [...prev, data]);
125
- setVisibleChats((prev) => [...prev, data]);
202
+ // trimmed - imp msgs
203
+ // visible - shown
204
+ // chatresponses - all
126
205
  // add non-progress responses to trimmedChats
127
- if (data.type !== "progress") {
128
- setTrimmedChats((prevTrimmed) => {
129
- const updated = [...prevTrimmed, data];
130
- // update visible chats with all trimmed messages
131
- setVisibleChats(updated);
132
- return updated;
133
- });
206
+ // @todo this is where the messages get trimmed
207
+ // if (data.type !== "progress") {
208
+ // setTrimmedChats((prevTrimmed) => {
209
+ // const updated = [...prevTrimmed, data];
210
+ // // update visible chats with all trimmed messages
211
+ // setVisibleChats(updated);
212
+ // return updated;
213
+ // });
214
+ // }
215
+ if (data.type === "response") {
216
+ let messages = data.data.messages ? data.data.messages : [];
217
+ messages = mapStoredMessagesToChatMessages(messages);
218
+ switch (data.data.type) {
219
+ case "messages":
220
+ if (data.data.sender !== "toolNode") {
221
+ setVisibleChats(old => [...old, ...messages]);
222
+ }
223
+ // logd(`LOGGING RESPONSE SENDER: ${data.data.sender}`);
224
+ break;
225
+ case "values":
226
+ if (data.data.state) {
227
+ setGraphState(data.data.state);
228
+ }
229
+ break;
230
+ case "updates":
231
+ if (data.data.sender !== "toolNode" && data.data.sender !== "routerNode") {
232
+ setChatResponseMessages(old => [...old, ...messages]);
233
+ }
234
+ if (data.data.sender === "answerNode") {
235
+ setTrimmedChats(prev => {
236
+ setVisibleChats([...prev, ...messages]);
237
+ return [...prev, ...messages];
238
+ });
239
+ setIsLoading(false);
240
+ }
241
+ if (data.data.sender === "userNode") {
242
+ setIsLoading(false);
243
+ }
244
+ break;
245
+ default:
246
+ break;
247
+ }
134
248
  }
135
- if (data.type === "response" ||
136
- data.type === "error" ||
249
+ if (data.type === "error" ||
137
250
  data.type === "ask_user") {
138
251
  setIsLoading(false);
252
+ setCustomMessage(null);
139
253
  }
140
254
  }
141
255
  catch (error) {
142
- console.warn("WebSocket message handling warning:", error);
256
+ setIsLoading(false);
257
+ // console.warn("WebSocket message handling warning:", error);
143
258
  return;
144
259
  }
145
260
  };
@@ -160,34 +275,160 @@ export function useWebSocket(url) {
160
275
  // };
161
276
  // }
162
277
  }, [url, authKey]);
163
- const sendQuery = useCallback((query, logs, state) => {
278
+ const sendQuery = useCallback((messages, architecture, logs, planningDoc) => {
164
279
  const socket = socketRef.current;
165
280
  if (socket && socket.readyState === WebSocket.OPEN) {
166
281
  setIsLoading(true);
167
- const payload = state
168
- ? { type: "query", socketId, logs, query, state }
169
- : { type: "query", socketId, logs, query };
282
+ setCustomMessage("🤔 Thinking...");
283
+ const md = getProjectMetadata();
284
+ const payload = {
285
+ type: "query",
286
+ authKey,
287
+ serviceId: md?.window_id,
288
+ userQuery: {
289
+ messages: graphState
290
+ ? mapChatMessagesToStoredMessages([messages[messages.length - 1]])
291
+ : mapChatMessagesToStoredMessages(messages),
292
+ architecture: architecture,
293
+ logs,
294
+ planningDoc: "",
295
+ },
296
+ planningDoc,
297
+ graphState: graphState || undefined,
298
+ };
170
299
  socket.send(JSON.stringify(payload));
171
300
  }
172
301
  else {
173
302
  setConnectionError("WebSocket not connected");
174
303
  throw new Error("Cannot send the message: Web socket not connected");
175
304
  }
176
- }, [socketId]);
305
+ }, [socketId, graphState]);
177
306
  // Clean up on unmount
178
307
  useEffect(() => {
179
308
  return () => {
180
309
  if (socketRef.current) {
181
- socketRef.current.close();
310
+ // socketRef.current.close();
182
311
  }
183
312
  };
184
313
  }, []);
314
+ async function grepSearch(searchTerm, data) {
315
+ logd(`[grepSearch] 🔍 Grep search invoked: searchTerm=${searchTerm}, tool_call_id=${data.tool_call_id}, args=${JSON.stringify(data.args)}`);
316
+ const projectMetadata = getProjectMetadata();
317
+ const workingDirectory = projectMetadata?.path || process.cwd();
318
+ logd(`[grepSearch] 📁 Working directory: workingDirectory=${workingDirectory}, projectPath=${projectMetadata?.path || 'N/A'}, processCwd=${process.cwd()}`);
319
+ const maxResults = data.args?.max_results || 20;
320
+ const caseSensitive = data.args?.case_sensitive || false;
321
+ const fileTypes = data.args?.file_types || [];
322
+ logd(`[grepSearch] ⚙️ Search options: maxResults=${maxResults}, caseSensitive=${caseSensitive}, fileTypes=${JSON.stringify(fileTypes)}`);
323
+ try {
324
+ const results = await ripgrepSearch(searchTerm, {
325
+ maxResults,
326
+ caseSensitive,
327
+ fileTypes,
328
+ workingDirectory,
329
+ });
330
+ logd(`[grepSearch] ✅ Search completed: resultCount=${results.length}, results=${JSON.stringify(results.map((r) => ({
331
+ filePath: r.filePath,
332
+ line: r.line,
333
+ previewLength: r.preview?.length || 0,
334
+ })))}`);
335
+ await postToolCallResult(data, results, setChatResponseMessages);
336
+ }
337
+ catch (error) {
338
+ logd(`[grepSearch] ❌ Error during grep search: ${error instanceof Error ? error.message : String(error)}, stack=${error instanceof Error ? error.stack : "N/A"}`);
339
+ await postToolCallResult(data, [], setChatResponseMessages);
340
+ }
341
+ }
342
+ async function readLogs(pageNumber, data) {
343
+ logd(`Triggered readLogs: ${pageNumber}`);
344
+ pageNumber = parseInt(pageNumber.toString());
345
+ const lines = rawLogData
346
+ .getLogs()
347
+ .split("\n")
348
+ .slice(-50 * pageNumber)
349
+ .join("\n");
350
+ logd(`Triggered readLogs - Posting result: ${lines.length}`);
351
+ await postToolCallResult(data, lines, setChatResponseMessages);
352
+ logd(`Triggered readLogs - done Posting result`);
353
+ }
354
+ async function tailLogs(n, data) {
355
+ try {
356
+ logd(`Triggered tailLogs: ${n}`);
357
+ n = parseInt(n.toString());
358
+ if (Number.isNaN(n) || n <= 0) {
359
+ await postToolCallResult(data, "Invalid value for n. Please provide a positive number of log lines to fetch.", setChatResponseMessages);
360
+ return;
361
+ }
362
+ const tail = rawLogData.getTailLogs(n);
363
+ logd(`Triggered tailLogs - Posting result: ${tail.length}`);
364
+ await postToolCallResult(data, tail, setChatResponseMessages);
365
+ logd(`Triggered tailLogs - done Posting result`);
366
+ }
367
+ catch (error) {
368
+ logd(`Triggered tailLogs - error: ${JSON.stringify(error)}`);
369
+ await postToolCallResult(data, "Failed to fetch tail logs from CLI.", setChatResponseMessages);
370
+ }
371
+ }
372
+ async function grepLogs(pattern, before, after, data) {
373
+ try {
374
+ logd(`Triggered grepLogs: pattern=${pattern}, before=${before}, after=${after}`);
375
+ if (!pattern || !pattern.trim()) {
376
+ await postToolCallResult(data, "Invalid pattern for grep_logs. Please provide a non-empty pattern.", setChatResponseMessages);
377
+ return;
378
+ }
379
+ const beforeCount = before !== undefined ? parseInt(before.toString()) : 5;
380
+ const afterCount = after !== undefined ? parseInt(after.toString()) : 5;
381
+ const result = rawLogData.getGrepLogs(pattern, beforeCount, afterCount);
382
+ await postToolCallResult(data, result, setChatResponseMessages);
383
+ logd(`Triggered grepLogs - done Posting result of length ${result.length}`);
384
+ }
385
+ catch (error) {
386
+ logd(`Triggered grepLogs - error: ${JSON.stringify(error)}`);
387
+ await postToolCallResult(data, "Failed to search logs with grep_logs.", setChatResponseMessages);
388
+ }
389
+ }
390
+ async function getRecentErrors(n, data) {
391
+ try {
392
+ logd(`Triggered getRecentErrors: n=${n}`);
393
+ n = parseInt(n.toString());
394
+ if (Number.isNaN(n) || n <= 0) {
395
+ await postToolCallResult(data, "Invalid value for n. Number Provided :" + n.toString(), setChatResponseMessages);
396
+ return;
397
+ }
398
+ const result = rawLogData.getRecentErrors(n);
399
+ await postToolCallResult(data, result, setChatResponseMessages);
400
+ logd(`Triggered getRecentErrors - done Posting result of length ${result.length}`);
401
+ }
402
+ catch (error) {
403
+ logd(`Triggered getRecentErrors - error: ${JSON.stringify(error)}`);
404
+ await postToolCallResult(data, "Failed to fetch recent error logs.", setChatResponseMessages);
405
+ }
406
+ }
407
+ async function postToolCallResult(data, result, setChatResponseMessages) {
408
+ try {
409
+ await toolFunctionCall(data?.tool_call_id, result, data?.args, "tool_function_call");
410
+ }
411
+ catch (error) {
412
+ logd(`Triggered readLogs - failer podting: ${JSON.stringify(error)}`);
413
+ // setChatResponseMessages((prev) => [
414
+ // ...prev,
415
+ // error?.response?.message || "Error, please try again",
416
+ // ]);
417
+ }
418
+ }
419
+ async function denyToolAccess(functionName, data) {
420
+ const serviceId = getServiceId();
421
+ const message = `No Access to execute ${functionName}. with the serviceId of ${serviceId ?? "unknown"}.`;
422
+ await postToolCallResult(data, message, setChatResponseMessages);
423
+ }
185
424
  return {
186
425
  connectWebSocket,
187
426
  socketId,
188
427
  sendQuery,
189
- chatResponseMessages: visibleChats,
190
- setChatResponseMessages: setVisibleChats,
428
+ chatResponseMessages: chatResponseMessages, // Full history including ToolMessage (needed for backend)
429
+ visibleChats: visibleChats, // Filtered for UI display (excludes ToolMessage except reflections)
430
+ setVisibleChats: setVisibleChats,
431
+ setChatResponseMessages,
191
432
  setTrimmedChats,
192
433
  isConnected,
193
434
  connectionError,
@@ -197,10 +438,12 @@ export function useWebSocket(url) {
197
438
  setIsConnected,
198
439
  socket: socketRef.current,
199
440
  setIsLoading,
200
- setIsControlRPressed,
201
441
  setShowControlR,
202
442
  showControlR,
203
443
  setCompleteChatHistory: setChatResponseMessages,
444
+ customMessage,
445
+ graphState,
446
+ setGraphState,
204
447
  };
205
448
  }
206
449
  //# sourceMappingURL=useWebSocket.js.map
@@ -0,0 +1,2 @@
1
+ export function checkVersionCompatibility(forceCheck?: boolean): Promise<boolean>;
2
+ export function checkVersionAndExit(): Promise<void>;
@@ -0,0 +1,124 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import os from "os";
4
+ import { fileURLToPath } from "url";
5
+ import axios from "axios";
6
+ const ONCALL_DIR = path.join(os.homedir(), ".oncall");
7
+ const VERSION_CACHE_FILE = path.join(ONCALL_DIR, "version-cache.json");
8
+ const API_BASE_URL = "http://api.oncall.build/v2/api";
9
+ const CACHE_DURATION_MS = 24 * 60 * 60 * 1000;
10
+ function compareVersions(v1, v2) {
11
+ const parts1 = v1.split(".").map(Number);
12
+ const parts2 = v2.split(".").map(Number);
13
+ for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
14
+ const part1 = parts1[i] || 0;
15
+ const part2 = parts2[i] || 0;
16
+ if (part1 < part2)
17
+ return -1;
18
+ if (part1 > part2)
19
+ return 1;
20
+ }
21
+ return 0;
22
+ }
23
+ function isVersionDeprecated(currentVersion, minimumVersion) {
24
+ return compareVersions(currentVersion, minimumVersion) < 0;
25
+ }
26
+ function readVersionCache() {
27
+ if (fs.existsSync(VERSION_CACHE_FILE)) {
28
+ const content = fs.readFileSync(VERSION_CACHE_FILE, "utf8");
29
+ return JSON.parse(content);
30
+ }
31
+ return null;
32
+ }
33
+ function ensureOnCallDir() {
34
+ if (!fs.existsSync(ONCALL_DIR)) {
35
+ fs.mkdirSync(ONCALL_DIR, { recursive: true });
36
+ }
37
+ }
38
+ function writeVersionCache(cache) {
39
+ ensureOnCallDir();
40
+ fs.writeFileSync(VERSION_CACHE_FILE, JSON.stringify(cache, null, 2));
41
+ }
42
+ function shouldCheckVersion() {
43
+ const cache = readVersionCache();
44
+ if (!cache || !cache.lastCheck) {
45
+ return true;
46
+ }
47
+ const now = Date.now();
48
+ const timeSinceLastCheck = now - cache.lastCheck;
49
+ return timeSinceLastCheck >= CACHE_DURATION_MS;
50
+ }
51
+ async function fetchMinimumVersion() {
52
+ try {
53
+ const response = await axios.get(`${API_BASE_URL}/health/minimum-cli-version`);
54
+ if (response.data?.success && response.data?.minimumCliVersion) {
55
+ return response.data.minimumCliVersion;
56
+ }
57
+ }
58
+ catch (error) {
59
+ console.warn("Failed to check CLI version:", error instanceof Error ? error.message : "Unknown error");
60
+ }
61
+ return null;
62
+ }
63
+ function getCurrentVersion() {
64
+ try {
65
+ const __filename = fileURLToPath(import.meta.url);
66
+ const __dirname = path.dirname(__filename);
67
+ const possiblePaths = [
68
+ path.resolve(__dirname, "../package.json"),
69
+ path.resolve(__dirname, "../../package.json"),
70
+ ];
71
+ for (const pkgPath of possiblePaths) {
72
+ if (fs.existsSync(pkgPath)) {
73
+ const pkgJson = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
74
+ return pkgJson.version || "0.0.0";
75
+ }
76
+ }
77
+ return "0.0.0";
78
+ }
79
+ catch (error) {
80
+ return "0.0.0";
81
+ }
82
+ }
83
+ function formatDeprecationError(currentVersion, minimumVersion) {
84
+ return `
85
+ ⚠️ CLI VERSION DEPRECATED
86
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
87
+
88
+ Your current version: ${currentVersion}
89
+ Minimum required version: ${minimumVersion}
90
+
91
+ Please update to the latest version to continue using OnCall CLI.
92
+
93
+ To update, run:
94
+ npm install -g oncall-cli@latest
95
+
96
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
97
+ `;
98
+ }
99
+ export async function checkVersionCompatibility(forceCheck = false) {
100
+ const currentVersion = getCurrentVersion();
101
+ const cache = readVersionCache();
102
+ if (!forceCheck && cache?.lastCheck && !shouldCheckVersion()) {
103
+ return true;
104
+ }
105
+ const minimumVersion = await fetchMinimumVersion();
106
+ if (!minimumVersion) {
107
+ return true;
108
+ }
109
+ writeVersionCache({
110
+ lastCheck: Date.now(),
111
+ });
112
+ if (isVersionDeprecated(currentVersion, minimumVersion)) {
113
+ console.error(formatDeprecationError(currentVersion, minimumVersion));
114
+ return false;
115
+ }
116
+ return true;
117
+ }
118
+ export async function checkVersionAndExit() {
119
+ const isCompatible = await checkVersionCompatibility(true);
120
+ if (!isCompatible) {
121
+ process.exit(1);
122
+ }
123
+ }
124
+ //# sourceMappingURL=version-check.js.map
package/dist/utils.d.ts CHANGED
@@ -1 +1,17 @@
1
+ import { BaseMessage } from "langchain";
1
2
  export declare function getContextLines(fileName: string, lineNumber: number, before?: number, after?: number): string;
3
+ export declare function extractMessageContent(messages: BaseMessage[]): string;
4
+ /**
5
+ * Get a unique key for a message for deduplication purposes
6
+ * Uses message.id if available (string), otherwise checks other locations, then creates a fallback key
7
+ */
8
+ export declare function getMessageKey(message: BaseMessage): string;
9
+ /**
10
+ * Deduplicate messages array by message ID, keeping the last occurrence
11
+ * Preserves message order
12
+ */
13
+ export declare function deduplicateMessages(messages: BaseMessage[]): BaseMessage[];
14
+ export declare function groupMessageChunks(messages: BaseMessage[]): {
15
+ lastId: string;
16
+ messages: BaseMessage[][];
17
+ };