fraim-hub 2.0.204

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.
Files changed (120) hide show
  1. package/README.md +467 -0
  2. package/bin/fraim-hub.js +17 -0
  3. package/dist/src/ai-hub/brand-store.js +17 -0
  4. package/dist/src/ai-hub/catalog.js +412 -0
  5. package/dist/src/ai-hub/cert-store.js +70 -0
  6. package/dist/src/ai-hub/cli.js +212 -0
  7. package/dist/src/ai-hub/configured-agents.js +283 -0
  8. package/dist/src/ai-hub/conversation-store.js +428 -0
  9. package/dist/src/ai-hub/desktop-main.js +449 -0
  10. package/dist/src/ai-hub/hosts.js +1669 -0
  11. package/dist/src/ai-hub/hub-latest-version.js +52 -0
  12. package/dist/src/ai-hub/hub-launch-decision.js +57 -0
  13. package/dist/src/ai-hub/hub-runtime-file.js +43 -0
  14. package/dist/src/ai-hub/managed-browser.js +269 -0
  15. package/dist/src/ai-hub/manager-turns.js +64 -0
  16. package/dist/src/ai-hub/office-sideload.js +156 -0
  17. package/dist/src/ai-hub/openclaw-bridge.js +250 -0
  18. package/dist/src/ai-hub/preferences.js +201 -0
  19. package/dist/src/ai-hub/remote-hub-gateway.js +88 -0
  20. package/dist/src/ai-hub/server.js +4013 -0
  21. package/dist/src/ai-hub/types.js +2 -0
  22. package/dist/src/ai-hub/url-safety.js +19 -0
  23. package/dist/src/api/admin/payments.js +33 -0
  24. package/dist/src/api/admin/sales-leads.js +21 -0
  25. package/dist/src/api/payment/create-session.js +338 -0
  26. package/dist/src/api/payment/dashboard-link.js +149 -0
  27. package/dist/src/api/payment/session-details.js +31 -0
  28. package/dist/src/api/payment/webhook.js +587 -0
  29. package/dist/src/api/sales/contact.js +44 -0
  30. package/dist/src/cli/api/get-provider-client.js +41 -0
  31. package/dist/src/cli/api/provider-client.js +107 -0
  32. package/dist/src/cli/commands/add-ide.js +462 -0
  33. package/dist/src/cli/fraim-hub.js +50 -0
  34. package/dist/src/cli/internal/device-flow-service.js +83 -0
  35. package/dist/src/cli/mcp/command-resolution.js +81 -0
  36. package/dist/src/cli/mcp/fraim-mcp-latest-launcher.js +136 -0
  37. package/dist/src/cli/mcp/ide-formats.js +317 -0
  38. package/dist/src/cli/mcp/mcp-server-builder.js +48 -0
  39. package/dist/src/cli/mcp/mcp-server-registry.js +173 -0
  40. package/dist/src/cli/providers/local-provider-registry.js +165 -0
  41. package/dist/src/cli/providers/provider-registry.js +230 -0
  42. package/dist/src/cli/setup/claude-code-telemetry.js +59 -0
  43. package/dist/src/cli/setup/codex-local-config.js +37 -0
  44. package/dist/src/cli/setup/ide-detector.js +383 -0
  45. package/dist/src/cli/setup/ide-global-integration.js +100 -0
  46. package/dist/src/cli/setup/ide-invocation-surfaces.js +160 -0
  47. package/dist/src/cli/setup/mcp-config-generator.js +225 -0
  48. package/dist/src/cli/setup/provider-prompts.js +339 -0
  49. package/dist/src/cli/utils/managed-agent-paths.js +118 -0
  50. package/dist/src/cli/utils/script-sync-utils.js +221 -0
  51. package/dist/src/cli/utils/user-config.js +100 -0
  52. package/dist/src/cli/utils/version-utils.js +35 -0
  53. package/dist/src/config/ai-manager-hiring.js +121 -0
  54. package/dist/src/config/feature-flags.js +25 -0
  55. package/dist/src/config/persona-capability-bundles.js +277 -0
  56. package/dist/src/config/persona-hiring.js +270 -0
  57. package/dist/src/config/portfolio-slug-overrides.js +17 -0
  58. package/dist/src/config/pricing.js +37 -0
  59. package/dist/src/config/stripe.js +43 -0
  60. package/dist/src/core/brand-store.js +232 -0
  61. package/dist/src/core/quality-evidence.js +331 -0
  62. package/dist/src/core/utils/git-utils.js +194 -0
  63. package/dist/src/core/utils/ports.js +32 -0
  64. package/dist/src/core/utils/project-fraim-paths.js +220 -0
  65. package/dist/src/db/payment-repository.js +61 -0
  66. package/dist/src/first-run/types.js +93 -0
  67. package/dist/src/fraim/db-service.js +2409 -0
  68. package/dist/src/local-mcp-server/agent-token-prices.js +167 -0
  69. package/dist/src/local-mcp-server/learning-context-builder.js +975 -0
  70. package/dist/src/middleware/rate-limit.js +110 -0
  71. package/dist/src/models/payment.js +2 -0
  72. package/dist/src/routes/payment-routes.js +186 -0
  73. package/dist/src/routes/persona-catalog-routes.js +84 -0
  74. package/dist/src/services/dashboard-access.js +27 -0
  75. package/dist/src/services/email-service.js +951 -0
  76. package/dist/src/services/persona-entitlement-service.js +352 -0
  77. package/dist/src/services/workspace-identity.js +21 -0
  78. package/dist/src/types/analytics.js +2 -0
  79. package/dist/src/utils/payment-calculator.js +52 -0
  80. package/extensions/office-word/favicon.ico +0 -0
  81. package/extensions/office-word/icon-64.png +0 -0
  82. package/extensions/office-word/manifest.xml +33 -0
  83. package/extensions/office-word/taskpane.html +242 -0
  84. package/index.js +85 -0
  85. package/package.json +106 -0
  86. package/public/ai-hub/index.html +910 -0
  87. package/public/ai-hub/powerpoint-taskpane/icon-64.png +0 -0
  88. package/public/ai-hub/powerpoint-taskpane/index.html +236 -0
  89. package/public/ai-hub/powerpoint-taskpane/manifest.xml +30 -0
  90. package/public/ai-hub/review.css +444 -0
  91. package/public/ai-hub/script.js +11737 -0
  92. package/public/ai-hub/styles.css +5102 -0
  93. package/public/first-run/error-frame.js +100 -0
  94. package/public/first-run/index.html +35 -0
  95. package/public/first-run/script.js +739 -0
  96. package/public/first-run/styles.css +929 -0
  97. package/public/portfolio/ashley.html +523 -0
  98. package/public/portfolio/auditya.html +83 -0
  99. package/public/portfolio/banke.html +83 -0
  100. package/public/portfolio/beza.html +659 -0
  101. package/public/portfolio/careena.html +632 -0
  102. package/public/portfolio/casey.html +568 -0
  103. package/public/portfolio/celia.html +490 -0
  104. package/public/portfolio/deidre.html +642 -0
  105. package/public/portfolio/gautam.html +597 -0
  106. package/public/portfolio/hari.html +469 -0
  107. package/public/portfolio/huxley.html +1354 -0
  108. package/public/portfolio/index.html +741 -0
  109. package/public/portfolio/maestro.html +518 -0
  110. package/public/portfolio/mandy.html +590 -0
  111. package/public/portfolio/mona.html +597 -0
  112. package/public/portfolio/pam.html +887 -0
  113. package/public/portfolio/procella.html +107 -0
  114. package/public/portfolio/qasm.html +569 -0
  115. package/public/portfolio/ricardo.html +489 -0
  116. package/public/portfolio/sade.html +560 -0
  117. package/public/portfolio/sam.html +654 -0
  118. package/public/portfolio/sechar.html +580 -0
  119. package/public/portfolio/sreya.html +599 -0
  120. package/public/portfolio/swen.html +601 -0
