@rigstate/cli 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +5 -0
- package/IMPLEMENTATION.md +239 -0
- package/QUICK_START.md +220 -0
- package/README.md +150 -0
- package/dist/index.cjs +3987 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3964 -0
- package/dist/index.js.map +1 -0
- package/install.sh +15 -0
- package/package.json +53 -0
- package/src/commands/check.ts +329 -0
- package/src/commands/config.ts +81 -0
- package/src/commands/daemon.ts +197 -0
- package/src/commands/env.ts +158 -0
- package/src/commands/fix.ts +140 -0
- package/src/commands/focus.ts +134 -0
- package/src/commands/hooks.ts +163 -0
- package/src/commands/init.ts +282 -0
- package/src/commands/link.ts +45 -0
- package/src/commands/login.ts +35 -0
- package/src/commands/mcp.ts +73 -0
- package/src/commands/nexus.ts +81 -0
- package/src/commands/override.ts +65 -0
- package/src/commands/scan.ts +242 -0
- package/src/commands/sync-rules.ts +191 -0
- package/src/commands/sync.ts +339 -0
- package/src/commands/watch.ts +283 -0
- package/src/commands/work.ts +172 -0
- package/src/daemon/bridge-listener.ts +127 -0
- package/src/daemon/core.ts +184 -0
- package/src/daemon/factory.ts +45 -0
- package/src/daemon/file-watcher.ts +97 -0
- package/src/daemon/guardian-monitor.ts +133 -0
- package/src/daemon/heuristic-engine.ts +203 -0
- package/src/daemon/intervention-protocol.ts +128 -0
- package/src/daemon/telemetry.ts +23 -0
- package/src/daemon/types.ts +18 -0
- package/src/hive/gateway.ts +74 -0
- package/src/hive/protocol.ts +29 -0
- package/src/hive/scrubber.ts +72 -0
- package/src/index.ts +85 -0
- package/src/nexus/council.ts +103 -0
- package/src/nexus/dispatcher.ts +133 -0
- package/src/utils/config.ts +83 -0
- package/src/utils/files.ts +95 -0
- package/src/utils/governance.ts +128 -0
- package/src/utils/logger.ts +66 -0
- package/src/utils/manifest.ts +18 -0
- package/src/utils/rule-engine.ts +292 -0
- package/src/utils/skills-provisioner.ts +153 -0
- package/src/utils/version.ts +1 -0
- package/src/utils/watchdog.ts +215 -0
- package/tsconfig.json +29 -0
- package/tsup.config.ts +11 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import fs from 'fs/promises';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import ora from 'ora';
|
|
6
|
+
import { execSync } from 'child_process';
|
|
7
|
+
import { loadManifest } from '../utils/manifest.js';
|
|
8
|
+
import { getApiUrl, getApiKey, setProjectId } from '../utils/config.js';
|
|
9
|
+
import axios from 'axios';
|
|
10
|
+
|
|
11
|
+
export function createInitCommand() {
|
|
12
|
+
return new Command('init')
|
|
13
|
+
.description('Initialize or link a Rigstate project (interactive mode available)')
|
|
14
|
+
.argument('[project-id]', 'ID of the project to link (optional, prompts if not provided)')
|
|
15
|
+
.option('-f, --force', 'Overwrite existing .cursorrules file')
|
|
16
|
+
.option('--rules-only', 'Only regenerate .cursorrules without interactive setup')
|
|
17
|
+
.action(async (projectIdArg, options) => {
|
|
18
|
+
const spinner = ora('Initializing Rigstate project...').start();
|
|
19
|
+
|
|
20
|
+
let apiKey: string;
|
|
21
|
+
try {
|
|
22
|
+
apiKey = getApiKey();
|
|
23
|
+
} catch (e) {
|
|
24
|
+
spinner.fail(chalk.red('Not authenticated. Run "rigstate login" first.'));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const apiUrl = getApiUrl();
|
|
29
|
+
let projectId = projectIdArg;
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
// If --rules-only, just regenerate rules from existing manifest
|
|
33
|
+
if (options.rulesOnly) {
|
|
34
|
+
const manifest = await loadManifest();
|
|
35
|
+
if (!manifest) {
|
|
36
|
+
spinner.fail('No .rigstate manifest found. Run "rigstate init" first.');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
projectId = manifest.project_id;
|
|
40
|
+
await generateRules(apiUrl, apiKey, projectId, options.force, spinner);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Interactive mode if no project ID provided
|
|
45
|
+
if (!projectId) {
|
|
46
|
+
spinner.stop();
|
|
47
|
+
|
|
48
|
+
// Dynamic import for inquirer
|
|
49
|
+
const inquirer = (await import('inquirer')).default;
|
|
50
|
+
|
|
51
|
+
spinner.start('Fetching your projects...');
|
|
52
|
+
|
|
53
|
+
// Fetch projects via API
|
|
54
|
+
let projects: any[] = [];
|
|
55
|
+
try {
|
|
56
|
+
const projectsResponse = await axios.get(`${apiUrl}/api/v1/projects`, {
|
|
57
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
58
|
+
});
|
|
59
|
+
if (projectsResponse.data.success) {
|
|
60
|
+
projects = projectsResponse.data.data.projects || [];
|
|
61
|
+
}
|
|
62
|
+
} catch (e: any) {
|
|
63
|
+
// API might not exist yet - fallback to manual entry
|
|
64
|
+
spinner.info('Projects API not available. Using manual entry mode.');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
spinner.stop();
|
|
68
|
+
|
|
69
|
+
if (projects.length === 0) {
|
|
70
|
+
// Fallback: Ask for project ID manually
|
|
71
|
+
const { manualProjectId } = await inquirer.prompt([
|
|
72
|
+
{
|
|
73
|
+
type: 'input',
|
|
74
|
+
name: 'manualProjectId',
|
|
75
|
+
message: 'Enter Project ID (from Rigstate dashboard):',
|
|
76
|
+
validate: (input: string) => input.trim() ? true : 'Project ID is required'
|
|
77
|
+
}
|
|
78
|
+
]);
|
|
79
|
+
projectId = manualProjectId;
|
|
80
|
+
} else {
|
|
81
|
+
// Build choices from fetched projects
|
|
82
|
+
const choices: any[] = [
|
|
83
|
+
{ name: 'ā Create New Project', value: 'NEW' },
|
|
84
|
+
new inquirer.Separator()
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
projects.forEach((p: any) => {
|
|
88
|
+
choices.push({
|
|
89
|
+
name: `[${p.organization_name || 'Personal'}] ${p.name} (${p.status})`,
|
|
90
|
+
value: p.id
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const { selectedId } = await inquirer.prompt([
|
|
95
|
+
{
|
|
96
|
+
type: 'list',
|
|
97
|
+
name: 'selectedId',
|
|
98
|
+
message: 'Select a project to link:',
|
|
99
|
+
choices: choices,
|
|
100
|
+
pageSize: 15
|
|
101
|
+
}
|
|
102
|
+
]);
|
|
103
|
+
|
|
104
|
+
if (selectedId === 'NEW') {
|
|
105
|
+
// Create new project flow
|
|
106
|
+
const { newName } = await inquirer.prompt([
|
|
107
|
+
{
|
|
108
|
+
type: 'input',
|
|
109
|
+
name: 'newName',
|
|
110
|
+
message: 'Enter project name:',
|
|
111
|
+
validate: (input: string) => input.trim() ? true : 'Name is required'
|
|
112
|
+
}
|
|
113
|
+
]);
|
|
114
|
+
|
|
115
|
+
// Fetch organizations (with fallback)
|
|
116
|
+
spinner.start('Fetching organizations...');
|
|
117
|
+
let orgs: any[] = [];
|
|
118
|
+
try {
|
|
119
|
+
const orgsResponse = await axios.get(`${apiUrl}/api/v1/organizations`, {
|
|
120
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
121
|
+
});
|
|
122
|
+
orgs = orgsResponse.data.data?.organizations || [];
|
|
123
|
+
} catch (e) {
|
|
124
|
+
// API might not exist - skip org selection
|
|
125
|
+
}
|
|
126
|
+
spinner.stop();
|
|
127
|
+
|
|
128
|
+
let selectedOrgId = orgs[0]?.id;
|
|
129
|
+
|
|
130
|
+
if (orgs.length > 1) {
|
|
131
|
+
const { orgId } = await inquirer.prompt([
|
|
132
|
+
{
|
|
133
|
+
type: 'list',
|
|
134
|
+
name: 'orgId',
|
|
135
|
+
message: 'Which organization does this belong to?',
|
|
136
|
+
choices: orgs.map((org: any) => ({
|
|
137
|
+
name: `${org.name} (${org.role || 'member'})`,
|
|
138
|
+
value: org.id
|
|
139
|
+
}))
|
|
140
|
+
}
|
|
141
|
+
]);
|
|
142
|
+
selectedOrgId = orgId;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (!selectedOrgId) {
|
|
146
|
+
console.log(chalk.yellow('No organization available. Please create the project via the Rigstate dashboard.'));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Create project via API
|
|
151
|
+
spinner.start('Creating new project...');
|
|
152
|
+
try {
|
|
153
|
+
const createResponse = await axios.post(`${apiUrl}/api/v1/projects`, {
|
|
154
|
+
name: newName,
|
|
155
|
+
organization_id: selectedOrgId
|
|
156
|
+
}, {
|
|
157
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
if (!createResponse.data.success) {
|
|
161
|
+
spinner.fail(chalk.red('Failed to create project: ' + createResponse.data.error));
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
projectId = createResponse.data.data.project.id;
|
|
166
|
+
spinner.succeed(chalk.green(`Created new project: ${newName}`));
|
|
167
|
+
} catch (e: any) {
|
|
168
|
+
spinner.fail(chalk.red('Project creation API not available. Please create via dashboard.'));
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
projectId = selectedId;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
spinner.start(`Linking to project ID: ${projectId}...`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Core link logic
|
|
180
|
+
// Save project ID to config
|
|
181
|
+
setProjectId(projectId);
|
|
182
|
+
|
|
183
|
+
// Write local manifest
|
|
184
|
+
const manifestPath = path.join(process.cwd(), '.rigstate');
|
|
185
|
+
const manifestContent = {
|
|
186
|
+
project_id: projectId,
|
|
187
|
+
last_linked: new Date().toISOString(),
|
|
188
|
+
api_url: apiUrl
|
|
189
|
+
};
|
|
190
|
+
await fs.writeFile(manifestPath, JSON.stringify(manifestContent, null, 2), 'utf-8');
|
|
191
|
+
|
|
192
|
+
// Initialize git if needed
|
|
193
|
+
try {
|
|
194
|
+
await fs.access('.git');
|
|
195
|
+
} catch {
|
|
196
|
+
spinner.text = 'Initializing git repository...';
|
|
197
|
+
execSync('git init', { stdio: 'ignore' });
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
spinner.succeed(chalk.green(`ā
Linked to project: ${projectId}`));
|
|
201
|
+
|
|
202
|
+
// Generate rules
|
|
203
|
+
await generateRules(apiUrl, apiKey, projectId, options.force, spinner);
|
|
204
|
+
|
|
205
|
+
console.log('');
|
|
206
|
+
console.log(chalk.blue('Next steps:'));
|
|
207
|
+
console.log(chalk.dim(' rigstate sync - Sync roadmap and context'));
|
|
208
|
+
console.log(chalk.dim(' rigstate watch - Start development loop'));
|
|
209
|
+
console.log(chalk.dim(' rigstate focus - Get current task'));
|
|
210
|
+
|
|
211
|
+
} catch (e: any) {
|
|
212
|
+
spinner.fail(chalk.red('Initialization failed: ' + e.message));
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async function generateRules(apiUrl: string, apiKey: string, projectId: string, force: boolean, spinner: any) {
|
|
218
|
+
spinner.start('Generating AI rules (MDC + AGENTS.md)...');
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
const response = await axios.post(`${apiUrl}/api/v1/rules/generate`, {
|
|
222
|
+
project_id: projectId
|
|
223
|
+
}, {
|
|
224
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
if (response.data.success || response.data.files) {
|
|
228
|
+
const files = response.data.files || [];
|
|
229
|
+
|
|
230
|
+
if (files.length === 0 && response.data.rules) {
|
|
231
|
+
// Fallback to legacy mono-file if no multiple files returned
|
|
232
|
+
const rulesPath = path.join(process.cwd(), '.cursorrules');
|
|
233
|
+
await fs.writeFile(rulesPath, response.data.rules, 'utf-8');
|
|
234
|
+
spinner.succeed(chalk.green('ā Generated .cursorrules (legacy mode)'));
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
for (const file of files) {
|
|
239
|
+
const targetPath = path.join(process.cwd(), file.path);
|
|
240
|
+
const targetDir = path.dirname(targetPath);
|
|
241
|
+
|
|
242
|
+
// Ensure directory exists
|
|
243
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
244
|
+
|
|
245
|
+
// Check if exists and force
|
|
246
|
+
try {
|
|
247
|
+
await fs.access(targetPath);
|
|
248
|
+
if (!force && !file.path.startsWith('.cursor/rules/')) {
|
|
249
|
+
console.log(chalk.dim(` ${file.path} already exists. Skipping.`));
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
} catch {
|
|
253
|
+
// File doesn't exist, proceed
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
await fs.writeFile(targetPath, file.content, 'utf-8');
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Cleanup legacy .cursorrules if we have new files and aren't inhibited
|
|
260
|
+
if (files.length > 0) {
|
|
261
|
+
const legacyPath = path.join(process.cwd(), '.cursorrules');
|
|
262
|
+
try {
|
|
263
|
+
const stats = await fs.stat(legacyPath);
|
|
264
|
+
if (stats.isFile()) {
|
|
265
|
+
await fs.rename(legacyPath, `${legacyPath}.bak`);
|
|
266
|
+
console.log(chalk.dim(' Moved legacy .cursorrules to .cursorrules.bak'));
|
|
267
|
+
}
|
|
268
|
+
} catch (e) {
|
|
269
|
+
// Ignore if legacy file doesn't exist
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
spinner.succeed(chalk.green(`ā Generated ${files.length} rule files (v${response.data.version || '3.0'})`));
|
|
274
|
+
} else {
|
|
275
|
+
spinner.info(chalk.dim(' Rules generation skipped (API response invalid)'));
|
|
276
|
+
}
|
|
277
|
+
} catch (e: any) {
|
|
278
|
+
spinner.info(chalk.dim(` Rules generation failed: ${e.message}`));
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import os from 'os';
|
|
6
|
+
|
|
7
|
+
export function createLinkCommand() {
|
|
8
|
+
return new Command('link')
|
|
9
|
+
.description('Link current directory to a Rigstate project')
|
|
10
|
+
.argument('<projectId>', 'Project ID to link')
|
|
11
|
+
.action(async (projectId) => {
|
|
12
|
+
// Check Global Override (Rigstate v2.7)
|
|
13
|
+
try {
|
|
14
|
+
const globalPath = path.join(os.homedir(), '.rigstate', 'config.json');
|
|
15
|
+
const globalData = await fs.readFile(globalPath, 'utf-8').catch(() => null);
|
|
16
|
+
if (globalData) {
|
|
17
|
+
const config = JSON.parse(globalData);
|
|
18
|
+
const cwd = process.cwd();
|
|
19
|
+
if (config.overrides && config.overrides[cwd]) {
|
|
20
|
+
const overrideId = config.overrides[cwd];
|
|
21
|
+
if (overrideId !== projectId) {
|
|
22
|
+
console.warn(chalk.yellow(`Global override detected. Enforcing project ID: ${overrideId}`));
|
|
23
|
+
projectId = overrideId;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
} catch (e) { }
|
|
28
|
+
|
|
29
|
+
const manifestPath = path.join(process.cwd(), '.rigstate');
|
|
30
|
+
|
|
31
|
+
const content = {
|
|
32
|
+
project_id: projectId,
|
|
33
|
+
api_url: process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000',
|
|
34
|
+
linked_at: new Date().toISOString()
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
await fs.writeFile(manifestPath, JSON.stringify(content, null, 2), 'utf-8');
|
|
39
|
+
console.log(chalk.green(`ā Linked to project ID: ${projectId}`));
|
|
40
|
+
console.log(chalk.dim(`Created local context manifest at .rigstate`));
|
|
41
|
+
} catch (error: any) {
|
|
42
|
+
console.error(chalk.red(`Failed to link project: ${error.message}`));
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { setApiKey } from '../utils/config.js';
|
|
4
|
+
|
|
5
|
+
export function createLoginCommand(): Command {
|
|
6
|
+
return new Command('login')
|
|
7
|
+
.description('Authenticate with your Rigstate API key')
|
|
8
|
+
.argument('<api-key>', 'Your Rigstate API key (starts with sk_)')
|
|
9
|
+
.action(async (apiKey: string) => {
|
|
10
|
+
try {
|
|
11
|
+
// Basic validation
|
|
12
|
+
if (!apiKey || !apiKey.startsWith('sk_rigstate_')) {
|
|
13
|
+
console.error(chalk.red('ā Invalid API key format'));
|
|
14
|
+
console.error(chalk.dim('API keys must start with "sk_rigstate_"'));
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Store the API key
|
|
19
|
+
setApiKey(apiKey);
|
|
20
|
+
|
|
21
|
+
console.log(chalk.green('ā
Successfully logged in!'));
|
|
22
|
+
console.log(
|
|
23
|
+
chalk.dim(
|
|
24
|
+
`\nYour API key has been securely stored. You can now use "rigstate scan" to audit your code.`
|
|
25
|
+
)
|
|
26
|
+
);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error(
|
|
29
|
+
chalk.red('ā Login failed:'),
|
|
30
|
+
error instanceof Error ? error.message : 'Unknown error'
|
|
31
|
+
);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { spawn } from 'child_process';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
|
|
8
|
+
// ESM compatibility for __dirname
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
|
|
12
|
+
export function createMcpCommand() {
|
|
13
|
+
const mcp = new Command('mcp');
|
|
14
|
+
|
|
15
|
+
mcp
|
|
16
|
+
.description('Run the Rigstate MCP server for AI editors')
|
|
17
|
+
.action(async () => {
|
|
18
|
+
// Determine the path to the MCP server
|
|
19
|
+
const possiblePaths = [
|
|
20
|
+
// From packages/cli -> packages/mcp (sibling package)
|
|
21
|
+
path.resolve(__dirname, '../../mcp/dist/index.js'),
|
|
22
|
+
// If installed globally or via npm
|
|
23
|
+
path.resolve(__dirname, '../../../mcp/dist/index.js'),
|
|
24
|
+
// Development path from packages/cli/dist
|
|
25
|
+
path.resolve(__dirname, '../../../packages/mcp/dist/index.js'),
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
let serverPath = '';
|
|
29
|
+
for (const p of possiblePaths) {
|
|
30
|
+
if (fs.existsSync(p)) {
|
|
31
|
+
serverPath = p;
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!serverPath) {
|
|
37
|
+
console.error(chalk.red('ā Error: Rigstate MCP Server binary not found.'));
|
|
38
|
+
console.error(chalk.yellow('Please ensure that the mcp package is built:'));
|
|
39
|
+
console.error(chalk.white(' cd packages/mcp && npm run build'));
|
|
40
|
+
console.error('');
|
|
41
|
+
console.error(chalk.dim('Or run directly with:'));
|
|
42
|
+
console.error(chalk.white(' npx @rigstate/mcp'));
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
console.log(chalk.dim(`Starting MCP server from: ${serverPath}`));
|
|
47
|
+
|
|
48
|
+
// Map VIBE_API_KEY to RIGSTATE_API_KEY if needed (backwards compatibility)
|
|
49
|
+
if (process.env.VIBE_API_KEY && !process.env.RIGSTATE_API_KEY) {
|
|
50
|
+
process.env.RIGSTATE_API_KEY = process.env.VIBE_API_KEY;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Spawn the MCP server as a child process
|
|
54
|
+
// MCP uses stdio for communication, so we inherit it
|
|
55
|
+
const worker = spawn('node', [serverPath], {
|
|
56
|
+
env: process.env,
|
|
57
|
+
stdio: ['inherit', 'inherit', 'inherit']
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
worker.on('error', (err) => {
|
|
61
|
+
console.error(chalk.red(`ā Failed to start MCP server: ${err.message}`));
|
|
62
|
+
process.exit(1);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
worker.on('exit', (code) => {
|
|
66
|
+
if (code !== 0 && code !== null) {
|
|
67
|
+
process.exit(code);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return mcp;
|
|
73
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { NexusDispatcher } from '../nexus/dispatcher.js';
|
|
5
|
+
import { NexusContext } from '@rigstate/shared';
|
|
6
|
+
import inquirer from 'inquirer';
|
|
7
|
+
|
|
8
|
+
export function createNexusCommand() {
|
|
9
|
+
const command = new Command('nexus');
|
|
10
|
+
|
|
11
|
+
command
|
|
12
|
+
.description('Interact with The Multi-Agent Nexus (Phase 8)')
|
|
13
|
+
.argument('<intent>', 'The natural language instruction for the swarm')
|
|
14
|
+
.option('--dry-run', 'Enable Dry-Run mode (Kill-Switch Active)', true)
|
|
15
|
+
.option('--force', 'Disable Dry-Run mode (DANGEROUS)', false)
|
|
16
|
+
.action(async (intent: string, options) => {
|
|
17
|
+
console.log(chalk.bold.magenta('\nš¦ Welcome to The Nexus (Phase 8)\n'));
|
|
18
|
+
|
|
19
|
+
const dryRun = !options.force;
|
|
20
|
+
|
|
21
|
+
if (!dryRun) {
|
|
22
|
+
console.log(chalk.black.bgYellow(' WARNING ') + chalk.yellow(' Dry-Run disabled! Eitri is authorized to write code.'));
|
|
23
|
+
const { confirm } = await inquirer.prompt([{
|
|
24
|
+
type: 'confirm',
|
|
25
|
+
name: 'confirm',
|
|
26
|
+
message: 'Are you absolutely sure you want to bypass the Kill-Switch?',
|
|
27
|
+
default: false
|
|
28
|
+
}]);
|
|
29
|
+
if (!confirm) {
|
|
30
|
+
console.log('Aborting.');
|
|
31
|
+
process.exit(0);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Context setup
|
|
36
|
+
const context: NexusContext = {
|
|
37
|
+
projectId: process.env.RIGSTATE_PROJECT_ID || 'local',
|
|
38
|
+
rootPath: process.cwd(),
|
|
39
|
+
sessionUser: 'cli-user', // Should strictly be pulled from auth
|
|
40
|
+
dryRun
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const dispatcher = new NexusDispatcher(context);
|
|
44
|
+
|
|
45
|
+
// Setup Listeners for Visualization
|
|
46
|
+
dispatcher.on('order:created', (o) => {
|
|
47
|
+
console.log(chalk.blue(`š [${o.id.slice(0, 6)}] Order Created: `) + o.intent);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
dispatcher.on('order:started', (o) => {
|
|
51
|
+
console.log(chalk.yellow(`ā³ [${o.id.slice(0, 6)}] Processing...`));
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
dispatcher.on('order:blocked', (o) => {
|
|
55
|
+
console.log(chalk.red(`š [${o.id.slice(0, 6)}] BLOCKED by Kill-Switch`));
|
|
56
|
+
console.log(chalk.dim(` Target: ${o.targetAgent} | Action: ${o.action}`));
|
|
57
|
+
console.log(chalk.dim(' Run with --force to execute automatically (NOT RECOMMENDED).'));
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
dispatcher.on('agent:SINDRE', (o) => console.log(chalk.cyan(`š¤ Sindre (Vault): I'm on it! (${o.action})`)));
|
|
61
|
+
dispatcher.on('agent:EITRI', (o) => console.log(chalk.green(`š· Eitri (Smith): Ready to build! (${o.action})`)));
|
|
62
|
+
|
|
63
|
+
// Simulation: Frank Analysis Logic
|
|
64
|
+
// In a real scenario, this would call LLM/MCP to get the decomposition.
|
|
65
|
+
// Here we hardcode a mocked response to demonstrate the CLI flow.
|
|
66
|
+
|
|
67
|
+
console.log(chalk.dim('š§ Frank is analyzing your intent...'));
|
|
68
|
+
await new Promise(r => setTimeout(r, 800)); // Simulate thinking
|
|
69
|
+
|
|
70
|
+
// MOCK: Dispatch logic
|
|
71
|
+
if (intent.toLowerCase().includes('db') || intent.toLowerCase().includes('database')) {
|
|
72
|
+
await dispatcher.dispatch('FRANK', 'SINDRE', intent, 'db.analyze', { raw: intent });
|
|
73
|
+
} else if (intent.toLowerCase().includes('create') || intent.toLowerCase().includes('code')) {
|
|
74
|
+
await dispatcher.dispatch('FRANK', 'EITRI', intent, 'fs.write', { path: 'src/demo.ts', content: '// demo' });
|
|
75
|
+
} else {
|
|
76
|
+
console.log(chalk.gray("Frank didn't understand. Try 'create file' or 'check database'."));
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
return command;
|
|
81
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { performOverride, getSessionState } from '../utils/governance.js';
|
|
4
|
+
import { getApiKey, getProjectId, getApiUrl } from '../utils/config.js';
|
|
5
|
+
import axios from 'axios';
|
|
6
|
+
|
|
7
|
+
export function createOverrideCommand() {
|
|
8
|
+
const override = new Command('override');
|
|
9
|
+
|
|
10
|
+
override
|
|
11
|
+
.description('Emergency Override for Governance Soft Locks')
|
|
12
|
+
.argument('<violationId>', 'ID of the violation to override (or "all")')
|
|
13
|
+
.requiredOption('-r, --reason <reason>', 'Description of why this override is necessary')
|
|
14
|
+
.action(async (violationId, options) => {
|
|
15
|
+
const { reason } = options;
|
|
16
|
+
|
|
17
|
+
console.log(chalk.bold(`\nš Initiating Governance Override Protocol...`));
|
|
18
|
+
|
|
19
|
+
const session = await getSessionState(process.cwd());
|
|
20
|
+
|
|
21
|
+
if (session.status !== 'SOFT_LOCK') {
|
|
22
|
+
console.log(chalk.yellow(' Info: Session is not currently locked.'));
|
|
23
|
+
return; // Not locked, but maybe we still want to log the "intent"?
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
console.log(chalk.dim(` Active Violation: ${session.active_violation}`));
|
|
27
|
+
console.log(chalk.dim(` Reason Provided: "${reason}"`));
|
|
28
|
+
|
|
29
|
+
// Perform Local Unlock
|
|
30
|
+
const success = await performOverride(violationId, reason, process.cwd());
|
|
31
|
+
|
|
32
|
+
if (success) {
|
|
33
|
+
console.log(chalk.green(` ā
Session UNLOCKED.`));
|
|
34
|
+
console.log(chalk.dim(` This event has been logged to the Mission Report.`));
|
|
35
|
+
|
|
36
|
+
// Optional: Notify Cloud about the Override (Audit Trail)
|
|
37
|
+
try {
|
|
38
|
+
const projectId = getProjectId();
|
|
39
|
+
if (projectId) {
|
|
40
|
+
const apiUrl = getApiUrl();
|
|
41
|
+
const apiKey = getApiKey();
|
|
42
|
+
|
|
43
|
+
await axios.post(`${apiUrl}/api/v1/execution-logs`, {
|
|
44
|
+
project_id: projectId,
|
|
45
|
+
task_id: 'OVERRIDE-' + Date.now(),
|
|
46
|
+
task_title: `Governance Override: ${violationId}`,
|
|
47
|
+
status: 'COMPLETED',
|
|
48
|
+
execution_summary: `Manual override executed. Reason: ${reason}`,
|
|
49
|
+
agent_role: 'SUPERVISOR' // Override is a supervisor action
|
|
50
|
+
}, {
|
|
51
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
52
|
+
});
|
|
53
|
+
console.log(chalk.dim(` ā Audit log synced to Cloud.`));
|
|
54
|
+
}
|
|
55
|
+
} catch (e: any) {
|
|
56
|
+
console.log(chalk.dim(` (Cloud audit sync failed: ${e.message})`));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
} else {
|
|
60
|
+
console.log(chalk.red(` š Override Failed. Check project configuration.`));
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return override;
|
|
65
|
+
}
|