@realtimex/sdk 1.0.8 → 1.0.9

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.
package/README.md CHANGED
@@ -24,8 +24,9 @@ Before using this SDK, ensure your Supabase database is set up:
24
24
  ```typescript
25
25
  import { RealtimeXSDK } from '@realtimex/sdk';
26
26
 
27
- // No config needed - RTX_APP_ID is auto-detected from environment
28
- const sdk = new RealtimeXSDK();
27
+ const sdk = new RealtimeXSDK({
28
+ permissions: ['activities.read', 'activities.write', 'webhook.trigger']
29
+ });
29
30
 
30
31
  // Insert activity
31
32
  const activity = await sdk.activities.insert({
package/dist/index.d.mts CHANGED
@@ -8,6 +8,7 @@ interface SDKConfig {
8
8
  appName?: string;
9
9
  };
10
10
  defaultPort?: number;
11
+ permissions?: string[];
11
12
  }
12
13
  interface Activity {
13
14
  id: string;
@@ -86,7 +87,12 @@ interface Task {
86
87
  declare class ActivitiesModule {
87
88
  private baseUrl;
88
89
  private appId;
89
- constructor(realtimexUrl: string, appId: string);
90
+ private appName;
91
+ constructor(realtimexUrl: string, appId: string, appName?: string);
92
+ /**
93
+ * Request a single permission from Electron via internal API
94
+ */
95
+ private requestPermission;
90
96
  private request;
91
97
  /**
92
98
  * Insert a new activity
@@ -114,15 +120,16 @@ declare class ActivitiesModule {
114
120
  }): Promise<Activity[]>;
115
121
  }
116
122
 
117
- /**
118
- * Webhook Module - Call RealtimeX webhook
119
- */
120
-
121
123
  declare class WebhookModule {
122
124
  private realtimexUrl;
123
125
  private appName?;
124
126
  private appId?;
125
127
  constructor(realtimexUrl: string, appName?: string, appId?: string);
128
+ /**
129
+ * Request a single permission from Electron via internal API
130
+ */
131
+ private requestPermission;
132
+ private request;
126
133
  triggerAgent(payload: TriggerAgentPayload): Promise<TriggerAgentResponse>;
127
134
  ping(): Promise<{
128
135
  success: boolean;
@@ -135,11 +142,34 @@ declare class WebhookModule {
135
142
  * API Module - Call RealtimeX public APIs
136
143
  */
137
144
 
145
+ /**
146
+ * Error thrown when a permission is permanently denied
147
+ */
148
+ declare class PermissionDeniedError extends Error {
149
+ readonly permission: string;
150
+ constructor(permission: string, message?: string);
151
+ }
152
+ /**
153
+ * Error thrown when a permission needs to be granted
154
+ */
155
+ declare class PermissionRequiredError extends Error {
156
+ readonly permission: string;
157
+ constructor(permission: string, message?: string);
158
+ }
138
159
  declare class ApiModule {
139
160
  private realtimexUrl;
140
161
  private appId;
141
- constructor(realtimexUrl: string, appId: string);
162
+ private appName;
163
+ constructor(realtimexUrl: string, appId: string, appName?: string);
142
164
  private getHeaders;
165
+ /**
166
+ * Request a single permission from Electron via internal API
167
+ */
168
+ private requestPermission;
169
+ /**
170
+ * Make an API call with automatic permission handling
171
+ */
172
+ private apiCall;
143
173
  getAgents(): Promise<Agent[]>;
144
174
  getWorkspaces(): Promise<Workspace[]>;
145
175
  getThreads(workspaceSlug: string): Promise<Thread[]>;
@@ -234,12 +264,19 @@ declare class RealtimeXSDK {
234
264
  port: PortModule;
235
265
  readonly appId: string;
236
266
  readonly appName: string | undefined;
267
+ private readonly realtimexUrl;
268
+ private readonly permissions;
237
269
  private static DEFAULT_REALTIMEX_URL;
238
270
  constructor(config?: SDKConfig);
271
+ /**
272
+ * Register app with RealtimeX hub and request declared permissions upfront.
273
+ * This is called automatically if permissions are provided in constructor.
274
+ */
275
+ register(permissions?: string[]): Promise<void>;
239
276
  /**
240
277
  * Get environment variable (works in Node.js and browser)
241
278
  */
242
279
  private getEnvVar;
243
280
  }
244
281
 
245
- export { ActivitiesModule, type Activity, type Agent, ApiModule, PortModule, RealtimeXSDK, type SDKConfig, type Task, TaskModule, type TaskRun, type Thread, type TriggerAgentPayload, type TriggerAgentResponse, WebhookModule, type Workspace };
282
+ export { ActivitiesModule, type Activity, type Agent, ApiModule, PermissionDeniedError, PermissionRequiredError, PortModule, RealtimeXSDK, type SDKConfig, type Task, TaskModule, type TaskRun, type Thread, type TriggerAgentPayload, type TriggerAgentResponse, WebhookModule, type Workspace };
package/dist/index.d.ts CHANGED
@@ -8,6 +8,7 @@ interface SDKConfig {
8
8
  appName?: string;
9
9
  };
10
10
  defaultPort?: number;
11
+ permissions?: string[];
11
12
  }
12
13
  interface Activity {
13
14
  id: string;
@@ -86,7 +87,12 @@ interface Task {
86
87
  declare class ActivitiesModule {
87
88
  private baseUrl;
88
89
  private appId;
89
- constructor(realtimexUrl: string, appId: string);
90
+ private appName;
91
+ constructor(realtimexUrl: string, appId: string, appName?: string);
92
+ /**
93
+ * Request a single permission from Electron via internal API
94
+ */
95
+ private requestPermission;
90
96
  private request;
91
97
  /**
92
98
  * Insert a new activity
@@ -114,15 +120,16 @@ declare class ActivitiesModule {
114
120
  }): Promise<Activity[]>;
115
121
  }
116
122
 
117
- /**
118
- * Webhook Module - Call RealtimeX webhook
119
- */
120
-
121
123
  declare class WebhookModule {
122
124
  private realtimexUrl;
123
125
  private appName?;
124
126
  private appId?;
125
127
  constructor(realtimexUrl: string, appName?: string, appId?: string);
128
+ /**
129
+ * Request a single permission from Electron via internal API
130
+ */
131
+ private requestPermission;
132
+ private request;
126
133
  triggerAgent(payload: TriggerAgentPayload): Promise<TriggerAgentResponse>;
127
134
  ping(): Promise<{
128
135
  success: boolean;
@@ -135,11 +142,34 @@ declare class WebhookModule {
135
142
  * API Module - Call RealtimeX public APIs
136
143
  */
137
144
 
145
+ /**
146
+ * Error thrown when a permission is permanently denied
147
+ */
148
+ declare class PermissionDeniedError extends Error {
149
+ readonly permission: string;
150
+ constructor(permission: string, message?: string);
151
+ }
152
+ /**
153
+ * Error thrown when a permission needs to be granted
154
+ */
155
+ declare class PermissionRequiredError extends Error {
156
+ readonly permission: string;
157
+ constructor(permission: string, message?: string);
158
+ }
138
159
  declare class ApiModule {
139
160
  private realtimexUrl;
140
161
  private appId;
141
- constructor(realtimexUrl: string, appId: string);
162
+ private appName;
163
+ constructor(realtimexUrl: string, appId: string, appName?: string);
142
164
  private getHeaders;
165
+ /**
166
+ * Request a single permission from Electron via internal API
167
+ */
168
+ private requestPermission;
169
+ /**
170
+ * Make an API call with automatic permission handling
171
+ */
172
+ private apiCall;
143
173
  getAgents(): Promise<Agent[]>;
144
174
  getWorkspaces(): Promise<Workspace[]>;
145
175
  getThreads(workspaceSlug: string): Promise<Thread[]>;
@@ -234,12 +264,19 @@ declare class RealtimeXSDK {
234
264
  port: PortModule;
235
265
  readonly appId: string;
236
266
  readonly appName: string | undefined;
267
+ private readonly realtimexUrl;
268
+ private readonly permissions;
237
269
  private static DEFAULT_REALTIMEX_URL;
238
270
  constructor(config?: SDKConfig);
271
+ /**
272
+ * Register app with RealtimeX hub and request declared permissions upfront.
273
+ * This is called automatically if permissions are provided in constructor.
274
+ */
275
+ register(permissions?: string[]): Promise<void>;
239
276
  /**
240
277
  * Get environment variable (works in Node.js and browser)
241
278
  */
242
279
  private getEnvVar;
243
280
  }
244
281
 
245
- export { ActivitiesModule, type Activity, type Agent, ApiModule, PortModule, RealtimeXSDK, type SDKConfig, type Task, TaskModule, type TaskRun, type Thread, type TriggerAgentPayload, type TriggerAgentResponse, WebhookModule, type Workspace };
282
+ export { ActivitiesModule, type Activity, type Agent, ApiModule, PermissionDeniedError, PermissionRequiredError, PortModule, RealtimeXSDK, type SDKConfig, type Task, TaskModule, type TaskRun, type Thread, type TriggerAgentPayload, type TriggerAgentResponse, WebhookModule, type Workspace };
package/dist/index.js CHANGED
@@ -32,6 +32,8 @@ var index_exports = {};
32
32
  __export(index_exports, {
33
33
  ActivitiesModule: () => ActivitiesModule,
34
34
  ApiModule: () => ApiModule,
35
+ PermissionDeniedError: () => PermissionDeniedError,
36
+ PermissionRequiredError: () => PermissionRequiredError,
35
37
  PortModule: () => PortModule,
36
38
  RealtimeXSDK: () => RealtimeXSDK,
37
39
  TaskModule: () => TaskModule,
@@ -39,11 +41,131 @@ __export(index_exports, {
39
41
  });
40
42
  module.exports = __toCommonJS(index_exports);
41
43
 
44
+ // src/modules/api.ts
45
+ var PermissionDeniedError = class extends Error {
46
+ constructor(permission, message) {
47
+ super(message || `Permission '${permission}' was denied`);
48
+ this.name = "PermissionDeniedError";
49
+ this.permission = permission;
50
+ }
51
+ };
52
+ var PermissionRequiredError = class extends Error {
53
+ constructor(permission, message) {
54
+ super(message || `Permission '${permission}' is required`);
55
+ this.name = "PermissionRequiredError";
56
+ this.permission = permission;
57
+ }
58
+ };
59
+ var ApiModule = class {
60
+ constructor(realtimexUrl, appId, appName) {
61
+ this.realtimexUrl = realtimexUrl.replace(/\/$/, "");
62
+ this.appId = appId;
63
+ this.appName = appName || process.env.RTX_APP_NAME || "Local App";
64
+ }
65
+ getHeaders() {
66
+ return {
67
+ "Content-Type": "application/json",
68
+ "x-app-id": this.appId
69
+ };
70
+ }
71
+ /**
72
+ * Request a single permission from Electron via internal API
73
+ */
74
+ async requestPermission(permission) {
75
+ try {
76
+ const response = await fetch(`${this.realtimexUrl}/api/local-apps/request-permission`, {
77
+ method: "POST",
78
+ headers: { "Content-Type": "application/json" },
79
+ body: JSON.stringify({
80
+ app_id: this.appId,
81
+ app_name: this.appName,
82
+ permission
83
+ })
84
+ });
85
+ const data = await response.json();
86
+ return data.granted === true;
87
+ } catch (error) {
88
+ return false;
89
+ }
90
+ }
91
+ /**
92
+ * Make an API call with automatic permission handling
93
+ */
94
+ async apiCall(method, endpoint, options) {
95
+ const url = `${this.realtimexUrl}${endpoint}`;
96
+ const response = await fetch(url, {
97
+ method,
98
+ headers: this.getHeaders(),
99
+ ...options
100
+ });
101
+ const data = await response.json();
102
+ if (response.status === 403) {
103
+ const errorCode = data.error;
104
+ const permission = data.permission;
105
+ const message = data.message;
106
+ if (errorCode === "PERMISSION_REQUIRED" && permission) {
107
+ const granted = await this.requestPermission(permission);
108
+ if (granted) {
109
+ return this.apiCall(method, endpoint, options);
110
+ } else {
111
+ throw new PermissionDeniedError(permission, message);
112
+ }
113
+ }
114
+ if (errorCode === "PERMISSION_DENIED") {
115
+ throw new PermissionDeniedError(permission, message);
116
+ }
117
+ throw new Error(data.error || "Permission denied");
118
+ }
119
+ if (!response.ok) {
120
+ throw new Error(data.error || `API call failed: ${response.status}`);
121
+ }
122
+ return data;
123
+ }
124
+ async getAgents() {
125
+ const data = await this.apiCall("GET", "/agents");
126
+ return data.agents;
127
+ }
128
+ async getWorkspaces() {
129
+ const data = await this.apiCall("GET", "/workspaces");
130
+ return data.workspaces;
131
+ }
132
+ async getThreads(workspaceSlug) {
133
+ const data = await this.apiCall("GET", `/workspaces/${encodeURIComponent(workspaceSlug)}/threads`);
134
+ return data.threads;
135
+ }
136
+ async getTask(taskUuid) {
137
+ const data = await this.apiCall("GET", `/task/${encodeURIComponent(taskUuid)}`);
138
+ return { ...data.task, runs: data.runs };
139
+ }
140
+ };
141
+
42
142
  // src/modules/activities.ts
43
143
  var ActivitiesModule = class {
44
- constructor(realtimexUrl, appId) {
144
+ constructor(realtimexUrl, appId, appName) {
45
145
  this.baseUrl = realtimexUrl.replace(/\/$/, "");
46
146
  this.appId = appId;
147
+ this.appName = appName || process.env.RTX_APP_NAME || "Local App";
148
+ }
149
+ /**
150
+ * Request a single permission from Electron via internal API
151
+ */
152
+ async requestPermission(permission) {
153
+ try {
154
+ const response = await fetch(`${this.baseUrl}/api/local-apps/request-permission`, {
155
+ method: "POST",
156
+ headers: { "Content-Type": "application/json" },
157
+ body: JSON.stringify({
158
+ app_id: this.appId,
159
+ app_name: this.appName,
160
+ permission
161
+ })
162
+ });
163
+ const data = await response.json();
164
+ return data.granted === true;
165
+ } catch (error) {
166
+ console.error("[SDK] Permission request failed:", error);
167
+ return false;
168
+ }
47
169
  }
48
170
  async request(path, options = {}) {
49
171
  const url = `${this.baseUrl}${path}`;
@@ -61,6 +183,22 @@ var ActivitiesModule = class {
61
183
  }
62
184
  });
63
185
  const data = await response.json();
186
+ if (response.status === 403) {
187
+ const errorCode = data.error;
188
+ const permission = data.permission;
189
+ const message = data.message;
190
+ if (errorCode === "PERMISSION_REQUIRED" && permission) {
191
+ const granted = await this.requestPermission(permission);
192
+ if (granted) {
193
+ return this.request(path, options);
194
+ } else {
195
+ throw new PermissionDeniedError(permission, message);
196
+ }
197
+ }
198
+ if (errorCode === "PERMISSION_DENIED") {
199
+ throw new PermissionDeniedError(permission, message);
200
+ }
201
+ }
64
202
  if (!response.ok) {
65
203
  throw new Error(data.error || `Request failed: ${response.status}`);
66
204
  }
@@ -127,13 +265,64 @@ var WebhookModule = class {
127
265
  this.appName = appName;
128
266
  this.appId = appId;
129
267
  }
268
+ /**
269
+ * Request a single permission from Electron via internal API
270
+ */
271
+ async requestPermission(permission) {
272
+ try {
273
+ const response = await fetch(`${this.realtimexUrl}/api/local-apps/request-permission`, {
274
+ method: "POST",
275
+ headers: { "Content-Type": "application/json" },
276
+ body: JSON.stringify({
277
+ app_id: this.appId,
278
+ app_name: this.appName,
279
+ permission
280
+ })
281
+ });
282
+ const data = await response.json();
283
+ return data.granted === true;
284
+ } catch (error) {
285
+ console.error("[SDK] Permission request failed:", error);
286
+ return false;
287
+ }
288
+ }
289
+ async request(path, options = {}) {
290
+ const url = `${this.realtimexUrl}${path}`;
291
+ const response = await fetch(url, {
292
+ ...options,
293
+ headers: {
294
+ "Content-Type": "application/json",
295
+ ...options.headers
296
+ }
297
+ });
298
+ const data = await response.json();
299
+ if (response.status === 403) {
300
+ const errorCode = data.error;
301
+ const permission = data.permission;
302
+ const message = data.message;
303
+ if (errorCode === "PERMISSION_REQUIRED" && permission) {
304
+ const granted = await this.requestPermission(permission);
305
+ if (granted) {
306
+ return this.request(path, options);
307
+ } else {
308
+ throw new PermissionDeniedError(permission, message);
309
+ }
310
+ }
311
+ if (errorCode === "PERMISSION_DENIED") {
312
+ throw new PermissionDeniedError(permission, message);
313
+ }
314
+ }
315
+ if (!response.ok) {
316
+ throw new Error(data.error || `Request failed: ${response.status}`);
317
+ }
318
+ return data;
319
+ }
130
320
  async triggerAgent(payload) {
131
321
  if (payload.auto_run && (!payload.agent_name || !payload.workspace_slug)) {
132
322
  throw new Error("auto_run requires agent_name and workspace_slug");
133
323
  }
134
- const response = await fetch(`${this.realtimexUrl}/webhooks/realtimex`, {
324
+ return this.request("/webhooks/realtimex", {
135
325
  method: "POST",
136
- headers: { "Content-Type": "application/json" },
137
326
  body: JSON.stringify({
138
327
  app_name: this.appName,
139
328
  app_id: this.appId,
@@ -148,69 +337,16 @@ var WebhookModule = class {
148
337
  }
149
338
  })
150
339
  });
151
- const data = await response.json();
152
- if (!response.ok) throw new Error(data.error || "Failed to trigger agent");
153
- return data;
154
340
  }
155
341
  async ping() {
156
- const response = await fetch(`${this.realtimexUrl}/webhooks/realtimex`, {
342
+ return this.request("/webhooks/realtimex", {
157
343
  method: "POST",
158
- headers: { "Content-Type": "application/json" },
159
344
  body: JSON.stringify({
160
345
  app_name: this.appName,
161
346
  app_id: this.appId,
162
347
  event: "ping"
163
348
  })
164
349
  });
165
- const data = await response.json();
166
- if (!response.ok) throw new Error(data.error || "Ping failed");
167
- return data;
168
- }
169
- };
170
-
171
- // src/modules/api.ts
172
- var ApiModule = class {
173
- constructor(realtimexUrl, appId) {
174
- this.realtimexUrl = realtimexUrl.replace(/\/$/, "");
175
- this.appId = appId;
176
- }
177
- getHeaders() {
178
- return {
179
- "Content-Type": "application/json",
180
- "x-app-id": this.appId
181
- };
182
- }
183
- async getAgents() {
184
- const response = await fetch(`${this.realtimexUrl}/agents`, {
185
- headers: this.getHeaders()
186
- });
187
- const data = await response.json();
188
- if (!response.ok) throw new Error(data.error || "Failed to get agents");
189
- return data.agents;
190
- }
191
- async getWorkspaces() {
192
- const response = await fetch(`${this.realtimexUrl}/workspaces`, {
193
- headers: this.getHeaders()
194
- });
195
- const data = await response.json();
196
- if (!response.ok) throw new Error(data.error || "Failed to get workspaces");
197
- return data.workspaces;
198
- }
199
- async getThreads(workspaceSlug) {
200
- const response = await fetch(`${this.realtimexUrl}/workspaces/${encodeURIComponent(workspaceSlug)}/threads`, {
201
- headers: this.getHeaders()
202
- });
203
- const data = await response.json();
204
- if (!response.ok) throw new Error(data.error || "Failed to get threads");
205
- return data.threads;
206
- }
207
- async getTask(taskUuid) {
208
- const response = await fetch(`${this.realtimexUrl}/task/${encodeURIComponent(taskUuid)}`, {
209
- headers: this.getHeaders()
210
- });
211
- const data = await response.json();
212
- if (!response.ok) throw new Error(data.error || "Failed to get task");
213
- return { ...data.task, runs: data.runs };
214
350
  }
215
351
  };
216
352
 
@@ -343,12 +479,44 @@ var _RealtimeXSDK = class _RealtimeXSDK {
343
479
  const envAppName = this.getEnvVar("RTX_APP_NAME");
344
480
  this.appId = config.realtimex?.appId || envAppId || "";
345
481
  this.appName = config.realtimex?.appName || envAppName;
346
- const realtimexUrl = config.realtimex?.url || _RealtimeXSDK.DEFAULT_REALTIMEX_URL;
347
- this.activities = new ActivitiesModule(realtimexUrl, this.appId);
348
- this.webhook = new WebhookModule(realtimexUrl, this.appName, this.appId);
349
- this.api = new ApiModule(realtimexUrl, this.appId);
350
- this.task = new TaskModule(realtimexUrl, this.appName, this.appId);
482
+ this.permissions = config.permissions || [];
483
+ this.realtimexUrl = config.realtimex?.url || _RealtimeXSDK.DEFAULT_REALTIMEX_URL;
484
+ this.activities = new ActivitiesModule(this.realtimexUrl, this.appId, this.appName);
485
+ this.webhook = new WebhookModule(this.realtimexUrl, this.appName, this.appId);
486
+ this.api = new ApiModule(this.realtimexUrl, this.appId, this.appName);
487
+ this.task = new TaskModule(this.realtimexUrl, this.appName, this.appId);
351
488
  this.port = new PortModule(config.defaultPort);
489
+ if (this.permissions.length > 0) {
490
+ this.register().catch((err) => {
491
+ console.error("[RealtimeX SDK] Auto-registration failed:", err.message);
492
+ });
493
+ }
494
+ }
495
+ /**
496
+ * Register app with RealtimeX hub and request declared permissions upfront.
497
+ * This is called automatically if permissions are provided in constructor.
498
+ */
499
+ async register(permissions) {
500
+ const perms = permissions || this.permissions;
501
+ if (perms.length === 0) return;
502
+ try {
503
+ const response = await fetch(`${this.realtimexUrl.replace(/\/$/, "")}/sdk/register`, {
504
+ method: "POST",
505
+ headers: { "Content-Type": "application/json" },
506
+ body: JSON.stringify({
507
+ app_id: this.appId,
508
+ app_name: this.appName,
509
+ permissions: perms
510
+ })
511
+ });
512
+ const data = await response.json();
513
+ if (!response.ok) {
514
+ throw new Error(data.error || "Registration failed");
515
+ }
516
+ console.log(`[RealtimeX SDK] App registered successfully (${data.message})`);
517
+ } catch (error) {
518
+ throw new Error(`Failed to register app: ${error.message}`);
519
+ }
352
520
  }
353
521
  /**
354
522
  * Get environment variable (works in Node.js and browser)
@@ -369,6 +537,8 @@ var RealtimeXSDK = _RealtimeXSDK;
369
537
  0 && (module.exports = {
370
538
  ActivitiesModule,
371
539
  ApiModule,
540
+ PermissionDeniedError,
541
+ PermissionRequiredError,
372
542
  PortModule,
373
543
  RealtimeXSDK,
374
544
  TaskModule,
package/dist/index.mjs CHANGED
@@ -1,8 +1,128 @@
1
+ // src/modules/api.ts
2
+ var PermissionDeniedError = class extends Error {
3
+ constructor(permission, message) {
4
+ super(message || `Permission '${permission}' was denied`);
5
+ this.name = "PermissionDeniedError";
6
+ this.permission = permission;
7
+ }
8
+ };
9
+ var PermissionRequiredError = class extends Error {
10
+ constructor(permission, message) {
11
+ super(message || `Permission '${permission}' is required`);
12
+ this.name = "PermissionRequiredError";
13
+ this.permission = permission;
14
+ }
15
+ };
16
+ var ApiModule = class {
17
+ constructor(realtimexUrl, appId, appName) {
18
+ this.realtimexUrl = realtimexUrl.replace(/\/$/, "");
19
+ this.appId = appId;
20
+ this.appName = appName || process.env.RTX_APP_NAME || "Local App";
21
+ }
22
+ getHeaders() {
23
+ return {
24
+ "Content-Type": "application/json",
25
+ "x-app-id": this.appId
26
+ };
27
+ }
28
+ /**
29
+ * Request a single permission from Electron via internal API
30
+ */
31
+ async requestPermission(permission) {
32
+ try {
33
+ const response = await fetch(`${this.realtimexUrl}/api/local-apps/request-permission`, {
34
+ method: "POST",
35
+ headers: { "Content-Type": "application/json" },
36
+ body: JSON.stringify({
37
+ app_id: this.appId,
38
+ app_name: this.appName,
39
+ permission
40
+ })
41
+ });
42
+ const data = await response.json();
43
+ return data.granted === true;
44
+ } catch (error) {
45
+ return false;
46
+ }
47
+ }
48
+ /**
49
+ * Make an API call with automatic permission handling
50
+ */
51
+ async apiCall(method, endpoint, options) {
52
+ const url = `${this.realtimexUrl}${endpoint}`;
53
+ const response = await fetch(url, {
54
+ method,
55
+ headers: this.getHeaders(),
56
+ ...options
57
+ });
58
+ const data = await response.json();
59
+ if (response.status === 403) {
60
+ const errorCode = data.error;
61
+ const permission = data.permission;
62
+ const message = data.message;
63
+ if (errorCode === "PERMISSION_REQUIRED" && permission) {
64
+ const granted = await this.requestPermission(permission);
65
+ if (granted) {
66
+ return this.apiCall(method, endpoint, options);
67
+ } else {
68
+ throw new PermissionDeniedError(permission, message);
69
+ }
70
+ }
71
+ if (errorCode === "PERMISSION_DENIED") {
72
+ throw new PermissionDeniedError(permission, message);
73
+ }
74
+ throw new Error(data.error || "Permission denied");
75
+ }
76
+ if (!response.ok) {
77
+ throw new Error(data.error || `API call failed: ${response.status}`);
78
+ }
79
+ return data;
80
+ }
81
+ async getAgents() {
82
+ const data = await this.apiCall("GET", "/agents");
83
+ return data.agents;
84
+ }
85
+ async getWorkspaces() {
86
+ const data = await this.apiCall("GET", "/workspaces");
87
+ return data.workspaces;
88
+ }
89
+ async getThreads(workspaceSlug) {
90
+ const data = await this.apiCall("GET", `/workspaces/${encodeURIComponent(workspaceSlug)}/threads`);
91
+ return data.threads;
92
+ }
93
+ async getTask(taskUuid) {
94
+ const data = await this.apiCall("GET", `/task/${encodeURIComponent(taskUuid)}`);
95
+ return { ...data.task, runs: data.runs };
96
+ }
97
+ };
98
+
1
99
  // src/modules/activities.ts
2
100
  var ActivitiesModule = class {
3
- constructor(realtimexUrl, appId) {
101
+ constructor(realtimexUrl, appId, appName) {
4
102
  this.baseUrl = realtimexUrl.replace(/\/$/, "");
5
103
  this.appId = appId;
104
+ this.appName = appName || process.env.RTX_APP_NAME || "Local App";
105
+ }
106
+ /**
107
+ * Request a single permission from Electron via internal API
108
+ */
109
+ async requestPermission(permission) {
110
+ try {
111
+ const response = await fetch(`${this.baseUrl}/api/local-apps/request-permission`, {
112
+ method: "POST",
113
+ headers: { "Content-Type": "application/json" },
114
+ body: JSON.stringify({
115
+ app_id: this.appId,
116
+ app_name: this.appName,
117
+ permission
118
+ })
119
+ });
120
+ const data = await response.json();
121
+ return data.granted === true;
122
+ } catch (error) {
123
+ console.error("[SDK] Permission request failed:", error);
124
+ return false;
125
+ }
6
126
  }
7
127
  async request(path, options = {}) {
8
128
  const url = `${this.baseUrl}${path}`;
@@ -20,6 +140,22 @@ var ActivitiesModule = class {
20
140
  }
21
141
  });
22
142
  const data = await response.json();
143
+ if (response.status === 403) {
144
+ const errorCode = data.error;
145
+ const permission = data.permission;
146
+ const message = data.message;
147
+ if (errorCode === "PERMISSION_REQUIRED" && permission) {
148
+ const granted = await this.requestPermission(permission);
149
+ if (granted) {
150
+ return this.request(path, options);
151
+ } else {
152
+ throw new PermissionDeniedError(permission, message);
153
+ }
154
+ }
155
+ if (errorCode === "PERMISSION_DENIED") {
156
+ throw new PermissionDeniedError(permission, message);
157
+ }
158
+ }
23
159
  if (!response.ok) {
24
160
  throw new Error(data.error || `Request failed: ${response.status}`);
25
161
  }
@@ -86,13 +222,64 @@ var WebhookModule = class {
86
222
  this.appName = appName;
87
223
  this.appId = appId;
88
224
  }
225
+ /**
226
+ * Request a single permission from Electron via internal API
227
+ */
228
+ async requestPermission(permission) {
229
+ try {
230
+ const response = await fetch(`${this.realtimexUrl}/api/local-apps/request-permission`, {
231
+ method: "POST",
232
+ headers: { "Content-Type": "application/json" },
233
+ body: JSON.stringify({
234
+ app_id: this.appId,
235
+ app_name: this.appName,
236
+ permission
237
+ })
238
+ });
239
+ const data = await response.json();
240
+ return data.granted === true;
241
+ } catch (error) {
242
+ console.error("[SDK] Permission request failed:", error);
243
+ return false;
244
+ }
245
+ }
246
+ async request(path, options = {}) {
247
+ const url = `${this.realtimexUrl}${path}`;
248
+ const response = await fetch(url, {
249
+ ...options,
250
+ headers: {
251
+ "Content-Type": "application/json",
252
+ ...options.headers
253
+ }
254
+ });
255
+ const data = await response.json();
256
+ if (response.status === 403) {
257
+ const errorCode = data.error;
258
+ const permission = data.permission;
259
+ const message = data.message;
260
+ if (errorCode === "PERMISSION_REQUIRED" && permission) {
261
+ const granted = await this.requestPermission(permission);
262
+ if (granted) {
263
+ return this.request(path, options);
264
+ } else {
265
+ throw new PermissionDeniedError(permission, message);
266
+ }
267
+ }
268
+ if (errorCode === "PERMISSION_DENIED") {
269
+ throw new PermissionDeniedError(permission, message);
270
+ }
271
+ }
272
+ if (!response.ok) {
273
+ throw new Error(data.error || `Request failed: ${response.status}`);
274
+ }
275
+ return data;
276
+ }
89
277
  async triggerAgent(payload) {
90
278
  if (payload.auto_run && (!payload.agent_name || !payload.workspace_slug)) {
91
279
  throw new Error("auto_run requires agent_name and workspace_slug");
92
280
  }
93
- const response = await fetch(`${this.realtimexUrl}/webhooks/realtimex`, {
281
+ return this.request("/webhooks/realtimex", {
94
282
  method: "POST",
95
- headers: { "Content-Type": "application/json" },
96
283
  body: JSON.stringify({
97
284
  app_name: this.appName,
98
285
  app_id: this.appId,
@@ -107,69 +294,16 @@ var WebhookModule = class {
107
294
  }
108
295
  })
109
296
  });
110
- const data = await response.json();
111
- if (!response.ok) throw new Error(data.error || "Failed to trigger agent");
112
- return data;
113
297
  }
114
298
  async ping() {
115
- const response = await fetch(`${this.realtimexUrl}/webhooks/realtimex`, {
299
+ return this.request("/webhooks/realtimex", {
116
300
  method: "POST",
117
- headers: { "Content-Type": "application/json" },
118
301
  body: JSON.stringify({
119
302
  app_name: this.appName,
120
303
  app_id: this.appId,
121
304
  event: "ping"
122
305
  })
123
306
  });
124
- const data = await response.json();
125
- if (!response.ok) throw new Error(data.error || "Ping failed");
126
- return data;
127
- }
128
- };
129
-
130
- // src/modules/api.ts
131
- var ApiModule = class {
132
- constructor(realtimexUrl, appId) {
133
- this.realtimexUrl = realtimexUrl.replace(/\/$/, "");
134
- this.appId = appId;
135
- }
136
- getHeaders() {
137
- return {
138
- "Content-Type": "application/json",
139
- "x-app-id": this.appId
140
- };
141
- }
142
- async getAgents() {
143
- const response = await fetch(`${this.realtimexUrl}/agents`, {
144
- headers: this.getHeaders()
145
- });
146
- const data = await response.json();
147
- if (!response.ok) throw new Error(data.error || "Failed to get agents");
148
- return data.agents;
149
- }
150
- async getWorkspaces() {
151
- const response = await fetch(`${this.realtimexUrl}/workspaces`, {
152
- headers: this.getHeaders()
153
- });
154
- const data = await response.json();
155
- if (!response.ok) throw new Error(data.error || "Failed to get workspaces");
156
- return data.workspaces;
157
- }
158
- async getThreads(workspaceSlug) {
159
- const response = await fetch(`${this.realtimexUrl}/workspaces/${encodeURIComponent(workspaceSlug)}/threads`, {
160
- headers: this.getHeaders()
161
- });
162
- const data = await response.json();
163
- if (!response.ok) throw new Error(data.error || "Failed to get threads");
164
- return data.threads;
165
- }
166
- async getTask(taskUuid) {
167
- const response = await fetch(`${this.realtimexUrl}/task/${encodeURIComponent(taskUuid)}`, {
168
- headers: this.getHeaders()
169
- });
170
- const data = await response.json();
171
- if (!response.ok) throw new Error(data.error || "Failed to get task");
172
- return { ...data.task, runs: data.runs };
173
307
  }
174
308
  };
175
309
 
@@ -302,12 +436,44 @@ var _RealtimeXSDK = class _RealtimeXSDK {
302
436
  const envAppName = this.getEnvVar("RTX_APP_NAME");
303
437
  this.appId = config.realtimex?.appId || envAppId || "";
304
438
  this.appName = config.realtimex?.appName || envAppName;
305
- const realtimexUrl = config.realtimex?.url || _RealtimeXSDK.DEFAULT_REALTIMEX_URL;
306
- this.activities = new ActivitiesModule(realtimexUrl, this.appId);
307
- this.webhook = new WebhookModule(realtimexUrl, this.appName, this.appId);
308
- this.api = new ApiModule(realtimexUrl, this.appId);
309
- this.task = new TaskModule(realtimexUrl, this.appName, this.appId);
439
+ this.permissions = config.permissions || [];
440
+ this.realtimexUrl = config.realtimex?.url || _RealtimeXSDK.DEFAULT_REALTIMEX_URL;
441
+ this.activities = new ActivitiesModule(this.realtimexUrl, this.appId, this.appName);
442
+ this.webhook = new WebhookModule(this.realtimexUrl, this.appName, this.appId);
443
+ this.api = new ApiModule(this.realtimexUrl, this.appId, this.appName);
444
+ this.task = new TaskModule(this.realtimexUrl, this.appName, this.appId);
310
445
  this.port = new PortModule(config.defaultPort);
446
+ if (this.permissions.length > 0) {
447
+ this.register().catch((err) => {
448
+ console.error("[RealtimeX SDK] Auto-registration failed:", err.message);
449
+ });
450
+ }
451
+ }
452
+ /**
453
+ * Register app with RealtimeX hub and request declared permissions upfront.
454
+ * This is called automatically if permissions are provided in constructor.
455
+ */
456
+ async register(permissions) {
457
+ const perms = permissions || this.permissions;
458
+ if (perms.length === 0) return;
459
+ try {
460
+ const response = await fetch(`${this.realtimexUrl.replace(/\/$/, "")}/sdk/register`, {
461
+ method: "POST",
462
+ headers: { "Content-Type": "application/json" },
463
+ body: JSON.stringify({
464
+ app_id: this.appId,
465
+ app_name: this.appName,
466
+ permissions: perms
467
+ })
468
+ });
469
+ const data = await response.json();
470
+ if (!response.ok) {
471
+ throw new Error(data.error || "Registration failed");
472
+ }
473
+ console.log(`[RealtimeX SDK] App registered successfully (${data.message})`);
474
+ } catch (error) {
475
+ throw new Error(`Failed to register app: ${error.message}`);
476
+ }
311
477
  }
312
478
  /**
313
479
  * Get environment variable (works in Node.js and browser)
@@ -327,6 +493,8 @@ var RealtimeXSDK = _RealtimeXSDK;
327
493
  export {
328
494
  ActivitiesModule,
329
495
  ApiModule,
496
+ PermissionDeniedError,
497
+ PermissionRequiredError,
330
498
  PortModule,
331
499
  RealtimeXSDK,
332
500
  TaskModule,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@realtimex/sdk",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "SDK for building Local Apps that integrate with RealtimeX",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",