@ssheleg/sheleg-dev 0.10.3 → 0.10.5
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/CHANGELOG.md +45 -0
- package/README.md +7 -0
- package/SECURITY.md +27 -11
- package/bin/sheleg-dev.js +86 -0
- package/package.json +4 -4
- package/plugins/sheleg-dev/.claude-plugin/plugin.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,51 @@ All notable changes to this project are documented here.
|
|
|
4
4
|
Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
5
5
|
Versioning: [SemVer](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## v0.10.5 — the installers refuse the shadow they used to write
|
|
8
|
+
|
|
9
|
+
Both install channels — `npx @ssheleg/sheleg-dev` and `install.sh` — now consult the
|
|
10
|
+
target home's `~/.claude/plugins/installed_plugins.json` before writing anything under
|
|
11
|
+
`~/.claude/skills/`. If the sheleg-dev **plugin** is installed there, the install is
|
|
12
|
+
**refused with exit 3**: a plain copy beside a plugin shadows the plugin's skill of the
|
|
13
|
+
same name and serves the frozen version forever, and until this release neither channel
|
|
14
|
+
looked at all. Reproduced live in the family on 2026-08-29: a bare
|
|
15
|
+
`npx @ssheleg/telegram-dev` shipped three shadows while the plugin was enabled.
|
|
16
|
+
|
|
17
|
+
- `installed_plugins.json` is the signal, because it is the record of what is actually
|
|
18
|
+
installed. A check keyed on the `plugins/marketplaces/<name>` directory alone — the
|
|
19
|
+
shape other family members carried — is the fail-open class: a marketplace added from
|
|
20
|
+
a local `directory` source has no dir there, and plugin names differ from marketplace
|
|
21
|
+
names. The directory is kept only as a fallback signal.
|
|
22
|
+
- The refusal names its remedy with the **real spec read from the JSON**
|
|
23
|
+
(`claude plugin marketplace update sheleg-dev`, `claude plugin update <spec>`), plus
|
|
24
|
+
the family launcher line, and offers `--force` as the explicit override for running
|
|
25
|
+
two channels deliberately.
|
|
26
|
+
- An absent or corrupt `installed_plugins.json` reads as "no plugin": the check fails
|
|
27
|
+
open and never crashes an install — the fresh HOME is the common case.
|
|
28
|
+
- New suite `test/installer_test.js` (11 cases, both channels, throwaway HOMEs), wired
|
|
29
|
+
into `npm test` and CI. **Watched failing first**: run against the pre-refusal
|
|
30
|
+
installers it went red on 7 of 11 cases, each showing the shadow landing at exit 0.
|
|
31
|
+
- Both success paths now end by saying how the next version arrives — the update line —
|
|
32
|
+
and `install.sh` gained `--force`/usage parsing (unknown argument exits 2) to carry
|
|
33
|
+
the same contract as the npm channel.
|
|
34
|
+
- `.claude-plugin/marketplace.json` still said "Six integration skills" while seven
|
|
35
|
+
ship (the validator counts 7; README and SECURITY say seven). It now says seven and
|
|
36
|
+
names error tracking — the second hand-written count this repository has caught after
|
|
37
|
+
the fact, which is why the new suite derives the roster from the tree.
|
|
38
|
+
- `SECURITY.md`'s installer contract moved with the behaviour: the read surface now
|
|
39
|
+
includes the two read-only looks at the target home, and the I/O-surface grep line
|
|
40
|
+
count was re-measured (twelve, and it was already twelve before this change — the
|
|
41
|
+
"eleven" had rotted when a comment naming `rm -rf` arrived).
|
|
42
|
+
|
|
43
|
+
## v0.10.4 — the channel that sends the installs, on npm too
|
|
44
|
+
|
|
45
|
+
- The `skills.sh` badge and the canonical `homepage` reached GitHub in the previous cycle and stopped
|
|
46
|
+
there: npm serves the README and the metadata from the last **publish**, so the package
|
|
47
|
+
page still showed a badge-less README and a homepage pointing at GitHub.
|
|
48
|
+
This release carries both across.
|
|
49
|
+
- No behaviour changes. Cut because a change that lands on `main` and never publishes is a
|
|
50
|
+
change the package's own readers cannot see.
|
|
51
|
+
|
|
7
52
|
## v0.10.3 — the shared seam is explicit
|
|
8
53
|
|
|
9
54
|
Both shared validators now state `diverges: none`, completing the umbrella
|
package/README.md
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/@ssheleg/sheleg-dev)
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
[](https://skills.sshlg.me/skills/sheleg-dev/)
|
|
7
|
+
[](https://skills.sh/ssheleg/sheleg-dev)
|
|
7
8
|
|
|
8
9
|
**Wire the product seams where money, identity, measurement, errors and frontend performance meet production.**
|
|
9
10
|
|
|
@@ -117,6 +118,12 @@ a Lighthouse score.
|
|
|
117
118
|
npx @ssheleg/sheleg-dev
|
|
118
119
|
```
|
|
119
120
|
|
|
121
|
+
If the sheleg-dev **plugin** is already installed in that home, the npm installer
|
|
122
|
+
and `install.sh` refuse with exit 3 instead of writing plain copies — a plain
|
|
123
|
+
copy shadows the plugin's skill of the same name and serves the frozen version
|
|
124
|
+
forever. The refusal prints the plugin-channel update commands; `--force`
|
|
125
|
+
overrides it, deliberately.
|
|
126
|
+
|
|
120
127
|
**Any of 70+ agents:**
|
|
121
128
|
|
|
122
129
|
```bash
|
package/SECURITY.md
CHANGED
|
@@ -33,7 +33,7 @@ no socket; the greps below prove all three.
|
|
|
33
33
|
| `plugins/sheleg-dev/hooks/money-gate.js` | 1 | Runs on a tool call, when the plugin is enabled. Requires `path` and its own decision module — nothing else. Reads stdin, writes one JSON line, exits 0 on every path. |
|
|
34
34
|
| `plugins/sheleg-dev/hooks/lib/moneygate.js` | 1 | Pure. Payload and environment in, verdict out. No `require` at all, no filesystem, no clock. |
|
|
35
35
|
| `bin/sheleg-dev.js` — the npm installer | 1 | Runs only when you invoke it. Node built-ins only: `fs`, `path`, `os`. |
|
|
36
|
-
| `install.sh` — the shell installer | 1 | **Not in the tarball** — `files` ships `bin/` and `plugins/`, so this one reaches you only through a clone. Runs when you invoke it.
|
|
36
|
+
| `install.sh` — the shell installer | 1 | **Not in the tarball** — `files` ships `bin/` and `plugins/`, so this one reaches you only through a clone. Runs when you invoke it. Shell tools only: `cd`, `pwd`, `dirname`, `basename`, `mkdir`, `rm`, `cp`, `echo`, plus `sed` and `head` to read the plugin registry it refuses to shadow. It is the destructive channel: `rm -rf` per skill, then `cp -R`. |
|
|
37
37
|
| `plugins/sheleg-dev/skills/*/fixtures/*.json` — webhook payloads | 16 | Data. Placeholder ids only; no key, token, signing secret or real customer id. |
|
|
38
38
|
| `plugins/sheleg-dev/skills/*/fixtures/*.mjs` — the assertion packs and their reference implementations | 4 | Runs only when you invoke it. Reads the JSON beside it with `readFileSync` and nothing else: no `child_process`, no `fetch`, no write, no `process.env`. |
|
|
39
39
|
| `plugins/sheleg-dev/skills/*/fixtures/manifest.json` and its `README.md` | 4 | Text. The invariant-to-fixture-to-document map, which `test/validate.py` reads. |
|
|
@@ -61,24 +61,32 @@ nothing but the CLI you typed.
|
|
|
61
61
|
## What the installers read and write
|
|
62
62
|
|
|
63
63
|
Both do one thing: copy the seven skill directories out of the package into
|
|
64
|
-
`~/.claude/skills/<name
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
`~/.claude/skills/<name>` — unless the sheleg-dev **plugin** is already installed
|
|
65
|
+
in that home, in which case both refuse with exit 3 rather than write plain
|
|
66
|
+
copies that would shadow the plugin's skills and serve this frozen version
|
|
67
|
+
forever. `--force` overrides the refusal, deliberately.
|
|
68
|
+
|
|
69
|
+
- **Read:** files inside the package, plus two read-only looks at the target
|
|
70
|
+
home to decide the refusal above: `~/.claude/plugins/installed_plugins.json`
|
|
71
|
+
(the record of installed plugins; a missing or unparsable file reads as "no
|
|
72
|
+
plugin", so the check fails open and never crashes an install) and whether
|
|
73
|
+
`~/.claude/plugins/marketplaces/sheleg-dev` exists, kept only as a fallback
|
|
74
|
+
signal.
|
|
67
75
|
- **Write:** only `~/.claude/skills/` — seven directories, named for the seven skills.
|
|
68
76
|
Nothing else on the filesystem, nothing outside `$HOME`.
|
|
69
77
|
- **Network: none.** Neither file opens a socket or resolves a hostname.
|
|
70
78
|
`bin/sheleg-dev.js` requires `fs`, `path` and `os` and nothing else — no
|
|
71
79
|
`child_process`, no `fetch`, no `http` — so it spawns no process at all;
|
|
72
|
-
`install.sh` runs only the
|
|
73
|
-
whole surface of both files in
|
|
80
|
+
`install.sh` runs only the shell tools named above. The grep below prints the
|
|
81
|
+
whole surface of both files in twelve lines.
|
|
74
82
|
- **No telemetry, no analytics, no phone-home.** Nothing here has anywhere to
|
|
75
83
|
send anything.
|
|
76
84
|
|
|
77
85
|
**One destructive difference between the two installers.** `bin/sheleg-dev.js`
|
|
78
86
|
skips a skill that is already installed and says so, unless you pass `--force`
|
|
79
|
-
(`bin/sheleg-dev.js:
|
|
87
|
+
(`bin/sheleg-dev.js:89-90`). `install.sh` does not ask: for each of the seven names
|
|
80
88
|
it runs `rm -rf` on the destination and re-copies, every time
|
|
81
|
-
(`install.sh:
|
|
89
|
+
(`install.sh:62-63`). If you have hand-edited an installed skill, the shell
|
|
82
90
|
installer deletes your edits and the node installer does not.
|
|
83
91
|
|
|
84
92
|
## Credentials
|
|
@@ -193,14 +201,22 @@ node test/moneygate_test.js
|
|
|
193
201
|
# The command prints its own count.
|
|
194
202
|
node test/fixtures_test.js
|
|
195
203
|
|
|
204
|
+
# Both installers against throwaway HOMEs: fresh install, rerun, --force, and the
|
|
205
|
+
# plugin-present refusal — exit 3, the remedy naming the real plugin spec, nothing
|
|
206
|
+
# written; a corrupt ~/.claude/plugins/installed_plugins.json fails open. The command
|
|
207
|
+
# prints its own count.
|
|
208
|
+
node test/installer_test.js
|
|
209
|
+
|
|
196
210
|
# What the assertion packs cannot reach: NO OUTPUT, and grep exits 1 because it
|
|
197
211
|
# matched nothing. `readFileSync` of the fixtures beside them is their whole I/O.
|
|
198
212
|
grep -rnE "child_process|\bfetch\(|require\('(http|https|net|dns|tls)'\)|spawn\(|execFile|writeFileSync|appendFileSync|unlinkSync|process\.env\." \
|
|
199
213
|
plugins/sheleg-dev/skills/*/fixtures/*.mjs
|
|
200
214
|
|
|
201
|
-
# The entire I/O surface of the only two executable files:
|
|
202
|
-
# built-in requires, the copy/mkdir/remove calls, homedir,
|
|
203
|
-
# rm -rf + cp -R. No process, no socket, no
|
|
215
|
+
# The entire I/O surface of the only two executable files: twelve lines. Three
|
|
216
|
+
# built-in requires, the copy/mkdir/remove calls, homedir, install.sh's
|
|
217
|
+
# rm -rf + cp -R, and one comment naming rm -rf. No process, no socket, no
|
|
218
|
+
# network — the plugin-registry read that decides the shadow refusal is
|
|
219
|
+
# readFileSync in the node file and sed in the shell one.
|
|
204
220
|
grep -nE "require|child_process|exec|spawn|fetch|socket|rm -rf|cp -R|copyFileSync|mkdirSync|rmSync|homedir" bin/sheleg-dev.js install.sh
|
|
205
221
|
|
|
206
222
|
# Live-key shapes across the skill payload: one line, and it is a placeholder.
|
package/bin/sheleg-dev.js
CHANGED
|
@@ -17,6 +17,44 @@ const os = require('os');
|
|
|
17
17
|
const ROOT = path.resolve(__dirname, '..');
|
|
18
18
|
const REPO = 'ssheleg/sheleg-dev';
|
|
19
19
|
|
|
20
|
+
// Exit codes are the contract: 0 installed or skipped, 1 corrupted package,
|
|
21
|
+
// 2 usage error, 3 refused — the plugin channel owns this agent (--force overrides).
|
|
22
|
+
const EXIT_PLUGIN_PRESENT = 3;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The plugin spec (`<name>@<marketplace>`) installed for `name` in this home,
|
|
26
|
+
* or null.
|
|
27
|
+
*
|
|
28
|
+
* `installed_plugins.json` is the record of what is actually installed. The
|
|
29
|
+
* `plugins/marketplaces/<name>` directory — the check other members of this
|
|
30
|
+
* family keyed on until 2026-08-29 — under-reports: a marketplace added from a
|
|
31
|
+
* local `directory` source has no dir there at all, and plugin names differ
|
|
32
|
+
* from marketplace names, so a check keyed on it stays green while the shadow
|
|
33
|
+
* lands. Absence and corruption both read as "no plugin": the fresh HOME is
|
|
34
|
+
* the common case, and an installer that crashes on a parse error refuses the
|
|
35
|
+
* machines that need it most.
|
|
36
|
+
*/
|
|
37
|
+
function installedPluginSpec(home, name) {
|
|
38
|
+
try {
|
|
39
|
+
const raw = fs.readFileSync(
|
|
40
|
+
path.join(home, '.claude', 'plugins', 'installed_plugins.json'), 'utf8');
|
|
41
|
+
const parsed = JSON.parse(raw);
|
|
42
|
+
const plugins =
|
|
43
|
+
parsed && typeof parsed === 'object' &&
|
|
44
|
+
parsed.plugins && typeof parsed.plugins === 'object'
|
|
45
|
+
? parsed.plugins
|
|
46
|
+
: parsed;
|
|
47
|
+
if (!plugins || typeof plugins !== 'object') return null;
|
|
48
|
+
for (const spec of Object.keys(plugins)) {
|
|
49
|
+
if (spec === name) return `${name}@${name}`;
|
|
50
|
+
if (spec.startsWith(name + '@')) return spec;
|
|
51
|
+
}
|
|
52
|
+
} catch {
|
|
53
|
+
// missing or corrupt = no plugin — fail open on absence, never crash
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
20
58
|
function usage() {
|
|
21
59
|
console.log(`sheleg-dev installer
|
|
22
60
|
|
|
@@ -25,6 +63,12 @@ Usage:
|
|
|
25
63
|
into ~/.claude (skip existing unless --force)
|
|
26
64
|
npx @ssheleg/sheleg-dev --help
|
|
27
65
|
|
|
66
|
+
Exit codes:
|
|
67
|
+
0 installed or skipped 2 usage error
|
|
68
|
+
1 corrupted package 3 refused: the sheleg-dev PLUGIN is installed in
|
|
69
|
+
this home — plain copies would shadow its skills
|
|
70
|
+
(pass --force to write them anyway)
|
|
71
|
+
|
|
28
72
|
Other install paths:
|
|
29
73
|
Claude Code plugin: /plugin marketplace add ${REPO}
|
|
30
74
|
/plugin install sheleg-dev@sheleg-dev
|
|
@@ -84,6 +128,39 @@ function main(argv) {
|
|
|
84
128
|
}
|
|
85
129
|
|
|
86
130
|
const home = os.homedir();
|
|
131
|
+
|
|
132
|
+
// One channel per agent. Plain copies under ~/.claude/skills/ beside the
|
|
133
|
+
// installed sheleg-dev plugin are a second listing of the same skills, and
|
|
134
|
+
// the stale copies win — the exact shadow this family's canon forbids.
|
|
135
|
+
// Refuse rather than create them, and refuse LOUDLY: a marketplaces/-dir
|
|
136
|
+
// check alone is the fail-open class (a directory-sourced marketplace has no
|
|
137
|
+
// dir there, plugin names differ from marketplace names), and a refusal that
|
|
138
|
+
// exits 0 reads as success to every script above it. Reproduced live
|
|
139
|
+
// 2026-08-29: a bare `npx @ssheleg/telegram-dev` shipped three shadows past
|
|
140
|
+
// exactly that hole while the plugin was enabled.
|
|
141
|
+
const spec = installedPluginSpec(home, 'sheleg-dev');
|
|
142
|
+
const marketplace = path.join(home, '.claude', 'plugins', 'marketplaces', 'sheleg-dev');
|
|
143
|
+
const viaMarketplaceDir = !spec && fs.existsSync(marketplace);
|
|
144
|
+
if ((spec || viaMarketplaceDir) && !force) {
|
|
145
|
+
const found = spec
|
|
146
|
+
? `installed as the Claude Code plugin ${spec}\n` +
|
|
147
|
+
' (declared in ~/.claude/plugins/installed_plugins.json)'
|
|
148
|
+
: `registered as a Claude Code marketplace\n (${marketplace})`;
|
|
149
|
+
console.error(
|
|
150
|
+
`refused: sheleg-dev is already ${found}.\n` +
|
|
151
|
+
` Plain copies in ~/.claude/skills/ would shadow the plugin's\n` +
|
|
152
|
+
` ${names.length} skills and serve this frozen version forever. Update the\n` +
|
|
153
|
+
' plugin channel instead:\n' +
|
|
154
|
+
' claude plugin marketplace update sheleg-dev\n' +
|
|
155
|
+
` claude plugin update ${spec || 'sheleg-dev@sheleg-dev'}\n` +
|
|
156
|
+
' Family launcher (updates every member, prunes shadow copies):\n' +
|
|
157
|
+
' npx --yes sshlg-skills@latest update\n' +
|
|
158
|
+
' Pass --force to write the plain copies anyway — a deliberate choice\n' +
|
|
159
|
+
' to run two channels, where the stale one wins.'
|
|
160
|
+
);
|
|
161
|
+
return EXIT_PLUGIN_PRESENT;
|
|
162
|
+
}
|
|
163
|
+
|
|
87
164
|
for (const name of names) {
|
|
88
165
|
installOne(
|
|
89
166
|
`${name} skill`,
|
|
@@ -111,6 +188,15 @@ function main(argv) {
|
|
|
111
188
|
console.log('skills copy. To get it here, register it yourself — README.md, section');
|
|
112
189
|
console.log('"The manual gate", has the settings snippet. Nothing enforces this step.');
|
|
113
190
|
}
|
|
191
|
+
// The last line says how the next version arrives — "Installed" is not a
|
|
192
|
+
// complete sentence. Auto-update is off on purpose: this member composes
|
|
193
|
+
// with its family, and per-marketplace autoUpdate moves each member on its
|
|
194
|
+
// own clock, into combinations nobody tested together.
|
|
195
|
+
console.log(
|
|
196
|
+
'\nUpdates: rerun `npx @ssheleg/sheleg-dev@latest --force`, or refresh the\n' +
|
|
197
|
+
'whole family with `npx --yes sshlg-skills@latest update` (every channel,\n' +
|
|
198
|
+
'and it prunes plain copies that would shadow a plugin).'
|
|
199
|
+
);
|
|
114
200
|
return 0;
|
|
115
201
|
}
|
|
116
202
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ssheleg/sheleg-dev",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.5",
|
|
4
4
|
"description": "Seven integration skills for the layer a product reaches once it has users: Stripe subscription billing and cancellation retention, crypto payments, ad and conversion tracking under Consent Mode v2, Google sign-in and the wider Google auth surface, error tracking, and Core Web Vitals. This package is the installer CLI.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"sheleg-dev": "bin/sheleg-dev.js"
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"SECURITY.md"
|
|
18
18
|
],
|
|
19
19
|
"repository": "github:ssheleg/sheleg-dev",
|
|
20
|
-
"homepage": "https://
|
|
20
|
+
"homepage": "https://skills.sshlg.me/skills/sheleg-dev/",
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"author": {
|
|
23
23
|
"name": "ssheleg",
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
"skill"
|
|
44
44
|
],
|
|
45
45
|
"scripts": {
|
|
46
|
-
"test": "python3 test/validate.py && node test/moneygate_test.js && node test/fixtures_test.js",
|
|
46
|
+
"test": "python3 test/validate.py && node test/moneygate_test.js && node test/fixtures_test.js && node test/installer_test.js",
|
|
47
47
|
"test:negatives": "python3 test/negatives.py",
|
|
48
|
-
"test:all": "python3 test/validate.py && node test/moneygate_test.js && node test/fixtures_test.js && python3 test/negatives.py"
|
|
48
|
+
"test:all": "python3 test/validate.py && node test/moneygate_test.js && node test/fixtures_test.js && node test/installer_test.js && python3 test/negatives.py"
|
|
49
49
|
}
|
|
50
50
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "sheleg-dev",
|
|
4
4
|
"displayName": "Sheleg Dev",
|
|
5
5
|
"description": "The integration layer a product needs once it has users: Stripe subscription billing reconciled into your own database, crypto payments that survive over-payment and duplicate webhooks, ad and conversion tracking under Consent Mode v2, Google sign-in with the account-linking guards, the wider Google auth surface, and Core Web Vitals work that moves the score, and Sentry error tracking wired so it does not forward your own credentials to a third party and so a stack trace names the commit that caused it.",
|
|
6
|
-
"version": "0.10.
|
|
6
|
+
"version": "0.10.5",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "ssheleg",
|
|
9
9
|
"url": "https://x.com/sshlg93"
|