@hakimedes/dsh-easyremote 0.2.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 (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +17 -0
  3. package/dist/android.js +50 -0
  4. package/dist/android.js.map +1 -0
  5. package/dist/autostart.js +104 -0
  6. package/dist/autostart.js.map +1 -0
  7. package/dist/cli-views.js +32 -0
  8. package/dist/cli-views.js.map +1 -0
  9. package/dist/cli.js +584 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/cloudflared.js +54 -0
  12. package/dist/cloudflared.js.map +1 -0
  13. package/dist/command-router.js +12 -0
  14. package/dist/command-router.js.map +1 -0
  15. package/dist/connector-install.js +42 -0
  16. package/dist/connector-install.js.map +1 -0
  17. package/dist/controller.js +206 -0
  18. package/dist/controller.js.map +1 -0
  19. package/dist/doctor.js +46 -0
  20. package/dist/doctor.js.map +1 -0
  21. package/dist/domain.js +33 -0
  22. package/dist/domain.js.map +1 -0
  23. package/dist/download.js +59 -0
  24. package/dist/download.js.map +1 -0
  25. package/dist/index.js +16 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/install-state.js +48 -0
  28. package/dist/install-state.js.map +1 -0
  29. package/dist/local-runtime.js +115 -0
  30. package/dist/local-runtime.js.map +1 -0
  31. package/dist/maintenance.js +58 -0
  32. package/dist/maintenance.js.map +1 -0
  33. package/dist/named-tunnel.js +49 -0
  34. package/dist/named-tunnel.js.map +1 -0
  35. package/dist/runtime.js +110 -0
  36. package/dist/runtime.js.map +1 -0
  37. package/dist/setup-progress.js +23 -0
  38. package/dist/setup-progress.js.map +1 -0
  39. package/dist/supervisor.js +81 -0
  40. package/dist/supervisor.js.map +1 -0
  41. package/dist/wizard.js +251 -0
  42. package/dist/wizard.js.map +1 -0
  43. package/package.json +56 -0
  44. package/runtime/connector/README.md +52 -0
  45. package/runtime/connector/cordis.patch.yml +3 -0
  46. package/runtime/connector/dsh.plugin.json +16 -0
  47. package/runtime/connector/lib/client.js +262 -0
  48. package/runtime/connector/lib/command-cache.d.ts +7 -0
  49. package/runtime/connector/lib/command-cache.js +37 -0
  50. package/runtime/connector/lib/command-cache.js.map +1 -0
  51. package/runtime/connector/lib/connector-config.d.ts +20 -0
  52. package/runtime/connector/lib/connector-config.js +74 -0
  53. package/runtime/connector/lib/connector-config.js.map +1 -0
  54. package/runtime/connector/lib/dsh-api.d.ts +38 -0
  55. package/runtime/connector/lib/dsh-api.js +76 -0
  56. package/runtime/connector/lib/dsh-api.js.map +1 -0
  57. package/runtime/connector/lib/index.d.ts +4 -0
  58. package/runtime/connector/lib/index.js +913 -0
  59. package/runtime/connector/lib/index.js.map +1 -0
  60. package/runtime/connector/lib/protocol.d.ts +15 -0
  61. package/runtime/connector/lib/protocol.js +96 -0
  62. package/runtime/connector/lib/protocol.js.map +1 -0
  63. package/runtime/connector/package.json +44 -0
  64. package/runtime/hub/database.js +239 -0
  65. package/runtime/hub/database.js.map +1 -0
  66. package/runtime/hub/index.js +1804 -0
  67. package/runtime/hub/index.js.map +1 -0
  68. package/runtime/hub/schema.js +111 -0
  69. package/runtime/hub/schema.js.map +1 -0
@@ -0,0 +1,262 @@
1
+ /**
2
+ * @dsh-remote/hub-connector — browser client half.
3
+ *
4
+ * Speaks the dsh module-loader protocol consumed by the client-modules boot
5
+ * graph: the whole file is one `window.__ModuleLoader__.load({ id, factory })`
6
+ * call whose factory receives a `require` that answers the platform seed rows
7
+ * ('react' family, cordis, ui-primitives). Everything else stays
8
+ * self-contained so this ships as a single static artifact with no bundler:
9
+ * `scripts/build-client.mjs` copies it verbatim to lib/client.js, which the
10
+ * package exposes as exports["./client"] and declares via package.json
11
+ * `dsh.client`. The banner id MUST equal the npm package name.
12
+ *
13
+ * Contributes one additive settings page (`settings.section`, id "dsh-remote")
14
+ * so remote connection lives inside DSH Web's Settings panel instead of only
15
+ * at the standalone /__dsh_remote_v1/pair URL: live connection status, the
16
+ * one-time pairing QR while unpaired, mobile recovery when connected, and a
17
+ * link out to the full page. Data comes from same-origin
18
+ * GET /__dsh_remote_v1/pair-data; recovery POSTs /__dsh_remote_v1/recover.
19
+ */
20
+ window.__ModuleLoader__.load({ id: '@dsh-remote/hub-connector', factory: (require) => {
21
+ var module = { exports: {} };
22
+ var exports = module.exports;
23
+
24
+ var React = require('react');
25
+
26
+ var DATA_URL = '/__dsh_remote_v1/pair-data';
27
+ var RECOVER_URL = '/__dsh_remote_v1/recover';
28
+ var PAIR_PAGE_URL = '/__dsh_remote_v1/pair';
29
+ var POLL_MS = 4000;
30
+ var STYLE_ID = 'dsh-remote-connector-css';
31
+
32
+ var TONE_COLOR = {
33
+ success: 'var(--dsw-alias-state-success-primary,#4ade80)',
34
+ warn: 'var(--dsw-alias-state-warn-primary,#fbbf24)',
35
+ error: 'var(--dsw-alias-state-error-primary,#f87171)',
36
+ neutral: 'var(--dsw-alias-label-secondary,#9da7b3)',
37
+ };
38
+
39
+ var TONE_BY_STATUS = {
40
+ online: 'success',
41
+ pairing: 'warn',
42
+ connecting: 'warn',
43
+ starting: 'neutral',
44
+ offline: 'error',
45
+ revoked: 'error',
46
+ };
47
+
48
+ var CSS_TEXT = [
49
+ '.dsh-remote-section{display:flex;flex-direction:column;gap:14px;max-width:600px;}',
50
+ '.dsh-remote-card{border:1px solid var(--dsw-alias-border-l1,#30363d);border-radius:12px;',
51
+ 'background:var(--dsw-alias-bg-layer-1,#161b22);padding:16px;display:flex;flex-direction:column;gap:12px;}',
52
+ '.dsh-remote-head{display:flex;align-items:center;justify-content:space-between;gap:8px;}',
53
+ '.dsh-remote-title{font-size:13px;font-weight:700;letter-spacing:.02em;text-transform:uppercase;',
54
+ 'color:var(--dsw-alias-label-secondary,#9da7b3);}',
55
+ '.dsh-remote-pill{display:inline-flex;align-items:center;gap:6px;border-radius:999px;padding:2px 10px;',
56
+ 'font-size:11px;font-weight:700;background:var(--dsw-alias-bg-layer-2,#21262d);white-space:nowrap;}',
57
+ '.dsh-remote-dot{width:7px;height:7px;border-radius:4px;background:currentColor;}',
58
+ '.dsh-remote-meta{display:flex;flex-direction:column;gap:4px;margin:0;padding:0;list-style:none;}',
59
+ '.dsh-remote-meta li{display:flex;gap:8px;font-size:12px;line-height:18px;min-width:0;}',
60
+ '.dsh-remote-k{flex:none;width:64px;color:var(--dsw-alias-label-secondary,#9da7b3);}',
61
+ '.dsh-remote-v{color:var(--dsw-alias-label-primary,#f4f5f7);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}',
62
+ '.dsh-remote-qrbox{align-self:center;background:#fff;border-radius:14px;padding:14px;line-height:0;max-width:100%;}',
63
+ '.dsh-remote-qrbox svg{width:232px;height:232px;max-width:100%;}',
64
+ '.dsh-remote-count{align-self:center;font-size:12px;color:var(--dsw-alias-label-secondary,#9da7b3);}',
65
+ '.dsh-remote-note{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary,#9da7b3);margin:0;}',
66
+ '.dsh-remote-warn{display:flex;gap:8px;align-items:flex-start;font-size:12px;line-height:18px;border-radius:9px;',
67
+ 'padding:10px 12px;background:var(--dsw-alias-bg-layer-2,#21262d);color:var(--dsw-alias-state-warn-primary,#fbbf24);}',
68
+ '.dsh-remote-actions{display:flex;gap:10px;align-items:center;flex-wrap:wrap;}',
69
+ '.dsh-remote-btn{appearance:none;border:0;border-radius:10px;padding:9px 16px;font:600 13px system-ui;cursor:pointer;',
70
+ 'background:var(--dsw-alias-brand-primary,#4d6bfe);color:#fff;}',
71
+ '.dsh-remote-btn:disabled{opacity:.55;cursor:default;}',
72
+ '.dsh-remote-link{font-size:12px;font-weight:600;color:var(--dsw-alias-brand-primary,#4d6bfe);text-decoration:none;}',
73
+ ].join('');
74
+
75
+ function ensureStyles() {
76
+ if (typeof document === 'undefined' || document.getElementById(STYLE_ID)) return;
77
+ var tag = document.createElement('style');
78
+ tag.id = STYLE_ID;
79
+ tag.textContent = CSS_TEXT;
80
+ document.head.appendChild(tag);
81
+ }
82
+
83
+ function Pill(_ref) {
84
+ var label = _ref.label;
85
+ var tone = TONE_COLOR[TONE_BY_STATUS[label] ? TONE_BY_STATUS[label] : 'neutral'];
86
+ return React.createElement(
87
+ 'span',
88
+ { className: 'dsh-remote-pill', style: { color: tone } },
89
+ React.createElement('span', { className: 'dsh-remote-dot' }),
90
+ String(label || ''),
91
+ );
92
+ }
93
+
94
+ function MetaRow(key, value) {
95
+ return React.createElement(
96
+ 'li',
97
+ null,
98
+ React.createElement('span', { className: 'dsh-remote-k' }, key),
99
+ React.createElement('span', { className: 'dsh-remote-v', title: String(value) }, String(value)),
100
+ );
101
+ }
102
+
103
+ function formatRemaining(expiresAt, now) {
104
+ if (!expiresAt) return '';
105
+ var remaining = Math.max(0, expiresAt - now);
106
+ var m = Math.floor(remaining / 60000);
107
+ var s = Math.floor((remaining % 60000) / 1000);
108
+ return m + ':' + String(s).padStart(2, '0');
109
+ }
110
+
111
+ function RemoteSection(props) {
112
+ var _React$useState = React.useState(null);
113
+ var data = _React$useState[0];
114
+ var setData = _React$useState[1];
115
+
116
+ var _React$useState2 = React.useState(true);
117
+ var busy = _React$useState2[0];
118
+ var setBusy = _React$useState2[1];
119
+
120
+ var _React$useState3 = React.useState(null);
121
+ var actionError = _React$useState3[0];
122
+ var setActionError = _React$useState3[1];
123
+
124
+ var _React$useState4 = React.useState(Date.now());
125
+ var now = _React$useState4[0];
126
+ var setNow = _React$useState4[1];
127
+
128
+ React.useEffect(function () {
129
+ var alive = true;
130
+ async function load() {
131
+ try {
132
+ var res = await fetch(DATA_URL, { cache: 'no-store' });
133
+ var next = await res.json();
134
+ if (alive && next && next.ok) {
135
+ setData(next);
136
+ setBusy(false);
137
+ }
138
+ } catch (error) { /* keep last state; next poll retries */ }
139
+ }
140
+ load();
141
+ var poll = setInterval(load, POLL_MS);
142
+ var beat = setInterval(function () { setNow(Date.now()); }, 1000);
143
+ return function () {
144
+ alive = false;
145
+ clearInterval(poll);
146
+ clearInterval(beat);
147
+ };
148
+ }, []);
149
+
150
+ async function recover() {
151
+ setBusy(true);
152
+ setActionError(null);
153
+ try {
154
+ var res = await fetch(RECOVER_URL, { method: 'POST' });
155
+ if (!res.ok) throw new Error('HTTP ' + res.status);
156
+ var next = await fetch(DATA_URL, { cache: 'no-store' }).then(function (r) { return r.json(); });
157
+ if (next && next.ok) setData(next);
158
+ } catch (error) {
159
+ setActionError(error instanceof Error ? error.message : String(error));
160
+ } finally {
161
+ setBusy(false);
162
+ }
163
+ }
164
+
165
+ var qrSvg = data && typeof data.qrSvg === 'string' ? data.qrSvg : '';
166
+ var status = data ? String(data.status || '') : '';
167
+
168
+ return React.createElement(
169
+ 'div',
170
+ { className: 'dsh-remote-section' },
171
+ React.createElement(
172
+ 'div',
173
+ { className: 'dsh-remote-card' },
174
+ React.createElement(
175
+ 'div',
176
+ { className: 'dsh-remote-head' },
177
+ React.createElement('span', { className: 'dsh-remote-title' }, 'Remote connection'),
178
+ data ? React.createElement(Pill, { label: status }) : null,
179
+ ),
180
+ !data
181
+ ? React.createElement('p', { className: 'dsh-remote-note' }, 'Loading remote state…')
182
+ : React.createElement(
183
+ React.Fragment,
184
+ null,
185
+ React.createElement(
186
+ 'ul',
187
+ { className: 'dsh-remote-meta' },
188
+ MetaRow('Node', data.nodeName || '—'),
189
+ MetaRow('Hub', data.hub || '—'),
190
+ MetaRow('Node ID', data.nodeId || 'Not paired yet'),
191
+ ),
192
+ qrSvg
193
+ ? React.createElement(
194
+ React.Fragment,
195
+ null,
196
+ React.createElement('div', {
197
+ className: 'dsh-remote-qrbox',
198
+ // Server-rendered SVG from our own connector route (qrcode lib output).
199
+ dangerouslySetInnerHTML: { __html: qrSvg },
200
+ }),
201
+ React.createElement('div', { className: 'dsh-remote-count' },
202
+ data.pairingExpiresAt
203
+ ? 'Expires in ' + formatRemaining(data.pairingExpiresAt, now) + ' · one-time use'
204
+ : null),
205
+ React.createElement('p', { className: 'dsh-remote-note' },
206
+ data.recovering
207
+ ? 'Scan this QR with DSH Mobile on a phone that was paired before to restore access.'
208
+ : 'Scan this QR with DSH Mobile to pair this Harness as a remote node.'),
209
+ )
210
+ : data.nodeId
211
+ ? React.createElement('p', { className: 'dsh-remote-note' },
212
+ 'Remote access is active. Revoke this node from Nodes in the mobile app.')
213
+ : React.createElement('div', { className: 'dsh-remote-warn' },
214
+ React.createElement('span', null,
215
+ data.error
216
+ ? 'Hub unreachable — retrying in the background. Last error: ' + data.error
217
+ : 'Preparing a pairing QR…')),
218
+ actionError
219
+ ? React.createElement('div', { className: 'dsh-remote-warn' },
220
+ React.createElement('span', null, 'Recovery failed: ' + actionError))
221
+ : null,
222
+ data.nodeId && !qrSvg
223
+ ? React.createElement(
224
+ 'div',
225
+ { className: 'dsh-remote-actions' },
226
+ React.createElement('button', {
227
+ className: 'dsh-remote-btn',
228
+ disabled: busy,
229
+ onClick: function () { void recover(); },
230
+ }, busy ? 'Working…' : 'Reconnect mobile'),
231
+ React.createElement('span', { className: 'dsh-remote-note' },
232
+ 'Issues a one-time recovery QR for a previously paired phone.'),
233
+ )
234
+ : null,
235
+ ),
236
+ ),
237
+ React.createElement(
238
+ 'div',
239
+ { className: 'dsh-remote-actions' },
240
+ React.createElement('a', { className: 'dsh-remote-link', href: PAIR_PAGE_URL, target: '_blank', rel: 'noreferrer' },
241
+ 'Open standalone pair page ↗'),
242
+ ),
243
+ );
244
+ }
245
+
246
+ module.exports = {
247
+ inject: [],
248
+ apply: function apply(ctx) {
249
+ ensureStyles();
250
+ var slots = ctx.get('slots');
251
+ if (slots === undefined) return;
252
+ slots.inject('settings.section', function () {
253
+ return slots.register(
254
+ { name: 'settings.section', id: 'dsh-remote', order: 30, label: 'Remote' },
255
+ RemoteSection,
256
+ );
257
+ });
258
+ },
259
+ };
260
+
261
+ return module.exports;
262
+ } });
@@ -0,0 +1,7 @@
1
+ export declare class CommandReplayCache<T> {
2
+ private readonly maxEntries;
3
+ private readonly entries;
4
+ constructor(maxEntries?: number);
5
+ execute(commandId: string, operation: () => Promise<T>): Promise<T>;
6
+ private trim;
7
+ }
@@ -0,0 +1,37 @@
1
+ export class CommandReplayCache {
2
+ maxEntries;
3
+ entries = new Map();
4
+ constructor(maxEntries = 500) {
5
+ this.maxEntries = maxEntries;
6
+ }
7
+ execute(commandId, operation) {
8
+ const existing = this.entries.get(commandId);
9
+ if (existing)
10
+ return existing.promise;
11
+ const entry = {
12
+ settled: false,
13
+ promise: Promise.resolve().then(operation),
14
+ };
15
+ this.entries.set(commandId, entry);
16
+ void entry.promise.finally(() => {
17
+ entry.settled = true;
18
+ this.trim();
19
+ }).catch(() => {
20
+ // The original promise remains the sole observable rejection.
21
+ });
22
+ this.trim();
23
+ return entry.promise;
24
+ }
25
+ trim() {
26
+ if (this.entries.size <= this.maxEntries)
27
+ return;
28
+ for (const [key, entry] of this.entries) {
29
+ if (!entry.settled)
30
+ continue;
31
+ this.entries.delete(key);
32
+ if (this.entries.size <= this.maxEntries)
33
+ break;
34
+ }
35
+ }
36
+ }
37
+ //# sourceMappingURL=command-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-cache.js","sourceRoot":"","sources":["../src/command-cache.ts"],"names":[],"mappings":"AAKA,MAAM,OAAO,kBAAkB;IAGA;IAFZ,OAAO,GAAG,IAAI,GAAG,EAAyB,CAAC;IAE5D,YAA6B,aAAa,GAAG;QAAhB,eAAU,GAAV,UAAU,CAAM;IAAG,CAAC;IAEjD,OAAO,CAAC,SAAiB,EAAE,SAA2B;QACpD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC7C,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAC,OAAO,CAAC;QAEtC,MAAM,KAAK,GAAkB;YAC3B,OAAO,EAAE,KAAK;YACd,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC;SAC3C,CAAC;QACF,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QACnC,KAAK,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE;YAC9B,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC;YACrB,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;YACZ,8DAA8D;QAChE,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,OAAO,KAAK,CAAC,OAAO,CAAC;IACvB,CAAC;IAEO,IAAI;QACV,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,UAAU;YAAE,OAAO;QACjD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACxC,IAAI,CAAC,KAAK,CAAC,OAAO;gBAAE,SAAS;YAC7B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACzB,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,UAAU;gBAAE,MAAM;QAClD,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,20 @@
1
+ export type ConnectorConfigFile = {
2
+ schemaVersion: 1;
3
+ hubUrl: string;
4
+ nodeName?: string;
5
+ defaultCwd?: string;
6
+ };
7
+ export type ResolvedConnectorConfig = ConnectorConfigFile & {
8
+ nodeName: string;
9
+ source: 'file' | 'environment';
10
+ };
11
+ type LoadOptions = {
12
+ path?: string;
13
+ environment?: Record<string, string | undefined>;
14
+ fallbackNodeName?: string;
15
+ };
16
+ export declare function connectorConfigPath(environment?: Record<string, string | undefined>): string;
17
+ export declare function asHttpUrl(value: string): string;
18
+ export declare function loadConnectorConfig(options?: LoadOptions): ResolvedConnectorConfig;
19
+ export declare function watchConnectorConfig(path: string, onChange: () => void, onError: (error: unknown) => void, interval?: number): () => void;
20
+ export {};
@@ -0,0 +1,74 @@
1
+ import { existsSync, readFileSync, unwatchFile, watchFile } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ export function connectorConfigPath(environment = process.env) {
5
+ return environment.DSH_EASYREMOTE_CONFIG_PATH || join(homedir(), '.dsh-easyremote', 'connector.json');
6
+ }
7
+ export function asHttpUrl(value) {
8
+ const parsed = new URL(value.trim());
9
+ if (!['http:', 'https:'].includes(parsed.protocol)) {
10
+ throw new Error('Hub URL must use http:// or https://');
11
+ }
12
+ if (parsed.username || parsed.password || parsed.search || parsed.hash) {
13
+ throw new Error('Hub URL must be an origin without credentials, query, or fragment');
14
+ }
15
+ return parsed.origin;
16
+ }
17
+ function optionalString(value) {
18
+ return typeof value === 'string' && value.trim() ? value.trim() : undefined;
19
+ }
20
+ export function loadConnectorConfig(options = {}) {
21
+ const environment = options.environment ?? process.env;
22
+ const path = options.path ?? connectorConfigPath(environment);
23
+ const fallbackNodeName = options.fallbackNodeName || 'DSH Node';
24
+ if (existsSync(path)) {
25
+ let value;
26
+ try {
27
+ value = JSON.parse(readFileSync(path, 'utf8'));
28
+ }
29
+ catch (error) {
30
+ throw new Error(`Connector config is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
31
+ }
32
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
33
+ throw new Error('Connector config must be a JSON object');
34
+ }
35
+ const config = value;
36
+ if (config.schemaVersion !== 1 || typeof config.hubUrl !== 'string') {
37
+ throw new Error('Connector config requires schemaVersion 1 and hubUrl');
38
+ }
39
+ return {
40
+ schemaVersion: 1,
41
+ hubUrl: asHttpUrl(config.hubUrl),
42
+ nodeName: optionalString(config.nodeName) || fallbackNodeName,
43
+ ...(optionalString(config.defaultCwd) ? { defaultCwd: optionalString(config.defaultCwd) } : {}),
44
+ source: 'file',
45
+ };
46
+ }
47
+ return {
48
+ schemaVersion: 1,
49
+ hubUrl: asHttpUrl(environment.DSH_REMOTE_HUB_URL || 'https://dsh.infomind.cc'),
50
+ nodeName: optionalString(environment.DSH_REMOTE_NODE_NAME) || fallbackNodeName,
51
+ ...(optionalString(environment.DSH_REMOTE_DEFAULT_CWD)
52
+ ? { defaultCwd: optionalString(environment.DSH_REMOTE_DEFAULT_CWD) }
53
+ : {}),
54
+ source: 'environment',
55
+ };
56
+ }
57
+ export function watchConnectorConfig(path, onChange, onError, interval = 1_000) {
58
+ let previous = existsSync(path) ? readFileSync(path, 'utf8') : null;
59
+ const listener = () => {
60
+ try {
61
+ const next = existsSync(path) ? readFileSync(path, 'utf8') : null;
62
+ if (next === previous)
63
+ return;
64
+ previous = next;
65
+ onChange();
66
+ }
67
+ catch (error) {
68
+ onError(error);
69
+ }
70
+ };
71
+ watchFile(path, { interval, persistent: false }, listener);
72
+ return () => unwatchFile(path, listener);
73
+ }
74
+ //# sourceMappingURL=connector-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connector-config.js","sourceRoot":"","sources":["../src/connector-config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ type RpcIdFactory = () => string;
2
+ type CreateSessionInput = {
3
+ sessionId: string;
4
+ cwd: string;
5
+ agentPreset?: string;
6
+ };
7
+ type SelectModelInput = {
8
+ sessionId: string;
9
+ provider: string;
10
+ model: string;
11
+ reasoningEffort?: string;
12
+ };
13
+ export declare function toRemoteSessionSummary(session: any, title?: string, status?: string, now?: number): Record<string, unknown>;
14
+ export declare class DshApiBridgeError extends Error {
15
+ readonly code: string;
16
+ constructor(code: string, message: string);
17
+ }
18
+ export declare class DshApiBridge {
19
+ private readonly apiProxy;
20
+ private readonly createRpcId;
21
+ constructor(apiProxy: any, createRpcId: RpcIdFactory);
22
+ private request;
23
+ private value;
24
+ listAgentPresets(): Promise<{
25
+ presets: unknown[];
26
+ }>;
27
+ createSession(input: CreateSessionInput): Promise<{
28
+ sessionId: string;
29
+ agentPreset?: string;
30
+ }>;
31
+ sessionModels(sessionId: string): Promise<any>;
32
+ selectModel(input: SelectModelInput): Promise<any>;
33
+ renameSession(sessionId: string, title: string): Promise<{
34
+ title: string;
35
+ seq: number;
36
+ }>;
37
+ }
38
+ export {};
@@ -0,0 +1,76 @@
1
+ import { basename } from 'node:path';
2
+ export function toRemoteSessionSummary(session, title, status, now = Date.now()) {
3
+ const header = session?.header || session?.session || {};
4
+ const events = Array.isArray(session?.events) ? session.events : [];
5
+ const last = events.at(-1);
6
+ const cwd = typeof header.cwd === 'string' ? header.cwd : '';
7
+ return {
8
+ id: header.id,
9
+ title: title || basename(cwd) || `Session ${String(header.id || '').slice(0, 8)}`,
10
+ status: status === 'running' ? 'running' : 'idle',
11
+ lastSourceSeq: typeof last?.seq === 'number' ? last.seq : -1,
12
+ createdAt: typeof header.createdAt === 'number' ? header.createdAt : now,
13
+ updatedAt: typeof last?.time === 'number' ? last.time : header.createdAt || now,
14
+ ...(cwd ? { cwdLabel: basename(cwd) } : {}),
15
+ ...(typeof header.agentPreset === 'string' && header.agentPreset
16
+ ? { agentPreset: header.agentPreset }
17
+ : {}),
18
+ };
19
+ }
20
+ function remoteCode(value) {
21
+ return String(value || 'internal').replaceAll('-', '_').toUpperCase();
22
+ }
23
+ export class DshApiBridgeError extends Error {
24
+ code;
25
+ constructor(code, message) {
26
+ super(message);
27
+ this.name = 'DshApiBridgeError';
28
+ this.code = code;
29
+ }
30
+ }
31
+ export class DshApiBridge {
32
+ apiProxy;
33
+ createRpcId;
34
+ constructor(apiProxy, createRpcId) {
35
+ this.apiProxy = apiProxy;
36
+ this.createRpcId = createRpcId;
37
+ }
38
+ request(payload) {
39
+ return { rpcId: this.createRpcId(), payload };
40
+ }
41
+ value(response) {
42
+ if (response?.result?.ok)
43
+ return response.result.value;
44
+ const error = response?.result?.error;
45
+ throw new DshApiBridgeError(remoteCode(error?.code), error?.message || 'DSH request failed');
46
+ }
47
+ async listAgentPresets() {
48
+ const response = await this.apiProxy.agentPresets.list(this.request({}));
49
+ const value = this.value(response);
50
+ return { presets: Array.isArray(value.presets) ? value.presets : [] };
51
+ }
52
+ async createSession(input) {
53
+ const payload = {
54
+ sessionId: input.sessionId,
55
+ cwd: input.cwd,
56
+ ...(input.agentPreset ? { agentPreset: input.agentPreset } : {}),
57
+ };
58
+ return this.value(await this.apiProxy.sessions.create(this.request(payload)));
59
+ }
60
+ async sessionModels(sessionId) {
61
+ return this.value(await this.apiProxy.sessions.models(this.request({ sessionId })));
62
+ }
63
+ async selectModel(input) {
64
+ const payload = {
65
+ sessionId: input.sessionId,
66
+ provider: input.provider,
67
+ model: input.model,
68
+ ...(input.reasoningEffort ? { reasoningEffort: input.reasoningEffort } : {}),
69
+ };
70
+ return this.value(await this.apiProxy.sessions.selectModel(this.request(payload)));
71
+ }
72
+ async renameSession(sessionId, title) {
73
+ return this.value(await this.apiProxy.sessions.rename(this.request({ sessionId, title })));
74
+ }
75
+ }
76
+ //# sourceMappingURL=dsh-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dsh-api.js","sourceRoot":"","sources":["../src/dsh-api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export declare const name = "dsh-easyremote-connector";
2
+ export declare const inject: string[];
3
+ export declare function shouldRotateRecoveryAfterReconnect(endpointChanged: boolean, nodeId?: string): boolean;
4
+ export declare function apply(ctx: any): void;