@soleri/cli 1.12.5 → 8.0.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 (38) hide show
  1. package/dist/commands/add-pack.d.ts +2 -0
  2. package/dist/commands/add-pack.js +154 -0
  3. package/dist/commands/add-pack.js.map +1 -0
  4. package/dist/commands/agent.js +151 -2
  5. package/dist/commands/agent.js.map +1 -1
  6. package/dist/commands/cognee.d.ts +10 -0
  7. package/dist/commands/cognee.js +364 -0
  8. package/dist/commands/cognee.js.map +1 -0
  9. package/dist/commands/create.js +63 -5
  10. package/dist/commands/create.js.map +1 -1
  11. package/dist/commands/dev.js +104 -17
  12. package/dist/commands/dev.js.map +1 -1
  13. package/dist/commands/install.js +70 -18
  14. package/dist/commands/install.js.map +1 -1
  15. package/dist/commands/telegram.d.ts +10 -0
  16. package/dist/commands/telegram.js +423 -0
  17. package/dist/commands/telegram.js.map +1 -0
  18. package/dist/commands/uninstall.js +35 -6
  19. package/dist/commands/uninstall.js.map +1 -1
  20. package/dist/main.js +6 -0
  21. package/dist/main.js.map +1 -1
  22. package/dist/prompts/create-wizard.js +87 -6
  23. package/dist/prompts/create-wizard.js.map +1 -1
  24. package/dist/utils/agent-context.d.ts +9 -2
  25. package/dist/utils/agent-context.js +32 -0
  26. package/dist/utils/agent-context.js.map +1 -1
  27. package/package.json +1 -1
  28. package/src/commands/add-pack.ts +170 -0
  29. package/src/commands/agent.ts +174 -3
  30. package/src/commands/cognee.ts +419 -0
  31. package/src/commands/create.ts +90 -6
  32. package/src/commands/dev.ts +114 -18
  33. package/src/commands/install.ts +136 -18
  34. package/src/commands/telegram.ts +488 -0
  35. package/src/commands/uninstall.ts +41 -7
  36. package/src/main.ts +6 -0
  37. package/src/prompts/create-wizard.ts +93 -7
  38. package/src/utils/agent-context.ts +39 -2
