@wangyaoshen/remux 0.3.8-dev.29e114b

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +47 -0
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +38 -0
  3. package/.github/PULL_REQUEST_TEMPLATE.md +28 -0
  4. package/.github/dependabot.yml +33 -0
  5. package/.github/workflows/ci.yml +65 -0
  6. package/.github/workflows/deploy.yml +65 -0
  7. package/.github/workflows/publish.yml +312 -0
  8. package/.github/workflows/release-please.yml +21 -0
  9. package/.gitmodules +3 -0
  10. package/.nvmrc +1 -0
  11. package/.release-please-manifest.json +3 -0
  12. package/CLAUDE.md +104 -0
  13. package/Dockerfile +23 -0
  14. package/LICENSE +21 -0
  15. package/README.md +120 -0
  16. package/apps/ios/Config/signing.xcconfig +4 -0
  17. package/apps/ios/Package.swift +26 -0
  18. package/apps/ios/Remux.xcodeproj/project.pbxproj +477 -0
  19. package/apps/ios/Remux.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  20. package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/Contents.json +23 -0
  21. package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_1024x1024.png +0 -0
  22. package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_120x120.png +0 -0
  23. package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_152x152.png +0 -0
  24. package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_167x167.png +0 -0
  25. package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_180x180.png +0 -0
  26. package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_20x20.png +0 -0
  27. package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_29x29.png +0 -0
  28. package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_40x40.png +0 -0
  29. package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_58x58.png +0 -0
  30. package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_60x60.png +0 -0
  31. package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_76x76.png +0 -0
  32. package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_80x80.png +0 -0
  33. package/apps/ios/Sources/Remux/Assets.xcassets/AppIcon.appiconset/icon_87x87.png +0 -0
  34. package/apps/ios/Sources/Remux/Assets.xcassets/Contents.json +6 -0
  35. package/apps/ios/Sources/Remux/Extensions/FaceIDManager.swift +29 -0
  36. package/apps/ios/Sources/Remux/Extensions/InspectCache.swift +66 -0
  37. package/apps/ios/Sources/Remux/MainTabView.swift +32 -0
  38. package/apps/ios/Sources/Remux/Remux.entitlements +8 -0
  39. package/apps/ios/Sources/Remux/RemuxiOSApp.swift +14 -0
  40. package/apps/ios/Sources/Remux/RootView.swift +130 -0
  41. package/apps/ios/Sources/Remux/Views/Control/ControlView.swift +102 -0
  42. package/apps/ios/Sources/Remux/Views/Inspect/InspectView.swift +98 -0
  43. package/apps/ios/Sources/Remux/Views/Live/LiveTerminalView.swift +132 -0
  44. package/apps/ios/Sources/Remux/Views/Now/NowView.swift +173 -0
  45. package/apps/ios/Sources/Remux/Views/Onboarding/ManualConnectView.swift +55 -0
  46. package/apps/ios/Sources/Remux/Views/Onboarding/OnboardingView.swift +70 -0
  47. package/apps/ios/Sources/Remux/Views/Onboarding/QRScannerView.swift +92 -0
  48. package/apps/ios/Sources/Remux/Views/Settings/MeView.swift +136 -0
  49. package/apps/macos/Package.swift +37 -0
  50. package/apps/macos/Resources/shell-integration/bash/bash-preexec.sh +382 -0
  51. package/apps/macos/Resources/shell-integration/bash/ghostty.bash +315 -0
  52. package/apps/macos/Resources/shell-integration/elvish/lib/ghostty-integration.elv +191 -0
  53. package/apps/macos/Resources/shell-integration/fish/vendor_conf.d/ghostty-shell-integration.fish +246 -0
  54. package/apps/macos/Resources/shell-integration/nushell/vendor/autoload/ghostty.nu +110 -0
  55. package/apps/macos/Resources/shell-integration/zsh/.zshenv +61 -0
  56. package/apps/macos/Resources/shell-integration/zsh/ghostty-integration +458 -0
  57. package/apps/macos/Resources/terminfo/67/ghostty +0 -0
  58. package/apps/macos/Resources/terminfo/78/xterm-ghostty +0 -0
  59. package/apps/macos/Sources/Remux/AppDelegate.swift +257 -0
  60. package/apps/macos/Sources/Remux/CrashReporter.swift +210 -0
  61. package/apps/macos/Sources/Remux/FinderIntegration.swift +117 -0
  62. package/apps/macos/Sources/Remux/GhosttyConfig.swift +311 -0
  63. package/apps/macos/Sources/Remux/KeyboardShortcuts/ShortcutAction.swift +115 -0
  64. package/apps/macos/Sources/Remux/KeyboardShortcuts/ShortcutSettingsView.swift +271 -0
  65. package/apps/macos/Sources/Remux/KeyboardShortcuts/StoredShortcut.swift +149 -0
  66. package/apps/macos/Sources/Remux/MainContentView.swift +308 -0
  67. package/apps/macos/Sources/Remux/MenuBarManager.swift +275 -0
  68. package/apps/macos/Sources/Remux/NotificationManager.swift +145 -0
  69. package/apps/macos/Sources/Remux/PortScanner.swift +152 -0
  70. package/apps/macos/Sources/Remux/RemuxApp.swift +13 -0
  71. package/apps/macos/Sources/Remux/SSHDetector.swift +151 -0
  72. package/apps/macos/Sources/Remux/SessionPersistence.swift +226 -0
  73. package/apps/macos/Sources/Remux/SocketController.swift +258 -0
  74. package/apps/macos/Sources/Remux/UpdateChecker.swift +152 -0
  75. package/apps/macos/Sources/Remux/Views/CommandPalette.swift +198 -0
  76. package/apps/macos/Sources/Remux/Views/ConnectionView.swift +84 -0
  77. package/apps/macos/Sources/Remux/Views/InspectView.swift +127 -0
  78. package/apps/macos/Sources/Remux/Views/SettingsView.swift +77 -0
  79. package/apps/macos/Sources/Remux/Views/Sidebar/SidebarView.swift +410 -0
  80. package/apps/macos/Sources/Remux/Views/SplitTree/BrowserPanel.swift +193 -0
  81. package/apps/macos/Sources/Remux/Views/SplitTree/MarkdownPanel.swift +277 -0
  82. package/apps/macos/Sources/Remux/Views/SplitTree/PanelProtocol.swift +14 -0
  83. package/apps/macos/Sources/Remux/Views/SplitTree/SplitNode.swift +149 -0
  84. package/apps/macos/Sources/Remux/Views/SplitTree/SplitView.swift +234 -0
  85. package/apps/macos/Sources/Remux/Views/SplitTree/TerminalPanel.swift +26 -0
  86. package/apps/macos/Sources/Remux/Views/TabBarView.swift +94 -0
  87. package/apps/macos/Sources/Remux/Views/Terminal/ClipboardHelper.swift +101 -0
  88. package/apps/macos/Sources/Remux/Views/Terminal/CopyModeOverlay.swift +325 -0
  89. package/apps/macos/Sources/Remux/Views/Terminal/GhosttyNativeTerminalView.swift +39 -0
  90. package/apps/macos/Sources/Remux/Views/Terminal/GhosttyNativeView.swift +559 -0
  91. package/apps/macos/Sources/Remux/Views/Terminal/SurfaceSearchOverlay.swift +109 -0
  92. package/apps/macos/Sources/Remux/Views/Terminal/TerminalContainerView.swift +95 -0
  93. package/apps/macos/Sources/Remux/Views/Terminal/TerminalRelay.swift +117 -0
  94. package/build.mjs +33 -0
  95. package/native/android/DecodeGoldenPayloads.kt +487 -0
  96. package/native/android/ProtocolModels.kt +188 -0
  97. package/native/ios/DecodeGoldenPayloads.swift +711 -0
  98. package/native/ios/ProtocolModels.swift +200 -0
  99. package/package.json +45 -0
  100. package/packages/RemuxKit/Package.swift +27 -0
  101. package/packages/RemuxKit/Sources/RemuxKit/Device/DeviceManager.swift +27 -0
  102. package/packages/RemuxKit/Sources/RemuxKit/Models/ProtocolModels.swift +206 -0
  103. package/packages/RemuxKit/Sources/RemuxKit/Networking/MessageRouter.swift +108 -0
  104. package/packages/RemuxKit/Sources/RemuxKit/Networking/RemuxConnection.swift +395 -0
  105. package/packages/RemuxKit/Sources/RemuxKit/State/RemuxState.swift +188 -0
  106. package/packages/RemuxKit/Sources/RemuxKit/Storage/KeychainStore.swift +142 -0
  107. package/packages/RemuxKit/Sources/RemuxKit/Terminal/GhosttyBridge.swift +145 -0
  108. package/packages/RemuxKit/Sources/RemuxKit/Terminal/GhosttyTerminalView.swift +35 -0
  109. package/packages/RemuxKit/Sources/RemuxKit/Terminal/Resources/ghostty-terminal.html +91 -0
  110. package/packages/RemuxKit/Tests/RemuxKitTests/ConnectionIntegrationTest.swift +74 -0
  111. package/packages/RemuxKit/Tests/RemuxKitTests/KeychainStoreTests.swift +81 -0
  112. package/packages/RemuxKit/Tests/RemuxKitTests/ProtocolModelsTests.swift +179 -0
  113. package/packages/RemuxKit/Tests/RemuxKitTests/RemuxStateTests.swift +62 -0
  114. package/playwright.config.ts +17 -0
  115. package/pnpm-lock.yaml +1588 -0
  116. package/pty-daemon.js +303 -0
  117. package/release-please-config.json +14 -0
  118. package/scripts/auto-deploy.sh +46 -0
  119. package/scripts/build-dmg.sh +121 -0
  120. package/scripts/build-ghostty-kit.sh +43 -0
  121. package/scripts/check-active-terminology.mjs +132 -0
  122. package/scripts/setup-ci-secrets.sh +80 -0
  123. package/scripts/sync-ghostty-web.sh +28 -0
  124. package/scripts/upload-testflight.sh +100 -0
  125. package/server.js +7074 -0
  126. package/src/adapters/agent-events.ts +246 -0
  127. package/src/adapters/claude-code.ts +158 -0
  128. package/src/adapters/codex.ts +210 -0
  129. package/src/adapters/generic-shell.ts +58 -0
  130. package/src/adapters/index.ts +15 -0
  131. package/src/adapters/registry.ts +99 -0
  132. package/src/adapters/types.ts +41 -0
  133. package/src/auth.ts +174 -0
  134. package/src/e2ee.ts +236 -0
  135. package/src/git-service.ts +168 -0
  136. package/src/message-buffer.ts +137 -0
  137. package/src/pty-daemon.ts +357 -0
  138. package/src/push.ts +127 -0
  139. package/src/renderers.ts +455 -0
  140. package/src/server.ts +2407 -0
  141. package/src/service.ts +226 -0
  142. package/src/session.ts +978 -0
  143. package/src/store.ts +1422 -0
  144. package/src/team.ts +123 -0
  145. package/src/tunnel.ts +126 -0
  146. package/src/types.d.ts +50 -0
  147. package/src/vt-tracker.ts +188 -0
  148. package/src/workspace-head.ts +144 -0
  149. package/src/workspace.ts +153 -0
  150. package/src/ws-handler.ts +1526 -0
  151. package/start.ps1 +83 -0
  152. package/tests/adapters.test.js +171 -0
  153. package/tests/auth.test.js +243 -0
  154. package/tests/codex-adapter.test.js +535 -0
  155. package/tests/durable-stream.test.js +153 -0
  156. package/tests/e2e/app.spec.js +530 -0
  157. package/tests/e2ee.test.js +325 -0
  158. package/tests/message-buffer.test.js +245 -0
  159. package/tests/message-routing.test.js +305 -0
  160. package/tests/pty-daemon.test.js +346 -0
  161. package/tests/push.test.js +281 -0
  162. package/tests/renderers.test.js +391 -0
  163. package/tests/search-shell.test.js +499 -0
  164. package/tests/server.test.js +882 -0
  165. package/tests/service.test.js +267 -0
  166. package/tests/store.test.js +369 -0
  167. package/tests/tunnel.test.js +67 -0
  168. package/tests/workspace-head.test.js +116 -0
  169. package/tests/workspace.test.js +417 -0
  170. package/tsconfig.backend.json +11 -0
  171. package/tsconfig.json +15 -0
  172. package/tui/client/client_test.go +125 -0
  173. package/tui/client/connection.go +342 -0
  174. package/tui/client/host_manager.go +141 -0
  175. package/tui/config/cache.go +81 -0
  176. package/tui/config/config.go +53 -0
  177. package/tui/config/config_test.go +89 -0
  178. package/tui/go.mod +32 -0
  179. package/tui/go.sum +50 -0
  180. package/tui/main.go +261 -0
  181. package/tui/tests/integration_test.go +283 -0
  182. package/tui/ui/model.go +310 -0
  183. package/vitest.config.js +10 -0
