@rayul/kordis 0.1.14 → 0.1.16
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 +525 -92
- package/package.json +1 -1
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", {
|
|
@@ -975,7 +984,7 @@ var init_src2 = __esm({
|
|
|
975
984
|
|
|
976
985
|
// src/index.ts
|
|
977
986
|
init_src2();
|
|
978
|
-
import { Command as
|
|
987
|
+
import { Command as Command18 } from "commander";
|
|
979
988
|
|
|
980
989
|
// src/commands/login.ts
|
|
981
990
|
import { Command } from "commander";
|
|
@@ -1593,8 +1602,8 @@ async function bundleSkillZip(zipPath) {
|
|
|
1593
1602
|
|
|
1594
1603
|
// src/commands/pull.ts
|
|
1595
1604
|
import { Command as Command5 } from "commander";
|
|
1596
|
-
import { mkdir as
|
|
1597
|
-
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";
|
|
1598
1607
|
|
|
1599
1608
|
// ../../packages/harness-schema/src/deps.ts
|
|
1600
1609
|
init_src();
|
|
@@ -2421,15 +2430,106 @@ function getTranslator(type, format) {
|
|
|
2421
2430
|
return fn;
|
|
2422
2431
|
}
|
|
2423
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
|
+
|
|
2424
2456
|
// src/commands/pull.ts
|
|
2457
|
+
async function doPullInstall(fullSlug, tool, installRoot, version) {
|
|
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, {
|
|
2464
|
+
format: "canonical",
|
|
2465
|
+
...version ? { version } : {}
|
|
2466
|
+
});
|
|
2467
|
+
const parsed = validatePackageSource(contentRes.content);
|
|
2468
|
+
if (!parsed.ok || !parsed.type || !parsed.frontmatter) {
|
|
2469
|
+
throw new Error("package stored content is not currently valid");
|
|
2470
|
+
}
|
|
2471
|
+
const written = [];
|
|
2472
|
+
const notes = [];
|
|
2473
|
+
const format = tool;
|
|
2474
|
+
try {
|
|
2475
|
+
const translator = getTranslator(parsed.type, format);
|
|
2476
|
+
const result = translator({
|
|
2477
|
+
fullSlug,
|
|
2478
|
+
frontmatter: parsed.frontmatter.data,
|
|
2479
|
+
body: parsed.body,
|
|
2480
|
+
version: contentRes.version,
|
|
2481
|
+
assets: contentRes.assets
|
|
2482
|
+
});
|
|
2483
|
+
for (const f of result.files) {
|
|
2484
|
+
const abs = join6(installRoot, f.path);
|
|
2485
|
+
await mkdir4(dirname2(abs), { recursive: true });
|
|
2486
|
+
await writeEmittedFile(abs, f.content, f.encoding);
|
|
2487
|
+
written.push(abs);
|
|
2488
|
+
}
|
|
2489
|
+
notes.push(...result.notes);
|
|
2490
|
+
} catch (err) {
|
|
2491
|
+
if (err instanceof TranslatorNotSupportedError) {
|
|
2492
|
+
const canonical = getTranslator(parsed.type, "canonical");
|
|
2493
|
+
const result = canonical({
|
|
2494
|
+
fullSlug,
|
|
2495
|
+
frontmatter: parsed.frontmatter.data,
|
|
2496
|
+
body: parsed.body,
|
|
2497
|
+
version: contentRes.version,
|
|
2498
|
+
assets: contentRes.assets
|
|
2499
|
+
});
|
|
2500
|
+
notes.push(
|
|
2501
|
+
`Tool "${format}" is not supported for type "${parsed.type}"; installed canonical form instead.`
|
|
2502
|
+
);
|
|
2503
|
+
for (const f of result.files) {
|
|
2504
|
+
const abs = join6(installRoot, f.path);
|
|
2505
|
+
await mkdir4(dirname2(abs), { recursive: true });
|
|
2506
|
+
await writeEmittedFile(abs, f.content, f.encoding);
|
|
2507
|
+
written.push(abs);
|
|
2508
|
+
}
|
|
2509
|
+
} else {
|
|
2510
|
+
throw err;
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
const installedVersion = contentRes.version;
|
|
2514
|
+
await recordInstalled(fullSlug, {
|
|
2515
|
+
version: installedVersion,
|
|
2516
|
+
tool,
|
|
2517
|
+
target: installRoot,
|
|
2518
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2519
|
+
});
|
|
2520
|
+
return { version: installedVersion, tool, installRoot, written, notes };
|
|
2521
|
+
}
|
|
2425
2522
|
function pullCommand() {
|
|
2426
|
-
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(
|
|
2427
|
-
async (
|
|
2523
|
+
return new Command5("pull").description("install a package into the current project").argument("<full_slug>", "package identifier {username}/{slug} or {username}/{slug}@{version}").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(
|
|
2524
|
+
async (fullSlugArg, opts) => {
|
|
2525
|
+
const atIdx = fullSlugArg.lastIndexOf("@");
|
|
2526
|
+
const hasPinnedVersion = atIdx > fullSlugArg.indexOf("/");
|
|
2527
|
+
const fullSlug = hasPinnedVersion ? fullSlugArg.slice(0, atIdx) : fullSlugArg;
|
|
2528
|
+
const pinnedVersion = hasPinnedVersion ? fullSlugArg.slice(atIdx + 1) : void 0;
|
|
2428
2529
|
const [username, slug] = fullSlug.split("/");
|
|
2429
2530
|
if (!username || !slug) {
|
|
2430
|
-
throw new Error('full_slug must be "{username}/{slug}"');
|
|
2531
|
+
throw new Error('full_slug must be "{username}/{slug}" or "{username}/{slug}@{version}"');
|
|
2431
2532
|
}
|
|
2432
|
-
const bundle = await useClient();
|
|
2433
2533
|
const userCwd = process.env["INIT_CWD"] ?? process.cwd();
|
|
2434
2534
|
const installRoot = opts.target ?? userCwd;
|
|
2435
2535
|
const detected = opts.tool ?? await detectTool(installRoot);
|
|
@@ -2438,74 +2538,25 @@ function pullCommand() {
|
|
|
2438
2538
|
"Cannot detect target tool from the current directory.\n Pass --tool <name> explicitly. Valid: claude-code, codex, cursor,\n continue, windsurf, antigravity, copilot.\n Or run `kordis pull` inside a project that already has one of:\n .claude/, .codex/, .cursor/, AGENTS.md, CLAUDE.md, .cursorrules,\n .continue/, .windsurf/, .antigravity/, .github/copilot-instructions.md"
|
|
2439
2539
|
);
|
|
2440
2540
|
}
|
|
2441
|
-
const
|
|
2442
|
-
const
|
|
2443
|
-
await
|
|
2444
|
-
const contentRes = await bundle.api.getContent(username, slug, {
|
|
2445
|
-
format: "canonical"
|
|
2446
|
-
});
|
|
2541
|
+
const spinLabel = pinnedVersion ? `Pulling ${fullSlug}@${pinnedVersion}` : `Pulling ${fullSlug}`;
|
|
2542
|
+
const spin = spinner(spinLabel);
|
|
2543
|
+
const result = await doPullInstall(fullSlug, detected, installRoot, pinnedVersion);
|
|
2447
2544
|
spin.succeed();
|
|
2448
|
-
const parsed = validatePackageSource(contentRes.content);
|
|
2449
|
-
if (!parsed.ok || !parsed.type || !parsed.frontmatter) {
|
|
2450
|
-
throw new Error("package stored content is not currently valid");
|
|
2451
|
-
}
|
|
2452
|
-
const written = [];
|
|
2453
|
-
const notes = [];
|
|
2454
|
-
const format = detected;
|
|
2455
|
-
try {
|
|
2456
|
-
const translator = getTranslator(parsed.type, format);
|
|
2457
|
-
const result = translator({
|
|
2458
|
-
fullSlug,
|
|
2459
|
-
frontmatter: parsed.frontmatter.data,
|
|
2460
|
-
body: parsed.body,
|
|
2461
|
-
version: contentRes.version,
|
|
2462
|
-
assets: contentRes.assets
|
|
2463
|
-
});
|
|
2464
|
-
for (const f of result.files) {
|
|
2465
|
-
const abs = join5(installRoot, f.path);
|
|
2466
|
-
await mkdir3(dirname2(abs), { recursive: true });
|
|
2467
|
-
await writeEmittedFile(abs, f.content, f.encoding);
|
|
2468
|
-
written.push(abs);
|
|
2469
|
-
}
|
|
2470
|
-
notes.push(...result.notes);
|
|
2471
|
-
} catch (err) {
|
|
2472
|
-
if (err instanceof TranslatorNotSupportedError) {
|
|
2473
|
-
const canonical = getTranslator(parsed.type, "canonical");
|
|
2474
|
-
const result = canonical({
|
|
2475
|
-
fullSlug,
|
|
2476
|
-
frontmatter: parsed.frontmatter.data,
|
|
2477
|
-
body: parsed.body,
|
|
2478
|
-
version: contentRes.version,
|
|
2479
|
-
assets: contentRes.assets
|
|
2480
|
-
});
|
|
2481
|
-
notes.push(
|
|
2482
|
-
`Tool "${format}" is not supported for type "${parsed.type}"; installed canonical form instead.`
|
|
2483
|
-
);
|
|
2484
|
-
for (const f of result.files) {
|
|
2485
|
-
const abs = join5(installRoot, f.path);
|
|
2486
|
-
await mkdir3(dirname2(abs), { recursive: true });
|
|
2487
|
-
await writeEmittedFile(abs, f.content, f.encoding);
|
|
2488
|
-
written.push(abs);
|
|
2489
|
-
}
|
|
2490
|
-
} else {
|
|
2491
|
-
throw err;
|
|
2492
|
-
}
|
|
2493
|
-
}
|
|
2494
2545
|
printDual({
|
|
2495
2546
|
json: {
|
|
2496
2547
|
status: "installed",
|
|
2497
2548
|
full_slug: fullSlug,
|
|
2498
|
-
version:
|
|
2499
|
-
tool_detected:
|
|
2500
|
-
installed_paths: written,
|
|
2501
|
-
notes
|
|
2549
|
+
version: result.version,
|
|
2550
|
+
tool_detected: result.tool,
|
|
2551
|
+
installed_paths: result.written,
|
|
2552
|
+
notes: result.notes
|
|
2502
2553
|
},
|
|
2503
2554
|
human: () => {
|
|
2504
2555
|
printHuman(
|
|
2505
|
-
c.green(`\u2713 Installed ${c.bold(fullSlug)} v${
|
|
2556
|
+
c.green(`\u2713 Installed ${c.bold(fullSlug)} v${result.version}`)
|
|
2506
2557
|
);
|
|
2507
|
-
for (const w of written) printHuman(` ${c.gray("wrote")} ${w}`);
|
|
2508
|
-
for (const n of notes) printHuman(c.yellow(` ! ${n}`));
|
|
2558
|
+
for (const w of result.written) printHuman(` ${c.gray("wrote")} ${w}`);
|
|
2559
|
+
for (const n of result.notes) printHuman(c.yellow(` ! ${n}`));
|
|
2509
2560
|
}
|
|
2510
2561
|
});
|
|
2511
2562
|
}
|
|
@@ -2528,7 +2579,7 @@ async function detectTool(root) {
|
|
|
2528
2579
|
];
|
|
2529
2580
|
for (const [path, tool] of checks) {
|
|
2530
2581
|
try {
|
|
2531
|
-
await stat2(
|
|
2582
|
+
await stat2(join6(root, path));
|
|
2532
2583
|
return tool;
|
|
2533
2584
|
} catch {
|
|
2534
2585
|
}
|
|
@@ -2537,9 +2588,9 @@ async function detectTool(root) {
|
|
|
2537
2588
|
}
|
|
2538
2589
|
async function writeEmittedFile(abs, content, encoding) {
|
|
2539
2590
|
if (encoding === "base64") {
|
|
2540
|
-
await
|
|
2591
|
+
await writeFile4(abs, Buffer.from(content, "base64"));
|
|
2541
2592
|
} else {
|
|
2542
|
-
await
|
|
2593
|
+
await writeFile4(abs, content, "utf8");
|
|
2543
2594
|
}
|
|
2544
2595
|
}
|
|
2545
2596
|
|
|
@@ -2639,9 +2690,9 @@ function logoutCommand() {
|
|
|
2639
2690
|
}
|
|
2640
2691
|
|
|
2641
2692
|
// src/commands/setup.ts
|
|
2642
|
-
import { readFile as
|
|
2643
|
-
import { homedir as
|
|
2644
|
-
import { join as
|
|
2693
|
+
import { readFile as readFile5, writeFile as writeFile5, mkdir as mkdir5, access } from "fs/promises";
|
|
2694
|
+
import { homedir as homedir4 } from "os";
|
|
2695
|
+
import { join as join7, dirname as dirname3 } from "path";
|
|
2645
2696
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2646
2697
|
import { Command as Command9 } from "commander";
|
|
2647
2698
|
var MARKER_START = "<!-- kordis-start -->";
|
|
@@ -2674,18 +2725,18 @@ function assetRoots() {
|
|
|
2674
2725
|
const __dir2 = dirname3(fileURLToPath2(import.meta.url));
|
|
2675
2726
|
return {
|
|
2676
2727
|
// Built binary: bin/kordis.js → assets/ is one level up
|
|
2677
|
-
bundled:
|
|
2728
|
+
bundled: join7(__dir2, "..", "assets"),
|
|
2678
2729
|
// Dev mode: src/commands/setup.ts → 4 levels up to repo root, then packages/skills/
|
|
2679
|
-
source:
|
|
2730
|
+
source: join7(__dir2, "..", "..", "..", "..", "packages", "skills")
|
|
2680
2731
|
};
|
|
2681
2732
|
}
|
|
2682
2733
|
async function pickAssetRoot() {
|
|
2683
2734
|
const { bundled, source } = assetRoots();
|
|
2684
|
-
return await exists(
|
|
2735
|
+
return await exists(join7(bundled, "KORDIS.md")) ? bundled : source;
|
|
2685
2736
|
}
|
|
2686
2737
|
async function loadHubContent() {
|
|
2687
2738
|
const root = await pickAssetRoot();
|
|
2688
|
-
const raw = await
|
|
2739
|
+
const raw = await readFile5(join7(root, "KORDIS.md"), "utf-8");
|
|
2689
2740
|
if (raw.startsWith("---")) {
|
|
2690
2741
|
const end = raw.indexOf("\n---", 4);
|
|
2691
2742
|
if (end !== -1) return raw.slice(end + 4).trimStart();
|
|
@@ -2694,7 +2745,7 @@ async function loadHubContent() {
|
|
|
2694
2745
|
}
|
|
2695
2746
|
async function loadSubSkillContent(name) {
|
|
2696
2747
|
const root = await pickAssetRoot();
|
|
2697
|
-
return
|
|
2748
|
+
return readFile5(join7(root, name, "SKILL.md"), "utf-8");
|
|
2698
2749
|
}
|
|
2699
2750
|
async function injectBlock(filePath, content, dryRun) {
|
|
2700
2751
|
const block = `${MARKER_START}
|
|
@@ -2703,14 +2754,14 @@ ${MARKER_END}`;
|
|
|
2703
2754
|
let existing = "";
|
|
2704
2755
|
let fileExists = true;
|
|
2705
2756
|
try {
|
|
2706
|
-
existing = await
|
|
2757
|
+
existing = await readFile5(filePath, "utf-8");
|
|
2707
2758
|
} catch {
|
|
2708
2759
|
fileExists = false;
|
|
2709
2760
|
}
|
|
2710
2761
|
if (!fileExists) {
|
|
2711
2762
|
if (!dryRun) {
|
|
2712
|
-
await
|
|
2713
|
-
await
|
|
2763
|
+
await mkdir5(dirname3(filePath), { recursive: true });
|
|
2764
|
+
await writeFile5(filePath, block + "\n", "utf-8");
|
|
2714
2765
|
}
|
|
2715
2766
|
return "injected";
|
|
2716
2767
|
}
|
|
@@ -2721,31 +2772,31 @@ ${MARKER_END}`;
|
|
|
2721
2772
|
if (current === block) return "unchanged";
|
|
2722
2773
|
if (!dryRun) {
|
|
2723
2774
|
const updated = existing.slice(0, startIdx) + block + existing.slice(endIdx + MARKER_END.length);
|
|
2724
|
-
await
|
|
2775
|
+
await writeFile5(filePath, updated, "utf-8");
|
|
2725
2776
|
}
|
|
2726
2777
|
return "updated";
|
|
2727
2778
|
}
|
|
2728
2779
|
if (!dryRun) {
|
|
2729
|
-
await
|
|
2780
|
+
await writeFile5(filePath, existing.trimEnd() + "\n\n" + block + "\n", "utf-8");
|
|
2730
2781
|
}
|
|
2731
2782
|
return "injected";
|
|
2732
2783
|
}
|
|
2733
2784
|
async function copyBundledSkill(filePath, content, dryRun) {
|
|
2734
2785
|
let existing = null;
|
|
2735
2786
|
try {
|
|
2736
|
-
existing = await
|
|
2787
|
+
existing = await readFile5(filePath, "utf-8");
|
|
2737
2788
|
} catch {
|
|
2738
2789
|
}
|
|
2739
2790
|
if (existing === content) return "unchanged";
|
|
2740
2791
|
if (!dryRun) {
|
|
2741
|
-
await
|
|
2742
|
-
await
|
|
2792
|
+
await mkdir5(dirname3(filePath), { recursive: true });
|
|
2793
|
+
await writeFile5(filePath, content, "utf-8");
|
|
2743
2794
|
}
|
|
2744
2795
|
return existing === null ? "injected" : "updated";
|
|
2745
2796
|
}
|
|
2746
2797
|
function setupCommand() {
|
|
2747
2798
|
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) => {
|
|
2748
|
-
const home =
|
|
2799
|
+
const home = homedir4();
|
|
2749
2800
|
const dryRun = opts.dryRun ?? false;
|
|
2750
2801
|
let hubContent;
|
|
2751
2802
|
let subSkillContents;
|
|
@@ -2762,8 +2813,8 @@ function setupCommand() {
|
|
|
2762
2813
|
let anyFound = false;
|
|
2763
2814
|
const changedTools = /* @__PURE__ */ new Set();
|
|
2764
2815
|
for (const target of HUB_TARGETS) {
|
|
2765
|
-
const dirPath =
|
|
2766
|
-
const filePath =
|
|
2816
|
+
const dirPath = join7(home, target.dir);
|
|
2817
|
+
const filePath = join7(home, target.file);
|
|
2767
2818
|
if (!await exists(dirPath)) {
|
|
2768
2819
|
if (!opts.silent) {
|
|
2769
2820
|
printHuman(c.gray(` - ${target.tool.padEnd(12)} not installed, skipped`));
|
|
@@ -2790,10 +2841,10 @@ function setupCommand() {
|
|
|
2790
2841
|
}
|
|
2791
2842
|
}
|
|
2792
2843
|
for (const target of SKILL_TARGETS) {
|
|
2793
|
-
const dirPath =
|
|
2844
|
+
const dirPath = join7(home, target.dir);
|
|
2794
2845
|
if (!await exists(dirPath)) continue;
|
|
2795
2846
|
for (const subName of SUB_SKILLS) {
|
|
2796
|
-
const filePath =
|
|
2847
|
+
const filePath = join7(home, target.skillsDir, subName, "SKILL.md");
|
|
2797
2848
|
const dest = `~/${target.skillsDir}/${subName}/SKILL.md`;
|
|
2798
2849
|
try {
|
|
2799
2850
|
const result = await copyBundledSkill(filePath, subSkillContents[subName], dryRun);
|
|
@@ -2962,9 +3013,386 @@ function deleteCommand() {
|
|
|
2962
3013
|
});
|
|
2963
3014
|
}
|
|
2964
3015
|
|
|
3016
|
+
// src/commands/list.ts
|
|
3017
|
+
import { Command as Command13 } from "commander";
|
|
3018
|
+
function listCommand() {
|
|
3019
|
+
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) => {
|
|
3020
|
+
const sort = opts.sort === "popular" ? "popular" : "latest";
|
|
3021
|
+
const bundle = await useClient();
|
|
3022
|
+
if (!bundle.username) {
|
|
3023
|
+
throw new Error("Not logged in. Run `kordis login` first.");
|
|
3024
|
+
}
|
|
3025
|
+
const spin = spinner("Fetching your packages");
|
|
3026
|
+
const res = await bundle.authedRequest(
|
|
3027
|
+
(api) => api.listMyPackages({ page: opts.page, limit: opts.limit, sort })
|
|
3028
|
+
);
|
|
3029
|
+
spin.succeed();
|
|
3030
|
+
printDual({
|
|
3031
|
+
json: res,
|
|
3032
|
+
human: () => {
|
|
3033
|
+
if (res.packages.length === 0) {
|
|
3034
|
+
printHuman(c.yellow("(no packages yet \u2014 use `kordis push` to publish your first)"));
|
|
3035
|
+
return;
|
|
3036
|
+
}
|
|
3037
|
+
printHuman(
|
|
3038
|
+
c.gray(
|
|
3039
|
+
`Showing ${res.packages.length} of ${res.total} package${res.total !== 1 ? "s" : ""} (page ${res.page})`
|
|
3040
|
+
)
|
|
3041
|
+
);
|
|
3042
|
+
printHuman("");
|
|
3043
|
+
for (const p of res.packages) {
|
|
3044
|
+
const desc = p.description.length > 60 ? p.description.slice(0, 57) + "..." : p.description;
|
|
3045
|
+
printHuman(
|
|
3046
|
+
`${c.bold(p.full_slug)} ${c.gray(`v${p.current_version}`)} ${c.cyan(`[${p.type}]`)}`
|
|
3047
|
+
);
|
|
3048
|
+
printHuman(` ${desc}`);
|
|
3049
|
+
printHuman(
|
|
3050
|
+
c.gray(` \u2605 ${p.stars} \u21E3 ${p.downloads} ${p.visibility}`)
|
|
3051
|
+
);
|
|
3052
|
+
printHuman("");
|
|
3053
|
+
}
|
|
3054
|
+
if (res.page * res.limit < res.total) {
|
|
3055
|
+
printHuman(
|
|
3056
|
+
c.gray(`(more available \u2014 pass --page ${res.page + 1})`)
|
|
3057
|
+
);
|
|
3058
|
+
}
|
|
3059
|
+
}
|
|
3060
|
+
});
|
|
3061
|
+
});
|
|
3062
|
+
}
|
|
3063
|
+
|
|
3064
|
+
// src/commands/versions.ts
|
|
3065
|
+
import { Command as Command14 } from "commander";
|
|
3066
|
+
function versionsCommand() {
|
|
3067
|
+
return new Command14("versions").description("list all versions of a package").argument("<full_slug>", "package identifier {username}/{slug}").action(async (fullSlug) => {
|
|
3068
|
+
const [username, slug] = fullSlug.split("/");
|
|
3069
|
+
if (!username || !slug) {
|
|
3070
|
+
throw new Error('full_slug must be "{username}/{slug}"');
|
|
3071
|
+
}
|
|
3072
|
+
const bundle = await useClient();
|
|
3073
|
+
const spin = spinner(`Fetching versions for ${fullSlug}`);
|
|
3074
|
+
const res = await bundle.api.versions(username, slug);
|
|
3075
|
+
spin.succeed();
|
|
3076
|
+
printDual({
|
|
3077
|
+
json: res,
|
|
3078
|
+
human: () => {
|
|
3079
|
+
printHuman(
|
|
3080
|
+
`${c.bold(res.full_slug)} \u2014 ${res.versions.length} version${res.versions.length !== 1 ? "s" : ""} (current: ${c.green(res.current_version)})`
|
|
3081
|
+
);
|
|
3082
|
+
printHuman("");
|
|
3083
|
+
for (const v of res.versions) {
|
|
3084
|
+
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);
|
|
3085
|
+
const age = relativeTime(v.published_at);
|
|
3086
|
+
const isCurrent = v.version === res.current_version;
|
|
3087
|
+
printHuman(
|
|
3088
|
+
` ${c.bold(v.version)}${isCurrent ? c.cyan(" (current)") : ""} ${statusColor} ${c.gray(age)}`
|
|
3089
|
+
);
|
|
3090
|
+
}
|
|
3091
|
+
}
|
|
3092
|
+
});
|
|
3093
|
+
});
|
|
3094
|
+
}
|
|
3095
|
+
function relativeTime(isoString) {
|
|
3096
|
+
const then = new Date(isoString).getTime();
|
|
3097
|
+
const now = Date.now();
|
|
3098
|
+
const diffSec = Math.floor((now - then) / 1e3);
|
|
3099
|
+
if (diffSec < 60) return "just now";
|
|
3100
|
+
if (diffSec < 3600) {
|
|
3101
|
+
const m = Math.floor(diffSec / 60);
|
|
3102
|
+
return `${m} minute${m !== 1 ? "s" : ""} ago`;
|
|
3103
|
+
}
|
|
3104
|
+
if (diffSec < 86400) {
|
|
3105
|
+
const h = Math.floor(diffSec / 3600);
|
|
3106
|
+
return `${h} hour${h !== 1 ? "s" : ""} ago`;
|
|
3107
|
+
}
|
|
3108
|
+
if (diffSec < 86400 * 30) {
|
|
3109
|
+
const d = Math.floor(diffSec / 86400);
|
|
3110
|
+
return `${d} day${d !== 1 ? "s" : ""} ago`;
|
|
3111
|
+
}
|
|
3112
|
+
if (diffSec < 86400 * 365) {
|
|
3113
|
+
const mo = Math.floor(diffSec / (86400 * 30));
|
|
3114
|
+
return `${mo} month${mo !== 1 ? "s" : ""} ago`;
|
|
3115
|
+
}
|
|
3116
|
+
const y = Math.floor(diffSec / (86400 * 365));
|
|
3117
|
+
return `${y} year${y !== 1 ? "s" : ""} ago`;
|
|
3118
|
+
}
|
|
3119
|
+
|
|
3120
|
+
// src/commands/outdated.ts
|
|
3121
|
+
import { Command as Command15 } from "commander";
|
|
3122
|
+
function isNewer(a, b) {
|
|
3123
|
+
const parse = (v) => {
|
|
3124
|
+
const [maj = 0, min = 0, pat = 0] = v.split("-")[0].split(".").map(Number);
|
|
3125
|
+
return [maj, min, pat];
|
|
3126
|
+
};
|
|
3127
|
+
const [aMaj, aMin, aPat] = parse(a);
|
|
3128
|
+
const [bMaj, bMin, bPat] = parse(b);
|
|
3129
|
+
if (bMaj !== aMaj) return bMaj > aMaj;
|
|
3130
|
+
if (bMin !== aMin) return bMin > aMin;
|
|
3131
|
+
return bPat > aPat;
|
|
3132
|
+
}
|
|
3133
|
+
function outdatedCommand() {
|
|
3134
|
+
return new Command15("outdated").description("check installed packages for available updates").action(async () => {
|
|
3135
|
+
const lf = await readLockfile();
|
|
3136
|
+
const slugs = Object.keys(lf.packages);
|
|
3137
|
+
if (slugs.length === 0) {
|
|
3138
|
+
printDual({
|
|
3139
|
+
json: [],
|
|
3140
|
+
human: () => printHuman(
|
|
3141
|
+
"No packages tracked. Use `kordis pull` to install packages."
|
|
3142
|
+
)
|
|
3143
|
+
});
|
|
3144
|
+
return;
|
|
3145
|
+
}
|
|
3146
|
+
const bundle = await useClient();
|
|
3147
|
+
const spin = spinner(`Checking ${slugs.length} package${slugs.length !== 1 ? "s" : ""}`);
|
|
3148
|
+
const entries = [];
|
|
3149
|
+
for (const fullSlug of slugs) {
|
|
3150
|
+
const [username, slug] = fullSlug.split("/");
|
|
3151
|
+
if (!username || !slug) continue;
|
|
3152
|
+
const installed = lf.packages[fullSlug].version;
|
|
3153
|
+
try {
|
|
3154
|
+
const detail = await bundle.api.getPackage(username, slug);
|
|
3155
|
+
const latest = detail.current_version;
|
|
3156
|
+
entries.push({ full_slug: fullSlug, installed, latest, outdated: isNewer(installed, latest) });
|
|
3157
|
+
} catch {
|
|
3158
|
+
entries.push({ full_slug: fullSlug, installed, latest: installed, outdated: false });
|
|
3159
|
+
}
|
|
3160
|
+
}
|
|
3161
|
+
spin.succeed();
|
|
3162
|
+
printDual({
|
|
3163
|
+
json: entries,
|
|
3164
|
+
human: () => {
|
|
3165
|
+
const outdatedCount = entries.filter((e) => e.outdated).length;
|
|
3166
|
+
if (outdatedCount === 0) {
|
|
3167
|
+
printHuman(c.green(`\u2713 All ${entries.length} package${entries.length !== 1 ? "s" : ""} are up to date`));
|
|
3168
|
+
return;
|
|
3169
|
+
}
|
|
3170
|
+
printHuman(
|
|
3171
|
+
c.yellow(`${outdatedCount} of ${entries.length} package${entries.length !== 1 ? "s" : ""} can be updated:`)
|
|
3172
|
+
);
|
|
3173
|
+
printHuman("");
|
|
3174
|
+
for (const e of entries) {
|
|
3175
|
+
const status = e.outdated ? c.yellow("Outdated") : c.green("Up to date");
|
|
3176
|
+
const arrow = e.outdated ? c.yellow(` \u2192 ${e.latest}`) : "";
|
|
3177
|
+
printHuman(
|
|
3178
|
+
` ${c.bold(e.full_slug)} ${c.gray(e.installed)}${arrow} ${status}`
|
|
3179
|
+
);
|
|
3180
|
+
}
|
|
3181
|
+
printHuman("");
|
|
3182
|
+
printHuman(c.gray("Run `kordis update` to upgrade all outdated packages."));
|
|
3183
|
+
}
|
|
3184
|
+
});
|
|
3185
|
+
});
|
|
3186
|
+
}
|
|
3187
|
+
|
|
3188
|
+
// src/commands/update.ts
|
|
3189
|
+
import { Command as Command16 } from "commander";
|
|
3190
|
+
function isNewer2(a, b) {
|
|
3191
|
+
const parse = (v) => {
|
|
3192
|
+
const [maj = 0, min = 0, pat = 0] = v.split("-")[0].split(".").map(Number);
|
|
3193
|
+
return [maj, min, pat];
|
|
3194
|
+
};
|
|
3195
|
+
const [aMaj, aMin, aPat] = parse(a);
|
|
3196
|
+
const [bMaj, bMin, bPat] = parse(b);
|
|
3197
|
+
if (bMaj !== aMaj) return bMaj > aMaj;
|
|
3198
|
+
if (bMin !== aMin) return bMin > aMin;
|
|
3199
|
+
return bPat > aPat;
|
|
3200
|
+
}
|
|
3201
|
+
function updateCommand() {
|
|
3202
|
+
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) => {
|
|
3203
|
+
const lf = await readLockfile();
|
|
3204
|
+
let slugs = Object.keys(lf.packages);
|
|
3205
|
+
if (slugs.length === 0) {
|
|
3206
|
+
printDual({
|
|
3207
|
+
json: [],
|
|
3208
|
+
human: () => printHuman("No packages tracked. Use `kordis pull` to install packages.")
|
|
3209
|
+
});
|
|
3210
|
+
return;
|
|
3211
|
+
}
|
|
3212
|
+
if (fullSlugArg) {
|
|
3213
|
+
if (!lf.packages[fullSlugArg]) {
|
|
3214
|
+
throw new Error(
|
|
3215
|
+
`Package "${fullSlugArg}" is not in the lockfile. Install it first with \`kordis pull ${fullSlugArg}\`.`
|
|
3216
|
+
);
|
|
3217
|
+
}
|
|
3218
|
+
slugs = [fullSlugArg];
|
|
3219
|
+
}
|
|
3220
|
+
const bundle = await useClient();
|
|
3221
|
+
const spin = spinner(`Checking ${slugs.length} package${slugs.length !== 1 ? "s" : ""} for updates`);
|
|
3222
|
+
const candidates = [];
|
|
3223
|
+
for (const fullSlug of slugs) {
|
|
3224
|
+
const entry = lf.packages[fullSlug];
|
|
3225
|
+
const [username, slug] = fullSlug.split("/");
|
|
3226
|
+
if (!username || !slug) continue;
|
|
3227
|
+
try {
|
|
3228
|
+
const detail = await bundle.api.getPackage(username, slug);
|
|
3229
|
+
if (isNewer2(entry.version, detail.current_version)) {
|
|
3230
|
+
candidates.push({
|
|
3231
|
+
fullSlug,
|
|
3232
|
+
installed: entry.version,
|
|
3233
|
+
latest: detail.current_version,
|
|
3234
|
+
tool: entry.tool,
|
|
3235
|
+
target: entry.target
|
|
3236
|
+
});
|
|
3237
|
+
}
|
|
3238
|
+
} catch {
|
|
3239
|
+
}
|
|
3240
|
+
}
|
|
3241
|
+
spin.stop();
|
|
3242
|
+
if (candidates.length === 0) {
|
|
3243
|
+
printDual({
|
|
3244
|
+
json: [],
|
|
3245
|
+
human: () => printHuman(c.green(`\u2713 All tracked packages are up to date`))
|
|
3246
|
+
});
|
|
3247
|
+
return;
|
|
3248
|
+
}
|
|
3249
|
+
if (opts.dryRun) {
|
|
3250
|
+
const dryEntries = candidates.map((c2) => ({
|
|
3251
|
+
full_slug: c2.fullSlug,
|
|
3252
|
+
from: c2.installed,
|
|
3253
|
+
to: c2.latest,
|
|
3254
|
+
status: "dry_run"
|
|
3255
|
+
}));
|
|
3256
|
+
printDual({
|
|
3257
|
+
json: dryEntries,
|
|
3258
|
+
human: () => {
|
|
3259
|
+
printHuman(
|
|
3260
|
+
c.yellow(
|
|
3261
|
+
`Would update ${candidates.length} package${candidates.length !== 1 ? "s" : ""} (--dry-run):`
|
|
3262
|
+
)
|
|
3263
|
+
);
|
|
3264
|
+
printHuman("");
|
|
3265
|
+
for (const cand of candidates) {
|
|
3266
|
+
printHuman(
|
|
3267
|
+
` ${c.bold(cand.fullSlug)} ${c.gray(cand.installed)} \u2192 ${c.green(cand.latest)}`
|
|
3268
|
+
);
|
|
3269
|
+
}
|
|
3270
|
+
}
|
|
3271
|
+
});
|
|
3272
|
+
return;
|
|
3273
|
+
}
|
|
3274
|
+
const summary = [];
|
|
3275
|
+
for (const cand of candidates) {
|
|
3276
|
+
printHuman(
|
|
3277
|
+
` ${c.cyan("\u2191")} Updating ${c.bold(cand.fullSlug)} ${c.gray(cand.installed)} \u2192 ${c.green(cand.latest)}`
|
|
3278
|
+
);
|
|
3279
|
+
try {
|
|
3280
|
+
const result = await doPullInstall(cand.fullSlug, cand.tool, cand.target);
|
|
3281
|
+
summary.push({
|
|
3282
|
+
full_slug: cand.fullSlug,
|
|
3283
|
+
from: cand.installed,
|
|
3284
|
+
to: result.version,
|
|
3285
|
+
status: "updated"
|
|
3286
|
+
});
|
|
3287
|
+
for (const w of result.written) printHuman(` ${c.gray("wrote")} ${w}`);
|
|
3288
|
+
for (const n of result.notes) printHuman(c.yellow(` ! ${n}`));
|
|
3289
|
+
} catch (err) {
|
|
3290
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3291
|
+
summary.push({
|
|
3292
|
+
full_slug: cand.fullSlug,
|
|
3293
|
+
from: cand.installed,
|
|
3294
|
+
to: cand.latest,
|
|
3295
|
+
status: "failed",
|
|
3296
|
+
error: message
|
|
3297
|
+
});
|
|
3298
|
+
printHuman(c.red(` \u2717 ${message}`));
|
|
3299
|
+
}
|
|
3300
|
+
}
|
|
3301
|
+
const updatedCount = summary.filter((s) => s.status === "updated").length;
|
|
3302
|
+
const failedCount = summary.filter((s) => s.status === "failed").length;
|
|
3303
|
+
printDual({
|
|
3304
|
+
json: summary,
|
|
3305
|
+
human: () => {
|
|
3306
|
+
printHuman("");
|
|
3307
|
+
if (failedCount === 0) {
|
|
3308
|
+
printHuman(
|
|
3309
|
+
c.green(
|
|
3310
|
+
`\u2713 Updated ${updatedCount} package${updatedCount !== 1 ? "s" : ""}`
|
|
3311
|
+
)
|
|
3312
|
+
);
|
|
3313
|
+
} else {
|
|
3314
|
+
printHuman(
|
|
3315
|
+
c.yellow(
|
|
3316
|
+
`Updated ${updatedCount}, failed ${failedCount} package${failedCount !== 1 ? "s" : ""}`
|
|
3317
|
+
)
|
|
3318
|
+
);
|
|
3319
|
+
}
|
|
3320
|
+
}
|
|
3321
|
+
});
|
|
3322
|
+
});
|
|
3323
|
+
}
|
|
3324
|
+
|
|
3325
|
+
// src/commands/diff.ts
|
|
3326
|
+
import { Command as Command17 } from "commander";
|
|
3327
|
+
function diffCommand() {
|
|
3328
|
+
return new Command17("diff").description("show changes between two versions of a package").argument("<full_slug>", "package identifier: {username}/{slug}, or {username}/{slug}@{from}..{to}").argument("[from]", "older version (omit to use second-latest)").argument("[to]", "newer version (omit to use latest)").action(async (fullSlugArg, fromArg, toArg) => {
|
|
3329
|
+
let fullSlug = fullSlugArg;
|
|
3330
|
+
let from = fromArg;
|
|
3331
|
+
let to = toArg;
|
|
3332
|
+
const rangeMatch = /^(.+)@([^.]+)\.\.(.+)$/.exec(fullSlugArg);
|
|
3333
|
+
if (rangeMatch) {
|
|
3334
|
+
fullSlug = rangeMatch[1];
|
|
3335
|
+
from = rangeMatch[2];
|
|
3336
|
+
to = rangeMatch[3];
|
|
3337
|
+
}
|
|
3338
|
+
const [username, slug] = fullSlug.split("/");
|
|
3339
|
+
if (!username || !slug) {
|
|
3340
|
+
throw new Error('full_slug must be "{username}/{slug}"');
|
|
3341
|
+
}
|
|
3342
|
+
const bundle = await useClient();
|
|
3343
|
+
if (!from || !to) {
|
|
3344
|
+
const spin2 = spinner(`Fetching versions for ${fullSlug}`);
|
|
3345
|
+
const versionsRes = await bundle.api.versions(username, slug);
|
|
3346
|
+
spin2.stop();
|
|
3347
|
+
const passed = versionsRes.versions.filter((v) => v.review_status === "passed");
|
|
3348
|
+
if (passed.length < 2) {
|
|
3349
|
+
throw new Error(
|
|
3350
|
+
`Need at least 2 published versions to auto-diff. Pass explicit versions: kordis diff ${fullSlug} <from> <to>`
|
|
3351
|
+
);
|
|
3352
|
+
}
|
|
3353
|
+
to = to ?? passed[0].version;
|
|
3354
|
+
from = from ?? passed[1].version;
|
|
3355
|
+
}
|
|
3356
|
+
const spin = spinner(`Diffing ${fullSlug} ${from}..${to}`);
|
|
3357
|
+
const diff = await bundle.api.diff(username, slug, from, to);
|
|
3358
|
+
spin.succeed();
|
|
3359
|
+
printDual({
|
|
3360
|
+
json: diff,
|
|
3361
|
+
human: () => {
|
|
3362
|
+
printHuman(
|
|
3363
|
+
`${c.bold(diff.full_slug)} ${c.gray(diff.from)} ${c.dim("\u2192")} ${c.green(diff.to)}`
|
|
3364
|
+
);
|
|
3365
|
+
printHuman(
|
|
3366
|
+
c.gray(
|
|
3367
|
+
` +${diff.stats.additions} \u2212${diff.stats.deletions} (${diff.stats.files_changed} file${diff.stats.files_changed !== 1 ? "s" : ""} changed)`
|
|
3368
|
+
)
|
|
3369
|
+
);
|
|
3370
|
+
printHuman("");
|
|
3371
|
+
if (!diff.unified_diff) {
|
|
3372
|
+
printHuman(c.dim("(no content changes between these versions)"));
|
|
3373
|
+
return;
|
|
3374
|
+
}
|
|
3375
|
+
for (const line of diff.unified_diff.split("\n")) {
|
|
3376
|
+
if (line.startsWith("+++") || line.startsWith("---")) {
|
|
3377
|
+
printHuman(c.bold(line));
|
|
3378
|
+
} else if (line.startsWith("+")) {
|
|
3379
|
+
printHuman(c.green(line));
|
|
3380
|
+
} else if (line.startsWith("-")) {
|
|
3381
|
+
printHuman(c.red(line));
|
|
3382
|
+
} else if (line.startsWith("@@")) {
|
|
3383
|
+
printHuman(c.cyan(line));
|
|
3384
|
+
} else {
|
|
3385
|
+
printHuman(c.gray(line));
|
|
3386
|
+
}
|
|
3387
|
+
}
|
|
3388
|
+
}
|
|
3389
|
+
});
|
|
3390
|
+
});
|
|
3391
|
+
}
|
|
3392
|
+
|
|
2965
3393
|
// src/index.ts
|
|
2966
3394
|
function buildProgram() {
|
|
2967
|
-
const program = new
|
|
3395
|
+
const program = new Command18("kordis").version(VERSION).description("Kordis \u2014 registry for agentic-coding packages").option("--json", "emit JSON output to stdout (default: human)", false).hook("preAction", (thisCommand) => {
|
|
2968
3396
|
const opts = thisCommand.optsWithGlobals();
|
|
2969
3397
|
if (opts.json) setJsonMode(true);
|
|
2970
3398
|
});
|
|
@@ -2980,6 +3408,11 @@ function buildProgram() {
|
|
|
2980
3408
|
program.addCommand(starCommand());
|
|
2981
3409
|
program.addCommand(unstarCommand());
|
|
2982
3410
|
program.addCommand(deleteCommand());
|
|
3411
|
+
program.addCommand(listCommand());
|
|
3412
|
+
program.addCommand(versionsCommand());
|
|
3413
|
+
program.addCommand(outdatedCommand());
|
|
3414
|
+
program.addCommand(updateCommand());
|
|
3415
|
+
program.addCommand(diffCommand());
|
|
2983
3416
|
program.addCommand(setupCommand());
|
|
2984
3417
|
return program;
|
|
2985
3418
|
}
|