73 Commits

Author SHA1 Message Date
Alex Dunmow
ed365f9030 feat(plugin): add NormalizeTags helper and slug rules
Add NormalizeTags, TagMinLen, TagMaxLen, TagMaxCount constants and
tagSlugRe to plugin/mod.go. Full TDD: 8 tests covering happy path,
trim/lowercase, case-insensitive dedupe, empty-drop, bad-slug rejection,
boundary acceptance, cap enforcement, and nil input.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 15:20:24 +08:00
Alex Dunmow
ba87684696 feat(plugin): add RequiredIconPacks to PluginRegistration and ModPlugin
Lets plugins declare icon-pack dependencies (e.g. "tabler", "phosphor")
in plugin.mod and PluginRegistration. The CMS loader auto-installs
declared packs from the bundled registry before the plugin loads.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
v0.12.4
2026-06-07 15:14:15 +08:00
Alex Dunmow
c3c7b2d441 docs: follow CMS module rename
CMS Go module renamed from git.dev.alexdunmow.com/block/ninja to
git.dev.alexdunmow.com/block/cms (along with the git remote rename
on gitea). All import paths, string-literal rules, test fixtures,
and doc references updated; (historical 'ninja-orchestrator' refs
preserved). go mod tidy regenerated checksums.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 13:55:49 +08:00
Alex Dunmow
48c54814ec docs: update paths after 2026-06-06 repo consolidation
Repos consolidated under ~/src/blockninja/ parent (collection, not
monorepo). This repo moved from ~/src/core to ~/src/blockninja/core.
Updates historical plan/audit docs that referenced the old paths.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 13:02:51 +08:00
Alex Dunmow
af7f44c34d fix(plugin): drop VisibilityLabel and its proto import
When core/plugin imported core/internal/api/orchestrator/v1 for the
PluginVisibility enum, every consumer of core/plugin (including the
orchestrator) transitively pulled in core's generated bindings — and
those bindings register the same proto descriptors as the orchestrator's
own bindings, panicking at startup.

Move the label helper into the CLI's cmd package where it belongs;
core/plugin no longer references the proto package at all.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
v0.12.3
2026-06-04 20:58:09 +08:00
Alex Dunmow
7fc20a990b fix(cli): use renamed ListMyAccountsForCLI RPC
Tracks the shared proto rename that resolves the message-name collision
between PluginAuthService and AccountService.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
v0.12.2
2026-06-04 20:55:10 +08:00
Alex Dunmow
051253396c fix(proto): narrow core generation to plugin_registry.proto only
When the orchestrator imports `block/core/internal/api/orchestrator/v1`
transitively through other core packages and also generates its own
bindings for the same files (accounts.proto etc.), proto registration
panics at startup: "file ... is already registered". Tests in the
orchestrator confirmed this.

Fix:
- buf generate now uses --path to limit core's output to
  proto/orchestrator/v1/plugin_registry.proto (see new `make proto`).
- Adds a minimal MyAccount message and PluginAuthService.ListMyAccounts
  RPC to plugin_registry.proto (already pushed to block/proto) so the
  CLI's account picker no longer needs accounts.proto generated.
- CLI switches back to cli.Auth.ListMyAccounts; orchclient.Client drops
  the Account field.

Side effect: every previously-generated orchestrator/v1 binding besides
plugin_registry is removed from this module.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
v0.12.1
2026-06-04 20:53:44 +08:00
Alex Dunmow
35436581b9 feat(proto): consume canonical block/proto as a submodule
Remove core's local proto/ fork and pull the canonical block/proto repo in
as a submodule at the same path. buf.yaml now sources from the submodule's
orchestrator/v1 namespace; everything outside that (blockninja, helpdesk)
is excluded from generation.

This brings the orchestrator's local-only RPCs (PluginScopeService.ListMyPlugins,
PluginRegistryService.SubmitForReview, the full PluginModerationService) into
core's bindings — harmless surface area for the CLI, prerequisite for the
orchestrator to also stop forking the proto.

