@rayul/kordis 0.1.13 → 0.1.15
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/assets/kordis-publishing/SKILL.md +12 -0
- package/assets/kordis-using/SKILL.md +8 -0
- package/dist/index.js +486 -88
- package/package.json +1 -1
|
@@ -25,6 +25,18 @@ Publish a valid, AI-review-passing version. ALWAYS dry-run first.
|
|
|
25
25
|
`version` differs from that result, the API rejects with `usage`. Either
|
|
26
26
|
drop the `version` field on updates, set `--bump` to match, or align
|
|
27
27
|
them.
|
|
28
|
+
- RECOMMENDED: **omit `version:` from frontmatter on update pushes** and
|
|
29
|
+
use only `--bump patch|minor|major` to control versioning. Having both
|
|
30
|
+
is the most common push failure.
|
|
31
|
+
|
|
32
|
+
## Deleting a package
|
|
33
|
+
|
|
34
|
+
There is **no `kordis delete` CLI command**. Options:
|
|
35
|
+
- Web UI: `https://kordis.store/p/<full_slug>` (if a delete button is available)
|
|
36
|
+
- Registry owner: soft-delete via the prod Postgres
|
|
37
|
+
(`UPDATE packages SET deleted_at = NOW() WHERE full_slug = '...'`)
|
|
38
|
+
- Workaround: push a new version with `visibility: unlisted` or a
|
|
39
|
+
deprecation notice in the body
|
|
28
40
|
|
|
29
41
|
If the body contains third-party content, MUST preserve the original copyright
|
|
30
42
|
and license text inside the body and link to source via `homepage`.
|
|
@@ -27,6 +27,14 @@ DEFAULT to `read` when you spontaneously want to consult a skill. DEFAULT to
|
|
|
27
27
|
- Trust gate: only `review_status=passed` returned by default. To get a
|
|
28
28
|
`pending_review` body, pass `--allow-pending` AFTER warning the user the
|
|
29
29
|
package has not been AI-reviewed.
|
|
30
|
+
- Note: `jit_blocked` fires for BOTH ineligible types AND packages still
|
|
31
|
+
under review. Check `kordis info --json` to distinguish: `review_status`
|
|
32
|
+
tells you which case you're in.
|
|
33
|
+
|
|
34
|
+
`pull` vs `read` on pending packages: `pull` writes files even while
|
|
35
|
+
`review_status=pending_review`; `read` blocks. When you need the content
|
|
36
|
+
immediately (e.g. to verify a package you just pushed), use `pull` with
|
|
37
|
+
`--tool <name>` — it bypasses the JIT trust gate.
|
|
30
38
|
|
|
31
39
|
## Commands
|
|
32
40
|
|
package/dist/index.js
CHANGED
|
@@ -821,10 +821,19 @@ var init_client = __esm({
|
|
|
821
821
|
revokeAllSessions() {
|
|
822
822
|
return this.request("/auth/sessions/revoke-all", { method: "POST" });
|
|
823
823
|
}
|
|
824
|
-
// ───
|
|
824
|
+
// ─── authenticated user ────────────────────────────────────────────────
|
|
825
825
|
me() {
|
|
826
826
|
return this.request("/api/me");
|
|
827
827
|
}
|
|
828
|
+
listMyPackages(opts = {}) {
|
|
829
|
+
return this.request("/api/me/packages", {
|
|
830
|
+
query: {
|
|
831
|
+
...opts.page !== void 0 ? { page: opts.page } : {},
|
|
832
|
+
...opts.limit !== void 0 ? { limit: opts.limit } : {},
|
|
833
|
+
...opts.sort !== void 0 ? { sort: opts.sort } : {}
|
|
834
|
+
}
|
|
835
|
+
});
|
|
836
|
+
}
|
|
828
837
|
// ─── packages ──────────────────────────────────────────────────────────
|
|
829
838
|
publish(body, opts = {}) {
|
|
830
839
|
return this.request("/api/packages", {
|
|
@@ -842,6 +851,9 @@ var init_client = __esm({
|
|
|
842
851
|
getPackage(username, slug) {
|
|
843
852
|
return this.request(`/api/packages/${username}/${slug}`);
|
|
844
853
|
}
|
|
854
|
+
deletePackage(username, slug) {
|
|
855
|
+
return this.request(`/api/packages/${username}/${slug}`, { method: "DELETE" });
|
|
856
|
+
}
|
|
845
857
|
getContent(username, slug, opts = {}) {
|
|
846
858
|
return this.request(
|
|
847
859
|
`/api/packages/${username}/${slug}/content`,
|
|
@@ -972,7 +984,7 @@ var init_src2 = __esm({
|
|
|
972
984
|
|
|
973
985
|
// src/index.ts
|
|
974
986
|
init_src2();
|
|
975
|
-
import { Command as
|
|
987
|
+
import { Command as Command17 } from "commander";
|
|
976
988
|
|
|
977
989
|
// src/commands/login.ts
|
|
978
990
|
import { Command } from "commander";
|
|
@@ -1590,8 +1602,8 @@ async function bundleSkillZip(zipPath) {
|
|
|
1590
1602
|
|
|
1591
1603
|
// src/commands/pull.ts
|
|
1592
1604
|
import { Command as Command5 } from "commander";
|
|
1593
|
-
import { mkdir as
|
|
1594
|
-
import { dirname as dirname2, join as
|
|
1605
|
+
import { mkdir as mkdir4, writeFile as writeFile4 } from "fs/promises";
|
|
1606
|
+
import { dirname as dirname2, join as join6 } from "path";
|
|
1595
1607
|
|
|
1596
1608
|
// ../../packages/harness-schema/src/deps.ts
|
|
1597
1609
|
init_src();
|
|
@@ -2418,7 +2430,91 @@ function getTranslator(type, format) {
|
|
|
2418
2430
|
return fn;
|
|
2419
2431
|
}
|
|
2420
2432
|
|
|
2433
|
+
// src/lockfile.ts
|
|
2434
|
+
import { homedir as homedir3 } from "os";
|
|
2435
|
+
import { join as join5 } from "path";
|
|
2436
|
+
import { readFile as readFile4, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
2437
|
+
var LOCKFILE_PATH = join5(homedir3(), ".kordis", "installed.json");
|
|
2438
|
+
async function readLockfile() {
|
|
2439
|
+
try {
|
|
2440
|
+
const raw = await readFile4(LOCKFILE_PATH, "utf8");
|
|
2441
|
+
return JSON.parse(raw);
|
|
2442
|
+
} catch {
|
|
2443
|
+
return { packages: {} };
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
async function writeLockfile(lf) {
|
|
2447
|
+
await mkdir3(join5(homedir3(), ".kordis"), { recursive: true });
|
|
2448
|
+
await writeFile3(LOCKFILE_PATH, JSON.stringify(lf, null, 2) + "\n", "utf8");
|
|
2449
|
+
}
|
|
2450
|
+
async function recordInstalled(fullSlug, entry) {
|
|
2451
|
+
const lf = await readLockfile();
|
|
2452
|
+
lf.packages[fullSlug] = entry;
|
|
2453
|
+
await writeLockfile(lf);
|
|
2454
|
+
}
|
|
2455
|
+
|
|
2421
2456
|
// src/commands/pull.ts
|
|
2457
|
+
async function doPullInstall(fullSlug, tool, installRoot) {
|
|
2458
|
+
const [username, slug] = fullSlug.split("/");
|
|
2459
|
+
if (!username || !slug) throw new Error('full_slug must be "{username}/{slug}"');
|
|
2460
|
+
const bundle = await useClient();
|
|
2461
|
+
const detail = await bundle.api.getPackage(username, slug);
|
|
2462
|
+
await bundle.api.pull(username, slug);
|
|
2463
|
+
const contentRes = await bundle.api.getContent(username, slug, { format: "canonical" });
|
|
2464
|
+
const parsed = validatePackageSource(contentRes.content);
|
|
2465
|
+
if (!parsed.ok || !parsed.type || !parsed.frontmatter) {
|
|
2466
|
+
throw new Error("package stored content is not currently valid");
|
|
2467
|
+
}
|
|
2468
|
+
const written = [];
|
|
2469
|
+
const notes = [];
|
|
2470
|
+
const format = tool;
|
|
2471
|
+
try {
|
|
2472
|
+
const translator = getTranslator(parsed.type, format);
|
|
2473
|
+
const result = translator({
|
|
2474
|
+
fullSlug,
|
|
2475
|
+
frontmatter: parsed.frontmatter.data,
|
|
2476
|
+
body: parsed.body,
|
|
2477
|
+
version: contentRes.version,
|
|
2478
|
+
assets: contentRes.assets
|
|
2479
|
+
});
|
|
2480
|
+
for (const f of result.files) {
|
|
2481
|
+
const abs = join6(installRoot, f.path);
|
|
2482
|
+
await mkdir4(dirname2(abs), { recursive: true });
|
|
2483
|
+
await writeEmittedFile(abs, f.content, f.encoding);
|
|
2484
|
+
written.push(abs);
|
|
2485
|
+
}
|
|
2486
|
+
notes.push(...result.notes);
|
|
2487
|
+
} catch (err) {
|
|
2488
|
+
if (err instanceof TranslatorNotSupportedError) {
|
|
2489
|
+
const canonical = getTranslator(parsed.type, "canonical");
|
|
2490
|
+
const result = canonical({
|
|
2491
|
+
fullSlug,
|
|
2492
|
+
frontmatter: parsed.frontmatter.data,
|
|
2493
|
+
body: parsed.body,
|
|
2494
|
+
version: contentRes.version,
|
|
2495
|
+
assets: contentRes.assets
|
|
2496
|
+
});
|
|
2497
|
+
notes.push(
|
|
2498
|
+
`Tool "${format}" is not supported for type "${parsed.type}"; installed canonical form instead.`
|
|
2499
|
+
);
|
|
2500
|
+
for (const f of result.files) {
|
|
2501
|
+
const abs = join6(installRoot, f.path);
|
|
2502
|
+
await mkdir4(dirname2(abs), { recursive: true });
|
|
2503
|
+
await writeEmittedFile(abs, f.content, f.encoding);
|
|
2504
|
+
written.push(abs);
|
|
2505
|
+
}
|
|
2506
|
+
} else {
|
|
2507
|
+
throw err;
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
await recordInstalled(fullSlug, {
|
|
2511
|
+
version: detail.current_version,
|
|
2512
|
+
tool,
|
|
2513
|
+
target: installRoot,
|
|
2514
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2515
|
+
});
|
|
2516
|
+
return { version: detail.current_version, tool, installRoot, written, notes };
|
|
2517
|
+
}
|
|
2422
2518
|
function pullCommand() {
|
|
2423
2519
|
return new Command5("pull").description("install a package into the current project").argument("<full_slug>", "package identifier {username}/{slug}").option("--tool <name>", "target tool format (default: auto-detect)").option("--target <path>", "install into a subdirectory (default: cwd)").option("--with-recommends", "also install recommended dependencies", false).action(
|
|
2424
2520
|
async (fullSlug, opts) => {
|
|
@@ -2426,7 +2522,6 @@ function pullCommand() {
|
|
|
2426
2522
|
if (!username || !slug) {
|
|
2427
2523
|
throw new Error('full_slug must be "{username}/{slug}"');
|
|
2428
2524
|
}
|
|
2429
|
-
const bundle = await useClient();
|
|
2430
2525
|
const userCwd = process.env["INIT_CWD"] ?? process.cwd();
|
|
2431
2526
|
const installRoot = opts.target ?? userCwd;
|
|
2432
2527
|
const detected = opts.tool ?? await detectTool(installRoot);
|
|
@@ -2436,73 +2531,23 @@ function pullCommand() {
|
|
|
2436
2531
|
);
|
|
2437
2532
|
}
|
|
2438
2533
|
const spin = spinner(`Pulling ${fullSlug}`);
|
|
2439
|
-
const
|
|
2440
|
-
await bundle.api.pull(username, slug);
|
|
2441
|
-
const contentRes = await bundle.api.getContent(username, slug, {
|
|
2442
|
-
format: "canonical"
|
|
2443
|
-
});
|
|
2534
|
+
const result = await doPullInstall(fullSlug, detected, installRoot);
|
|
2444
2535
|
spin.succeed();
|
|
2445
|
-
const parsed = validatePackageSource(contentRes.content);
|
|
2446
|
-
if (!parsed.ok || !parsed.type || !parsed.frontmatter) {
|
|
2447
|
-
throw new Error("package stored content is not currently valid");
|
|
2448
|
-
}
|
|
2449
|
-
const written = [];
|
|
2450
|
-
const notes = [];
|
|
2451
|
-
const format = detected;
|
|
2452
|
-
try {
|
|
2453
|
-
const translator = getTranslator(parsed.type, format);
|
|
2454
|
-
const result = translator({
|
|
2455
|
-
fullSlug,
|
|
2456
|
-
frontmatter: parsed.frontmatter.data,
|
|
2457
|
-
body: parsed.body,
|
|
2458
|
-
version: contentRes.version,
|
|
2459
|
-
assets: contentRes.assets
|
|
2460
|
-
});
|
|
2461
|
-
for (const f of result.files) {
|
|
2462
|
-
const abs = join5(installRoot, f.path);
|
|
2463
|
-
await mkdir3(dirname2(abs), { recursive: true });
|
|
2464
|
-
await writeEmittedFile(abs, f.content, f.encoding);
|
|
2465
|
-
written.push(abs);
|
|
2466
|
-
}
|
|
2467
|
-
notes.push(...result.notes);
|
|
2468
|
-
} catch (err) {
|
|
2469
|
-
if (err instanceof TranslatorNotSupportedError) {
|
|
2470
|
-
const canonical = getTranslator(parsed.type, "canonical");
|
|
2471
|
-
const result = canonical({
|
|
2472
|
-
fullSlug,
|
|
2473
|
-
frontmatter: parsed.frontmatter.data,
|
|
2474
|
-
body: parsed.body,
|
|
2475
|
-
version: contentRes.version,
|
|
2476
|
-
assets: contentRes.assets
|
|
2477
|
-
});
|
|
2478
|
-
notes.push(
|
|
2479
|
-
`Tool "${format}" is not supported for type "${parsed.type}"; installed canonical form instead.`
|
|
2480
|
-
);
|
|
2481
|
-
for (const f of result.files) {
|
|
2482
|
-
const abs = join5(installRoot, f.path);
|
|
2483
|
-
await mkdir3(dirname2(abs), { recursive: true });
|
|
2484
|
-
await writeEmittedFile(abs, f.content, f.encoding);
|
|
2485
|
-
written.push(abs);
|
|
2486
|
-
}
|
|
2487
|
-
} else {
|
|
2488
|
-
throw err;
|
|
2489
|
-
}
|
|
2490
|
-
}
|
|
2491
2536
|
printDual({
|
|
2492
2537
|
json: {
|
|
2493
2538
|
status: "installed",
|
|
2494
2539
|
full_slug: fullSlug,
|
|
2495
|
-
version:
|
|
2496
|
-
tool_detected:
|
|
2497
|
-
installed_paths: written,
|
|
2498
|
-
notes
|
|
2540
|
+
version: result.version,
|
|
2541
|
+
tool_detected: result.tool,
|
|
2542
|
+
installed_paths: result.written,
|
|
2543
|
+
notes: result.notes
|
|
2499
2544
|
},
|
|
2500
2545
|
human: () => {
|
|
2501
2546
|
printHuman(
|
|
2502
|
-
c.green(`\u2713 Installed ${c.bold(fullSlug)} v${
|
|
2547
|
+
c.green(`\u2713 Installed ${c.bold(fullSlug)} v${result.version}`)
|
|
2503
2548
|
);
|
|
2504
|
-
for (const w of written) printHuman(` ${c.gray("wrote")} ${w}`);
|
|
2505
|
-
for (const n of notes) printHuman(c.yellow(` ! ${n}`));
|
|
2549
|
+
for (const w of result.written) printHuman(` ${c.gray("wrote")} ${w}`);
|
|
2550
|
+
for (const n of result.notes) printHuman(c.yellow(` ! ${n}`));
|
|
2506
2551
|
}
|
|
2507
2552
|
});
|
|
2508
2553
|
}
|
|
@@ -2525,7 +2570,7 @@ async function detectTool(root) {
|
|
|
2525
2570
|
];
|
|
2526
2571
|
for (const [path, tool] of checks) {
|
|
2527
2572
|
try {
|
|
2528
|
-
await stat2(
|
|
2573
|
+
await stat2(join6(root, path));
|
|
2529
2574
|
return tool;
|
|
2530
2575
|
} catch {
|
|
2531
2576
|
}
|
|
@@ -2534,9 +2579,9 @@ async function detectTool(root) {
|
|
|
2534
2579
|
}
|
|
2535
2580
|
async function writeEmittedFile(abs, content, encoding) {
|
|
2536
2581
|
if (encoding === "base64") {
|
|
2537
|
-
await
|
|
2582
|
+
await writeFile4(abs, Buffer.from(content, "base64"));
|
|
2538
2583
|
} else {
|
|
2539
|
-
await
|
|
2584
|
+
await writeFile4(abs, content, "utf8");
|
|
2540
2585
|
}
|
|
2541
2586
|
}
|
|
2542
2587
|
|
|
@@ -2636,9 +2681,9 @@ function logoutCommand() {
|
|
|
2636
2681
|
}
|
|
2637
2682
|
|
|
2638
2683
|
// src/commands/setup.ts
|
|
2639
|
-
import { readFile as
|
|
2640
|
-
import { homedir as
|
|
2641
|
-
import { join as
|
|
2684
|
+
import { readFile as readFile5, writeFile as writeFile5, mkdir as mkdir5, access } from "fs/promises";
|
|
2685
|
+
import { homedir as homedir4 } from "os";
|
|
2686
|
+
import { join as join7, dirname as dirname3 } from "path";
|
|
2642
2687
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2643
2688
|
import { Command as Command9 } from "commander";
|
|
2644
2689
|
var MARKER_START = "<!-- kordis-start -->";
|
|
@@ -2671,18 +2716,18 @@ function assetRoots() {
|
|
|
2671
2716
|
const __dir2 = dirname3(fileURLToPath2(import.meta.url));
|
|
2672
2717
|
return {
|
|
2673
2718
|
// Built binary: bin/kordis.js → assets/ is one level up
|
|
2674
|
-
bundled:
|
|
2719
|
+
bundled: join7(__dir2, "..", "assets"),
|
|
2675
2720
|
// Dev mode: src/commands/setup.ts → 4 levels up to repo root, then packages/skills/
|
|
2676
|
-
source:
|
|
2721
|
+
source: join7(__dir2, "..", "..", "..", "..", "packages", "skills")
|
|
2677
2722
|
};
|
|
2678
2723
|
}
|
|
2679
2724
|
async function pickAssetRoot() {
|
|
2680
2725
|
const { bundled, source } = assetRoots();
|
|
2681
|
-
return await exists(
|
|
2726
|
+
return await exists(join7(bundled, "KORDIS.md")) ? bundled : source;
|
|
2682
2727
|
}
|
|
2683
2728
|
async function loadHubContent() {
|
|
2684
2729
|
const root = await pickAssetRoot();
|
|
2685
|
-
const raw = await
|
|
2730
|
+
const raw = await readFile5(join7(root, "KORDIS.md"), "utf-8");
|
|
2686
2731
|
if (raw.startsWith("---")) {
|
|
2687
2732
|
const end = raw.indexOf("\n---", 4);
|
|
2688
2733
|
if (end !== -1) return raw.slice(end + 4).trimStart();
|
|
@@ -2691,7 +2736,7 @@ async function loadHubContent() {
|
|
|
2691
2736
|
}
|
|
2692
2737
|
async function loadSubSkillContent(name) {
|
|
2693
2738
|
const root = await pickAssetRoot();
|
|
2694
|
-
return
|
|
2739
|
+
return readFile5(join7(root, name, "SKILL.md"), "utf-8");
|
|
2695
2740
|
}
|
|
2696
2741
|
async function injectBlock(filePath, content, dryRun) {
|
|
2697
2742
|
const block = `${MARKER_START}
|
|
@@ -2700,14 +2745,14 @@ ${MARKER_END}`;
|
|
|
2700
2745
|
let existing = "";
|
|
2701
2746
|
let fileExists = true;
|
|
2702
2747
|
try {
|
|
2703
|
-
existing = await
|
|
2748
|
+
existing = await readFile5(filePath, "utf-8");
|
|
2704
2749
|
} catch {
|
|
2705
2750
|
fileExists = false;
|
|
2706
2751
|
}
|
|
2707
2752
|
if (!fileExists) {
|
|
2708
2753
|
if (!dryRun) {
|
|
2709
|
-
await
|
|
2710
|
-
await
|
|
2754
|
+
await mkdir5(dirname3(filePath), { recursive: true });
|
|
2755
|
+
await writeFile5(filePath, block + "\n", "utf-8");
|
|
2711
2756
|
}
|
|
2712
2757
|
return "injected";
|
|
2713
2758
|
}
|
|
@@ -2718,31 +2763,31 @@ ${MARKER_END}`;
|
|
|
2718
2763
|
if (current === block) return "unchanged";
|
|
2719
2764
|
if (!dryRun) {
|
|
2720
2765
|
const updated = existing.slice(0, startIdx) + block + existing.slice(endIdx + MARKER_END.length);
|
|
2721
|
-
await
|
|
2766
|
+
await writeFile5(filePath, updated, "utf-8");
|
|
2722
2767
|
}
|
|
2723
2768
|
return "updated";
|
|
2724
2769
|
}
|
|
2725
2770
|
if (!dryRun) {
|
|
2726
|
-
await
|
|
2771
|
+
await writeFile5(filePath, existing.trimEnd() + "\n\n" + block + "\n", "utf-8");
|
|
2727
2772
|
}
|
|
2728
2773
|
return "injected";
|
|
2729
2774
|
}
|
|
2730
2775
|
async function copyBundledSkill(filePath, content, dryRun) {
|
|
2731
2776
|
let existing = null;
|
|
2732
2777
|
try {
|
|
2733
|
-
existing = await
|
|
2778
|
+
existing = await readFile5(filePath, "utf-8");
|
|
2734
2779
|
} catch {
|
|
2735
2780
|
}
|
|
2736
2781
|
if (existing === content) return "unchanged";
|
|
2737
2782
|
if (!dryRun) {
|
|
2738
|
-
await
|
|
2739
|
-
await
|
|
2783
|
+
await mkdir5(dirname3(filePath), { recursive: true });
|
|
2784
|
+
await writeFile5(filePath, content, "utf-8");
|
|
2740
2785
|
}
|
|
2741
2786
|
return existing === null ? "injected" : "updated";
|
|
2742
2787
|
}
|
|
2743
2788
|
function setupCommand() {
|
|
2744
2789
|
return new Command9("setup").description("inject kordis usage guide into global agent instruction files").option("--silent", "suppress errors \u2014 safe for postinstall").option("--dry-run", "detect installed tools without writing any files").action(async (opts) => {
|
|
2745
|
-
const home =
|
|
2790
|
+
const home = homedir4();
|
|
2746
2791
|
const dryRun = opts.dryRun ?? false;
|
|
2747
2792
|
let hubContent;
|
|
2748
2793
|
let subSkillContents;
|
|
@@ -2759,8 +2804,8 @@ function setupCommand() {
|
|
|
2759
2804
|
let anyFound = false;
|
|
2760
2805
|
const changedTools = /* @__PURE__ */ new Set();
|
|
2761
2806
|
for (const target of HUB_TARGETS) {
|
|
2762
|
-
const dirPath =
|
|
2763
|
-
const filePath =
|
|
2807
|
+
const dirPath = join7(home, target.dir);
|
|
2808
|
+
const filePath = join7(home, target.file);
|
|
2764
2809
|
if (!await exists(dirPath)) {
|
|
2765
2810
|
if (!opts.silent) {
|
|
2766
2811
|
printHuman(c.gray(` - ${target.tool.padEnd(12)} not installed, skipped`));
|
|
@@ -2787,10 +2832,10 @@ function setupCommand() {
|
|
|
2787
2832
|
}
|
|
2788
2833
|
}
|
|
2789
2834
|
for (const target of SKILL_TARGETS) {
|
|
2790
|
-
const dirPath =
|
|
2835
|
+
const dirPath = join7(home, target.dir);
|
|
2791
2836
|
if (!await exists(dirPath)) continue;
|
|
2792
2837
|
for (const subName of SUB_SKILLS) {
|
|
2793
|
-
const filePath =
|
|
2838
|
+
const filePath = join7(home, target.skillsDir, subName, "SKILL.md");
|
|
2794
2839
|
const dest = `~/${target.skillsDir}/${subName}/SKILL.md`;
|
|
2795
2840
|
try {
|
|
2796
2841
|
const result = await copyBundledSkill(filePath, subSkillContents[subName], dryRun);
|
|
@@ -2920,9 +2965,357 @@ function unstarCommand() {
|
|
|
2920
2965
|
});
|
|
2921
2966
|
}
|
|
2922
2967
|
|
|
2968
|
+
// src/commands/delete.ts
|
|
2969
|
+
import { Command as Command12 } from "commander";
|
|
2970
|
+
import prompts from "prompts";
|
|
2971
|
+
function deleteCommand() {
|
|
2972
|
+
return new Command12("delete").description("permanently delete a package you own").argument("<full_slug>", "package identifier {username}/{slug}").option("--force", "skip the confirmation prompt").action(async (fullSlug, opts) => {
|
|
2973
|
+
const [username, slug] = fullSlug.split("/");
|
|
2974
|
+
if (!username || !slug) {
|
|
2975
|
+
throw new Error('full_slug must be "{username}/{slug}"');
|
|
2976
|
+
}
|
|
2977
|
+
const bundle = await useClient();
|
|
2978
|
+
const detail = await bundle.api.getPackage(username, slug);
|
|
2979
|
+
if (!opts.force) {
|
|
2980
|
+
printHuman(c.yellow(` \u26A0 You are about to permanently delete:`));
|
|
2981
|
+
printHuman(` ${c.bold(detail.full_slug)} v${detail.current_version}`);
|
|
2982
|
+
printHuman(` "${detail.description?.slice(0, 80) ?? ""}"`);
|
|
2983
|
+
printHuman(c.yellow(` The slug cannot be reused after deletion. This cannot be undone via the CLI.`));
|
|
2984
|
+
printHuman("");
|
|
2985
|
+
const { confirmed } = await prompts({
|
|
2986
|
+
type: "confirm",
|
|
2987
|
+
name: "confirmed",
|
|
2988
|
+
message: `Delete ${c.bold(fullSlug)}?`,
|
|
2989
|
+
initial: false
|
|
2990
|
+
});
|
|
2991
|
+
if (!confirmed) {
|
|
2992
|
+
printHuman(c.gray("Aborted."));
|
|
2993
|
+
process.exit(0);
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
await bundle.authedRequest((api) => api.deletePackage(username, slug));
|
|
2997
|
+
printDual({
|
|
2998
|
+
json: { status: "deleted", full_slug: fullSlug },
|
|
2999
|
+
human: () => {
|
|
3000
|
+
printHuman(c.green(`\u2713 Deleted ${c.bold(fullSlug)}`));
|
|
3001
|
+
printHuman(c.gray(` The package is no longer visible. Slug "${fullSlug}" is reserved permanently.`));
|
|
3002
|
+
}
|
|
3003
|
+
});
|
|
3004
|
+
});
|
|
3005
|
+
}
|
|
3006
|
+
|
|
3007
|
+
// src/commands/list.ts
|
|
3008
|
+
import { Command as Command13 } from "commander";
|
|
3009
|
+
function listCommand() {
|
|
3010
|
+
return new Command13("list").description("list your own packages (requires login)").option("--limit <n>", "max results per page", (v) => parseInt(v, 10), 20).option("--page <n>", "page number (1-based)", (v) => parseInt(v, 10), 1).option("--sort <mode>", "sort: latest|popular", "latest").action(async (opts) => {
|
|
3011
|
+
const sort = opts.sort === "popular" ? "popular" : "latest";
|
|
3012
|
+
const bundle = await useClient();
|
|
3013
|
+
if (!bundle.username) {
|
|
3014
|
+
throw new Error("Not logged in. Run `kordis login` first.");
|
|
3015
|
+
}
|
|
3016
|
+
const spin = spinner("Fetching your packages");
|
|
3017
|
+
const res = await bundle.authedRequest(
|
|
3018
|
+
(api) => api.listMyPackages({ page: opts.page, limit: opts.limit, sort })
|
|
3019
|
+
);
|
|
3020
|
+
spin.succeed();
|
|
3021
|
+
printDual({
|
|
3022
|
+
json: res,
|
|
3023
|
+
human: () => {
|
|
3024
|
+
if (res.packages.length === 0) {
|
|
3025
|
+
printHuman(c.yellow("(no packages yet \u2014 use `kordis push` to publish your first)"));
|
|
3026
|
+
return;
|
|
3027
|
+
}
|
|
3028
|
+
printHuman(
|
|
3029
|
+
c.gray(
|
|
3030
|
+
`Showing ${res.packages.length} of ${res.total} package${res.total !== 1 ? "s" : ""} (page ${res.page})`
|
|
3031
|
+
)
|
|
3032
|
+
);
|
|
3033
|
+
printHuman("");
|
|
3034
|
+
for (const p of res.packages) {
|
|
3035
|
+
const desc = p.description.length > 60 ? p.description.slice(0, 57) + "..." : p.description;
|
|
3036
|
+
printHuman(
|
|
3037
|
+
`${c.bold(p.full_slug)} ${c.gray(`v${p.current_version}`)} ${c.cyan(`[${p.type}]`)}`
|
|
3038
|
+
);
|
|
3039
|
+
printHuman(` ${desc}`);
|
|
3040
|
+
printHuman(
|
|
3041
|
+
c.gray(` \u2605 ${p.stars} \u21E3 ${p.downloads} ${p.visibility}`)
|
|
3042
|
+
);
|
|
3043
|
+
printHuman("");
|
|
3044
|
+
}
|
|
3045
|
+
if (res.page * res.limit < res.total) {
|
|
3046
|
+
printHuman(
|
|
3047
|
+
c.gray(`(more available \u2014 pass --page ${res.page + 1})`)
|
|
3048
|
+
);
|
|
3049
|
+
}
|
|
3050
|
+
}
|
|
3051
|
+
});
|
|
3052
|
+
});
|
|
3053
|
+
}
|
|
3054
|
+
|
|
3055
|
+
// src/commands/versions.ts
|
|
3056
|
+
import { Command as Command14 } from "commander";
|
|
3057
|
+
function versionsCommand() {
|
|
3058
|
+
return new Command14("versions").description("list all versions of a package").argument("<full_slug>", "package identifier {username}/{slug}").action(async (fullSlug) => {
|
|
3059
|
+
const [username, slug] = fullSlug.split("/");
|
|
3060
|
+
if (!username || !slug) {
|
|
3061
|
+
throw new Error('full_slug must be "{username}/{slug}"');
|
|
3062
|
+
}
|
|
3063
|
+
const bundle = await useClient();
|
|
3064
|
+
const spin = spinner(`Fetching versions for ${fullSlug}`);
|
|
3065
|
+
const res = await bundle.api.versions(username, slug);
|
|
3066
|
+
spin.succeed();
|
|
3067
|
+
printDual({
|
|
3068
|
+
json: res,
|
|
3069
|
+
human: () => {
|
|
3070
|
+
printHuman(
|
|
3071
|
+
`${c.bold(res.full_slug)} \u2014 ${res.versions.length} version${res.versions.length !== 1 ? "s" : ""} (current: ${c.green(res.current_version)})`
|
|
3072
|
+
);
|
|
3073
|
+
printHuman("");
|
|
3074
|
+
for (const v of res.versions) {
|
|
3075
|
+
const statusColor = v.review_status === "passed" ? c.green(v.review_status) : v.review_status === "rejected" ? c.red(v.review_status) : c.yellow(v.review_status);
|
|
3076
|
+
const age = relativeTime(v.published_at);
|
|
3077
|
+
const isCurrent = v.version === res.current_version;
|
|
3078
|
+
printHuman(
|
|
3079
|
+
` ${c.bold(v.version)}${isCurrent ? c.cyan(" (current)") : ""} ${statusColor} ${c.gray(age)}`
|
|
3080
|
+
);
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
3083
|
+
});
|
|
3084
|
+
});
|
|
3085
|
+
}
|
|
3086
|
+
function relativeTime(isoString) {
|
|
3087
|
+
const then = new Date(isoString).getTime();
|
|
3088
|
+
const now = Date.now();
|
|
3089
|
+
const diffSec = Math.floor((now - then) / 1e3);
|
|
3090
|
+
if (diffSec < 60) return "just now";
|
|
3091
|
+
if (diffSec < 3600) {
|
|
3092
|
+
const m = Math.floor(diffSec / 60);
|
|
3093
|
+
return `${m} minute${m !== 1 ? "s" : ""} ago`;
|
|
3094
|
+
}
|
|
3095
|
+
if (diffSec < 86400) {
|
|
3096
|
+
const h = Math.floor(diffSec / 3600);
|
|
3097
|
+
return `${h} hour${h !== 1 ? "s" : ""} ago`;
|
|
3098
|
+
}
|
|
3099
|
+
if (diffSec < 86400 * 30) {
|
|
3100
|
+
const d = Math.floor(diffSec / 86400);
|
|
3101
|
+
return `${d} day${d !== 1 ? "s" : ""} ago`;
|
|
3102
|
+
}
|
|
3103
|
+
if (diffSec < 86400 * 365) {
|
|
3104
|
+
const mo = Math.floor(diffSec / (86400 * 30));
|
|
3105
|
+
return `${mo} month${mo !== 1 ? "s" : ""} ago`;
|
|
3106
|
+
}
|
|
3107
|
+
const y = Math.floor(diffSec / (86400 * 365));
|
|
3108
|
+
return `${y} year${y !== 1 ? "s" : ""} ago`;
|
|
3109
|
+
}
|
|
3110
|
+
|
|
3111
|
+
// src/commands/outdated.ts
|
|
3112
|
+
import { Command as Command15 } from "commander";
|
|
3113
|
+
function isNewer(a, b) {
|
|
3114
|
+
const parse = (v) => {
|
|
3115
|
+
const [maj = 0, min = 0, pat = 0] = v.split("-")[0].split(".").map(Number);
|
|
3116
|
+
return [maj, min, pat];
|
|
3117
|
+
};
|
|
3118
|
+
const [aMaj, aMin, aPat] = parse(a);
|
|
3119
|
+
const [bMaj, bMin, bPat] = parse(b);
|
|
3120
|
+
if (bMaj !== aMaj) return bMaj > aMaj;
|
|
3121
|
+
if (bMin !== aMin) return bMin > aMin;
|
|
3122
|
+
return bPat > aPat;
|
|
3123
|
+
}
|
|
3124
|
+
function outdatedCommand() {
|
|
3125
|
+
return new Command15("outdated").description("check installed packages for available updates").action(async () => {
|
|
3126
|
+
const lf = await readLockfile();
|
|
3127
|
+
const slugs = Object.keys(lf.packages);
|
|
3128
|
+
if (slugs.length === 0) {
|
|
3129
|
+
printDual({
|
|
3130
|
+
json: [],
|
|
3131
|
+
human: () => printHuman(
|
|
3132
|
+
"No packages tracked. Use `kordis pull` to install packages."
|
|
3133
|
+
)
|
|
3134
|
+
});
|
|
3135
|
+
return;
|
|
3136
|
+
}
|
|
3137
|
+
const bundle = await useClient();
|
|
3138
|
+
const spin = spinner(`Checking ${slugs.length} package${slugs.length !== 1 ? "s" : ""}`);
|
|
3139
|
+
const entries = [];
|
|
3140
|
+
for (const fullSlug of slugs) {
|
|
3141
|
+
const [username, slug] = fullSlug.split("/");
|
|
3142
|
+
if (!username || !slug) continue;
|
|
3143
|
+
const installed = lf.packages[fullSlug].version;
|
|
3144
|
+
try {
|
|
3145
|
+
const detail = await bundle.api.getPackage(username, slug);
|
|
3146
|
+
const latest = detail.current_version;
|
|
3147
|
+
entries.push({ full_slug: fullSlug, installed, latest, outdated: isNewer(installed, latest) });
|
|
3148
|
+
} catch {
|
|
3149
|
+
entries.push({ full_slug: fullSlug, installed, latest: installed, outdated: false });
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
spin.succeed();
|
|
3153
|
+
printDual({
|
|
3154
|
+
json: entries,
|
|
3155
|
+
human: () => {
|
|
3156
|
+
const outdatedCount = entries.filter((e) => e.outdated).length;
|
|
3157
|
+
if (outdatedCount === 0) {
|
|
3158
|
+
printHuman(c.green(`\u2713 All ${entries.length} package${entries.length !== 1 ? "s" : ""} are up to date`));
|
|
3159
|
+
return;
|
|
3160
|
+
}
|
|
3161
|
+
printHuman(
|
|
3162
|
+
c.yellow(`${outdatedCount} of ${entries.length} package${entries.length !== 1 ? "s" : ""} can be updated:`)
|
|
3163
|
+
);
|
|
3164
|
+
printHuman("");
|
|
3165
|
+
for (const e of entries) {
|
|
3166
|
+
const status = e.outdated ? c.yellow("Outdated") : c.green("Up to date");
|
|
3167
|
+
const arrow = e.outdated ? c.yellow(` \u2192 ${e.latest}`) : "";
|
|
3168
|
+
printHuman(
|
|
3169
|
+
` ${c.bold(e.full_slug)} ${c.gray(e.installed)}${arrow} ${status}`
|
|
3170
|
+
);
|
|
3171
|
+
}
|
|
3172
|
+
printHuman("");
|
|
3173
|
+
printHuman(c.gray("Run `kordis update` to upgrade all outdated packages."));
|
|
3174
|
+
}
|
|
3175
|
+
});
|
|
3176
|
+
});
|
|
3177
|
+
}
|
|
3178
|
+
|
|
3179
|
+
// src/commands/update.ts
|
|
3180
|
+
import { Command as Command16 } from "commander";
|
|
3181
|
+
function isNewer2(a, b) {
|
|
3182
|
+
const parse = (v) => {
|
|
3183
|
+
const [maj = 0, min = 0, pat = 0] = v.split("-")[0].split(".").map(Number);
|
|
3184
|
+
return [maj, min, pat];
|
|
3185
|
+
};
|
|
3186
|
+
const [aMaj, aMin, aPat] = parse(a);
|
|
3187
|
+
const [bMaj, bMin, bPat] = parse(b);
|
|
3188
|
+
if (bMaj !== aMaj) return bMaj > aMaj;
|
|
3189
|
+
if (bMin !== aMin) return bMin > aMin;
|
|
3190
|
+
return bPat > aPat;
|
|
3191
|
+
}
|
|
3192
|
+
function updateCommand() {
|
|
3193
|
+
return new Command16("update").description("update installed packages to their latest versions").argument("[full_slug]", "package identifier {username}/{slug} (omit to update all)").option("--dry-run", "show what would be updated without doing it", false).action(async (fullSlugArg, opts) => {
|
|
3194
|
+
const lf = await readLockfile();
|
|
3195
|
+
let slugs = Object.keys(lf.packages);
|
|
3196
|
+
if (slugs.length === 0) {
|
|
3197
|
+
printDual({
|
|
3198
|
+
json: [],
|
|
3199
|
+
human: () => printHuman("No packages tracked. Use `kordis pull` to install packages.")
|
|
3200
|
+
});
|
|
3201
|
+
return;
|
|
3202
|
+
}
|
|
3203
|
+
if (fullSlugArg) {
|
|
3204
|
+
if (!lf.packages[fullSlugArg]) {
|
|
3205
|
+
throw new Error(
|
|
3206
|
+
`Package "${fullSlugArg}" is not in the lockfile. Install it first with \`kordis pull ${fullSlugArg}\`.`
|
|
3207
|
+
);
|
|
3208
|
+
}
|
|
3209
|
+
slugs = [fullSlugArg];
|
|
3210
|
+
}
|
|
3211
|
+
const bundle = await useClient();
|
|
3212
|
+
const spin = spinner(`Checking ${slugs.length} package${slugs.length !== 1 ? "s" : ""} for updates`);
|
|
3213
|
+
const candidates = [];
|
|
3214
|
+
for (const fullSlug of slugs) {
|
|
3215
|
+
const entry = lf.packages[fullSlug];
|
|
3216
|
+
const [username, slug] = fullSlug.split("/");
|
|
3217
|
+
if (!username || !slug) continue;
|
|
3218
|
+
try {
|
|
3219
|
+
const detail = await bundle.api.getPackage(username, slug);
|
|
3220
|
+
if (isNewer2(entry.version, detail.current_version)) {
|
|
3221
|
+
candidates.push({
|
|
3222
|
+
fullSlug,
|
|
3223
|
+
installed: entry.version,
|
|
3224
|
+
latest: detail.current_version,
|
|
3225
|
+
tool: entry.tool,
|
|
3226
|
+
target: entry.target
|
|
3227
|
+
});
|
|
3228
|
+
}
|
|
3229
|
+
} catch {
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3232
|
+
spin.stop();
|
|
3233
|
+
if (candidates.length === 0) {
|
|
3234
|
+
printDual({
|
|
3235
|
+
json: [],
|
|
3236
|
+
human: () => printHuman(c.green(`\u2713 All tracked packages are up to date`))
|
|
3237
|
+
});
|
|
3238
|
+
return;
|
|
3239
|
+
}
|
|
3240
|
+
if (opts.dryRun) {
|
|
3241
|
+
const dryEntries = candidates.map((c2) => ({
|
|
3242
|
+
full_slug: c2.fullSlug,
|
|
3243
|
+
from: c2.installed,
|
|
3244
|
+
to: c2.latest,
|
|
3245
|
+
status: "dry_run"
|
|
3246
|
+
}));
|
|
3247
|
+
printDual({
|
|
3248
|
+
json: dryEntries,
|
|
3249
|
+
human: () => {
|
|
3250
|
+
printHuman(
|
|
3251
|
+
c.yellow(
|
|
3252
|
+
`Would update ${candidates.length} package${candidates.length !== 1 ? "s" : ""} (--dry-run):`
|
|
3253
|
+
)
|
|
3254
|
+
);
|
|
3255
|
+
printHuman("");
|
|
3256
|
+
for (const cand of candidates) {
|
|
3257
|
+
printHuman(
|
|
3258
|
+
` ${c.bold(cand.fullSlug)} ${c.gray(cand.installed)} \u2192 ${c.green(cand.latest)}`
|
|
3259
|
+
);
|
|
3260
|
+
}
|
|
3261
|
+
}
|
|
3262
|
+
});
|
|
3263
|
+
return;
|
|
3264
|
+
}
|
|
3265
|
+
const summary = [];
|
|
3266
|
+
for (const cand of candidates) {
|
|
3267
|
+
printHuman(
|
|
3268
|
+
` ${c.cyan("\u2191")} Updating ${c.bold(cand.fullSlug)} ${c.gray(cand.installed)} \u2192 ${c.green(cand.latest)}`
|
|
3269
|
+
);
|
|
3270
|
+
try {
|
|
3271
|
+
const result = await doPullInstall(cand.fullSlug, cand.tool, cand.target);
|
|
3272
|
+
summary.push({
|
|
3273
|
+
full_slug: cand.fullSlug,
|
|
3274
|
+
from: cand.installed,
|
|
3275
|
+
to: result.version,
|
|
3276
|
+
status: "updated"
|
|
3277
|
+
});
|
|
3278
|
+
for (const w of result.written) printHuman(` ${c.gray("wrote")} ${w}`);
|
|
3279
|
+
for (const n of result.notes) printHuman(c.yellow(` ! ${n}`));
|
|
3280
|
+
} catch (err) {
|
|
3281
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3282
|
+
summary.push({
|
|
3283
|
+
full_slug: cand.fullSlug,
|
|
3284
|
+
from: cand.installed,
|
|
3285
|
+
to: cand.latest,
|
|
3286
|
+
status: "failed",
|
|
3287
|
+
error: message
|
|
3288
|
+
});
|
|
3289
|
+
printHuman(c.red(` \u2717 ${message}`));
|
|
3290
|
+
}
|
|
3291
|
+
}
|
|
3292
|
+
const updatedCount = summary.filter((s) => s.status === "updated").length;
|
|
3293
|
+
const failedCount = summary.filter((s) => s.status === "failed").length;
|
|
3294
|
+
printDual({
|
|
3295
|
+
json: summary,
|
|
3296
|
+
human: () => {
|
|
3297
|
+
printHuman("");
|
|
3298
|
+
if (failedCount === 0) {
|
|
3299
|
+
printHuman(
|
|
3300
|
+
c.green(
|
|
3301
|
+
`\u2713 Updated ${updatedCount} package${updatedCount !== 1 ? "s" : ""}`
|
|
3302
|
+
)
|
|
3303
|
+
);
|
|
3304
|
+
} else {
|
|
3305
|
+
printHuman(
|
|
3306
|
+
c.yellow(
|
|
3307
|
+
`Updated ${updatedCount}, failed ${failedCount} package${failedCount !== 1 ? "s" : ""}`
|
|
3308
|
+
)
|
|
3309
|
+
);
|
|
3310
|
+
}
|
|
3311
|
+
}
|
|
3312
|
+
});
|
|
3313
|
+
});
|
|
3314
|
+
}
|
|
3315
|
+
|
|
2923
3316
|
// src/index.ts
|
|
2924
3317
|
function buildProgram() {
|
|
2925
|
-
const program = new
|
|
3318
|
+
const program = new Command17("kordis").version(VERSION).description("Kordis \u2014 registry for agentic-coding packages").option("--json", "emit JSON output to stdout (default: human)", false).hook("preAction", (thisCommand) => {
|
|
2926
3319
|
const opts = thisCommand.optsWithGlobals();
|
|
2927
3320
|
if (opts.json) setJsonMode(true);
|
|
2928
3321
|
});
|
|
@@ -2937,6 +3330,11 @@ function buildProgram() {
|
|
|
2937
3330
|
program.addCommand(commentCommand());
|
|
2938
3331
|
program.addCommand(starCommand());
|
|
2939
3332
|
program.addCommand(unstarCommand());
|
|
3333
|
+
program.addCommand(deleteCommand());
|
|
3334
|
+
program.addCommand(listCommand());
|
|
3335
|
+
program.addCommand(versionsCommand());
|
|
3336
|
+
program.addCommand(outdatedCommand());
|
|
3337
|
+
program.addCommand(updateCommand());
|
|
2940
3338
|
program.addCommand(setupCommand());
|
|
2941
3339
|
return program;
|
|
2942
3340
|
}
|