agency-orchestrator 0.5.3 → 0.6.1

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/cli.js CHANGED
@@ -9,7 +9,7 @@
9
9
  * ao roles --agents-dir ./agents
10
10
  */
11
11
  import { readFileSync, existsSync } from 'node:fs';
12
- import { resolve } from 'node:path';
12
+ import { resolve, join, dirname } from 'node:path';
13
13
  import { execSync } from 'node:child_process';
14
14
  import { parseWorkflow, validateWorkflow } from './core/parser.js';
15
15
  import { buildDAG, formatDAG } from './core/dag.js';
@@ -17,6 +17,9 @@ import { listAgents } from './agents/loader.js';
17
17
  import { run } from './index.js';
18
18
  import { scheduleUpdateCheck } from './utils/version-check.js';
19
19
  import { t, detectLang } from './i18n.js';
20
+ import { loadEnvFile, writeEnvFile, ensureEnvGitignored } from './utils/env-loader.js';
21
+ // Auto-load ./.env (shell env wins; no overwrite)
22
+ loadEnvFile();
20
23
  // Suppress Node's DEP0190 warning from legitimate shell:true on Windows (.cmd shims).
21
24
  const origEmit = process.emit.bind(process);
22
25
  process.emit = function (name, data, ...rest) {
@@ -93,8 +96,9 @@ async function handleRun() {
93
96
  const watch = args.includes('--watch');
94
97
  let resumeDir = getArgValue('--resume');
95
98
  const fromStep = getArgValue('--from');
96
- const provider = getArgValue('--provider');
97
- const model = getArgValue('--model');
99
+ // Precedence: CLI flag > .env (AO_PROVIDER/AO_MODEL) > YAML
100
+ const provider = (getArgValue('--provider') || process.env.AO_PROVIDER);
101
+ const model = getArgValue('--model') || process.env.AO_MODEL;
98
102
  // --resume last: 自动找最近一次的输出目录
99
103
  if (resumeDir === 'last') {
100
104
  const { findLatestOutput } = await import('./output/reporter.js');
@@ -335,6 +339,50 @@ async function handleInit() {
335
339
  await interactiveInitWorkflow();
336
340
  return;
337
341
  }
342
+ // ao init --provider X --model Y --base-url Z --api-key K → write ./.env
343
+ const cfgProvider = getArgValue('--provider');
344
+ const cfgModel = getArgValue('--model');
345
+ const cfgBaseUrl = getArgValue('--base-url') || getArgValue('--baseurl');
346
+ const cfgApiKey = getArgValue('--api-key') || getArgValue('--apikey');
347
+ if (cfgProvider || cfgModel || cfgBaseUrl || cfgApiKey) {
348
+ const updates = {};
349
+ if (cfgProvider)
350
+ updates.AO_PROVIDER = cfgProvider;
351
+ if (cfgModel)
352
+ updates.AO_MODEL = cfgModel;
353
+ if (cfgBaseUrl) {
354
+ // Route to the env var the matching connector already reads
355
+ const p = (cfgProvider || '').toLowerCase();
356
+ const urlVar = p === 'ollama' ? 'OLLAMA_BASE_URL' :
357
+ 'OPENAI_BASE_URL';
358
+ updates[urlVar] = cfgBaseUrl;
359
+ }
360
+ if (cfgApiKey) {
361
+ // Route api key to the provider-specific var the connectors already read
362
+ const p = (cfgProvider || process.env.AO_PROVIDER || '').toLowerCase();
363
+ const keyVar = p === 'deepseek' ? 'DEEPSEEK_API_KEY' :
364
+ p === 'openai' ? 'OPENAI_API_KEY' :
365
+ p === 'anthropic' || p === 'claude' ? 'ANTHROPIC_API_KEY' :
366
+ p === 'zhipu' || p === 'glm' ? 'ZHIPU_API_KEY' :
367
+ p === 'qwen' || p === 'dashscope' ? 'DASHSCOPE_API_KEY' :
368
+ p === 'moonshot' || p === 'kimi' ? 'MOONSHOT_API_KEY' :
369
+ 'AO_API_KEY';
370
+ updates[keyVar] = cfgApiKey;
371
+ }
372
+ const envPath = resolve(process.cwd(), '.env');
373
+ writeEnvFile(updates);
374
+ const gitignoreUpdated = ensureEnvGitignored();
375
+ console.log(` ✅ 已写入 ${envPath}`);
376
+ for (const [k, v] of Object.entries(updates)) {
377
+ const shown = /KEY|TOKEN|SECRET/i.test(k) ? v.slice(0, 6) + '…' : v;
378
+ console.log(` ${k}=${shown}`);
379
+ }
380
+ if (gitignoreUpdated)
381
+ console.log(` 🔒 已将 .env 加入 .gitignore`);
382
+ console.log(`\n 下次运行 ao 时会自动加载这些配置。`);
383
+ console.log(` 也可手动编辑 .env 或复制到其他项目复用。`);
384
+ return;
385
+ }
338
386
  const lang = getArgValue('--lang') === 'en' ? 'en' : 'zh';
339
387
  const pkgName = lang === 'en' ? 'agency-agents' : 'agency-agents-zh';
340
388
  const gitRepo = lang === 'en'
@@ -478,16 +526,32 @@ function parseInputArgs() {
478
526
  * 2. ./agency-agents (英文角色,ao init --lang en 下载的)
479
527
  * 3. ../agency-agents-zh 或 ../agency-agents (同级目录)
480
528
  * 4. ./agents (自定义)
529
+ * 5. node_modules/agency-agents-zh (npm 依赖,全局安装时)
530
+ * 6. 包内置 agency-agents/ (随 npm 包分发,与 dist/ 同级)
481
531
  *
482
532
  * @param preferLang 优先语言,影响搜索顺序
483
533
  */
484
534
  function resolveAgentsDir(preferLang) {
535
+ // scriptDir = dist/ inside the installed package
536
+ const scriptDir = dirname(new URL(import.meta.url).pathname);
485
537
  const zhFirst = [
486
538
  './agency-agents-zh',
487
539
  './agency-agents',
488
540
  '../agency-agents-zh',
489
541
  '../agency-agents',
490
542
  './agents',
543
+ // npm 依赖 (cwd/node_modules)
544
+ './node_modules/agency-agents-zh',
545
+ './node_modules/agency-agents',
546
+ // npm 依赖 (包自身的 node_modules,全局安装时)
547
+ join(scriptDir, '..', 'node_modules', 'agency-agents-zh'),
548
+ join(scriptDir, '..', 'node_modules', 'agency-agents'),
549
+ // hoisted node_modules (npm 有时会提升依赖)
550
+ join(scriptDir, '..', '..', 'node_modules', 'agency-agents-zh'),
551
+ join(scriptDir, '..', '..', 'node_modules', 'agency-agents'),
552
+ // 包内置 agency-agents/ (与 dist/ 同级)
553
+ join(scriptDir, '..', 'agency-agents-zh'),
554
+ join(scriptDir, '..', 'agency-agents'),
491
555
  ];
492
556
  const enFirst = [
493
557
  './agency-agents',
@@ -495,12 +559,24 @@ function resolveAgentsDir(preferLang) {
495
559
  '../agency-agents',
496
560
  '../agency-agents-zh',
497
561
  './agents',
562
+ // npm 依赖 (cwd/node_modules)
563
+ './node_modules/agency-agents',
564
+ './node_modules/agency-agents-zh',
565
+ // npm 依赖 (包自身的 node_modules,全局安装时)
566
+ join(scriptDir, '..', 'node_modules', 'agency-agents'),
567
+ join(scriptDir, '..', 'node_modules', 'agency-agents-zh'),
568
+ // hoisted node_modules
569
+ join(scriptDir, '..', '..', 'node_modules', 'agency-agents'),
570
+ join(scriptDir, '..', '..', 'node_modules', 'agency-agents-zh'),
571
+ // 包内置
572
+ join(scriptDir, '..', 'agency-agents'),
573
+ join(scriptDir, '..', 'agency-agents-zh'),
498
574
  ];
499
575
  const candidates = preferLang === 'en' ? enFirst : zhFirst;
500
576
  for (const dir of candidates) {
501
577
  const full = resolve(dir);
502
578
  if (existsSync(full))
503
- return dir;
579
+ return full;
504
580
  }
505
581
  return preferLang === 'en' ? './agency-agents' : './agency-agents-zh';
506
582
  }
package/dist/i18n.js CHANGED
@@ -152,6 +152,7 @@ const dict = {
152
152
  demo 零配置体验多智能体协作(mock + 真实 AI)
153
153
  init 下载/更新角色库 (--lang en 下载英文版)
154
154
  init --workflow 交互式创建新工作流
155
+ init --provider <p> --model <m> 写入 .env 默认配置(支持 --base-url, --api-key)
155
156
  compose "描述" AI 智能编排工作流(一句话生成 YAML)
156
157
  compose "描述" --run 生成并立即运行(一句话出结果)
157
158
  serve 启动 MCP Server(供 Claude Code / Cursor 调用)
@@ -204,6 +205,7 @@ const dict = {
204
205
  demo Zero-config demo (mock + real AI)
205
206
  init Download/update role library (--lang en for English)
206
207
  init --workflow Interactively create a new workflow
208
+ init --provider <p> --model <m> Write default config to .env (supports --base-url, --api-key)
207
209
  compose "desc" AI-compose a workflow from one sentence
208
210
  compose "desc" --run Compose and run immediately
209
211
  serve Start MCP server (for Claude Code / Cursor)
@@ -0,0 +1,9 @@
1
+ export declare function loadEnvFile(path?: string): Record<string, string>;
2
+ /**
3
+ * Merge KV pairs into .env (preserves existing lines, updates matching keys, appends new).
4
+ */
5
+ export declare function writeEnvFile(updates: Record<string, string>, path?: string): void;
6
+ /**
7
+ * Ensure .env is in .gitignore (only if .gitignore exists).
8
+ */
9
+ export declare function ensureEnvGitignored(cwd?: string): boolean;
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Minimal .env loader — no deps.
3
+ *
4
+ * Reads ./.env from cwd and populates process.env WITHOUT overwriting
5
+ * values already set by the shell. This preserves the precedence:
6
+ * shell env > .env > defaults
7
+ *
8
+ * Supported syntax:
9
+ * KEY=value
10
+ * KEY="value with spaces"
11
+ * KEY='value'
12
+ * # comment lines
13
+ * blank lines
14
+ *
15
+ * Not supported (keep it simple): multi-line values, variable expansion.
16
+ */
17
+ import { readFileSync, existsSync, writeFileSync, chmodSync } from 'node:fs';
18
+ import { resolve } from 'node:path';
19
+ const LINE_RE = /^\s*([A-Z_][A-Z0-9_]*)\s*=\s*(.*)\s*$/i;
20
+ export function loadEnvFile(path = resolve(process.cwd(), '.env')) {
21
+ if (!existsSync(path))
22
+ return {};
23
+ const raw = readFileSync(path, 'utf-8');
24
+ const out = {};
25
+ for (const rawLine of raw.split(/\r?\n/)) {
26
+ const line = rawLine.trim();
27
+ if (!line || line.startsWith('#'))
28
+ continue;
29
+ const m = line.match(LINE_RE);
30
+ if (!m)
31
+ continue;
32
+ const key = m[1];
33
+ let val = m[2];
34
+ // Strip inline comments (only when value isn't quoted)
35
+ if (!/^["']/.test(val)) {
36
+ const hashIdx = val.indexOf(' #');
37
+ if (hashIdx >= 0)
38
+ val = val.slice(0, hashIdx).trim();
39
+ }
40
+ // Unquote
41
+ if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
42
+ val = val.slice(1, -1);
43
+ }
44
+ out[key] = val;
45
+ // Shell env takes precedence — don't clobber
46
+ if (process.env[key] === undefined) {
47
+ process.env[key] = val;
48
+ }
49
+ }
50
+ return out;
51
+ }
52
+ /**
53
+ * Merge KV pairs into .env (preserves existing lines, updates matching keys, appends new).
54
+ */
55
+ export function writeEnvFile(updates, path = resolve(process.cwd(), '.env')) {
56
+ const existing = existsSync(path) ? readFileSync(path, 'utf-8') : '';
57
+ const lines = existing.split(/\r?\n/);
58
+ const seen = new Set();
59
+ const patched = lines.map(line => {
60
+ const m = line.match(LINE_RE);
61
+ if (!m)
62
+ return line;
63
+ const key = m[1];
64
+ if (key in updates) {
65
+ seen.add(key);
66
+ return `${key}=${quoteIfNeeded(updates[key])}`;
67
+ }
68
+ return line;
69
+ });
70
+ // Drop trailing blank lines for clean append
71
+ while (patched.length > 0 && patched[patched.length - 1].trim() === '')
72
+ patched.pop();
73
+ const toAppend = [];
74
+ for (const [k, v] of Object.entries(updates)) {
75
+ if (!seen.has(k))
76
+ toAppend.push(`${k}=${quoteIfNeeded(v)}`);
77
+ }
78
+ // Only add the header once — skip if the file already has it
79
+ const hasHeader = patched.some(l => l.trim() === '# Added by `ao init`');
80
+ const appendBlock = toAppend.length
81
+ ? (hasHeader ? ['', ...toAppend] : ['', '# Added by `ao init`', ...toAppend])
82
+ : [];
83
+ const final = [...patched, ...appendBlock, ''].join('\n');
84
+ writeFileSync(path, final, 'utf-8');
85
+ // Lock down — .env often contains API keys. Windows ignores chmod, so swallow errors.
86
+ try {
87
+ chmodSync(path, 0o600);
88
+ }
89
+ catch { /* non-POSIX fs */ }
90
+ }
91
+ function quoteIfNeeded(v) {
92
+ if (/[\s#"']/.test(v))
93
+ return `"${v.replace(/"/g, '\\"')}"`;
94
+ return v;
95
+ }
96
+ /**
97
+ * Ensure .env is in .gitignore (only if .gitignore exists).
98
+ */
99
+ export function ensureEnvGitignored(cwd = process.cwd()) {
100
+ const gitignorePath = resolve(cwd, '.gitignore');
101
+ if (!existsSync(gitignorePath))
102
+ return false;
103
+ const content = readFileSync(gitignorePath, 'utf-8');
104
+ const hasEnv = content.split(/\r?\n/).some(l => l.trim() === '.env');
105
+ if (hasEnv)
106
+ return false;
107
+ const appended = content + (content.endsWith('\n') ? '' : '\n') + '\n# agency-orchestrator local config\n.env\n';
108
+ writeFileSync(gitignorePath, appended, 'utf-8');
109
+ return true;
110
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agency-orchestrator",
3
- "version": "0.5.3",
3
+ "version": "0.6.1",
4
4
  "description": "Multi-agent YAML workflow engine — 211 AI roles, auto DAG parallelism, zero code. One sentence → multiple AI roles collaborate → complete plan in minutes. 10 LLM providers, 7 need no API key.",
5
5
  "keywords": [
6
6
  "multi-agent",