agency-orchestrator 0.5.3 → 0.6.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.
- package/dist/cli.js +50 -2
- 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
|
@@ -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'
|
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.0",
|
|
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",
|