@ouro.bot/cli 0.1.0-alpha.464 → 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 +5 -0
- package/changelog.json +16 -0
- package/dist/heart/daemon/cli-exec.js +177 -0
- package/dist/heart/daemon/cli-help.js +27 -2
- package/dist/heart/daemon/cli-parse.js +137 -0
- package/dist/heart/daemon/vault-items.js +56 -0
- package/dist/nerves/coverage/file-completeness.js +2 -1
- package/dist/repertoire/tools-credential.js +37 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -180,6 +180,9 @@ 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>
|
|
185
|
+
ouro vault ops porkbun set --agent <name> --account <account>
|
|
183
186
|
ouro connect --agent <name>
|
|
184
187
|
ouro connect providers --agent <name>
|
|
185
188
|
ouro connect perplexity --agent <name>
|
|
@@ -207,6 +210,8 @@ ouro mcp-serve --agent <name> # start MCP server on stdin/stdout (us
|
|
|
207
210
|
ouro hook <event> --agent <name> # fire a lifecycle hook (SessionStart, Stop, PostToolUse)
|
|
208
211
|
```
|
|
209
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
|
+
|
|
210
215
|
## Setting Up On Another Machine
|
|
211
216
|
|
|
212
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,22 @@
|
|
|
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
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"version": "0.1.0-alpha.465",
|
|
14
|
+
"changes": [
|
|
15
|
+
"`ouro vault ops porkbun set` now stores account-scoped Porkbun API credentials in the owning agent vault through hidden prompts, outside the connect bay and runtime/config.",
|
|
16
|
+
"Porkbun ops credentials are named by their true authority boundary at `ops/registrars/porkbun/accounts/<account>`, so multiple Porkbun accounts can coexist and domain allowlists remain separate.",
|
|
17
|
+
"Auth/provider docs now clarify that `ouro connect` is only for harness-managed capabilities, while registrar and deployment credentials belong in explicit `ops/...` vault items."
|
|
18
|
+
]
|
|
19
|
+
},
|
|
4
20
|
{
|
|
5
21
|
"version": "0.1.0-alpha.464",
|
|
6
22
|
"changes": [
|
|
@@ -103,6 +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 vault_items_1 = require("./vault-items");
|
|
106
107
|
// ── ensureDaemonRunning ──
|
|
107
108
|
const DEFAULT_DAEMON_STARTUP_TIMEOUT_MS = 60_000;
|
|
108
109
|
const DEFAULT_DAEMON_STARTUP_POLL_INTERVAL_MS = 500;
|
|
@@ -325,6 +326,9 @@ function agentResolutionFailureMode(command) {
|
|
|
325
326
|
case "vault.status":
|
|
326
327
|
case "vault.config.set":
|
|
327
328
|
case "vault.config.status":
|
|
329
|
+
case "vault.item.set":
|
|
330
|
+
case "vault.item.status":
|
|
331
|
+
case "vault.item.list":
|
|
328
332
|
case "connect":
|
|
329
333
|
case "account.ensure":
|
|
330
334
|
case "mail.import-mbox":
|
|
@@ -2009,6 +2013,170 @@ async function executeVaultConfigStatus(command, deps) {
|
|
|
2009
2013
|
deps.writeStdout(message);
|
|
2010
2014
|
return message;
|
|
2011
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) {
|
|
2078
|
+
if (command.agent === "SerpentGuide") {
|
|
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));
|
|
2094
|
+
}
|
|
2095
|
+
const payload = {
|
|
2096
|
+
schemaVersion: 1,
|
|
2097
|
+
updatedAt: providerCliNow(deps).toISOString(),
|
|
2098
|
+
publicFields,
|
|
2099
|
+
secretFields,
|
|
2100
|
+
};
|
|
2101
|
+
const progress = createHumanCommandProgress(deps, command.compatibilityAlias === vault_items_1.PORKBUN_OPS_COMPATIBILITY_ALIAS ? "vault ops porkbun" : "vault item set");
|
|
2102
|
+
try {
|
|
2103
|
+
await runCommandProgressPhase(progress, command.compatibilityAlias === vault_items_1.PORKBUN_OPS_COMPATIBILITY_ALIAS ? "storing ordinary vault item" : "storing vault item", async () => {
|
|
2104
|
+
const store = (0, credential_access_1.getCredentialStore)(command.agent);
|
|
2105
|
+
await store.store(command.item, {
|
|
2106
|
+
...(account ? { username: account } : {}),
|
|
2107
|
+
password: JSON.stringify(payload),
|
|
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
|
+
: {}),
|
|
2113
|
+
});
|
|
2114
|
+
return command.item;
|
|
2115
|
+
}, () => "secret stored");
|
|
2116
|
+
}
|
|
2117
|
+
finally {
|
|
2118
|
+
progress.end();
|
|
2119
|
+
}
|
|
2120
|
+
const message = [
|
|
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"}`,
|
|
2128
|
+
"secret values were not printed",
|
|
2129
|
+
].join("\n");
|
|
2130
|
+
deps.writeStdout(message);
|
|
2131
|
+
return message;
|
|
2132
|
+
}
|
|
2133
|
+
async function executeVaultItemStatus(command, deps) {
|
|
2134
|
+
if (command.agent === "SerpentGuide") {
|
|
2135
|
+
const message = "SerpentGuide has no persistent credential vault. Vault items belong in the owning agent vault.";
|
|
2136
|
+
deps.writeStdout(message);
|
|
2137
|
+
return message;
|
|
2138
|
+
}
|
|
2139
|
+
const store = (0, credential_access_1.getCredentialStore)(command.agent);
|
|
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");
|
|
2152
|
+
lines.push("secret values were not printed");
|
|
2153
|
+
const message = lines.join("\n");
|
|
2154
|
+
deps.writeStdout(message);
|
|
2155
|
+
return message;
|
|
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
|
+
}
|
|
2012
2180
|
function requirePromptSecret(deps, purpose) {
|
|
2013
2181
|
if (deps.promptSecret)
|
|
2014
2182
|
return deps.promptSecret;
|
|
@@ -5354,6 +5522,15 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
5354
5522
|
if (command.kind === "vault.config.status") {
|
|
5355
5523
|
return executeVaultConfigStatus(command, deps);
|
|
5356
5524
|
}
|
|
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);
|
|
5530
|
+
}
|
|
5531
|
+
if (command.kind === "vault.item.list") {
|
|
5532
|
+
return executeVaultItemList(command, deps);
|
|
5533
|
+
}
|
|
5357
5534
|
// ── auth (local, no daemon socket needed) ──
|
|
5358
5535
|
if (command.kind === "auth.run") {
|
|
5359
5536
|
return executeAuthRun(command, deps);
|
|
@@ -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> [--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"],
|
|
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,6 +366,31 @@ 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
|
+
},
|
|
384
|
+
"vault ops porkbun set": {
|
|
385
|
+
description: "deprecated compatibility alias for `ouro vault item set --template porkbun-api`; stores an ordinary vault item, not a special credential kind",
|
|
386
|
+
usage: "ouro vault ops porkbun set [--agent <name>] --account <account>",
|
|
387
|
+
example: "ouro vault ops porkbun set --agent slugger --account ari@mendelow.me",
|
|
388
|
+
},
|
|
389
|
+
"vault ops porkbun status": {
|
|
390
|
+
description: "deprecated compatibility alias for checking the ordinary vault item used by the Porkbun API template",
|
|
391
|
+
usage: "ouro vault ops porkbun status [--agent <name>] [--account <account>]",
|
|
392
|
+
example: "ouro vault ops porkbun status --agent slugger --account ari@mendelow.me",
|
|
393
|
+
},
|
|
369
394
|
};
|
|
370
395
|
// ── Levenshtein distance ──
|
|
371
396
|
function levenshteinDistance(a, b) {
|
|
@@ -16,6 +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 vault_items_1 = require("./vault-items");
|
|
19
20
|
// ── Shared helpers ──
|
|
20
21
|
function extractAgentFlag(args) {
|
|
21
22
|
const idx = args.indexOf("--agent");
|
|
@@ -95,6 +96,11 @@ function usage() {
|
|
|
95
96
|
" ouro vault status [--agent <name>] [--store auto|macos-keychain|windows-dpapi|linux-secret-service|plaintext-file]",
|
|
96
97
|
" ouro vault config set [--agent <name>] --key <path> [--value <value>] [--scope agent|machine]",
|
|
97
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>]",
|
|
102
|
+
" ouro vault ops porkbun set [--agent <name>] --account <account>",
|
|
103
|
+
" ouro vault ops porkbun status [--agent <name>] [--account <account>]",
|
|
98
104
|
" ouro chat <agent>",
|
|
99
105
|
" ouro msg --to <agent> [--session <id>] [--task <ref>] <message>",
|
|
100
106
|
" ouro poke <agent> --task <task-id>",
|
|
@@ -458,6 +464,10 @@ function parseVaultCommand(args) {
|
|
|
458
464
|
const sub = args[0];
|
|
459
465
|
if (sub === "config")
|
|
460
466
|
return parseVaultConfigCommand(args.slice(1));
|
|
467
|
+
if (sub === "item")
|
|
468
|
+
return parseVaultItemCommand(args.slice(1));
|
|
469
|
+
if (sub === "ops")
|
|
470
|
+
return parseVaultOpsCommand(args.slice(1));
|
|
461
471
|
const { agent, rest } = extractAgentFlag(args.slice(1));
|
|
462
472
|
let email;
|
|
463
473
|
let serverUrl;
|
|
@@ -544,6 +554,133 @@ function parseVaultCommand(args) {
|
|
|
544
554
|
}
|
|
545
555
|
return { kind: "vault.status", ...(agent ? { agent } : {}), ...(store ? { store } : {}) };
|
|
546
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
|
+
}
|
|
638
|
+
function parseVaultOpsCommand(args) {
|
|
639
|
+
const provider = args[0];
|
|
640
|
+
const action = args[1];
|
|
641
|
+
if (provider !== "porkbun") {
|
|
642
|
+
throw new Error("Usage: ouro vault ops porkbun set|status [--agent <name>] [--account <account>]");
|
|
643
|
+
}
|
|
644
|
+
if (action !== "set" && action !== "status") {
|
|
645
|
+
throw new Error("Usage: ouro vault ops porkbun set|status [--agent <name>] [--account <account>]");
|
|
646
|
+
}
|
|
647
|
+
const { agent, rest } = extractAgentFlag(args.slice(2));
|
|
648
|
+
let account;
|
|
649
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
650
|
+
const token = rest[i];
|
|
651
|
+
if (token === "--account") {
|
|
652
|
+
account = (0, vault_items_1.normalizePorkbunOpsAccount)(rest[i + 1]);
|
|
653
|
+
i += 1;
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
throw new Error(`Usage: ouro vault ops porkbun ${action} [--agent <name>] [--account <account>]`);
|
|
657
|
+
}
|
|
658
|
+
if (action === "set") {
|
|
659
|
+
if (!account)
|
|
660
|
+
throw new Error("Usage: ouro vault ops porkbun set [--agent <name>] --account <account>");
|
|
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
|
+
};
|
|
668
|
+
}
|
|
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
|
+
};
|
|
683
|
+
}
|
|
547
684
|
function parseVaultConfigCommand(args) {
|
|
548
685
|
const sub = args[0];
|
|
549
686
|
const { agent, rest } = extractAgentFlag(args.slice(1));
|
|
@@ -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
|
+
}
|
|
@@ -59,10 +59,11 @@ const DISPATCH_EXEMPT_PATTERNS = [
|
|
|
59
59
|
"repertoire/tools-config",
|
|
60
60
|
"repertoire/tools-base",
|
|
61
61
|
// CLI sub-modules: cli-exec.ts is the router with emitNervesEvent calls;
|
|
62
|
-
// cli-parse, cli-render,
|
|
62
|
+
// cli-parse, cli-render, cli-help, and their small helpers are pure functions/data with no side effects.
|
|
63
63
|
"daemon/cli-parse",
|
|
64
64
|
"daemon/cli-render",
|
|
65
65
|
"daemon/cli-help",
|
|
66
|
+
"daemon/vault-items",
|
|
66
67
|
// Shared utility modules: pure helpers consumed by modules that own observability.
|
|
67
68
|
"arc/json-store",
|
|
68
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 */
|