Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

## 0.1.0-canary.4

- Use the vllnt logo as the Claude plugin icon: `assets/vllnt-logo.png` is wrapped by the generator into `.claude-plugin/icon.svg`.

- Remove distribution version numbers from the README so routine version bumps need no README edits; `VERSION` and this changelog remain the version records.

- Record the `v0.1.0-canary.3` GitHub prerelease.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Then start a new chat and invoke a namespaced skill such as `/vstack:plan-work`.
| `upstream.lock.json` | Trusted upstream repository and immutable commit |
| `VERSION` | Shared distribution version |
| `scripts/sync.py` | Standard-library generator and read-only drift check |
| `assets/claude-icon.svg` | Claude plugin icon, copied into the Claude package as `.claude-plugin/icon.svg` |
| `assets/vllnt-logo.png` | vllnt logo; the generator wraps it as the Claude package's `.claude-plugin/icon.svg` |
| `plugins/claude/` | Generated Claude Code native plugin |
| `plugins/codex/` | Generated portable Agent Plugin for Codex |
| `plugins/cursor/` | Generated Cursor native plugin and principles rule |
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.1.0-canary.3
0.1.0-canary.4
10 changes: 0 additions & 10 deletions assets/claude-icon.svg

This file was deleted.

Binary file added assets/vllnt-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 1 addition & 10 deletions plugins/claude/.claude-plugin/icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion plugins/claude/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@
"license": "MIT",
"name": "vstack",
"repository": "https://github.com/vllnt/stack",
"version": "0.1.0-canary.3"
"version": "0.1.0-canary.4"
}
4 changes: 2 additions & 2 deletions plugins/claude/SOURCE.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"files": {
".claude-plugin/icon.svg": "dc3b420724330eed1296c63c5c6d3290fa031d0eceb39180a2258f6992c76108",
".claude-plugin/plugin.json": "ff44d6e276f70392c84dcfa3cf86f33ce5001ab864781dae8adbadbc90102689",
".claude-plugin/icon.svg": "0b4b639f6df841b4904ec0cd9f6d38131c6373ce4a52a1e7fe6452a37598d7f1",
".claude-plugin/plugin.json": "4808262e4a7a3e327124bfd2b6e23590f33502b981cfcc4e893be02674ab4a53",
"LICENSE": "24ba061a4603738ddd5105ef4abc198eeded5ac7efce23b360561816f4dd6559",
"skills/build-landing-page/README.md": "46a280eadac8962180cf3f0c42eb0e845a3b0156a644569182d932881e1d51f8",
"skills/build-landing-page/SKILL.md": "b08e4963d6149f3647b28a0d9dccdc8793812abab95a1d081d93699e142de0f0",
Expand Down
2 changes: 1 addition & 1 deletion plugins/codex/SOURCE.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"files": {
"LICENSE": "24ba061a4603738ddd5105ef4abc198eeded5ac7efce23b360561816f4dd6559",
"plugin.json": "aea0fdbea75ce7f02be70116bf560bcffc31258b6def32b79d302f2aee307dd4",
"plugin.json": "789b793f660535ffddadde5268e5d776ea9a76db6b4809014eb71537d877f235",
"skills/build-landing-page/README.md": "46a280eadac8962180cf3f0c42eb0e845a3b0156a644569182d932881e1d51f8",
"skills/build-landing-page/SKILL.md": "b08e4963d6149f3647b28a0d9dccdc8793812abab95a1d081d93699e142de0f0",
"skills/build-landing-page/references/copywriting-formulas.md": "6d19f6166e1badcfdda0af4f8b303160e94ee6e6149321157e12849654cc013e",
Expand Down
2 changes: 1 addition & 1 deletion plugins/codex/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
"description": "Evidence-led engineering workflows to plan, build, review, and ship software with AI agents. Part of the vllnt universe: open, sovereign tools for freedom by design (vllnt.com).",
"license": "MIT",
"name": "vstack",
"version": "0.1.0-canary.3"
"version": "0.1.0-canary.4"
}
2 changes: 1 addition & 1 deletion plugins/cursor/.cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
"description": "Evidence-led engineering workflows to plan, build, review, and ship software with AI agents. Part of the vllnt universe: open, sovereign tools for freedom by design (vllnt.com).",
"license": "MIT",
"name": "vstack",
"version": "0.1.0-canary.3"
"version": "0.1.0-canary.4"
}
2 changes: 1 addition & 1 deletion plugins/cursor/SOURCE.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"files": {
".cursor-plugin/plugin.json": "3fbf8954a01a9f6a73fec13119d07eaa08e465fb3ac59c27cf711685cdd860f3",
".cursor-plugin/plugin.json": "b4325fd2a33221ef271b6b92d621c9c62052b6bd6f468b567d47401fe35ee2ea",
"LICENSE": "24ba061a4603738ddd5105ef4abc198eeded5ac7efce23b360561816f4dd6559",
"rules/vstack-principles.mdc": "5234bc0506e0a328d6163cd83785d2f37dd1fc23ef84287d71e03b92459e064a",
"skills/build-landing-page/README.md": "46a280eadac8962180cf3f0c42eb0e845a3b0156a644569182d932881e1d51f8",
Expand Down
19 changes: 17 additions & 2 deletions scripts/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from __future__ import annotations

import argparse
import base64
import gzip
import hashlib
import io
Expand All @@ -12,6 +13,7 @@
import re
import selectors
import shutil
import struct
import subprocess
import tarfile
import tempfile
Expand All @@ -24,7 +26,7 @@
HOMEPAGE = "https://vllnt.com"
DESCRIPTION = ("Evidence-led engineering workflows to plan, build, review, and ship software with AI agents. "
"Part of the vllnt universe: open, sovereign tools for freedom by design (vllnt.com).")
CLAUDE_ICON = ROOT / "assets" / "claude-icon.svg"
CLAUDE_ICON = ROOT / "assets" / "vllnt-logo.png"
HOSTS = ("claude", "codex", "cursor")
PRINCIPLES = tuple(f"vllnt-{name}-principles" for name in ("thinking", "orchestration", "collaboration"))
MAX_ARCHIVE = 32 * 1024 * 1024
Expand Down Expand Up @@ -217,6 +219,19 @@ def payload(files: dict[str, bytes]) -> tuple[dict[str, bytes], bytes]:
return skills, rule.encode()


def icon_svg(png: bytes) -> bytes:
"""Wrap a square PNG in the SVG file Claude's plugin directory looks for."""
if png[:8] != b"\x89PNG\r\n\x1a\n":
raise Invalid("Claude icon must be a PNG")
width, height = struct.unpack(">II", png[16:24])
if width != height or width < 128:
raise Invalid("Claude icon must be square and at least 128px")
data = base64.b64encode(png).decode()
return (f'<svg xmlns="http://www.w3.org/2000/svg" width="{width}" height="{height}" '
f'viewBox="0 0 {width} {height}"><image width="{width}" height="{height}" '
f'href="data:image/png;base64,{data}"/></svg>\n').encode()


def build(files: dict[str, bytes], lock: dict, version: str) -> dict[str, bytes]:
if not re.fullmatch(r"\d+\.\d+\.\d+(?:-[a-zA-Z0-9.-]+)?", version):
raise Invalid("VERSION must contain a semantic version")
Expand All @@ -235,7 +250,7 @@ def build(files: dict[str, bytes], lock: dict, version: str) -> dict[str, bytes]
"cursor": ".cursor-plugin/plugin.json"}[host]
package = {**skills, "LICENSE": files["LICENSE"], manifest_path: encoded(manifest)}
if host == "claude":
package[".claude-plugin/icon.svg"] = CLAUDE_ICON.read_bytes()
package[".claude-plugin/icon.svg"] = icon_svg(CLAUDE_ICON.read_bytes())
if host == "cursor":
package["rules/vstack-principles.mdc"] = rule
package["SOURCE.json"] = encoded({"upstream": lock, "files": {
Expand Down
9 changes: 9 additions & 0 deletions tests/test_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import io
import json
from pathlib import Path
import struct
import subprocess
import tarfile
import tempfile
Expand Down Expand Up @@ -54,6 +55,7 @@ def test_deterministic_standalone_packages_and_provenance(self):
self.assertEqual("rules/vstack-principles.mdc" in package, host == "cursor")
self.assertEqual(package["LICENSE"], files["LICENSE"])
self.assertEqual(".claude-plugin/icon.svg" in package, host == "claude")
self.assertIn(b"data:image/png;base64,", first["plugins/claude/.claude-plugin/icon.svg"])
claude = json.loads(first[".claude-plugin/marketplace.json"])
codex = json.loads(first[".agents/plugins/marketplace.json"])
cursor = json.loads(first[".cursor-plugin/marketplace.json"])
Expand All @@ -70,6 +72,13 @@ def test_deterministic_standalone_packages_and_provenance(self):
self.assertNotIn("repository", manifests["codex"])
self.assertNotIn("repository", manifests["cursor"])

def test_claude_icon_requires_square_png(self):
png = lambda w, h: b"\x89PNG\r\n\x1a\n" + b"\0\0\0\rIHDR" + struct.pack(">II", w, h)
self.assertIn(b'viewBox="0 0 256 256"', sync.icon_svg(png(256, 256)))
for bad in (png(256, 128), png(64, 64), b"<svg/>" + bytes(32)):
with self.assertRaises(sync.Invalid):
sync.icon_svg(bad)

def test_added_and_removed_workflows(self):
files = source()
files["workflows/another-task/SKILL.md"] = skill("another-task")
Expand Down
Loading