@maccesar/aiskills 1.18.1 → 1.20.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.
@@ -0,0 +1,57 @@
1
+ # Migrating a package to trusted publishing
2
+
3
+ Done once per package. The order matters, because two of the steps can only be done by the package owner with an interactive 2FA challenge, and one of them is easy to test cheaply before it can cost a bad publish.
4
+
5
+ ## 1. Audit what is there
6
+
7
+ Before changing anything, establish the starting state — this is what `scripts/auditar_npm.py` reports, and each finding maps to a step below:
8
+
9
+ - **Tokens on disk**: `~/.npmrc` and any project `.npmrc`. An `_authToken` line from before December 2025 is dead weight producing confusing 401s [source: authentication.md].
10
+ - **Actions secrets**: anything named like an npm credential (`NPM_TOKEN` and variants). A secret no workflow references is orphaned — a live credential with nothing watching it.
11
+ - **Existing workflows**: which ones publish, and whether they authenticate with a token or with OIDC.
12
+ - **Version files**: `package.json`, and `.claude-plugin/plugin.json` if the repo also ships a Claude Code plugin. They feed the guard in step 2.
13
+ - **Install-time scripts** in the dependency tree, which is a different question but the same audit [source: install-defaults.md].
14
+
15
+ ## 2. Add the workflow and push it
16
+
17
+ Copy `assets/publish.yml`, adjust the version files in the guard to match the repo, commit, push to the default branch. Details of each line are in [trusted-publishing.md](trusted-publishing.md).
18
+
19
+ The file must be on GitHub before the registration points at it, and before any tag is pushed.
20
+
21
+ ## 3. Register the trusted publisher
22
+
23
+ npmjs.com → the package → **Settings → Trusted Publisher → GitHub Actions**, with the four fields from [trusted-publishing.md](trusted-publishing.md). Read the repository name from the GitHub API rather than from the local folder name — see [verification.md](verification.md) for the command.
24
+
25
+ **Only the package owner can do this**, interactively, with 2FA. It is one of the operations 2FA-bypass tokens lost in July 2026 [source: authentication.md]. Hand over the exact field values; do not attempt it on their behalf.
26
+
27
+ If the package is configured as *"Require two-factor authentication and disallow tokens"*, leave that setting alone. Whether it coexists with OIDC publishing is not established here.
28
+
29
+ ## 4. Test the guard before trusting it
30
+
31
+ The version guard is the one part of the workflow that can be verified without publishing anything. Run its logic locally against a tag that should pass and one that should fail:
32
+
33
+ ```bash
34
+ tag="1.18.1"; node -p "require('./package.json').version" # expect a match
35
+ tag="9.9.9"; node -p "require('./package.json').version" # expect a mismatch → the job must exit 1
36
+ ```
37
+
38
+ A guard that passes everything is not a guard. Confirm both directions before relying on it — the same discipline as running a positive control on any measuring instrument.
39
+
40
+ ## 5. First real release
41
+
42
+ Push a tag and watch the run rather than assuming it. The verification commands are in [verification.md](verification.md). Three things distinguish a real success from a plausible one: the workflow run is green, the registry (not the local CLI cache) reports the new version, and npmjs.com lists the publisher as **GitHub Actions** rather than a username.
43
+
44
+ If it fails, nothing is lost: `npm login` plus a manual publish is still the fallback, and it stays available permanently [source: authentication.md].
45
+
46
+ ## 6. Clean up what the old flow left behind
47
+
48
+ - **Orphaned Actions secrets.** `gh secret delete NPM_TOKEN --repo <owner>/<repo>` once nothing references it. An unused publish credential is strictly a liability.
49
+ - **Dead `_authToken` in `~/.npmrc`.** `npm logout` removes it. Not dangerous, but it is what makes `npm whoami` answer 401 on a machine that looks logged in.
50
+ - **Empty workflow directories** left by a removed CI file, and any documentation that still tells the reader to run `npm publish` by hand or to set a token.
51
+ - **Expired tokens on npmjs.com.** They do nothing, and they hide the one entry that matters in the list.
52
+
53
+ Delete secrets only with the user's agreement, and never print a secret's value — listing names is enough to reason about orphans.
54
+
55
+ ## What the release flow becomes
56
+
57
+ `/release` still does everything it did: semantic commits, version bump, CHANGELOG, tag, push. The publish is what moved — it now happens because the tag landed, not because someone typed `npm publish`. Nothing needs to run by hand, and the release is not finished until that workflow run is green.
@@ -0,0 +1,79 @@
1
+ # Trusted publishing (OIDC)
2
+
3
+ The runner proves who it is instead of carrying a secret. GitHub Actions mints a short-lived OIDC credential describing the exact repository, workflow and ref that is running; npm checks that description against the trusted publisher registered for the package and, if it matches, accepts the publish. Nothing is stored anywhere: there is no token to rotate, no secret to leak, no session to keep alive.
4
+
5
+ Two consequences worth stating up front: **provenance is attached automatically** to a publish made this way — npm links the published version to the exact commit and workflow run — and the registry records the publisher as **GitHub Actions** rather than a person.
6
+
7
+ ## The registration on npmjs.com
8
+
9
+ Per package, under **Settings → Trusted Publisher → GitHub Actions**:
10
+
11
+ | Field | Value |
12
+ |---|---|
13
+ | Organization or user | the GitHub owner, e.g. `macCesar` |
14
+ | Repository | the repository name **as GitHub spells it** |
15
+ | Workflow filename | the file's basename with extension, e.g. `publish.yml` |
16
+ | Environment | empty, unless the job declares `environment:` |
17
+
18
+ Three ways this goes wrong:
19
+
20
+ - **The `Repository` field is the canonical repository name, not the local folder name.** A project cloned into `~/Developer/TiTools` can live at `github.com/macCesar/titools`; the OIDC claim carries the canonical one, and a mismatch fails the publish with an authorization error that says nothing about capitalization. Read it from the API rather than from the filesystem — see [verification.md](verification.md).
21
+ - **The workflow filename must match exactly**, extension included. Renaming the file later breaks publishing until the registration on npmjs.com is updated, and the break surfaces only on the next release.
22
+ - **Registering it requires an interactive 2FA challenge.** Changing the trusted publishing configuration is one of the operations 2FA-bypass tokens lost on 31 July 2026 [source: authentication.md]. This is a step only the package owner can do; it cannot be automated or done on their behalf.
23
+
24
+ ## Order of operations
25
+
26
+ 1. Commit and **push the workflow to the default branch** first. The registration points at a file; the file should exist.
27
+ 2. Register the publisher on npmjs.com.
28
+ 3. Push a tag.
29
+
30
+ Doing 2 before 1 is not fatal, but it leaves a registration pointing at nothing and the first release fails for a reason that looks like the registration is wrong.
31
+
32
+ ## The workflow
33
+
34
+ The template lives at `assets/publish.yml` in this skill. The parts that matter:
35
+
36
+ ```yaml
37
+ permissions:
38
+ contents: read
39
+ id-token: write # without this there is no OIDC token and npm falls back to token auth
40
+ ```
41
+
42
+ `id-token: write` is the whole switch. Omit it and the publish does not error with "you forgot a permission" — it degrades to looking for a token, finds none, and fails as a credentials problem.
43
+
44
+ ```yaml
45
+ - uses: actions/setup-node@v7
46
+ with:
47
+ node-version: '24'
48
+ registry-url: 'https://registry.npmjs.org'
49
+ ```
50
+
51
+ **Minimums: Node ≥ 22.14.0 and npm ≥ 11.5.1.** Older combinations do not know how to present the OIDC credential and quietly fall back to token auth. `registry-url` is what makes `setup-node` write the `.npmrc` pointing at the public registry.
52
+
53
+ **Do not set `NODE_AUTH_TOKEN`.** It is the environment variable `setup-node` wires up for token auth; present, it puts the publish back on a secret and gives up provenance. A trusted-publishing workflow has no `env:` block on the publish step and no `secrets.*` reference anywhere.
54
+
55
+ Pin the actions. `actions/checkout` and `actions/setup-node` are on v7 as of August 2026.
56
+
57
+ ## The version guard
58
+
59
+ The template compares the pushed tag against every version file in the repo before publishing anything:
60
+
61
+ ```bash
62
+ tag="${GITHUB_REF_NAME#v}"
63
+ pkg=$(node -p "require('./package.json').version")
64
+ [ "$tag" = "$pkg" ] || { echo "::error::tag $tag does not match package.json $pkg"; exit 1; }
65
+ ```
66
+
67
+ It exists because a tag and a manifest can disagree and npm will happily publish the manifest's version under a tag that says something else. In a repo that also ships a Claude Code plugin, `.claude-plugin/plugin.json` is a second version file and belongs in the same check — TiTools published 2.6.0 to npm while its `plugin.json` still read 3.0.0, and the marketplace announced a version that did not exist.
68
+
69
+ Costs one step, removes the whole class of mistake, and fails before `npm ci` so a bad tag never reaches the registry.
70
+
71
+ ## What the release flow looks like afterwards
72
+
73
+ ```
74
+ /release → creates the tag and pushes it → Actions publishes
75
+ ```
76
+
77
+ No `npm login`, no two-hour session, no OTP. The human checkpoint does not disappear; it moves to the tag, which is still created deliberately after reviewing the diff.
78
+
79
+ If a publish fails for any reason, nothing is trapped: `npm login` and a manual `npm publish` remain available as they always were [source: authentication.md].
@@ -0,0 +1,84 @@
1
+ # Verification
2
+
3
+ Every claim in this area is checkable in one command, which is the reason none of them should be asserted from memory. This file is the list of those commands and, for each, what a wrong answer looks like.
4
+
5
+ ## The repository's canonical name
6
+
7
+ The trusted publisher matches the repository as GitHub spells it, which is not necessarily how the folder is spelled on disk:
8
+
9
+ ```bash
10
+ gh api repos/<owner>/<repo> --jq .full_name
11
+ ```
12
+
13
+ Run it before handing the user any value to type into a form. A folder named `TiTools` whose repository is `macCesar/titools` produces an OIDC claim that never matches a registration typed from the filesystem, and the resulting failure says nothing about capitalization.
14
+
15
+ This is the general rule for anything the user has to copy by hand: verify the exact string first, then hand it over.
16
+
17
+ ## Credentials, without reading them
18
+
19
+ ```bash
20
+ gh secret list --repo <owner>/<repo> # names and dates only, never values
21
+ grep -rl "secrets\." .github/workflows/ # which workflows reference any secret
22
+ npm whoami # 401 means the local session or token is dead
23
+ ```
24
+
25
+ A secret that appears in `gh secret list` and in no workflow is orphaned. Names and timestamps are enough to establish that; the value is never needed and must never be printed.
26
+
27
+ ## Whether a workflow publishes, and how
28
+
29
+ ```bash
30
+ grep -l "npm publish" .github/workflows/*.yml
31
+ grep -n "id-token\|NODE_AUTH_TOKEN\|NPM_TOKEN" .github/workflows/*.yml
32
+ ```
33
+
34
+ `id-token: write` present and no token reference means OIDC. A `NODE_AUTH_TOKEN` or `NPM_TOKEN` reference means token auth, regardless of what the file is named or what a comment claims.
35
+
36
+ ## The registry, not the CLI cache
37
+
38
+ ```bash
39
+ npm view <pkg> version # ← do not report this as the state of the registry
40
+ curl -s https://registry.npmjs.org/<pkg> | python3 -c "import json,sys; d=json.load(sys.stdin); print(d['dist-tags'])"
41
+ ```
42
+
43
+ **`npm view` is cached and it lies.** Measured on 2026-08-14: minutes after `@maccesar/aiskills` 1.18.1 published successfully, the local `npm view` still answered 1.18.0 while a direct request to the registry already returned 1.18.1. Reporting the cached answer as a failed publish sends someone to debug a release that worked.
44
+
45
+ For a scoped package the URL encodes the slash: `https://registry.npmjs.org/@maccesar%2Faiskills`.
46
+
47
+ ## Provenance and the publisher
48
+
49
+ ```bash
50
+ npm view <pkg> --json | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('dist',{}).get('attestations'))"
51
+ ```
52
+
53
+ The visual signal is stronger and takes one glance: on npmjs.com, the package's version list shows **`GitHub Actions`** in the publisher column where a manually published package shows a username. That is trusted publishing made visible — npm recorded that a workflow identified by OIDC uploaded the package — and it is what backs the provenance badge.
54
+
55
+ ## The workflow run behind a tag
56
+
57
+ A pushed tag is not a publish. It starts a run, and the run can fail on the version guard, on the tests, or on a publisher that was never registered:
58
+
59
+ ```bash
60
+ gh run watch # follow the run started by the tag
61
+ gh run list --workflow=publish.yml --limit 5 # after the fact
62
+ ```
63
+
64
+ A release is finished when that run is green and the registry confirms the version — not when `git push origin v1.2.3` returns.
65
+
66
+ ## Badges
67
+
68
+ A shields.io badge asks the registry for a package by name. If the name is wrong the badge renders "package not found" rather than erroring, so a broken badge survives for months:
69
+
70
+ ```bash
71
+ curl -s -o /dev/null -w "%{http_code}\n" https://registry.npmjs.org/<name-exactly-as-the-badge-spells-it>
72
+ ```
73
+
74
+ The failure mode is specific to **scoped packages**: a README badge that says `npm/dm/aiskills` for a package published as `@maccesar/aiskills` looks plausible and reports nothing. It hid a real number here — 568 downloads per month — across three badges for months. An unscoped package cannot have this bug, since the badge name and the package name are the same string.
75
+
76
+ npmjs.com renders the README of the **published version**, so correcting a badge requires a release, not just a push to the default branch.
77
+
78
+ ## Install-time scripts in the tree
79
+
80
+ ```bash
81
+ npm approve-scripts --allow-scripts-pending # authoritative: what npm itself would ask about
82
+ ```
83
+
84
+ Reading each dependency's `scripts` block is the offline approximation and is what the auditor in this skill does; the command above is what npm actually evaluates [source: install-defaults.md].