@orchagent/cli 0.3.63 → 0.3.65

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.
@@ -34,6 +34,7 @@ const service_1 = require("./service");
34
34
  const transfer_1 = require("./transfer");
35
35
  const pull_1 = require("./pull");
36
36
  const logs_1 = require("./logs");
37
+ const secrets_1 = require("./secrets");
37
38
  function registerCommands(program) {
38
39
  (0, login_1.registerLoginCommand)(program);
39
40
  (0, whoami_1.registerWhoamiCommand)(program);
@@ -68,4 +69,5 @@ function registerCommands(program) {
68
69
  (0, transfer_1.registerTransferCommand)(program);
69
70
  (0, pull_1.registerPullCommand)(program);
70
71
  (0, logs_1.registerLogsCommand)(program);
72
+ (0, secrets_1.registerSecretsCommand)(program);
71
73
  }
@@ -7,6 +7,7 @@ exports.registerInitCommand = registerInitCommand;
7
7
  const promises_1 = __importDefault(require("fs/promises"));
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const errors_1 = require("../lib/errors");
10
+ const github_weekly_summary_1 = require("./templates/github-weekly-summary");
10
11
  const MANIFEST_TEMPLATE = `{
11
12
  "name": "my-agent",
12
13
  "description": "A simple AI agent",
@@ -86,15 +87,73 @@ if __name__ == "__main__":
86
87
  main()
87
88
  `;
88
89
  function readmeTemplate(agentName, flavor) {
89
- const inputField = flavor === 'managed_loop' ? 'task' : 'input';
90
- const inputDescription = flavor === 'managed_loop' ? 'The task to perform' : 'The input to process';
90
+ if (flavor === 'discord') {
91
+ return `# ${agentName}
92
+
93
+ An always-on Discord bot powered by Claude.
94
+
95
+ ## Setup
96
+
97
+ ### 1. Create a Discord bot
98
+
99
+ 1. Go to the [Discord Developer Portal](https://discord.com/developers/applications)
100
+ 2. Create a new application, then go to **Bot** and copy the bot token
101
+ 3. Under **Privileged Gateway Intents**, enable **Message Content Intent**
102
+ 4. Go to **OAuth2 > URL Generator**, select \`bot\` scope, then invite to your server
103
+
104
+ ### 2. Get channel IDs
105
+
106
+ Enable Developer Mode in Discord (Settings > Advanced), then right-click a channel and copy its ID.
107
+
108
+ ### 3. Local development
109
+
110
+ \`\`\`sh
111
+ cp .env.example .env
112
+ # Fill in DISCORD_BOT_TOKEN, ANTHROPIC_API_KEY, DISCORD_CHANNEL_IDS
113
+
114
+ pip install -r requirements.txt
115
+ python main.py
116
+ \`\`\`
117
+
118
+ ### 4. Deploy
119
+
120
+ \`\`\`sh
121
+ orch publish
122
+
123
+ # Add secrets in your workspace (web dashboard > Settings > Secrets):
124
+ # DISCORD_BOT_TOKEN — your bot token
125
+ # DISCORD_CHANNEL_IDS — comma-separated channel IDs
126
+
127
+ orch service deploy
128
+ \`\`\`
129
+
130
+ ## Customization
131
+
132
+ Edit \`main.py\` to customize:
133
+
134
+ - **SYSTEM_PROMPT** — controls how the bot responds
135
+ - **MODEL** / **MAX_TOKENS** — override via env vars
136
+
137
+ ## Environment Variables
138
+
139
+ | Variable | Required | Description |
140
+ |----------|----------|-------------|
141
+ | \`DISCORD_BOT_TOKEN\` | Yes | Discord bot token (workspace secret) |
142
+ | \`ANTHROPIC_API_KEY\` | Auto | Injected by orchagent via \`supported_providers\` |
143
+ | \`DISCORD_CHANNEL_IDS\` | Yes | Comma-separated channel IDs (workspace secret) |
144
+ | \`MODEL\` | No | Claude model (default: claude-sonnet-4-5-20250929) |
145
+ | \`MAX_TOKENS\` | No | Max response tokens (default: 1024) |
146
+ `;
147
+ }
148
+ const inputField = flavor === 'managed_loop' || flavor === 'orchestrator' ? 'task' : 'input';
149
+ const inputDescription = flavor === 'managed_loop' || flavor === 'orchestrator' ? 'The task to perform' : 'The input to process';
91
150
  const cloudExample = flavor === 'code_runtime'
92
151
  ? `orchagent run ${agentName} --data '{"input": "Hello world"}'`
93
152
  : `orchagent run ${agentName} --data '{"${inputField}": "Hello world"}'`;
94
153
  const localExample = flavor === 'code_runtime'
95
154
  ? `orchagent run ${agentName} --local --data '{"input": "Hello world"}'`
96
155
  : `orchagent run ${agentName} --local --data '{"${inputField}": "Hello world"}'`;
97
- return `# ${agentName}
156
+ let readme = `# ${agentName}
98
157
 
99
158
  A brief description of what this agent does.
100
159
 
@@ -124,6 +183,20 @@ ${localExample}
124
183
  |-------|------|-------------|
125
184
  | \`result\` | string | The agent's response |
126
185
  `;
186
+ if (flavor === 'orchestrator') {
187
+ readme += `
188
+ ## Dependencies
189
+
190
+ This orchestrator calls other agents. Update \`manifest.dependencies\` in \`orchagent.json\` with your actual dependencies.
191
+
192
+ **Publish order:** Publish dependency agents first, then this orchestrator.
193
+
194
+ | Dependency | Version | Description |
195
+ |------------|---------|-------------|
196
+ | \`org/agent-name\` | v1 | TODO: describe what this agent does |
197
+ `;
198
+ }
199
+ return readme;
127
200
  }
128
201
  const AGENT_PROMPT_TEMPLATE = `You are a helpful AI agent.
129
202
 
@@ -157,6 +230,221 @@ const AGENT_SCHEMA_TEMPLATE = `{
157
230
  }
158
231
  }
