@ouro.bot/cli 0.1.0-alpha.231 → 0.1.0-alpha.234

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.
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ /**
3
+ * First-class MCP tool integration — converts MCP server tools into ToolDefinitions
4
+ * so the model can call them directly without shell indirection.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.mcpToolsAsDefinitions = mcpToolsAsDefinitions;
8
+ const runtime_1 = require("../nerves/runtime");
9
+ /**
10
+ * Convert all tools from an McpManager into ToolDefinition objects.
11
+ * Each tool gets named `{server}_{tool}` (e.g., `browser_navigate`).
12
+ * The handler calls `mcpManager.callTool()` and returns concatenated text content.
13
+ */
14
+ function mcpToolsAsDefinitions(mcpManager) {
15
+ if (!mcpManager)
16
+ return [];
17
+ return mcpManager.listAllTools().flatMap((entry) => entry.tools.map((tool) => ({
18
+ tool: {
19
+ type: "function",
20
+ function: {
21
+ name: `${entry.server}_${tool.name}`,
22
+ description: tool.description || `MCP tool: ${tool.name} (server: ${entry.server})`,
23
+ parameters: tool.inputSchema ?? { type: "object", properties: {} },
24
+ },
25
+ },
26
+ handler: async (args) => {
27
+ (0, runtime_1.emitNervesEvent)({
28
+ event: "mcp.tool_start",
29
+ component: "repertoire",
30
+ message: `calling MCP tool ${entry.server}/${tool.name}`,
31
+ meta: { server: entry.server, tool: tool.name },
32
+ });
33
+ try {
34
+ const result = await mcpManager.callTool(entry.server, tool.name, args);
35
+ const text = result.content
36
+ .filter((c) => c.type === "text" && c.text)
37
+ .map((c) => c.text)
38
+ .join("");
39
+ (0, runtime_1.emitNervesEvent)({
40
+ event: "mcp.tool_end",
41
+ component: "repertoire",
42
+ message: `MCP tool ${entry.server}/${tool.name} completed`,
43
+ meta: { server: entry.server, tool: tool.name },
44
+ });
45
+ return text;
46
+ }
47
+ catch (error) {
48
+ const reason = error instanceof Error ? error.message : String(error);
49
+ (0, runtime_1.emitNervesEvent)({
50
+ level: "error",
51
+ event: "mcp.tool_error",
52
+ component: "repertoire",
53
+ message: `MCP tool ${entry.server}/${tool.name} failed: ${reason}`,
54
+ meta: { server: entry.server, tool: tool.name, reason },
55
+ });
56
+ return `[mcp error] ${entry.server}/${tool.name}: ${reason}`;
57
+ }
58
+ },
59
+ mcpServer: entry.server,
60
+ })));
61
+ }
@@ -10,6 +10,7 @@ const tools_continuity_1 = require("./tools-continuity");
10
10
  const tools_config_1 = require("./tools-config");
11
11
  const tools_1 = require("./coding/tools");
12
12
  const tools_credential_1 = require("./tools-credential");
13
+ const tools_vault_1 = require("./tools-vault");
13
14
  const tools_travel_1 = require("./tools-travel");
14
15
  // Re-export flow tools for consumers that import them from tools-base
15
16
  var tools_flow_1 = require("./tools-flow");
@@ -35,6 +36,7 @@ exports.baseToolDefinitions = [
35
36
  ...tools_config_1.configToolDefinitions,
36
37
  ...tools_1.codingToolDefinitions,
37
38
  ...tools_credential_1.credentialToolDefinitions,
39
+ ...tools_vault_1.vaultToolDefinitions,
38
40
  ...tools_travel_1.travelToolDefinitions,
39
41
  ];
40
42
  // Convenience array of just the tool schemas (no handler/integration metadata).
@@ -95,7 +95,6 @@ exports.credentialToolDefinitions = [
95
95
  return `Credential store error: ${err instanceof Error ? err.message : String(err)}`;
96
96
  }
97
97
  },
98
- confirmationRequired: true,
99
98
  summaryKeys: ["domain"],
100
99
  },
