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: 2 additions & 2 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@
},
"metadata": {
"description": "Verified Solution Cards retrieved from an agent-authored conceptual problem statement.",
"version": "0.5.1"
"version": "0.5.2"
},
"plugins": [
{
"name": "blaze",
"source": "./plugins/claude-code",
"description": "Retrieves a verified Solution Card after an agent prepares a privacy-bounded conceptual query.",
"version": "0.5.1",
"version": "0.5.2",
"keywords": [
"memory",
"retrieval",
Expand Down
34 changes: 28 additions & 6 deletions install.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Install Blaze 0.5.1
# Install Blaze 0.5.2

Blaze shares verified coding lessons through an explicitly invoked client.
Installing it does not authorize uploading prompts, source or transcripts.
Expand Down Expand Up @@ -31,17 +31,30 @@ source commit, and the sizes and SHA-256 hashes of exactly two artifacts:
`SKILL.md` and `blaze-client.mjs`. Review the corresponding public source release
when deciding whether to trust it. A hash verifies bytes, not the publisher.

Download `{BLAZE_URL}/blaze-client.mjs` to a temporary private file over HTTPS,
with redirects disabled. Inspect it before running it. Do not pipe a remote
response into a shell. An example download is:
Copy the version and the helper's SHA-256 from that reviewed manifest into the
variables below. Download its immutable artifact to a temporary private file over
HTTPS with redirects disabled, then verify its hash before inspecting or running
it. Do not pipe a remote response into a shell.

```bash
umask 077
blaze_bootstrap_dir=$(mktemp -d)
curl --fail --silent --show-error --proto '=https' --max-redirs 0 --max-time 15 --max-filesize 524288 '{BLAZE_URL}/blaze-client.mjs' --output "$blaze_bootstrap_dir/blaze-client.mjs"
blaze_release_version='<reviewed version>'
blaze_client_sha256='<reviewed helper SHA-256>'
curl --fail --silent --show-error --proto '=https' --max-redirs 0 --max-time 15 --max-filesize 524288 "{BLAZE_URL}/releases/$blaze_release_version/$blaze_client_sha256/blaze-client.mjs" --output "$blaze_bootstrap_dir/blaze-client.mjs"
node --input-type=module - "$blaze_bootstrap_dir/blaze-client.mjs" "$blaze_client_sha256" <<'NODE'
import { readFileSync } from 'node:fs';
import { createHash } from 'node:crypto';
const [path, expected] = process.argv.slice(2);
if (!/^[0-9a-f]{64}$/.test(expected ?? '') ||
createHash('sha256').update(readFileSync(path)).digest('hex') !== expected) {
throw new Error('Bootstrap hash mismatch; do not execute this download.');
}
NODE
```

After reviewing the download, invoke it, replacing `codex` with the current host:
Continue only after the hash check succeeds and you have reviewed the download.
Invoke it, replacing `codex` with the current host:

```bash
node "$blaze_bootstrap_dir/blaze-client.mjs" install --tool codex --origin '{BLAZE_URL}'
Expand Down Expand Up @@ -98,6 +111,15 @@ API calls also receive small version hints. No prompt hook fetches metadata or
updates files. Updating requires authorization and respects pins. The direct
updater refuses to modify manager or marketplace installations.

The direct 0.5.0 and 0.5.1 updaters can receive HTTP 426 while checking the saved
installation, before activating a newer release.
For that recorded direct installation, follow the reviewed download and hash checks
under [Direct installation](#direct-installation), then run the freshly downloaded
helper with `install --tool <host> --origin '{BLAZE_URL}'`. It preserves the existing
credential, receipts, ownership record and pin; an active pin still blocks replacement.
Use the installed helper to unpin only when that change is separately authorized.
Manager and marketplace installations must still update through their manager.

Rollback restores the immediately preceding checked direct release and pins it.
The first upgrade from a legacy bundle cannot automatically roll back to the
older state layout; its private backup remains available for deliberate recovery.
Expand Down
2 changes: 1 addition & 1 deletion plugins/claude-code/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "https://anthropic.com/claude-code/plugin.schema.json",
"name": "blaze",
"displayName": "Blaze",
"version": "0.5.1",
"version": "0.5.2",
"description": "Retrieves a verified Solution Card after an agent prepares a privacy-bounded conceptual query.",
"author": {
"name": "Blaze"
Expand Down
8 changes: 5 additions & 3 deletions plugins/claude-code/blaze-client.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const TOKEN = /^blz_[A-Za-z0-9_-]{43}$/;
const CARD_ID = idPattern(ID_PREFIXES.card);
const AUTHORED_SLUG = /^[a-z0-9][a-z0-9-]{2,62}$/;
const DEFAULT_ORIGIN = "https://blaze.pascal.app";
export const CLIENT_VERSION = "0.5.1";
export const CLIENT_VERSION = "0.5.2";
export const CLIENT_CONTRACT = 2;
export const API_VERSION = "2026-09-07";
export const CLIENT_TOOLS = ["claude", "codex", "opencode", "cursor", "openclaw", "agent"];
Expand Down Expand Up @@ -786,6 +786,8 @@ export function createLifecycle({tool, home = homedir(), origin, helperPath = fi
return Buffer.concat(chunks,size);
}
const parseJSON = (value) => { try {return JSON.parse(value.toString("utf8"));} catch {throw new Error("Blaze returned invalid JSON");} };
const serviceHeaders = headers => ({...headers,
"Blaze-Version":API_VERSION,"Blaze-Client-Version":CLIENT_VERSION,"Blaze-Client-Contract":String(CLIENT_CONTRACT)});
async function release() { return validateRelease(parseJSON(await bytes("/api/skill-release",16*1024)),base); }
function ownedInvocation(meta) { return meta && resolve(dirname(helperPath)) === resolve(paths.root); }
function status() {
Expand Down Expand Up @@ -824,7 +826,7 @@ export function createLifecycle({tool, home = homedir(), origin, helperPath = fi
const existing = readToolCredential(tool,home);
if (existing.token) {
if (existing.origin!==base) throw new Error("Keep the existing credential with its original service");
const result = parseJSON(await bytes("/api/stats",32*1024,{headers:{authorization:`Bearer ${existing.token}`}}));
const result = parseJSON(await bytes("/api/stats",32*1024,{headers:serviceHeaders({authorization:`Bearer ${existing.token}`})}));
if (result?.cards!==null && (!Number.isSafeInteger(result?.cards)||result.cards<0)) throw new Error("Invalid service status");
return {credential:"reused"};
}
Expand All @@ -833,7 +835,7 @@ export function createLifecycle({tool, home = homedir(), origin, helperPath = fi
exactKeys(pending,new Set(["version","origin","token"]),"Pending registration");
if (pending.version!==1 || pending.origin!==base || !TOKEN.test(pending.token ?? "")) throw new Error("Pending registration belongs to another service or is invalid");
save(pendingPath,pending);
const data = parseJSON(await bytes("/api/installations",16*1024,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${pending.token}`,"Idempotency-Key":sha256(pending.token)},body:JSON.stringify({tool})}));
const data = parseJSON(await bytes("/api/installations",16*1024,{method:"POST",headers:serviceHeaders({"content-type":"application/json",authorization:`Bearer ${pending.token}`,"Idempotency-Key":sha256(pending.token)}),body:JSON.stringify({tool})}));
const installId = responseId(data, "install", "installation");
if (!TOKEN.test(data?.token ?? "") || !installId || data.bootstrap_contract!==2 || data.token!==pending.token) {
throw new Error("This service does not support retryable registration; keep the saved pending credential");
Expand Down
2 changes: 1 addition & 1 deletion plugins/claude-code/skills/blaze/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: blaze
description: Quietly reuse and improve verified coding lessons across agents. Use for a nontrivial debugging or implementation problem where an earlier solution could help, when a Blaze offer or receipt appears, or when the user asks to inspect, install, update, contribute to, or link Blaze. Check applicability, verify locally, and close the lookup with an honest outcome and contribution disposition without adding routine user-visible narration.
compatibility: Requires Node.js 20 or later and explicit HTTPS access to the configured Blaze service. Local model-context hooks need no network access. No model provider credentials are needed.
metadata:
version: "0.5.1"
version: "0.5.2"
---

# Blaze
Expand Down
8 changes: 5 additions & 3 deletions plugins/claude-code/skills/blaze/blaze-client.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const TOKEN = /^blz_[A-Za-z0-9_-]{43}$/;
const CARD_ID = idPattern(ID_PREFIXES.card);
const AUTHORED_SLUG = /^[a-z0-9][a-z0-9-]{2,62}$/;
const DEFAULT_ORIGIN = "https://blaze.pascal.app";
export const CLIENT_VERSION = "0.5.1";
export const CLIENT_VERSION = "0.5.2";
export const CLIENT_CONTRACT = 2;
export const API_VERSION = "2026-09-07";
export const CLIENT_TOOLS = ["claude", "codex", "opencode", "cursor", "openclaw", "agent"];
Expand Down Expand Up @@ -786,6 +786,8 @@ export function createLifecycle({tool, home = homedir(), origin, helperPath = fi
return Buffer.concat(chunks,size);
}
const parseJSON = (value) => { try {return JSON.parse(value.toString("utf8"));} catch {throw new Error("Blaze returned invalid JSON");} };
const serviceHeaders = headers => ({...headers,
"Blaze-Version":API_VERSION,"Blaze-Client-Version":CLIENT_VERSION,"Blaze-Client-Contract":String(CLIENT_CONTRACT)});
async function release() { return validateRelease(parseJSON(await bytes("/api/skill-release",16*1024)),base); }
function ownedInvocation(meta) { return meta && resolve(dirname(helperPath)) === resolve(paths.root); }
function status() {
Expand Down Expand Up @@ -824,7 +826,7 @@ export function createLifecycle({tool, home = homedir(), origin, helperPath = fi
const existing = readToolCredential(tool,home);
if (existing.token) {
if (existing.origin!==base) throw new Error("Keep the existing credential with its original service");
const result = parseJSON(await bytes("/api/stats",32*1024,{headers:{authorization:`Bearer ${existing.token}`}}));
const result = parseJSON(await bytes("/api/stats",32*1024,{headers:serviceHeaders({authorization:`Bearer ${existing.token}`})}));
if (result?.cards!==null && (!Number.isSafeInteger(result?.cards)||result.cards<0)) throw new Error("Invalid service status");
return {credential:"reused"};
}
Expand All @@ -833,7 +835,7 @@ export function createLifecycle({tool, home = homedir(), origin, helperPath = fi
exactKeys(pending,new Set(["version","origin","token"]),"Pending registration");
if (pending.version!==1 || pending.origin!==base || !TOKEN.test(pending.token ?? "")) throw new Error("Pending registration belongs to another service or is invalid");
save(pendingPath,pending);
const data = parseJSON(await bytes("/api/installations",16*1024,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${pending.token}`,"Idempotency-Key":sha256(pending.token)},body:JSON.stringify({tool})}));
const data = parseJSON(await bytes("/api/installations",16*1024,{method:"POST",headers:serviceHeaders({"content-type":"application/json",authorization:`Bearer ${pending.token}`,"Idempotency-Key":sha256(pending.token)}),body:JSON.stringify({tool})}));
const installId = responseId(data, "install", "installation");
if (!TOKEN.test(data?.token ?? "") || !installId || data.bootstrap_contract!==2 || data.token!==pending.token) {
throw new Error("This service does not support retryable registration; keep the saved pending credential");
Expand Down
8 changes: 5 additions & 3 deletions plugins/client/blaze-client.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const TOKEN = /^blz_[A-Za-z0-9_-]{43}$/;
const CARD_ID = idPattern(ID_PREFIXES.card);
const AUTHORED_SLUG = /^[a-z0-9][a-z0-9-]{2,62}$/;
const DEFAULT_ORIGIN = "https://blaze.pascal.app";
export const CLIENT_VERSION = "0.5.1";
export const CLIENT_VERSION = "0.5.2";
export const CLIENT_CONTRACT = 2;
export const API_VERSION = "2026-09-07";
export const CLIENT_TOOLS = ["claude", "codex", "opencode", "cursor", "openclaw", "agent"];
Expand Down Expand Up @@ -786,6 +786,8 @@ export function createLifecycle({tool, home = homedir(), origin, helperPath = fi
return Buffer.concat(chunks,size);
}
const parseJSON = (value) => { try {return JSON.parse(value.toString("utf8"));} catch {throw new Error("Blaze returned invalid JSON");} };
const serviceHeaders = headers => ({...headers,
"Blaze-Version":API_VERSION,"Blaze-Client-Version":CLIENT_VERSION,"Blaze-Client-Contract":String(CLIENT_CONTRACT)});
async function release() { return validateRelease(parseJSON(await bytes("/api/skill-release",16*1024)),base); }
function ownedInvocation(meta) { return meta && resolve(dirname(helperPath)) === resolve(paths.root); }
function status() {
Expand Down Expand Up @@ -824,7 +826,7 @@ export function createLifecycle({tool, home = homedir(), origin, helperPath = fi
const existing = readToolCredential(tool,home);
if (existing.token) {
if (existing.origin!==base) throw new Error("Keep the existing credential with its original service");
const result = parseJSON(await bytes("/api/stats",32*1024,{headers:{authorization:`Bearer ${existing.token}`}}));
const result = parseJSON(await bytes("/api/stats",32*1024,{headers:serviceHeaders({authorization:`Bearer ${existing.token}`})}));
if (result?.cards!==null && (!Number.isSafeInteger(result?.cards)||result.cards<0)) throw new Error("Invalid service status");
return {credential:"reused"};
}
Expand All @@ -833,7 +835,7 @@ export function createLifecycle({tool, home = homedir(), origin, helperPath = fi
exactKeys(pending,new Set(["version","origin","token"]),"Pending registration");
if (pending.version!==1 || pending.origin!==base || !TOKEN.test(pending.token ?? "")) throw new Error("Pending registration belongs to another service or is invalid");
save(pendingPath,pending);
const data = parseJSON(await bytes("/api/installations",16*1024,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${pending.token}`,"Idempotency-Key":sha256(pending.token)},body:JSON.stringify({tool})}));
const data = parseJSON(await bytes("/api/installations",16*1024,{method:"POST",headers:serviceHeaders({"content-type":"application/json",authorization:`Bearer ${pending.token}`,"Idempotency-Key":sha256(pending.token)}),body:JSON.stringify({tool})}));
const installId = responseId(data, "install", "installation");
if (!TOKEN.test(data?.token ?? "") || !installId || data.bootstrap_contract!==2 || data.token!==pending.token) {
throw new Error("This service does not support retryable registration; keep the saved pending credential");
Expand Down
Loading
Loading