onkol 0.1.0 → 0.2.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/dist/cli/index.js +256 -163
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -16,18 +16,77 @@ program
|
|
|
16
16
|
.name('onkol')
|
|
17
17
|
.description('Decentralized on-call agent system')
|
|
18
18
|
.version('0.1.0');
|
|
19
|
+
function loadCheckpoint(homeDir) {
|
|
20
|
+
const checkpointPath = resolve(homeDir, '.onkol-setup-checkpoint.json');
|
|
21
|
+
if (existsSync(checkpointPath)) {
|
|
22
|
+
try {
|
|
23
|
+
return JSON.parse(readFileSync(checkpointPath, 'utf-8'));
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
function saveCheckpoint(homeDir, checkpoint) {
|
|
32
|
+
writeFileSync(resolve(homeDir, '.onkol-setup-checkpoint.json'), JSON.stringify(checkpoint, null, 2));
|
|
33
|
+
}
|
|
34
|
+
function clearCheckpoint(homeDir) {
|
|
35
|
+
const p = resolve(homeDir, '.onkol-setup-checkpoint.json');
|
|
36
|
+
if (existsSync(p)) {
|
|
37
|
+
const { unlinkSync } = require('fs');
|
|
38
|
+
unlinkSync(p);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function markStep(homeDir, checkpoint, step) {
|
|
42
|
+
checkpoint.completed.push(step);
|
|
43
|
+
saveCheckpoint(homeDir, checkpoint);
|
|
44
|
+
}
|
|
19
45
|
program
|
|
20
46
|
.command('setup')
|
|
21
47
|
.description('Set up an Onkol node on this VM')
|
|
22
48
|
.action(async () => {
|
|
23
49
|
console.log(chalk.bold('\nWelcome to Onkol Setup\n'));
|
|
24
50
|
const homeDir = process.env.HOME || '/root';
|
|
25
|
-
|
|
51
|
+
let answers;
|
|
52
|
+
let checkpoint;
|
|
53
|
+
// Check for existing checkpoint
|
|
54
|
+
const existing = loadCheckpoint(homeDir);
|
|
55
|
+
if (existing) {
|
|
56
|
+
const { resume } = await (await import('inquirer')).default.prompt([{
|
|
57
|
+
type: 'list',
|
|
58
|
+
name: 'resume',
|
|
59
|
+
message: `Found a previous setup attempt (${existing.completed.length} steps completed). What do you want to do?`,
|
|
60
|
+
choices: [
|
|
61
|
+
{ name: `Resume from where it left off (node: ${existing.answers.nodeName})`, value: 'resume' },
|
|
62
|
+
{ name: 'Start fresh', value: 'fresh' },
|
|
63
|
+
],
|
|
64
|
+
}]);
|
|
65
|
+
if (resume === 'resume') {
|
|
66
|
+
answers = existing.answers;
|
|
67
|
+
checkpoint = existing;
|
|
68
|
+
console.log(chalk.green(`Resuming setup for "${answers.nodeName}". Skipping ${checkpoint.completed.length} completed steps.\n`));
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
answers = await runSetupPrompts(homeDir);
|
|
72
|
+
checkpoint = { answers, completed: [] };
|
|
73
|
+
saveCheckpoint(homeDir, checkpoint);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
answers = await runSetupPrompts(homeDir);
|
|
78
|
+
checkpoint = { answers, completed: [] };
|
|
79
|
+
saveCheckpoint(homeDir, checkpoint);
|
|
80
|
+
}
|
|
26
81
|
const dir = resolve(answers.installDir);
|
|
82
|
+
const skip = (step) => checkpoint.completed.includes(step);
|
|
27
83
|
// Create directory structure
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
84
|
+
if (!skip('directories')) {
|
|
85
|
+
console.log(chalk.gray('Creating directories...'));
|
|
86
|
+
for (const sub of ['knowledge', 'workers', 'workers/.archive', 'scripts', 'plugins/discord-filtered', '.claude']) {
|
|
87
|
+
mkdirSync(resolve(dir, sub), { recursive: true });
|
|
88
|
+
}
|
|
89
|
+
markStep(homeDir, checkpoint, 'directories');
|
|
31
90
|
}
|
|
32
91
|
// Build allowed users list from Discord user ID prompt
|
|
33
92
|
const user = process.env.USER || 'root';
|
|
@@ -36,182 +95,206 @@ program
|
|
|
36
95
|
allowedUsers.push(answers.discordUserId.trim());
|
|
37
96
|
}
|
|
38
97
|
// --- CRITICAL: Create Discord category and orchestrator channel ---
|
|
39
|
-
|
|
40
|
-
let
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
98
|
+
let categoryId = checkpoint.categoryId || '';
|
|
99
|
+
let orchChannelId = checkpoint.orchChannelId || '';
|
|
100
|
+
if (!skip('discord')) {
|
|
101
|
+
console.log(chalk.gray('Creating Discord category and channel...'));
|
|
102
|
+
try {
|
|
103
|
+
const category = await createCategory(answers.botToken, answers.guildId, answers.nodeName);
|
|
104
|
+
const orchChannel = await createChannel(answers.botToken, answers.guildId, 'orchestrator', category.id);
|
|
105
|
+
categoryId = category.id;
|
|
106
|
+
orchChannelId = orchChannel.id;
|
|
107
|
+
checkpoint.categoryId = categoryId;
|
|
108
|
+
checkpoint.orchChannelId = orchChannelId;
|
|
109
|
+
markStep(homeDir, checkpoint, 'discord');
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
console.error(chalk.red(`\nFATAL: Could not create Discord category/channel.`));
|
|
113
|
+
console.error(chalk.red(`${err instanceof Error ? err.message : err}`));
|
|
114
|
+
console.error(chalk.red('\nCheck that:'));
|
|
115
|
+
console.error(chalk.red(' 1. Your bot token is correct'));
|
|
116
|
+
console.error(chalk.red(' 2. Your server (guild) ID is correct'));
|
|
117
|
+
console.error(chalk.red(' 3. The bot has been invited to the server with "Manage Channels" permission'));
|
|
118
|
+
console.error(chalk.yellow('\nYour answers have been saved. Fix the issue and run `npx onkol setup` again to resume.'));
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
console.log(chalk.green('✓ Discord category and #orchestrator channel created'));
|
|
45
122
|
}
|
|
46
|
-
|
|
47
|
-
console.
|
|
48
|
-
console.error(chalk.red(`${err instanceof Error ? err.message : err}`));
|
|
49
|
-
console.error(chalk.red('\nCheck that:'));
|
|
50
|
-
console.error(chalk.red(' 1. Your bot token is correct'));
|
|
51
|
-
console.error(chalk.red(' 2. Your server (guild) ID is correct'));
|
|
52
|
-
console.error(chalk.red(' 3. The bot has been invited to the server with "Manage Channels" permission'));
|
|
53
|
-
process.exit(1);
|
|
123
|
+
else {
|
|
124
|
+
console.log(chalk.gray(' Discord category already created, skipping'));
|
|
54
125
|
}
|
|
55
|
-
console.log(chalk.green('✓ Discord category and #orchestrator channel created'));
|
|
56
126
|
// Write config.json
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
copyFileSync(answers.registryPath, resolve(dir, 'registry.json'));
|
|
72
|
-
}
|
|
73
|
-
else if (answers.registryMode !== 'prompt') {
|
|
74
|
-
writeFileSync(resolve(dir, 'registry.json'), '{}');
|
|
75
|
-
}
|
|
76
|
-
// Handle services
|
|
77
|
-
let servicesMd = '# Services\n\nNo services configured yet.\n';
|
|
78
|
-
if (answers.serviceMode === 'auto') {
|
|
79
|
-
console.log(chalk.gray('Discovering services...'));
|
|
80
|
-
const services = discoverServices();
|
|
81
|
-
servicesMd = formatServicesMarkdown(services);
|
|
82
|
-
console.log(chalk.green(`Found ${services.length} services.`));
|
|
83
|
-
}
|
|
84
|
-
else if (answers.serviceMode === 'import' && answers.serviceSummaryPath) {
|
|
85
|
-
servicesMd = readFileSync(answers.serviceSummaryPath, 'utf-8');
|
|
86
|
-
}
|
|
87
|
-
if (answers.serviceMode !== 'prompt') {
|
|
88
|
-
writeFileSync(resolve(dir, 'services.md'), servicesMd);
|
|
127
|
+
if (!skip('config')) {
|
|
128
|
+
const config = {
|
|
129
|
+
nodeName: answers.nodeName,
|
|
130
|
+
botToken: answers.botToken,
|
|
131
|
+
guildId: answers.guildId,
|
|
132
|
+
categoryId,
|
|
133
|
+
orchestratorChannelId: orchChannelId,
|
|
134
|
+
allowedUsers,
|
|
135
|
+
maxWorkers: 3,
|
|
136
|
+
installDir: dir,
|
|
137
|
+
plugins: answers.plugins,
|
|
138
|
+
};
|
|
139
|
+
writeFileSync(resolve(dir, 'config.json'), JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
140
|
+
markStep(homeDir, checkpoint, 'config');
|
|
89
141
|
}
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
142
|
+
// Write files (registry, services, CLAUDE.md, settings, mcp.json, state)
|
|
143
|
+
if (!skip('files')) {
|
|
144
|
+
// Handle registry
|
|
145
|
+
if (answers.registryMode === 'import' && answers.registryPath) {
|
|
146
|
+
copyFileSync(answers.registryPath, resolve(dir, 'registry.json'));
|
|
147
|
+
}
|
|
148
|
+
else if (answers.registryMode !== 'prompt') {
|
|
149
|
+
writeFileSync(resolve(dir, 'registry.json'), '{}');
|
|
150
|
+
}
|
|
151
|
+
// Handle services
|
|
152
|
+
let servicesMd = '# Services\n\nNo services configured yet.\n';
|
|
153
|
+
if (answers.serviceMode === 'auto') {
|
|
154
|
+
console.log(chalk.gray('Discovering services...'));
|
|
155
|
+
const services = discoverServices();
|
|
156
|
+
servicesMd = formatServicesMarkdown(services);
|
|
157
|
+
console.log(chalk.green(`Found ${services.length} services.`));
|
|
158
|
+
}
|
|
159
|
+
else if (answers.serviceMode === 'import' && answers.serviceSummaryPath) {
|
|
160
|
+
servicesMd = readFileSync(answers.serviceSummaryPath, 'utf-8');
|
|
161
|
+
}
|
|
162
|
+
if (answers.serviceMode !== 'prompt') {
|
|
163
|
+
writeFileSync(resolve(dir, 'services.md'), servicesMd);
|
|
164
|
+
}
|
|
165
|
+
// Generate CLAUDE.md, settings, mcp.json, state files
|
|
166
|
+
writeFileSync(resolve(dir, 'CLAUDE.md'), renderOrchestratorClaude({ nodeName: answers.nodeName, maxWorkers: 3 }));
|
|
167
|
+
writeFileSync(resolve(dir, '.claude/settings.json'), renderSettings({ bashLogPath: resolve(dir, 'bash-log.txt') }));
|
|
168
|
+
const pluginPath = resolve(dir, 'plugins/discord-filtered/index.ts');
|
|
169
|
+
const mcpJson = {
|
|
170
|
+
mcpServers: {
|
|
171
|
+
'discord-filtered': {
|
|
172
|
+
command: 'bun',
|
|
173
|
+
args: [pluginPath],
|
|
174
|
+
env: {
|
|
175
|
+
DISCORD_BOT_TOKEN: answers.botToken,
|
|
176
|
+
DISCORD_CHANNEL_ID: orchChannelId,
|
|
177
|
+
DISCORD_ALLOWED_USERS: JSON.stringify(allowedUsers),
|
|
178
|
+
},
|
|
107
179
|
},
|
|
108
180
|
},
|
|
109
|
-
},
|
|
110
|
-
};
|
|
111
|
-
writeFileSync(resolve(dir, '.mcp.json'), JSON.stringify(mcpJson, null, 2));
|
|
112
|
-
// Initialize tracking and knowledge index
|
|
113
|
-
writeFileSync(resolve(dir, 'workers/tracking.json'), '[]');
|
|
114
|
-
writeFileSync(resolve(dir, 'knowledge/index.json'), '[]');
|
|
115
|
-
writeFileSync(resolve(dir, 'state.md'), '');
|
|
116
|
-
// Pre-accept Claude Code trust dialog for the onkol directory
|
|
117
|
-
console.log(chalk.gray('Configuring Claude Code trust...'));
|
|
118
|
-
const claudeJsonPath = resolve(homeDir, '.claude/.claude.json');
|
|
119
|
-
try {
|
|
120
|
-
const claudeJson = existsSync(claudeJsonPath) ? JSON.parse(readFileSync(claudeJsonPath, 'utf-8')) : {};
|
|
121
|
-
if (!claudeJson.projects)
|
|
122
|
-
claudeJson.projects = {};
|
|
123
|
-
claudeJson.projects[dir] = {
|
|
124
|
-
...(claudeJson.projects[dir] || {}),
|
|
125
|
-
allowedTools: [],
|
|
126
|
-
hasTrustDialogAccepted: true,
|
|
127
181
|
};
|
|
128
|
-
writeFileSync(
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
182
|
+
writeFileSync(resolve(dir, '.mcp.json'), JSON.stringify(mcpJson, null, 2));
|
|
183
|
+
if (!existsSync(resolve(dir, 'workers/tracking.json')))
|
|
184
|
+
writeFileSync(resolve(dir, 'workers/tracking.json'), '[]');
|
|
185
|
+
if (!existsSync(resolve(dir, 'knowledge/index.json')))
|
|
186
|
+
writeFileSync(resolve(dir, 'knowledge/index.json'), '[]');
|
|
187
|
+
if (!existsSync(resolve(dir, 'state.md')))
|
|
188
|
+
writeFileSync(resolve(dir, 'state.md'), '');
|
|
189
|
+
// Pre-accept Claude Code trust
|
|
190
|
+
console.log(chalk.gray('Configuring Claude Code trust...'));
|
|
191
|
+
const claudeJsonPath = resolve(homeDir, '.claude/.claude.json');
|
|
192
|
+
try {
|
|
193
|
+
const claudeJson = existsSync(claudeJsonPath) ? JSON.parse(readFileSync(claudeJsonPath, 'utf-8')) : {};
|
|
194
|
+
if (!claudeJson.projects)
|
|
195
|
+
claudeJson.projects = {};
|
|
196
|
+
claudeJson.projects[dir] = { ...(claudeJson.projects[dir] || {}), allowedTools: [], hasTrustDialogAccepted: true };
|
|
197
|
+
writeFileSync(claudeJsonPath, JSON.stringify(claudeJson, null, 2));
|
|
198
|
+
console.log(chalk.green('✓ Claude Code trust pre-accepted'));
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
console.log(chalk.yellow('⚠ Could not pre-accept trust dialog.'));
|
|
202
|
+
}
|
|
203
|
+
// Handle setup prompts
|
|
204
|
+
const pendingPrompts = [];
|
|
205
|
+
if (answers.registryPrompt)
|
|
206
|
+
pendingPrompts.push({ target: 'registry.json', prompt: answers.registryPrompt, status: 'pending' });
|
|
207
|
+
if (answers.servicesPrompt)
|
|
208
|
+
pendingPrompts.push({ target: 'services.md', prompt: answers.servicesPrompt, status: 'pending' });
|
|
209
|
+
if (answers.claudeMdPrompt)
|
|
210
|
+
pendingPrompts.push({ target: 'CLAUDE.md', prompt: answers.claudeMdPrompt, status: 'pending' });
|
|
211
|
+
if (pendingPrompts.length > 0) {
|
|
212
|
+
writeFileSync(resolve(dir, 'setup-prompts.json'), JSON.stringify({ pending: pendingPrompts }, null, 2));
|
|
213
|
+
}
|
|
214
|
+
markStep(homeDir, checkpoint, 'files');
|
|
144
215
|
}
|
|
145
|
-
|
|
146
|
-
|
|
216
|
+
else {
|
|
217
|
+
console.log(chalk.gray(' Config files already written, skipping'));
|
|
147
218
|
}
|
|
148
219
|
// --- CRITICAL: Copy scripts ---
|
|
149
220
|
const requiredScripts = ['spawn-worker.sh', 'dissolve-worker.sh', 'list-workers.sh', 'check-worker.sh', 'healthcheck.sh', 'start-orchestrator.sh'];
|
|
150
221
|
const scriptsSource = resolve(__dirname, '../../scripts');
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
console.error(chalk.red(`\nFATAL: Scripts directory not found at ${scriptsSource}`));
|
|
154
|
-
console.error(chalk.red('The onkol package appears to be corrupted. Reinstall with: npm install -g onkol'));
|
|
155
|
-
process.exit(1);
|
|
222
|
+
if (skip('scripts')) {
|
|
223
|
+
console.log(chalk.gray(' Scripts already installed, skipping'));
|
|
156
224
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
console.error(chalk.red(
|
|
225
|
+
else {
|
|
226
|
+
console.log(chalk.gray('Copying scripts...'));
|
|
227
|
+
if (!existsSync(scriptsSource)) {
|
|
228
|
+
console.error(chalk.red(`\nFATAL: Scripts directory not found at ${scriptsSource}`));
|
|
229
|
+
console.error(chalk.red('The onkol package appears to be corrupted. Reinstall with: npm install -g onkol'));
|
|
162
230
|
process.exit(1);
|
|
163
231
|
}
|
|
164
|
-
|
|
165
|
-
|
|
232
|
+
for (const script of requiredScripts) {
|
|
233
|
+
const src = resolve(scriptsSource, script);
|
|
234
|
+
const dst = resolve(dir, 'scripts', script);
|
|
235
|
+
if (!existsSync(src)) {
|
|
236
|
+
console.error(chalk.red(`\nFATAL: Required script not found: ${src}`));
|
|
237
|
+
process.exit(1);
|
|
238
|
+
}
|
|
239
|
+
copyFileSync(src, dst);
|
|
240
|
+
execSync(`chmod +x "${dst}"`);
|
|
241
|
+
}
|
|
242
|
+
console.log(chalk.green(`✓ ${requiredScripts.length} scripts installed`));
|
|
243
|
+
markStep(homeDir, checkpoint, 'scripts');
|
|
166
244
|
}
|
|
167
|
-
console.log(chalk.green(`✓ ${requiredScripts.length} scripts installed`));
|
|
168
245
|
// --- CRITICAL: Copy plugin source ---
|
|
169
|
-
// Look for .ts source files first (for bun), fall back to .js compiled files
|
|
170
246
|
const pluginFiles = ['index', 'mcp-server', 'discord-client', 'message-batcher'];
|
|
171
247
|
const pluginSourceDir = resolve(__dirname, '../plugin');
|
|
172
248
|
const projectSrcDir = resolve(__dirname, '../../src/plugin');
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
for (const base of pluginFiles) {
|
|
176
|
-
const dst = resolve(dir, 'plugins/discord-filtered', `${base}.ts`);
|
|
177
|
-
// Try .ts from project src first, then .ts from dist, then .js from dist
|
|
178
|
-
const candidates = [
|
|
179
|
-
resolve(projectSrcDir, `${base}.ts`),
|
|
180
|
-
resolve(pluginSourceDir, `${base}.ts`),
|
|
181
|
-
resolve(pluginSourceDir, `${base}.js`),
|
|
182
|
-
];
|
|
183
|
-
const found = candidates.find(c => existsSync(c));
|
|
184
|
-
if (found) {
|
|
185
|
-
copyFileSync(found, found.endsWith('.js') ? resolve(dir, 'plugins/discord-filtered', `${base}.js`) : dst);
|
|
186
|
-
pluginCopied++;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
if (pluginCopied < pluginFiles.length) {
|
|
190
|
-
console.error(chalk.red(`\nFATAL: Only ${pluginCopied}/${pluginFiles.length} plugin files found.`));
|
|
191
|
-
console.error(chalk.red(`Searched in:\n ${projectSrcDir}\n ${pluginSourceDir}`));
|
|
192
|
-
console.error(chalk.red('The onkol package appears to be corrupted. Reinstall with: npm install -g onkol'));
|
|
193
|
-
process.exit(1);
|
|
194
|
-
}
|
|
195
|
-
// Create plugin package.json and install deps
|
|
196
|
-
const pluginPkgJson = {
|
|
197
|
-
name: 'discord-filtered',
|
|
198
|
-
version: '0.1.0',
|
|
199
|
-
private: true,
|
|
200
|
-
dependencies: {
|
|
201
|
-
'@modelcontextprotocol/sdk': '^1.0.0',
|
|
202
|
-
'discord.js': '^14.0.0',
|
|
203
|
-
},
|
|
204
|
-
};
|
|
205
|
-
writeFileSync(resolve(dir, 'plugins/discord-filtered/package.json'), JSON.stringify(pluginPkgJson, null, 2));
|
|
206
|
-
console.log(chalk.gray('Installing plugin dependencies (bun install)...'));
|
|
207
|
-
try {
|
|
208
|
-
execSync('bun install', { cwd: resolve(dir, 'plugins/discord-filtered'), stdio: 'pipe' });
|
|
209
|
-
console.log(chalk.green(`✓ Plugin installed with ${pluginCopied} files + dependencies`));
|
|
249
|
+
if (skip('plugin')) {
|
|
250
|
+
console.log(chalk.gray(' Plugin already installed, skipping'));
|
|
210
251
|
}
|
|
211
|
-
|
|
212
|
-
console.
|
|
213
|
-
|
|
214
|
-
|
|
252
|
+
else {
|
|
253
|
+
console.log(chalk.gray('Installing discord-filtered plugin...'));
|
|
254
|
+
let pluginCopied = 0;
|
|
255
|
+
for (const base of pluginFiles) {
|
|
256
|
+
const dst = resolve(dir, 'plugins/discord-filtered', `${base}.ts`);
|
|
257
|
+
// Try .ts from project src first, then .ts from dist, then .js from dist
|
|
258
|
+
const candidates = [
|
|
259
|
+
resolve(projectSrcDir, `${base}.ts`),
|
|
260
|
+
resolve(pluginSourceDir, `${base}.ts`),
|
|
261
|
+
resolve(pluginSourceDir, `${base}.js`),
|
|
262
|
+
];
|
|
263
|
+
const found = candidates.find(c => existsSync(c));
|
|
264
|
+
if (found) {
|
|
265
|
+
copyFileSync(found, found.endsWith('.js') ? resolve(dir, 'plugins/discord-filtered', `${base}.js`) : dst);
|
|
266
|
+
pluginCopied++;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (pluginCopied < pluginFiles.length) {
|
|
270
|
+
console.error(chalk.red(`\nFATAL: Only ${pluginCopied}/${pluginFiles.length} plugin files found.`));
|
|
271
|
+
console.error(chalk.red(`Searched in:\n ${projectSrcDir}\n ${pluginSourceDir}`));
|
|
272
|
+
console.error(chalk.red('The onkol package appears to be corrupted. Reinstall with: npm install -g onkol'));
|
|
273
|
+
process.exit(1);
|
|
274
|
+
}
|
|
275
|
+
// Create plugin package.json and install deps
|
|
276
|
+
const pluginPkgJson = {
|
|
277
|
+
name: 'discord-filtered',
|
|
278
|
+
version: '0.1.0',
|
|
279
|
+
private: true,
|
|
280
|
+
dependencies: {
|
|
281
|
+
'@modelcontextprotocol/sdk': '^1.0.0',
|
|
282
|
+
'discord.js': '^14.0.0',
|
|
283
|
+
},
|
|
284
|
+
};
|
|
285
|
+
writeFileSync(resolve(dir, 'plugins/discord-filtered/package.json'), JSON.stringify(pluginPkgJson, null, 2));
|
|
286
|
+
console.log(chalk.gray('Installing plugin dependencies (bun install)...'));
|
|
287
|
+
try {
|
|
288
|
+
execSync('bun install', { cwd: resolve(dir, 'plugins/discord-filtered'), stdio: 'pipe' });
|
|
289
|
+
console.log(chalk.green(`✓ Plugin installed with ${pluginCopied} files + dependencies`));
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
console.error(chalk.red('\nFATAL: Failed to install plugin dependencies.'));
|
|
293
|
+
console.error(chalk.red('Is bun installed? Install with: curl -fsSL https://bun.sh/install | bash'));
|
|
294
|
+
console.error(chalk.yellow('\nYour progress has been saved. Fix the issue and run `npx onkol setup` again to resume.'));
|
|
295
|
+
process.exit(1);
|
|
296
|
+
}
|
|
297
|
+
markStep(homeDir, checkpoint, 'plugin');
|
|
215
298
|
}
|
|
216
299
|
// Install systemd service
|
|
217
300
|
const systemdUnit = generateSystemdUnit(answers.nodeName, user, dir);
|
|
@@ -282,11 +365,19 @@ program
|
|
|
282
365
|
console.log(chalk.yellow(` You'll need to set up periodic health checks manually.`));
|
|
283
366
|
}
|
|
284
367
|
// Report pending setup prompts
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
368
|
+
const setupPromptsPath = resolve(dir, 'setup-prompts.json');
|
|
369
|
+
if (existsSync(setupPromptsPath)) {
|
|
370
|
+
try {
|
|
371
|
+
const sp = JSON.parse(readFileSync(setupPromptsPath, 'utf-8'));
|
|
372
|
+
const pending = (sp.pending || []).filter((p) => p.status === 'pending');
|
|
373
|
+
if (pending.length > 0) {
|
|
374
|
+
console.log(chalk.cyan('\nPending setup prompts saved. On first boot, the orchestrator will:'));
|
|
375
|
+
for (const p of pending) {
|
|
376
|
+
console.log(chalk.cyan(` - Generate ${p.target} from your ${p.target === 'CLAUDE.md' ? 'description' : 'prompt'}`));
|
|
377
|
+
}
|
|
378
|
+
}
|
|
289
379
|
}
|
|
380
|
+
catch { /* ignore */ }
|
|
290
381
|
}
|
|
291
382
|
// Start orchestrator
|
|
292
383
|
console.log(chalk.gray('\nStarting orchestrator...'));
|
|
@@ -298,6 +389,8 @@ program
|
|
|
298
389
|
console.log(chalk.yellow(`⚠ Could not start orchestrator automatically.`));
|
|
299
390
|
console.log(chalk.yellow(` Start manually: ${dir}/scripts/start-orchestrator.sh`));
|
|
300
391
|
}
|
|
392
|
+
// Setup complete — clear checkpoint
|
|
393
|
+
clearCheckpoint(homeDir);
|
|
301
394
|
// Done
|
|
302
395
|
console.log(chalk.green.bold(`\n✓ Onkol node "${answers.nodeName}" is live!`));
|
|
303
396
|
console.log(chalk.green(`✓ Discord category "${answers.nodeName}" created with #orchestrator channel`));
|