@grainulation/grainulation 1.0.1 → 1.1.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/lib/router.js CHANGED
@@ -1,7 +1,7 @@
1
- const { execSync, spawn } = require("node:child_process");
2
- const { existsSync } = require("node:fs");
3
- const path = require("node:path");
4
- const { getByName, getInstallable } = require("./ecosystem");
1
+ const { execFileSync, spawn } = require('node:child_process');
2
+ const { existsSync } = require('node:fs');
3
+ const path = require('node:path');
4
+ const { getByName, getInstallable } = require('./ecosystem');
5
5
 
6
6
  /**
7
7
  * Intent-based router.
@@ -12,10 +12,10 @@ const { getByName, getInstallable } = require("./ecosystem");
12
12
  const DELEGATE_COMMANDS = new Set(getInstallable().map((t) => t.name));
13
13
 
14
14
  function overviewData() {
15
- const { TOOLS } = require("./ecosystem");
15
+ const { TOOLS } = require('./ecosystem');
16
16
  return {
17
- name: "grainulation",
18
- description: "Structured research for decisions that satisfice.",
17
+ name: 'grainulation',
18
+ description: 'Structured research for decisions that satisfice.',
19
19
  tools: TOOLS.map((tool) => ({
20
20
  name: tool.name,
21
21
  package: tool.package,
@@ -23,62 +23,52 @@ function overviewData() {
23
23
  category: tool.category,
24
24
  installed: isInstalled(tool.package),
25
25
  })),
26
- commands: ["up", "down", "ps", "init", "status", "doctor", "<tool>"],
26
+ commands: ['up', 'down', 'ps', 'init', 'status', 'doctor', '<tool>'],
27
27
  };
28
28
  }
29
29
 
30
30
  function overview() {
31
- const { TOOLS } = require("./ecosystem");
31
+ const { TOOLS } = require('./ecosystem');
32
32
  const lines = [
33
- "",
34
- " \x1b[1;33mgrainulation\x1b[0m",
35
- " Structured research for decisions that satisfice.",
36
- "",
37
- " \x1b[2mEcosystem:\x1b[0m",
38
- "",
33
+ '',
34
+ ' \x1b[1;33mgrainulation\x1b[0m',
35
+ ' Structured research for decisions that satisfice.',
36
+ '',
37
+ ' \x1b[2mEcosystem:\x1b[0m',
38
+ '',
39
39
  ];
40
40
 
41
41
  for (const tool of TOOLS) {
42
42
  const installed = isInstalled(tool.package);
43
- const marker = installed ? "\x1b[32m+\x1b[0m" : "\x1b[2m-\x1b[0m";
44
- const port = tool.port ? `:${tool.port}` : "";
45
- lines.push(
46
- ` ${marker} \x1b[1m${tool.name.padEnd(12)}\x1b[0m ${tool.role.padEnd(28)} \x1b[2m${port}\x1b[0m`,
47
- );
48
- }
49
-
50
- lines.push("");
51
- lines.push(" \x1b[2mProcess management:\x1b[0m");
52
- lines.push(
53
- " grainulation up [tools] Start tool servers (default: farmer + wheat)",
54
- );
55
- lines.push(" grainulation down Stop all running tools");
56
- lines.push(" grainulation ps Show running tools, ports, health");
57
- lines.push("");
58
- lines.push(" \x1b[2mWorkflow:\x1b[0m");
59
- lines.push(
60
- " grainulation init Detect context and start a research sprint",
61
- );
62
- lines.push(
63
- " grainulation status Cross-tool status: sprints, claims, services",
64
- );
65
- lines.push(
66
- " grainulation doctor Check ecosystem health (install detection)",
67
- );
68
- lines.push(" grainulation <tool> Delegate to a grainulation tool");
69
- lines.push("");
70
- lines.push(" \x1b[2mStart here:\x1b[0m");
71
- lines.push(" grainulation up && grainulation init");
72
- lines.push("");
73
-
74
- return lines.join("\n");
43
+ const marker = installed ? '\x1b[32m+\x1b[0m' : '\x1b[2m-\x1b[0m';
44
+ const port = tool.port ? `:${tool.port}` : '';
45
+ lines.push(` ${marker} \x1b[1m${tool.name.padEnd(12)}\x1b[0m ${tool.role.padEnd(28)} \x1b[2m${port}\x1b[0m`);
46
+ }
47
+
48
+ lines.push('');
49
+ lines.push(' \x1b[2mProcess management:\x1b[0m');
50
+ lines.push(' grainulation up [tools] Start tool servers (default: farmer + wheat)');
51
+ lines.push(' grainulation down Stop all running tools');
52
+ lines.push(' grainulation ps Show running tools, ports, health');
53
+ lines.push('');
54
+ lines.push(' \x1b[2mWorkflow:\x1b[0m');
55
+ lines.push(' grainulation init Detect context and start a research sprint');
56
+ lines.push(' grainulation status Cross-tool status: sprints, claims, services');
57
+ lines.push(' grainulation doctor Check ecosystem health (install detection)');
58
+ lines.push(' grainulation <tool> Delegate to a grainulation tool');
59
+ lines.push('');
60
+ lines.push(' \x1b[2mStart here:\x1b[0m');
61
+ lines.push(' grainulation up && grainulation init');
62
+ lines.push('');
63
+
64
+ return lines.join('\n');
75
65
  }
76
66
 
77
67
  function isInstalled(packageName) {
78
68
  try {
79
- if (packageName === "grainulation") return true;
80
- execSync(`npm list -g ${packageName} --depth=0 2>/dev/null`, {
81
- stdio: "pipe",
69
+ if (packageName === 'grainulation') return true;
70
+ execFileSync('npm', ['list', '-g', packageName, '--depth=0'], {
71
+ stdio: 'pipe',
82
72
  });
83
73
  return true;
84
74
  } catch {
@@ -96,21 +86,17 @@ function isInstalled(packageName) {
96
86
  * Returns the bin path if found, null otherwise.
97
87
  */
