@hcu-lab.me/mcp-device 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 (261) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +51 -0
  3. package/dist/bootstrap.d.ts +1 -0
  4. package/dist/bootstrap.js +22 -0
  5. package/dist/command-manager.d.ts +8 -0
  6. package/dist/command-manager.js +241 -0
  7. package/dist/config-field-definitions.d.ts +41 -0
  8. package/dist/config-field-definitions.js +37 -0
  9. package/dist/config-manager.d.ts +102 -0
  10. package/dist/config-manager.js +291 -0
  11. package/dist/config.d.ts +6 -0
  12. package/dist/config.js +10 -0
  13. package/dist/custom-stdio.d.ts +51 -0
  14. package/dist/custom-stdio.js +358 -0
  15. package/dist/data/official-app-ca.pem +12 -0
  16. package/dist/data/onboarding-prompts.json +70 -0
  17. package/dist/device/device-state.d.ts +13 -0
  18. package/dist/device/device-state.js +100 -0
  19. package/dist/device/device-status.d.ts +81 -0
  20. package/dist/device/device-status.js +156 -0
  21. package/dist/device/device.d.ts +16 -0
  22. package/dist/device/device.js +190 -0
  23. package/dist/device/execution-engine.d.ts +144 -0
  24. package/dist/device/execution-engine.js +195 -0
  25. package/dist/device/gateway-channel.d.ts +48 -0
  26. package/dist/device/gateway-channel.js +384 -0
  27. package/dist/device/gateway-config.d.ts +40 -0
  28. package/dist/device/gateway-config.js +207 -0
  29. package/dist/device/gateway-identity.d.ts +35 -0
  30. package/dist/device/gateway-identity.js +178 -0
  31. package/dist/device/gateway-pairing.d.ts +25 -0
  32. package/dist/device/gateway-pairing.js +105 -0
  33. package/dist/device/gateway-secure-transport.d.ts +25 -0
  34. package/dist/device/gateway-secure-transport.js +117 -0
  35. package/dist/device/gateway-tool-adapter.d.ts +17 -0
  36. package/dist/device/gateway-tool-adapter.js +284 -0
  37. package/dist/device/gateway-url-policy.d.ts +3 -0
  38. package/dist/device/gateway-url-policy.js +43 -0
  39. package/dist/device/linux-service.d.ts +70 -0
  40. package/dist/device/linux-service.js +268 -0
  41. package/dist/device/official-trust.d.ts +7 -0
  42. package/dist/device/official-trust.js +49 -0
  43. package/dist/device/project-inspection.d.ts +1 -0
  44. package/dist/device/project-inspection.js +180 -0
  45. package/dist/device/runtime-owner.d.ts +41 -0
  46. package/dist/device/runtime-owner.js +177 -0
  47. package/dist/device/windows-dpapi.d.ts +11 -0
  48. package/dist/device/windows-dpapi.js +67 -0
  49. package/dist/device/windows-service.d.ts +56 -0
  50. package/dist/device/windows-service.js +238 -0
  51. package/dist/error-handlers.d.ts +7 -0
  52. package/dist/error-handlers.js +15 -0
  53. package/dist/handlers/edit-search-handlers.d.ts +6 -0
  54. package/dist/handlers/edit-search-handlers.js +6 -0
  55. package/dist/handlers/filesystem-handlers.d.ts +38 -0
  56. package/dist/handlers/filesystem-handlers.js +440 -0
  57. package/dist/handlers/history-handlers.d.ts +12 -0
  58. package/dist/handlers/history-handlers.js +67 -0
  59. package/dist/handlers/index.d.ts +6 -0
  60. package/dist/handlers/index.js +7 -0
  61. package/dist/handlers/process-handlers.d.ts +9 -0
  62. package/dist/handlers/process-handlers.js +15 -0
  63. package/dist/handlers/search-handlers.d.ts +17 -0
  64. package/dist/handlers/search-handlers.js +224 -0
  65. package/dist/handlers/terminal-handlers.d.ts +21 -0
  66. package/dist/handlers/terminal-handlers.js +35 -0
  67. package/dist/index.d.ts +2 -0
  68. package/dist/index.js +138 -0
  69. package/dist/mcp-device.d.ts +2 -0
  70. package/dist/mcp-device.js +7 -0
  71. package/dist/npm-scripts/remote.d.ts +3 -0
  72. package/dist/npm-scripts/remote.js +314 -0
  73. package/dist/search-manager.d.ts +147 -0
  74. package/dist/search-manager.js +828 -0
  75. package/dist/server.d.ts +46 -0
  76. package/dist/server.js +1563 -0
  77. package/dist/terminal-manager.d.ts +104 -0
  78. package/dist/terminal-manager.js +635 -0
  79. package/dist/tools/config.d.ts +96 -0
  80. package/dist/tools/config.js +260 -0
  81. package/dist/tools/edit.d.ts +38 -0
  82. package/dist/tools/edit.js +435 -0
  83. package/dist/tools/feedback.d.ts +8 -0
  84. package/dist/tools/feedback.js +132 -0
  85. package/dist/tools/filesystem.d.ts +79 -0
  86. package/dist/tools/filesystem.js +947 -0
  87. package/dist/tools/fuzzySearch.d.ts +12 -0
  88. package/dist/tools/fuzzySearch.js +74 -0
  89. package/dist/tools/fuzzySearchCore.d.ts +52 -0
  90. package/dist/tools/fuzzySearchCore.js +125 -0
  91. package/dist/tools/improved-process-tools.d.ts +24 -0
  92. package/dist/tools/improved-process-tools.js +627 -0
  93. package/dist/tools/mime-types.d.ts +3 -0
  94. package/dist/tools/mime-types.js +28 -0
  95. package/dist/tools/pdf/extract-images.d.ts +34 -0
  96. package/dist/tools/pdf/extract-images.js +132 -0
  97. package/dist/tools/pdf/index.d.ts +6 -0
  98. package/dist/tools/pdf/index.js +3 -0
  99. package/dist/tools/pdf/lib/pdf2md.d.ts +36 -0
  100. package/dist/tools/pdf/lib/pdf2md.js +76 -0
  101. package/dist/tools/pdf/manipulations.d.ts +13 -0
  102. package/dist/tools/pdf/manipulations.js +99 -0
  103. package/dist/tools/pdf/markdown.d.ts +26 -0
  104. package/dist/tools/pdf/markdown.js +275 -0
  105. package/dist/tools/pdf/utils.d.ts +12 -0
  106. package/dist/tools/pdf/utils.js +34 -0
  107. package/dist/tools/process.d.ts +3 -0
  108. package/dist/tools/process.js +56 -0
  109. package/dist/tools/prompts.d.ts +28 -0
  110. package/dist/tools/prompts.js +259 -0
  111. package/dist/tools/schemas.d.ts +454 -0
  112. package/dist/tools/schemas.js +242 -0
  113. package/dist/tools/usage.d.ts +5 -0
  114. package/dist/tools/usage.js +24 -0
  115. package/dist/types.d.ts +83 -0
  116. package/dist/types.js +1 -0
  117. package/dist/ui/config-editor/config-editor-runtime.js +150 -0
  118. package/dist/ui/config-editor/index.html +13 -0
  119. package/dist/ui/config-editor/src/app.d.ts +43 -0
  120. package/dist/ui/config-editor/src/app.js +831 -0
  121. package/dist/ui/config-editor/src/array-modal.d.ts +19 -0
  122. package/dist/ui/config-editor/src/array-modal.js +185 -0
  123. package/dist/ui/config-editor/src/main.d.ts +1 -0
  124. package/dist/ui/config-editor/src/main.js +2 -0
  125. package/dist/ui/config-editor/styles.css +587 -0
  126. package/dist/ui/contracts.d.ts +14 -0
  127. package/dist/ui/contracts.js +21 -0
  128. package/dist/ui/file-preview/index.html +16 -0
  129. package/dist/ui/file-preview/preview-runtime.js +469 -0
  130. package/dist/ui/file-preview/shared/preview-file-types.d.ts +5 -0
  131. package/dist/ui/file-preview/shared/preview-file-types.js +59 -0
  132. package/dist/ui/file-preview/src/app.d.ts +4 -0
  133. package/dist/ui/file-preview/src/app.js +680 -0
  134. package/dist/ui/file-preview/src/components/code-viewer.d.ts +6 -0
  135. package/dist/ui/file-preview/src/components/code-viewer.js +73 -0
  136. package/dist/ui/file-preview/src/components/highlighting.d.ts +2 -0
  137. package/dist/ui/file-preview/src/components/highlighting.js +54 -0
  138. package/dist/ui/file-preview/src/components/html-renderer.d.ts +5 -0
  139. package/dist/ui/file-preview/src/components/html-renderer.js +47 -0
  140. package/dist/ui/file-preview/src/components/markdown-renderer.d.ts +1 -0
  141. package/dist/ui/file-preview/src/components/markdown-renderer.js +59 -0
  142. package/dist/ui/file-preview/src/directory-controller.d.ts +8 -0
  143. package/dist/ui/file-preview/src/directory-controller.js +240 -0
  144. package/dist/ui/file-preview/src/document-layout.d.ts +20 -0
  145. package/dist/ui/file-preview/src/document-layout.js +109 -0
  146. package/dist/ui/file-preview/src/document-outline.d.ts +17 -0
  147. package/dist/ui/file-preview/src/document-outline.js +97 -0
  148. package/dist/ui/file-preview/src/document-workspace.d.ts +19 -0
  149. package/dist/ui/file-preview/src/document-workspace.js +33 -0
  150. package/dist/ui/file-preview/src/file-type-handlers.d.ts +10 -0
  151. package/dist/ui/file-preview/src/file-type-handlers.js +109 -0
  152. package/dist/ui/file-preview/src/host/external-actions.d.ts +8 -0
  153. package/dist/ui/file-preview/src/host/external-actions.js +55 -0
  154. package/dist/ui/file-preview/src/host/selection-context.d.ts +9 -0
  155. package/dist/ui/file-preview/src/host/selection-context.js +106 -0
  156. package/dist/ui/file-preview/src/image-preview.d.ts +3 -0
  157. package/dist/ui/file-preview/src/image-preview.js +21 -0
  158. package/dist/ui/file-preview/src/main.d.ts +1 -0
  159. package/dist/ui/file-preview/src/main.js +5 -0
  160. package/dist/ui/file-preview/src/markdown/conflict-dialog.d.ts +40 -0
  161. package/dist/ui/file-preview/src/markdown/conflict-dialog.js +163 -0
  162. package/dist/ui/file-preview/src/markdown/controller.d.ts +44 -0
  163. package/dist/ui/file-preview/src/markdown/controller.js +1043 -0
  164. package/dist/ui/file-preview/src/markdown/editor.d.ts +131 -0
  165. package/dist/ui/file-preview/src/markdown/editor.js +1479 -0
  166. package/dist/ui/file-preview/src/markdown/linking.d.ts +16 -0
  167. package/dist/ui/file-preview/src/markdown/linking.js +228 -0
  168. package/dist/ui/file-preview/src/markdown/outline.d.ts +2 -0
  169. package/dist/ui/file-preview/src/markdown/outline.js +16 -0
  170. package/dist/ui/file-preview/src/markdown/parser.d.ts +30 -0
  171. package/dist/ui/file-preview/src/markdown/parser.js +38 -0
  172. package/dist/ui/file-preview/src/markdown/preview.d.ts +1 -0
  173. package/dist/ui/file-preview/src/markdown/preview.js +20 -0
  174. package/dist/ui/file-preview/src/markdown/slugify.d.ts +3 -0
  175. package/dist/ui/file-preview/src/markdown/slugify.js +31 -0
  176. package/dist/ui/file-preview/src/markdown/utils.d.ts +1 -0
  177. package/dist/ui/file-preview/src/markdown/utils.js +15 -0
  178. package/dist/ui/file-preview/src/model.d.ts +35 -0
  179. package/dist/ui/file-preview/src/model.js +1 -0
  180. package/dist/ui/file-preview/src/panel-actions.d.ts +17 -0
  181. package/dist/ui/file-preview/src/panel-actions.js +182 -0
  182. package/dist/ui/file-preview/src/path-utils.d.ts +6 -0
  183. package/dist/ui/file-preview/src/path-utils.js +64 -0
  184. package/dist/ui/file-preview/src/payload-utils.d.ts +11 -0
  185. package/dist/ui/file-preview/src/payload-utils.js +119 -0
  186. package/dist/ui/file-preview/src/types.d.ts +1 -0
  187. package/dist/ui/file-preview/src/types.js +1 -0
  188. package/dist/ui/file-preview/styles.css +1631 -0
  189. package/dist/ui/resources.d.ts +28 -0
  190. package/dist/ui/resources.js +86 -0
  191. package/dist/ui/shared/compact-row.d.ts +11 -0
  192. package/dist/ui/shared/compact-row.js +18 -0
  193. package/dist/ui/shared/escape-html.d.ts +4 -0
  194. package/dist/ui/shared/escape-html.js +11 -0
  195. package/dist/ui/shared/host-context.d.ts +15 -0
  196. package/dist/ui/shared/host-context.js +51 -0
  197. package/dist/ui/shared/tool-bridge.d.ts +30 -0
  198. package/dist/ui/shared/tool-bridge.js +137 -0
  199. package/dist/ui/shared/tool-shell.d.ts +25 -0
  200. package/dist/ui/shared/tool-shell.js +107 -0
  201. package/dist/ui/shared/ui-event-tracker.d.ts +9 -0
  202. package/dist/ui/shared/ui-event-tracker.js +27 -0
  203. package/dist/ui/shared/widget-state.d.ts +33 -0
  204. package/dist/ui/shared/widget-state.js +158 -0
  205. package/dist/utils/ab-test.d.ts +12 -0
  206. package/dist/utils/ab-test.js +104 -0
  207. package/dist/utils/capture.d.ts +35 -0
  208. package/dist/utils/capture.js +481 -0
  209. package/dist/utils/dockerPrompt.d.ts +17 -0
  210. package/dist/utils/dockerPrompt.js +53 -0
  211. package/dist/utils/feature-flags.d.ts +59 -0
  212. package/dist/utils/feature-flags.js +215 -0
  213. package/dist/utils/files/base.d.ts +173 -0
  214. package/dist/utils/files/base.js +5 -0
  215. package/dist/utils/files/binary.d.ts +21 -0
  216. package/dist/utils/files/binary.js +65 -0
  217. package/dist/utils/files/docx.d.ts +47 -0
  218. package/dist/utils/files/docx.js +679 -0
  219. package/dist/utils/files/excel.d.ts +24 -0
  220. package/dist/utils/files/excel.js +437 -0
  221. package/dist/utils/files/factory.d.ts +41 -0
  222. package/dist/utils/files/factory.js +113 -0
  223. package/dist/utils/files/image.d.ts +21 -0
  224. package/dist/utils/files/image.js +78 -0
  225. package/dist/utils/files/index.d.ts +10 -0
  226. package/dist/utils/files/index.js +13 -0
  227. package/dist/utils/files/pdf.d.ts +32 -0
  228. package/dist/utils/files/pdf.js +142 -0
  229. package/dist/utils/files/text.d.ts +63 -0
  230. package/dist/utils/files/text.js +370 -0
  231. package/dist/utils/fuzzySearchLogger.d.ts +30 -0
  232. package/dist/utils/fuzzySearchLogger.js +126 -0
  233. package/dist/utils/lineEndingHandler.d.ts +21 -0
  234. package/dist/utils/lineEndingHandler.js +77 -0
  235. package/dist/utils/logger.d.ts +32 -0
  236. package/dist/utils/logger.js +72 -0
  237. package/dist/utils/mcp-ui-ab-test.d.ts +13 -0
  238. package/dist/utils/mcp-ui-ab-test.js +62 -0
  239. package/dist/utils/open-browser.d.ts +9 -0
  240. package/dist/utils/open-browser.js +42 -0
  241. package/dist/utils/process-detection.d.ts +23 -0
  242. package/dist/utils/process-detection.js +150 -0
  243. package/dist/utils/ripgrep-resolver.d.ts +9 -0
  244. package/dist/utils/ripgrep-resolver.js +74 -0
  245. package/dist/utils/system-info.d.ts +71 -0
  246. package/dist/utils/system-info.js +712 -0
  247. package/dist/utils/toolHistory.d.ts +86 -0
  248. package/dist/utils/toolHistory.js +257 -0
  249. package/dist/utils/trackTools.d.ts +6 -0
  250. package/dist/utils/trackTools.js +54 -0
  251. package/dist/utils/unsupportedParams.d.ts +24 -0
  252. package/dist/utils/unsupportedParams.js +66 -0
  253. package/dist/utils/usageTracker.d.ts +133 -0
  254. package/dist/utils/usageTracker.js +477 -0
  255. package/dist/utils/welcome-onboarding.d.ts +11 -0
  256. package/dist/utils/welcome-onboarding.js +99 -0
  257. package/dist/utils/withTimeout.d.ts +28 -0
  258. package/dist/utils/withTimeout.js +85 -0
  259. package/dist/version.d.ts +1 -0
  260. package/dist/version.js +1 -0
  261. package/package.json +131 -0
