feat(web): import oh-my-openagent-web Next.js + Cloudflare Workers site
Imports the public marketing site previously living in ../oh-my-opencode-web. Independent of the npm plugin: own package.json, bun.lock, tsconfig.json. Not included in the published package — root files: array still only ships dist/, bin/, postinstall.mjs. Stack: - Next.js 15.5 App Router + RSC, deployed to Cloudflare Workers via @opennextjs/cloudflare (build target .open-next/worker.js). - Tailwind v4 + shadcn/ui primitives. - next-intl with 4 locales (en/ja/ko/zh) under app/[locale]/. - Playwright e2e tests under web/e2e/. - Custom domains ohmyopenagent.com (primary) and ohmyopencode.org (legacy alias) declared in web/wrangler.toml. Source files were re-formatted via `bun run format` to bring them in line with the existing .prettierrc (singleQuote: false). Functional code unchanged.
@@ -0,0 +1,22 @@
|
||||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{js,jsx,ts,tsx,json,css,scss,md}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.yml]
|
||||
indent_size = 2
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
@@ -0,0 +1,52 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/versions
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
/e2e/test-results/
|
||||
/e2e/playwright-report/
|
||||
/e2e/.auth/
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
/.open-next/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
.env
|
||||
.dev.vars
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
# playwright
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/blob-report/
|
||||
/playwright/.cache/
|
||||
@@ -0,0 +1,31 @@
|
||||
# Dependencies
|
||||
node_modules
|
||||
.pnp
|
||||
.pnp.*
|
||||
|
||||
# Build output
|
||||
.next
|
||||
out
|
||||
build
|
||||
dist
|
||||
|
||||
# Testing
|
||||
coverage
|
||||
test-results
|
||||
playwright-report
|
||||
.playwright
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
# Lock files
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
pnpm-lock.yaml
|
||||
|
||||
# TypeScript
|
||||
*.tsbuildinfo
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"semi": false,
|
||||
"trailingComma": "all",
|
||||
"singleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"printWidth": 100,
|
||||
"arrowParens": "always",
|
||||
"endOfLine": "lf",
|
||||
"plugins": ["prettier-plugin-tailwindcss"]
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
# web/ — Marketing Site (Next.js + Cloudflare Workers)
|
||||
|
||||
**Generated:** 2026-05-08
|
||||
|
||||
## OVERVIEW
|
||||
|
||||
Public-facing marketing site for oh-my-opencode / oh-my-openagent. Next.js 15 (App Router) deployed to Cloudflare Workers via [@opennextjs/cloudflare](https://opennext.js.org/cloudflare). Independent of the npm plugin — its own `package.json`, `bun.lock`, and `tsconfig.json`.
|
||||
|
||||
## STACK
|
||||
|
||||
| Layer | Choice |
|
||||
| -------------- | ----------------------------------------------------------------------------------- |
|
||||
| Framework | Next.js 15.5 (App Router, RSC) |
|
||||
| Runtime target | Cloudflare Workers (`compatibility_flags: ["nodejs_compat"]`) |
|
||||
| Adapter | `@opennextjs/cloudflare` (build → `.open-next/worker.js`) |
|
||||
| Styling | Tailwind v4 (`@tailwindcss/postcss`) + shadcn/ui (`components.json`) |
|
||||
| i18n | `next-intl` with `app/[locale]/...` routing; 4 locales (en/ja/ko/zh) in `messages/` |
|
||||
| Animation | `motion` (Framer Motion v12) |
|
||||
| E2E | Playwright (`e2e/*.spec.ts`) |
|
||||
| Lint/Format | ESLint flat config + Prettier (Tailwind plugin) |
|
||||
|
||||
## STRUCTURE
|
||||
|
||||
```
|
||||
web/
|
||||
├── app/[locale]/ # localized routes (App Router)
|
||||
├── components/ # shared UI primitives + shadcn-generated
|
||||
├── lib/ # utility helpers (cn, etc.)
|
||||
├── messages/{en,ja,ko,zh}.json # i18n strings
|
||||
├── i18n/ # next-intl request/routing config
|
||||
├── middleware.ts # next-intl middleware
|
||||
├── public/ # static assets (largest dir, ~4 MB)
|
||||
├── e2e/ # Playwright tests
|
||||
├── scripts/prepare-build.mjs # purges .next/cache/fetch-cache before build
|
||||
├── next.config.ts
|
||||
├── open-next.config.ts
|
||||
├── wrangler.toml # worker name + compatibility settings
|
||||
├── playwright.config.ts
|
||||
├── eslint.config.mjs
|
||||
├── postcss.config.mjs
|
||||
├── tsconfig.json
|
||||
├── components.json # shadcn config
|
||||
└── package.json
|
||||
```
|
||||
|
||||
## SCRIPTS
|
||||
|
||||
```bash
|
||||
# from web/ directory
|
||||
bun install
|
||||
bun run dev # next dev (local Node.js)
|
||||
bun run lint # eslint
|
||||
bun run lint:fix
|
||||
bun run format # prettier --write
|
||||
bun run format:check
|
||||
bun run type-check # tsc --noEmit
|
||||
bun run build # next build (Node target — for sanity)
|
||||
bun run preview # opennextjs-cloudflare build + preview locally
|
||||
bun run deploy # opennextjs-cloudflare build + deploy to Cloudflare
|
||||
bun run test:e2e # playwright test
|
||||
bun run cf-typegen # regenerate cloudflare-env.d.ts from wrangler.toml bindings
|
||||
```
|
||||
|
||||
## CI/CD
|
||||
|
||||
| Workflow | Trigger | What |
|
||||
| ---------------------------------- | ------------------------------------------------------- | ----------------------------------------------------------------------- |
|
||||
| `.github/workflows/web-ci.yml` | push/PR to master/dev that touches `web/**` | format check, lint, type-check, next build, opennextjs-cloudflare build |
|
||||
| `.github/workflows/web-deploy.yml` | push to master that touches `web/**` OR manual dispatch | full deploy via `cloudflare/wrangler-action@v3` |
|
||||
|
||||
**Required secrets** (must be configured in repo settings before deploy works):
|
||||
|
||||
- `CLOUDFLARE_API_TOKEN` — token with `Workers Scripts: Edit` permission
|
||||
- `CLOUDFLARE_ACCOUNT_ID` — Cloudflare account ID
|
||||
|
||||
A `web-production` GitHub environment is referenced by the deploy workflow so deploys can be gated behind required reviewers / wait timers if desired.
|
||||
|
||||
## RELATIONSHIP TO npm PACKAGE
|
||||
|
||||
The npm package `oh-my-opencode` ships only `dist/`, `bin/`, and `postinstall.mjs` (see root `package.json` `files` field). `web/` is **not** included in any npm publish — it is exclusively a separate Cloudflare deployment target.
|
||||
|
||||
Root `bun test` is scoped to `bin script src` (see root `package.json`) so `web/e2e/*.spec.ts` does not pollute plugin tests.
|
||||
|
||||
## CONVENTIONS
|
||||
|
||||
- **No path aliases globally** in the omo project, but `web/` is a Next.js app where `@/*` aliases are the framework default. Keep `@/*` confined to web/.
|
||||
- Use the existing shadcn primitives in `components/ui/` rather than installing new UI libs.
|
||||
- All user-facing copy goes through `messages/{locale}.json`; never hardcode strings in components.
|
||||
- Format with prettier before commit — `web-ci.yml` enforces `format:check`.
|
||||
|
||||
## ANTI-PATTERNS
|
||||
|
||||
- Never run `npm install` in `web/`. Use `bun install` only. (Root `.gitignore` already blocks `package-lock.json`.)
|
||||
- Never commit `.next/`, `.open-next/`, `.wrangler/`, `node_modules/` (covered by `web/.gitignore`).
|
||||
- Never deploy locally with `bun run deploy` against production — use the GitHub Actions workflow so Cloudflare credentials live in one place.
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { Metadata } from "next"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Documentation",
|
||||
description:
|
||||
"Configuration reference for Oh My OpenAgent. Agents, categories, skills, hooks, MCPs, and more.",
|
||||
}
|
||||
|
||||
export default function DocsLayout({ children }: { children: React.ReactNode }) {
|
||||
return children
|
||||
}
|
||||
@@ -0,0 +1,527 @@
|
||||
import { getTranslations } from "next-intl/server"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Separator } from "@/components/ui/separator"
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@/components/ui/accordion"
|
||||
import { CodeBlock } from "@/components/ui/code-block"
|
||||
import { OptionTable } from "@/components/ui/option-table"
|
||||
import { DocsShell } from "@/components/docs/docs-shell"
|
||||
import { DOC_SECTION_IDS, DOC_SECTION_TITLE_KEYS } from "@/lib/docs-sections"
|
||||
|
||||
export default async function DocsPage() {
|
||||
const t = await getTranslations("docs")
|
||||
const sections = DOC_SECTION_IDS.map((id) => ({
|
||||
id,
|
||||
title: t(`sections.${DOC_SECTION_TITLE_KEYS[id]}`),
|
||||
}))
|
||||
|
||||
return (
|
||||
<DocsShell
|
||||
mobileHeader={t("mobileHeader")}
|
||||
searchPlaceholder={t("searchPlaceholder")}
|
||||
sections={sections}
|
||||
>
|
||||
<section id="overview" className="scroll-mt-24 space-y-4">
|
||||
<h1 className="text-4xl font-bold tracking-tight">{t("overview.title")}</h1>
|
||||
<p className="text-muted-foreground text-lg">
|
||||
{t("overview.description", { command: "bunx oh-my-openagent install" })}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section id="quick-start" className="scroll-mt-24 space-y-4">
|
||||
<h2 className="text-2xl font-semibold tracking-tight">{t("quickStart.title")}</h2>
|
||||
<CodeBlock
|
||||
code={`{
|
||||
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/master/assets/oh-my-openagent.schema.json",
|
||||
"agents": {
|
||||
"oracle": { "model": "openai/gpt-5.4", "variant": "high" },
|
||||
"explore": { "model": "github-copilot/grok-code-fast-1" }
|
||||
},
|
||||
"categories": {
|
||||
"quick": { "model": "opencode/gpt-5-nano" },
|
||||
"visual-engineering": { "model": "google/gemini-3.1-pro" }
|
||||
}
|
||||
}`}
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section id="config-locations" className="scroll-mt-24 space-y-4">
|
||||
<h2 className="text-2xl font-semibold tracking-tight">{t("configLocations.title")}</h2>
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<ul className="text-muted-foreground list-disc space-y-2 pl-5">
|
||||
<li>
|
||||
<code className="text-foreground font-mono">.opencode/oh-my-openagent.json</code>{" "}
|
||||
{t("configLocations.projectLevel")}
|
||||
</li>
|
||||
<li>
|
||||
<code className="text-foreground font-mono">
|
||||
~/.config/opencode/oh-my-openagent.json
|
||||
</code>{" "}
|
||||
{t("configLocations.userLevel")}
|
||||
</li>
|
||||
</ul>
|
||||
<p className="text-muted-foreground mt-4 text-sm">{t("configLocations.jsonc")}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</section>
|
||||
|
||||
<section id="agents" className="scroll-mt-24 space-y-6">
|
||||
<h2 className="text-2xl font-semibold tracking-tight">{t("agentsSection.title")}</h2>
|
||||
<p className="text-muted-foreground">{t("agentsSection.description")}</p>
|
||||
|
||||
<h3 className="text-xl font-semibold">{t("agentsSection.overrideOptions")}</h3>
|
||||
<OptionTable
|
||||
options={[
|
||||
{ name: "model", type: "string", description: t("agentsSection.options.model") },
|
||||
{ name: "variant", type: "string", description: t("agentsSection.options.variant") },
|
||||
{ name: "category", type: "string", description: t("agentsSection.options.category") },
|
||||
{
|
||||
name: "temperature",
|
||||
type: "number",
|
||||
description: t("agentsSection.options.temperature"),
|
||||
},
|
||||
{ name: "top_p", type: "number", description: t("agentsSection.options.topP") },
|
||||
{ name: "prompt", type: "string", description: t("agentsSection.options.prompt") },
|
||||
{
|
||||
name: "prompt_append",
|
||||
type: "string",
|
||||
description: t("agentsSection.options.promptAppend"),
|
||||
},
|
||||
{ name: "tools", type: "Record", description: t("agentsSection.options.tools") },
|
||||
{
|
||||
name: "disable",
|
||||
type: "boolean",
|
||||
default: "false",
|
||||
description: t("agentsSection.options.disable"),
|
||||
},
|
||||
{
|
||||
name: "maxTokens",
|
||||
type: "number",
|
||||
description: t("agentsSection.options.maxTokens"),
|
||||
},
|
||||
{ name: "thinking", type: "object", description: t("agentsSection.options.thinking") },
|
||||
{
|
||||
name: "reasoningEffort",
|
||||
type: "string",
|
||||
description: t("agentsSection.options.reasoningEffort"),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<h3 className="text-xl font-semibold">{t("agentsSection.permissions")}</h3>
|
||||
<div className="border-border overflow-hidden rounded-lg border">
|
||||
<table className="w-full text-sm">
|
||||
<thead className="bg-muted/50 text-left font-medium">
|
||||
<tr>
|
||||
<th className="p-3">Permission</th>
|
||||
<th className="p-3">Values</th>
|
||||
<th className="p-3">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-border divide-y">
|
||||
{[
|
||||
{ name: "edit", descKey: "edit" as const },
|
||||
{ name: "bash", descKey: "bash" as const },
|
||||
{ name: "webfetch", descKey: "webfetch" as const },
|
||||
{ name: "doom_loop", descKey: "doomLoop" as const },
|
||||
{ name: "external_directory", descKey: "externalDirectory" as const },
|
||||
].map((p) => (
|
||||
<tr key={p.name}>
|
||||
<td className="p-3 font-mono font-medium">{p.name}</td>
|
||||
<td className="text-muted-foreground p-3 font-mono">
|
||||
{t("agentsSection.permissionValues")}
|
||||
</td>
|
||||
<td className="text-muted-foreground p-3">
|
||||
{t(`agentsSection.permissionDescriptions.${p.descKey}`)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="categories" className="scroll-mt-24 space-y-6">
|
||||
<h2 className="text-2xl font-semibold tracking-tight">{t("categoriesSection.title")}</h2>
|
||||
<p className="text-muted-foreground">{t("categoriesSection.description")}</p>
|
||||
|
||||
<div className="border-border overflow-hidden rounded-lg border">
|
||||
<table className="w-full text-sm">
|
||||
<thead className="bg-muted/50 text-left font-medium">
|
||||
<tr>
|
||||
<th className="p-3">Category</th>
|
||||
<th className="p-3">Default Model</th>
|
||||
<th className="p-3">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-border divide-y">
|
||||
{[
|
||||
{
|
||||
name: "visual-engineering",
|
||||
model: "gemini-3.1-pro (high)",
|
||||
descKey: "visualEngineering" as const,
|
||||
},
|
||||
{
|
||||
name: "ultrabrain",
|
||||
model: "gpt-5.3-codex (xhigh)",
|
||||
descKey: "ultrabrain" as const,
|
||||
},
|
||||
{ name: "deep", model: "gpt-5.3-codex (medium)", descKey: "deep" as const },
|
||||
{ name: "artistry", model: "gemini-3.1-pro (high)", descKey: "artistry" as const },
|
||||
{ name: "quick", model: "claude-haiku-4-5", descKey: "quick" as const },
|
||||
{
|
||||
name: "unspecified-low",
|
||||
model: "claude-sonnet-4-6",
|
||||
descKey: "unspecifiedLow" as const,
|
||||
},
|
||||
{
|
||||
name: "unspecified-high",
|
||||
model: "gpt-5.4 (high)",
|
||||
descKey: "unspecifiedHigh" as const,
|
||||
},
|
||||
{ name: "writing", model: "gemini-3-flash", descKey: "writing" as const },
|
||||
].map((c) => (
|
||||
<tr key={c.name}>
|
||||
<td className="text-primary p-3 font-mono font-medium">{c.name}</td>
|
||||
<td className="text-muted-foreground p-3 font-mono">{c.model}</td>
|
||||
<td className="text-muted-foreground p-3">
|
||||
{t(`categoriesSection.categories.${c.descKey}`)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p className="text-muted-foreground text-sm">
|
||||
{t("categoriesSection.availableOptions", {
|
||||
options:
|
||||
"model, variant, temperature, top_p, maxTokens, thinking, reasoningEffort, textVerbosity, tools, prompt_append, is_unstable_agent",
|
||||
})}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section id="skills" className="scroll-mt-24 space-y-4">
|
||||
<h2 className="text-2xl font-semibold tracking-tight">{t("skillsSection.title")}</h2>
|
||||
<p className="text-muted-foreground">
|
||||
{t("skillsSection.description", {
|
||||
playwright: "playwright",
|
||||
agentBrowser: "agent-browser",
|
||||
gitMaster: "git-master",
|
||||
})}
|
||||
</p>
|
||||
<CodeBlock
|
||||
code={`"skills": {
|
||||
"my-custom-skill": {
|
||||
"description": "A custom skill for specific tasks",
|
||||
"instructions": "Always use this skill when..."
|
||||
}
|
||||
}`}
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section id="background-tasks" className="scroll-mt-24 space-y-4">
|
||||
<h2 className="text-2xl font-semibold tracking-tight">
|
||||
{t("backgroundTasksSection.title")}
|
||||
</h2>
|
||||
<OptionTable
|
||||
options={[
|
||||
{
|
||||
name: "defaultConcurrency",
|
||||
type: "number",
|
||||
description: t("backgroundTasksSection.options.defaultConcurrency"),
|
||||
},
|
||||
{
|
||||
name: "staleTimeoutMs",
|
||||
type: "number",
|
||||
description: t("backgroundTasksSection.options.staleTimeoutMs"),
|
||||
},
|
||||
{
|
||||
name: "providerConcurrency",
|
||||
type: "number",
|
||||
description: t("backgroundTasksSection.options.providerConcurrency"),
|
||||
},
|
||||
{
|
||||
name: "modelConcurrency",
|
||||
type: "number",
|
||||
description: t("backgroundTasksSection.options.modelConcurrency"),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<div className="text-muted-foreground flex items-center gap-2 text-sm">
|
||||
<span className="font-semibold">{t("backgroundTasksSection.priority")}</span>
|
||||
<Badge variant="secondary">modelConcurrency</Badge> >
|
||||
<Badge variant="secondary">providerConcurrency</Badge> >
|
||||
<Badge variant="secondary">defaultConcurrency</Badge>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="hooks" className="scroll-mt-24 space-y-4">
|
||||
<h2 className="text-2xl font-semibold tracking-tight">{t("hooksSection.title")}</h2>
|
||||
<p className="text-muted-foreground mb-4">{t("hooksSection.description")}</p>
|
||||
<div className="grid grid-cols-1 gap-2 md:grid-cols-2 lg:grid-cols-3">
|
||||
{[
|
||||
"agent-usage-reminder",
|
||||
"anthropic-context-window-limit-recovery",
|
||||
"anthropic-effort",
|
||||
"atlas",
|
||||
"auto-slash-command",
|
||||
"auto-update-checker",
|
||||
"background-notification",
|
||||
"category-skill-reminder",
|
||||
"claude-code-hooks",
|
||||
"comment-checker",
|
||||
"compaction-context-injector",
|
||||
"compaction-todo-preserver",
|
||||
"delegate-task-retry",
|
||||
"directory-agents-injector",
|
||||
"directory-readme-injector",
|
||||
"edit-error-recovery",
|
||||
"interactive-bash-session",
|
||||
"keyword-detector",
|
||||
"non-interactive-env",
|
||||
"prometheus-md-only",
|
||||
"question-label-truncator",
|
||||
"ralph-loop",
|
||||
"rules-injector",
|
||||
"session-recovery",
|
||||
"sisyphus-junior-notepad",
|
||||
"start-work",
|
||||
"stop-continuation-guard",
|
||||
"subagent-question-blocker",
|
||||
"task-reminder",
|
||||
"task-resume-info",
|
||||
"tasks-todowrite-disabler",
|
||||
"think-mode",
|
||||
"thinking-block-validator",
|
||||
"unstable-agent-babysitter",
|
||||
"write-existing-file-guard",
|
||||
].map((hook) => (
|
||||
<div
|
||||
key={hook}
|
||||
className="border-border bg-card text-muted-foreground rounded border p-2 font-mono text-xs transition-colors"
|
||||
>
|
||||
{hook}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="mcps" className="scroll-mt-24 space-y-4">
|
||||
<h2 className="text-2xl font-semibold tracking-tight">{t("mcpsSection.title")}</h2>
|
||||
<div className="grid gap-4 md:grid-cols-3">
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-base">{t("mcpsSection.websearch.title")}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
{t("mcpsSection.websearch.description")}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-base">{t("mcpsSection.context7.title")}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
{t("mcpsSection.context7.description")}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-base">{t("mcpsSection.grepApp.title")}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
{t("mcpsSection.grepApp.description")}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="browser-automation" className="scroll-mt-24 space-y-4">
|
||||
<h2 className="text-2xl font-semibold tracking-tight">
|
||||
{t("browserAutomationSection.title")}
|
||||
</h2>
|
||||
<div className="border-border overflow-hidden rounded-lg border">
|
||||
<table className="w-full text-sm">
|
||||
<thead className="bg-muted/50 text-left font-medium">
|
||||
<tr>
|
||||
<th className="p-3">Tool</th>
|
||||
<th className="p-3">Description</th>
|
||||
<th className="p-3">Use Case</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-border divide-y">
|
||||
<tr>
|
||||
<td className="text-primary p-3 font-mono font-medium">
|
||||
{t("browserAutomationSection.playwright.tool")}
|
||||
</td>
|
||||
<td className="text-muted-foreground p-3">
|
||||
{t("browserAutomationSection.playwright.description")}
|
||||
</td>
|
||||
<td className="text-muted-foreground p-3">
|
||||
{t("browserAutomationSection.playwright.useCase")}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="text-primary p-3 font-mono font-medium">
|
||||
{t("browserAutomationSection.agentBrowser.tool")}
|
||||
</td>
|
||||
<td className="text-muted-foreground p-3">
|
||||
{t("browserAutomationSection.agentBrowser.description")}
|
||||
</td>
|
||||
<td className="text-muted-foreground p-3">
|
||||
{t("browserAutomationSection.agentBrowser.useCase")}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="tmux" className="scroll-mt-24 space-y-4">
|
||||
<h2 className="text-2xl font-semibold tracking-tight">{t("tmuxSection.title")}</h2>
|
||||
<OptionTable
|
||||
options={[
|
||||
{ name: "enabled", type: "boolean", description: t("tmuxSection.options.enabled") },
|
||||
{ name: "layout", type: "string", description: t("tmuxSection.options.layout") },
|
||||
{
|
||||
name: "main_pane_size",
|
||||
type: "string",
|
||||
description: t("tmuxSection.options.mainPaneSize"),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section id="git-master" className="scroll-mt-24 space-y-4">
|
||||
<h2 className="text-2xl font-semibold tracking-tight">{t("gitMasterSection.title")}</h2>
|
||||
<OptionTable
|
||||
options={[
|
||||
{
|
||||
name: "commit_footer",
|
||||
type: "string",
|
||||
description: t("gitMasterSection.options.commitFooter"),
|
||||
},
|
||||
{
|
||||
name: "include_co_authored_by",
|
||||
type: "boolean",
|
||||
description: t("gitMasterSection.options.includeCoAuthoredBy"),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section id="comment-checker" className="scroll-mt-24 space-y-4">
|
||||
<h2 className="text-2xl font-semibold tracking-tight">
|
||||
{t("commentCheckerSection.title")}
|
||||
</h2>
|
||||
<p className="text-muted-foreground">
|
||||
{t("commentCheckerSection.description", { placeholder: "{{comments}}" })}
|
||||
</p>
|
||||
<CodeBlock
|
||||
code={`"comment-checker": {
|
||||
"custom_prompt": "Review these comments: {{comments}}"
|
||||
}`}
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section id="experimental" className="scroll-mt-24 space-y-4">
|
||||
<h2 className="text-2xl font-semibold tracking-tight">{t("experimentalSection.title")}</h2>
|
||||
<OptionTable
|
||||
options={[
|
||||
{
|
||||
name: "aggressive_truncation",
|
||||
type: "boolean",
|
||||
description: t("experimentalSection.options.aggressiveTruncation"),
|
||||
},
|
||||
{
|
||||
name: "auto_resume",
|
||||
type: "boolean",
|
||||
description: t("experimentalSection.options.autoResume"),
|
||||
},
|
||||
{
|
||||
name: "preemptive_compaction",
|
||||
type: "boolean",
|
||||
description: t("experimentalSection.options.preemptiveCompaction"),
|
||||
},
|
||||
{
|
||||
name: "truncate_all_tool_outputs",
|
||||
type: "boolean",
|
||||
description: t("experimentalSection.options.truncateAllToolOutputs"),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<Accordion type="single" collapsible className="w-full">
|
||||
<AccordionItem value="dynamic-pruning">
|
||||
<AccordionTrigger>{t("experimentalSection.dynamicPruning.trigger")}</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<p className="text-muted-foreground mb-2 text-sm">
|
||||
{t("experimentalSection.dynamicPruning.description")}
|
||||
</p>
|
||||
<CodeBlock
|
||||
code={`"dynamic_context_pruning": {
|
||||
"enabled": true,
|
||||
"strategy": "smart",
|
||||
"max_tokens": 10000
|
||||
}`}
|
||||
/>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</section>
|
||||
|
||||
<section id="lsp" className="scroll-mt-24 space-y-4">
|
||||
<h2 className="text-2xl font-semibold tracking-tight">{t("lspSection.title")}</h2>
|
||||
<OptionTable
|
||||
options={[
|
||||
{ name: "command", type: "string", description: t("lspSection.options.command") },
|
||||
{ name: "extensions", type: "array", description: t("lspSection.options.extensions") },
|
||||
{ name: "priority", type: "number", description: t("lspSection.options.priority") },
|
||||
{ name: "env", type: "object", description: t("lspSection.options.env") },
|
||||
{
|
||||
name: "initialization",
|
||||
type: "object",
|
||||
description: t("lspSection.options.initialization"),
|
||||
},
|
||||
{ name: "disabled", type: "boolean", description: t("lspSection.options.disabled") },
|
||||
]}
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section id="env-vars" className="scroll-mt-24 space-y-4">
|
||||
<h2 className="text-2xl font-semibold tracking-tight">{t("envVarsSection.title")}</h2>
|
||||
<div className="border-border rounded-lg border p-4">
|
||||
<div className="flex items-start justify-between">
|
||||
<div>
|
||||
<h3 className="text-primary font-mono font-bold">
|
||||
{t("envVarsSection.opencodeConfigDir.name")}
|
||||
</h3>
|
||||
<p className="text-muted-foreground mt-1 text-sm">
|
||||
{t("envVarsSection.opencodeConfigDir.description")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Separator className="my-12" />
|
||||
|
||||
<footer className="text-muted-foreground text-sm">
|
||||
<p>{t("footer", { year: new Date().getFullYear().toString() })}</p>
|
||||
</footer>
|
||||
</DocsShell>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { Metadata } from "next"
|
||||
import type { JSX } from "react"
|
||||
import { notFound } from "next/navigation"
|
||||
import { hasLocale } from "next-intl"
|
||||
import { setRequestLocale } from "next-intl/server"
|
||||
import { LocalizedPageShell } from "@/app/_components/localized-page-shell"
|
||||
import { routing } from "@/i18n/routing"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
description:
|
||||
"Meet Sisyphus: The batteries-included agent that codes like you. Multi-model orchestration, background agents, 40+ lifecycle hooks.",
|
||||
}
|
||||
|
||||
export function generateStaticParams() {
|
||||
return routing.locales.map((locale) => ({ locale }))
|
||||
}
|
||||
|
||||
export default async function LocaleLayout({
|
||||
children,
|
||||
params,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
params: Promise<{ locale: string }>
|
||||
}): Promise<JSX.Element> {
|
||||
const { locale } = await params
|
||||
|
||||
if (!hasLocale(routing.locales, locale)) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
setRequestLocale(locale)
|
||||
|
||||
return <LocalizedPageShell locale={locale}>{children}</LocalizedPageShell>
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { Metadata } from "next"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Ultrawork Manifesto",
|
||||
description:
|
||||
"The philosophy of high-output engineering. Why human developers should be architects, not spell-checkers.",
|
||||
}
|
||||
|
||||
export default function ManifestoLayout({ children }: { children: React.ReactNode }) {
|
||||
return children
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
import { getTranslations } from "next-intl/server"
|
||||
import Image from "next/image"
|
||||
import { ArrowRight, Check, Terminal, Zap } from "lucide-react"
|
||||
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Section } from "@/components/ui/section"
|
||||
import { Separator } from "@/components/ui/separator"
|
||||
import { Link } from "@/i18n/routing"
|
||||
|
||||
async function ManifestoPage() {
|
||||
const t = await getTranslations("manifesto")
|
||||
|
||||
const painPointKeys = ["fixing", "syntax", "copyPasting", "reviewing"] as const
|
||||
const indistinguishableKeys = [
|
||||
"patterns",
|
||||
"errorHandling",
|
||||
"tests",
|
||||
"noSlop",
|
||||
"comments",
|
||||
] as const
|
||||
const ultraworkStepKeys = ["analyze", "breakdown", "execute", "verify", "commit"] as const
|
||||
|
||||
const coreLoopKeys = [
|
||||
"prometheus",
|
||||
"metis",
|
||||
"momus",
|
||||
"orchestrator",
|
||||
"todoContinuation",
|
||||
"categorySystem",
|
||||
"backgroundAgents",
|
||||
"wisdomAccumulation",
|
||||
] as const
|
||||
|
||||
const futureKeys = ["focus", "quality", "complexity", "promptEngineering"] as const
|
||||
|
||||
return (
|
||||
<main className="bg-background text-foreground min-h-screen overflow-x-hidden">
|
||||
<section className="relative flex min-h-[80vh] flex-col items-center justify-center overflow-hidden px-6 pt-20 text-center">
|
||||
<div className="absolute inset-0 z-0 opacity-20">
|
||||
<Image
|
||||
src="/images/core-loop.png"
|
||||
alt="Background"
|
||||
fill
|
||||
className="object-cover object-center"
|
||||
priority
|
||||
/>
|
||||
<div className="from-background/80 via-background/90 to-background absolute inset-0 bg-gradient-to-b" />
|
||||
</div>
|
||||
|
||||
<div className="animate-in fade-in slide-in-from-bottom-2 relative z-10 mx-auto max-w-4xl space-y-6 duration-500">
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="border-primary/50 text-primary mb-4 px-4 py-1 text-sm"
|
||||
>
|
||||
{t("badge")}
|
||||
</Badge>
|
||||
<h1 className="from-foreground to-foreground/60 bg-gradient-to-b bg-clip-text text-5xl font-bold tracking-tight text-transparent md:text-7xl">
|
||||
{t("hero.title")}
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-xl font-light tracking-wide md:text-2xl">
|
||||
{t("hero.subtitle")}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Section className="mx-auto max-w-3xl">
|
||||
<div className="space-y-12">
|
||||
<div className="text-primary/90 border-primary/20 bg-primary/5 border-y py-8 text-center font-mono text-lg md:text-xl">
|
||||
{t("bottleneck")}
|
||||
</div>
|
||||
|
||||
<div className="prose prose-invert prose-lg max-w-none">
|
||||
<p>{t("autonomousCar")}</p>
|
||||
|
||||
<h2 className="mt-8 mb-4 text-2xl font-bold">{t("whyDifferent")}</h2>
|
||||
|
||||
<p>{t("micromanagement")}</p>
|
||||
|
||||
<ul className="my-6 list-none space-y-4 pl-0">
|
||||
{painPointKeys.map((key) => (
|
||||
<li key={key} className="flex items-start gap-3">
|
||||
<span className="mt-1 text-red-500">✕</span>
|
||||
<span>{t(`painPoints.${key}`)}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<p className="my-8 border-l-4 border-red-500 bg-red-500/5 py-2 pl-6 text-xl font-semibold">
|
||||
{t("notCollaboration")}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<Link href="/" className="text-primary underline-offset-4 hover:underline">
|
||||
{t("premiseLinkText")}
|
||||
</Link>{" "}
|
||||
{t("premise", { linkText: "" })}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Separator className="mx-auto max-w-4xl opacity-20" />
|
||||
|
||||
<Section className="mx-auto max-w-3xl">
|
||||
<h2 className="mb-8 text-3xl font-bold md:text-4xl">{t("indistinguishable.title")}</h2>
|
||||
|
||||
<p className="text-muted-foreground mb-8 text-xl">{t("indistinguishable.subtitle")}</p>
|
||||
|
||||
<div className="mb-10 grid gap-6">
|
||||
{indistinguishableKeys.map((key) => (
|
||||
<div
|
||||
key={key}
|
||||
className="bg-secondary/30 border-border/50 flex items-start gap-4 rounded-lg border p-4"
|
||||
>
|
||||
<Check className="h-6 w-6 shrink-0 text-green-500" />
|
||||
<span>{t(`indistinguishable.items.${key}`)}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<blockquote className="border-primary bg-primary/5 rounded-r-lg border-l-4 py-4 pl-6 text-2xl font-light italic">
|
||||
{t("indistinguishable.quote")}
|
||||
</blockquote>
|
||||
</Section>
|
||||
|
||||
<Section className="mx-auto max-w-4xl">
|
||||
<div className="grid items-center gap-12 md:grid-cols-2">
|
||||
<div>
|
||||
<h2 className="mb-6 text-3xl font-bold md:text-4xl">{t("tokenCost.title")}</h2>
|
||||
<p className="text-muted-foreground mb-6 text-lg">{t("tokenCost.description")}</p>
|
||||
<ul className="mb-8 space-y-3">
|
||||
<li className="flex items-center gap-2">
|
||||
<Zap className="h-5 w-5 text-yellow-500" />
|
||||
<span>{t("tokenCost.parallelAgents")}</span>
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<Zap className="h-5 w-5 text-yellow-500" />
|
||||
<span>{t("tokenCost.completeWork")}</span>
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<Zap className="h-5 w-5 text-yellow-500" />
|
||||
<span>{t("tokenCost.selfVerification")}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="bg-secondary/20 border-border/50 rounded-xl border p-8">
|
||||
<h3 className="text-primary mb-4 text-xl font-semibold">{t("tokenCost.however")}</h3>
|
||||
<p className="text-muted-foreground mb-4">{t("tokenCost.optimizeDescription")}</p>
|
||||
<ul className="space-y-2 text-sm">
|
||||
<li className="text-muted-foreground flex items-center gap-2">
|
||||
<div className="bg-primary h-1.5 w-1.5 rounded-full" />
|
||||
{t("tokenCost.cheaperModels")}
|
||||
</li>
|
||||
<li className="text-muted-foreground flex items-center gap-2">
|
||||
<div className="bg-primary h-1.5 w-1.5 rounded-full" />
|
||||
{t("tokenCost.avoidingRedundant")}
|
||||
</li>
|
||||
<li className="text-muted-foreground flex items-center gap-2">
|
||||
<div className="bg-primary h-1.5 w-1.5 rounded-full" />
|
||||
{t("tokenCost.intelligentCaching")}
|
||||
</li>
|
||||
<li className="text-muted-foreground flex items-center gap-2">
|
||||
<div className="bg-primary h-1.5 w-1.5 rounded-full" />
|
||||
{t("tokenCost.stoppingExactly")}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section className="mx-auto max-w-5xl">
|
||||
<div className="mb-16 text-center">
|
||||
<h2 className="mb-4 text-3xl font-bold md:text-4xl">{t("cognitiveLoad.title")}</h2>
|
||||
<p className="text-muted-foreground mx-auto max-w-2xl text-xl">
|
||||
{t("cognitiveLoad.subtitle")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-8 md:grid-cols-2">
|
||||
<Card className="from-background to-primary/5 border-primary/20 relative overflow-hidden bg-gradient-to-br">
|
||||
<div className="absolute top-0 right-0 p-4 opacity-10">
|
||||
<Terminal className="h-24 w-24" />
|
||||
</div>
|
||||
<CardHeader>
|
||||
<Badge className="mb-2 w-fit">{t("cognitiveLoad.ultrawork.badge")}</Badge>
|
||||
<CardTitle className="text-2xl">{t("cognitiveLoad.ultrawork.title")}</CardTitle>
|
||||
<p className="text-muted-foreground">{t("cognitiveLoad.ultrawork.subtitle")}</p>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="border-primary/20 relative ml-2 space-y-6 border-l pl-4">
|
||||
{ultraworkStepKeys.map((key) => (
|
||||
<div key={key} className="relative">
|
||||
<div className="bg-primary border-background absolute top-1.5 -left-[21px] h-3 w-3 rounded-full border-2" />
|
||||
<p className="text-sm">{t(`cognitiveLoad.ultrawork.steps.${key}`)}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="border-border/50 text-primary mt-8 border-t pt-6 text-center font-bold">
|
||||
{t("cognitiveLoad.ultrawork.footer")}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-secondary/10 border-border/50">
|
||||
<CardHeader>
|
||||
<Badge variant="secondary" className="mb-2 w-fit">
|
||||
{t("cognitiveLoad.prometheus.badge")}
|
||||
</Badge>
|
||||
<CardTitle className="text-2xl">{t("cognitiveLoad.prometheus.title")}</CardTitle>
|
||||
<p className="text-muted-foreground">{t("cognitiveLoad.prometheus.subtitle")}</p>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
<div className="space-y-4">
|
||||
<div className="bg-background/50 border-border/50 rounded-lg border p-4">
|
||||
<h3 className="text-primary mb-1 font-semibold">
|
||||
{t("cognitiveLoad.prometheus.prometheusTitle")}
|
||||
</h3>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
{t("cognitiveLoad.prometheus.prometheusDescription")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<ArrowRight className="text-muted-foreground/50 rotate-90 md:rotate-0" />
|
||||
</div>
|
||||
<div className="bg-background/50 border-border/50 rounded-lg border p-4">
|
||||
<h3 className="text-primary mb-1 font-semibold">
|
||||
{t("cognitiveLoad.prometheus.atlasTitle")}
|
||||
</h3>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
{t("cognitiveLoad.prometheus.atlasDescription")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-border/50 text-muted-foreground mt-4 border-t pt-6 text-center font-bold">
|
||||
{t("cognitiveLoad.prometheus.footer")}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section className="mx-auto max-w-6xl">
|
||||
<div className="grid gap-8 md:grid-cols-3">
|
||||
{(["predictable", "continuous", "delegatable"] as const).map((key) => (
|
||||
<div
|
||||
key={key}
|
||||
className="bg-secondary/10 border-border/30 rounded-xl border p-6 text-center transition-colors"
|
||||
>
|
||||
<div className="mb-4 flex justify-center">
|
||||
<Image
|
||||
src={`/images/${key}.png`}
|
||||
alt={key}
|
||||
width={64}
|
||||
height={64}
|
||||
className="rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
<h3 className="mb-3 text-xl font-bold">{t(`principles.${key}.title`)}</h3>
|
||||
<p className="text-muted-foreground">{t(`principles.${key}.description`)}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Separator className="mx-auto max-w-4xl opacity-20" />
|
||||
|
||||
<Section className="mx-auto max-w-5xl">
|
||||
<h2 className="mb-12 text-center text-3xl font-bold md:text-4xl">{t("coreLoop.title")}</h2>
|
||||
|
||||
<div className="bg-background border-border/50 mb-16 rounded-xl border p-6 shadow-lg">
|
||||
<div className="flex flex-wrap items-center justify-center gap-4 py-8 md:gap-8">
|
||||
<div className="rounded-lg border-2 border-white bg-black px-6 py-3 text-sm font-semibold text-white md:text-base">
|
||||
Human Intent
|
||||
</div>
|
||||
<svg
|
||||
className="text-muted-foreground h-8 w-8 shrink-0"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path d="M5 12h14M12 5l7 7-7 7" />
|
||||
</svg>
|
||||
<div className="rounded-lg border-2 border-zinc-600 bg-zinc-900 px-6 py-3 text-sm font-semibold text-white md:text-base">
|
||||
Agent Execution
|
||||
</div>
|
||||
<svg
|
||||
className="text-muted-foreground h-8 w-8 shrink-0"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path d="M5 12h14M12 5l7 7-7 7" />
|
||||
</svg>
|
||||
<div className="rounded-lg border-2 border-cyan-500 bg-black px-6 py-3 text-sm font-semibold text-cyan-400 md:text-base">
|
||||
Verified Result
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-muted-foreground mt-2 text-center text-xs">↻ Minimum Intervention</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
{coreLoopKeys.map((key) => (
|
||||
<Card key={key} className="bg-secondary/5 border-border/40 transition-colors">
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-primary text-lg">
|
||||
{t(`coreLoop.features.${key}.feature`)}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
{t(`coreLoop.features.${key}.purpose`)}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section className="mx-auto max-w-3xl text-center">
|
||||
<h2 className="mb-8 text-3xl font-bold md:text-4xl">{t("future.title")}</h2>
|
||||
|
||||
<div className="mx-auto mb-12 max-w-2xl space-y-4 text-left">
|
||||
{futureKeys.map((key) => (
|
||||
<div key={key} className="flex items-center gap-3">
|
||||
<div className="bg-primary h-2 w-2 shrink-0 rounded-full" />
|
||||
<span className="text-lg">{t(`future.items.${key}`)}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<p className="text-2xl font-light">{t("future.quote1")}</p>
|
||||
<p className="text-primary text-3xl font-bold">{t("future.quote2")}</p>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<section className="from-primary/10 to-background bg-gradient-to-t px-6 py-32 text-center">
|
||||
<div className="space-y-8">
|
||||
<h2 className="text-foreground text-6xl font-black tracking-tighter md:text-8xl">
|
||||
{t("finalCta.title")}
|
||||
</h2>
|
||||
|
||||
<Button size="lg" className="rounded-full px-8 py-6 text-lg" asChild>
|
||||
<Link href="https://github.com/code-yeongyu/oh-my-openagent" target="_blank">
|
||||
{t("finalCta.button")} <ArrowRight className="ml-2 h-5 w-5" />
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
export default ManifestoPage
|
||||
@@ -0,0 +1,17 @@
|
||||
export { landingMetadata as metadata } from "@/app/_components/landing-page"
|
||||
|
||||
import type { JSX } from "react"
|
||||
import { setRequestLocale } from "next-intl/server"
|
||||
import { LandingPage } from "@/app/_components/landing-page"
|
||||
|
||||
export default async function LocaleLandingPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ locale: string }>
|
||||
}): Promise<JSX.Element> {
|
||||
const { locale } = await params
|
||||
|
||||
setRequestLocale(locale)
|
||||
|
||||
return <LandingPage />
|
||||
}
|
||||
@@ -0,0 +1,712 @@
|
||||
import type { Metadata } from "next"
|
||||
import type { JSX, SVGProps } from "react"
|
||||
import { getTranslations } from "next-intl/server"
|
||||
import Image from "next/image"
|
||||
import {
|
||||
Layers,
|
||||
Star,
|
||||
Check,
|
||||
Zap,
|
||||
Search,
|
||||
Code2,
|
||||
Brain,
|
||||
Eye,
|
||||
MessageSquare,
|
||||
Shield,
|
||||
Lightbulb,
|
||||
Route,
|
||||
HardDrive,
|
||||
ArrowRight,
|
||||
Target,
|
||||
} from "lucide-react"
|
||||
import { HeroStats } from "@/components/landing/hero-stats"
|
||||
import { InstallCommand } from "@/components/landing/install-command"
|
||||
import { TerminalTypewriter } from "@/components/landing/motion-wrappers"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Link } from "@/i18n/routing"
|
||||
import { formatStats, getStats } from "@/lib/stats"
|
||||
|
||||
const FALLBACK_STATS = {
|
||||
stars: "40k+",
|
||||
totalDownloads: "1M+",
|
||||
monthlyDownloads: "580k+",
|
||||
weeklyDownloads: "90k+",
|
||||
}
|
||||
|
||||
export const landingMetadata: Metadata = {
|
||||
title: "Oh My OpenAgent — The Best Agent Harness",
|
||||
description:
|
||||
"Meet Sisyphus: The batteries-included agent that codes like you. Multi-model orchestration, background agents, 40+ lifecycle hooks.",
|
||||
}
|
||||
|
||||
export async function LandingPage(): Promise<JSX.Element> {
|
||||
const t = await getTranslations("landing")
|
||||
|
||||
let formattedStats = FALLBACK_STATS
|
||||
try {
|
||||
const stats = await getStats()
|
||||
formattedStats = formatStats(stats)
|
||||
} catch {
|
||||
formattedStats = FALLBACK_STATS
|
||||
}
|
||||
|
||||
const subAgentKeys = ["oracle", "librarian", "explore", "metis", "momus"] as const
|
||||
type SubAgentKey = (typeof subAgentKeys)[number]
|
||||
|
||||
const agentStyles: Record<
|
||||
SubAgentKey,
|
||||
{ color: string; border: string; bg: string; icon: typeof Brain }
|
||||
> = {
|
||||
oracle: {
|
||||
color: "text-purple-400",
|
||||
border: "border-zinc-800",
|
||||
bg: "bg-purple-400/5",
|
||||
icon: Eye,
|
||||
},
|
||||
librarian: {
|
||||
color: "text-green-400",
|
||||
border: "border-zinc-800",
|
||||
bg: "bg-green-400/5",
|
||||
icon: Search,
|
||||
},
|
||||
explore: {
|
||||
color: "text-blue-400",
|
||||
border: "border-zinc-800",
|
||||
bg: "bg-blue-400/5",
|
||||
icon: Code2,
|
||||
},
|
||||
metis: {
|
||||
color: "text-pink-400",
|
||||
border: "border-zinc-800",
|
||||
bg: "bg-pink-400/5",
|
||||
icon: MessageSquare,
|
||||
},
|
||||
momus: { color: "text-red-400", border: "border-zinc-800", bg: "bg-red-400/5", icon: Check },
|
||||
}
|
||||
|
||||
const reviewKeys = ["review1", "review2", "review3", "review4", "review5", "review6"] as const
|
||||
|
||||
const principleKeys = [
|
||||
"specialization",
|
||||
"trustVerify",
|
||||
"wisdom",
|
||||
"modelOptimization",
|
||||
"categories",
|
||||
"continuity",
|
||||
] as const
|
||||
type PrincipleKey = (typeof principleKeys)[number]
|
||||
|
||||
const principleIcons: Record<PrincipleKey, typeof Brain> = {
|
||||
specialization: Target,
|
||||
trustVerify: Shield,
|
||||
wisdom: Lightbulb,
|
||||
modelOptimization: Zap,
|
||||
categories: Route,
|
||||
continuity: HardDrive,
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col overflow-x-hidden">
|
||||
<section className="relative flex min-h-[90vh] items-center justify-center overflow-hidden pt-16">
|
||||
<div className="absolute inset-0 z-0 opacity-30">
|
||||
<Image
|
||||
src="/images/hero.png"
|
||||
alt=""
|
||||
fill
|
||||
priority
|
||||
fetchPriority="high"
|
||||
className="object-cover object-center"
|
||||
sizes="100vw"
|
||||
/>
|
||||
</div>
|
||||
<div className="absolute inset-0 z-0 bg-gradient-to-b from-black/80 via-black/90 to-[#0a0a0a]" />
|
||||
|
||||
<div className="relative z-10 container mx-auto flex flex-col items-center gap-8 px-4 text-center md:px-6">
|
||||
<div className="space-y-4">
|
||||
<h1 className="text-5xl font-bold tracking-tighter text-white md:text-7xl">
|
||||
{t("hero.title")}
|
||||
<span className="text-cyan-400">{t("hero.titleHighlight")}</span>
|
||||
</h1>
|
||||
<p className="mx-auto max-w-3xl text-xl font-light text-zinc-400 md:text-2xl">
|
||||
{t("hero.subtitle", {
|
||||
stars: formattedStats.stars,
|
||||
downloads: formattedStats.totalDownloads,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<HeroStats
|
||||
initialStats={{
|
||||
stars: formattedStats.stars,
|
||||
totalDownloads: formattedStats.totalDownloads,
|
||||
monthlyDownloads: formattedStats.monthlyDownloads,
|
||||
weeklyDownloads: formattedStats.weeklyDownloads,
|
||||
}}
|
||||
labels={{
|
||||
githubStars: t("hero.githubStars", { count: "{count}" }),
|
||||
specializedAgents: t("hero.specializedAgents", { count: "10" }),
|
||||
totalDownloads: t("hero.totalDownloads", { count: "{count}" }),
|
||||
monthlyDownloads: t("hero.monthlyDownloads", { count: "{count}" }),
|
||||
lifecycleHooks: t("hero.lifecycleHooks", { count: "40+" }),
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="w-full max-w-md">
|
||||
<InstallCommand command={t("hero.installCommand")} />
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4 sm:flex-row">
|
||||
<Link href="https://github.com/code-yeongyu/oh-my-openagent" target="_blank">
|
||||
<Button
|
||||
size="lg"
|
||||
className="h-12 bg-cyan-500 px-8 text-lg font-bold text-black shadow-sm hover:bg-cyan-600"
|
||||
>
|
||||
{t("hero.getStarted")}
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="https://github.com/code-yeongyu/oh-my-openagent" target="_blank">
|
||||
<Button
|
||||
size="lg"
|
||||
variant="outline"
|
||||
className="h-12 border-zinc-700 px-8 text-lg text-white hover:bg-zinc-800"
|
||||
>
|
||||
<GithubIcon className="mr-2 h-5 w-5" />
|
||||
{t("hero.viewOnGitHub")}
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="features" className="overflow-hidden border-t border-white/5 bg-[#0a0a0a] py-24">
|
||||
<div className="container mx-auto px-4 md:px-6">
|
||||
<div className="flex flex-col items-center gap-16 lg:flex-row">
|
||||
<div className="flex-1 space-y-8">
|
||||
<Badge className="border-cyan-500/20 bg-cyan-500/10 px-4 py-1.5 text-cyan-400">
|
||||
{t("ulw.badge")}
|
||||
</Badge>
|
||||
<h2 className="bg-gradient-to-r from-cyan-400 to-purple-600 bg-clip-text text-4xl font-black tracking-tighter text-transparent md:text-5xl">
|
||||
{t("ulw.title")}
|
||||
</h2>
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-3xl font-bold text-white">{t("ulw.headline")}</h3>
|
||||
<p className="text-xl leading-relaxed text-zinc-400">{t("ulw.description")}</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<Badge className="border-cyan-500/20 bg-cyan-500/10 px-4 py-2 text-sm text-cyan-400">
|
||||
{t("ulw.autoPlanning")}
|
||||
</Badge>
|
||||
<Badge className="border-purple-500/20 bg-purple-500/10 px-4 py-2 text-sm text-purple-400">
|
||||
{t("ulw.deepResearch")}
|
||||
</Badge>
|
||||
<Badge className="border-green-500/20 bg-green-500/10 px-4 py-2 text-sm text-green-400">
|
||||
{t("ulw.selfCorrection")}
|
||||
</Badge>
|
||||
<Badge className="border-amber-500/20 bg-amber-500/10 px-4 py-2 text-sm text-amber-400">
|
||||
{t("ulw.parallelAgents")}
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-lg text-zinc-400/90 italic">{t("ulw.tagline")}</p>
|
||||
</div>
|
||||
|
||||
<div className="w-full max-w-xl flex-1">
|
||||
<div className="overflow-hidden rounded-xl border border-zinc-800 bg-black shadow-xl">
|
||||
<div className="flex items-center gap-2 border-b border-zinc-800 bg-zinc-900/50 px-4 py-3">
|
||||
<div className="h-3 w-3 rounded-full border border-red-500/50 bg-red-500/20" />
|
||||
<div className="h-3 w-3 rounded-full border border-yellow-500/50 bg-yellow-500/20" />
|
||||
<div className="h-3 w-3 rounded-full border border-green-500/50 bg-green-500/20" />
|
||||
<div className="ml-2 font-mono text-xs text-zinc-400">
|
||||
{t("ulw.terminalTitle")}
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-4 overflow-x-auto p-6 font-mono text-sm">
|
||||
<div className="flex gap-2">
|
||||
<span className="text-green-500">➜</span>
|
||||
<span className="text-cyan-500">~</span>
|
||||
<TerminalTypewriter text={t("ulw.terminalInput")} />
|
||||
</div>
|
||||
<div className="space-y-2 border-l-2 border-zinc-800 pl-4">
|
||||
<div className="text-cyan-400">{t("ulw.steps.scanning")}</div>
|
||||
<div className="text-zinc-400">{t("ulw.steps.context")}</div>
|
||||
<div className="text-purple-400">{t("ulw.steps.planning")}</div>
|
||||
<div className="text-amber-400">{t("ulw.steps.delegating")}</div>
|
||||
<div className="text-blue-400">{t("ulw.steps.verifying")}</div>
|
||||
</div>
|
||||
<div className="flex gap-2 pt-4">
|
||||
<span className="text-green-500">✓</span>
|
||||
<span className="font-bold text-green-400">{t("ulw.steps.complete")}</span>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<span className="text-green-500">➜</span>
|
||||
<span className="text-cyan-500">~</span>
|
||||
<span className="animate-pulse text-white">_</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="agents" className="relative overflow-hidden bg-black py-24">
|
||||
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_center,_var(--tw-gradient-stops))] from-cyan-900/20 via-black to-black opacity-50" />
|
||||
<div className="relative z-10 container mx-auto px-4 md:px-6">
|
||||
<div className="mx-auto max-w-4xl">
|
||||
<div className="mb-6 flex items-center gap-3">
|
||||
<Badge className="border-cyan-500/20 bg-cyan-500/10 px-4 py-1.5 text-cyan-400">
|
||||
{t("sisyphus.badge")}
|
||||
</Badge>
|
||||
<Badge variant="outline" className="border-zinc-700 text-xs text-zinc-400">
|
||||
{t("sisyphus.model")}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<h2 className="mb-4 text-4xl font-bold text-white md:text-5xl">
|
||||
<span className="text-cyan-400">{t("sisyphus.title")}</span>
|
||||
</h2>
|
||||
<h3 className="mb-6 text-2xl font-bold text-zinc-300 md:text-3xl">
|
||||
{t("sisyphus.headline")}
|
||||
</h3>
|
||||
<p className="mb-12 max-w-3xl text-xl leading-relaxed text-zinc-400">
|
||||
{t("sisyphus.description")}
|
||||
</p>
|
||||
|
||||
<div className="mb-12 grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
{(["intent", "explore", "delegate", "verify"] as const).map((phase, i) => (
|
||||
<div key={phase}>
|
||||
<Card className="h-full border-zinc-800 bg-zinc-900/30">
|
||||
<CardHeader className="pb-2">
|
||||
<div className="mb-1 font-mono text-xs text-cyan-400">PHASE {i + 1}</div>
|
||||
<CardTitle className="text-lg text-white">
|
||||
{t(`sisyphus.phases.${phase}.title`)}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm text-zinc-400">
|
||||
{t(`sisyphus.phases.${phase}.description`)}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="rounded-xl border border-cyan-400/20 bg-cyan-400/5 p-6 md:p-8">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="rounded-lg bg-cyan-400/10 p-3">
|
||||
<HardDrive className="h-6 w-6 text-cyan-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="mb-2 text-xl font-bold text-cyan-400">
|
||||
{t("sisyphus.boulderTitle")}
|
||||
</h4>
|
||||
<p className="leading-relaxed text-zinc-300">
|
||||
{t("sisyphus.boulderDescription")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="border-y border-white/5 bg-[#0a0a0a] py-24">
|
||||
<div className="container mx-auto px-4 md:px-6">
|
||||
<div className="mb-16 text-center">
|
||||
<Badge className="mb-6 border-amber-500/20 bg-amber-500/10 px-4 py-1.5 text-amber-400">
|
||||
{t("prometheusAtlas.badge")}
|
||||
</Badge>
|
||||
<h2 className="mb-4 text-4xl font-bold text-white md:text-5xl">
|
||||
{t("prometheusAtlas.title")}
|
||||
</h2>
|
||||
<p className="mx-auto max-w-2xl text-xl text-zinc-400">
|
||||
{t("prometheusAtlas.headline")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mb-12 grid grid-cols-1 gap-8 lg:grid-cols-2">
|
||||
<div>
|
||||
<Card className="h-full border-zinc-800 bg-zinc-900/30">
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="rounded-lg bg-amber-400/10 p-2">
|
||||
<Brain className="h-6 w-6 text-amber-400" />
|
||||
</div>
|
||||
<Badge variant="outline" className="border-zinc-700 text-xs text-zinc-400">
|
||||
{t("prometheusAtlas.prometheus.model")}
|
||||
</Badge>
|
||||
</div>
|
||||
<CardTitle className="mt-4 text-2xl text-amber-400">
|
||||
{t("prometheusAtlas.prometheus.name")}
|
||||
</CardTitle>
|
||||
<CardDescription className="font-medium text-zinc-400">
|
||||
{t("prometheusAtlas.prometheus.role")}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<p className="leading-relaxed text-zinc-300">
|
||||
{t("prometheusAtlas.prometheus.description")}
|
||||
</p>
|
||||
<ul className="space-y-2">
|
||||
{([0, 1, 2, 3] as const).map((i) => (
|
||||
<li key={i} className="flex items-center gap-2 text-sm text-zinc-400">
|
||||
<ArrowRight className="h-3 w-3 shrink-0 text-amber-400" />
|
||||
{t(`prometheusAtlas.prometheus.features.${i}`)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Card className="h-full border-zinc-800 bg-zinc-900/30">
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="rounded-lg bg-indigo-400/10 p-2">
|
||||
<Layers className="h-6 w-6 text-indigo-400" />
|
||||
</div>
|
||||
<Badge variant="outline" className="border-zinc-700 text-xs text-zinc-400">
|
||||
{t("prometheusAtlas.atlas.model")}
|
||||
</Badge>
|
||||
</div>
|
||||
<CardTitle className="mt-4 text-2xl text-indigo-400">
|
||||
{t("prometheusAtlas.atlas.name")}
|
||||
</CardTitle>
|
||||
<CardDescription className="font-medium text-zinc-400">
|
||||
{t("prometheusAtlas.atlas.role")}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<p className="leading-relaxed text-zinc-300">
|
||||
{t("prometheusAtlas.atlas.description")}
|
||||
</p>
|
||||
<ul className="space-y-2">
|
||||
{([0, 1, 2, 3] as const).map((i) => (
|
||||
<li key={i} className="flex items-center gap-2 text-sm text-zinc-400">
|
||||
<ArrowRight className="h-3 w-3 shrink-0 text-indigo-400" />
|
||||
{t(`prometheusAtlas.atlas.features.${i}`)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="overflow-x-auto rounded-xl border border-zinc-800 bg-zinc-900/20 p-6 md:p-8">
|
||||
<div className="flex min-w-[600px] flex-col items-start justify-between gap-4 md:min-w-0 md:flex-row md:items-center md:gap-0">
|
||||
{([1, 2, 3, 4, 5] as const).map((step, i) => (
|
||||
<div key={step} className="flex flex-1 items-center gap-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-full border border-zinc-700 bg-zinc-800 font-mono text-xs text-zinc-400">
|
||||
{step}
|
||||
</div>
|
||||
<span className="text-sm whitespace-nowrap text-zinc-300">
|
||||
{t(`prometheusAtlas.workflow.step${step}`)}
|
||||
</span>
|
||||
</div>
|
||||
{i < 4 && (
|
||||
<ArrowRight className="ml-auto hidden h-4 w-4 shrink-0 text-zinc-600 md:block" />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<p className="mt-6 border-t border-zinc-800 pt-6 text-center text-zinc-400 italic">
|
||||
{t("prometheusAtlas.whyItWorks")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="relative overflow-hidden bg-black py-24">
|
||||
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_bottom,_var(--tw-gradient-stops))] from-orange-900/10 via-black to-black opacity-70" />
|
||||
<div className="relative z-10 container mx-auto px-4 md:px-6">
|
||||
<div className="mx-auto max-w-4xl">
|
||||
<div className="mb-6 flex items-center gap-3">
|
||||
<Badge className="border-orange-500/20 bg-orange-500/10 px-4 py-1.5 text-orange-400">
|
||||
{t("hephaestus.badge")}
|
||||
</Badge>
|
||||
<Badge variant="outline" className="border-zinc-700 text-xs text-zinc-400">
|
||||
{t("hephaestus.model")}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<h2 className="mb-4 text-4xl font-bold md:text-5xl">
|
||||
<span className="text-orange-400">{t("hephaestus.title")}</span>
|
||||
</h2>
|
||||
<h3 className="mb-6 text-2xl font-bold text-zinc-300 md:text-3xl">
|
||||
{t("hephaestus.headline")}
|
||||
</h3>
|
||||
<p className="mb-12 max-w-3xl text-xl leading-relaxed text-zinc-400">
|
||||
{t("hephaestus.description")}
|
||||
</p>
|
||||
|
||||
<div className="mb-8 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-5">
|
||||
{(["explore", "plan", "decide", "execute", "verify"] as const).map((step, i) => (
|
||||
<div key={step}>
|
||||
<div className="rounded-lg border border-orange-400/20 bg-orange-400/5 p-4 text-center">
|
||||
<div className="mb-2 font-mono text-xs text-orange-400">0{i + 1}</div>
|
||||
<p className="text-sm leading-snug break-keep text-zinc-300">
|
||||
{t(`hephaestus.loop.${step}`)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<p className="text-center text-lg text-zinc-400/90 italic">{t("hephaestus.tagline")}</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="border-t border-white/5 bg-[#0a0a0a] py-24">
|
||||
<div className="container mx-auto px-4 md:px-6">
|
||||
<div className="mb-16 text-center">
|
||||
<h2 className="mb-4 text-4xl font-bold text-white md:text-5xl">{t("agents.title")}</h2>
|
||||
<p className="text-xl text-zinc-400">{t("agents.subtitle")}</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 md:grid-cols-5">
|
||||
{subAgentKeys.map((key) => {
|
||||
const style = agentStyles[key]
|
||||
const Icon = style.icon
|
||||
return (
|
||||
<div key={key}>
|
||||
<Card
|
||||
className={`h-full border-zinc-800 bg-zinc-900/30 ${style.border} ${style.bg}`}
|
||||
>
|
||||
<CardHeader className="pb-2">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className={`rounded-lg bg-black/50 p-2 ${style.color}`}>
|
||||
<Icon className="h-5 w-5" />
|
||||
</div>
|
||||
<Badge variant="outline" className="border-zinc-700 text-xs text-zinc-400">
|
||||
{t(`agents.${key}.model`)}
|
||||
</Badge>
|
||||
</div>
|
||||
<CardTitle className={`mt-3 text-lg ${style.color}`}>
|
||||
{t(`agents.${key}.name`)}
|
||||
</CardTitle>
|
||||
<CardDescription className="text-sm font-medium text-zinc-400">
|
||||
{t(`agents.${key}.role`)}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm leading-relaxed text-zinc-300">
|
||||
{t(`agents.${key}.description`)}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
|
||||
<div className="col-span-1 mt-4 md:col-span-2 lg:col-span-5">
|
||||
<Card className="h-full overflow-hidden border-zinc-800 bg-zinc-900/30">
|
||||
<CardHeader>
|
||||
<div className="mb-2 flex items-center gap-3">
|
||||
<Badge className="border-teal-500/20 bg-teal-500/10 px-3 py-1 text-teal-400">
|
||||
{t("agents.dynamicSystem.role")}
|
||||
</Badge>
|
||||
</div>
|
||||
<CardTitle className="text-2xl text-teal-400">
|
||||
{t("agents.dynamicSystem.name")}
|
||||
</CardTitle>
|
||||
<CardDescription className="max-w-3xl text-base text-zinc-400">
|
||||
{t("agents.dynamicSystem.description")}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="mt-4 grid grid-cols-1 gap-8 md:grid-cols-2">
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-sm font-bold tracking-wider text-zinc-300 uppercase">
|
||||
Category Routing
|
||||
</h3>
|
||||
<div className="space-y-2">
|
||||
{[
|
||||
{ cat: "visual-engineering", model: "Gemini 3.1 Pro" },
|
||||
{ cat: "ultrabrain", model: "GPT 5.4" },
|
||||
{ cat: "artistry", model: "Gemini 3.1 Pro" },
|
||||
{ cat: "quick", model: "Claude Haiku 4.5" },
|
||||
{ cat: "deep", model: "GPT 5.3 Codex" },
|
||||
{ cat: "writing", model: "Kimi K2.5" },
|
||||
{ cat: "git", model: "Claude Haiku 4.5" },
|
||||
].map((item) => (
|
||||
<div
|
||||
key={item.cat}
|
||||
className="flex items-center justify-between rounded border border-zinc-800/50 bg-black/40 p-2"
|
||||
>
|
||||
<span className="font-mono text-sm text-teal-400">{item.cat}</span>
|
||||
<ArrowRight className="h-3 w-3 text-zinc-600" />
|
||||
<span className="text-sm text-zinc-300">{item.model}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-sm font-bold tracking-wider text-zinc-300 uppercase">
|
||||
Skill Injection
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{["playwright", "git-master", "frontend-ui-ux", "dev-browser"].map(
|
||||
(skill) => (
|
||||
<div
|
||||
key={skill}
|
||||
className="flex items-center gap-2 rounded border border-zinc-700/50 bg-zinc-800/30 p-3"
|
||||
>
|
||||
<Zap className="h-4 w-4 text-yellow-400" />
|
||||
<span className="font-mono text-sm text-zinc-200">{skill}</span>
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-6 rounded-lg border border-teal-500/10 bg-teal-500/5 p-4">
|
||||
<p className="text-sm text-teal-300 italic">
|
||||
"The right model + right expertise, every time."
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="bg-black py-24">
|
||||
<div className="container mx-auto px-4 md:px-6">
|
||||
<div className="mb-16 text-center">
|
||||
<h2 className="mb-4 text-4xl font-bold text-white md:text-5xl">
|
||||
{t("architecture.title")}
|
||||
</h2>
|
||||
<p className="text-xl text-zinc-400">{t("architecture.subtitle")}</p>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto grid max-w-5xl grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
{principleKeys.map((key) => {
|
||||
const Icon = principleIcons[key]
|
||||
return (
|
||||
<div key={key}>
|
||||
<Card className="h-full border-zinc-800 bg-zinc-900/30">
|
||||
<CardHeader>
|
||||
<div className="w-fit rounded-lg bg-zinc-800 p-2">
|
||||
<Icon className="h-5 w-5 text-zinc-300" />
|
||||
</div>
|
||||
<CardTitle className="mt-3 text-lg text-white">
|
||||
{t(`architecture.principles.${key}.title`)}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm leading-relaxed text-zinc-400">
|
||||
{t(`architecture.principles.${key}.description`)}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="border-t border-white/5 bg-[#0a0a0a] py-24">
|
||||
<div className="container mx-auto px-4 md:px-6">
|
||||
<div>
|
||||
<h2 className="mb-16 text-center text-4xl font-bold text-white md:text-5xl">
|
||||
{t("reviews.title")}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
{reviewKeys.map((key) => (
|
||||
<div key={key}>
|
||||
<Card className="h-full border-zinc-800 bg-zinc-900/30">
|
||||
<CardContent className="pt-6">
|
||||
<div className="mb-4 text-cyan-500">
|
||||
<Star className="h-5 w-5 fill-cyan-500" />
|
||||
</div>
|
||||
<p className="mb-6 leading-relaxed text-zinc-300 italic">
|
||||
“{t(`reviews.${key}.text`)}”
|
||||
</p>
|
||||
<p className="text-sm font-medium text-zinc-400">
|
||||
— {t(`reviews.${key}.author`)}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="bg-black py-24">
|
||||
<div className="container mx-auto px-4 md:px-6">
|
||||
<div>
|
||||
<div className="relative overflow-hidden rounded-3xl border border-zinc-800 bg-zinc-900/30 p-8 text-center md:p-16">
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-cyan-500/10 via-purple-500/10 to-cyan-500/10 opacity-50" />
|
||||
<div className="relative z-10 mx-auto max-w-3xl space-y-8">
|
||||
<h2 className="text-4xl font-bold text-white md:text-5xl">{t("cta.title")}</h2>
|
||||
<p className="text-lg text-zinc-400">{t("cta.subtitle")}</p>
|
||||
<div className="flex justify-center">
|
||||
<div className="inline-flex items-center gap-2 rounded-full border border-zinc-800 bg-black px-6 py-3 font-mono text-sm text-zinc-300">
|
||||
<span className="text-cyan-500">$</span>
|
||||
{t("cta.installCommand")}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col justify-center gap-4 sm:flex-row">
|
||||
<Link href="https://github.com/code-yeongyu/oh-my-openagent" target="_blank">
|
||||
<Button
|
||||
size="lg"
|
||||
className="h-12 bg-cyan-500 px-8 font-bold text-black hover:bg-cyan-600"
|
||||
>
|
||||
{t("cta.installNow")}
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/docs">
|
||||
<Button
|
||||
size="lg"
|
||||
variant="outline"
|
||||
className="h-12 border-zinc-700 px-8 text-white hover:bg-zinc-800"
|
||||
>
|
||||
{t("cta.readTheDocs")}
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function GithubIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
role="img"
|
||||
>
|
||||
<title>GitHub</title>
|
||||
<path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4" />
|
||||
<path d="M9 18c-4.51 2-5-2-7-2" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import type { JSX } from "react"
|
||||
import { NextIntlClientProvider } from "next-intl"
|
||||
import { Footer } from "@/components/footer"
|
||||
import { NavHeader } from "@/components/nav-header"
|
||||
import type { Locale } from "@/i18n/config"
|
||||
|
||||
type LocalizedPageShellProps = {
|
||||
children: React.ReactNode
|
||||
locale: Locale
|
||||
}
|
||||
|
||||
type IntlMessages = Record<string, Record<string, unknown>>
|
||||
|
||||
function getLanguageTag(locale: Locale): string {
|
||||
switch (locale) {
|
||||
case "zh":
|
||||
return "zh-CN"
|
||||
default:
|
||||
return locale
|
||||
}
|
||||
}
|
||||
|
||||
export async function LocalizedPageShell({
|
||||
children,
|
||||
locale,
|
||||
}: LocalizedPageShellProps): Promise<JSX.Element> {
|
||||
const messages = (await import(`../../messages/${locale}.json`)).default as IntlMessages
|
||||
const languageTag = getLanguageTag(locale)
|
||||
|
||||
return (
|
||||
<NextIntlClientProvider locale={locale} messages={messages}>
|
||||
<div lang={languageTag} data-locale={locale} className="flex min-h-screen flex-col">
|
||||
<NavHeader />
|
||||
<main className="flex-1">{children}</main>
|
||||
<Footer locale={locale} />
|
||||
</div>
|
||||
</NextIntlClientProvider>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import { NextResponse } from "next/server"
|
||||
import { getStats } from "@/lib/stats"
|
||||
|
||||
/**
|
||||
* Shields.io endpoint badge for combined NPM downloads.
|
||||
* Usage: https://img.shields.io/endpoint?url=https://ohmyopenagent.com/api/npm-downloads
|
||||
*
|
||||
* Combines downloads from both oh-my-opencode and oh-my-openagent packages.
|
||||
*/
|
||||
|
||||
function formatDownloads(num: number): string {
|
||||
if (num >= 1_000_000) {
|
||||
const formatted = (num / 1_000_000).toFixed(1)
|
||||
return `${formatted.replace(/\.0$/, "")}M`
|
||||
}
|
||||
if (num >= 1_000) {
|
||||
const formatted = (num / 1_000).toFixed(1)
|
||||
return `${formatted.replace(/\.0$/, "")}k`
|
||||
}
|
||||
return String(num)
|
||||
}
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const { searchParams } = new URL(request.url)
|
||||
const period = searchParams.get("period") ?? "total"
|
||||
|
||||
try {
|
||||
const stats = await getStats()
|
||||
|
||||
let value: number
|
||||
let label: string
|
||||
|
||||
switch (period) {
|
||||
case "monthly":
|
||||
value = stats.monthlyDownloads
|
||||
label = "npm downloads/month"
|
||||
break
|
||||
case "weekly":
|
||||
value = stats.weeklyDownloads
|
||||
label = "npm downloads/week"
|
||||
break
|
||||
case "total":
|
||||
default:
|
||||
value = stats.totalDownloads
|
||||
label = "npm downloads"
|
||||
break
|
||||
}
|
||||
|
||||
// Shields.io endpoint badge schema
|
||||
// https://shields.io/badges/endpoint-badge
|
||||
const badge = {
|
||||
schemaVersion: 1,
|
||||
label,
|
||||
message: formatDownloads(value),
|
||||
color: "ff6b35",
|
||||
labelColor: "000000",
|
||||
style: "flat-square",
|
||||
}
|
||||
|
||||
return NextResponse.json(badge, {
|
||||
headers: {
|
||||
"Cache-Control": "public, s-maxage=3600, stale-while-revalidate=86400",
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
},
|
||||
})
|
||||
} catch {
|
||||
// Fallback badge
|
||||
return NextResponse.json(
|
||||
{
|
||||
schemaVersion: 1,
|
||||
label: "npm downloads",
|
||||
message: "1M+",
|
||||
color: "ff6b35",
|
||||
labelColor: "000000",
|
||||
style: "flat-square",
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
"Cache-Control": "public, s-maxage=300, stale-while-revalidate=3600",
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { NextResponse } from "next/server"
|
||||
import { getStats, formatStats } from "@/lib/stats"
|
||||
|
||||
const FALLBACK = {
|
||||
stars: "37.3k",
|
||||
totalDownloads: "1M+",
|
||||
monthlyDownloads: "580k+",
|
||||
weeklyDownloads: "90k+",
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const stats = await getStats()
|
||||
const formatted = formatStats(stats)
|
||||
|
||||
return NextResponse.json(
|
||||
{ ...formatted, raw: stats },
|
||||
{
|
||||
headers: {
|
||||
"Cache-Control": "public, s-maxage=3600, stale-while-revalidate=86400",
|
||||
},
|
||||
},
|
||||
)
|
||||
} catch {
|
||||
return NextResponse.json(FALLBACK, {
|
||||
headers: {
|
||||
"Cache-Control": "public, s-maxage=300, stale-while-revalidate=3600",
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="180" height="180" viewBox="0 0 180 180">
|
||||
<rect width="180" height="180" rx="40" fill="#0a0a0a"/>
|
||||
<text x="90" y="128" font-family="system-ui, sans-serif" font-size="120" font-weight="bold" fill="#00d4ff" text-anchor="middle">O</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 292 B |
@@ -0,0 +1,234 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@plugin "tailwindcss-animate";
|
||||
|
||||
@custom-variant dark (&:where(.dark, .dark *));
|
||||
|
||||
@theme {
|
||||
--font-sans: var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif;
|
||||
--font-mono: var(--font-geist-mono), ui-monospace, SFMono-Regular, monospace;
|
||||
--font-manrope: var(--font-manrope), sans-serif;
|
||||
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
|
||||
--color-destructive: var(--destructive);
|
||||
--color-destructive-foreground: var(--destructive-foreground);
|
||||
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-5: var(--chart-5);
|
||||
|
||||
--radius-lg: var(--radius);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
|
||||
--animate-accordion-down: accordion-down 0.2s ease-out;
|
||||
--animate-accordion-up: accordion-up 0.2s ease-out;
|
||||
|
||||
@keyframes accordion-down {
|
||||
from {
|
||||
height: 0;
|
||||
}
|
||||
to {
|
||||
height: var(--radix-accordion-content-height);
|
||||
}
|
||||
}
|
||||
@keyframes accordion-up {
|
||||
from {
|
||||
height: var(--radix-accordion-content-height);
|
||||
}
|
||||
to {
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
The default border color has changed to `currentColor` in Tailwind CSS v4,
|
||||
so we've added these compatibility styles to make sure everything still
|
||||
looks the same as it did with Tailwind CSS v3.
|
||||
*/
|
||||
@layer base {
|
||||
*,
|
||||
::after,
|
||||
::before,
|
||||
::backdrop,
|
||||
::file-selector-button {
|
||||
border-color: var(--color-gray-200, currentColor);
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
/* Dark Theme Only - Terminal/Hacker Aesthetic */
|
||||
|
||||
/* Colors */
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
|
||||
--card: #111111;
|
||||
--card-foreground: #ededed;
|
||||
|
||||
--popover: #111111;
|
||||
--popover-foreground: #ededed;
|
||||
|
||||
--primary: #00d4ff;
|
||||
--primary-foreground: #000000;
|
||||
|
||||
--secondary: #7c3aed;
|
||||
--secondary-foreground: #ffffff;
|
||||
|
||||
--muted: #1a1a1a;
|
||||
--muted-foreground: #a1a1a1;
|
||||
|
||||
--accent: #1a1a1a;
|
||||
--accent-foreground: #ededed;
|
||||
|
||||
--destructive: #ef4444;
|
||||
--destructive-foreground: #ffffff;
|
||||
|
||||
--border: #262626;
|
||||
--input: #262626;
|
||||
--ring: #00d4ff;
|
||||
|
||||
/* Charts */
|
||||
--chart-1: #00d4ff;
|
||||
--chart-2: #7c3aed;
|
||||
--chart-3: #10b981;
|
||||
--chart-4: #f59e0b;
|
||||
--chart-5: #ef4444;
|
||||
|
||||
/* Code */
|
||||
--code-bg: #1e1e2e;
|
||||
--code-text: #cdd6f4;
|
||||
|
||||
/* Spacing */
|
||||
--radius: 0.5rem;
|
||||
|
||||
/* Typography */
|
||||
--font-geist-sans: var(--font-geist-sans);
|
||||
--font-geist-mono: var(--font-geist-mono);
|
||||
--font-manrope: var(--font-manrope);
|
||||
--font-inter: var(--font-inter);
|
||||
|
||||
/* Semantic Fonts */
|
||||
--font-heading: var(--font-manrope);
|
||||
--font-body: var(--font-geist-sans);
|
||||
--font-code: var(--font-geist-mono);
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
font-feature-settings:
|
||||
"rlig" 1,
|
||||
"calt" 1;
|
||||
}
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-family: var(--font-manrope);
|
||||
}
|
||||
|
||||
[lang|="ko"] h1,
|
||||
[lang|="ko"] h2,
|
||||
[lang|="ko"] h3,
|
||||
[lang|="ko"] h4,
|
||||
[lang|="ko"] h5,
|
||||
[lang|="ko"] h6,
|
||||
[lang|="ja"] h1,
|
||||
[lang|="ja"] h2,
|
||||
[lang|="ja"] h3,
|
||||
[lang|="ja"] h4,
|
||||
[lang|="ja"] h5,
|
||||
[lang|="ja"] h6,
|
||||
[lang|="zh"] h1,
|
||||
[lang|="zh"] h2,
|
||||
[lang|="zh"] h3,
|
||||
[lang|="zh"] h4,
|
||||
[lang|="zh"] h5,
|
||||
[lang|="zh"] h6 {
|
||||
letter-spacing: normal !important;
|
||||
text-wrap: pretty;
|
||||
}
|
||||
|
||||
:where([lang|="ko"], [lang|="ja"], [lang|="zh"])
|
||||
:where(p, li, blockquote, figcaption, td, th, a, button, span) {
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
[lang|="ko"]
|
||||
:where(h1, h2, h3, h4, h5, h6, p, li, blockquote, figcaption, td, th, a, button, span) {
|
||||
word-break: keep-all;
|
||||
}
|
||||
|
||||
:where([lang|="ja"], [lang|="zh"])
|
||||
:where(h1, h2, h3, h4, h5, h6, p, li, blockquote, figcaption, td, th, a, button, span) {
|
||||
word-break: normal;
|
||||
line-break: strict;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: auto;
|
||||
}
|
||||
|
||||
::selection {
|
||||
@apply bg-primary/20 text-primary;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
@apply bg-muted;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
@apply bg-border hover:bg-muted-foreground/50 rounded-full transition-colors;
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.glow-cyan {
|
||||
box-shadow: 0 0 15px -5px rgba(0, 212, 255, 0.3);
|
||||
}
|
||||
.glow-purple {
|
||||
box-shadow: 0 0 15px -5px rgba(124, 58, 237, 0.3);
|
||||
}
|
||||
|
||||
.text-glow-cyan {
|
||||
text-shadow: 0 0 8px rgba(0, 212, 255, 0.3);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
||||
<rect width="32" height="32" rx="8" fill="#0a0a0a"/>
|
||||
<text x="16" y="23" font-family="system-ui, sans-serif" font-size="22" font-weight="bold" fill="#00d4ff" text-anchor="middle">O</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 283 B |
@@ -0,0 +1,122 @@
|
||||
import type { Metadata } from "next"
|
||||
import { GeistSans } from "geist/font/sans"
|
||||
import { GeistMono } from "geist/font/mono"
|
||||
import { Inter, Manrope } from "next/font/google"
|
||||
import Script from "next/script"
|
||||
import "./globals.css"
|
||||
|
||||
const inter = Inter({
|
||||
subsets: ["latin"],
|
||||
variable: "--font-inter",
|
||||
display: "swap",
|
||||
})
|
||||
|
||||
const manrope = Manrope({
|
||||
subsets: ["latin"],
|
||||
variable: "--font-manrope",
|
||||
display: "swap",
|
||||
})
|
||||
|
||||
const primarySiteUrl = "https://ohmyopenagent.com"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
metadataBase: new URL(primarySiteUrl),
|
||||
title: {
|
||||
default: "Oh My OpenAgent — The Best Agent Harness",
|
||||
template: "%s | Oh My OpenAgent",
|
||||
},
|
||||
description:
|
||||
"Meet Sisyphus: The batteries-included agent that codes like you. Multi-model orchestration, background agents, 40+ lifecycle hooks.",
|
||||
keywords: [
|
||||
"opencode",
|
||||
"oh-my-opencode",
|
||||
"openagent",
|
||||
"oh-my-openagent",
|
||||
"ai agent",
|
||||
"code agent",
|
||||
"sisyphus",
|
||||
"multi-model",
|
||||
"claude",
|
||||
"gpt",
|
||||
"gemini",
|
||||
"coding assistant",
|
||||
],
|
||||
authors: [{ name: "Yeongyu Kim", url: "https://github.com/code-yeongyu" }],
|
||||
creator: "Yeongyu Kim",
|
||||
openGraph: {
|
||||
type: "website",
|
||||
locale: "en_US",
|
||||
url: primarySiteUrl,
|
||||
siteName: "Oh My OpenAgent",
|
||||
title: "Oh My OpenAgent — The Best Agent Harness",
|
||||
description:
|
||||
"Meet Sisyphus: The batteries-included agent that codes like you. Multi-model orchestration, background agents, 40+ lifecycle hooks.",
|
||||
images: [{ url: "/images/hero.png", width: 1200, height: 630, alt: "Oh My OpenAgent" }],
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: "Oh My OpenAgent — The Best Agent Harness",
|
||||
description: "Meet Sisyphus: The batteries-included agent that codes like you.",
|
||||
images: ["/images/hero.png"],
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
googleBot: {
|
||||
index: true,
|
||||
follow: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const jsonLd = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "SoftwareApplication",
|
||||
name: "Oh My OpenAgent",
|
||||
applicationCategory: "DeveloperApplication",
|
||||
operatingSystem: "macOS, Linux, Windows",
|
||||
url: primarySiteUrl,
|
||||
author: {
|
||||
"@type": "Person",
|
||||
name: "Yeongyu Kim",
|
||||
url: "https://github.com/code-yeongyu",
|
||||
},
|
||||
description:
|
||||
"The batteries-included agent harness for OpenCode. Multi-model orchestration, background agents, 40+ lifecycle hooks.",
|
||||
offers: {
|
||||
"@type": "Offer",
|
||||
price: "0",
|
||||
priceCurrency: "USD",
|
||||
},
|
||||
}
|
||||
|
||||
const gaMeasurementId = "G-S0QJFKT46Q"
|
||||
const gaTrackedDomain = "ohmyopenagent.com"
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html
|
||||
lang="en"
|
||||
className={`dark ${GeistSans.variable} ${GeistMono.variable} ${inter.variable} ${manrope.variable}`}
|
||||
suppressHydrationWarning
|
||||
>
|
||||
<body className="flex min-h-screen flex-col bg-[#0a0a0a] text-[#ededed] antialiased">
|
||||
<Script
|
||||
src={`https://www.googletagmanager.com/gtag/js?id=${gaMeasurementId}`}
|
||||
strategy="afterInteractive"
|
||||
/>
|
||||
<Script id="google-analytics" strategy="afterInteractive">
|
||||
{`window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('config', '${gaMeasurementId}', { cookie_domain: '${gaTrackedDomain}' });`}
|
||||
</Script>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
||||
/>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { MetadataRoute } from "next"
|
||||
|
||||
export default function manifest(): MetadataRoute.Manifest {
|
||||
return {
|
||||
name: "Oh My OpenAgent",
|
||||
short_name: "OMO",
|
||||
description:
|
||||
"The Best Agent Harness. Meet Sisyphus: The batteries-included agent that codes like you.",
|
||||
start_url: "/",
|
||||
display: "standalone",
|
||||
background_color: "#0a0a0a",
|
||||
theme_color: "#00d4ff",
|
||||
icons: [
|
||||
{
|
||||
src: "/icon.svg",
|
||||
sizes: "any",
|
||||
type: "image/svg+xml",
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
export { landingMetadata as metadata } from "@/app/_components/landing-page"
|
||||
|
||||
import type { JSX } from "react"
|
||||
import { setRequestLocale } from "next-intl/server"
|
||||
import { LandingPage } from "@/app/_components/landing-page"
|
||||
import { LocalizedPageShell } from "@/app/_components/localized-page-shell"
|
||||
import { defaultLocale } from "@/i18n/config"
|
||||
|
||||
export default function HomePage(): JSX.Element {
|
||||
setRequestLocale(defaultLocale)
|
||||
|
||||
return (
|
||||
<LocalizedPageShell locale={defaultLocale}>
|
||||
<LandingPage />
|
||||
</LocalizedPageShell>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { MetadataRoute } from "next"
|
||||
|
||||
export default function robots(): MetadataRoute.Robots {
|
||||
return {
|
||||
rules: {
|
||||
userAgent: "*",
|
||||
allow: "/",
|
||||
},
|
||||
sitemap: "https://ohmyopenagent.com/sitemap.xml",
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { MetadataRoute } from "next"
|
||||
|
||||
const BASE_URL = "https://ohmyopenagent.com"
|
||||
|
||||
export default function sitemap(): MetadataRoute.Sitemap {
|
||||
const routes = ["", "/docs", "/manifesto"]
|
||||
const locales = ["en", "ko", "ja", "zh"]
|
||||
|
||||
return routes.flatMap((route) =>
|
||||
locales.map((locale) => ({
|
||||
url: `${BASE_URL}/${locale}${route}`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: "weekly" as const,
|
||||
priority: route === "" ? 1 : 0.8,
|
||||
})),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"rsc": true,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.ts",
|
||||
"css": "app/globals.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Menu, Search } from "lucide-react"
|
||||
import { Link } from "@/i18n/routing"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { DOC_SECTION_IDS, type DocSectionId } from "@/lib/docs-sections"
|
||||
|
||||
type DocsShellSection = {
|
||||
id: DocSectionId
|
||||
title: string
|
||||
}
|
||||
|
||||
export function DocsShell({
|
||||
mobileHeader,
|
||||
searchPlaceholder,
|
||||
sections,
|
||||
children,
|
||||
}: {
|
||||
mobileHeader: string
|
||||
searchPlaceholder: string
|
||||
sections: DocsShellSection[]
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const [searchQuery, setSearchQuery] = React.useState("")
|
||||
const [activeSection, setActiveSection] = React.useState<DocSectionId>("overview")
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = React.useState(false)
|
||||
|
||||
const activeSectionRef = React.useRef<DocSectionId>("overview")
|
||||
|
||||
React.useEffect(() => {
|
||||
activeSectionRef.current = activeSection
|
||||
}, [activeSection])
|
||||
|
||||
const filteredSections = React.useMemo(() => {
|
||||
const query = searchQuery.trim().toLowerCase()
|
||||
if (!query) return sections
|
||||
return sections.filter((section) => section.title.toLowerCase().includes(query))
|
||||
}, [searchQuery, sections])
|
||||
|
||||
React.useEffect(() => {
|
||||
const sectionEls = DOC_SECTION_IDS.map((id) => document.getElementById(id))
|
||||
let rafId: number | null = null
|
||||
|
||||
const handleScroll = () => {
|
||||
if (rafId !== null) return
|
||||
|
||||
rafId = window.requestAnimationFrame(() => {
|
||||
rafId = null
|
||||
|
||||
const scrollPosition = window.scrollY + 100
|
||||
let nextActive: DocSectionId | null = null
|
||||
|
||||
for (let i = 0; i < sectionEls.length; i++) {
|
||||
const el = sectionEls[i]
|
||||
if (!el) continue
|
||||
if (el.offsetTop <= scrollPosition && el.offsetTop + el.offsetHeight > scrollPosition) {
|
||||
nextActive = el.id as DocSectionId
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (nextActive && nextActive !== activeSectionRef.current) {
|
||||
activeSectionRef.current = nextActive
|
||||
setActiveSection(nextActive)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
window.addEventListener("scroll", handleScroll, { passive: true })
|
||||
handleScroll()
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("scroll", handleScroll)
|
||||
if (rafId !== null) window.cancelAnimationFrame(rafId)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const scrollToSection = (id: DocSectionId) => {
|
||||
const element = document.getElementById(id)
|
||||
if (!element) return
|
||||
|
||||
window.scrollTo({ top: element.offsetTop - 80, behavior: "auto" })
|
||||
activeSectionRef.current = id
|
||||
setActiveSection(id)
|
||||
setIsMobileMenuOpen(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-background text-foreground flex min-h-screen">
|
||||
<div className="bg-background/95 fixed top-0 right-0 left-0 z-50 flex items-center justify-between border-b px-4 py-3 backdrop-blur md:hidden">
|
||||
<Link href="/" className="font-bold">
|
||||
{mobileHeader}
|
||||
</Link>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
type="button"
|
||||
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
|
||||
aria-label="Toggle sidebar menu"
|
||||
>
|
||||
<Menu className="h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<aside
|
||||
className={`bg-background fixed inset-y-0 left-0 z-40 w-64 transform border-r transition-transform duration-200 ease-in-out md:translate-x-0 ${isMobileMenuOpen ? "translate-x-0" : "-translate-x-full"} pt-16`}
|
||||
>
|
||||
<div className="flex h-full flex-col">
|
||||
<div className="p-4">
|
||||
<div className="relative">
|
||||
<Search className="text-muted-foreground absolute top-2.5 left-2 h-4 w-4" />
|
||||
<Input
|
||||
placeholder={searchPlaceholder}
|
||||
className="pl-8"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<nav className="flex-1 overflow-y-auto px-2 pb-4">
|
||||
<ul className="space-y-1">
|
||||
{filteredSections.map((section) => (
|
||||
<li key={section.id}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => scrollToSection(section.id)}
|
||||
className={`w-full rounded-md px-3 py-2 text-left text-sm font-medium transition-colors ${
|
||||
activeSection === section.id
|
||||
? "bg-primary/10 text-primary"
|
||||
: "text-muted-foreground hover:bg-muted hover:text-foreground"
|
||||
} `}
|
||||
>
|
||||
{section.title}
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main className="flex-1 px-4 pt-20 pb-20 md:ml-64 md:px-8 md:pt-8">
|
||||
<div className="mx-auto max-w-4xl space-y-12">{children}</div>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { getTranslations } from "next-intl/server"
|
||||
import { Link } from "@/i18n/routing"
|
||||
|
||||
export async function Footer({ locale }: { locale?: string } = {}) {
|
||||
const t = locale
|
||||
? await getTranslations({ locale, namespace: "footer" })
|
||||
: await getTranslations("footer")
|
||||
const currentYear = new Date().getUTCFullYear()
|
||||
|
||||
return (
|
||||
<footer className="border-t border-white/10 bg-black py-12">
|
||||
<div className="container mx-auto px-4 md:px-6">
|
||||
<div className="flex flex-col items-center justify-between gap-6 md:flex-row">
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className="text-lg font-bold text-white">{t("brand")}</span>
|
||||
<p className="text-sm text-zinc-400">
|
||||
{t("copyright", { year: currentYear.toString() })}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-8 text-sm text-zinc-400">
|
||||
<a
|
||||
href="https://github.com/code-yeongyu/oh-my-openagent"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="transition-colors hover:text-cyan-400"
|
||||
>
|
||||
{t("github")}
|
||||
</a>
|
||||
<a
|
||||
href="https://discord.gg/indentcorp"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="transition-colors hover:text-cyan-400"
|
||||
>
|
||||
{t("discord")}
|
||||
</a>
|
||||
<Link href="/docs" locale={locale} className="transition-colors hover:text-cyan-400">
|
||||
{t("documentation")}
|
||||
</Link>
|
||||
<Link
|
||||
href="/manifesto"
|
||||
locale={locale}
|
||||
className="transition-colors hover:text-cyan-400"
|
||||
>
|
||||
{t("manifesto")}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
"use client"
|
||||
|
||||
import type { ReactNode } from "react"
|
||||
import { Star, Bot, Download, GitBranch } from "lucide-react"
|
||||
import { useLiveStats } from "./live-stats"
|
||||
|
||||
interface HeroStatsProps {
|
||||
initialStats: {
|
||||
stars: string
|
||||
totalDownloads: string
|
||||
monthlyDownloads: string
|
||||
weeklyDownloads: string
|
||||
}
|
||||
labels: {
|
||||
githubStars: string
|
||||
specializedAgents: string
|
||||
totalDownloads: string
|
||||
monthlyDownloads: string
|
||||
lifecycleHooks: string
|
||||
}
|
||||
}
|
||||
|
||||
export function HeroStats({ initialStats, labels }: HeroStatsProps): ReactNode {
|
||||
const stats = useLiveStats(initialStats)
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap justify-center gap-4 text-sm text-zinc-300 md:gap-8 md:text-base">
|
||||
<div className="flex items-center gap-2">
|
||||
<Star className="h-5 w-5 fill-yellow-400 text-yellow-400" />
|
||||
<span>{labels.githubStars.replace("{count}", stats.stars)}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Bot className="h-5 w-5 text-cyan-400" />
|
||||
<span>{labels.specializedAgents}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Download className="h-5 w-5 text-green-400" />
|
||||
<span>{labels.totalDownloads.replace("{count}", stats.totalDownloads)}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Download className="h-5 w-5 text-emerald-400" />
|
||||
<span>{labels.monthlyDownloads.replace("{count}", stats.monthlyDownloads)}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<GitBranch className="h-5 w-5 text-purple-400" />
|
||||
<span>{labels.lifecycleHooks}</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { Copy, Check } from "lucide-react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
export function InstallCommand({ command }: { command: string }) {
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
const copyCommand = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(command)
|
||||
setCopied(true)
|
||||
window.setTimeout(() => setCopied(false), 2000)
|
||||
} catch (err) {
|
||||
console.warn("Failed to copy install command", err)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative rounded-lg border border-zinc-800 bg-black/50 p-4 font-mono text-sm text-zinc-300 shadow-2xl shadow-cyan-500/10 backdrop-blur-sm">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-cyan-500">$</span>
|
||||
<span>{command}</span>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-8 w-8 text-zinc-400 hover:text-white"
|
||||
onClick={copyCommand}
|
||||
aria-label="Copy install command"
|
||||
>
|
||||
{copied ? <Check className="h-4 w-4 text-green-500" /> : <Copy className="h-4 w-4" />}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect, useState } from "react"
|
||||
|
||||
interface StatsData {
|
||||
stars: string
|
||||
totalDownloads: string
|
||||
monthlyDownloads: string
|
||||
weeklyDownloads: string
|
||||
}
|
||||
|
||||
export function useLiveStats(initial: StatsData): StatsData {
|
||||
const [stats, setStats] = useState<StatsData>(initial)
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false
|
||||
async function refresh() {
|
||||
try {
|
||||
const res = await fetch("/api/stats")
|
||||
if (!res.ok || cancelled) return
|
||||
const data = await res.json()
|
||||
if (cancelled) return
|
||||
setStats({
|
||||
stars: data.stars ?? initial.stars,
|
||||
totalDownloads: data.totalDownloads ?? initial.totalDownloads,
|
||||
monthlyDownloads: data.monthlyDownloads ?? initial.monthlyDownloads,
|
||||
weeklyDownloads: data.weeklyDownloads ?? initial.weeklyDownloads,
|
||||
})
|
||||
} catch {
|
||||
// keep SSG values on error
|
||||
}
|
||||
}
|
||||
refresh()
|
||||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [initial.stars, initial.totalDownloads, initial.monthlyDownloads, initial.weeklyDownloads])
|
||||
|
||||
return stats
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
"use client"
|
||||
|
||||
import { useRef, useEffect, useState } from "react"
|
||||
|
||||
interface TerminalTypewriterProps {
|
||||
text: string
|
||||
}
|
||||
|
||||
export function TerminalTypewriter({ text }: TerminalTypewriterProps) {
|
||||
const ref = useRef<HTMLSpanElement>(null)
|
||||
const [isInView, setIsInView] = useState(false)
|
||||
const [displayed, setDisplayed] = useState("")
|
||||
|
||||
useEffect(() => {
|
||||
const element = ref.current
|
||||
if (!element || isInView) return
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
if (!entry?.isIntersecting) return
|
||||
setIsInView(true)
|
||||
observer.disconnect()
|
||||
},
|
||||
{ threshold: 0.1 },
|
||||
)
|
||||
|
||||
observer.observe(element)
|
||||
|
||||
return () => observer.disconnect()
|
||||
}, [isInView])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isInView) return
|
||||
let i = 0
|
||||
const interval = setInterval(() => {
|
||||
if (i <= text.length) {
|
||||
setDisplayed(text.slice(0, i))
|
||||
i++
|
||||
} else {
|
||||
clearInterval(interval)
|
||||
}
|
||||
}, 40)
|
||||
return () => clearInterval(interval)
|
||||
}, [isInView, text])
|
||||
|
||||
return (
|
||||
<span ref={ref} className="text-zinc-300">
|
||||
{displayed}
|
||||
{displayed.length < text.length && <span className="animate-pulse">_</span>}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { useTranslations } from "next-intl"
|
||||
import { Github, Menu, X } from "lucide-react"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Link } from "@/i18n/routing"
|
||||
|
||||
export function NavHeader() {
|
||||
const t = useTranslations("nav")
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<header className="sticky top-0 z-50 w-full border-b border-white/10 bg-black/50 backdrop-blur-xl">
|
||||
<div className="container mx-auto flex h-16 items-center justify-between px-4 md:px-6">
|
||||
<div className="flex items-center gap-6">
|
||||
<Link href="/" className="flex items-center gap-2">
|
||||
<span className="text-lg font-bold tracking-tight text-white">{t("brand")}</span>
|
||||
</Link>
|
||||
<nav className="hidden items-center gap-6 text-sm font-medium text-zinc-400 md:flex">
|
||||
<Link href="/#features" className="transition-colors hover:text-cyan-400">
|
||||
{t("features")}
|
||||
</Link>
|
||||
<Link href="/#agents" className="transition-colors hover:text-cyan-400">
|
||||
{t("agents")}
|
||||
</Link>
|
||||
<Link href="/docs" className="transition-colors hover:text-cyan-400">
|
||||
{t("docs")}
|
||||
</Link>
|
||||
<Link href="/manifesto" className="transition-colors hover:text-cyan-400">
|
||||
{t("manifesto")}
|
||||
</Link>
|
||||
</nav>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<a
|
||||
href="https://github.com/code-yeongyu/oh-my-openagent"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="hidden sm:flex"
|
||||
>
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className="gap-1 border-zinc-700 bg-zinc-800 text-zinc-300 hover:bg-zinc-700"
|
||||
>
|
||||
<Github className="h-3 w-3" />
|
||||
<span>{t("starOnGitHub")}</span>
|
||||
</Badge>
|
||||
</a>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="text-zinc-400 hover:bg-zinc-800 hover:text-white md:hidden"
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
aria-label={isOpen ? "Close menu" : "Open menu"}
|
||||
aria-expanded={isOpen}
|
||||
aria-controls="mobile-nav"
|
||||
>
|
||||
{isOpen ? <X className="h-5 w-5" /> : <Menu className="h-5 w-5" />}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
id="mobile-nav"
|
||||
className={
|
||||
`overflow-hidden bg-black/95 backdrop-blur-xl transition-[max-height,opacity] duration-200 ease-in-out md:hidden ` +
|
||||
(isOpen
|
||||
? "max-h-[420px] border-b border-white/10 opacity-100"
|
||||
: "pointer-events-none max-h-0 opacity-0")
|
||||
}
|
||||
aria-hidden={!isOpen}
|
||||
>
|
||||
<nav className="flex flex-col gap-4 p-4 text-sm font-medium text-zinc-400">
|
||||
<Link
|
||||
href="/#features"
|
||||
className="transition-colors hover:text-cyan-400"
|
||||
onClick={() => setIsOpen(false)}
|
||||
>
|
||||
{t("features")}
|
||||
</Link>
|
||||
<Link
|
||||
href="/#agents"
|
||||
className="transition-colors hover:text-cyan-400"
|
||||
onClick={() => setIsOpen(false)}
|
||||
>
|
||||
{t("agents")}
|
||||
</Link>
|
||||
<Link
|
||||
href="/docs"
|
||||
className="transition-colors hover:text-cyan-400"
|
||||
onClick={() => setIsOpen(false)}
|
||||
>
|
||||
{t("docs")}
|
||||
</Link>
|
||||
<Link
|
||||
href="/manifesto"
|
||||
className="transition-colors hover:text-cyan-400"
|
||||
onClick={() => setIsOpen(false)}
|
||||
>
|
||||
{t("manifesto")}
|
||||
</Link>
|
||||
<a
|
||||
href="https://github.com/code-yeongyu/oh-my-openagent"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-2 transition-colors hover:text-cyan-400 sm:hidden"
|
||||
onClick={() => setIsOpen(false)}
|
||||
>
|
||||
<Github className="h-4 w-4" />
|
||||
<span>{t("starOnGitHub")}</span>
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as AccordionPrimitive from "@radix-ui/react-accordion"
|
||||
import { ChevronDown } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Accordion = AccordionPrimitive.Root
|
||||
|
||||
const AccordionItem = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AccordionPrimitive.Item ref={ref} className={cn("border-b", className)} {...props} />
|
||||
))
|
||||
AccordionItem.displayName = "AccordionItem"
|
||||
|
||||
const AccordionTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<AccordionPrimitive.Header className="flex">
|
||||
<AccordionPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex flex-1 items-center justify-between py-4 text-left text-sm font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronDown className="text-muted-foreground h-4 w-4 shrink-0 transition-transform duration-200" />
|
||||
</AccordionPrimitive.Trigger>
|
||||
</AccordionPrimitive.Header>
|
||||
))
|
||||
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
|
||||
|
||||
const AccordionContent = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<AccordionPrimitive.Content
|
||||
ref={ref}
|
||||
className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
|
||||
{...props}
|
||||
>
|
||||
<div className={cn("pt-0 pb-4", className)}>{children}</div>
|
||||
</AccordionPrimitive.Content>
|
||||
))
|
||||
AccordionContent.displayName = AccordionPrimitive.Content.displayName
|
||||
|
||||
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
|
||||
@@ -0,0 +1,31 @@
|
||||
import * as React from "react"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const badgeVariants = cva(
|
||||
"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "border-transparent bg-primary text-primary-foreground shadow",
|
||||
secondary: "border-transparent bg-secondary text-secondary-foreground",
|
||||
destructive: "border-transparent bg-destructive text-destructive-foreground shadow",
|
||||
outline: "text-foreground",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
export interface BadgeProps
|
||||
extends React.HTMLAttributes<HTMLSpanElement>,
|
||||
VariantProps<typeof badgeVariants> {}
|
||||
|
||||
function Badge({ className, variant, ...props }: BadgeProps) {
|
||||
return <span className={cn(badgeVariants({ variant }), className)} {...props} />
|
||||
}
|
||||
|
||||
export { Badge, badgeVariants }
|
||||
@@ -0,0 +1,50 @@
|
||||
import * as React from "react"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
|
||||
destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
|
||||
outline:
|
||||
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
|
||||
secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
default: "h-9 px-4 py-2",
|
||||
sm: "h-8 rounded-md px-3 text-xs",
|
||||
lg: "h-10 rounded-md px-8",
|
||||
icon: "h-9 w-9",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean
|
||||
}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : "button"
|
||||
return (
|
||||
<Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />
|
||||
)
|
||||
},
|
||||
)
|
||||
Button.displayName = "Button"
|
||||
|
||||
export { Button, buttonVariants }
|
||||
@@ -0,0 +1,55 @@
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("bg-card text-card-foreground rounded-xl border shadow", className)}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
)
|
||||
Card.displayName = "Card"
|
||||
|
||||
const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn("flex flex-col space-y-1.5 p-6", className)} {...props} />
|
||||
),
|
||||
)
|
||||
CardHeader.displayName = "CardHeader"
|
||||
|
||||
const CardTitle = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("leading-none font-semibold tracking-tight", className)}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
)
|
||||
CardTitle.displayName = "CardTitle"
|
||||
|
||||
const CardDescription = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn("text-muted-foreground text-sm", className)} {...props} />
|
||||
),
|
||||
)
|
||||
CardDescription.displayName = "CardDescription"
|
||||
|
||||
const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
||||
),
|
||||
)
|
||||
CardContent.displayName = "CardContent"
|
||||
|
||||
const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn("flex items-center p-6 pt-0", className)} {...props} />
|
||||
),
|
||||
)
|
||||
CardFooter.displayName = "CardFooter"
|
||||
|
||||
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
||||
@@ -0,0 +1,23 @@
|
||||
import * as React from "react"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
interface CodeBlockProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
code: string
|
||||
language?: string
|
||||
}
|
||||
|
||||
export function CodeBlock({ code, language = "json", className, ...props }: CodeBlockProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"border-border/50 relative my-4 overflow-x-auto rounded-lg border bg-[#1e1e2e] p-4 font-mono text-sm text-[#cdd6f4] shadow-sm",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<pre>
|
||||
<code className={`language-${language}`}>{code}</code>
|
||||
</pre>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
|
||||
({ className, type, ...props }, ref) => {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
"border-input file:text-foreground placeholder:text-muted-foreground focus-visible:ring-ring flex h-9 w-full rounded-md border bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-1 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
},
|
||||
)
|
||||
Input.displayName = "Input"
|
||||
|
||||
export { Input }
|
||||
@@ -0,0 +1,48 @@
|
||||
import * as React from "react"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
export interface Option {
|
||||
name: string
|
||||
type: string
|
||||
default?: string
|
||||
description: string
|
||||
}
|
||||
|
||||
interface OptionTableProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
options: Option[]
|
||||
}
|
||||
|
||||
export function OptionTable({ options, className, ...props }: OptionTableProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn("border-border my-4 w-full overflow-x-auto rounded-lg border", className)}
|
||||
{...props}
|
||||
>
|
||||
<table className="w-full text-sm">
|
||||
<thead className="bg-muted/50 text-left font-medium">
|
||||
<tr>
|
||||
<th className="p-3">Option</th>
|
||||
<th className="p-3">Type</th>
|
||||
<th className="p-3">Default</th>
|
||||
<th className="p-3">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-border divide-y">
|
||||
{options.map((opt) => (
|
||||
<tr key={opt.name}>
|
||||
<td className="text-primary p-3 font-mono font-medium">{opt.name}</td>
|
||||
<td className="p-3">
|
||||
<Badge variant="outline" className="font-mono text-xs">
|
||||
{opt.type}
|
||||
</Badge>
|
||||
</td>
|
||||
<td className="text-muted-foreground p-3 font-mono">{opt.default || "-"}</td>
|
||||
<td className="text-muted-foreground p-3">{opt.description}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import * as React from "react"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
interface SectionProps extends React.ComponentPropsWithoutRef<"section"> {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export function Section({ children, className, ...props }: SectionProps) {
|
||||
return (
|
||||
<section className={cn("px-6 py-24 md:py-32", className)} {...props}>
|
||||
{children}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as SeparatorPrimitive from "@radix-ui/react-separator"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Separator = React.forwardRef<
|
||||
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
||||
>(({ className, orientation = "horizontal", decorative = true, ...props }, ref) => (
|
||||
<SeparatorPrimitive.Root
|
||||
ref={ref}
|
||||
decorative={decorative}
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
"bg-border shrink-0",
|
||||
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Separator.displayName = SeparatorPrimitive.Root.displayName
|
||||
|
||||
export { Separator }
|
||||
@@ -0,0 +1,168 @@
|
||||
import { test, expect } from "@playwright/test"
|
||||
|
||||
test.describe("Landing Page", () => {
|
||||
test("renders hero section with title and CTA", async ({ page }) => {
|
||||
// given
|
||||
await page.goto("/")
|
||||
|
||||
// when
|
||||
const heading = page.getByRole("heading", { name: "The Best Agent Harness", level: 1 })
|
||||
const getStartedButton = page.getByRole("button", { name: "Get Started" })
|
||||
|
||||
// then
|
||||
await expect(page).toHaveTitle(/Oh My OpenAgent/)
|
||||
await expect(heading).toBeVisible()
|
||||
await expect(getStartedButton).toBeVisible()
|
||||
})
|
||||
|
||||
test("renders install command", async ({ page }) => {
|
||||
// given
|
||||
await page.goto("/")
|
||||
|
||||
// when
|
||||
const installCommand = page.getByText("bunx oh-my-openagent install").first()
|
||||
|
||||
// then
|
||||
await expect(installCommand).toBeVisible()
|
||||
})
|
||||
|
||||
test("renders all 10 agent cards", async ({ page }) => {
|
||||
// given
|
||||
await page.goto("/")
|
||||
|
||||
// when / then
|
||||
const agentNames = [
|
||||
"Sisyphus",
|
||||
"Hephaestus",
|
||||
"Oracle",
|
||||
"Librarian",
|
||||
"Explore",
|
||||
"Prometheus",
|
||||
"Metis",
|
||||
"Momus",
|
||||
"Atlas",
|
||||
"Sisyphus Junior",
|
||||
]
|
||||
for (const name of agentNames) {
|
||||
await expect(page.getByText(name, { exact: true }).first()).toBeVisible()
|
||||
}
|
||||
})
|
||||
|
||||
test("mobile nav toggles menu", async ({ page }) => {
|
||||
// given
|
||||
await page.setViewportSize({ width: 375, height: 800 })
|
||||
await page.goto("/")
|
||||
|
||||
const mobileNav = page.locator("#mobile-nav")
|
||||
await expect(mobileNav).toBeHidden()
|
||||
|
||||
// when
|
||||
await page.getByRole("button", { name: "Open menu" }).click()
|
||||
|
||||
// then
|
||||
await expect(mobileNav).toBeVisible()
|
||||
await expect(mobileNav.getByRole("link", { name: "Docs", exact: true })).toBeVisible()
|
||||
await expect(mobileNav.getByRole("link", { name: "Manifesto", exact: true })).toBeVisible()
|
||||
})
|
||||
|
||||
test("navigates to docs page", async ({ page }) => {
|
||||
// given
|
||||
await page.goto("/")
|
||||
|
||||
// when
|
||||
await Promise.all([
|
||||
page.waitForURL("**/docs", { timeout: 15000 }),
|
||||
page.getByRole("navigation").getByRole("link", { name: "Docs", exact: true }).click(),
|
||||
])
|
||||
|
||||
// then
|
||||
await expect(page).toHaveURL(/\/docs/)
|
||||
await expect(page.getByRole("heading", { name: "Configuration Reference" })).toBeVisible()
|
||||
})
|
||||
|
||||
test("navigates to manifesto page", async ({ page }) => {
|
||||
// given
|
||||
await page.goto("/")
|
||||
|
||||
// when
|
||||
await Promise.all([
|
||||
page.waitForURL("**/manifesto", { timeout: 15000 }),
|
||||
page.getByRole("navigation").getByRole("link", { name: "Manifesto", exact: true }).click(),
|
||||
])
|
||||
|
||||
// then
|
||||
await expect(page).toHaveURL(/\/manifesto/)
|
||||
await expect(page.getByRole("heading", { name: "Ultrawork Manifesto" })).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
test.describe("Docs Page", () => {
|
||||
test("renders sidebar and config reference", async ({ page }) => {
|
||||
// given
|
||||
await page.goto("/docs")
|
||||
|
||||
// when
|
||||
const heading = page.getByRole("heading", { name: "Configuration Reference" })
|
||||
const sidebarItems = ["Overview", "Quick Start", "Agents", "Categories", "Skills", "Hooks"]
|
||||
|
||||
// then
|
||||
await expect(heading).toBeVisible()
|
||||
for (const item of sidebarItems) {
|
||||
await expect(page.getByRole("button", { name: item })).toBeVisible()
|
||||
}
|
||||
})
|
||||
|
||||
test("has working search input", async ({ page }) => {
|
||||
// given
|
||||
await page.goto("/docs")
|
||||
const searchInput = page.getByPlaceholder("Search docs...")
|
||||
|
||||
// when
|
||||
await searchInput.fill("agent")
|
||||
|
||||
// then
|
||||
await expect(page.getByRole("button", { name: "Agents" })).toBeVisible()
|
||||
})
|
||||
|
||||
test("sidebar navigation scrolls instantly and highlights active section", async ({ page }) => {
|
||||
// given
|
||||
await page.goto("/docs")
|
||||
const quickStartButton = page.getByRole("button", { name: "Quick Start" })
|
||||
|
||||
// when
|
||||
await quickStartButton.click()
|
||||
|
||||
// then
|
||||
await expect(page.locator("#quick-start")).toBeInViewport()
|
||||
await expect(quickStartButton).toHaveClass(/bg-primary\/10/)
|
||||
})
|
||||
})
|
||||
|
||||
test.describe("Manifesto Page", () => {
|
||||
test("renders hero and core philosophy", async ({ page }) => {
|
||||
test.setTimeout(60000)
|
||||
// given
|
||||
await page.goto("/manifesto", { waitUntil: "domcontentloaded", timeout: 45000 })
|
||||
|
||||
// when
|
||||
const heading = page.getByRole("heading", { name: "Ultrawork Manifesto" })
|
||||
const bottleneckText = page.getByText("HUMAN IN THE LOOP = BOTTLENECK").first()
|
||||
|
||||
// then
|
||||
await expect(heading).toBeVisible()
|
||||
await expect(bottleneckText).toBeVisible()
|
||||
})
|
||||
|
||||
test("renders CTA with GitHub link", async ({ page }) => {
|
||||
test.setTimeout(60000)
|
||||
// given
|
||||
await page.goto("/manifesto", { waitUntil: "domcontentloaded", timeout: 45000 })
|
||||
|
||||
// when
|
||||
const ctaLink = page.getByRole("link", { name: /Get Oh My OpenAgent/i })
|
||||
|
||||
// then
|
||||
await expect(ctaLink).toBeVisible()
|
||||
await expect(ctaLink).toHaveAttribute("href", "https://github.com/code-yeongyu/oh-my-openagent")
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,23 @@
|
||||
import { test, expect } from "@playwright/test"
|
||||
|
||||
test("homepage renders heading", async ({ page }) => {
|
||||
// given
|
||||
await page.goto("/")
|
||||
|
||||
// when
|
||||
const heading = page.getByRole("heading", { name: "Oh My OpenCode", level: 1 })
|
||||
|
||||
// then
|
||||
await expect(heading).toBeVisible()
|
||||
})
|
||||
|
||||
test("homepage renders description", async ({ page }) => {
|
||||
// given
|
||||
await page.goto("/")
|
||||
|
||||
// when
|
||||
const description = page.getByText("The Best Agent Harness").first()
|
||||
|
||||
// then
|
||||
await expect(description).toBeVisible()
|
||||
})
|
||||
@@ -0,0 +1,36 @@
|
||||
import { test, expect } from "@playwright/test"
|
||||
|
||||
test.describe("Hero Stats", () => {
|
||||
test("displays GitHub star count", async ({ page }) => {
|
||||
// given
|
||||
await page.goto("/")
|
||||
|
||||
// when
|
||||
const starStat = page.locator("text=/[\\d.]+k GitHub Stars/")
|
||||
|
||||
// then
|
||||
await expect(starStat).toBeVisible()
|
||||
})
|
||||
|
||||
test("displays total download count", async ({ page }) => {
|
||||
// given
|
||||
await page.goto("/")
|
||||
|
||||
// when
|
||||
const totalDownloads = page.locator("text=/[\\d.]+[kM]\\+? Total Downloads/")
|
||||
|
||||
// then
|
||||
await expect(totalDownloads).toBeVisible()
|
||||
})
|
||||
|
||||
test("displays monthly download count", async ({ page }) => {
|
||||
// given
|
||||
await page.goto("/")
|
||||
|
||||
// when
|
||||
const monthlyDownloads = page.locator("text=/[\\d.]+[kM]\\+? Monthly Downloads/")
|
||||
|
||||
// then
|
||||
await expect(monthlyDownloads).toBeVisible()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,9 @@
|
||||
declare module "eslint-config-next/core-web-vitals" {
|
||||
const config: unknown
|
||||
export default config
|
||||
}
|
||||
|
||||
declare module "eslint-config-prettier/flat" {
|
||||
const config: unknown
|
||||
export default config
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import globals from "globals"
|
||||
import nextPlugin from "@next/eslint-plugin-next"
|
||||
import prettier from "eslint-config-prettier/flat"
|
||||
import tseslint from "typescript-eslint"
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: [".next/**", "out/**", "build/**", "next-env.d.ts"],
|
||||
},
|
||||
{
|
||||
files: ["**/*.{js,mjs,cjs,ts,tsx,jsx}"],
|
||||
languageOptions: {
|
||||
ecmaVersion: "latest",
|
||||
sourceType: "module",
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.nodeBuiltin,
|
||||
},
|
||||
},
|
||||
},
|
||||
nextPlugin.flatConfig.coreWebVitals,
|
||||
...tseslint.configs.recommended,
|
||||
prettier,
|
||||
{
|
||||
rules: {
|
||||
"@typescript-eslint/no-explicit-any": "error",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
argsIgnorePattern: "^_",
|
||||
varsIgnorePattern: "^_",
|
||||
},
|
||||
],
|
||||
"@typescript-eslint/consistent-type-imports": [
|
||||
"error",
|
||||
{
|
||||
prefer: "type-imports",
|
||||
},
|
||||
],
|
||||
"no-console": ["warn", { allow: ["warn", "error"] }],
|
||||
"prefer-const": "error",
|
||||
"no-var": "error",
|
||||
},
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,5 @@
|
||||
export const locales = ["en", "ko", "ja", "zh"] as const
|
||||
|
||||
export type Locale = (typeof locales)[number]
|
||||
|
||||
export const defaultLocale: Locale = "en"
|
||||
@@ -0,0 +1,16 @@
|
||||
import { getRequestConfig } from "next-intl/server"
|
||||
import type { Locale } from "./config"
|
||||
import { routing } from "./routing"
|
||||
|
||||
export default getRequestConfig(async ({ requestLocale }) => {
|
||||
const requested = await requestLocale
|
||||
const locale: Locale =
|
||||
requested && routing.locales.includes(requested as Locale)
|
||||
? (requested as Locale)
|
||||
: routing.defaultLocale
|
||||
|
||||
return {
|
||||
locale,
|
||||
messages: (await import(`../messages/${locale}.json`)).default,
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,12 @@
|
||||
import { defineRouting } from "next-intl/routing"
|
||||
import { createNavigation } from "next-intl/navigation"
|
||||
import { locales, defaultLocale } from "./config"
|
||||
|
||||
export const routing = defineRouting({
|
||||
locales,
|
||||
defaultLocale,
|
||||
localePrefix: "as-needed",
|
||||
localeDetection: true,
|
||||
})
|
||||
|
||||
export const { Link, redirect, usePathname, useRouter, getPathname } = createNavigation(routing)
|
||||
@@ -0,0 +1,39 @@
|
||||
export const DOC_SECTION_IDS = [
|
||||
"overview",
|
||||
"quick-start",
|
||||
"config-locations",
|
||||
"agents",
|
||||
"categories",
|
||||
"skills",
|
||||
"background-tasks",
|
||||
"hooks",
|
||||
"mcps",
|
||||
"browser-automation",
|
||||
"tmux",
|
||||
"git-master",
|
||||
"comment-checker",
|
||||
"experimental",
|
||||
"lsp",
|
||||
"env-vars",
|
||||
] as const
|
||||
|
||||
export type DocSectionId = (typeof DOC_SECTION_IDS)[number]
|
||||
|
||||
export const DOC_SECTION_TITLE_KEYS: Record<DocSectionId, string> = {
|
||||
overview: "overview",
|
||||
"quick-start": "quickStart",
|
||||
"config-locations": "configLocations",
|
||||
agents: "agents",
|
||||
categories: "categories",
|
||||
skills: "skills",
|
||||
"background-tasks": "backgroundTasks",
|
||||
hooks: "hooks",
|
||||
mcps: "mcps",
|
||||
"browser-automation": "browserAutomation",
|
||||
tmux: "tmux",
|
||||
"git-master": "gitMaster",
|
||||
"comment-checker": "commentChecker",
|
||||
experimental: "experimental",
|
||||
lsp: "lsp",
|
||||
"env-vars": "envVars",
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
const GITHUB_OWNER = "code-yeongyu"
|
||||
const GITHUB_REPO = "oh-my-openagent"
|
||||
const NPM_PACKAGES = ["oh-my-opencode", "oh-my-openagent"]
|
||||
const NPM_FIRST_PUBLISH_YEAR = 2025
|
||||
|
||||
const CACHE_TTL_MS = 60 * 60 * 1000
|
||||
|
||||
interface StatsCache {
|
||||
data: StatsData
|
||||
timestamp: number
|
||||
}
|
||||
|
||||
export interface StatsData {
|
||||
stars: number
|
||||
totalDownloads: number
|
||||
monthlyDownloads: number
|
||||
weeklyDownloads: number
|
||||
}
|
||||
|
||||
let cache: StatsCache | null = null
|
||||
|
||||
function formatCount(num: number): string {
|
||||
if (num >= 1_000_000) {
|
||||
const formatted = (num / 1_000_000).toFixed(1)
|
||||
return `${formatted.replace(/\.0$/, "")}M+`
|
||||
}
|
||||
if (num >= 1_000) {
|
||||
const formatted = (num / 1_000).toFixed(1)
|
||||
return `${formatted.replace(/\.0$/, "")}k`
|
||||
}
|
||||
return String(num)
|
||||
}
|
||||
|
||||
async function fetchGitHubStars(): Promise<number> {
|
||||
const headers: Record<string, string> = {
|
||||
Accept: "application/vnd.github.v3+json",
|
||||
"User-Agent": "oh-my-openagent-web",
|
||||
}
|
||||
|
||||
const token = process.env.GITHUB_TOKEN
|
||||
if (token) {
|
||||
headers.Authorization = `Bearer ${token}`
|
||||
}
|
||||
|
||||
const res = await fetch(`https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}`, {
|
||||
headers,
|
||||
next: { revalidate: 3600 },
|
||||
})
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`GitHub API error: ${res.status}`)
|
||||
}
|
||||
|
||||
const data = await res.json()
|
||||
return data.stargazers_count
|
||||
}
|
||||
|
||||
async function fetchNpmDownloadsForPackage(period: string, pkg: string): Promise<number> {
|
||||
try {
|
||||
const res = await fetch(`https://api.npmjs.org/downloads/point/${period}/${pkg}`, {
|
||||
next: { revalidate: 3600 },
|
||||
})
|
||||
|
||||
if (!res.ok) return 0
|
||||
|
||||
const data = await res.json()
|
||||
return data.downloads ?? 0
|
||||
} catch {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchNpmDownloads(period: string): Promise<number> {
|
||||
const results = await Promise.all(
|
||||
NPM_PACKAGES.map((pkg) => fetchNpmDownloadsForPackage(period, pkg)),
|
||||
)
|
||||
return results.reduce((sum, n) => sum + n, 0)
|
||||
}
|
||||
|
||||
async function fetchAllNpmDownloadsForPackage(pkg: string): Promise<number> {
|
||||
const now = new Date()
|
||||
let total = 0
|
||||
let year = NPM_FIRST_PUBLISH_YEAR
|
||||
|
||||
while (year <= now.getFullYear()) {
|
||||
const start = `${year}-01-01`
|
||||
const endDate = new Date(year, 11, 31)
|
||||
const end = endDate > now ? now.toISOString().split("T")[0]! : `${year}-12-31`
|
||||
|
||||
try {
|
||||
const res = await fetch(`https://api.npmjs.org/downloads/point/${start}:${end}/${pkg}`, {
|
||||
next: { revalidate: 3600 },
|
||||
})
|
||||
if (res.ok) {
|
||||
const data = await res.json()
|
||||
total += data.downloads ?? 0
|
||||
}
|
||||
} catch {
|
||||
continue
|
||||
}
|
||||
year++
|
||||
}
|
||||
|
||||
return total
|
||||
}
|
||||
|
||||
async function fetchAllNpmDownloads(): Promise<number> {
|
||||
const results = await Promise.all(NPM_PACKAGES.map((pkg) => fetchAllNpmDownloadsForPackage(pkg)))
|
||||
return results.reduce((sum, n) => sum + n, 0)
|
||||
}
|
||||
|
||||
export async function getStats(): Promise<StatsData> {
|
||||
const now = Date.now()
|
||||
|
||||
if (cache && now - cache.timestamp < CACHE_TTL_MS) {
|
||||
return cache.data
|
||||
}
|
||||
|
||||
const [stars, monthlyDownloads, weeklyDownloads, totalDownloads] = await Promise.all([
|
||||
fetchGitHubStars(),
|
||||
fetchNpmDownloads("last-month"),
|
||||
fetchNpmDownloads("last-week"),
|
||||
fetchAllNpmDownloads(),
|
||||
])
|
||||
|
||||
const data: StatsData = { stars, totalDownloads, monthlyDownloads, weeklyDownloads }
|
||||
cache = { data, timestamp: now }
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
export function formatStats(stats: StatsData) {
|
||||
return {
|
||||
stars: formatCount(stats.stars),
|
||||
totalDownloads: formatCount(stats.totalDownloads),
|
||||
monthlyDownloads: formatCount(stats.monthlyDownloads),
|
||||
weeklyDownloads: formatCount(stats.weeklyDownloads),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { type ClassValue, clsx } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
@@ -0,0 +1,561 @@
|
||||
{
|
||||
"metadata": {
|
||||
"title": "Oh My OpenAgent",
|
||||
"description": "The Best Agent Harness. Meet Sisyphus: The Batteries-Included Agent that codes like you."
|
||||
},
|
||||
"nav": {
|
||||
"brand": "Oh My OpenAgent",
|
||||
"features": "Features",
|
||||
"agents": "Agents",
|
||||
"docs": "Docs",
|
||||
"manifesto": "Manifesto",
|
||||
"starOnGitHub": "Star on GitHub"
|
||||
},
|
||||
"footer": {
|
||||
"brand": "Oh My OpenAgent",
|
||||
"copyright": "© {year} Oh My OpenAgent. Open source under SUL-1.0.",
|
||||
"github": "GitHub",
|
||||
"discord": "Discord",
|
||||
"documentation": "Documentation",
|
||||
"manifesto": "Manifesto"
|
||||
},
|
||||
"landing": {
|
||||
"hero": {
|
||||
"title": "The Best ",
|
||||
"titleHighlight": "Agent Harness",
|
||||
"subtitle": "{stars} stars. {downloads} downloads. Battle-tested and production-proven.",
|
||||
"githubStars": "{count} GitHub Stars",
|
||||
"specializedAgents": "{count} Specialized Agents",
|
||||
"totalDownloads": "{count} Total Downloads",
|
||||
"monthlyDownloads": "{count} Monthly Downloads",
|
||||
"lifecycleHooks": "{count} Lifecycle Hooks",
|
||||
"installCommand": "bunx oh-my-openagent install",
|
||||
"getStarted": "Get Started",
|
||||
"viewOnGitHub": "View on GitHub"
|
||||
},
|
||||
"ulw": {
|
||||
"badge": "The Magic Word",
|
||||
"title": "ulw",
|
||||
"headline": "Type Three Letters. Walk Away.",
|
||||
"description": "Ultra Work mode triggers maximum precision: auto-planning, deep research, parallel agents, and self-correction loops. The system doesn't stop until it's done. You don't babysit.",
|
||||
"autoPlanning": "Auto-Planning",
|
||||
"deepResearch": "Deep Research",
|
||||
"selfCorrection": "Self-Correction",
|
||||
"parallelAgents": "Parallel Agents",
|
||||
"terminalTitle": "sisyphus — -zsh — 80x24",
|
||||
"terminalInput": "ulw add authentication to the API",
|
||||
"steps": {
|
||||
"scanning": "Scanning codebase...",
|
||||
"context": "Launching 3 explore agents...",
|
||||
"planning": "Building execution plan...",
|
||||
"delegating": "Deploying 4 parallel agents...",
|
||||
"verifying": "Running verification suite...",
|
||||
"complete": "Done. 847 lines changed, 0 issues."
|
||||
},
|
||||
"tagline": "Zero intervention. Full autonomy. Just results."
|
||||
},
|
||||
"sisyphus": {
|
||||
"badge": "Primary Orchestrator",
|
||||
"title": "Sisyphus",
|
||||
"headline": "The CTO Who Never Sleeps",
|
||||
"description": "Named after the mythological figure rolling his boulder uphill forever. Sisyphus parses implicit requirements, adapts to your codebase's maturity, and delegates to specialists. Interrupted? The boulder system resumes exactly where you left off.",
|
||||
"phases": {
|
||||
"intent": {
|
||||
"title": "Intent Gate",
|
||||
"description": "Parses what you meant, not just what you typed"
|
||||
},
|
||||
"explore": {
|
||||
"title": "Codebase Assessment",
|
||||
"description": "Maps your architecture before touching a line"
|
||||
},
|
||||
"delegate": {
|
||||
"title": "Smart Delegation",
|
||||
"description": "Routes to the right specialist agent"
|
||||
},
|
||||
"verify": {
|
||||
"title": "Independent Verification",
|
||||
"description": "Trusts nothing. Verifies everything."
|
||||
}
|
||||
},
|
||||
"boulderTitle": "Session Continuity",
|
||||
"boulderDescription": "Active work tracked in boulder.json. Power outage? System crash? Doesn't matter. Pick up exactly where you stopped.",
|
||||
"model": "Claude Opus 4.6 Max"
|
||||
},
|
||||
"prometheusAtlas": {
|
||||
"badge": "Orchestration Workflow",
|
||||
"title": "Think, Then Act",
|
||||
"headline": "Planning and Execution Are Different Jobs",
|
||||
"prometheus": {
|
||||
"name": "Prometheus",
|
||||
"role": "Strategic Planner",
|
||||
"model": "Claude Opus 4.6 Max",
|
||||
"description": "The architect. Interviews you, explores your codebase, and creates detailed battle plans. Never writes code. Uses Metis and Momus as quality gates.",
|
||||
"features": [
|
||||
"Intelligent interview mode",
|
||||
"Multi-agent codebase exploration",
|
||||
"Gap analysis with Metis",
|
||||
"Ruthless review with Momus"
|
||||
]
|
||||
},
|
||||
"atlas": {
|
||||
"name": "Atlas",
|
||||
"role": "Master Executor",
|
||||
"model": "Claude Sonnet 4.6",
|
||||
"description": "The builder. Reads verified plans, delegates to specialized agents via category+skills system. Tracks learnings across tasks. Verifies independently — never trusts subagent claims.",
|
||||
"features": [
|
||||
"Intent-based task routing",
|
||||
"Wisdom accumulation across tasks",
|
||||
"Boulder session continuity",
|
||||
"Independent result verification"
|
||||
]
|
||||
},
|
||||
"metis": {
|
||||
"name": "Metis",
|
||||
"description": "Catches ambiguities before they become bugs."
|
||||
},
|
||||
"momus": {
|
||||
"name": "Momus",
|
||||
"description": "Only says OKAY when the plan is actually perfect."
|
||||
},
|
||||
"workflow": {
|
||||
"step1": "Describe your work to Prometheus",
|
||||
"step2": "Metis analyzes for gaps",
|
||||
"step3": "Momus validates ruthlessly",
|
||||
"step4": "Atlas delegates to specialists",
|
||||
"step5": "Independent verification on everything"
|
||||
},
|
||||
"whyItWorks": "The planner doesn't code. The executor follows verified plans. Intelligence lives in the system, not individual agents."
|
||||
},
|
||||
"hephaestus": {
|
||||
"badge": "Deep Worker",
|
||||
"title": "Hephaestus",
|
||||
"headline": "Give Him a Goal, Not a Recipe",
|
||||
"description": "Named after the Greek god of the forge. Methodical, thorough, obsessive. Fires 2-5 parallel explore agents before writing any code. Built for deep architectural reasoning, complex debugging, and cross-domain synthesis.",
|
||||
"loop": {
|
||||
"explore": "EXPLORE — Map the terrain",
|
||||
"plan": "PLAN — Chart the course",
|
||||
"decide": "DECIDE — Commit to the path",
|
||||
"execute": "EXECUTE — Build with precision",
|
||||
"verify": "VERIFY — Prove it works"
|
||||
},
|
||||
"tagline": "For when good enough isn't.",
|
||||
"model": "GPT 5.3 Codex Medium"
|
||||
},
|
||||
"agents": {
|
||||
"title": "Specialized Agents",
|
||||
"subtitle": "Five built-in agents for common workflows. Plus a dynamic system that assembles custom agents on demand.",
|
||||
"oracle": {
|
||||
"name": "Oracle",
|
||||
"role": "Architecture Consultant",
|
||||
"model": "GPT 5.4 High",
|
||||
"description": "Complex debugging and architectural decisions. When the path forward isn't obvious."
|
||||
},
|
||||
"librarian": {
|
||||
"name": "Librarian",
|
||||
"role": "Docs & Code Search",
|
||||
"model": "MiniMax M2.5",
|
||||
"description": "Finds real GitHub examples and official docs. Evidence-based, with permalinks."
|
||||
},
|
||||
"explore": {
|
||||
"name": "Explore",
|
||||
"role": "Codebase Grep",
|
||||
"model": "MiniMax M2.5",
|
||||
"description": "Blazing fast codebase search. Cheap, parallel, always background."
|
||||
},
|
||||
"metis": {
|
||||
"name": "Metis",
|
||||
"role": "Plan Consultant",
|
||||
"model": "Claude Opus 4.6",
|
||||
"description": "Catches ambiguities in plans before they become production bugs."
|
||||
},
|
||||
"momus": {
|
||||
"name": "Momus",
|
||||
"role": "Plan Reviewer",
|
||||
"model": "GPT 5.4",
|
||||
"description": "Ruthless validation. Only approves when the plan is bulletproof."
|
||||
},
|
||||
"dynamicSystem": {
|
||||
"name": "Dynamic Agent",
|
||||
"role": "Assembled for the task at hand",
|
||||
"description": "Matches your request to the right model, loads the necessary skills, and builds an agent in real-time."
|
||||
}
|
||||
},
|
||||
"architecture": {
|
||||
"title": "Built Different",
|
||||
"subtitle": "Design decisions that actually matter.",
|
||||
"principles": {
|
||||
"specialization": {
|
||||
"title": "Specialization",
|
||||
"description": "Each agent does one thing exceptionally well. No jacks of all trades."
|
||||
},
|
||||
"trustVerify": {
|
||||
"title": "Trust But Verify",
|
||||
"description": "Orchestrator runs independent verification on everything. Subagents don't get a free pass."
|
||||
},
|
||||
"wisdom": {
|
||||
"title": "Wisdom Accumulation",
|
||||
"description": "Learnings from each task passed to all subsequent tasks. The system gets smarter as it works."
|
||||
},
|
||||
"modelOptimization": {
|
||||
"title": "Model Optimization",
|
||||
"description": "Expensive models for planning and complex decisions. Cheap models for routine work. Maximum output per dollar."
|
||||
},
|
||||
"categories": {
|
||||
"title": "Category System",
|
||||
"description": "Intent-based routing. Say what you need — ultrabrain, visual-engineering, quick — and the right model handles it."
|
||||
},
|
||||
"continuity": {
|
||||
"title": "Session Continuity",
|
||||
"description": "Boulder system: interrupted work resumes exactly where it stopped. Zero context loss."
|
||||
}
|
||||
}
|
||||
},
|
||||
"reviews": {
|
||||
"title": "What Devs Actually Say",
|
||||
"review1": {
|
||||
"text": "It made me cancel my Cursor subscription. Unbelievable things are happening in the open source community.",
|
||||
"author": "Arthur Guiot"
|
||||
},
|
||||
"review2": {
|
||||
"text": "If Claude Code does in 7 days what a human does in 3 months, Sisyphus does it in 1 hour.",
|
||||
"author": "B, Quant Researcher"
|
||||
},
|
||||
"review3": {
|
||||
"text": "Knocked out 8000 eslint warnings with Oh My Opencode, just in a day.",
|
||||
"author": "Jacob Ferrari"
|
||||
},
|
||||
"review4": {
|
||||
"text": "use oh-my-openagent, you will never go back",
|
||||
"author": "d0t3ch"
|
||||
},
|
||||
"review5": {
|
||||
"text": "You guys should pull this into core and recruit him. Seriously. It's really, really, really good.",
|
||||
"author": "Henning Kilset"
|
||||
},
|
||||
"review6": {
|
||||
"text": "Oh My OpenCode Is Actually Insane",
|
||||
"author": "Darren Builds AI (YouTube)"
|
||||
}
|
||||
},
|
||||
"cta": {
|
||||
"title": "Stop Writing Boilerplate",
|
||||
"subtitle": "Install Oh My OpenAgent and let the agents handle it. Your first ulw command is 30 seconds away.",
|
||||
"installCommand": "bunx oh-my-openagent install",
|
||||
"installNow": "Install Now",
|
||||
"readTheDocs": "Read the Docs"
|
||||
}
|
||||
},
|
||||
"docs": {
|
||||
"mobileHeader": "Oh My OpenAgent Docs",
|
||||
"searchPlaceholder": "Search docs...",
|
||||
"sections": {
|
||||
"overview": "Overview",
|
||||
"quickStart": "Quick Start",
|
||||
"configLocations": "Config File Locations",
|
||||
"agents": "Agents",
|
||||
"categories": "Categories",
|
||||
"skills": "Skills",
|
||||
"backgroundTasks": "Background Tasks",
|
||||
"hooks": "Hooks",
|
||||
"mcps": "MCPs",
|
||||
"browserAutomation": "Browser Automation",
|
||||
"tmux": "Tmux Integration",
|
||||
"gitMaster": "Git Master",
|
||||
"commentChecker": "Comment Checker",
|
||||
"experimental": "Experimental Features",
|
||||
"lsp": "LSP Configuration",
|
||||
"envVars": "Environment Variables"
|
||||
},
|
||||
"overview": {
|
||||
"title": "Configuration Reference",
|
||||
"description": "Oh My OpenAgent is highly opinionated but adjustable to taste. Most users don't need to configure anything — run {command} and go."
|
||||
},
|
||||
"quickStart": {
|
||||
"title": "Quick Start"
|
||||
},
|
||||
"configLocations": {
|
||||
"title": "Config File Locations",
|
||||
"projectLevel": "(Project level)",
|
||||
"userLevel": "(User level)",
|
||||
"jsonc": "JSONC is supported, allowing comments and trailing commas."
|
||||
},
|
||||
"agentsSection": {
|
||||
"title": "Agents",
|
||||
"description": "Configure specific behaviors for the built-in agents: Sisyphus, Hephaestus, Oracle, Librarian, Explore, Multimodal Looker, Prometheus, Metis, Momus, Atlas, and Sisyphus Junior.",
|
||||
"overrideOptions": "Override Options",
|
||||
"permissions": "Permissions",
|
||||
"options": {
|
||||
"model": "Model identifier (e.g., openai/gpt-4o)",
|
||||
"variant": "Model variant (max, high, medium, low)",
|
||||
"category": "Inherit configuration from a category",
|
||||
"temperature": "Sampling temperature (0-2)",
|
||||
"topP": "Top-p sampling (0-1)",
|
||||
"prompt": "Override the system prompt completely",
|
||||
"promptAppend": "Append text to the system prompt",
|
||||
"tools": "Enable or disable specific tools",
|
||||
"disable": "Disable this agent",
|
||||
"maxTokens": "Maximum tokens for response",
|
||||
"thinking": "Extended thinking configuration",
|
||||
"reasoningEffort": "Reasoning effort: low, medium, high, xhigh"
|
||||
},
|
||||
"permissionValues": "ask / allow / deny",
|
||||
"permissionDescriptions": {
|
||||
"edit": "File editing capabilities",
|
||||
"bash": "Bash command execution",
|
||||
"webfetch": "Web request capabilities",
|
||||
"doomLoop": "Infinite loop override",
|
||||
"externalDirectory": "Access files outside project"
|
||||
}
|
||||
},
|
||||
"categoriesSection": {
|
||||
"title": "Categories",
|
||||
"description": "Categories allow you to define shared configurations that agents can inherit from.",
|
||||
"availableOptions": "Available options for categories: {options}.",
|
||||
"categories": {
|
||||
"visualEngineering": "Frontend, UI/UX, design tasks",
|
||||
"ultrabrain": "Deep logical reasoning",
|
||||
"deep": "Autonomous problem-solving, thorough research",
|
||||
"artistry": "Creative tasks",
|
||||
"quick": "Trivial, fast tasks",
|
||||
"unspecifiedLow": "Low effort general tasks",
|
||||
"unspecifiedHigh": "High effort general tasks",
|
||||
"writing": "Documentation and prose"
|
||||
}
|
||||
},
|
||||
"skillsSection": {
|
||||
"title": "Skills",
|
||||
"description": "Built-in skills include {playwright}, {agentBrowser}, and {gitMaster}. You can also define custom skills."
|
||||
},
|
||||
"backgroundTasksSection": {
|
||||
"title": "Background Tasks",
|
||||
"priority": "Priority:",
|
||||
"options": {
|
||||
"defaultConcurrency": "Default max concurrent tasks",
|
||||
"staleTimeoutMs": "Timeout for stale tasks in ms",
|
||||
"providerConcurrency": "Concurrency limit per provider",
|
||||
"modelConcurrency": "Concurrency limit per model"
|
||||
}
|
||||
},
|
||||
"hooksSection": {
|
||||
"title": "Hooks",
|
||||
"description": "Hooks allow you to extend functionality at various lifecycle points."
|
||||
},
|
||||
"mcpsSection": {
|
||||
"title": "MCPs",
|
||||
"websearch": {
|
||||
"title": "websearch",
|
||||
"description": "Powered by Exa for high-quality search results."
|
||||
},
|
||||
"context7": {
|
||||
"title": "context7",
|
||||
"description": "Documentation retrieval and context management."
|
||||
},
|
||||
"grepApp": {
|
||||
"title": "grep_app",
|
||||
"description": "GitHub code search integration."
|
||||
}
|
||||
},
|
||||
"browserAutomationSection": {
|
||||
"title": "Browser Automation",
|
||||
"playwright": {
|
||||
"tool": "playwright",
|
||||
"description": "Full browser automation (default)",
|
||||
"useCase": "Testing, complex interactions"
|
||||
},
|
||||
"agentBrowser": {
|
||||
"tool": "agent-browser",
|
||||
"description": "Lightweight browser agent",
|
||||
"useCase": "Quick lookups, simple scraping"
|
||||
}
|
||||
},
|
||||
"tmuxSection": {
|
||||
"title": "Tmux Integration",
|
||||
"options": {
|
||||
"enabled": "Enable Tmux integration",
|
||||
"layout": "Tmux window layout",
|
||||
"mainPaneSize": "Size of the main pane"
|
||||
}
|
||||
},
|
||||
"gitMasterSection": {
|
||||
"title": "Git Master",
|
||||
"options": {
|
||||
"commitFooter": "Text to append to commit messages",
|
||||
"includeCoAuthoredBy": "Add Co-authored-by trailer"
|
||||
}
|
||||
},
|
||||
"commentCheckerSection": {
|
||||
"title": "Comment Checker",
|
||||
"description": "Validates comments in your code. Use the {placeholder} placeholder in your custom prompt."
|
||||
},
|
||||
"experimentalSection": {
|
||||
"title": "Experimental Features",
|
||||
"options": {
|
||||
"aggressiveTruncation": "Aggressively truncate outputs",
|
||||
"autoResume": "Automatically resume interrupted tasks",
|
||||
"preemptiveCompaction": "Compact context before limits",
|
||||
"truncateAllToolOutputs": "Truncate all tool outputs"
|
||||
},
|
||||
"dynamicPruning": {
|
||||
"trigger": "dynamic_context_pruning",
|
||||
"description": "Configure dynamic pruning rules to manage context window usage efficiently."
|
||||
}
|
||||
},
|
||||
"lspSection": {
|
||||
"title": "LSP Configuration",
|
||||
"options": {
|
||||
"command": "LSP server command",
|
||||
"extensions": "File extensions to match",
|
||||
"priority": "Server priority",
|
||||
"env": "Environment variables",
|
||||
"initialization": "Initialization options",
|
||||
"disabled": "Disable this LSP"
|
||||
}
|
||||
},
|
||||
"envVarsSection": {
|
||||
"title": "Environment Variables",
|
||||
"opencodeConfigDir": {
|
||||
"name": "OPENCODE_CONFIG_DIR",
|
||||
"description": "Override the default configuration directory path."
|
||||
}
|
||||
},
|
||||
"footer": "Oh My OpenAgent Documentation © {year}"
|
||||
},
|
||||
"manifesto": {
|
||||
"badge": "Manifesto",
|
||||
"hero": {
|
||||
"title": "Ultrawork Manifesto",
|
||||
"subtitle": "The Philosophy of High-Output Engineering"
|
||||
},
|
||||
"bottleneck": "> HUMAN IN THE LOOP = BOTTLENECK",
|
||||
"autonomousCar": "Imagine an autonomous car that requires you to grab the steering wheel every 30 seconds. Would you call that \"autonomous\"? No. You'd call it driver assist — barely better than cruise control.",
|
||||
"whyDifferent": "Why is coding any different?",
|
||||
"micromanagement": "We've accepted a paradigm where \"AI coding\" means a chatbot that writes 20 lines, then waits for you to fix it. That's not automation — that's micromanagement.",
|
||||
"painPoints": {
|
||||
"fixing": "Fixing AI's half-finished code",
|
||||
"syntax": "Manually correcting syntax errors",
|
||||
"copyPasting": "Copy-pasting context back and forth",
|
||||
"reviewing": "Reviewing every single line for hallucinations"
|
||||
},
|
||||
"notCollaboration": "That's not \"human-AI collaboration\" — that's the AI failing to do its job.",
|
||||
"premise": "{linkText} is built on the premise that the human should be the architect, not the spell-checker.",
|
||||
"premiseLinkText": "Oh My OpenAgent",
|
||||
"indistinguishable": {
|
||||
"title": "Indistinguishable Code",
|
||||
"subtitle": "Agent-written code should be indistinguishable from code written by a senior engineer.",
|
||||
"items": {
|
||||
"patterns": "Follows existing codebase patterns and architecture",
|
||||
"errorHandling": "Implements proper error handling and edge cases",
|
||||
"tests": "Writes tests that actually test behavior, not just coverage",
|
||||
"noSlop": "No 'AI slop' — clean, concise, maintainable code",
|
||||
"comments": "Comments only when they add value — never stating the obvious"
|
||||
},
|
||||
"quote": "\"If you can tell whether a commit was made by a human or an agent, the agent has failed.\""
|
||||
},
|
||||
"tokenCost": {
|
||||
"title": "Token Cost vs. Productivity",
|
||||
"description": "We don't care about token usage. We care about output. If spending $5 on tokens saves an hour of engineering time, that's a 20x ROI.",
|
||||
"parallelAgents": "Parallel agents exploring multiple solutions",
|
||||
"completeWork": "Complete work without human intervention",
|
||||
"selfVerification": "Thorough self-verification loops",
|
||||
"however": "However...",
|
||||
"optimizeDescription": "We optimize for efficiency where it counts. Not by crippling the model, but by:",
|
||||
"cheaperModels": "Using cheaper models for routine tasks",
|
||||
"avoidingRedundant": "Avoiding redundant exploration",
|
||||
"intelligentCaching": "Intelligent caching of context",
|
||||
"stoppingExactly": "Stopping exactly when sufficient"
|
||||
},
|
||||
"cognitiveLoad": {
|
||||
"title": "Minimize Human Cognitive Load",
|
||||
"subtitle": "The human should only need to say what they want. Everything else is the agent's job.",
|
||||
"ultrawork": {
|
||||
"badge": "Approach 1",
|
||||
"title": "Ultrawork",
|
||||
"subtitle": "Just say \"ulw\" and walk away.",
|
||||
"steps": {
|
||||
"analyze": "Analyzes codebase context",
|
||||
"breakdown": "Breaks down task into atomic steps",
|
||||
"execute": "Executes implementation",
|
||||
"verify": "Verifies against requirements",
|
||||
"commit": "Commits changes"
|
||||
},
|
||||
"footer": "Zero intervention. Full autonomy. Just results."
|
||||
},
|
||||
"prometheus": {
|
||||
"badge": "Approach 2",
|
||||
"title": "Prometheus + Atlas",
|
||||
"subtitle": "When you want strategic control.",
|
||||
"prometheusTitle": "Prometheus",
|
||||
"prometheusDescription": "Conducts interview, researches context, and generates a detailed YAML plan.",
|
||||
"atlasTitle": "Atlas",
|
||||
"atlasDescription": "Executes the plan, delegates to sub-agents, manages waves, and tracks progress.",
|
||||
"footer": "You architect. Agents execute. Full transparency."
|
||||
}
|
||||
},
|
||||
"principles": {
|
||||
"predictable": {
|
||||
"title": "Predictable",
|
||||
"description": "Same inputs = consistent output. No random deviations or creative liberties unless requested."
|
||||
},
|
||||
"continuous": {
|
||||
"title": "Continuous",
|
||||
"description": "Survives interruptions. Tracks progress in real-time. Preserves context across sessions."
|
||||
},
|
||||
"delegatable": {
|
||||
"title": "Delegatable",
|
||||
"description": "Clear acceptance criteria. Self-correcting mechanisms. Escalation only when absolutely needed."
|
||||
}
|
||||
},
|
||||
"coreLoop": {
|
||||
"title": "The Core Loop",
|
||||
"features": {
|
||||
"prometheus": {
|
||||
"feature": "Prometheus",
|
||||
"purpose": "Extract intent through intelligent interview"
|
||||
},
|
||||
"metis": {
|
||||
"feature": "Metis",
|
||||
"purpose": "Catch ambiguities before they become bugs"
|
||||
},
|
||||
"momus": {
|
||||
"feature": "Momus",
|
||||
"purpose": "Verify plans are complete before execution"
|
||||
},
|
||||
"orchestrator": {
|
||||
"feature": "Orchestrator",
|
||||
"purpose": "Coordinate work without human micromanagement"
|
||||
},
|
||||
"todoContinuation": {
|
||||
"feature": "Todo Continuation",
|
||||
"purpose": "Force completion, prevent \"I'm done\" lies"
|
||||
},
|
||||
"categorySystem": {
|
||||
"feature": "Category System",
|
||||
"purpose": "Route to optimal model without human decision"
|
||||
},
|
||||
"backgroundAgents": {
|
||||
"feature": "Background Agents",
|
||||
"purpose": "Parallel research without blocking user"
|
||||
},
|
||||
"wisdomAccumulation": {
|
||||
"feature": "Wisdom Accumulation",
|
||||
"purpose": "Learn from work, don't repeat mistakes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"future": {
|
||||
"title": "The Future We're Building",
|
||||
"items": {
|
||||
"focus": "Human developers focus on WHAT to build, not HOW to get AI to build it",
|
||||
"quality": "Code quality independent of who wrote it",
|
||||
"complexity": "Complex projects as easy as simple ones",
|
||||
"promptEngineering": "\"Prompt engineering\" becomes obsolete"
|
||||
},
|
||||
"quote1": "\"The agent should be invisible. Like electricity, like running water.\"",
|
||||
"quote2": "\"You flip the switch. The light turns on. You don't think about the power grid.\""
|
||||
},
|
||||
"finalCta": {
|
||||
"title": "just ulw ulw",
|
||||
"button": "Get Oh My OpenAgent"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,561 @@
|
||||
{
|
||||
"metadata": {
|
||||
"title": "Oh My OpenAgent",
|
||||
"description": "究極のエージェントハーネス。Sisyphusと出会おう:あなたのようなコーディングをする、バッテリー内蔵型エージェントです。"
|
||||
},
|
||||
"nav": {
|
||||
"brand": "Oh My OpenAgent",
|
||||
"features": "機能",
|
||||
"agents": "エージェント",
|
||||
"docs": "ドキュメント",
|
||||
"manifesto": "マニフェスト",
|
||||
"starOnGitHub": "GitHubでスターをつける"
|
||||
},
|
||||
"footer": {
|
||||
"brand": "Oh My OpenAgent",
|
||||
"copyright": "© {year} Oh My OpenAgent. SUL-1.0ライセンスでオープンソースです。",
|
||||
"github": "GitHub",
|
||||
"discord": "Discord",
|
||||
"documentation": "ドキュメント",
|
||||
"manifesto": "マニフェスト"
|
||||
},
|
||||
"landing": {
|
||||
"hero": {
|
||||
"title": "最高の ",
|
||||
"titleHighlight": "Agent Harness",
|
||||
"subtitle": "{stars}スター。{downloads}ダウンロード。実戦で証明済み。",
|
||||
"githubStars": "{count} GitHub Stars",
|
||||
"specializedAgents": "{count}個の専門Agent",
|
||||
"totalDownloads": "累計{count}+ダウンロード",
|
||||
"monthlyDownloads": "月間{count}+ダウンロード",
|
||||
"lifecycleHooks": "{count}+ライフサイクルHooks",
|
||||
"installCommand": "bunx oh-my-openagent install",
|
||||
"getStarted": "始める",
|
||||
"viewOnGitHub": "GitHubで見る"
|
||||
},
|
||||
"ulw": {
|
||||
"badge": "魔法の言葉",
|
||||
"title": "ulw",
|
||||
"headline": "3文字タイプ。離れる。",
|
||||
"description": "Ultra Workモードは最大精度を発揮:自動計画、深い調査、並列エージェント、自己修正ループ。システムは完了するまで止まりません。あなたは監視しません。",
|
||||
"autoPlanning": "自動計画",
|
||||
"deepResearch": "深い調査",
|
||||
"selfCorrection": "自己修正",
|
||||
"parallelAgents": "並列エージェント",
|
||||
"terminalTitle": "sisyphus — -zsh — 80x24",
|
||||
"terminalInput": "ulw add authentication to the API",
|
||||
"steps": {
|
||||
"scanning": "コードベースをスキャン中...",
|
||||
"context": "3個のexploreエージェントを起動中...",
|
||||
"planning": "実行計画を構築中...",
|
||||
"delegating": "4個の並列エージェントを展開中...",
|
||||
"verifying": "検証テストを実行中...",
|
||||
"complete": "完了。847行変更、0個の問題。"
|
||||
},
|
||||
"tagline": "ゼロ介入。完全な自律性。ただ結果。"
|
||||
},
|
||||
"sisyphus": {
|
||||
"badge": "メインオーケストレーター",
|
||||
"title": "Sisyphus",
|
||||
"headline": "眠らないCTO",
|
||||
"description": "神話の人物にちなんで名付けられた、永遠に巨石を転がし続けるオーケストレーター。暗黙的な要件を解析し、コードベースの成熟度に適応し、スペシャリストに委譲します。中断されても、boulderシステムが正確にその場所から再開します。",
|
||||
"phases": {
|
||||
"intent": {
|
||||
"title": "Intent Gate",
|
||||
"description": "入力した通りではなく、本当に望んだことを解析します"
|
||||
},
|
||||
"explore": {
|
||||
"title": "Codebase Assessment",
|
||||
"description": "一行も触れる前にアーキテクチャをマッピングします"
|
||||
},
|
||||
"delegate": {
|
||||
"title": "Smart Delegation",
|
||||
"description": "適切なスペシャリストエージェントにルーティングします"
|
||||
},
|
||||
"verify": {
|
||||
"title": "Independent Verification",
|
||||
"description": "何も信じません。すべてを検証します。"
|
||||
}
|
||||
},
|
||||
"boulderTitle": "Session Continuity",
|
||||
"boulderDescription": "アクティブな作業はboulder.jsonに追跡されます。停電?システムクラッシュ?関係ありません。止まったそのままの場所から再開できます。",
|
||||
"model": "Claude Opus 4.6 Max"
|
||||
},
|
||||
"prometheusAtlas": {
|
||||
"badge": "オーケストレーションワークフロー",
|
||||
"title": "考えて、動け",
|
||||
"headline": "計画と実行は別の仕事だ",
|
||||
"prometheus": {
|
||||
"name": "Prometheus",
|
||||
"role": "Strategic Planner",
|
||||
"model": "Claude Opus 4.6 Max",
|
||||
"description": "設計者。あなたとインタビューし、コードベースを探索し、詳細な実行計画を作成します。コードは絶対に書きません。MetisとMomusを品質ゲートとして使用します。",
|
||||
"features": [
|
||||
"知的なインタビューモード",
|
||||
"マルチエージェントコードベース探索",
|
||||
"Metisによるギャップ分析",
|
||||
"Momusとの容赦ないレビュー"
|
||||
]
|
||||
},
|
||||
"atlas": {
|
||||
"name": "Atlas",
|
||||
"role": "Master Executor",
|
||||
"model": "Claude Sonnet 4.6",
|
||||
"description": "建設者。検証済みの計画を読み、category+skillsシステムを通じて専門エージェントに委譲します。タスク間の学習を追跡します。独立して検証 — サブエージェントの主張を信じません。",
|
||||
"features": [
|
||||
"意図ベースのタスクルーティング",
|
||||
"タスク間の叡智蓄積",
|
||||
"Boulderセッション継続性",
|
||||
"独立した結果検証"
|
||||
]
|
||||
},
|
||||
"metis": {
|
||||
"name": "Metis",
|
||||
"description": "バグになる前に曖昧さを捕捉します。"
|
||||
},
|
||||
"momus": {
|
||||
"name": "Momus",
|
||||
"description": "プランが実際に完璧な時だけOKAYと言います。"
|
||||
},
|
||||
"workflow": {
|
||||
"step1": "Prometheusに作業を説明する",
|
||||
"step2": "Metisがギャップを分析する",
|
||||
"step3": "Momusが容赦なく検証する",
|
||||
"step4": "Atlasがスペシャリストに委譲する",
|
||||
"step5": "すべてに対して独立した検証が行われる"
|
||||
},
|
||||
"whyItWorks": "プランナーはコードを書かない。実行者は検証済みの計画に従う。知能は個々のエージェントではなくシステムにある。"
|
||||
},
|
||||
"hephaestus": {
|
||||
"badge": "ディープワーカー",
|
||||
"title": "Hephaestus",
|
||||
"headline": "目標をくれ、レシピじゃなくて",
|
||||
"description": "ギリシャの鍛冶の神にちなんで名付けられた、方法的、徹底的、執念的。コードを書く前に2-5個の並列exploreエージェントを起動。深いアーキテクチャ推論、複雑なデバッグ、クロスドメイン統成に最適。",
|
||||
"loop": {
|
||||
"explore": "EXPLORE — 地形をマッピングする",
|
||||
"plan": "PLAN — コースを計画する",
|
||||
"decide": "DECIDE — 道を確定する",
|
||||
"execute": "EXECUTE — 精密に構築する",
|
||||
"verify": "VERIFY — 動作を証明する"
|
||||
},
|
||||
"tagline": "良いだけではない時に。",
|
||||
"model": "GPT 5.3 Codex Medium"
|
||||
},
|
||||
"agents": {
|
||||
"title": "Specialized Agents",
|
||||
"subtitle": "5つの専用エージェントに加え、必要に応じて生成されるダイナミックエージェント。",
|
||||
"oracle": {
|
||||
"name": "Oracle",
|
||||
"role": "Architecture Consultant",
|
||||
"model": "GPT 5.4 High",
|
||||
"description": "複雑なデバッグとアーキテクチャ決定。前進の道が明らかでない時。"
|
||||
},
|
||||
"librarian": {
|
||||
"name": "Librarian",
|
||||
"role": "Docs & Code Search",
|
||||
"model": "MiniMax M2.5",
|
||||
"description": "実際のGitHub例と公式ドキュメントを見つける。証拠ベース、パーマリンク付き。"
|
||||
},
|
||||
"explore": {
|
||||
"name": "Explore",
|
||||
"role": "Codebase Grep",
|
||||
"model": "MiniMax M2.5",
|
||||
"description": "超高速コードベース検索。安価で、並列で、常にバックグラウンドで。"
|
||||
},
|
||||
"metis": {
|
||||
"name": "Metis",
|
||||
"role": "Plan Consultant",
|
||||
"model": "Claude Opus 4.6",
|
||||
"description": "プランの曖昧さをプロダクションバグになる前に捕捉する。"
|
||||
},
|
||||
"momus": {
|
||||
"name": "Momus",
|
||||
"role": "Plan Reviewer",
|
||||
"model": "GPT 5.4",
|
||||
"description": "容赦ない検証。プランが弾丸不可能な時だけ承認する。"
|
||||
},
|
||||
"dynamicSystem": {
|
||||
"name": "Dynamic Agent",
|
||||
"role": "必要な瞬間に生成される",
|
||||
"description": "リクエストを解析し、最適なモデルとスキルを組み合わせてエージェントをリアルタイムに構築します。"
|
||||
}
|
||||
},
|
||||
"architecture": {
|
||||
"title": "異なる設計",
|
||||
"subtitle": "実際に重要な設計決定。",
|
||||
"principles": {
|
||||
"specialization": {
|
||||
"title": "Specialization",
|
||||
"description": "各エージェントは一つのことを例外的に上手にやる。何でも屋はいない。"
|
||||
},
|
||||
"trustVerify": {
|
||||
"title": "Trust But Verify",
|
||||
"description": "オーケストレーターはすべてを独立して検証する。サブエージェントにタダパスはない。"
|
||||
},
|
||||
"wisdom": {
|
||||
"title": "Wisdom Accumulation",
|
||||
"description": "各タスクからの学習がその後のすべてのタスクに渡される。システムは作業しながら賢くなる。"
|
||||
},
|
||||
"modelOptimization": {
|
||||
"title": "Model Optimization",
|
||||
"description": "計画と複雑な決定には高価なモデル。日常的な作業には安価なモデル。ドルあたりの最大出力。"
|
||||
},
|
||||
"categories": {
|
||||
"title": "Category System",
|
||||
"description": "意図ベースのルーティング。必要なものを言って — ultrabrain、visual-engineering、quick — 正しいモデルが処理する。"
|
||||
},
|
||||
"continuity": {
|
||||
"title": "Session Continuity",
|
||||
"description": "Boulderシステム:中断された作業は正確に止まった場所から再開される。ゼロコンテキストロス。"
|
||||
}
|
||||
}
|
||||
},
|
||||
"reviews": {
|
||||
"title": "開発者たちの声",
|
||||
"review1": {
|
||||
"text": "It made me cancel my Cursor subscription. Unbelievable things are happening in the open source community.",
|
||||
"author": "Arthur Guiot"
|
||||
},
|
||||
"review2": {
|
||||
"text": "If Claude Code does in 7 days what a human does in 3 months, Sisyphus does it in 1 hour.",
|
||||
"author": "B, Quant Researcher"
|
||||
},
|
||||
"review3": {
|
||||
"text": "Knocked out 8000 eslint warnings with Oh My Opencode, just in a day.",
|
||||
"author": "Jacob Ferrari"
|
||||
},
|
||||
"review4": {
|
||||
"text": "use oh-my-openagent, you will never go back",
|
||||
"author": "d0t3ch"
|
||||
},
|
||||
"review5": {
|
||||
"text": "You guys should pull this into core and recruit him. Seriously. It's really, really, really good.",
|
||||
"author": "Henning Kilset"
|
||||
},
|
||||
"review6": {
|
||||
"text": "Oh My OpenCode Is Actually Insane",
|
||||
"author": "Darren Builds AI (YouTube)"
|
||||
}
|
||||
},
|
||||
"cta": {
|
||||
"title": "ボイラープレートを書くのをやめろ",
|
||||
"subtitle": "Oh My OpenAgentをインストールしてエージェントに任せよう。最初のulwコマンドまであと30秒。",
|
||||
"installCommand": "bunx oh-my-openagent install",
|
||||
"installNow": "今すぐインストール",
|
||||
"readTheDocs": "ドキュメントを読む"
|
||||
}
|
||||
},
|
||||
"docs": {
|
||||
"mobileHeader": "Oh My OpenAgentドキュメント",
|
||||
"searchPlaceholder": "ドキュメントを検索...",
|
||||
"sections": {
|
||||
"overview": "概要",
|
||||
"quickStart": "クイックスタート",
|
||||
"configLocations": "設定ファイルの場所",
|
||||
"agents": "エージェント",
|
||||
"categories": "カテゴリ",
|
||||
"skills": "スキル",
|
||||
"backgroundTasks": "バックグラウンドタスク",
|
||||
"hooks": "フック",
|
||||
"mcps": "MCP",
|
||||
"browserAutomation": "ブラウザ自動化",
|
||||
"tmux": "Tmux統合",
|
||||
"gitMaster": "Git Master",
|
||||
"commentChecker": "コメントチェッカー",
|
||||
"experimental": "実験的機能",
|
||||
"lsp": "LSP設定",
|
||||
"envVars": "環境変数"
|
||||
},
|
||||
"overview": {
|
||||
"title": "設定リファレンス",
|
||||
"description": "Oh My OpenAgentは意見を持っていますが、好みに応じて調整可能です。ほとんどのユーザーは何も設定する必要はありません — {command}を実行して始めてください。"
|
||||
},
|
||||
"quickStart": {
|
||||
"title": "クイックスタート"
|
||||
},
|
||||
"configLocations": {
|
||||
"title": "設定ファイルの場所",
|
||||
"projectLevel": "(プロジェクトレベル)",
|
||||
"userLevel": "(ユーザーレベル)",
|
||||
"jsonc": "JSONCがサポートされており、コメントと末尾のカンマを使用できます。"
|
||||
},
|
||||
"agentsSection": {
|
||||
"title": "エージェント",
|
||||
"description": "Sisyphus、Hephaestus、Oracle、Librarian、Explore、Multimodal Looker、Prometheus、Metis、Momus、Atlas、Sisyphus Juniorなど、組み込みエージェントの動作を設定します。",
|
||||
"overrideOptions": "オーバーライドオプション",
|
||||
"permissions": "権限",
|
||||
"options": {
|
||||
"model": "モデル識別子(例:openai/gpt-4o)",
|
||||
"variant": "モデルバリアント(max, high, medium, low)",
|
||||
"category": "カテゴリから設定を継承",
|
||||
"temperature": "サンプリング温度(0-2)",
|
||||
"topP": "Top-pサンプリング(0-1)",
|
||||
"prompt": "システムプロンプトを完全にオーバーライド",
|
||||
"promptAppend": "システムプロンプトにテキストを追加",
|
||||
"tools": "特定のツールを有効または無効にする",
|
||||
"disable": "このエージェントを無効にする",
|
||||
"maxTokens": "レスポンスの最大トークン数",
|
||||
"thinking": "拡張思考設定",
|
||||
"reasoningEffort": "推論努力:low, medium, high, xhigh"
|
||||
},
|
||||
"permissionValues": "ask / allow / deny",
|
||||
"permissionDescriptions": {
|
||||
"edit": "ファイル編集機能",
|
||||
"bash": "Bashコマンド実行",
|
||||
"webfetch": "Webリクエスト機能",
|
||||
"doomLoop": "無限ループオーバーライド",
|
||||
"externalDirectory": "プロジェクト外のファイルへのアクセス"
|
||||
}
|
||||
},
|
||||
"categoriesSection": {
|
||||
"title": "カテゴリ",
|
||||
"description": "カテゴリを使用すると、エージェントが継承できる共有設定を定義できます。",
|
||||
"availableOptions": "カテゴリで利用可能なオプション:{options}。",
|
||||
"categories": {
|
||||
"visualEngineering": "フロントエンド、UI/UX、デザインタスク",
|
||||
"ultrabrain": "深い論理的推論",
|
||||
"deep": "自律的な問題解決と徹底したリサーチ",
|
||||
"artistry": "クリエイティブタスク",
|
||||
"quick": "些細で高速なタスク",
|
||||
"unspecifiedLow": "低労力の一般的タスク",
|
||||
"unspecifiedHigh": "高労力の一般的タスク",
|
||||
"writing": "ドキュメントと散文"
|
||||
}
|
||||
},
|
||||
"skillsSection": {
|
||||
"title": "スキル",
|
||||
"description": "組み込みスキルには{playwright}、{agentBrowser}、{gitMaster}が含まれます。カスタムスキルを定義することもできます。"
|
||||
},
|
||||
"backgroundTasksSection": {
|
||||
"title": "バックグラウンドタスク",
|
||||
"priority": "優先度:",
|
||||
"options": {
|
||||
"defaultConcurrency": "デフォルトの最大並列タスク数",
|
||||
"staleTimeoutMs": "古いタスクのタイムアウト(ミリ秒)",
|
||||
"providerConcurrency": "プロバイダーごとの並列制限",
|
||||
"modelConcurrency": "モデルごとの並列制限"
|
||||
}
|
||||
},
|
||||
"hooksSection": {
|
||||
"title": "フック",
|
||||
"description": "フックを使用すると、さまざまなライフサイクルポイントで機能を拡張できます。"
|
||||
},
|
||||
"mcpsSection": {
|
||||
"title": "MCP",
|
||||
"websearch": {
|
||||
"title": "websearch",
|
||||
"description": "Exa提供の高品質検索結果。"
|
||||
},
|
||||
"context7": {
|
||||
"title": "context7",
|
||||
"description": "ドキュメント取得とコンテキスト管理。"
|
||||
},
|
||||
"grepApp": {
|
||||
"title": "grep_app",
|
||||
"description": "GitHubコード検索統合。"
|
||||
}
|
||||
},
|
||||
"browserAutomationSection": {
|
||||
"title": "ブラウザ自動化",
|
||||
"playwright": {
|
||||
"tool": "playwright",
|
||||
"description": "フルブラウザ自動化(デフォルト)",
|
||||
"useCase": "テスト、複雑なインタラクション"
|
||||
},
|
||||
"agentBrowser": {
|
||||
"tool": "agent-browser",
|
||||
"description": "軽量ブラウザエージェント",
|
||||
"useCase": "クイックルックアップ、簡単なスクレイピング"
|
||||
}
|
||||
},
|
||||
"tmuxSection": {
|
||||
"title": "Tmux統合",
|
||||
"options": {
|
||||
"enabled": "Tmux統合を有効にする",
|
||||
"layout": "Tmuxウィンドウレイアウト",
|
||||
"mainPaneSize": "メインペインのサイズ"
|
||||
}
|
||||
},
|
||||
"gitMasterSection": {
|
||||
"title": "Git Master",
|
||||
"options": {
|
||||
"commitFooter": "コミットメッセージに追加するテキスト",
|
||||
"includeCoAuthoredBy": "Co-authored-byトレーラーを追加"
|
||||
}
|
||||
},
|
||||
"commentCheckerSection": {
|
||||
"title": "コメントチェッカー",
|
||||
"description": "コード内のコメントを検証します。カスタムプロンプトには{placeholder}プレースホルダーを使用してください。"
|
||||
},
|
||||
"experimentalSection": {
|
||||
"title": "実験的機能",
|
||||
"options": {
|
||||
"aggressiveTruncation": "出力を積極的に切り詰める",
|
||||
"autoResume": "中断されたタスクを自動的に再開",
|
||||
"preemptiveCompaction": "制限前にコンテキストを圧縮",
|
||||
"truncateAllToolOutputs": "すべてのツール出力を切り詰める"
|
||||
},
|
||||
"dynamicPruning": {
|
||||
"trigger": "dynamic_context_pruning",
|
||||
"description": "コンテキストウィンドウの使用量を効率的に管理するための動的プルーニングルールを設定します。"
|
||||
}
|
||||
},
|
||||
"lspSection": {
|
||||
"title": "LSP設定",
|
||||
"options": {
|
||||
"command": "LSPサーバーコマンド",
|
||||
"extensions": "一致させるファイル拡張子",
|
||||
"priority": "サーバーの優先度",
|
||||
"env": "環境変数",
|
||||
"initialization": "初期化オプション",
|
||||
"disabled": "このLSPを無効にする"
|
||||
}
|
||||
},
|
||||
"envVarsSection": {
|
||||
"title": "環境変数",
|
||||
"opencodeConfigDir": {
|
||||
"name": "OPENCODE_CONFIG_DIR",
|
||||
"description": "デフォルトの設定ディレクトリパスを上書きします。"
|
||||
}
|
||||
},
|
||||
"footer": "Oh My OpenAgentドキュメント © {year}"
|
||||
},
|
||||
"manifesto": {
|
||||
"badge": "マニフェスト",
|
||||
"hero": {
|
||||
"title": "Ultraworkマニフェスト",
|
||||
"subtitle": "高生産性エンジニアリングの哲学"
|
||||
},
|
||||
"bottleneck": "> 人間がループに入る = ボトルネック",
|
||||
"autonomousCar": "30秒ごとにハンドルを握る必要がある自動運転車を想像してください。それを「自律的」と呼びますか?いいえ。クルーズコントロールよりほんの少しマシなドライバー支援機能と呼ぶでしょう。",
|
||||
"whyDifferent": "なぜコーディングは違うのでしょうか?",
|
||||
"micromanagement": "「AIコーディング」が20行のコードを書いて、あなたが修正するのを待つチャットボットを意味するというパラダイムを私たちは受け入れてきました。それは自動化ではありません。それはマイクロマネジメントです。",
|
||||
"painPoints": {
|
||||
"fixing": "AIの中途半端なコードを修正する",
|
||||
"syntax": "構文エラーを手動で修正する",
|
||||
"copyPasting": "コンテキストを行ったり来たりコピー貼り付けする",
|
||||
"reviewing": "すべての行を幻覚のためにレビューする"
|
||||
},
|
||||
"notCollaboration": "それは「人間-AIコラボレーション」ではありません。それはAIが仕事を果たせていないということです。",
|
||||
"premise": "{linkText}は、人間がスペルチェッカーではなくアーキテクトであるべきという前提に基づいて構築されています。",
|
||||
"premiseLinkText": "Oh My OpenAgent",
|
||||
"indistinguishable": {
|
||||
"title": "区別できないコード",
|
||||
"subtitle": "エージェントが書いたコードは、シニアエンジニアが書いたコードと区別がつかないべきです。",
|
||||
"items": {
|
||||
"patterns": "既存のコードベースのパターンとアーキテクチャに従う",
|
||||
"errorHandling": "適切なエラーハンドリングとエッジケースを実装する",
|
||||
"tests": "カバレッジだけでなく実際の動作をテストするテストを書く",
|
||||
"noSlop": "「AIスロップ」なし—クリーンで簡潔で保守しやすいコード",
|
||||
"comments": "価値を追加する場合にのみコメントし、自明なことを述べない"
|
||||
},
|
||||
"quote": "「コミットが人間によるものかエージェントによるものかわかるなら、エージェントは失敗している。」"
|
||||
},
|
||||
"tokenCost": {
|
||||
"title": "トークンコスト対生産性",
|
||||
"description": "私たちはトークン使用量を気にしません。私たちが気にするのはアウトプットです。トークンに5ドル費やしてエンジニアリング時間を1時間節約できれば、それは20倍のROIです。",
|
||||
"parallelAgents": "複数の解決策を並列で探索するエージェント",
|
||||
"completeWork": "人間の介入なしに作業を完了させる",
|
||||
"selfVerification": "徹底的な自己検証ループ",
|
||||
"however": "しかし...",
|
||||
"optimizeDescription": "重要な場所で効率性を最適化します。モデルを弱体化させるのではなく、以下によって:",
|
||||
"cheaperModels": "定型タスクに安価なモデルを使用する",
|
||||
"avoidingRedundant": "冗長な探索を避ける",
|
||||
"intelligentCaching": "コンテキストのインテリジェントなキャッシング",
|
||||
"stoppingExactly": "十分である時点で正確に停止する"
|
||||
},
|
||||
"cognitiveLoad": {
|
||||
"title": "人間の認知負荷を最小化",
|
||||
"subtitle": "人間は望みを伝えるだけで十分です。それ以外はすべてエージェントの仕事です。",
|
||||
"ultrawork": {
|
||||
"badge": "アプローチ1",
|
||||
"title": "Ultrawork",
|
||||
"subtitle": "「ulw」と言って離れればよい。",
|
||||
"steps": {
|
||||
"analyze": "コードベースのコンテキストを分析",
|
||||
"breakdown": "タスクをアトミックなステップに分解",
|
||||
"execute": "実装を実行",
|
||||
"verify": "要件に対して検証",
|
||||
"commit": "変更をコミット"
|
||||
},
|
||||
"footer": "ゼロ介入。完全な自律性。ただ結果。"
|
||||
},
|
||||
"prometheus": {
|
||||
"badge": "アプローチ2",
|
||||
"title": "Prometheus + Atlas",
|
||||
"subtitle": "戦略的なコントロールが欲しい時。",
|
||||
"prometheusTitle": "Prometheus",
|
||||
"prometheusDescription": "インタビューを実施し、コンテキストを調査し、詳細なYAMLプランを生成します。",
|
||||
"atlasTitle": "Atlas",
|
||||
"atlasDescription": "プランを実行し、サブエージェントに委譲し、ウェーブを管理し、進捗を追跡します。",
|
||||
"footer": "あなたがアーキテクト。エージェントが実行。完全な透明性。"
|
||||
}
|
||||
},
|
||||
"principles": {
|
||||
"predictable": {
|
||||
"title": "予測可能",
|
||||
"description": "同じ入力 = 一貫した出力。要求されない限り、ランダムな逸脱や創造的な自由はありません。"
|
||||
},
|
||||
"continuous": {
|
||||
"title": "継続的",
|
||||
"description": "中断を耐え抜きます。リアルタイムで進捗を追跡します。セッション間でコンテキストを保持します。"
|
||||
},
|
||||
"delegatable": {
|
||||
"title": "委譲可能",
|
||||
"description": "明確な受け入れ基準。自己修正メカニズム。絶対に必要な時のみエスカレーション。"
|
||||
}
|
||||
},
|
||||
"coreLoop": {
|
||||
"title": "コアループ",
|
||||
"features": {
|
||||
"prometheus": {
|
||||
"feature": "Prometheus",
|
||||
"purpose": "知的なインタビューを通じて意図を抽出"
|
||||
},
|
||||
"metis": {
|
||||
"feature": "Metis",
|
||||
"purpose": "バグになる前に曖昧さを捕捉"
|
||||
},
|
||||
"momus": {
|
||||
"feature": "Momus",
|
||||
"purpose": "実行前にプランが完全であることを検証"
|
||||
},
|
||||
"orchestrator": {
|
||||
"feature": "Orchestrator",
|
||||
"purpose": "人間のマイクロマネジメントなしに作業を調整"
|
||||
},
|
||||
"todoContinuation": {
|
||||
"feature": "Todo Continuation",
|
||||
"purpose": "完了を強制、「終わりました」の嘘を防止"
|
||||
},
|
||||
"categorySystem": {
|
||||
"feature": "Category System",
|
||||
"purpose": "人間の決定なしに最適なモデルにルーティング"
|
||||
},
|
||||
"backgroundAgents": {
|
||||
"feature": "Background Agents",
|
||||
"purpose": "ユーザーをブロックせずに並列調査"
|
||||
},
|
||||
"wisdomAccumulation": {
|
||||
"feature": "Wisdom Accumulation",
|
||||
"purpose": "仕事から学び、間違いを繰り返さない"
|
||||
}
|
||||
}
|
||||
},
|
||||
"future": {
|
||||
"title": "私たちが構築する未来",
|
||||
"items": {
|
||||
"focus": "人間の開発者は何を作るかに集中し、AIにどう作らせるかには集中しない",
|
||||
"quality": "誰が書いたかに関係なくコードの品質",
|
||||
"complexity": "複雑なプロジェクトも簡単なプロジェクトと同じくらい簡単",
|
||||
"promptEngineering": "「プロンプトエンジニアリング」が時代遅れになる"
|
||||
},
|
||||
"quote1": "「エージェントは目に見えないべきだ。電気のように、水道のように。」",
|
||||
"quote2": "「スイッチを入れる。電灯がつく。配電網のことなど考えない。」"
|
||||
},
|
||||
"finalCta": {
|
||||
"title": "just ulw ulw",
|
||||
"button": "Oh My OpenAgentを入手"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,561 @@
|
||||
{
|
||||
"metadata": {
|
||||
"title": "Oh My OpenAgent",
|
||||
"description": "최고의 Agent Harness. Sisyphus를 만나보세요: 당신처럼 코딩하는 완벽한 Agent."
|
||||
},
|
||||
"nav": {
|
||||
"brand": "Oh My OpenAgent",
|
||||
"features": "Features",
|
||||
"agents": "Agents",
|
||||
"docs": "Docs",
|
||||
"manifesto": "Manifesto",
|
||||
"starOnGitHub": "GitHub에서 Star"
|
||||
},
|
||||
"footer": {
|
||||
"brand": "Oh My OpenAgent",
|
||||
"copyright": "© {year} Oh My OpenAgent. SUL-1.0 오픈소스 라이선스.",
|
||||
"github": "GitHub",
|
||||
"discord": "Discord",
|
||||
"documentation": "Documentation",
|
||||
"manifesto": "Manifesto"
|
||||
},
|
||||
"landing": {
|
||||
"hero": {
|
||||
"title": "최고의 ",
|
||||
"titleHighlight": "에이전트 하네스",
|
||||
"subtitle": "{stars} 스타. {downloads} 다운로드. 실전 검증 완료.",
|
||||
"githubStars": "{count} GitHub 스타",
|
||||
"specializedAgents": "{count}개 전문 에이전트",
|
||||
"totalDownloads": "총 {count} 다운로드",
|
||||
"monthlyDownloads": "월 {count} 다운로드",
|
||||
"lifecycleHooks": "{count}개 라이프사이클 훅",
|
||||
"installCommand": "bunx oh-my-openagent install",
|
||||
"getStarted": "시작하기",
|
||||
"viewOnGitHub": "GitHub에서 보기"
|
||||
},
|
||||
"ulw": {
|
||||
"badge": "마법의 주문",
|
||||
"title": "ulw",
|
||||
"headline": "세 글자만 입력하세요. 그리고 떠나세요.",
|
||||
"description": "Ultra Work 모드는 극한의 정밀함을 발휘합니다. 자동 계획, 심층 리서치, 병렬 에이전트, 자기 수정 루프까지. 작업이 끝날 때까지 시스템은 멈추지 않습니다. 감시는 필요 없습니다.",
|
||||
"autoPlanning": "자동 계획 수립",
|
||||
"deepResearch": "심층 리서치",
|
||||
"selfCorrection": "자기 수정 루프",
|
||||
"parallelAgents": "병렬 에이전트",
|
||||
"terminalTitle": "sisyphus — -zsh — 80x24",
|
||||
"terminalInput": "ulw add authentication to the API",
|
||||
"steps": {
|
||||
"scanning": "코드베이스 스캔 중...",
|
||||
"context": "3개의 Explore 에이전트 가동...",
|
||||
"planning": "실행 계획 수립 중...",
|
||||
"delegating": "4개의 병렬 에이전트 배포...",
|
||||
"verifying": "검증 테스트 수행 중...",
|
||||
"complete": "완료. 847줄 변경, 이슈 0개."
|
||||
},
|
||||
"tagline": "개입 제로. 완전 자율. 오직 결과뿐."
|
||||
},
|
||||
"sisyphus": {
|
||||
"badge": "메인 오케스트레이터",
|
||||
"title": "Sisyphus",
|
||||
"headline": "잠들지 않는 CTO",
|
||||
"description": "영원히 바위를 굴리는 신화 속 인물처럼, Sisyphus는 멈추지 않습니다. 숨겨진 요구사항을 파악하고, 코드베이스 성숙도에 맞춰 전문가들에게 작업을 위임합니다. 중단되었나요? Boulder 시스템이 정확히 그 지점에서 다시 시작합니다.",
|
||||
"phases": {
|
||||
"intent": {
|
||||
"title": "의도 파악",
|
||||
"description": "입력한 텍스트가 아닌, 진짜 의도를 읽어냅니다"
|
||||
},
|
||||
"explore": {
|
||||
"title": "코드베이스 진단",
|
||||
"description": "코드 한 줄 건드리기 전에 아키텍처부터 파악합니다"
|
||||
},
|
||||
"delegate": {
|
||||
"title": "스마트 위임",
|
||||
"description": "최적의 전문가 에이전트에게 작업을 배분합니다"
|
||||
},
|
||||
"verify": {
|
||||
"title": "독립 검증",
|
||||
"description": "아무것도 믿지 않고, 모든 것을 검증합니다"
|
||||
}
|
||||
},
|
||||
"boulderTitle": "세션 연속성",
|
||||
"boulderDescription": "모든 작업은 boulder.json에 기록됩니다. 정전? 시스템 크래시? 상관없습니다. 멈춘 그 자리에서 즉시 재개하세요.",
|
||||
"model": "Claude Opus 4.6 Max"
|
||||
},
|
||||
"prometheusAtlas": {
|
||||
"badge": "오케스트레이션 워크플로우",
|
||||
"title": "생각하고, 행동하라",
|
||||
"headline": "계획과 실행은 엄연히 다른 영역입니다",
|
||||
"prometheus": {
|
||||
"name": "Prometheus",
|
||||
"role": "전략 기획자",
|
||||
"model": "Claude Opus 4.6 Max",
|
||||
"description": "설계자입니다. 당신과 인터뷰하고, 코드베이스를 탐색하여 상세한 작전 계획을 짭니다. 코드는 절대 쓰지 않습니다. Metis와 Momus를 통해 품질을 검증합니다.",
|
||||
"features": [
|
||||
"지능형 인터뷰 모드",
|
||||
"다중 에이전트 코드베이스 탐색",
|
||||
"Metis를 통한 갭 분석",
|
||||
"Momus의 무자비한 검토"
|
||||
]
|
||||
},
|
||||
"atlas": {
|
||||
"name": "Atlas",
|
||||
"role": "마스터 실행가",
|
||||
"model": "Claude Sonnet 4.6",
|
||||
"description": "건설자입니다. 검증된 계획을 바탕으로 category+skills 시스템을 통해 전문 에이전트에게 작업을 위임합니다. 작업 간 학습 내용을 축적하며, 하위 에이전트를 맹신하지 않고 독립적으로 검증합니다.",
|
||||
"features": [
|
||||
"의도 기반 작업 라우팅",
|
||||
"작업 간 지혜(Wisdom) 축적",
|
||||
"Boulder 세션 연속성",
|
||||
"독립적 결과 검증"
|
||||
]
|
||||
},
|
||||
"metis": {
|
||||
"name": "Metis",
|
||||
"description": "버그가 되기 전, 모호함을 잡아냅니다."
|
||||
},
|
||||
"momus": {
|
||||
"name": "Momus",
|
||||
"description": "계획이 완벽할 때만 OK 사인을 보냅니다."
|
||||
},
|
||||
"workflow": {
|
||||
"step1": "Prometheus에게 작업을 설명하세요",
|
||||
"step2": "Metis가 빈틈을 분석합니다",
|
||||
"step3": "Momus가 철저하게 검증합니다",
|
||||
"step4": "Atlas가 전문가에게 위임합니다",
|
||||
"step5": "모든 단계에서 독립 검증 수행"
|
||||
},
|
||||
"whyItWorks": "기획자는 코드를 짜지 않고, 실행자는 검증된 계획만 따릅니다. 지능은 개별 에이전트가 아닌 시스템 전체에 있습니다."
|
||||
},
|
||||
"hephaestus": {
|
||||
"badge": "심층 작업자",
|
||||
"title": "Hephaestus",
|
||||
"headline": "방법이 아닌, 목표를 주세요",
|
||||
"description": "그리스 대장장이 신의 이름을 땄습니다. 체계적이고, 철저하며, 집요합니다. 코드를 쓰기 전 2-5개의 Explore 에이전트를 동시에 띄웁니다. 심층 아키텍처 추론, 복잡한 디버깅, 다중 도메인 통합에 최적화되어 있습니다.",
|
||||
"loop": {
|
||||
"explore": "EXPLORE — 지형 파악",
|
||||
"plan": "PLAN — 경로 설계",
|
||||
"decide": "DECIDE — 방향 결정",
|
||||
"execute": "EXECUTE — 정밀 구축",
|
||||
"verify": "VERIFY — 동작 증명"
|
||||
},
|
||||
"tagline": "'적당히'로는 충분하지 않을 때.",
|
||||
"model": "GPT 5.3 Codex Medium"
|
||||
},
|
||||
"agents": {
|
||||
"title": "특화 에이전트",
|
||||
"subtitle": "각 태스크에 최적화된 프롬프트와 툴셋으로 무장한 전문가들입니다.",
|
||||
"oracle": {
|
||||
"name": "Oracle",
|
||||
"role": "아키텍처 컨설턴트",
|
||||
"model": "GPT 5.4 High",
|
||||
"description": "복잡한 디버깅과 아키텍처 결정. 길이 보이지 않을 때."
|
||||
},
|
||||
"librarian": {
|
||||
"name": "Librarian",
|
||||
"role": "문서 & 코드 검색",
|
||||
"model": "MiniMax M2.5",
|
||||
"description": "실제 GitHub 예제와 공식 문서를 찾습니다. 증거 기반, 퍼머링크 제공."
|
||||
},
|
||||
"explore": {
|
||||
"name": "Explore",
|
||||
"role": "코드베이스 탐색",
|
||||
"model": "MiniMax M2.5",
|
||||
"description": "초고속 코드베이스 검색. 저렴하고 빠르며, 항상 백그라운드에서 실행."
|
||||
},
|
||||
"metis": {
|
||||
"name": "Metis",
|
||||
"role": "계획 컨설턴트",
|
||||
"model": "Claude Opus 4.6",
|
||||
"description": "계획의 모호함이 프로덕션 버그로 이어지기 전에 차단합니다."
|
||||
},
|
||||
"momus": {
|
||||
"name": "Momus",
|
||||
"role": "계획 검토자",
|
||||
"model": "GPT 5.4",
|
||||
"description": "무자비한 검증. 계획이 완벽하지 않으면 승인하지 않습니다."
|
||||
},
|
||||
"dynamicSystem": {
|
||||
"name": "동적 오케스트레이터",
|
||||
"role": "실시간 에이전트 조립",
|
||||
"description": "요청 카테고리에 따라 모델을 라우팅하고 스킬을 주입하여, 최적의 에이전트를 실시간으로 조립합니다."
|
||||
}
|
||||
},
|
||||
"architecture": {
|
||||
"title": "태생부터 다른 설계",
|
||||
"subtitle": "진짜 중요한 설계 결정들.",
|
||||
"principles": {
|
||||
"specialization": {
|
||||
"title": "전문화",
|
||||
"description": "각 에이전트는 한 가지를 압도적으로 잘합니다. 어설픈 만능은 없습니다."
|
||||
},
|
||||
"trustVerify": {
|
||||
"title": "신뢰하되 검증하라",
|
||||
"description": "오케스트레이터는 모든 것을 독립적으로 검증합니다. 하위 에이전트라고 봐주는 건 없습니다."
|
||||
},
|
||||
"wisdom": {
|
||||
"title": "지혜 축적",
|
||||
"description": "하나의 작업에서 얻은 배움은 다음 작업으로 이어집니다. 시스템은 쓸수록 똑똑해집니다."
|
||||
},
|
||||
"modelOptimization": {
|
||||
"title": "모델 최적화",
|
||||
"description": "계획과 복잡한 판단엔 고성능 모델을, 단순 작업엔 가성비 모델을. 비용 대비 효율을 극대화합니다."
|
||||
},
|
||||
"categories": {
|
||||
"title": "카테고리 시스템",
|
||||
"description": "의도 기반 라우팅. 필요한 걸 말하세요—ultrabrain, visual-engineering, quick—적합한 모델이 알아서 처리합니다."
|
||||
},
|
||||
"continuity": {
|
||||
"title": "세션 연속성",
|
||||
"description": "Boulder 시스템: 작업이 중단되어도 정확히 그 지점에서 다시 시작합니다. 컨텍스트 손실 제로."
|
||||
}
|
||||
}
|
||||
},
|
||||
"reviews": {
|
||||
"title": "개발자들의 리얼한 반응",
|
||||
"review1": {
|
||||
"text": "It made me cancel my Cursor subscription. Unbelievable things are happening in the open source community.",
|
||||
"author": "Arthur Guiot"
|
||||
},
|
||||
"review2": {
|
||||
"text": "If Claude Code does in 7 days what a human does in 3 months, Sisyphus does it in 1 hour.",
|
||||
"author": "B, Quant Researcher"
|
||||
},
|
||||
"review3": {
|
||||
"text": "Knocked out 8000 eslint warnings with Oh My Opencode, just in a day.",
|
||||
"author": "Jacob Ferrari"
|
||||
},
|
||||
"review4": {
|
||||
"text": "use oh-my-openagent, you will never go back",
|
||||
"author": "d0t3ch"
|
||||
},
|
||||
"review5": {
|
||||
"text": "You guys should pull this into core and recruit him. Seriously. It's really, really, really good.",
|
||||
"author": "Henning Kilset"
|
||||
},
|
||||
"review6": {
|
||||
"text": "Oh My OpenCode Is Actually Insane",
|
||||
"author": "Darren Builds AI (YouTube)"
|
||||
}
|
||||
},
|
||||
"cta": {
|
||||
"title": "보일러플레이트는 이제 그만",
|
||||
"subtitle": "Oh My OpenAgent를 설치하고 에이전트에게 맡기세요. 첫 ulw 명령까지 30초면 충분합니다.",
|
||||
"installCommand": "bunx oh-my-openagent install",
|
||||
"installNow": "지금 설치하기",
|
||||
"readTheDocs": "문서 보기"
|
||||
}
|
||||
},
|
||||
"docs": {
|
||||
"mobileHeader": "Oh My OpenAgent Docs",
|
||||
"searchPlaceholder": "문서 검색...",
|
||||
"sections": {
|
||||
"overview": "Overview",
|
||||
"quickStart": "Quick Start",
|
||||
"configLocations": "Config File Locations",
|
||||
"agents": "Agents",
|
||||
"categories": "Categories",
|
||||
"skills": "Skills",
|
||||
"backgroundTasks": "Background Tasks",
|
||||
"hooks": "Hooks",
|
||||
"mcps": "MCPs",
|
||||
"browserAutomation": "Browser Automation",
|
||||
"tmux": "Tmux Integration",
|
||||
"gitMaster": "Git Master",
|
||||
"commentChecker": "Comment Checker",
|
||||
"experimental": "Experimental Features",
|
||||
"lsp": "LSP Configuration",
|
||||
"envVars": "Environment Variables"
|
||||
},
|
||||
"overview": {
|
||||
"title": "Configuration Reference",
|
||||
"description": "Oh My OpenAgent는 강력한 의견을 가지고 있지만 취향에 맞게 조정할 수 있습니다. 대부분의 사용자는 설정할 필요가 없습니다. {command}를 실행하고 바로 시작하세요."
|
||||
},
|
||||
"quickStart": {
|
||||
"title": "Quick Start"
|
||||
},
|
||||
"configLocations": {
|
||||
"title": "Config File Locations",
|
||||
"projectLevel": "(프로젝트 레벨)",
|
||||
"userLevel": "(사용자 레벨)",
|
||||
"jsonc": "JSONC가 지원되어 주석과 후행 쉼표를 사용할 수 있습니다."
|
||||
},
|
||||
"agentsSection": {
|
||||
"title": "Agents",
|
||||
"description": "내장된 Agent의 특정 동작을 설정합니다: Sisyphus, Hephaestus, Oracle, Librarian, Explore, Multimodal Looker, Prometheus, Metis, Momus, Atlas, Sisyphus Junior.",
|
||||
"overrideOptions": "Override 옵션",
|
||||
"permissions": "Permissions",
|
||||
"options": {
|
||||
"model": "모델 식별자 (예: openai/gpt-4o)",
|
||||
"variant": "모델 변형 (max, high, medium, low)",
|
||||
"category": "카테고리에서 설정 상속",
|
||||
"temperature": "샘플링 온도 (0-2)",
|
||||
"topP": "Top-p 샘플링 (0-1)",
|
||||
"prompt": "시스템 프롬프트 완전히 재정의",
|
||||
"promptAppend": "시스템 프롬프트에 텍스트 추가",
|
||||
"tools": "특정 도구 활성화 또는 비활성화",
|
||||
"disable": "이 Agent 비활성화",
|
||||
"maxTokens": "응답의 최대 토큰 수",
|
||||
"thinking": "확장 사고 설정",
|
||||
"reasoningEffort": "추론 노력: low, medium, high, xhigh"
|
||||
},
|
||||
"permissionValues": "ask / allow / deny",
|
||||
"permissionDescriptions": {
|
||||
"edit": "파일 편집 기능",
|
||||
"bash": "Bash 명령어 실행",
|
||||
"webfetch": "웹 요청 기능",
|
||||
"doomLoop": "무한 루프 재정의",
|
||||
"externalDirectory": "프로젝트 외부 파일 접근"
|
||||
}
|
||||
},
|
||||
"categoriesSection": {
|
||||
"title": "Categories",
|
||||
"description": "Categories를 사용하면 Agent가 상속할 수 있는 공유 설정을 정의할 수 있습니다.",
|
||||
"availableOptions": "Categories 사용 가능 옵션: {options}.",
|
||||
"categories": {
|
||||
"visualEngineering": "프론트엔드, UI/UX, 디자인 작업",
|
||||
"ultrabrain": "심층 논리적 추론",
|
||||
"deep": "자율 문제 해결, 철저한 리서치",
|
||||
"artistry": "창의적 작업",
|
||||
"quick": "간단하고 빠른 작업",
|
||||
"unspecifiedLow": "저노력 일반 작업",
|
||||
"unspecifiedHigh": "고노력 일반 작업",
|
||||
"writing": "문서 및 산문"
|
||||
}
|
||||
},
|
||||
"skillsSection": {
|
||||
"title": "Skills",
|
||||
"description": "내장된 스킬에는 {playwright}, {agentBrowser}, {gitMaster}가 포함됩니다. 커스텀 스킬도 정의할 수 있습니다."
|
||||
},
|
||||
"backgroundTasksSection": {
|
||||
"title": "Background Tasks",
|
||||
"priority": "우선순위:",
|
||||
"options": {
|
||||
"defaultConcurrency": "기본 최대 동시 작업 수",
|
||||
"staleTimeoutMs": "오래된 작업 타임아웃 (ms)",
|
||||
"providerConcurrency": "프로바이더당 동시성 제한",
|
||||
"modelConcurrency": "모델당 동시성 제한"
|
||||
}
|
||||
},
|
||||
"hooksSection": {
|
||||
"title": "Hooks",
|
||||
"description": "Hooks를 사용하면 다양한 라이프사이클 지점에서 기능을 확장할 수 있습니다."
|
||||
},
|
||||
"mcpsSection": {
|
||||
"title": "MCPs",
|
||||
"websearch": {
|
||||
"title": "websearch",
|
||||
"description": "Exa로 구동되는 고품질 검색 결과."
|
||||
},
|
||||
"context7": {
|
||||
"title": "context7",
|
||||
"description": "문서 검색 및 컨텍스트 관리."
|
||||
},
|
||||
"grepApp": {
|
||||
"title": "grep_app",
|
||||
"description": "GitHub 코드 검색 통합."
|
||||
}
|
||||
},
|
||||
"browserAutomationSection": {
|
||||
"title": "Browser Automation",
|
||||
"playwright": {
|
||||
"tool": "playwright",
|
||||
"description": "풀 브라우저 자동화 (기본값)",
|
||||
"useCase": "테스트, 복잡한 상호작용"
|
||||
},
|
||||
"agentBrowser": {
|
||||
"tool": "agent-browser",
|
||||
"description": "가벼운 브라우저 Agent",
|
||||
"useCase": "빠른 조회, 간단한 스크래핑"
|
||||
}
|
||||
},
|
||||
"tmuxSection": {
|
||||
"title": "Tmux Integration",
|
||||
"options": {
|
||||
"enabled": "Tmux 통합 활성화",
|
||||
"layout": "Tmux 창 레이아웃",
|
||||
"mainPaneSize": "메인 창 크기"
|
||||
}
|
||||
},
|
||||
"gitMasterSection": {
|
||||
"title": "Git Master",
|
||||
"options": {
|
||||
"commitFooter": "커밋 메시지에 추가할 텍스트",
|
||||
"includeCoAuthoredBy": "Co-authored-by 트레일러 추가"
|
||||
}
|
||||
},
|
||||
"commentCheckerSection": {
|
||||
"title": "Comment Checker",
|
||||
"description": "코드의 주석을 검증합니다. 커스텀 프롬프트에서 {placeholder} 자리 표시자를 사용하세요."
|
||||
},
|
||||
"experimentalSection": {
|
||||
"title": "Experimental Features",
|
||||
"options": {
|
||||
"aggressiveTruncation": "출력을 적극적으로 잘라내기",
|
||||
"autoResume": "중단된 작업 자동 재개",
|
||||
"preemptiveCompaction": "제한 전 컨텍스트 압축",
|
||||
"truncateAllToolOutputs": "모든 도구 출력 잘라내기"
|
||||
},
|
||||
"dynamicPruning": {
|
||||
"trigger": "dynamic_context_pruning",
|
||||
"description": "컨텍스트 윈도우 사용량을 효율적으로 관리하기 위한 동적 가지치기 규칙을 설정합니다."
|
||||
}
|
||||
},
|
||||
"lspSection": {
|
||||
"title": "LSP Configuration",
|
||||
"options": {
|
||||
"command": "LSP 서버 명령어",
|
||||
"extensions": "매칭할 파일 확장자",
|
||||
"priority": "서버 우선순위",
|
||||
"env": "환경 변수",
|
||||
"initialization": "초기화 옵션",
|
||||
"disabled": "이 LSP 비활성화"
|
||||
}
|
||||
},
|
||||
"envVarsSection": {
|
||||
"title": "Environment Variables",
|
||||
"opencodeConfigDir": {
|
||||
"name": "OPENCODE_CONFIG_DIR",
|
||||
"description": "기본 설정 디렉토리 경로를 재정의합니다."
|
||||
}
|
||||
},
|
||||
"footer": "Oh My OpenAgent Documentation © {year}"
|
||||
},
|
||||
"manifesto": {
|
||||
"badge": "Manifesto",
|
||||
"hero": {
|
||||
"title": "Ultrawork Manifesto",
|
||||
"subtitle": "고성과 엔지니어링의 철학"
|
||||
},
|
||||
"bottleneck": "> HUMAN IN THE LOOP = BOTTLENECK",
|
||||
"autonomousCar": "30초마다 스티어링 휠을 잡아야 하는 자율주행차를 상상해 보세요. 그걸 '자율주행'이라고 부를 수 있을까요? 아닙니다. 크루즈 컨트롤보다 조금 나은 운전 보조 기능에 불과합니다.",
|
||||
"whyDifferent": "코딩은 왜 다르다고 생각하나요?",
|
||||
"micromanagement": "우리는 'AI 코딩'이 20줄 코드를 쓰고 사용자가 고치기를 기다리는 챗봇을 의미하는 패러다임을 받아들였습니다. 그건 자동화가 아닙니다 — 마이크로매니징입니다.",
|
||||
"painPoints": {
|
||||
"fixing": "AI의 미완성 코드 고치기",
|
||||
"syntax": "구문 오류를 수동으로 수정하기",
|
||||
"copyPasting": "컨텍스트를 복사해서 붙여넣기",
|
||||
"reviewing": "환각을 위해 모든 줄을 검토하기"
|
||||
},
|
||||
"notCollaboration": "그건 '인간-AI 협업'이 아닙니다 — AI가 일을 못 하고 있는 겁니다.",
|
||||
"premise": "{linkText}는 인간이 스펠체커가 아닌 설계자가 되어야 한다는 전제로 만들어졌습니다.",
|
||||
"premiseLinkText": "Oh My OpenAgent",
|
||||
"indistinguishable": {
|
||||
"title": "구분 불가능한 코드",
|
||||
"subtitle": "Agent가 작성한 코드는 시니어 엔지니어가 작성한 코드와 구분할 수 없어야 합니다.",
|
||||
"items": {
|
||||
"patterns": "기존 코드베이스 패턴과 아키텍처를 따름",
|
||||
"errorHandling": "적절한 오류 처리와 엣지 케이스 구현",
|
||||
"tests": "커버리지가 아닌 실제 동작을 테스트하는 테스트 작성",
|
||||
"noSlop": "'AI 슬롭' 없음—깨끗하고 간결하며 유지보수 가능한 코드",
|
||||
"comments": "값을 더할 때만 주석 사용, 뻔한 내용은 제외"
|
||||
},
|
||||
"quote": "\"커밋이 인간이 한 건지 Agent가 한 건지 알 수 있다면, Agent는 실패한 것입니다.\""
|
||||
},
|
||||
"tokenCost": {
|
||||
"title": "Token Cost vs. Productivity",
|
||||
"description": "우리는 토큰 사용량을 신경 쓰지 않습니다. 출력을 신경 씁니다. 토큰에 $5를 써서 엔지니어링 시간 1시간을 절약하면 20배의 ROI입니다.",
|
||||
"parallelAgents": "여러 해결책을 탐색하는 병렬 Agent",
|
||||
"completeWork": "인간 개입 없이 작업 완료",
|
||||
"selfVerification": "철저한 자기 검증 루프",
|
||||
"however": "하지만...",
|
||||
"optimizeDescription": "우리는 효율성을 최적화합니다. 모델을 묶어두는 것이 아니라:",
|
||||
"cheaperModels": "일상 작업에 저렴한 모델 사용",
|
||||
"avoidingRedundant": "중복 탐색 방지",
|
||||
"intelligentCaching": "컨텍스트의 지능형 캐싱",
|
||||
"stoppingExactly": "충분할 때 정확히 중지"
|
||||
},
|
||||
"cognitiveLoad": {
|
||||
"title": "인간 인지 부하 최소화",
|
||||
"subtitle": "인간은 원하는 것을 말하기만 하면 됩니다. 나머지는 Agent의 일입니다.",
|
||||
"ultrawork": {
|
||||
"badge": "방식 1",
|
||||
"title": "Ultrawork",
|
||||
"subtitle": "그냥 'ulw'라고 하고 자리를 뜨세요.",
|
||||
"steps": {
|
||||
"analyze": "코드베이스 컨텍스트 분석",
|
||||
"breakdown": "작업을 원자적 단계로 분해",
|
||||
"execute": "구현 실행",
|
||||
"verify": "요구사항 대비 검증",
|
||||
"commit": "변경사항 커밋"
|
||||
},
|
||||
"footer": "제로 개입. 완전한 자율성. 오직 결과만."
|
||||
},
|
||||
"prometheus": {
|
||||
"badge": "방식 2",
|
||||
"title": "Prometheus + Atlas",
|
||||
"subtitle": "전략적 통제를 원할 때.",
|
||||
"prometheusTitle": "Prometheus",
|
||||
"prometheusDescription": "인터뷰를 진행하고, 컨텍스트를 조사하며, 상세한 YAML 계획을 생성합니다.",
|
||||
"atlasTitle": "Atlas",
|
||||
"atlasDescription": "계획을 실행하고, 하위 Agent에 위임하고, 웨이브를 관리하며, 진행 상황을 추적합니다.",
|
||||
"footer": "당신이 설계합니다. Agent가 실행합니다. 완전한 투명성."
|
||||
}
|
||||
},
|
||||
"principles": {
|
||||
"predictable": {
|
||||
"title": "예측 가능",
|
||||
"description": "같은 입력 = 일관된 출력. 요청하지 않은 한 랜덤한 편차나 창의적 자유 없음."
|
||||
},
|
||||
"continuous": {
|
||||
"title": "지속 가능",
|
||||
"description": "중단에서 살아남습니다. 실시간으로 진행 상황을 추적합니다. 세션 간 컨텍스트를 보존합니다."
|
||||
},
|
||||
"delegatable": {
|
||||
"title": "위임 가능",
|
||||
"description": "명확한 수용 기준. 자기 수정 메커니즘. 절대적으로 필요할 때만 에스컬레이션."
|
||||
}
|
||||
},
|
||||
"coreLoop": {
|
||||
"title": "핵심 루프",
|
||||
"features": {
|
||||
"prometheus": {
|
||||
"feature": "Prometheus",
|
||||
"purpose": "지능형 인터뷰로 의도 추출"
|
||||
},
|
||||
"metis": {
|
||||
"feature": "Metis",
|
||||
"purpose": "버그가 되기 전에 모호함 포착"
|
||||
},
|
||||
"momus": {
|
||||
"feature": "Momus",
|
||||
"purpose": "실행 전 계획의 완성도 검증"
|
||||
},
|
||||
"orchestrator": {
|
||||
"feature": "Orchestrator",
|
||||
"purpose": "인간 마이크로매니징 없이 작업 조율"
|
||||
},
|
||||
"todoContinuation": {
|
||||
"feature": "Todo Continuation",
|
||||
"purpose": "완료 강제, '다 했어요' 거짓말 방지"
|
||||
},
|
||||
"categorySystem": {
|
||||
"feature": "Category System",
|
||||
"purpose": "인간 결정 없이 최적 모델로 라우팅"
|
||||
},
|
||||
"backgroundAgents": {
|
||||
"feature": "Background Agents",
|
||||
"purpose": "사용자 차단 없이 병렬 리서치"
|
||||
},
|
||||
"wisdomAccumulation": {
|
||||
"feature": "Wisdom Accumulation",
|
||||
"purpose": "작업에서 학습, 실수 반복 금지"
|
||||
}
|
||||
}
|
||||
},
|
||||
"future": {
|
||||
"title": "우리가 만드는 미래",
|
||||
"items": {
|
||||
"focus": "인간 개발자는 WHAT을 구축할지에 집중하고, AI가 이를 구축하도록 만드는 HOW에는 집중하지 않음",
|
||||
"quality": "누가 작성했는지와 무관한 코드 품질",
|
||||
"complexity": "복잡한 프로젝트가 간단한 프로젝트만큼 쉬움",
|
||||
"promptEngineering": "'프롬프트 엔지니어링'은 구식이 됨"
|
||||
},
|
||||
"quote1": "\"Agent는 보이지 않아야 합니다. 전기처럼, 수돗물처럼.\"",
|
||||
"quote2": "\"스위치를 누릅니다. 불이 켜집니다. 발전소는 생각하지 않습니다.\""
|
||||
},
|
||||
"finalCta": {
|
||||
"title": "just ulw ulw",
|
||||
"button": "Oh My OpenAgent 받기"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,561 @@
|
||||
{
|
||||
"metadata": {
|
||||
"title": "Oh My OpenAgent",
|
||||
"description": "最佳 Agent harness。遇见 Sisyphus:开箱即用的 Agent,像你一样编程。"
|
||||
},
|
||||
"nav": {
|
||||
"brand": "Oh My OpenAgent",
|
||||
"features": "功能特性",
|
||||
"agents": "Agents",
|
||||
"docs": "文档",
|
||||
"manifesto": "宣言",
|
||||
"starOnGitHub": "在 GitHub 上标星"
|
||||
},
|
||||
"footer": {
|
||||
"brand": "Oh My OpenAgent",
|
||||
"copyright": "© {year} Oh My OpenAgent。基于 SUL-1.0 开源许可。",
|
||||
"github": "GitHub",
|
||||
"discord": "Discord",
|
||||
"documentation": "文档",
|
||||
"manifesto": "宣言"
|
||||
},
|
||||
"landing": {
|
||||
"hero": {
|
||||
"title": "最强 ",
|
||||
"titleHighlight": "Agent Harness",
|
||||
"subtitle": "{stars} Stars、{downloads} 下载。实战验证,生产级品质。",
|
||||
"githubStars": "{count} GitHub Stars",
|
||||
"specializedAgents": "{count} 个专业 Agent",
|
||||
"totalDownloads": "累计 {count}+ 下载",
|
||||
"monthlyDownloads": "月度 {count}+ 下载",
|
||||
"lifecycleHooks": "{count}+ 生命周期 Hooks",
|
||||
"installCommand": "bunx oh-my-openagent install",
|
||||
"getStarted": "开始使用",
|
||||
"viewOnGitHub": "在 GitHub 上查看"
|
||||
},
|
||||
"ulw": {
|
||||
"badge": "魔法咒语",
|
||||
"title": "ulw",
|
||||
"headline": "敲三个字母。走人。",
|
||||
"description": "Ultra Work 模式触发最大精度:自动规划、深度研究、并行 Agent、自我纠正循环。系统直到完成才会停止。你不需要盯梢。",
|
||||
"autoPlanning": "自动规划",
|
||||
"deepResearch": "深度研究",
|
||||
"selfCorrection": "自我纠错",
|
||||
"parallelAgents": "并行 Agent",
|
||||
"terminalTitle": "sisyphus — -zsh — 80x24",
|
||||
"terminalInput": "ulw add authentication to the API",
|
||||
"steps": {
|
||||
"scanning": "正在扫描代码库...",
|
||||
"context": "正在启动 3 个 explore agent...",
|
||||
"planning": "正在构建执行计划...",
|
||||
"delegating": "正在部署 4 个并行 agent...",
|
||||
"verifying": "正在运行验证测试...",
|
||||
"complete": "完成。847 行更改,0 个问题。"
|
||||
},
|
||||
"tagline": "零干预。完全自主。只看结果。"
|
||||
},
|
||||
"sisyphus": {
|
||||
"badge": "主编排器",
|
||||
"title": "Sisyphus",
|
||||
"headline": "永不眠的 CTO",
|
||||
"description": "以神话人物命名——永远推着巨石上山。Sisyphus 解析隐含需求,适应代码库成熟度,并委派给专家。被中断了?boulder 系统会从你停下的地方精确恢复。",
|
||||
"phases": {
|
||||
"intent": {
|
||||
"title": "Intent Gate",
|
||||
"description": "解析你真正的意图,而不只是你输入的内容"
|
||||
},
|
||||
"explore": {
|
||||
"title": "Codebase Assessment",
|
||||
"description": "在碰任何一行代码之前先映射架构"
|
||||
},
|
||||
"delegate": {
|
||||
"title": "Smart Delegation",
|
||||
"description": "路由到正确的专家 agent"
|
||||
},
|
||||
"verify": {
|
||||
"title": "Independent Verification",
|
||||
"description": "不相信任何东西。验证一切。"
|
||||
}
|
||||
},
|
||||
"boulderTitle": "Session Continuity",
|
||||
"boulderDescription": "活跃工作被记录在 boulder.json 中。停电?系统崩溃?没关系。从你停下的地方精确恢复。",
|
||||
"model": "Claude Opus 4.6 Max"
|
||||
},
|
||||
"prometheusAtlas": {
|
||||
"badge": "编排工作流",
|
||||
"title": "先想,再动",
|
||||
"headline": "规划和执行是不同的工作",
|
||||
"prometheus": {
|
||||
"name": "Prometheus",
|
||||
"role": "Strategic Planner",
|
||||
"model": "Claude Opus 4.6 Max",
|
||||
"description": "架构师。与你访谈,探索代码库,并创建详细的执行计划。从不写代码。使用 Metis 和 Momus 作为质量关卡。",
|
||||
"features": [
|
||||
"智能访谈模式",
|
||||
"多 agent 代码库探索",
|
||||
"与 Metis 进行差距分析",
|
||||
"与 Momus 进行严苛审查"
|
||||
]
|
||||
},
|
||||
"atlas": {
|
||||
"name": "Atlas",
|
||||
"role": "Master Executor",
|
||||
"model": "Claude Sonnet 4.6",
|
||||
"description": "构建者。阅读已验证的计划,通过 category+skills 系统委派给专业 agent。跨任务追踪学习。独立验证 — 从不相信子 agent 的说法。",
|
||||
"features": [
|
||||
"基于意图的任务路由",
|
||||
"跨任务的智慧积累",
|
||||
"Boulder 会话连续性",
|
||||
"独立的结果验证"
|
||||
]
|
||||
},
|
||||
"metis": {
|
||||
"name": "Metis",
|
||||
"description": "在歧义变成 Bug 之前捕获它们。"
|
||||
},
|
||||
"momus": {
|
||||
"name": "Momus",
|
||||
"description": "只有当计划真正完美时才说 OKAY。"
|
||||
},
|
||||
"workflow": {
|
||||
"step1": "向 Prometheus 描述你的工作",
|
||||
"step2": "Metis 分析差距",
|
||||
"step3": "Momus 严苛验证",
|
||||
"step4": "Atlas 委派给专家",
|
||||
"step5": "对一切进行独立验证"
|
||||
},
|
||||
"whyItWorks": "规划者不编码。执行者遵循已验证的计划。智能存在于系统中,而非单个 agent。"
|
||||
},
|
||||
"hephaestus": {
|
||||
"badge": "深度工作者",
|
||||
"title": "Hephaestus",
|
||||
"headline": "给他目标,不是菜谱",
|
||||
"description": "以希腊锻造之神命名。有条不紊、彻底、执着。在写任何代码前启动 2-5 个并行 explore agent。专为深度架构推理、复杂调试和跨领域综合而设计。",
|
||||
"loop": {
|
||||
"explore": "EXPLORE — 绘制地形",
|
||||
"plan": "PLAN — 规划路线",
|
||||
"decide": "DECIDE — 确定路径",
|
||||
"execute": "EXECUTE — 精确构建",
|
||||
"verify": "VERIFY — 证明有效"
|
||||
},
|
||||
"tagline": "当足够好还不够时。",
|
||||
"model": "GPT 5.3 Codex Medium"
|
||||
},
|
||||
"agents": {
|
||||
"title": "Specialized Agents",
|
||||
"subtitle": "五个专用 Agent 覆盖常见任务,外加按需动态构建的 Agent 系统。",
|
||||
"oracle": {
|
||||
"name": "Oracle",
|
||||
"role": "Architecture Consultant",
|
||||
"model": "GPT 5.4 High",
|
||||
"description": "复杂调试和架构决策。当前路不明时。"
|
||||
},
|
||||
"librarian": {
|
||||
"name": "Librarian",
|
||||
"role": "Docs & Code Search",
|
||||
"model": "MiniMax M2.5",
|
||||
"description": "寻找真正的 GitHub 示例和官方文档。基于证据,附带永久链接。"
|
||||
},
|
||||
"explore": {
|
||||
"name": "Explore",
|
||||
"role": "Codebase Grep",
|
||||
"model": "MiniMax M2.5",
|
||||
"description": "超高速代码库搜索。便宜、并行、始终在后台运行。"
|
||||
},
|
||||
"metis": {
|
||||
"name": "Metis",
|
||||
"role": "Plan Consultant",
|
||||
"model": "Claude Opus 4.6 Max",
|
||||
"description": "在计划中的歧义变成生产 Bug 之前捕获它们。"
|
||||
},
|
||||
"momus": {
|
||||
"name": "Momus",
|
||||
"role": "Plan Reviewer",
|
||||
"model": "GPT 5.4 Extra High",
|
||||
"description": "严苛验证。只有当计划无懈可击时才批准。"
|
||||
},
|
||||
"dynamicSystem": {
|
||||
"name": "Dynamic Agent",
|
||||
"role": "随需而造",
|
||||
"description": "根据请求自动匹配最佳模型、注入所需技能,并实时构建专属 Agent。"
|
||||
}
|
||||
},
|
||||
"architecture": {
|
||||
"title": "与众不同的设计",
|
||||
"subtitle": "真正重要的设计决策。",
|
||||
"principles": {
|
||||
"specialization": {
|
||||
"title": "Specialization",
|
||||
"description": "每个 agent 把一件事做到极致。没有万事通。"
|
||||
},
|
||||
"trustVerify": {
|
||||
"title": "Trust But Verify",
|
||||
"description": "编排器对一切进行独立验证。子 agent 没有免费通行证。"
|
||||
},
|
||||
"wisdom": {
|
||||
"title": "Wisdom Accumulation",
|
||||
"description": "每个任务的学习传递给后续所有任务。系统在工作时变得更聪明。"
|
||||
},
|
||||
"modelOptimization": {
|
||||
"title": "Model Optimization",
|
||||
"description": "昂贵的模型用于规划和复杂决策。便宜的模型用于日常工作。每美元最大产出。"
|
||||
},
|
||||
"categories": {
|
||||
"title": "Category System",
|
||||
"description": "基于意图的路由。说出你需要什么 — ultrabrain、visual-engineering、quick — 正确的模型来处理。"
|
||||
},
|
||||
"continuity": {
|
||||
"title": "Session Continuity",
|
||||
"description": "Boulder 系统:中断的工作从你停下的地方精确恢复。零上下文丢失。"
|
||||
}
|
||||
}
|
||||
},
|
||||
"reviews": {
|
||||
"title": "开发者们怎么说",
|
||||
"review1": {
|
||||
"text": "It made me cancel my Cursor subscription. Unbelievable things are happening in the open source community.",
|
||||
"author": "Arthur Guiot"
|
||||
},
|
||||
"review2": {
|
||||
"text": "If Claude Code does in 7 days what a human does in 3 months, Sisyphus does it in 1 hour.",
|
||||
"author": "B, Quant Researcher"
|
||||
},
|
||||
"review3": {
|
||||
"text": "Knocked out 8000 eslint warnings with Oh My Opencode, just in a day.",
|
||||
"author": "Jacob Ferrari"
|
||||
},
|
||||
"review4": {
|
||||
"text": "use oh-my-openagent, you will never go back",
|
||||
"author": "d0t3ch"
|
||||
},
|
||||
"review5": {
|
||||
"text": "You guys should pull this into core and recruit him. Seriously. It's really, really, really good.",
|
||||
"author": "Henning Kilset"
|
||||
},
|
||||
"review6": {
|
||||
"text": "Oh My OpenCode Is Actually Insane",
|
||||
"author": "Darren Builds AI (YouTube)"
|
||||
}
|
||||
},
|
||||
"cta": {
|
||||
"title": "别写样板代码了",
|
||||
"subtitle": "安装 Oh My OpenAgent,让 agent 来处理。距离你的第一个 ulw 命令只需 30 秒。",
|
||||
"installCommand": "bunx oh-my-openagent install",
|
||||
"installNow": "立即安装",
|
||||
"readTheDocs": "阅读文档"
|
||||
}
|
||||
},
|
||||
"docs": {
|
||||
"mobileHeader": "Oh My OpenAgent 文档",
|
||||
"searchPlaceholder": "搜索文档...",
|
||||
"sections": {
|
||||
"overview": "概览",
|
||||
"quickStart": "快速开始",
|
||||
"configLocations": "配置文件位置",
|
||||
"agents": "Agents",
|
||||
"categories": "分类",
|
||||
"skills": "Skills",
|
||||
"backgroundTasks": "后台任务",
|
||||
"hooks": "钩子",
|
||||
"mcps": "MCPs",
|
||||
"browserAutomation": "浏览器自动化",
|
||||
"tmux": "Tmux 集成",
|
||||
"gitMaster": "Git Master",
|
||||
"commentChecker": "注释检查器",
|
||||
"experimental": "实验性功能",
|
||||
"lsp": "LSP 配置",
|
||||
"envVars": "环境变量"
|
||||
},
|
||||
"overview": {
|
||||
"title": "配置参考",
|
||||
"description": "Oh My OpenAgent 高度自有主张,但可根据个人喜好调整。大多数用户无需配置——运行 {command} 即可开始。"
|
||||
},
|
||||
"quickStart": {
|
||||
"title": "快速开始"
|
||||
},
|
||||
"configLocations": {
|
||||
"title": "配置文件位置",
|
||||
"projectLevel": "(项目级)",
|
||||
"userLevel": "(用户级)",
|
||||
"jsonc": "支持 JSONC 格式,允许注释和尾随逗号。"
|
||||
},
|
||||
"agentsSection": {
|
||||
"title": "Agents",
|
||||
"description": "为内置 Agent 配置特定行为:Sisyphus、Hephaestus、Oracle、Librarian、Explore、Multimodal Looker、Prometheus、Metis、Momus、Atlas 和 Sisyphus Junior。",
|
||||
"overrideOptions": "覆盖选项",
|
||||
"permissions": "权限",
|
||||
"options": {
|
||||
"model": "模型标识符(例如 openai/gpt-4o)",
|
||||
"variant": "模型变体(max、high、medium、low)",
|
||||
"category": "从分类继承配置",
|
||||
"temperature": "采样温度(0-2)",
|
||||
"topP": "Top-p 采样(0-1)",
|
||||
"prompt": "完全覆盖系统提示词",
|
||||
"promptAppend": "在系统提示词后追加文本",
|
||||
"tools": "启用或禁用特定工具",
|
||||
"disable": "禁用此 Agent",
|
||||
"maxTokens": "响应的最大 token 数",
|
||||
"thinking": "扩展思考配置",
|
||||
"reasoningEffort": "推理强度:low、medium、high、xhigh"
|
||||
},
|
||||
"permissionValues": "ask / allow / deny",
|
||||
"permissionDescriptions": {
|
||||
"edit": "文件编辑能力",
|
||||
"bash": "Bash 命令执行",
|
||||
"webfetch": "网络请求能力",
|
||||
"doomLoop": "无限循环覆盖",
|
||||
"externalDirectory": "访问项目外部文件"
|
||||
}
|
||||
},
|
||||
"categoriesSection": {
|
||||
"title": "分类",
|
||||
"description": "分类允许你定义 Agent 可以继承的共享配置。",
|
||||
"availableOptions": "分类可用选项:{options}。",
|
||||
"categories": {
|
||||
"visualEngineering": "前端、UI/UX、设计任务",
|
||||
"ultrabrain": "深度逻辑推理",
|
||||
"deep": "自主解决问题与深入研究",
|
||||
"artistry": "创意任务",
|
||||
"quick": "简单、快速任务",
|
||||
"unspecifiedLow": "低投入一般任务",
|
||||
"unspecifiedHigh": "高投入一般任务",
|
||||
"writing": "文档和写作"
|
||||
}
|
||||
},
|
||||
"skillsSection": {
|
||||
"title": "Skills",
|
||||
"description": "内置技能包括 {playwright}、{agentBrowser} 和 {gitMaster}。你也可以定义自定义技能。"
|
||||
},
|
||||
"backgroundTasksSection": {
|
||||
"title": "后台任务",
|
||||
"priority": "优先级:",
|
||||
"options": {
|
||||
"defaultConcurrency": "默认最大并发任务数",
|
||||
"staleTimeoutMs": "过时任务超时时间(毫秒)",
|
||||
"providerConcurrency": "每个提供商的并发限制",
|
||||
"modelConcurrency": "每个模型的并发限制"
|
||||
}
|
||||
},
|
||||
"hooksSection": {
|
||||
"title": "钩子",
|
||||
"description": "钩子允许你在各个生命周期点扩展功能。"
|
||||
},
|
||||
"mcpsSection": {
|
||||
"title": "MCPs",
|
||||
"websearch": {
|
||||
"title": "websearch",
|
||||
"description": "由 Exa 驱动,提供高质量搜索结果。"
|
||||
},
|
||||
"context7": {
|
||||
"title": "context7",
|
||||
"description": "文档检索和上下文管理。"
|
||||
},
|
||||
"grepApp": {
|
||||
"title": "grep_app",
|
||||
"description": "GitHub 代码搜索集成。"
|
||||
}
|
||||
},
|
||||
"browserAutomationSection": {
|
||||
"title": "浏览器自动化",
|
||||
"playwright": {
|
||||
"tool": "playwright",
|
||||
"description": "完整的浏览器自动化(默认)",
|
||||
"useCase": "测试、复杂交互"
|
||||
},
|
||||
"agentBrowser": {
|
||||
"tool": "agent-browser",
|
||||
"description": "轻量级浏览器 Agent",
|
||||
"useCase": "快速查询、简单抓取"
|
||||
}
|
||||
},
|
||||
"tmuxSection": {
|
||||
"title": "Tmux 集成",
|
||||
"options": {
|
||||
"enabled": "启用 Tmux 集成",
|
||||
"layout": "Tmux 窗口布局",
|
||||
"mainPaneSize": "主面板大小"
|
||||
}
|
||||
},
|
||||
"gitMasterSection": {
|
||||
"title": "Git Master",
|
||||
"options": {
|
||||
"commitFooter": "追加到提交消息的文本",
|
||||
"includeCoAuthoredBy": "添加 Co-authored-by 标记"
|
||||
}
|
||||
},
|
||||
"commentCheckerSection": {
|
||||
"title": "注释检查器",
|
||||
"description": "验证代码中的注释。在自定义提示词中使用 {placeholder} 占位符。"
|
||||
},
|
||||
"experimentalSection": {
|
||||
"title": "实验性功能",
|
||||
"options": {
|
||||
"aggressiveTruncation": "积极截断输出",
|
||||
"autoResume": "自动恢复中断的任务",
|
||||
"preemptiveCompaction": "在达到限制前压缩上下文",
|
||||
"truncateAllToolOutputs": "截断所有工具输出"
|
||||
},
|
||||
"dynamicPruning": {
|
||||
"trigger": "dynamic_context_pruning",
|
||||
"description": "配置动态剪枝规则,高效管理上下文窗口使用。"
|
||||
}
|
||||
},
|
||||
"lspSection": {
|
||||
"title": "LSP 配置",
|
||||
"options": {
|
||||
"command": "LSP 服务器命令",
|
||||
"extensions": "匹配的文件扩展名",
|
||||
"priority": "服务器优先级",
|
||||
"env": "环境变量",
|
||||
"initialization": "初始化选项",
|
||||
"disabled": "禁用此 LSP"
|
||||
}
|
||||
},
|
||||
"envVarsSection": {
|
||||
"title": "环境变量",
|
||||
"opencodeConfigDir": {
|
||||
"name": "OPENCODE_CONFIG_DIR",
|
||||
"description": "覆盖默认配置目录路径。"
|
||||
}
|
||||
},
|
||||
"footer": "Oh My OpenAgent 文档 © {year}"
|
||||
},
|
||||
"manifesto": {
|
||||
"badge": "宣言",
|
||||
"hero": {
|
||||
"title": "Ultrawork 宣言",
|
||||
"subtitle": "高产出工程的哲学"
|
||||
},
|
||||
"bottleneck": "> 人类介入 = 瓶颈",
|
||||
"autonomousCar": "想象一辆自动驾驶汽车,每 30 秒就需要你握住方向盘。你会称它为'自动驾驶'吗?不会。你会说它是个比定速巡航好不了多少的驾驶辅助功能。",
|
||||
"whyDifferent": "编程又有什么不同?",
|
||||
"micromanagement": "我们已经接受了一种范式,'AI 编程'意味着一个聊天机器人写 20 行代码,然后等你修复。这不是自动化——这是微观管理。",
|
||||
"painPoints": {
|
||||
"fixing": "修复 AI 的半成品代码",
|
||||
"syntax": "手动纠正语法错误",
|
||||
"copyPasting": "来回复制粘贴上下文",
|
||||
"reviewing": "逐行检查幻觉"
|
||||
},
|
||||
"notCollaboration": "那不是'人机协作'——那是 AI 没有完成它的工作。",
|
||||
"premise": "{linkText} 建立在一个人类应该是架构师,而非拼写检查员的前提上。",
|
||||
"premiseLinkText": "Oh My OpenAgent",
|
||||
"indistinguishable": {
|
||||
"title": "无法区分的代码",
|
||||
"subtitle": "Agent 写的代码应该与资深工程师写的代码无法区分。",
|
||||
"items": {
|
||||
"patterns": "遵循现有代码库的模式和架构",
|
||||
"errorHandling": "实现正确的错误处理和边界情况",
|
||||
"tests": "编写真正测试行为的测试,而不仅是覆盖率",
|
||||
"noSlop": "没有'AI 垃圾代码'——干净、简洁、可维护的代码",
|
||||
"comments": "只在增加价值时注释,不陈述显而易见的内容"
|
||||
},
|
||||
"quote": "\"如果你能分辨出提交是由人类还是 Agent 完成的,那么 Agent 就失败了。\""
|
||||
},
|
||||
"tokenCost": {
|
||||
"title": "Token 成本 vs 生产力",
|
||||
"description": "我们不在乎 token 使用量。我们在乎产出。如果花 5 美元在 token 上能节省一小时的工程时间,那就是 20 倍的投资回报。",
|
||||
"parallelAgents": "并行 Agent 探索多种解决方案",
|
||||
"completeWork": "无需人工干预即可完成工作",
|
||||
"selfVerification": "彻底的自我验证循环",
|
||||
"however": "然而...",
|
||||
"optimizeDescription": "我们在关键之处优化效率。不是通过削弱模型,而是:",
|
||||
"cheaperModels": "对常规任务使用更便宜的模型",
|
||||
"avoidingRedundant": "避免冗余探索",
|
||||
"intelligentCaching": "智能缓存上下文",
|
||||
"stoppingExactly": "恰到好处地停止"
|
||||
},
|
||||
"cognitiveLoad": {
|
||||
"title": "最小化人类认知负荷",
|
||||
"subtitle": "人类只需要说出他们想要什么。其余都是 Agent 的工作。",
|
||||
"ultrawork": {
|
||||
"badge": "方法 1",
|
||||
"title": "Ultrawork",
|
||||
"subtitle": "只需说 'ulw' 然后离开。",
|
||||
"steps": {
|
||||
"analyze": "分析代码库上下文",
|
||||
"breakdown": "将任务分解为原子步骤",
|
||||
"execute": "执行实现",
|
||||
"verify": "针对需求验证",
|
||||
"commit": "提交更改"
|
||||
},
|
||||
"footer": "零干预。完全自主。只看结果。"
|
||||
},
|
||||
"prometheus": {
|
||||
"badge": "方法 2",
|
||||
"title": "Prometheus + Atlas",
|
||||
"subtitle": "当你需要战略性控制时。",
|
||||
"prometheusTitle": "Prometheus",
|
||||
"prometheusDescription": "进行访谈、研究上下文并生成详细的 YAML 计划。",
|
||||
"atlasTitle": "Atlas",
|
||||
"atlasDescription": "执行计划,委派给子 Agent,管理波次并跟踪进度。",
|
||||
"footer": "你设计架构。Agent 执行。完全透明。"
|
||||
}
|
||||
},
|
||||
"principles": {
|
||||
"predictable": {
|
||||
"title": "可预测",
|
||||
"description": "相同输入 = 一致输出。除非要求,否则没有随机偏差或创意发挥。"
|
||||
},
|
||||
"continuous": {
|
||||
"title": "持续",
|
||||
"description": "经得起中断。实时跟踪进度。跨会话保留上下文。"
|
||||
},
|
||||
"delegatable": {
|
||||
"title": "可委派",
|
||||
"description": "明确的验收标准。自我纠错机制。仅在绝对必要时升级。"
|
||||
}
|
||||
},
|
||||
"coreLoop": {
|
||||
"title": "核心循环",
|
||||
"features": {
|
||||
"prometheus": {
|
||||
"feature": "Prometheus",
|
||||
"purpose": "通过智能访谈提取意图"
|
||||
},
|
||||
"metis": {
|
||||
"feature": "Metis",
|
||||
"purpose": "在歧义变成 Bug 之前捕获它们"
|
||||
},
|
||||
"momus": {
|
||||
"feature": "Momus",
|
||||
"purpose": "在执行前验证计划完整性"
|
||||
},
|
||||
"orchestrator": {
|
||||
"feature": "Orchestrator",
|
||||
"purpose": "无需人工微观管理协调工作"
|
||||
},
|
||||
"todoContinuation": {
|
||||
"feature": "Todo Continuation",
|
||||
"purpose": "强制完成,防止'我完成了'的谎言"
|
||||
},
|
||||
"categorySystem": {
|
||||
"feature": "Category System",
|
||||
"purpose": "无需人工决策路由到最优模型"
|
||||
},
|
||||
"backgroundAgents": {
|
||||
"feature": "Background Agents",
|
||||
"purpose": "并行研究而不阻塞用户"
|
||||
},
|
||||
"wisdomAccumulation": {
|
||||
"feature": "Wisdom Accumulation",
|
||||
"purpose": "从工作中学习,不重复错误"
|
||||
}
|
||||
}
|
||||
},
|
||||
"future": {
|
||||
"title": "我们正在构建的未来",
|
||||
"items": {
|
||||
"focus": "人类开发者专注于构建什么,而非如何让 AI 构建它",
|
||||
"quality": "代码质量与谁编写的无关",
|
||||
"complexity": "复杂项目与简单项目一样容易",
|
||||
"promptEngineering": "'提示工程'变得过时"
|
||||
},
|
||||
"quote1": "\"Agent 应该是无形的。就像电,就像自来水。\"",
|
||||
"quote2": "\"你按下开关。灯亮了。你不会去想电网。\""
|
||||
},
|
||||
"finalCta": {
|
||||
"title": "只需 ulw ulw",
|
||||
"button": "获取 Oh My OpenAgent"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { NextResponse, type NextRequest } from "next/server"
|
||||
import createMiddleware from "next-intl/middleware"
|
||||
import { routing } from "./i18n/routing"
|
||||
|
||||
const handleI18nRouting = createMiddleware(routing)
|
||||
const oldHosts = new Set(["ohmyopencode.org", "www.ohmyopencode.org"])
|
||||
const primaryHost = "ohmyopenagent.com"
|
||||
|
||||
export default function middleware(request: NextRequest) {
|
||||
const forwardedHost = request.headers.get("x-forwarded-host")
|
||||
const requestHost = request.headers.get("host")
|
||||
const hostname = (forwardedHost ?? requestHost ?? request.nextUrl.hostname).split(":")[0]
|
||||
|
||||
if (hostname && oldHosts.has(hostname)) {
|
||||
const redirectUrl = request.nextUrl.clone()
|
||||
redirectUrl.protocol = "https"
|
||||
redirectUrl.host = primaryHost
|
||||
return NextResponse.redirect(redirectUrl, 308)
|
||||
}
|
||||
|
||||
return handleI18nRouting(request)
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: ["/", "/((?!api|_next|_vercel|.*\\..*).+)"],
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import type { NextConfig } from "next"
|
||||
import createNextIntlPlugin from "next-intl/plugin"
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
allowedDevOrigins: ["127.0.0.1", "::1"],
|
||||
turbopack: {
|
||||
root: __dirname,
|
||||
},
|
||||
}
|
||||
|
||||
const withNextIntl = createNextIntlPlugin()
|
||||
export default withNextIntl(nextConfig)
|
||||
@@ -0,0 +1,3 @@
|
||||
import { defineCloudflareConfig } from "@opennextjs/cloudflare"
|
||||
|
||||
export default defineCloudflareConfig()
|
||||
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"name": "oh-my-openagent-web",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"prebuild": "node ./scripts/prepare-build.mjs",
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "eslint \"app/**/*.{ts,tsx}\" \"components/**/*.{ts,tsx}\" \"lib/**/*.{ts,tsx}\" \"e2e/**/*.{ts,tsx}\"",
|
||||
"lint:fix": "eslint --fix \"app/**/*.{ts,tsx}\" \"components/**/*.{ts,tsx}\" \"lib/**/*.{ts,tsx}\" \"e2e/**/*.{ts,tsx}\"",
|
||||
"format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,css,md}\"",
|
||||
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,json,css,md}\"",
|
||||
"type-check": "tsc --noEmit",
|
||||
"test:e2e": "playwright test",
|
||||
"test:e2e:ui": "playwright test --ui",
|
||||
"test:e2e:headed": "playwright test --headed",
|
||||
"preview": "node ./scripts/prepare-build.mjs && opennextjs-cloudflare build && opennextjs-cloudflare preview",
|
||||
"deploy": "node ./scripts/prepare-build.mjs && opennextjs-cloudflare build && opennextjs-cloudflare deploy",
|
||||
"cf-typegen": "wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-accordion": "^1.2.12",
|
||||
"@radix-ui/react-separator": "^1.1.8",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"geist": "^1.5.1",
|
||||
"lucide-react": "0.553.0",
|
||||
"motion": "^12.33.0",
|
||||
"next": "15.5.10",
|
||||
"next-intl": "^4.8.2",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@opennextjs/cloudflare": "^1.17.1",
|
||||
"@playwright/test": "1.56.1",
|
||||
"@tailwindcss/postcss": "4.1.17",
|
||||
"@types/node": "^22",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.5.10",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"eslint-plugin-prettier": "5.5.4",
|
||||
"globals": "^16.4.0",
|
||||
"postcss": "^8",
|
||||
"prettier": "3.6.2",
|
||||
"prettier-plugin-tailwindcss": "^0.6.0",
|
||||
"tailwindcss": "4.1.17",
|
||||
"typescript-eslint": "^8.56.1",
|
||||
"typescript": "5.9.3",
|
||||
"wrangler": "^4.65.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { defineConfig, devices } from "@playwright/test"
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
testDir: "./e2e",
|
||||
fullyParallel: true,
|
||||
forbidOnly: !!process.env.CI,
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
reporter: process.env.CI ? [["github"], ["list"]] : "list",
|
||||
use: {
|
||||
baseURL: "http://127.0.0.1:3000",
|
||||
trace: "on-first-retry",
|
||||
headless: true,
|
||||
},
|
||||
|
||||
projects: [
|
||||
{
|
||||
name: "chromium",
|
||||
use: {
|
||||
...devices["Desktop Chrome"],
|
||||
headless: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
webServer: {
|
||||
command: "next build --webpack && next start",
|
||||
url: "http://127.0.0.1:3000",
|
||||
reuseExistingServer: !process.env.CI,
|
||||
timeout: 180000,
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,8 @@
|
||||
/** @type {import('postcss-load-config').Config} */
|
||||
const config = {
|
||||
plugins: {
|
||||
"@tailwindcss/postcss": {},
|
||||
},
|
||||
}
|
||||
|
||||
export default config
|
||||
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 743 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 888 KiB |
@@ -0,0 +1,7 @@
|
||||
import { rmSync } from "node:fs"
|
||||
|
||||
const buildCachePaths = [".next/cache/fetch-cache"]
|
||||
|
||||
for (const filePath of buildCachePaths) {
|
||||
rmSync(filePath, { force: true, recursive: true })
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noImplicitReturns": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts",
|
||||
".next/dev/types/**/*.ts"
|
||||
],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
main = ".open-next/worker.js"
|
||||
name = "oh-my-openagent-web"
|
||||
compatibility_date = "2025-03-25"
|
||||
compatibility_flags = ["nodejs_compat"]
|
||||
workers_dev = false
|
||||
preview_urls = false
|
||||
|
||||
[assets]
|
||||
directory = ".open-next/assets"
|
||||
binding = "ASSETS"
|
||||
|
||||
[[routes]]
|
||||
pattern = "ohmyopenagent.com"
|
||||
custom_domain = true
|
||||
|
||||
[[routes]]
|
||||
pattern = "ohmyopencode.org"
|
||||
custom_domain = true
|
||||