@schuttdev/kon 0.2.9 → 0.3.1
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 +362 -47
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -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.1";
|
|
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
|
|
@@ -505,32 +575,151 @@ For MCP tools, the first arg is the MCP tool name:
|
|
|
505
575
|
kon <mcp-tool> <mcp-action> [json-args]
|
|
506
576
|
\`\`\`
|
|
507
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
|
+
|
|
508
589
|
## Multiple servers
|
|
509
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
|
+
|
|
510
593
|
\`\`\`bash
|
|
511
|
-
kon
|
|
512
|
-
kon
|
|
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
|
|
513
597
|
\`\`\`
|
|
514
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
|
+
|
|
515
613
|
## Important
|
|
516
614
|
|
|
517
615
|
- Always run the setup block before first use in a new conversation
|
|
518
616
|
- All commands execute on the **user's machine**, not in this sandbox
|
|
519
617
|
- If you get auth errors, run \`kon connect\` to refresh the session
|
|
520
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
|
|
521
620
|
`;
|
|
522
621
|
async function hasExistingSkill() {
|
|
523
622
|
try {
|
|
524
|
-
await
|
|
623
|
+
await readFile3("/mnt/skills/user/gigai/config.json", "utf8");
|
|
525
624
|
return true;
|
|
526
625
|
} catch {
|
|
527
626
|
return false;
|
|
528
627
|
}
|
|
529
628
|
}
|
|
530
|
-
|
|
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) {
|
|
531
720
|
let skillConfig = { servers: {} };
|
|
532
721
|
try {
|
|
533
|
-
const raw = await
|
|
722
|
+
const raw = await readFile3("/mnt/skills/user/gigai/config.json", "utf8");
|
|
534
723
|
const existing = JSON.parse(raw);
|
|
535
724
|
if (existing.servers) {
|
|
536
725
|
skillConfig = existing;
|
|
@@ -551,21 +740,31 @@ async function generateSkillZip(serverName, serverUrl, token) {
|
|
|
551
740
|
skillConfig.activeServer = serverName;
|
|
552
741
|
}
|
|
553
742
|
const configJson = JSON.stringify(skillConfig, null, 2) + "\n";
|
|
554
|
-
|
|
743
|
+
const entries = [
|
|
555
744
|
{ path: "gigai/SKILL.md", data: Buffer.from(SKILL_MD, "utf8") },
|
|
556
745
|
{ path: "gigai/config.json", data: Buffer.from(configJson, "utf8") }
|
|
557
|
-
]
|
|
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);
|
|
558
757
|
}
|
|
559
758
|
async function writeSkillZip(zip) {
|
|
560
759
|
const outputsDir = "/mnt/user-data/outputs";
|
|
561
760
|
try {
|
|
562
|
-
await
|
|
761
|
+
await mkdir3(outputsDir, { recursive: true });
|
|
563
762
|
const outPath = `${outputsDir}/gigai.zip`;
|
|
564
|
-
await
|
|
763
|
+
await writeFile3(outPath, zip);
|
|
565
764
|
return outPath;
|
|
566
765
|
} catch {
|
|
567
766
|
const outPath = "gigai.zip";
|
|
568
|
-
await
|
|
767
|
+
await writeFile3(outPath, zip);
|
|
569
768
|
return outPath;
|
|
570
769
|
}
|
|
571
770
|
}
|
|
@@ -655,8 +854,21 @@ async function pair(code, serverUrl) {
|
|
|
655
854
|
});
|
|
656
855
|
await addServer(res.serverName, serverUrl, res.encryptedToken);
|
|
657
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
|
+
}
|
|
658
870
|
const existing = await hasExistingSkill();
|
|
659
|
-
const zip = await generateSkillZip(res.serverName, serverUrl, res.encryptedToken);
|
|
871
|
+
const zip = await generateSkillZip(res.serverName, serverUrl, res.encryptedToken, toolDetails);
|
|
660
872
|
const outPath = await writeSkillZip(zip);
|
|
661
873
|
console.log(`
|
|
662
874
|
Skill zip written to: ${outPath}`);
|
|
@@ -667,38 +879,6 @@ Skill zip written to: ${outPath}`);
|
|
|
667
879
|
}
|
|
668
880
|
}
|
|
669
881
|
|
|
670
|
-
// ../cli/src/discover.ts
|
|
671
|
-
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
672
|
-
import { join as join2 } from "path";
|
|
673
|
-
import { homedir as homedir2 } from "os";
|
|
674
|
-
var MANIFEST_TTL = 5 * 60 * 1e3;
|
|
675
|
-
function getManifestPath() {
|
|
676
|
-
const dir = process.env.GIGAI_CONFIG_DIR ?? join2(homedir2(), ".gigai");
|
|
677
|
-
return join2(dir, "tool-manifest.json");
|
|
678
|
-
}
|
|
679
|
-
async function fetchTools(http) {
|
|
680
|
-
try {
|
|
681
|
-
const raw = await readFile3(getManifestPath(), "utf8");
|
|
682
|
-
const cache = JSON.parse(raw);
|
|
683
|
-
if (Date.now() - cache.fetchedAt < MANIFEST_TTL) {
|
|
684
|
-
return cache.tools;
|
|
685
|
-
}
|
|
686
|
-
} catch {
|
|
687
|
-
}
|
|
688
|
-
const res = await http.get("/tools");
|
|
689
|
-
try {
|
|
690
|
-
const dir = process.env.GIGAI_CONFIG_DIR ?? join2(homedir2(), ".gigai");
|
|
691
|
-
await mkdir3(dir, { recursive: true });
|
|
692
|
-
const cache = { tools: res.tools, fetchedAt: Date.now() };
|
|
693
|
-
await writeFile3(getManifestPath(), JSON.stringify(cache));
|
|
694
|
-
} catch {
|
|
695
|
-
}
|
|
696
|
-
return res.tools;
|
|
697
|
-
}
|
|
698
|
-
async function fetchToolDetail(http, name) {
|
|
699
|
-
return http.get(`/tools/${encodeURIComponent(name)}`);
|
|
700
|
-
}
|
|
701
|
-
|
|
702
882
|
// ../cli/src/exec.ts
|
|
703
883
|
async function execTool(http, name, args, timeout) {
|
|
704
884
|
const res = await http.post("/exec", {
|
|
@@ -800,7 +980,8 @@ function formatStatus(config) {
|
|
|
800
980
|
for (const name of serverNames) {
|
|
801
981
|
const entry = config.servers[name];
|
|
802
982
|
const active = name === config.activeServer ? " (active)" : "";
|
|
803
|
-
|
|
983
|
+
const platformTag = entry.platform ? ` [${entry.platform}]` : "";
|
|
984
|
+
lines.push(` ${name}${active}${platformTag} ${entry.server}`);
|
|
804
985
|
if (entry.sessionExpiresAt) {
|
|
805
986
|
const remaining = entry.sessionExpiresAt - Date.now();
|
|
806
987
|
if (remaining > 0) {
|
|
@@ -824,6 +1005,8 @@ var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
824
1005
|
"upload",
|
|
825
1006
|
"download",
|
|
826
1007
|
"version",
|
|
1008
|
+
"skill",
|
|
1009
|
+
"cron",
|
|
827
1010
|
"--help",
|
|
828
1011
|
"-h"
|
|
829
1012
|
]);
|
|
@@ -934,6 +1117,136 @@ function runCitty() {
|
|
|
934
1117
|
console.log(`kon v${VERSION}`);
|
|
935
1118
|
}
|
|
936
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
|
+
});
|
|
937
1250
|
const main = defineCommand({
|
|
938
1251
|
meta: {
|
|
939
1252
|
name: "kon",
|
|
@@ -948,7 +1261,9 @@ function runCitty() {
|
|
|
948
1261
|
status: statusCommand,
|
|
949
1262
|
upload: uploadCommand,
|
|
950
1263
|
download: downloadCommand,
|
|
951
|
-
version: versionCommand
|
|
1264
|
+
version: versionCommand,
|
|
1265
|
+
skill: skillCommand,
|
|
1266
|
+
cron: cronCommand
|
|
952
1267
|
}
|
|
953
1268
|
});
|
|
954
1269
|
runMain(main);
|