@termix-ssh/plugin-sdk 1.0.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.
Files changed (236) hide show
  1. package/README.md +52 -0
  2. package/cli/commands/build.mjs +246 -0
  3. package/cli/commands/migrations.mjs +248 -0
  4. package/cli/commands/pack.mjs +42 -0
  5. package/cli/commands/sign.mjs +83 -0
  6. package/cli/commands/test.mjs +33 -0
  7. package/cli/commands/validate.mjs +215 -0
  8. package/cli/index.mjs +65 -0
  9. package/cli/lib/changelog.mjs +126 -0
  10. package/cli/lib/externals.mjs +46 -0
  11. package/cli/lib/plugin-dir.mjs +29 -0
  12. package/cli/lib/signing.mjs +86 -0
  13. package/cli/lib/static-url-imports.mjs +54 -0
  14. package/cli/lib/tmxplug.mjs +106 -0
  15. package/cli/patches/guacamole-common-js-flush.cjs +76 -0
  16. package/cli/patches/xterm-android-ime.cjs +130 -0
  17. package/client.d.ts +13 -0
  18. package/dist/backend.d.ts +1777 -0
  19. package/dist/backend.js +84 -0
  20. package/dist/capabilities.d.ts +38 -0
  21. package/dist/capabilities.js +71 -0
  22. package/dist/db.d.ts +112 -0
  23. package/dist/db.js +189 -0
  24. package/dist/ddl.d.ts +76 -0
  25. package/dist/ddl.js +678 -0
  26. package/dist/desktop.d.ts +346 -0
  27. package/dist/desktop.js +5 -0
  28. package/dist/frontend.d.ts +1206 -0
  29. package/dist/frontend.js +388 -0
  30. package/dist/host/chunk-F3SDZDDG.js +6017 -0
  31. package/dist/host/chunk-XGWE7BTJ.js +90 -0
  32. package/dist/host/chunk-XH5NXL6Y.js +56314 -0
  33. package/dist/host/chunk-YC2MAKSP.js +14 -0
  34. package/dist/host/linked-server-FZGWQW32.js +12 -0
  35. package/dist/host/rbac-api-6KDMBVDZ.js +52 -0
  36. package/dist/host/testing-host.js +414 -0
  37. package/dist/host/theme.css +91 -0
  38. package/dist/host/types/src/types/auth-protocols.d.ts +10 -0
  39. package/dist/host/types/src/types/connection-log.d.ts +13 -0
  40. package/dist/host/types/src/types/credential-sidebar-preferences.d.ts +38 -0
  41. package/dist/host/types/src/types/host-defaults.d.ts +60 -0
  42. package/dist/host/types/src/types/host-sidebar-preferences.d.ts +55 -0
  43. package/dist/host/types/src/types/index.d.ts +298 -0
  44. package/dist/host/types/src/types/keybindings.d.ts +27 -0
  45. package/dist/host/types/src/types/ui-preferences.d.ts +102 -0
  46. package/dist/host/types/src/types/ui-types.d.ts +260 -0
  47. package/dist/host/types/src/ui/api/admin-user-data-api.d.ts +11 -0
  48. package/dist/host/types/src/ui/api/auth-methods-api.d.ts +63 -0
  49. package/dist/host/types/src/ui/api/credential-sidebar-preferences-api.d.ts +3 -0
  50. package/dist/host/types/src/ui/api/credentials-api.d.ts +56 -0
  51. package/dist/host/types/src/ui/api/dashboard-api.d.ts +23 -0
  52. package/dist/host/types/src/ui/api/external-account-api.d.ts +8 -0
  53. package/dist/host/types/src/ui/api/host-sidebar-preferences-api.d.ts +3 -0
  54. package/dist/host/types/src/ui/api/host-status-api.d.ts +11 -0
  55. package/dist/host/types/src/ui/api/open-tabs-api.d.ts +74 -0
  56. package/dist/host/types/src/ui/api/plugins-api.d.ts +128 -0
  57. package/dist/host/types/src/ui/api/rbac-api.d.ts +167 -0
  58. package/dist/host/types/src/ui/api/settings-api.d.ts +22 -0
  59. package/dist/host/types/src/ui/api/ssh-host-management-api.d.ts +65 -0
  60. package/dist/host/types/src/ui/api/sync-api.d.ts +93 -0
  61. package/dist/host/types/src/ui/api/system-status-api.d.ts +49 -0
  62. package/dist/host/types/src/ui/api/ui-preferences-api.d.ts +8 -0
  63. package/dist/host/types/src/ui/api/user-management-api.d.ts +80 -0
  64. package/dist/host/types/src/ui/auth/Auth.d.ts +12 -0
  65. package/dist/host/types/src/ui/auth/auth-bits.d.ts +11 -0
  66. package/dist/host/types/src/ui/auth/silent-signin.d.ts +2 -0
  67. package/dist/host/types/src/ui/components/FullScreenAppWrapper.d.ts +9 -0
  68. package/dist/host/types/src/ui/components/alert-dialog.d.ts +14 -0
  69. package/dist/host/types/src/ui/components/alert.d.ts +9 -0
  70. package/dist/host/types/src/ui/components/badge.d.ts +9 -0
  71. package/dist/host/types/src/ui/components/button.d.ts +13 -0
  72. package/dist/host/types/src/ui/components/card-grid/CardGridCanvas.d.ts +22 -0
  73. package/dist/host/types/src/ui/components/card-grid/layout-utils.d.ts +26 -0
  74. package/dist/host/types/src/ui/components/card-grid/types.d.ts +29 -0
  75. package/dist/host/types/src/ui/components/card-masonry.d.ts +16 -0
  76. package/dist/host/types/src/ui/components/card.d.ts +11 -0
  77. package/dist/host/types/src/ui/components/charts/BarSeries.d.ts +16 -0
  78. package/dist/host/types/src/ui/components/charts/LineChart.d.ts +17 -0
  79. package/dist/host/types/src/ui/components/charts/RadialGauge.d.ts +18 -0
  80. package/dist/host/types/src/ui/components/charts/Sparkline.d.ts +22 -0
  81. package/dist/host/types/src/ui/components/charts/StatRow.d.ts +15 -0
  82. package/dist/host/types/src/ui/components/charts/geometry.d.ts +20 -0
  83. package/dist/host/types/src/ui/components/charts/index.d.ts +4 -0
  84. package/dist/host/types/src/ui/components/checkbox.d.ts +4 -0
  85. package/dist/host/types/src/ui/components/connection/ConnectionGate.d.ts +56 -0
  86. package/dist/host/types/src/ui/components/connection/ConnectionLogPanel.d.ts +9 -0
  87. package/dist/host/types/src/ui/components/connection/ConnectionScreen.d.ts +34 -0
  88. package/dist/host/types/src/ui/components/connection/connection-status.d.ts +1 -0
  89. package/dist/host/types/src/ui/components/data-view.d.ts +32 -0
  90. package/dist/host/types/src/ui/components/dialog.d.ts +17 -0
  91. package/dist/host/types/src/ui/components/dropdown-menu.d.ts +29 -0
  92. package/dist/host/types/src/ui/components/empty-state.d.ts +19 -0
  93. package/dist/host/types/src/ui/components/form-fields.d.ts +115 -0
  94. package/dist/host/types/src/ui/components/input.d.ts +3 -0
  95. package/dist/host/types/src/ui/components/kbd.d.ts +12 -0
  96. package/dist/host/types/src/ui/components/label.d.ts +4 -0
  97. package/dist/host/types/src/ui/components/manager-card.d.ts +25 -0
  98. package/dist/host/types/src/ui/components/meter.d.ts +14 -0
  99. package/dist/host/types/src/ui/components/metric-card.d.ts +18 -0
  100. package/dist/host/types/src/ui/components/panel-layout.d.ts +83 -0
  101. package/dist/host/types/src/ui/components/password-input.d.ts +4 -0
  102. package/dist/host/types/src/ui/components/popover.d.ts +7 -0
  103. package/dist/host/types/src/ui/components/scroll-area.d.ts +5 -0
  104. package/dist/host/types/src/ui/components/section-card.d.ts +29 -0
  105. package/dist/host/types/src/ui/components/select.d.ts +15 -0
  106. package/dist/host/types/src/ui/components/select2.d.ts +8 -0
  107. package/dist/host/types/src/ui/components/separator.d.ts +4 -0
  108. package/dist/host/types/src/ui/components/sheet.d.ts +14 -0
  109. package/dist/host/types/src/ui/components/sidebar.d.ts +69 -0
  110. package/dist/host/types/src/ui/components/skeleton.d.ts +2 -0
  111. package/dist/host/types/src/ui/components/slider.d.ts +4 -0
  112. package/dist/host/types/src/ui/components/sonner.d.ts +4 -0
  113. package/dist/host/types/src/ui/components/surface/prompt-styles.d.ts +4 -0
  114. package/dist/host/types/src/ui/components/surface/surface-scope.d.ts +79 -0
  115. package/dist/host/types/src/ui/components/switch.d.ts +4 -0
  116. package/dist/host/types/src/ui/components/tab-strip.d.ts +19 -0
  117. package/dist/host/types/src/ui/components/tabs.d.ts +7 -0
  118. package/dist/host/types/src/ui/components/textarea.d.ts +4 -0
  119. package/dist/host/types/src/ui/components/theme-provider.d.ts +14 -0
  120. package/dist/host/types/src/ui/components/tooltip.d.ts +7 -0
  121. package/dist/host/types/src/ui/contexts/BrandingContext.d.ts +15 -0
  122. package/dist/host/types/src/ui/contexts/UiPreferencesContext.d.ts +38 -0
  123. package/dist/host/types/src/ui/contexts/branding-document.d.ts +10 -0
  124. package/dist/host/types/src/ui/dashboard/dashboard-cards-registry.d.ts +30 -0
  125. package/dist/host/types/src/ui/hooks/use-action-slot.d.ts +10 -0
  126. package/dist/host/types/src/ui/hooks/use-adaptive-polling.d.ts +5 -0
  127. package/dist/host/types/src/ui/hooks/use-mobile.d.ts +1 -0
  128. package/dist/host/types/src/ui/hooks/use-permissions.d.ts +29 -0
  129. package/dist/host/types/src/ui/hooks/use-status-color-scheme.d.ts +12 -0
  130. package/dist/host/types/src/ui/hooks/use-sync-status.d.ts +4 -0
  131. package/dist/host/types/src/ui/hooks/useSshAuthProviders.d.ts +17 -0
  132. package/dist/host/types/src/ui/i18n/i18n.d.ts +52 -0
  133. package/dist/host/types/src/ui/lib/ServerStatusContext.d.ts +33 -0
  134. package/dist/host/types/src/ui/lib/adaptive-polling.d.ts +29 -0
  135. package/dist/host/types/src/ui/lib/adaptive-resource-budget.d.ts +39 -0
  136. package/dist/host/types/src/ui/lib/app-keyboard-shortcuts.d.ts +8 -0
  137. package/dist/host/types/src/ui/lib/base-path.d.ts +1 -0
  138. package/dist/host/types/src/ui/lib/clipboard-provider.d.ts +9 -0
  139. package/dist/host/types/src/ui/lib/clipboard.d.ts +20 -0
  140. package/dist/host/types/src/ui/lib/connection-origin.d.ts +47 -0
  141. package/dist/host/types/src/ui/lib/db-health-monitor.d.ts +34 -0
  142. package/dist/host/types/src/ui/lib/device-id.d.ts +1 -0
  143. package/dist/host/types/src/ui/lib/electron.d.ts +1 -0
  144. package/dist/host/types/src/ui/lib/embedded-server-status.d.ts +15 -0
  145. package/dist/host/types/src/ui/lib/error-message.d.ts +9 -0
  146. package/dist/host/types/src/ui/lib/frontend-logger.d.ts +67 -0
  147. package/dist/host/types/src/ui/lib/global-shortcut-handler.d.ts +3 -0
  148. package/dist/host/types/src/ui/lib/host-defaults-context.d.ts +48 -0
  149. package/dist/host/types/src/ui/lib/hosts-request-cache.d.ts +9 -0
  150. package/dist/host/types/src/ui/lib/keybinding-match.d.ts +3 -0
  151. package/dist/host/types/src/ui/lib/linked-server.d.ts +10 -0
  152. package/dist/host/types/src/ui/lib/local-adaptive-engine.d.ts +40 -0
  153. package/dist/host/types/src/ui/lib/permissions.d.ts +16 -0
  154. package/dist/host/types/src/ui/lib/plugin-i18n.d.ts +11 -0
  155. package/dist/host/types/src/ui/lib/plugin-icon.d.ts +11 -0
  156. package/dist/host/types/src/ui/lib/plugin-transport.d.ts +50 -0
  157. package/dist/host/types/src/ui/lib/registry.d.ts +29 -0
  158. package/dist/host/types/src/ui/lib/rem.d.ts +7 -0
  159. package/dist/host/types/src/ui/lib/remote-server-api.d.ts +11 -0
  160. package/dist/host/types/src/ui/lib/server-status-store.d.ts +37 -0
  161. package/dist/host/types/src/ui/lib/sync-events.d.ts +2 -0
  162. package/dist/host/types/src/ui/lib/sync-status.d.ts +6 -0
  163. package/dist/host/types/src/ui/lib/tab-jump-hotkey.d.ts +4 -0
  164. package/dist/host/types/src/ui/lib/theme.d.ts +21 -0
  165. package/dist/host/types/src/ui/lib/ttl-request-cache.d.ts +10 -0
  166. package/dist/host/types/src/ui/lib/usage-color.d.ts +5 -0
  167. package/dist/host/types/src/ui/lib/utils.d.ts +2 -0
  168. package/dist/host/types/src/ui/lib/visible-interval.d.ts +5 -0
  169. package/dist/host/types/src/ui/lib/widget-title.d.ts +8 -0
  170. package/dist/host/types/src/ui/lib/ws-auth.d.ts +1 -0
  171. package/dist/host/types/src/ui/main-axios.d.ts +170 -0
  172. package/dist/host/types/src/ui/plugin-host/PluginViewPlaceholder.d.ts +13 -0
  173. package/dist/host/types/src/ui/plugin-host/app.d.ts +18 -0
  174. package/dist/host/types/src/ui/plugin-host/auth-registry.d.ts +50 -0
  175. package/dist/host/types/src/ui/plugin-host/bridge.d.ts +26 -0
  176. package/dist/host/types/src/ui/plugin-host/component-registry.d.ts +24 -0
  177. package/dist/host/types/src/ui/plugin-host/desktop.d.ts +7 -0
  178. package/dist/host/types/src/ui/plugin-host/extension-registry.d.ts +16 -0
  179. package/dist/host/types/src/ui/plugin-host/host-scope.d.ts +26 -0
  180. package/dist/host/types/src/ui/plugin-host/loader.d.ts +52 -0
  181. package/dist/host/types/src/ui/plugin-host/plugin-store.d.ts +40 -0
  182. package/dist/host/types/src/ui/plugin-host/scope.d.ts +20 -0
  183. package/dist/host/types/src/ui/plugin-host/sdk-ui.d.ts +99 -0
  184. package/dist/host/types/src/ui/plugin-host/shell-bridge.d.ts +67 -0
  185. package/dist/host/types/src/ui/plugin-host/view-ownership.d.ts +31 -0
  186. package/dist/host/types/src/ui/settings/HostPluginSections.d.ts +31 -0
  187. package/dist/host/types/src/ui/settings/SettingsFields.d.ts +22 -0
  188. package/dist/host/types/src/ui/settings/host-feature-tabs.d.ts +11 -0
  189. package/dist/host/types/src/ui/settings/settings-components.d.ts +32 -0
  190. package/dist/host/types/src/ui/settings/settings-fields-util.d.ts +11 -0
  191. package/dist/host/types/src/ui/shell/ActionSlot.d.ts +41 -0
  192. package/dist/host/types/src/ui/shell/TabContext.d.ts +30 -0
  193. package/dist/host/types/src/ui/shell/action-registry.d.ts +99 -0
  194. package/dist/host/types/src/ui/shell/keybinding-registry.d.ts +62 -0
  195. package/dist/host/types/src/ui/shell/palette-registry.d.ts +55 -0
  196. package/dist/host/types/src/ui/shell/panel-registry.d.ts +24 -0
  197. package/dist/host/types/src/ui/shell/tab-registry.d.ts +92 -0
  198. package/dist/host/types/src/ui/sidebar/HostManagerTabs.d.ts +65 -0
  199. package/dist/host/types/src/ui/sidebar/host-contributions.d.ts +72 -0
  200. package/dist/host/types/src/ui/sidebar/host-defaults/catalog.d.ts +17 -0
  201. package/dist/host/types/src/ui/sidebar/host-protocols.d.ts +49 -0
  202. package/dist/host/types/src/ui/sidebar/quick-connect-host.d.ts +21 -0
  203. package/dist/host/types/src/ui/sidebar/rail-items.d.ts +103 -0
  204. package/dist/host/types/src/ui/ssh/connection-log/ConnectionLogContext.d.ts +17 -0
  205. package/dist/host/types/src/ui/ssh/dialogs/BrowserSignInDialog.d.ts +18 -0
  206. package/dist/host/types/src/ui/ssh/dialogs/HostKeyVerificationDialog.d.ts +17 -0
  207. package/dist/host/types/src/ui/ssh/dialogs/PassphraseDialog.d.ts +15 -0
  208. package/dist/host/types/src/ui/ssh/dialogs/SSHAuthDialog.d.ts +20 -0
  209. package/dist/host/types/src/ui/ssh/dialogs/TOTPDialog.d.ts +13 -0
  210. package/dist/host/ui.d.ts +1 -0
  211. package/dist/host/ui.js +31754 -0
  212. package/dist/host/use-permissions-NULZL43J.js +18 -0
  213. package/dist/host-commands.d.ts +89 -0
  214. package/dist/host-commands.js +387 -0
  215. package/dist/manifest.d.ts +461 -0
  216. package/dist/manifest.js +1499 -0
  217. package/dist/settings.d.ts +43 -0
  218. package/dist/settings.js +168 -0
  219. package/dist/ssh-certs.d.ts +25 -0
  220. package/dist/ssh-certs.js +234 -0
  221. package/dist/ssh-options.d.ts +23 -0
  222. package/dist/ssh-options.js +1 -0
  223. package/dist/table-builder.d.ts +26 -0
  224. package/dist/table-builder.js +87 -0
  225. package/dist/testing/safe-ports.d.ts +10 -0
  226. package/dist/testing/safe-ports.js +59 -0
  227. package/dist/testing/setup.d.ts +7 -0
  228. package/dist/testing/setup.js +146 -0
  229. package/dist/testing.d.ts +537 -0
  230. package/dist/testing.js +1451 -0
  231. package/dist/vitest-preset.d.ts +54 -0
  232. package/dist/vitest-preset.js +96 -0
  233. package/package.json +143 -0
  234. package/schema/changelog.schema.json +67 -0
  235. package/schema/manifest.schema.json +1160 -0
  236. package/tsconfig.plugin.json +31 -0
