@schuttdev/gigai 0.2.6 → 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
@@ -3,1077 +3,175 @@
3
3
  // src/index.ts
4
4
  import { defineCommand, runMain } from "citty";
5
5
 
6
- // src/mode.ts
7
- function detectMode() {
8
- if (process.env.GIGAI_MODE === "client") return "client";
9
- if (process.env.GIGAI_MODE === "server") return "server";
10
- if (process.env.ANTHROPIC_API_KEY || process.env.ANTHROPIC_PROXY_URL || process.env.CLAUDE_CODE_EXEC) {
11
- return "client";
12
- }
13
- return "server";
14
- }
6
+ // src/version.ts
7
+ var VERSION = "0.2.7";
15
8
 
16
- // src/config.ts
17
- import { readFile, writeFile, mkdir } from "fs/promises";
18
- import { join } from "path";
19
- import { homedir } from "os";
20
- function getConfigDir() {
21
- return process.env.GIGAI_CONFIG_DIR ?? join(homedir(), ".gigai");
22
- }
23
- function getConfigPath() {
24
- return join(getConfigDir(), "config.json");
25
- }
26
- async function readConfig() {
27
- try {
28
- const raw = await readFile(getConfigPath(), "utf8");
29
- const parsed = JSON.parse(raw);
30
- if (parsed.server && parsed.token && !parsed.servers) {
31
- const name = deriveServerName(parsed.server);
32
- const migrated = {
33
- activeServer: name,
34
- servers: {
35
- [name]: {
36
- server: parsed.server,
37
- token: parsed.token,
38
- sessionToken: parsed.sessionToken,
39
- sessionExpiresAt: parsed.sessionExpiresAt
40
- }
41
- }
42
- };
43
- await writeConfig(migrated);
44
- return migrated;
45
- }
46
- return { activeServer: parsed.activeServer, servers: parsed.servers ?? {} };
47
- } catch {
48
- return { servers: {} };
49
- }
50
- }
51
- async function writeConfig(config) {
52
- const dir = getConfigDir();
53
- await mkdir(dir, { recursive: true });
54
- await writeFile(getConfigPath(), JSON.stringify(config, null, 2) + "\n", { mode: 384 });
55
- }
56
- function getActiveEntry(config) {
57
- if (!config.activeServer || !config.servers[config.activeServer]) return void 0;
58
- return { name: config.activeServer, entry: config.servers[config.activeServer] };
59
- }
60
- async function addServer(name, server, token) {
61
- const config = await readConfig();
62
- for (const [existingName, entry] of Object.entries(config.servers)) {
63
- if (normalizeUrl(entry.server) === normalizeUrl(server)) {
64
- config.servers[existingName] = { server, token };
65
- config.activeServer = existingName;
66
- await writeConfig(config);
67
- return;
68
- }
69
- }
70
- config.servers[name] = { server, token };
71
- config.activeServer = name;
72
- await writeConfig(config);
73
- }
74
- async function updateServerSession(name, sessionToken, sessionExpiresAt) {
75
- const config = await readConfig();
76
- const entry = config.servers[name];
77
- if (!entry) return;
78
- entry.sessionToken = sessionToken;
79
- entry.sessionExpiresAt = sessionExpiresAt;
80
- await writeConfig(config);
81
- }
82
- function deriveServerName(url) {
83
- try {
84
- const hostname = new URL(url).hostname;
85
- return hostname.split(".")[0];
86
- } catch {
87
- return "default";
88
- }
89
- }
90
- function normalizeUrl(url) {
9
+ // src/index.ts
10
+ async function requireServer() {
91
11
  try {
92
- const u = new URL(url);
93
- return u.hostname.toLowerCase();
12
+ return await import("./dist-MGLETZTT.js");
94
13
  } catch {
95
- return url.toLowerCase();
96
- }
97
- }
98
-
99
- // ../shared/dist/index.mjs
100
- import { z } from "zod";
101
- var TailscaleHttpsConfigSchema = z.object({
102
- provider: z.literal("tailscale"),
103
- funnelPort: z.number().optional()
104
- });
105
- var CloudflareHttpsConfigSchema = z.object({
106
- provider: z.literal("cloudflare"),
107
- tunnelName: z.string(),
108
- domain: z.string().optional()
109
- });
110
- var ManualHttpsConfigSchema = z.object({
111
- provider: z.literal("manual"),
112
- certPath: z.string(),
113
- keyPath: z.string()
114
- });
115
- var HttpsConfigSchema = z.discriminatedUnion("provider", [
116
- TailscaleHttpsConfigSchema,
117
- CloudflareHttpsConfigSchema,
118
- ManualHttpsConfigSchema
119
- ]);
120
- var CliToolConfigSchema = z.object({
121
- type: z.literal("cli"),
122
- name: z.string(),
123
- command: z.string(),
124
- args: z.array(z.string()).optional(),
125
- description: z.string(),
126
- timeout: z.number().optional(),
127
- cwd: z.string().optional(),
128
- env: z.record(z.string()).optional()
129
- });
130
- var McpToolConfigSchema = z.object({
131
- type: z.literal("mcp"),
132
- name: z.string(),
133
- command: z.string(),
134
- args: z.array(z.string()).optional(),
135
- description: z.string(),
136
- env: z.record(z.string()).optional()
137
- });
138
- var ScriptToolConfigSchema = z.object({
139
- type: z.literal("script"),
140
- name: z.string(),
141
- path: z.string(),
142
- description: z.string(),
143
- timeout: z.number().optional(),
144
- interpreter: z.string().optional()
145
- });
146
- var BuiltinToolConfigSchema = z.object({
147
- type: z.literal("builtin"),
148
- name: z.string(),
149
- builtin: z.enum(["filesystem", "shell"]),
150
- description: z.string(),
151
- config: z.record(z.unknown()).optional()
152
- });
153
- var ToolConfigSchema = z.discriminatedUnion("type", [
154
- CliToolConfigSchema,
155
- McpToolConfigSchema,
156
- ScriptToolConfigSchema,
157
- BuiltinToolConfigSchema
158
- ]);
159
- var AuthConfigSchema = z.object({
160
- encryptionKey: z.string().length(64),
161
- pairingTtlSeconds: z.number().default(300),
162
- sessionTtlSeconds: z.number().default(14400)
163
- });
164
- var ServerConfigSchema = z.object({
165
- port: z.number().default(7443),
166
- host: z.string().default("0.0.0.0"),
167
- https: HttpsConfigSchema.optional()
168
- });
169
- var GigaiConfigSchema = z.object({
170
- serverName: z.string().optional(),
171
- server: ServerConfigSchema,
172
- auth: AuthConfigSchema,
173
- tools: z.array(ToolConfigSchema).default([])
174
- });
175
- function decodeJWTPayload(token) {
176
- const parts = token.split(".");
177
- if (parts.length !== 3) {
178
- throw new Error("Invalid JWT format: expected 3 segments");
14
+ console.error("Server dependencies not installed.");
15
+ console.error("Run: npm install -g @schuttdev/gigai");
16
+ process.exit(1);
179
17
  }
180
- const payload = parts[1];
181
- const base64 = payload.replace(/-/g, "+").replace(/_/g, "/");
182
- const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
183
- const decoded = Buffer.from(padded, "base64").toString("utf8");
184
- return JSON.parse(decoded);
185
18
  }
186
-
187
- // src/identity.ts
188
- function getOrgUUID() {
189
- if (process.env.GIGAI_ORG_UUID) {
190
- return process.env.GIGAI_ORG_UUID;
191
- }
192
- const proxyUrl = process.env.HTTPS_PROXY || process.env.HTTP_PROXY || "";
193
- const jwtMatch = proxyUrl.match(/jwt_([^@]+)/);
194
- if (jwtMatch) {
195
- try {
196
- const payload = decodeJWTPayload(jwtMatch[1]);
197
- if (payload.organization_uuid) {
198
- return payload.organization_uuid;
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();
199
35
  }
200
- } catch {
201
- }
202
- }
203
- const anthropicProxy = process.env.ANTHROPIC_PROXY_URL ?? "";
204
- const anthropicJwtMatch = anthropicProxy.match(/([A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+)/);
205
- if (anthropicJwtMatch) {
206
- try {
207
- const payload = decodeJWTPayload(anthropicJwtMatch[1]);
208
- if (payload.organization_uuid) {
209
- 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();
210
42
  }
211
- } catch {
212
- }
213
- }
214
- const apiKey = process.env.ANTHROPIC_API_KEY ?? "";
215
- if (apiKey.includes(".")) {
216
- try {
217
- const payload = decodeJWTPayload(apiKey);
218
- if (payload.organization_uuid) {
219
- 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);
220
52
  }
221
- } catch {
222
- }
223
- }
224
- throw new Error(
225
- "Cannot determine organization UUID. Set GIGAI_ORG_UUID environment variable."
226
- );
227
- }
228
-
229
- // src/http.ts
230
- async function getProxyDispatcher() {
231
- const proxyUrl = process.env.HTTPS_PROXY || process.env.HTTP_PROXY;
232
- if (!proxyUrl) return void 0;
233
- try {
234
- const undici = await import("undici");
235
- return new undici.ProxyAgent(proxyUrl);
236
- } catch {
237
- return void 0;
238
- }
239
- }
240
- var _dispatcher = null;
241
- async function ensureDispatcher() {
242
- if (_dispatcher === null) {
243
- _dispatcher = await getProxyDispatcher();
244
- }
245
- return _dispatcher;
246
- }
247
- function createHttpClient(serverUrl, sessionToken) {
248
- const baseUrl = serverUrl.replace(/\/$/, "");
249
- async function request(path, init = {}) {
250
- const headers = {
251
- ...init.headers ?? {}
252
- };
253
- if (sessionToken) {
254
- headers["Authorization"] = `Bearer ${sessionToken}`;
255
- }
256
- if (!headers["Content-Type"] && init.body && typeof init.body === "string") {
257
- headers["Content-Type"] = "application/json";
258
- }
259
- const dispatcher = await ensureDispatcher();
260
- const fetchOpts = {
261
- ...init,
262
- headers
263
- };
264
- if (dispatcher) {
265
- fetchOpts.dispatcher = dispatcher;
266
- }
267
- const res = await fetch(`${baseUrl}${path}`, fetchOpts);
268
- if (!res.ok) {
269
- let errorBody;
270
- try {
271
- errorBody = await res.json();
272
- } catch {
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);
273
62
  }
274
- const message = errorBody?.error?.message ?? `HTTP ${res.status}: ${res.statusText}`;
275
- throw new Error(message);
276
- }
277
- return res.json();
278
- }
279
- return {
280
- get(path) {
281
- return request(path);
282
- },
283
- post(path, body) {
284
- return request(path, {
285
- method: "POST",
286
- body: body ? JSON.stringify(body) : void 0
287
- });
288
- },
289
- async postMultipart(path, formData) {
290
- const headers = {};
291
- if (sessionToken) {
292
- headers["Authorization"] = `Bearer ${sessionToken}`;
63
+ }),
64
+ uninstall: defineCommand({
65
+ meta: { name: "uninstall", description: "Remove background service" },
66
+ async run() {
67
+ const { uninstallDaemon } = await requireServer();
68
+ await uninstallDaemon();
293
69
  }
294
- const dispatcher = await ensureDispatcher();
295
- const fetchOpts = {
296
- method: "POST",
297
- headers,
298
- body: formData
299
- };
300
- if (dispatcher) {
301
- fetchOpts.dispatcher = dispatcher;
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 = [];
76
+ try {
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);
79
+ } catch {
80
+ }
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
+ }
302
93
  }
303
- const res = await fetch(`${baseUrl}${path}`, fetchOpts);
304
- if (!res.ok) {
305
- let errorBody;
94
+ }),
95
+ status: defineCommand({
96
+ meta: { name: "status", description: "Show server status" },
97
+ async run() {
98
+ console.log("Server status: checking...");
306
99
  try {
307
- errorBody = await res.json();
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`);
308
105
  } catch {
106
+ console.log("Server is not running.");
309
107
  }
310
- throw new Error(errorBody?.error?.message ?? `HTTP ${res.status}`);
311
108
  }
312
- return res.json();
313
- },
314
- async getRaw(path) {
315
- const headers = {};
316
- if (sessionToken) {
317
- headers["Authorization"] = `Bearer ${sessionToken}`;
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();
318
120
  }
319
- const dispatcher = await ensureDispatcher();
320
- const fetchOpts = { headers };
321
- if (dispatcher) {
322
- fetchOpts.dispatcher = dispatcher;
121
+ }),
122
+ mcp: defineCommand({
123
+ meta: { name: "mcp", description: "Wrap an MCP server" },
124
+ async run() {
125
+ const { wrapMcp } = await requireServer();
126
+ await wrapMcp();
323
127
  }
324
- const res = await fetch(`${baseUrl}${path}`, fetchOpts);
325
- if (!res.ok) {
326
- throw new Error(`HTTP ${res.status}: ${res.statusText}`);
128
+ }),
129
+ script: defineCommand({
130
+ meta: { name: "script", description: "Wrap a script" },
131
+ async run() {
132
+ const { wrapScript } = await requireServer();
133
+ await wrapScript();
327
134
  }
328
- return res;
329
- }
330
- };
331
- }
332
-
333
- // src/version.ts
334
- var VERSION = "0.2.6";
335
-
336
- // src/connect.ts
337
- async function connect(serverName) {
338
- const config = await readConfig();
339
- if (serverName) {
340
- if (!config.servers[serverName]) {
341
- const available = Object.keys(config.servers);
342
- throw new Error(
343
- available.length > 0 ? `Unknown server "${serverName}". Available: ${available.join(", ")}` : `No servers configured. Run 'gigai pair' first.`
344
- );
345
- }
346
- config.activeServer = serverName;
347
- await writeConfig(config);
348
- }
349
- const active = getActiveEntry(config);
350
- if (!active) {
351
- throw new Error("No server configured. Run 'gigai pair' first.");
352
- }
353
- const { name, entry } = active;
354
- if (entry.sessionToken && entry.sessionExpiresAt) {
355
- if (Date.now() < entry.sessionExpiresAt - 5 * 60 * 1e3) {
356
- await checkAndUpdateServer(entry.server, entry.sessionToken);
357
- return { serverUrl: entry.server, sessionToken: entry.sessionToken };
358
- }
359
- }
360
- const orgUuid = getOrgUUID();
361
- const http = createHttpClient(entry.server);
362
- const res = await http.post("/auth/connect", {
363
- encryptedToken: entry.token,
364
- orgUuid
365
- });
366
- await updateServerSession(name, res.sessionToken, res.expiresAt);
367
- await checkAndUpdateServer(entry.server, res.sessionToken);
368
- return { serverUrl: entry.server, sessionToken: res.sessionToken };
369
- }
370
- async function checkAndUpdateServer(serverUrl, sessionToken) {
371
- try {
372
- const http = createHttpClient(serverUrl);
373
- const health = await http.get("/health");
374
- if (isNewer(VERSION, health.version)) {
375
- console.log(`Server is outdated (${health.version} \u2192 ${VERSION}). Updating...`);
376
- const authedHttp = createHttpClient(serverUrl, sessionToken);
377
- const res = await authedHttp.post("/admin/update");
378
- if (res.updated) {
379
- console.log("Server updated and restarting.");
380
- await waitForServer(serverUrl, 15e3);
381
- console.log("Server is back online.");
382
- } else {
383
- console.log(`Server update failed: ${res.error ?? "unknown error"}`);
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);
384
144
  }
385
- }
386
- } catch {
145
+ })
387
146
  }
388
- }
389
- async function waitForServer(serverUrl, timeoutMs) {
390
- const start = Date.now();
391
- const http = createHttpClient(serverUrl);
392
- await new Promise((r) => setTimeout(r, 2e3));
393
- while (Date.now() - start < timeoutMs) {
394
- try {
395
- await http.get("/health");
396
- return;
397
- } catch {
398
- await new Promise((r) => setTimeout(r, 1e3));
399
- }
400
- }
401
- }
402
- function isNewer(client, server) {
403
- const parse = (v) => {
404
- const [core, pre] = v.replace(/^v/, "").split("-");
405
- const parts = core.split(".").map(Number);
406
- const preNum = pre ? parseInt(pre.replace(/\D+/g, "")) || 0 : Infinity;
407
- return [...parts, preNum];
408
- };
409
- const c = parse(client);
410
- const s = parse(server);
411
- for (let i = 0; i < Math.max(c.length, s.length); i++) {
412
- const cv = c[i] ?? 0;
413
- const sv = s[i] ?? 0;
414
- if (cv > sv) return true;
415
- if (cv < sv) return false;
416
- }
417
- return false;
418
- }
419
-
420
- // src/skill.ts
421
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
422
- import "path";
423
- var SKILL_MD = `---
424
- name: gigai
425
- description: Access tools on the user's machine via the gigai CLI
426
- ---
427
-
428
- # gigai
429
-
430
- 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.
431
-
432
- ## Setup (run once per conversation)
433
-
434
- \`\`\`bash
435
- npm install -g @schuttdev/gigai --omit=optional 2>/dev/null
436
- mkdir -p ~/.gigai
437
- cp /mnt/skills/user/gigai/config.json ~/.gigai/config.json
438
- gigai connect
439
- \`\`\`
440
-
441
- If \`gigai connect\` succeeds, you are ready to use tools. If it fails, tell the user.
442
-
443
- ## Usage
444
-
445
- ### List available tools
446
- \`\`\`bash
447
- gigai list
448
- \`\`\`
449
-
450
- ### Run a tool
451
- \`\`\`bash
452
- gigai <tool-name> [args...]
453
- \`\`\`
454
-
455
- Examples:
456
- \`\`\`bash
457
- gigai shell date
458
- gigai fs list Documents
459
- gigai shell whoami
460
- \`\`\`
461
-
462
- ### Get help for a tool
463
- \`\`\`bash
464
- gigai help <tool-name>
465
- \`\`\`
466
-
467
- ### Switch server (if multiple are configured)
468
- \`\`\`bash
469
- gigai connect <server-name>
470
- \`\`\`
471
-
472
- ### File transfer
473
- \`\`\`bash
474
- gigai upload <file>
475
- gigai download <id> <dest>
476
- \`\`\`
477
-
478
- ## Important
479
-
480
- - Always run the setup block before first use in a new conversation
481
- - All commands execute on the **user's machine**, not in this sandbox
482
- - If you get auth errors, run \`gigai connect\` to refresh the session
483
- - Tools are scoped to what the user has configured \u2014 if a tool is missing, tell the user
484
- `;
485
- async function hasExistingSkill() {
486
- try {
487
- await readFile2("/mnt/skills/user/gigai/config.json", "utf8");
488
- return true;
489
- } catch {
490
- return false;
491
- }
492
- }
493
- async function generateSkillZip(serverName, serverUrl, token) {
494
- let skillConfig = { servers: {} };
495
- try {
496
- const raw = await readFile2("/mnt/skills/user/gigai/config.json", "utf8");
497
- const existing = JSON.parse(raw);
498
- if (existing.servers) {
499
- skillConfig = existing;
500
- }
501
- } catch {
502
- }
503
- let merged = false;
504
- for (const [name, entry] of Object.entries(skillConfig.servers)) {
505
- if (normalizeHost(entry.server) === normalizeHost(serverUrl)) {
506
- skillConfig.servers[name] = { server: serverUrl, token };
507
- skillConfig.activeServer = name;
508
- merged = true;
509
- break;
510
- }
511
- }
512
- if (!merged) {
513
- skillConfig.servers[serverName] = { server: serverUrl, token };
514
- skillConfig.activeServer = serverName;
515
- }
516
- const configJson = JSON.stringify(skillConfig, null, 2) + "\n";
517
- return createZip([
518
- { path: "gigai/SKILL.md", data: Buffer.from(SKILL_MD, "utf8") },
519
- { path: "gigai/config.json", data: Buffer.from(configJson, "utf8") }
520
- ]);
521
- }
522
- async function writeSkillZip(zip) {
523
- const outputsDir = "/mnt/user-data/outputs";
524
- try {
525
- await mkdir2(outputsDir, { recursive: true });
526
- const outPath = `${outputsDir}/gigai.zip`;
527
- await writeFile2(outPath, zip);
528
- return outPath;
529
- } catch {
530
- const outPath = "gigai.zip";
531
- await writeFile2(outPath, zip);
532
- return outPath;
533
- }
534
- }
535
- function normalizeHost(url) {
536
- try {
537
- return new URL(url).hostname.toLowerCase();
538
- } catch {
539
- return url.toLowerCase();
540
- }
541
- }
542
- var crc32Table = new Uint32Array(256);
543
- for (let i = 0; i < 256; i++) {
544
- let c = i;
545
- for (let j = 0; j < 8; j++) {
546
- c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
547
- }
548
- crc32Table[i] = c >>> 0;
549
- }
550
- function crc32(data) {
551
- let crc = 4294967295;
552
- for (const byte of data) {
553
- crc = crc >>> 8 ^ crc32Table[(crc ^ byte) & 255];
554
- }
555
- return (crc ^ 4294967295) >>> 0;
556
- }
557
- function createZip(entries) {
558
- const parts = [];
559
- const centralParts = [];
560
- let offset = 0;
561
- for (const entry of entries) {
562
- const name = Buffer.from(entry.path, "utf8");
563
- const checksum = crc32(entry.data);
564
- const local = Buffer.alloc(30);
565
- local.writeUInt32LE(67324752, 0);
566
- local.writeUInt16LE(20, 4);
567
- local.writeUInt16LE(0, 6);
568
- local.writeUInt16LE(0, 8);
569
- local.writeUInt16LE(0, 10);
570
- local.writeUInt16LE(0, 12);
571
- local.writeUInt32LE(checksum, 14);
572
- local.writeUInt32LE(entry.data.length, 18);
573
- local.writeUInt32LE(entry.data.length, 22);
574
- local.writeUInt16LE(name.length, 26);
575
- local.writeUInt16LE(0, 28);
576
- parts.push(local, name, entry.data);
577
- const central = Buffer.alloc(46);
578
- central.writeUInt32LE(33639248, 0);
579
- central.writeUInt16LE(20, 4);
580
- central.writeUInt16LE(20, 6);
581
- central.writeUInt16LE(0, 8);
582
- central.writeUInt16LE(0, 10);
583
- central.writeUInt16LE(0, 12);
584
- central.writeUInt16LE(0, 14);
585
- central.writeUInt32LE(checksum, 16);
586
- central.writeUInt32LE(entry.data.length, 20);
587
- central.writeUInt32LE(entry.data.length, 24);
588
- central.writeUInt16LE(name.length, 28);
589
- central.writeUInt16LE(0, 30);
590
- central.writeUInt16LE(0, 32);
591
- central.writeUInt16LE(0, 34);
592
- central.writeUInt16LE(0, 36);
593
- central.writeUInt32LE(0, 38);
594
- central.writeUInt32LE(offset, 42);
595
- centralParts.push(central, name);
596
- offset += 30 + name.length + entry.data.length;
597
- }
598
- const centralDir = Buffer.concat(centralParts);
599
- const eocd = Buffer.alloc(22);
600
- eocd.writeUInt32LE(101010256, 0);
601
- eocd.writeUInt16LE(0, 4);
602
- eocd.writeUInt16LE(0, 6);
603
- eocd.writeUInt16LE(entries.length, 8);
604
- eocd.writeUInt16LE(entries.length, 10);
605
- eocd.writeUInt32LE(centralDir.length, 12);
606
- eocd.writeUInt32LE(offset, 16);
607
- eocd.writeUInt16LE(0, 20);
608
- return Buffer.concat([...parts, centralDir, eocd]);
609
- }
610
-
611
- // src/pair.ts
612
- async function pair(code, serverUrl) {
613
- const orgUuid = getOrgUUID();
614
- const http = createHttpClient(serverUrl);
615
- const res = await http.post("/auth/pair", {
616
- pairingCode: code,
617
- orgUuid
618
- });
619
- await addServer(res.serverName, serverUrl, res.encryptedToken);
620
- console.log(`Paired with "${res.serverName}" successfully!`);
621
- const existing = await hasExistingSkill();
622
- const zip = await generateSkillZip(res.serverName, serverUrl, res.encryptedToken);
623
- const outPath = await writeSkillZip(zip);
624
- console.log(`
625
- Skill zip written to: ${outPath}`);
626
- if (existing) {
627
- console.log("Skill file updated. Download and re-upload to Claude.");
628
- } else {
629
- console.log("Upload this file as a skill in Claude (Settings \u2192 Customize \u2192 Upload Skill).");
630
- }
631
- }
632
-
633
- // src/discover.ts
634
- import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
635
- import { join as join2 } from "path";
636
- import { homedir as homedir2 } from "os";
637
- var MANIFEST_TTL = 5 * 60 * 1e3;
638
- function getManifestPath() {
639
- const dir = process.env.GIGAI_CONFIG_DIR ?? join2(homedir2(), ".gigai");
640
- return join2(dir, "tool-manifest.json");
641
- }
642
- async function fetchTools(http) {
643
- try {
644
- const raw = await readFile3(getManifestPath(), "utf8");
645
- const cache = JSON.parse(raw);
646
- if (Date.now() - cache.fetchedAt < MANIFEST_TTL) {
647
- return cache.tools;
648
- }
649
- } catch {
650
- }
651
- const res = await http.get("/tools");
652
- try {
653
- const dir = process.env.GIGAI_CONFIG_DIR ?? join2(homedir2(), ".gigai");
654
- await mkdir3(dir, { recursive: true });
655
- const cache = { tools: res.tools, fetchedAt: Date.now() };
656
- await writeFile3(getManifestPath(), JSON.stringify(cache));
657
- } catch {
658
- }
659
- return res.tools;
660
- }
661
- async function fetchToolDetail(http, name) {
662
- return http.get(`/tools/${encodeURIComponent(name)}`);
663
- }
664
-
665
- // src/exec.ts
666
- async function execTool(http, name, args, timeout) {
667
- const res = await http.post("/exec", {
668
- tool: name,
669
- args,
670
- timeout
671
- });
672
- if (res.stdout) process.stdout.write(res.stdout);
673
- if (res.stderr) process.stderr.write(res.stderr);
674
- process.exitCode = res.exitCode;
675
- }
676
- async function execMcpTool(http, tool, mcpTool, args) {
677
- const res = await http.post("/exec/mcp", {
678
- tool,
679
- mcpTool,
680
- args
681
- });
682
- for (const content of res.content) {
683
- if (content.type === "text" && content.text) {
684
- process.stdout.write(content.text + "\n");
685
- } else if (content.type === "image") {
686
- console.log(`[Image: ${content.mimeType}]`);
687
- } else if (content.type === "resource") {
688
- console.log(`[Resource: ${content.mimeType}]`);
689
- }
690
- }
691
- if (res.isError) {
692
- process.exitCode = 1;
693
- }
694
- }
695
-
696
- // src/transfer.ts
697
- import { readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
698
- import { basename } from "path";
699
- import { Blob } from "buffer";
700
- async function upload(http, filePath) {
701
- const content = await readFile4(filePath);
702
- const filename = basename(filePath);
703
- const formData = new FormData();
704
- const blob = new Blob([content]);
705
- formData.append("file", blob, filename);
706
- const res = await http.postMultipart(
707
- "/transfer/upload",
708
- formData
709
- );
710
- console.log(`Uploaded: ${res.id}`);
711
- return res.id;
712
- }
713
- async function download(http, id, destPath) {
714
- const res = await http.getRaw(`/transfer/${encodeURIComponent(id)}`);
715
- const buffer = Buffer.from(await res.arrayBuffer());
716
- await writeFile4(destPath, buffer);
717
- console.log(`Downloaded to: ${destPath}`);
718
- }
719
-
720
- // src/output.ts
721
- function formatToolList(tools) {
722
- if (tools.length === 0) return "No tools registered.";
723
- const maxName = Math.max(...tools.map((t) => t.name.length));
724
- const maxType = Math.max(...tools.map((t) => t.type.length));
725
- const lines = tools.map((t) => {
726
- const name = t.name.padEnd(maxName);
727
- const type = t.type.padEnd(maxType);
728
- return ` ${name} ${type} ${t.description}`;
729
- });
730
- return `Available tools:
731
- ${lines.join("\n")}`;
732
- }
733
- function formatToolDetail(detail) {
734
- const lines = [];
735
- lines.push(`${detail.name} (${detail.type})`);
736
- lines.push(` ${detail.description}`);
737
- if (detail.usage) {
738
- lines.push(`
739
- Usage: ${detail.usage}`);
740
- }
741
- if (detail.args?.length) {
742
- lines.push("\nArguments:");
743
- for (const arg of detail.args) {
744
- const req = arg.required ? " (required)" : "";
745
- const def = arg.default ? ` [default: ${arg.default}]` : "";
746
- lines.push(` ${arg.name}${req}${def} \u2014 ${arg.description}`);
747
- }
748
- }
749
- if (detail.mcpTools?.length) {
750
- lines.push("\nMCP Tools:");
751
- for (const t of detail.mcpTools) {
752
- lines.push(` ${t.name} \u2014 ${t.description}`);
753
- }
754
- }
755
- return lines.join("\n");
756
- }
757
- function formatStatus(config) {
758
- const serverNames = Object.keys(config.servers);
759
- if (serverNames.length === 0) {
760
- return "Not connected. Run 'gigai pair <code> <server-url>' to set up.";
761
- }
762
- const lines = [];
763
- for (const name of serverNames) {
764
- const entry = config.servers[name];
765
- const active = name === config.activeServer ? " (active)" : "";
766
- lines.push(` ${name}${active} ${entry.server}`);
767
- if (entry.sessionExpiresAt) {
768
- const remaining = entry.sessionExpiresAt - Date.now();
769
- if (remaining > 0) {
770
- lines.push(` Session expires in ${Math.floor(remaining / 6e4)} minutes`);
771
- } else {
772
- lines.push(" Session expired \u2014 will auto-renew on next command");
773
- }
774
- }
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);
775
156
  }
776
- return `Servers:
777
- ${lines.join("\n")}`;
778
- }
779
-
780
- // src/index.ts
781
- var mode = detectMode();
782
- var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
783
- "pair",
784
- "connect",
785
- "list",
786
- "help",
787
- "status",
788
- "upload",
789
- "download",
790
- "version",
791
- "--help",
792
- "-h",
793
- "server",
794
- "wrap",
795
- "unwrap"
796
- ]);
797
- var firstArg = process.argv[2];
798
- if (firstArg && !firstArg.startsWith("-") && !KNOWN_COMMANDS.has(firstArg)) {
799
- const toolName = firstArg;
800
- const toolArgs = process.argv.slice(3);
801
- try {
802
- const { serverUrl, sessionToken } = await connect();
803
- const http = createHttpClient(serverUrl, sessionToken);
804
- const { tool: detail } = await fetchToolDetail(http, toolName);
805
- if (detail.type === "mcp") {
806
- const mcpToolName = toolArgs[0];
807
- if (!mcpToolName) {
808
- const toolNames = (detail.mcpTools ?? []).map((t) => ` ${t.name} \u2014 ${t.description}`);
809
- console.log(`MCP tools for ${toolName}:
810
- ${toolNames.join("\n")}`);
811
- } else {
812
- const jsonArg = toolArgs.slice(1).join(" ");
813
- const args = jsonArg ? JSON.parse(jsonArg) : {};
814
- await execMcpTool(http, toolName, mcpToolName, args);
815
- }
816
- } else {
817
- await execTool(http, toolName, toolArgs);
818
- }
819
- } catch (e) {
820
- console.error(`Error: ${e.message}`);
821
- process.exitCode = 1;
157
+ });
158
+ var versionCommand = defineCommand({
159
+ meta: { name: "version", description: "Show version" },
160
+ run() {
161
+ console.log(`gigai v${VERSION}`);
822
162
  }
823
- } else {
824
- runCitty();
825
- }
826
- async function requireServer() {
827
- try {
828
- return await requireServer();
829
- } catch {
830
- console.error("Server dependencies not installed.");
831
- console.error("Run: npm install -g @schuttdev/gigai");
832
- process.exit(1);
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
833
175
  }
834
- }
835
- function runCitty() {
836
- const pairCommand = defineCommand({
837
- meta: { name: "pair", description: "Pair with a gigai server" },
838
- args: {
839
- code: { type: "positional", description: "Pairing code", required: true },
840
- server: { type: "positional", description: "Server URL", required: true }
841
- },
842
- async run({ args }) {
843
- await pair(args.code, args.server);
844
- }
845
- });
846
- const connectCommand = defineCommand({
847
- meta: { name: "connect", description: "Establish a session with the server" },
848
- args: {
849
- name: { type: "positional", description: "Server name (optional)", required: false }
850
- },
851
- async run({ args }) {
852
- const { serverUrl } = await connect(args.name);
853
- console.log(`Connected to ${serverUrl}`);
854
- }
855
- });
856
- const listCommand = defineCommand({
857
- meta: { name: "list", description: "List available tools" },
858
- async run() {
859
- const { serverUrl, sessionToken } = await connect();
860
- const http = createHttpClient(serverUrl, sessionToken);
861
- const tools = await fetchTools(http);
862
- console.log(formatToolList(tools));
863
- }
864
- });
865
- const helpCommand = defineCommand({
866
- meta: { name: "help", description: "Show help for a tool" },
867
- args: {
868
- tool: { type: "positional", description: "Tool name", required: true }
869
- },
870
- async run({ args }) {
871
- const { serverUrl, sessionToken } = await connect();
872
- const http = createHttpClient(serverUrl, sessionToken);
873
- const { tool } = await fetchToolDetail(http, args.tool);
874
- console.log(formatToolDetail(tool));
875
- }
876
- });
877
- const statusCommand = defineCommand({
878
- meta: { name: "status", description: "Show connection status" },
879
- async run() {
880
- const config = await readConfig();
881
- console.log(formatStatus(config));
882
- }
883
- });
884
- const uploadCommand = defineCommand({
885
- meta: { name: "upload", description: "Upload a file to the server" },
886
- args: {
887
- file: { type: "positional", description: "File path", required: true }
888
- },
889
- async run({ args }) {
890
- const { serverUrl, sessionToken } = await connect();
891
- const http = createHttpClient(serverUrl, sessionToken);
892
- await upload(http, args.file);
893
- }
894
- });
895
- const downloadCommand = defineCommand({
896
- meta: { name: "download", description: "Download a file from the server" },
897
- args: {
898
- id: { type: "positional", description: "Transfer ID", required: true },
899
- dest: { type: "positional", description: "Destination path", required: true }
900
- },
901
- async run({ args }) {
902
- const { serverUrl, sessionToken } = await connect();
903
- const http = createHttpClient(serverUrl, sessionToken);
904
- await download(http, args.id, args.dest);
905
- }
906
- });
907
- const versionCommand = defineCommand({
908
- meta: { name: "version", description: "Show version" },
909
- run() {
910
- console.log(`gigai v${VERSION}`);
911
- }
912
- });
913
- const serverCommand = defineCommand({
914
- meta: { name: "server", description: "Server management commands" },
915
- subCommands: {
916
- start: defineCommand({
917
- meta: { name: "start", description: "Start the gigai server" },
918
- args: {
919
- config: { type: "string", alias: "c", description: "Config file path" },
920
- dev: { type: "boolean", description: "Development mode (no HTTPS)" }
921
- },
922
- async run({ args }) {
923
- const { startServer } = await requireServer();
924
- const extraArgs = [];
925
- if (args.config) extraArgs.push("--config", args.config);
926
- if (args.dev) extraArgs.push("--dev");
927
- process.argv.push(...extraArgs);
928
- await startServer();
929
- }
930
- }),
931
- init: defineCommand({
932
- meta: { name: "init", description: "Interactive setup wizard" },
933
- async run() {
934
- const { runInit } = await requireServer();
935
- await runInit();
936
- }
937
- }),
938
- pair: defineCommand({
939
- meta: { name: "pair", description: "Generate a pairing code" },
940
- args: {
941
- config: { type: "string", alias: "c", description: "Config file path" }
942
- },
943
- async run({ args }) {
944
- const { generateServerPairingCode } = await requireServer();
945
- await generateServerPairingCode(args.config);
946
- }
947
- }),
948
- install: defineCommand({
949
- meta: { name: "install", description: "Install as persistent background service" },
950
- args: {
951
- config: { type: "string", alias: "c", description: "Config file path" }
952
- },
953
- async run({ args }) {
954
- const { installDaemon } = await requireServer();
955
- await installDaemon(args.config);
956
- }
957
- }),
958
- uninstall: defineCommand({
959
- meta: { name: "uninstall", description: "Remove background service" },
960
- async run() {
961
- const { uninstallDaemon } = await requireServer();
962
- await uninstallDaemon();
963
- }
964
- }),
965
- stop: defineCommand({
966
- meta: { name: "stop", description: "Stop the running gigai server" },
967
- async run() {
968
- const { execFileSync } = await import("child_process");
969
- let pids = [];
970
- try {
971
- const out = execFileSync("pgrep", ["-f", "gigai server start"], { encoding: "utf8" });
972
- pids = out.trim().split("\n").map(Number).filter((pid) => pid && pid !== process.pid);
973
- } catch {
974
- }
975
- if (pids.length === 0) {
976
- console.log("No running gigai server found.");
977
- return;
978
- }
979
- for (const pid of pids) {
980
- try {
981
- process.kill(pid, "SIGTERM");
982
- console.log(`Stopped gigai server (PID ${pid})`);
983
- } catch (e) {
984
- console.error(`Failed to stop PID ${pid}: ${e.message}`);
985
- }
986
- }
987
- }
988
- }),
989
- status: defineCommand({
990
- meta: { name: "status", description: "Show server status" },
991
- async run() {
992
- console.log("Server status: checking...");
993
- try {
994
- const res = await fetch("http://localhost:7443/health");
995
- const data = await res.json();
996
- console.log(`Status: ${data.status}`);
997
- console.log(`Version: ${data.version}`);
998
- console.log(`Uptime: ${Math.floor(data.uptime / 1e3)}s`);
999
- } catch {
1000
- console.log("Server is not running.");
1001
- }
1002
- }
1003
- })
1004
- }
1005
- });
1006
- const wrapCommand = defineCommand({
1007
- meta: { name: "wrap", description: "Register a tool" },
1008
- subCommands: {
1009
- cli: defineCommand({
1010
- meta: { name: "cli", description: "Wrap a CLI command" },
1011
- async run() {
1012
- const { wrapCli } = await requireServer();
1013
- await wrapCli();
1014
- }
1015
- }),
1016
- mcp: defineCommand({
1017
- meta: { name: "mcp", description: "Wrap an MCP server" },
1018
- async run() {
1019
- const { wrapMcp } = await requireServer();
1020
- await wrapMcp();
1021
- }
1022
- }),
1023
- script: defineCommand({
1024
- meta: { name: "script", description: "Wrap a script" },
1025
- async run() {
1026
- const { wrapScript } = await requireServer();
1027
- await wrapScript();
1028
- }
1029
- }),
1030
- import: defineCommand({
1031
- meta: { name: "import", description: "Import from claude_desktop_config.json" },
1032
- args: {
1033
- path: { type: "positional", description: "Path to config file", required: true }
1034
- },
1035
- async run({ args }) {
1036
- const { wrapImport } = await requireServer();
1037
- await wrapImport(args.path);
1038
- }
1039
- })
1040
- }
1041
- });
1042
- const unwrapCommand = defineCommand({
1043
- meta: { name: "unwrap", description: "Unregister a tool" },
1044
- args: {
1045
- name: { type: "positional", description: "Tool name", required: true }
1046
- },
1047
- async run({ args }) {
1048
- const { unwrapTool } = await requireServer();
1049
- await unwrapTool(args.name);
1050
- }
1051
- });
1052
- const main = defineCommand({
1053
- meta: {
1054
- name: "gigai",
1055
- version: VERSION,
1056
- description: "Bridge CLI tools to Claude across platforms"
1057
- },
1058
- subCommands: mode === "client" ? {
1059
- pair: pairCommand,
1060
- connect: connectCommand,
1061
- list: listCommand,
1062
- help: helpCommand,
1063
- status: statusCommand,
1064
- upload: uploadCommand,
1065
- download: downloadCommand,
1066
- version: versionCommand
1067
- } : {
1068
- server: serverCommand,
1069
- wrap: wrapCommand,
1070
- unwrap: unwrapCommand,
1071
- version: versionCommand,
1072
- pair: pairCommand,
1073
- connect: connectCommand,
1074
- list: listCommand,
1075
- status: statusCommand
1076
- }
1077
- });
1078
- runMain(main);
1079
- }
176
+ });
177
+ runMain(main);