@shipers-dev/multi 0.1.1 → 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.
package/dist/index.js CHANGED
@@ -71,21 +71,25 @@ async function getVersion(path) {
71
71
  }
72
72
 
73
73
  // src/client.ts
74
+ var authToken = null;
75
+ function setAuthToken(token) {
76
+ authToken = token;
77
+ }
74
78
  async function request(url, options) {
75
79
  try {
76
- const response = await fetch(url, {
77
- ...options,
78
- headers: {
79
- "Content-Type": "application/json",
80
- ...options?.headers
81
- }
82
- });
83
- const raw = await response.json();
80
+ const headers = {
81
+ "Content-Type": "application/json",
82
+ ...options?.headers
83
+ };
84
+ if (authToken && !headers["Authorization"])
85
+ headers["Authorization"] = `Bearer ${authToken}`;
86
+ const response = await fetch(url, { ...options, headers });
87
+ const raw = await response.json().catch(() => ({}));
84
88
  if (!response.ok) {
85
- return { success: false, error: raw?.error || `HTTP ${response.status}` };
89
+ return { success: false, error: raw?.error || `HTTP ${response.status}`, status: response.status };
86
90
  }
87
91
  const data = raw && typeof raw === "object" && "results" in raw ? raw.results : raw;
88
- return { success: true, data };
92
+ return { success: true, data, status: response.status };
89
93
  } catch (error) {
90
94
  return { success: false, error: String(error) };
91
95
  }
@@ -5413,9 +5417,11 @@ async function main() {
5413
5417
  }
5414
5418
  const apiUrl = args.values.api || process.env.MULTI_API_URL || "https://multi-api.adnb3r.workers.dev";
5415
5419
  const config = loadConfig();
5420
+ if (config.token)
5421
+ setAuthToken(config.token);
5416
5422
  switch (command) {
5417
5423
  case "setup":
5418
- await cmdSetup(args.values.name, args.values.workspace, apiUrl);
5424
+ await cmdSetup(args.values.name, apiUrl);
5419
5425
  break;
5420
5426
  case "connect":
5421
5427
  case "start":
@@ -5466,12 +5472,8 @@ Examples:
5466
5472
  multi-agent connect
5467
5473
  `);
5468
5474
  }
5469
- async function cmdSetup(name, workspaceId, apiUrl) {
5475
+ async function cmdSetup(name, apiUrl) {
5470
5476
  ensureDirs();
5471
- if (!workspaceId) {
5472
- console.log("\u274C Workspace ID required. Run: multi-agent setup --workspace <id>");
5473
- process.exit(1);
5474
- }
5475
5477
  console.log("\uD83D\uDD0D Detecting local agent runtimes...");
5476
5478
  const detected = await detectAgents();
5477
5479
  for (const a of detected)
@@ -5480,23 +5482,62 @@ async function cmdSetup(name, workspaceId, apiUrl) {
5480
5482
  console.log(" (none detected \u2014 stub runtime will be used)");
5481
5483
  const deviceName = name || process.env.HOSTNAME || "Unknown Device";
5482
5484
  console.log(`
5483
- \uD83D\uDCDD Registering device "${deviceName}"...`);
5484
- const result = await apiClient.post(`${apiUrl}/api/devices/register`, {
5485
- workspace_id: workspaceId,
5485
+ \uD83D\uDCE1 Requesting pairing code for "${deviceName}"...`);
5486
+ const start = await apiClient.post(`${apiUrl}/api/pair/start`, {
5486
5487
  name: deviceName,
5487
5488
  platform: process.platform,
5488
5489
  arch: process.arch,
5489
5490
  os_version: process.version,
5490
5491
  detected_runtimes: detected.map((a) => a.type)
5491
5492
  });
5492
- if (!result.success || !result.data) {
5493
+ if (!start.success || !start.data) {
5493
5494
  console.log(`
5494
- \u274C Registration failed:`, result.error);
5495
+ \u274C Failed to start pairing:`, start.error);
5495
5496
  process.exit(1);
5496
5497
  }
5497
- console.log(`\u2705 Device registered. ID: ${result.data.id}`);
5498
- saveConfig({ deviceId: result.data.id, workspaceId, apiUrl });
5499
- await syncSkills(apiUrl, workspaceId);
5498
+ const { code } = start.data;
5499
+ const pairUrl = `${apiUrl}/pair/${code}`;
5500
+ console.log(`
5501
+ \uD83D\uDC49 Open this URL in your browser to approve:
5502
+
5503
+ ${pairUrl}
5504
+ `);
5505
+ console.log(` Or enter code manually: ${code}`);
5506
+ console.log(`
5507
+ \u23F3 Waiting for approval (10 min timeout)...`);
5508
+ const deadline = Date.now() + 10 * 60 * 1000;
5509
+ let approved = null;
5510
+ while (Date.now() < deadline) {
5511
+ await sleep(3000);
5512
+ const poll = await apiClient.get(`${apiUrl}/api/pair/poll/${code}`);
5513
+ if (!poll.success) {
5514
+ if (poll.status === 410) {
5515
+ console.log(`
5516
+ \u274C Pairing expired. Run setup again.`);
5517
+ process.exit(1);
5518
+ }
5519
+ continue;
5520
+ }
5521
+ if (poll.data?.status === "approved") {
5522
+ approved = { device_id: poll.data.device_id, token: poll.data.token };
5523
+ break;
5524
+ }
5525
+ }
5526
+ if (!approved) {
5527
+ console.log(`
5528
+ \u274C Timed out.`);
5529
+ process.exit(1);
5530
+ }
5531
+ console.log(`
5532
+ \u2705 Device paired. ID: ${approved.device_id}`);
5533
+ saveConfig({ deviceId: approved.device_id, token: approved.token, apiUrl });
5534
+ setAuthToken(approved.token);
5535
+ const dev = await apiClient.get(`${apiUrl}/api/devices/${approved.device_id}`);
5536
+ const workspaceId = dev.data?.workspace_id;
5537
+ if (workspaceId) {
5538
+ saveConfig({ deviceId: approved.device_id, token: approved.token, workspaceId, apiUrl });
5539
+ await syncSkills(apiUrl, workspaceId);
5540
+ }
5500
5541
  console.log(`
5501
5542
  Next: link to an agent with: multi-agent link --agent <agentId>`);
5502
5543
  console.log("Then: multi-agent connect");
@@ -5549,7 +5590,7 @@ async function cmdConnect(apiUrl, config) {
5549
5590
  log(`\uD83D\uDE80 Connecting device ${config.deviceId} (pid ${process.pid})`);
5550
5591
  log(` runtimes: ${detected.map((d) => d.type).join(", ") || "stub"}`);
5551
5592
  await apiClient.post(`${apiUrl}/api/devices/${config.deviceId}/heartbeat`, { status: "online" });
5552
- const wsUrl = apiUrl.replace(/^http/, "ws") + `/ws/devices/${config.deviceId}`;
5593
+ const wsUrl = apiUrl.replace(/^http/, "ws") + `/ws/devices/${config.deviceId}?token=${encodeURIComponent(config.token || "")}`;
5553
5594
  let ws = null;
5554
5595
  let running = true;
5555
5596
  const shutdown = async (reason) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipers-dev/multi",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "multi-agent": "./dist/index.js"
package/src/client.ts CHANGED
@@ -2,29 +2,35 @@ interface ApiResponse<T = unknown> {
2
2
  success: boolean;
3
3
  data?: T;
4
4
  error?: string;
5
+ status?: number;
6
+ }
7
+
8
+ let authToken: string | null = null;
9
+
10
+ export function setAuthToken(token: string | null) {
11
+ authToken = token;
5
12
  }
6
13
 
7
14
  async function request<T>(url: string, options?: RequestInit): Promise<ApiResponse<T>> {
8
15
  try {
9
- const response = await fetch(url, {
10
- ...options,
11
- headers: {
12
- 'Content-Type': 'application/json',
13
- ...options?.headers,
14
- },
15
- });
16
+ const headers: Record<string, string> = {
17
+ 'Content-Type': 'application/json',
18
+ ...(options?.headers as Record<string, string> | undefined),
19
+ };
20
+ if (authToken && !headers['Authorization']) headers['Authorization'] = `Bearer ${authToken}`;
16
21
 
17
- const raw = await response.json();
22
+ const response = await fetch(url, { ...options, headers });
23
+ const raw = await response.json().catch(() => ({}));
18
24
 
19
25
  if (!response.ok) {
20
- return { success: false, error: (raw as any)?.error || `HTTP ${response.status}` };
26
+ return { success: false, error: (raw as any)?.error || `HTTP ${response.status}`, status: response.status };
21
27
  }
22
28
 
23
29
  const data = (raw && typeof raw === 'object' && 'results' in (raw as any))
24
30
  ? (raw as any).results
25
31
  : raw;
26
32
 
27
- return { success: true, data };
33
+ return { success: true, data, status: response.status };
28
34
  } catch (error) {
29
35
  return { success: false, error: String(error) };
30
36
  }
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
3
  import { detectAgents } from './detect';
4
- import { apiClient } from './client';
4
+ import { apiClient, setAuthToken } from './client';
5
5
  import { runAcp } from './acp-runner';
6
6
  import { parseArgs } from 'util';
7
7
  import { mkdirSync, existsSync, writeFileSync, readFileSync, appendFileSync, unlinkSync } from 'fs';
@@ -61,10 +61,11 @@ async function main() {
61
61
 
62
62
  const apiUrl = args.values.api || process.env.MULTI_API_URL || 'https://multi-api.adnb3r.workers.dev';
63
63
  const config = loadConfig();
64
+ if (config.token) setAuthToken(config.token);
64
65
 
65
66
  switch (command) {
66
67
  case 'setup':
67
- await cmdSetup(args.values.name, args.values.workspace, apiUrl);
68
+ await cmdSetup(args.values.name, apiUrl);
68
69
  break;
69
70
  case 'connect':
70
71
  case 'start':
@@ -117,12 +118,8 @@ Examples:
117
118
  `);
118
119
  }
119
120
 
120
- async function cmdSetup(name?: string, workspaceId?: string, apiUrl?: string) {
121
+ async function cmdSetup(name?: string, apiUrl?: string) {
121
122
  ensureDirs();
122
- if (!workspaceId) {
123
- console.log('āŒ Workspace ID required. Run: multi-agent setup --workspace <id>');
124
- process.exit(1);
125
- }
126
123
 
127
124
  console.log('šŸ” Detecting local agent runtimes...');
128
125
  const detected = await detectAgents();
@@ -130,25 +127,53 @@ async function cmdSetup(name?: string, workspaceId?: string, apiUrl?: string) {
130
127
  if (!detected.length) console.log(' (none detected — stub runtime will be used)');
131
128
 
132
129
  const deviceName = name || process.env.HOSTNAME || 'Unknown Device';
133
- console.log(`\nšŸ“ Registering device "${deviceName}"...`);
130
+ console.log(`\nšŸ“” Requesting pairing code for "${deviceName}"...`);
134
131
 
135
- const result = await apiClient.post<{ id: string }>(`${apiUrl}/api/devices/register`, {
136
- workspace_id: workspaceId,
132
+ const start = await apiClient.post<{ code: string; expires_at: number }>(`${apiUrl}/api/pair/start`, {
137
133
  name: deviceName,
138
134
  platform: process.platform,
139
135
  arch: process.arch,
140
136
  os_version: process.version,
141
137
  detected_runtimes: detected.map(a => a.type),
142
138
  });
143
-
144
- if (!result.success || !result.data) {
145
- console.log('\nāŒ Registration failed:', result.error);
139
+ if (!start.success || !start.data) {
140
+ console.log('\nāŒ Failed to start pairing:', start.error);
146
141
  process.exit(1);
147
142
  }
148
143
 
149
- console.log(`āœ… Device registered. ID: ${result.data.id}`);
150
- saveConfig({ deviceId: result.data.id, workspaceId, apiUrl });
151
- await syncSkills(apiUrl!, workspaceId);
144
+ const { code } = start.data;
145
+ const pairUrl = `${apiUrl}/pair/${code}`;
146
+ console.log(`\nšŸ‘‰ Open this URL in your browser to approve:\n\n ${pairUrl}\n`);
147
+ console.log(` Or enter code manually: ${code}`);
148
+ console.log('\nā³ Waiting for approval (10 min timeout)...');
149
+
150
+ const deadline = Date.now() + 10 * 60 * 1000;
151
+ let approved: { device_id: string; token: string } | null = null;
152
+ while (Date.now() < deadline) {
153
+ await sleep(3000);
154
+ const poll = await apiClient.get<any>(`${apiUrl}/api/pair/poll/${code}`);
155
+ if (!poll.success) {
156
+ if (poll.status === 410) { console.log('\nāŒ Pairing expired. Run setup again.'); process.exit(1); }
157
+ continue;
158
+ }
159
+ if (poll.data?.status === 'approved') {
160
+ approved = { device_id: poll.data.device_id, token: poll.data.token };
161
+ break;
162
+ }
163
+ }
164
+ if (!approved) { console.log('\nāŒ Timed out.'); process.exit(1); }
165
+
166
+ console.log(`\nāœ… Device paired. ID: ${approved.device_id}`);
167
+ saveConfig({ deviceId: approved.device_id, token: approved.token, apiUrl });
168
+ setAuthToken(approved.token);
169
+
170
+ // Fetch workspace_id from device (now authed)
171
+ const dev = await apiClient.get<any>(`${apiUrl}/api/devices/${approved.device_id}`);
172
+ const workspaceId = dev.data?.workspace_id;
173
+ if (workspaceId) {
174
+ saveConfig({ deviceId: approved.device_id, token: approved.token, workspaceId, apiUrl });
175
+ await syncSkills(apiUrl!, workspaceId);
176
+ }
152
177
  console.log('\nNext: link to an agent with: multi-agent link --agent <agentId>');
153
178
  console.log('Then: multi-agent connect');
154
179
  }
@@ -204,7 +229,7 @@ async function cmdConnect(apiUrl: string, config: Config) {
204
229
 
205
230
  await apiClient.post(`${apiUrl}/api/devices/${config.deviceId}/heartbeat`, { status: 'online' });
206
231
 
207
- const wsUrl = apiUrl.replace(/^http/, 'ws') + `/ws/devices/${config.deviceId}`;
232
+ const wsUrl = apiUrl.replace(/^http/, 'ws') + `/ws/devices/${config.deviceId}?token=${encodeURIComponent(config.token || '')}`;
208
233
  let ws: WebSocket | null = null;
209
234
  let running = true;
210
235
 
@@ -777,6 +802,7 @@ interface Config {
777
802
  deviceId?: string;
778
803
  workspaceId?: string;
779
804
  apiUrl?: string;
805
+ token?: string;
780
806
  // legacy
781
807
  agentId?: string;
782
808
  }