@soleri/cli 1.12.5 → 8.0.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 (38) hide show
  1. package/dist/commands/add-pack.d.ts +2 -0
  2. package/dist/commands/add-pack.js +154 -0
  3. package/dist/commands/add-pack.js.map +1 -0
  4. package/dist/commands/agent.js +151 -2
  5. package/dist/commands/agent.js.map +1 -1
  6. package/dist/commands/cognee.d.ts +10 -0
  7. package/dist/commands/cognee.js +364 -0
  8. package/dist/commands/cognee.js.map +1 -0
  9. package/dist/commands/create.js +63 -5
  10. package/dist/commands/create.js.map +1 -1
  11. package/dist/commands/dev.js +104 -17
  12. package/dist/commands/dev.js.map +1 -1
  13. package/dist/commands/install.js +70 -18
  14. package/dist/commands/install.js.map +1 -1
  15. package/dist/commands/telegram.d.ts +10 -0
  16. package/dist/commands/telegram.js +423 -0
  17. package/dist/commands/telegram.js.map +1 -0
  18. package/dist/commands/uninstall.js +35 -6
  19. package/dist/commands/uninstall.js.map +1 -1
  20. package/dist/main.js +6 -0
  21. package/dist/main.js.map +1 -1
  22. package/dist/prompts/create-wizard.js +87 -6
  23. package/dist/prompts/create-wizard.js.map +1 -1
  24. package/dist/utils/agent-context.d.ts +9 -2
  25. package/dist/utils/agent-context.js +32 -0
  26. package/dist/utils/agent-context.js.map +1 -1
  27. package/package.json +1 -1
  28. package/src/commands/add-pack.ts +170 -0
  29. package/src/commands/agent.ts +174 -3
  30. package/src/commands/cognee.ts +419 -0
  31. package/src/commands/create.ts +90 -6
  32. package/src/commands/dev.ts +114 -18
  33. package/src/commands/install.ts +136 -18
  34. package/src/commands/telegram.ts +488 -0
  35. package/src/commands/uninstall.ts +41 -7
  36. package/src/main.ts +6 -0
  37. package/src/prompts/create-wizard.ts +93 -7
  38. package/src/utils/agent-context.ts +39 -2
@@ -1,4 +1,6 @@
1
1
  import { spawn } from 'node:child_process';
2
+ import { watch, writeFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
2
4
  import type { Command } from 'commander';
3
5
  import * as p from '@clack/prompts';
4
6
  import { detectAgent } from '../utils/agent-context.js';
@@ -14,26 +16,120 @@ export function registerDev(program: Command): void {
14
16
  process.exit(1);
15
17
  }
16
18
 
17
- p.log.info(`Starting ${ctx.agentId} in dev mode...`);
19
+ if (ctx.format === 'filetree') {
20
+ // v7: File-tree agent — watch files and regenerate CLAUDE.md
21
+ runFileTreeDev(ctx.agentPath, ctx.agentId);
22
+ } else {
23
+ // Legacy: TypeScript agent — run via tsx
24
+ runLegacyDev(ctx.agentPath, ctx.agentId);
25
+ }
26
+ });
27
+ }
18
28
 
