optbrew 0.1.3 → 0.2.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +2 -2
- package/AGENT_INSTALL.md +13 -11
- package/CHANGELOG.md +52 -0
- package/README.ko.md +40 -15
- package/README.md +40 -15
- package/THIRD_PARTY_NOTICES.md +88 -0
- package/docs/platforms.md +10 -10
- package/docs/release.md +37 -5
- package/lib/cli.mjs +5 -2
- package/lib/install.mjs +2 -1
- package/lib/records.mjs +2 -1
- package/lib/transaction.mjs +12 -5
- package/lib/update.mjs +3 -2
- package/package.json +6 -4
- package/plugin.json +2 -2
- package/skills/.claude-plugin/icon.png +0 -0
- package/skills/.claude-plugin/plugin.json +23 -0
- package/skills/README.md +27 -0
- package/skills/optbrew/SKILL.md +223 -8
- package/skills/optbrew/THIRD_PARTY_NOTICES.md +88 -0
- package/skills/optbrew/bundle-manifest.json +407 -47
- package/skills/optbrew/modules/naver-searchadvisor/scripts/audit.mjs +4 -4
- package/skills/optbrew/references/api.md +5 -3
- package/skills/optbrew/references/check-registry.json +16 -16
- package/skills/optbrew/references/chrome-paths.json +31 -0
- package/skills/optbrew/references/endpoints.json +10 -0
- package/skills/optbrew/references/harness-signals.json +5 -0
- package/skills/optbrew/references/intake.md +76 -5
- package/skills/optbrew/references/judging.md +233 -0
- package/skills/optbrew/references/limits.json +26 -0
- package/skills/optbrew/references/measurement.md +1 -1
- package/skills/optbrew/references/messages.ko.json +170 -0
- package/skills/optbrew/references/report.md +18 -6
- package/skills/optbrew/references/routing.md +2 -2
- package/skills/optbrew/references/runbooks/anthropic-api-key.md +34 -0
- package/skills/optbrew/references/runbooks/chrome-install.md +56 -0
- package/skills/optbrew/references/runbooks/cwv-site-collection.md +38 -0
- package/skills/optbrew/references/runbooks/google-api-key.md +51 -0
- package/skills/optbrew/references/runbooks/gsc-export.md +32 -0
- package/skills/optbrew/references/runbooks/index.json +18 -0
- package/skills/optbrew/references/runbooks/key-set-claude-cloud.md +32 -0
- package/skills/optbrew/references/runbooks/key-set-codex.md +33 -0
- package/skills/optbrew/references/runbooks/key-set-local.md +40 -0
- package/skills/optbrew/references/runbooks/naver-searchadvisor-export.md +32 -0
- package/skills/optbrew/references/runbooks/network-claude-cloud-custom.md +33 -0
- package/skills/optbrew/references/runbooks/network-codex-approval.md +26 -0
- package/skills/optbrew/references/runbooks/network-codex-config.md +36 -0
- package/skills/optbrew/references/runbooks/seranking.md +38 -0
- package/skills/optbrew/references/schemas/contract.schema.json +42 -15
- package/skills/optbrew/references/scoring.md +40 -5
- package/skills/optbrew/references/source-analysis.md +40 -0
- package/skills/optbrew/references/versions.json +9 -0
- package/skills/optbrew/scripts/adapters/anthropic.mjs +5 -5
- package/skills/optbrew/scripts/adapters/imports.mjs +138 -1
- package/skills/optbrew/scripts/audit.mjs +10 -112
- package/skills/optbrew/scripts/cli.mjs +5 -2
- package/skills/optbrew/scripts/core/bundle.mjs +30 -13
- package/skills/optbrew/scripts/core/capabilities.mjs +8 -4
- package/skills/optbrew/scripts/core/cdp.mjs +105 -0
- package/skills/optbrew/scripts/core/checks/canonical-consistency.mjs +56 -0
- package/skills/optbrew/scripts/core/checks/cwv-field.mjs +77 -0
- package/skills/optbrew/scripts/core/checks/discovery.mjs +43 -0
- package/skills/optbrew/scripts/core/checks/document-title.mjs +62 -0
- package/skills/optbrew/scripts/core/checks/entity-identity.mjs +31 -0
- package/skills/optbrew/scripts/core/checks/fact-consistency.mjs +31 -0
- package/skills/optbrew/scripts/core/checks/fetch-response.mjs +18 -0
- package/skills/optbrew/scripts/core/checks/index-policy.mjs +16 -0
- package/skills/optbrew/scripts/core/checks/index.mjs +18 -0
- package/skills/optbrew/scripts/core/checks/internal-navigation.mjs +67 -0
- package/skills/optbrew/scripts/core/checks/locale-links.mjs +55 -0
- package/skills/optbrew/scripts/core/checks/naver-markup.mjs +21 -0
- package/skills/optbrew/scripts/core/checks/observation.mjs +37 -0
- package/skills/optbrew/scripts/core/checks/prepare.mjs +103 -0
- package/skills/optbrew/scripts/core/checks/render-content.mjs +104 -0
- package/skills/optbrew/scripts/core/checks/search-crawl-policy.mjs +29 -0
- package/skills/optbrew/scripts/core/checks/structured-data-truth.mjs +46 -0
- package/skills/optbrew/scripts/core/chrome-temp.mjs +68 -0
- package/skills/optbrew/scripts/core/chrome.mjs +546 -0
- package/skills/optbrew/scripts/core/collector.mjs +45 -49
- package/skills/optbrew/scripts/core/compare.mjs +18 -1
- package/skills/optbrew/scripts/core/config.mjs +31 -0
- package/skills/optbrew/scripts/core/contracts.mjs +31 -2
- package/skills/optbrew/scripts/core/cwv.mjs +100 -0
- package/skills/optbrew/scripts/core/derive.mjs +10 -186
- package/skills/optbrew/scripts/core/evidence-store.mjs +112 -0
- package/skills/optbrew/scripts/core/facts.mjs +175 -17
- package/skills/optbrew/scripts/core/findings.mjs +19 -1
- package/skills/optbrew/scripts/core/html.mjs +41 -3
- package/skills/optbrew/scripts/core/index-targets.mjs +118 -0
- package/skills/optbrew/scripts/core/judgments.mjs +675 -0
- package/skills/optbrew/scripts/core/keys.mjs +86 -0
- package/skills/optbrew/scripts/core/link-graph.mjs +93 -0
- package/skills/optbrew/scripts/core/messages.mjs +23 -0
- package/skills/optbrew/scripts/core/metrics.mjs +4 -2
- package/skills/optbrew/scripts/core/migration.mjs +37 -0
- package/skills/optbrew/scripts/core/page-select.mjs +136 -0
- package/skills/optbrew/scripts/core/probe.mjs +83 -0
- package/skills/optbrew/scripts/core/protocol.mjs +38 -14
- package/skills/optbrew/scripts/core/render.mjs +160 -0
- package/skills/optbrew/scripts/core/report-compose.mjs +377 -0
- package/skills/optbrew/scripts/core/report-html.mjs +198 -0
- package/skills/optbrew/scripts/core/report-page.mjs +14 -0
- package/skills/optbrew/scripts/core/report.mjs +327 -122
- package/skills/optbrew/scripts/core/runbooks.mjs +63 -0
- package/skills/optbrew/scripts/core/scope.mjs +23 -5
- package/skills/optbrew/scripts/core/score.mjs +12 -4
- package/skills/optbrew/scripts/core/secrets.mjs +2 -0
- package/skills/optbrew/scripts/core/stages.mjs +694 -0
- package/skills/optbrew/scripts/core/transport.mjs +157 -0
- package/skills/optbrew/scripts/core/weights.mjs +2 -1
- package/skills/optbrew/scripts/key.mjs +90 -0
- package/skills/optbrew/scripts/preflight.mjs +176 -0
- package/skills/optbrew/scripts/run.mjs +81 -0
- package/skills/optbrew/source-lock.json +1 -1
- package/source-lock.json +1 -1
package/docs/platforms.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Platform installation and update paths
|
|
2
2
|
|
|
3
|
-
Optbrew v0.
|
|
3
|
+
Optbrew v0.2.0 contains one skill source at `skills/optbrew`. The managed CLI can place it at five hosts' documented default paths. A file in a host path proves placement, not that the host discovered, loaded, or invoked the skill. Confirm those three steps in a fresh host session before reporting host support as exercised.
|
|
4
4
|
|
|
5
5
|
| Host | Managed project target | Managed user target | Discovery and invocation boundary |
|
|
6
6
|
|---|---|---|---|
|
|
@@ -45,10 +45,10 @@ Bash and PowerShell use the same commands:
|
|
|
45
45
|
```text
|
|
46
46
|
node bin/optbrew.mjs check-updates --json
|
|
47
47
|
node bin/optbrew.mjs update --scope project --project ./site --json
|
|
48
|
-
node bin/optbrew.mjs rollback --scope project --project ./site --release-version 0.
|
|
48
|
+
node bin/optbrew.mjs rollback --scope project --project ./site --release-version 0.2.0 --json
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
Rollback works only between releases that share the evaluation version, so no release before 0.2.0 can be rolled back to. The shown command reports that no earlier release is retained on a fresh 0.2.0 install; it applies once a later release that shares evaluation version 0.4.0 has updated the install.
|
|
52
52
|
|
|
53
53
|
The CLI validates bundle hashes before an install or update. `update` replaces every target in one managed record as a unit. An edited target makes the default update stop; the explicit `--local-changes backup-and-replace` policy retains a readable backup. `rollback --release-version VERSION` works only when that version is in the record's retained prior releases. A fresh installation has no older release.
|
|
54
54
|
|
|
@@ -56,9 +56,9 @@ After a public npm release, `npm install -g optbrew` and `npm update -g optbrew`
|
|
|
56
56
|
|
|
57
57
|
## Reinstall after a migration_required update
|
|
58
58
|
|
|
59
|
-
Release 0.1.3
|
|
59
|
+
Release 0.2.0 uses evaluation version 0.4.0, audit protocol 0.2.0 and contract schema 2.0.0. Release 0.1.3 used evaluation version 0.3.0 and 0.1.2 used 0.2.0. The managed updater refuses a change of evaluation version or of a scoring contract file, so `optbrew update` stops with `migration_required` and changes nothing on a managed install of any 0.1.x release. There is no `uninstall` command. Replace the install by hand: the blocks below delete the install's targets and then its managed root, and run `install` again from the 0.2.0 checkout. Scores and baselines made with a 0.1.x release are not comparable with 0.2.0; `compare` returns `not_comparable`, so run the baseline audit again. An output folder or evidence packet from a 0.1.x run is refused with one sentence that tells you to run the audit again in a new folder. A plugin install from a marketplace is not a managed install: it follows the repository and needs no reinstall.
|
|
60
60
|
|
|
61
|
-
The blocks do not trust the install record. Each one first runs the read-only `node bin/optbrew.mjs reinstall-plan`, which resolves the home directory the way the CLI does (`HOME`, else the operating system's home directory), checks that the record belongs to this scope, that every recorded target is exactly the agent's `skills/optbrew` path (absolute, no `..`, no linked folder on the way, inside the scope or project), and that an existing target is a link to the managed bundle or a real folder holding a bundle manifest. If any check fails it prints the reason, exits with a nonzero code, and the block deletes nothing. Otherwise the shell deletes only the paths it printed: the targets first, then `<scope root>/.optbrew/installs/user` (user scope) or `<project>/.optbrew/install` (project scope), which holds the install record. Because the record goes last, you can run the same block again after a failure. The commands never touch `<scope root>/.optbrew/runs`, where
|
|
61
|
+
The blocks do not trust the install record. Each one first runs the read-only `node bin/optbrew.mjs reinstall-plan`, which resolves the home directory the way the CLI does (`HOME`, else the operating system's home directory), checks that the record belongs to this scope, that every recorded target is exactly the agent's `skills/optbrew` path (absolute, no `..`, no linked folder on the way, inside the scope or project), and that an existing target is a link to the managed bundle or a real folder holding a bundle manifest. If any check fails it prints the reason, exits with a nonzero code, and the block deletes nothing. Otherwise the shell deletes only the paths it printed: the targets first, then `<scope root>/.optbrew/installs/user` (user scope) or `<project>/.optbrew/install` (project scope), which holds the install record. Because the record goes last, you can run the same block again after a failure. The commands never touch `<scope root>/.optbrew/runs`, where the audit evidence of 0.1.x lives, and never touch an `optbrew-audit` folder; do not delete the whole `.optbrew` folder, which also holds the Google key file. Each agent is installed again with the strategy (`link` or `copy`) the record had, then `doctor` runs. Run the blocks from the 0.2.0 checkout root. If `reinstall-plan` refuses the record, fix or remove the named path by hand and look at it first; the CLI also refuses to overwrite a target it does not own.
|
|
62
62
|
|
|
63
63
|
User scope:
|
|
64
64
|
|
|
@@ -128,17 +128,17 @@ while IFS=$'\t' read -r kind a b; do
|
|
|
128
128
|
done <<< "$plan"
|
|
129
129
|
```
|
|
130
130
|
|
|
131
|
-
Inside 0.
|
|
131
|
+
Inside 0.2.0 and later releases that keep evaluation version 0.4.0 and the scoring contract files, `update` and `rollback` work as before. The same refusal and steps apply to any future release that changes the evaluation version or a scoring contract file.
|
|
132
132
|
|
|
133
133
|
## Native host routes
|
|
134
134
|
|
|
135
135
|
These are alternatives to the managed CLI. Do not mix their ownership records for the same target path.
|
|
136
136
|
|
|
137
137
|
- **Agent Skills tool:** Current [Vercel Skills CLI](https://github.com/vercel-labs/skills) documents `npx skills add ./` for a local source and `npx skills update optbrew` for an installation it owns. Run from a checkout containing the skill. Confirm its host selection and destination before writing. The public repository is `https://github.com/nowwcastle-sudo/optbrew`.
|
|
138
|
-
- **Claude Code plugin:** `.claude-plugin/marketplace.json` and `plugin.json`
|
|
139
|
-
- **Codex plugin:** `.agents/plugins/marketplace.json` lists the same `optbrew` plugin. Run `codex plugin marketplace add nowwcastle-sudo/optbrew`, then `codex plugin add optbrew@optbrew`, or choose it in `/plugins`. Start a new Codex task afterwards.
|
|
140
|
-
- **Pi package:** Current [Pi package instructions](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/packages.md) document `pi install ./local-package`, `pi install npm:package-name@version`, `pi list`, and `pi update --extensions`. `pi install ./` installs from a local checkout when Pi is installed; `pi install npm:optbrew@0.
|
|
141
|
-
- **Claude web:**
|
|
138
|
+
- **Claude Code plugin:** `.claude-plugin/marketplace.json` names the plugin `optbrew@optbrew` and serves the `skills/` folder, whose `skills/.claude-plugin/plugin.json` lists `./optbrew`. Neither file pins a version, so each commit to `main` is a new plugin version. Run `claude plugin marketplace add nowwcastle-sudo/optbrew`, then `claude plugin install optbrew@optbrew` (add `--scope project` to keep it in one project). Later, `claude plugin update optbrew@optbrew` applies the marketplace's newer version and requires a restart. `claude plugin validate .` checks the marketplace and the served plugin from a checkout. The root `.claude-plugin/plugin.json` (version 0.2.0) still loads the whole checkout with `claude --plugin-dir .`. [Claude Code plugin instructions](https://code.claude.com/docs/en/plugins) also describe the interactive `/plugin` path. Marketplace auto-update is a user setting; the marketplace file does not enable it. Verify a fresh-session invocation after a native update.
|
|
139
|
+
- **Codex plugin:** `.agents/plugins/marketplace.json` lists the same `optbrew` plugin. Run `codex plugin marketplace add nowwcastle-sudo/optbrew`, then `codex plugin add optbrew@optbrew`, or choose it in `/plugins`. Start a new Codex task afterwards.
|
|
140
|
+
- **Pi package:** Current [Pi package instructions](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/packages.md) document `pi install ./local-package`, `pi install npm:package-name@version`, `pi list`, and `pi update --extensions`. `pi install ./` installs from a local checkout when Pi is installed; `pi install npm:optbrew@0.2.0` installs the published npm package. The package's `pi.skills` entry points to `./skills/optbrew`. A project-local package declaration uses Pi's `--local` option and is subject to project trust. Check the installed Pi version's help and package list before updating.
|
|
141
|
+
- **Claude web (claude.ai, Claude Desktop, Cowork):** In Customize > Plugins, choose Add > Add marketplace and enter `nowwcastle-sudo/optbrew`, then add `optbrew`. The plugin is saved to your account, so cloud Claude Code sessions receive it too. Turn on Sync automatically for the marketplace, or select Check for updates, to pick up new commits. The marketplace serves `skills/` because claude.ai refuses a plugin with a top-level `bin/` folder. Uploading a ZIP of `skills/` (Add > Upload plugin) also works, but each update needs a new upload.
|
|
142
142
|
- **Ordinary ChatGPT chat:** Attach the relevant skill files or ZIP and give the assistant the target and scope. This is attachment-guided use. It is not the Codex installation path and does not establish Work/desktop plugin discovery.
|
|
143
143
|
|
|
144
144
|
The native syntax above comes from host documentation checked on 2026-10-14. It is documentation evidence, not proof that an installed host accepted the package or called the skill. No host registration, OAuth, account setting, or user-level install is performed by the documentation checks.
|
package/docs/release.md
CHANGED
|
@@ -1,15 +1,47 @@
|
|
|
1
1
|
# Release procedure
|
|
2
2
|
|
|
3
|
-
This checkout is version 0.
|
|
3
|
+
This checkout is version 0.2.0. A package name, local manifest, or generated ZIP does not establish a public release. Publication, a Git tag, a push, and account changes require their own authorization. This page describes the reviewable local release artifacts and the checks to run before that decision.
|
|
4
4
|
|
|
5
5
|
1. Confirm the source commit and clean intended diff. Match `package.json`, the portable plugin, Codex plugin, Claude plugin, marketplace entry, and ZIP name to one release version. Keep `evaluation_version` and `protocol_version` separate from the release version.
|
|
6
6
|
2. Run `npm test` and `python -m unittest discover -s tests/python` on Python 3.10 or later. Run the focused `node --test tests/docs-commands.test.mjs` check when changing examples. The command check runs optbrew against isolated local fixtures; it makes no provider call.
|
|
7
|
-
3. Run `npm run build-bundle`, `node scripts/build-release.mjs --version 0.
|
|
7
|
+
3. Build from a checkout made with `git clone -c core.autocrlf=false`. The root and `skills/` carry no `.gitattributes` (the Claude plugin directory refuses one there), so with `core.autocrlf=true` the skill files are CRLF in the working tree; the bundle hashes still match, but the ZIP and npm verifiers compare exact bytes and refuse them. Run `npm run build-bundle`, `node scripts/build-release.mjs --version 0.2.0 --output-dir dist`, `npm pack --pack-destination dist`, `node scripts/verify-release.mjs --dist dist`, and `npm pack --dry-run --json` from the repository root. Rebuild after changing the README or packaged files. Do not treat an older tarball as containing a later documentation edit.
|
|
8
8
|
4. Inspect the npm file list, the ZIP file list, `bundle-manifest.json`, `source-lock.json`, licenses, and secret scan output. The same skill bytes and version must be traceable across the npm package and ZIP. Source notices belong in both. User run evidence, account configuration, keys, private cards, and authoring materials must stay outside both.
|
|
9
9
|
5. Test a fresh isolated managed install, `doctor`, update from a retained earlier release, rollback, and fresh host discovery or invocation where a host is available. A dry run or manifest check does not prove invocation. Record the untested hosts and capabilities.
|
|
10
|
-
6.
|
|
11
|
-
7.
|
|
10
|
+
6. Confirm CI on the exact commit that will be tagged. That commit exists only after the merge: `main` takes changes through a pull request with linear history, so the merge makes a commit that no earlier run has seen, and runs on the release branch or the pull request do not count. Do this after the merge of step 8 and before its tag. The `test` workflow (`.github/workflows/test.yml`) runs `npm test` on `ubuntu-latest` and on `windows-latest` with `core.autocrlf=true`, Chrome integration tests included, and its last step (`scripts/check-skips.mjs`) fails when a Chrome test was skipped or another test was skipped for a reason that is not listed there. The push to `main` starts the first run. Start two more on the same commit, one after the other, each when the previous one has finished (the workflow keeps one run per commit at a time): `gh workflow run test.yml --ref main`. Then `gh run list --workflow test.yml --branch main --commit <merge commit sha> --json event,conclusion,headSha,url` must list three runs (the `push` run and two `workflow_dispatch` runs) with `conclusion` `success`, each green on both systems. Do not rerun a red run until it turns green: one failed Chrome test (a leftover `gpu-process` of its own Chrome was seen once under load) is a flake to investigate. Read the two `Point optbrew at the runner's Chrome` steps of the newest run and add the Chrome version and path each runner printed to [Chrome locations and DevTools names](#chrome-locations-and-devtools-names-2026-10-16) with the date; also record whether the first Ubuntu run needed any sandbox workaround.
|
|
11
|
+
7. Only after a reviewed release decision, and only after step 6 is green on the merge commit, publish or upload the approved artifacts and verify the public npm and repository readback. Do not infer publication from successful local builds. The owner runs these actions, not an agent: `git push`, the merge, the tag and its push, `gh release create`, and `npm publish dist/optbrew-X.Y.Z.tgz`. An agent prepares the artifacts and reads the results back.
|
|
12
|
+
8. `main` is protected by a repository ruleset: no force push, no deletion, changes only through a pull request, linear history. Tags matching `v*` cannot be moved or deleted. Merge the release branch through a pull request, then tag the merge commit `vX.Y.Z`. The README's PowerShell installer and agent prompt load `install.ps1` and `AGENT_INSTALL.md` from that tag, so test both raw URLs after the tag is pushed. Do not push the tag before step 6 is green for that merge commit.
|
|
13
|
+
9. A tag alone is not a GitHub Release. Publish one for the tag, with the CHANGELOG section as notes and the reviewed ZIP attached: `gh release create vX.Y.Z dist/<zip> --title vX.Y.Z --notes-file <notes.md>`. Read it back: `gh api repos/nowwcastle-sudo/optbrew/releases --jq '.[].tag_name'` must list the tag, and `npm view optbrew dist-tags` must show the same version as `latest`.
|
|
14
|
+
10. The Claude marketplace and directory sync on a push to `main`, sent by the repository webhook. After the merge, confirm that the webhook got a push for `refs/heads/main` with the merge commit: `gh api repos/nowwcastle-sudo/optbrew/hooks/<id>/deliveries` lists the delivery, and `.../deliveries/<delivery id>` shows `request.payload.ref` and `request.payload.after`. A branch deletion is answered `ignored_delete` and does not sync. Then check that the claude.ai copy shows the merge commit.
|
|
15
|
+
11. The Codex marketplace file is `.agents/plugins/marketplace.json`. After the merge, in a Codex that has the marketplace (`codex plugin marketplace add nowwcastle-sudo/optbrew` adds it), run `codex plugin marketplace upgrade`, then `codex plugin list --marketplace optbrew` and check that `optbrew` is listed as available (`--json` gives a machine-readable form). A listing shows that the marketplace file is readable. It does not show that a Codex task discovers the skill: start a new Codex task, invoke the skill, and record the Codex version used (`codex --version`).
|
|
12
16
|
|
|
13
17
|
The bundle includes five pinned source bundles plus seven selected marketing guides. The Naver source articles under `references/guide/` (55 original bodies) remain excluded; the index links to official pages and full offline article lookup is unavailable. The published notice must retain upstream MIT/ISC permission texts and modifications. Optbrew's own code uses MIT. These code licenses do not grant service subscriptions or API data rights.
|
|
14
18
|
|
|
15
|
-
The direct Anthropic adapter
|
|
19
|
+
The direct Anthropic adapter keeps its text-only scope and four model IDs in [API boundaries](../skills/optbrew/references/api.md). No live paid result, search indexing, traffic change, or customer adoption is established by a passing package check. See [platform paths](platforms.md) for update mechanisms after release.
|
|
20
|
+
|
|
21
|
+
## Confirmation records
|
|
22
|
+
|
|
23
|
+
Values the code relies on that were confirmed from a primary source, with the date of confirmation. Re-confirm before changing the matching file.
|
|
24
|
+
|
|
25
|
+
### Chrome locations and DevTools names (2026-10-16)
|
|
26
|
+
|
|
27
|
+
Confirmed for `skills/optbrew/references/chrome-paths.json` and `skills/optbrew/scripts/core/chrome.mjs`:
|
|
28
|
+
|
|
29
|
+
- Windows `\Google\Chrome\Application\chrome.exe` and `\Microsoft\Edge\Application\msedge.exe`, tried under `%ProgramFiles%`, `%ProgramFiles(x86)%` and `%LocalAppData%`; macOS `/Applications/Google Chrome.app/Contents/MacOS/Google Chrome` and `/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge`; Linux `/opt/google/chrome/chrome` and `/opt/microsoft/msedge/msedge`. Source: Playwright's channel table and Windows prefix list, https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/registry/index.ts (read 2026-10-16). The Windows prefixes (`LOCALAPPDATA`, `PROGRAMFILES`, `PROGRAMFILES(X86)`) also match Google's `chrome-launcher`, https://github.com/GoogleChrome/chrome-launcher/blob/main/src/chrome-finder.ts.
|
|
30
|
+
- Names searched on `PATH`: `google-chrome-stable`, `google-chrome`, `chromium-browser`, `chromium` (Google's `chrome-launcher`, same file); `microsoft-edge-stable` (Playwright's Edge install script, https://github.com/microsoft/playwright/blob/main/packages/playwright-core/bin/reinstall_msedge_stable_linux.sh); `chrome` and `msedge` (the executable names above).
|
|
31
|
+
- Playwright's Chromium: `<PLAYWRIGHT_BROWSERS_PATH>/chromium-<revision>/chrome-win64/chrome.exe`, `chrome-linux64/chrome`, `chrome-linux-arm64/chrome`, `chrome-mac-{arm64,x64}/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing`; the value `0` means a package-local directory and is ignored (same Playwright file). A Chromium path on macOS (`/Applications/Chromium.app`) was not confirmed and is not listed.
|
|
32
|
+
- `--remote-debugging-pipe`: messages are JSON texts ended by a NUL byte, Chrome reads fd 3 and writes fd 4, so the parent writes `stdio[3]` and reads `stdio[4]`. Sources: Puppeteer's `PipeTransport.ts` and `BrowserLauncher.ts` (https://github.com/puppeteer/puppeteer/tree/main/packages/puppeteer-core/src/node), and Chromium's `content/browser/devtools/devtools_pipe_handler.cc`.
|
|
33
|
+
- DevTools Protocol names (`json/browser_protocol.json` of https://github.com/ChromeDevTools/devtools-protocol, master, read 2026-10-16): `Network.ErrorReason` includes `BlockedByClient`; `Network.ResourceType` values `Document`, `Stylesheet`, `Image`, `Font`, `Script`, `XHR`, `Fetch`; `Fetch.enable` takes `patterns` and `handleAuthRequests`; `Fetch.requestPaused` carries `requestId`, `request`, `frameId`, `resourceType`, `redirectedRequestId`; `Fetch.failRequest` takes `errorReason`; `Fetch.authRequired` carries `authChallenge.source` (`Server` or `Proxy`); `Fetch.continueWithAuth` takes `authChallengeResponse.response` (`Default`, `CancelAuth`, `ProvideCredentials`) with `username` and `password`; `Target.createBrowserContext`, `createTarget`, `attachToTarget` (`flatten`), `closeTarget`, `disposeBrowserContext`; `Browser.close`; `Page.setLifecycleEventsEnabled` and the `networkIdle` lifecycle event.
|
|
34
|
+
- Auto-attach (same protocol JSON, 2026-10-16): `Target.setAutoAttach` takes `autoAttach`, `waitForDebuggerOnStart`, `flatten` and an experimental `filter` (`TargetFilter`: entries are matched in order, the first match wins, a last `{ exclude: true }` drops every other type); `Target.attachedToTarget` carries `sessionId`, `targetInfo` (`targetId`, `type`, `openerId`, `browserContextId`) and `waitingForDebugger`; a paused target runs after `Runtime.runIfWaitingForDebugger`. Observed with Chrome on 2026-10-16: a page-session auto-attach delivers iframes (cross-site ones run in their own process), dedicated workers and service workers, but not popups, which only a browser-level auto-attach delivers (so the render page claims them by `browserContextId`). A dedicated worker session answers `Fetch.enable` with "'Fetch.enable' wasn't found"; its requests are paused on the session of the frame that owns it.
|
|
35
|
+
- Unix socket path limit (Chromium `chrome/browser/process_singleton_posix.cc`, main, read 2026-10-18): the single-instance socket path must be shorter than `sizeof(sun_path)` (108 bytes on Linux; 104 on macOS) or Chrome aborts at start (`LOG(FATAL) << "Socket path too long"`); the socket sits in `<temp folder>/<org.chromium.Chromium.XXXXXX>/SingletonSocket`. `chrome-temp.mjs` therefore redirects Chrome's temp folder into the profile only while that folder path is at most 48 bytes, and otherwise leaves the user's own. First Ubuntu run (CI run 39049765483): every launch from a Hangul profile path and every render test exited at start; the cause was not read from Chrome itself, so the next failing launch prints Chrome's last stderr words in its error message.
|
|
36
|
+
|
|
37
|
+
### Host resolver rules (2026-10-16)
|
|
38
|
+
|
|
39
|
+
Confirmed for `skills/optbrew/scripts/core/render.mjs`: Chromium `net/base/host_mapping_rules.h` (chromium/chromium main, read 2026-10-16) defines the rules as a comma separated list of `MAP <hostname_pattern> <replacement_host>[:<port>]` and `EXCLUDE <hostname_pattern>`; the replacement is a hostname or an IP address literal, and a rule that maps to `^NOTFOUND` yields an invalid URL, so the request fails. Observed with the installed Chrome: a host mapped to `^NOTFOUND` fails to load with `net::ERR_NAME_NOT_RESOLVED`. The header does not say how a bracketed IPv6 replacement is parsed, so IPv6 pinning is not covered by a test.
|
|
40
|
+
|
|
41
|
+
### Harness signals (2026-10-17)
|
|
42
|
+
|
|
43
|
+
Confirmed for `skills/optbrew/references/harness-signals.json` and `skills/optbrew/scripts/core/probe.mjs`:
|
|
44
|
+
|
|
45
|
+
- Claude Code: `CLAUDECODE` is set to `1` in subprocesses Claude Code spawns (Bash and PowerShell tools, hooks, status line, stdio MCP servers; IDE extensions set it in their integrated terminals). `CLAUDE_CODE_REMOTE` is set automatically to `true` when Claude Code runs as a cloud session. Both rows are in https://code.claude.com/docs/en/env-vars (read 2026-10-17); the cloud session page, https://code.claude.com/docs/en/claude-code-on-the-web, is the cross-reference. A cloud session also has `CLAUDECODE`, so `CLAUDE_CODE_REMOTE` is tested first.
|
|
46
|
+
- Codex: `CODEX_SANDBOX_NETWORK_DISABLED` is set to `1` for a process spawned by a shell tool call when the network sandbox policy is restricted; `CODEX_SANDBOX` is set when the process runs under a sandbox (`seatbelt` on macOS, "may change" for other mechanisms). Source: openai/codex `codex-rs/core/src/spawn.rs`, https://github.com/openai/codex/blob/main/codex-rs/core/src/spawn.rs (read 2026-10-17, constants `CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR` and `CODEX_SANDBOX_ENV_VAR`; the file defines `CODEX_SANDBOX` but does not set it). The variable is marked experimental in that comment. The Codex docs page `docs/sandbox.md` names neither variable.
|
|
47
|
+
- Limits: a Codex run with full network access sets neither variable, so it is detected as `other` and gets the general runbooks. Only `CODEX_SANDBOX_NETWORK_DISABLED` makes a failed DNS lookup a `codex_sandbox` block; with only `CODEX_SANDBOX` set the same failure is `site_down`. No variable separates a Claude Code cloud network level from a down site, so a failed lookup or a refused proxy tunnel in a cloud session is `cloud_network_level`.
|
package/lib/cli.mjs
CHANGED
|
@@ -9,10 +9,12 @@ import { readFile } from 'node:fs/promises';
|
|
|
9
9
|
import { readInstallRecord } from './records.mjs';
|
|
10
10
|
import { runCli } from '../skills/optbrew/scripts/cli.mjs';
|
|
11
11
|
import { reinstallPlan } from './reinstall.mjs';
|
|
12
|
+
import { runKeyCommand } from '../skills/optbrew/scripts/key.mjs';
|
|
13
|
+
import { keyHome } from '../skills/optbrew/scripts/core/keys.mjs';
|
|
12
14
|
|
|
13
15
|
const defaultBundle = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../skills/optbrew');
|
|
14
16
|
class UsageError extends Error {}
|
|
15
|
-
const usage = 'usage: optbrew <install|doctor|check-updates|update|rollback|reinstall-plan|score|compare|report> [options]';
|
|
17
|
+
const usage = 'usage: optbrew <install|doctor|check-updates|update|rollback|reinstall-plan|score|compare|report|key> [options]';
|
|
16
18
|
|
|
17
19
|
function parseOptions(argv, allowed) {
|
|
18
20
|
const options = {};
|
|
@@ -54,9 +56,10 @@ function computeArgs(argv, command, cwd) {
|
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
/** Return CLI process code: 0 success, 2 argument error, 1 environment or file failure. */
|
|
57
|
-
export async function main(argv, { stdout = process.stdout, stderr = process.stderr, env = process.env, cwd = process.cwd(), bundleRoot = defaultBundle, registryTransport } = {}) {
|
|
59
|
+
export async function main(argv, { stdin = process.stdin, stdout = process.stdout, stderr = process.stderr, env = process.env, cwd = process.cwd(), bundleRoot = defaultBundle, registryTransport } = {}) {
|
|
58
60
|
try {
|
|
59
61
|
const [command, ...rest] = argv;
|
|
62
|
+
if (command === 'key') return await runKeyCommand(rest, { stdin, stdout, stderr, env, home: keyHome(env) });
|
|
60
63
|
if (['score', 'compare', 'report'].includes(command)) {
|
|
61
64
|
await runCli(computeArgs(rest, command, cwd));
|
|
62
65
|
stdout.write(`${command} complete\n`);
|
package/lib/install.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { cp, lstat, mkdir, open, readFile, realpath, rename, rm, symlink } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { isDistributedBundlePath, verifyBundle } from '../skills/optbrew/scripts/core/bundle.mjs';
|
|
4
|
+
import { versions } from '../skills/optbrew/scripts/core/config.mjs';
|
|
4
5
|
import { readInstallRecord, writeInstallRecord } from './records.mjs';
|
|
5
6
|
import { resolveTargets, scopePaths } from './paths.mjs';
|
|
6
7
|
|
|
@@ -104,7 +105,7 @@ export async function install({ bundleRoot, agents, scope, home, projectRoot, dr
|
|
|
104
105
|
changed.push(managedBundle);
|
|
105
106
|
} finally { await rm(staging, { recursive: true, force: true }); }
|
|
106
107
|
next = {
|
|
107
|
-
schema_version:
|
|
108
|
+
schema_version: versions().install_record_schema, scope_id: `${scope}:${scopeRoot}`, release_version: source.manifest.release_version,
|
|
108
109
|
bundle_hash: source.manifest.bundle_hash, source: path.resolve(bundleRoot), managed_root: managedRoot,
|
|
109
110
|
targets: [], file_hashes: Object.fromEntries(source.manifest.files.map(f => [f.path, f.sha256])),
|
|
110
111
|
previous_releases: [], transaction: { state: 'pending' }, source_ref: 'bundle-manifest.json',
|
package/lib/records.mjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { mkdir, readFile, open, rename, rm, lstat } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { versions } from '../skills/optbrew/scripts/core/config.mjs';
|
|
4
5
|
|
|
5
6
|
export function validateInstallRecord(record) {
|
|
6
|
-
if (record?.schema_version !==
|
|
7
|
+
if (record?.schema_version !== versions().install_record_schema) throw new Error('migration_required: unsupported install record schema; use a compatible optbrew release');
|
|
7
8
|
if (!Array.isArray(record.targets) || !/^[a-f0-9]{64}$/.test(record.bundle_hash) ||
|
|
8
9
|
typeof record.managed_root !== 'string' || typeof record.scope_id !== 'string' ||
|
|
9
10
|
typeof record.release_version !== 'string' || !record.file_hashes || typeof record.file_hashes !== 'object' ||
|
package/lib/transaction.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import { cp, lstat, mkdir, open, readFile, readlink, realpath, rename, rm } from
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { createHash, randomUUID } from 'node:crypto';
|
|
4
4
|
import { createBundleManifest, isDistributedBundlePath, verifyBundle } from '../skills/optbrew/scripts/core/bundle.mjs';
|
|
5
|
+
import { versions } from '../skills/optbrew/scripts/core/config.mjs';
|
|
5
6
|
import { readInstallRecord, validateInstallRecord, writeDurableJson, writeInstallRecord } from './records.mjs';
|
|
6
7
|
import { resolveTargets, scopePaths } from './paths.mjs';
|
|
7
8
|
|
|
@@ -16,12 +17,18 @@ export async function assertManagedPath(scopeRoot, destination) {
|
|
|
16
17
|
const relative = path.relative(scopeRoot, destination);
|
|
17
18
|
if (!relative || relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) throw new Error(`outside managed scope: ${destination}`);
|
|
18
19
|
let current = path.parse(scopeRoot).root;
|
|
20
|
+
// Folders above the scope root are the user's own choice of location, and Windows may spell them in the 8.3 short form
|
|
21
|
+
// (C:\Users\RUNNER~1 for C:\Users\runneradmin, as a TEMP variable often does). Links among them are still refused; the
|
|
22
|
+
// resolved-path comparison applies below the root, measured from the root's real path.
|
|
23
|
+
const realRoot = (await statOrNull(scopeRoot)) ? path.resolve(await realpath(scopeRoot)) : scopeRoot;
|
|
19
24
|
for (const part of path.relative(current, path.dirname(destination)).split(path.sep)) {
|
|
20
25
|
current = path.join(current, part);
|
|
21
26
|
const stat = await statOrNull(current);
|
|
22
27
|
if (stat?.isSymbolicLink()) throw new Error(`linked ancestor: ${current}`);
|
|
23
28
|
if (stat && !stat.isDirectory()) throw new Error(`invalid ancestor: ${current}`);
|
|
24
|
-
|
|
29
|
+
const below = path.relative(scopeRoot, current);
|
|
30
|
+
if (!stat || !below || below === '..' || below.startsWith(`..${path.sep}`) || path.isAbsolute(below)) continue;
|
|
31
|
+
if (path.resolve(await realpath(current)).toLowerCase() !== path.join(realRoot, below).toLowerCase()) throw new Error(`resolved path changed: ${current}`);
|
|
25
32
|
}
|
|
26
33
|
}
|
|
27
34
|
|
|
@@ -47,11 +54,11 @@ export async function verifiedBundle(bundleRoot) {
|
|
|
47
54
|
if (!(await lstat(bundleRoot)).isDirectory() || (await lstat(bundleRoot)).isSymbolicLink()) throw new Error('bundle ownership: expected real directory');
|
|
48
55
|
if ((await lstat(path.join(bundleRoot, 'bundle-manifest.json'))).isSymbolicLink()) throw new Error('manifest is a link');
|
|
49
56
|
const manifest = JSON.parse(await readFile(path.join(bundleRoot, 'bundle-manifest.json'), 'utf8'));
|
|
50
|
-
if (manifest.schema_version !==
|
|
57
|
+
if (manifest.schema_version !== versions().bundle_manifest_schema) throw new Error('migration_required: unsupported bundle manifest schema');
|
|
51
58
|
const verification = await verifyBundle({ bundleRoot, manifest });
|
|
52
59
|
if (verification.status !== 'valid') throw new Error(`bundle integrity failed: ${JSON.stringify(verification.errors)}`);
|
|
53
60
|
const lock = JSON.parse(await readFile(path.join(bundleRoot, 'source-lock.json'), 'utf8'));
|
|
54
|
-
if (lock.schema_version !==
|
|
61
|
+
if (lock.schema_version !== versions().bundle_manifest_schema || !Array.isArray(lock.files) || !Array.isArray(lock.modules)) throw new Error('invalid source-lock');
|
|
55
62
|
const hashes = new Map(manifest.files.map(f => [f.path, f.sha256]));
|
|
56
63
|
const seen = new Set();
|
|
57
64
|
for (const file of lock.files) {
|
|
@@ -75,7 +82,7 @@ export async function fingerprint(file) {
|
|
|
75
82
|
if (!stat) return null;
|
|
76
83
|
if (stat.isSymbolicLink()) return `link:${await readlink(file)}`;
|
|
77
84
|
if (!stat.isDirectory()) throw new Error(`unexpected managed path type: ${file}`);
|
|
78
|
-
const manifest = await createBundleManifest({ bundleRoot: file, releaseVersion:
|
|
85
|
+
const manifest = await createBundleManifest({ bundleRoot: file, releaseVersion: versions().release });
|
|
79
86
|
const meta = await readFile(path.join(file, 'bundle-manifest.json'));
|
|
80
87
|
return createHash('sha256').update(manifest.bundle_hash).update(meta).digest('hex');
|
|
81
88
|
}
|
|
@@ -142,7 +149,7 @@ async function recoverLocked(recordRoot) {
|
|
|
142
149
|
if (!journal) return { status: 'clean', restored_version: null, unresolved_paths: [], evidence_ref: null };
|
|
143
150
|
const evidence_ref = path.join(recordRoot, 'transaction.json');
|
|
144
151
|
try {
|
|
145
|
-
if (journal.schema_version !==
|
|
152
|
+
if (journal.schema_version !== versions().install_record_schema || !/^[a-f0-9-]{36}$/.test(journal.id) || !Array.isArray(journal.operations)) throw new Error('invalid journal');
|
|
146
153
|
const scopeRoot = await validateScope(recordRoot, journal.before);
|
|
147
154
|
await validateScope(recordRoot, journal.after);
|
|
148
155
|
const expected = [path.join(recordRoot, 'bundle'), ...journal.before.targets.filter(t => t.strategy === 'copy').map(t => t.path)];
|
package/lib/update.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import { lstat, mkdir, readFile, realpath } from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
4
|
import { createBundleManifest } from '../skills/optbrew/scripts/core/bundle.mjs';
|
|
5
|
+
import { endpoints, versions } from '../skills/optbrew/scripts/core/config.mjs';
|
|
5
6
|
import { readInstallRecord, writeDurableJson, writeInstallRecord } from './records.mjs';
|
|
6
7
|
import { reinstallGuidance } from './reinstall.mjs';
|
|
7
8
|
import { assertManagedPath, copyBundle, fingerprint, finishJournal, managedRename, operationPaths,
|
|
@@ -31,7 +32,7 @@ export async function checkUpdates({ packageName, currentVersion, registryTransp
|
|
|
31
32
|
if (!versionPattern.test(currentVersion)) throw new TypeError('invalid current version');
|
|
32
33
|
if (!packageName) return { current_version: currentVersion, latest_version: null, status: 'local_only', source_ref: 'bundled release; registry package unconfirmed' };
|
|
33
34
|
if (!/^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+$/.test(packageName)) throw new TypeError('invalid registry package name');
|
|
34
|
-
const source_ref =
|
|
35
|
+
const source_ref = `${endpoints().npm_registry}${encodeURIComponent(packageName)}`;
|
|
35
36
|
const transport = registryTransport ?? (async () => {
|
|
36
37
|
const response = await fetch(source_ref, { signal: AbortSignal.timeout(10000), redirect: 'error', headers: { accept: 'application/json' } });
|
|
37
38
|
if (!response.ok) throw new Error(`registry HTTP ${response.status}`);
|
|
@@ -141,7 +142,7 @@ async function applyRelease({ bundleRoot, recordRoot, localChanges, faultHook, k
|
|
|
141
142
|
source: path.resolve(bundleRoot), file_hashes: Object.fromEntries(source.manifest.files.map(f => [f.path, f.sha256])),
|
|
142
143
|
previous_releases: [...before.previous_releases, { release_version: before.release_version, bundle_hash: before.bundle_hash, bundle_root: snapshotRoot }],
|
|
143
144
|
transaction: { state: 'committed', id, kind, conflicts, backups: decision.backups } };
|
|
144
|
-
const journal = { schema_version:
|
|
145
|
+
const journal = { schema_version: versions().install_record_schema, id, state: 'prepared', before, after, operations };
|
|
145
146
|
await writeDurableJson(path.join(recordRoot, 'transaction.json'), journal);
|
|
146
147
|
try {
|
|
147
148
|
await faultHook?.('after_staging');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "optbrew",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Pinned search audit and improvement skill bundle",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
]
|
|
24
24
|
},
|
|
25
25
|
"engines": {
|
|
26
|
-
"node": ">=22"
|
|
26
|
+
"node": ">=22.4"
|
|
27
27
|
},
|
|
28
28
|
"bin": {
|
|
29
29
|
"optbrew": "bin/optbrew.mjs"
|
|
@@ -32,6 +32,8 @@
|
|
|
32
32
|
"bin/",
|
|
33
33
|
"lib/",
|
|
34
34
|
"skills/optbrew/",
|
|
35
|
+
"skills/.claude-plugin/",
|
|
36
|
+
"skills/README.md",
|
|
35
37
|
"plugin.json",
|
|
36
38
|
".codex-plugin/",
|
|
37
39
|
".claude-plugin/",
|
|
@@ -51,8 +53,8 @@
|
|
|
51
53
|
"!**/test_*.py"
|
|
52
54
|
],
|
|
53
55
|
"scripts": {
|
|
54
|
-
"test": "node
|
|
55
|
-
"build-bundle": "node scripts/build-bundle.mjs --bundle skills/optbrew
|
|
56
|
+
"test": "node scripts/test.mjs",
|
|
57
|
+
"build-bundle": "node scripts/build-bundle.mjs --bundle skills/optbrew",
|
|
56
58
|
"vendor": "node scripts/vendor.mjs",
|
|
57
59
|
"scan-secrets": "node scripts/scan-secrets.mjs --tracked-and-staged"
|
|
58
60
|
}
|
package/plugin.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "optbrew",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.0",
|
|
5
5
|
"description": "Pinned search audit and improvement skill bundle",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"extensions": {
|
|
8
8
|
"com.openai": {
|
|
9
9
|
"skills": ["./skills/optbrew"],
|
|
10
|
-
"release_version": "0.
|
|
10
|
+
"release_version": "0.2.0"
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
}
|
|
Binary file
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "optbrew",
|
|
3
|
+
"displayName": "optbrew",
|
|
4
|
+
"description": "Audit a site for Google, Naver and AI answer engines, fix the local files you approve, re-check under the same conditions and write a scored report",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "nowwcastle-sudo",
|
|
7
|
+
"url": "https://github.com/nowwcastle-sudo"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/nowwcastle-sudo/optbrew",
|
|
10
|
+
"repository": "https://github.com/nowwcastle-sudo/optbrew",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"seo",
|
|
14
|
+
"naver",
|
|
15
|
+
"aeo",
|
|
16
|
+
"geo",
|
|
17
|
+
"llmo",
|
|
18
|
+
"audit"
|
|
19
|
+
],
|
|
20
|
+
"skills": [
|
|
21
|
+
"./optbrew"
|
|
22
|
+
]
|
|
23
|
+
}
|
package/skills/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# optbrew
|
|
2
|
+
|
|
3
|
+
A search-readiness audit skill. optbrew checks a website for Google, Naver and AI answer engines, fixes only the local files you approve, checks again under the same conditions, and writes a scored report built from evidence you can replay.
|
|
4
|
+
|
|
5
|
+
It covers five profiles: SEO, NEO (Naver), AEO, GEO and LLMO. Five pinned audit guide bundles are merged into 22 check families, so an issue that several guides report is counted once. When less than 80% of the applicable checks could be measured, the report shows the measured part and its bounds instead of one headline score.
|
|
6
|
+
|
|
7
|
+
## How to use it
|
|
8
|
+
|
|
9
|
+
Start a session and ask, for example: "Run optbrew on https://example.com. Goal: show up in Naver and get cited in AI answers." optbrew reads the site and your setup, then sends all its start questions in one message: guidance level, goal, search questions, site facts, page limit and optional extras. The questions, guides and report are in Korean. Answer by number, or reply "시작" (start) to take the defaults.
|
|
10
|
+
|
|
11
|
+
The audit then runs in one go. It writes `report.html`, `report.md` and `report.json` to an `optbrew-audit` folder in your working folder. Open `report.html` first. The report text is in Korean. Each problem has one fix with the number of pages it affects, and each blank has a numbered list of steps that fill it. Missing extras, such as a Google key or Chrome, never stop the audit.
|
|
12
|
+
|
|
13
|
+
## What it runs, reads and sends
|
|
14
|
+
|
|
15
|
+
- **Site collection.** It fetches only URLs on your site's own address and its `www` or `https` twin: pages from the sitemap and from links on the start page, up to a limit of 50 requests by default (up to 200 if you raise it), counting pages, redirects and robots or sitemap files together, two at a time. It follows a redirect to the `www` or `https` twin of your address and says so in the report. Private, internal and local addresses (such as `localhost`) are refused, so audit the public address of the site. Every response is stored with its hash in the `optbrew-audit` folder.
|
|
16
|
+
- **Chrome.** To see text that scripts draw, it renders up to 10 pages, one of each kind, in a headless Chrome of its own with a temporary profile, and closes it when the audit ends. Your own Chrome is never reused. The pages it renders may load files from other public hosts, such as fonts and scripts; optbrew fetches those only from public addresses. Without Chrome the audit still runs and the render checks stay blank.
|
|
17
|
+
- **Speed data.** To get the Core Web Vitals of real visitors, it asks Google's Chrome UX Report and PageSpeed Insights about your site's address. If you saved a Google API key, it is sent to Google only in a request header. The key is never written to a report, a log or the evidence. Without a key, those cells can stay blank.
|
|
18
|
+
- **Google key file.** `optbrew key set google` (or `node <skill folder>/scripts/key.mjs set google`), run by you in your own terminal, saves the key in `.optbrew/credentials.json` in your home folder. The environment variable `OPTBREW_GOOGLE_API_KEY` takes priority. The skill never asks for the key in the chat.
|
|
19
|
+
- **Your export files.** If you give the path of a Search Console zip or a Naver Search Advisor text file, the audit reads that file from disk. It sends nothing from it.
|
|
20
|
+
- **Local scripts.** The skill tells Claude to run the Node.js and Python scripts bundled in this folder for collection, scoring, comparison and reports. They need Node.js 22 or later; Python 3.10 or later is optional.
|
|
21
|
+
- **Local changes.** It edits only local files you approve, after keeping a backup copy. It does not deploy, publish, or submit a site to a search engine.
|
|
22
|
+
- **Optional paid APIs.** Nothing requires an account. If you choose to use the Anthropic API, the bundled adapter reads your `ANTHROPIC_API_KEY` environment variable and sends it only to `api.anthropic.com`, and only after you approve a budget and a stop condition. The SE Ranking guides describe SE Ranking's own API and MCP with your own SE Ranking key, sent only to SE Ranking. Keys never go into a report.
|
|
23
|
+
- **No other network use.** The plugin has no hooks, MCP servers or background tasks, and it does not download or run code at install time.
|
|
24
|
+
|
|
25
|
+
## License
|
|
26
|
+
|
|
27
|
+
MIT for optbrew's own code. The bundled guides keep their own licenses, listed in `optbrew/THIRD_PARTY_NOTICES.md`. Source and documentation: https://github.com/nowwcastle-sudo/optbrew
|