@rayul/kordis 0.9.3 → 0.10.0

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.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: kordis-publishing
3
- description: Use whenever the user wants to publish, share, or push a package to Kordis — phrases like "publish this", "share this skill to Kordis", "push this rule", "release a new version of <slug>", "bump the version", "semver", "버전 올리기". Covers dry-run flow, frontmatter requirements, version bump strategies, semver rules, and the AI review pipeline.
3
+ description: Use whenever the user wants to publish, share, or push a package to Kordis — phrases like "publish this", "share this skill to Kordis", "push this rule", "release a new version of <slug>", "bump the version", "semver", "버전 올리기", "yank", "withdraw a version", "unpublish a release", "철회". Covers dry-run flow, frontmatter requirements, version bump strategies, semver rules, yank/unyank for retracting bad releases, and the AI review pipeline.
4
4
  type: skill
5
5
  allowed-tools: ["Bash(kordis:*)", "Read", "Edit"]
6
6
  ---
@@ -36,6 +36,40 @@ single `.md` file that doesn't match the SKILL.md filename pattern.
36
36
  use only `--bump patch|minor|major` to control versioning. Having both
37
37
  is the most common push failure.
38
38
 
39
+ ## Yanking a bad version (0.10.0+)
40
+
41
+ When a single release goes wrong — leaked secret, broken on platform X,
42
+ superseded by an urgent fix — use **yank**, not delete.
43
+
44
+ ### `kordis yank <full_slug>@<version> [--reason <text>] [--force]`
45
+ Soft-marks the specific version as "unrecommended". Author-only.
46
+ Versions yanked this way:
47
+ - Disappear from `kordis pull <slug>` (bare slug) and from `@^X.Y` /
48
+ `@X.x` / `@*` range resolution
49
+ - **Still install** when explicitly pinned to that exact version
50
+ (`kordis pull <slug>@1.2.3`) — but the consumer's CLI prints a
51
+ stderr warning naming the reason. This preserves reproducibility of
52
+ prior pinned installs.
53
+ - Reversible at any time with `kordis unyank`
54
+
55
+ `--reason` is plain text capped at 280 chars — shown verbatim to
56
+ consumers in `info` / `versions` / pull warning. Examples that work
57
+ well: `"leaked AWS key"`, `"broken on Node 18"`, `"superseded by
58
+ 0.5.0 — security fix"`.
59
+
60
+ ### `kordis unyank <full_slug>@<version>`
61
+ Reverses a yank. Idempotent. After unyank, the version re-enters
62
+ range resolution and `current_version` recomputes if appropriate.
63
+
64
+ ### Yank vs delete — pick the right one
65
+ - **`kordis yank <slug>@<version>`** — one bad release. Reversible.
66
+ Per-version. Preserves pinned-install reproducibility.
67
+ - **`kordis delete <slug>`** — the whole package should disappear.
68
+ Permanent + slug reservation forever (no successor can republish
69
+ under the same name). Last-resort.
70
+
71
+ If you just pushed a wrong version, the answer is almost always yank.
72
+
39
73
  ## Deleting a package
40
74
 
41
75
  ### `kordis delete <full_slug> [--force]`
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: kordis-using
3
- description: Use whenever the user wants to consult or install a Kordis package — including any `<username>/<slug>` reference, "install this skill", "use the X skill", "fetch the Y rule", "set this repo up with…", "pin to version", "specific version", "버전 고정", "전이 의존성", "transitive dependency". Covers JIT read vs pull, the five Kordis package types, dependencies, version pinning, range syntax, and attribution. Load eagerly when in doubt.
3
+ description: Use whenever the user wants to consult or install a Kordis package — including any `<username>/<slug>` reference, "install this skill", "use the X skill", "fetch the Y rule", "set this repo up with…", "pin to version", "specific version", "버전 고정", "전이 의존성", "transitive dependency", "yanked", "version withdrawn", "철회", "unrecommended version". Covers JIT read vs pull, the five Kordis package types, dependencies, version pinning, range syntax, yank semantics, and attribution. Load eagerly when in doubt.
4
4
  type: skill
5
5
  allowed-tools: ["Bash(kordis:*)"]
6
6
  ---
@@ -132,6 +132,31 @@ transitive deps — read is a one-shot context inject, no install plan.
132
132
  `kordis info user/foo@^1` shows a `Requested: 1.4.2 (current: 2.0.0)`
