@pleri/olam-cli 0.1.196 → 0.1.199

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.
Files changed (139) hide show
  1. package/README.md +52 -0
  2. package/dist/ask/knowledge-pack.generated.d.ts.map +1 -1
  3. package/dist/ask/knowledge-pack.generated.js +10 -8
  4. package/dist/ask/knowledge-pack.generated.js.map +1 -1
  5. package/dist/commands/auth-list-json.d.ts +34 -0
  6. package/dist/commands/auth-list-json.d.ts.map +1 -1
  7. package/dist/commands/auth-list-json.js +24 -0
  8. package/dist/commands/auth-list-json.js.map +1 -1
  9. package/dist/commands/auth-migrate.d.ts +212 -0
  10. package/dist/commands/auth-migrate.d.ts.map +1 -0
  11. package/dist/commands/auth-migrate.js +465 -0
  12. package/dist/commands/auth-migrate.js.map +1 -0
  13. package/dist/commands/auth.d.ts.map +1 -1
  14. package/dist/commands/auth.js +239 -184
  15. package/dist/commands/auth.js.map +1 -1
  16. package/dist/commands/bootstrap.d.ts +4 -0
  17. package/dist/commands/bootstrap.d.ts.map +1 -1
  18. package/dist/commands/bootstrap.js +10 -0
  19. package/dist/commands/bootstrap.js.map +1 -1
  20. package/dist/commands/dispatch.d.ts.map +1 -1
  21. package/dist/commands/dispatch.js +11 -1
  22. package/dist/commands/dispatch.js.map +1 -1
  23. package/dist/commands/doctor.d.ts +33 -0
  24. package/dist/commands/doctor.d.ts.map +1 -1
  25. package/dist/commands/doctor.js +299 -12
  26. package/dist/commands/doctor.js.map +1 -1
  27. package/dist/commands/kg-mirror.d.ts +18 -2
  28. package/dist/commands/kg-mirror.d.ts.map +1 -1
  29. package/dist/commands/kg-mirror.js +78 -3
  30. package/dist/commands/kg-mirror.js.map +1 -1
  31. package/dist/commands/mcp/complete.d.ts +36 -0
  32. package/dist/commands/mcp/complete.d.ts.map +1 -0
  33. package/dist/commands/mcp/complete.js +66 -0
  34. package/dist/commands/mcp/complete.js.map +1 -0
  35. package/dist/commands/mcp/index.d.ts +1 -1
  36. package/dist/commands/mcp/index.d.ts.map +1 -1
  37. package/dist/commands/mcp/index.js +3 -1
  38. package/dist/commands/mcp/index.js.map +1 -1
  39. package/dist/commands/memory/bridge.d.ts +1 -1
  40. package/dist/commands/memory/bridge.d.ts.map +1 -1
  41. package/dist/commands/memory/bridge.js +2 -6
  42. package/dist/commands/memory/bridge.js.map +1 -1
  43. package/dist/commands/memory/secret.d.ts.map +1 -1
  44. package/dist/commands/memory/secret.js +4 -3
  45. package/dist/commands/memory/secret.js.map +1 -1
  46. package/dist/commands/observe.d.ts +3 -3
  47. package/dist/commands/observe.d.ts.map +1 -1
  48. package/dist/commands/observe.js +11 -8
  49. package/dist/commands/observe.js.map +1 -1
  50. package/dist/commands/runbooks.d.ts.map +1 -1
  51. package/dist/commands/runbooks.js +77 -10
  52. package/dist/commands/runbooks.js.map +1 -1
  53. package/dist/commands/services-tls.d.ts.map +1 -1
  54. package/dist/commands/services-tls.js +41 -0
  55. package/dist/commands/services-tls.js.map +1 -1
  56. package/dist/commands/services.d.ts +45 -3
  57. package/dist/commands/services.d.ts.map +1 -1
  58. package/dist/commands/services.js +198 -71
  59. package/dist/commands/services.js.map +1 -1
  60. package/dist/commands/setup-phase-8-kg-hook.d.ts +48 -0
  61. package/dist/commands/setup-phase-8-kg-hook.d.ts.map +1 -0
  62. package/dist/commands/setup-phase-8-kg-hook.js +93 -0
  63. package/dist/commands/setup-phase-8-kg-hook.js.map +1 -0
  64. package/dist/commands/setup-phase-9-memory-bridge.d.ts +36 -0
  65. package/dist/commands/setup-phase-9-memory-bridge.d.ts.map +1 -0
  66. package/dist/commands/setup-phase-9-memory-bridge.js +59 -0
  67. package/dist/commands/setup-phase-9-memory-bridge.js.map +1 -0
  68. package/dist/commands/setup.d.ts +34 -1
  69. package/dist/commands/setup.d.ts.map +1 -1
  70. package/dist/commands/setup.js +328 -23
  71. package/dist/commands/setup.js.map +1 -1
  72. package/dist/commands/update.d.ts +24 -0
  73. package/dist/commands/update.d.ts.map +1 -1
  74. package/dist/commands/update.js +53 -0
  75. package/dist/commands/update.js.map +1 -1
  76. package/dist/commands/upgrade.d.ts +5 -0
  77. package/dist/commands/upgrade.d.ts.map +1 -1
  78. package/dist/commands/upgrade.js +31 -8
  79. package/dist/commands/upgrade.js.map +1 -1
  80. package/dist/image-digests.json +8 -8
  81. package/dist/index.js +4302 -2466
  82. package/dist/lib/auth-backend.d.ts +168 -0
  83. package/dist/lib/auth-backend.d.ts.map +1 -0
  84. package/dist/lib/auth-backend.js +172 -0
  85. package/dist/lib/auth-backend.js.map +1 -0
  86. package/dist/lib/auth-list-cache.d.ts +67 -0
  87. package/dist/lib/auth-list-cache.d.ts.map +1 -0
  88. package/dist/lib/auth-list-cache.js +84 -0
  89. package/dist/lib/auth-list-cache.js.map +1 -0
  90. package/dist/lib/auth-list.d.ts +107 -0
  91. package/dist/lib/auth-list.d.ts.map +1 -0
  92. package/dist/lib/auth-list.js +123 -0
  93. package/dist/lib/auth-list.js.map +1 -0
  94. package/dist/lib/auth-login.d.ts +92 -0
  95. package/dist/lib/auth-login.d.ts.map +1 -0
  96. package/dist/lib/auth-login.js +124 -0
  97. package/dist/lib/auth-login.js.map +1 -0
  98. package/dist/lib/auth-mutator-backend.d.ts +54 -0
  99. package/dist/lib/auth-mutator-backend.d.ts.map +1 -0
  100. package/dist/lib/auth-mutator-backend.js +62 -0
  101. package/dist/lib/auth-mutator-backend.js.map +1 -0
  102. package/dist/lib/auth-remote.d.ts +50 -0
  103. package/dist/lib/auth-remote.d.ts.map +1 -1
  104. package/dist/lib/auth-remote.js +84 -2
  105. package/dist/lib/auth-remote.js.map +1 -1
  106. package/dist/lib/bootstrap-kubernetes.d.ts +93 -12
  107. package/dist/lib/bootstrap-kubernetes.d.ts.map +1 -1
  108. package/dist/lib/bootstrap-kubernetes.js +364 -53
  109. package/dist/lib/bootstrap-kubernetes.js.map +1 -1
  110. package/dist/lib/config.d.ts +7 -0
  111. package/dist/lib/config.d.ts.map +1 -1
  112. package/dist/lib/config.js.map +1 -1
  113. package/dist/lib/health-probes.d.ts +0 -22
  114. package/dist/lib/health-probes.d.ts.map +1 -1
  115. package/dist/lib/health-probes.js +23 -2
  116. package/dist/lib/health-probes.js.map +1 -1
  117. package/dist/lib/peripheral-registry.d.ts +11 -0
  118. package/dist/lib/peripheral-registry.d.ts.map +1 -1
  119. package/dist/lib/peripheral-registry.js +5 -0
  120. package/dist/lib/peripheral-registry.js.map +1 -1
  121. package/dist/lib/plans-client.d.ts.map +1 -1
  122. package/dist/lib/plans-client.js +6 -3
  123. package/dist/lib/plans-client.js.map +1 -1
  124. package/dist/mcp-server.js +14 -3
  125. package/hermes-bundle/version.json +1 -1
  126. package/host-cp/k8s/manifests/30-configmap.yaml +4 -0
  127. package/host-cp/k8s/manifests/50-deployment.yaml +13 -1
  128. package/host-cp/k8s/manifests/auth-service/50-deployment.yaml +1 -1
  129. package/host-cp/k8s/manifests/kg-service/50-deployment.yaml +1 -1
  130. package/host-cp/k8s/manifests/mcp-auth-service/50-deployment.yaml +1 -1
  131. package/host-cp/k8s/manifests/memory-service/50-deployment.yaml +1 -1
  132. package/host-cp/src/dispatch-persister.mjs +157 -0
  133. package/host-cp/src/pr-nanny.mjs +7 -0
  134. package/host-cp/src/server.mjs +175 -3
  135. package/host-cp/src/world-watchdog-pid-lookup.mjs +119 -0
  136. package/host-cp/src/world-watchdog-probes.mjs +271 -0
  137. package/host-cp/src/world-watchdog-recovery.mjs +192 -0
  138. package/host-cp/src/world-watchdog.mjs +313 -0
  139. package/package.json +1 -1
