Repository navigation
Expand file tree
/
Copy pathconfigStore.ts
More file actions
114 lines (105 loc) · 3.15 KB
/
Copy pathconfigStore.ts
File metadata and controls
114 lines (105 loc) · 3.15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
/**
* Config store — AGENTSTACK_HOME or ~/.agentstack (Windows: %USERPROFILE%\.agentstack).
* Genetic tag: repo.tooling.user_cli.gen1
*/
import { chmod, mkdir, readFile, rename, writeFile } from 'node:fs/promises';
import { homedir, platform } from 'node:os';
import { join } from 'node:path';
export interface CliProfile {
apiKey?: string;
projectId?: number;
}
export interface CliConfig {
apiBase: string;
mcpUrl?: string;
defaultProjectId?: number;
activeProfile: string;
profiles: Record<string, CliProfile>;
output?: 'human' | 'json' | 'quiet';
}
const DEFAULTS: CliConfig = {
apiBase: 'https://agentstack.tech/api',
activeProfile: 'default',
profiles: { default: {} },
output: 'human',
};
export function resolveAgentStackHome(
env: NodeJS.ProcessEnv = process.env,
): string {
const fromEnv = env.AGENTSTACK_HOME?.trim();
if (fromEnv) return fromEnv;
return join(homedir(), '.agentstack');
}
export function configPath(home = resolveAgentStackHome()): string {
return join(home, 'config.json');
}
export async function loadConfig(
home = resolveAgentStackHome(),
): Promise<CliConfig> {
try {
const raw = await readFile(configPath(home), 'utf8');
const parsed = JSON.parse(raw) as Partial<CliConfig>;
return {
...DEFAULTS,
...parsed,
profiles: { ...DEFAULTS.profiles, ...(parsed.profiles || {}) },
};
} catch {
return { ...DEFAULTS, profiles: { default: {} } };
}
}
export async function saveConfig(
cfg: CliConfig,
home = resolveAgentStackHome(),
env: NodeJS.ProcessEnv = process.env,
): Promise<void> {
if (env.AGENTSTACK_CONFIG_READONLY === '1') {
throw new Error(
'config is read-only (AGENTSTACK_CONFIG_READONLY=1) — unset to write secrets',
);
}
await mkdir(home, { recursive: true });
const target = configPath(home);
const tmp = `${target}.${process.pid}.tmp`;
await writeFile(tmp, `${JSON.stringify(cfg, null, 2)}\n`, 'utf8');
await rename(tmp, target);
if (platform() !== 'win32') {
try {
await chmod(target, 0o600);
} catch {
/* best-effort */
}
}
}
export function mergeEnvOverrides(
cfg: CliConfig,
env: NodeJS.ProcessEnv = process.env,
): {
apiBase: string;
mcpUrl: string | undefined;
apiKey: string | undefined;
projectId: number | undefined;
profile: string;
} {
const profileName =
env.AGENTSTACK_PROFILE?.trim() || cfg.activeProfile || 'default';
const profile = cfg.profiles[profileName] || {};
const apiKey =
env.AGENTSTACK_API_KEY?.trim() ||
env.AGENTSTACK_ACCESS_TOKEN?.trim() ||
env.AGENTSTACK_TOKEN?.trim() ||
profile.apiKey;
const projectRaw =
env.AGENTSTACK_PROJECT_ID?.trim() ||
(profile.projectId != null ? String(profile.projectId) : undefined) ||
(cfg.defaultProjectId != null ? String(cfg.defaultProjectId) : undefined);
const projectId = projectRaw ? Number(projectRaw) : undefined;
return {
apiBase: env.AGENTSTACK_API_BASE?.trim() || cfg.apiBase || DEFAULTS.apiBase,
mcpUrl: env.AGENTSTACK_MCP_URL?.trim() || cfg.mcpUrl,
apiKey,
projectId:
projectId != null && Number.isFinite(projectId) ? projectId : undefined,
profile: profileName,
};
}