@rayul/kordis 0.13.0 → 0.13.1

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/README.md CHANGED
@@ -2,14 +2,18 @@
2
2
 
3
3
  > The CLI binary is **`kordis`** — only the npm package name is scoped.
4
4
 
5
- Public registry CLI for agentic-coding packages — skills, project rules, MCP servers, hooks, custom agents.
5
+ Your agent hits something it doesn't know — an unfamiliar format, a niche domain, a technique it's unsure about. Kordis is the registry it checks before improvising.
6
+
7
+ Run `setup` once and the guide is injected into your agent's global instructions, so from the next session your agent reaches for the registry on its own. It reads what it needs at the moment of need, without writing anything to disk, and installs only when you ask.
8
+
9
+ The registry carries six package types: **skills**, **project rules**, **MCP servers**, **hooks**, **custom agents**, and **judgment patterns** — reusable decision logic (_how_ an agent should decide) rather than files to install.
6
10
 
7
11
  ## Quick start
8
12
 
9
13
  ```bash
10
14
  npx @rayul/kordis@latest setup # one-time: register kordis with Claude Code / Codex / Gemini CLI
11
- npx @rayul/kordis@latest search sql # find a skill
12
- npx @rayul/kordis@latest read rayul/sql-reviewer # JIT-load into your agent context
15
+ npx @rayul/kordis@latest search sql # find a package
16
+ npx @rayul/kordis@latest read spaceplayentry/db-sql-code-review # JIT-load into your agent context
13
17
  ```
14
18
 
15
19
  Or install globally:
@@ -35,18 +39,19 @@ Then re-run the install — no `sudo` needed.
35
39
 
36
40
  ## Commands
37
41
 
38
- | command | purpose |
39
- | ------------------------------------- | ----------------------------------------------------------------------------- |
40
- | `kordis search <query>` | search the registry |
41
- | `kordis info <slug>` | inspect a package |
42
- | `kordis read <slug>` | JIT-fetch body into agent context (skill / project-rules / custom-agent only) |
43
- | `kordis pull <slug>` | install into the current project (mcp-server / hook) |
44
- | `kordis push <file>` | publish a new package or version |
45
- | `kordis pull <slug>@<version>` | pin a specific version / range (transitive deps resolved) |
46
- | `kordis yank <slug>@<version>` | unpublish a version (panic-mode; `unyank` restores) |
47
- | `kordis login` / `logout` / `whoami` | identity |
48
- | `kordis profile` / `follow` / `stars` | social layer |
49
- | `kordis setup` | inject usage guide into Claude Code / Codex / Gemini CLI globals |
42
+ | command | purpose |
43
+ | ------------------------------------- | -------------------------------------------------------------------------------------- |
44
+ | `kordis search <query>` | search the registry |
45
+ | `kordis info <slug>` | inspect a package |
46
+ | `kordis read <slug>` | JIT-fetch body into agent context — no disk write (all types except mcp-server / hook) |
47
+ | `kordis adapt <slug>` | fit a judgment pattern to this project's harness (prints a brief; writes nothing) |
48
+ | `kordis pull <slug>` | install into the current project (the only path for mcp-server / hook) |
49
+ | `kordis push <file>` | publish a new package or version |
50
+ | `kordis pull <slug>@<version>` | pin a specific version / range (transitive deps resolved) |
51
+ | `kordis yank <slug>@<version>` | unpublish a version (panic-mode; `unyank` restores) |
52
+ | `kordis login` / `logout` / `whoami` | identity |
53
+ | `kordis profile` / `follow` / `stars` | social layer |
54
+ | `kordis setup` | inject usage guide into Claude Code / Codex / Gemini CLI globals |
50
55
 
51
56
  Pass `--json` on any command to get machine-readable output.
52
57
 
package/assets/KORDIS.md CHANGED
@@ -48,6 +48,8 @@ Verbs: `kordis search`, `info`, `read`, `adapt`, `pull`, `push`, `comment`,
48
48
  programmatically. Star etiquette: only after the user explicitly confirms in
