@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 +20 -7
- package/README.zh-CN.md +15 -7
- package/assets/bootstrap/AGENTS.md +1 -1
- package/bin/keel +9 -0
- package/bin/keel.js +58 -7
- package/npm-shrinkwrap.json +1137 -0
- package/package.json +2 -1
- package/plugins/keel/.claude-plugin/plugin.json +1 -1
- package/plugins/keel/.codex-plugin/plugin.json +1 -1
- package/plugins/keel/scripts/session-start.js +68 -9
- package/scripts/bump_version.js +26 -7
- package/scripts/install_to_repo.py +107 -0
- package/scripts/validate_plugin.py +656 -22
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
55
|
-
keel
|
|
54
|
+
claude plugin marketplace add TanglmChris/keel
|
|
55
|
+
claude plugin install keel@keel-marketplace
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
|
|
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
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
**CLI** —— 一条命令(同时装上捆绑的 OpenSpec CLI):
|
|
41
|
+
**Claude Code** —— 装插件就够了。插件本身就是发布的 `@christang/keel` 包,技能和 hook 之外还带着
|
|
42
|
+
`keel` CLI 和捆绑的 OpenSpec;agent 运行的 `keel` 就是插件带来的这一份:
|
|
44
43
|
|
|
45
44
|
```bash
|
|
46
|
-
|
|
47
|
-
keel
|
|
45
|
+
claude plugin marketplace add TanglmChris/keel
|
|
46
|
+
claude plugin install keel@keel-marketplace
|
|
48
47
|
```
|
|
49
48
|
|
|
50
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
774
|
-
|
|
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(
|
|
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:
|
|
1740
|
+
unreadable: `repo ${declared.lockName} unreadable`,
|
|
1690
1741
|
}[declared.state];
|
|
1691
1742
|
const versions = `${resolvedVersion || "version unreadable"}, ${declaredText}`;
|
|
1692
1743
|
printDoctorLine(
|