@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.
- package/dist/commands/add-pack.d.ts +2 -0
- package/dist/commands/add-pack.js +154 -0
- package/dist/commands/add-pack.js.map +1 -0
- package/dist/commands/agent.js +151 -2
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/cognee.d.ts +10 -0
- package/dist/commands/cognee.js +364 -0
- package/dist/commands/cognee.js.map +1 -0
- package/dist/commands/create.js +63 -5
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/dev.js +104 -17
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/install.js +70 -18
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/telegram.d.ts +10 -0
- package/dist/commands/telegram.js +423 -0
- package/dist/commands/telegram.js.map +1 -0
- package/dist/commands/uninstall.js +35 -6
- package/dist/commands/uninstall.js.map +1 -1
- package/dist/main.js +6 -0
- package/dist/main.js.map +1 -1
- package/dist/prompts/create-wizard.js +87 -6
- package/dist/prompts/create-wizard.js.map +1 -1
- package/dist/utils/agent-context.d.ts +9 -2
- package/dist/utils/agent-context.js +32 -0
- package/dist/utils/agent-context.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/add-pack.ts +170 -0
- package/src/commands/agent.ts +174 -3
- package/src/commands/cognee.ts +419 -0
- package/src/commands/create.ts +90 -6
- package/src/commands/dev.ts +114 -18
- package/src/commands/install.ts +136 -18
- package/src/commands/telegram.ts +488 -0
- package/src/commands/uninstall.ts +41 -7
- package/src/main.ts +6 -0
- package/src/prompts/create-wizard.ts +93 -7
- package/src/utils/agent-context.ts +39 -2
package/src/commands/dev.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
32
|
-
if (
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
}
|
package/src/commands/install.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
29
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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
|
|
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
|
|
87
|
-
p.log.error(`Invalid target "${target}". Use:
|
|
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 (
|
|
92
|
-
|
|
198
|
+
if (isFileTree) {
|
|
199
|
+
p.log.info(`Detected file-tree agent (v7) — registering via @soleri/engine`);
|
|
93
200
|
}
|
|
94
201
|
|
|
95
|
-
if (target === '
|
|
96
|
-
|
|
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
|
}
|