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.
Files changed (2) hide show
  1. package/dist/cli/index.js +256 -163
  2. 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
- const answers = await runSetupPrompts(homeDir);
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
- console.log(chalk.gray('Creating directories...'));
29
- for (const sub of ['knowledge', 'workers', 'workers/.archive', 'scripts', 'plugins/discord-filtered', '.claude']) {
30
- mkdirSync(resolve(dir, sub), { recursive: true });
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
- console.log(chalk.gray('Creating Discord category and channel...'));
40
- let category;
41
- let orchChannel;
42
- try {
43
- category = await createCategory(answers.botToken, answers.guildId, answers.nodeName);
44
- orchChannel = await createChannel(answers.botToken, answers.guildId, 'orchestrator', category.id);
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
- catch (err) {
47
- console.error(chalk.red(`\nFATAL: Could not create Discord category/channel.`));
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
- const config = {
58
- nodeName: answers.nodeName,
59
- botToken: answers.botToken,
60
- guildId: answers.guildId,
61
- categoryId: category.id,
62
- orchestratorChannelId: orchChannel.id,
63
- allowedUsers,
64
- maxWorkers: 3,
65
- installDir: dir,
66
- plugins: answers.plugins,
67
- };
68
- writeFileSync(resolve(dir, 'config.json'), JSON.stringify(config, null, 2), { mode: 0o600 });
69
- // Handle registry
70
- if (answers.registryMode === 'import' && answers.registryPath) {
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
- // Generate CLAUDE.md
91
- const claudeMd = renderOrchestratorClaude({ nodeName: answers.nodeName, maxWorkers: 3 });
92
- writeFileSync(resolve(dir, 'CLAUDE.md'), claudeMd);
93
- // Generate .claude/settings.json
94
- const settings = renderSettings({ bashLogPath: resolve(dir, 'bash-log.txt') });
95
- writeFileSync(resolve(dir, '.claude/settings.json'), settings);
96
- // Write orchestrator .mcp.json
97
- const pluginPath = resolve(dir, 'plugins/discord-filtered/index.ts');
98
- const mcpJson = {
99
- mcpServers: {
100
- 'discord-filtered': {
101
- command: 'bun',
102
- args: [pluginPath],
103
- env: {
104
- DISCORD_BOT_TOKEN: answers.botToken,
105
- DISCORD_CHANNEL_ID: orchChannel.id,
106
- DISCORD_ALLOWED_USERS: JSON.stringify(allowedUsers),
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(claudeJsonPath, JSON.stringify(claudeJson, null, 2));
129
- console.log(chalk.green('✓ Claude Code trust pre-accepted for ' + dir));
130
- }
131
- catch {
132
- console.log(chalk.yellow('⚠ Could not pre-accept trust dialog. You may need to accept it manually on first run.'));
133
- }
134
- // Handle setup prompts
135
- const pendingPrompts = [];
136
- if (answers.registryPrompt) {
137
- pendingPrompts.push({ target: 'registry.json', prompt: answers.registryPrompt, status: 'pending' });
138
- }
139
- if (answers.servicesPrompt) {
140
- pendingPrompts.push({ target: 'services.md', prompt: answers.servicesPrompt, status: 'pending' });
141
- }
142
- if (answers.claudeMdPrompt) {
143
- pendingPrompts.push({ target: 'CLAUDE.md', prompt: answers.claudeMdPrompt, status: 'pending' });
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
- if (pendingPrompts.length > 0) {
146
- writeFileSync(resolve(dir, 'setup-prompts.json'), JSON.stringify({ pending: pendingPrompts }, null, 2));
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
- console.log(chalk.gray('Copying scripts...'));
152
- if (!existsSync(scriptsSource)) {
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
- for (const script of requiredScripts) {
158
- const src = resolve(scriptsSource, script);
159
- const dst = resolve(dir, 'scripts', script);
160
- if (!existsSync(src)) {
161
- console.error(chalk.red(`\nFATAL: Required script not found: ${src}`));
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
- copyFileSync(src, dst);
165
- execSync(`chmod +x "${dst}"`);
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
- console.log(chalk.gray('Installing discord-filtered plugin...'));
174
- let pluginCopied = 0;
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
- catch {
212
- console.error(chalk.red('\nFATAL: Failed to install plugin dependencies.'));
213
- console.error(chalk.red('Is bun installed? Install with: curl -fsSL https://bun.sh/install | bash'));
214
- process.exit(1);
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
- if (pendingPrompts.length > 0) {
286
- console.log(chalk.cyan('\nPending setup prompts saved. On first boot, the orchestrator will:'));
287
- for (const p of pendingPrompts) {
288
- console.log(chalk.cyan(` - Generate ${p.target} from your ${p.target === 'CLAUDE.md' ? 'description' : 'prompt'}`));
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`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "onkol",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Decentralized on-call agent system powered by Claude Code",
5
5
  "type": "module",
6
6
  "bin": {