@schuttdev/gigai 0.1.0-beta.9 → 0.2.2
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/{chunk-4XUWD3DZ.js → chunk-7C3UYEKE.js} +1 -8
- package/dist/{dist-CU5WVKG2.js → dist-U7NYIMA4.js} +535 -143
- package/dist/index.js +508 -92
- package/package.json +3 -2
- package/dist/chunk-FN4LCKUA.js +0 -42
- package/dist/chunk-OMGUT7RM.js +0 -82
- package/dist/pairing-IGMDVOIZ-RA7GNFU7.js +0 -10
- package/dist/store-XDNMGPYX-5CGK2GXY.js +0 -7
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
decodeJWTPayload
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-7C3UYEKE.js";
|
|
5
5
|
|
|
6
6
|
// src/index.ts
|
|
7
7
|
import { defineCommand, runMain } from "citty";
|
|
@@ -29,9 +29,26 @@ function getConfigPath() {
|
|
|
29
29
|
async function readConfig() {
|
|
30
30
|
try {
|
|
31
31
|
const raw = await readFile(getConfigPath(), "utf8");
|
|
32
|
-
|
|
32
|
+
const parsed = JSON.parse(raw);
|
|
33
|
+
if (parsed.server && parsed.token && !parsed.servers) {
|
|
34
|
+
const name = deriveServerName(parsed.server);
|
|
35
|
+
const migrated = {
|
|
36
|
+
activeServer: name,
|
|
37
|
+
servers: {
|
|
38
|
+
[name]: {
|
|
39
|
+
server: parsed.server,
|
|
40
|
+
token: parsed.token,
|
|
41
|
+
sessionToken: parsed.sessionToken,
|
|
42
|
+
sessionExpiresAt: parsed.sessionExpiresAt
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
await writeConfig(migrated);
|
|
47
|
+
return migrated;
|
|
48
|
+
}
|
|
49
|
+
return { activeServer: parsed.activeServer, servers: parsed.servers ?? {} };
|
|
33
50
|
} catch {
|
|
34
|
-
return {};
|
|
51
|
+
return { servers: {} };
|
|
35
52
|
}
|
|
36
53
|
}
|
|
37
54
|
async function writeConfig(config) {
|
|
@@ -39,11 +56,47 @@ async function writeConfig(config) {
|
|
|
39
56
|
await mkdir(dir, { recursive: true });
|
|
40
57
|
await writeFile(getConfigPath(), JSON.stringify(config, null, 2) + "\n", { mode: 384 });
|
|
41
58
|
}
|
|
42
|
-
|
|
59
|
+
function getActiveEntry(config) {
|
|
60
|
+
if (!config.activeServer || !config.servers[config.activeServer]) return void 0;
|
|
61
|
+
return { name: config.activeServer, entry: config.servers[config.activeServer] };
|
|
62
|
+
}
|
|
63
|
+
async function addServer(name, server, token) {
|
|
64
|
+
const config = await readConfig();
|
|
65
|
+
for (const [existingName, entry] of Object.entries(config.servers)) {
|
|
66
|
+
if (normalizeUrl(entry.server) === normalizeUrl(server)) {
|
|
67
|
+
config.servers[existingName] = { server, token };
|
|
68
|
+
config.activeServer = existingName;
|
|
69
|
+
await writeConfig(config);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
config.servers[name] = { server, token };
|
|
74
|
+
config.activeServer = name;
|
|
75
|
+
await writeConfig(config);
|
|
76
|
+
}
|
|
77
|
+
async function updateServerSession(name, sessionToken, sessionExpiresAt) {
|
|
43
78
|
const config = await readConfig();
|
|
44
|
-
|
|
79
|
+
const entry = config.servers[name];
|
|
80
|
+
if (!entry) return;
|
|
81
|
+
entry.sessionToken = sessionToken;
|
|
82
|
+
entry.sessionExpiresAt = sessionExpiresAt;
|
|
45
83
|
await writeConfig(config);
|
|
46
|
-
|
|
84
|
+
}
|
|
85
|
+
function deriveServerName(url) {
|
|
86
|
+
try {
|
|
87
|
+
const hostname = new URL(url).hostname;
|
|
88
|
+
return hostname.split(".")[0];
|
|
89
|
+
} catch {
|
|
90
|
+
return "default";
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function normalizeUrl(url) {
|
|
94
|
+
try {
|
|
95
|
+
const u = new URL(url);
|
|
96
|
+
return u.hostname.toLowerCase();
|
|
97
|
+
} catch {
|
|
98
|
+
return url.toLowerCase();
|
|
99
|
+
}
|
|
47
100
|
}
|
|
48
101
|
|
|
49
102
|
// src/identity.ts
|
|
@@ -51,8 +104,8 @@ function getOrgUUID() {
|
|
|
51
104
|
if (process.env.GIGAI_ORG_UUID) {
|
|
52
105
|
return process.env.GIGAI_ORG_UUID;
|
|
53
106
|
}
|
|
54
|
-
const proxyUrl = process.env.
|
|
55
|
-
const jwtMatch = proxyUrl.match(
|
|
107
|
+
const proxyUrl = process.env.HTTPS_PROXY || process.env.HTTP_PROXY || "";
|
|
108
|
+
const jwtMatch = proxyUrl.match(/jwt_([^@]+)/);
|
|
56
109
|
if (jwtMatch) {
|
|
57
110
|
try {
|
|
58
111
|
const payload = decodeJWTPayload(jwtMatch[1]);
|
|
@@ -62,18 +115,25 @@ function getOrgUUID() {
|
|
|
62
115
|
} catch {
|
|
63
116
|
}
|
|
64
117
|
}
|
|
118
|
+
const anthropicProxy = process.env.ANTHROPIC_PROXY_URL ?? "";
|
|
119
|
+
const anthropicJwtMatch = anthropicProxy.match(/([A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+)/);
|
|
120
|
+
if (anthropicJwtMatch) {
|
|
121
|
+
try {
|
|
122
|
+
const payload = decodeJWTPayload(anthropicJwtMatch[1]);
|
|
123
|
+
if (payload.organization_uuid) {
|
|
124
|
+
return payload.organization_uuid;
|
|
125
|
+
}
|
|
126
|
+
} catch {
|
|
127
|
+
}
|
|
128
|
+
}
|
|
65
129
|
const apiKey = process.env.ANTHROPIC_API_KEY ?? "";
|
|
66
|
-
if (apiKey.includes("
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const payload = decodeJWTPayload(jwtPart);
|
|
72
|
-
if (payload.organization_uuid) {
|
|
73
|
-
return payload.organization_uuid;
|
|
74
|
-
}
|
|
75
|
-
} catch {
|
|
130
|
+
if (apiKey.includes(".")) {
|
|
131
|
+
try {
|
|
132
|
+
const payload = decodeJWTPayload(apiKey);
|
|
133
|
+
if (payload.organization_uuid) {
|
|
134
|
+
return payload.organization_uuid;
|
|
76
135
|
}
|
|
136
|
+
} catch {
|
|
77
137
|
}
|
|
78
138
|
}
|
|
79
139
|
throw new Error(
|
|
@@ -82,6 +142,23 @@ function getOrgUUID() {
|
|
|
82
142
|
}
|
|
83
143
|
|
|
84
144
|
// src/http.ts
|
|
145
|
+
async function getProxyDispatcher() {
|
|
146
|
+
const proxyUrl = process.env.HTTPS_PROXY || process.env.HTTP_PROXY;
|
|
147
|
+
if (!proxyUrl) return void 0;
|
|
148
|
+
try {
|
|
149
|
+
const undici = await import("undici");
|
|
150
|
+
return new undici.ProxyAgent(proxyUrl);
|
|
151
|
+
} catch {
|
|
152
|
+
return void 0;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
var _dispatcher = null;
|
|
156
|
+
async function ensureDispatcher() {
|
|
157
|
+
if (_dispatcher === null) {
|
|
158
|
+
_dispatcher = await getProxyDispatcher();
|
|
159
|
+
}
|
|
160
|
+
return _dispatcher;
|
|
161
|
+
}
|
|
85
162
|
function createHttpClient(serverUrl, sessionToken) {
|
|
86
163
|
const baseUrl = serverUrl.replace(/\/$/, "");
|
|
87
164
|
async function request(path, init = {}) {
|
|
@@ -94,10 +171,15 @@ function createHttpClient(serverUrl, sessionToken) {
|
|
|
94
171
|
if (!headers["Content-Type"] && init.body && typeof init.body === "string") {
|
|
95
172
|
headers["Content-Type"] = "application/json";
|
|
96
173
|
}
|
|
97
|
-
const
|
|
174
|
+
const dispatcher = await ensureDispatcher();
|
|
175
|
+
const fetchOpts = {
|
|
98
176
|
...init,
|
|
99
177
|
headers
|
|
100
|
-
}
|
|
178
|
+
};
|
|
179
|
+
if (dispatcher) {
|
|
180
|
+
fetchOpts.dispatcher = dispatcher;
|
|
181
|
+
}
|
|
182
|
+
const res = await fetch(`${baseUrl}${path}`, fetchOpts);
|
|
101
183
|
if (!res.ok) {
|
|
102
184
|
let errorBody;
|
|
103
185
|
try {
|
|
@@ -124,11 +206,16 @@ function createHttpClient(serverUrl, sessionToken) {
|
|
|
124
206
|
if (sessionToken) {
|
|
125
207
|
headers["Authorization"] = `Bearer ${sessionToken}`;
|
|
126
208
|
}
|
|
127
|
-
const
|
|
209
|
+
const dispatcher = await ensureDispatcher();
|
|
210
|
+
const fetchOpts = {
|
|
128
211
|
method: "POST",
|
|
129
212
|
headers,
|
|
130
213
|
body: formData
|
|
131
|
-
}
|
|
214
|
+
};
|
|
215
|
+
if (dispatcher) {
|
|
216
|
+
fetchOpts.dispatcher = dispatcher;
|
|
217
|
+
}
|
|
218
|
+
const res = await fetch(`${baseUrl}${path}`, fetchOpts);
|
|
132
219
|
if (!res.ok) {
|
|
133
220
|
let errorBody;
|
|
134
221
|
try {
|
|
@@ -144,7 +231,12 @@ function createHttpClient(serverUrl, sessionToken) {
|
|
|
144
231
|
if (sessionToken) {
|
|
145
232
|
headers["Authorization"] = `Bearer ${sessionToken}`;
|
|
146
233
|
}
|
|
147
|
-
const
|
|
234
|
+
const dispatcher = await ensureDispatcher();
|
|
235
|
+
const fetchOpts = { headers };
|
|
236
|
+
if (dispatcher) {
|
|
237
|
+
fetchOpts.dispatcher = dispatcher;
|
|
238
|
+
}
|
|
239
|
+
const res = await fetch(`${baseUrl}${path}`, fetchOpts);
|
|
148
240
|
if (!res.ok) {
|
|
149
241
|
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
|
|
150
242
|
}
|
|
@@ -153,31 +245,274 @@ function createHttpClient(serverUrl, sessionToken) {
|
|
|
153
245
|
};
|
|
154
246
|
}
|
|
155
247
|
|
|
248
|
+
// src/version.ts
|
|
249
|
+
var VERSION = "0.2.2";
|
|
250
|
+
|
|
156
251
|
// src/connect.ts
|
|
157
|
-
async function connect() {
|
|
252
|
+
async function connect(serverName) {
|
|
158
253
|
const config = await readConfig();
|
|
159
|
-
if (
|
|
160
|
-
|
|
254
|
+
if (serverName) {
|
|
255
|
+
if (!config.servers[serverName]) {
|
|
256
|
+
const available = Object.keys(config.servers);
|
|
257
|
+
throw new Error(
|
|
258
|
+
available.length > 0 ? `Unknown server "${serverName}". Available: ${available.join(", ")}` : `No servers configured. Run 'gigai pair' first.`
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
config.activeServer = serverName;
|
|
262
|
+
await writeConfig(config);
|
|
161
263
|
}
|
|
162
|
-
|
|
163
|
-
|
|
264
|
+
const active = getActiveEntry(config);
|
|
265
|
+
if (!active) {
|
|
266
|
+
throw new Error("No server configured. Run 'gigai pair' first.");
|
|
164
267
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
268
|
+
const { name, entry } = active;
|
|
269
|
+
if (entry.sessionToken && entry.sessionExpiresAt) {
|
|
270
|
+
if (Date.now() < entry.sessionExpiresAt - 5 * 60 * 1e3) {
|
|
271
|
+
await checkAndUpdateServer(entry.server, entry.sessionToken);
|
|
272
|
+
return { serverUrl: entry.server, sessionToken: entry.sessionToken };
|
|
168
273
|
}
|
|
169
274
|
}
|
|
170
275
|
const orgUuid = getOrgUUID();
|
|
171
|
-
const http = createHttpClient(
|
|
276
|
+
const http = createHttpClient(entry.server);
|
|
172
277
|
const res = await http.post("/auth/connect", {
|
|
173
|
-
encryptedToken:
|
|
278
|
+
encryptedToken: entry.token,
|
|
174
279
|
orgUuid
|
|
175
280
|
});
|
|
176
|
-
await
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
281
|
+
await updateServerSession(name, res.sessionToken, res.expiresAt);
|
|
282
|
+
await checkAndUpdateServer(entry.server, res.sessionToken);
|
|
283
|
+
return { serverUrl: entry.server, sessionToken: res.sessionToken };
|
|
284
|
+
}
|
|
285
|
+
async function checkAndUpdateServer(serverUrl, sessionToken) {
|
|
286
|
+
try {
|
|
287
|
+
const http = createHttpClient(serverUrl);
|
|
288
|
+
const health = await http.get("/health");
|
|
289
|
+
if (isNewer(VERSION, health.version)) {
|
|
290
|
+
console.log(`Server is outdated (${health.version} \u2192 ${VERSION}). Updating...`);
|
|
291
|
+
const authedHttp = createHttpClient(serverUrl, sessionToken);
|
|
292
|
+
const res = await authedHttp.post("/admin/update");
|
|
293
|
+
if (res.updated) {
|
|
294
|
+
console.log("Server updated and restarting.");
|
|
295
|
+
await waitForServer(serverUrl, 15e3);
|
|
296
|
+
console.log("Server is back online.");
|
|
297
|
+
} else {
|
|
298
|
+
console.log(`Server update failed: ${res.error ?? "unknown error"}`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
} catch {
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
async function waitForServer(serverUrl, timeoutMs) {
|
|
305
|
+
const start = Date.now();
|
|
306
|
+
const http = createHttpClient(serverUrl);
|
|
307
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
308
|
+
while (Date.now() - start < timeoutMs) {
|
|
309
|
+
try {
|
|
310
|
+
await http.get("/health");
|
|
311
|
+
return;
|
|
312
|
+
} catch {
|
|
313
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
function isNewer(client, server) {
|
|
318
|
+
const parse = (v) => {
|
|
319
|
+
const [core, pre] = v.replace(/^v/, "").split("-");
|
|
320
|
+
const parts = core.split(".").map(Number);
|
|
321
|
+
const preNum = pre ? parseInt(pre.replace(/\D+/g, "")) || 0 : Infinity;
|
|
322
|
+
return [...parts, preNum];
|
|
323
|
+
};
|
|
324
|
+
const c = parse(client);
|
|
325
|
+
const s = parse(server);
|
|
326
|
+
for (let i = 0; i < Math.max(c.length, s.length); i++) {
|
|
327
|
+
const cv = c[i] ?? 0;
|
|
328
|
+
const sv = s[i] ?? 0;
|
|
329
|
+
if (cv > sv) return true;
|
|
330
|
+
if (cv < sv) return false;
|
|
331
|
+
}
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// src/skill.ts
|
|
336
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
337
|
+
import "path";
|
|
338
|
+
var SKILL_MD = `---
|
|
339
|
+
name: gigai
|
|
340
|
+
description: Access tools on the user's machine via the gigai CLI
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
# gigai
|
|
344
|
+
|
|
345
|
+
You have access to tools running on the user's local machine through the gigai CLI. This CLI proxies commands over HTTPS to a server on the user's machine.
|
|
346
|
+
|
|
347
|
+
## Setup (run once per conversation)
|
|
348
|
+
|
|
349
|
+
\`\`\`bash
|
|
350
|
+
npm install -g @schuttdev/gigai 2>/dev/null
|
|
351
|
+
mkdir -p ~/.gigai
|
|
352
|
+
cp /mnt/skills/user/gigai/config.json ~/.gigai/config.json
|
|
353
|
+
gigai connect
|
|
354
|
+
\`\`\`
|
|
355
|
+
|
|
356
|
+
If \`gigai connect\` succeeds, you are ready to use tools. If it fails, tell the user.
|
|
357
|
+
|
|
358
|
+
## Usage
|
|
359
|
+
|
|
360
|
+
### List available tools
|
|
361
|
+
\`\`\`bash
|
|
362
|
+
gigai list
|
|
363
|
+
\`\`\`
|
|
364
|
+
|
|
365
|
+
### Run a tool
|
|
366
|
+
\`\`\`bash
|
|
367
|
+
gigai <tool-name> [args...]
|
|
368
|
+
\`\`\`
|
|
369
|
+
|
|
370
|
+
Examples:
|
|
371
|
+
\`\`\`bash
|
|
372
|
+
gigai shell date
|
|
373
|
+
gigai fs list Documents
|
|
374
|
+
gigai shell whoami
|
|
375
|
+
\`\`\`
|
|
376
|
+
|
|
377
|
+
### Get help for a tool
|
|
378
|
+
\`\`\`bash
|
|
379
|
+
gigai help <tool-name>
|
|
380
|
+
\`\`\`
|
|
381
|
+
|
|
382
|
+
### Switch server (if multiple are configured)
|
|
383
|
+
\`\`\`bash
|
|
384
|
+
gigai connect <server-name>
|
|
385
|
+
\`\`\`
|
|
386
|
+
|
|
387
|
+
### File transfer
|
|
388
|
+
\`\`\`bash
|
|
389
|
+
gigai upload <file>
|
|
390
|
+
gigai download <id> <dest>
|
|
391
|
+
\`\`\`
|
|
392
|
+
|
|
393
|
+
## Important
|
|
394
|
+
|
|
395
|
+
- Always run the setup block before first use in a new conversation
|
|
396
|
+
- All commands execute on the **user's machine**, not in this sandbox
|
|
397
|
+
- If you get auth errors, run \`gigai connect\` to refresh the session
|
|
398
|
+
- Tools are scoped to what the user has configured \u2014 if a tool is missing, tell the user
|
|
399
|
+
`;
|
|
400
|
+
async function generateSkillZip(serverName, serverUrl, token) {
|
|
401
|
+
let skillConfig = { servers: {} };
|
|
402
|
+
try {
|
|
403
|
+
const raw = await readFile2("/mnt/skills/user/gigai/config.json", "utf8");
|
|
404
|
+
const existing = JSON.parse(raw);
|
|
405
|
+
if (existing.servers) {
|
|
406
|
+
skillConfig = existing;
|
|
407
|
+
}
|
|
408
|
+
} catch {
|
|
409
|
+
}
|
|
410
|
+
let merged = false;
|
|
411
|
+
for (const [name, entry] of Object.entries(skillConfig.servers)) {
|
|
412
|
+
if (normalizeHost(entry.server) === normalizeHost(serverUrl)) {
|
|
413
|
+
skillConfig.servers[name] = { server: serverUrl, token };
|
|
414
|
+
skillConfig.activeServer = name;
|
|
415
|
+
merged = true;
|
|
416
|
+
break;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
if (!merged) {
|
|
420
|
+
skillConfig.servers[serverName] = { server: serverUrl, token };
|
|
421
|
+
skillConfig.activeServer = serverName;
|
|
422
|
+
}
|
|
423
|
+
const configJson = JSON.stringify(skillConfig, null, 2) + "\n";
|
|
424
|
+
return createZip([
|
|
425
|
+
{ path: "gigai/SKILL.md", data: Buffer.from(SKILL_MD, "utf8") },
|
|
426
|
+
{ path: "gigai/config.json", data: Buffer.from(configJson, "utf8") }
|
|
427
|
+
]);
|
|
428
|
+
}
|
|
429
|
+
async function writeSkillZip(zip) {
|
|
430
|
+
const outputsDir = "/mnt/user-data/outputs";
|
|
431
|
+
try {
|
|
432
|
+
await mkdir2(outputsDir, { recursive: true });
|
|
433
|
+
const outPath = `${outputsDir}/gigai.zip`;
|
|
434
|
+
await writeFile2(outPath, zip);
|
|
435
|
+
return outPath;
|
|
436
|
+
} catch {
|
|
437
|
+
const outPath = "gigai.zip";
|
|
438
|
+
await writeFile2(outPath, zip);
|
|
439
|
+
return outPath;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
function normalizeHost(url) {
|
|
443
|
+
try {
|
|
444
|
+
return new URL(url).hostname.toLowerCase();
|
|
445
|
+
} catch {
|
|
446
|
+
return url.toLowerCase();
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
var crc32Table = new Uint32Array(256);
|
|
450
|
+
for (let i = 0; i < 256; i++) {
|
|
451
|
+
let c = i;
|
|
452
|
+
for (let j = 0; j < 8; j++) {
|
|
453
|
+
c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
454
|
+
}
|
|
455
|
+
crc32Table[i] = c >>> 0;
|
|
456
|
+
}
|
|
457
|
+
function crc32(data) {
|
|
458
|
+
let crc = 4294967295;
|
|
459
|
+
for (const byte of data) {
|
|
460
|
+
crc = crc >>> 8 ^ crc32Table[(crc ^ byte) & 255];
|
|
461
|
+
}
|
|
462
|
+
return (crc ^ 4294967295) >>> 0;
|
|
463
|
+
}
|
|
464
|
+
function createZip(entries) {
|
|
465
|
+
const parts = [];
|
|
466
|
+
const centralParts = [];
|
|
467
|
+
let offset = 0;
|
|
468
|
+
for (const entry of entries) {
|
|
469
|
+
const name = Buffer.from(entry.path, "utf8");
|
|
470
|
+
const checksum = crc32(entry.data);
|
|
471
|
+
const local = Buffer.alloc(30);
|
|
472
|
+
local.writeUInt32LE(67324752, 0);
|
|
473
|
+
local.writeUInt16LE(20, 4);
|
|
474
|
+
local.writeUInt16LE(0, 6);
|
|
475
|
+
local.writeUInt16LE(0, 8);
|
|
476
|
+
local.writeUInt16LE(0, 10);
|
|
477
|
+
local.writeUInt16LE(0, 12);
|
|
478
|
+
local.writeUInt32LE(checksum, 14);
|
|
479
|
+
local.writeUInt32LE(entry.data.length, 18);
|
|
480
|
+
local.writeUInt32LE(entry.data.length, 22);
|
|
481
|
+
local.writeUInt16LE(name.length, 26);
|
|
482
|
+
local.writeUInt16LE(0, 28);
|
|
483
|
+
parts.push(local, name, entry.data);
|
|
484
|
+
const central = Buffer.alloc(46);
|
|
485
|
+
central.writeUInt32LE(33639248, 0);
|
|
486
|
+
central.writeUInt16LE(20, 4);
|
|
487
|
+
central.writeUInt16LE(20, 6);
|
|
488
|
+
central.writeUInt16LE(0, 8);
|
|
489
|
+
central.writeUInt16LE(0, 10);
|
|
490
|
+
central.writeUInt16LE(0, 12);
|
|
491
|
+
central.writeUInt16LE(0, 14);
|
|
492
|
+
central.writeUInt32LE(checksum, 16);
|
|
493
|
+
central.writeUInt32LE(entry.data.length, 20);
|
|
494
|
+
central.writeUInt32LE(entry.data.length, 24);
|
|
495
|
+
central.writeUInt16LE(name.length, 28);
|
|
496
|
+
central.writeUInt16LE(0, 30);
|
|
497
|
+
central.writeUInt16LE(0, 32);
|
|
498
|
+
central.writeUInt16LE(0, 34);
|
|
499
|
+
central.writeUInt16LE(0, 36);
|
|
500
|
+
central.writeUInt32LE(0, 38);
|
|
501
|
+
central.writeUInt32LE(offset, 42);
|
|
502
|
+
centralParts.push(central, name);
|
|
503
|
+
offset += 30 + name.length + entry.data.length;
|
|
504
|
+
}
|
|
505
|
+
const centralDir = Buffer.concat(centralParts);
|
|
506
|
+
const eocd = Buffer.alloc(22);
|
|
507
|
+
eocd.writeUInt32LE(101010256, 0);
|
|
508
|
+
eocd.writeUInt16LE(0, 4);
|
|
509
|
+
eocd.writeUInt16LE(0, 6);
|
|
510
|
+
eocd.writeUInt16LE(entries.length, 8);
|
|
511
|
+
eocd.writeUInt16LE(entries.length, 10);
|
|
512
|
+
eocd.writeUInt32LE(centralDir.length, 12);
|
|
513
|
+
eocd.writeUInt32LE(offset, 16);
|
|
514
|
+
eocd.writeUInt16LE(0, 20);
|
|
515
|
+
return Buffer.concat([...parts, centralDir, eocd]);
|
|
181
516
|
}
|
|
182
517
|
|
|
183
518
|
// src/pair.ts
|
|
@@ -188,17 +523,17 @@ async function pair(code, serverUrl) {
|
|
|
188
523
|
pairingCode: code,
|
|
189
524
|
orgUuid
|
|
190
525
|
});
|
|
191
|
-
await
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
});
|
|
197
|
-
console.log("
|
|
526
|
+
await addServer(res.serverName, serverUrl, res.encryptedToken);
|
|
527
|
+
console.log(`Paired with "${res.serverName}" successfully!
|
|
528
|
+
`);
|
|
529
|
+
const zip = await generateSkillZip(res.serverName, serverUrl, res.encryptedToken);
|
|
530
|
+
const outPath = await writeSkillZip(zip);
|
|
531
|
+
console.log(`Skill zip written to: ${outPath}`);
|
|
532
|
+
console.log("Upload this file as a skill in Claude Desktop (Settings \u2192 Customize \u2192 Upload Skill).");
|
|
198
533
|
}
|
|
199
534
|
|
|
200
535
|
// src/discover.ts
|
|
201
|
-
import { readFile as
|
|
536
|
+
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
202
537
|
import { join as join2 } from "path";
|
|
203
538
|
import { homedir as homedir2 } from "os";
|
|
204
539
|
var MANIFEST_TTL = 5 * 60 * 1e3;
|
|
@@ -208,7 +543,7 @@ function getManifestPath() {
|
|
|
208
543
|
}
|
|
209
544
|
async function fetchTools(http) {
|
|
210
545
|
try {
|
|
211
|
-
const raw = await
|
|
546
|
+
const raw = await readFile3(getManifestPath(), "utf8");
|
|
212
547
|
const cache = JSON.parse(raw);
|
|
213
548
|
if (Date.now() - cache.fetchedAt < MANIFEST_TTL) {
|
|
214
549
|
return cache.tools;
|
|
@@ -218,9 +553,9 @@ async function fetchTools(http) {
|
|
|
218
553
|
const res = await http.get("/tools");
|
|
219
554
|
try {
|
|
220
555
|
const dir = process.env.GIGAI_CONFIG_DIR ?? join2(homedir2(), ".gigai");
|
|
221
|
-
await
|
|
556
|
+
await mkdir3(dir, { recursive: true });
|
|
222
557
|
const cache = { tools: res.tools, fetchedAt: Date.now() };
|
|
223
|
-
await
|
|
558
|
+
await writeFile3(getManifestPath(), JSON.stringify(cache));
|
|
224
559
|
} catch {
|
|
225
560
|
}
|
|
226
561
|
return res.tools;
|
|
@@ -240,13 +575,32 @@ async function execTool(http, name, args, timeout) {
|
|
|
240
575
|
if (res.stderr) process.stderr.write(res.stderr);
|
|
241
576
|
process.exitCode = res.exitCode;
|
|
242
577
|
}
|
|
578
|
+
async function execMcpTool(http, tool, mcpTool, args) {
|
|
579
|
+
const res = await http.post("/exec/mcp", {
|
|
580
|
+
tool,
|
|
581
|
+
mcpTool,
|
|
582
|
+
args
|
|
583
|
+
});
|
|
584
|
+
for (const content of res.content) {
|
|
585
|
+
if (content.type === "text" && content.text) {
|
|
586
|
+
process.stdout.write(content.text + "\n");
|
|
587
|
+
} else if (content.type === "image") {
|
|
588
|
+
console.log(`[Image: ${content.mimeType}]`);
|
|
589
|
+
} else if (content.type === "resource") {
|
|
590
|
+
console.log(`[Resource: ${content.mimeType}]`);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
if (res.isError) {
|
|
594
|
+
process.exitCode = 1;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
243
597
|
|
|
244
598
|
// src/transfer.ts
|
|
245
|
-
import { readFile as
|
|
599
|
+
import { readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
|
|
246
600
|
import { basename } from "path";
|
|
247
601
|
import { Blob } from "buffer";
|
|
248
602
|
async function upload(http, filePath) {
|
|
249
|
-
const content = await
|
|
603
|
+
const content = await readFile4(filePath);
|
|
250
604
|
const filename = basename(filePath);
|
|
251
605
|
const formData = new FormData();
|
|
252
606
|
const blob = new Blob([content]);
|
|
@@ -261,7 +615,7 @@ async function upload(http, filePath) {
|
|
|
261
615
|
async function download(http, id, destPath) {
|
|
262
616
|
const res = await http.getRaw(`/transfer/${encodeURIComponent(id)}`);
|
|
263
617
|
const buffer = Buffer.from(await res.arrayBuffer());
|
|
264
|
-
await
|
|
618
|
+
await writeFile4(destPath, buffer);
|
|
265
619
|
console.log(`Downloaded to: ${destPath}`);
|
|
266
620
|
}
|
|
267
621
|
|
|
@@ -302,26 +656,32 @@ Usage: ${detail.usage}`);
|
|
|
302
656
|
}
|
|
303
657
|
return lines.join("\n");
|
|
304
658
|
}
|
|
305
|
-
function formatStatus(
|
|
306
|
-
|
|
659
|
+
function formatStatus(config) {
|
|
660
|
+
const serverNames = Object.keys(config.servers);
|
|
661
|
+
if (serverNames.length === 0) {
|
|
307
662
|
return "Not connected. Run 'gigai pair <code> <server-url>' to set up.";
|
|
308
663
|
}
|
|
309
|
-
const lines = [
|
|
310
|
-
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
664
|
+
const lines = [];
|
|
665
|
+
for (const name of serverNames) {
|
|
666
|
+
const entry = config.servers[name];
|
|
667
|
+
const active = name === config.activeServer ? " (active)" : "";
|
|
668
|
+
lines.push(` ${name}${active} ${entry.server}`);
|
|
669
|
+
if (entry.sessionExpiresAt) {
|
|
670
|
+
const remaining = entry.sessionExpiresAt - Date.now();
|
|
671
|
+
if (remaining > 0) {
|
|
672
|
+
lines.push(` Session expires in ${Math.floor(remaining / 6e4)} minutes`);
|
|
673
|
+
} else {
|
|
674
|
+
lines.push(" Session expired \u2014 will auto-renew on next command");
|
|
675
|
+
}
|
|
317
676
|
}
|
|
318
677
|
}
|
|
319
|
-
return
|
|
678
|
+
return `Servers:
|
|
679
|
+
${lines.join("\n")}`;
|
|
320
680
|
}
|
|
321
681
|
|
|
322
682
|
// src/index.ts
|
|
323
683
|
var mode = detectMode();
|
|
324
|
-
var
|
|
684
|
+
var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
325
685
|
"pair",
|
|
326
686
|
"connect",
|
|
327
687
|
"list",
|
|
@@ -331,23 +691,36 @@ var CLIENT_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
331
691
|
"download",
|
|
332
692
|
"version",
|
|
333
693
|
"--help",
|
|
334
|
-
"-h"
|
|
694
|
+
"-h",
|
|
695
|
+
"server",
|
|
696
|
+
"wrap",
|
|
697
|
+
"unwrap"
|
|
335
698
|
]);
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
699
|
+
var firstArg = process.argv[2];
|
|
700
|
+
if (firstArg && !firstArg.startsWith("-") && !KNOWN_COMMANDS.has(firstArg)) {
|
|
701
|
+
const toolName = firstArg;
|
|
702
|
+
const toolArgs = process.argv.slice(3);
|
|
703
|
+
try {
|
|
704
|
+
const { serverUrl, sessionToken } = await connect();
|
|
705
|
+
const http = createHttpClient(serverUrl, sessionToken);
|
|
706
|
+
const { tool: detail } = await fetchToolDetail(http, toolName);
|
|
707
|
+
if (detail.type === "mcp") {
|
|
708
|
+
const mcpToolName = toolArgs[0];
|
|
709
|
+
if (!mcpToolName) {
|
|
710
|
+
const toolNames = (detail.mcpTools ?? []).map((t) => ` ${t.name} \u2014 ${t.description}`);
|
|
711
|
+
console.log(`MCP tools for ${toolName}:
|
|
712
|
+
${toolNames.join("\n")}`);
|
|
713
|
+
} else {
|
|
714
|
+
const jsonArg = toolArgs.slice(1).join(" ");
|
|
715
|
+
const args = jsonArg ? JSON.parse(jsonArg) : {};
|
|
716
|
+
await execMcpTool(http, toolName, mcpToolName, args);
|
|
717
|
+
}
|
|
718
|
+
} else {
|
|
344
719
|
await execTool(http, toolName, toolArgs);
|
|
345
|
-
} catch (e) {
|
|
346
|
-
console.error(`Error: ${e.message}`);
|
|
347
|
-
process.exitCode = 1;
|
|
348
720
|
}
|
|
349
|
-
}
|
|
350
|
-
|
|
721
|
+
} catch (e) {
|
|
722
|
+
console.error(`Error: ${e.message}`);
|
|
723
|
+
process.exitCode = 1;
|
|
351
724
|
}
|
|
352
725
|
} else {
|
|
353
726
|
runCitty();
|
|
@@ -365,8 +738,11 @@ function runCitty() {
|
|
|
365
738
|
});
|
|
366
739
|
const connectCommand = defineCommand({
|
|
367
740
|
meta: { name: "connect", description: "Establish a session with the server" },
|
|
368
|
-
|
|
369
|
-
|
|
741
|
+
args: {
|
|
742
|
+
name: { type: "positional", description: "Server name (optional)", required: false }
|
|
743
|
+
},
|
|
744
|
+
async run({ args }) {
|
|
745
|
+
const { serverUrl } = await connect(args.name);
|
|
370
746
|
console.log(`Connected to ${serverUrl}`);
|
|
371
747
|
}
|
|
372
748
|
});
|
|
@@ -395,8 +771,7 @@ function runCitty() {
|
|
|
395
771
|
meta: { name: "status", description: "Show connection status" },
|
|
396
772
|
async run() {
|
|
397
773
|
const config = await readConfig();
|
|
398
|
-
|
|
399
|
-
console.log(formatStatus(connected, config.server, config.sessionExpiresAt));
|
|
774
|
+
console.log(formatStatus(config));
|
|
400
775
|
}
|
|
401
776
|
});
|
|
402
777
|
const uploadCommand = defineCommand({
|
|
@@ -425,7 +800,7 @@ function runCitty() {
|
|
|
425
800
|
const versionCommand = defineCommand({
|
|
426
801
|
meta: { name: "version", description: "Show version" },
|
|
427
802
|
run() {
|
|
428
|
-
console.log(
|
|
803
|
+
console.log(`gigai v${VERSION}`);
|
|
429
804
|
}
|
|
430
805
|
});
|
|
431
806
|
const serverCommand = defineCommand({
|
|
@@ -438,7 +813,7 @@ function runCitty() {
|
|
|
438
813
|
dev: { type: "boolean", description: "Development mode (no HTTPS)" }
|
|
439
814
|
},
|
|
440
815
|
async run({ args }) {
|
|
441
|
-
const { startServer } = await import("./dist-
|
|
816
|
+
const { startServer } = await import("./dist-U7NYIMA4.js");
|
|
442
817
|
const extraArgs = [];
|
|
443
818
|
if (args.config) extraArgs.push("--config", args.config);
|
|
444
819
|
if (args.dev) extraArgs.push("--dev");
|
|
@@ -449,7 +824,7 @@ function runCitty() {
|
|
|
449
824
|
init: defineCommand({
|
|
450
825
|
meta: { name: "init", description: "Interactive setup wizard" },
|
|
451
826
|
async run() {
|
|
452
|
-
const { runInit } = await import("./dist-
|
|
827
|
+
const { runInit } = await import("./dist-U7NYIMA4.js");
|
|
453
828
|
await runInit();
|
|
454
829
|
}
|
|
455
830
|
}),
|
|
@@ -459,10 +834,51 @@ function runCitty() {
|
|
|
459
834
|
config: { type: "string", alias: "c", description: "Config file path" }
|
|
460
835
|
},
|
|
461
836
|
async run({ args }) {
|
|
462
|
-
const { generateServerPairingCode } = await import("./dist-
|
|
837
|
+
const { generateServerPairingCode } = await import("./dist-U7NYIMA4.js");
|
|
463
838
|
await generateServerPairingCode(args.config);
|
|
464
839
|
}
|
|
465
840
|
}),
|
|
841
|
+
install: defineCommand({
|
|
842
|
+
meta: { name: "install", description: "Install as persistent background service" },
|
|
843
|
+
args: {
|
|
844
|
+
config: { type: "string", alias: "c", description: "Config file path" }
|
|
845
|
+
},
|
|
846
|
+
async run({ args }) {
|
|
847
|
+
const { installDaemon } = await import("./dist-U7NYIMA4.js");
|
|
848
|
+
await installDaemon(args.config);
|
|
849
|
+
}
|
|
850
|
+
}),
|
|
851
|
+
uninstall: defineCommand({
|
|
852
|
+
meta: { name: "uninstall", description: "Remove background service" },
|
|
853
|
+
async run() {
|
|
854
|
+
const { uninstallDaemon } = await import("./dist-U7NYIMA4.js");
|
|
855
|
+
await uninstallDaemon();
|
|
856
|
+
}
|
|
857
|
+
}),
|
|
858
|
+
stop: defineCommand({
|
|
859
|
+
meta: { name: "stop", description: "Stop the running gigai server" },
|
|
860
|
+
async run() {
|
|
861
|
+
const { execFileSync } = await import("child_process");
|
|
862
|
+
let pids = [];
|
|
863
|
+
try {
|
|
864
|
+
const out = execFileSync("pgrep", ["-f", "gigai server start"], { encoding: "utf8" });
|
|
865
|
+
pids = out.trim().split("\n").map(Number).filter((pid) => pid && pid !== process.pid);
|
|
866
|
+
} catch {
|
|
867
|
+
}
|
|
868
|
+
if (pids.length === 0) {
|
|
869
|
+
console.log("No running gigai server found.");
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
for (const pid of pids) {
|
|
873
|
+
try {
|
|
874
|
+
process.kill(pid, "SIGTERM");
|
|
875
|
+
console.log(`Stopped gigai server (PID ${pid})`);
|
|
876
|
+
} catch (e) {
|
|
877
|
+
console.error(`Failed to stop PID ${pid}: ${e.message}`);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
}),
|
|
466
882
|
status: defineCommand({
|
|
467
883
|
meta: { name: "status", description: "Show server status" },
|
|
468
884
|
async run() {
|
|
@@ -486,21 +902,21 @@ function runCitty() {
|
|
|
486
902
|
cli: defineCommand({
|
|
487
903
|
meta: { name: "cli", description: "Wrap a CLI command" },
|
|
488
904
|
async run() {
|
|
489
|
-
const { wrapCli } = await import("./dist-
|
|
905
|
+
const { wrapCli } = await import("./dist-U7NYIMA4.js");
|
|
490
906
|
await wrapCli();
|
|
491
907
|
}
|
|
492
908
|
}),
|
|
493
909
|
mcp: defineCommand({
|
|
494
910
|
meta: { name: "mcp", description: "Wrap an MCP server" },
|
|
495
911
|
async run() {
|
|
496
|
-
const { wrapMcp } = await import("./dist-
|
|
912
|
+
const { wrapMcp } = await import("./dist-U7NYIMA4.js");
|
|
497
913
|
await wrapMcp();
|
|
498
914
|
}
|
|
499
915
|
}),
|
|
500
916
|
script: defineCommand({
|
|
501
917
|
meta: { name: "script", description: "Wrap a script" },
|
|
502
918
|
async run() {
|
|
503
|
-
const { wrapScript } = await import("./dist-
|
|
919
|
+
const { wrapScript } = await import("./dist-U7NYIMA4.js");
|
|
504
920
|
await wrapScript();
|
|
505
921
|
}
|
|
506
922
|
}),
|
|
@@ -510,7 +926,7 @@ function runCitty() {
|
|
|
510
926
|
path: { type: "positional", description: "Path to config file", required: true }
|
|
511
927
|
},
|
|
512
928
|
async run({ args }) {
|
|
513
|
-
const { wrapImport } = await import("./dist-
|
|
929
|
+
const { wrapImport } = await import("./dist-U7NYIMA4.js");
|
|
514
930
|
await wrapImport(args.path);
|
|
515
931
|
}
|
|
516
932
|
})
|
|
@@ -522,14 +938,14 @@ function runCitty() {
|
|
|
522
938
|
name: { type: "positional", description: "Tool name", required: true }
|
|
523
939
|
},
|
|
524
940
|
async run({ args }) {
|
|
525
|
-
const { unwrapTool } = await import("./dist-
|
|
941
|
+
const { unwrapTool } = await import("./dist-U7NYIMA4.js");
|
|
526
942
|
await unwrapTool(args.name);
|
|
527
943
|
}
|
|
528
944
|
});
|
|
529
945
|
const main = defineCommand({
|
|
530
946
|
meta: {
|
|
531
947
|
name: "gigai",
|
|
532
|
-
version:
|
|
948
|
+
version: VERSION,
|
|
533
949
|
description: "Bridge CLI tools to Claude across platforms"
|
|
534
950
|
},
|
|
535
951
|
subCommands: mode === "client" ? {
|