@grainulation/grainulation 1.0.1 → 1.1.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/README.md +1 -1
- package/bin/grainulation.js +13 -14
- package/lib/doctor.js +67 -105
- package/lib/ecosystem.js +57 -65
- package/lib/pm.js +44 -69
- package/lib/router.js +179 -215
- package/lib/server.mjs +33 -38
- package/lib/setup.js +42 -51
- package/package.json +2 -4
- package/public/index.html +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
|
-
<a href="https://www.npmjs.com/package/@grainulation/grainulation"><img src="https://img.shields.io/npm/v/@grainulation/grainulation" alt="npm version"></a>
|
|
6
|
+
<a href="https://www.npmjs.com/package/@grainulation/grainulation"><img src="https://img.shields.io/npm/v/@grainulation/grainulation?label=%40grainulation%2Fgrainulation" alt="npm version"></a>
|
|
7
7
|
<a href="https://www.npmjs.com/package/@grainulation/grainulation"><img src="https://img.shields.io/npm/dm/@grainulation/grainulation" alt="npm downloads"></a>
|
|
8
8
|
<a href="https://github.com/grainulation/grainulation/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="license"></a>
|
|
9
9
|
<a href="https://nodejs.org"><img src="https://img.shields.io/node/v/@grainulation/grainulation" alt="node"></a>
|
package/bin/grainulation.js
CHANGED
|
@@ -14,38 +14,37 @@
|
|
|
14
14
|
* grainulation <tool> [args] Delegate to a grainulation tool
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
const verbose =
|
|
18
|
-
|
|
19
|
-
const jsonMode = process.argv.includes("--json");
|
|
17
|
+
const verbose = process.argv.includes('--verbose') || process.argv.includes('-v');
|
|
18
|
+
const jsonMode = process.argv.includes('--json');
|
|
20
19
|
function vlog(...a) {
|
|
21
20
|
if (!verbose) return;
|
|
22
21
|
const ts = new Date().toISOString();
|
|
23
|
-
process.stderr.write(`[${ts}] grainulation: ${a.join(
|
|
22
|
+
process.stderr.write(`[${ts}] grainulation: ${a.join(' ')}\n`);
|
|
24
23
|
}
|
|
25
24
|
|
|
26
25
|
const command = process.argv[2];
|
|
27
|
-
vlog(
|
|
26
|
+
vlog('startup', `command=${command || '(none)'}`, `cwd=${process.cwd()}`);
|
|
28
27
|
|
|
29
28
|
// Serve command — start the HTTP server (ESM module)
|
|
30
|
-
if (command ===
|
|
31
|
-
const path = require(
|
|
32
|
-
const { spawn } = require(
|
|
33
|
-
const serverPath = path.join(__dirname,
|
|
29
|
+
if (command === 'serve') {
|
|
30
|
+
const path = require('node:path');
|
|
31
|
+
const { spawn } = require('node:child_process');
|
|
32
|
+
const serverPath = path.join(__dirname, '..', 'lib', 'server.mjs');
|
|
34
33
|
|
|
35
34
|
// Forward remaining args to the server
|
|
36
35
|
const serverArgs = process.argv.slice(3);
|
|
37
36
|
const child = spawn(process.execPath, [serverPath, ...serverArgs], {
|
|
38
|
-
stdio:
|
|
37
|
+
stdio: 'inherit',
|
|
39
38
|
});
|
|
40
39
|
|
|
41
|
-
child.on(
|
|
42
|
-
child.on(
|
|
40
|
+
child.on('close', (code) => process.exit(code ?? 0));
|
|
41
|
+
child.on('error', (err) => {
|
|
43
42
|
console.error(`grainulation: failed to start server: ${err.message}`);
|
|
44
43
|
process.exit(1);
|
|
45
44
|
});
|
|
46
45
|
} else {
|
|
47
|
-
const { route } = require(
|
|
46
|
+
const { route } = require('../lib/router');
|
|
48
47
|
// Strip --json from args before routing (it's handled as a mode flag)
|
|
49
|
-
const args = process.argv.slice(2).filter((a) => a !==
|
|
48
|
+
const args = process.argv.slice(2).filter((a) => a !== '--json');
|
|
50
49
|
route(args, { json: jsonMode });
|
|
51
50
|
}
|
package/lib/doctor.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
const {
|
|
2
|
-
const { existsSync } = require(
|
|
3
|
-
const path = require(
|
|
4
|
-
const { getInstallable } = require(
|
|
1
|
+
const { execFileSync } = require('node:child_process');
|
|
2
|
+
const { existsSync, readFileSync, readdirSync } = require('node:fs');
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const { getInstallable } = require('./ecosystem');
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Health check.
|
|
@@ -17,13 +17,13 @@ const { getInstallable } = require("./ecosystem");
|
|
|
17
17
|
*/
|
|
18
18
|
function checkGlobal(packageName) {
|
|
19
19
|
try {
|
|
20
|
-
const out =
|
|
21
|
-
stdio:
|
|
22
|
-
encoding:
|
|
20
|
+
const out = execFileSync('npm', ['list', '-g', packageName, '--depth=0'], {
|
|
21
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
22
|
+
encoding: 'utf-8',
|
|
23
23
|
timeout: 5000,
|
|
24
24
|
});
|
|
25
25
|
const match = out.match(new RegExp(`${escapeRegex(packageName)}@(\\S+)`));
|
|
26
|
-
return match ? { version: match[1], method:
|
|
26
|
+
return match ? { version: match[1], method: 'global' } : null;
|
|
27
27
|
} catch {
|
|
28
28
|
return null;
|
|
29
29
|
}
|
|
@@ -35,34 +35,25 @@ function checkGlobal(packageName) {
|
|
|
35
35
|
*/
|
|
36
36
|
function checkNpxCache(packageName) {
|
|
37
37
|
try {
|
|
38
|
-
const prefix =
|
|
39
|
-
stdio:
|
|
40
|
-
encoding:
|
|
38
|
+
const prefix = execFileSync('npm', ['config', 'get', 'cache'], {
|
|
39
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
40
|
+
encoding: 'utf-8',
|
|
41
41
|
timeout: 5000,
|
|
42
42
|
}).trim();
|
|
43
|
-
const npxDir = path.join(prefix,
|
|
43
|
+
const npxDir = path.join(prefix, '_npx');
|
|
44
44
|
if (!existsSync(npxDir)) return null;
|
|
45
45
|
|
|
46
46
|
// npx cache has hash-named directories, each with node_modules
|
|
47
|
-
const { readdirSync } = require("node:fs");
|
|
48
47
|
const entries = readdirSync(npxDir, { withFileTypes: true });
|
|
49
48
|
for (const entry of entries) {
|
|
50
49
|
if (!entry.isDirectory()) continue;
|
|
51
|
-
const pkgJson = path.join(
|
|
52
|
-
npxDir,
|
|
53
|
-
entry.name,
|
|
54
|
-
"node_modules",
|
|
55
|
-
packageName,
|
|
56
|
-
"package.json",
|
|
57
|
-
);
|
|
50
|
+
const pkgJson = path.join(npxDir, entry.name, 'node_modules', packageName, 'package.json');
|
|
58
51
|
if (existsSync(pkgJson)) {
|
|
59
52
|
try {
|
|
60
|
-
const pkg = JSON.parse(
|
|
61
|
-
|
|
62
|
-
);
|
|
63
|
-
return { version: pkg.version || "installed", method: "npx cache" };
|
|
53
|
+
const pkg = JSON.parse(readFileSync(pkgJson, 'utf-8'));
|
|
54
|
+
return { version: pkg.version || 'installed', method: 'npx cache' };
|
|
64
55
|
} catch {
|
|
65
|
-
return { version:
|
|
56
|
+
return { version: 'installed', method: 'npx cache' };
|
|
66
57
|
}
|
|
67
58
|
}
|
|
68
59
|
}
|
|
@@ -78,15 +69,10 @@ function checkNpxCache(packageName) {
|
|
|
78
69
|
*/
|
|
79
70
|
function checkLocal(packageName) {
|
|
80
71
|
try {
|
|
81
|
-
const pkgJson = path.join(
|
|
82
|
-
process.cwd(),
|
|
83
|
-
"node_modules",
|
|
84
|
-
packageName,
|
|
85
|
-
"package.json",
|
|
86
|
-
);
|
|
72
|
+
const pkgJson = path.join(process.cwd(), 'node_modules', packageName, 'package.json');
|
|
87
73
|
if (existsSync(pkgJson)) {
|
|
88
|
-
const pkg = JSON.parse(
|
|
89
|
-
return { version: pkg.version ||
|
|
74
|
+
const pkg = JSON.parse(readFileSync(pkgJson, 'utf-8'));
|
|
75
|
+
return { version: pkg.version || 'installed', method: 'local' };
|
|
90
76
|
}
|
|
91
77
|
return null;
|
|
92
78
|
} catch {
|
|
@@ -102,27 +88,18 @@ function checkLocal(packageName) {
|
|
|
102
88
|
function checkSource(packageName) {
|
|
103
89
|
const candidates = [
|
|
104
90
|
// Sibling directory (monorepo or co-located checkouts)
|
|
105
|
-
path.join(process.cwd(),
|
|
106
|
-
path.join(
|
|
107
|
-
process.cwd(),
|
|
108
|
-
"..",
|
|
109
|
-
packageName.replace(/^@[^/]+\//, ""),
|
|
110
|
-
"package.json",
|
|
111
|
-
),
|
|
91
|
+
path.join(process.cwd(), '..', packageName.replace(/^@[^/]+\//, '')),
|
|
92
|
+
path.join(process.cwd(), '..', packageName.replace(/^@[^/]+\//, ''), 'package.json'),
|
|
112
93
|
// Packages dir (monorepo)
|
|
113
|
-
path.join(process.cwd(),
|
|
94
|
+
path.join(process.cwd(), 'packages', packageName.replace(/^@[^/]+\//, '')),
|
|
114
95
|
];
|
|
115
96
|
for (const candidate of candidates) {
|
|
116
|
-
const pkgJson = candidate.endsWith(
|
|
117
|
-
? candidate
|
|
118
|
-
: path.join(candidate, "package.json");
|
|
97
|
+
const pkgJson = candidate.endsWith('package.json') ? candidate : path.join(candidate, 'package.json');
|
|
119
98
|
if (existsSync(pkgJson)) {
|
|
120
99
|
try {
|
|
121
|
-
const pkg = JSON.parse(
|
|
122
|
-
require("node:fs").readFileSync(pkgJson, "utf-8"),
|
|
123
|
-
);
|
|
100
|
+
const pkg = JSON.parse(readFileSync(pkgJson, 'utf-8'));
|
|
124
101
|
if (pkg.name === packageName) {
|
|
125
|
-
return { version: pkg.version ||
|
|
102
|
+
return { version: pkg.version || 'installed', method: 'source' };
|
|
126
103
|
}
|
|
127
104
|
} catch {
|
|
128
105
|
// not a match, continue
|
|
@@ -139,24 +116,21 @@ function checkSource(packageName) {
|
|
|
139
116
|
*/
|
|
140
117
|
function checkNpxNoInstall(packageName) {
|
|
141
118
|
try {
|
|
142
|
-
const out =
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
timeout: 5000,
|
|
148
|
-
},
|
|
149
|
-
).trim();
|
|
119
|
+
const out = execFileSync('npx', ['--no-install', packageName, '--version'], {
|
|
120
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
121
|
+
encoding: 'utf-8',
|
|
122
|
+
timeout: 5000,
|
|
123
|
+
}).trim();
|
|
150
124
|
// Expect a version-like string
|
|
151
125
|
const match = out.match(/v?(\d+\.\d+\.\d+\S*)/);
|
|
152
|
-
return match ? { version: match[1], method:
|
|
126
|
+
return match ? { version: match[1], method: 'npx' } : null;
|
|
153
127
|
} catch {
|
|
154
128
|
return null;
|
|
155
129
|
}
|
|
156
130
|
}
|
|
157
131
|
|
|
158
132
|
function escapeRegex(str) {
|
|
159
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g,
|
|
133
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
160
134
|
}
|
|
161
135
|
|
|
162
136
|
/**
|
|
@@ -188,21 +162,21 @@ function getNodeVersion() {
|
|
|
188
162
|
|
|
189
163
|
function getNpmVersion() {
|
|
190
164
|
try {
|
|
191
|
-
return
|
|
192
|
-
stdio:
|
|
193
|
-
encoding:
|
|
165
|
+
return execFileSync('npm', ['--version'], {
|
|
166
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
167
|
+
encoding: 'utf-8',
|
|
194
168
|
timeout: 5000,
|
|
195
169
|
}).trim();
|
|
196
170
|
} catch {
|
|
197
|
-
return
|
|
171
|
+
return 'not found';
|
|
198
172
|
}
|
|
199
173
|
}
|
|
200
174
|
|
|
201
175
|
function getPnpmVersion() {
|
|
202
176
|
try {
|
|
203
|
-
return
|
|
204
|
-
stdio:
|
|
205
|
-
encoding:
|
|
177
|
+
return execFileSync('pnpm', ['--version'], {
|
|
178
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
179
|
+
encoding: 'utf-8',
|
|
206
180
|
timeout: 5000,
|
|
207
181
|
}).trim();
|
|
208
182
|
} catch {
|
|
@@ -212,9 +186,9 @@ function getPnpmVersion() {
|
|
|
212
186
|
|
|
213
187
|
function getBiomeVersion() {
|
|
214
188
|
try {
|
|
215
|
-
const out =
|
|
216
|
-
stdio:
|
|
217
|
-
encoding:
|
|
189
|
+
const out = execFileSync('npx', ['biome', '--version'], {
|
|
190
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
191
|
+
encoding: 'utf-8',
|
|
218
192
|
timeout: 5000,
|
|
219
193
|
}).trim();
|
|
220
194
|
const match = out.match(/(\d+\.\d+\.\d+\S*)/);
|
|
@@ -226,9 +200,9 @@ function getBiomeVersion() {
|
|
|
226
200
|
|
|
227
201
|
function getHooksPath() {
|
|
228
202
|
try {
|
|
229
|
-
return
|
|
230
|
-
stdio:
|
|
231
|
-
encoding:
|
|
203
|
+
return execFileSync('git', ['config', 'core.hooksPath'], {
|
|
204
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
205
|
+
encoding: 'utf-8',
|
|
232
206
|
timeout: 5000,
|
|
233
207
|
}).trim();
|
|
234
208
|
} catch {
|
|
@@ -237,7 +211,7 @@ function getHooksPath() {
|
|
|
237
211
|
}
|
|
238
212
|
|
|
239
213
|
function run(opts) {
|
|
240
|
-
const json = opts
|
|
214
|
+
const json = opts?.json;
|
|
241
215
|
const tools = getInstallable();
|
|
242
216
|
|
|
243
217
|
const pnpmVersion = getPnpmVersion();
|
|
@@ -276,76 +250,64 @@ function run(opts) {
|
|
|
276
250
|
let installed = 0;
|
|
277
251
|
let missing = 0;
|
|
278
252
|
|
|
279
|
-
console.log(
|
|
280
|
-
console.log(
|
|
281
|
-
console.log(
|
|
282
|
-
console.log(
|
|
253
|
+
console.log('');
|
|
254
|
+
console.log(' \x1b[1;33mgrainulation doctor\x1b[0m');
|
|
255
|
+
console.log(' Checking ecosystem health...');
|
|
256
|
+
console.log('');
|
|
283
257
|
|
|
284
258
|
// Environment
|
|
285
|
-
console.log(
|
|
259
|
+
console.log(' \x1b[2mEnvironment:\x1b[0m');
|
|
286
260
|
console.log(` Node ${getNodeVersion()}`);
|
|
287
261
|
console.log(` npm v${getNpmVersion()}`);
|
|
288
262
|
if (pnpmVersion) {
|
|
289
263
|
console.log(` pnpm v${pnpmVersion}`);
|
|
290
264
|
} else {
|
|
291
|
-
console.log(
|
|
265
|
+
console.log(' pnpm \x1b[2mnot found\x1b[0m');
|
|
292
266
|
}
|
|
293
|
-
console.log(
|
|
267
|
+
console.log('');
|
|
294
268
|
|
|
295
269
|
// DX tooling
|
|
296
|
-
console.log(
|
|
270
|
+
console.log(' \x1b[2mDX tooling:\x1b[0m');
|
|
297
271
|
if (biomeVersion) {
|
|
298
272
|
console.log(` \x1b[32m\u2713\x1b[0m Biome v${biomeVersion}`);
|
|
299
273
|
} else {
|
|
300
|
-
console.log(
|
|
301
|
-
" \x1b[2m\u2717 Biome not found (pnpm install to set up)\x1b[0m",
|
|
302
|
-
);
|
|
274
|
+
console.log(' \x1b[2m\u2717 Biome not found (pnpm install to set up)\x1b[0m');
|
|
303
275
|
}
|
|
304
276
|
if (hooksPath) {
|
|
305
277
|
console.log(` \x1b[32m\u2713\x1b[0m Git hooks ${hooksPath}`);
|
|
306
278
|
} else {
|
|
307
|
-
console.log(
|
|
308
|
-
" \x1b[2m\u2717 Git hooks not configured (run: git config core.hooksPath .githooks)\x1b[0m",
|
|
309
|
-
);
|
|
279
|
+
console.log(' \x1b[2m\u2717 Git hooks not configured (run: git config core.hooksPath .githooks)\x1b[0m');
|
|
310
280
|
}
|
|
311
|
-
console.log(
|
|
281
|
+
console.log('');
|
|
312
282
|
|
|
313
283
|
// Tools
|
|
314
|
-
console.log(
|
|
284
|
+
console.log(' \x1b[2mTools:\x1b[0m');
|
|
315
285
|
for (const tool of tools) {
|
|
316
286
|
const result = detect(tool.package);
|
|
317
287
|
if (result) {
|
|
318
288
|
installed++;
|
|
319
289
|
const ver = `v${result.version}`.padEnd(10);
|
|
320
|
-
console.log(
|
|
321
|
-
` \x1b[32m\u2713\x1b[0m ${tool.name.padEnd(12)} ${ver} \x1b[2m(${result.method})\x1b[0m`,
|
|
322
|
-
);
|
|
290
|
+
console.log(` \x1b[32m\u2713\x1b[0m ${tool.name.padEnd(12)} ${ver} \x1b[2m(${result.method})\x1b[0m`);
|
|
323
291
|
} else {
|
|
324
292
|
missing++;
|
|
325
|
-
console.log(
|
|
326
|
-
` \x1b[2m\u2717 ${tool.name.padEnd(12)} -- (not found)\x1b[0m`,
|
|
327
|
-
);
|
|
293
|
+
console.log(` \x1b[2m\u2717 ${tool.name.padEnd(12)} -- (not found)\x1b[0m`);
|
|
328
294
|
}
|
|
329
295
|
}
|
|
330
296
|
|
|
331
|
-
console.log(
|
|
297
|
+
console.log('');
|
|
332
298
|
|
|
333
299
|
// Summary
|
|
334
300
|
if (missing === tools.length) {
|
|
335
|
-
console.log(
|
|
336
|
-
console.log(
|
|
301
|
+
console.log(' \x1b[33mNo grainulation tools found.\x1b[0m');
|
|
302
|
+
console.log(' Start with: npx @grainulation/wheat init');
|
|
337
303
|
} else if (missing > 0) {
|
|
338
|
-
console.log(
|
|
339
|
-
|
|
340
|
-
);
|
|
341
|
-
console.log(
|
|
342
|
-
" Run \x1b[1mgrainulation setup\x1b[0m to install what you need.",
|
|
343
|
-
);
|
|
304
|
+
console.log(` \x1b[32m${installed} found\x1b[0m, \x1b[2m${missing} not found\x1b[0m`);
|
|
305
|
+
console.log(' Run \x1b[1mgrainulation setup\x1b[0m to install what you need.');
|
|
344
306
|
} else {
|
|
345
|
-
console.log(
|
|
307
|
+
console.log(' \x1b[32mAll tools found. Full ecosystem ready.\x1b[0m');
|
|
346
308
|
}
|
|
347
309
|
|
|
348
|
-
console.log(
|
|
310
|
+
console.log('');
|
|
349
311
|
}
|
|
350
312
|
|
|
351
313
|
module.exports = {
|
package/lib/ecosystem.js
CHANGED
|
@@ -7,99 +7,91 @@
|
|
|
7
7
|
|
|
8
8
|
const TOOLS = [
|
|
9
9
|
{
|
|
10
|
-
name:
|
|
11
|
-
package:
|
|
12
|
-
icon:
|
|
13
|
-
role:
|
|
14
|
-
description:
|
|
15
|
-
|
|
16
|
-
category: "core",
|
|
10
|
+
name: 'wheat',
|
|
11
|
+
package: '@grainulation/wheat',
|
|
12
|
+
icon: 'W',
|
|
13
|
+
role: 'Grows evidence',
|
|
14
|
+
description: 'Research sprint engine. Ask a question, grow claims, compile a brief.',
|
|
15
|
+
category: 'core',
|
|
17
16
|
port: 9091,
|
|
18
|
-
serveCmd: [
|
|
17
|
+
serveCmd: ['serve'],
|
|
19
18
|
entryPoint: true,
|
|
20
19
|
},
|
|
21
20
|
{
|
|
22
|
-
name:
|
|
23
|
-
package:
|
|
24
|
-
icon:
|
|
25
|
-
role:
|
|
26
|
-
description:
|
|
27
|
-
|
|
28
|
-
category: "core",
|
|
21
|
+
name: 'farmer',
|
|
22
|
+
package: '@grainulation/farmer',
|
|
23
|
+
icon: 'F',
|
|
24
|
+
role: 'Permission dashboard',
|
|
25
|
+
description: 'Permission dashboard. Approve tool calls, review AI actions in real time.',
|
|
26
|
+
category: 'core',
|
|
29
27
|
port: 9090,
|
|
30
|
-
serveCmd: [
|
|
28
|
+
serveCmd: ['start'],
|
|
31
29
|
entryPoint: false,
|
|
32
30
|
},
|
|
33
31
|
{
|
|
34
|
-
name:
|
|
35
|
-
package:
|
|
36
|
-
icon:
|
|
37
|
-
role:
|
|
38
|
-
description:
|
|
39
|
-
|
|
40
|
-
category: "foundation",
|
|
32
|
+
name: 'barn',
|
|
33
|
+
package: '@grainulation/barn',
|
|
34
|
+
icon: 'B',
|
|
35
|
+
role: 'Shared tools',
|
|
36
|
+
description: 'Public utilities. Claim schemas, HTML templates, shared validators.',
|
|
37
|
+
category: 'foundation',
|
|
41
38
|
port: 9093,
|
|
42
|
-
serveCmd: [
|
|
39
|
+
serveCmd: ['serve'],
|
|
43
40
|
entryPoint: false,
|
|
44
41
|
},
|
|
45
42
|
{
|
|
46
|
-
name:
|
|
47
|
-
package:
|
|
48
|
-
icon:
|
|
49
|
-
role:
|
|
50
|
-
description:
|
|
51
|
-
|
|
52
|
-
category: "output",
|
|
43
|
+
name: 'mill',
|
|
44
|
+
package: '@grainulation/mill',
|
|
45
|
+
icon: 'M',
|
|
46
|
+
role: 'Processes output',
|
|
47
|
+
description: 'Export and publish. Turn compiled research into PDFs, slides, wikis.',
|
|
48
|
+
category: 'output',
|
|
53
49
|
port: 9094,
|
|
54
|
-
serveCmd: [
|
|
50
|
+
serveCmd: ['serve'],
|
|
55
51
|
entryPoint: false,
|
|
56
52
|
},
|
|
57
53
|
{
|
|
58
|
-
name:
|
|
59
|
-
package:
|
|
60
|
-
icon:
|
|
61
|
-
role:
|
|
62
|
-
description:
|
|
63
|
-
|
|
64
|
-
category: "storage",
|
|
54
|
+
name: 'silo',
|
|
55
|
+
package: '@grainulation/silo',
|
|
56
|
+
icon: 'S',
|
|
57
|
+
role: 'Stores knowledge',
|
|
58
|
+
description: 'Reusable claim libraries. Share vetted claims across sprints and teams.',
|
|
59
|
+
category: 'storage',
|
|
65
60
|
port: 9095,
|
|
66
|
-
serveCmd: [
|
|
61
|
+
serveCmd: ['serve'],
|
|
67
62
|
entryPoint: false,
|
|
68
63
|
},
|
|
69
64
|
{
|
|
70
|
-
name:
|
|
71
|
-
package:
|
|
72
|
-
icon:
|
|
73
|
-
role:
|
|
74
|
-
description:
|
|
75
|
-
|
|
76
|
-
category: "analytics",
|
|
65
|
+
name: 'harvest',
|
|
66
|
+
package: '@grainulation/harvest',
|
|
67
|
+
icon: 'H',
|
|
68
|
+
role: 'Analytics & retrospectives',
|
|
69
|
+
description: 'Cross-sprint learning. Track prediction accuracy, find blind spots over time.',
|
|
70
|
+
category: 'analytics',
|
|
77
71
|
port: 9096,
|
|
78
|
-
serveCmd: [
|
|
72
|
+
serveCmd: ['serve'],
|
|
79
73
|
entryPoint: false,
|
|
80
74
|
},
|
|
81
75
|
{
|
|
82
|
-
name:
|
|
83
|
-
package:
|
|
84
|
-
icon:
|
|
85
|
-
role:
|
|
86
|
-
description:
|
|
87
|
-
|
|
88
|
-
category: "orchestration",
|
|
76
|
+
name: 'orchard',
|
|
77
|
+
package: '@grainulation/orchard',
|
|
78
|
+
icon: 'O',
|
|
79
|
+
role: 'Orchestration',
|
|
80
|
+
description: 'Multi-sprint coordination. Run parallel research tracks, merge results.',
|
|
81
|
+
category: 'orchestration',
|
|
89
82
|
port: 9097,
|
|
90
|
-
serveCmd: [
|
|
83
|
+
serveCmd: ['serve'],
|
|
91
84
|
entryPoint: false,
|
|
92
85
|
},
|
|
93
86
|
{
|
|
94
|
-
name:
|
|
95
|
-
package:
|
|
96
|
-
icon:
|
|
97
|
-
role:
|
|
98
|
-
description:
|
|
99
|
-
|
|
100
|
-
category: "meta",
|
|
87
|
+
name: 'grainulation',
|
|
88
|
+
package: '@grainulation/grainulation',
|
|
89
|
+
icon: 'G',
|
|
90
|
+
role: 'The machine',
|
|
91
|
+
description: 'Process manager and ecosystem hub. Start, stop, and monitor all tools.',
|
|
92
|
+
category: 'meta',
|
|
101
93
|
port: 9098,
|
|
102
|
-
serveCmd: [
|
|
94
|
+
serveCmd: ['serve'],
|
|
103
95
|
entryPoint: false,
|
|
104
96
|
},
|
|
105
97
|
];
|
|
@@ -113,7 +105,7 @@ function getByName(name) {
|
|
|
113
105
|
}
|
|
114
106
|
|
|
115
107
|
function getInstallable() {
|
|
116
|
-
return TOOLS.filter((t) => t.name !==
|
|
108
|
+
return TOOLS.filter((t) => t.name !== 'grainulation');
|
|
117
109
|
}
|
|
118
110
|
|
|
119
111
|
function getCategories() {
|