@qoder-ai/qodercli 0.2.2-beta.9 → 0.2.3
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/README.md +2 -327
- package/bin/qodercli +47 -0
- package/package.json +93 -15
- package/scripts/install.js +647 -0
- package/bin/builtin/agent-creator/SKILL.md +0 -327
- package/bin/builtin/hook-config/SKILL.md +0 -480
- package/bin/builtin/mcp-config/SKILL.md +0 -155
- package/bin/builtin/skill-creator/SKILL.md +0 -294
- package/bin/node_modules/@google/gemini-cli-devtools/dist/client/main.js +0 -101
- package/bin/node_modules/@google/gemini-cli-devtools/dist/src/_client-assets.d.ts +0 -7
- package/bin/node_modules/@google/gemini-cli-devtools/dist/src/_client-assets.js +0 -9
- package/bin/node_modules/@google/gemini-cli-devtools/dist/src/index.d.ts +0 -48
- package/bin/node_modules/@google/gemini-cli-devtools/dist/src/index.js +0 -323
- package/bin/node_modules/@google/gemini-cli-devtools/dist/src/types.d.ts +0 -36
- package/bin/node_modules/@google/gemini-cli-devtools/dist/src/types.js +0 -7
- package/bin/node_modules/@google/gemini-cli-devtools/package.json +0 -34
- package/bin/run.js +0 -85
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2025 Google LLC
|
|
4
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
-
*/
|
|
6
|
-
import { EventEmitter } from 'node:events';
|
|
7
|
-
import { type WebSocket } from 'ws';
|
|
8
|
-
import type { NetworkLog, ConsoleLogPayload } from './types.js';
|
|
9
|
-
export type { NetworkLog, ConsoleLogPayload, InspectorConsoleLog, } from './types.js';
|
|
10
|
-
interface IncomingNetworkPayload extends Partial<NetworkLog> {
|
|
11
|
-
chunk?: {
|
|
12
|
-
index: number;
|
|
13
|
-
data: string;
|
|
14
|
-
timestamp: number;
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
export interface SessionInfo {
|
|
18
|
-
sessionId: string;
|
|
19
|
-
ws: WebSocket;
|
|
20
|
-
lastPing: number;
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* DevTools Viewer
|
|
24
|
-
*
|
|
25
|
-
* Receives logs via WebSocket from CLI sessions.
|
|
26
|
-
*/
|
|
27
|
-
export declare class DevTools extends EventEmitter {
|
|
28
|
-
private static instance;
|
|
29
|
-
private logs;
|
|
30
|
-
private consoleLogs;
|
|
31
|
-
private server;
|
|
32
|
-
private wss;
|
|
33
|
-
private sessions;
|
|
34
|
-
private heartbeatTimer;
|
|
35
|
-
private port;
|
|
36
|
-
private static readonly DEFAULT_PORT;
|
|
37
|
-
private static readonly MAX_PORT_RETRIES;
|
|
38
|
-
private constructor();
|
|
39
|
-
static getInstance(): DevTools;
|
|
40
|
-
addInternalConsoleLog(payload: ConsoleLogPayload, sessionId?: string, timestamp?: number): void;
|
|
41
|
-
addInternalNetworkLog(payload: IncomingNetworkPayload, sessionId?: string, timestamp?: number): void;
|
|
42
|
-
getUrl(): string;
|
|
43
|
-
getPort(): number;
|
|
44
|
-
stop(): Promise<void>;
|
|
45
|
-
start(): Promise<string>;
|
|
46
|
-
private setupWebSocketServer;
|
|
47
|
-
private handleWebSocketMessage;
|
|
48
|
-
}
|
|
@@ -1,323 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2025 Google LLC
|
|
4
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
-
*/
|
|
6
|
-
import http from 'node:http';
|
|
7
|
-
import { randomUUID } from 'node:crypto';
|
|
8
|
-
import { EventEmitter } from 'node:events';
|
|
9
|
-
import { WebSocketServer } from 'ws';
|
|
10
|
-
import { INDEX_HTML, CLIENT_JS } from './_client-assets.js';
|
|
11
|
-
/**
|
|
12
|
-
* DevTools Viewer
|
|
13
|
-
*
|
|
14
|
-
* Receives logs via WebSocket from CLI sessions.
|
|
15
|
-
*/
|
|
16
|
-
export class DevTools extends EventEmitter {
|
|
17
|
-
static instance;
|
|
18
|
-
logs = [];
|
|
19
|
-
consoleLogs = [];
|
|
20
|
-
server = null;
|
|
21
|
-
wss = null;
|
|
22
|
-
sessions = new Map();
|
|
23
|
-
heartbeatTimer = null;
|
|
24
|
-
port = 25417;
|
|
25
|
-
static DEFAULT_PORT = 25417;
|
|
26
|
-
static MAX_PORT_RETRIES = 10;
|
|
27
|
-
constructor() {
|
|
28
|
-
super();
|
|
29
|
-
// Each SSE client adds 3 listeners; raise the limit to avoid warnings
|
|
30
|
-
this.setMaxListeners(50);
|
|
31
|
-
}
|
|
32
|
-
static getInstance() {
|
|
33
|
-
if (!DevTools.instance) {
|
|
34
|
-
DevTools.instance = new DevTools();
|
|
35
|
-
}
|
|
36
|
-
return DevTools.instance;
|
|
37
|
-
}
|
|
38
|
-
addInternalConsoleLog(payload, sessionId, timestamp) {
|
|
39
|
-
const entry = {
|
|
40
|
-
...payload,
|
|
41
|
-
id: randomUUID(),
|
|
42
|
-
sessionId,
|
|
43
|
-
timestamp: timestamp || Date.now(),
|
|
44
|
-
};
|
|
45
|
-
this.consoleLogs.push(entry);
|
|
46
|
-
if (this.consoleLogs.length > 5000)
|
|
47
|
-
this.consoleLogs.shift();
|
|
48
|
-
this.emit('console-update', entry);
|
|
49
|
-
}
|
|
50
|
-
addInternalNetworkLog(payload, sessionId, timestamp) {
|
|
51
|
-
if (!payload.id)
|
|
52
|
-
return;
|
|
53
|
-
const existingIndex = this.logs.findIndex((l) => l.id === payload.id);
|
|
54
|
-
if (existingIndex > -1) {
|
|
55
|
-
const existing = this.logs[existingIndex];
|
|
56
|
-
// Handle chunk accumulation
|
|
57
|
-
if (payload.chunk) {
|
|
58
|
-
const chunks = existing.chunks || [];
|
|
59
|
-
chunks.push(payload.chunk);
|
|
60
|
-
this.logs[existingIndex] = {
|
|
61
|
-
...existing,
|
|
62
|
-
chunks,
|
|
63
|
-
sessionId: sessionId || existing.sessionId,
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
else {
|
|
67
|
-
this.logs[existingIndex] = {
|
|
68
|
-
...existing,
|
|
69
|
-
...payload,
|
|
70
|
-
sessionId: sessionId || existing.sessionId,
|
|
71
|
-
// Drop chunks once we have the full response body — the data
|
|
72
|
-
// is redundant and keeping both can blow past V8's string limit
|
|
73
|
-
// when serializing the snapshot.
|
|
74
|
-
chunks: payload.response?.body ? undefined : existing.chunks,
|
|
75
|
-
response: payload.response
|
|
76
|
-
? { ...existing.response, ...payload.response }
|
|
77
|
-
: existing.response,
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
this.emit('update', this.logs[existingIndex]);
|
|
81
|
-
}
|
|
82
|
-
else if (payload.url) {
|
|
83
|
-
const entry = {
|
|
84
|
-
...payload,
|
|
85
|
-
sessionId,
|
|
86
|
-
timestamp: timestamp || Date.now(),
|
|
87
|
-
chunks: payload.chunk ? [payload.chunk] : undefined,
|
|
88
|
-
};
|
|
89
|
-
this.logs.push(entry);
|
|
90
|
-
if (this.logs.length > 2000)
|
|
91
|
-
this.logs.shift();
|
|
92
|
-
this.emit('update', entry);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
getUrl() {
|
|
96
|
-
return `http://127.0.0.1:${this.port}`;
|
|
97
|
-
}
|
|
98
|
-
getPort() {
|
|
99
|
-
return this.port;
|
|
100
|
-
}
|
|
101
|
-
stop() {
|
|
102
|
-
return new Promise((resolve) => {
|
|
103
|
-
if (this.heartbeatTimer) {
|
|
104
|
-
clearInterval(this.heartbeatTimer);
|
|
105
|
-
this.heartbeatTimer = null;
|
|
106
|
-
}
|
|
107
|
-
if (this.wss) {
|
|
108
|
-
this.wss.close();
|
|
109
|
-
this.wss = null;
|
|
110
|
-
}
|
|
111
|
-
if (this.server) {
|
|
112
|
-
this.server.close(() => resolve());
|
|
113
|
-
this.server = null;
|
|
114
|
-
}
|
|
115
|
-
else {
|
|
116
|
-
resolve();
|
|
117
|
-
}
|
|
118
|
-
// Reset singleton so a fresh start() is possible
|
|
119
|
-
DevTools.instance = undefined;
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
start() {
|
|
123
|
-
return new Promise((resolve, reject) => {
|
|
124
|
-
if (this.server) {
|
|
125
|
-
resolve(this.getUrl());
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
this.server = http.createServer((req, res) => {
|
|
129
|
-
// Only allow same-origin requests — the client is served from this
|
|
130
|
-
// server so cross-origin access is unnecessary and would let arbitrary
|
|
131
|
-
// websites exfiltrate logs (which may contain API keys/headers).
|
|
132
|
-
const origin = req.headers.origin;
|
|
133
|
-
if (origin) {
|
|
134
|
-
const allowed = `http://127.0.0.1:${this.port}`;
|
|
135
|
-
if (origin === allowed) {
|
|
136
|
-
res.setHeader('Access-Control-Allow-Origin', allowed);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
// API routes
|
|
140
|
-
if (req.url === '/api/trigger-debugger' && req.method === 'POST') {
|
|
141
|
-
let body = '';
|
|
142
|
-
req.on('data', (chunk) => {
|
|
143
|
-
body += chunk;
|
|
144
|
-
});
|
|
145
|
-
req.on('end', () => {
|
|
146
|
-
try {
|
|
147
|
-
const parsed = JSON.parse(body);
|
|
148
|
-
if (typeof parsed !== 'object' ||
|
|
149
|
-
parsed === null ||
|
|
150
|
-
!('sessionId' in parsed) ||
|
|
151
|
-
typeof parsed.sessionId !== 'string') {
|
|
152
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
153
|
-
res.end(JSON.stringify({ error: 'Invalid request' }));
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
const sessionId = parsed.sessionId;
|
|
157
|
-
const session = this.sessions.get(sessionId);
|
|
158
|
-
if (session) {
|
|
159
|
-
session.ws.send(JSON.stringify({ type: 'trigger-debugger' }));
|
|
160
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
161
|
-
res.end(JSON.stringify({ success: true }));
|
|
162
|
-
}
|
|
163
|
-
else {
|
|
164
|
-
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
165
|
-
res.end(JSON.stringify({ error: 'Session not found' }));
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
catch {
|
|
169
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
170
|
-
res.end(JSON.stringify({ error: 'Invalid request' }));
|
|
171
|
-
}
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
else if (req.url === '/events') {
|
|
175
|
-
res.writeHead(200, {
|
|
176
|
-
'Content-Type': 'text/event-stream',
|
|
177
|
-
'Cache-Control': 'no-cache',
|
|
178
|
-
Connection: 'keep-alive',
|
|
179
|
-
});
|
|
180
|
-
// Send full snapshot on connect
|
|
181
|
-
const snapshot = JSON.stringify({
|
|
182
|
-
networkLogs: this.logs,
|
|
183
|
-
consoleLogs: this.consoleLogs,
|
|
184
|
-
sessions: Array.from(this.sessions.keys()),
|
|
185
|
-
});
|
|
186
|
-
res.write(`event: snapshot\ndata: ${snapshot}\n\n`);
|
|
187
|
-
// Incremental updates
|
|
188
|
-
const onNetwork = (log) => {
|
|
189
|
-
res.write(`event: network\ndata: ${JSON.stringify(log)}\n\n`);
|
|
190
|
-
};
|
|
191
|
-
const onConsole = (log) => {
|
|
192
|
-
res.write(`event: console\ndata: ${JSON.stringify(log)}\n\n`);
|
|
193
|
-
};
|
|
194
|
-
const onSession = () => {
|
|
195
|
-
const sessions = Array.from(this.sessions.keys());
|
|
196
|
-
res.write(`event: session\ndata: ${JSON.stringify(sessions)}\n\n`);
|
|
197
|
-
};
|
|
198
|
-
this.on('update', onNetwork);
|
|
199
|
-
this.on('console-update', onConsole);
|
|
200
|
-
this.on('session-update', onSession);
|
|
201
|
-
req.on('close', () => {
|
|
202
|
-
this.off('update', onNetwork);
|
|
203
|
-
this.off('console-update', onConsole);
|
|
204
|
-
this.off('session-update', onSession);
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
else if (req.url === '/' || req.url === '/index.html') {
|
|
208
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
209
|
-
res.end(INDEX_HTML);
|
|
210
|
-
}
|
|
211
|
-
else if (req.url === '/assets/main.js') {
|
|
212
|
-
res.writeHead(200, { 'Content-Type': 'application/javascript' });
|
|
213
|
-
res.end(CLIENT_JS);
|
|
214
|
-
}
|
|
215
|
-
else {
|
|
216
|
-
res.writeHead(404);
|
|
217
|
-
res.end('Not Found');
|
|
218
|
-
}
|
|
219
|
-
});
|
|
220
|
-
this.server.on('error', (e) => {
|
|
221
|
-
if (typeof e === 'object' &&
|
|
222
|
-
e !== null &&
|
|
223
|
-
'code' in e &&
|
|
224
|
-
e.code === 'EADDRINUSE') {
|
|
225
|
-
if (this.port - DevTools.DEFAULT_PORT >= DevTools.MAX_PORT_RETRIES) {
|
|
226
|
-
reject(new Error(`DevTools: all ports ${DevTools.DEFAULT_PORT}–${this.port} in use`));
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
this.port++;
|
|
230
|
-
this.server?.listen(this.port, '127.0.0.1');
|
|
231
|
-
}
|
|
232
|
-
else {
|
|
233
|
-
reject(e instanceof Error ? e : new Error(String(e)));
|
|
234
|
-
}
|
|
235
|
-
});
|
|
236
|
-
this.server.listen(this.port, '127.0.0.1', () => {
|
|
237
|
-
this.setupWebSocketServer();
|
|
238
|
-
resolve(this.getUrl());
|
|
239
|
-
});
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
setupWebSocketServer() {
|
|
243
|
-
if (!this.server)
|
|
244
|
-
return;
|
|
245
|
-
this.wss = new WebSocketServer({ server: this.server, path: '/ws' });
|
|
246
|
-
this.wss.on('connection', (ws) => {
|
|
247
|
-
let sessionId = null;
|
|
248
|
-
ws.on('message', (data) => {
|
|
249
|
-
try {
|
|
250
|
-
const message = JSON.parse(data.toString());
|
|
251
|
-
// Handle registration first
|
|
252
|
-
if (message.type === 'register') {
|
|
253
|
-
sessionId = String(message.sessionId);
|
|
254
|
-
if (!sessionId)
|
|
255
|
-
return;
|
|
256
|
-
this.sessions.set(sessionId, {
|
|
257
|
-
sessionId,
|
|
258
|
-
ws,
|
|
259
|
-
lastPing: Date.now(),
|
|
260
|
-
});
|
|
261
|
-
// Notify session update
|
|
262
|
-
this.emit('session-update');
|
|
263
|
-
// Send registration acknowledgement
|
|
264
|
-
ws.send(JSON.stringify({
|
|
265
|
-
type: 'registered',
|
|
266
|
-
sessionId,
|
|
267
|
-
timestamp: Date.now(),
|
|
268
|
-
}));
|
|
269
|
-
}
|
|
270
|
-
else if (sessionId) {
|
|
271
|
-
this.handleWebSocketMessage(sessionId, message);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
catch {
|
|
275
|
-
// Invalid WebSocket message
|
|
276
|
-
}
|
|
277
|
-
});
|
|
278
|
-
ws.on('close', () => {
|
|
279
|
-
if (sessionId) {
|
|
280
|
-
this.sessions.delete(sessionId);
|
|
281
|
-
this.emit('session-update');
|
|
282
|
-
}
|
|
283
|
-
});
|
|
284
|
-
ws.on('error', () => {
|
|
285
|
-
// WebSocket error — no action needed
|
|
286
|
-
});
|
|
287
|
-
});
|
|
288
|
-
// Heartbeat mechanism
|
|
289
|
-
this.heartbeatTimer = setInterval(() => {
|
|
290
|
-
const now = Date.now();
|
|
291
|
-
this.sessions.forEach((session, sessionId) => {
|
|
292
|
-
if (now - session.lastPing > 30000) {
|
|
293
|
-
session.ws.close();
|
|
294
|
-
this.sessions.delete(sessionId);
|
|
295
|
-
}
|
|
296
|
-
else {
|
|
297
|
-
// Send ping
|
|
298
|
-
session.ws.send(JSON.stringify({ type: 'ping', timestamp: now }));
|
|
299
|
-
}
|
|
300
|
-
});
|
|
301
|
-
}, 10000);
|
|
302
|
-
this.heartbeatTimer.unref();
|
|
303
|
-
}
|
|
304
|
-
handleWebSocketMessage(sessionId, message) {
|
|
305
|
-
const session = this.sessions.get(sessionId);
|
|
306
|
-
if (!session)
|
|
307
|
-
return;
|
|
308
|
-
switch (message['type']) {
|
|
309
|
-
case 'pong':
|
|
310
|
-
session.lastPing = Date.now();
|
|
311
|
-
break;
|
|
312
|
-
case 'console':
|
|
313
|
-
this.addInternalConsoleLog(message['payload'], sessionId, message['timestamp']);
|
|
314
|
-
break;
|
|
315
|
-
case 'network':
|
|
316
|
-
this.addInternalNetworkLog(message['payload'], sessionId, message['timestamp']);
|
|
317
|
-
break;
|
|
318
|
-
default:
|
|
319
|
-
break;
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2025 Google LLC
|
|
4
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
-
*/
|
|
6
|
-
export interface NetworkLog {
|
|
7
|
-
id: string;
|
|
8
|
-
sessionId?: string;
|
|
9
|
-
timestamp: number;
|
|
10
|
-
method: string;
|
|
11
|
-
url: string;
|
|
12
|
-
headers: Record<string, string | string[] | undefined>;
|
|
13
|
-
body?: string;
|
|
14
|
-
pending?: boolean;
|
|
15
|
-
chunks?: Array<{
|
|
16
|
-
index: number;
|
|
17
|
-
data: string;
|
|
18
|
-
timestamp: number;
|
|
19
|
-
}>;
|
|
20
|
-
response?: {
|
|
21
|
-
status: number;
|
|
22
|
-
headers: Record<string, string | string[] | undefined>;
|
|
23
|
-
body?: string;
|
|
24
|
-
durationMs: number;
|
|
25
|
-
};
|
|
26
|
-
error?: string;
|
|
27
|
-
}
|
|
28
|
-
export interface ConsoleLogPayload {
|
|
29
|
-
type: 'log' | 'warn' | 'error' | 'debug' | 'info';
|
|
30
|
-
content: string;
|
|
31
|
-
}
|
|
32
|
-
export interface InspectorConsoleLog extends ConsoleLogPayload {
|
|
33
|
-
id: string;
|
|
34
|
-
sessionId?: string;
|
|
35
|
-
timestamp: number;
|
|
36
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@google/gemini-cli-devtools",
|
|
3
|
-
"version": "0.2.0",
|
|
4
|
-
"license": "Apache-2.0",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "dist/src/index.js",
|
|
7
|
-
"types": "dist/src/index.d.ts",
|
|
8
|
-
"exports": {
|
|
9
|
-
".": {
|
|
10
|
-
"types": "./dist/src/index.d.ts",
|
|
11
|
-
"default": "./dist/src/index.js"
|
|
12
|
-
}
|
|
13
|
-
},
|
|
14
|
-
"scripts": {
|
|
15
|
-
"build": "npm run build:client && tsc -p tsconfig.build.json",
|
|
16
|
-
"build:client": "node esbuild.client.js"
|
|
17
|
-
},
|
|
18
|
-
"files": [
|
|
19
|
-
"dist",
|
|
20
|
-
"client/index.html"
|
|
21
|
-
],
|
|
22
|
-
"engines": {
|
|
23
|
-
"node": ">=20"
|
|
24
|
-
},
|
|
25
|
-
"devDependencies": {
|
|
26
|
-
"@types/node": "^20.11.24",
|
|
27
|
-
"react": "^19.2.0",
|
|
28
|
-
"react-dom": "^19.2.0",
|
|
29
|
-
"typescript": "^5.3.3"
|
|
30
|
-
},
|
|
31
|
-
"dependencies": {
|
|
32
|
-
"ws": "^8.16.0"
|
|
33
|
-
}
|
|
34
|
-
}
|
package/bin/run.js
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
const { spawnSync } = require('child_process');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
const fs = require('fs');
|
|
5
|
-
|
|
6
|
-
const platform = process.platform;
|
|
7
|
-
const arch = process.arch;
|
|
8
|
-
const target = `${platform}-${arch}`;
|
|
9
|
-
const pkgName = `@qoder-ai/qodercli-${target}`;
|
|
10
|
-
const subPkgDir = pkgName.split('/')[1]; // e.g. "qodercli-win32-x64"
|
|
11
|
-
|
|
12
|
-
// 查找平台子包中的二进制
|
|
13
|
-
const BINARY_NAME = platform === 'win32' ? 'qodercli.exe' : 'qodercli';
|
|
14
|
-
|
|
15
|
-
// __dirname = .../node_modules/@qoder-ai/qodercli/bin/
|
|
16
|
-
// npm global install 结构:
|
|
17
|
-
// prefix/node_modules/@qoder-ai/qodercli/ (主包)
|
|
18
|
-
// prefix/node_modules/@qoder-ai/qodercli-win32-x64/ (子包, 同级)
|
|
19
|
-
// npm local install 结构:
|
|
20
|
-
// project/node_modules/@qoder-ai/qodercli/
|
|
21
|
-
// project/node_modules/@qoder-ai/qodercli-win32-x64/ (hoisted)
|
|
22
|
-
// 或 project/node_modules/@qoder-ai/qodercli/node_modules/@qoder-ai/qodercli-win32-x64/ (nested)
|
|
23
|
-
|
|
24
|
-
const candidates = [
|
|
25
|
-
// 同级 scope 目录下 (global + hoisted local)
|
|
26
|
-
path.join(__dirname, '..', '..', subPkgDir, 'bin', BINARY_NAME),
|
|
27
|
-
// nested in main package's node_modules
|
|
28
|
-
path.join(__dirname, '..', 'node_modules', pkgName, 'bin', BINARY_NAME),
|
|
29
|
-
// 3 levels up (some pnpm/yarn structures)
|
|
30
|
-
path.join(__dirname, '..', '..', '..', 'node_modules', pkgName, 'bin', BINARY_NAME),
|
|
31
|
-
// npm global on Windows may place under node_modules directly
|
|
32
|
-
path.join(__dirname, '..', '..', '..', subPkgDir, 'bin', BINARY_NAME),
|
|
33
|
-
// resolve from require (most reliable for complex layouts)
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
// Also try require.resolve to find the sub-package
|
|
37
|
-
try {
|
|
38
|
-
const subPkgJson = require.resolve(`${pkgName}/package.json`, { paths: [path.join(__dirname, '..')] });
|
|
39
|
-
const subPkgBin = path.join(path.dirname(subPkgJson), 'bin', BINARY_NAME);
|
|
40
|
-
candidates.push(subPkgBin);
|
|
41
|
-
} catch {}
|
|
42
|
-
|
|
43
|
-
let binPath = null;
|
|
44
|
-
for (const p of candidates) {
|
|
45
|
-
try {
|
|
46
|
-
if (fs.existsSync(p)) {
|
|
47
|
-
binPath = p;
|
|
48
|
-
break;
|
|
49
|
-
}
|
|
50
|
-
} catch {}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (!binPath) {
|
|
54
|
-
console.error(`Error: Cannot find qodercli binary for your platform (${target})`);
|
|
55
|
-
console.error('');
|
|
56
|
-
console.error(`The platform-specific package "${pkgName}" does not appear to be installed.`);
|
|
57
|
-
console.error('');
|
|
58
|
-
console.error('__dirname:', __dirname);
|
|
59
|
-
console.error('Searched paths:');
|
|
60
|
-
candidates.forEach(p => console.error(` - ${p} [${fs.existsSync(p) ? 'EXISTS' : 'NOT FOUND'}]`));
|
|
61
|
-
console.error('');
|
|
62
|
-
// List what's actually in the scope directory
|
|
63
|
-
const scopeDir = path.join(__dirname, '..', '..');
|
|
64
|
-
try {
|
|
65
|
-
console.error('Contents of scope dir (' + scopeDir + '):');
|
|
66
|
-
fs.readdirSync(scopeDir).forEach(f => console.error(' ' + f));
|
|
67
|
-
} catch (e) { console.error(' (cannot read: ' + e.message + ')'); }
|
|
68
|
-
console.error('');
|
|
69
|
-
console.error('Supported platforms: darwin-arm64, darwin-x64, linux-x64, linux-arm64, win32-x64');
|
|
70
|
-
console.error('Try: npm install -g @qoder-ai/qodercli --include=optional');
|
|
71
|
-
process.exit(1);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Use spawnSync for reliable cross-platform behavior
|
|
75
|
-
const result = spawnSync(binPath, process.argv.slice(2), {
|
|
76
|
-
stdio: 'inherit',
|
|
77
|
-
windowsHide: false,
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
if (result.error) {
|
|
81
|
-
console.error('Failed to execute qodercli:', result.error.message);
|
|
82
|
-
process.exit(1);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
process.exit(result.status !== null ? result.status : 1);
|