@schuttdev/gigai 0.2.5 → 0.2.7

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
@@ -1,985 +1,177 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- decodeJWTPayload
4
- } from "./chunk-7C3UYEKE.js";
5
2
 
6
3
  // src/index.ts
7
4
  import { defineCommand, runMain } from "citty";
8
5
 
9
- // src/mode.ts
10
- function detectMode() {
11
- if (process.env.GIGAI_MODE === "client") return "client";
12
- if (process.env.GIGAI_MODE === "server") return "server";
13
- if (process.env.ANTHROPIC_API_KEY || process.env.ANTHROPIC_PROXY_URL || process.env.CLAUDE_CODE_EXEC) {
14
- return "client";
15
- }
16
- return "server";
17
- }
6
+ // src/version.ts
7
+ var VERSION = "0.2.7";
18
8
 
19
- // src/config.ts
20
- import { readFile, writeFile, mkdir } from "fs/promises";
21
- import { join } from "path";
22
- import { homedir } from "os";
23
- function getConfigDir() {
24
- return process.env.GIGAI_CONFIG_DIR ?? join(homedir(), ".gigai");
25
- }
26
- function getConfigPath() {
27
- return join(getConfigDir(), "config.json");
28
- }
29
- async function readConfig() {
30
- try {
31
- const raw = await readFile(getConfigPath(), "utf8");
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 ?? {} };
50
- } catch {
51
- return { servers: {} };
52
- }
53
- }
54
- async function writeConfig(config) {
55
- const dir = getConfigDir();
56
- await mkdir(dir, { recursive: true });
57
- await writeFile(getConfigPath(), JSON.stringify(config, null, 2) + "\n", { mode: 384 });
58
- }
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) {
78
- const config = await readConfig();
79
- const entry = config.servers[name];
80
- if (!entry) return;
81
- entry.sessionToken = sessionToken;
82
- entry.sessionExpiresAt = sessionExpiresAt;
83
- await writeConfig(config);
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) {
9
+ // src/index.ts
10
+ async function requireServer() {
94
11
  try {
95
- const u = new URL(url);
96
- return u.hostname.toLowerCase();
12
+ return await import("./dist-MGLETZTT.js");
97
13
  } catch {
98
- return url.toLowerCase();
99
- }
100
- }
101
-
102
- // src/identity.ts
103
- function getOrgUUID() {
104
- if (process.env.GIGAI_ORG_UUID) {
105
- return process.env.GIGAI_ORG_UUID;
106
- }
107
- const proxyUrl = process.env.HTTPS_PROXY || process.env.HTTP_PROXY || "";
108
- const jwtMatch = proxyUrl.match(/jwt_([^@]+)/);
109
- if (jwtMatch) {
110
- try {
111
- const payload = decodeJWTPayload(jwtMatch[1]);
112
- if (payload.organization_uuid) {
113
- return payload.organization_uuid;
14
+ console.error("Server dependencies not installed.");
15
+ console.error("Run: npm install -g @schuttdev/gigai");
16
+ process.exit(1);
17
+ }
18
+ }
19
+ var serverCommand = defineCommand({
20
+ meta: { name: "server", description: "Server management commands" },
21
+ subCommands: {
22
+ start: defineCommand({
23
+ meta: { name: "start", description: "Start the gigai server" },
24
+ args: {
25
+ config: { type: "string", alias: "c", description: "Config file path" },
26
+ dev: { type: "boolean", description: "Development mode (no HTTPS)" }
27
+ },
28
+ async run({ args }) {
29
+ const { startServer } = await requireServer();
30
+ const extraArgs = [];
31
+ if (args.config) extraArgs.push("--config", args.config);
32
+ if (args.dev) extraArgs.push("--dev");
33
+ process.argv.push(...extraArgs);
34
+ await startServer();
114
35
  }
115
- } catch {
116
- }
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;
36
+ }),
37
+ init: defineCommand({
38
+ meta: { name: "init", description: "Interactive setup wizard" },
39
+ async run() {
40
+ const { runInit } = await requireServer();
41
+ await runInit();
125
42
  }
126
- } catch {
127
- }
128
- }
129
- const apiKey = process.env.ANTHROPIC_API_KEY ?? "";
130
- if (apiKey.includes(".")) {
131
- try {
132
- const payload = decodeJWTPayload(apiKey);
133
- if (payload.organization_uuid) {
134
- return payload.organization_uuid;
43
+ }),
44
+ pair: defineCommand({
45
+ meta: { name: "pair", description: "Generate a pairing code" },
46
+ args: {
47
+ config: { type: "string", alias: "c", description: "Config file path" }
48
+ },
49
+ async run({ args }) {
50
+ const { generateServerPairingCode } = await requireServer();
51
+ await generateServerPairingCode(args.config);
135
52
  }
136
- } catch {
137
- }
138
- }
139
- throw new Error(
140
- "Cannot determine organization UUID. Set GIGAI_ORG_UUID environment variable."
141
- );
142
- }
143
-
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
- }
162
- function createHttpClient(serverUrl, sessionToken) {
163
- const baseUrl = serverUrl.replace(/\/$/, "");
164
- async function request(path, init = {}) {
165
- const headers = {
166
- ...init.headers ?? {}
167
- };
168
- if (sessionToken) {
169
- headers["Authorization"] = `Bearer ${sessionToken}`;
170
- }
171
- if (!headers["Content-Type"] && init.body && typeof init.body === "string") {
172
- headers["Content-Type"] = "application/json";
173
- }
174
- const dispatcher = await ensureDispatcher();
175
- const fetchOpts = {
176
- ...init,
177
- headers
178
- };
179
- if (dispatcher) {
180
- fetchOpts.dispatcher = dispatcher;
181
- }
182
- const res = await fetch(`${baseUrl}${path}`, fetchOpts);
183
- if (!res.ok) {
184
- let errorBody;
185
- try {
186
- errorBody = await res.json();
187
- } catch {
188
- }
189
- const message = errorBody?.error?.message ?? `HTTP ${res.status}: ${res.statusText}`;
190
- throw new Error(message);
191
- }
192
- return res.json();
193
- }
194
- return {
195
- get(path) {
196
- return request(path);
197
- },
198
- post(path, body) {
199
- return request(path, {
200
- method: "POST",
201
- body: body ? JSON.stringify(body) : void 0
202
- });
203
- },
204
- async postMultipart(path, formData) {
205
- const headers = {};
206
- if (sessionToken) {
207
- headers["Authorization"] = `Bearer ${sessionToken}`;
53
+ }),
54
+ install: defineCommand({
55
+ meta: { name: "install", description: "Install as persistent background service" },
56
+ args: {
57
+ config: { type: "string", alias: "c", description: "Config file path" }
58
+ },
59
+ async run({ args }) {
60
+ const { installDaemon } = await requireServer();
61
+ await installDaemon(args.config);
208
62
  }
209
- const dispatcher = await ensureDispatcher();
210
- const fetchOpts = {
211
- method: "POST",
212
- headers,
213
- body: formData
214
- };
215
- if (dispatcher) {
216
- fetchOpts.dispatcher = dispatcher;
63
+ }),
64
+ uninstall: defineCommand({
65
+ meta: { name: "uninstall", description: "Remove background service" },
66
+ async run() {
67
+ const { uninstallDaemon } = await requireServer();
68
+ await uninstallDaemon();
217
69
  }
218
- const res = await fetch(`${baseUrl}${path}`, fetchOpts);
219
- if (!res.ok) {
220
- let errorBody;
70
+ }),
71
+ stop: defineCommand({
72
+ meta: { name: "stop", description: "Stop the running gigai server" },
73
+ async run() {
74
+ const { execFileSync } = await import("child_process");
75
+ let pids = [];
221
76
  try {
222
- errorBody = await res.json();
77
+ const out = execFileSync("pgrep", ["-f", "gigai server start"], { encoding: "utf8" });
78
+ pids = out.trim().split("\n").map(Number).filter((pid) => pid && pid !== process.pid);
223
79
  } catch {
224
80
  }
225
- throw new Error(errorBody?.error?.message ?? `HTTP ${res.status}`);
226
- }
227
- return res.json();
228
- },
229
- async getRaw(path) {
230
- const headers = {};
231
- if (sessionToken) {
232
- headers["Authorization"] = `Bearer ${sessionToken}`;
81
+ if (pids.length === 0) {
82
+ console.log("No running gigai server found.");
83
+ return;
84
+ }
85
+ for (const pid of pids) {
86
+ try {
87
+ process.kill(pid, "SIGTERM");
88
+ console.log(`Stopped gigai server (PID ${pid})`);
89
+ } catch (e) {
90
+ console.error(`Failed to stop PID ${pid}: ${e.message}`);
91
+ }
92
+ }
233
93
  }
234
- const dispatcher = await ensureDispatcher();
235
- const fetchOpts = { headers };
236
- if (dispatcher) {
237
- fetchOpts.dispatcher = dispatcher;
94
+ }),
95
+ status: defineCommand({
96
+ meta: { name: "status", description: "Show server status" },
97
+ async run() {
98
+ console.log("Server status: checking...");
99
+ try {
100
+ const res = await fetch("http://localhost:7443/health");
101
+ const data = await res.json();
102
+ console.log(`Status: ${data.status}`);
103
+ console.log(`Version: ${data.version}`);
104
+ console.log(`Uptime: ${Math.floor(data.uptime / 1e3)}s`);
105
+ } catch {
106
+ console.log("Server is not running.");
107
+ }
238
108
  }
239
- const res = await fetch(`${baseUrl}${path}`, fetchOpts);
240
- if (!res.ok) {
241
- throw new Error(`HTTP ${res.status}: ${res.statusText}`);
109
+ })
110
+ }
111
+ });
112
+ var wrapCommand = defineCommand({
113
+ meta: { name: "wrap", description: "Register a tool" },
114
+ subCommands: {
115
+ cli: defineCommand({
116
+ meta: { name: "cli", description: "Wrap a CLI command" },
117
+ async run() {
118
+ const { wrapCli } = await requireServer();
119
+ await wrapCli();
242
120
  }
243
- return res;
244
- }
245
- };
246
- }
247
-
248
- // src/version.ts
249
- var VERSION = "0.2.5";
250
-
251
- // src/connect.ts
252
- async function connect(serverName) {
253
- const config = await readConfig();
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);
263
- }
264
- const active = getActiveEntry(config);
265
- if (!active) {
266
- throw new Error("No server configured. Run 'gigai pair' first.");
267
- }
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 };
273
- }
274
- }
275
- const orgUuid = getOrgUUID();
276
- const http = createHttpClient(entry.server);
277
- const res = await http.post("/auth/connect", {
278
- encryptedToken: entry.token,
279
- orgUuid
280
- });
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"}`);
121
+ }),
122
+ mcp: defineCommand({
123
+ meta: { name: "mcp", description: "Wrap an MCP server" },
124
+ async run() {
125
+ const { wrapMcp } = await requireServer();
126
+ await wrapMcp();
299
127
  }
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 hasExistingSkill() {
401
- try {
402
- await readFile2("/mnt/skills/user/gigai/config.json", "utf8");
403
- return true;
404
- } catch {
405
- return false;
406
- }
407
- }
408
- async function generateSkillZip(serverName, serverUrl, token) {
409
- let skillConfig = { servers: {} };
410
- try {
411
- const raw = await readFile2("/mnt/skills/user/gigai/config.json", "utf8");
412
- const existing = JSON.parse(raw);
413
- if (existing.servers) {
414
- skillConfig = existing;
415
- }
416
- } catch {
417
- }
418
- let merged = false;
419
- for (const [name, entry] of Object.entries(skillConfig.servers)) {
420
- if (normalizeHost(entry.server) === normalizeHost(serverUrl)) {
421
- skillConfig.servers[name] = { server: serverUrl, token };
422
- skillConfig.activeServer = name;
423
- merged = true;
424
- break;
425
- }
426
- }
427
- if (!merged) {
428
- skillConfig.servers[serverName] = { server: serverUrl, token };
429
- skillConfig.activeServer = serverName;
430
- }
431
- const configJson = JSON.stringify(skillConfig, null, 2) + "\n";
432
- return createZip([
433
- { path: "gigai/SKILL.md", data: Buffer.from(SKILL_MD, "utf8") },
434
- { path: "gigai/config.json", data: Buffer.from(configJson, "utf8") }
435
- ]);
436
- }
437
- async function writeSkillZip(zip) {
438
- const outputsDir = "/mnt/user-data/outputs";
439
- try {
440
- await mkdir2(outputsDir, { recursive: true });
441
- const outPath = `${outputsDir}/gigai.zip`;
442
- await writeFile2(outPath, zip);
443
- return outPath;
444
- } catch {
445
- const outPath = "gigai.zip";
446
- await writeFile2(outPath, zip);
447
- return outPath;
448
- }
449
- }
450
- function normalizeHost(url) {
451
- try {
452
- return new URL(url).hostname.toLowerCase();
453
- } catch {
454
- return url.toLowerCase();
455
- }
456
- }
457
- var crc32Table = new Uint32Array(256);
458
- for (let i = 0; i < 256; i++) {
459
- let c = i;
460
- for (let j = 0; j < 8; j++) {
461
- c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
462
- }
463
- crc32Table[i] = c >>> 0;
464
- }
465
- function crc32(data) {
466
- let crc = 4294967295;
467
- for (const byte of data) {
468
- crc = crc >>> 8 ^ crc32Table[(crc ^ byte) & 255];
469
- }
470
- return (crc ^ 4294967295) >>> 0;
471
- }
472
- function createZip(entries) {
473
- const parts = [];
474
- const centralParts = [];
475
- let offset = 0;
476
- for (const entry of entries) {
477
- const name = Buffer.from(entry.path, "utf8");
478
- const checksum = crc32(entry.data);
479
- const local = Buffer.alloc(30);
480
- local.writeUInt32LE(67324752, 0);
481
- local.writeUInt16LE(20, 4);
482
- local.writeUInt16LE(0, 6);
483
- local.writeUInt16LE(0, 8);
484
- local.writeUInt16LE(0, 10);
485
- local.writeUInt16LE(0, 12);
486
- local.writeUInt32LE(checksum, 14);
487
- local.writeUInt32LE(entry.data.length, 18);
488
- local.writeUInt32LE(entry.data.length, 22);
489
- local.writeUInt16LE(name.length, 26);
490
- local.writeUInt16LE(0, 28);
491
- parts.push(local, name, entry.data);
492
- const central = Buffer.alloc(46);
493
- central.writeUInt32LE(33639248, 0);
494
- central.writeUInt16LE(20, 4);
495
- central.writeUInt16LE(20, 6);
496
- central.writeUInt16LE(0, 8);
497
- central.writeUInt16LE(0, 10);
498
- central.writeUInt16LE(0, 12);
499
- central.writeUInt16LE(0, 14);
500
- central.writeUInt32LE(checksum, 16);
501
- central.writeUInt32LE(entry.data.length, 20);
502
- central.writeUInt32LE(entry.data.length, 24);
503
- central.writeUInt16LE(name.length, 28);
504
- central.writeUInt16LE(0, 30);
505
- central.writeUInt16LE(0, 32);
506
- central.writeUInt16LE(0, 34);
507
- central.writeUInt16LE(0, 36);
508
- central.writeUInt32LE(0, 38);
509
- central.writeUInt32LE(offset, 42);
510
- centralParts.push(central, name);
511
- offset += 30 + name.length + entry.data.length;
512
- }
513
- const centralDir = Buffer.concat(centralParts);
514
- const eocd = Buffer.alloc(22);
515
- eocd.writeUInt32LE(101010256, 0);
516
- eocd.writeUInt16LE(0, 4);
517
- eocd.writeUInt16LE(0, 6);
518
- eocd.writeUInt16LE(entries.length, 8);
519
- eocd.writeUInt16LE(entries.length, 10);
520
- eocd.writeUInt32LE(centralDir.length, 12);
521
- eocd.writeUInt32LE(offset, 16);
522
- eocd.writeUInt16LE(0, 20);
523
- return Buffer.concat([...parts, centralDir, eocd]);
524
- }
525
-
526
- // src/pair.ts
527
- async function pair(code, serverUrl) {
528
- const orgUuid = getOrgUUID();
529
- const http = createHttpClient(serverUrl);
530
- const res = await http.post("/auth/pair", {
531
- pairingCode: code,
532
- orgUuid
533
- });
534
- await addServer(res.serverName, serverUrl, res.encryptedToken);
535
- console.log(`Paired with "${res.serverName}" successfully!`);
536
- const existing = await hasExistingSkill();
537
- const zip = await generateSkillZip(res.serverName, serverUrl, res.encryptedToken);
538
- const outPath = await writeSkillZip(zip);
539
- console.log(`
540
- Skill zip written to: ${outPath}`);
541
- if (existing) {
542
- console.log("Skill file updated. Download and re-upload to Claude.");
543
- } else {
544
- console.log("Upload this file as a skill in Claude (Settings \u2192 Customize \u2192 Upload Skill).");
545
- }
546
- }
547
-
548
- // src/discover.ts
549
- import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
550
- import { join as join2 } from "path";
551
- import { homedir as homedir2 } from "os";
552
- var MANIFEST_TTL = 5 * 60 * 1e3;
553
- function getManifestPath() {
554
- const dir = process.env.GIGAI_CONFIG_DIR ?? join2(homedir2(), ".gigai");
555
- return join2(dir, "tool-manifest.json");
556
- }
557
- async function fetchTools(http) {
558
- try {
559
- const raw = await readFile3(getManifestPath(), "utf8");
560
- const cache = JSON.parse(raw);
561
- if (Date.now() - cache.fetchedAt < MANIFEST_TTL) {
562
- return cache.tools;
563
- }
564
- } catch {
565
- }
566
- const res = await http.get("/tools");
567
- try {
568
- const dir = process.env.GIGAI_CONFIG_DIR ?? join2(homedir2(), ".gigai");
569
- await mkdir3(dir, { recursive: true });
570
- const cache = { tools: res.tools, fetchedAt: Date.now() };
571
- await writeFile3(getManifestPath(), JSON.stringify(cache));
572
- } catch {
573
- }
574
- return res.tools;
575
- }
576
- async function fetchToolDetail(http, name) {
577
- return http.get(`/tools/${encodeURIComponent(name)}`);
578
- }
579
-
580
- // src/exec.ts
581
- async function execTool(http, name, args, timeout) {
582
- const res = await http.post("/exec", {
583
- tool: name,
584
- args,
585
- timeout
586
- });
587
- if (res.stdout) process.stdout.write(res.stdout);
588
- if (res.stderr) process.stderr.write(res.stderr);
589
- process.exitCode = res.exitCode;
590
- }
591
- async function execMcpTool(http, tool, mcpTool, args) {
592
- const res = await http.post("/exec/mcp", {
593
- tool,
594
- mcpTool,
595
- args
596
- });
597
- for (const content of res.content) {
598
- if (content.type === "text" && content.text) {
599
- process.stdout.write(content.text + "\n");
600
- } else if (content.type === "image") {
601
- console.log(`[Image: ${content.mimeType}]`);
602
- } else if (content.type === "resource") {
603
- console.log(`[Resource: ${content.mimeType}]`);
604
- }
605
- }
606
- if (res.isError) {
607
- process.exitCode = 1;
608
- }
609
- }
610
-
611
- // src/transfer.ts
612
- import { readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
613
- import { basename } from "path";
614
- import { Blob } from "buffer";
615
- async function upload(http, filePath) {
616
- const content = await readFile4(filePath);
617
- const filename = basename(filePath);
618
- const formData = new FormData();
619
- const blob = new Blob([content]);
620
- formData.append("file", blob, filename);
621
- const res = await http.postMultipart(
622
- "/transfer/upload",
623
- formData
624
- );
625
- console.log(`Uploaded: ${res.id}`);
626
- return res.id;
627
- }
628
- async function download(http, id, destPath) {
629
- const res = await http.getRaw(`/transfer/${encodeURIComponent(id)}`);
630
- const buffer = Buffer.from(await res.arrayBuffer());
631
- await writeFile4(destPath, buffer);
632
- console.log(`Downloaded to: ${destPath}`);
633
- }
634
-
635
- // src/output.ts
636
- function formatToolList(tools) {
637
- if (tools.length === 0) return "No tools registered.";
638
- const maxName = Math.max(...tools.map((t) => t.name.length));
639
- const maxType = Math.max(...tools.map((t) => t.type.length));
640
- const lines = tools.map((t) => {
641
- const name = t.name.padEnd(maxName);
642
- const type = t.type.padEnd(maxType);
643
- return ` ${name} ${type} ${t.description}`;
644
- });
645
- return `Available tools:
646
- ${lines.join("\n")}`;
647
- }
648
- function formatToolDetail(detail) {
649
- const lines = [];
650
- lines.push(`${detail.name} (${detail.type})`);
651
- lines.push(` ${detail.description}`);
652
- if (detail.usage) {
653
- lines.push(`
654
- Usage: ${detail.usage}`);
655
- }
656
- if (detail.args?.length) {
657
- lines.push("\nArguments:");
658
- for (const arg of detail.args) {
659
- const req = arg.required ? " (required)" : "";
660
- const def = arg.default ? ` [default: ${arg.default}]` : "";
661
- lines.push(` ${arg.name}${req}${def} \u2014 ${arg.description}`);
662
- }
663
- }
664
- if (detail.mcpTools?.length) {
665
- lines.push("\nMCP Tools:");
666
- for (const t of detail.mcpTools) {
667
- lines.push(` ${t.name} \u2014 ${t.description}`);
668
- }
669
- }
670
- return lines.join("\n");
671
- }
672
- function formatStatus(config) {
673
- const serverNames = Object.keys(config.servers);
674
- if (serverNames.length === 0) {
675
- return "Not connected. Run 'gigai pair <code> <server-url>' to set up.";
676
- }
677
- const lines = [];
678
- for (const name of serverNames) {
679
- const entry = config.servers[name];
680
- const active = name === config.activeServer ? " (active)" : "";
681
- lines.push(` ${name}${active} ${entry.server}`);
682
- if (entry.sessionExpiresAt) {
683
- const remaining = entry.sessionExpiresAt - Date.now();
684
- if (remaining > 0) {
685
- lines.push(` Session expires in ${Math.floor(remaining / 6e4)} minutes`);
686
- } else {
687
- lines.push(" Session expired \u2014 will auto-renew on next command");
128
+ }),
129
+ script: defineCommand({
130
+ meta: { name: "script", description: "Wrap a script" },
131
+ async run() {
132
+ const { wrapScript } = await requireServer();
133
+ await wrapScript();
688
134
  }
689
- }
690
- }
691
- return `Servers:
692
- ${lines.join("\n")}`;
693
- }
694
-
695
- // src/index.ts
696
- var mode = detectMode();
697
- var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
698
- "pair",
699
- "connect",
700
- "list",
701
- "help",
702
- "status",
703
- "upload",
704
- "download",
705
- "version",
706
- "--help",
707
- "-h",
708
- "server",
709
- "wrap",
710
- "unwrap"
711
- ]);
712
- var firstArg = process.argv[2];
713
- if (firstArg && !firstArg.startsWith("-") && !KNOWN_COMMANDS.has(firstArg)) {
714
- const toolName = firstArg;
715
- const toolArgs = process.argv.slice(3);
716
- try {
717
- const { serverUrl, sessionToken } = await connect();
718
- const http = createHttpClient(serverUrl, sessionToken);
719
- const { tool: detail } = await fetchToolDetail(http, toolName);
720
- if (detail.type === "mcp") {
721
- const mcpToolName = toolArgs[0];
722
- if (!mcpToolName) {
723
- const toolNames = (detail.mcpTools ?? []).map((t) => ` ${t.name} \u2014 ${t.description}`);
724
- console.log(`MCP tools for ${toolName}:
725
- ${toolNames.join("\n")}`);
726
- } else {
727
- const jsonArg = toolArgs.slice(1).join(" ");
728
- const args = jsonArg ? JSON.parse(jsonArg) : {};
729
- await execMcpTool(http, toolName, mcpToolName, args);
135
+ }),
136
+ import: defineCommand({
137
+ meta: { name: "import", description: "Import from claude_desktop_config.json" },
138
+ args: {
139
+ path: { type: "positional", description: "Path to config file", required: true }
140
+ },
141
+ async run({ args }) {
142
+ const { wrapImport } = await requireServer();
143
+ await wrapImport(args.path);
730
144
  }
731
- } else {
732
- await execTool(http, toolName, toolArgs);
733
- }
734
- } catch (e) {
735
- console.error(`Error: ${e.message}`);
736
- process.exitCode = 1;
737
- }
738
- } else {
739
- runCitty();
740
- }
741
- function runCitty() {
742
- const pairCommand = defineCommand({
743
- meta: { name: "pair", description: "Pair with a gigai server" },
744
- args: {
745
- code: { type: "positional", description: "Pairing code", required: true },
746
- server: { type: "positional", description: "Server URL", required: true }
747
- },
748
- async run({ args }) {
749
- await pair(args.code, args.server);
750
- }
751
- });
752
- const connectCommand = defineCommand({
753
- meta: { name: "connect", description: "Establish a session with the server" },
754
- args: {
755
- name: { type: "positional", description: "Server name (optional)", required: false }
756
- },
757
- async run({ args }) {
758
- const { serverUrl } = await connect(args.name);
759
- console.log(`Connected to ${serverUrl}`);
760
- }
761
- });
762
- const listCommand = defineCommand({
763
- meta: { name: "list", description: "List available tools" },
764
- async run() {
765
- const { serverUrl, sessionToken } = await connect();
766
- const http = createHttpClient(serverUrl, sessionToken);
767
- const tools = await fetchTools(http);
768
- console.log(formatToolList(tools));
769
- }
770
- });
771
- const helpCommand = defineCommand({
772
- meta: { name: "help", description: "Show help for a tool" },
773
- args: {
774
- tool: { type: "positional", description: "Tool name", required: true }
775
- },
776
- async run({ args }) {
777
- const { serverUrl, sessionToken } = await connect();
778
- const http = createHttpClient(serverUrl, sessionToken);
779
- const { tool } = await fetchToolDetail(http, args.tool);
780
- console.log(formatToolDetail(tool));
781
- }
782
- });
783
- const statusCommand = defineCommand({
784
- meta: { name: "status", description: "Show connection status" },
785
- async run() {
786
- const config = await readConfig();
787
- console.log(formatStatus(config));
788
- }
789
- });
790
- const uploadCommand = defineCommand({
791
- meta: { name: "upload", description: "Upload a file to the server" },
792
- args: {
793
- file: { type: "positional", description: "File path", required: true }
794
- },
795
- async run({ args }) {
796
- const { serverUrl, sessionToken } = await connect();
797
- const http = createHttpClient(serverUrl, sessionToken);
798
- await upload(http, args.file);
799
- }
800
- });
801
- const downloadCommand = defineCommand({
802
- meta: { name: "download", description: "Download a file from the server" },
803
- args: {
804
- id: { type: "positional", description: "Transfer ID", required: true },
805
- dest: { type: "positional", description: "Destination path", required: true }
806
- },
807
- async run({ args }) {
808
- const { serverUrl, sessionToken } = await connect();
809
- const http = createHttpClient(serverUrl, sessionToken);
810
- await download(http, args.id, args.dest);
811
- }
812
- });
813
- const versionCommand = defineCommand({
814
- meta: { name: "version", description: "Show version" },
815
- run() {
816
- console.log(`gigai v${VERSION}`);
817
- }
818
- });
819
- const serverCommand = defineCommand({
820
- meta: { name: "server", description: "Server management commands" },
821
- subCommands: {
822
- start: defineCommand({
823
- meta: { name: "start", description: "Start the gigai server" },
824
- args: {
825
- config: { type: "string", alias: "c", description: "Config file path" },
826
- dev: { type: "boolean", description: "Development mode (no HTTPS)" }
827
- },
828
- async run({ args }) {
829
- const { startServer } = await import("./dist-G2KJBUZX.js");
830
- const extraArgs = [];
831
- if (args.config) extraArgs.push("--config", args.config);
832
- if (args.dev) extraArgs.push("--dev");
833
- process.argv.push(...extraArgs);
834
- await startServer();
835
- }
836
- }),
837
- init: defineCommand({
838
- meta: { name: "init", description: "Interactive setup wizard" },
839
- async run() {
840
- const { runInit } = await import("./dist-G2KJBUZX.js");
841
- await runInit();
842
- }
843
- }),
844
- pair: defineCommand({
845
- meta: { name: "pair", description: "Generate a pairing code" },
846
- args: {
847
- config: { type: "string", alias: "c", description: "Config file path" }
848
- },
849
- async run({ args }) {
850
- const { generateServerPairingCode } = await import("./dist-G2KJBUZX.js");
851
- await generateServerPairingCode(args.config);
852
- }
853
- }),
854
- install: defineCommand({
855
- meta: { name: "install", description: "Install as persistent background service" },
856
- args: {
857
- config: { type: "string", alias: "c", description: "Config file path" }
858
- },
859
- async run({ args }) {
860
- const { installDaemon } = await import("./dist-G2KJBUZX.js");
861
- await installDaemon(args.config);
862
- }
863
- }),
864
- uninstall: defineCommand({
865
- meta: { name: "uninstall", description: "Remove background service" },
866
- async run() {
867
- const { uninstallDaemon } = await import("./dist-G2KJBUZX.js");
868
- await uninstallDaemon();
869
- }
870
- }),
871
- stop: defineCommand({
872
- meta: { name: "stop", description: "Stop the running gigai server" },
873
- async run() {
874
- const { execFileSync } = await import("child_process");
875
- let pids = [];
876
- try {
877
- const out = execFileSync("pgrep", ["-f", "gigai server start"], { encoding: "utf8" });
878
- pids = out.trim().split("\n").map(Number).filter((pid) => pid && pid !== process.pid);
879
- } catch {
880
- }
881
- if (pids.length === 0) {
882
- console.log("No running gigai server found.");
883
- return;
884
- }
885
- for (const pid of pids) {
886
- try {
887
- process.kill(pid, "SIGTERM");
888
- console.log(`Stopped gigai server (PID ${pid})`);
889
- } catch (e) {
890
- console.error(`Failed to stop PID ${pid}: ${e.message}`);
891
- }
892
- }
893
- }
894
- }),
895
- status: defineCommand({
896
- meta: { name: "status", description: "Show server status" },
897
- async run() {
898
- console.log("Server status: checking...");
899
- try {
900
- const res = await fetch("http://localhost:7443/health");
901
- const data = await res.json();
902
- console.log(`Status: ${data.status}`);
903
- console.log(`Version: ${data.version}`);
904
- console.log(`Uptime: ${Math.floor(data.uptime / 1e3)}s`);
905
- } catch {
906
- console.log("Server is not running.");
907
- }
908
- }
909
- })
910
- }
911
- });
912
- const wrapCommand = defineCommand({
913
- meta: { name: "wrap", description: "Register a tool" },
914
- subCommands: {
915
- cli: defineCommand({
916
- meta: { name: "cli", description: "Wrap a CLI command" },
917
- async run() {
918
- const { wrapCli } = await import("./dist-G2KJBUZX.js");
919
- await wrapCli();
920
- }
921
- }),
922
- mcp: defineCommand({
923
- meta: { name: "mcp", description: "Wrap an MCP server" },
924
- async run() {
925
- const { wrapMcp } = await import("./dist-G2KJBUZX.js");
926
- await wrapMcp();
927
- }
928
- }),
929
- script: defineCommand({
930
- meta: { name: "script", description: "Wrap a script" },
931
- async run() {
932
- const { wrapScript } = await import("./dist-G2KJBUZX.js");
933
- await wrapScript();
934
- }
935
- }),
936
- import: defineCommand({
937
- meta: { name: "import", description: "Import from claude_desktop_config.json" },
938
- args: {
939
- path: { type: "positional", description: "Path to config file", required: true }
940
- },
941
- async run({ args }) {
942
- const { wrapImport } = await import("./dist-G2KJBUZX.js");
943
- await wrapImport(args.path);
944
- }
945
- })
946
- }
947
- });
948
- const unwrapCommand = defineCommand({
949
- meta: { name: "unwrap", description: "Unregister a tool" },
950
- args: {
951
- name: { type: "positional", description: "Tool name", required: true }
952
- },
953
- async run({ args }) {
954
- const { unwrapTool } = await import("./dist-G2KJBUZX.js");
955
- await unwrapTool(args.name);
956
- }
957
- });
958
- const main = defineCommand({
959
- meta: {
960
- name: "gigai",
961
- version: VERSION,
962
- description: "Bridge CLI tools to Claude across platforms"
963
- },
964
- subCommands: mode === "client" ? {
965
- pair: pairCommand,
966
- connect: connectCommand,
967
- list: listCommand,
968
- help: helpCommand,
969
- status: statusCommand,
970
- upload: uploadCommand,
971
- download: downloadCommand,
972
- version: versionCommand
973
- } : {
974
- server: serverCommand,
975
- wrap: wrapCommand,
976
- unwrap: unwrapCommand,
977
- version: versionCommand,
978
- pair: pairCommand,
979
- connect: connectCommand,
980
- list: listCommand,
981
- status: statusCommand
982
- }
983
- });
984
- runMain(main);
985
- }
145
+ })
146
+ }
147
+ });
148
+ var unwrapCommand = defineCommand({
149
+ meta: { name: "unwrap", description: "Unregister a tool" },
150
+ args: {
151
+ name: { type: "positional", description: "Tool name", required: true }
152
+ },
153
+ async run({ args }) {
154
+ const { unwrapTool } = await requireServer();
155
+ await unwrapTool(args.name);
156
+ }
157
+ });
158
+ var versionCommand = defineCommand({
159
+ meta: { name: "version", description: "Show version" },
160
+ run() {
161
+ console.log(`gigai v${VERSION}`);
162
+ }
163
+ });
164
+ var main = defineCommand({
165
+ meta: {
166
+ name: "gigai",
167
+ version: VERSION,
168
+ description: "gigai server \u2014 bridge CLI tools to Claude"
169
+ },
170
+ subCommands: {
171
+ server: serverCommand,
172
+ wrap: wrapCommand,
173
+ unwrap: unwrapCommand,
174
+ version: versionCommand
175
+ }
176
+ });
177
+ runMain(main);