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 +80 -4
- package/dist/i18n.js +2 -0
- package/dist/utils/env-loader.d.ts +9 -0
- package/dist/utils/env-loader.js +110 -0
- package/package.json +1 -1
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
|
-
|
|
97
|
-
const
|
|
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
|
|
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.
|
|
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",
|