@ouro.bot/cli 0.1.0-alpha.470 → 0.1.0-alpha.471

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,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.471",
6
+ "changes": [
7
+ "`ouro account ensure` and `ouro connect mail` can now repair hosted Mail Control registry/vault drift with `--rotate-missing-mail-keys`, storing freshly returned one-time private keys in the owning agent vault without printing them.",
8
+ "Hosted Mail setup now detects missing private key material before writing partial runtime config, rotates only the missing native mailbox and/or delegated source keys, and preserves source separation for native agent mail versus delegated human mail.",
9
+ "Agent Mail setup and recovery docs now teach hosted key rotation explicitly, including that rotation repairs future mail access but cannot decrypt mail already encrypted to a lost private key."
10
+ ]
11
+ },
4
12
  {
5
13
  "version": "0.1.0-alpha.470",
6
14
  "changes": [
@@ -3055,12 +3055,43 @@ function requiredHostedKeyIds(body) {
3055
3055
  .map((record) => stringField(record, "keyId"))
3056
3056
  .filter((keyId) => keyId.length > 0);
3057
3057
  }
3058
+ function hostedMissingMailKeys(body, keys) {
3059
+ const mailbox = isPlainRecord(body.mailbox) ? body.mailbox : undefined;
3060
+ const sourceGrant = isPlainRecord(body.sourceGrant) ? body.sourceGrant : undefined;
3061
+ const mailboxKeyId = mailbox ? stringField(mailbox, "keyId") : "";
3062
+ const sourceKeyId = sourceGrant ? stringField(sourceGrant, "keyId") : "";
3063
+ const missingMailbox = mailboxKeyId.length > 0 && !keys[mailboxKeyId];
3064
+ const missingSourceGrant = sourceKeyId.length > 0 && !keys[sourceKeyId];
3065
+ return {
3066
+ keyIds: [
3067
+ ...(missingMailbox ? [mailboxKeyId] : []),
3068
+ ...(missingSourceGrant ? [sourceKeyId] : []),
3069
+ ],
3070
+ rotateMailbox: missingMailbox,
3071
+ rotateSourceGrant: missingSourceGrant,
3072
+ };
3073
+ }
3058
3074
  function assertHostedPrivateKeys(input) {
3059
3075
  for (const keyId of input.requiredKeyIds) {
3060
3076
  if (input.keys[keyId])
3061
3077
  continue;
3062
- throw new Error(`hosted Mail Control references private mail key ${keyId}, but it was not returned and is not present in ${input.agent}'s vault runtime/config. Repair requires a fresh Mail Control one-time key response or key rotation.`);
3078
+ throw new Error(`hosted Mail Control references private mail key ${keyId}, but it was not returned and is not present in ${input.agent}'s vault runtime/config. Repair requires a fresh Mail Control one-time key response or rerunning setup with --rotate-missing-mail-keys.`);
3079
+ }
3080
+ }
3081
+ async function requestHostedMailControl(input) {
3082
+ const response = await input.fetchImpl(`${input.config.url}${input.path}`, {
3083
+ method: "POST",
3084
+ headers: {
3085
+ authorization: `Bearer ${input.config.token}`,
3086
+ "content-type": "application/json",
3087
+ },
3088
+ body: JSON.stringify(input.payload),
3089
+ });
3090
+ const body = await response.json();
3091
+ if (!response.ok || body.ok === false) {
3092
+ throw new Error(`hosted Mail Control ${input.label} failed (${response.status}): ${body.error ?? response.statusText}`);
3063
3093
  }
3094
+ return body;
3064
3095
  }
3065
3096
  async function promptDelegatedMailSource(deps, input = {}) {
3066
3097
  if (input.noDelegatedSource)
@@ -3109,38 +3140,52 @@ async function ensureAgentMailroom(agent, input, deps, progressLabel) {
3109
3140
  if (hostedConfig) {
3110
3141
  progress.updateDetail("calling hosted Mail Control");
3111
3142
  const fetchImpl = deps.fetchImpl ?? fetch;
3112
- const response = await fetchImpl(`${hostedConfig.url}/v1/mailboxes/ensure`, {
3113
- method: "POST",
3114
- headers: {
3115
- authorization: `Bearer ${hostedConfig.token}`,
3116
- "content-type": "application/json",
3117
- },
3118
- body: JSON.stringify({
3119
- agentId: agent,
3120
- ...(input.ownerEmail ? { ownerEmail: input.ownerEmail } : {}),
3121
- ...(input.source ? { source: input.source } : {}),
3122
- }),
3143
+ const basePayload = {
3144
+ agentId: agent,
3145
+ ...(input.ownerEmail ? { ownerEmail: input.ownerEmail } : {}),
3146
+ ...(input.source ? { source: input.source } : {}),
3147
+ };
3148
+ let body = await requestHostedMailControl({
3149
+ fetchImpl,
3150
+ config: hostedConfig,
3151
+ path: "/v1/mailboxes/ensure",
3152
+ payload: basePayload,
3153
+ label: "ensure",
3123
3154
  });
3124
- const body = await response.json();
3125
- if (!response.ok || body.ok === false) {
3126
- throw new Error(`hosted Mail Control ensure failed (${response.status}): ${body.error ?? response.statusText}`);
3127
- }
3128
- const publicRegistry = requiredResponseRecord(body.publicRegistry, "publicRegistry");
3129
- const blobStore = requiredResponseRecord(body.blobStore, "blobStore");
3130
- mailboxAddress = typeof body.mailboxAddress === "string" && body.mailboxAddress.trim()
3131
- ? body.mailboxAddress.trim()
3132
- : requiredResponseText(requiredResponseRecord(body.mailbox, "mailbox"), "canonicalAddress", "mailboxAddress");
3133
- sourceAlias = typeof body.sourceAlias === "string" && body.sourceAlias.trim() ? body.sourceAlias.trim() : null;
3134
- const generatedPrivateKeys = responsePrivateKeys(body.generatedPrivateKeys);
3155
+ let generatedPrivateKeys = responsePrivateKeys(body.generatedPrivateKeys);
3135
3156
  const privateKeys = {
3136
3157
  ...mailroomPrivateKeys(existingMailroom),
3137
3158
  ...generatedPrivateKeys,
3138
3159
  };
3160
+ const missingKeys = hostedMissingMailKeys(body, privateKeys);
3161
+ if (missingKeys.keyIds.length > 0 && input.rotateMissingMailKeys) {
3162
+ progress.updateDetail("rotating missing hosted mail keys");
3163
+ body = await requestHostedMailControl({
3164
+ fetchImpl,
3165
+ config: hostedConfig,
3166
+ path: "/v1/mailboxes/rotate-keys",
3167
+ payload: {
3168
+ ...basePayload,
3169
+ rotateMailbox: missingKeys.rotateMailbox,
3170
+ rotateSourceGrant: missingKeys.rotateSourceGrant,
3171
+ reason: "missing private mail keys in agent vault",
3172
+ },
3173
+ label: "key rotation",
3174
+ });
3175
+ generatedPrivateKeys = responsePrivateKeys(body.generatedPrivateKeys);
3176
+ Object.assign(privateKeys, generatedPrivateKeys);
3177
+ }
3139
3178
  assertHostedPrivateKeys({
3140
3179
  agent,
3141
3180
  keys: privateKeys,
3142
3181
  requiredKeyIds: requiredHostedKeyIds(body),
3143
3182
  });
3183
+ const publicRegistry = requiredResponseRecord(body.publicRegistry, "publicRegistry");
3184
+ const blobStore = requiredResponseRecord(body.blobStore, "blobStore");
3185
+ mailboxAddress = typeof body.mailboxAddress === "string" && body.mailboxAddress.trim()
3186
+ ? body.mailboxAddress.trim()
3187
+ : requiredResponseText(requiredResponseRecord(body.mailbox, "mailbox"), "canonicalAddress", "mailboxAddress");
3188
+ sourceAlias = typeof body.sourceAlias === "string" && body.sourceAlias.trim() ? body.sourceAlias.trim() : null;
3144
3189
  mode = "hosted";
3145
3190
  registryPath = null;
3146
3191
  storePath = null;
@@ -3243,7 +3288,10 @@ async function executeConnectMail(agent, deps, input = {}) {
3243
3288
  ],
3244
3289
  });
3245
3290
  const setup = await promptDelegatedMailSource(deps, input);
3246
- const outcome = await ensureAgentMailroom(agent, setup, deps, "connect mail");
3291
+ const outcome = await ensureAgentMailroom(agent, {
3292
+ ...setup,
3293
+ ...(input.rotateMissingMailKeys ? { rotateMissingMailKeys: true } : {}),
3294
+ }, deps, "connect mail");
3247
3295
  return writeCapabilityOutcome(deps, {
3248
3296
  subtitle: `${agent}'s Mail sense is configured.`,
3249
3297
  summary: `Agent Mail is ready for ${agent}.`,
@@ -3309,7 +3357,10 @@ async function executeAccountEnsure(command, deps) {
3309
3357
  ],
3310
3358
  });
3311
3359
  const setup = await promptDelegatedMailSource(deps, command);
3312
- const outcome = await ensureAgentMailroom(command.agent, setup, deps, "account ensure");
3360
+ const outcome = await ensureAgentMailroom(command.agent, {
3361
+ ...setup,
3362
+ ...(command.rotateMissingMailKeys ? { rotateMissingMailKeys: true } : {}),
3363
+ }, deps, "account ensure");
3313
3364
  return writeCapabilityOutcome(deps, {
3314
3365
  subtitle: `${command.agent}'s work substrate account is configured.`,
3315
3366
  summary: `Ouro work substrate is ready for ${command.agent}.`,
@@ -313,12 +313,12 @@ const SUBCOMMAND_HELP = {
313
313
  },
314
314
  "connect mail": {
315
315
  description: "Provision portable Agent Mail / Mailroom access and enable the Mail sense",
316
- usage: "ouro connect mail [--agent <name>] [--owner-email <email> --source <label>|--no-delegated-source]",
316
+ usage: "ouro connect mail [--agent <name>] [--owner-email <email> --source <label>|--no-delegated-source] [--rotate-missing-mail-keys]",
317
317
  example: "ouro connect mail --agent slugger --owner-email ari@mendelow.me --source hey",
318
318
  },
319
319
  "account ensure": {
320
320
  description: "Idempotently prepare an agent's vault-backed work substrate account and private Mailroom mailbox",
321
- usage: "ouro account ensure [--agent <name>] [--owner-email <email> --source <label>|--no-delegated-source]",
321
+ usage: "ouro account ensure [--agent <name>] [--owner-email <email> --source <label>|--no-delegated-source] [--rotate-missing-mail-keys]",
322
322
  example: "ouro account ensure --agent slugger --owner-email ari@mendelow.me --source hey",
323
323
  },
324
324
  "mail import-mbox": {
@@ -84,8 +84,8 @@ function usage() {
84
84
  " ouro config model [--agent <name>] <model-name>",
85
85
  " ouro config models [--agent <name>]",
86
86
  " ouro auth [--agent <name>] [--provider <provider>]",
87
- " ouro account ensure [--agent <name>] [--owner-email <email> --source <label>|--no-delegated-source]",
88
- " ouro connect [providers|perplexity|embeddings|teams|bluebubbles|mail] [--agent <name>] [--owner-email <email> --source <label>|--no-delegated-source]",
87
+ " ouro account ensure [--agent <name>] [--owner-email <email> --source <label>|--no-delegated-source] [--rotate-missing-mail-keys]",
88
+ " ouro connect [providers|perplexity|embeddings|teams|bluebubbles|mail] [--agent <name>] [--owner-email <email> --source <label>|--no-delegated-source] [--rotate-missing-mail-keys]",
89
89
  " ouro mail import-mbox --file <path> [--owner-email <email>] [--source <label>] [--agent <name>]",
90
90
  " ouro auth verify [--agent <name>] [--provider <provider>]",
91
91
  " ouro auth switch [--agent <name>] --provider <provider>",
@@ -817,6 +817,7 @@ function extractMailSourceFlags(args, usageText) {
817
817
  let ownerEmail;
818
818
  let source;
819
819
  let noDelegatedSource = false;
820
+ let rotateMissingMailKeys = false;
820
821
  let hasMailSourceFlags = false;
821
822
  for (let i = 0; i < args.length; i += 1) {
822
823
  const token = args[i];
@@ -839,6 +840,11 @@ function extractMailSourceFlags(args, usageText) {
839
840
  hasMailSourceFlags = true;
840
841
  continue;
841
842
  }
843
+ if (token === "--rotate-missing-mail-keys") {
844
+ rotateMissingMailKeys = true;
845
+ hasMailSourceFlags = true;
846
+ continue;
847
+ }
842
848
  rest.push(token);
843
849
  }
844
850
  if (noDelegatedSource && (ownerEmail !== undefined || source !== undefined)) {
@@ -852,11 +858,12 @@ function extractMailSourceFlags(args, usageText) {
852
858
  ...(ownerEmail !== undefined ? { ownerEmail } : {}),
853
859
  ...(source !== undefined ? { source } : {}),
854
860
  ...(noDelegatedSource ? { noDelegatedSource: true } : {}),
861
+ ...(rotateMissingMailKeys ? { rotateMissingMailKeys: true } : {}),
855
862
  hasMailSourceFlags,
856
863
  };
857
864
  }
858
865
  function parseConnectCommand(args) {
859
- const usageText = "Usage: ouro connect [providers|perplexity|embeddings|teams|bluebubbles|mail] [--agent <name>] [--owner-email <email> --source <label>|--no-delegated-source]";
866
+ const usageText = "Usage: ouro connect [providers|perplexity|embeddings|teams|bluebubbles|mail] [--agent <name>] [--owner-email <email> --source <label>|--no-delegated-source] [--rotate-missing-mail-keys]";
860
867
  const { agent, rest: afterAgent } = extractAgentFlag(args);
861
868
  const mailFlags = extractMailSourceFlags(afterAgent, usageText);
862
869
  if (mailFlags.rest.length > 1)
@@ -872,6 +879,7 @@ function parseConnectCommand(args) {
872
879
  ...(mailFlags.ownerEmail !== undefined ? { ownerEmail: mailFlags.ownerEmail } : {}),
873
880
  ...(mailFlags.source !== undefined ? { source: mailFlags.source } : {}),
874
881
  ...(mailFlags.noDelegatedSource ? { noDelegatedSource: true } : {}),
882
+ ...(mailFlags.rotateMissingMailKeys ? { rotateMissingMailKeys: true } : {}),
875
883
  };
876
884
  }
877
885
  function parseMailCommand(args) {
@@ -912,7 +920,7 @@ function parseMailCommand(args) {
912
920
  }
913
921
  function parseAccountCommand(args) {
914
922
  const [sub, ...subArgs] = args;
915
- const usageText = "Usage: ouro account ensure [--agent <name>] [--owner-email <email> --source <label>|--no-delegated-source]";
923
+ const usageText = "Usage: ouro account ensure [--agent <name>] [--owner-email <email> --source <label>|--no-delegated-source] [--rotate-missing-mail-keys]";
916
924
  if (sub !== "ensure") {
917
925
  throw new Error(usageText);
918
926
  }
@@ -926,6 +934,7 @@ function parseAccountCommand(args) {
926
934
  ...(mailFlags.ownerEmail !== undefined ? { ownerEmail: mailFlags.ownerEmail } : {}),
927
935
  ...(mailFlags.source !== undefined ? { source: mailFlags.source } : {}),
928
936
  ...(mailFlags.noDelegatedSource ? { noDelegatedSource: true } : {}),
937
+ ...(mailFlags.rotateMissingMailKeys ? { rotateMissingMailKeys: true } : {}),
929
938
  };
930
939
  }
931
940
  function parseProviderUseCommand(args) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.470",
3
+ "version": "0.1.0-alpha.471",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",