@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 +66 -25
- package/package.json +1 -1
- package/src/client.ts +16 -10
- package/src/index.ts +43 -17
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
|
|
77
|
-
|
|
78
|
-
headers
|
|
79
|
-
|
|
80
|
-
|
|
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,
|
|
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,
|
|
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\
|
|
5484
|
-
const
|
|
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 (!
|
|
5493
|
+
if (!start.success || !start.data) {
|
|
5493
5494
|
console.log(`
|
|
5494
|
-
\u274C
|
|
5495
|
+
\u274C Failed to start pairing:`, start.error);
|
|
5495
5496
|
process.exit(1);
|
|
5496
5497
|
}
|
|
5497
|
-
|
|
5498
|
-
|
|
5499
|
-
|
|
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
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
|
|
10
|
-
|
|
11
|
-
headers
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
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,
|
|
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,
|
|
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
|
|
130
|
+
console.log(`\nš” Requesting pairing code for "${deviceName}"...`);
|
|
134
131
|
|
|
135
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
}
|