-
Notifications
You must be signed in to change notification settings - Fork 0
feat(dist): the installer, the two channels, the plugin, and the socket (9.14, 10.1-10.6) #16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| { | ||
| "name": "protonspy", | ||
| "description": "open-wiki: a project's documentation as a wiki the agent already has open, with every write going through a gate that validates it.", | ||
| "owner": { | ||
| "name": "protonspy", | ||
| "url": "https://github.com/protonspy" | ||
| }, | ||
| "plugins": [ | ||
| { | ||
| "name": "open-wiki", | ||
| "source": "./plugins/open-wiki", | ||
| "description": "The write gate and the scaffolding command for an open-wiki project: PreToolUse validates a page before it exists and PostToolUse records it.", | ||
| "version": "0.1.0", | ||
| "author": { "name": "protonspy" }, | ||
| "homepage": "https://github.com/protonspy/open-wiki", | ||
| "license": "Apache-2.0", | ||
| "keywords": ["wiki", "documentation", "provenance"] | ||
| } | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,13 +10,23 @@ concurrency: | |
| group: release-${{ github.ref }} | ||
| cancel-in-progress: false | ||
|
|
||
| permissions: | ||
| contents: write | ||
| permissions: {} | ||
|
|
||
| jobs: | ||
| release: | ||
| name: Build and publish the installer | ||
| runs-on: windows-latest | ||
| permissions: | ||
| # Least privilege, and granted per job rather than per workflow: the | ||
| # release needs to create one, and `id-token` is what npm provenance | ||
| # signs with. Nothing else in this repository should inherit either. | ||
| contents: write | ||
| id-token: write | ||
| env: | ||
| # Never interpolated into a `run:` body. A tag name may contain `"`, `$`, | ||
| # `;` and a backtick — `git check-ref-format` permits all four — and this | ||
| # job holds the npm token and the signing certificate. | ||
| TAG: ${{ github.ref_name }} | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
|
|
||
|
|
@@ -30,41 +40,73 @@ jobs: | |
| exit 1 | ||
| } | ||
|
|
||
| - name: Check the tag matches the app version | ||
| # Both artifacts, one version. The installer and the npm package ship | ||
| # from this tag, and `adr:0014` names what a skew costs: it "fails | ||
| # looking like corrupted state rather than a bad install". | ||
| - name: Check the tag and both artifacts agree | ||
| if: startsWith(github.ref, 'refs/tags/v') | ||
| shell: pwsh | ||
| run: | | ||
| $tag = "${{ github.ref_name }}".TrimStart("v") | ||
| $version = (Get-Content "apps/desktop/package.json" -Raw | ConvertFrom-Json).version | ||
| if ($tag -ne $version) { | ||
| Write-Error "tag v$tag does not match apps/desktop/package.json version $version" | ||
| exit 1 | ||
| } | ||
| Write-Host "releasing $version" | ||
| run: node scripts/ci/release-version.mjs "$env:TAG" | ||
|
|
||
| - name: Refuse to republish an existing release | ||
| # Both registries, before anything is built. A GitHub release can be | ||
| # deleted; an npm version cannot, so finding out afterwards that one of | ||
| # the two is already taken leaves a half-published release with no way | ||
| # back except bumping the version — which is what the agreement check | ||
| # above exists to make impossible. | ||
| - name: Refuse to republish | ||
| if: startsWith(github.ref, 'refs/tags/v') | ||
| shell: pwsh | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| run: | | ||
| # A published release has been downloaded; deleting it does not undo that. | ||
| gh release view "${{ github.ref_name }}" 2>$null | ||
| gh release view "$env:TAG" 2>$null | ||
| if ($LASTEXITCODE -eq 0) { | ||
| Write-Error "release $env:TAG already exists — bump the version and tag again" | ||
| exit 1 | ||
| } | ||
| $version = $env:TAG.TrimStart("v") | ||
| npm view "open-wiki@$version" version 2>$null | ||
| if ($LASTEXITCODE -eq 0) { | ||
| Write-Error "release ${{ github.ref_name }} already exists — bump the version and tag again" | ||
| Write-Error "open-wiki@$version is already on npm — a version there is permanent; bump and tag again" | ||
| exit 1 | ||
| } | ||
| Write-Host "no release for ${{ github.ref_name }} yet" | ||
| Write-Host "neither registry has $env:TAG yet" | ||
|
|
||
| - uses: pnpm/action-setup@v4 | ||
|
|
||
| - uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: 22 | ||
| cache: pnpm | ||
| # Without this, `setup-node` writes no .npmrc and NODE_AUTH_TOKEN is | ||
| # read by nothing — the publish below would be unauthenticated. | ||
| registry-url: https://registry.npmjs.org | ||
|
|
||
| - run: pnpm install --frozen-lockfile | ||
|
|
||
| # The installer carries them; `vendor/ffmpeg/` is gitignored and fetched | ||
| # with hash verification, and the recorder is the one Rust crate. | ||
| - uses: dtolnay/rust-toolchain@stable | ||
| - uses: Swatinem/rust-cache@v2 | ||
|
Comment on lines
76
to
+91
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift Address the cache-poisoning risk flagged on the caching actions. Static analysis flags both Consider scoping the cache key to this workflow only (for example a key that includes the workflow name or 🧰 Tools🪛 zizmor (1.28.0)[error] 78-78: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): this step (cache-poisoning) [error] 91-91: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): enables caching by default (cache-poisoning) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
| - run: cargo build --release --locked | ||
|
|
||
| - name: Fetch and verify ffmpeg | ||
| shell: pwsh | ||
| env: | ||
| FFMPEG_URL: ${{ vars.FFMPEG_URL }} | ||
| FFMPEG_SHA256: ${{ vars.FFMPEG_SHA256 }} | ||
| run: | | ||
| # The hash is a repository variable rather than a committed constant | ||
| # because the upstream URL is a moving one. An empty value here fails | ||
| # inside the fetch script as "no expected hash", which reads like a | ||
| # bug in the script rather than a setting nobody set. | ||
| if (-not $env:FFMPEG_SHA256) { | ||
| Write-Error "vars.FFMPEG_SHA256 is not set — the installer bundles ffmpeg and the download is verified against it. Set it (with vars.FFMPEG_URL) in the repository settings." | ||
| exit 1 | ||
| } | ||
| node scripts/fetch-ffmpeg.mjs | ||
|
|
||
| - name: Build the installer | ||
| shell: pwsh | ||
| env: | ||
|
|
@@ -92,18 +134,64 @@ jobs: | |
| Out-File -FilePath "$dir/SHA256SUMS.txt" -Encoding utf8 | ||
| Get-Content "$dir/SHA256SUMS.txt" | ||
|
|
||
| # 10.4 — the manifests, generated from the tag and the hash of the | ||
| # installer that is about to be released. A manifest carrying last | ||
| # release's hash fails as "the download is corrupt" rather than | ||
| # "somebody forgot to update a file". | ||
| - name: Generate the winget and Scoop manifests | ||
| if: startsWith(github.ref, 'refs/tags/v') | ||
| shell: pwsh | ||
| run: | | ||
| # By name, not by position: the collect step allows more than one | ||
| # `.exe`, and taking the first line would silently publish some other | ||
| # file's hash. | ||
| $version = $env:TAG.TrimStart("v") | ||
| $wanted = "open-wiki-Setup-$version.exe" | ||
| $line = Get-Content "apps/desktop/release/SHA256SUMS.txt" | | ||
| Where-Object { $_ -match [regex]::Escape($wanted) + '$' } | | ||
| Select-Object -First 1 | ||
| if (-not $line) { | ||
| Write-Error "no checksum line for $wanted — the installer is not named what the manifests will point at" | ||
| exit 1 | ||
| } | ||
| $sha = $line.Split(" ")[0] | ||
| node scripts/ci/package-manifests.mjs "$env:TAG" $sha "dist/manifests" | ||
| Copy-Item "dist/manifests/scoop/open-wiki.json" "apps/desktop/release/open-wiki.json" | ||
| Compress-Archive -Path "dist/manifests/*" -DestinationPath "apps/desktop/release/manifests.zip" | ||
|
|
||
| # The reversible step first. A GitHub release can be deleted and made | ||
| # again; an npm version is permanent, so publishing to npm before the | ||
| # release existed meant any later failure left `open-wiki@X` on the | ||
| # registry with no installer to go with it and no way to re-run the tag. | ||
| - name: Publish the release | ||
| if: startsWith(github.ref, 'refs/tags/v') | ||
| uses: softprops/action-gh-release@v2 | ||
| with: | ||
| files: | | ||
| apps/desktop/release/*.exe | ||
| apps/desktop/release/SHA256SUMS.txt | ||
| apps/desktop/release/open-wiki.json | ||
| apps/desktop/release/manifests.zip | ||
| generate_release_notes: true | ||
| # A tag carrying a suffix — v0.1.0-beta.1 — is not a stable release. | ||
| prerelease: ${{ contains(github.ref_name, '-') }} | ||
| fail_on_unmatched_files: true | ||
|
|
||
| # 10.3 — the second artifact, from the same tag. Provenance is on, and | ||
| # it is not decoration: a product that verifies the SHA256 of its own | ||
| # ffmpeg cannot ship a `npx` entry point that verifies nothing. | ||
| - name: Publish the CLI to npm | ||
| if: startsWith(github.ref, 'refs/tags/v') | ||
| shell: pwsh | ||
| env: | ||
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | ||
| run: | | ||
| # A prerelease must not become what `npx open-wiki` resolves to. | ||
| $pre = node scripts/ci/release-version.mjs --prerelease "$env:TAG" | ||
| $tag = if ($pre) { "next" } else { "latest" } | ||
| Write-Host "publishing to the '$tag' dist-tag" | ||
| pnpm --filter open-wiki publish --access public --no-git-checks --tag $tag | ||
|
|
||
| # workflow_dispatch builds without a tag: useful for checking the packaging | ||
| # still works without publishing anything. | ||
| - name: Upload the installer as a build artifact | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| # open-wiki | ||
|
|
||
| A project's documentation, as a wiki the AI agent already has open. | ||
|
|
||
| Today a project's documentation is scattered: an architecture PDF, a | ||
| requirements `.docx`, decisions that exist only in a recorded meeting. None of | ||
| it answers _what is the current state of project X and how did we get here_, and | ||
| none of it is read by an agent without somebody pasting it into a prompt by | ||
| hand. | ||
|
|
||
| open-wiki does three things and refuses the rest. It **takes in sources** — a | ||
| file or a recording — and reduces them to text with provenance anchors. It | ||
| **stores the wiki** as validated markdown. And it **lives inside the project | ||
| directory**, which is where the harness is already working, so reading it needs | ||
| no protocol at all. | ||
|
|
||
| **The application calls no LLM.** Reading the source text, applying the | ||
| convention and writing the pages is the agent's job. The application does not | ||
| write content; it validates what comes in and records everything that changes. | ||
|
|
||
| Windows 10/11. Apache-2.0. | ||
|
|
||
| ## Install | ||
|
|
||
| The installer from [Releases](https://github.com/protonspy/open-wiki/releases). | ||
| Every release publishes a `SHA256SUMS.txt` beside it — check it. | ||
|
|
||
| Or through Scoop, from the manifest that release attaches: | ||
|
|
||
| ```powershell | ||
| scoop install https://github.com/protonspy/open-wiki/releases/latest/download/open-wiki.json | ||
| ``` | ||
|
|
||
| A winget manifest is generated and attached to every release as well | ||
| (`manifests.zip`), quoting the same hash. **It is not in the winget community | ||
| repository yet** — submitting it is a pull request to `microsoft/winget-pkgs` | ||
| that nothing here opens for you, so `winget install protonspy.open-wiki` does | ||
| not work today. | ||
|
|
||
| For the CLI alone, with nothing installed: | ||
|
|
||
| ``` | ||
| npx open-wiki init | ||
| ``` | ||
|
Comment on lines
+42
to
+44
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Add a language to the fenced code block. markdownlint (MD040) flags this fence for missing a language identifier. 📝 Proposed fix-```
+```bash
npx open-wiki init🧰 Tools🪛 markdownlint-cli2 (0.23.1)[warning] 42-42: Fenced code blocks should have a language specified (MD040, fenced-code-language) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
|
|
||
| ### The SmartScreen warning | ||
|
|
||
| The installer is **not code-signed**, so Windows SmartScreen will say | ||
| "Windows protected your PC" and name an unknown publisher. That is accurate: | ||
| there is no certificate behind this build. Verify the SHA256 against | ||
| `SHA256SUMS.txt` on the release page before choosing **More info → Run anyway**, | ||
| and if the hash does not match, do not run it. A certificate costs money and | ||
| proves the publisher is _someone_, not that the software is safe; the hash is | ||
| the thing that actually tells you the bytes are the ones that were built. | ||
|
|
||
| ## Using it | ||
|
|
||
| `ow` in a project directory opens the application scoped to it, the way `code .` | ||
| does. `ow init` scaffolds `raw/`, `wiki/`, `.state/`, the convention as skills, | ||
| and a short `CLAUDE.md`. Then you talk to your agent in that same directory. | ||
|
|
||
| ## Recording a meeting | ||
|
|
||
| This application records audio, from your microphone **and** from what your | ||
| computer is playing — which in a call is everybody else. | ||
|
|
||
| **Telling the other people in the call that you are recording is your | ||
| responsibility, and in many places it is the law.** Recording a conversation | ||
| without consent is a criminal offence in a number of jurisdictions and grounds | ||
| for a civil claim in more; where one-party consent is enough it is still, at | ||
| minimum, a thing people are entitled to know. This software will not ask them | ||
| for you, and it has no way to tell whether you did. | ||
|
|
||
| A recording indicator is in the window whenever capture is running, and it is | ||
| deliberately hard to miss — the failure it exists to prevent is somebody | ||
| forgetting it is on and ending up with a recording of a conversation the other | ||
| people in it believe ended. | ||
|
|
||
| ## Committing a wiki | ||
|
|
||
| The project directory is usually a git repository, and that is your business — | ||
| this application neither reads nor writes one. But it is worth saying plainly | ||
| what committing a wiki does: | ||
|
|
||
| **Everything in `wiki/` goes to everyone with repository access.** So does | ||
| everything in `raw/` except what the generated `.gitignore` excludes — recorded | ||
| audio and `.state/` are out by default, and committing them is opting in. That | ||
| default is not tidiness: `.state/` holds every page as it was before each write, | ||
| which is where a redaction survives the redaction. | ||
|
|
||
| A meeting transcript is a verbatim record of what people said, including the | ||
| part they would not have written down. Read a source before you commit it. | ||
|
|
||
| **The transcription credential is never in the project directory.** It lives in | ||
| the application's own data directory, keyed by project path, unconditionally — | ||
| because `git init` a week later turns a conditional rule into a leak. | ||
|
|
||
| ## What it does not do | ||
|
|
||
| Extraction or page-writing by the application; chat inside the application; a | ||
| hosted service, accounts or telemetry; a block editor; real-time collaboration; | ||
| embeddings or a vector store; versioning (your git is welcome to it); macOS and | ||
| Linux; real-time transcription or a bot that joins the meeting. | ||
|
|
||
| The reasoning for each is in [`docs/adr/`](docs/adr/), and the shape of the | ||
| whole thing is in [`plans/open-wiki.md`](plans/open-wiki.md). | ||
|
|
||
| ## Building it | ||
|
|
||
| See [`.claude/rules/project.md`](.claude/rules/project.md) for the commands. | ||
| It is a pnpm workspace plus one Rust crate — the audio recorder, which is the | ||
| only thing here not written in TypeScript | ||
| (`adr:0014-typescript-everywhere-except-audio-capture`). | ||
|
|
||
| ## Licence | ||
|
|
||
| Apache-2.0. See [LICENSE](LICENSE). | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Set
persist-credentials: falseon this checkout.This job later runs
npm i -g@anthropic-ai/claude-code``, third-party code with a postinstall path. By default,actions/checkoutleaves the `GITHUB_TOKEN` credential in `.git/config`. If the installed package or one of its dependencies is compromised, it can read and exfiltrate that token.Add
persist-credentials: false, since this job does not need to push or fetch with the token afterward.🔒 Proposed fix
📝 Committable suggestion
🧰 Tools
🪛 zizmor (1.28.0)
[warning] 136-136: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
🤖 Prompt for AI Agents
Source: Linters/SAST tools