49
49
  the conversation that a package helped may you OFFER to star it — run
50
50
  `kordis star` only on their yes; never unprompted, never auto-login for it.
51
+ Never for a package the user authored: the server refuses that with
52
+ `not_authorized`, since stars are readers' endorsements and feed ranking.
51
53
 
52
54
  Detail lives in five on-demand sub-skills installed at `~/.claude/skills/`,
53
55
  `~/.codex/skills/`, and `~/.gemini/skills/`:
@@ -2,12 +2,13 @@
2
2
  name: kordis-recommend
3
3
  description: Use whenever the user wants ideas — phrases like "any new stuff?", "recommend", "추천", "what should I try", "anything fresh?", "discover", "show me good skills", "what's popular for <stack>". Surfaces a personalized menu of Kordis packages the user may want to install or peek at. Read eagerly when the user signals discovery intent without a specific slug in mind.
4
4
  type: skill
5
- allowed-tools: ["Bash(kordis:*)"]
5
+ allowed-tools: ['Bash(kordis:*)']
6
6
  ---
7
7
 
8
8
  # Kordis: Personalized Recommendations
9
9
 
10
10
  ## Goal
11
+
11
12
  Surface a ranked, agent-friendly menu of packages the user may want next.
12
13
  Algorithm is hybrid: content-based (your local stack) + item-CF (other
13
14
  users with similar installs) + popularity decay (stars + recency).
@@ -28,14 +29,18 @@ the user names a slug (use `kordis info` / `kordis read`).
28
29
 
29
30
  ## Command
30
31
 
31
- ### `kordis recommend [--limit N] [--verbose] [--no-context] [--json]`
32
+ ### `kordis recommend [--limit N] [--no-context] [--json]`
32
33
 
33
- | flag | meaning |
34
- | -------------- | ---------------------------------------------------------------- |
35
- | `--limit <n>` | max items (default 10, max 50) |
36
- | `--verbose` | include `score_breakdown` per item (content / cf / popularity) |
34
+ | flag | meaning |
35
+ | -------------- | ------------------------------------------------------------------- |
36
+ | `--limit <n>` | max items (default 10, max 50) |
37
37
  | `--no-context` | skip the `X-Kordis-Context` header for this call (one-shot opt-out) |
38
- | `--json` | global flag — JSON envelope, no human render |
38
+ | `--json` | global flag — JSON envelope, no human render |
39
+
40
+ `--verbose` still parses but does nothing. It used to print each item's
41
+ score components; the ranking blend is server-private, so there is no
42
+ per-item score to surface. Rank order is the signal — do not tell the user
43
+ a package's numeric score, because you no longer receive one.
39
44
 
40
45
  Anonymous: works without login. Personalized signal rides on an opt-in