@@ -0,0 +1,191 @@
1
+ {
2
+ use platform
3
+ use str
4
+
5
+ # Clean up XDG_DATA_DIRS by removing GHOSTTY_SHELL_INTEGRATION_XDG_DIR
6
+ if (and (has-env GHOSTTY_SHELL_INTEGRATION_XDG_DIR) (has-env XDG_DATA_DIRS)) {
7
+ set-env XDG_DATA_DIRS (str:replace $E:GHOSTTY_SHELL_INTEGRATION_XDG_DIR":" "" $E:XDG_DATA_DIRS)
8
+ unset-env GHOSTTY_SHELL_INTEGRATION_XDG_DIR
9
+ }
10
+
11
+ # List of enabled shell integration features
12
+ var features = [(str:split ',' $E:GHOSTTY_SHELL_FEATURES)]
13
+
14
+ # State tracking for semantic prompt sequences
15
+ # Values: 'prompt-start', 'pre-exec', 'post-exec'
16
+ fn set-prompt-state {|new| set-env __ghostty_prompt_state $new }
17
+
18
+ fn mark-prompt-start {
19
+ if (not-eq $E:__ghostty_prompt_state 'prompt-start') {
20
+ printf "\e]133;D;aid="$pid"\a"
21
+ }
22
+ set-prompt-state 'prompt-start'
23
+ printf "\e]133;A;aid="$pid"\a"
24
+ }
25
+
26
+ fn mark-output-start {|_|
27
+ set-prompt-state 'pre-exec'
28
+ printf "\e]133;C\a"
29
+ }
30
+
31
+ fn mark-output-end {|cmd-info|
32
+ set-prompt-state 'post-exec'
33
+
34
+ var exit-status = 0
35
+
36
+ # in case of error: retrieve exit status,
37
+ # unless does not exist (= builtin function failure), then default to 1
38
+ if (not-eq $nil $cmd-info[error]) {
39
+ set exit-status = 1
40
+
41
+ if (has-key $cmd-info[error] reason) {
42
+ if (has-key $cmd-info[error][reason] exit-status) {
43
+ set exit-status = $cmd-info[error][reason][exit-status]
44
+ }
45
+ }
46
+ }
47
+
48
+ printf "\e]133;D;"$exit-status";aid="$pid"\a"
49
+ }
50
+
51
+ # NOTE: OSC 133;B (end of prompt, start of input) cannot be reliably
52
+ # implemented at the script level in Elvish. The prompt function's output is
53
+ # escaped, and writing to /dev/tty has timing issues because Elvish renders
54
+ # its prompts on a background thread. Full semantic prompt support requires a
55
+ # native implementation: https://github.com/elves/elvish/pull/1917
56
+
57
+ fn sudo-with-terminfo {|@args|
58
+ var sudoedit = $false
59
+ for arg $args {
60
+ if (str:has-prefix $arg --) {
61
+ if (eq $arg --edit) {
62
+ set sudoedit = $true
63
+ break
64
+ }
65
+ } elif (str:has-prefix $arg -) {
66
+ if (str:contains (str:trim-prefix $arg -) e) {
67
+ set sudoedit = $true
68
+ break
69
+ }
70
+ } elif (not (str:contains $arg =)) {
71
+ break
72
+ }
73
+ }
74
+
75
+ if (not $sudoedit) { set args = [ --preserve-env=TERMINFO $@args ] }
76
+ (external sudo) $@args
77
+ }
78
+
79
+ fn ssh-integration {|@args|
80
+ var ssh-term = "xterm-256color"
81
+ var ssh-opts = []
82
+
83
+ # Configure environment variables for remote session
84
+ if (has-value $features ssh-env) {
85
+ set ssh-opts = (conj $ssh-opts ^
86
+ -o "SetEnv COLORTERM=truecolor" ^
87
+ -o "SendEnv TERM_PROGRAM TERM_PROGRAM_VERSION")
88
+ }
89
+
90
+ if (has-value $features ssh-terminfo) {
91
+ var ssh-user = ""
92
+ var ssh-hostname = ""
93
+
94
+ # Parse ssh config
95
+ for line [((external ssh) -G $@args)] {
96
+ var parts = [(str:fields $line)]
97
+ if (> (count $parts) 1) {
98
+ var ssh-key = $parts[0]
99
+ var ssh-value = $parts[1]
100
+ if (eq $ssh-key user) {
101
+ set ssh-user = $ssh-value
102
+ } elif (eq $ssh-key hostname) {
103
+ set ssh-hostname = $ssh-value
104
+ }
105
+ if (and (not-eq $ssh-user "") (not-eq $ssh-hostname "")) {
106
+ break
107
+ }
108
+ }
109
+ }
110
+
111
+ if (not-eq $ssh-hostname "") {
112
+ var ghostty = $E:GHOSTTY_BIN_DIR/"ghostty"
113
+ var ssh-target = $ssh-user"@"$ssh-hostname
114
+
115
+ # Check if terminfo is already cached
116
+ if (bool ?($ghostty +ssh-cache --host=$ssh-target)) {
117
+ set ssh-term = "xterm-ghostty"
118
+ } elif (has-external infocmp) {
119
+ var ssh-terminfo = ((external infocmp) -0 -x xterm-ghostty 2>/dev/null | slurp)
120
+
121
+ if (not-eq $ssh-terminfo "") {
122
+ echo "Setting up xterm-ghostty terminfo on "$ssh-hostname"..." >&2
123
+
124
+ use os
125
+ var ssh-cpath-dir = (os:temp-dir "ghostty-ssh-"$ssh-user".*")
126
+ var ssh-cpath = $ssh-cpath-dir"/socket"
127
+
128
+ if (bool ?(echo $ssh-terminfo | (external ssh) $@ssh-opts -o ControlMaster=yes -o ControlPath=$ssh-cpath -o ControlPersist=60s $@args '
129
+ infocmp xterm-ghostty >/dev/null 2>&1 && exit 0
130
+ command -v tic >/dev/null 2>&1 || exit 1
131
+ mkdir -p ~/.terminfo 2>/dev/null && tic -x - 2>/dev/null && exit 0
132
+ exit 1
133
+ ' 2>/dev/null)) {
134
+ set ssh-term = "xterm-ghostty"
135
+ set ssh-opts = (conj $ssh-opts -o ControlPath=$ssh-cpath)
136
+
137
+ # Cache successful installation
138
+ $ghostty +ssh-cache --add=$ssh-target >/dev/null
139
+ } else {
140
+ echo "Warning: Failed to install terminfo." >&2
141
+ }
142
+ } else {
143
+ echo "Warning: Could not generate terminfo data." >&2
144
+ }
145
+ } else {
146
+ echo "Warning: ghostty command not available for cache management." >&2
147
+ }
148
+ }
149
+ }
150
+
151
+ with [E:TERM = $ssh-term] {
152
+ (external ssh) $@ssh-opts $@args
153
+ }
154
+ }
155
+
156
+ defer {
157
+ mark-prompt-start
158
+ }
159
+
160
+ set edit:before-readline = (conj $edit:before-readline $mark-prompt-start~)
161
+ set edit:after-readline = (conj $edit:after-readline $mark-output-start~)
162
+ set edit:after-command = (conj $edit:after-command $mark-output-end~)
163
+
164
+ if (str:contains $E:GHOSTTY_SHELL_FEATURES "cursor") {
165
+ var cursor = "5" # blinking bar
166
+ if (has-value $features cursor:steady) {
167
+ set cursor = "6" # steady bar
168
+ }
169
+
170
+ fn beam { printf "\e["$cursor" q" }
171
+ fn reset { printf "\e[0 q" }
172
+ set edit:before-readline = (conj $edit:before-readline $beam~)
173
+ set edit:after-readline = (conj $edit:after-readline {|_| reset })
174
+ }
175
+ if (and (has-value $features path) (has-env GHOSTTY_BIN_DIR)) {
176
+ if (not (has-value $paths $E:GHOSTTY_BIN_DIR)) {
177
+ set paths = [$@paths $E:GHOSTTY_BIN_DIR]
178
+ }
179
+ }
180
+ if (and (has-value $features sudo) (not-eq "" $E:TERMINFO) (has-external sudo)) {
181
+ edit:add-var sudo~ $sudo-with-terminfo~
182
+ }
183
+ if (and (str:contains $E:GHOSTTY_SHELL_FEATURES ssh-) (has-external ssh)) {
184
+ edit:add-var ssh~ $ssh-integration~
185
+ }
186
+
187
+ # Report changes to the current directory.
188
+ fn report-pwd { printf "\e]7;kitty-shell-cwd://%s%s\a" (platform:hostname) $pwd }
189
+ set after-chdir = (conj $after-chdir {|_| report-pwd })
190
+ report-pwd
191
+ }
@@ -0,0 +1,246 @@
1
+ # This shell script aims to be written in a way where it can't really fail
2
+ # or all failure scenarios are handled, so that we never leave the shell in
3
+ # a weird state. If you find a way to break this, please report a bug!
4
+
5
+ function ghostty_restore_xdg_data_dir -d "restore the original XDG_DATA_DIR value"
6
+ # If we don't have our own data dir then we don't need to do anything.
7
+ if not set -q GHOSTTY_SHELL_INTEGRATION_XDG_DIR
8
+ return
9
+ end
10
+
11
+ # If the data dir isn't set at all then we don't need to do anything.
12
+ if not set -q XDG_DATA_DIRS
13
+ return
14
+ end
15
+
16
+ # We need to do this so that XDG_DATA_DIRS turns into an array.
17
+ set --function --path xdg_data_dirs "$XDG_DATA_DIRS"
18
+
19
+ # If our data dir is in the list then remove it.
20
+ if set --function index (contains --index "$GHOSTTY_SHELL_INTEGRATION_XDG_DIR" $xdg_data_dirs)
21
+ set --erase --function xdg_data_dirs[$index]
22
+ end
23
+
24
+ # Re-export our data dir
25
+ if set -q xdg_data_dirs[1]
26
+ set --global --export --unpath XDG_DATA_DIRS "$xdg_data_dirs"
27
+ else
28
+ set --erase --global XDG_DATA_DIRS
29
+ end
30
+
31
+ set --erase GHOSTTY_SHELL_INTEGRATION_XDG_DIR
32
+ end
33
+
34
+ function ghostty_exit -d "exit the shell integration setup"
35
+ functions -e ghostty_restore_xdg_data_dir
36
+ functions -e ghostty_exit
37
+ exit 0
38
+ end
39
+
40
+ # We always try to restore the XDG data dir
41
+ ghostty_restore_xdg_data_dir
42
+
43
+ # If we aren't interactive or we've already run, don't run.
44
+ status --is-interactive || ghostty_exit
45
+
46
+ # We do the full setup on the first prompt render. We do this so that other
47
+ # shell integrations that setup the prompt and modify things are able to run
48
+ # first. We want to run _last_.
49
+ function __ghostty_setup --on-event fish_prompt -d "Setup ghostty integration"
50
+ functions -e __ghostty_setup
51
+
52
+ set --local features (string split , $GHOSTTY_SHELL_FEATURES)
53
+
54
+ # Parse the fish version for feature detection.
55
+ # Default to 0.0 if version is unavailable or malformed.
56
+ set -l fish_major 0
57
+ set -l fish_minor 0
58
+ if set -q version[1]
59
+ set -l fish_ver (string match -r '(\d+)\.(\d+)' -- $version[1])
60
+ if set -q fish_ver[2]; and test -n "$fish_ver[2]"
61
+ set fish_major "$fish_ver[2]"
62
+ end
63
+ if set -q fish_ver[3]; and test -n "$fish_ver[3]"
64
+ set fish_minor "$fish_ver[3]"
65
+ end
66
+ end
67
+
68
+ # Our OSC133A (prompt start) sequence. If we're using Fish >= 4.1
69
+ # then it supports click_events so we enable that.
70
+ set -g __ghostty_prompt_start_mark "\e]133;A\a"
71
+ if test "$fish_major" -gt 4; or test "$fish_major" -eq 4 -a "$fish_minor" -ge 1
72
+ set -g __ghostty_prompt_start_mark "\e]133;A;click_events=1\a"
73
+ end
74
+
75
+ if string match -q 'cursor*' -- $features
76
+ set -l cursor 5 # blinking bar
77
+ contains cursor:steady $features && set cursor 6 # steady bar
78
+
79
+ # Change the cursor to a beam on prompt.
80
+ function __ghostty_set_cursor_beam --on-event fish_prompt -V cursor -d "Set cursor shape"
81
+ if not functions -q fish_vi_cursor_handle
82
+ echo -en "\e[$cursor q"
83
+ end
84
+ end
85
+ function __ghostty_reset_cursor --on-event fish_preexec -d "Reset cursor shape"
86
+ if not functions -q fish_vi_cursor_handle
87
+ echo -en "\e[0 q"
88
+ end
89
+ end
90
+ end
91
+
92
+ # Add Ghostty binary to PATH if the path feature is enabled
93
+ if contains path $features; and test -n "$GHOSTTY_BIN_DIR"
94
+ fish_add_path --global --path --append "$GHOSTTY_BIN_DIR"
95
+ end
96
+
97
+ # When using sudo shell integration feature, ensure $TERMINFO is set
98
+ # and `sudo` is not already a function or alias
99
+ if contains sudo $features; and test -n "$TERMINFO"; and test file = (type -t sudo 2> /dev/null; or echo "x")
100
+ # Wrap `sudo` command to ensure Ghostty terminfo is preserved
101
+ function sudo -d "Wrap sudo to preserve terminfo"
102
+ set --function sudo_has_sudoedit_flags no
103
+ for arg in $argv
104
+ # Check if argument is '-e' or '--edit' (sudoedit flags)
105
+ if string match -q -- -e "$arg"; or string match -q -- --edit "$arg"
106
+ set --function sudo_has_sudoedit_flags yes
107
+ break
108
+ end
109
+ # Check if argument is neither an option nor a key-value pair
110
+ if not string match -r -q -- "^-" "$arg"; and not string match -r -q -- "=" "$arg"
111
+ break
112
+ end
113
+ end
114
+ if test "$sudo_has_sudoedit_flags" = yes
115
+ command sudo $argv
116
+ else
117
+ command sudo --preserve-env=TERMINFO $argv
118
+ end
119
+ end
120
+ end
121
+
122
+ # SSH Integration
123
+ set -l features (string split ',' -- "$GHOSTTY_SHELL_FEATURES")
124
+ if contains ssh-env $features; or contains ssh-terminfo $features
125
+ function ssh --wraps=ssh --description "SSH wrapper with Ghostty integration"
126
+ set -l features (string split ',' -- "$GHOSTTY_SHELL_FEATURES")
127
+ set -l ssh_term xterm-256color
128
+ set -l ssh_opts
129
+
130
+ # Configure environment variables for remote session
131
+ if contains ssh-env $features
132
+ set -a ssh_opts -o "SetEnv COLORTERM=truecolor"
133
+ set -a ssh_opts -o "SendEnv TERM_PROGRAM TERM_PROGRAM_VERSION"
134
+ end
135
+
136
+ # Install terminfo on remote host if needed
137
+ if contains ssh-terminfo $features
138
+ set -l ssh_user
139
+ set -l ssh_hostname
140
+
141
+ for line in (command ssh -G $argv 2>/dev/null)
142
+ set -l parts (string split ' ' -- $line)
143
+ if test (count $parts) -ge 2
144
+ switch $parts[1]
145
+ case user
146
+ set ssh_user $parts[2]
147
+ case hostname
148
+ set ssh_hostname $parts[2]
149
+ end
150
+ if test -n "$ssh_user"; and test -n "$ssh_hostname"
151
+ break
152
+ end
153
+ end
154
+ end
155
+
156
+ if test -n "$ssh_hostname"
157
+ set -l ssh_target "$ssh_user@$ssh_hostname"
158
+
159
+ # Check if terminfo is already cached
160
+ if test -x "$GHOSTTY_BIN_DIR/ghostty"; and "$GHOSTTY_BIN_DIR/ghostty" +ssh-cache --host="$ssh_target" >/dev/null 2>&1
161
+ set ssh_term xterm-ghostty
162
+ else if command -q infocmp
163
+ set -l ssh_terminfo
164
+ set -l ssh_cpath_dir
165
+ set -l ssh_cpath
166
+
167
+ set ssh_terminfo "$(infocmp -0 -x xterm-ghostty 2>/dev/null)"
168
+
169
+ if test -n "$ssh_terminfo"
170
+ echo "Setting up xterm-ghostty terminfo on $ssh_hostname..." >&2
171
+
172
+ set ssh_cpath_dir (mktemp -d "/tmp/ghostty-ssh-$ssh_user.XXXXXX" 2>/dev/null; or echo "/tmp/ghostty-ssh-$ssh_user."(random))
173
+ set ssh_cpath "$ssh_cpath_dir/socket"
174
+
175
+ if echo "$ssh_terminfo" | command ssh $ssh_opts -o ControlMaster=yes -o ControlPath="$ssh_cpath" -o ControlPersist=60s $argv '
176
+ infocmp xterm-ghostty >/dev/null 2>&1 && exit 0
177
+ command -v tic >/dev/null 2>&1 || exit 1
178
+ mkdir -p ~/.terminfo 2>/dev/null && tic -x - 2>/dev/null && exit 0
179
+ exit 1
180
+ ' 2>/dev/null
181
+ set ssh_term xterm-ghostty
182
+ set -a ssh_opts -o "ControlPath=$ssh_cpath"
183
+
184
+ # Cache successful installation
185
+ if test -x "$GHOSTTY_BIN_DIR/ghostty"
186
+ "$GHOSTTY_BIN_DIR/ghostty" +ssh-cache --add="$ssh_target" >/dev/null 2>&1; or true
187
+ end
188
+ else
189
+ echo "Warning: Failed to install terminfo." >&2
190
+ end
191
+ else
192
+ echo "Warning: Could not generate terminfo data." >&2
193
+ end
194
+ else
195
+ echo "Warning: ghostty command not available for cache management." >&2
196
+ end
197
+ end
198
+ end
199
+
200
+ # Execute SSH with TERM environment variable
201
+ TERM="$ssh_term" command ssh $ssh_opts $argv
202
+ end
203
+ end
204
+
205
+ # Setup prompt marking
206
+ function __ghostty_mark_prompt_start --on-event fish_prompt --on-event fish_posterror
207
+ # If we never got the output end event, then we need to send it now.
208
+ if test "$__ghostty_prompt_state" != prompt-start
209
+ echo -en "\e]133;D\a"
210
+ end
211
+
212
+ set --global __ghostty_prompt_state prompt-start
213
+ echo -en $__ghostty_prompt_start_mark
214
+ end
215
+
216
+ function __ghostty_mark_output_start --on-event fish_preexec
217
+ set --global __ghostty_prompt_state pre-exec
218
+ echo -en "\e]133;C\a"
219
+ end
220
+
221
+ function __ghostty_mark_output_end --on-event fish_postexec
222
+ set --global __ghostty_prompt_state post-exec
223
+ echo -en "\e]133;D;$status\a"
224
+ end
225
+
226
+ # Report pwd. This is actually built-in to fish but only for terminals
227
+ # that match an allowlist and that isn't us.
228
+ function __update_cwd_osc --on-variable PWD -d 'Notify capable terminals when $PWD changes'
229
+ if status --is-command-substitution || set -q INSIDE_EMACS
230
+ return
231
+ end
232
+ printf \e\]7\;file://%s%s\a $hostname (string escape --style=url $PWD)
233
+ end
234
+
235
+ # Enable fish to handle reflow because Ghostty clears the prompt on resize.
236
+ set --global fish_handle_reflow 1
237
+
238
+ # Initial calls for first prompt
239
+ if string match -q 'cursor*' -- $features
240
+ __ghostty_set_cursor_beam
241
+ end
242
+ __ghostty_mark_prompt_start
243
+ __update_cwd_osc
244
+ end
245
+
246
+ ghostty_exit
@@ -0,0 +1,110 @@
1
+ # Ghostty shell integration
2
+ export module ghostty {
3
+ def has_feature [feature: string] {
4
+ $feature in ($env.GHOSTTY_SHELL_FEATURES | default "" | split row ',')
5
+ }
6
+
7
+ # Wrap `ssh` with Ghostty TERMINFO support
8
+ export def --wrapped ssh [...args] {
9
+ mut ssh_env = {}
10
+ mut ssh_opts = []
11
+
12
+ # `ssh-env`: use xterm-256color and propagate COLORTERM/TERM_PROGRAM vars
13
+ if (has_feature "ssh-env") {
14
+ $ssh_env.TERM = "xterm-256color"
15
+ $ssh_opts = [
16
+ "-o" "SetEnv COLORTERM=truecolor"
17
+ "-o" "SendEnv TERM_PROGRAM TERM_PROGRAM_VERSION"
18
+ ]
19
+ }
20
+
21
+ # `ssh-terminfo`: auto-install xterm-ghostty terminfo on remote hosts
22
+ if (has_feature "ssh-terminfo") {
23
+ let ghostty = ($env.GHOSTTY_BIN_DIR? | default "") | path join "ghostty"
24
+
25
+ let ssh_cfg = ^ssh -G ...$args
26
+ | lines
27
+ | parse "{key} {value}"
28
+ | where key in ["user" "hostname"]
29
+ | select key value
30
+ | transpose -rd
31
+ | default {user: $env.USER hostname: "localhost"}
32
+ let ssh_id = $"($ssh_cfg.user)@($ssh_cfg.hostname)"
33
+
34
+ if (^$ghostty "+ssh-cache" $"--host=($ssh_id)" | complete | $in.exit_code == 0) {
35
+ $ssh_env.TERM = "xterm-ghostty"
36
+ } else {
37
+ $ssh_env.TERM = "xterm-256color"
38
+
39
+ let terminfo = try {
40
+ ^infocmp -0 -x xterm-ghostty
41
+ } catch {
42
+ print -e "infocmp failed, using xterm-256color"
43
+ }
44
+
45
+ if ($terminfo | is-not-empty) {
46
+ print $"Setting up xterm-ghostty terminfo on ($ssh_cfg.hostname)..."
47
+
48
+ let ctrl_path = (
49
+ mktemp -td $"ghostty-ssh-($ssh_cfg.user).XXXXXX"
50
+ | path join "socket"
51
+ )
52
+
53
+ let remote_args = $ssh_opts ++ [
54
+ "-o" "ControlMaster=yes"
55
+ "-o" $"ControlPath=($ctrl_path)"
56
+ "-o" "ControlPersist=60s"
57
+ ] ++ $args
58
+
59
+ $terminfo | ^ssh ...$remote_args '
60
+ infocmp xterm-ghostty >/dev/null 2>&1 && exit 0
61
+ command -v tic >/dev/null 2>&1 || exit 1
62
+ mkdir -p ~/.terminfo 2>/dev/null && tic -x - 2>/dev/null && exit 0
63
+ exit 1'
64
+ | complete
65
+ | if $in.exit_code == 0 {
66
+ ^$ghostty "+ssh-cache" $"--add=($ssh_id)" e>| print -e
67
+ $ssh_env.TERM = "xterm-ghostty"
68
+ $ssh_opts = ($ssh_opts ++ ["-o" $"ControlPath=($ctrl_path)"])
69
+ } else {
70
+ print -e "terminfo install failed, using xterm-256color"
71
+ }
72
+ }
73
+ }
74
+ }
75
+
76
+ let ssh_args = $ssh_opts ++ $args
77
+ with-env $ssh_env {
78
+ ^ssh ...$ssh_args
79
+ }
80
+ }
81
+
82
+ # Wrap `sudo` to preserve Ghostty's TERMINFO environment variable
83
+ export def --wrapped sudo [...args] {
84
+ mut sudo_args = $args
85
+
86
+ if (has_feature "sudo") {
87
+ # Extract just the sudo options (before the command)
88
+ let sudo_options = (
89
+ $args | take until {|arg|
90
+ not (($arg | str starts-with "-") or ($arg | str contains "="))
91
+ }
92
+ )
93
+
94
+ # Prepend TERMINFO preservation flag if not using sudoedit
95
+ if (not ("-e" in $sudo_options or "--edit" in $sudo_options)) {
96
+ $sudo_args = ($args | prepend "--preserve-env=TERMINFO")
97
+ }
98
+ }
99
+
100
+ ^sudo ...$sudo_args
101
+ }
102
+ }
103
+
104
+ # Clean up XDG_DATA_DIRS by removing GHOSTTY_SHELL_INTEGRATION_XDG_DIR
105
+ if 'GHOSTTY_SHELL_INTEGRATION_XDG_DIR' in $env {
106
+ if 'XDG_DATA_DIRS' in $env {
107
+ $env.XDG_DATA_DIRS = ($env.XDG_DATA_DIRS | str replace $"($env.GHOSTTY_SHELL_INTEGRATION_XDG_DIR):" "")
108
+ }
109
+ hide-env GHOSTTY_SHELL_INTEGRATION_XDG_DIR
110
+ }
@@ -0,0 +1,61 @@
1
+ # Based on (started as) a copy of Kitty's zsh integration. Kitty is
2
+ # distributed under GPLv3, so this file is also distributed under GPLv3.
3
+ # The license header is reproduced below:
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ # This script is sourced automatically by zsh when ZDOTDIR is set to this
19
+ # directory. It therefore assumes it's running within our shell integration
20
+ # environment and should not be sourced manually (unlike ghostty-integration).
21
+ #
22
+ # This file can get sourced with aliases enabled. To avoid alias expansion
23
+ # we quote everything that can be quoted. Some aliases will still break us
24
+ # though.
25
+
26
+ # Restore the original ZDOTDIR value if GHOSTTY_ZSH_ZDOTDIR is set.
27
+ # Otherwise, unset the ZDOTDIR that was set during shell injection.
28
+ if [[ -n "${GHOSTTY_ZSH_ZDOTDIR+X}" ]]; then
29
+ 'builtin' 'export' ZDOTDIR="$GHOSTTY_ZSH_ZDOTDIR"
30
+ 'builtin' 'unset' 'GHOSTTY_ZSH_ZDOTDIR'
31
+ else
32
+ 'builtin' 'unset' 'ZDOTDIR'
33
+ fi
34
+
35
+ # Use try-always to have the right error code.
36
+ {
37
+ # Zsh treats unset ZDOTDIR as if it was HOME. We do the same.
38
+ #
39
+ # Source the user's .zshenv before sourcing ghostty-integration because the
40
+ # former might set fpath and other things without which ghostty-integration
41
+ # won't work.
42
+ #
43
+ # Use typeset in case we are in a function with warn_create_global in
44
+ # effect. Unlikely but better safe than sorry.
45
+ 'builtin' 'typeset' _ghostty_file=${ZDOTDIR-$HOME}"/.zshenv"
46
+ # Zsh ignores unreadable rc files. We do the same.
47
+ # Zsh ignores rc files that are directories, and so does source.
48
+ [[ ! -r "$_ghostty_file" ]] || 'builtin' 'source' '--' "$_ghostty_file"
49
+ } always {
50
+ if [[ -o 'interactive' ]]; then
51
+ # ${(%):-%x} is the path to the current file.
52
+ # On top of it we add :A:h to get the directory.
53
+ 'builtin' 'typeset' _ghostty_file="${${(%):-%x}:A:h}"/ghostty-integration
54
+ if [[ -r "$_ghostty_file" ]]; then
55
+ 'builtin' 'autoload' '-Uz' '--' "$_ghostty_file"
56
+ "${_ghostty_file:t}"
57
+ 'builtin' 'unfunction' '--' "${_ghostty_file:t}"
58
+ fi
59
+ fi
60
+ 'builtin' 'unset' '_ghostty_file'
61
+ }