@soleri/cli 9.10.0 → 9.12.1

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.
@@ -55,9 +55,10 @@ export function registerAgent(program: Command): void {
55
55
  agent
56
56
  .command('status')
57
57
  .option('--json', 'Output as JSON')
58
+ .option('--path <dir>', 'Path to agent directory (default: cwd)')
58
59
  .description('Show agent health: version, packs, vault, and update availability')
59
- .action((opts: { json?: boolean }) => {
60
- const ctx = detectAgent();
60
+ .action((opts: { json?: boolean; path?: string }) => {
61
+ const ctx = detectAgent(opts.path);
61
62
  if (!ctx) {
62
63
  p.log.error('No agent project detected in current directory.');
63
64
  process.exit(1);
@@ -104,7 +105,7 @@ export function registerAgent(program: Command): void {
104
105
  {
105
106
  agent: agentName,
106
107
  id: agentId,
107
- format: 'file-tree (v7)',
108
+ format: 'file-tree',
108
109
  engine: engineVersion,
109
110
  engineLatest: latestCore,
110
111
  domains,
@@ -120,7 +121,7 @@ export function registerAgent(program: Command): void {
120
121
 
121
122
  console.log(`\n Agent: ${agentName}`);
122
123
  console.log(` ID: ${agentId}`);
123
- console.log(` Format: file-tree (v7)`);
124
+ console.log(` Format: file-tree`);
124
125
  console.log(
125
126
  ` Engine: @soleri/core ${engineVersion}${latestCore && latestCore !== engineVersion ? ` (update available: ${latestCore})` : ''}`,
126
127
  );
@@ -205,9 +206,10 @@ export function registerAgent(program: Command): void {
205
206
  .command('update')
206
207
  .option('--check', 'Show what would change without updating')
207
208
  .option('--dry-run', 'Preview migration steps')
209
+ .option('--path <dir>', 'Path to agent directory (default: cwd)')
208
210
  .description('Update agent engine to latest compatible version')
209
- .action((opts: { check?: boolean; dryRun?: boolean }) => {
210
- const ctx = detectAgent();
211
+ .action((opts: { check?: boolean; dryRun?: boolean; path?: string }) => {
212
+ const ctx = detectAgent(opts.path);
211
213
  if (!ctx) {
212
214
  p.log.error('No agent project detected in current directory.');
213
215
  process.exit(1);
@@ -366,9 +368,10 @@ export function registerAgent(program: Command): void {
366
368
  .command('refresh')
367
369
  .option('--dry-run', 'Preview what would change without writing')
368
370
  .option('--skip-skills', 'Skip skill sync (only regenerate activation files)')
371
+ .option('--path <dir>', 'Path to agent directory (default: cwd)')
369
372
  .description('Regenerate activation files and sync skills from latest forge templates')
370
- .action((opts: { dryRun?: boolean; skipSkills?: boolean }) => {
371
- const ctx = detectAgent();
373
+ .action((opts: { dryRun?: boolean; skipSkills?: boolean; path?: string }) => {
374
+ const ctx = detectAgent(opts.path);
372
375
  if (!ctx) {
373
376
  p.log.error('No agent project detected in current directory.');
374
377
  process.exit(1);
@@ -510,9 +513,10 @@ export function registerAgent(program: Command): void {
510
513
  // ─── diff ───────────────────────────────────────────────────
511
514
  agent
512
515
  .command('diff')
516
+ .option('--path <dir>', 'Path to agent directory (default: cwd)')
513
517
  .description('Show drift between agent templates and latest engine templates')
514
- .action(() => {
515
- const ctx = detectAgent();
518
+ .action((opts: { path?: string }) => {
519
+ const ctx = detectAgent(opts.path);
516
520
  if (!ctx) {
517
521
  p.log.error('No agent project detected in current directory.');
518
522
  process.exit(1);
@@ -1,4 +1,4 @@
1
- import { accessSync, constants as fsConstants, readFileSync, existsSync } from 'node:fs';
1
+ import { accessSync, constants as fsConstants, mkdirSync, readFileSync, existsSync } from 'node:fs';
2
2
  import { resolve } from 'node:path';
3
3
  import type { Command } from 'commander';
4
4
  import * as p from '@clack/prompts';
@@ -185,7 +185,15 @@ export function registerCreate(program: Command): void {
185
185
 
186
186
  const outputDir = opts?.dir ? resolve(opts.dir) : (config.outputDir ?? process.cwd());
187
187
 
188
- // Preflight: check output directory is writable
188
+ // Preflight: ensure output directory exists and is writable
189
+ if (!existsSync(outputDir)) {
190
+ try {
191
+ mkdirSync(outputDir, { recursive: true });
192
+ } catch {
193
+ p.log.error(`Cannot create ${outputDir} — check permissions`);
194
+ process.exit(1);
195
+ }
196
+ }
189
197
  try {
190
198
  accessSync(outputDir, fsConstants.W_OK);
191
199
  } catch {
@@ -1,5 +1,5 @@
1
1
  import { spawn } from 'node:child_process';
2
- import { watch, writeFileSync } from 'node:fs';
2
+ import { existsSync, watch, writeFileSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
4
  import type { Command } from 'commander';
5
5
  import * as p from '@clack/prompts';
@@ -26,18 +26,29 @@ export function registerDev(program: Command): void {
26
26
  });
27
27
  }
28
28
 
29
- function runFileTreeDev(agentPath: string, agentId: string): void {
29
+ async function runFileTreeDev(agentPath: string, agentId: string): Promise<void> {
30
30
  p.log.info(`Starting ${agentId} in file-tree dev mode...`);
31
31
  p.log.info('Starting Knowledge Engine + watching for file changes.');
32
32
  p.log.info('CLAUDE.md will be regenerated automatically on changes.');
33
33
  p.log.info('Press Ctrl+C to stop.\n');
34
34
 
35
- regenerateClaudeMd(agentPath);
35
+ await regenerateClaudeMd(agentPath);
36
36
 
37
37
  // Start the engine server
38
38
  let engineBin: string;
39
39
  try {
40
- engineBin = require.resolve('@soleri/core/dist/engine/bin/soleri-engine.js');
40
+ const candidate = join(
41
+ agentPath,
42
+ 'node_modules',
43
+ '@soleri',
44
+ 'core',
45
+ 'dist',
46
+ 'engine',
47
+ 'bin',
48
+ 'soleri-engine.js',
49
+ );
50
+ if (!existsSync(candidate)) throw new Error('Engine not found at ' + candidate);
51
+ engineBin = candidate;
41
52
  } catch {
42
53
  p.log.error('Engine not found. Run: npm install @soleri/core');
43
54
  process.exit(1);
@@ -73,10 +84,10 @@ function runFileTreeDev(agentPath: string, agentId: string): void {
73
84
 
74
85
  // Debounce — regenerate at most once per 200ms
75
86
  if (debounceTimer) clearTimeout(debounceTimer);
76
- debounceTimer = setTimeout(() => {
87
+ debounceTimer = setTimeout(async () => {
77
88
  const changedFile = filename ? ` (${filename})` : '';
78
89
  p.log.info(`Change detected${changedFile} — regenerating CLAUDE.md`);
79
- regenerateClaudeMd(agentPath);
90
+ await regenerateClaudeMd(agentPath);
80
91
  }, 200);
81
92
  });
82
93
  } catch (err: unknown) {
@@ -105,11 +116,10 @@ function runFileTreeDev(agentPath: string, agentId: string): void {
105
116
  });
106
117
  }
107
118
 
108
- function regenerateClaudeMd(agentPath: string): void {
119
+ async function regenerateClaudeMd(agentPath: string): Promise<void> {
109
120
  try {
110
121
  // Dynamic import to avoid loading forge at CLI startup
111
- // eslint-disable-next-line @typescript-eslint/no-require-imports
112
- const { composeClaudeMd } = require('@soleri/forge/lib');
122
+ const { composeClaudeMd } = await import('@soleri/forge/lib');
113
123
  const { content } = composeClaudeMd(agentPath);
114
124
  writeFileSync(join(agentPath, 'CLAUDE.md'), content, 'utf-8');
115
125
  p.log.success('CLAUDE.md regenerated');
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Dream CLI — vault memory consolidation.
3
+ *
4
+ * `soleri dream` — run a dream pass immediately
5
+ * `soleri dream schedule [--time HH:MM]` — schedule daily cron
6
+ * `soleri dream unschedule` — remove cron entry
7
+ * `soleri dream status` — show dream status + cron info
8
+ */
9
+
10
+ import { existsSync } from 'node:fs';
11
+ import { join } from 'node:path';
12
+ import type { Command } from 'commander';
13
+ import { detectAgent } from '../utils/agent-context.js';
14
+ import { pass, fail, info, heading, dim } from '../utils/logger.js';
15
+ import { SOLERI_HOME } from '@soleri/core';
16
+
17
+ function resolveVaultDbPath(agentId: string): string | null {
18
+ const newDbPath = join(SOLERI_HOME, agentId, 'vault.db');
19
+ const legacyDbPath = join(SOLERI_HOME, '..', `.${agentId}`, 'vault.db');
20
+ if (existsSync(newDbPath)) return newDbPath;
21
+ if (existsSync(legacyDbPath)) return legacyDbPath;
22
+ return null;
23
+ }
24
+
25
+ export function registerDream(program: Command): void {
26
+ const dream = program.command('dream').description('Vault memory consolidation');
27
+
28
+ // ─── soleri dream (no subcommand) — run immediately ─────────
29
+ dream
30
+ .command('run', { isDefault: true })
31
+ .description('Run a dream pass immediately')
32
+ .action(async () => {
33
+ const agent = detectAgent();
34
+ if (!agent) {
35
+ fail('Not in a Soleri agent project', 'Run from an agent directory');
36
+ process.exit(1);
37
+ }
38
+
39
+ const dbPath = resolveVaultDbPath(agent.agentId);
40
+ if (!dbPath) {
41
+ fail('Vault DB not found', 'Run the agent once to initialize its vault database.');
42
+ process.exit(1);
43
+ }
44
+
45
+ const { Vault, Curator, DreamEngine, ensureDreamSchema } = await import('@soleri/core');
46
+ const vault = new Vault(dbPath);
47
+
48
+ try {
49
+ ensureDreamSchema(vault.getProvider());
50
+ const curator = new Curator(vault);
51
+ const engine = new DreamEngine(vault, curator);
52
+
53
+ heading('Dream — Memory Consolidation');
54
+ info('Running dream pass...');
55
+ console.log();
56
+
57
+ const report = engine.run();
58
+
59
+ pass('Dream pass complete');
60
+ console.log();
61
+ console.log(' Field Value');
62
+ console.log(' ───────────────────── ──────────────');
63
+ dim(` Duration ${report.durationMs}ms`);
64
+ dim(` Duplicates found ${report.duplicatesFound}`);
65
+ dim(` Stale archived ${report.staleArchived}`);
66
+ dim(` Contradictions found ${report.contradictionsFound}`);
67
+ dim(` Total dreams ${report.totalDreams}`);
68
+ dim(` Timestamp ${report.timestamp}`);
69
+ console.log();
70
+ } finally {
71
+ vault.close();
72
+ }
73
+ });
74
+
75
+ // ─── soleri dream schedule ──────────────────────────────────
76
+ dream
77
+ .command('schedule')
78
+ .description('Schedule daily dream cron job')
79
+ .option('--time <HH:MM>', 'Time to run (24h format)', '22:00')
80
+ .action(async (opts: { time: string }) => {
81
+ const agent = detectAgent();
82
+ if (!agent) {
83
+ fail('Not in a Soleri agent project', 'Run from an agent directory');
84
+ process.exit(1);
85
+ }
86
+
87
+ const { scheduleDream } = await import('@soleri/core');
88
+
89
+ const result = scheduleDream(opts.time, agent.agentPath);
90
+
91
+ if (!result.isScheduled) {
92
+ fail('Failed to schedule dream cron', 'Check time format (HH:MM) and crontab access.');
93
+ process.exit(1);
94
+ }
95
+
96
+ heading('Dream — Scheduled');
97
+ pass(`Daily dream scheduled at ${result.time}`);
98
+ dim(` Log path: ${result.logPath}`);
99
+ dim(` Project: ${result.projectDir}`);
100
+ console.log();
101
+ });
102
+
103
+ // ─── soleri dream unschedule ────────────────────────────────
104
+ dream
105
+ .command('unschedule')
106
+ .description('Remove dream cron entry')
107
+ .action(async () => {
108
+ const { unscheduleDream } = await import('@soleri/core');
109
+
110
+ unscheduleDream();
111
+
112
+ heading('Dream — Unscheduled');
113
+ pass('Dream cron entry removed');
114
+ console.log();
115
+ });
116
+
117
+ // ─── soleri dream status ────────────────────────────────────
118
+ dream
119
+ .command('status')
120
+ .description('Show dream status and cron info')
121
+ .action(async () => {
122
+ const agent = detectAgent();
123
+ if (!agent) {
124
+ fail('Not in a Soleri agent project', 'Run from an agent directory');
125
+ process.exit(1);
126
+ }
127
+
128
+ const dbPath = resolveVaultDbPath(agent.agentId);
129
+
130
+ heading('Dream — Status');
131
+
132
+ // Dream engine status (only if vault exists)
133
+ if (dbPath) {
134
+ const { Vault, Curator, DreamEngine, ensureDreamSchema } = await import('@soleri/core');
135
+ const vault = new Vault(dbPath);
136
+
137
+ try {
138
+ ensureDreamSchema(vault.getProvider());
139
+ const curator = new Curator(vault);
140
+ const engine = new DreamEngine(vault, curator);
141
+ const status = engine.getStatus();
142
+
143
+ console.log(' Field Value');
144
+ console.log(' ──────────────────────── ──────────────');
145
+ dim(` Sessions since last ${status.sessionsSinceLastDream}`);
146
+ dim(` Last dream at ${status.lastDreamAt ?? 'never'}`);
147
+ dim(
148
+ ` Last duration ${status.lastDreamDurationMs !== null ? `${status.lastDreamDurationMs}ms` : 'n/a'}`,
149
+ );
150
+ dim(` Total dreams ${status.totalDreams}`);
151
+ dim(` Gate eligible ${status.gateEligible ? 'yes' : 'no'}`);
152
+ } finally {
153
+ vault.close();
154
+ }
155
+ } else {
156
+ info('Vault DB not found — dream engine status unavailable.');
157
+ }
158
+
159
+ console.log();
160
+
161
+ // Cron schedule status
162
+ const { getDreamSchedule } = await import('@soleri/core');
163
+ const cron = getDreamSchedule();
164
+
165
+ if (cron.isScheduled) {
166
+ pass(`Cron scheduled at ${cron.time}`);
167
+ dim(` Log path: ${cron.logPath}`);
168
+ dim(` Project: ${cron.projectDir}`);
169
+ } else {
170
+ info('No cron schedule configured. Run `soleri dream schedule` to set one.');
171
+ }
172
+ console.log();
173
+ });
174
+ }
@@ -4,6 +4,16 @@ import { join, resolve } from 'node:path';
4
4
  import { homedir } from 'node:os';
5
5
  import * as p from '@clack/prompts';
6
6
  import { detectAgent } from '../utils/agent-context.js';
7
+ import {
8
+ detectArtifacts,
9
+ removeDirectory,
10
+ removeClaudeMdBlock,
11
+ removePermissionEntries,
12
+ removeLauncherScript,
13
+ type ArtifactManifest,
14
+ type RemovalResult,
15
+ } from '../utils/agent-artifacts.js';
16
+ import { pass, fail, warn, skip, heading, dim } from '../utils/logger.js';
7
17
 
8
18
  type Target = 'claude' | 'codex' | 'opencode' | 'both' | 'all';
9
19
 
@@ -89,39 +99,212 @@ function escapeRegExp(s: string): string {
89
99
  return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
90
100
  }
91
101
 
102
+ // ---------------------------------------------------------------------------
103
+ // Full uninstall helpers
104
+ // ---------------------------------------------------------------------------
105
+
106
+ function countArtifacts(manifest: ArtifactManifest): number {
107
+ let count = 0;
108
+ if (manifest.projectDir?.exists) count++;
109
+ if (manifest.dataDir?.exists) count++;
110
+ if (manifest.dataDirLegacy?.exists) count++;
111
+ count += manifest.claudeMdBlocks.length;
112
+ count += manifest.mcpServerEntries.length;
113
+ if (manifest.permissionEntries.length > 0) count++;
114
+ if (manifest.launcherScript?.exists) count++;
115
+ return count;
116
+ }
117
+
118
+ function displayManifest(manifest: ArtifactManifest): void {
119
+ heading(`Artifacts for "${manifest.agentId}"`);
120
+
121
+ const show = (label: string, loc: { path: string; exists: boolean } | null) => {
122
+ if (loc?.exists) warn(label, loc.path);
123
+ else dim(`${label} — not found`);
124
+ };
125
+
126
+ show('Project directory', manifest.projectDir);
127
+ show('Data directory', manifest.dataDir);
128
+ show('Data directory (legacy)', manifest.dataDirLegacy);
129
+ show('Launcher script', manifest.launcherScript);
130
+
131
+ if (manifest.claudeMdBlocks.length > 0) {
132
+ for (const block of manifest.claudeMdBlocks) {
133
+ warn('CLAUDE.md block', `${block.path} (lines ${block.startLine}-${block.endLine})`);
134
+ }
135
+ } else {
136
+ dim('CLAUDE.md blocks — none found');
137
+ }
138
+
139
+ if (manifest.mcpServerEntries.length > 0) {
140
+ for (const entry of manifest.mcpServerEntries) {
141
+ warn(`MCP server (${entry.target})`, `${entry.file} → ${entry.key}`);
142
+ }
143
+ } else {
144
+ dim('MCP server entries — none found');
145
+ }
146
+
147
+ if (manifest.permissionEntries.length > 0) {
148
+ for (const pe of manifest.permissionEntries) {
149
+ warn(`Permissions (${pe.matches.length} entries)`, pe.file);
150
+ }
151
+ } else {
152
+ dim('Permission entries — none found');
153
+ }
154
+ }
155
+
156
+ function reportResult(label: string, result: RemovalResult): void {
157
+ if (result.removed) pass(label, result.path);
158
+ else if (result.error) fail(label, result.error);
159
+ else skip(label, result.path);
160
+ }
161
+
162
+ async function fullUninstall(
163
+ agentId: string,
164
+ agentDir: string | undefined,
165
+ target: Target,
166
+ dryRun: boolean,
167
+ force: boolean,
168
+ ): Promise<void> {
169
+ const manifest = detectArtifacts(agentId, agentDir);
170
+ const total = countArtifacts(manifest);
171
+
172
+ displayManifest(manifest);
173
+
174
+ if (total === 0) {
175
+ p.log.info('Nothing to remove.');
176
+ process.exit(2);
177
+ }
178
+
179
+ console.log(`\n Found ${total} artifact(s) to remove.\n`);
180
+
181
+ if (dryRun) {
182
+ p.log.info('Dry run — no changes made.');
183
+ return;
184
+ }
185
+
186
+ if (!force) {
187
+ const confirmed = await p.confirm({
188
+ message: `Remove all artifacts for "${agentId}"? This cannot be undone.`,
189
+ });
190
+ if (p.isCancel(confirmed) || !confirmed) {
191
+ p.log.info('Cancelled.');
192
+ return;
193
+ }
194
+ }
195
+
196
+ let removed = 0;
197
+ heading('Removing artifacts');
198
+
199
+ // 1. MCP server entries (existing logic)
200
+ if (target === 'claude' || target === 'both' || target === 'all') {
201
+ uninstallClaude(agentId);
202
+ }
203
+ if (target === 'codex' || target === 'both' || target === 'all') {
204
+ uninstallCodex(agentId);
205
+ }
206
+ if (target === 'opencode' || target === 'all') {
207
+ uninstallOpencode(agentId);
208
+ }
209
+ removed += manifest.mcpServerEntries.length;
210
+
211
+ // 2. Permission entries
212
+ for (const pe of manifest.permissionEntries) {
213
+ const result = await removePermissionEntries(pe.file, agentId);
214
+ reportResult(`Permissions (${pe.matches.length} entries)`, result);
215
+ if (result.removed) removed++;
216
+ }
217
+
218
+ // 3. CLAUDE.md blocks (reverse order to preserve line numbers)
219
+ const sortedBlocks = [...manifest.claudeMdBlocks].sort((a, b) => b.startLine - a.startLine);
220
+ for (const block of sortedBlocks) {
221
+ const result = await removeClaudeMdBlock(block.path, block.startLine, block.endLine);
222
+ reportResult('CLAUDE.md block', result);
223
+ if (result.removed) removed++;
224
+ }
225
+
226
+ // 4. Launcher script
227
+ if (manifest.launcherScript?.exists) {
228
+ const result = await removeLauncherScript(manifest.launcherScript.path);
229
+ reportResult('Launcher script', result);
230
+ if (result.removed) removed++;
231
+ }
232
+
233
+ // 5. Data directories
234
+ if (manifest.dataDir?.exists) {
235
+ const result = await removeDirectory(manifest.dataDir.path);
236
+ reportResult('Data directory', result);
237
+ if (result.removed) removed++;
238
+ }
239
+ if (manifest.dataDirLegacy?.exists) {
240
+ const result = await removeDirectory(manifest.dataDirLegacy.path);
241
+ reportResult('Data directory (legacy)', result);
242
+ if (result.removed) removed++;
243
+ }
244
+
245
+ // 6. Project directory (last — most destructive)
246
+ if (manifest.projectDir?.exists) {
247
+ const result = await removeDirectory(manifest.projectDir.path);
248
+ reportResult('Project directory', result);
249
+ if (result.removed) removed++;
250
+ }
251
+
252
+ console.log(`\n Removed ${removed}/${total} artifacts for "${agentId}".\n`);
253
+ }
254
+
255
+ // ---------------------------------------------------------------------------
256
+ // Command registration
257
+ // ---------------------------------------------------------------------------
258
+
92
259
  export function registerUninstall(program: Command): void {
93
260
  program
94
261
  .command('uninstall')
95
262
  .argument('[dir]', 'Agent directory (defaults to cwd)')
96
- .option('--target <target>', 'Registration target: opencode, claude, codex, or all', 'opencode')
97
- .description('Remove agent MCP server entry from editor config')
98
- .action(async (dir?: string, opts?: { target?: string }) => {
99
- const resolvedDir = dir ? resolve(dir) : undefined;
100
- const ctx = detectAgent(resolvedDir);
101
-
102
- if (!ctx) {
103
- p.log.error('Not in an agent project. Run from an agent directory or pass its path.');
104
- process.exit(1);
105
- }
106
-
107
- const target = (opts?.target ?? 'opencode') as Target;
108
- const validTargets: Target[] = ['claude', 'codex', 'opencode', 'both', 'all'];
109
-
110
- if (!validTargets.includes(target)) {
111
- p.log.error(`Invalid target "${target}". Use: ${validTargets.join(', ')}`);
112
- process.exit(1);
113
- }
114
-
115
- if (target === 'claude' || target === 'both' || target === 'all') {
116
- uninstallClaude(ctx.agentId);
117
- }
118
-
119
- if (target === 'codex' || target === 'both' || target === 'all') {
120
- uninstallCodex(ctx.agentId);
121
- }
122
-
123
- if (target === 'opencode' || target === 'all') {
124
- uninstallOpencode(ctx.agentId);
125
- }
126
- });
263
+ .option('--target <target>', 'Registration target: opencode, claude, codex, or all')
264
+ .option('--full', 'Remove all agent artifacts (project, data, configs, permissions, launcher)')
265
+ .option('--dry-run', 'Show what would be removed without making changes')
266
+ .option('--force', 'Skip confirmation prompt')
267
+ .description('Remove agent MCP server entries (or all artifacts with --full)')
268
+ .action(
269
+ async (
270
+ dir?: string,
271
+ opts?: { target?: string; full?: boolean; dryRun?: boolean; force?: boolean },
272
+ ) => {
273
+ const resolvedDir = dir ? resolve(dir) : undefined;
274
+ const ctx = detectAgent(resolvedDir);
275
+
276
+ if (!ctx) {
277
+ p.log.error('Not in an agent project. Run from an agent directory or pass its path.');
278
+ process.exit(1);
279
+ }
280
+
281
+ // Default: 'all' when --full, 'opencode' otherwise
282
+ const defaultTarget = opts?.full ? 'all' : 'opencode';
283
+ const target = (opts?.target ?? defaultTarget) as Target;
284
+ const validTargets: Target[] = ['claude', 'codex', 'opencode', 'both', 'all'];
285
+
286
+ if (!validTargets.includes(target)) {
287
+ p.log.error(`Invalid target "${target}". Use: ${validTargets.join(', ')}`);
288
+ process.exit(1);
289
+ }
290
+
291
+ if (opts?.full) {
292
+ await fullUninstall(ctx.agentId, resolvedDir, target, !!opts.dryRun, !!opts.force);
293
+ return;
294
+ }
295
+
296
+ // Default: MCP-only removal (backward compatible)
297
+ if (target === 'claude' || target === 'both' || target === 'all') {
298
+ uninstallClaude(ctx.agentId);
299
+ }
300
+
301
+ if (target === 'codex' || target === 'both' || target === 'all') {
302
+ uninstallCodex(ctx.agentId);
303
+ }
304
+
305
+ if (target === 'opencode' || target === 'all') {
306
+ uninstallOpencode(ctx.agentId);
307
+ }
308
+ },
309
+ );
127
310
  }
package/src/main.ts CHANGED
@@ -31,6 +31,7 @@ import { registerTelegram } from './commands/telegram.js';
31
31
  import { registerStaging } from './commands/staging.js';
32
32
  import { registerVault } from './commands/vault.js';
33
33
  import { registerYolo } from './commands/yolo.js';
34
+ import { registerDream } from './commands/dream.js';
34
35
 
35
36
  const require = createRequire(import.meta.url);
36
37
  const { version } = require('../package.json');
@@ -94,4 +95,5 @@ registerTelegram(program);
94
95
  registerStaging(program);
95
96
  registerVault(program);
96
97
  registerYolo(program);
98
+ registerDream(program);
97
99
  program.parse();