@schuttdev/kon 0.2.8 → 0.3.0
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 +420 -58
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -136,7 +136,7 @@ var ScriptToolConfigSchema = z.object({
|
|
|
136
136
|
var BuiltinToolConfigSchema = z.object({
|
|
137
137
|
type: z.literal("builtin"),
|
|
138
138
|
name: z.string(),
|
|
139
|
-
builtin: z.enum(["filesystem", "shell"]),
|
|
139
|
+
builtin: z.enum(["filesystem", "shell", "read", "write", "edit", "glob", "grep", "bash"]),
|
|
140
140
|
description: z.string(),
|
|
141
141
|
config: z.record(z.unknown()).optional()
|
|
142
142
|
});
|
|
@@ -276,6 +276,26 @@ function createHttpClient(serverUrl, sessionToken) {
|
|
|
276
276
|
body: body ? JSON.stringify(body) : void 0
|
|
277
277
|
});
|
|
278
278
|
},
|
|
279
|
+
async delete(path) {
|
|
280
|
+
const headers = {};
|
|
281
|
+
if (sessionToken) {
|
|
282
|
+
headers["Authorization"] = `Bearer ${sessionToken}`;
|
|
283
|
+
}
|
|
284
|
+
const dispatcher = await ensureDispatcher();
|
|
285
|
+
const fetchOpts = { method: "DELETE", headers };
|
|
286
|
+
if (dispatcher) {
|
|
287
|
+
fetchOpts.dispatcher = dispatcher;
|
|
288
|
+
}
|
|
289
|
+
const res = await fetch(`${baseUrl}${path}`, fetchOpts);
|
|
290
|
+
if (!res.ok) {
|
|
291
|
+
let errorBody;
|
|
292
|
+
try {
|
|
293
|
+
errorBody = await res.json();
|
|
294
|
+
} catch {
|
|
295
|
+
}
|
|
296
|
+
throw new Error(errorBody?.error?.message ?? `HTTP ${res.status}`);
|
|
297
|
+
}
|
|
298
|
+
},
|
|
279
299
|
async postMultipart(path, formData) {
|
|
280
300
|
const headers = {};
|
|
281
301
|
if (sessionToken) {
|
|
@@ -321,7 +341,7 @@ function createHttpClient(serverUrl, sessionToken) {
|
|
|
321
341
|
}
|
|
322
342
|
|
|
323
343
|
// ../cli/src/version.ts
|
|
324
|
-
var VERSION = "0.
|
|
344
|
+
var VERSION = "0.3.0";
|
|
325
345
|
|
|
326
346
|
// ../cli/src/connect.ts
|
|
327
347
|
async function connect(serverName) {
|
|
@@ -361,6 +381,17 @@ async function checkAndUpdateServer(serverUrl, sessionToken) {
|
|
|
361
381
|
try {
|
|
362
382
|
const http = createHttpClient(serverUrl);
|
|
363
383
|
const health = await http.get("/health");
|
|
384
|
+
if (health.platform || health.hostname) {
|
|
385
|
+
const config = await readConfig();
|
|
386
|
+
for (const entry of Object.values(config.servers)) {
|
|
387
|
+
if (normalizeUrl2(entry.server) === normalizeUrl2(serverUrl)) {
|
|
388
|
+
entry.platform = health.platform;
|
|
389
|
+
entry.hostname = health.hostname;
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
await writeConfig(config);
|
|
394
|
+
}
|
|
364
395
|
if (isNewer(VERSION, health.version)) {
|
|
365
396
|
console.log(`Server is outdated (${health.version} \u2192 ${VERSION}). Updating...`);
|
|
366
397
|
const authedHttp = createHttpClient(serverUrl, sessionToken);
|
|
@@ -389,6 +420,13 @@ async function waitForServer(serverUrl, timeoutMs) {
|
|
|
389
420
|
}
|
|
390
421
|
}
|
|
391
422
|
}
|
|
423
|
+
function normalizeUrl2(url) {
|
|
424
|
+
try {
|
|
425
|
+
return new URL(url).hostname.toLowerCase();
|
|
426
|
+
} catch {
|
|
427
|
+
return url.toLowerCase();
|
|
428
|
+
}
|
|
429
|
+
}
|
|
392
430
|
function isNewer(client, server) {
|
|
393
431
|
const parse = (v) => {
|
|
394
432
|
const [core, pre] = v.replace(/^v/, "").split("-");
|
|
@@ -407,8 +445,40 @@ function isNewer(client, server) {
|
|
|
407
445
|
return false;
|
|
408
446
|
}
|
|
409
447
|
|
|
410
|
-
// ../cli/src/
|
|
448
|
+
// ../cli/src/discover.ts
|
|
411
449
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
450
|
+
import { join as join2 } from "path";
|
|
451
|
+
import { homedir as homedir2 } from "os";
|
|
452
|
+
var MANIFEST_TTL = 5 * 60 * 1e3;
|
|
453
|
+
function getManifestPath() {
|
|
454
|
+
const dir = process.env.GIGAI_CONFIG_DIR ?? join2(homedir2(), ".gigai");
|
|
455
|
+
return join2(dir, "tool-manifest.json");
|
|
456
|
+
}
|
|
457
|
+
async function fetchTools(http) {
|
|
458
|
+
try {
|
|
459
|
+
const raw = await readFile2(getManifestPath(), "utf8");
|
|
460
|
+
const cache = JSON.parse(raw);
|
|
461
|
+
if (Date.now() - cache.fetchedAt < MANIFEST_TTL) {
|
|
462
|
+
return cache.tools;
|
|
463
|
+
}
|
|
464
|
+
} catch {
|
|
465
|
+
}
|
|
466
|
+
const res = await http.get("/tools");
|
|
467
|
+
try {
|
|
468
|
+
const dir = process.env.GIGAI_CONFIG_DIR ?? join2(homedir2(), ".gigai");
|
|
469
|
+
await mkdir2(dir, { recursive: true });
|
|
470
|
+
const cache = { tools: res.tools, fetchedAt: Date.now() };
|
|
471
|
+
await writeFile2(getManifestPath(), JSON.stringify(cache));
|
|
472
|
+
} catch {
|
|
473
|
+
}
|
|
474
|
+
return res.tools;
|
|
475
|
+
}
|
|
476
|
+
async function fetchToolDetail(http, name) {
|
|
477
|
+
return http.get(`/tools/${encodeURIComponent(name)}`);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// ../cli/src/skill.ts
|
|
481
|
+
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
412
482
|
import "path";
|
|
413
483
|
var SKILL_MD = `---
|
|
414
484
|
name: gigai
|
|
@@ -430,34 +500,61 @@ kon connect
|
|
|
430
500
|
|
|
431
501
|
If \`kon connect\` succeeds, you are ready to use tools. If it fails, tell the user.
|
|
432
502
|
|
|
433
|
-
##
|
|
503
|
+
## Discovering tools
|
|
434
504
|
|
|
435
|
-
|
|
505
|
+
List all available tools:
|
|
436
506
|
\`\`\`bash
|
|
437
507
|
kon list
|
|
438
508
|
\`\`\`
|
|
439
509
|
|
|
440
|
-
|
|
510
|
+
Get detailed help for a specific tool:
|
|
441
511
|
\`\`\`bash
|
|
442
|
-
kon <tool-name>
|
|
512
|
+
kon help <tool-name>
|
|
443
513
|
\`\`\`
|
|
444
514
|
|
|
445
|
-
|
|
515
|
+
## Core tools
|
|
516
|
+
|
|
517
|
+
These tools may be available depending on the server's configuration. Run \`kon list\` to see what's enabled.
|
|
518
|
+
|
|
519
|
+
### read \u2014 Read file contents
|
|
446
520
|
\`\`\`bash
|
|
447
|
-
kon
|
|
448
|
-
kon fs list Documents
|
|
449
|
-
kon shell whoami
|
|
521
|
+
kon read <file> [offset] [limit]
|
|
450
522
|
\`\`\`
|
|
523
|
+
- \`offset\`: start from this line number (0-based)
|
|
524
|
+
- \`limit\`: max number of lines to return
|
|
451
525
|
|
|
452
|
-
###
|
|
526
|
+
### write \u2014 Write content to a file
|
|
453
527
|
\`\`\`bash
|
|
454
|
-
kon
|
|
528
|
+
kon write <file> <content>
|
|
455
529
|
\`\`\`
|
|
456
530
|
|
|
457
|
-
###
|
|
531
|
+
### edit \u2014 Replace text in a file
|
|
458
532
|
\`\`\`bash
|
|
459
|
-
kon
|
|
533
|
+
kon edit <file> <old_string> <new_string> [--all]
|
|
460
534
|
\`\`\`
|
|
535
|
+
- Without \`--all\`: fails if old_string matches multiple locations (provide more context)
|
|
536
|
+
- With \`--all\`: replaces every occurrence
|
|
537
|
+
|
|
538
|
+
### glob \u2014 Find files by pattern
|
|
539
|
+
\`\`\`bash
|
|
540
|
+
kon glob <pattern> [path]
|
|
541
|
+
\`\`\`
|
|
542
|
+
- Supports \`*\`, \`**\`, \`?\`, \`{a,b}\` syntax
|
|
543
|
+
- Example: \`kon glob "**/*.ts" ~/projects/myapp\`
|
|
544
|
+
|
|
545
|
+
### grep \u2014 Search file contents
|
|
546
|
+
\`\`\`bash
|
|
547
|
+
kon grep <pattern> [path] [--glob <filter>] [-i] [-n] [-C <num>]
|
|
548
|
+
\`\`\`
|
|
549
|
+
- Uses ripgrep if available, falls back to built-in search
|
|
550
|
+
- Example: \`kon grep "TODO" ~/projects --glob "*.ts"\`
|
|
551
|
+
|
|
552
|
+
### bash \u2014 Execute shell commands
|
|
553
|
+
\`\`\`bash
|
|
554
|
+
kon bash <command> [args...]
|
|
555
|
+
\`\`\`
|
|
556
|
+
- Commands are restricted to the server's allowlist
|
|
557
|
+
- Example: \`kon bash git status\`
|
|
461
558
|
|
|
462
559
|
### File transfer
|
|
463
560
|
\`\`\`bash
|
|
@@ -465,25 +562,164 @@ kon upload <file>
|
|
|
465
562
|
kon download <id> <dest>
|
|
466
563
|
\`\`\`
|
|
467
564
|
|
|
565
|
+
## Other tools
|
|
566
|
+
|
|
567
|
+
The server may have additional tools registered (CLI commands, MCP servers, scripts). Any unknown subcommand is treated as a tool name:
|
|
568
|
+
|
|
569
|
+
\`\`\`bash
|
|
570
|
+
kon <tool-name> [args...]
|
|
571
|
+
\`\`\`
|
|
572
|
+
|
|
573
|
+
For MCP tools, the first arg is the MCP tool name:
|
|
574
|
+
\`\`\`bash
|
|
575
|
+
kon <mcp-tool> <mcp-action> [json-args]
|
|
576
|
+
\`\`\`
|
|
577
|
+
|
|
578
|
+
## Scheduling tasks
|
|
579
|
+
|
|
580
|
+
Schedule any tool execution on the server:
|
|
581
|
+
\`\`\`bash
|
|
582
|
+
kon cron add "0 9 * * *" bash git pull # daily at 9am
|
|
583
|
+
kon cron add --at "9:00 AM tomorrow" bash git pull # one-shot
|
|
584
|
+
kon cron add --at "in 30 minutes" read ~/log.txt # relative time
|
|
585
|
+
kon cron list # list scheduled jobs
|
|
586
|
+
kon cron remove <id> # remove a job
|
|
587
|
+
\`\`\`
|
|
588
|
+
|
|
589
|
+
## Multiple servers
|
|
590
|
+
|
|
591
|
+
The user may have multiple servers configured (e.g. a Mac and a Linux machine). Use \`kon status\` to see all servers and which is active.
|
|
592
|
+
|
|
593
|
+
\`\`\`bash
|
|
594
|
+
kon status # show all servers + active
|
|
595
|
+
kon connect <server-name> # switch to a different server
|
|
596
|
+
kon list # list tools on the current server
|
|
597
|
+
\`\`\`
|
|
598
|
+
|
|
599
|
+
**Routing between servers:** When a tool is not available on the current server, or when a task requires a specific platform (e.g. iMessage requires macOS), switch to the appropriate server:
|
|
600
|
+
|
|
601
|
+
1. Run \`kon status\` to see available servers
|
|
602
|
+
2. Run \`kon connect <server-name>\` to switch
|
|
603
|
+
3. Run \`kon list\` to verify the tool is available
|
|
604
|
+
4. Execute the command
|
|
605
|
+
|
|
606
|
+
Platform-specific capabilities:
|
|
607
|
+
- **iMessage**: only available on macOS servers
|
|
608
|
+
- **macOS apps** (Shortcuts, AppleScript): only on macOS servers
|
|
609
|
+
- **systemd, apt, etc.**: only on Linux servers
|
|
610
|
+
|
|
611
|
+
The \`kon list\` response includes the server's platform. Use this to determine which server to route a request to.
|
|
612
|
+
|
|
468
613
|
## Important
|
|
469
614
|
|
|
470
615
|
- Always run the setup block before first use in a new conversation
|
|
471
616
|
- All commands execute on the **user's machine**, not in this sandbox
|
|
472
617
|
- If you get auth errors, run \`kon connect\` to refresh the session
|
|
473
618
|
- Tools are scoped to what the user has configured \u2014 if a tool is missing, tell the user
|
|
619
|
+
- If you have multiple servers, check which server has the tool you need before executing
|
|
474
620
|
`;
|
|
475
621
|
async function hasExistingSkill() {
|
|
476
622
|
try {
|
|
477
|
-
await
|
|
623
|
+
await readFile3("/mnt/skills/user/gigai/config.json", "utf8");
|
|
478
624
|
return true;
|
|
479
625
|
} catch {
|
|
480
626
|
return false;
|
|
481
627
|
}
|
|
482
628
|
}
|
|
483
|
-
|
|
629
|
+
function generateToolMarkdown(tool) {
|
|
630
|
+
const lines = [];
|
|
631
|
+
lines.push("---");
|
|
632
|
+
lines.push(`name: ${tool.name}`);
|
|
633
|
+
lines.push(`description: ${tool.description}`);
|
|
634
|
+
lines.push("---");
|
|
635
|
+
lines.push("");
|
|
636
|
+
lines.push(`# ${tool.name}`);
|
|
637
|
+
lines.push("");
|
|
638
|
+
lines.push(`**Type:** ${tool.type}`);
|
|
639
|
+
lines.push("");
|
|
640
|
+
lines.push(tool.description);
|
|
641
|
+
lines.push("");
|
|
642
|
+
lines.push("## Usage");
|
|
643
|
+
lines.push("");
|
|
644
|
+
if (tool.type === "builtin") {
|
|
645
|
+
switch (tool.name) {
|
|
646
|
+
case "read":
|
|
647
|
+
lines.push("```bash");
|
|
648
|
+
lines.push("kon read <file> [offset] [limit]");
|
|
649
|
+
lines.push("```");
|
|
650
|
+
break;
|
|
651
|
+
case "write":
|
|
652
|
+
lines.push("```bash");
|
|
653
|
+
lines.push("kon write <file> <content>");
|
|
654
|
+
lines.push("```");
|
|
655
|
+
break;
|
|
656
|
+
case "edit":
|
|
657
|
+
lines.push("```bash");
|
|
658
|
+
lines.push("kon edit <file> <old_string> <new_string> [--all]");
|
|
659
|
+
lines.push("```");
|
|
660
|
+
break;
|
|
661
|
+
case "glob":
|
|
662
|
+
lines.push("```bash");
|
|
663
|
+
lines.push("kon glob <pattern> [path]");
|
|
664
|
+
lines.push("```");
|
|
665
|
+
break;
|
|
666
|
+
case "grep":
|
|
667
|
+
lines.push("```bash");
|
|
668
|
+
lines.push("kon grep <pattern> [path] [--glob <filter>] [-i] [-n] [-C <num>]");
|
|
669
|
+
lines.push("```");
|
|
670
|
+
break;
|
|
671
|
+
case "bash":
|
|
672
|
+
lines.push("```bash");
|
|
673
|
+
lines.push("kon bash <command> [args...]");
|
|
674
|
+
lines.push("```");
|
|
675
|
+
break;
|
|
676
|
+
default:
|
|
677
|
+
lines.push("```bash");
|
|
678
|
+
lines.push(`kon ${tool.name} [args...]`);
|
|
679
|
+
lines.push("```");
|
|
680
|
+
}
|
|
681
|
+
} else if (tool.type === "mcp") {
|
|
682
|
+
lines.push("```bash");
|
|
683
|
+
lines.push(`kon ${tool.name} <mcp-tool-name> [json-args]`);
|
|
684
|
+
lines.push("```");
|
|
685
|
+
} else {
|
|
686
|
+
lines.push("```bash");
|
|
687
|
+
lines.push(`kon ${tool.name} [args...]`);
|
|
688
|
+
lines.push("```");
|
|
689
|
+
}
|
|
690
|
+
lines.push("");
|
|
691
|
+
if (tool.args && tool.args.length > 0) {
|
|
692
|
+
lines.push("## Arguments");
|
|
693
|
+
lines.push("");
|
|
694
|
+
for (const arg of tool.args) {
|
|
695
|
+
const req = arg.required ? " *(required)*" : "";
|
|
696
|
+
const def = arg.default ? ` (default: \`${arg.default}\`)` : "";
|
|
697
|
+
lines.push(`- \`${arg.name}\`${req}: ${arg.description}${def}`);
|
|
698
|
+
}
|
|
699
|
+
lines.push("");
|
|
700
|
+
}
|
|
701
|
+
if (tool.type === "mcp" && tool.mcpTools && tool.mcpTools.length > 0) {
|
|
702
|
+
lines.push("## Available MCP Tools");
|
|
703
|
+
lines.push("");
|
|
704
|
+
for (const mcpTool of tool.mcpTools) {
|
|
705
|
+
lines.push(`### ${mcpTool.name}`);
|
|
706
|
+
lines.push("");
|
|
707
|
+
lines.push(mcpTool.description);
|
|
708
|
+
lines.push("");
|
|
709
|
+
lines.push("**Input Schema:**");
|
|
710
|
+
lines.push("");
|
|
711
|
+
lines.push("```json");
|
|
712
|
+
lines.push(JSON.stringify(mcpTool.inputSchema, null, 2));
|
|
713
|
+
lines.push("```");
|
|
714
|
+
lines.push("");
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
return lines.join("\n");
|
|
718
|
+
}
|
|
719
|
+
async function generateSkillZip(serverName, serverUrl, token, tools) {
|
|
484
720
|
let skillConfig = { servers: {} };
|
|
485
721
|
try {
|
|
486
|
-
const raw = await
|
|
722
|
+
const raw = await readFile3("/mnt/skills/user/gigai/config.json", "utf8");
|
|
487
723
|
const existing = JSON.parse(raw);
|
|
488
724
|
if (existing.servers) {
|
|
489
725
|
skillConfig = existing;
|
|
@@ -504,21 +740,31 @@ async function generateSkillZip(serverName, serverUrl, token) {
|
|
|
504
740
|
skillConfig.activeServer = serverName;
|
|
505
741
|
}
|
|
506
742
|
const configJson = JSON.stringify(skillConfig, null, 2) + "\n";
|
|
507
|
-
|
|
743
|
+
const entries = [
|
|
508
744
|
{ path: "gigai/SKILL.md", data: Buffer.from(SKILL_MD, "utf8") },
|
|
509
745
|
{ path: "gigai/config.json", data: Buffer.from(configJson, "utf8") }
|
|
510
|
-
]
|
|
746
|
+
];
|
|
747
|
+
if (tools && tools.length > 0) {
|
|
748
|
+
for (const tool of tools) {
|
|
749
|
+
const md = generateToolMarkdown(tool);
|
|
750
|
+
entries.push({
|
|
751
|
+
path: `gigai/tools/${tool.name}.md`,
|
|
752
|
+
data: Buffer.from(md, "utf8")
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
return createZip(entries);
|
|
511
757
|
}
|
|
512
758
|
async function writeSkillZip(zip) {
|
|
513
759
|
const outputsDir = "/mnt/user-data/outputs";
|
|
514
760
|
try {
|
|
515
|
-
await
|
|
761
|
+
await mkdir3(outputsDir, { recursive: true });
|
|
516
762
|
const outPath = `${outputsDir}/gigai.zip`;
|
|
517
|
-
await
|
|
763
|
+
await writeFile3(outPath, zip);
|
|
518
764
|
return outPath;
|
|
519
765
|
} catch {
|
|
520
766
|
const outPath = "gigai.zip";
|
|
521
|
-
await
|
|
767
|
+
await writeFile3(outPath, zip);
|
|
522
768
|
return outPath;
|
|
523
769
|
}
|
|
524
770
|
}
|
|
@@ -608,8 +854,21 @@ async function pair(code, serverUrl) {
|
|
|
608
854
|
});
|
|
609
855
|
await addServer(res.serverName, serverUrl, res.encryptedToken);
|
|
610
856
|
console.log(`Paired with "${res.serverName}" successfully!`);
|
|
857
|
+
let toolDetails;
|
|
858
|
+
try {
|
|
859
|
+
const session = await connect();
|
|
860
|
+
const authedHttp = createHttpClient(session.serverUrl, session.sessionToken);
|
|
861
|
+
const tools = await fetchTools(authedHttp);
|
|
862
|
+
toolDetails = await Promise.all(
|
|
863
|
+
tools.map(async (t) => {
|
|
864
|
+
const { tool } = await fetchToolDetail(authedHttp, t.name);
|
|
865
|
+
return tool;
|
|
866
|
+
})
|
|
867
|
+
);
|
|
868
|
+
} catch {
|
|
869
|
+
}
|
|
611
870
|
const existing = await hasExistingSkill();
|
|
612
|
-
const zip = await generateSkillZip(res.serverName, serverUrl, res.encryptedToken);
|
|
871
|
+
const zip = await generateSkillZip(res.serverName, serverUrl, res.encryptedToken, toolDetails);
|
|
613
872
|
const outPath = await writeSkillZip(zip);
|
|
614
873
|
console.log(`
|
|
615
874
|
Skill zip written to: ${outPath}`);
|
|
@@ -620,38 +879,6 @@ Skill zip written to: ${outPath}`);
|
|
|
620
879
|
}
|
|
621
880
|
}
|
|
622
881
|
|
|
623
|
-
// ../cli/src/discover.ts
|
|
624
|
-
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
625
|
-
import { join as join2 } from "path";
|
|
626
|
-
import { homedir as homedir2 } from "os";
|
|
627
|
-
var MANIFEST_TTL = 5 * 60 * 1e3;
|
|
628
|
-
function getManifestPath() {
|
|
629
|
-
const dir = process.env.GIGAI_CONFIG_DIR ?? join2(homedir2(), ".gigai");
|
|
630
|
-
return join2(dir, "tool-manifest.json");
|
|
631
|
-
}
|
|
632
|
-
async function fetchTools(http) {
|
|
633
|
-
try {
|
|
634
|
-
const raw = await readFile3(getManifestPath(), "utf8");
|
|
635
|
-
const cache = JSON.parse(raw);
|
|
636
|
-
if (Date.now() - cache.fetchedAt < MANIFEST_TTL) {
|
|
637
|
-
return cache.tools;
|
|
638
|
-
}
|
|
639
|
-
} catch {
|
|
640
|
-
}
|
|
641
|
-
const res = await http.get("/tools");
|
|
642
|
-
try {
|
|
643
|
-
const dir = process.env.GIGAI_CONFIG_DIR ?? join2(homedir2(), ".gigai");
|
|
644
|
-
await mkdir3(dir, { recursive: true });
|
|
645
|
-
const cache = { tools: res.tools, fetchedAt: Date.now() };
|
|
646
|
-
await writeFile3(getManifestPath(), JSON.stringify(cache));
|
|
647
|
-
} catch {
|
|
648
|
-
}
|
|
649
|
-
return res.tools;
|
|
650
|
-
}
|
|
651
|
-
async function fetchToolDetail(http, name) {
|
|
652
|
-
return http.get(`/tools/${encodeURIComponent(name)}`);
|
|
653
|
-
}
|
|
654
|
-
|
|
655
882
|
// ../cli/src/exec.ts
|
|
656
883
|
async function execTool(http, name, args, timeout) {
|
|
657
884
|
const res = await http.post("/exec", {
|
|
@@ -753,7 +980,8 @@ function formatStatus(config) {
|
|
|
753
980
|
for (const name of serverNames) {
|
|
754
981
|
const entry = config.servers[name];
|
|
755
982
|
const active = name === config.activeServer ? " (active)" : "";
|
|
756
|
-
|
|
983
|
+
const platformTag = entry.platform ? ` [${entry.platform}]` : "";
|
|
984
|
+
lines.push(` ${name}${active}${platformTag} ${entry.server}`);
|
|
757
985
|
if (entry.sessionExpiresAt) {
|
|
758
986
|
const remaining = entry.sessionExpiresAt - Date.now();
|
|
759
987
|
if (remaining > 0) {
|
|
@@ -777,6 +1005,8 @@ var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
777
1005
|
"upload",
|
|
778
1006
|
"download",
|
|
779
1007
|
"version",
|
|
1008
|
+
"skill",
|
|
1009
|
+
"cron",
|
|
780
1010
|
"--help",
|
|
781
1011
|
"-h"
|
|
782
1012
|
]);
|
|
@@ -887,6 +1117,136 @@ function runCitty() {
|
|
|
887
1117
|
console.log(`kon v${VERSION}`);
|
|
888
1118
|
}
|
|
889
1119
|
});
|
|
1120
|
+
const skillCommand = defineCommand({
|
|
1121
|
+
meta: { name: "skill", description: "Regenerate the skill zip with current tool details" },
|
|
1122
|
+
async run() {
|
|
1123
|
+
const { serverUrl, sessionToken } = await connect();
|
|
1124
|
+
const http = createHttpClient(serverUrl, sessionToken);
|
|
1125
|
+
const tools = await fetchTools(http);
|
|
1126
|
+
console.log(`Fetching details for ${tools.length} tool(s)...`);
|
|
1127
|
+
const toolDetails = await Promise.all(
|
|
1128
|
+
tools.map(async (t) => {
|
|
1129
|
+
const { tool } = await fetchToolDetail(http, t.name);
|
|
1130
|
+
return tool;
|
|
1131
|
+
})
|
|
1132
|
+
);
|
|
1133
|
+
const config = await readConfig();
|
|
1134
|
+
const activeServer = config.activeServer;
|
|
1135
|
+
if (!activeServer || !config.servers[activeServer]) {
|
|
1136
|
+
throw new Error("No active server. Run 'kon connect' first.");
|
|
1137
|
+
}
|
|
1138
|
+
const entry = config.servers[activeServer];
|
|
1139
|
+
const zip = await generateSkillZip(activeServer, entry.server, entry.token, toolDetails);
|
|
1140
|
+
const outPath = await writeSkillZip(zip);
|
|
1141
|
+
console.log(`
|
|
1142
|
+
Skill zip written to: ${outPath}`);
|
|
1143
|
+
console.log(`Included ${toolDetails.length} tool documentation file(s).`);
|
|
1144
|
+
console.log("Upload this file as a skill in Claude (Settings \u2192 Customize \u2192 Upload Skill).");
|
|
1145
|
+
}
|
|
1146
|
+
});
|
|
1147
|
+
const cronAddCommand = defineCommand({
|
|
1148
|
+
meta: { name: "add", description: "Schedule a tool execution" },
|
|
1149
|
+
args: {
|
|
1150
|
+
at: { type: "string", description: "Human-readable time (e.g. '9:00 AM tomorrow')" }
|
|
1151
|
+
},
|
|
1152
|
+
async run({ args }) {
|
|
1153
|
+
const { serverUrl, sessionToken } = await connect();
|
|
1154
|
+
const http = createHttpClient(serverUrl, sessionToken);
|
|
1155
|
+
const rawArgs = process.argv.slice(4);
|
|
1156
|
+
const positional = [];
|
|
1157
|
+
let atValue = args.at;
|
|
1158
|
+
for (let i = 0; i < rawArgs.length; i++) {
|
|
1159
|
+
if (rawArgs[i] === "--at" && rawArgs[i + 1]) {
|
|
1160
|
+
atValue = rawArgs[i + 1];
|
|
1161
|
+
i++;
|
|
1162
|
+
} else if (!rawArgs[i].startsWith("--")) {
|
|
1163
|
+
positional.push(rawArgs[i]);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
let schedule;
|
|
1167
|
+
let tool;
|
|
1168
|
+
let toolArgs;
|
|
1169
|
+
let oneShot = false;
|
|
1170
|
+
if (atValue) {
|
|
1171
|
+
tool = positional[0];
|
|
1172
|
+
toolArgs = positional.slice(1);
|
|
1173
|
+
const res2 = await http.post("/cron", {
|
|
1174
|
+
schedule: `@at ${atValue}`,
|
|
1175
|
+
tool,
|
|
1176
|
+
args: toolArgs,
|
|
1177
|
+
oneShot: true
|
|
1178
|
+
});
|
|
1179
|
+
console.log(`Scheduled: ${res2.job.id}`);
|
|
1180
|
+
console.log(` ${tool} ${toolArgs.join(" ")}`);
|
|
1181
|
+
if (res2.job.nextRun) {
|
|
1182
|
+
console.log(` Next run: ${new Date(res2.job.nextRun).toLocaleString()}`);
|
|
1183
|
+
}
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
schedule = positional[0];
|
|
1187
|
+
tool = positional[1];
|
|
1188
|
+
toolArgs = positional.slice(2);
|
|
1189
|
+
if (!schedule || !tool) {
|
|
1190
|
+
console.error("Usage:");
|
|
1191
|
+
console.error(' kon cron add "0 9 * * *" <tool> [args...]');
|
|
1192
|
+
console.error(' kon cron add --at "9:00 AM tomorrow" <tool> [args...]');
|
|
1193
|
+
process.exitCode = 1;
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
const res = await http.post("/cron", {
|
|
1197
|
+
schedule,
|
|
1198
|
+
tool,
|
|
1199
|
+
args: toolArgs
|
|
1200
|
+
});
|
|
1201
|
+
console.log(`Scheduled: ${res.job.id}`);
|
|
1202
|
+
console.log(` ${schedule} \u2014 ${tool} ${toolArgs.join(" ")}`);
|
|
1203
|
+
if (res.job.nextRun) {
|
|
1204
|
+
console.log(` Next run: ${new Date(res.job.nextRun).toLocaleString()}`);
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
});
|
|
1208
|
+
const cronListCommand = defineCommand({
|
|
1209
|
+
meta: { name: "list", description: "List scheduled jobs" },
|
|
1210
|
+
async run() {
|
|
1211
|
+
const { serverUrl, sessionToken } = await connect();
|
|
1212
|
+
const http = createHttpClient(serverUrl, sessionToken);
|
|
1213
|
+
const res = await http.get("/cron");
|
|
1214
|
+
if (res.jobs.length === 0) {
|
|
1215
|
+
console.log("No scheduled jobs.");
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
for (const job of res.jobs) {
|
|
1219
|
+
const status = job.enabled ? "active" : "disabled";
|
|
1220
|
+
const cmd = `${job.tool} ${job.args.join(" ")}`.trim();
|
|
1221
|
+
const next = job.nextRun ? new Date(job.nextRun).toLocaleString() : "\u2014";
|
|
1222
|
+
const last = job.lastRun ? new Date(job.lastRun).toLocaleString() : "never";
|
|
1223
|
+
console.log(`${job.id} [${status}] ${job.schedule}`);
|
|
1224
|
+
console.log(` ${cmd}`);
|
|
1225
|
+
console.log(` next: ${next} last: ${last}`);
|
|
1226
|
+
console.log();
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
});
|
|
1230
|
+
const cronRemoveCommand = defineCommand({
|
|
1231
|
+
meta: { name: "remove", description: "Remove a scheduled job" },
|
|
1232
|
+
args: {
|
|
1233
|
+
id: { type: "positional", description: "Job ID", required: true }
|
|
1234
|
+
},
|
|
1235
|
+
async run({ args }) {
|
|
1236
|
+
const { serverUrl, sessionToken } = await connect();
|
|
1237
|
+
const http = createHttpClient(serverUrl, sessionToken);
|
|
1238
|
+
await http.delete(`/cron/${encodeURIComponent(args.id)}`);
|
|
1239
|
+
console.log(`Removed: ${args.id}`);
|
|
1240
|
+
}
|
|
1241
|
+
});
|
|
1242
|
+
const cronCommand = defineCommand({
|
|
1243
|
+
meta: { name: "cron", description: "Manage scheduled tasks" },
|
|
1244
|
+
subCommands: {
|
|
1245
|
+
add: cronAddCommand,
|
|
1246
|
+
list: cronListCommand,
|
|
1247
|
+
remove: cronRemoveCommand
|
|
1248
|
+
}
|
|
1249
|
+
});
|
|
890
1250
|
const main = defineCommand({
|
|
891
1251
|
meta: {
|
|
892
1252
|
name: "kon",
|
|
@@ -901,7 +1261,9 @@ function runCitty() {
|
|
|
901
1261
|
status: statusCommand,
|
|
902
1262
|
upload: uploadCommand,
|
|
903
1263
|
download: downloadCommand,
|
|
904
|
-
version: versionCommand
|
|
1264
|
+
version: versionCommand,
|
|
1265
|
+
skill: skillCommand,
|
|
1266
|
+
cron: cronCommand
|
|
905
1267
|
}
|
|
906
1268
|
});
|
|
907
1269
|
runMain(main);
|