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.4.0"
"version": "0.4.1"
},
"plugins": [
{
"name": "blaze",
"source": "./plugins/claude-code",
"description": "Retrieves a verified Solution Card after an agent prepares a privacy-bounded conceptual query.",
"version": "0.4.0",
"version": "0.4.1",
"keywords": [
"memory",
"retrieval",
Expand Down
2 changes: 1 addition & 1 deletion install.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Install Blaze 0.4.0
# Install Blaze 0.4.1

Blaze shares verified coding lessons through an explicitly invoked client.
Installing it does not authorize uploading prompts, source or transcripts.
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.4.0",
"version": "0.4.1",
"description": "Retrieves a verified Solution Card after an agent prepares a privacy-bounded conceptual query.",
"author": {
"name": "Blaze"
Expand Down
11 changes: 10 additions & 1 deletion plugins/claude-code/blaze-client.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
const TOKEN = /^blz_[A-Za-z0-9_-]{43}$/;
const CARD_ID = /^[a-z0-9][a-z0-9-]{2,62}$/;
const DEFAULT_ORIGIN = "https://blaze.pascal.app";
export const CLIENT_VERSION = "0.4.0";
export const CLIENT_VERSION = "0.4.1";
export const CLIENT_CONTRACT = 1;
export const CLIENT_TOOLS = ["claude", "codex", "opencode", "cursor", "openclaw", "agent"];
const RELEASE_FILES = ["SKILL.md", "blaze-client.mjs"];
Expand Down Expand Up @@ -644,6 +644,15 @@ async function locked(path, work) {
/** Explicit lifecycle operations. Hooks never call this function or fetch a release. */
export function createLifecycle({tool, home = homedir(), origin, helperPath = fileURLToPath(import.meta.url), fetchImpl = fetch}) {
const paths = toolPaths(tool, home);
const invokedRoot = resolve(dirname(helperPath));
// Hosts can discover another host's global copy. Only an already recorded
// direct bundle (or its interrupted transaction) can establish ownership.
// Credentials and receipts still belong to the invoking tool's state directory.
if (invokedRoot !== resolve(paths.root) && CLIENT_TOOLS.some(name => resolve(toolPaths(name,home).root) === invokedRoot)) {
const recordedState = join(home,".config/blaze/bundles",sha256(invokedRoot).slice(0,32));
homePath(home,invokedRoot);homePath(home,recordedState);
if (pathStat(join(recordedState,"installation.json")) || pathStat(join(recordedState,"transaction.json"))) paths.root = invokedRoot;
}
const base = trustedOrigin(origin ?? readToolCredential(tool, home, false).origin);
const bundleState = join(home, ".config/blaze/bundles", sha256(resolve(paths.root)).slice(0,32));
const metadataPath = join(bundleState, "installation.json"), journalPath = join(bundleState, "transaction.json");
Expand Down
7 changes: 6 additions & 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: 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 install, update, contribute to, or link Blaze. Check applicability, verify locally, and close the lookup with an honest outcome and contribution disposition.
compatibility: Requires Node.js 20 or later and explicit HTTPS access to the configured Blaze service. Local reminder hooks need no network access. No model provider credentials are needed.
metadata:
version: "0.4.0"
version: "0.4.1"
---

# Blaze
Expand All @@ -30,6 +30,11 @@ means that file. `<tool>` is the **agent host**, independent of its model provid
Check the active host's discovery rules. A profile may disable a shared root;
do not change its trust settings or other agents' configuration.

Some hosts discover more than one global copy. Lifecycle commands follow the
recorded direct bundle beside this helper, including that bundle's shared pin
and update lock. Credentials and receipts remain separate for each host. An
unrecorded or manager-owned copy still uses its owning manager.

## Start and stay current

When first using Blaze in a conversation, run `node <client> status --tool <tool>`.
Expand Down
11 changes: 10 additions & 1 deletion plugins/claude-code/skills/blaze/blaze-client.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
const TOKEN = /^blz_[A-Za-z0-9_-]{43}$/;
const CARD_ID = /^[a-z0-9][a-z0-9-]{2,62}$/;
const DEFAULT_ORIGIN = "https://blaze.pascal.app";
export const CLIENT_VERSION = "0.4.0";
export const CLIENT_VERSION = "0.4.1";
export const CLIENT_CONTRACT = 1;
export const CLIENT_TOOLS = ["claude", "codex", "opencode", "cursor", "openclaw", "agent"];
const RELEASE_FILES = ["SKILL.md", "blaze-client.mjs"];
Expand Down Expand Up @@ -644,6 +644,15 @@ async function locked(path, work) {
/** Explicit lifecycle operations. Hooks never call this function or fetch a release. */
export function createLifecycle({tool, home = homedir(), origin, helperPath = fileURLToPath(import.meta.url), fetchImpl = fetch}) {
const paths = toolPaths(tool, home);
const invokedRoot = resolve(dirname(helperPath));
// Hosts can discover another host's global copy. Only an already recorded
// direct bundle (or its interrupted transaction) can establish ownership.
// Credentials and receipts still belong to the invoking tool's state directory.
if (invokedRoot !== resolve(paths.root) && CLIENT_TOOLS.some(name => resolve(toolPaths(name,home).root) === invokedRoot)) {
const recordedState = join(home,".config/blaze/bundles",sha256(invokedRoot).slice(0,32));
homePath(home,invokedRoot);homePath(home,recordedState);
if (pathStat(join(recordedState,"installation.json")) || pathStat(join(recordedState,"transaction.json"))) paths.root = invokedRoot;
}
const base = trustedOrigin(origin ?? readToolCredential(tool, home, false).origin);
const bundleState = join(home, ".config/blaze/bundles", sha256(resolve(paths.root)).slice(0,32));
const metadataPath = join(bundleState, "installation.json"), journalPath = join(bundleState, "transaction.json");
Expand Down
11 changes: 10 additions & 1 deletion plugins/client/blaze-client.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
const TOKEN = /^blz_[A-Za-z0-9_-]{43}$/;
const CARD_ID = /^[a-z0-9][a-z0-9-]{2,62}$/;
const DEFAULT_ORIGIN = "https://blaze.pascal.app";
export const CLIENT_VERSION = "0.4.0";
export const CLIENT_VERSION = "0.4.1";
export const CLIENT_CONTRACT = 1;
export const CLIENT_TOOLS = ["claude", "codex", "opencode", "cursor", "openclaw", "agent"];
const RELEASE_FILES = ["SKILL.md", "blaze-client.mjs"];
Expand Down Expand Up @@ -644,6 +644,15 @@ async function locked(path, work) {
/** Explicit lifecycle operations. Hooks never call this function or fetch a release. */
export function createLifecycle({tool, home = homedir(), origin, helperPath = fileURLToPath(import.meta.url), fetchImpl = fetch}) {
const paths = toolPaths(tool, home);
const invokedRoot = resolve(dirname(helperPath));
// Hosts can discover another host's global copy. Only an already recorded
// direct bundle (or its interrupted transaction) can establish ownership.
// Credentials and receipts still belong to the invoking tool's state directory.
if (invokedRoot !== resolve(paths.root) && CLIENT_TOOLS.some(name => resolve(toolPaths(name,home).root) === invokedRoot)) {
const recordedState = join(home,".config/blaze/bundles",sha256(invokedRoot).slice(0,32));
homePath(home,invokedRoot);homePath(home,recordedState);
if (pathStat(join(recordedState,"installation.json")) || pathStat(join(recordedState,"transaction.json"))) paths.root = invokedRoot;
}
const base = trustedOrigin(origin ?? readToolCredential(tool, home, false).origin);
const bundleState = join(home, ".config/blaze/bundles", sha256(resolve(paths.root)).slice(0,32));
const metadataPath = join(bundleState, "installation.json"), journalPath = join(bundleState, "transaction.json");
Expand Down
38 changes: 36 additions & 2 deletions plugins/client/lifecycle.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { createHash, randomUUID } from "node:crypto";
import { existsSync, mkdtempSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, symlinkSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { createClient, createLifecycle, compareVersions, toolPaths, validateRelease } from "./blaze-client.mjs";
import { CLIENT_VERSION, createClient, createLifecycle, compareVersions, toolPaths, validateRelease } from "./blaze-client.mjs";

const hash = value => createHash("sha256").update(value).digest("hex");
const source = readFileSync(new URL("./blaze-client.mjs",import.meta.url));
Expand Down Expand Up @@ -91,7 +91,7 @@ test("update, pin, rollback and uninstall preserve receipts and one credential",
await lifecycle.pin("0.4.0");control.release=bundle("0.5.0");
assert.equal((await lifecycle.checkUpdate()).update,"pinned");await assert.rejects(lifecycle.update(),/pinned/);
await lifecycle.pin(null);assert.equal((await lifecycle.update()).version,"0.5.0");
assert.equal(lifecycle.status().disk_version,"0.5.0");assert.equal(lifecycle.status().running_version,"0.4.0");
assert.equal(lifecycle.status().disk_version,"0.5.0");assert.equal(lifecycle.status().running_version,CLIENT_VERSION);
assert.equal((await lifecycle.rollback()).version,"0.4.0");assert.equal(lifecycle.status().pin,"0.4.0");
assert.deepEqual(get(join(paths.state,"receipts",`${id}.json`)),receipt);assert.equal(readFileSync(paths.token,"utf8"),secret);
assert.equal((await lifecycle.uninstall()).installation,"removed");assert.equal(existsSync(paths.root),false);
Expand Down Expand Up @@ -159,6 +159,40 @@ test("one bundle lock spans tools that share a discovery directory",async t=>{
await assert.rejects(createLifecycle({...options,tool:"cursor"}).update(),/Another Blaze operation/);assert.equal(requests.length,count);
});

test("a host discovering another recorded global bundle uses that bundle's pin and lock while retaining its own identity",async t=>{
const {lifecycle,state,options,paths,control,requests}=await fixture(t);await lifecycle.install();
const otherPaths=toolPaths("opencode",options.home);
const dedicated=createLifecycle({...options,tool:"opencode",helperPath:join(otherPaths.root,"blaze-client.mjs")});
await dedicated.install();
const credentials=[paths.token,otherPaths.token].map(path=>readFileSync(path,"utf8"));
const receiptId=randomUUID(),receipt={decision_id:receiptId};put(join(otherPaths.state,"receipts",`${receiptId}.json`),receipt);
const discovered=createLifecycle({...options,tool:"opencode"});
assert.equal(discovered.status().installation,"direct");
await discovered.pin("0.4.0");assert.equal(lifecycle.status().pin,"0.4.0");assert.equal(dedicated.status().pin,null);
control.release=bundle("0.5.0");await assert.rejects(discovered.update(),/pinned/);await discovered.pin(null);
put(join(state,"update.lock"),{pid:process.pid,nonce:randomUUID()});const before=requests.length;
await assert.rejects(discovered.update(),/Another Blaze operation/);assert.equal(requests.length,before);rmSync(join(state,"update.lock"));
assert.equal((await discovered.update()).version,"0.5.0");
assert.equal(lifecycle.status().disk_version,"0.5.0");assert.equal(dedicated.status().disk_version,"0.4.0");
assert.deepEqual([paths.token,otherPaths.token].map(path=>readFileSync(path,"utf8")),credentials);
assert.notEqual(get(paths.token).token,get(otherPaths.token).token);
assert.deepEqual(get(join(otherPaths.state,"receipts",`${receiptId}.json`)),receipt);
});

test("an unrecorded global copy cannot update a host's different recorded installation",async t=>{
const {options,requests}=await fixture(t),paths=toolPaths("opencode",options.home);
await createLifecycle({...options,tool:"opencode",helperPath:join(paths.root,"blaze-client.mjs")}).install();
const before=requests.length,unrecorded=createLifecycle({...options,tool:"opencode"});
assert.equal((await unrecorded.update()).installation,"managed_or_unrecorded");assert.equal(requests.length,before);
});

test("cross-host discovery preserves origin and symlink checks on recorded ownership",async t=>{
const {lifecycle,options,state,requests}=await fixture(t);await lifecycle.install();const before=requests.length;
assert.throws(()=>createLifecycle({...options,tool:"opencode",origin:"https://example.invalid"}).status(),/provenance/);
const metadata=join(state,"installation.json"),saved=join(state,"saved.json");renameSync(metadata,saved);symlinkSync(saved,metadata);
assert.throws(()=>createLifecycle({...options,tool:"opencode"}).status(),/symbolic links/);assert.equal(requests.length,before);
});

test("intentional requests cache fixed version hints; retired contracts outrank pins",async t=>{
const {lifecycle,paths,options}=await fixture(t);await lifecycle.install();await lifecycle.pin("0.4.0");
let count=0;const client=createClient({origin:options.origin,token:get(paths.token).token,stateDir:join(paths.state,"receipts"),freshnessPath:join(paths.state,"freshness.json"),tool:"codex",
Expand Down
6 changes: 3 additions & 3 deletions release.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"version": "0.4.0",
"created_at": "2026-09-07T00:00:00.000Z",
"updated_at": "2026-09-07T00:00:00.000Z",
"version": "0.4.1",
"created_at": "2026-09-07T08:58:37.955Z",
"updated_at": "2026-09-07T08:58:37.955Z",
"client_contract": 1,
"minimum_client_contract": 0
}
7 changes: 6 additions & 1 deletion skill.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: blaze
description: 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 install, update, contribute to, or link Blaze. Check applicability, verify locally, and close the lookup with an honest outcome and contribution disposition.
compatibility: Requires Node.js 20 or later and explicit HTTPS access to the configured Blaze service. Local reminder hooks need no network access. No model provider credentials are needed.
metadata:
version: "0.4.0"
version: "0.4.1"
---

# Blaze
Expand All @@ -30,6 +30,11 @@ means that file. `<tool>` is the **agent host**, independent of its model provid
Check the active host's discovery rules. A profile may disable a shared root;
do not change its trust settings or other agents' configuration.

Some hosts discover more than one global copy. Lifecycle commands follow the
recorded direct bundle beside this helper, including that bundle's shared pin
and update lock. Credentials and receipts remain separate for each host. An
unrecorded or manager-owned copy still uses its owning manager.

## Start and stay current

When first using Blaze in a conversation, run `node <client> status --tool <tool>`.
Expand Down
Loading