@soleri/forge 5.11.0 → 5.12.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 (53) hide show
  1. package/dist/facades/forge.facade.js +3 -3
  2. package/dist/facades/forge.facade.js.map +1 -1
  3. package/dist/lib.d.ts +2 -2
  4. package/dist/lib.js +1 -1
  5. package/dist/lib.js.map +1 -1
  6. package/dist/scaffolder.js +137 -20
  7. package/dist/scaffolder.js.map +1 -1
  8. package/dist/templates/agents-md.d.ts +5 -0
  9. package/dist/templates/agents-md.js +33 -0
  10. package/dist/templates/agents-md.js.map +1 -0
  11. package/dist/templates/entry-point.js +15 -2
  12. package/dist/templates/entry-point.js.map +1 -1
  13. package/dist/templates/package-json.js +7 -0
  14. package/dist/templates/package-json.js.map +1 -1
  15. package/dist/templates/readme.js +80 -27
  16. package/dist/templates/readme.js.map +1 -1
  17. package/dist/templates/setup-script.d.ts +1 -1
  18. package/dist/templates/setup-script.js +135 -53
  19. package/dist/templates/setup-script.js.map +1 -1
  20. package/dist/templates/skills.d.ts +0 -7
  21. package/dist/templates/skills.js +0 -21
  22. package/dist/templates/skills.js.map +1 -1
  23. package/dist/templates/telegram-agent.d.ts +6 -0
  24. package/dist/templates/telegram-agent.js +212 -0
  25. package/dist/templates/telegram-agent.js.map +1 -0
  26. package/dist/templates/telegram-bot.d.ts +6 -0
  27. package/dist/templates/telegram-bot.js +450 -0
  28. package/dist/templates/telegram-bot.js.map +1 -0
  29. package/dist/templates/telegram-config.d.ts +6 -0
  30. package/dist/templates/telegram-config.js +81 -0
  31. package/dist/templates/telegram-config.js.map +1 -0
  32. package/dist/templates/telegram-supervisor.d.ts +6 -0
  33. package/dist/templates/telegram-supervisor.js +148 -0
  34. package/dist/templates/telegram-supervisor.js.map +1 -0
  35. package/dist/types.d.ts +13 -5
  36. package/dist/types.js +7 -1
  37. package/dist/types.js.map +1 -1
  38. package/package.json +1 -1
  39. package/src/__tests__/scaffolder.test.ts +62 -0
  40. package/src/facades/forge.facade.ts +3 -3
  41. package/src/lib.ts +2 -1
  42. package/src/scaffolder.ts +170 -28
  43. package/src/templates/agents-md.ts +35 -0
  44. package/src/templates/entry-point.ts +15 -2
  45. package/src/templates/package-json.ts +7 -0
  46. package/src/templates/readme.ts +89 -27
  47. package/src/templates/setup-script.ts +141 -54
  48. package/src/templates/skills.ts +0 -23
  49. package/src/templates/telegram-agent.ts +214 -0
  50. package/src/templates/telegram-bot.ts +452 -0
  51. package/src/templates/telegram-config.ts +83 -0
  52. package/src/templates/telegram-supervisor.ts +150 -0
  53. package/src/types.ts +9 -2
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Template: Telegram configuration module.
3
+ * Generated by @soleri/forge for agents with Telegram transport.
4
+ */
5
+ export function generateTelegramConfig(config) {
6
+ return `/**
7
+ * Telegram Configuration — env + file-based config loading.
8
+ * Generated by @soleri/forge.
9
+ */
10
+
11
+ import { readFileSync, existsSync } from 'node:fs';
12
+ import { join } from 'node:path';
13
+ import { homedir } from 'node:os';
14
+
15
+ export interface TelegramConfig {
16
+ /** Telegram bot token (from @BotFather). */
17
+ botToken: string;
18
+ /** Anthropic API key. */
19
+ apiKey: string;
20
+ /** Claude model to use. Default: 'claude-sonnet-4-20250514'. */
21
+ model?: string;
22
+ /** Working directory for the agent. Default: cwd. */
23
+ workingDirectory?: string;
24
+ /** Allowed user IDs (empty = any authenticated user). */
25
+ allowedUsers?: number[];
26
+ /** Auth passphrase. If unset, auth is disabled. */
27
+ passphrase?: string;
28
+ /** Max output tokens per LLM call. Default: 16384. */
29
+ maxTokens?: number;
30
+ /** Max agent loop iterations. Default: 200. */
31
+ maxIterations?: number;
32
+ }
33
+
34
+ const CONFIG_DIR = join(homedir(), '.${config.id}');
35
+ const CONFIG_FILE = join(CONFIG_DIR, 'telegram.json');
36
+
37
+ /**
38
+ * Load Telegram config from environment variables and config file.
39
+ * Environment variables take priority over file-based config.
40
+ */
41
+ export function loadTelegramConfig(): TelegramConfig {
42
+ let fileConfig: Partial<TelegramConfig> = {};
43
+
44
+ if (existsSync(CONFIG_FILE)) {
45
+ try {
46
+ fileConfig = JSON.parse(readFileSync(CONFIG_FILE, 'utf-8'));
47
+ } catch {
48
+ console.error('[config] Failed to parse telegram.json, using defaults');
49
+ }
50
+ }
51
+
52
+ const botToken = process.env.TELEGRAM_BOT_TOKEN ?? fileConfig.botToken ?? '';
53
+ const apiKey =
54
+ process.env.ANTHROPIC_API_KEY ?? fileConfig.apiKey ?? '';
55
+
56
+ if (!botToken) {
57
+ throw new Error(
58
+ 'Missing TELEGRAM_BOT_TOKEN. Set via environment or ~/.${config.id}/telegram.json',
59
+ );
60
+ }
61
+ if (!apiKey) {
62
+ throw new Error(
63
+ 'Missing ANTHROPIC_API_KEY. Set via environment or ~/.${config.id}/telegram.json',
64
+ );
65
+ }
66
+
67
+ return {
68
+ botToken,
69
+ apiKey,
70
+ model: process.env.ANTHROPIC_MODEL ?? fileConfig.model ?? 'claude-sonnet-4-20250514',
71
+ workingDirectory:
72
+ process.env.AGENT_WORKING_DIR ?? fileConfig.workingDirectory ?? process.cwd(),
73
+ allowedUsers: fileConfig.allowedUsers ?? [],
74
+ passphrase: fileConfig.passphrase,
75
+ maxTokens: fileConfig.maxTokens ?? 16384,
76
+ maxIterations: fileConfig.maxIterations ?? 200,
77
+ };
78
+ }
79
+ `;
80
+ }
81
+ //# sourceMappingURL=telegram-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telegram-config.js","sourceRoot":"","sources":["../../src/templates/telegram-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,UAAU,sBAAsB,CAAC,MAAmB;IACxD,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;uCA4B8B,MAAM,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;+DAwBe,MAAM,CAAC,EAAE;;;;;8DAKV,MAAM,CAAC,EAAE;;;;;;;;;;;;;;;;CAgBtE,CAAC;AACF,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Template: Telegram supervisor — process management with restart and logging.
3
+ * Generated by @soleri/forge for agents with Telegram transport.
4
+ */
5
+ import type { AgentConfig } from '../types.js';
6
+ export declare function generateTelegramSupervisor(config: AgentConfig): string;
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Template: Telegram supervisor — process management with restart and logging.
3
+ * Generated by @soleri/forge for agents with Telegram transport.
4
+ */
5
+ export function generateTelegramSupervisor(config) {
6
+ return `/**
7
+ * ${config.name} — Telegram Bot Supervisor.
8
+ * Generated by @soleri/forge.
9
+ *
10
+ * Wraps the bot process with:
11
+ * - Restart on crash (exponential backoff)
12
+ * - Restart on self-update (exit code 75)
13
+ * - Graceful shutdown (SIGINT/SIGTERM)
14
+ * - Log rotation
15
+ */
16
+
17
+ import { spawn } from 'node:child_process';
18
+ import { mkdirSync, appendFileSync, readdirSync, rmSync, statSync } from 'node:fs';
19
+ import { join } from 'node:path';
20
+ import { homedir } from 'node:os';
21
+
22
+ const LOG_DIR = join(homedir(), '.${config.id}', 'logs');
23
+ const MAX_LOG_AGE_DAYS = 7;
24
+ const SELF_UPDATE_EXIT_CODE = 75;
25
+
26
+ // Exponential backoff: 1s, 2s, 4s, 8s, 16s, 30s max
27
+ const MIN_RESTART_DELAY = 1000;
28
+ const MAX_RESTART_DELAY = 30_000;
29
+
30
+ let restartCount = 0;
31
+ let shutdownRequested = false;
32
+
33
+ function log(message: string): void {
34
+ const ts = new Date().toISOString();
35
+ const line = \`[\${ts}] [supervisor] \${message}\`;
36
+ console.log(line);
37
+ try {
38
+ mkdirSync(LOG_DIR, { recursive: true });
39
+ const date = ts.split('T')[0];
40
+ appendFileSync(join(LOG_DIR, \`telegram-\${date}.log\`), line + '\\n', 'utf-8');
41
+ } catch {
42
+ // Log write failure is non-critical
43
+ }
44
+ }
45
+
46
+ function rotateLogs(): void {
47
+ try {
48
+ const cutoff = Date.now() - MAX_LOG_AGE_DAYS * 24 * 60 * 60 * 1000;
49
+ const files = readdirSync(LOG_DIR);
50
+ for (const file of files) {
51
+ if (!file.startsWith('telegram-') && !file.startsWith('events-')) continue;
52
+ const filePath = join(LOG_DIR, file);
53
+ try {
54
+ const stat = statSync(filePath);
55
+ if (stat.mtimeMs < cutoff) {
56
+ rmSync(filePath);
57
+ log(\`Rotated old log: \${file}\`);
58
+ }
59
+ } catch {
60
+ // Skip files we can't stat
61
+ }
62
+ }
63
+ } catch {
64
+ // Rotation failure is non-critical
65
+ }
66
+ }
67
+
68
+ function getRestartDelay(): number {
69
+ const delay = Math.min(MIN_RESTART_DELAY * Math.pow(2, restartCount), MAX_RESTART_DELAY);
70
+ return delay;
71
+ }
72
+
73
+ function startBot(): void {
74
+ rotateLogs();
75
+ log(\`Starting bot (attempt \${restartCount + 1})...\`);
76
+
77
+ const child = spawn('node', [join(process.cwd(), 'dist', 'telegram-bot.js')], {
78
+ stdio: 'inherit',
79
+ env: { ...process.env },
80
+ });
81
+
82
+ child.on('exit', (code, signal) => {
83
+ if (shutdownRequested) {
84
+ log('Bot stopped (shutdown requested).');
85
+ process.exit(0);
86
+ }
87
+
88
+ if (code === SELF_UPDATE_EXIT_CODE) {
89
+ log('Bot requested self-update restart. Rebuilding...');
90
+ restartCount = 0;
91
+ try {
92
+ const { execFileSync } = require('node:child_process');
93
+ execFileSync('npm', ['run', 'build'], { cwd: process.cwd(), stdio: 'pipe', timeout: 60_000 });
94
+ log('Rebuild succeeded. Restarting...');
95
+ } catch (buildErr: unknown) {
96
+ const buildMsg = buildErr instanceof Error ? buildErr.message : String(buildErr);
97
+ log(\`Rebuild failed: \${buildMsg}. Attempting rollback...\`);
98
+ try {
99
+ const { execFileSync: execFile2 } = require('node:child_process');
100
+ execFile2('git', ['revert', 'HEAD', '--no-edit'], { cwd: process.cwd(), stdio: 'pipe', timeout: 30_000 });
101
+ execFile2('npm', ['run', 'build'], { cwd: process.cwd(), stdio: 'pipe', timeout: 60_000 });
102
+ log('Rollback succeeded. Restarting with previous code...');
103
+ } catch {
104
+ log('Rollback also failed. Restarting with current code anyway...');
105
+ }
106
+ }
107
+ setTimeout(startBot, 1000);
108
+ return;
109
+ }
110
+
111
+ if (code === 0) {
112
+ log('Bot exited cleanly.');
113
+ process.exit(0);
114
+ }
115
+
116
+ restartCount++;
117
+ const delay = getRestartDelay();
118
+ log(\`Bot exited with code \${code ?? signal}. Restarting in \${delay}ms...\`);
119
+ setTimeout(startBot, delay);
120
+ });
121
+
122
+ child.on('error', (err) => {
123
+ log(\`Failed to start bot: \${err.message}\`);
124
+ restartCount++;
125
+ const delay = getRestartDelay();
126
+ setTimeout(startBot, delay);
127
+ });
128
+
129
+ // Reset backoff after 60 seconds of successful running
130
+ setTimeout(() => {
131
+ if (!shutdownRequested) restartCount = 0;
132
+ }, 60_000);
133
+ }
134
+
135
+ // Graceful shutdown
136
+ process.on('SIGINT', () => {
137
+ shutdownRequested = true;
138
+ log('SIGINT received, shutting down...');
139
+ });
140
+ process.on('SIGTERM', () => {
141
+ shutdownRequested = true;
142
+ log('SIGTERM received, shutting down...');
143
+ });
144
+
145
+ startBot();
146
+ `;
147
+ }
148
+ //# sourceMappingURL=telegram-supervisor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telegram-supervisor.js","sourceRoot":"","sources":["../../src/templates/telegram-supervisor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,UAAU,0BAA0B,CAAC,MAAmB;IAC5D,OAAO;KACJ,MAAM,CAAC,IAAI;;;;;;;;;;;;;;;oCAeoB,MAAM,CAAC,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4H5C,CAAC;AACF,CAAC"}
package/dist/types.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
- /** Communication tone for the agent persona */
3
- export declare const TONES: readonly ["precise", "mentor", "pragmatic"];
4
- export type Tone = (typeof TONES)[number];
2
+ /** Where to scaffold host/client integration setup. */
3
+ export declare const SETUP_TARGETS: readonly ["claude", "codex", "both"];
4
+ export type SetupTarget = (typeof SETUP_TARGETS)[number];
5
5
  /** Agent configuration — everything needed to scaffold */
