@ccoalm/ccl-skills 0.2.0 → 0.4.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 +12 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/classify_envelope.py +34 -3
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_abort_leak_state_helpers.sh +148 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_classify_envelope.sh +28 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_parse_review_json.sh +7 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_gate.sh +13 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_gate_abort_leak.sh +271 -34
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/SKILL.md +8 -8
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/eval-routing.md +7 -8
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/extraction-quickstart.md +3 -2
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/harness-patterns-and-eval.md +7 -6
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/recurring-anti-patterns-checklist.md +18 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-register.md +29 -2
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-to-skill-extraction.md +28 -29
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/check-ccl-skills.sh +153 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/eval-health.rb +23 -10
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/impact-chain-gate.rb +303 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_impact_chain_refscripts.sh +222 -91
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_regressions.sh +6 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_gate_dateless_host.sh +6 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_gate_verdict_differential.sh +1 -1
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_round_attribution.sh +12 -12
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_self_adjudication.sh +455 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_source_refuted.sh +20 -20
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_liveness_predicate_gate.sh +288 -0
- package/dist/assets/release.json +60 -25
- package/dist/cli.js +23 -2
- package/dist/update-notice.d.ts +71 -0
- package/dist/update-notice.js +173 -0
- package/dist/version-check.d.ts +2 -0
- package/dist/version-check.js +7 -0
- package/package.json +1 -1
package/dist/assets/release.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": 1,
|
|
3
3
|
"npmPackage": "@ccoalm/ccl-skills",
|
|
4
|
-
"version": "0.
|
|
5
|
-
"sourceCommit": "
|
|
4
|
+
"version": "0.4.0",
|
|
5
|
+
"sourceCommit": "b6a56485f7e4764e36a2c2ac14693db25964f018",
|
|
6
6
|
"sourceState": "clean",
|
|
7
7
|
"files": [
|
|
8
8
|
{
|
|
@@ -287,7 +287,7 @@
|
|
|
287
287
|
},
|
|
288
288
|
{
|
|
289
289
|
"path": "marketplace/plugins/ccl-skills/skills/code-review/scripts/classify_envelope.py",
|
|
290
|
-
"sha256": "
|
|
290
|
+
"sha256": "72956ebdfc3c22281b86fb6ada6c2e88a6e21c3a2aa6a80b09767adb62fb18b0",
|
|
291
291
|
"mode": 493
|
|
292
292
|
},
|
|
293
293
|
{
|
|
@@ -375,9 +375,14 @@
|
|
|
375
375
|
"sha256": "b92a8b2ae91608081665eabcee482b7550415d66df8ad654f1d041add6b3de8f",
|
|
376
376
|
"mode": 420
|
|
377
377
|
},
|
|
378
|
+
{
|
|
379
|
+
"path": "marketplace/plugins/ccl-skills/skills/code-review/scripts/test_abort_leak_state_helpers.sh",
|
|
380
|
+
"sha256": "6696d671f0aa071537e35fe93f9f39453659bafeb17a5ab5441407b557a2bdb1",
|
|
381
|
+
"mode": 493
|
|
382
|
+
},
|
|
378
383
|
{
|
|
379
384
|
"path": "marketplace/plugins/ccl-skills/skills/code-review/scripts/test_classify_envelope.sh",
|
|
380
|
-
"sha256": "
|
|
385
|
+
"sha256": "6ced09a9338753975d12fe81d45b513c276cd38470ef1b2a16849561d88bf19f",
|
|
381
386
|
"mode": 493
|
|
382
387
|
},
|
|
383
388
|
{
|
|
@@ -437,7 +442,7 @@
|
|
|
437
442
|
},
|
|
438
443
|
{
|
|
439
444
|
"path": "marketplace/plugins/ccl-skills/skills/code-review/scripts/test_parse_review_json.sh",
|
|
440
|
-
"sha256": "
|
|
445
|
+
"sha256": "62be1010b6f01dfef76c3e6e6b96a8ff5d400c4d9c68060b33b0982ff2b8c754",
|
|
441
446
|
"mode": 493
|
|
442
447
|
},
|
|
443
448
|
{
|
|
@@ -452,12 +457,12 @@
|
|
|
452
457
|
},
|
|
453
458
|
{
|
|
454
459
|
"path": "marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_gate_abort_leak.sh",
|
|
455
|
-
"sha256": "
|
|
460
|
+
"sha256": "b4bd03c04e822eb8ed212e9624ce9ca8c93bdcd363885cfdcf2fd77e74dd21a8",
|
|
456
461
|
"mode": 493
|
|
457
462
|
},
|
|
458
463
|
{
|
|
459
464
|
"path": "marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_gate.sh",
|
|
460
|
-
"sha256": "
|
|
465
|
+
"sha256": "244a56a2dbc3b02d11024ec7c4a5d45d31f98938a438b0bfd4ba5be3ea3bf60b",
|
|
461
466
|
"mode": 493
|
|
462
467
|
},
|
|
463
468
|
{
|
|
@@ -1912,7 +1917,7 @@
|
|
|
1912
1917
|
},
|
|
1913
1918
|
{
|
|
1914
1919
|
"path": "marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/eval-routing.md",
|
|
1915
|
-
"sha256": "
|
|
1920
|
+
"sha256": "73ce45ed4bb5ca29f9e9b5e286c5da8bbad9d39d48fb8405b43050990814554d",
|
|
1916
1921
|
"mode": 420
|
|
1917
1922
|
},
|
|
1918
1923
|
{
|
|
@@ -1937,7 +1942,7 @@
|
|
|
1937
1942
|
},
|
|
1938
1943
|
{
|
|
1939
1944
|
"path": "marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/extraction-quickstart.md",
|
|
1940
|
-
"sha256": "
|
|
1945
|
+
"sha256": "4c3660c0bf33bf742e7d690c11661f7edef1eab0d63a100866e8001ad3adfddb",
|
|
1941
1946
|
"mode": 420
|
|
1942
1947
|
},
|
|
1943
1948
|
{
|
|
@@ -1947,7 +1952,7 @@
|
|
|
1947
1952
|
},
|
|
1948
1953
|
{
|
|
1949
1954
|
"path": "marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/harness-patterns-and-eval.md",
|
|
1950
|
-
"sha256": "
|
|
1955
|
+
"sha256": "2c106cb1df85e13b4034866fafbd32c0f9dacf89be7f15ffe8cd7089a8c8d7ef",
|
|
1951
1956
|
"mode": 420
|
|
1952
1957
|
},
|
|
1953
1958
|
{
|
|
@@ -1977,7 +1982,7 @@
|
|
|
1977
1982
|
},
|
|
1978
1983
|
{
|
|
1979
1984
|
"path": "marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/recurring-anti-patterns-checklist.md",
|
|
1980
|
-
"sha256": "
|
|
1985
|
+
"sha256": "38a2abe3391b245fef430d334f6aac91a62f75a6f32d2f2a76ad9f946fabb6ec",
|
|
1981
1986
|
"mode": 420
|
|
1982
1987
|
},
|
|
1983
1988
|
{
|
|
@@ -2012,12 +2017,12 @@
|
|
|
2012
2017
|
},
|
|
2013
2018
|
{
|
|
2014
2019
|
"path": "marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-register.md",
|
|
2015
|
-
"sha256": "
|
|
2020
|
+
"sha256": "bfc26af574b990ce5a8e7dcfcd42b17eaf302e1ba0ec61b10f1db774e85c2a30",
|
|
2016
2021
|
"mode": 420
|
|
2017
2022
|
},
|
|
2018
2023
|
{
|
|
2019
2024
|
"path": "marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-to-skill-extraction.md",
|
|
2020
|
-
"sha256": "
|
|
2025
|
+
"sha256": "63f3e920df63add61965cefbb5673f6ef2b30249473b548af81fb3a89520b8cb",
|
|
2021
2026
|
"mode": 420
|
|
2022
2027
|
},
|
|
2023
2028
|
{
|
|
@@ -2047,7 +2052,7 @@
|
|
|
2047
2052
|
},
|
|
2048
2053
|
{
|
|
2049
2054
|
"path": "marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/check-ccl-skills.sh",
|
|
2050
|
-
"sha256": "
|
|
2055
|
+
"sha256": "4addb9d800b78b8cc2eb1c8f4429959631bcf092ef00d28306b95b9cd5f209da",
|
|
2051
2056
|
"mode": 493
|
|
2052
2057
|
},
|
|
2053
2058
|
{
|
|
@@ -2077,7 +2082,7 @@
|
|
|
2077
2082
|
},
|
|
2078
2083
|
{
|
|
2079
2084
|
"path": "marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/eval-health.rb",
|
|
2080
|
-
"sha256": "
|
|
2085
|
+
"sha256": "c44e09befe49a5dd9491772bd1bdffb2c881df478fe2705e94d6006543aabadc",
|
|
2081
2086
|
"mode": 493
|
|
2082
2087
|
},
|
|
2083
2088
|
{
|
|
@@ -2102,7 +2107,7 @@
|
|
|
2102
2107
|
},
|
|
2103
2108
|
{
|
|
2104
2109
|
"path": "marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/impact-chain-gate.rb",
|
|
2105
|
-
"sha256": "
|
|
2110
|
+
"sha256": "af64b8f307c75943c81553af22ddb3702cdc1e3ecd77d7a9a3052b680874eaed",
|
|
2106
2111
|
"mode": 493
|
|
2107
2112
|
},
|
|
2108
2113
|
{
|
|
@@ -2132,7 +2137,7 @@
|
|
|
2132
2137
|
},
|
|
2133
2138
|
{
|
|
2134
2139
|
"path": "marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_impact_chain_refscripts.sh",
|
|
2135
|
-
"sha256": "
|
|
2140
|
+
"sha256": "04e4b75799c9e6e97402a6515f063dd1c59dc0b4d5f02cc9448142dc99459fea",
|
|
2136
2141
|
"mode": 493
|
|
2137
2142
|
},
|
|
2138
2143
|
{
|
|
@@ -2147,7 +2152,7 @@
|
|
|
2147
2152
|
},
|
|
2148
2153
|
{
|
|
2149
2154
|
"path": "marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_regressions.sh",
|
|
2150
|
-
"sha256": "
|
|
2155
|
+
"sha256": "afaa9197cd4e0bf0e0b8d6e3189f0e939561b8142ec1e76c9baf9dd968a3af90",
|
|
2151
2156
|
"mode": 493
|
|
2152
2157
|
},
|
|
2153
2158
|
{
|
|
@@ -2217,22 +2222,32 @@
|
|
|
2217
2222
|
},
|
|
2218
2223
|
{
|
|
2219
2224
|
"path": "marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_gate_dateless_host.sh",
|
|
2220
|
-
"sha256": "
|
|
2225
|
+
"sha256": "08e707048810264e6d74d5df4d9415601b14749204eed55f9f160ae5dc782a60",
|
|
2221
2226
|
"mode": 493
|
|
2222
2227
|
},
|
|
2223
2228
|
{
|
|
2224
2229
|
"path": "marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_gate_verdict_differential.sh",
|
|
2225
|
-
"sha256": "
|
|
2230
|
+
"sha256": "30d94cbf6d2255d1a9eb38eb558ef2d8c638b4d1a65d739b1c713f339b9b22ad",
|
|
2226
2231
|
"mode": 493
|
|
2227
2232
|
},
|
|
2228
2233
|
{
|
|
2229
2234
|
"path": "marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_round_attribution.sh",
|
|
2230
|
-
"sha256": "
|
|
2235
|
+
"sha256": "034179e057ff3dbba0ecf3077fad6836caf45536a68479742ace8bbd016ab283",
|
|
2236
|
+
"mode": 493
|
|
2237
|
+
},
|
|
2238
|
+
{
|
|
2239
|
+
"path": "marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_self_adjudication.sh",
|
|
2240
|
+
"sha256": "85dbe8967dabe779b99d0e9543b7a6cc24d525f26e6f55eadd6995e16679fb76",
|
|
2231
2241
|
"mode": 493
|
|
2232
2242
|
},
|
|
2233
2243
|
{
|
|
2234
2244
|
"path": "marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_source_refuted.sh",
|
|
2235
|
-
"sha256": "
|
|
2245
|
+
"sha256": "f2f24a5e16d90b11ab0878004612c59a56091bca26f3f4a030490c3dd64a8449",
|
|
2246
|
+
"mode": 493
|
|
2247
|
+
},
|
|
2248
|
+
{
|
|
2249
|
+
"path": "marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_liveness_predicate_gate.sh",
|
|
2250
|
+
"sha256": "59a64a65c1998a18506164f93100cb270319ee9c6eddd485d32d78520b064e6a",
|
|
2236
2251
|
"mode": 493
|
|
2237
2252
|
},
|
|
2238
2253
|
{
|
|
@@ -2287,7 +2302,7 @@
|
|
|
2287
2302
|
},
|
|
2288
2303
|
{
|
|
2289
2304
|
"path": "marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/SKILL.md",
|
|
2290
|
-
"sha256": "
|
|
2305
|
+
"sha256": "bff6bbfcc86cf5a2c523331621e113c2032a84130237ce1a89b79b5faf0b97dc",
|
|
2291
2306
|
"mode": 420
|
|
2292
2307
|
},
|
|
2293
2308
|
{
|
|
@@ -2719,7 +2734,7 @@
|
|
|
2719
2734
|
},
|
|
2720
2735
|
{
|
|
2721
2736
|
"path": "cli.js",
|
|
2722
|
-
"sha256": "
|
|
2737
|
+
"sha256": "5b5ab7f39b7faa81ea209b3e479295da64f2a834969ee5ac8d76354a182c2822",
|
|
2723
2738
|
"mode": 493
|
|
2724
2739
|
},
|
|
2725
2740
|
{
|
|
@@ -2812,6 +2827,26 @@
|
|
|
2812
2827
|
"sha256": "2aeabe2364755dc91d7058bf3e3b8e2bb4086a0392da6b8f87a3892879334915",
|
|
2813
2828
|
"mode": 420
|
|
2814
2829
|
},
|
|
2830
|
+
{
|
|
2831
|
+
"path": "update-notice.d.ts",
|
|
2832
|
+
"sha256": "2caf9b8512c1969c4ecdb31e90de0265ef435b70a9bcca07f649d5055d63c6ab",
|
|
2833
|
+
"mode": 420
|
|
2834
|
+
},
|
|
2835
|
+
{
|
|
2836
|
+
"path": "update-notice.js",
|
|
2837
|
+
"sha256": "da5ceca856f4492afd38010918d4c33c5f8dfc04d490bbf84ff955a8e12f7245",
|
|
2838
|
+
"mode": 420
|
|
2839
|
+
},
|
|
2840
|
+
{
|
|
2841
|
+
"path": "version-check.d.ts",
|
|
2842
|
+
"sha256": "43e818adf60173644896298637f47b01d5819b17eda46eaa32d0c7d64724d012",
|
|
2843
|
+
"mode": 420
|
|
2844
|
+
},
|
|
2845
|
+
{
|
|
2846
|
+
"path": "version-check.js",
|
|
2847
|
+
"sha256": "bd6babcff7eb222ce39f8ed4d8945f55f4004f45d17600fee497ee64357099da",
|
|
2848
|
+
"mode": 420
|
|
2849
|
+
},
|
|
2815
2850
|
{
|
|
2816
2851
|
"path": "version.d.ts",
|
|
2817
2852
|
"sha256": "edc6c24c912373b1ffc68f7b261f2e04d616b0486e9ab2401d1201f094c5a885",
|
|
@@ -2823,5 +2858,5 @@
|
|
|
2823
2858
|
"mode": 420
|
|
2824
2859
|
}
|
|
2825
2860
|
],
|
|
2826
|
-
"snapshotHash": "
|
|
2861
|
+
"snapshotHash": "6bf3d196d1ce59b751b9082bbb62b0c93cda0ceeb7e3c16431576522a5d9158e"
|
|
2827
2862
|
}
|
package/dist/cli.js
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
import { readFileSync, realpathSync } from "node:fs";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { Worker } from "node:worker_threads";
|
|
5
|
-
import { spawnSync } from "node:child_process";
|
|
5
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
6
|
+
import { paths } from "./paths.js";
|
|
7
|
+
import { cachePath, emitNotice } from "./update-notice.js";
|
|
6
8
|
const pkg = JSON.parse(readFileSync(fileURLToPath(new URL("../package.json", import.meta.url)), "utf8"));
|
|
7
|
-
const HELP = `ccl-skills — npm snapshot installer for Claude Code, Codex, and OpenCode\n\nUsage:\n ccl-skills install [--host claude|codex|opencode] [--json]\n ccl-skills update [--host claude|codex|opencode] [--yes] [--allow-downgrade] [--json]\n ccl-skills doctor [--host claude|codex|opencode] [--json]\n ccl-skills uninstall [--host claude|codex|opencode] [--yes] [--json]\n\nWithout --host, every installed or package-owned host is selected.\nUpdate and uninstall are dry-run unless --yes is supplied.\nBy default update --yes upgrades the global npm package to @latest before refreshing assets.\nSet CCL_SKILLS_SKIP_SELF_UPDATE=1 for an assets-only refresh; --allow-downgrade always uses the invoked package without installing @latest first.`;
|
|
9
|
+
const HELP = `ccl-skills — npm snapshot installer for Claude Code, Codex, and OpenCode\n\nUsage:\n ccl-skills install [--host claude|codex|opencode] [--json]\n ccl-skills update [--host claude|codex|opencode] [--yes] [--allow-downgrade] [--json]\n ccl-skills doctor [--host claude|codex|opencode] [--json]\n ccl-skills uninstall [--host claude|codex|opencode] [--yes] [--json]\n\nWithout --host, every installed or package-owned host is selected.\nUpdate and uninstall are dry-run unless --yes is supplied.\nBy default update --yes upgrades the global npm package to @latest before refreshing assets.\nSet CCL_SKILLS_SKIP_SELF_UPDATE=1 for an assets-only refresh; --allow-downgrade always uses the invoked package without installing @latest first.\n\nAn interactive run prints an update notice on stderr at most once a day.\nSet CCL_SKILLS_NO_UPDATE_NOTIFIER=1 (or NO_UPDATE_NOTIFIER) to silence it; it is\nalready silent under --json, in CI, and when output is not a terminal.`;
|
|
8
10
|
export function parseArgs(args) {
|
|
9
11
|
if (!args.length ||
|
|
10
12
|
args.includes("--help") ||
|
|
@@ -164,10 +166,28 @@ function selfUpdate(options) {
|
|
|
164
166
|
return { code: 5, status: "package-updated-assets-unknown", message: "npm package updated, but the fresh CLI returned an invalid result" };
|
|
165
167
|
}
|
|
166
168
|
}
|
|
169
|
+
/** Secondary guidance chrome. It is handed a stderr writer only, runs after the
|
|
170
|
+
* command's own output, and schedules its registry check in a detached child,
|
|
171
|
+
* so it can neither reach stdout nor add latency to this invocation. */
|
|
172
|
+
function notifyUpdate(json) {
|
|
173
|
+
emitNotice({
|
|
174
|
+
current: pkg.version,
|
|
175
|
+
now: Date.now(),
|
|
176
|
+
env: process.env,
|
|
177
|
+
stdoutTTY: !!process.stdout.isTTY,
|
|
178
|
+
stderrTTY: !!process.stderr.isTTY,
|
|
179
|
+
columns: process.stdout.columns,
|
|
180
|
+
json,
|
|
181
|
+
}, cachePath(paths().root), {
|
|
182
|
+
stderr: (text) => write(process.stderr, text),
|
|
183
|
+
spawnCheck: () => spawn(process.execPath, [fileURLToPath(new URL("./version-check.js", import.meta.url))], { detached: true, stdio: "ignore" }).unref(),
|
|
184
|
+
});
|
|
185
|
+
}
|
|
167
186
|
export async function main(args) {
|
|
168
187
|
const parsed = parseArgs(args);
|
|
169
188
|
if (parsed.direct) {
|
|
170
189
|
write(parsed.direct.stream === "stdout" ? process.stdout : process.stderr, parsed.direct.text);
|
|
190
|
+
notifyUpdate(false);
|
|
171
191
|
return parsed.direct.code;
|
|
172
192
|
}
|
|
173
193
|
const shouldSelfUpdate = parsed.command === "update" && parsed.options.yes && !parsed.options.allowDowngrade && process.env.CCL_SKILLS_SKIP_SELF_UPDATE !== "1";
|
|
@@ -184,6 +204,7 @@ export async function main(args) {
|
|
|
184
204
|
write(process.stdout, parsed.json
|
|
185
205
|
? JSON.stringify(result)
|
|
186
206
|
: `${result.status}: ${result.message}${result.plan ? `\n${result.plan.map((x) => `- ${x}`).join("\n")}` : ""}`);
|
|
207
|
+
notifyUpdate(!!parsed.json);
|
|
187
208
|
return result.code;
|
|
188
209
|
}
|
|
189
210
|
export function isDirectEntrypoint(importMetaUrl, argv1) {
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { mkdirSync, renameSync, unlinkSync, writeFileSync, readFileSync } from "node:fs";
|
|
2
|
+
export type Cache = {
|
|
3
|
+
latest?: string;
|
|
4
|
+
lastCheckedAt?: string;
|
|
5
|
+
notifiedVersion?: string;
|
|
6
|
+
notifiedAt?: string;
|
|
7
|
+
};
|
|
8
|
+
export type NoticeInput = {
|
|
9
|
+
current: string;
|
|
10
|
+
cache?: Cache;
|
|
11
|
+
now: number;
|
|
12
|
+
env: Record<string, string | undefined>;
|
|
13
|
+
stdoutTTY: boolean;
|
|
14
|
+
stderrTTY: boolean;
|
|
15
|
+
columns?: number;
|
|
16
|
+
json: boolean;
|
|
17
|
+
};
|
|
18
|
+
export type Verdict = {
|
|
19
|
+
show: false;
|
|
20
|
+
reason: string;
|
|
21
|
+
} | {
|
|
22
|
+
show: true;
|
|
23
|
+
reason: "update-available";
|
|
24
|
+
latest: string;
|
|
25
|
+
};
|
|
26
|
+
export declare const TTL_MS: number;
|
|
27
|
+
export declare const CACHE_FILE = "version-check.json";
|
|
28
|
+
/** Numeric precedence. Returns 0 when either side is not a stable version, so
|
|
29
|
+
* an unparseable value can never be reported as newer. */
|
|
30
|
+
export declare function compareVersions(a: string, b: string): number;
|
|
31
|
+
export declare function pickLatest(packument: unknown): string | null;
|
|
32
|
+
/** Fails open toward refreshing: an absent, malformed, or future timestamp all
|
|
33
|
+
* mean "check again", never "assume fresh". */
|
|
34
|
+
export declare function needsRefresh(cache: Cache | undefined, now: number, ttl?: number): boolean;
|
|
35
|
+
export declare function refreshAllowed(input: NoticeInput): boolean;
|
|
36
|
+
export declare function shouldNotify(input: NoticeInput): Verdict;
|
|
37
|
+
/** Plain text only: colour is an enhancement, never the state carrier, and
|
|
38
|
+
* every line fits the narrow-terminal floor this notice is gated on. */
|
|
39
|
+
export declare function formatNotice(current: string, latest: string): string;
|
|
40
|
+
export declare function cachePath(root: string): string;
|
|
41
|
+
export type EmitDeps = {
|
|
42
|
+
stderr: (text: string) => void;
|
|
43
|
+
spawnCheck: () => void;
|
|
44
|
+
};
|
|
45
|
+
/** The only entry the CLI calls. It is handed a stderr writer and nothing that
|
|
46
|
+
* can reach stdout, so the notice structurally cannot pollute primary output.
|
|
47
|
+
* Returns the sanitized reason code; it never throws and never changes exit. */
|
|
48
|
+
export declare function emitNotice(input: NoticeInput, file: string, deps: EmitDeps): string;
|
|
49
|
+
export type RefreshOptions = {
|
|
50
|
+
file: string;
|
|
51
|
+
now?: number;
|
|
52
|
+
fetchImpl?: (url: string, init: unknown) => Promise<{
|
|
53
|
+
ok: boolean;
|
|
54
|
+
json: () => Promise<unknown>;
|
|
55
|
+
}>;
|
|
56
|
+
timeoutMs?: number;
|
|
57
|
+
};
|
|
58
|
+
/** Runs in a detached child, never on the caller's path. The check time is
|
|
59
|
+
* stamped even on failure so an offline machine retries once per TTL, not once
|
|
60
|
+
* per command; a failed check never erases a previously known version. */
|
|
61
|
+
export declare function refresh(options: RefreshOptions): Promise<void>;
|
|
62
|
+
export declare function readCache(path: string, read?: typeof readFileSync): Cache | undefined;
|
|
63
|
+
export type WriteDeps = {
|
|
64
|
+
writeFileSync?: typeof writeFileSync;
|
|
65
|
+
renameSync?: typeof renameSync;
|
|
66
|
+
unlinkSync?: typeof unlinkSync;
|
|
67
|
+
mkdirSync?: typeof mkdirSync;
|
|
68
|
+
};
|
|
69
|
+
/** Temp file in the target directory, then rename: a reader sees the old cache
|
|
70
|
+
* or the whole new one, never a truncated one, and never an EXDEV failure. */
|
|
71
|
+
export declare function writeCacheAtomic(path: string, cache: Cache, deps?: WriteDeps): void;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// Update notice: a terminal side channel, never primary output.
|
|
2
|
+
// Display is bound to eligibility, cache freshness, and cooldown; the network
|
|
3
|
+
// check runs detached so no invocation pays for it. Every failure path is
|
|
4
|
+
// swallowed — a version check must never change what the command itself does.
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import { mkdirSync, renameSync, unlinkSync, writeFileSync, readFileSync } from "node:fs";
|
|
7
|
+
export const TTL_MS = 24 * 60 * 60 * 1000;
|
|
8
|
+
export const CACHE_FILE = "version-check.json";
|
|
9
|
+
const MIN_COLUMNS = 60;
|
|
10
|
+
const OPT_OUT = ["CCL_SKILLS_NO_UPDATE_NOTIFIER", "NO_UPDATE_NOTIFIER"];
|
|
11
|
+
const CI_VARS = [
|
|
12
|
+
"CI",
|
|
13
|
+
"CONTINUOUS_INTEGRATION",
|
|
14
|
+
"BUILD_NUMBER",
|
|
15
|
+
"GITHUB_ACTIONS",
|
|
16
|
+
"GITLAB_CI",
|
|
17
|
+
"BUILDKITE",
|
|
18
|
+
"CIRCLECI",
|
|
19
|
+
"TRAVIS",
|
|
20
|
+
"TEAMCITY_VERSION",
|
|
21
|
+
"JENKINS_URL",
|
|
22
|
+
];
|
|
23
|
+
// Releases accept only stable MAJOR.MINOR.PATCH, so anything else — prerelease,
|
|
24
|
+
// build metadata, or a hostile string — is not a version we will ever suggest.
|
|
25
|
+
const STABLE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
|
|
26
|
+
const set = (env, names) => names.some((name) => (env[name] ?? "") !== "");
|
|
27
|
+
/** Numeric precedence. Returns 0 when either side is not a stable version, so
|
|
28
|
+
* an unparseable value can never be reported as newer. */
|
|
29
|
+
export function compareVersions(a, b) {
|
|
30
|
+
if (!STABLE.test(a) || !STABLE.test(b))
|
|
31
|
+
return 0;
|
|
32
|
+
const left = a.split(".").map(Number), right = b.split(".").map(Number);
|
|
33
|
+
for (let i = 0; i < 3; i++)
|
|
34
|
+
if (left[i] !== right[i])
|
|
35
|
+
return left[i] - right[i];
|
|
36
|
+
return 0;
|
|
37
|
+
}
|
|
38
|
+
export function pickLatest(packument) {
|
|
39
|
+
const tags = packument?.["dist-tags"];
|
|
40
|
+
const latest = tags?.latest;
|
|
41
|
+
return typeof latest === "string" && STABLE.test(latest) ? latest : null;
|
|
42
|
+
}
|
|
43
|
+
/** Fails open toward refreshing: an absent, malformed, or future timestamp all
|
|
44
|
+
* mean "check again", never "assume fresh". */
|
|
45
|
+
export function needsRefresh(cache, now, ttl = TTL_MS) {
|
|
46
|
+
const at = Date.parse(cache?.lastCheckedAt ?? "");
|
|
47
|
+
return Number.isNaN(at) || at > now || now - at >= ttl;
|
|
48
|
+
}
|
|
49
|
+
/** The display gates that do not depend on cache contents. Shared by the notice
|
|
50
|
+
* and the background check so `--json`, CI, and opt-out make no network call. */
|
|
51
|
+
function displayAllowed(input) {
|
|
52
|
+
if (set(input.env, OPT_OUT))
|
|
53
|
+
return { ok: false, reason: "opt-out" };
|
|
54
|
+
if (set(input.env, CI_VARS) || !input.stdoutTTY || !input.stderrTTY)
|
|
55
|
+
return { ok: false, reason: "non-interactive" };
|
|
56
|
+
if (input.json)
|
|
57
|
+
return { ok: false, reason: "structured-output" };
|
|
58
|
+
// A pty with no window size reports 0: that is "width unknown", not "zero
|
|
59
|
+
// cells wide", so it must not be treated as a narrow terminal.
|
|
60
|
+
if (input.columns !== undefined && input.columns > 0 && input.columns < MIN_COLUMNS)
|
|
61
|
+
return { ok: false, reason: "narrow-terminal" };
|
|
62
|
+
return { ok: true, reason: "eligible" };
|
|
63
|
+
}
|
|
64
|
+
export function refreshAllowed(input) {
|
|
65
|
+
return displayAllowed(input).ok;
|
|
66
|
+
}
|
|
67
|
+
export function shouldNotify(input) {
|
|
68
|
+
const gate = displayAllowed(input);
|
|
69
|
+
if (!gate.ok)
|
|
70
|
+
return { show: false, reason: gate.reason };
|
|
71
|
+
const latest = input.cache?.latest;
|
|
72
|
+
if (!latest)
|
|
73
|
+
return { show: false, reason: "no-cache" };
|
|
74
|
+
if (compareVersions(latest, input.current) <= 0)
|
|
75
|
+
return { show: false, reason: "up-to-date" };
|
|
76
|
+
const notifiedAt = Date.parse(input.cache?.notifiedAt ?? "");
|
|
77
|
+
if (input.cache?.notifiedVersion === latest &&
|
|
78
|
+
!Number.isNaN(notifiedAt) &&
|
|
79
|
+
input.now - notifiedAt < TTL_MS)
|
|
80
|
+
return { show: false, reason: "cooldown" };
|
|
81
|
+
return { show: true, reason: "update-available", latest };
|
|
82
|
+
}
|
|
83
|
+
/** Plain text only: colour is an enhancement, never the state carrier, and
|
|
84
|
+
* every line fits the narrow-terminal floor this notice is gated on. */
|
|
85
|
+
export function formatNotice(current, latest) {
|
|
86
|
+
return [
|
|
87
|
+
`Update available: ${current} -> ${latest}`,
|
|
88
|
+
"Run: ccl-skills update --yes",
|
|
89
|
+
"Silence: CCL_SKILLS_NO_UPDATE_NOTIFIER=1",
|
|
90
|
+
].join("\n");
|
|
91
|
+
}
|
|
92
|
+
export function cachePath(root) {
|
|
93
|
+
return join(root, CACHE_FILE);
|
|
94
|
+
}
|
|
95
|
+
/** The only entry the CLI calls. It is handed a stderr writer and nothing that
|
|
96
|
+
* can reach stdout, so the notice structurally cannot pollute primary output.
|
|
97
|
+
* Returns the sanitized reason code; it never throws and never changes exit. */
|
|
98
|
+
export function emitNotice(input, file, deps) {
|
|
99
|
+
try {
|
|
100
|
+
const cache = input.cache ?? readCache(file);
|
|
101
|
+
const resolved = { ...input, cache };
|
|
102
|
+
const verdict = shouldNotify(resolved);
|
|
103
|
+
if (verdict.show) {
|
|
104
|
+
deps.stderr(formatNotice(input.current, verdict.latest));
|
|
105
|
+
writeCacheAtomic(file, {
|
|
106
|
+
...cache,
|
|
107
|
+
notifiedVersion: verdict.latest,
|
|
108
|
+
notifiedAt: new Date(input.now).toISOString(),
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
if (refreshAllowed(resolved) && needsRefresh(cache, input.now))
|
|
112
|
+
deps.spawnCheck();
|
|
113
|
+
return verdict.reason;
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return "unavailable";
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const REGISTRY = "https://registry.npmjs.org/@ccoalm%2fccl-skills";
|
|
120
|
+
/** Runs in a detached child, never on the caller's path. The check time is
|
|
121
|
+
* stamped even on failure so an offline machine retries once per TTL, not once
|
|
122
|
+
* per command; a failed check never erases a previously known version. */
|
|
123
|
+
export async function refresh(options) {
|
|
124
|
+
const now = options.now ?? Date.now(), previous = readCache(options.file) ?? {};
|
|
125
|
+
let latest = null;
|
|
126
|
+
try {
|
|
127
|
+
const response = await (options.fetchImpl ?? fetch)(REGISTRY, {
|
|
128
|
+
headers: { Accept: "application/vnd.npm.install-v1+json" },
|
|
129
|
+
signal: AbortSignal.timeout(options.timeoutMs ?? 5000),
|
|
130
|
+
});
|
|
131
|
+
if (response.ok)
|
|
132
|
+
latest = pickLatest(await response.json());
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
/* offline, throttled, or malformed: keep the previous value */
|
|
136
|
+
}
|
|
137
|
+
writeCacheAtomic(options.file, {
|
|
138
|
+
...previous,
|
|
139
|
+
...(latest ? { latest } : {}),
|
|
140
|
+
lastCheckedAt: new Date(now).toISOString(),
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
export function readCache(path, read = readFileSync) {
|
|
144
|
+
try {
|
|
145
|
+
const parsed = JSON.parse(read(path, "utf8"));
|
|
146
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
147
|
+
? parsed
|
|
148
|
+
: undefined;
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/** Temp file in the target directory, then rename: a reader sees the old cache
|
|
155
|
+
* or the whole new one, never a truncated one, and never an EXDEV failure. */
|
|
156
|
+
export function writeCacheAtomic(path, cache, deps = {}) {
|
|
157
|
+
const write = deps.writeFileSync ?? writeFileSync, rename = deps.renameSync ?? renameSync, unlink = deps.unlinkSync ?? unlinkSync, temp = join(dirname(path), `.${CACHE_FILE}.tmp-${process.pid}`);
|
|
158
|
+
try {
|
|
159
|
+
// Without this the state root is absent until a host install runs, the
|
|
160
|
+
// write fails every time, and the caller reschedules the check forever.
|
|
161
|
+
(deps.mkdirSync ?? mkdirSync)(dirname(path), { recursive: true });
|
|
162
|
+
write(temp, `${JSON.stringify(cache)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
163
|
+
rename(temp, path);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
try {
|
|
167
|
+
unlink(temp);
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
/* nothing to clean up */
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Detached entrypoint for the background version check. It is spawned with
|
|
3
|
+
// stdio ignored and unref'd, so it cannot slow, block, or outlive-block the
|
|
4
|
+
// command that scheduled it, and it exits 0 whatever the registry does.
|
|
5
|
+
import { paths } from "./paths.js";
|
|
6
|
+
import { cachePath, refresh } from "./update-notice.js";
|
|
7
|
+
refresh({ file: cachePath(paths().root) }).then(() => process.exit(0), () => process.exit(0));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ccoalm/ccl-skills",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Self-contained CCL Skills installer for Claude Code, Codex, and OpenCode",
|
|
5
5
|
"keywords": ["agent-skills", "claude-code", "codex", "opencode", "developer-tools"],
|
|
6
6
|
"type": "module",
|