98
88
  function findSourceBin(tool) {
99
- const shortName = tool.package.replace(/^@[^/]+\//, "");
100
- const candidates = [
101
- path.join(__dirname, "..", "..", shortName),
102
- path.join(process.cwd(), "..", shortName),
103
- ];
89
+ const shortName = tool.package.replace(/^@[^/]+\//, '');
90
+ const candidates = [path.join(__dirname, '..', '..', shortName), path.join(process.cwd(), '..', shortName)];
104
91
  for (const dir of candidates) {
105
92
  try {
106
- const pkgPath = path.join(dir, "package.json");
93
+ const pkgPath = path.join(dir, 'package.json');
107
94
  if (!existsSync(pkgPath)) continue;
108
- const pkg = JSON.parse(require("node:fs").readFileSync(pkgPath, "utf-8"));
95
+ const pkg = JSON.parse(require('node:fs').readFileSync(pkgPath, 'utf-8'));
109
96
  if (pkg.name !== tool.package) continue;
110
97
  // Find the bin entry
111
98
  if (pkg.bin) {
112
- const binFile =
113
- typeof pkg.bin === "string" ? pkg.bin : Object.values(pkg.bin)[0];
99
+ const binFile = typeof pkg.bin === 'string' ? pkg.bin : Object.values(pkg.bin)[0];
114
100
  if (binFile) {
115
101
  const binPath = path.resolve(dir, binFile);
116
102
  if (existsSync(binPath)) return binPath;
@@ -138,23 +124,21 @@ function delegate(toolName, args) {
138
124
  cmd = process.execPath;
139
125
  cmdArgs = [sourceBin, ...args];
140
126
  } else {
141
- cmd = "npx";
127
+ cmd = 'npx';
142
128
  cmdArgs = [tool.package, ...args];
143
129
  }
144
130
 
145
131
  const child = spawn(cmd, cmdArgs, {
146
- stdio: "inherit",
147
- shell: cmd === "npx",
132
+ stdio: 'inherit',
133
+ shell: false,
148
134
  });
149
135
 
150
- child.on("close", (code) => {
136
+ child.on('close', (code) => {
151
137
  process.exit(code ?? 0);
152
138
  });
153
139
 
154
- child.on("error", (err) => {
155
- console.error(
156
- `\x1b[31mgrainulation: failed to run ${tool.package}: ${err.message}\x1b[0m`,
157
- );
140
+ child.on('error', (err) => {
141
+ console.error(`\x1b[31mgrainulation: failed to run ${tool.package}: ${err.message}\x1b[0m`);
158
142
  console.error(`Try: npm install -g ${tool.package}`);
159
143
  process.exit(1);
160
144
  });
@@ -165,19 +149,19 @@ function delegate(toolName, args) {
165
149
  * Checks for existing claims.json, package.json, git repo, etc.
166
150
  */
167
151
  function init(args, opts) {
168
- const json = opts && opts.json;
152
+ const json = opts?.json;
169
153
  const cwd = process.cwd();
170
- const hasClaims = existsSync(path.join(cwd, "claims.json"));
171
- const hasCompilation = existsSync(path.join(cwd, "compilation.json"));
172
- const hasGit = existsSync(path.join(cwd, ".git"));
173
- const hasPkg = existsSync(path.join(cwd, "package.json"));
154
+ const hasClaims = existsSync(path.join(cwd, 'claims.json'));
155
+ const hasCompilation = existsSync(path.join(cwd, 'compilation.json'));
156
+ const hasGit = existsSync(path.join(cwd, '.git'));
157
+ const hasPkg = existsSync(path.join(cwd, 'package.json'));
174
158
 
175
159
  if (json) {
176
160
  // Pass --json through to wheat init
177
161
  if (hasClaims && hasCompilation) {
178
162
  console.log(
179
163
  JSON.stringify({
180
- status: "exists",
164
+ status: 'exists',
181
165
  directory: cwd,
182
166
  hasClaims,
183
167
  hasCompilation,
@@ -186,71 +170,69 @@ function init(args, opts) {
186
170
  return;
187
171
  }
188
172
  if (hasClaims) {
189
- delegate("wheat", ["compile", "--json", ...args]);
173
+ delegate('wheat', ['compile', '--json', ...args]);
190
174
  return;
191
175
  }
192
- const { detect } = require("./doctor");
193
- const wheatInfo = detect("@grainulation/wheat");
176
+ const { detect } = require('./doctor');
177
+ const wheatInfo = detect('@grainulation/wheat');
194
178
  if (!wheatInfo) {
195
179
  console.log(
196
180
  JSON.stringify({
197
- status: "missing",
198
- tool: "wheat",
199
- install: "npm install -g @grainulation/wheat",
181
+ status: 'missing',
182
+ tool: 'wheat',
183
+ install: 'npm install -g @grainulation/wheat',
200
184
  }),
201
185
  );
202
186
  return;
203
187
  }
204
- delegate("wheat", ["init", "--json", ...args]);
188
+ delegate('wheat', ['init', '--json', ...args]);
205
189
  return;
206
190
  }
207
191
 
208
- console.log("");
209
- console.log(" \x1b[1;33mgrainulation init\x1b[0m");
210
- console.log("");
192
+ console.log('');
193
+ console.log(' \x1b[1;33mgrainulation init\x1b[0m');
194
+ console.log('');
211
195
 
212
196
  // Context detection
213
- console.log(" \x1b[2mDetected context:\x1b[0m");
197
+ console.log(' \x1b[2mDetected context:\x1b[0m');
214
198
  console.log(` Directory ${cwd}`);
215
- console.log(` Git repo ${hasGit ? "yes" : "no"}`);
216
- console.log(` package.json ${hasPkg ? "yes" : "no"}`);
217
- console.log(` claims.json ${hasClaims ? "yes (existing sprint)" : "no"}`);
218
- console.log("");
199
+ console.log(` Git repo ${hasGit ? 'yes' : 'no'}`);
200
+ console.log(` package.json ${hasPkg ? 'yes' : 'no'}`);
201
+ console.log(` claims.json ${hasClaims ? 'yes (existing sprint)' : 'no'}`);
202
+ console.log('');
219
203
 
220
204
  if (hasClaims && hasCompilation) {
221
- console.log(" An active sprint already exists in this directory.");
222
- console.log(" To continue, use: grainulation wheat compile");
223
- console.log(" To start fresh, remove claims.json first.");
224
- console.log("");
205
+ console.log(' An active sprint already exists in this directory.');
206
+ console.log(' To continue, use: grainulation wheat compile');
207
+ console.log(' To start fresh, remove claims.json first.');
208
+ console.log('');
225
209
  return;
226
210
  }
227
211
 
228
212
  if (hasClaims) {
229
- console.log(
230
- " Found claims.json but no compilation. Routing to wheat compile.",
231
- );
232
- console.log("");
233
- delegate("wheat", ["compile", ...args]);
213
+ console.log(' Found claims.json but no compilation. Routing to wheat compile.');
214
+ console.log('');
215
+ delegate('wheat', ['compile', ...args]);
234
216
  return;
235
217
  }
236
218
 
237
219
  // Check if wheat is available before delegating
238
- const { detect } = require("./doctor");
239
- const wheatInfo = detect("@grainulation/wheat");
220
+ const { detect } = require('./doctor');
221
+ const wheatInfo = detect('@grainulation/wheat');
240
222
  if (!wheatInfo) {
241
- console.log(" wheat is not installed. Install it first:");
242
- console.log(" npm install -g @grainulation/wheat");
243
- console.log("");
244
- console.log(" Or run interactively:");
245
- console.log(" grainulation setup");
246
- console.log("");
223
+ console.log(' wheat is not installed. Install it first:');
224
+ console.log(' npm install -g @grainulation/wheat');
225
+ console.log('');
226
+ console.log(' Or run interactively:');
227
+ console.log(' grainulation setup');
228
+ console.log('');
247
229
  return;
248
230
  }
249
231
 
250
232
  // Route to wheat init
251
- console.log(" Routing to wheat init...");
252
- console.log("");
253
- delegate("wheat", ["init", ...args]);
233
+ console.log(' Routing to wheat init...');
234
+ console.log('');
235
+ delegate('wheat', ['init', ...args]);
254
236
  }
255
237
 
256
238
  /**
@@ -258,7 +240,7 @@ function init(args, opts) {
258
240
  */
259
241
  function statusData() {
260
242
  const cwd = process.cwd();
261
- const { detect } = require("./doctor");
243
+ const { detect } = require('./doctor');
262
244
  const installable = getInstallable();
263
245
 
264
246
  const tools = [];
@@ -273,36 +255,33 @@ function statusData() {
273
255
  });
274
256
  }
275
257
 
276
- const hasClaims = existsSync(path.join(cwd, "claims.json"));
277
- const hasCompilation = existsSync(path.join(cwd, "compilation.json"));
258
+ const hasClaims = existsSync(path.join(cwd, 'claims.json'));
259
+ const hasCompilation = existsSync(path.join(cwd, 'compilation.json'));
278
260
  let sprint = null;
279
261
 
280
262
  if (hasClaims) {
281
263
  try {
282
- const claimsRaw = require("node:fs").readFileSync(
283
- path.join(cwd, "claims.json"),
284
- "utf-8",
285
- );
264
+ const claimsRaw = require('node:fs').readFileSync(path.join(cwd, 'claims.json'), 'utf-8');
286
265
  const claims = JSON.parse(claimsRaw);
287
266
  const claimList = Array.isArray(claims) ? claims : claims.claims || [];
288
267
  const byType = {};
289
268
  for (const c of claimList) {
290
- const t = c.type || "unknown";
269
+ const t = c.type || 'unknown';
291
270
  byType[t] = (byType[t] || 0) + 1;
292
271
  }
293
272
  sprint = { claims: claimList.length, byType, compiled: hasCompilation };
294
273
  } catch {
295
- sprint = { error: "claims.json found but could not be parsed" };
274
+ sprint = { error: 'claims.json found but could not be parsed' };
296
275
  }
297
276
  }
298
277
 
299
278
  let farmerPidValue = null;
300
- const farmerPidPath = path.join(cwd, "dashboard", ".farmer.pid");
301
- const farmerPidAlt = path.join(cwd, ".farmer.pid");
279
+ const farmerPidPath = path.join(cwd, 'dashboard', '.farmer.pid');
280
+ const farmerPidAlt = path.join(cwd, '.farmer.pid');
302
281
  for (const pidFile of [farmerPidPath, farmerPidAlt]) {
303
282
  if (existsSync(pidFile)) {
304
283
  try {
305
- const pid = require("node:fs").readFileSync(pidFile, "utf-8").trim();
284
+ const pid = require('node:fs').readFileSync(pidFile, 'utf-8').trim();
306
285
  process.kill(Number(pid), 0);
307
286
  farmerPidValue = Number(pid);
308
287
  } catch {
@@ -321,94 +300,89 @@ function statusData() {
321
300
  }
322
301
 
323
302
  function status(opts) {
324
- if (opts && opts.json) {
303
+ if (opts?.json) {
325
304
  console.log(JSON.stringify(statusData()));
326
305
  return;
327
306
  }
328
307
 
329
308
  const cwd = process.cwd();
330
- const { TOOLS } = require("./ecosystem");
331
- const { detect } = require("./doctor");
309
+ const { TOOLS } = require('./ecosystem');
310
+ const { detect } = require('./doctor');
332
311
 
333
- console.log("");
334
- console.log(" \x1b[1;33mgrainulation status\x1b[0m");
335
- console.log("");
312
+ console.log('');
313
+ console.log(' \x1b[1;33mgrainulation status\x1b[0m');
314
+ console.log('');
336
315
 
337
316
  // Installed tools
338
- console.log(" \x1b[2mInstalled tools:\x1b[0m");
317
+ console.log(' \x1b[2mInstalled tools:\x1b[0m');
339
318
  const installable = getInstallable();
340
319
  let installedCount = 0;
341
320
  for (const tool of installable) {
342
321
  const result = detect(tool.package);
343
322
  if (result) {
344
323
  installedCount++;
345
- console.log(
346
- ` \x1b[32m+\x1b[0m ${tool.name.padEnd(12)} v${result.version} \x1b[2m(${result.method})\x1b[0m`,
347
- );
324
+ console.log(` \x1b[32m+\x1b[0m ${tool.name.padEnd(12)} v${result.version} \x1b[2m(${result.method})\x1b[0m`);
348
325
  }
349
326
  }
350
327
  if (installedCount === 0) {
351
- console.log(" \x1b[2m(none)\x1b[0m");
328
+ console.log(' \x1b[2m(none)\x1b[0m');
352
329
  }
353
- console.log("");
330
+ console.log('');
354
331
 
355
332
  // Active sprint detection
356
- console.log(" \x1b[2mCurrent directory:\x1b[0m");
333
+ console.log(' \x1b[2mCurrent directory:\x1b[0m');
357
334
  console.log(` ${cwd}`);
358
- console.log("");
335
+ console.log('');
359
336
 
360
- const hasClaims = existsSync(path.join(cwd, "claims.json"));
361
- const hasCompilation = existsSync(path.join(cwd, "compilation.json"));
337
+ const hasClaims = existsSync(path.join(cwd, 'claims.json'));
338
+ const hasCompilation = existsSync(path.join(cwd, 'compilation.json'));
362
339
 
363
340
  if (hasClaims) {
364
341
  try {
365
- const claimsRaw = require("node:fs").readFileSync(
366
- path.join(cwd, "claims.json"),
367
- "utf-8",
368
- );
342
+ const claimsRaw = require('node:fs').readFileSync(path.join(cwd, 'claims.json'), 'utf-8');
369
343
  const claims = JSON.parse(claimsRaw);
370
344
  const claimList = Array.isArray(claims) ? claims : claims.claims || [];
371
345
  const total = claimList.length;
372
346
  const byType = {};
373
347
  for (const c of claimList) {
374
- const t = c.type || "unknown";
348
+ const t = c.type || 'unknown';
375
349
  byType[t] = (byType[t] || 0) + 1;
376
350
  }
377
- console.log(" \x1b[2mActive sprint:\x1b[0m");
351
+ console.log(' \x1b[2mActive sprint:\x1b[0m');
378
352
  console.log(` Claims ${total}`);
379
353
  const typeStr = Object.entries(byType)
380
354
  .map(([k, v]) => `${k}: ${v}`)
381
- .join(", ");
355
+ .join(', ');
382
356
  if (typeStr) {
383
357
  console.log(` Breakdown ${typeStr}`);
384
358
  }
385
359
  if (hasCompilation) {
386
- console.log(" Compiled yes");
360
+ console.log(' Compiled yes');
387
361
  } else {
388
- console.log(" Compiled no (run: grainulation wheat compile)");
362
+ console.log(' Compiled no (run: grainulation wheat compile)');
389
363
  }
390
364
  } catch {
391
- console.log(" \x1b[2mActive sprint:\x1b[0m");
392
- console.log(" claims.json found but could not be parsed");
365
+ console.log(' \x1b[2mActive sprint:\x1b[0m');
366
+ console.log(' claims.json found but could not be parsed');
393
367
  }
394
368
  } else {
395
- console.log(" \x1b[2mNo active sprint in this directory.\x1b[0m");
396
- console.log(" Start one with: grainulation init");
369
+ console.log(' \x1b[2mNo active sprint in this directory.\x1b[0m');
370
+ console.log(' Start one with: grainulation init');
397
371
  }
398
372
 
399
373
  // Check for running farmer (look for .farmer.pid)
400
- const farmerPid = path.join(cwd, "dashboard", ".farmer.pid");
401
- const farmerPidAlt = path.join(cwd, ".farmer.pid");
374
+ const farmerPid = path.join(cwd, 'dashboard', '.farmer.pid');
375
+ const farmerPidAlt = path.join(cwd, '.farmer.pid');
402
376
  let farmerRunning = false;
403
377
  for (const pidFile of [farmerPid, farmerPidAlt]) {
404
378
  if (existsSync(pidFile)) {
405
379
  try {
406
- const pid = require("node:fs").readFileSync(pidFile, "utf-8").trim();
380
+ const pid = require('node:fs').readFileSync(pidFile, 'utf-8').trim();
407
381
  // Check if process is still running
408
382
  process.kill(Number(pid), 0);
409
383
  farmerRunning = true;
410
- console.log("");
411
- console.log(" \x1b[2mRunning services:\x1b[0m");
384
+ console.log('');
385
+ console.log(' \x1b[2mRunning services:\x1b[0m');
412
386
  console.log(` farmer pid ${pid}`);
413
387
  } catch {
414
388
  // PID file exists but process is not running
@@ -418,17 +392,17 @@ function status(opts) {
418
392
  }
419
393
 
420
394
  if (!farmerRunning) {
421
- console.log("");
422
- console.log(" \x1b[2mNo running services detected.\x1b[0m");
395
+ console.log('');
396
+ console.log(' \x1b[2mNo running services detected.\x1b[0m');
423
397
  }
424
398
 
425
- console.log("");
399
+ console.log('');
426
400
  }
427
401
 
428
402
  function route(args, opts) {
429
403
  const command = args[0];
430
404
  const rest = args.slice(1);
431
- const json = opts && opts.json;
405
+ const json = opts?.json;
432
406
 
433
407
  // No args — show overview
434
408
  if (!command) {
@@ -441,37 +415,37 @@ function route(args, opts) {
441
415
  }
442
416
 
443
417
  // Built-in commands
444
- if (command === "doctor") {
445
- require("./doctor").run({ json });
418
+ if (command === 'doctor') {
419
+ require('./doctor').run({ json });
446
420
  return;
447
421
  }
448
- if (command === "setup") {
449
- require("./setup").run();
422
+ if (command === 'setup') {
423
+ require('./setup').run();
450
424
  return;
451
425
  }
452
- if (command === "init") {
426
+ if (command === 'init') {
453
427
  init(rest, { json });
454
428
  return;
455
429
  }
456
- if (command === "status") {
430
+ if (command === 'status') {
457
431
  status({ json });
458
432
  return;
459
433
  }
460
434
 
461
435
  // Process management
462
- if (command === "up") {
436
+ if (command === 'up') {
463
437
  pmUp(rest, { json });
464
438
  return;
465
439
  }
466
- if (command === "down") {
440
+ if (command === 'down') {
467
441
  pmDown(rest, { json });
468
442
  return;
469
443
  }
470
- if (command === "ps") {
444
+ if (command === 'ps') {
471
445
  pmPs({ json });
472
446
  return;
473
447
  }
474
- if (command === "help" || command === "--help" || command === "-h") {
448
+ if (command === 'help' || command === '--help' || command === '-h') {
475
449
  if (json) {
476
450
  console.log(JSON.stringify(overviewData()));
477
451
  } else {
@@ -479,8 +453,8 @@ function route(args, opts) {
479
453
  }
480
454
  return;
481
455
  }
482
- if (command === "--version" || command === "-v") {
483
- const pkg = require("../package.json");
456
+ if (command === '--version' || command === '-v') {
457
+ const pkg = require('../package.json');
484
458
  if (json) {
485
459
  console.log(JSON.stringify({ version: pkg.version }));
486
460
  } else {
@@ -491,7 +465,7 @@ function route(args, opts) {
491
465
 
492
466
  // Delegate to a tool — pass --json through
493
467
  if (DELEGATE_COMMANDS.has(command)) {
494
- const delegateArgs = json ? ["--json", ...rest] : rest;
468
+ const delegateArgs = json ? ['--json', ...rest] : rest;
495
469
  delegate(command, delegateArgs);
496
470
  return;
497
471
  }
@@ -509,123 +483,113 @@ function route(args, opts) {
509
483
  // --- Process management commands ---
510
484
 
511
485
  function pmUp(args, opts) {
512
- const pm = require("./pm");
513
- const toolNames = args.filter((a) => !a.startsWith("-"));
486
+ const pm = require('./pm');
487
+ const toolNames = args.filter((a) => !a.startsWith('-'));
514
488
  const results = pm.up(toolNames.length > 0 ? toolNames : undefined);
515
489
 
516
- if (opts && opts.json) {
490
+ if (opts?.json) {
517
491
  console.log(JSON.stringify(results));
518
492
  return;
519
493
  }
520
494
 
521
- console.log("");
522
- console.log(" \x1b[1;33mgrainulation up\x1b[0m");
523
- console.log("");
495
+ console.log('');
496
+ console.log(' \x1b[1;33mgrainulation up\x1b[0m');
497
+ console.log('');
524
498
 
525
499
  for (const r of results) {
526
500
  if (r.error) {
527
501
  console.log(` \x1b[31mx\x1b[0m ${r.name.padEnd(12)} ${r.error}`);
528
502
  } else if (r.alreadyRunning) {
529
- console.log(
530
- ` \x1b[33m~\x1b[0m ${r.name.padEnd(12)} already running (pid ${r.pid}, port ${r.port})`,
531
- );
503
+ console.log(` \x1b[33m~\x1b[0m ${r.name.padEnd(12)} already running (pid ${r.pid}, port ${r.port})`);
532
504
  } else {
533
- console.log(
534
- ` \x1b[32m+\x1b[0m ${r.name.padEnd(12)} started (pid ${r.pid}, port ${r.port})`,
535
- );
505
+ console.log(` \x1b[32m+\x1b[0m ${r.name.padEnd(12)} started (pid ${r.pid}, port ${r.port})`);
536
506
  }
537
507
  }
538
508
 
539
- console.log("");
509
+ console.log('');
540
510
 
541
511
  // Wait a moment then probe health
542
512
  setTimeout(async () => {
543
513
  const statuses = await pm.ps();
544
514
  const running = statuses.filter((s) => s.alive);
545
515
  if (running.length > 0) {
546
- console.log(" \x1b[2mHealth check:\x1b[0m");
516
+ console.log(' \x1b[2mHealth check:\x1b[0m');
547
517
  for (const s of running) {
548
- console.log(
549
- ` \x1b[32m+\x1b[0m ${s.name.padEnd(12)} :${s.port} (${s.latencyMs}ms)`,
550
- );
518
+ console.log(` \x1b[32m+\x1b[0m ${s.name.padEnd(12)} :${s.port} (${s.latencyMs}ms)`);
551
519
  }
552
- console.log("");
520
+ console.log('');
553
521
  }
554
522
  }, 2000);
555
523
  }
556
524
 
557
525
  function pmDown(args, opts) {
558
- const pm = require("./pm");
559
- const toolNames = args.filter((a) => !a.startsWith("-"));
526
+ const pm = require('./pm');
527
+ const toolNames = args.filter((a) => !a.startsWith('-'));
560
528
  const results = pm.down(toolNames.length > 0 ? toolNames : undefined);
561
529
 
562
- if (opts && opts.json) {
530
+ if (opts?.json) {
563
531
  console.log(JSON.stringify(results));
564
532
  return;
565
533
  }
566
534
 
567
- console.log("");
568
- console.log(" \x1b[1;33mgrainulation down\x1b[0m");
569
- console.log("");
535
+ console.log('');
536
+ console.log(' \x1b[1;33mgrainulation down\x1b[0m');
537
+ console.log('');
570
538
 
571
539
  let stoppedAny = false;
572
540
  for (const r of results) {
573
541
  if (r.stopped) {
574
542
  stoppedAny = true;
575
- console.log(
576
- ` \x1b[31m-\x1b[0m ${r.name.padEnd(12)} stopped (pid ${r.pid})`,
577
- );
543
+ console.log(` \x1b[31m-\x1b[0m ${r.name.padEnd(12)} stopped (pid ${r.pid})`);
578
544
  }
579
545
  }
580
546
 
581
547
  if (!stoppedAny) {
582
- console.log(" \x1b[2mNo running tools to stop.\x1b[0m");
548
+ console.log(' \x1b[2mNo running tools to stop.\x1b[0m');
583
549
  }
584
550
 
585
- console.log("");
551
+ console.log('');
586
552
  }
587
553
 
588
554
  async function pmPs(opts) {
589
- const pm = require("./pm");
555
+ const pm = require('./pm');
590
556
  const statuses = await pm.ps();
591
557
 
592
- if (opts && opts.json) {
558
+ if (opts?.json) {
593
559
  console.log(JSON.stringify(statuses));
594
560
  return;
595
561
  }
596
562
 
597
- console.log("");
598
- console.log(" \x1b[1;33mgrainulation ps\x1b[0m");
599
- console.log("");
563
+ console.log('');
564
+ console.log(' \x1b[1;33mgrainulation ps\x1b[0m');
565
+ console.log('');
600
566
 
601
567
  const running = statuses.filter((s) => s.alive);
602
568
  const stopped = statuses.filter((s) => !s.alive);
603
569
 
604
570
  if (running.length > 0) {
605
- console.log(" \x1b[2mRunning:\x1b[0m");
571
+ console.log(' \x1b[2mRunning:\x1b[0m');
606
572
  for (const s of running) {
607
- const pidStr = s.pid ? `pid ${s.pid}` : "unknown pid";
608
- const latency = s.latencyMs ? `${s.latencyMs}ms` : "";
573
+ const pidStr = s.pid ? `pid ${s.pid}` : 'unknown pid';
574
+ const latency = s.latencyMs ? `${s.latencyMs}ms` : '';
609
575
  console.log(
610
576
  ` \x1b[32m+\x1b[0m ${s.name.padEnd(12)} :${String(s.port).padEnd(6)} ${pidStr.padEnd(14)} ${latency}`,
611
577
  );
612
578
  }
613
- console.log("");
579
+ console.log('');
614
580
  }
615
581
 
616
582
  if (stopped.length > 0) {
617
- console.log(" \x1b[2mStopped:\x1b[0m");
583
+ console.log(' \x1b[2mStopped:\x1b[0m');
618
584
  for (const s of stopped) {
619
585
  console.log(` \x1b[2m- ${s.name.padEnd(12)} :${s.port}\x1b[0m`);
620
586
  }
621
- console.log("");
587
+ console.log('');
622
588
  }
623
589
 
624
590
  if (running.length === 0) {
625
- console.log(
626
- " \x1b[2mNo tools running. Start with: grainulation up\x1b[0m",
627
- );
628
- console.log("");
591
+ console.log(' \x1b[2mNo tools running. Start with: grainulation up\x1b[0m');
592
+ console.log('');
629
593
  }
630
594
  }
631
595