19
- const child = spawn('npx', ['tsx', 'src/index.ts'], {
20
- cwd: ctx.agentPath,
21
- stdio: 'inherit',
22
- env: { ...process.env },
23
- });
29
+ function runFileTreeDev(agentPath: string, agentId: string): void {
30
+ p.log.info(`Starting ${agentId} in file-tree dev mode...`);
31
+ p.log.info('Starting Knowledge Engine + watching for file changes.');
32
+ p.log.info('CLAUDE.md will be regenerated automatically on changes.');
33
+ p.log.info('Press Ctrl+C to stop.\n');
24
34
 
25
- child.on('error', (err) => {
26
- p.log.error(`Failed to start: ${err.message}`);
27
- p.log.info('Make sure tsx is available: npm install -g tsx');
28
- process.exit(1);
29
- });
35
+ regenerateClaudeMd(agentPath);
36
+
37
+ // Start the engine server
38
+ const engineBin = require.resolve('@soleri/core/dist/engine/bin/soleri-engine.js');
39
+ const engine = spawn('node', [engineBin, '--agent', join(agentPath, 'agent.yaml')], {
40
+ stdio: ['pipe', 'inherit', 'inherit'],
41
+ env: { ...process.env },
42
+ });
43
+
44
+ engine.on('error', (err) => {
45
+ p.log.error(`Engine failed to start: ${err.message}`);
46
+ p.log.info('Make sure @soleri/core is built: cd packages/core && npm run build');
47
+ });
48
+
49
+ // Watch directories for changes
50
+ const watchPaths = [
51
+ join(agentPath, 'agent.yaml'),
52
+ join(agentPath, 'instructions'),
53
+ join(agentPath, 'workflows'),
54
+ join(agentPath, 'skills'),
55
+ ];
56
+
57
+ let debounceTimer: ReturnType<typeof setTimeout> | null = null;
58
+
59
+ for (const watchPath of watchPaths) {
60
+ try {
61
+ watch(watchPath, { recursive: true }, (_event, filename) => {
62
+ // Ignore CLAUDE.md changes (we generate it)
63
+ if (filename === 'CLAUDE.md' || filename === 'AGENTS.md') return;
64
+ // Ignore _engine.md changes (we generate it)
65
+ if (filename === '_engine.md') return;
30
66
 
31
- child.on('exit', (code, signal) => {
32
- if (signal) {
33
- p.log.warn(`Process terminated by signal ${signal}`);
34
- process.exit(1);
35
- }
36
- process.exit(code ?? 0);
67
+ // Debounce regenerate at most once per 200ms
68
+ if (debounceTimer) clearTimeout(debounceTimer);
69
+ debounceTimer = setTimeout(() => {
70
+ const changedFile = filename ? ` (${filename})` : '';
71
+ p.log.info(`Change detected${changedFile} — regenerating CLAUDE.md`);
72
+ regenerateClaudeMd(agentPath);
73
+ }, 200);
37
74
  });
38
- });
75
+ } catch {
76
+ // Directory may not exist yet — that's OK
77
+ }
78
+ }
79
+
80
+ // Graceful shutdown — kill engine too
81
+ const shutdown = () => {
82
+ p.log.info('\nStopping dev mode...');
83
+ engine.kill();
84
+ process.exit(0);
85
+ };
86
+
87
+ process.on('SIGINT', shutdown);
88
+ process.on('SIGTERM', shutdown);
89
+
90
+ engine.on('exit', (code) => {
91
+ if (code !== 0 && code !== null) {
92
+ p.log.error(`Engine exited with code ${code}`);
93
+ process.exit(1);
94
+ }
95
+ });
96
+ }
97
+
98
+ function regenerateClaudeMd(agentPath: string): void {
99
+ try {
100
+ // Dynamic import to avoid loading forge at CLI startup
101
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
102
+ const { composeClaudeMd } = require('@soleri/forge/lib');
103
+ const { content } = composeClaudeMd(agentPath);
104
+ writeFileSync(join(agentPath, 'CLAUDE.md'), content, 'utf-8');
105
+ p.log.success('CLAUDE.md regenerated');
106
+ } catch (err) {
107
+ p.log.error(
108
+ `Failed to regenerate CLAUDE.md: ${err instanceof Error ? err.message : String(err)}`,
109
+ );
110
+ }
111
+ }
112
+
113
+ function runLegacyDev(agentPath: string, agentId: string): void {
114
+ p.log.info(`Starting ${agentId} in dev mode...`);
115
+
116
+ const child = spawn('npx', ['tsx', 'src/index.ts'], {
117
+ cwd: agentPath,
118
+ stdio: 'inherit',
119
+ env: { ...process.env },
120
+ });
121
+
122
+ child.on('error', (err) => {
123
+ p.log.error(`Failed to start: ${err.message}`);
124
+ p.log.info('Make sure tsx is available: npm install -g tsx');
125
+ process.exit(1);
126
+ });
127
+
128
+ child.on('exit', (code, signal) => {
129
+ if (signal) {
130
+ p.log.warn(`Process terminated by signal ${signal}`);
131
+ process.exit(1);
132
+ }
133
+ process.exit(code ?? 0);
134
+ });
39
135
  }
@@ -5,9 +5,28 @@ import { homedir } from 'node:os';
5
5
  import * as p from '@clack/prompts';
6
6
  import { detectAgent } from '../utils/agent-context.js';
7
7
 
8
- type Target = 'claude' | 'codex' | 'both';
8
+ type Target = 'claude' | 'codex' | 'opencode' | 'both' | 'all';
9
9
 