6
6
  export declare const AgentConfigSchema: z.ZodObject<{
7
7
  /** Agent identifier (kebab-case, used for directory and package name) */
@@ -26,21 +26,27 @@ export declare const AgentConfigSchema: z.ZodObject<{
26
26
  hookPacks: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
27
27
  /** Skills to include (if omitted, all skills are included for backward compat) */
28
28
  skills: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
29
+ /** AI client setup target: Claude Code, Codex, or both */
30
+ setupTarget: z.ZodDefault<z.ZodOptional<z.ZodEnum<["claude", "codex", "both"]>>>;
31
+ /** Enable Telegram transport scaffolding. Default: false. */
32
+ telegram: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
29
33
  }, "strip", z.ZodTypeAny, {
30
- name: string;
31
34
  id: string;
35
+ name: string;
32
36
  role: string;
33
37
  description: string;
34
38
  domains: string[];
35
39
  principles: string[];
36
40
  tone: "precise" | "mentor" | "pragmatic";
37
41
  outputDir: string;
42
+ setupTarget: "claude" | "codex" | "both";
43
+ telegram: boolean;
38
44
  greeting?: string | undefined;
39
45
  hookPacks?: string[] | undefined;
40
46
  skills?: string[] | undefined;
41
47
  }, {
42
- name: string;
43
48
  id: string;
49
+ name: string;
44
50
  role: string;
45
51
  description: string;
46
52
  domains: string[];
@@ -50,6 +56,8 @@ export declare const AgentConfigSchema: z.ZodObject<{
50
56
  outputDir?: string | undefined;
51
57
  hookPacks?: string[] | undefined;
52
58
  skills?: string[] | undefined;
59
+ setupTarget?: "claude" | "codex" | "both" | undefined;
60
+ telegram?: boolean | undefined;
53
61
  }>;
54
62
  export type AgentConfig = z.infer<typeof AgentConfigSchema>;
55
63
  /** Result of scaffolding */
package/dist/types.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import { z } from 'zod';
2
2
  /** Communication tone for the agent persona */
3
- export const TONES = ['precise', 'mentor', 'pragmatic'];
3
+ const TONES = ['precise', 'mentor', 'pragmatic'];
4
+ /** Where to scaffold host/client integration setup. */
5
+ export const SETUP_TARGETS = ['claude', 'codex', 'both'];
4
6
  /** Agent configuration — everything needed to scaffold */
5
7
  export const AgentConfigSchema = z.object({
6
8
  /** Agent identifier (kebab-case, used for directory and package name) */
@@ -25,5 +27,9 @@ export const AgentConfigSchema = z.object({
25
27
  hookPacks: z.array(z.string()).optional(),
26
28
  /** Skills to include (if omitted, all skills are included for backward compat) */
27
29
  skills: z.array(z.string()).optional(),
30
+ /** AI client setup target: Claude Code, Codex, or both */
31
+ setupTarget: z.enum(SETUP_TARGETS).optional().default('claude'),
32
+ /** Enable Telegram transport scaffolding. Default: false. */
33
+ telegram: z.boolean().optional().default(false),
28
34
  });
29
35
  //# sourceMappingURL=types.js.map
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,+CAA+C;AAC/C,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,SAAS,EAAE,QAAQ,EAAE,WAAW,CAAU,CAAC;AAGjE,0DAA0D;AAC1D,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;IACxC,yEAAyE;IACzE,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,mBAAmB,EAAE,gDAAgD,CAAC;IAC3F,kCAAkC;IAClC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;IAC/B,gCAAgC;IAChC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;IAChC,iDAAiD;IACjD,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;IACxC,0CAA0C;IAC1C,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;IAClD,0DAA0D;IAC1D,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;IAC9C,wDAAwD;IACxD,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,WAAW,CAAC;IACnD,gFAAgF;IAChF,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;IAChD,oFAAoF;IACpF,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;IAC9D,yDAAyD;IACzD,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;IACzC,kFAAkF;IAClF,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;CACvC,CAAC,CAAC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,+CAA+C;AAC/C,MAAM,KAAK,GAAG,CAAC,SAAS,EAAE,QAAQ,EAAE,WAAW,CAAU,CAAC;AAE1D,uDAAuD;AACvD,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAU,CAAC;AAGlE,0DAA0D;AAC1D,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;IACxC,yEAAyE;IACzE,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,mBAAmB,EAAE,gDAAgD,CAAC;IAC3F,kCAAkC;IAClC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;IAC/B,gCAAgC;IAChC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;IAChC,iDAAiD;IACjD,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;IACxC,0CAA0C;IAC1C,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;IAClD,0DAA0D;IAC1D,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;IAC9C,wDAAwD;IACxD,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,WAAW,CAAC;IACnD,gFAAgF;IAChF,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;IAChD,oFAAoF;IACpF,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;IAC9D,yDAAyD;IACzD,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;IACzC,kFAAkF;IAClF,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;IACtC,0DAA0D;IAC1D,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC;IAC/D,6DAA6D;IAC7D,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;CAChD,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soleri/forge",
3
- "version": "5.11.0",
3
+ "version": "5.12.0",
4
4
  "description": "Scaffold AI agents that learn, remember, and grow with you.",
5
5
  "keywords": [
6
6
  "agent",
@@ -379,4 +379,66 @@ describe('Scaffolder', () => {
379
379
  expect(agents).toEqual([]);
380
380
  });
381
381
  });
382
+
383
+ describe('telegram scaffolding', () => {
384
+ const telegramConfig: AgentConfig = {
385
+ ...testConfig,
386
+ telegram: true,
387
+ };
388
+
389
+ it('should include telegram files in preview', () => {
390
+ telegramConfig.outputDir = tempDir;
391
+ const preview = previewScaffold(telegramConfig);
392
+ const paths = preview.files.map((f) => f.path);
393
+ expect(paths).toContain('src/telegram-bot.ts');
394
+ expect(paths).toContain('src/telegram-config.ts');
395
+ expect(paths).toContain('src/telegram-agent.ts');
396
+ expect(paths).toContain('src/telegram-supervisor.ts');
397
+ });
398
+
399
+ it('should not include telegram files without flag', () => {
400
+ const preview = previewScaffold(testConfig);
401
+ const paths = preview.files.map((f) => f.path);
402
+ expect(paths).not.toContain('src/telegram-bot.ts');
403
+ });
404
+
405
+ it('should generate telegram source files', () => {
406
+ telegramConfig.outputDir = tempDir;
407
+ const result = scaffold(telegramConfig);
408
+ // Build may fail (grammy not installed) but files should be created
409
+ expect(result.filesCreated).toContain('src/telegram-bot.ts');
410
+ expect(result.filesCreated).toContain('src/telegram-config.ts');
411
+ expect(result.filesCreated).toContain('src/telegram-agent.ts');
412
+ expect(result.filesCreated).toContain('src/telegram-supervisor.ts');
413
+
414
+ // Verify file contents reference the agent
415
+ const botContent = readFileSync(join(tempDir, 'atlas', 'src', 'telegram-bot.ts'), 'utf-8');
416
+ expect(botContent).toContain('Atlas');
417
+ expect(botContent).toContain('grammy');
418
+
419
+ const configContent = readFileSync(
420
+ join(tempDir, 'atlas', 'src', 'telegram-config.ts'),
421
+ 'utf-8',
422
+ );
423
+ expect(configContent).toContain('.atlas');
424
+ expect(configContent).toContain('TELEGRAM_BOT_TOKEN');
425
+ });
426
+
427
+ it('should include grammy dependency in package.json', () => {
428
+ telegramConfig.outputDir = tempDir;
429
+ scaffold(telegramConfig);
430
+ const pkg = JSON.parse(readFileSync(join(tempDir, 'atlas', 'package.json'), 'utf-8'));
431
+ expect(pkg.dependencies.grammy).toBeDefined();
432
+ expect(pkg.scripts['telegram:start']).toBeDefined();
433
+ expect(pkg.scripts['telegram:dev']).toBeDefined();
434
+ });
435
+
436
+ it('should not include grammy without telegram flag', () => {
437
+ const result = scaffold(testConfig);
438
+ if (result.success) {
439
+ const pkg = JSON.parse(readFileSync(join(tempDir, 'atlas', 'package.json'), 'utf-8'));
440
+ expect(pkg.dependencies.grammy).toBeUndefined();
441
+ }
442
+ });
443
+ });
382
444
  });
@@ -112,15 +112,15 @@ export const forgeOps: OpDef[] = [
112
112
  {
113
113
  step: 6,
114
114
  action: 'Create the agent',
115
- ask: 'Call create with the confirmed config. The agent is auto-built (npm install + build) and MCP server is registered in ~/.claude.json automatically.',
115
+ ask: 'Call create with the confirmed config. The agent is auto-built (npm install + build) and MCP server registration is applied automatically for the selected setup target (Claude, Codex, or both).',
116
116
  },
117
117
  {
118
118
  step: 7,
119
119
  action: 'Next steps',
120
120
  suggest: [
121
- 'Restart Claude Code so the new MCP server is picked up',
121
+ 'Restart your selected host client(s) so the new MCP server is picked up',
122
122
  'Say "Hello, {AgentName}!" to activate the persona in any session',
123
- 'The agent will check setup status and offer to inject its CLAUDE.md sections',
123
+ 'For Claude setup, the agent can check setup status and inject CLAUDE.md sections',
124
124
  'Add initial knowledge by creating entries in the intelligence data JSON files',
125
125
  'Or use the agent and capture knowledge via the capture ops as you work',
126
126
  ],
package/src/lib.ts CHANGED
@@ -10,11 +10,12 @@ export { addDomain } from './domain-manager.js';
10
10
  export { patchIndexTs, patchClaudeMdContent } from './patching.js';
11
11
  export type {
12
12
  AgentConfig,
13
+ SetupTarget,
13
14
  ScaffoldResult,
14
15
  ScaffoldPreview,
15
16
  AgentInfo,
16
17
  InstallKnowledgeResult,
17
18
  AddDomainResult,
18
19
  } from './types.js';
19
- export { AgentConfigSchema } from './types.js';
20
+ export { AgentConfigSchema, SETUP_TARGETS } from './types.js';
20
21
  export { generateExtensionsIndex, generateExampleOp } from './templates/extensions.js';