Side effect: the CLI's account picker now uses the canonical
AccountService.ListMyAccounts in accounts.proto rather than the duplicate
PluginAuthService.ListMyAccounts that lived only in core's fork. The
existing Account message uses Name (no DisplayName / Role), so the picker
output collapses to "slug — Name".

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
v0.12.0
2026-06-04 20:45:47 +08:00
Alex Dunmow
c390e16b5c build(make): auto-bump release version + distribute-sdk subcommand
- `make release` (no args) infers the next version from the last vX.Y.Z
  tag using conventional-commits: BREAKING/`!:` → major, `feat` → minor,
  otherwise → patch. LEVEL=major|minor|patch forces; VERSION=vX.Y.Z is
  the explicit escape hatch.
- New `distribute-sdk` subcommand commits + pushes the pin bump in each
  downstream. Surgical (commits go.mod + go.sum only) so any unrelated
  WIP in a downstream is left alone instead of getting swept into the
  commit. Repos without an origin remote land the commit locally.
- `release` now chains tag → push → update-sdk → distribute-sdk so one
  command takes the ecosystem from new commit to fully-distributed.
v0.11.1
2026-06-04 20:18:26 +08:00
Alex Dunmow
87910e22ff fix(make): check-sdk-pins matches single-line require form
The previous regex only matched indented entries inside a `require (...)`
block, so single-line requires (like blockninja-themes/lcars uses) were
reported as unpinned even when the version was correct.
2026-06-04 08:53:17 +08:00
Alex Dunmow
c4d00a11d9 build(make): release target + expand SDK_DOWNSTREAM_DIRS
- `make release VERSION=vX.Y.Z` checks the tree is clean, pushes HEAD,
  tags, pushes the tag, then runs update-sdk so every downstream repo's
  go.mod gets bumped in one shot.
