@pellux/goodvibes-tui 0.19.23 → 0.19.25

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 (72) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +5 -5
  3. package/bin/goodvibes +5 -0
  4. package/bin/goodvibes-daemon +5 -0
  5. package/docs/foundation-artifacts/operator-contract.json +1 -1
  6. package/package.json +2 -2
  7. package/src/cli/completion.ts +89 -0
  8. package/src/cli/config-overrides.ts +159 -0
  9. package/src/cli/endpoints.ts +63 -0
  10. package/src/cli/entrypoint.ts +155 -0
  11. package/src/cli/help.ts +122 -0
  12. package/src/cli/index.ts +8 -0
  13. package/src/cli/management-commands.ts +576 -0
  14. package/src/cli/management.ts +693 -0
  15. package/src/cli/parser.ts +367 -0
  16. package/src/cli/status.ts +112 -0
  17. package/src/cli/tui-startup.ts +32 -0
  18. package/src/cli/types.ts +63 -0
  19. package/src/cli-flags.ts +17 -55
  20. package/src/config/index.ts +1 -1
  21. package/src/config/secrets.ts +44 -0
  22. package/src/core/conversation.ts +36 -13
  23. package/src/daemon/cli.ts +62 -11
  24. package/src/input/command-registry.ts +3 -0
  25. package/src/input/commands/guidance-runtime.ts +9 -4
  26. package/src/input/commands/local-runtime.ts +21 -7
  27. package/src/input/commands/local-setup.ts +31 -38
  28. package/src/input/commands/onboarding-runtime.ts +14 -0
  29. package/src/input/commands/runtime-services.ts +9 -0
  30. package/src/input/commands.ts +2 -0
  31. package/src/input/feed-context-factory.ts +8 -1
  32. package/src/input/handler-feed.ts +13 -8
  33. package/src/input/handler-interactions.ts +266 -0
  34. package/src/input/handler-modal-stack.ts +23 -3
  35. package/src/input/handler-modal-token-routes.ts +23 -1
  36. package/src/input/handler-onboarding.ts +696 -0
  37. package/src/input/handler-picker-routes.ts +15 -7
  38. package/src/input/handler-ui-state.ts +58 -0
  39. package/src/input/handler.ts +120 -246
  40. package/src/input/onboarding/handler-onboarding-routes.ts +105 -0
  41. package/src/input/onboarding/onboarding-wizard-apply.ts +211 -0
  42. package/src/input/onboarding/onboarding-wizard-constants.ts +148 -0
  43. package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +712 -0
  44. package/src/input/onboarding/onboarding-wizard-helpers.ts +218 -0
  45. package/src/input/onboarding/onboarding-wizard-rules.ts +224 -0
  46. package/src/input/onboarding/onboarding-wizard-state.ts +354 -0
  47. package/src/input/onboarding/onboarding-wizard-steps.ts +642 -0
  48. package/src/input/onboarding/onboarding-wizard-types.ts +170 -0
  49. package/src/input/onboarding/onboarding-wizard.ts +594 -0
  50. package/src/main.ts +32 -39
  51. package/src/panels/builtin/operations.ts +0 -10
  52. package/src/panels/index.ts +0 -1
  53. package/src/panels/panel-manager.ts +6 -2
  54. package/src/renderer/conversation-overlays.ts +6 -0
  55. package/src/renderer/help-overlay.ts +1 -1
  56. package/src/renderer/onboarding/onboarding-wizard.ts +533 -0
  57. package/src/renderer/panel-composite.ts +42 -5
  58. package/src/renderer/panel-workspace-bar.ts +5 -1
  59. package/src/runtime/bootstrap-core.ts +1 -0
  60. package/src/runtime/bootstrap.ts +123 -0
  61. package/src/runtime/onboarding/apply.ts +685 -0
  62. package/src/runtime/onboarding/derivation.ts +495 -0
  63. package/src/runtime/onboarding/index.ts +7 -0
  64. package/src/runtime/onboarding/markers.ts +161 -0
  65. package/src/runtime/onboarding/snapshot.ts +400 -0
  66. package/src/runtime/onboarding/state.ts +140 -0
  67. package/src/runtime/onboarding/types.ts +402 -0
  68. package/src/runtime/onboarding/verify.ts +233 -0
  69. package/src/runtime/ui-services.ts +16 -0
  70. package/src/shell/ui-openers.ts +12 -2
  71. package/src/version.ts +1 -1
  72. package/src/panels/welcome-panel.ts +0 -64