@@ -0,0 +1,488 @@
1
+ /**
2
+ * Telegram transport management — enable, disable, setup, status.
3
+ *
4
+ * `soleri telegram enable` — Add Telegram files to the current agent
5
+ * `soleri telegram disable` — Remove Telegram files from the current agent
6
+ * `soleri telegram setup` — Interactive config wizard (bot token, API key, model)
7
+ * `soleri telegram status` — Check Telegram configuration status
8
+ */
9
+
10
+ import { join } from 'node:path';
11
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from 'node:fs';
12
+ import { homedir } from 'node:os';
13
+ import { execFileSync } from 'node:child_process';
14
+ import type { Command } from 'commander';
15
+ import * as p from '@clack/prompts';
16
+ import {
17
+ generateTelegramBot,
18
+ generateTelegramAgent,
19
+ generateTelegramConfig,
20
+ generateTelegramSupervisor,
21
+ } from '@soleri/forge/lib';
22
+ import type { AgentConfig } from '@soleri/forge/lib';
23
+ import { detectAgent } from '../utils/agent-context.js';
24
+
25
+ // ─── Telegram file paths relative to agent src/ ─────────────────────
26
+
27
+ const TELEGRAM_FILES = [
28
+ 'src/telegram-bot.ts',
29
+ 'src/telegram-agent.ts',
30
+ 'src/telegram-config.ts',
31
+ 'src/telegram-supervisor.ts',
32
+ ] as const;
33
+
34
+ // ─── Registration ───────────────────────────────────────────────────
35
+
36
+ export function registerTelegram(program: Command): void {
37
+ const tg = program
38
+ .command('telegram')
39
+ .description('Manage Telegram transport for the current agent');
40
+
41
+ // ─── enable ─────────────────────────────────────────────────────
42
+ tg.command('enable')
43
+ .description('Add Telegram transport files to the current agent')
44
+ .action(() => {
45
+ const ctx = detectAgent();
46
+ if (!ctx) {
47
+ p.log.error('No agent project detected in current directory.');
48
+ process.exit(1);
49
+ return;
50
+ }
51
+
52
+ // Check if already enabled
53
+ const existingFiles = TELEGRAM_FILES.filter((f) => existsSync(join(ctx.agentPath, f)));
54
+ if (existingFiles.length === TELEGRAM_FILES.length) {
55
+ p.log.warn('Telegram is already enabled for this agent.');
56
+ p.log.info('Run `soleri telegram setup` to configure it.');
57
+ return;
58
+ }
59
+ if (existingFiles.length > 0) {
60
+ p.log.warn(
61
+ `Partial Telegram setup detected (${existingFiles.length}/${TELEGRAM_FILES.length} files). Regenerating all files.`,
62
+ );
63
+ }
64
+
65
+ // Reconstruct AgentConfig
66
+ const config = readAgentConfig(ctx.agentPath, ctx.agentId);
67
+ if (!config) {
68
+ p.log.error('Could not read agent config from persona.ts and entry point.');
69
+ process.exit(1);
70
+ return;
71
+ }
72
+
73
+ // Generate the 4 Telegram files
74
+ const s = p.spinner();
75
+ s.start('Generating Telegram transport files...');
76
+
77
+ const telegramFiles: Array<[string, string]> = [
78
+ ['src/telegram-bot.ts', generateTelegramBot(config)],
79
+ ['src/telegram-agent.ts', generateTelegramAgent(config)],
80
+ ['src/telegram-config.ts', generateTelegramConfig(config)],
81
+ ['src/telegram-supervisor.ts', generateTelegramSupervisor(config)],
82
+ ];
83
+
84
+ for (const [relPath, content] of telegramFiles) {
85
+ writeFileSync(join(ctx.agentPath, relPath), content, 'utf-8');
86
+ }
87
+
88
+ s.stop('Generated 4 Telegram transport files');
89
+
90
+ // Add grammy to package.json if not present
91
+ const pkgPath = join(ctx.agentPath, 'package.json');
92
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
93
+ let pkgChanged = false;
94
+
95
+ if (!pkg.dependencies?.grammy) {
96
+ if (!pkg.dependencies) pkg.dependencies = {};
97
+ pkg.dependencies.grammy = '^1.35.0';
98
+ pkgChanged = true;
99
+ p.log.info('Added grammy dependency to package.json');
100
+ }
101
+
102
+ // Add npm scripts if not present
103
+ if (!pkg.scripts) pkg.scripts = {};
104
+ if (!pkg.scripts['telegram:start']) {
105
+ pkg.scripts['telegram:start'] = 'node dist/telegram-supervisor.js';
106
+ pkgChanged = true;
107
+ }
108
+ if (!pkg.scripts['telegram:dev']) {
109
+ pkg.scripts['telegram:dev'] = 'tsx src/telegram-bot.ts';
110
+ pkgChanged = true;
111
+ }
112
+
113
+ if (pkgChanged) {
114
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
115
+ p.log.info('Updated package.json with telegram scripts');
116
+ }
117
+
118
+ // Install grammy
119
+ const installSpinner = p.spinner();
120
+ installSpinner.start('Installing grammy...');
121
+ try {
122
+ execFileSync('npm', ['install', '--no-fund', '--no-audit'], {
123
+ cwd: ctx.agentPath,
124
+ stdio: 'pipe',
125
+ timeout: 120_000,
126
+ });
127
+ installSpinner.stop('Installed grammy');
128
+ } catch {
129
+ installSpinner.stop('npm install skipped — run `npm install` manually');
130
+ }
131
+
132
+ p.log.success('Telegram enabled!');
133
+ p.log.info(`Run \`soleri telegram setup\` to configure bot token and API key.`);
134
+ });
135
+
136
+ // ─── setup ──────────────────────────────────────────────────────
137
+ tg.command('setup')
138
+ .description('Interactive Telegram configuration wizard')
139
+ .action(async () => {
140
+ const ctx = detectAgent();
141
+ if (!ctx) {
142
+ p.log.error('No agent project detected in current directory.');
143
+ process.exit(1);
144
+ return;
145
+ }
146
+
147
+ // Check that Telegram files exist
148
+ const missingFiles = TELEGRAM_FILES.filter((f) => !existsSync(join(ctx.agentPath, f)));
149
+ if (missingFiles.length > 0) {
150
+ p.log.error('Telegram is not enabled. Run `soleri telegram enable` first.');
151
+ process.exit(1);
152
+ return;
153
+ }
154
+
155
+ p.intro(`Telegram Setup for ${ctx.agentId}`);
156
+
157
+ // Step 1: Bot token
158
+ p.log.step('Step 1: Create a Telegram bot');
159
+ p.log.message(
160
+ [
161
+ ' Open Telegram and talk to @BotFather',
162
+ ' Send /newbot and follow the instructions',
163
+ ' Copy the bot token when you get it',
164
+ ].join('\n'),
165
+ );
166
+
167
+ const botToken = await p.text({
168
+ message: 'Paste your bot token:',
169
+ placeholder: '123456789:ABCdefGHIjklMNOpqrsTUVwxyz',
170
+ validate: (val) => {
171
+ if (!val || val.trim().length === 0) return 'Bot token is required';
172
+ if (!val.includes(':')) return 'Invalid token format (expected number:string)';
173
+ return undefined;
174
+ },
175
+ });
176
+ if (p.isCancel(botToken)) {
177
+ p.cancel('Setup cancelled.');
178
+ process.exit(0);
179
+ }
180
+
181
+ // Step 2: LLM API key
182
+ p.log.step('Step 2: LLM API Key');
183
+ p.log.message(' Your agent needs an API key to think');
184
+
185
+ const provider = await p.select({
186
+ message: 'Which provider?',
187
+ options: [
188
+ { value: 'anthropic', label: 'Anthropic (Claude)' },
189
+ { value: 'openai', label: 'OpenAI' },
190
+ { value: 'env', label: 'Use environment variable (skip)' },
191
+ ],
192
+ });
193
+ if (p.isCancel(provider)) {
194
+ p.cancel('Setup cancelled.');
195
+ process.exit(0);
196
+ }
197
+
198
+ let apiKey = '';
199
+ if (provider !== 'env') {
200
+ const keyPlaceholder = provider === 'anthropic' ? 'sk-ant-...' : 'sk-...';
201
+ const envHint = provider === 'anthropic' ? 'ANTHROPIC_API_KEY' : 'OPENAI_API_KEY';
202
+
203
+ const keyInput = await p.text({
204
+ message: `Paste your ${provider === 'anthropic' ? 'Anthropic' : 'OpenAI'} API key:`,
205
+ placeholder: keyPlaceholder,
206
+ validate: (val) => {
207
+ if (!val || val.trim().length === 0)
208
+ return `API key is required (or set ${envHint} env var)`;
209
+ return undefined;
210
+ },
211
+ });
212
+ if (p.isCancel(keyInput)) {
213
+ p.cancel('Setup cancelled.');
214
+ process.exit(0);
215
+ }
216
+ apiKey = keyInput;
217
+ }
218
+
219
+ // Step 3: Security
220
+ p.log.step('Step 3: Security (optional)');
221
+
222
+ const passphrase = await p.text({
223
+ message: 'Set a passphrase? Users must send this to authenticate.',
224
+ placeholder: '(empty for open access)',
225
+ defaultValue: '',
226
+ });
227
+ if (p.isCancel(passphrase)) {
228
+ p.cancel('Setup cancelled.');
229
+ process.exit(0);
230
+ }
231
+
232
+ // Step 4: Model selection
233
+ p.log.step('Step 4: Model selection');
234
+
235
+ const modelOptions =
236
+ provider === 'openai'
237
+ ? [
238
+ { value: 'gpt-4.1', label: 'gpt-4.1 (recommended)' },
239
+ { value: 'gpt-4.1-mini', label: 'gpt-4.1-mini (fast)' },
240
+ { value: 'o3', label: 'o3 (reasoning)' },
241
+ ]
242
+ : [
243
+ { value: 'claude-sonnet-4-20250514', label: 'claude-sonnet-4 (fast, recommended)' },
244
+ { value: 'claude-opus-4-20250514', label: 'claude-opus-4 (powerful)' },
245
+ { value: 'claude-haiku-3-5-20241022', label: 'claude-3.5-haiku (economical)' },
246
+ ];
247
+
248
+ const model = await p.select({
249
+ message: 'Default model:',
250
+ options: modelOptions,
251
+ });
252
+ if (p.isCancel(model)) {
253
+ p.cancel('Setup cancelled.');
254
+ process.exit(0);
255
+ }
256
+
257
+ // Save config
258
+ const configDir = join(homedir(), `.${ctx.agentId}`);
259
+ mkdirSync(configDir, { recursive: true });
260
+ const configPath = join(configDir, 'telegram.json');
261
+
262
+ const telegramConfig: Record<string, unknown> = {
263
+ botToken: (botToken as string).trim(),
264
+ ...(apiKey ? { apiKey } : {}),
265
+ provider: provider === 'env' ? 'anthropic' : provider,
266
+ model,
267
+ ...(passphrase ? { passphrase } : {}),
268
+ allowedUsers: [],
269
+ };
270
+
271
+ writeFileSync(configPath, JSON.stringify(telegramConfig, null, 2) + '\n', 'utf-8');
272
+
273
+ p.outro(`Configuration saved to ${configPath}`);
274
+
275
+ console.log('');
276
+ p.log.info(' Run: npm run telegram:start');
277
+ p.log.info(' Or: npm run telegram:dev (with auto-restart)');
278
+ console.log('');
279
+ });
280
+
281
+ // ─── disable ────────────────────────────────────────────────────
282
+ tg.command('disable')
283
+ .description('Remove Telegram transport from the current agent')
284
+ .action(async () => {
285
+ const ctx = detectAgent();
286
+ if (!ctx) {
287
+ p.log.error('No agent project detected in current directory.');
288
+ process.exit(1);
289
+ return;
290
+ }
291
+
292
+ // Check if any Telegram files exist
293
+ const existingFiles = TELEGRAM_FILES.filter((f) => existsSync(join(ctx.agentPath, f)));
294
+ if (existingFiles.length === 0) {
295
+ p.log.warn('Telegram is not enabled for this agent.');
296
+ return;
297
+ }
298
+
299
+ const confirmed = await p.confirm({
300
+ message: `Remove ${existingFiles.length} Telegram files and related config from ${ctx.agentId}?`,
301
+ });
302
+ if (p.isCancel(confirmed) || !confirmed) {
303
+ p.cancel('Cancelled.');
304
+ return;
305
+ }
306
+
307
+ // Remove Telegram source files
308
+ for (const relPath of TELEGRAM_FILES) {
309
+ const fullPath = join(ctx.agentPath, relPath);
310
+ if (existsSync(fullPath)) {
311
+ unlinkSync(fullPath);
312
+ }
313
+ }
314
+ p.log.info(`Removed ${existingFiles.length} Telegram source files`);
315
+
316
+ // Remove grammy from package.json and telegram scripts
317
+ const pkgPath = join(ctx.agentPath, 'package.json');
318
+ if (existsSync(pkgPath)) {
319
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
320
+ let changed = false;
321
+
322
+ if (pkg.dependencies?.grammy) {
323
+ delete pkg.dependencies.grammy;
324
+ changed = true;
325
+ }
326
+ if (pkg.scripts?.['telegram:start']) {
327
+ delete pkg.scripts['telegram:start'];
328
+ changed = true;
329
+ }
330
+ if (pkg.scripts?.['telegram:dev']) {
331
+ delete pkg.scripts['telegram:dev'];
332
+ changed = true;
333
+ }
334
+
335
+ if (changed) {
336
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
337
+ p.log.info('Removed grammy dependency and telegram scripts from package.json');
338
+ }
339
+ }
340
+
341
+ p.log.success('Telegram disabled.');
342
+ p.log.info('Run `npm install` to clean up node_modules.');
343
+ });
344
+
345
+ // ─── status ─────────────────────────────────────────────────────
346
+ tg.command('status')
347
+ .description('Check Telegram configuration status')
348
+ .action(() => {
349
+ const ctx = detectAgent();
350
+ if (!ctx) {
351
+ p.log.error('No agent project detected in current directory.');
352
+ process.exit(1);
353
+ return;
354
+ }
355
+
356
+ console.log(`\n Agent: ${ctx.agentId}`);
357
+
358
+ // Check source files
359
+ const presentFiles = TELEGRAM_FILES.filter((f) => existsSync(join(ctx.agentPath, f)));
360
+ const filesEnabled = presentFiles.length === TELEGRAM_FILES.length;
361
+ console.log(
362
+ ` Files: ${filesEnabled ? 'all present' : `${presentFiles.length}/${TELEGRAM_FILES.length} present`}`,
363
+ );
364
+ if (!filesEnabled && presentFiles.length > 0) {
365
+ for (const f of TELEGRAM_FILES) {
366
+ const exists = existsSync(join(ctx.agentPath, f));
367
+ console.log(` ${exists ? '+' : '-'} ${f}`);
368
+ }
369
+ }
370
+
371
+ // Check grammy dependency
372
+ const pkgPath = join(ctx.agentPath, 'package.json');
373
+ if (existsSync(pkgPath)) {
374
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
375
+ const hasGrammy = !!pkg.dependencies?.grammy;
376
+ console.log(
377
+ ` Grammy: ${hasGrammy ? `installed (${pkg.dependencies.grammy})` : 'not in dependencies'}`,
378
+ );
379
+
380
+ const hasStartScript = !!pkg.scripts?.['telegram:start'];
381
+ const hasDevScript = !!pkg.scripts?.['telegram:dev'];
382
+ console.log(
383
+ ` Scripts: ${hasStartScript && hasDevScript ? 'telegram:start, telegram:dev' : hasStartScript ? 'telegram:start only' : hasDevScript ? 'telegram:dev only' : 'none'}`,
384
+ );
385
+ }
386
+
387
+ // Check config file
388
+ const configPath = join(homedir(), `.${ctx.agentId}`, 'telegram.json');
389
+ if (existsSync(configPath)) {
390
+ try {
391
+ const config = JSON.parse(readFileSync(configPath, 'utf-8'));
392
+ const hasToken = !!config.botToken;
393
+ const hasKey = !!config.apiKey;
394
+ console.log(` Config: ${configPath}`);
395
+ console.log(` Bot token: ${hasToken ? 'set' : 'not set'}`);
396
+ console.log(` API key: ${hasKey ? 'set' : 'not set (check env vars)'}`);
397
+ if (config.model) console.log(` Model: ${config.model}`);
398
+ if (config.passphrase) console.log(` Passphrase: set`);
399
+ } catch {
400
+ console.log(` Config: ${configPath} (invalid JSON)`);
401
+ }
402
+ } else {
403
+ console.log(` Config: not found at ${configPath}`);
404
+ if (filesEnabled) {
405
+ console.log(' Run `soleri telegram setup` to configure.');
406
+ }
407
+ }
408
+
409
+ // Overall status
410
+ const ready = filesEnabled && existsSync(configPath);
411
+ console.log(`\n Status: ${ready ? 'ready to start' : 'needs configuration'}`);
412
+ if (!filesEnabled) {
413
+ console.log(' Next: Run `soleri telegram enable`');
414
+ } else if (!existsSync(configPath)) {
415
+ console.log(' Next: Run `soleri telegram setup`');
416
+ } else {
417
+ console.log(' Next: Run `npm run telegram:start` or `npm run telegram:dev`');
418
+ }
419
+ console.log('');
420
+ });
421
+ }
422
+
423
+ // ─── Helpers ──────────────────────────────────────────────────────────
424
+
425
+ /**
426
+ * Reconstruct an AgentConfig from an existing scaffolded agent.
427
+ * Mirrors the logic in agent.ts but returns a config with telegram: true.
428
+ */
429
+ function readAgentConfig(agentPath: string, agentId: string): AgentConfig | null {
430
+ // Try both locations: v6+ (src/identity/) and v5 (src/activation/)
431
+ const personaCandidates = [
432
+ join(agentPath, 'src', 'identity', 'persona.ts'),
433
+ join(agentPath, 'src', 'activation', 'persona.ts'),
434
+ ];
435
+ const personaPath = personaCandidates.find((candidate) => existsSync(candidate));
436
+ if (!personaPath) return null;
437
+ const personaSrc = readFileSync(personaPath, 'utf-8');
438
+
439
+ const name = extractStringField(personaSrc, 'name') ?? agentId;
440
+ const role = extractStringField(personaSrc, 'role') ?? '';
441
+ const description = extractStringField(personaSrc, 'description') ?? '';
442
+ const tone =
443
+ (extractStringField(personaSrc, 'tone') as 'precise' | 'mentor' | 'pragmatic') ?? 'pragmatic';
444
+ const greeting = extractStringField(personaSrc, 'greeting') ?? `Hello! I'm ${name}.`;
445
+ const principles = extractArrayField(personaSrc, 'principles');
446
+
447
+ // Read domains from entry point
448
+ const indexPath = join(agentPath, 'src', 'index.ts');
449
+ const domains = existsSync(indexPath) ? extractDomains(readFileSync(indexPath, 'utf-8')) : [];
450
+
451
+ const pkg = JSON.parse(readFileSync(join(agentPath, 'package.json'), 'utf-8'));
452
+
453
+ return {
454
+ id: agentId,
455
+ name,
456
+ role,
457
+ description,
458
+ domains,
459
+ principles,
460
+ tone,
461
+ greeting,
462
+ outputDir: agentPath,
463
+ hookPacks: [],
464
+ model: pkg.soleri?.model ?? 'claude-code-sonnet-4',
465
+ setupTarget: pkg.soleri?.setupTarget ?? 'claude',
466
+ telegram: true, // Force true — we're enabling telegram
467
+ cognee: pkg.soleri?.cognee ?? false,
468
+ };
469
+ }
470
+
471
+ function extractStringField(src: string, field: string): string | undefined {
472
+ const re = new RegExp(`${field}:\\s*'([^']*)'`);
473
+ const m = src.match(re);
474
+ return m ? m[1].replace(/\\'/g, "'") : undefined;
475
+ }
476
+
477
+ function extractArrayField(src: string, field: string): string[] {
478
+ const re = new RegExp(`${field}:\\s*\\[([\\s\\S]*?)\\]`);
479
+ const m = src.match(re);
480
+ if (!m) return [];
481
+ return [...m[1].matchAll(/'([^']*)'/g)].map((x) => x[1]);
482
+ }
483
+
484
+ function extractDomains(indexSrc: string): string[] {
485
+ const m = indexSrc.match(/createDomainFacades\(runtime,\s*['"][^'"]+['"]\s*,\s*\[([\s\S]*?)\]\)/);
486
+ if (!m) return [];
487
+ return [...m[1].matchAll(/['"]([^'"]+)['"]/g)].map((x) => x[1]);
488
+ }
@@ -5,7 +5,7 @@ import { homedir } from 'node:os';
5
5
  import * as p from '@clack/prompts';
6
6
  import { detectAgent } from '../utils/agent-context.js';
7
7
 
8
- type Target = 'claude' | 'codex' | 'both';
8
+ type Target = 'claude' | 'codex' | 'opencode' | 'both' | 'all';
9
9
 
10
10
  function uninstallClaude(agentId: string): void {
11
11
  const configPath = join(homedir(), '.claude.json');
@@ -56,6 +56,35 @@ function uninstallCodex(agentId: string): void {
56
56
  p.log.success(`Removed ${agentId} from ~/.codex/config.toml`);
57
57
  }
58
58
 
59
+ function uninstallOpencode(agentId: string): void {
60
+ const configPath = join(homedir(), '.opencode.json');
61
+
62
+ if (!existsSync(configPath)) {
63
+ p.log.warn(`~/.opencode.json not found — nothing to remove.`);
64
+ return;
65
+ }
66
+
67
+ let config: Record<string, unknown>;
68
+ try {
69
+ const raw = readFileSync(configPath, 'utf-8');
70
+ const stripped = raw.replace(/^\s*\/\/.*$/gm, '');
71
+ config = JSON.parse(stripped);
72
+ } catch {
73
+ p.log.error(`Failed to parse ${configPath}.`);
74
+ process.exit(1);
75
+ }
76
+
77
+ const servers = config.mcpServers as Record<string, unknown> | undefined;
78
+ if (!servers || !(agentId in servers)) {
79
+ p.log.warn(`${agentId} not found in ~/.opencode.json — nothing to remove.`);
80
+ return;
81
+ }
82
+
83
+ delete servers[agentId];
84
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
85
+ p.log.success(`Removed ${agentId} from ~/.opencode.json`);
86
+ }
87
+
59
88
  function escapeRegExp(s: string): string {
60
89
  return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
61
90
  }
@@ -64,7 +93,7 @@ export function registerUninstall(program: Command): void {
64
93
  program
65
94
  .command('uninstall')
66
95
  .argument('[dir]', 'Agent directory (defaults to cwd)')
67
- .option('--target <target>', 'Registration target: claude, codex, or both', 'claude')
96
+ .option('--target <target>', 'Registration target: opencode, claude, codex, or all', 'opencode')
68
97
  .description('Remove agent MCP server entry from editor config')
69
98
  .action(async (dir?: string, opts?: { target?: string }) => {
70
99
  const resolvedDir = dir ? resolve(dir) : undefined;
@@ -75,19 +104,24 @@ export function registerUninstall(program: Command): void {
75
104
  process.exit(1);
76
105
  }
77
106
 
78
- const target = (opts?.target ?? 'claude') as Target;
107
+ const target = (opts?.target ?? 'opencode') as Target;
108
+ const validTargets: Target[] = ['claude', 'codex', 'opencode', 'both', 'all'];
79
109
 
80
- if (target !== 'claude' && target !== 'codex' && target !== 'both') {
81
- p.log.error(`Invalid target "${target}". Use: claude, codex, or both`);
110
+ if (!validTargets.includes(target)) {
111
+ p.log.error(`Invalid target "${target}". Use: ${validTargets.join(', ')}`);
82
112
  process.exit(1);
83
113
  }
84
114
 
85
- if (target === 'claude' || target === 'both') {
115
+ if (target === 'claude' || target === 'both' || target === 'all') {
86
116
  uninstallClaude(ctx.agentId);
87
117
  }
88
118
 
89
- if (target === 'codex' || target === 'both') {
119
+ if (target === 'codex' || target === 'both' || target === 'all') {
90
120
  uninstallCodex(ctx.agentId);
91
121
  }
122
+
123
+ if (target === 'opencode' || target === 'all') {
124
+ uninstallOpencode(ctx.agentId);
125
+ }
92
126
  });
93
127
  }
package/src/main.ts CHANGED
@@ -5,6 +5,7 @@ import { Command } from 'commander';
5
5
  import { registerCreate } from './commands/create.js';
6
6
  import { registerList } from './commands/list.js';
7
7
  import { registerAddDomain } from './commands/add-domain.js';
8
+ import { registerAddPack } from './commands/add-pack.js';
8
9
  import { registerInstallKnowledge } from './commands/install-knowledge.js';
9
10
  import { registerDev } from './commands/dev.js';
10
11
  import { registerDoctor } from './commands/doctor.js';
@@ -18,6 +19,8 @@ import { registerUninstall } from './commands/uninstall.js';
18
19
  import { registerPack } from './commands/pack.js';
19
20
  import { registerSkills } from './commands/skills.js';
20
21
  import { registerAgent } from './commands/agent.js';
22
+ import { registerTelegram } from './commands/telegram.js';
23
+ import { registerCognee } from './commands/cognee.js';
21
24
 
22
25
  const require = createRequire(import.meta.url);
23
26
  const { version } = require('../package.json');
@@ -63,6 +66,7 @@ program
63
66
  registerCreate(program);
64
67
  registerList(program);
65
68
  registerAddDomain(program);
69
+ registerAddPack(program);
66
70
  registerInstallKnowledge(program);
67
71
  registerDev(program);
68
72
  registerDoctor(program);
@@ -76,5 +80,7 @@ registerUninstall(program);
76
80
  registerPack(program);
77
81
  registerSkills(program);
78
82
  registerAgent(program);
83
+ registerTelegram(program);
84
+ registerCognee(program);
79
85
 
80
86
  program.parse();