@@ -0,0 +1,18 @@
1
+ const __termixNoGlob = () => ({});
2
+ import {
3
+ hasPermission,
4
+ notifyPermissionsChanged,
5
+ preloadPermissions,
6
+ resetPermissionsCache,
7
+ setPermissionsForTesting,
8
+ usePermissions
9
+ } from "./chunk-XGWE7BTJ.js";
10
+ import "./chunk-YC2MAKSP.js";
11
+ export {
12
+ hasPermission,
13
+ notifyPermissionsChanged,
14
+ preloadPermissions,
15
+ resetPermissionsCache,
16
+ setPermissionsForTesting,
17
+ usePermissions
18
+ };
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Generic host command helpers: platform detection, package manager actions
3
+ * and the shell-argument validators that guard them.
4
+ *
5
+ * Pure functions and thin ssh2 wrappers, no core imports. fleets and
6
+ * host-metrics both build their platform/package features on this instead of
7
+ * duplicating it or reaching into core.
8
+ *
9
+ * A plugin passes the `Client` it got from ctx.ssh.connect/withConnection.
10
+ */
11
+ export type { Client, ClientChannel } from "ssh2";
12
+ import type { Client } from "ssh2";
13
+ export interface ExecResult {
14
+ stdout: string;
15
+ stderr: string;
16
+ code: number | null;
17
+ }
18
+ /** Runs one non-interactive command over an existing ssh2 client. */
19
+ export declare function execCommand(client: Client, command: string, timeoutMs?: number): Promise<ExecResult>;
20
+ export type PackageManager = "apt" | "dnf" | "yum" | "pacman" | null;
21
+ export interface PlatformInfo {
22
+ hasSystemd: boolean;
23
+ pkg: PackageManager;
24
+ hasCertbot: boolean;
25
+ hasAcmeSh: boolean;
26
+ hasDocker: boolean;
27
+ osPrettyName: string | null;
28
+ }
29
+ /**
30
+ * Non-interactive SSH shells don't source ~/.zprofile or ~/.bash_profile,
31
+ * so PATH additions from installers like Homebrew or OrbStack are missing.
32
+ * Extend PATH with their common install locations before probing.
33
+ */
34
+ export declare const EXTRA_PATH_DIRS = "/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:\"$HOME/.orbstack/bin\"";
35
+ /**
36
+ * Single probe that reports which tooling is available. Each line is
37
+ * "key=value" so the parser is trivial and order-independent.
38
+ */
39
+ export declare const PLATFORM_PROBE_COMMAND: string;
40
+ export declare function parsePlatformProbe(output: string): PlatformInfo;
41
+ export declare function detectPlatform(client: Client): Promise<PlatformInfo>;
42
+ export type HostPlatform = "darwin" | "linux" | "windows" | "other";
43
+ export declare function encodePowerShellCommand(script: string): string;
44
+ export declare function execPowerShell(client: Client, script: string, timeoutMs?: number): Promise<ExecResult>;
45
+ export declare function detectHostPlatform(client: Client): Promise<HostPlatform>;
46
+ export interface UpgradablePackage {
47
+ name: string;
48
+ currentVersion?: string;
49
+ newVersion?: string;
50
+ }
51
+ export declare function buildListUpgradableCommand(pkg: PackageManager): string | null;
52
+ export declare function parseUpgradable(pkg: PackageManager, output: string): UpgradablePackage[];
53
+ export type PackageAction = "upgrade-all" | "install" | "upgrade";
54
+ export declare function buildPackageActionCommand(pkg: PackageManager, action: PackageAction, name?: string): string | null;
55
+ export declare function buildPackageRemoveCommand(pkg: PackageManager, name: string): string | null;
56
+ export declare function isValidPackageName(pkg: unknown): pkg is string;
57
+ export type ElevationErrorCode = "SUDO_REQUIRED" | "SUDO_FAILED" | "NOT_SUDOER";
58
+ export declare class ElevationError extends Error {
59
+ code: ElevationErrorCode;
60
+ constructor(code: ElevationErrorCode, message: string);
61
+ }
62
+ export interface ElevatedResult {
63
+ stdout: string;
64
+ stderr: string;
65
+ code: number | null;
66
+ usedSudo: boolean;
67
+ }
68
+ /** Escape a value for single-quoted shell context. */
69
+ export declare function shellSingleQuote(value: string): string;
70
+ /**
71
+ * Build the elevated command string:
72
+ * echo '<pw>' | sudo -S -p '' sh -c 'echo __TX_SUDO_OK__; <command>'
73
+ *
74
+ * `-p ''` suppresses the prompt. stderr is NOT merged into stdout, so the
75
+ * command's own output stays clean and sudo's auth errors stay on stderr. The
76
+ * marker is echoed by the inner shell once sudo authenticates, letting us
77
+ * tell a genuine auth failure from command output that merely contains scary
78
+ * words.
79
+ */
80
+ export declare function buildSudoCommand(command: string, sudoPassword: string): string;
81
+ /**
82
+ * Run a command on a client, elevating with the host's stored sudo password
83
+ * only when needed (or when forced). Throws a typed `ElevationError` when
84
+ * elevation is required but unavailable/incorrect.
85
+ */
86
+ export declare function execElevated(client: Client, command: string, sudoPassword: string | undefined, opts?: {
87
+ forceSudo?: boolean;
88
+ timeoutMs?: number;
89
+ }): Promise<ElevatedResult>;
@@ -0,0 +1,387 @@
1
+ /**
2
+ * Generic host command helpers: platform detection, package manager actions
3
+ * and the shell-argument validators that guard them.
4
+ *
5
+ * Pure functions and thin ssh2 wrappers, no core imports. fleets and
6
+ * host-metrics both build their platform/package features on this instead of
7
+ * duplicating it or reaching into core.
8
+ *
9
+ * A plugin passes the `Client` it got from ctx.ssh.connect/withConnection.
10
+ */
11
+ /** Runs one non-interactive command over an existing ssh2 client. */
12
+ export function execCommand(client, command, timeoutMs = 30000) {
13
+ return new Promise((resolve, reject) => {
14
+ let settled = false;
15
+ let stream = null;
16
+ const timeout = setTimeout(() => {
17
+ if (!settled) {
18
+ settled = true;
19
+ cleanup();
20
+ reject(new Error(`Command timeout after ${timeoutMs}ms: ${command}`));
21
+ }
22
+ }, timeoutMs);
23
+ const cleanup = () => {
24
+ clearTimeout(timeout);
25
+ if (stream) {
26
+ try {
27
+ stream.removeAllListeners();
28
+ if (stream.stderr) {
29
+ stream.stderr.removeAllListeners();
30
+ }
31
+ stream.destroy();
32
+ }
33
+ catch {
34
+ // expected - cleanup errors ignored
35
+ }
36
+ }
37
+ };
38
+ client.exec(command, { pty: false }, (err, _stream) => {
39
+ if (err) {
40
+ if (!settled) {
41
+ settled = true;
42
+ cleanup();
43
+ reject(err);
44
+ }
45
+ return;
46
+ }
47
+ stream = _stream;
48
+ let stdout = "";
49
+ let stderr = "";
50
+ let exitCode = null;
51
+ stream
52
+ .on("close", (code) => {
53
+ if (!settled) {
54
+ settled = true;
55
+ exitCode = typeof code === "number" ? code : null;
56
+ cleanup();
57
+ resolve({ stdout, stderr, code: exitCode });
58
+ }
59
+ })
60
+ .on("data", (data) => {
61
+ stdout += data.toString("utf8");
62
+ })
63
+ .on("error", (streamErr) => {
64
+ if (!settled) {
65
+ settled = true;
66
+ cleanup();
67
+ reject(streamErr);
68
+ }
69
+ });
70
+ if (stream.stderr) {
71
+ stream.stderr
72
+ .on("data", (data) => {
73
+ stderr += data.toString("utf8");
74
+ })
75
+ .on("error", (stderrErr) => {
76
+ if (!settled) {
77
+ settled = true;
78
+ cleanup();
79
+ reject(stderrErr);
80
+ }
81
+ });
82
+ }
83
+ });
84
+ });
85
+ }
86
+ /**
87
+ * Non-interactive SSH shells don't source ~/.zprofile or ~/.bash_profile,
88
+ * so PATH additions from installers like Homebrew or OrbStack are missing.
89
+ * Extend PATH with their common install locations before probing.
90
+ */
91
+ export const EXTRA_PATH_DIRS = '/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:"$HOME/.orbstack/bin"';
92
+ const DOCKER_PROBE = `command -v docker >/dev/null 2>&1` +
93
+ ` || [ -S /var/run/docker.sock ]` +
94
+ ` || [ -S "$HOME/.orbstack/run/docker.sock" ]`;
95
+ /**
96
+ * Single probe that reports which tooling is available. Each line is
97
+ * "key=value" so the parser is trivial and order-independent.
98
+ */
99
+ export const PLATFORM_PROBE_COMMAND = [
100
+ `PATH="${EXTRA_PATH_DIRS}:$PATH"`,
101
+ "echo systemd=$(command -v systemctl >/dev/null 2>&1 && echo 1 || echo 0)",
102
+ "echo apt=$(command -v apt-get >/dev/null 2>&1 && echo 1 || echo 0)",
103
+ "echo dnf=$(command -v dnf >/dev/null 2>&1 && echo 1 || echo 0)",
104
+ "echo yum=$(command -v yum >/dev/null 2>&1 && echo 1 || echo 0)",
105
+ "echo pacman=$(command -v pacman >/dev/null 2>&1 && echo 1 || echo 0)",
106
+ "echo certbot=$(command -v certbot >/dev/null 2>&1 && echo 1 || echo 0)",
107
+ 'echo acmesh=$( { command -v acme.sh >/dev/null 2>&1 || [ -x "$HOME/.acme.sh/acme.sh" ]; } && echo 1 || echo 0)',
108
+ `echo docker=$(${DOCKER_PROBE} && echo 1 || echo 0)`,
109
+ 'echo os=$(. /etc/os-release 2>/dev/null && echo "$PRETTY_NAME")',
110
+ ].join("; ");
111
+ export function parsePlatformProbe(output) {
112
+ const map = new Map();
113
+ for (const line of output.split("\n")) {
114
+ const idx = line.indexOf("=");
115
+ if (idx === -1)
116
+ continue;
117
+ map.set(line.slice(0, idx).trim(), line.slice(idx + 1).trim());
118
+ }
119
+ const on = (k) => map.get(k) === "1";
120
+ // Prefer dnf over yum when both exist (dnf is the modern front-end).
121
+ let pkg = null;
122
+ if (on("apt"))
123
+ pkg = "apt";
124
+ else if (on("dnf"))
125
+ pkg = "dnf";
126
+ else if (on("yum"))
127
+ pkg = "yum";
128
+ else if (on("pacman"))
129
+ pkg = "pacman";
130
+ const os = map.get("os");
131
+ return {
132
+ hasSystemd: on("systemd"),
133
+ pkg,
134
+ hasCertbot: on("certbot"),
135
+ hasAcmeSh: on("acmesh"),
136
+ hasDocker: on("docker"),
137
+ osPrettyName: os && os.length > 0 ? os : null,
138
+ };
139
+ }
140
+ export async function detectPlatform(client) {
141
+ const { stdout } = await execCommand(client, PLATFORM_PROBE_COMMAND, 15000);
142
+ return parsePlatformProbe(stdout);
143
+ }
144
+ // Windows OpenSSH's default shell is usually cmd.exe (occasionally
145
+ // PowerShell), so scripts are sent base64-encoded via -EncodedCommand -
146
+ // that avoids every cmd.exe quoting/escaping pitfall entirely, since the
147
+ // argument ends up being plain alphanumeric text with no shell metacharacters.
148
+ export function encodePowerShellCommand(script) {
149
+ return Buffer.from(script, "utf16le").toString("base64");
150
+ }
151
+ export function execPowerShell(client, script, timeoutMs = 30000) {
152
+ const encoded = encodePowerShellCommand(script);
153
+ return execCommand(client, `powershell -NoProfile -NonInteractive -EncodedCommand ${encoded}`, timeoutMs);
154
+ }
155
+ export async function detectHostPlatform(client) {
156
+ try {
157
+ const { stdout, code } = await execCommand(client, "uname -s", 10000);
158
+ const kernel = stdout.trim().toLowerCase();
159
+ if (kernel === "darwin")
160
+ return "darwin";
161
+ if (kernel === "linux")
162
+ return "linux";
163
+ if (code === 0 && kernel)
164
+ return "other";
165
+ }
166
+ catch {
167
+ // expected on hosts with no POSIX shell (e.g. Windows via cmd.exe)
168
+ }
169
+ try {
170
+ const { stdout } = await execPowerShell(client, "'WIN_OK'", 10000);
171
+ if (stdout.includes("WIN_OK"))
172
+ return "windows";
173
+ }
174
+ catch {
175
+ // expected on hosts with no PowerShell available
176
+ }
177
+ return "other";
178
+ }
179
+ export function buildListUpgradableCommand(pkg) {
180
+ switch (pkg) {
181
+ case "apt":
182
+ return "apt list --upgradable 2>/dev/null | tail -n +2";
183
+ case "dnf":
184
+ return "dnf -q check-update 2>/dev/null || true";
185
+ case "yum":
186
+ return "yum -q check-update 2>/dev/null || true";
187
+ case "pacman":
188
+ return "pacman -Qu 2>/dev/null || true";
189
+ default:
190
+ return null;
191
+ }
192
+ }
193
+ export function parseUpgradable(pkg, output) {
194
+ const out = [];
195
+ const lines = output
196
+ .split("\n")
197
+ .map((l) => l.trim())
198
+ .filter(Boolean);
199
+ if (pkg === "apt") {
200
+ for (const line of lines) {
201
+ // name/suite newver arch [upgradable from: oldver]
202
+ const m = line.match(/^([^/\s]+)\/\S+\s+(\S+)\s+\S+(?:\s+\[upgradable from:\s+(\S+)\])?/);
203
+ if (m)
204
+ out.push({ name: m[1], newVersion: m[2], currentVersion: m[3] });
205
+ }
206
+ }
207
+ else if (pkg === "dnf" || pkg === "yum") {
208
+ for (const line of lines) {
209
+ if (/^(Last metadata|Obsoleting|Security:)/i.test(line))
210
+ continue;
211
+ const m = line.match(/^(\S+)\s+(\S+)\s+\S+$/);
212
+ if (m && m[1].includes("."))
213
+ out.push({ name: m[1], newVersion: m[2] });
214
+ }
215
+ }
216
+ else if (pkg === "pacman") {
217
+ for (const line of lines) {
218
+ const m = line.match(/^(\S+)\s+(\S+)\s+->\s+(\S+)$/);
219
+ if (m)
220
+ out.push({ name: m[1], currentVersion: m[2], newVersion: m[3] });
221
+ }
222
+ }
223
+ return out;
224
+ }
225
+ export function buildPackageActionCommand(pkg, action, name) {
226
+ const target = name ? ` ${name}` : "";
227
+ switch (pkg) {
228
+ case "apt":
229
+ if (action === "upgrade-all")
230
+ return "DEBIAN_FRONTEND=noninteractive apt-get -y upgrade";
231
+ return `DEBIAN_FRONTEND=noninteractive apt-get -y install${target}`;
232
+ case "dnf":
233
+ return action === "upgrade-all"
234
+ ? "dnf -y upgrade"
235
+ : `dnf -y install${target}`;
236
+ case "yum":
237
+ return action === "upgrade-all"
238
+ ? "yum -y update"
239
+ : `yum -y install${target}`;
240
+ case "pacman":
241
+ return action === "upgrade-all"
242
+ ? "pacman -Syu --noconfirm"
243
+ : `pacman -S --noconfirm${target}`;
244
+ default:
245
+ return null;
246
+ }
247
+ }
248
+ export function buildPackageRemoveCommand(pkg, name) {
249
+ switch (pkg) {
250
+ case "apt":
251
+ return `DEBIAN_FRONTEND=noninteractive apt-get -y remove ${name}`;
252
+ case "dnf":
253
+ return `dnf -y remove ${name}`;
254
+ case "yum":
255
+ return `yum -y remove ${name}`;
256
+ case "pacman":
257
+ return `pacman -R --noconfirm ${name}`;
258
+ default:
259
+ return null;
260
+ }
261
+ }
262
+ const PACKAGE_RE = /^[A-Za-z0-9][A-Za-z0-9.+_-]*$/;
263
+ export function isValidPackageName(pkg) {
264
+ return typeof pkg === "string" && pkg.length <= 128 && PACKAGE_RE.test(pkg);
265
+ }
266
+ export class ElevationError extends Error {
267
+ code;
268
+ constructor(code, message) {
269
+ super(message);
270
+ this.name = "ElevationError";
271
+ this.code = code;
272
+ }
273
+ }
274
+ const PERMISSION_DENIED = [
275
+ "permission denied",
276
+ "operation not permitted",
277
+ "must be run as root",
278
+ "must be superuser",
279
+ "you need to be root",
280
+ "are you root",
281
+ "access denied",
282
+ ];
283
+ /**
284
+ * Phrases sudo itself prints to STDERR when authentication or authorization
285
+ * fails. Matched only against sudo's own stderr (never command output), so a
286
+ * command that happens to print "permission denied" on stdout is not mistaken
287
+ * for a sudo failure.
288
+ */
289
+ const SUDO_AUTH_FAILED = [
290
+ "incorrect password",
291
+ "a password is required",
292
+ "a terminal is required",
293
+ "no tty present",
294
+ "sorry, try again",
295
+ "no password was provided",
296
+ "1 incorrect password attempt",
297
+ ];
298
+ const SUDO_NOT_SUDOER = [
299
+ "is not in the sudoers file",
300
+ "not allowed to run sudo",
301
+ "not allowed to execute",
302
+ ];
303
+ /**
304
+ * Marker printed only after sudo has successfully authenticated and started
305
+ * the inner shell. Its presence on stdout is the authoritative "elevation
306
+ * worked" signal; its absence (together with sudo stderr) means auth failed.
307
+ */
308
+ const SUDO_OK_MARKER = "__TX_SUDO_OK__";
309
+ const SUDO_OK_LINE_RE = new RegExp(`^${SUDO_OK_MARKER}\\r?\\n?`);
310
+ /** Escape a value for single-quoted shell context. */
311
+ export function shellSingleQuote(value) {
312
+ return `'${value.replace(/'/g, "'\"'\"'")}'`;
313
+ }
314
+ /**
315
+ * Build the elevated command string:
316
+ * echo '<pw>' | sudo -S -p '' sh -c 'echo __TX_SUDO_OK__; <command>'
317
+ *
318
+ * `-p ''` suppresses the prompt. stderr is NOT merged into stdout, so the
319
+ * command's own output stays clean and sudo's auth errors stay on stderr. The
320
+ * marker is echoed by the inner shell once sudo authenticates, letting us
321
+ * tell a genuine auth failure from command output that merely contains scary
322
+ * words.
323
+ */
324
+ export function buildSudoCommand(command, sudoPassword) {
325
+ const pw = shellSingleQuote(sudoPassword);
326
+ const inner = shellSingleQuote(`echo ${SUDO_OK_MARKER}; ${command}`);
327
+ return `echo ${pw} | sudo -S -p '' sh -c ${inner}`;
328
+ }
329
+ function includesAny(text, needles) {
330
+ const lower = text.toLowerCase();
331
+ return needles.some((n) => lower.includes(n));
332
+ }
333
+ function looksLikePermissionDenied(text) {
334
+ return includesAny(text, PERMISSION_DENIED);
335
+ }
336
+ /** Strip the success marker line from the front of stdout. */
337
+ function stripMarker(stdout) {
338
+ return stdout.replace(SUDO_OK_LINE_RE, "");
339
+ }
340
+ /**
341
+ * Run a command on a client, elevating with the host's stored sudo password
342
+ * only when needed (or when forced). Throws a typed `ElevationError` when
343
+ * elevation is required but unavailable/incorrect.
344
+ */
345
+ export async function execElevated(client, command, sudoPassword, opts = {}) {
346
+ const timeoutMs = opts.timeoutMs ?? 30000;
347
+ if (!opts.forceSudo) {
348
+ const direct = await execCommand(client, command, timeoutMs);
349
+ if (direct.code === 0) {
350
+ return { ...direct, usedSudo: false };
351
+ }
352
+ // Only escalate when the failure looks like a privilege problem. A
353
+ // permission-denied phrase on stderr (not arbitrary stdout) is the signal.
354
+ if (!looksLikePermissionDenied(direct.stderr)) {
355
+ return { ...direct, usedSudo: false };
356
+ }
357
+ if (!sudoPassword) {
358
+ throw new ElevationError("SUDO_REQUIRED", "This action requires elevated privileges. Set a sudo password for this host to continue.");
359
+ }
360
+ }
361
+ else if (!sudoPassword) {
362
+ throw new ElevationError("SUDO_REQUIRED", "This action requires elevated privileges. Set a sudo password for this host to continue.");
363
+ }
364
+ const sudoCmd = buildSudoCommand(command, sudoPassword);
365
+ const result = await execCommand(client, sudoCmd, timeoutMs);
366
+ const authenticated = result.stdout.includes(SUDO_OK_MARKER);
367
+ if (!authenticated) {
368
+ // Elevation never started: diagnose from sudo's stderr only.
369
+ const notSudoer = includesAny(result.stderr, SUDO_NOT_SUDOER);
370
+ const authFailed = includesAny(result.stderr, SUDO_AUTH_FAILED);
371
+ if (notSudoer) {
372
+ throw new ElevationError("NOT_SUDOER", "The connected user is not permitted to use sudo on this host.");
373
+ }
374
+ if (authFailed) {
375
+ throw new ElevationError("SUDO_FAILED", "Elevation failed. Check the host's sudo password.");
376
+ }
377
+ // No marker and no recognizable sudo error: treat as a generic failure
378
+ // but keep the original output so the caller can surface it.
379
+ throw new ElevationError("SUDO_FAILED", "Elevation failed. Check the host's sudo password.");
380
+ }
381
+ return {
382
+ stdout: stripMarker(result.stdout),
383
+ stderr: result.stderr,
384
+ code: result.code,
385
+ usedSudo: true,
386
+ };
387
+ }