@@ -0,0 +1,212 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.runHub = runHub;
40
+ // Hub-owned launcher used by the fraim-hub package. Keep this outside
41
+ // src/cli/commands so the core fraim package has no Hub command implementation.
42
+ const git_utils_1 = require("../core/utils/git-utils");
43
+ const path_1 = __importDefault(require("path"));
44
+ const child_process_1 = require("child_process");
45
+ const fs_1 = __importDefault(require("fs"));
46
+ const net_1 = __importDefault(require("net"));
47
+ const http_1 = __importDefault(require("http"));
48
+ const hub_launch_decision_1 = require("./hub-launch-decision");
49
+ const hub_runtime_file_1 = require("./hub-runtime-file");
50
+ const project_fraim_paths_1 = require("../core/utils/project-fraim-paths");
51
+ const version_utils_1 = require("../cli/utils/version-utils");
52
+ function resolveElectronBinary() {
53
+ try {
54
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
55
+ return require('electron');
56
+ }
57
+ catch {
58
+ return null;
59
+ }
60
+ }
61
+ function resolveDesktopEntry() {
62
+ const candidates = [
63
+ path_1.default.resolve(__dirname, 'desktop-main.js'),
64
+ path_1.default.resolve(process.cwd(), 'dist', 'src', 'ai-hub', 'desktop-main.js'),
65
+ ];
66
+ for (const candidate of candidates) {
67
+ try {
68
+ fs_1.default.accessSync(candidate);
69
+ return candidate;
70
+ }
71
+ catch {
72
+ continue;
73
+ }
74
+ }
75
+ return null;
76
+ }
77
+ function openDesktopWindow(projectPath, preferredPort) {
78
+ const electronBinary = resolveElectronBinary();
79
+ const desktopEntry = resolveDesktopEntry();
80
+ if (!electronBinary || !desktopEntry) {
81
+ return false;
82
+ }
83
+ const child = (0, child_process_1.spawn)(electronBinary, [desktopEntry, '--project-path', projectPath, '--port', String(preferredPort)], {
84
+ detached: true,
85
+ stdio: 'ignore',
86
+ });
87
+ child.unref();
88
+ return true;
89
+ }
90
+ function openBrowser(url) {
91
+ if (process.platform === 'win32') {
92
+ const child = (0, child_process_1.spawn)('cmd', ['/c', 'start', '', url], { detached: true, stdio: 'ignore' });
93
+ child.unref();
94
+ return;
95
+ }
96
+ if (process.platform === 'darwin') {
97
+ const child = (0, child_process_1.spawn)('open', [url], { detached: true, stdio: 'ignore' });
98
+ child.unref();
99
+ return;
100
+ }
101
+ const child = (0, child_process_1.spawn)('xdg-open', [url], { detached: true, stdio: 'ignore' });
102
+ child.unref();
103
+ }
104
+ function isProcessAlive(pid) {
105
+ try {
106
+ process.kill(pid, 0);
107
+ return true;
108
+ }
109
+ catch (e) {
110
+ return !!(e && e.code === 'EPERM');
111
+ }
112
+ }
113
+ function killPid(pid) {
114
+ try {
115
+ if (process.platform === 'win32') {
116
+ (0, child_process_1.execFileSync)('taskkill', ['/PID', String(pid), '/T', '/F'], { stdio: 'ignore' });
117
+ }
118
+ else {
119
+ process.kill(pid, 'SIGTERM');
120
+ }
121
+ }
122
+ catch {
123
+ /* already gone */
124
+ }
125
+ }
126
+ function isPortFree(port, timeoutMs = 500) {
127
+ return new Promise((resolve) => {
128
+ const sock = net_1.default.connect({ host: '127.0.0.1', port }, () => { sock.destroy(); resolve(false); });
129
+ sock.on('error', () => resolve(true));
130
+ sock.setTimeout(timeoutMs, () => { sock.destroy(); resolve(true); });
131
+ });
132
+ }
133
+ async function waitForPortFree(port, totalMs = 5000) {
134
+ const start = Date.now();
135
+ while (Date.now() - start < totalMs) {
136
+ if (await isPortFree(port))
137
+ return;
138
+ await new Promise((r) => setTimeout(r, 200));
139
+ }
140
+ }
141
+ function fetchRunningHubVersion(port) {
142
+ return new Promise((resolve) => {
143
+ const req = http_1.default.get({ host: '127.0.0.1', port, path: '/api/ai-hub/version', timeout: 1000 }, (res) => {
144
+ if (res.statusCode !== 200) {
145
+ res.resume();
146
+ return resolve(null);
147
+ }
148
+ let body = '';
149
+ res.on('data', (c) => { body += c; });
150
+ res.on('end', () => { try {
151
+ resolve(JSON.parse(body).version ?? null);
152
+ }
153
+ catch {
154
+ resolve(null);
155
+ } });
156
+ });
157
+ req.on('error', () => resolve(null));
158
+ req.on('timeout', () => { req.destroy(); resolve(null); });
159
+ });
160
+ }
161
+ async function reconcileRunningHub(flags) {
162
+ const running = (0, hub_runtime_file_1.readHubRuntimeFile)((0, project_fraim_paths_1.getUserFraimDirPath)());
163
+ const confirmedVersion = running ? await fetchRunningHubVersion(running.port) : null;
164
+ const live = !!(running && confirmedVersion && isProcessAlive(running.pid));
165
+ const effective = live && running ? { ...running, version: confirmedVersion } : null;
166
+ const decision = (0, hub_launch_decision_1.decideHubLaunch)({
167
+ running: effective,
168
+ pidAlive: live,
169
+ cliVersion: (0, version_utils_1.getFraimVersion)(),
170
+ restart: flags.restart,
171
+ noRestart: flags.keepRunning,
172
+ });
173
+ if (decision.action === 'replace' && effective) {
174
+ console.log(`Replacing running Hub (v${effective.version}, pid ${effective.pid}) - ${decision.reason}`);
175
+ killPid(effective.pid);
176
+ await waitForPortFree(effective.port);
177
+ }
178
+ else if (decision.action === 'focus-existing' && effective) {
179
+ console.log(`A Hub (v${effective.version}) is already running - focusing it. Use --restart to replace it.`);
180
+ }
181
+ }
182
+ async function runHub(options) {
183
+ const { AiHubServer, findAvailablePort } = await Promise.resolve().then(() => __importStar(require('./server')));
184
+ const preferredPort = options.port || (0, git_utils_1.getPort)() + 100;
185
+ const projectPath = path_1.default.resolve(options.projectPath || process.cwd());
186
+ if (options.open) {
187
+ const wantDesktop = !options.browser;
188
+ if (wantDesktop) {
189
+ await reconcileRunningHub({ restart: !!options.restart, keepRunning: !!options.keepRunning });
190
+ }
191
+ const openedDesktop = wantDesktop && openDesktopWindow(projectPath, preferredPort);
192
+ if (!openedDesktop) {
193
+ const port = await findAvailablePort(preferredPort);
194
+ const server = new AiHubServer({ projectPath });
195
+ await server.start(port);
196
+ const url = `http://127.0.0.1:${port}/ai-hub/`;
197
+ console.log(`AI Hub running at ${url}`);
198
+ console.log(`Project path: ${projectPath}`);
199
+ openBrowser(url);
200
+ return;
201
+ }
202
+ console.log('AI Hub desktop shell launched.');
203
+ console.log(`Project path: ${projectPath}`);
204
+ return;
205
+ }
206
+ const port = await findAvailablePort(preferredPort);
207
+ const server = new AiHubServer({ projectPath });
208
+ await server.start(port);
209
+ const url = `http://127.0.0.1:${port}/ai-hub/`;
210
+ console.log(`AI Hub running at ${url}`);
211
+ console.log(`Project path: ${projectPath}`);
212
+ }
@@ -0,0 +1,283 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.AiHubConfiguredAgentStore = void 0;
7
+ exports.synthesizeDefaultConfiguredAgents = synthesizeDefaultConfiguredAgents;
8
+ exports.checkConfiguredAgentAvailability = checkConfiguredAgentAvailability;
9
+ exports.projectConfiguredAgent = projectConfiguredAgent;
10
+ exports.resolveConfiguredAgentForHost = resolveConfiguredAgentForHost;
11
+ exports.resolveConfiguredAgentEnv = resolveConfiguredAgentEnv;
12
+ exports.decorateHostPlanWithConfiguredAgent = decorateHostPlanWithConfiguredAgent;
13
+ const fs_1 = __importDefault(require("fs"));
14
+ const path_1 = __importDefault(require("path"));
15
+ const child_process_1 = require("child_process");
16
+ const project_fraim_paths_1 = require("../core/utils/project-fraim-paths");
17
+ const VALID_HOSTS = ['codex', 'claude', 'gemini', 'copilot'];
18
+ const ENV_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
19
+ function nowIso() {
20
+ return new Date().toISOString();
21
+ }
22
+ function storePath() {
23
+ return path_1.default.join((0, project_fraim_paths_1.getUserFraimDirPath)(), 'hub-configured-agents.json');
24
+ }
25
+ function normalizeId(value) {
26
+ return String(value || '')
27
+ .trim()
28
+ .toLowerCase()
29
+ .replace(/[^a-z0-9-]+/g, '-')
30
+ .replace(/^-+|-+$/g, '');
31
+ }
32
+ function stringArray(value) {
33
+ if (!Array.isArray(value))
34
+ return [];
35
+ return value.map((item) => String(item || '').trim()).filter(Boolean);
36
+ }
37
+ function normalizeAgent(input) {
38
+ const id = normalizeId(input.id);
39
+ const label = String(input.label || '').trim();
40
+ const baseHostId = input.baseHostId;
41
+ if (!id)
42
+ return { ok: false, error: 'Configured agent id is required.' };
43
+ if (!label)
44
+ return { ok: false, error: 'Configured agent label is required.' };
45
+ if (!VALID_HOSTS.includes(baseHostId))
46
+ return { ok: false, error: 'Choose a supported base agent.' };
47
+ const setupScript = normalizeSetupScript(input.setupScript);
48
+ if (input.setupScript && !setupScript)
49
+ return { ok: false, error: 'Setup script command is required.' };
50
+ const createdAt = input.createdAt || nowIso();
51
+ return {
52
+ ok: true,
53
+ agent: {
54
+ id,
55
+ label,
56
+ ...(input.description && String(input.description).trim() ? { description: String(input.description).trim() } : {}),
57
+ baseHostId,
58
+ enabled: input.enabled !== false,
59
+ ...(setupScript ? { setupScript } : {}),
60
+ createdAt,
61
+ updatedAt: nowIso(),
62
+ },
63
+ };
64
+ }
65
+ function normalizeSetupScript(value) {
66
+ if (!value)
67
+ return null;
68
+ const runner = value.runner;
69
+ const command = String(value.command || '').trim();
70
+ if (!['powershell', 'bash'].includes(runner) || !command)
71
+ return null;
72
+ return {
73
+ runner,
74
+ command,
75
+ args: stringArray(value.args),
76
+ timeoutMs: typeof value.timeoutMs === 'number' && value.timeoutMs > 0 ? Math.min(value.timeoutMs, 120_000) : 30_000,
77
+ };
78
+ }
79
+ function synthesizeDefaultConfiguredAgents(employees) {
80
+ const timestamp = '1970-01-01T00:00:00.000Z';
81
+ return employees.map((employee) => ({
82
+ id: `${employee.id}-default`,
83
+ label: employee.label,
84
+ description: 'Default local launch for this agent tool.',
85
+ baseHostId: employee.id,
86
+ enabled: true,
87
+ createdAt: timestamp,
88
+ updatedAt: timestamp,
89
+ }));
90
+ }
91
+ class AiHubConfiguredAgentStore {
92
+ constructor(filePath = storePath()) {
93
+ this.filePath = filePath;
94
+ }
95
+ list() {
96
+ try {
97
+ if (!fs_1.default.existsSync(this.filePath))
98
+ return [];
99
+ const parsed = JSON.parse(fs_1.default.readFileSync(this.filePath, 'utf8'));
100
+ if (!Array.isArray(parsed))
101
+ return [];
102
+ return parsed
103
+ .map((item) => normalizeAgent(item))
104
+ .filter((result) => result.ok && !!result.agent)
105
+ .map((result) => result.agent);
106
+ }
107
+ catch {
108
+ return [];
109
+ }
110
+ }
111
+ listWithDefaults(employees) {
112
+ const persisted = this.list();
113
+ const defaults = synthesizeDefaultConfiguredAgents(employees);
114
+ const seen = new Set(persisted.map((agent) => agent.id));
115
+ return [...persisted, ...defaults.filter((agent) => !seen.has(agent.id))];
116
+ }
117
+ upsert(input) {
118
+ const existing = this.list().find((agent) => agent.id === normalizeId(input.id));
119
+ const normalized = normalizeAgent({ ...input, createdAt: existing?.createdAt || input.createdAt });
120
+ if (!normalized.ok || !normalized.agent)
121
+ return normalized;
122
+ const list = this.list().filter((agent) => agent.id !== normalized.agent.id);
123
+ list.push(normalized.agent);
124
+ this.save(list);
125
+ return normalized;
126
+ }
127
+ delete(id) {
128
+ const normalizedId = normalizeId(id);
129
+ if (!normalizedId || normalizedId.endsWith('-default'))
130
+ return false;
131
+ const list = this.list();
132
+ const next = list.filter((agent) => agent.id !== normalizedId);
133
+ if (next.length === list.length)
134
+ return false;
135
+ this.save(next);
136
+ return true;
137
+ }
138
+ save(agents) {
139
+ fs_1.default.mkdirSync(path_1.default.dirname(this.filePath), { recursive: true });
140
+ fs_1.default.writeFileSync(this.filePath, JSON.stringify(agents, null, 2) + '\n', { encoding: 'utf8', mode: 0o600 });
141
+ }
142
+ }
143
+ exports.AiHubConfiguredAgentStore = AiHubConfiguredAgentStore;
144
+ function checkConfiguredAgentAvailability(agent, employees, env = process.env) {
145
+ const reasons = [];
146
+ const warnings = [];
147
+ const employee = employees.find((entry) => entry.id === agent.baseHostId);
148
+ if (!agent.enabled)
149
+ reasons.push('Configured agent is disabled.');
150
+ if (!employee?.available)
151
+ reasons.push(`${employee?.label || agent.baseHostId} is not available on this machine.`);
152
+ if (agent.setupScript) {
153
+ const command = agent.setupScript.command;
154
+ const isPathLike = /[\\/]/.test(command) || /^[A-Za-z]:[\\/]/.test(command);
155
+ if (isPathLike && !fs_1.default.existsSync(command)) {
156
+ reasons.push(`Setup script is missing: ${path_1.default.basename(command)}`);
157
+ }
158
+ }
159
+ else if (!agent.id.endsWith('-default')) {
160
+ warnings.push('No setup script configured; this profile launches the base CLI unchanged.');
161
+ }
162
+ return { id: agent.id, label: agent.label, baseHostId: agent.baseHostId, enabled: agent.enabled, available: reasons.length === 0, reasons, warnings };
163
+ }
164
+ function projectConfiguredAgent(agent, employees) {
165
+ const check = checkConfiguredAgentAvailability(agent, employees);
166
+ return {
167
+ id: agent.id,
168
+ label: agent.label,
169
+ ...(agent.description ? { description: agent.description } : {}),
170
+ baseHostId: agent.baseHostId,
171
+ enabled: agent.enabled,
172
+ ...(agent.setupScript ? { setupScript: { ...agent.setupScript, args: [...(agent.setupScript.args || [])] } } : {}),
173
+ createdAt: agent.createdAt,
174
+ updatedAt: agent.updatedAt,
175
+ available: check.available,
176
+ reasons: check.reasons,
177
+ warnings: check.warnings,
178
+ };
179
+ }
180
+ function resolveConfiguredAgentForHost(configuredAgentId, hostId, agents) {
181
+ const explicit = configuredAgentId ? agents.find((agent) => agent.id === configuredAgentId) : undefined;
182
+ if (explicit)
183
+ return { agent: explicit, baseHostId: explicit.baseHostId };
184
+ const fallbackHost = hostId && VALID_HOSTS.includes(hostId) ? hostId : 'claude';
185
+ const fallback = agents.find((agent) => agent.id === `${fallbackHost}-default`) || {
186
+ id: `${fallbackHost}-default`,
187
+ label: fallbackHost,
188
+ baseHostId: fallbackHost,
189
+ enabled: true,
190
+ };
191
+ return { agent: fallback, baseHostId: fallback.baseHostId };
192
+ }
193
+ function resolveConfiguredAgentEnv(agent) {
194
+ if (!agent.setupScript)
195
+ return {};
196
+ return runSetupScript(agent.setupScript);
197
+ }
198
+ function runSetupScript(setupScript) {
199
+ const args = setupScript.args || [];
200
+ const captureEnv = {
201
+ ...process.env,
202
+ FRAIM_HUB_SETUP_SCRIPT_COMMAND: setupScript.command,
203
+ FRAIM_HUB_SETUP_SCRIPT_ARGS_JSON: JSON.stringify(args),
204
+ };
205
+ const invocation = setupScript.runner === 'powershell'
206
+ ? ['powershell.exe', [
207
+ '-NoProfile',
208
+ '-ExecutionPolicy',
209
+ 'Bypass',
210
+ '-Command',
211
+ [
212
+ '$ErrorActionPreference = "Stop";',
213
+ '$before = @{};',
214
+ '[Environment]::GetEnvironmentVariables("Process").GetEnumerator() | ForEach-Object { $before[$_.Key] = [string]$_.Value };',
215
+ '$scriptArgs = @();',
216
+ 'if ($env:FRAIM_HUB_SETUP_SCRIPT_ARGS_JSON) { $scriptArgs = @(ConvertFrom-Json $env:FRAIM_HUB_SETUP_SCRIPT_ARGS_JSON); }',
217
+ '. $env:FRAIM_HUB_SETUP_SCRIPT_COMMAND @scriptArgs *> $null;',
218
+ '[Environment]::GetEnvironmentVariables("Process").GetEnumerator() | ForEach-Object {',
219
+ ' $name = [string]$_.Key;',
220
+ ' $value = [string]$_.Value;',
221
+ ' if ($name -match "^[A-Za-z_][A-Za-z0-9_]*$" -and (!$before.ContainsKey($name) -or $before[$name] -ne $value)) { "$name=$value" }',
222
+ '}',
223
+ ].join(' '),
224
+ ]]
225
+ : ['bash', [
226
+ '-lc',
227
+ [
228
+ 'set -e',
229
+ 'before="$(mktemp)"',
230
+ 'after="$(mktemp)"',
231
+ 'env | sort > "$before"',
232
+ 'source "$1" "${@:2}" >/dev/null',
233
+ 'env | sort > "$after"',
234
+ 'comm -13 "$before" "$after"',
235
+ 'rm -f "$before" "$after"',
236
+ ].join('; '),
237
+ 'fraim-hub-setup',
238
+ setupScript.command,
239
+ ...args,
240
+ ]];
241
+ const result = (0, child_process_1.spawnSync)(invocation[0], invocation[1], {
242
+ encoding: 'utf8',
243
+ timeout: setupScript.timeoutMs || 30_000,
244
+ env: captureEnv,
245
+ });
246
+ if (result.error || result.status !== 0) {
247
+ const detail = result.error?.message || result.stderr?.trim() || `exit ${result.status}`;
248
+ throw new Error(`Configured agent setup script failed: ${detail}`);
249
+ }
250
+ return filterSafeEnv(parseEnvLines(result.stdout || ''));
251
+ }
252
+ function parseEnvLines(stdout) {
253
+ const env = {};
254
+ for (const line of stdout.split(/\r?\n/)) {
255
+ const idx = line.indexOf('=');
256
+ if (idx <= 0)
257
+ continue;
258
+ const key = line.slice(0, idx).trim();
259
+ const value = line.slice(idx + 1);
260
+ if (ENV_NAME_PATTERN.test(key))
261
+ env[key] = value;
262
+ }
263
+ return env;
264
+ }
265
+ function filterSafeEnv(env) {
266
+ const safe = {};
267
+ for (const [key, value] of Object.entries(env)) {
268
+ if (!ENV_NAME_PATTERN.test(key))
269
+ continue;
270
+ if (key.startsWith('FRAIM_HUB_SETUP_SCRIPT_'))
271
+ continue;
272
+ safe[key] = value;
273
+ }
274
+ return safe;
275
+ }
276
+ function decorateHostPlanWithConfiguredAgent(plan, agent, agentEnv = {}) {
277
+ if (!agent)
278
+ return plan;
279
+ return {
280
+ ...plan,
281
+ env: { ...agentEnv, ...(plan.env || {}) },
282
+ };
283
+ }