gm-skill 0.1.2 → 2.0.1080

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.
Files changed (85) hide show
  1. package/AGENTS.md +1 -0
  2. package/LICENSE +21 -0
  3. package/README.md +20 -84
  4. package/agents/gm.md +22 -0
  5. package/agents/memorize.md +100 -0
  6. package/agents/research-worker.md +36 -0
  7. package/agents/textprocessing.md +47 -0
  8. package/bin/bootstrap.js +702 -0
  9. package/bin/plugkit.js +136 -0
  10. package/bin/plugkit.sha256 +7 -0
  11. package/bin/plugkit.version +1 -0
  12. package/bin/plugkit.wasm +0 -0
  13. package/bin/plugkit.wasm.sha256 +1 -0
  14. package/bin/rtk.sha256 +6 -0
  15. package/bin/rtk.version +1 -0
  16. package/gm-plugkit/bootstrap.js +694 -0
  17. package/gm-plugkit/cli.js +48 -0
  18. package/gm-plugkit/index.js +12 -0
  19. package/gm-plugkit/package.json +26 -0
  20. package/gm-plugkit/plugkit-wasm-wrapper.js +190 -0
  21. package/gm-plugkit/plugkit.sha256 +6 -0
  22. package/gm-plugkit/plugkit.version +1 -0
  23. package/gm.json +27 -0
  24. package/lang/browser.js +45 -0
  25. package/lang/ssh.js +166 -0
  26. package/lib/browser-spool-handler.js +130 -0
  27. package/lib/browser.js +131 -0
  28. package/lib/codeinsight.js +109 -0
  29. package/lib/daemon-bootstrap.js +253 -132
  30. package/lib/git.js +0 -1
  31. package/lib/learning.js +169 -0
  32. package/lib/skill-bootstrap.js +406 -0
  33. package/lib/spool-dispatch.js +100 -0
  34. package/lib/spool.js +87 -49
  35. package/lib/wasm-host.js +241 -0
  36. package/package.json +38 -20
  37. package/prompts/bash-deny.txt +22 -0
  38. package/prompts/pre-compact.txt +21 -0
  39. package/prompts/prompt-submit.txt +83 -0
  40. package/prompts/session-start.txt +15 -0
  41. package/scripts/run-hook.sh +7 -0
  42. package/scripts/watch-cascade.js +166 -0
  43. package/skills/browser/SKILL.md +80 -0
  44. package/skills/code-search/SKILL.md +48 -0
  45. package/skills/create-lang-plugin/SKILL.md +121 -0
  46. package/skills/gm/SKILL.md +10 -49
  47. package/skills/gm-complete/SKILL.md +16 -87
  48. package/skills/gm-emit/SKILL.md +17 -50
  49. package/skills/gm-execute/SKILL.md +18 -69
  50. package/skills/gm-skill/SKILL.md +43 -0
  51. package/skills/gm-skill/index.js +21 -0
  52. package/skills/governance/SKILL.md +97 -0
  53. package/skills/pages/SKILL.md +208 -0
  54. package/skills/planning/SKILL.md +21 -97
  55. package/skills/research/SKILL.md +43 -0
  56. package/skills/ssh/SKILL.md +71 -0
  57. package/skills/textprocessing/SKILL.md +40 -0
  58. package/skills/update-docs/SKILL.md +24 -43
  59. package/gm-complete.SKILL.md +0 -106
  60. package/gm-emit.SKILL.md +0 -70
  61. package/gm-execute.SKILL.md +0 -88
  62. package/gm.SKILL.md +0 -63
  63. package/index.js +0 -1
  64. package/lib/index.js +0 -37
  65. package/lib/loader.js +0 -66
  66. package/lib/manifest.js +0 -99
  67. package/lib/prepare.js +0 -14
  68. package/planning.SKILL.md +0 -118
  69. package/skills/gm/index.js +0 -113
  70. package/skills/gm-complete/index.js +0 -118
  71. package/skills/gm-complete.SKILL.md +0 -106
  72. package/skills/gm-emit/index.js +0 -90
  73. package/skills/gm-emit.SKILL.md +0 -70
  74. package/skills/gm-execute/index.js +0 -91
  75. package/skills/gm-execute.SKILL.md +0 -88
  76. package/skills/gm.SKILL.md +0 -63
  77. package/skills/planning/index.js +0 -107
  78. package/skills/planning.SKILL.md +0 -118
  79. package/skills/update-docs/index.js +0 -108
  80. package/skills/update-docs.SKILL.md +0 -66
  81. package/test-build.js +0 -29
  82. package/test-e2e.js +0 -117
  83. package/test-unified.js +0 -24
  84. package/test.js +0 -89
  85. package/update-docs.SKILL.md +0 -66