10
- function installClaude(agentId: string, agentDir: string): void {
10
+ /** MCP server entry for file-tree agents (uses npx @soleri/engine) */
11
+ function fileTreeMcpEntry(agentDir: string): Record<string, unknown> {
12
+ return {
13
+ type: 'stdio',
14
+ command: 'npx',
15
+ args: ['@soleri/engine', '--agent', join(agentDir, 'agent.yaml')],
16
+ };
17
+ }
18
+
19
+ /** MCP server entry for legacy TypeScript agents (uses node dist/index.js) */
20
+ function legacyMcpEntry(agentDir: string): Record<string, unknown> {
21
+ return {
22
+ type: 'stdio',
23
+ command: 'node',
24
+ args: [join(agentDir, 'dist', 'index.js')],
25
+ env: {},
26
+ };
27
+ }
28
+
29
+ function installClaude(agentId: string, agentDir: string, isFileTree: boolean): void {
11
30
  const configPath = join(homedir(), '.claude.json');
12
31
  let config: Record<string, unknown> = {};
13
32
 
@@ -24,18 +43,15 @@ function installClaude(agentId: string, agentDir: string): void {
24
43
  config.mcpServers = {};
25
44
  }
26
45
 
27
- (config.mcpServers as Record<string, unknown>)[agentId] = {
28
- type: 'stdio',
29
- command: 'node',
30
- args: [join(agentDir, 'dist', 'index.js')],
31
- env: {},
32
- };
46
+ (config.mcpServers as Record<string, unknown>)[agentId] = isFileTree
47
+ ? fileTreeMcpEntry(agentDir)
48
+ : legacyMcpEntry(agentDir);
33
49
 
34
50
  writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
35
51
  p.log.success(`Registered ${agentId} in ~/.claude.json`);
36
52
  }
37
53
 
38
- function installCodex(agentId: string, agentDir: string): void {
54
+ function installCodex(agentId: string, agentDir: string, isFileTree: boolean): void {
39
55
  const codexDir = join(homedir(), '.codex');
40
56
  const configPath = join(codexDir, 'config.toml');
41
57
 
@@ -53,8 +69,14 @@ function installCodex(agentId: string, agentDir: string): void {
53
69
  const sectionRegex = new RegExp(`\\[mcp_servers\\.${escapeRegExp(agentId)}\\][^\\[]*`, 's');
54
70
  content = content.replace(sectionRegex, '').trim();
55
71
 
56
- const entryPoint = join(agentDir, 'dist', 'index.js');
57
- const section = `\n\n${sectionHeader}\ncommand = "node"\nargs = ["${entryPoint}"]\n`;
72
+ let section: string;
73
+ if (isFileTree) {
74
+ const agentYamlPath = join(agentDir, 'agent.yaml');
75
+ section = `\n\n${sectionHeader}\ncommand = "npx"\nargs = ["@soleri/engine", "--agent", "${agentYamlPath}"]\n`;
76
+ } else {
77
+ const entryPoint = join(agentDir, 'dist', 'index.js');
78
+ section = `\n\n${sectionHeader}\ncommand = "node"\nargs = ["${entryPoint}"]\n`;
79
+ }
58
80
 
59
81
  content = content + section;
60
82
 
@@ -62,15 +84,98 @@ function installCodex(agentId: string, agentDir: string): void {
62
84
  p.log.success(`Registered ${agentId} in ~/.codex/config.toml`);
63
85
  }
64
86
 
87
+ function installOpencode(agentId: string, agentDir: string, isFileTree: boolean): void {
88
+ // OpenCode uses ~/.config/opencode/opencode.json (not ~/.opencode.json)
89
+ // Config uses "mcp" (not "mcpServers"), type "local" (not "stdio"), command as array
90
+ const configDir = join(homedir(), '.config', 'opencode');
91
+ const configPath = join(configDir, 'opencode.json');
92
+
93
+ if (!existsSync(configDir)) {
94
+ mkdirSync(configDir, { recursive: true });
95
+ }
96
+
97
+ let config: Record<string, unknown> = {};
98
+ if (existsSync(configPath)) {
99
+ try {
100
+ const raw = readFileSync(configPath, 'utf-8');
101
+ const stripped = raw.replace(/^\s*\/\/.*$/gm, '');
102
+ config = JSON.parse(stripped);
103
+ } catch {
104
+ config = {};
105
+ }
106
+ }
107
+
108
+ if (!config.mcp || typeof config.mcp !== 'object') {
109
+ config.mcp = {};
110
+ }
111
+
112
+ const servers = config.mcp as Record<string, unknown>;
113
+ if (isFileTree) {
114
+ servers[agentId] = {
115
+ type: 'local',
116
+ command: [
117
+ 'node',
118
+ join(
119
+ agentDir,
120
+ '..',
121
+ 'soleri',
122
+ 'packages',
123
+ 'core',
124
+ 'dist',
125
+ 'engine',
126
+ 'bin',
127
+ 'soleri-engine.js',
128
+ ),
129
+ '--agent',
130
+ join(agentDir, 'agent.yaml'),
131
+ ],
132
+ };
133
+ } else {
134
+ servers[agentId] = {
135
+ type: 'local',
136
+ command: ['node', join(agentDir, 'dist', 'index.js')],
137
+ };
138
+ }
139
+
140
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
141
+ p.log.success(`Registered ${agentId} in ~/.config/opencode/opencode.json`);
142
+ }
143
+
65
144
  function escapeRegExp(s: string): string {
66
145
  return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
67
146
  }
68
147
 
148
+ /**
149
+ * Create a global launcher script so the agent can be invoked by name from any directory.
150
+ * e.g., typing `ernesto` opens Claude Code with that agent's MCP config.
151
+ */
152
+ function installLauncher(agentId: string, agentDir: string): void {
153
+ const binPath = join('/usr/local/bin', agentId);
154
+
155
+ const script = [
156
+ '#!/bin/bash',
157
+ `# ${agentId} — Soleri second brain launcher`,
158
+ `# Type "${agentId}" from any directory to open Claude Code with this agent`,
159
+ `exec claude --mcp-config ${join(agentDir, '.mcp.json')}`,
160
+ '',
161
+ ].join('\n');
162
+
163
+ try {
164
+ writeFileSync(binPath, script, { mode: 0o755 });
165
+ p.log.success(`Launcher created: type "${agentId}" from any directory to start`);
166
+ } catch {
167
+ p.log.warn(`Could not create launcher at ${binPath} (may need sudo)`);
168
+ p.log.info(
169
+ `To create manually: sudo bash -c 'cat > ${binPath} << "EOF"\\n#!/bin/bash\\nexec claude --mcp-config ${join(agentDir, '.mcp.json')}\\nEOF' && chmod +x ${binPath}`,
170
+ );
171
+ }
172
+ }
173
+
69
174
  export function registerInstall(program: Command): void {
70
175
  program
71
176
  .command('install')
72
177
  .argument('[dir]', 'Agent directory (defaults to cwd)')
73
- .option('--target <target>', 'Registration target: claude, codex, or both', 'claude')
178
+ .option('--target <target>', 'Registration target: claude, opencode, codex, or all', 'claude')
74
179
  .description('Register agent as MCP server in editor config')
75
180
  .action(async (dir?: string, opts?: { target?: string }) => {
76
181
  const resolvedDir = dir ? resolve(dir) : undefined;
@@ -82,20 +187,33 @@ export function registerInstall(program: Command): void {
82
187
  }
83
188
 
84
189
  const target = (opts?.target ?? 'claude') as Target;
190
+ const validTargets: Target[] = ['claude', 'codex', 'opencode', 'both', 'all'];
191
+ const isFileTree = ctx.format === 'filetree';
85
192
 
86
- if (target !== 'claude' && target !== 'codex' && target !== 'both') {
87
- p.log.error(`Invalid target "${target}". Use: claude, codex, or both`);
193
+ if (!validTargets.includes(target)) {
194
+ p.log.error(`Invalid target "${target}". Use: ${validTargets.join(', ')}`);
88
195
  process.exit(1);
89
196
  }
90
197
 
91
- if (target === 'claude' || target === 'both') {
92
- installClaude(ctx.agentId, ctx.agentPath);
198
+ if (isFileTree) {
199
+ p.log.info(`Detected file-tree agent (v7) — registering via @soleri/engine`);
93
200
  }
94
201
 
95
- if (target === 'codex' || target === 'both') {
96
- installCodex(ctx.agentId, ctx.agentPath);
202
+ if (target === 'claude' || target === 'both' || target === 'all') {
203
+ installClaude(ctx.agentId, ctx.agentPath, isFileTree);
97
204
  }
98
205
 
206
+ if (target === 'codex' || target === 'both' || target === 'all') {
207
+ installCodex(ctx.agentId, ctx.agentPath, isFileTree);
208
+ }
209
+
210
+ if (target === 'opencode' || target === 'all') {
211
+ installOpencode(ctx.agentId, ctx.agentPath, isFileTree);
212
+ }
213
+
214
+ // Create global launcher script
215
+ installLauncher(ctx.agentId, ctx.agentPath);
216
+
99
217
  p.log.info(`Agent ${ctx.agentId} is now available as an MCP server.`);
100
218
  });
101
219
  }