@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/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  decodeJWTPayload
4
- } from "./chunk-4XUWD3DZ.js";
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
- return JSON.parse(raw);
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
- async function updateConfig(updates) {
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
- Object.assign(config, updates);
79
+ const entry = config.servers[name];
80
+ if (!entry) return;
81
+ entry.sessionToken = sessionToken;
82
+ entry.sessionExpiresAt = sessionExpiresAt;
45
83
  await writeConfig(config);
46
- return 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) {
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.ANTHROPIC_PROXY_URL ?? "";
55
- const jwtMatch = proxyUrl.match(/\/([A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+)/);
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
- const parts = apiKey.split("/");
68
- const jwtPart = parts.find((p) => p.includes(".") && p.split(".").length === 3);
69
- if (jwtPart) {
70
- try {
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 res = await fetch(`${baseUrl}${path}`, {
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 res = await fetch(`${baseUrl}${path}`, {
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 res = await fetch(`${baseUrl}${path}`, { headers });
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 (!config.server) {
160
- throw new Error("No server configured. Run 'gigai pair' first.");
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
- if (!config.token) {
163
- throw new Error("No auth token found. Run 'gigai pair' first.");
264
+ const active = getActiveEntry(config);
265
+ if (!active) {
266
+ throw new Error("No server configured. Run 'gigai pair' first.");
164
267
  }
165
- if (config.sessionToken && config.sessionExpiresAt) {
166
- if (Date.now() < config.sessionExpiresAt - 5 * 60 * 1e3) {
167
- return { serverUrl: config.server, sessionToken: config.sessionToken };
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(config.server);
276
+ const http = createHttpClient(entry.server);
172
277
  const res = await http.post("/auth/connect", {
173
- encryptedToken: config.token,
278
+ encryptedToken: entry.token,
174
279
  orgUuid
175
280
  });
176
- await updateConfig({
177
- sessionToken: res.sessionToken,
178
- sessionExpiresAt: res.expiresAt
179
- });
180
- return { serverUrl: config.server, sessionToken: res.sessionToken };
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 updateConfig({
192
- server: serverUrl,
193
- token: res.encryptedToken,
194
- sessionToken: void 0,
195
- sessionExpiresAt: void 0
196
- });
197
- console.log("Paired successfully!");
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 readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
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 readFile2(getManifestPath(), "utf8");
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 mkdir2(dir, { recursive: true });
556
+ await mkdir3(dir, { recursive: true });
222
557
  const cache = { tools: res.tools, fetchedAt: Date.now() };
223
- await writeFile2(getManifestPath(), JSON.stringify(cache));
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 readFile3, writeFile as writeFile3 } from "fs/promises";
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 readFile3(filePath);
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 writeFile3(destPath, buffer);
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(connected, serverUrl, sessionExpiresAt) {
306
- if (!connected) {
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 = [`Connected to: ${serverUrl}`];
310
- if (sessionExpiresAt) {
311
- const remaining = sessionExpiresAt - Date.now();
312
- if (remaining > 0) {
313
- const mins = Math.floor(remaining / 6e4);
314
- lines.push(`Session expires in: ${mins} minutes`);
315
- } else {
316
- lines.push("Session expired. Will auto-renew on next command.");
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 lines.join("\n");
678
+ return `Servers:
679
+ ${lines.join("\n")}`;
320
680
  }
321
681
 
322
682
  // src/index.ts
323
683
  var mode = detectMode();
324
- var CLIENT_COMMANDS = /* @__PURE__ */ new Set([
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
- if (mode === "client") {
337
- const firstArg = process.argv[2];
338
- if (firstArg && !firstArg.startsWith("-") && !CLIENT_COMMANDS.has(firstArg)) {
339
- const toolName = firstArg;
340
- const toolArgs = process.argv.slice(3);
341
- try {
342
- const { serverUrl, sessionToken } = await connect();
343
- const http = createHttpClient(serverUrl, sessionToken);
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
- } else {
350
- runCitty();
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
- async run() {
369
- const { serverUrl } = await connect();
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
- const connected = Boolean(config.server && config.token);
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("gigai v0.1.0");
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-CU5WVKG2.js");
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-CU5WVKG2.js");
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-CU5WVKG2.js");
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-CU5WVKG2.js");
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-CU5WVKG2.js");
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-CU5WVKG2.js");
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-CU5WVKG2.js");
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-CU5WVKG2.js");
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: "0.1.0",
948
+ version: VERSION,
533
949
  description: "Bridge CLI tools to Claude across platforms"
534
950
  },
535
951
  subCommands: mode === "client" ? {