159
232
  `;
233
+ const ORCHESTRATOR_MAIN_PY = `"""
234
+ orchagent orchestrator entrypoint.
235
+
236
+ Reads JSON input from stdin, calls dependency agents via the orchagent SDK,
237
+ and writes JSON output to stdout.
238
+
239
+ Usage:
240
+ echo '{"task": "do something"}' | python main.py
241
+ """
242
+
243
+ import asyncio
244
+ import json
245
+ import sys
246
+
247
+ from orchagent import AgentClient
248
+
249
+
250
+ def main():
251
+ # Read JSON input from stdin
252
+ raw = sys.stdin.read()
253
+ try:
254
+ data = json.loads(raw) if raw.strip() else {}
255
+ except json.JSONDecodeError:
256
+ print(json.dumps({"error": "Invalid JSON input"}))
257
+ sys.exit(1)
258
+
259
+ task = data.get("task", "")
260
+
261
+ # --- Your orchestration logic here ---
262
+ # The AgentClient reads ORCHAGENT_SERVICE_KEY from the environment automatically.
263
+ # Do NOT add ORCHAGENT_SERVICE_KEY to required_secrets — the gateway injects it.
264
+ client = AgentClient()
265
+
266
+ # Call a dependency agent (must be listed in manifest.dependencies)
267
+ result = asyncio.run(
268
+ client.call("org/agent-name@v1", {"input": task})
269
+ )
270
+
271
+ # You can chain multiple calls, run them in parallel, or add conditional logic:
272
+ #
273
+ # Sequential:
274
+ # result2 = asyncio.run(client.call("org/another-agent@v1", {"input": result}))
275
+ #
276
+ # Parallel:
277
+ # r1, r2 = asyncio.run(asyncio.gather(
278
+ # client.call("org/agent-a@v1", {"input": task}),
279
+ # client.call("org/agent-b@v1", {"input": task}),
280
+ # ))
281
+ # --- End orchestration logic ---
282
+
283
+ # Write JSON output to stdout
284
+ print(json.dumps({"result": result, "success": True}))
285
+
286
+
287
+ if __name__ == "__main__":
288
+ main()
289
+ `;
290
+ const ORCHESTRATOR_REQUIREMENTS = `orchagent-sdk>=0.1.0
291
+ `;
292
+ const DISCORD_MAIN_PY = `"""
293
+ Discord bot agent — powered by Claude.
294
+
295
+ Listens for messages in configured channels and responds using the Anthropic API.
296
+
297
+ Local development:
298
+ 1. Copy .env.example to .env and fill in your tokens
299
+ 2. pip install -r requirements.txt
300
+ 3. python main.py
301
+ """
302
+
303
+ import asyncio
304
+ import logging
305
+ import os
306
+ import sys
307
+
308
+ import anthropic
309
+ import discord
310
+
311
+ # ---------------------------------------------------------------------------
312
+ # Configuration
313
+ # ---------------------------------------------------------------------------
314
+
315
+ DISCORD_BOT_TOKEN = os.environ.get("DISCORD_BOT_TOKEN", "")
316
+ ANTHROPIC_API_KEY = os.environ.get("ANTHROPIC_API_KEY", "")
317
+ DISCORD_CHANNEL_IDS = os.environ.get("DISCORD_CHANNEL_IDS", "")
318
+
319
+ MODEL = os.environ.get("MODEL", "claude-sonnet-4-5-20250929")
320
+ MAX_TOKENS = int(os.environ.get("MAX_TOKENS", "1024"))
321
+
322
+ logging.basicConfig(
323
+ level=logging.INFO,
324
+ format="%(asctime)s %(levelname)s %(name)s: %(message)s",
325
+ stream=sys.stdout,
326
+ )
327
+ logger = logging.getLogger("discord-bot")
328
+
329
+ SYSTEM_PROMPT = """\\
330
+ You are a helpful assistant in a Discord server.
331
+
332
+ Be concise and friendly. Use code blocks for code examples.
333
+ Keep responses under 1800 characters (Discord limit is 2000)."""
334
+
335
+
336
+ # ---------------------------------------------------------------------------
337
+ # Anthropic API
338
+ # ---------------------------------------------------------------------------
339
+
340
+
341
+ def ask_claude(client: anthropic.Anthropic, user_message: str) -> str:
342
+ """Send a message to Claude and return the response."""
343
+ response = client.messages.create(
344
+ model=MODEL,
345
+ max_tokens=MAX_TOKENS,
346
+ system=SYSTEM_PROMPT,
347
+ messages=[{"role": "user", "content": user_message}],
348
+ )
349
+ return response.content[0].text
350
+
351
+
352
+ # ---------------------------------------------------------------------------
353
+ # Discord bot
354
+ # ---------------------------------------------------------------------------
355
+
356
+
357
+ def parse_channel_ids(raw: str) -> set[int]:
358
+ """Parse comma-separated channel IDs from env var."""
359
+ return {int(x.strip()) for x in raw.split(",") if x.strip().isdigit()}
360
+
361
+
362
+ class Bot(discord.Client):
363
+ def __init__(self, anthropic_client: anthropic.Anthropic, allowed_channels: set[int]):
364
+ intents = discord.Intents.default()
365
+ intents.message_content = True
366
+ super().__init__(intents=intents)
367
+ self.anthropic_client = anthropic_client
368
+ self.allowed_channels = allowed_channels
369
+
370
+ async def on_ready(self):
371
+ logger.info("Bot connected as %s", self.user)
372
+
373
+ async def on_message(self, message: discord.Message):
374
+ if message.author.bot or not message.content.strip():
375
+ return
376
+
377
+ # Only respond in allowed channels (or threads within them)
378
+ channel_id = message.channel.id
379
+ parent_id = getattr(message.channel, "parent_id", None)
380
+ if channel_id not in self.allowed_channels and parent_id not in self.allowed_channels:
381
+ return
382
+
383
+ logger.info("Message from %s: %.100s", message.author, message.content)
384
+
385
+ async with message.channel.typing():
386
+ try:
387
+ answer = await asyncio.to_thread(
388
+ ask_claude, self.anthropic_client, message.content
389
+ )
390
+ except anthropic.APIError as exc:
391
+ logger.error("Anthropic API error: %s", exc)
392
+ await message.reply("Sorry, I ran into an issue. Please try again.")
393
+ return
394
+
395
+ if len(answer) > 1900:
396
+ answer = answer[:1897] + "..."
397
+
398
+ await message.reply(answer)
399
+
400
+
401
+ # ---------------------------------------------------------------------------
402
+ # Entry point
403
+ # ---------------------------------------------------------------------------
404
+
405
+
406
+ def main():
407
+ if not DISCORD_BOT_TOKEN:
408
+ logger.error("DISCORD_BOT_TOKEN not set")
409
+ sys.exit(1)
410
+ if not ANTHROPIC_API_KEY:
411
+ logger.error("ANTHROPIC_API_KEY not set")
412
+ sys.exit(1)
413
+ if not DISCORD_CHANNEL_IDS:
414
+ logger.error("DISCORD_CHANNEL_IDS not set — add comma-separated channel IDs")
415
+ sys.exit(1)
416
+
417
+ allowed = parse_channel_ids(DISCORD_CHANNEL_IDS)
418
+ if not allowed:
419
+ logger.error("No valid channel IDs in DISCORD_CHANNEL_IDS=%r", DISCORD_CHANNEL_IDS)
420
+ sys.exit(1)
421
+
422
+ logger.info("Starting bot — model: %s, channels: %s", MODEL, allowed)
423
+
424
+ client = anthropic.Anthropic(api_key=ANTHROPIC_API_KEY)
425
+ bot = Bot(client, allowed)
426
+ bot.run(DISCORD_BOT_TOKEN, log_handler=None)
427
+
428
+
429
+ if __name__ == "__main__":
430
+ main()
431
+ `;
432
+ const DISCORD_REQUIREMENTS = `discord.py>=2.3.0,<3.0.0
433
+ anthropic>=0.40.0,<1.0.0
434
+ `;
435
+ const DISCORD_ENV_EXAMPLE = `# Required — get your bot token from https://discord.com/developers/applications
436
+ DISCORD_BOT_TOKEN=
437
+
438
+ # Required for local dev — auto-injected in production via supported_providers
439
+ ANTHROPIC_API_KEY=
440
+
441
+ # Required — comma-separated Discord channel IDs where the bot should respond
442
+ DISCORD_CHANNEL_IDS=
443
+
444
+ # Optional — customize the model and response length
445
+ # MODEL=claude-sonnet-4-5-20250929
446
+ # MAX_TOKENS=1024
447
+ `;
160
448
  const SKILL_TEMPLATE = `---