@@ -10,6 +10,7 @@
10
10
  * - lifecycle.ts: save/shutdown helpers
11
11
  */
12
12
  import { join } from 'node:path';
13
+ import net from 'node:net';
13
14
  import { Orchestrator } from '../core/orchestrator.ts';
14
15
  import { AcpManager } from '@pellux/goodvibes-sdk/platform/acp/manager';
15
16
  import { getTierPromptSupplement, getTierForContextWindow } from '@pellux/goodvibes-sdk/platform/providers/tier-prompts';
@@ -292,6 +293,83 @@ export async function bootstrapRuntime(
292
293
 
293
294
  const deferredStartup = createDeferredStartupCoordinator();
294
295
 
296
+ interface ExternalServiceBindingSnapshot {
297
+ readonly daemon: {
298
+ readonly host: string;
299
+ readonly port: number;
300
+ };
301
+ readonly httpListener: {
302
+ readonly host: string;
303
+ readonly port: number;
304
+ };
305
+ }
306
+
307
+ interface ExternalServicePortState {
308
+ readonly daemonPortInUse: boolean;
309
+ readonly httpListenerPortInUse: boolean;
310
+ }
311
+
312
+ const readExternalServiceBindings = (): ExternalServiceBindingSnapshot => ({
313
+ daemon: {
314
+ host: String(configManager.get('controlPlane.host') ?? '127.0.0.1'),
315
+ port: Number(configManager.get('controlPlane.port') ?? 3421),
316
+ },
317
+ httpListener: {
318
+ host: String(configManager.get('httpListener.host') ?? '127.0.0.1'),
319
+ port: Number(configManager.get('httpListener.port') ?? 3422),
320
+ },
321
+ });
322
+
323
+ const getProbeHosts = (host: string): readonly string[] => {
324
+ const normalized = host.trim().toLowerCase();
325
+ if (normalized === '0.0.0.0') return ['127.0.0.1'];
326
+ if (normalized === '::' || normalized === '[::]') return ['::1'];
327
+ if (normalized.length === 0) return ['127.0.0.1'];
328
+ return [host];
329
+ };
330
+
331
+ const isTcpPortInUse = async (host: string, port: number): Promise<boolean> => new Promise((resolve) => {
332
+ const socket = new net.Socket();
333
+ let settled = false;
334
+ const finish = (result: boolean): void => {
335
+ if (settled) return;
336
+ settled = true;
337
+ socket.destroy();
338
+ resolve(result);
339
+ };
340
+
341
+ socket.setTimeout(250);
342
+ socket.once('connect', () => finish(true));
343
+ socket.once('timeout', () => finish(false));
344
+ socket.once('error', () => finish(false));
345
+ socket.connect(port, host);
346
+ });
347
+
348
+ const inspectExternalPorts = async (
349
+ bindings: readonly ExternalServiceBindingSnapshot[],
350
+ ): Promise<ExternalServicePortState> => {
351
+ const daemonTargets = new Map<string, { readonly host: string; readonly port: number }>();
352
+ const listenerTargets = new Map<string, { readonly host: string; readonly port: number }>();
353
+ for (const binding of bindings) {
354
+ for (const host of getProbeHosts(binding.daemon.host)) {
355
+ daemonTargets.set(`${host}:${binding.daemon.port}`, { host, port: binding.daemon.port });
356
+ }
357
+ for (const host of getProbeHosts(binding.httpListener.host)) {
358
+ listenerTargets.set(`${host}:${binding.httpListener.port}`, { host, port: binding.httpListener.port });
359
+ }
360
+ }
361
+
362
+ const [daemonResults, listenerResults] = await Promise.all([
363
+ Promise.all([...daemonTargets.values()].map((target) => isTcpPortInUse(target.host, target.port))),
364
+ Promise.all([...listenerTargets.values()].map((target) => isTcpPortInUse(target.host, target.port))),
365
+ ]);
366
+
367
+ return {
368
+ daemonPortInUse: daemonResults.some(Boolean),
369
+ httpListenerPortInUse: listenerResults.some(Boolean),
370
+ };
371
+ };
372
+
295
373
  let externalServices: ExternalServicesHandle = {
296
374
  daemonServer: null,
297
375
  httpListener: null,
@@ -299,6 +377,49 @@ export async function bootstrapRuntime(
299
377
  async stop(): Promise<void> {},
300
378
  };
301
379
  let externalServicesPromise: Promise<ExternalServicesHandle> | null = null;
380
+ let externalServiceBindings = readExternalServiceBindings();
381
+ let externalServicePortState: ExternalServicePortState = {
382
+ daemonPortInUse: false,
383
+ httpListenerPortInUse: false,
384
+ };
385
+ const inspectExternalServices = () => ({
386
+ daemonRunning: externalServices.daemonServer !== null,
387
+ daemonPortInUse: externalServicePortState.daemonPortInUse,
388
+ httpListenerRunning: externalServices.httpListener !== null,
389
+ httpListenerPortInUse: externalServicePortState.httpListenerPortInUse,
390
+ });
391
+ const platformExternalServices = uiServices.platform as typeof uiServices.platform & {
392
+ externalServices: NonNullable<typeof uiServices.platform.externalServices>;
393
+ };
394
+ platformExternalServices.externalServices = {
395
+ inspect: inspectExternalServices,
396
+ restart: async () => {
397
+ if (externalServicesPromise) {
398
+ try {
399
+ externalServices = await externalServicesPromise;
400
+ } catch {
401
+ // A failed previous startup should not prevent a restart attempt.
402
+ }
403
+ }
404
+ await externalServices.stop();
405
+ const previousBindings = externalServiceBindings;
406
+ externalServiceBindings = readExternalServiceBindings();
407
+ const daemonHomeDir = join(services.homeDirectory, '.goodvibes', 'daemon');
408
+ const companionTokenRecord = getOrCreateCompanionToken('tui', { daemonHomeDir });
409
+ externalServicesPromise = startExternalServices(
410
+ configManager,
411
+ runtimeBus,
412
+ hookDispatcher,
413
+ { sharedDaemonToken: companionTokenRecord.token },
414
+ services,
415
+ );
416
+ externalServices = await externalServicesPromise;
417
+ controlPlaneRecentEventsRef.value = (limit) => externalServices.listRecentControlPlaneEvents(limit);
418
+ externalServicePortState = await inspectExternalPorts([previousBindings, externalServiceBindings]);
419
+ requestRender();
420
+ return inspectExternalServices();
421
+ },
422
+ };
302
423
  deferredStartup.schedule({
303
424
  label: 'plugins',
304
425
  run: async () => {
@@ -349,6 +470,7 @@ export async function bootstrapRuntime(
349
470
  if (prune.failedPaths.length > 0) {
350
471
  logger.warn(`[bootstrap] Failed to prune ${prune.failedPaths.length} stale operator-token file(s) (permission/race): ${prune.failedPaths.join(', ')}`);
351
472
  }
473
+ externalServiceBindings = readExternalServiceBindings();
352
474
  externalServicesPromise = startExternalServices(
353
475
  configManager,
354
476
  runtimeBus,
@@ -358,6 +480,7 @@ export async function bootstrapRuntime(
358
480
  );
359
481
  externalServices = await externalServicesPromise;
360
482
  controlPlaneRecentEventsRef.value = (limit) => externalServices.listRecentControlPlaneEvents(limit);
483
+ externalServicePortState = await inspectExternalPorts([externalServiceBindings]);
361
484
  requestRender();
362
485
  },
363
486
  onError: (error) => {