@grainulation/grainulation 1.0.0 → 1.0.1

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