@@ -39,7 +39,11 @@ import { readConfig } from '../lib/config.js';
39
39
  import { applyK8sAuthRefresh, resolveKubectlContext, } from '../lib/auth-refresh-kubernetes.js';
40
40
  import { remoteOAuthStart, remoteListServiceTokens, remoteListAccounts, remoteBindServiceToken, remoteDeleteServiceToken, remoteIssueAnthropicToken, remoteListAnthropicTokens, remoteRevokeAnthropicToken, runDoctorChecks, } from '../lib/auth-remote.js';
41
41
  import { resolveCfAccessServiceToken } from '../lib/cf-access-token.js';
42
- import { renderAuthListJson } from './auth-list-json.js';
42
+ import { renderAuthListJson, renderRemoteAuthListJson } from './auth-list-json.js';
43
+ import { runAuthLogin } from '../lib/auth-login.js';
44
+ import { runAuthList, } from '../lib/auth-list.js';
45
+ import { resolveMutatorBackend, } from '../lib/auth-mutator-backend.js';
46
+ import { registerAuthMigrate } from './auth-migrate.js';
43
47
  /**
44
48
  * Best-effort browser opener. Uses the host's native "open URL" command:
45
49
  * `open` on macOS, `xdg-open` on Linux, `start` on Windows. Silently
@@ -57,6 +61,106 @@ function openBrowser(url) {
57
61
  // Ignore — caller already printed the URL.
58
62
  }
59
63
  }
64
+ /**
65
+ * B4 (narrowed) — render an `olam auth list` result to stdout. Returns the
66
+ * desired exit code (0 = success; 1 = error). Pulled out of the action body
67
+ * so the orchestration in `runAuthList` is testable without driving the
68
+ * Commander.js scaffolding.
69
+ */
70
+ function renderAuthList(result, opts) {
71
+ if (result.mode === 'error') {
72
+ if (opts.json) {
73
+ console.log(JSON.stringify({ error: result.message }));
74
+ }
75
+ else {
76
+ printError(result.message);
77
+ }
78
+ return result.exitCode;
79
+ }
80
+ if (result.mode === 'local') {
81
+ if (!result.reachable) {
82
+ if (opts.json) {
83
+ console.log(JSON.stringify({ error: 'auth-container-unreachable', reachable: false }));
84
+ return 1;
85
+ }
86
+ printError('Auth container is not reachable. Run `olam services up` first.');
87
+ return 1;
88
+ }
89
+ if (opts.json) {
90
+ console.log(renderAuthListJson(result.accounts));
91
+ return 0;
92
+ }
93
+ printHeader(`Credentials (${result.accounts.length})`);
94
+ if (result.accounts.length === 0) {
95
+ console.log(` ${pc.dim('No credentials — run: olam auth login --label primary')}`);
96
+ return 0;
97
+ }
98
+ const stateColor = (s) => {
99
+ if (s === 'active')
100
+ return pc.green('active');
101
+ if (s === 'cooldown')
102
+ return pc.yellow('cooldown');
103
+ if (s === 'expired')
104
+ return pc.red('expired');
105
+ if (s === 'disabled')
106
+ return pc.dim('disabled');
107
+ return pc.dim(s ?? 'unknown');
108
+ };
109
+ for (const a of result.accounts) {
110
+ const label = a.accountLabel ?? a.id;
111
+ const reqs = a.usage?.requestCount5h ?? 0;
112
+ const last429 = a.usage?.last429At
113
+ ? `last429=${a.usage.last429At}`
114
+ : 'last429=never';
115
+ const reset = a.rateLimitResetsAt ? `resets=${a.rateLimitResetsAt}` : '';
116
+ console.log(` ${pc.bold(label.padEnd(18))} ${stateColor(a.state).padEnd(18)} ` +
117
+ `${pc.dim(`req5h=${reqs}`)} ${pc.dim(`exp=${a.expiresIn}`)} ${pc.dim(last429)} ${pc.yellow(reset)}`);
118
+ }
119
+ return 0;
120
+ }
121
+ // remote
122
+ if (opts.json) {
123
+ console.log(renderRemoteAuthListJson(result.accounts, result.stale, result.fetchedAt));
124
+ return 0;
125
+ }
126
+ const staleSuffix = result.stale ? pc.yellow(' (stale)') : '';
127
+ printHeader(`Remote accounts (${result.accounts.length})${staleSuffix}`);
128
+ if (result.accounts.length === 0) {
129
+ console.log(` ${pc.dim('No accounts — run: olam auth login --remote ' + result.baseUrl)}`);
130
+ }
131
+ else {
132
+ for (const a of result.accounts) {
133
+ const label = a.label ?? a.id;
134
+ const state = a.state ?? 'unknown';
135
+ const exp = a.expiresIn ?? '';
136
+ console.log(` ${pc.bold(label.padEnd(20))} ${pc.green(state).padEnd(16)} ${pc.dim(exp)}`);
137
+ }
138
+ }
139
+ if (result.stale) {
140
+ const fetchedAtIso = new Date(result.fetchedAt).toISOString();
141
+ console.log(`\n ${pc.yellow('warning:')} showing cached results from ${fetchedAtIso}; ` +
142
+ `fresh fetch failed (${result.fetchError ?? 'unknown error'}).`);
143
+ console.log(` ${pc.dim('Retry with --no-cache once connectivity is restored.')}`);
144
+ }
145
+ return 0;
146
+ }
147
+ /**
148
+ * B4 (narrowed) — adapter around `resolveMutatorBackend` for the three
149
+ * local-only mutator subcommands. Prints the message body to stderr and
150
+ * returns the desired exit code (0 = proceed locally; non-zero = stop).
151
+ */
152
+ function handleMutatorBackend(subcommand, opts) {
153
+ const outcome = resolveMutatorBackend(subcommand, opts);
154
+ if (outcome.outcome === 'proceed-local')
155
+ return 0;
156
+ if (outcome.outcome === 'conflict') {
157
+ process.stderr.write(`Error: ${outcome.message}\n`);
158
+ return 1;
159
+ }
160
+ // remote-unsupported
161
+ process.stderr.write(`Error: ${outcome.message}\n`);
162
+ return outcome.exitCode;
163
+ }
60
164
  async function promptLine(question) {
61
165
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
62
166
  try {
@@ -98,9 +202,16 @@ export function registerAuth(program) {
98
202
  });
99
203
  auth
100
204
  .command('disable')
101
- .description('Take a credential out of rotation (manual cooldown)')
205
+ .description('Take a credential out of rotation (manual cooldown). LOCAL ONLY — no cloud equivalent yet (see OQ7 in docs/plans/cloud-only-vault/README.md).')
102
206
  .argument('<label>', 'Credential label or id')
103
- .action(async (label) => {
207
+ .option('--local', 'Explicit local backend (default only backend supported today).')
208
+ .option('--remote [url]', 'Reserved — exits 2 with structured message pointing to OQ7.')
209
+ .action(async (label, opts) => {
210
+ const code = handleMutatorBackend('disable', opts);
211
+ if (code !== 0) {
212
+ process.exitCode = code;
213
+ return;
214
+ }
104
215
  const client = new AuthClient();
105
216
  try {
106
217
  await client.disableAccount(label);
@@ -113,9 +224,16 @@ export function registerAuth(program) {
113
224
  });
114
225
  auth
115
226
  .command('enable')
116
- .description('Re-enable a disabled credential')
227
+ .description('Re-enable a disabled credential. LOCAL ONLY — no cloud equivalent yet (see OQ7 in docs/plans/cloud-only-vault/README.md).')
117
228
  .argument('<label>', 'Credential label or id')
118
- .action(async (label) => {
229
+ .option('--local', 'Explicit local backend (default only backend supported today).')
230
+ .option('--remote [url]', 'Reserved — exits 2 with structured message pointing to OQ7.')
231
+ .action(async (label, opts) => {
232
+ const code = handleMutatorBackend('enable', opts);
233
+ if (code !== 0) {
234
+ process.exitCode = code;
235
+ return;
236
+ }
119
237
  const client = new AuthClient();
120
238
  try {
121
239
  await client.enableAccount(label);
@@ -143,73 +261,85 @@ export function registerAuth(program) {
143
261
  });
144
262
  auth
145
263
  .command('login')
146
- .description('Run the OAuth PKCE flow to store a Claude account in the auth container, or print a remote OAuth URL (--remote)')
264
+ .description('Log into the cloud auth-worker by default (Phase B); use --local to opt into the legacy local auth-service container PKCE flow.')
147
265
  .option('--label <label>', 'Account label (e.g. primary, burner-1). Defaults to "primary" for the first account, "burner-N" thereafter.')
148
- .option('--remote <url>', 'Auth-worker base URL. Prints the OAuth start URL for browser completion (v1 dogfood).')
266
+ .option('--local', 'Opt into the deprecated local auth-service container PKCE flow. Emits a deprecation warning.')
267
+ .option('--remote [url]', 'Force remote login. Optionally accepts an explicit auth-worker URL (back-compat); without a URL falls through to env/file/default discovery.')
268
+ .option('--yes, -y', 'Skip the first-time interactive confirm prompt before flipping to remote.')
149
269
  .option('--print-url', 'Print the OAuth URL without attempting to open a browser (implied by --remote).')
150
270
  .option('--service-token <client_id:secret>', 'Delegate to bind-service-token flow instead (use with --remote).')
151
271
  .action(async (opts) => {
152
- // ── Remote path (e4) ──────────────────────────────────────────────────
153
- if (opts.remote) {
154
- const baseUrl = opts.remote;
155
- if (opts.serviceToken) {
156
- // Delegate to bind-service-token flow
157
- const parts = opts.serviceToken.split(':');
158
- if (parts.length < 2) {
159
- printError('--service-token must be in format <client_id>:<secret>');
160
- process.exitCode = 1;
161
- return;
162
- }
163
- const [clientId] = parts;
164
- const label = opts.label ?? clientId ?? 'service-token';
165
- printHeader('Auth worker bind service token');
166
- console.log(`\n ${pc.bold('Step 1:')} Open ${pc.cyan(baseUrl + '/v1/oauth/start')} in a browser.`);
167
- console.log(` Complete CF Access SSO. You may see a JSON response or "no credentials" — that is normal.`);
168
- const rawCookie = await promptLine(`\n ${pc.dim('Paste CF_Authorization cookie value (from DevTools > Application > Cookies):')} `);
169
- if (!rawCookie) {
170
- printError('No cookie provided. Aborting.');
171
- process.exitCode = 1;
172
- return;
173
- }
174
- const clientIdStr = clientId ?? '';
175
- try {
176
- await remoteBindServiceToken({ baseUrl, cfAuthCookie: rawCookie }, { client_id: clientIdStr, label });
177
- printSuccess(`Service token bound: ${clientIdStr} (label=${label})`);
178
- console.log(`\n${pc.dim('Set ANTHROPIC_BASE' + '_URL=' + baseUrl + '/v1/proxy in your shell to route Claude API calls through this worker.')}`);
179
- }
180
- catch (err) {
181
- printError(err instanceof Error ? err.message : 'bind failed');
182
- process.exitCode = 1;
183
- }
272
+ const result = await runAuthLogin(opts, {
273
+ promptConfirm: (question) => promptLine(question),
274
+ executeRemoteLogin: (baseUrl, o) => executeRemoteLogin(baseUrl, o),
275
+ executeLocalLogin: (o) => executeLocalLogin(o),
276
+ });
277
+ if (result.exitCode !== 0) {
278
+ process.exitCode = result.exitCode;
279
+ }
280
+ });
281
+ // ── Legacy login executors (extracted from the action body so runAuthLogin
282
+ // can drive them; behaviour preserved verbatim other than the --remote
283
+ // <url> form now arrives via the resolution-helper rather than opts.remote
284
+ // directly). ───────────────────────────────────────────────────────────────
285
+ async function executeRemoteLogin(baseUrl, opts) {
286
+ if (opts.serviceToken) {
287
+ // Delegate to bind-service-token flow
288
+ const parts = opts.serviceToken.split(':');
289
+ if (parts.length < 2) {
290
+ printError('--service-token must be in format <client_id>:<secret>');
291
+ process.exitCode = 1;
184
292
  return;
185
293
  }
186
- // Default: print (and optionally open) the OAuth start URL.
187
- // V1 dogfood: browser completes the full flow (CF Access SSO → Anthropic
188
- // OAuth worker callback token stored). CLI re-checks via `list --remote`.
189
- // Full CLI-orchestrated cookie-bridging is deferred to v2.
190
- let startUrl;
191
- try {
192
- const r = await remoteOAuthStart({ baseUrl });
193
- startUrl = r.authorize_url;
294
+ const [clientId] = parts;
295
+ const label = opts.label ?? clientId ?? 'service-token';
296
+ printHeader('Auth worker bind service token');
297
+ console.log(`\n ${pc.bold('Step 1:')} Open ${pc.cyan(baseUrl + '/v1/oauth/start')} in a browser.`);
298
+ console.log(` Complete CF Access SSO. You may see a JSON response or "no credentials" — that is normal.`);
299
+ const rawCookie = await promptLine(`\n ${pc.dim('Paste CF_Authorization cookie value (from DevTools > Application > Cookies):')} `);
300
+ if (!rawCookie) {
301
+ printError('No cookie provided. Aborting.');
302
+ process.exitCode = 1;
303
+ return;
194
304
  }
195
- catch {
196
- // Fallback if /v1/oauth/start isn't reachable (CF Access gate, etc.)
197
- startUrl = `${baseUrl.replace(/\/+$/, '')}/v1/oauth/start`;
305
+ const clientIdStr = clientId ?? '';
306
+ try {
307
+ await remoteBindServiceToken({ baseUrl, cfAuthCookie: rawCookie }, { client_id: clientIdStr, label });
308
+ printSuccess(`Service token bound: ${clientIdStr} (label=${label})`);
309
+ console.log(`\n${pc.dim('Set ANTHROPIC_BASE' + '_URL=' + baseUrl + '/v1/proxy in your shell to route Claude API calls through this worker.')}`);
198
310
  }
199
- printHeader('Auth worker — OAuth login (v1 dogfood)');
200
- console.log(`\n ${pc.bold('1.')} Open this URL in your browser to authenticate via CF Access + Anthropic OAuth:`);
201
- console.log(`\n ${pc.cyan(startUrl)}\n`);
202
- console.log(` ${pc.bold('2.')} Complete the CF Access SSO and Anthropic OAuth consent.`);
203
- console.log(` The worker stores your tokens on callback — the browser lands on a success page.`);
204
- console.log(`\n ${pc.bold('3.')} Confirm your credentials are stored:\n`);
205
- console.log(` ${pc.dim('olam auth list --remote ' + baseUrl)}\n`);
206
- console.log(` ${pc.dim('Note: full CLI-orchestrated browser SSO flow is deferred to v2 (cookie-bridging complexity).')}\n`);
207
- if (!opts.printUrl) {
208
- openBrowser(startUrl);
311
+ catch (err) {
312
+ printError(err instanceof Error ? err.message : 'bind failed');
313
+ process.exitCode = 1;
209
314
  }
210
315
  return;
211
316
  }
212
- // ── Local path (existing behaviour) ──────────────────────────────────
317
+ // Default: print (and optionally open) the OAuth start URL.
318
+ // V1 dogfood: browser completes the full flow (CF Access SSO → Anthropic
319
+ // OAuth → worker callback → token stored). CLI re-checks via `list --remote`.
320
+ // Full CLI-orchestrated cookie-bridging is deferred to v2.
321
+ let startUrl;
322
+ try {
323
+ const r = await remoteOAuthStart({ baseUrl });
324
+ startUrl = r.authorize_url;
325
+ }
326
+ catch {
327
+ // Fallback if /v1/oauth/start isn't reachable (CF Access gate, etc.)
328
+ startUrl = `${baseUrl.replace(/\/+$/, '')}/v1/oauth/start`;
329
+ }
330
+ printHeader('Auth worker — OAuth login (v1 dogfood)');
331
+ console.log(`\n ${pc.bold('1.')} Open this URL in your browser to authenticate via CF Access + Anthropic OAuth:`);
332
+ console.log(`\n ${pc.cyan(startUrl)}\n`);
333
+ console.log(` ${pc.bold('2.')} Complete the CF Access SSO and Anthropic OAuth consent.`);
334
+ console.log(` The worker stores your tokens on callback — the browser lands on a success page.`);
335
+ console.log(`\n ${pc.bold('3.')} Confirm your credentials are stored:\n`);
336
+ console.log(` ${pc.dim('olam auth list --remote ' + baseUrl)}\n`);
337
+ console.log(` ${pc.dim('Tokens written to cloud auth-worker. Full CLI-orchestrated browser SSO flow is deferred to v2.')}\n`);
338
+ if (!opts.printUrl) {
339
+ openBrowser(startUrl);
340
+ }
341
+ }
342
+ async function executeLocalLogin(opts) {
213
343
  const preflight = await runAuthPreflight({ autoStart: true });
214
344
  if (preflight.verdict !== 'ok' && preflight.verdict !== 'no-accounts') {
215
345
  printError(preflight.message);
@@ -249,7 +379,7 @@ export function registerAuth(program) {
249
379
  process.exitCode = 1;
250
380
  return;
251
381
  }
252
- printHeader('Claude OAuth — PKCE flow');
382
+ printHeader('Claude OAuth — PKCE flow (legacy local container)');
253
383
  console.log(`\n ${pc.bold('1.')} Opening Claude in your default browser…`);
254
384
  console.log(` ${pc.dim(pending.loginUrl)}`);
255
385
  openBrowser(pending.loginUrl);
@@ -265,13 +395,14 @@ export function registerAuth(program) {
265
395
  try {
266
396
  const result = await client.completeLogin(statePart, codePart);
267
397
  printSuccess(`Account stored: ${result.account} (${result.expiresIn})`);
398
+ console.log(`${pc.dim('Tokens written to ~/.olam/auth-data/accounts.json (deprecated; cloud is the new default).')}`);
268
399
  console.log(`\n${pc.dim('Next: olam create --name my-world')}`);
269
400
  }
270
401
  catch (err) {
271
402
  printError(err instanceof Error ? err.message : 'token exchange failed');
272
403
  process.exitCode = 1;
273
404
  }
274
- });
405
+ }
275
406
  auth
276
407
  .command('logout')
277
408
  .description('Remove an account from the auth container')
@@ -289,9 +420,16 @@ export function registerAuth(program) {
289
420
  });
290
421
  auth
291
422
  .command('refresh')
292
- .description('Force-refresh an account token (substrate-aware: updates kubernetes Secret on k8s substrate)')
423
+ .description('Force-refresh an account token (substrate-aware: updates kubernetes Secret on k8s substrate). LOCAL ONLY — no cloud equivalent yet (see OQ7 in docs/plans/cloud-only-vault/README.md).')
293
424
  .argument('<account>', 'Account id')
294
- .action(async (accountId) => {
425
+ .option('--local', 'Explicit local backend (default only backend supported today).')
426
+ .option('--remote [url]', 'Reserved — exits 2 with structured message pointing to OQ7.')
427
+ .action(async (accountId, opts) => {
428
+ const code = handleMutatorBackend('refresh', opts);
429
+ if (code !== 0) {
430
+ process.exitCode = code;
431
+ return;
432
+ }
295
433
  const cfg = readConfig();
296
434
  const isK8s = cfg.host.substrate === 'kubernetes';
297
435
  // Kubernetes substrate: validate context before touching auth-service (SEC-NEW-003).
@@ -366,132 +504,49 @@ export function registerAuth(program) {
366
504
  process.exitCode = 1;
367
505
  }
368
506
  });
369
- // ── e6: list (local + --remote) ─────────────────────────────────────────
507
+ // ── B4 (narrowed): list defaults to cloud auth-worker ────────────────────
370
508
  auth
371
509
  .command('list')
372
- .description('List credentials (local by default; --remote to query a remote auth-worker)')
373
- .option('--remote <url>', 'Auth-worker base URL')
374
- .option('--cookie <value>', 'CF_Authorization cookie for authenticated requests')
375
- .option('--json', 'Emit machine-readable JSON instead of the text table (local vault only)', false)
510
+ .description('List credentials. Defaults to the cloud auth-worker (Phase B). Pass --local to read the legacy ~/.olam/auth-data/accounts.json (emits deprecation warning).')
511
+ .option('--local', 'Read the legacy local auth-service vault. Emits a deprecation warning.')
512
+ .option('--remote [url]', 'Force remote. Optionally accepts an explicit auth-worker URL; without one falls through to env/file/default discovery.')
513
+ .option('--cookie <value>', 'CF_Authorization cookie for authenticated requests (remote path)')
514
+ .option('--no-cache', 'Bypass the 30 s TTL cache and force a fresh fetch (remote path)')
515
+ .option('--json', 'Emit machine-readable JSON instead of the text table', false)
376
516
  .action(async (opts) => {
377
- if (opts.remote) {
378
- const baseUrl = opts.remote;
379
- const clientOpts = { baseUrl, cfAuthCookie: opts.cookie };
380
- let accounts = [];
381
- let tokens = [];
382
- try {
383
- accounts = await remoteListAccounts(clientOpts);
384
- }
385
- catch (err) {
386
- printWarning(`Could not fetch accounts: ${err instanceof Error ? err.message : String(err)}`);
387
- }
388
- try {
389
- tokens = await remoteListServiceTokens(clientOpts);
390
- }
391
- catch (err) {
392
- printWarning(`Could not fetch service tokens: ${err instanceof Error ? err.message : String(err)}`);
393
- }
394
- printHeader(`Remote accounts (${accounts.length})`);
395
- if (accounts.length === 0) {
396
- console.log(` ${pc.dim('No accounts — run: olam auth login --remote ' + baseUrl)}`);
397
- }
398
- else {
399
- for (const a of accounts) {
400
- const label = a.label ?? a.id;
401
- const state = a.state ?? 'unknown';
402
- const exp = a.expiresIn ?? '';
403
- console.log(` ${pc.bold(label.padEnd(20))} ${pc.green(state).padEnd(16)} ${pc.dim(exp)}`);
404
- }
405
- }
406
- printHeader(`Remote service tokens (${tokens.length})`);
407
- if (tokens.length === 0) {
408
- console.log(` ${pc.dim('No service tokens — run: olam auth bind-service-token --remote ' + baseUrl)}`);
409
- }
410
- else {
411
- for (const t of tokens) {
412
- const label = t.label ?? t.client_id;
413
- const created = t.created_at ?? '';
414
- console.log(` ${pc.bold(label.padEnd(20))} ${pc.dim(t.client_id)} ${pc.dim(created)}`);
415
- }
416
- }
417
- return;
418
- }
419
- // Local list (original behaviour)
420
- const client = new AuthClient();
421
- const status = await client.status();
422
- if (!status.reachable) {
423
- if (opts.json) {
424
- // Machine-readable error so drivers never screen-scrape the
425
- // human "not reachable" line. Exit code unchanged (1).
426
- console.log(JSON.stringify({ error: 'auth-container-unreachable', reachable: false }));
427
- process.exitCode = 1;
428
- return;
429
- }
430
- printError('Auth container is not reachable. Run `olam services up` first.');
431
- process.exitCode = 1;
432
- return;
433
- }
434
- if (opts.json) {
435
- console.log(renderAuthListJson(status.accounts));
436
- return;
437
- }
438
- printHeader(`Credentials (${status.accounts.length})`);
439
- if (status.accounts.length === 0) {
440
- console.log(` ${pc.dim('No credentials — run: olam auth login --label primary')}`);
441
- return;
442
- }
443
- const stateColor = (s) => {
444
- if (s === 'active')
445
- return pc.green('active');
446
- if (s === 'cooldown')
447
- return pc.yellow('cooldown');
448
- if (s === 'expired')
449
- return pc.red('expired');
450
- if (s === 'disabled')
451
- return pc.dim('disabled');
452
- return pc.dim(s ?? 'unknown');
517
+ const listOpts = {
518
+ local: opts.local,
519
+ remote: opts.remote,
520
+ cookie: opts.cookie,
521
+ // commander.js maps `--no-cache` to `cache: false`. Translate.
522
+ noCache: opts.cache === false,
523
+ json: opts.json,
453
524
  };
454
- for (const a of status.accounts) {
455
- const label = a.accountLabel ?? a.id;
456
- const reqs = a.usage?.requestCount5h ?? 0;
457
- const last429 = a.usage?.last429At
458
- ? `last429=${a.usage.last429At}`
459
- : 'last429=never';
460
- const reset = a.rateLimitResetsAt ? `resets=${a.rateLimitResetsAt}` : '';
461
- console.log(` ${pc.bold(label.padEnd(18))} ${stateColor(a.state).padEnd(18)} ` +
462
- `${pc.dim(`req5h=${reqs}`)} ${pc.dim(`exp=${a.expiresIn}`)} ${pc.dim(last429)} ${pc.yellow(reset)}`);
463
- }
525
+ const result = await runAuthList(listOpts, {
526
+ fetchRemoteAccounts: async (baseUrl, cookie) => {
527
+ return remoteListAccounts({ baseUrl, cfAuthCookie: cookie });
528
+ },
529
+ fetchLocalStatus: () => new AuthClient().status(),
530
+ });
531
+ const exitCode = renderAuthList(result, listOpts);
532
+ if (exitCode !== 0)
533
+ process.exitCode = exitCode;
464
534
  });
465
- // ── e6: migrate-to-remote ──────────────────────────────────────────────
535
+ // ── B3: `olam auth migrate` (one-shot migration tool) ──────────────────
536
+ registerAuthMigrate(auth);
537
+ // ── Deprecation pointer: old `migrate-to-remote` → `migrate` ────────────
538
+ // Phase B3 replaces the e6 placeholder. Operators with scripted invocations
539
+ // of the old name see a one-line pointer + exit 0 (no script breakage); the
540
+ // real auto-migration lives at `olam auth migrate` now.
466
541
  auth
467
542
  .command('migrate-to-remote')
468
- .description('Print guidance for re-authenticating local credentials against the remote auth-worker (v1: no auto-migration of secrets)')
469
- .requiredOption('--url <url>', 'Auth-worker base URL')
470
- .action(async (opts) => {
471
- const baseUrl = opts.url;
472
- let accountLabels = [];
473
- try {
474
- const client = new AuthClient();
475
- const status = await client.status();
476
- accountLabels = status.accounts.map((a) => a.accountLabel ?? a.id);
477
- }
478
- catch {
479
- // Auth container may not be running — proceed with generic guidance.
480
- }
481
- printHeader('Migrate local credentials to remote auth-worker');
482
- console.log(`\n Remote URL: ${pc.cyan(baseUrl)}\n`);
483
- console.log(` ${pc.dim('V1 note: secrets cannot be migrated automatically. Each account must re-authenticate via OAuth.')}\n`);
484
- if (accountLabels.length === 0) {
485
- console.log(` No local accounts found. Run:`);
486
- console.log(`\n ${pc.cyan('olam auth login --remote ' + baseUrl)}\n`);
487
- }
488
- else {
489
- console.log(` Found ${accountLabels.length} local account(s). For each, run:\n`);
490
- for (const label of accountLabels) {
491
- console.log(` ${pc.cyan('olam auth login --remote ' + baseUrl + ' --label ' + label)}`);
492
- }
493
- console.log();
494
- }
543
+ .description('(deprecated) renamed to `olam auth migrate`. Prints pointer + exits 0.')
544
+ .option('--url <url>', 'Auth-worker base URL (ignored; pass --remote to `migrate` instead)')
545
+ .action(() => {
546
+ printHeader('migrate-to-remote has been renamed');
547
+ console.log(`\n Run ${pc.cyan('olam auth migrate --help')} for the new auto-migration flow.\n` +
548
+ ` (B3 of cloud-only-vault: per-account re-OAuth against the cloud auth-worker,\n` +
549
+ ` content-hash idempotent, atomic state writes, --dry-run preview.)\n`);
495
550
  });
496
551
  // ── e6: rotate-service-token ───────────────────────────────────────────
497
552
  auth