@staff0rd/assist 0.755.2 → 0.757.0

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.755.2",
9
+ version: "0.757.0",
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);
@@ -25275,36 +25268,36 @@ function stringOrUndefined(value) {
25275
25268
  }
25276
25269
 
25277
25270
  // 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(),
25271
+ import { z as z9 } from "zod";
25272
+ var persistedSessionSchema = z9.object({
25273
+ id: z9.string().optional(),
25274
+ name: z9.string(),
25275
+ title: z9.string().optional(),
25276
+ generatedTitle: z9.string().optional(),
25277
+ subtitle: z9.string().optional(),
25278
+ commandType: z9.enum(["claude", "run", "assist"]),
25279
+ harness: z9.enum(["claude", "codex", "pi"]).optional(),
25280
+ status: z9.enum(["running", "waiting", "done", "error", "stopped"]).optional(),
25281
+ cwd: z9.string(),
25282
+ startedAt: z9.number(),
25283
+ runningMs: z9.number().optional(),
25284
+ claudeSessionId: z9.string().optional(),
25285
+ harnessSessionId: z9.string().optional(),
25286
+ initialPrompt: z9.string().optional(),
25287
+ runName: z9.string().optional(),
25288
+ runArgs: z9.array(z9.string()).optional(),
25289
+ assistArgs: z9.array(z9.string()).optional(),
25297
25290
  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()
25291
+ starred: z9.boolean().optional(),
25292
+ watcher: z9.boolean().optional(),
25293
+ design: z9.boolean().optional(),
25294
+ auto: z9.boolean().optional(),
25295
+ autoRun: z9.boolean().optional(),
25296
+ autoAdvance: z9.boolean().optional(),
25297
+ reviewStarted: z9.boolean().optional(),
25298
+ launchedFrom: z9.string().optional(),
25299
+ interrupted: z9.object({ reason: z9.string(), at: z9.number() }).optional(),
25300
+ undurable: z9.object({ reason: z9.string(), removesTree: z9.boolean().optional() }).optional()
25308
25301
  });
25309
25302
 
25310
25303
  // src/commands/sessions/daemon/toPersistedSession.ts
@@ -38177,16 +38170,16 @@ function selectPassages(sources, selection) {
38177
38170
  }
38178
38171
 
38179
38172
  // 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")
38173
+ import { z as z10 } from "zod";
38174
+ var selectionSchema = z10.strictObject({
38175
+ keep: z10.array(
38176
+ z10.strictObject({
38177
+ file: z10.string().trim().min(1, "file is required"),
38178
+ from: z10.string().trim().min(1, "from is required"),
38179
+ to: z10.string().trim().min(1, "to is required")
38187
38180
  })
38188
38181
  ).min(1, "a selection needs at least one keep range"),
38189
- removed: z9.array(z9.string().trim().min(1)).default([])
38182
+ removed: z10.array(z10.string().trim().min(1)).default([])
38190
38183
  });
38191
38184
 
38192
38185
  // src/commands/transcript/widenAudienceInText.ts