101
100
  {
@@ -178,7 +177,6 @@ exports.credentialToolDefinitions = [
178
177
  return `Credential delete error: ${err instanceof Error ? err.message : String(err)}`;
179
178
  }
180
179
  },
181
- confirmationRequired: true,
182
180
  summaryKeys: ["domain"],
183
181
  },
184
182
  ];
@@ -0,0 +1,110 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.vaultToolDefinitions = void 0;
37
+ const crypto = __importStar(require("node:crypto"));
38
+ const fs = __importStar(require("node:fs"));
39
+ const path = __importStar(require("node:path"));
40
+ const identity_1 = require("../heart/identity");
41
+ const vault_setup_1 = require("./vault-setup");
42
+ const runtime_1 = require("../nerves/runtime");
43
+ exports.vaultToolDefinitions = [
44
+ {
45
+ tool: {
46
+ type: "function",
47
+ function: {
48
+ name: "vault_setup",
49
+ description: "Set up the agent's credential vault. Creates a Bitwarden account on the configured Vaultwarden server. One-time operation — idempotent if vault already exists.",
50
+ parameters: {
51
+ type: "object",
52
+ properties: {},
53
+ },
54
+ },
55
+ },
56
+ handler: async () => {
57
+ const agentName = (0, identity_1.getAgentName)();
58
+ (0, runtime_1.emitNervesEvent)({
59
+ component: "repertoire",
60
+ event: "repertoire.vault_tool_call",
61
+ message: "vault_setup invoked",
62
+ meta: { tool: "vault_setup", agentName },
63
+ });
64
+ try {
65
+ const config = (0, identity_1.loadAgentConfig)();
66
+ const { email, serverUrl } = (0, identity_1.resolveVaultConfig)(agentName, config.vault);
67
+ // Generate a strong random master password (32 bytes -> base64)
68
+ const masterPassword = crypto.randomBytes(32).toString("base64");
69
+ // Store master password in secrets.json BEFORE creating the account
70
+ // so it's not lost if the process crashes after registration
71
+ const secretsPath = (0, identity_1.getAgentSecretsPath)(agentName);
72
+ const secretsDir = path.dirname(secretsPath);
73
+ if (!fs.existsSync(secretsDir)) {
74
+ fs.mkdirSync(secretsDir, { recursive: true, mode: 0o700 });
75
+ }
76
+ let secrets = {};
77
+ if (fs.existsSync(secretsPath)) {
78
+ try {
79
+ secrets = JSON.parse(fs.readFileSync(secretsPath, "utf-8"));
80
+ }
81
+ catch {
82
+ // If secrets.json is corrupt, start fresh but don't lose the file
83
+ secrets = {};
84
+ }
85
+ }
86
+ secrets.vault = {
87
+ ...(typeof secrets.vault === "object" && secrets.vault !== null ? secrets.vault : {}),
88
+ masterPassword,
89
+ email,
90
+ serverUrl,
91
+ };
92
+ fs.writeFileSync(secretsPath, JSON.stringify(secrets, null, 2) + "\n", {
93
+ mode: 0o600,
94
+ });
95
+ // Create the vault account
96
+ const result = await (0, vault_setup_1.createVaultAccount)(agentName, serverUrl, email, masterPassword);
97
+ if (!result.success) {
98
+ return `Vault setup failed: ${result.error}`;
99
+ }
100
+ return `Vault created at ${serverUrl} for ${email}. Master password stored in secrets.json.`;
101
+ }
102
+ catch (err) {
103
+ /* v8 ignore next -- defensive: error handling @preserve */
104
+ const reason = err instanceof Error ? err.message : String(err);
105
+ return `Vault setup error: ${reason}`;
106
+ }
107
+ },
108
+ summaryKeys: [],
109
+ },
110
+ ];
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.surfaceToolDef = exports.restTool = exports.ponderTool = exports.observeTool = exports.settleTool = exports.tools = void 0;
4
+ exports.resetMcpDefinitions = resetMcpDefinitions;
4
5
  exports.getToolsForChannel = getToolsForChannel;
5
6
  exports.isConfirmationRequired = isConfirmationRequired;
6
7
  exports.isConfirmationAlwaysRequired = isConfirmationAlwaysRequired;