133
133
  line above `Type` when the pin differs from the registry default.
134
134
 
135
+ ## When a version is yanked (0.10.0+)
136
+
137
+ Authors can soft-mark a specific release as "yanked" via `kordis yank
138
+ <slug>@<version>` — usually to discourage installs of a broken or
139
+ unsafe version. Yank is reversible (`kordis unyank`), per-version (NOT
140
+ whole-package), and preserves reproducibility of prior pinned
141
+ installs.
142
+
143
+ Three install behaviours:
144
+
145
+ | Input | Behaviour |
146
+ | ---------------------------------- | ---------------------------------------------------------------- |
147
+ | `kordis pull <slug>` | Always picks the highest **non-yanked** version. Yanked versions are silently skipped — user sees the next-best release. |
148
+ | `kordis pull <slug>@^X.Y` | Range resolution skips yanked. If every match is yanked, server returns `not_found` with hint "every version satisfying X has been yanked". |
149
+ | `kordis pull <slug>@<exact>` (pinned to a yanked version) | Succeeds (exit 0) with a stderr warning: `⚠ pulled yanked version X of slug (reason: …) — author marked this as unrecommended on YYYY-MM-DD`. Stays installable so old lockfiles keep working. |
150
+
151
+ `kordis info <slug>` shows a red `⚠ Yanked YYYY-MM-DD` banner with the
152
+ reason when the resolved version is yanked. `kordis versions <slug>`
153
+ greys out yanked rows and suffixes their status with ` (yanked)`.
154
+
155
+ Transitive yank: if `A → B@^1` and the only `B@^1` is yanked, server
156
+ returns `dependency_conflict` (exit 35) with a chain link naming the
157
+ parent. Surface that chain to the user — fixing usually means pinning
158
+ A's requirement to a non-yanked range, or contacting B's author.
159
+
135
160
  ## Dependencies
136
161
  `kordis pull` reports `depends` and `recommends` from the package metadata.
137
162
  Surface the dependency tree to the user FIRST. If they accept,
package/dist/index.js CHANGED
@@ -334,7 +334,14 @@ var init_package = __esm({
334
334
  * concrete resolved version. Absent for bare-slug calls (where the
335
335
  * response reflects `current_version`).
336
336
  */
337
- requested_version: SemverSchema.optional()
337
+ requested_version: SemverSchema.optional(),
338
+ /**
339
+ * 0.10.0+ — yank status of the *displayed* version (requested_version
340
+ * if pinned, else current_version). Absent for non-yanked versions
341
+ * so pre-0.10 clients keep ignoring unknown fields.
342
+ */
343
+ yanked_at: IsoDateTimeSchema.nullable().optional(),
344
+ yank_reason: z6.string().max(280).nullable().optional()
338
345
  });