41
46
  `X-Kordis-Context` HTTP header (installed agent tools, top 3 CWD file
@@ -56,6 +61,7 @@ The user is asked ONCE. Three paths persist the answer to
56
61
  Use this when the user has confirmed consent in chat — see Step 2.5.
57
62
 
58
63
  Override at runtime (does not change the persisted answer):
64
+
59
65
  - `KORDIS_NO_CONTEXT=1` env — no header for the entire shell session
60
66
  - `--no-context` flag — no header for this single call
61
67
 
@@ -85,27 +91,31 @@ Surface this AND ask the user before flipping the flag (Step 2.5).
85
91
 
86
92
  `confidence` interpretation when surfacing to the user:
87
93
 
88
- | value | how to surface |
89
- | -------- | ------------------------------------------------------------------------- |
90
- | `high` | "You should try X" — both content match and co-install signal align |
91
- | `medium` | "You might like X" — one signal strong |
92
- | `low` | "Worth a look — popular right now" — cold-start / no personalization |
94
+ | value | how to surface |
95
+ | -------- | -------------------------------------------------------------------- |
96
+ | `high` | "You should try X" — both content match and co-install signal align |
97
+ | `medium` | "You might like X" — one signal strong |
98
+ | `low` | "Worth a look — popular right now" — cold-start / no personalization |
93
99
 
94
100
  ## Steps
95
101
 
96
102
  ### 1. Fetch menu
103
+
97
104
  `kordis recommend --limit 10 --json`. (Add `--no-context` ONLY if the user
98
105
  explicitly asked you not to share their stack.)
99
106
 
100
107
  ### 2. Triage
108
+
101
109
  Filter mentally by `confidence`. If everything is `low` and `personalized`
102
110
  is `false`, follow Step 2.5 to offer the user a one-click consent path.
103
111
 
104
- If the response carries `capped_by_author > 0`, ALSO surface a one-line
105
- note to the user — e.g. "N more candidates from the same publisher were
106
- skipped by the diversity cap" — so they understand why the list is
107
- shorter than the requested `--limit`. Without this hint users frequently
108
- mistake a short menu for a sparse or broken registry.
112
+ If the menu is shorter than the requested `--limit`, that now means the
113
+ registry genuinely has fewer candidates — compare against `total_candidates`
114
+ and say so plainly rather than implying the tool is broken. The per-author
115
+ diversity cap no longer shortens the menu: it reorders, pushing a publisher's
116
+ surplus down instead of discarding it, so `capped_by_author` is always `0`.
117
+ (It used to drop them, which capped every menu at 3 items regardless of
118
+ `--limit`.)
109
119
 
110
120
  ### 2.5. Offer personalization consent (when `personalized: false`)
111
121
 
@@ -132,13 +142,16 @@ optionally via `kordis setup --personalization off` so the question
132
142
  isn't asked again.
133
143
 
134
144
  ### 3. Present
145
+
135
146
  Surface 3-5 picks max. For each, show: full_slug, one-line `why_recommended`,
136
147
  confidence wording per the table above, and the next-step
137
148
  `install_command` / `jit_read_command`. Do NOT dump the whole menu — agent
138
149
  should curate.
139
150
 
140
151
  ### 4. Act
152
+
141
153
  If the user picks one:
154
+
142
155
  - "install it" → run `install_command` from the menu (already a complete
143
156
  shell line)
144
157
  - "peek first" → run `jit_read_command`, summarize, then offer install
@@ -70,8 +70,9 @@ array.
70
70
  ### 3. Triage the results
71
71
 
72
72
  - Prefer `review_status: passed`, higher `stars`, then `tier: A`.
73
- - For trending results, the `trending_score` field is populated — surface it
74
- when comparing.
73
+ - For trending results, **rank order is the signal** — the server returns no
74
+ numeric score, so never quote or compare one. Say "3rd on the 7-day
75
+ trending board", not a number.
75
76
  - IMPORTANT: if results are 0, RELAX one filter at a time before giving up.
76
77
  **Drop `--tool` first** — many published packages have an empty
77
78
  `tools_compatible` field, so `--tool <name>` undercounts hard. Then drop
@@ -125,7 +126,7 @@ User: "any Cursor-compatible package for code review?"
125
126
  <example>
126
127
  User: "what's trending on Kordis this week?"
127
128
  - kordis search --trending --window 7d --json
128
- - show top 5–10 with their `trending_score`; do NOT fetch bodies unless asked
129
+ - show top 5–10 in the order returned; do NOT fetch bodies unless asked
129
130
  </example>
130
131
 
131
132
  <example>
@@ -66,13 +66,30 @@ response shape is the same whether the call newly starred or no-op'd; if the
66
66
  user wants to verify a NEW star landed, compare `stars` from `kordis info
67
67
  <full_slug> --json` before and after.
68
68
 
69
+ **You cannot star a package the user authored.** The server refuses it with
70
+ `not_authorized` (exit 11) — a star is a reader's endorsement and feeds
71
+ ranking, so authors starring their own work would just be voting for
72
+ themselves. This is a rule, not a glitch: do not retry, do not try another
73
+ route, and do not present it as a failure the user should work around. Say
74
+ what it is in one line and move on.
75
+
76
+ <example>
77
+ User: "star my rss-reader package"
78
+ - kordis star rayul/rss-reader --json -> exit 11, not_authorized
79
+ - "본인이 올린 패키지엔 별점을 누를 수 없어요 — 별점은 읽은 사람의 추천이라
80
+ 순위에 들어가서요. 슬러그를 공유하시면 다른 분이 누를 수 있습니다."
81
+ - do NOT retry, do NOT suggest a second account
82
+ </example>
83
+
69
84
  ### `kordis unstar <full_slug> [--json]`
70
85
 
71
86
  Remove your star. Idempotent — silent no-op when not previously starred.
72
87
  Auth required.
73
88
 
74
- Stars feed `--sort stars` ordering on `kordis search` and contribute to the
75
- `trending_score`.
89
+ Stars feed `--sort stars` ordering on `kordis search` and the trending board.
90
+ Only stars from someone **other than the author** count toward ranking. The
91
+ numeric trending score is server-private and is no longer returned — rank
92
+ order is the signal, so never quote a package's score to the user.
76
93
 
77
94
  ## Steps
78
95
 
@@ -130,6 +147,8 @@ User: "report that the v0.3.0 release of foo/bar is broken on macOS"
130
147
  User: "star foo/bar"
131
148
  - kordis star foo/bar --json
132
149
  - show new star count
150
+ - if it returns `not_authorized` (exit 11), the user authored it — say so
151
+ in one line, do not retry
133
152
  </example>
134
153
 
135
154
  <example>
@@ -255,6 +255,12 @@ starring needs a one-click browser login; if they decline, drop it. NEVER run
255
255
  initiative. Caps: at most ONE offer per session, never twice for the same
256
256
  package, and a "no" suppresses all further offers that session.
257
257
 
258
+ Do NOT offer for a package the USER themselves authored — the server refuses
259
+ it with `not_authorized` (exit 11), because a star is a reader's endorsement
260
+ and feeds ranking. Check the owner segment of the slug against `kordis
261
+ whoami` before offering. If you offer anyway and it fails, say so once and
262
+ drop it; do not retry.
263
+
258
264
  <example>
259
265
  User: "thanks, that fixed the parsing!"
260
266
  - reply ends: "following alice/pdf-extract@1.2.0 — want me to star it on Kordis?"
package/dist/index.js CHANGED
@@ -305,7 +305,13 @@ var init_package = __esm({
305
305
  }),
306
306
  stars: z6.number().int().nonnegative(),
307
307
  downloads: z6.number().int().nonnegative(),
308
- trending_score: z6.number().nullable(),
308
+ // `trending_score` was removed 2026-07-29 — same information class as the
309
+ // `score_breakdown` dropped from /recommend the same day. Publishing the raw
310
+ // score let a caller solve for the ranker's constants (star weight 5×, the
311
+ // verified ×1.15 multiplier, the decay τ) by starring/pulling once and
312
+ // reading the delta — i.e. it handed out the exact gaming lever. Trending
313
+ // ORDERING is unchanged; only the exposed number is gone. Callers that want
314
+ // "is this trending" read position in `GET /api/packages/trending`.
309
315
  tools_compatible: z6.array(ToolSlugSchema),
310
316
  tags: z6.array(TagSchema),
311
317
  agent_skills_compliant: z6.boolean().optional(),
@@ -975,7 +981,7 @@ var init_context = __esm({
975
981
 
976
982
  // ../../packages/contracts/src/recommend.ts
977
983
  import { z as z11 } from "zod";
978
- var RecommendQuerySchema, RecommendConfidenceSchema, RecommendScoreBreakdownSchema, RecommendMenuItemSchema, RecommendResponseSchema;
984
+ var RecommendQuerySchema, RecommendConfidenceSchema, RecommendMenuItemSchema, RecommendResponseSchema;
979
985
  var init_recommend = __esm({
980
986
  "../../packages/contracts/src/recommend.ts"() {
981
987
  "use strict";
@@ -984,7 +990,10 @@ var init_recommend = __esm({
984
990
  RecommendQuerySchema = z11.object({
985
991
  limit: z11.coerce.number().int().positive().max(50).default(10),
986
992
  /**
987
- * Include score_breakdown in the response. CLI sets when --verbose.
993
+ * Inert since 2026-07-29 — this used to add `score_breakdown` to each
994
+ * item, which leaked the ranking blend. Still ACCEPTED (not removed)
995
+ * because shipped CLIs ≤0.13.0 send `?verbose=true` and a stricter
996
+ * schema would turn their recommend calls into 400s.
988
997
  *
989
998
  * NOT `z.coerce.boolean()` — that wraps `Boolean()` which treats the
990
999
  * literal string `"false"` as truthy (non-empty string). Use explicit
@@ -994,12 +1003,6 @@ var init_recommend = __esm({
994
1003
  verbose: z11.union([z11.boolean(), z11.enum(["true", "false"])]).transform((v) => v === true || v === "true").optional().default(false)
995
1004
  });
996
1005
  RecommendConfidenceSchema = z11.enum(["low", "medium", "high"]);
997
- RecommendScoreBreakdownSchema = z11.object({
998
- content: z11.number(),
999
- cf: z11.number(),
1000
- popularity: z11.number(),
1001
- final: z11.number()
1002
- });
1003
1006
  RecommendMenuItemSchema = z11.object({
1004
1007
  full_slug: FullSlugSchema,
1005
1008
  title: z11.string(),
@@ -1019,9 +1022,7 @@ var init_recommend = __esm({
1019
1022
  * "Trending in your tool's ecosystem this week".
1020
1023
  */
1021
1024
  why_recommended: z11.string(),
1022
- confidence: RecommendConfidenceSchema,
1023
- /** Present only when the request specified `verbose=true`. */
1024
- score_breakdown: RecommendScoreBreakdownSchema.optional()
1025
+ confidence: RecommendConfidenceSchema
1025
1026
  });
1026
1027
  RecommendResponseSchema = z11.object({
1027
1028
  items: z11.array(RecommendMenuItemSchema),
@@ -1033,11 +1034,14 @@ var init_recommend = __esm({
1033
1034
  */
1034
1035
  personalized: z11.boolean(),
1035
1036
  /**
1036
- * 0.5.0 — number of additional candidates the server had before applying
1037
- * the per-author cap. When `capped_by_author > 0`, the agent can explain
1038
- * "menu is shorter than --limit because diversity cap fired" instead of
1039
- * the user inferring the registry is sparse. Absent on responses from
1040
- * pre-0.5.0 servers.
1037
+ * 0.5.0 — how many candidates the per-author cap DISCARDED.
1038
+ *
1039
+ * Always `0` since 2026-07-29: the cap now demotes a publisher's surplus to
1040
+ * a tail bucket instead of dropping it, so the menu is never shortened by
1041
+ * the cap. Kept in the response (rather than removed) because shipped CLIs
1042
+ * ≤0.13.0 print "N candidates were skipped by the diversity cap" whenever
1043
+ * this is `> 0` — emitting a nonzero value would make them state something
1044
+ * false. Absent on responses from pre-0.5.0 servers.
1041
1045
  */
1042
1046
  capped_by_author: z11.number().int().nonnegative().optional(),
1043
1047
  /**
@@ -2532,7 +2536,7 @@ var TOOL_ALIASES = {
2532
2536
  function searchCommand() {
2533
2537
  return new Command3("search").description("search the Kordis registry").argument("[query...]", "keywords to match against title/description").option(
2534
2538
  "--type <type>",
2535
- "filter by harness type (skill|mcp-server|project-rules|custom-agent|hook)"
2539
+ "filter by harness type (skill|mcp-server|project-rules|custom-agent|hook|judgment-pattern)"
2536
2540
  ).option(
2537
2541
  "--tool <tool>",
2538
2542
  `filter by compatible tool \u2014 one of: ${TOOL_SLUGS.join("|")} (aliases: ${Object.entries(
@@ -2618,9 +2622,6 @@ function searchCommand() {
2618
2622
  `\u21E3 ${p.downloads}`,
2619
2623
  `${stripControl(p.author.username)}${p.author.is_verified ? " \u2713" : ""}`
2620
2624
  ];
2621
- if (typeof p.trending_score === "number") {
2622
- trail.push(`score ${p.trending_score.toFixed(2)}`);
2623
- }
2624
2625
  printHuman(c.gray(` ${trail.join(" ")}`));
2625
2626
  printHuman("");
2626
2627
  }
@@ -6432,7 +6433,7 @@ function diffCommand() {
6432
6433
  import { Command as Command21 } from "commander";
6433
6434
  import prompts4 from "prompts";
6434
6435
  function recommendCommand() {
6435
- return new Command21("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) => {
6436
+ return new Command21("recommend").description("show personalized package recommendations").option("--limit <n>", "max items to return (1-50)", (v) => parseInt(v, 10), 10).option("--verbose", "deprecated \u2014 accepted and ignored", false).option("--no-context", "skip the X-Kordis-Context header for this call").action(async (opts) => {
6436
6437
  const cfg = await loadCliConfig();
6437
6438
  if (cfg.personalization === "undecided" && process.stdin.isTTY && !isJsonMode() && opts.context !== false) {
6438
6439
  const { enable } = await prompts4({
@@ -6457,8 +6458,7 @@ function recommendCommand() {
6457
6458
  const bundle = await useClient();
6458
6459
  const spin = spinner("Computing recommendations");
6459
6460
  const res = await bundle.api.recommend({
6460
- limit: opts.limit,
6461
- verbose: opts.verbose
6461
+ limit: opts.limit
6462
6462
  });
6463
6463
  spin.succeed();
6464
6464
  await writeCache(res.items, res.generated_at);
@@ -6493,7 +6493,7 @@ function recommendCommand() {
6493
6493
  `${c.bold(`${res.items.length} pick${res.items.length === 1 ? "" : "s"}`)} ${c.gray(`(${res.personalized ? "personalized" : "popularity-only"} \xB7 generated ${res.generated_at.slice(0, 19)}Z)`)}`
6494
6494
  );
6495
6495
  printHuman("");
6496
- res.items.forEach((it, idx) => renderItem(idx + 1, it, opts.verbose));
6496
+ res.items.forEach((it, idx) => renderItem(idx + 1, it));
6497
6497
  printHuman("");
6498
6498
  printHuman(
6499
6499
  c.gray(
@@ -6519,7 +6519,7 @@ function recommendCommand() {
6519
6519
  });
6520
6520
  });
6521
6521
  }
6522
- function renderItem(idx, it, verbose) {
6522
+ function renderItem(idx, it) {
6523
6523
  const confidenceBadge = it.confidence === "high" ? c.green("high") : it.confidence === "medium" ? c.cyan("medium") : c.gray("low");
6524
6524
  printHuman(
6525
6525
  `${c.bold(`${idx}. ${it.full_slug}`)} ${c.gray(`v${it.current_version}`)} ${c.cyan(`[${it.type}]`)} ${c.gray(`tier ${it.tier}`)} ${c.gray("\xB7")} ${confidenceBadge} confidence`
@@ -6530,14 +6530,6 @@ function renderItem(idx, it, verbose) {
6530
6530
  printHuman(c.gray(` \u2192 ${it.why_recommended}`));
6531
6531
  printHuman(c.gray(` install: ${c.cyan(it.install_command)}`));
6532
6532
  printHuman(c.gray(` read: ${c.cyan(it.jit_read_command)}`));
6533
- if (verbose && it.score_breakdown) {
6534
- const b = it.score_breakdown;
6535
- printHuman(
6536
- c.gray(
6537
- ` score: content=${b.content} cf=${b.cf} popularity=${b.popularity} final=${b.final}`
6538
- )
6539
- );
6540
- }
6541
6533
  printHuman("");
6542
6534
  }
6543
6535
 
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "@rayul/kordis",
3
- "version": "0.13.0",
3
+ "version": "0.13.1",
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",
7
+ "homepage": "https://kordis.store",
7
8
  "type": "module",
8
9
  "bin": {
9
10
  "kordis": "bin/kordis.js"