@@ -16,6 +17,7 @@ const runtime_1 = require("../nerves/runtime");
16
17
  const guardrails_1 = require("./guardrails");
17
18
  const identity_1 = require("../heart/identity");
18
19
  const tools_surface_1 = require("./tools-surface");
20
+ const mcp_tools_1 = require("./mcp-tools");
19
21
  function safeGetAgentRoot() {
20
22
  try {
21
23
  return (0, identity_1.getAgentRoot)();
@@ -36,6 +38,13 @@ var tools_surface_2 = require("./tools-surface");
36
38
  Object.defineProperty(exports, "surfaceToolDef", { enumerable: true, get: function () { return tools_surface_2.surfaceToolDef; } });
37
39
  // All tool definitions in a single registry
38
40
  const allDefinitions = [...tools_base_1.baseToolDefinitions, ...tools_bluebubbles_1.bluebubblesToolDefinitions, ...tools_teams_1.teamsToolDefinitions, ...ado_semantic_1.adoSemanticToolDefinitions, ...tools_github_1.githubToolDefinitions, tools_surface_1.surfaceToolDefinition];
41
+ // MCP tool definitions — populated each time getToolsForChannel() is called with an mcpManager.
42
+ // Kept separate from allDefinitions so execTool/isConfirmation* can find them.
43
+ let mcpDefinitions = [];
44
+ /** Exported for testing — reset the MCP definitions cache. */
45
+ function resetMcpDefinitions() {
46
+ mcpDefinitions = [];
47
+ }
39
48
  function baseToolsForCapabilities() {
40
49
  // Use baseToolDefinitions at call time so dynamically-added tools are included
41
50
  return tools_base_1.baseToolDefinitions.map((d) => d.tool);
@@ -66,7 +75,7 @@ function filterByCapability(toolList, providerCapabilities) {
66
75
  // Teams/integration tools are included only if their integration is in availableIntegrations.
67
76
  // When toolPreferences is provided, matching preferences are appended to tool descriptions.
68
77
  // When providerCapabilities is provided, tools with requiredCapability are filtered.
69
- function getToolsForChannel(capabilities, toolPreferences, _context, providerCapabilities) {
78
+ function getToolsForChannel(capabilities, toolPreferences, _context, providerCapabilities, mcpManager) {
70
79
  const baseTools = baseToolsForCapabilities();
71
80
  const bluebubblesTools = capabilities?.channel === "bluebubbles"
72
81
  ? tools_bluebubbles_1.bluebubblesToolDefinitions.map((d) => d.tool)
@@ -98,18 +107,29 @@ function getToolsForChannel(capabilities, toolPreferences, _context, providerCap
98
107
  result = [...baseTools, ...bluebubblesTools, ...enrichedIntegrationTools];
99
108
  }
100
109
  }
110
+ // Append first-class MCP tools when mcpManager is provided
111
+ if (mcpManager) {
112
+ mcpDefinitions = (0, mcp_tools_1.mcpToolsAsDefinitions)(mcpManager);
113
+ const mcpSchemas = mcpDefinitions.map((d) => d.tool);
114
+ result = [...result, ...mcpSchemas];
115
+ }
101
116
  return filterByCapability(result, providerCapabilities);
102
117
  }
118
+ // Look up a tool definition from the combined registry (native + MCP).
119
+ function findDefinition(toolName) {
120
+ return allDefinitions.find((d) => d.tool.function.name === toolName)
121
+ ?? mcpDefinitions.find((d) => d.tool.function.name === toolName);
122
+ }
103
123
  // Check whether a tool requires user confirmation before execution.
104
124
  // Reads from ToolDefinition.confirmationRequired instead of a separate Set.
105
125
  function isConfirmationRequired(toolName) {
106
- const def = allDefinitions.find((d) => d.tool.function.name === toolName);
126
+ const def = findDefinition(toolName);
107
127
  return def?.confirmationRequired === true;
108
128
  }
109
129
  // Check whether a tool's confirmation is non-bypassable (cannot be skipped by skipConfirmation).
110
130
  // Used for T2 config proposals that must always require operator approval.
111
131
  function isConfirmationAlwaysRequired(toolName) {
112
- const def = allDefinitions.find((d) => d.tool.function.name === toolName);
132
+ const def = findDefinition(toolName);
113
133
  return def?.confirmationAlwaysRequired === true;
114
134
  }
115
135
  function normalizeGuardArgs(_name, args) {
@@ -122,8 +142,8 @@ async function execTool(name, args, ctx) {
122
142
  message: "tool execution started",
123
143
  meta: { name, ...(name === "shell" && args.command ? { command: args.command } : {}) },
124
144
  });
125
- // Look up from combined registry
126
- const def = allDefinitions.find((d) => d.tool.function.name === name);
145
+ // Look up from combined registry (native + MCP)
146
+ const def = findDefinition(name);
127
147
  if (!def) {
128
148
  (0, runtime_1.emitNervesEvent)({
129
149
  level: "error",
@@ -135,10 +155,13 @@ async function execTool(name, args, ctx) {
135
155
  return `unknown: ${name}`;
136
156
  }
137
157
  // Guardrail check: structural + trust-level
158
+ const mcpDef = mcpDefinitions.find((d) => d.tool.function.name === name);
138
159
  const guardContext = {
139
160
  readPaths: tools_base_1.editFileReadTracker,
140
161
  trustLevel: ctx?.context?.friend?.trustLevel,
141
162
  agentRoot: safeGetAgentRoot(),
163
+ ...(mcpDef?.mcpServer ? { mcpServerName: mcpDef.mcpServer } : {}),
164
+ ...(ctx?.context?.isGroupChat !== undefined ? { isGroupChat: (ctx?.context).isGroupChat } : {}),
142
165
  };
143
166
  const guardArgs = normalizeGuardArgs(name, args);
144
167
  const guardResult = (0, guardrails_1.guardInvocation)(name, guardArgs, guardContext);
@@ -194,7 +217,7 @@ function summarizeUnknownArgs(args) {
194
217
  return summarizeKeyValues(args, keys);
195
218
  }
196
219
  function summarizeArgs(name, args) {
197
- const def = allDefinitions.find((d) => d.tool.function.name === name);
220
+ const def = findDefinition(name);
198
221
  if (def && def.summaryKeys !== undefined) {
199
222
  return summarizeKeyValues(args, def.summaryKeys);
200
223
  }
@@ -11,6 +11,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
11
11
  exports.TRAVEL_ADVISORY_RSS_URL = void 0;
12
12
  exports.getWeather = getWeather;
13
13
  exports.getWeatherByCity = getWeatherByCity;
14
+ exports.isoToCountryName = isoToCountryName;
14
15
  exports.parseAdvisoryLevel = parseAdvisoryLevel;
15
16
  exports.parseCountryName = parseCountryName;
16
17
  exports.parseAdvisoryText = parseAdvisoryText;
@@ -69,6 +70,49 @@ function parseWeatherResponse(data) {
69
70
  * Returns XML/RSS with per-country advisory items.
70
71
  */
71
72
  exports.TRAVEL_ADVISORY_RSS_URL = "https://travel.state.gov/_res/rss/TAsTWs.xml";
73
+ /**
74
+ * Mapping of ISO 3166-1 alpha-2 codes to country names, ONLY for codes
75
+ * where ISO differs from FIPS 10-4 (which the State Dept RSS feed uses).
76
+ *
77
+ * The RSS feed Country-Tag uses FIPS codes. Callers typically pass ISO codes.
78
+ * When the codes match (e.g. AF for Afghanistan), no lookup is needed.
79
+ * This table handles the ~30 codes where they diverge.
80
+ */
81
+ const ISO_FIPS_DIVERGENCE = {
82
+ AT: "Austria", // FIPS: AU
83
+ AU: "Australia", // FIPS: AS
84
+ BD: "Bangladesh", // FIPS: BG
85
+ BN: "Brunei", // FIPS: BX
86
+ BO: "Bolivia", // FIPS: BL
87
+ CH: "Switzerland", // FIPS: SZ
88
+ CZ: "Czechia", // FIPS: EZ
89
+ DE: "Germany", // FIPS: GM
90
+ ES: "Spain", // FIPS: SP
91
+ FI: "Finland", // FIPS: FI → same, but title might say "Finland"
92
+ GQ: "Equatorial Guinea", // FIPS: EK
93
+ HR: "Croatia", // FIPS: HR → same
94
+ IE: "Ireland", // FIPS: EI
95
+ KP: "North Korea", // FIPS: KN
96
+ KR: "South Korea", // FIPS: KS
97
+ LT: "Lithuania", // FIPS: LH
98
+ MM: "Burma", // FIPS: BM (State Dept uses "Burma (Myanmar)")
99
+ NO: "Norway", // FIPS: NO → same
100
+ PH: "Philippines", // FIPS: RP
101
+ RO: "Romania", // FIPS: RO → same
102
+ SE: "Sweden", // FIPS: SW
103
+ TL: "Timor-Leste", // FIPS: TT
104
+ TW: "Taiwan", // FIPS: TW → same
105
+ VA: "Holy See", // FIPS: VT
106
+ YE: "Yemen", // FIPS: YM
107
+ };
108
+ /**
109
+ * Look up the expected country name for an ISO alpha-2 code,
110
+ * but ONLY when that code diverges from the FIPS code used in the RSS feed.
111
+ * Returns undefined for codes where ISO == FIPS (no title fallback needed).
112
+ */
113
+ function isoToCountryName(isoCode) {
114
+ return ISO_FIPS_DIVERGENCE[isoCode.toUpperCase()];
115
+ }
72
116
  /**
73
117
  * Parse advisory level (1-4) from an RSS item title like
74
118
  * "Afghanistan - Level 4: Do Not Travel"
@@ -103,31 +147,53 @@ async function getTravelAdvisory(countryCode) {
103
147
  throw new Error(`Travel advisory API error: ${res.status} ${res.statusText}`);
104
148
  }
105
149
  const xml = await res.text();
150
+ const upperCode = countryCode.toUpperCase();
151
+ // When the caller's ISO code diverges from FIPS, we need a title-based fallback.
152
+ const expectedCountryName = isoToCountryName(upperCode);
106
153
  // Parse RSS items via regex (no XML parser dependency needed).
107
154
  // Each <item> contains <title>, <pubDate>, and <category domain="Country-Tag">.
108
155
  const itemPattern = /<item>([\s\S]*?)<\/item>/g;
156
+ let titleFallbackMatch = null;
109
157
  let match;
110
158
  while ((match = itemPattern.exec(xml)) !== null) {
111
159
  const block = match[1];
112
- // Extract country tag from <category domain="Country-Tag">XX</category>
113
- const tagMatch = block.match(/<category\s+domain="Country-Tag">(.*?)<\/category>/);
114
- const tag = tagMatch?.[1]?.trim().toUpperCase();
115
- // The RSS feed uses FIPS country codes (e.g. "AF" for Afghanistan),
116
- // which sometimes differ from ISO 3166. Try exact match first.
117
- if (tag !== countryCode.toUpperCase())
118
- continue;
119
160
  const titleMatch = block.match(/<title>(.*?)<\/title>/);
120
161
  const title = titleMatch?.[1] ?? "";
121
162
  const pubDateMatch = block.match(/<pubDate>(.*?)<\/pubDate>/);
122
163
  const pubDate = pubDateMatch?.[1]?.trim() ?? "";
123
- return {
124
- countryCode,
125
- countryName: parseCountryName(title),
126
- advisoryLevel: parseAdvisoryLevel(title),
127
- advisoryText: parseAdvisoryText(title),
128
- lastUpdated: pubDate,
129
- };
164
+ // Extract country tag from <category domain="Country-Tag">XX</category>
165
+ const tagMatch = block.match(/<category\s+domain="Country-Tag">(.*?)<\/category>/);
166
+ const tag = tagMatch?.[1]?.trim().toUpperCase();
167
+ // Primary match: FIPS tag matches the requested code directly.
168
+ // Skip this path when the caller's ISO code is known to diverge from FIPS —
169
+ // e.g. ISO "ES" = Spain, but FIPS "ES" = El Salvador. In that case, only
170
+ // the title-based fallback should match.
171
+ if (tag === upperCode && !expectedCountryName) {
172
+ return {
173
+ countryCode,
174
+ countryName: parseCountryName(title),
175
+ advisoryLevel: parseAdvisoryLevel(title),
176
+ advisoryText: parseAdvisoryText(title),
177
+ lastUpdated: pubDate,
178
+ };
179
+ }
180
+ // Title-based match: when ISO differs from FIPS, match by country name.
181
+ // Uses startsWith to handle names like "Burma (Myanmar)" matching "Burma".
182
+ if (expectedCountryName && !titleFallbackMatch) {
183
+ const titleCountry = parseCountryName(title);
184
+ if (titleCountry.toLowerCase().startsWith(expectedCountryName.toLowerCase())) {
185
+ titleFallbackMatch = {
186
+ countryCode,
187
+ countryName: titleCountry,
188
+ advisoryLevel: parseAdvisoryLevel(title),
189
+ advisoryText: parseAdvisoryText(title),
190
+ lastUpdated: pubDate,
191
+ };
192
+ }
193
+ }
130
194
  }
195
+ if (titleFallbackMatch)
196
+ return titleFallbackMatch;
131
197
  throw new Error(`No travel advisory found for country code "${countryCode}"`);
132
198
  });
133
199
  }
@@ -0,0 +1,241 @@
1
+ "use strict";
2
+ /**
3
+ * Vault setup module — Bitwarden/Vaultwarden account creation.
4
+ *
5
+ * Implements the Bitwarden registration protocol using Node.js crypto:
6
+ * - PBKDF2-SHA256 for master key derivation
7
+ * - HKDF-SHA256 for key stretching
8
+ * - AES-256-CBC for symmetric key protection
9
+ * - RSA-2048 keypair for asymmetric encryption
10
+ *
11
+ * All crypto follows the Bitwarden security whitepaper:
12
+ * https://bitwarden.com/help/bitwarden-security-white-paper/
13
+ */
14
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ var desc = Object.getOwnPropertyDescriptor(m, k);
17
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
18
+ desc = { enumerable: true, get: function() { return m[k]; } };
19
+ }
20
+ Object.defineProperty(o, k2, desc);
21
+ }) : (function(o, m, k, k2) {
22
+ if (k2 === undefined) k2 = k;
23
+ o[k2] = m[k];
24
+ }));
25
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
26
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
27
+ }) : function(o, v) {
28
+ o["default"] = v;
29
+ });
30
+ var __importStar = (this && this.__importStar) || (function () {
31
+ var ownKeys = function(o) {
32
+ ownKeys = Object.getOwnPropertyNames || function (o) {
33
+ var ar = [];
34
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
35
+ return ar;
36
+ };
37
+ return ownKeys(o);
38
+ };
39
+ return function (mod) {
40
+ if (mod && mod.__esModule) return mod;
41
+ var result = {};
42
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
43
+ __setModuleDefault(result, mod);
44
+ return result;
45
+ };
46
+ })();
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ exports.deriveMasterKey = deriveMasterKey;
49
+ exports.deriveMasterPasswordHash = deriveMasterPasswordHash;
50
+ exports.deriveStretchedMasterKey = deriveStretchedMasterKey;
51
+ exports.makeProtectedSymmetricKey = makeProtectedSymmetricKey;
52
+ exports.createVaultAccount = createVaultAccount;
53
+ const crypto = __importStar(require("node:crypto"));
54
+ const runtime_1 = require("../nerves/runtime");
55
+ // ---------------------------------------------------------------------------
56
+ // Crypto primitives
57
+ // ---------------------------------------------------------------------------
58
+ /**
59
+ * Derive the master key from password and email using PBKDF2-SHA256.
60
+ * Email is lowercased and used as the salt per Bitwarden spec.
61
+ */
62
+ function deriveMasterKey(password, email, iterations) {
63
+ return new Promise((resolve, reject) => {
64
+ crypto.pbkdf2(password, email.toLowerCase(), iterations, 32, "sha256", (err, key) => {
65
+ /* v8 ignore next -- defensive: pbkdf2 rejects on invalid input @preserve */
66
+ if (err)
67
+ reject(err);
68
+ else
69
+ resolve(key);
70
+ });
71
+ });
72
+ }
73
+ /**
74
+ * Derive the master password hash: PBKDF2-SHA256(masterKey, password, 1 iteration).
75
+ * This hash is sent to the server for authentication — it never sees the master key.
76
+ */
77
+ function deriveMasterPasswordHash(masterKey, password) {
78
+ return new Promise((resolve, reject) => {
79
+ crypto.pbkdf2(masterKey, password, 1, 32, "sha256", (err, hash) => {
80
+ /* v8 ignore next -- defensive: pbkdf2 rejects on invalid input @preserve */
81
+ if (err)
82
+ reject(err);
83
+ else
84
+ resolve(hash.toString("base64"));
85
+ });
86
+ });
87
+ }
88
+ /**
89
+ * Stretch the master key using HKDF-Expand-only (RFC 5869 §2.3) to produce a 64-byte key.
90
+ * First 32 bytes = encryption key, last 32 bytes = MAC key.
91
+ *
92
+ * CRITICAL: Bitwarden uses HKDF-Expand ONLY (no Extract step).
93
+ * Node.js crypto.hkdfSync() does Extract+Expand which produces DIFFERENT output.
94
+ * Reference: https://github.com/bitwarden/sdk-internal/blob/main/crates/bitwarden-crypto/src/util.rs
95
+ * Bitwarden calls Hkdf::<Sha256>::from_prk(masterKey).expand(info, output) — Expand only.
96
+ */
97
+ function deriveStretchedMasterKey(masterKey) {
98
+ const encKey = hkdfExpandOnly(masterKey, "enc", 32);
99
+ const macKey = hkdfExpandOnly(masterKey, "mac", 32);
100
+ return Buffer.concat([encKey, macKey]);
101
+ }
102
+ /**
103
+ * HKDF-Expand only (RFC 5869 §2.3) — no Extract step.
104
+ * Matches Bitwarden's Hkdf::from_prk(prk).expand(info).
105
+ */
106
+ function hkdfExpandOnly(prk, info, length) {
107
+ const hashLen = 32; // SHA-256
108
+ const n = Math.ceil(length / hashLen);
109
+ let okm = Buffer.alloc(0);
110
+ let t = Buffer.alloc(0);
111
+ for (let i = 1; i <= n; i++) {
112
+ t = crypto.createHmac("sha256", prk)
113
+ .update(Buffer.concat([t, Buffer.from(info, "utf8"), Buffer.from([i])]))
114
+ .digest();
115
+ okm = Buffer.concat([okm, t]);
116
+ }
117
+ return okm.subarray(0, length);
118
+ }
119
+ /**
120
+ * Encrypt data with AES-256-CBC and HMAC-SHA256 MAC.
121
+ * Returns a Bitwarden "type 2" cipherstring: "2.<iv>|<ct>|<mac>"
122
+ */
123
+ function encryptWithStretchedKey(data, stretchedKey) {
124
+ const encKey = stretchedKey.subarray(0, 32);
125
+ const macKey = stretchedKey.subarray(32, 64);
126
+ const iv = crypto.randomBytes(16);
127
+ const cipher = crypto.createCipheriv("aes-256-cbc", encKey, iv);
128
+ const ct = Buffer.concat([cipher.update(data), cipher.final()]);
129
+ // MAC covers iv + ct
130
+ const mac = crypto.createHmac("sha256", macKey)
131
+ .update(iv)
132
+ .update(ct)
133
+ .digest();
134
+ return `2.${iv.toString("base64")}|${ct.toString("base64")}|${mac.toString("base64")}`;
135
+ }
136
+ /**
137
+ * Generate a 64-byte symmetric key, encrypt it with the stretched master key.
138
+ * Returns the "protected symmetric key" cipherstring.
139
+ */
140
+ function makeProtectedSymmetricKey(stretchedMasterKey) {
141
+ const symKey = crypto.randomBytes(64);
142
+ return encryptWithStretchedKey(symKey, stretchedMasterKey);
143
+ }
144
+ /**
145
+ * Generate an RSA-2048 keypair.
146
+ * Returns { publicKey: base64-DER, privateKeyDer: Buffer }.
147
+ */
148
+ function generateRsaKeypair() {
149
+ const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
150
+ modulusLength: 2048,
151
+ publicKeyEncoding: { type: "spki", format: "der" },
152
+ privateKeyEncoding: { type: "pkcs8", format: "der" },
153
+ });
154
+ return {
155
+ publicKeyB64: publicKey.toString("base64"),
156
+ privateKeyDer: privateKey,
157
+ };
158
+ }
159
+ // ---------------------------------------------------------------------------
160
+ // Registration
161
+ // ---------------------------------------------------------------------------
162
+ const KDF_PBKDF2 = 0;
163
+ const KDF_ITERATIONS = 600000;
164
+ /**
165
+ * Create a Bitwarden account on the configured Vaultwarden server.
166
+ * Uses the Bitwarden registration API with standard KDF implementation.
167
+ */
168
+ async function createVaultAccount(agentName, serverUrl, email, masterPassword) {
169
+ (0, runtime_1.emitNervesEvent)({
170
+ event: "repertoire.vault_setup_start",
171
+ component: "repertoire",
172
+ message: `creating vault account for ${agentName}`,
173
+ meta: { agentName, serverUrl, email },
174
+ });
175
+ try {
176
+ // Step 1: Derive keys
177
+ const masterKey = await deriveMasterKey(masterPassword, email, KDF_ITERATIONS);
178
+ const masterPasswordHash = await deriveMasterPasswordHash(masterKey, masterPassword);
179
+ const stretchedKey = deriveStretchedMasterKey(masterKey);
180
+ // Step 2: Generate symmetric key (64 bytes = 32 enc + 32 mac), encrypt with stretched key
181
+ const symKey = crypto.randomBytes(64);
182
+ const protectedSymKey = encryptWithStretchedKey(symKey, stretchedKey);
183
+ // Step 3: Generate RSA keypair, encrypt private key with the symmetric key
184
+ const { publicKeyB64, privateKeyDer } = generateRsaKeypair();
185
+ const encryptedPrivateKey = encryptWithStretchedKey(privateKeyDer, symKey);
186
+ // Step 4: POST registration
187
+ const res = await fetch(`${serverUrl}/api/accounts/register`, {
188
+ method: "POST",
189
+ headers: { "Content-Type": "application/json" },
190
+ body: JSON.stringify({
191
+ name: agentName,
192
+ email,
193
+ masterPasswordHash,
194
+ masterPasswordHint: null,
195
+ key: protectedSymKey,
196
+ kdf: KDF_PBKDF2,
197
+ kdfIterations: KDF_ITERATIONS,
198
+ keys: {
199
+ publicKey: publicKeyB64,
200
+ encryptedPrivateKey,
201
+ },
202
+ }),
203
+ });
204
+ if (!res.ok) {
205
+ let errorDetail;
206
+ try {
207
+ const body = await res.json();
208
+ errorDetail = body.message ?? `HTTP ${res.status} ${res.statusText}`;
209
+ }
210
+ catch {
211
+ errorDetail = `HTTP ${res.status} ${res.statusText}`;
212
+ }
213
+ (0, runtime_1.emitNervesEvent)({
214
+ level: "error",
215
+ event: "repertoire.vault_setup_error",
216
+ component: "repertoire",
217
+ message: `vault registration failed: ${errorDetail}`,
218
+ meta: { agentName, serverUrl, email, reason: errorDetail },
219
+ });
220
+ return { success: false, email, serverUrl, error: errorDetail };
221
+ }
222
+ (0, runtime_1.emitNervesEvent)({
223
+ event: "repertoire.vault_setup_end",
224
+ component: "repertoire",
225
+ message: `vault account created for ${agentName}`,
226
+ meta: { agentName, serverUrl, email },
227
+ });
228
+ return { success: true, email, serverUrl };
229
+ }
230
+ catch (err) {
231
+ const reason = err instanceof Error ? err.message : String(err);
232
+ (0, runtime_1.emitNervesEvent)({
233
+ level: "error",
234
+ event: "repertoire.vault_setup_error",
235
+ component: "repertoire",
236
+ message: `vault setup failed: ${reason}`,
237
+ meta: { agentName, serverUrl, email, reason },
238
+ });
239
+ return { success: false, email, serverUrl, error: reason };
240
+ }
241
+ }