specrails-desktop 2.5.0 → 2.7.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 (76) hide show
  1. package/client/dist/assets/{ActivityFeedPage-BTYWMRwB.js → ActivityFeedPage-LKqd18-G.js} +1 -1
  2. package/client/dist/assets/{AgentsPage-BfOCeHHt.js → AgentsPage-Cb-b-6Ot.js} +1 -1
  3. package/client/dist/assets/AnalyticsPage-HVxQQ1wy.js +1 -0
  4. package/client/dist/assets/{BarChart-DlshJN3Z.js → BarChart-BOyHB0dw.js} +1 -1
  5. package/client/dist/assets/{CodePage-DJCjDG4I.js → CodePage-DnOnwKGB.js} +1 -1
  6. package/client/dist/assets/{DesktopAnalyticsPage-CTqZ9mbB.js → DesktopAnalyticsPage-D2auU39x.js} +1 -1
  7. package/client/dist/assets/{DocsDialog-KiJOSRvX.js → DocsDialog-CTuDX3GK.js} +1 -1
  8. package/client/dist/assets/{DocsPage-B17CR54A.js → DocsPage-DRyMmu0Z.js} +1 -1
  9. package/client/dist/assets/{ExportDropdown-BAu6z3b6.js → ExportDropdown-DO-GGiMh.js} +1 -1
  10. package/client/dist/assets/{IntegrationsPage-CCG64Q-6.js → IntegrationsPage-BhbO4jFT.js} +1 -1
  11. package/client/dist/assets/{JobDetailPage-BnGJSMiS.js → JobDetailPage-DJooEg1s.js} +1 -1
  12. package/client/dist/assets/{JobsPage-B-tn4CIf.js → JobsPage-BbaC-YOg.js} +1 -1
  13. package/client/dist/assets/{addspec-DeDOztDr.js → addspec-B-BKlvDj.js} +1 -1
  14. package/client/dist/assets/{addspec-v8j6A7CD.js → addspec-BErjOdNK.js} +1 -1
  15. package/client/dist/assets/{addspec-B1FTtI2a.js → addspec-CIGb34PS.js} +1 -1
  16. package/client/dist/assets/{addspec-GWm4ffKl.js → addspec-C_3NBarY.js} +1 -1
  17. package/client/dist/assets/{addspec-Dw-0Dg-4.js → addspec-DDvvnE6N.js} +1 -1
  18. package/client/dist/assets/{addspec-DpRgmfmx.js → addspec-RuL8Zd7w.js} +1 -1
  19. package/client/dist/assets/{addspec-rp496P_F.js → addspec-rmhOaH7N.js} +1 -1
  20. package/client/dist/assets/{addspec-BCT9vm_c.js → addspec-xjDbYZWL.js} +1 -1
  21. package/client/dist/assets/{dist-js-B16c3VyT.js → dist-js-CiIVMsx3.js} +1 -1
  22. package/client/dist/assets/{dist-js-P2FkJ6fA.js → dist-js-Xc2lRKp2.js} +1 -1
  23. package/client/dist/assets/{index-AfVF6BgE.js → index-DK214dak.js} +45 -45
  24. package/client/dist/assets/index-DgKfQFcf.css +2 -0
  25. package/client/dist/assets/{lib-rNNmltMb.js → lib-Bo5s6xpe.js} +1 -1
  26. package/client/dist/assets/{settings-D3LurcR5.js → settings-BI_cVCqN.js} +1 -1
  27. package/client/dist/assets/{settings-5tzo0Rn3.js → settings-BRaLLSVi.js} +1 -1
  28. package/client/dist/assets/{settings-BEWv3VEu.js → settings-BcqH0oea.js} +1 -1
  29. package/client/dist/assets/settings-C0-7Fpxg.js +1 -0
  30. package/client/dist/assets/{settings-BORg56um.js → settings-D6QMBlGQ.js} +1 -1
  31. package/client/dist/assets/{settings-DcqWIEM6.js → settings-GOBKOTGl.js} +1 -1
  32. package/client/dist/assets/{settings-BDAW3trC.js → settings-pT3MzfRu.js} +1 -1
  33. package/client/dist/assets/{settings-Dfz8QbZS.js → settings-u-16ISHt.js} +1 -1
  34. package/client/dist/assets/{setup-D3rNZA9A.js → setup-BIIkb-_K.js} +1 -1
  35. package/client/dist/assets/{setup-C1IA-9YS.js → setup-BeQxu9kD.js} +1 -1
  36. package/client/dist/assets/{setup-pjgmYHx6.js → setup-CPa6GnlI.js} +1 -1
  37. package/client/dist/assets/{setup-gzLG8T6F.js → setup-CZl4OEJx.js} +1 -1
  38. package/client/dist/assets/{setup-C0dzw8j4.js → setup-ChpodNfn.js} +1 -1
  39. package/client/dist/assets/{setup-WP6WOYQh.js → setup-D_fjJH6u.js} +1 -1
  40. package/client/dist/assets/{setup-UD2aanGs.js → setup-YzD8DX4O.js} +1 -1
  41. package/client/dist/assets/{setup-CpfjaNut.js → setup-fRpDozmq.js} +1 -1
  42. package/client/dist/assets/{useProjectCache-Cid_GxRM.js → useProjectCache-DVNypkmR.js} +1 -1
  43. package/client/dist/index.html +5 -5
  44. package/docs/adding-a-provider.md +107 -0
  45. package/docs/agy-cli-provider-study.md +179 -0
  46. package/docs/gemini-cli-provider-study.md +301 -0
  47. package/docs/gemini-core-support-evaluation.md +160 -0
  48. package/docs/gemini.md +106 -0
  49. package/docs/internals/api-reference.md +4 -7
  50. package/package.json +2 -2
  51. package/server/dist/chat-manager.js +1 -1
  52. package/server/dist/core-package.js +6 -1
  53. package/server/dist/desktop-router.js +27 -8
  54. package/server/dist/explore-cwd-manager.js +1 -1
  55. package/server/dist/mobile/index.js +5 -5
  56. package/server/dist/mobile/mobile-admin-router.js +28 -35
  57. package/server/dist/mobile/mobile-datachannel.js +228 -0
  58. package/server/dist/mobile/mobile-gateway.js +72 -98
  59. package/server/dist/mobile/mobile-router.js +4 -35
  60. package/server/dist/mobile/mobile-signal-reconnect.js +84 -0
  61. package/server/dist/mobile/mobile-types.js +5 -5
  62. package/server/dist/mobile/mobile-webrtc-peer.js +129 -0
  63. package/server/dist/mobile/mobile-webrtc.js +117 -0
  64. package/server/dist/pricing.js +13 -0
  65. package/server/dist/project-router-tickets.js +63 -18
  66. package/server/dist/providers/gemini-adapter.js +234 -0
  67. package/server/dist/providers/index.js +4 -1
  68. package/server/dist/setup-manager.js +13 -7
  69. package/server/dist/setup-prerequisites.js +4 -0
  70. package/server/dist/spec-models.js +17 -3
  71. package/server/dist/util/cli-prompt.js +17 -1
  72. package/client/dist/assets/AnalyticsPage-AbVXKh9v.js +0 -1
  73. package/client/dist/assets/index-NlH5BbXJ.css +0 -2
  74. package/client/dist/assets/settings-yMubjqYw.js +0 -1
  75. package/server/dist/mobile/mobile-mdns.js +0 -81
  76. package/server/dist/mobile/mobile-pairing.js +0 -179