339
346
  PackageVersionSchema = z6.object({
340
347
  id: UuidSchema,
@@ -347,7 +354,15 @@ var init_package = __esm({
347
354
  review_status: ReviewStatusSchema,
348
355
  review_score: z6.number().nullable(),
349
356
  review_flags: z6.array(ReviewFlagSchema),
350
- published_at: IsoDateTimeSchema
357
+ published_at: IsoDateTimeSchema,
358
+ /**
359
+ * 0.10.0+ — when set, this version was soft-marked as yanked by the
360
+ * author (or an admin override). Excluded from range/`*` resolution
361
+ * but still installable when explicitly pinned (`@<exact>`). Null
362
+ * for active versions.
363
+ */
364
+ yanked_at: IsoDateTimeSchema.nullable().optional(),
365
+ yank_reason: z6.string().max(280).nullable().optional()
351
366
  });
352
367
  PackageVersionSummarySchema = PackageVersionSchema.omit({ content: true });
353
368
  VersionDiffSchema = z6.object({
@@ -500,7 +515,7 @@ var init_auth = __esm({
500
515
 
501
516
  // ../../packages/contracts/src/api.ts
502
517
  import { z as z9 } from "zod";
503
- var SearchQuerySchema, SearchResponseSchema, TrendingQuerySchema, RisingQuerySchema, StarResponseSchema, AssetSchema, PublishPackageRequestSchema, PublishWaitQuerySchema, PublishPackageResponseSchema, PackagePathParamsSchema, ContentQuerySchema, PackageContentResponseSchema, JitReadRequestSchema, JitReadResponseSchema, PullPackageRequestSchema, PullPackageResponseSchema, ResolveVersionQuerySchema, ResolveVersionResponseSchema, GetPackageQuerySchema, ListVersionsResponseSchema, VersionDiffQuerySchema, ListCommentsQuerySchema, ListCommentsResponseSchema, UserProfileResponseSchema, UserPackagesQuerySchema, ListUserPackagesResponseSchema, ListUserStarsResponseSchema, FollowResponseSchema, ListFollowersQuerySchema, ListFollowersResponseSchema;
518
+ var SearchQuerySchema, SearchResponseSchema, TrendingQuerySchema, RisingQuerySchema, StarResponseSchema, AssetSchema, PublishPackageRequestSchema, PublishWaitQuerySchema, PublishPackageResponseSchema, PackagePathParamsSchema, ContentQuerySchema, PackageContentResponseSchema, JitReadRequestSchema, JitReadResponseSchema, PullPackageRequestSchema, PullPackageResponseSchema, YankRequestSchema, YankResponseSchema, ResolveVersionQuerySchema, ResolveVersionResponseSchema, GetPackageQuerySchema, ListVersionsResponseSchema, VersionDiffQuerySchema, ListCommentsQuerySchema, ListCommentsResponseSchema, UserProfileResponseSchema, UserPackagesQuerySchema, ListUserPackagesResponseSchema, ListUserStarsResponseSchema, FollowResponseSchema, ListFollowersQuerySchema, ListFollowersResponseSchema;
504
519
  var init_api = __esm({
505
520
  "../../packages/contracts/src/api.ts"() {
506
521
  "use strict";
@@ -692,6 +707,17 @@ var init_api = __esm({
692
707
  installed_paths: z9.array(z9.string()),
693
708
  tool_detected: ToolSlugSchema.nullable(),
694
709
  format: z9.string(),
710
+ /**
711
+ * 0.10.0+ — true when the version being pulled was explicitly
712
+ * pinned (@version) AND has been yanked by its author. CLI uses this
713
+ * to print the stderr warning. Bare-slug / range pulls never set
714
+ * this flag (those paths silently skip yanked versions). Field is
715
+ * omitted entirely (not false) when not relevant, so pre-0.10
716
+ * clients ignore it cleanly.
717
+ */
718
+ yanked: z9.boolean().optional(),
719
+ yank_reason: z9.string().max(280).nullable().optional(),
720
+ yanked_at: IsoDateTimeSchema.nullable().optional(),
695
721
  dependencies: z9.object({
696
722
  required: z9.array(
697
723
  z9.object({
@@ -718,6 +744,16 @@ var init_api = __esm({
718
744
  }),
719
745
  recommends_hint: z9.string().nullable()
720
746
  });
747
+ YankRequestSchema = z9.object({
748
+ reason: z9.string().max(280).optional()
749
+ });
750
+ YankResponseSchema = z9.object({
751
+ full_slug: FullSlugSchema,
752
+ version: SemverSchema,
753
+ yanked_at: IsoDateTimeSchema.nullable(),
754
+ yank_reason: z9.string().max(280).nullable(),
755
+ effective_current_version: SemverSchema
756
+ });
721
757
  ResolveVersionQuerySchema = z9.object({
722
758
  range: SemverRangeSchema
723
759
  });
@@ -1302,6 +1338,25 @@ var init_client = __esm({
1302
1338
  `/api/packages/${username}/${slug}/versions`
1303
1339
  );
1304
1340
  }
1341
+ /**
1342
+ * 0.10.0+ — soft-yank a specific version. Author-only by default;
1343
+ * pass `asAdmin: true` to use the admin override (server validates
1344
+ * the caller's role). Reason is plain text, 280 chars max.
1345
+ */
1346
+ yankVersion(username, slug, version, body = {}, opts = {}) {
1347
+ const query = opts.asAdmin ? { as: "admin" } : void 0;
1348
+ return this.request(
1349
+ `/api/packages/${username}/${slug}/versions/${version}/yank`,
1350
+ { method: "POST", body, ...query ? { query } : {} }
1351
+ );
1352
+ }
1353
+ unyankVersion(username, slug, version, opts = {}) {
1354
+ const query = opts.asAdmin ? { as: "admin" } : void 0;
1355
+ return this.request(
1356
+ `/api/packages/${username}/${slug}/versions/${version}/yank`,
1357
+ { method: "DELETE", ...query ? { query } : {} }
1358
+ );
1359
+ }
1305
1360
  diff(username, slug, from, to) {
1306
1361
  return this.request(`/api/packages/${username}/${slug}/diff`, {
1307
1362
  query: { from, to }
@@ -1430,7 +1485,7 @@ var init_src2 = __esm({
1430
1485
 
1431
1486
  // src/index.ts
1432
1487
  init_src2();
1433
- import { Command as Command23 } from "commander";
1488
+ import { Command as Command25 } from "commander";
1434
1489
 
1435
1490
  // src/commands/login.ts
1436
1491
  import { Command } from "commander";
@@ -3859,7 +3914,12 @@ async function doPullInstall(fullSlug, tool, installRoot, opts = {}) {
3859
3914
  installRoot,
3860
3915
  written,
3861
3916
  notes,
3862
- plan: pullRes.dependencies
3917
+ plan: pullRes.dependencies,
3918
+ ...pullRes.yanked ? {
3919
+ yanked: true,
3920
+ yankReason: pullRes.yank_reason ?? null,
3921
+ yankedAt: pullRes.yanked_at ?? null
3922
+ } : {}
3863
3923
  };
3864
3924
  }
3865
3925
  function pullCommand() {
@@ -3928,6 +3988,11 @@ function pullCommand() {
3928
3988
  tool_detected: result.tool,
3929
3989
  installed_paths: result.written,
3930
3990
  notes: result.notes,
3991
+ ...result.yanked ? {
3992
+ yanked: true,
3993
+ yank_reason: result.yankReason,
3994
+ yanked_at: result.yankedAt
3995
+ } : {},
3931
3996
  dependencies: dependencyInstalls.map((d) => ({
3932
3997
  full_slug: d.fullSlug,
3933
3998
  version: d.result.version,
@@ -3938,6 +4003,16 @@ function pullCommand() {
3938
4003
  printHuman(
3939
4004
  c.green(`\u2713 Installed ${c.bold(parsed.fullSlug)} v${result.version}`)
3940
4005
  );
4006
+ if (result.yanked) {
4007
+ const reasonSuffix = result.yankReason ? ` (reason: ${result.yankReason})` : "";
4008
+ const dateSuffix = result.yankedAt ? ` on ${new Date(result.yankedAt).toISOString().slice(0, 10)}` : "";
4009
+ process.stderr.write(
4010
+ c.yellow(
4011
+ `\u26A0 pulled yanked version ${result.version} of ${parsed.fullSlug}${reasonSuffix} \u2014 author marked this as unrecommended${dateSuffix}
4012
+ `
4013
+ )
4014
+ );
4015
+ }
3941
4016
  for (const w of result.written) printHuman(` ${c.gray("wrote")} ${w}`);
3942
4017
  for (const n of result.notes) printHuman(c.yellow(` ! ${n}`));
3943
4018
  for (const dep of dependencyInstalls) {
@@ -4060,6 +4135,15 @@ function infoCommand() {
4060
4135
  printHuman(c.bold(detail.full_slug) + " " + c.gray(`v${headerVersion}`));
4061
4136
  printHuman(" " + detail.description);
4062
4137
  printHuman("");
4138
+ if (detail.yanked_at) {
4139
+ printHuman(
4140
+ c.red(" \u26A0 Yanked") + " " + c.gray(new Date(detail.yanked_at).toISOString().slice(0, 10))
4141
+ );
4142
+ if (detail.yank_reason) {
4143
+ printHuman(c.gray(` reason: ${detail.yank_reason}`));
4144
+ }
4145
+ printHuman("");
4146
+ }
4063
4147
  if (detail.requested_version && detail.requested_version !== detail.current_version) {
4064
4148
  printHuman(
4065
4149
  ` ${c.cyan("Requested")}: ${detail.requested_version} ${c.gray(`(current: ${detail.current_version})`)}`
@@ -4630,10 +4714,135 @@ function deleteCommand() {
4630
4714
  });
4631
4715
  }
4632
4716
 
4633
- // src/commands/list.ts
4717
+ // src/commands/yank.ts
4634
4718
  import { Command as Command13 } from "commander";
4719
+ import prompts3 from "prompts";
4720
+ function yankCommand() {
4721
+ return new Command13("yank").description("soft-mark a specific version of your package as yanked").argument(
4722
+ "<full_slug>",
4723
+ 'package identifier with explicit version, e.g. "user/slug@1.2.3"'
4724
+ ).option("--reason <text>", "optional reason shown to consumers (\u2264280 chars)").option("--force", "skip the confirmation prompt").action(
4725
+ async (fullSlugArg, opts) => {
4726
+ const parsed = parseSlugAtVersion(fullSlugArg);
4727
+ if (!parsed.version) {
4728
+ throw new Error(
4729
+ 'yank requires an explicit version pin \u2014 use the form "user/slug@1.2.3" (range syntax not allowed)'
4730
+ );
4731
+ }
4732
+ const bundle = await useClient();
4733
+ const detail = await bundle.api.getPackage(parsed.username, parsed.slug);
4734
+ if (!opts.force) {
4735
+ printHuman(c.yellow(" \u26A0 You are about to yank:"));
4736
+ printHuman(
4737
+ ` ${c.bold(detail.full_slug)} v${parsed.version}`
4738
+ );
4739
+ if (opts.reason) {
4740
+ printHuman(` reason: ${opts.reason}`);
4741
+ }
4742
+ printHuman(
4743
+ c.gray(
4744
+ " Yanked versions disappear from range/`*` resolution. Pinned installs (`@" + parsed.version + "`) still work. Reversible with `kordis unyank`."
4745
+ )
4746
+ );
4747
+ printHuman("");
4748
+ const { confirmed } = await prompts3({
4749
+ type: "confirm",
4750
+ name: "confirmed",
4751
+ message: `Yank ${c.bold(`${parsed.fullSlug}@${parsed.version}`)}?`,
4752
+ initial: false
4753
+ });
4754
+ if (!confirmed) {
4755
+ printHuman(c.gray("Aborted."));
4756
+ process.exit(0);
4757
+ }
4758
+ }
4759
+ const res = await bundle.authedRequest(
4760
+ (api) => api.yankVersion(parsed.username, parsed.slug, parsed.version, {
4761
+ ...opts.reason ? { reason: opts.reason } : {}
4762
+ })
4763
+ );
4764
+ printDual({
4765
+ json: res,
4766
+ human: () => {
4767
+ printHuman(
4768
+ c.green(
4769
+ `\u2713 Yanked ${c.bold(parsed.fullSlug)}@${parsed.version}`
4770
+ )
4771
+ );
4772
+ if (res.yank_reason) {
4773
+ printHuman(c.gray(` reason: ${res.yank_reason}`));
4774
+ }
4775
+ if (res.effective_current_version !== parsed.version) {
4776
+ printHuman(
4777
+ c.gray(
4778
+ ` current_version is now ${c.bold(res.effective_current_version)} (was ${parsed.version})`
4779
+ )
4780
+ );
4781
+ } else {
4782
+ printHuman(
4783
+ c.gray(
4784
+ ` current_version unchanged at ${c.bold(res.effective_current_version)} (no higher non-yanked version exists)`
4785
+ )
4786
+ );
4787
+ }
4788
+ printHuman(
4789
+ c.gray(
4790
+ ` \u2192 Reverse with: kordis unyank ${parsed.fullSlug}@${parsed.version}`
4791
+ )
4792
+ );
4793
+ }
4794
+ });
4795
+ }
4796
+ );
4797
+ }
4798
+
4799
+ // src/commands/unyank.ts
4800
+ import { Command as Command14 } from "commander";
4801
+ function unyankCommand() {
4802
+ return new Command14("unyank").description("reverse a yank on a specific version").argument(
4803
+ "<full_slug>",
4804
+ 'package identifier with explicit version, e.g. "user/slug@1.2.3"'
4805
+ ).action(async (fullSlugArg) => {
4806
+ const parsed = parseSlugAtVersion(fullSlugArg);
4807
+ if (!parsed.version) {
4808
+ throw new Error(
4809
+ 'unyank requires an explicit version pin \u2014 use "user/slug@1.2.3"'
4810
+ );
4811
+ }
4812
+ const bundle = await useClient();
4813
+ const res = await bundle.authedRequest(
4814
+ (api) => api.unyankVersion(parsed.username, parsed.slug, parsed.version)
4815
+ );
4816
+ printDual({
4817
+ json: res,
4818
+ human: () => {
4819
+ printHuman(
4820
+ c.green(
4821
+ `\u2713 Unyanked ${c.bold(parsed.fullSlug)}@${parsed.version}`
4822
+ )
4823
+ );
4824
+ if (res.effective_current_version !== parsed.version) {
4825
+ printHuman(
4826
+ c.gray(
4827
+ ` current_version is ${c.bold(res.effective_current_version)} (highest non-yanked)`
4828
+ )
4829
+ );
4830
+ } else {
4831
+ printHuman(
4832
+ c.gray(
4833
+ ` current_version is now ${c.bold(res.effective_current_version)}`
4834
+ )
4835
+ );
4836
+ }
4837
+ }
4838
+ });
4839
+ });
4840
+ }
4841
+
4842
+ // src/commands/list.ts
4843
+ import { Command as Command15 } from "commander";
4635
4844
  function listCommand() {
4636
- return new Command13("list").description("list packages \u2014 your own (no arg) or a public user\u2019s (with <username>)").argument("[username]", "kordis username to list (omit for self)").option("--limit <n>", "max results per page", (v) => parseInt(v, 10), 20).option("--page <n>", "self-mode page number (1-based)", (v) => parseInt(v, 10), 1).option("--cursor <token>", "public-mode pagination cursor").option("--sort <mode>", "sort: latest|popular (self) or stars|downloads|recent|updated (public)", "latest").action(async (rawUsername, opts) => {
4845
+ return new Command15("list").description("list packages \u2014 your own (no arg) or a public user\u2019s (with <username>)").argument("[username]", "kordis username to list (omit for self)").option("--limit <n>", "max results per page", (v) => parseInt(v, 10), 20).option("--page <n>", "self-mode page number (1-based)", (v) => parseInt(v, 10), 1).option("--cursor <token>", "public-mode pagination cursor").option("--sort <mode>", "sort: latest|popular (self) or stars|downloads|recent|updated (public)", "latest").action(async (rawUsername, opts) => {
4637
4846
  const bundle = await useClient();
4638
4847
  if (rawUsername === void 0) {
4639
4848
  requireAuth(bundle);
@@ -4704,9 +4913,9 @@ function listCommand() {
4704
4913
  }
4705
4914
 
4706
4915
  // src/commands/versions.ts
4707
- import { Command as Command14 } from "commander";
4916
+ import { Command as Command16 } from "commander";
4708
4917
  function versionsCommand() {
4709
- return new Command14("versions").description("list all versions of a package").argument("<full_slug>", "package identifier {username}/{slug}").action(async (fullSlug) => {
4918
+ return new Command16("versions").description("list all versions of a package").argument("<full_slug>", "package identifier {username}/{slug}").action(async (fullSlug) => {
4710
4919
  const [username, slug] = fullSlug.split("/");
4711
4920
  if (!username || !slug) {
4712
4921
  throw new Error('full_slug must be "{username}/{slug}"');
@@ -4723,12 +4932,17 @@ function versionsCommand() {
4723
4932
  );
4724
4933
  printHuman("");
4725
4934
  for (const v of res.versions) {
4726
- 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);
4935
+ const isYanked = Boolean(v.yanked_at);
4936
+ const reviewLabel = isYanked ? c.gray(`${v.review_status} (yanked)`) : v.review_status === "passed" ? c.green(v.review_status) : v.review_status === "rejected" ? c.red(v.review_status) : c.yellow(v.review_status);
4727
4937
  const age = relativeTime(v.published_at);
4728
4938
  const isCurrent = v.version === res.current_version;
4939
+ const versionLabel = isYanked ? c.gray(v.version) : c.bold(v.version);
4729
4940
  printHuman(
4730
- ` ${c.bold(v.version)}${isCurrent ? c.cyan(" (current)") : ""} ${statusColor} ${c.gray(age)}`
4941
+ ` ${versionLabel}${isCurrent ? c.cyan(" (current)") : ""} ${reviewLabel} ${c.gray(age)}`
4731
4942
  );
4943
+ if (isYanked && v.yank_reason) {
4944
+ printHuman(` ${c.gray(`reason: ${v.yank_reason}`)}`);
4945
+ }
4732
4946
  }
4733
4947
  }
4734
4948
  });
@@ -4760,13 +4974,13 @@ function relativeTime(isoString) {
4760
4974
  }
4761
4975
 
4762
4976
  // src/commands/outdated.ts
4763
- import { Command as Command15 } from "commander";
4977
+ import { Command as Command17 } from "commander";
4764
4978
  import semver from "semver";
4765
4979
  function effectiveRange(entry) {
4766
4980
  return entry.requestedRange ?? `^${entry.version}`;
4767
4981
  }
4768
4982
  function outdatedCommand() {
4769
- return new Command15("outdated").description("check installed packages for available updates").action(async () => {
4983
+ return new Command17("outdated").description("check installed packages for available updates").action(async () => {
4770
4984
  const lf = await readLockfile();
4771
4985
  const slugs = Object.keys(lf.packages);
4772
4986
  if (slugs.length === 0) {
@@ -4864,13 +5078,13 @@ function outdatedCommand() {
4864
5078
  }
4865
5079
 
4866
5080
  // src/commands/update.ts
4867
- import { Command as Command16 } from "commander";
5081
+ import { Command as Command18 } from "commander";
4868
5082
  import semver2 from "semver";
4869
5083
  function effectiveRange2(entry) {
4870
5084
  return entry.requestedRange ?? `^${entry.version}`;
4871
5085
  }
4872
5086
  function updateCommand() {
4873
- 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) => {
5087
+ return new Command18("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) => {
4874
5088
  const lf = await readLockfile();
4875
5089
  let slugs = Object.keys(lf.packages);
4876
5090
  if (slugs.length === 0) {
@@ -5004,9 +5218,9 @@ function updateCommand() {
5004
5218
  }
5005
5219
 
5006
5220
  // src/commands/diff.ts
5007
- import { Command as Command17 } from "commander";
5221
+ import { Command as Command19 } from "commander";
5008
5222
  function diffCommand() {
5009
- 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) => {
5223
+ return new Command19("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) => {
5010
5224
  let fullSlug = fullSlugArg;
5011
5225
  let from = fromArg;
5012
5226
  let to = toArg;
@@ -5077,13 +5291,13 @@ function diffCommand() {
5077
5291
  }
5078
5292
 
5079
5293
  // src/commands/recommend.ts
5080
- import { Command as Command18 } from "commander";
5081
- import prompts3 from "prompts";
5294
+ import { Command as Command20 } from "commander";
5295
+ import prompts4 from "prompts";
5082
5296
  function recommendCommand() {
5083
- return new Command18("recommend").description("show personalized package recommendations").option("--limit <n>", "max items to return (1-50)", (v) => parseInt(v, 10), 10).option("--verbose", "include score_breakdown per item", false).option("--no-context", "skip the X-Kordis-Context header for this call").action(async (opts) => {
5297
+ return new Command20("recommend").description("show personalized package recommendations").option("--limit <n>", "max items to return (1-50)", (v) => parseInt(v, 10), 10).option("--verbose", "include score_breakdown per item", false).option("--no-context", "skip the X-Kordis-Context header for this call").action(async (opts) => {
5084
5298
  const cfg = await loadCliConfig();
5085
5299
  if (cfg.personalization === "undecided" && process.stdin.isTTY && !isJsonMode() && opts.context !== false) {
5086
- const { enable } = await prompts3({
5300
+ const { enable } = await prompts4({
5087
5301
  type: "confirm",
5088
5302
  name: "enable",
5089
5303
  message: "Personalize recommendations using anonymous local signals (installed agent tools, language stats, frameworks)? Server never stores them \u2014 only re-ranks results in-flight.",
@@ -5188,9 +5402,9 @@ function renderItem(idx, it, verbose) {
5188
5402
  }
5189
5403
 
5190
5404
  // src/commands/profile.ts
5191
- import { Command as Command19 } from "commander";
5405
+ import { Command as Command21 } from "commander";
5192
5406
  function profileCommand() {
5193
- return new Command19("profile").description("inspect a user\u2019s public profile").argument("<username>", "kordis username").action(async (raw) => {
5407
+ return new Command21("profile").description("inspect a user\u2019s public profile").argument("<username>", "kordis username").action(async (raw) => {
5194
5408
  const username = parseUsername(raw);
5195
5409
  const bundle = await useClient();
5196
5410
  const profile = await bundle.api.getUserProfile(username);
@@ -5225,9 +5439,9 @@ function profileCommand() {
5225
5439
  }
5226
5440
 
5227
5441
  // src/commands/stars.ts
5228
- import { Command as Command20 } from "commander";
5442
+ import { Command as Command22 } from "commander";
5229
5443
  function starsCommand() {
5230
- return new Command20("stars").description("list packages a user has starred").argument("<username>", "kordis username").option("--limit <n>", "max results per page (1-100, default 50)", (v) => parseInt(v, 10), 50).option("--cursor <token>", "pagination cursor returned by a prior response").action(async (raw, opts) => {
5444
+ return new Command22("stars").description("list packages a user has starred").argument("<username>", "kordis username").option("--limit <n>", "max results per page (1-100, default 50)", (v) => parseInt(v, 10), 50).option("--cursor <token>", "pagination cursor returned by a prior response").action(async (raw, opts) => {
5231
5445
  const username = parseUsername(raw);
5232
5446
  const bundle = await useClient();
5233
5447
  const res = await bundle.api.listUserStars(username, {
@@ -5259,9 +5473,9 @@ function starsCommand() {
5259
5473
  }
5260
5474
 
5261
5475
  // src/commands/follow.ts
5262
- import { Command as Command21 } from "commander";
5476
+ import { Command as Command23 } from "commander";
5263
5477
  function followCommand() {
5264
- return new Command21("follow").description("follow a kordis user").argument("<username>", "kordis username").action(async (raw) => {
5478
+ return new Command23("follow").description("follow a kordis user").argument("<username>", "kordis username").action(async (raw) => {
5265
5479
  const username = parseUsername(raw);
5266
5480
  const bundle = await useClient();
5267
5481
  requireAuth(bundle);
@@ -5276,7 +5490,7 @@ function followCommand() {
5276
5490
  });
5277
5491
  }
5278
5492
  function unfollowCommand() {
5279
- return new Command21("unfollow").description("stop following a kordis user").argument("<username>", "kordis username").action(async (raw) => {
5493
+ return new Command23("unfollow").description("stop following a kordis user").argument("<username>", "kordis username").action(async (raw) => {
5280
5494
  const username = parseUsername(raw);
5281
5495
  const bundle = await useClient();
5282
5496
  requireAuth(bundle);
@@ -5292,9 +5506,9 @@ function unfollowCommand() {
5292
5506
  }
5293
5507
 
5294
5508
  // src/commands/followers.ts
5295
- import { Command as Command22 } from "commander";
5509
+ import { Command as Command24 } from "commander";
5296
5510
  function makeListing(name) {
5297
- return new Command22(name).description(
5511
+ return new Command24(name).description(
5298
5512
  name === "followers" ? "list users following <username>" : "list users that <username> follows"
5299
5513
  ).argument("<username>", "kordis username").option("--limit <n>", "max results per page (1-100, default 50)", (v) => parseInt(v, 10), 50).option("--cursor <token>", "pagination cursor returned by a prior response").action(async (raw, opts) => {
5300
5514
  const username = parseUsername(raw);
@@ -5339,7 +5553,7 @@ function followingCommand() {
5339
5553
 
5340
5554
  // src/index.ts
5341
5555
  function buildProgram() {
5342
- const program = new Command23("kordis").version(VERSION).description("Kordis \u2014 registry for agentic-coding packages").option("--json", "emit JSON output to stdout (default: human)", false).hook("preAction", (thisCommand) => {
5556
+ const program = new Command25("kordis").version(VERSION).description("Kordis \u2014 registry for agentic-coding packages").option("--json", "emit JSON output to stdout (default: human)", false).hook("preAction", (thisCommand) => {
5343
5557
  const opts = thisCommand.optsWithGlobals();
5344
5558
  if (opts.json) setJsonMode(true);
5345
5559
  }).addHelpText("after", `
@@ -5372,6 +5586,8 @@ Environment variables (see README.md "Privacy & environment variables"):
5372
5586
  program.addCommand(followersCommand());
5373
5587
  program.addCommand(followingCommand());
5374
5588
  program.addCommand(deleteCommand());
5589
+ program.addCommand(yankCommand());
5590
+ program.addCommand(unyankCommand());
5375
5591
  program.addCommand(listCommand());
5376
5592
  program.addCommand(versionsCommand());
5377
5593
  program.addCommand(outdatedCommand());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rayul/kordis",
3
- "version": "0.9.3",
3
+ "version": "0.10.0",
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",