161
449
  name: my-skill
162
450
  description: When to use this skill
@@ -189,14 +477,42 @@ function registerInitCommand(program) {
189
477
  .description('Initialize a new agent project')
190
478
  .argument('[name]', 'Agent name (default: current directory name)')
191
479
  .option('--type <type>', 'Type: prompt, tool, agent, or skill (legacy aliases: agentic, code)', 'prompt')
480
+ .option('--orchestrator', 'Create an orchestrator agent with dependency scaffolding and SDK boilerplate')
192
481
  .option('--run-mode <mode>', 'Run mode for agents: on_demand or always_on', 'on_demand')
482
+ .option('--template <name>', 'Start from a template (available: github-weekly-summary, discord)')
193
483
  .action(async (name, options) => {
194
484
  const cwd = process.cwd();
195
- const runMode = (options.runMode || 'on_demand').trim().toLowerCase();
485
+ let runMode = (options.runMode || 'on_demand').trim().toLowerCase();
196
486
  if (!['on_demand', 'always_on'].includes(runMode)) {
197
487
  throw new errors_1.CliError("Invalid --run-mode. Use 'on_demand' or 'always_on'.");
198
488
  }
199
- const initMode = resolveInitFlavor(options.type);
489
+ let initMode = resolveInitFlavor(options.type);
490
+ if (options.orchestrator) {
491
+ if (initMode.type === 'skill') {
492
+ throw new errors_1.CliError('Cannot use --orchestrator with --type skill. Orchestrators are agent-type agents that call other agents.');
493
+ }
494
+ initMode = { type: 'agent', flavor: 'orchestrator' };
495
+ }
496
+ if (options.template) {
497
+ const template = options.template.trim().toLowerCase();
498
+ const validTemplates = ['discord', 'github-weekly-summary'];
499
+ if (!validTemplates.includes(template)) {
500
+ throw new errors_1.CliError(`Unknown --template '${template}'. Available templates: ${validTemplates.join(', ')}`);
501
+ }
502
+ if (options.orchestrator) {
503
+ throw new errors_1.CliError('Cannot use --template with --orchestrator.');
504
+ }
505
+ if (initMode.type === 'skill') {
506
+ throw new errors_1.CliError('Cannot use --template with --type skill.');
507
+ }
508
+ if (template === 'discord') {
509
+ initMode = { type: 'agent', flavor: 'discord' };
510
+ runMode = 'always_on';
511
+ }
512
+ else if (template === 'github-weekly-summary') {
513
+ initMode = { type: 'agent', flavor: 'github_weekly_summary' };
514
+ }
515
+ }
200
516
  // When a name is provided, create a subdirectory for the project
201
517
  const targetDir = name ? path_1.default.join(cwd, name) : cwd;
202
518
  const agentName = name || path_1.default.basename(cwd);
@@ -234,6 +550,61 @@ function registerInitCommand(program) {
234
550
  }
235
551
  return;
236
552
  }
553
+ // Handle github-weekly-summary template separately (own file set + output)
554
+ if (initMode.flavor === 'github_weekly_summary') {
555
+ const manifestPath = path_1.default.join(targetDir, 'orchagent.json');
556
+ // Check if already initialized
557
+ try {
558
+ await promises_1.default.access(manifestPath);
559
+ throw new errors_1.CliError(`Already initialized (orchagent.json exists in ${name ? name + '/' : 'current directory'})`);
560
+ }
561
+ catch (err) {
562
+ if (err.code !== 'ENOENT') {
563
+ throw err;
564
+ }
565
+ }
566
+ const sub = (s) => s.replace(/\{\{name\}\}/g, agentName);
567
+ // Create prompts/ subdirectory
568
+ await promises_1.default.mkdir(path_1.default.join(targetDir, 'prompts'), { recursive: true });
569
+ // Write all files
570
+ await promises_1.default.writeFile(manifestPath, sub(github_weekly_summary_1.TEMPLATE_MANIFEST));
571
+ await promises_1.default.writeFile(path_1.default.join(targetDir, 'main.py'), github_weekly_summary_1.TEMPLATE_MAIN_PY);
572
+ await promises_1.default.writeFile(path_1.default.join(targetDir, 'config.py'), github_weekly_summary_1.TEMPLATE_CONFIG_PY);
573
+ await promises_1.default.writeFile(path_1.default.join(targetDir, 'github_fetcher.py'), github_weekly_summary_1.TEMPLATE_GITHUB_FETCHER_PY);
574
+ await promises_1.default.writeFile(path_1.default.join(targetDir, 'activity_store.py'), github_weekly_summary_1.TEMPLATE_ACTIVITY_STORE_PY);
575
+ await promises_1.default.writeFile(path_1.default.join(targetDir, 'analyst.py'), github_weekly_summary_1.TEMPLATE_ANALYST_PY);
576
+ await promises_1.default.writeFile(path_1.default.join(targetDir, 'models.py'), github_weekly_summary_1.TEMPLATE_MODELS_PY);
577
+ await promises_1.default.writeFile(path_1.default.join(targetDir, 'requirements.txt'), github_weekly_summary_1.TEMPLATE_REQUIREMENTS_TXT);
578
+ await promises_1.default.writeFile(path_1.default.join(targetDir, 'prompts', 'weekly_summary.md'), github_weekly_summary_1.TEMPLATE_WEEKLY_SUMMARY_PROMPT);
579
+ await promises_1.default.writeFile(path_1.default.join(targetDir, '.env.example'), sub(github_weekly_summary_1.TEMPLATE_ENV_EXAMPLE));
580
+ await promises_1.default.writeFile(path_1.default.join(targetDir, 'README.md'), sub(github_weekly_summary_1.TEMPLATE_README));
581
+ const prefix = name ? name + '/' : '';
582
+ process.stdout.write(`\nInitialized github-weekly-summary agent "${agentName}" in ${targetDir}\n`);
583
+ process.stdout.write(`\nFiles created:\n`);
584
+ process.stdout.write(` ${prefix}orchagent.json Agent manifest\n`);
585
+ process.stdout.write(` ${prefix}main.py Entrypoint\n`);
586
+ process.stdout.write(` ${prefix}config.py Config loader\n`);
587
+ process.stdout.write(` ${prefix}github_fetcher.py GitHub API client\n`);
588
+ process.stdout.write(` ${prefix}activity_store.py Stats computation\n`);
589
+ process.stdout.write(` ${prefix}analyst.py LLM summary generator\n`);
590
+ process.stdout.write(` ${prefix}models.py Data models\n`);
591
+ process.stdout.write(` ${prefix}requirements.txt Python dependencies\n`);
592
+ process.stdout.write(` ${prefix}prompts/weekly_summary.md LLM prompt template\n`);
593
+ process.stdout.write(` ${prefix}.env.example Secret reference\n`);
594
+ process.stdout.write(` ${prefix}README.md Setup guide\n`);
595
+ process.stdout.write(`\nNext steps:\n`);
596
+ const s = name ? 2 : 1;
597
+ if (name) {
598
+ process.stdout.write(` 1. cd ${name}\n`);
599
+ }
600
+ process.stdout.write(` ${s}. orch github connect Connect your GitHub account\n`);
601
+ process.stdout.write(` ${s + 1}. orch publish Publish the agent\n`);
602
+ process.stdout.write(` ${s + 2}. Add secrets in web dashboard ORCHAGENT_API_KEY, DISCORD_WEBHOOK_URL, ANTHROPIC_API_KEY, GITHUB_REPOS\n`);
603
+ process.stdout.write(` ${s + 3}. orch run <org>/${agentName} --cloud Test it\n`);
604
+ process.stdout.write(` ${s + 4}. orch schedule create <org>/${agentName} --cron "0 9 * * 1" Schedule weekly\n`);
605
+ process.stdout.write(`\n See README.md for full setup guide.\n`);
606
+ return;
607
+ }
237
608
  const manifestPath = path_1.default.join(targetDir, 'orchagent.json');
238
609
  const promptPath = path_1.default.join(targetDir, 'prompt.md');
239
610
  const schemaPath = path_1.default.join(targetDir, 'schema.json');
@@ -247,7 +618,7 @@ function registerInitCommand(program) {
247
618
  throw err;
248
619
  }
249
620
  }
250
- if (initMode.flavor !== 'code_runtime' && runMode === 'always_on') {
621
+ if (initMode.flavor !== 'code_runtime' && initMode.flavor !== 'orchestrator' && initMode.flavor !== 'discord' && runMode === 'always_on') {
251
622
  throw new errors_1.CliError("run_mode=always_on requires runtime.command in orchagent.json (e.g. \"runtime\": { \"command\": \"python main.py\" }). Use --type tool for code-runtime agents.");
252
623
  }
253
624
  // Create manifest and type-specific files
@@ -255,19 +626,53 @@ function registerInitCommand(program) {
255
626
  manifest.name = agentName;
256
627
  manifest.type = initMode.type;
257
628
  manifest.run_mode = runMode;
258
- if (initMode.flavor === 'managed_loop') {
629
+ if (initMode.flavor === 'orchestrator') {
630
+ manifest.description = 'An orchestrator agent that coordinates other agents';
631
+ manifest.runtime = { command: 'python main.py' };
632
+ manifest.manifest = {
633
+ manifest_version: 1,
634
+ dependencies: [{ id: 'org/agent-name', version: 'v1' }],
635
+ max_hops: 3,
636
+ timeout_ms: 120000,
637
+ per_call_downstream_cap: 50,
638
+ };
639
+ manifest.required_secrets = [];
640
+ }
641
+ else if (initMode.flavor === 'managed_loop') {
259
642
  manifest.description = 'An AI agent with tool use';
260
643
  manifest.supported_providers = ['anthropic'];
261
644
  manifest.loop = { max_turns: 25 };
262
645
  manifest.required_secrets = [];
263
646
  }
647
+ else if (initMode.flavor === 'discord') {
648
+ manifest.description = 'An always-on Discord bot powered by Claude';
649
+ manifest.runtime = { command: 'python main.py' };
650
+ manifest.supported_providers = ['anthropic'];
651
+ manifest.required_secrets = ['DISCORD_BOT_TOKEN', 'DISCORD_CHANNEL_IDS'];
652
+ manifest.tags = ['discord', 'always-on'];
653
+ }
264
654
  else if (initMode.flavor === 'code_runtime') {
265
655
  manifest.description = 'A code-runtime agent';
266
656
  manifest.runtime = { command: 'python main.py' };
267
657
  manifest.required_secrets = [];
268
658
  }
269
659
  await promises_1.default.writeFile(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
270
- if (initMode.flavor === 'code_runtime') {
660
+ if (initMode.flavor === 'orchestrator') {
661
+ const entrypointPath = path_1.default.join(targetDir, 'main.py');
662
+ const requirementsPath = path_1.default.join(targetDir, 'requirements.txt');
663
+ await promises_1.default.writeFile(entrypointPath, ORCHESTRATOR_MAIN_PY);
664
+ await promises_1.default.writeFile(requirementsPath, ORCHESTRATOR_REQUIREMENTS);
665
+ await promises_1.default.writeFile(schemaPath, AGENT_SCHEMA_TEMPLATE);
666
+ }
667
+ else if (initMode.flavor === 'discord') {
668
+ const entrypointPath = path_1.default.join(targetDir, 'main.py');
669
+ const requirementsPath = path_1.default.join(targetDir, 'requirements.txt');
670
+ const envExamplePath = path_1.default.join(targetDir, '.env.example');
671
+ await promises_1.default.writeFile(entrypointPath, DISCORD_MAIN_PY);
672
+ await promises_1.default.writeFile(requirementsPath, DISCORD_REQUIREMENTS);
673
+ await promises_1.default.writeFile(envExamplePath, DISCORD_ENV_EXAMPLE);
674
+ }
675
+ else if (initMode.flavor === 'code_runtime') {
271
676
  const entrypointPath = path_1.default.join(targetDir, 'main.py');
272
677
  await promises_1.default.writeFile(entrypointPath, CODE_TEMPLATE_PY);
273
678
  await promises_1.default.writeFile(schemaPath, SCHEMA_TEMPLATE);
@@ -286,19 +691,50 @@ function registerInitCommand(program) {
286
691
  process.stdout.write(`Initialized agent "${agentName}" in ${targetDir}\n`);
287
692
  process.stdout.write(`\nFiles created:\n`);
288
693
  const prefix = name ? name + '/' : '';
289
- process.stdout.write(` ${prefix}orchagent.json - Agent configuration\n`);
290
- if (initMode.flavor === 'code_runtime') {
291
- process.stdout.write(` ${prefix}main.py - Agent entrypoint (stdin/stdout JSON)\n`);
694
+ process.stdout.write(` ${prefix}orchagent.json - Agent configuration\n`);
695
+ if (initMode.flavor === 'orchestrator') {
696
+ process.stdout.write(` ${prefix}main.py - Orchestrator entrypoint (SDK calls)\n`);
697
+ process.stdout.write(` ${prefix}requirements.txt - Python dependencies (orchagent-sdk)\n`);
698
+ }
699
+ else if (initMode.flavor === 'discord') {
700
+ process.stdout.write(` ${prefix}main.py - Discord bot (discord.py + Anthropic)\n`);
701
+ process.stdout.write(` ${prefix}requirements.txt - Python dependencies\n`);
702
+ process.stdout.write(` ${prefix}.env.example - Environment variables template\n`);
703
+ }
704
+ else if (initMode.flavor === 'code_runtime') {
705
+ process.stdout.write(` ${prefix}main.py - Agent entrypoint (stdin/stdout JSON)\n`);
292
706
  }
293
707
  else {
294
- process.stdout.write(` ${prefix}prompt.md - Prompt template\n`);
708
+ process.stdout.write(` ${prefix}prompt.md - Prompt template\n`);
295
709
  }
296
- process.stdout.write(` ${prefix}schema.json - Input/output schemas\n`);
297
- process.stdout.write(` ${prefix}README.md - Agent documentation\n`);
710
+ if (initMode.flavor !== 'discord') {
711
+ process.stdout.write(` ${prefix}schema.json - Input/output schemas\n`);
712
+ }
713
+ process.stdout.write(` ${prefix}README.md - Agent documentation\n`);
298
714
  process.stdout.write(` Run mode: ${runMode}\n`);
299
- process.stdout.write(` Execution: ${initMode.flavor}\n`);
715
+ process.stdout.write(` Execution: ${initMode.flavor === 'orchestrator' ? 'code_runtime (orchestrator)' : initMode.flavor === 'discord' ? 'code_runtime (discord)' : initMode.flavor}\n`);
300
716
  process.stdout.write(`\nNext steps:\n`);
301
- if (initMode.flavor === 'code_runtime') {
717
+ if (initMode.flavor === 'orchestrator') {
718
+ const stepNum = name ? 2 : 1;
719
+ if (name) {
720
+ process.stdout.write(` 1. cd ${name}\n`);
721
+ }
722
+ process.stdout.write(` ${stepNum}. Update manifest.dependencies in orchagent.json with your actual agents\n`);
723
+ process.stdout.write(` ${stepNum + 1}. Edit main.py with your orchestration logic\n`);
724
+ process.stdout.write(` ${stepNum + 2}. Publish dependency agents first, then: orchagent publish\n`);
725
+ }
726
+ else if (initMode.flavor === 'discord') {
727
+ const stepNum = name ? 2 : 1;
728
+ if (name) {
729
+ process.stdout.write(` 1. cd ${name}\n`);
730
+ }
731
+ process.stdout.write(` ${stepNum}. Create a Discord bot at https://discord.com/developers/applications\n`);
732
+ process.stdout.write(` ${stepNum + 1}. Enable Message Content Intent in bot settings\n`);
733
+ process.stdout.write(` ${stepNum + 2}. Copy .env.example to .env and fill in your tokens\n`);
734
+ process.stdout.write(` ${stepNum + 3}. Test locally: pip install -r requirements.txt && python main.py\n`);
735
+ process.stdout.write(` ${stepNum + 4}. Deploy: orch publish\n`);
736
+ }
737
+ else if (initMode.flavor === 'code_runtime') {
302
738
  const stepNum = name ? 2 : 1;
303
739
  if (name) {
304
740
  process.stdout.write(` 1. cd ${name}\n`);