@christang/keel 5.73.1 → 5.75.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.
package/README.md CHANGED
@@ -46,22 +46,35 @@ Node.js `>=20.19.0` (the bundled OpenSpec CLI needs it).
46
46
 
47
47
  ## Install
48
48
 
49
- Two pieces: the `keel` CLI and the `keel` plugin.
50
-
51
- **CLI** — one command (also installs the bundled OpenSpec CLI):
49
+ **Claude Code** — the plugin is the whole install. It is the published `@christang/keel`
50
+ package, so it carries the `keel` CLI and the bundled OpenSpec along with the skills and hooks,
51
+ and the agent's `keel` commands run the copy the plugin brought:
52
52
 
53
53
  ```bash
54
- npm install -g @christang/keel
55
- keel --version
54
+ claude plugin marketplace add TanglmChris/keel
55
+ claude plugin install keel@keel-marketplace
56
56
  ```
57
57
 
58
- **Plugin** — the execution skills and runtime hooks:
58
+ Updates arrive by themselves once a project is set up: `keel --init --target claude` (and
59
+ `keel --install`) declares auto-update for `keel-marketplace` in the project's
60
+ `.claude/settings.json`, which Claude reads before its own default of off. A new release is fetched
61
+ in the background after a session's first message; `/reload-plugins` applies it in the running
62
+ session, and otherwise it applies at the next start. To opt out, set that entry's `autoUpdate` to
63
+ `false`; Keel keeps a value the project states, and `keel --doctor` reports which one is declared.
64
+
65
+ **Codex, and your own terminal** — install the CLI as well (it also installs the bundled
66
+ OpenSpec CLI):
59
67
 
60
68
  ```bash
61
- claude plugin install keel@<marketplace> # Claude Code
69
+ npm install -g @christang/keel
70
+ keel --version
62
71
  codex plugin add keel@<marketplace> # Codex
63
72
  ```
64
73
 
74
+ A global `keel` comes first on PATH, ahead of the plugin's copy, so on Claude Code the agent runs
75
+ it instead. Keep it at the plugin's version or remove it (`npm rm -g @christang/keel`); the
76
+ session-start line names it whenever the two disagree.
77
+
65
78
  <details>
66
79
  <summary>Install the latest unreleased build from GitHub</summary>
67
80
 
package/README.zh-CN.md CHANGED
@@ -38,22 +38,30 @@ Node.js `>=20.19.0`(内置的 OpenSpec CLI 需要)。
38
38
 
39
39
  ## 安装
40
40
 
41
- 两部分:`keel` CLI 和 `keel` 插件。
42
-
43
- **CLI** —— 一条命令(同时装上捆绑的 OpenSpec CLI):
41
+ **Claude Code** —— 装插件就够了。插件本身就是发布的 `@christang/keel` 包,技能和 hook 之外还带着
42
+ `keel` CLI 和捆绑的 OpenSpec;agent 运行的 `keel` 就是插件带来的这一份:
44
43
 
45
44
  ```bash
46
- npm install -g @christang/keel
47
- keel --version
45
+ claude plugin marketplace add TanglmChris/keel
46
+ claude plugin install keel@keel-marketplace
48
47
  ```
49
48
 
50
- **插件** —— 执行技能和运行时 hook:
49
+ 项目初始化后,更新会自己到来:`keel --init --target claude`(以及 `keel --install`)会在项目的
50
+ `.claude/settings.json` 里为 `keel-marketplace` 声明自动更新,Claude 先读这个声明,而不是它默认的"关闭"。
51
+ 新版本会在会话发出第一条消息后在后台下载;执行 `/reload-plugins` 即在当前会话生效,否则下次启动时生效。
52
+ 不想自动更新,就把那一项的 `autoUpdate` 设为 `false`;项目写明的值 Keel 会保留,`keel --doctor` 会报告当前声明的是哪一个。
53
+
54
+ **Codex,以及你自己的终端** —— 另外装一份 CLI(同时装上捆绑的 OpenSpec CLI):
51
55
 
52
56
  ```bash
53
- claude plugin install keel@<marketplace> # Claude Code
57
+ npm install -g @christang/keel
58
+ keel --version
54
59
  codex plugin add keel@<marketplace> # Codex
55
60
  ```
56
61
 
62
+ 全局装的 `keel` 在 PATH 里排在插件那份前面,所以在 Claude Code 里 agent 会用它。要么让它和插件同版本,
63
+ 要么卸掉(`npm rm -g @christang/keel`);两者版本不一致时,会话启动那一行会指出来。
64
+
57
65
  > 捆绑的 OpenSpec 依赖在安装时会打印一行 opt-in 的 shell 补全提示。如果你的 npm 拦截安装脚本,
58
66
  > 这行提示会被跳过,它纯属装饰,keel 照常工作。
59
67
 
@@ -1,4 +1,4 @@
1
- <!-- keel:start version=5.73.1 -->
1
+ <!-- keel:start version=5.75.0 -->
2
2
  ## Keel Bootstrap
3
3
 
4
4
  - Start every session with `keel context`; OpenSpec artifacts and Git are the only durable authority — never native memory, goals, or transcripts.
