@bli-cockpit/cli 0.2.33 → 0.2.35

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.
@@ -185,7 +185,7 @@ async function refreshOnboardAutostart(command, roots, io) {
185
185
  return result;
186
186
  }
187
187
  writeLine(io.stdout, result.loaded
188
- ? "Background autostart refreshed; Cockpit syncs at login and every 15 min."
188
+ ? "Background autostart refreshed; Tower syncs at login and every 15 min."
189
189
  : "Background autostart refreshed, but the operating-system scheduler reported a problem; check `cockpit autostart status`.");
190
190
  writeLine(io.stdout, autostartLocationLine(result));
191
191
  return result;
@@ -205,7 +205,7 @@ async function refreshOnboardAgentRules(command, roots, io) {
205
205
  }
206
206
  function onboardAgentRulesInstallLine(result) {
207
207
  if (result.targets.some((target) => target.stale_block_replaced)) {
208
- return "updated; replaced stale Cockpit ticket-binding guidance.";
208
+ return "updated; replaced stale Tower ticket-binding guidance.";
209
209
  }
210
210
  switch (result.status) {
211
211
  case "created":
@@ -311,7 +311,7 @@ function backgroundSyncLine(result) {
311
311
  return result.status;
312
312
  }
313
313
  function writeOnboardBanner(command, io) {
314
- writeLine(io.stdout, "Setting up Cockpit");
314
+ writeLine(io.stdout, "Setting up Tower");
315
315
  writeLine(io.stdout, `Dashboard: ${command.dashboardUrl}`);
316
316
  writeLine(io.stdout, `Ticket: ${displayTicketId(command.activeTicketId)}`);
317
317
  }
@@ -411,297 +411,355 @@ async function resolveAndSaveDoctorRoots(command, io) {
411
411
  writeLine(io.stdout, `Saved collection roots: ${resolution.collectionRoots.join(", ")}`);
412
412
  }
413
413
  }
414
+ /**
415
+ * Every onboard exit funnels through here so the best-effort install-event
416
+ * report always fires, pass or blocked — replaces a `finish` closure that used
417
+ * to live inside `runOnboard` so the flow functions below can call it too.
418
+ */
419
+ async function finishOnboardRun(command, installEvents, io, code) {
420
+ await reportInstallEventsBestEffort({
421
+ homeDir: command.homeDir,
422
+ dashboardUrl: command.dashboardUrl,
423
+ command: "onboard",
424
+ events: installEvents,
425
+ json: command.json,
426
+ io,
427
+ });
428
+ return code;
429
+ }
430
+ /**
431
+ * Every onboard `--json` payload is `onboardResult` plus the roots this pass
432
+ * resolved plus whatever extra keys that arm of onboarding adds. Assembling it
433
+ * here means the call sites below cannot drift on key order or forget a field
434
+ * a sibling call site remembers.
435
+ */
436
+ function onboardJsonPayload(resultStatus, command, install, pair, sync, status, rootsResult, extra = {}) {
437
+ return {
438
+ ...onboardResult(resultStatus, command, install, pair, sync, status),
439
+ collection_roots: rootsResult?.roots ?? [],
440
+ root_resolution: rootsResult,
441
+ ...extra,
442
+ };
443
+ }
444
+ function writeOnboardJson(io, payload) {
445
+ writeLine(io.stdout, JSON.stringify(payload, null, 2));
446
+ }
447
+ /** Step 0: which folders are approved, and who claims to own this machine's uploads. */
448
+ async function prepareOnboardRoots(command, installEvents, io) {
449
+ const resolvedRoots = await resolveOnboardingRootsForCommand(command, io);
450
+ const rootsResult = resolvedRoots.rootsResult;
451
+ const collectionRoots = rootsResult.roots;
452
+ const attributionCollectionRoots = await collectionRootConsentAliases(collectionRoots);
453
+ const primaryRoot = resolvedRoots.primaryRoot;
454
+ const resolvedCommand = {
455
+ ...command,
456
+ repoRoot: primaryRoot,
457
+ collectionRoots,
458
+ };
459
+ if (!command.json) {
460
+ writeLine(io.stdout, `Collecting from: ${collectionRoots.join(", ")}`);
461
+ }
462
+ if (rootsResult.homeRootOptIn) {
463
+ addInstallEvent(installEvents, "home_root_optin", "ok");
464
+ }
465
+ return {
466
+ resolvedRoots,
467
+ rootsResult,
468
+ collectionRoots,
469
+ attributionCollectionRoots,
470
+ primaryRoot,
471
+ existingConfig: resolvedRoots.existingConfig,
472
+ resolvedCommand,
473
+ };
474
+ }
475
+ /** Step 1: write the local config that makes this machine a known collector. */
476
+ async function installOnboardConfig(command, resolvedRoots, installEvents, io) {
477
+ const install = await persistOnboardingRootConfig(command, resolvedRoots);
478
+ addInstallEvent(installEvents, "install", "ok");
479
+ if (!command.json) {
480
+ writeLine(io.stdout, "1/5 Installed local collector.");
481
+ writeLine(io.stdout, `Config: ${install.paths.config_file}`);
482
+ }
483
+ return install;
484
+ }
485
+ /**
486
+ * The onboarding runbook: resolve roots, install, pair, discover worktrees,
487
+ * then hand off to whichever arm matches what was found — one repo runs the
488
+ * single-repo sync-then-backfill sequence (`runOnboardSingleRepoFlow`),
489
+ * several run the parent-folder one (`runOnboardMultiRepoFlow`). Any failure
490
+ * anywhere in either arm unwinds to the single catch below.
491
+ */
414
492
  async function runOnboard(command, io) {
415
493
  const installEvents = [];
416
- const finish = async (code) => {
417
- await reportInstallEventsBestEffort({
418
- homeDir: command.homeDir,
419
- dashboardUrl: command.dashboardUrl,
420
- command: "onboard",
421
- events: installEvents,
422
- json: command.json,
423
- io,
424
- });
425
- return code;
426
- };
427
494
  let install = null;
428
495
  let pair = null;
429
- let sync = null;
430
- let status = null;
431
496
  let rootsResult = null;
432
- let agentRules = null;
433
- let autostart = null;
497
+ // `sync` has to survive a throw from inside either flow function, so it is
498
+ // mutated through this holder rather than returned — a return only happens
499
+ // on the success path, and the failure path needs the value too.
500
+ const syncHolder = {
501
+ current: null,
502
+ };
434
503
  try {
435
504
  if (!command.json)
436
505
  writeOnboardBanner(command, io);
437
- const resolvedRoots = await resolveOnboardingRootsForCommand(command, io);
438
- const existingConfig = resolvedRoots.existingConfig;
439
- rootsResult = resolvedRoots.rootsResult;
440
- const collectionRoots = rootsResult.roots;
441
- const attributionCollectionRoots = await collectionRootConsentAliases(collectionRoots);
442
- const primaryRoot = resolvedRoots.primaryRoot;
443
- const resolvedCommand = {
444
- ...command,
445
- repoRoot: primaryRoot,
446
- collectionRoots,
447
- };
448
- if (!command.json) {
449
- writeLine(io.stdout, `Collecting from: ${collectionRoots.join(", ")}`);
450
- }
451
- if (rootsResult.homeRootOptIn) {
452
- addInstallEvent(installEvents, "home_root_optin", "ok");
453
- }
454
- const claimedOwnerEmail = await resolveOnboardEmail(resolvedCommand, collectionRoots, existingConfig, io);
455
- install = await persistOnboardingRootConfig(command, resolvedRoots);
456
- addInstallEvent(installEvents, "install", "ok");
457
- if (!command.json) {
458
- writeLine(io.stdout, "1/5 Installed local collector.");
459
- writeLine(io.stdout, `Config: ${install.paths.config_file}`);
460
- }
461
- pair = await pairForOnboarding(command, { primaryRoot, claimedOwnerEmail }, installEvents, io);
462
- const worktrees = await discoverCommandWorktrees(collectionRoots, {
506
+ const roots = await prepareOnboardRoots(command, installEvents, io);
507
+ rootsResult = roots.rootsResult;
508
+ const claimedOwnerEmail = await resolveOnboardEmail(roots.resolvedCommand, roots.collectionRoots, roots.existingConfig, io);
509
+ install = await installOnboardConfig(command, roots.resolvedRoots, installEvents, io);
510
+ pair = await pairForOnboarding(command, { primaryRoot: roots.primaryRoot, claimedOwnerEmail }, installEvents, io);
511
+ const worktrees = await discoverCommandWorktrees(roots.collectionRoots, {
463
512
  maxDepth: command.maxDepth,
464
513
  maxRepos: command.maxRepos,
465
514
  homeDir: command.homeDir,
466
515
  allowEmpty: true,
467
516
  }, io);
468
- if (worktrees.length > 1) {
469
- const multi = await runMultiRepoOnboard(resolvedCommand, io, worktrees);
470
- addInstallEvent(installEvents, "work_context", "ok");
471
- addInstallEvent(installEvents, "sync", multi.ok ? "ok" : "fail", multi.ok ? undefined : "sync_blocked");
472
- if (!multi.ok) {
473
- if (command.json) {
474
- writeLine(io.stdout, JSON.stringify({
475
- ...onboardResult("blocked", resolvedCommand, install, pair, null, null),
476
- collection_roots: collectionRoots,
477
- root_resolution: rootsResult,
478
- mode: "multi_repo",
479
- repos: multi.results,
480
- codex_sessions: multi.codex_sessions,
481
- blocker: "sync_blocked",
482
- next_step: "Run `cockpit sync --json` until every root returns an uploaded receipt.",
483
- }, null, 2));
484
- }
485
- return finish(1);
486
- }
487
- const backfill = await runRequiredOnboardBackfill(resolvedCommand, io);
488
- const completedBackfill = backfill.status === "complete" ? backfill.result : null;
489
- addInstallEvent(installEvents, "backfill", completedBackfill ? "ok" : "fail", completedBackfill
490
- ? undefined
491
- : backfill.failureReason ?? "backfill_incomplete");
492
- if (!completedBackfill) {
493
- writeOnboardBackfillBlockedResult(io, {
494
- json: command.json,
495
- base: {
496
- ...onboardResult("blocked", resolvedCommand, install, pair, null, null),
497
- collection_roots: collectionRoots,
498
- root_resolution: rootsResult,
499
- },
500
- extra: {
501
- mode: "multi_repo",
502
- repos: multi.results,
503
- codex_sessions: multi.codex_sessions,
504
- },
505
- backfill,
506
- });
507
- return finish(1);
508
- }
509
- const multiSetup = await completeOnboardingSetup(resolvedCommand, collectionRoots, installEvents, io);
510
- agentRules = multiSetup.agentRules;
511
- autostart = multiSetup.autostart;
512
- const onboardOk = multiSetup.ok;
513
- if (command.json) {
514
- writeLine(io.stdout, JSON.stringify({
515
- ...onboardResult(onboardOk ? "pass" : "blocked", resolvedCommand, install, pair, null, null),
516
- collection_roots: collectionRoots,
517
- root_resolution: rootsResult,
518
- agent_rules: agentRules,
519
- autostart,
520
- backfill: completedBackfill,
521
- mode: "multi_repo",
522
- repos: multi.results,
523
- codex_sessions: multi.codex_sessions,
524
- ...(!onboardOk && multi.ok
525
- ? {
526
- blocker: "autostart_load_failed",
527
- next_step: "Run `cockpit autostart install`, then require `cockpit autostart status --json` to exit 0.",
528
- }
529
- : {}),
530
- }, null, 2));
531
- }
532
- if (onboardOk && !command.json) {
533
- writeLine(io.stdout, "PASS: Cockpit is set up and collecting.");
534
- writeOnboardLiveStatus(io, resolvedCommand, collectionRoots, {
535
- pair,
536
- status,
537
- sync: null,
538
- agentRules,
539
- autostart,
540
- initialSyncOk: true,
541
- backfill: completedBackfill,
542
- });
543
- }
544
- else if (multi.ok && !command.json) {
545
- writeOnboardAutostartBlocker(io, autostart);
546
- }
547
- return finish(onboardOk ? 0 : 1);
548
- }
549
- const worktreeRoot = worktrees[0]?.repo_root ?? primaryRoot;
550
- const context = await startLocalWorkContext({
551
- homeDir: command.homeDir,
552
- repoRoot: worktreeRoot,
553
- branch: command.branch,
554
- activeTicketId: command.activeTicketId,
555
- });
556
- if (!command.json) {
557
- writeLine(io.stdout, "3/5 Work context active.");
558
- writeLine(io.stdout, `Repo: ${context.repo}`);
559
- writeLine(io.stdout, `Branch: ${context.branch}`);
560
- writeLine(io.stdout, `Ticket: ${displayTicketId(context.active_ticket_id)}`);
561
- writeLine(io.stdout, `Context: ${context.work_context_id}`);
562
- }
563
- addInstallEvent(installEvents, "work_context", "ok");
564
- const run = await runAttributedWorktreeSync({
565
- homeDir: command.homeDir,
566
- dashboardUrl: command.dashboardUrl,
567
- collectionRoots: attributionCollectionRoots,
568
- startContexts: false,
517
+ const ctx = {
518
+ command,
519
+ resolvedCommand: roots.resolvedCommand,
520
+ collectionRoots: roots.collectionRoots,
521
+ rootsResult: roots.rootsResult,
522
+ install,
523
+ pair,
524
+ installEvents,
525
+ io,
569
526
  worktrees,
570
- fetchImpl: io.fetch,
571
- });
572
- sync = run.outcomes[0]?.sync ?? null;
573
- if (!sync) {
574
- throw new Error("Onboard sync produced no result for the repo worktree.");
527
+ };
528
+ if (worktrees.length > 1) {
529
+ return await runOnboardMultiRepoFlow(ctx);
575
530
  }
576
- status = await inspectLocalCollectorStatus({
577
- homeDir: command.homeDir,
578
- repoRoot: worktreeRoot,
579
- branch: command.branch,
531
+ return await runOnboardSingleRepoFlow(ctx, {
532
+ attributionCollectionRoots: roots.attributionCollectionRoots,
533
+ primaryRoot: roots.primaryRoot,
534
+ syncHolder,
580
535
  });
581
- if (!run.ok || sync.status !== "uploaded") {
582
- addInstallEvent(installEvents, "sync", "fail", "sync_blocked");
583
- const collectionRunStatus = attributedSyncRunStatus(run);
584
- if (command.json) {
585
- writeLine(io.stdout, JSON.stringify({
586
- ...onboardResult("blocked", resolvedCommand, install, pair, sync, status),
587
- collection_roots: collectionRoots,
588
- root_resolution: rootsResult,
589
- codex_sessions: run.summary,
590
- collection_status: collectionRunStatus,
591
- }, null, 2));
592
- }
593
- else {
594
- const syncFailureReason = sync.status === "spooled" ? sync.failure_reason : null;
595
- const retryCommand = sync.status === "spooled"
596
- ? sync.retry_command
597
- : `cockpit sync --workspace ${JSON.stringify(worktreeRoot)}`;
598
- writeLine(io.stderr, "BLOCKED: initial collection is incomplete; retry until every eligible session has a durable receipt or explicit terminal reason.");
599
- writeLine(io.stderr, `Failure: ${syncFailureReason ?? (run.summary.report_posted ? collectionRunStatus : run.summary.report_reason)}`);
600
- writeAgentSessionSummary(io, run.summary);
601
- writeLine(io.stderr, `Retry: ${retryCommand}`);
602
- }
603
- return finish(1);
604
- }
605
- addInstallEvent(installEvents, "sync", "ok");
606
- const backfill = await runRequiredOnboardBackfill(resolvedCommand, io);
607
- const completedBackfill = backfill.status === "complete" ? backfill.result : null;
608
- addInstallEvent(installEvents, "backfill", completedBackfill ? "ok" : "fail", completedBackfill
609
- ? undefined
610
- : backfill.failureReason ?? "backfill_incomplete");
611
- if (!completedBackfill) {
612
- writeOnboardBackfillBlockedResult(io, {
613
- json: command.json,
614
- base: {
615
- ...onboardResult("blocked", resolvedCommand, install, pair, sync, status),
616
- collection_roots: collectionRoots,
617
- root_resolution: rootsResult,
618
- },
619
- extra: { codex_sessions: run.summary },
620
- backfill,
621
- });
622
- return finish(1);
623
- }
536
+ }
537
+ catch (error) {
538
+ return await handleOnboardFailure(error, command, rootsResult, install, pair, syncHolder.current, installEvents, io);
539
+ }
540
+ }
541
+ /** The parent-folder arm: sync every discovered worktree, then backfill and finish setup once. */
542
+ async function runOnboardMultiRepoFlow(ctx) {
543
+ const { command, resolvedCommand, collectionRoots, rootsResult, install, pair, installEvents, io, worktrees, } = ctx;
544
+ const multi = await runMultiRepoOnboard(resolvedCommand, io, worktrees);
545
+ addInstallEvent(installEvents, "work_context", "ok");
546
+ addInstallEvent(installEvents, "sync", multi.ok ? "ok" : "fail", multi.ok ? undefined : "sync_blocked");
547
+ if (!multi.ok) {
624
548
  if (command.json) {
625
- const jsonSetup = await completeOnboardingSetup(resolvedCommand, collectionRoots, installEvents, io);
626
- agentRules = jsonSetup.agentRules;
627
- autostart = jsonSetup.autostart;
628
- const onboardOk = jsonSetup.ok;
629
- writeLine(io.stdout, JSON.stringify({
630
- ...onboardResult(onboardOk ? "pass" : "blocked", resolvedCommand, install, pair, sync, status),
631
- collection_roots: collectionRoots,
632
- root_resolution: rootsResult,
633
- agent_rules: agentRules,
634
- autostart,
635
- backfill: completedBackfill,
636
- codex_sessions: run.summary,
637
- ...(!onboardOk
638
- ? {
639
- blocker: "autostart_load_failed",
640
- next_step: "Run `cockpit autostart install`, then require `cockpit autostart status --json` to exit 0.",
641
- }
642
- : {}),
643
- }, null, 2));
644
- return finish(onboardOk ? 0 : 1);
645
- }
646
- writeLine(io.stdout, "4/5 Uploaded what you worked on.");
647
- writeLine(io.stdout, `HTTP: ${sync.http_status}`);
648
- writeLine(io.stdout, `Things recorded: ${sync.event_count}`);
649
- writeLine(io.stdout, `Sources: ${sync.source_scan_count}`);
650
- writeLine(io.stdout, `Risk flags: ${sync.risk_flag_count}`);
651
- writeLine(io.stdout, `Raw evidence files: ${sync.raw_evidence_file_count}`);
652
- writeLine(io.stdout, rawEvidenceSyncLine(sync));
653
- writeAgentSessionSummary(io, run.summary);
654
- writeLine(io.stdout, "5/5 Status ready.");
655
- writeLine(io.stdout, `Upload state: ${status.upload_state}`);
656
- writeLine(io.stdout, `Open: ${command.dashboardUrl}/my-work`);
657
- const setup = await completeOnboardingSetup(resolvedCommand, collectionRoots, installEvents, io);
658
- agentRules = setup.agentRules;
659
- autostart = setup.autostart;
660
- if (!setup.ok) {
661
- writeOnboardAutostartBlocker(io, autostart);
662
- return finish(1);
549
+ writeOnboardJson(io, onboardJsonPayload("blocked", resolvedCommand, install, pair, null, null, rootsResult, {
550
+ mode: "multi_repo",
551
+ repos: multi.results,
552
+ codex_sessions: multi.codex_sessions,
553
+ blocker: "sync_blocked",
554
+ next_step: "Run `cockpit sync --json` until every root returns an uploaded receipt.",
555
+ }));
663
556
  }
664
- writeLine(io.stdout, "PASS: Cockpit is set up and collecting.");
557
+ return finishOnboardRun(command, installEvents, io, 1);
558
+ }
559
+ const backfill = await runRequiredOnboardBackfill(resolvedCommand, io);
560
+ const completedBackfill = backfill.status === "complete" ? backfill.result : null;
561
+ addInstallEvent(installEvents, "backfill", completedBackfill ? "ok" : "fail", completedBackfill
562
+ ? undefined
563
+ : backfill.failureReason ?? "backfill_incomplete");
564
+ if (!completedBackfill) {
565
+ writeOnboardBackfillBlockedResult(io, {
566
+ json: command.json,
567
+ base: onboardJsonPayload("blocked", resolvedCommand, install, pair, null, null, rootsResult),
568
+ extra: {
569
+ mode: "multi_repo",
570
+ repos: multi.results,
571
+ codex_sessions: multi.codex_sessions,
572
+ },
573
+ backfill,
574
+ });
575
+ return finishOnboardRun(command, installEvents, io, 1);
576
+ }
577
+ const multiSetup = await completeOnboardingSetup(resolvedCommand, collectionRoots, installEvents, io);
578
+ const { agentRules, autostart } = multiSetup;
579
+ const onboardOk = multiSetup.ok;
580
+ if (command.json) {
581
+ writeOnboardJson(io, onboardJsonPayload(onboardOk ? "pass" : "blocked", resolvedCommand, install, pair, null, null, rootsResult, {
582
+ agent_rules: agentRules,
583
+ autostart,
584
+ backfill: completedBackfill,
585
+ mode: "multi_repo",
586
+ repos: multi.results,
587
+ codex_sessions: multi.codex_sessions,
588
+ ...(!onboardOk && multi.ok
589
+ ? {
590
+ blocker: "autostart_load_failed",
591
+ next_step: "Run `cockpit autostart install`, then require `cockpit autostart status --json` to exit 0.",
592
+ }
593
+ : {}),
594
+ }));
595
+ }
596
+ if (onboardOk && !command.json) {
597
+ writeLine(io.stdout, "PASS: Tower is set up and collecting.");
665
598
  writeOnboardLiveStatus(io, resolvedCommand, collectionRoots, {
666
599
  pair,
667
- status,
668
- sync,
600
+ status: null,
601
+ sync: null,
669
602
  agentRules,
670
603
  autostart,
671
604
  initialSyncOk: true,
672
605
  backfill: completedBackfill,
673
606
  });
674
- return finish(0);
675
607
  }
676
- catch (error) {
677
- const message = errorMessage(error);
678
- const statusRoot = rootsResult?.roots[0] ?? command.repoRoot;
679
- status = statusRoot
680
- ? await inspectLocalCollectorStatus({
681
- homeDir: command.homeDir,
682
- repoRoot: statusRoot,
683
- branch: command.branch,
684
- }).catch(() => null)
685
- : null;
686
- const blocker = classifyOnboardBlocker(message);
687
- addOnboardFailureEvent(installEvents, blocker);
688
- const nextStep = nextStepForOnboardBlocker(blocker, command);
608
+ else if (multi.ok && !command.json) {
609
+ writeOnboardAutostartBlocker(io, autostart);
610
+ }
611
+ return finishOnboardRun(command, installEvents, io, onboardOk ? 0 : 1);
612
+ }
613
+ /** The single-repo arm: start the work context, sync it, backfill, then finish setup once. */
614
+ async function runOnboardSingleRepoFlow(ctx, single) {
615
+ const { command, resolvedCommand, collectionRoots, rootsResult, install, pair, installEvents, io, worktrees, } = ctx;
616
+ const worktreeRoot = worktrees[0]?.repo_root ?? single.primaryRoot;
617
+ const context = await startLocalWorkContext({
618
+ homeDir: command.homeDir,
619
+ repoRoot: worktreeRoot,
620
+ branch: command.branch,
621
+ activeTicketId: command.activeTicketId,
622
+ });
623
+ if (!command.json) {
624
+ writeLine(io.stdout, "3/5 Work context active.");
625
+ writeLine(io.stdout, `Repo: ${context.repo}`);
626
+ writeLine(io.stdout, `Branch: ${context.branch}`);
627
+ writeLine(io.stdout, `Ticket: ${displayTicketId(context.active_ticket_id)}`);
628
+ writeLine(io.stdout, `Context: ${context.work_context_id}`);
629
+ }
630
+ addInstallEvent(installEvents, "work_context", "ok");
631
+ const run = await runAttributedWorktreeSync({
632
+ homeDir: command.homeDir,
633
+ dashboardUrl: command.dashboardUrl,
634
+ collectionRoots: single.attributionCollectionRoots,
635
+ startContexts: false,
636
+ worktrees,
637
+ fetchImpl: io.fetch,
638
+ });
639
+ const sync = run.outcomes[0]?.sync ?? null;
640
+ single.syncHolder.current = sync;
641
+ if (!sync) {
642
+ throw new Error("Onboard sync produced no result for the repo worktree.");
643
+ }
644
+ const status = await inspectLocalCollectorStatus({
645
+ homeDir: command.homeDir,
646
+ repoRoot: worktreeRoot,
647
+ branch: command.branch,
648
+ });
649
+ if (!run.ok || sync.status !== "uploaded") {
650
+ addInstallEvent(installEvents, "sync", "fail", "sync_blocked");
651
+ const collectionRunStatus = attributedSyncRunStatus(run);
689
652
  if (command.json) {
690
- writeLine(io.stdout, JSON.stringify({
691
- ...onboardResult("blocked", command, install, pair, sync, status),
692
- collection_roots: rootsResult?.roots ?? [],
693
- root_resolution: rootsResult,
694
- blocker,
695
- message,
696
- next_step: nextStep,
697
- }, null, 2));
653
+ writeOnboardJson(io, onboardJsonPayload("blocked", resolvedCommand, install, pair, sync, status, rootsResult, {
654
+ codex_sessions: run.summary,
655
+ collection_status: collectionRunStatus,
656
+ }));
698
657
  }
699
658
  else {
700
- writeLine(io.stderr, `BLOCKED: ${message}`);
701
- writeLine(io.stderr, `Next: ${nextStep}`);
659
+ const syncFailureReason = sync.status === "spooled" ? sync.failure_reason : null;
660
+ const retryCommand = sync.status === "spooled"
661
+ ? sync.retry_command
662
+ : `cockpit sync --workspace ${JSON.stringify(worktreeRoot)}`;
663
+ writeLine(io.stderr, "BLOCKED: initial collection is incomplete; retry until every eligible session has a durable receipt or explicit terminal reason.");
664
+ writeLine(io.stderr, `Failure: ${syncFailureReason ?? (run.summary.report_posted ? collectionRunStatus : run.summary.report_reason)}`);
665
+ writeAgentSessionSummary(io, run.summary);
666
+ writeLine(io.stderr, `Retry: ${retryCommand}`);
702
667
  }
703
- return finish(1);
668
+ return finishOnboardRun(command, installEvents, io, 1);
669
+ }
670
+ addInstallEvent(installEvents, "sync", "ok");
671
+ const backfill = await runRequiredOnboardBackfill(resolvedCommand, io);
672
+ const completedBackfill = backfill.status === "complete" ? backfill.result : null;
673
+ addInstallEvent(installEvents, "backfill", completedBackfill ? "ok" : "fail", completedBackfill
674
+ ? undefined
675
+ : backfill.failureReason ?? "backfill_incomplete");
676
+ if (!completedBackfill) {
677
+ writeOnboardBackfillBlockedResult(io, {
678
+ json: command.json,
679
+ base: onboardJsonPayload("blocked", resolvedCommand, install, pair, sync, status, rootsResult),
680
+ extra: { codex_sessions: run.summary },
681
+ backfill,
682
+ });
683
+ return finishOnboardRun(command, installEvents, io, 1);
684
+ }
685
+ if (command.json) {
686
+ const jsonSetup = await completeOnboardingSetup(resolvedCommand, collectionRoots, installEvents, io);
687
+ const { agentRules, autostart } = jsonSetup;
688
+ const onboardOk = jsonSetup.ok;
689
+ writeOnboardJson(io, onboardJsonPayload(onboardOk ? "pass" : "blocked", resolvedCommand, install, pair, sync, status, rootsResult, {
690
+ agent_rules: agentRules,
691
+ autostart,
692
+ backfill: completedBackfill,
693
+ codex_sessions: run.summary,
694
+ ...(!onboardOk
695
+ ? {
696
+ blocker: "autostart_load_failed",
697
+ next_step: "Run `cockpit autostart install`, then require `cockpit autostart status --json` to exit 0.",
698
+ }
699
+ : {}),
700
+ }));
701
+ return finishOnboardRun(command, installEvents, io, onboardOk ? 0 : 1);
702
+ }
703
+ writeLine(io.stdout, "4/5 Uploaded what you worked on.");
704
+ writeLine(io.stdout, `HTTP: ${sync.http_status}`);
705
+ writeLine(io.stdout, `Things recorded: ${sync.event_count}`);
706
+ writeLine(io.stdout, `Sources: ${sync.source_scan_count}`);
707
+ writeLine(io.stdout, `Risk flags: ${sync.risk_flag_count}`);
708
+ writeLine(io.stdout, `Raw evidence files: ${sync.raw_evidence_file_count}`);
709
+ writeLine(io.stdout, rawEvidenceSyncLine(sync));
710
+ writeAgentSessionSummary(io, run.summary);
711
+ writeLine(io.stdout, "5/5 Status ready.");
712
+ writeLine(io.stdout, `Upload state: ${status.upload_state}`);
713
+ writeLine(io.stdout, `Open: ${command.dashboardUrl}/my-work`);
714
+ const setup = await completeOnboardingSetup(resolvedCommand, collectionRoots, installEvents, io);
715
+ const { agentRules, autostart } = setup;
716
+ if (!setup.ok) {
717
+ writeOnboardAutostartBlocker(io, autostart);
718
+ return finishOnboardRun(command, installEvents, io, 1);
719
+ }
720
+ writeLine(io.stdout, "PASS: Tower is set up and collecting.");
721
+ writeOnboardLiveStatus(io, resolvedCommand, collectionRoots, {
722
+ pair,
723
+ status,
724
+ sync,
725
+ agentRules,
726
+ autostart,
727
+ initialSyncOk: true,
728
+ backfill: completedBackfill,
729
+ });
730
+ return finishOnboardRun(command, installEvents, io, 0);
731
+ }
732
+ /**
733
+ * Every uncaught error from either onboarding arm lands here. `status` is
734
+ * always recomputed fresh against whichever root got furthest, never trusted
735
+ * from before the throw — the arms already recompute it themselves on their
736
+ * own success paths, so nothing upstream is lost by not threading it through.
737
+ */
738
+ async function handleOnboardFailure(error, command, rootsResult, install, pair, sync, installEvents, io) {
739
+ const message = errorMessage(error);
740
+ const statusRoot = rootsResult?.roots[0] ?? command.repoRoot;
741
+ const status = statusRoot
742
+ ? await inspectLocalCollectorStatus({
743
+ homeDir: command.homeDir,
744
+ repoRoot: statusRoot,
745
+ branch: command.branch,
746
+ }).catch(() => null)
747
+ : null;
748
+ const blocker = classifyOnboardBlocker(message);
749
+ addOnboardFailureEvent(installEvents, blocker);
750
+ const nextStep = nextStepForOnboardBlocker(blocker, command);
751
+ if (command.json) {
752
+ writeOnboardJson(io, onboardJsonPayload("blocked", command, install, pair, sync, status, rootsResult, {
753
+ blocker,
754
+ message,
755
+ next_step: nextStep,
756
+ }));
757
+ }
758
+ else {
759
+ writeLine(io.stderr, `BLOCKED: ${message}`);
760
+ writeLine(io.stderr, `Next: ${nextStep}`);
704
761
  }
762
+ return finishOnboardRun(command, installEvents, io, 1);
705
763
  }
706
764
  async function runMultiRepoOnboard(command, io, worktrees) {
707
765
  if (!command.json) {
@@ -736,7 +794,7 @@ async function runMultiRepoOnboard(command, io, worktrees) {
736
794
  writeLine(io.stdout, "Live sync receipts complete; verifying all-history Codex and Claude backfill.");
737
795
  }
738
796
  else {
739
- writeLine(io.stderr, `BLOCKED: Cockpit collection is ${collectionRunStatus}; retry until every eligible session has a durable receipt or explicit terminal reason.`);
797
+ writeLine(io.stderr, `BLOCKED: Tower collection is ${collectionRunStatus}; retry until every eligible session has a durable receipt or explicit terminal reason.`);
740
798
  }
741
799
  writeLine(io.stdout, `Open: ${command.dashboardUrl}/my-work`);
742
800
  }
@@ -773,7 +831,7 @@ async function runLogin(command, io) {
773
831
  onPairStarted: command.json
774
832
  ? undefined
775
833
  : (request) => {
776
- writeLine(io.stdout, "Cockpit device pairing started.");
834
+ writeLine(io.stdout, "Tower device pairing started.");
777
835
  writeLine(io.stdout, `Open: ${request.approve_url}`);
778
836
  writeLine(io.stdout, `Code: ${request.user_code}`);
779
837
  writeLine(io.stdout, "Waiting for dashboard approval...");
@@ -783,13 +841,13 @@ async function runLogin(command, io) {
783
841
  writeLine(io.stdout, JSON.stringify(result, null, 2));
784
842
  return 0;
785
843
  }
786
- writeLine(io.stdout, "Cockpit collector paired.");
844
+ writeLine(io.stdout, "Tower collector paired.");
787
845
  writeLine(io.stdout, `Session: ${result.session_file}`);
788
846
  writeLine(io.stdout, `User: ${result.session.email ?? result.session.auth_subject_id}`);
789
847
  writeLine(io.stdout, `Device: ${result.session.device_name ?? result.session.device_id ?? "unknown"}`);
790
848
  writeLine(io.stdout, config.default_repo_paths.length > 0
791
849
  ? "Next: run `cockpit start` inside the repo."
792
- : "Next: run `cockpit onboard` from your repo root so Cockpit knows which repos to collect.");
850
+ : "Next: run `cockpit onboard` from your repo root so Tower knows which repos to collect.");
793
851
  return 0;
794
852
  }
795
853
  function writePairingInstructions(io, request) {
@@ -862,8 +920,8 @@ async function runLogout(command, io) {
862
920
  return 0;
863
921
  }
864
922
  writeLine(io.stdout, result.removed
865
- ? "Cockpit collector session removed."
866
- : "No Cockpit collector session found.");
923
+ ? "Tower collector session removed."
924
+ : "No Tower collector session found.");
867
925
  return 0;
868
926
  }
869
927
  async function runStart(command, io) {
@@ -888,7 +946,7 @@ async function runStart(command, io) {
888
946
  writeLine(io.stdout, JSON.stringify({ mode: "multi_repo", contexts }, null, 2));
889
947
  return 0;
890
948
  }
891
- writeLine(io.stdout, `Cockpit parent work context active for ${contexts.length} worktree(s).`);
949
+ writeLine(io.stdout, `Tower parent work context active for ${contexts.length} worktree(s).`);
892
950
  for (const context of contexts) {
893
951
  writeLine(io.stdout, `- ${context.repo_label ?? context.repo}/${context.worktree_label ?? "worktree"} · ${context.branch} · ${context.work_context_id}`);
894
952
  }
@@ -899,7 +957,7 @@ async function runStart(command, io) {
899
957
  writeLine(io.stdout, JSON.stringify(context, null, 2));
900
958
  return 0;
901
959
  }
902
- writeLine(io.stdout, "Cockpit work context active.");
960
+ writeLine(io.stdout, "Tower work context active.");
903
961
  writeLine(io.stdout, `Repo: ${context.repo}`);
904
962
  writeLine(io.stdout, `Branch: ${context.branch}`);
905
963
  writeLine(io.stdout, `Ticket: ${displayTicketId(context.active_ticket_id)}`);
@@ -1156,7 +1214,7 @@ async function runSyncWithHealthReceipt(command, io) {
1156
1214
  }, null, 2));
1157
1215
  }
1158
1216
  else {
1159
- writeLine(io.stdout, "Cockpit sync already running; skipping this run.");
1217
+ writeLine(io.stdout, "Tower sync already running; skipping this run.");
1160
1218
  }
1161
1219
  return {
1162
1220
  exitCode: 0,
@@ -1207,6 +1265,13 @@ function syncResult(run) {
1207
1265
  failureReasons: run.ok ? [] : run.failure_reasons,
1208
1266
  };
1209
1267
  }
1268
+ /**
1269
+ * The sync runbook: resolve which roots to collect, dedup staged packs before
1270
+ * touching anything else, discover worktrees, sync them, then report — one of
1271
+ * three shapes depending on how many worktrees came back. The three shapes
1272
+ * share nothing but `dedup`, so each gets its own step function below rather
1273
+ * than one branchy body.
1274
+ */
1210
1275
  async function runSyncLocked(command, io) {
1211
1276
  const collectionRoots = await resolveSyncCollectionRoots(command);
1212
1277
  // Before anything is collected: collapse byte-identical staged packs. It runs
@@ -1233,33 +1298,7 @@ async function runSyncLocked(command, io) {
1233
1298
  fetchImpl: io.fetch,
1234
1299
  });
1235
1300
  if (run.outcomes.length > 1) {
1236
- const rows = run.outcomes.map((outcome) => worktreeSyncRow(outcome, run));
1237
- const collectionRunStatus = attributedSyncRunStatus(run);
1238
- const gc = run.ok ? await runSyncRawEvidenceGc(command, io) : null;
1239
- if (command.json) {
1240
- writeLine(io.stdout, JSON.stringify({
1241
- mode: "multi_repo",
1242
- status: collectionRunStatus,
1243
- collection_complete: run.ok,
1244
- results: run.outcomes.map((outcome) => outcome.sync),
1245
- repos: rows,
1246
- codex_sessions: run.summary,
1247
- raw_evidence_gc: gc,
1248
- raw_evidence_dedup: dedup,
1249
- }, null, 2));
1250
- return syncResult(run);
1251
- }
1252
- writeLine(run.ok ? io.stdout : io.stderr, `Cockpit parent sync ${collectionRunStatus} ${run.outcomes.filter((outcome) => outcome.sync.status === "uploaded").length}/${run.outcomes.length} worktree(s).`);
1253
- for (const outcome of run.outcomes) {
1254
- const { worktree, sync } = outcome;
1255
- const uploaded = sync.status === "uploaded";
1256
- const failureSuffix = sync.status === "spooled" ? ` reason:${sync.failure_reason}` : "";
1257
- writeLine(uploaded ? io.stdout : io.stderr, `- ${worktree.repo_label}/${worktree.worktree_label} (${worktree.branch}) head:${shortSha(sync.head_sha ?? worktree.head_sha)} ${sync.status} objects:${sync.raw_evidence_uploaded_object_count} chunks:${sync.raw_evidence_uploaded_chunk_count} reused:${sync.raw_evidence_reused_count} failed:${sync.raw_evidence_failed_count} cursor:${sync.cursor_tracked_object_count}${failureSuffix}`);
1258
- }
1259
- writeAgentSessionSummary(io, run.summary);
1260
- if (gc && !gc.skipped)
1261
- writeLine(io.stdout, rawEvidenceGcSummary(gc));
1262
- return syncResult(run);
1301
+ return reportMultiRepoSync(command, io, run, dedup);
1263
1302
  }
1264
1303
  // Zero worktrees is a legitimate steady state, not a failure: an approved
1265
1304
  // root can hold no git repos, and sessions upload independently of
@@ -1268,25 +1307,60 @@ async function runSyncLocked(command, io) {
1268
1307
  // fleet machine with a single empty root and taught people to ignore
1269
1308
  // sync_failed (BLI-2722). A genuinely broken run still fails via run.ok.
1270
1309
  if (run.outcomes.length === 0) {
1271
- const collectionRunStatus = attributedSyncRunStatus(run);
1272
- const gc = run.ok ? await runSyncRawEvidenceGc(command, io) : null;
1273
- if (command.json) {
1274
- writeLine(io.stdout, JSON.stringify({
1275
- mode: "no_worktrees",
1276
- status: collectionRunStatus,
1277
- collection_complete: run.ok,
1278
- codex_sessions: run.summary,
1279
- raw_evidence_gc: gc,
1280
- raw_evidence_dedup: dedup,
1281
- }, null, 2));
1282
- return syncResult(run);
1283
- }
1284
- writeLine(run.ok ? io.stdout : io.stderr, `Cockpit sync ${collectionRunStatus}: no git worktrees under this root; session scan ran.`);
1285
- writeAgentSessionSummary(io, run.summary);
1286
- if (gc && !gc.skipped)
1287
- writeLine(io.stdout, rawEvidenceGcSummary(gc));
1310
+ return reportNoWorktreeSync(command, io, run, dedup);
1311
+ }
1312
+ return reportSingleRepoSync(command, io, run, dedup);
1313
+ }
1314
+ async function reportMultiRepoSync(command, io, run, dedup) {
1315
+ const rows = run.outcomes.map((outcome) => worktreeSyncRow(outcome, run));
1316
+ const collectionRunStatus = attributedSyncRunStatus(run);
1317
+ const gc = run.ok ? await runSyncRawEvidenceGc(command, io) : null;
1318
+ if (command.json) {
1319
+ writeLine(io.stdout, JSON.stringify({
1320
+ mode: "multi_repo",
1321
+ status: collectionRunStatus,
1322
+ collection_complete: run.ok,
1323
+ results: run.outcomes.map((outcome) => outcome.sync),
1324
+ repos: rows,
1325
+ codex_sessions: run.summary,
1326
+ raw_evidence_gc: gc,
1327
+ raw_evidence_dedup: dedup,
1328
+ }, null, 2));
1288
1329
  return syncResult(run);
1289
1330
  }
1331
+ writeLine(run.ok ? io.stdout : io.stderr, `Tower parent sync ${collectionRunStatus} ${run.outcomes.filter((outcome) => outcome.sync.status === "uploaded").length}/${run.outcomes.length} worktree(s).`);
1332
+ for (const outcome of run.outcomes) {
1333
+ const { worktree, sync } = outcome;
1334
+ const uploaded = sync.status === "uploaded";
1335
+ const failureSuffix = sync.status === "spooled" ? ` reason:${sync.failure_reason}` : "";
1336
+ writeLine(uploaded ? io.stdout : io.stderr, `- ${worktree.repo_label}/${worktree.worktree_label} (${worktree.branch}) head:${shortSha(sync.head_sha ?? worktree.head_sha)} ${sync.status} objects:${sync.raw_evidence_uploaded_object_count} chunks:${sync.raw_evidence_uploaded_chunk_count} reused:${sync.raw_evidence_reused_count} failed:${sync.raw_evidence_failed_count} cursor:${sync.cursor_tracked_object_count}${failureSuffix}`);
1337
+ }
1338
+ writeAgentSessionSummary(io, run.summary);
1339
+ if (gc && !gc.skipped)
1340
+ writeLine(io.stdout, rawEvidenceGcSummary(gc));
1341
+ return syncResult(run);
1342
+ }
1343
+ async function reportNoWorktreeSync(command, io, run, dedup) {
1344
+ const collectionRunStatus = attributedSyncRunStatus(run);
1345
+ const gc = run.ok ? await runSyncRawEvidenceGc(command, io) : null;
1346
+ if (command.json) {
1347
+ writeLine(io.stdout, JSON.stringify({
1348
+ mode: "no_worktrees",
1349
+ status: collectionRunStatus,
1350
+ collection_complete: run.ok,
1351
+ codex_sessions: run.summary,
1352
+ raw_evidence_gc: gc,
1353
+ raw_evidence_dedup: dedup,
1354
+ }, null, 2));
1355
+ return syncResult(run);
1356
+ }
1357
+ writeLine(run.ok ? io.stdout : io.stderr, `Tower sync ${collectionRunStatus}: no git worktrees under this root; session scan ran.`);
1358
+ writeAgentSessionSummary(io, run.summary);
1359
+ if (gc && !gc.skipped)
1360
+ writeLine(io.stdout, rawEvidenceGcSummary(gc));
1361
+ return syncResult(run);
1362
+ }
1363
+ async function reportSingleRepoSync(command, io, run, dedup) {
1290
1364
  const result = run.outcomes[0]?.sync;
1291
1365
  if (!result) {
1292
1366
  throw new Error("Sync produced no result for the repo worktree.");
@@ -1305,7 +1379,7 @@ async function runSyncLocked(command, io) {
1305
1379
  return syncResult(run);
1306
1380
  }
1307
1381
  if (run.ok) {
1308
- writeLine(io.stdout, "Cockpit uploaded this session.");
1382
+ writeLine(io.stdout, "Tower uploaded this session.");
1309
1383
  writeLine(io.stdout, `Ticket: ${displayTicketId(result.ticket_id)}`);
1310
1384
  writeLine(io.stdout, `Context: ${result.work_context_id}`);
1311
1385
  writeLine(io.stdout, `Head: ${shortSha(result.head_sha)}`);
@@ -1320,11 +1394,11 @@ async function runSyncLocked(command, io) {
1320
1394
  return syncResult(run);
1321
1395
  }
1322
1396
  if (result.status === "uploaded") {
1323
- writeLine(io.stderr, "Cockpit uploaded, but some sessions did not make it. Run `cockpit sync` again.");
1397
+ writeLine(io.stderr, "Tower uploaded, but some sessions did not make it. Run `cockpit sync` again.");
1324
1398
  writeAgentSessionSummary(io, run.summary);
1325
1399
  return syncResult(run);
1326
1400
  }
1327
- writeLine(io.stderr, "Cockpit could not upload. It saved a note to retry and will try again on the next sync.");
1401
+ writeLine(io.stderr, "Tower could not upload. It saved a note to retry and will try again on the next sync.");
1328
1402
  writeLine(io.stderr, `Ticket: ${displayTicketId(result.ticket_id)}`);
1329
1403
  writeLine(io.stderr, `Failure: ${result.failure_reason}`);
1330
1404
  writeLine(io.stderr, `Retry: ${result.retry_command}`);
@@ -1364,8 +1438,8 @@ async function runAnalyze(command, io) {
1364
1438
  sync: syncOutput,
1365
1439
  error: syncStderr.join("").trim()
1366
1440
  || (syncExitCode === 0
1367
- ? "Cockpit sync did not upload fresh evidence."
1368
- : "Cockpit sync failed."),
1441
+ ? "Tower sync did not upload fresh evidence."
1442
+ : "Tower sync failed."),
1369
1443
  }, null, 2));
1370
1444
  }
1371
1445
  else {
@@ -1387,7 +1461,7 @@ async function runAnalyze(command, io) {
1387
1461
  ...describeError(error),
1388
1462
  }));
1389
1463
  }
1390
- throw new Error("Cockpit is not signed in. Run `cockpit onboard` or `cockpit login` first.");
1464
+ throw new Error("Tower is not signed in. Run `cockpit onboard` or `cockpit login` first.");
1391
1465
  });