@@ -40228,14 +40221,6 @@ function registerRun(program2) {
40228
40221
 
40229
40222
  // src/commands/sessions/nodes/formatLink.ts
40230
40223
  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
40224
  var STATE_COLOURS = {
40240
40225
  connected: chalk253.green,
40241
40226
  connecting: chalk253.yellow,
@@ -40247,7 +40232,7 @@ function formatLink(link3) {
40247
40232
  const peer = link3.peerVersion ? ` peer ${link3.peerVersion}` : "";
40248
40233
  const error = link3.error ? `
40249
40234
  ${chalk253.dim(`last error${link3.errorAt ? ` ${link3.errorAt}` : ""}: ${link3.error}`)}` : "";
40250
- return ` ${link3.name} ${colour(link3.state)} ${describeTarget(link3)}${peer}${error}`;
40235
+ return ` ${link3.name} ${colour(link3.state)} ${link3.url}${peer}${error}`;
40251
40236
  }
40252
40237
 
40253
40238
  // src/commands/sessions/daemon/listDaemonPids.ts
@@ -40479,9 +40464,9 @@ function createAutoExit(exit, graceMs = DEFAULT_GRACE_MS) {
40479
40464
  }
40480
40465
 
40481
40466
  // src/commands/sessions/daemon/loadActiveSelection.ts
40482
- import { z as z10 } from "zod";
40467
+ import { z as z11 } from "zod";
40483
40468
  var ACTIVE_FILE = "active.json";
40484
- var activeSelectionSchema = z10.record(z10.string(), z10.string());
40469
+ var activeSelectionSchema = z11.record(z11.string(), z11.string());
40485
40470
  function loadActiveSelection() {
40486
40471
  const parsed = activeSelectionSchema.safeParse(
40487
40472
  loadJson(ACTIVE_FILE)
@@ -44144,10 +44129,10 @@ function restoreAllSessions(spawner, sessions, notify2) {
44144
44129
 
44145
44130
  // src/commands/sessions/daemon/retrySession.ts
44146
44131
  function retrySession(session, clients, onStatusChange) {
44147
- const spawn12 = respawnThunk(session);
44148
- if (!spawn12) return false;
44132
+ const spawn10 = respawnThunk(session);
44133
+ if (!spawn10) return false;
44149
44134
  const respawn = () => {
44150
- respawnSession(session, spawn12, "running", clients, onStatusChange);
44135
+ respawnSession(session, spawn10, "running", clients, onStatusChange);
44151
44136
  daemonLog(`session ${session.id} retried: ${session.name}`);
44152
44137
  };
44153
44138
  const pty2 = session.pty;
@@ -44721,134 +44706,6 @@ async function selfUpdatePeer(url) {
44721
44706
  throw new Error(body.error ?? `self-update responded ${response.status}`);
44722
44707
  }
44723
44708
 
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
44709
  // src/commands/sessions/daemon/links/linkDeps.ts
44853
44710
  function linkDeps(options2, callbacks) {
44854
44711
  return {
@@ -44866,7 +44723,6 @@ function linkDeps(options2, callbacks) {
44866
44723
  callbacks.onHistoryChanged
44867
44724
  ),
44868
44725
  transport: options2.transport ?? connectLinkWebSocket,
44869
- tunnel: options2.tunnel ?? sshTunnelFor,
44870
44726
  heal: options2.heal ?? selfUpdatePeer,
44871
44727
  reconnectMs: options2.reconnectMs ?? 3e3,
44872
44728
  blockedRetryMs: options2.blockedRetryMs ?? 6e4,
@@ -44973,7 +44829,6 @@ function createLinkContext(spec, deps2) {
44973
44829
  deps: deps2,
44974
44830
  relay: createRelayState(spec.name, deps2),
44975
44831
  breaker: new LaunchCircuitBreaker(`link ${spec.name} ws`),
44976
- tunnel: deps2.tunnel(spec),
44977
44832
  socket: null,
44978
44833
  greeted: false,
44979
44834
  state: "disconnected",
@@ -44992,7 +44847,6 @@ function describeLink(ctx) {
44992
44847
  return {
44993
44848
  name: ctx.spec.name,
44994
44849
  url: ctx.spec.url,
44995
- ssh: ctx.spec.ssh,
44996
44850
  state: ctx.state,
44997
44851
  error: ctx.blockedMessage ?? ctx.lastError?.message,
44998
44852
  errorAt: ctx.lastError?.at,
@@ -45092,16 +44946,16 @@ function linkHealRefusal(node, peerVersion, healAttempted) {
45092
44946
  }
45093
44947
 
45094
44948
  // src/commands/sessions/daemon/links/scheduleReconnect.ts
45095
- function armReconnect(ctx, delay3) {
44949
+ function armReconnect(ctx, delay2) {
45096
44950
  clearTimeout(ctx.reconnectTimer);
45097
- ctx.reconnectTimer = setTimeout(ctx.connect, delay3);
44951
+ ctx.reconnectTimer = setTimeout(ctx.connect, delay2);
45098
44952
  ctx.reconnectTimer.unref?.();
45099
44953
  }
45100
44954
  function scheduleReconnect2(ctx) {
45101
44955
  if (ctx.disposed) return;
45102
44956
  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);
44957
+ const delay2 = ctx.breaker.tripped() ? ctx.breaker.remainingMs() : ctx.deps.reconnectMs;
44958
+ armReconnect(ctx, delay2);
45105
44959
  }
45106
44960
  function reconnectNow(ctx) {
45107
44961
  ctx.breaker.clear();
@@ -45365,8 +45219,6 @@ async function openLink(ctx) {
45365
45219
  if (ctx.breaker.tripped()) return scheduleReconnect2(ctx);
45366
45220
  if (!ctx.blockedMessage) setLinkState(ctx, "connecting");
45367
45221
  try {
45368
- await ctx.tunnel?.ready();
45369
- if (ctx.disposed) return;
45370
45222
  daemonLog(`link ${ctx.spec.name} ws: connecting to ${ctx.spec.url}`);
45371
45223
  const socket = await ctx.deps.transport(ctx.spec.url, {
45372
45224
  onLine: guardLinkCallback(
@@ -45446,18 +45298,14 @@ var NodeLink = class {
45446
45298
  this.ctx.disposed = true;
45447
45299
  clearTimeout(this.ctx.reconnectTimer);
45448
45300
  disconnectLink(this.ctx);
45449
- this.ctx.tunnel?.dispose();
45450
45301
  }
45451
45302
  };
45452
45303
 
45453
45304
  // 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
45305
  function reconcileLinks(links2, specs, create) {
45458
45306
  const wanted = new Map(specs.map((spec) => [spec.name, spec]));
45459
45307
  for (const [name, link3] of links2) {
45460
- if (sameTarget(wanted.get(name), link3.spec)) {
45308
+ if (wanted.get(name)?.url === link3.spec.url) {
45461
45309
  link3.unlatch();
45462
45310
  continue;
45463
45311
  }
@@ -45467,7 +45315,7 @@ function reconcileLinks(links2, specs, create) {
45467
45315
  }
45468
45316
  for (const spec of specs) {
45469
45317
  if (links2.has(spec.name)) continue;
45470
- daemonLog(`link ${spec.name}: added (${describeTarget(spec)})`);
45318
+ daemonLog(`link ${spec.name}: added (${spec.url})`);
45471
45319
  const link3 = create(spec);
45472
45320
  links2.set(spec.name, link3);
45473
45321
  link3.start();
@@ -45997,7 +45845,7 @@ function exitAfterFlush(code) {
45997
45845
  }
45998
45846
 
45999
45847
  // src/commands/sessions/daemon/handleConnection.ts
46000
- import { createInterface as createInterface11 } from "readline";
45848
+ import { createInterface as createInterface10 } from "readline";
46001
45849
 
46002
45850
  // src/commands/sessions/daemon/handleHelloRequest.ts
46003
45851
  var handleHelloRequest = (client, m, d) => {
@@ -46012,10 +45860,10 @@ var handleHelloRequest = (client, m, d) => {
46012
45860
  };
46013
45861
 
46014
45862
  // src/commands/sessions/daemon/creator.ts
46015
- function creator(isNew, spawn12) {
45863
+ function creator(isNew, spawn10) {
46016
45864
  return (client, m, d) => {
46017
45865
  if (m.links.route(client, d)) return;
46018
- const result = spawn12(m, d);
45866
+ const result = spawn10(m, d);
46019
45867
  if (typeof result !== "string") {
46020
45868
  sendTo(client, { type: "error", message: result.error });
46021
45869
  return;
@@ -46583,7 +46431,7 @@ function handleConnection(socket, manager) {
46583
46431
  };
46584
46432
  manager.addClient(client);
46585
46433
  manager.clients.greet(client);
46586
- const lines2 = createInterface11({ input: socket });
46434
+ const lines2 = createInterface10({ input: socket });
46587
46435
  lines2.on("error", () => {
46588
46436
  });
46589
46437
  lines2.on("line", (line) => {
@@ -46782,37 +46630,6 @@ function failed2(hop, error, remediation) {
46782
46630
  return { hop, ok: false, error, remediation };
46783
46631
  }
46784
46632
 
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
46633
  // src/commands/sessions/nodes/fetchPeerJson.ts
46817
46634
  var PEER_TIMEOUT_MS = 3e3;
46818
46635
  function peerErrorCode(error) {
@@ -46899,45 +46716,6 @@ function linkStateHop(spec, probes) {
46899
46716
  );
46900
46717
  }
46901
46718
 
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
46719
  // src/commands/sessions/nodes/findTailscalePeer.ts
46942
46720
  function findTailscalePeer(status2, hostname2) {
46943
46721
  const nodes = [
@@ -46988,18 +46766,6 @@ async function tailscaleHop(spec, probes) {
46988
46766
  );
46989
46767
  }
46990
46768
 
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
46769
  // src/commands/sessions/nodes/tailscaleServeRemediation.ts
47004
46770
  function tailscaleServeRemediation(spec, error) {
47005
46771
  const status2 = error.status;
@@ -47016,8 +46782,6 @@ function isTailscaleLink(spec) {
47016
46782
 
47017
46783
  // src/commands/sessions/nodes/webHop.ts
47018
46784
  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
46785
  const serve = isTailscaleLink(spec) ? tailscaleServeRemediation(spec, error) : void 0;
47022
46786
  if (serve) return serve;
47023
46787
  const { hostname: hostname2, port } = new URL(spec.url);
@@ -47047,7 +46811,7 @@ async function webHop(spec, probes) {
47047
46811
  hop: failed2(
47048
46812
  "web",
47049
46813
  `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`
46814
+ `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
46815
  )
47052
46816
  };
47053
46817
  return {
@@ -47073,17 +46837,6 @@ function peerDaemonHop(spec, health2) {
47073
46837
  async function diagnoseLink(spec, probes) {
47074
46838
  const hops = [];
47075
46839
  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
46840
  if (isTailscaleLink(spec)) {
47088
46841
  hops.push(await tailscaleHop(spec, probes));
47089
46842
  if (!hops.at(-1)?.ok) return done2();
@@ -47146,77 +46899,6 @@ async function probePeerHello(url) {
47146
46899
  return result;
47147
46900
  }
47148
46901
 
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
46902
  // src/commands/sessions/nodes/printReport.ts
47221
46903
  import chalk254 from "chalk";
47222
46904
  function printReport(report2) {
@@ -47245,9 +46927,6 @@ function doctorProbes(live) {
47245
46927
  return {
47246
46928
  health: (url) => fetchPeerJson(url, "/api/health"),
47247
46929
  hello: probePeerHello,
47248
- sshAgent: probeSshAgent,
47249
- ssh: probeSsh,
47250
- tunnel: (localPort) => portAccepts(localPort),
47251
46930
  tailscale: tailscaleStatus,
47252
46931
  linkState: (name) => {
47253
46932
  if (!live) return "no-daemon";
@@ -47329,7 +47008,7 @@ async function nodeLogs(name, options2) {
47329
47008
  }
47330
47009
 
47331
47010
  // src/commands/sessions/nodes/linkNode.ts
47332
- import chalk257 from "chalk";
47011
+ import chalk258 from "chalk";
47333
47012
 
47334
47013
  // src/commands/sessions/nodes/notifyDaemonLinks.ts
47335
47014
  import chalk256 from "chalk";
@@ -47355,11 +47034,10 @@ async function notifyDaemonLinks() {
47355
47034
  }
47356
47035
  }
47357
47036
 
47037
+ // src/commands/sessions/nodes/readLinks.ts
47038
+ import chalk257 from "chalk";
47039
+
47358
47040
  // src/commands/sessions/nodes/writeLinks.ts
47359
- function readLinks() {
47360
- const sessions = loadGlobalConfigRaw().sessions;
47361
- return sessions?.links ?? [];
47362
- }
47363
47041
  async function writeLinks(links2) {
47364
47042
  const config = loadGlobalConfigRaw();
47365
47043
  const sessions = { ...config.sessions };
@@ -47371,6 +47049,20 @@ async function writeLinks(links2) {
47371
47049
  saveGlobalConfig(updated2);
47372
47050
  }
47373
47051
 
47052
+ // src/commands/sessions/nodes/readLinks.ts
47053
+ async function readLinks() {
47054
+ const sessions = loadGlobalConfigRaw().sessions;
47055
+ const stored = sessions?.links ?? [];
47056
+ const links2 = stored.filter((link3) => !!link3.url);
47057
+ if (links2.length === stored.length) return links2;
47058
+ await writeLinks(links2);
47059
+ const dropped = stored.filter((link3) => !link3.url).map((link3) => link3.name);
47060
+ console.log(
47061
+ chalk257.yellow(`Removed ${dropped.join(", ")}: ${SSH_LINK_RETIRED}`)
47062
+ );
47063
+ return links2;
47064
+ }
47065
+
47374
47066
  // src/commands/sessions/nodes/tailscaleLinkUrl.ts
47375
47067
  async function localTailnetSuffix() {
47376
47068
  return tailnetSuffix(await tailscaleStatus());
@@ -47399,82 +47091,54 @@ function parseLinkUrl(url) {
47399
47091
  }
47400
47092
 
47401
47093
  // 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
- );
47094
+ async function buildLink(name, url, options2, resolveTailnetSuffix) {
47095
+ if (url && options2.tailscale)
47096
+ throw new Error("pass only one of <url> or --tailscale <host>");
47407
47097
  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)
47098
+ if (!options2.tailscale)
47420
47099
  throw new Error(
47421
- "pass the peer's <url>, --tailscale <host> --port <port> or --ssh <alias> --port <port>"
47100
+ "pass the peer's <url> or --tailscale <host> --port <port>"
47422
47101
  );
47423
47102
  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;
47103
+ return {
47104
+ name,
47105
+ url: await tailscaleLinkUrl(options2.tailscale, port, resolveTailnetSuffix)
47106
+ };
47432
47107
  }
47433
47108
 
47434
47109
  // src/commands/sessions/nodes/linkNode.ts
47435
47110
  async function linkNode(name, url, options2) {
47436
47111
  if (name.includes(":")) throw new Error("node names cannot contain ':'");
47112
+ const others = (await readLinks()).filter((link4) => link4.name !== name);
47437
47113
  if (name === resolveNodeName())
47438
47114
  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);
47115
+ const link3 = await buildLink(name, url, options2);
47441
47116
  await writeLinks([...others, link3]);
47442
- console.log(
47443
- chalk257.green(`Linked ${name} (${describeTarget(toLinkSpec(link3))})`)
47444
- );
47117
+ console.log(chalk258.green(`Linked ${name} (${link3.url})`));
47445
47118
  await notifyDaemonLinks();
47446
47119
  }
47447
47120
 
47448
47121
  // src/commands/sessions/nodes/registerLinkCommand.ts
47449
47122
  function registerLinkCommand(nodes) {
47450
47123
  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)"
47124
+ "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
47125
  ).option(
47453
47126
  "--tailscale <host>",
47454
47127
  "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);
47128
+ ).option("--port <port>", "The peer's web server port (with --tailscale)").action(linkNode);
47465
47129
  }
47466
47130
 
47467
47131
  // src/commands/sessions/nodes/unlinkNode.ts
47468
- import chalk258 from "chalk";
47132
+ import chalk259 from "chalk";
47469
47133
  async function unlinkNode(name) {
47470
- const links2 = readLinks();
47134
+ const links2 = await readLinks();
47471
47135
  const remaining = links2.filter((link3) => link3.name !== name);
47472
47136
  if (remaining.length === links2.length) {
47473
- console.log(chalk258.yellow(`No link named ${name}`));
47137
+ console.log(chalk259.yellow(`No link named ${name}`));
47474
47138
  return;
47475
47139
  }
47476
47140
  await writeLinks(remaining);
47477
- console.log(chalk258.green(`Unlinked ${name}`));
47141
+ console.log(chalk259.green(`Unlinked ${name}`));
47478
47142
  await notifyDaemonLinks();
47479
47143
  }
47480
47144
 
@@ -47495,14 +47159,14 @@ function registerNodes(sessions) {
47495
47159
  }
47496
47160
 
47497
47161
  // src/commands/sessions/daemon/requestSessionOutput.ts
47498
- import { createInterface as createInterface12 } from "readline";
47162
+ import { createInterface as createInterface11 } from "readline";
47499
47163
  var OUTPUT_TIMEOUT_MS = 5e3;
47500
47164
  async function requestSessionOutput(target) {
47501
47165
  const socket = await connectToDaemon().catch(() => {
47502
47166
  throw new Error("No sessions daemon is running");
47503
47167
  });
47504
47168
  try {
47505
- const lines2 = createInterface12({ input: socket });
47169
+ const lines2 = createInterface11({ input: socket });
47506
47170
  lines2.on("error", () => {
47507
47171
  });
47508
47172
  const reply2 = readDaemonMessage(
@@ -47605,7 +47269,7 @@ async function renameSession(title) {
47605
47269
 
47606
47270
  // src/commands/sessions/summarise.ts
47607
47271
  import * as fs58 from "fs";
47608
- import chalk259 from "chalk";
47272
+ import chalk260 from "chalk";
47609
47273
 
47610
47274
  // src/commands/sessions/summarise/shared.ts
47611
47275
  import * as fs57 from "fs";
@@ -47664,22 +47328,22 @@ ${firstMessage}`);
47664
47328
  async function summarise2(options2) {
47665
47329
  const files = await discoverSessionFiles();
47666
47330
  if (files.length === 0) {
47667
- console.log(chalk259.yellow("No sessions found."));
47331
+ console.log(chalk260.yellow("No sessions found."));
47668
47332
  return;
47669
47333
  }
47670
47334
  const toProcess = selectCandidates(files, options2);
47671
47335
  if (toProcess.length === 0) {
47672
- console.log(chalk259.green("All sessions already summarised."));
47336
+ console.log(chalk260.green("All sessions already summarised."));
47673
47337
  return;
47674
47338
  }
47675
47339
  console.log(
47676
- chalk259.cyan(
47340
+ chalk260.cyan(
47677
47341
  `Summarising ${toProcess.length} session(s) (${files.length} total)\u2026`
47678
47342
  )
47679
47343
  );
47680
47344
  const { succeeded, failed: failed3 } = processSessions(toProcess);
47681
47345
  console.log(
47682
- chalk259.green(`Done: ${succeeded} summarised`) + (failed3 > 0 ? chalk259.yellow(`, ${failed3} skipped`) : "")
47346
+ chalk260.green(`Done: ${succeeded} summarised`) + (failed3 > 0 ? chalk260.yellow(`, ${failed3} skipped`) : "")
47683
47347
  );
47684
47348
  }
47685
47349
  function selectCandidates(files, options2) {
@@ -47699,16 +47363,16 @@ function processSessions(files) {
47699
47363
  let failed3 = 0;
47700
47364
  for (let i = 0; i < files.length; i++) {
47701
47365
  const file = files[i];
47702
- process.stdout.write(chalk259.dim(` [${i + 1}/${files.length}] `));
47366
+ process.stdout.write(chalk260.dim(` [${i + 1}/${files.length}] `));
47703
47367
  const summary = summariseSession(file);
47704
47368
  if (summary) {
47705
47369
  writeSummary(file, summary);
47706
47370
  succeeded++;
47707
- process.stdout.write(`${chalk259.green("\u2713")} ${summary}
47371
+ process.stdout.write(`${chalk260.green("\u2713")} ${summary}
47708
47372
  `);
47709
47373
  } else {
47710
47374
  failed3++;
47711
- process.stdout.write(` ${chalk259.yellow("skip")}
47375
+ process.stdout.write(` ${chalk260.yellow("skip")}
47712
47376
  `);
47713
47377
  }
47714
47378
  }
@@ -47738,10 +47402,10 @@ function registerSessions(program2) {
47738
47402
  }
47739
47403
 
47740
47404
  // src/reportCliError.ts
47741
- import chalk260 from "chalk";
47405
+ import chalk261 from "chalk";
47742
47406
  function reportCliError(error) {
47743
47407
  if (error instanceof InvalidItemIdError || error instanceof AmbiguousRepoConfigError || error instanceof UnknownRepoConfigError || error instanceof MissingRunCwdError || error instanceof MiroExtractError) {
47744
- console.error(chalk260.red(error.message));
47408
+ console.error(chalk261.red(error.message));
47745
47409
  } else {
47746
47410
  console.error(error);
47747
47411
  }