@ouro.bot/cli 0.1.0-alpha.465 → 0.1.0-alpha.467

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.
@@ -103,7 +103,7 @@ const provider_ping_1 = require("../provider-ping");
103
103
  const agent_discovery_1 = require("./agent-discovery");
104
104
  const connect_bay_1 = require("./connect-bay");
105
105
  const runtime_capability_check_1 = require("../runtime-capability-check");
106
- const porkbun_ops_1 = require("./porkbun-ops");
106
+ const vault_items_1 = require("./vault-items");
107
107
  // ── ensureDaemonRunning ──
108
108
  const DEFAULT_DAEMON_STARTUP_TIMEOUT_MS = 60_000;
109
109
  const DEFAULT_DAEMON_STARTUP_POLL_INTERVAL_MS = 500;
@@ -326,8 +326,10 @@ function agentResolutionFailureMode(command) {
326
326
  case "vault.status":
327
327
  case "vault.config.set":
328
328
  case "vault.config.status":
329
- case "vault.ops.porkbun.set":
330
- case "vault.ops.porkbun.status":
329
+ case "vault.item.set":
330
+ case "vault.item.status":
331
+ case "vault.item.list":
332
+ case "dns.workflow":
331
333
  case "connect":
332
334
  case "account.ensure":
333
335
  case "mail.import-mbox":
@@ -2012,72 +2014,287 @@ async function executeVaultConfigStatus(command, deps) {
2012
2014
  deps.writeStdout(message);
2013
2015
  return message;
2014
2016
  }
2015
- async function executeVaultOpsPorkbunSet(command, deps) {
2017
+ function parseVaultItemPublicFields(fields) {
2018
+ const parsed = {};
2019
+ for (const field of fields ?? []) {
2020
+ const separator = field.indexOf("=");
2021
+ const key = (0, vault_items_1.normalizeVaultItemFieldName)(field.slice(0, separator));
2022
+ parsed[key] = field.slice(separator + 1);
2023
+ }
2024
+ return parsed;
2025
+ }
2026
+ function uniqueVaultItemSecretFields(command) {
2027
+ const fields = command.template ? (0, vault_items_1.vaultItemTemplateSecretFields)(command.template) : [];
2028
+ for (const field of command.secretFields ?? []) {
2029
+ if (!fields.includes(field))
2030
+ fields.push(field);
2031
+ }
2032
+ return fields;
2033
+ }
2034
+ function vaultItemCompatibilityNotice(command) {
2035
+ if (command.compatibilityAlias !== vault_items_1.PORKBUN_OPS_COMPATIBILITY_ALIAS)
2036
+ return [];
2037
+ return ["deprecated compatibility alias: use ouro vault item set --template porkbun-api"];
2038
+ }
2039
+ function freeformVaultItemReservedMessage(itemName) {
2040
+ if (itemName.startsWith("providers/")) {
2041
+ return `Vault item "${itemName}" is reserved for harness-managed workflows. Use ouro auth or ouro connect for provider credentials.`;
2042
+ }
2043
+ if (itemName === "runtime/config" || /^runtime\/machines\/[^/]+\/config$/.test(itemName)) {
2044
+ return `Vault item "${itemName}" is reserved for harness-managed workflows. Use ouro connect or ouro vault config for runtime and sense configuration.`;
2045
+ }
2046
+ return undefined;
2047
+ }
2048
+ function assertFreeformVaultItemWritable(itemName) {
2049
+ const reservedMessage = freeformVaultItemReservedMessage(itemName);
2050
+ if (reservedMessage)
2051
+ throw new Error(reservedMessage);
2052
+ }
2053
+ function porkbunAccountForCompatibility(command) {
2054
+ if (command.compatibilityAlias !== vault_items_1.PORKBUN_OPS_COMPATIBILITY_ALIAS)
2055
+ return undefined;
2056
+ return (0, vault_items_1.normalizePorkbunOpsAccount)((0, vault_items_1.porkbunOpsAccountFromItemName)(command.item));
2057
+ }
2058
+ const PORKBUN_OPS_PROMPT_LABELS = {
2059
+ apiKey: (account) => `Porkbun API key for ${account}: `,
2060
+ secretApiKey: (account) => `Porkbun Secret API key for ${account}: `,
2061
+ };
2062
+ const PORKBUN_OPS_VALIDATION_LABELS = {
2063
+ apiKey: "Porkbun API key",
2064
+ secretApiKey: "Porkbun Secret API key",
2065
+ };
2066
+ function vaultItemTemplatePromptLabel(command, field, account) {
2067
+ if (command.compatibilityAlias === vault_items_1.PORKBUN_OPS_COMPATIBILITY_ALIAS) {
2068
+ return PORKBUN_OPS_PROMPT_LABELS[field](account);
2069
+ }
2070
+ return `Secret field ${field} for ${command.item}: `;
2071
+ }
2072
+ function vaultItemSecretValidationLabel(command, field) {
2073
+ if (command.compatibilityAlias === vault_items_1.PORKBUN_OPS_COMPATIBILITY_ALIAS) {
2074
+ return PORKBUN_OPS_VALIDATION_LABELS[field];
2075
+ }
2076
+ return `Secret field ${field}`;
2077
+ }
2078
+ async function executeVaultItemSet(command, deps) {
2016
2079
  if (command.agent === "SerpentGuide") {
2017
- throw new Error("SerpentGuide has no persistent credential vault. Store ops credentials in the owning agent vault.");
2080
+ throw new Error("SerpentGuide has no persistent credential vault. Store vault items in the owning agent vault.");
2081
+ }
2082
+ if (!command.compatibilityAlias)
2083
+ assertFreeformVaultItemWritable(command.item);
2084
+ const account = porkbunAccountForCompatibility(command);
2085
+ const promptSecret = requirePromptSecret(deps, command.compatibilityAlias === vault_items_1.PORKBUN_OPS_COMPATIBILITY_ALIAS ? "Porkbun ops credential entry" : "Vault item secret entry");
2086
+ const secretFieldNames = uniqueVaultItemSecretFields(command);
2087
+ const publicFields = parseVaultItemPublicFields(command.publicFields);
2088
+ if (account && !publicFields.account)
2089
+ publicFields.account = account;
2090
+ const secretFields = {};
2091
+ for (const field of secretFieldNames) {
2092
+ const normalized = (0, vault_items_1.normalizeVaultItemFieldName)(field);
2093
+ const value = await promptSecret(vaultItemTemplatePromptLabel(command, normalized, account));
2094
+ secretFields[normalized] = (0, vault_items_1.requireVaultItemSecret)(value, vaultItemSecretValidationLabel(command, normalized));
2018
2095
  }
2019
- const account = (0, porkbun_ops_1.normalizePorkbunOpsAccount)(command.account);
2020
- const promptSecret = requirePromptSecret(deps, "Porkbun ops credential entry");
2021
- const apiKey = (0, porkbun_ops_1.requirePorkbunOpsSecret)(await promptSecret(`Porkbun API key for ${account}: `), "Porkbun API key");
2022
- const secretApiKey = (0, porkbun_ops_1.requirePorkbunOpsSecret)(await promptSecret(`Porkbun Secret API key for ${account}: `), "Porkbun Secret API key");
2023
- const itemName = (0, porkbun_ops_1.porkbunOpsCredentialItemName)(account);
2024
2096
  const payload = {
2025
2097
  schemaVersion: 1,
2026
- kind: porkbun_ops_1.PORKBUN_OPS_CREDENTIAL_KIND,
2027
2098
  updatedAt: providerCliNow(deps).toISOString(),
2028
- account,
2029
- apiKey,
2030
- secretApiKey,
2099
+ publicFields,
2100
+ secretFields,
2031
2101
  };
2032
- const progress = createHumanCommandProgress(deps, "vault ops porkbun");
2102
+ const progress = createHumanCommandProgress(deps, command.compatibilityAlias === vault_items_1.PORKBUN_OPS_COMPATIBILITY_ALIAS ? "vault ops porkbun" : "vault item set");
2033
2103
  try {
2034
- await runCommandProgressPhase(progress, "storing Porkbun ops credentials", async () => {
2104
+ await runCommandProgressPhase(progress, command.compatibilityAlias === vault_items_1.PORKBUN_OPS_COMPATIBILITY_ALIAS ? "storing ordinary vault item" : "storing vault item", async () => {
2035
2105
  const store = (0, credential_access_1.getCredentialStore)(command.agent);
2036
- await store.store(itemName, {
2037
- username: account,
2106
+ await store.store(command.item, {
2107
+ ...(account ? { username: account } : {}),
2038
2108
  password: JSON.stringify(payload),
2039
- notes: "Operational Porkbun account API credential. Domain API access and Ouro DNS allowlists live outside this secret item.",
2109
+ ...(command.note !== undefined
2110
+ ? { notes: command.note }
2111
+ : command.compatibilityAlias === vault_items_1.PORKBUN_OPS_COMPATIBILITY_ALIAS
2112
+ ? { notes: "Ordinary vault item written by a deprecated Porkbun compatibility alias. Notes are for human/agent orientation only; workflow bindings live outside this item." }
2113
+ : {}),
2040
2114
  });
2041
- return itemName;
2115
+ return command.item;
2042
2116
  }, () => "secret stored");
2043
2117
  }
2044
2118
  finally {
2045
2119
  progress.end();
2046
2120
  }
2047
2121
  const message = [
2048
- `stored Porkbun ops credentials for ${command.agent}`,
2049
- `item: vault:${command.agent}:${itemName}`,
2050
- `account: ${account}`,
2051
- "authority: Porkbun account-level API credential",
2052
- "domain bindings and DNS allowlists live outside this secret item",
2122
+ ...vaultItemCompatibilityNotice(command),
2123
+ `stored ordinary vault item for ${command.agent}`,
2124
+ `item: vault:${command.agent}:${command.item}`,
2125
+ ...(account ? [`account: ${account}`] : []),
2126
+ `public fields: ${Object.keys(publicFields).length === 0 ? "none" : Object.keys(publicFields).sort().join(", ")}`,
2127
+ `secret fields: ${Object.keys(secretFields).sort().join(", ")}`,
2128
+ `notes: ${command.note !== undefined || command.compatibilityAlias === vault_items_1.PORKBUN_OPS_COMPATIBILITY_ALIAS ? "present" : "absent"}`,
2053
2129
  "secret values were not printed",
2054
2130
  ].join("\n");
2055
2131
  deps.writeStdout(message);
2056
2132
  return message;
2057
2133
  }
2058
- async function executeVaultOpsPorkbunStatus(command, deps) {
2134
+ async function executeVaultItemStatus(command, deps) {
2135
+ if (command.agent === "SerpentGuide") {
2136
+ const message = "SerpentGuide has no persistent credential vault. Vault items belong in the owning agent vault.";
2137
+ deps.writeStdout(message);
2138
+ return message;
2139
+ }
2140
+ const store = (0, credential_access_1.getCredentialStore)(command.agent);
2141
+ const account = porkbunAccountForCompatibility(command);
2142
+ const meta = await store.get(command.item);
2143
+ const lines = [
2144
+ ...vaultItemCompatibilityNotice(command),
2145
+ `agent: ${command.agent}`,
2146
+ `${command.compatibilityAlias ? "ordinary vault item" : "item"}: vault:${command.agent}:${command.item}`,
2147
+ `status: ${meta ? "present" : "missing"}`,
2148
+ ];
2149
+ if (meta?.username)
2150
+ lines.push(account ? `account: ${meta.username}` : `username: ${meta.username}`);
2151
+ if (meta?.notes)
2152
+ lines.push("notes: present");
2153
+ lines.push("secret values were not printed");
2154
+ const message = lines.join("\n");
2155
+ deps.writeStdout(message);
2156
+ return message;
2157
+ }
2158
+ async function executeVaultItemList(command, deps) {
2059
2159
  if (command.agent === "SerpentGuide") {
2060
- const message = "SerpentGuide has no persistent credential vault. Ops credentials belong in the owning agent vault.";
2160
+ const message = "SerpentGuide has no persistent credential vault. Vault items belong in the owning agent vault.";
2061
2161
  deps.writeStdout(message);
2062
2162
  return message;
2063
2163
  }
2064
2164
  const store = (0, credential_access_1.getCredentialStore)(command.agent);
2065
- const lines = [`agent: ${command.agent}`, "ops credentials: Porkbun"];
2066
- if (command.account) {
2067
- const account = (0, porkbun_ops_1.normalizePorkbunOpsAccount)(command.account);
2068
- const itemName = (0, porkbun_ops_1.porkbunOpsCredentialItemName)(account);
2069
- const meta = await store.get(itemName);
2070
- lines.push(`item: vault:${command.agent}:${itemName}`);
2071
- lines.push(`status: ${meta ? "present" : "missing"}`);
2072
- if (meta?.username)
2073
- lines.push(`account: ${meta.username}`);
2165
+ const prefix = command.prefix;
2166
+ const items = (await store.list())
2167
+ .filter((item) => !prefix || item.domain.startsWith(prefix.endsWith("/") ? prefix : `${prefix}/`))
2168
+ .map((item) => item.domain)
2169
+ .sort();
2170
+ const lines = [
2171
+ ...vaultItemCompatibilityNotice(command),
2172
+ `agent: ${command.agent}`,
2173
+ ...(prefix ? [`prefix: ${prefix}`] : []),
2174
+ `items: ${items.length === 0 ? "none stored" : items.join(", ")}`,
2175
+ "secret values were not printed",
2176
+ ];
2177
+ const message = lines.join("\n");
2178
+ deps.writeStdout(message);
2179
+ return message;
2180
+ }
2181
+ function resolveWorkflowFilePath(filePath, deps) {
2182
+ return path.isAbsolute(filePath)
2183
+ ? filePath
2184
+ : path.resolve(deps.getRepoCwd?.() ?? process.cwd(), filePath);
2185
+ }
2186
+ function extractDnsBackupRecords(input) {
2187
+ const value = input;
2188
+ const records = value.plan?.backup?.records ?? value.backup?.records ?? value.records;
2189
+ if (!Array.isArray(records))
2190
+ throw new Error("dns rollback backup does not contain records");
2191
+ return records;
2192
+ }
2193
+ async function executeDnsWorkflow(command, deps) {
2194
+ const workflow = await Promise.resolve().then(() => __importStar(require("./dns-workflow")));
2195
+ const bindingPath = resolveWorkflowFilePath(command.bindingPath, deps);
2196
+ const binding = workflow.loadDnsWorkflowBinding(JSON.parse(fs.readFileSync(bindingPath, "utf-8")));
2197
+ const store = (0, credential_access_1.getCredentialStore)(command.agent);
2198
+ const reader = {
2199
+ readSecretField: async (item, field) => {
2200
+ const raw = await store.getRawSecret(item, "password");
2201
+ const payload = JSON.parse(raw);
2202
+ const value = [payload.secretFields?.[field], payload[field]]
2203
+ .find((candidate) => typeof candidate === "string" && candidate.trim() !== "");
2204
+ if (!value) {
2205
+ throw new Error(`vault item ${item} is missing required secret field ${field}`);
2206
+ }
2207
+ return value;
2208
+ },
2209
+ };
2210
+ const secrets = await workflow.resolveDnsWorkflowSecrets(binding, reader);
2211
+ const driver = workflow.createPorkbunDnsDriver({ fetchImpl: deps.fetchImpl ?? fetch });
2212
+ if (command.action === "certificate") {
2213
+ if (!binding.certificate)
2214
+ throw new Error("DNS workflow binding does not define a certificate");
2215
+ if (binding.certificate.source !== "porkbun-ssl") {
2216
+ throw new Error(`DNS workflow certificate source ${binding.certificate.source} is not implemented`);
2217
+ }
2218
+ const certificate = await driver.retrieveCertificate({ domain: binding.domain, secrets });
2219
+ const payload = {
2220
+ schemaVersion: 1,
2221
+ updatedAt: providerCliNow(deps).toISOString(),
2222
+ publicFields: {
2223
+ domain: binding.domain,
2224
+ host: binding.certificate.host,
2225
+ source: binding.certificate.source,
2226
+ },
2227
+ secretFields: certificate,
2228
+ };
2229
+ await store.store(binding.certificate.storeItem, {
2230
+ username: binding.certificate.host,
2231
+ password: JSON.stringify(payload),
2232
+ notes: "TLS certificate bundle retrieved by DNS workflow. Notes are for human/agent orientation only; workflow bindings and deploy config remain the machine contract.",
2233
+ });
2234
+ const artifactPayload = workflow.redactDnsWorkflowArtifact({
2235
+ action: command.action,
2236
+ binding,
2237
+ certificate: {
2238
+ host: binding.certificate.host,
2239
+ storeItem: binding.certificate.storeItem,
2240
+ ...certificate,
2241
+ },
2242
+ });
2243
+ if (command.outputPath) {
2244
+ const outputPath = resolveWorkflowFilePath(command.outputPath, deps);
2245
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
2246
+ fs.writeFileSync(outputPath, `${JSON.stringify(artifactPayload, null, 2)}\n`, "utf-8");
2247
+ }
2248
+ const message = [
2249
+ `dns certificate for ${binding.domain}`,
2250
+ `driver: ${binding.driver}`,
2251
+ `credential item: vault:${command.agent}:${binding.credentialItem}`,
2252
+ `certificate item: vault:${command.agent}:${binding.certificate.storeItem}`,
2253
+ command.outputPath ? `artifact: ${command.outputPath}` : "artifact: not written",
2254
+ "secret values were not printed",
2255
+ ].join("\n");
2256
+ deps.writeStdout(message);
2257
+ return message;
2258
+ }
2259
+ const currentRecords = await driver.retrieveRecords({ domain: binding.domain, secrets });
2260
+ let plan;
2261
+ if (command.action === "rollback") {
2262
+ const backupPath = command.backupPath;
2263
+ plan = workflow.planDnsRollback({
2264
+ binding,
2265
+ currentRecords,
2266
+ backupRecords: extractDnsBackupRecords(JSON.parse(fs.readFileSync(resolveWorkflowFilePath(backupPath, deps), "utf-8"))),
2267
+ });
2074
2268
  }
2075
2269
  else {
2076
- const items = (await store.list()).filter((item) => item.domain.startsWith(`${porkbun_ops_1.PORKBUN_OPS_CREDENTIAL_PREFIX}/`));
2077
- lines.push(`items: ${items.length === 0 ? "none stored" : items.map((item) => item.domain).sort().join(", ")}`);
2270
+ plan = workflow.planDnsWorkflow({ binding, currentRecords });
2271
+ }
2272
+ const applied = command.action === "apply" || command.action === "rollback"
2273
+ ? await workflow.applyDnsWorkflowPlan({ driver, domain: binding.domain, secrets, plan })
2274
+ : [];
2275
+ const payload = workflow.redactDnsWorkflowArtifact({
2276
+ action: command.action,
2277
+ binding,
2278
+ plan,
2279
+ applied,
2280
+ });
2281
+ if (command.outputPath) {
2282
+ const outputPath = resolveWorkflowFilePath(command.outputPath, deps);
2283
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
2284
+ fs.writeFileSync(outputPath, `${JSON.stringify(payload, null, 2)}\n`, "utf-8");
2078
2285
  }
2079
- lines.push("secret values were not printed");
2080
- const message = lines.join("\n");
2286
+ const changed = plan.changes.length;
2287
+ const preserved = plan.preservedRecords.length;
2288
+ const message = [
2289
+ `dns ${command.action} for ${binding.domain}`,
2290
+ `driver: ${binding.driver}`,
2291
+ `credential item: vault:${command.agent}:${binding.credentialItem}`,
2292
+ `changes: ${changed}`,
2293
+ ...(applied.length > 0 ? [`applied: ${applied.length}`] : []),
2294
+ `preserved records: ${preserved}`,
2295
+ command.outputPath ? `artifact: ${command.outputPath}` : "artifact: not written",
2296
+ "secret values were not printed",
2297
+ ].join("\n");
2081
2298
  deps.writeStdout(message);
2082
2299
  return message;
2083
2300
  }
@@ -2798,6 +3015,53 @@ function mailroomPrivateKeys(mailroom) {
2798
3015
  const keys = isPlainRecord(mailroom?.privateKeys) ? mailroom.privateKeys : {};
2799
3016
  return Object.fromEntries(Object.entries(keys).filter((entry) => typeof entry[1] === "string"));
2800
3017
  }
3018
+ function hostedMailControlConfig(config) {
3019
+ const workSubstrate = isPlainRecord(config.workSubstrate) ? config.workSubstrate : undefined;
3020
+ if (!workSubstrate || stringField(workSubstrate, "mode") !== "hosted")
3021
+ return null;
3022
+ const mailControl = isPlainRecord(workSubstrate.mailControl) ? workSubstrate.mailControl : undefined;
3023
+ const url = mailControl ? stringField(mailControl, "url").replace(/\/+$/, "") : "";
3024
+ const token = mailControl ? (stringField(mailControl, "token") || stringField(mailControl, "adminToken")) : "";
3025
+ if (!url || !token) {
3026
+ throw new Error("hosted workSubstrate.mailControl requires url and token in the agent vault runtime/config item");
3027
+ }
3028
+ return { url, token };
3029
+ }
3030
+ function cleanHostedMailroomBase(existingMailroom) {
3031
+ const base = { ...(existingMailroom ?? {}) };
3032
+ delete base.registryPath;
3033
+ delete base.storePath;
3034
+ return base;
3035
+ }
3036
+ function requiredResponseRecord(value, label) {
3037
+ if (!isPlainRecord(value))
3038
+ throw new Error(`Mail Control response missing ${label}`);
3039
+ return value;
3040
+ }
3041
+ function requiredResponseText(record, key, label) {
3042
+ const value = stringField(record, key);
3043
+ if (!value)
3044
+ throw new Error(`Mail Control response missing ${label}`);
3045
+ return value;
3046
+ }
3047
+ function responsePrivateKeys(value) {
3048
+ if (!isPlainRecord(value))
3049
+ return {};
3050
+ return Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[0] === "string" && typeof entry[1] === "string" && entry[1].trim().length > 0));
3051
+ }
3052
+ function requiredHostedKeyIds(body) {
3053
+ return [body.mailbox, body.sourceGrant]
3054
+ .filter(isPlainRecord)
3055
+ .map((record) => stringField(record, "keyId"))
3056
+ .filter((keyId) => keyId.length > 0);
3057
+ }
3058
+ function assertHostedPrivateKeys(input) {
3059
+ for (const keyId of input.requiredKeyIds) {
3060
+ if (input.keys[keyId])
3061
+ continue;
3062
+ throw new Error(`hosted Mail Control references private mail key ${keyId}, but it was not returned and is not present in ${input.agent}'s vault runtime/config. Repair requires a fresh Mail Control one-time key response or key rotation.`);
3063
+ }
3064
+ }
2801
3065
  async function promptDelegatedMailSource(deps, input = {}) {
2802
3066
  if (input.noDelegatedSource)
2803
3067
  return { ownerEmail: "", source: "" };
@@ -2825,8 +3089,11 @@ async function ensureAgentMailroom(agent, input, deps, progressLabel) {
2825
3089
  let stored;
2826
3090
  let mailboxAddress = `${agent.toLowerCase()}@ouro.bot`;
2827
3091
  let sourceAlias = null;
3092
+ let mode = "local";
2828
3093
  let registryPath = path.join(mailStateDir, "registry.json");
2829
3094
  let storePath = mailStateDir;
3095
+ let hostedControlUrl;
3096
+ let blobStoreLabel;
2830
3097
  try {
2831
3098
  progress.startPhase("checking Mailroom runtime config");
2832
3099
  const current = await (0, runtime_credentials_1.refreshRuntimeCredentialConfig)(agent, { preserveCachedOnFailure: true });
@@ -2835,31 +3102,94 @@ async function ensureAgentMailroom(agent, input, deps, progressLabel) {
2835
3102
  }
2836
3103
  const currentConfig = current.ok ? current.config : {};
2837
3104
  const existingMailroom = isPlainRecord(currentConfig.mailroom) ? currentConfig.mailroom : undefined;
3105
+ const hostedConfig = hostedMailControlConfig(currentConfig);
2838
3106
  registryPath = stringField(existingMailroom ?? {}, "registryPath") || registryPath;
2839
3107
  storePath = stringField(existingMailroom ?? {}, "storePath") || storePath;
2840
- progress.updateDetail("ensuring Mailroom identity");
2841
- const ensured = (0, core_1.ensureMailboxRegistry)({
2842
- agentId: agent,
2843
- registry: readMailroomRegistryFromDisk(registryPath),
2844
- keys: mailroomPrivateKeys(existingMailroom),
2845
- ownerEmail: input.ownerEmail || undefined,
2846
- source: input.source || undefined,
2847
- });
2848
- mailboxAddress = ensured.mailboxAddress;
2849
- sourceAlias = ensured.sourceAlias;
2850
- fs.mkdirSync(path.dirname(registryPath), { recursive: true });
2851
- fs.mkdirSync(storePath, { recursive: true });
2852
- fs.writeFileSync(registryPath, `${JSON.stringify(ensured.registry, null, 2)}\n`, "utf-8");
2853
- const nextConfig = {
2854
- ...currentConfig,
2855
- mailroom: {
2856
- ...(existingMailroom ?? {}),
2857
- mailboxAddress,
2858
- registryPath,
2859
- storePath,
2860
- privateKeys: ensured.keys,
2861
- },
2862
- };
3108
+ let nextConfig;
3109
+ if (hostedConfig) {
3110
+ progress.updateDetail("calling hosted Mail Control");
3111
+ const fetchImpl = deps.fetchImpl ?? fetch;
3112
+ const response = await fetchImpl(`${hostedConfig.url}/v1/mailboxes/ensure`, {
3113
+ method: "POST",
3114
+ headers: {
3115
+ authorization: `Bearer ${hostedConfig.token}`,
3116
+ "content-type": "application/json",
3117
+ },
3118
+ body: JSON.stringify({
3119
+ agentId: agent,
3120
+ ...(input.ownerEmail ? { ownerEmail: input.ownerEmail } : {}),
3121
+ ...(input.source ? { source: input.source } : {}),
3122
+ }),
3123
+ });
3124
+ const body = await response.json();
3125
+ if (!response.ok || body.ok === false) {
3126
+ throw new Error(`hosted Mail Control ensure failed (${response.status}): ${body.error ?? response.statusText}`);
3127
+ }
3128
+ const publicRegistry = requiredResponseRecord(body.publicRegistry, "publicRegistry");
3129
+ const blobStore = requiredResponseRecord(body.blobStore, "blobStore");
3130
+ mailboxAddress = typeof body.mailboxAddress === "string" && body.mailboxAddress.trim()
3131
+ ? body.mailboxAddress.trim()
3132
+ : requiredResponseText(requiredResponseRecord(body.mailbox, "mailbox"), "canonicalAddress", "mailboxAddress");
3133
+ sourceAlias = typeof body.sourceAlias === "string" && body.sourceAlias.trim() ? body.sourceAlias.trim() : null;
3134
+ const generatedPrivateKeys = responsePrivateKeys(body.generatedPrivateKeys);
3135
+ const privateKeys = {
3136
+ ...mailroomPrivateKeys(existingMailroom),
3137
+ ...generatedPrivateKeys,
3138
+ };
3139
+ assertHostedPrivateKeys({
3140
+ agent,
3141
+ keys: privateKeys,
3142
+ requiredKeyIds: requiredHostedKeyIds(body),
3143
+ });
3144
+ mode = "hosted";
3145
+ registryPath = null;
3146
+ storePath = null;
3147
+ hostedControlUrl = hostedConfig.url;
3148
+ blobStoreLabel = `${requiredResponseText(blobStore, "azureAccountUrl", "blobStore.azureAccountUrl")}/${requiredResponseText(blobStore, "container", "blobStore.container")}`;
3149
+ nextConfig = {
3150
+ ...currentConfig,
3151
+ mailroom: {
3152
+ ...cleanHostedMailroomBase(existingMailroom),
3153
+ mode,
3154
+ mailboxAddress,
3155
+ sourceAlias,
3156
+ azureAccountUrl: requiredResponseText(blobStore, "azureAccountUrl", "blobStore.azureAccountUrl"),
3157
+ azureContainer: requiredResponseText(blobStore, "container", "blobStore.container"),
3158
+ registryAzureAccountUrl: requiredResponseText(publicRegistry, "azureAccountUrl", "publicRegistry.azureAccountUrl"),
3159
+ registryContainer: requiredResponseText(publicRegistry, "container", "publicRegistry.container"),
3160
+ registryBlob: requiredResponseText(publicRegistry, "blob", "publicRegistry.blob"),
3161
+ registryDomain: requiredResponseText(publicRegistry, "domain", "publicRegistry.domain"),
3162
+ registryRevision: requiredResponseText(publicRegistry, "revision", "publicRegistry.revision"),
3163
+ privateKeys,
3164
+ },
3165
+ };
3166
+ }
3167
+ else {
3168
+ progress.updateDetail("ensuring local Mailroom identity");
3169
+ const ensured = (0, core_1.ensureMailboxRegistry)({
3170
+ agentId: agent,
3171
+ registry: readMailroomRegistryFromDisk(registryPath),
3172
+ keys: mailroomPrivateKeys(existingMailroom),
3173
+ ownerEmail: input.ownerEmail || undefined,
3174
+ source: input.source || undefined,
3175
+ });
3176
+ mailboxAddress = ensured.mailboxAddress;
3177
+ sourceAlias = ensured.sourceAlias;
3178
+ fs.mkdirSync(path.dirname(registryPath), { recursive: true });
3179
+ fs.mkdirSync(storePath, { recursive: true });
3180
+ fs.writeFileSync(registryPath, `${JSON.stringify(ensured.registry, null, 2)}\n`, "utf-8");
3181
+ nextConfig = {
3182
+ ...currentConfig,
3183
+ mailroom: {
3184
+ ...(existingMailroom ?? {}),
3185
+ mode,
3186
+ mailboxAddress,
3187
+ registryPath,
3188
+ storePath,
3189
+ privateKeys: ensured.keys,
3190
+ },
3191
+ };
3192
+ }
2863
3193
  progress.updateDetail("storing private mail keys in vault");
2864
3194
  stored = await (0, runtime_credentials_1.upsertRuntimeCredentialConfig)(agent, nextConfig, providerCliNow(deps));
2865
3195
  progress.updateDetail("enabling Mail sense in agent.json");
@@ -2875,7 +3205,7 @@ async function ensureAgentMailroom(agent, input, deps, progressLabel) {
2875
3205
  if (!stored)
2876
3206
  throw new Error("Mailroom setup did not store runtime credentials");
2877
3207
  const syncSummary = pushAgentBundleAfterCliMutation(agent, deps);
2878
- return { mailboxAddress, sourceAlias, registryPath, storePath, stored, syncSummary };
3208
+ return { mailboxAddress, sourceAlias, mode, registryPath, storePath, hostedControlUrl, blobStoreLabel, stored, syncSummary };
2879
3209
  }
2880
3210
  async function executeConnectMail(agent, deps, input = {}) {
2881
3211
  writeConnectorIntro(deps, {
@@ -2920,8 +3250,10 @@ async function executeConnectMail(agent, deps, input = {}) {
2920
3250
  whatChanged: [
2921
3251
  `Mailbox: ${outcome.mailboxAddress}`,
2922
3252
  ...(outcome.sourceAlias ? [`Delegated alias: ${outcome.sourceAlias}`] : []),
2923
- `Registry: ${outcome.registryPath}`,
2924
- `Encrypted store: ${outcome.storePath}`,
3253
+ ...(outcome.hostedControlUrl ? [`Hosted Mail Control: ${outcome.hostedControlUrl}`] : []),
3254
+ ...(outcome.blobStoreLabel ? [`Blob store: ${outcome.blobStoreLabel}`] : []),
3255
+ ...(outcome.registryPath ? [`Registry: ${outcome.registryPath}`] : []),
3256
+ ...(outcome.storePath ? [`Encrypted store: ${outcome.storePath}`] : []),
2925
3257
  `Stored: ${outcome.stored.itemPath}`,
2926
3258
  "agent.json: senses.mail.enabled = true",
2927
3259
  "private mail keys were not printed",
@@ -2937,8 +3269,10 @@ async function executeConnectMail(agent, deps, input = {}) {
2937
3269
  `Agent Mail connected for ${agent}`,
2938
3270
  `mailbox: ${outcome.mailboxAddress}`,
2939
3271
  ...(outcome.sourceAlias ? [`delegated alias: ${outcome.sourceAlias}`] : []),
2940
- `registry: ${outcome.registryPath}`,
2941
- `encrypted store: ${outcome.storePath}`,
3272
+ ...(outcome.hostedControlUrl ? [`Hosted Mail Control: ${outcome.hostedControlUrl}`] : []),
3273
+ ...(outcome.blobStoreLabel ? [`Blob store: ${outcome.blobStoreLabel}`] : []),
3274
+ ...(outcome.registryPath ? [`registry: ${outcome.registryPath}`] : []),
3275
+ ...(outcome.storePath ? [`encrypted store: ${outcome.storePath}`] : []),
2942
3276
  `stored: ${outcome.stored.itemPath}`,
2943
3277
  "agent.json: senses.mail.enabled = true",
2944
3278
  "private mail keys were not printed",
@@ -2983,9 +3317,12 @@ async function executeAccountEnsure(command, deps) {
2983
3317
  "Vault item: runtime/config",
2984
3318
  `Mailbox: ${outcome.mailboxAddress}`,
2985
3319
  ...(outcome.sourceAlias ? [`Delegated alias: ${outcome.sourceAlias}`] : []),
2986
- `Registry: ${outcome.registryPath}`,
2987
- `Encrypted store: ${outcome.storePath}`,
3320
+ ...(outcome.hostedControlUrl ? [`Hosted Mail Control: ${outcome.hostedControlUrl}`] : []),
3321
+ ...(outcome.blobStoreLabel ? [`Blob store: ${outcome.blobStoreLabel}`] : []),
3322
+ ...(outcome.registryPath ? [`Registry: ${outcome.registryPath}`] : []),
3323
+ ...(outcome.storePath ? [`Encrypted store: ${outcome.storePath}`] : []),
2988
3324
  "agent.json: senses.mail.enabled = true",
3325
+ "private mail keys were not printed",
2989
3326
  ...(outcome.syncSummary ? [outcome.syncSummary] : []),
2990
3327
  ],
2991
3328
  nextMoves: [
@@ -2998,9 +3335,12 @@ async function executeAccountEnsure(command, deps) {
2998
3335
  "vault: runtime/config",
2999
3336
  `mailbox: ${outcome.mailboxAddress}`,
3000
3337
  ...(outcome.sourceAlias ? [`delegated alias: ${outcome.sourceAlias}`] : []),
3001
- `registry: ${outcome.registryPath}`,
3002
- `encrypted store: ${outcome.storePath}`,
3338
+ ...(outcome.hostedControlUrl ? [`Hosted Mail Control: ${outcome.hostedControlUrl}`] : []),
3339
+ ...(outcome.blobStoreLabel ? [`Blob store: ${outcome.blobStoreLabel}`] : []),
3340
+ ...(outcome.registryPath ? [`registry: ${outcome.registryPath}`] : []),
3341
+ ...(outcome.storePath ? [`encrypted store: ${outcome.storePath}`] : []),
3003
3342
  "agent.json: senses.mail.enabled = true",
3343
+ "private mail keys were not printed",
3004
3344
  ...(outcome.syncSummary ? [outcome.syncSummary] : []),
3005
3345
  ],
3006
3346
  });
@@ -5426,11 +5766,17 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
5426
5766
  if (command.kind === "vault.config.status") {
5427
5767
  return executeVaultConfigStatus(command, deps);
5428
5768
  }
5429
- if (command.kind === "vault.ops.porkbun.set") {
5430
- return executeVaultOpsPorkbunSet(command, deps);
5769
+ if (command.kind === "vault.item.set") {
5770
+ return executeVaultItemSet(command, deps);
5771
+ }
5772
+ if (command.kind === "vault.item.status") {
5773
+ return executeVaultItemStatus(command, deps);
5774
+ }
5775
+ if (command.kind === "vault.item.list") {
5776
+ return executeVaultItemList(command, deps);
5431
5777
  }
5432
- if (command.kind === "vault.ops.porkbun.status") {
5433
- return executeVaultOpsPorkbunStatus(command, deps);
5778
+ if (command.kind === "dns.workflow") {
5779
+ return executeDnsWorkflow(command, deps);
5434
5780
  }
5435
5781
  // ── auth (local, no daemon socket needed) ──
5436
5782
  if (command.kind === "auth.run") {