- SDK_DOWNSTREAM_DIRS now includes orchestrator/backend and
  blockninja-themes/* (globbed), which were both missing previously.
v0.11.0
2026-06-04 08:49:28 +08:00
Alex Dunmow
7615bd92ca feat(cli): multi-account login, private-plugin SDK, publish dirty handling
- ninja login forces account selection (interactive when >1); creds now
  carry ActiveAccountID/Slug. New `ninja account` group.
- ninja plugin list / delete / delete-version split public vs active-account
  @private sections; `publish --private` is sticky in plugin.mod.
- GetPluginRequest gains active_account_id so @private resolution works
  alongside the public (scope, name) path.
- publish auto-commits a dirty plugin.mod (path-scoped, leaves other staged
  paths alone) so the bump→publish loop never trips the dirty check.
  --allow-dirty is replaced with --strict (default now ships dirty trees
  via stash-create).
- bump auto-commits its plugin.mod write with `bump to X.Y.Z`; --no-commit
  opts out.
- Design doc updated to match the new defaults.
2026-06-04 08:49:23 +08:00
Alex Dunmow
264116f44e feat(core): private-plugin SDK, PluginVisibility enum, and Go 1.26.4 bump
Add private-plugin RPCs (ListPrivatePlugins, DeletePrivatePlugin,
DeletePrivatePluginVersion, ListPrivatePluginInstallSites) and
ListMyAccounts to the proto/generated stubs; introduce PluginVisibility
enum replacing the loose string field; add ModPlugin.Private + Coords()
routing to @private/<name>@<version>; update ninja CLI to use
VisibilityLabel helper; bump go directive to 1.26.4 for ABI alignment.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-04 01:00:50 +08:00
Alex Dunmow
06cabd6eb9 fix(cli): init prompts default to existing plugin.mod values 2026-06-03 12:01:56 +08:00
Alex Dunmow
041a7c2e3f feat(core): plugin display_name + description; CLI prompts; SDK fields 2026-06-03 11:31:23 +08:00
Alex Dunmow
dae3aa918a feat(core): sync plugin_registry proto with canonical (DenyDevice, GetDeviceStatus) 2026-06-03 10:57:07 +08:00
Alex Dunmow
46e3389045 test(cli): cover emitPublishWarnings across all three warning paths
Proves the publish command's warning surface end-to-end: tracked-yet-
gitignored files, declared submodules, untracked files on --allow-dirty,
and that the dirty-tree abort suppresses the untracked warning.
2026-06-03 10:15:24 +08:00
Alex Dunmow
824d55a1fa refactor(cli): extract publish-time warnings into emitPublishWarnings
Pulls the three warning calls and the dirty-tree check out of the
publish RunE closure into a single helper so a refactor that drops one
warning can be caught by a fixture-based test.
2026-06-03 10:14:50 +08:00
Alex Dunmow
ea744888ae test(cli): lock in trailing newline on gitignored-tracked warning
Guards against a silent refactor from Fprintln to Fprint dropping the
terminating newline that downstream output relies on.
2026-06-03 10:14:06 +08:00
Alex Dunmow
3d62071f77 test(cli): cover autoCommitPluginMod detached HEAD and missing git
Adds coverage for two git-edge cases: commits land correctly on a
detached HEAD with the prior commit as parent, and an empty PATH
produces a git-mentioning error rather than a panic.
2026-06-03 10:13:53 +08:00
Alex Dunmow
e076a03c33 test(cli): cover publish-time warning helpers
Add fires/no-op pairs for gitignoredTrackedWarning, untrackedFilesWarning,
and submoduleWarning. The submodule case writes a hand-crafted .gitmodules
file rather than wiring real submodules — submodulePaths reads the file
directly so that's sufficient.
2026-06-03 09:13:00 +08:00
Alex Dunmow
fda01e81b5 refactor(cli): extract publish-time warnings into testable helpers
Pull the three inline warning blocks in newPluginPublishCmd —
gitignoredTrackedWarning, untrackedFilesWarning, submoduleWarning — into
package-private helpers that take a repo dir and an io.Writer. Output is
byte-identical to the previous inline code; this just makes them unit-
testable without driving the whole cobra command.
2026-06-03 09:12:56 +08:00
Alex Dunmow
421f5ee0cb test(cli): cover autoCommitPluginMod commit and no-op paths
autoCommitPluginMod runs `git status --porcelain plugin.mod` then commits if
dirty. Add two cases: dirty plugin.mod produces an "Add plugin.mod" commit,
and a clean state leaves HEAD unchanged. Uses t.Chdir to scope CWD to the
temp repo without polluting parent state.
2026-06-03 09:11:37 +08:00
Alex Dunmow
ee76d76dc6 test(cli): cover BuildSourceArchive dirty-tree stash-create path
The dirty-tree branch (where git stash create captures uncommitted tracked
changes) was untested. Add two cases: one asserting the archive contains
the dirty working-copy contents (not HEAD) and the working tree is not
mutated; another asserting untracked files are excluded — the contract
the --allow-dirty publish warning relies on.
2026-06-03 09:11:08 +08:00
Alex Dunmow
ab465ef07c fix(cli): run gitignore-tracked warning before dirty-check so it fires unconditionally 2026-06-03 08:59:41 +08:00
Alex Dunmow
137a50c932 fix(cli): warn when publishing a repo that contains submodules
`git archive` does not recurse into submodules, so a plugin shipping
vendored code via submodule produced a tarball where the submodule path
existed but was empty — silent failure. Now publish reads .gitmodules
and lists submodule paths to stderr with guidance to vendor or pack
them separately. The publish still proceeds, since the developer may
not actually need the submodule contents in the archive.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-03 08:57:46 +08:00
Alex Dunmow
c3cfa18ae0 fix(cli): warn about untracked files when publishing with --allow-dirty
`git stash create` only captures tracked content, so a developer using
--allow-dirty after creating new files (but forgetting to `git add`)
would ship a tarball missing them with no indication. Now publish lists
the untracked, non-ignored files to stderr and suggests `git add` when
--allow-dirty is in play.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-03 08:56:43 +08:00
Alex Dunmow
4c0104619e fix(cli): friendly error when publishing from a repo with no commits
Previously a brand-new repo (git init, no commits) surfaced `git stash
create: exit status 128: You do not have the initial commit yet` from
deep inside the archive helper. Now the publish flow detects this case
via `git rev-parse --verify HEAD` up front and prints "no commits in
repository; run `git add . && git commit` before publishing". Also
updates the init flow's hint to mention `git init && git commit` so
users aren't misled into thinking `git init` alone is enough.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-03 08:56:27 +08:00
Alex Dunmow
20a7b35e50 docs(sdk): document Coords scope normalisation and accept-both contract
Adds a doc comment to ModFile.Coords explaining the leading-@ trim and a
note on ModPlugin.Scope clarifying that consumers should trim "@" before
comparing. Locks in the contract with a test asserting both call shapes
produce the same display string.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-03 08:55:46 +08:00
Alex Dunmow
7af42c1c83 docs(uat): tick all 86 UAT boxes after end-to-end observation 2026-06-03 07:26:13 +08:00
Alex Dunmow
d1c194ce66 fix(cli): archive working tree (via stash create) so --allow-dirty publishes uncommitted plugin.mod 2026-06-03 07:07:10 +08:00
Alex Dunmow
139d9b8543 fix(sdk): Coords trims leading @ from scope so callers can store either form 2026-06-03 07:03:19 +08:00
Alex Dunmow
12afdbd25e fix(cli): store scope without @ in plugin.mod so Coords() yields single @ 2026-06-03 07:02:35 +08:00
Alex Dunmow
f232effe69 feat(cli): init prompts for kind and categories 2026-06-03 06:56:07 +08:00
Alex Dunmow
a79aa709c2 feat(core): mirror Plugin.kind + ListCategories proto regen 2026-06-03 01:44:26 +08:00
Alex Dunmow
08be22ec34 feat(sdk): add Kind and Categories to ModPlugin; writeMod emits them 2026-06-03 01:41:56 +08:00
Alex Dunmow
aafdc44f6f fix(cli): drop duplicated version in publish output (Coords already includes it) 2026-06-03 01:41:03 +08:00
Alex Dunmow
57a217f54d feat(cli): warn at publish when tracked files match .gitignore 2026-06-03 01:35:04 +08:00
Alex Dunmow
c825942c8d feat(cli): init auto-commits plugin.mod, drops ninja git remote 2026-06-03 01:34:42 +08:00
Alex Dunmow
e5b27f5a65 feat(cli): rewrite plugin publish to send tar.zst archive 2026-06-03 01:33:12 +08:00
Alex Dunmow
a827cda37a feat(core): mirror PublishVersionRequest archive bytes proto change 2026-06-03 01:27:16 +08:00
Alex Dunmow
31e7b72b49 feat(cli): add BuildSourceArchive for plugin publish tar.zst 2026-06-03 01:21:55 +08:00
Alex Dunmow
680cbe0160 chore(core): add klauspost/compress for plugin archive zstd 2026-06-03 01:19:51 +08:00
Alex Dunmow
e9bef5b065 docs: plan, UAT, and execution prompt for plugin tarball publish + categories 2026-06-03 01:18:13 +08:00
Alex Dunmow
2a76b30c51 feat(cli): scope subcommand, interactive scope prompt, bump+version helpers
Pre-existing CLI improvements ahead of the tarball-publish refactor:
- New top-level `ninja scope` command (create, list, set-default).
- `init` accepts no --scope: prompts from ListMyScopes or uses creds default.
- Plugin name prompted if not provided.
- `plugin bump <major|minor|patch>` writes the bumped version into plugin.mod.
- `plugin version` prints the current plugin.mod version.
- `login` prints a URL with ?user_code= so the link is one click.
- creds: HostCreds gains optional default_scope.
- plugin/version: ParseBaseSemver + BumpVersion helpers, with tests.
2026-06-03 01:18:11 +08:00
Alex Dunmow
1d9ca44f55 docs(spec): plugin publish (tarball) + categories design
Design for two coupled changes: drop git as the publish transport in
favour of tar.zst uploads, and add a first-class plugin kind plus a
configurable, validated category list.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-03 00:58:21 +08:00
Alex Dunmow
32c6528162 feat(templates): add HTMLComponent interface and first-class pongo2 engine
Decouple TemplateFunc from templ.Component by introducing a generic
HTMLComponent interface that both templ and pongo2 satisfy via Go
structural typing. Add a complete pongo2 rendering engine in
templates/pongo/ with page templates, block templates (with BlockContext
injection and icon processing), template overrides, and email wrappers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
v0.10.0
2026-06-02 23:07:11 +08:00
Alex Dunmow
7f4bce79c9 feat(cli): ninja CLI with login, plugin init/publish/status
Cobra scaffold, credentials store, Connect client, device-flow login,
whoami/logout, plugin init (creates + adds git remote), publish (tag + push
+ registry RPC), and status (list scopes/plugins). Proto copied from
orchestrator with buf codegen for client stubs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-01 23:55:01 +08:00
Alex Dunmow
7ff326ef25 feat(sdk): ModFile struct, ParseModFull, and tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-01 22:45:26 +08:00
Alex Dunmow
a5caf2d9e7 chore(sdk): add deps for plugin.mod parser and ninja CLI
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-01 22:44:55 +08:00