@staff0rd/assist 0.756.0 → 0.757.1

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/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { Command } from "commander";
6
6
  // package.json
7
7
  var package_default = {
8
8
  name: "@staff0rd/assist",
9
- version: "0.756.0",
9
+ version: "0.757.1",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -563,7 +563,7 @@ function stripLegacyConfigKeys(config) {
563
563
  }
564
564
 
565
565
  // src/shared/types.ts
566
- import { z as z3 } from "zod";
566
+ import { z as z4 } from "zod";
567
567
 
568
568
  // src/shared/adviceFragmentNames.ts
569
569
  var adviceFragmentTitles = {
@@ -638,267 +638,273 @@ function isSecretSchemaNode(node) {
638
638
  return meta?.[SECRET_META_KEY] === true;
639
639
  }
640
640
 
641
+ // src/shared/sessionLinkSchema.ts
642
+ import { z as z3 } from "zod";
643
+ var SSH_LINK_RETIRED = "ssh links are no longer supported; relink with `assist sessions nodes link <name> --tailscale <host> --port <port>`";
644
+ var sessionLinkSchema = z3.preprocess(
645
+ (link3, ctx) => {
646
+ if (link3 && typeof link3 === "object" && "ssh" in link3)
647
+ ctx.issues.push({
648
+ code: "custom",
649
+ message: SSH_LINK_RETIRED,
650
+ input: link3
651
+ });
652
+ return link3;
653
+ },
654
+ z3.strictObject({ name: z3.string(), url: z3.string() })
655
+ );
656
+
641
657
  // src/shared/types.ts
642
- var transcriptConfigSchema = z3.strictObject({
643
- vttDir: z3.string(),
644
- transcriptsDir: z3.string(),
645
- summaryDir: z3.string()
658
+ var transcriptConfigSchema = z4.strictObject({
659
+ vttDir: z4.string(),
660
+ transcriptsDir: z4.string(),
661
+ summaryDir: z4.string()
646
662
  });
647
- var miroExtractSchema = z3.strictObject({
648
- board: z3.string().optional(),
649
- frame: z3.string().optional(),
650
- topLeft: z3.string(),
651
- bottomRight: z3.string(),
652
- items: z3.string(),
653
- ignore: z3.string().optional(),
654
- out: z3.string().optional()
663
+ var miroExtractSchema = z4.strictObject({
664
+ board: z4.string().optional(),
665
+ frame: z4.string().optional(),
666
+ topLeft: z4.string(),
667
+ bottomRight: z4.string(),
668
+ items: z4.string(),
669
+ ignore: z4.string().optional(),
670
+ out: z4.string().optional()
655
671
  });
656
672
  var DEFAULT_BACKUP_DIR = "~/.assist/backups";
657
673
  var DEFAULT_CLONE_DIR = "~/git";
658
674
  var assistConfigShape = {
659
- advice: z3.strictObject({
660
- sections: z3.partialRecord(z3.enum(adviceFragmentNames), z3.boolean()).default({}),
661
- extra: z3.string().optional(),
662
- verify: z3.string().optional()
675
+ advice: z4.strictObject({
676
+ sections: z4.partialRecord(z4.enum(adviceFragmentNames), z4.boolean()).default({}),
677
+ extra: z4.string().optional(),
678
+ verify: z4.string().optional()
663
679
  }).optional(),
664
- backlog: z3.strictObject({
665
- previewComments: z3.boolean().default(false)
680
+ backlog: z4.strictObject({
681
+ previewComments: z4.boolean().default(false)
666
682
  }).optional(),
667
- backup: z3.strictObject({
668
- dir: z3.string().default(DEFAULT_BACKUP_DIR)
683
+ backup: z4.strictObject({
684
+ dir: z4.string().default(DEFAULT_BACKUP_DIR)
669
685
  }).default({ dir: DEFAULT_BACKUP_DIR }),
670
- clone: z3.strictObject({
671
- baseDir: z3.string().default(DEFAULT_CLONE_DIR)
686
+ clone: z4.strictObject({
687
+ baseDir: z4.string().default(DEFAULT_CLONE_DIR)
672
688
  }).default({ baseDir: DEFAULT_CLONE_DIR }),
673
- commit: z3.strictObject({
674
- conventional: z3.boolean().default(false),
675
- pull: z3.boolean().default(false),
676
- push: z3.boolean().default(false),
677
- expectedBranch: z3.string().optional()
689
+ commit: z4.strictObject({
690
+ conventional: z4.boolean().default(false),
691
+ pull: z4.boolean().default(false),
692
+ push: z4.boolean().default(false),
693
+ expectedBranch: z4.string().optional()
678
694
  }).default({ conventional: false, pull: false, push: false }),
679
- branch: z3.strictObject({
680
- prefix: z3.string().optional(),
681
- defaultBranch: z3.string().optional()
695
+ branch: z4.strictObject({
696
+ prefix: z4.string().optional(),
697
+ defaultBranch: z4.string().optional()
682
698
  }).optional(),
683
- devlog: z3.strictObject({
684
- name: z3.string().optional(),
685
- ignore: z3.array(z3.string()).optional(),
686
- skip: z3.record(z3.string(), z3.array(z3.string())).optional()
699
+ devlog: z4.strictObject({
700
+ name: z4.string().optional(),
701
+ ignore: z4.array(z4.string()).optional(),
702
+ skip: z4.record(z4.string(), z4.array(z4.string())).optional()
687
703
  }).optional(),
688
- notify: z3.strictObject({
689
- enabled: z3.boolean().default(true)
704
+ notify: z4.strictObject({
705
+ enabled: z4.boolean().default(true)
690
706
  }).default({ enabled: true }),
691
- complexity: z3.strictObject({
692
- ignore: z3.array(z3.string()).default(["**/*test.ts*"])
707
+ complexity: z4.strictObject({
708
+ ignore: z4.array(z4.string()).default(["**/*test.ts*"])
693
709
  }).default({ ignore: ["**/*test.ts*"] }),
694
- hardcodedColors: z3.strictObject({
695
- ignore: z3.array(z3.string()).default([])
710
+ hardcodedColors: z4.strictObject({
711
+ ignore: z4.array(z4.string()).default([])
696
712
  }).optional(),
697
- blockCodeComments: z3.strictObject({
698
- ignore: z3.array(z3.string()).default([])
713
+ blockCodeComments: z4.strictObject({
714
+ ignore: z4.array(z4.string()).default([])
699
715
  }).optional(),
700
- restructure: z3.strictObject({
701
- ignore: z3.array(z3.string()).default([]),
702
- pin: z3.array(z3.string()).default([]),
703
- maxDepth: z3.number().int().positive().default(10)
716
+ restructure: z4.strictObject({
717
+ ignore: z4.array(z4.string()).default([]),
718
+ pin: z4.array(z4.string()).default([]),
719
+ maxDepth: z4.number().int().positive().default(10)
704
720
  }).optional(),
705
- jira: z3.strictObject({
706
- acField: z3.string().default("customfield_11937")
721
+ jira: z4.strictObject({
722
+ acField: z4.string().default("customfield_11937")
707
723
  }).optional(),
708
- roam: z3.strictObject({
709
- clientId: secretConfigValue(z3.string()),
710
- clientSecret: secretConfigValue(z3.string()),
711
- accessToken: secretConfigValue(z3.string()).optional(),
712
- refreshToken: secretConfigValue(z3.string()).optional(),
713
- tokenExpiresAt: z3.number().optional()
724
+ roam: z4.strictObject({
725
+ clientId: secretConfigValue(z4.string()),
726
+ clientSecret: secretConfigValue(z4.string()),
727
+ accessToken: secretConfigValue(z4.string()).optional(),
728
+ refreshToken: secretConfigValue(z4.string()).optional(),
729
+ tokenExpiresAt: z4.number().optional()
714
730
  }).optional(),
715
- slack: z3.strictObject({
716
- channel: z3.string().optional()
731
+ slack: z4.strictObject({
732
+ channel: z4.string().optional()
717
733
  }).optional(),
718
- prs: z3.strictObject({
719
- slack: z3.string().optional(),
720
- required: z3.boolean().default(true),
721
- promptJira: z3.boolean().default(false),
722
- promptGithub: z3.boolean().default(false),
723
- draft: z3.boolean().default(false),
724
- readingWordsPerMinute: z3.number().int().positive().optional()
734
+ prs: z4.strictObject({
735
+ slack: z4.string().optional(),
736
+ required: z4.boolean().default(true),
737
+ promptJira: z4.boolean().default(false),
738
+ promptGithub: z4.boolean().default(false),
739
+ draft: z4.boolean().default(false),
740
+ readingWordsPerMinute: z4.number().int().positive().optional()
725
741
  }).optional(),
726
- review: z3.strictObject({
727
- codexModel: z3.string().optional(),
728
- highLevel: z3.strictObject({
729
- criticalPaths: z3.array(z3.string()).default([]),
730
- uiPaths: z3.array(z3.string()).default([]),
731
- descriptionWordCap: z3.number().int().positive().default(300)
742
+ review: z4.strictObject({
743
+ codexModel: z4.string().optional(),
744
+ highLevel: z4.strictObject({
745
+ criticalPaths: z4.array(z4.string()).default([]),
746
+ uiPaths: z4.array(z4.string()).default([]),
747
+ descriptionWordCap: z4.number().int().positive().default(300)
732
748
  }).optional()
733
749
  }).optional(),
734
- readTime: z3.strictObject({
735
- wordsPerMinute: z3.number().int().positive().default(200)
750
+ readTime: z4.strictObject({
751
+ wordsPerMinute: z4.number().int().positive().default(200)
736
752
  }).optional(),
737
- releases: z3.strictObject({
738
- streams: z3.array(
739
- z3.strictObject({
740
- name: z3.string(),
741
- repo: z3.string(),
742
- workflow: z3.string(),
743
- nodes: z3.array(
744
- z3.strictObject({
745
- id: z3.string(),
746
- environment: z3.string().optional(),
747
- kind: z3.enum(["build", "gate"]).optional(),
748
- label: z3.string().optional()
753
+ releases: z4.strictObject({
754
+ streams: z4.array(
755
+ z4.strictObject({
756
+ name: z4.string(),
757
+ repo: z4.string(),
758
+ workflow: z4.string(),
759
+ nodes: z4.array(
760
+ z4.strictObject({
761
+ id: z4.string(),
762
+ environment: z4.string().optional(),
763
+ kind: z4.enum(["build", "gate"]).optional(),
764
+ label: z4.string().optional()
749
765
  })
750
766
  ),
751
- edges: z3.array(z3.tuple([z3.string(), z3.string()])).default([])
767
+ edges: z4.array(z4.tuple([z4.string(), z4.string()])).default([])
752
768
  })
753
769
  ).default([])
754
770
  }).optional(),
755
- worktree: z3.strictObject({
756
- enabled: z3.boolean().default(false),
757
- watcher: z3.boolean().default(false),
758
- trunk: z3.boolean().default(false),
759
- includeDrafts: z3.boolean().default(false),
760
- root: z3.string().optional(),
761
- install: z3.union([z3.boolean(), z3.string(), z3.array(z3.string())]).default(true),
762
- commitBeforeManualChecks: z3.boolean().default(false),
763
- commitBeforePhaseEnd: z3.boolean().optional(),
764
- copy: z3.array(z3.string()).default([
771
+ worktree: z4.strictObject({
772
+ enabled: z4.boolean().default(false),
773
+ watcher: z4.boolean().default(false),
774
+ trunk: z4.boolean().default(false),
775
+ includeDrafts: z4.boolean().default(false),
776
+ root: z4.string().optional(),
777
+ install: z4.union([z4.boolean(), z4.string(), z4.array(z4.string())]).default(true),
778
+ commitBeforeManualChecks: z4.boolean().default(false),
779
+ commitBeforePhaseEnd: z4.boolean().optional(),
780
+ copy: z4.array(z4.string()).default([
765
781
  ".env",
766
782
  "settings.local.json",
767
783
  ".claude/settings.local.json"
768
784
  ])
769
785
  }).optional(),
770
- run: z3.array(z3.union([runConfigSchema, runLinkSchema])).optional(),
771
- subtasks: z3.array(
772
- z3.strictObject({
773
- title: z3.string(),
774
- description: z3.string().optional()
786
+ run: z4.array(z4.union([runConfigSchema, runLinkSchema])).optional(),
787
+ subtasks: z4.array(
788
+ z4.strictObject({
789
+ title: z4.string(),
790
+ description: z4.string().optional()
775
791
  })
776
792
  ).optional(),
777
793
  transcript: transcriptConfigSchema.optional(),
778
- cliReadVerbs: z3.record(z3.string(), z3.array(z3.string())).optional(),
779
- cliHook: z3.strictObject({
780
- blockNpmRun: z3.boolean().default(true)
794
+ cliReadVerbs: z4.record(z4.string(), z4.array(z4.string())).optional(),
795
+ cliHook: z4.strictObject({
796
+ blockNpmRun: z4.boolean().default(true)
781
797
  }).optional(),
782
- dotnet: z3.strictObject({
783
- inspect: z3.strictObject({
784
- suppress: z3.array(z3.string()).default([])
798
+ dotnet: z4.strictObject({
799
+ inspect: z4.strictObject({
800
+ suppress: z4.array(z4.string()).default([])
785
801
  }).default({ suppress: [] })
786
802
  }).optional(),
787
- litellm: z3.strictObject({
788
- baseUrl: z3.string().optional(),
789
- apiKey: secretConfigValue(z3.string()).optional()
803
+ litellm: z4.strictObject({
804
+ baseUrl: z4.string().optional(),
805
+ apiKey: secretConfigValue(z4.string()).optional()
790
806
  }).optional(),
791
- ravendb: z3.strictObject({
792
- connections: z3.array(
793
- z3.strictObject({
794
- name: z3.string(),
795
- url: z3.string(),
796
- database: z3.string(),
797
- apiKeyRef: z3.string()
807
+ ravendb: z4.strictObject({
808
+ connections: z4.array(
809
+ z4.strictObject({
810
+ name: z4.string(),
811
+ url: z4.string(),
812
+ database: z4.string(),
813
+ apiKeyRef: z4.string()
798
814
  })
799
815
  ).default([]),
800
- defaultConnection: z3.string().optional()
816
+ defaultConnection: z4.string().optional()
801
817
  }).optional(),
802
- seq: z3.strictObject({
803
- connections: z3.array(
804
- z3.strictObject({
805
- name: z3.string(),
806
- url: z3.string(),
807
- apiToken: secretConfigValue(z3.string())
818
+ seq: z4.strictObject({
819
+ connections: z4.array(
820
+ z4.strictObject({
821
+ name: z4.string(),
822
+ url: z4.string(),
823
+ apiToken: secretConfigValue(z4.string())
808
824
  })
809
825
  ).default([]),
810
- defaultConnection: z3.string().optional()
826
+ defaultConnection: z4.string().optional()
811
827
  }).optional(),
812
- sql: z3.strictObject({
813
- connections: z3.array(
814
- z3.strictObject({
815
- name: z3.string(),
816
- server: z3.string(),
817
- port: z3.number(),
818
- user: z3.string(),
819
- password: secretConfigValue(z3.string()),
820
- database: z3.string()
828
+ sql: z4.strictObject({
829
+ connections: z4.array(
830
+ z4.strictObject({
831
+ name: z4.string(),
832
+ server: z4.string(),
833
+ port: z4.number(),
834
+ user: z4.string(),
835
+ password: secretConfigValue(z4.string()),
836
+ database: z4.string()
821
837
  })
822
838
  ).default([]),
823
- defaultConnection: z3.string().optional()
839
+ defaultConnection: z4.string().optional()
824
840
  }).optional(),
825
- screenshot: z3.strictObject({
826
- outputDir: z3.string().default("./screenshots")
841
+ screenshot: z4.strictObject({
842
+ outputDir: z4.string().default("./screenshots")
827
843
  }).default({ outputDir: "./screenshots" }),
828
- sessions: z3.strictObject({
829
- nodeName: z3.string().optional(),
830
- links: z3.array(
831
- z3.union([
832
- z3.strictObject({ name: z3.string(), url: z3.string() }),
833
- z3.strictObject({
834
- name: z3.string(),
835
- ssh: z3.string(),
836
- port: z3.number().int(),
837
- localPort: z3.number().int().optional()
838
- })
839
- ])
840
- ).optional(),
841
- linkVersionCheck: z3.enum(["block", "warn", "off"]).default("block"),
842
- tailscaleServe: z3.boolean().default(true),
843
- includeCommittedChanges: z3.boolean().default(true),
844
- topBar: z3.boolean().default(true),
845
- floatWaiting: z3.boolean().default(true),
846
- floatWaitingAfterMs: z3.number().default(5e3),
847
- maxLive: z3.number().default(24),
848
- newSessionMode: z3.enum(["draft", "bug", "prompt", "design"]).default("draft")
844
+ sessions: z4.strictObject({
845
+ nodeName: z4.string().optional(),
846
+ links: z4.array(sessionLinkSchema).optional(),
847
+ linkVersionCheck: z4.enum(["block", "warn", "off"]).default("block"),
848
+ tailscaleServe: z4.boolean().default(true),
849
+ includeCommittedChanges: z4.boolean().default(true),
850
+ topBar: z4.boolean().default(true),
851
+ floatWaiting: z4.boolean().default(true),
852
+ floatWaitingAfterMs: z4.number().default(5e3),
853
+ maxLive: z4.number().default(24),
854
+ newSessionMode: z4.enum(["draft", "bug", "prompt", "design"]).default("draft")
849
855
  }).optional(),
850
- database: z3.strictObject({
851
- url: secretConfigValue(z3.string()).optional()
856
+ database: z4.strictObject({
857
+ url: secretConfigValue(z4.string()).optional()
852
858
  }).default({}),
853
- mermaid: z3.strictObject({
854
- krokiUrl: z3.string().default("https://kroki.io")
859
+ mermaid: z4.strictObject({
860
+ krokiUrl: z4.string().default("https://kroki.io")
855
861
  }).default({ krokiUrl: "https://kroki.io" }),
856
- miro: z3.strictObject({
857
- extracts: z3.record(z3.string(), miroExtractSchema).optional()
862
+ miro: z4.strictObject({
863
+ extracts: z4.record(z4.string(), miroExtractSchema).optional()
858
864
  }).optional(),
859
- news: z3.strictObject({
860
- showInNav: z3.boolean().default(false)
865
+ news: z4.strictObject({
866
+ showInNav: z4.boolean().default(false)
861
867
  }).optional(),
862
- next: z3.strictObject({
863
- peers: z3.array(z3.string()).optional(),
864
- repos: z3.array(z3.string()).optional(),
865
- projects: z3.array(
866
- z3.string().regex(
868
+ next: z4.strictObject({
869
+ peers: z4.array(z4.string()).optional(),
870
+ repos: z4.array(z4.string()).optional(),
871
+ projects: z4.array(
872
+ z4.string().regex(
867
873
  /^[^/\s]+\/\d+$/,
868
874
  "Use <owner>/<project number>, e.g. my-org/3"
869
875
  )
870
876
  ).optional(),
871
- pickStatuses: z3.array(z3.string()).optional(),
872
- excludeLabels: z3.array(z3.string()).optional(),
873
- excludeTypes: z3.array(z3.string()).optional()
877
+ pickStatuses: z4.array(z4.string()).optional(),
878
+ excludeLabels: z4.array(z4.string()).optional(),
879
+ excludeTypes: z4.array(z4.string()).optional()
874
880
  }).optional(),
875
- deny: z3.array(
876
- z3.strictObject({
877
- pattern: z3.string(),
878
- message: z3.string()
881
+ deny: z4.array(
882
+ z4.strictObject({
883
+ pattern: z4.string(),
884
+ message: z4.string()
879
885
  })
880
886
  ).optional(),
881
- forbiddenStrings: z3.array(
882
- z3.strictObject({
883
- file: z3.string(),
884
- paths: z3.array(z3.string()),
885
- disallowed: z3.string()
887
+ forbiddenStrings: z4.array(
888
+ z4.strictObject({
889
+ file: z4.string(),
890
+ paths: z4.array(z4.string()),
891
+ disallowed: z4.string()
886
892
  })
887
893
  ).optional(),
888
- sync: z3.strictObject({
889
- autoConfirm: z3.boolean().default(false)
894
+ sync: z4.strictObject({
895
+ autoConfirm: z4.boolean().default(false)
890
896
  }).default({ autoConfirm: false }),
891
- harness: z3.strictObject({
892
- engine: z3.enum(["claude", "codex", "pi"]).default("claude"),
893
- exposeCodexActions: z3.boolean().optional(),
894
- exposePiActions: z3.boolean().optional(),
895
- codexModel: z3.string().optional()
897
+ harness: z4.strictObject({
898
+ engine: z4.enum(["claude", "codex", "pi"]).default("claude"),
899
+ exposeCodexActions: z4.boolean().optional(),
900
+ exposePiActions: z4.boolean().optional(),
901
+ codexModel: z4.string().optional()
896
902
  }).default({ engine: "claude" })
897
903
  };
898
- var repoConfigSchema = z3.strictObject(assistConfigShape).partial();
899
- var assistConfigSchema = z3.strictObject({
904
+ var repoConfigSchema = z4.strictObject(assistConfigShape).partial();
905
+ var assistConfigSchema = z4.strictObject({
900
906
  ...assistConfigShape,
901
- repos: z3.record(z3.string(), repoConfigSchema).optional()
907
+ repos: z4.record(z4.string(), repoConfigSchema).optional()
902
908
  });
903
909
 
904
910
  // src/shared/loadConfigFrom.ts
@@ -5071,52 +5077,52 @@ async function loadAllItems(orm, origin) {
5071
5077
  import { readFileSync as readFileSync13 } from "fs";
5072
5078
 
5073
5079
  // src/commands/backlog/types.ts
5074
- import { z as z4 } from "zod";
5075
- var backlogStatusSchema = z4.enum(["todo", "in-progress", "done", "wontdo"]);
5076
- var backlogTypeSchema = z4.enum(["story", "bug"]);
5077
- var planTaskSchema = z4.object({
5078
- task: z4.string()
5080
+ import { z as z5 } from "zod";
5081
+ var backlogStatusSchema = z5.enum(["todo", "in-progress", "done", "wontdo"]);
5082
+ var backlogTypeSchema = z5.enum(["story", "bug"]);
5083
+ var planTaskSchema = z5.object({
5084
+ task: z5.string()
5079
5085
  }).strip();
5080
- var planPhaseSchema = z4.strictObject({
5081
- name: z4.string(),
5082
- tasks: z4.array(planTaskSchema),
5083
- manualChecks: z4.array(z4.string()).optional()
5086
+ var planPhaseSchema = z5.strictObject({
5087
+ name: z5.string(),
5088
+ tasks: z5.array(planTaskSchema),
5089
+ manualChecks: z5.array(z5.string()).optional()
5084
5090
  });
5085
- var phaseUsageSchema = z4.strictObject({
5086
- phaseIdx: z4.number(),
5087
- tokensUp: z4.number(),
5088
- tokensDown: z4.number(),
5089
- activeMs: z4.number(),
5090
- peakContextPct: z4.number()
5091
+ var phaseUsageSchema = z5.strictObject({
5092
+ phaseIdx: z5.number(),
5093
+ tokensUp: z5.number(),
5094
+ tokensDown: z5.number(),
5095
+ activeMs: z5.number(),
5096
+ peakContextPct: z5.number()
5091
5097
  });
5092
- var phaseSessionSchema = z4.strictObject({
5093
- phaseIdx: z4.number(),
5094
- claudeSessionId: z4.string(),
5095
- hostname: z4.string(),
5096
- osUser: z4.string(),
5097
- createdAt: z4.string().optional()
5098
+ var phaseSessionSchema = z5.strictObject({
5099
+ phaseIdx: z5.number(),
5100
+ claudeSessionId: z5.string(),
5101
+ hostname: z5.string(),
5102
+ osUser: z5.string(),
5103
+ createdAt: z5.string().optional()
5098
5104
  });
5099
- var phaseUsageTotalSchema = z4.strictObject({
5100
- tokensUp: z4.number(),
5101
- tokensDown: z4.number(),
5102
- activeMs: z4.number(),
5103
- peakContextPct: z4.number()
5105
+ var phaseUsageTotalSchema = z5.strictObject({
5106
+ tokensUp: z5.number(),
5107
+ tokensDown: z5.number(),
5108
+ activeMs: z5.number(),
5109
+ peakContextPct: z5.number()
5104
5110
  });
5105
- var subtaskStatusSchema = z4.enum(["todo", "in-progress", "done"]);
5106
- var subtaskSchema = z4.strictObject({
5107
- title: z4.string(),
5108
- description: z4.string().optional(),
5111
+ var subtaskStatusSchema = z5.enum(["todo", "in-progress", "done"]);
5112
+ var subtaskSchema = z5.strictObject({
5113
+ title: z5.string(),
5114
+ description: z5.string().optional(),
5109
5115
  status: subtaskStatusSchema.default("todo")
5110
5116
  });
5111
- var backlogCommentTypeSchema = z4.enum(["comment", "summary"]);
5112
- var backlogCommentSchema = z4.strictObject({
5113
- id: z4.number().optional(),
5114
- text: z4.string(),
5115
- phase: z4.number().optional(),
5116
- timestamp: z4.string(),
5117
+ var backlogCommentTypeSchema = z5.enum(["comment", "summary"]);
5118
+ var backlogCommentSchema = z5.strictObject({
5119
+ id: z5.number().optional(),
5120
+ text: z5.string(),
5121
+ phase: z5.number().optional(),
5122
+ timestamp: z5.string(),
5117
5123
  type: backlogCommentTypeSchema
5118
5124
  });
5119
- var gitRefKindSchema = z4.enum([
5125
+ var gitRefKindSchema = z5.enum([
5120
5126
  "branch",
5121
5127
  "commit",
5122
5128
  "commit-parent",
@@ -5124,41 +5130,41 @@ var gitRefKindSchema = z4.enum([
5124
5130
  "slack",
5125
5131
  "session"
5126
5132
  ]);
5127
- var gitRefSchema = z4.strictObject({
5133
+ var gitRefSchema = z5.strictObject({
5128
5134
  kind: gitRefKindSchema,
5129
- ref: z4.string(),
5130
- title: z4.string().optional(),
5131
- url: z4.string().optional(),
5132
- state: z4.string().optional(),
5133
- createdAt: z4.string().optional()
5135
+ ref: z5.string(),
5136
+ title: z5.string().optional(),
5137
+ url: z5.string().optional(),
5138
+ state: z5.string().optional(),
5139
+ createdAt: z5.string().optional()
5134
5140
  });
5135
- var backlogLinkTypeSchema = z4.enum(["relates-to", "depends-on"]);
5136
- var backlogLinkSchema = z4.strictObject({
5141
+ var backlogLinkTypeSchema = z5.enum(["relates-to", "depends-on"]);
5142
+ var backlogLinkSchema = z5.strictObject({
5137
5143
  type: backlogLinkTypeSchema,
5138
- targetId: z4.number()
5144
+ targetId: z5.number()
5139
5145
  });
5140
- var backlogItemSchema = z4.strictObject({
5141
- id: z4.number(),
5146
+ var backlogItemSchema = z5.strictObject({
5147
+ id: z5.number(),
5142
5148
  type: backlogTypeSchema.default("story"),
5143
- name: z4.string(),
5144
- description: z4.string().optional(),
5145
- acceptanceCriteria: z4.array(z4.string()),
5146
- plan: z4.array(planPhaseSchema).optional(),
5147
- currentPhase: z4.number().optional(),
5148
- starred: z4.boolean().default(false),
5149
+ name: z5.string(),
5150
+ description: z5.string().optional(),
5151
+ acceptanceCriteria: z5.array(z5.string()),
5152
+ plan: z5.array(planPhaseSchema).optional(),
5153
+ currentPhase: z5.number().optional(),
5154
+ starred: z5.boolean().default(false),
5149
5155
  status: backlogStatusSchema,
5150
- comments: z4.array(backlogCommentSchema).optional(),
5151
- subtasks: z4.array(subtaskSchema).optional(),
5152
- links: z4.array(backlogLinkSchema).optional(),
5153
- phaseUsage: z4.array(phaseUsageSchema).optional(),
5154
- phaseSessions: z4.array(phaseSessionSchema).optional(),
5156
+ comments: z5.array(backlogCommentSchema).optional(),
5157
+ subtasks: z5.array(subtaskSchema).optional(),
5158
+ links: z5.array(backlogLinkSchema).optional(),
5159
+ phaseUsage: z5.array(phaseUsageSchema).optional(),
5160
+ phaseSessions: z5.array(phaseSessionSchema).optional(),
5155
5161
  usageTotal: phaseUsageTotalSchema.optional(),
5156
- gitRefs: z4.array(gitRefSchema).optional(),
5157
- origin: z4.string().optional(),
5158
- jiraKey: z4.string().optional(),
5159
- githubIssue: z4.string().optional()
5162
+ gitRefs: z5.array(gitRefSchema).optional(),
5163
+ origin: z5.string().optional(),
5164
+ jiraKey: z5.string().optional(),
5165
+ githubIssue: z5.string().optional()
5160
5166
  });
5161
- var backlogFileSchema = z4.array(backlogItemSchema);
5167
+ var backlogFileSchema = z5.array(backlogItemSchema);
5162
5168
 
5163
5169
  // src/commands/backlog/parseBacklogJsonl.ts
5164
5170
  function parseBacklogJsonl(path106) {
@@ -6525,7 +6531,7 @@ ${issues}`);
6525
6531
  }
6526
6532
 
6527
6533
  // src/commands/backlog/propose/proposedItemSchema.ts
6528
- import { z as z5 } from "zod";
6534
+ import { z as z6 } from "zod";
6529
6535
 
6530
6536
  // src/commands/backlog/buildCommentLines.ts
6531
6537
  function buildCommentLines(comments3) {
@@ -6677,20 +6683,20 @@ function buildPhasePrompt(item, phaseNumber, phase) {
6677
6683
  }
6678
6684
 
6679
6685
  // src/commands/backlog/propose/proposedItemSchema.ts
6680
- var proposedPhaseSchema = z5.strictObject({
6681
- name: z5.string().trim().min(1, "phase name is required").refine(
6686
+ var proposedPhaseSchema = z6.strictObject({
6687
+ name: z6.string().trim().min(1, "phase name is required").refine(
6682
6688
  (name) => name.toLowerCase() !== REVIEW_PHASE_NAME.toLowerCase(),
6683
6689
  `"${REVIEW_PHASE_NAME}" is owned by the runner and appended to every plan automatically \u2014 remove this phase`
6684
6690
  ),
6685
- tasks: z5.array(z5.string().trim().min(1)).min(1, "a phase needs at least one task"),
6686
- manualChecks: z5.array(z5.string().trim().min(1)).default([])
6691
+ tasks: z6.array(z6.string().trim().min(1)).min(1, "a phase needs at least one task"),
6692
+ manualChecks: z6.array(z6.string().trim().min(1)).default([])
6687
6693
  });
6688
- var proposedItemSchema = z5.strictObject({
6689
- name: z5.string().trim().min(1, "name is required"),
6690
- type: z5.enum(["story", "bug"]),
6691
- description: z5.string().optional(),
6692
- acceptanceCriteria: z5.array(z5.string().trim().min(1)).default([]),
6693
- phases: z5.array(proposedPhaseSchema).default([])
6694
+ var proposedItemSchema = z6.strictObject({
6695
+ name: z6.string().trim().min(1, "name is required"),
6696
+ type: z6.enum(["story", "bug"]),
6697
+ description: z6.string().optional(),
6698
+ acceptanceCriteria: z6.array(z6.string().trim().min(1)).default([]),
6699
+ phases: z6.array(proposedPhaseSchema).default([])
6694
6700
  });
6695
6701
 
6696
6702
  // src/commands/backlog/propose/readProposedItem.ts
@@ -8215,18 +8221,18 @@ async function persistPhaseSessionId(itemId2, phaseIdx, claudeSessionId) {
8215
8221
  import { mkdirSync as mkdirSync9, readFileSync as readFileSync19, rmSync as rmSync2, writeFileSync as writeFileSync20 } from "fs";
8216
8222
  import { homedir as homedir11 } from "os";
8217
8223
  import { dirname as dirname18, join as join30 } from "path";
8218
- import { z as z6 } from "zod";
8219
- var activitySchema = z6.object({
8220
- kind: z6.enum(["command", "backlog"]),
8221
- harness: z6.enum(["claude", "codex", "pi"]).optional(),
8222
- name: z6.string().optional(),
8223
- itemId: z6.number().optional(),
8224
- itemName: z6.string().optional(),
8225
- phase: z6.number().optional(),
8226
- phaseName: z6.string().optional(),
8227
- totalPhases: z6.number().optional(),
8228
- claudeSessionId: z6.string().optional(),
8229
- startedAt: z6.number()
8224
+ import { z as z7 } from "zod";
8225
+ var activitySchema = z7.object({
8226
+ kind: z7.enum(["command", "backlog"]),
8227
+ harness: z7.enum(["claude", "codex", "pi"]).optional(),
8228
+ name: z7.string().optional(),
8229
+ itemId: z7.number().optional(),
8230
+ itemName: z7.string().optional(),
8231
+ phase: z7.number().optional(),
8232
+ phaseName: z7.string().optional(),
8233
+ totalPhases: z7.number().optional(),
8234
+ claudeSessionId: z7.string().optional(),
8235
+ startedAt: z7.number()
8230
8236
  });
8231
8237
  function activityPath(sessionId) {
8232
8238
  return join30(homedir11(), ".assist", "activity", `activity-${sessionId}.json`);
@@ -10243,9 +10249,9 @@ async function update(id, options2) {
10243
10249
  import chalk102 from "chalk";
10244
10250
 
10245
10251
  // src/commands/backlog/updatePlan/planUpdateSchema.ts
10246
- import { z as z7 } from "zod";
10247
- var planUpdateSchema = z7.strictObject({
10248
- phases: z7.array(proposedPhaseSchema).min(1, "a plan needs at least one phase")
10252
+ import { z as z8 } from "zod";
10253
+ var planUpdateSchema = z8.strictObject({
10254
+ phases: z8.array(proposedPhaseSchema).min(1, "a plan needs at least one phase")
10249
10255
  });
10250
10256
 
10251
10257
  // src/commands/backlog/updatePlan/readPlanUpdate.ts
@@ -12997,8 +13003,8 @@ var seqConfigHelp = [
12997
13003
  var nodesConfigHelp = [
12998
13004
  {
12999
13005
  key: "sessions.links",
13000
- setter: "assist sessions nodes link <name> http://127.0.0.1:<port> (or --tailscale <host> --port <port>, or --ssh <alias> --port <port>)",
13001
- note: "linked nodes whose sessions merge into this node's web UI, each direct (url, including a Tailscale <host>.<tailnet>.ts.net https url) or over an ssh tunnel (ssh alias, peer port, localPort); each name must match the peer's sessions.nodeName"
13006
+ setter: "assist sessions nodes link <name> --tailscale <host> --port <port> (or a peer <url>)",
13007
+ note: "linked nodes whose sessions merge into this node's web UI, each dialled at its url (a Tailscale https://<host>.<tailnet>.ts.net:<port> url, or any direct url); each name must match the peer's sessions.nodeName"
13002
13008
  },
13003
13009
  {
13004
13010
  key: "sessions.linkVersionCheck",
@@ -15007,21 +15013,8 @@ async function prList(req, res) {
15007
15013
  }
15008
15014
 
15009
15015
  // src/commands/sessions/shared/loadLinkSpecs.ts
15010
- var TUNNEL_PORT_BASE = 43e3;
15011
- function defaultTunnelPort(port) {
15012
- return TUNNEL_PORT_BASE + port % 1e3;
15013
- }
15014
- function toLinkSpec(link3) {
15015
- if ("url" in link3) return { name: link3.name, url: link3.url };
15016
- const localPort = link3.localPort ?? defaultTunnelPort(link3.port);
15017
- return {
15018
- name: link3.name,
15019
- url: `http://127.0.0.1:${localPort}`,
15020
- ssh: { alias: link3.ssh, port: link3.port, localPort }
15021
- };
15022
- }
15023
15016
  function loadLinkSpecs() {
15024
- return (loadConfig().sessions?.links ?? []).map(toLinkSpec);
15017
+ return loadConfig().sessions?.links ?? [];
15025
15018
  }
15026
15019
  function findLinkSpec(name) {
15027
15020
  return loadLinkSpecs().find((spec) => spec.name === name);
@@ -15040,6 +15033,20 @@ import {
15040
15033
  } from "http";
15041
15034
  import { request as httpsRequest } from "https";
15042
15035
 
15036
+ // src/commands/sessions/web/peerTimeoutMs.ts
15037
+ var peerTimeoutMs = 2e4;
15038
+
15039
+ // src/commands/sessions/web/boundUpstream.ts
15040
+ function boundUpstream(upstream, res) {
15041
+ upstream.setTimeout(
15042
+ peerTimeoutMs,
15043
+ () => upstream.destroy(new Error(`no response after ${peerTimeoutMs}ms`))
15044
+ );
15045
+ res.on("close", () => {
15046
+ if (!res.writableFinished) upstream.destroy();
15047
+ });
15048
+ }
15049
+
15043
15050
  // src/commands/sessions/web/isQuietPoll.ts
15044
15051
  var POLLED_PATHS = /* @__PURE__ */ new Set(["/api/git-status"]);
15045
15052
  function isQuietPoll(pathname, status2) {
@@ -15078,6 +15085,7 @@ function forwardToPeer(req, res, node, url) {
15078
15085
  },
15079
15086
  (peer) => relayPeerResponse(peer, res, url.pathname, log2, resolve26)
15080
15087
  );
15088
+ boundUpstream(upstream, res);
15081
15089
  upstream.on("error", (error) => {
15082
15090
  log2(`failed: ${error.message}`);
15083
15091
  if (res.headersSent) res.destroy();
@@ -25275,36 +25283,36 @@ function stringOrUndefined(value) {
25275
25283
  }
25276
25284
 
25277
25285
  // src/commands/sessions/daemon/persistedSessionSchema.ts
25278
- import { z as z8 } from "zod";
25279
- var persistedSessionSchema = z8.object({
25280
- id: z8.string().optional(),
25281
- name: z8.string(),
25282
- title: z8.string().optional(),
25283
- generatedTitle: z8.string().optional(),
25284
- subtitle: z8.string().optional(),
25285
- commandType: z8.enum(["claude", "run", "assist"]),
25286
- harness: z8.enum(["claude", "codex", "pi"]).optional(),
25287
- status: z8.enum(["running", "waiting", "done", "error", "stopped"]).optional(),
25288
- cwd: z8.string(),
25289
- startedAt: z8.number(),
25290
- runningMs: z8.number().optional(),
25291
- claudeSessionId: z8.string().optional(),
25292
- harnessSessionId: z8.string().optional(),
25293
- initialPrompt: z8.string().optional(),
25294
- runName: z8.string().optional(),
25295
- runArgs: z8.array(z8.string()).optional(),
25296
- assistArgs: z8.array(z8.string()).optional(),
25286
+ import { z as z9 } from "zod";
25287
+ var persistedSessionSchema = z9.object({
25288
+ id: z9.string().optional(),
25289
+ name: z9.string(),
25290
+ title: z9.string().optional(),
25291
+ generatedTitle: z9.string().optional(),
25292
+ subtitle: z9.string().optional(),
25293
+ commandType: z9.enum(["claude", "run", "assist"]),
25294
+ harness: z9.enum(["claude", "codex", "pi"]).optional(),
25295
+ status: z9.enum(["running", "waiting", "done", "error", "stopped"]).optional(),
25296
+ cwd: z9.string(),
25297
+ startedAt: z9.number(),
25298
+ runningMs: z9.number().optional(),
25299
+ claudeSessionId: z9.string().optional(),
25300
+ harnessSessionId: z9.string().optional(),
25301
+ initialPrompt: z9.string().optional(),
25302
+ runName: z9.string().optional(),
25303
+ runArgs: z9.array(z9.string()).optional(),
25304
+ assistArgs: z9.array(z9.string()).optional(),
25297
25305
  activity: activitySchema.optional(),
25298
- starred: z8.boolean().optional(),
25299
- watcher: z8.boolean().optional(),
25300
- design: z8.boolean().optional(),
25301
- auto: z8.boolean().optional(),
25302
- autoRun: z8.boolean().optional(),
25303
- autoAdvance: z8.boolean().optional(),
25304
- reviewStarted: z8.boolean().optional(),
25305
- launchedFrom: z8.string().optional(),
25306
- interrupted: z8.object({ reason: z8.string(), at: z8.number() }).optional(),
25307
- undurable: z8.object({ reason: z8.string(), removesTree: z8.boolean().optional() }).optional()
25306
+ starred: z9.boolean().optional(),
25307
+ watcher: z9.boolean().optional(),
25308
+ design: z9.boolean().optional(),
25309
+ auto: z9.boolean().optional(),
25310
+ autoRun: z9.boolean().optional(),
25311
+ autoAdvance: z9.boolean().optional(),
25312
+ reviewStarted: z9.boolean().optional(),
25313
+ launchedFrom: z9.string().optional(),
25314
+ interrupted: z9.object({ reason: z9.string(), at: z9.number() }).optional(),
25315
+ undurable: z9.object({ reason: z9.string(), removesTree: z9.boolean().optional() }).optional()
25308
25316
  });
25309
25317
 
25310
25318
  // src/commands/sessions/daemon/toPersistedSession.ts
@@ -38177,16 +38185,16 @@ function selectPassages(sources, selection) {
38177
38185
  }
38178
38186
 
38179
38187
  // src/commands/transcript/selectionSchema.ts
38180
- import { z as z9 } from "zod";
38181
- var selectionSchema = z9.strictObject({
38182
- keep: z9.array(
38183
- z9.strictObject({
38184
- file: z9.string().trim().min(1, "file is required"),
38185
- from: z9.string().trim().min(1, "from is required"),
38186
- to: z9.string().trim().min(1, "to is required")
38188
+ import { z as z10 } from "zod";
38189
+ var selectionSchema = z10.strictObject({
38190
+ keep: z10.array(
38191
+ z10.strictObject({
38192
+ file: z10.string().trim().min(1, "file is required"),
38193
+ from: z10.string().trim().min(1, "from is required"),
38194
+ to: z10.string().trim().min(1, "to is required")
38187
38195
  })
38188
38196
  ).min(1, "a selection needs at least one keep range"),
38189
- removed: z9.array(z9.string().trim().min(1)).default([])
38197
+ removed: z10.array(z10.string().trim().min(1)).default([])
38190
38198
  });
38191
38199
 
38192
38200
  // src/commands/transcript/widenAudienceInText.ts
@@ -40228,14 +40236,6 @@ function registerRun(program2) {
40228
40236
 
40229
40237
  // src/commands/sessions/nodes/formatLink.ts
40230
40238
  import chalk253 from "chalk";
40231
-
40232
- // src/commands/sessions/daemon/links/describeTarget.ts
40233
- function describeTarget({ url, ssh }) {
40234
- if (!ssh) return `direct ${url}`;
40235
- return `ssh ${ssh.alias}:${ssh.port} via 127.0.0.1:${ssh.localPort}`;
40236
- }
40237
-
40238
- // src/commands/sessions/nodes/formatLink.ts
40239
40239
  var STATE_COLOURS = {
40240
40240
  connected: chalk253.green,
40241
40241
  connecting: chalk253.yellow,
@@ -40247,7 +40247,7 @@ function formatLink(link3) {
40247
40247
  const peer = link3.peerVersion ? ` peer ${link3.peerVersion}` : "";
40248
40248
  const error = link3.error ? `
40249
40249
  ${chalk253.dim(`last error${link3.errorAt ? ` ${link3.errorAt}` : ""}: ${link3.error}`)}` : "";
40250
- return ` ${link3.name} ${colour(link3.state)} ${describeTarget(link3)}${peer}${error}`;
40250
+ return ` ${link3.name} ${colour(link3.state)} ${link3.url}${peer}${error}`;
40251
40251
  }
40252
40252
 
40253
40253
  // src/commands/sessions/daemon/listDaemonPids.ts
@@ -40479,9 +40479,9 @@ function createAutoExit(exit, graceMs = DEFAULT_GRACE_MS) {
40479
40479
  }
40480
40480
 
40481
40481
  // src/commands/sessions/daemon/loadActiveSelection.ts
40482
- import { z as z10 } from "zod";
40482
+ import { z as z11 } from "zod";
40483
40483
  var ACTIVE_FILE = "active.json";
40484
- var activeSelectionSchema = z10.record(z10.string(), z10.string());
40484
+ var activeSelectionSchema = z11.record(z11.string(), z11.string());
40485
40485
  function loadActiveSelection() {
40486
40486
  const parsed = activeSelectionSchema.safeParse(
40487
40487
  loadJson(ACTIVE_FILE)
@@ -44144,10 +44144,10 @@ function restoreAllSessions(spawner, sessions, notify2) {
44144
44144
 
44145
44145
  // src/commands/sessions/daemon/retrySession.ts
44146
44146
  function retrySession(session, clients, onStatusChange) {
44147
- const spawn12 = respawnThunk(session);
44148
- if (!spawn12) return false;
44147
+ const spawn10 = respawnThunk(session);
44148
+ if (!spawn10) return false;
44149
44149
  const respawn = () => {
44150
- respawnSession(session, spawn12, "running", clients, onStatusChange);
44150
+ respawnSession(session, spawn10, "running", clients, onStatusChange);
44151
44151
  daemonLog(`session ${session.id} retried: ${session.name}`);
44152
44152
  };
44153
44153
  const pty2 = session.pty;
@@ -44721,134 +44721,6 @@ async function selfUpdatePeer(url) {
44721
44721
  throw new Error(body.error ?? `self-update responded ${response.status}`);
44722
44722
  }
44723
44723
 
44724
- // src/commands/sessions/daemon/links/SshTunnel.ts
44725
- import { createInterface as createInterface10 } from "readline";
44726
- import { setTimeout as delay2 } from "timers/promises";
44727
-
44728
- // src/commands/sessions/daemon/links/killOrphanOnDaemonExit.ts
44729
- var liveChildren = /* @__PURE__ */ new Set();
44730
- var exitHookInstalled = false;
44731
- function killOrphanOnDaemonExit(child) {
44732
- liveChildren.add(child);
44733
- child.once("exit", () => liveChildren.delete(child));
44734
- if (exitHookInstalled) return;
44735
- exitHookInstalled = true;
44736
- process.once("exit", () => {
44737
- for (const live of liveChildren) live.kill();
44738
- });
44739
- }
44740
-
44741
- // src/commands/sessions/daemon/links/sshTunnelArgs.ts
44742
- function sshTunnelArgs({ alias, port, localPort }) {
44743
- return [
44744
- "-N",
44745
- "-o",
44746
- "BatchMode=yes",
44747
- "-o",
44748
- "ExitOnForwardFailure=yes",
44749
- "-o",
44750
- "ServerAliveInterval=15",
44751
- "-o",
44752
- "ServerAliveCountMax=3",
44753
- "-L",
44754
- `${localPort}:127.0.0.1:${port}`,
44755
- alias
44756
- ];
44757
- }
44758
-
44759
- // src/commands/sessions/daemon/links/SshTunnelIo.ts
44760
- import { spawn as spawn10 } from "child_process";
44761
-
44762
- // src/commands/sessions/shared/portAccepts.ts
44763
- import { connect as connect3 } from "net";
44764
- function portAccepts(port, timeoutMs = 1e3) {
44765
- return new Promise((resolve26) => {
44766
- const socket = connect3({ host: "127.0.0.1", port });
44767
- const settle = (ok) => {
44768
- socket.destroy();
44769
- resolve26(ok);
44770
- };
44771
- socket.setTimeout(timeoutMs, () => settle(false));
44772
- socket.once("connect", () => settle(true));
44773
- socket.once("error", () => settle(false));
44774
- });
44775
- }
44776
-
44777
- // src/commands/sessions/daemon/links/SshTunnelIo.ts
44778
- var defaultSshTunnelIo = {
44779
- spawn: (args) => spawn10("ssh", args, {
44780
- stdio: ["ignore", "ignore", "pipe"],
44781
- windowsHide: true
44782
- }),
44783
- accepts: (port) => portAccepts(port),
44784
- bindTimeoutMs: 15e3,
44785
- pollMs: 200
44786
- };
44787
-
44788
- // src/commands/sessions/daemon/links/SshTunnel.ts
44789
- var SshTunnel = class {
44790
- constructor(name, target, io = defaultSshTunnelIo) {
44791
- this.name = name;
44792
- this.target = target;
44793
- this.io = io;
44794
- }
44795
- child;
44796
- exitReason = "";
44797
- lastStderr = "";
44798
- async ready() {
44799
- if (!this.child) this.start();
44800
- await this.waitBound(this.child);
44801
- }
44802
- dispose() {
44803
- const child = this.child;
44804
- this.child = void 0;
44805
- if (!child) return;
44806
- daemonLog(`link ${this.name} tunnel: stopping ssh`);
44807
- child.kill();
44808
- }
44809
- start() {
44810
- const args = sshTunnelArgs(this.target);
44811
- daemonLog(`link ${this.name} tunnel: spawning ssh ${args.join(" ")}`);
44812
- const child = this.io.spawn(args);
44813
- this.child = child;
44814
- this.lastStderr = "";
44815
- killOrphanOnDaemonExit(child);
44816
- if (child.stderr)
44817
- createInterface10({ input: child.stderr }).on("line", (line) => {
44818
- this.lastStderr = line;
44819
- daemonLog(`link ${this.name} ssh: ${line}`);
44820
- });
44821
- child.once("error", (error) => this.onExit(child, error.message));
44822
- child.once(
44823
- "exit",
44824
- (code, signal) => this.onExit(child, signal ? `signal ${signal}` : `code ${code}`)
44825
- );
44826
- }
44827
- onExit(child, reason4) {
44828
- if (this.child !== child) return;
44829
- this.child = void 0;
44830
- this.exitReason = this.lastStderr ? `${reason4}: ${this.lastStderr}` : reason4;
44831
- daemonLog(`link ${this.name} tunnel: ssh exited (${this.exitReason})`);
44832
- }
44833
- async waitBound(child) {
44834
- const { alias, localPort } = this.target;
44835
- const deadline = Date.now() + this.io.bindTimeoutMs;
44836
- while (Date.now() < deadline) {
44837
- if (this.child !== child)
44838
- throw new Error(`ssh tunnel to ${alias} exited (${this.exitReason})`);
44839
- if (await this.io.accepts(localPort)) return;
44840
- await delay2(this.io.pollMs);
44841
- }
44842
- this.dispose();
44843
- throw new Error(
44844
- `ssh tunnel to ${alias} did not bind 127.0.0.1:${localPort} within ${this.io.bindTimeoutMs / 1e3}s`
44845
- );
44846
- }
44847
- };
44848
-
44849
- // src/commands/sessions/daemon/links/sshTunnelFor.ts
44850
- var sshTunnelFor = (spec) => spec.ssh ? new SshTunnel(spec.name, spec.ssh) : void 0;
44851
-
44852
44724
  // src/commands/sessions/daemon/links/linkDeps.ts
44853
44725
  function linkDeps(options2, callbacks) {
44854
44726
  return {
@@ -44866,7 +44738,6 @@ function linkDeps(options2, callbacks) {
44866
44738
  callbacks.onHistoryChanged
44867
44739
  ),
44868
44740
  transport: options2.transport ?? connectLinkWebSocket,
44869
- tunnel: options2.tunnel ?? sshTunnelFor,
44870
44741
  heal: options2.heal ?? selfUpdatePeer,
44871
44742
  reconnectMs: options2.reconnectMs ?? 3e3,
44872
44743
  blockedRetryMs: options2.blockedRetryMs ?? 6e4,
@@ -44973,7 +44844,6 @@ function createLinkContext(spec, deps2) {
44973
44844
  deps: deps2,
44974
44845
  relay: createRelayState(spec.name, deps2),
44975
44846
  breaker: new LaunchCircuitBreaker(`link ${spec.name} ws`),
44976
- tunnel: deps2.tunnel(spec),
44977
44847
  socket: null,
44978
44848
  greeted: false,
44979
44849
  state: "disconnected",
@@ -44992,7 +44862,6 @@ function describeLink(ctx) {
44992
44862
  return {
44993
44863
  name: ctx.spec.name,
44994
44864
  url: ctx.spec.url,
44995
- ssh: ctx.spec.ssh,
44996
44865
  state: ctx.state,
44997
44866
  error: ctx.blockedMessage ?? ctx.lastError?.message,
44998
44867
  errorAt: ctx.lastError?.at,
@@ -45092,16 +44961,16 @@ function linkHealRefusal(node, peerVersion, healAttempted) {
45092
44961
  }
45093
44962
 
45094
44963
  // src/commands/sessions/daemon/links/scheduleReconnect.ts
45095
- function armReconnect(ctx, delay3) {
44964
+ function armReconnect(ctx, delay2) {
45096
44965
  clearTimeout(ctx.reconnectTimer);
45097
- ctx.reconnectTimer = setTimeout(ctx.connect, delay3);
44966
+ ctx.reconnectTimer = setTimeout(ctx.connect, delay2);
45098
44967
  ctx.reconnectTimer.unref?.();
45099
44968
  }
45100
44969
  function scheduleReconnect2(ctx) {
45101
44970
  if (ctx.disposed) return;
45102
44971
  if (ctx.blockedMessage) return armReconnect(ctx, ctx.deps.blockedRetryMs);
45103
- const delay3 = ctx.breaker.tripped() ? ctx.breaker.remainingMs() : ctx.deps.reconnectMs;
45104
- armReconnect(ctx, delay3);
44972
+ const delay2 = ctx.breaker.tripped() ? ctx.breaker.remainingMs() : ctx.deps.reconnectMs;
44973
+ armReconnect(ctx, delay2);
45105
44974
  }
45106
44975
  function reconnectNow(ctx) {
45107
44976
  ctx.breaker.clear();
@@ -45365,8 +45234,6 @@ async function openLink(ctx) {
45365
45234
  if (ctx.breaker.tripped()) return scheduleReconnect2(ctx);
45366
45235
  if (!ctx.blockedMessage) setLinkState(ctx, "connecting");
45367
45236
  try {
45368
- await ctx.tunnel?.ready();
45369
- if (ctx.disposed) return;
45370
45237
  daemonLog(`link ${ctx.spec.name} ws: connecting to ${ctx.spec.url}`);
45371
45238
  const socket = await ctx.deps.transport(ctx.spec.url, {
45372
45239
  onLine: guardLinkCallback(
@@ -45446,18 +45313,14 @@ var NodeLink = class {
45446
45313
  this.ctx.disposed = true;
45447
45314
  clearTimeout(this.ctx.reconnectTimer);
45448
45315
  disconnectLink(this.ctx);
45449
- this.ctx.tunnel?.dispose();
45450
45316
  }
45451
45317
  };
45452
45318
 
45453
45319
  // src/commands/sessions/daemon/links/reconcileLinks.ts
45454
- function sameTarget(wanted, current) {
45455
- return wanted?.url === current.url && JSON.stringify(wanted.ssh) === JSON.stringify(current.ssh);
45456
- }
45457
45320
  function reconcileLinks(links2, specs, create) {
45458
45321
  const wanted = new Map(specs.map((spec) => [spec.name, spec]));
45459
45322
  for (const [name, link3] of links2) {
45460
- if (sameTarget(wanted.get(name), link3.spec)) {
45323
+ if (wanted.get(name)?.url === link3.spec.url) {
45461
45324
  link3.unlatch();
45462
45325
  continue;
45463
45326
  }
@@ -45467,7 +45330,7 @@ function reconcileLinks(links2, specs, create) {
45467
45330
  }
45468
45331
  for (const spec of specs) {
45469
45332
  if (links2.has(spec.name)) continue;
45470
- daemonLog(`link ${spec.name}: added (${describeTarget(spec)})`);
45333
+ daemonLog(`link ${spec.name}: added (${spec.url})`);
45471
45334
  const link3 = create(spec);
45472
45335
  links2.set(spec.name, link3);
45473
45336
  link3.start();
@@ -45997,7 +45860,7 @@ function exitAfterFlush(code) {
45997
45860
  }
45998
45861
 
45999
45862
  // src/commands/sessions/daemon/handleConnection.ts
46000
- import { createInterface as createInterface11 } from "readline";
45863
+ import { createInterface as createInterface10 } from "readline";
46001
45864
 
46002
45865
  // src/commands/sessions/daemon/handleHelloRequest.ts
46003
45866
  var handleHelloRequest = (client, m, d) => {
@@ -46012,10 +45875,10 @@ var handleHelloRequest = (client, m, d) => {
46012
45875
  };
46013
45876
 
46014
45877
  // src/commands/sessions/daemon/creator.ts
46015
- function creator(isNew, spawn12) {
45878
+ function creator(isNew, spawn10) {
46016
45879
  return (client, m, d) => {
46017
45880
  if (m.links.route(client, d)) return;
46018
- const result = spawn12(m, d);
45881
+ const result = spawn10(m, d);
46019
45882
  if (typeof result !== "string") {
46020
45883
  sendTo(client, { type: "error", message: result.error });
46021
45884
  return;
@@ -46583,7 +46446,7 @@ function handleConnection(socket, manager) {
46583
46446
  };
46584
46447
  manager.addClient(client);
46585
46448
  manager.clients.greet(client);
46586
- const lines2 = createInterface11({ input: socket });
46449
+ const lines2 = createInterface10({ input: socket });
46587
46450
  lines2.on("error", () => {
46588
46451
  });
46589
46452
  lines2.on("line", (line) => {
@@ -46782,37 +46645,6 @@ function failed2(hop, error, remediation) {
46782
46645
  return { hop, ok: false, error, remediation };
46783
46646
  }
46784
46647
 
46785
- // src/commands/sessions/nodes/agentHop.ts
46786
- var ONEPASSWORD_AGENT = '"~/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock"';
46787
- async function agentHop(ssh, probes) {
46788
- const agent = await probes.sshAgent(ssh.alias);
46789
- switch (agent.status) {
46790
- case "ok":
46791
- return passed(
46792
- "agent",
46793
- `${agent.keys} key(s)${agent.socket ? ` via ${agent.socket}` : ""}`
46794
- );
46795
- case "missing":
46796
- return failed2(
46797
- "agent",
46798
- `no IdentityAgent for ${ssh.alias} and SSH_AUTH_SOCK is unset`,
46799
- `IdentityAgent missing \u2014 add \`IdentityAgent ${ONEPASSWORD_AGENT}\` under \`Host ${ssh.alias}\` in ~/.ssh/config`
46800
- );
46801
- case "no-keys":
46802
- return failed2(
46803
- "agent",
46804
- `the agent${agent.socket ? ` at ${agent.socket}` : ""} holds no keys`,
46805
- "add or import an SSH key in 1Password, then allow it in the SSH agent"
46806
- );
46807
- case "unreachable":
46808
- return failed2(
46809
- "agent",
46810
- agent.error,
46811
- "1Password SSH agent not reachable \u2014 open and unlock 1Password and turn on Settings \u2192 Developer \u2192 Use the SSH agent"
46812
- );
46813
- }
46814
- }
46815
-
46816
46648
  // src/commands/sessions/nodes/fetchPeerJson.ts
46817
46649
  var PEER_TIMEOUT_MS = 3e3;
46818
46650
  function peerErrorCode(error) {
@@ -46899,45 +46731,6 @@ function linkStateHop(spec, probes) {
46899
46731
  );
46900
46732
  }
46901
46733
 
46902
- // src/commands/sessions/nodes/sshHop.ts
46903
- var REMEDIATIONS = [
46904
- [
46905
- /connection refused/i,
46906
- (alias) => `Remote Login is off on ${alias} \u2014 on a Mac turn on System Settings \u2192 General \u2192 Sharing \u2192 Remote Login; on Windows run \`Start-Service sshd\` (OpenSSH Server)`
46907
- ],
46908
- [
46909
- /could not resolve hostname/i,
46910
- (alias) => `no \`Host ${alias}\` in ~/.ssh/config and ${alias} does not resolve \u2014 add a Host entry with its HostName`
46911
- ],
46912
- [
46913
- /host key verification failed/i,
46914
- (alias) => `run \`ssh ${alias}\` once to accept its host key`
46915
- ],
46916
- [
46917
- /agent refused operation|sign_and_send_pubkey/i,
46918
- () => "1Password refused to sign \u2014 unlock it and approve the key request"
46919
- ],
46920
- [
46921
- /permission denied/i,
46922
- (alias) => `${alias} rejected every key offered \u2014 add this node's 1Password public key to ~/.ssh/authorized_keys on ${alias} (C:\\ProgramData\\ssh\\administrators_authorized_keys for a Windows administrator)`
46923
- ],
46924
- [
46925
- /timed out/i,
46926
- (alias) => `${alias} did not answer on its ssh port \u2014 is it awake and on this network?`
46927
- ]
46928
- ];
46929
- async function sshHop(ssh, probes) {
46930
- const { code, stderr } = await probes.ssh(ssh.alias);
46931
- if (code === 0) return passed("ssh", `ssh ${ssh.alias} authenticated`);
46932
- const error = stderr || `ssh exited ${code}`;
46933
- const match = REMEDIATIONS.find(([pattern2]) => pattern2.test(error));
46934
- return failed2(
46935
- "ssh",
46936
- error,
46937
- match?.[1](ssh.alias) ?? `run \`ssh -v ${ssh.alias}\` to see where it fails`
46938
- );
46939
- }
46940
-
46941
46734
  // src/commands/sessions/nodes/findTailscalePeer.ts
46942
46735
  function findTailscalePeer(status2, hostname2) {
46943
46736
  const nodes = [
@@ -46988,18 +46781,6 @@ async function tailscaleHop(spec, probes) {
46988
46781
  );
46989
46782
  }
46990
46783
 
46991
- // src/commands/sessions/nodes/tunnelHop.ts
46992
- async function tunnelHop(spec, ssh, probes) {
46993
- const bound = `127.0.0.1:${ssh.localPort}`;
46994
- if (await probes.tunnel(ssh.localPort))
46995
- return passed("tunnel", `${bound} \u2192 ${ssh.alias}:${ssh.port}`);
46996
- return failed2(
46997
- "tunnel",
46998
- `nothing bound on ${bound}`,
46999
- `this node's daemon keeps \`ssh -N -L ${ssh.localPort}:127.0.0.1:${ssh.port} ${ssh.alias}\` up \u2014 run \`assist daemon restart\` and check daemon.log for \`link ${spec.name} tunnel:\` and \`link ${spec.name} ssh:\` lines`
47000
- );
47001
- }
47002
-
47003
46784
  // src/commands/sessions/nodes/tailscaleServeRemediation.ts
47004
46785
  function tailscaleServeRemediation(spec, error) {
47005
46786
  const status2 = error.status;
@@ -47016,8 +46797,6 @@ function isTailscaleLink(spec) {
47016
46797
 
47017
46798
  // src/commands/sessions/nodes/webHop.ts
47018
46799
  function webRemediation(spec, error) {
47019
- if (spec.ssh && error.status === void 0)
47020
- return `nothing listening on ${spec.ssh.port} on ${spec.ssh.alias} \u2014 is project-switch running ${spec.name}'s web server?`;
47021
46800
  const serve = isTailscaleLink(spec) ? tailscaleServeRemediation(spec, error) : void 0;
47022
46801
  if (serve) return serve;
47023
46802
  const { hostname: hostname2, port } = new URL(spec.url);
@@ -47047,7 +46826,7 @@ async function webHop(spec, probes) {
47047
46826
  hop: failed2(
47048
46827
  "web",
47049
46828
  `peer reports nodeName ${health2.nodeName}, link expects ${spec.name}`,
47050
- `relink with \`assist sessions nodes unlink ${spec.name}\` then \`assist sessions nodes link ${health2.nodeName} ${spec.ssh ? `--ssh ${spec.ssh.alias} --port ${spec.ssh.port}` : spec.url}\`, or set sessions.nodeName on the peer`
46829
+ `relink with \`assist sessions nodes unlink ${spec.name}\` then \`assist sessions nodes link ${health2.nodeName} ${spec.url}\`, or set sessions.nodeName on the peer`
47051
46830
  )
47052
46831
  };
47053
46832
  return {
@@ -47073,17 +46852,6 @@ function peerDaemonHop(spec, health2) {
47073
46852
  async function diagnoseLink(spec, probes) {
47074
46853
  const hops = [];
47075
46854
  const done2 = () => ({ ...spec, ok: hops.every((h) => h.ok), hops });
47076
- if (spec.ssh) {
47077
- const { ssh } = spec;
47078
- for (const probe of [
47079
- () => agentHop(ssh, probes),
47080
- () => sshHop(ssh, probes),
47081
- () => tunnelHop(spec, ssh, probes)
47082
- ]) {
47083
- hops.push(await probe());
47084
- if (!hops.at(-1)?.ok) return done2();
47085
- }
47086
- }
47087
46855
  if (isTailscaleLink(spec)) {
47088
46856
  hops.push(await tailscaleHop(spec, probes));
47089
46857
  if (!hops.at(-1)?.ok) return done2();
@@ -47146,77 +46914,6 @@ async function probePeerHello(url) {
47146
46914
  return result;
47147
46915
  }
47148
46916
 
47149
- // src/commands/sessions/nodes/probeSsh.ts
47150
- import { spawn as spawn11 } from "child_process";
47151
- var PROBE_TIMEOUT_MS = 15e3;
47152
- function probeSsh(alias) {
47153
- return new Promise((resolve26) => {
47154
- const child = spawn11(
47155
- "ssh",
47156
- ["-o", "BatchMode=yes", "-o", "ConnectTimeout=5", alias, "exit"],
47157
- { stdio: ["ignore", "ignore", "pipe"], windowsHide: true }
47158
- );
47159
- let stderr = "";
47160
- child.stderr.on("data", (chunk) => {
47161
- stderr += chunk;
47162
- });
47163
- const timer = setTimeout(() => child.kill(), PROBE_TIMEOUT_MS);
47164
- child.once("error", (error) => {
47165
- clearTimeout(timer);
47166
- resolve26({ code: null, stderr: error.message });
47167
- });
47168
- child.once("close", (code) => {
47169
- clearTimeout(timer);
47170
- resolve26({ code, stderr: stderr.trim() });
47171
- });
47172
- });
47173
- }
47174
-
47175
- // src/commands/sessions/nodes/probeSshAgent.ts
47176
- import { spawnSync as spawnSync9 } from "child_process";
47177
- import { homedir as homedir25 } from "os";
47178
- var PROBE_TIMEOUT_MS2 = 5e3;
47179
- function parseIdentityAgent(sshConfig, env = process.env) {
47180
- const line = sshConfig.split(/\r?\n/).find((l) => l.toLowerCase().startsWith("identityagent "));
47181
- const value = line?.slice("identityagent ".length).trim().replace(/^"(.*)"$/, "$1");
47182
- if (!value || value.toLowerCase() === "none") return void 0;
47183
- if (value === "SSH_AUTH_SOCK" || value === "$SSH_AUTH_SOCK")
47184
- return env.SSH_AUTH_SOCK;
47185
- return value.replace(/^~(?=$|[/\\])/, homedir25());
47186
- }
47187
- function configuredAgent(alias) {
47188
- const result = spawnSync9("ssh", ["-G", alias], {
47189
- encoding: "utf8",
47190
- timeout: PROBE_TIMEOUT_MS2,
47191
- windowsHide: true
47192
- });
47193
- return parseIdentityAgent(result.stdout ?? "") ?? process.env.SSH_AUTH_SOCK;
47194
- }
47195
- async function probeSshAgent(alias) {
47196
- const socket = configuredAgent(alias);
47197
- if (!socket && process.platform !== "win32") return { status: "missing" };
47198
- const result = spawnSync9("ssh-add", ["-l"], {
47199
- encoding: "utf8",
47200
- timeout: PROBE_TIMEOUT_MS2,
47201
- windowsHide: true,
47202
- env: socket ? { ...process.env, SSH_AUTH_SOCK: socket } : process.env
47203
- });
47204
- if (result.error)
47205
- return { status: "unreachable", socket, error: result.error.message };
47206
- if (result.status === 0)
47207
- return {
47208
- status: "ok",
47209
- socket,
47210
- keys: result.stdout.trim().split(/\r?\n/).length
47211
- };
47212
- if (result.status === 1) return { status: "no-keys", socket };
47213
- return {
47214
- status: "unreachable",
47215
- socket,
47216
- error: result.stderr.trim() || `ssh-add exited ${result.status}`
47217
- };
47218
- }
47219
-
47220
46917
  // src/commands/sessions/nodes/printReport.ts
47221
46918
  import chalk254 from "chalk";
47222
46919
  function printReport(report2) {
@@ -47245,9 +46942,6 @@ function doctorProbes(live) {
47245
46942
  return {
47246
46943
  health: (url) => fetchPeerJson(url, "/api/health"),
47247
46944
  hello: probePeerHello,
47248
- sshAgent: probeSshAgent,
47249
- ssh: probeSsh,
47250
- tunnel: (localPort) => portAccepts(localPort),
47251
46945
  tailscale: tailscaleStatus,
47252
46946
  linkState: (name) => {
47253
46947
  if (!live) return "no-daemon";
@@ -47329,7 +47023,7 @@ async function nodeLogs(name, options2) {
47329
47023
  }
47330
47024
 
47331
47025
  // src/commands/sessions/nodes/linkNode.ts
47332
- import chalk257 from "chalk";
47026
+ import chalk258 from "chalk";
47333
47027
 
47334
47028
  // src/commands/sessions/nodes/notifyDaemonLinks.ts
47335
47029
  import chalk256 from "chalk";
@@ -47355,11 +47049,10 @@ async function notifyDaemonLinks() {
47355
47049
  }
47356
47050
  }
47357
47051
 
47052
+ // src/commands/sessions/nodes/readLinks.ts
47053
+ import chalk257 from "chalk";
47054
+
47358
47055
  // src/commands/sessions/nodes/writeLinks.ts
47359
- function readLinks() {
47360
- const sessions = loadGlobalConfigRaw().sessions;
47361
- return sessions?.links ?? [];
47362
- }
47363
47056
  async function writeLinks(links2) {
47364
47057
  const config = loadGlobalConfigRaw();
47365
47058
  const sessions = { ...config.sessions };
@@ -47371,6 +47064,20 @@ async function writeLinks(links2) {
47371
47064
  saveGlobalConfig(updated2);
47372
47065
  }
47373
47066
 
47067
+ // src/commands/sessions/nodes/readLinks.ts
47068
+ async function readLinks() {
47069
+ const sessions = loadGlobalConfigRaw().sessions;
47070
+ const stored = sessions?.links ?? [];
47071
+ const links2 = stored.filter((link3) => !!link3.url);
47072
+ if (links2.length === stored.length) return links2;
47073
+ await writeLinks(links2);
47074
+ const dropped = stored.filter((link3) => !link3.url).map((link3) => link3.name);
47075
+ console.log(
47076
+ chalk257.yellow(`Removed ${dropped.join(", ")}: ${SSH_LINK_RETIRED}`)
47077
+ );
47078
+ return links2;
47079
+ }
47080
+
47374
47081
  // src/commands/sessions/nodes/tailscaleLinkUrl.ts
47375
47082
  async function localTailnetSuffix() {
47376
47083
  return tailnetSuffix(await tailscaleStatus());
@@ -47399,82 +47106,54 @@ function parseLinkUrl(url) {
47399
47106
  }
47400
47107
 
47401
47108
  // src/commands/sessions/nodes/buildLink.ts
47402
- async function buildLink(name, url, options2, others, resolveTailnetSuffix) {
47403
- if ([url, options2.ssh, options2.tailscale].filter(Boolean).length > 1)
47404
- throw new Error(
47405
- "pass only one of <url>, --tailscale <host> or --ssh <alias>"
47406
- );
47109
+ async function buildLink(name, url, options2, resolveTailnetSuffix) {
47110
+ if (url && options2.tailscale)
47111
+ throw new Error("pass only one of <url> or --tailscale <host>");
47407
47112
  if (url) return { name, url: parseLinkUrl(url) };
47408
- if (options2.tailscale) {
47409
- const port2 = parsePort("--port", options2.port);
47410
- return {
47411
- name,
47412
- url: await tailscaleLinkUrl(
47413
- options2.tailscale,
47414
- port2,
47415
- resolveTailnetSuffix
47416
- )
47417
- };
47418
- }
47419
- if (!options2.ssh)
47113
+ if (!options2.tailscale)
47420
47114
  throw new Error(
47421
- "pass the peer's <url>, --tailscale <host> --port <port> or --ssh <alias> --port <port>"
47115
+ "pass the peer's <url> or --tailscale <host> --port <port>"
47422
47116
  );
47423
47117
  const port = parsePort("--port", options2.port);
47424
- const localPort = options2.localPort ? parsePort("--local-port", options2.localPort) : freeTunnelPort(port, others);
47425
- return { name, ssh: options2.ssh, port, localPort };
47426
- }
47427
- function freeTunnelPort(port, others) {
47428
- const taken = new Set(others.map((link3) => toLinkSpec(link3).ssh?.localPort));
47429
- let candidate = defaultTunnelPort(port);
47430
- while (taken.has(candidate)) candidate++;
47431
- return candidate;
47118
+ return {
47119
+ name,
47120
+ url: await tailscaleLinkUrl(options2.tailscale, port, resolveTailnetSuffix)
47121
+ };
47432
47122
  }
47433
47123
 
47434
47124
  // src/commands/sessions/nodes/linkNode.ts
47435
47125
  async function linkNode(name, url, options2) {
47436
47126
  if (name.includes(":")) throw new Error("node names cannot contain ':'");
47127
+ const others = (await readLinks()).filter((link4) => link4.name !== name);
47437
47128
  if (name === resolveNodeName())
47438
47129
  throw new Error(`${name} is this node; link to a different node`);
47439
- const others = readLinks().filter((link4) => link4.name !== name);
47440
- const link3 = await buildLink(name, url, options2, others);
47130
+ const link3 = await buildLink(name, url, options2);
47441
47131
  await writeLinks([...others, link3]);
47442
- console.log(
47443
- chalk257.green(`Linked ${name} (${describeTarget(toLinkSpec(link3))})`)
47444
- );
47132
+ console.log(chalk258.green(`Linked ${name} (${link3.url})`));
47445
47133
  await notifyDaemonLinks();
47446
47134
  }
47447
47135
 
47448
47136
  // src/commands/sessions/nodes/registerLinkCommand.ts
47449
47137
  function registerLinkCommand(nodes) {
47450
47138
  nodes.command("link <name> [url]").description(
47451
- "Link a peer node by its web server URL, by its Tailscale name with --tailscale and --port, or over ssh with --ssh and --port (name must match the peer's sessions.nodeName)"
47139
+ "Link a peer node by its web server URL or by its Tailscale name with --tailscale and --port (name must match the peer's sessions.nodeName)"
47452
47140
  ).option(
47453
47141
  "--tailscale <host>",
47454
47142
  "Dial the peer at https://<host>.<tailnet>.ts.net:<port> (the peer runs tailscale serve for its web server port)"
47455
- ).option(
47456
- "--ssh <alias>",
47457
- "Tunnel to the peer through this ~/.ssh/config alias"
47458
- ).option(
47459
- "--port <port>",
47460
- "The peer's web server port (with --tailscale or --ssh)"
47461
- ).option(
47462
- "--local-port <port>",
47463
- "Local end of the ssh tunnel (default: 43000 + port % 1000, bumped past other links)"
47464
- ).action(linkNode);
47143
+ ).option("--port <port>", "The peer's web server port (with --tailscale)").action(linkNode);
47465
47144
  }
47466
47145
 
47467
47146
  // src/commands/sessions/nodes/unlinkNode.ts
47468
- import chalk258 from "chalk";
47147
+ import chalk259 from "chalk";
47469
47148
  async function unlinkNode(name) {
47470
- const links2 = readLinks();
47149
+ const links2 = await readLinks();
47471
47150
  const remaining = links2.filter((link3) => link3.name !== name);
47472
47151
  if (remaining.length === links2.length) {
47473
- console.log(chalk258.yellow(`No link named ${name}`));
47152
+ console.log(chalk259.yellow(`No link named ${name}`));
47474
47153
  return;
47475
47154
  }
47476
47155
  await writeLinks(remaining);
47477
- console.log(chalk258.green(`Unlinked ${name}`));
47156
+ console.log(chalk259.green(`Unlinked ${name}`));
47478
47157
  await notifyDaemonLinks();
47479
47158
  }
47480
47159
 
@@ -47495,14 +47174,14 @@ function registerNodes(sessions) {
47495
47174
  }
47496
47175
 
47497
47176
  // src/commands/sessions/daemon/requestSessionOutput.ts
47498
- import { createInterface as createInterface12 } from "readline";
47177
+ import { createInterface as createInterface11 } from "readline";
47499
47178
  var OUTPUT_TIMEOUT_MS = 5e3;
47500
47179
  async function requestSessionOutput(target) {
47501
47180
  const socket = await connectToDaemon().catch(() => {
47502
47181
  throw new Error("No sessions daemon is running");
47503
47182
  });
47504
47183
  try {
47505
- const lines2 = createInterface12({ input: socket });
47184
+ const lines2 = createInterface11({ input: socket });
47506
47185
  lines2.on("error", () => {
47507
47186
  });
47508
47187
  const reply2 = readDaemonMessage(
@@ -47605,7 +47284,7 @@ async function renameSession(title) {
47605
47284
 
47606
47285
  // src/commands/sessions/summarise.ts
47607
47286
  import * as fs58 from "fs";
47608
- import chalk259 from "chalk";
47287
+ import chalk260 from "chalk";
47609
47288
 
47610
47289
  // src/commands/sessions/summarise/shared.ts
47611
47290
  import * as fs57 from "fs";
@@ -47664,22 +47343,22 @@ ${firstMessage}`);
47664
47343
  async function summarise2(options2) {
47665
47344
  const files = await discoverSessionFiles();
47666
47345
  if (files.length === 0) {
47667
- console.log(chalk259.yellow("No sessions found."));
47346
+ console.log(chalk260.yellow("No sessions found."));
47668
47347
  return;
47669
47348
  }
47670
47349
  const toProcess = selectCandidates(files, options2);
47671
47350
  if (toProcess.length === 0) {
47672
- console.log(chalk259.green("All sessions already summarised."));
47351
+ console.log(chalk260.green("All sessions already summarised."));
47673
47352
  return;
47674
47353
  }
47675
47354
  console.log(
47676
- chalk259.cyan(
47355
+ chalk260.cyan(
47677
47356
  `Summarising ${toProcess.length} session(s) (${files.length} total)\u2026`
47678
47357
  )
47679
47358
  );
47680
47359
  const { succeeded, failed: failed3 } = processSessions(toProcess);
47681
47360
  console.log(
47682
- chalk259.green(`Done: ${succeeded} summarised`) + (failed3 > 0 ? chalk259.yellow(`, ${failed3} skipped`) : "")
47361
+ chalk260.green(`Done: ${succeeded} summarised`) + (failed3 > 0 ? chalk260.yellow(`, ${failed3} skipped`) : "")
47683
47362
  );
47684
47363
  }
47685
47364
  function selectCandidates(files, options2) {
@@ -47699,16 +47378,16 @@ function processSessions(files) {
47699
47378
  let failed3 = 0;
47700
47379
  for (let i = 0; i < files.length; i++) {
47701
47380
  const file = files[i];
47702
- process.stdout.write(chalk259.dim(` [${i + 1}/${files.length}] `));
47381
+ process.stdout.write(chalk260.dim(` [${i + 1}/${files.length}] `));
47703
47382
  const summary = summariseSession(file);
47704
47383
  if (summary) {
47705
47384
  writeSummary(file, summary);
47706
47385
  succeeded++;
47707
- process.stdout.write(`${chalk259.green("\u2713")} ${summary}
47386
+ process.stdout.write(`${chalk260.green("\u2713")} ${summary}
47708
47387
  `);
47709
47388
  } else {
47710
47389
  failed3++;
47711
- process.stdout.write(` ${chalk259.yellow("skip")}
47390
+ process.stdout.write(` ${chalk260.yellow("skip")}
47712
47391
  `);
47713
47392
  }
47714
47393
  }
@@ -47738,10 +47417,10 @@ function registerSessions(program2) {
47738
47417
  }
47739
47418
 
47740
47419
  // src/reportCliError.ts
47741
- import chalk260 from "chalk";
47420
+ import chalk261 from "chalk";
47742
47421
  function reportCliError(error) {
47743
47422
  if (error instanceof InvalidItemIdError || error instanceof AmbiguousRepoConfigError || error instanceof UnknownRepoConfigError || error instanceof MissingRunCwdError || error instanceof MiroExtractError) {
47744
- console.error(chalk260.red(error.message));
47423
+ console.error(chalk261.red(error.message));
47745
47424
  } else {
47746
47425
  console.error(error);
47747
47426
  }