@schuttdev/gigai 0.1.0-beta.20 → 0.1.0-beta.21
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.
|
@@ -253,6 +253,7 @@ var ServerConfigSchema = z.object({
|
|
|
253
253
|
https: HttpsConfigSchema.optional()
|
|
254
254
|
});
|
|
255
255
|
var GigaiConfigSchema = z.object({
|
|
256
|
+
serverName: z.string().optional(),
|
|
256
257
|
server: ServerConfigSchema,
|
|
257
258
|
auth: AuthConfigSchema,
|
|
258
259
|
tools: z.array(ToolConfigSchema).default([])
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
decrypt,
|
|
7
7
|
encrypt,
|
|
8
8
|
generateEncryptionKey
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-7C3UYEKE.js";
|
|
10
10
|
|
|
11
11
|
// ../server/dist/index.mjs
|
|
12
12
|
import { parseArgs } from "util";
|
|
@@ -17,6 +17,7 @@ import multipart from "@fastify/multipart";
|
|
|
17
17
|
import fp from "fastify-plugin";
|
|
18
18
|
import { nanoid } from "nanoid";
|
|
19
19
|
import { randomBytes } from "crypto";
|
|
20
|
+
import { hostname } from "os";
|
|
20
21
|
import { nanoid as nanoid2 } from "nanoid";
|
|
21
22
|
import fp2 from "fastify-plugin";
|
|
22
23
|
import fp3 from "fastify-plugin";
|
|
@@ -185,6 +186,7 @@ function validateAndPair(store, code, orgUuid, encryptionKey, serverFingerprint)
|
|
|
185
186
|
}
|
|
186
187
|
function registerAuthRoutes(server, store, config) {
|
|
187
188
|
const serverFingerprint = randomBytes(16).toString("hex");
|
|
189
|
+
const serverName = config.serverName ?? hostname();
|
|
188
190
|
server.post("/auth/pair", {
|
|
189
191
|
config: {
|
|
190
192
|
rateLimit: { max: 5, timeWindow: "1 hour" }
|
|
@@ -208,7 +210,7 @@ function registerAuthRoutes(server, store, config) {
|
|
|
208
210
|
config.auth.encryptionKey,
|
|
209
211
|
serverFingerprint
|
|
210
212
|
);
|
|
211
|
-
return { encryptedToken: JSON.stringify(encryptedToken) };
|
|
213
|
+
return { encryptedToken: JSON.stringify(encryptedToken), serverName };
|
|
212
214
|
});
|
|
213
215
|
server.post("/auth/connect", {
|
|
214
216
|
config: {
|
|
@@ -1131,8 +1133,25 @@ async function runInit() {
|
|
|
1131
1133
|
config: { allowlist, allowSudo }
|
|
1132
1134
|
});
|
|
1133
1135
|
}
|
|
1136
|
+
let serverName;
|
|
1137
|
+
if (httpsProvider === "tailscale") {
|
|
1138
|
+
const dnsName = await getTailscaleDnsName();
|
|
1139
|
+
if (dnsName) {
|
|
1140
|
+
serverName = dnsName.split(".")[0];
|
|
1141
|
+
}
|
|
1142
|
+
} else if (httpsProvider === "cloudflare") {
|
|
1143
|
+
serverName = await input({
|
|
1144
|
+
message: "Server name (identifies this machine):",
|
|
1145
|
+
required: true
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1148
|
+
if (!serverName) {
|
|
1149
|
+
const { hostname: osHostname } = await import("os");
|
|
1150
|
+
serverName = osHostname();
|
|
1151
|
+
}
|
|
1134
1152
|
const encryptionKey = generateEncryptionKey();
|
|
1135
1153
|
const config = {
|
|
1154
|
+
serverName,
|
|
1136
1155
|
server: {
|
|
1137
1156
|
port,
|
|
1138
1157
|
host: "0.0.0.0",
|
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
|
|
@@ -193,56 +246,42 @@ function createHttpClient(serverUrl, sessionToken) {
|
|
|
193
246
|
}
|
|
194
247
|
|
|
195
248
|
// src/connect.ts
|
|
196
|
-
async function connect() {
|
|
249
|
+
async function connect(serverName) {
|
|
197
250
|
const config = await readConfig();
|
|
198
|
-
if (
|
|
199
|
-
|
|
251
|
+
if (serverName) {
|
|
252
|
+
if (!config.servers[serverName]) {
|
|
253
|
+
const available = Object.keys(config.servers);
|
|
254
|
+
throw new Error(
|
|
255
|
+
available.length > 0 ? `Unknown server "${serverName}". Available: ${available.join(", ")}` : `No servers configured. Run 'gigai pair' first.`
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
config.activeServer = serverName;
|
|
259
|
+
await writeConfig(config);
|
|
200
260
|
}
|
|
201
|
-
|
|
202
|
-
|
|
261
|
+
const active = getActiveEntry(config);
|
|
262
|
+
if (!active) {
|
|
263
|
+
throw new Error("No server configured. Run 'gigai pair' first.");
|
|
203
264
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
265
|
+
const { name, entry } = active;
|
|
266
|
+
if (entry.sessionToken && entry.sessionExpiresAt) {
|
|
267
|
+
if (Date.now() < entry.sessionExpiresAt - 5 * 60 * 1e3) {
|
|
268
|
+
return { serverUrl: entry.server, sessionToken: entry.sessionToken };
|
|
207
269
|
}
|
|
208
270
|
}
|
|
209
271
|
const orgUuid = getOrgUUID();
|
|
210
|
-
const http = createHttpClient(
|
|
272
|
+
const http = createHttpClient(entry.server);
|
|
211
273
|
const res = await http.post("/auth/connect", {
|
|
212
|
-
encryptedToken:
|
|
274
|
+
encryptedToken: entry.token,
|
|
213
275
|
orgUuid
|
|
214
276
|
});
|
|
215
|
-
await
|
|
216
|
-
|
|
217
|
-
sessionExpiresAt: res.expiresAt
|
|
218
|
-
});
|
|
219
|
-
return { serverUrl: config.server, sessionToken: res.sessionToken };
|
|
277
|
+
await updateServerSession(name, res.sessionToken, res.expiresAt);
|
|
278
|
+
return { serverUrl: entry.server, sessionToken: res.sessionToken };
|
|
220
279
|
}
|
|
221
280
|
|
|
222
|
-
// src/
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
const res = await http.post("/auth/pair", {
|
|
227
|
-
pairingCode: code,
|
|
228
|
-
orgUuid
|
|
229
|
-
});
|
|
230
|
-
await updateConfig({
|
|
231
|
-
server: serverUrl,
|
|
232
|
-
token: res.encryptedToken,
|
|
233
|
-
sessionToken: void 0,
|
|
234
|
-
sessionExpiresAt: void 0
|
|
235
|
-
});
|
|
236
|
-
console.log("Paired successfully!\n");
|
|
237
|
-
const tokenStr = JSON.stringify(res.encryptedToken);
|
|
238
|
-
const skillMd = generateSkill(serverUrl, tokenStr);
|
|
239
|
-
console.log("Save the following as a Claude Desktop skill:\n");
|
|
240
|
-
console.log("\u2500".repeat(50));
|
|
241
|
-
console.log(skillMd);
|
|
242
|
-
console.log("\u2500".repeat(50));
|
|
243
|
-
}
|
|
244
|
-
function generateSkill(serverUrl, token) {
|
|
245
|
-
return `---
|
|
281
|
+
// src/skill.ts
|
|
282
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
283
|
+
import "path";
|
|
284
|
+
var SKILL_MD = `---
|
|
246
285
|
name: gigai
|
|
247
286
|
description: Access tools on the user's machine via the gigai CLI
|
|
248
287
|
---
|
|
@@ -251,21 +290,12 @@ description: Access tools on the user's machine via the gigai CLI
|
|
|
251
290
|
|
|
252
291
|
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.
|
|
253
292
|
|
|
254
|
-
## Setup (run once per
|
|
255
|
-
|
|
256
|
-
Before using any gigai commands, you must install and configure the CLI in your code execution environment. Run this setup block:
|
|
293
|
+
## Setup (run once per conversation)
|
|
257
294
|
|
|
258
295
|
\`\`\`bash
|
|
259
296
|
npm install -g @schuttdev/gigai 2>/dev/null
|
|
260
|
-
|
|
261
297
|
mkdir -p ~/.gigai
|
|
262
|
-
|
|
263
|
-
{
|
|
264
|
-
"server": "${serverUrl}",
|
|
265
|
-
"token": ${token}
|
|
266
|
-
}
|
|
267
|
-
GIGAI_EOF
|
|
268
|
-
|
|
298
|
+
cp /mnt/skills/user/gigai/config.json ~/.gigai/config.json
|
|
269
299
|
gigai connect
|
|
270
300
|
\`\`\`
|
|
271
301
|
|
|
@@ -278,14 +308,26 @@ If \`gigai connect\` succeeds, you are ready to use tools. If it fails, tell the
|
|
|
278
308
|
gigai list
|
|
279
309
|
\`\`\`
|
|
280
310
|
|
|
311
|
+
### Run a tool
|
|
312
|
+
\`\`\`bash
|
|
313
|
+
gigai <tool-name> [args...]
|
|
314
|
+
\`\`\`
|
|
315
|
+
|
|
316
|
+
Examples:
|
|
317
|
+
\`\`\`bash
|
|
318
|
+
gigai shell date
|
|
319
|
+
gigai fs list Documents
|
|
320
|
+
gigai shell whoami
|
|
321
|
+
\`\`\`
|
|
322
|
+
|
|
281
323
|
### Get help for a tool
|
|
282
324
|
\`\`\`bash
|
|
283
325
|
gigai help <tool-name>
|
|
284
326
|
\`\`\`
|
|
285
327
|
|
|
286
|
-
###
|
|
328
|
+
### Switch server (if multiple are configured)
|
|
287
329
|
\`\`\`bash
|
|
288
|
-
gigai <
|
|
330
|
+
gigai connect <server-name>
|
|
289
331
|
\`\`\`
|
|
290
332
|
|
|
291
333
|
### File transfer
|
|
@@ -296,15 +338,148 @@ gigai download <id> <dest>
|
|
|
296
338
|
|
|
297
339
|
## Important
|
|
298
340
|
|
|
299
|
-
- Always run the setup block before first use in a new
|
|
341
|
+
- Always run the setup block before first use in a new conversation
|
|
300
342
|
- All commands execute on the **user's machine**, not in this sandbox
|
|
301
343
|
- If you get auth errors, run \`gigai connect\` to refresh the session
|
|
302
344
|
- Tools are scoped to what the user has configured \u2014 if a tool is missing, tell the user
|
|
303
345
|
`;
|
|
346
|
+
async function generateSkillZip(serverName, serverUrl, token) {
|
|
347
|
+
let skillConfig = { servers: {} };
|
|
348
|
+
try {
|
|
349
|
+
const raw = await readFile2("/mnt/skills/user/gigai/config.json", "utf8");
|
|
350
|
+
const existing = JSON.parse(raw);
|
|
351
|
+
if (existing.servers) {
|
|
352
|
+
skillConfig = existing;
|
|
353
|
+
}
|
|
354
|
+
} catch {
|
|
355
|
+
}
|
|
356
|
+
let merged = false;
|
|
357
|
+
for (const [name, entry] of Object.entries(skillConfig.servers)) {
|
|
358
|
+
if (normalizeHost(entry.server) === normalizeHost(serverUrl)) {
|
|
359
|
+
skillConfig.servers[name] = { server: serverUrl, token };
|
|
360
|
+
skillConfig.activeServer = name;
|
|
361
|
+
merged = true;
|
|
362
|
+
break;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
if (!merged) {
|
|
366
|
+
skillConfig.servers[serverName] = { server: serverUrl, token };
|
|
367
|
+
skillConfig.activeServer = serverName;
|
|
368
|
+
}
|
|
369
|
+
const configJson = JSON.stringify(skillConfig, null, 2) + "\n";
|
|
370
|
+
return createZip([
|
|
371
|
+
{ path: "gigai/SKILL.md", data: Buffer.from(SKILL_MD, "utf8") },
|
|
372
|
+
{ path: "gigai/config.json", data: Buffer.from(configJson, "utf8") }
|
|
373
|
+
]);
|
|
374
|
+
}
|
|
375
|
+
async function writeSkillZip(zip) {
|
|
376
|
+
const outputsDir = "/mnt/user-data/outputs";
|
|
377
|
+
try {
|
|
378
|
+
await mkdir2(outputsDir, { recursive: true });
|
|
379
|
+
const outPath = `${outputsDir}/gigai.zip`;
|
|
380
|
+
await writeFile2(outPath, zip);
|
|
381
|
+
return outPath;
|
|
382
|
+
} catch {
|
|
383
|
+
const outPath = "gigai.zip";
|
|
384
|
+
await writeFile2(outPath, zip);
|
|
385
|
+
return outPath;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
function normalizeHost(url) {
|
|
389
|
+
try {
|
|
390
|
+
return new URL(url).hostname.toLowerCase();
|
|
391
|
+
} catch {
|
|
392
|
+
return url.toLowerCase();
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
var crc32Table = new Uint32Array(256);
|
|
396
|
+
for (let i = 0; i < 256; i++) {
|
|
397
|
+
let c = i;
|
|
398
|
+
for (let j = 0; j < 8; j++) {
|
|
399
|
+
c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
400
|
+
}
|
|
401
|
+
crc32Table[i] = c >>> 0;
|
|
402
|
+
}
|
|
403
|
+
function crc32(data) {
|
|
404
|
+
let crc = 4294967295;
|
|
405
|
+
for (const byte of data) {
|
|
406
|
+
crc = crc >>> 8 ^ crc32Table[(crc ^ byte) & 255];
|
|
407
|
+
}
|
|
408
|
+
return (crc ^ 4294967295) >>> 0;
|
|
409
|
+
}
|
|
410
|
+
function createZip(entries) {
|
|
411
|
+
const parts = [];
|
|
412
|
+
const centralParts = [];
|
|
413
|
+
let offset = 0;
|
|
414
|
+
for (const entry of entries) {
|
|
415
|
+
const name = Buffer.from(entry.path, "utf8");
|
|
416
|
+
const checksum = crc32(entry.data);
|
|
417
|
+
const local = Buffer.alloc(30);
|
|
418
|
+
local.writeUInt32LE(67324752, 0);
|
|
419
|
+
local.writeUInt16LE(20, 4);
|
|
420
|
+
local.writeUInt16LE(0, 6);
|
|
421
|
+
local.writeUInt16LE(0, 8);
|
|
422
|
+
local.writeUInt16LE(0, 10);
|
|
423
|
+
local.writeUInt16LE(0, 12);
|
|
424
|
+
local.writeUInt32LE(checksum, 14);
|
|
425
|
+
local.writeUInt32LE(entry.data.length, 18);
|
|
426
|
+
local.writeUInt32LE(entry.data.length, 22);
|
|
427
|
+
local.writeUInt16LE(name.length, 26);
|
|
428
|
+
local.writeUInt16LE(0, 28);
|
|
429
|
+
parts.push(local, name, entry.data);
|
|
430
|
+
const central = Buffer.alloc(46);
|
|
431
|
+
central.writeUInt32LE(33639248, 0);
|
|
432
|
+
central.writeUInt16LE(20, 4);
|
|
433
|
+
central.writeUInt16LE(20, 6);
|
|
434
|
+
central.writeUInt16LE(0, 8);
|
|
435
|
+
central.writeUInt16LE(0, 10);
|
|
436
|
+
central.writeUInt16LE(0, 12);
|
|
437
|
+
central.writeUInt16LE(0, 14);
|
|
438
|
+
central.writeUInt32LE(checksum, 16);
|
|
439
|
+
central.writeUInt32LE(entry.data.length, 20);
|
|
440
|
+
central.writeUInt32LE(entry.data.length, 24);
|
|
441
|
+
central.writeUInt16LE(name.length, 28);
|
|
442
|
+
central.writeUInt16LE(0, 30);
|
|
443
|
+
central.writeUInt16LE(0, 32);
|
|
444
|
+
central.writeUInt16LE(0, 34);
|
|
445
|
+
central.writeUInt16LE(0, 36);
|
|
446
|
+
central.writeUInt32LE(0, 38);
|
|
447
|
+
central.writeUInt32LE(offset, 42);
|
|
448
|
+
centralParts.push(central, name);
|
|
449
|
+
offset += 30 + name.length + entry.data.length;
|
|
450
|
+
}
|
|
451
|
+
const centralDir = Buffer.concat(centralParts);
|
|
452
|
+
const eocd = Buffer.alloc(22);
|
|
453
|
+
eocd.writeUInt32LE(101010256, 0);
|
|
454
|
+
eocd.writeUInt16LE(0, 4);
|
|
455
|
+
eocd.writeUInt16LE(0, 6);
|
|
456
|
+
eocd.writeUInt16LE(entries.length, 8);
|
|
457
|
+
eocd.writeUInt16LE(entries.length, 10);
|
|
458
|
+
eocd.writeUInt32LE(centralDir.length, 12);
|
|
459
|
+
eocd.writeUInt32LE(offset, 16);
|
|
460
|
+
eocd.writeUInt16LE(0, 20);
|
|
461
|
+
return Buffer.concat([...parts, centralDir, eocd]);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// src/pair.ts
|
|
465
|
+
async function pair(code, serverUrl) {
|
|
466
|
+
const orgUuid = getOrgUUID();
|
|
467
|
+
const http = createHttpClient(serverUrl);
|
|
468
|
+
const res = await http.post("/auth/pair", {
|
|
469
|
+
pairingCode: code,
|
|
470
|
+
orgUuid
|
|
471
|
+
});
|
|
472
|
+
await addServer(res.serverName, serverUrl, res.encryptedToken);
|
|
473
|
+
console.log(`Paired with "${res.serverName}" successfully!
|
|
474
|
+
`);
|
|
475
|
+
const zip = await generateSkillZip(res.serverName, serverUrl, res.encryptedToken);
|
|
476
|
+
const outPath = await writeSkillZip(zip);
|
|
477
|
+
console.log(`Skill zip written to: ${outPath}`);
|
|
478
|
+
console.log("Upload this file as a skill in Claude Desktop (Settings \u2192 Customize \u2192 Upload Skill).");
|
|
304
479
|
}
|
|
305
480
|
|
|
306
481
|
// src/discover.ts
|
|
307
|
-
import { readFile as
|
|
482
|
+
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
308
483
|
import { join as join2 } from "path";
|
|
309
484
|
import { homedir as homedir2 } from "os";
|
|
310
485
|
var MANIFEST_TTL = 5 * 60 * 1e3;
|
|
@@ -314,7 +489,7 @@ function getManifestPath() {
|
|
|
314
489
|
}
|
|
315
490
|
async function fetchTools(http) {
|
|
316
491
|
try {
|
|
317
|
-
const raw = await
|
|
492
|
+
const raw = await readFile3(getManifestPath(), "utf8");
|
|
318
493
|
const cache = JSON.parse(raw);
|
|
319
494
|
if (Date.now() - cache.fetchedAt < MANIFEST_TTL) {
|
|
320
495
|
return cache.tools;
|
|
@@ -324,9 +499,9 @@ async function fetchTools(http) {
|
|
|
324
499
|
const res = await http.get("/tools");
|
|
325
500
|
try {
|
|
326
501
|
const dir = process.env.GIGAI_CONFIG_DIR ?? join2(homedir2(), ".gigai");
|
|
327
|
-
await
|
|
502
|
+
await mkdir3(dir, { recursive: true });
|
|
328
503
|
const cache = { tools: res.tools, fetchedAt: Date.now() };
|
|
329
|
-
await
|
|
504
|
+
await writeFile3(getManifestPath(), JSON.stringify(cache));
|
|
330
505
|
} catch {
|
|
331
506
|
}
|
|
332
507
|
return res.tools;
|
|
@@ -367,11 +542,11 @@ async function execMcpTool(http, tool, mcpTool, args) {
|
|
|
367
542
|
}
|
|
368
543
|
|
|
369
544
|
// src/transfer.ts
|
|
370
|
-
import { readFile as
|
|
545
|
+
import { readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
|
|
371
546
|
import { basename } from "path";
|
|
372
547
|
import { Blob } from "buffer";
|
|
373
548
|
async function upload(http, filePath) {
|
|
374
|
-
const content = await
|
|
549
|
+
const content = await readFile4(filePath);
|
|
375
550
|
const filename = basename(filePath);
|
|
376
551
|
const formData = new FormData();
|
|
377
552
|
const blob = new Blob([content]);
|
|
@@ -386,7 +561,7 @@ async function upload(http, filePath) {
|
|
|
386
561
|
async function download(http, id, destPath) {
|
|
387
562
|
const res = await http.getRaw(`/transfer/${encodeURIComponent(id)}`);
|
|
388
563
|
const buffer = Buffer.from(await res.arrayBuffer());
|
|
389
|
-
await
|
|
564
|
+
await writeFile4(destPath, buffer);
|
|
390
565
|
console.log(`Downloaded to: ${destPath}`);
|
|
391
566
|
}
|
|
392
567
|
|
|
@@ -427,26 +602,32 @@ Usage: ${detail.usage}`);
|
|
|
427
602
|
}
|
|
428
603
|
return lines.join("\n");
|
|
429
604
|
}
|
|
430
|
-
function formatStatus(
|
|
431
|
-
|
|
605
|
+
function formatStatus(config) {
|
|
606
|
+
const serverNames = Object.keys(config.servers);
|
|
607
|
+
if (serverNames.length === 0) {
|
|
432
608
|
return "Not connected. Run 'gigai pair <code> <server-url>' to set up.";
|
|
433
609
|
}
|
|
434
|
-
const lines = [
|
|
435
|
-
|
|
436
|
-
const
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
610
|
+
const lines = [];
|
|
611
|
+
for (const name of serverNames) {
|
|
612
|
+
const entry = config.servers[name];
|
|
613
|
+
const active = name === config.activeServer ? " (active)" : "";
|
|
614
|
+
lines.push(` ${name}${active} ${entry.server}`);
|
|
615
|
+
if (entry.sessionExpiresAt) {
|
|
616
|
+
const remaining = entry.sessionExpiresAt - Date.now();
|
|
617
|
+
if (remaining > 0) {
|
|
618
|
+
lines.push(` Session expires in ${Math.floor(remaining / 6e4)} minutes`);
|
|
619
|
+
} else {
|
|
620
|
+
lines.push(" Session expired \u2014 will auto-renew on next command");
|
|
621
|
+
}
|
|
442
622
|
}
|
|
443
623
|
}
|
|
444
|
-
return
|
|
624
|
+
return `Servers:
|
|
625
|
+
${lines.join("\n")}`;
|
|
445
626
|
}
|
|
446
627
|
|
|
447
628
|
// src/index.ts
|
|
448
629
|
var mode = detectMode();
|
|
449
|
-
var
|
|
630
|
+
var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
450
631
|
"pair",
|
|
451
632
|
"connect",
|
|
452
633
|
"list",
|
|
@@ -456,37 +637,36 @@ var CLIENT_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
456
637
|
"download",
|
|
457
638
|
"version",
|
|
458
639
|
"--help",
|
|
459
|
-
"-h"
|
|
640
|
+
"-h",
|
|
641
|
+
"server",
|
|
642
|
+
"wrap",
|
|
643
|
+
"unwrap"
|
|
460
644
|
]);
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
console.log(`MCP tools for ${toolName}:
|
|
645
|
+
var firstArg = process.argv[2];
|
|
646
|
+
if (firstArg && !firstArg.startsWith("-") && !KNOWN_COMMANDS.has(firstArg)) {
|
|
647
|
+
const toolName = firstArg;
|
|
648
|
+
const toolArgs = process.argv.slice(3);
|
|
649
|
+
try {
|
|
650
|
+
const { serverUrl, sessionToken } = await connect();
|
|
651
|
+
const http = createHttpClient(serverUrl, sessionToken);
|
|
652
|
+
const { tool: detail } = await fetchToolDetail(http, toolName);
|
|
653
|
+
if (detail.type === "mcp") {
|
|
654
|
+
const mcpToolName = toolArgs[0];
|
|
655
|
+
if (!mcpToolName) {
|
|
656
|
+
const toolNames = (detail.mcpTools ?? []).map((t) => ` ${t.name} \u2014 ${t.description}`);
|
|
657
|
+
console.log(`MCP tools for ${toolName}:
|
|
475
658
|
${toolNames.join("\n")}`);
|
|
476
|
-
} else {
|
|
477
|
-
const jsonArg = toolArgs.slice(1).join(" ");
|
|
478
|
-
const args = jsonArg ? JSON.parse(jsonArg) : {};
|
|
479
|
-
await execMcpTool(http, toolName, mcpToolName, args);
|
|
480
|
-
}
|
|
481
659
|
} else {
|
|
482
|
-
|
|
660
|
+
const jsonArg = toolArgs.slice(1).join(" ");
|
|
661
|
+
const args = jsonArg ? JSON.parse(jsonArg) : {};
|
|
662
|
+
await execMcpTool(http, toolName, mcpToolName, args);
|
|
483
663
|
}
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
process.exitCode = 1;
|
|
664
|
+
} else {
|
|
665
|
+
await execTool(http, toolName, toolArgs);
|
|
487
666
|
}
|
|
488
|
-
}
|
|
489
|
-
|
|
667
|
+
} catch (e) {
|
|
668
|
+
console.error(`Error: ${e.message}`);
|
|
669
|
+
process.exitCode = 1;
|
|
490
670
|
}
|
|
491
671
|
} else {
|
|
492
672
|
runCitty();
|
|
@@ -504,8 +684,11 @@ function runCitty() {
|
|
|
504
684
|
});
|
|
505
685
|
const connectCommand = defineCommand({
|
|
506
686
|
meta: { name: "connect", description: "Establish a session with the server" },
|
|
507
|
-
|
|
508
|
-
|
|
687
|
+
args: {
|
|
688
|
+
name: { type: "positional", description: "Server name (optional)", required: false }
|
|
689
|
+
},
|
|
690
|
+
async run({ args }) {
|
|
691
|
+
const { serverUrl } = await connect(args.name);
|
|
509
692
|
console.log(`Connected to ${serverUrl}`);
|
|
510
693
|
}
|
|
511
694
|
});
|
|
@@ -534,8 +717,7 @@ function runCitty() {
|
|
|
534
717
|
meta: { name: "status", description: "Show connection status" },
|
|
535
718
|
async run() {
|
|
536
719
|
const config = await readConfig();
|
|
537
|
-
|
|
538
|
-
console.log(formatStatus(connected, config.server, config.sessionExpiresAt));
|
|
720
|
+
console.log(formatStatus(config));
|
|
539
721
|
}
|
|
540
722
|
});
|
|
541
723
|
const uploadCommand = defineCommand({
|
|
@@ -577,7 +759,7 @@ function runCitty() {
|
|
|
577
759
|
dev: { type: "boolean", description: "Development mode (no HTTPS)" }
|
|
578
760
|
},
|
|
579
761
|
async run({ args }) {
|
|
580
|
-
const { startServer } = await import("./dist-
|
|
762
|
+
const { startServer } = await import("./dist-BH5MOHNB.js");
|
|
581
763
|
const extraArgs = [];
|
|
582
764
|
if (args.config) extraArgs.push("--config", args.config);
|
|
583
765
|
if (args.dev) extraArgs.push("--dev");
|
|
@@ -588,7 +770,7 @@ function runCitty() {
|
|
|
588
770
|
init: defineCommand({
|
|
589
771
|
meta: { name: "init", description: "Interactive setup wizard" },
|
|
590
772
|
async run() {
|
|
591
|
-
const { runInit } = await import("./dist-
|
|
773
|
+
const { runInit } = await import("./dist-BH5MOHNB.js");
|
|
592
774
|
await runInit();
|
|
593
775
|
}
|
|
594
776
|
}),
|
|
@@ -598,7 +780,7 @@ function runCitty() {
|
|
|
598
780
|
config: { type: "string", alias: "c", description: "Config file path" }
|
|
599
781
|
},
|
|
600
782
|
async run({ args }) {
|
|
601
|
-
const { generateServerPairingCode } = await import("./dist-
|
|
783
|
+
const { generateServerPairingCode } = await import("./dist-BH5MOHNB.js");
|
|
602
784
|
await generateServerPairingCode(args.config);
|
|
603
785
|
}
|
|
604
786
|
}),
|
|
@@ -608,14 +790,14 @@ function runCitty() {
|
|
|
608
790
|
config: { type: "string", alias: "c", description: "Config file path" }
|
|
609
791
|
},
|
|
610
792
|
async run({ args }) {
|
|
611
|
-
const { installDaemon } = await import("./dist-
|
|
793
|
+
const { installDaemon } = await import("./dist-BH5MOHNB.js");
|
|
612
794
|
await installDaemon(args.config);
|
|
613
795
|
}
|
|
614
796
|
}),
|
|
615
797
|
uninstall: defineCommand({
|
|
616
798
|
meta: { name: "uninstall", description: "Remove background service" },
|
|
617
799
|
async run() {
|
|
618
|
-
const { uninstallDaemon } = await import("./dist-
|
|
800
|
+
const { uninstallDaemon } = await import("./dist-BH5MOHNB.js");
|
|
619
801
|
await uninstallDaemon();
|
|
620
802
|
}
|
|
621
803
|
}),
|
|
@@ -666,21 +848,21 @@ function runCitty() {
|
|
|
666
848
|
cli: defineCommand({
|
|
667
849
|
meta: { name: "cli", description: "Wrap a CLI command" },
|
|
668
850
|
async run() {
|
|
669
|
-
const { wrapCli } = await import("./dist-
|
|
851
|
+
const { wrapCli } = await import("./dist-BH5MOHNB.js");
|
|
670
852
|
await wrapCli();
|
|
671
853
|
}
|
|
672
854
|
}),
|
|
673
855
|
mcp: defineCommand({
|
|
674
856
|
meta: { name: "mcp", description: "Wrap an MCP server" },
|
|
675
857
|
async run() {
|
|
676
|
-
const { wrapMcp } = await import("./dist-
|
|
858
|
+
const { wrapMcp } = await import("./dist-BH5MOHNB.js");
|
|
677
859
|
await wrapMcp();
|
|
678
860
|
}
|
|
679
861
|
}),
|
|
680
862
|
script: defineCommand({
|
|
681
863
|
meta: { name: "script", description: "Wrap a script" },
|
|
682
864
|
async run() {
|
|
683
|
-
const { wrapScript } = await import("./dist-
|
|
865
|
+
const { wrapScript } = await import("./dist-BH5MOHNB.js");
|
|
684
866
|
await wrapScript();
|
|
685
867
|
}
|
|
686
868
|
}),
|
|
@@ -690,7 +872,7 @@ function runCitty() {
|
|
|
690
872
|
path: { type: "positional", description: "Path to config file", required: true }
|
|
691
873
|
},
|
|
692
874
|
async run({ args }) {
|
|
693
|
-
const { wrapImport } = await import("./dist-
|
|
875
|
+
const { wrapImport } = await import("./dist-BH5MOHNB.js");
|
|
694
876
|
await wrapImport(args.path);
|
|
695
877
|
}
|
|
696
878
|
})
|
|
@@ -702,7 +884,7 @@ function runCitty() {
|
|
|
702
884
|
name: { type: "positional", description: "Tool name", required: true }
|
|
703
885
|
},
|
|
704
886
|
async run({ args }) {
|
|
705
|
-
const { unwrapTool } = await import("./dist-
|
|
887
|
+
const { unwrapTool } = await import("./dist-BH5MOHNB.js");
|
|
706
888
|
await unwrapTool(args.name);
|
|
707
889
|
}
|
|
708
890
|
});
|