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
1 change: 1 addition & 0 deletions .agents/skills/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@
- Keep these cross-repository workflows vendored and independently usable; never resolve a skill through a sibling checkout or Git submodule.
- Refresh the KB skills from one reviewed immutable `hraness/kb` release and the orchestration skill from the reviewed portable template, then validate the complete copied directories.
- Keep product-specific operating skills in the root `skills/` directory when present. Portable repository workflows belong here.
- Mark every portable repository workflow with `metadata.internal: true` so public `skills add hraness/atet` discovery exposes only the product-owned `atet` skill.
- Preserve upstream attribution and license notices for adapted public resources.
2 changes: 2 additions & 0 deletions .agents/skills/percolate-kb/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
---
name: percolate-kb
description: Review a hraness/kb Markdown vault for recurring ideas and missing structural connections, then promote evidence-backed concepts and typed relationships with the KB CLI. Use after materially adding or revising notes, when organizing an accumulated vault, or when an agent needs to turn repeated tags and prose references into an explicit queryable knowledge graph.
metadata:
internal: true
---

# Percolate concepts and relationships
Expand Down
2 changes: 2 additions & 0 deletions .agents/skills/phase-orchestrator/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
---
name: phase-orchestrator
description: Orchestrate phase-based implementation plans in Codex with dependency analysis, parallel sub-agent lanes, join gates, validation, review/fix loops, and final integration. Use when the user provides a plan, checklist, PRD, or phased task and explicitly asks Codex to execute it with phases, delegation, parallel agents, or orchestration in any repository.
metadata:
internal: true
---

# Phase Orchestrator
Expand Down
2 changes: 2 additions & 0 deletions .agents/skills/plan-kb/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
---
name: plan-kb
description: Create or evolve a durable Markdown plan inside a hraness/kb vault. Use when a user asks for an implementation plan, proposal, RFC, migration plan, execution audit, phased checklist, or an update to an existing plan's decisions, progress, review findings, verification evidence, or final result.
metadata:
internal: true
---

# Write a durable plan
Expand Down
2 changes: 2 additions & 0 deletions .agents/skills/query-kb/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
---
name: query-kb
description: Load scoped repository context, then search and navigate a hraness/kb Markdown vault with hybrid text retrieval, exact metadata, bounded links and typed relationships, backlinks, whole-vault graph reports, and optional Git provenance. Use when an agent needs the applicable repository instructions, rationale, prior knowledge, plans, captures, decisions, concepts, relationships, or evidence before answering, planning, or changing code.
metadata:
internal: true
---

# Query the knowledge base
Expand Down
2 changes: 2 additions & 0 deletions .agents/skills/refresh-kb/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
---
name: refresh-kb
description: Refresh and validate a hraness/kb Markdown knowledge graph after notes, concepts, typed relationships, attachments, repository scopes, or context mappings change. Use when an agent needs to maintain a managed or authored catalog, inspect graph and lifecycle findings, validate local artifacts and scope hubs, or complete a vault health check.
metadata:
internal: true
---

# Refresh a knowledge base
Expand Down
2 changes: 2 additions & 0 deletions .agents/skills/save-pdf-kb/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ description: >-
archive, extract, or turn a PDF into KB Markdown, especially scanned
documents and PDFs containing screenshots, Slack messages, charts, photos,
or a mix of native text and images.
metadata:
internal: true
---

# Save a PDF to the knowledge base
Expand Down
2 changes: 2 additions & 0 deletions .agents/skills/save-url-kb/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ description: >-
YouTube page, or another page already visible in their browser. Supports URL
capture, the current signed-in tab, temporary browser-profile snapshots,
saved HTML, media, evidence, honest completeness, and knowledge-base linking.
metadata:
internal: true
---

# Capture web content
Expand Down
30 changes: 24 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

