@orchagent/cli 0.3.70 → 0.3.72

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.
@@ -87,6 +87,185 @@ def main():
87
87
  if __name__ == "__main__":
88
88
  main()
89
89
  `;
90
+ const CODE_TEMPLATE_JS = `/**
91
+ * orchagent tool entrypoint.
92
+ *
93
+ * Reads JSON input from stdin, processes it, and writes JSON output to stdout.
94
+ * This is the standard orchagent tool protocol.
95
+ *
96
+ * Usage:
97
+ * echo '{"input": "hello"}' | node main.js
98
+ */
99
+
100
+ const fs = require('fs');
101
+
102
+ function main() {
103
+ const raw = fs.readFileSync('/dev/stdin', 'utf-8');
104
+ let data;
105
+ try {
106
+ data = raw.trim() ? JSON.parse(raw) : {};
107
+ } catch {
108
+ console.log(JSON.stringify({ error: 'Invalid JSON input' }));
109
+ process.exit(1);
110
+ }
111
+
112
+ const input = data.input || '';
113
+
114
+ // --- Your logic here ---
115
+ // To use workspace secrets, add them to "required_secrets" in orchagent.json:
116
+ // "required_secrets": ["MY_API_KEY"]
117
+ // Then access via: process.env.MY_API_KEY
118
+ const result = \`Received: \${input}\`;
119
+ // --- End your logic ---
120
+
121
+ console.log(JSON.stringify({ result }));
122
+ }
123
+
124
+ main();
125
+ `;
126
+ const DISCORD_MAIN_JS = `/**
127
+ * Discord bot agent — powered by Claude.
128
+ *
129
+ * Listens for messages in configured channels and responds using the Anthropic API.
130
+ *
131
+ * Local development:
132
+ * 1. Copy .env.example to .env and fill in your tokens
133
+ * 2. npm install
134
+ * 3. node main.js
135
+ */
136
+
137
+ const { Client, GatewayIntentBits } = require('discord.js');
138
+ const Anthropic = require('@anthropic-ai/sdk');
139
+
140
+ // ---------------------------------------------------------------------------
141
+ // Configuration
142
+ // ---------------------------------------------------------------------------
143
+
144
+ const DISCORD_BOT_TOKEN = process.env.DISCORD_BOT_TOKEN || '';
145
+ const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY || '';
146
+ const DISCORD_CHANNEL_IDS = process.env.DISCORD_CHANNEL_IDS || '';
147
+
148
+ const MODEL = process.env.MODEL || 'claude-sonnet-4-5-20250929';
149
+ const MAX_TOKENS = parseInt(process.env.MAX_TOKENS || '1024', 10);
150
+
151
+ const SYSTEM_PROMPT = \`\\
152
+ You are a helpful assistant in a Discord server.
153
+
154
+ Be concise and friendly. Use code blocks for code examples.
155
+ Keep responses under 1800 characters (Discord limit is 2000).\`;
156
+
157
+
158
+ // ---------------------------------------------------------------------------
159
+ // Anthropic API
160
+ // ---------------------------------------------------------------------------
161
+
162
+ async function askClaude(client, userMessage) {
163
+ const response = await client.messages.create({
164
+ model: MODEL,
165
+ max_tokens: MAX_TOKENS,
166
+ system: SYSTEM_PROMPT,
167
+ messages: [{ role: 'user', content: userMessage }],
168
+ });
169
+ return response.content[0].text;
170
+ }
171
+
172
+
173
+ // ---------------------------------------------------------------------------
174
+ // Discord bot
175
+ // ---------------------------------------------------------------------------
176
+
177
+ function parseChannelIds(raw) {
178
+ return new Set(
179
+ raw.split(',')
180
+ .map(s => s.trim())
181
+ .filter(s => /^\\d+$/.test(s))
182
+ );
183
+ }
184
+
185
+ function main() {
186
+ if (!DISCORD_BOT_TOKEN) {
187
+ console.error('DISCORD_BOT_TOKEN not set');
188
+ process.exit(1);
189
+ }
190
+ if (!ANTHROPIC_API_KEY) {
191
+ console.error('ANTHROPIC_API_KEY not set');
192
+ process.exit(1);
193
+ }
194
+ if (!DISCORD_CHANNEL_IDS) {
195
+ console.error('DISCORD_CHANNEL_IDS not set — add comma-separated channel IDs');
196
+ process.exit(1);
197
+ }
198
+
199
+ const allowedChannels = parseChannelIds(DISCORD_CHANNEL_IDS);
200
+ if (allowedChannels.size === 0) {
201
+ console.error('No valid channel IDs in DISCORD_CHANNEL_IDS:', DISCORD_CHANNEL_IDS);
202
+ process.exit(1);
203
+ }
204
+
205
+ console.log(\`Starting bot — model: \${MODEL}, channels: \${[...allowedChannels].join(', ')}\`);
206
+
207
+ const anthropic = new Anthropic({ apiKey: ANTHROPIC_API_KEY });
208
+
209
+ const bot = new Client({
210
+ intents: [
211
+ GatewayIntentBits.Guilds,
212
+ GatewayIntentBits.GuildMessages,
213
+ GatewayIntentBits.MessageContent,
214
+ ],
215
+ });
216
+
217
+ bot.on('ready', () => {
218
+ console.log(\`Bot connected as \${bot.user.tag}\`);
219
+ });
220
+
221
+ bot.on('messageCreate', async (message) => {
222
+ if (message.author.bot || !message.content.trim()) return;
223
+
224
+ const channelId = message.channelId;
225
+ const parentId = message.channel.parentId;
226
+ if (!allowedChannels.has(channelId) && !allowedChannels.has(parentId)) return;
227
+
228
+ console.log(\`Message from \${message.author.tag}: \${message.content.slice(0, 100)}\`);
229
+
230
+ try {
231
+ await message.channel.sendTyping();
232
+ const answer = await askClaude(anthropic, message.content);
233
+ const trimmed = answer.length > 1900 ? answer.slice(0, 1897) + '...' : answer;
234
+ await message.reply(trimmed);
235
+ } catch (err) {
236
+ console.error('Anthropic API error:', err.message || err);
237
+ await message.reply('Sorry, I ran into an issue. Please try again.');
238
+ }
239
+ });
240
+
241
+ bot.login(DISCORD_BOT_TOKEN);
242
+ }
243
+
244
+ main();
245
+ `;
246
+ const DISCORD_PACKAGE_JSON = `{
247
+ "name": "discord-bot",
248
+ "private": true,
249
+ "type": "commonjs",
250
+ "dependencies": {
251
+ "discord.js": "^14.16.0",
252
+ "@anthropic-ai/sdk": "^0.30.0"
253
+ }
254
+ }
255
+ `;
256
+ const DISCORD_JS_ENV_EXAMPLE = `# Required — get your bot token from https://discord.com/developers/applications
257
+ DISCORD_BOT_TOKEN=
258
+
259
+ # Required for local dev — auto-injected in production via supported_providers
260
+ ANTHROPIC_API_KEY=
261
+
262
+ # Required — comma-separated Discord channel IDs where the bot should respond
263
+ DISCORD_CHANNEL_IDS=
264
+
265
+ # Optional — customize the model and response length
266
+ # MODEL=claude-sonnet-4-5-20250929
267
+ # MAX_TOKENS=1024
268
+ `;
90
269
  function readmeTemplate(agentName, flavor) {
91
270
  if (flavor === 'support_agent') {
92
271
  return `# ${agentName}
@@ -335,6 +514,70 @@ if __name__ == "__main__":
335
514
  `;
336
515
  const ORCHESTRATOR_REQUIREMENTS = `orchagent-sdk>=0.1.0
337
516
  `;
517
+ const ORCHESTRATOR_MAIN_JS = `/**
518
+ * orchagent orchestrator entrypoint.
519
+ *
520
+ * Reads JSON input from stdin, calls dependency agents via the orchagent SDK,
521
+ * and writes JSON output to stdout.
522
+ *
523
+ * Usage:
524
+ * echo '{"task": "do something"}' | node main.js
525
+ */
526
+
527
+ const fs = require('fs');
528
+ const { AgentClient } = require('orchagent-sdk');
529
+
530
+ async function main() {
531
+ // Read JSON input from stdin
532
+ const raw = fs.readFileSync('/dev/stdin', 'utf-8');
533
+ let data;
534
+ try {
535
+ data = raw.trim() ? JSON.parse(raw) : {};
536
+ } catch {
537
+ console.log(JSON.stringify({ error: 'Invalid JSON input' }));
538
+ process.exit(1);
539
+ }
540
+
541
+ const task = data.task || '';
542
+
543
+ // --- Your orchestration logic here ---
544
+ // The AgentClient reads ORCHAGENT_SERVICE_KEY from the environment automatically.
545
+ // Do NOT add ORCHAGENT_SERVICE_KEY to required_secrets — the gateway injects it.
546
+ const client = new AgentClient();
547
+
548
+ // Call a dependency agent (must be listed in manifest.dependencies)
549
+ const result = await client.call('org/agent-name@v1', { input: task });
550
+
551
+ // You can chain multiple calls, run them in parallel, or add conditional logic:
552
+ //
553
+ // Sequential:
554
+ // const result2 = await client.call('org/another-agent@v1', { input: result });
555
+ //
556
+ // Parallel:
557
+ // const [r1, r2] = await Promise.all([
558
+ // client.call('org/agent-a@v1', { input: task }),
559
+ // client.call('org/agent-b@v1', { input: task }),
560
+ // ]);
561
+ // --- End orchestration logic ---
562
+
563
+ // Write JSON output to stdout
564
+ console.log(JSON.stringify({ result, success: true }));
565
+ }
566
+
567
+ main().catch(err => {
568
+ console.error(err);
569
+ process.exit(1);
570
+ });
571
+ `;
572
+ const ORCHESTRATOR_PACKAGE_JSON = `{
573
+ "name": "orchestrator",
574
+ "private": true,
575
+ "type": "commonjs",
576
+ "dependencies": {
577
+ "orchagent-sdk": "^0.1.0"
578
+ }
579
+ }
580
+ `;
338
581
  const DISCORD_MAIN_PY = `"""
339
582
  Discord bot agent — powered by Claude.
340
583
 
@@ -525,7 +768,8 @@ function registerInitCommand(program) {
525
768
  .option('--type <type>', 'Type: prompt, tool, agent, or skill (legacy aliases: agentic, code)', 'prompt')
526
769
  .option('--orchestrator', 'Create an orchestrator agent with dependency scaffolding and SDK boilerplate')
527
770
  .option('--run-mode <mode>', 'Run mode for agents: on_demand or always_on', 'on_demand')
528
- .option('--template <name>', 'Start from a template (available: support-agent, discord, github-weekly-summary)')
771
+ .option('--language <lang>', 'Language: python or javascript (default: python)', 'python')
772
+ .option('--template <name>', 'Start from a template (available: support-agent, discord, discord-js, github-weekly-summary)')
529
773
  .action(async (name, options) => {
530
774
  const cwd = process.cwd();
531
775
  let runMode = (options.runMode || 'on_demand').trim().toLowerCase();
@@ -539,9 +783,20 @@ function registerInitCommand(program) {
539
783
  }
540
784
  initMode = { type: 'agent', flavor: 'orchestrator' };
541
785
  }
786
+ // Validate --language option
787
+ const language = (options.language || 'python').trim().toLowerCase();
788
+ if (!['python', 'javascript', 'js', 'typescript', 'ts'].includes(language)) {
789
+ throw new errors_1.CliError(`Invalid --language '${options.language}'. Use 'python' or 'javascript'.`);
790
+ }
791
+ const isJavaScript = ['javascript', 'js', 'typescript', 'ts'].includes(language);
792
+ // Block unsupported JS flavors
793
+ if (isJavaScript && initMode.flavor === 'managed_loop') {
794
+ throw new errors_1.CliError('JavaScript agent-type agents are not yet supported. Use --type tool for JavaScript agents.');
795
+ }
796
+ // JS orchestrators are now supported via the orchagent-sdk npm package
542
797
  if (options.template) {
543
798
  const template = options.template.trim().toLowerCase();
544
- const validTemplates = ['support-agent', 'discord', 'github-weekly-summary'];
799
+ const validTemplates = ['support-agent', 'discord', 'discord-js', 'github-weekly-summary'];
545
800
  if (!validTemplates.includes(template)) {
546
801
  throw new errors_1.CliError(`Unknown --template '${template}'. Available templates: ${validTemplates.join(', ')}`);
547
802
  }
@@ -559,6 +814,10 @@ function registerInitCommand(program) {
559
814
  initMode = { type: 'agent', flavor: 'discord' };
560
815
  runMode = 'always_on';
561
816
  }
817
+ else if (template === 'discord-js') {
818
+ initMode = { type: 'agent', flavor: 'discord_js' };
819
+ runMode = 'always_on';
820
+ }
562
821
  else if (template === 'github-weekly-summary') {
563
822
  initMode = { type: 'agent', flavor: 'github_weekly_summary' };
564
823
  }
@@ -672,6 +931,7 @@ function registerInitCommand(program) {
672
931
  process.stdout.write(` ${s + 2}. Copy .env.example to .env and add platform tokens\n`);
673
932
  process.stdout.write(` ${s + 3}. Test locally: pip install -r requirements.txt && python main.py\n`);
674
933
  process.stdout.write(` ${s + 4}. Deploy: orch publish && orch service deploy\n`);
934
+ process.stdout.write(`\n Skill: orch skill install orchagent/agent-builder — gives your AI the full platform builder reference\n`);
675
935
  return;
676
936
  }
677
937
  // Handle github-weekly-summary template separately (own file set + output)
@@ -727,6 +987,57 @@ function registerInitCommand(program) {
727
987
  process.stdout.write(` ${s + 3}. orch run <org>/${agentName} Test it\n`);
728
988
  process.stdout.write(` ${s + 4}. orch schedule create <org>/${agentName} --cron "0 9 * * 1" Schedule weekly\n`);
729
989
  process.stdout.write(`\n See README.md for full setup guide.\n`);
990
+ process.stdout.write(`\n Skill: orch skill install orchagent/agent-builder — gives your AI the full platform builder reference\n`);
991
+ return;
992
+ }
993
+ // Handle discord-js template separately (JS Discord bot)
994
+ if (initMode.flavor === 'discord_js') {
995
+ const manifestPath = path_1.default.join(targetDir, 'orchagent.json');
996
+ // Check if already initialized
997
+ try {
998
+ await promises_1.default.access(manifestPath);
999
+ throw new errors_1.CliError(`Already initialized (orchagent.json exists in ${name ? name + '/' : 'current directory'})`);
1000
+ }
1001
+ catch (err) {
1002
+ if (err.code !== 'ENOENT') {
1003
+ throw err;
1004
+ }
1005
+ }
1006
+ const manifest = {
1007
+ name: agentName,
1008
+ type: 'agent',
1009
+ description: 'An always-on Discord bot powered by Claude (JavaScript)',
1010
+ run_mode: 'always_on',
1011
+ runtime: { command: 'node main.js' },
1012
+ entrypoint: 'main.js',
1013
+ supported_providers: ['anthropic'],
1014
+ required_secrets: ['DISCORD_BOT_TOKEN', 'DISCORD_CHANNEL_IDS'],
1015
+ tags: ['discord', 'always-on', 'javascript'],
1016
+ };
1017
+ await promises_1.default.writeFile(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
1018
+ await promises_1.default.writeFile(path_1.default.join(targetDir, 'main.js'), DISCORD_MAIN_JS);
1019
+ await promises_1.default.writeFile(path_1.default.join(targetDir, 'package.json'), DISCORD_PACKAGE_JSON);
1020
+ await promises_1.default.writeFile(path_1.default.join(targetDir, '.env.example'), DISCORD_JS_ENV_EXAMPLE);
1021
+ await promises_1.default.writeFile(path_1.default.join(targetDir, 'README.md'), readmeTemplate(agentName, 'discord'));
1022
+ const prefix = name ? name + '/' : '';
1023
+ process.stdout.write(`\nInitialized JS Discord bot "${agentName}" in ${targetDir}\n`);
1024
+ process.stdout.write(`\nFiles created:\n`);
1025
+ process.stdout.write(` ${prefix}orchagent.json - Agent configuration\n`);
1026
+ process.stdout.write(` ${prefix}main.js - Discord bot (discord.js + Anthropic)\n`);
1027
+ process.stdout.write(` ${prefix}package.json - npm dependencies\n`);
1028
+ process.stdout.write(` ${prefix}.env.example - Environment variables template\n`);
1029
+ process.stdout.write(` ${prefix}README.md - Setup guide\n`);
1030
+ process.stdout.write(`\nNext steps:\n`);
1031
+ const stepNum = name ? 2 : 1;
1032
+ if (name) {
1033
+ process.stdout.write(` 1. cd ${name}\n`);
1034
+ }
1035
+ process.stdout.write(` ${stepNum}. Create a Discord bot at https://discord.com/developers/applications\n`);
1036
+ process.stdout.write(` ${stepNum + 1}. Enable Message Content Intent in bot settings\n`);
1037
+ process.stdout.write(` ${stepNum + 2}. Copy .env.example to .env and fill in your tokens\n`);
1038
+ process.stdout.write(` ${stepNum + 3}. Test locally: npm install && node main.js\n`);
1039
+ process.stdout.write(` ${stepNum + 4}. Deploy: orch publish\n`);
1040
+ process.stdout.write(`\n Skill: orch skill install orchagent/agent-builder — gives your AI the full platform builder reference\n`);
730
1041
  return;
731
1042
  }
732
1043
  const manifestPath = path_1.default.join(targetDir, 'orchagent.json');
@@ -752,7 +1063,13 @@ function registerInitCommand(program) {
752
1063
  manifest.run_mode = runMode;
753
1064
  if (initMode.flavor === 'orchestrator') {
754
1065
  manifest.description = 'An orchestrator agent that coordinates other agents';
755
- manifest.runtime = { command: 'python main.py' };
1066
+ if (isJavaScript) {
1067
+ manifest.runtime = { command: 'node main.js' };
1068
+ manifest.entrypoint = 'main.js';
1069
+ }
1070
+ else {
1071
+ manifest.runtime = { command: 'python main.py' };
1072
+ }
756
1073
  manifest.manifest = {
757
1074
  manifest_version: 1,
758
1075
  dependencies: [{ id: 'org/agent-name', version: 'v1' }],
@@ -777,15 +1094,25 @@ function registerInitCommand(program) {
777
1094
  }
778
1095
  else if (initMode.flavor === 'code_runtime') {
779
1096
  manifest.description = 'A code-runtime agent';
780
- manifest.runtime = { command: 'python main.py' };
1097
+ if (isJavaScript) {
1098
+ manifest.runtime = { command: 'node main.js' };
1099
+ manifest.entrypoint = 'main.js';
1100
+ }
1101
+ else {
1102
+ manifest.runtime = { command: 'python main.py' };
1103
+ }
781
1104
  manifest.required_secrets = [];
782
1105
  }
783
1106
  await promises_1.default.writeFile(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
784
1107
  if (initMode.flavor === 'orchestrator') {
785
- const entrypointPath = path_1.default.join(targetDir, 'main.py');
786
- const requirementsPath = path_1.default.join(targetDir, 'requirements.txt');
787
- await promises_1.default.writeFile(entrypointPath, ORCHESTRATOR_MAIN_PY);
788
- await promises_1.default.writeFile(requirementsPath, ORCHESTRATOR_REQUIREMENTS);
1108
+ if (isJavaScript) {
1109
+ await promises_1.default.writeFile(path_1.default.join(targetDir, 'main.js'), ORCHESTRATOR_MAIN_JS);
1110
+ await promises_1.default.writeFile(path_1.default.join(targetDir, 'package.json'), ORCHESTRATOR_PACKAGE_JSON);
1111
+ }
1112
+ else {
1113
+ await promises_1.default.writeFile(path_1.default.join(targetDir, 'main.py'), ORCHESTRATOR_MAIN_PY);
1114
+ await promises_1.default.writeFile(path_1.default.join(targetDir, 'requirements.txt'), ORCHESTRATOR_REQUIREMENTS);
1115
+ }
789
1116
  await promises_1.default.writeFile(schemaPath, AGENT_SCHEMA_TEMPLATE);
790
1117
  }
791
1118
  else if (initMode.flavor === 'discord') {
@@ -797,8 +1124,18 @@ function registerInitCommand(program) {
797
1124
  await promises_1.default.writeFile(envExamplePath, DISCORD_ENV_EXAMPLE);
798
1125
  }
799
1126
  else if (initMode.flavor === 'code_runtime') {
800
- const entrypointPath = path_1.default.join(targetDir, 'main.py');
801
- await promises_1.default.writeFile(entrypointPath, CODE_TEMPLATE_PY);
1127
+ if (isJavaScript) {
1128
+ await promises_1.default.writeFile(path_1.default.join(targetDir, 'main.js'), CODE_TEMPLATE_JS);
1129
+ await promises_1.default.writeFile(path_1.default.join(targetDir, 'package.json'), JSON.stringify({
1130
+ name: agentName,
1131
+ private: true,
1132
+ type: 'commonjs',
1133
+ dependencies: {},
1134
+ }, null, 2) + '\n');
1135
+ }
1136
+ else {
1137
+ await promises_1.default.writeFile(path_1.default.join(targetDir, 'main.py'), CODE_TEMPLATE_PY);
1138
+ }
802
1139
  await promises_1.default.writeFile(schemaPath, SCHEMA_TEMPLATE);
803
1140
  }
804
1141
  else if (initMode.flavor === 'managed_loop') {
@@ -817,8 +1154,14 @@ function registerInitCommand(program) {
817
1154
  const prefix = name ? name + '/' : '';
818
1155
  process.stdout.write(` ${prefix}orchagent.json - Agent configuration\n`);
819
1156
  if (initMode.flavor === 'orchestrator') {
820
- process.stdout.write(` ${prefix}main.py - Orchestrator entrypoint (SDK calls)\n`);
821
- process.stdout.write(` ${prefix}requirements.txt - Python dependencies (orchagent-sdk)\n`);
1157
+ if (isJavaScript) {
1158
+ process.stdout.write(` ${prefix}main.js - Orchestrator entrypoint (SDK calls)\n`);
1159
+ process.stdout.write(` ${prefix}package.json - npm dependencies (orchagent-sdk)\n`);
1160
+ }
1161
+ else {
1162
+ process.stdout.write(` ${prefix}main.py - Orchestrator entrypoint (SDK calls)\n`);
1163
+ process.stdout.write(` ${prefix}requirements.txt - Python dependencies (orchagent-sdk)\n`);
1164
+ }
822
1165
  }
823
1166
  else if (initMode.flavor === 'discord') {
824
1167
  process.stdout.write(` ${prefix}main.py - Discord bot (discord.py + Anthropic)\n`);
@@ -826,7 +1169,13 @@ function registerInitCommand(program) {
826
1169
  process.stdout.write(` ${prefix}.env.example - Environment variables template\n`);
827
1170
  }
828
1171
  else if (initMode.flavor === 'code_runtime') {
829
- process.stdout.write(` ${prefix}main.py - Agent entrypoint (stdin/stdout JSON)\n`);
1172
+ if (isJavaScript) {
1173
+ process.stdout.write(` ${prefix}main.js - Agent entrypoint (stdin/stdout JSON)\n`);
1174
+ process.stdout.write(` ${prefix}package.json - npm dependencies\n`);
1175
+ }
1176
+ else {
1177
+ process.stdout.write(` ${prefix}main.py - Agent entrypoint (stdin/stdout JSON)\n`);
1178
+ }
830
1179
  }
831
1180
  else {
832
1181
  process.stdout.write(` ${prefix}prompt.md - Prompt template\n`);
@@ -844,7 +1193,12 @@ function registerInitCommand(program) {
844
1193
  process.stdout.write(` 1. cd ${name}\n`);
845
1194
  }
846
1195
  process.stdout.write(` ${stepNum}. Update manifest.dependencies in orchagent.json with your actual agents\n`);
847
- process.stdout.write(` ${stepNum + 1}. Edit main.py with your orchestration logic\n`);
1196
+ if (isJavaScript) {
1197
+ process.stdout.write(` ${stepNum + 1}. Edit main.js with your orchestration logic\n`);
1198
+ }
1199
+ else {
1200
+ process.stdout.write(` ${stepNum + 1}. Edit main.py with your orchestration logic\n`);
1201
+ }
848
1202
  process.stdout.write(` ${stepNum + 2}. Publish dependency agents first, then: orchagent publish\n`);
849
1203
  }
850
1204
  else if (initMode.flavor === 'discord') {
@@ -863,10 +1217,18 @@ function registerInitCommand(program) {
863
1217
  if (name) {
864
1218
  process.stdout.write(` 1. cd ${name}\n`);
865
1219
  }
866
- process.stdout.write(` ${stepNum}. Edit main.py with your agent logic\n`);
867
- process.stdout.write(` ${stepNum + 1}. Edit schema.json with your input/output schemas\n`);
868
- process.stdout.write(` ${stepNum + 2}. Test: echo '{"input": "test"}' | python main.py\n`);
869
- process.stdout.write(` ${stepNum + 3}. Run: orchagent publish\n`);
1220
+ if (isJavaScript) {
1221
+ process.stdout.write(` ${stepNum}. Edit main.js with your agent logic\n`);
1222
+ process.stdout.write(` ${stepNum + 1}. Edit schema.json with your input/output schemas\n`);
1223
+ process.stdout.write(` ${stepNum + 2}. Test: echo '{"input": "test"}' | node main.js\n`);
1224
+ process.stdout.write(` ${stepNum + 3}. Run: orchagent publish\n`);
1225
+ }
1226
+ else {
1227
+ process.stdout.write(` ${stepNum}. Edit main.py with your agent logic\n`);
1228
+ process.stdout.write(` ${stepNum + 1}. Edit schema.json with your input/output schemas\n`);
1229
+ process.stdout.write(` ${stepNum + 2}. Test: echo '{"input": "test"}' | python main.py\n`);
1230
+ process.stdout.write(` ${stepNum + 3}. Run: orchagent publish\n`);
1231
+ }
870
1232
  }
871
1233
  else {
872
1234
  const stepNum = name ? 2 : 1;
@@ -877,5 +1239,6 @@ function registerInitCommand(program) {
877
1239
  process.stdout.write(` ${stepNum + 1}. Edit schema.json with your input/output schemas\n`);
878
1240
  process.stdout.write(` ${stepNum + 2}. Run: orchagent publish\n`);
879
1241
  }
1242
+ process.stdout.write(`\n Skill: orch skill install orchagent/agent-builder — gives your AI the full platform builder reference\n`);
880
1243
  });
881
1244
  }
@@ -80,7 +80,9 @@ async function scanUndeclaredEnvVars(agentDir, requiredSecrets) {
80
80
  /os\.getenv\s*\(\s*['"]([A-Z][A-Z0-9_]*)['"]/g,
81
81
  ];
82
82
  const found = new Set();
83
- // Scan .py files in the agent directory (up to 2 levels deep)
83
+ // JS/TS env var access pattern
84
+ const jsEnvPattern = /process\.env\.([A-Z_][A-Z0-9_]*)/g;
85
+ // Scan .py/.js/.ts files in the agent directory (up to 2 levels deep)
84
86
  async function scanDir(dir, depth) {
85
87
  let entries;
86
88
  try {
@@ -112,6 +114,19 @@ async function scanUndeclaredEnvVars(agentDir, requiredSecrets) {
112
114
  // Skip unreadable files
113
115
  }
114
116
  }
117
+ else if (entry.isFile() && (name.endsWith('.js') || name.endsWith('.ts'))) {
118
+ try {
119
+ const content = await promises_1.default.readFile(fullPath, 'utf-8');
120
+ jsEnvPattern.lastIndex = 0;
121
+ let m;
122
+ while ((m = jsEnvPattern.exec(content)) !== null) { // eslint-disable-line no-cond-assign
123
+ found.add(m[1]);
124
+ }
125
+ }
126
+ catch {
127
+ // Skip unreadable files
128
+ }
129
+ }
115
130
  }
116
131
  }
117
132
  await scanDir(agentDir, 0);
@@ -147,6 +162,19 @@ async function detectSdkCompatible(agentDir) {
147
162
  catch {
148
163
  // File doesn't exist
149
164
  }
165
+ // Check package.json (JS/TS agents)
166
+ const pkgJsonPath = path_1.default.join(agentDir, 'package.json');
167
+ try {
168
+ const content = await promises_1.default.readFile(pkgJsonPath, 'utf-8');
169
+ const pkgJson = JSON.parse(content);
170
+ const deps = { ...pkgJson.dependencies, ...pkgJson.devDependencies };
171
+ if (deps['orchagent-sdk'] || deps['@orchagent/sdk']) {
172
+ return true;
173
+ }
174
+ }
175
+ catch {
176
+ // File doesn't exist or invalid JSON
177
+ }
150
178
  return false;
151
179
  }
152
180
  async function parseSkillMd(filePath) {
@@ -886,6 +914,26 @@ function registerPublishCommand(program) {
886
914
  catch {
887
915
  // Optional
888
916
  }
917
+ // Include package.json + lockfile for JS agents (overrides DEFAULT_EXCLUDES)
918
+ const pkgPath = path_1.default.join(cwd, 'package.json');
919
+ try {
920
+ await promises_1.default.access(pkgPath);
921
+ includePatterns.push('package.json');
922
+ process.stdout.write(` Including package.json for sandbox dependencies\n`);
923
+ // Include lockfile for deterministic installs (npm ci)
924
+ const lockPath = path_1.default.join(cwd, 'package-lock.json');
925
+ try {
926
+ await promises_1.default.access(lockPath);
927
+ includePatterns.push('package-lock.json');
928
+ process.stdout.write(` Including package-lock.json for deterministic installs\n`);
929
+ }
930
+ catch {
931
+ // No lockfile — npm install will be used instead of npm ci
932
+ }
933
+ }
934
+ catch {
935
+ // No package.json — not a JS agent
936
+ }
889
937
  }
890
938
  const bundleResult = await (0, bundle_1.createCodeBundle)(cwd, bundlePath, {
891
939
  entrypoint: bundleEntrypoint,
@@ -992,5 +1040,6 @@ function registerPublishCommand(program) {
992
1040
  process.stdout.write(`\nNote: Hosted code execution is in beta. Contact support for full deployment.\n`);
993
1041
  }
994
1042
  process.stdout.write(`\nView analytics and usage: https://orchagent.io/dashboard\n`);
1043
+ process.stdout.write(`\nSkill: orch skill install orchagent/agent-builder — gives your AI the full platform builder reference\n`);
995
1044
  });
996
1045
  }
@@ -69,6 +69,16 @@ const DOWNSTREAM_REMAINING_ENV = 'ORCHAGENT_DOWNSTREAM_REMAINING';
69
69
  const CONTENT_FIELD_NAMES = ['code', 'content', 'text', 'source', 'input', 'file_content', 'body'];
70
70
  // Keys that might indicate local file path references in JSON payloads
71
71
  const LOCAL_PATH_KEYS = ['path', 'directory', 'file', 'filepath', 'dir', 'folder', 'local'];
72
+ /**
73
+ * Return the correct local command for a given entrypoint file extension.
74
+ * Uses `python3` (not `python`) to match existing behavior on macOS/Linux.
75
+ */
76
+ function localCommandForEntrypoint(entrypoint) {
77
+ if (entrypoint.endsWith('.js') || entrypoint.endsWith('.mjs') || entrypoint.endsWith('.cjs')) {
78
+ return 'node';
79
+ }
80
+ return 'python3';
81
+ }
72
82
  function parseAgentRef(value) {
73
83
  const [ref, versionPart] = value.split('@');
74
84
  const version = versionPart?.trim() || DEFAULT_VERSION;
@@ -652,25 +662,6 @@ async function detectAllLlmKeys(supportedProviders, config) {
652
662
  }
653
663
  }
654
664
  }
655
- if (config?.apiKey) {
656
- try {
657
- const { fetchLlmKeys } = await Promise.resolve().then(() => __importStar(require('../lib/api')));
658
- const serverKeys = await fetchLlmKeys(config);
659
- for (const serverKey of serverKeys) {
660
- if (!seen.has(serverKey.provider)) {
661
- seen.add(serverKey.provider);
662
- providers.push({
663
- provider: serverKey.provider,
664
- apiKey: serverKey.api_key,
665
- model: serverKey.model || (0, llm_1.getDefaultModel)(serverKey.provider),
666
- });
667
- }
668
- }
669
- }
670
- catch {
671
- // Server fetch failed, continue with what we have
672
- }
673
- }
674
665
  return providers;
675
666
  }
676
667
  async function executePromptLocally(agentData, inputData, skillPrompts = [], config, providerOverride, modelOverride) {
@@ -1062,6 +1053,32 @@ async function executeBundleAgent(config, org, agentName, version, agentData, ar
1062
1053
  throw err;
1063
1054
  }
1064
1055
  }
1056
+ // Install npm dependencies if package.json exists (JS agents)
1057
+ const pkgJsonPath = path_1.default.join(extractDir, 'package.json');
1058
+ try {
1059
+ await promises_1.default.access(pkgJsonPath);
1060
+ await (0, spinner_1.withSpinner)('Installing npm dependencies...', async () => {
1061
+ const lockfilePath = path_1.default.join(extractDir, 'package-lock.json');
1062
+ let useNpmCi = false;
1063
+ try {
1064
+ await promises_1.default.access(lockfilePath);
1065
+ useNpmCi = true;
1066
+ }
1067
+ catch { /* no lockfile */ }
1068
+ const npmArgs = useNpmCi
1069
+ ? ['ci', '--no-audit', '--no-fund']
1070
+ : ['install', '--production', '--no-audit', '--no-fund'];
1071
+ const { code } = await runCommand('npm', npmArgs);
1072
+ if (code !== 0) {
1073
+ throw new errors_1.CliError('Failed to install npm dependencies');
1074
+ }
1075
+ }, { successText: 'npm dependencies installed' });
1076
+ }
1077
+ catch (err) {
1078
+ if (err.code !== 'ENOENT') {
1079
+ throw err;
1080
+ }
1081
+ }
1065
1082
  const entrypoint = agentData.entrypoint || 'sandbox_main.py';
1066
1083
  const entrypointPath = path_1.default.join(extractDir, entrypoint);
1067
1084
  try {
@@ -1116,7 +1133,8 @@ async function executeBundleAgent(config, org, agentName, version, agentData, ar
1116
1133
  }
1117
1134
  }
1118
1135
  }
1119
- process.stderr.write(`\nRunning: python3 ${entrypoint}\n\n`);
1136
+ const execCmd = localCommandForEntrypoint(entrypoint);
1137
+ process.stderr.write(`\nRunning: ${execCmd} ${entrypoint}\n\n`);
1120
1138
  const subprocessEnv = { ...process.env };
1121
1139
  if (config.apiKey) {
1122
1140
  subprocessEnv.ORCHAGENT_SERVICE_KEY = config.apiKey;
@@ -1133,7 +1151,7 @@ async function executeBundleAgent(config, org, agentName, version, agentData, ar
1133
1151
  subprocessEnv[MAX_HOPS_ENV] = String(manifest.manifest?.max_hops || 10);
1134
1152
  subprocessEnv[DOWNSTREAM_REMAINING_ENV] = String(manifest.manifest?.per_call_downstream_cap || 100);
1135
1153
  }
1136
- const proc = (0, child_process_1.spawn)('python3', [entrypointPath], {
1154
+ const proc = (0, child_process_1.spawn)(execCmd, [entrypointPath], {
1137
1155
  cwd: extractDir,
1138
1156
  stdio: ['pipe', 'pipe', 'pipe'],
1139
1157
  env: subprocessEnv,
@@ -1442,7 +1460,11 @@ async function executeLocalFromDir(dirPath, args, options) {
1442
1460
  return;
1443
1461
  }
1444
1462
  // Code runtime agents with bundle
1445
- const entrypoint = manifest.entrypoint || 'sandbox_main.py';
1463
+ const runtimeObj = manifest.runtime;
1464
+ const runtimeCommand = runtimeObj?.command;
1465
+ const entrypoint = manifest.entrypoint
1466
+ || (runtimeCommand ? runtimeCommand.split(' ').pop() || 'sandbox_main.py' : null)
1467
+ || 'sandbox_main.py';
1446
1468
  const entrypointPath = path_1.default.join(resolved, entrypoint);
1447
1469
  try {
1448
1470
  await promises_1.default.access(entrypointPath);
@@ -1492,6 +1514,29 @@ async function executeLocalFromDir(dirPath, args, options) {
1492
1514
  catch {
1493
1515
  // No requirements.txt
1494
1516
  }
1517
+ // Install npm dependencies if package.json exists (JS agents)
1518
+ const localPkgJsonPath = path_1.default.join(resolved, 'package.json');
1519
+ try {
1520
+ await promises_1.default.access(localPkgJsonPath);
1521
+ const lockfilePath = path_1.default.join(resolved, 'package-lock.json');
1522
+ let useNpmCi = false;
1523
+ try {
1524
+ await promises_1.default.access(lockfilePath);
1525
+ useNpmCi = true;
1526
+ }
1527
+ catch { /* no lockfile */ }
1528
+ const npmArgs = useNpmCi
1529
+ ? ['ci', '--no-audit', '--no-fund']
1530
+ : ['install', '--production', '--no-audit', '--no-fund'];
1531
+ process.stderr.write('Installing npm dependencies...\n');
1532
+ const { code } = await runCommand('npm', npmArgs);
1533
+ if (code !== 0) {
1534
+ process.stderr.write('Warning: Failed to install npm dependencies\n');
1535
+ }
1536
+ }
1537
+ catch {
1538
+ // No package.json
1539
+ }
1495
1540
  // Check for keyed file/mount injection (tool path)
1496
1541
  const toolFileArgs = options.file ?? [];
1497
1542
  const toolKeyedFiles = toolFileArgs.filter(a => isKeyedFileArg(a) !== null);
@@ -1517,13 +1562,14 @@ async function executeLocalFromDir(dirPath, args, options) {
1517
1562
  else if (args.length > 0) {
1518
1563
  inputJson = JSON.stringify({ input: args[0] });
1519
1564
  }
1520
- process.stderr.write(`\nRunning: python3 ${entrypoint}\n\n`);
1565
+ const localExecCmd = localCommandForEntrypoint(entrypoint);
1566
+ process.stderr.write(`\nRunning: ${localExecCmd} ${entrypoint}\n\n`);
1521
1567
  const subprocessEnv = { ...process.env };
1522
1568
  if (config.apiKey) {
1523
1569
  subprocessEnv.ORCHAGENT_SERVICE_KEY = config.apiKey;
1524
1570
  subprocessEnv.ORCHAGENT_API_URL = config.apiUrl;
1525
1571
  }
1526
- const proc = (0, child_process_1.spawn)('python3', [entrypointPath], {
1572
+ const proc = (0, child_process_1.spawn)(localExecCmd, [entrypointPath], {
1527
1573
  cwd: resolved,
1528
1574
  stdio: ['pipe', 'pipe', 'pipe'],
1529
1575
  env: subprocessEnv,
package/dist/lib/api.js CHANGED
@@ -46,7 +46,7 @@ exports.updateOrg = updateOrg;
46
46
  exports.getPublicAgent = getPublicAgent;
47
47
  exports.listMyAgents = listMyAgents;
48
48
  exports.createAgent = createAgent;
49
- exports.fetchLlmKeys = fetchLlmKeys;
49
+ exports.listLlmKeys = listLlmKeys;
50
50
  exports.downloadCodeBundle = downloadCodeBundle;
51
51
  exports.uploadCodeBundle = uploadCodeBundle;
52
52
  exports.getMyAgent = getMyAgent;
@@ -306,9 +306,8 @@ async function createAgent(config, data, workspaceId) {
306
306
  headers,
307
307
  });
308
308
  }
309
- async function fetchLlmKeys(config) {
310
- const result = await request(config, 'GET', '/llm-keys/export');
311
- return result.keys;
309
+ async function listLlmKeys(config) {
310
+ return request(config, 'GET', '/llm-keys');
312
311
  }
313
312
  /**
314
313
  * Download a code-runtime bundle for local execution.
@@ -44,7 +44,7 @@ const DEFAULT_EXCLUDES = [
44
44
  'npm-debug.log',
45
45
  'yarn-error.log',
46
46
  'package.json',
47
- 'package-lock.json',
47
+ // package-lock.json intentionally NOT excluded — included explicitly for JS agents
48
48
  'yarn.lock',
49
49
  'bun.lockb',
50
50
  'tsconfig.json',
@@ -87,7 +87,7 @@ async function runLlmChecks(options) {
87
87
  try {
88
88
  const config = await (0, config_1.getResolvedConfig)();
89
89
  if (config.apiKey) {
90
- const keys = await (0, api_1.fetchLlmKeys)(config);
90
+ const keys = await (0, api_1.listLlmKeys)(config);
91
91
  serverProviders = keys.map((k) => k.provider);
92
92
  }
93
93
  }
package/dist/lib/llm.js CHANGED
@@ -5,39 +5,6 @@
5
5
  * Centralized LLM provider configuration and utilities.
6
6
  * Used by run, call, and skill commands.
7
7
  */
8
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
- if (k2 === undefined) k2 = k;
10
- var desc = Object.getOwnPropertyDescriptor(m, k);
11
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
- desc = { enumerable: true, get: function() { return m[k]; } };
13
- }
14
- Object.defineProperty(o, k2, desc);
15
- }) : (function(o, m, k, k2) {
16
- if (k2 === undefined) k2 = k;
17
- o[k2] = m[k];
18
- }));
19
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
- Object.defineProperty(o, "default", { enumerable: true, value: v });
21
- }) : function(o, v) {
22
- o["default"] = v;
23
- });
24
- var __importStar = (this && this.__importStar) || (function () {
25
- var ownKeys = function(o) {
26
- ownKeys = Object.getOwnPropertyNames || function (o) {
27
- var ar = [];
28
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
- return ar;
30
- };
31
- return ownKeys(o);
32
- };
33
- return function (mod) {
34
- if (mod && mod.__esModule) return mod;
35
- var result = {};
36
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
- __setModuleDefault(result, mod);
38
- return result;
39
- };
40
- })();
41
8
  Object.defineProperty(exports, "__esModule", { value: true });
42
9
  exports.DEFAULT_MODELS = exports.PROVIDER_ENV_VARS = exports.LlmError = void 0;
43
10
  exports.isRateLimitError = isRateLimitError;
@@ -107,37 +74,10 @@ function detectLlmKeyFromEnv(supportedProviders) {
107
74
  * Checks local env vars first, then fetches from server if available.
108
75
  * Returns provider, key, and optionally the model from server config.
109
76
  */
110
- async function detectLlmKey(supportedProviders, config) {
111
- // 1. Check local env vars first (fast path)
112
- const envKey = detectLlmKeyFromEnv(supportedProviders);
113
- if (envKey)
114
- return envKey;
115
- // 2. If no env var, try server
116
- if (config?.apiKey) {
117
- try {
118
- const { fetchLlmKeys } = await Promise.resolve().then(() => __importStar(require('./api')));
119
- const serverKeys = await fetchLlmKeys(config);
120
- for (const provider of supportedProviders) {
121
- if (provider === 'any') {
122
- // Return first available key
123
- if (serverKeys.length > 0) {
124
- const first = serverKeys[0];
125
- return { provider: first.provider, key: first.api_key, model: first.model };
126
- }
127
- }
128
- else {
129
- const match = serverKeys.find((k) => k.provider === provider);
130
- if (match) {
131
- return { provider: match.provider, key: match.api_key, model: match.model };
132
- }
133
- }
134
- }
135
- }
136
- catch {
137
- // Server fetch failed, continue without
138
- }
139
- }
140
- return null;
77
+ async function detectLlmKey(supportedProviders, _config) {
78
+ // LLM keys are only available from local env vars.
79
+ // Server-stored keys are never exported (security best practice).
80
+ return detectLlmKeyFromEnv(supportedProviders);
141
81
  }
142
82
  /**
143
83
  * Get the default model for a provider.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orchagent/cli",
3
- "version": "0.3.70",
3
+ "version": "0.3.72",
4
4
  "description": "Command-line interface for orchagent — deploy and run AI agents for your team",
5
5
  "license": "MIT",
6
6
  "author": "orchagent <hello@orchagent.io>",