@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.
- package/README.md +44 -15
- package/dist/commands/agent.js +11 -7
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/create.js +11 -2
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/dev.js +11 -9
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/dream.d.ts +10 -0
- package/dist/commands/dream.js +151 -0
- package/dist/commands/dream.js.map +1 -0
- package/dist/commands/uninstall.js +161 -3
- package/dist/commands/uninstall.js.map +1 -1
- package/dist/main.js +2 -0
- package/dist/main.js.map +1 -1
- package/dist/utils/agent-artifacts.d.ts +63 -0
- package/dist/utils/agent-artifacts.js +276 -0
- package/dist/utils/agent-artifacts.js.map +1 -0
- package/package.json +1 -1
- package/src/__tests__/dream.test.ts +119 -0
- package/src/__tests__/uninstall-full.test.ts +566 -0
- package/src/commands/agent.ts +14 -10
- package/src/commands/create.ts +10 -2
- package/src/commands/dev.ts +19 -9
- package/src/commands/dream.ts +174 -0
- package/src/commands/uninstall.ts +214 -31
- package/src/main.ts +2 -0
- package/src/utils/agent-artifacts.ts +366 -0
package/src/commands/agent.ts
CHANGED
|
@@ -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
|
|
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
|
|
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);
|
package/src/commands/create.ts
CHANGED
|
@@ -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:
|
|
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 {
|
package/src/commands/dev.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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'
|
|
97
|
-
.
|
|
98
|
-
.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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();
|