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
12 changes: 12 additions & 0 deletions apps/web/public/brand/npm-wordmark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 8 additions & 22 deletions apps/web/src/components/agent-setup-view/agent-setup-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,9 @@ export const agentSetupEntries: readonly AgentSetupEntry[] = [
icon: "/agents/codex.svg",
id: "codex",
name: "Codex",
notes: [
"Installs as a Codex plugin, so the skill is always a command away.",
"No account, API key, or local browser needed.",
],
notes: ["No account, API key, or local browser needed."],
status: "Plugin",
summary:
"Install the plugin, then ask Codex to diagram anything.",
summary: "Install the plugin, then ask Codex to diagram anything.",
tagline: "OpenAI's coding agent",
},
{
Expand All @@ -84,13 +80,9 @@ curl -fsSL ${portableSkillUrl} -o "$SKETCHI_SKILL_DIR/SKILL.md"`,
icon: "/agents/opencode.svg",
id: "opencode",
name: "OpenCode",
notes: [
"Setup is just the skill plus the public MCP server.",
"No account, API key, or local browser needed; provider and model behavior remain OpenCode concerns.",
],
notes: ["No account, API key, or local browser needed."],
status: "MCP",
summary:
"Point OpenCode at Sketchi and diagram from the terminal.",
summary: "Point OpenCode at Sketchi and diagram from the terminal.",
tagline: "Open-source terminal agent",
},
{
Expand Down Expand Up @@ -120,13 +112,9 @@ curl -fsSL ${portableSkillUrl} -o "$SKETCHI_SKILL_DIR/SKILL.md"`,
icon: "/agents/claude-code.svg",
id: "claude-code",
name: "Claude Code",
notes: [
"The plugin bundles the skill, so you can ask in plain language.",
"No account, API key, or local browser needed.",
],
notes: ["No account, API key, or local browser needed."],
status: "Plugin",
summary:
"Add the plugin, then turn any explanation into a diagram.",
summary: "Add the plugin, then turn any explanation into a diagram.",
tagline: "Anthropic's coding agent",
},
{
Expand All @@ -152,12 +140,10 @@ curl -fsSL ${portableSkillUrl} -o .agents/skills/sketchi-code-mode/SKILL.md`,
name: "Antigravity",
notes: [
"Save as .agents/mcp_config.json, merging the sketchi-code-mode server into any existing mcpServers object instead of overwriting it.",
"Setup is just the skill plus the public MCP server.",
"No account, API key, or local browser needed; model and harness behavior remain Agy concerns.",
"No account, API key, or local browser needed.",
],
status: "MCP",
summary:
"Add the skill and point Agy at the Code Mode server.",
summary: "Add the skill and point Agy at the Code Mode server.",
tagline: "Google's agent IDE",
},
] satisfies readonly AgentSetupEntry[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ describe("AgentSetupView", () => {
"href",
"http://localhost:3000/agents/opencode",
);
expect(
screen.getByText("https://playground.sketchi.app/mcp"),
).toBeTruthy();
expect(screen.getByText("https://playground.sketchi.app/mcp")).toBeTruthy();
expect(screen.queryByRole("link", { name: /Eval harness/ })).toBeNull();
expect(screen.queryByRole("link", { name: /Excalidraw app/ })).toBeNull();
});
Expand All @@ -41,7 +39,9 @@ describe("AgentSetupView", () => {
expect(
screen.getByText(/opencode\/skills\/sketchi-code-mode/),
).toBeTruthy();
expect(screen.getByText(/provider and model behavior/)).toBeTruthy();
expect(
screen.getByText("No account, API key, or local browser needed."),
).toBeTruthy();
});

it("renders the Codex plugin marketplace commands", () => {
Expand Down
9 changes: 4 additions & 5 deletions apps/web/src/components/agent-setup-view/agent-setup-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,11 @@ export function AgentSetupView({ agentId }: AgentSetupViewProps) {
return <AgentSetupHub />;
}

function EndpointCard({ meta }: { meta: string }) {
function EndpointCard() {
return (
<div className="agent-endpoint" aria-label="Sketchi server URL">
<span className="agent-endpoint__label">Server URL</span>
<code>{codeModeMcpEndpoint}</code>
<span className="agent-endpoint__meta">{meta}</span>
</div>
);
}
Expand All @@ -41,10 +40,10 @@ function AgentSetupHub() {
<h1>Sketch diagrams without leaving your agent.</h1>
<p>
Connect it once, then ask in plain language. Your agent hands back
a real, editable diagram, not a wall of ASCII.
a real, editable diagram.
</p>
</div>
<EndpointCard meta="Add this URL wherever your agent keeps its MCP config." />
<EndpointCard />
</div>
</section>

Expand Down Expand Up @@ -101,7 +100,7 @@ function AgentSetupDetail({ entry }: { entry: AgentSetupEntry }) {
</div>
<p>{entry.summary}</p>
</div>
<EndpointCard meta="Public endpoint used by the setup below." />
<EndpointCard />
</div>
</section>

Expand Down
39 changes: 24 additions & 15 deletions apps/web/src/components/cli-band/cli-band.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { describe, expect, it, vi } from "vitest";
import { CliBand } from "./cli-band";
import {
CLI_INSTALL_COMMAND,
CLI_NODE_REQUIREMENT,
CLI_NPM_INSTALL_COMMAND,
CLI_NPM_URL,
} from "../../lib/cli-package";
Expand Down Expand Up @@ -60,20 +61,29 @@ describe("CliBand", () => {
});

/**
* `generate`, `share`, and `pull` all make HTTPS requests. Only the other
* seven commands are offline, so a blanket "offline except generation" claim
* is false — and a wrong privacy claim is worse than a vague one.
* Generation needs the network, so counting the commands that do not is a
* claim nobody installs a CLI for. The band makes no offline claim at all
* now, and must not grow one back.
*/
it("does not overstate what works offline", () => {
it("makes no offline claim", () => {
const { container } = render(<CliBand />);

expect(container.textContent).not.toMatch(/offline/i);
});

/**
* Every install command carried a sentence explaining it. The commands are
* self-evident, so the terminal card is commands and the Node floor, nothing
* else.
*/
it("captions none of the commands", () => {
const { container } = render(<CliBand />);
const text = container.textContent ?? "";

expect(text).not.toMatch(/offline for everything but generation/);
expect(
screen.getByText(
"Create, patch, show, edit, list, export, and restore never touch the network or a model.",
[...container.querySelectorAll(".cli-band__body p")].map(
(element) => element.textContent,
),
).toBeTruthy();
).toEqual([`Requires ${CLI_NODE_REQUIREMENT}.`]);
});

it("links to the real npm package page with the npm mark", () => {
Expand All @@ -88,14 +98,13 @@ describe("CliBand", () => {
);
});

it("links to the CLI docs section", () => {
render(<CliBand />);
it("offers npm as the section's only trailing link", () => {
const { container } = render(<CliBand />);

expect(container.querySelectorAll(".cli-band__links a")).toHaveLength(1);
expect(
screen
.getByRole("link", { name: /Read the CLI docs/ })
.getAttribute("href"),
).toBe("/docs#cli");
screen.queryByRole("link", { name: /Read the CLI docs/ }),
).toBeNull();
});

it("is anchored so navigation and footer entries can reach it", () => {
Expand Down
44 changes: 10 additions & 34 deletions apps/web/src/components/cli-band/cli-band.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,20 @@ import {
CLI_NODE_REQUIREMENT,
CLI_NPM_URL,
CLI_PACKAGE_NAME,
cliCapabilities,
cliInstallOptions,
} from "../../lib/cli-package";

export interface CliBandProps {
/** Link to the published npm package. */
npmUrl?: string;
/** Where the fuller CLI writeup lives. */
docsHref?: string;
}

/**
* The third way in. The playground and the agent route both get a full section
* on this page; the terminal is a peer of both, so it gets one too — the
* install command is the section, copy-pasteable, not a link to go find it.
*/
export function CliBand({
docsHref = "/docs#cli",
npmUrl = CLI_NPM_URL,
}: CliBandProps) {
export function CliBand({ npmUrl = CLI_NPM_URL }: CliBandProps) {
return (
<section className="sk-section cli-band" id="cli">
<div className="sk-shell cli-band__inner">
Expand All @@ -33,30 +27,16 @@ export function CliBand({
Or never leave the terminal.
</h2>
<p className="cli-band__lead">
<code>{CLI_PACKAGE_NAME}</code> is a real command-line tool, not a
wrapper around this website. It keeps your diagrams in{" "}
<code>~/.sketchi/diagrams</code>, does its authoring, export, and
recovery work offline, and speaks <code>--output json</code> on
every command so an agent can drive it.
<code>{CLI_PACKAGE_NAME}</code> draws diagrams from your shell. No
account, no API key, and <code>--output json</code> on every command
so an agent can drive it.
</p>

<dl className="cli-band__points">
{cliCapabilities.map((capability) => (
<div className="cli-band__point" key={capability.title}>
<dt>{capability.title}</dt>
<dd>{capability.description}</dd>
</div>
))}
</dl>

<div className="cli-band__links">
<a className="sk-btn sk-btn--ghost cli-band__npm" href={npmUrl}>
<BrandIcon label="npm" size={18} src="/brand/npm.svg" />
View on npm
</a>
<a className="cli-band__docs-link" href={docsHref}>
Read the CLI docs →
</a>
</div>
</div>

Expand All @@ -72,19 +52,15 @@ export function CliBand({

<div className="cli-band__body">
{cliInstallOptions.map((option) => (
<div className="cli-band__step" key={option.command}>
<div className="code-snippet">
<pre className="docs-codeblock">
<code>{option.command}</code>
</pre>
<CopyButton label={option.label} value={option.command} />
</div>
<p className="cli-band__note">{option.detail}</p>
<div className="code-snippet" key={option.command}>
<pre className="docs-codeblock">
<code>{option.command}</code>
</pre>
<CopyButton label={option.label} value={option.command} />
</div>
))}

<p className="cli-band__then">Then ask for a diagram:</p>
<div className="code-snippet">
<div className="code-snippet cli-band__example">
<pre className="docs-codeblock">
<code>{CLI_EXAMPLE_COMMAND}</code>
</pre>
Expand Down
16 changes: 9 additions & 7 deletions apps/web/src/components/cta-band/cta-band.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ describe("CtaBand", () => {
).toHaveProperty("href", "http://localhost:3000/agents");
});

it("offers the CLI as the third closing route", () => {
render(<CtaBand />);
/**
* Three trailing routes out of one closing sheet is clutter. The playground
* is the primary, the agent hub is the one alternative, and the CLI keeps
* its own full section further up the page.
*/
it("closes on one primary action and one alternative", () => {
const { container } = render(<CtaBand />);

expect(
screen
.getByRole("link", { name: /install the CLI/ })
.getAttribute("href"),
).toBe("/#cli");
expect(container.querySelectorAll(".cta-band__actions a")).toHaveLength(2);
expect(screen.queryByRole("link", { name: /install the CLI/ })).toBeNull();
});
});
10 changes: 2 additions & 8 deletions apps/web/src/components/cta-band/cta-band.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,16 @@ export interface CtaBandProps {
playgroundHref?: string;
/** Link to the agent setup hub. */
agentsHref?: string;
/** Link to the CLI section. */
cliHref?: string;
}

/**
* Closing invitation — a warm, blank sketch sheet echoing the hero board.
* One clear next step, with the agent and terminal paths as quiet secondary
* links for readers the browser is not the answer for.
* One clear next step, and one quiet alternative for readers the browser is
* not the answer for. The CLI has its own section above.
*/
export function CtaBand({
playgroundHref = DEFAULT_WEB_SURFACE_URLS.playground,
agentsHref = "/agents",
cliHref = "/#cli",
}: CtaBandProps) {
return (
<section className="cta-band">
Expand All @@ -33,9 +30,6 @@ export function CtaBand({
<a className="cta-band__link" href={agentsHref}>
or add it to your coding agent →
</a>
<a className="cta-band__link" href={cliHref}>
or install the CLI →
</a>
</div>
</div>
</div>
Expand Down
Loading
Loading