package/bin/keel ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ // The bare `keel` a Claude plugin's `bin/` puts on the agent's PATH (#164).
5
+ // On Claude the plugin is this published package, and the host adds `bin/`
6
+ // to PATH by file name, so `keel.js` alone would answer to `keel.js`. npm's
7
+ // own `bin` map keeps pointing at `keel.js`; this file only gives the plugin
8
+ // the same name.
9
+ require("./keel.js");
package/bin/keel.js CHANGED
@@ -755,6 +755,46 @@ function openspecReportedVersion(command) {
755
755
  return match ? match[0] : null;
756
756
  }
757
757
 
758
+ // What the project declares about Keel plugin auto-update (#164). Claude reads
759
+ // it first from `autoUpdate` on the marketplace's `extraKnownMarketplaces`
760
+ // entry, and `keel --install --target claude` writes it. The host reports no
761
+ // auto-update state Keel could read, so this is the declaration only; whether
762
+ // updates actually arrive is the host's to show.
763
+ function pluginAutoUpdateDeclaration(repo) {
764
+ const settingsPath = path.join(repo, ".claude", "settings.json");
765
+ const observation =
766
+ "Keel reads the declaration; the host does not expose whether updates run";
767
+ let entry;
768
+ try {
769
+ const settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
770
+ entry = ((settings && settings.extraKnownMarketplaces) || {})[
771
+ "keel-marketplace"
772
+ ];
773
+ } catch {
774
+ entry = undefined;
775
+ }
776
+ if (entry && entry.autoUpdate === true) {
777
+ return [
778
+ "ok",
779
+ `.claude/settings.json declares keel-marketplace autoUpdate: true; ${observation}`,
780
+ ];
781
+ }
782
+ if (entry && entry.autoUpdate === false) {
783
+ return [
784
+ "manual",
785
+ ".claude/settings.json declares keel-marketplace autoUpdate: false, "
786
+ + "which is the project's choice; updates arrive only through "
787
+ + "`claude plugin update`",
788
+ ];
789
+ }
790
+ return [
791
+ "manual",
792
+ "not declared, so Claude leaves auto-update off for this marketplace; "
793
+ + "run keel --install --target claude, or enable it under /plugin → "
794
+ + "Marketplaces",
795
+ ];
796
+ }
797
+
758
798
  // The root is the repository under diagnosis, never PACKAGE_ROOT. Rooting this
759
799
  // at Keel's own install location made the line a statement about a repository
760
800
  // the reader was never shown: a consumer pinning 9.9.9 was told the version
@@ -769,21 +809,28 @@ function openspecReportedVersion(command) {
769
809
  // project that does not depend on OpenSpec directly. A lockfile that exists and
770
810
  // cannot be parsed is a read failure. Collapsing them would either warn at
771
811
  // everyone or hide a real failure.
812
+ //
813
+ // The lockfile is the one npm installs from: `npm-shrinkwrap.json` when it
814
+ // exists, else `package-lock.json`. A package that publishes itself — Keel
815
+ // does since #164 — carries the shrinkwrap, because npm never publishes a
816
+ // `package-lock.json`.
772
817
  function declaredOpenSpecVersion(repo) {
773
- const lockPath = path.join(repo, "package-lock.json");
774
- if (!fs.existsSync(lockPath)) return { state: "none", version: null };
818
+ const lockName = ["npm-shrinkwrap.json", "package-lock.json"].find((name) =>
819
+ fs.existsSync(path.join(repo, name))
820
+ );
821
+ if (!lockName) return { state: "none", version: null, lockName: null };
775
822
  let lock;
776
823
  try {
777
- lock = JSON.parse(fs.readFileSync(lockPath, "utf8"));
824
+ lock = JSON.parse(fs.readFileSync(path.join(repo, lockName), "utf8"));
778
825
  } catch {
779
- return { state: "unreadable", version: null };
826
+ return { state: "unreadable", version: null, lockName };
780
827
  }
781
828
  for (const [name, entry] of Object.entries(lock.packages || {})) {
782
829
  if (name.endsWith("@fission-ai/openspec") && entry && entry.version) {
783
- return { state: "declared", version: entry.version };
830
+ return { state: "declared", version: entry.version, lockName };
784
831
  }
785
832
  }
786
- return { state: "none", version: null };
833
+ return { state: "none", version: null, lockName };
787
834
  }
788
835
 
789
836
  // "Not installed" and "installed and Keel cannot reach it" need different
@@ -1565,6 +1612,10 @@ function printTargetSurface(repo, target) {
1565
1612
  : "claude plugin install keel@<marketplace>"
1566
1613
  } and verify in a fresh session`
1567
1614
  );
1615
+ if (target === "claude") {
1616
+ const [status, detail] = pluginAutoUpdateDeclaration(repo);
1617
+ printDoctorLine("plugin auto-update", status, detail);
1618
+ }
1568
1619
  }
1569
1620
 
1570
1621
  const openspecSkillRoot = openspecSkillRootForTarget(target);
@@ -1686,7 +1737,7 @@ function runDoctor(options) {
1686
1737
  const declaredText = {
1687
1738
  declared: `repo pins ${declared.version}`,
1688
1739
  none: "repo declares no OpenSpec version",
1689
- unreadable: "repo package-lock.json unreadable",
1740
+ unreadable: `repo ${declared.lockName} unreadable`,
1690
1741
  }[declared.state];
1691
1742
  const versions = `${resolvedVersion || "version unreadable"}, ${declaredText}`;
1692
1743
  printDoctorLine(