@ouro.bot/cli 0.1.0-alpha.465 → 0.1.0-alpha.466
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 +8 -0
- package/dist/heart/daemon/cli-exec.js +144 -45
- package/dist/heart/daemon/cli-help.js +19 -4
- package/dist/heart/daemon/cli-parse.js +109 -4
- package/dist/heart/daemon/vault-items.js +56 -0
- package/dist/nerves/coverage/file-completeness.js +1 -1
- package/dist/repertoire/tools-credential.js +37 -17
- package/package.json +1 -1
- package/dist/heart/daemon/porkbun-ops.js +0 -25
package/README.md
CHANGED
|
@@ -180,6 +180,8 @@ ouro vault unlock --agent <name>
|
|
|
180
180
|
ouro vault status --agent <name>
|
|
181
181
|
ouro vault config set --agent <name> --key teams.clientSecret
|
|
182
182
|
ouro vault config status --agent <name> --scope all
|
|
183
|
+
ouro vault item set --agent <name> --item <path> --secret-field <field>
|
|
184
|
+
ouro vault item status --agent <name> --item <path>
|
|
183
185
|
ouro vault ops porkbun set --agent <name> --account <account>
|
|
184
186
|
ouro connect --agent <name>
|
|
185
187
|
ouro connect providers --agent <name>
|
|
@@ -208,6 +210,8 @@ ouro mcp-serve --agent <name> # start MCP server on stdin/stdout (us
|
|
|
208
210
|
ouro hook <event> --agent <name> # fire a lifecycle hook (SessionStart, Stop, PostToolUse)
|
|
209
211
|
```
|
|
210
212
|
|
|
213
|
+
The generic secret primitive is a vault item / credential in the owning agent vault: stable item name/path, hidden secret material, optional public fields, notes, timestamps/provenance, and no assumed use. `ouro connect` is for harness-managed workflows; workflow bindings reference ordinary vault items when they need secret material.
|
|
214
|
+
|
|
211
215
|
## Setting Up On Another Machine
|
|
212
216
|
|
|
213
217
|
To clone an existing agent onto a new machine (macOS, Linux, or Windows via WSL2), see **[docs/cross-machine-setup.md](docs/cross-machine-setup.md)**. The short version is bundle plus vault: `npx ouro.bot@latest`, open the home deck, choose clone, enter the bundle's git remote URL, unlock the agent vault, refresh/verify credentials, and start with `ouro up`.
|
package/changelog.json
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.466",
|
|
6
|
+
"changes": [
|
|
7
|
+
"`ouro vault item set/status/list` is now the generic human-facing path for storing ordinary agent-owned vault items with hidden secret fields, optional public fields, freeform notes, metadata-only status/list output, and no assumed provider semantics.",
|
|
8
|
+
"`ouro vault ops porkbun` remains as a deprecated compatibility alias over ordinary vault items, while docs and help teach vault item -> managed workflow -> non-secret binding instead of treating Porkbun, DNS, or ops credentials as separate credential species.",
|
|
9
|
+
"Vault item guardrails now keep harness-managed provider/runtime items on `ouro auth`, `ouro connect`, and `ouro vault config`, and coverage locks no-secret logging, notes-as-orientation, reserved item errors, compatibility behavior, and 100% branch coverage for the new surface."
|
|
10
|
+
]
|
|
11
|
+
},
|
|
4
12
|
{
|
|
5
13
|
"version": "0.1.0-alpha.465",
|
|
6
14
|
"changes": [
|
|
@@ -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,9 @@ 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":
|
|
331
332
|
case "connect":
|
|
332
333
|
case "account.ensure":
|
|
333
334
|
case "mail.import-mbox":
|
|
@@ -2012,75 +2013,170 @@ async function executeVaultConfigStatus(command, deps) {
|
|
|
2012
2013
|
deps.writeStdout(message);
|
|
2013
2014
|
return message;
|
|
2014
2015
|
}
|
|
2015
|
-
|
|
2016
|
+
function parseVaultItemPublicFields(fields) {
|
|
2017
|
+
const parsed = {};
|
|
2018
|
+
for (const field of fields ?? []) {
|
|
2019
|
+
const separator = field.indexOf("=");
|
|
2020
|
+
const key = (0, vault_items_1.normalizeVaultItemFieldName)(field.slice(0, separator));
|
|
2021
|
+
parsed[key] = field.slice(separator + 1);
|
|
2022
|
+
}
|
|
2023
|
+
return parsed;
|
|
2024
|
+
}
|
|
2025
|
+
function uniqueVaultItemSecretFields(command) {
|
|
2026
|
+
const fields = command.template ? (0, vault_items_1.vaultItemTemplateSecretFields)(command.template) : [];
|
|
2027
|
+
for (const field of command.secretFields ?? []) {
|
|
2028
|
+
if (!fields.includes(field))
|
|
2029
|
+
fields.push(field);
|
|
2030
|
+
}
|
|
2031
|
+
return fields;
|
|
2032
|
+
}
|
|
2033
|
+
function vaultItemCompatibilityNotice(command) {
|
|
2034
|
+
if (command.compatibilityAlias !== vault_items_1.PORKBUN_OPS_COMPATIBILITY_ALIAS)
|
|
2035
|
+
return [];
|
|
2036
|
+
return ["deprecated compatibility alias: use ouro vault item set --template porkbun-api"];
|
|
2037
|
+
}
|
|
2038
|
+
function freeformVaultItemReservedMessage(itemName) {
|
|
2039
|
+
if (itemName.startsWith("providers/")) {
|
|
2040
|
+
return `Vault item "${itemName}" is reserved for harness-managed workflows. Use ouro auth or ouro connect for provider credentials.`;
|
|
2041
|
+
}
|
|
2042
|
+
if (itemName === "runtime/config" || /^runtime\/machines\/[^/]+\/config$/.test(itemName)) {
|
|
2043
|
+
return `Vault item "${itemName}" is reserved for harness-managed workflows. Use ouro connect or ouro vault config for runtime and sense configuration.`;
|
|
2044
|
+
}
|
|
2045
|
+
return undefined;
|
|
2046
|
+
}
|
|
2047
|
+
function assertFreeformVaultItemWritable(itemName) {
|
|
2048
|
+
const reservedMessage = freeformVaultItemReservedMessage(itemName);
|
|
2049
|
+
if (reservedMessage)
|
|
2050
|
+
throw new Error(reservedMessage);
|
|
2051
|
+
}
|
|
2052
|
+
function porkbunAccountForCompatibility(command) {
|
|
2053
|
+
if (command.compatibilityAlias !== vault_items_1.PORKBUN_OPS_COMPATIBILITY_ALIAS)
|
|
2054
|
+
return undefined;
|
|
2055
|
+
return (0, vault_items_1.normalizePorkbunOpsAccount)((0, vault_items_1.porkbunOpsAccountFromItemName)(command.item));
|
|
2056
|
+
}
|
|
2057
|
+
const PORKBUN_OPS_PROMPT_LABELS = {
|
|
2058
|
+
apiKey: (account) => `Porkbun API key for ${account}: `,
|
|
2059
|
+
secretApiKey: (account) => `Porkbun Secret API key for ${account}: `,
|
|
2060
|
+
};
|
|
2061
|
+
const PORKBUN_OPS_VALIDATION_LABELS = {
|
|
2062
|
+
apiKey: "Porkbun API key",
|
|
2063
|
+
secretApiKey: "Porkbun Secret API key",
|
|
2064
|
+
};
|
|
2065
|
+
function vaultItemTemplatePromptLabel(command, field, account) {
|
|
2066
|
+
if (command.compatibilityAlias === vault_items_1.PORKBUN_OPS_COMPATIBILITY_ALIAS) {
|
|
2067
|
+
return PORKBUN_OPS_PROMPT_LABELS[field](account);
|
|
2068
|
+
}
|
|
2069
|
+
return `Secret field ${field} for ${command.item}: `;
|
|
2070
|
+
}
|
|
2071
|
+
function vaultItemSecretValidationLabel(command, field) {
|
|
2072
|
+
if (command.compatibilityAlias === vault_items_1.PORKBUN_OPS_COMPATIBILITY_ALIAS) {
|
|
2073
|
+
return PORKBUN_OPS_VALIDATION_LABELS[field];
|
|
2074
|
+
}
|
|
2075
|
+
return `Secret field ${field}`;
|
|
2076
|
+
}
|
|
2077
|
+
async function executeVaultItemSet(command, deps) {
|
|
2016
2078
|
if (command.agent === "SerpentGuide") {
|
|
2017
|
-
throw new Error("SerpentGuide has no persistent credential vault. Store
|
|
2079
|
+
throw new Error("SerpentGuide has no persistent credential vault. Store vault items in the owning agent vault.");
|
|
2080
|
+
}
|
|
2081
|
+
if (!command.compatibilityAlias)
|
|
2082
|
+
assertFreeformVaultItemWritable(command.item);
|
|
2083
|
+
const account = porkbunAccountForCompatibility(command);
|
|
2084
|
+
const promptSecret = requirePromptSecret(deps, command.compatibilityAlias === vault_items_1.PORKBUN_OPS_COMPATIBILITY_ALIAS ? "Porkbun ops credential entry" : "Vault item secret entry");
|
|
2085
|
+
const secretFieldNames = uniqueVaultItemSecretFields(command);
|
|
2086
|
+
const publicFields = parseVaultItemPublicFields(command.publicFields);
|
|
2087
|
+
if (account && !publicFields.account)
|
|
2088
|
+
publicFields.account = account;
|
|
2089
|
+
const secretFields = {};
|
|
2090
|
+
for (const field of secretFieldNames) {
|
|
2091
|
+
const normalized = (0, vault_items_1.normalizeVaultItemFieldName)(field);
|
|
2092
|
+
const value = await promptSecret(vaultItemTemplatePromptLabel(command, normalized, account));
|
|
2093
|
+
secretFields[normalized] = (0, vault_items_1.requireVaultItemSecret)(value, vaultItemSecretValidationLabel(command, normalized));
|
|
2018
2094
|
}
|
|
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
2095
|
const payload = {
|
|
2025
2096
|
schemaVersion: 1,
|
|
2026
|
-
kind: porkbun_ops_1.PORKBUN_OPS_CREDENTIAL_KIND,
|
|
2027
2097
|
updatedAt: providerCliNow(deps).toISOString(),
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
secretApiKey,
|
|
2098
|
+
publicFields,
|
|
2099
|
+
secretFields,
|
|
2031
2100
|
};
|
|
2032
|
-
const progress = createHumanCommandProgress(deps, "vault ops porkbun");
|
|
2101
|
+
const progress = createHumanCommandProgress(deps, command.compatibilityAlias === vault_items_1.PORKBUN_OPS_COMPATIBILITY_ALIAS ? "vault ops porkbun" : "vault item set");
|
|
2033
2102
|
try {
|
|
2034
|
-
await runCommandProgressPhase(progress, "storing
|
|
2103
|
+
await runCommandProgressPhase(progress, command.compatibilityAlias === vault_items_1.PORKBUN_OPS_COMPATIBILITY_ALIAS ? "storing ordinary vault item" : "storing vault item", async () => {
|
|
2035
2104
|
const store = (0, credential_access_1.getCredentialStore)(command.agent);
|
|
2036
|
-
await store.store(
|
|
2037
|
-
username: account,
|
|
2105
|
+
await store.store(command.item, {
|
|
2106
|
+
...(account ? { username: account } : {}),
|
|
2038
2107
|
password: JSON.stringify(payload),
|
|
2039
|
-
|
|
2108
|
+
...(command.note !== undefined
|
|
2109
|
+
? { notes: command.note }
|
|
2110
|
+
: command.compatibilityAlias === vault_items_1.PORKBUN_OPS_COMPATIBILITY_ALIAS
|
|
2111
|
+
? { notes: "Ordinary vault item written by a deprecated Porkbun compatibility alias. Notes are for human/agent orientation only; workflow bindings live outside this item." }
|
|
2112
|
+
: {}),
|
|
2040
2113
|
});
|
|
2041
|
-
return
|
|
2114
|
+
return command.item;
|
|
2042
2115
|
}, () => "secret stored");
|
|
2043
2116
|
}
|
|
2044
2117
|
finally {
|
|
2045
2118
|
progress.end();
|
|
2046
2119
|
}
|
|
2047
2120
|
const message = [
|
|
2048
|
-
|
|
2049
|
-
`item
|
|
2050
|
-
`
|
|
2051
|
-
|
|
2052
|
-
|
|
2121
|
+
...vaultItemCompatibilityNotice(command),
|
|
2122
|
+
`stored ordinary vault item for ${command.agent}`,
|
|
2123
|
+
`item: vault:${command.agent}:${command.item}`,
|
|
2124
|
+
...(account ? [`account: ${account}`] : []),
|
|
2125
|
+
`public fields: ${Object.keys(publicFields).length === 0 ? "none" : Object.keys(publicFields).sort().join(", ")}`,
|
|
2126
|
+
`secret fields: ${Object.keys(secretFields).sort().join(", ")}`,
|
|
2127
|
+
`notes: ${command.note !== undefined || command.compatibilityAlias === vault_items_1.PORKBUN_OPS_COMPATIBILITY_ALIAS ? "present" : "absent"}`,
|
|
2053
2128
|
"secret values were not printed",
|
|
2054
2129
|
].join("\n");
|
|
2055
2130
|
deps.writeStdout(message);
|
|
2056
2131
|
return message;
|
|
2057
2132
|
}
|
|
2058
|
-
async function
|
|
2133
|
+
async function executeVaultItemStatus(command, deps) {
|
|
2059
2134
|
if (command.agent === "SerpentGuide") {
|
|
2060
|
-
const message = "SerpentGuide has no persistent credential vault.
|
|
2135
|
+
const message = "SerpentGuide has no persistent credential vault. Vault items belong in the owning agent vault.";
|
|
2061
2136
|
deps.writeStdout(message);
|
|
2062
2137
|
return message;
|
|
2063
2138
|
}
|
|
2064
2139
|
const store = (0, credential_access_1.getCredentialStore)(command.agent);
|
|
2065
|
-
const
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
lines.push(`items: ${items.length === 0 ? "none stored" : items.map((item) => item.domain).sort().join(", ")}`);
|
|
2078
|
-
}
|
|
2140
|
+
const account = porkbunAccountForCompatibility(command);
|
|
2141
|
+
const meta = await store.get(command.item);
|
|
2142
|
+
const lines = [
|
|
2143
|
+
...vaultItemCompatibilityNotice(command),
|
|
2144
|
+
`agent: ${command.agent}`,
|
|
2145
|
+
`${command.compatibilityAlias ? "ordinary vault item" : "item"}: vault:${command.agent}:${command.item}`,
|
|
2146
|
+
`status: ${meta ? "present" : "missing"}`,
|
|
2147
|
+
];
|
|
2148
|
+
if (meta?.username)
|
|
2149
|
+
lines.push(account ? `account: ${meta.username}` : `username: ${meta.username}`);
|
|
2150
|
+
if (meta?.notes)
|
|
2151
|
+
lines.push("notes: present");
|
|
2079
2152
|
lines.push("secret values were not printed");
|
|
2080
2153
|
const message = lines.join("\n");
|
|
2081
2154
|
deps.writeStdout(message);
|
|
2082
2155
|
return message;
|
|
2083
2156
|
}
|
|
2157
|
+
async function executeVaultItemList(command, deps) {
|
|
2158
|
+
if (command.agent === "SerpentGuide") {
|
|
2159
|
+
const message = "SerpentGuide has no persistent credential vault. Vault items belong in the owning agent vault.";
|
|
2160
|
+
deps.writeStdout(message);
|
|
2161
|
+
return message;
|
|
2162
|
+
}
|
|
2163
|
+
const store = (0, credential_access_1.getCredentialStore)(command.agent);
|
|
2164
|
+
const prefix = command.prefix;
|
|
2165
|
+
const items = (await store.list())
|
|
2166
|
+
.filter((item) => !prefix || item.domain.startsWith(prefix.endsWith("/") ? prefix : `${prefix}/`))
|
|
2167
|
+
.map((item) => item.domain)
|
|
2168
|
+
.sort();
|
|
2169
|
+
const lines = [
|
|
2170
|
+
...vaultItemCompatibilityNotice(command),
|
|
2171
|
+
`agent: ${command.agent}`,
|
|
2172
|
+
...(prefix ? [`prefix: ${prefix}`] : []),
|
|
2173
|
+
`items: ${items.length === 0 ? "none stored" : items.join(", ")}`,
|
|
2174
|
+
"secret values were not printed",
|
|
2175
|
+
];
|
|
2176
|
+
const message = lines.join("\n");
|
|
2177
|
+
deps.writeStdout(message);
|
|
2178
|
+
return message;
|
|
2179
|
+
}
|
|
2084
2180
|
function requirePromptSecret(deps, purpose) {
|
|
2085
2181
|
if (deps.promptSecret)
|
|
2086
2182
|
return deps.promptSecret;
|
|
@@ -5426,11 +5522,14 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
5426
5522
|
if (command.kind === "vault.config.status") {
|
|
5427
5523
|
return executeVaultConfigStatus(command, deps);
|
|
5428
5524
|
}
|
|
5429
|
-
if (command.kind === "vault.
|
|
5430
|
-
return
|
|
5525
|
+
if (command.kind === "vault.item.set") {
|
|
5526
|
+
return executeVaultItemSet(command, deps);
|
|
5527
|
+
}
|
|
5528
|
+
if (command.kind === "vault.item.status") {
|
|
5529
|
+
return executeVaultItemStatus(command, deps);
|
|
5431
5530
|
}
|
|
5432
|
-
if (command.kind === "vault.
|
|
5433
|
-
return
|
|
5531
|
+
if (command.kind === "vault.item.list") {
|
|
5532
|
+
return executeVaultItemList(command, deps);
|
|
5434
5533
|
}
|
|
5435
5534
|
// ── auth (local, no daemon socket needed) ──
|
|
5436
5535
|
if (command.kind === "auth.run") {
|
|
@@ -212,9 +212,9 @@ exports.COMMAND_REGISTRY = {
|
|
|
212
212
|
vault: {
|
|
213
213
|
category: "Auth",
|
|
214
214
|
description: "Create, replace, recover, unlock, inspect, and populate the agent credential vault",
|
|
215
|
-
usage: "ouro vault <create|replace|recover|unlock|status|config|ops> [--agent <name>]",
|
|
215
|
+
usage: "ouro vault <create|replace|recover|unlock|status|config|item|ops> [--agent <name>]",
|
|
216
216
|
example: "ouro vault status",
|
|
217
|
-
subcommands: ["create", "replace", "recover", "unlock", "status", "config set", "config status", "ops porkbun set", "ops porkbun status"],
|
|
217
|
+
subcommands: ["create", "replace", "recover", "unlock", "status", "config set", "config status", "vault item set", "vault item status", "vault item list", "vault ops porkbun set", "vault ops porkbun status"],
|
|
218
218
|
},
|
|
219
219
|
thoughts: {
|
|
220
220
|
category: "Internal",
|
|
@@ -366,13 +366,28 @@ const SUBCOMMAND_HELP = {
|
|
|
366
366
|
usage: "ouro vault config status [--agent <name>] [--scope agent|machine|all]",
|
|
367
367
|
example: "ouro vault config status --scope all",
|
|
368
368
|
},
|
|
369
|
+
"vault item set": {
|
|
370
|
+
description: "Store an ordinary vault item / credential with no assumed use. Prompts for hidden secret fields, stores optional public fields and notes, and secret values are not printed.",
|
|
371
|
+
usage: "ouro vault item set [--agent <name>] --item <path> (--secret-field <name>...|--template <template>) [--public-field <key=value>] [--note <text>]",
|
|
372
|
+
example: "ouro vault item set --agent slugger --item ops/porkbun/ari@mendelow.me --template porkbun-api",
|
|
373
|
+
},
|
|
374
|
+
"vault item status": {
|
|
375
|
+
description: "Show metadata for an ordinary vault item without printing secret values",
|
|
376
|
+
usage: "ouro vault item status [--agent <name>] --item <path>",
|
|
377
|
+
example: "ouro vault item status --agent slugger --item ops/porkbun/ari@mendelow.me",
|
|
378
|
+
},
|
|
379
|
+
"vault item list": {
|
|
380
|
+
description: "List ordinary vault item names and metadata without printing secret values",
|
|
381
|
+
usage: "ouro vault item list [--agent <name>] [--prefix <path-prefix>]",
|
|
382
|
+
example: "ouro vault item list --agent slugger --prefix ops/",
|
|
383
|
+
},
|
|
369
384
|
"vault ops porkbun set": {
|
|
370
|
-
description: "
|
|
385
|
+
description: "deprecated compatibility alias for `ouro vault item set --template porkbun-api`; stores an ordinary vault item, not a special credential kind",
|
|
371
386
|
usage: "ouro vault ops porkbun set [--agent <name>] --account <account>",
|
|
372
387
|
example: "ouro vault ops porkbun set --agent slugger --account ari@mendelow.me",
|
|
373
388
|
},
|
|
374
389
|
"vault ops porkbun status": {
|
|
375
|
-
description: "
|
|
390
|
+
description: "deprecated compatibility alias for checking the ordinary vault item used by the Porkbun API template",
|
|
376
391
|
usage: "ouro vault ops porkbun status [--agent <name>] [--account <account>]",
|
|
377
392
|
example: "ouro vault ops porkbun status --agent slugger --account ari@mendelow.me",
|
|
378
393
|
},
|
|
@@ -16,7 +16,7 @@ exports.parseMcpServeCommand = parseMcpServeCommand;
|
|
|
16
16
|
exports.parseOuroCommand = parseOuroCommand;
|
|
17
17
|
const types_1 = require("../../mind/friends/types");
|
|
18
18
|
const cli_help_1 = require("./cli-help");
|
|
19
|
-
const
|
|
19
|
+
const vault_items_1 = require("./vault-items");
|
|
20
20
|
// ── Shared helpers ──
|
|
21
21
|
function extractAgentFlag(args) {
|
|
22
22
|
const idx = args.indexOf("--agent");
|
|
@@ -96,6 +96,9 @@ function usage() {
|
|
|
96
96
|
" ouro vault status [--agent <name>] [--store auto|macos-keychain|windows-dpapi|linux-secret-service|plaintext-file]",
|
|
97
97
|
" ouro vault config set [--agent <name>] --key <path> [--value <value>] [--scope agent|machine]",
|
|
98
98
|
" ouro vault config status [--agent <name>] [--scope agent|machine|all]",
|
|
99
|
+
" ouro vault item set [--agent <name>] --item <path> --secret-field <name> [--public-field <key=value>] [--note <text>]",
|
|
100
|
+
" ouro vault item status [--agent <name>] --item <path>",
|
|
101
|
+
" ouro vault item list [--agent <name>] [--prefix <path-prefix>]",
|
|
99
102
|
" ouro vault ops porkbun set [--agent <name>] --account <account>",
|
|
100
103
|
" ouro vault ops porkbun status [--agent <name>] [--account <account>]",
|
|
101
104
|
" ouro chat <agent>",
|
|
@@ -461,6 +464,8 @@ function parseVaultCommand(args) {
|
|
|
461
464
|
const sub = args[0];
|
|
462
465
|
if (sub === "config")
|
|
463
466
|
return parseVaultConfigCommand(args.slice(1));
|
|
467
|
+
if (sub === "item")
|
|
468
|
+
return parseVaultItemCommand(args.slice(1));
|
|
464
469
|
if (sub === "ops")
|
|
465
470
|
return parseVaultOpsCommand(args.slice(1));
|
|
466
471
|
const { agent, rest } = extractAgentFlag(args.slice(1));
|
|
@@ -549,6 +554,87 @@ function parseVaultCommand(args) {
|
|
|
549
554
|
}
|
|
550
555
|
return { kind: "vault.status", ...(agent ? { agent } : {}), ...(store ? { store } : {}) };
|
|
551
556
|
}
|
|
557
|
+
function parseVaultItemCommand(args) {
|
|
558
|
+
const action = args[0];
|
|
559
|
+
if (action !== "set" && action !== "status" && action !== "list") {
|
|
560
|
+
throw new Error("Usage: ouro vault item set|status|list [--agent <name>] --item <path>");
|
|
561
|
+
}
|
|
562
|
+
const { agent, rest } = extractAgentFlag(args.slice(1));
|
|
563
|
+
let item;
|
|
564
|
+
let prefix;
|
|
565
|
+
let template;
|
|
566
|
+
let note;
|
|
567
|
+
const secretFields = [];
|
|
568
|
+
const publicFields = [];
|
|
569
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
570
|
+
const token = rest[i];
|
|
571
|
+
if (token === "--item") {
|
|
572
|
+
item = (0, vault_items_1.normalizeVaultItemName)(rest[i + 1]);
|
|
573
|
+
i += 1;
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
if (token === "--prefix") {
|
|
577
|
+
const value = rest[i + 1]?.trim() ?? "";
|
|
578
|
+
if (!value || /[\r\n\t]/.test(value) || value.startsWith("/")) {
|
|
579
|
+
throw new Error("Vault item prefix must be non-empty, relative, and free of control characters.");
|
|
580
|
+
}
|
|
581
|
+
prefix = value;
|
|
582
|
+
i += 1;
|
|
583
|
+
continue;
|
|
584
|
+
}
|
|
585
|
+
if (token === "--template") {
|
|
586
|
+
const value = rest[i + 1];
|
|
587
|
+
if (!(0, vault_items_1.isVaultItemTemplate)(value)) {
|
|
588
|
+
throw new Error("vault item --template must be porkbun-api");
|
|
589
|
+
}
|
|
590
|
+
template = value;
|
|
591
|
+
i += 1;
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
if (token === "--secret-field") {
|
|
595
|
+
secretFields.push((0, vault_items_1.normalizeVaultItemFieldName)(rest[i + 1]));
|
|
596
|
+
i += 1;
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
if (token === "--public-field") {
|
|
600
|
+
const value = rest[i + 1]?.trim() ?? "";
|
|
601
|
+
const separator = value.indexOf("=");
|
|
602
|
+
if (separator <= 0 || separator === value.length - 1) {
|
|
603
|
+
throw new Error("vault item --public-field must be key=value");
|
|
604
|
+
}
|
|
605
|
+
(0, vault_items_1.normalizeVaultItemFieldName)(value.slice(0, separator));
|
|
606
|
+
publicFields.push(value);
|
|
607
|
+
i += 1;
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
610
|
+
if (token === "--note") {
|
|
611
|
+
note = rest[i + 1] ?? "";
|
|
612
|
+
i += 1;
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
throw new Error(`Usage: ouro vault item ${action} [--agent <name>] --item <path>`);
|
|
616
|
+
}
|
|
617
|
+
if (action === "list") {
|
|
618
|
+
return { kind: "vault.item.list", ...(agent ? { agent } : {}), ...(prefix ? { prefix } : {}) };
|
|
619
|
+
}
|
|
620
|
+
if (!item)
|
|
621
|
+
throw new Error(`Usage: ouro vault item ${action} [--agent <name>] --item <path>`);
|
|
622
|
+
if (action === "status") {
|
|
623
|
+
return { kind: "vault.item.status", ...(agent ? { agent } : {}), item };
|
|
624
|
+
}
|
|
625
|
+
if (!template && secretFields.length === 0) {
|
|
626
|
+
throw new Error("ouro vault item set requires --secret-field or --template");
|
|
627
|
+
}
|
|
628
|
+
return {
|
|
629
|
+
kind: "vault.item.set",
|
|
630
|
+
...(agent ? { agent } : {}),
|
|
631
|
+
item,
|
|
632
|
+
...(template ? { template } : {}),
|
|
633
|
+
...(secretFields.length > 0 ? { secretFields } : {}),
|
|
634
|
+
...(publicFields.length > 0 ? { publicFields } : {}),
|
|
635
|
+
...(note !== undefined ? { note } : {}),
|
|
636
|
+
};
|
|
637
|
+
}
|
|
552
638
|
function parseVaultOpsCommand(args) {
|
|
553
639
|
const provider = args[0];
|
|
554
640
|
const action = args[1];
|
|
@@ -563,7 +649,7 @@ function parseVaultOpsCommand(args) {
|
|
|
563
649
|
for (let i = 0; i < rest.length; i += 1) {
|
|
564
650
|
const token = rest[i];
|
|
565
651
|
if (token === "--account") {
|
|
566
|
-
account = (0,
|
|
652
|
+
account = (0, vault_items_1.normalizePorkbunOpsAccount)(rest[i + 1]);
|
|
567
653
|
i += 1;
|
|
568
654
|
continue;
|
|
569
655
|
}
|
|
@@ -572,9 +658,28 @@ function parseVaultOpsCommand(args) {
|
|
|
572
658
|
if (action === "set") {
|
|
573
659
|
if (!account)
|
|
574
660
|
throw new Error("Usage: ouro vault ops porkbun set [--agent <name>] --account <account>");
|
|
575
|
-
return {
|
|
661
|
+
return {
|
|
662
|
+
kind: "vault.item.set",
|
|
663
|
+
...(agent ? { agent } : {}),
|
|
664
|
+
item: (0, vault_items_1.porkbunOpsCredentialItemName)(account),
|
|
665
|
+
template: "porkbun-api",
|
|
666
|
+
compatibilityAlias: vault_items_1.PORKBUN_OPS_COMPATIBILITY_ALIAS,
|
|
667
|
+
};
|
|
576
668
|
}
|
|
577
|
-
|
|
669
|
+
if (account) {
|
|
670
|
+
return {
|
|
671
|
+
kind: "vault.item.status",
|
|
672
|
+
...(agent ? { agent } : {}),
|
|
673
|
+
item: (0, vault_items_1.porkbunOpsCredentialItemName)(account),
|
|
674
|
+
compatibilityAlias: vault_items_1.PORKBUN_OPS_COMPATIBILITY_ALIAS,
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
return {
|
|
678
|
+
kind: "vault.item.list",
|
|
679
|
+
...(agent ? { agent } : {}),
|
|
680
|
+
prefix: vault_items_1.PORKBUN_OPS_CREDENTIAL_PREFIX,
|
|
681
|
+
compatibilityAlias: vault_items_1.PORKBUN_OPS_COMPATIBILITY_ALIAS,
|
|
682
|
+
};
|
|
578
683
|
}
|
|
579
684
|
function parseVaultConfigCommand(args) {
|
|
580
685
|
const sub = args[0];
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PORKBUN_OPS_COMPATIBILITY_ALIAS = exports.PORKBUN_OPS_CREDENTIAL_PREFIX = void 0;
|
|
4
|
+
exports.isVaultItemTemplate = isVaultItemTemplate;
|
|
5
|
+
exports.normalizeVaultItemName = normalizeVaultItemName;
|
|
6
|
+
exports.normalizeVaultItemFieldName = normalizeVaultItemFieldName;
|
|
7
|
+
exports.vaultItemTemplateSecretFields = vaultItemTemplateSecretFields;
|
|
8
|
+
exports.normalizePorkbunOpsAccount = normalizePorkbunOpsAccount;
|
|
9
|
+
exports.porkbunOpsCredentialItemName = porkbunOpsCredentialItemName;
|
|
10
|
+
exports.porkbunOpsAccountFromItemName = porkbunOpsAccountFromItemName;
|
|
11
|
+
exports.requireVaultItemSecret = requireVaultItemSecret;
|
|
12
|
+
exports.PORKBUN_OPS_CREDENTIAL_PREFIX = "ops/registrars/porkbun/accounts";
|
|
13
|
+
exports.PORKBUN_OPS_COMPATIBILITY_ALIAS = "vault ops porkbun";
|
|
14
|
+
const VAULT_ITEM_NAME_FORBIDDEN = /[\r\n\t]/;
|
|
15
|
+
const VAULT_ITEM_FIELD_FORBIDDEN = /[\r\n\t=]/;
|
|
16
|
+
const PORKBUN_OPS_ACCOUNT_FORBIDDEN = /[\/\r\n\t]/;
|
|
17
|
+
function isVaultItemTemplate(value) {
|
|
18
|
+
return value === "porkbun-api";
|
|
19
|
+
}
|
|
20
|
+
function normalizeVaultItemName(item) {
|
|
21
|
+
const normalized = item?.trim() ?? "";
|
|
22
|
+
if (!normalized || VAULT_ITEM_NAME_FORBIDDEN.test(normalized) || normalized.startsWith("/") || normalized.endsWith("/")) {
|
|
23
|
+
throw new Error("Vault item name/path must be non-empty, relative, and free of control characters.");
|
|
24
|
+
}
|
|
25
|
+
return normalized;
|
|
26
|
+
}
|
|
27
|
+
function normalizeVaultItemFieldName(field) {
|
|
28
|
+
const normalized = field?.trim() ?? "";
|
|
29
|
+
if (!normalized || VAULT_ITEM_FIELD_FORBIDDEN.test(normalized)) {
|
|
30
|
+
throw new Error("Vault item field names must be non-empty and free of control characters or '='.");
|
|
31
|
+
}
|
|
32
|
+
return normalized;
|
|
33
|
+
}
|
|
34
|
+
function vaultItemTemplateSecretFields(_template) {
|
|
35
|
+
return ["apiKey", "secretApiKey"];
|
|
36
|
+
}
|
|
37
|
+
function normalizePorkbunOpsAccount(account) {
|
|
38
|
+
const normalized = account?.trim() ?? "";
|
|
39
|
+
if (!normalized || PORKBUN_OPS_ACCOUNT_FORBIDDEN.test(normalized)) {
|
|
40
|
+
throw new Error("Porkbun account must be a non-empty account label without slashes or control characters.");
|
|
41
|
+
}
|
|
42
|
+
return normalized;
|
|
43
|
+
}
|
|
44
|
+
function porkbunOpsCredentialItemName(account) {
|
|
45
|
+
return `${exports.PORKBUN_OPS_CREDENTIAL_PREFIX}/${normalizePorkbunOpsAccount(account)}`;
|
|
46
|
+
}
|
|
47
|
+
function porkbunOpsAccountFromItemName(itemName) {
|
|
48
|
+
const prefix = `${exports.PORKBUN_OPS_CREDENTIAL_PREFIX}/`;
|
|
49
|
+
return itemName.startsWith(prefix) ? itemName.slice(prefix.length) : undefined;
|
|
50
|
+
}
|
|
51
|
+
function requireVaultItemSecret(value, label) {
|
|
52
|
+
const trimmed = value.trim();
|
|
53
|
+
if (!trimmed)
|
|
54
|
+
throw new Error(`${label} cannot be blank`);
|
|
55
|
+
return trimmed;
|
|
56
|
+
}
|
|
@@ -63,7 +63,7 @@ const DISPATCH_EXEMPT_PATTERNS = [
|
|
|
63
63
|
"daemon/cli-parse",
|
|
64
64
|
"daemon/cli-render",
|
|
65
65
|
"daemon/cli-help",
|
|
66
|
-
"daemon/
|
|
66
|
+
"daemon/vault-items",
|
|
67
67
|
// Shared utility modules: pure helpers consumed by modules that own observability.
|
|
68
68
|
"arc/json-store",
|
|
69
69
|
"repertoire/api-client",
|
|
@@ -72,6 +72,11 @@ function optionalTrimmedText(value, fieldName) {
|
|
|
72
72
|
const trimmed = value.trim();
|
|
73
73
|
return trimmed.length > 0 ? trimmed : undefined;
|
|
74
74
|
}
|
|
75
|
+
function resolveVaultItemArg(args, legacyFieldName = "domain") {
|
|
76
|
+
if (args.item !== undefined)
|
|
77
|
+
return requireTrimmedText(args.item, "item");
|
|
78
|
+
return requireTrimmedText(args[legacyFieldName], legacyFieldName);
|
|
79
|
+
}
|
|
75
80
|
function parsePasswordLength(value) {
|
|
76
81
|
if (value === undefined || value === null || value === "")
|
|
77
82
|
return DEFAULT_PASSWORD_LENGTH;
|
|
@@ -127,13 +132,17 @@ exports.credentialToolDefinitions = [
|
|
|
127
132
|
type: "function",
|
|
128
133
|
function: {
|
|
129
134
|
name: "credential_get",
|
|
130
|
-
description: "Get credential metadata for a
|
|
135
|
+
description: "Get credential metadata for a vault item name/path. Returns username, notes, and creation date. Never returns passwords — the credential gateway handles secret injection internally.",
|
|
131
136
|
parameters: {
|
|
132
137
|
type: "object",
|
|
133
138
|
properties: {
|
|
139
|
+
item: {
|
|
140
|
+
type: "string",
|
|
141
|
+
description: "Vault item name/path to look up (e.g. 'airbnb.com' or 'ops/porkbun/account')",
|
|
142
|
+
},
|
|
134
143
|
domain: {
|
|
135
144
|
type: "string",
|
|
136
|
-
description: "
|
|
145
|
+
description: "compatibility alias for item when the vault item name is a service domain",
|
|
137
146
|
},
|
|
138
147
|
},
|
|
139
148
|
required: ["domain"],
|
|
@@ -141,17 +150,18 @@ exports.credentialToolDefinitions = [
|
|
|
141
150
|
},
|
|
142
151
|
},
|
|
143
152
|
handler: async (args) => {
|
|
153
|
+
const itemName = resolveVaultItemArg(args);
|
|
144
154
|
(0, runtime_1.emitNervesEvent)({
|
|
145
155
|
component: "repertoire",
|
|
146
156
|
event: "repertoire.credential_tool_call",
|
|
147
157
|
message: "credential_get invoked",
|
|
148
|
-
meta: { tool: "credential_get", domain:
|
|
158
|
+
meta: { tool: "credential_get", domain: itemName, item: itemName },
|
|
149
159
|
});
|
|
150
160
|
try {
|
|
151
161
|
const store = (0, credential_access_1.getCredentialStore)();
|
|
152
|
-
const meta = await store.get(
|
|
162
|
+
const meta = await store.get(itemName);
|
|
153
163
|
if (!meta) {
|
|
154
|
-
return `No credential found for "${
|
|
164
|
+
return `No credential found for "${itemName}".`;
|
|
155
165
|
}
|
|
156
166
|
return JSON.stringify(meta, null, 2);
|
|
157
167
|
}
|
|
@@ -220,13 +230,17 @@ exports.credentialToolDefinitions = [
|
|
|
220
230
|
type: "function",
|
|
221
231
|
function: {
|
|
222
232
|
name: "credential_store",
|
|
223
|
-
description: "Store credentials the agent acquired or just used successfully
|
|
233
|
+
description: "Store credentials in a vault item name/path after the agent acquired or just used them successfully. Prefer credential_generate_password for new passwords, then call this tool once the site accepts the exact password. Stored passwords are never returned later — only metadata is visible.",
|
|
224
234
|
parameters: {
|
|
225
235
|
type: "object",
|
|
226
236
|
properties: {
|
|
237
|
+
item: {
|
|
238
|
+
type: "string",
|
|
239
|
+
description: "Vault item name/path to store under; domains are examples, not the schema",
|
|
240
|
+
},
|
|
227
241
|
domain: {
|
|
228
242
|
type: "string",
|
|
229
|
-
description: "
|
|
243
|
+
description: "compatibility alias for item when the vault item name is a service domain",
|
|
230
244
|
},
|
|
231
245
|
username: {
|
|
232
246
|
type: "string",
|
|
@@ -238,7 +252,7 @@ exports.credentialToolDefinitions = [
|
|
|
238
252
|
},
|
|
239
253
|
notes: {
|
|
240
254
|
type: "string",
|
|
241
|
-
description: "Optional notes about this credential",
|
|
255
|
+
description: "Optional human/agent orientation notes about this credential; not parsed by code",
|
|
242
256
|
},
|
|
243
257
|
},
|
|
244
258
|
required: ["domain", "username", "password"],
|
|
@@ -250,14 +264,15 @@ exports.credentialToolDefinitions = [
|
|
|
250
264
|
let username = "";
|
|
251
265
|
let password = "";
|
|
252
266
|
let notes;
|
|
267
|
+
const itemNameForEvent = typeof args.item === "string" && args.item.trim() ? args.item.trim() : args.domain;
|
|
253
268
|
(0, runtime_1.emitNervesEvent)({
|
|
254
269
|
component: "repertoire",
|
|
255
270
|
event: "repertoire.credential_tool_call",
|
|
256
271
|
message: "credential_store invoked",
|
|
257
|
-
meta: { tool: "credential_store", domain:
|
|
272
|
+
meta: { tool: "credential_store", domain: itemNameForEvent, item: itemNameForEvent },
|
|
258
273
|
});
|
|
259
274
|
try {
|
|
260
|
-
domain =
|
|
275
|
+
domain = resolveVaultItemArg(args);
|
|
261
276
|
username = requireTrimmedText(args.username, "username");
|
|
262
277
|
password = requireNonBlankSecret(args.password, "password");
|
|
263
278
|
notes = optionalTrimmedText(args.notes, "notes");
|
|
@@ -281,13 +296,13 @@ exports.credentialToolDefinitions = [
|
|
|
281
296
|
type: "function",
|
|
282
297
|
function: {
|
|
283
298
|
name: "credential_list",
|
|
284
|
-
description: "List stored
|
|
299
|
+
description: "List stored vault items. Returns metadata only (item/domain name, username, notes, creation date). Never returns passwords.",
|
|
285
300
|
parameters: {
|
|
286
301
|
type: "object",
|
|
287
302
|
properties: {
|
|
288
303
|
search: {
|
|
289
304
|
type: "string",
|
|
290
|
-
description: "Optional search filter to match against
|
|
305
|
+
description: "Optional search filter to match against vault item names/paths",
|
|
291
306
|
},
|
|
292
307
|
},
|
|
293
308
|
},
|
|
@@ -323,13 +338,17 @@ exports.credentialToolDefinitions = [
|
|
|
323
338
|
type: "function",
|
|
324
339
|
function: {
|
|
325
340
|
name: "credential_delete",
|
|
326
|
-
description: "Delete stored credentials for a
|
|
341
|
+
description: "Delete stored credentials for a vault item name/path.",
|
|
327
342
|
parameters: {
|
|
328
343
|
type: "object",
|
|
329
344
|
properties: {
|
|
345
|
+
item: {
|
|
346
|
+
type: "string",
|
|
347
|
+
description: "Vault item name/path whose credentials should be deleted",
|
|
348
|
+
},
|
|
330
349
|
domain: {
|
|
331
350
|
type: "string",
|
|
332
|
-
description: "
|
|
351
|
+
description: "compatibility alias for item when the vault item name is a service domain",
|
|
333
352
|
},
|
|
334
353
|
},
|
|
335
354
|
required: ["domain"],
|
|
@@ -344,12 +363,13 @@ exports.credentialToolDefinitions = [
|
|
|
344
363
|
meta: { tool: "credential_delete", domain: args.domain },
|
|
345
364
|
});
|
|
346
365
|
try {
|
|
366
|
+
const itemName = resolveVaultItemArg(args);
|
|
347
367
|
const store = (0, credential_access_1.getCredentialStore)();
|
|
348
|
-
const deleted = await store.delete(
|
|
368
|
+
const deleted = await store.delete(itemName);
|
|
349
369
|
if (deleted) {
|
|
350
|
-
return `Credentials for "${
|
|
370
|
+
return `Credentials for "${itemName}" deleted.`;
|
|
351
371
|
}
|
|
352
|
-
return `No credential found for "${
|
|
372
|
+
return `No credential found for "${itemName}".`;
|
|
353
373
|
}
|
|
354
374
|
catch (err) {
|
|
355
375
|
/* v8 ignore next -- defensive: store.delete wraps errors @preserve */
|
package/package.json
CHANGED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PORKBUN_OPS_CREDENTIAL_PREFIX = exports.PORKBUN_OPS_CREDENTIAL_KIND = void 0;
|
|
4
|
-
exports.normalizePorkbunOpsAccount = normalizePorkbunOpsAccount;
|
|
5
|
-
exports.porkbunOpsCredentialItemName = porkbunOpsCredentialItemName;
|
|
6
|
-
exports.requirePorkbunOpsSecret = requirePorkbunOpsSecret;
|
|
7
|
-
exports.PORKBUN_OPS_CREDENTIAL_KIND = "ops-credential/porkbun";
|
|
8
|
-
exports.PORKBUN_OPS_CREDENTIAL_PREFIX = "ops/registrars/porkbun/accounts";
|
|
9
|
-
const PORKBUN_OPS_ACCOUNT_FORBIDDEN = /[\/\r\n\t]/;
|
|
10
|
-
function normalizePorkbunOpsAccount(account) {
|
|
11
|
-
const normalized = account?.trim() ?? "";
|
|
12
|
-
if (!normalized || PORKBUN_OPS_ACCOUNT_FORBIDDEN.test(normalized)) {
|
|
13
|
-
throw new Error("Porkbun account must be a non-empty account label without slashes or control characters.");
|
|
14
|
-
}
|
|
15
|
-
return normalized;
|
|
16
|
-
}
|
|
17
|
-
function porkbunOpsCredentialItemName(account) {
|
|
18
|
-
return `${exports.PORKBUN_OPS_CREDENTIAL_PREFIX}/${normalizePorkbunOpsAccount(account)}`;
|
|
19
|
-
}
|
|
20
|
-
function requirePorkbunOpsSecret(value, label) {
|
|
21
|
-
const trimmed = value.trim();
|
|
22
|
-
if (!trimmed)
|
|
23
|
-
throw new Error(`${label} cannot be blank`);
|
|
24
|
-
return trimmed;
|
|
25
|
-
}
|