rrce-workflow 0.2.21 → 0.2.23
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 +144 -0
- package/dist/index.js +1103 -259
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -1,24 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const result = execSync("git config user.name", { encoding: "utf-8" });
|
|
11
|
-
return result.trim() || null;
|
|
12
|
-
} catch {
|
|
13
|
-
return null;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res) => function __init() {
|
|
4
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
|
+
};
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
16
10
|
|
|
17
11
|
// src/lib/paths.ts
|
|
18
12
|
import * as fs from "fs";
|
|
19
13
|
import * as path from "path";
|
|
20
|
-
var RRCE_HOME = process.env.RRCE_HOME || path.join(process.env.HOME || "~", ".rrce-workflow");
|
|
21
|
-
var RRCE_WORKSPACE = process.env.RRCE_WORKSPACE;
|
|
22
14
|
function detectWorkspaceRoot() {
|
|
23
15
|
if (RRCE_WORKSPACE) {
|
|
24
16
|
return RRCE_WORKSPACE;
|
|
@@ -132,8 +124,224 @@ function getEffectiveRRCEHome(workspaceRoot) {
|
|
|
132
124
|
}
|
|
133
125
|
return getDefaultRRCEHome();
|
|
134
126
|
}
|
|
127
|
+
var RRCE_HOME, RRCE_WORKSPACE;
|
|
128
|
+
var init_paths = __esm({
|
|
129
|
+
"src/lib/paths.ts"() {
|
|
130
|
+
"use strict";
|
|
131
|
+
RRCE_HOME = process.env.RRCE_HOME || path.join(process.env.HOME || "~", ".rrce-workflow");
|
|
132
|
+
RRCE_WORKSPACE = process.env.RRCE_WORKSPACE;
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// src/lib/autocomplete-prompt.ts
|
|
137
|
+
import * as fs6 from "fs";
|
|
138
|
+
import * as path6 from "path";
|
|
139
|
+
import * as readline from "readline";
|
|
140
|
+
import pc from "picocolors";
|
|
141
|
+
function directoryPrompt(opts) {
|
|
142
|
+
return new Promise((resolve) => {
|
|
143
|
+
process.stdout.write(`${pc.cyan("\u25C6")} ${opts.message}
|
|
144
|
+
`);
|
|
145
|
+
process.stdout.write(`${pc.cyan("\u2502")} `);
|
|
146
|
+
const rl = readline.createInterface({
|
|
147
|
+
input: process.stdin,
|
|
148
|
+
output: process.stdout,
|
|
149
|
+
completer: completeDirectory,
|
|
150
|
+
terminal: true
|
|
151
|
+
});
|
|
152
|
+
if (opts.defaultValue) {
|
|
153
|
+
rl.write(opts.defaultValue);
|
|
154
|
+
}
|
|
155
|
+
rl.on("line", (input) => {
|
|
156
|
+
const value = input.trim();
|
|
157
|
+
const expandedPath = value.startsWith("~") ? value.replace(/^~/, process.env.HOME || "") : value;
|
|
158
|
+
if (opts.validate) {
|
|
159
|
+
const error = opts.validate(expandedPath);
|
|
160
|
+
if (error) {
|
|
161
|
+
process.stdout.write(`${pc.yellow("\u2502")} ${pc.yellow(error)}
|
|
162
|
+
`);
|
|
163
|
+
process.stdout.write(`${pc.cyan("\u2502")} `);
|
|
164
|
+
rl.write(value);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
rl.close();
|
|
169
|
+
process.stdout.write(`${pc.green("\u2713")} ${pc.dim(expandedPath)}
|
|
170
|
+
`);
|
|
171
|
+
resolve(expandedPath);
|
|
172
|
+
});
|
|
173
|
+
rl.on("close", () => {
|
|
174
|
+
});
|
|
175
|
+
rl.on("SIGINT", () => {
|
|
176
|
+
rl.close();
|
|
177
|
+
process.stdout.write("\n");
|
|
178
|
+
resolve(/* @__PURE__ */ Symbol("cancel"));
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
function completeDirectory(line) {
|
|
183
|
+
const expanded = line.startsWith("~") ? line.replace(/^~/, process.env.HOME || "") : line;
|
|
184
|
+
try {
|
|
185
|
+
let dirToScan;
|
|
186
|
+
let prefix;
|
|
187
|
+
let basePath;
|
|
188
|
+
if (expanded === "" || expanded === "/") {
|
|
189
|
+
dirToScan = expanded || "/";
|
|
190
|
+
prefix = "";
|
|
191
|
+
basePath = expanded;
|
|
192
|
+
} else if (expanded.endsWith("/")) {
|
|
193
|
+
dirToScan = expanded;
|
|
194
|
+
prefix = "";
|
|
195
|
+
basePath = expanded;
|
|
196
|
+
} else {
|
|
197
|
+
dirToScan = path6.dirname(expanded);
|
|
198
|
+
prefix = path6.basename(expanded).toLowerCase();
|
|
199
|
+
basePath = dirToScan === "/" ? "/" : dirToScan + "/";
|
|
200
|
+
}
|
|
201
|
+
if (!fs6.existsSync(dirToScan)) {
|
|
202
|
+
return [[], line];
|
|
203
|
+
}
|
|
204
|
+
const entries = fs6.readdirSync(dirToScan, { withFileTypes: true }).filter((entry) => {
|
|
205
|
+
if (!entry.isDirectory()) return false;
|
|
206
|
+
if (entry.name.startsWith(".") && !prefix.startsWith(".")) return false;
|
|
207
|
+
return prefix === "" || entry.name.toLowerCase().startsWith(prefix);
|
|
208
|
+
}).map((entry) => {
|
|
209
|
+
const fullPath = path6.join(dirToScan, entry.name);
|
|
210
|
+
const displayPath = fullPath.startsWith(process.env.HOME || "") ? fullPath.replace(process.env.HOME || "", "~") : fullPath;
|
|
211
|
+
return displayPath + "/";
|
|
212
|
+
}).sort();
|
|
213
|
+
if (entries.length === 1) {
|
|
214
|
+
return [entries, line];
|
|
215
|
+
}
|
|
216
|
+
if (entries.length > 1) {
|
|
217
|
+
const commonPrefix = getCommonPrefix(entries);
|
|
218
|
+
if (commonPrefix.length > line.length) {
|
|
219
|
+
return [[commonPrefix], line];
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return [entries, line];
|
|
223
|
+
} catch {
|
|
224
|
+
return [[], line];
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function getCommonPrefix(strings) {
|
|
228
|
+
if (strings.length === 0) return "";
|
|
229
|
+
if (strings.length === 1) return strings[0] || "";
|
|
230
|
+
let prefix = strings[0] || "";
|
|
231
|
+
for (let i = 1; i < strings.length; i++) {
|
|
232
|
+
const str = strings[i] || "";
|
|
233
|
+
while (prefix.length > 0 && !str.startsWith(prefix)) {
|
|
234
|
+
prefix = prefix.slice(0, -1);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return prefix;
|
|
238
|
+
}
|
|
239
|
+
function isCancelled(value) {
|
|
240
|
+
return typeof value === "symbol";
|
|
241
|
+
}
|
|
242
|
+
var init_autocomplete_prompt = __esm({
|
|
243
|
+
"src/lib/autocomplete-prompt.ts"() {
|
|
244
|
+
"use strict";
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// src/lib/tui-utils.ts
|
|
249
|
+
var tui_utils_exports = {};
|
|
250
|
+
__export(tui_utils_exports, {
|
|
251
|
+
resolveGlobalPath: () => resolveGlobalPath
|
|
252
|
+
});
|
|
253
|
+
import { select, note, isCancel } from "@clack/prompts";
|
|
254
|
+
import pc2 from "picocolors";
|
|
255
|
+
import * as path7 from "path";
|
|
256
|
+
async function resolveGlobalPath() {
|
|
257
|
+
const defaultPath = getDefaultRRCEHome();
|
|
258
|
+
const isDefaultWritable = checkWriteAccess(defaultPath);
|
|
259
|
+
const options = [
|
|
260
|
+
{
|
|
261
|
+
value: "default",
|
|
262
|
+
label: `Default (${defaultPath})`,
|
|
263
|
+
hint: isDefaultWritable ? pc2.green("\u2713 writable") : pc2.red("\u2717 not writable")
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
value: "custom",
|
|
267
|
+
label: "Custom path",
|
|
268
|
+
hint: "Specify your own directory"
|
|
269
|
+
}
|
|
270
|
+
];
|
|
271
|
+
const choice = await select({
|
|
272
|
+
message: "Global storage location:",
|
|
273
|
+
options,
|
|
274
|
+
initialValue: isDefaultWritable ? "default" : "custom"
|
|
275
|
+
});
|
|
276
|
+
if (isCancel(choice)) {
|
|
277
|
+
return void 0;
|
|
278
|
+
}
|
|
279
|
+
if (choice === "default") {
|
|
280
|
+
if (!isDefaultWritable) {
|
|
281
|
+
note(
|
|
282
|
+
`${pc2.yellow("\u26A0")} Cannot write to default path:
|
|
283
|
+
${pc2.dim(defaultPath)}
|
|
284
|
+
|
|
285
|
+
This can happen when running via npx/bunx in restricted environments.
|
|
286
|
+
Please choose a custom path instead.`,
|
|
287
|
+
"Write Access Issue"
|
|
288
|
+
);
|
|
289
|
+
return resolveGlobalPath();
|
|
290
|
+
}
|
|
291
|
+
return defaultPath;
|
|
292
|
+
}
|
|
293
|
+
const suggestedPath = path7.join(process.env.HOME || "~", ".local", "share", "rrce-workflow");
|
|
294
|
+
const customPath = await directoryPrompt({
|
|
295
|
+
message: "Enter custom global path (Tab to autocomplete):",
|
|
296
|
+
defaultValue: suggestedPath,
|
|
297
|
+
validate: (value) => {
|
|
298
|
+
if (!value.trim()) {
|
|
299
|
+
return "Path cannot be empty";
|
|
300
|
+
}
|
|
301
|
+
if (!checkWriteAccess(value)) {
|
|
302
|
+
return `Cannot write to ${value}. Please choose a writable path.`;
|
|
303
|
+
}
|
|
304
|
+
return void 0;
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
if (isCancelled(customPath)) {
|
|
308
|
+
return void 0;
|
|
309
|
+
}
|
|
310
|
+
let expandedPath = customPath;
|
|
311
|
+
if (!expandedPath.endsWith(".rrce-workflow")) {
|
|
312
|
+
expandedPath = path7.join(expandedPath, ".rrce-workflow");
|
|
313
|
+
}
|
|
314
|
+
return expandedPath;
|
|
315
|
+
}
|
|
316
|
+
var init_tui_utils = __esm({
|
|
317
|
+
"src/lib/tui-utils.ts"() {
|
|
318
|
+
"use strict";
|
|
319
|
+
init_paths();
|
|
320
|
+
init_autocomplete_prompt();
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// src/commands/wizard/index.ts
|
|
325
|
+
import { intro, select as select3, spinner as spinner5, note as note6, outro as outro5, isCancel as isCancel6 } from "@clack/prompts";
|
|
326
|
+
import pc7 from "picocolors";
|
|
327
|
+
import * as fs11 from "fs";
|
|
328
|
+
|
|
329
|
+
// src/lib/git.ts
|
|
330
|
+
import { execSync } from "child_process";
|
|
331
|
+
function getGitUser() {
|
|
332
|
+
try {
|
|
333
|
+
const result = execSync("git config user.name", { encoding: "utf-8" });
|
|
334
|
+
return result.trim() || null;
|
|
335
|
+
} catch {
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// src/commands/wizard/index.ts
|
|
341
|
+
init_paths();
|
|
135
342
|
|
|
136
343
|
// src/lib/detection.ts
|
|
344
|
+
init_paths();
|
|
137
345
|
import * as fs2 from "fs";
|
|
138
346
|
import * as path2 from "path";
|
|
139
347
|
var SKIP_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
@@ -279,10 +487,11 @@ function parseWorkspaceConfig(configPath) {
|
|
|
279
487
|
}
|
|
280
488
|
|
|
281
489
|
// src/commands/wizard/setup-flow.ts
|
|
282
|
-
|
|
283
|
-
import
|
|
490
|
+
init_paths();
|
|
491
|
+
import { group, select as select2, multiselect, confirm, spinner, note as note2, outro, cancel } from "@clack/prompts";
|
|
492
|
+
import pc3 from "picocolors";
|
|
284
493
|
import * as fs7 from "fs";
|
|
285
|
-
import * as
|
|
494
|
+
import * as path8 from "path";
|
|
286
495
|
|
|
287
496
|
// src/lib/prompts.ts
|
|
288
497
|
import * as fs3 from "fs";
|
|
@@ -355,6 +564,7 @@ function getAgentCorePromptsDir() {
|
|
|
355
564
|
}
|
|
356
565
|
|
|
357
566
|
// src/commands/wizard/utils.ts
|
|
567
|
+
init_paths();
|
|
358
568
|
import * as fs4 from "fs";
|
|
359
569
|
import * as path4 from "path";
|
|
360
570
|
function copyPromptsToDir(prompts, targetDir, extension) {
|
|
@@ -381,6 +591,7 @@ function copyDirRecursive(src, dest) {
|
|
|
381
591
|
}
|
|
382
592
|
|
|
383
593
|
// src/commands/wizard/vscode.ts
|
|
594
|
+
init_paths();
|
|
384
595
|
import * as fs5 from "fs";
|
|
385
596
|
import * as path5 from "path";
|
|
386
597
|
function generateVSCodeWorkspace(workspacePath, workspaceName, linkedProjects, customGlobalPath) {
|
|
@@ -458,124 +669,18 @@ function generateVSCodeWorkspace(workspacePath, workspaceName, linkedProjects, c
|
|
|
458
669
|
workspace.settings["files.readonlyInclude"] = {
|
|
459
670
|
...cleanedReadonly,
|
|
460
671
|
...readonlyPatterns
|
|
461
|
-
};
|
|
462
|
-
}
|
|
463
|
-
fs5.writeFileSync(workspaceFilePath, JSON.stringify(workspace, null, 2));
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
// src/lib/autocomplete-prompt.ts
|
|
467
|
-
import * as fs6 from "fs";
|
|
468
|
-
import * as path6 from "path";
|
|
469
|
-
import * as readline from "readline";
|
|
470
|
-
import pc from "picocolors";
|
|
471
|
-
function directoryPrompt(opts) {
|
|
472
|
-
return new Promise((resolve) => {
|
|
473
|
-
process.stdout.write(`${pc.cyan("\u25C6")} ${opts.message}
|
|
474
|
-
`);
|
|
475
|
-
process.stdout.write(`${pc.cyan("\u2502")} `);
|
|
476
|
-
const rl = readline.createInterface({
|
|
477
|
-
input: process.stdin,
|
|
478
|
-
output: process.stdout,
|
|
479
|
-
completer: completeDirectory,
|
|
480
|
-
terminal: true
|
|
481
|
-
});
|
|
482
|
-
if (opts.defaultValue) {
|
|
483
|
-
rl.write(opts.defaultValue);
|
|
484
|
-
}
|
|
485
|
-
rl.on("line", (input) => {
|
|
486
|
-
const value = input.trim();
|
|
487
|
-
const expandedPath = value.startsWith("~") ? value.replace(/^~/, process.env.HOME || "") : value;
|
|
488
|
-
if (opts.validate) {
|
|
489
|
-
const error = opts.validate(expandedPath);
|
|
490
|
-
if (error) {
|
|
491
|
-
process.stdout.write(`${pc.yellow("\u2502")} ${pc.yellow(error)}
|
|
492
|
-
`);
|
|
493
|
-
process.stdout.write(`${pc.cyan("\u2502")} `);
|
|
494
|
-
rl.write(value);
|
|
495
|
-
return;
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
rl.close();
|
|
499
|
-
process.stdout.write(`${pc.green("\u2713")} ${pc.dim(expandedPath)}
|
|
500
|
-
`);
|
|
501
|
-
resolve(expandedPath);
|
|
502
|
-
});
|
|
503
|
-
rl.on("close", () => {
|
|
504
|
-
});
|
|
505
|
-
rl.on("SIGINT", () => {
|
|
506
|
-
rl.close();
|
|
507
|
-
process.stdout.write("\n");
|
|
508
|
-
resolve(/* @__PURE__ */ Symbol("cancel"));
|
|
509
|
-
});
|
|
510
|
-
});
|
|
511
|
-
}
|
|
512
|
-
function completeDirectory(line) {
|
|
513
|
-
const expanded = line.startsWith("~") ? line.replace(/^~/, process.env.HOME || "") : line;
|
|
514
|
-
try {
|
|
515
|
-
let dirToScan;
|
|
516
|
-
let prefix;
|
|
517
|
-
let basePath;
|
|
518
|
-
if (expanded === "" || expanded === "/") {
|
|
519
|
-
dirToScan = expanded || "/";
|
|
520
|
-
prefix = "";
|
|
521
|
-
basePath = expanded;
|
|
522
|
-
} else if (expanded.endsWith("/")) {
|
|
523
|
-
dirToScan = expanded;
|
|
524
|
-
prefix = "";
|
|
525
|
-
basePath = expanded;
|
|
526
|
-
} else {
|
|
527
|
-
dirToScan = path6.dirname(expanded);
|
|
528
|
-
prefix = path6.basename(expanded).toLowerCase();
|
|
529
|
-
basePath = dirToScan === "/" ? "/" : dirToScan + "/";
|
|
530
|
-
}
|
|
531
|
-
if (!fs6.existsSync(dirToScan)) {
|
|
532
|
-
return [[], line];
|
|
533
|
-
}
|
|
534
|
-
const entries = fs6.readdirSync(dirToScan, { withFileTypes: true }).filter((entry) => {
|
|
535
|
-
if (!entry.isDirectory()) return false;
|
|
536
|
-
if (entry.name.startsWith(".") && !prefix.startsWith(".")) return false;
|
|
537
|
-
return prefix === "" || entry.name.toLowerCase().startsWith(prefix);
|
|
538
|
-
}).map((entry) => {
|
|
539
|
-
const fullPath = path6.join(dirToScan, entry.name);
|
|
540
|
-
const displayPath = fullPath.startsWith(process.env.HOME || "") ? fullPath.replace(process.env.HOME || "", "~") : fullPath;
|
|
541
|
-
return displayPath + "/";
|
|
542
|
-
}).sort();
|
|
543
|
-
if (entries.length === 1) {
|
|
544
|
-
return [entries, line];
|
|
545
|
-
}
|
|
546
|
-
if (entries.length > 1) {
|
|
547
|
-
const commonPrefix = getCommonPrefix(entries);
|
|
548
|
-
if (commonPrefix.length > line.length) {
|
|
549
|
-
return [[commonPrefix], line];
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
return [entries, line];
|
|
553
|
-
} catch {
|
|
554
|
-
return [[], line];
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
function getCommonPrefix(strings) {
|
|
558
|
-
if (strings.length === 0) return "";
|
|
559
|
-
if (strings.length === 1) return strings[0] || "";
|
|
560
|
-
let prefix = strings[0] || "";
|
|
561
|
-
for (let i = 1; i < strings.length; i++) {
|
|
562
|
-
const str = strings[i] || "";
|
|
563
|
-
while (prefix.length > 0 && !str.startsWith(prefix)) {
|
|
564
|
-
prefix = prefix.slice(0, -1);
|
|
565
|
-
}
|
|
672
|
+
};
|
|
566
673
|
}
|
|
567
|
-
|
|
568
|
-
}
|
|
569
|
-
function isCancelled(value) {
|
|
570
|
-
return typeof value === "symbol";
|
|
674
|
+
fs5.writeFileSync(workspaceFilePath, JSON.stringify(workspace, null, 2));
|
|
571
675
|
}
|
|
572
676
|
|
|
573
677
|
// src/commands/wizard/setup-flow.ts
|
|
678
|
+
init_tui_utils();
|
|
574
679
|
async function runSetupFlow(workspacePath, workspaceName, existingProjects) {
|
|
575
680
|
const s = spinner();
|
|
576
681
|
const config = await group(
|
|
577
682
|
{
|
|
578
|
-
storageMode: () =>
|
|
683
|
+
storageMode: () => select2({
|
|
579
684
|
message: "Where should workflow data be stored?",
|
|
580
685
|
options: [
|
|
581
686
|
{ value: "global", label: "Global (~/.rrce-workflow/)", hint: "Cross-project access, clean workspace" },
|
|
@@ -600,8 +705,8 @@ async function runSetupFlow(workspacePath, workspaceName, existingProjects) {
|
|
|
600
705
|
options: existingProjects.map((project) => ({
|
|
601
706
|
value: `${project.name}:${project.source}`,
|
|
602
707
|
// Unique key
|
|
603
|
-
label: `${project.name} ${
|
|
604
|
-
hint:
|
|
708
|
+
label: `${project.name} ${pc3.dim(`(${project.source})`)}`,
|
|
709
|
+
hint: pc3.dim(
|
|
605
710
|
project.source === "global" ? `~/.rrce-workflow/workspaces/${project.name}` : project.dataPath
|
|
606
711
|
)
|
|
607
712
|
})),
|
|
@@ -609,7 +714,7 @@ async function runSetupFlow(workspacePath, workspaceName, existingProjects) {
|
|
|
609
714
|
});
|
|
610
715
|
},
|
|
611
716
|
addToGitignore: () => confirm({
|
|
612
|
-
message: "Add generated folders to .gitignore?",
|
|
717
|
+
message: "Add generated folders to .gitignore? (as comments - uncomment if needed)",
|
|
613
718
|
initialValue: true
|
|
614
719
|
}),
|
|
615
720
|
confirm: () => confirm({
|
|
@@ -656,7 +761,7 @@ async function runSetupFlow(workspacePath, workspaceName, existingProjects) {
|
|
|
656
761
|
`Storage: ${config.storageMode}`
|
|
657
762
|
];
|
|
658
763
|
if (customGlobalPath && customGlobalPath !== getDefaultRRCEHome()) {
|
|
659
|
-
summary.push(`Global path: ${
|
|
764
|
+
summary.push(`Global path: ${pc3.cyan(customGlobalPath)}`);
|
|
660
765
|
}
|
|
661
766
|
if (dataPaths.length > 0) {
|
|
662
767
|
summary.push(`Data paths:`);
|
|
@@ -669,13 +774,13 @@ async function runSetupFlow(workspacePath, workspaceName, existingProjects) {
|
|
|
669
774
|
const linkedProjects = config.linkedProjects;
|
|
670
775
|
if (linkedProjects.length > 0) {
|
|
671
776
|
summary.push(`Linked projects: ${linkedProjects.join(", ")}`);
|
|
672
|
-
summary.push(`Workspace file: ${
|
|
777
|
+
summary.push(`Workspace file: ${pc3.cyan(`${workspaceName}.code-workspace`)}`);
|
|
673
778
|
}
|
|
674
|
-
|
|
779
|
+
note2(summary.join("\n"), "Setup Summary");
|
|
675
780
|
if (linkedProjects.length > 0) {
|
|
676
|
-
outro(
|
|
781
|
+
outro(pc3.green(`\u2713 Setup complete! Open ${pc3.bold(`${workspaceName}.code-workspace`)} in VSCode to access linked knowledge.`));
|
|
677
782
|
} else {
|
|
678
|
-
outro(
|
|
783
|
+
outro(pc3.green(`\u2713 Setup complete! Your agents are ready to use.`));
|
|
679
784
|
}
|
|
680
785
|
} catch (error) {
|
|
681
786
|
s.stop("Error occurred");
|
|
@@ -683,77 +788,18 @@ async function runSetupFlow(workspacePath, workspaceName, existingProjects) {
|
|
|
683
788
|
process.exit(1);
|
|
684
789
|
}
|
|
685
790
|
}
|
|
686
|
-
async function resolveGlobalPath() {
|
|
687
|
-
const defaultPath = getDefaultRRCEHome();
|
|
688
|
-
const isDefaultWritable = checkWriteAccess(defaultPath);
|
|
689
|
-
const options = [];
|
|
690
|
-
options.push({
|
|
691
|
-
value: "default",
|
|
692
|
-
label: `Default (${defaultPath})`,
|
|
693
|
-
hint: isDefaultWritable ? pc2.green("\u2713 writable") : pc2.red("\u2717 not writable")
|
|
694
|
-
});
|
|
695
|
-
options.push({
|
|
696
|
-
value: "custom",
|
|
697
|
-
label: "Custom path",
|
|
698
|
-
hint: "Specify your own directory"
|
|
699
|
-
});
|
|
700
|
-
const choice = await select({
|
|
701
|
-
message: "Global storage location:",
|
|
702
|
-
options,
|
|
703
|
-
initialValue: isDefaultWritable ? "default" : "custom"
|
|
704
|
-
});
|
|
705
|
-
if (isCancel(choice)) {
|
|
706
|
-
return void 0;
|
|
707
|
-
}
|
|
708
|
-
if (choice === "default") {
|
|
709
|
-
if (!isDefaultWritable) {
|
|
710
|
-
note(
|
|
711
|
-
`${pc2.yellow("\u26A0")} Cannot write to default path:
|
|
712
|
-
${pc2.dim(defaultPath)}
|
|
713
|
-
|
|
714
|
-
This can happen when running via npx/bunx in restricted environments.
|
|
715
|
-
Please choose a custom path instead.`,
|
|
716
|
-
"Write Access Issue"
|
|
717
|
-
);
|
|
718
|
-
return resolveGlobalPath();
|
|
719
|
-
}
|
|
720
|
-
return defaultPath;
|
|
721
|
-
}
|
|
722
|
-
const suggestedPath = path7.join(process.env.HOME || "~", ".local", "share", "rrce-workflow");
|
|
723
|
-
const customPath = await directoryPrompt({
|
|
724
|
-
message: "Enter custom global path (Tab to autocomplete):",
|
|
725
|
-
defaultValue: suggestedPath,
|
|
726
|
-
validate: (value) => {
|
|
727
|
-
if (!value.trim()) {
|
|
728
|
-
return "Path cannot be empty";
|
|
729
|
-
}
|
|
730
|
-
if (!checkWriteAccess(value)) {
|
|
731
|
-
return `Cannot write to ${value}. Please choose a writable path.`;
|
|
732
|
-
}
|
|
733
|
-
return void 0;
|
|
734
|
-
}
|
|
735
|
-
});
|
|
736
|
-
if (isCancelled(customPath)) {
|
|
737
|
-
return void 0;
|
|
738
|
-
}
|
|
739
|
-
let expandedPath = customPath;
|
|
740
|
-
if (!expandedPath.endsWith(".rrce-workflow")) {
|
|
741
|
-
expandedPath = path7.join(expandedPath, ".rrce-workflow");
|
|
742
|
-
}
|
|
743
|
-
return expandedPath;
|
|
744
|
-
}
|
|
745
791
|
async function generateConfiguration(config, workspacePath, workspaceName, allProjects = []) {
|
|
746
792
|
const dataPaths = getDataPaths(config.storageMode, workspaceName, workspacePath, config.globalPath);
|
|
747
793
|
for (const dataPath of dataPaths) {
|
|
748
794
|
ensureDir(dataPath);
|
|
749
|
-
ensureDir(
|
|
750
|
-
ensureDir(
|
|
751
|
-
ensureDir(
|
|
752
|
-
ensureDir(
|
|
795
|
+
ensureDir(path8.join(dataPath, "knowledge"));
|
|
796
|
+
ensureDir(path8.join(dataPath, "refs"));
|
|
797
|
+
ensureDir(path8.join(dataPath, "tasks"));
|
|
798
|
+
ensureDir(path8.join(dataPath, "templates"));
|
|
753
799
|
}
|
|
754
800
|
const agentCoreDir = getAgentCoreDir();
|
|
755
801
|
syncMetadataToAll(agentCoreDir, dataPaths);
|
|
756
|
-
copyDirToAllStoragePaths(
|
|
802
|
+
copyDirToAllStoragePaths(path8.join(agentCoreDir, "templates"), "templates", dataPaths);
|
|
757
803
|
const prompts = loadPromptsFromDir(getAgentCorePromptsDir());
|
|
758
804
|
if (config.tools.includes("copilot")) {
|
|
759
805
|
const copilotPath = getAgentPromptPath(workspacePath, "copilot");
|
|
@@ -765,8 +811,8 @@ async function generateConfiguration(config, workspacePath, workspaceName, allPr
|
|
|
765
811
|
ensureDir(antigravityPath);
|
|
766
812
|
copyPromptsToDir(prompts, antigravityPath, ".md");
|
|
767
813
|
}
|
|
768
|
-
const workspaceConfigPath =
|
|
769
|
-
ensureDir(
|
|
814
|
+
const workspaceConfigPath = path8.join(workspacePath, ".rrce-workflow", "config.yaml");
|
|
815
|
+
ensureDir(path8.dirname(workspaceConfigPath));
|
|
770
816
|
let configContent = `# RRCE-Workflow Configuration
|
|
771
817
|
version: 1
|
|
772
818
|
|
|
@@ -806,8 +852,8 @@ linked_projects:
|
|
|
806
852
|
}
|
|
807
853
|
}
|
|
808
854
|
function getDataPaths(mode, workspaceName, workspaceRoot, customGlobalPath) {
|
|
809
|
-
const globalPath =
|
|
810
|
-
const workspacePath =
|
|
855
|
+
const globalPath = path8.join(customGlobalPath || getDefaultRRCEHome(), "workspaces", workspaceName);
|
|
856
|
+
const workspacePath = path8.join(workspaceRoot, ".rrce-workflow");
|
|
811
857
|
switch (mode) {
|
|
812
858
|
case "global":
|
|
813
859
|
return [globalPath];
|
|
@@ -818,7 +864,7 @@ function getDataPaths(mode, workspaceName, workspaceRoot, customGlobalPath) {
|
|
|
818
864
|
}
|
|
819
865
|
}
|
|
820
866
|
function updateGitignore(workspacePath, storageMode, tools) {
|
|
821
|
-
const gitignorePath =
|
|
867
|
+
const gitignorePath = path8.join(workspacePath, ".gitignore");
|
|
822
868
|
const entries = [];
|
|
823
869
|
if (storageMode === "workspace") {
|
|
824
870
|
entries.push(".rrce-workflow/");
|
|
@@ -841,7 +887,11 @@ function updateGitignore(workspacePath, storageMode, tools) {
|
|
|
841
887
|
const newEntries = [];
|
|
842
888
|
for (const entry of entries) {
|
|
843
889
|
const entryWithoutSlash = entry.replace(/\/$/, "");
|
|
844
|
-
|
|
890
|
+
const commentedEntry = `# ${entry}`;
|
|
891
|
+
const commentedEntryNoSlash = `# ${entryWithoutSlash}`;
|
|
892
|
+
if (!lines.some(
|
|
893
|
+
(line) => line === entry || line === entryWithoutSlash || line === commentedEntry || line === commentedEntryNoSlash
|
|
894
|
+
)) {
|
|
845
895
|
newEntries.push(entry);
|
|
846
896
|
}
|
|
847
897
|
}
|
|
@@ -853,9 +903,9 @@ function updateGitignore(workspacePath, storageMode, tools) {
|
|
|
853
903
|
newContent += "\n";
|
|
854
904
|
}
|
|
855
905
|
if (newContent === "" || !content.includes("# rrce-workflow")) {
|
|
856
|
-
newContent += "\n# rrce-workflow generated folders\n";
|
|
906
|
+
newContent += "\n# rrce-workflow generated folders (uncomment to ignore)\n";
|
|
857
907
|
}
|
|
858
|
-
newContent += newEntries.join("\n") + "\n";
|
|
908
|
+
newContent += newEntries.map((e) => `# ${e}`).join("\n") + "\n";
|
|
859
909
|
fs7.writeFileSync(gitignorePath, newContent);
|
|
860
910
|
return true;
|
|
861
911
|
} catch {
|
|
@@ -864,8 +914,9 @@ function updateGitignore(workspacePath, storageMode, tools) {
|
|
|
864
914
|
}
|
|
865
915
|
|
|
866
916
|
// src/commands/wizard/link-flow.ts
|
|
867
|
-
|
|
868
|
-
import
|
|
917
|
+
init_paths();
|
|
918
|
+
import { multiselect as multiselect2, spinner as spinner2, note as note3, outro as outro2, cancel as cancel2, isCancel as isCancel3 } from "@clack/prompts";
|
|
919
|
+
import pc4 from "picocolors";
|
|
869
920
|
import * as fs8 from "fs";
|
|
870
921
|
async function runLinkProjectsFlow(workspacePath, workspaceName) {
|
|
871
922
|
const projects = scanForProjects({
|
|
@@ -873,7 +924,7 @@ async function runLinkProjectsFlow(workspacePath, workspaceName) {
|
|
|
873
924
|
workspacePath
|
|
874
925
|
});
|
|
875
926
|
if (projects.length === 0) {
|
|
876
|
-
outro2(
|
|
927
|
+
outro2(pc4.yellow("No other projects found. Try setting up another project first."));
|
|
877
928
|
return;
|
|
878
929
|
}
|
|
879
930
|
const customGlobalPath = getEffectiveRRCEHome(workspacePath);
|
|
@@ -882,14 +933,14 @@ async function runLinkProjectsFlow(workspacePath, workspaceName) {
|
|
|
882
933
|
options: projects.map((project) => ({
|
|
883
934
|
value: `${project.name}:${project.source}`,
|
|
884
935
|
// Unique key
|
|
885
|
-
label: `${project.name} ${
|
|
886
|
-
hint:
|
|
936
|
+
label: `${project.name} ${pc4.dim(`(${project.source})`)}`,
|
|
937
|
+
hint: pc4.dim(
|
|
887
938
|
project.source === "global" ? `~/.rrce-workflow/workspaces/${project.name}` : project.dataPath
|
|
888
939
|
)
|
|
889
940
|
})),
|
|
890
941
|
required: true
|
|
891
942
|
});
|
|
892
|
-
if (
|
|
943
|
+
if (isCancel3(linkedProjects)) {
|
|
893
944
|
cancel2("Cancelled.");
|
|
894
945
|
process.exit(0);
|
|
895
946
|
}
|
|
@@ -937,43 +988,44 @@ linked_projects:
|
|
|
937
988
|
const workspaceFile = `${workspaceName}.code-workspace`;
|
|
938
989
|
const summary = [
|
|
939
990
|
`Linked projects:`,
|
|
940
|
-
...selectedProjects.map((p) => ` \u2713 ${p.name} ${
|
|
991
|
+
...selectedProjects.map((p) => ` \u2713 ${p.name} ${pc4.dim(`(${p.source})`)}`),
|
|
941
992
|
``,
|
|
942
|
-
`Workspace file: ${
|
|
993
|
+
`Workspace file: ${pc4.cyan(workspaceFile)}`
|
|
943
994
|
];
|
|
944
|
-
|
|
945
|
-
outro2(
|
|
995
|
+
note3(summary.join("\n"), "Link Summary");
|
|
996
|
+
outro2(pc4.green(`\u2713 Projects linked! Open ${pc4.bold(workspaceFile)} in VSCode to access linked data.`));
|
|
946
997
|
}
|
|
947
998
|
|
|
948
999
|
// src/commands/wizard/sync-flow.ts
|
|
949
|
-
|
|
950
|
-
import
|
|
1000
|
+
init_paths();
|
|
1001
|
+
import { confirm as confirm2, spinner as spinner3, note as note4, outro as outro3, cancel as cancel3, isCancel as isCancel4 } from "@clack/prompts";
|
|
1002
|
+
import pc5 from "picocolors";
|
|
951
1003
|
import * as fs9 from "fs";
|
|
952
|
-
import * as
|
|
1004
|
+
import * as path9 from "path";
|
|
953
1005
|
async function runSyncToGlobalFlow(workspacePath, workspaceName) {
|
|
954
1006
|
const localPath = getLocalWorkspacePath(workspacePath);
|
|
955
1007
|
const customGlobalPath = getEffectiveRRCEHome(workspacePath);
|
|
956
|
-
const globalPath =
|
|
1008
|
+
const globalPath = path9.join(customGlobalPath, "workspaces", workspaceName);
|
|
957
1009
|
const subdirs = ["knowledge", "prompts", "templates", "tasks", "refs"];
|
|
958
1010
|
const existingDirs = subdirs.filter(
|
|
959
|
-
(dir) => fs9.existsSync(
|
|
1011
|
+
(dir) => fs9.existsSync(path9.join(localPath, dir))
|
|
960
1012
|
);
|
|
961
1013
|
if (existingDirs.length === 0) {
|
|
962
|
-
outro3(
|
|
1014
|
+
outro3(pc5.yellow("No data found in workspace storage to sync."));
|
|
963
1015
|
return;
|
|
964
1016
|
}
|
|
965
|
-
|
|
1017
|
+
note4(
|
|
966
1018
|
`The following will be copied to global storage:
|
|
967
1019
|
${existingDirs.map((d) => ` \u2022 ${d}/`).join("\n")}
|
|
968
1020
|
|
|
969
|
-
Destination: ${
|
|
1021
|
+
Destination: ${pc5.cyan(globalPath)}`,
|
|
970
1022
|
"Sync Preview"
|
|
971
1023
|
);
|
|
972
1024
|
const shouldSync = await confirm2({
|
|
973
1025
|
message: "Proceed with sync to global storage?",
|
|
974
1026
|
initialValue: true
|
|
975
1027
|
});
|
|
976
|
-
if (
|
|
1028
|
+
if (isCancel4(shouldSync) || !shouldSync) {
|
|
977
1029
|
outro3("Sync cancelled.");
|
|
978
1030
|
return;
|
|
979
1031
|
}
|
|
@@ -982,8 +1034,8 @@ Destination: ${pc4.cyan(globalPath)}`,
|
|
|
982
1034
|
try {
|
|
983
1035
|
ensureDir(globalPath);
|
|
984
1036
|
for (const dir of existingDirs) {
|
|
985
|
-
const srcDir =
|
|
986
|
-
const destDir =
|
|
1037
|
+
const srcDir = path9.join(localPath, dir);
|
|
1038
|
+
const destDir = path9.join(globalPath, dir);
|
|
987
1039
|
ensureDir(destDir);
|
|
988
1040
|
copyDirRecursive(srcDir, destDir);
|
|
989
1041
|
}
|
|
@@ -992,12 +1044,12 @@ Destination: ${pc4.cyan(globalPath)}`,
|
|
|
992
1044
|
`Synced directories:`,
|
|
993
1045
|
...existingDirs.map((d) => ` \u2713 ${d}/`),
|
|
994
1046
|
``,
|
|
995
|
-
`Global path: ${
|
|
1047
|
+
`Global path: ${pc5.cyan(globalPath)}`,
|
|
996
1048
|
``,
|
|
997
1049
|
`Other projects can now link this knowledge!`
|
|
998
1050
|
];
|
|
999
|
-
|
|
1000
|
-
outro3(
|
|
1051
|
+
note4(summary.join("\n"), "Sync Summary");
|
|
1052
|
+
outro3(pc5.green("\u2713 Workspace knowledge synced to global storage!"));
|
|
1001
1053
|
} catch (error) {
|
|
1002
1054
|
s.stop("Error occurred");
|
|
1003
1055
|
cancel3(`Failed to sync: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -1006,10 +1058,11 @@ Destination: ${pc4.cyan(globalPath)}`,
|
|
|
1006
1058
|
}
|
|
1007
1059
|
|
|
1008
1060
|
// src/commands/wizard/update-flow.ts
|
|
1009
|
-
|
|
1010
|
-
import
|
|
1061
|
+
init_paths();
|
|
1062
|
+
import { confirm as confirm3, spinner as spinner4, note as note5, outro as outro4, cancel as cancel4, isCancel as isCancel5 } from "@clack/prompts";
|
|
1063
|
+
import pc6 from "picocolors";
|
|
1011
1064
|
import * as fs10 from "fs";
|
|
1012
|
-
import * as
|
|
1065
|
+
import * as path10 from "path";
|
|
1013
1066
|
async function runUpdateFlow(workspacePath, workspaceName, currentStorageMode) {
|
|
1014
1067
|
const s = spinner4();
|
|
1015
1068
|
s.start("Checking for updates");
|
|
@@ -1020,7 +1073,7 @@ async function runUpdateFlow(workspacePath, workspaceName, currentStorageMode) {
|
|
|
1020
1073
|
const customGlobalPath = getEffectiveRRCEHome(workspacePath);
|
|
1021
1074
|
const dataPaths = resolveAllDataPathsWithCustomGlobal(mode, workspaceName, workspacePath, customGlobalPath);
|
|
1022
1075
|
s.stop("Updates found");
|
|
1023
|
-
|
|
1076
|
+
note5(
|
|
1024
1077
|
`The following will be updated from the package:
|
|
1025
1078
|
\u2022 prompts/ (${prompts.length} agent prompts)
|
|
1026
1079
|
\u2022 templates/ (output templates)
|
|
@@ -1033,13 +1086,13 @@ ${dataPaths.map((p) => ` \u2022 ${p}`).join("\n")}`,
|
|
|
1033
1086
|
message: "Proceed with update?",
|
|
1034
1087
|
initialValue: true
|
|
1035
1088
|
});
|
|
1036
|
-
if (
|
|
1089
|
+
if (isCancel5(shouldUpdate) || !shouldUpdate) {
|
|
1037
1090
|
outro4("Update cancelled.");
|
|
1038
1091
|
return;
|
|
1039
1092
|
}
|
|
1040
1093
|
s.start("Updating from package");
|
|
1041
1094
|
for (const dataPath of dataPaths) {
|
|
1042
|
-
copyDirToAllStoragePaths(
|
|
1095
|
+
copyDirToAllStoragePaths(path10.join(agentCoreDir, "templates"), "templates", [dataPath]);
|
|
1043
1096
|
}
|
|
1044
1097
|
const configFilePath = getConfigPath(workspacePath);
|
|
1045
1098
|
const configContent = fs10.readFileSync(configFilePath, "utf-8");
|
|
@@ -1061,8 +1114,8 @@ ${dataPaths.map((p) => ` \u2022 ${p}`).join("\n")}`,
|
|
|
1061
1114
|
``,
|
|
1062
1115
|
`Your configuration and knowledge files were preserved.`
|
|
1063
1116
|
];
|
|
1064
|
-
|
|
1065
|
-
outro4(
|
|
1117
|
+
note5(summary.join("\n"), "Update Summary");
|
|
1118
|
+
outro4(pc6.green("\u2713 Successfully updated from package!"));
|
|
1066
1119
|
} catch (error) {
|
|
1067
1120
|
s.stop("Error occurred");
|
|
1068
1121
|
cancel4(`Failed to update: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -1070,8 +1123,8 @@ ${dataPaths.map((p) => ` \u2022 ${p}`).join("\n")}`,
|
|
|
1070
1123
|
}
|
|
1071
1124
|
}
|
|
1072
1125
|
function resolveAllDataPathsWithCustomGlobal(mode, workspaceName, workspaceRoot, customGlobalPath) {
|
|
1073
|
-
const globalPath =
|
|
1074
|
-
const workspacePath =
|
|
1126
|
+
const globalPath = path10.join(customGlobalPath, "workspaces", workspaceName);
|
|
1127
|
+
const workspacePath = path10.join(workspaceRoot, ".rrce-workflow");
|
|
1075
1128
|
switch (mode) {
|
|
1076
1129
|
case "global":
|
|
1077
1130
|
return [globalPath];
|
|
@@ -1084,7 +1137,7 @@ function resolveAllDataPathsWithCustomGlobal(mode, workspaceName, workspaceRoot,
|
|
|
1084
1137
|
|
|
1085
1138
|
// src/commands/wizard/index.ts
|
|
1086
1139
|
async function runWizard() {
|
|
1087
|
-
intro(
|
|
1140
|
+
intro(pc7.cyan(pc7.inverse(" RRCE-Workflow Setup ")));
|
|
1088
1141
|
const s = spinner5();
|
|
1089
1142
|
s.start("Detecting environment");
|
|
1090
1143
|
const workspacePath = detectWorkspaceRoot();
|
|
@@ -1092,9 +1145,9 @@ async function runWizard() {
|
|
|
1092
1145
|
const gitUser = getGitUser();
|
|
1093
1146
|
await new Promise((r) => setTimeout(r, 800));
|
|
1094
1147
|
s.stop("Environment detected");
|
|
1095
|
-
|
|
1096
|
-
`Git User: ${
|
|
1097
|
-
Workspace: ${
|
|
1148
|
+
note6(
|
|
1149
|
+
`Git User: ${pc7.bold(gitUser || "(not found)")}
|
|
1150
|
+
Workspace: ${pc7.bold(workspaceName)}`,
|
|
1098
1151
|
"Context"
|
|
1099
1152
|
);
|
|
1100
1153
|
const detectedProjects = scanForProjects({
|
|
@@ -1132,11 +1185,11 @@ Workspace: ${pc6.bold(workspaceName)}`,
|
|
|
1132
1185
|
}
|
|
1133
1186
|
menuOptions.push({ value: "update", label: "Update from package", hint: "Get latest prompts & templates" });
|
|
1134
1187
|
menuOptions.push({ value: "exit", label: "Exit" });
|
|
1135
|
-
const action = await
|
|
1188
|
+
const action = await select3({
|
|
1136
1189
|
message: "This workspace is already configured. What would you like to do?",
|
|
1137
1190
|
options: menuOptions
|
|
1138
1191
|
});
|
|
1139
|
-
if (
|
|
1192
|
+
if (isCancel6(action) || action === "exit") {
|
|
1140
1193
|
outro5("Exited.");
|
|
1141
1194
|
process.exit(0);
|
|
1142
1195
|
}
|
|
@@ -1157,18 +1210,18 @@ Workspace: ${pc6.bold(workspaceName)}`,
|
|
|
1157
1210
|
}
|
|
1158
1211
|
|
|
1159
1212
|
// src/commands/selector.ts
|
|
1160
|
-
import { intro as intro2, select as
|
|
1161
|
-
import
|
|
1162
|
-
import * as
|
|
1213
|
+
import { intro as intro2, select as select4, note as note7, cancel as cancel6, isCancel as isCancel7, outro as outro6 } from "@clack/prompts";
|
|
1214
|
+
import pc8 from "picocolors";
|
|
1215
|
+
import * as path11 from "path";
|
|
1163
1216
|
async function runSelector() {
|
|
1164
|
-
const workspaceName =
|
|
1165
|
-
intro2(
|
|
1217
|
+
const workspaceName = path11.basename(process.cwd());
|
|
1218
|
+
intro2(pc8.cyan(pc8.inverse(` RRCE-Workflow | ${workspaceName} `)));
|
|
1166
1219
|
const prompts = loadPromptsFromDir(getAgentCorePromptsDir());
|
|
1167
1220
|
if (prompts.length === 0) {
|
|
1168
1221
|
cancel6("No agents found. Run `rrce-workflow` to set up.");
|
|
1169
1222
|
process.exit(0);
|
|
1170
1223
|
}
|
|
1171
|
-
const selection = await
|
|
1224
|
+
const selection = await select4({
|
|
1172
1225
|
message: "Select an agent:",
|
|
1173
1226
|
options: prompts.map((p) => ({
|
|
1174
1227
|
value: p,
|
|
@@ -1176,23 +1229,814 @@ async function runSelector() {
|
|
|
1176
1229
|
hint: p.frontmatter.description
|
|
1177
1230
|
}))
|
|
1178
1231
|
});
|
|
1179
|
-
if (
|
|
1232
|
+
if (isCancel7(selection)) {
|
|
1180
1233
|
cancel6("Selection cancelled.");
|
|
1181
1234
|
process.exit(0);
|
|
1182
1235
|
}
|
|
1183
1236
|
const prompt = selection;
|
|
1184
|
-
|
|
1237
|
+
note7(
|
|
1185
1238
|
`Use this agent in your IDE by invoking:
|
|
1186
|
-
${
|
|
1239
|
+
${pc8.bold(pc8.cyan(`@${prompt.frontmatter.name}`))}`,
|
|
1187
1240
|
"Agent Selected"
|
|
1188
1241
|
);
|
|
1189
1242
|
outro6("Done");
|
|
1190
1243
|
}
|
|
1191
1244
|
|
|
1245
|
+
// src/mcp/index.ts
|
|
1246
|
+
import { intro as intro3, outro as outro7, select as select5, multiselect as multiselect3, confirm as confirm4, spinner as spinner6, note as note8, cancel as cancel7, isCancel as isCancel8 } from "@clack/prompts";
|
|
1247
|
+
import pc9 from "picocolors";
|
|
1248
|
+
|
|
1249
|
+
// src/mcp/config.ts
|
|
1250
|
+
init_paths();
|
|
1251
|
+
import * as fs12 from "fs";
|
|
1252
|
+
import * as path12 from "path";
|
|
1253
|
+
|
|
1254
|
+
// src/mcp/types.ts
|
|
1255
|
+
var DEFAULT_MCP_CONFIG = {
|
|
1256
|
+
server: {
|
|
1257
|
+
port: 3e3,
|
|
1258
|
+
autoStart: false
|
|
1259
|
+
},
|
|
1260
|
+
projects: [],
|
|
1261
|
+
defaults: {
|
|
1262
|
+
includeNew: true,
|
|
1263
|
+
permissions: {
|
|
1264
|
+
knowledge: true,
|
|
1265
|
+
tasks: true,
|
|
1266
|
+
refs: true
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
};
|
|
1270
|
+
var DEFAULT_PERMISSIONS = {
|
|
1271
|
+
knowledge: true,
|
|
1272
|
+
tasks: true,
|
|
1273
|
+
refs: true
|
|
1274
|
+
};
|
|
1275
|
+
|
|
1276
|
+
// src/mcp/config.ts
|
|
1277
|
+
function getMCPConfigPath() {
|
|
1278
|
+
const workspaceRoot = detectWorkspaceRoot();
|
|
1279
|
+
const rrceHome = getEffectiveRRCEHome(workspaceRoot);
|
|
1280
|
+
return path12.join(rrceHome, "mcp.yaml");
|
|
1281
|
+
}
|
|
1282
|
+
function ensureMCPGlobalPath() {
|
|
1283
|
+
const workspaceRoot = detectWorkspaceRoot();
|
|
1284
|
+
const rrceHome = getEffectiveRRCEHome(workspaceRoot);
|
|
1285
|
+
if (rrceHome.startsWith(".") || rrceHome.includes(".rrce-workflow/")) {
|
|
1286
|
+
const configPath = path12.join(workspaceRoot, ".rrce-workflow", "config.yaml");
|
|
1287
|
+
if (fs12.existsSync(configPath)) {
|
|
1288
|
+
const content = fs12.readFileSync(configPath, "utf-8");
|
|
1289
|
+
const modeMatch = content.match(/mode:\s*(global|workspace)/);
|
|
1290
|
+
if (modeMatch?.[1] === "workspace") {
|
|
1291
|
+
return {
|
|
1292
|
+
configured: false,
|
|
1293
|
+
path: rrceHome,
|
|
1294
|
+
reason: "Workspace mode configured. MCP requires a global storage path."
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
return {
|
|
1300
|
+
configured: true,
|
|
1301
|
+
path: rrceHome
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1304
|
+
function loadMCPConfig() {
|
|
1305
|
+
const configPath = getMCPConfigPath();
|
|
1306
|
+
if (!fs12.existsSync(configPath)) {
|
|
1307
|
+
return { ...DEFAULT_MCP_CONFIG };
|
|
1308
|
+
}
|
|
1309
|
+
try {
|
|
1310
|
+
const content = fs12.readFileSync(configPath, "utf-8");
|
|
1311
|
+
return parseMCPConfig(content);
|
|
1312
|
+
} catch {
|
|
1313
|
+
return { ...DEFAULT_MCP_CONFIG };
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
function saveMCPConfig(config) {
|
|
1317
|
+
const configPath = getMCPConfigPath();
|
|
1318
|
+
const dir = path12.dirname(configPath);
|
|
1319
|
+
if (!fs12.existsSync(dir)) {
|
|
1320
|
+
fs12.mkdirSync(dir, { recursive: true });
|
|
1321
|
+
}
|
|
1322
|
+
const content = serializeMCPConfig(config);
|
|
1323
|
+
fs12.writeFileSync(configPath, content);
|
|
1324
|
+
}
|
|
1325
|
+
function parseMCPConfig(content) {
|
|
1326
|
+
const config = { ...DEFAULT_MCP_CONFIG, projects: [] };
|
|
1327
|
+
const portMatch = content.match(/port:\s*(\d+)/);
|
|
1328
|
+
if (portMatch?.[1]) config.server.port = parseInt(portMatch[1], 10);
|
|
1329
|
+
const autoStartMatch = content.match(/autoStart:\s*(true|false)/);
|
|
1330
|
+
if (autoStartMatch) config.server.autoStart = autoStartMatch[1] === "true";
|
|
1331
|
+
const includeNewMatch = content.match(/includeNew:\s*(true|false)/);
|
|
1332
|
+
if (includeNewMatch) config.defaults.includeNew = includeNewMatch[1] === "true";
|
|
1333
|
+
const projectsMatch = content.match(/projects:\s*\n((?:\s+-[\s\S]*?(?=\n\w|\n*$))+)/);
|
|
1334
|
+
if (projectsMatch?.[1]) {
|
|
1335
|
+
const projectsContent = projectsMatch[1];
|
|
1336
|
+
const projectBlocks = projectsContent.split(/\n\s+-\s+name:/).filter(Boolean);
|
|
1337
|
+
for (const block of projectBlocks) {
|
|
1338
|
+
const nameMatch = block.match(/(?:^|\n\s*)name:\s*[\"']?([^\"'\n]+)[\"']?/) || block.match(/^[\"']?([^\"'\n:]+)[\"']?/);
|
|
1339
|
+
const exposeMatch = block.match(/expose:\s*(true|false)/);
|
|
1340
|
+
if (nameMatch?.[1]) {
|
|
1341
|
+
const permissions = parsePermissions(block);
|
|
1342
|
+
config.projects.push({
|
|
1343
|
+
name: nameMatch[1].trim(),
|
|
1344
|
+
expose: exposeMatch ? exposeMatch[1] === "true" : true,
|
|
1345
|
+
permissions
|
|
1346
|
+
});
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
return config;
|
|
1351
|
+
}
|
|
1352
|
+
function parsePermissions(block) {
|
|
1353
|
+
const permissions = { ...DEFAULT_PERMISSIONS };
|
|
1354
|
+
const knowledgeMatch = block.match(/knowledge:\s*(true|false)/);
|
|
1355
|
+
if (knowledgeMatch) permissions.knowledge = knowledgeMatch[1] === "true";
|
|
1356
|
+
const tasksMatch = block.match(/tasks:\s*(true|false)/);
|
|
1357
|
+
if (tasksMatch) permissions.tasks = tasksMatch[1] === "true";
|
|
1358
|
+
const refsMatch = block.match(/refs:\s*(true|false)/);
|
|
1359
|
+
if (refsMatch) permissions.refs = refsMatch[1] === "true";
|
|
1360
|
+
return permissions;
|
|
1361
|
+
}
|
|
1362
|
+
function serializeMCPConfig(config) {
|
|
1363
|
+
let content = `# RRCE MCP Hub Configuration
|
|
1364
|
+
# Manages which projects are exposed via MCP
|
|
1365
|
+
|
|
1366
|
+
server:
|
|
1367
|
+
port: ${config.server.port}
|
|
1368
|
+
autoStart: ${config.server.autoStart}
|
|
1369
|
+
|
|
1370
|
+
defaults:
|
|
1371
|
+
includeNew: ${config.defaults.includeNew}
|
|
1372
|
+
permissions:
|
|
1373
|
+
knowledge: ${config.defaults.permissions.knowledge}
|
|
1374
|
+
tasks: ${config.defaults.permissions.tasks}
|
|
1375
|
+
refs: ${config.defaults.permissions.refs}
|
|
1376
|
+
|
|
1377
|
+
projects:
|
|
1378
|
+
`;
|
|
1379
|
+
if (config.projects.length === 0) {
|
|
1380
|
+
content += ' # No projects configured yet. Run "rrce-workflow mcp" to add projects.\n';
|
|
1381
|
+
} else {
|
|
1382
|
+
for (const project of config.projects) {
|
|
1383
|
+
content += ` - name: "${project.name}"
|
|
1384
|
+
expose: ${project.expose}
|
|
1385
|
+
permissions:
|
|
1386
|
+
knowledge: ${project.permissions.knowledge}
|
|
1387
|
+
tasks: ${project.permissions.tasks}
|
|
1388
|
+
refs: ${project.permissions.refs}
|
|
1389
|
+
`;
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
return content;
|
|
1393
|
+
}
|
|
1394
|
+
function setProjectConfig(config, name, expose, permissions) {
|
|
1395
|
+
const existing = config.projects.find((p) => p.name === name);
|
|
1396
|
+
if (existing) {
|
|
1397
|
+
existing.expose = expose;
|
|
1398
|
+
if (permissions) {
|
|
1399
|
+
existing.permissions = { ...existing.permissions, ...permissions };
|
|
1400
|
+
}
|
|
1401
|
+
} else {
|
|
1402
|
+
config.projects.push({
|
|
1403
|
+
name,
|
|
1404
|
+
expose,
|
|
1405
|
+
permissions: permissions ? { ...DEFAULT_PERMISSIONS, ...permissions } : { ...DEFAULT_PERMISSIONS }
|
|
1406
|
+
});
|
|
1407
|
+
}
|
|
1408
|
+
return config;
|
|
1409
|
+
}
|
|
1410
|
+
function isProjectExposed(config, name) {
|
|
1411
|
+
const project = config.projects.find((p) => p.name === name);
|
|
1412
|
+
if (project) {
|
|
1413
|
+
return project.expose;
|
|
1414
|
+
}
|
|
1415
|
+
return config.defaults.includeNew;
|
|
1416
|
+
}
|
|
1417
|
+
function getProjectPermissions(config, name) {
|
|
1418
|
+
const project = config.projects.find((p) => p.name === name);
|
|
1419
|
+
return project?.permissions ?? config.defaults.permissions;
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
// src/mcp/server.ts
|
|
1423
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
1424
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
1425
|
+
import {
|
|
1426
|
+
CallToolRequestSchema,
|
|
1427
|
+
ListResourcesRequestSchema,
|
|
1428
|
+
ListToolsRequestSchema,
|
|
1429
|
+
ReadResourceRequestSchema,
|
|
1430
|
+
ListPromptsRequestSchema,
|
|
1431
|
+
GetPromptRequestSchema
|
|
1432
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
1433
|
+
import * as fs14 from "fs";
|
|
1434
|
+
import * as path14 from "path";
|
|
1435
|
+
|
|
1436
|
+
// src/mcp/resources.ts
|
|
1437
|
+
import * as fs13 from "fs";
|
|
1438
|
+
import * as path13 from "path";
|
|
1439
|
+
function getExposedProjects() {
|
|
1440
|
+
const config = loadMCPConfig();
|
|
1441
|
+
const allProjects = scanForProjects();
|
|
1442
|
+
return allProjects.filter((project) => isProjectExposed(config, project.name));
|
|
1443
|
+
}
|
|
1444
|
+
function getProjectContext(projectName) {
|
|
1445
|
+
const config = loadMCPConfig();
|
|
1446
|
+
if (!isProjectExposed(config, projectName)) {
|
|
1447
|
+
return null;
|
|
1448
|
+
}
|
|
1449
|
+
const permissions = getProjectPermissions(config, projectName);
|
|
1450
|
+
if (!permissions.knowledge) {
|
|
1451
|
+
return null;
|
|
1452
|
+
}
|
|
1453
|
+
const projects = scanForProjects();
|
|
1454
|
+
const project = projects.find((p) => p.name === projectName);
|
|
1455
|
+
if (!project?.knowledgePath) {
|
|
1456
|
+
return null;
|
|
1457
|
+
}
|
|
1458
|
+
const contextPath = path13.join(project.knowledgePath, "project-context.md");
|
|
1459
|
+
if (!fs13.existsSync(contextPath)) {
|
|
1460
|
+
return null;
|
|
1461
|
+
}
|
|
1462
|
+
return fs13.readFileSync(contextPath, "utf-8");
|
|
1463
|
+
}
|
|
1464
|
+
function getProjectTasks(projectName) {
|
|
1465
|
+
const config = loadMCPConfig();
|
|
1466
|
+
if (!isProjectExposed(config, projectName)) {
|
|
1467
|
+
return [];
|
|
1468
|
+
}
|
|
1469
|
+
const permissions = getProjectPermissions(config, projectName);
|
|
1470
|
+
if (!permissions.tasks) {
|
|
1471
|
+
return [];
|
|
1472
|
+
}
|
|
1473
|
+
const projects = scanForProjects();
|
|
1474
|
+
const project = projects.find((p) => p.name === projectName);
|
|
1475
|
+
if (!project?.tasksPath || !fs13.existsSync(project.tasksPath)) {
|
|
1476
|
+
return [];
|
|
1477
|
+
}
|
|
1478
|
+
const tasks = [];
|
|
1479
|
+
try {
|
|
1480
|
+
const taskDirs = fs13.readdirSync(project.tasksPath, { withFileTypes: true });
|
|
1481
|
+
for (const dir of taskDirs) {
|
|
1482
|
+
if (!dir.isDirectory()) continue;
|
|
1483
|
+
const metaPath = path13.join(project.tasksPath, dir.name, "meta.json");
|
|
1484
|
+
if (fs13.existsSync(metaPath)) {
|
|
1485
|
+
try {
|
|
1486
|
+
const meta = JSON.parse(fs13.readFileSync(metaPath, "utf-8"));
|
|
1487
|
+
tasks.push(meta);
|
|
1488
|
+
} catch {
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
} catch {
|
|
1493
|
+
}
|
|
1494
|
+
return tasks;
|
|
1495
|
+
}
|
|
1496
|
+
function searchKnowledge(query) {
|
|
1497
|
+
const config = loadMCPConfig();
|
|
1498
|
+
const projects = getExposedProjects();
|
|
1499
|
+
const results = [];
|
|
1500
|
+
const queryLower = query.toLowerCase();
|
|
1501
|
+
for (const project of projects) {
|
|
1502
|
+
const permissions = getProjectPermissions(config, project.name);
|
|
1503
|
+
if (!permissions.knowledge || !project.knowledgePath) continue;
|
|
1504
|
+
try {
|
|
1505
|
+
const files = fs13.readdirSync(project.knowledgePath);
|
|
1506
|
+
for (const file of files) {
|
|
1507
|
+
if (!file.endsWith(".md")) continue;
|
|
1508
|
+
const filePath = path13.join(project.knowledgePath, file);
|
|
1509
|
+
const content = fs13.readFileSync(filePath, "utf-8");
|
|
1510
|
+
const lines = content.split("\n");
|
|
1511
|
+
const matches = [];
|
|
1512
|
+
for (const line of lines) {
|
|
1513
|
+
if (line.toLowerCase().includes(queryLower)) {
|
|
1514
|
+
matches.push(line.trim());
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
if (matches.length > 0) {
|
|
1518
|
+
results.push({
|
|
1519
|
+
project: project.name,
|
|
1520
|
+
file,
|
|
1521
|
+
matches: matches.slice(0, 5)
|
|
1522
|
+
// Limit to 5 matches per file
|
|
1523
|
+
});
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
} catch {
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
return results;
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
// src/mcp/prompts.ts
|
|
1533
|
+
var AGENT_PROMPTS = [
|
|
1534
|
+
{
|
|
1535
|
+
name: "init",
|
|
1536
|
+
description: "Initialize project context by analyzing codebase structure, tech stack, and conventions",
|
|
1537
|
+
file: "init.md",
|
|
1538
|
+
arguments: [
|
|
1539
|
+
{ name: "PROJECT_NAME", description: "Project name (optional, auto-detected if omitted)", required: false }
|
|
1540
|
+
]
|
|
1541
|
+
},
|
|
1542
|
+
{
|
|
1543
|
+
name: "research",
|
|
1544
|
+
description: "Research and clarify requirements for a new task",
|
|
1545
|
+
file: "research_discussion.md",
|
|
1546
|
+
arguments: [
|
|
1547
|
+
{ name: "REQUEST", description: "Description of the task or feature to research", required: true },
|
|
1548
|
+
{ name: "TASK_SLUG", description: "Kebab-case identifier for the task", required: true },
|
|
1549
|
+
{ name: "TITLE", description: "Human-readable title for the task", required: false }
|
|
1550
|
+
]
|
|
1551
|
+
},
|
|
1552
|
+
{
|
|
1553
|
+
name: "plan",
|
|
1554
|
+
description: "Create an actionable execution plan from research findings",
|
|
1555
|
+
file: "planning_orchestrator.md",
|
|
1556
|
+
arguments: [
|
|
1557
|
+
{ name: "TASK_SLUG", description: "Task slug to create plan for", required: true }
|
|
1558
|
+
]
|
|
1559
|
+
},
|
|
1560
|
+
{
|
|
1561
|
+
name: "execute",
|
|
1562
|
+
description: "Implement the planned work with code and tests",
|
|
1563
|
+
file: "executor.md",
|
|
1564
|
+
arguments: [
|
|
1565
|
+
{ name: "TASK_SLUG", description: "Task slug to execute", required: true },
|
|
1566
|
+
{ name: "BRANCH", description: "Git branch reference (optional)", required: false }
|
|
1567
|
+
]
|
|
1568
|
+
},
|
|
1569
|
+
{
|
|
1570
|
+
name: "docs",
|
|
1571
|
+
description: "Generate documentation for completed work",
|
|
1572
|
+
file: "documentation.md",
|
|
1573
|
+
arguments: [
|
|
1574
|
+
{ name: "DOC_TYPE", description: "Type of documentation (api, architecture, runbook, changelog)", required: true },
|
|
1575
|
+
{ name: "TASK_SLUG", description: "Task slug if documenting specific task", required: false }
|
|
1576
|
+
]
|
|
1577
|
+
},
|
|
1578
|
+
{
|
|
1579
|
+
name: "sync",
|
|
1580
|
+
description: "Reconcile knowledge base with actual codebase state",
|
|
1581
|
+
file: "sync.md",
|
|
1582
|
+
arguments: [
|
|
1583
|
+
{ name: "SCOPE", description: "Specific path or module to sync (optional)", required: false }
|
|
1584
|
+
]
|
|
1585
|
+
}
|
|
1586
|
+
];
|
|
1587
|
+
|
|
1588
|
+
// src/mcp/server.ts
|
|
1589
|
+
var serverState = { running: false };
|
|
1590
|
+
var mcpServer = null;
|
|
1591
|
+
async function startMCPServer() {
|
|
1592
|
+
const config = loadMCPConfig();
|
|
1593
|
+
mcpServer = new Server(
|
|
1594
|
+
{ name: "rrce-mcp-hub", version: "1.0.0" },
|
|
1595
|
+
{ capabilities: { resources: {}, tools: {}, prompts: {} } }
|
|
1596
|
+
);
|
|
1597
|
+
registerResourceHandlers(mcpServer);
|
|
1598
|
+
registerToolHandlers(mcpServer);
|
|
1599
|
+
registerPromptHandlers(mcpServer);
|
|
1600
|
+
const transport = new StdioServerTransport();
|
|
1601
|
+
await mcpServer.connect(transport);
|
|
1602
|
+
serverState = { running: true, port: config.server.port, pid: process.pid };
|
|
1603
|
+
console.error(`RRCE MCP Hub started (pid: ${process.pid})`);
|
|
1604
|
+
console.error(`Exposed projects: ${getExposedProjects().map((p) => p.name).join(", ")}`);
|
|
1605
|
+
return { port: config.server.port, pid: process.pid };
|
|
1606
|
+
}
|
|
1607
|
+
function registerResourceHandlers(server) {
|
|
1608
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
1609
|
+
const projects = getExposedProjects();
|
|
1610
|
+
const resources = [];
|
|
1611
|
+
resources.push({
|
|
1612
|
+
uri: "rrce://projects",
|
|
1613
|
+
name: "Project List",
|
|
1614
|
+
description: "List of all RRCE projects exposed via MCP",
|
|
1615
|
+
mimeType: "application/json"
|
|
1616
|
+
});
|
|
1617
|
+
for (const project of projects) {
|
|
1618
|
+
const config = loadMCPConfig();
|
|
1619
|
+
const permissions = getProjectPermissions(config, project.name);
|
|
1620
|
+
if (permissions.knowledge) {
|
|
1621
|
+
resources.push({
|
|
1622
|
+
uri: `rrce://projects/${project.name}/context`,
|
|
1623
|
+
name: `${project.name} - Project Context`,
|
|
1624
|
+
description: `Project context and architecture for ${project.name}`,
|
|
1625
|
+
mimeType: "text/markdown"
|
|
1626
|
+
});
|
|
1627
|
+
}
|
|
1628
|
+
if (permissions.tasks) {
|
|
1629
|
+
resources.push({
|
|
1630
|
+
uri: `rrce://projects/${project.name}/tasks`,
|
|
1631
|
+
name: `${project.name} - Tasks`,
|
|
1632
|
+
description: `Task list and status for ${project.name}`,
|
|
1633
|
+
mimeType: "application/json"
|
|
1634
|
+
});
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
return { resources };
|
|
1638
|
+
});
|
|
1639
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
1640
|
+
const { uri } = request.params;
|
|
1641
|
+
if (uri === "rrce://projects") {
|
|
1642
|
+
const projects = getExposedProjects();
|
|
1643
|
+
return {
|
|
1644
|
+
contents: [{
|
|
1645
|
+
uri,
|
|
1646
|
+
mimeType: "application/json",
|
|
1647
|
+
text: JSON.stringify(projects.map((p) => ({ name: p.name, source: p.source, path: p.path })), null, 2)
|
|
1648
|
+
}]
|
|
1649
|
+
};
|
|
1650
|
+
}
|
|
1651
|
+
const projectMatch = uri.match(/^rrce:\/\/projects\/([^/]+)\/(.+)$/);
|
|
1652
|
+
if (projectMatch) {
|
|
1653
|
+
const [, projectName, resourceType] = projectMatch;
|
|
1654
|
+
const content = resourceType === "context" ? getProjectContext(projectName) : JSON.stringify(getProjectTasks(projectName), null, 2);
|
|
1655
|
+
if (content === null) throw new Error(`Resource not found: ${uri}`);
|
|
1656
|
+
return {
|
|
1657
|
+
contents: [{
|
|
1658
|
+
uri,
|
|
1659
|
+
mimeType: resourceType === "tasks" ? "application/json" : "text/markdown",
|
|
1660
|
+
text: content
|
|
1661
|
+
}]
|
|
1662
|
+
};
|
|
1663
|
+
}
|
|
1664
|
+
throw new Error(`Unknown resource: ${uri}`);
|
|
1665
|
+
});
|
|
1666
|
+
}
|
|
1667
|
+
function registerToolHandlers(server) {
|
|
1668
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
1669
|
+
tools: [
|
|
1670
|
+
{
|
|
1671
|
+
name: "search_knowledge",
|
|
1672
|
+
description: "Search across all exposed project knowledge bases",
|
|
1673
|
+
inputSchema: {
|
|
1674
|
+
type: "object",
|
|
1675
|
+
properties: { query: { type: "string", description: "Search query to find in knowledge files" } },
|
|
1676
|
+
required: ["query"]
|
|
1677
|
+
}
|
|
1678
|
+
},
|
|
1679
|
+
{
|
|
1680
|
+
name: "list_projects",
|
|
1681
|
+
description: "List all projects exposed via MCP",
|
|
1682
|
+
inputSchema: { type: "object", properties: {} }
|
|
1683
|
+
},
|
|
1684
|
+
{
|
|
1685
|
+
name: "get_project_context",
|
|
1686
|
+
description: "Get the project context/architecture for a specific project",
|
|
1687
|
+
inputSchema: {
|
|
1688
|
+
type: "object",
|
|
1689
|
+
properties: { project: { type: "string", description: "Name of the project to get context for" } },
|
|
1690
|
+
required: ["project"]
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
]
|
|
1694
|
+
}));
|
|
1695
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
1696
|
+
const { name, arguments: args } = request.params;
|
|
1697
|
+
switch (name) {
|
|
1698
|
+
case "search_knowledge": {
|
|
1699
|
+
const results = searchKnowledge(args.query);
|
|
1700
|
+
return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
|
|
1701
|
+
}
|
|
1702
|
+
case "list_projects": {
|
|
1703
|
+
const projects = getExposedProjects();
|
|
1704
|
+
return {
|
|
1705
|
+
content: [{
|
|
1706
|
+
type: "text",
|
|
1707
|
+
text: JSON.stringify(projects.map((p) => ({ name: p.name, source: p.source, path: p.path })), null, 2)
|
|
1708
|
+
}]
|
|
1709
|
+
};
|
|
1710
|
+
}
|
|
1711
|
+
case "get_project_context": {
|
|
1712
|
+
const context = getProjectContext(args.project);
|
|
1713
|
+
if (!context) {
|
|
1714
|
+
return { content: [{ type: "text", text: `No project context found for "${args.project}"` }], isError: true };
|
|
1715
|
+
}
|
|
1716
|
+
return { content: [{ type: "text", text: context }] };
|
|
1717
|
+
}
|
|
1718
|
+
default:
|
|
1719
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
1720
|
+
}
|
|
1721
|
+
});
|
|
1722
|
+
}
|
|
1723
|
+
function registerPromptHandlers(server) {
|
|
1724
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => ({
|
|
1725
|
+
prompts: AGENT_PROMPTS.map((p) => ({
|
|
1726
|
+
name: p.name,
|
|
1727
|
+
description: p.description,
|
|
1728
|
+
arguments: p.arguments.map((a) => ({ name: a.name, description: a.description, required: a.required }))
|
|
1729
|
+
}))
|
|
1730
|
+
}));
|
|
1731
|
+
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
1732
|
+
const { name, arguments: args } = request.params;
|
|
1733
|
+
const promptDef = AGENT_PROMPTS.find((p) => p.name === name);
|
|
1734
|
+
if (!promptDef) throw new Error(`Unknown prompt: ${name}`);
|
|
1735
|
+
const promptsDir = getAgentCorePromptsDir();
|
|
1736
|
+
const promptPath = path14.join(promptsDir, promptDef.file);
|
|
1737
|
+
if (!fs14.existsSync(promptPath)) throw new Error(`Prompt file not found: ${promptDef.file}`);
|
|
1738
|
+
let promptContent = fs14.readFileSync(promptPath, "utf-8");
|
|
1739
|
+
if (args) {
|
|
1740
|
+
for (const [key, value] of Object.entries(args)) {
|
|
1741
|
+
promptContent = promptContent.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), String(value));
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
return {
|
|
1745
|
+
description: promptDef.description,
|
|
1746
|
+
messages: [{ role: "user", content: { type: "text", text: promptContent } }]
|
|
1747
|
+
};
|
|
1748
|
+
});
|
|
1749
|
+
}
|
|
1750
|
+
function stopMCPServer() {
|
|
1751
|
+
if (mcpServer) {
|
|
1752
|
+
mcpServer.close();
|
|
1753
|
+
mcpServer = null;
|
|
1754
|
+
}
|
|
1755
|
+
serverState = { running: false };
|
|
1756
|
+
console.error("RRCE MCP Hub stopped");
|
|
1757
|
+
}
|
|
1758
|
+
function getMCPServerStatus() {
|
|
1759
|
+
return { ...serverState };
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
// src/mcp/index.ts
|
|
1763
|
+
async function runMCP(subcommand2) {
|
|
1764
|
+
if (subcommand2) {
|
|
1765
|
+
switch (subcommand2) {
|
|
1766
|
+
case "start":
|
|
1767
|
+
await startMCPServer();
|
|
1768
|
+
await new Promise(() => {
|
|
1769
|
+
});
|
|
1770
|
+
return;
|
|
1771
|
+
case "stop":
|
|
1772
|
+
await handleStopServer();
|
|
1773
|
+
return;
|
|
1774
|
+
case "status":
|
|
1775
|
+
await handleShowStatus();
|
|
1776
|
+
return;
|
|
1777
|
+
case "help":
|
|
1778
|
+
showHelp();
|
|
1779
|
+
return;
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
intro3(pc9.bgCyan(pc9.black(" RRCE MCP Hub ")));
|
|
1783
|
+
const globalPathCheck = await ensureMCPGlobalPath();
|
|
1784
|
+
if (!globalPathCheck.configured) {
|
|
1785
|
+
const configured = await handleConfigureGlobalPath();
|
|
1786
|
+
if (!configured) {
|
|
1787
|
+
outro7(pc9.yellow("MCP requires a global storage path. Setup cancelled."));
|
|
1788
|
+
return;
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
let running = true;
|
|
1792
|
+
while (running) {
|
|
1793
|
+
const action = await select5({
|
|
1794
|
+
message: "What would you like to do?",
|
|
1795
|
+
options: [
|
|
1796
|
+
{ value: "status", label: "\u{1F4CB} View project status", hint: "See which projects are exposed" },
|
|
1797
|
+
{ value: "configure", label: "\u2699\uFE0F Configure projects", hint: "Choose which projects to expose" },
|
|
1798
|
+
{ value: "start", label: "\u25B6\uFE0F Start MCP server", hint: "Start the MCP server" },
|
|
1799
|
+
{ value: "stop", label: "\u23F9\uFE0F Stop MCP server", hint: "Stop the running server" },
|
|
1800
|
+
{ value: "help", label: "\u2753 Help", hint: "Learn about MCP Hub" },
|
|
1801
|
+
{ value: "exit", label: "\u21A9 Exit", hint: "Return to shell" }
|
|
1802
|
+
]
|
|
1803
|
+
});
|
|
1804
|
+
if (isCancel8(action)) {
|
|
1805
|
+
cancel7("MCP Hub closed.");
|
|
1806
|
+
return;
|
|
1807
|
+
}
|
|
1808
|
+
switch (action) {
|
|
1809
|
+
case "status":
|
|
1810
|
+
await handleShowStatus();
|
|
1811
|
+
break;
|
|
1812
|
+
case "configure":
|
|
1813
|
+
await handleConfigure();
|
|
1814
|
+
break;
|
|
1815
|
+
case "start":
|
|
1816
|
+
await handleStartServer();
|
|
1817
|
+
running = false;
|
|
1818
|
+
break;
|
|
1819
|
+
case "stop":
|
|
1820
|
+
await handleStopServer();
|
|
1821
|
+
break;
|
|
1822
|
+
case "help":
|
|
1823
|
+
showHelp();
|
|
1824
|
+
break;
|
|
1825
|
+
case "exit":
|
|
1826
|
+
running = false;
|
|
1827
|
+
break;
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
outro7(pc9.green("MCP Hub closed."));
|
|
1831
|
+
}
|
|
1832
|
+
async function handleConfigureGlobalPath() {
|
|
1833
|
+
const { resolveGlobalPath: resolveGlobalPath2 } = await Promise.resolve().then(() => (init_tui_utils(), tui_utils_exports));
|
|
1834
|
+
const fs15 = await import("fs");
|
|
1835
|
+
const path15 = await import("path");
|
|
1836
|
+
note8(
|
|
1837
|
+
`MCP Hub requires a ${pc9.bold("global storage path")} to store its configuration
|
|
1838
|
+
and coordinate across projects.
|
|
1839
|
+
|
|
1840
|
+
Your current setup uses ${pc9.cyan("workspace")} mode, which stores data
|
|
1841
|
+
locally in each project. MCP needs a central location.`,
|
|
1842
|
+
"Global Path Required"
|
|
1843
|
+
);
|
|
1844
|
+
const resolvedPath = await resolveGlobalPath2();
|
|
1845
|
+
if (!resolvedPath) {
|
|
1846
|
+
return false;
|
|
1847
|
+
}
|
|
1848
|
+
try {
|
|
1849
|
+
if (!fs15.existsSync(resolvedPath)) {
|
|
1850
|
+
fs15.mkdirSync(resolvedPath, { recursive: true });
|
|
1851
|
+
}
|
|
1852
|
+
const config = loadMCPConfig();
|
|
1853
|
+
saveMCPConfig(config);
|
|
1854
|
+
note8(
|
|
1855
|
+
`${pc9.green("\u2713")} Global path configured: ${pc9.cyan(resolvedPath)}
|
|
1856
|
+
|
|
1857
|
+
MCP config will be stored at:
|
|
1858
|
+
${path15.join(resolvedPath, "mcp.yaml")}`,
|
|
1859
|
+
"Configuration Saved"
|
|
1860
|
+
);
|
|
1861
|
+
return true;
|
|
1862
|
+
} catch (error) {
|
|
1863
|
+
note8(
|
|
1864
|
+
`${pc9.red("\u2717")} Failed to create directory: ${error instanceof Error ? error.message : String(error)}`,
|
|
1865
|
+
"Error"
|
|
1866
|
+
);
|
|
1867
|
+
return false;
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
async function handleShowStatus() {
|
|
1871
|
+
const s = spinner6();
|
|
1872
|
+
s.start("Loading projects...");
|
|
1873
|
+
const config = loadMCPConfig();
|
|
1874
|
+
const projects = scanForProjects();
|
|
1875
|
+
s.stop("Projects loaded");
|
|
1876
|
+
if (projects.length === 0) {
|
|
1877
|
+
note8('No RRCE projects detected. Run "rrce-workflow" in a project to set it up.', "No Projects");
|
|
1878
|
+
return;
|
|
1879
|
+
}
|
|
1880
|
+
const lines = [
|
|
1881
|
+
`${pc9.bold("Project Status")}`,
|
|
1882
|
+
""
|
|
1883
|
+
];
|
|
1884
|
+
for (const project of projects) {
|
|
1885
|
+
const projectConfig = config.projects.find((p) => p.name === project.name);
|
|
1886
|
+
const isExposed = projectConfig?.expose ?? config.defaults.includeNew;
|
|
1887
|
+
const status = isExposed ? pc9.green("\u2713 exposed") : pc9.dim("\u25CB hidden");
|
|
1888
|
+
const source = pc9.dim(`(${project.source})`);
|
|
1889
|
+
lines.push(` ${status} ${project.name} ${source}`);
|
|
1890
|
+
}
|
|
1891
|
+
lines.push("");
|
|
1892
|
+
lines.push(pc9.dim(`Config: ${getMCPConfigPath()}`));
|
|
1893
|
+
const serverStatus = getMCPServerStatus();
|
|
1894
|
+
if (serverStatus.running) {
|
|
1895
|
+
lines.push(pc9.green(`Server: running on port ${serverStatus.port}`));
|
|
1896
|
+
} else {
|
|
1897
|
+
lines.push(pc9.dim("Server: not running"));
|
|
1898
|
+
}
|
|
1899
|
+
note8(lines.join("\n"), "MCP Hub Status");
|
|
1900
|
+
}
|
|
1901
|
+
async function handleConfigure() {
|
|
1902
|
+
const s = spinner6();
|
|
1903
|
+
s.start("Scanning for projects...");
|
|
1904
|
+
const config = loadMCPConfig();
|
|
1905
|
+
const projects = scanForProjects();
|
|
1906
|
+
s.stop("Projects found");
|
|
1907
|
+
if (projects.length === 0) {
|
|
1908
|
+
note8('No RRCE projects detected. Run "rrce-workflow" in a project to set it up.', "No Projects");
|
|
1909
|
+
return;
|
|
1910
|
+
}
|
|
1911
|
+
const options = projects.map((project) => {
|
|
1912
|
+
const projectConfig = config.projects.find((p) => p.name === project.name);
|
|
1913
|
+
const isExposed = projectConfig?.expose ?? config.defaults.includeNew;
|
|
1914
|
+
return {
|
|
1915
|
+
value: project.name,
|
|
1916
|
+
label: `${project.name} ${pc9.dim(`(${project.source})`)}`,
|
|
1917
|
+
hint: project.dataPath
|
|
1918
|
+
};
|
|
1919
|
+
});
|
|
1920
|
+
const currentlyExposed = projects.filter((p) => {
|
|
1921
|
+
const cfg = config.projects.find((c) => c.name === p.name);
|
|
1922
|
+
return cfg?.expose ?? config.defaults.includeNew;
|
|
1923
|
+
}).map((p) => p.name);
|
|
1924
|
+
const selected = await multiselect3({
|
|
1925
|
+
message: "Select projects to expose via MCP:",
|
|
1926
|
+
options,
|
|
1927
|
+
initialValues: currentlyExposed,
|
|
1928
|
+
required: false
|
|
1929
|
+
});
|
|
1930
|
+
if (isCancel8(selected)) {
|
|
1931
|
+
return;
|
|
1932
|
+
}
|
|
1933
|
+
const selectedNames = selected;
|
|
1934
|
+
for (const project of projects) {
|
|
1935
|
+
const shouldExpose = selectedNames.includes(project.name);
|
|
1936
|
+
setProjectConfig(config, project.name, shouldExpose);
|
|
1937
|
+
}
|
|
1938
|
+
saveMCPConfig(config);
|
|
1939
|
+
const exposedCount = selectedNames.length;
|
|
1940
|
+
note8(
|
|
1941
|
+
`${pc9.green("\u2713")} Configuration saved!
|
|
1942
|
+
|
|
1943
|
+
Exposed projects: ${exposedCount}
|
|
1944
|
+
Hidden projects: ${projects.length - exposedCount}`,
|
|
1945
|
+
"Configuration Updated"
|
|
1946
|
+
);
|
|
1947
|
+
}
|
|
1948
|
+
async function handleStartServer() {
|
|
1949
|
+
const s = spinner6();
|
|
1950
|
+
s.start("Starting MCP server...");
|
|
1951
|
+
try {
|
|
1952
|
+
const result = await startMCPServer();
|
|
1953
|
+
s.stop(pc9.green(`MCP server started on port ${result.port}`));
|
|
1954
|
+
note8(
|
|
1955
|
+
`The MCP server is now running.
|
|
1956
|
+
|
|
1957
|
+
To connect from Claude Desktop, add to your config:
|
|
1958
|
+
` + pc9.cyan(`~/.config/claude/claude_desktop_config.json`),
|
|
1959
|
+
"Server Started"
|
|
1960
|
+
);
|
|
1961
|
+
} catch (error) {
|
|
1962
|
+
s.stop(pc9.red("Failed to start server"));
|
|
1963
|
+
note8(
|
|
1964
|
+
`Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
1965
|
+
"Server Error"
|
|
1966
|
+
);
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
async function handleStopServer() {
|
|
1970
|
+
const status = getMCPServerStatus();
|
|
1971
|
+
if (!status.running) {
|
|
1972
|
+
note8("MCP server is not running.", "Status");
|
|
1973
|
+
return;
|
|
1974
|
+
}
|
|
1975
|
+
const confirmed = await confirm4({
|
|
1976
|
+
message: "Stop the MCP server?",
|
|
1977
|
+
initialValue: true
|
|
1978
|
+
});
|
|
1979
|
+
if (isCancel8(confirmed) || !confirmed) {
|
|
1980
|
+
return;
|
|
1981
|
+
}
|
|
1982
|
+
stopMCPServer();
|
|
1983
|
+
note8(pc9.green("MCP server stopped."), "Server Stopped");
|
|
1984
|
+
}
|
|
1985
|
+
function showHelp() {
|
|
1986
|
+
const help = `
|
|
1987
|
+
${pc9.bold("RRCE MCP Hub")} - Cross-project AI assistant server
|
|
1988
|
+
|
|
1989
|
+
${pc9.bold("ABOUT")}
|
|
1990
|
+
MCP (Model Context Protocol) allows AI assistants like Claude to
|
|
1991
|
+
access your project knowledge in real-time. The RRCE MCP Hub
|
|
1992
|
+
provides a central server that exposes selected projects.
|
|
1993
|
+
|
|
1994
|
+
${pc9.bold("MENU OPTIONS")}
|
|
1995
|
+
${pc9.cyan("View project status")} See which projects are exposed
|
|
1996
|
+
${pc9.cyan("Configure projects")} Choose which projects to expose
|
|
1997
|
+
${pc9.cyan("Start MCP server")} Start the server for AI access
|
|
1998
|
+
${pc9.cyan("Stop MCP server")} Stop the running server
|
|
1999
|
+
|
|
2000
|
+
${pc9.bold("DIRECT COMMANDS")}
|
|
2001
|
+
${pc9.dim("rrce-workflow mcp start")} Start server directly
|
|
2002
|
+
${pc9.dim("rrce-workflow mcp stop")} Stop server directly
|
|
2003
|
+
${pc9.dim("rrce-workflow mcp status")} Show status directly
|
|
2004
|
+
${pc9.dim("rrce-workflow mcp help")} Show this help
|
|
2005
|
+
|
|
2006
|
+
${pc9.bold("CLAUDE DESKTOP SETUP")}
|
|
2007
|
+
Add to ${pc9.cyan("~/.config/claude/claude_desktop_config.json")}:
|
|
2008
|
+
${pc9.dim(`{
|
|
2009
|
+
"mcpServers": {
|
|
2010
|
+
"rrce": {
|
|
2011
|
+
"command": "npx",
|
|
2012
|
+
"args": ["rrce-workflow", "mcp", "start"]
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
}`)}
|
|
2016
|
+
|
|
2017
|
+
${pc9.bold("RESOURCES EXPOSED")}
|
|
2018
|
+
${pc9.cyan("rrce://projects")} List all exposed projects
|
|
2019
|
+
${pc9.cyan("rrce://projects/{name}/context")} Get project context
|
|
2020
|
+
${pc9.cyan("rrce://projects/{name}/tasks")} Get project tasks
|
|
2021
|
+
|
|
2022
|
+
${pc9.bold("PROMPTS (Agent Commands)")}
|
|
2023
|
+
${pc9.cyan("init")} Initialize project context
|
|
2024
|
+
${pc9.cyan("research")} Research requirements for a task
|
|
2025
|
+
${pc9.cyan("plan")} Create execution plan
|
|
2026
|
+
${pc9.cyan("execute")} Implement planned work
|
|
2027
|
+
${pc9.cyan("docs")} Generate documentation
|
|
2028
|
+
${pc9.cyan("sync")} Sync knowledge with codebase
|
|
2029
|
+
`;
|
|
2030
|
+
note8(help.trim(), "Help");
|
|
2031
|
+
}
|
|
2032
|
+
|
|
1192
2033
|
// src/index.ts
|
|
1193
2034
|
var command = process.argv[2];
|
|
2035
|
+
var subcommand = process.argv[3];
|
|
1194
2036
|
if (!command || command === "wizard") {
|
|
1195
2037
|
runWizard();
|
|
2038
|
+
} else if (command === "mcp") {
|
|
2039
|
+
runMCP(subcommand);
|
|
1196
2040
|
} else {
|
|
1197
2041
|
runSelector();
|
|
1198
2042
|
}
|