@soleri/cli 9.0.2 → 9.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/agent.js +116 -3
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/create.js +6 -2
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/hooks.js +36 -13
- package/dist/commands/hooks.js.map +1 -1
- package/dist/commands/install.d.ts +1 -0
- package/dist/commands/install.js +61 -12
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/pack.js +0 -1
- package/dist/commands/pack.js.map +1 -1
- package/dist/commands/staging.d.ts +2 -0
- package/dist/commands/staging.js +175 -0
- package/dist/commands/staging.js.map +1 -0
- package/dist/hook-packs/full/manifest.json +2 -2
- package/dist/hook-packs/installer.d.ts +4 -11
- package/dist/hook-packs/installer.js +197 -23
- package/dist/hook-packs/installer.js.map +1 -1
- package/dist/hook-packs/installer.ts +223 -38
- package/dist/hook-packs/registry.d.ts +16 -13
- package/dist/hook-packs/registry.js +11 -18
- package/dist/hook-packs/registry.js.map +1 -1
- package/dist/hook-packs/registry.ts +31 -30
- package/dist/hook-packs/yolo-safety/manifest.json +23 -0
- package/dist/hook-packs/yolo-safety/scripts/anti-deletion.sh +214 -0
- package/dist/hooks/templates.js +1 -1
- package/dist/hooks/templates.js.map +1 -1
- package/dist/main.js +2 -0
- package/dist/main.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/create.test.ts +6 -2
- package/src/__tests__/hook-packs.test.ts +67 -25
- package/src/__tests__/wizard-e2e.mjs +153 -58
- package/src/commands/agent.ts +146 -3
- package/src/commands/create.ts +8 -2
- package/src/commands/hooks.ts +36 -31
- package/src/commands/install.ts +65 -22
- package/src/commands/pack.ts +0 -1
- package/src/commands/staging.ts +208 -0
- package/src/hook-packs/full/manifest.json +2 -2
- package/src/hook-packs/installer.ts +223 -38
- package/src/hook-packs/registry.ts +31 -30
- package/src/hook-packs/yolo-safety/manifest.json +23 -0
- package/src/hook-packs/yolo-safety/scripts/anti-deletion.sh +214 -0
- package/src/hooks/templates.ts +1 -1
- package/src/main.ts +2 -0
- package/dist/commands/cognee.d.ts +0 -10
- package/dist/commands/cognee.js +0 -364
- package/dist/commands/cognee.js.map +0 -1
|
@@ -35,11 +35,13 @@ function assert(cond, msg, ctx = '') {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
function stripAnsi(s) {
|
|
38
|
+
/* oxlint-disable eslint(no-control-regex) -- intentional ANSI control char stripping */
|
|
38
39
|
// eslint-disable-next-line no-control-regex
|
|
39
40
|
return s
|
|
40
41
|
.replace(new RegExp('\x1B\\[[0-9;]*[A-Za-z]', 'g'), '')
|
|
41
42
|
.replace(new RegExp('\x1B\\].*?\x07', 'g'), '')
|
|
42
43
|
.replace(new RegExp('\r', 'g'), '');
|
|
44
|
+
/* oxlint-enable eslint(no-control-regex) */
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
function sleep(ms) {
|
|
@@ -68,8 +70,12 @@ function runWizard(name, actions, opts = {}) {
|
|
|
68
70
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
69
71
|
});
|
|
70
72
|
|
|
71
|
-
proc.stdout.on('data', (d) => {
|
|
72
|
-
|
|
73
|
+
proc.stdout.on('data', (d) => {
|
|
74
|
+
buffer += d.toString();
|
|
75
|
+
});
|
|
76
|
+
proc.stderr.on('data', (d) => {
|
|
77
|
+
buffer += d.toString();
|
|
78
|
+
});
|
|
73
79
|
|
|
74
80
|
async function drive() {
|
|
75
81
|
while (actionIndex < actions.length && !state.completed) {
|
|
@@ -82,6 +88,7 @@ function runWizard(name, actions, opts = {}) {
|
|
|
82
88
|
|
|
83
89
|
if (matched) {
|
|
84
90
|
actionIndex++;
|
|
91
|
+
// oxlint-disable-next-line eslint(no-await-in-loop)
|
|
85
92
|
await sleep(a.delay || 150);
|
|
86
93
|
if (!state.completed) {
|
|
87
94
|
try {
|
|
@@ -89,6 +96,7 @@ function runWizard(name, actions, opts = {}) {
|
|
|
89
96
|
} catch {}
|
|
90
97
|
}
|
|
91
98
|
} else {
|
|
99
|
+
// oxlint-disable-next-line eslint(no-await-in-loop)
|
|
92
100
|
await sleep(100);
|
|
93
101
|
}
|
|
94
102
|
}
|
|
@@ -116,7 +124,9 @@ function runWizard(name, actions, opts = {}) {
|
|
|
116
124
|
clearInterval(poller);
|
|
117
125
|
proc.kill('SIGTERM');
|
|
118
126
|
setTimeout(() => {
|
|
119
|
-
try {
|
|
127
|
+
try {
|
|
128
|
+
proc.kill('SIGKILL');
|
|
129
|
+
} catch {}
|
|
120
130
|
resolve({
|
|
121
131
|
exitCode: -1,
|
|
122
132
|
output: stripAnsi(buffer) + '\n[TIMEOUT]',
|
|
@@ -153,13 +163,62 @@ function archetypeActions(outDir, { downCount = 0 } = {}) {
|
|
|
153
163
|
// Note: agentId is slugify(label), not the archetype value.
|
|
154
164
|
// e.g., "Full-Stack Assistant" → "full-stack-assistant"
|
|
155
165
|
const ARCHETYPES = [
|
|
156
|
-
{
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
166
|
+
{
|
|
167
|
+
value: 'code-reviewer',
|
|
168
|
+
agentId: 'code-reviewer',
|
|
169
|
+
label: 'Code Reviewer',
|
|
170
|
+
tone: 'mentor',
|
|
171
|
+
totalSkills: 10,
|
|
172
|
+
downCount: 0,
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
value: 'security-auditor',
|
|
176
|
+
agentId: 'security-auditor',
|
|
177
|
+
label: 'Security Auditor',
|
|
178
|
+
tone: 'precise',
|
|
179
|
+
totalSkills: 10,
|
|
180
|
+
downCount: 1,
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
value: 'api-architect',
|
|
184
|
+
agentId: 'api-architect',
|
|
185
|
+
label: 'API Architect',
|
|
186
|
+
tone: 'pragmatic',
|
|
187
|
+
totalSkills: 10,
|
|
188
|
+
downCount: 2,
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
value: 'test-engineer',
|
|
192
|
+
agentId: 'test-engineer',
|
|
193
|
+
label: 'Test Engineer',
|
|
194
|
+
tone: 'mentor',
|
|
195
|
+
totalSkills: 10,
|
|
196
|
+
downCount: 3,
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
value: 'devops-pilot',
|
|
200
|
+
agentId: 'devops-pilot',
|
|
201
|
+
label: 'DevOps Pilot',
|
|
202
|
+
tone: 'pragmatic',
|
|
203
|
+
totalSkills: 10,
|
|
204
|
+
downCount: 4,
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
value: 'database-architect',
|
|
208
|
+
agentId: 'database-architect',
|
|
209
|
+
label: 'Database Architect',
|
|
210
|
+
tone: 'precise',
|
|
211
|
+
totalSkills: 10,
|
|
212
|
+
downCount: 5,
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
value: 'full-stack',
|
|
216
|
+
agentId: 'full-stack-assistant',
|
|
217
|
+
label: 'Full-Stack Assistant',
|
|
218
|
+
tone: 'mentor',
|
|
219
|
+
totalSkills: 11,
|
|
220
|
+
downCount: 6,
|
|
221
|
+
},
|
|
163
222
|
];
|
|
164
223
|
|
|
165
224
|
// ══════════════════════════════════════════════════════════
|
|
@@ -168,43 +227,55 @@ const ARCHETYPES = [
|
|
|
168
227
|
|
|
169
228
|
async function testCancelArchetype() {
|
|
170
229
|
console.log('\n [1/14] Cancel at archetype (Ctrl+C)');
|
|
171
|
-
const r = await runWizard('cancel-arch', [
|
|
172
|
-
|
|
173
|
-
|
|
230
|
+
const r = await runWizard('cancel-arch', [{ waitFor: 'kind of agent', send: CTRL_C }], {
|
|
231
|
+
timeout: 15000,
|
|
232
|
+
});
|
|
174
233
|
assert(r.actionsCompleted >= 1, 'prompt reached', 'cancel-archetype');
|
|
175
234
|
}
|
|
176
235
|
|
|
177
236
|
async function testCancelName() {
|
|
178
237
|
console.log('\n [2/14] Cancel at display name');
|
|
179
|
-
const r = await runWizard(
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
238
|
+
const r = await runWizard(
|
|
239
|
+
'cancel-name',
|
|
240
|
+
[
|
|
241
|
+
{ waitFor: 'kind of agent', send: SPACE + ENTER },
|
|
242
|
+
{ waitFor: 'Display name', send: CTRL_C },
|
|
243
|
+
],
|
|
244
|
+
{ timeout: 15000 },
|
|
245
|
+
);
|
|
183
246
|
assert(r.actionsCompleted >= 2, 'reached name prompt', 'cancel-name');
|
|
184
247
|
}
|
|
185
248
|
|
|
186
249
|
async function testCancelRole() {
|
|
187
250
|
console.log('\n [3/14] Cancel at role');
|
|
188
|
-
const r = await runWizard(
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
251
|
+
const r = await runWizard(
|
|
252
|
+
'cancel-role',
|
|
253
|
+
[
|
|
254
|
+
{ waitFor: 'kind of agent', send: SPACE + ENTER },
|
|
255
|
+
{ waitFor: 'Display name', send: ENTER },
|
|
256
|
+
{ waitFor: 'Role', send: CTRL_C },
|
|
257
|
+
],
|
|
258
|
+
{ timeout: 15000 },
|
|
259
|
+
);
|
|
193
260
|
assert(r.actionsCompleted >= 3, 'reached role prompt', 'cancel-role');
|
|
194
261
|
}
|
|
195
262
|
|
|
196
263
|
async function testCancelSkills() {
|
|
197
264
|
console.log('\n [4/14] Cancel at skills');
|
|
198
|
-
const r = await runWizard(
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
265
|
+
const r = await runWizard(
|
|
266
|
+
'cancel-skills',
|
|
267
|
+
[
|
|
268
|
+
{ waitFor: 'kind of agent', send: SPACE + ENTER },
|
|
269
|
+
{ waitFor: 'Display name', send: ENTER },
|
|
270
|
+
{ waitFor: 'Role', send: ENTER },
|
|
271
|
+
{ waitFor: 'Description', send: ENTER },
|
|
272
|
+
{ waitFor: /domain|expertise/i, send: ENTER },
|
|
273
|
+
{ waitFor: /principle|guiding/i, send: ENTER },
|
|
274
|
+
{ waitFor: /tone/i, send: ENTER },
|
|
275
|
+
{ waitFor: /skill/i, send: CTRL_C },
|
|
276
|
+
],
|
|
277
|
+
{ timeout: 15000 },
|
|
278
|
+
);
|
|
208
279
|
assert(r.actionsCompleted >= 8, 'reached skills prompt', 'cancel-skills');
|
|
209
280
|
}
|
|
210
281
|
|
|
@@ -217,20 +288,24 @@ async function testDeclineConfirm() {
|
|
|
217
288
|
const outDir = join(TEST_ROOT, 'decline');
|
|
218
289
|
mkdirSync(outDir, { recursive: true });
|
|
219
290
|
|
|
220
|
-
const r = await runWizard(
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
291
|
+
const r = await runWizard(
|
|
292
|
+
'decline',
|
|
293
|
+
[
|
|
294
|
+
{ waitFor: 'kind of agent', send: SPACE + ENTER },
|
|
295
|
+
{ waitFor: 'Display name', send: ENTER },
|
|
296
|
+
{ waitFor: 'Role', send: ENTER },
|
|
297
|
+
{ waitFor: 'Description', send: ENTER },
|
|
298
|
+
{ waitFor: /domain|expertise/i, send: ENTER },
|
|
299
|
+
{ waitFor: /principle|guiding/i, send: ENTER },
|
|
300
|
+
{ waitFor: /tone/i, send: ENTER },
|
|
301
|
+
{ waitFor: /skill/i, send: ENTER },
|
|
302
|
+
{ waitFor: /greeting/i, send: ENTER },
|
|
303
|
+
{ waitFor: /output|directory/i, send: CTRL_U + outDir + ENTER, delay: 300 },
|
|
304
|
+
{ waitFor: /hook|pack/i, send: ENTER },
|
|
305
|
+
{ waitFor: /create agent/i, send: LEFT + ENTER },
|
|
306
|
+
],
|
|
307
|
+
{ timeout: 15000 },
|
|
308
|
+
);
|
|
234
309
|
|
|
235
310
|
assert(r.actionsCompleted >= 12, `all prompts reached (${r.actionsCompleted}/12)`, 'decline');
|
|
236
311
|
assert(!existsSync(join(outDir, 'code-reviewer', 'package.json')), 'no agent created', 'decline');
|
|
@@ -260,8 +335,11 @@ async function testArchetype(arch, idx) {
|
|
|
260
335
|
const personaPath = join(ad, 'src', 'identity', 'persona.ts');
|
|
261
336
|
if (existsSync(personaPath)) {
|
|
262
337
|
const persona = readFileSync(personaPath, 'utf-8');
|
|
263
|
-
assert(
|
|
264
|
-
`
|
|
338
|
+
assert(
|
|
339
|
+
persona.includes(`'${arch.label}'`) || persona.includes(`"${arch.label}"`),
|
|
340
|
+
`name = ${arch.label}`,
|
|
341
|
+
ctx,
|
|
342
|
+
);
|
|
265
343
|
assert(persona.includes(`tone: '${arch.tone}'`), `tone = ${arch.tone}`, ctx);
|
|
266
344
|
} else {
|
|
267
345
|
assert(false, 'persona.ts exists', ctx);
|
|
@@ -271,9 +349,21 @@ async function testArchetype(arch, idx) {
|
|
|
271
349
|
const skillsDir = join(ad, 'skills');
|
|
272
350
|
if (existsSync(skillsDir)) {
|
|
273
351
|
const skills = readdirSync(skillsDir);
|
|
274
|
-
assert(
|
|
352
|
+
assert(
|
|
353
|
+
skills.length === arch.totalSkills,
|
|
354
|
+
`${arch.totalSkills} skills (got ${skills.length})`,
|
|
355
|
+
ctx,
|
|
356
|
+
);
|
|
275
357
|
// Core skills always present
|
|
276
|
-
for (const core of [
|
|
358
|
+
for (const core of [
|
|
359
|
+
'brainstorming',
|
|
360
|
+
'systematic-debugging',
|
|
361
|
+
'verification-before-completion',
|
|
362
|
+
'health-check',
|
|
363
|
+
'context-resume',
|
|
364
|
+
'writing-plans',
|
|
365
|
+
'executing-plans',
|
|
366
|
+
]) {
|
|
277
367
|
assert(skills.includes(core), `core skill: ${core}`, ctx);
|
|
278
368
|
}
|
|
279
369
|
} else {
|
|
@@ -310,8 +400,9 @@ async function testCustomArchetype() {
|
|
|
310
400
|
const customName = 'GraphQL Guardian';
|
|
311
401
|
const customId = 'graphql-guardian';
|
|
312
402
|
const customRole = 'Validates GraphQL schemas against federation rules';
|
|
313
|
-
const customDesc =
|
|
314
|
-
|
|
403
|
+
const customDesc =
|
|
404
|
+
'This agent checks GraphQL schemas for breaking changes, naming conventions, and federation compatibility across subgraphs.';
|
|
405
|
+
const customGreeting = 'Hey! Drop your GraphQL schema and I will check it for issues.';
|
|
315
406
|
|
|
316
407
|
const r = await runWizard('custom', [
|
|
317
408
|
// Step 1: Select "✦ Create Custom" (9 downs — 9 archetypes before _custom)
|
|
@@ -422,7 +513,11 @@ async function testHookPacks() {
|
|
|
422
513
|
// Validate hooks were installed
|
|
423
514
|
const output = r.output;
|
|
424
515
|
assert(output.includes('a11y') && output.includes('installed'), 'a11y pack installed', ctx);
|
|
425
|
-
assert(
|
|
516
|
+
assert(
|
|
517
|
+
output.includes('typescript-safety') && output.includes('installed'),
|
|
518
|
+
'typescript-safety pack installed',
|
|
519
|
+
ctx,
|
|
520
|
+
);
|
|
426
521
|
|
|
427
522
|
// Check .claude directory has hooks
|
|
428
523
|
const claudeDir = join(ad, '.claude');
|
|
@@ -431,7 +526,9 @@ async function testHookPacks() {
|
|
|
431
526
|
assert(files.length > 0, `.claude/ has hook files (${files.length})`, ctx);
|
|
432
527
|
}
|
|
433
528
|
|
|
434
|
-
console.log(
|
|
529
|
+
console.log(
|
|
530
|
+
` exit=${r.exitCode}, agent=${existsSync(ad)}, hooks=${r.output.includes('installed')}`,
|
|
531
|
+
);
|
|
435
532
|
}
|
|
436
533
|
|
|
437
534
|
// ══════════════════════════════════════════════════════════
|
|
@@ -455,6 +552,7 @@ await testDeclineConfirm();
|
|
|
455
552
|
|
|
456
553
|
// All 7 archetypes (each scaffolds + builds, slower)
|
|
457
554
|
for (let i = 0; i < ARCHETYPES.length; i++) {
|
|
555
|
+
// oxlint-disable-next-line eslint(no-await-in-loop)
|
|
458
556
|
await testArchetype(ARCHETYPES[i], i);
|
|
459
557
|
}
|
|
460
558
|
|
|
@@ -469,10 +567,7 @@ rmSync(TEST_ROOT, { recursive: true, force: true });
|
|
|
469
567
|
|
|
470
568
|
// Clean up any MCP registrations
|
|
471
569
|
try {
|
|
472
|
-
const claudeJson = join(
|
|
473
|
-
process.env.HOME || process.env.USERPROFILE || '',
|
|
474
|
-
'.claude.json',
|
|
475
|
-
);
|
|
570
|
+
const claudeJson = join(process.env.HOME || process.env.USERPROFILE || '', '.claude.json');
|
|
476
571
|
if (existsSync(claudeJson)) {
|
|
477
572
|
const c = JSON.parse(readFileSync(claudeJson, 'utf-8'));
|
|
478
573
|
let changed = false;
|
package/src/commands/agent.ts
CHANGED
|
@@ -5,12 +5,22 @@
|
|
|
5
5
|
* `soleri agent update` — OTA engine upgrade with migration support.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { join } from 'node:path';
|
|
9
|
-
import {
|
|
8
|
+
import { join, dirname } from 'node:path';
|
|
9
|
+
import {
|
|
10
|
+
existsSync,
|
|
11
|
+
readFileSync,
|
|
12
|
+
readdirSync,
|
|
13
|
+
writeFileSync,
|
|
14
|
+
mkdirSync,
|
|
15
|
+
renameSync,
|
|
16
|
+
cpSync,
|
|
17
|
+
rmSync,
|
|
18
|
+
} from 'node:fs';
|
|
19
|
+
import { homedir } from 'node:os';
|
|
10
20
|
import { execFileSync } from 'node:child_process';
|
|
11
21
|
import type { Command } from 'commander';
|
|
12
22
|
import * as p from '@clack/prompts';
|
|
13
|
-
import { PackLockfile, checkNpmVersion, checkVersionCompat } from '@soleri/core';
|
|
23
|
+
import { PackLockfile, checkNpmVersion, checkVersionCompat, SOLERI_HOME } from '@soleri/core';
|
|
14
24
|
import {
|
|
15
25
|
generateClaudeMdTemplate,
|
|
16
26
|
generateInjectClaudeMd,
|
|
@@ -18,6 +28,7 @@ import {
|
|
|
18
28
|
} from '@soleri/forge/lib';
|
|
19
29
|
import type { AgentConfig } from '@soleri/forge/lib';
|
|
20
30
|
import { detectAgent } from '../utils/agent-context.js';
|
|
31
|
+
import { installClaude } from './install.js';
|
|
21
32
|
|
|
22
33
|
export function registerAgent(program: Command): void {
|
|
23
34
|
const agent = program.command('agent').description('Agent lifecycle management');
|
|
@@ -330,6 +341,138 @@ export function registerAgent(program: Command): void {
|
|
|
330
341
|
}
|
|
331
342
|
});
|
|
332
343
|
|
|
344
|
+
// ─── migrate ──────────────────────────────────────────────
|
|
345
|
+
// Temporary command — moves agent data from ~/.{agentId}/ to ~/.soleri/{agentId}/.
|
|
346
|
+
// Will be removed in the next major version after all users migrate.
|
|
347
|
+
agent
|
|
348
|
+
.command('migrate')
|
|
349
|
+
.argument('<agentId>', 'Agent ID to migrate (e.g. ernesto, salvador)')
|
|
350
|
+
.option('--dry-run', 'Preview what would be moved without executing')
|
|
351
|
+
.description('Move agent data from ~/.{agentId}/ to ~/.soleri/{agentId}/ (one-time migration)')
|
|
352
|
+
.action((agentId: string, opts: { dryRun?: boolean }) => {
|
|
353
|
+
const legacyHome = join(homedir(), `.${agentId}`);
|
|
354
|
+
const newHome = join(SOLERI_HOME, agentId);
|
|
355
|
+
|
|
356
|
+
// Data files to migrate (relative to agent home)
|
|
357
|
+
const dataFiles = [
|
|
358
|
+
'vault.db',
|
|
359
|
+
'vault.db-shm',
|
|
360
|
+
'vault.db-wal',
|
|
361
|
+
'plans.json',
|
|
362
|
+
'keys.json',
|
|
363
|
+
'flags.json',
|
|
364
|
+
];
|
|
365
|
+
const dataDirs = ['templates'];
|
|
366
|
+
|
|
367
|
+
// Check if legacy data exists
|
|
368
|
+
if (!existsSync(legacyHome)) {
|
|
369
|
+
p.log.info(`No legacy data found at ${legacyHome} — nothing to migrate.`);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Check if already migrated
|
|
374
|
+
if (existsSync(join(newHome, 'vault.db'))) {
|
|
375
|
+
p.log.warn(`Data already exists at ${newHome}/vault.db — migration may have already run.`);
|
|
376
|
+
p.log.info('If you want to force re-migration, remove the new directory first.');
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Discover what to move
|
|
381
|
+
const toMove: Array<{ src: string; dst: string; type: 'file' | 'dir' }> = [];
|
|
382
|
+
|
|
383
|
+
for (const file of dataFiles) {
|
|
384
|
+
const src = join(legacyHome, file);
|
|
385
|
+
if (existsSync(src)) {
|
|
386
|
+
toMove.push({ src, dst: join(newHome, file), type: 'file' });
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
for (const dir of dataDirs) {
|
|
391
|
+
const src = join(legacyHome, dir);
|
|
392
|
+
if (existsSync(src)) {
|
|
393
|
+
toMove.push({ src, dst: join(newHome, dir), type: 'dir' });
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (toMove.length === 0) {
|
|
398
|
+
p.log.info(`No data files found in ${legacyHome} — nothing to migrate.`);
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Preview
|
|
403
|
+
console.log(`\n Migration: ${legacyHome} → ${newHome}\n`);
|
|
404
|
+
for (const item of toMove) {
|
|
405
|
+
const label = item.type === 'dir' ? '(dir) ' : '';
|
|
406
|
+
console.log(` ${label}${item.src} → ${item.dst}`);
|
|
407
|
+
}
|
|
408
|
+
console.log('');
|
|
409
|
+
|
|
410
|
+
if (opts.dryRun) {
|
|
411
|
+
p.log.info(
|
|
412
|
+
`Dry run — ${toMove.length} items would be moved. Run without --dry-run to execute.`,
|
|
413
|
+
);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Execute migration
|
|
418
|
+
const s = p.spinner();
|
|
419
|
+
s.start('Migrating agent data...');
|
|
420
|
+
|
|
421
|
+
try {
|
|
422
|
+
// Create new home directory
|
|
423
|
+
mkdirSync(newHome, { recursive: true });
|
|
424
|
+
|
|
425
|
+
let moved = 0;
|
|
426
|
+
for (const item of toMove) {
|
|
427
|
+
mkdirSync(dirname(item.dst), { recursive: true });
|
|
428
|
+
try {
|
|
429
|
+
// Try atomic rename first (same filesystem)
|
|
430
|
+
renameSync(item.src, item.dst);
|
|
431
|
+
} catch {
|
|
432
|
+
// Cross-filesystem: copy then remove
|
|
433
|
+
if (item.type === 'dir') {
|
|
434
|
+
cpSync(item.src, item.dst, { recursive: true });
|
|
435
|
+
rmSync(item.src, { recursive: true });
|
|
436
|
+
} else {
|
|
437
|
+
cpSync(item.src, item.dst);
|
|
438
|
+
rmSync(item.src);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
moved++;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
s.stop(`Migrated ${moved} items to ${newHome}`);
|
|
445
|
+
|
|
446
|
+
// Detect agent definition (agent.yaml) to re-register MCP
|
|
447
|
+
const agentYaml = join(newHome, 'agent.yaml');
|
|
448
|
+
const legacyAgentYaml = join(legacyHome, 'agent.yaml');
|
|
449
|
+
|
|
450
|
+
if (existsSync(agentYaml) || existsSync(legacyAgentYaml)) {
|
|
451
|
+
// If agent.yaml is still in legacy dir, move it too
|
|
452
|
+
if (!existsSync(agentYaml) && existsSync(legacyAgentYaml)) {
|
|
453
|
+
p.log.info(
|
|
454
|
+
'Note: agent.yaml is still at the old location. Move the entire agent folder if needed.',
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Re-register MCP pointing to new location
|
|
460
|
+
const agentDir = existsSync(agentYaml) ? newHome : legacyHome;
|
|
461
|
+
if (existsSync(join(agentDir, 'agent.yaml'))) {
|
|
462
|
+
installClaude(agentId, agentDir, true);
|
|
463
|
+
p.log.success('MCP registration updated to new path.');
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
p.log.info(
|
|
467
|
+
`Legacy directory preserved at ${legacyHome} (safe to remove manually after verifying).`,
|
|
468
|
+
);
|
|
469
|
+
} catch (err) {
|
|
470
|
+
s.stop('Migration failed');
|
|
471
|
+
p.log.error(err instanceof Error ? err.message : String(err));
|
|
472
|
+
process.exit(1);
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
|
|
333
476
|
// ─── validate ──────────────────────────────────────────────
|
|
334
477
|
agent
|
|
335
478
|
.command('validate')
|
package/src/commands/create.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFileSync, existsSync } from 'node:fs';
|
|
2
|
-
import { resolve } from 'node:path';
|
|
2
|
+
import { resolve, join } from 'node:path';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
3
4
|
import type { Command } from 'commander';
|
|
4
5
|
import * as p from '@clack/prompts';
|
|
5
6
|
import {
|
|
@@ -14,6 +15,9 @@ import { runCreateWizard } from '../prompts/create-wizard.js';
|
|
|
14
15
|
import { listPacks } from '../hook-packs/registry.js';
|
|
15
16
|
import { installPack } from '../hook-packs/installer.js';
|
|
16
17
|
|
|
18
|
+
/** Default parent directory for new agents: ~/.soleri/ */
|
|
19
|
+
const SOLERI_HOME = process.env.SOLERI_HOME ?? join(homedir(), '.soleri');
|
|
20
|
+
|
|
17
21
|
function parseSetupTarget(value?: string): SetupTarget | undefined {
|
|
18
22
|
if (!value) return undefined;
|
|
19
23
|
if ((SETUP_TARGETS as readonly string[]).includes(value)) {
|
|
@@ -37,6 +41,7 @@ export function registerCreate(program: Command): void {
|
|
|
37
41
|
`Setup target: ${SETUP_TARGETS.join(', ')} (default: claude)`,
|
|
38
42
|
)
|
|
39
43
|
.option('-y, --yes', 'Skip confirmation prompts (use with --config for fully non-interactive)')
|
|
44
|
+
.option('--dir <path>', `Parent directory for the agent (default: ~/.soleri/)`)
|
|
40
45
|
.option('--filetree', 'Create a file-tree agent (v7 — no TypeScript, no build step)')
|
|
41
46
|
.option('--legacy', 'Create a legacy TypeScript agent (v6 — requires npm install + build)')
|
|
42
47
|
.description('Create a new Soleri agent')
|
|
@@ -46,6 +51,7 @@ export function registerCreate(program: Command): void {
|
|
|
46
51
|
opts?: {
|
|
47
52
|
config?: string;
|
|
48
53
|
yes?: boolean;
|
|
54
|
+
dir?: string;
|
|
49
55
|
setupTarget?: string;
|
|
50
56
|
filetree?: boolean;
|
|
51
57
|
legacy?: boolean;
|
|
@@ -148,7 +154,7 @@ export function registerCreate(program: Command): void {
|
|
|
148
154
|
})),
|
|
149
155
|
};
|
|
150
156
|
|
|
151
|
-
const outputDir = config.outputDir ??
|
|
157
|
+
const outputDir = opts?.dir ? resolve(opts.dir) : (config.outputDir ?? SOLERI_HOME);
|
|
152
158
|
const nonInteractive = !!(opts?.yes || opts?.config);
|
|
153
159
|
|
|
154
160
|
if (!nonInteractive) {
|