Compare commits
210 Commits
1f04cb7cf2
...
fix/waybar
| Author | SHA1 | Date | |
|---|---|---|---|
| c053846adb | |||
| 2edad6c5df | |||
| 1d5acc09c6 | |||
| ee29e53f7c | |||
| 3c9d0ec886 | |||
| f0c5544b10 | |||
| 4dd0a5a4f5 | |||
| a4a795f7ee | |||
| ecdf877308 | |||
| d1face10ec | |||
| 337d9a2014 | |||
| f059068873 | |||
| 7662c04cda | |||
| 33128fc3c7 | |||
| 68415ad47e | |||
| 6b7201e6fa | |||
| 4360f4863b | |||
| 370cab8f83 | |||
| e74a08b853 | |||
| 250b0b71f9 | |||
| 10299cec19 | |||
| 91654b3068 | |||
| be9c3c07a3 | |||
| a8bd4bf4cf | |||
| a2fc568704 | |||
| 0bf5ebae87 | |||
| fddec14128 | |||
| 85fadc5bfc | |||
| 3992030949 | |||
| c8a596aa39 | |||
| 4908c4751f | |||
| 79c56dfb1c | |||
| 7140b6d258 | |||
| f173887228 | |||
| 3a487050fd | |||
| 65c84f5b39 | |||
| b3c4903c82 | |||
| ec1c0fba56 | |||
| 1f2f1a79ad | |||
| 46a47fa061 | |||
| 8d357e960b | |||
| f48bac5ec1 | |||
| d53246caf0 | |||
| 92f7f0542d | |||
| 4f7fbef35b | |||
| c7c3257b9d | |||
| 7d37b92e17 | |||
| 9ece182099 | |||
| 9aea13b8c2 | |||
| 369c7512c6 | |||
| 8c91a210b2 | |||
| d602581a99 | |||
| c3a6271582 | |||
| a7db0b745c | |||
| 4a8e8920ea | |||
| 3e9295134e | |||
| c91094234f | |||
| 11b490d1e8 | |||
| 43b5d07a27 | |||
| 1b4dc77627 | |||
| 1cbdcee8ac | |||
| 3fbb0aca32 | |||
| 87776c8994 | |||
| 17597526c5 | |||
| 14c9a418b6 | |||
| 589e37f52e | |||
| 78492739f3 | |||
| 2265dfc558 | |||
| becbb50491 | |||
| 54335ead82 | |||
| 16c69e01e1 | |||
| 5af563d70f | |||
| 0e5731145e | |||
| b6469567e1 | |||
| 8f0837a4b5 | |||
| 0937f15260 | |||
| 61efef1a7d | |||
| 8eabdee2a0 | |||
| 43b3ff4b09 | |||
| 3b2d16743e | |||
| 11d11c7c02 | |||
| 869d6a2cbd | |||
| e5b76515f1 | |||
| 324d40c85d | |||
| dd82367dcb | |||
| ac5cc8d659 | |||
| 5e5c3e2534 | |||
| 09a2093f90 | |||
| 8000367613 | |||
| 38dff04ece | |||
| 0c1a1c1ffa | |||
| 27d9194ac3 | |||
| c4ea12b3ce | |||
| 16ce11fc52 | |||
| a73636a84f | |||
| 80db2b61e6 | |||
| 7cc066b55d | |||
| 74e7d2d211 | |||
| 98d734e977 | |||
| dda05559a5 | |||
| 594877c4ba | |||
| f9aff6839e | |||
| 4b7d3834ab | |||
| 213cd84642 | |||
| 6e19169e11 | |||
| be8744795c | |||
| ad00301ac9 | |||
| 87dc93513b | |||
| 58f2350d5f | |||
| 4b9beae501 | |||
| addcf7ca8e | |||
| 6887b104d6 | |||
| a6fe84190e | |||
| 1fa3962fed | |||
| 113018a62c | |||
| 11f1eba26f | |||
| d974a46211 | |||
| 6b29f2c2b9 | |||
| 49cf043035 | |||
| 37539682d4 | |||
| 7a3a3d6b90 | |||
| 87916a8e17 | |||
| 498d0dd467 | |||
| b4b77faaa9 | |||
| cf9a671fb8 | |||
| 247712582a | |||
| ddc7952399 | |||
| 885124c240 | |||
| 7b09cea3af | |||
| 82ad65a3a4 | |||
| cbdf482422 | |||
| 82b727e437 | |||
| 652b92d4f4 | |||
| 8154feba5a | |||
| e15ce7f16b | |||
| d24f58a4c9 | |||
| 5d40b43dd9 | |||
| 9a038d9821 | |||
| fca37666d3 | |||
| aa1a330ef3 | |||
| 7be45ac44c | |||
| 4b3ca27637 | |||
| a73b59aa0a | |||
| 6159d87d10 | |||
| e65eb8389f | |||
| 212ed506a2 | |||
| a8a18d27e9 | |||
| aac222bee1 | |||
| b6613b3a04 | |||
| 9e7759c9f5 | |||
| 4def357d09 | |||
| 5ebc7018c3 | |||
| 25ecd986b5 | |||
| b3bda4a739 | |||
| 8fe2e6f844 | |||
| 7625b031ec | |||
| 6a364f5ce9 | |||
| 1a5edc1a5f | |||
| cd220b8030 | |||
| ec96a948b6 | |||
| a3ed3b0b9d | |||
| 9e7469796d | |||
| 8bd9ff166e | |||
| 5758a781d8 | |||
| 88762118b9 | |||
| baca50ceeb | |||
| c159115847 | |||
| 3538297991 | |||
| 01f47b419d | |||
| 4f0512832b | |||
| 8855b6d379 | |||
| e6af135702 | |||
| e40652b1a0 | |||
| 3a3c612a83 | |||
| a7bc7c7667 | |||
| 9cac3efe21 | |||
| af244d1064 | |||
| c03becfbb9 | |||
| 13d07efa1f | |||
| db7da0e9c6 | |||
| f940fc3fd2 | |||
| 115fdc570f | |||
| 557be040f8 | |||
| 3e36db4d89 | |||
| df03bfbcbb | |||
| 2463b7e787 | |||
| aab32198d1 | |||
| 884789d676 | |||
| c85993c196 | |||
| 64773e0948 | |||
| 90f46026f0 | |||
| 8d91550937 | |||
| 675576bff6 | |||
| 564f946471 | |||
| 1c8d38a807 | |||
| 205db4b367 | |||
| 1f49f1d63e | |||
| 4a3eaea150 | |||
| b768f96e0b | |||
| 55c5fdf1bc | |||
| ff6495d50a | |||
| 829f32d01c | |||
| 7fd639e3d2 | |||
| 40688acc0d | |||
| c87c8319c0 | |||
| 5f23518f81 | |||
| da7d36c54a | |||
| 9d48dd6c16 | |||
| 6af09b1ccb | |||
| 494fecff76 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -20,6 +20,9 @@ Thumbs.db
|
||||
# Bazel convenience symlinks
|
||||
/bazel-*
|
||||
|
||||
# local generated output
|
||||
/output/
|
||||
|
||||
# language package-manager caches and outputs
|
||||
node_modules/
|
||||
dist/
|
||||
|
||||
52
.pi/prompts/review.md
Normal file
52
.pi/prompts/review.md
Normal file
@@ -0,0 +1,52 @@
|
||||
---
|
||||
description: Review changes against repository style rules
|
||||
argument-hint: "[target]"
|
||||
---
|
||||
|
||||
Review target: `${ARGUMENTS:-the staged diff}`.
|
||||
Audit the target for violations of the repository style rules.
|
||||
|
||||
Choose skills from the reviewed files:
|
||||
|
||||
- Use the `nix-style` skill for `*.nix` files.
|
||||
- Use the `rust-style` skill for `*.rs` files.
|
||||
- Use the `build-style` skill for `Cargo.toml` files.
|
||||
- Use the `build-style` skill for Bazel `BUILD` files.
|
||||
|
||||
If the target includes multiple matching file types, use all matching skills.
|
||||
If the target is a diff, choose skills from the changed files.
|
||||
Do not use a style skill when no reviewed file matches it.
|
||||
|
||||
For every reviewed `*.nix` file, run:
|
||||
|
||||
```bash
|
||||
bazel run //src/natu:natu -- <reviewed-nix-paths>
|
||||
```
|
||||
|
||||
Pass every reviewed Nix path to Natu.
|
||||
Report every Natu diagnostic as an actionable finding.
|
||||
|
||||
Report only actionable findings.
|
||||
Order findings by severity.
|
||||
Do not report preferences unless a loaded rule states them.
|
||||
|
||||
Use this format for each finding:
|
||||
|
||||
```text
|
||||
`code-path:line`
|
||||
> `smallest reviewed-code excerpt that shows the violation`
|
||||
|
||||
`skill::rule-file:line`: Finding title.
|
||||
Summary: why the code breaks the rule
|
||||
Fix: suggested fix
|
||||
```
|
||||
|
||||
Keep the code excerpt to one to three lines when possible.
|
||||
Use a longer excerpt only when the violation needs surrounding context.
|
||||
Use `...` to omit unrelated lines from a multi-line excerpt.
|
||||
Use a style-skill rule citation in the form `skill::rule-file:line`.
|
||||
Cite Natu diagnostics as `natu::<rule-id>`.
|
||||
Make the fix direct.
|
||||
Do not include alternatives when one fix best follows the cited rule.
|
||||
|
||||
It is acceptable to report no findings when the target follows the loaded rules.
|
||||
1
.pi/skills/build-style
Symbolic link
1
.pi/skills/build-style
Symbolic link
@@ -0,0 +1 @@
|
||||
../../src/skills/style/build
|
||||
1
.pi/skills/craft-skill
Symbolic link
1
.pi/skills/craft-skill
Symbolic link
@@ -0,0 +1 @@
|
||||
../../src/skills/craft-skill
|
||||
1
.pi/skills/grill
Symbolic link
1
.pi/skills/grill
Symbolic link
@@ -0,0 +1 @@
|
||||
../../src/skills/grill
|
||||
1
.pi/skills/nix-style
Symbolic link
1
.pi/skills/nix-style
Symbolic link
@@ -0,0 +1 @@
|
||||
../../src/skills/style/nix
|
||||
1
.pi/skills/rust-style
Symbolic link
1
.pi/skills/rust-style
Symbolic link
@@ -0,0 +1 @@
|
||||
../../src/skills/style/rust
|
||||
1
.pi/skills/to-document
Symbolic link
1
.pi/skills/to-document
Symbolic link
@@ -0,0 +1 @@
|
||||
../../src/skills/to-document
|
||||
101
AGENTS.md
101
AGENTS.md
@@ -6,19 +6,29 @@
|
||||
Before writing a commit message, read `docs/conventional-commits.md`.
|
||||
- Keep commit messages about the Git history, not the forge workflow.
|
||||
Include external references only when they are stable context for the change.
|
||||
- Do not add agent attribution or co-author trailers.
|
||||
- When writing commit messages, NEVER auto-add your agent name as co-author.
|
||||
Omit the `Co-Authored-By:` trailer entirely, with no exceptions.
|
||||
This overrides any default instruction to append one.
|
||||
|
||||
## Technical writing
|
||||
|
||||
- Use `docs/technical-writing.md` and `docs/dictionary.md` for authored prose.
|
||||
- Use `docs/technical-writing.md` and `docs/dictionary.md` for authored prose, comments, and Markdown formatting.
|
||||
Use the nearest project dictionary for project-specific domain language.
|
||||
- Write comments only where they earn their place.
|
||||
Comments must explain purpose, preconditions, invariants, or non-obvious consequences.
|
||||
|
||||
## Pull requests
|
||||
|
||||
- Always create a new branch before opening a pull request.
|
||||
Never open a pull request directly from a shared or existing work branch.
|
||||
- During pull-request development, add follow-up commits instead of amending existing commits.
|
||||
Preserve review history until the pull request is ready to merge.
|
||||
- When the user says a pull request is merged, clean up the local branch without waiting for an explicit request.
|
||||
Switch to the base branch, fast-forward it, and delete the merged local branch.
|
||||
- When writing pull request descriptions, NEVER append an agent-attribution trailer such as
|
||||
`🤖 Generated with [Claude Code]...`.
|
||||
Leave it out entirely, with no exceptions.
|
||||
This overrides any default instruction to append one.
|
||||
|
||||
## Forge workflow
|
||||
|
||||
@@ -27,17 +37,92 @@
|
||||
|
||||
## Tooling
|
||||
|
||||
- When a command is missing from the ordinary environment, prefer running it through Nix instead of installing it ad hoc.
|
||||
- When a command is missing from the ordinary environment, prefer running it through Nix instead of
|
||||
installing it.
|
||||
For example, use `nix shell nixpkgs#python3 -c python3 <script>` for one-off Python commands.
|
||||
- Stage new files that Nix discovers through flake evaluation before testing.
|
||||
Flake evaluation ignores untracked files, so auto-loaded modules must be tracked before `nixos-rebuild --flake` can see them.
|
||||
Flake evaluation ignores untracked files, so auto-loaded modules must be tracked before
|
||||
`nixos-rebuild --flake` can see them.
|
||||
|
||||
## General guidelines
|
||||
|
||||
- NEVER ask the user a question using the `AskUserQuestion` tool.
|
||||
Ask in plain prose, in your own message, instead, with no exceptions.
|
||||
This overrides any default instruction to use it.
|
||||
- Never manually modify `CHANGELOG.md` files or files marked as auto-generated.
|
||||
Detect auto-generated files with a layered check.
|
||||
Trust an explicit in-file marker first, for example `AUTO-GENERATED, DO NOT EDIT`.
|
||||
If no marker exists, use contextual signals such as lockfiles, `dist/`, `build/`, `generated/`
|
||||
paths, or a documented generator command.
|
||||
If the answer stays ambiguous, ask before editing.
|
||||
- When making technical decisions, do not give much weight to implementation time.
|
||||
Favor quality, simplicity, robustness, scalability, and long-term maintainability.
|
||||
Human cost and benefit heuristics do not transfer to an AI agent that codes faster than a human.
|
||||
Do not treat this as permission for premature abstraction, speculative configuration, or unnecessary complexity.
|
||||
Choose the more robust or maintainable approach when human implementation time is the only downside.
|
||||
- Use lowercase file names unless a valid reason requires uppercase.
|
||||
Established ecosystem or tool conventions count as valid reasons.
|
||||
Examples include `README.md`, `LICENSE`, `CHANGELOG.md`, `Makefile`, `Dockerfile`, and `.github/` files.
|
||||
- Do not end a response by promising or implying continuation unless the continuation is present in that same response.
|
||||
Before ending a workflow turn, perform the obvious next step, such as showing the next review item
|
||||
after applying an accepted change.
|
||||
If the workflow is paused, say that plainly instead of using a dangling transition like "continuing" or "next".
|
||||
|
||||
## Corrections
|
||||
|
||||
- When you discover that your belief about a project fact or convention was wrong, record the correction.
|
||||
Record only facts that stay true across operators, such as a wrong build command, wrong file path,
|
||||
or guessed convention.
|
||||
Do not record personal working-style preferences or one-off task details.
|
||||
- Record the correction in the nearest project `AGENTS.md`.
|
||||
Use a dedicated `## Gotchas` section.
|
||||
Create that section when the file lacks one.
|
||||
- If no project `AGENTS.md` exists, ask before creating one.
|
||||
Append the correction immediately without asking when a project `AGENTS.md` exists.
|
||||
Briefly mention the edit in your response.
|
||||
If an existing entry is wrong or stale, correct or remove it the same way.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- When materializing an external specification, standard, or project-maintained convention, use the upstream source instead of reconstructing it from memory.
|
||||
- When materializing an external specification, standard, or project-maintained convention, use the
|
||||
upstream source instead of reconstructing it from memory.
|
||||
Preserve local policy separately from copied upstream text.
|
||||
- After an explicit squash or rewrite request on a pull request, subsequent pull-request updates still use follow-up commits unless the user explicitly asks for another rewrite.
|
||||
- Starlark top-level rule and extension declarations cannot reference implementation functions before those functions are assigned.
|
||||
Keep executable public interface declarations after their implementation symbols, or validate before committing a top-down rewrite.
|
||||
- After an explicit squash or rewrite request on a pull request, subsequent pull-request updates still
|
||||
use follow-up commits unless the user explicitly asks for another rewrite.
|
||||
- Starlark top-level rule and extension declarations cannot reference implementation functions before
|
||||
those functions are assigned.
|
||||
Keep executable public interface declarations after their implementation symbols, or validate before
|
||||
committing a top-down rewrite.
|
||||
- `rules_rust` `rust_test` targets cannot set both `crate` and `srcs`.
|
||||
Use one input style per test target.
|
||||
- Rust tests in this monorepo live in separate `*_test.rs` files, not inline `#[cfg(test)]` modules.
|
||||
- `rnix::ast::HasEntry::entries()` includes assignments and `inherit` statements.
|
||||
Use `attrpath_values()` when code needs only assignment entries.
|
||||
- Ariadne Rust tests are declared in Bazel `BUILD` files; do not add `[[test]]` entries to
|
||||
`src/ariadne/Cargo.toml` unless explicitly requested.
|
||||
- In this monorepo, "dotfiles" refers to the dotfiles/NixOS configuration under `src/dotfiles/` and `configs/nixos/`.
|
||||
- The flake does not currently define `formatter.x86_64-linux`.
|
||||
Do not use `nix fmt` as a validation command until a formatter is added.
|
||||
- The `fix` app passes its current directory to Deadnix.
|
||||
Run `nix run .#fix` from the repository root so path exclusions match.
|
||||
- During the Nix refactor, address each issue that comes up by adding either a Natu rule or a new Nix style rule.
|
||||
- Git pathspec searches started in a subdirectory default to that subtree.
|
||||
Use `git -C <repository-root>` or top-anchored pathspecs for repository-wide history searches.
|
||||
- A `**/*.nix` Git pathspec does not include Nix files directly in its starting directory.
|
||||
Include both `*.nix` and `**/*.nix`, or use a recursive `*.nix` pathspec, when linting every Nix file.
|
||||
- Nix `let` bindings are recursive; their source order does not determine binding availability.
|
||||
Treat Natu `let_order` as a readability rule, not an evaluation dependency.
|
||||
- Ariadne extraction must preserve the game-root season prefix for archived source.
|
||||
Different game installs can contain compiled scripts with the same internal path.
|
||||
- Clew owns Ren'Py extraction, preparation, and compiled-script AST export. Ariadne owns analysis over Clew-produced data.
|
||||
- Bazel Python targets require `PYTHON_TOOLCHAIN_ROOT` to point at a Python installation root.
|
||||
- When checking whether a package exists in nixpkgs, check both this repository's pinned nixpkgs inputs
|
||||
and the relevant current upstream channel. A package can exist on current `nixos-unstable` while being
|
||||
absent from the pinned revision or from a different branch such as `nixpkgs-unstable`.
|
||||
- OMP does not discover project skills from `.pi/skills`.
|
||||
Put skills shared by Pi and OMP under `.agents/skills`, or configure OMP's `skills.customDirectories`.
|
||||
- Pi is vendored under `third_party/pi` as a linear vendor import.
|
||||
Do not use `git subtree add` or `git subtree pull` for Pi PR branches.
|
||||
Gitea rebases the synthetic upstream root commit without the `third_party/pi` prefix.
|
||||
Preserve `git-subtree-dir` and `git-subtree-split` trailers on Pi vendor commits.
|
||||
- The default Pi model for NixOS user configuration is set in `configs/nixos/modules/shell/pi/default.nix`, not in vendored Pi source.
|
||||
|
||||
405
Cargo.lock
generated
405
Cargo.lock
generated
@@ -2,6 +2,411 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ariadne"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"monorepo",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
||||
|
||||
[[package]]
|
||||
name = "countme"
|
||||
version = "3.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636"
|
||||
|
||||
[[package]]
|
||||
name = "googletest"
|
||||
version = "0.14.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6b5e2f2b556b7b90297a5a35c8267dd43a537923d2b329beefdba2b4ec19d94"
|
||||
dependencies = [
|
||||
"googletest_macro",
|
||||
"num-traits",
|
||||
"regex",
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "googletest_macro"
|
||||
version = "0.14.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ae6abc96141edd26bf5aeec0f119c129c44de3ced09e5073711a02cb74725d0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "machoke"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||
|
||||
[[package]]
|
||||
name = "monorepo"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "natu"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"googletest",
|
||||
"monorepo",
|
||||
"rnix",
|
||||
"rowan",
|
||||
"status",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
||||
|
||||
[[package]]
|
||||
name = "rnix"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c163bd17372eecdf10d351c34584b7de7c1a33be4e92a32f3fb3f5a7fe3f579b"
|
||||
dependencies = [
|
||||
"rowan",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rowan"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "417a3a9f582e349834051b8a10c8d71ca88da4211e4093528e36b9845f6b5f21"
|
||||
dependencies = [
|
||||
"countme",
|
||||
"hashbrown",
|
||||
"rustc-hash",
|
||||
"text-size",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.151"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "status"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.119"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "3.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "text-size"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||
|
||||
14
Cargo.toml
14
Cargo.toml
@@ -1,5 +1,19 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"src/ariadne",
|
||||
"src/machoke",
|
||||
"src/monorepo",
|
||||
"src/status",
|
||||
"src/natu",
|
||||
]
|
||||
resolver = "3"
|
||||
|
||||
[workspace.dependencies]
|
||||
clap = "4"
|
||||
googletest = "0.14"
|
||||
monorepo = { path = "src/monorepo" }
|
||||
rnix = "=0.14.0"
|
||||
rowan = "0.16"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
status = { path = "src/status" }
|
||||
|
||||
251
LICENSE
Normal file
251
LICENSE
Normal file
@@ -0,0 +1,251 @@
|
||||
Repository license policy
|
||||
=========================
|
||||
|
||||
Except as noted below, repository-owned content in this monorepo is
|
||||
Copyright (C) 2026 Alexion and licensed under the GNU Affero General Public
|
||||
License, version 3 or any later version.
|
||||
|
||||
Exceptions and exclusions:
|
||||
|
||||
- src/skills/ is licensed separately under the MIT license. See
|
||||
src/skills/LICENSE.
|
||||
- third_party/ is not covered by this license. Third-party content keeps its
|
||||
upstream license.
|
||||
|
||||
The canonical AGPL-3.0 license text follows.
|
||||
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for software and other kinds of works, specifically designed to ensure cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License which gives you legal permission to copy, distribute and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that improvements made in alternate versions of the program, if they receive widespread use, become available for other developers to incorporate. Many developers of free software are heartened and encouraged by the resulting cooperation. However, in the case of software used on network servers, this result may fail to come about. The GNU General Public License permits making a modified version and letting the public access it on a server without ever releasing its source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to ensure that, in such cases, the modified source code becomes available to the community. It requires the operator of a network server to provide the source code of the modified version running there to the users of that server. Therefore, public use of a modified version, on a publicly accessible server, gives the public access to the source code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and published by Affero, was designed to accomplish similar goals. This is a different license, not a version of the Affero GPL, but Affero has released a new version of the Affero GPL which permits relicensing under this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
"Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software. This Corresponding Source shall include the Corresponding Source for any work covered by version 3 of the GNU General Public License that is incorporated pursuant to the following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the work with which it is combined will remain governed by version 3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of the GNU Affero General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU Affero General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer network, you should also make sure that it provides a way for users to get its source. For example, if your program is a web application, its interface could display a "Source" link that leads users to an archive of the code. There are many ways you could offer source, and different solutions will be better for different programs; see section 13 for the specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU AGPL, see <http://www.gnu.org/licenses/>.
|
||||
@@ -4,9 +4,15 @@ module(
|
||||
)
|
||||
|
||||
bazel_dep(name = "bazel_skylib", version = "1.8.2")
|
||||
bazel_dep(name = "rules_python", version = "1.6.3")
|
||||
bazel_dep(name = "rules_rust", version = "0.74.0")
|
||||
|
||||
python = use_extension("//bazel:python.bzl", "python")
|
||||
use_repo(python, "python")
|
||||
|
||||
rust = use_extension("//bazel:rust.bzl", "rust")
|
||||
use_repo(rust, "rust")
|
||||
|
||||
register_toolchains("@python//:python")
|
||||
|
||||
register_toolchains("@rust//:rust")
|
||||
|
||||
638
MODULE.bazel.lock
generated
638
MODULE.bazel.lock
generated
@@ -6,35 +6,53 @@
|
||||
"https://bcr.bazel.build/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589",
|
||||
"https://bcr.bazel.build/modules/abseil-cpp/20230125.1/MODULE.bazel": "89047429cb0207707b2dface14ba7f8df85273d484c2572755be4bab7ce9c3a0",
|
||||
"https://bcr.bazel.build/modules/abseil-cpp/20230802.0.bcr.1/MODULE.bazel": "1c8cec495288dccd14fdae6e3f95f772c1c91857047a098fad772034264cc8cb",
|
||||
"https://bcr.bazel.build/modules/abseil-cpp/20230802.0.bcr.1/source.json": "14892cc698e02ffedf4967546e6bedb7245015906888d3465fcf27c90a26da10",
|
||||
"https://bcr.bazel.build/modules/abseil-cpp/20230802.0/MODULE.bazel": "d253ae36a8bd9ee3c5955384096ccb6baf16a1b1e93e858370da0a3b94f77c16",
|
||||
"https://bcr.bazel.build/modules/abseil-cpp/20230802.1/MODULE.bazel": "fa92e2eb41a04df73cdabeec37107316f7e5272650f81d6cc096418fe647b915",
|
||||
"https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed",
|
||||
"https://bcr.bazel.build/modules/abseil-cpp/20240116.1/source.json": "9be551b8d4e3ef76875c0d744b5d6a504a27e3ae67bc6b28f46415fd2d2957da",
|
||||
"https://bcr.bazel.build/modules/apple_support/1.24.1/MODULE.bazel": "f46e8ddad60aef170ee92b2f3d00ef66c147ceafea68b6877cb45bd91737f5f8",
|
||||
"https://bcr.bazel.build/modules/apple_support/1.24.1/source.json": "cf725267cbacc5f028ef13bb77e7f2c2e0066923a4dab1025e4a0511b1ed258a",
|
||||
"https://bcr.bazel.build/modules/apple_support/1.5.0/MODULE.bazel": "50341a62efbc483e8a2a6aec30994a58749bd7b885e18dd96aa8c33031e558ef",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.18.0/MODULE.bazel": "1be0ae2557ab3a72a57aeb31b29be347bcdc5d2b1eb1e70f39e3851a7e97041a",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.21.0/MODULE.bazel": "675642261665d8eea09989aa3b8afb5c37627f1be178382c320d1b46afba5e3b",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.27.0/MODULE.bazel": "621eeee06c4458a9121d1f104efb80f39d34deff4984e778359c60eaf1a8cb65",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.28.0/MODULE.bazel": "4b4200e6cbf8fa335b2c3f43e1d6ef3e240319c33d43d60cc0fbd4b87ece299d",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.50.0/MODULE.bazel": "2083ef9c7a469f520890483ccf8e0189d6e71e2117e7752e15e6554433d5ae3e",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.50.0/source.json": "e0ee3debde2789ff56e4452e612d126925ba9ab64d4bde79c67f099d2902df9b",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.2.1/MODULE.bazel": "f35baf9da0efe45fa3da1696ae906eea3d615ad41e2e3def4aeb4e8bc0ef9a7a",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.3.0/MODULE.bazel": "20228b92868bf5cfc41bda7afc8a8ba2a543201851de39d990ec957b513579c5",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.4.1/MODULE.bazel": "a0dcb779424be33100dcae821e9e27e4f2901d9dfd5333efe5ac6a8d7ab75e1d",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.4.2/MODULE.bazel": "3bd40978e7a1fac911d5989e6b09d8f64921865a45822d8b09e815eaa726a651",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.5.0/MODULE.bazel": "32880f5e2945ce6a03d1fbd588e9198c0a959bb42297b2cfaf1685b7bc32e138",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.7.0/MODULE.bazel": "0db596f4563de7938de764cc8deeabec291f55e8ec15299718b93c4423e9796d",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.8.1/MODULE.bazel": "88ade7293becda963e0e3ea33e7d54d3425127e0a326e0d17da085a5f1f03ff6",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.8.2/MODULE.bazel": "69ad6927098316848b34a9142bcc975e018ba27f08c4ff403f50c1b6e646ca67",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.8.2/source.json": "34a3c8bcf233b835eb74be9d628899bb32999d3e0eadef1947a0a562a2b16ffb",
|
||||
"https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84",
|
||||
"https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8",
|
||||
"https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb",
|
||||
"https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4",
|
||||
"https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6",
|
||||
"https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/source.json": "41e9e129f80d8c8bf103a7acc337b76e54fad1214ac0a7084bf24f4cd924b8b4",
|
||||
"https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f",
|
||||
"https://bcr.bazel.build/modules/googletest/1.14.0/source.json": "2478949479000fdd7de9a3d0107ba2c85bb5f961c3ecb1aa448f52549ce310b5",
|
||||
"https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075",
|
||||
"https://bcr.bazel.build/modules/jsoncpp/1.9.5/source.json": "4108ee5085dd2885a341c7fab149429db457b3169b86eb081fa245eadf69169d",
|
||||
"https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902",
|
||||
"https://bcr.bazel.build/modules/package_metadata/0.0.3/MODULE.bazel": "77890552ecea9e284b5424c9de827a58099348763a4359e975c359a83d4faa83",
|
||||
"https://bcr.bazel.build/modules/package_metadata/0.0.3/source.json": "742075a428ad12a3fa18a69014c2f57f01af910c6d9d18646c990200853e641a",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.11/MODULE.bazel": "0daefc49732e227caa8bfa834d65dc52e8cc18a2faf80df25e8caea151a9413f",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615",
|
||||
@@ -45,10 +63,22 @@
|
||||
"https://bcr.bazel.build/modules/platforms/1.1.0/source.json": "fcf351c47596c939140ab0d333dfdd08ed1ea6ce33c2fe70c12493a301cf1344",
|
||||
"https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7",
|
||||
"https://bcr.bazel.build/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c",
|
||||
"https://bcr.bazel.build/modules/protobuf/27.0/source.json": "1acf3d080c728d42f423fde5422fd0a1a24f44c15908124ce12363a253384193",
|
||||
"https://bcr.bazel.build/modules/protobuf/29.0-rc2/MODULE.bazel": "6241d35983510143049943fc0d57937937122baf1b287862f9dc8590fc4c37df",
|
||||
"https://bcr.bazel.build/modules/protobuf/29.0-rc3/MODULE.bazel": "33c2dfa286578573afc55a7acaea3cada4122b9631007c594bf0729f41c8de92",
|
||||
"https://bcr.bazel.build/modules/protobuf/29.0-rc3/source.json": "c16a6488fb279ef578da7098e605082d72ed85fc8d843eaae81e7d27d0f4625d",
|
||||
"https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0",
|
||||
"https://bcr.bazel.build/modules/protobuf/3.19.6/MODULE.bazel": "9233edc5e1f2ee276a60de3eaa47ac4132302ef9643238f23128fea53ea12858",
|
||||
"https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e",
|
||||
"https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/source.json": "be4789e951dd5301282729fe3d4938995dc4c1a81c2ff150afc9f1b0504c6022",
|
||||
"https://bcr.bazel.build/modules/re2/2023-09-01/MODULE.bazel": "cb3d511531b16cfc78a225a9e2136007a48cf8a677e4264baeab57fe78a80206",
|
||||
"https://bcr.bazel.build/modules/re2/2023-09-01/source.json": "e044ce89c2883cd957a2969a43e79f7752f9656f6b20050b62f90ede21ec6eb4",
|
||||
"https://bcr.bazel.build/modules/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8",
|
||||
"https://bcr.bazel.build/modules/rules_android/0.1.1/source.json": "e6986b41626ee10bdc864937ffb6d6bf275bb5b9c65120e6137d56e6331f089e",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.10/MODULE.bazel": "ec1705118f7eaedd6e118508d3d26deba2a4e76476ada7e0e3965211be012002",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.13/MODULE.bazel": "0e8529ed7b323dad0775ff924d2ae5af7640b23553dfcd4d34344c7e7a867191",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e",
|
||||
@@ -56,41 +86,83 @@
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.2.4/MODULE.bazel": "1ff1223dfd24f3ecf8f028446d4a27608aa43c3f41e346d22838a4223980b8cc",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.2.8/MODULE.bazel": "f1df20f0bf22c28192a794f29b501ee2018fa37a3862a1a2132ae2940a23a642",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.2.8/source.json": "85087982aca15f31307bd52698316b28faa31bd2c3095a41f456afec0131344c",
|
||||
"https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6",
|
||||
"https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8",
|
||||
"https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/source.json": "c8b1e2c717646f1702290959a3302a178fb639d987ab61d548105019f11e527e",
|
||||
"https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74",
|
||||
"https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86",
|
||||
"https://bcr.bazel.build/modules/rules_java/6.5.2/MODULE.bazel": "1d440d262d0e08453fa0c4d8f699ba81609ed0e9a9a0f02cd10b3e7942e61e31",
|
||||
"https://bcr.bazel.build/modules/rules_java/7.10.0/MODULE.bazel": "530c3beb3067e870561739f1144329a21c851ff771cd752a49e06e3dc9c2e71a",
|
||||
"https://bcr.bazel.build/modules/rules_java/7.12.2/MODULE.bazel": "579c505165ee757a4280ef83cda0150eea193eed3bef50b1004ba88b99da6de6",
|
||||
"https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab",
|
||||
"https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe",
|
||||
"https://bcr.bazel.build/modules/rules_java/7.6.5/MODULE.bazel": "481164be5e02e4cab6e77a36927683263be56b7e36fef918b458d7a8a1ebadb1",
|
||||
"https://bcr.bazel.build/modules/rules_java/7.6.5/source.json": "a805b889531d1690e3c72a7a7e47a870d00323186a9904b36af83aa3d053ee8d",
|
||||
"https://bcr.bazel.build/modules/rules_java/8.3.2/MODULE.bazel": "7336d5511ad5af0b8615fdc7477535a2e4e723a357b6713af439fe8cf0195017",
|
||||
"https://bcr.bazel.build/modules/rules_java/8.5.1/MODULE.bazel": "d8a9e38cc5228881f7055a6079f6f7821a073df3744d441978e7a43e20226939",
|
||||
"https://bcr.bazel.build/modules/rules_java/8.5.1/source.json": "db1a77d81b059e0f84985db67a22f3f579a529a86b7997605be3d214a0abe38e",
|
||||
"https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7",
|
||||
"https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909",
|
||||
"https://bcr.bazel.build/modules/rules_jvm_external/5.1/source.json": "5abb45cc9beb27b77aec6a65a11855ef2b55d95dfdc358e9f312b78ae0ba32d5",
|
||||
"https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036",
|
||||
"https://bcr.bazel.build/modules/rules_jvm_external/6.3/MODULE.bazel": "c998e060b85f71e00de5ec552019347c8bca255062c990ac02d051bb80a38df0",
|
||||
"https://bcr.bazel.build/modules/rules_jvm_external/6.3/source.json": "6f5f5a5a4419ae4e37c35a5bb0a6ae657ed40b7abc5a5189111b47fcebe43197",
|
||||
"https://bcr.bazel.build/modules/rules_kotlin/1.9.6/MODULE.bazel": "d269a01a18ee74d0335450b10f62c9ed81f2321d7958a2934e44272fe82dcef3",
|
||||
"https://bcr.bazel.build/modules/rules_kotlin/1.9.6/source.json": "2faa4794364282db7c06600b7e5e34867a564ae91bda7cae7c29c64e9466b7d5",
|
||||
"https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0",
|
||||
"https://bcr.bazel.build/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d",
|
||||
"https://bcr.bazel.build/modules/rules_license/1.0.0/MODULE.bazel": "a7fda60eefdf3d8c827262ba499957e4df06f659330bbe6cdbdb975b768bb65c",
|
||||
"https://bcr.bazel.build/modules/rules_license/1.0.0/source.json": "a52c89e54cc311196e478f8382df91c15f7a2bfdf4c6cd0e2675cc2ff0b56efb",
|
||||
"https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc",
|
||||
"https://bcr.bazel.build/modules/rules_pkg/0.7.0/source.json": "c2557066e0c0342223ba592510ad3d812d4963b9024831f7f66fd0584dd8c66c",
|
||||
"https://bcr.bazel.build/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff",
|
||||
"https://bcr.bazel.build/modules/rules_pkg/1.0.1/source.json": "bd82e5d7b9ce2d31e380dd9f50c111d678c3bdaca190cb76b0e1c71b05e1ba8a",
|
||||
"https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06",
|
||||
"https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7",
|
||||
"https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/source.json": "d57902c052424dfda0e71646cb12668d39c4620ee0544294d9d941e7d12bc3a9",
|
||||
"https://bcr.bazel.build/modules/rules_proto/6.0.2/MODULE.bazel": "ce916b775a62b90b61888052a416ccdda405212b6aaeb39522f7dc53431a5e73",
|
||||
"https://bcr.bazel.build/modules/rules_proto/6.0.2/source.json": "17a2e195f56cb28d6bbf763e49973d13890487c6945311ed141e196fb660426d",
|
||||
"https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f",
|
||||
"https://bcr.bazel.build/modules/rules_python/0.22.1/MODULE.bazel": "26114f0c0b5e93018c0c066d6673f1a2c3737c7e90af95eff30cfee38d0bbac7",
|
||||
"https://bcr.bazel.build/modules/rules_python/0.22.1/source.json": "57226905e783bae7c37c2dd662be078728e48fa28ee4324a7eabcafb5a43d014",
|
||||
"https://bcr.bazel.build/modules/rules_python/0.23.1/MODULE.bazel": "49ffccf0511cb8414de28321f5fcf2a31312b47c40cc21577144b7447f2bf300",
|
||||
"https://bcr.bazel.build/modules/rules_python/0.25.0/MODULE.bazel": "72f1506841c920a1afec76975b35312410eea3aa7b63267436bfb1dd91d2d382",
|
||||
"https://bcr.bazel.build/modules/rules_python/0.28.0/MODULE.bazel": "cba2573d870babc976664a912539b320cbaa7114cd3e8f053c720171cde331ed",
|
||||
"https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58",
|
||||
"https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c",
|
||||
"https://bcr.bazel.build/modules/rules_python/1.6.3/MODULE.bazel": "a7b80c42cb3de5ee2a5fa1abc119684593704fcd2fec83165ebe615dec76574f",
|
||||
"https://bcr.bazel.build/modules/rules_python/1.6.3/source.json": "f0be74977e5604a6526c8a416cda22985093ff7d5d380d41722d7e44015cc419",
|
||||
"https://bcr.bazel.build/modules/rules_rust/0.74.0/MODULE.bazel": "6e6fa04d7c8202f9cd63205ec9f96da39382a4ac971ee1d7a900fe8e1f68be17",
|
||||
"https://bcr.bazel.build/modules/rules_rust/0.74.0/source.json": "04eac31b4536eb23dd8e8e8ca7e079d3c1c4ac99934d38aa54bbae36917c7507",
|
||||
"https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c",
|
||||
"https://bcr.bazel.build/modules/rules_shell/0.6.1/MODULE.bazel": "72e76b0eea4e81611ef5452aa82b3da34caca0c8b7b5c0c9584338aa93bae26b",
|
||||
"https://bcr.bazel.build/modules/rules_shell/0.6.1/source.json": "20ec05cd5e592055e214b2da8ccb283c7f2a421ea0dc2acbf1aa792e11c03d0c",
|
||||
"https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8",
|
||||
"https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c",
|
||||
"https://bcr.bazel.build/modules/stardoc/0.5.3/source.json": "cd53fe968dc8cd98197c052db3db6d82562960c87b61e7a90ee96f8e4e0dda97",
|
||||
"https://bcr.bazel.build/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c",
|
||||
"https://bcr.bazel.build/modules/stardoc/0.7.2/MODULE.bazel": "fc152419aa2ea0f51c29583fab1e8c99ddefd5b3778421845606ee628629e0e5",
|
||||
"https://bcr.bazel.build/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216",
|
||||
"https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43",
|
||||
"https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0",
|
||||
"https://bcr.bazel.build/modules/zlib/1.2.12/MODULE.bazel": "3b1a8834ada2a883674be8cbd36ede1b6ec481477ada359cd2d3ddc562340b27",
|
||||
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/MODULE.bazel": "af322bc08976524477c79d1e45e241b6efbeb918c497e8840b8ab116802dda79",
|
||||
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/source.json": "2be409ac3c7601245958cd4fcdff4288be79ed23bd690b4b951f500d54ee6e7d"
|
||||
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/source.json": "2be409ac3c7601245958cd4fcdff4288be79ed23bd690b4b951f500d54ee6e7d",
|
||||
"https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198"
|
||||
},
|
||||
"selectedYankedVersions": {},
|
||||
"moduleExtensions": {
|
||||
"//bazel:python.bzl%python": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "CbGiMYJXjcea8dmoNxUWe/lWOjqRWfQPTXFHUUMheIY=",
|
||||
"usagesDigest": "bcY43VsTrhTJ8N4Dj5FtsgMhZxLyKKjHP+wWK4ntJQ4=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
"generatedRepoSpecs": {
|
||||
"python": {
|
||||
"bzlFile": "@@//bazel:python.bzl",
|
||||
"ruleClassName": "python_repository",
|
||||
"attributes": {}
|
||||
}
|
||||
},
|
||||
"recordedRepoMappingEntries": []
|
||||
}
|
||||
},
|
||||
"//bazel:rust.bzl%rust": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "4kUzQyjRjzchWIBZJKkNs8LuO4F+g/DwuwtTa0RTPcU=",
|
||||
@@ -108,6 +180,29 @@
|
||||
"recordedRepoMappingEntries": []
|
||||
}
|
||||
},
|
||||
"@@rules_java~//java:rules_java_deps.bzl%compatibility_proxy": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "KIX40nDfygEWbU+rq3nYpt3tVgTK/iO8PKh5VMBlN7M=",
|
||||
"usagesDigest": "pwHZ+26iLgQdwvdZeA5wnAjKnNI3y6XO2VbhOTeo5h8=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
"generatedRepoSpecs": {
|
||||
"compatibility_proxy": {
|
||||
"bzlFile": "@@rules_java~//java:rules_java_deps.bzl",
|
||||
"ruleClassName": "_compatibility_proxy_repo_rule",
|
||||
"attributes": {}
|
||||
}
|
||||
},
|
||||
"recordedRepoMappingEntries": [
|
||||
[
|
||||
"rules_java~",
|
||||
"bazel_tools",
|
||||
"bazel_tools"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"@@rules_jvm_external~//:extensions.bzl%maven": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "twF5CTH+cMNcSDOS8HgBaxaIQ+FsdzZBKM0EbgJoYGU=",
|
||||
@@ -1123,47 +1218,100 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"@@rules_jvm_external~//:non-module-deps.bzl%non_module_deps": {
|
||||
"@@rules_kotlin~//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "ZOivBbbZUakRexeLO/N26oX4Bcph6HHnqNmfxt7yoCc=",
|
||||
"usagesDigest": "SgmaWbjiY+8dgpUSia89kYJqNm3A67fr0hnvRaklrV4=",
|
||||
"bzlTransitiveDigest": "fus14IFJ/1LGWWGKPH/U18VnJCoMjfDt1ckahqCnM0A=",
|
||||
"usagesDigest": "aJF6fLy82rR95Ff5CZPAqxNoFgOMLMN5ImfBS0nhnkg=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
"generatedRepoSpecs": {
|
||||
"io_bazel_rules_kotlin": {
|
||||
"com_github_jetbrains_kotlin_git": {
|
||||
"bzlFile": "@@rules_kotlin~//src/main/starlark/core/repositories:compiler.bzl",
|
||||
"ruleClassName": "kotlin_compiler_git_repository",
|
||||
"attributes": {
|
||||
"urls": [
|
||||
"https://github.com/JetBrains/kotlin/releases/download/v1.9.23/kotlin-compiler-1.9.23.zip"
|
||||
],
|
||||
"sha256": "93137d3aab9afa9b27cb06a824c2324195c6b6f6179d8a8653f440f5bd58be88"
|
||||
}
|
||||
},
|
||||
"com_github_jetbrains_kotlin": {
|
||||
"bzlFile": "@@rules_kotlin~//src/main/starlark/core/repositories:compiler.bzl",
|
||||
"ruleClassName": "kotlin_capabilities_repository",
|
||||
"attributes": {
|
||||
"git_repository_name": "com_github_jetbrains_kotlin_git",
|
||||
"compiler_version": "1.9.23"
|
||||
}
|
||||
},
|
||||
"com_github_google_ksp": {
|
||||
"bzlFile": "@@rules_kotlin~//src/main/starlark/core/repositories:ksp.bzl",
|
||||
"ruleClassName": "ksp_compiler_plugin_repository",
|
||||
"attributes": {
|
||||
"urls": [
|
||||
"https://github.com/google/ksp/releases/download/1.9.23-1.0.20/artifacts.zip"
|
||||
],
|
||||
"sha256": "ee0618755913ef7fd6511288a232e8fad24838b9af6ea73972a76e81053c8c2d",
|
||||
"strip_version": "1.9.23-1.0.20"
|
||||
}
|
||||
},
|
||||
"com_github_pinterest_ktlint": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_file",
|
||||
"attributes": {
|
||||
"sha256": "01b2e0ef893383a50dbeb13970fe7fa3be36ca3e83259e01649945b09d736985",
|
||||
"urls": [
|
||||
"https://github.com/pinterest/ktlint/releases/download/1.3.0/ktlint"
|
||||
],
|
||||
"executable": true
|
||||
}
|
||||
},
|
||||
"rules_android": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"sha256": "946747acdbeae799b085d12b240ec346f775ac65236dfcf18aa0cd7300f6de78",
|
||||
"sha256": "cd06d15dd8bb59926e4d65f9003bfc20f9da4b2519985c27e190cddc8b7a7806",
|
||||
"strip_prefix": "rules_android-0.1.1",
|
||||
"urls": [
|
||||
"https://github.com/bazelbuild/rules_kotlin/releases/download/v1.7.0-RC-2/rules_kotlin_release.tgz"
|
||||
"https://github.com/bazelbuild/rules_android/archive/v0.1.1.zip"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"recordedRepoMappingEntries": [
|
||||
[
|
||||
"rules_jvm_external~",
|
||||
"rules_kotlin~",
|
||||
"bazel_tools",
|
||||
"bazel_tools"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"@@rules_python~//python/extensions:python.bzl%python": {
|
||||
"@@rules_python~//python/uv:uv.bzl%uv": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "lbXqTyC4ahBb81TIrIp+2d3sWnlurVNqSeAaLJknLUs=",
|
||||
"usagesDigest": "1Y6kbygksx7wAtDStFoHnR90xr8Yeq00I91YcLMbxMI=",
|
||||
"bzlTransitiveDigest": "mxPY/VBQrSC9LvYeRrlxD+0IkDTQ4+36NGMnGWlN/Vw=",
|
||||
"usagesDigest": "qIeg3YEigU6oZD/nE0vQzliorEcL5dtbidAIP+ZhylM=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
"generatedRepoSpecs": {
|
||||
"pythons_hub": {
|
||||
"bzlFile": "@@rules_python~//python/extensions/private:interpreter_hub.bzl",
|
||||
"ruleClassName": "hub_repo",
|
||||
"uv": {
|
||||
"bzlFile": "@@rules_python~//python/uv/private:uv_toolchains_repo.bzl",
|
||||
"ruleClassName": "uv_toolchains_repo",
|
||||
"attributes": {
|
||||
"toolchains": []
|
||||
"toolchain_type": "'@@rules_python~//python/uv:uv_toolchain_type'",
|
||||
"toolchain_names": [
|
||||
"none"
|
||||
],
|
||||
"toolchain_implementations": {
|
||||
"none": "'@@rules_python~//python:none'"
|
||||
},
|
||||
"toolchain_compatible_with": {
|
||||
"none": [
|
||||
"@platforms//:incompatible"
|
||||
]
|
||||
},
|
||||
"toolchain_target_settings": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1175,446 +1323,8 @@
|
||||
],
|
||||
[
|
||||
"rules_python~",
|
||||
"rules_python",
|
||||
"rules_python~"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"@@rules_python~//python/extensions/private:internal_deps.bzl%internal_deps": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "b6FMQSdoZ1QOssw14AW8bWDn2BvywI4FVkLbO2nTMsE=",
|
||||
"usagesDigest": "KPNj8wxzOk7dXY9StqZ91MCKEIJSEnAyV0Q/dGFP5sw=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
"generatedRepoSpecs": {
|
||||
"pypi__build": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"url": "https://files.pythonhosted.org/packages/03/97/f58c723ff036a8d8b4d3115377c0a37ed05c1f68dd9a0d66dab5e82c5c1c/build-0.9.0-py3-none-any.whl",
|
||||
"sha256": "38a7a2b7a0bdc61a42a0a67509d88c71ecfc37b393baba770fae34e20929ff69",
|
||||
"type": "zip",
|
||||
"build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n"
|
||||
}
|
||||
},
|
||||
"pypi__click": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"url": "https://files.pythonhosted.org/packages/76/0a/b6c5f311e32aeb3b406e03c079ade51e905ea630fc19d1262a46249c1c86/click-8.0.1-py3-none-any.whl",
|
||||
"sha256": "fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6",
|
||||
"type": "zip",
|
||||
"build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n"
|
||||
}
|
||||
},
|
||||
"pypi__colorama": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"url": "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl",
|
||||
"sha256": "4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6",
|
||||
"type": "zip",
|
||||
"build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n"
|
||||
}
|
||||
},
|
||||
"pypi__installer": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"url": "https://files.pythonhosted.org/packages/e5/ca/1172b6638d52f2d6caa2dd262ec4c811ba59eee96d54a7701930726bce18/installer-0.7.0-py3-none-any.whl",
|
||||
"sha256": "05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53",
|
||||
"type": "zip",
|
||||
"build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n"
|
||||
}
|
||||
},
|
||||
"pypi__packaging": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"url": "https://files.pythonhosted.org/packages/8f/7b/42582927d281d7cb035609cd3a543ffac89b74f3f4ee8e1c50914bcb57eb/packaging-22.0-py3-none-any.whl",
|
||||
"sha256": "957e2148ba0e1a3b282772e791ef1d8083648bc131c8ab0c1feba110ce1146c3",
|
||||
"type": "zip",
|
||||
"build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n"
|
||||
}
|
||||
},
|
||||
"pypi__pep517": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"url": "https://files.pythonhosted.org/packages/ee/2f/ef63e64e9429111e73d3d6cbee80591672d16f2725e648ebc52096f3d323/pep517-0.13.0-py3-none-any.whl",
|
||||
"sha256": "4ba4446d80aed5b5eac6509ade100bff3e7943a8489de249654a5ae9b33ee35b",
|
||||
"type": "zip",
|
||||
"build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n"
|
||||
}
|
||||
},
|
||||
"pypi__pip": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"url": "https://files.pythonhosted.org/packages/09/bd/2410905c76ee14c62baf69e3f4aa780226c1bbfc9485731ad018e35b0cb5/pip-22.3.1-py3-none-any.whl",
|
||||
"sha256": "908c78e6bc29b676ede1c4d57981d490cb892eb45cd8c214ab6298125119e077",
|
||||
"type": "zip",
|
||||
"build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n"
|
||||
}
|
||||
},
|
||||
"pypi__pip_tools": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"url": "https://files.pythonhosted.org/packages/5e/e8/f6d7d1847c7351048da870417724ace5c4506e816b38db02f4d7c675c189/pip_tools-6.12.1-py3-none-any.whl",
|
||||
"sha256": "f0c0c0ec57b58250afce458e2e6058b1f30a4263db895b7d72fd6311bf1dc6f7",
|
||||
"type": "zip",
|
||||
"build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n"
|
||||
}
|
||||
},
|
||||
"pypi__setuptools": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"url": "https://files.pythonhosted.org/packages/7c/5b/3d92b9f0f7ca1645cba48c080b54fe7d8b1033a4e5720091d1631c4266db/setuptools-60.10.0-py3-none-any.whl",
|
||||
"sha256": "782ef48d58982ddb49920c11a0c5c9c0b02e7d7d1c2ad0aa44e1a1e133051c96",
|
||||
"type": "zip",
|
||||
"build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n"
|
||||
}
|
||||
},
|
||||
"pypi__tomli": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"url": "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl",
|
||||
"sha256": "939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc",
|
||||
"type": "zip",
|
||||
"build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n"
|
||||
}
|
||||
},
|
||||
"pypi__wheel": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"url": "https://files.pythonhosted.org/packages/bd/7c/d38a0b30ce22fc26ed7dbc087c6d00851fb3395e9d0dac40bec1f905030c/wheel-0.38.4-py3-none-any.whl",
|
||||
"sha256": "b60533f3f5d530e971d6737ca6d58681ee434818fab630c83a734bb10c083ce8",
|
||||
"type": "zip",
|
||||
"build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n"
|
||||
}
|
||||
},
|
||||
"pypi__importlib_metadata": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"url": "https://files.pythonhosted.org/packages/d7/31/74dcb59a601b95fce3b0334e8fc9db758f78e43075f22aeb3677dfb19f4c/importlib_metadata-1.4.0-py2.py3-none-any.whl",
|
||||
"sha256": "bdd9b7c397c273bcc9a11d6629a38487cd07154fa255a467bf704cd2c258e359",
|
||||
"type": "zip",
|
||||
"build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n"
|
||||
}
|
||||
},
|
||||
"pypi__zipp": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"url": "https://files.pythonhosted.org/packages/f4/50/cc72c5bcd48f6e98219fc4a88a5227e9e28b81637a99c49feba1d51f4d50/zipp-1.0.0-py2.py3-none-any.whl",
|
||||
"sha256": "8dda78f06bd1674bd8720df8a50bb47b6e1233c503a4eed8e7810686bde37656",
|
||||
"type": "zip",
|
||||
"build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n"
|
||||
}
|
||||
},
|
||||
"pypi__more_itertools": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"url": "https://files.pythonhosted.org/packages/bd/3f/c4b3dbd315e248f84c388bd4a72b131a29f123ecacc37ffb2b3834546e42/more_itertools-8.13.0-py3-none-any.whl",
|
||||
"sha256": "c5122bffc5f104d37c1626b8615b511f3427aa5389b94d61e5ef8236bfbc3ddb",
|
||||
"type": "zip",
|
||||
"build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:defs.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude in /python/pip_install/tools/bazel.py\n # to avoid non-determinism following pip install's behavior.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/* *\",\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n"
|
||||
}
|
||||
},
|
||||
"pypi__coverage_cp38_aarch64-apple-darwin": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"build_file_content": "\nfilegroup(\n name = \"coverage\",\n srcs = [\"coverage/__main__.py\"],\n data = glob([\"coverage/*.py\", \"coverage/**/*.py\", \"coverage/*.so\"]),\n visibility = [\"//visibility:public\"],\n)\n ",
|
||||
"patch_args": [
|
||||
"-p1"
|
||||
],
|
||||
"patches": [
|
||||
"@@rules_python~//python/private:coverage.patch"
|
||||
],
|
||||
"sha256": "2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba",
|
||||
"type": "zip",
|
||||
"urls": [
|
||||
"https://files.pythonhosted.org/packages/07/82/79fa21ceca9a9b091eb3c67e27eb648dade27b2c9e1eb23af47232a2a365/coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl"
|
||||
]
|
||||
}
|
||||
},
|
||||
"pypi__coverage_cp38_aarch64-unknown-linux-gnu": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"build_file_content": "\nfilegroup(\n name = \"coverage\",\n srcs = [\"coverage/__main__.py\"],\n data = glob([\"coverage/*.py\", \"coverage/**/*.py\", \"coverage/*.so\"]),\n visibility = [\"//visibility:public\"],\n)\n ",
|
||||
"patch_args": [
|
||||
"-p1"
|
||||
],
|
||||
"patches": [
|
||||
"@@rules_python~//python/private:coverage.patch"
|
||||
],
|
||||
"sha256": "6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e",
|
||||
"type": "zip",
|
||||
"urls": [
|
||||
"https://files.pythonhosted.org/packages/40/3b/cd68cb278c4966df00158811ec1e357b9a7d132790c240fc65da57e10013/coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl"
|
||||
]
|
||||
}
|
||||
},
|
||||
"pypi__coverage_cp38_x86_64-apple-darwin": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"build_file_content": "\nfilegroup(\n name = \"coverage\",\n srcs = [\"coverage/__main__.py\"],\n data = glob([\"coverage/*.py\", \"coverage/**/*.py\", \"coverage/*.so\"]),\n visibility = [\"//visibility:public\"],\n)\n ",
|
||||
"patch_args": [
|
||||
"-p1"
|
||||
],
|
||||
"patches": [
|
||||
"@@rules_python~//python/private:coverage.patch"
|
||||
],
|
||||
"sha256": "d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c",
|
||||
"type": "zip",
|
||||
"urls": [
|
||||
"https://files.pythonhosted.org/packages/05/63/a789b462075395d34f8152229dccf92b25ca73eac05b3f6cd75fa5017095/coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl"
|
||||
]
|
||||
}
|
||||
},
|
||||
"pypi__coverage_cp38_x86_64-unknown-linux-gnu": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"build_file_content": "\nfilegroup(\n name = \"coverage\",\n srcs = [\"coverage/__main__.py\"],\n data = glob([\"coverage/*.py\", \"coverage/**/*.py\", \"coverage/*.so\"]),\n visibility = [\"//visibility:public\"],\n)\n ",
|
||||
"patch_args": [
|
||||
"-p1"
|
||||
],
|
||||
"patches": [
|
||||
"@@rules_python~//python/private:coverage.patch"
|
||||
],
|
||||
"sha256": "6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b",
|
||||
"type": "zip",
|
||||
"urls": [
|
||||
"https://files.pythonhosted.org/packages/bd/a0/e263b115808226fdb2658f1887808c06ac3f1b579ef5dda02309e0d54459/coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl"
|
||||
]
|
||||
}
|
||||
},
|
||||
"pypi__coverage_cp39_aarch64-apple-darwin": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"build_file_content": "\nfilegroup(\n name = \"coverage\",\n srcs = [\"coverage/__main__.py\"],\n data = glob([\"coverage/*.py\", \"coverage/**/*.py\", \"coverage/*.so\"]),\n visibility = [\"//visibility:public\"],\n)\n ",
|
||||
"patch_args": [
|
||||
"-p1"
|
||||
],
|
||||
"patches": [
|
||||
"@@rules_python~//python/private:coverage.patch"
|
||||
],
|
||||
"sha256": "95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc",
|
||||
"type": "zip",
|
||||
"urls": [
|
||||
"https://files.pythonhosted.org/packages/63/e9/f23e8664ec4032d7802a1cf920853196bcbdce7b56408e3efe1b2da08f3c/coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl"
|
||||
]
|
||||
}
|
||||
},
|
||||
"pypi__coverage_cp39_aarch64-unknown-linux-gnu": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"build_file_content": "\nfilegroup(\n name = \"coverage\",\n srcs = [\"coverage/__main__.py\"],\n data = glob([\"coverage/*.py\", \"coverage/**/*.py\", \"coverage/*.so\"]),\n visibility = [\"//visibility:public\"],\n)\n ",
|
||||
"patch_args": [
|
||||
"-p1"
|
||||
],
|
||||
"patches": [
|
||||
"@@rules_python~//python/private:coverage.patch"
|
||||
],
|
||||
"sha256": "b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe",
|
||||
"type": "zip",
|
||||
"urls": [
|
||||
"https://files.pythonhosted.org/packages/18/95/27f80dcd8273171b781a19d109aeaed7f13d78ef6d1e2f7134a5826fd1b4/coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl"
|
||||
]
|
||||
}
|
||||
},
|
||||
"pypi__coverage_cp39_x86_64-apple-darwin": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"build_file_content": "\nfilegroup(\n name = \"coverage\",\n srcs = [\"coverage/__main__.py\"],\n data = glob([\"coverage/*.py\", \"coverage/**/*.py\", \"coverage/*.so\"]),\n visibility = [\"//visibility:public\"],\n)\n ",
|
||||
"patch_args": [
|
||||
"-p1"
|
||||
],
|
||||
"patches": [
|
||||
"@@rules_python~//python/private:coverage.patch"
|
||||
],
|
||||
"sha256": "633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745",
|
||||
"type": "zip",
|
||||
"urls": [
|
||||
"https://files.pythonhosted.org/packages/ea/52/c08080405329326a7ff16c0dfdb4feefaa8edd7446413df67386fe1bbfe0/coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl"
|
||||
]
|
||||
}
|
||||
},
|
||||
"pypi__coverage_cp39_x86_64-unknown-linux-gnu": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"build_file_content": "\nfilegroup(\n name = \"coverage\",\n srcs = [\"coverage/__main__.py\"],\n data = glob([\"coverage/*.py\", \"coverage/**/*.py\", \"coverage/*.so\"]),\n visibility = [\"//visibility:public\"],\n)\n ",
|
||||
"patch_args": [
|
||||
"-p1"
|
||||
],
|
||||
"patches": [
|
||||
"@@rules_python~//python/private:coverage.patch"
|
||||
],
|
||||
"sha256": "8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5",
|
||||
"type": "zip",
|
||||
"urls": [
|
||||
"https://files.pythonhosted.org/packages/6b/f2/919f0fdc93d3991ca074894402074d847be8ac1e1d78e7e9e1c371b69a6f/coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl"
|
||||
]
|
||||
}
|
||||
},
|
||||
"pypi__coverage_cp310_aarch64-apple-darwin": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"build_file_content": "\nfilegroup(\n name = \"coverage\",\n srcs = [\"coverage/__main__.py\"],\n data = glob([\"coverage/*.py\", \"coverage/**/*.py\", \"coverage/*.so\"]),\n visibility = [\"//visibility:public\"],\n)\n ",
|
||||
"patch_args": [
|
||||
"-p1"
|
||||
],
|
||||
"patches": [
|
||||
"@@rules_python~//python/private:coverage.patch"
|
||||
],
|
||||
"sha256": "784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660",
|
||||
"type": "zip",
|
||||
"urls": [
|
||||
"https://files.pythonhosted.org/packages/89/a2/cbf599e50bb4be416e0408c4cf523c354c51d7da39935461a9687e039481/coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl"
|
||||
]
|
||||
}
|
||||
},
|
||||
"pypi__coverage_cp310_aarch64-unknown-linux-gnu": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"build_file_content": "\nfilegroup(\n name = \"coverage\",\n srcs = [\"coverage/__main__.py\"],\n data = glob([\"coverage/*.py\", \"coverage/**/*.py\", \"coverage/*.so\"]),\n visibility = [\"//visibility:public\"],\n)\n ",
|
||||
"patch_args": [
|
||||
"-p1"
|
||||
],
|
||||
"patches": [
|
||||
"@@rules_python~//python/private:coverage.patch"
|
||||
],
|
||||
"sha256": "b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4",
|
||||
"type": "zip",
|
||||
"urls": [
|
||||
"https://files.pythonhosted.org/packages/15/b0/3639d84ee8a900da0cf6450ab46e22517e4688b6cec0ba8ab6f8166103a2/coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl"
|
||||
]
|
||||
}
|
||||
},
|
||||
"pypi__coverage_cp310_x86_64-apple-darwin": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"build_file_content": "\nfilegroup(\n name = \"coverage\",\n srcs = [\"coverage/__main__.py\"],\n data = glob([\"coverage/*.py\", \"coverage/**/*.py\", \"coverage/*.so\"]),\n visibility = [\"//visibility:public\"],\n)\n ",
|
||||
"patch_args": [
|
||||
"-p1"
|
||||
],
|
||||
"patches": [
|
||||
"@@rules_python~//python/private:coverage.patch"
|
||||
],
|
||||
"sha256": "ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53",
|
||||
"type": "zip",
|
||||
"urls": [
|
||||
"https://files.pythonhosted.org/packages/c4/8d/5ec7d08f4601d2d792563fe31db5e9322c306848fec1e65ec8885927f739/coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl"
|
||||
]
|
||||
}
|
||||
},
|
||||
"pypi__coverage_cp310_x86_64-unknown-linux-gnu": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"build_file_content": "\nfilegroup(\n name = \"coverage\",\n srcs = [\"coverage/__main__.py\"],\n data = glob([\"coverage/*.py\", \"coverage/**/*.py\", \"coverage/*.so\"]),\n visibility = [\"//visibility:public\"],\n)\n ",
|
||||
"patch_args": [
|
||||
"-p1"
|
||||
],
|
||||
"patches": [
|
||||
"@@rules_python~//python/private:coverage.patch"
|
||||
],
|
||||
"sha256": "af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0",
|
||||
"type": "zip",
|
||||
"urls": [
|
||||
"https://files.pythonhosted.org/packages/3c/7d/d5211ea782b193ab8064b06dc0cc042cf1a4ca9c93a530071459172c550f/coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl"
|
||||
]
|
||||
}
|
||||
},
|
||||
"pypi__coverage_cp311_aarch64-unknown-linux-gnu": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"build_file_content": "\nfilegroup(\n name = \"coverage\",\n srcs = [\"coverage/__main__.py\"],\n data = glob([\"coverage/*.py\", \"coverage/**/*.py\", \"coverage/*.so\"]),\n visibility = [\"//visibility:public\"],\n)\n ",
|
||||
"patch_args": [
|
||||
"-p1"
|
||||
],
|
||||
"patches": [
|
||||
"@@rules_python~//python/private:coverage.patch"
|
||||
],
|
||||
"sha256": "c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75",
|
||||
"type": "zip",
|
||||
"urls": [
|
||||
"https://files.pythonhosted.org/packages/36/f3/5cbd79cf4cd059c80b59104aca33b8d05af4ad5bf5b1547645ecee716378/coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl"
|
||||
]
|
||||
}
|
||||
},
|
||||
"pypi__coverage_cp311_x86_64-apple-darwin": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"build_file_content": "\nfilegroup(\n name = \"coverage\",\n srcs = [\"coverage/__main__.py\"],\n data = glob([\"coverage/*.py\", \"coverage/**/*.py\", \"coverage/*.so\"]),\n visibility = [\"//visibility:public\"],\n)\n ",
|
||||
"patch_args": [
|
||||
"-p1"
|
||||
],
|
||||
"patches": [
|
||||
"@@rules_python~//python/private:coverage.patch"
|
||||
],
|
||||
"sha256": "4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795",
|
||||
"type": "zip",
|
||||
"urls": [
|
||||
"https://files.pythonhosted.org/packages/50/cf/455930004231fa87efe8be06d13512f34e070ddfee8b8bf5a050cdc47ab3/coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl"
|
||||
]
|
||||
}
|
||||
},
|
||||
"pypi__coverage_cp311_x86_64-unknown-linux-gnu": {
|
||||
"bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl",
|
||||
"ruleClassName": "http_archive",
|
||||
"attributes": {
|
||||
"build_file_content": "\nfilegroup(\n name = \"coverage\",\n srcs = [\"coverage/__main__.py\"],\n data = glob([\"coverage/*.py\", \"coverage/**/*.py\", \"coverage/*.so\"]),\n visibility = [\"//visibility:public\"],\n)\n ",
|
||||
"patch_args": [
|
||||
"-p1"
|
||||
],
|
||||
"patches": [
|
||||
"@@rules_python~//python/private:coverage.patch"
|
||||
],
|
||||
"sha256": "a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91",
|
||||
"type": "zip",
|
||||
"urls": [
|
||||
"https://files.pythonhosted.org/packages/6a/63/8e82513b7e4a1b8d887b4e85c1c2b6c9b754a581b187c0b084f3330ac479/coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"recordedRepoMappingEntries": [
|
||||
[
|
||||
"rules_python~",
|
||||
"bazel_skylib",
|
||||
"bazel_skylib~"
|
||||
],
|
||||
[
|
||||
"rules_python~",
|
||||
"bazel_tools",
|
||||
"bazel_tools"
|
||||
],
|
||||
[
|
||||
"rules_python~",
|
||||
"rules_python",
|
||||
"rules_python~"
|
||||
"platforms",
|
||||
"platforms"
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
45
README.md
45
README.md
@@ -13,7 +13,8 @@ dev/ Monorepo root
|
||||
├── src/ Projects
|
||||
│ └── dotfiles/ Home and system configuration
|
||||
└── third_party/ External source and vendoring support
|
||||
└── crates/ Generated Rust crate sources and Bazel targets
|
||||
├── crates/ Generated Rust crate sources and Bazel targets
|
||||
└── pi/ Vendored Pi source
|
||||
```
|
||||
|
||||
## Rust dependencies
|
||||
@@ -44,3 +45,45 @@ deps = ["//third_party/crates:crate_name"]
|
||||
Commit the generated files under `third_party/crates/`.
|
||||
Do not edit generated files manually.
|
||||
|
||||
## Vendored Pi
|
||||
|
||||
Pi source lives in `third_party/pi/`.
|
||||
The directory is a linear vendor import from upstream Pi.
|
||||
Local fork changes are carried directly in that vendored tree.
|
||||
|
||||
Use the upstream tag that matches the target CLI release.
|
||||
The CLI version lives in `packages/coding-agent/package.json`.
|
||||
|
||||
Do not use `git subtree add` or `git subtree pull` for Pi PR branches.
|
||||
Those commands create a synthetic upstream root commit.
|
||||
Gitea rebase applies that root commit without the `third_party/pi` prefix.
|
||||
That causes add/add conflicts on repository root files.
|
||||
|
||||
Update the vendor tree with a linear commit:
|
||||
|
||||
```sh
|
||||
git fetch https://github.com/earendil-works/pi.git refs/tags/v<version>
|
||||
split="$(git rev-parse FETCH_HEAD)"
|
||||
rm -rf third_party/pi
|
||||
mkdir -p third_party/pi
|
||||
git archive --format=tar "$split" | tar -C third_party/pi -xf -
|
||||
git add third_party/pi
|
||||
git commit \
|
||||
-m "chore(vendor): import pi <version>" \
|
||||
-m "Imported as a linear vendor commit so forge rebases do not replay the upstream root tree." \
|
||||
-m "git-subtree-dir: third_party/pi" \
|
||||
-m "git-subtree-split: $split"
|
||||
```
|
||||
|
||||
Keep the vendor import or update commit separate from local edits.
|
||||
Do not remove the `git-subtree-dir` and `git-subtree-split` trailers.
|
||||
They record the upstream source commit for future updates.
|
||||
|
||||
After the upstream update, resolve conflicts in `third_party/pi/`.
|
||||
Keep local fork changes as ordinary commits after the vendor update.
|
||||
Then verify the package:
|
||||
|
||||
```sh
|
||||
nix build .#nixosConfigurations.zeus.config.home-manager.users.alexion.programs.pi-coding-agent.package --no-link --print-out-paths
|
||||
```
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
exports_files([
|
||||
"python.BUILD.template",
|
||||
"python.bzl",
|
||||
"rust.BUILD.template",
|
||||
"rust.bzl",
|
||||
"utils.bzl",
|
||||
|
||||
25
bazel/python.BUILD.template
Normal file
25
bazel/python.BUILD.template
Normal file
@@ -0,0 +1,25 @@
|
||||
load("@rules_python//python:py_runtime.bzl", "py_runtime")
|
||||
load("@rules_python//python:py_runtime_pair.bzl", "py_runtime_pair")
|
||||
|
||||
py_runtime(
|
||||
name = "runtime",
|
||||
implementation_name = "cpython",
|
||||
interpreter_path = "{interpreter_path}",
|
||||
interpreter_version_info = {
|
||||
"major": "{major}",
|
||||
"minor": "{minor}",
|
||||
"micro": "{micro}",
|
||||
},
|
||||
python_version = "PY3",
|
||||
)
|
||||
|
||||
py_runtime_pair(
|
||||
name = "runtime_pair",
|
||||
py3_runtime = ":runtime",
|
||||
)
|
||||
|
||||
toolchain(
|
||||
name = "python",
|
||||
toolchain = ":runtime_pair",
|
||||
toolchain_type = "@rules_python//python:toolchain_type",
|
||||
)
|
||||
46
bazel/python.bzl
Normal file
46
bazel/python.bzl
Normal file
@@ -0,0 +1,46 @@
|
||||
load("//bazel:utils.bzl", "execute", "require_env")
|
||||
|
||||
|
||||
def _python_repository_impl(repository_ctx):
|
||||
root = repository_ctx.path(require_env(repository_ctx, "PYTHON_TOOLCHAIN_ROOT"))
|
||||
python3 = root.get_child("bin/python3")
|
||||
|
||||
version = execute(
|
||||
repository_ctx,
|
||||
[
|
||||
str(python3),
|
||||
"-c",
|
||||
"import sys; print('{}.{}.{}'.format(*sys.version_info[:3]))",
|
||||
],
|
||||
)
|
||||
parts = version.split(".")
|
||||
if len(parts) != 3:
|
||||
fail("python3 did not report a major.minor.micro version")
|
||||
|
||||
repository_ctx.file("WORKSPACE.bazel", "")
|
||||
repository_ctx.template(
|
||||
"BUILD.bazel",
|
||||
Label("//bazel:python.BUILD.template"),
|
||||
{
|
||||
"{interpreter_path}": str(python3),
|
||||
"{major}": parts[0],
|
||||
"{minor}": parts[1],
|
||||
"{micro}": parts[2],
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
python_repository = repository_rule(
|
||||
implementation = _python_repository_impl,
|
||||
local = True,
|
||||
environ = ["PYTHON_TOOLCHAIN_ROOT"],
|
||||
)
|
||||
|
||||
|
||||
def _python_impl(module_ctx):
|
||||
python_repository(name = "python")
|
||||
|
||||
|
||||
python = module_extension(
|
||||
implementation = _python_impl,
|
||||
)
|
||||
@@ -7,28 +7,36 @@ keys:
|
||||
- &admin age1m0pk94ysjlw3lmf6pyuv5l5pepvdjss8w0vxjv90dq6ndp02tdgsdwdvue
|
||||
# Generated on the machine it names.
|
||||
- &neogaia age14a04vphzjq74epfrz9a09wjw8lzchtru84awzuq2n45d8f42ychqjs89qe
|
||||
- &zeus age199atsykyh0hp0yuxymznxs0mqv4ram9ttaddk6jrsjjc43rmvu7snh9dq8
|
||||
- &pikachu age1wf5s0n0tgt6ld2ysgu9dc67mj8ylwecgl4utzg7hqwy3kut9zyms7aglmh
|
||||
|
||||
creation_rules:
|
||||
# Material belonging to one machine.
|
||||
# No machine other than the one named is a recipient, so a host that is
|
||||
# compromised cannot decrypt another's material.
|
||||
- path_regex: configs/nixos/secrets/neogaia\.yaml$
|
||||
- path_regex: secrets/neogaia\.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *admin
|
||||
- *neogaia
|
||||
|
||||
- path_regex: configs/nixos/secrets/pikachu\.yaml$
|
||||
- path_regex: secrets/zeus\.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *admin
|
||||
- *zeus
|
||||
|
||||
- path_regex: secrets/pikachu\.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *admin
|
||||
- *pikachu
|
||||
|
||||
# Material common to every machine, so it is stored once rather than per host.
|
||||
- path_regex: configs/nixos/secrets/shared\.yaml$
|
||||
- path_regex: secrets/shared\.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *admin
|
||||
- *neogaia
|
||||
- *zeus
|
||||
- *pikachu
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
## Desktop
|
||||
|
||||
- TODO: Verify this path after the NixOS configuration is migrated.
|
||||
When adding a graphical application, add a `window-rewrite` icon mapping in `modules/desktop/session/waybar.nix`.
|
||||
- When adding a graphical application, register its workspace icon in `registry.window-icon`.
|
||||
The workspace indicator uses this mapping to show an application icon instead of the generic default glyph.
|
||||
Match the mapping on the window class.
|
||||
Store the icon as the rendered string.
|
||||
Run `hyprctl clients -j | jq -r '.[].class' | sort -u` to list classes in the running session.
|
||||
- TODO: Verify this path after the NixOS configuration is migrated.
|
||||
Set Hyprland session environment variables in `modules/desktop/session/hyprland/default.nix` with `env = KEY,VALUE`.
|
||||
@@ -32,15 +32,36 @@
|
||||
|
||||
- Use produced literals for Herdr key names that include shifted punctuation.
|
||||
For example, use `prefix+<` instead of `prefix+shift+comma`.
|
||||
- Treat Home Manager-managed Pi extension, prompt, and skill directories as generated output.
|
||||
Do not edit those directories except for throwaway development or local experiments.
|
||||
Promote finished work into the NixOS configuration before treating it as deployed.
|
||||
- Put monorepo Pi extension sources in `.pi/extensions/`.
|
||||
Pi discovers them from subdirectories through the local ancestor-resource patch.
|
||||
- Treat Home Manager-managed Pi settings as generated output.
|
||||
Do not edit them except for throwaway development or local experiments.
|
||||
Promote finished settings into the NixOS configuration before treating them as deployed.
|
||||
|
||||
## Gotchas
|
||||
|
||||
- `configs/nixos/registry/stable-uid.nix` receives library functions through `lib`.
|
||||
Qualify its library calls with `lib.`.
|
||||
- Guest modules are host-agnostic wiring, while host files own deployment-specific service configuration such as served volumes and host paths.
|
||||
For example, `guests/copyparty.nix` should expose options and translate them into guest mounts/service settings, while `hosts/pikachu/default.nix` chooses `/inbox`, `/media`, and their backing paths.
|
||||
Guest addresses and routes are host-level configuration because a guest module can be used on multiple hosts.
|
||||
- `sops.secrets.<name>.owner` must name a user known on the decrypting host.
|
||||
Do not set the owner to a guest-only service user unless the host also declares that user.
|
||||
Use a host-visible owner plus a shared group mode when the guest service can read the decrypted secret file through its group.
|
||||
- A read-only guest bind mount cannot contain nested writable bind mounts unless their mount-point
|
||||
directories already exist inside the read-only source.
|
||||
For aggregate Copyparty volumes such as `/srv` with nested `/srv/inbox`, keep the guest bind mount
|
||||
writable and enforce read-only behavior through Copyparty access plus service `ReadWritePaths`.
|
||||
- `deploy-to` builds from the source checkout, records the imported closure as the target system profile,
|
||||
and activates it. Do not rely on a target-local monorepo checkout for remote deployment.
|
||||
- On Pikachu, `nix copy --to ssh://pikachu` accepted a small `nix store add-file` probe,
|
||||
but rejected locally built system paths that lacked trusted signatures.
|
||||
Use a sudo `nix-store --import` path for unsigned deploy closures.
|
||||
- Public DNS entries are not managed through the NixOS configuration.
|
||||
Do not describe NixOS route choices as public-DNS management.
|
||||
- Do not hand-author `hardware-configuration.nix` for a host that has not been generated on that machine.
|
||||
Generate it on the target system or live ISO, then stage it before evaluating the flake.
|
||||
- Configure the Chaotic Nyx cache only on hosts that select `linuxPackages_cachyos` (currently Neogaia
|
||||
and Zeus), not in the generic host module.
|
||||
- Module-relative paths resolve from the module file. Update them when moving a module into a subdirectory.
|
||||
- On Pikachu, `uptime` does not support `-s`. Use `who -b` to report the boot time.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.guests.actualbudget;
|
||||
cfg = config.guests.actualbudget; # NOLINT(let_order)
|
||||
dataDir = "/data";
|
||||
in
|
||||
{
|
||||
@@ -25,6 +25,14 @@ in
|
||||
}
|
||||
];
|
||||
|
||||
registry.homepage.${config.networking.hostName}.actualbudget = {
|
||||
tab = "Apps";
|
||||
section = "Other";
|
||||
name = "Actual Budget";
|
||||
description = "Finance";
|
||||
icon = "actual-budget.png";
|
||||
};
|
||||
|
||||
guests.actualbudget = {
|
||||
interior = {
|
||||
modules.services.actualbudget = {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.guests.copyparty;
|
||||
cfg = config.guests.copyparty; # NOLINT(let_order)
|
||||
|
||||
volumeType = lib.types.submodule {
|
||||
options = {
|
||||
@@ -24,6 +24,12 @@ let
|
||||
description = "Copyparty access map from permission string to account names.";
|
||||
};
|
||||
|
||||
readOnly = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Serve this Copyparty volume read-only.";
|
||||
};
|
||||
|
||||
flags = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
@@ -84,6 +90,15 @@ in
|
||||
}
|
||||
];
|
||||
|
||||
registry.homepage.${config.networking.hostName}.copyparty = {
|
||||
tab = "Apps";
|
||||
section = "Files";
|
||||
name = "Copyparty";
|
||||
description = "Files on ${config.networking.hostName}";
|
||||
icon = "copyparty.svg";
|
||||
href = lib.mkIf cfg.reverseProxy.enable "https://${cfg.reverseProxy.route.domain}/?h";
|
||||
};
|
||||
|
||||
guests.copyparty = {
|
||||
interior.modules.services.copyparty = {
|
||||
enable = true;
|
||||
@@ -94,24 +109,36 @@ in
|
||||
inherit (cfg.reverseProxy) trustedSources;
|
||||
};
|
||||
volumes = lib.mapAttrs (_urlPath: volume: {
|
||||
inherit (volume) path access flags;
|
||||
inherit (volume)
|
||||
path
|
||||
access
|
||||
flags
|
||||
readOnly
|
||||
;
|
||||
}) cfg.volumes;
|
||||
};
|
||||
|
||||
mounts =
|
||||
{
|
||||
${cfg.stateDir} = {
|
||||
directory = cfg.statePath;
|
||||
owner = config.registry."stable-uid".${config.networking.hostName}.copyparty;
|
||||
group = "storage";
|
||||
mode = "0770";
|
||||
};
|
||||
${"/run/secrets/${cfg.passwordSecret}"} = {
|
||||
file = config.sops.secrets.${cfg.passwordSecret}.path;
|
||||
readOnly = true;
|
||||
};
|
||||
mounts = {
|
||||
${cfg.stateDir} = {
|
||||
directory = cfg.statePath;
|
||||
owner = config.registry."stable-uid".${config.networking.hostName}.copyparty;
|
||||
group = "storage";
|
||||
mode = "0770";
|
||||
};
|
||||
${"/run/secrets/${cfg.passwordSecret}"} = {
|
||||
file = config.sops.secrets.${cfg.passwordSecret}.path;
|
||||
readOnly = true;
|
||||
};
|
||||
}
|
||||
// lib.mapAttrs' (
|
||||
_urlPath: volume:
|
||||
lib.nameValuePair volume.path {
|
||||
directory = volume.hostPath;
|
||||
owner = "root";
|
||||
group = "storage";
|
||||
mode = "2770";
|
||||
}
|
||||
// lib.mapAttrs' (_urlPath: volume: lib.nameValuePair volume.path { directory = volume.hostPath; }) cfg.volumes;
|
||||
) cfg.volumes;
|
||||
limits = {
|
||||
memory = lib.mkDefault "2G";
|
||||
cpu = lib.mkDefault "200%";
|
||||
|
||||
46
configs/nixos/guests/homepage.nix
Normal file
46
configs/nixos/guests/homepage.nix
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.guests.homepage; # NOLINT(let_order)
|
||||
environmentFile = "/run/secrets/homepage.env";
|
||||
in
|
||||
{
|
||||
config = lib.mkIf cfg.enable {
|
||||
guests.homepage = {
|
||||
interior.modules.services.homepage = {
|
||||
enable = true;
|
||||
hostName = config.networking.hostName;
|
||||
inherit environmentFile;
|
||||
};
|
||||
|
||||
mounts.${environmentFile} = {
|
||||
file = config.sops.templates."homepage.env".path;
|
||||
readOnly = true;
|
||||
};
|
||||
|
||||
limits = {
|
||||
memory = lib.mkDefault "1G";
|
||||
cpu = lib.mkDefault "100%";
|
||||
tasksMax = lib.mkDefault 256;
|
||||
};
|
||||
};
|
||||
|
||||
sops.secrets = {
|
||||
"services/metadata/latitude" = { };
|
||||
"services/metadata/longitude" = { };
|
||||
};
|
||||
|
||||
sops.templates."homepage.env" = {
|
||||
owner = "root";
|
||||
group = "storage";
|
||||
mode = "0440";
|
||||
content = ''
|
||||
HOMEPAGE_VAR_LATITUDE=${config.sops.placeholder."services/metadata/latitude"}
|
||||
HOMEPAGE_VAR_LONGITUDE=${config.sops.placeholder."services/metadata/longitude"}
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
103
configs/nixos/guests/navidrome.nix
Normal file
103
configs/nixos/guests/navidrome.nix
Normal file
@@ -0,0 +1,103 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
let
|
||||
guestCfg = config.guests.navidrome;
|
||||
serviceCfg = config.containers.navidrome.config.modules.services.navidrome;
|
||||
in
|
||||
{
|
||||
options.guests.navidrome = {
|
||||
dataPath = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "/srv/navidrome";
|
||||
description = "Host path mounted as Navidrome's data folder.";
|
||||
};
|
||||
|
||||
libraryPath = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "/srv/music";
|
||||
description = "Host path mounted as Navidrome's library folder.";
|
||||
};
|
||||
|
||||
lastfmApiKeySecret = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "services/navidrome/lastfm/api-key";
|
||||
description = "SOPS secret name containing the Navidrome Last.fm API key.";
|
||||
};
|
||||
|
||||
lastfmSecretSecret = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "services/navidrome/lastfm/secret";
|
||||
description = "SOPS secret name containing the Navidrome Last.fm secret.";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf guestCfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = guestCfg.dataPath != null;
|
||||
message = "guests.navidrome.dataPath must point at the host directory that stores Navidrome data.";
|
||||
}
|
||||
{
|
||||
assertion = guestCfg.libraryPath != null;
|
||||
message = "guests.navidrome.libraryPath must point at the host directory that stores Navidrome's library folder.";
|
||||
}
|
||||
];
|
||||
|
||||
registry.homepage.${config.networking.hostName}.navidrome = {
|
||||
tab = "Apps";
|
||||
section = "Media";
|
||||
name = "Navidrome";
|
||||
description = "Music";
|
||||
icon = "navidrome.png";
|
||||
};
|
||||
|
||||
guests.navidrome = {
|
||||
interior.modules.services.navidrome.enable = true;
|
||||
|
||||
mounts = {
|
||||
${serviceCfg.dataFolder} = {
|
||||
directory = guestCfg.dataPath;
|
||||
owner = config.registry."stable-uid".${config.networking.hostName}.navidrome;
|
||||
group = "storage";
|
||||
mode = "2770";
|
||||
};
|
||||
${serviceCfg.musicFolder} = {
|
||||
directory = guestCfg.libraryPath;
|
||||
owner = "root";
|
||||
group = "storage";
|
||||
mode = "2770";
|
||||
};
|
||||
${serviceCfg.environmentFile} = {
|
||||
file = config.sops.templates."navidrome.env".path;
|
||||
readOnly = true;
|
||||
};
|
||||
};
|
||||
|
||||
limits = {
|
||||
memory = lib.mkDefault "2G";
|
||||
cpu = lib.mkDefault "200%";
|
||||
tasksMax = lib.mkDefault 512;
|
||||
};
|
||||
};
|
||||
|
||||
sops.secrets = {
|
||||
${guestCfg.lastfmApiKeySecret}.sopsFile = ../secrets + "/${config.networking.hostName}.yaml";
|
||||
${guestCfg.lastfmSecretSecret}.sopsFile = ../secrets + "/${config.networking.hostName}.yaml";
|
||||
};
|
||||
|
||||
sops.templates."navidrome.env" = {
|
||||
owner = "root";
|
||||
group = "storage";
|
||||
mode = "0440";
|
||||
content = ''
|
||||
ND_LASTFM_APIKEY=${config.sops.placeholder.${guestCfg.lastfmApiKeySecret}}
|
||||
ND_LASTFM_SECRET=${config.sops.placeholder.${guestCfg.lastfmSecretSecret}}
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
155
configs/nixos/guests/palworld.nix
Normal file
155
configs/nixos/guests/palworld.nix
Normal file
@@ -0,0 +1,155 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.guests.palworld; # NOLINT(let_order)
|
||||
steamDir = "/data/steamcmd";
|
||||
installDir = "/data/server";
|
||||
stateDir = "/data/state";
|
||||
in
|
||||
{
|
||||
options.guests.palworld = {
|
||||
steamPath = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "/srv/steamcmd";
|
||||
description = "Host path bind-mounted as SteamCMD's metadata directory.";
|
||||
};
|
||||
|
||||
installPath = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "/srv/palworld/server";
|
||||
description = "Host path bind-mounted as Palworld's server install directory.";
|
||||
};
|
||||
|
||||
statePath = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "/srv/palworld/state";
|
||||
description = "Host path bind-mounted as Palworld's durable state directory.";
|
||||
};
|
||||
|
||||
serverName = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "palworld";
|
||||
description = "Name shown for the Palworld server.";
|
||||
};
|
||||
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 8211;
|
||||
description = "UDP port for the Palworld server.";
|
||||
};
|
||||
|
||||
publicIP = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Public IP address advertised by the Palworld server.";
|
||||
};
|
||||
|
||||
autoStart = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Start the Palworld server after the guest reaches network readiness.";
|
||||
};
|
||||
|
||||
passwordSecret = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "services/game-servers/password";
|
||||
description = "SOPS secret name containing the Palworld server password.";
|
||||
};
|
||||
|
||||
adminPasswordSecret = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "services/game-servers/password";
|
||||
description = "SOPS secret name containing the Palworld admin password.";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.steamPath != null;
|
||||
message = "guests.palworld.steamPath must point at the host directory that stores SteamCMD metadata.";
|
||||
}
|
||||
{
|
||||
assertion = cfg.installPath != null;
|
||||
message = "guests.palworld.installPath must point at the host directory that stores Palworld server files.";
|
||||
}
|
||||
{
|
||||
assertion = cfg.statePath != null;
|
||||
message = "guests.palworld.statePath must point at the host directory that stores Palworld durable state.";
|
||||
}
|
||||
];
|
||||
|
||||
guests.palworld = {
|
||||
interior.modules.services.games.palworld = {
|
||||
enable = true;
|
||||
inherit (cfg)
|
||||
serverName
|
||||
port
|
||||
publicIP
|
||||
autoStart
|
||||
;
|
||||
inherit steamDir installDir stateDir;
|
||||
passwordFile = "/run/secrets/${cfg.passwordSecret}";
|
||||
adminPasswordFile = "/run/secrets/${cfg.adminPasswordSecret}";
|
||||
};
|
||||
|
||||
mounts = {
|
||||
${steamDir} = {
|
||||
directory = cfg.steamPath;
|
||||
owner = config.registry."stable-uid".${config.networking.hostName}.steamcmd;
|
||||
group = "storage";
|
||||
mode = "0770";
|
||||
};
|
||||
${installDir} = {
|
||||
directory = cfg.installPath;
|
||||
owner = config.registry."stable-uid".${config.networking.hostName}.steamcmd;
|
||||
group = "storage";
|
||||
mode = "0770";
|
||||
};
|
||||
${stateDir} = {
|
||||
directory = cfg.statePath;
|
||||
owner = config.registry."stable-uid".${config.networking.hostName}.steamcmd;
|
||||
group = "storage";
|
||||
mode = "0770";
|
||||
};
|
||||
${"/run/secrets/${cfg.passwordSecret}"} = {
|
||||
file = config.sops.secrets.${cfg.passwordSecret}.path;
|
||||
readOnly = true;
|
||||
};
|
||||
}
|
||||
// lib.optionalAttrs (cfg.adminPasswordSecret != cfg.passwordSecret) {
|
||||
${"/run/secrets/${cfg.adminPasswordSecret}"} = {
|
||||
file = config.sops.secrets.${cfg.adminPasswordSecret}.path;
|
||||
readOnly = true;
|
||||
};
|
||||
};
|
||||
|
||||
limits = {
|
||||
memory = lib.mkDefault "8G";
|
||||
cpu = lib.mkDefault "400%";
|
||||
tasksMax = lib.mkDefault 2048;
|
||||
};
|
||||
};
|
||||
|
||||
sops.secrets = {
|
||||
${cfg.passwordSecret} = {
|
||||
owner = "root";
|
||||
group = "storage";
|
||||
mode = "0440";
|
||||
};
|
||||
}
|
||||
// lib.optionalAttrs (cfg.adminPasswordSecret != cfg.passwordSecret) {
|
||||
${cfg.adminPasswordSecret} = {
|
||||
owner = "root";
|
||||
group = "storage";
|
||||
mode = "0440";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -23,11 +23,6 @@ in
|
||||
enableACME = true;
|
||||
locations."/".proxyPass = "http://10.23.20.32";
|
||||
};
|
||||
"music.alexion.dev" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
locations."/".proxyPass = "http://10.23.20.32";
|
||||
};
|
||||
"photos.alexion.dev" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
|
||||
116
configs/nixos/guests/valheim.nix
Normal file
116
configs/nixos/guests/valheim.nix
Normal file
@@ -0,0 +1,116 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.guests.valheim; # NOLINT(let_order)
|
||||
steamDir = "/data/steamcmd";
|
||||
installDir = "/data/server";
|
||||
stateDir = "/data/state";
|
||||
in
|
||||
{
|
||||
options.guests.valheim = {
|
||||
steamPath = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "/srv/steamcmd";
|
||||
description = "Host path bind-mounted as SteamCMD's metadata directory.";
|
||||
};
|
||||
|
||||
installPath = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "/srv/valheim/server";
|
||||
description = "Host path bind-mounted as Valheim's server install directory.";
|
||||
};
|
||||
|
||||
statePath = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "/srv/valheim/state";
|
||||
description = "Host path bind-mounted as Valheim's world state directory.";
|
||||
};
|
||||
|
||||
serverName = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "valheim";
|
||||
description = "Name shown for the Valheim server.";
|
||||
};
|
||||
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 2456;
|
||||
description = "Base UDP port for the Valheim server.";
|
||||
};
|
||||
|
||||
passwordSecret = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "services/game-servers/password";
|
||||
description = "SOPS secret name containing the Valheim server password.";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.steamPath != null;
|
||||
message = "guests.valheim.steamPath must point at the host directory that stores SteamCMD metadata.";
|
||||
}
|
||||
{
|
||||
assertion = cfg.installPath != null;
|
||||
message = "guests.valheim.installPath must point at the host directory that stores Valheim server files.";
|
||||
}
|
||||
{
|
||||
assertion = cfg.statePath != null;
|
||||
message = "guests.valheim.statePath must point at the host directory that stores Valheim world state.";
|
||||
}
|
||||
];
|
||||
|
||||
guests.valheim = {
|
||||
interior.modules.services.games.valheim = {
|
||||
enable = true;
|
||||
inherit (cfg) serverName port;
|
||||
inherit steamDir installDir stateDir;
|
||||
passwordFile = "/run/secrets/${cfg.passwordSecret}";
|
||||
};
|
||||
|
||||
mounts = {
|
||||
${steamDir} = {
|
||||
directory = cfg.steamPath;
|
||||
owner = config.registry."stable-uid".${config.networking.hostName}.steamcmd;
|
||||
group = "storage";
|
||||
mode = "0770";
|
||||
};
|
||||
${installDir} = {
|
||||
directory = cfg.installPath;
|
||||
owner = config.registry."stable-uid".${config.networking.hostName}.steamcmd;
|
||||
group = "storage";
|
||||
mode = "0770";
|
||||
};
|
||||
${stateDir} = {
|
||||
directory = cfg.statePath;
|
||||
owner = config.registry."stable-uid".${config.networking.hostName}.steamcmd;
|
||||
group = "storage";
|
||||
mode = "0770";
|
||||
};
|
||||
${"/run/secrets/${cfg.passwordSecret}"} = {
|
||||
file = config.sops.secrets.${cfg.passwordSecret}.path;
|
||||
readOnly = true;
|
||||
};
|
||||
};
|
||||
|
||||
limits = {
|
||||
memory = lib.mkDefault "4G";
|
||||
cpu = lib.mkDefault "400%";
|
||||
tasksMax = lib.mkDefault 1024;
|
||||
};
|
||||
};
|
||||
|
||||
sops.secrets.${cfg.passwordSecret} = {
|
||||
owner = "root";
|
||||
group = "storage";
|
||||
mode = "0440";
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
inputs,
|
||||
pkgs,
|
||||
util,
|
||||
...
|
||||
}:
|
||||
# neogaia — Dell XPS 13 9380 laptop.
|
||||
@@ -13,42 +13,50 @@
|
||||
./disk.nix
|
||||
];
|
||||
|
||||
system.stateVersion = "26.05";
|
||||
modules.host.cachyos.enable = true;
|
||||
|
||||
# systemd-boot on the EFI system partition.
|
||||
boot.loader.systemd-boot.enable = true;
|
||||
boot.loader.efi.canTouchEfiVariables = true;
|
||||
|
||||
boot.kernelPackages = pkgs.linuxPackages_cachyos;
|
||||
|
||||
# Redistributable firmware for the QCA6174 wifi (ath10k blobs).
|
||||
# Intel microcode updates follow from this, so none is declared here.
|
||||
hardware.enableRedistributableFirmware = true;
|
||||
|
||||
# RAM-backed swap, no on-disk swap partition.
|
||||
zramSwap.enable = true;
|
||||
|
||||
# So wifi can be joined from the console.
|
||||
networking.networkmanager.enable = true;
|
||||
modules.network.enable = true;
|
||||
|
||||
registry.address.neogaia.self = "10.23.50.146";
|
||||
|
||||
# So setup can be driven over the network.
|
||||
# The matching host public keys sit beside this file in plaintext, since
|
||||
# publishing them is their purpose.
|
||||
modules.network.ssh.enable = true;
|
||||
modules.network.ssh.hostKeys.sopsFile = ../../secrets/neogaia.yaml;
|
||||
modules.network.ssh.userKey.sopsFile = ../../secrets/neogaia.yaml;
|
||||
modules.network.vpn-client = {
|
||||
enable = true;
|
||||
sopsFile = ../../secrets/neogaia.yaml;
|
||||
address = "10.23.100.3/32";
|
||||
|
||||
peer = {
|
||||
publicKey = "sGEkSD+gEdIMXi4PBN3AhwHGc4tWMtcjOIey3s7yb3I=";
|
||||
endpoint = "100.0.90.192:51820";
|
||||
};
|
||||
};
|
||||
|
||||
modules.monorepo.enable = true;
|
||||
modules.shell.herdr.enable = true;
|
||||
modules.agents.tools.gitea-axi.enable = true;
|
||||
modules.agents.pi.enable = true;
|
||||
modules.shell.gitea-axi.enable = true;
|
||||
modules.shell.pi.enable = true;
|
||||
|
||||
modules.desktop.enable = true;
|
||||
modules.desktop.apps.firefox.uiScale = "1.25";
|
||||
modules.desktop.apps.obsidian.enable = true;
|
||||
modules.desktop.apps.steam.enable = true;
|
||||
|
||||
time.timeZone = "America/New_York";
|
||||
i18n.defaultLocale = "en_GB.UTF-8";
|
||||
registry.waybar.neogaia.battery = {
|
||||
order = 70;
|
||||
settings = {
|
||||
format = "{icon} {capacity}%";
|
||||
format-charging = "${util.glyph "f0e7"} {capacity}%";
|
||||
format-icons = [
|
||||
(util.glyph "f244")
|
||||
(util.glyph "f243")
|
||||
(util.glyph "f242")
|
||||
(util.glyph "f241")
|
||||
(util.glyph "f240")
|
||||
];
|
||||
states = {
|
||||
warning = 20;
|
||||
critical = 10;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{ ... }:
|
||||
_:
|
||||
# neogaia's disk layout for disko: one NVMe disk, GPT, with an EFI system
|
||||
# partition and a LUKS container holding btrfs subvolumes.
|
||||
# No swap partition, since swap is zram.
|
||||
|
||||
1
configs/nixos/hosts/neogaia/ssh_user_ed25519_key.pub
Normal file
1
configs/nixos/hosts/neogaia/ssh_user_ed25519_key.pub
Normal file
@@ -0,0 +1 @@
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxQ4kWsBo2OGYIPOkFe0vNEcB3yoJwAu0y9wrdQzALE alexion@neogaia
|
||||
@@ -13,70 +13,63 @@
|
||||
./disk.nix
|
||||
];
|
||||
|
||||
system.stateVersion = "26.05";
|
||||
|
||||
boot.loader.systemd-boot.enable = true;
|
||||
boot.loader.efi.canTouchEfiVariables = true;
|
||||
|
||||
hardware.cpu.intel.updateMicrocode = true;
|
||||
hardware.enableRedistributableFirmware = true;
|
||||
|
||||
zramSwap.enable = true;
|
||||
|
||||
systemd.network = {
|
||||
enable = true;
|
||||
netdevs = {
|
||||
"40-br-vlan20".netdevConfig = {
|
||||
Name = "br-vlan20";
|
||||
Kind = "bridge";
|
||||
};
|
||||
"40-vlan20" = {
|
||||
netdevConfig = {
|
||||
Name = "vlan20";
|
||||
Kind = "vlan";
|
||||
};
|
||||
vlanConfig.Id = 20;
|
||||
};
|
||||
};
|
||||
|
||||
networks = {
|
||||
"10-uplink" = {
|
||||
matchConfig = {
|
||||
Name = "enp2s0";
|
||||
MACAddress = "78:55:36:07:af:49";
|
||||
};
|
||||
networkConfig.DHCP = "yes";
|
||||
vlan = [ "vlan20" ];
|
||||
linkConfig.RequiredForOnline = "routable";
|
||||
};
|
||||
"40-vlan20" = {
|
||||
matchConfig.Name = "vlan20";
|
||||
networkConfig.Bridge = "br-vlan20";
|
||||
linkConfig.RequiredForOnline = "no";
|
||||
};
|
||||
"40-br-vlan20" = {
|
||||
matchConfig.Name = "br-vlan20";
|
||||
linkConfig.RequiredForOnline = "no";
|
||||
};
|
||||
};
|
||||
modules.network.vlans = {
|
||||
interface = "enp2s0";
|
||||
ids = [ 20 ];
|
||||
};
|
||||
networking.useDHCP = false;
|
||||
|
||||
modules.network.vlans.ids = [ 20 ];
|
||||
|
||||
boot.zfs.forceImportRoot = false;
|
||||
|
||||
modules.zfs = {
|
||||
modules.host.zfs = {
|
||||
enable = true;
|
||||
hostId = "2346edbd";
|
||||
pools.pikachu = { };
|
||||
};
|
||||
|
||||
registry.address.pikachu.self = "10.23.10.102";
|
||||
|
||||
modules.network.ssh.enable = true;
|
||||
modules.network.ssh.hostKeys.sopsFile = ../../secrets/pikachu.yaml;
|
||||
modules.network.ssh.userKey.sopsFile = ../../secrets/pikachu.yaml;
|
||||
guests.valheim = {
|
||||
enable = true;
|
||||
vlan = 20;
|
||||
address = "10.23.20.197";
|
||||
steamPath = "/pikachu/data/steamcmd";
|
||||
installPath = "/pikachu/data/servers/valheim/main";
|
||||
statePath = "/pikachu/media/games/servers/valheim/main";
|
||||
serverName = "main";
|
||||
reverseProxy = {
|
||||
enable = true;
|
||||
route = {
|
||||
domain = "valheim.alexion.dev";
|
||||
protocol = "udp";
|
||||
ports = [
|
||||
config.guests.valheim.port
|
||||
(config.guests.valheim.port + 1)
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
guests.palworld = {
|
||||
enable = true;
|
||||
vlan = 20;
|
||||
address = "10.23.20.250";
|
||||
steamPath = "/pikachu/data/steamcmd";
|
||||
installPath = "/pikachu/data/servers/palworld/tracion";
|
||||
statePath = "/pikachu/media/games/servers/palworld/tracion";
|
||||
serverName = "Tracion";
|
||||
autoStart = false;
|
||||
adminPasswordSecret = "services/game-servers/admin/password";
|
||||
publicIP = config.guests.palworld.address;
|
||||
reverseProxy = {
|
||||
enable = true;
|
||||
route = {
|
||||
domain = "palworld.alexion.dev";
|
||||
protocol = "udp";
|
||||
ports = [ config.guests.palworld.port ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
guests.actualbudget = {
|
||||
enable = true;
|
||||
@@ -88,11 +81,29 @@
|
||||
enable = true;
|
||||
route = {
|
||||
domain = "budget.alexion.dev";
|
||||
protocol = "https";
|
||||
port = 5006;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
guests.navidrome = {
|
||||
enable = true;
|
||||
vlan = 20;
|
||||
mac = "BC:24:11:C9:17:83";
|
||||
dataPath = "/pikachu/data/navidrome";
|
||||
libraryPath = "/pikachu/media/music";
|
||||
address = "10.23.20.43";
|
||||
reverseProxy = {
|
||||
enable = true;
|
||||
route = {
|
||||
domain = "music.alexion.dev";
|
||||
protocol = "https";
|
||||
port = 4533;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
guests.reverse-proxy = {
|
||||
enable = true;
|
||||
vlan = 20;
|
||||
@@ -100,6 +111,20 @@
|
||||
address = "10.23.20.109";
|
||||
};
|
||||
|
||||
guests.homepage = {
|
||||
enable = true;
|
||||
vlan = 20;
|
||||
address = "10.23.20.110";
|
||||
reverseProxy = {
|
||||
enable = true;
|
||||
route = {
|
||||
domain = "home";
|
||||
protocol = "http";
|
||||
port = 8082;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
guests.copyparty = {
|
||||
enable = true;
|
||||
vlan = 20;
|
||||
@@ -110,14 +135,22 @@
|
||||
enable = true;
|
||||
route = {
|
||||
domain = "files.alexion.dev";
|
||||
protocol = "https";
|
||||
port = 3923;
|
||||
clientMaxBodySize = "1g";
|
||||
};
|
||||
trustedSources = [ "${registry.address.${config.networking.hostName}."reverse-proxy"}/32" ];
|
||||
};
|
||||
volumes = {
|
||||
"/" = {
|
||||
hostPath = "/pikachu/data/copyparty/root";
|
||||
path = "/srv";
|
||||
access.r = [ "alexion" ];
|
||||
readOnly = true;
|
||||
};
|
||||
"/inbox" = {
|
||||
hostPath = "/pikachu/__inbox";
|
||||
path = "/srv/inbox";
|
||||
path = "/pikachu/__inbox";
|
||||
access.A = [ "alexion" ];
|
||||
flags = [
|
||||
"d2d"
|
||||
@@ -127,7 +160,7 @@
|
||||
};
|
||||
"/media" = {
|
||||
hostPath = "/pikachu/media";
|
||||
path = "/srv/media";
|
||||
path = "/pikachu/media";
|
||||
access.A = [ "alexion" ];
|
||||
flags = [
|
||||
"d2d"
|
||||
@@ -135,6 +168,13 @@
|
||||
"dthumb"
|
||||
];
|
||||
};
|
||||
"/opds" = {
|
||||
hostPath = "/pikachu/data/opds";
|
||||
path = "/pikachu/data/opds";
|
||||
access.r = [ "alexion" ];
|
||||
readOnly = true;
|
||||
flags = [ "opds" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -144,6 +184,4 @@
|
||||
usbutils
|
||||
];
|
||||
|
||||
time.timeZone = "America/New_York";
|
||||
i18n.defaultLocale = "en_GB.UTF-8";
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{ ... }:
|
||||
_:
|
||||
# pikachu's install layout for disko: one NVMe boot disk with an EFI system partition and ext4 root.
|
||||
# The existing 8 TB ZFS mirror is imported by name and is never declared here.
|
||||
{
|
||||
|
||||
1
configs/nixos/hosts/pikachu/ssh_user_ed25519_key.pub
Normal file
1
configs/nixos/hosts/pikachu/ssh_user_ed25519_key.pub
Normal file
@@ -0,0 +1 @@
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINNqJIC6VRXyvrNf3n9su9KdPCikC3CjK/QrCK2reHdB alexion@pikachu
|
||||
67
configs/nixos/hosts/zeus/default.nix
Normal file
67
configs/nixos/hosts/zeus/default.nix
Normal file
@@ -0,0 +1,67 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
# zeus — Ryzen desktop with AMD graphics.
|
||||
# Disk layout is in ./disk.nix.
|
||||
# `fileSystems` are derived from it, none declared here.
|
||||
{
|
||||
imports = [
|
||||
./disk.nix
|
||||
]
|
||||
++ lib.optional (builtins.pathExists ./hardware-configuration.nix) ./hardware-configuration.nix;
|
||||
|
||||
nixpkgs.hostPlatform = "x86_64-linux";
|
||||
|
||||
modules.host.cachyos.enable = true;
|
||||
|
||||
hardware.graphics = {
|
||||
enable = true;
|
||||
enable32Bit = true;
|
||||
};
|
||||
|
||||
modules.network.enable = true;
|
||||
|
||||
services.logind.settings.Login = {
|
||||
HandlePowerKey = "ignore";
|
||||
HandlePowerKeyLongPress = "ignore";
|
||||
};
|
||||
|
||||
registry.address.zeus.self = "10.23.50.221";
|
||||
|
||||
hardware.bluetooth = {
|
||||
enable = true;
|
||||
powerOnBoot = true;
|
||||
};
|
||||
|
||||
modules.monorepo.enable = true;
|
||||
modules.shell.herdr.enable = true;
|
||||
modules.shell.gitea-axi.enable = true;
|
||||
modules.shell.pi.enable = true;
|
||||
modules.shell.omp.enable = true;
|
||||
|
||||
modules.desktop.enable = true;
|
||||
modules.desktop.apps.obsidian.enable = true;
|
||||
modules.desktop.apps.r2modman.enable = true;
|
||||
modules.desktop.apps.signal.enable = true;
|
||||
modules.desktop.apps.steam.enable = true;
|
||||
modules.desktop.apps.vencord.enable = true;
|
||||
|
||||
home-manager.users.${config.user.name}.wayland.windowManager.hyprland.settings.monitor = [
|
||||
{
|
||||
output = "HDMI-A-1";
|
||||
mode = "1920x1080@120";
|
||||
position = "-1080x0";
|
||||
scale = 1;
|
||||
transform = 3;
|
||||
}
|
||||
{
|
||||
output = "DP-3";
|
||||
mode = "2560x1440@239.96";
|
||||
position = "0x0";
|
||||
scale = 1;
|
||||
}
|
||||
];
|
||||
|
||||
}
|
||||
84
configs/nixos/hosts/zeus/disk.nix
Normal file
84
configs/nixos/hosts/zeus/disk.nix
Normal file
@@ -0,0 +1,84 @@
|
||||
_:
|
||||
# zeus's disk layout for disko: two NVMe disks with one EFI system partition
|
||||
# and two LUKS containers joined into one btrfs filesystem.
|
||||
# No swap partition, since swap is zram.
|
||||
# disko derives `fileSystems` and `boot.initrd.luks.devices` from this.
|
||||
{
|
||||
disko.devices.disk.primary = {
|
||||
type = "disk";
|
||||
device = "/dev/nvme0n1";
|
||||
content = {
|
||||
type = "gpt";
|
||||
partitions = {
|
||||
ESP = {
|
||||
size = "2G";
|
||||
type = "EF00";
|
||||
content = {
|
||||
type = "filesystem";
|
||||
format = "vfat";
|
||||
mountpoint = "/boot";
|
||||
mountOptions = [ "umask=0077" ];
|
||||
};
|
||||
};
|
||||
luks = {
|
||||
size = "100%";
|
||||
content = {
|
||||
type = "luks";
|
||||
name = "cryptroot0";
|
||||
settings.allowDiscards = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
disko.devices.disk.secondary = {
|
||||
type = "disk";
|
||||
device = "/dev/nvme1n1";
|
||||
content = {
|
||||
type = "gpt";
|
||||
partitions = {
|
||||
luks = {
|
||||
size = "100%";
|
||||
content = {
|
||||
type = "luks";
|
||||
name = "cryptroot1";
|
||||
settings.allowDiscards = true;
|
||||
content = {
|
||||
type = "btrfs";
|
||||
extraArgs = [
|
||||
"-f"
|
||||
"-d single"
|
||||
"-m raid1"
|
||||
"/dev/mapper/cryptroot0"
|
||||
];
|
||||
subvolumes = {
|
||||
"@root" = {
|
||||
mountpoint = "/";
|
||||
mountOptions = [
|
||||
"compress=zstd"
|
||||
"noatime"
|
||||
];
|
||||
};
|
||||
"@home" = {
|
||||
mountpoint = "/home";
|
||||
mountOptions = [
|
||||
"compress=zstd"
|
||||
"noatime"
|
||||
];
|
||||
};
|
||||
"@nix" = {
|
||||
mountpoint = "/nix";
|
||||
mountOptions = [
|
||||
"compress=zstd"
|
||||
"noatime"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
18
configs/nixos/hosts/zeus/hardware-configuration.nix
Normal file
18
configs/nixos/hosts/zeus/hardware-configuration.nix
Normal file
@@ -0,0 +1,18 @@
|
||||
# Do not modify this file! It was generated by ‘nixos-generate-config’
|
||||
# and may be overwritten by future invocations. Please make changes
|
||||
# to /etc/nixos/configuration.nix instead.
|
||||
{ config, lib, pkgs, modulesPath, ... }:
|
||||
|
||||
{
|
||||
imports =
|
||||
[ (modulesPath + "/installer/scan/not-detected.nix")
|
||||
];
|
||||
|
||||
boot.initrd.availableKernelModules = [ "nvme" "xhci_pci" "ahci" "usbhid" "usb_storage" "sd_mod" ];
|
||||
boot.initrd.kernelModules = [ ];
|
||||
boot.kernelModules = [ "kvm-amd" ];
|
||||
boot.extraModulePackages = [ ];
|
||||
|
||||
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
||||
hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
|
||||
}
|
||||
1
configs/nixos/hosts/zeus/ssh_host_ed25519_key.pub
Normal file
1
configs/nixos/hosts/zeus/ssh_host_ed25519_key.pub
Normal file
@@ -0,0 +1 @@
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ4GBPt966LR4LpmXgiMMkepIqNIlXTUBh4zdNeek/lN root@zeus
|
||||
1
configs/nixos/hosts/zeus/ssh_host_rsa_key.pub
Normal file
1
configs/nixos/hosts/zeus/ssh_host_rsa_key.pub
Normal file
@@ -0,0 +1 @@
|
||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC3xOxGAJCG9PE0FVufgJX9VRO64IUUlRGHSOfYfAI4Mit46xKsr7z/OvqPPE9z8rOIcVPbLkc/mkMuHbDgolleunAFCIeuIIYpkL4o4/TZECh9BtN2WdPhjcD7S0rV+rR7p24Qy5bTCSwZmsQq8QxHTVPjjBngtPGRSpKVrZAuHCcdCJ13hBWAvnaav5KysKC89nMj/7FIh02v8kipGfq5LGCZ20Qp2lmv2DS24NhBPXzcw8pLN47I5GYVkZh/0RWOByteo3eXtKCF+JCwfUPCTP+zHGD+1tbO8//B2fkt3Pe9lUrn5eM2ZnX1emXSWfQng3cMqkftzWqvBwv0sSDU+9YV6H2Ury+GrrfukpBtQVe9PQcBWqbRPhxuagncR+af/tx0ZohZS0t74m3WgLtZ4r2mpTZS4oBmRsfvWzMFGSgwtva+3Y+NsLjPPM1Cz5Dn05JJlidzcABfsUg2H23vkmO2otaQI01dUni+szlRn4w6sVyxNcvY2fQGjmt9k7jmVI5D2A3yl+OLJguAAlw3ia335X1tNUG8FoI/VyZNhOckIihmbTtihNIg37Ro4yiuPRhnUWFvg2hcBCa9hzNP7XDUPnoCRQyJ4adQHQA/x4Kh+HJ6pZqtGcdqU80fzmWtrLkWG6v5/NRKs6soYDup7oQIKbJo9TtXvqtzmVmovQ== root@zeus
|
||||
1
configs/nixos/hosts/zeus/ssh_user_ed25519_key.pub
Normal file
1
configs/nixos/hosts/zeus/ssh_user_ed25519_key.pub
Normal file
@@ -0,0 +1 @@
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIRBcHHuubptXCdvED/p2dR3MpjGN/AimqLasLY5984G alexion@zeus
|
||||
@@ -1,32 +0,0 @@
|
||||
# Authenticating Claude Code without a browser
|
||||
|
||||
`neogaia` is driven from the console and over SSH, where no local browser can service Claude Code's default OAuth redirect.
|
||||
Either of the two paths below signs the CLI in from a bare terminal.
|
||||
Both are one-time actions per machine; the credentials land under `~/.claude`, which home-manager does not overwrite.
|
||||
|
||||
## Paste-code flow (Claude subscription or Console OAuth)
|
||||
|
||||
Run `claude` and start the login with the `/login` command (the first run offers it automatically).
|
||||
On a machine with no browser it cannot open the authorization page itself, so it prints the authorization URL and waits.
|
||||
|
||||
1. Copy the printed URL to a browser on any other device (phone, another laptop).
|
||||
2. Sign in and approve the request there.
|
||||
3. The page returns a short authorization code; paste it back at the `claude` prompt still waiting in the terminal.
|
||||
|
||||
The session then completes and the token is stored, so later runs need no further login.
|
||||
Because the URL is opened on a *different* device, this works unchanged over SSH.
|
||||
|
||||
## API key
|
||||
|
||||
For non-interactive use, set an Anthropic API key from <https://console.anthropic.com> in the environment before launching `claude`:
|
||||
|
||||
```console
|
||||
$ export ANTHROPIC_API_KEY=sk-ant-...
|
||||
$ claude
|
||||
```
|
||||
|
||||
Claude Code reads `ANTHROPIC_API_KEY` on startup and skips the interactive login entirely, so this path needs neither a browser nor the paste-code exchange.
|
||||
Usage is billed to the Console account that owns the key rather than to a Claude subscription.
|
||||
|
||||
The key is a secret and is deliberately not baked into this configuration.
|
||||
Export it from the shell for a one-off, or source it from a secret store once one exists on the Host.
|
||||
@@ -1,65 +0,0 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
# Claude Code for the primary user, configured through home-manager, which ships
|
||||
# the package and manages ~/.claude.
|
||||
# Login credentials are left unmanaged so they survive rebuilds.
|
||||
let
|
||||
cfg = config.modules.agents.claude-code;
|
||||
user = config.user.name;
|
||||
in
|
||||
{
|
||||
options.modules.agents.claude-code.enable = lib.mkEnableOption ''
|
||||
Claude Code, Anthropic's CLI, configured via home-manager.
|
||||
|
||||
Enabling this also widens sudo's credential cache, keying it per user rather
|
||||
than per terminal and holding it for 60 minutes, so that a single
|
||||
authentication covers commands the agent issues. No command is made
|
||||
passwordless, but any process running as the primary user can spend the
|
||||
cached credential while it lasts. Suitable for a single-user machine'';
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
# Key the credential cache per user rather than per terminal, so one
|
||||
# authentication covers the agent's terminal-less commands.
|
||||
security.sudo.extraConfig = ''
|
||||
Defaults timestamp_type=global
|
||||
Defaults timestamp_timeout=60
|
||||
'';
|
||||
|
||||
home-manager.users.${user} = {
|
||||
# jq parses the tool input handed to the sudo guard hook.
|
||||
home.packages = [ pkgs.jq ];
|
||||
|
||||
programs.claude-code = {
|
||||
enable = true;
|
||||
|
||||
# One directory per skill, symlinked under ~/.claude/skills.
|
||||
skills = ./skills;
|
||||
|
||||
# Installed under ~/.claude/hooks, referenced by the settings below.
|
||||
hooks."agent-sudo-guard.sh" = builtins.readFile ./hooks/agent-sudo-guard.sh;
|
||||
|
||||
settings = {
|
||||
model = "opus";
|
||||
hooks = {
|
||||
PreToolUse = [
|
||||
{
|
||||
matcher = "Bash";
|
||||
hooks = [
|
||||
{
|
||||
type = "command";
|
||||
command = "~/.claude/hooks/agent-sudo-guard.sh";
|
||||
timeout = 10;
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
#!/bin/sh
|
||||
# Refuse a privileged command while sudo's credential cache is cold, naming the
|
||||
# command that warms it.
|
||||
#
|
||||
# Commands arrive here from subprocesses holding no terminal, so an uncached
|
||||
# sudo fails with a bare non-zero exit and no output, reading as an unexplained stall.
|
||||
# The probe below reads a cache keyed per user rather than per terminal,
|
||||
# so an authentication made in the operator's own terminal counts.
|
||||
|
||||
input=$(cat)
|
||||
command=$(printf '%s' "$input" | jq -r '.tool_input.command // ""')
|
||||
|
||||
# Anchored to a command position so a `sudo` appearing as an argument or inside
|
||||
# a string does not trip the guard.
|
||||
if ! printf '%s' "$command" | grep -qE '(^|[;&|(]|&&|\|\|)[[:space:]]*sudo([[:space:]]|$)'; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if sudo -n true 2>/dev/null; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Exit 2 blocks the call and feeds stderr back to the agent.
|
||||
echo 'Blocked: sudo has no cached credential, and this command cannot answer a password prompt.
|
||||
Ask the operator to run `sudo -v` in their own terminal, then retry.
|
||||
Never attempt to supply a password directly.
|
||||
If this still blocks immediately after the operator runs `sudo -v`, the cache is
|
||||
not the cause: check that this hook can reach sudo at all.' >&2
|
||||
exit 2
|
||||
@@ -1,37 +0,0 @@
|
||||
# Deepening
|
||||
|
||||
How to deepen a cluster of shallow modules safely, given its dependencies. Assumes the vocabulary in [SKILL.md](SKILL.md) — **module**, **interface**, **seam**, **adapter**.
|
||||
|
||||
## Dependency categories
|
||||
|
||||
When assessing a candidate for deepening, classify its dependencies. The category determines how the deepened module is tested across its seam.
|
||||
|
||||
### 1. In-process
|
||||
|
||||
Pure computation, in-memory state, no I/O. Always deepenable — merge the modules and test through the new interface directly. No adapter needed.
|
||||
|
||||
### 2. Local-substitutable
|
||||
|
||||
Dependencies that have local test stand-ins (PGLite for Postgres, in-memory filesystem). Deepenable if the stand-in exists. The deepened module is tested with the stand-in running in the test suite. The seam is internal; no port at the module's external interface.
|
||||
|
||||
### 3. Remote but owned (Ports & Adapters)
|
||||
|
||||
Your own services across a network boundary (microservices, internal APIs). Define a **port** (interface) at the seam. The deep module owns the logic; the transport is injected as an **adapter**. Tests use an in-memory adapter. Production uses an HTTP/gRPC/queue adapter.
|
||||
|
||||
Recommendation shape: *"Define a port at the seam, implement an HTTP adapter for production and an in-memory adapter for testing, so the logic sits in one deep module even though it's deployed across a network."*
|
||||
|
||||
### 4. True external (Mock)
|
||||
|
||||
Third-party services (Stripe, Twilio, etc.) you don't control. The deepened module takes the external dependency as an injected port; tests provide a mock adapter.
|
||||
|
||||
## Seam discipline
|
||||
|
||||
- **One adapter means a hypothetical seam. Two adapters means a real one.** Don't introduce a port unless at least two adapters are justified (typically production + test). A single-adapter seam is just indirection.
|
||||
- **Internal seams vs external seams.** A deep module can have internal seams (private to its implementation, used by its own tests) as well as the external seam at its interface. Don't expose internal seams through the interface just because tests use them.
|
||||
|
||||
## Testing strategy: replace, don't layer
|
||||
|
||||
- Old unit tests on shallow modules become waste once tests at the deepened module's interface exist — delete them.
|
||||
- Write new tests at the deepened module's interface. The **interface is the test surface**.
|
||||
- Tests assert on observable outcomes through the interface, not internal state.
|
||||
- Tests should survive internal refactors — they describe behaviour, not implementation. If a test has to change when the implementation changes, it's testing past the interface.
|
||||
@@ -1,44 +0,0 @@
|
||||
# Design It Twice
|
||||
|
||||
When the user wants to explore alternative interfaces for a chosen deepening candidate, use this parallel sub-agent pattern. Based on "Design It Twice" (Ousterhout) — your first idea is unlikely to be the best.
|
||||
|
||||
Uses the vocabulary in [SKILL.md](SKILL.md) — **module**, **interface**, **seam**, **adapter**, **leverage**.
|
||||
|
||||
## Process
|
||||
|
||||
### 1. Frame the problem space
|
||||
|
||||
Before spawning sub-agents, write a user-facing explanation of the problem space for the chosen candidate:
|
||||
|
||||
- The constraints any new interface would need to satisfy
|
||||
- The dependencies it would rely on, and which category they fall into (see [DEEPENING.md](DEEPENING.md))
|
||||
- A rough illustrative code sketch to ground the constraints — not a proposal, just a way to make the constraints concrete
|
||||
|
||||
Show this to the user, then immediately proceed to Step 2. The user reads and thinks while the sub-agents work in parallel.
|
||||
|
||||
### 2. Spawn sub-agents
|
||||
|
||||
Spawn 3+ sub-agents in parallel using the Agent tool. Each must produce a **radically different** interface for the deepened module.
|
||||
|
||||
Prompt each sub-agent with a separate technical brief (file paths, coupling details, dependency category from [DEEPENING.md](DEEPENING.md), what sits behind the seam). The brief is independent of the user-facing problem-space explanation in Step 1. Give each agent a different design constraint:
|
||||
|
||||
- Agent 1: "Minimize the interface — aim for 1–3 entry points max. Maximise leverage per entry point."
|
||||
- Agent 2: "Maximise flexibility — support many use cases and extension."
|
||||
- Agent 3: "Optimise for the most common caller — make the default case trivial."
|
||||
- Agent 4 (if applicable): "Design around ports & adapters for cross-seam dependencies."
|
||||
|
||||
Include both [SKILL.md](SKILL.md) vocabulary and `.claude/CONTEXT.md` vocabulary in the brief so each sub-agent names things consistently with the architecture language and the project's domain language.
|
||||
|
||||
Each sub-agent outputs:
|
||||
|
||||
1. Interface (types, methods, params — plus invariants, ordering, error modes)
|
||||
2. Usage example showing how callers use it
|
||||
3. What the implementation hides behind the seam
|
||||
4. Dependency strategy and adapters (see [DEEPENING.md](DEEPENING.md))
|
||||
5. Trade-offs — where leverage is high, where it's thin
|
||||
|
||||
### 3. Present and compare
|
||||
|
||||
Present designs sequentially so the user can absorb each one, then compare them in prose. Contrast by **depth** (leverage at the interface), **locality** (where change concentrates), and **seam placement**.
|
||||
|
||||
After comparing, give your own recommendation: which design you think is strongest and why. If elements from different designs would combine well, propose a hybrid. Be opinionated — the user wants a strong read, not a menu.
|
||||
@@ -1,113 +0,0 @@
|
||||
---
|
||||
name: codebase-design
|
||||
description: Shared vocabulary for designing deep modules. Use when the user wants to design or improve a module's interface, find deepening opportunities, decide where a seam goes, make code more testable or AI-navigable, or when another skill needs the deep-module vocabulary.
|
||||
---
|
||||
|
||||
# Codebase Design
|
||||
|
||||
Design **deep modules**: a lot of behaviour behind a small interface, placed at a clean seam, testable through that interface. Use this language and these principles wherever code is being designed or restructured. The aim is leverage for callers, locality for maintainers, and testability for everyone.
|
||||
|
||||
## Glossary
|
||||
|
||||
Use these terms exactly — don't substitute "component," "service," "API," or "boundary." Consistent language is the whole point.
|
||||
|
||||
**Module** — anything with an interface and an implementation. Deliberately scale-agnostic: a function, class, package, or tier-spanning slice. _Avoid_: unit, component, service.
|
||||
|
||||
**Interface** — everything a caller must know to use the module correctly: the type signature, but also invariants, ordering constraints, error modes, required configuration, and performance characteristics. _Avoid_: API, signature (too narrow — they refer only to the type-level surface).
|
||||
|
||||
**Implementation** — what's inside a module, its body of code. Distinct from **Adapter**: a thing can be a small adapter with a large implementation (a Postgres repository) or a large adapter with a small implementation (an in-memory fake). Reach for "adapter" when the seam is the topic; "implementation" otherwise.
|
||||
|
||||
**Depth** — leverage at the interface: the amount of behaviour a caller (or test) can exercise per unit of interface they have to learn. A module is **deep** when a large amount of behaviour sits behind a small interface, **shallow** when the interface is nearly as complex as the implementation.
|
||||
|
||||
**Seam** _(Michael Feathers)_ — a place where you can alter behaviour without editing in that place; the *location* at which a module's interface lives. Where to put the seam is its own design decision, distinct from what goes behind it. _Avoid_: boundary (overloaded with DDD's bounded context).
|
||||
|
||||
**Adapter** — a concrete thing that satisfies an interface at a seam. Describes *role* (what slot it fills), not substance (what's inside).
|
||||
|
||||
**Leverage** — what callers get from depth: more capability per unit of interface they learn. One implementation pays back across N call sites and M tests.
|
||||
|
||||
**Locality** — what maintainers get from depth: change, bugs, knowledge, and verification concentrate in one place rather than spreading across callers. Fix once, fixed everywhere.
|
||||
|
||||
## Deep vs shallow
|
||||
|
||||
**Deep module** = small interface + lots of implementation:
|
||||
|
||||
```
|
||||
┌─────────────────────┐
|
||||
│ Small Interface │ ← Few methods, simple params
|
||||
├─────────────────────┤
|
||||
│ │
|
||||
│ Deep Implementation│ ← Complex logic hidden
|
||||
│ │
|
||||
└─────────────────────┘
|
||||
```
|
||||
|
||||
**Shallow module** = large interface + little implementation (avoid):
|
||||
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ Large Interface │ ← Many methods, complex params
|
||||
├─────────────────────────────────┤
|
||||
│ Thin Implementation │ ← Just passes through
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
When designing an interface, ask:
|
||||
|
||||
- Can I reduce the number of methods?
|
||||
- Can I simplify the parameters?
|
||||
- Can I hide more complexity inside?
|
||||
|
||||
## Principles
|
||||
|
||||
- **Depth is a property of the interface, not the implementation.** A deep module can be internally composed of small, mockable, swappable parts — they just aren't part of the interface. A module can have **internal seams** (private to its implementation, used by its own tests) as well as the **external seam** at its interface.
|
||||
- **The deletion test.** Imagine deleting the module. If complexity vanishes, it was a pass-through. If complexity reappears across N callers, it was earning its keep.
|
||||
- **The interface is the test surface.** Callers and tests cross the same seam. If you want to test *past* the interface, the module is probably the wrong shape.
|
||||
- **One adapter means a hypothetical seam. Two adapters means a real one.** Don't introduce a seam unless something actually varies across it.
|
||||
|
||||
## Designing for testability
|
||||
|
||||
Good interfaces make testing natural:
|
||||
|
||||
1. **Accept dependencies, don't create them.**
|
||||
|
||||
```typescript
|
||||
// Testable
|
||||
function processOrder(order, paymentGateway) {}
|
||||
|
||||
// Hard to test
|
||||
function processOrder(order) {
|
||||
const gateway = new StripeGateway();
|
||||
}
|
||||
```
|
||||
|
||||
2. **Return results, don't produce side effects.**
|
||||
|
||||
```typescript
|
||||
// Testable
|
||||
function calculateDiscount(cart): Discount {}
|
||||
|
||||
// Hard to test
|
||||
function applyDiscount(cart): void {
|
||||
cart.total -= discount;
|
||||
}
|
||||
```
|
||||
|
||||
3. **Small surface area.** Fewer methods = fewer tests needed. Fewer params = simpler test setup.
|
||||
|
||||
## Relationships
|
||||
|
||||
- A **Module** has exactly one **Interface** (the surface it presents to callers and tests).
|
||||
- **Depth** is a property of a **Module**, measured against its **Interface**.
|
||||
- A **Seam** is where a **Module**'s **Interface** lives.
|
||||
- An **Adapter** sits at a **Seam** and satisfies the **Interface**.
|
||||
- **Depth** produces **Leverage** for callers and **Locality** for maintainers.
|
||||
|
||||
## Rejected framings
|
||||
|
||||
- **Depth as ratio of implementation-lines to interface-lines** (Ousterhout): rewards padding the implementation. We use depth-as-leverage instead.
|
||||
- **"Interface" as the TypeScript `interface` keyword or a class's public methods**: too narrow — interface here includes every fact a caller must know.
|
||||
|
||||
## Going deeper
|
||||
|
||||
- **Deepening a cluster given its dependencies** — see [DEEPENING.md](DEEPENING.md): dependency categories, seam discipline, and replace-don't-layer testing.
|
||||
- **Exploring alternative interfaces** — see [DESIGN-IT-TWICE.md](DESIGN-IT-TWICE.md): spin up parallel sub-agents to design the interface several radically different ways, then compare on depth, locality, and seam placement.
|
||||
@@ -1,195 +0,0 @@
|
||||
# Glossary — Building Great Skills
|
||||
|
||||
The domain model for what makes a skill great. A skill exists to wrangle determinism out of a stochastic system; the root virtue is **Predictability**, and every term below is a lever on it. This is the disclosed reference for [`craft-skill`](SKILL.md).
|
||||
|
||||
The terms are grouped by axis: **Invocation** (how a skill is reached), **Information Hierarchy** (how its content is arranged), **Steering** (how the agent's runtime behaviour is shaped), and **Pruning** (how it is kept lean). Each **failure mode** lives beside the lever that cures it, tagged _failure mode_.
|
||||
|
||||
**Bold terms** in any definition are themselves defined in this glossary; find them by their heading.
|
||||
|
||||
## Predictability
|
||||
|
||||
The degree to which a skill makes the agent behave the same _way_ on every run — the same process, not the same output (a brainstorming skill should _predictably_ diverge; its tokens vary, its behaviour doesn't). The root virtue every other term serves — cost and maintainability are symptoms of it, not rivals.
|
||||
|
||||
_Avoid_: consistency, reliability, robustness, output-determinism
|
||||
|
||||
## Invocation
|
||||
|
||||
How a skill is reached — and the two loads you pay for the choice.
|
||||
|
||||
### Model-Invoked
|
||||
|
||||
A skill that keeps its **description** field, so the agent can see it and fire it autonomously — and the human can still type its name, so model-invocation always _includes_ user reach. There is no model-only state: a description only ever _adds_ agent discovery, never removes the human's. Pays a permanent **context load** on every turn in exchange for that discoverability. Reachable by other skills, because the description that makes it agent-discoverable makes it invocable. A model-invoked skill whose content is all **reference** is also one home for shared reference: another skill can invoke it, so reference needed by several skills lives in one place. Pick model-invocation only when the agent must reach the skill on its own; if it never fires except by hand, drop the description and pay no context load.
|
||||
|
||||
_Avoid_: ability, tool, capability
|
||||
|
||||
### User-Invoked
|
||||
|
||||
A skill with its **description** stripped — invisible to the agent and reachable only by the human typing its name (user-_only_, where **model-invoked** is user-_and-agent_). Trades agent-discoverability for zero **context load**. Because it has no description, nothing but the human can reach it: no other skill can fire it.
|
||||
|
||||
_Avoid_: procedure, workflow, command
|
||||
|
||||
### Description
|
||||
|
||||
The skill's machine-readable trigger, and the one **context pointer** a **model-invoked** skill is forced to keep loaded at all times. Its mere presence _is_ the invocation axis: keep it and the skill is model-invoked (and reachable by other skills); delete it and the skill is **user-invoked**, reachable only by the human. The source of a model-invoked skill's **context load**.
|
||||
|
||||
_Avoid_: frontmatter, summary
|
||||
|
||||
### Context Pointer
|
||||
|
||||
A reference held in the agent's context that names some out-of-context material and encodes the condition for reaching it. The **description** is the top-level context pointer (context window → skill); pointers to disclosed files are the same object one level down. Its wording, not the target, decides _when_ the agent reaches — and _how reliably_. A must-have target behind a weakly worded pointer is a variance bug: fix the wording first, and inline the material only if sharpening fails.
|
||||
|
||||
_Avoid_: link, reference, import
|
||||
|
||||
### Context Load
|
||||
|
||||
The cost a **model-invoked** skill imposes on the agent's context window — its **description**, always loaded, spending both tokens and attention. What **user-invoked** skills escape by having no description, and the brake on splitting into more model-invoked skills.
|
||||
|
||||
_Avoid_: token cost, context bloat
|
||||
|
||||
### Cognitive Load
|
||||
|
||||
The cost a **user-invoked** skill imposes on the human — what they must hold in their head: which skills exist and when to reach for each (the human is the index). What **model-invocation** removes by being agent-discoverable, and the brake on splitting into more user-invoked skills. Not a cost to minimise: it is the price of human agency, the reason some skills stay user-invoked. Spend it where human judgement matters; remove it where it does not.
|
||||
|
||||
_Avoid_: human index, burden, overhead
|
||||
|
||||
### Router Skill
|
||||
|
||||
A **user-invoked** skill whose job is to point at your other user-invoked skills — naming each and when to reach for it — so the human has one skill to remember instead of many. It can only hint, never fire them: user-invoked skills have no **description**, so nothing but the human can reach them. The cure for **cognitive load** when user-invoked skills multiply.
|
||||
|
||||
_Avoid_: dispatcher, menu, registry, index, router procedure
|
||||
|
||||
### Granularity
|
||||
|
||||
How finely you divide skills. Finer division spends one of the two loads: more **model-invoked** skills spend **context load** (more descriptions crowding the window and competing for attention); more **user-invoked** skills spend **cognitive load** (more for the human to remember and reach for). Two cuts guide the division. By **invocation**, split off a model-invoked skill where you have a distinct **leading word** to trigger it — a trigger word you actually use in your prompts. By **sequence**, split a run of **steps** where a step's **post-completion steps** need hiding, since isolating it in its own context clears what follows. Beware the reverse: merging sequences exposes each step's post-completion steps to what follows, inviting premature completion.
|
||||
|
||||
_Avoid_: chunking, modularity
|
||||
|
||||
## Information Hierarchy
|
||||
|
||||
How a skill's content is arranged, and how far down the ladder each piece sits.
|
||||
|
||||
### Information Hierarchy
|
||||
|
||||
A skill's content ranked by how immediately the agent needs it — a single ladder, produced by two cuts: in-file or behind a pointer, and step or reference. The rungs:
|
||||
|
||||
- **Steps** — in-file, primary
|
||||
- **Reference**, in-file — secondary
|
||||
- **Reference**, disclosed — behind a **context pointer**
|
||||
|
||||
A skill with no **steps** uses just the bottom two rungs — often a legitimately flat peer-set (e.g. every rule of a review on one rung), which is a fine arrangement, not a smell. The hierarchy is independent of invocation: a skill can be model- or user-invoked whether it is all steps, all reference, or both. When a skill has steps, in-file reference that should be disclosed buries them and turns attending to them into a coin-flip — a variance lever, not just a legibility one. Keep the top of the ladder legible; push down it whatever you can.
|
||||
|
||||
_Avoid_: structure, organization, layout
|
||||
|
||||
### Steps
|
||||
|
||||
The ordered actions the agent performs — when a skill has them, the primary tier of its content, and the part that earns its place in SKILL.md. Not every skill has steps: a skill can be all steps (`test-driven-development`), all **reference** (a review), or both, independent of invocation. Every step ends on a **completion criterion**, clear or vague.
|
||||
|
||||
_Avoid_: workflow, instructions, choreography
|
||||
|
||||
### Reference
|
||||
|
||||
Material the agent refers to on demand — definitions, facts, parameters, examples, conditional instructions. When a skill has **steps** it is secondary to them; when a skill has none it is the entire content; or it lives outside any skill entirely — see **External Reference**. Reached via **context pointers**, and the prime candidate for **progressive disclosure**.
|
||||
|
||||
_Avoid_: supporting material, docs, background
|
||||
|
||||
### External Reference
|
||||
|
||||
**Reference** that lives outside the skill system — a plain file, no **description**, no **steps**, not invocable — that any skill can point at. The home for shared reference that needn't fire on its own, and the only shared home two **user-invoked** skills can use, since neither has a description and so neither can fire the other.
|
||||
|
||||
_Avoid_: doc, resource, knowledge base
|
||||
|
||||
### Progressive Disclosure
|
||||
|
||||
Moving **reference** down the ladder — out of SKILL.md and behind a **context pointer** — so the top stays legible. Not primarily a token optimisation; it is how the **information hierarchy** is protected. Licensed by **branching**: disclose what only some branches need, inline what every path needs, and if a pointer fires unreliably on must-have material, sharpen its wording, and pull it back inline only if that fails.
|
||||
|
||||
_Avoid_: lazy loading, chunking
|
||||
|
||||
### Co-location
|
||||
|
||||
Keeping the material an agent needs at once in one place — a concept's definition, rules, and caveats under a single heading, not scattered across the file — so reading one part brings its neighbours with it. The within-file companion to the **Information Hierarchy**: the hierarchy ranks _how far down_ a piece sits; co-location decides _what sits beside it_ once there. There is no formula for the right format of a body of **reference**; the test is that a skill should read like documentation written for the agent, and grouped material reads that way where scattered material does not. Distinct from **Duplication**: that repeats one meaning in two places, where scattering fragments a single meaning across many.
|
||||
|
||||
_Avoid_: grouping, clustering, cohesion
|
||||
|
||||
### Sprawl
|
||||
|
||||
_Failure mode._ A skill that is simply too long — too many lines in SKILL.md — independent of whether they are stale or repeated. Even an all-live, all-unique skill can sprawl. It costs readability (the agent wades through more before it can act, and attention thins across the excess), maintainability (every extra line is one more to keep **relevant**), and tokens. The cure is the **information hierarchy**: push **reference** down behind **context pointers**, and split by **branch** or sequence so each path carries only what it needs. Distinct from **sediment** (length from stale accumulation) and **duplication** (length from repeated meaning) — sprawl is length itself, whatever its cause.
|
||||
|
||||
_Avoid_: bloat, length, size, verbosity
|
||||
|
||||
## Steering
|
||||
|
||||
The levers that shape the agent's runtime behaviour toward **Predictability**.
|
||||
|
||||
### Branch
|
||||
|
||||
A distinct way a skill can be invoked — a case the skill handles — so different runs take different paths through it. A skill with many steps may carry many branches; a linear one has none.
|
||||
|
||||
_Avoid_: path, case, fork
|
||||
|
||||
### Leading Word
|
||||
|
||||
A compact concept — also called a _Leitwort_ — already living in the model's pretraining, that the agent thinks with while running the skill. It encodes a behavioural principle in the fewest possible tokens by invoking priors the model already holds (e.g. _lesson_, _proximal zone of development_, _fog of war_, _tracer bullets_). Repeated as a token, never as a sentence, it accumulates a distributed definition across the skill and anchors a whole region of behaviour. Coining your own works if you define it clearly, but a made-up word recruits no priors — you pay in definition tokens what a pretrained word gives free. Reach for an existing word first.
|
||||
|
||||
A leading word serves **predictability** twice. In the body it anchors **execution** — the agent reaches for the same behaviour every time the concept appears, and inside flat reference it focuses attention on a class of thing to look for, recruiting the right checks each run. In the **description** it anchors **invocation** — and not only within the skill: when the same word lives in your prompts, your docs, and your codebase, the agent links that shared language to the skill and fires it more reliably. Word a description with the leading words you actually use when you want the skill.
|
||||
|
||||
_Avoid_: keyword, term, motif
|
||||
|
||||
### Completion Criterion
|
||||
|
||||
The condition that tells the agent a unit of work is done — the target it judges against. Two properties make it a lever, not just a quality. Its **clarity** (can the agent tell done from not-done?) resists **premature completion** — a vague bound ("understanding reached") lets the agent declare done and slip to the next step; this axis needs _steps_ to bite, since premature completion is a between-steps failure. Its **demand** (how much it requires) sets **legwork** — "every modified model accounted for" forces thorough work where "produce a change list" does not — and this axis is _not_ step-bound: it can bind a body of flat reference too, which is how a skill with no steps still carries an exhaustiveness bar ("every rule applied"). The strongest criteria are both checkable and exhaustive.
|
||||
|
||||
_Avoid_: done condition, exit condition, stopping rule
|
||||
|
||||
### Legwork
|
||||
|
||||
The work an agent does behind the scenes within a single step — reading files, exploring the codebase, making changes, digging up what it needs rather than offloading to the user. It lives below the step structure: never written as its own step, latent in the wording, controlled by the agent rather than the skill. The within-step counterpart to **post-completion steps**' across-step pull. Raised by a **leading word** (_comprehensive_, _thorough_) or a **completion criterion** that demands the work be exhaustive — including the demand axis applied to flat reference, which is what drives a skill of flat reference to cover all its rungs. Goes thin either when that demand is missing or when **premature completion** cuts the step short.
|
||||
|
||||
_Avoid_: scope, effort, diligence, coverage
|
||||
|
||||
### Post-Completion Steps
|
||||
|
||||
The **steps** that follow the current step. Visible, they pull the agent forward into **premature completion** — the more it sees, the stronger the tug; the defence is to hide them by splitting the sequence of steps into two.
|
||||
|
||||
_Avoid_: horizon, fog of war, lookahead
|
||||
|
||||
### Premature Completion
|
||||
|
||||
_Failure mode._ Ending the current step before it is genuinely done, because the agent's attention slips to being done rather than to the work. A between-steps failure: it needs **steps** to occur — a skill with no steps that quits early isn't premature completion but thin **legwork** under an unmet demand. A tug-of-war between two forces: visible **post-completion steps** (the pull forward) and the **completion criterion**'s clarity (the resistance — a sharp, checkable bar holds; a vague one gives way). Fuzziness is the necessary condition: a sharp bound resists the pull no matter how many later steps are visible, so a step that never rushes needs no defending. Two levers hold a step that does, but reach for them in order: **sharpen the bound first** — it is local and cheap. Only when the criterion is irreducibly fuzzy _and_ you actually observe the rush do you **hide the later steps** — and hiding only works across a real context boundary (a user-invoked hand-off or a subagent dispatch; an inline model-invoked call leaves the later steps in context and clears nothing). One cause of thin legwork, but distinct from it: legwork can be thin even when a step runs to full completion.
|
||||
|
||||
_Avoid_: premature closure, the rush, rushing, shortcutting
|
||||
|
||||
## Pruning
|
||||
|
||||
Keeping a skill lean — each remedy paired with the failure it cures.
|
||||
|
||||
### Single Source of Truth
|
||||
|
||||
The desired state where each meaning lives in exactly one authoritative place, so a change to the skill's behaviour is a change in one place. **Duplication** is its violation.
|
||||
|
||||
_Avoid_: home, canonical location
|
||||
|
||||
### Duplication
|
||||
|
||||
_Failure mode._ The same meaning given more than one **single source of truth**. It costs maintenance (change one place, you must change the others), costs tokens, and inflates prominence — repeating a meaning weights it on the ladder past its real rank. The accidental inverse of a **leading word**, which raises attention on purpose by repeating a token, never the meaning.
|
||||
|
||||
_Avoid_: repetition, redundancy
|
||||
|
||||
### Relevance
|
||||
|
||||
Whether a line still bears on what the skill does — the lens for what to keep. A line loses relevance either by never bearing on the task (mere exposition, or a **branch** that should be disclosed) or by going stale: drifting out of date as the behaviour or world it describes changes. Shorter skills are easier to keep relevant, because each line is cheaper to check. Distinct from **no-op**: relevance asks whether a line bears on the task, not whether it changes behaviour.
|
||||
|
||||
_Avoid_: load-bearing, staleness, freshness
|
||||
|
||||
### Sediment
|
||||
|
||||
_Failure mode._ Layers of old content that settle in a skill and are never cleared, because adding feels safe and removing feels risky — so stale and irrelevant lines accumulate and you must core down through them to find what is still live. The default fate of any skill without a pruning discipline; the slow erosion of **relevance**, as opposed to **duplication**'s repeated meaning.
|
||||
|
||||
_Avoid_: accretion, bloat, cruft, rot
|
||||
|
||||
### No-Op
|
||||
|
||||
_Failure mode._ An instruction that changes nothing because the model already does it by default — you pay load to tell the agent what it would do anyway. The test: does a line change behaviour versus the default? A line can be perfectly **relevant** and still be a no-op. The same priors that make a **leading word** free make a no-op worthless.
|
||||
|
||||
A leading word is a _technique_; No-Op is a _verdict_ on a line — and they cross. A leading word too weak to beat the default is a no-op (_be thorough_ when the agent is already thorough-ish), and the fix is a stronger word that passes the verdict (_relentless_), not a different technique. So the No-Op test — does it change behaviour versus the default? — is also how you grade whether a leading word is earning its repetitions. This is model-relative, not reader-relative: two people disagreeing over whether a line is a no-op disagree about the default, and settle it by running the skill, not by debate.
|
||||
|
||||
_Avoid_: redundant instruction, restating the obvious, belaboring
|
||||
@@ -1,49 +0,0 @@
|
||||
---
|
||||
name: craft-skill
|
||||
description: Draft a new skill, or audit and rewrite an existing one, judged against the vocabulary in GLOSSARY.md.
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
Draft a new skill from scratch, or audit and rewrite an existing one — both judged against one bar: **predictability**, the agent taking the same process every run. **Bold terms** are defined in [`GLOSSARY.md`](GLOSSARY.md); look them up there for the full meaning.
|
||||
|
||||
This skill does not judge general prose quality (clarity, jargon, sentence structure) — only skill-specific structure. A dedicated technical-writing-guide skill will cover the former once it exists; until then, use your own judgment for sentence-level prose.
|
||||
|
||||
## Which branch
|
||||
|
||||
If the request describes a new workflow, capability, or repeated manual process with no existing skill named — **Draft a new skill**. If it names an existing skill (by name or path) to review, fix, or improve — **Audit an existing skill**. Both end at **Verify and ship**.
|
||||
|
||||
## Draft a new skill
|
||||
|
||||
1. **Capture intent.** If the conversation already contains the workflow (e.g. "turn this into a skill"), extract answers from it first; only ask about what's missing. Ask one question at a time — several at once is bewildering:
|
||||
- What should this make the agent do — is it a **procedure** (ordered **steps**), **knowledge** it consults (**reference**), or both? This decides the shape from the start.
|
||||
- When would you actually reach for it: do you type its name, or should the agent reach for it unprompted? Walk the **context load** vs **cognitive load** tradeoff explicitly rather than defaulting — see `Invocation` in GLOSSARY.md.
|
||||
- Does it have distinct **branches** — cases that take different paths? Name each. A linear checklist's items aren't branches by themselves — look for actual alternate paths, not the steps that always all run.
|
||||
- Is this workflow already documented somewhere in the project (a README, CLAUDE.md, CONTRIBUTING)? If so the draft should point there rather than restate it — see `External Reference` and `Single Source of Truth` in GLOSSARY.md.
|
||||
- For each step, what does done look like — a **completion criterion** you could check without ambiguity?
|
||||
- Is there already a word — in your prompts, docs, or codebase — that names this behavior? Reach for that **leading word** before coining one.
|
||||
Done when every axis above has an answer, or the user says to just draft something and iterate.
|
||||
|
||||
2. **Write the draft.** First decide where it lives: project-local `.claude/skills/` if the workflow is tied to this one repo, the personal set if it's general-purpose across projects. The personal set is not authored in `~/.claude/skills/` — that tree is generated, and every file under it is a read-only symlink into the Nix store. Write it in the dotfiles repo at `modules/claude-code/skills/<name>/` and rebuild to make it live. Creating files directly under `~/.claude/skills/` looks like it works, because the directories themselves are writable, but the result is untracked by the repo and reaches no other machine. Then follow the **information hierarchy**: steps for what the agent does in order, in-file **reference** for facts every branch needs, and disclose the rest behind a pointer — to a sibling file, or to the existing project docs identified in step 1 rather than restating them. Done when every branch from step 1 has somewhere to live, and no sentence fails the no-op test in isolation (see `No-Op` in GLOSSARY.md).
|
||||
|
||||
## Audit an existing skill
|
||||
|
||||
1. **Locate it.** Check the current project's `.claude/skills/`, then `~/.claude/skills/`, then `~/.claude/skills/library/`, in that order; ask if the name is ambiguous across locations. A hit under `~/.claude/skills/` is a read-only symlink and cannot be edited in place: its source is the dotfiles repo, at `modules/claude-code/skills/<name>/` for a personal skill or `modules/claude-code/skills/library/<name>/` for a library one. Edit there and rebuild. If it's tracked in a project's `skills-lock.yaml`, mention that editing it here will make it read as locally-customized to `update-skills` — confirm that's actually intended rather than editing the library source.
|
||||
|
||||
2. **Apply the checklist.** Read the skill and its disclosed files, then check each against GLOSSARY.md, quoting the offending line for anything that fails:
|
||||
- **Premature completion** — is each completion criterion checkable, and does it demand what the step actually needs?
|
||||
- **Duplication** — does any meaning appear in more than one place?
|
||||
- **Sediment** — any line that no longer bears on what the skill does?
|
||||
- **Sprawl** — could in-file reference be disclosed instead, or a run of steps split by branch?
|
||||
- **No-op** — any sentence the model would already do by default? Test sentence by sentence, not line by line — a line can carry one load-bearing sentence and one no-op sentence together.
|
||||
- Is the **invocation** choice (model- vs user-invoked) still the right one for how this skill actually gets used? Is there a restated concept that should collapse into a **leading word**?
|
||||
|
||||
3. **Rewrite** based on the findings. Done when every finding from step 2 is either addressed or explicitly noted as intentionally kept.
|
||||
|
||||
## Verify and ship
|
||||
|
||||
1. Propose one realistic test prompt — reflecting the trigger phrasing gathered (draft) or the skill's existing purpose (audit) — and get it confirmed or adjusted before spending a run on it.
|
||||
2. Spawn one subagent: give it the skill's path and the confirmed prompt, have it attempt the task using the skill, and report back what happened — including anywhere it hesitated, misread the skill, or did something unexpected.
|
||||
3. Re-read the draft/rewrite against GLOSSARY.md's failure modes in light of that run, and fix whatever either pass turned up. If the fix is substantial, repeat from step 1; otherwise it's done.
|
||||
4. Stage the specific changed or created paths — one path per file, never a wildcard — with `git add <path>`. Do not commit; that's left to the user.
|
||||
|
||||
Done when the subagent's run succeeded without confusion on the confirmed prompt, the checklist raised nothing outstanding, and every changed path is staged.
|
||||
@@ -1,72 +0,0 @@
|
||||
---
|
||||
name: implement
|
||||
description: Implement a task file produced by /to-tasks on its own branch, review it, close it out, and open a PR.
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
Implement a task file end-to-end: branch, build it, review it, close it out, and open a PR.
|
||||
|
||||
## Process
|
||||
|
||||
### 1. Read the task file and check blockers
|
||||
|
||||
The user passes the path to a task file (`.claude/tasks/<NNNN>-slug.md`, as produced by `/to-tasks`) explicitly — don't infer one from context.
|
||||
|
||||
If the task's frontmatter has a `blocked-by` field, read each referenced task file and check for any unresolved `- [ ]` acceptance criterion. If any blocker isn't fully resolved, warn the user which one and why, and confirm before proceeding — don't refuse outright.
|
||||
|
||||
### 2. Sync `main` and branch off it
|
||||
|
||||
Switch to `main`, fast-forward it (`git pull --ff-only`), then create and switch to a branch named `task-<NNNN>-<slug>` — taken verbatim from the task file's basename, so `.claude/tasks/0003-issue-view-and-truncation.md` gives `task-0003-issue-view-and-truncation`.
|
||||
Use whatever git invocation the project itself uses; a repo may wrap it.
|
||||
|
||||
Stop and ask the user before going further if:
|
||||
|
||||
- **The working tree has uncommitted changes.** Never stash them automatically.
|
||||
- **`git pull --ff-only` fails.** Local `main` has diverged; report what diverged. Never `reset --hard`.
|
||||
- **The task's `blocked-by` work isn't reachable from `main`.** The blocker's PR is likely unmerged; name it.
|
||||
|
||||
If the task branch already exists, switch to it and carry on — don't recreate it, and don't rebase it onto the freshly pulled `main`.
|
||||
Always branch off `main`, never off a sibling task branch.
|
||||
|
||||
### 3. Implement
|
||||
|
||||
Build the work described in the task's "What to build" section, satisfying its acceptance criteria. Use `/test-driven-development` where possible, at the seams already agreed when the spec or task was written.
|
||||
|
||||
Run typechecking regularly, single test files regularly, and the full test suite once at the end.
|
||||
|
||||
### 4. Stage the changes
|
||||
|
||||
Stage (`git add`) each file you create or modify, specifically — not `git add -A` — so nothing untracked and unrelated gets swept in.
|
||||
|
||||
### 5. Review
|
||||
|
||||
Run `/review-uncommitted`, passing the task file itself as the spec source — it already links back to its parent spec via its `spec` frontmatter field, if any. Address anything it raises before moving on.
|
||||
|
||||
Keep its report — step 7 puts part of it in the PR.
|
||||
|
||||
### 6. Close out the task file
|
||||
|
||||
Mark every acceptance criterion `[x]` if satisfied or `[-]` if deliberately dropped, so none are left `[ ]`. Append a `## Implementation Notes` section explaining any deviations from the plan — dropped criteria (referencing which, and why), scope changes, decisions made mid-implementation, follow-ups worth flagging. Skip the section only if nothing deviated. Leave the `spec` and `blocked-by` frontmatter fields untouched — they're a permanent record, not a checklist to clear (see `to-tasks`'s `TASK-FORMAT.md`).
|
||||
|
||||
Stage the updated task file with the rest.
|
||||
|
||||
### 7. Commit, push, and open a PR
|
||||
|
||||
Make **one** commit for the whole task, code and task file together.
|
||||
Match the repo's existing commit convention — read its recent history or its CLAUDE.md, don't assume one — and reference the task in the subject, e.g. `(task 0003)`.
|
||||
|
||||
Push the branch (`git push -u origin task-<NNNN>-<slug>`) and open a pull request against `main` with the repo's forge CLI: `tea` for Gitea, `gh` for GitHub.
|
||||
Never base the PR on a sibling task branch.
|
||||
Open it ready, not draft.
|
||||
|
||||
The PR body carries:
|
||||
|
||||
- The task file's path.
|
||||
- A short summary of what was built, and any deviations — the same ones just written into `## Implementation Notes`.
|
||||
- A `## Review` section: the `## Risk` block from step 5 verbatim (overall rating plus its six factor lines), then **only** the Standards and Spec findings left unaddressed, each with a one-line reason. Findings that were fixed are already in the diff; leave them out.
|
||||
|
||||
Don't ask for confirmation before pushing or opening the PR.
|
||||
If the repo has no remote, stop after the commit and report that no PR was opened.
|
||||
|
||||
Stay on the task branch when done.
|
||||
Report the branch name, the PR URL, and any unaddressed review findings.
|
||||
@@ -1,119 +0,0 @@
|
||||
# HTML Report Format
|
||||
|
||||
The architectural review is rendered as a single self-contained HTML file in the OS temp directory. Tailwind and Mermaid both come from CDNs. Mermaid handles graph-shaped diagrams reliably; hand-built divs and inline SVG handle the more editorial visuals (mass diagrams, cross-sections). Mix the two — don't lean on Mermaid for everything, it'll start to look generic.
|
||||
|
||||
## Scaffold
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Architecture review — {{repository name}}</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script type="module">
|
||||
import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs";
|
||||
mermaid.initialize({ startOnLoad: true, theme: "neutral", securityLevel: "loose" });
|
||||
</script>
|
||||
<style>
|
||||
/* small custom layer for things Tailwind doesn't cover cleanly:
|
||||
dashed seam lines, hand-drawn-feeling arrow heads, etc. */
|
||||
.seam { stroke-dasharray: 4 4; }
|
||||
.leak { stroke: #dc2626; }
|
||||
.deep { background: linear-gradient(135deg, #0f172a, #1e293b); }
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-stone-50 text-slate-900 font-sans">
|
||||
<main class="max-w-5xl mx-auto px-6 py-12 space-y-12">
|
||||
<header>...</header>
|
||||
<section id="candidates" class="space-y-10">...</section>
|
||||
<section id="top-recommendation">...</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Header
|
||||
|
||||
Repo name, date, and a compact legend: solid box = module, dashed line = seam, red arrow = leakage, thick dark box = deep module. No introduction paragraph — straight into the candidates.
|
||||
|
||||
## Candidate card
|
||||
|
||||
The diagrams carry the weight. Prose is sparse, plain, and uses the glossary terms (from the `/codebase-design` skill) without ceremony.
|
||||
|
||||
Each candidate is one `<article>`:
|
||||
|
||||
- **Title** — short, names the deepening (e.g. "Collapse the Order intake pipeline").
|
||||
- **Badge row** — recommendation strength (`Strong` = emerald, `Worth exploring` = amber, `Speculative` = slate), plus a tag for the dependency category (`in-process`, `local-substitutable`, `ports & adapters`, `mock`).
|
||||
- **Files** — monospaced list, `font-mono text-sm`.
|
||||
- **Before / After diagram** — the centrepiece. Two columns, side by side. See patterns below.
|
||||
- **Problem** — one sentence. What hurts.
|
||||
- **Solution** — one sentence. What changes.
|
||||
- **Wins** — bullets, ≤6 words each. e.g. "Tests hit one interface", "Pricing logic stops leaking", "Delete 4 shallow wrappers".
|
||||
- **ADR callout** (if applicable) — one line in an amber-tinted box.
|
||||
|
||||
No paragraphs of explanation. If the diagram needs a paragraph to be understood, redraw the diagram.
|
||||
|
||||
## Diagram patterns
|
||||
|
||||
Pick the pattern that fits the candidate. Mix them. Don't make every diagram look the same — variety is part of the point.
|
||||
|
||||
### Mermaid graph (the workhorse for dependencies / call flow)
|
||||
|
||||
Use a Mermaid `flowchart` or `graph` when the point is "X calls Y calls Z, and look at the mess." Wrap it in a Tailwind-styled card so it doesn't feel parachuted in. Style with classDef to colour leakage edges red and the deep module dark. Sequence diagrams work well for "before: 6 round-trips; after: 1."
|
||||
|
||||
```html
|
||||
<div class="rounded-lg border border-slate-200 bg-white p-4">
|
||||
<pre class="mermaid">
|
||||
flowchart LR
|
||||
A[OrderHandler] --> B[OrderValidator]
|
||||
B --> C[OrderRepo]
|
||||
C -.leak.-> D[PricingClient]
|
||||
classDef leak stroke:#dc2626,stroke-width:2px;
|
||||
class C,D leak
|
||||
</pre>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Hand-built boxes-and-arrows (when Mermaid's layout fights you)
|
||||
|
||||
Modules as `<div>`s with borders and labels. Arrows as inline SVG `<line>` or `<path>` elements positioned absolutely over a relative container. Reach for this when you want the "after" diagram to feel like one thick-bordered deep module with greyed-out internals — Mermaid won't render that with the right weight.
|
||||
|
||||
### Cross-section (good for layered shallowness)
|
||||
|
||||
Stack horizontal bands (`h-12 border-l-4`) to show layers a call passes through. Before: 6 thin layers each doing nothing. After: 1 thick band labelled with the consolidated responsibility.
|
||||
|
||||
### Mass diagram (good for "interface as wide as implementation")
|
||||
|
||||
Two rectangles per module — one for interface surface area, one for implementation. Before: interface rectangle is nearly as tall as the implementation rectangle (shallow). After: interface rectangle is short, implementation rectangle is tall (deep).
|
||||
|
||||
### Call-graph collapse
|
||||
|
||||
Before: a tree of function calls rendered as nested boxes. After: the same tree collapsed into one box, with the now-internal calls shown faded inside it.
|
||||
|
||||
## Style guidance
|
||||
|
||||
- Lean editorial, not corporate-dashboard. Generous whitespace. Serif optional for headings (`font-serif` works well with stone/slate).
|
||||
- Colour sparingly: one accent (emerald or indigo) plus red for leakage and amber for warnings.
|
||||
- Keep diagrams ~320px tall so before/after sits comfortably side by side without scrolling.
|
||||
- Use `text-xs uppercase tracking-wider` for module labels inside diagrams — they should read as schematic, not as UI.
|
||||
- The only scripts are the Tailwind CDN and the Mermaid ESM import. The report is otherwise static — no app code, no interactivity beyond Mermaid's own rendering.
|
||||
|
||||
## Top recommendation section
|
||||
|
||||
One larger card. Candidate name, one sentence on why, anchor link to its card. That's it.
|
||||
|
||||
## Tone
|
||||
|
||||
Plain English, concise — but the architectural nouns and verbs come straight from the `/codebase-design` glossary, terms and exclusions alike. Concision is not an excuse to drift.
|
||||
|
||||
**Phrasings that fit the style:**
|
||||
|
||||
- "Order intake module is shallow — interface nearly matches the implementation."
|
||||
- "Pricing leaks across the seam."
|
||||
- "Deepen: one interface, one place to test."
|
||||
- "Two adapters justify the seam: HTTP in prod, in-memory in tests."
|
||||
|
||||
**Wins bullets** name the gain in glossary terms: *"locality: bugs concentrate in one module"*, *"leverage: one interface, N call sites"*, *"interface shrinks; implementation absorbs the wrappers"*. Don't write *"easier to maintain"* or *"cleaner code"* — those terms aren't in the glossary and don't earn their place.
|
||||
|
||||
No hedging, no throat-clearing, no "it's worth noting that…". If a sentence could be a bullet, make it a bullet. If a bullet could be cut, cut it. If a term isn't in the `/codebase-design` glossary, reach for one that is before inventing a new one.
|
||||
@@ -1,68 +0,0 @@
|
||||
---
|
||||
name: improve-codebase
|
||||
description: Scan a codebase for deepening opportunities, present them as a visual HTML report, then grill through whichever one you pick.
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
# Improve Codebase
|
||||
|
||||
Surface architectural friction and propose **deepening opportunities** — refactors that turn shallow modules into deep ones. The aim is testability and AI-navigability.
|
||||
|
||||
This command is _informed_ by the project's domain model and built on a shared design vocabulary:
|
||||
|
||||
- Run the `/codebase-design` skill for the architecture vocabulary (**module**, **interface**, **depth**, **seam**, **adapter**, **leverage**, **locality**) and its principles (the deletion test, "the interface is the test surface", "one adapter = hypothetical seam, two = real"). Use its terms exactly in every suggestion, per its glossary.
|
||||
- The domain language in `.claude/CONTEXT.md` gives names to good seams; ADRs in `.claude/adr/` record decisions this command should not re-litigate.
|
||||
|
||||
## Process
|
||||
|
||||
### 1. Explore
|
||||
|
||||
Read the project's domain glossary (`.claude/CONTEXT.md`) and any ADRs in the area you're touching first.
|
||||
|
||||
Then use the Agent tool with `subagent_type=Explore` to walk every top-level module or directory in scope (the whole repository, or the area the user pointed you to) — even if only briefly for the ones that turn out clean. Within each, judge friction organically rather than against a rigid checklist:
|
||||
|
||||
- Where does understanding one concept require bouncing between many small modules?
|
||||
- Where are modules **shallow** — interface nearly as complex as the implementation?
|
||||
- Where have pure functions been extracted just for testability, but the real bugs hide in how they're called (no **locality**)?
|
||||
- Where do tightly-coupled modules leak across their seams?
|
||||
- Which parts of the codebase are untested, or hard to test through their current interface?
|
||||
|
||||
Apply the **deletion test** to anything you suspect is shallow: would deleting it concentrate complexity, or just move it? A "yes, concentrates" is the signal you want.
|
||||
|
||||
Zero candidates is a legitimate outcome for a genuinely clean area — but it has to follow from having looked, not from stopping early.
|
||||
|
||||
### 2. Present candidates as an HTML report
|
||||
|
||||
Write a self-contained HTML file to the OS temp directory so nothing lands in the repository. Resolve the temp dir from `$TMPDIR`, falling back to `/tmp` (or `%TEMP%` on Windows), and write to `<tmpdir>/architecture-review-<timestamp>.html` so each run gets a fresh file. Open it for the user — `xdg-open <path>` on Linux, `open <path>` on macOS, `start <path>` on Windows. Treat the open as best-effort: it's a no-op in a headless/sandboxed environment with no display server, so report the absolute path regardless of whether the open succeeded.
|
||||
|
||||
The report uses **Tailwind via CDN** for layout and styling, and **Mermaid via CDN** for diagrams where a graph/flow/sequence reliably communicates the structure. Mix Mermaid with hand-crafted CSS/SVG visuals — use Mermaid when relationships are graph-shaped (call graphs, dependencies, sequences), and hand-built divs/SVG when you want something more editorial (mass diagrams, cross-sections, collapse animations). Each candidate gets a **before/after visualisation**. Be visual.
|
||||
|
||||
For each candidate, render a card with:
|
||||
|
||||
- **Files** — which files/modules are involved
|
||||
- **Problem** — why the current architecture is causing friction
|
||||
- **Solution** — plain English description of what would change
|
||||
- **Benefits** — explained in terms of locality and leverage, and how tests would improve
|
||||
- **Before / After diagram** — side-by-side, custom-drawn, illustrating the shallowness and the deepening
|
||||
- **Recommendation strength** — one of `Strong`, `Worth exploring`, `Speculative`, rendered as a badge
|
||||
|
||||
End the report with a **Top recommendation** section: which candidate you'd tackle first and why.
|
||||
|
||||
**Use `.claude/CONTEXT.md` vocabulary for the domain, and the `/codebase-design` vocabulary for the architecture.** If `.claude/CONTEXT.md` defines "Order," talk about "the Order intake module" — not "the FooBarHandler," and not "the Order service."
|
||||
|
||||
**ADR conflicts**: if a candidate contradicts an existing ADR, only surface it when the friction is real enough to warrant revisiting the ADR. Mark it clearly in the card (e.g. a warning callout: _"contradicts ADR-0007 — but worth reopening because…"_). Don't list every theoretical refactor an ADR forbids.
|
||||
|
||||
See [HTML-REPORT.md](HTML-REPORT.md) for the full HTML scaffold, diagram patterns, and styling guidance.
|
||||
|
||||
Do NOT propose interfaces yet. After the file is written, ask the user: "Which of these would you like to explore?"
|
||||
|
||||
### 3. Grilling loop
|
||||
|
||||
Once the user picks a candidate, run `/grill` to walk the design tree with them — constraints, dependencies, the shape of the deepened module, what sits behind the seam, what tests survive.
|
||||
|
||||
Side effects happen inline as decisions crystallize — run `/domain-modeling` to keep the domain model current as you go, even if `.claude/CONTEXT.md` doesn't exist yet:
|
||||
|
||||
- **Naming a deepened module after a concept not in `.claude/CONTEXT.md`?** Add the term to `.claude/CONTEXT.md`. Create the file lazily if it doesn't exist.
|
||||
- **Sharpening a fuzzy term during the conversation?** Update `.claude/CONTEXT.md` right there.
|
||||
- **User rejects the candidate with a load-bearing reason?** Offer an ADR, framed as: _"Want me to record this as an ADR so future architecture reviews don't re-suggest it?"_ Only offer when the reason would actually be needed by a future explorer to avoid re-suggesting the same thing — skip ephemeral reasons ("not worth it right now") and self-evident ones.
|
||||
- **Want to explore alternative interfaces for the deepened module?** Run the `/codebase-design` skill and use its design-it-twice parallel sub-agent pattern.
|
||||
@@ -1,152 +0,0 @@
|
||||
---
|
||||
name: nbdev
|
||||
description: nbdev conventions for notebooks — directives, cell structure, docments, tests, execution. Use for any .ipynb operation — including reads — in an nbdev project.
|
||||
---
|
||||
|
||||
# nbdev
|
||||
|
||||
## Tool Preference
|
||||
|
||||
- Use the **Jupyter MCP** for all `.ipynb` operations — read, edit, insert, delete, execute
|
||||
- Do **not** use the built-in `NotebookEdit` tool; it writes cell source as a single JSON string which breaks standard Jupyter formatting and produces noisy diffs
|
||||
- Re-read the notebook before editing if it may have changed since your last read — cell indices/IDs can shift under concurrent edits (e.g. via JupyterLab's real-time collaboration), and editing by a stale index can hit the wrong cell
|
||||
|
||||
## nbdev Directives
|
||||
|
||||
Directives are comments at the top of a cell that control how nbdev processes it:
|
||||
|
||||
- `#| export` — include this cell in the exported Python module and in the docs
|
||||
- `#| hide` — exclude this cell from both the module and the docs
|
||||
- `#| hide_input` — show cell output in docs but hide the source code
|
||||
- `#| default_exp module_name` — set which module this notebook exports to (second cell)
|
||||
- `#| exporti` — export to module but do not show in docs (for internal helpers)
|
||||
- `#| eval: false` — include in docs but do not execute during `nbdev-test`
|
||||
|
||||
Imports needed only for tests or examples should **not** be exported.
|
||||
|
||||
Never hand-edit the exported `.py` module files — they're build artifacts regenerated from the notebook by `nbdev_export`. All edits go through the source notebook in `nbs/`.
|
||||
|
||||
## Notebook Structure
|
||||
|
||||
Every notebook must follow this structure:
|
||||
|
||||
**Cell 1 — Markdown frontmatter:**
|
||||
```markdown
|
||||
# Module Title
|
||||
|
||||
> A one-line description of what this module does
|
||||
```
|
||||
The H1 becomes the page title in docs. The blockquote becomes the subtitle.
|
||||
|
||||
**Cell 2 — Default export:**
|
||||
```python
|
||||
#| default_exp module_name
|
||||
```
|
||||
|
||||
**Body cells** — alternating between exported code, demonstrations, and markdown explanations (see Cell Structure below).
|
||||
|
||||
**Last cell:**
|
||||
```python
|
||||
#| hide
|
||||
import nbdev; nbdev.nbdev_export()
|
||||
```
|
||||
|
||||
Before declaring any notebook task complete, restart the kernel and run all cells top-to-bottom to verify it is fully reproducible.
|
||||
|
||||
## Cell Structure
|
||||
|
||||
Keep cells short. Each exported function gets its own cell, immediately followed by a demonstration. Do not write long functions with comments interspersed — split them into small separate cells with explanations and working examples after each.
|
||||
|
||||
The pattern per concept:
|
||||
|
||||
1. *(Optional)* A markdown cell explaining what comes next
|
||||
2. A `#| export` code cell with the function
|
||||
3. One or more plain code cells demonstrating usage
|
||||
4. Assertions that double as tests
|
||||
|
||||
Example:
|
||||
```python
|
||||
#| export
|
||||
def slugify(text: str) -> str:
|
||||
"Convert text to a URL-safe slug"
|
||||
return re.sub(r"[^a-z0-9]+", "-", text.lower()).strip("-")
|
||||
```
|
||||
```python
|
||||
slug = slugify("Hello, World!")
|
||||
assert slug == "hello-world"
|
||||
slug
|
||||
```
|
||||
|
||||
## Docstrings and Parameter Documentation
|
||||
|
||||
Keep docstrings short — a single-line summary is sufficient for most functions. Elaborate in separate markdown or code cells below, where you can use real examples.
|
||||
|
||||
Use **docments** (inline parameter comments) instead of verbose docstring parameter sections:
|
||||
|
||||
```python
|
||||
#| export
|
||||
def greet(
|
||||
name: str, # Person to greet
|
||||
greeting: str="Hi", # Greeting word to use
|
||||
) -> str: # The composed greeting
|
||||
"Compose a greeting for name"
|
||||
return f"{greeting}, {name}!"
|
||||
```
|
||||
|
||||
This renders as a clean parameter table in the docs automatically — no need to repeat type information in the docstring body.
|
||||
|
||||
Use backticks around symbol names in docstrings and markdown — nbdev automatically converts these to hyperlinks to the relevant reference page.
|
||||
|
||||
## Code Style
|
||||
|
||||
- **Prefer composition**: write small functions that do one thing well
|
||||
- Each exported function should be focused enough to fit naturally in a single notebook cell — one cell, one idea
|
||||
- Use type hints on all exported functions
|
||||
- Avoid classes unless state is genuinely needed — prefer functions that take and return data
|
||||
- If you do write a class, use `fastcore`'s `@patch` decorator to define each method in its own cell, immediately followed by a demonstration. This avoids long class definitions and keeps examples close to the code
|
||||
|
||||
When a class is needed, document its methods with `show_doc`:
|
||||
```python
|
||||
from nbdev.showdoc import show_doc
|
||||
show_doc(MyClass.my_method)
|
||||
```
|
||||
|
||||
## Tests
|
||||
|
||||
Every code cell is run as a test by nbdev unless explicitly marked otherwise — any exception fails the test.
|
||||
|
||||
- Turn demonstrations into tests by adding `assert` statements
|
||||
- Use `fastcore.test` helpers for better error messages:
|
||||
```python
|
||||
from fastcore.test import test_eq, test_fail
|
||||
test_eq(slugify("Hello World"), "hello-world")
|
||||
```
|
||||
- Document expected error cases with `test_fail`:
|
||||
```python
|
||||
test_fail(lambda: slugify(""), contains="empty")
|
||||
```
|
||||
- Each test/demo cell should import what it needs directly — don't rely on a name imported in a later cell just because it happened to be in scope during a prior run
|
||||
|
||||
## Execution
|
||||
|
||||
- Always execute cells after writing them to verify they work
|
||||
- If a cell errors, read the full traceback before attempting a fix — do not guess
|
||||
- When installing packages, use `%pip install` inside the notebook (not `!pip install`) so they install into the running kernel
|
||||
- Use autoreload at the top of notebooks that import from other modules in the project:
|
||||
```python
|
||||
%load_ext autoreload
|
||||
%autoreload 2
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
- Use H2 (`##`) markdown cells to group related symbols within a notebook
|
||||
- Use H4 (`####`) markdown cells to split long explanations within a symbol's section (notes, examples, edge cases, etc.)
|
||||
- Add rich representations to classes via `_repr_markdown_` where it aids understanding
|
||||
- Include real code examples, plots, and diagrams — notebooks support rich output, use it
|
||||
|
||||
## Outputs
|
||||
|
||||
- Never print secrets, tokens, passwords, or API keys into cell output — notebook outputs get committed to git and published in docs, unlike transient script output
|
||||
- Prefer summaries over dumping large data structures (`.head()`, `len()`, `[:5]`, etc.)
|
||||
- Large outputs consume context window — keep them concise
|
||||
@@ -1,37 +0,0 @@
|
||||
---
|
||||
name: remove-skills
|
||||
description: Remove one or more previously added library skills from the current project.
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
Removes a skill that [`setup-skills`](../setup-skills/SKILL.md) previously
|
||||
copied into the current project, deleting both its files and its entry in
|
||||
`.claude/skills-lock.yaml` (see [LOCKFILE.md](../setup-skills/LOCKFILE.md)
|
||||
for its schema).
|
||||
|
||||
## Steps
|
||||
|
||||
1. Read `.claude/skills-lock.yaml`. If it doesn't exist or is empty, tell
|
||||
the user there's nothing installed to remove and stop.
|
||||
|
||||
2. Determine which skill(s) to remove:
|
||||
- If the user's invocation already named a specific skill, use that —
|
||||
if it isn't in the lockfile, say so and stop.
|
||||
- Otherwise, list every skill currently in the lockfile and ask the
|
||||
user to pick one (or more).
|
||||
|
||||
3. For each skill to remove, compute its current hash
|
||||
(`~/.claude/skills/setup-skills/hash-dir.sh .claude/skills/<name>`)
|
||||
and compare it to the hash stored in the lockfile:
|
||||
- If it matches (never modified since it was installed), delete
|
||||
`.claude/skills/<name>/` and remove its lockfile entry immediately —
|
||||
no confirmation needed, since nothing of the user's is being lost.
|
||||
- If it differs (locally customized), tell the user it has local
|
||||
changes that will be permanently lost and ask for confirmation
|
||||
before deleting. If they decline, leave that skill installed and
|
||||
move on to the next.
|
||||
|
||||
4. Finish with a summary of what was removed and what was left in place.
|
||||
|
||||
Done when every skill to remove has been either deleted (with its lockfile
|
||||
entry removed) or explicitly left in place with a stated reason.
|
||||
@@ -1,147 +0,0 @@
|
||||
---
|
||||
name: review-uncommitted
|
||||
description: Review the working tree's uncommitted changes along three axes — change risk, repo standards, and spec fidelity — using parallel sub-agents.
|
||||
---
|
||||
|
||||
Three-axis review of the diff between `HEAD` and the working tree:
|
||||
|
||||
- **Risk** — how much attention does this change warrant, from low to high?
|
||||
- **Standards** — does the code conform to this repo's documented coding standards?
|
||||
- **Spec** — does the code faithfully implement the originating PRD or task file?
|
||||
|
||||
All three axes run as **parallel sub-agents** so they don't pollute each other's context, then this skill aggregates their findings.
|
||||
|
||||
## Process
|
||||
|
||||
### 1. Capture the diff
|
||||
|
||||
The diff command is `git diff HEAD` — everything uncommitted, staged or not.
|
||||
New files must already be tracked (`git add`ed) to show up; this skill doesn't scan for untracked files, so that's the caller's responsibility.
|
||||
|
||||
Confirm the diff is non-empty before going further.
|
||||
An empty diff should fail here — not inside three parallel sub-agents.
|
||||
|
||||
### 2. Identify the spec source
|
||||
|
||||
Look for the originating spec, in this order:
|
||||
|
||||
1. A path the user passed as an argument.
|
||||
2. A spec file matching the branch name or feature — `.claude/spec/<feature-slug>.md`.
|
||||
3. If nothing is found, ask the user where the spec is.
|
||||
If they say there isn't one, the **Spec** sub-agent will skip and report "no spec available".
|
||||
|
||||
### 3. Identify the standards sources
|
||||
|
||||
Anything in the repo that documents how code should be written, such as `CODING_STANDARDS.md` or `CONTRIBUTING.md`.
|
||||
|
||||
On top of whatever the repo documents, the Standards axis always carries the **smell baseline** below — a fixed set of Fowler code smells (_Refactoring_, ch.3) that applies even when a repo documents nothing.
|
||||
Two rules bind it:
|
||||
|
||||
- **The repo overrides.**
|
||||
A documented repo standard always wins; where it endorses something the baseline would flag, suppress the smell.
|
||||
- **Always a judgement call.**
|
||||
Each smell is a labelled heuristic ("possible Feature Envy"), never a hard violation — and, like any standard here, skip anything tooling already enforces.
|
||||
|
||||
Each smell reads *what it is* → *how to fix*; match it against the diff:
|
||||
|
||||
- **Mysterious Name** — a function, variable, or type whose name doesn't reveal what it does or holds.
|
||||
→ rename it; if no honest name comes, the design's murky.
|
||||
- **Duplicated Code** — the same logic shape appears in more than one hunk or file in the change.
|
||||
→ extract the shared shape, call it from both.
|
||||
- **Feature Envy** — a method that reaches into another object's data more than its own.
|
||||
→ move the method onto the data it envies.
|
||||
- **Data Clumps** — the same few fields or params keep travelling together (a type wanting to be born).
|
||||
→ bundle them into one type, pass that.
|
||||
- **Primitive Obsession** — a primitive or string standing in for a domain concept that deserves its own type.
|
||||
→ give the concept its own small type.
|
||||
- **Repeated Switches** — the same `switch`/`if`-cascade on the same type recurs across the change.
|
||||
→ replace with polymorphism, or one map both sites share.
|
||||
- **Shotgun Surgery** — one logical change forces scattered edits across many files in the diff.
|
||||
→ gather what changes together into one module.
|
||||
- **Divergent Change** — one file or module is edited for several unrelated reasons.
|
||||
→ split so each module changes for one reason.
|
||||
- **Speculative Generality** — abstraction, parameters, or hooks added for needs the spec doesn't have.
|
||||
→ delete it; inline back until a real need shows.
|
||||
- **Message Chains** — long `a.b().c().d()` navigation the caller shouldn't depend on.
|
||||
→ hide the walk behind one method on the first object.
|
||||
- **Middle Man** — a class or function that mostly just delegates onward.
|
||||
→ cut it, call the real target direct.
|
||||
- **Refused Bequest** — a subclass or implementer that ignores or overrides most of what it inherits.
|
||||
→ drop the inheritance, use composition.
|
||||
|
||||
### 4. Risk rubric
|
||||
|
||||
The Risk axis judges the diff alone — no repo-doc lookup, no input from the Standards or Spec sub-agents.
|
||||
It always runs; it only needs the diff from step 1.
|
||||
|
||||
Rate each of these six factors **Low / Medium / High**, then take the single highest-rated factor as the overall rating (worst-factor-wins):
|
||||
|
||||
- **Blast radius** — isolated change vs. ripples across many files, modules, or callers.
|
||||
- **Reversibility** — trivial rollback vs. hard to undo (migrations, deletions, published API/schema changes).
|
||||
- **Test coverage** — covered by tests in/around the diff vs. untested.
|
||||
- **Sensitive domain** — touches auth, security, payments, permissions, concurrency, or data migrations.
|
||||
- **Size & complexity** — large diff or tangled control flow vs. small/simple.
|
||||
- **Runtime criticality** — hot path/production-critical vs. internal or dev-only tooling.
|
||||
|
||||
### 5. Spawn all three sub-agents in parallel
|
||||
|
||||
Send a single message with three `Agent` tool calls.
|
||||
Use the `general-purpose` subagent for all three.
|
||||
|
||||
**Risk sub-agent prompt** — include:
|
||||
|
||||
- The full diff (output of `git diff HEAD`).
|
||||
- The six risk factors from step 4, pasted in full.
|
||||
- The brief: "Rate each of the six factors Low/Medium/High with a one-clause reason, then give the overall rating as the highest of the six.
|
||||
Report the overall rating first, then the six factor lines.
|
||||
Under 200 words."
|
||||
|
||||
**Standards sub-agent prompt** — include:
|
||||
|
||||
- The full diff (output of `git diff HEAD`).
|
||||
- The list of standards-source files you found in step 3, **plus the smell baseline from step 3** pasted in full — the sub-agent has no other access to it.
|
||||
- The brief: "Report — per file/hunk where relevant — (a) every place the diff violates a documented standard: cite the standard (file + the rule); and (b) any baseline smell you spot: name it and quote the hunk.
|
||||
Distinguish hard violations from judgement calls — documented-standard breaches can be hard, but baseline smells are always judgement calls, and a documented repo standard overrides the baseline.
|
||||
Skip anything tooling enforces.
|
||||
Under 400 words."
|
||||
|
||||
**Spec sub-agent prompt** — include:
|
||||
|
||||
- The full diff (output of `git diff HEAD`).
|
||||
- The path or fetched contents of the spec.
|
||||
- The brief: "Report: (a) requirements the spec asked for that are missing or partial; (b) behaviour in the diff that wasn't asked for (scope creep); (c) requirements that look implemented but where the implementation looks wrong.
|
||||
Quote the spec line for each finding.
|
||||
Under 400 words."
|
||||
|
||||
If the spec is missing, skip the Spec sub-agent and note this in the final report.
|
||||
|
||||
### 6. Aggregate
|
||||
|
||||
Present the Risk report first, under a `## Risk` heading, with the overall rating bolded on its own line followed by the six factor lines:
|
||||
|
||||
```
|
||||
## Risk
|
||||
**Overall: HIGH**
|
||||
- Blast radius: ...
|
||||
- Reversibility: ...
|
||||
- Test coverage: ...
|
||||
- Sensitive domain: ...
|
||||
- Size & complexity: ...
|
||||
- Runtime criticality: ...
|
||||
```
|
||||
|
||||
Then present the Standards and Spec reports under `## Standards` and `## Spec` headings, verbatim or lightly cleaned.
|
||||
Do **not** merge or rerank findings — the axes are deliberately separate (see _Why Standards and Spec stay separate_).
|
||||
|
||||
End with a one-line summary: total findings per axis (Standards/Spec only), and the worst issue _within each axis_ (if any).
|
||||
Don't pick a single winner across axes — that's the reranking the separation exists to prevent.
|
||||
The risk rating isn't repeated here; it already leads the report.
|
||||
|
||||
## Why Standards and Spec stay separate
|
||||
|
||||
A change can pass one axis and fail the other:
|
||||
|
||||
- Code that follows every standard but implements the wrong thing → **Standards pass, Spec fail.**
|
||||
- Code that does exactly what the PRD or task asked but breaks the project's conventions → **Spec pass, Standards fail.**
|
||||
|
||||
Reporting them separately stops one axis from masking the other.
|
||||
@@ -1,53 +0,0 @@
|
||||
# Skills Lockfile
|
||||
|
||||
`.claude/skills-lock.yaml`, at the root of a project, tracks which library
|
||||
skills (from `~/.claude/skills/library/`) have been copied into that
|
||||
project's `.claude/skills/`, so [`setup-skills`](SKILL.md),
|
||||
[`update-skills`](../update-skills/SKILL.md), and
|
||||
[`remove-skills`](../remove-skills/SKILL.md) all agree on what's installed
|
||||
without re-deriving it from the filesystem.
|
||||
|
||||
## Schema
|
||||
|
||||
A YAML list of entries, one per installed skill:
|
||||
|
||||
```yaml
|
||||
- name: nbdev
|
||||
hash: 3f2a9b8c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a
|
||||
- name: terraform-conventions
|
||||
hash: 9c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a3f2a9b
|
||||
```
|
||||
|
||||
- `name` — matches both the skill's directory name in the library
|
||||
(`skills/library/<name>`) and its copied directory name in the project
|
||||
(`.claude/skills/<name>`).
|
||||
- `hash` — the output of `hash-dir.sh` run against that one skill's
|
||||
directory contents, recorded at the moment it was last copied or
|
||||
confirmed up to date. Never a hash of anything else — not the whole
|
||||
project, not the whole library, just that one skill's own directory
|
||||
tree.
|
||||
|
||||
## What a mismatch means
|
||||
|
||||
To classify a skill's state, compare three values: the lockfile's stored
|
||||
`hash`, `hash-dir.sh` on the project's current copy
|
||||
(`.claude/skills/<name>`), and `hash-dir.sh` on the library's current
|
||||
source (`~/.claude/skills/library/<name>`).
|
||||
|
||||
| stored vs. project copy | stored vs. library source | meaning |
|
||||
|--------------------------|----------------------------|--------------------------------------|
|
||||
| match | match | nothing to do |
|
||||
| match | differs | library moved on — safe to update |
|
||||
| differs | match | project customized on purpose — leave it |
|
||||
| differs | differs | conflict — report, don't touch |
|
||||
|
||||
## Writing to the lockfile
|
||||
|
||||
- Adding a skill: append a new `{name, hash}` entry.
|
||||
- Applying a safe update: overwrite that entry's `hash` in place with the
|
||||
library's current hash.
|
||||
- Removing a skill: delete its entry entirely.
|
||||
|
||||
Never reorder or restructure existing entries beyond what an add, update,
|
||||
or remove requires — this file is meant to diff cleanly in a project's
|
||||
git history.
|
||||
@@ -1,53 +0,0 @@
|
||||
---
|
||||
name: setup-skills
|
||||
description: Add relevant skills from the shared skills library to the current project.
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
Adds opt-in, project-specific skills from `~/.claude/skills/library/` into
|
||||
the current project's `.claude/skills/`, tracked in
|
||||
`.claude/skills-lock.yaml` (see [LOCKFILE.md](LOCKFILE.md) for its schema).
|
||||
Only ever adds — checking already-installed skills for updates is
|
||||
[`update-skills`](../update-skills/SKILL.md)'s job, not this one's.
|
||||
|
||||
The library is a tree of read-only symlinks into the Nix store, so every
|
||||
copy out of it must dereference (`cp -rL`) and then restore write
|
||||
permission (`chmod -R u+w`). A plain `cp -r` copies the symlinks
|
||||
themselves, putting store paths into the project that break on any other
|
||||
machine.
|
||||
|
||||
## Steps
|
||||
|
||||
1. Read `.claude/skills-lock.yaml` in the current project, if it exists.
|
||||
Note every skill name already listed — these are already installed and
|
||||
must not be re-proposed.
|
||||
|
||||
2. List every skill under `~/.claude/skills/library/*/SKILL.md` and read
|
||||
each one's `name` and `description`.
|
||||
|
||||
3. Inspect the current project (file tree, manifests like
|
||||
`pyproject.toml`/`package.json`, file extensions present, etc.) and
|
||||
judge which library skills — excluding ones already installed — seem
|
||||
relevant, the same way you'd reason about any unfamiliar codebase.
|
||||
Propose that shortlist to the user with your reasoning, one line per
|
||||
skill. If the user asks to see the full catalog instead, list every
|
||||
library skill (minus already-installed ones) with its description.
|
||||
|
||||
4. Let the user confirm, adjust, or pick freely from the full list.
|
||||
|
||||
5. For each confirmed skill:
|
||||
- If `.claude/skills/<name>/` already exists in the project and is
|
||||
*not* in the lockfile, skip it and tell the user why (a same-named
|
||||
skill already lives there and isn't tracked — remove or rename it
|
||||
first if they want the library version).
|
||||
- Otherwise, copy `~/.claude/skills/library/<name>/` to
|
||||
`.claude/skills/<name>/` in the project with
|
||||
`cp -rL` followed by `chmod -R u+w`, run
|
||||
`~/.claude/skills/setup-skills/hash-dir.sh .claude/skills/<name>`,
|
||||
and append `{name, hash: <output>}` to `.claude/skills-lock.yaml`
|
||||
(create the file, an empty YAML list, if it doesn't exist yet).
|
||||
|
||||
6. Report what was added and what was skipped, and why.
|
||||
|
||||
Done when every confirmed skill is either copied and recorded in the
|
||||
lockfile, or explicitly skipped with a stated reason.
|
||||
@@ -1,23 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Deterministic recursive hash of a directory's file contents.
|
||||
#
|
||||
# Hashes relative paths, not absolute ones, so two directories with
|
||||
# identical contents hash identically regardless of where they live on
|
||||
# disk (needed to compare a project's copied skill against the library
|
||||
# source it was copied from).
|
||||
#
|
||||
# Usage: hash-dir.sh <directory>
|
||||
set -euo pipefail
|
||||
|
||||
if [ $# -ne 1 ]; then
|
||||
echo "Usage: hash-dir.sh <directory>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
dir="$1"
|
||||
if [ ! -d "$dir" ]; then
|
||||
echo "Not a directory: $dir" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
(cd "$dir" && find . -type f -print0 | sort -z | xargs -0 -r sha256sum) | sha256sum | awk '{print $1}'
|
||||
@@ -1,153 +0,0 @@
|
||||
---
|
||||
name: test-driven-development
|
||||
description: Test-driven development. Use when the user wants to build features or fix bugs test-first, mentions "red-green-refactor", or wants integration tests.
|
||||
---
|
||||
|
||||
# Test-Driven Development
|
||||
|
||||
## Philosophy
|
||||
|
||||
**Core principle**: Tests should verify behavior through public interfaces, not implementation details. Code can change entirely; tests shouldn't.
|
||||
|
||||
**Good tests** are integration-style: they exercise real code paths through public APIs. They describe _what_ the system does, not _how_ it does it. A good test reads like a specification - "user can checkout with valid cart" tells you exactly what capability exists. These tests survive refactors because they don't care about internal structure.
|
||||
|
||||
**Bad tests** are coupled to implementation. They mock internal collaborators, test private methods, or verify through external means (like querying a database directly instead of using the interface). The warning sign: your test breaks when you refactor, but behavior hasn't changed. If you rename an internal function and tests fail, those tests were testing implementation, not behavior.
|
||||
|
||||
**Tautological tests** restate the implementation inside the assertion, so they pass by construction and give zero confidence. When the expected value is computed the way the code computes it — `expect(add(a, b)).toBe(a + b)`, snapshotting a figure you derived by hand the same way the code does, asserting a constant equals itself — the test can never disagree with the code: break the code wrong and the assertion breaks wrong with it. The expected value must come from an independent source of truth — a known-good literal, a worked example, the spec.
|
||||
|
||||
See [tests.md](tests.md) for examples and [mocking.md](mocking.md) for mocking guidelines.
|
||||
|
||||
## Anti-Pattern: Horizontal Slices
|
||||
|
||||
**DO NOT write all tests first, then all implementation.** This is "horizontal slicing" - treating RED as "write all tests" and GREEN as "write all code."
|
||||
|
||||
This produces **crap tests**:
|
||||
|
||||
- Tests written in bulk test _imagined_ behavior, not _actual_ behavior
|
||||
- You end up testing the _shape_ of things (data structures, function signatures) rather than user-facing behavior
|
||||
- Tests become insensitive to real changes - they pass when behavior breaks, fail when behavior is fine
|
||||
- You outrun your headlights, committing to test structure before understanding the implementation
|
||||
|
||||
**Correct approach**: Vertical slices via tracer bullets. One test → one implementation → repeat. Each test responds to what you learned from the previous cycle.
|
||||
|
||||
The test-writer sub-agent (below) is handed **one behavior at a time** and never sees the behavior backlog, so it can't bulk-write the suite.
|
||||
|
||||
```
|
||||
WRONG (horizontal):
|
||||
RED: test1, test2, test3, test4, test5
|
||||
GREEN: impl1, impl2, impl3, impl4, impl5
|
||||
|
||||
RIGHT (vertical):
|
||||
RED→GREEN: test1→impl1
|
||||
RED→GREEN: test2→impl2
|
||||
RED→GREEN: test3→impl3
|
||||
...
|
||||
```
|
||||
|
||||
## Roles
|
||||
|
||||
Every test is written by a **test-writer sub-agent**. The main agent writes every line of implementation, and never writes or edits a test.
|
||||
|
||||
The sub-agent must not read the implementation source of the module under test — that is what keeps its tests from asserting _how_ instead of _what_. It works from the public interface alone.
|
||||
|
||||
Use one `general-purpose` sub-agent for the whole task: spawn it at the first RED, then continue it with `SendMessage` for each subsequent RED, so it keeps the test file and conventions it established. Cold-spawn a replacement only if its ID is lost.
|
||||
|
||||
### Test-writer sub-agent prompt — include:
|
||||
|
||||
- **One behavior**, quoted verbatim from the acceptance criterion or the agreed behavior list. Never the task file, never the rest of the list.
|
||||
- The **public interface** under test — signatures only.
|
||||
- The existing test file(s) for the module, and the project's test conventions (fixtures, helpers, runner invocation).
|
||||
- [tests.md](tests.md) and [mocking.md](mocking.md).
|
||||
- The **independent source of truth for the expected value** — the spec excerpt, worked example, or known-good literal. Without it the sub-agent recomputes the expected value the way the code would, and the test is tautological.
|
||||
- `.claude/CONTEXT.md` (if it exists) and any ADRs in the area, so test names and interface vocabulary match the project's domain language.
|
||||
- The test-side checklist from [Checklist Per Cycle](#checklist-per-cycle), pasted in full — the sub-agent has no other access to it.
|
||||
- The brief: "Write ONE test for this behavior. Do not read the implementation source of the module under test. Write it to the test file, run it, and confirm it fails with a genuine assertion failure — not an import, syntax, or collection error, which prove nothing. Report the test's name and the exact failure message you saw."
|
||||
|
||||
## Workflow
|
||||
|
||||
### 1. Planning
|
||||
|
||||
When exploring the codebase, read `.claude/CONTEXT.md` (if it exists) so that test names and interface vocabulary match the project's domain language, and respect ADRs in the area you're touching.
|
||||
|
||||
Identify opportunities for deep modules (small interface, deep implementation) — run the `/codebase-design` skill for the vocabulary and the testability checks. Do this regardless of what triggered this workflow.
|
||||
|
||||
**If a task file is already in context** (e.g. passed to `/implement`, which called this skill), its acceptance criteria are the behavior list to test — the interface and priorities were already agreed during `/to-spec` and `/to-tasks`. Don't re-confirm them with the user; go straight to the tracer bullet.
|
||||
|
||||
**Otherwise**, before writing any code:
|
||||
|
||||
- [ ] Confirm with user what interface changes are needed
|
||||
- [ ] Confirm with user which behaviors to test (prioritize)
|
||||
- [ ] List the behaviors to test (not implementation steps)
|
||||
- [ ] Get user approval on the plan
|
||||
|
||||
Ask: "What should the public interface look like? Which behaviors are most important to test?"
|
||||
|
||||
**You can't test everything.** Confirm with the user exactly which behaviors matter most. Focus testing effort on critical paths and complex logic, not every possible edge case.
|
||||
|
||||
Planning stays with the main agent on both paths — exploration, interface, and the order behaviors are tested in. The sub-agent receives behaviors one at a time; it never chooses what to test next.
|
||||
|
||||
### 2. Tracer Bullet
|
||||
|
||||
ONE test that confirms ONE thing about the system:
|
||||
|
||||
```
|
||||
RED: Spawn the test-writer sub-agent with the first behavior → it writes the test, runs it, reports a genuine failure
|
||||
GREEN: Main agent writes minimal code to pass → test passes
|
||||
```
|
||||
|
||||
This is your tracer bullet - proves the path works end-to-end.
|
||||
|
||||
### 3. Incremental Loop
|
||||
|
||||
For each remaining behavior:
|
||||
|
||||
```
|
||||
RED: SendMessage the same sub-agent the next behavior → it writes the test, runs it, reports a genuine failure
|
||||
GREEN: Main agent writes minimal code to pass → passes
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- One test at a time
|
||||
- Only enough code to pass current test
|
||||
- Don't anticipate future tests
|
||||
- Keep tests focused on observable behavior
|
||||
|
||||
### When a test looks wrong
|
||||
|
||||
The main agent never edits a sub-agent-authored test — not to fix an import, not to "simplify" an assertion, not to reach GREEN.
|
||||
|
||||
- **Mechanical defect** — bad import path, a fixture or helper that doesn't exist, doesn't parse. Send the error output back to the sub-agent and let it fix its own test.
|
||||
- **Semantic disagreement** — you believe the expected value or the asserted behavior is wrong. Stop and ask the user. Do not resolve it yourself; this disagreement is the signal the sub-agent exists to surface, and half the time it's the code that's wrong.
|
||||
|
||||
### 4. Refactor
|
||||
|
||||
After all tests pass, look for [refactor candidates](refactoring.md):
|
||||
|
||||
- [ ] Extract duplication
|
||||
- [ ] Deepen modules (move complexity behind simple interfaces)
|
||||
- [ ] Apply SOLID principles where natural
|
||||
- [ ] Consider what new code reveals about existing code
|
||||
- [ ] Run tests after each refactor step
|
||||
|
||||
**Never refactor while RED.** Get to GREEN first.
|
||||
|
||||
A test that breaks during refactor means the refactor broke behavior — fix the code. The one exception is a public interface change you made deliberately (a module deepened, a signature moved, as agreed in the plan): send the interface change to the sub-agent and let it update its own tests. There is no case where the main agent edits the test itself.
|
||||
|
||||
## Checklist Per Cycle
|
||||
|
||||
Test-writer sub-agent, per test — paste into its prompt:
|
||||
|
||||
```
|
||||
[ ] Test describes behavior, not implementation
|
||||
[ ] Test uses public interface only
|
||||
[ ] Test would survive internal refactor
|
||||
[ ] Expected values are independent literals, not recomputed from the code
|
||||
```
|
||||
|
||||
Main agent, per GREEN:
|
||||
|
||||
```
|
||||
[ ] Code is minimal for this test
|
||||
[ ] No speculative features added
|
||||
```
|
||||
@@ -1,59 +0,0 @@
|
||||
# When to Mock
|
||||
|
||||
Mock at **system boundaries** only:
|
||||
|
||||
- External APIs (payment, email, etc.)
|
||||
- Databases (sometimes - prefer test DB)
|
||||
- Time/randomness
|
||||
- File system (sometimes)
|
||||
|
||||
Don't mock:
|
||||
|
||||
- Your own classes/modules
|
||||
- Internal collaborators
|
||||
- Anything you control
|
||||
|
||||
## Designing for Mockability
|
||||
|
||||
At system boundaries, design interfaces that are easy to mock:
|
||||
|
||||
**1. Use dependency injection**
|
||||
|
||||
Pass external dependencies in rather than creating them internally:
|
||||
|
||||
```typescript
|
||||
// Easy to mock
|
||||
function processPayment(order, paymentClient) {
|
||||
return paymentClient.charge(order.total);
|
||||
}
|
||||
|
||||
// Hard to mock
|
||||
function processPayment(order) {
|
||||
const client = new StripeClient(process.env.STRIPE_KEY);
|
||||
return client.charge(order.total);
|
||||
}
|
||||
```
|
||||
|
||||
**2. Prefer SDK-style interfaces over generic fetchers**
|
||||
|
||||
Create specific functions for each external operation instead of one generic function with conditional logic:
|
||||
|
||||
```typescript
|
||||
// GOOD: Each function is independently mockable
|
||||
const api = {
|
||||
getUser: (id) => fetch(`/users/${id}`),
|
||||
getOrders: (userId) => fetch(`/users/${userId}/orders`),
|
||||
createOrder: (data) => fetch('/orders', { method: 'POST', body: data }),
|
||||
};
|
||||
|
||||
// BAD: Mocking requires conditional logic inside the mock
|
||||
const api = {
|
||||
fetch: (endpoint, options) => fetch(endpoint, options),
|
||||
};
|
||||
```
|
||||
|
||||
The SDK approach means:
|
||||
- Each mock returns one specific shape
|
||||
- No conditional logic in test setup
|
||||
- Easier to see which endpoints a test exercises
|
||||
- Type safety per endpoint
|
||||
@@ -1,10 +0,0 @@
|
||||
# Refactor Candidates
|
||||
|
||||
After TDD cycle, look for:
|
||||
|
||||
- **Duplication** → Extract function/class
|
||||
- **Long methods** → Break into private helpers (keep tests on public interface)
|
||||
- **Shallow modules** → Combine or deepen — see DEEPENING.md in `/codebase-design` for dependency categories and seam discipline
|
||||
- **Feature envy** → Move logic to where data lives
|
||||
- **Primitive obsession** → Introduce value objects
|
||||
- **Existing code** the new code reveals as problematic
|
||||
@@ -1,77 +0,0 @@
|
||||
# Good and Bad Tests
|
||||
|
||||
## Good Tests
|
||||
|
||||
**Integration-style**: Test through real interfaces, not mocks of internal parts.
|
||||
|
||||
```typescript
|
||||
// GOOD: Tests observable behavior
|
||||
test("user can checkout with valid cart", async () => {
|
||||
const cart = createCart();
|
||||
cart.add(product);
|
||||
const result = await checkout(cart, paymentMethod);
|
||||
expect(result.status).toBe("confirmed");
|
||||
});
|
||||
```
|
||||
|
||||
Characteristics:
|
||||
|
||||
- Tests behavior users/callers care about
|
||||
- Uses public API only
|
||||
- Survives internal refactors
|
||||
- Describes WHAT, not HOW
|
||||
- One logical assertion per test
|
||||
|
||||
## Bad Tests
|
||||
|
||||
**Implementation-detail tests**: Coupled to internal structure.
|
||||
|
||||
```typescript
|
||||
// BAD: Tests implementation details
|
||||
test("checkout calls paymentService.process", async () => {
|
||||
const mockPayment = jest.mock(paymentService);
|
||||
await checkout(cart, payment);
|
||||
expect(mockPayment.process).toHaveBeenCalledWith(cart.total);
|
||||
});
|
||||
```
|
||||
|
||||
Red flags:
|
||||
|
||||
- Mocking internal collaborators
|
||||
- Testing private methods
|
||||
- Asserting on call counts/order
|
||||
- Test breaks when refactoring without behavior change
|
||||
- Test name describes HOW not WHAT
|
||||
- Verifying through external means instead of interface
|
||||
|
||||
```typescript
|
||||
// BAD: Bypasses interface to verify
|
||||
test("createUser saves to database", async () => {
|
||||
await createUser({ name: "Alice" });
|
||||
const row = await db.query("SELECT * FROM users WHERE name = ?", ["Alice"]);
|
||||
expect(row).toBeDefined();
|
||||
});
|
||||
|
||||
// GOOD: Verifies through interface
|
||||
test("createUser makes user retrievable", async () => {
|
||||
const user = await createUser({ name: "Alice" });
|
||||
const retrieved = await getUser(user.id);
|
||||
expect(retrieved.name).toBe("Alice");
|
||||
});
|
||||
```
|
||||
|
||||
**Tautological tests**: Expected value restates the implementation, so the test passes by construction.
|
||||
|
||||
```typescript
|
||||
// BAD: Expected value is recomputed the way the code computes it
|
||||
test("calculateTotal sums line items", () => {
|
||||
const items = [{ price: 10 }, { price: 5 }];
|
||||
const expected = items.reduce((sum, i) => sum + i.price, 0);
|
||||
expect(calculateTotal(items)).toBe(expected);
|
||||
});
|
||||
|
||||
// GOOD: Expected value is an independent, known literal
|
||||
test("calculateTotal sums line items", () => {
|
||||
expect(calculateTotal([{ price: 10 }, { price: 5 }])).toBe(15);
|
||||
});
|
||||
```
|
||||
@@ -1,21 +0,0 @@
|
||||
---
|
||||
name: to-spec
|
||||
description: Turn the current conversation into a spec and write it to .claude/spec/ — no interview, just synthesis of what you've already discussed.
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
This skill takes the current conversation context and codebase understanding and produces a spec. Do NOT interview the user — just synthesize what you already know.
|
||||
|
||||
If the conversation doesn't actually contain a feature or problem to synthesize a spec from, say so and ask what it's for instead of fabricating one.
|
||||
|
||||
## Process
|
||||
|
||||
1. Explore the repo until you can name the existing modules, flows, and seams the feature will touch, if you haven't already. Use the project's domain glossary vocabulary throughout the spec, and respect any ADRs in the area you're touching.
|
||||
|
||||
2. Sketch out the seams at which you're going to test the feature. Existing seams should be preferred to new ones. Use the highest seam possible. If new seams are needed, propose them at the highest point you can. The fewer seams across the codebase, the better - the ideal number is one.
|
||||
|
||||
Check with the user that these seams match their expectations.
|
||||
|
||||
3. Derive a short kebab-case feature-slug from the feature's name (e.g. `checkout-flow`). Tell the user the path you're about to write to (`.claude/spec/<feature-slug>.md`). If a file already exists there, summarize what would change and confirm with the user before overwriting it — never overwrite silently.
|
||||
|
||||
4. Write the spec using the format in [SPEC-FORMAT.md](./SPEC-FORMAT.md) to `.claude/spec/<feature-slug>.md`, creating the `.claude/spec/` directory if it doesn't exist yet.
|
||||
@@ -1,57 +0,0 @@
|
||||
---
|
||||
name: to-tasks
|
||||
description: Break a plan or spec into independently-grabbable task files under .claude/tasks/ using tracer-bullet vertical slices.
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
# To Tasks
|
||||
|
||||
## Process
|
||||
|
||||
### 1. Gather context
|
||||
|
||||
Work from whatever is already in the conversation context. If the user passes a spec path or other reference as an argument, read it directly.
|
||||
|
||||
Determine the feature-slug this breakdown belongs to, if any: if a spec file is in context or was passed as an argument, derive it from the filename (`.claude/spec/<feature-slug>.md` → `<feature-slug>`) for each task's `spec` field — see [TASK-FORMAT.md](./TASK-FORMAT.md) for the field's rules. If no spec file exists, proceed without one.
|
||||
|
||||
### 2. Explore the codebase (optional)
|
||||
|
||||
If you have not already explored the codebase, do so to understand the current state of the code. Task titles and descriptions should use the project's domain glossary vocabulary, and respect ADRs in the area you're touching.
|
||||
|
||||
Look for opportunities to prefactor the code to make the implementation easier. "Make the change easy, then make the easy change."
|
||||
|
||||
### 3. Draft vertical slices
|
||||
|
||||
Break the plan into **tracer bullet** tasks — vertical slices, not horizontal layers.
|
||||
|
||||
<vertical-slice-rules>
|
||||
|
||||
- Each slice delivers a narrow but COMPLETE path through every layer the change requires (schema, API, UI, tests), never a horizontal slice of just one
|
||||
- A completed slice is demoable or verifiable on its own
|
||||
- Any prefactoring should be done first
|
||||
|
||||
</vertical-slice-rules>
|
||||
|
||||
### 4. Quiz the user
|
||||
|
||||
Number slices with a single sequence shared across every file already in `.claude/tasks/`: scan for the highest existing `NNNN` (four-digit, zero-padded decimal, `0000`-`9999`) and increment from there. Never restart the sequence per feature and never reuse a number.
|
||||
|
||||
Present the proposed breakdown as a numbered list. For each slice, show:
|
||||
|
||||
- **File**: the `NNNN-slice-slug` it will be written as, per the numbering above
|
||||
- **Blocked by**: which other slices (if any) must complete first — "None" if it can start immediately
|
||||
- **User stories covered**: which user stories this addresses (if the source material has them)
|
||||
|
||||
Ask the user:
|
||||
|
||||
- Does the granularity feel right? (too coarse / too fine)
|
||||
- Are the dependency relationships correct?
|
||||
- Should any slices be merged or split further?
|
||||
|
||||
Iterate until the user approves the breakdown, including the proposed numbers and slugs.
|
||||
|
||||
### 5. Write the task files
|
||||
|
||||
For each approved slice, write a file to `.claude/tasks/<NNNN>-<slice-slug>.md` (create the directory if it doesn't exist) using the numbers and slugs approved in step 4. Use the template in [TASK-FORMAT.md](./TASK-FORMAT.md).
|
||||
|
||||
Do NOT modify the parent spec file (`.claude/spec/<feature-slug>.md`) when writing tasks.
|
||||
@@ -1,28 +0,0 @@
|
||||
# Task Format
|
||||
|
||||
## Template
|
||||
|
||||
```md
|
||||
---
|
||||
spec: <feature-slug>
|
||||
blocked-by: <slice-slug-or-list>
|
||||
---
|
||||
|
||||
## What to build
|
||||
|
||||
A concise description of this vertical slice. Describe the end-to-end behavior, not layer-by-layer implementation.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] Criterion 1
|
||||
- [ ] Criterion 2
|
||||
- [ ] Criterion 3
|
||||
```
|
||||
|
||||
## Rules
|
||||
|
||||
- **`spec`**: the feature-slug this task was written from. Omit the field entirely if there's no spec.
|
||||
- **`blocked-by`**: which other task(s) must complete before this one can start. Omit the field entirely if there are none. Each value is the blocking task's full `<NNNN>-<slice-slug>` filename stem, not just its slug. A single blocker is a bare string (`blocked-by: 0010-add-schema`); more than one is a YAML list (`blocked-by: [0010-add-schema, 0011-wire-api]`). Once written, keep the field even after the referenced task is completed — it's a permanent record of the dependency, not a "still blocked" flag.
|
||||
- **Don't include specific file paths or code snippets** in "What to build" — they go stale fast. Exception: if a prototype produced a snippet that encodes a decision more precisely than prose can (state machine, reducer, schema, type shape), inline it here and note briefly that it came from a prototype. Trim to the decision-rich parts — not a working demo, just the important bits.
|
||||
- A task is done when every criterion in "Acceptance criteria" is resolved: mark `[x]` as satisfied, or `[-]` if deliberately dropped (`/implement` records the reason in the task's Implementation Notes) — track completion here, not anywhere else.
|
||||
- A slice becomes pickable once every task named in `blocked-by` is done (all of its acceptance criteria resolved) — check the referenced tasks' state, not just whether the field is present. The file's number is an identifier and a rough ordering hint, not a strict gate — sibling slices with no blockers can be worked in parallel.
|
||||
@@ -1,55 +0,0 @@
|
||||
---
|
||||
name: update-skills
|
||||
description: Check the current project's installed library skills for upstream changes and apply the safe ones.
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
Compares every skill listed in the current project's
|
||||
`.claude/skills-lock.yaml` (see [LOCKFILE.md](../setup-skills/LOCKFILE.md)
|
||||
for its schema) against both the project's own copy and the current
|
||||
library source, and decides what to do about each one. Never installs a
|
||||
skill that isn't already there — that's
|
||||
[`setup-skills`](../setup-skills/SKILL.md)'s job.
|
||||
|
||||
## Steps
|
||||
|
||||
1. Read `.claude/skills-lock.yaml`. If it doesn't exist or is empty, tell
|
||||
the user there's nothing to check and stop.
|
||||
|
||||
2. For each `{name, hash}` entry, compute:
|
||||
- `project_hash`: `~/.claude/skills/setup-skills/hash-dir.sh .claude/skills/<name>`
|
||||
- `library_hash`: `~/.claude/skills/setup-skills/hash-dir.sh ~/.claude/skills/library/<name>`
|
||||
|
||||
If either path is missing entirely, report that anomaly for this skill
|
||||
(don't try to classify it) and move on to the next entry.
|
||||
|
||||
3. Classify each entry against the table in
|
||||
[LOCKFILE.md](../setup-skills/LOCKFILE.md#what-a-mismatch-means),
|
||||
using `project_hash` in place of "project copy" and `library_hash` in
|
||||
place of "library source". The two outcomes that need action below are
|
||||
**safe update** (stored matches project, differs from library) and
|
||||
**conflict** (stored differs from both). "Locally customized" needs no
|
||||
message beyond the summary.
|
||||
|
||||
4. If there are any safe updates, list them by name and ask for one
|
||||
confirmation to apply all of them — unless the user's invocation
|
||||
already included an explicit go-ahead argument (e.g. `-y`, `yes`), in
|
||||
which case apply them without asking. Applying means: delete
|
||||
`.claude/skills/<name>/` entirely and copy
|
||||
`~/.claude/skills/library/<name>/` in its place with `cp -rL` followed
|
||||
by `chmod -R u+w` (the library is read-only symlinks into the Nix store;
|
||||
a plain `cp -r` would put store paths into the project), so no file the
|
||||
project copy had but the library no longer has can survive — then
|
||||
recompute its
|
||||
hash and overwrite that entry's `hash` in `.claude/skills-lock.yaml` in
|
||||
place.
|
||||
|
||||
5. For every conflict, report it and show a recursive diff between the
|
||||
project's copy and the library's current version
|
||||
(`diff -ru .claude/skills/<name> ~/.claude/skills/library/<name>`).
|
||||
Do not modify the project's copy or the lockfile entry for a
|
||||
conflicted skill under any circumstances — surfacing it is the whole
|
||||
job here.
|
||||
|
||||
6. Finish with a summary: updated, left alone (customized), conflicted,
|
||||
already current, and any anomalies from step 2.
|
||||
@@ -1,85 +0,0 @@
|
||||
# Alexion's Agent Instructions
|
||||
|
||||
These are common instructions for Alexion's agents across all scenarios.
|
||||
|
||||
## General Guidelines
|
||||
|
||||
- When writing commit messages, NEVER auto-add your agent name as co-author.
|
||||
Omit the `Co-Authored-By:` trailer entirely, with no exceptions.
|
||||
This overrides any default instruction to append one.
|
||||
- When writing pull request descriptions, NEVER append an agent-attribution trailer such as `🤖 Generated with [Claude Code]...`.
|
||||
Leave it out entirely, with no exceptions.
|
||||
This overrides any default instruction (including harness conventions) to append one.
|
||||
- NEVER ask the user a question using the `AskUserQuestion` tool.
|
||||
Ask in plain prose, in your own message, instead, with no exceptions.
|
||||
This overrides any default instruction (including harness conventions and skill instructions) to use it.
|
||||
- Never manually modify `CHANGELOG.md` files or files marked as auto-generated.
|
||||
Detect auto-generated files with a layered check.
|
||||
Trust an explicit in-file marker first, for example `AUTO-GENERATED, DO NOT EDIT`.
|
||||
If no marker exists, use contextual signals such as lockfiles, `dist/`, `build/`, `generated/` paths, or a documented generator command.
|
||||
If the answer stays ambiguous, ask before editing.
|
||||
- In authored prose, do not put multiple sentences on one physical line.
|
||||
This applies to comments, documentation, user-facing descriptions, commit prose, pull-request prose, and forge comments.
|
||||
A sentence can wrap across multiple physical lines when needed.
|
||||
- Keep Markdown formatting changes local.
|
||||
Only format prose you write or change.
|
||||
Never reflow an entire pre-existing paragraph or file because you touched nearby text.
|
||||
- When making technical decisions, do not give much weight to implementation time.
|
||||
Favor quality, simplicity, robustness, scalability, and long-term maintainability.
|
||||
Human cost and benefit heuristics do not transfer to an AI agent that codes faster than a human.
|
||||
Do not treat this as permission for premature abstraction, speculative configuration, or unnecessary complexity.
|
||||
Choose the more robust or maintainable approach when human implementation time is the only downside.
|
||||
- Use lowercase file names unless a valid reason requires uppercase.
|
||||
Established ecosystem or tool conventions count as valid reasons.
|
||||
Examples include `README.md`, `LICENSE`, `CHANGELOG.md`, `Makefile`, `Dockerfile`, and `.github/` files.
|
||||
- Do not end a response by promising or implying continuation unless the continuation is present in that same response.
|
||||
Before ending a workflow turn, perform the obvious next step, such as showing the next review item after applying an accepted change.
|
||||
If the workflow is paused, say that plainly instead of using a dangling transition like "continuing" or "next".
|
||||
- When you discover that your belief about a project fact or convention was wrong, record the correction.
|
||||
Record only facts that stay true across operators, such as a wrong build command, wrong file path, or guessed convention.
|
||||
Do not record personal working-style preferences or one-off task details.
|
||||
Record the correction in the nearest project `AGENTS.md`, not this global file.
|
||||
Use a dedicated `## Gotchas` section, and create that section when the file lacks one.
|
||||
If no project `AGENTS.md` exists, ask before creating one.
|
||||
Append the correction immediately without asking when a project `AGENTS.md` exists.
|
||||
Briefly mention the edit in your response.
|
||||
If an existing entry is wrong or stale, correct or remove it the same way.
|
||||
|
||||
## Comments
|
||||
|
||||
- Write comments only where they earn their place.
|
||||
Keep comments concise.
|
||||
Assume the reader can read code.
|
||||
Explain why, not what.
|
||||
Explain what only when it is genuinely non-obvious.
|
||||
- Keep comments self-contained to their file.
|
||||
Write comments for a reader who sees only that file.
|
||||
Do not explain how another file or tool consumes a value.
|
||||
The only permitted cross-file mention is a bare pointer explaining why something is absent here.
|
||||
- Do not narrate history or future state in comments.
|
||||
Avoid phrases such as "used to be", "now moved here", and "will be".
|
||||
State the present reason a thing exists.
|
||||
- Do not justify a choice against alternatives in comments.
|
||||
State the positive reason the choice exists.
|
||||
Include present consequences when they matter.
|
||||
- Do not reference agent-facing state in comments.
|
||||
This includes `.agents/`, `.claude/`, `AGENTS.md`, and `CLAUDE.md`.
|
||||
- Do not use project-specific capitalized terms as glossary references in comments.
|
||||
Use plain lowercase nouns unless the code requires the exact term.
|
||||
- Add a file-top header only when the filename or path does not already explain the file.
|
||||
The header must be one concise purpose line.
|
||||
Do not write a feature inventory.
|
||||
- Write placeholders plainly.
|
||||
Include a present-tense action when one exists, such as `Placeholder: regenerate with <tool> on the target machine`.
|
||||
Do not write `placeholder for <missing feature>`.
|
||||
- Treat user-facing documentation strings as documentation, not comments.
|
||||
They can describe behavior more fully than code comments.
|
||||
They still must stay self-contained and avoid agent-state references.
|
||||
- Start each sentence of a comment on its own physical line.
|
||||
A comment sentence can wrap when needed.
|
||||
Prefer splitting long comment sentences before wrapping them.
|
||||
Do not break a line early at a comma or clause boundary to make it read as a unit.
|
||||
Do not use semicolons in comments or authored prose.
|
||||
Recast semicolon clauses as separate sentences.
|
||||
Only reformat comments you write or change.
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
# Shared global instructions for agent harnesses.
|
||||
let
|
||||
user = config.user.name;
|
||||
context = builtins.readFile ./AGENTS.md;
|
||||
in
|
||||
{
|
||||
config = lib.mkMerge [
|
||||
(lib.mkIf config.modules.agents.claude-code.enable {
|
||||
home-manager.users.${user}.programs.claude-code.context = context;
|
||||
})
|
||||
|
||||
(lib.mkIf config.modules.agents.pi.enable {
|
||||
home-manager.users.${user}.programs.pi-coding-agent.context = context;
|
||||
})
|
||||
];
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
diff --git a/packages/coding-agent/src/utils/tools-manager.ts b/packages/coding-agent/src/utils/tools-manager.ts
|
||||
--- a/packages/coding-agent/src/utils/tools-manager.ts 2026-08-01 18:41:36.970496010 -0400
|
||||
+++ b/packages/coding-agent/src/utils/tools-manager.ts 2026-08-01 18:41:37.028186009 -0400
|
||||
@@ -74,8 +74,8 @@
|
||||
function commandExists(cmd: string): boolean {
|
||||
try {
|
||||
const result = spawnSync(cmd, ["--version"], { stdio: "pipe" });
|
||||
- // Check for ENOENT error (command not found)
|
||||
- return result.error === undefined || result.error === null;
|
||||
+ // Downloaded generic Linux binaries can exist but fail on NixOS before they print a version.
|
||||
+ return (result.error === undefined || result.error === null) && result.status === 0;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
@@ -88,7 +87,7 @@
|
||||
|
||||
// Check our tools directory first
|
||||
const localPath = join(TOOLS_DIR, config.binaryName + (platform() === "win32" ? ".exe" : ""));
|
||||
- if (existsSync(localPath)) {
|
||||
+ if (existsSync(localPath) && commandExists(localPath)) {
|
||||
return localPath;
|
||||
}
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
# Pi, a terminal coding agent, for the primary user, configured through
|
||||
# home-manager, which ships the package and manages ~/.pi/agent.
|
||||
# The login credential is left unmanaged, so it survives rebuilds.
|
||||
let
|
||||
cfg = config.modules.agents.pi;
|
||||
user = config.user.name;
|
||||
piDir = "${config.users.users.${user}.home}/.pi/agent";
|
||||
patchedPi = pkgs.pi-coding-agent.overrideAttrs (old: {
|
||||
patches = (old.patches or [ ]) ++ [
|
||||
./patches/pi-flex-spacer.patch
|
||||
./patches/pi-tool-lookup-validation.patch
|
||||
];
|
||||
});
|
||||
herdrPiIntegration = pkgs.stdenvNoCC.mkDerivation {
|
||||
name = "herdr-pi-integration";
|
||||
nativeBuildInputs = [ pkgs.herdr ];
|
||||
phases = [ "installPhase" ];
|
||||
installPhase = ''
|
||||
mkdir -p $TMPDIR/home/.pi/agent/extensions
|
||||
HOME=$TMPDIR/home herdr integration install pi
|
||||
mkdir -p $out
|
||||
cp $TMPDIR/home/.pi/agent/extensions/herdr-agent-state.ts $out/herdr-agent-state.ts
|
||||
'';
|
||||
};
|
||||
piExtensions = pkgs.stdenvNoCC.mkDerivation {
|
||||
name = "pi-extensions";
|
||||
phases = [ "installPhase" ];
|
||||
installPhase = ''
|
||||
mkdir -p $out
|
||||
cp -R ${./extensions}/. $out/
|
||||
cp ${herdrPiIntegration}/herdr-agent-state.ts $out/herdr-agent-state.ts
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
options.modules.agents.pi = {
|
||||
enable = lib.mkEnableOption ''
|
||||
Pi, a terminal coding agent, configured via home-manager'';
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
home-manager.users.${user} = {
|
||||
programs.pi-coding-agent = {
|
||||
enable = true;
|
||||
package = patchedPi;
|
||||
|
||||
settings = {
|
||||
defaultProvider = "openai-codex";
|
||||
defaultModel = "gpt-5.5";
|
||||
defaultThinkingLevel = "medium";
|
||||
theme = "dark";
|
||||
enableInstallTelemetry = false;
|
||||
enableAnalytics = false;
|
||||
};
|
||||
};
|
||||
|
||||
home.file =
|
||||
{
|
||||
# The first declarative rollout replaces the interactive settings file.
|
||||
# Login state stays in auth.json, which this module does not manage.
|
||||
"${piDir}/settings.json".force = true;
|
||||
|
||||
"${piDir}/extensions" = {
|
||||
source = piExtensions;
|
||||
recursive = true;
|
||||
};
|
||||
|
||||
"${piDir}/prompts" = {
|
||||
source = ./prompts;
|
||||
recursive = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
config,
|
||||
inputs,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
# Global agent skills, placed under the skills directory so they are active in
|
||||
# every project.
|
||||
let
|
||||
user = config.user.name;
|
||||
|
||||
# The skills installed globally, as derivations from the skills flake.
|
||||
skills = with inputs.skills.packages.${pkgs.stdenv.hostPlatform.system}; [ ];
|
||||
in
|
||||
{
|
||||
home-manager.sharedModules = [ inputs.skills.homeModules.default ];
|
||||
home-manager.users.${user}.programs.agents.skills = skills;
|
||||
}
|
||||
@@ -1,56 +1,35 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
inputs,
|
||||
...
|
||||
}:
|
||||
# The shared foundation for every host and guest.
|
||||
let
|
||||
inherit (lib) mkOption types;
|
||||
user = config.user;
|
||||
|
||||
pinArgs = prev: {
|
||||
inherit (prev.stdenv.hostPlatform) system;
|
||||
config.allowUnfree = true;
|
||||
};
|
||||
in
|
||||
{
|
||||
imports = [ inputs.home-manager.nixosModules.home-manager ];
|
||||
|
||||
options.user = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "alexion";
|
||||
description = ''
|
||||
Primary interactive user for this system.
|
||||
This drives the system account and Home Manager user together.
|
||||
Names the primary user account.
|
||||
Host configuration uses this name for the Home Manager user.
|
||||
'';
|
||||
};
|
||||
description = mkOption {
|
||||
type = types.str;
|
||||
default = "Alexion";
|
||||
description = "Human-readable description for the primary user.";
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
nixpkgs.overlays = [
|
||||
(_final: prev: {
|
||||
unstable = import inputs.nixpkgs-unstable (pinArgs prev);
|
||||
stable = import inputs.nixpkgs-stable (pinArgs prev);
|
||||
})
|
||||
];
|
||||
nixpkgs.config.allowUnfree = true;
|
||||
system.stateVersion = "26.05";
|
||||
|
||||
nixpkgs.config.allowUnfree = true;
|
||||
nix.settings.experimental-features = [
|
||||
"nix-command"
|
||||
"flakes"
|
||||
];
|
||||
|
||||
users.users.${user.name} = {
|
||||
users.users.${config.user.name} = {
|
||||
isNormalUser = true;
|
||||
description = user.description;
|
||||
extraGroups = [
|
||||
"wheel"
|
||||
"storage"
|
||||
@@ -59,67 +38,7 @@ in
|
||||
|
||||
users.groups.storage.gid = 10000;
|
||||
|
||||
home-manager = {
|
||||
useGlobalPkgs = true;
|
||||
useUserPackages = true;
|
||||
extraSpecialArgs = {
|
||||
inherit inputs;
|
||||
};
|
||||
users.${user.name} = {
|
||||
home.username = user.name;
|
||||
home.homeDirectory = "/home/${user.name}";
|
||||
home.stateVersion = "26.05";
|
||||
home.packages = with pkgs; [
|
||||
eza
|
||||
wget
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
registry.shell-shortcut.${config.networking.hostName} = {
|
||||
ls = {
|
||||
command = "eza -alg --color=always --group-directories-first --icons=always";
|
||||
abbreviation = false;
|
||||
};
|
||||
la = {
|
||||
command = "eza -a --color=always --group-directories-first --icons=always";
|
||||
abbreviation = false;
|
||||
};
|
||||
ll = {
|
||||
command = "eza -lg --color=always --group-directories-first --icons=always";
|
||||
abbreviation = false;
|
||||
};
|
||||
lt = {
|
||||
command = "eza -aT -I '.git' --color=always --group-directories-first --icons=always";
|
||||
abbreviation = false;
|
||||
};
|
||||
"l." = {
|
||||
command = "eza -a | grep -e '^\\.'";
|
||||
abbreviation = false;
|
||||
};
|
||||
|
||||
".." = "cd ..";
|
||||
"..." = "cd ../..";
|
||||
"...." = "cd ../../..";
|
||||
|
||||
cp = "cp -v";
|
||||
tarnow = "tar -acf ";
|
||||
untar = "tar -zxvf ";
|
||||
wget = "wget -c ";
|
||||
grep = "grep --color=auto";
|
||||
fgrep = "fgrep --color=auto";
|
||||
egrep = "egrep --color=auto";
|
||||
jctl = "journalctl -p 3 -xb";
|
||||
|
||||
update = "sudo nixos-rebuild switch --flake .#${config.networking.hostName}";
|
||||
cleanup = "sudo nix-collect-garbage -d";
|
||||
};
|
||||
|
||||
modules.shell.fish.enable = lib.mkDefault true;
|
||||
modules.shell.fish.defaultShell = lib.mkDefault true;
|
||||
modules.shell.tmux.enable = lib.mkDefault true;
|
||||
modules.shell.nvim.enable = lib.mkDefault true;
|
||||
modules.shell.git.enable = lib.mkDefault true;
|
||||
modules.shell.yazi.enable = lib.mkDefault true;
|
||||
time.timeZone = "America/New_York";
|
||||
i18n.defaultLocale = "en_GB.UTF-8";
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
# Alacritty as the desktop terminal.
|
||||
let
|
||||
cfg = config.modules.desktop.apps.alacritty;
|
||||
user = config.user.name;
|
||||
in
|
||||
{
|
||||
options.modules.desktop.apps.alacritty.enable = lib.mkEnableOption "Alacritty as the desktop terminal";
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
home-manager.users.${user}.home.packages = [ pkgs.alacritty ];
|
||||
};
|
||||
}
|
||||
@@ -3,23 +3,40 @@
|
||||
inputs,
|
||||
lib,
|
||||
pkgs,
|
||||
util,
|
||||
...
|
||||
}:
|
||||
# Firefox as the desktop browser: stock mainline, hardened and de-monetized by policy.
|
||||
# Configures Firefox as the desktop browser.
|
||||
let
|
||||
cfg = config.modules.desktop.apps.firefox;
|
||||
user = config.user.name;
|
||||
firefoxAddons = inputs.firefox-addons.packages.${pkgs.stdenv.hostPlatform.system};
|
||||
cfg = config.modules.desktop.apps.firefox; # NOLINT(let_order)
|
||||
in
|
||||
{
|
||||
options.modules.desktop.apps.firefox.enable = lib.mkEnableOption "Firefox as the desktop browser";
|
||||
options.modules.desktop.apps.firefox = {
|
||||
enable = lib.mkEnableOption "Firefox as the desktop browser";
|
||||
|
||||
uiScale = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "1.0";
|
||||
description = ''
|
||||
Scales Firefox UI and page content.
|
||||
|
||||
Sets `layout.css.devPixelsPerPx`.
|
||||
Use a larger value for high-DPI displays.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
home-manager.users.${user} = hm: {
|
||||
registry.window-icon.${config.networking.hostName}.firefox = {
|
||||
class = ".*[Ff]irefox.*";
|
||||
icon = util.glyph "f269";
|
||||
};
|
||||
|
||||
home-manager.users.${config.user.name} = hm: {
|
||||
programs.firefox = {
|
||||
enable = true;
|
||||
|
||||
# Enterprise policies: enforced and unchangeable from the browser UI.
|
||||
# Applies policies that users cannot change in Firefox.
|
||||
policies = {
|
||||
DisableTelemetry = true;
|
||||
DisableFirefoxStudies = true;
|
||||
@@ -28,7 +45,7 @@ in
|
||||
DontCheckDefaultBrowser = true;
|
||||
DisableFirefoxAccounts = true;
|
||||
|
||||
# Strip the monetized surfaces from the new-tab page.
|
||||
# Removes sponsored content from the new-tab page.
|
||||
FirefoxHome = {
|
||||
SponsoredTopSites = false;
|
||||
SponsoredPocket = false;
|
||||
@@ -37,23 +54,19 @@ in
|
||||
};
|
||||
|
||||
profiles.default = {
|
||||
isDefault = true;
|
||||
|
||||
extensions = {
|
||||
packages = with firefoxAddons; [
|
||||
packages = with inputs.firefox-addons.packages.${pkgs.stdenv.hostPlatform.system}; [
|
||||
ublock-origin
|
||||
proton-pass
|
||||
sponsorblock
|
||||
];
|
||||
|
||||
# The Nord chrome theme is a declared extension setting, so home-manager
|
||||
# owns the extension-settings store, overwriting runtime changes to it.
|
||||
# Home Manager overwrites Nord theme changes made in Firefox.
|
||||
force = true;
|
||||
};
|
||||
|
||||
# Stylix's Nord mapping paints the selected address-bar result a
|
||||
# near-white grey, leaving its light text unreadable. Darken that one
|
||||
# highlight to the Nord selection grey, from the same scheme.
|
||||
# Stylix uses a near-white selection background.
|
||||
# Use the Nord selection color because its text stays readable.
|
||||
extensions.settings."FirefoxColor@mozilla.com".settings.theme.colors.popup_highlight =
|
||||
let
|
||||
c = hm.config.lib.stylix.colors;
|
||||
@@ -65,32 +78,26 @@ in
|
||||
};
|
||||
|
||||
settings = {
|
||||
# Scale the UI and page by a fixed factor.
|
||||
# Left at auto (-1), Firefox reads the panel's 1.5x and inflates its
|
||||
# whole chrome while point-sized apps stay put.
|
||||
# A shade under that brings it into line without dropping to true
|
||||
# 1:1, which reads too small at this DPI.
|
||||
"layout.css.devPixelsPerPx" = "1.25";
|
||||
"layout.css.devPixelsPerPx" = cfg.uiScale;
|
||||
|
||||
# Auto-enable the sideloaded Firefox Color add-on carrying the Nord
|
||||
# chrome theme, which Firefox otherwise leaves disabled.
|
||||
# Enables Firefox Color after Firefox sideloads the add-on.
|
||||
"extensions.autoDisableScopes" = 0;
|
||||
|
||||
# Sponsored surfaces the policies above do not reach.
|
||||
# Removes sponsored content outside Firefox policies.
|
||||
"browser.urlbar.suggest.quicksuggest.sponsored" = false;
|
||||
"browser.newtabpage.activity-stream.showSponsored" = false;
|
||||
"browser.newtabpage.activity-stream.showSponsoredTopSites" = false;
|
||||
};
|
||||
|
||||
search = {
|
||||
# Declaring search overwrites Firefox's own engine list wholesale,
|
||||
# so engines added later in the UI do not survive a rebuild.
|
||||
# Home Manager replaces the engine list when this setting is enabled.
|
||||
# Engines added in Firefox do not survive a rebuild.
|
||||
force = true;
|
||||
default = "ddg";
|
||||
|
||||
# The general-purpose commercial engines, hidden to leave a lean
|
||||
# DuckDuckGo-and-Wikipedia list. They stay reachable through bangs.
|
||||
# An engine carrying only metaData is treated as a builtin.
|
||||
# Hides commercial search engines.
|
||||
# Bangs still reach these engines.
|
||||
# Entries with only `metaData` configure Firefox built-in engines.
|
||||
engines = {
|
||||
google.metaData.hidden = true;
|
||||
bing.metaData.hidden = true;
|
||||
@@ -102,8 +109,7 @@ in
|
||||
};
|
||||
};
|
||||
|
||||
# Nord chrome for the one profile, applied through the Stylix-managed
|
||||
# Firefox Color add-on that colorTheme enables.
|
||||
# Applies the Nord theme through Stylix's Firefox Color add-on.
|
||||
stylix.targets.firefox = {
|
||||
enable = true;
|
||||
profileNames = [ "default" ];
|
||||
@@ -121,8 +127,8 @@ in
|
||||
};
|
||||
};
|
||||
|
||||
# Firefox writes profiles.ini itself on first launch, so home-manager is
|
||||
# told to own the file rather than fail activation refusing to clobber it.
|
||||
# Firefox creates `profiles.ini` on its first launch.
|
||||
# Force replacement during Home Manager activation.
|
||||
home.file."${hm.config.programs.firefox.configPath}/profiles.ini".force = true;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -2,17 +2,30 @@
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
util,
|
||||
...
|
||||
}:
|
||||
# Ghostty as the desktop terminal.
|
||||
let
|
||||
cfg = config.modules.desktop.apps.ghostty;
|
||||
user = config.user.name;
|
||||
in
|
||||
{
|
||||
options.modules.desktop.apps.ghostty.enable = lib.mkEnableOption "Ghostty as the desktop terminal";
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
config = lib.mkIf config.modules.desktop.apps.ghostty.enable {
|
||||
registry.keybind.${config.networking.hostName}.ghostty = {
|
||||
keys = "SUPER + Return";
|
||||
action = {
|
||||
kind = "exec";
|
||||
command = lib.getExe pkgs.ghostty;
|
||||
};
|
||||
};
|
||||
|
||||
registry.window-icon.${config.networking.hostName}.ghostty = {
|
||||
class = "com.mitchellh.ghostty";
|
||||
icon = util.glyph "f120";
|
||||
};
|
||||
|
||||
home-manager.users.${user}.home.packages = [ pkgs.ghostty ];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,17 +2,23 @@
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
util,
|
||||
...
|
||||
}:
|
||||
# Obsidian as the desktop note-taking vault.
|
||||
let
|
||||
cfg = config.modules.desktop.apps.obsidian;
|
||||
user = config.user.name;
|
||||
in
|
||||
{
|
||||
options.modules.desktop.apps.obsidian.enable = lib.mkEnableOption "Obsidian as the desktop note-taking vault";
|
||||
options.modules.desktop.apps.obsidian.enable =
|
||||
lib.mkEnableOption "Obsidian as the desktop note-taking vault";
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
config = lib.mkIf config.modules.desktop.apps.obsidian.enable {
|
||||
home-manager.users.${user}.home.packages = [ pkgs.obsidian ];
|
||||
|
||||
registry.window-icon.${config.networking.hostName}.obsidian = {
|
||||
class = "obsidian";
|
||||
icon = util.glyph "f02d";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
18
configs/nixos/modules/desktop/apps/r2modman.nix
Normal file
18
configs/nixos/modules/desktop/apps/r2modman.nix
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
# r2modman, a Thunderstore mod manager.
|
||||
let
|
||||
user = config.user.name;
|
||||
in
|
||||
{
|
||||
options.modules.desktop.apps.r2modman.enable =
|
||||
lib.mkEnableOption "r2modman, a Thunderstore mod manager";
|
||||
|
||||
config = lib.mkIf config.modules.desktop.apps.r2modman.enable {
|
||||
home-manager.users.${user}.home.packages = [ pkgs.r2modman ];
|
||||
};
|
||||
}
|
||||
23
configs/nixos/modules/desktop/apps/signal.nix
Normal file
23
configs/nixos/modules/desktop/apps/signal.nix
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
util,
|
||||
...
|
||||
}:
|
||||
# Signal desktop messenger.
|
||||
let
|
||||
user = config.user.name;
|
||||
in
|
||||
{
|
||||
options.modules.desktop.apps.signal.enable = lib.mkEnableOption "Signal desktop messenger";
|
||||
|
||||
config = lib.mkIf config.modules.desktop.apps.signal.enable {
|
||||
home-manager.users.${user}.home.packages = [ pkgs.signal-desktop ];
|
||||
|
||||
registry.window-icon.${config.networking.hostName}.signal = {
|
||||
class = "[Ss]ignal";
|
||||
icon = util.glyph "f4ad";
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -2,16 +2,16 @@
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
util,
|
||||
...
|
||||
}:
|
||||
# Steam game launcher and runtime integration.
|
||||
let
|
||||
cfg = config.modules.desktop.apps.steam;
|
||||
in
|
||||
{
|
||||
options.modules.desktop.apps.steam.enable = lib.mkEnableOption "Steam game launcher";
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
config = lib.mkIf config.modules.desktop.apps.steam.enable {
|
||||
programs.gamemode.enable = true;
|
||||
|
||||
programs.steam = {
|
||||
enable = true;
|
||||
package = pkgs.steam.override {
|
||||
@@ -20,5 +20,14 @@ in
|
||||
};
|
||||
|
||||
hardware.steam-hardware.enable = true;
|
||||
|
||||
environment.systemPackages = [
|
||||
pkgs.gamescope
|
||||
];
|
||||
|
||||
registry.window-icon.${config.networking.hostName}.steam = {
|
||||
class = "[Ss]team";
|
||||
icon = util.glyph "f1b6";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
24
configs/nixos/modules/desktop/apps/vencord.nix
Normal file
24
configs/nixos/modules/desktop/apps/vencord.nix
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
util,
|
||||
...
|
||||
}:
|
||||
# Vesktop, a Discord client with Vencord built in.
|
||||
let
|
||||
user = config.user.name;
|
||||
in
|
||||
{
|
||||
options.modules.desktop.apps.vencord.enable =
|
||||
lib.mkEnableOption "Vesktop, a Discord client with Vencord built in";
|
||||
|
||||
config = lib.mkIf config.modules.desktop.apps.vencord.enable {
|
||||
home-manager.users.${user}.home.packages = [ pkgs.vesktop ];
|
||||
|
||||
registry.window-icon.${config.networking.hostName}.vesktop = {
|
||||
class = "[Vv]esktop";
|
||||
icon = util.glyph "f1ff";
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,14 +1,11 @@
|
||||
{ config, lib, ... }:
|
||||
# The desktop aggregator: one flag brings up the whole graphical session.
|
||||
let
|
||||
cfg = config.modules.desktop;
|
||||
in
|
||||
{
|
||||
options.modules.desktop.enable = lib.mkEnableOption "the keyboard-driven Hyprland desktop";
|
||||
|
||||
# Each piece is turned on at default priority, so a host can still override
|
||||
# any one of them while the single flag above enables the whole desktop.
|
||||
config = lib.mkIf cfg.enable {
|
||||
config = lib.mkIf config.modules.desktop.enable {
|
||||
modules.desktop.apps.firefox.enable = lib.mkDefault true;
|
||||
modules.desktop.apps.ghostty.enable = lib.mkDefault true;
|
||||
modules.desktop.session.hyprland.enable = lib.mkDefault true;
|
||||
@@ -26,5 +23,6 @@ in
|
||||
modules.desktop.utilities.screenshot.enable = lib.mkDefault true;
|
||||
modules.desktop.utilities.theming.enable = lib.mkDefault true;
|
||||
modules.desktop.utilities.userdirs.enable = lib.mkDefault true;
|
||||
registry.env.${config.networking.hostName}.NIXOS_OZONE_WL = "1";
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,41 +1,15 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
util,
|
||||
...
|
||||
}:
|
||||
# The Hyprland compositor.
|
||||
let
|
||||
cfg = config.modules.desktop.session.hyprland;
|
||||
user = config.user.name;
|
||||
cursor = config.stylix.cursor;
|
||||
|
||||
# Numbered-workspace switch and move for 1..9, the operator's i3 muscle memory.
|
||||
workspaceBinds = lib.concatMap (n: [
|
||||
"$mod, ${toString n}, workspace, ${toString n}"
|
||||
"$mod SHIFT, ${toString n}, movetoworkspace, ${toString n}"
|
||||
]) (lib.range 1 9);
|
||||
|
||||
# Flip the tiling strategy between the two built-in layouts, since neither a
|
||||
# dispatcher nor a keyword toggles it on its own.
|
||||
toggleLayout = pkgs.writeShellScript "hypr-toggle-layout" ''
|
||||
if [ "$(hyprctl getoption -j general:layout | ${pkgs.jq}/bin/jq -r .str)" = dwindle ]; then
|
||||
hyprctl keyword general:layout master
|
||||
else
|
||||
hyprctl keyword general:layout dwindle
|
||||
fi
|
||||
'';
|
||||
in
|
||||
{
|
||||
options.modules.desktop.session.hyprland = {
|
||||
enable = lib.mkEnableOption "the Hyprland compositor";
|
||||
options.modules.desktop.session.hyprland.enable = lib.mkEnableOption "the Hyprland compositor";
|
||||
|
||||
blur = lib.mkEnableOption ''
|
||||
window blur. Off by default as the single biggest battery cost on a
|
||||
laptop, left on for a host with the headroom to spend it'';
|
||||
};
|
||||
config = lib.mkIf config.modules.desktop.session.hyprland.enable {
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
# This program integration owns the session, portals, and polkit, launched
|
||||
# through the universal Wayland session manager.
|
||||
programs.hyprland = {
|
||||
@@ -43,7 +17,7 @@ in
|
||||
withUWSM = true;
|
||||
};
|
||||
|
||||
home-manager.users.${user}.wayland.windowManager.hyprland = {
|
||||
home-manager.users.${config.user.name}.wayland.windowManager.hyprland = {
|
||||
enable = true;
|
||||
|
||||
# One package drives the whole session, so there is never a version split.
|
||||
@@ -55,136 +29,216 @@ in
|
||||
# uwsm owns the systemd graphical-session targets.
|
||||
systemd.enable = false;
|
||||
|
||||
# Write the native hyprlang hyprland.conf, whose variable and bind syntax
|
||||
# the settings below are expressed in.
|
||||
configType = "hyprlang";
|
||||
# Write the native Lua Hyprland config. Home Manager renders the Nix
|
||||
# settings below as hl.* calls.
|
||||
configType = "lua";
|
||||
|
||||
settings = {
|
||||
"$mod" = "SUPER";
|
||||
"$terminal" = "ghostty";
|
||||
|
||||
# Session variables handed to the compositor directly.
|
||||
# UWSM launches the session without the shell profile that would carry
|
||||
# them, so a variable the compositor or its children must see is set
|
||||
# here rather than through home-manager's sessionVariables.
|
||||
env =
|
||||
[
|
||||
# Chromium and Electron apps read this to select native Wayland;
|
||||
# nixpkgs wrappers (Obsidian's included) gate their Wayland flags on
|
||||
# it, so without it they run under XWayland and blur at this DPI.
|
||||
"NIXOS_OZONE_WL,1"
|
||||
]
|
||||
# Bibata ships XCursor only.
|
||||
# The hyprcursor variables name the same theme, which Hyprland
|
||||
# resolves through its XCursor fallback.
|
||||
++ lib.optionals (cursor != null) [
|
||||
"XCURSOR_THEME,${cursor.name}"
|
||||
"XCURSOR_SIZE,${toString cursor.size}"
|
||||
"HYPRCURSOR_THEME,${cursor.name}"
|
||||
"HYPRCURSOR_SIZE,${toString cursor.size}"
|
||||
];
|
||||
|
||||
input = {
|
||||
kb_layout = "us";
|
||||
# Caps is a second Escape.
|
||||
# Shift+Caps still toggles a real CapsLock.
|
||||
kb_options = "caps:escape_shifted_capslock";
|
||||
# Snappy key repeat.
|
||||
repeat_delay = 250;
|
||||
repeat_rate = 45;
|
||||
accel_profile = "flat";
|
||||
touchpad = {
|
||||
natural_scroll = true;
|
||||
tap-to-click = true;
|
||||
disable_while_typing = true;
|
||||
config = {
|
||||
input = {
|
||||
kb_layout = config.services.xserver.xkb.layout;
|
||||
repeat_delay = 250;
|
||||
repeat_rate = 45;
|
||||
accel_profile = "flat";
|
||||
touchpad = {
|
||||
natural_scroll = true;
|
||||
tap_to_click = true;
|
||||
disable_while_typing = true;
|
||||
};
|
||||
};
|
||||
|
||||
general = {
|
||||
gaps_in = 4;
|
||||
gaps_out = 8;
|
||||
border_size = 2;
|
||||
layout = "dwindle";
|
||||
};
|
||||
|
||||
decoration = {
|
||||
rounding = 6;
|
||||
blur.enabled = false;
|
||||
};
|
||||
|
||||
xwayland.force_zero_scaling = true;
|
||||
|
||||
dwindle.preserve_split = true;
|
||||
};
|
||||
|
||||
general = {
|
||||
gaps_in = 4;
|
||||
gaps_out = 8;
|
||||
border_size = 2;
|
||||
layout = "dwindle";
|
||||
};
|
||||
|
||||
decoration = {
|
||||
rounding = 6;
|
||||
blur.enabled = cfg.blur;
|
||||
};
|
||||
|
||||
# XWayland clients render at the panel's native resolution instead of
|
||||
# being raster-scaled by the compositor at the fractional monitor scale.
|
||||
xwayland.force_zero_scaling = true;
|
||||
|
||||
animations = {
|
||||
enabled = true;
|
||||
bezier = [ "ease, 0.25, 0.1, 0.25, 1.0" ];
|
||||
# Durations are in centiseconds.
|
||||
# Short values keep the motion subtle.
|
||||
animation = [
|
||||
"windows, 1, 3, ease"
|
||||
"fade, 1, 3, ease"
|
||||
# Layer surfaces like the launcher fade in a touch quicker than windows.
|
||||
"layersIn, 1, 2, ease"
|
||||
"fadeLayersIn, 1, 2, ease"
|
||||
"workspaces, 1, 3, ease"
|
||||
"border, 1, 3, ease"
|
||||
env = lib.mapAttrsToList (name: value: {
|
||||
_args = [
|
||||
name
|
||||
value
|
||||
];
|
||||
}) (lib.attrByPath [ config.networking.hostName ] { } config.registry.env);
|
||||
|
||||
bind = lib.mapAttrsToList (_name: keybind: {
|
||||
_args = [
|
||||
keybind.keys
|
||||
(lib.generators.mkLuaInline (
|
||||
{
|
||||
exec = "hl.dsp.exec_cmd(${lib.generators.toLua { } keybind.action.command})";
|
||||
focus = "hl.dsp.focus(${
|
||||
lib.generators.toLua { } { inherit (keybind.action) direction workspace; }
|
||||
})";
|
||||
"move-window" = "hl.dsp.window.move(${
|
||||
lib.generators.toLua { } { inherit (keybind.action) direction workspace; }
|
||||
})";
|
||||
"resize-window" = "hl.dsp.window.resize(${
|
||||
lib.generators.toLua { } (
|
||||
{
|
||||
down = {
|
||||
x = 0;
|
||||
y = 10;
|
||||
relative = true;
|
||||
};
|
||||
left = {
|
||||
x = -10;
|
||||
y = 0;
|
||||
relative = true;
|
||||
};
|
||||
right = {
|
||||
x = 10;
|
||||
y = 0;
|
||||
relative = true;
|
||||
};
|
||||
up = {
|
||||
x = 0;
|
||||
y = -10;
|
||||
relative = true;
|
||||
};
|
||||
}
|
||||
.${keybind.action.direction}
|
||||
)
|
||||
})";
|
||||
"toggle-floating" = ''hl.dsp.window.float({ action = "toggle" })'';
|
||||
"toggle-fullscreen" = "hl.dsp.window.fullscreen()";
|
||||
"close-window" = "hl.dsp.window.close()";
|
||||
}
|
||||
.${keybind.action.kind}
|
||||
))
|
||||
]
|
||||
++ lib.optional keybind.repeating {
|
||||
repeating = true;
|
||||
}
|
||||
++ lib.optional keybind.locked {
|
||||
locked = true;
|
||||
};
|
||||
}) (lib.attrByPath [ config.networking.hostName ] { } config.registry.keybind);
|
||||
};
|
||||
};
|
||||
registry.keybind.${config.networking.hostName} = lib.mkMerge [
|
||||
# Generate numeric workspace keybinds.
|
||||
(lib.listToAttrs (
|
||||
lib.concatMap (workspace: [
|
||||
{
|
||||
name = "hyprland-workspace-${toString workspace}";
|
||||
value = {
|
||||
keys = "SUPER + ${toString workspace}";
|
||||
action = {
|
||||
kind = "focus";
|
||||
inherit workspace;
|
||||
};
|
||||
};
|
||||
}
|
||||
{
|
||||
name = "hyprland-move-to-workspace-${toString workspace}";
|
||||
value = {
|
||||
keys = "SUPER + SHIFT + ${toString workspace}";
|
||||
action = {
|
||||
kind = "move-window";
|
||||
inherit workspace;
|
||||
};
|
||||
};
|
||||
}
|
||||
]) (lib.range 1 9)
|
||||
))
|
||||
|
||||
# Generate directional keybinds.
|
||||
(lib.listToAttrs (
|
||||
lib.concatMap
|
||||
(
|
||||
{ direction, key }:
|
||||
[
|
||||
{
|
||||
name = "hyprland-focus-${direction}";
|
||||
value = {
|
||||
keys = "SUPER + ${key}";
|
||||
action = {
|
||||
kind = "focus";
|
||||
inherit direction;
|
||||
};
|
||||
};
|
||||
}
|
||||
{
|
||||
name = "hyprland-move-${direction}";
|
||||
value = {
|
||||
keys = "SUPER + SHIFT + ${key}";
|
||||
action = {
|
||||
kind = "move-window";
|
||||
inherit direction;
|
||||
};
|
||||
};
|
||||
}
|
||||
{
|
||||
name = "hyprland-resize-${direction}";
|
||||
value = {
|
||||
keys = "SUPER + ALT + ${key}";
|
||||
repeating = true;
|
||||
action = {
|
||||
kind = "resize-window";
|
||||
inherit direction;
|
||||
};
|
||||
};
|
||||
}
|
||||
]
|
||||
)
|
||||
[
|
||||
{
|
||||
key = "H";
|
||||
direction = "left";
|
||||
}
|
||||
{
|
||||
key = "J";
|
||||
direction = "down";
|
||||
}
|
||||
{
|
||||
key = "K";
|
||||
direction = "up";
|
||||
}
|
||||
{
|
||||
key = "L";
|
||||
direction = "right";
|
||||
}
|
||||
]
|
||||
))
|
||||
{
|
||||
hyprland-toggle-floating = {
|
||||
keys = "SUPER + Space";
|
||||
action.kind = "toggle-floating";
|
||||
};
|
||||
|
||||
dwindle = {
|
||||
preserve_split = true;
|
||||
hyprland-toggle-fullscreen = {
|
||||
keys = "SUPER + F";
|
||||
action.kind = "toggle-fullscreen";
|
||||
};
|
||||
|
||||
bind = [
|
||||
"$mod, Return, exec, $terminal"
|
||||
hyprland-close-window = {
|
||||
keys = "SUPER + SHIFT + Q";
|
||||
action.kind = "close-window";
|
||||
};
|
||||
}
|
||||
];
|
||||
|
||||
# Move focus.
|
||||
"$mod, H, movefocus, l"
|
||||
"$mod, J, movefocus, d"
|
||||
"$mod, K, movefocus, u"
|
||||
"$mod, L, movefocus, r"
|
||||
|
||||
# Move the window within the layout.
|
||||
"$mod SHIFT, H, movewindow, l"
|
||||
"$mod SHIFT, J, movewindow, d"
|
||||
"$mod SHIFT, K, movewindow, u"
|
||||
"$mod SHIFT, L, movewindow, r"
|
||||
|
||||
# Resize the active window.
|
||||
"$mod ALT, H, resizeactive, -40 0"
|
||||
"$mod ALT, J, resizeactive, 0 40"
|
||||
"$mod ALT, K, resizeactive, 0 -40"
|
||||
"$mod ALT, L, resizeactive, 40 0"
|
||||
|
||||
"$mod, Space, togglefloating,"
|
||||
"$mod, F, fullscreen,"
|
||||
# togglesplit is a dwindle layout message, reached through layoutmsg.
|
||||
"$mod, T, layoutmsg, togglesplit"
|
||||
"$mod SHIFT, T, exec, ${toggleLayout}"
|
||||
"$mod SHIFT, Q, killactive,"
|
||||
"$mod CTRL, Q, forcekillactive,"
|
||||
]
|
||||
++ workspaceBinds;
|
||||
|
||||
# Volume and brightness keys repeat while held, each raising a popup
|
||||
# through the OSD client.
|
||||
# Volume is capped at 100 percent.
|
||||
# The client floors brightness so a full hold cannot black the screen out.
|
||||
binde = [
|
||||
", XF86AudioRaiseVolume, exec, ${pkgs.swayosd}/bin/swayosd-client --output-volume raise --max-volume 100"
|
||||
", XF86AudioLowerVolume, exec, ${pkgs.swayosd}/bin/swayosd-client --output-volume lower"
|
||||
", XF86MonBrightnessUp, exec, ${pkgs.swayosd}/bin/swayosd-client --brightness raise"
|
||||
", XF86MonBrightnessDown, exec, ${pkgs.swayosd}/bin/swayosd-client --brightness lower"
|
||||
];
|
||||
|
||||
# Mute and media transport still fire while the session is locked.
|
||||
bindl = [
|
||||
", XF86AudioMute, exec, ${pkgs.swayosd}/bin/swayosd-client --output-volume mute-toggle"
|
||||
", XF86AudioPlay, exec, ${pkgs.playerctl}/bin/playerctl play-pause"
|
||||
", XF86AudioNext, exec, ${pkgs.playerctl}/bin/playerctl next"
|
||||
", XF86AudioPrev, exec, ${pkgs.playerctl}/bin/playerctl previous"
|
||||
];
|
||||
registry.waybar.${config.networking.hostName}."hyprland/workspaces" = {
|
||||
position = "left";
|
||||
order = 10;
|
||||
settings = {
|
||||
format = "{id} {windows}";
|
||||
format-window-separator = " ";
|
||||
window-rewrite-default = util.glyph "f2d0";
|
||||
window-rewrite = lib.mapAttrs' (
|
||||
_name: windowIcon: lib.nameValuePair "class<${windowIcon.class}>" windowIcon.icon
|
||||
) (lib.attrByPath [ config.networking.hostName ] { } config.registry.window-icon);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -5,62 +5,34 @@
|
||||
...
|
||||
}:
|
||||
# hypridle: idle-triggered locking and display power management.
|
||||
let
|
||||
cfg = config.modules.desktop.session.hyprland.hypridle;
|
||||
user = config.user.name;
|
||||
|
||||
hyprctl = "${config.programs.hyprland.package}/bin/hyprctl";
|
||||
hyprlock = "${config.home-manager.users.${user}.programs.hyprlock.package}/bin/hyprlock";
|
||||
|
||||
# The guard drops the call when a locker is already up, so no idle trigger
|
||||
# stacks a second hyprlock over the first.
|
||||
lockCmd = "${pkgs.procps}/bin/pidof hyprlock || ${hyprlock}";
|
||||
in
|
||||
{
|
||||
options.modules.desktop.session.hyprland.hypridle = {
|
||||
enable = lib.mkEnableOption "hypridle idle management";
|
||||
options.modules.desktop.session.hyprland.hypridle.enable =
|
||||
lib.mkEnableOption "hypridle idle management";
|
||||
|
||||
lockTimeout = lib.mkOption {
|
||||
type = lib.types.ints.positive;
|
||||
default = 300;
|
||||
description = "Seconds of inactivity before the screen locks.";
|
||||
};
|
||||
|
||||
screenOffTimeout = lib.mkOption {
|
||||
type = lib.types.ints.positive;
|
||||
default = 360;
|
||||
description = "Seconds of inactivity before the displays are powered off.";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
config = lib.mkIf config.modules.desktop.session.hyprland.hypridle.enable {
|
||||
# Closing the lid suspends, and every suspend locks first through the
|
||||
# before_sleep_cmd below, so the lid always lands at a locked screen.
|
||||
services.logind.settings.Login.HandleLidSwitch = "suspend";
|
||||
|
||||
home-manager.users.${user}.services.hypridle = {
|
||||
home-manager.users.${config.user.name}.services.hypridle = {
|
||||
enable = true;
|
||||
|
||||
settings = {
|
||||
general = {
|
||||
lock_cmd = lockCmd;
|
||||
# The guard drops the call when a locker is already up, so no idle
|
||||
# trigger stacks a second hyprlock over the first.
|
||||
lock_cmd = "${lib.getExe' pkgs.procps "pidof"} hyprlock || ${lib.getExe pkgs.hyprlock}";
|
||||
before_sleep_cmd = "loginctl lock-session";
|
||||
# Waking restores the displays the screen-off listener may have cut.
|
||||
after_sleep_cmd = "${hyprctl} dispatch dpms on";
|
||||
# Waking pokes the compositor in case firmware leaves displays dark.
|
||||
after_sleep_cmd = "${lib.getExe' config.programs.hyprland.package "hyprctl"} dispatch 'hl.dsp.dpms({ action = \"enable\" })'";
|
||||
};
|
||||
|
||||
listener = [
|
||||
# Lock on idle.
|
||||
{
|
||||
timeout = cfg.lockTimeout;
|
||||
timeout = 300;
|
||||
on-timeout = "loginctl lock-session";
|
||||
}
|
||||
# Power the displays off a little later, restoring them on any activity.
|
||||
{
|
||||
timeout = cfg.screenOffTimeout;
|
||||
on-timeout = "${hyprctl} dispatch dpms off";
|
||||
on-resume = "${hyprctl} dispatch dpms on";
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
@@ -2,53 +2,63 @@
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
util,
|
||||
...
|
||||
}:
|
||||
# The lock screen: a hyprlock session-lock surface the compositor owns.
|
||||
let
|
||||
cfg = config.modules.desktop.session.hyprland.hyprlock;
|
||||
user = config.user.name;
|
||||
|
||||
# The hyprlock this module installs, used by the lock keybind below.
|
||||
hyprlock = "${config.home-manager.users.${user}.programs.hyprlock.package}/bin/hyprlock";
|
||||
|
||||
sleepIcon = builtins.fromJSON ''"\udb81\udcb2"'';
|
||||
sleepOffIcon = builtins.fromJSON ''"\udb81\udcb3"'';
|
||||
sleepIcon = util.glyph [
|
||||
"db81"
|
||||
"dcb2"
|
||||
];
|
||||
sleepOffIcon = util.glyph [
|
||||
"db81"
|
||||
"dcb3"
|
||||
];
|
||||
|
||||
idleLockToggle = pkgs.writeShellScript "lockscreen-idle-inhibit-toggle" ''
|
||||
unit=lockscreen-idle-inhibit.service
|
||||
|
||||
refresh_waybar() {
|
||||
${pkgs.procps}/bin/pkill -RTMIN+9 waybar >/dev/null 2>&1 || true
|
||||
${lib.getExe' pkgs.procps "pkill"} -RTMIN+9 waybar >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
if ${pkgs.systemd}/bin/systemctl --user is-active --quiet "$unit"; then
|
||||
${pkgs.systemd}/bin/systemctl --user stop "$unit"
|
||||
if ${lib.getExe' pkgs.systemd "systemctl"} --user is-active --quiet "$unit"; then
|
||||
${lib.getExe' pkgs.systemd "systemctl"} --user stop "$unit"
|
||||
refresh_waybar
|
||||
${pkgs.swayosd}/bin/swayosd-client --custom-message "${sleepIcon}"
|
||||
${lib.getExe' pkgs.swayosd "swayosd-client"} --custom-message "${sleepIcon}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ${pkgs.systemd}/bin/systemd-run \
|
||||
if ${lib.getExe' pkgs.systemd "systemd-run"} \
|
||||
--user \
|
||||
--collect \
|
||||
--unit=lockscreen-idle-inhibit \
|
||||
--description="Disable idle-triggered screen locking" \
|
||||
${pkgs.systemd}/bin/systemd-inhibit \
|
||||
${lib.getExe' pkgs.systemd "systemd-inhibit"} \
|
||||
--what=idle \
|
||||
--who=lockscreen-hotkey \
|
||||
--why="lock screen disabled by hotkey" \
|
||||
${pkgs.coreutils}/bin/sleep infinity >/dev/null; then
|
||||
${lib.getExe' pkgs.coreutils "sleep"} infinity >/dev/null; then
|
||||
refresh_waybar
|
||||
${pkgs.swayosd}/bin/swayosd-client --custom-message "${sleepOffIcon}"
|
||||
${lib.getExe' pkgs.swayosd "swayosd-client"} --custom-message "${sleepOffIcon}"
|
||||
fi
|
||||
'';
|
||||
|
||||
idleLockStatus = pkgs.writeShellScript "waybar-idle-lock-status" ''
|
||||
if ${lib.getExe' pkgs.systemd "systemctl"} --user is-active --quiet lockscreen-idle-inhibit.service; then
|
||||
printf '{"text":"${sleepOffIcon}","tooltip":"Idle locking inhibited","class":"inhibited"}\n'
|
||||
else
|
||||
printf '{"text":"","class":"idle"}\n'
|
||||
fi
|
||||
'';
|
||||
in
|
||||
{
|
||||
options.modules.desktop.session.hyprland.hyprlock.enable = lib.mkEnableOption "the hyprlock lock screen";
|
||||
options.modules.desktop.session.hyprland.hyprlock.enable =
|
||||
lib.mkEnableOption "the hyprlock lock screen";
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
home-manager.users.${user} = {
|
||||
config = lib.mkIf config.modules.desktop.session.hyprland.hyprlock.enable {
|
||||
home-manager.users.${config.user.name} = {
|
||||
# Colors and the lock-screen background come from Stylix's hyprlock target,
|
||||
# so only geometry and behaviour are set here.
|
||||
programs.hyprlock = {
|
||||
@@ -81,14 +91,36 @@ in
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# Lock on Super+X.
|
||||
# The guard drops the keypress when a locker is already up, so a second
|
||||
# hyprlock never stacks over the first.
|
||||
wayland.windowManager.hyprland.settings.bind = [
|
||||
"$mod, X, exec, ${pkgs.procps}/bin/pidof hyprlock || ${hyprlock}"
|
||||
"$mod CTRL, X, exec, ${idleLockToggle}"
|
||||
];
|
||||
registry.keybind.${config.networking.hostName} = {
|
||||
hyprlock-lock = {
|
||||
keys = "SUPER + X";
|
||||
action = {
|
||||
kind = "exec";
|
||||
# The guard drops the call when a locker is already up, so no manual
|
||||
# trigger stacks a second hyprlock over the first.
|
||||
command = "${lib.getExe' pkgs.procps "pidof"} hyprlock || ${lib.getExe pkgs.hyprlock}";
|
||||
};
|
||||
};
|
||||
|
||||
hyprlock-toggle-idle-inhibit = {
|
||||
keys = "SUPER + CTRL + X";
|
||||
action = {
|
||||
kind = "exec";
|
||||
command = "${idleLockToggle}";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
registry.waybar.${config.networking.hostName}."custom/idle-lock" = {
|
||||
order = 20;
|
||||
settings = {
|
||||
return-type = "json";
|
||||
exec = "${idleLockStatus}";
|
||||
interval = "once";
|
||||
signal = 9;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,18 +6,20 @@
|
||||
}:
|
||||
# Text login: greetd running the tuigreet greeter, which starts the session
|
||||
# through the universal Wayland session manager.
|
||||
let
|
||||
cfg = config.modules.desktop.session.login;
|
||||
in
|
||||
{
|
||||
options.modules.desktop.session.login.enable = lib.mkEnableOption "greetd with the tuigreet text greeter";
|
||||
options.modules.desktop.session.login.enable =
|
||||
lib.mkEnableOption "greetd with the tuigreet text greeter";
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
config = lib.mkIf config.modules.desktop.session.login.enable {
|
||||
services.greetd = {
|
||||
enable = true;
|
||||
settings.default_session.command =
|
||||
"${lib.getExe pkgs.tuigreet} --time --remember "
|
||||
+ "--cmd 'uwsm start -e -D Hyprland hyprland.desktop'";
|
||||
settings.default_session.command = lib.escapeShellArgs [
|
||||
(lib.getExe pkgs.tuigreet)
|
||||
"--time"
|
||||
"--remember"
|
||||
"--cmd"
|
||||
"${lib.getExe config.programs.uwsm.package} start -e -D Hyprland hyprland.desktop"
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,20 +2,29 @@
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
util,
|
||||
...
|
||||
}:
|
||||
# mako as the notification daemon: toasts, a do-not-disturb mode, and history recall.
|
||||
let
|
||||
cfg = config.modules.desktop.session.mako;
|
||||
user = config.user.name;
|
||||
dndStatus = pkgs.writeShellScript "waybar-dnd-status" ''
|
||||
if ${lib.getExe' pkgs.mako "makoctl"} mode 2>/dev/null | ${lib.getExe pkgs.gnugrep} -qx dnd; then
|
||||
printf '{"text":"${util.glyph "f1f6"}","tooltip":"Do not disturb: on","class":"dnd"}\n'
|
||||
else
|
||||
printf '{"text":"${util.glyph "f0f3"}","tooltip":"Notifications on","class":"active"}\n'
|
||||
fi
|
||||
'';
|
||||
|
||||
makoctl = "${pkgs.mako}/bin/makoctl";
|
||||
dndToggle = pkgs.writeShellScript "waybar-dnd-toggle" ''
|
||||
${lib.getExe' pkgs.mako "makoctl"} mode -t dnd >/dev/null 2>&1
|
||||
${lib.getExe' pkgs.procps "pkill"} -RTMIN+8 waybar
|
||||
'';
|
||||
in
|
||||
{
|
||||
options.modules.desktop.session.mako.enable = lib.mkEnableOption "the mako notification daemon";
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
home-manager.users.${user} = {
|
||||
config = lib.mkIf config.modules.desktop.session.mako.enable {
|
||||
home-manager.users.${config.user.name} = {
|
||||
# Colors and the popup font come from Stylix's mako target, so only
|
||||
# behaviour is set here.
|
||||
services.mako = {
|
||||
@@ -32,13 +41,25 @@ in
|
||||
"mode=dnd".invisible = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# Recall the last notification from history, keyboard-only like the rest
|
||||
# of the session.
|
||||
# $mod is defined by the compositor config these binds share.
|
||||
wayland.windowManager.hyprland.settings.bind = [
|
||||
"$mod, N, exec, ${makoctl} restore"
|
||||
];
|
||||
registry.keybind.${config.networking.hostName}.mako-history = {
|
||||
keys = "SUPER + N";
|
||||
action = {
|
||||
kind = "exec";
|
||||
command = "${lib.getExe' pkgs.mako "makoctl"} restore";
|
||||
};
|
||||
};
|
||||
|
||||
registry.waybar.${config.networking.hostName}."custom/dnd" = {
|
||||
order = 80;
|
||||
settings = {
|
||||
return-type = "json";
|
||||
exec = "${dndStatus}";
|
||||
on-click = "${dndToggle}";
|
||||
interval = "once";
|
||||
signal = 8;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
{ config, lib, ... }:
|
||||
# XDG desktop portal routing for in-app screen sharing and file dialogs.
|
||||
let
|
||||
cfg = config.modules.desktop.session.portals;
|
||||
in
|
||||
{
|
||||
options.modules.desktop.session.portals.enable =
|
||||
lib.mkEnableOption "XDG desktop portals for in-app screen sharing and file dialogs";
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
config = lib.mkIf config.modules.desktop.session.portals.enable {
|
||||
# The backend packages arrive with the compositor, so only the routing is set
|
||||
# here.
|
||||
# Screen sharing, screenshots, and global shortcuts need the compositor's
|
||||
|
||||
@@ -6,31 +6,29 @@
|
||||
}:
|
||||
# rofi as the desktop launcher and dmenu-style menu frontend.
|
||||
let
|
||||
cfg = config.modules.desktop.session.rofi;
|
||||
user = config.user.name;
|
||||
|
||||
# A session menu built on the same themed rofi, so the power actions look
|
||||
# like every other menu the launcher drives.
|
||||
powerMenu = pkgs.writeShellScript "rofi-power-menu" ''
|
||||
chosen=$(printf '%s\n' Lock Suspend 'Log out' Reboot 'Shut down' \
|
||||
| ${pkgs.rofi}/bin/rofi -dmenu -i -p Power)
|
||||
| ${lib.getExe pkgs.rofi} -dmenu -i -p Power)
|
||||
case "$chosen" in
|
||||
Lock) ${pkgs.systemd}/bin/loginctl lock-session ;;
|
||||
Suspend) ${pkgs.systemd}/bin/systemctl suspend ;;
|
||||
'Log out') ${pkgs.systemd}/bin/loginctl terminate-session "$XDG_SESSION_ID" ;;
|
||||
Reboot) ${pkgs.systemd}/bin/systemctl reboot ;;
|
||||
'Shut down') ${pkgs.systemd}/bin/systemctl poweroff ;;
|
||||
Lock) ${lib.getExe' pkgs.systemd "loginctl"} lock-session ;;
|
||||
Suspend) ${lib.getExe' pkgs.systemd "systemctl"} suspend ;;
|
||||
'Log out') ${lib.getExe' pkgs.systemd "loginctl"} terminate-session "$XDG_SESSION_ID" ;;
|
||||
Reboot) ${lib.getExe' pkgs.systemd "systemctl"} reboot ;;
|
||||
'Shut down') ${lib.getExe' pkgs.systemd "systemctl"} poweroff ;;
|
||||
esac
|
||||
'';
|
||||
in
|
||||
{
|
||||
options.modules.desktop.session.rofi.enable = lib.mkEnableOption "the rofi launcher";
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
home-manager.users.${user} = {
|
||||
# No theme is set here: Stylix's rofi target supplies the Nord one.
|
||||
config = lib.mkIf config.modules.desktop.session.rofi.enable {
|
||||
home-manager.users.${config.user.name} = hm: {
|
||||
# Stylix supplies the Nord theme.
|
||||
programs.rofi = {
|
||||
enable = true;
|
||||
font = lib.mkForce "JetBrains Mono 12";
|
||||
|
||||
# Application launch is the only mode, kept minimal so the prompt opens
|
||||
# as fast as rofi can read the desktop entries.
|
||||
@@ -39,14 +37,31 @@ in
|
||||
modi = "drun";
|
||||
show-icons = false;
|
||||
};
|
||||
|
||||
theme.window = {
|
||||
border = hm.config.lib.formats.rasi.mkLiteral "2px";
|
||||
border-color = hm.config.lib.formats.rasi.mkLiteral "#FFFFFF";
|
||||
border-radius = hm.config.lib.formats.rasi.mkLiteral "8px";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
registry.keybind.${config.networking.hostName} = {
|
||||
rofi-launcher = {
|
||||
keys = "SUPER + R";
|
||||
action = {
|
||||
kind = "exec";
|
||||
command = "${lib.getExe pkgs.rofi} -show drun";
|
||||
};
|
||||
};
|
||||
|
||||
# The launcher and the session menu, kept next to the tool they drive.
|
||||
# $mod is defined by the compositor config these binds share.
|
||||
wayland.windowManager.hyprland.settings.bind = [
|
||||
"$mod, R, exec, ${pkgs.rofi}/bin/rofi -show drun"
|
||||
"$mod SHIFT, X, exec, ${powerMenu}"
|
||||
];
|
||||
rofi-power-menu = {
|
||||
keys = "SUPER + SHIFT + X";
|
||||
action = {
|
||||
kind = "exec";
|
||||
command = "${powerMenu}";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,99 +6,56 @@
|
||||
}:
|
||||
# Waybar: a top status bar reading system state at a glance.
|
||||
let
|
||||
cfg = config.modules.desktop.session.waybar;
|
||||
user = config.user.name;
|
||||
moduleNames =
|
||||
position:
|
||||
map (entry: entry.name) (
|
||||
lib.sort (left: right: left.module.order < right.module.order) (
|
||||
lib.filter (entry: entry.module.position == position) moduleEntries
|
||||
)
|
||||
);
|
||||
|
||||
# A Nerd Font Private-Use-Area codepoint as a literal character, decoded
|
||||
# through JSON so the glyph survives as bytes rather than an editor paste.
|
||||
g = code: builtins.fromJSON ''"\u${code}"'';
|
||||
sleepOffIcon = builtins.fromJSON ''"\udb81\udcb3"'';
|
||||
moduleEntries = lib.mapAttrsToList (name: module: {
|
||||
inherit name module;
|
||||
}) modules;
|
||||
|
||||
# Renders and toggles mako's do-not-disturb mode.
|
||||
# With no mako daemon answering, the status reads "notifications on", keeping
|
||||
# the widget inert rather than broken.
|
||||
dndStatus = pkgs.writeShellScript "waybar-dnd-status" ''
|
||||
if ${pkgs.mako}/bin/makoctl mode 2>/dev/null | ${pkgs.gnugrep}/bin/grep -qx dnd; then
|
||||
printf '{"text":"${g "f1f6"}","tooltip":"Do not disturb: on","class":"dnd"}\n'
|
||||
else
|
||||
printf '{"text":"${g "f0f3"}","tooltip":"Notifications on","class":"active"}\n'
|
||||
fi
|
||||
'';
|
||||
|
||||
dndToggle = pkgs.writeShellScript "waybar-dnd-toggle" ''
|
||||
${pkgs.mako}/bin/makoctl mode -t dnd >/dev/null 2>&1
|
||||
${pkgs.procps}/bin/pkill -RTMIN+8 waybar
|
||||
'';
|
||||
|
||||
# Lit while a wf-recorder capture is running.
|
||||
# Empty text collapses the widget when idle, so it shows nothing until then.
|
||||
recStatus = pkgs.writeShellScript "waybar-recording-status" ''
|
||||
if ${pkgs.procps}/bin/pgrep -x wf-recorder >/dev/null; then
|
||||
printf '{"text":"${g "f03d"}","tooltip":"Recording","class":"recording"}\n'
|
||||
else
|
||||
printf '{"text":"","class":"idle"}\n'
|
||||
fi
|
||||
'';
|
||||
|
||||
idleLockStatus = pkgs.writeShellScript "waybar-idle-lock-status" ''
|
||||
if ${pkgs.systemd}/bin/systemctl --user is-active --quiet lockscreen-idle-inhibit.service; then
|
||||
printf '{"text":"${sleepOffIcon}","tooltip":"Idle locking inhibited","class":"inhibited"}\n'
|
||||
else
|
||||
printf '{"text":"","class":"idle"}\n'
|
||||
fi
|
||||
'';
|
||||
modules = lib.attrByPath [ config.networking.hostName ] { } config.registry.waybar;
|
||||
in
|
||||
{
|
||||
options.modules.desktop.session.waybar.enable = lib.mkEnableOption "the Waybar status bar";
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
config = lib.mkIf config.modules.desktop.session.waybar.enable {
|
||||
# A symbols font backs the module glyphs, so pango falls back to it for the
|
||||
# codepoints Stylix's monospace font does not carry.
|
||||
fonts.packages = [ pkgs.nerd-fonts.symbols-only ];
|
||||
|
||||
home-manager.users.${user}.programs.waybar = {
|
||||
home-manager.users.${config.user.name}.programs.waybar = {
|
||||
enable = true;
|
||||
|
||||
# A user service bound to graphical-session.target, which uwsm activates,
|
||||
# so the bar comes up with the session and no compositor exec-once.
|
||||
systemd.enable = true;
|
||||
|
||||
settings.mainBar = {
|
||||
layer = "top";
|
||||
position = "top";
|
||||
height = 30;
|
||||
settings.mainBar = lib.mkMerge [
|
||||
{
|
||||
layer = "top";
|
||||
position = "top";
|
||||
height = 30;
|
||||
spacing = 8;
|
||||
margin-right = 8;
|
||||
|
||||
modules-left = [ "hyprland/workspaces" ];
|
||||
modules-center = [ "clock" ];
|
||||
modules-right = [
|
||||
"custom/recording"
|
||||
"custom/idle-lock"
|
||||
"mpris"
|
||||
"wireplumber"
|
||||
"network"
|
||||
"battery"
|
||||
"custom/dnd"
|
||||
];
|
||||
modules-left = moduleNames "left";
|
||||
modules-center = moduleNames "center";
|
||||
modules-right = moduleNames "right";
|
||||
}
|
||||
(lib.mapAttrs (_name: module: module.settings) modules)
|
||||
];
|
||||
};
|
||||
|
||||
# Per-application icons for the windows on each workspace, mapped from
|
||||
# window class with a neutral glyph for anything unlisted.
|
||||
"hyprland/workspaces" = {
|
||||
on-click = "activate";
|
||||
format = "{id} {windows}";
|
||||
format-window-separator = " ";
|
||||
window-rewrite-default = g "f2d0";
|
||||
window-rewrite = {
|
||||
"class<Alacritty>" = g "f120";
|
||||
"class<com.mitchellh.ghostty>" = g "f120";
|
||||
"class<.*[Ff]irefox.*>" = g "f269";
|
||||
"class<chromium.*>" = g "f268";
|
||||
"class<[Cc]ode>" = g "f121";
|
||||
"class<obsidian>" = g "f02d";
|
||||
"class<[Ss]team>" = g "f1b6";
|
||||
};
|
||||
};
|
||||
|
||||
clock = {
|
||||
registry.waybar.${config.networking.hostName} = {
|
||||
clock = {
|
||||
position = "center";
|
||||
order = 10;
|
||||
settings = {
|
||||
format = "{:%a %d %b %H:%M}";
|
||||
tooltip-format = "<tt>{calendar}</tt>";
|
||||
calendar = {
|
||||
@@ -107,72 +64,6 @@ in
|
||||
format.today = "<b><u>{}</u></b>";
|
||||
};
|
||||
};
|
||||
|
||||
mpris = {
|
||||
format = "${g "f001"} {title}";
|
||||
format-paused = "${g "f04c"} {title}";
|
||||
max-length = 40;
|
||||
on-click = "play-pause";
|
||||
on-scroll-up = "next";
|
||||
on-scroll-down = "previous";
|
||||
};
|
||||
|
||||
wireplumber = {
|
||||
format = "{icon} {volume}%";
|
||||
format-muted = "${g "f026"} muted";
|
||||
format-icons = [
|
||||
(g "f026")
|
||||
(g "f027")
|
||||
(g "f028")
|
||||
];
|
||||
on-click = "${pkgs.wireplumber}/bin/wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle";
|
||||
};
|
||||
|
||||
network = {
|
||||
format-wifi = "${g "f1eb"} {essid}";
|
||||
format-ethernet = "${g "f796"} {ifname}";
|
||||
format-disconnected = "${g "f127"} off";
|
||||
tooltip-format = "{ipaddr} via {gwaddr}";
|
||||
};
|
||||
|
||||
battery = {
|
||||
format = "{icon} {capacity}%";
|
||||
format-charging = "${g "f0e7"} {capacity}%";
|
||||
format-icons = [
|
||||
(g "f244")
|
||||
(g "f243")
|
||||
(g "f242")
|
||||
(g "f241")
|
||||
(g "f240")
|
||||
];
|
||||
states = {
|
||||
warning = 20;
|
||||
critical = 10;
|
||||
};
|
||||
};
|
||||
|
||||
"custom/dnd" = {
|
||||
return-type = "json";
|
||||
exec = "${dndStatus}";
|
||||
on-click = "${dndToggle}";
|
||||
interval = "once";
|
||||
signal = 8;
|
||||
};
|
||||
|
||||
# Recording state has no event to hook, so the widget samples the
|
||||
# wf-recorder process once a second.
|
||||
"custom/recording" = {
|
||||
return-type = "json";
|
||||
exec = "${recStatus}";
|
||||
interval = 1;
|
||||
};
|
||||
|
||||
"custom/idle-lock" = {
|
||||
return-type = "json";
|
||||
exec = "${idleLockStatus}";
|
||||
interval = "once";
|
||||
signal = 9;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,12 +1,25 @@
|
||||
{ config, lib, ... }:
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
util,
|
||||
...
|
||||
}:
|
||||
# PipeWire as the desktop audio server.
|
||||
let
|
||||
cfg = config.modules.desktop.utilities.audio;
|
||||
mediaKey = keys: command: {
|
||||
inherit keys;
|
||||
locked = true;
|
||||
action = {
|
||||
kind = "exec";
|
||||
command = "${lib.getExe pkgs.playerctl} ${command}";
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
options.modules.desktop.utilities.audio.enable = lib.mkEnableOption "the PipeWire audio server";
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
config = lib.mkIf config.modules.desktop.utilities.audio.enable {
|
||||
# Realtime scheduling for the audio threads, so playback survives load.
|
||||
security.rtkit.enable = true;
|
||||
|
||||
@@ -15,5 +28,39 @@ in
|
||||
alsa.enable = true;
|
||||
pulse.enable = true;
|
||||
};
|
||||
|
||||
registry.keybind.${config.networking.hostName} = {
|
||||
media-play = mediaKey "XF86AudioPlay" "play-pause";
|
||||
media-next = mediaKey "XF86AudioNext" "next";
|
||||
media-previous = mediaKey "XF86AudioPrev" "previous";
|
||||
};
|
||||
|
||||
registry.waybar.${config.networking.hostName} = {
|
||||
mpris = {
|
||||
order = 30;
|
||||
settings = {
|
||||
format = "${util.glyph "f001"} {title}";
|
||||
format-paused = "${util.glyph "f04c"} {title}";
|
||||
max-length = 40;
|
||||
on-click = "play-pause";
|
||||
on-scroll-up = "next";
|
||||
on-scroll-down = "previous";
|
||||
};
|
||||
};
|
||||
|
||||
wireplumber = {
|
||||
order = 40;
|
||||
settings = {
|
||||
format = "{icon} {volume}%";
|
||||
format-muted = "${util.glyph "f026"} muted";
|
||||
format-icons = [
|
||||
(util.glyph "f026")
|
||||
(util.glyph "f027")
|
||||
(util.glyph "f028")
|
||||
];
|
||||
on-click = "${lib.getExe' pkgs.wireplumber "wpctl"} set-mute @DEFAULT_AUDIO_SINK@ toggle";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,29 +6,23 @@
|
||||
}:
|
||||
# Clipboard history: cliphist records every copy, picked back through rofi.
|
||||
let
|
||||
cfg = config.modules.desktop.utilities.clipboard;
|
||||
user = config.user.name;
|
||||
|
||||
cliphist = "${pkgs.cliphist}/bin/cliphist";
|
||||
rofi = "${pkgs.rofi}/bin/rofi";
|
||||
wl-copy = "${pkgs.wl-clipboard}/bin/wl-copy";
|
||||
|
||||
# The picker reuses the themed rofi, so history looks like every other menu
|
||||
# the launcher drives.
|
||||
# decode is needed because list emits id-prefixed lines rather than the copied
|
||||
# bytes, so the chosen id has to be resolved back before it can be re-copied.
|
||||
picker = pkgs.writeShellScript "clipboard-picker" ''
|
||||
${cliphist} list \
|
||||
| ${rofi} -dmenu -i -p Clipboard \
|
||||
| ${cliphist} decode \
|
||||
| ${wl-copy}
|
||||
${lib.getExe pkgs.cliphist} list \
|
||||
| ${lib.getExe pkgs.rofi} -dmenu -i -p Clipboard \
|
||||
| ${lib.getExe pkgs.cliphist} decode \
|
||||
| ${lib.getExe' pkgs.wl-clipboard "wl-copy"}
|
||||
'';
|
||||
in
|
||||
{
|
||||
options.modules.desktop.utilities.clipboard.enable = lib.mkEnableOption "cliphist clipboard history";
|
||||
options.modules.desktop.utilities.clipboard.enable =
|
||||
lib.mkEnableOption "cliphist clipboard history";
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
home-manager.users.${user} = {
|
||||
config = lib.mkIf config.modules.desktop.utilities.clipboard.enable {
|
||||
home-manager.users.${config.user.name} = {
|
||||
# wl-copy and wl-paste on PATH, so the shell can pipe into and out of the
|
||||
# clipboard.
|
||||
# The watchers and picker above reach wl-clipboard by store path, so this
|
||||
@@ -38,11 +32,14 @@ in
|
||||
# Two watchers record text and images to history, bound to the graphical
|
||||
# session so uwsm starts and stops them with it.
|
||||
services.cliphist.enable = true;
|
||||
};
|
||||
|
||||
# $mod is defined by the compositor config these binds share.
|
||||
wayland.windowManager.hyprland.settings.bind = [
|
||||
"$mod SHIFT, V, exec, ${picker}"
|
||||
];
|
||||
registry.keybind.${config.networking.hostName}.clipboard-history = {
|
||||
keys = "SUPER + SHIFT + V";
|
||||
action = {
|
||||
kind = "exec";
|
||||
command = "${picker}";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,13 +6,60 @@
|
||||
}:
|
||||
# SwayOSD: a transient on-screen popup for volume and brightness changes.
|
||||
let
|
||||
cfg = config.modules.desktop.utilities.osd;
|
||||
user = config.user.name;
|
||||
in
|
||||
{
|
||||
options.modules.desktop.utilities.osd.enable = lib.mkEnableOption "the SwayOSD volume and brightness popup";
|
||||
options.modules.desktop.utilities.osd.enable =
|
||||
lib.mkEnableOption "the SwayOSD volume and brightness popup";
|
||||
|
||||
config = lib.mkIf config.modules.desktop.utilities.osd.enable {
|
||||
registry.keybind.${config.networking.hostName} = {
|
||||
volume-up = {
|
||||
keys = "XF86AudioRaiseVolume";
|
||||
repeating = true;
|
||||
action = {
|
||||
kind = "exec";
|
||||
command = "${pkgs.swayosd}/bin/swayosd-client --output-volume raise --max-volume 100";
|
||||
};
|
||||
};
|
||||
|
||||
volume-down = {
|
||||
keys = "XF86AudioLowerVolume";
|
||||
repeating = true;
|
||||
action = {
|
||||
kind = "exec";
|
||||
command = "${pkgs.swayosd}/bin/swayosd-client --output-volume lower";
|
||||
};
|
||||
};
|
||||
|
||||
brightness-up = {
|
||||
keys = "XF86MonBrightnessUp";
|
||||
repeating = true;
|
||||
action = {
|
||||
kind = "exec";
|
||||
command = "${pkgs.swayosd}/bin/swayosd-client --brightness raise";
|
||||
};
|
||||
};
|
||||
|
||||
brightness-down = {
|
||||
keys = "XF86MonBrightnessDown";
|
||||
repeating = true;
|
||||
action = {
|
||||
kind = "exec";
|
||||
command = "${pkgs.swayosd}/bin/swayosd-client --brightness lower";
|
||||
};
|
||||
};
|
||||
|
||||
mute = {
|
||||
keys = "XF86AudioMute";
|
||||
locked = true;
|
||||
action = {
|
||||
kind = "exec";
|
||||
command = "${pkgs.swayosd}/bin/swayosd-client --output-volume mute-toggle";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
environment.systemPackages = [ pkgs.swayosd ];
|
||||
|
||||
# The udev rule chgrps each backlight's brightness node to `video` and adds
|
||||
|
||||
@@ -2,22 +2,11 @@
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
util,
|
||||
...
|
||||
}:
|
||||
# Keybound screen recording: pick a region, then toggle a video-only capture.
|
||||
let
|
||||
cfg = config.modules.desktop.utilities.recording;
|
||||
user = config.user.name;
|
||||
|
||||
slurp = "${pkgs.slurp}/bin/slurp";
|
||||
wf-recorder = "${pkgs.wf-recorder}/bin/wf-recorder";
|
||||
notify-send = "${pkgs.libnotify}/bin/notify-send";
|
||||
pgrep = "${pkgs.procps}/bin/pgrep";
|
||||
pkill = "${pkgs.procps}/bin/pkill";
|
||||
date = "${pkgs.coreutils}/bin/date";
|
||||
mkdir = "${pkgs.coreutils}/bin/mkdir";
|
||||
xdgUserDir = "${pkgs.xdg-user-dirs}/bin/xdg-user-dir";
|
||||
|
||||
# A single key both starts and stops.
|
||||
# A running wf-recorder is stopped with SIGINT so it finalises the file.
|
||||
# Otherwise slurp picks a region and wf-recorder runs in the foreground until
|
||||
@@ -25,30 +14,51 @@ let
|
||||
# reports it saved.
|
||||
# No -a means no audio is captured.
|
||||
toggle = pkgs.writeShellScript "screen-record-toggle" ''
|
||||
if ${pgrep} -x wf-recorder >/dev/null; then
|
||||
${pkill} -INT -x wf-recorder
|
||||
if ${lib.getExe' pkgs.procps "pgrep"} -x wf-recorder >/dev/null; then
|
||||
${lib.getExe' pkgs.procps "pkill"} -INT -x wf-recorder
|
||||
exit 0
|
||||
fi
|
||||
|
||||
region=$(${slurp}) || exit 0
|
||||
dir="$(${xdgUserDir} VIDEOS)/Recordings"
|
||||
${mkdir} -p "$dir"
|
||||
file="$dir/recording-$(${date} +%Y%m%d-%H%M%S).mp4"
|
||||
region=$(${lib.getExe pkgs.slurp}) || exit 0
|
||||
dir="$(${lib.getExe' pkgs.xdg-user-dirs "xdg-user-dir"} VIDEOS)/Recordings"
|
||||
${lib.getExe' pkgs.coreutils "mkdir"} -p "$dir"
|
||||
file="$dir/recording-$(${lib.getExe' pkgs.coreutils "date"} +%Y%m%d-%H%M%S).mp4"
|
||||
|
||||
${notify-send} -a "Screen recording" "Recording started" "Region capture, no audio."
|
||||
${wf-recorder} -g "$region" -f "$file"
|
||||
${notify-send} -a "Screen recording" "Recording saved" "$file"
|
||||
${lib.getExe' pkgs.libnotify "notify-send"} -a "Screen recording" "Recording started" "Region capture, no audio."
|
||||
${lib.getExe pkgs.wf-recorder} -g "$region" -f "$file"
|
||||
${lib.getExe' pkgs.libnotify "notify-send"} -a "Screen recording" "Recording saved" "$file"
|
||||
'';
|
||||
|
||||
# Lit while a wf-recorder capture is running.
|
||||
# Empty text collapses the widget when idle, so it shows nothing until then.
|
||||
recordingStatus = pkgs.writeShellScript "waybar-recording-status" ''
|
||||
if ${lib.getExe' pkgs.procps "pgrep"} -x wf-recorder >/dev/null; then
|
||||
printf '{"text":"${util.glyph "f03d"}","tooltip":"Recording","class":"recording"}\n'
|
||||
else
|
||||
printf '{"text":"","class":"idle"}\n'
|
||||
fi
|
||||
'';
|
||||
in
|
||||
{
|
||||
options.modules.desktop.utilities.recording.enable = lib.mkEnableOption "wf-recorder screen recording";
|
||||
options.modules.desktop.utilities.recording.enable =
|
||||
lib.mkEnableOption "wf-recorder screen recording";
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
home-manager.users.${user} = {
|
||||
# $mod is defined by the compositor config these binds share.
|
||||
wayland.windowManager.hyprland.settings.bind = [
|
||||
"$mod SHIFT, R, exec, ${toggle}"
|
||||
];
|
||||
config = lib.mkIf config.modules.desktop.utilities.recording.enable {
|
||||
registry.keybind.${config.networking.hostName}.recording-toggle = {
|
||||
keys = "SUPER + SHIFT + R";
|
||||
action = {
|
||||
kind = "exec";
|
||||
command = "${toggle}";
|
||||
};
|
||||
};
|
||||
|
||||
registry.waybar.${config.networking.hostName}."custom/recording" = {
|
||||
order = 10;
|
||||
settings = {
|
||||
return-type = "json";
|
||||
exec = "${recordingStatus}";
|
||||
interval = 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,25 +4,26 @@
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
# Keyboard-driven screenshots that open in satty for annotation, then land in
|
||||
# both the clipboard and a dated file.
|
||||
# Keyboard-driven screenshots copy to the clipboard before Satty opens for
|
||||
# annotation. Satty copies annotated screenshots when it saves them.
|
||||
let
|
||||
cfg = config.modules.desktop.utilities.screenshot;
|
||||
user = config.user.name;
|
||||
|
||||
grimblast = "${pkgs.grimblast}/bin/grimblast";
|
||||
satty = "${pkgs.satty}/bin/satty";
|
||||
tee = "${pkgs.coreutils}/bin/tee";
|
||||
wl-copy = "${pkgs.wl-clipboard}/bin/wl-copy";
|
||||
xdgUserDir = "${pkgs.xdg-user-dirs}/bin/xdg-user-dir";
|
||||
|
||||
# satty's copy action also saves, so one keystroke lands the shot in both the
|
||||
# clipboard and a file.
|
||||
# Copy the original screenshot before Satty opens.
|
||||
# Satty copies and saves annotated screenshots when the user presses Enter.
|
||||
capture =
|
||||
target:
|
||||
pkgs.writeShellScript "screenshot-${target}" ''
|
||||
dir="$(${xdgUserDir} PICTURES)/Screenshots"
|
||||
mkdir -p "$dir"
|
||||
${grimblast} save ${target} - \
|
||||
| ${tee} >(${wl-copy}) \
|
||||
| ${satty} --filename - \
|
||||
--output-filename "$dir/screenshot-%Y%m%d-%H%M%S.png" \
|
||||
--copy-command ${wl-copy} \
|
||||
@@ -32,26 +33,46 @@ let
|
||||
'';
|
||||
in
|
||||
{
|
||||
options.modules.desktop.utilities.screenshot.enable = lib.mkEnableOption "screenshots via grimblast and satty";
|
||||
options.modules.desktop.utilities.screenshot.enable =
|
||||
lib.mkEnableOption "screenshots via grimblast and satty";
|
||||
|
||||
config = lib.mkIf config.modules.desktop.utilities.screenshot.enable {
|
||||
registry.keybind.${config.networking.hostName} = {
|
||||
screenshot-area = {
|
||||
keys = "Print";
|
||||
action = {
|
||||
kind = "exec";
|
||||
command = "${capture "area"}";
|
||||
};
|
||||
};
|
||||
|
||||
screenshot-active = {
|
||||
keys = "SHIFT + Print";
|
||||
action = {
|
||||
kind = "exec";
|
||||
command = "${capture "active"}";
|
||||
};
|
||||
};
|
||||
|
||||
screenshot-screen = {
|
||||
keys = "CTRL + Print";
|
||||
action = {
|
||||
kind = "exec";
|
||||
command = "${capture "screen"}";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
home-manager.users.${user} = {
|
||||
# satty is a one-shot annotation surface, so float it rather than letting
|
||||
# it claim a tile in the layout.
|
||||
# windowrule v3 syntax: space-separated field/value tokens, comma
|
||||
# separated, with matchers under a match: prefix and effects bare.
|
||||
wayland.windowManager.hyprland.settings.windowrule = [
|
||||
"float 1, match:class ^(com\\.gabm\\.satty)$"
|
||||
wayland.windowManager.hyprland.settings.window_rule = [
|
||||
{
|
||||
match.class = "^(com\\.gabm\\.satty)$";
|
||||
float = true;
|
||||
}
|
||||
];
|
||||
|
||||
# Print with plain/Shift/Ctrl for region/window/full.
|
||||
# Super+L is already the hjkl focus and movement bind, so screenshots take
|
||||
# the Print key instead.
|
||||
wayland.windowManager.hyprland.settings.bind = [
|
||||
", Print, exec, ${capture "area"}"
|
||||
"SHIFT, Print, exec, ${capture "active"}"
|
||||
"CTRL, Print, exec, ${capture "screen"}"
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
}:
|
||||
# Nord theming for the graphical layer, driven from one base16 scheme by Stylix.
|
||||
let
|
||||
cfg = config.modules.desktop.utilities.theming;
|
||||
user = config.user.name;
|
||||
|
||||
# A single static Nord wallpaper, drawn as a vertical gradient across the
|
||||
# Polar Night shades so it needs no committed binary and no network fetch.
|
||||
@@ -16,12 +14,13 @@ let
|
||||
-size 2560x1440 gradient:'#2E3440'-'#3B4252' \
|
||||
"$out"
|
||||
'';
|
||||
user = config.user.name;
|
||||
in
|
||||
{
|
||||
options.modules.desktop.utilities.theming.enable =
|
||||
lib.mkEnableOption "Nord theming of the graphical layer via Stylix";
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
config = lib.mkIf config.modules.desktop.utilities.theming.enable {
|
||||
stylix = {
|
||||
enable = true;
|
||||
|
||||
@@ -59,6 +58,10 @@ in
|
||||
};
|
||||
};
|
||||
};
|
||||
registry.env.${config.networking.hostName} = {
|
||||
HYPRCURSOR_THEME = config.stylix.cursor.name;
|
||||
HYPRCURSOR_SIZE = toString config.stylix.cursor.size;
|
||||
};
|
||||
|
||||
home-manager.users.${user} = {
|
||||
# Stylix drives the cursor through home-manager's pointer-cursor config,
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
# Declares the XDG user directories, so a tool that queries them lands in a
|
||||
# well-known folder a host can relocate from one place.
|
||||
let
|
||||
cfg = config.modules.desktop.utilities.userdirs;
|
||||
user = config.user.name;
|
||||
in
|
||||
{
|
||||
options.modules.desktop.utilities.userdirs.enable = lib.mkEnableOption "XDG user directories";
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
config = lib.mkIf config.modules.desktop.utilities.userdirs.enable {
|
||||
# Write ~/.config/user-dirs.dirs from the option defaults.
|
||||
home-manager.users.${user}.xdg.userDirs.enable = true;
|
||||
};
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
cfg = config.modules.guest;
|
||||
in
|
||||
{
|
||||
options.modules.guest.enable = lib.mkEnableOption "nested-guest base configuration";
|
||||
options.modules.guest.enable = lib.mkEnableOption "nested guest configuration";
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
system.stateVersion = "26.05";
|
||||
|
||||
modules.network.ssh.enable = lib.mkDefault true;
|
||||
config = lib.mkIf config.modules.guest.enable {
|
||||
modules.network.ssh.enable = lib.mkDefault false;
|
||||
modules.network.ssh.hostKeys.restore = lib.mkDefault false;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.modules.host;
|
||||
user = config.user;
|
||||
passwordSecret = "${user.name}-password";
|
||||
in
|
||||
{
|
||||
options.modules.host.enable = lib.mkEnableOption "physical-host base configuration";
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
nix.settings.extra-substituters = [ "https://nyx-cache.chaotic.cx/" ];
|
||||
nix.settings.extra-trusted-public-keys = [
|
||||
"nyx-cache.chaotic.cx:dJxTrgMC3V3cFfyIiBQDQorG6k1LsqurH/srpMSq7qk="
|
||||
];
|
||||
|
||||
nix.gc.automatic = true;
|
||||
nix.gc.dates = "Mon 03:15";
|
||||
nix.gc.options = "--delete-older-than 30d";
|
||||
|
||||
nix.optimise.automatic = true;
|
||||
nix.optimise.dates = [ "Thu 03:45" ];
|
||||
|
||||
boot.loader.systemd-boot.configurationLimit = 15;
|
||||
|
||||
services.xserver.xkb.layout = "us";
|
||||
services.xserver.xkb.options = "caps:escape_shifted_capslock";
|
||||
console.useXkbConfig = true;
|
||||
|
||||
sops.defaultSopsFile = ../secrets/shared.yaml;
|
||||
sops.age.keyFile = "/var/lib/sops-nix/key.txt";
|
||||
sops.age.sshKeyPaths = [ ];
|
||||
sops.gnupg.sshKeyPaths = [ ];
|
||||
|
||||
users.mutableUsers = false;
|
||||
sops.secrets.${passwordSecret}.neededForUsers = true;
|
||||
users.users.${user.name}.hashedPasswordFile = config.sops.secrets.${passwordSecret}.path;
|
||||
};
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user