@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.
Files changed (61) hide show
  1. package/dist/commands/add-domain.js +1 -0
  2. package/dist/commands/add-domain.js.map +1 -1
  3. package/dist/commands/add-pack.js +7 -147
  4. package/dist/commands/add-pack.js.map +1 -1
  5. package/dist/commands/agent.js +130 -0
  6. package/dist/commands/agent.js.map +1 -1
  7. package/dist/commands/create.js +78 -2
  8. package/dist/commands/create.js.map +1 -1
  9. package/dist/commands/doctor.js +2 -0
  10. package/dist/commands/doctor.js.map +1 -1
  11. package/dist/commands/extend.js +17 -0
  12. package/dist/commands/extend.js.map +1 -1
  13. package/dist/commands/install-knowledge.js +1 -0
  14. package/dist/commands/install-knowledge.js.map +1 -1
  15. package/dist/commands/test.js +140 -1
  16. package/dist/commands/test.js.map +1 -1
  17. package/dist/hook-packs/flock-guard/manifest.json +2 -1
  18. package/dist/hook-packs/installer.js +12 -5
  19. package/dist/hook-packs/installer.js.map +1 -1
  20. package/dist/hook-packs/installer.ts +26 -7
  21. package/dist/hook-packs/marketing-research/manifest.json +2 -1
  22. package/dist/hook-packs/registry.d.ts +2 -0
  23. package/dist/hook-packs/registry.js.map +1 -1
  24. package/dist/hook-packs/registry.ts +2 -0
  25. package/dist/prompts/create-wizard.d.ts +16 -2
  26. package/dist/prompts/create-wizard.js +84 -11
  27. package/dist/prompts/create-wizard.js.map +1 -1
  28. package/dist/utils/checks.d.ts +8 -5
  29. package/dist/utils/checks.js +105 -10
  30. package/dist/utils/checks.js.map +1 -1
  31. package/dist/utils/format-paths.d.ts +14 -0
  32. package/dist/utils/format-paths.js +27 -0
  33. package/dist/utils/format-paths.js.map +1 -0
  34. package/dist/utils/git.d.ts +29 -0
  35. package/dist/utils/git.js +88 -0
  36. package/dist/utils/git.js.map +1 -0
  37. package/dist/utils/logger.d.ts +1 -0
  38. package/dist/utils/logger.js +4 -0
  39. package/dist/utils/logger.js.map +1 -1
  40. package/package.json +1 -1
  41. package/src/__tests__/create-wizard-git.test.ts +208 -0
  42. package/src/__tests__/git-utils.test.ts +268 -0
  43. package/src/__tests__/hook-packs.test.ts +5 -1
  44. package/src/__tests__/scaffold-git-e2e.test.ts +105 -0
  45. package/src/commands/add-domain.ts +1 -0
  46. package/src/commands/add-pack.ts +10 -163
  47. package/src/commands/agent.ts +161 -0
  48. package/src/commands/create.ts +89 -3
  49. package/src/commands/doctor.ts +1 -0
  50. package/src/commands/extend.ts +20 -1
  51. package/src/commands/install-knowledge.ts +1 -0
  52. package/src/commands/test.ts +141 -2
  53. package/src/hook-packs/flock-guard/manifest.json +2 -1
  54. package/src/hook-packs/installer.ts +26 -7
  55. package/src/hook-packs/marketing-research/manifest.json +2 -1
  56. package/src/hook-packs/registry.ts +2 -0
  57. package/src/prompts/create-wizard.ts +109 -14
  58. package/src/utils/checks.ts +122 -13
  59. package/src/utils/format-paths.ts +41 -0
  60. package/src/utils/git.ts +118 -0
  61. package/src/utils/logger.ts +5 -0
@@ -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('<package>', 'npm package name (e.g., "@soleri/domain-design")')
13
- .option('--no-install', 'Skip npm install (assume already installed)')
14
- .option('--no-inject', 'Skip forge inject (CLAUDE.md, entry-point regeneration)')
15
- .option('--force', 'Overwrite existing skills')
16
- .description('Add a domain pack to the agent in the current directory')
17
- .action(
18
- async (packageName: string, opts: { install: boolean; inject: boolean; force: boolean }) => {
19
- const ctx = detectAgent();
20
- if (!ctx) {
21
- p.log.error(
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
  }
@@ -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.');
@@ -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,
@@ -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;
@@ -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 });
@@ -98,6 +98,7 @@ export function registerInstallKnowledge(program: Command): void {
98
98
  agentPath: ctx.agentPath,
99
99
  bundlePath,
100
100
  generateFacades: opts.facades,
101
+ format: ctx.format,
101
102
  });
102
103
 
103
104
  s.stop(result.success ? result.summary : 'Installation failed');