@@ -1,14 +1,14 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
- const net = require('net');
4
3
  const crypto = require('crypto');
5
4
  const { spawn, execSync } = require('child_process');
6
5
  const os = require('os');
6
+ const spool = require('./spool.js');
7
7
 
8
8
  const LOG_DIR = path.join(os.homedir(), '.claude', 'gm-log');
9
9
  const GM_STATE_DIR = path.join(os.homedir(), '.gm');
10
10
 
11
- function emitEvent(daemon, severity, message, details = {}) {
11
+ function emitDaemonEvent(daemon, severity, message, details) {
12
12
  try {
13
13
  const date = new Date().toISOString().split('T')[0];
14
14
  const logDir = path.join(LOG_DIR, date);
@@ -25,7 +25,7 @@ function emitEvent(daemon, severity, message, details = {}) {
25
25
  };
26
26
  fs.appendFileSync(logFile, JSON.stringify(entry) + '\n');
27
27
  } catch (e) {
28
- console.error(`[daemon-bootstrap] emit failed: ${e.message}`);
28
+ console.error(`[daemon-bootstrap] Failed to emit event: ${e.message}`);
29
29
  }
30
30
  }
31
31
 
@@ -65,92 +65,202 @@ function isDaemonRunning(daemonName) {
65
65
  }
66
66
 
67
67
  function checkPortReachable(host, port, timeoutMs = 500) {
68
- return new Promise((resolve) => {
69
- const socket = new net.Socket();
70
- const timeoutHandle = setTimeout(() => {
71
- socket.destroy();
72
- resolve(false);
73
- }, timeoutMs);
74
-
75
- socket.connect(port, host, () => {
76
- clearTimeout(timeoutHandle);
77
- socket.destroy();
78
- resolve(true);
79
- });
68
+ return spool.execSpool('health', 'health', { timeoutMs, sessionId: getSessionId() })
69
+ .then((r) => !!(r && r.ok))
70
+ .catch(() => false);
71
+ }
80
72
 
81
- socket.on('error', () => {
82
- clearTimeout(timeoutHandle);
83
- resolve(false);
84
- });
85
- });
73
+ function computeIndexDigest(cwd = process.cwd()) {
74
+ try {
75
+ let mtimeSum = 0;
76
+ const walkDir = (dir) => {
77
+ try {
78
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
79
+ for (const entry of entries) {
80
+ if (entry.isDirectory()) {
81
+ if (!entry.name.startsWith('.') && entry.name !== 'node_modules') {
82
+ walkDir(path.join(dir, entry.name));
83
+ }
84
+ } else {
85
+ const fullPath = path.join(dir, entry.name);
86
+ const stat = fs.statSync(fullPath);
87
+ mtimeSum += stat.mtimeMs;
88
+ }
89
+ }
90
+ } catch (e) {
91
+ return;
92
+ }
93
+ };
94
+
95
+ walkDir(cwd);
96
+
97
+ let gitHead = '';
98
+ try {
99
+ gitHead = execSync('git rev-parse HEAD', { cwd, encoding: 'utf8' }).trim();
100
+ } catch {
101
+ gitHead = 'unknown';
102
+ }
103
+
104
+ let dirtyStatus = 'clean';
105
+ try {
106
+ const porcelain = execSync('git status --porcelain', { cwd, encoding: 'utf8' }).trim();
107
+ if (porcelain.length > 0) {
108
+ dirtyStatus = 'dirty';
109
+ }
110
+ } catch {
111
+ dirtyStatus = 'unknown';
112
+ }
113
+
114
+ const digestInput = `${mtimeSum}:${gitHead}:${dirtyStatus}`;
115
+ const digest = crypto.createHash('sha256').update(digestInput).digest('hex');
116
+ return `v1:${digest}:files=${mtimeSum}`;
117
+ } catch (e) {
118
+ emitDaemonEvent('digest', 'error', 'Failed to compute digest', { error: e.message });
119
+ return '';
120
+ }
86
121
  }
87
122
 
88
- function writeStatusFile(daemonName, status, details = {}) {
123
+ function writeStatusFile(daemonName, status, sessionId) {
89
124
  try {
90
125
  fs.mkdirSync(GM_STATE_DIR, { recursive: true });
91
126
  const statusFile = path.join(GM_STATE_DIR, `${daemonName}-status.json`);
92
127
  const payload = {
93
128
  daemon: daemonName,
94
129
  status,
95
- sessionId: getSessionId(),
130
+ sessionId,
96
131
  timestamp: new Date().toISOString(),
97
132
  pid: process.pid,
98
- ...details,
99
133
  };
100
134
  fs.writeFileSync(statusFile, JSON.stringify(payload, null, 2));
101
- emitEvent(daemonName, 'info', 'Status written', { file: statusFile });
135
+ emitDaemonEvent(daemonName, 'info', 'Status written', { file: statusFile });
102
136
  } catch (e) {
103
- emitEvent(daemonName, 'warn', 'Failed to write status file', { error: e.message });
137
+ emitDaemonEvent(daemonName, 'warn', 'Failed to write status file', { error: e.message });
104
138
  }
105
139
  }
106
140
 
107
- async function checkState(daemonName) {
141
+ async function ensureRsLearningDaemonRunning() {
142
+ const daemonName = 'rs-learn';
108
143
  const sessionId = getSessionId();
109
144
  const startTime = Date.now();
110
145
 
111
146
  try {
112
- emitEvent(daemonName, 'info', 'checkState initiated', { sessionId });
147
+ emitDaemonEvent(daemonName, 'info', 'Daemon startup check initiated', { sessionId });
113
148
 
114
- const running = isDaemonRunning(daemonName);
115
- if (!running) {
116
- emitEvent(daemonName, 'info', 'Daemon not running', { sessionId });
117
- writeStatusFile(daemonName, 'not_running', { sessionId });
118
- return { ok: true, running: false, durationMs: Date.now() - startTime };
149
+ if (isDaemonRunning(daemonName)) {
150
+ emitDaemonEvent(daemonName, 'info', 'Daemon already running', { sessionId });
151
+ writeStatusFile(daemonName, 'running', sessionId);
152
+ return { ok: true, already_running: true };
119
153
  }
120
154
 
121
- emitEvent(daemonName, 'info', 'Daemon running', { sessionId });
122
- writeStatusFile(daemonName, 'running', { sessionId });
123
- return { ok: true, running: true, durationMs: Date.now() - startTime };
155
+ emitDaemonEvent(daemonName, 'info', 'Spawning daemon', { sessionId });
156
+
157
+ const env = Object.assign({}, process.env, {
158
+ CLAUDE_SESSION_ID: sessionId,
159
+ });
160
+
161
+ const proc = spawn('bun', ['x', 'rs-learn@latest'], {
162
+ detached: true,
163
+ stdio: 'ignore',
164
+ windowsHide: true,
165
+ env,
166
+ });
167
+
168
+ const pid = proc.pid;
169
+ proc.unref();
170
+
171
+ emitDaemonEvent(daemonName, 'info', 'Daemon spawned successfully', { pid, sessionId });
172
+ writeStatusFile(daemonName, 'started', sessionId);
173
+
174
+ return {
175
+ ok: true,
176
+ pid,
177
+ sessionId,
178
+ durationMs: Date.now() - startTime,
179
+ };
124
180
  } catch (e) {
125
- emitEvent(daemonName, 'error', 'checkState failed', {
181
+ emitDaemonEvent(daemonName, 'error', 'Daemon spawn failed', {
126
182
  error: e.message,
127
183
  sessionId,
128
184
  durationMs: Date.now() - startTime,
129
185
  });
130
- return { ok: false, error: e.message, durationMs: Date.now() - startTime };
186
+ writeStatusFile(daemonName, 'error', sessionId);
187
+ throw e;
131
188
  }
132
189
  }
133
190
 
134
- async function spawnDaemon(daemonName, cmd) {
191
+ async function ensureAcptoapiRunning() {
192
+ const sessionId = getSessionId();
193
+ const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
194
+ const statusPath = path.join(projectDir, '.gm', 'acptoapi-status.json');
195
+
196
+ const host = '127.0.0.1';
197
+ const port = 4800;
198
+
199
+ try {
200
+ const reachable = await checkPortReachable(host, port);
201
+
202
+ if (reachable) {
203
+ emitDaemonEvent('acptoapi', 'info', 'Already running', { port, sessionId });
204
+ writeStatusFile('acptoapi', 'running', sessionId);
205
+ return { ok: true, message: 'acptoapi already running' };
206
+ }
207
+
208
+ emitDaemonEvent('acptoapi', 'info', 'Spawning daemon', { port, sessionId });
209
+
210
+ const env = Object.assign({}, process.env, {
211
+ CLAUDE_SESSION_ID: sessionId,
212
+ });
213
+
214
+ try {
215
+ const child = spawn('bun', ['x', 'acptoapi@latest'], {
216
+ detached: true,
217
+ stdio: 'ignore',
218
+ windowsHide: true,
219
+ env,
220
+ });
221
+ child.unref();
222
+ emitDaemonEvent('acptoapi', 'info', 'Daemon spawned', { pid: child.pid, port, sessionId });
223
+ writeStatusFile('acptoapi', 'spawned', sessionId);
224
+ return { ok: true, message: 'acptoapi spawned', pid: child.pid };
225
+ } catch (spawnErr) {
226
+ emitDaemonEvent('acptoapi', 'warn', 'Spawn failed, fallback to SDK', {
227
+ error: spawnErr.message,
228
+ sessionId,
229
+ });
230
+ writeStatusFile('acptoapi', 'spawn_failed', sessionId);
231
+ return { ok: false, message: 'acptoapi spawn failed, fallback to Anthropic SDK', error: spawnErr.message };
232
+ }
233
+ } catch (err) {
234
+ emitDaemonEvent('acptoapi', 'error', 'Check failed', {
235
+ error: err.message,
236
+ sessionId,
237
+ });
238
+ writeStatusFile('acptoapi', 'check_error', sessionId);
239
+ return { ok: false, message: 'acptoapi check failed, fallback to Anthropic SDK', error: err.message };
240
+ }
241
+ }
242
+
243
+ async function ensureRsCodeinsightDaemonRunning() {
244
+ const daemonName = 'rs-codeinsight';
135
245
  const sessionId = getSessionId();
136
246
  const startTime = Date.now();
137
247
 
138
248
  try {
139
- emitEvent(daemonName, 'info', 'spawn initiated', { cmd, sessionId });
249
+ emitDaemonEvent(daemonName, 'info', 'Daemon startup check initiated', { sessionId });
140
250
 
141
251
  if (isDaemonRunning(daemonName)) {
142
- emitEvent(daemonName, 'info', 'Already running, skipping spawn', { sessionId });
143
- writeStatusFile(daemonName, 'running', { sessionId });
144
- return { ok: true, already_running: true, durationMs: Date.now() - startTime };
252
+ emitDaemonEvent(daemonName, 'info', 'Daemon already running', { sessionId });
253
+ writeStatusFile(daemonName, 'running', sessionId);
254
+ return { ok: true, already_running: true };
145
255
  }
146
256
 
147
- emitEvent(daemonName, 'info', 'Spawning daemon', { cmd, sessionId });
257
+ emitDaemonEvent(daemonName, 'info', 'Spawning daemon', { sessionId });
148
258
 
149
259
  const env = Object.assign({}, process.env, {
150
260
  CLAUDE_SESSION_ID: sessionId,
151
261
  });
152
262
 
153
- const proc = spawn('bun', ['x', cmd], {
263
+ const proc = spawn('bun', ['x', 'rs-codeinsight@latest'], {
154
264
  detached: true,
155
265
  stdio: 'ignore',
156
266
  windowsHide: true,
@@ -160,155 +270,166 @@ async function spawnDaemon(daemonName, cmd) {
160
270
  const pid = proc.pid;
161
271
  proc.unref();
162
272
 
163
- emitEvent(daemonName, 'info', 'Daemon spawned', { pid, cmd, sessionId });
164
- writeStatusFile(daemonName, 'spawned', { pid, sessionId });
273
+ emitDaemonEvent(daemonName, 'info', 'Daemon spawned successfully', { pid, sessionId });
274
+ writeStatusFile(daemonName, 'started', sessionId);
165
275
 
166
- return {
167
- ok: true,
168
- pid,
169
- cmd,
170
- sessionId,
171
- durationMs: Date.now() - startTime,
172
- };
276
+ return { ok: true, pid, sessionId, durationMs: Date.now() - startTime };
173
277
  } catch (e) {
174
- emitEvent(daemonName, 'error', 'spawn failed', {
278
+ emitDaemonEvent(daemonName, 'error', 'Daemon spawn failed', {
175
279
  error: e.message,
176
- cmd,
177
280
  sessionId,
178
281
  durationMs: Date.now() - startTime,
179
282
  });
180
- writeStatusFile(daemonName, 'spawn_error', { error: e.message, sessionId });
181
- return { ok: false, error: e.message, durationMs: Date.now() - startTime };
283
+ writeStatusFile(daemonName, 'error', sessionId);
284
+ throw e;
182
285
  }
183
286
  }
184
287
 
185
- async function waitForReady(daemonName, host, port, timeoutMs = 30000) {
288
+ async function ensureRsSearchDaemonRunning() {
289
+ const daemonName = 'rs-search';
186
290
  const sessionId = getSessionId();
187
291
  const startTime = Date.now();
188
292
 
189
293
  try {
190
- emitEvent(daemonName, 'info', 'waitForReady initiated', {
191
- host,
192
- port,
193
- timeoutMs,
294
+ emitDaemonEvent(daemonName, 'info', 'Daemon startup check initiated', { sessionId });
295
+
296
+ if (isDaemonRunning(daemonName)) {
297
+ emitDaemonEvent(daemonName, 'info', 'Daemon already running', { sessionId });
298
+ writeStatusFile(daemonName, 'running', sessionId);
299
+ return { ok: true, already_running: true };
300
+ }
301
+
302
+ emitDaemonEvent(daemonName, 'info', 'Spawning daemon', { sessionId });
303
+
304
+ const env = Object.assign({}, process.env, {
305
+ CLAUDE_SESSION_ID: sessionId,
306
+ });
307
+
308
+ const proc = spawn('bun', ['x', 'rs-search@latest'], {
309
+ detached: true,
310
+ stdio: 'ignore',
311
+ windowsHide: true,
312
+ env,
313
+ });
314
+
315
+ const pid = proc.pid;
316
+ proc.unref();
317
+
318
+ emitDaemonEvent(daemonName, 'info', 'Daemon spawned successfully', { pid, sessionId });
319
+ writeStatusFile(daemonName, 'started', sessionId);
320
+
321
+ return { ok: true, pid, sessionId, durationMs: Date.now() - startTime };
322
+ } catch (e) {
323
+ emitDaemonEvent(daemonName, 'error', 'Daemon spawn failed', {
324
+ error: e.message,
194
325
  sessionId,
326
+ durationMs: Date.now() - startTime,
195
327
  });
328
+ writeStatusFile(daemonName, 'error', sessionId);
329
+ throw e;
330
+ }
331
+ }
332
+
333
+ async function ensureRsCodeinsightReady(sessionId = getSessionId()) {
334
+ const startTime = Date.now();
335
+ const daemonName = 'rs-codeinsight';
336
+ const host = '127.0.0.1';
337
+ const port = 4802;
196
338
 
197
- const deadline = startTime + timeoutMs;
339
+ try {
340
+ emitDaemonEvent(daemonName, 'info', 'Ensuring daemon readiness', { sessionId, host, port });
341
+
342
+ await ensureRsCodeinsightDaemonRunning();
343
+
344
+ const maxWaitMs = 30000;
198
345
  const pollIntervalMs = 500;
346
+ const deadline = Date.now() + maxWaitMs;
199
347
 
200
348
  while (Date.now() < deadline) {
201
349
  const reachable = await checkPortReachable(host, port, 1000);
202
350
  if (reachable) {
203
- emitEvent(daemonName, 'info', 'Ready', {
351
+ emitDaemonEvent(daemonName, 'info', 'Daemon ready', {
204
352
  host,
205
353
  port,
206
354
  elapsedMs: Date.now() - startTime,
207
355
  sessionId,
208
356
  });
209
- writeStatusFile(daemonName, 'ready', { host, port, sessionId });
210
- return { ok: true, host, port, elapsedMs: Date.now() - startTime };
357
+ writeStatusFile(daemonName, 'ready', sessionId);
358
+ return { ok: true, host, port, sessionId, durationMs: Date.now() - startTime };
211
359
  }
212
360
 
213
361
  await new Promise(resolve => setTimeout(resolve, pollIntervalMs));
214
362
  }
215
363
 
216
- emitEvent(daemonName, 'warn', 'Timeout waiting for readiness', {
364
+ emitDaemonEvent(daemonName, 'warn', 'Timeout waiting for readiness', {
217
365
  host,
218
366
  port,
219
- timeoutMs,
367
+ maxWaitMs,
220
368
  sessionId,
221
369
  elapsedMs: Date.now() - startTime,
222
370
  });
223
- writeStatusFile(daemonName, 'timeout', { host, port, timeoutMs, sessionId });
224
- return { ok: false, error: 'Timeout', timeoutMs, elapsedMs: Date.now() - startTime };
371
+ writeStatusFile(daemonName, 'timeout', sessionId);
372
+ return { ok: false, error: 'Timeout waiting for codeinsight daemon', durationMs: Date.now() - startTime };
225
373
  } catch (e) {
226
- emitEvent(daemonName, 'error', 'waitForReady failed', {
374
+ emitDaemonEvent(daemonName, 'error', 'Failed to ensure readiness', {
227
375
  error: e.message,
228
- host,
229
- port,
230
376
  sessionId,
231
- elapsedMs: Date.now() - startTime,
232
- });
233
- return { ok: false, error: e.message, elapsedMs: Date.now() - startTime };
234
- }
235
- }
236
-
237
- async function getSocket(daemonName) {
238
- try {
239
- emitEvent(daemonName, 'info', 'getSocket initiated', { sessionId: getSessionId() });
240
-
241
- const statusFile = path.join(GM_STATE_DIR, `${daemonName}-status.json`);
242
- if (!fs.existsSync(statusFile)) {
243
- emitEvent(daemonName, 'warn', 'No status file found', { statusFile });
244
- return { ok: false, error: 'No status file found' };
245
- }
246
-
247
- const status = JSON.parse(fs.readFileSync(statusFile, 'utf8'));
248
- const socket = `${status.host || '127.0.0.1'}:${status.port || 'unknown'}`;
249
-
250
- emitEvent(daemonName, 'info', 'Socket retrieved', { socket });
251
- return { ok: true, socket, ...status };
252
- } catch (e) {
253
- emitEvent(daemonName, 'error', 'getSocket failed', {
254
- error: e.message,
255
- sessionId: getSessionId(),
377
+ durationMs: Date.now() - startTime,
256
378
  });
257
- return { ok: false, error: e.message };
379
+ writeStatusFile(daemonName, 'error', sessionId);
380
+ return { ok: false, error: e.message, durationMs: Date.now() - startTime };
258
381
  }
259
382
  }
260
383
 
261
- async function shutdown(daemonName) {
262
- const sessionId = getSessionId();
384
+ async function ensureBrowserReady(sessionId = getSessionId()) {
263
385
  const startTime = Date.now();
386
+ const host = '127.0.0.1';
387
+ const port = 5000;
264
388
 
265
389
  try {
266
- emitEvent(daemonName, 'info', 'shutdown initiated', { sessionId });
390
+ emitDaemonEvent('browser', 'info', 'Checking browser readiness', { sessionId, host, port });
267
391
 
268
- const plat = getPlatformKey();
269
- let killed = false;
392
+ const maxWaitMs = 10000;
393
+ const pollIntervalMs = 250;
394
+ const deadline = Date.now() + maxWaitMs;
270
395
 
271
- if (plat === 'win32') {
272
- try {
273
- execSync(`taskkill /F /IM ${daemonName}* /T`, { stdio: 'ignore' });
274
- killed = true;
275
- } catch {
276
- killed = false;
277
- }
278
- } else {
279
- try {
280
- execSync(`pkill -9 -f "${daemonName}"`, { stdio: 'ignore' });
281
- killed = true;
282
- } catch {
283
- killed = false;
396
+ while (Date.now() < deadline) {
397
+ const reachable = await checkPortReachable(host, port, 1000);
398
+ if (reachable) {
399
+ emitDaemonEvent('browser', 'info', 'Browser ready', {
400
+ host,
401
+ port,
402
+ elapsedMs: Date.now() - startTime,
403
+ sessionId,
404
+ });
405
+ return { ok: true, host, port, sessionId, durationMs: Date.now() - startTime };
284
406
  }
407
+
408
+ await new Promise(resolve => setTimeout(resolve, pollIntervalMs));
285
409
  }
286
410
 
287
- emitEvent(daemonName, 'info', 'shutdown completed', {
288
- killed,
411
+ emitDaemonEvent('browser', 'warn', 'Browser not available', {
412
+ host,
413
+ port,
414
+ maxWaitMs,
289
415
  sessionId,
290
- durationMs: Date.now() - startTime,
291
416
  });
292
- writeStatusFile(daemonName, 'shutdown', { killed, sessionId });
293
-
294
- return { ok: true, killed, durationMs: Date.now() - startTime };
417
+ return { ok: false, error: 'Browser API not available at 127.0.0.1:5000', durationMs: Date.now() - startTime };
295
418
  } catch (e) {
296
- emitEvent(daemonName, 'error', 'shutdown failed', {
419
+ emitDaemonEvent('browser', 'error', 'Failed to check browser', {
297
420
  error: e.message,
298
421
  sessionId,
299
- durationMs: Date.now() - startTime,
300
422
  });
301
423
  return { ok: false, error: e.message, durationMs: Date.now() - startTime };
302
424
  }
303
425
  }
304
426
 
305
427
  module.exports = {
306
- checkState,
307
- spawnDaemon,
308
- waitForReady,
309
- getSocket,
310
- shutdown,
311
- emitEvent,
312
- isDaemonRunning,
428
+ ensureAcptoapiRunning,
429
+ ensureRsCodeinsightDaemonRunning,
430
+ ensureRsCodeinsightReady,
431
+ ensureRsSearchDaemonRunning,
432
+ ensureRsLearningDaemonRunning,
433
+ ensureBrowserReady,
313
434
  checkPortReachable,
314
435
  };
package/lib/git.js CHANGED
@@ -1,4 +1,3 @@
1
- const net = require('net');
2
1
  const path = require('path');
3
2
  const fs = require('fs');
4
3
  const os = require('os');