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.
- package/LICENSE +21 -0
- package/README.md +172 -0
- package/bin/oncall.js +396 -0
- package/dist/api.js +10 -1
- package/dist/cli.js +20 -20
- package/dist/config.js +7 -7
- package/dist/helpers/cli-helpers.d.ts +25 -0
- package/dist/helpers/cli-helpers.js +329 -0
- package/dist/helpers/config-helpers.js +189 -0
- package/dist/helpers/ripgrep-tool.d.ts +15 -0
- package/dist/helpers/ripgrep-tool.js +126 -0
- package/dist/index.js +318 -122
- package/dist/logsManager.d.ts +31 -0
- package/dist/logsManager.js +90 -0
- package/dist/postinstall.js +20 -0
- package/dist/tools/ripgrep.d.ts +15 -0
- package/dist/tools/ripgrep.js +110 -0
- package/dist/useWebSocket.d.ts +14 -7
- package/dist/useWebSocket.js +291 -48
- package/dist/utils/version-check.d.ts +2 -0
- package/dist/utils/version-check.js +124 -0
- package/dist/utils.d.ts +16 -0
- package/dist/utils.js +125 -4
- package/dist/websocket-server.d.ts +24 -0
- package/dist/websocket-server.js +235 -0
- package/package.json +19 -6
- package/bin/clippy.js +0 -109
- package/dist/api.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/code_hierarchy.js.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/ui-graph.js.map +0 -1
- package/dist/useWebSocket.js.map +0 -1
- package/dist/utils.js.map +0 -1
package/dist/config.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import dotenv from "dotenv";
|
|
2
2
|
dotenv.config();
|
|
3
3
|
export const config = {
|
|
4
|
-
redis_username: process.env.REDIS_USERNAME
|
|
5
|
-
redis_password: process.env.REDIS_PASSWORD
|
|
6
|
-
redis_host: process.env.REDIS_HOST
|
|
7
|
-
redis_port: process.env.REDIS_PORT
|
|
8
|
-
websocket_url: process.env.WEB_SOCKET_URL || "wss://
|
|
9
|
-
auth_key: process.env.AUTH_KEY
|
|
10
|
-
api_base_url: process.env.API_BASE_URL || "https://
|
|
4
|
+
redis_username: process.env.REDIS_USERNAME,
|
|
5
|
+
redis_password: process.env.REDIS_PASSWORD,
|
|
6
|
+
redis_host: process.env.REDIS_HOST,
|
|
7
|
+
redis_port: process.env.REDIS_PORT,
|
|
8
|
+
websocket_url: process.env.WEB_SOCKET_URL || "wss://api.oncall.build/v2/ws",
|
|
9
|
+
auth_key: process.env.AUTH_KEY,
|
|
10
|
+
api_base_url: process.env.API_BASE_URL || "https://api.oncall.build/v2/api",
|
|
11
11
|
};
|
|
12
12
|
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export function printHelp(): void;
|
|
2
|
+
export function printLoginHelp(): void;
|
|
3
|
+
export function printInitHelp(): void;
|
|
4
|
+
export function printClusterHelp(): void;
|
|
5
|
+
export function ensureConfigDir(): void;
|
|
6
|
+
export function buildYaml(projectId: any, description: any, name: any): string;
|
|
7
|
+
export function loadProjectMetadata(cwdPath?: string): {
|
|
8
|
+
id: any;
|
|
9
|
+
description: any;
|
|
10
|
+
name: any;
|
|
11
|
+
window_id: number;
|
|
12
|
+
logs_available: any;
|
|
13
|
+
code_available: any;
|
|
14
|
+
path: string;
|
|
15
|
+
raw: string;
|
|
16
|
+
};
|
|
17
|
+
export function registerProjectWithCluster(metadata: any, clusterUrl?: string): Promise<any>;
|
|
18
|
+
export function fetchProjectsFromCluster(metadata: any, clusterUrl?: string): Promise<any>;
|
|
19
|
+
export function ensureClusterIsReady(metadata: any, registrationPromise: any): Promise<boolean>;
|
|
20
|
+
export function logd(d: any): void;
|
|
21
|
+
export const HOME_DIR: string;
|
|
22
|
+
export const ONCALL_DIR: string;
|
|
23
|
+
export const CONFIG_PATH: string;
|
|
24
|
+
export const LOGS_DIR: string;
|
|
25
|
+
export const CONFIG_TEMPLATE: "# OnCall Configuration File\n#\n# Place your OnCall API key here.\n# You can get a key from OnCall Web Studio.\n#\nAPI_KEY=\n\n# Project mappings (stored as JSON)\nprojects=[]\n";
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import WebSocket from "ws";
|
|
5
|
+
import YAML from "yaml";
|
|
6
|
+
export const HOME_DIR = os.homedir();
|
|
7
|
+
export const ONCALL_DIR = path.join(HOME_DIR, ".oncall");
|
|
8
|
+
export const CONFIG_PATH = path.join(ONCALL_DIR, "config");
|
|
9
|
+
export const LOGS_DIR = path.join(ONCALL_DIR, "logs");
|
|
10
|
+
export const CONFIG_TEMPLATE = `# OnCall Configuration File
|
|
11
|
+
#
|
|
12
|
+
# Place your OnCall API key here.
|
|
13
|
+
# You can get a key from OnCall Web Studio.
|
|
14
|
+
#
|
|
15
|
+
API_KEY=
|
|
16
|
+
|
|
17
|
+
# Project mappings (stored as JSON)
|
|
18
|
+
projects=[]
|
|
19
|
+
`;
|
|
20
|
+
//print help
|
|
21
|
+
export function printHelp() {
|
|
22
|
+
console.log(`OnCall — AI debugging copilot for running applications
|
|
23
|
+
|
|
24
|
+
Usage:
|
|
25
|
+
oncall <your command>
|
|
26
|
+
|
|
27
|
+
OnCall requires a local server to be running:
|
|
28
|
+
oncall cluster
|
|
29
|
+
|
|
30
|
+
Run your app with OnCall watching it:
|
|
31
|
+
oncall <your command>
|
|
32
|
+
|
|
33
|
+
Examples:
|
|
34
|
+
oncall npm run start
|
|
35
|
+
oncall node server.js
|
|
36
|
+
oncall python app.py
|
|
37
|
+
|
|
38
|
+
Commands:
|
|
39
|
+
login authenticate OnCall using an auth key
|
|
40
|
+
init register the current project
|
|
41
|
+
cluster start the local OnCall server
|
|
42
|
+
version show version information
|
|
43
|
+
|
|
44
|
+
Use --help with individual commands for details.
|
|
45
|
+
|
|
46
|
+
Get an auth key from:
|
|
47
|
+
<https://app.oncall.build>
|
|
48
|
+
`);
|
|
49
|
+
}
|
|
50
|
+
//printLoginHelp
|
|
51
|
+
export function printLoginHelp() {
|
|
52
|
+
console.log(`Authenticate OnCall using an auth key.
|
|
53
|
+
|
|
54
|
+
Usage:
|
|
55
|
+
oncall login <auth-key>
|
|
56
|
+
|
|
57
|
+
Get an auth key from:
|
|
58
|
+
<https://oncall.dev/account>
|
|
59
|
+
`);
|
|
60
|
+
}
|
|
61
|
+
//print init help
|
|
62
|
+
export function printInitHelp() {
|
|
63
|
+
console.log(`Register a process with an OnCall project.
|
|
64
|
+
|
|
65
|
+
An OnCall project groups multiple processes together
|
|
66
|
+
so their runtime signals can be referenced in the same context.
|
|
67
|
+
|
|
68
|
+
Usage:
|
|
69
|
+
oncall init --id <project-id> -m "<process description>"
|
|
70
|
+
|
|
71
|
+
Options:
|
|
72
|
+
--id Project identifier used to group related processes
|
|
73
|
+
-m Short description of this process (used in conversations)
|
|
74
|
+
|
|
75
|
+
Notes:
|
|
76
|
+
• Multiple processes can share the same project ID
|
|
77
|
+
• Each process must have its own description
|
|
78
|
+
• Processes may be started from different directories
|
|
79
|
+
|
|
80
|
+
`);
|
|
81
|
+
}
|
|
82
|
+
//print cluster help
|
|
83
|
+
export function printClusterHelp() {
|
|
84
|
+
console.log(`Start the local OnCall server for this project.
|
|
85
|
+
|
|
86
|
+
The OnCall server connects all processes in the project
|
|
87
|
+
so their runtime signals (logs, errors, requests) can be
|
|
88
|
+
referenced together.
|
|
89
|
+
|
|
90
|
+
This command must be running before you execute:
|
|
91
|
+
oncall <your command>
|
|
92
|
+
|
|
93
|
+
Usage:
|
|
94
|
+
oncall cluster
|
|
95
|
+
|
|
96
|
+
Notes:
|
|
97
|
+
• Keep this running in a separate terminal
|
|
98
|
+
• Run each service or process using "oncall <your command>"
|
|
99
|
+
• Press Ctrl+C to stop the server
|
|
100
|
+
`);
|
|
101
|
+
}
|
|
102
|
+
export function ensureConfigDir() {
|
|
103
|
+
if (!fs.existsSync(ONCALL_DIR)) {
|
|
104
|
+
fs.mkdirSync(ONCALL_DIR, { recursive: true });
|
|
105
|
+
console.log(`✅ Created configuration directory: ${ONCALL_DIR}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
export function buildYaml(projectId, description, name) {
|
|
109
|
+
const escape = (value) => value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
110
|
+
const lines = [
|
|
111
|
+
`id: "${escape(projectId)}"`,
|
|
112
|
+
`description: "${escape(description)}"`,
|
|
113
|
+
];
|
|
114
|
+
if (name) {
|
|
115
|
+
lines.push(`name: "${escape(name)}"`);
|
|
116
|
+
}
|
|
117
|
+
const window_id = Date.now();
|
|
118
|
+
lines.push(`window_id: ${window_id}`);
|
|
119
|
+
lines.push(`logs_available: true`);
|
|
120
|
+
lines.push(`code_available: true`);
|
|
121
|
+
return `${lines.join("\n")}\n`;
|
|
122
|
+
}
|
|
123
|
+
export function loadProjectMetadata(cwdPath = process.cwd()) {
|
|
124
|
+
const yamlPath = path.join(cwdPath, "oncall.yaml");
|
|
125
|
+
if (!fs.existsSync(yamlPath)) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
const contents = fs.readFileSync(yamlPath, "utf8");
|
|
130
|
+
const data = YAML.parse(contents);
|
|
131
|
+
if (!data?.id || !data?.description) {
|
|
132
|
+
console.warn('oncall.yaml is missing either "id" or "description". Proceeding without project metadata.');
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
id: data?.id || "",
|
|
137
|
+
description: data?.description || "",
|
|
138
|
+
name: data?.name || "",
|
|
139
|
+
window_id: data?.window_id ? Number(data.window_id) : undefined,
|
|
140
|
+
logs_available: data?.logs_available || true,
|
|
141
|
+
code_available: data?.code_available || true,
|
|
142
|
+
path: cwdPath,
|
|
143
|
+
raw: contents,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
console.warn("Failed to read oncall.yaml. Proceeding without it.", error);
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
export function registerProjectWithCluster(metadata, clusterUrl = process.env.ONCALL_CLUSTER_URL || "ws://127.0.0.1:4466") {
|
|
152
|
+
return new Promise((resolve) => {
|
|
153
|
+
let settled = false;
|
|
154
|
+
let socket;
|
|
155
|
+
try {
|
|
156
|
+
socket = new WebSocket(clusterUrl);
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
console.warn("Unable to connect to local cluster server. Continuing without registration.");
|
|
160
|
+
resolve(false);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const cleanup = (result) => {
|
|
164
|
+
if (settled)
|
|
165
|
+
return;
|
|
166
|
+
settled = true;
|
|
167
|
+
clearTimeout(timeoutId);
|
|
168
|
+
try {
|
|
169
|
+
socket?.close();
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
// ignore
|
|
173
|
+
}
|
|
174
|
+
resolve(result);
|
|
175
|
+
};
|
|
176
|
+
const timeoutId = setTimeout(() => {
|
|
177
|
+
cleanup(false);
|
|
178
|
+
}, 2000);
|
|
179
|
+
socket.on("open", () => {
|
|
180
|
+
try {
|
|
181
|
+
const projectPayload = {
|
|
182
|
+
id: metadata.id,
|
|
183
|
+
description: metadata.description,
|
|
184
|
+
name: metadata.name,
|
|
185
|
+
path: metadata.path,
|
|
186
|
+
window_id: typeof metadata.window_id === "number"
|
|
187
|
+
? metadata.window_id
|
|
188
|
+
: Date.now(),
|
|
189
|
+
logs_available: typeof metadata.logs_available === "boolean"
|
|
190
|
+
? metadata.logs_available
|
|
191
|
+
: true,
|
|
192
|
+
code_available: typeof metadata.code_available === "boolean"
|
|
193
|
+
? metadata.code_available
|
|
194
|
+
: true,
|
|
195
|
+
};
|
|
196
|
+
socket.send(JSON.stringify({
|
|
197
|
+
type: "register",
|
|
198
|
+
project: projectPayload,
|
|
199
|
+
}));
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
console.warn("Failed to send register payload to local cluster server. Continuing without registration.");
|
|
203
|
+
cleanup(false);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
socket.on("message", (data) => {
|
|
207
|
+
try {
|
|
208
|
+
const serialized = typeof data === "string" ? data : data.toString("utf8");
|
|
209
|
+
if (!serialized) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const parsed = JSON.parse(serialized);
|
|
213
|
+
if (parsed.type === "register_ack") {
|
|
214
|
+
cleanup(true);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
// ignore malformed ack
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
socket.on("close", () => {
|
|
222
|
+
cleanup(true);
|
|
223
|
+
});
|
|
224
|
+
socket.on("error", () => {
|
|
225
|
+
cleanup(false);
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
export function fetchProjectsFromCluster(metadata, clusterUrl = process.env.ONCALL_CLUSTER_URL || "ws://127.0.0.1:4466") {
|
|
230
|
+
logd(`Called fetchProjects, ${JSON.stringify(metadata)}`);
|
|
231
|
+
return new Promise((resolve) => {
|
|
232
|
+
let settled = false;
|
|
233
|
+
let socket;
|
|
234
|
+
try {
|
|
235
|
+
logd(`connecting to socket cluster to fetch`);
|
|
236
|
+
socket = new WebSocket(clusterUrl);
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
console.warn("Unable to connect to local cluster server. Could not fetch projects.");
|
|
240
|
+
logd("Unable to connect to local cluster server. Could not fetch projects.");
|
|
241
|
+
resolve(false);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const cleanup = (result) => {
|
|
245
|
+
if (settled)
|
|
246
|
+
return;
|
|
247
|
+
settled = true;
|
|
248
|
+
clearTimeout(timeoutId);
|
|
249
|
+
try {
|
|
250
|
+
socket?.close();
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
// ignore
|
|
254
|
+
}
|
|
255
|
+
resolve(result);
|
|
256
|
+
};
|
|
257
|
+
const timeoutId = setTimeout(() => {
|
|
258
|
+
logd("Fetching projects timed out");
|
|
259
|
+
cleanup(false);
|
|
260
|
+
}, 2000);
|
|
261
|
+
socket.on("open", () => {
|
|
262
|
+
try {
|
|
263
|
+
socket.send(JSON.stringify({
|
|
264
|
+
type: "fetch_projects",
|
|
265
|
+
id: metadata.id,
|
|
266
|
+
}));
|
|
267
|
+
}
|
|
268
|
+
catch (error) {
|
|
269
|
+
console.warn("Failed to send request to local cluster server. Continuing without fetching projects.");
|
|
270
|
+
logd("error sending fetch_projects req to cluster");
|
|
271
|
+
cleanup(false);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
socket.on("message", (data) => {
|
|
275
|
+
try {
|
|
276
|
+
const serialized = typeof data === "string" ? data : data.toString("utf8");
|
|
277
|
+
if (!serialized) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
const parsed = JSON.parse(serialized);
|
|
281
|
+
if (parsed.type === "fetch_projects_ack") {
|
|
282
|
+
cleanup(parsed);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
// ignore malformed ack
|
|
287
|
+
logd("Malformed ack");
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
socket.on("close", () => {
|
|
291
|
+
logd("Connection closed");
|
|
292
|
+
cleanup(false);
|
|
293
|
+
});
|
|
294
|
+
socket.on("error", () => {
|
|
295
|
+
logd("Connection errored");
|
|
296
|
+
cleanup(false);
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
export async function ensureClusterIsReady(metadata, registrationPromise) {
|
|
301
|
+
if (!metadata) {
|
|
302
|
+
await registrationPromise?.catch(() => null);
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
try {
|
|
306
|
+
const result = await registrationPromise;
|
|
307
|
+
return result !== false;
|
|
308
|
+
}
|
|
309
|
+
catch {
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
export function logd(d) {
|
|
314
|
+
// try {
|
|
315
|
+
// if (!fs.existsSync(LOGS_DIR)) {
|
|
316
|
+
// fs.mkdirSync(LOGS_DIR, { recursive: true });
|
|
317
|
+
// }
|
|
318
|
+
// const now = new Date();
|
|
319
|
+
// const day = String(now.getDate()).padStart(2, "0");
|
|
320
|
+
// const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
321
|
+
// const year = String(now.getFullYear());
|
|
322
|
+
// const dateStr = `${day}${month}${year}`;
|
|
323
|
+
// const logFilePath = path.join(LOGS_DIR, `logs_${dateStr}.txt`);
|
|
324
|
+
// fs.appendFileSync(logFilePath, `${d}\n`);
|
|
325
|
+
// } catch (error) {
|
|
326
|
+
// console.error("Failed to log to logs file", error);
|
|
327
|
+
// }
|
|
328
|
+
}
|
|
329
|
+
//# sourceMappingURL=cli-helpers.js.map
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import readline from "readline";
|
|
2
|
+
import yaml from "js-yaml";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
|
|
5
|
+
let onCallYAML = {};
|
|
6
|
+
if (fs.existsSync("oncall.yaml")) {
|
|
7
|
+
const readYAMLFile = fs.readFileSync("oncall.yaml", "utf8");
|
|
8
|
+
onCallYAML = yaml.load(readYAMLFile) ?? {};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function escapeRegExp(value) {
|
|
12
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function upsertConfigValue(contents, key, value) {
|
|
16
|
+
const keyRegex = new RegExp(`^${escapeRegExp(key)}\\s*=.*$`, "m");
|
|
17
|
+
const nextLine = `${key}=${value}`;
|
|
18
|
+
if (keyRegex.test(contents)) {
|
|
19
|
+
return contents.replace(keyRegex, nextLine);
|
|
20
|
+
}
|
|
21
|
+
const trimmed = contents.trimEnd();
|
|
22
|
+
const prefix = trimmed.length > 0 ? `${trimmed}\n` : "";
|
|
23
|
+
return `${prefix}${nextLine}\n`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function readProjects(contents) {
|
|
27
|
+
const match = contents.match(/^projects\s*=\s*(\[.*\]|\{.*\})$/m);
|
|
28
|
+
if (!match) {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const parsed = JSON.parse(match[1]);
|
|
33
|
+
if (Array.isArray(parsed)) {
|
|
34
|
+
return parsed;
|
|
35
|
+
}
|
|
36
|
+
return Object.entries(parsed).map(([id, list]) => ({
|
|
37
|
+
id,
|
|
38
|
+
projects: Array.isArray(list) ? list : [],
|
|
39
|
+
}));
|
|
40
|
+
} catch (err) {
|
|
41
|
+
console.warn(
|
|
42
|
+
"⚠️ Failed to parse projects JSON; resetting to empty array."
|
|
43
|
+
);
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function writeProjects(contents, projectsArr) {
|
|
49
|
+
const serialized = JSON.stringify(projectsArr);
|
|
50
|
+
return upsertConfigValue(contents, "projects", serialized);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function parseConfigFlags(argsList) {
|
|
54
|
+
let projectId = onCallYAML.id;
|
|
55
|
+
let message = onCallYAML.description;
|
|
56
|
+
|
|
57
|
+
for (let i = 1; i < argsList.length; i += 1) {
|
|
58
|
+
const token = argsList[i];
|
|
59
|
+
|
|
60
|
+
if (token === "-id") {
|
|
61
|
+
if (projectId !== undefined) {
|
|
62
|
+
console.error("❌ Duplicate -id flag detected.");
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
const next = argsList[i + 1];
|
|
66
|
+
if (!next) {
|
|
67
|
+
console.error(`Missing project ID.
|
|
68
|
+
|
|
69
|
+
Usage:
|
|
70
|
+
oncall init --id <project-id> [-m "<description>"]`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
projectId = next.trim();
|
|
74
|
+
i += 1;
|
|
75
|
+
} else if (token === "-m") {
|
|
76
|
+
if (message !== undefined) {
|
|
77
|
+
console.error("❌ Duplicate -m flag detected.");
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
const next = argsList[i + 1];
|
|
81
|
+
if (!next) {
|
|
82
|
+
console.error(`Missing process description.
|
|
83
|
+
|
|
84
|
+
Each process needs a short description so it can
|
|
85
|
+
be referenced later in conversations.
|
|
86
|
+
|
|
87
|
+
Usage:
|
|
88
|
+
oncall init -id <project-id> -m "<process description>"
|
|
89
|
+
`);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
message = next.trim();
|
|
93
|
+
i += 1;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (!message) {
|
|
97
|
+
console.error(`Missing process description.
|
|
98
|
+
|
|
99
|
+
Each process needs a short description so it can
|
|
100
|
+
be referenced later in conversations.
|
|
101
|
+
|
|
102
|
+
Usage:
|
|
103
|
+
oncall init -id <project-id> -m "<process description>"
|
|
104
|
+
`);
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
return { projectId, message };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function prompt(question) {
|
|
111
|
+
const rl = readline.createInterface({
|
|
112
|
+
input: process.stdin,
|
|
113
|
+
output: process.stdout,
|
|
114
|
+
});
|
|
115
|
+
return new Promise((resolve) => {
|
|
116
|
+
rl.question(question, (answer) => {
|
|
117
|
+
rl.close();
|
|
118
|
+
resolve(answer.trim());
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function promptForNewProjectId(projectsList) {
|
|
124
|
+
while (true) {
|
|
125
|
+
const input = await prompt("Enter new project id: ");
|
|
126
|
+
if (!input) {
|
|
127
|
+
console.log("Project id cannot be empty.");
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const exists = projectsList.some((entry) => entry.id === input);
|
|
131
|
+
if (exists) {
|
|
132
|
+
console.log("Project id already exists. Please choose a different id.");
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
return input;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function promptForExistingProject(projectsList) {
|
|
140
|
+
console.log("\nAvailable projects:");
|
|
141
|
+
projectsList.forEach((entry, index) => {
|
|
142
|
+
console.log(` ${index + 1}. ${entry.id}`);
|
|
143
|
+
});
|
|
144
|
+
while (true) {
|
|
145
|
+
const input = await prompt("Select a project by number: ");
|
|
146
|
+
const choice = Number.parseInt(input, 10);
|
|
147
|
+
if (Number.isNaN(choice) || choice < 1 || choice > projectsList.length) {
|
|
148
|
+
console.log("Please enter a valid number from the list.");
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
return projectsList[choice - 1].id;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export async function resolveProjectId(initialId, projectsList) {
|
|
156
|
+
if (initialId) {
|
|
157
|
+
return initialId;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
console.log("\nChoose an option:");
|
|
161
|
+
console.log(" 1. Create new project");
|
|
162
|
+
console.log(" 2. Use existing project");
|
|
163
|
+
|
|
164
|
+
while (true) {
|
|
165
|
+
const choice = await prompt("Enter choice (1 or 2): ");
|
|
166
|
+
if (choice === "1") {
|
|
167
|
+
return promptForNewProjectId(projectsList);
|
|
168
|
+
}
|
|
169
|
+
if (choice === "2") {
|
|
170
|
+
if (projectsList.length === 0) {
|
|
171
|
+
console.log("No existing projects found. Please create a new project.");
|
|
172
|
+
return promptForNewProjectId(projectsList);
|
|
173
|
+
}
|
|
174
|
+
return promptForExistingProject(projectsList);
|
|
175
|
+
}
|
|
176
|
+
console.log("Invalid choice. Please enter 1 or 2.");
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export async function promptForInput(message) {
|
|
181
|
+
while (true) {
|
|
182
|
+
const input = await prompt(`${message} `);
|
|
183
|
+
if (!input) {
|
|
184
|
+
console.log("Project id cannot be empty.");
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
return input;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface RipgrepResult {
|
|
2
|
+
filePath: string;
|
|
3
|
+
line?: number | null;
|
|
4
|
+
preview?: string;
|
|
5
|
+
score?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface RipgrepOptions {
|
|
8
|
+
maxResults?: number;
|
|
9
|
+
caseSensitive?: boolean;
|
|
10
|
+
fileTypes?: string[];
|
|
11
|
+
excludePatterns?: string[];
|
|
12
|
+
workingDirectory?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function ripgrepSearch(query: string, options?: RipgrepOptions): Promise<RipgrepResult[]>;
|
|
15
|
+
export declare function ripgrepSearchMultiple(queries: string[], options?: RipgrepOptions): Promise<RipgrepResult[]>;
|