by YOKOYAMA Taku
Animals gather in a side panel while you stay focused on your notes. The longer you focus, the more show up. A paid activation code unlocks rarer animals.
Focus Safari ๐ฆ (source repository)
Private. This is the source repository for Focus Safari. Users install the plugin from the public distribution repository, seekseep/focus-safari, which holds only
manifest.json,versions.json,styles.cssand the releases. See Releasing.
A small, playful side panel: the longer you stay focused on your notes, the more animals wander into your safari.
- Free: six animals (ant, mouse, rabbit, fox, deer, bear) arrive as your cumulative focus time grows.
- Pro: ten rarer animals โ lion, elephant, parrot, giraffe, zebra, panda, crocodile, sloth, rhino, flamingo.
๐ณ Paid features
The Pro animals require a paid activation code. Everything else โ the timer, the panel, the six free animals โ is fully functional without paying.
๐ Network use
None. Activation codes are Ed25519-signed and verified entirely offline against a public key embedded in the plugin. Nothing is sent anywhere, and no telemetry is collected.
How it works
While the app window has focus, one second of focus time is added per second. Large jumps (for example after the machine wakes from sleep) are clamped to two seconds per tick. When your cumulative focus time crosses an animal's threshold, that animal arrives and you get a notice.
| Animal | Focus required | Tier |
|---|---|---|
| ๐ Ant | 30s | Free |
| ๐ Mouse | 2m | Free |
| ๐ Rabbit | 5m | Free |
| ๐ฆ Lion | 10m | Pro |
| ๐ฆ Fox | 15m | Free |
| ๐ Elephant | 20m | Pro |
| ๐ฆ Deer | 30m | Free |
| ๐ป Bear | 1h | Free |
| ๐ผ Panda | 1.5h | Pro |
| ๐ฆฉ Flamingo | 3h | Pro |
The full roster lives in src/animals.ts.
Usage
Open the panel from the paw-print ribbon icon, or run the Open safari command. Focus time keeps accumulating whether or not the panel is open.
Settings let you paste an activation code, remove it again, or reset the safari (clears focus time and sends every animal home).
Manual installation
- Download
main.js,manifest.jsonandstyles.cssfrom the latest release. - Copy them into
<Vault>/.obsidian/plugins/focus-safari/. - Reload, then enable Focus Safari under Community plugins.
Development
npm install
npm run dev # watch build
npm run build # production build -> main.js
Animal artwork
Animals are drawn with Twemoji rather than the platform emoji
font, so the safari looks the same on every OS. The SVGs are inlined into the bundle
(src/twemoji-assets.ts) โ nothing is fetched at runtime. After changing
the roster in src/animals.ts, regenerate them:
node tools/fetch-twemoji.mjs
Activation codes
Key generation and code issuance live in a separate repository,
focus-safari-licensing โ this repo only
verifies codes (src/license.ts) against the public key committed in
src/public-key.ts.
- The signing private key is never in this repository and never in CI. It lives outside any repo
at the path
LICENSE_PRIVATE_KEY_PATHpoints to. Losing it makes future issuance impossible; replacing it invalidates every code already delivered. src/public-key.tsmust stay in step with the signing key.npm run key:checkderives the public key fromLICENSE_PRIVATE_KEY_PATH(default~/.config/focus-safari/private.key) and fails on drift. It skips silently where no key is present, so CI is unaffected.- Never regenerate the keypair to make a check pass โ that invalidates every code already delivered.
Two checks guard the contract with the issuer. Run both after touching src/license.ts:
npm run key:check # embedded public key still matches the signing key
npm run verify:roundtrip # sign codes like the issuer does, verify with the real verifier
verify:roundtrip bundles src/license.ts itself, so it tests the shipped logic rather than a
copy of it, and asserts the negative cases too (wrong product, expired, tampered, malformed).
Product id
Codes carry product: "focus-safari". Builds up to 1.0.4 expected the old id
"obsidian-safari" and will reject anything issued now โ activation requires 1.0.5 or later.
Say so in the release notes.
To test activation end to end, run a Stripe test purchase from the licensing repo and paste the resulting code into Settings โ Focus Safari โ Activation code.
Releasing
Never edit package.json, manifest.json and versions.json by hand โ that is how
1.0.3 and 1.0.4 shipped a manifest still declaring 1.0.2. Use npm version, which runs
tools/version-sync.mjs and keeps all three in step:
npm version patch # or minor / major
git push origin main --follow-tags
The tag must be bare semver โ Obsidian forbids a v prefix, and
tools/check-release-version.mjs rejects one. npm tags as v1.2.3 by default, so
.npmrc sets an empty tag-version-prefix. Keep that file; without it every
npm version produces a tag the release cannot accept.
1.0.3 and 1.0.4 are missing from versions.json on purpose: both shipped a manifest still
declaring 1.0.2, so neither ever existed as a version Obsidian could install. Do not
backfill them.
Pushing the tag runs .github/workflows/release.yml, which:
- verifies that the tag,
package.json,manifest.jsonandversions.jsonall agree (tools/check-release-version.mjs) and fails the release if they do not; - refuses to continue if a release for that tag already exists in the public repo;
- builds
main.js; - signs
main.js,manifest.jsonandstyles.csswith cosign (see below); - commits
manifest.json,styles.cssandversions.jsonto the public repo'smain, refusing to publish any file outside those three; - tags the public repo and creates the release, attaching the three files plus their
*.sigstore.jsonbundles.
Nothing from src/ or tools/ is ever copied to the public repo. main.js is build output
and is a release asset only โ it is committed nowhere. manifest.json and versions.json do
get committed because Obsidian reads them from the public repo's default branch.
PUBLIC_REPO_TOKEN
A fine-grained personal access token scoped to seekseep/focus-safari only, with
Contents: Read and write. Stored as an Actions secret on this repository. It expires โ
renew it before the expiry date or releases will start failing at the checkout step.
Signing and verification
actions/attest-build-provenance cannot be used here: GitHub's attestation store rejects
user-owned private repositories (Feature not available for user-owned private repositories),
and create-storage-record: false does not bypass it. The workflow signs with
cosign attest-blob --new-bundle-format instead, which writes an in-toto SLSA provenance
statement to the Sigstore public-good instance and emits a bundle file per artifact.
Because the bundle travels with the release, anyone can verify offline โ no cosign, no authentication, and no access to this repository:
gh attestation verify main.js \
--bundle main.js.sigstore.json \
--repo seekseep/focus-safari-source
Verification fails if the artifact is modified or if a different --repo is claimed.
Note what this does and does not prove. The source is private, so provenance cannot make the
build reproducible or auditable by a third party. What it establishes is that a given
main.js came out of this repository's release workflow at a given tag and has not been
altered since โ tamper-evidence, not transparency.
License
Proprietary โ see LICENSE. Versions up to 1.0.4 were released under MIT.
The bundled animal graphics come from Twemoji, ยฉ Twitter, Inc and other contributors, licensed under CC-BY 4.0.
ๆฅๆฌ่ช
ใใผใใซ้ไธญใใฆใใๆ้ใๆบใพใใปใฉใใตใคใใใใซใฎใใตใใกใชใใซๅ็ฉใ้ใพใฃใฆใใใใฉใฐใคใณใงใใ็กๆใง6็จฎ้กใPro๏ผๆๆใฎใขใฏใใฃใใผใทใงใณใณใผใใๅฟ ่ฆ๏ผใงใใใซ10็จฎ้กใฎใฌใขๅ็ฉใ็ปๅ ดใใพใใ
ใณใผใใฎๆค่จผใฏ Ed25519 ็ฝฒๅใซใใใชใใฉใคใณๆค่จผใงใใใใใฏใผใฏ้ไฟกใปใใฌใกใใชใฏไธๅใใใพใใใ้พๅคใฎ่ชฟๆดใฏ src/animals.ts ใๆธใๆใใใ ใใงใใ
For plugin developers
Search results and similarity scores are powered by semantic analysis of your plugin's README. If your plugin isn't appearing for searches you'd expect, try updating your README to clearly describe your plugin's purpose, features, and use cases.