@@ -1,81 +0,0 @@
1
- "use strict";
2
- // Best-effort mDNS/DNS-SD advertising via @homebridge/ciao (pure-JS, RFC
3
- // 6762/6763, Windows-safe). Discovery is a SECONDARY convenience — the QR is the
4
- // primary path — so any failure here is logged and swallowed; the gateway works
5
- // without it. TXT carries only { id, fp } (never a token or a revealing name).
6
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
- if (k2 === undefined) k2 = k;
8
- var desc = Object.getOwnPropertyDescriptor(m, k);
9
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
- desc = { enumerable: true, get: function() { return m[k]; } };
11
- }
12
- Object.defineProperty(o, k2, desc);
13
- }) : (function(o, m, k, k2) {
14
- if (k2 === undefined) k2 = k;
15
- o[k2] = m[k];
16
- }));
17
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
- Object.defineProperty(o, "default", { enumerable: true, value: v });
19
- }) : function(o, v) {
20
- o["default"] = v;
21
- });
22
- var __importStar = (this && this.__importStar) || (function () {
23
- var ownKeys = function(o) {
24
- ownKeys = Object.getOwnPropertyNames || function (o) {
25
- var ar = [];
26
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
27
- return ar;
28
- };
29
- return ownKeys(o);
30
- };
31
- return function (mod) {
32
- if (mod && mod.__esModule) return mod;
33
- var result = {};
34
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
35
- __setModuleDefault(result, mod);
36
- return result;
37
- };
38
- })();
39
- Object.defineProperty(exports, "__esModule", { value: true });
40
- exports.advertiseMdns = advertiseMdns;
41
- exports.withdrawMdns = withdrawMdns;
42
- let responder = null;
43
- let service = null;
44
- async function advertiseMdns(opts) {
45
- try {
46
- const mod = (await Promise.resolve().then(() => __importStar(require('@homebridge/ciao'))));
47
- const getResponder = mod.getResponder ?? mod.default?.getResponder;
48
- if (!getResponder)
49
- return false;
50
- responder = getResponder();
51
- service = responder.createService({
52
- name: opts.name,
53
- // mobile-app v1 wire compat — mDNS service type is frozen, do not rename.
54
- type: 'specrailshub',
55
- port: opts.port,
56
- txt: { id: opts.instanceId, fp: opts.fingerprint },
57
- });
58
- await service.advertise();
59
- return true;
60
- }
61
- catch (err) {
62
- console.warn('[mobile-mdns] advertise failed (non-fatal):', err instanceof Error ? err.message : err);
63
- responder = null;
64
- service = null;
65
- return false;
66
- }
67
- }
68
- async function withdrawMdns() {
69
- try {
70
- if (service)
71
- await service.end();
72
- }
73
- catch { /* ignore */ }
74
- try {
75
- if (responder)
76
- await responder.shutdown();
77
- }
78
- catch { /* ignore */ }
79
- service = null;
80
- responder = null;
81
- }
@@ -1,179 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PairingManager = void 0;
4
- const crypto_1 = require("crypto");
5
- const auth_1 = require("../auth");
6
- // In-memory pairing state machine. At most one pairing session is open at a time
7
- // (the desktop opens it when the user clicks "Pair device"). Sessions, lockout
8
- // counters, and the one-time token delivery live ONLY in memory — they are voided
9
- // by a process restart/sleep/self-update, which is acceptable (paired devices
10
- // survive in desktop.sqlite; an in-progress pair just restarts).
11
- //
12
- // Security (verified gaps from the adversarial review):
13
- // - secret: 16 random bytes (128-bit), single-use, 60s TTL → no PAKE needed.
14
- // - claimId: separate 16-byte handle; the token is delivered EXACTLY ONCE then
15
- // scrubbed (a later poll returns approved-without-token).
16
- // - lockout: per-IP (5 bad secrets → 60s) AND a global attempt cap (defeats
17
- // IPv6 privacy-address rotation) that destroys the session on breach.
18
- // - desktop Approve click gates token issuance (QR possession is not enough).
19
- // 5 minutes: long enough for a manual copy→paste→type flow (e.g. pairing a
20
- // simulator with no camera) while still single-use + gated by the desktop
21
- // approval click + a 128-bit secret, so the leaked-QR window stays small.
22
- const DEFAULT_TTL_MS = 300_000;
23
- const PER_IP_MAX_FAILS = 5;
24
- const PER_IP_LOCKOUT_MS = 60_000;
25
- const GLOBAL_WINDOW_MS = 60_000;
26
- const GLOBAL_MAX_ATTEMPTS = 20;
27
- class PairingManager {
28
- _session = null;
29
- _perIp = new Map();
30
- _globalAttempts = [];
31
- _deps;
32
- constructor(deps) {
33
- this._deps = {
34
- ...deps,
35
- clock: deps.clock ?? (() => Date.now()),
36
- genBytes: deps.genBytes ?? ((n) => (0, crypto_1.randomBytes)(n)),
37
- ttlMs: deps.ttlMs ?? DEFAULT_TTL_MS,
38
- };
39
- }
40
- now() {
41
- return this._deps.clock();
42
- }
43
- b64url(n) {
44
- return this._deps.genBytes(n).toString('base64url');
45
- }
46
- /** Open (or replace) a pairing session and return the QR payload to render. */
47
- createSession() {
48
- const now = this.now();
49
- const session = {
50
- secret: this.b64url(16),
51
- claimId: this.b64url(16),
52
- exp: now + this._deps.ttlMs,
53
- status: 'pending',
54
- delivered: false,
55
- };
56
- this._session = session;
57
- this._perIp.clear();
58
- this._globalAttempts = [];
59
- return {
60
- v: 1,
61
- // mobile-app v1 wire compat — QR field name `hub` is frozen, do not rename.
62
- hub: this._deps.desktopInstanceId(),
63
- name: this._deps.desktopName(),
64
- addrs: this._deps.lanAddresses(),
65
- port: this._deps.port(),
66
- fp: this._deps.certFingerprint(),
67
- secret: session.secret,
68
- claimId: session.claimId,
69
- exp: Math.floor(session.exp / 1000),
70
- };
71
- }
72
- isExpired(s) {
73
- return this.now() > s.exp && s.status !== 'approved';
74
- }
75
- /** Phone submits the QR secret. Enforces lockout + single-claim. */
76
- claim(secret, device, ip) {
77
- const s = this._session;
78
- if (!s)
79
- return { ok: false, reason: 'no-session' };
80
- if (this.isExpired(s)) {
81
- this._session = null;
82
- return { ok: false, reason: 'expired' };
83
- }
84
- // Per-IP lockout
85
- const rec = this._perIp.get(ip);
86
- if (rec && rec.until > this.now())
87
- return { ok: false, reason: 'locked' };
88
- // Global attempt cap (sliding 60s window) — defeats IPv6 privacy rotation.
89
- const now = this.now();
90
- this._globalAttempts = this._globalAttempts.filter((t) => now - t < GLOBAL_WINDOW_MS);
91
- this._globalAttempts.push(now);
92
- if (this._globalAttempts.length > GLOBAL_MAX_ATTEMPTS) {
93
- this._session = null;
94
- return { ok: false, reason: 'locked' };
95
- }
96
- if (s.status !== 'pending')
97
- return { ok: false, reason: 'already-claimed' };
98
- if (!(0, auth_1.safeEqual)(secret, s.secret)) {
99
- const next = { count: (rec?.count ?? 0) + 1, until: rec?.until ?? 0 };
100
- if (next.count >= PER_IP_MAX_FAILS)
101
- next.until = now + PER_IP_LOCKOUT_MS;
102
- this._perIp.set(ip, next);
103
- return { ok: false, reason: 'invalid' };
104
- }
105
- s.status = 'claimed';
106
- s.device = { name: device.name.slice(0, 80) || 'Unknown device', platform: device.platform };
107
- try {
108
- this._deps.onClaimed?.(s.device);
109
- }
110
- catch { /* non-fatal */ }
111
- return { ok: true };
112
- }
113
- /** Desktop approves → issue a per-device token (delivered once via pollStatus). */
114
- approve() {
115
- const s = this._session;
116
- if (!s)
117
- return { ok: false, reason: 'no-session' };
118
- if (s.status !== 'claimed' || !s.device)
119
- return { ok: false, reason: 'not-claimed' };
120
- const token = this._deps.genBytes(32).toString('hex');
121
- const deviceId = this._deps.createDevice({
122
- name: s.device.name,
123
- platform: s.device.platform,
124
- token,
125
- certFingerprint: this._deps.certFingerprint(),
126
- });
127
- s.approved = {
128
- approved: true,
129
- deviceToken: token,
130
- deviceId,
131
- // mobile-app v1 wire compat — `hubName`/`hubInstanceId` field names are
132
- // frozen (delivered to the phone via /pair/status), do not rename.
133
- hubName: this._deps.desktopName(),
134
- hubInstanceId: this._deps.desktopInstanceId(),
135
- };
136
- s.status = 'approved';
137
- return { ok: true };
138
- }
139
- deny() {
140
- if (this._session)
141
- this._session.status = 'denied';
142
- }
143
- cancel() {
144
- this._session = null;
145
- }
146
- /** Desktop UI poll. */
147
- getDesktopState() {
148
- const s = this._session;
149
- if (!s)
150
- return null;
151
- const status = this.isExpired(s) ? 'expired' : s.status;
152
- return { status, claimId: s.claimId, device: s.device };
153
- }
154
- /** Phone poll. Delivers the token EXACTLY ONCE, then scrubs it. */
155
- pollStatus(claimId) {
156
- const s = this._session;
157
- if (!s || !(0, auth_1.safeEqual)(claimId, s.claimId))
158
- return { status: 'expired' };
159
- if (s.status === 'approved' && s.approved) {
160
- if (!s.delivered) {
161
- s.delivered = true;
162
- const result = s.approved;
163
- // Scrub the token from memory after the single delivery.
164
- s.approved = { ...result, deviceToken: '' };
165
- return result;
166
- }
167
- // Already delivered — never hand the token out twice.
168
- return { status: 'expired' };
169
- }
170
- if (this.isExpired(s))
171
- return { status: 'expired' };
172
- if (s.status === 'denied')
173
- return { status: 'denied' };
174
- if (s.status === 'claimed')
175
- return { status: 'claimed' };
176
- return { status: 'pending' };
177
- }
178
- }
179
- exports.PairingManager = PairingManager;