@rayul/kordis 0.1.10 → 0.1.12

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.
@@ -100,12 +100,16 @@ auto-resolve silently.
100
100
  `"following <full_slug>@<version>"`). RECOMMENDED: end your reply with that
101
101
  exact line. Skip only when it would clutter a casual non-technical reply.
102
102
 
103
- ## Bundled assets caveat
104
- A skill body may reference helper files (e.g. `references/<file>.md`,
105
- `scripts/<file>.sh`). Today the registry stores only the canonical
106
- SKILL.md text — bundled side-files are NOT shipped. If the body refers to
107
- one and you need it, follow the package's `homepage` to the upstream
108
- source instead of fabricating the asset.
103
+ ## Bundled assets
104
+ Skills published as a `.skill` bundle or directory include `assets/`,
105
+ `references/`, `scripts/`, and `agents/` subdirectories alongside
106
+ `SKILL.md`. After `kordis pull`, these files land next to `SKILL.md` in
107
+ the tool's skill directory (e.g. `.claude/skills/<name>/assets/foo.html`)
108
+ and are directly readable by the agent.
109
+
110
+ If a skill body references a helper file and the file isn't present after
111
+ pull, the package was published as a single-file skill — follow its
112
+ `homepage` to the upstream source.
109
113
 
110
114
  ## Examples
111
115
 