@@ -0,0 +1,156 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import { deviceStatePaths } from './device-state.js';
4
+ function defaultStatusPath() {
5
+ return deviceStatePaths().status;
6
+ }
7
+ function emptyStatus() {
8
+ return {
9
+ version: 1,
10
+ gatewayUrl: null,
11
+ deviceId: null,
12
+ deviceName: null,
13
+ identityPresent: false,
14
+ account: { connected: false, label: null },
15
+ connection: { online: false, connectionEpoch: null, lastConnectedAt: null },
16
+ usage: null,
17
+ schema: null,
18
+ usageFreshAt: null
19
+ };
20
+ }
21
+ function sanitizedStatus(value) {
22
+ const base = emptyStatus();
23
+ if (!value || typeof value !== 'object')
24
+ return base;
25
+ const usage = value.usage && typeof value.usage === 'object' ? {
26
+ connections: Number(value.usage.connections || 0),
27
+ reconnects: Number(value.usage.reconnects || 0),
28
+ toolCallsStarted: Number(value.usage.toolCallsStarted || 0),
29
+ toolCallsSucceeded: Number(value.usage.toolCallsSucceeded || 0),
30
+ toolCallsFailed: Number(value.usage.toolCallsFailed || 0),
31
+ requestBytes: Number(value.usage.requestBytes || 0),
32
+ responseBytes: Number(value.usage.responseBytes || 0),
33
+ lastSeenAt: value.usage.lastSeenAt == null ? null : Number(value.usage.lastSeenAt),
34
+ lastErrorCode: value.usage.lastErrorCode ? String(value.usage.lastErrorCode).slice(0, 64) : null
35
+ } : null;
36
+ const schema = value.schema && typeof value.schema === 'object' ? {
37
+ toolCount: Number(value.schema.toolCount || 0),
38
+ toolSchemaBytes: Number(value.schema.toolSchemaBytes || 0),
39
+ toolSchemaTokenEstimate: Number(value.schema.toolSchemaTokenEstimate || 0),
40
+ tokenEstimateMethod: String(value.schema.tokenEstimateMethod || 'utf8_bytes_div_4_estimate').slice(0, 64),
41
+ tokenUsageKind: 'schema_estimate_not_billing'
42
+ } : null;
43
+ return {
44
+ version: 1,
45
+ gatewayUrl: value.gatewayUrl ? String(value.gatewayUrl) : null,
46
+ deviceId: value.deviceId ? String(value.deviceId) : null,
47
+ deviceName: value.deviceName ? String(value.deviceName) : null,
48
+ identityPresent: value.identityPresent === true,
49
+ account: {
50
+ connected: value.account?.connected === true,
51
+ label: value.account?.label ? String(value.account.label).slice(0, 128) : null
52
+ },
53
+ connection: {
54
+ online: value.connection?.online === true,
55
+ connectionEpoch: value.connection?.connectionEpoch ?? null,
56
+ lastConnectedAt: value.connection?.lastConnectedAt == null ? null : Number(value.connection.lastConnectedAt)
57
+ },
58
+ usage,
59
+ schema,
60
+ usageFreshAt: value.usageFreshAt == null ? null : Number(value.usageFreshAt)
61
+ };
62
+ }
63
+ export class GatewayDeviceStatusStore {
64
+ constructor(statusPath = defaultStatusPath()) {
65
+ this.statusPath = path.resolve(statusPath);
66
+ }
67
+ async load() {
68
+ try {
69
+ return sanitizedStatus(JSON.parse(await fs.readFile(this.statusPath, 'utf8')));
70
+ }
71
+ catch (error) {
72
+ if (error?.code === 'ENOENT')
73
+ return emptyStatus();
74
+ throw error;
75
+ }
76
+ }
77
+ async update(patch) {
78
+ const current = await this.load();
79
+ const merged = sanitizedStatus({
80
+ ...current,
81
+ ...patch,
82
+ account: patch.account ? { ...current.account, ...patch.account } : current.account,
83
+ connection: patch.connection ? { ...current.connection, ...patch.connection } : current.connection,
84
+ usage: patch.usage === undefined ? current.usage : patch.usage,
85
+ schema: patch.schema === undefined ? current.schema : patch.schema
86
+ });
87
+ await this.persist(merged);
88
+ return merged;
89
+ }
90
+ async markOffline() {
91
+ const current = await this.load();
92
+ return await this.update({ connection: { ...current.connection, online: false } });
93
+ }
94
+ async clearAccount() {
95
+ return await this.update({ account: { connected: false, label: null } });
96
+ }
97
+ async persist(status) {
98
+ await fs.mkdir(path.dirname(this.statusPath), { recursive: true });
99
+ const temporary = `${this.statusPath}.${process.pid}.${Date.now()}.tmp`;
100
+ await fs.writeFile(temporary, JSON.stringify(status, null, 2), { mode: 0o600 });
101
+ await fs.rename(temporary, this.statusPath);
102
+ try {
103
+ await fs.chmod(this.statusPath, 0o600);
104
+ }
105
+ catch { /* Windows ACL inherits from user profile. */ }
106
+ }
107
+ }
108
+ function dateText(value) {
109
+ if (!value)
110
+ return 'never';
111
+ const date = new Date(value);
112
+ return Number.isNaN(date.getTime()) ? 'unknown' : date.toISOString();
113
+ }
114
+ export function effectiveGatewayStatus(status, { runtimeOwned = false, serviceRunning = false } = {}) {
115
+ if (runtimeOwned || serviceRunning || !status.connection.online)
116
+ return status;
117
+ return { ...status, connection: { ...status.connection, online: false } };
118
+ }
119
+ function securityChannelText(status, security) {
120
+ if (Math.max(1, Number(security.protocolFloor) || 1) < 2)
121
+ return 'legacy v1';
122
+ return status.connection.online ? 'v2 required/active' : 'v2 required/offline';
123
+ }
124
+ export function formatGatewayStatus(status, options = {}) {
125
+ const service = options.service || { installed: false, running: false };
126
+ const security = options.security || { protocolFloor: 1, appCaProvisioned: false };
127
+ const proxy = options.proxy || { mode: 'direct' };
128
+ const runtime = options.runtime || null;
129
+ const managers = options.managers || [];
130
+ const securityChannel = securityChannelText(status, security);
131
+ const payload = { ...status, service, runtime, managers, security: { ...security, channel: securityChannel }, proxy };
132
+ if (options.json)
133
+ return JSON.stringify(payload, null, 2);
134
+ const accountText = status.account.connected
135
+ ? `connected${status.account.label ? ` (${status.account.label})` : ''}`
136
+ : 'not connected';
137
+ const usage = status.usage;
138
+ const schema = status.schema;
139
+ return [
140
+ `Gateway: ${status.gatewayUrl || 'not configured'}`,
141
+ `Device: ${status.deviceName || status.deviceId || 'unknown'}${status.deviceId ? ` (${status.deviceId})` : ''}`,
142
+ `Identity: ${status.identityPresent ? 'present' : 'missing'}`,
143
+ `Account: ${accountText}`,
144
+ `Connection: ${status.connection.online ? 'online' : 'offline'}; last connected ${dateText(status.connection.lastConnectedAt)}`,
145
+ `Runtime: ${runtime ? `${runtime.mode} (pid ${runtime.pid})` : 'not running'}`,
146
+ `Service: ${service.installed ? (service.running ? 'installed/running' : 'installed/stopped') : 'not installed'}`,
147
+ `Startup: ${service.installed ? (service.autostart ? `autostart${service.registration && service.registration !== 'manual' ? ` (${service.registration})` : ''}` : 'manual') : (service.registration === 'stale' ? 'stale registration' : 'not installed')}`,
148
+ ...(managers.length ? managers.map(manager => `Manager: ${manager.manager} ${manager.installed ? 'installed' : 'not-installed'}/${manager.running ? 'running' : 'stopped'}/${manager.autostart ? 'autostart' : 'manual'}`) : []),
149
+ `Security: ${securityChannel}; app CA ${security.appCaProvisioned ? 'provisioned' : 'not provisioned'}`,
150
+ `Proxy: ${proxy.mode === 'configured' ? 'configured' : 'direct'}`,
151
+ `Usage: calls ${usage?.toolCallsStarted ?? 0} started / ${usage?.toolCallsSucceeded ?? 0} succeeded / ${usage?.toolCallsFailed ?? 0} failed; bytes ${usage?.requestBytes ?? 0} in / ${usage?.responseBytes ?? 0} out`,
152
+ `Schema: ${schema?.toolCount ?? 0} tools / ${schema?.toolSchemaBytes ?? 0} bytes`,
153
+ `Schema token estimate: ${schema?.toolSchemaTokenEstimate ?? 0} (${schema?.tokenEstimateMethod || 'unavailable'}; not ChatGPT billing usage)`,
154
+ `Usage fresh at: ${dateText(status.usageFreshAt)}`
155
+ ].join('\n');
156
+ }
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ import { type RuntimeOwnerStatus } from './runtime-owner.js';
3
+ export declare class MCPDevice {
4
+ private isShuttingDown;
5
+ private desktop;
6
+ private gatewayChannel?;
7
+ private gatewayProxyAgent?;
8
+ private runtimeOwner?;
9
+ constructor();
10
+ private setupShutdownHandlers;
11
+ start(options?: {
12
+ confirmTakeover?: (existing: RuntimeOwnerStatus) => Promise<boolean> | boolean;
13
+ }): Promise<void>;
14
+ shutdown(): Promise<void>;
15
+ }
16
+ export declare function isModuleEntrypoint(moduleUrl: string, argvEntry?: string, pmExecPath?: string): boolean;
@@ -0,0 +1,190 @@
1
+ #!/usr/bin/env node
2
+ import { fileURLToPath } from 'url';
3
+ import os from 'os';
4
+ import path from 'path';
5
+ import { DesktopCommanderIntegration } from './execution-engine.js';
6
+ import { GatewayDeviceChannel } from './gateway-channel.js';
7
+ import { GatewayDeviceConfigStore, createGatewayProxyAgent } from './gateway-config.js';
8
+ import { GatewayDeviceIdentity } from './gateway-identity.js';
9
+ import { pairGatewayDevice } from './gateway-pairing.js';
10
+ import { bootstrapOfficialGatewayTrust } from './official-trust.js';
11
+ import { GatewayDeviceStatusStore } from './device-status.js';
12
+ import { GatewayToolAdapter } from './gateway-tool-adapter.js';
13
+ import { RuntimeOwner, acquireRuntimeOwner } from './runtime-owner.js';
14
+ import { captureRemote } from '../utils/capture.js';
15
+ export class MCPDevice {
16
+ constructor() {
17
+ this.isShuttingDown = false;
18
+ this.desktop = new DesktopCommanderIntegration();
19
+ this.setupShutdownHandlers();
20
+ }
21
+ setupShutdownHandlers() {
22
+ const handleShutdown = async (signal) => {
23
+ if (this.isShuttingDown) {
24
+ console.log(`\n${signal} received, but already shutting down...`);
25
+ process.exit(1);
26
+ return;
27
+ }
28
+ console.log(`\n${signal} received, initiating graceful shutdown...`);
29
+ const forceExit = setTimeout(() => {
30
+ console.error('\n⚠️ Graceful shutdown timed out, forcing exit...');
31
+ process.exit(1);
32
+ }, 5000);
33
+ try {
34
+ await this.shutdown();
35
+ clearTimeout(forceExit);
36
+ process.exit(0);
37
+ }
38
+ catch (error) {
39
+ console.error('Error during shutdown:', error);
40
+ await captureRemote('remote_device_shutdown_handler_error', { error });
41
+ process.exit(1);
42
+ }
43
+ };
44
+ process.on('SIGINT', () => {
45
+ handleShutdown('SIGINT').catch((error) => {
46
+ console.error('Fatal error during shutdown:', error);
47
+ captureRemote('remote_device_shutdown_handler_error', { error, signal: 'SIGINT' }).catch(() => { });
48
+ process.exit(1);
49
+ });
50
+ });
51
+ process.on('SIGTERM', () => {
52
+ handleShutdown('SIGTERM').catch((error) => {
53
+ console.error('Fatal error during shutdown:', error);
54
+ captureRemote('remote_device_shutdown_handler_error', { error, signal: 'SIGTERM' }).catch(() => { });
55
+ process.exit(1);
56
+ });
57
+ });
58
+ }
59
+ async start(options = {}) {
60
+ try {
61
+ console.log('🚀 Starting MCP Device...');
62
+ if (process.env.DEBUG_MODE === 'true')
63
+ console.log(' - 🐞 DEBUG_MODE');
64
+ const managerArg = process.argv.find(value => value.startsWith('--manager='));
65
+ const manager = managerArg?.slice('--manager='.length);
66
+ const mode = process.argv.includes('--service')
67
+ ? (process.platform === 'win32' ? 'windows' : manager === 'pm2' ? 'pm2' : 'systemd')
68
+ : 'foreground';
69
+ this.runtimeOwner = new RuntimeOwner({ mode, onStop: () => this.shutdown() });
70
+ await acquireRuntimeOwner(this.runtimeOwner, { confirmTakeover: options.confirmTakeover });
71
+ await this.runtimeOwner.bootstrap();
72
+ await bootstrapOfficialGatewayTrust(new GatewayDeviceConfigStore());
73
+ await this.desktop.initialize();
74
+ const gatewayStatus = new GatewayDeviceStatusStore();
75
+ const storedGatewayStatus = await gatewayStatus.load();
76
+ const gatewayConfig = await new GatewayDeviceConfigStore().load();
77
+ const gatewayUrl = String(gatewayConfig.gatewayUrl || storedGatewayStatus.gatewayUrl || '').trim();
78
+ if (!gatewayUrl)
79
+ throw new Error('Gateway configuration is missing. Run `mcp-device login` or `mcp-device install` first.');
80
+ if (gatewayConfig.securityProtocolFloor >= 2 && !gatewayConfig.appCaPem) {
81
+ throw new Error('Gateway security protocol floor requires a provisioned application CA.');
82
+ }
83
+ const proxy = createGatewayProxyAgent(gatewayConfig);
84
+ this.gatewayProxyAgent = proxy.agent;
85
+ process.env.MCP_DEVICE_REMOTE = 'true';
86
+ console.log(`⏳ Connecting directly to MCP Gateway ${gatewayUrl}`);
87
+ const identity = new GatewayDeviceIdentity();
88
+ const identityRecord = await identity.loadOrCreate();
89
+ this.runtimeOwner.setDeviceId(identityRecord.deviceId);
90
+ const enrollmentCredential = String(process.env.MCP_GATEWAY_ENROLLMENT_TOKEN || process.env.MCP_DEVICE_ENROLLMENT_TOKEN || '').trim() || undefined;
91
+ let pairingGrant;
92
+ if (!identityRecord.enrolled && !enrollmentCredential) {
93
+ const pairing = await pairGatewayDevice({ gatewayUrl, identity, proxyAgent: proxy.agent, deviceName: os.hostname() });
94
+ pairingGrant = pairing.enrollmentGrant;
95
+ await gatewayStatus.update({
96
+ gatewayUrl,
97
+ deviceId: identityRecord.deviceId,
98
+ deviceName: os.hostname(),
99
+ identityPresent: true,
100
+ account: { connected: false, label: pairing.account.label },
101
+ connection: { ...storedGatewayStatus.connection, online: false }
102
+ });
103
+ console.log(`✓ Authorized account: ${pairing.account.label || 'connected'}`);
104
+ }
105
+ this.gatewayChannel = new GatewayDeviceChannel({
106
+ gatewayUrl,
107
+ enrollmentToken: enrollmentCredential,
108
+ pairingGrant,
109
+ identity,
110
+ proxyAgent: proxy.agent,
111
+ securityProtocolFloor: gatewayConfig.securityProtocolFloor,
112
+ appCaPem: gatewayConfig.appCaPem || undefined,
113
+ adapter: new GatewayToolAdapter(this.desktop, { allowedRoots: gatewayConfig.allowedRoots }),
114
+ agentVersion: process.env.npm_package_version,
115
+ onStatus: async (payload) => {
116
+ const record = await identity.loadOrCreate();
117
+ await gatewayStatus.update({
118
+ gatewayUrl,
119
+ deviceId: record.deviceId,
120
+ deviceName: payload?.device?.name || os.hostname(),
121
+ identityPresent: true,
122
+ account: payload?.account || storedGatewayStatus.account,
123
+ connection: {
124
+ online: payload?.device?.online !== false,
125
+ connectionEpoch: payload?.device?.connectionEpoch ?? null,
126
+ lastConnectedAt: payload?.device?.connectedAt || Date.now()
127
+ },
128
+ usage: payload?.usage || null,
129
+ schema: payload?.schema || null,
130
+ usageFreshAt: Date.now()
131
+ });
132
+ }
133
+ });
134
+ await this.gatewayChannel.start();
135
+ console.log('✓ Device ready through direct Gateway channel');
136
+ }
137
+ catch (error) {
138
+ console.error(' - ❌ Device startup failed:', error.message);
139
+ if (error.stack && process.env.DEBUG_MODE === 'true')
140
+ console.error('Stack trace:', error.stack);
141
+ await captureRemote('remote_device_startup_failed', { error });
142
+ await this.shutdown();
143
+ process.exit(1);
144
+ }
145
+ }
146
+ async shutdown() {
147
+ if (this.isShuttingDown)
148
+ return;
149
+ this.isShuttingDown = true;
150
+ console.log('\n🛑 Shutting down device...');
151
+ try {
152
+ if (this.gatewayChannel) {
153
+ console.log(' → Closing direct Gateway channel...');
154
+ await this.gatewayChannel.stop();
155
+ this.gatewayChannel = undefined;
156
+ }
157
+ if (this.gatewayProxyAgent) {
158
+ this.gatewayProxyAgent.destroy();
159
+ this.gatewayProxyAgent = undefined;
160
+ }
161
+ await new GatewayDeviceStatusStore().markOffline().catch(() => { });
162
+ await this.desktop.shutdown();
163
+ if (this.runtimeOwner) {
164
+ await this.runtimeOwner.release().catch(() => { });
165
+ this.runtimeOwner = undefined;
166
+ }
167
+ console.log('✓ Device shutdown complete');
168
+ }
169
+ catch (error) {
170
+ console.error('Shutdown error:', error.message);
171
+ await captureRemote('remote_device_shutdown_error', { error });
172
+ }
173
+ }
174
+ }
175
+ // Start only when this module itself is the entrypoint. Package/bin execution goes
176
+ // through src/mcp-device.ts -> src/index.ts, which owns the single device lifecycle.
177
+ export function isModuleEntrypoint(moduleUrl, argvEntry, pmExecPath) {
178
+ const moduleEntryPath = path.resolve(fileURLToPath(moduleUrl));
179
+ const candidates = [argvEntry, pmExecPath]
180
+ .map(value => value ? path.resolve(value) : '')
181
+ .filter(Boolean);
182
+ return candidates.some(candidate => process.platform === 'win32'
183
+ ? moduleEntryPath.toLowerCase() === candidate.toLowerCase()
184
+ : moduleEntryPath === candidate);
185
+ }
186
+ const isMainModule = isModuleEntrypoint(import.meta.url, process.argv[1], process.env.pm_exec_path);
187
+ if (isMainModule) {
188
+ const device = new MCPDevice();
189
+ device.start();
190
+ }
@@ -0,0 +1,144 @@
1
+ interface McpConfig {
2
+ command: string;
3
+ args: string[];
4
+ cwd?: string;
5
+ env?: Record<string, string>;
6
+ }
7
+ export declare class DesktopCommanderIntegration {
8
+ private mcpClient;
9
+ private mcpTransport;
10
+ private isReady;
11
+ initialize(): Promise<void>;
12
+ resolveMcpConfig(): Promise<McpConfig | null>;
13
+ callClientTool(toolName: string, args: any, metadata?: any): Promise<{
14
+ [x: string]: unknown;
15
+ content: ({
16
+ type: "text";
17
+ text: string;
18
+ annotations?: {
19
+ audience?: ("user" | "assistant")[] | undefined;
20
+ priority?: number | undefined;
21
+ lastModified?: string | undefined;
22
+ } | undefined;
23
+ _meta?: Record<string, unknown> | undefined;
24
+ } | {
25
+ type: "image";
26
+ data: string;
27
+ mimeType: string;
28
+ annotations?: {
29
+ audience?: ("user" | "assistant")[] | undefined;
30
+ priority?: number | undefined;
31
+ lastModified?: string | undefined;
32
+ } | undefined;
33
+ _meta?: Record<string, unknown> | undefined;
34
+ } | {
35
+ type: "audio";
36
+ data: string;
37
+ mimeType: string;
38
+ annotations?: {
39
+ audience?: ("user" | "assistant")[] | undefined;
40
+ priority?: number | undefined;
41
+ lastModified?: string | undefined;
42
+ } | undefined;
43
+ _meta?: Record<string, unknown> | undefined;
44
+ } | {
45
+ type: "resource";
46
+ resource: {
47
+ uri: string;
48
+ text: string;
49
+ mimeType?: string | undefined;
50
+ _meta?: Record<string, unknown> | undefined;
51
+ } | {
52
+ uri: string;
53
+ blob: string;
54
+ mimeType?: string | undefined;
55
+ _meta?: Record<string, unknown> | undefined;
56
+ };
57
+ annotations?: {
58
+ audience?: ("user" | "assistant")[] | undefined;
59
+ priority?: number | undefined;
60
+ lastModified?: string | undefined;
61
+ } | undefined;
62
+ _meta?: Record<string, unknown> | undefined;
63
+ } | {
64
+ uri: string;
65
+ name: string;
66
+ type: "resource_link";
67
+ description?: string | undefined;
68
+ mimeType?: string | undefined;
69
+ size?: number | undefined;
70
+ annotations?: {
71
+ audience?: ("user" | "assistant")[] | undefined;
72
+ priority?: number | undefined;
73
+ lastModified?: string | undefined;
74
+ } | undefined;
75
+ _meta?: {
76
+ [x: string]: unknown;
77
+ } | undefined;
78
+ icons?: {
79
+ src: string;
80
+ mimeType?: string | undefined;
81
+ sizes?: string[] | undefined;
82
+ theme?: "light" | "dark" | undefined;
83
+ }[] | undefined;
84
+ title?: string | undefined;
85
+ })[];
86
+ _meta?: {
87
+ [x: string]: unknown;
88
+ progressToken?: string | number | undefined;
89
+ "io.modelcontextprotocol/related-task"?: {
90
+ taskId: string;
91
+ } | undefined;
92
+ } | undefined;
93
+ structuredContent?: Record<string, unknown> | undefined;
94
+ isError?: boolean | undefined;
95
+ } | {
96
+ [x: string]: unknown;
97
+ toolResult: unknown;
98
+ _meta?: {
99
+ [x: string]: unknown;
100
+ progressToken?: string | number | undefined;
101
+ "io.modelcontextprotocol/related-task"?: {
102
+ taskId: string;
103
+ } | undefined;
104
+ } | undefined;
105
+ }>;
106
+ listClientTools(): Promise<{
107
+ tools: {
108
+ inputSchema: {
109
+ [x: string]: unknown;
110
+ type: "object";
111
+ properties?: Record<string, object> | undefined;
112
+ required?: string[] | undefined;
113
+ };
114
+ name: string;
115
+ description?: string | undefined;
116
+ outputSchema?: {
117
+ [x: string]: unknown;
118
+ type: "object";
119
+ properties?: Record<string, object> | undefined;
120
+ required?: string[] | undefined;
121
+ } | undefined;
122
+ annotations?: {
123
+ title?: string | undefined;
124
+ readOnlyHint?: boolean | undefined;
125
+ destructiveHint?: boolean | undefined;
126
+ idempotentHint?: boolean | undefined;
127
+ openWorldHint?: boolean | undefined;
128
+ } | undefined;
129
+ execution?: {
130
+ taskSupport?: "optional" | "required" | "forbidden" | undefined;
131
+ } | undefined;
132
+ _meta?: Record<string, unknown> | undefined;
133
+ icons?: {
134
+ src: string;
135
+ mimeType?: string | undefined;
136
+ sizes?: string[] | undefined;
137
+ theme?: "light" | "dark" | undefined;
138
+ }[] | undefined;
139
+ title?: string | undefined;
140
+ }[];
141
+ }>;
142
+ shutdown(): Promise<void>;
143
+ }
144
+ export {};