[![Atet: AI media generation and video editing for coding agents](https://atet.sh/og.png)](https://atet.sh)

[![skills.sh](https://skills.sh/b/hraness/atet)](https://skills.sh/hraness/atet)

**AI media generation and video editing for coding agents.**

Atet lets Codex, Claude, and other coding agents generate images, video, and
Expand All @@ -17,15 +19,30 @@ an Atet service.

## Install Atet

Atet requires [Bun 1.3.14 or newer](https://bun.sh). Run these commands once:
Install the single Atet Agent Skill with either runner:

```sh
npx skills add hraness/atet
# or
bunx skills add hraness/atet
```

Start a new agent session after installing the skill. It teaches Codex, Claude
Code, Cursor, and other compatible agents how to turn a finished-media request
into checked Atet operations.

Atet requires [Bun 1.3.14 or newer](https://bun.sh). Install the current CLI,
then inspect the local media host:

```sh
bun add --global github:hraness/atet
atet skill install
atet doctor
```

The first command installs the Atet CLI. The second installs a short guide that
teaches Codex when and how to use it.
The CLI carries the guide released with that exact CLI version. Prefer the
public `skills` command for the current repository guide. Use
`atet skill install` when you specifically need the CLI's version-matched
runner installer.

Move into the project where you want Atet to work, then check the available
recording, rendering, browser, and media tools:
Expand All @@ -35,8 +52,9 @@ cd /path/to/your/project
atet doctor
```

Start a new agent session after installing the skill. The default installation
works with Codex across your user account.
The public `skills` command follows the scope selected in that installer. If
you instead use the CLI's version-matched guide, `atet skill install` defaults
to Codex across your user account.

For Claude Code or another system that reads Agent Skills:

Expand Down
36 changes: 36 additions & 0 deletions apps/web/scripts/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,37 @@ function renderAppearanceMenu(): string {
</div>`
}

type CopyCommandOptions = Readonly<{
alternateCommand: string
command: string
id: string
}>

function escapeHtml(value: string): string {
return value.replace(/[&<>"']/gu, character => ({
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#39;",
})[character] ?? character)
}

function renderCopyCommand(options: CopyCommandOptions): string {
const alternateCommand = escapeHtml(options.alternateCommand)
const command = escapeHtml(options.command)
const id = escapeHtml(options.id)

return `<div class="copy-command" data-copy-command>
<code class="copy-command__value" data-copy-command-value>${command}</code>
<button aria-describedby="${id}" aria-label="Copy install command" class="copy-command__button"
data-copy-command-button hidden type="button">Copy</button>
<p class="copy-command__note">Using Bun? <code>${alternateCommand}</code></p>
<p aria-atomic="true" aria-live="polite" class="copy-command__status"
data-copy-command-status id="${id}"></p>
</div>`
}

type BuildEnvironment = Readonly<Record<string, string | undefined>>

type BuildOptions = Readonly<{
Expand Down Expand Up @@ -179,6 +210,11 @@ export async function buildWebsite(options: BuildOptions = {}): Promise<Readonly
"{{ANALYTICS_SCRIPT}}": analyticsPath === null
? ""
: `<script src="${analyticsPath}" type="module"></script>`,
"{{SKILL_INSTALL_COMMAND}}": renderCopyCommand({
alternateCommand: "bunx skills add hraness/atet",
command: "npx skills add hraness/atet",
id: "skill-install-copy-status",
}),
} as const

await rm(outputDirectory, { force: true, recursive: true })
Expand Down
38 changes: 35 additions & 3 deletions apps/web/site.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,14 +178,15 @@ describe("static Atet site", () => {
})

test("puts the complete agent install before the first section ends", async () => {
const html = await readSource("index.html")
const html = await readBuilt("index.html")
const searchableHtml = html.replace(/\s+/gu, " ")
const commands = [
"npx skills add hraness/atet",
"bun add --global github:hraness/atet",
"atet skill install",
"atet doctor",
]
const positions = commands.map(command => html.indexOf(command))
const heroHtml = html.slice(0, html.indexOf("</section>") + "</section>".length)

expect(positions.every(position => position >= 0)).toBe(true)
expect(positions).toEqual([...positions].sort((left, right) => left - right))
Expand All @@ -195,15 +196,41 @@ describe("static Atet site", () => {
expect(searchableHtml).toContain("edit screen recordings and imported footage")
expect(searchableHtml).toContain("add captions, graphics, and motion")
expect(searchableHtml).toContain("export finished videos")
expect(html).toContain("Requires Bun 1.3.14+ · Installs for Codex by default")
expect(html).toContain("Install the Atet Agent Skill")
expect(html).toContain("Install the local media tools · Requires Bun 1.3.14+")
expect(html).toContain("Using Bun? <code>bunx skills add hraness/atet</code>")
expect(html).toContain("inside the project you want to work")
expect(html).toContain("start a new agent session")
expect(heroHtml).not.toContain("atet skill install")
expect(html).toContain("When that command is not being used")
expect(html).toContain("atet skill install --target claude")
expect(html).toContain("atet skill install --target agents")
expect(html).toContain("--scope project")
expect(html).not.toContain("github:hraness/atet#")
})

test("renders a progressively enhanced reusable copy command in the hero", async () => {
const [html, build, client] = await Promise.all([
readBuilt("index.html"),
readFile(join(appDirectory, "scripts/build.ts"), "utf8"),
readSource("copy-command.ts"),
])

expect(build).toContain("function renderCopyCommand(options: CopyCommandOptions)")
expect(html.match(/data-copy-command(?:>|\s)/gu)).toHaveLength(1)
expect(html).toContain('<code class="copy-command__value" data-copy-command-value>npx skills add hraness/atet</code>')
expect(html).toContain("<code>bunx skills add hraness/atet</code>")
expect(html).toContain('aria-label="Copy install command"')
expect(html).toContain("data-copy-command-button hidden type=\"button\">Copy</button>")
expect(html).toContain('aria-live="polite"')
expect(html).toContain('aria-describedby="skill-install-copy-status"')
expect(client).toContain("button.hidden = false")
expect(client).toContain("navigator.clipboard.writeText(value)")
expect(client).toContain('ownerDocument.execCommand("copy")')
expect(client).toContain('button.addEventListener("click"')
expect(client).not.toMatch(/fetch\(|XMLHttpRequest|WebSocket|EventSource|sendBeacon/)
})

test("uses one install, examples, workflow, and design information architecture", async () => {
const html = await readSource("index.html")
const modes = ["> Install<", "> Examples<", "> Workflow<", "> Design<"]
Expand Down Expand Up @@ -368,6 +395,7 @@ describe("static Atet site", () => {
const html = await readSource("index.html")
const css = await readSource("styles.css")
const theme = await readSource("theme.ts")
const copyCommand = await readSource("copy-command.ts")
const analytics = await readSource("analytics.ts")
const build = await readFile(join(appDirectory, "scripts/build.ts"), "utf8")
const manifest = JSON.parse(
Expand All @@ -391,6 +419,7 @@ describe("static Atet site", () => {
expect(new TextEncoder().encode(html).byteLength).toBeLessThan(20_000)
expect(new TextEncoder().encode(css).byteLength).toBeLessThan(28_000)
expect(new TextEncoder().encode(theme).byteLength).toBeLessThan(3_000)
expect(new TextEncoder().encode(copyCommand).byteLength).toBeLessThan(4_000)
expect(html).not.toMatch(/https:\/\/[^"']+\.(?:css|js)/)
expect(html).toContain('<link rel="stylesheet" href="{{CSS_ASSET}}">')
expect(html).toContain('<script src="{{THEME_ASSET}}"></script>')
Expand All @@ -401,7 +430,9 @@ describe("static Atet site", () => {
expect(html.match(/<script\b/gu)).toHaveLength(2)
expect(theme).toContain('from "@hraness/design-kit/browser"')
expect(theme).toContain('storageKey: "atet.appearance"')
expect(theme).toContain('import { installCopyCommands } from "./copy-command"')
expect(theme).not.toMatch(/fetch\(|XMLHttpRequest|WebSocket|EventSource|sendBeacon/)
expect(copyCommand).not.toMatch(/fetch\(|XMLHttpRequest|WebSocket|EventSource|sendBeacon/)
expect(analytics).toContain('cookieless_mode: "always"')
expect(analytics).toContain('person_profiles: "never"')
expect(analytics).toContain("disable_external_dependency_loading: true")
Expand All @@ -416,6 +447,7 @@ describe("static Atet site", () => {
expect(build).toContain('format: "iife"')
expect(build).toContain('import.meta.resolve("@hraness/design-kit/appearance-menu.css")')
expect(build).toContain("renderAppearanceMenu()")
expect(build).toContain("renderCopyCommand({")
expect(build).toContain('environment.VERCEL_ENV !== "production"')
expect(build).not.toContain("docsTemplate")
expect(build).not.toContain('outputDirectory, "docs"')
Expand Down
68 changes: 68 additions & 0 deletions apps/web/src/copy-command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
const feedbackDurationMilliseconds = 2_500

function fallbackCopy(value: string, ownerDocument: Document): boolean {
const input = ownerDocument.createElement("textarea")
input.value = value
input.readOnly = true
input.style.position = "fixed"
input.style.inset = "0 auto auto -9999px"
input.style.opacity = "0"
ownerDocument.body.append(input)
input.select()
input.setSelectionRange(0, value.length)

try {
return ownerDocument.execCommand("copy")
} finally {
input.remove()
}
}

async function copyText(value: string, ownerDocument: Document): Promise<boolean> {
try {
await navigator.clipboard.writeText(value)
return true
} catch {
try {
return fallbackCopy(value, ownerDocument)
} catch {
return false
}
}
}

export function installCopyCommands(ownerDocument: Document = document): void {
for (const component of ownerDocument.querySelectorAll<HTMLElement>("[data-copy-command]")) {
const command = component.querySelector<HTMLElement>("[data-copy-command-value]")
const button = component.querySelector<HTMLButtonElement>("[data-copy-command-button]")
const status = component.querySelector<HTMLElement>("[data-copy-command-status]")
if (command === null || button === null || status === null) {
continue
}

button.hidden = false
let resetTimer: number | undefined
button.addEventListener("click", async () => {
if (resetTimer !== undefined) {
window.clearTimeout(resetTimer)
}

const copied = await copyText(command.textContent ?? "", ownerDocument)
button.textContent = copied ? "Copied" : "Copy"
button.dataset.copyState = copied ? "copied" : "failed"
status.textContent = copied
? "Install command copied."
: "Could not copy the command. Select it and copy it manually."
button.focus()

if (copied) {
resetTimer = window.setTimeout(() => {
button.textContent = "Copy"
delete button.dataset.copyState
status.textContent = ""
resetTimer = undefined
}, feedbackDurationMilliseconds)
}
})
}
}
35 changes: 20 additions & 15 deletions apps/web/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -143,17 +143,20 @@ <h1 id="page-title">Make and edit visual media with your coding agent.</h1>
</p>
</div>

<div class="install-panel docs-install-panel" aria-label="Install Atet for Codex">
<p class="panel-label">Requires Bun 1.3.14+ · Installs for Codex by default</p>
<ol class="install-commands">
<li><span>01</span><code>bun add --global github:hraness/atet</code></li>
<li><span>02</span><code>atet skill install</code></li>
<li><span>03</span><code>atet doctor</code></li>
</ol>
<p class="panel-note">
Run <code>atet doctor</code> inside the project you want to work
in. Then start a new agent session and describe the finished result.
</p>
<div class="install-panel docs-install-panel" aria-label="Install Atet for your coding agent">
<p class="panel-label">Install the Atet Agent Skill</p>
{{SKILL_INSTALL_COMMAND}}
<div class="cli-install">
<p class="panel-label">Install the local media tools · Requires Bun 1.3.14+</p>
<ol class="install-commands">
<li><span>01</span><code>bun add --global github:hraness/atet</code></li>
<li><span>02</span><code>atet doctor</code></li>
</ol>
<p class="panel-note">
Run <code>atet doctor</code> inside the project you want to work
in. Then start a new agent session and describe the finished result.
</p>
</div>
</div>
</section>

Expand All @@ -166,14 +169,16 @@ <h1 id="page-title">Make and edit visual media with your coding agent.</h1>

<div class="docs-page">
<section aria-labelledby="targets-title" class="docs-section install-details">
<div class="docs-label"><p>Other setups</p></div>
<div class="docs-label"><p>Version-matched guide</p></div>
<div class="docs-prose">
<h2 id="targets-title">Install the instructions where your agent can read them.</h2>
<h2 id="targets-title">Use the guide shipped with the CLI when you need it.</h2>
<p>
The CLI performs the work. The Agent Skill explains the creative
tools and the decisions an agent should make before it uses them.
By default, Atet installs that guide for Codex across your user
account.
The public skills command above installs the current repository
guide. When that command is not being used, <code>atet skill
install</code> installs the guide shipped with your CLI for Codex
by default.
</p>
<dl class="setup-options">
<div><dt>Claude Code</dt><dd><code>atet skill install --target claude</code></dd></div>
Expand Down
Loading
Loading