1392
1466
  const dashboardUrl = normalizeUrl(command.dashboardUrl ?? session.dashboard_url ?? DEFAULT_DASHBOARD_URL);
1393
1467
  const response = await io.fetch(`${dashboardUrl}/api/ambient/analyze`, {
@@ -1433,8 +1507,8 @@ async function runAnalyze(command, io) {
1433
1507
  return 0;
1434
1508
  }
1435
1509
  replayCaptured(io.stderr, syncStderr);
1436
- writeLine(io.stdout, "Cockpit uploaded your latest work.");
1437
- writeLine(io.stdout, "Cockpit analysis queued.");
1510
+ writeLine(io.stdout, "Tower uploaded your latest work.");
1511
+ writeLine(io.stdout, "Tower analysis queued.");
1438
1512
  if (jobId)
1439
1513
  writeLine(io.stdout, `Job: ${jobId}`);
1440
1514
  writeLine(io.stdout, `Status: ${jobStatus === "pending" ? "queued" : jobStatus}`);
@@ -1476,7 +1550,7 @@ async function runServe(command, io) {
1476
1550
  await new Promise((resolve) => {
1477
1551
  server.listen(command.port, "127.0.0.1", resolve);
1478
1552
  });
1479
- writeLine(io.stdout, `Cockpit collector serving on http://127.0.0.1:${command.port}`);
1553
+ writeLine(io.stdout, `Tower collector serving on http://127.0.0.1:${command.port}`);
1480
1554
  await new Promise((resolve) => {
1481
1555
  server.on("close", resolve);
1482
1556
  });
@@ -1544,8 +1618,8 @@ async function runAgentRules(command, io) {
1544
1618
  }
1545
1619
  if ("installed" in result) {
1546
1620
  writeLine(io.stdout, result.installed
1547
- ? "Cockpit agent rules are installed."
1548
- : "Cockpit agent rules are not installed.");
1621
+ ? "Tower agent rules are installed."
1622
+ : "Tower agent rules are not installed.");
1549
1623
  }
1550
1624
  else {
1551
1625
  writeLine(io.stdout, agentRulesStatusLine(result));
@@ -1561,13 +1635,13 @@ function agentRuleHosts(host) {
1561
1635
  function agentRulesStatusLine(result) {
1562
1636
  switch (result.status) {
1563
1637
  case "created":
1564
- return "Cockpit agent rules installed.";
1638
+ return "Tower agent rules installed.";
1565
1639
  case "updated":
1566
- return "Cockpit agent rules updated.";
1640
+ return "Tower agent rules updated.";
1567
1641
  case "unchanged":
1568
- return "Cockpit agent rules already current.";
1642
+ return "Tower agent rules already current.";
1569
1643
  case "missing":
1570
- return "Cockpit agent rules were not installed.";
1644
+ return "Tower agent rules were not installed.";
1571
1645
  }
1572
1646
  }
1573
1647
  function agentRuleHostLabel(host) {
@@ -1577,8 +1651,8 @@ function writeAutostartResult(io, result) {
1577
1651
  switch (result.status) {
1578
1652
  case "installed":
1579
1653
  writeLine(io.stdout, result.loaded
1580
- ? "Cockpit autostart installed and loaded."
1581
- : "Cockpit autostart installed (the operating-system scheduler reported a problem).");
1654
+ ? "Tower autostart installed and loaded."
1655
+ : "Tower autostart installed (the operating-system scheduler reported a problem).");
1582
1656
  writeLine(io.stdout, `Label: ${result.label}`);
1583
1657
  writeLine(io.stdout, autostartLocationLine(result));
1584
1658
  writeLine(io.stdout, `Interval: every ${result.interval_seconds}s`);
@@ -1588,19 +1662,19 @@ function writeAutostartResult(io, result) {
1588
1662
  writeLine(io.stderr, result.message);
1589
1663
  return;
1590
1664
  case "uninstalled":
1591
- writeLine(io.stdout, "Cockpit autostart removed.");
1665
+ writeLine(io.stdout, "Tower autostart removed.");
1592
1666
  writeLine(io.stdout, autostartLocationLine(result));
1593
1667
  return;
1594
1668
  case "absent":
1595
- writeLine(io.stdout, "Cockpit autostart is not installed.");
1669
+ writeLine(io.stdout, "Tower autostart is not installed.");
1596
1670
  writeLine(io.stdout, autostartLocationLine(result));
1597
1671
  return;
1598
1672
  case "loaded":
1599
- writeLine(io.stdout, "Cockpit autostart is installed and loaded.");
1673
+ writeLine(io.stdout, "Tower autostart is installed and loaded.");
1600
1674
  writeLine(io.stdout, autostartLocationLine(result));
1601
1675
  return;
1602
1676
  case "not_loaded":
1603
- writeLine(io.stdout, "Cockpit autostart exists but is disabled or not loaded; run `cockpit autostart install` to repair it.");
1677
+ writeLine(io.stdout, "Tower autostart exists but is disabled or not loaded; run `cockpit autostart install` to repair it.");
1604
1678
  writeLine(io.stdout, autostartLocationLine(result));
1605
1679
  if (result.message)
1606
1680
  writeLine(io.stderr, result.message);