@borgee/agents-host 0.1.5 → 0.1.7

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-args.js CHANGED
@@ -1,8 +1,8 @@
1
1
  /**
2
- * Maps `agents-host start` CLI flags to the env vars `config.ts` reads.
3
- * Keeping this as a plain lookup table (rather than duplicating
4
- * `loadConfigFromEnv`'s parsing logic) means the CLI and the plain
5
- * env-var entry point (`index.ts`) always agree on defaults/validation.
2
+ * Maps single-agent `agents-host start` CLI flags to the env vars `config.ts`
3
+ * reads. Keeping this as a plain lookup table (rather than duplicating
4
+ * `loadConfigFromEnv`'s parsing logic) means the CLI and the env-var entry
5
+ * point (`index.ts`) always agree on defaults/validation.
6
6
  */
7
7
  export const CLI_FLAG_TO_ENV = {
8
8
  name: 'BORGEE_AGENT_NAME',
@@ -11,56 +11,246 @@ export const CLI_FLAG_TO_ENV = {
11
11
  'claude-args': 'CLAUDE_ARGS',
12
12
  'copilot-command': 'COPILOT_COMMAND',
13
13
  'copilot-args': 'COPILOT_ARGS',
14
+ 'copilot-session-ttl-minutes': 'COPILOT_SESSION_TTL_MINUTES',
14
15
  };
16
+ const SINGLE_AGENT_FLAG_NAMES = new Set(Object.keys(CLI_FLAG_TO_ENV));
15
17
  export class CliUsageError extends Error {
16
18
  }
17
- /**
18
- * Parses `start <serverUrl> <apiKey> [--flag value ...]` (the argv slice
19
- * after the `start` command word). Throws `CliUsageError` with a
20
- * human-readable message on any usage problem instead of exiting the
21
- * process, so callers (and tests) can decide how to report it.
22
- */
23
19
  export function parseStartArgs(argv) {
24
20
  const positionals = [];
25
21
  const env = {};
22
+ const seenSingleAgentFlags = new Set();
23
+ let configPath;
26
24
  for (let i = 0; i < argv.length; i++) {
27
25
  const arg = argv[i];
28
- if (arg.startsWith('--')) {
29
- const flag = arg.slice(2);
30
- const value = argv[i + 1];
31
- if (value === undefined || value.startsWith('--')) {
32
- throw new CliUsageError(`Missing value for --${flag}`);
33
- }
34
- const envKey = CLI_FLAG_TO_ENV[flag];
35
- if (!envKey) {
36
- throw new CliUsageError(`Unknown option: --${flag}`);
26
+ if (!arg.startsWith('--')) {
27
+ positionals.push(arg);
28
+ continue;
29
+ }
30
+ const flag = arg.slice(2);
31
+ const value = argv[i + 1];
32
+ if (value === undefined || value.startsWith('--')) {
33
+ throw new CliUsageError(`Missing value for --${flag}`);
34
+ }
35
+ if (flag === 'config') {
36
+ if (configPath !== undefined) {
37
+ throw new CliUsageError('--config may only be specified once');
37
38
  }
38
- env[envKey] = value;
39
+ configPath = value;
39
40
  i++;
41
+ continue;
40
42
  }
41
- else {
42
- positionals.push(arg);
43
+ const envKey = CLI_FLAG_TO_ENV[flag];
44
+ if (!envKey) {
45
+ throw new CliUsageError(`Unknown option: --${flag}`);
43
46
  }
47
+ env[envKey] = value;
48
+ seenSingleAgentFlags.add(flag);
49
+ i++;
50
+ }
51
+ if (configPath !== undefined) {
52
+ if (positionals.length > 0) {
53
+ throw new CliUsageError('Cannot combine --config with <serverUrl> <apiKey>');
54
+ }
55
+ if (seenSingleAgentFlags.size > 0) {
56
+ throw new CliUsageError(`Cannot combine --config with single-agent option --${[...seenSingleAgentFlags][0]}`);
57
+ }
58
+ return {
59
+ mode: 'local-config',
60
+ configPath,
61
+ };
44
62
  }
45
63
  const [serverUrl, apiKey, ...extra] = positionals;
46
- if (!serverUrl)
64
+ if (!serverUrl) {
47
65
  throw new CliUsageError('Missing <serverUrl>');
48
- if (!apiKey)
66
+ }
67
+ if (!apiKey) {
49
68
  throw new CliUsageError('Missing <apiKey>');
50
- if (extra.length > 0)
69
+ }
70
+ if (extra.length > 0) {
51
71
  throw new CliUsageError(`Unexpected argument: ${extra[0]}`);
52
- return { serverUrl, apiKey, env };
72
+ }
73
+ return {
74
+ mode: 'single-agent',
75
+ serverUrl,
76
+ apiKey,
77
+ env,
78
+ };
79
+ }
80
+ export function parseValidateArgs(argv) {
81
+ const positionals = [];
82
+ let configPath;
83
+ for (let i = 0; i < argv.length; i++) {
84
+ const arg = argv[i];
85
+ if (!arg.startsWith('--')) {
86
+ positionals.push(arg);
87
+ continue;
88
+ }
89
+ const flag = arg.slice(2);
90
+ if (flag === 'config') {
91
+ const value = argv[i + 1];
92
+ if (value === undefined || value.startsWith('--')) {
93
+ throw new CliUsageError('Missing value for --config');
94
+ }
95
+ if (configPath !== undefined) {
96
+ throw new CliUsageError('--config may only be specified once');
97
+ }
98
+ configPath = value;
99
+ i++;
100
+ continue;
101
+ }
102
+ if (SINGLE_AGENT_FLAG_NAMES.has(flag)) {
103
+ throw new CliUsageError(`Validate accepts only --config; cannot use single-agent option --${flag}`);
104
+ }
105
+ throw new CliUsageError(`Unknown option: --${flag}`);
106
+ }
107
+ if (positionals.length > 0) {
108
+ throw new CliUsageError(`Unexpected argument: ${positionals[0]}`);
109
+ }
110
+ if (!configPath) {
111
+ throw new CliUsageError('Missing required --config <path>');
112
+ }
113
+ return { configPath };
53
114
  }
54
- export const USAGE = `Usage: agents-host start <serverUrl> <apiKey> [options]
115
+ export function parseDescribeArgs(argv) {
116
+ const parsed = parseValidateArgs(argv);
117
+ return { configPath: parsed.configPath };
118
+ }
119
+ export function parsePrintLayoutArgs(argv) {
120
+ const positionals = [];
121
+ let rootPath;
122
+ for (let i = 0; i < argv.length; i++) {
123
+ const arg = argv[i];
124
+ if (!arg.startsWith('--')) {
125
+ positionals.push(arg);
126
+ continue;
127
+ }
128
+ const flag = arg.slice(2);
129
+ if (flag === 'root') {
130
+ const value = argv[i + 1];
131
+ if (value === undefined || value.startsWith('--')) {
132
+ throw new CliUsageError('Missing value for --root');
133
+ }
134
+ if (rootPath !== undefined) {
135
+ throw new CliUsageError('--root may only be specified once');
136
+ }
137
+ rootPath = value;
138
+ i++;
139
+ continue;
140
+ }
141
+ if (flag === 'config') {
142
+ throw new CliUsageError('print-layout accepts only --root; cannot use --config');
143
+ }
144
+ if (SINGLE_AGENT_FLAG_NAMES.has(flag)) {
145
+ throw new CliUsageError(`print-layout accepts only --root; cannot use single-agent option --${flag}`);
146
+ }
147
+ throw new CliUsageError(`Unknown option: --${flag}`);
148
+ }
149
+ if (positionals.length > 0) {
150
+ throw new CliUsageError(`Unexpected argument: ${positionals[0]}`);
151
+ }
152
+ if (!rootPath) {
153
+ throw new CliUsageError('Missing required --root <dir>');
154
+ }
155
+ return { rootPath };
156
+ }
157
+ export function parseGenerateConfigArgs(argv) {
158
+ const positionals = [];
159
+ let rootPath;
160
+ let specJson;
161
+ let stdin = false;
162
+ for (let i = 0; i < argv.length; i++) {
163
+ const arg = argv[i];
164
+ if (!arg.startsWith('--')) {
165
+ positionals.push(arg);
166
+ continue;
167
+ }
168
+ const flag = arg.slice(2);
169
+ if (flag === 'stdin') {
170
+ if (stdin) {
171
+ throw new CliUsageError('--stdin may only be specified once');
172
+ }
173
+ stdin = true;
174
+ continue;
175
+ }
176
+ if (flag === 'root' || flag === 'spec-json') {
177
+ const value = argv[i + 1];
178
+ if (value === undefined || value.startsWith('--')) {
179
+ throw new CliUsageError(`Missing value for --${flag}`);
180
+ }
181
+ if (flag === 'root') {
182
+ if (rootPath !== undefined) {
183
+ throw new CliUsageError('--root may only be specified once');
184
+ }
185
+ rootPath = value;
186
+ }
187
+ else {
188
+ if (specJson !== undefined) {
189
+ throw new CliUsageError('--spec-json may only be specified once');
190
+ }
191
+ specJson = value;
192
+ }
193
+ i++;
194
+ continue;
195
+ }
196
+ if (flag === 'config') {
197
+ throw new CliUsageError('generate-config accepts only --root and --spec-json; cannot use --config');
198
+ }
199
+ if (SINGLE_AGENT_FLAG_NAMES.has(flag)) {
200
+ throw new CliUsageError(`generate-config accepts only --root and --spec-json; cannot use single-agent option --${flag}`);
201
+ }
202
+ throw new CliUsageError(`Unknown option: --${flag}`);
203
+ }
204
+ if (positionals.length > 0) {
205
+ throw new CliUsageError(`Unexpected argument: ${positionals[0]}`);
206
+ }
207
+ if (!rootPath) {
208
+ throw new CliUsageError('Missing required --root <dir>');
209
+ }
210
+ if (stdin && specJson !== undefined) {
211
+ throw new CliUsageError('generate-config accepts exactly one input source: --stdin or --spec-json');
212
+ }
213
+ if (stdin) {
214
+ return { rootPath, input: 'stdin' };
215
+ }
216
+ if (!specJson) {
217
+ throw new CliUsageError('Missing required input: use --stdin or --spec-json <json>');
218
+ }
219
+ return { rootPath, input: 'argv', specJson };
220
+ }
221
+ export const USAGE = `Usage:
222
+ agents-host start <serverUrl> <apiKey> [options]
223
+ agents-host start --config <path-to-host-config>
224
+ agents-host validate --config <path-to-host-config>
225
+ agents-host describe --config <path-to-host-config>
226
+ agents-host print-layout --root <dir>
227
+ agents-host generate-config --root <dir> --stdin
228
+ agents-host generate-config --root <dir> --spec-json <json>
229
+
230
+ Single-agent options:
231
+ --name <name> Display name (default: Assistant)
232
+ --provider <claude|copilot> Runtime provider (default: claude)
233
+ --claude-command <cmd> Local Claude CLI command (default: claude)
234
+ --claude-args <args> Local Claude CLI args (default: --print)
235
+ --copilot-command <cmd> Local Copilot CLI command (default: copilot)
236
+ --copilot-args <args> Ignored by the Copilot ACP prototype
237
+ --copilot-session-ttl-minutes <minutes>
238
+ Idle session TTL for Copilot ACP sessions (default: 2880)
55
239
 
56
- Options:
57
- --name <name> Display name (default: Assistant)
58
- --provider <claude|copilot> Runtime provider (default: claude)
59
- --claude-command <cmd> Local Claude CLI command (default: claude)
60
- --claude-args <args> Local Claude CLI args (default: --print)
61
- --copilot-command <cmd> Local Copilot CLI command (default: copilot)
62
- --copilot-args <args> Local Copilot CLI args
240
+ Local-config mode:
241
+ start --config <path> Start agents + supervisor + watchers from a host config file
242
+ validate --config <path> Validate local-config files without starting agents or watchers
243
+ describe --config <path> Print the current managed full-set spec as JSON
244
+ print-layout --root <dir> Print the canonical default local-config layout as JSON
245
+ generate-config --root <dir> --stdin Materialize canonical local-config files from stdin
246
+ generate-config --root <dir> --spec-json <json>
247
+ Compatibility input; JSON is exposed in process arguments
63
248
 
64
- Example:
249
+ Examples:
65
250
  agents-host start https://borgee.example.com bgr_xxxxxxxx --provider copilot
251
+ agents-host start --config ./agents-host.yaml
252
+ agents-host validate --config ./agents-host.yaml
253
+ agents-host describe --config ./agents-host.yaml
254
+ agents-host print-layout --root ./runtime-root
255
+ printf '%s' '{"host":{"borgeeBaseUrl":"https://borgee.example.com"},"agents":[{"key":"cp1","name":"Copilot","apiKey":"bgr_xxx","provider":"copilot"}]}' | agents-host generate-config --root ./runtime-root --stdin
66
256
  `;
package/dist/cli.d.ts CHANGED
@@ -1,2 +1,14 @@
1
1
  #!/usr/bin/env node
2
- export {};
2
+ import { describeLocalConfig, generateLocalConfig, printLocalConfigLayout, runMain, validateLocalConfig } from './run.js';
3
+ export interface CliDeps {
4
+ env?: NodeJS.ProcessEnv;
5
+ logger?: Pick<Console, 'error'>;
6
+ runMain?: typeof runMain;
7
+ validateLocalConfig?: typeof validateLocalConfig;
8
+ describeLocalConfig?: typeof describeLocalConfig;
9
+ printLocalConfigLayout?: typeof printLocalConfigLayout;
10
+ generateLocalConfig?: typeof generateLocalConfig;
11
+ readStdin?: () => Promise<string>;
12
+ }
13
+ export declare function dispatchCli(argv: string[], deps?: Omit<CliDeps, 'logger'>): Promise<void>;
14
+ export declare function main(argv?: string[], deps?: CliDeps): Promise<number>;
package/dist/cli.js CHANGED
@@ -1,34 +1,100 @@
1
1
  #!/usr/bin/env node
2
- import { CliUsageError, parseStartArgs, USAGE } from './cli-args.js';
3
- import { runMain } from './run.js';
4
- function fail(message) {
5
- if (message)
6
- console.error(`[agents-host] ${message}`);
7
- console.error(USAGE);
8
- process.exit(message ? 1 : 0);
2
+ import { resolve } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { CliUsageError, parseDescribeArgs, parseGenerateConfigArgs, parsePrintLayoutArgs, parseStartArgs, parseValidateArgs, USAGE, } from './cli-args.js';
5
+ import { describeLocalConfig, generateLocalConfig, printLocalConfigLayout, runMain, validateLocalConfig } from './run.js';
6
+ function formatErrorMessage(error) {
7
+ if (error instanceof Error) {
8
+ return error.message;
9
+ }
10
+ return String(error);
9
11
  }
10
- const [command, ...rest] = process.argv.slice(2);
11
- if (command === undefined || command === '--help' || command === '-h') {
12
- fail();
12
+ export async function dispatchCli(argv, deps = {}) {
13
+ const [command, ...rest] = argv;
14
+ const env = deps.env ?? process.env;
15
+ const runMainImpl = deps.runMain ?? runMain;
16
+ const validateLocalConfigImpl = deps.validateLocalConfig ?? validateLocalConfig;
17
+ const describeLocalConfigImpl = deps.describeLocalConfig ?? describeLocalConfig;
18
+ const printLocalConfigLayoutImpl = deps.printLocalConfigLayout ?? printLocalConfigLayout;
19
+ const generateLocalConfigImpl = deps.generateLocalConfig ?? generateLocalConfig;
20
+ const readStdin = deps.readStdin ?? readAllStdin;
21
+ if (command === 'start') {
22
+ const parsed = parseStartArgs(rest);
23
+ if (parsed.mode === 'single-agent') {
24
+ env.BORGEE_BASE_URL = parsed.serverUrl;
25
+ env.BORGEE_AGENT_API_KEY = parsed.apiKey;
26
+ for (const [key, value] of Object.entries(parsed.env)) {
27
+ env[key] = value;
28
+ }
29
+ await runMainImpl();
30
+ return;
31
+ }
32
+ await runMainImpl({ configPath: parsed.configPath });
33
+ return;
34
+ }
35
+ if (command === 'validate') {
36
+ const parsed = parseValidateArgs(rest);
37
+ await validateLocalConfigImpl(parsed.configPath);
38
+ return;
39
+ }
40
+ if (command === 'describe') {
41
+ const parsed = parseDescribeArgs(rest);
42
+ await describeLocalConfigImpl(parsed.configPath);
43
+ return;
44
+ }
45
+ if (command === 'print-layout') {
46
+ const parsed = parsePrintLayoutArgs(rest);
47
+ await printLocalConfigLayoutImpl(parsed.rootPath);
48
+ return;
49
+ }
50
+ if (command === 'generate-config') {
51
+ const parsed = parseGenerateConfigArgs(rest);
52
+ if (parsed.input === 'stdin') {
53
+ await generateLocalConfigImpl(parsed.rootPath, await readStdin(), '<stdin>');
54
+ }
55
+ else {
56
+ await generateLocalConfigImpl(parsed.rootPath, parsed.specJson);
57
+ }
58
+ return;
59
+ }
60
+ throw new CliUsageError(`Unknown command: ${command}`);
13
61
  }
14
- if (command !== 'start') {
15
- fail(`Unknown command: ${command}`);
62
+ async function readAllStdin() {
63
+ const chunks = [];
64
+ for await (const chunk of process.stdin) {
65
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
66
+ }
67
+ return Buffer.concat(chunks).toString('utf8');
16
68
  }
17
- try {
18
- const { serverUrl, apiKey, env } = parseStartArgs(rest);
19
- process.env.BORGEE_BASE_URL = serverUrl;
20
- process.env.BORGEE_AGENT_API_KEY = apiKey;
21
- for (const [key, value] of Object.entries(env)) {
22
- process.env[key] = value;
69
+ export async function main(argv = process.argv.slice(2), deps = {}) {
70
+ const logger = deps.logger ?? console;
71
+ const [command] = argv;
72
+ if (command === undefined || command === '--help' || command === '-h') {
73
+ logger.error(USAGE);
74
+ return 0;
75
+ }
76
+ try {
77
+ await dispatchCli(argv, deps);
78
+ return 0;
79
+ }
80
+ catch (error) {
81
+ if (error instanceof CliUsageError) {
82
+ logger.error(`[agents-host] ${error.message}`);
83
+ logger.error(USAGE);
84
+ return 1;
85
+ }
86
+ logger.error(`[agents-host] ${formatErrorMessage(error)}`, error);
87
+ return 1;
23
88
  }
24
89
  }
25
- catch (error) {
26
- if (error instanceof CliUsageError) {
27
- fail(error.message);
90
+ function isCliEntrypoint(importMetaUrl, argv1 = process.argv[1]) {
91
+ if (!argv1) {
92
+ return false;
28
93
  }
29
- throw error;
94
+ return fileURLToPath(importMetaUrl) === resolve(argv1);
95
+ }
96
+ if (isCliEntrypoint(import.meta.url)) {
97
+ void main().then((exitCode) => {
98
+ process.exitCode = exitCode;
99
+ });
30
100
  }
31
- runMain().catch((error) => {
32
- console.error('[agents-host] fatal error:', error);
33
- process.exitCode = 1;
34
- });
package/dist/config.d.ts CHANGED
@@ -1,7 +1,12 @@
1
- import type { AgentsHostConfig } from './types.js';
2
- /**
3
- * Loads agents-host configuration from environment variables. This process
4
- * hosts exactly one Borgee agent (`BORGEE_AGENT_API_KEY`) — to run more
5
- * agents, run more processes with different env vars.
6
- */
7
- export declare function loadConfigFromEnv(): AgentsHostConfig;
1
+ import type { AgentsHostConfig, ProviderCommandConfig, ProviderKind } from './types.js';
2
+ export declare const MAX_COPILOT_SESSION_TTL_MINUTES: number;
3
+ export declare const DEFAULT_COPILOT_SESSION_TTL_MINUTES: number;
4
+ export declare const DEFAULT_PROVIDER_COMMAND_CONFIG: ProviderCommandConfig;
5
+ export declare function requireNonEmptyString(value: unknown, message: string): string;
6
+ export declare function optionalNonEmptyString(value: unknown, fieldName: string, sourceLabel: string): string | undefined;
7
+ export declare function parseArgs(value: string): string[];
8
+ export declare function optionalStringArray(value: unknown, fieldName: string, sourceLabel: string): string[] | undefined;
9
+ export declare function parseCopilotSessionTtlMinutesValue(value: unknown, sourceLabel: string): number;
10
+ export declare function resolveProvider(rawValue: string, sourceLabel: string): ProviderKind;
11
+ export declare function resolveProviderCommandConfig(overrides?: Partial<ProviderCommandConfig>): ProviderCommandConfig;
12
+ export declare function loadConfigFromEnv(env?: NodeJS.ProcessEnv): AgentsHostConfig;
package/dist/config.js CHANGED
@@ -1,39 +1,92 @@
1
- function requireEnv(name) {
2
- const value = process.env[name];
3
- if (!value || value.trim().length === 0) {
4
- throw new Error(`Missing required environment variable: ${name}`);
1
+ const MAX_TIMER_DELAY_MS = 2_147_483_647;
2
+ export const MAX_COPILOT_SESSION_TTL_MINUTES = MAX_TIMER_DELAY_MS / 60_000;
3
+ export const DEFAULT_COPILOT_SESSION_TTL_MINUTES = 2 * 24 * 60;
4
+ export const DEFAULT_PROVIDER_COMMAND_CONFIG = {
5
+ claudeCommand: 'claude',
6
+ claudeArgs: ['--print'],
7
+ copilotCommand: 'copilot',
8
+ copilotArgs: ['-s', '--no-color', '--allow-all-tools', '--output-format', 'text'],
9
+ copilotSessionTtlMinutes: DEFAULT_COPILOT_SESSION_TTL_MINUTES,
10
+ };
11
+ function requireEnv(name, env) {
12
+ return requireNonEmptyString(env[name], `Missing required environment variable: ${name}`);
13
+ }
14
+ function envOr(name, fallback, env) {
15
+ const value = env[name];
16
+ return value && value.trim().length > 0 ? value.trim() : fallback;
17
+ }
18
+ export function requireNonEmptyString(value, message) {
19
+ if (typeof value !== 'string' || value.trim().length === 0) {
20
+ throw new Error(message);
5
21
  }
6
22
  return value.trim();
7
23
  }
8
- function envOr(name, fallback) {
9
- const value = process.env[name];
10
- return value && value.trim().length > 0 ? value.trim() : fallback;
24
+ export function optionalNonEmptyString(value, fieldName, sourceLabel) {
25
+ if (value === undefined || value === null) {
26
+ return undefined;
27
+ }
28
+ return requireNonEmptyString(value, `Invalid ${fieldName} in ${sourceLabel}: expected a non-empty string`);
11
29
  }
12
- function parseArgs(value) {
30
+ export function parseArgs(value) {
13
31
  return value.trim().length > 0 ? value.trim().split(/\s+/) : [];
14
32
  }
15
- function resolveProvider() {
16
- const raw = envOr('RUNTIME_PROVIDER', 'claude').toLowerCase();
17
- if (raw === 'claude' || raw === 'copilot')
33
+ export function optionalStringArray(value, fieldName, sourceLabel) {
34
+ if (value === undefined || value === null) {
35
+ return undefined;
36
+ }
37
+ if (!Array.isArray(value)) {
38
+ throw new Error(`Invalid ${fieldName} in ${sourceLabel}: expected an array of strings`);
39
+ }
40
+ return value.map((entry, index) => {
41
+ if (typeof entry !== 'string' || entry.trim().length === 0) {
42
+ throw new Error(`Invalid ${fieldName}[${index}] in ${sourceLabel}: expected a non-empty string`);
43
+ }
44
+ return entry.trim();
45
+ });
46
+ }
47
+ export function parseCopilotSessionTtlMinutesValue(value, sourceLabel) {
48
+ const parsed = typeof value === 'number' ? value : Number(String(value).trim());
49
+ if (!Number.isFinite(parsed) || parsed <= 0) {
50
+ throw new Error(`Invalid COPILOT_SESSION_TTL_MINUTES in ${sourceLabel}: expected a positive number`);
51
+ }
52
+ if (parsed > MAX_COPILOT_SESSION_TTL_MINUTES) {
53
+ throw new Error(`Invalid COPILOT_SESSION_TTL_MINUTES in ${sourceLabel}: must be <= ${MAX_COPILOT_SESSION_TTL_MINUTES.toFixed(2)} minutes to fit within the Node.js timer limit`);
54
+ }
55
+ return parsed;
56
+ }
57
+ export function resolveProvider(rawValue, sourceLabel) {
58
+ const raw = rawValue.trim().toLowerCase();
59
+ if (raw === 'claude' || raw === 'copilot') {
18
60
  return raw;
19
- throw new Error(`Unsupported RUNTIME_PROVIDER: ${raw} (expected "claude" or "copilot")`);
20
- }
21
- /**
22
- * Loads agents-host configuration from environment variables. This process
23
- * hosts exactly one Borgee agent (`BORGEE_AGENT_API_KEY`) — to run more
24
- * agents, run more processes with different env vars.
25
- */
26
- export function loadConfigFromEnv() {
61
+ }
62
+ throw new Error(`${sourceLabel}: expected "claude" or "copilot", got "${rawValue}"`);
63
+ }
64
+ export function resolveProviderCommandConfig(overrides = {}) {
65
+ return {
66
+ claudeCommand: overrides.claudeCommand ?? DEFAULT_PROVIDER_COMMAND_CONFIG.claudeCommand,
67
+ claudeArgs: [...(overrides.claudeArgs ?? DEFAULT_PROVIDER_COMMAND_CONFIG.claudeArgs)],
68
+ copilotCommand: overrides.copilotCommand ?? DEFAULT_PROVIDER_COMMAND_CONFIG.copilotCommand,
69
+ copilotArgs: [...(overrides.copilotArgs ?? DEFAULT_PROVIDER_COMMAND_CONFIG.copilotArgs)],
70
+ copilotSessionTtlMinutes: overrides.copilotSessionTtlMinutes ?? DEFAULT_PROVIDER_COMMAND_CONFIG.copilotSessionTtlMinutes,
71
+ };
72
+ }
73
+ export function loadConfigFromEnv(env = process.env) {
74
+ const provider = resolveProvider(envOr('RUNTIME_PROVIDER', 'claude', env), 'Unsupported RUNTIME_PROVIDER');
27
75
  return {
28
- borgeeBaseUrl: requireEnv('BORGEE_BASE_URL'),
29
- claudeCommand: envOr('CLAUDE_COMMAND', 'claude'),
30
- claudeArgs: parseArgs(envOr('CLAUDE_ARGS', '--print')),
31
- copilotCommand: envOr('COPILOT_COMMAND', 'copilot'),
32
- copilotArgs: parseArgs(envOr('COPILOT_ARGS', '-s --no-color --allow-all-tools --output-format text')),
76
+ borgeeBaseUrl: requireEnv('BORGEE_BASE_URL', env),
77
+ ...resolveProviderCommandConfig({
78
+ claudeCommand: envOr('CLAUDE_COMMAND', DEFAULT_PROVIDER_COMMAND_CONFIG.claudeCommand, env),
79
+ claudeArgs: parseArgs(envOr('CLAUDE_ARGS', DEFAULT_PROVIDER_COMMAND_CONFIG.claudeArgs.join(' '), env)),
80
+ copilotCommand: envOr('COPILOT_COMMAND', DEFAULT_PROVIDER_COMMAND_CONFIG.copilotCommand, env),
81
+ copilotArgs: parseArgs(envOr('COPILOT_ARGS', DEFAULT_PROVIDER_COMMAND_CONFIG.copilotArgs.join(' '), env)),
82
+ copilotSessionTtlMinutes: env.COPILOT_SESSION_TTL_MINUTES && env.COPILOT_SESSION_TTL_MINUTES.trim().length > 0
83
+ ? parseCopilotSessionTtlMinutesValue(env.COPILOT_SESSION_TTL_MINUTES, 'environment variable COPILOT_SESSION_TTL_MINUTES')
84
+ : DEFAULT_PROVIDER_COMMAND_CONFIG.copilotSessionTtlMinutes,
85
+ }),
33
86
  agent: {
34
- agentApiKey: requireEnv('BORGEE_AGENT_API_KEY'),
35
- agentName: envOr('BORGEE_AGENT_NAME', 'Assistant'),
36
- provider: resolveProvider(),
87
+ agentApiKey: requireEnv('BORGEE_AGENT_API_KEY', env),
88
+ agentName: envOr('BORGEE_AGENT_NAME', 'Assistant', env),
89
+ provider,
37
90
  },
38
91
  };
39
92
  }
@@ -0,0 +1,51 @@
1
+ import type { LocalConfigGenerateResult, LocalConfigGenerateSpec, LocalConfigSnapshot } from './types.js';
2
+ export declare const DEFAULT_LOCAL_HOST_CONFIG_FILENAME = "agents-host.yaml";
3
+ export declare const DEFAULT_LOCAL_AGENTS_DIRNAME = "agents";
4
+ export declare const MANAGED_WRITE_LOCK_DIRNAME = ".generate-config.lock";
5
+ export interface LocalConfigLayout {
6
+ root: string;
7
+ hostConfigPath: string;
8
+ agentsDir: string;
9
+ }
10
+ export interface LocalConfigDirEntry {
11
+ name: string;
12
+ isFile: boolean;
13
+ }
14
+ export interface LocalConfigFileSystem {
15
+ readFile(path: string): Promise<string>;
16
+ readDir(path: string): Promise<LocalConfigDirEntry[]>;
17
+ realPath?(path: string): Promise<string>;
18
+ }
19
+ interface ManagedPathStatus {
20
+ isDirectory: boolean;
21
+ isSymbolicLink: boolean;
22
+ mtimeMs: number;
23
+ }
24
+ export interface ManagedLocalConfigFileSystem extends LocalConfigFileSystem {
25
+ writeFile(path: string, content: string): Promise<void>;
26
+ mkdir(path: string, options?: {
27
+ recursive?: boolean;
28
+ mode?: number;
29
+ }): Promise<void>;
30
+ removeFile(path: string): Promise<void>;
31
+ removeTree(path: string): Promise<void>;
32
+ rename(from: string, to: string): Promise<void>;
33
+ symlink(target: string, path: string): Promise<void>;
34
+ readLink(path: string): Promise<string>;
35
+ lstat(path: string): Promise<ManagedPathStatus>;
36
+ chmod(path: string, mode: number): Promise<void>;
37
+ }
38
+ export declare function resolveLocalConfigLayout(rootPath: string): LocalConfigLayout;
39
+ export declare function parseGenerateConfigSpec(value: unknown, sourceLabel: string): LocalConfigGenerateSpec;
40
+ export declare function loadLocalConfigSnapshot(hostConfigPath: string, deps?: {
41
+ fileSystem?: LocalConfigFileSystem;
42
+ acquireManagedGenerationLease?: boolean;
43
+ }): Promise<LocalConfigSnapshot>;
44
+ export declare function loadLocalConfigGenerateSpec(hostConfigPath: string, deps?: {
45
+ fileSystem?: LocalConfigFileSystem;
46
+ acquireManagedGenerationLease?: boolean;
47
+ }): Promise<LocalConfigGenerateSpec>;
48
+ export declare function materializeLocalConfig(rootPath: string, spec: LocalConfigGenerateSpec, deps?: {
49
+ fileSystem?: ManagedLocalConfigFileSystem;
50
+ }): Promise<LocalConfigGenerateResult>;
51
+ export {};