@pellux/goodvibes-agent 0.1.81 → 0.1.82

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.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  All notable changes to GoodVibes Agent will be recorded here.
4
4
 
5
+ ## 0.1.82 - 2026-06-01
6
+
7
+ - 3185531 Remove Agent local auth ownership paths
8
+
5
9
  ## 0.1.81 - 2026-06-01
6
10
 
7
11
  - Added local Agent skill bundles so users can group related local skills, enable or disable the bundle, and inject the bundle's member procedures into the same serial assistant conversation.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pellux/goodvibes-agent",
3
- "version": "0.1.81",
3
+ "version": "0.1.82",
4
4
  "private": false,
5
5
  "description": "GoodVibes personal operator assistant TUI with a proactive Agent product brain, isolated Agent Knowledge, local profiles, routines, skills, personas, and explicit build delegation.",
6
6
  "type": "module",
package/src/cli/help.ts CHANGED
@@ -38,7 +38,7 @@ export function renderGoodVibesHelp(binary = 'goodvibes-agent'): string {
38
38
  ' providers List/inspect/use provider config/auth posture',
39
39
  ' profiles Manage isolated Agent profile homes',
40
40
  ' routines Inspect local routines and explicitly promote one to an external schedule',
41
- ' auth Inspect and manage local users, sessions, and bootstrap auth',
41
+ ' auth Inspect Agent auth posture and external runtime token state',
42
42
  ' compat Inspect Agent SDK pin, runtime version, and Agent knowledge route readiness',
43
43
  ' knowledge Use isolated Agent Knowledge/Wiki routes',
44
44
  ' ask|search Shortcuts for isolated Agent Knowledge ask/search',
@@ -170,9 +170,9 @@ const COMMAND_HELP: Record<string, CommandHelp> = {
170
170
  examples: ['models current', 'models openai', 'models use openai:gpt-5.4'],
171
171
  },
172
172
  auth: {
173
- usage: ['auth status', 'auth users', 'auth sessions', 'auth add-user <username>', 'auth clear-bootstrap'],
174
- summary: 'Inspect and manage local admin users, bootstrap auth, and local sessions.',
175
- examples: ['auth', 'auth add-user admin --password-stdin', 'auth clear-bootstrap'],
173
+ usage: ['auth', 'auth status', 'auth review', 'auth users', 'auth sessions'],
174
+ summary: 'Inspect Agent auth posture and external runtime token state. Runtime user/session administration belongs to the runtime-owning TUI or host tooling.',
175
+ examples: ['auth', 'auth status', 'auth users'],
176
176
  },
177
177
  compat: {
178
178
  usage: ['compat', 'compat --json'],
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
1
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
2
2
  import { dirname, join } from 'node:path';
3
3
  import net from 'node:net';
4
4
  import { spawn } from 'node:child_process';
@@ -21,7 +21,6 @@ import { beginOpenAICodexLogin, exchangeOpenAICodexCode } from '@pellux/goodvibe
21
21
  import { inspectProviderAuth } from '@/runtime/index.ts';
22
22
  import { getOrCreateCompanionToken, buildCompanionConnectionInfo, encodeConnectionPayload, formatConnectionBlock } from '@pellux/goodvibes-sdk/platform/pairing';
23
23
  import { generateQrMatrix, renderQrToString } from '@pellux/goodvibes-sdk/platform/pairing';
24
- import { UserAuthManager } from '@pellux/goodvibes-sdk/platform/security';
25
24
  import type { GoodVibesCliParseResult } from './types.ts';
26
25
  import { formatProviderAuthRoute, summarizeProviderAuthRoutes } from './provider-auth-routes.ts';
27
26
  import { classifyProviderSetup } from './provider-classification.ts';
@@ -60,7 +59,7 @@ export function formatJsonOrText(cli: GoodVibesCliParseResult): Formatter {
60
59
  }
61
60
 
62
61
  function exitCodeForText(output: string): number {
63
- if (output.startsWith('Usage:') || output.startsWith('Invalid ')) return 2;
62
+ if (output.startsWith('Usage:') || output.startsWith('Invalid ') || output.startsWith('Unsupported:')) return 2;
64
63
  if (output.startsWith('Session not found:') || output.startsWith('Unknown task:') || output.startsWith('Task submit failed ')) return 1;
65
64
  if (output.startsWith('No stored ') || output.startsWith('No pending ') || output.startsWith('No model ') || output.startsWith('No provider ') || output.startsWith('No auth ')) return 1;
66
65
  if (output.startsWith('Unknown ')) return 1;
@@ -74,59 +73,10 @@ function splitCommandOption(token: string): { readonly name: string; readonly va
74
73
  return { name: token.slice(0, index), value: token.slice(index + 1) };
75
74
  }
76
75
 
77
- function readOptionValue(args: readonly string[], name: string): string | undefined {
78
- for (let index = 0; index < args.length; index += 1) {
79
- const token = args[index]!;
80
- const split = splitCommandOption(token);
81
- if (split.name !== name) continue;
82
- if (split.value !== undefined) return split.value;
83
- const next = args[index + 1];
84
- if (next === undefined || next.startsWith('--')) return undefined;
85
- return next;
86
- }
87
- return undefined;
88
- }
89
-
90
- function readOptionValues(args: readonly string[], name: string): string[] {
91
- const values: string[] = [];
92
- for (let index = 0; index < args.length; index += 1) {
93
- const token = args[index]!;
94
- const split = splitCommandOption(token);
95
- if (split.name !== name) continue;
96
- if (split.value !== undefined) {
97
- values.push(split.value);
98
- continue;
99
- }
100
- const next = args[index + 1];
101
- if (next !== undefined && !next.startsWith('--')) values.push(next);
102
- }
103
- return values;
104
- }
105
-
106
76
  export function hasCommandFlag(args: readonly string[], name: string): boolean {
107
77
  return args.some((arg) => splitCommandOption(arg).name === name);
108
78
  }
109
79
 
110
- function commandValues(args: readonly string[]): string[] {
111
- const values: string[] = [];
112
- for (let index = 0; index < args.length; index += 1) {
113
- const token = args[index]!;
114
- if (!token.startsWith('--')) {
115
- values.push(token);
116
- continue;
117
- }
118
- if (!token.includes('=') && args[index + 1] && !args[index + 1]!.startsWith('--')) index += 1;
119
- }
120
- return values;
121
- }
122
-
123
- function readPassword(args: readonly string[]): string | null {
124
- const explicit = readOptionValue(args, '--password');
125
- if (explicit !== undefined) return explicit;
126
- if (hasCommandFlag(args, '--password-stdin')) return readFileSync(0, 'utf-8').trimEnd();
127
- return process.env.GOODVIBES_AUTH_PASSWORD ?? null;
128
- }
129
-
130
80
  export function extractAuthorizationCode(input: string): string {
131
81
  try {
132
82
  const url = new URL(input);
@@ -291,14 +241,6 @@ export function readAuthPaths(runtime: CliCommandRuntime) {
291
241
  };
292
242
  }
293
243
 
294
- function createCliLocalUserAuthManager(runtime: CliCommandRuntime): UserAuthManager {
295
- const paths = readAuthPaths(runtime);
296
- return new UserAuthManager({
297
- bootstrapFilePath: paths.userStorePath,
298
- bootstrapCredentialPath: paths.bootstrapCredentialPath,
299
- });
300
- }
301
-
302
244
  export async function runNonInteractiveAgent(runtime: CliCommandRuntime): Promise<number> {
303
245
  const prompt = runtime.cli.flags.prompt ?? runtime.cli.positionals.join(' ').trim();
304
246
  if (!prompt) {
@@ -584,84 +526,60 @@ async function renderModels(runtime: CliCommandRuntime): Promise<string> {
584
526
  }
585
527
 
586
528
  async function renderAuth(runtime: CliCommandRuntime): Promise<string> {
587
- const localUserAuthManager = createCliLocalUserAuthManager(runtime);
588
- const [sub = 'status', ...rawRest] = runtime.cli.commandArgs;
589
- const rest = commandValues(rawRest);
590
- if (sub === 'add-user' || sub === 'add') {
591
- const username = rest[0];
592
- if (!username) return 'Usage: goodvibes auth add-user <username> [--password <value>|--password-stdin] [--role <role>]';
593
- const password = readPassword(rawRest);
594
- if (!password) return 'Usage: goodvibes auth add-user <username> [--password <value>|--password-stdin] [--role <role>]';
595
- const roles = readOptionValues(rawRest, '--role').filter((role) => role.length > 0);
596
- const user = localUserAuthManager.addUser(username, password, roles.length > 0 ? roles : ['user']);
597
- return `Auth user added: ${user.username} (${user.roles.join(', ') || 'no roles'})`;
598
- }
599
- if (sub === 'delete-user' || sub === 'remove-user') {
600
- const username = rest[0];
601
- if (!username) return 'Usage: goodvibes auth delete-user <username>';
602
- return localUserAuthManager.deleteUser(username)
603
- ? `Auth user deleted: ${username}`
604
- : `No auth user found: ${username}`;
605
- }
606
- if (sub === 'rotate-password' || sub === 'passwd') {
607
- const username = rest[0];
608
- if (!username) return 'Usage: goodvibes auth rotate-password <username> [--password <value>|--password-stdin]';
609
- const password = readPassword(rawRest);
610
- if (!password) return 'Usage: goodvibes auth rotate-password <username> [--password <value>|--password-stdin]';
611
- localUserAuthManager.rotatePassword(username, password);
612
- return `Auth password rotated: ${username}`;
613
- }
614
- if (sub === 'revoke-session') {
615
- const token = rest[0];
616
- if (!token) return 'Usage: goodvibes auth revoke-session <token-or-fingerprint>';
617
- return localUserAuthManager.revokeSession(token)
618
- ? 'Auth session revoked.'
619
- : 'No auth session found.';
620
- }
621
- if (sub === 'revoke-sessions') {
622
- const username = rest[0];
623
- if (!username) return 'Usage: goodvibes auth revoke-sessions <username>';
624
- const count = localUserAuthManager.revokeSessionsForUser(username);
625
- return `Auth sessions revoked for ${username}: ${count}`;
626
- }
627
- if (sub === 'clear-bootstrap') {
628
- return localUserAuthManager.clearBootstrapCredentialFile()
629
- ? 'Bootstrap credential file removed.'
630
- : 'Bootstrap credential file was already absent.';
631
- }
632
- if (sub !== 'status' && sub !== 'list' && sub !== 'users' && sub !== 'sessions') {
633
- return 'Usage: goodvibes auth [status|users|sessions|add-user|delete-user|rotate-password|revoke-session|revoke-sessions|clear-bootstrap]';
634
- }
635
- const snapshot = localUserAuthManager.inspect();
636
- const paths = readAuthPaths(runtime);
637
- const value = {
638
- ...paths,
639
- users: snapshot.users.map((user) => ({ username: user.username, roles: user.roles })),
640
- sessions: snapshot.sessions.length,
641
- permissionMode: runtime.configManager.get('permissions.mode'),
642
- };
643
- if (sub === 'users') {
644
- return formatJsonOrText(runtime.cli)(value.users, [
645
- `GoodVibes auth users (${value.users.length})`,
646
- ...value.users.map((user) => ` ${user.username} (${user.roles.join(', ') || 'no roles'})`),
647
- ].join('\n'));
648
- }
649
- if (sub === 'sessions') {
650
- return formatJsonOrText(runtime.cli)(snapshot.sessions, [
651
- `GoodVibes auth sessions (${snapshot.sessions.length})`,
652
- ...snapshot.sessions.map((session) => ` ${session.username} expires=${new Date(session.expiresAt).toISOString()} fingerprint=${session.tokenFingerprint}`),
653
- ].join('\n'));
654
- }
529
+ const [sub = 'status'] = runtime.cli.commandArgs;
530
+ const blocked = new Set([
531
+ 'add-user',
532
+ 'add',
533
+ 'delete-user',
534
+ 'remove-user',
535
+ 'rotate-password',
536
+ 'passwd',
537
+ 'revoke-session',
538
+ 'revoke-sessions',
539
+ 'clear-bootstrap',
540
+ ]);
541
+ if (blocked.has(sub)) {
542
+ return [
543
+ 'Unsupported: runtime auth user/session administration is external to GoodVibes Agent.',
544
+ 'GoodVibes Agent connects to an already-running runtime and does not create, delete, rotate, revoke, or clear runtime users, sessions, or bootstrap credentials.',
545
+ 'Use the runtime-owning GoodVibes TUI or host tooling for runtime auth administration.',
546
+ ].join('\n');
547
+ }
548
+ if (sub !== 'status' && sub !== 'review' && sub !== 'list' && sub !== 'users' && sub !== 'sessions') {
549
+ return 'Usage: goodvibes-agent auth [status|review|users|sessions]';
550
+ }
551
+ const paths = readAuthPaths(runtime);
552
+ const value = {
553
+ authOwner: 'external-runtime',
554
+ operatorTokenPresent: paths.operatorTokenPresent,
555
+ operatorTokenPath: paths.operatorTokenPath,
556
+ compatibilityUserStorePresent: paths.userStorePresent,
557
+ compatibilityUserStorePath: paths.userStorePath,
558
+ compatibilityBootstrapCredentialPresent: paths.bootstrapCredentialPresent,
559
+ compatibilityBootstrapCredentialPath: paths.bootstrapCredentialPath,
560
+ permissionMode: runtime.configManager.get('permissions.mode'),
561
+ };
562
+ if (sub === 'users' || sub === 'sessions') {
655
563
  return formatJsonOrText(runtime.cli)(value, [
656
- 'GoodVibes auth',
657
- ` permission mode: ${String(value.permissionMode)}`,
658
- ` users: ${value.users.length}`,
659
- ...value.users.map((user) => ` ${user.username} (${user.roles.join(', ') || 'no roles'})`),
660
- ` sessions: ${value.sessions}`,
661
- ` user store: ${paths.userStorePresent ? 'present' : 'missing'} (${paths.userStorePath})`,
662
- ` bootstrap credential: ${paths.bootstrapCredentialPresent ? 'present' : 'missing'} (${paths.bootstrapCredentialPath})`,
663
- ` operator tokens: ${paths.operatorTokenPresent ? 'present' : 'missing'} (${paths.operatorTokenPath})`,
564
+ `GoodVibes Agent auth ${sub}`,
565
+ ' owner: external GoodVibes runtime',
566
+ ` operator token: ${paths.operatorTokenPresent ? 'present' : 'missing'}`,
567
+ ` operator token path: ${paths.operatorTokenPath}`,
568
+ ` ${sub}: managed by the runtime-owning TUI or host tooling`,
569
+ ' Agent does not enumerate or mutate runtime users/sessions from the local CLI.',
664
570
  ].join('\n'));
571
+ }
572
+ return formatJsonOrText(runtime.cli)(value, [
573
+ 'GoodVibes Agent auth',
574
+ ' owner: external GoodVibes runtime',
575
+ ` permission mode: ${String(value.permissionMode)}`,
576
+ ` operator token: ${paths.operatorTokenPresent ? 'present' : 'missing'} (${paths.operatorTokenPath})`,
577
+ ` compatibility user store: ${paths.userStorePresent ? 'present' : 'missing'} (${paths.userStorePath})`,
578
+ ` compatibility bootstrap credential: ${paths.bootstrapCredentialPresent ? 'present' : 'missing'} (${paths.bootstrapCredentialPath})`,
579
+ ' runtime user/session administration: external',
580
+ ' next: goodvibes-agent providers',
581
+ ' next: goodvibes-agent subscription providers',
582
+ ].join('\n'));
665
583
  }
666
584
 
667
585
 
package/src/cli/status.ts CHANGED
@@ -50,7 +50,7 @@ export interface CliStatusSnapshot {
50
50
  readonly permissionLabel: string;
51
51
  readonly secretPolicy: unknown;
52
52
  readonly secretPolicyLabel: string;
53
- readonly localUsers: CliAuthStatus | null;
53
+ readonly runtimeAuthSignal: CliAuthStatus | null;
54
54
  };
55
55
  readonly runtimeConnection: {
56
56
  readonly enabled: unknown;
@@ -182,13 +182,13 @@ export function buildCliDoctorFindings(options: CliStatusOptions): readonly CliD
182
182
 
183
183
  if (networkFacingSurfaces.length > 0 && options.auth?.userStorePresent !== true) {
184
184
  findings.push({
185
- id: 'network-endpoint-without-local-users',
185
+ id: 'network-endpoint-without-runtime-auth-signal',
186
186
  area: 'auth',
187
187
  severity: 'risk',
188
- summary: 'Network-facing runtime endpoints are enabled before local users are configured.',
189
- cause: `${networkFacingSurfaces.map(([name]) => name).join(', ')} are LAN/custom-bound, but no local auth user store was found.`,
190
- impact: 'Remote access paths may be unusable or unsafe until local admin auth is configured.',
191
- action: 'Create/verify a local admin user before exposing GoodVibes on the network.',
188
+ summary: 'Network-facing runtime endpoints are enabled without a visible runtime auth signal.',
189
+ cause: `${networkFacingSurfaces.map(([name]) => name).join(', ')} are LAN/custom-bound, but Agent cannot see runtime auth state from its local compatibility files.`,
190
+ impact: 'Remote access paths may be unusable or unsafe unless the external runtime owner configured auth.',
191
+ action: 'Review runtime auth from the owning GoodVibes TUI or host tooling; Agent will not create local runtime users.',
192
192
  });
193
193
  }
194
194
 
@@ -200,7 +200,7 @@ export function buildCliDoctorFindings(options: CliStatusOptions): readonly CliD
200
200
  summary: 'A bootstrap credential is still present while network-facing surfaces are enabled.',
201
201
  cause: `${networkFacingSurfaces.map(([name]) => name).join(', ')} are LAN/custom-bound and auth-bootstrap.txt exists.`,
202
202
  impact: 'Bootstrap credentials should be treated as temporary setup material, not long-lived network access credentials.',
203
- action: 'Replace bootstrap auth with a named admin user and retire the bootstrap credential.',
203
+ action: 'Use the runtime-owning GoodVibes TUI or host tooling to replace bootstrap auth and retire the bootstrap credential.',
204
204
  });
205
205
  }
206
206
 
@@ -264,7 +264,7 @@ export function buildCliStatusSnapshot(options: CliStatusOptions): CliStatusSnap
264
264
  permissionLabel: permissionModeLabel(config.get('permissions.mode')),
265
265
  secretPolicy: config.get('storage.secretPolicy'),
266
266
  secretPolicyLabel: secretPolicyLabel(config.get('storage.secretPolicy')),
267
- localUsers: options.auth ?? null,
267
+ runtimeAuthSignal: options.auth ?? null,
268
268
  },
269
269
  runtimeConnection: {
270
270
  enabled: config.get('service.enabled'),
@@ -317,8 +317,8 @@ export function renderCliStatus(options: CliStatusOptions): string {
317
317
  ` permissions: ${permissionModeLabel(config.get('permissions.mode'))} (${String(config.get('permissions.mode'))})`,
318
318
  ` secretPolicy: ${secretPolicyLabel(config.get('storage.secretPolicy'))} (${String(config.get('storage.secretPolicy'))})`,
319
319
  options.auth
320
- ? ` localUsers: ${options.auth.userStorePresent ? 'present' : 'missing'} (${options.auth.userStorePath})`
321
- : ' localUsers: unknown',
320
+ ? ` runtimeAuthSignal: ${options.auth.userStorePresent ? 'present' : 'missing'} (${options.auth.userStorePath})`
321
+ : ' runtimeAuthSignal: unknown',
322
322
  options.auth
323
323
  ? ` bootstrapCredential: ${options.auth.bootstrapCredentialPresent ? 'present' : 'missing'} (${options.auth.bootstrapCredentialPath})`
324
324
  : ' bootstrapCredential: unknown',
@@ -8,7 +8,6 @@ import { getSettingsControlPlaneSnapshot } from '@/runtime/index.ts';
8
8
  import { checkRecoveryFile, readLastSessionPointer } from '@/runtime/index.ts';
9
9
  import {
10
10
  openCommandPanel,
11
- requireLocalUserAuthManager,
12
11
  requireOperatorClient,
13
12
  requireProviderApi,
14
13
  requireReadModels,
@@ -78,12 +77,13 @@ export function registerHealthRuntimeCommands(registry: CommandRegistry): void {
78
77
  if (sub === 'auth') {
79
78
  const auth = readModels.localAuth.getSnapshot();
80
79
  ctx.print([
81
- 'Health Review: Local Auth',
82
- ` users: ${auth.userCount}`,
83
- ` sessions: ${auth.sessionCount}`,
84
- ` bootstrap file: ${auth.bootstrapCredentialPresent ? 'present' : 'cleared'}`,
85
- ...(auth.userCount <= 1 ? [' issue: only one local auth user configured'] : []),
86
- ...(auth.bootstrapCredentialPresent ? [' issue: bootstrap credential file still present; rotate or clear it when no longer needed'] : []),
80
+ 'Health Review: Runtime Auth',
81
+ ' owner: external GoodVibes runtime host',
82
+ ` compatibility users visible: ${auth.userCount}`,
83
+ ` compatibility sessions visible: ${auth.sessionCount}`,
84
+ ` bootstrap file signal: ${auth.bootstrapCredentialPresent ? 'present' : 'cleared'}`,
85
+ ' Agent action: review provider/subscription auth only; do not mutate runtime auth users or bootstrap credentials.',
86
+ ...(auth.bootstrapCredentialPresent ? [' issue: bootstrap cleanup belongs to the runtime-owning TUI or host tooling'] : []),
87
87
  ].join('\n'));
88
88
  return;
89
89
  }
@@ -222,13 +222,11 @@ export function registerHealthRuntimeCommands(registry: CommandRegistry): void {
222
222
  ));
223
223
  lines.push(' verify: /health settings');
224
224
  } else if (domain === 'auth') {
225
- const auth = requireLocalUserAuthManager(ctx).inspect();
226
225
  lines.push(' domain: auth');
227
- lines.push(...(
228
- auth.bootstrapCredentialPresent
229
- ? [' /auth local review', ' /auth local rotate-password admin <password> --yes', ' /auth local clear-bootstrap-file --yes']
230
- : [' /auth local review']
231
- ));
226
+ lines.push(' /auth review');
227
+ lines.push(' /providers');
228
+ lines.push(' /subscription providers');
229
+ lines.push(' runtime auth users/bootstrap cleanup: use the runtime-owning GoodVibes TUI or host tooling');
232
230
  lines.push(' verify: /health auth');
233
231
  } else if (domain === 'accounts') {
234
232
  lines.push(' domain: accounts');
@@ -316,7 +314,7 @@ export function registerHealthRuntimeCommands(registry: CommandRegistry): void {
316
314
  ` account issues: ${accountSnapshot.issueCount}`,
317
315
  ` settings conflicts: ${settingsSnapshot.conflicts.length}`,
318
316
  ` managed locks: ${settingsSnapshot.managedLockCount}`,
319
- ` local auth users: ${readModels.localAuth.getSnapshot().userCount}`,
317
+ ` runtime auth owner: external`,
320
318
  ` remote workers: ${snapshot.remoteRunnerCount}`,
321
319
  ...formatSessionMaintenanceLines(maintenance, 'guided').map((line) => ` ${line}`),
322
320
  ...(snapshot.issues.length > 0 ? ['', ...snapshot.issues.map((issue) => ` [${issue.severity.toUpperCase()}] ${issue.area}: ${issue.message}`)] : []),
@@ -2,7 +2,6 @@ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
2
  import { dirname } from 'node:path';
3
3
  import type { CommandRegistry } from '../command-registry.ts';
4
4
  import { listBuiltinSubscriptionProviders } from '@pellux/goodvibes-sdk/platform/config';
5
- import { handleLocalAuthCommand } from './local-auth-runtime.ts';
6
5
  import { buildAuthInspectionSnapshot, inspectProviderAuth } from '@/runtime/index.ts';
7
6
  import { requireSecretsManager, requireServiceRegistry, requireShellPaths, requireSubscriptionManager } from './runtime-services.ts';
8
7
  import { requireYesFlag, stripYesFlag } from './confirmation.ts';
@@ -113,7 +112,7 @@ export function registerPlatformAccessRuntimeCommands(registry: CommandRegistry)
113
112
  registry.register({
114
113
  name: 'auth',
115
114
  description: 'Review auth posture and exchange session login tokens with local services',
116
- usage: '[review|show <provider>|repair <provider>|bundle export <path> --yes|bundle inspect <path>|login <runtime|listener> <baseUrl> <username> <password> [secretKey] --yes|local <review|panel|add-user --yes|delete-user --yes|rotate-password --yes|revoke-session --yes|clear-bootstrap-file --yes>]',
115
+ usage: '[review|show <provider>|repair <provider>|bundle export <path> --yes|bundle inspect <path>|login <runtime|listener> <baseUrl> <username> <password> [secretKey] --yes]',
117
116
  async handler(args, ctx) {
118
117
  const parsed = stripYesFlag(args);
119
118
  const commandArgs = [...parsed.rest];
@@ -123,7 +122,12 @@ export function registerPlatformAccessRuntimeCommands(registry: CommandRegistry)
123
122
  const serviceRegistry = requireServiceRegistry(ctx);
124
123
  const secretsManager = requireSecretsManager(ctx);
125
124
  if (sub === 'local') {
126
- handleLocalAuthCommand(args.slice(1), ctx);
125
+ ctx.print([
126
+ 'Local runtime auth management is external to GoodVibes Agent.',
127
+ 'Agent connects to an already-running GoodVibes runtime and does not create, delete, rotate, revoke, or clear runtime auth users, sessions, or bootstrap credentials.',
128
+ 'Use the runtime-owning GoodVibes TUI or host tooling for runtime auth administration.',
129
+ 'Agent auth commands available here: /auth review, /auth show <provider>, /auth repair <provider>, /auth login <runtime|listener> ... --yes.',
130
+ ].join('\n'));
127
131
  return;
128
132
  }
129
133
  if (sub === 'review') {
@@ -270,7 +274,7 @@ export function registerPlatformAccessRuntimeCommands(registry: CommandRegistry)
270
274
  return;
271
275
  }
272
276
 
273
- ctx.print('Usage: /auth [review|show <provider>|bundle export <path> --yes|bundle inspect <path>|login <runtime|listener> <baseUrl> <username> <password> [secretKey] --yes|local <review|panel|add-user --yes|delete-user --yes|rotate-password --yes|revoke-session --yes|clear-bootstrap-file --yes>]');
277
+ ctx.print('Usage: /auth [review|show <provider>|bundle export <path> --yes|bundle inspect <path>|login <runtime|listener> <baseUrl> <username> <password> [secretKey] --yes]');
274
278
  },
275
279
  });
276
280
  }
@@ -23,7 +23,6 @@ import { registerTasksRuntimeCommands } from './commands/tasks-runtime.ts';
23
23
  import { registerLocalProviderRuntimeCommands } from './commands/local-provider-runtime.ts';
24
24
  import { registerHealthRuntimeCommands } from './commands/health-runtime.ts';
25
25
  import { registerProviderAccountsRuntimeCommands } from './commands/provider-accounts-runtime.ts';
26
- import { registerLocalAuthRuntimeCommands } from './commands/local-auth-runtime.ts';
27
26
  import { registerConversationRuntimeCommands } from './commands/conversation-runtime.ts';
28
27
  import { registerQrcodeRuntimeCommands } from './commands/qrcode-runtime.ts';
29
28
  import { registerOnboardingRuntimeCommands } from './commands/onboarding-runtime.ts';
@@ -62,7 +61,6 @@ export function registerBuiltinCommands(registry: CommandRegistry): void {
62
61
  registerLocalProviderRuntimeCommands(registry);
63
62
  registerHealthRuntimeCommands(registry);
64
63
  registerProviderAccountsRuntimeCommands(registry);
65
- registerLocalAuthRuntimeCommands(registry);
66
64
  registerConversationRuntimeCommands(registry);
67
65
  registerQrcodeRuntimeCommands(registry);
68
66
  registerOnboardingRuntimeCommands(registry);
@@ -49,7 +49,7 @@ export function buildDefaultDerivedState(): OnboardingStepDerivationState {
49
49
  required: false,
50
50
  accepted: false,
51
51
  reason: 'not-needed',
52
- detail: 'No local auth state needs confirmation.',
52
+ detail: 'No external runtime auth signal needs confirmation.',
53
53
  },
54
54
  },
55
55
  };
@@ -2,7 +2,6 @@ import type { PanelManager } from '../panel-manager.ts';
2
2
  import { ApprovalPanel } from '../approval-panel.ts';
3
3
  import { AutomationControlPanel } from '../automation-control-panel.ts';
4
4
  import { SubscriptionPanel } from '../subscription-panel.ts';
5
- import { LocalAuthPanel } from '../local-auth-panel.ts';
6
5
  import { ProviderAccountsPanel } from '../provider-accounts-panel.ts';
7
6
  import { SecurityPanel } from '../security-panel.ts';
8
7
  import { TasksPanel } from '../tasks-panel.ts';
@@ -59,15 +58,6 @@ export function registerOperationsPanels(manager: PanelManager, deps: ResolvedBu
59
58
  factory: () => new SubscriptionPanel(deps.serviceRegistry, deps.subscriptionManager),
60
59
  });
61
60
 
62
- manager.registerType({
63
- id: 'local-auth',
64
- name: 'Local Auth',
65
- icon: 'U',
66
- category: 'monitoring',
67
- description: 'Local runtime auth users, bootstrap posture, and active sessions',
68
- factory: () => new LocalAuthPanel(deps.localUserAuthManager),
69
- });
70
-
71
61
  manager.registerType({
72
62
  id: 'accounts',
73
63
  name: 'Accounts',
@@ -44,18 +44,19 @@ export function buildProviderHealthDomainSummaries(
44
44
 
45
45
  summaries.push({
46
46
  name: 'auth',
47
- level: auth.bootstrapCredentialPresent || auth.userCount <= 1 ? 'warn' : 'good',
47
+ level: auth.bootstrapCredentialPresent ? 'warn' : 'info',
48
48
  summary: auth.bootstrapCredentialPresent
49
- ? 'bootstrap credential file still present'
50
- : `${auth.userCount} users / ${auth.sessionCount} sessions`,
51
- next: auth.bootstrapCredentialPresent ? '/auth local clear-bootstrap-file --yes' : '/auth local review',
49
+ ? 'external runtime bootstrap credential visible in local compatibility state'
50
+ : 'runtime auth administration belongs to the external runtime owner',
51
+ next: '/auth review',
52
52
  details: [
53
- auth.bootstrapCredentialPresent ? 'bootstrap credential file should be cleared after rotation' : `${auth.userCount} local auth users configured`,
54
- auth.userCount <= 1 ? 'only one local auth user configured' : `${auth.sessionCount} active local auth sessions`,
53
+ 'GoodVibes Agent does not create, delete, rotate, revoke, or clear runtime auth users or sessions.',
54
+ `${auth.userCount} compatibility user record(s) and ${auth.sessionCount} session record(s) are visible for diagnostics only.`,
55
+ auth.bootstrapCredentialPresent ? 'Runtime bootstrap cleanup must be done from the runtime-owning TUI or host tooling.' : '',
55
56
  ].filter(Boolean),
56
57
  nextSteps: auth.bootstrapCredentialPresent
57
- ? ['/auth local review', '/auth local rotate-password <user> <password> --yes', '/auth local clear-bootstrap-file --yes']
58
- : ['/auth local review'],
58
+ ? ['/auth review', '/providers', '/subscription providers']
59
+ : ['/auth review', '/providers'],
59
60
  });
60
61
 
61
62
  const settingIssueCount = settings.conflictCount + settings.recentFailureCount + (settings.hasStagedManagedBundle ? 1 : 0);
@@ -62,22 +62,6 @@ function setNestedValue(root: Record<string, unknown>, key: string, value: unkno
62
62
 
63
63
  type RollbackAction = () => Promise<void> | void;
64
64
 
65
- interface BootstrapCredential {
66
- readonly username: string;
67
- readonly password: string;
68
- }
69
-
70
- interface PersistedAuthUser {
71
- readonly username: string;
72
- readonly passwordHash: string;
73
- readonly roles?: readonly string[];
74
- }
75
-
76
- interface MutableAuthManager {
77
- readonly users?: Map<string, PersistedAuthUser>;
78
- readonly sessions?: Map<string, { readonly token: string; readonly username: string; readonly expiresAt: number }>;
79
- }
80
-
81
65
  function restoreFile(path: string, previous: string | null, reload?: () => void): void {
82
66
  if (previous === null) {
83
67
  if (existsSync(path)) unlinkSync(path);
@@ -88,31 +72,6 @@ function restoreFile(path: string, previous: string | null, reload?: () => void)
88
72
  reload?.();
89
73
  }
90
74
 
91
- function parseBootstrapCredential(content: string | null): BootstrapCredential | null {
92
- if (content === null) return null;
93
- let username = '';
94
- let password = '';
95
- for (const rawLine of content.split('\n')) {
96
- const line = rawLine.trim();
97
- if (line.startsWith('username=')) username = line.slice('username='.length);
98
- if (line.startsWith('password=')) password = line.slice('password='.length);
99
- }
100
- return username.length > 0 && password.length > 0 ? { username, password } : null;
101
- }
102
-
103
- function parsePersistedAuthUsers(content: string): readonly PersistedAuthUser[] {
104
- const parsed = JSON.parse(content) as unknown;
105
- if (!isPlainObject(parsed) || parsed.version !== 1 || !Array.isArray(parsed.users)) {
106
- throw new Error('Expected a version 1 local auth user store.');
107
- }
108
- return parsed.users.filter((user): user is PersistedAuthUser => (
109
- isPlainObject(user)
110
- && typeof user.username === 'string'
111
- && typeof user.passwordHash === 'string'
112
- && (user.roles === undefined || (Array.isArray(user.roles) && user.roles.every((role) => typeof role === 'string')))
113
- ));
114
- }
115
-
116
75
  function snapshotFileRollback(path: string, reload?: () => void): RollbackAction {
117
76
  const previous = existsSync(path) ? readFileSync(path, 'utf-8') : null;
118
77
  return () => restoreFile(path, previous, reload);
@@ -227,18 +186,10 @@ function validateSecretOperation(
227
186
  }
228
187
 
229
188
  function validateAuthOperation(
230
- deps: OnboardingApplyDependencies,
231
- operation: Extract<OnboardingApplyOperation, { kind: 'ensure-auth-user' }>,
189
+ _deps: OnboardingApplyDependencies,
190
+ _operation: Extract<OnboardingApplyOperation, { kind: 'ensure-auth-user' }>,
232
191
  ): void {
233
- if (!deps.auth) throw new Error('Local auth management is unavailable.');
234
- if (operation.username.trim().length === 0) throw new Error('Local auth username is required.');
235
- if (operation.password.length === 0) throw new Error(`Local auth password for ${operation.username} is required.`);
236
- const username = operation.username.trim();
237
- const existing = deps.auth.inspect().users.find((user) => user.username === username);
238
- const requiredRoles = operation.roles ?? ['admin'];
239
- if (existing && !requiredRoles.every((role) => existing.roles.includes(role))) {
240
- throw new Error(`Existing local auth user ${username} is missing required role(s): ${requiredRoles.join(', ')}.`);
241
- }
192
+ throw new Error('Runtime auth user/session administration is external to GoodVibes Agent onboarding.');
242
193
  }
243
194
 
244
195
  function validateAcknowledgementOperation(
@@ -313,44 +264,6 @@ async function applySecretOperation(
313
264
  };
314
265
  }
315
266
 
316
- function applyAuthOperation(
317
- deps: OnboardingApplyDependencies,
318
- operation: Extract<OnboardingApplyOperation, { kind: 'ensure-auth-user' }>,
319
- ): OnboardingAppliedOperation {
320
- validateAuthOperation(deps, operation);
321
- const auth = deps.auth!;
322
- const username = operation.username.trim();
323
- const before = auth.inspect();
324
- const existing = before.users.find((user) => user.username === username);
325
- const bootstrapCredential = before.bootstrapCredentialPresent
326
- ? parseBootstrapCredential(readFileSync(before.bootstrapCredentialPath, 'utf-8'))
327
- : null;
328
-
329
- if (existing) {
330
- auth.rotatePassword(username, operation.password);
331
- } else {
332
- auth.addUser(username, operation.password, operation.roles ?? ['admin']);
333
- }
334
-
335
- if (operation.retireBootstrapCredential) {
336
- if (bootstrapCredential && bootstrapCredential.username !== username && auth.getUser(bootstrapCredential.username)) {
337
- auth.deleteUser(bootstrapCredential.username);
338
- }
339
- auth.clearBootstrapCredentialFile();
340
- }
341
-
342
- if (operation.createSession ?? true) {
343
- auth.createSession(username);
344
- }
345
-
346
- return {
347
- kind: operation.kind,
348
- summary: existing
349
- ? `Updated local auth user ${username}.`
350
- : `Created local auth user ${username}.`,
351
- };
352
- }
353
-
354
267
  async function buildSecretRollbackAction(
355
268
  deps: OnboardingApplyDependencies,
356
269
  operation: Extract<OnboardingApplyOperation, { kind: 'set-secret' }>,
@@ -369,67 +282,6 @@ async function buildSecretRollbackAction(
369
282
  };
370
283
  }
371
284
 
372
- function buildAuthRollbackAction(
373
- deps: OnboardingApplyDependencies,
374
- operation: Extract<OnboardingApplyOperation, { kind: 'ensure-auth-user' }>,
375
- ): RollbackAction {
376
- validateAuthOperation(deps, operation);
377
- const auth = deps.auth!;
378
- const mutable = auth as unknown as MutableAuthManager;
379
- const username = operation.username.trim();
380
- const before = auth.inspect();
381
- const existingUser = before.users.find((user) => user.username === username);
382
- const existingSessionFingerprints = new Set(before.sessions
383
- .filter((session) => session.username === username)
384
- .map((session) => session.tokenFingerprint));
385
- const userStoreSnapshot = existsSync(before.userStorePath) ? readFileSync(before.userStorePath, 'utf-8') : null;
386
- const bootstrapCredentialSnapshot = existsSync(before.bootstrapCredentialPath)
387
- ? readFileSync(before.bootstrapCredentialPath, 'utf-8')
388
- : null;
389
- const bootstrapCredential = parseBootstrapCredential(bootstrapCredentialSnapshot);
390
- const beforeSessions = mutable.sessions instanceof Map
391
- ? [...mutable.sessions.entries()].map(([token, session]) => [token, { ...session }] as const)
392
- : [];
393
-
394
- return () => {
395
- for (const session of auth.inspect().sessions) {
396
- if (session.username === username && !existingSessionFingerprints.has(session.tokenFingerprint)) {
397
- auth.revokeSession(session.tokenFingerprint);
398
- }
399
- }
400
-
401
- if (bootstrapCredential && !auth.getUser(bootstrapCredential.username)) {
402
- auth.addUser(bootstrapCredential.username, bootstrapCredential.password, ['admin']);
403
- }
404
-
405
- if (!existingUser && auth.getUser(username)) {
406
- try {
407
- auth.deleteUser(username);
408
- } catch (error) {
409
- if (mutable.users instanceof Map) mutable.users.delete(username);
410
- else throw error;
411
- }
412
- }
413
-
414
- restoreFile(before.bootstrapCredentialPath, bootstrapCredentialSnapshot);
415
- restoreFile(before.userStorePath, userStoreSnapshot);
416
-
417
- if (mutable.users instanceof Map) {
418
- if (userStoreSnapshot === null) {
419
- if (before.users.length === 0) mutable.users.clear();
420
- } else {
421
- mutable.users.clear();
422
- for (const user of parsePersistedAuthUsers(userStoreSnapshot)) mutable.users.set(user.username, user);
423
- }
424
- }
425
-
426
- if (mutable.sessions instanceof Map) {
427
- mutable.sessions.clear();
428
- for (const [token, session] of beforeSessions) mutable.sessions.set(token, session);
429
- }
430
- };
431
- }
432
-
433
285
  async function buildRollbackAction(
434
286
  deps: OnboardingApplyDependencies,
435
287
  operation: OnboardingApplyOperation,
@@ -453,7 +305,8 @@ async function buildRollbackAction(
453
305
  }
454
306
 
455
307
  if (operation.kind === 'ensure-auth-user') {
456
- return buildAuthRollbackAction(deps, operation);
308
+ validateAuthOperation(deps, operation);
309
+ return () => {};
457
310
  }
458
311
 
459
312
  if (operation.kind === 'acknowledge') {
@@ -633,8 +486,7 @@ export async function applyOnboardingRequest(
633
486
  }
634
487
 
635
488
  if (operation.kind === 'ensure-auth-user') {
636
- applied.push(applyAuthOperation(deps, operation));
637
- rollbacks.push(rollback);
489
+ validateAuthOperation(deps, operation);
638
490
  continue;
639
491
  }
640
492
 
@@ -500,23 +500,23 @@ export function deriveReopenEditAcknowledgementState(
500
500
  snapshot,
501
501
  'auth',
502
502
  'bootstrap-credential',
503
- 'The local auth bootstrap credential file is still present.',
503
+ 'An external runtime bootstrap credential signal is still visible to Agent.',
504
504
  )
505
505
  : authSessionCount > 0
506
506
  ? buildRequiredAcknowledgement(
507
507
  snapshot,
508
508
  'auth',
509
509
  'active-sessions',
510
- `${authSessionCount} local auth session(s) are currently active.`,
510
+ `${authSessionCount} external runtime auth session signal(s) are currently visible.`,
511
511
  )
512
512
  : authUserCount > 0
513
513
  ? buildRequiredAcknowledgement(
514
514
  snapshot,
515
515
  'auth',
516
516
  'auth-state',
517
- `${authUserCount} local auth user(s) are already configured.`,
517
+ `${authUserCount} external runtime auth user signal(s) are already visible.`,
518
518
  )
519
- : buildNotNeededAcknowledgement(snapshot, 'auth', 'No local auth state needs confirmation.');
519
+ : buildNotNeededAcknowledgement(snapshot, 'auth', 'No external runtime auth signal needs confirmation.');
520
520
 
521
521
  return {
522
522
  providers,
@@ -383,17 +383,7 @@ export interface OnboardingApplyDependencies {
383
383
  readonly clock?: () => number;
384
384
  readonly config: Pick<ConfigManager, 'get' | 'getRaw' | 'load' | 'setDynamic'>;
385
385
  readonly secrets?: Pick<SecretsManager, 'delete' | 'get' | 'inspect' | 'set'>;
386
- readonly auth?: Pick<
387
- UserAuthManager,
388
- 'addUser'
389
- | 'clearBootstrapCredentialFile'
390
- | 'createSession'
391
- | 'deleteUser'
392
- | 'getUser'
393
- | 'inspect'
394
- | 'revokeSession'
395
- | 'rotatePassword'
396
- >;
386
+ readonly auth?: Pick<UserAuthManager, 'inspect'>;
397
387
  readonly shellPaths: OnboardingShellPaths;
398
388
  readonly acknowledgementScope?: OnboardingStateScope;
399
389
  }
@@ -116,36 +116,14 @@ async function verifySecretOperation(
116
116
  }
117
117
 
118
118
  function verifyAuthOperation(
119
- deps: OnboardingVerificationDependencies,
119
+ _deps: OnboardingVerificationDependencies,
120
120
  operation: Extract<OnboardingApplyOperation, { kind: 'ensure-auth-user' }>,
121
121
  ): OnboardingVerificationItem {
122
- if (!deps.auth) {
123
- return {
124
- id: `auth:${operation.username}`,
125
- status: 'fail',
126
- message: 'Local auth manager is unavailable.',
127
- target: operation.username,
128
- };
129
- }
130
-
131
- const snapshot = deps.auth.inspect();
132
122
  const username = operation.username.trim();
133
- const user = snapshot.users.find((entry) => entry.username === username);
134
- const requiredRoles = operation.roles ?? ['admin'];
135
- const userExists = Boolean(user) && requiredRoles.every((role) => user!.roles.includes(role));
136
- const sessionExists = operation.createSession === false
137
- ? true
138
- : snapshot.sessions.some((session) => session.username === username);
139
- const bootstrapRetired = operation.retireBootstrapCredential
140
- ? snapshot.bootstrapCredentialPresent === false
141
- : true;
142
- const ok = userExists && sessionExists && bootstrapRetired;
143
123
  return {
144
124
  id: `auth:${username}`,
145
- status: ok ? 'pass' : 'fail',
146
- message: ok
147
- ? `${username} local auth user has required role(s) and session state.`
148
- : `${username} local auth user/session/role/bootstrap state was not created.`,
125
+ status: 'fail',
126
+ message: 'Runtime auth user/session administration is external to GoodVibes Agent onboarding.',
149
127
  target: username,
150
128
  };
151
129
  }
package/src/version.ts CHANGED
@@ -6,7 +6,7 @@ import { join } from 'node:path';
6
6
  // The prebuild script updates the fallback value before compilation.
7
7
  // Uses import.meta.dir (Bun) to locate package.json relative to this file,
8
8
  // which is correct regardless of the process working directory.
9
- let _version = '0.1.81';
9
+ let _version = '0.1.82';
10
10
  let _sdkVersion = '0.33.35';
11
11
  try {
12
12
  const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8')) as {
@@ -1,128 +0,0 @@
1
- import type { CommandContext, CommandRegistry } from '../command-registry.ts';
2
- import { openCommandPanel, requireLocalUserAuthManager } from './runtime-services.ts';
3
- import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
4
- import { requireYesFlag, stripYesFlag } from './confirmation.ts';
5
-
6
- function formatRoles(roles: readonly string[]): string {
7
- return roles.length > 0 ? roles.join(', ') : '(none)';
8
- }
9
-
10
- export function handleLocalAuthCommand(args: string[], ctx: CommandContext): void {
11
- const parsed = stripYesFlag(args);
12
- const commandArgs = [...parsed.rest];
13
- const sub = (commandArgs[0] ?? 'review').toLowerCase();
14
- const auth = requireLocalUserAuthManager(ctx);
15
- if (sub === 'panel' || sub === 'open') {
16
- openCommandPanel(ctx, 'local-auth');
17
- return;
18
- }
19
-
20
- if (sub === 'add-user') {
21
- const username = commandArgs[1];
22
- const password = commandArgs[2];
23
- const roles = commandArgs[3]?.split(',').map((value) => value.trim()).filter(Boolean) ?? ['admin'];
24
- if (!username || !password) {
25
- ctx.print('Usage: /auth local add-user <username> <password> [roles] --yes');
26
- return;
27
- }
28
- if (!parsed.yes) {
29
- requireYesFlag(ctx, `add local auth user ${username}`, '/auth local add-user <username> <password> [roles] --yes');
30
- return;
31
- }
32
- try {
33
- const added = auth.addUser(username, password, roles);
34
- ctx.print(`Added local auth user ${added.username} (${formatRoles(added.roles)}).`);
35
- } catch (error) {
36
- ctx.print(summarizeError(error));
37
- }
38
- return;
39
- }
40
-
41
- if (sub === 'delete-user') {
42
- const username = commandArgs[1];
43
- if (!username) {
44
- ctx.print('Usage: /auth local delete-user <username> --yes');
45
- return;
46
- }
47
- if (!parsed.yes) {
48
- requireYesFlag(ctx, `delete local auth user ${username}`, '/auth local delete-user <username> --yes');
49
- return;
50
- }
51
- try {
52
- const deleted = auth.deleteUser(username);
53
- ctx.print(deleted ? `Deleted local auth user ${username}.` : `Unknown local auth user: ${username}`);
54
- } catch (error) {
55
- ctx.print(summarizeError(error));
56
- }
57
- return;
58
- }
59
-
60
- if (sub === 'rotate-password') {
61
- const username = commandArgs[1];
62
- const password = commandArgs[2];
63
- if (!username || !password) {
64
- ctx.print('Usage: /auth local rotate-password <username> <password> --yes');
65
- return;
66
- }
67
- if (!parsed.yes) {
68
- requireYesFlag(ctx, `rotate password for local auth user ${username}`, '/auth local rotate-password <username> <password> --yes');
69
- return;
70
- }
71
- try {
72
- auth.rotatePassword(username, password);
73
- ctx.print(`Rotated password for ${username}. Existing sessions were revoked.`);
74
- } catch (error) {
75
- ctx.print(summarizeError(error));
76
- }
77
- return;
78
- }
79
-
80
- if (sub === 'revoke-session') {
81
- const token = commandArgs[1];
82
- if (!token) {
83
- ctx.print('Usage: /auth local revoke-session <token-or-fingerprint> --yes');
84
- return;
85
- }
86
- if (!parsed.yes) {
87
- requireYesFlag(ctx, 'revoke local auth session', '/auth local revoke-session <token-or-fingerprint> --yes');
88
- return;
89
- }
90
- ctx.print(auth.revokeSession(token) ? `Revoked session ${token.slice(0, 12)}…` : `Unknown session token or fingerprint: ${token}`);
91
- return;
92
- }
93
-
94
- if (sub === 'clear-bootstrap-file') {
95
- if (!parsed.yes) {
96
- requireYesFlag(ctx, 'clear the local auth bootstrap credential file', '/auth local clear-bootstrap-file --yes');
97
- return;
98
- }
99
- ctx.print(auth.clearBootstrapCredentialFile()
100
- ? 'Removed bootstrap credential file.'
101
- : 'No bootstrap credential file was present.');
102
- return;
103
- }
104
-
105
- const snapshot = auth.inspect();
106
- ctx.print([
107
- 'Local Auth Review',
108
- ` user store: ${snapshot.userStorePath}`,
109
- ` bootstrap file: ${snapshot.bootstrapCredentialPath}`,
110
- ` bootstrap credentials: ${snapshot.bootstrapCredentialPresent ? 'present' : 'cleared'}`,
111
- ` users: ${snapshot.userCount}`,
112
- ` sessions: ${snapshot.sessionCount}`,
113
- ...snapshot.users.map((user) => ` user: ${user.username} roles=${formatRoles(user.roles)}`),
114
- ...snapshot.sessions.map((session) => ` session: ${session.username} expires=${new Date(session.expiresAt).toISOString()} fingerprint=${session.tokenFingerprint}`),
115
- ].join('\n'));
116
- }
117
-
118
- export function registerLocalAuthRuntimeCommands(registry: CommandRegistry): void {
119
- registry.register({
120
- name: 'local-auth',
121
- aliases: ['auth-local'],
122
- description: 'Inspect and manage local runtime auth users, sessions, and bootstrap credentials',
123
- usage: '[review|panel|add-user <username> <password> [roles] --yes|delete-user <username> --yes|rotate-password <username> <password> --yes|revoke-session <token-or-fingerprint> --yes|clear-bootstrap-file --yes]',
124
- handler(args, ctx) {
125
- handleLocalAuthCommand(args, ctx);
126
- },
127
- });
128
- }
@@ -1,130 +0,0 @@
1
- import type { Line } from '../types/grid.ts';
2
- import { createEmptyLine } from '../types/grid.ts';
3
- import { ScrollableListPanel } from './scrollable-list-panel.ts';
4
- import {
5
- buildDetailBlock,
6
- buildGuidanceLine,
7
- buildPanelListRow,
8
- buildPanelLine,
9
- buildSummaryBlock,
10
- buildPanelWorkspace,
11
- DEFAULT_PANEL_PALETTE,
12
- type PanelPalette,
13
- } from './polish.ts';
14
- import type { LocalAuthSnapshot } from '@pellux/goodvibes-sdk/platform/security';
15
- import type { LocalAuthInspectionQuery } from '../runtime/ui-service-queries.ts';
16
-
17
- const C = {
18
- ...DEFAULT_PANEL_PALETTE,
19
- info: '#38bdf8',
20
- warn: '#eab308',
21
- error: '#ef4444',
22
- selectBg: '#1e293b',
23
- } as const;
24
-
25
- function formatRoles(roles: readonly string[]): string {
26
- return roles.length > 0 ? roles.join(', ') : '(none)';
27
- }
28
-
29
- type LocalAuthUser = LocalAuthSnapshot['users'][number];
30
-
31
- export class LocalAuthPanel extends ScrollableListPanel<LocalAuthUser> {
32
- private readonly authManager: LocalAuthInspectionQuery;
33
-
34
- public constructor(authManager: LocalAuthInspectionQuery) {
35
- super('local-auth', 'Local Auth', 'U', 'monitoring');
36
- this.showSelectionGutter = true; // I5: non-color selection affordance
37
- this.authManager = authManager;
38
- }
39
-
40
- protected override getPalette(): PanelPalette {
41
- return C;
42
- }
43
-
44
- protected getItems(): readonly LocalAuthUser[] {
45
- return this.authManager.inspect().users;
46
- }
47
-
48
- protected renderItem(user: LocalAuthUser, _index: number, selected: boolean, width: number): Line {
49
- return buildPanelListRow(width, [
50
- { text: user.username.padEnd(20), fg: C.value },
51
- { text: ` roles=${formatRoles(user.roles)}`.slice(0, Math.max(0, width - 24)), fg: C.info },
52
- ], C, { selected });
53
- }
54
-
55
- protected override getEmptyStateMessage(): string {
56
- return ' No local auth users configured.';
57
- }
58
-
59
- public render(width: number, height: number): Line[] {
60
- const intro = 'Review local runtime auth users, bootstrap state, and active sessions.';
61
- const snapshot = this.authManager.inspect();
62
- const users = this.getItems();
63
-
64
- const issueMessages: string[] = [];
65
- if (snapshot.bootstrapCredentialPresent) issueMessages.push('Bootstrap credential file still exists and should be cleared after password rotation.');
66
- if (snapshot.userCount <= 1) issueMessages.push('Only one local auth user is configured.');
67
- if (snapshot.sessionCount === 0) issueMessages.push('No active local auth sessions are currently tracked.');
68
-
69
- const headerLines: Line[] = [
70
- ...buildSummaryBlock(width, 'Local auth posture', [
71
- buildPanelLine(width, [
72
- [' users ', C.label],
73
- [String(snapshot.userCount), C.value],
74
- [' sessions ', C.label],
75
- [String(snapshot.sessionCount), snapshot.sessionCount > 0 ? C.info : C.dim],
76
- [' bootstrap ', C.label],
77
- [snapshot.bootstrapCredentialPresent ? 'present' : 'cleared', snapshot.bootstrapCredentialPresent ? C.warn : C.good],
78
- ]),
79
- buildPanelLine(width, [[' user store ', C.label], [snapshot.userStorePath.slice(0, Math.max(0, width - 13)), C.dim]]),
80
- buildPanelLine(width, [[' bootstrap file ', C.label], [snapshot.bootstrapCredentialPath.slice(0, Math.max(0, width - 18)), C.dim]]),
81
- ...(issueMessages.length > 0
82
- ? issueMessages.map((issue) => buildPanelLine(width, [[` issue: ${issue}`.slice(0, Math.max(0, width)), C.warn]]))
83
- : [buildPanelLine(width, [[' local auth posture looks healthy.', C.good]])]),
84
- buildGuidanceLine(width, '/auth local rotate-password <user> <password> --yes', 'rotate bootstrap/default credentials and revoke older sessions as needed', C),
85
- ], C),
86
- ];
87
-
88
- if (users.length === 0) {
89
- const workspace = buildPanelWorkspace(width, height, {
90
- title: 'Local Auth Control Room',
91
- intro,
92
- sections: [{ lines: headerLines }],
93
- palette: C,
94
- });
95
- while (workspace.length < height) workspace.push(createEmptyLine(width));
96
- return workspace;
97
- }
98
-
99
- this.clampSelection();
100
- const selected = users[this.selectedIndex];
101
-
102
- const footerLines: Line[] = [];
103
- if (selected) {
104
- footerLines.push(
105
- ...buildDetailBlock(width, 'Selected user', [
106
- buildPanelLine(width, [[' username ', C.label], [selected.username, C.value], [' roles ', C.label], [formatRoles(selected.roles).slice(0, Math.max(0, width - 23)), C.info]]),
107
- buildPanelLine(width, [[` next: /auth local rotate-password ${selected.username} <password> --yes`.slice(0, Math.max(0, width)), C.dim]]),
108
- buildPanelLine(width, [[` next: /auth local delete-user ${selected.username} --yes`.slice(0, Math.max(0, width)), C.dim]]),
109
- ], C),
110
- );
111
- }
112
-
113
- if (snapshot.sessions.length > 0) {
114
- footerLines.push(
115
- ...snapshot.sessions.slice(0, 8).map((session) => buildPanelLine(width, [
116
- [' ', C.label],
117
- [session.username.padEnd(18), C.value],
118
- [` expires ${new Date(session.expiresAt).toLocaleString()}`.slice(0, Math.max(0, width - 20)), C.dim],
119
- ])),
120
- );
121
- }
122
- footerLines.push(buildPanelLine(width, [[' /auth local review mutations require --yes: add-user rotate-password revoke-session ', C.dim]]));
123
-
124
- return this.renderList(width, height, {
125
- title: 'Local Auth Control Room',
126
- header: headerLines,
127
- footer: footerLines,
128
- });
129
- }
130
- }