@ouro.bot/cli 0.1.0-alpha.385 → 0.1.0-alpha.388

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/changelog.json CHANGED
@@ -1,6 +1,25 @@
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.388",
6
+ "changes": [
7
+ "The default agent credential vault host now points at the live Vaultwarden endpoint `https://vault.ouroboros.bot` instead of the website redirect host.",
8
+ "`ouro vault create` and `ouro vault recover` now create accounts through the live Bitwarden/Vaultwarden identity registration endpoint and include the registration URL in setup failures.",
9
+ "Vault unlock/create/recover prompts now use a non-echoing secret prompt for vault unlock secrets, and the supported docs/help path asks the human to provide a saved secret instead of generating or printing one.",
10
+ "Deprecated `--generate-unlock-secret` vault flags now fail fast with guidance to rerun interactively, preventing new vault unlock secrets from being printed into terminals or logs.",
11
+ "Vault setup, unlock, provider CLI, help, docs, and runtime-config tests now encode the canonical vault host and human-provided secret flow so the wrong host or unsafe prompt path cannot silently return.",
12
+ "`@ouro.bot/cli` and the `ouro.bot` wrapper are version-synced for the canonical vault recovery release."
13
+ ]
14
+ },
15
+ {
16
+ "version": "0.1.0-alpha.386",
17
+ "changes": [
18
+ "`ouro vault recover --help` and `ouro help vault recover` now show the recovery-specific flags instead of generic vault help.",
19
+ "Nested command help now preserves subcommand words before flags, so help output can be specific to commands like `vault recover`.",
20
+ "`@ouro.bot/cli` and the `ouro.bot` wrapper are version-synced for the vault recovery help release."
21
+ ]
22
+ },
4
23
  {
5
24
  "version": "0.1.0-alpha.385",
6
25
  "changes": [
@@ -221,6 +221,40 @@ async function defaultPromptInput(question) {
221
221
  rl.close();
222
222
  }
223
223
  }
224
+ async function defaultPromptSecret(question) {
225
+ const readline = await Promise.resolve().then(() => __importStar(require("readline")));
226
+ const rl = readline.createInterface({
227
+ input: process.stdin,
228
+ output: process.stdout,
229
+ terminal: true,
230
+ });
231
+ const mutableRl = rl;
232
+ const originalWriteToOutput = mutableRl._writeToOutput;
233
+ let muted = false;
234
+ if (originalWriteToOutput) {
235
+ mutableRl._writeToOutput = (stringToWrite) => {
236
+ if (!muted) {
237
+ originalWriteToOutput.call(rl, stringToWrite);
238
+ }
239
+ };
240
+ }
241
+ try {
242
+ const response = await new Promise((resolve) => {
243
+ rl.question(question, (answer) => {
244
+ process.stdout.write("\n");
245
+ resolve(answer);
246
+ });
247
+ muted = true;
248
+ });
249
+ return response.trim();
250
+ }
251
+ finally {
252
+ if (originalWriteToOutput) {
253
+ mutableRl._writeToOutput = originalWriteToOutput;
254
+ }
255
+ rl.close();
256
+ }
257
+ }
224
258
  function defaultListDiscoveredAgents() {
225
259
  return (0, agent_discovery_1.listEnabledBundleAgents)({
226
260
  bundlesRoot: (0, identity_1.getAgentBundlesRoot)(),
@@ -464,6 +498,7 @@ function createDefaultOuroCliDeps(socketPath = socket_client_1.DEFAULT_DAEMON_SO
464
498
  listDiscoveredAgents: defaultListDiscoveredAgents,
465
499
  runHatchFlow: hatch_flow_1.runHatchFlow,
466
500
  promptInput: defaultPromptInput,
501
+ promptSecret: defaultPromptSecret,
467
502
  runSerpentGuide: defaultRunSerpentGuide,
468
503
  runAuthFlow: auth_flow_1.runRuntimeAuthFlow,
469
504
  registerOuroBundleType: ouro_uti_1.registerOuroBundleUti,
@@ -694,16 +694,22 @@ function defaultRecoveredVaultEmail(agentName, now) {
694
694
  const stamp = now.toISOString().replace(/[-:.TZ]/g, "").slice(0, 14);
695
695
  return `${local}+recovered-${stamp}@ouro.bot`;
696
696
  }
697
+ function ensureVaultSecretPrompt(promptSecret, action) {
698
+ if (promptSecret)
699
+ return promptSecret;
700
+ throw new Error(`vault ${action} requires an interactive secret prompt that does not echo the vault unlock secret`);
701
+ }
702
+ function rejectGeneratedVaultUnlockSecret(action) {
703
+ throw new Error(`vault ${action} no longer supports --generate-unlock-secret. Re-run without that flag and enter a human-chosen unlock secret; Ouro will not print vault unlock secrets.`);
704
+ }
697
705
  async function executeVaultUnlock(command, deps) {
698
706
  if (command.agent === "SerpentGuide") {
699
707
  throw new Error("SerpentGuide does not have a persistent credential vault. Hatch bootstrap uses selected provider credentials in memory only.");
700
708
  }
701
- const prompt = deps.promptInput;
702
- if (!prompt)
703
- throw new Error("vault unlock requires an interactive prompt to capture the vault unlock secret");
709
+ const promptSecret = ensureVaultSecretPrompt(deps.promptSecret, "unlock");
704
710
  const { config } = (0, auth_flow_1.readAgentConfigForAgent)(command.agent, deps.bundlesRoot);
705
711
  const vault = (0, identity_1.resolveVaultConfig)(command.agent, config.vault);
706
- const unlockSecret = await prompt(`Ouro vault unlock secret for ${vault.email}: `);
712
+ const unlockSecret = await promptSecret(`Ouro vault unlock secret for ${vault.email}: `);
707
713
  const store = (0, vault_unlock_1.storeVaultUnlockSecret)({
708
714
  agentName: command.agent,
709
715
  email: vault.email,
@@ -723,6 +729,8 @@ async function executeVaultCreate(command, deps) {
723
729
  if (command.agent === "SerpentGuide") {
724
730
  throw new Error("SerpentGuide does not have a persistent credential vault. Create a vault for the hatchling agent, not SerpentGuide.");
725
731
  }
732
+ if (command.generateUnlockSecret)
733
+ rejectGeneratedVaultUnlockSecret("create");
726
734
  const prompt = deps.promptInput;
727
735
  const { configPath, config } = (0, auth_flow_1.readAgentConfigForAgent)(command.agent, deps.bundlesRoot);
728
736
  const configuredVault = (0, identity_1.resolveVaultConfig)(command.agent, config.vault);
@@ -730,16 +738,12 @@ async function executeVaultCreate(command, deps) {
730
738
  if (!email) {
731
739
  throw new Error("vault create requires --email <email> when the agent bundle has no vault.email");
732
740
  }
741
+ const promptSecret = ensureVaultSecretPrompt(deps.promptSecret, "create");
733
742
  const serverUrl = command.serverUrl ?? config.vault?.serverUrl ?? configuredVault.serverUrl;
734
- const requestedUnlockSecret = command.generateUnlockSecret
735
- ? ""
736
- : prompt
737
- ? (await prompt(`Choose Ouro vault unlock secret for ${email}: `)).trim()
738
- : "";
739
- if (!requestedUnlockSecret && !command.generateUnlockSecret) {
740
- throw new Error("vault create requires an unlock secret. Re-run with an interactive terminal or pass --generate-unlock-secret.");
743
+ const unlockSecret = (await promptSecret(`Choose Ouro vault unlock secret for ${email}: `)).trim();
744
+ if (!unlockSecret) {
745
+ throw new Error("vault create requires an unlock secret. Re-run in an interactive terminal and enter a human-chosen unlock secret.");
741
746
  }
742
- const unlockSecret = requestedUnlockSecret || (0, crypto_1.randomBytes)(32).toString("base64");
743
747
  const result = await (0, vault_setup_1.createVaultAccount)("Ouro credential vault", serverUrl, email, unlockSecret);
744
748
  if (!result.success) {
745
749
  const message = [
@@ -764,14 +768,7 @@ async function executeVaultCreate(command, deps) {
764
768
  `vault: ${email} at ${serverUrl}`,
765
769
  `local unlock store: ${store.kind}${store.secure ? "" : " (explicit plaintext fallback)"}`,
766
770
  "All raw credentials for this agent will be stored in this Ouro credential vault.",
767
- ...(command.generateUnlockSecret
768
- ? [
769
- "",
770
- `vault unlock secret: ${unlockSecret}`,
771
- "",
772
- "Keep this saved outside Ouro now. Another machine cannot unlock the vault without it.",
773
- ]
774
- : ["Keep the vault unlock secret saved outside Ouro. Another machine will need it once."]),
771
+ "Keep the vault unlock secret saved outside Ouro. Another machine will need it once.",
775
772
  ].join("\n");
776
773
  deps.writeStdout(message);
777
774
  return message;
@@ -780,21 +777,18 @@ async function executeVaultRecover(command, deps) {
780
777
  if (command.agent === "SerpentGuide") {
781
778
  throw new Error("SerpentGuide does not have a persistent credential vault. Recover the hatchling agent vault, not SerpentGuide.");
782
779
  }
783
- const prompt = deps.promptInput;
780
+ if (command.generateUnlockSecret)
781
+ rejectGeneratedVaultUnlockSecret("recover");
782
+ const promptSecret = ensureVaultSecretPrompt(deps.promptSecret, "recover");
784
783
  const now = providerCliNow(deps);
785
784
  const { configPath, config } = (0, auth_flow_1.readAgentConfigForAgent)(command.agent, deps.bundlesRoot);
786
785
  const configuredVault = (0, identity_1.resolveVaultConfig)(command.agent, config.vault);
787
786
  const email = command.email ?? defaultRecoveredVaultEmail(command.agent, now);
788
787
  const serverUrl = command.serverUrl ?? config.vault?.serverUrl ?? configuredVault.serverUrl;
789
- const requestedUnlockSecret = command.generateUnlockSecret
790
- ? ""
791
- : prompt
792
- ? (await prompt(`Choose replacement Ouro vault unlock secret for ${email}: `)).trim()
793
- : "";
794
- if (!requestedUnlockSecret && !command.generateUnlockSecret) {
795
- throw new Error("vault recover requires a replacement unlock secret. Re-run with an interactive terminal or pass --generate-unlock-secret.");
788
+ const unlockSecret = (await promptSecret(`Choose replacement Ouro vault unlock secret for ${email}: `)).trim();
789
+ if (!unlockSecret) {
790
+ throw new Error("vault recover requires a replacement unlock secret. Re-run in an interactive terminal and enter a human-chosen unlock secret.");
796
791
  }
797
- const unlockSecret = requestedUnlockSecret || (0, crypto_1.randomBytes)(32).toString("base64");
798
792
  const sourceImports = command.sources.map(readVaultRecoverSource);
799
793
  const result = await (0, vault_setup_1.createVaultAccount)("Ouro credential vault", serverUrl, email, unlockSecret);
800
794
  if (!result.success) {
@@ -843,14 +837,7 @@ async function executeVaultRecover(command, deps) {
843
837
  `provider credentials imported: ${providerList.length === 0 ? "none" : providerList.join(", ")}`,
844
838
  `runtime credentials imported: ${runtimeFields.length === 0 ? "none" : runtimeFields.join(", ")}`,
845
839
  "credential values were not printed",
846
- ...(command.generateUnlockSecret
847
- ? [
848
- "",
849
- `vault unlock secret: ${unlockSecret}`,
850
- "",
851
- "Keep this saved outside Ouro now. Another machine cannot unlock the vault without it.",
852
- ]
853
- : ["Keep the replacement vault unlock secret saved outside Ouro. Another machine will need it once."]),
840
+ "Keep the replacement vault unlock secret saved outside Ouro. Another machine will need it once.",
854
841
  ].join("\n");
855
842
  deps.writeStdout(message);
856
843
  return message;
@@ -229,6 +229,38 @@ exports.COMMAND_REGISTRY = {
229
229
  subcommands: ["replay"],
230
230
  },
231
231
  };
232
+ const SUBCOMMAND_HELP = {
233
+ "vault create": {
234
+ description: "Create an agent credential vault and store local unlock material",
235
+ usage: "ouro vault create --agent <name> --email <email> [--server <url>] [--store <store>]",
236
+ example: "ouro vault create --agent ouroboros --email ouroboros@ouro.bot",
237
+ },
238
+ "vault recover": {
239
+ description: "Create a replacement agent vault and import local JSON credential exports",
240
+ usage: "ouro vault recover --agent <name> --from <json> [--from <json>] [--email <email>] [--server <url>] [--store <store>]",
241
+ example: "ouro vault recover --agent ouroboros --from ./credentials.json",
242
+ },
243
+ "vault unlock": {
244
+ description: "Unlock an existing agent credential vault on this machine",
245
+ usage: "ouro vault unlock --agent <name> [--store <store>]",
246
+ example: "ouro vault unlock --agent ouroboros",
247
+ },
248
+ "vault status": {
249
+ description: "Show whether this machine can unlock an agent credential vault",
250
+ usage: "ouro vault status --agent <name> [--store <store>]",
251
+ example: "ouro vault status --agent ouroboros",
252
+ },
253
+ "vault config set": {
254
+ description: "Write runtime configuration into the agent credential vault",
255
+ usage: "ouro vault config set --agent <name> --key <path> [--value <value>]",
256
+ example: "ouro vault config set --agent ouroboros --key senses/outlook/clientId",
257
+ },
258
+ "vault config status": {
259
+ description: "List runtime configuration keys stored in the agent credential vault",
260
+ usage: "ouro vault config status --agent <name>",
261
+ example: "ouro vault config status --agent ouroboros",
262
+ },
263
+ };
232
264
  // ── Levenshtein distance ──
233
265
  function levenshteinDistance(a, b) {
234
266
  if (a.length === 0)
@@ -296,7 +328,7 @@ function getGroupedHelp() {
296
328
  }
297
329
  // ── Per-command help ──
298
330
  function getCommandHelp(name) {
299
- const entry = exports.COMMAND_REGISTRY[name];
331
+ const entry = SUBCOMMAND_HELP[name] ?? exports.COMMAND_REGISTRY[name];
300
332
  if (!entry)
301
333
  return null;
302
334
  const lines = [
@@ -42,6 +42,17 @@ function facingToProviderLane(facing) {
42
42
  function isProviderLane(value) {
43
43
  return value === "outward" || value === "inner";
44
44
  }
45
+ function helpCommandName(args) {
46
+ const positional = [];
47
+ for (const arg of args) {
48
+ if (arg === "--help" || arg === "-h")
49
+ break;
50
+ if (arg.startsWith("-"))
51
+ break;
52
+ positional.push(arg);
53
+ }
54
+ return positional.length > 0 ? positional.join(" ") : undefined;
55
+ }
45
56
  function extractLaneFlag(args) {
46
57
  const idx = args.indexOf("--lane");
47
58
  if (idx === -1 || idx + 1 >= args.length)
@@ -73,8 +84,8 @@ function usage() {
73
84
  " ouro auth --agent <name> [--provider <provider>]",
74
85
  " ouro auth verify --agent <name> [--provider <provider>]",
75
86
  " ouro auth switch --agent <name> --provider <provider>",
76
- " ouro vault create --agent <name> --email <email> [--server <url>] [--store <store>] [--generate-unlock-secret]",
77
- " ouro vault recover --agent <name> --from <json> [--from <json>] [--email <email>] [--server <url>] [--store <store>] [--generate-unlock-secret]",
87
+ " ouro vault create --agent <name> --email <email> [--server <url>] [--store <store>]",
88
+ " ouro vault recover --agent <name> --from <json> [--from <json>] [--email <email>] [--server <url>] [--store <store>]",
78
89
  " ouro vault unlock --agent <name> [--store auto|macos-keychain|windows-dpapi|linux-secret-service|plaintext-file]",
79
90
  " ouro vault status --agent <name> [--store auto|macos-keychain|windows-dpapi|linux-secret-service|plaintext-file]",
80
91
  " ouro vault config set --agent <name> --key <path> [--value <value>]",
@@ -932,11 +943,13 @@ function parseOuroCommand(args) {
932
943
  return { kind: "daemon.up" };
933
944
  // ── help command ──
934
945
  if (head === "help") {
935
- return second ? { kind: "help", command: second } : { kind: "help" };
946
+ const command = helpCommandName(args.slice(1));
947
+ return command ? { kind: "help", command } : { kind: "help" };
936
948
  }
937
949
  // ── per-command --help ──
938
- if (args.includes("--help")) {
939
- return { kind: "help", command: head };
950
+ if (args.includes("--help") || args.includes("-h")) {
951
+ const command = helpCommandName(args);
952
+ return command ? { kind: "help", command } : { kind: "help" };
940
953
  }
941
954
  if (head === "--agent" && second) {
942
955
  return parseOuroCommand(args.slice(2));
@@ -75,7 +75,7 @@ exports.DEFAULT_AGENT_PHRASES = {
75
75
  tool: ["running tool"],
76
76
  followup: ["processing"],
77
77
  };
78
- exports.DEFAULT_VAULT_SERVER_URL = "https://vault.ouro.bot";
78
+ exports.DEFAULT_VAULT_SERVER_URL = "https://vault.ouroboros.bot";
79
79
  /**
80
80
  * Resolve the vault config for an agent, applying defaults.
81
81
  * If vault is not configured in agent.json, returns default values.
@@ -161,6 +161,7 @@ function generateRsaKeypair() {
161
161
  // ---------------------------------------------------------------------------
162
162
  const KDF_PBKDF2 = 0;
163
163
  const KDF_ITERATIONS = 600000;
164
+ const REGISTER_ACCOUNT_PATH = "/identity/accounts/register";
164
165
  /**
165
166
  * Create a Bitwarden account on the configured Vaultwarden server.
166
167
  * Uses the Bitwarden registration API with standard KDF implementation.
@@ -184,7 +185,8 @@ async function createVaultAccount(agentName, serverUrl, email, masterPassword) {
184
185
  const { publicKeyB64, privateKeyDer } = generateRsaKeypair();
185
186
  const encryptedPrivateKey = encryptWithStretchedKey(privateKeyDer, symKey);
186
187
  // Step 4: POST registration
187
- const res = await fetch(`${serverUrl}/api/accounts/register`, {
188
+ const registrationUrl = `${serverUrl}${REGISTER_ACCOUNT_PATH}`;
189
+ const res = await fetch(registrationUrl, {
188
190
  method: "POST",
189
191
  headers: { "Content-Type": "application/json" },
190
192
  body: JSON.stringify({
@@ -210,14 +212,15 @@ async function createVaultAccount(agentName, serverUrl, email, masterPassword) {
210
212
  catch {
211
213
  errorDetail = `HTTP ${res.status} ${res.statusText}`;
212
214
  }
215
+ const endpointAwareError = `${errorDetail} from ${registrationUrl}. Check --server; Ouro expects a Bitwarden/Vaultwarden identity API.`;
213
216
  (0, runtime_1.emitNervesEvent)({
214
217
  level: "error",
215
218
  event: "repertoire.vault_setup_error",
216
219
  component: "repertoire",
217
- message: `vault registration failed: ${errorDetail}`,
218
- meta: { agentName, serverUrl, email, reason: errorDetail },
220
+ message: `vault registration failed: ${endpointAwareError}`,
221
+ meta: { agentName, serverUrl, email, registrationUrl, reason: endpointAwareError },
219
222
  });
220
- return { success: false, email, serverUrl, error: errorDetail };
223
+ return { success: false, email, serverUrl, error: endpointAwareError };
221
224
  }
222
225
  (0, runtime_1.emitNervesEvent)({
223
226
  event: "repertoire.vault_setup_end",
@@ -229,13 +232,15 @@ async function createVaultAccount(agentName, serverUrl, email, masterPassword) {
229
232
  }
230
233
  catch (err) {
231
234
  const reason = err instanceof Error ? err.message : String(err);
235
+ const registrationUrl = `${serverUrl}${REGISTER_ACCOUNT_PATH}`;
236
+ const endpointAwareError = `cannot reach vault registration endpoint ${registrationUrl}: ${reason}. Check network, DNS/TLS, and --server.`;
232
237
  (0, runtime_1.emitNervesEvent)({
233
238
  level: "error",
234
239
  event: "repertoire.vault_setup_error",
235
240
  component: "repertoire",
236
- message: `vault setup failed: ${reason}`,
237
- meta: { agentName, serverUrl, email, reason },
241
+ message: `vault setup failed: ${endpointAwareError}`,
242
+ meta: { agentName, serverUrl, email, registrationUrl, reason: endpointAwareError },
238
243
  });
239
- return { success: false, email, serverUrl, error: reason };
244
+ return { success: false, email, serverUrl, error: endpointAwareError };
240
245
  }
241
246
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.385",
3
+ "version": "0.1.0-alpha.388",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",