@soleri/cli 1.12.5 → 7.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 +416 -0
- package/src/commands/create.ts +90 -6
- package/src/commands/dev.ts +114 -18
- package/src/commands/install.ts +78 -19
- 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/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,6 +84,33 @@ 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
|
+
const configPath = join(homedir(), '.opencode.json');
|
|
89
|
+
|
|
90
|
+
let config: Record<string, unknown> = {};
|
|
91
|
+
if (existsSync(configPath)) {
|
|
92
|
+
try {
|
|
93
|
+
const raw = readFileSync(configPath, 'utf-8');
|
|
94
|
+
const stripped = raw.replace(/^\s*\/\/.*$/gm, '');
|
|
95
|
+
config = JSON.parse(stripped);
|
|
96
|
+
} catch {
|
|
97
|
+
config = {};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!config.mcpServers || typeof config.mcpServers !== 'object') {
|
|
102
|
+
config.mcpServers = {};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const servers = config.mcpServers as Record<string, unknown>;
|
|
106
|
+
servers[agentId] = isFileTree
|
|
107
|
+
? fileTreeMcpEntry(agentDir)
|
|
108
|
+
: { type: 'stdio', command: 'node', args: [join(agentDir, 'dist', 'index.js')] };
|
|
109
|
+
|
|
110
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
111
|
+
p.log.success(`Registered ${agentId} in ~/.opencode.json`);
|
|
112
|
+
}
|
|
113
|
+
|
|
65
114
|
function escapeRegExp(s: string): string {
|
|
66
115
|
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
67
116
|
}
|
|
@@ -70,7 +119,7 @@ export function registerInstall(program: Command): void {
|
|
|
70
119
|
program
|
|
71
120
|
.command('install')
|
|
72
121
|
.argument('[dir]', 'Agent directory (defaults to cwd)')
|
|
73
|
-
.option('--target <target>', 'Registration target: claude, codex, or
|
|
122
|
+
.option('--target <target>', 'Registration target: opencode, claude, codex, or all', 'opencode')
|
|
74
123
|
.description('Register agent as MCP server in editor config')
|
|
75
124
|
.action(async (dir?: string, opts?: { target?: string }) => {
|
|
76
125
|
const resolvedDir = dir ? resolve(dir) : undefined;
|
|
@@ -81,19 +130,29 @@ export function registerInstall(program: Command): void {
|
|
|
81
130
|
process.exit(1);
|
|
82
131
|
}
|
|
83
132
|
|
|
84
|
-
const target = (opts?.target ?? '
|
|
133
|
+
const target = (opts?.target ?? 'opencode') as Target;
|
|
134
|
+
const validTargets: Target[] = ['claude', 'codex', 'opencode', 'both', 'all'];
|
|
135
|
+
const isFileTree = ctx.format === 'filetree';
|
|
85
136
|
|
|
86
|
-
if (target
|
|
87
|
-
p.log.error(`Invalid target "${target}". Use:
|
|
137
|
+
if (!validTargets.includes(target)) {
|
|
138
|
+
p.log.error(`Invalid target "${target}". Use: ${validTargets.join(', ')}`);
|
|
88
139
|
process.exit(1);
|
|
89
140
|
}
|
|
90
141
|
|
|
91
|
-
if (
|
|
92
|
-
|
|
142
|
+
if (isFileTree) {
|
|
143
|
+
p.log.info(`Detected file-tree agent (v7) — registering via @soleri/engine`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (target === 'claude' || target === 'both' || target === 'all') {
|
|
147
|
+
installClaude(ctx.agentId, ctx.agentPath, isFileTree);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (target === 'codex' || target === 'both' || target === 'all') {
|
|
151
|
+
installCodex(ctx.agentId, ctx.agentPath, isFileTree);
|
|
93
152
|
}
|
|
94
153
|
|
|
95
|
-
if (target === '
|
|
96
|
-
|
|
154
|
+
if (target === 'opencode' || target === 'all') {
|
|
155
|
+
installOpencode(ctx.agentId, ctx.agentPath, isFileTree);
|
|
97
156
|
}
|
|
98
157
|
|
|
99
158
|
p.log.info(`Agent ${ctx.agentId} is now available as an MCP server.`);
|
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram transport management — enable, disable, setup, status.
|
|
3
|
+
*
|
|
4
|
+
* `soleri telegram enable` — Add Telegram files to the current agent
|
|
5
|
+
* `soleri telegram disable` — Remove Telegram files from the current agent
|
|
6
|
+
* `soleri telegram setup` — Interactive config wizard (bot token, API key, model)
|
|
7
|
+
* `soleri telegram status` — Check Telegram configuration status
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from 'node:fs';
|
|
12
|
+
import { homedir } from 'node:os';
|
|
13
|
+
import { execFileSync } from 'node:child_process';
|
|
14
|
+
import type { Command } from 'commander';
|
|
15
|
+
import * as p from '@clack/prompts';
|
|
16
|
+
import {
|
|
17
|
+
generateTelegramBot,
|
|
18
|
+
generateTelegramAgent,
|
|
19
|
+
generateTelegramConfig,
|
|
20
|
+
generateTelegramSupervisor,
|
|
21
|
+
} from '@soleri/forge/lib';
|
|
22
|
+
import type { AgentConfig } from '@soleri/forge/lib';
|
|
23
|
+
import { detectAgent } from '../utils/agent-context.js';
|
|
24
|
+
|
|
25
|
+
// ─── Telegram file paths relative to agent src/ ─────────────────────
|
|
26
|
+
|
|
27
|
+
const TELEGRAM_FILES = [
|
|
28
|
+
'src/telegram-bot.ts',
|
|
29
|
+
'src/telegram-agent.ts',
|
|
30
|
+
'src/telegram-config.ts',
|
|
31
|
+
'src/telegram-supervisor.ts',
|
|
32
|
+
] as const;
|
|
33
|
+
|
|
34
|
+
// ─── Registration ───────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
export function registerTelegram(program: Command): void {
|
|
37
|
+
const tg = program
|
|
38
|
+
.command('telegram')
|
|
39
|
+
.description('Manage Telegram transport for the current agent');
|
|
40
|
+
|
|
41
|
+
// ─── enable ─────────────────────────────────────────────────────
|
|
42
|
+
tg.command('enable')
|
|
43
|
+
.description('Add Telegram transport files to the current agent')
|
|
44
|
+
.action(() => {
|
|
45
|
+
const ctx = detectAgent();
|
|
46
|
+
if (!ctx) {
|
|
47
|
+
p.log.error('No agent project detected in current directory.');
|
|
48
|
+
process.exit(1);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Check if already enabled
|
|
53
|
+
const existingFiles = TELEGRAM_FILES.filter((f) => existsSync(join(ctx.agentPath, f)));
|
|
54
|
+
if (existingFiles.length === TELEGRAM_FILES.length) {
|
|
55
|
+
p.log.warn('Telegram is already enabled for this agent.');
|
|
56
|
+
p.log.info('Run `soleri telegram setup` to configure it.');
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (existingFiles.length > 0) {
|
|
60
|
+
p.log.warn(
|
|
61
|
+
`Partial Telegram setup detected (${existingFiles.length}/${TELEGRAM_FILES.length} files). Regenerating all files.`,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Reconstruct AgentConfig
|
|
66
|
+
const config = readAgentConfig(ctx.agentPath, ctx.agentId);
|
|
67
|
+
if (!config) {
|
|
68
|
+
p.log.error('Could not read agent config from persona.ts and entry point.');
|
|
69
|
+
process.exit(1);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Generate the 4 Telegram files
|
|
74
|
+
const s = p.spinner();
|
|
75
|
+
s.start('Generating Telegram transport files...');
|
|
76
|
+
|
|
77
|
+
const telegramFiles: Array<[string, string]> = [
|
|
78
|
+
['src/telegram-bot.ts', generateTelegramBot(config)],
|
|
79
|
+
['src/telegram-agent.ts', generateTelegramAgent(config)],
|
|
80
|
+
['src/telegram-config.ts', generateTelegramConfig(config)],
|
|
81
|
+
['src/telegram-supervisor.ts', generateTelegramSupervisor(config)],
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
for (const [relPath, content] of telegramFiles) {
|
|
85
|
+
writeFileSync(join(ctx.agentPath, relPath), content, 'utf-8');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
s.stop('Generated 4 Telegram transport files');
|
|
89
|
+
|
|
90
|
+
// Add grammy to package.json if not present
|
|
91
|
+
const pkgPath = join(ctx.agentPath, 'package.json');
|
|
92
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
93
|
+
let pkgChanged = false;
|
|
94
|
+
|
|
95
|
+
if (!pkg.dependencies?.grammy) {
|
|
96
|
+
if (!pkg.dependencies) pkg.dependencies = {};
|
|
97
|
+
pkg.dependencies.grammy = '^1.35.0';
|
|
98
|
+
pkgChanged = true;
|
|
99
|
+
p.log.info('Added grammy dependency to package.json');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Add npm scripts if not present
|
|
103
|
+
if (!pkg.scripts) pkg.scripts = {};
|
|
104
|
+
if (!pkg.scripts['telegram:start']) {
|
|
105
|
+
pkg.scripts['telegram:start'] = 'node dist/telegram-supervisor.js';
|
|
106
|
+
pkgChanged = true;
|
|
107
|
+
}
|
|
108
|
+
if (!pkg.scripts['telegram:dev']) {
|
|
109
|
+
pkg.scripts['telegram:dev'] = 'tsx src/telegram-bot.ts';
|
|
110
|
+
pkgChanged = true;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (pkgChanged) {
|
|
114
|
+
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
|
|
115
|
+
p.log.info('Updated package.json with telegram scripts');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Install grammy
|
|
119
|
+
const installSpinner = p.spinner();
|
|
120
|
+
installSpinner.start('Installing grammy...');
|
|
121
|
+
try {
|
|
122
|
+
execFileSync('npm', ['install', '--no-fund', '--no-audit'], {
|
|
123
|
+
cwd: ctx.agentPath,
|
|
124
|
+
stdio: 'pipe',
|
|
125
|
+
timeout: 120_000,
|
|
126
|
+
});
|
|
127
|
+
installSpinner.stop('Installed grammy');
|
|
128
|
+
} catch {
|
|
129
|
+
installSpinner.stop('npm install skipped — run `npm install` manually');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
p.log.success('Telegram enabled!');
|
|
133
|
+
p.log.info(`Run \`soleri telegram setup\` to configure bot token and API key.`);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// ─── setup ──────────────────────────────────────────────────────
|
|
137
|
+
tg.command('setup')
|
|
138
|
+
.description('Interactive Telegram configuration wizard')
|
|
139
|
+
.action(async () => {
|
|
140
|
+
const ctx = detectAgent();
|
|
141
|
+
if (!ctx) {
|
|
142
|
+
p.log.error('No agent project detected in current directory.');
|
|
143
|
+
process.exit(1);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Check that Telegram files exist
|
|
148
|
+
const missingFiles = TELEGRAM_FILES.filter((f) => !existsSync(join(ctx.agentPath, f)));
|
|
149
|
+
if (missingFiles.length > 0) {
|
|
150
|
+
p.log.error('Telegram is not enabled. Run `soleri telegram enable` first.');
|
|
151
|
+
process.exit(1);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
p.intro(`Telegram Setup for ${ctx.agentId}`);
|
|
156
|
+
|
|
157
|
+
// Step 1: Bot token
|
|
158
|
+
p.log.step('Step 1: Create a Telegram bot');
|
|
159
|
+
p.log.message(
|
|
160
|
+
[
|
|
161
|
+
' Open Telegram and talk to @BotFather',
|
|
162
|
+
' Send /newbot and follow the instructions',
|
|
163
|
+
' Copy the bot token when you get it',
|
|
164
|
+
].join('\n'),
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
const botToken = await p.text({
|
|
168
|
+
message: 'Paste your bot token:',
|
|
169
|
+
placeholder: '123456789:ABCdefGHIjklMNOpqrsTUVwxyz',
|
|
170
|
+
validate: (val) => {
|
|
171
|
+
if (!val || val.trim().length === 0) return 'Bot token is required';
|
|
172
|
+
if (!val.includes(':')) return 'Invalid token format (expected number:string)';
|
|
173
|
+
return undefined;
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
if (p.isCancel(botToken)) {
|
|
177
|
+
p.cancel('Setup cancelled.');
|
|
178
|
+
process.exit(0);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Step 2: LLM API key
|
|
182
|
+
p.log.step('Step 2: LLM API Key');
|
|
183
|
+
p.log.message(' Your agent needs an API key to think');
|
|
184
|
+
|
|
185
|
+
const provider = await p.select({
|
|
186
|
+
message: 'Which provider?',
|
|
187
|
+
options: [
|
|
188
|
+
{ value: 'anthropic', label: 'Anthropic (Claude)' },
|
|
189
|
+
{ value: 'openai', label: 'OpenAI' },
|
|
190
|
+
{ value: 'env', label: 'Use environment variable (skip)' },
|
|
191
|
+
],
|
|
192
|
+
});
|
|
193
|
+
if (p.isCancel(provider)) {
|
|
194
|
+
p.cancel('Setup cancelled.');
|
|
195
|
+
process.exit(0);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
let apiKey = '';
|
|
199
|
+
if (provider !== 'env') {
|
|
200
|
+
const keyPlaceholder = provider === 'anthropic' ? 'sk-ant-...' : 'sk-...';
|
|
201
|
+
const envHint = provider === 'anthropic' ? 'ANTHROPIC_API_KEY' : 'OPENAI_API_KEY';
|
|
202
|
+
|
|
203
|
+
const keyInput = await p.text({
|
|
204
|
+
message: `Paste your ${provider === 'anthropic' ? 'Anthropic' : 'OpenAI'} API key:`,
|
|
205
|
+
placeholder: keyPlaceholder,
|
|
206
|
+
validate: (val) => {
|
|
207
|
+
if (!val || val.trim().length === 0)
|
|
208
|
+
return `API key is required (or set ${envHint} env var)`;
|
|
209
|
+
return undefined;
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
if (p.isCancel(keyInput)) {
|
|
213
|
+
p.cancel('Setup cancelled.');
|
|
214
|
+
process.exit(0);
|
|
215
|
+
}
|
|
216
|
+
apiKey = keyInput;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Step 3: Security
|
|
220
|
+
p.log.step('Step 3: Security (optional)');
|
|
221
|
+
|
|
222
|
+
const passphrase = await p.text({
|
|
223
|
+
message: 'Set a passphrase? Users must send this to authenticate.',
|
|
224
|
+
placeholder: '(empty for open access)',
|
|
225
|
+
defaultValue: '',
|
|
226
|
+
});
|
|
227
|
+
if (p.isCancel(passphrase)) {
|
|
228
|
+
p.cancel('Setup cancelled.');
|
|
229
|
+
process.exit(0);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Step 4: Model selection
|
|
233
|
+
p.log.step('Step 4: Model selection');
|
|
234
|
+
|
|
235
|
+
const modelOptions =
|
|
236
|
+
provider === 'openai'
|
|
237
|
+
? [
|
|
238
|
+
{ value: 'gpt-4.1', label: 'gpt-4.1 (recommended)' },
|
|
239
|
+
{ value: 'gpt-4.1-mini', label: 'gpt-4.1-mini (fast)' },
|
|
240
|
+
{ value: 'o3', label: 'o3 (reasoning)' },
|
|
241
|
+
]
|
|
242
|
+
: [
|
|
243
|
+
{ value: 'claude-sonnet-4-20250514', label: 'claude-sonnet-4 (fast, recommended)' },
|
|
244
|
+
{ value: 'claude-opus-4-20250514', label: 'claude-opus-4 (powerful)' },
|
|
245
|
+
{ value: 'claude-haiku-3-5-20241022', label: 'claude-3.5-haiku (economical)' },
|
|
246
|
+
];
|
|
247
|
+
|
|
248
|
+
const model = await p.select({
|
|
249
|
+
message: 'Default model:',
|
|
250
|
+
options: modelOptions,
|
|
251
|
+
});
|
|
252
|
+
if (p.isCancel(model)) {
|
|
253
|
+
p.cancel('Setup cancelled.');
|
|
254
|
+
process.exit(0);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Save config
|
|
258
|
+
const configDir = join(homedir(), `.${ctx.agentId}`);
|
|
259
|
+
mkdirSync(configDir, { recursive: true });
|
|
260
|
+
const configPath = join(configDir, 'telegram.json');
|
|
261
|
+
|
|
262
|
+
const telegramConfig: Record<string, unknown> = {
|
|
263
|
+
botToken: (botToken as string).trim(),
|
|
264
|
+
...(apiKey ? { apiKey } : {}),
|
|
265
|
+
provider: provider === 'env' ? 'anthropic' : provider,
|
|
266
|
+
model,
|
|
267
|
+
...(passphrase ? { passphrase } : {}),
|
|
268
|
+
allowedUsers: [],
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
writeFileSync(configPath, JSON.stringify(telegramConfig, null, 2) + '\n', 'utf-8');
|
|
272
|
+
|
|
273
|
+
p.outro(`Configuration saved to ${configPath}`);
|
|
274
|
+
|
|
275
|
+
console.log('');
|
|
276
|
+
p.log.info(' Run: npm run telegram:start');
|
|
277
|
+
p.log.info(' Or: npm run telegram:dev (with auto-restart)');
|
|
278
|
+
console.log('');
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// ─── disable ────────────────────────────────────────────────────
|
|
282
|
+
tg.command('disable')
|
|
283
|
+
.description('Remove Telegram transport from the current agent')
|
|
284
|
+
.action(async () => {
|
|
285
|
+
const ctx = detectAgent();
|
|
286
|
+
if (!ctx) {
|
|
287
|
+
p.log.error('No agent project detected in current directory.');
|
|
288
|
+
process.exit(1);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Check if any Telegram files exist
|
|
293
|
+
const existingFiles = TELEGRAM_FILES.filter((f) => existsSync(join(ctx.agentPath, f)));
|
|
294
|
+
if (existingFiles.length === 0) {
|
|
295
|
+
p.log.warn('Telegram is not enabled for this agent.');
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const confirmed = await p.confirm({
|
|
300
|
+
message: `Remove ${existingFiles.length} Telegram files and related config from ${ctx.agentId}?`,
|
|
301
|
+
});
|
|
302
|
+
if (p.isCancel(confirmed) || !confirmed) {
|
|
303
|
+
p.cancel('Cancelled.');
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Remove Telegram source files
|
|
308
|
+
for (const relPath of TELEGRAM_FILES) {
|
|
309
|
+
const fullPath = join(ctx.agentPath, relPath);
|
|
310
|
+
if (existsSync(fullPath)) {
|
|
311
|
+
unlinkSync(fullPath);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
p.log.info(`Removed ${existingFiles.length} Telegram source files`);
|
|
315
|
+
|
|
316
|
+
// Remove grammy from package.json and telegram scripts
|
|
317
|
+
const pkgPath = join(ctx.agentPath, 'package.json');
|
|
318
|
+
if (existsSync(pkgPath)) {
|
|
319
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
320
|
+
let changed = false;
|
|
321
|
+
|
|
322
|
+
if (pkg.dependencies?.grammy) {
|
|
323
|
+
delete pkg.dependencies.grammy;
|
|
324
|
+
changed = true;
|
|
325
|
+
}
|
|
326
|
+
if (pkg.scripts?.['telegram:start']) {
|
|
327
|
+
delete pkg.scripts['telegram:start'];
|
|
328
|
+
changed = true;
|
|
329
|
+
}
|
|
330
|
+
if (pkg.scripts?.['telegram:dev']) {
|
|
331
|
+
delete pkg.scripts['telegram:dev'];
|
|
332
|
+
changed = true;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (changed) {
|
|
336
|
+
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
|
|
337
|
+
p.log.info('Removed grammy dependency and telegram scripts from package.json');
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
p.log.success('Telegram disabled.');
|
|
342
|
+
p.log.info('Run `npm install` to clean up node_modules.');
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// ─── status ─────────────────────────────────────────────────────
|
|
346
|
+
tg.command('status')
|
|
347
|
+
.description('Check Telegram configuration status')
|
|
348
|
+
.action(() => {
|
|
349
|
+
const ctx = detectAgent();
|
|
350
|
+
if (!ctx) {
|
|
351
|
+
p.log.error('No agent project detected in current directory.');
|
|
352
|
+
process.exit(1);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
console.log(`\n Agent: ${ctx.agentId}`);
|
|
357
|
+
|
|
358
|
+
// Check source files
|
|
359
|
+
const presentFiles = TELEGRAM_FILES.filter((f) => existsSync(join(ctx.agentPath, f)));
|
|
360
|
+
const filesEnabled = presentFiles.length === TELEGRAM_FILES.length;
|
|
361
|
+
console.log(
|
|
362
|
+
` Files: ${filesEnabled ? 'all present' : `${presentFiles.length}/${TELEGRAM_FILES.length} present`}`,
|
|
363
|
+
);
|
|
364
|
+
if (!filesEnabled && presentFiles.length > 0) {
|
|
365
|
+
for (const f of TELEGRAM_FILES) {
|
|
366
|
+
const exists = existsSync(join(ctx.agentPath, f));
|
|
367
|
+
console.log(` ${exists ? '+' : '-'} ${f}`);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Check grammy dependency
|
|
372
|
+
const pkgPath = join(ctx.agentPath, 'package.json');
|
|
373
|
+
if (existsSync(pkgPath)) {
|
|
374
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
375
|
+
const hasGrammy = !!pkg.dependencies?.grammy;
|
|
376
|
+
console.log(
|
|
377
|
+
` Grammy: ${hasGrammy ? `installed (${pkg.dependencies.grammy})` : 'not in dependencies'}`,
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
const hasStartScript = !!pkg.scripts?.['telegram:start'];
|
|
381
|
+
const hasDevScript = !!pkg.scripts?.['telegram:dev'];
|
|
382
|
+
console.log(
|
|
383
|
+
` Scripts: ${hasStartScript && hasDevScript ? 'telegram:start, telegram:dev' : hasStartScript ? 'telegram:start only' : hasDevScript ? 'telegram:dev only' : 'none'}`,
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Check config file
|
|
388
|
+
const configPath = join(homedir(), `.${ctx.agentId}`, 'telegram.json');
|
|
389
|
+
if (existsSync(configPath)) {
|
|
390
|
+
try {
|
|
391
|
+
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
392
|
+
const hasToken = !!config.botToken;
|
|
393
|
+
const hasKey = !!config.apiKey;
|
|
394
|
+
console.log(` Config: ${configPath}`);
|
|
395
|
+
console.log(` Bot token: ${hasToken ? 'set' : 'not set'}`);
|
|
396
|
+
console.log(` API key: ${hasKey ? 'set' : 'not set (check env vars)'}`);
|
|
397
|
+
if (config.model) console.log(` Model: ${config.model}`);
|
|
398
|
+
if (config.passphrase) console.log(` Passphrase: set`);
|
|
399
|
+
} catch {
|
|
400
|
+
console.log(` Config: ${configPath} (invalid JSON)`);
|
|
401
|
+
}
|
|
402
|
+
} else {
|
|
403
|
+
console.log(` Config: not found at ${configPath}`);
|
|
404
|
+
if (filesEnabled) {
|
|
405
|
+
console.log(' Run `soleri telegram setup` to configure.');
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Overall status
|
|
410
|
+
const ready = filesEnabled && existsSync(configPath);
|
|
411
|
+
console.log(`\n Status: ${ready ? 'ready to start' : 'needs configuration'}`);
|
|
412
|
+
if (!filesEnabled) {
|
|
413
|
+
console.log(' Next: Run `soleri telegram enable`');
|
|
414
|
+
} else if (!existsSync(configPath)) {
|
|
415
|
+
console.log(' Next: Run `soleri telegram setup`');
|
|
416
|
+
} else {
|
|
417
|
+
console.log(' Next: Run `npm run telegram:start` or `npm run telegram:dev`');
|
|
418
|
+
}
|
|
419
|
+
console.log('');
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// ─── Helpers ──────────────────────────────────────────────────────────
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Reconstruct an AgentConfig from an existing scaffolded agent.
|
|
427
|
+
* Mirrors the logic in agent.ts but returns a config with telegram: true.
|
|
428
|
+
*/
|
|
429
|
+
function readAgentConfig(agentPath: string, agentId: string): AgentConfig | null {
|
|
430
|
+
// Try both locations: v6+ (src/identity/) and v5 (src/activation/)
|
|
431
|
+
const personaCandidates = [
|
|
432
|
+
join(agentPath, 'src', 'identity', 'persona.ts'),
|
|
433
|
+
join(agentPath, 'src', 'activation', 'persona.ts'),
|
|
434
|
+
];
|
|
435
|
+
const personaPath = personaCandidates.find((candidate) => existsSync(candidate));
|
|
436
|
+
if (!personaPath) return null;
|
|
437
|
+
const personaSrc = readFileSync(personaPath, 'utf-8');
|
|
438
|
+
|
|
439
|
+
const name = extractStringField(personaSrc, 'name') ?? agentId;
|
|
440
|
+
const role = extractStringField(personaSrc, 'role') ?? '';
|
|
441
|
+
const description = extractStringField(personaSrc, 'description') ?? '';
|
|
442
|
+
const tone =
|
|
443
|
+
(extractStringField(personaSrc, 'tone') as 'precise' | 'mentor' | 'pragmatic') ?? 'pragmatic';
|
|
444
|
+
const greeting = extractStringField(personaSrc, 'greeting') ?? `Hello! I'm ${name}.`;
|
|
445
|
+
const principles = extractArrayField(personaSrc, 'principles');
|
|
446
|
+
|
|
447
|
+
// Read domains from entry point
|
|
448
|
+
const indexPath = join(agentPath, 'src', 'index.ts');
|
|
449
|
+
const domains = existsSync(indexPath) ? extractDomains(readFileSync(indexPath, 'utf-8')) : [];
|
|
450
|
+
|
|
451
|
+
const pkg = JSON.parse(readFileSync(join(agentPath, 'package.json'), 'utf-8'));
|
|
452
|
+
|
|
453
|
+
return {
|
|
454
|
+
id: agentId,
|
|
455
|
+
name,
|
|
456
|
+
role,
|
|
457
|
+
description,
|
|
458
|
+
domains,
|
|
459
|
+
principles,
|
|
460
|
+
tone,
|
|
461
|
+
greeting,
|
|
462
|
+
outputDir: agentPath,
|
|
463
|
+
hookPacks: [],
|
|
464
|
+
model: pkg.soleri?.model ?? 'claude-code-sonnet-4',
|
|
465
|
+
setupTarget: pkg.soleri?.setupTarget ?? 'claude',
|
|
466
|
+
telegram: true, // Force true — we're enabling telegram
|
|
467
|
+
cognee: pkg.soleri?.cognee ?? false,
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function extractStringField(src: string, field: string): string | undefined {
|
|
472
|
+
const re = new RegExp(`${field}:\\s*'([^']*)'`);
|
|
473
|
+
const m = src.match(re);
|
|
474
|
+
return m ? m[1].replace(/\\'/g, "'") : undefined;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function extractArrayField(src: string, field: string): string[] {
|
|
478
|
+
const re = new RegExp(`${field}:\\s*\\[([\\s\\S]*?)\\]`);
|
|
479
|
+
const m = src.match(re);
|
|
480
|
+
if (!m) return [];
|
|
481
|
+
return [...m[1].matchAll(/'([^']*)'/g)].map((x) => x[1]);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function extractDomains(indexSrc: string): string[] {
|
|
485
|
+
const m = indexSrc.match(/createDomainFacades\(runtime,\s*['"][^'"]+['"]\s*,\s*\[([\s\S]*?)\]\)/);
|
|
486
|
+
if (!m) return [];
|
|
487
|
+
return [...m[1].matchAll(/['"]([^'"]+)['"]/g)].map((x) => x[1]);
|
|
488
|
+
}
|