@soleri/cli 9.7.1 → 9.8.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/add-domain.js +1 -0
- package/dist/commands/add-domain.js.map +1 -1
- package/dist/commands/add-pack.js +7 -147
- package/dist/commands/add-pack.js.map +1 -1
- package/dist/commands/agent.js +130 -0
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/create.js +78 -2
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/doctor.js +2 -0
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/extend.js +17 -0
- package/dist/commands/extend.js.map +1 -1
- package/dist/commands/install-knowledge.js +1 -0
- package/dist/commands/install-knowledge.js.map +1 -1
- package/dist/commands/test.js +140 -1
- package/dist/commands/test.js.map +1 -1
- package/dist/hook-packs/flock-guard/manifest.json +2 -1
- package/dist/hook-packs/installer.js +12 -5
- package/dist/hook-packs/installer.js.map +1 -1
- package/dist/hook-packs/installer.ts +26 -7
- package/dist/hook-packs/marketing-research/manifest.json +2 -1
- package/dist/hook-packs/registry.d.ts +2 -0
- package/dist/hook-packs/registry.js.map +1 -1
- package/dist/hook-packs/registry.ts +2 -0
- package/dist/prompts/create-wizard.d.ts +16 -2
- package/dist/prompts/create-wizard.js +84 -11
- package/dist/prompts/create-wizard.js.map +1 -1
- package/dist/utils/checks.d.ts +8 -5
- package/dist/utils/checks.js +105 -10
- package/dist/utils/checks.js.map +1 -1
- package/dist/utils/format-paths.d.ts +14 -0
- package/dist/utils/format-paths.js +27 -0
- package/dist/utils/format-paths.js.map +1 -0
- package/dist/utils/git.d.ts +29 -0
- package/dist/utils/git.js +88 -0
- package/dist/utils/git.js.map +1 -0
- package/dist/utils/logger.d.ts +1 -0
- package/dist/utils/logger.js +4 -0
- package/dist/utils/logger.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/create-wizard-git.test.ts +208 -0
- package/src/__tests__/git-utils.test.ts +268 -0
- package/src/__tests__/hook-packs.test.ts +5 -1
- package/src/__tests__/scaffold-git-e2e.test.ts +105 -0
- package/src/commands/add-domain.ts +1 -0
- package/src/commands/add-pack.ts +10 -163
- package/src/commands/agent.ts +161 -0
- package/src/commands/create.ts +89 -3
- package/src/commands/doctor.ts +1 -0
- package/src/commands/extend.ts +20 -1
- package/src/commands/install-knowledge.ts +1 -0
- package/src/commands/test.ts +141 -2
- package/src/hook-packs/flock-guard/manifest.json +2 -1
- package/src/hook-packs/installer.ts +26 -7
- package/src/hook-packs/marketing-research/manifest.json +2 -1
- package/src/hook-packs/registry.ts +2 -0
- package/src/prompts/create-wizard.ts +109 -14
- package/src/utils/checks.ts +122 -13
- package/src/utils/format-paths.ts +41 -0
- package/src/utils/git.ts +118 -0
- package/src/utils/logger.ts +5 -0
package/src/commands/add-pack.ts
CHANGED
|
@@ -1,170 +1,17 @@
|
|
|
1
|
-
// @ts-nocheck — TODO: rewrite for v7 file-tree agents (uses removed @soleri/core APIs)
|
|
2
1
|
import type { Command } from 'commander';
|
|
3
2
|
import * as p from '@clack/prompts';
|
|
4
|
-
import { execFileSync } from 'node:child_process';
|
|
5
|
-
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
6
|
-
import { join } from 'node:path';
|
|
7
|
-
import { detectAgent } from '../utils/agent-context.js';
|
|
8
3
|
|
|
9
4
|
export function registerAddPack(program: Command): void {
|
|
10
5
|
program
|
|
11
6
|
.command('add-pack')
|
|
12
|
-
.argument('<
|
|
13
|
-
.
|
|
14
|
-
.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
'No agent project detected in current directory. Run this from an agent root.',
|
|
23
|
-
);
|
|
24
|
-
process.exit(1);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const s = p.spinner();
|
|
28
|
-
|
|
29
|
-
// Step 1: npm install
|
|
30
|
-
if (opts.install) {
|
|
31
|
-
s.start(`Installing ${packageName}...`);
|
|
32
|
-
try {
|
|
33
|
-
execFileSync('npm', ['install', packageName], {
|
|
34
|
-
cwd: ctx.agentPath,
|
|
35
|
-
stdio: 'pipe',
|
|
36
|
-
});
|
|
37
|
-
s.stop(`Installed ${packageName}`);
|
|
38
|
-
} catch (err) {
|
|
39
|
-
s.stop('npm install failed');
|
|
40
|
-
p.log.error(err instanceof Error ? err.message : String(err));
|
|
41
|
-
process.exit(1);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Step 2: Validate it's a DomainPack
|
|
46
|
-
s.start('Validating domain pack...');
|
|
47
|
-
let pack;
|
|
48
|
-
try {
|
|
49
|
-
const { loadDomainPack } = await import('@soleri/core');
|
|
50
|
-
pack = await loadDomainPack(packageName);
|
|
51
|
-
s.stop(
|
|
52
|
-
`Validated: ${pack.name} v${pack.version} (${pack.ops.length} ops, ${pack.domains.length} domains${pack.facades?.length ? `, ${pack.facades.length} facades` : ''})`,
|
|
53
|
-
);
|
|
54
|
-
} catch (err) {
|
|
55
|
-
s.stop('Validation failed');
|
|
56
|
-
p.log.error(err instanceof Error ? err.message : String(err));
|
|
57
|
-
process.exit(1);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Step 3: Update agent-config.json
|
|
61
|
-
s.start('Updating agent config...');
|
|
62
|
-
const configPath = join(ctx.agentPath, 'agent-config.json');
|
|
63
|
-
if (existsSync(configPath)) {
|
|
64
|
-
try {
|
|
65
|
-
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
66
|
-
if (!config.domainPacks) config.domainPacks = [];
|
|
67
|
-
|
|
68
|
-
const existing = config.domainPacks.find(
|
|
69
|
-
(ref: { package: string }) => ref.package === packageName,
|
|
70
|
-
);
|
|
71
|
-
if (existing) {
|
|
72
|
-
existing.version = pack.version;
|
|
73
|
-
s.stop('Updated existing pack reference in config');
|
|
74
|
-
} else {
|
|
75
|
-
config.domainPacks.push({
|
|
76
|
-
name: pack.name,
|
|
77
|
-
package: packageName,
|
|
78
|
-
version: pack.version,
|
|
79
|
-
});
|
|
80
|
-
s.stop('Added pack reference to config');
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
84
|
-
} catch (err) {
|
|
85
|
-
s.stop('Config update failed');
|
|
86
|
-
p.log.error(err instanceof Error ? err.message : String(err));
|
|
87
|
-
process.exit(1);
|
|
88
|
-
}
|
|
89
|
-
} else {
|
|
90
|
-
s.stop('No agent-config.json found — skipped config update');
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Step 4: Install knowledge (tiered)
|
|
94
|
-
s.start('Installing knowledge...');
|
|
95
|
-
try {
|
|
96
|
-
const { installKnowledge, createAgentRuntime } = await import('@soleri/core');
|
|
97
|
-
const runtime = createAgentRuntime({
|
|
98
|
-
agentId: ctx.agentId,
|
|
99
|
-
vaultPath: join(ctx.agentPath, '.vault', 'vault.db'),
|
|
100
|
-
});
|
|
101
|
-
const resolvedDir = join(ctx.agentPath, 'node_modules', packageName);
|
|
102
|
-
const knowledgeResult = await installKnowledge(pack, runtime, resolvedDir);
|
|
103
|
-
runtime.close();
|
|
104
|
-
const total =
|
|
105
|
-
knowledgeResult.canonical + knowledgeResult.curated + knowledgeResult.captured;
|
|
106
|
-
s.stop(
|
|
107
|
-
total > 0
|
|
108
|
-
? `Installed ${total} knowledge entries (${knowledgeResult.canonical} canonical, ${knowledgeResult.curated} curated, ${knowledgeResult.captured} captured)`
|
|
109
|
-
: 'No knowledge to install',
|
|
110
|
-
);
|
|
111
|
-
} catch (err) {
|
|
112
|
-
s.stop('Knowledge install skipped (non-fatal)');
|
|
113
|
-
p.log.warn(err instanceof Error ? err.message : String(err));
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Step 5: Install skills
|
|
117
|
-
s.start('Installing skills...');
|
|
118
|
-
try {
|
|
119
|
-
const { installSkills } = await import('@soleri/core');
|
|
120
|
-
const resolvedDir = join(ctx.agentPath, 'node_modules', packageName);
|
|
121
|
-
const skillsDir = join(ctx.agentPath, 'skills');
|
|
122
|
-
const skillsResult = installSkills(pack, skillsDir, resolvedDir, opts.force);
|
|
123
|
-
s.stop(
|
|
124
|
-
skillsResult.installed > 0
|
|
125
|
-
? `Installed ${skillsResult.installed} skills (${skillsResult.skipped} skipped)`
|
|
126
|
-
: 'No skills to install',
|
|
127
|
-
);
|
|
128
|
-
} catch (err) {
|
|
129
|
-
s.stop('Skills install skipped (non-fatal)');
|
|
130
|
-
p.log.warn(err instanceof Error ? err.message : String(err));
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Step 6: Inject CLAUDE.md domain rules
|
|
134
|
-
if (pack.rules) {
|
|
135
|
-
s.start('Injecting CLAUDE.md domain rules...');
|
|
136
|
-
try {
|
|
137
|
-
const { injectDomainRules } = await import('@soleri/core');
|
|
138
|
-
const claudeMdPath = join(ctx.agentPath, 'CLAUDE.md');
|
|
139
|
-
injectDomainRules(claudeMdPath, pack.name, pack.rules);
|
|
140
|
-
s.stop('Injected domain rules into CLAUDE.md');
|
|
141
|
-
} catch (err) {
|
|
142
|
-
s.stop('CLAUDE.md injection skipped (non-fatal)');
|
|
143
|
-
p.log.warn(err instanceof Error ? err.message : String(err));
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Step 7: Run forge inject (regenerate entry-point, tests)
|
|
148
|
-
if (opts.inject) {
|
|
149
|
-
s.start('Regenerating agent code...');
|
|
150
|
-
try {
|
|
151
|
-
execFileSync('npx', ['soleri', 'agent', 'refresh'], {
|
|
152
|
-
cwd: ctx.agentPath,
|
|
153
|
-
stdio: 'pipe',
|
|
154
|
-
});
|
|
155
|
-
s.stop('Regenerated entry-point, tests, and CLAUDE.md');
|
|
156
|
-
} catch {
|
|
157
|
-
s.stop('Forge inject skipped — run `soleri agent refresh` manually');
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Summary
|
|
162
|
-
p.log.success(`\nDomain pack "${pack.name}" added to ${ctx.agentId}`);
|
|
163
|
-
p.log.info(` Domains: ${pack.domains.join(', ')}`);
|
|
164
|
-
p.log.info(` Ops: ${pack.ops.length} custom operations`);
|
|
165
|
-
if (pack.facades?.length) {
|
|
166
|
-
p.log.info(` Facades: ${pack.facades.map((f) => f.name).join(', ')}`);
|
|
167
|
-
}
|
|
168
|
-
},
|
|
169
|
-
);
|
|
7
|
+
.argument('<pack>', 'Pack name')
|
|
8
|
+
.description('[DEPRECATED] Use "soleri pack install" or "soleri hooks add-pack" instead')
|
|
9
|
+
.action(async () => {
|
|
10
|
+
p.log.warn(
|
|
11
|
+
'The "add-pack" command is deprecated.\n\n' +
|
|
12
|
+
'Use these commands instead:\n' +
|
|
13
|
+
' • soleri pack install <pack> — install knowledge/domain packs\n' +
|
|
14
|
+
' • soleri hooks add-pack <pack> — install hook packs\n',
|
|
15
|
+
);
|
|
16
|
+
});
|
|
170
17
|
}
|
package/src/commands/agent.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { join, dirname } from 'node:path';
|
|
9
|
+
import { createRequire } from 'node:module';
|
|
9
10
|
import {
|
|
10
11
|
existsSync,
|
|
11
12
|
readFileSync,
|
|
@@ -20,6 +21,7 @@ import { homedir } from 'node:os';
|
|
|
20
21
|
import { execFileSync } from 'node:child_process';
|
|
21
22
|
import type { Command } from 'commander';
|
|
22
23
|
import * as p from '@clack/prompts';
|
|
24
|
+
import { parse as parseYaml } from 'yaml';
|
|
23
25
|
import { PackLockfile, checkNpmVersion, checkVersionCompat, SOLERI_HOME } from '@soleri/core';
|
|
24
26
|
import {
|
|
25
27
|
generateClaudeMdTemplate,
|
|
@@ -47,6 +49,75 @@ export function registerAgent(program: Command): void {
|
|
|
47
49
|
return;
|
|
48
50
|
}
|
|
49
51
|
|
|
52
|
+
if (ctx.format === 'filetree') {
|
|
53
|
+
// ─── File-tree agent (v7+) ─────────────────────────────
|
|
54
|
+
const yamlPath = join(ctx.agentPath, 'agent.yaml');
|
|
55
|
+
const yaml = parseYaml(readFileSync(yamlPath, 'utf-8'));
|
|
56
|
+
const agentName = yaml.name || yaml.id || 'unknown';
|
|
57
|
+
const agentId = yaml.id || 'unknown';
|
|
58
|
+
const domains: string[] = Array.isArray(yaml.domains) ? yaml.domains : [];
|
|
59
|
+
|
|
60
|
+
// Count skills (directories inside skills/)
|
|
61
|
+
const skillsDir = join(ctx.agentPath, 'skills');
|
|
62
|
+
let skillsCount = 0;
|
|
63
|
+
if (existsSync(skillsDir)) {
|
|
64
|
+
skillsCount = readdirSync(skillsDir, { withFileTypes: true }).filter((d) =>
|
|
65
|
+
d.isDirectory(),
|
|
66
|
+
).length;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Resolve engine version via require.resolve
|
|
70
|
+
let engineVersion = 'not installed';
|
|
71
|
+
try {
|
|
72
|
+
const req = createRequire(join(ctx.agentPath, 'package.json'));
|
|
73
|
+
const corePkgPath = req.resolve('@soleri/core/package.json');
|
|
74
|
+
engineVersion = JSON.parse(readFileSync(corePkgPath, 'utf-8')).version || 'unknown';
|
|
75
|
+
} catch {
|
|
76
|
+
engineVersion = 'not installed';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Check for core update
|
|
80
|
+
const latestCore = checkNpmVersion('@soleri/core');
|
|
81
|
+
|
|
82
|
+
// Count vault entries if db exists
|
|
83
|
+
const dbPath = join(ctx.agentPath, 'data', 'vault.db');
|
|
84
|
+
const hasVault = existsSync(dbPath);
|
|
85
|
+
|
|
86
|
+
if (opts.json) {
|
|
87
|
+
console.log(
|
|
88
|
+
JSON.stringify(
|
|
89
|
+
{
|
|
90
|
+
agent: agentName,
|
|
91
|
+
id: agentId,
|
|
92
|
+
format: 'file-tree (v7)',
|
|
93
|
+
engine: engineVersion,
|
|
94
|
+
engineLatest: latestCore,
|
|
95
|
+
domains,
|
|
96
|
+
skills: skillsCount,
|
|
97
|
+
vault: { exists: hasVault },
|
|
98
|
+
},
|
|
99
|
+
null,
|
|
100
|
+
2,
|
|
101
|
+
),
|
|
102
|
+
);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
console.log(`\n Agent: ${agentName}`);
|
|
107
|
+
console.log(` ID: ${agentId}`);
|
|
108
|
+
console.log(` Format: file-tree (v7)`);
|
|
109
|
+
console.log(
|
|
110
|
+
` Engine: @soleri/core ${engineVersion}${latestCore && latestCore !== engineVersion ? ` (update available: ${latestCore})` : ''}`,
|
|
111
|
+
);
|
|
112
|
+
console.log(` Domains: ${domains.length > 0 ? domains.join(', ') : 'none'}`);
|
|
113
|
+
console.log(` Skills: ${skillsCount}`);
|
|
114
|
+
console.log(`\n Vault: ${hasVault ? 'initialized' : 'not initialized'}`);
|
|
115
|
+
console.log('');
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ─── Legacy TypeScript agent ──────────────────────────────
|
|
120
|
+
|
|
50
121
|
// Read agent package.json
|
|
51
122
|
const pkgPath = join(ctx.agentPath, 'package.json');
|
|
52
123
|
const pkg = existsSync(pkgPath) ? JSON.parse(readFileSync(pkgPath, 'utf-8')) : {};
|
|
@@ -128,6 +199,96 @@ export function registerAgent(program: Command): void {
|
|
|
128
199
|
return;
|
|
129
200
|
}
|
|
130
201
|
|
|
202
|
+
// ─── File-tree agent (v7+) ────────────────────────────────
|
|
203
|
+
if (ctx.format === 'filetree') {
|
|
204
|
+
// Resolve installed @soleri/core version
|
|
205
|
+
let installedVersion: string | null = null;
|
|
206
|
+
try {
|
|
207
|
+
const req = createRequire(import.meta.url);
|
|
208
|
+
const corePkgPath = req.resolve('@soleri/core/package.json');
|
|
209
|
+
installedVersion = JSON.parse(readFileSync(corePkgPath, 'utf-8')).version ?? null;
|
|
210
|
+
} catch {
|
|
211
|
+
// @soleri/core not resolvable — will show as unknown
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Check latest version on npm
|
|
215
|
+
const latestCore = checkNpmVersion('@soleri/core');
|
|
216
|
+
|
|
217
|
+
if (opts.check) {
|
|
218
|
+
const installed = installedVersion ?? 'unknown';
|
|
219
|
+
const latest = latestCore ?? 'unknown';
|
|
220
|
+
if (installed === latest) {
|
|
221
|
+
console.log(`\n Engine: @soleri/core ${installed} (up to date)`);
|
|
222
|
+
} else {
|
|
223
|
+
console.log(`\n Engine: @soleri/core ${installed} → ${latest}`);
|
|
224
|
+
}
|
|
225
|
+
console.log(` Format: file-tree (updates via template refresh)`);
|
|
226
|
+
console.log('');
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (opts.dryRun) {
|
|
231
|
+
p.log.info('Would refresh templates (regenerate _engine.md, CLAUDE.md, sync skills)');
|
|
232
|
+
if (latestCore && installedVersion && latestCore !== installedVersion) {
|
|
233
|
+
p.log.info(
|
|
234
|
+
`Engine: ${installedVersion} → ${latestCore} (update @soleri/core globally to upgrade)`,
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Run refresh: regenerate _engine.md, recompose CLAUDE.md, sync skills
|
|
241
|
+
const enginePath = join(ctx.agentPath, 'instructions', '_engine.md');
|
|
242
|
+
const claudeMdPath = join(ctx.agentPath, 'CLAUDE.md');
|
|
243
|
+
|
|
244
|
+
const skillFiles = generateSkills({ id: ctx.agentId } as AgentConfig);
|
|
245
|
+
|
|
246
|
+
// 1. Sync skills
|
|
247
|
+
if (skillFiles.length > 0) {
|
|
248
|
+
let newCount = 0;
|
|
249
|
+
let updatedCount = 0;
|
|
250
|
+
for (const [relPath, content] of skillFiles) {
|
|
251
|
+
const fullPath = join(ctx.agentPath, relPath);
|
|
252
|
+
const dirPath = dirname(fullPath);
|
|
253
|
+
const isNew = !existsSync(fullPath);
|
|
254
|
+
mkdirSync(dirPath, { recursive: true });
|
|
255
|
+
writeFileSync(fullPath, content, 'utf-8');
|
|
256
|
+
if (isNew) newCount++;
|
|
257
|
+
else updatedCount++;
|
|
258
|
+
}
|
|
259
|
+
p.log.success(
|
|
260
|
+
`Synced ${skillFiles.length} skills (${newCount} new, ${updatedCount} updated)`,
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// 2. Regenerate _engine.md
|
|
265
|
+
mkdirSync(join(ctx.agentPath, 'instructions'), { recursive: true });
|
|
266
|
+
writeFileSync(enginePath, getEngineRulesContent(), 'utf-8');
|
|
267
|
+
p.log.success(`Regenerated ${enginePath}`);
|
|
268
|
+
|
|
269
|
+
// 3. Recompose CLAUDE.md
|
|
270
|
+
const result = composeClaudeMd(ctx.agentPath);
|
|
271
|
+
writeFileSync(claudeMdPath, result.content, 'utf-8');
|
|
272
|
+
p.log.success(
|
|
273
|
+
`Regenerated ${claudeMdPath} (${result.sources.length} sources, ${result.content.length} bytes)`,
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
// 4. Show engine version status
|
|
277
|
+
const installed = installedVersion ?? 'unknown';
|
|
278
|
+
const latest = latestCore ?? 'unknown';
|
|
279
|
+
if (installed !== 'unknown' && latest !== 'unknown' && installed !== latest) {
|
|
280
|
+
p.log.info(
|
|
281
|
+
`Engine: ${installed} → ${latest} (run \`npm update -g @soleri/cli\` to upgrade)`,
|
|
282
|
+
);
|
|
283
|
+
} else if (installed !== 'unknown') {
|
|
284
|
+
p.log.info(`Engine: @soleri/core ${installed} (up to date)`);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
p.log.info('File-tree agents update by refreshing templates from the installed engine.');
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// ─── Legacy TypeScript agent ──────────────────────────────
|
|
131
292
|
const pkgPath = join(ctx.agentPath, 'package.json');
|
|
132
293
|
if (!existsSync(pkgPath)) {
|
|
133
294
|
p.log.error('No package.json found in agent directory.');
|
package/src/commands/create.ts
CHANGED
|
@@ -10,9 +10,17 @@ import {
|
|
|
10
10
|
type SetupTarget,
|
|
11
11
|
scaffoldFileTree,
|
|
12
12
|
} from '@soleri/forge/lib';
|
|
13
|
-
import { runCreateWizard } from '../prompts/create-wizard.js';
|
|
13
|
+
import { runCreateWizard, type WizardGitConfig } from '../prompts/create-wizard.js';
|
|
14
14
|
import { listPacks } from '../hook-packs/registry.js';
|
|
15
15
|
import { installPack } from '../hook-packs/installer.js';
|
|
16
|
+
import {
|
|
17
|
+
isGitInstalled,
|
|
18
|
+
gitInit,
|
|
19
|
+
gitInitialCommit,
|
|
20
|
+
gitAddRemote,
|
|
21
|
+
gitPush,
|
|
22
|
+
ghCreateRepo,
|
|
23
|
+
} from '../utils/git.js';
|
|
16
24
|
|
|
17
25
|
function parseSetupTarget(value?: string): SetupTarget | undefined {
|
|
18
26
|
if (!value) return undefined;
|
|
@@ -40,6 +48,7 @@ export function registerCreate(program: Command): void {
|
|
|
40
48
|
.option('--dir <path>', `Parent directory for the agent (default: current directory)`)
|
|
41
49
|
.option('--filetree', 'Create a file-tree agent (v7 — no TypeScript, no build step)')
|
|
42
50
|
.option('--legacy', 'Create a legacy TypeScript agent (v6 — requires npm install + build)')
|
|
51
|
+
.option('--no-git', 'Skip git repository initialization (git init is on by default)')
|
|
43
52
|
.description('Create a new Soleri agent')
|
|
44
53
|
.action(
|
|
45
54
|
async (
|
|
@@ -51,11 +60,15 @@ export function registerCreate(program: Command): void {
|
|
|
51
60
|
setupTarget?: string;
|
|
52
61
|
filetree?: boolean;
|
|
53
62
|
legacy?: boolean;
|
|
63
|
+
git?: boolean;
|
|
54
64
|
},
|
|
55
65
|
) => {
|
|
56
66
|
try {
|
|
57
67
|
let config;
|
|
58
68
|
|
|
69
|
+
let gitConfig: WizardGitConfig | undefined;
|
|
70
|
+
const skipGit = opts?.git === false; // Commander sets git=false when --no-git is passed
|
|
71
|
+
|
|
59
72
|
if (name && opts?.yes && !opts?.config) {
|
|
60
73
|
// Quick non-interactive: name + --yes = Italian Craftsperson defaults
|
|
61
74
|
const id = name
|
|
@@ -73,6 +86,10 @@ export function registerCreate(program: Command): void {
|
|
|
73
86
|
tone: 'mentor',
|
|
74
87
|
greeting: `Ciao! I'm ${name}. Ready to build something beautiful today?`,
|
|
75
88
|
});
|
|
89
|
+
// Non-interactive default: git init yes, no remote
|
|
90
|
+
if (!skipGit) {
|
|
91
|
+
gitConfig = { init: true };
|
|
92
|
+
}
|
|
76
93
|
} else if (opts?.config) {
|
|
77
94
|
// Non-interactive: read from config file
|
|
78
95
|
const configPath = resolve(opts.config);
|
|
@@ -87,6 +104,10 @@ export function registerCreate(program: Command): void {
|
|
|
87
104
|
process.exit(1);
|
|
88
105
|
}
|
|
89
106
|
config = parsed.data;
|
|
107
|
+
// Config path: default to git init unless --no-git or config specifies git
|
|
108
|
+
if (!skipGit) {
|
|
109
|
+
gitConfig = raw.git ?? { init: true };
|
|
110
|
+
}
|
|
90
111
|
} else {
|
|
91
112
|
// Interactive wizard
|
|
92
113
|
const wizardResult = await runCreateWizard(name);
|
|
@@ -94,12 +115,15 @@ export function registerCreate(program: Command): void {
|
|
|
94
115
|
p.outro('Cancelled.');
|
|
95
116
|
return;
|
|
96
117
|
}
|
|
97
|
-
const parsed = AgentConfigSchema.safeParse(wizardResult);
|
|
118
|
+
const parsed = AgentConfigSchema.safeParse(wizardResult.config);
|
|
98
119
|
if (!parsed.success) {
|
|
99
120
|
p.log.error(`Invalid config: ${parsed.error.message}`);
|
|
100
121
|
process.exit(1);
|
|
101
122
|
}
|
|
102
123
|
config = parsed.data;
|
|
124
|
+
if (!skipGit) {
|
|
125
|
+
gitConfig = wizardResult.git;
|
|
126
|
+
}
|
|
103
127
|
}
|
|
104
128
|
|
|
105
129
|
const setupTarget = parseSetupTarget(opts?.setupTarget);
|
|
@@ -177,6 +201,68 @@ export function registerCreate(program: Command): void {
|
|
|
177
201
|
process.exit(1);
|
|
178
202
|
}
|
|
179
203
|
|
|
204
|
+
// ─── Git initialization ──────────────────────────────
|
|
205
|
+
if (gitConfig?.init) {
|
|
206
|
+
const hasGit = await isGitInstalled();
|
|
207
|
+
if (!hasGit) {
|
|
208
|
+
p.log.warn(
|
|
209
|
+
'git is not installed — skipping repository initialization. Install git from https://git-scm.com/',
|
|
210
|
+
);
|
|
211
|
+
} else {
|
|
212
|
+
const agentDir = result.agentDir;
|
|
213
|
+
s.start('Initializing git repository...');
|
|
214
|
+
|
|
215
|
+
const initResult = await gitInit(agentDir);
|
|
216
|
+
if (!initResult.ok) {
|
|
217
|
+
s.stop('git init failed');
|
|
218
|
+
p.log.warn(`git init failed: ${initResult.error}`);
|
|
219
|
+
} else {
|
|
220
|
+
const commitResult = await gitInitialCommit(
|
|
221
|
+
agentDir,
|
|
222
|
+
`feat: scaffold agent "${config.name}"`,
|
|
223
|
+
);
|
|
224
|
+
if (!commitResult.ok) {
|
|
225
|
+
s.stop('Initial commit failed');
|
|
226
|
+
p.log.warn(`Initial commit failed: ${commitResult.error}`);
|
|
227
|
+
} else {
|
|
228
|
+
s.stop('Git repository initialized with initial commit');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Remote setup
|
|
232
|
+
if (gitConfig.remote && initResult.ok && commitResult.ok) {
|
|
233
|
+
if (gitConfig.remote.type === 'gh') {
|
|
234
|
+
s.start('Creating GitHub repository...');
|
|
235
|
+
const ghResult = await ghCreateRepo(config.id, {
|
|
236
|
+
visibility: gitConfig.remote.visibility ?? 'private',
|
|
237
|
+
dir: agentDir,
|
|
238
|
+
});
|
|
239
|
+
if (!ghResult.ok) {
|
|
240
|
+
s.stop('GitHub repo creation failed');
|
|
241
|
+
p.log.warn(`gh repo create failed: ${ghResult.error}`);
|
|
242
|
+
} else {
|
|
243
|
+
s.stop(`Pushed to ${ghResult.url ?? 'GitHub'}`);
|
|
244
|
+
}
|
|
245
|
+
} else if (gitConfig.remote.type === 'manual' && gitConfig.remote.url) {
|
|
246
|
+
s.start('Setting up remote...');
|
|
247
|
+
const remoteResult = await gitAddRemote(agentDir, gitConfig.remote.url);
|
|
248
|
+
if (!remoteResult.ok) {
|
|
249
|
+
s.stop('Failed to add remote');
|
|
250
|
+
p.log.warn(`git remote add failed: ${remoteResult.error}`);
|
|
251
|
+
} else {
|
|
252
|
+
const pushResult = await gitPush(agentDir);
|
|
253
|
+
if (!pushResult.ok) {
|
|
254
|
+
s.stop('Push failed');
|
|
255
|
+
p.log.warn(`git push failed: ${pushResult.error}`);
|
|
256
|
+
} else {
|
|
257
|
+
s.stop('Pushed to remote');
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
180
266
|
p.note(result.summary, 'Next steps');
|
|
181
267
|
p.outro('Done!');
|
|
182
268
|
return;
|
|
@@ -208,7 +294,7 @@ export function registerCreate(program: Command): void {
|
|
|
208
294
|
selectedPacks = selectedPacks.filter((pk) => available.includes(pk));
|
|
209
295
|
}
|
|
210
296
|
} else if (!nonInteractive && claudeSetup) {
|
|
211
|
-
const packs = listPacks();
|
|
297
|
+
const packs = listPacks().filter((pk) => pk.scaffoldDefault !== false);
|
|
212
298
|
const packChoices = packs.map((pk) => ({
|
|
213
299
|
value: pk.name,
|
|
214
300
|
label: pk.name,
|
package/src/commands/doctor.ts
CHANGED
|
@@ -15,6 +15,7 @@ export function registerDoctor(program: Command): void {
|
|
|
15
15
|
|
|
16
16
|
for (const r of results) {
|
|
17
17
|
if (r.status === 'pass') log.pass(r.label, r.detail);
|
|
18
|
+
else if (r.status === 'skip') log.skip(r.label, r.detail);
|
|
18
19
|
else if (r.status === 'warn') {
|
|
19
20
|
log.warn(r.label, r.detail);
|
|
20
21
|
hasWarnings = true;
|
package/src/commands/extend.ts
CHANGED
|
@@ -2,7 +2,22 @@ import type { Command } from 'commander';
|
|
|
2
2
|
import * as p from '@clack/prompts';
|
|
3
3
|
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
|
-
import { detectAgent } from '../utils/agent-context.js';
|
|
5
|
+
import { detectAgent, type AgentContext } from '../utils/agent-context.js';
|
|
6
|
+
|
|
7
|
+
function warnFiletreeAndExit(ctx: AgentContext): boolean {
|
|
8
|
+
if (ctx.format !== 'filetree') return false;
|
|
9
|
+
p.log.warn(
|
|
10
|
+
[
|
|
11
|
+
`The 'extend' command requires a TypeScript agent format.`,
|
|
12
|
+
'',
|
|
13
|
+
'For file-tree agents, use these alternatives:',
|
|
14
|
+
' • Custom skills: add SKILL.md files to skills/{name}/SKILL.md',
|
|
15
|
+
' • Hook packs: soleri hooks add-pack <pack>',
|
|
16
|
+
' • Custom knowledge: add JSON files to knowledge/',
|
|
17
|
+
].join('\n'),
|
|
18
|
+
);
|
|
19
|
+
process.exit(0);
|
|
20
|
+
}
|
|
6
21
|
|
|
7
22
|
export function registerExtend(program: Command): void {
|
|
8
23
|
const extend = program
|
|
@@ -18,6 +33,7 @@ export function registerExtend(program: Command): void {
|
|
|
18
33
|
p.log.error('No agent project detected. Run this from an agent root.');
|
|
19
34
|
process.exit(1);
|
|
20
35
|
}
|
|
36
|
+
warnFiletreeAndExit(ctx);
|
|
21
37
|
|
|
22
38
|
const extDir = join(ctx.agentPath, 'src', 'extensions');
|
|
23
39
|
if (existsSync(join(extDir, 'index.ts'))) {
|
|
@@ -53,6 +69,7 @@ export function registerExtend(program: Command): void {
|
|
|
53
69
|
p.log.error('No agent project detected. Run this from an agent root.');
|
|
54
70
|
process.exit(1);
|
|
55
71
|
}
|
|
72
|
+
warnFiletreeAndExit(ctx);
|
|
56
73
|
|
|
57
74
|
const opsDir = join(ctx.agentPath, 'src', 'extensions', 'ops');
|
|
58
75
|
mkdirSync(opsDir, { recursive: true });
|
|
@@ -108,6 +125,7 @@ export function ${fnName}(runtime: AgentRuntime): OpDefinition {
|
|
|
108
125
|
p.log.error('No agent project detected. Run this from an agent root.');
|
|
109
126
|
process.exit(1);
|
|
110
127
|
}
|
|
128
|
+
warnFiletreeAndExit(ctx);
|
|
111
129
|
|
|
112
130
|
const facadesDir = join(ctx.agentPath, 'src', 'extensions', 'facades');
|
|
113
131
|
mkdirSync(facadesDir, { recursive: true });
|
|
@@ -162,6 +180,7 @@ export function create${className}Facade(runtime: AgentRuntime): FacadeConfig {
|
|
|
162
180
|
p.log.error('No agent project detected. Run this from an agent root.');
|
|
163
181
|
process.exit(1);
|
|
164
182
|
}
|
|
183
|
+
warnFiletreeAndExit(ctx);
|
|
165
184
|
|
|
166
185
|
const mwDir = join(ctx.agentPath, 'src', 'extensions', 'middleware');
|
|
167
186
|
mkdirSync(mwDir, { recursive: true });
|