rrce-workflow 0.2.14 → 0.2.16
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/bin/rrce-workflow.js +3 -33
- package/dist/commands/selector.d.ts +1 -0
- package/dist/commands/selector.js +29 -0
- package/dist/commands/wizard/index.d.ts +1 -0
- package/dist/commands/wizard/index.js +86 -0
- package/dist/commands/wizard/link-flow.d.ts +5 -0
- package/dist/commands/wizard/link-flow.js +97 -0
- package/dist/commands/wizard/setup-flow.d.ts +4 -0
- package/dist/commands/wizard/setup-flow.js +262 -0
- package/dist/commands/wizard/sync-flow.d.ts +4 -0
- package/dist/commands/wizard/sync-flow.js +67 -0
- package/dist/commands/wizard/update-flow.d.ts +4 -0
- package/dist/commands/wizard/update-flow.js +85 -0
- package/dist/commands/wizard/utils.d.ts +9 -0
- package/dist/commands/wizard/utils.js +33 -0
- package/dist/commands/wizard/vscode.d.ts +15 -0
- package/dist/commands/wizard/vscode.js +148 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1181 -0
- package/dist/lib/autocomplete-prompt.d.ts +14 -0
- package/dist/lib/autocomplete-prompt.js +167 -0
- package/dist/lib/detection.d.ts +44 -0
- package/dist/lib/detection.js +185 -0
- package/dist/lib/git.d.ts +12 -0
- package/dist/lib/git.js +37 -0
- package/dist/lib/paths.d.ts +108 -0
- package/dist/lib/paths.js +296 -0
- package/dist/lib/prompts.d.ts +18 -0
- package/dist/lib/prompts.js +62 -0
- package/dist/types/prompt.d.ts +54 -0
- package/dist/types/prompt.js +20 -0
- package/package.json +10 -7
- package/src/commands/selector.ts +0 -42
- package/src/commands/wizard/index.ts +0 -114
- package/src/commands/wizard/link-flow.ts +0 -118
- package/src/commands/wizard/setup-flow.ts +0 -347
- package/src/commands/wizard/sync-flow.ts +0 -93
- package/src/commands/wizard/update-flow.ts +0 -124
- package/src/commands/wizard/utils.ts +0 -38
- package/src/commands/wizard/vscode.ts +0 -197
- package/src/index.ts +0 -11
- package/src/lib/autocomplete-prompt.ts +0 -190
- package/src/lib/detection.ts +0 -235
- package/src/lib/git.ts +0 -37
- package/src/lib/paths.ts +0 -332
- package/src/lib/prompts.ts +0 -73
- package/src/types/prompt.ts +0 -54
package/bin/rrce-workflow.js
CHANGED
|
@@ -1,35 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
// Get the directory of this script
|
|
8
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
-
const __dirname = dirname(__filename);
|
|
10
|
-
|
|
11
|
-
// Path to tsx in node_modules
|
|
12
|
-
const tsxPath = join(__dirname, '..', 'node_modules', '.bin', 'tsx');
|
|
13
|
-
|
|
14
|
-
// Path to the main TypeScript file
|
|
15
|
-
const mainPath = join(__dirname, '..', 'src', 'index.ts');
|
|
16
|
-
|
|
17
|
-
// Get command line arguments (skip node and script path)
|
|
18
|
-
const args = process.argv.slice(2);
|
|
19
|
-
|
|
20
|
-
// Spawn tsx with the main file and pass through all arguments
|
|
21
|
-
const child = spawn(tsxPath, [mainPath, ...args], {
|
|
22
|
-
stdio: 'inherit',
|
|
23
|
-
cwd: process.cwd(),
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
// Exit with the same code as the child process
|
|
27
|
-
child.on('exit', (code) => {
|
|
28
|
-
process.exit(code ?? 0);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
// Forward signals
|
|
32
|
-
child.on('error', (err) => {
|
|
33
|
-
console.error('Failed to start:', err.message);
|
|
34
|
-
process.exit(1);
|
|
35
|
-
});
|
|
3
|
+
// This is the compiled entry point for the CLI
|
|
4
|
+
// The source is in src/index.ts, compiled to dist/index.js
|
|
5
|
+
import('../dist/index.js');
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runSelector(): Promise<void>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { intro, select, note, cancel, isCancel, outro } from '@clack/prompts';
|
|
2
|
+
import pc from 'picocolors';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { loadPromptsFromDir, getAgentCorePromptsDir } from '../lib/prompts';
|
|
5
|
+
export async function runSelector() {
|
|
6
|
+
const workspaceName = path.basename(process.cwd());
|
|
7
|
+
intro(pc.cyan(pc.inverse(` RRCE-Workflow | ${workspaceName} `)));
|
|
8
|
+
const prompts = loadPromptsFromDir(getAgentCorePromptsDir());
|
|
9
|
+
if (prompts.length === 0) {
|
|
10
|
+
cancel('No agents found. Run `rrce-workflow` to set up.');
|
|
11
|
+
process.exit(0);
|
|
12
|
+
}
|
|
13
|
+
const selection = await select({
|
|
14
|
+
message: 'Select an agent:',
|
|
15
|
+
options: prompts.map(p => ({
|
|
16
|
+
value: p,
|
|
17
|
+
label: p.frontmatter.name,
|
|
18
|
+
hint: p.frontmatter.description
|
|
19
|
+
})),
|
|
20
|
+
});
|
|
21
|
+
if (isCancel(selection)) {
|
|
22
|
+
cancel('Selection cancelled.');
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
25
|
+
const prompt = selection;
|
|
26
|
+
note(`Use this agent in your IDE by invoking:
|
|
27
|
+
${pc.bold(pc.cyan(`@${prompt.frontmatter.name}`))}`, 'Agent Selected');
|
|
28
|
+
outro('Done');
|
|
29
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runWizard(): Promise<void>;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { intro, select, spinner, note, outro, isCancel } from '@clack/prompts';
|
|
2
|
+
import pc from 'picocolors';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import { getGitUser } from '../../lib/git';
|
|
5
|
+
import { detectWorkspaceRoot, getWorkspaceName, listGlobalProjects, getLocalWorkspacePath, getConfigPath } from '../../lib/paths';
|
|
6
|
+
// Import flows
|
|
7
|
+
import { runSetupFlow } from './setup-flow';
|
|
8
|
+
import { runLinkProjectsFlow } from './link-flow';
|
|
9
|
+
import { runSyncToGlobalFlow } from './sync-flow';
|
|
10
|
+
import { runUpdateFlow } from './update-flow';
|
|
11
|
+
export async function runWizard() {
|
|
12
|
+
intro(pc.cyan(pc.inverse(' RRCE-Workflow Setup ')));
|
|
13
|
+
const s = spinner();
|
|
14
|
+
s.start('Detecting environment');
|
|
15
|
+
const workspacePath = detectWorkspaceRoot();
|
|
16
|
+
const workspaceName = getWorkspaceName(workspacePath);
|
|
17
|
+
const gitUser = getGitUser();
|
|
18
|
+
await new Promise(r => setTimeout(r, 800)); // Dramatic pause
|
|
19
|
+
s.stop('Environment detected');
|
|
20
|
+
note(`Git User: ${pc.bold(gitUser || '(not found)')}
|
|
21
|
+
Workspace: ${pc.bold(workspaceName)}`, 'Context');
|
|
22
|
+
// Check for existing projects in global storage
|
|
23
|
+
const existingProjects = listGlobalProjects(workspaceName);
|
|
24
|
+
// Check if already configured (using getConfigPath for new/legacy support)
|
|
25
|
+
const configFilePath = getConfigPath(workspacePath);
|
|
26
|
+
const isAlreadyConfigured = fs.existsSync(configFilePath);
|
|
27
|
+
// Check current storage mode from config
|
|
28
|
+
let currentStorageMode = null;
|
|
29
|
+
if (isAlreadyConfigured) {
|
|
30
|
+
try {
|
|
31
|
+
const configContent = fs.readFileSync(configFilePath, 'utf-8');
|
|
32
|
+
const modeMatch = configContent.match(/mode:\s*(global|workspace|both)/);
|
|
33
|
+
currentStorageMode = modeMatch?.[1] ?? null;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// Ignore parse errors
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// Check if workspace has local data that could be synced
|
|
40
|
+
const localDataPath = getLocalWorkspacePath(workspacePath);
|
|
41
|
+
const hasLocalData = fs.existsSync(localDataPath);
|
|
42
|
+
// If already configured, show menu
|
|
43
|
+
if (isAlreadyConfigured) {
|
|
44
|
+
const menuOptions = [];
|
|
45
|
+
// Add link option if other projects exist
|
|
46
|
+
if (existingProjects.length > 0) {
|
|
47
|
+
menuOptions.push({
|
|
48
|
+
value: 'link',
|
|
49
|
+
label: 'Link other project knowledge',
|
|
50
|
+
hint: `${existingProjects.length} projects available`
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
// Add sync to global option if using workspace-only mode
|
|
54
|
+
if (currentStorageMode === 'workspace' && hasLocalData) {
|
|
55
|
+
menuOptions.push({
|
|
56
|
+
value: 'sync-global',
|
|
57
|
+
label: 'Sync to global storage',
|
|
58
|
+
hint: 'Share knowledge with other projects'
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
menuOptions.push({ value: 'update', label: 'Update from package', hint: 'Get latest prompts & templates' });
|
|
62
|
+
menuOptions.push({ value: 'exit', label: 'Exit' });
|
|
63
|
+
const action = await select({
|
|
64
|
+
message: 'This workspace is already configured. What would you like to do?',
|
|
65
|
+
options: menuOptions,
|
|
66
|
+
});
|
|
67
|
+
if (isCancel(action) || action === 'exit') {
|
|
68
|
+
outro('Exited.');
|
|
69
|
+
process.exit(0);
|
|
70
|
+
}
|
|
71
|
+
if (action === 'link') {
|
|
72
|
+
await runLinkProjectsFlow(workspacePath, workspaceName, existingProjects);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (action === 'sync-global') {
|
|
76
|
+
await runSyncToGlobalFlow(workspacePath, workspaceName);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (action === 'update') {
|
|
80
|
+
await runUpdateFlow(workspacePath, workspaceName, currentStorageMode);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// Run full setup flow for new workspaces
|
|
85
|
+
await runSetupFlow(workspacePath, workspaceName, existingProjects);
|
|
86
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run the link-only flow for adding other project knowledge to an existing workspace
|
|
3
|
+
* Now supports detecting workspace-scoped sibling projects, not just global storage
|
|
4
|
+
*/
|
|
5
|
+
export declare function runLinkProjectsFlow(workspacePath: string, workspaceName: string, existingProjects?: string[]): Promise<void>;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { multiselect, spinner, note, outro, cancel, isCancel } from '@clack/prompts';
|
|
2
|
+
import pc from 'picocolors';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import { getEffectiveRRCEHome, getConfigPath } from '../../lib/paths';
|
|
5
|
+
import { generateVSCodeWorkspace } from './vscode';
|
|
6
|
+
import { scanForProjects, getProjectDisplayLabel } from '../../lib/detection';
|
|
7
|
+
/**
|
|
8
|
+
* Run the link-only flow for adding other project knowledge to an existing workspace
|
|
9
|
+
* Now supports detecting workspace-scoped sibling projects, not just global storage
|
|
10
|
+
*/
|
|
11
|
+
export async function runLinkProjectsFlow(workspacePath, workspaceName, existingProjects) {
|
|
12
|
+
// Scan for projects using the new detection system
|
|
13
|
+
const detectedProjects = scanForProjects({
|
|
14
|
+
excludeWorkspace: workspaceName,
|
|
15
|
+
workspacePath: workspacePath,
|
|
16
|
+
scanSiblings: true,
|
|
17
|
+
});
|
|
18
|
+
// If legacy string array is passed, use that instead (for backwards compat)
|
|
19
|
+
const projects = existingProjects
|
|
20
|
+
? existingProjects.map(name => ({ name, source: 'global' }))
|
|
21
|
+
: detectedProjects;
|
|
22
|
+
if (projects.length === 0) {
|
|
23
|
+
outro(pc.yellow('No other projects found. Try setting up another project first.'));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const customGlobalPath = getEffectiveRRCEHome(workspacePath);
|
|
27
|
+
// Build options with source labels
|
|
28
|
+
const linkedProjects = await multiselect({
|
|
29
|
+
message: 'Select projects to link:',
|
|
30
|
+
options: projects.map(project => ({
|
|
31
|
+
value: project.name,
|
|
32
|
+
label: project.name,
|
|
33
|
+
hint: pc.dim(getProjectDisplayLabel(project)),
|
|
34
|
+
})),
|
|
35
|
+
required: true,
|
|
36
|
+
});
|
|
37
|
+
if (isCancel(linkedProjects)) {
|
|
38
|
+
cancel('Cancelled.');
|
|
39
|
+
process.exit(0);
|
|
40
|
+
}
|
|
41
|
+
const selectedProjectNames = linkedProjects;
|
|
42
|
+
if (selectedProjectNames.length === 0) {
|
|
43
|
+
outro('No projects selected.');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
// Get the full DetectedProject objects for selected projects
|
|
47
|
+
const selectedProjects = projects.filter(p => selectedProjectNames.includes(p.name));
|
|
48
|
+
const s = spinner();
|
|
49
|
+
s.start('Linking projects');
|
|
50
|
+
// Update config.yaml with linked projects
|
|
51
|
+
const configFilePath = getConfigPath(workspacePath);
|
|
52
|
+
let configContent = fs.readFileSync(configFilePath, 'utf-8');
|
|
53
|
+
// Check if linked_projects section exists
|
|
54
|
+
if (configContent.includes('linked_projects:')) {
|
|
55
|
+
// Append to existing section - find and update
|
|
56
|
+
const lines = configContent.split('\n');
|
|
57
|
+
const linkedIndex = lines.findIndex(l => l.trim() === 'linked_projects:');
|
|
58
|
+
if (linkedIndex !== -1) {
|
|
59
|
+
// Find where to insert new projects (after existing ones)
|
|
60
|
+
let insertIndex = linkedIndex + 1;
|
|
61
|
+
while (insertIndex < lines.length && lines[insertIndex]?.startsWith(' - ')) {
|
|
62
|
+
insertIndex++;
|
|
63
|
+
}
|
|
64
|
+
// Add new projects that aren't already there
|
|
65
|
+
for (const name of selectedProjectNames) {
|
|
66
|
+
if (!configContent.includes(` - ${name}`)) {
|
|
67
|
+
lines.splice(insertIndex, 0, ` - ${name}`);
|
|
68
|
+
insertIndex++;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
configContent = lines.join('\n');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
// Add new linked_projects section
|
|
76
|
+
configContent += `\nlinked_projects:\n`;
|
|
77
|
+
selectedProjectNames.forEach(name => {
|
|
78
|
+
configContent += ` - ${name}\n`;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
fs.writeFileSync(configFilePath, configContent);
|
|
82
|
+
// Update VSCode workspace file with full project info (includes refs, tasks)
|
|
83
|
+
generateVSCodeWorkspace(workspacePath, workspaceName, selectedProjects, customGlobalPath);
|
|
84
|
+
s.stop('Projects linked');
|
|
85
|
+
// Show summary with project sources
|
|
86
|
+
const workspaceFile = `${workspaceName}.code-workspace`;
|
|
87
|
+
const summary = [
|
|
88
|
+
`Linked projects:`,
|
|
89
|
+
...selectedProjects.map(p => ` ✓ ${p.name} ${pc.dim(`(${p.source})`)}`),
|
|
90
|
+
``,
|
|
91
|
+
`Workspace file: ${pc.cyan(workspaceFile)}`,
|
|
92
|
+
``,
|
|
93
|
+
pc.dim('Includes: knowledge, refs, tasks folders'),
|
|
94
|
+
];
|
|
95
|
+
note(summary.join('\n'), 'Link Summary');
|
|
96
|
+
outro(pc.green(`✓ Projects linked! Open ${pc.bold(workspaceFile)} in VSCode to access linked knowledge.`));
|
|
97
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { group, select, multiselect, confirm, spinner, note, outro, cancel, isCancel } from '@clack/prompts';
|
|
2
|
+
import pc from 'picocolors';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import { ensureDir, getAgentPromptPath, syncMetadataToAll, copyDirToAllStoragePaths, checkWriteAccess, getDefaultRRCEHome } from '../../lib/paths';
|
|
6
|
+
import { loadPromptsFromDir, getAgentCorePromptsDir, getAgentCoreDir } from '../../lib/prompts';
|
|
7
|
+
import { copyPromptsToDir } from './utils';
|
|
8
|
+
import { generateVSCodeWorkspace } from './vscode';
|
|
9
|
+
import { directoryAutocomplete, isCancel as isAutocompleteCancel } from '../../lib/autocomplete-prompt';
|
|
10
|
+
/**
|
|
11
|
+
* Run the full setup flow for new workspaces
|
|
12
|
+
*/
|
|
13
|
+
export async function runSetupFlow(workspacePath, workspaceName, existingProjects) {
|
|
14
|
+
const s = spinner();
|
|
15
|
+
// Full setup flow
|
|
16
|
+
const config = await group({
|
|
17
|
+
storageMode: () => select({
|
|
18
|
+
message: 'Where should workflow data be stored?',
|
|
19
|
+
options: [
|
|
20
|
+
{ value: 'global', label: 'Global (~/.rrce-workflow/)' },
|
|
21
|
+
{ value: 'workspace', label: 'Workspace (.rrce-workflow/)' },
|
|
22
|
+
{ value: 'both', label: 'Both' },
|
|
23
|
+
],
|
|
24
|
+
initialValue: 'global',
|
|
25
|
+
}),
|
|
26
|
+
tools: () => multiselect({
|
|
27
|
+
message: 'Which AI tools do you use?',
|
|
28
|
+
options: [
|
|
29
|
+
{ value: 'copilot', label: 'GitHub Copilot', hint: 'VSCode' },
|
|
30
|
+
{ value: 'antigravity', label: 'Antigravity IDE' },
|
|
31
|
+
],
|
|
32
|
+
required: false,
|
|
33
|
+
}),
|
|
34
|
+
linkedProjects: () => {
|
|
35
|
+
// Only show if there are other projects to link
|
|
36
|
+
if (existingProjects.length === 0) {
|
|
37
|
+
return Promise.resolve([]);
|
|
38
|
+
}
|
|
39
|
+
return multiselect({
|
|
40
|
+
message: 'Link knowledge from other projects?',
|
|
41
|
+
options: existingProjects.map(name => ({
|
|
42
|
+
value: name,
|
|
43
|
+
label: name,
|
|
44
|
+
hint: `~/.rrce-workflow/workspaces/${name}/knowledge`
|
|
45
|
+
})),
|
|
46
|
+
required: false,
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
confirm: () => confirm({
|
|
50
|
+
message: 'Create configuration?',
|
|
51
|
+
initialValue: true,
|
|
52
|
+
}),
|
|
53
|
+
}, {
|
|
54
|
+
onCancel: () => {
|
|
55
|
+
cancel('Setup process cancelled.');
|
|
56
|
+
process.exit(0);
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
if (!config.confirm) {
|
|
60
|
+
outro('Setup cancelled by user.');
|
|
61
|
+
process.exit(0);
|
|
62
|
+
}
|
|
63
|
+
// Determine global path for 'global' or 'both' modes
|
|
64
|
+
let customGlobalPath;
|
|
65
|
+
if (config.storageMode === 'global' || config.storageMode === 'both') {
|
|
66
|
+
customGlobalPath = await resolveGlobalPath();
|
|
67
|
+
if (!customGlobalPath) {
|
|
68
|
+
cancel('Setup cancelled - no writable global path available.');
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
s.start('Generating configuration');
|
|
73
|
+
try {
|
|
74
|
+
await generateConfiguration({
|
|
75
|
+
storageMode: config.storageMode,
|
|
76
|
+
globalPath: customGlobalPath,
|
|
77
|
+
tools: config.tools,
|
|
78
|
+
linkedProjects: config.linkedProjects,
|
|
79
|
+
}, workspacePath, workspaceName);
|
|
80
|
+
s.stop('Configuration generated');
|
|
81
|
+
// Show summary
|
|
82
|
+
const dataPaths = getDataPaths(config.storageMode, workspaceName, workspacePath, customGlobalPath);
|
|
83
|
+
const summary = [
|
|
84
|
+
`Storage: ${config.storageMode === 'both' ? 'global + workspace' : config.storageMode}`,
|
|
85
|
+
];
|
|
86
|
+
if (customGlobalPath && customGlobalPath !== getDefaultRRCEHome()) {
|
|
87
|
+
summary.push(`Global path: ${pc.cyan(customGlobalPath)}`);
|
|
88
|
+
}
|
|
89
|
+
if (dataPaths.length > 0) {
|
|
90
|
+
summary.push(`Data paths:`);
|
|
91
|
+
dataPaths.forEach(p => summary.push(` - ${p}`));
|
|
92
|
+
}
|
|
93
|
+
const selectedTools = config.tools;
|
|
94
|
+
if (selectedTools.length > 0) {
|
|
95
|
+
summary.push(`Tools: ${selectedTools.join(', ')}`);
|
|
96
|
+
}
|
|
97
|
+
const linkedProjects = config.linkedProjects;
|
|
98
|
+
if (linkedProjects.length > 0) {
|
|
99
|
+
summary.push(`Linked projects: ${linkedProjects.join(', ')}`);
|
|
100
|
+
summary.push(`Workspace file: ${pc.cyan(`${workspaceName}.code-workspace`)}`);
|
|
101
|
+
}
|
|
102
|
+
note(summary.join('\n'), 'Setup Summary');
|
|
103
|
+
// Show appropriate outro message
|
|
104
|
+
if (linkedProjects.length > 0) {
|
|
105
|
+
outro(pc.green(`✓ Setup complete! Open ${pc.bold(`${workspaceName}.code-workspace`)} in VSCode to access linked knowledge.`));
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
outro(pc.green(`✓ Setup complete! Your agents are ready to use.`));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
s.stop('Error occurred');
|
|
113
|
+
cancel(`Failed to setup: ${error instanceof Error ? error.message : String(error)}`);
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Resolve global path - always prompt user to choose default or custom
|
|
119
|
+
*/
|
|
120
|
+
async function resolveGlobalPath() {
|
|
121
|
+
const defaultPath = getDefaultRRCEHome();
|
|
122
|
+
const isDefaultWritable = checkWriteAccess(defaultPath);
|
|
123
|
+
// Build options
|
|
124
|
+
const options = [];
|
|
125
|
+
// Default option
|
|
126
|
+
options.push({
|
|
127
|
+
value: 'default',
|
|
128
|
+
label: `Default (${defaultPath})`,
|
|
129
|
+
hint: isDefaultWritable ? pc.green('✓ writable') : pc.red('✗ not writable'),
|
|
130
|
+
});
|
|
131
|
+
// Custom option
|
|
132
|
+
options.push({
|
|
133
|
+
value: 'custom',
|
|
134
|
+
label: 'Custom path',
|
|
135
|
+
hint: 'Specify your own directory',
|
|
136
|
+
});
|
|
137
|
+
const choice = await select({
|
|
138
|
+
message: 'Global storage location:',
|
|
139
|
+
options,
|
|
140
|
+
initialValue: isDefaultWritable ? 'default' : 'custom',
|
|
141
|
+
});
|
|
142
|
+
if (isCancel(choice)) {
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
if (choice === 'default') {
|
|
146
|
+
// Verify it's writable
|
|
147
|
+
if (!isDefaultWritable) {
|
|
148
|
+
note(`${pc.yellow('⚠')} Cannot write to default path:\n ${pc.dim(defaultPath)}\n\nThis can happen when running via npx/bunx in restricted environments.\nPlease choose a custom path instead.`, 'Write Access Issue');
|
|
149
|
+
// Recursively ask again
|
|
150
|
+
return resolveGlobalPath();
|
|
151
|
+
}
|
|
152
|
+
return defaultPath;
|
|
153
|
+
}
|
|
154
|
+
// Custom path input with Tab autocomplete
|
|
155
|
+
const suggestedPath = path.join(process.env.HOME || '~', '.local', 'share', 'rrce-workflow');
|
|
156
|
+
const customPath = await directoryAutocomplete({
|
|
157
|
+
message: 'Enter custom global path:',
|
|
158
|
+
initialValue: suggestedPath,
|
|
159
|
+
hint: 'Tab to autocomplete',
|
|
160
|
+
validate: (value) => {
|
|
161
|
+
if (!value.trim()) {
|
|
162
|
+
return 'Path cannot be empty';
|
|
163
|
+
}
|
|
164
|
+
// Expand ~ to home directory
|
|
165
|
+
const expandedPath = value.startsWith('~')
|
|
166
|
+
? value.replace('~', process.env.HOME || '')
|
|
167
|
+
: value;
|
|
168
|
+
if (!checkWriteAccess(expandedPath)) {
|
|
169
|
+
return `Cannot write to ${expandedPath}. Please choose a writable path.`;
|
|
170
|
+
}
|
|
171
|
+
return undefined;
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
if (isAutocompleteCancel(customPath)) {
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
// Path is already expanded by directoryAutocomplete
|
|
178
|
+
return customPath;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Generate configuration files and directories
|
|
182
|
+
*/
|
|
183
|
+
async function generateConfiguration(config, workspacePath, workspaceName) {
|
|
184
|
+
const dataPaths = getDataPaths(config.storageMode, workspaceName, workspacePath, config.globalPath);
|
|
185
|
+
for (const dataPath of dataPaths) {
|
|
186
|
+
ensureDir(dataPath);
|
|
187
|
+
// Create agent metadata subdirectories (data only, no prompts)
|
|
188
|
+
ensureDir(path.join(dataPath, 'knowledge'));
|
|
189
|
+
ensureDir(path.join(dataPath, 'refs'));
|
|
190
|
+
ensureDir(path.join(dataPath, 'tasks'));
|
|
191
|
+
ensureDir(path.join(dataPath, 'templates'));
|
|
192
|
+
}
|
|
193
|
+
// Get the agent-core directory path
|
|
194
|
+
const agentCoreDir = getAgentCoreDir();
|
|
195
|
+
// Sync metadata (knowledge, refs, tasks) from agent-core to all storage locations
|
|
196
|
+
syncMetadataToAll(agentCoreDir, dataPaths);
|
|
197
|
+
// Also copy templates to all storage locations
|
|
198
|
+
copyDirToAllStoragePaths(path.join(agentCoreDir, 'templates'), 'templates', dataPaths);
|
|
199
|
+
// Load prompts for IDE-specific locations
|
|
200
|
+
const prompts = loadPromptsFromDir(getAgentCorePromptsDir());
|
|
201
|
+
// Copy prompts to tool-specific locations (for IDE integration)
|
|
202
|
+
if (config.tools.includes('copilot')) {
|
|
203
|
+
const copilotPath = getAgentPromptPath(workspacePath, 'copilot');
|
|
204
|
+
ensureDir(copilotPath);
|
|
205
|
+
copyPromptsToDir(prompts, copilotPath, '.agent.md');
|
|
206
|
+
}
|
|
207
|
+
if (config.tools.includes('antigravity')) {
|
|
208
|
+
const antigravityPath = getAgentPromptPath(workspacePath, 'antigravity');
|
|
209
|
+
ensureDir(antigravityPath);
|
|
210
|
+
copyPromptsToDir(prompts, antigravityPath, '.md');
|
|
211
|
+
}
|
|
212
|
+
// Create workspace config (inside .rrce-workflow folder)
|
|
213
|
+
const workspaceConfigPath = path.join(workspacePath, '.rrce-workflow', 'config.yaml');
|
|
214
|
+
ensureDir(path.dirname(workspaceConfigPath));
|
|
215
|
+
let configContent = `# RRCE-Workflow Configuration
|
|
216
|
+
version: 1
|
|
217
|
+
|
|
218
|
+
storage:
|
|
219
|
+
mode: ${config.storageMode}`;
|
|
220
|
+
// Add custom global path if different from default
|
|
221
|
+
if (config.globalPath && config.globalPath !== getDefaultRRCEHome()) {
|
|
222
|
+
configContent += `\n globalPath: "${config.globalPath}"`;
|
|
223
|
+
}
|
|
224
|
+
configContent += `
|
|
225
|
+
|
|
226
|
+
project:
|
|
227
|
+
name: "${workspaceName}"
|
|
228
|
+
|
|
229
|
+
tools:
|
|
230
|
+
copilot: ${config.tools.includes('copilot')}
|
|
231
|
+
antigravity: ${config.tools.includes('antigravity')}
|
|
232
|
+
`;
|
|
233
|
+
// Add linked projects if any
|
|
234
|
+
if (config.linkedProjects.length > 0) {
|
|
235
|
+
configContent += `\nlinked_projects:\n`;
|
|
236
|
+
config.linkedProjects.forEach(name => {
|
|
237
|
+
configContent += ` - ${name}\n`;
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
fs.writeFileSync(workspaceConfigPath, configContent);
|
|
241
|
+
// Generate VSCode workspace file if using copilot or has linked projects
|
|
242
|
+
if (config.tools.includes('copilot') || config.linkedProjects.length > 0) {
|
|
243
|
+
generateVSCodeWorkspace(workspacePath, workspaceName, config.linkedProjects, config.globalPath);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Get data paths based on storage mode and custom global path
|
|
248
|
+
*/
|
|
249
|
+
function getDataPaths(mode, workspaceName, workspaceRoot, customGlobalPath) {
|
|
250
|
+
const globalPath = path.join(customGlobalPath || getDefaultRRCEHome(), 'workspaces', workspaceName);
|
|
251
|
+
const workspacePath = path.join(workspaceRoot, '.rrce-workflow');
|
|
252
|
+
switch (mode) {
|
|
253
|
+
case 'global':
|
|
254
|
+
return [globalPath];
|
|
255
|
+
case 'workspace':
|
|
256
|
+
return [workspacePath];
|
|
257
|
+
case 'both':
|
|
258
|
+
return [workspacePath, globalPath];
|
|
259
|
+
default:
|
|
260
|
+
return [globalPath];
|
|
261
|
+
}
|
|
262
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { confirm, spinner, note, outro, cancel, isCancel } from '@clack/prompts';
|
|
2
|
+
import pc from 'picocolors';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import { ensureDir, getLocalWorkspacePath, getEffectiveRRCEHome, getConfigPath } from '../../lib/paths';
|
|
6
|
+
import { copyDirRecursive } from './utils';
|
|
7
|
+
/**
|
|
8
|
+
* Sync workspace knowledge to global storage so other projects can reference it
|
|
9
|
+
*/
|
|
10
|
+
export async function runSyncToGlobalFlow(workspacePath, workspaceName) {
|
|
11
|
+
const localPath = getLocalWorkspacePath(workspacePath);
|
|
12
|
+
const customGlobalPath = getEffectiveRRCEHome(workspacePath);
|
|
13
|
+
const globalPath = path.join(customGlobalPath, 'workspaces', workspaceName);
|
|
14
|
+
// Check what exists locally
|
|
15
|
+
const subdirs = ['knowledge', 'prompts', 'templates', 'tasks', 'refs'];
|
|
16
|
+
const existingDirs = subdirs.filter(dir => fs.existsSync(path.join(localPath, dir)));
|
|
17
|
+
if (existingDirs.length === 0) {
|
|
18
|
+
outro(pc.yellow('No data found in workspace storage to sync.'));
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
// Show what will be synced
|
|
22
|
+
note(`The following will be copied to global storage:\n${existingDirs.map(d => ` • ${d}/`).join('\n')}\n\nDestination: ${pc.cyan(globalPath)}`, 'Sync Preview');
|
|
23
|
+
const shouldSync = await confirm({
|
|
24
|
+
message: 'Proceed with sync to global storage?',
|
|
25
|
+
initialValue: true,
|
|
26
|
+
});
|
|
27
|
+
if (isCancel(shouldSync) || !shouldSync) {
|
|
28
|
+
outro('Sync cancelled.');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const s = spinner();
|
|
32
|
+
s.start('Syncing to global storage');
|
|
33
|
+
try {
|
|
34
|
+
// Ensure global directory exists
|
|
35
|
+
ensureDir(globalPath);
|
|
36
|
+
// Copy each directory
|
|
37
|
+
for (const dir of existingDirs) {
|
|
38
|
+
const srcDir = path.join(localPath, dir);
|
|
39
|
+
const destDir = path.join(globalPath, dir);
|
|
40
|
+
ensureDir(destDir);
|
|
41
|
+
// Copy files recursively
|
|
42
|
+
copyDirRecursive(srcDir, destDir);
|
|
43
|
+
}
|
|
44
|
+
// Update the config to reflect 'both' mode
|
|
45
|
+
const configFilePath = getConfigPath(workspacePath);
|
|
46
|
+
let configContent = fs.readFileSync(configFilePath, 'utf-8');
|
|
47
|
+
configContent = configContent.replace(/mode:\s*workspace/, 'mode: both');
|
|
48
|
+
fs.writeFileSync(configFilePath, configContent);
|
|
49
|
+
s.stop('Sync complete');
|
|
50
|
+
const summary = [
|
|
51
|
+
`Synced directories:`,
|
|
52
|
+
...existingDirs.map(d => ` ✓ ${d}/`),
|
|
53
|
+
``,
|
|
54
|
+
`Global path: ${pc.cyan(globalPath)}`,
|
|
55
|
+
`Storage mode updated to: ${pc.bold('both')}`,
|
|
56
|
+
``,
|
|
57
|
+
`Other projects can now link this knowledge!`,
|
|
58
|
+
];
|
|
59
|
+
note(summary.join('\n'), 'Sync Summary');
|
|
60
|
+
outro(pc.green('✓ Workspace knowledge synced to global storage!'));
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
s.stop('Error occurred');
|
|
64
|
+
cancel(`Failed to sync: ${error instanceof Error ? error.message : String(error)}`);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
}
|