@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.
- package/README.md +4 -0
- package/changelog.json +16 -0
- package/dist/heart/daemon/cli-exec.js +422 -76
- package/dist/heart/daemon/cli-help.js +19 -4
- package/dist/heart/daemon/cli-parse.js +180 -4
- package/dist/heart/daemon/dns-workflow.js +365 -0
- package/dist/heart/daemon/vault-items.js +56 -0
- package/dist/heart/outlook/readers/mail.js +34 -1
- package/dist/mailroom/attention.js +13 -0
- package/dist/mailroom/core.js +27 -0
- package/dist/mailroom/outbound.js +4 -0
- package/dist/nerves/coverage/file-completeness.js +1 -1
- package/dist/repertoire/tools-credential.js +37 -17
- package/dist/repertoire/tools-mail.js +22 -1
- package/dist/senses/mail.js +33 -25
- package/package.json +1 -1
- package/dist/heart/daemon/porkbun-ops.js +0 -25
|
@@ -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
|
|
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.
|
|
330
|
-
case "vault.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
2029
|
-
|
|
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
|
|
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(
|
|
2037
|
-
username: account,
|
|
2106
|
+
await store.store(command.item, {
|
|
2107
|
+
...(account ? { username: account } : {}),
|
|
2038
2108
|
password: JSON.stringify(payload),
|
|
2039
|
-
|
|
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
|
|
2115
|
+
return command.item;
|
|
2042
2116
|
}, () => "secret stored");
|
|
2043
2117
|
}
|
|
2044
2118
|
finally {
|
|
2045
2119
|
progress.end();
|
|
2046
2120
|
}
|
|
2047
2121
|
const message = [
|
|
2048
|
-
|
|
2049
|
-
`item
|
|
2050
|
-
`
|
|
2051
|
-
|
|
2052
|
-
|
|
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
|
|
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.
|
|
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
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
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
|
-
|
|
2077
|
-
|
|
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
|
-
|
|
2080
|
-
const
|
|
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
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
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
|
-
`
|
|
2924
|
-
`
|
|
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
|
-
`
|
|
2941
|
-
`
|
|
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
|
-
`
|
|
2987
|
-
`
|
|
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
|
-
`
|
|
3002
|
-
`
|
|
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.
|
|
5430
|
-
return
|
|
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 === "
|
|
5433
|
-
return
|
|
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") {
|