@rayul/kordis 0.1.14 → 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.
Files changed (2) hide show
  1. package/dist/index.js +443 -88
  2. 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
- // ─── users ─────────────────────────────────────────────────────────────
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 Command13 } from "commander";
987
+ import { Command as Command17 } 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 mkdir3, writeFile as writeFile3 } from "fs/promises";
1597
- import { dirname as dirname2, join as join5 } from "path";
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,7 +2430,91 @@ 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) {
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
+ }
2425
2518
  function pullCommand() {
2426
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(
2427
2520
  async (fullSlug, opts) => {
@@ -2429,7 +2522,6 @@ function pullCommand() {
2429
2522
  if (!username || !slug) {
2430
2523
  throw new Error('full_slug must be "{username}/{slug}"');
2431
2524
  }
2432
- const bundle = await useClient();
2433
2525
  const userCwd = process.env["INIT_CWD"] ?? process.cwd();
2434
2526
  const installRoot = opts.target ?? userCwd;
2435
2527
  const detected = opts.tool ?? await detectTool(installRoot);
@@ -2439,73 +2531,23 @@ function pullCommand() {
2439
2531
  );
2440
2532
  }
2441
2533
  const spin = spinner(`Pulling ${fullSlug}`);
2442
- const detail = await bundle.api.getPackage(username, slug);
2443
- await bundle.api.pull(username, slug);
2444
- const contentRes = await bundle.api.getContent(username, slug, {
2445
- format: "canonical"
2446
- });
2534
+ const result = await doPullInstall(fullSlug, detected, installRoot);
2447
2535
  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
2536
  printDual({
2495
2537
  json: {
2496
2538
  status: "installed",
2497
2539
  full_slug: fullSlug,
2498
- version: detail.current_version,
2499
- tool_detected: detected ?? null,
2500
- installed_paths: written,
2501
- notes
2540
+ version: result.version,
2541
+ tool_detected: result.tool,
2542
+ installed_paths: result.written,
2543
+ notes: result.notes
2502
2544
  },
2503
2545
  human: () => {
2504
2546
  printHuman(
2505
- c.green(`\u2713 Installed ${c.bold(fullSlug)} v${detail.current_version}`)
2547
+ c.green(`\u2713 Installed ${c.bold(fullSlug)} v${result.version}`)
2506
2548
  );
2507
- for (const w of written) printHuman(` ${c.gray("wrote")} ${w}`);
2508
- 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}`));
2509
2551
  }
2510
2552
  });
2511
2553
  }
@@ -2528,7 +2570,7 @@ async function detectTool(root) {
2528
2570
  ];
2529
2571
  for (const [path, tool] of checks) {
2530
2572
  try {
2531
- await stat2(join5(root, path));
2573
+ await stat2(join6(root, path));
2532
2574
  return tool;
2533
2575
  } catch {
2534
2576
  }
@@ -2537,9 +2579,9 @@ async function detectTool(root) {
2537
2579
  }
2538
2580
  async function writeEmittedFile(abs, content, encoding) {
2539
2581
  if (encoding === "base64") {
2540
- await writeFile3(abs, Buffer.from(content, "base64"));
2582
+ await writeFile4(abs, Buffer.from(content, "base64"));
2541
2583
  } else {
2542
- await writeFile3(abs, content, "utf8");
2584
+ await writeFile4(abs, content, "utf8");
2543
2585
  }
2544
2586
  }
2545
2587
 
@@ -2639,9 +2681,9 @@ function logoutCommand() {
2639
2681
  }
2640
2682
 
2641
2683
  // src/commands/setup.ts
2642
- import { readFile as readFile4, writeFile as writeFile4, mkdir as mkdir4, access } from "fs/promises";
2643
- import { homedir as homedir3 } from "os";
2644
- import { join as join6, dirname as dirname3 } from "path";
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";
2645
2687
  import { fileURLToPath as fileURLToPath2 } from "url";
2646
2688
  import { Command as Command9 } from "commander";
2647
2689
  var MARKER_START = "<!-- kordis-start -->";
@@ -2674,18 +2716,18 @@ function assetRoots() {
2674
2716
  const __dir2 = dirname3(fileURLToPath2(import.meta.url));
2675
2717
  return {
2676
2718
  // Built binary: bin/kordis.js → assets/ is one level up
2677
- bundled: join6(__dir2, "..", "assets"),
2719
+ bundled: join7(__dir2, "..", "assets"),
2678
2720
  // Dev mode: src/commands/setup.ts → 4 levels up to repo root, then packages/skills/
2679
- source: join6(__dir2, "..", "..", "..", "..", "packages", "skills")
2721
+ source: join7(__dir2, "..", "..", "..", "..", "packages", "skills")
2680
2722
  };
2681
2723
  }
2682
2724
  async function pickAssetRoot() {
2683
2725
  const { bundled, source } = assetRoots();
2684
- return await exists(join6(bundled, "KORDIS.md")) ? bundled : source;
2726
+ return await exists(join7(bundled, "KORDIS.md")) ? bundled : source;
2685
2727
  }
2686
2728
  async function loadHubContent() {
2687
2729
  const root = await pickAssetRoot();
2688
- const raw = await readFile4(join6(root, "KORDIS.md"), "utf-8");
2730
+ const raw = await readFile5(join7(root, "KORDIS.md"), "utf-8");
2689
2731
  if (raw.startsWith("---")) {
2690
2732
  const end = raw.indexOf("\n---", 4);
2691
2733
  if (end !== -1) return raw.slice(end + 4).trimStart();
@@ -2694,7 +2736,7 @@ async function loadHubContent() {
2694
2736
  }
2695
2737
  async function loadSubSkillContent(name) {
2696
2738
  const root = await pickAssetRoot();
2697
- return readFile4(join6(root, name, "SKILL.md"), "utf-8");
2739
+ return readFile5(join7(root, name, "SKILL.md"), "utf-8");
2698
2740
  }
2699
2741
  async function injectBlock(filePath, content, dryRun) {
2700
2742
  const block = `${MARKER_START}
@@ -2703,14 +2745,14 @@ ${MARKER_END}`;
2703
2745
  let existing = "";
2704
2746
  let fileExists = true;
2705
2747
  try {
2706
- existing = await readFile4(filePath, "utf-8");
2748
+ existing = await readFile5(filePath, "utf-8");
2707
2749
  } catch {
2708
2750
  fileExists = false;
2709
2751
  }
2710
2752
  if (!fileExists) {
2711
2753
  if (!dryRun) {
2712
- await mkdir4(dirname3(filePath), { recursive: true });
2713
- await writeFile4(filePath, block + "\n", "utf-8");
2754
+ await mkdir5(dirname3(filePath), { recursive: true });
2755
+ await writeFile5(filePath, block + "\n", "utf-8");
2714
2756
  }
2715
2757
  return "injected";
2716
2758
  }
@@ -2721,31 +2763,31 @@ ${MARKER_END}`;
2721
2763
  if (current === block) return "unchanged";
2722
2764
  if (!dryRun) {
2723
2765
  const updated = existing.slice(0, startIdx) + block + existing.slice(endIdx + MARKER_END.length);
2724
- await writeFile4(filePath, updated, "utf-8");
2766
+ await writeFile5(filePath, updated, "utf-8");
2725
2767
  }
2726
2768
  return "updated";
2727
2769
  }
2728
2770
  if (!dryRun) {
2729
- await writeFile4(filePath, existing.trimEnd() + "\n\n" + block + "\n", "utf-8");
2771
+ await writeFile5(filePath, existing.trimEnd() + "\n\n" + block + "\n", "utf-8");
2730
2772
  }
2731
2773
  return "injected";
2732
2774
  }
2733
2775
  async function copyBundledSkill(filePath, content, dryRun) {
2734
2776
  let existing = null;
2735
2777
  try {
2736
- existing = await readFile4(filePath, "utf-8");
2778
+ existing = await readFile5(filePath, "utf-8");
2737
2779
  } catch {
2738
2780
  }
2739
2781
  if (existing === content) return "unchanged";
2740
2782
  if (!dryRun) {
2741
- await mkdir4(dirname3(filePath), { recursive: true });
2742
- await writeFile4(filePath, content, "utf-8");
2783
+ await mkdir5(dirname3(filePath), { recursive: true });
2784
+ await writeFile5(filePath, content, "utf-8");
2743
2785
  }
2744
2786
  return existing === null ? "injected" : "updated";
2745
2787
  }
2746
2788
  function setupCommand() {
2747
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) => {
2748
- const home = homedir3();
2790
+ const home = homedir4();
2749
2791
  const dryRun = opts.dryRun ?? false;
2750
2792
  let hubContent;
2751
2793
  let subSkillContents;
@@ -2762,8 +2804,8 @@ function setupCommand() {
2762
2804
  let anyFound = false;
2763
2805
  const changedTools = /* @__PURE__ */ new Set();
2764
2806
  for (const target of HUB_TARGETS) {
2765
- const dirPath = join6(home, target.dir);
2766
- const filePath = join6(home, target.file);
2807
+ const dirPath = join7(home, target.dir);
2808
+ const filePath = join7(home, target.file);
2767
2809
  if (!await exists(dirPath)) {
2768
2810
  if (!opts.silent) {
2769
2811
  printHuman(c.gray(` - ${target.tool.padEnd(12)} not installed, skipped`));
@@ -2790,10 +2832,10 @@ function setupCommand() {
2790
2832
  }
2791
2833
  }
2792
2834
  for (const target of SKILL_TARGETS) {
2793
- const dirPath = join6(home, target.dir);
2835
+ const dirPath = join7(home, target.dir);
2794
2836
  if (!await exists(dirPath)) continue;
2795
2837
  for (const subName of SUB_SKILLS) {
2796
- const filePath = join6(home, target.skillsDir, subName, "SKILL.md");
2838
+ const filePath = join7(home, target.skillsDir, subName, "SKILL.md");
2797
2839
  const dest = `~/${target.skillsDir}/${subName}/SKILL.md`;
2798
2840
  try {
2799
2841
  const result = await copyBundledSkill(filePath, subSkillContents[subName], dryRun);
@@ -2962,9 +3004,318 @@ function deleteCommand() {
2962
3004
  });
2963
3005
  }
2964
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
+
2965
3316
  // src/index.ts
2966
3317
  function buildProgram() {
2967
- const program = new Command13("kordis").version(VERSION).description("Kordis \u2014 registry for agentic-coding packages").option("--json", "emit JSON output to stdout (default: human)", false).hook("preAction", (thisCommand) => {
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) => {
2968
3319
  const opts = thisCommand.optsWithGlobals();
2969
3320
  if (opts.json) setJsonMode(true);
2970
3321
  });
@@ -2980,6 +3331,10 @@ function buildProgram() {
2980
3331
  program.addCommand(starCommand());
2981
3332
  program.addCommand(unstarCommand());
2982
3333
  program.addCommand(deleteCommand());
3334
+ program.addCommand(listCommand());
3335
+ program.addCommand(versionsCommand());
3336
+ program.addCommand(outdatedCommand());
3337
+ program.addCommand(updateCommand());
2983
3338
  program.addCommand(setupCommand());
2984
3339
  return program;
2985
3340
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rayul/kordis",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "private": false,
5
5
  "description": "Command-line client for the Kordis registry — discover, install, and publish agentic-coding packages.",
6
6
  "license": "MIT",