package/dist/index.js CHANGED
@@ -459,7 +459,7 @@ var init_auth = __esm({
459
459
 
460
460
  // ../../packages/contracts/src/api.ts
461
461
  import { z as z9 } from "zod";
462
- var SearchQuerySchema, SearchResponseSchema, TrendingQuerySchema, RisingQuerySchema, StarResponseSchema, PublishPackageRequestSchema, PublishWaitQuerySchema, PublishPackageResponseSchema, PackagePathParamsSchema, ContentQuerySchema, PackageContentResponseSchema, JitReadRequestSchema, JitReadResponseSchema, PullPackageResponseSchema, ListVersionsResponseSchema, VersionDiffQuerySchema, ListCommentsQuerySchema, ListCommentsResponseSchema, UserProfileResponseSchema, UserPackagesQuerySchema;
462
+ var SearchQuerySchema, SearchResponseSchema, TrendingQuerySchema, RisingQuerySchema, StarResponseSchema, AssetSchema, PublishPackageRequestSchema, PublishWaitQuerySchema, PublishPackageResponseSchema, PackagePathParamsSchema, ContentQuerySchema, PackageContentResponseSchema, JitReadRequestSchema, JitReadResponseSchema, PullPackageResponseSchema, ListVersionsResponseSchema, VersionDiffQuerySchema, ListCommentsQuerySchema, ListCommentsResponseSchema, UserProfileResponseSchema, UserPackagesQuerySchema;
463
463
  var init_api = __esm({
464
464
  "../../packages/contracts/src/api.ts"() {
465
465
  "use strict";
@@ -498,6 +498,11 @@ var init_api = __esm({
498
498
  starred: z9.boolean(),
499
499
  star_count: z9.number().int().nonnegative()
500
500
  });
501
+ AssetSchema = z9.object({
502
+ path: z9.string().min(1).max(255),
503
+ content: z9.string(),
504
+ encoding: z9.enum(["utf8", "base64"]).default("utf8")
505
+ });
501
506
  PublishPackageRequestSchema = z9.object({
502
507
  /**
503
508
  * Raw source content — YAML frontmatter + markdown body. Server parses and
@@ -528,7 +533,12 @@ var init_api = __esm({
528
533
  /**
529
534
  * Skip draft validation (preview mode) — only allowed in dev builds.
530
535
  */
531
- dry_run: z9.boolean().optional()
536
+ dry_run: z9.boolean().optional(),
537
+ /**
538
+ * Bundled files beyond SKILL.md: assets, references, scripts, agents, etc.
539
+ * Max 50 entries; max 2 MB per file; max 5 MB total bundle.
540
+ */
541
+ assets: z9.array(AssetSchema).max(50).optional()
532
542
  });
533
543
  PublishWaitQuerySchema = z9.object({
534
544
  /**
@@ -574,8 +584,9 @@ var init_api = __esm({
574
584
  version: SemverSchema,
575
585
  format: z9.string(),
576
586
  content: z9.string(),
577
- notes: z9.array(z9.string()).default([])
587
+ notes: z9.array(z9.string()).default([]),
578
588
  // translator warnings, e.g. ".mdc → Claude: @filepath unsupported"
589
+ assets: z9.array(AssetSchema).default([])
579
590
  });
580
591
  JitReadRequestSchema = z9.object({
581
592
  /**
@@ -1004,6 +1015,7 @@ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, unlink
1004
1015
  import { homedir as homedir2 } from "os";
1005
1016
  import { join as join2 } from "path";
1006
1017
  var SERVICE = "kordis";
1018
+ var plaintextWarningEmitted = false;
1007
1019
  async function loadKeytar() {
1008
1020
  try {
1009
1021
  const mod = await import("keytar");
@@ -1031,6 +1043,16 @@ async function createKeychain() {
1031
1043
  }
1032
1044
  };
1033
1045
  }
1046
+ if (!plaintextWarningEmitted && process.env["KORDIS_JSON"] !== "1") {
1047
+ plaintextWarningEmitted = true;
1048
+ const yellow = "\x1B[33m";
1049
+ const reset = "\x1B[0m";
1050
+ process.stderr.write(
1051
+ `${yellow}[kordis] WARNING: OS keychain unavailable; falling back to plaintext token storage at ~/.kordis/.tokens.json (mode 0600).
1052
+ [kordis] On macOS install Xcode Command Line Tools, on Linux install libsecret-1-dev to enable keytar.${reset}
1053
+ `
1054
+ );
1055
+ }
1034
1056
  const store = await readAll();
1035
1057
  return {
1036
1058
  async save(username, tokens) {
@@ -1321,8 +1343,9 @@ function searchCommand() {
1321
1343
 
1322
1344
  // src/commands/push.ts
1323
1345
  import { Command as Command4 } from "commander";
1324
- import { readFile as readFile3 } from "fs/promises";
1325
- import { resolve } from "path";
1346
+ import { readFile as readFile3, stat, readdir } from "fs/promises";
1347
+ import { resolve, join as join4, posix, sep } from "path";
1348
+ import JSZip from "jszip";
1326
1349
 
1327
1350
  // ../../packages/harness-schema/src/parser.ts
1328
1351
  import { parse as parseYaml } from "yaml";
@@ -1366,21 +1389,32 @@ var FrontmatterParseError = class extends Error {
1366
1389
 
1367
1390
  // src/commands/push.ts
1368
1391
  function pushCommand() {
1369
- return new Command4("push").description("publish a new package or a new version").argument("<file>", "path to the canonical package source").option("--bump <strategy>", "patch|minor|major (update only)", "patch").option(
1392
+ return new Command4("push").description("publish a new package or a new version").argument("<path>", "SKILL.md file, .skill ZIP, or skill directory").option("--bump <strategy>", "patch|minor|major (update only)", "patch").option(
1370
1393
  "--slug <slug>",
1371
1394
  'override slug; accepts "user/slug" to point at a specific package'
1372
1395
  ).option("--type <type>", "override type inference").option("--changelog <text>", "optional changelog entry").option("--dry-run", "validate only; do not publish").option("--no-wait", "return immediately without waiting for review").action(
1373
- async (file, opts) => {
1396
+ async (path, opts) => {
1374
1397
  const bundle = await useClient();
1375
- const abs = resolve(process.cwd(), file);
1376
- const content = await readFile3(abs, "utf8");
1398
+ const abs = resolve(process.cwd(), path);
1399
+ const s = await stat(abs);
1400
+ let content;
1401
+ let assets = [];
1402
+ if (s.isDirectory()) {
1403
+ ({ content, assets } = await bundleDirectory(abs));
1404
+ content = ensureTypeField(content, "skill");
1405
+ } else if (abs.endsWith(".skill")) {
1406
+ ({ content, assets } = await bundleSkillZip(abs));
1407
+ content = ensureTypeField(content, "skill");
1408
+ } else {
1409
+ content = await readFile3(abs, "utf8");
1410
+ }
1377
1411
  let targetUser = null;
1378
1412
  let targetSlug = null;
1379
1413
  if (opts.slug && opts.slug.includes("/")) {
1380
- const [u, s] = opts.slug.split("/", 2);
1381
- if (u && s) {
1414
+ const [u, s2] = opts.slug.split("/", 2);
1415
+ if (u && s2) {
1382
1416
  targetUser = u;
1383
- targetSlug = s;
1417
+ targetSlug = s2;
1384
1418
  }
1385
1419
  } else {
1386
1420
  let nameFromFm = null;
@@ -1415,6 +1449,7 @@ function pushCommand() {
1415
1449
  (api) => api.publish(
1416
1450
  {
1417
1451
  content,
1452
+ assets: assets.length > 0 ? assets : void 0,
1418
1453
  bump: opts.bump,
1419
1454
  slug: opts.slug && !opts.slug.includes("/") ? opts.slug : void 0,
1420
1455
  target_full_slug: targetFullSlug,
@@ -1434,6 +1469,9 @@ function pushCommand() {
1434
1469
  printHuman(
1435
1470
  `${c.bold(res.full_slug)} ${c.gray(`v${res.version}`)} \u2192 ${res.status === "published" ? c.green("published") : res.status === "pending" ? c.yellow("pending review") : c.red("rejected")}`
1436
1471
  );
1472
+ if (assets.length > 0) {
1473
+ printHuman(c.gray(` + ${assets.length} bundled file(s)`));
1474
+ }
1437
1475
  printHuman(` ${c.cyan(res.url)}`);
1438
1476
  if (res.status === "pending") {
1439
1477
  printHuman(
@@ -1453,11 +1491,104 @@ function pushCommand() {
1453
1491
  }
1454
1492
  );
1455
1493
  }
1494
+ function ensureTypeField(content, defaultType) {
1495
+ const fmMatch = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content);
1496
+ if (!fmMatch) return content;
1497
+ const fmBlock = fmMatch[1];
1498
+ if (/^type\s*:/m.test(fmBlock)) return content;
1499
+ const injected = fmMatch[0].replace(/^---\r?\n/, `---
1500
+ type: ${defaultType}
1501
+ `);
1502
+ return content.replace(fmMatch[0], injected);
1503
+ }
1504
+ var EXCLUDE_NAMES = /* @__PURE__ */ new Set([".DS_Store", "Thumbs.db", ".gitkeep"]);
1505
+ var EXCLUDE_DIRS = /* @__PURE__ */ new Set(["__pycache__", "node_modules", ".git", "evals", "dist", ".turbo"]);
1506
+ var EXCLUDE_EXTS = /* @__PURE__ */ new Set([".pyc", ".pyo", ".rbc"]);
1507
+ function shouldExclude(relPath, isDir) {
1508
+ const parts = relPath.split(posix.sep);
1509
+ const name = parts[parts.length - 1];
1510
+ if (EXCLUDE_NAMES.has(name)) return true;
1511
+ if (isDir && EXCLUDE_DIRS.has(name)) return true;
1512
+ const ext = name.includes(".") ? "." + name.split(".").pop() : "";
1513
+ if (EXCLUDE_EXTS.has(ext)) return true;
1514
+ return false;
1515
+ }
1516
+ async function walkDir(base, relDir, collected) {
1517
+ const entries = await readdir(join4(base, relDir), { withFileTypes: true });
1518
+ for (const entry of entries) {
1519
+ const rel = relDir ? `${relDir}/${entry.name}` : entry.name;
1520
+ if (shouldExclude(rel, entry.isDirectory())) continue;
1521
+ if (entry.isDirectory()) {
1522
+ await walkDir(base, rel, collected);
1523
+ } else if (entry.isFile()) {
1524
+ collected.push({ rel, abs: join4(base, rel) });
1525
+ }
1526
+ }
1527
+ }
1528
+ async function readAsset(absPath) {
1529
+ const buf = await readFile3(absPath);
1530
+ const text = buf.toString("utf8");
1531
+ const roundtrip = Buffer.from(text, "utf8");
1532
+ if (roundtrip.equals(buf)) {
1533
+ return { content: text, encoding: "utf8" };
1534
+ }
1535
+ const b64 = buf.toString("base64");
1536
+ printHuman(c.gray(` \u2192 encoding ${absPath.split(sep).pop()} as base64`));
1537
+ return { content: b64, encoding: "base64" };
1538
+ }
1539
+ async function bundleDirectory(dir) {
1540
+ const all = [];
1541
+ await walkDir(dir, "", all);
1542
+ const skillEntry = all.find((f) => f.rel === "SKILL.md");
1543
+ if (!skillEntry) {
1544
+ throw new Error(`No SKILL.md found in directory ${dir}. Push a directory that has SKILL.md at its root.`);
1545
+ }
1546
+ const content = await readFile3(skillEntry.abs, "utf8");
1547
+ const assets = [];
1548
+ for (const f of all) {
1549
+ if (f.rel === "SKILL.md") continue;
1550
+ const { content: fc, encoding } = await readAsset(f.abs);
1551
+ assets.push({ path: f.rel, content: fc, encoding });
1552
+ }
1553
+ return { content, assets };
1554
+ }
1555
+ async function bundleSkillZip(zipPath) {
1556
+ const buf = await readFile3(zipPath);
1557
+ const zip = await JSZip.loadAsync(buf);
1558
+ const entries = Object.keys(zip.files).filter((k) => !zip.files[k].dir);
1559
+ if (entries.length === 0) throw new Error(".skill file is empty");
1560
+ const firstParts = entries[0].split("/");
1561
+ const prefix = firstParts.length > 1 ? firstParts[0] + "/" : "";
1562
+ const stripped = entries.map((k) => ({
1563
+ zipKey: k,
1564
+ rel: prefix ? k.slice(prefix.length) : k
1565
+ })).filter((f) => f.rel.length > 0 && !shouldExclude(f.rel, false));
1566
+ const skillEntry = stripped.find((f) => f.rel === "SKILL.md");
1567
+ if (!skillEntry) {
1568
+ throw new Error(`.skill ZIP has no SKILL.md at its root (prefix="${prefix}")`);
1569
+ }
1570
+ const content = await zip.files[skillEntry.zipKey].async("string");
1571
+ const assets = [];
1572
+ for (const f of stripped) {
1573
+ if (f.rel === "SKILL.md") continue;
1574
+ const rawBuf = await zip.files[f.zipKey].async("uint8array");
1575
+ const buf2 = Buffer.from(rawBuf);
1576
+ const text = buf2.toString("utf8");
1577
+ const roundtrip = Buffer.from(text, "utf8");
1578
+ if (roundtrip.equals(buf2)) {
1579
+ assets.push({ path: f.rel, content: text, encoding: "utf8" });
1580
+ } else {
1581
+ printHuman(c.gray(` \u2192 encoding ${f.rel} as base64`));
1582
+ assets.push({ path: f.rel, content: buf2.toString("base64"), encoding: "base64" });
1583
+ }
1584
+ }
1585
+ return { content, assets };
1586
+ }
1456
1587
 
1457
1588
  // src/commands/pull.ts
1458
1589
  import { Command as Command5 } from "commander";
1459
1590
  import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
1460
- import { dirname as dirname2, join as join4 } from "path";
1591
+ import { dirname as dirname2, join as join5 } from "path";
1461
1592
 
1462
1593
  // ../../packages/harness-schema/src/deps.ts
1463
1594
  init_src();
@@ -1777,7 +1908,12 @@ var BLOCKING_CODES = /* @__PURE__ */ new Set([
1777
1908
  "missing_description",
1778
1909
  "invalid_frontmatter",
1779
1910
  "content_too_short",
1780
- "content_too_long"
1911
+ "content_too_long",
1912
+ // asset-bundle codes
1913
+ "asset_path_unsafe",
1914
+ "asset_too_large",
1915
+ "bundle_too_large",
1916
+ "too_many_assets"
1781
1917
  ]);
1782
1918
  function earlyExit(firstFlag, override = {}) {
1783
1919
  return {
@@ -1860,10 +1996,11 @@ function buildSkillFile(input, outputPath) {
1860
1996
  }
1861
1997
  frontmatter.push("---");
1862
1998
  const content = frontmatter.join("\n") + "\n\n" + body;
1999
+ const resolvedPath = outputPath.replace("{name}", String(name));
1863
2000
  return {
1864
2001
  files: [
1865
2002
  {
1866
- path: outputPath.replace("{name}", String(name)),
2003
+ path: resolvedPath,
1867
2004
  content,
1868
2005
  mergeable: false,
1869
2006
  format: "markdown"
@@ -1873,17 +2010,45 @@ function buildSkillFile(input, outputPath) {
1873
2010
  format: "agent-skills"
1874
2011
  };
1875
2012
  }
1876
- var skillToClaude = (input) => buildSkillFile(input, ".claude/skills/{name}/SKILL.md");
1877
- var skillToCodex = (input) => buildSkillFile(input, ".codex/skills/{name}.md");
1878
- var skillToCursor = (input) => buildSkillFile(input, ".cursor/skills/{name}.md");
1879
- var skillToWindsurf = (input) => buildSkillFile(input, ".windsurf/skills/{name}.md");
1880
- var skillToContinue = (input) => buildSkillFile(input, ".continue/skills/{name}.md");
1881
- var skillToCopilot = (input) => buildSkillFile(input, ".github/skills/{name}.md");
1882
- var skillToGeminiCli = (input) => buildSkillFile(input, ".gemini/skills/{name}.md");
1883
- var skillToOpenCode = (input) => buildSkillFile(input, ".opencode/skills/{name}.md");
1884
- var skillToAntigravity = (input) => buildSkillFile(input, ".antigravity/skills/{name}.md");
2013
+ function buildSkillDir(input, outputPath) {
2014
+ const result = buildSkillFile(input, outputPath);
2015
+ const skillMdPath = result.files[0].path;
2016
+ const skillDir = skillMdPath.slice(0, skillMdPath.lastIndexOf("/"));
2017
+ const assetFiles = (input.assets ?? []).map((asset) => ({
2018
+ path: `${skillDir}/${asset.path}`,
2019
+ content: asset.content,
2020
+ mergeable: false,
2021
+ format: "plain",
2022
+ encoding: asset.encoding ?? "utf8"
2023
+ }));
2024
+ return {
2025
+ files: [...result.files, ...assetFiles],
2026
+ notes: result.notes,
2027
+ format: result.format
2028
+ };
2029
+ }
2030
+ function buildSkillSingleFile(input, outputPath) {
2031
+ const result = buildSkillFile(input, outputPath);
2032
+ const notes = [...result.notes];
2033
+ if (input.assets && input.assets.length > 0) {
2034
+ const tool = outputPath.split("/")[0]?.replace(".", "") ?? "this tool";
2035
+ notes.push(
2036
+ `${input.assets.length} bundled asset(s) not installed \u2014 ${tool} uses single-file skill convention. Pull with --tool claude-code to install the full bundle.`
2037
+ );
2038
+ }
2039
+ return { ...result, notes };
2040
+ }
2041
+ var skillToClaude = (input) => buildSkillDir(input, ".claude/skills/{name}/SKILL.md");
2042
+ var skillToCodex = (input) => buildSkillDir(input, ".codex/skills/{name}/SKILL.md");
2043
+ var skillToCursor = (input) => buildSkillSingleFile(input, ".cursor/skills/{name}.md");
2044
+ var skillToWindsurf = (input) => buildSkillSingleFile(input, ".windsurf/skills/{name}.md");
2045
+ var skillToContinue = (input) => buildSkillSingleFile(input, ".continue/skills/{name}.md");
2046
+ var skillToCopilot = (input) => buildSkillSingleFile(input, ".github/skills/{name}.md");
2047
+ var skillToGeminiCli = (input) => buildSkillDir(input, ".gemini/skills/{name}/SKILL.md");
2048
+ var skillToOpenCode = (input) => buildSkillSingleFile(input, ".opencode/skills/{name}.md");
2049
+ var skillToAntigravity = (input) => buildSkillSingleFile(input, ".antigravity/skills/{name}.md");
1885
2050
  var skillCanonical = (input) => {
1886
- const res = buildSkillFile(input, "SKILL.md");
2051
+ const res = buildSkillDir(input, "SKILL.md");
1887
2052
  res.format = "agent-skills-canonical";
1888
2053
  return res;
1889
2054
  };
@@ -2287,12 +2452,13 @@ function pullCommand() {
2287
2452
  fullSlug,
2288
2453
  frontmatter: parsed.frontmatter.data,
2289
2454
  body: parsed.body,
2290
- version: contentRes.version
2455
+ version: contentRes.version,
2456
+ assets: contentRes.assets
2291
2457
  });
2292
2458
  for (const f of result.files) {
2293
- const abs = join4(installRoot, f.path);
2459
+ const abs = join5(installRoot, f.path);
2294
2460
  await mkdir3(dirname2(abs), { recursive: true });
2295
- await writeFile3(abs, f.content, "utf8");
2461
+ await writeEmittedFile(abs, f.content, f.encoding);
2296
2462
  written.push(abs);
2297
2463
  }
2298
2464
  notes.push(...result.notes);
@@ -2303,15 +2469,16 @@ function pullCommand() {
2303
2469
  fullSlug,
2304
2470
  frontmatter: parsed.frontmatter.data,
2305
2471
  body: parsed.body,
2306
- version: contentRes.version
2472
+ version: contentRes.version,
2473
+ assets: contentRes.assets
2307
2474
  });
2308
2475
  notes.push(
2309
2476
  `Tool "${format}" is not supported for type "${parsed.type}"; installed canonical form instead.`
2310
2477
  );
2311
2478
  for (const f of result.files) {
2312
- const abs = join4(installRoot, f.path);
2479
+ const abs = join5(installRoot, f.path);
2313
2480
  await mkdir3(dirname2(abs), { recursive: true });
2314
- await writeFile3(abs, f.content, "utf8");
2481
+ await writeEmittedFile(abs, f.content, f.encoding);
2315
2482
  written.push(abs);
2316
2483
  }
2317
2484
  } else {
@@ -2339,7 +2506,7 @@ function pullCommand() {
2339
2506
  );
2340
2507
  }
2341
2508
  async function detectTool(root) {
2342
- const { stat } = await import("fs/promises");
2509
+ const { stat: stat2 } = await import("fs/promises");
2343
2510
  const checks = [
2344
2511
  [".claude", "claude-code"],
2345
2512
  [".cursor", "cursor"],
@@ -2355,13 +2522,20 @@ async function detectTool(root) {
2355
2522
  ];
2356
2523
  for (const [path, tool] of checks) {
2357
2524
  try {
2358
- await stat(join4(root, path));
2525
+ await stat2(join5(root, path));
2359
2526
  return tool;
2360
2527
  } catch {
2361
2528
  }
2362
2529
  }
2363
2530
  return null;
2364
2531
  }
2532
+ async function writeEmittedFile(abs, content, encoding) {
2533
+ if (encoding === "base64") {
2534
+ await writeFile3(abs, Buffer.from(content, "base64"));
2535
+ } else {
2536
+ await writeFile3(abs, content, "utf8");
2537
+ }
2538
+ }
2365
2539
 
2366
2540
  // src/commands/read.ts
2367
2541
  import { Command as Command6 } from "commander";
@@ -2461,7 +2635,7 @@ function logoutCommand() {
2461
2635
  // src/commands/setup.ts
2462
2636
  import { readFile as readFile4, writeFile as writeFile4, mkdir as mkdir4, access } from "fs/promises";
2463
2637
  import { homedir as homedir3 } from "os";
2464
- import { join as join5, dirname as dirname3 } from "path";
2638
+ import { join as join6, dirname as dirname3 } from "path";
2465
2639
  import { fileURLToPath as fileURLToPath2 } from "url";
2466
2640
  import { Command as Command9 } from "commander";
2467
2641
  var MARKER_START = "<!-- kordis-start -->";
@@ -2494,18 +2668,18 @@ function assetRoots() {
2494
2668
  const __dir2 = dirname3(fileURLToPath2(import.meta.url));
2495
2669
  return {
2496
2670
  // Built binary: bin/kordis.js → assets/ is one level up
2497
- bundled: join5(__dir2, "..", "assets"),
2671
+ bundled: join6(__dir2, "..", "assets"),
2498
2672
  // Dev mode: src/commands/setup.ts → 4 levels up to repo root, then packages/skills/
2499
- source: join5(__dir2, "..", "..", "..", "..", "packages", "skills")
2673
+ source: join6(__dir2, "..", "..", "..", "..", "packages", "skills")
2500
2674
  };
2501
2675
  }
2502
2676
  async function pickAssetRoot() {
2503
2677
  const { bundled, source } = assetRoots();
2504
- return await exists(join5(bundled, "KORDIS.md")) ? bundled : source;
2678
+ return await exists(join6(bundled, "KORDIS.md")) ? bundled : source;
2505
2679
  }
2506
2680
  async function loadHubContent() {
2507
2681
  const root = await pickAssetRoot();
2508
- const raw = await readFile4(join5(root, "KORDIS.md"), "utf-8");
2682
+ const raw = await readFile4(join6(root, "KORDIS.md"), "utf-8");
2509
2683
  if (raw.startsWith("---")) {
2510
2684
  const end = raw.indexOf("\n---", 4);
2511
2685
  if (end !== -1) return raw.slice(end + 4).trimStart();
@@ -2514,7 +2688,7 @@ async function loadHubContent() {
2514
2688
  }
2515
2689
  async function loadSubSkillContent(name) {
2516
2690
  const root = await pickAssetRoot();
2517
- return readFile4(join5(root, name, "SKILL.md"), "utf-8");
2691
+ return readFile4(join6(root, name, "SKILL.md"), "utf-8");
2518
2692
  }
2519
2693
  async function injectBlock(filePath, content, dryRun) {
2520
2694
  const block = `${MARKER_START}
@@ -2582,8 +2756,8 @@ function setupCommand() {
2582
2756
  let anyFound = false;
2583
2757
  const changedTools = /* @__PURE__ */ new Set();
2584
2758
  for (const target of HUB_TARGETS) {
2585
- const dirPath = join5(home, target.dir);
2586
- const filePath = join5(home, target.file);
2759
+ const dirPath = join6(home, target.dir);
2760
+ const filePath = join6(home, target.file);
2587
2761
  if (!await exists(dirPath)) {
2588
2762
  if (!opts.silent) {
2589
2763
  printHuman(c.gray(` - ${target.tool.padEnd(12)} not installed, skipped`));
@@ -2610,10 +2784,10 @@ function setupCommand() {
2610
2784
  }
2611
2785
  }
2612
2786
  for (const target of SKILL_TARGETS) {
2613
- const dirPath = join5(home, target.dir);
2787
+ const dirPath = join6(home, target.dir);
2614
2788
  if (!await exists(dirPath)) continue;
2615
2789
  for (const subName of SUB_SKILLS) {
2616
- const filePath = join5(home, target.skillsDir, subName, "SKILL.md");
2790
+ const filePath = join6(home, target.skillsDir, subName, "SKILL.md");
2617
2791
  const dest = `~/${target.skillsDir}/${subName}/SKILL.md`;
2618
2792
  try {
2619
2793
  const result = await copyBundledSkill(filePath, subSkillContents[subName], dryRun);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rayul/kordis",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
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",
@@ -26,6 +26,7 @@
26
26
  "dependencies": {
27
27
  "@iarna/toml": "^2.2.5",
28
28
  "commander": "^12.1.0",
29
+ "jszip": "^3.10.1",
29
30
  "kleur": "^4.1.5",
30
31
  "open": "^10.1.0",
31
32
  "ora": "^8.1.1",
@@ -42,6 +43,7 @@
42
43
  "@kordis/contracts": "workspace:*",
43
44
  "@kordis/harness-schema": "workspace:*",
44
45
  "@kordis/translators": "workspace:*",
46
+ "@types/jszip": "^3.4.1",
45
47
  "@types/node": "^22.9.0",
46
48
  "@types/prompts": "^2.4.9",
47
49
  "tsup": "^8.3.5",