@dsiloed/silo-link 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 (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +350 -0
  3. package/dist/api/dsiloed-client.d.ts +32 -0
  4. package/dist/api/dsiloed-client.d.ts.map +1 -0
  5. package/dist/api/dsiloed-client.js +240 -0
  6. package/dist/api/dsiloed-client.js.map +1 -0
  7. package/dist/cable/action-cable-client.d.ts +28 -0
  8. package/dist/cable/action-cable-client.d.ts.map +1 -0
  9. package/dist/cable/action-cable-client.js +194 -0
  10. package/dist/cable/action-cable-client.js.map +1 -0
  11. package/dist/cable/subscription-manager.d.ts +29 -0
  12. package/dist/cable/subscription-manager.d.ts.map +1 -0
  13. package/dist/cable/subscription-manager.js +125 -0
  14. package/dist/cable/subscription-manager.js.map +1 -0
  15. package/dist/cli/commands.d.ts +4 -0
  16. package/dist/cli/commands.d.ts.map +1 -0
  17. package/dist/cli/commands.js +144 -0
  18. package/dist/cli/commands.js.map +1 -0
  19. package/dist/cli/daemon.d.ts +5 -0
  20. package/dist/cli/daemon.d.ts.map +1 -0
  21. package/dist/cli/daemon.js +35 -0
  22. package/dist/cli/daemon.js.map +1 -0
  23. package/dist/config/config-manager.d.ts +7 -0
  24. package/dist/config/config-manager.d.ts.map +1 -0
  25. package/dist/config/config-manager.js +76 -0
  26. package/dist/config/config-manager.js.map +1 -0
  27. package/dist/config/jwt-generator.d.ts +15 -0
  28. package/dist/config/jwt-generator.d.ts.map +1 -0
  29. package/dist/config/jwt-generator.js +54 -0
  30. package/dist/config/jwt-generator.js.map +1 -0
  31. package/dist/core/bridge.d.ts +37 -0
  32. package/dist/core/bridge.d.ts.map +1 -0
  33. package/dist/core/bridge.js +247 -0
  34. package/dist/core/bridge.js.map +1 -0
  35. package/dist/core/claude-launcher.d.ts +78 -0
  36. package/dist/core/claude-launcher.d.ts.map +1 -0
  37. package/dist/core/claude-launcher.js +352 -0
  38. package/dist/core/claude-launcher.js.map +1 -0
  39. package/dist/core/message-queue.d.ts +47 -0
  40. package/dist/core/message-queue.d.ts.map +1 -0
  41. package/dist/core/message-queue.js +139 -0
  42. package/dist/core/message-queue.js.map +1 -0
  43. package/dist/core/session-manager.d.ts +24 -0
  44. package/dist/core/session-manager.d.ts.map +1 -0
  45. package/dist/core/session-manager.js +111 -0
  46. package/dist/core/session-manager.js.map +1 -0
  47. package/dist/index.d.ts +3 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +4 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/mcp/server.d.ts +27 -0
  52. package/dist/mcp/server.d.ts.map +1 -0
  53. package/dist/mcp/server.js +109 -0
  54. package/dist/mcp/server.js.map +1 -0
  55. package/dist/mcp/tools/register-tools.d.ts +19 -0
  56. package/dist/mcp/tools/register-tools.d.ts.map +1 -0
  57. package/dist/mcp/tools/register-tools.js +385 -0
  58. package/dist/mcp/tools/register-tools.js.map +1 -0
  59. package/dist/types/index.d.ts +74 -0
  60. package/dist/types/index.d.ts.map +1 -0
  61. package/dist/types/index.js +2 -0
  62. package/dist/types/index.js.map +1 -0
  63. package/package.json +57 -0
@@ -0,0 +1,28 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import type { SiloLinkConfig, ConnectionState } from '../types/index.js';
3
+ import type { JwtGenerator } from '../config/jwt-generator.js';
4
+ export declare class ActionCableClient extends EventEmitter {
5
+ private config;
6
+ private jwt;
7
+ private ws;
8
+ private state;
9
+ private reconnectAttempts;
10
+ private reconnectTimer;
11
+ private pendingSubscriptions;
12
+ private confirmedSubscriptions;
13
+ constructor(config: SiloLinkConfig, jwt: JwtGenerator);
14
+ getState(): ConnectionState;
15
+ connect(): void;
16
+ disconnect(): void;
17
+ subscribe(conversationId: number): void;
18
+ unsubscribe(conversationId: number): void;
19
+ /**
20
+ * Subscribe to a generic ActionCable channel (not conversation-specific).
21
+ */
22
+ subscribeChannel(channelName: string, params?: Record<string, unknown>): void;
23
+ private sendSubscribe;
24
+ private handleMessage;
25
+ private scheduleReconnect;
26
+ private clearReconnectTimer;
27
+ }
28
+ //# sourceMappingURL=action-cable-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action-cable-client.d.ts","sourceRoot":"","sources":["../../src/cable/action-cable-client.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAU/D,qBAAa,iBAAkB,SAAQ,YAAY;IACjD,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,GAAG,CAAe;IAC1B,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,KAAK,CAAmC;IAChD,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,oBAAoB,CAAkC;IAC9D,OAAO,CAAC,sBAAsB,CAA0B;gBAE5C,MAAM,EAAE,cAAc,EAAE,GAAG,EAAE,YAAY;IAMrD,QAAQ,IAAI,eAAe;IAI3B,OAAO,IAAI,IAAI;IA8Cf,UAAU,IAAI,IAAI;IAelB,SAAS,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI;IAavC,WAAW,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI;IAiBzC;;OAEG;IACH,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAa7E,OAAO,CAAC,aAAa;IASrB,OAAO,CAAC,aAAa;IAgErB,OAAO,CAAC,iBAAiB;IAsBzB,OAAO,CAAC,mBAAmB;CAM5B"}
@@ -0,0 +1,194 @@
1
+ import WebSocket from 'ws';
2
+ import { EventEmitter } from 'node:events';
3
+ export class ActionCableClient extends EventEmitter {
4
+ config;
5
+ jwt;
6
+ ws = null;
7
+ state = 'disconnected';
8
+ reconnectAttempts = 0;
9
+ reconnectTimer = null;
10
+ pendingSubscriptions = new Map();
11
+ confirmedSubscriptions = new Set();
12
+ constructor(config, jwt) {
13
+ super();
14
+ this.config = config;
15
+ this.jwt = jwt;
16
+ }
17
+ getState() {
18
+ return this.state;
19
+ }
20
+ connect() {
21
+ if (this.state === 'connected' || this.state === 'connecting')
22
+ return;
23
+ this.state = 'connecting';
24
+ this.emit('stateChange', this.state);
25
+ const token = this.jwt.getToken();
26
+ const wsHost = this.config.host.replace(/^http/, 'ws');
27
+ const url = `${wsHost}/cable?token=${encodeURIComponent(token)}&tenant_id=${encodeURIComponent(this.config.tenant_id)}`;
28
+ this.ws = new WebSocket(url);
29
+ this.ws.on('open', () => {
30
+ // Wait for welcome message before considering connected
31
+ });
32
+ this.ws.on('message', (data) => {
33
+ try {
34
+ const raw = data.toString();
35
+ const envelope = JSON.parse(raw);
36
+ if (envelope.type !== 'ping') {
37
+ console.log(`[ActionCable] ← ${raw.substring(0, 200)}`);
38
+ }
39
+ this.handleMessage(envelope);
40
+ }
41
+ catch {
42
+ console.log(`[ActionCable] ← (unparseable): ${data.toString().substring(0, 100)}`);
43
+ }
44
+ });
45
+ this.ws.on('close', (code, reason) => {
46
+ console.log(`[ActionCable] Connection closed: code=${code}, reason=${reason.toString()}`);
47
+ const previousState = this.state;
48
+ this.confirmedSubscriptions.clear();
49
+ this.state = 'disconnected';
50
+ this.emit('stateChange', this.state);
51
+ if (previousState === 'connected' || previousState === 'connecting') {
52
+ this.scheduleReconnect();
53
+ }
54
+ });
55
+ this.ws.on('error', (err) => {
56
+ this.emit('error', err);
57
+ });
58
+ }
59
+ disconnect() {
60
+ this.clearReconnectTimer();
61
+ this.reconnectAttempts = 0;
62
+ if (this.ws) {
63
+ this.ws.removeAllListeners();
64
+ this.ws.close();
65
+ this.ws = null;
66
+ }
67
+ this.confirmedSubscriptions.clear();
68
+ this.state = 'disconnected';
69
+ this.emit('stateChange', this.state);
70
+ }
71
+ subscribe(conversationId) {
72
+ const identifier = JSON.stringify({
73
+ channel: 'ConversationChannel',
74
+ conversation_id: conversationId,
75
+ });
76
+ this.pendingSubscriptions.set(identifier, String(conversationId));
77
+ if (this.state === 'connected' && this.ws) {
78
+ this.sendSubscribe(identifier);
79
+ }
80
+ }
81
+ unsubscribe(conversationId) {
82
+ const identifier = JSON.stringify({
83
+ channel: 'ConversationChannel',
84
+ conversation_id: conversationId,
85
+ });
86
+ this.pendingSubscriptions.delete(identifier);
87
+ this.confirmedSubscriptions.delete(identifier);
88
+ if (this.state === 'connected' && this.ws) {
89
+ this.ws.send(JSON.stringify({
90
+ command: 'unsubscribe',
91
+ identifier,
92
+ }));
93
+ }
94
+ }
95
+ /**
96
+ * Subscribe to a generic ActionCable channel (not conversation-specific).
97
+ */
98
+ subscribeChannel(channelName, params) {
99
+ const identifier = JSON.stringify({
100
+ channel: channelName,
101
+ ...params,
102
+ });
103
+ this.pendingSubscriptions.set(identifier, `channel:${channelName}`);
104
+ if (this.state === 'connected' && this.ws) {
105
+ this.sendSubscribe(identifier);
106
+ }
107
+ }
108
+ sendSubscribe(identifier) {
109
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
110
+ this.ws.send(JSON.stringify({
111
+ command: 'subscribe',
112
+ identifier,
113
+ }));
114
+ }
115
+ }
116
+ handleMessage(envelope) {
117
+ switch (envelope.type) {
118
+ case 'welcome':
119
+ this.state = 'connected';
120
+ this.reconnectAttempts = 0;
121
+ this.emit('stateChange', this.state);
122
+ // Re-subscribe to all pending subscriptions (skip already confirmed)
123
+ for (const identifier of this.pendingSubscriptions.keys()) {
124
+ if (!this.confirmedSubscriptions.has(identifier)) {
125
+ this.sendSubscribe(identifier);
126
+ }
127
+ }
128
+ break;
129
+ case 'ping':
130
+ // ActionCable ping — no response needed
131
+ break;
132
+ case 'confirm_subscription':
133
+ if (envelope.identifier) {
134
+ this.confirmedSubscriptions.add(envelope.identifier);
135
+ this.emit('subscribed', envelope.identifier);
136
+ }
137
+ break;
138
+ case 'reject_subscription':
139
+ if (envelope.identifier) {
140
+ this.emit('rejected', envelope.identifier);
141
+ }
142
+ break;
143
+ case 'disconnect':
144
+ if (envelope.reconnect === false) {
145
+ this.disconnect();
146
+ }
147
+ break;
148
+ default:
149
+ // Data message — has identifier + message but no type
150
+ if (envelope.identifier && envelope.message) {
151
+ try {
152
+ const parsed = JSON.parse(envelope.identifier);
153
+ if (parsed.channel === 'SilolinkControlChannel') {
154
+ // Control channel message — route to control handler
155
+ this.emit('control', { data: envelope.message });
156
+ }
157
+ else if (parsed.conversation_id) {
158
+ // Conversation message
159
+ this.emit('message', {
160
+ conversationId: parsed.conversation_id,
161
+ data: envelope.message,
162
+ });
163
+ }
164
+ }
165
+ catch {
166
+ // Ignore malformed identifiers
167
+ }
168
+ }
169
+ break;
170
+ }
171
+ }
172
+ scheduleReconnect() {
173
+ if (this.reconnectAttempts >= this.config.max_reconnect_attempts) {
174
+ this.emit('maxReconnectAttemptsReached');
175
+ return;
176
+ }
177
+ this.state = 'reconnecting';
178
+ this.emit('stateChange', this.state);
179
+ // Exponential backoff: 1s, 2s, 4s, 8s, ... capped at 60s
180
+ const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 60000);
181
+ this.reconnectAttempts++;
182
+ this.reconnectTimer = setTimeout(() => {
183
+ this.ws = null;
184
+ this.connect();
185
+ }, delay);
186
+ }
187
+ clearReconnectTimer() {
188
+ if (this.reconnectTimer) {
189
+ clearTimeout(this.reconnectTimer);
190
+ this.reconnectTimer = null;
191
+ }
192
+ }
193
+ }
194
+ //# sourceMappingURL=action-cable-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action-cable-client.js","sourceRoot":"","sources":["../../src/cable/action-cable-client.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,IAAI,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAY3C,MAAM,OAAO,iBAAkB,SAAQ,YAAY;IACzC,MAAM,CAAiB;IACvB,GAAG,CAAe;IAClB,EAAE,GAAqB,IAAI,CAAC;IAC5B,KAAK,GAAoB,cAAc,CAAC;IACxC,iBAAiB,GAAG,CAAC,CAAC;IACtB,cAAc,GAAyC,IAAI,CAAC;IAC5D,oBAAoB,GAAwB,IAAI,GAAG,EAAE,CAAC;IACtD,sBAAsB,GAAgB,IAAI,GAAG,EAAE,CAAC;IAExD,YAAY,MAAsB,EAAE,GAAiB;QACnD,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;IACjB,CAAC;IAED,QAAQ;QACN,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,OAAO;QACL,IAAI,IAAI,CAAC,KAAK,KAAK,WAAW,IAAI,IAAI,CAAC,KAAK,KAAK,YAAY;YAAE,OAAO;QAEtE,IAAI,CAAC,KAAK,GAAG,YAAY,CAAC;QAC1B,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QAErC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;QAClC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACvD,MAAM,GAAG,GAAG,GAAG,MAAM,gBAAgB,kBAAkB,CAAC,KAAK,CAAC,cAAc,kBAAkB,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;QAExH,IAAI,CAAC,EAAE,GAAG,IAAI,SAAS,CAAC,GAAG,CAAC,CAAC;QAE7B,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;YACtB,wDAAwD;QAC1D,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,IAAoB,EAAE,EAAE;YAC7C,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAyB,CAAC;gBACzD,IAAI,QAAQ,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;oBAC7B,OAAO,CAAC,GAAG,CAAC,mBAAmB,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;gBAC1D,CAAC;gBACD,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;YAC/B,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,CAAC,GAAG,CAAC,kCAAkC,IAAI,CAAC,QAAQ,EAAE,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;YACrF,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAY,EAAE,MAAc,EAAE,EAAE;YACnD,OAAO,CAAC,GAAG,CAAC,yCAAyC,IAAI,YAAY,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;YAC1F,MAAM,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC;YACjC,IAAI,CAAC,sBAAsB,CAAC,KAAK,EAAE,CAAC;YACpC,IAAI,CAAC,KAAK,GAAG,cAAc,CAAC;YAC5B,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;YAErC,IAAI,aAAa,KAAK,WAAW,IAAI,aAAa,KAAK,YAAY,EAAE,CAAC;gBACpE,IAAI,CAAC,iBAAiB,EAAE,CAAC;YAC3B,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAU,EAAE,EAAE;YACjC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QAC1B,CAAC,CAAC,CAAC;IACL,CAAC;IAED,UAAU;QACR,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC3B,IAAI,CAAC,iBAAiB,GAAG,CAAC,CAAC;QAE3B,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC;YACZ,IAAI,CAAC,EAAE,CAAC,kBAAkB,EAAE,CAAC;YAC7B,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;YAChB,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC;QACjB,CAAC;QAED,IAAI,CAAC,sBAAsB,CAAC,KAAK,EAAE,CAAC;QACpC,IAAI,CAAC,KAAK,GAAG,cAAc,CAAC;QAC5B,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IACvC,CAAC;IAED,SAAS,CAAC,cAAsB;QAC9B,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC;YAChC,OAAO,EAAE,qBAAqB;YAC9B,eAAe,EAAE,cAAc;SAChC,CAAC,CAAC;QAEH,IAAI,CAAC,oBAAoB,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC;QAElE,IAAI,IAAI,CAAC,KAAK,KAAK,WAAW,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC;YAC1C,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IAED,WAAW,CAAC,cAAsB;QAChC,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC;YAChC,OAAO,EAAE,qBAAqB;YAC9B,eAAe,EAAE,cAAc;SAChC,CAAC,CAAC;QAEH,IAAI,CAAC,oBAAoB,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC7C,IAAI,CAAC,sBAAsB,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAE/C,IAAI,IAAI,CAAC,KAAK,KAAK,WAAW,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC;YAC1C,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC;gBAC1B,OAAO,EAAE,aAAa;gBACtB,UAAU;aACX,CAAC,CAAC,CAAC;QACN,CAAC;IACH,CAAC;IAED;;OAEG;IACH,gBAAgB,CAAC,WAAmB,EAAE,MAAgC;QACpE,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC;YAChC,OAAO,EAAE,WAAW;YACpB,GAAG,MAAM;SACV,CAAC,CAAC;QAEH,IAAI,CAAC,oBAAoB,CAAC,GAAG,CAAC,UAAU,EAAE,WAAW,WAAW,EAAE,CAAC,CAAC;QAEpE,IAAI,IAAI,CAAC,KAAK,KAAK,WAAW,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC;YAC1C,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IAEO,aAAa,CAAC,UAAkB;QACtC,IAAI,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,EAAE,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;YACrD,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC;gBAC1B,OAAO,EAAE,WAAW;gBACpB,UAAU;aACX,CAAC,CAAC,CAAC;QACN,CAAC;IACH,CAAC;IAEO,aAAa,CAAC,QAA8B;QAClD,QAAQ,QAAQ,CAAC,IAAI,EAAE,CAAC;YACtB,KAAK,SAAS;gBACZ,IAAI,CAAC,KAAK,GAAG,WAAW,CAAC;gBACzB,IAAI,CAAC,iBAAiB,GAAG,CAAC,CAAC;gBAC3B,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;gBACrC,qEAAqE;gBACrE,KAAK,MAAM,UAAU,IAAI,IAAI,CAAC,oBAAoB,CAAC,IAAI,EAAE,EAAE,CAAC;oBAC1D,IAAI,CAAC,IAAI,CAAC,sBAAsB,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;wBACjD,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;oBACjC,CAAC;gBACH,CAAC;gBACD,MAAM;YAER,KAAK,MAAM;gBACT,wCAAwC;gBACxC,MAAM;YAER,KAAK,sBAAsB;gBACzB,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC;oBACxB,IAAI,CAAC,sBAAsB,CAAC,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;oBACrD,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;gBAC/C,CAAC;gBACD,MAAM;YAER,KAAK,qBAAqB;gBACxB,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC;oBACxB,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;gBAC7C,CAAC;gBACD,MAAM;YAER,KAAK,YAAY;gBACf,IAAI,QAAQ,CAAC,SAAS,KAAK,KAAK,EAAE,CAAC;oBACjC,IAAI,CAAC,UAAU,EAAE,CAAC;gBACpB,CAAC;gBACD,MAAM;YAER;gBACE,sDAAsD;gBACtD,IAAI,QAAQ,CAAC,UAAU,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;oBAC5C,IAAI,CAAC;wBACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,UAAU,CAG5C,CAAC;wBAEF,IAAI,MAAM,CAAC,OAAO,KAAK,wBAAwB,EAAE,CAAC;4BAChD,qDAAqD;4BACrD,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;wBACnD,CAAC;6BAAM,IAAI,MAAM,CAAC,eAAe,EAAE,CAAC;4BAClC,uBAAuB;4BACvB,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE;gCACnB,cAAc,EAAE,MAAM,CAAC,eAAe;gCACtC,IAAI,EAAE,QAAQ,CAAC,OAAO;6BACvB,CAAC,CAAC;wBACL,CAAC;oBACH,CAAC;oBAAC,MAAM,CAAC;wBACP,+BAA+B;oBACjC,CAAC;gBACH,CAAC;gBACD,MAAM;QACV,CAAC;IACH,CAAC;IAEO,iBAAiB;QACvB,IAAI,IAAI,CAAC,iBAAiB,IAAI,IAAI,CAAC,MAAM,CAAC,sBAAsB,EAAE,CAAC;YACjE,IAAI,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;YACzC,OAAO;QACT,CAAC;QAED,IAAI,CAAC,KAAK,GAAG,cAAc,CAAC;QAC5B,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QAErC,yDAAyD;QACzD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CACpB,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,iBAAiB,CAAC,EAC1C,KAAK,CACN,CAAC;QACF,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAEzB,IAAI,CAAC,cAAc,GAAG,UAAU,CAAC,GAAG,EAAE;YACpC,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC;YACf,IAAI,CAAC,OAAO,EAAE,CAAC;QACjB,CAAC,EAAE,KAAK,CAAC,CAAC;IACZ,CAAC;IAEO,mBAAmB;QACzB,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,YAAY,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;YAClC,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;QAC7B,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,29 @@
1
+ import type { ActionCableClient } from './action-cable-client.js';
2
+ import type { MessageQueue } from '../core/message-queue.js';
3
+ import type { SessionManager } from '../core/session-manager.js';
4
+ import type { ClaudeLauncher } from '../core/claude-launcher.js';
5
+ import type { DSiloedClient } from '../api/dsiloed-client.js';
6
+ export declare class SubscriptionManager {
7
+ private cableClient;
8
+ private messageQueue;
9
+ private sessionManager;
10
+ private claudeLauncher;
11
+ private dsiloedClient;
12
+ private outboundMessageIds;
13
+ private pendingMessages;
14
+ constructor(cableClient: ActionCableClient, messageQueue: MessageQueue, sessionManager: SessionManager, claudeLauncher?: ClaudeLauncher, dsiloedClient?: DSiloedClient);
15
+ subscribe(conversationId: number): void;
16
+ unsubscribe(conversationId: number): void;
17
+ /**
18
+ * Clean up pending messages older than 5 minutes (session never registered).
19
+ */
20
+ cleanupStalePendingMessages(): void;
21
+ /**
22
+ * Drain any buffered messages for a conversation into the session's queue.
23
+ * Called after a session registers for a conversation.
24
+ */
25
+ drainPendingMessages(conversationId: number, sessionId: string): void;
26
+ trackOutboundMessageId(messageId: number): void;
27
+ private handleCableMessage;
28
+ }
29
+ //# sourceMappingURL=subscription-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subscription-manager.d.ts","sourceRoot":"","sources":["../../src/cable/subscription-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAK9D,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,WAAW,CAAoB;IACvC,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,cAAc,CAAwB;IAC9C,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,kBAAkB,CAAgB;IAE1C,OAAO,CAAC,eAAe,CAA4C;gBAGjE,WAAW,EAAE,iBAAiB,EAC9B,YAAY,EAAE,YAAY,EAC1B,cAAc,EAAE,cAAc,EAC9B,cAAc,CAAC,EAAE,cAAc,EAC/B,aAAa,CAAC,EAAE,aAAa;IAa/B,SAAS,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI;IAIvC,WAAW,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI;IAIzC;;OAEG;IACH,2BAA2B,IAAI,IAAI;IAanC;;;OAGG;IACH,oBAAoB,CAAC,cAAc,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAWrE,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAO/C,OAAO,CAAC,kBAAkB;CA0E3B"}
@@ -0,0 +1,125 @@
1
+ const MAX_TRACKED_IDS = 100;
2
+ export class SubscriptionManager {
3
+ cableClient;
4
+ messageQueue;
5
+ sessionManager;
6
+ claudeLauncher;
7
+ dsiloedClient;
8
+ outboundMessageIds = [];
9
+ // Buffer messages for conversations that don't have a session yet (pending launch)
10
+ pendingMessages = new Map();
11
+ constructor(cableClient, messageQueue, sessionManager, claudeLauncher, dsiloedClient) {
12
+ this.cableClient = cableClient;
13
+ this.messageQueue = messageQueue;
14
+ this.sessionManager = sessionManager;
15
+ this.claudeLauncher = claudeLauncher || null;
16
+ this.dsiloedClient = dsiloedClient || null;
17
+ this.cableClient.on('message', (event) => {
18
+ this.handleCableMessage(event.conversationId, event.data);
19
+ });
20
+ }
21
+ subscribe(conversationId) {
22
+ this.cableClient.subscribe(conversationId);
23
+ }
24
+ unsubscribe(conversationId) {
25
+ this.cableClient.unsubscribe(conversationId);
26
+ }
27
+ /**
28
+ * Clean up pending messages older than 5 minutes (session never registered).
29
+ */
30
+ cleanupStalePendingMessages() {
31
+ const maxAge = 5 * 60 * 1000;
32
+ const now = Date.now();
33
+ for (const [convId, msgs] of this.pendingMessages) {
34
+ const fresh = msgs.filter(m => now - m.receivedAt.getTime() < maxAge);
35
+ if (fresh.length === 0) {
36
+ this.pendingMessages.delete(convId);
37
+ }
38
+ else {
39
+ this.pendingMessages.set(convId, fresh);
40
+ }
41
+ }
42
+ }
43
+ /**
44
+ * Drain any buffered messages for a conversation into the session's queue.
45
+ * Called after a session registers for a conversation.
46
+ */
47
+ drainPendingMessages(conversationId, sessionId) {
48
+ const pending = this.pendingMessages.get(conversationId);
49
+ if (pending && pending.length > 0) {
50
+ console.log(`[SiloLink] Draining ${pending.length} buffered message(s) for conversation ${conversationId}`);
51
+ for (const msg of pending) {
52
+ this.messageQueue.enqueue(sessionId, msg);
53
+ }
54
+ this.pendingMessages.delete(conversationId);
55
+ }
56
+ }
57
+ trackOutboundMessageId(messageId) {
58
+ this.outboundMessageIds.push(messageId);
59
+ if (this.outboundMessageIds.length > MAX_TRACKED_IDS) {
60
+ this.outboundMessageIds.shift();
61
+ }
62
+ }
63
+ handleCableMessage(conversationId, data) {
64
+ const msg = data;
65
+ // Silently skip non-message events (typing indicators, read receipts, etc.)
66
+ if (msg.type !== 'new_message' || !msg.message)
67
+ return;
68
+ console.log(`[SiloLink] Cable message received: conv=${conversationId}, msgId=${msg.message?.id}, sender=${msg.message?.sender_name}`);
69
+ // Echo prevention — skip our own messages
70
+ // Primary: check tracked outbound message IDs
71
+ if (this.outboundMessageIds.includes(msg.message.id)) {
72
+ console.log(`[SiloLink] Skipping echo (tracked ID): ${msg.message.id}`);
73
+ return;
74
+ }
75
+ // Extract content — handle both nested object and string formats
76
+ const rawMessage = msg.message.message;
77
+ const content = typeof rawMessage === 'string'
78
+ ? rawMessage
79
+ : rawMessage?.content || '';
80
+ const role = typeof rawMessage === 'string'
81
+ ? 'user'
82
+ : rawMessage?.role || 'user';
83
+ // Secondary echo prevention: skip messages with our prefix
84
+ if (content.startsWith('**[Claude Code]**')) {
85
+ console.log(`[SiloLink] Skipping echo (prefix match): ${content.substring(0, 50)}`);
86
+ return;
87
+ }
88
+ console.log(`[SiloLink] Inbound message accepted: id=${msg.message.id}, role=${role}, content="${content.substring(0, 80)}"`);
89
+ const inbound = {
90
+ id: msg.message.id,
91
+ role,
92
+ content,
93
+ senderName: msg.message.sender_name,
94
+ receivedAt: new Date(),
95
+ };
96
+ // Route to correct session by conversation ID
97
+ const session = this.sessionManager.getByConversationId(conversationId);
98
+ if (session) {
99
+ console.log(`[SiloLink] Enqueuing to session ${session.sessionId}`);
100
+ this.messageQueue.enqueue(session.sessionId, inbound);
101
+ }
102
+ else {
103
+ console.log(`[SiloLink] No session found for conversation ${conversationId}`);
104
+ // Auto-launch Claude Code if configured and no session exists
105
+ if (this.claudeLauncher?.isEnabled()) {
106
+ console.log(`[SiloLink] Triggering auto-launch for inbound message...`);
107
+ // Let the user know we're restarting
108
+ if (this.dsiloedClient) {
109
+ this.dsiloedClient.postMessage(conversationId, '**[Claude Code]** Restarting session... Claude is initializing and will be ready shortly.')
110
+ .catch(() => { });
111
+ }
112
+ // Buffer the message so it's delivered once the session registers
113
+ if (!this.pendingMessages.has(conversationId)) {
114
+ this.pendingMessages.set(conversationId, []);
115
+ }
116
+ this.pendingMessages.get(conversationId).push(inbound);
117
+ console.log(`[SiloLink] Buffered message for conversation ${conversationId} (pending session)`);
118
+ this.claudeLauncher.launchOnMessage(conversationId).catch(err => {
119
+ console.error(`[SiloLink] Auto-launch failed: ${err.message}`);
120
+ });
121
+ }
122
+ }
123
+ }
124
+ }
125
+ //# sourceMappingURL=subscription-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subscription-manager.js","sourceRoot":"","sources":["../../src/cable/subscription-manager.ts"],"names":[],"mappings":"AAOA,MAAM,eAAe,GAAG,GAAG,CAAC;AAE5B,MAAM,OAAO,mBAAmB;IACtB,WAAW,CAAoB;IAC/B,YAAY,CAAe;IAC3B,cAAc,CAAiB;IAC/B,cAAc,CAAwB;IACtC,aAAa,CAAuB;IACpC,kBAAkB,GAAa,EAAE,CAAC;IAC1C,mFAAmF;IAC3E,eAAe,GAAkC,IAAI,GAAG,EAAE,CAAC;IAEnE,YACE,WAA8B,EAC9B,YAA0B,EAC1B,cAA8B,EAC9B,cAA+B,EAC/B,aAA6B;QAE7B,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,cAAc,GAAG,cAAc,CAAC;QACrC,IAAI,CAAC,cAAc,GAAG,cAAc,IAAI,IAAI,CAAC;QAC7C,IAAI,CAAC,aAAa,GAAG,aAAa,IAAI,IAAI,CAAC;QAE3C,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,KAAgD,EAAE,EAAE;YAClF,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAC5D,CAAC,CAAC,CAAC;IACL,CAAC;IAED,SAAS,CAAC,cAAsB;QAC9B,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;IAC7C,CAAC;IAED,WAAW,CAAC,cAAsB;QAChC,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,cAAc,CAAC,CAAC;IAC/C,CAAC;IAED;;OAEG;IACH,2BAA2B;QACzB,MAAM,MAAM,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;QAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,KAAK,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YAClD,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,MAAM,CAAC,CAAC;YACtE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACvB,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACtC,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;YAC1C,CAAC;QACH,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,oBAAoB,CAAC,cAAsB,EAAE,SAAiB;QAC5D,MAAM,OAAO,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QACzD,IAAI,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAClC,OAAO,CAAC,GAAG,CAAC,uBAAuB,OAAO,CAAC,MAAM,yCAAyC,cAAc,EAAE,CAAC,CAAC;YAC5G,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;gBAC1B,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;YAC5C,CAAC;YACD,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;IAED,sBAAsB,CAAC,SAAiB;QACtC,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACxC,IAAI,IAAI,CAAC,kBAAkB,CAAC,MAAM,GAAG,eAAe,EAAE,CAAC;YACrD,IAAI,CAAC,kBAAkB,CAAC,KAAK,EAAE,CAAC;QAClC,CAAC;IACH,CAAC;IAEO,kBAAkB,CAAC,cAAsB,EAAE,IAAa;QAC9D,MAAM,GAAG,GAAG,IAOX,CAAC;QAEF,4EAA4E;QAC5E,IAAI,GAAG,CAAC,IAAI,KAAK,aAAa,IAAI,CAAC,GAAG,CAAC,OAAO;YAAE,OAAO;QAEvD,OAAO,CAAC,GAAG,CAAC,2CAA2C,cAAc,WAAW,GAAG,CAAC,OAAO,EAAE,EAAE,YAAY,GAAG,CAAC,OAAO,EAAE,WAAW,EAAE,CAAC,CAAC;QAEvI,0CAA0C;QAC1C,8CAA8C;QAC9C,IAAI,IAAI,CAAC,kBAAkB,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC;YACrD,OAAO,CAAC,GAAG,CAAC,0CAA0C,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;YACxE,OAAO;QACT,CAAC;QAED,iEAAiE;QACjE,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC;QACvC,MAAM,OAAO,GAAG,OAAO,UAAU,KAAK,QAAQ;YAC5C,CAAC,CAAC,UAAU;YACZ,CAAC,CAAC,UAAU,EAAE,OAAO,IAAI,EAAE,CAAC;QAC9B,MAAM,IAAI,GAAG,OAAO,UAAU,KAAK,QAAQ;YACzC,CAAC,CAAC,MAAM;YACR,CAAC,CAAC,UAAU,EAAE,IAAI,IAAI,MAAM,CAAC;QAE/B,2DAA2D;QAC3D,IAAI,OAAO,CAAC,UAAU,CAAC,mBAAmB,CAAC,EAAE,CAAC;YAC5C,OAAO,CAAC,GAAG,CAAC,4CAA4C,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC;YACpF,OAAO;QACT,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,2CAA2C,GAAG,CAAC,OAAO,CAAC,EAAE,UAAU,IAAI,cAAc,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC;QAE9H,MAAM,OAAO,GAAmB;YAC9B,EAAE,EAAE,GAAG,CAAC,OAAO,CAAC,EAAE;YAClB,IAAI;YACJ,OAAO;YACP,UAAU,EAAE,GAAG,CAAC,OAAO,CAAC,WAAW;YACnC,UAAU,EAAE,IAAI,IAAI,EAAE;SACvB,CAAC;QAEF,8CAA8C;QAC9C,MAAM,OAAO,GAAG,IAAI,CAAC,cAAc,CAAC,mBAAmB,CAAC,cAAc,CAAC,CAAC;QACxE,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,CAAC,GAAG,CAAC,mCAAmC,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;YACpE,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QACxD,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,gDAAgD,cAAc,EAAE,CAAC,CAAC;YAC9E,8DAA8D;YAC9D,IAAI,IAAI,CAAC,cAAc,EAAE,SAAS,EAAE,EAAE,CAAC;gBACrC,OAAO,CAAC,GAAG,CAAC,0DAA0D,CAAC,CAAC;gBACxE,qCAAqC;gBACrC,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;oBACvB,IAAI,CAAC,aAAa,CAAC,WAAW,CAAC,cAAc,EAAE,2FAA2F,CAAC;yBACxI,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;gBACrB,CAAC;gBACD,kEAAkE;gBAClE,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,CAAC;oBAC9C,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;gBAC/C,CAAC;gBACD,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,cAAc,CAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;gBACxD,OAAO,CAAC,GAAG,CAAC,gDAAgD,cAAc,oBAAoB,CAAC,CAAC;gBAChG,IAAI,CAAC,cAAc,CAAC,eAAe,CAAC,cAAc,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE;oBAC9D,OAAO,CAAC,KAAK,CAAC,kCAAkC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;gBACjE,CAAC,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,4 @@
1
+ import { Command } from 'commander';
2
+ declare const program: Command;
3
+ export { program };
4
+ //# sourceMappingURL=commands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/cli/commands.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AA8J9B,OAAO,EAAE,OAAO,EAAE,CAAC"}
@@ -0,0 +1,144 @@
1
+ import { Command } from 'commander';
2
+ import { spawn } from 'node:child_process';
3
+ import { fileURLToPath } from 'node:url';
4
+ import path from 'node:path';
5
+ import { interactiveConfig, configExists, loadConfig } from '../config/config-manager.js';
6
+ import { writePidFile, readPidFile, removePidFile, isProcessRunning } from './daemon.js';
7
+ import { Bridge } from '../core/bridge.js';
8
+ const program = new Command();
9
+ program
10
+ .name('silolink')
11
+ .description('Claude Code Remote Bridge — connects Claude Code to DSiloed')
12
+ .version('0.1.0');
13
+ program
14
+ .command('start')
15
+ .description('Start the SiloLink bridge')
16
+ .option('-d, --daemon', 'Run in background')
17
+ .action(async (opts) => {
18
+ if (!configExists()) {
19
+ console.error('Config not found. Run "silolink config" first.');
20
+ process.exit(1);
21
+ }
22
+ // Check if already running
23
+ const existingPid = readPidFile();
24
+ if (existingPid && isProcessRunning(existingPid)) {
25
+ console.log(`SiloLink already running (PID ${existingPid})`);
26
+ process.exit(0);
27
+ }
28
+ if (opts.daemon) {
29
+ // Spawn detached process
30
+ const entryPoint = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../index.js');
31
+ const child = spawn(process.execPath, [entryPoint, 'start'], {
32
+ detached: true,
33
+ stdio: 'ignore',
34
+ });
35
+ child.unref();
36
+ console.log(`SiloLink started in background (PID ${child.pid})`);
37
+ process.exit(0);
38
+ }
39
+ // Foreground mode
40
+ writePidFile();
41
+ process.on('exit', removePidFile);
42
+ const bridge = new Bridge();
43
+ await bridge.start();
44
+ });
45
+ program
46
+ .command('stop')
47
+ .description('Stop the SiloLink bridge')
48
+ .action(() => {
49
+ const pid = readPidFile();
50
+ if (!pid) {
51
+ console.log('SiloLink is not running (no PID file)');
52
+ return;
53
+ }
54
+ if (!isProcessRunning(pid)) {
55
+ console.log('SiloLink is not running (stale PID file)');
56
+ removePidFile();
57
+ return;
58
+ }
59
+ try {
60
+ process.kill(pid, 'SIGTERM');
61
+ console.log(`Sent SIGTERM to SiloLink (PID ${pid})`);
62
+ removePidFile();
63
+ }
64
+ catch (err) {
65
+ console.error(`Failed to stop SiloLink: ${err}`);
66
+ }
67
+ });
68
+ program
69
+ .command('status')
70
+ .description('Show SiloLink status')
71
+ .action(async () => {
72
+ const pid = readPidFile();
73
+ if (!pid || !isProcessRunning(pid)) {
74
+ console.log('SiloLink is not running');
75
+ return;
76
+ }
77
+ console.log(`SiloLink running (PID ${pid})`);
78
+ try {
79
+ const config = loadConfig();
80
+ const res = await fetch(`http://localhost:${config.mcp_port}/health`);
81
+ const status = await res.json();
82
+ console.log(` ActionCable: ${status.cable}`);
83
+ console.log(` Sessions: ${status.sessions}`);
84
+ }
85
+ catch {
86
+ console.log(' (could not reach health endpoint)');
87
+ }
88
+ });
89
+ program
90
+ .command('sessions')
91
+ .description('List active sessions')
92
+ .action(async () => {
93
+ try {
94
+ const config = loadConfig();
95
+ const res = await fetch(`http://localhost:${config.mcp_port}/health`);
96
+ const status = (await res.json());
97
+ console.log(`Active sessions: ${status.sessions}`);
98
+ }
99
+ catch {
100
+ console.log('SiloLink is not running or unreachable');
101
+ }
102
+ });
103
+ program
104
+ .command('launch')
105
+ .description('Launch a Claude Code session connected to SiloLink')
106
+ .option('-p, --prompt <prompt>', 'Custom prompt for Claude Code')
107
+ .action(async (opts) => {
108
+ if (!configExists()) {
109
+ console.error('Config not found. Run "silolink config" first.');
110
+ process.exit(1);
111
+ }
112
+ const config = loadConfig();
113
+ if (!config.claude_command || !config.claude_working_directory) {
114
+ console.error('Claude launcher not configured. Set claude_command and claude_working_directory in ~/.silolink/config.json');
115
+ process.exit(1);
116
+ }
117
+ // Import dynamically to avoid circular deps
118
+ const { ClaudeLauncher } = await import('../core/claude-launcher.js');
119
+ const { SessionManager } = await import('../core/session-manager.js');
120
+ const sessionManager = new SessionManager();
121
+ const launcher = new ClaudeLauncher(config, sessionManager);
122
+ if (opts.prompt) {
123
+ config.claude_session_prompt = opts.prompt;
124
+ }
125
+ const launched = await launcher.launch('cli');
126
+ if (!launched) {
127
+ console.error('Failed to launch Claude Code');
128
+ process.exit(1);
129
+ }
130
+ console.log('Claude Code launched. It will register with SiloLink automatically.');
131
+ // Keep process alive until Claude exits
132
+ process.on('SIGINT', () => {
133
+ launcher.kill();
134
+ process.exit(0);
135
+ });
136
+ });
137
+ program
138
+ .command('config')
139
+ .description('Configure SiloLink')
140
+ .action(async () => {
141
+ await interactiveConfig();
142
+ });
143
+ export { program };
144
+ //# sourceMappingURL=commands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.js","sourceRoot":"","sources":["../../src/cli/commands.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export declare function writePidFile(): void;
2
+ export declare function readPidFile(): number | null;
3
+ export declare function removePidFile(): void;
4
+ export declare function isProcessRunning(pid: number): boolean;
5
+ //# sourceMappingURL=daemon.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../../src/cli/daemon.ts"],"names":[],"mappings":"AAMA,wBAAgB,YAAY,IAAI,IAAI,CAGnC;AAED,wBAAgB,WAAW,IAAI,MAAM,GAAG,IAAI,CAO3C;AAED,wBAAgB,aAAa,IAAI,IAAI,CAMpC;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAOrD"}
@@ -0,0 +1,35 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { getConfigDir } from '../config/config-manager.js';
4
+ const PID_FILE = path.join(getConfigDir(), 'silolink.pid');
5
+ export function writePidFile() {
6
+ fs.mkdirSync(path.dirname(PID_FILE), { recursive: true });
7
+ fs.writeFileSync(PID_FILE, String(process.pid));
8
+ }
9
+ export function readPidFile() {
10
+ try {
11
+ const pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8').trim(), 10);
12
+ return isNaN(pid) ? null : pid;
13
+ }
14
+ catch {
15
+ return null;
16
+ }
17
+ }
18
+ export function removePidFile() {
19
+ try {
20
+ fs.unlinkSync(PID_FILE);
21
+ }
22
+ catch {
23
+ // Ignore if already removed
24
+ }
25
+ }
26
+ export function isProcessRunning(pid) {
27
+ try {
28
+ process.kill(pid, 0);
29
+ return true;
30
+ }
31
+ catch {
32
+ return false;
33
+ }
34
+ }
35
+ //# sourceMappingURL=daemon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon.js","sourceRoot":"","sources":["../../src/cli/daemon.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAE3D,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,cAAc,CAAC,CAAC;AAE3D,MAAM,UAAU,YAAY;IAC1B,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1D,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,QAAQ,CAAC,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;QACpE,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,IAAI,CAAC;QACH,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,4BAA4B;IAC9B,CAAC;AACH,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC1C,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { SiloLinkConfig } from '../types/index.js';
2
+ export declare function getConfigDir(): string;
3
+ export declare function loadConfig(): SiloLinkConfig;
4
+ export declare function saveConfig(config: SiloLinkConfig): void;
5
+ export declare function configExists(): boolean;
6
+ export declare function interactiveConfig(): Promise<void>;
7
+ //# sourceMappingURL=config-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-manager.d.ts","sourceRoot":"","sources":["../../src/config/config-manager.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAoBxD,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED,wBAAgB,UAAU,IAAI,cAAc,CAS3C;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAGvD;AAED,wBAAgB,YAAY,IAAI,OAAO,CAEtC;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CA0CvD"}