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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Pass exactly one bare domain. The client supports Node.js `>=20.19.0`; CI verifi
- [Agent skill](SKILL.md)
- [Machine-readable guidance](llms.txt)
- [Discovery examples](examples/)
- [Launch demo](examples/launch/README.md)

## Approved use cases

Expand Down Expand Up @@ -58,4 +59,4 @@ The client in this repository is MIT-licensed. The hosted aggregate service at `

## Contribution and release notes

See `CONTRIBUTING.md` for checks and contribution boundaries. CI runs smoke/contract checks on Node 20.x and 22.x. The published scoped npm release is `@replynodes/brand-kit@0.1.1`; v0.1.2 is a separate keyword patch.
See `CONTRIBUTING.md` for checks and contribution boundaries. CI runs smoke/contract checks on Node 20.x and 22.x. The published scoped npm release is `@replynodes/brand-kit@0.1.2`.
33 changes: 33 additions & 0 deletions examples/launch/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Launch demo

Run the published package against a public domain:

```sh
npx --yes @replynodes/brand-kit@0.1.2 <domain>
```

The terminal smoke demo runs the same command in a temporary directory and checks the result:

```sh
./examples/launch/terminal-demo.sh
./examples/launch/terminal-demo.sh vercel.com
```

It creates exactly six artifacts in `./brand/`:

- `DESIGN.md` — concise human-readable design guidance.
- `brand.json` — the normalized domain and brand URL.
- `colors.json` — referenced color data.
- `fonts.json` — referenced font data.
- `logos.json` — referenced logo references.
- `tokens.css` — CSS custom properties for the available tokens.

The launch is limited to public domains, Node.js `>=20.19.0`, and one HTTPS aggregate request. This is references-only: optional signals can be unavailable, and the client does not download logos, fonts, or other binary assets. It requires no signup, API key, cookies, or telemetry. An existing `./brand` directory is not overwritten.

Any recorded output must come from the script or a live run; do not fabricate output.

Suggested launch copy:

> Generate a deterministic, agent-ready brand context package from a public domain with one command: `npx --yes @replynodes/brand-kit@0.1.2 example.com`. It writes six references-only brand artifacts for agents, design handoff, and CSS/token work.

This is a demo only; do not submit it externally from this repository.
96 changes: 96 additions & 0 deletions examples/launch/terminal-demo.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#!/usr/bin/env bash

set -euo pipefail

if (( $# > 1 )); then
printf 'Usage: %s [domain]\n' "$0" >&2
exit 2
fi

domain=${1:-linear.app}
work_dir=$(mktemp -d)
npm_user_config="$work_dir/npmrc"
npm_cache="$work_dir/npm-cache"

cleanup() {
rm -rf "$work_dir"
}
trap cleanup EXIT

: > "$npm_user_config"

printf 'Running in temporary directory: %s\n' "$work_dir"
printf '+ npx --yes @replynodes/brand-kit@0.1.2 %q\n' "$domain"

set +e
(
cd "$work_dir"
npm_config_userconfig="$npm_user_config" \
npm_config_cache="$npm_cache" \
npm_config_audit=false \
npm_config_fund=false \
npm_config_update_notifier=false \
npm_config_progress=false \
timeout --signal=TERM --kill-after=5s 90s \
npx --yes @replynodes/brand-kit@0.1.2 "$domain"
)
status=$?
set -e

printf 'Command exit status: %d\n' "$status"
if (( status != 0 )); then
printf 'Error: launch command failed.\n' >&2
exit "$status"
fi

cd "$work_dir"
node --input-type=module - <<'NODE'
import { readdir, readFile } from 'node:fs/promises';

const expected = new Set([
'DESIGN.md',
'brand.json',
'colors.json',
'fonts.json',
'logos.json',
'tokens.css',
]);
const entries = await readdir('./brand', { withFileTypes: true });
const actual = entries.map((entry) => entry.name).sort();
const expectedNames = [...expected].sort();

if (entries.length !== expected.size || actual.some((name, index) => name !== expectedNames[index])) {
throw new Error(`./brand must contain exactly: ${expectedNames.join(', ')}`);
}
if (entries.some((entry) => !entry.isFile())) {
throw new Error('./brand must contain only regular files');
}

const readJson = async (name) => {
try {
return JSON.parse(await readFile(`./brand/${name}`, 'utf8'));
} catch (error) {
throw new Error(`could not parse ./brand/${name}: ${error.message}`);
}
};
const json = Object.fromEntries(
await Promise.all(['brand.json', 'colors.json', 'fonts.json', 'logos.json'].map(async (name) => [name, await readJson(name)])),
);

for (const [name, value] of Object.entries(json)) {
if (value.schema_version !== '0.1') {
throw new Error(`./brand/${name} must have schema_version "0.1"`);
}
}
if (typeof json['brand.json'].domain !== 'string' || typeof json['brand.json'].url !== 'string') {
throw new Error('./brand/brand.json must have string domain and url');
}
for (const name of ['colors.json', 'fonts.json', 'logos.json']) {
const key = name.slice(0, -5);
if (!Array.isArray(json[name][key])) {
throw new Error(`./brand/${name} must have an array at ${key}`);
}
}

console.log('Verified ./brand: exactly six regular files and the required JSON shape.');
NODE
Loading