mirror of
https://github.com/codewhale-hq/Codewhale.git
synced 2026-11-08 08:04:25 -10:00
docs(web): rewrite codewhale.net/docs as task pages checked against the engine
Every docs page now answers what the reader can do, how, and where to go next, from one renderer (app/[locale]/docs/_components/doc-article.tsx) and one dictionary shape (DocsPageDict). Titles are tasks: Connect a provider, Set modes and approvals, Review what changed (new), Run a workflow, Run agents in parallel, Connect tools with MCP (with code mode), Run commands on events, Limit what commands can touch, See what leaves your machine, Change settings, Fix a problem, Open the browser client, Automate with the Runtime API, Send a task to the cloud, Track progress. zh is rewritten in step with en; commands are byte-identical across locales. Claims were re-verified against crates/ and docs/ and corrected: - Sub-agent roles use the current Fleet names (general/explore/implement/ test/advisor), not the retired worker/scout/builder set; sub-agents may nest (default 3, max 8) rather than being leaf-only. - Project hooks need /hooks review + /hooks approve <digest>, not just trust; 15 events documented. - MCP config reloads with /mcp reload (no restart); code mode is experimental, off by default, and cannot call MCP tools yet. - Turn receipts are designed, not built; only review receipts are (--write-receipt / --check-receipt). - Cloud dispatch is labelled preview (live path not yet smoke-tested, no private repos) and no longer tells users to bring a sandbox-vendor key. - getting-started: `codewhale fleet status` counts runs, it does not show the saved Fleet. - Ask applies workspace edits without asking; the page now says so. Registries: docs-map topics retitled and regrouped (Get started / Work with Codewhale / Extend and automate / Get help / Reference), new review topic, docs-tasks rewritten (review, receipts, code mode tasks); sitemap lists /docs/review. Tests pinning old copy were rewritten to invariants: en/zh section and command parity, every internal docs link resolves to a page. Verification (local, node 22): tsc --noEmit clean; eslint on touched files clean; check-locales PASS; check-docs PASS; web vitest 482 passed / 12 failed — the 12 are CSS/design contracts from the in-flight site renovation (design-grammar, docs-theme, gpui-role-tokens, typography, blue-stage, reduced-motion) plus gt-catalog needing `npm run i18n:gt -- export` (a generator, not run here). Full `npm test && npm run check:web` not run: the shared checkout carries the site agent's uncommitted work. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01R9rJEoMuUSRWznU6QjkE7h
This commit is contained in:
1 parent
3f0b3ab04d
commit
9c3dcee1d1
63 files changed
+4270
-2584
No files matched your search
@@ -0,0 +1,182 @@
|
||||
import Link from "next/link";
|
||||
import { Fragment, type ReactNode } from "react";
|
||||
import { getDocsShell } from "@/lib/i18n/dictionaries";
|
||||
import type { DocsBlock, DocsPageDict } from "@/lib/i18n/dictionaries/types";
|
||||
|
||||
/**
|
||||
* <DocArticle> — the one renderer for every task page under /docs.
|
||||
*
|
||||
* A page answers three questions in order: what you can do here and why
|
||||
* (title + lede), how (sections of prose, commands, tables and steps), and
|
||||
* what to do next (closing links). All copy comes from a `DocsPageDict`; the
|
||||
* page file only picks the dictionary and supplies metadata.
|
||||
*
|
||||
* Inline syntax inside prose strings is deliberately tiny:
|
||||
* `code` → <code class="inline">
|
||||
* [label](/path) → a link; a leading "/" is made locale-relative
|
||||
* Nothing else is interpreted, so dictionary text cannot inject markup.
|
||||
*/
|
||||
|
||||
/** Links are underlined so they never rely on color alone (WCAG 1.4.1). */
|
||||
const LINK = "docs-inline-link underline underline-offset-2 hover:text-indigo";
|
||||
|
||||
const INLINE = /(`[^`]+`|\[[^\]]+\]\([^)\s]+\))/g;
|
||||
|
||||
export function renderInline(text: string, locale: string): ReactNode {
|
||||
return text.split(INLINE).map((part, i) => {
|
||||
if (part.startsWith("`") && part.endsWith("`") && part.length > 1) {
|
||||
return (
|
||||
<code key={i} className="inline">
|
||||
{part.slice(1, -1)}
|
||||
</code>
|
||||
);
|
||||
}
|
||||
const link = part.match(/^\[([^\]]+)\]\(([^)\s]+)\)$/);
|
||||
if (link) {
|
||||
const [, label, href] = link;
|
||||
if (href.startsWith("/")) {
|
||||
return (
|
||||
<Link key={i} href={`/${locale}${href}`} className={LINK}>
|
||||
{label}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<a key={i} href={href} className={LINK} target="_blank" rel="noreferrer">
|
||||
{label}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
return <Fragment key={i}>{part}</Fragment>;
|
||||
});
|
||||
}
|
||||
|
||||
function Block({
|
||||
block,
|
||||
locale,
|
||||
body,
|
||||
noteLabel,
|
||||
}: {
|
||||
block: DocsBlock;
|
||||
locale: string;
|
||||
body: string;
|
||||
noteLabel: string;
|
||||
}) {
|
||||
if ("p" in block) {
|
||||
return <p className={`${body} mt-3`}>{renderInline(block.p, locale)}</p>;
|
||||
}
|
||||
if ("code" in block) {
|
||||
return (
|
||||
<pre className="code-block mt-4" aria-label={block.lang}>
|
||||
<code>{block.code}</code>
|
||||
</pre>
|
||||
);
|
||||
}
|
||||
if ("rows" in block) {
|
||||
return (
|
||||
<dl className="docs-ref-rows mt-4">
|
||||
{block.rows.map(([term, detail]) => (
|
||||
<div key={term}>
|
||||
<dt>{block.codeTerms ? <code className="inline">{term}</code> : renderInline(term, locale)}</dt>
|
||||
<dd>{renderInline(detail, locale)}</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
);
|
||||
}
|
||||
if ("steps" in block) {
|
||||
return (
|
||||
<ol className={`${body} docs-steps mt-3 list-decimal space-y-2 pl-6`}>
|
||||
{block.steps.map((step) => (
|
||||
<li key={step}>{renderInline(step, locale)}</li>
|
||||
))}
|
||||
</ol>
|
||||
);
|
||||
}
|
||||
if ("list" in block) {
|
||||
return (
|
||||
<ul className={`${body} docs-list mt-3 list-disc space-y-2 pl-6`}>
|
||||
{block.list.map((item) => (
|
||||
<li key={item}>{renderInline(item, locale)}</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<p className={`${body} docs-note hairline-l mt-4 pl-4`}>
|
||||
<strong>{noteLabel}</strong>{" "}
|
||||
{renderInline(block.note, locale)}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
export function DocArticle({
|
||||
t,
|
||||
locale,
|
||||
extra,
|
||||
}: {
|
||||
t: DocsPageDict;
|
||||
locale: string;
|
||||
/** Page-owned content rendered at the end of the section with this id. */
|
||||
extra?: Record<string, ReactNode>;
|
||||
}) {
|
||||
const shell = getDocsShell(locale);
|
||||
return (
|
||||
<div className="docs-article space-y-10">
|
||||
<header id="overview" className="scroll-mt-32">
|
||||
<h1 className="font-display text-3xl mb-1">{t.title}</h1>
|
||||
<p className={`${t.bodyClassName} docs-lede mt-3`}>{renderInline(t.lede, locale)}</p>
|
||||
{t.sections.length > 2 && (
|
||||
<nav className="docs-toc mt-5" aria-label={shell.onThisPage}>
|
||||
<ol className="flex flex-wrap gap-x-5 gap-y-1 text-sm">
|
||||
{t.sections.map((section) => (
|
||||
<li key={section.id}>
|
||||
<a href={`#${section.id}`} className={LINK}>
|
||||
{section.title}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</nav>
|
||||
)}
|
||||
</header>
|
||||
|
||||
{t.sections.map((section) => (
|
||||
<section key={section.id} id={section.id} className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{section.title}</h2>
|
||||
{section.blocks.map((block, i) => (
|
||||
<Block
|
||||
key={i}
|
||||
block={block}
|
||||
locale={locale}
|
||||
body={t.bodyClassName}
|
||||
noteLabel={shell.noteLabel}
|
||||
/>
|
||||
))}
|
||||
{extra?.[section.id]}
|
||||
</section>
|
||||
))}
|
||||
|
||||
{t.next.length > 0 && (
|
||||
<section id="next" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{shell.nextHeading}</h2>
|
||||
<div className="hairline-t mt-4">
|
||||
{t.next.map((item) => (
|
||||
<div key={item.href} className="py-4 hairline-b">
|
||||
<h3 className="font-display text-xl">
|
||||
<Link href={`/${locale}${item.href}`} className={LINK}>
|
||||
{item.label}
|
||||
</Link>
|
||||
</h3>
|
||||
<p className={`${t.bodyClassName} mt-1 text-sm`}>{renderInline(item.note, locale)}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Maintainer pointer: kept out of the rendered copy. */}
|
||||
<div hidden data-source-note={t.sourceNote} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,20 +1,7 @@
|
||||
import { RefRows, withCodeSpans } from "@/components/code-spans";
|
||||
import { DocArticle } from "../_components/doc-article";
|
||||
import { getDocsAuth } from "@/lib/i18n/dictionaries";
|
||||
import { APP_LOGIN_URL, APP_SIGNUP_URL } from "@/lib/i18n/links";
|
||||
import { buildPageMetadata } from "@/lib/page-meta";
|
||||
|
||||
/** Code-owned literals from docs/CONFIGURATION.md. Not copy. */
|
||||
const SPANS: Record<string, string> = {
|
||||
authSet: "codewhale auth set --provider <provider>",
|
||||
apiKeyFlag: "--api-key",
|
||||
login: "codewhale login",
|
||||
accountLogin: "codewhale account login",
|
||||
profile: "--profile",
|
||||
cloud: "codewhale cloud …",
|
||||
keys: "codewhale account keys list|set|remove",
|
||||
portable: "codewhale config export --portable",
|
||||
};
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
const t = getDocsAuth(locale);
|
||||
@@ -28,59 +15,5 @@ export async function generateMetadata({ params }: { params: Promise<{ locale: s
|
||||
|
||||
export default async function AuthPage({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
const t = getDocsAuth(locale);
|
||||
|
||||
return (
|
||||
<section className="space-y-10">
|
||||
<section id="overview" className="scroll-mt-32">
|
||||
<h1 className="font-display text-3xl mb-1">{t.overviewTitle}</h1>
|
||||
<p className={`${t.bodyClassName} mt-3 mb-4`}>{t.overviewLead}</p>
|
||||
<RefRows rows={t.credentials} spans={SPANS} />
|
||||
</section>
|
||||
|
||||
<section id="provider-keys" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.providerTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{withCodeSpans(t.providerLead, SPANS)}</p>
|
||||
<pre className="code-block mt-4">{`codewhale auth set --provider deepseek
|
||||
codewhale --model deepseek-v4-flash`}</pre>
|
||||
</section>
|
||||
|
||||
<section id="account" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.accountTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3 mb-4`}>{withCodeSpans(t.accountLead, SPANS)}</p>
|
||||
<RefRows rows={t.accountCommands} termSpans />
|
||||
</section>
|
||||
|
||||
<section id="storage" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.storageTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{withCodeSpans(t.storageLead, SPANS)}</p>
|
||||
</section>
|
||||
|
||||
<section id="vault" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.vaultTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{withCodeSpans(t.vaultLead, SPANS)}</p>
|
||||
</section>
|
||||
|
||||
<section id="portable" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.portableTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{withCodeSpans(t.portableLead, SPANS)}</p>
|
||||
</section>
|
||||
|
||||
<section id="web" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.appTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{t.appLead}</p>
|
||||
<div className="portal-actions">
|
||||
<a className="portal-button portal-button-primary" href={APP_LOGIN_URL}>
|
||||
{t.appSignIn}
|
||||
</a>
|
||||
<a className="portal-button portal-button-secondary" href={APP_SIGNUP_URL}>
|
||||
{t.appRegister}
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Maintainer pointer: kept out of the rendered copy (experience mark 5). */}
|
||||
<div hidden data-source-note={t.sourceNote} />
|
||||
</section>
|
||||
);
|
||||
return <DocArticle t={getDocsAuth(locale)} locale={locale} />;
|
||||
}
|
||||
@@ -1,24 +1,7 @@
|
||||
import { RefRows, withCodeSpans } from "@/components/code-spans";
|
||||
import { DocArticle } from "../_components/doc-article";
|
||||
import { getDocsComputers } from "@/lib/i18n/dictionaries";
|
||||
import { buildPageMetadata } from "@/lib/page-meta";
|
||||
|
||||
/** Code-owned literals from docs/DAYTONA_CLOUD_DISPATCH.md. Not copy. */
|
||||
const SPANS: Record<string, string> = {
|
||||
cloudAgent: "codewhale cloud-agent",
|
||||
dispatch: "codewhale dispatch",
|
||||
kind: "kind=cloud",
|
||||
remoteFlag: "--remote",
|
||||
apiKey: "export DAYTONA_API_KEY=…",
|
||||
apiUrl: "DAYTONA_API_URL",
|
||||
slot: "daytona",
|
||||
alias: "CWC_DAYTONA_TOKEN",
|
||||
status: "codewhale dispatch --status",
|
||||
bare: "/dispatch",
|
||||
proposed: "proposed",
|
||||
refused: "refused",
|
||||
login: "codewhale login",
|
||||
};
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
const t = getDocsComputers(locale);
|
||||
@@ -32,62 +15,5 @@ export async function generateMetadata({ params }: { params: Promise<{ locale: s
|
||||
|
||||
export default async function ComputersPage({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
const t = getDocsComputers(locale);
|
||||
|
||||
return (
|
||||
<section className="space-y-10">
|
||||
<section id="overview" className="scroll-mt-32">
|
||||
<h1 className="font-display text-3xl mb-1">{t.overviewTitle}</h1>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{t.overviewLead}</p>
|
||||
</section>
|
||||
|
||||
<section id="propose" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.proposeTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{withCodeSpans(t.proposeLead, SPANS)}</p>
|
||||
<pre className="code-block mt-4">{`codewhale dispatch "open a PR that fixes the flake" --remote github
|
||||
codewhale dispatch --confirm cloud_<id>
|
||||
|
||||
# the same two steps inside the TUI
|
||||
/dispatch open a PR that fixes the flake --remote github
|
||||
/dispatch confirm cloud_<id>`}</pre>
|
||||
<p className={`${t.bodyClassName} mt-4`}>{withCodeSpans(t.jobsLead, SPANS)}</p>
|
||||
<pre className="code-block mt-4">{`/jobs list
|
||||
/dispatch list
|
||||
/dispatch show <id>
|
||||
/dispatch cancel <id>
|
||||
codewhale dispatch --list`}</pre>
|
||||
</section>
|
||||
|
||||
<section id="remotes" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.remotesTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3 mb-4`}>{withCodeSpans(t.remotesLead, SPANS)}</p>
|
||||
<RefRows rows={t.remotes} spans={SPANS} />
|
||||
</section>
|
||||
|
||||
<section id="enable" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.enableTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3 mb-4`}>{t.enableLead}</p>
|
||||
<RefRows rows={t.enableSteps} spans={SPANS} />
|
||||
<p className={`${t.bodyClassName} mt-4 text-sm`}>{withCodeSpans(t.cliNote, SPANS)}</p>
|
||||
</section>
|
||||
|
||||
<section id="rules" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-4">{t.rulesTitle}</h2>
|
||||
<RefRows rows={t.rules} spans={SPANS} />
|
||||
</section>
|
||||
|
||||
<section id="membership" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.membershipTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{withCodeSpans(t.membershipLead, SPANS)}</p>
|
||||
</section>
|
||||
|
||||
<section id="leftover" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-4">{t.leftoverTitle}</h2>
|
||||
<RefRows rows={t.leftover} spans={SPANS} />
|
||||
</section>
|
||||
|
||||
{/* Maintainer pointer: kept out of the rendered copy (experience mark 5). */}
|
||||
<div hidden data-source-note={t.sourceNote} />
|
||||
</section>
|
||||
);
|
||||
return <DocArticle t={getDocsComputers(locale)} locale={locale} />;
|
||||
}
|
||||
@@ -1,27 +1,7 @@
|
||||
import { Fragment } from "react";
|
||||
import { getDocsConfiguration, splitTokens } from "@/lib/i18n/dictionaries";
|
||||
import { DocArticle } from "../_components/doc-article";
|
||||
import { getDocsConfiguration } from "@/lib/i18n/dictionaries";
|
||||
import { buildPageMetadata } from "@/lib/page-meta";
|
||||
|
||||
const CODE_SPANS: Record<string, string> = {
|
||||
auditCommand: "/config audit",
|
||||
apiKey: "--api-key",
|
||||
authStatus: "codewhale auth status",
|
||||
providerConfig: 'provider = "<id>"',
|
||||
providerFlag: "codewhale --provider <id>",
|
||||
};
|
||||
|
||||
function RichCopy({ text }: { text: string }) {
|
||||
return splitTokens(text).map((part, i) =>
|
||||
"token" in part ? (
|
||||
<code key={`${i}-${part.token}`} className="inline">
|
||||
{CODE_SPANS[part.token] ?? `{${part.token}}`}
|
||||
</code>
|
||||
) : (
|
||||
<Fragment key={`${i}-text`}>{part.text}</Fragment>
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
const t = getDocsConfiguration(locale);
|
||||
@@ -33,46 +13,7 @@ export async function generateMetadata({ params }: { params: Promise<{ locale: s
|
||||
});
|
||||
}
|
||||
|
||||
export default async function ConfigurationPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ locale: string }>;
|
||||
}) {
|
||||
export default async function ConfigurationPage({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
const t = getDocsConfiguration(locale);
|
||||
|
||||
return (
|
||||
<section className="space-y-10">
|
||||
<section id="overview" className="scroll-mt-32">
|
||||
<h1 className="font-display text-3xl mb-1">{t.overviewTitle}</h1>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{t.overviewLead}</p>
|
||||
<pre className="code-block mt-4">{`codewhale --config /path/to/config.toml
|
||||
CODEWHALE_CONFIG_PATH=/path/to/config.toml`}</pre>
|
||||
<p className={`${t.bodyClassName} mt-3`}>
|
||||
<RichCopy text={t.auditLead} />
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section id="project-overlay" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.overlayTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{t.overlayLead}</p>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{t.overlayLimits}</p>
|
||||
</section>
|
||||
|
||||
<section id="credentials" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.credentialsTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3`}>
|
||||
<RichCopy text={t.credentialsLead} />
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section id="legacy-paths" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.legacyTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{t.legacyLead}</p>
|
||||
</section>
|
||||
|
||||
{/* Maintainer pointer: kept out of the rendered copy (experience mark 5). */}
|
||||
<div hidden data-source-note={t.sourceNote} />
|
||||
</section>
|
||||
);
|
||||
return <DocArticle t={getDocsConfiguration(locale)} locale={locale} />;
|
||||
}
|
||||
@@ -1,16 +1,7 @@
|
||||
import { Fragment } from "react";
|
||||
import { PRODUCT_TERMS } from "@/lib/content/vocabulary";
|
||||
import { getDocsFleet, pickText, splitTokens } from "@/lib/i18n/dictionaries";
|
||||
import { DocArticle } from "../_components/doc-article";
|
||||
import { getDocsFleet } from "@/lib/i18n/dictionaries";
|
||||
import { buildPageMetadata } from "@/lib/page-meta";
|
||||
|
||||
const CODE_SPANS: Record<string, string> = {
|
||||
fleetSaved: "/fleet saved",
|
||||
fleetStatusTui: "/fleet status",
|
||||
fleetWorkers: "/fleet workers",
|
||||
subagents: "/subagents",
|
||||
fleetStatusShell: "codewhale fleet status",
|
||||
};
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
const t = getDocsFleet(locale);
|
||||
@@ -22,75 +13,8 @@ export async function generateMetadata({ params }: { params: Promise<{ locale: s
|
||||
});
|
||||
}
|
||||
|
||||
/** "Run a workflow": Fleet roles, Workflow files, Lanes, and Fleet runs. */
|
||||
export default async function FleetPage({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
const t = getDocsFleet(locale);
|
||||
const vocabulary = PRODUCT_TERMS.map((row) => ({
|
||||
term: row.term,
|
||||
definition: pickText(row.long, locale),
|
||||
}));
|
||||
|
||||
return (
|
||||
<section className="space-y-10">
|
||||
<section id="overview" className="scroll-mt-32">
|
||||
<h1 className="font-display text-3xl mb-1">{t.overviewTitle}</h1>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{t.overviewLead}</p>
|
||||
<div className="hairline-t mt-6">
|
||||
{vocabulary.map((row) => (
|
||||
<section key={row.term} className="py-4 hairline-b">
|
||||
<h2 className="font-display text-xl">{row.term}</h2>
|
||||
<p className={`${t.bodyClassName} mt-1 text-sm`}>{row.definition}</p>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="cli" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.runTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{t.runLead}</p>
|
||||
<pre className="code-block mt-4">{`codewhale fleet run tasks.json --max-workers 4
|
||||
codewhale fleet status
|
||||
codewhale fleet inspect <worker-id>
|
||||
codewhale fleet logs <worker-id>
|
||||
codewhale fleet interrupt <worker-id>
|
||||
codewhale fleet resume <run-id>
|
||||
codewhale fleet stop --all`}</pre>
|
||||
<p className={`${t.bodyClassName} mt-3`}>
|
||||
{splitTokens(t.statusLead).map((part, i) =>
|
||||
"token" in part ? (
|
||||
<code key={`${i}-${part.token}`} className="inline">
|
||||
{CODE_SPANS[part.token] ?? `{${part.token}}`}
|
||||
</code>
|
||||
) : (
|
||||
<Fragment key={`${i}-text`}>{part.text}</Fragment>
|
||||
),
|
||||
)}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section id="profiles" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.profilesTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3`}>
|
||||
{splitTokens(t.profilesLead).map((part, i) =>
|
||||
"token" in part ? (
|
||||
<code key={`${i}-${part.token}`} className="inline">
|
||||
{CODE_SPANS[part.token] ?? `{${part.token}}`}
|
||||
</code>
|
||||
) : (
|
||||
<Fragment key={`${i}-text`}>{part.text}</Fragment>
|
||||
),
|
||||
)}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section id="workflow" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.workflowTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{t.workflowLead}</p>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{t.workflowLimits}</p>
|
||||
</section>
|
||||
|
||||
{/* Maintainer pointer: kept out of the rendered copy (experience mark 5). */}
|
||||
<div hidden data-source-note={t.sourceNote} />
|
||||
</section>
|
||||
);
|
||||
return <DocArticle t={getDocsFleet(locale)} locale={locale} />;
|
||||
}
|
||||
@@ -1,18 +1,7 @@
|
||||
import { Fragment } from "react";
|
||||
import { getDocsHooks, splitTokens } from "@/lib/i18n/dictionaries";
|
||||
import { DocArticle } from "../_components/doc-article";
|
||||
import { getDocsHooks } from "@/lib/i18n/dictionaries";
|
||||
import { buildPageMetadata } from "@/lib/page-meta";
|
||||
|
||||
/**
|
||||
* Config syntax the overview sentence typesets as inline `<code>`. These are
|
||||
* literals, not copy, so they stay here rather than in the dictionaries —
|
||||
* `configIntro` carries a `{token}` for each one.
|
||||
*/
|
||||
const CODE_SPANS: Record<string, string> = {
|
||||
hooksTable: "[[hooks.hooks]]",
|
||||
hooksCommand: "/hooks",
|
||||
enabledKey: "[hooks].enabled",
|
||||
};
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
const t = getDocsHooks(locale);
|
||||
@@ -26,41 +15,5 @@ export async function generateMetadata({ params }: { params: Promise<{ locale: s
|
||||
|
||||
export default async function HooksPage({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
const t = getDocsHooks(locale);
|
||||
|
||||
return (
|
||||
<section className="space-y-10">
|
||||
<section id="overview" className="scroll-mt-32">
|
||||
<h1 className="font-display text-3xl mb-1">{t.overviewTitle}</h1>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{t.overviewLead}</p>
|
||||
<p className={`${t.bodyClassName} mt-3`}>
|
||||
{splitTokens(t.configIntro).map((part, i) =>
|
||||
"token" in part ? (
|
||||
<code key={`${i}-${part.token}`} className="inline">
|
||||
{CODE_SPANS[part.token] ?? `{${part.token}}`}
|
||||
</code>
|
||||
) : (
|
||||
<Fragment key={`${i}-text`}>{part.text}</Fragment>
|
||||
),
|
||||
)}
|
||||
</p>
|
||||
<div className="hairline-t mt-6">
|
||||
{t.events.map(([name, detail]) => (
|
||||
<section key={name} className="py-4 hairline-b">
|
||||
<h3 className="font-display text-lg">{name}</h3>
|
||||
<p className={`${t.bodyClassName} mt-1 text-sm`}>{detail}</p>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="project" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.projectTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{t.projectLead}</p>
|
||||
</section>
|
||||
|
||||
{/* Maintainer pointer: kept out of the rendered copy (experience mark 5). */}
|
||||
<div hidden data-source-note={t.sourceNote} />
|
||||
</section>
|
||||
);
|
||||
return <DocArticle t={getDocsHooks(locale)} locale={locale} />;
|
||||
}
|
||||
@@ -1,37 +1,7 @@
|
||||
import { Fragment } from "react";
|
||||
import { getDocsMcp, splitTokens } from "@/lib/i18n/dictionaries";
|
||||
import { DocArticle } from "../_components/doc-article";
|
||||
import { getDocsMcp } from "@/lib/i18n/dictionaries";
|
||||
import { buildPageMetadata } from "@/lib/page-meta";
|
||||
|
||||
const CODE_SPANS: Record<string, string> = {
|
||||
configPath: "~/.codewhale/mcp.json",
|
||||
legacyConfigPath: "~/.deepseek/mcp.json",
|
||||
configPathOption: "mcp_config_path",
|
||||
configEnvVar: "DEEPSEEK_MCP_CONFIG",
|
||||
serversKey: "mcpServers",
|
||||
initCommand: "codewhale mcp init",
|
||||
mcpCommand: "/mcp",
|
||||
toolNamePattern: "mcp_<server>_<tool>",
|
||||
gitServer: "git",
|
||||
statusTool: "status",
|
||||
gitStatusTool: "mcp_git_status",
|
||||
serveMcp: "codewhale serve --mcp",
|
||||
mcpServerCommand: "codewhale mcp-server",
|
||||
addSelfCommand: "codewhale mcp add-self",
|
||||
serveHttp: "serve --http",
|
||||
};
|
||||
|
||||
function withCodeSpans(template: string) {
|
||||
return splitTokens(template).map((part, i) =>
|
||||
"token" in part ? (
|
||||
<code key={`${i}-${part.token}`} className="inline">
|
||||
{CODE_SPANS[part.token] ?? `{${part.token}}`}
|
||||
</code>
|
||||
) : (
|
||||
<Fragment key={`${i}-text`}>{part.text}</Fragment>
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
const t = getDocsMcp(locale);
|
||||
@@ -45,45 +15,5 @@ export async function generateMetadata({ params }: { params: Promise<{ locale: s
|
||||
|
||||
export default async function McpPage({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
const t = getDocsMcp(locale);
|
||||
|
||||
return (
|
||||
<section className="space-y-10">
|
||||
<section id="overview" className="scroll-mt-32">
|
||||
<h1 className="font-display text-3xl mb-1">MCP</h1>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{t.overviewLead}</p>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{withCodeSpans(t.overviewConfig)}</p>
|
||||
</section>
|
||||
|
||||
<section id="setup" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.setupTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{withCodeSpans(t.setupLead)}</p>
|
||||
<pre className="code-block mt-4">{`codewhale mcp add <name> --command "<cmd>" --arg "<arg>"
|
||||
codewhale mcp add <name> --url "https://example.com/mcp" --bearer-token-env-var MCP_TOKEN
|
||||
codewhale mcp login <name> # OAuth for remote servers
|
||||
codewhale mcp list
|
||||
codewhale mcp validate`}</pre>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{t.setupReload}</p>
|
||||
</section>
|
||||
|
||||
<section id="auth" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.authTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{t.authLead}</p>
|
||||
</section>
|
||||
|
||||
<section id="tools" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.toolsTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{withCodeSpans(t.toolsLead)}</p>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{t.toolsTrust}</p>
|
||||
</section>
|
||||
|
||||
<section id="server" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.serverTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{withCodeSpans(t.serverLead)}</p>
|
||||
</section>
|
||||
|
||||
{/* Maintainer pointer: kept out of the rendered copy (experience mark 5). */}
|
||||
<div hidden data-source-note={t.sourceNote} />
|
||||
</section>
|
||||
);
|
||||
return <DocArticle t={getDocsMcp(locale)} locale={locale} />;
|
||||
}
|
||||
@@ -1,30 +1,7 @@
|
||||
import { Fragment } from "react";
|
||||
import { getDocsModes, splitTokens } from "@/lib/i18n/dictionaries";
|
||||
import { DocArticle } from "../_components/doc-article";
|
||||
import { getDocsModes } from "@/lib/i18n/dictionaries";
|
||||
import { buildPageMetadata } from "@/lib/page-meta";
|
||||
|
||||
function renderModeToken(token: string) {
|
||||
if (token === "tab") {
|
||||
return <kbd className="font-mono text-xs px-1.5 py-0.5 hairline">Tab</kbd>;
|
||||
}
|
||||
if (token === "shiftTab") {
|
||||
return <kbd className="font-mono text-xs px-1.5 py-0.5 hairline">Shift+Tab</kbd>;
|
||||
}
|
||||
if (token === "configCommand") {
|
||||
return <code className="inline">/config</code>;
|
||||
}
|
||||
return `{${token}}`;
|
||||
}
|
||||
|
||||
function renderRichText(text: string) {
|
||||
return splitTokens(text).map((part, i) =>
|
||||
"token" in part ? (
|
||||
<Fragment key={`${i}-${part.token}`}>{renderModeToken(part.token)}</Fragment>
|
||||
) : (
|
||||
<Fragment key={`${i}-text`}>{part.text}</Fragment>
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
const t = getDocsModes(locale);
|
||||
@@ -38,47 +15,5 @@ export async function generateMetadata({ params }: { params: Promise<{ locale: s
|
||||
|
||||
export default async function ModesPage({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
const t = getDocsModes(locale);
|
||||
|
||||
return (
|
||||
<section className="space-y-10">
|
||||
<section id="overview" className="scroll-mt-32">
|
||||
<h1 className="font-display text-3xl mb-1">{t.overviewTitle}</h1>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{t.overviewLead}</p>
|
||||
<div className="hairline-t mt-6">
|
||||
{t.modes.map(([name, description]) => (
|
||||
<section key={name} className="py-4 hairline-b">
|
||||
<h3 className="font-display text-xl">{name}</h3>
|
||||
<p className={`${t.bodyClassName} mt-1 text-sm`}>{description}</p>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="switching" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.switchingTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{renderRichText(t.switchingLead)}</p>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{t.switchingCommandLead}</p>
|
||||
<pre className="code-block mt-4">{`/mode plan
|
||||
/mode work
|
||||
/mode operate`}</pre>
|
||||
</section>
|
||||
|
||||
<section id="permissions" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.permissionsTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{renderRichText(t.permissionsLead)}</p>
|
||||
<div className="hairline-t mt-6">
|
||||
{t.postures.map(([name, description]) => (
|
||||
<section key={name} className="py-4 hairline-b">
|
||||
<h3 className="font-display text-lg">{name}</h3>
|
||||
<p className={`${t.bodyClassName} mt-1 text-sm`}>{description}</p>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Maintainer pointer: kept out of the rendered copy (experience mark 5). */}
|
||||
<div hidden data-source-note={t.sourceNote} />
|
||||
</section>
|
||||
);
|
||||
return <DocArticle t={getDocsModes(locale)} locale={locale} />;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { DocArticle } from "../_components/doc-article";
|
||||
import { getDocsReview } from "@/lib/i18n/dictionaries";
|
||||
import { buildPageMetadata } from "@/lib/page-meta";
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
const t = getDocsReview(locale);
|
||||
return buildPageMetadata({
|
||||
path: "/docs/review",
|
||||
locale,
|
||||
title: t.metaTitle,
|
||||
description: t.metaDescription,
|
||||
});
|
||||
}
|
||||
|
||||
export default async function ReviewPage({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
return <DocArticle t={getDocsReview(locale)} locale={locale} />;
|
||||
}
|
||||
@@ -1,31 +1,7 @@
|
||||
import { Fragment } from "react";
|
||||
import { getDocsRuntimeApi, splitTokens } from "@/lib/i18n/dictionaries";
|
||||
import { DocArticle } from "../_components/doc-article";
|
||||
import { getDocsRuntimeApi } from "@/lib/i18n/dictionaries";
|
||||
import { buildPageMetadata } from "@/lib/page-meta";
|
||||
|
||||
/**
|
||||
* The command each entry row is headed by, and the flags and environment
|
||||
* variables the security paragraph typesets as inline `<code>`. `docs/VOICE.md`
|
||||
* keeps commands, key names and paths code-owned, so the dictionaries carry
|
||||
* keys and `{token}`s rather than the literals.
|
||||
*/
|
||||
const ENTRY_COMMANDS: Record<string, string> = {
|
||||
http: "codewhale app-server --http",
|
||||
mobile: "codewhale app-server --mobile",
|
||||
stdio: "codewhale app-server --stdio",
|
||||
web: "codewhale web [--port 7878]",
|
||||
doctor: "codewhale doctor --json",
|
||||
acp: "codewhale serve --acp",
|
||||
exec: "codewhale exec [args]",
|
||||
};
|
||||
|
||||
const CODE_SPANS: Record<string, string> = {
|
||||
authToken: "--auth-token",
|
||||
runtimeTokenEnv: "CODEWHALE_RUNTIME_TOKEN",
|
||||
legacyTokenEnv: "DEEPSEEK_RUNTIME_TOKEN",
|
||||
insecureFlag: "--insecure-no-auth",
|
||||
mobileFlag: "app-server --mobile",
|
||||
};
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
const t = getDocsRuntimeApi(locale);
|
||||
@@ -39,51 +15,5 @@ export async function generateMetadata({ params }: { params: Promise<{ locale: s
|
||||
|
||||
export default async function RuntimeApiPage({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
const t = getDocsRuntimeApi(locale);
|
||||
|
||||
return (
|
||||
<section className="space-y-10">
|
||||
<section id="overview" className="scroll-mt-32">
|
||||
<h1 className="font-display text-3xl mb-1">{t.overviewTitle}</h1>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{t.overviewLead}</p>
|
||||
<div className="hairline-t mt-6">
|
||||
{t.entries.map(([key, detail]) => (
|
||||
<section key={key} className="py-4 hairline-b">
|
||||
<h3 className="font-mono text-sm font-semibold">{ENTRY_COMMANDS[key] ?? key}</h3>
|
||||
<p className={`${t.bodyClassName} mt-1 text-sm`}>{detail}</p>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="stdio" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.stdioTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{t.stdioLead}</p>
|
||||
<pre className="code-block mt-4">{`printf '%s\n' \\
|
||||
'{"jsonrpc":"2.0","id":1,"method":"healthz"}' \\
|
||||
'{"jsonrpc":"2.0","id":2,"method":"capabilities"}' \\
|
||||
'{"jsonrpc":"2.0","id":3,"method":"shutdown"}' \\
|
||||
| codewhale app-server --stdio`}</pre>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{t.interruptNote}</p>
|
||||
</section>
|
||||
|
||||
<section id="security" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.securityTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3`}>
|
||||
{splitTokens(t.securityLead).map((part, i) =>
|
||||
"token" in part ? (
|
||||
<code key={`${i}-${part.token}`} className="inline">
|
||||
{CODE_SPANS[part.token] ?? `{${part.token}}`}
|
||||
</code>
|
||||
) : (
|
||||
<Fragment key={`${i}-text`}>{part.text}</Fragment>
|
||||
),
|
||||
)}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{/* Maintainer pointer: kept out of the rendered copy (experience mark 5). */}
|
||||
<div hidden data-source-note={t.sourceNote} />
|
||||
</section>
|
||||
);
|
||||
return <DocArticle t={getDocsRuntimeApi(locale)} locale={locale} />;
|
||||
}
|
||||
@@ -1,20 +1,7 @@
|
||||
import { Fragment } from "react";
|
||||
import { getDocsSandbox, splitTokens } from "@/lib/i18n/dictionaries";
|
||||
import { DocArticle } from "../_components/doc-article";
|
||||
import { getDocsSandbox } from "@/lib/i18n/dictionaries";
|
||||
import { buildPageMetadata } from "@/lib/page-meta";
|
||||
|
||||
/**
|
||||
* The config values the policies paragraph typesets as inline `<code>`.
|
||||
* `docs/VOICE.md` keeps key names code-owned, so the dictionaries carry
|
||||
* `{token}`s rather than the literals.
|
||||
*/
|
||||
const CODE_SPANS: Record<string, string> = {
|
||||
sandboxMode: "sandbox_mode",
|
||||
readOnly: "read-only",
|
||||
workspaceWrite: "workspace-write",
|
||||
dangerFullAccess: "danger-full-access",
|
||||
externalSandbox: "external-sandbox",
|
||||
};
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
const t = getDocsSandbox(locale);
|
||||
@@ -28,55 +15,5 @@ export async function generateMetadata({ params }: { params: Promise<{ locale: s
|
||||
|
||||
export default async function SandboxPage({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
const t = getDocsSandbox(locale);
|
||||
|
||||
return (
|
||||
<section className="space-y-10">
|
||||
<section id="overview" className="scroll-mt-32">
|
||||
<h1 className="font-display text-3xl mb-1">{t.overviewTitle}</h1>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{t.overviewLead}</p>
|
||||
<div className="hairline-t mt-6">
|
||||
{t.platforms.map(([name, detail]) => (
|
||||
<section key={name} className="py-4 hairline-b">
|
||||
<h3 className="font-display text-lg">{name}</h3>
|
||||
<p className={`${t.bodyClassName} mt-1 text-sm`}>{detail}</p>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="policies" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.policiesTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3`}>
|
||||
{splitTokens(t.policiesLead).map((part, i) =>
|
||||
"token" in part ? (
|
||||
<code key={`${i}-${part.token}`} className="inline">
|
||||
{CODE_SPANS[part.token] ?? `{${part.token}}`}
|
||||
</code>
|
||||
) : (
|
||||
<Fragment key={`${i}-text`}>{part.text}</Fragment>
|
||||
),
|
||||
)}
|
||||
</p>
|
||||
<pre className="code-block mt-4">{`# config.toml
|
||||
sandbox_mode = "workspace-write"
|
||||
prefer_bwrap = true # Linux opt-in
|
||||
|
||||
# Canonical environment overrides
|
||||
CODEWHALE_SANDBOX_MODE
|
||||
CODEWHALE_SANDBOX_BACKEND
|
||||
CODEWHALE_SANDBOX_URL
|
||||
CODEWHALE_SANDBOX_API_KEY`}</pre>
|
||||
</section>
|
||||
|
||||
<section id="diagnostics" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.diagnosticsTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{t.diagnosticsLead}</p>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{t.diagnosticsLimits}</p>
|
||||
</section>
|
||||
|
||||
{/* Maintainer pointer: kept out of the rendered copy (experience mark 5). */}
|
||||
<div hidden data-source-note={t.sourceNote} />
|
||||
</section>
|
||||
);
|
||||
return <DocArticle t={getDocsSandbox(locale)} locale={locale} />;
|
||||
}
|
||||
@@ -1,43 +1,7 @@
|
||||
import { Fragment } from "react";
|
||||
import { getDocsSubagents, splitTokens } from "@/lib/i18n/dictionaries";
|
||||
import { DocArticle } from "../_components/doc-article";
|
||||
import { getDocsSubagents } from "@/lib/i18n/dictionaries";
|
||||
import { buildPageMetadata } from "@/lib/page-meta";
|
||||
|
||||
/** Role identifiers, in the order the page lists them. Not copy. */
|
||||
const ROLE_ORDER = [
|
||||
"worker",
|
||||
"scout",
|
||||
"planner",
|
||||
"reviewer",
|
||||
"builder",
|
||||
"verifier",
|
||||
"consultant",
|
||||
"custom",
|
||||
] as const;
|
||||
|
||||
const CODE_SPANS: Record<string, string> = {
|
||||
agentTool: "agent",
|
||||
forkContext: "fork_context: true",
|
||||
worktreeFlag: "worktree: true",
|
||||
branchPattern: "codex/agent-<name>-<id>",
|
||||
worktreeDir: ".codewhale-worktrees/",
|
||||
writeAuthority: "write_authority",
|
||||
writeRoots: "write_roots",
|
||||
exactFiles: "exact_files",
|
||||
coordinationContracts: "coordination_contracts",
|
||||
};
|
||||
|
||||
function withCodeSpans(template: string) {
|
||||
return splitTokens(template).map((part, i) =>
|
||||
"token" in part ? (
|
||||
<code key={`${i}-${part.token}`} className="inline">
|
||||
{CODE_SPANS[part.token] ?? `{${part.token}}`}
|
||||
</code>
|
||||
) : (
|
||||
<Fragment key={`${i}-text`}>{part.text}</Fragment>
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
const t = getDocsSubagents(locale);
|
||||
@@ -51,42 +15,5 @@ export async function generateMetadata({ params }: { params: Promise<{ locale: s
|
||||
|
||||
export default async function SubagentsPage({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
const t = getDocsSubagents(locale);
|
||||
const roleDetails = new Map(t.roles);
|
||||
|
||||
return (
|
||||
<section className="space-y-10">
|
||||
<section id="overview" className="scroll-mt-32">
|
||||
<h1 className="font-display text-3xl mb-1">{t.overviewTitle}</h1>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{t.overviewLead}</p>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{t.overviewFleetNote}</p>
|
||||
<div className="hairline-t mt-6">
|
||||
{ROLE_ORDER.map((role) => (
|
||||
<section key={role} className="py-4 hairline-b">
|
||||
<h3 className="font-display text-lg">{role}</h3>
|
||||
<p className={`${t.bodyClassName} mt-1 text-sm`}>{roleDetails.get(role)}</p>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="fork" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.forkTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{withCodeSpans(t.forkLead)}</p>
|
||||
</section>
|
||||
|
||||
<section id="worktree" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.worktreeTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{withCodeSpans(t.worktreeLead)}</p>
|
||||
</section>
|
||||
|
||||
<section id="capacity" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.capacityTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{t.capacityLead}</p>
|
||||
</section>
|
||||
|
||||
{/* Maintainer pointer: kept out of the rendered copy (experience mark 5). */}
|
||||
<div hidden data-source-note={t.sourceNote} />
|
||||
</section>
|
||||
);
|
||||
return <DocArticle t={getDocsSubagents(locale)} locale={locale} />;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { DocArticle } from "../_components/doc-article";
|
||||
import { getDocsTroubleshooting } from "@/lib/i18n/dictionaries";
|
||||
import { buildPageMetadata } from "@/lib/page-meta";
|
||||
|
||||
@@ -12,45 +13,7 @@ export async function generateMetadata({ params }: { params: Promise<{ locale: s
|
||||
});
|
||||
}
|
||||
|
||||
export default async function TroubleshootingPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ locale: string }>;
|
||||
}) {
|
||||
export default async function TroubleshootingPage({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
const t = getDocsTroubleshooting(locale);
|
||||
|
||||
return (
|
||||
<section className="space-y-10">
|
||||
<section id="overview" className="scroll-mt-32">
|
||||
<h1 className="font-display text-3xl mb-1">{t.overviewTitle}</h1>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{t.overviewLead}</p>
|
||||
<div className="hairline-t mt-6">
|
||||
{t.incidents.map(([name, detail]) => (
|
||||
<section key={name} className="py-4 hairline-b">
|
||||
<h3 className="font-display text-lg">{name}</h3>
|
||||
<p className={`${t.bodyClassName} mt-1 text-sm`}>{detail}</p>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="docker" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.dockerTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{t.dockerLead}</p>
|
||||
<pre className="code-block mt-4">{`docker volume create codewhale-home
|
||||
|
||||
docker run --rm -it \\
|
||||
-e DEEPSEEK_API_KEY="your-api-key-here" \\
|
||||
-v codewhale-home:/home/codewhale/.codewhale \\
|
||||
-v "$PWD:/workspace" \\
|
||||
-w /workspace \\
|
||||
ghcr.io/hmbown/codewhale:latest`}</pre>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{t.dockerToolboxNote}</p>
|
||||
</section>
|
||||
|
||||
{/* Maintainer pointer: kept out of the rendered copy (experience mark 5). */}
|
||||
<div hidden data-source-note={t.sourceNote} />
|
||||
</section>
|
||||
);
|
||||
return <DocArticle t={getDocsTroubleshooting(locale)} locale={locale} />;
|
||||
}
|
||||
@@ -1,25 +1,7 @@
|
||||
import { RefRows, withCodeSpans } from "@/components/code-spans";
|
||||
import { getChrome, getDocsTrust } from "@/lib/i18n/dictionaries";
|
||||
import { DocArticle } from "../_components/doc-article";
|
||||
import { getDocsTrust } from "@/lib/i18n/dictionaries";
|
||||
import { buildPageMetadata, SITE_SECURITY_EMAIL } from "@/lib/page-meta";
|
||||
|
||||
/** Code-owned literals from docs/SANDBOX.md and docs/TELEMETRY.md. Not copy. */
|
||||
const SPANS: Record<string, string> = {
|
||||
seatbelt: "macos-seatbelt",
|
||||
preferBwrap: "prefer_bwrap = true",
|
||||
bwrap: "linux-bwrap",
|
||||
none: "none",
|
||||
opensandbox: 'sandbox_backend = "opensandbox"',
|
||||
endpoint: "https://telemetry.codewhale.net/v1/telemetry",
|
||||
dryRun: 'telemetry_endpoint = ""',
|
||||
dryRunFile: "$CODEWHALE_HOME/telemetry/dryrun.jsonl",
|
||||
configOff: "codewhale config set telemetry false",
|
||||
envOff: "CODEWHALE_TELEMETRY=0",
|
||||
auditLog: "$CODEWHALE_HOME/audit.log",
|
||||
};
|
||||
|
||||
/** The one security contact the spine publishes (lib/page-meta.ts). */
|
||||
const SECURITY_MAILTO = `mailto:${SITE_SECURITY_EMAIL}`;
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
const t = getDocsTrust(locale);
|
||||
@@ -33,56 +15,20 @@ export async function generateMetadata({ params }: { params: Promise<{ locale: s
|
||||
|
||||
export default async function TrustPage({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
const t = getDocsTrust(locale);
|
||||
const chrome = getChrome(locale);
|
||||
|
||||
return (
|
||||
<section className="space-y-10">
|
||||
<section id="overview" className="scroll-mt-32">
|
||||
<h1 className="font-display text-3xl mb-1">{t.overviewTitle}</h1>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{t.overviewLead}</p>
|
||||
</section>
|
||||
|
||||
<section id="boundary" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-4">{t.boundaryTitle}</h2>
|
||||
<RefRows rows={t.boundaries} />
|
||||
</section>
|
||||
|
||||
<section id="approvals" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.approvalTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{t.approvalLead}</p>
|
||||
</section>
|
||||
|
||||
<section id="sandbox" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.sandboxTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3 mb-4`}>{t.sandboxLead}</p>
|
||||
<RefRows rows={t.sandboxes} spans={SPANS} />
|
||||
<p className={`${t.bodyClassName} mt-4 text-sm`}>{t.sandboxNote}</p>
|
||||
</section>
|
||||
|
||||
<section id="telemetry" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.telemetryTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3 mb-4`}>{t.telemetryLead}</p>
|
||||
<RefRows rows={t.telemetry} spans={SPANS} />
|
||||
</section>
|
||||
|
||||
<section id="audit" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.auditTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{withCodeSpans(t.auditLead, SPANS)}</p>
|
||||
</section>
|
||||
|
||||
<section id="report" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.reportTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{t.reportLead}</p>
|
||||
<div className="portal-actions">
|
||||
<a className="portal-button portal-button-primary" href={SECURITY_MAILTO}>
|
||||
{t.reportCta} · {chrome.footerSecurity}
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Maintainer pointer: kept out of the rendered copy (experience mark 5). */}
|
||||
<div hidden data-source-note={t.sourceNote} />
|
||||
</section>
|
||||
<DocArticle
|
||||
t={getDocsTrust(locale)}
|
||||
locale={locale}
|
||||
extra={{
|
||||
// The contact is code-owned so every locale reports to the same inbox.
|
||||
report: (
|
||||
<div className="portal-actions">
|
||||
<a className="portal-button portal-button-primary" href={`mailto:${SITE_SECURITY_EMAIL}`}>
|
||||
{SITE_SECURITY_EMAIL}
|
||||
</a>
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,35 +1,7 @@
|
||||
import { Fragment } from "react";
|
||||
import { getDocsWeb, splitTokens } from "@/lib/i18n/dictionaries";
|
||||
import { DocArticle } from "../_components/doc-article";
|
||||
import { getDocsWeb } from "@/lib/i18n/dictionaries";
|
||||
import { buildPageMetadata } from "@/lib/page-meta";
|
||||
|
||||
/**
|
||||
* The commands, flags and addresses the overview and local paragraphs typeset
|
||||
* as inline `<code>`. `docs/VOICE.md` keeps commands, flags and paths
|
||||
* code-owned, so the dictionaries carry `{token}`s rather than the literals.
|
||||
*/
|
||||
const CODE_SPANS: Record<string, string> = {
|
||||
webCommand: "codewhale web",
|
||||
loopbackHost: "127.0.0.1",
|
||||
defaultUrl: "http://127.0.0.1:7878",
|
||||
portExample: "codewhale web --port 8788",
|
||||
portFlag: "--port",
|
||||
hostFlag: "--host",
|
||||
mobileCommand: "codewhale app-server --mobile",
|
||||
httpFlag: "--http",
|
||||
};
|
||||
|
||||
function withCodeSpans(template: string) {
|
||||
return splitTokens(template).map((part, i) =>
|
||||
"token" in part ? (
|
||||
<code key={`${i}-${part.token}`} className="inline">
|
||||
{CODE_SPANS[part.token] ?? `{${part.token}}`}
|
||||
</code>
|
||||
) : (
|
||||
<Fragment key={`${i}-text`}>{part.text}</Fragment>
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
const t = getDocsWeb(locale);
|
||||
@@ -43,39 +15,5 @@ export async function generateMetadata({ params }: { params: Promise<{ locale: s
|
||||
|
||||
export default async function WebClientPage({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
const t = getDocsWeb(locale);
|
||||
|
||||
return (
|
||||
<section className="space-y-10">
|
||||
<section id="overview" className="scroll-mt-32">
|
||||
<h1 className="font-display text-3xl mb-1">{t.overviewTitle}</h1>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{withCodeSpans(t.overviewLead)}</p>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{t.overviewBody}</p>
|
||||
</section>
|
||||
|
||||
<section id="auth" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.authTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{t.authLead}</p>
|
||||
</section>
|
||||
|
||||
<section id="local" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.localTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{withCodeSpans(t.localLead)}</p>
|
||||
</section>
|
||||
|
||||
<section id="remote-control" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.remoteTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{t.remoteLead}</p>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{t.remoteBody}</p>
|
||||
</section>
|
||||
|
||||
<section id="troubleshooting" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.troubleshootingTitle}</h2>
|
||||
<p className={`${t.bodyClassName} mt-3`}>{t.troubleshootingLead}</p>
|
||||
</section>
|
||||
|
||||
{/* Maintainer pointer: kept out of the rendered copy (experience mark 5). */}
|
||||
<div hidden data-source-note={t.sourceNote} />
|
||||
</section>
|
||||
);
|
||||
return <DocArticle t={getDocsWeb(locale)} locale={locale} />;
|
||||
}
|
||||
@@ -1,29 +1,7 @@
|
||||
import { Fragment } from "react";
|
||||
import { getDocsWork, splitTokens } from "@/lib/i18n/dictionaries";
|
||||
import { DocArticle } from "../_components/doc-article";
|
||||
import { getDocsWork } from "@/lib/i18n/dictionaries";
|
||||
import { buildPageMetadata } from "@/lib/page-meta";
|
||||
|
||||
const CODE_SPANS: Record<string, string> = {
|
||||
todoWrite: "todo_write",
|
||||
checklistAlias: "checklist_*",
|
||||
todoAlias: "todo_*",
|
||||
pending: "[ ]",
|
||||
inProgress: "[~]",
|
||||
completed: "[✓]",
|
||||
cancelled: "[-]",
|
||||
};
|
||||
|
||||
function withCodeSpans(template: string) {
|
||||
return splitTokens(template).map((part, i) =>
|
||||
"token" in part ? (
|
||||
<code key={`${i}-${part.token}`} className="inline">
|
||||
{CODE_SPANS[part.token] ?? `{${part.token}}`}
|
||||
</code>
|
||||
) : (
|
||||
<Fragment key={`${i}-text`}>{part.text}</Fragment>
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
const t = getDocsWork(locale);
|
||||
@@ -35,56 +13,7 @@ export async function generateMetadata({ params }: { params: Promise<{ locale: s
|
||||
});
|
||||
}
|
||||
|
||||
export default async function WorkSurfacePage({ params }: { params: Promise<{ locale: string }> }) {
|
||||
export default async function WorkPage({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params;
|
||||
const t = getDocsWork(locale);
|
||||
const bodyClass = t.bodyClassName;
|
||||
|
||||
return (
|
||||
<section className="space-y-10">
|
||||
<section id="overview" className="scroll-mt-32">
|
||||
<h1 className="font-display text-3xl mb-1">{t.overviewTitle}</h1>
|
||||
<p className={`${bodyClass} mt-3`}>{t.overviewLead}</p>
|
||||
</section>
|
||||
|
||||
<section id="checklist" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.checklistTitle}</h2>
|
||||
<p className={`${bodyClass} mt-3`}>{withCodeSpans(t.checklistBody)}</p>
|
||||
</section>
|
||||
|
||||
<section id="strategy" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.strategyTitle}</h2>
|
||||
<p className={`${bodyClass} mt-3`}>{t.strategyLead}</p>
|
||||
</section>
|
||||
|
||||
<section id="continuity" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.continuityTitle}</h2>
|
||||
<p className={`${bodyClass} mt-3`}>{t.continuityLead}</p>
|
||||
</section>
|
||||
|
||||
<section id="capture" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.captureTitle}</h2>
|
||||
<p className={`${bodyClass} mt-3`}>{t.captureLead}</p>
|
||||
<pre className="code-block mt-4">{`To-do
|
||||
◆ Goal: Land the v0.9.2 website docs cluster
|
||||
elapsed: 18m
|
||||
[█████████░░░░░░░░░░░] 45%
|
||||
50% settled (2/4)
|
||||
[✓] #1 Read docs-map.ts and the Modes page pattern
|
||||
[✓] #2 Draft the Fleet and Sandbox pages
|
||||
[~] #3 Write the Work surface page
|
||||
[ ] #4 Run check:docs, tests, and the build`}</pre>
|
||||
<p className={`${bodyClass} mt-3`}>{withCodeSpans(t.captureLegend)}</p>
|
||||
</section>
|
||||
|
||||
<section id="model-facing" className="scroll-mt-32">
|
||||
<h2 className="font-display text-2xl mb-1">{t.modelFacingTitle}</h2>
|
||||
<p className={`${bodyClass} mt-3`}>{t.modelFacingLead}</p>
|
||||
<p className={`${bodyClass} mt-3`}>{t.modelFacingBoundaries}</p>
|
||||
</section>
|
||||
|
||||
{/* Maintainer pointer: kept out of the rendered copy (experience mark 5). */}
|
||||
<div hidden data-source-note={t.sourceNote} />
|
||||
</section>
|
||||
);
|
||||
return <DocArticle t={getDocsWork(locale)} locale={locale} />;
|
||||
}
|
||||
+1
-1
@@ -4,7 +4,7 @@ import { SITE_URL } from "@/lib/page-meta";
|
||||
|
||||
// Public, indexable routes (locale-prefixed). /admin and /api are
|
||||
// intentionally excluded; see app/robots.ts.
|
||||
const PATHS = ["", "/product", "/install", "/constitution", "/models", "/plugins", "/runtime", "/docs", "/docs/auth", "/docs/computers", "/docs/configuration", "/docs/guide", "/docs/hooks", "/docs/mcp", "/docs/modes", "/docs/fleet", "/docs/runtime-api", "/docs/sandbox", "/docs/subagents", "/docs/tools", "/docs/troubleshooting", "/docs/trust", "/docs/vocabulary", "/docs/web", "/docs/work", "/faq", "/roadmap", "/feed", "/digest", "/changelog", "/contribute", "/community", "/signin", "/signup", "/legal/terms", "/legal/privacy"];
|
||||
const PATHS = ["", "/product", "/install", "/constitution", "/models", "/plugins", "/runtime", "/docs", "/docs/auth", "/docs/computers", "/docs/configuration", "/docs/guide", "/docs/hooks", "/docs/mcp", "/docs/modes", "/docs/review", "/docs/fleet", "/docs/runtime-api", "/docs/sandbox", "/docs/subagents", "/docs/tools", "/docs/troubleshooting", "/docs/trust", "/docs/vocabulary", "/docs/web", "/docs/work", "/faq", "/roadmap", "/feed", "/digest", "/changelog", "/contribute", "/community", "/signin", "/signup", "/legal/terms", "/legal/privacy"];
|
||||
|
||||
export default function sitemap(): MetadataRoute.Sitemap {
|
||||
return [...PATHS, "/computer-use"].flatMap((path) =>
|
||||
|
||||
@@ -47,39 +47,39 @@ export const GETTING_STARTED_STEPS: GuideStep[] = [
|
||||
id: "connect-provider",
|
||||
title: { en: "Connect your model", zh: "连接你的模型" },
|
||||
body: {
|
||||
en: "Model replies need a connection to a hosted or local model. Use your own provider key, as in the DeepSeek example below, or follow the provider guide for other services and local models. Offline setup does not require a key.",
|
||||
zh: "模型回复需要连接云端或本地模型。你可以使用自己的提供商密钥(下方以 DeepSeek 为例),或按提供商指南连接其他服务和本地模型。离线配置不需要密钥。",
|
||||
en: "Codewhale needs a model to answer. Save your own provider key, as in the DeepSeek example below, or run a local model such as Ollama, which needs no key. You pay the provider directly.",
|
||||
zh: "Codewhale 需要一个模型来回答问题。你可以像下方的 DeepSeek 示例那样保存自己的提供商密钥,也可以运行 Ollama 等本地模型,本地模型不需要密钥。费用由你直接付给提供商。",
|
||||
},
|
||||
commands: ["codewhale auth set --provider deepseek"],
|
||||
link: {
|
||||
href: "/models",
|
||||
label: { en: "Choose a provider", zh: "选择提供商" },
|
||||
href: "/docs/auth",
|
||||
label: { en: "Connect a provider", zh: "连接模型提供商" },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "first-session",
|
||||
title: { en: "Give it a task", zh: "交给它一项任务" },
|
||||
body: {
|
||||
en: "Open Codewhale in your project folder. Try /mode plan and ask it to explain the project, then use /mode work when you want edits and commands. Shift+Tab changes the approval setting; /provider and /model change the model connection.",
|
||||
zh: "在项目文件夹中打开 Codewhale。先用 /mode plan 让它解释项目;需要修改文件或运行命令时,再切换到 /mode work。Shift+Tab 切换审批设置,/provider 和 /model 用于更改模型连接。",
|
||||
en: "Open Codewhale in your project folder and ask for something concrete. Start in /mode plan to have it explain the project without changing anything, then switch to /mode work for edits and commands. It shows each edit as a diff and asks before running a shell command.",
|
||||
zh: "在项目文件夹中打开 Codewhale,交给它一件具体的事。可以先用 /mode plan 让它在不改动任何东西的前提下讲解项目,需要修改文件或运行命令时再切换到 /mode work。它会以 diff 展示每一处修改,并在运行 shell 命令前先征求你的同意。",
|
||||
},
|
||||
commands: ["codewhale"],
|
||||
link: {
|
||||
href: "/docs/modes",
|
||||
label: { en: "Modes and permissions", zh: "模式与权限" },
|
||||
label: { en: "Set modes and approvals", zh: "设置模式与审批" },
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "fleet-workflow",
|
||||
title: { en: "Add a Fleet when you need one", zh: "需要时配置 Fleet" },
|
||||
body: {
|
||||
en: "When a task would benefit from several models and roles, run /fleet setup inside Codewhale to put a team together. You can see the saved Fleet from your shell with codewhale fleet status.",
|
||||
zh: "当任务需要多个模型和角色配合时,可以在 Codewhale 中运行 /fleet setup 来配置团队,然后在 shell 中用 codewhale fleet status 查看已保存的 Fleet。",
|
||||
en: "When a task would benefit from several models and roles, run /fleet setup inside Codewhale to save roles and the model each one uses. From your shell, codewhale fleet status counts the Fleet runs that are queued, running, or finished.",
|
||||
zh: "当任务需要多个模型和角色配合时,可以在 Codewhale 中运行 /fleet setup,保存角色以及每个角色使用的模型。在 shell 中,codewhale fleet status 会统计排队中、运行中和已结束的 Fleet 运行。",
|
||||
},
|
||||
commands: ["/fleet setup", "codewhale fleet status"],
|
||||
link: {
|
||||
href: "/docs/fleet",
|
||||
label: { en: "Fleet and Workflow docs", zh: "Fleet 与 Workflow 文档" },
|
||||
label: { en: "Run a workflow", zh: "运行 Workflow" },
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -91,27 +91,27 @@ export const GETTING_STARTED_STEPS: GuideStep[] = [
|
||||
*/
|
||||
export const GUIDE_NEXT_LINKS: { href: string; label: LocalizedText; note: LocalizedText }[] = [
|
||||
{
|
||||
href: "/docs/hooks",
|
||||
label: { en: "Hooks", zh: "钩子" },
|
||||
href: "/docs/review",
|
||||
label: { en: "Review what changed", zh: "查看改动" },
|
||||
note: {
|
||||
en: "Run your own commands before and after tool calls, at turn end, and on session events, with per-project trust rules.",
|
||||
zh: "借助项目级信任规则,响应生命周期事件——工具调用前后、回合结束、会话事件。",
|
||||
en: "See every edit in a session, roll files back to an earlier turn, and get a code review before you push.",
|
||||
zh: "查看一次会话中的每处修改,把文件回滚到之前的回合,并在推送前做一次代码审查。",
|
||||
},
|
||||
},
|
||||
{
|
||||
href: "/docs/modes",
|
||||
label: { en: "Modes and permissions", zh: "模式与权限" },
|
||||
label: { en: "Set modes and approvals", zh: "设置模式与审批" },
|
||||
note: {
|
||||
en: "Plan / Work / Operate and Ask / Auto-Review / Full Access: what each one allows.",
|
||||
zh: "Plan / Work / Operate 与 Ask / Auto-Review / Full Access:各自允许做什么。",
|
||||
en: "Plan, Work, or Operate for the kind of work; Ask, Auto-Review, or Full Access for when it stops to ask you.",
|
||||
zh: "用 Plan、Work、Operate 选择工作类型,用 Ask、Auto-Review、Full Access 决定它什么时候停下来问你。",
|
||||
},
|
||||
},
|
||||
{
|
||||
href: "/docs",
|
||||
label: { en: "Documentation hub", zh: "文档中心" },
|
||||
href: "/docs/hooks",
|
||||
label: { en: "Run commands on events", zh: "在事件发生时运行命令" },
|
||||
note: {
|
||||
en: "Browse and search the documentation for help on a specific topic, with a link from each page to its source in the repository.",
|
||||
zh: "你可以浏览或搜索文档来查找需要的帮助,并通过每页的链接查看仓库中的源文档。",
|
||||
en: "Run your own scripts when a session starts, before a tool call, or when a turn ends — to add context, enforce a rule, or get notified.",
|
||||
zh: "在会话开始、工具调用之前或回合结束时运行你自己的脚本——用来补充上下文、执行规则或接收通知。",
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { LocalizedText } from "./vocabulary";
|
||||
|
||||
export const TOOLS_COPY = {
|
||||
metaTitle: { en: "Tools · Codewhale Docs", zh: "工具 · Codewhale 文档" },
|
||||
metaTitle: { en: "Built-in tools · Codewhale Docs", zh: "内置工具 · Codewhale 文档" },
|
||||
metaDescription: { en: "Core work tools, on-demand discovery, and exact replay compatibility.", zh: "核心工作工具、按需发现与精确回放兼容。" },
|
||||
title: { en: "Tools", zh: "工具" },
|
||||
lead: { en: "Core work controls are available without discovery; specialized tools are loaded when needed. Mode and permission rules still apply.", zh: "核心工作工具无需搜索即可使用,专用工具按需加载。模式与权限规则始终生效。" },
|
||||
title: { en: "Built-in tools", zh: "内置工具" },
|
||||
lead: { en: "Every session starts with a small set of tools for reading, editing, running commands, and delegating. Codewhale loads specialized tools only when a task needs them, and your mode and approval setting still apply to all of them.", zh: "每个会话一开始就带着一小组工具,用于阅读、修改、运行命令和委派任务。专用工具只在任务需要时才加载,而你的模式和审批设置对所有工具都同样有效。" },
|
||||
reference: { en: "Tool contract and design rationale", zh: "工具约定与设计说明" },
|
||||
rows: [
|
||||
{ name: "read", detail: { en: "path · offset? · limit?", zh: "path · offset? · limit?" } },
|
||||
|
||||
@@ -101,24 +101,17 @@ describe("shared getting-started path", () => {
|
||||
});
|
||||
|
||||
it("points every step and next link at a real on-site route", () => {
|
||||
const knownRoutes = [
|
||||
"/install",
|
||||
"/models",
|
||||
"/docs",
|
||||
"/docs/guide",
|
||||
"/docs/vocabulary",
|
||||
"/docs/fleet",
|
||||
"/docs/hooks",
|
||||
"/docs/modes",
|
||||
];
|
||||
// A link is real when its locale-relative route has an app-router page.
|
||||
const routeExists = (href: string) =>
|
||||
existsSync(new URL(`../../app/[locale]${href}/page.tsx`, import.meta.url));
|
||||
for (const step of GETTING_STARTED_STEPS) {
|
||||
expect(knownRoutes, step.link.href).toContain(step.link.href);
|
||||
expect(routeExists(step.link.href), step.link.href).toBe(true);
|
||||
expect(step.title.en.trim().length).toBeGreaterThan(0);
|
||||
expect(step.title.zh.trim().length).toBeGreaterThan(0);
|
||||
expect(`${step.body.en}\n${step.body.zh}`).not.toMatch(BANNED_COPY);
|
||||
}
|
||||
for (const link of GUIDE_NEXT_LINKS) {
|
||||
expect(knownRoutes, link.href).toContain(link.href);
|
||||
expect(routeExists(link.href), link.href).toBe(true);
|
||||
}
|
||||
// Hooks discovery is a first-class next step, not buried prose.
|
||||
expect(GUIDE_NEXT_LINKS.some((l) => l.href === "/docs/hooks")).toBe(true);
|
||||
|
||||
@@ -23,14 +23,14 @@ describe("docs breadcrumbs", () => {
|
||||
expect(resolveDocsBreadcrumbs("en", "/en/docs/mcp")).toEqual([
|
||||
{ name: "Home", href: "/en" },
|
||||
{ name: "Docs", href: "/en/docs" },
|
||||
{ name: "Extending" },
|
||||
{ name: "MCP" },
|
||||
{ name: "Extend and automate" },
|
||||
{ name: "Connect tools with MCP" },
|
||||
]);
|
||||
expect(resolveDocsBreadcrumbs("zh", "/zh/docs/modes/")).toEqual([
|
||||
{ name: "首页", href: "/zh" },
|
||||
{ name: "文档", href: "/zh/docs" },
|
||||
{ name: "核心概念" },
|
||||
{ name: "模式" },
|
||||
{ name: "日常使用" },
|
||||
{ name: "设置模式与审批" },
|
||||
]);
|
||||
expect(resolveDocsTopic("en", "/en/docs/guide")?.id).toBe("guide");
|
||||
});
|
||||
@@ -54,7 +54,7 @@ describe("docs breadcrumbs", () => {
|
||||
{
|
||||
"@type": "ListItem",
|
||||
position: 3,
|
||||
name: "Sandbox & Approval",
|
||||
name: "Limit what commands can touch",
|
||||
item: `${SITE_URL}/en/docs/sandbox`,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -47,7 +47,7 @@ describe("docs-map registration", () => {
|
||||
const vocabulary = getTopic("vocabulary");
|
||||
expect(guide?.hasPage).toBe(true);
|
||||
expect(vocabulary?.hasPage).toBe(true);
|
||||
expect(vocabulary?.category).toBe("core-concepts");
|
||||
expect(vocabulary?.category).toBe("reference");
|
||||
expect(docTopicHref(guide!, "en")).toBe("/en/docs/guide");
|
||||
expect(docTopicHref(vocabulary!, "zh")).toBe("/zh/docs/vocabulary");
|
||||
expect(docsTopicIsCurrent(vocabulary!, "en", "/en/docs/vocabulary")).toBe(true);
|
||||
@@ -93,7 +93,7 @@ describe("sitemap and hreflang preservation", () => {
|
||||
it("keeps sitemap and hreflang output aligned with real translation coverage", () => {
|
||||
// 18 home locales + 10 guide locales + English-only install + (en, zh) for every other route
|
||||
// (including /product, /plugins, and /changelog, whose bodies ship en/zh only).
|
||||
expect(sitemapEntries).toHaveLength(97);
|
||||
expect(sitemapEntries).toHaveLength(99);
|
||||
expect(sitemapEntries.filter(entry => entry.url.endsWith("/install")).map(entry => entry.url))
|
||||
.toEqual([`${SITE_URL}/en/install`]);
|
||||
expect(sitemapEntries.some(entry => entry.url.endsWith("/pricing"))).toBe(false);
|
||||
@@ -138,7 +138,7 @@ describe("sitemap and hreflang preservation", () => {
|
||||
expect(existsSync(new URL(`app/[locale]${path}/page.tsx`, webRoot)), path).toBe(true);
|
||||
}
|
||||
const entry = webText("components/public-account-entry.tsx");
|
||||
expect(entry).toContain("CANONICAL_MARK_SRC");
|
||||
expect(entry).toContain("<WhalePose");
|
||||
expect(entry).toContain("installLocally");
|
||||
expect(webText("app/[locale]/signin/page.tsx")).toContain('kind="sign-in"');
|
||||
expect(webText("app/[locale]/signup/page.tsx")).toContain('kind="sign-up"');
|
||||
|
||||
+154
-142
@@ -32,21 +32,21 @@ export interface DocTopic {
|
||||
|
||||
/** Sidebar and breadcrumb labels for each docs-map category. */
|
||||
export const DOC_CATEGORY_LABELS: Record<DocTopic["category"], { en: string; zh: string }> = {
|
||||
"getting-started": { en: "Getting started", zh: "入门" },
|
||||
"core-concepts": { en: "Core concepts", zh: "核心概念" },
|
||||
"getting-started": { en: "Get started", zh: "开始使用" },
|
||||
"core-concepts": { en: "Work with Codewhale", zh: "日常使用" },
|
||||
extending: { en: "Extend and automate", zh: "扩展与自动化" },
|
||||
operations: { en: "Get help", zh: "获取帮助" },
|
||||
reference: { en: "Reference", zh: "参考" },
|
||||
extending: { en: "Extending", zh: "扩展" },
|
||||
operations: { en: "Operations", zh: "运维" },
|
||||
};
|
||||
|
||||
export const DOC_TOPICS: DocTopic[] = [
|
||||
{
|
||||
id: "install",
|
||||
slug: "install",
|
||||
label: { en: "Install", zh: "安装" },
|
||||
label: { en: "Install Codewhale", zh: "安装 Codewhale" },
|
||||
description: {
|
||||
en: "npm, Cargo, Homebrew, Docker, prebuilt binaries, CNB mirror, and where config lives.",
|
||||
zh: "npm、Cargo、Homebrew、Docker、预编译二进制、CNB 镜像,以及配置文件位置。",
|
||||
en: "The installer, npm, Homebrew tap, Cargo, and release binaries, with the output each step prints.",
|
||||
zh: "安装脚本、npm、Homebrew tap、Cargo 与发布版二进制文件,以及每一步应有的输出。",
|
||||
},
|
||||
repoSource: "docs/INSTALL.md",
|
||||
hasPage: true,
|
||||
@@ -56,46 +56,22 @@ export const DOC_TOPICS: DocTopic[] = [
|
||||
{
|
||||
id: "guide",
|
||||
slug: "guide",
|
||||
label: { en: "User Guide", zh: "使用指南" },
|
||||
label: { en: "Start your first task", zh: "开始第一个任务" },
|
||||
description: {
|
||||
en: "First run, sessions, commands, keyboard shortcuts, and everyday workflows.",
|
||||
zh: "首次运行、会话、命令、快捷键和日常使用流程。",
|
||||
en: "Install, connect a model, and give Codewhale a task in your project.",
|
||||
zh: "安装、连接模型,然后在你的项目里交给 Codewhale 一项任务。",
|
||||
},
|
||||
repoSource: ["docs/GUIDE.md", "docs/KEYBINDINGS.md"],
|
||||
hasPage: true,
|
||||
category: "getting-started",
|
||||
},
|
||||
{
|
||||
id: "vocabulary",
|
||||
slug: "vocabulary",
|
||||
label: { en: "Vocabulary", zh: "产品名词" },
|
||||
description: {
|
||||
en: "The exact product nouns — Fleet, Workflow, Lane, Runtime; Plan / Work / Operate; Advisor; and explicit route provenance — plus measurement principles.",
|
||||
zh: "确切的产品名词——Fleet、Workflow、Lane、Runtime;Plan / Work / Operate;Advisor;明确的路由来源——以及测量原则。",
|
||||
},
|
||||
repoSource: ["docs/FLEET.md", "docs/MODES.md", "docs/public-surface-facts.json"],
|
||||
hasPage: true,
|
||||
category: "core-concepts",
|
||||
},
|
||||
{
|
||||
id: "configuration",
|
||||
slug: "configuration",
|
||||
label: { en: "Configuration", zh: "配置" },
|
||||
description: {
|
||||
en: "config.toml reference, environment variables, project overrides, and legacy paths.",
|
||||
zh: "config.toml 参考、环境变量、项目覆盖和旧版路径。",
|
||||
},
|
||||
repoSource: ["docs/CONFIGURATION.md", "docs/LEGACY_PATHS.md"],
|
||||
hasPage: true,
|
||||
category: "getting-started",
|
||||
},
|
||||
{
|
||||
id: "auth",
|
||||
slug: "auth",
|
||||
label: { en: "Account & Keys", zh: "账户与密钥" },
|
||||
label: { en: "Connect a provider", zh: "连接模型提供商" },
|
||||
description: {
|
||||
en: "Provider keys versus the optional Codewhale account: how each is set, where each is stored, and what needs no account.",
|
||||
zh: "提供商密钥与可选的 Codewhale 账户:各自如何设置、存放在哪里,以及哪些操作不需要账户。",
|
||||
en: "Save a provider key, check which key is used, run a local model, or sign in to the optional account.",
|
||||
zh: "保存提供商密钥、查看当前使用的密钥、运行本地模型,或登录可选的账户。",
|
||||
},
|
||||
repoSource: ["docs/CONFIGURATION.md", "docs/CODEWHALE_AGENT.md"],
|
||||
hasPage: true,
|
||||
@@ -104,60 +80,59 @@ export const DOC_TOPICS: DocTopic[] = [
|
||||
{
|
||||
id: "providers",
|
||||
slug: "providers",
|
||||
label: { en: "Providers & Models", zh: "提供商与模型" },
|
||||
label: { en: "Choose a model", zh: "选择模型" },
|
||||
description: {
|
||||
en: "Supported providers, model switching, local runtimes (vLLM, Ollama, SGLang), and Model Lab.",
|
||||
zh: "支持的提供商、模型切换、本地运行时(vLLM、Ollama、SGLang)和模型实验室。",
|
||||
en: "Supported providers, switching models, and local runners such as Ollama, vLLM, and SGLang.",
|
||||
zh: "支持的提供商、切换模型,以及 Ollama、vLLM、SGLang 等本地运行器。",
|
||||
},
|
||||
repoSource: ["docs/PROVIDERS.md", "docs/MODEL_LAB.md"],
|
||||
hasPage: true,
|
||||
sitePath: "models",
|
||||
category: "reference",
|
||||
category: "getting-started",
|
||||
},
|
||||
{
|
||||
id: "constitution",
|
||||
slug: "constitution",
|
||||
label: { en: "Constitution", zh: "嵌套宪章" },
|
||||
id: "configuration",
|
||||
slug: "configuration",
|
||||
label: { en: "Change settings", zh: "修改设置" },
|
||||
description: {
|
||||
en: "Agent identity, authority hierarchy, evidence rules, and the nested law system.",
|
||||
zh: "Agent 自我模型、权威层次、证据规则和嵌套法律系统。",
|
||||
en: "Find the config file, change settings from the session or shell, and see what a repository may override.",
|
||||
zh: "找到配置文件,在会话或 shell 中修改设置,并了解仓库可以覆盖哪些设置。",
|
||||
},
|
||||
repoSource: "docs/ARCHITECTURE.md",
|
||||
repoSource: ["docs/CONFIGURATION.md", "docs/LEGACY_PATHS.md"],
|
||||
hasPage: true,
|
||||
sitePath: "constitution",
|
||||
category: "core-concepts",
|
||||
category: "getting-started",
|
||||
},
|
||||
{
|
||||
id: "modes",
|
||||
slug: "modes",
|
||||
label: { en: "Modes", zh: "模式" },
|
||||
label: { en: "Set modes and approvals", zh: "设置模式与审批" },
|
||||
description: {
|
||||
en: "Plan, Work, Operate modes and orthogonal permission posture.",
|
||||
zh: "Plan、Work、Operate 三种模式与正交权限姿态。",
|
||||
en: "Plan, Work, or Operate for the kind of work; Ask, Auto-Review, or Full Access for when it asks you.",
|
||||
zh: "用 Plan、Work、Operate 选择工作类型,用 Ask、Auto-Review、Full Access 决定它何时问你。",
|
||||
},
|
||||
repoSource: "docs/MODES.md",
|
||||
hasPage: true,
|
||||
category: "core-concepts",
|
||||
},
|
||||
{
|
||||
id: "tools",
|
||||
slug: "tools",
|
||||
label: { en: "Tools", zh: "工具" },
|
||||
id: "review",
|
||||
slug: "review",
|
||||
label: { en: "Review what changed", zh: "查看改动" },
|
||||
description: {
|
||||
en: "Canonical action tools, deferred discovery, and replay compatibility.",
|
||||
zh: "小型核心工具、按需搜索、会话缓存与精确回放兼容边界。",
|
||||
en: "See every edit, roll files back to an earlier turn, get a code review, and keep a review receipt.",
|
||||
zh: "查看每一处修改,把文件回滚到之前的回合,做代码审查,并保留审查收据。",
|
||||
},
|
||||
repoSource: ["docs/TOOL_SURFACE.md", "docs/RUNTIME_SIMPLIFICATION_DESIGN.md"],
|
||||
repoSource: ["docs/RECEIPTS.md", "docs/CONFIGURATION.md"],
|
||||
hasPage: true,
|
||||
category: "core-concepts",
|
||||
},
|
||||
{
|
||||
id: "work",
|
||||
slug: "work",
|
||||
label: { en: "Work Surface", zh: "工作面板" },
|
||||
label: { en: "Track progress", zh: "跟踪进度" },
|
||||
description: {
|
||||
en: "The single To-do list, how the model sees it through its own tool results, and how work state flows to the sidebar, relay, and sub-agents.",
|
||||
zh: "唯一的 To-do 列表、模型如何通过自己的工具结果看到它,以及工作状态如何流向侧栏、relay 和子 Agent。",
|
||||
en: "Follow the goal, To-do list, and sub-agents in the workbar, and hand work to a fresh session.",
|
||||
zh: "在工作栏中跟踪目标、To-do 列表和子 Agent,并把工作交接给新的会话。",
|
||||
},
|
||||
repoSource: ["docs/TOOL_SURFACE.md", "docs/TOOL_LIFECYCLE.md"],
|
||||
hasPage: true,
|
||||
@@ -166,70 +141,34 @@ export const DOC_TOPICS: DocTopic[] = [
|
||||
{
|
||||
id: "subagents",
|
||||
slug: "subagents",
|
||||
label: { en: "Sub-Agents", zh: "子 Agent" },
|
||||
label: { en: "Run agents in parallel", zh: "并行运行 Agent" },
|
||||
description: {
|
||||
en: "Parallel execution, role types, transcript handles, and nesting.",
|
||||
zh: "并行执行、角色类型、transcript 句柄和嵌套。",
|
||||
en: "Hand independent parts of a task to sub-agents, pick roles, and keep parallel edits in worktrees.",
|
||||
zh: "把任务中独立的部分交给子 Agent,选择角色,并用工作树隔离并行修改。",
|
||||
},
|
||||
repoSource: "docs/SUBAGENTS.md",
|
||||
hasPage: true,
|
||||
category: "core-concepts",
|
||||
},
|
||||
{
|
||||
id: "mcp",
|
||||
slug: "mcp",
|
||||
label: { en: "MCP", zh: "MCP" },
|
||||
id: "fleet",
|
||||
slug: "fleet",
|
||||
label: { en: "Run a workflow", zh: "运行 Workflow" },
|
||||
description: {
|
||||
en: "Model Context Protocol — consuming and exposing tools via stdio and HTTP/SSE.",
|
||||
zh: "Model Context Protocol — 通过 stdio 和 HTTP/SSE 消费和暴露工具。",
|
||||
en: "Save roles in a Fleet, write a repeatable Workflow, run it as a Lane, and run batches of tasks.",
|
||||
zh: "在 Fleet 中保存角色,编写可重复的 Workflow,把它作为 Lane 运行,并批量执行任务。",
|
||||
},
|
||||
repoSource: "docs/MCP.md",
|
||||
repoSource: ["docs/FLEET.md", "docs/WORKFLOW_AUTHORING.md"],
|
||||
hasPage: true,
|
||||
category: "extending",
|
||||
},
|
||||
{
|
||||
id: "hooks",
|
||||
slug: "hooks",
|
||||
label: { en: "Hooks", zh: "钩子" },
|
||||
description: {
|
||||
en: "Lifecycle hooks for pre/post tool execution, mode changes, and session events.",
|
||||
zh: "工具执行前后、模式切换和会话事件的生命周期钩子。",
|
||||
},
|
||||
repoSource: ["docs/rfcs/1364-hooks-lifecycle.md", "docs/CONFIGURATION.md"],
|
||||
hasPage: true,
|
||||
category: "extending",
|
||||
},
|
||||
{
|
||||
id: "skills",
|
||||
slug: "skills",
|
||||
label: { en: "Skills", zh: "技能" },
|
||||
description: {
|
||||
en: "Install, discover, trust, and load reusable instruction packages.",
|
||||
zh: "安装、发现、信任并加载可复用的指令包。",
|
||||
},
|
||||
repoSource: "docs/SKILLS.md",
|
||||
hasPage: false,
|
||||
category: "extending",
|
||||
},
|
||||
{
|
||||
id: "plugins",
|
||||
slug: "plugins",
|
||||
label: { en: "Plugins", zh: "插件" },
|
||||
description: {
|
||||
en: "Plugin discovery, installation, bundles, trust boundaries, and runtime lifecycle.",
|
||||
zh: "插件发现、安装、Bundle、信任边界与运行时生命周期。",
|
||||
},
|
||||
repoSource: ["docs/PLUGINS.md", "docs/PLUGIN_BUNDLES.md"],
|
||||
hasPage: false,
|
||||
category: "extending",
|
||||
category: "core-concepts",
|
||||
},
|
||||
{
|
||||
id: "sandbox",
|
||||
slug: "sandbox",
|
||||
label: { en: "Sandbox & Approval", zh: "沙箱与审批" },
|
||||
label: { en: "Limit what commands can touch", zh: "限制命令的访问范围" },
|
||||
description: {
|
||||
en: "Available Seatbelt (macOS), opt-in bubblewrap (Linux), platform gaps, and approval policies.",
|
||||
zh: "可用的 Seatbelt(macOS)、显式启用的 bubblewrap(Linux)、平台缺口和审批策略。",
|
||||
en: "The OS sandbox on macOS, Linux, and Windows, how to turn it on, and how much a command may write.",
|
||||
zh: "macOS、Linux 和 Windows 上的操作系统沙箱、如何开启,以及命令能写到哪里。",
|
||||
},
|
||||
repoSource: "docs/SANDBOX.md",
|
||||
hasPage: true,
|
||||
@@ -238,22 +177,70 @@ export const DOC_TOPICS: DocTopic[] = [
|
||||
{
|
||||
id: "trust",
|
||||
slug: "trust",
|
||||
label: { en: "Security & Trust", zh: "安全与信任" },
|
||||
label: { en: "See what leaves your machine", zh: "了解哪些数据会离开本机" },
|
||||
description: {
|
||||
en: "What stays local, what a hosted provider receives, approvals versus the OS sandbox, telemetry field by field, and where to report a vulnerability.",
|
||||
zh: "哪些留在本地、托管提供商收到什么、审批与 OS 沙箱的区别、逐项说明的遥测,以及在哪里报告漏洞。",
|
||||
en: "What stays local, what a provider receives, what usage counting sends, and how to turn it off.",
|
||||
zh: "哪些留在本地、提供商会收到什么、用量统计发送什么,以及如何关闭。",
|
||||
},
|
||||
repoSource: ["docs/SANDBOX.md", "docs/AUTHORIZATION_ORDER.md", "docs/TELEMETRY.md", "docs/public-surface-facts.json"],
|
||||
hasPage: true,
|
||||
category: "reference",
|
||||
category: "core-concepts",
|
||||
},
|
||||
{
|
||||
id: "mcp",
|
||||
slug: "mcp",
|
||||
label: { en: "Connect tools with MCP", zh: "用 MCP 连接工具" },
|
||||
description: {
|
||||
en: "Add local (stdio) or remote (HTTP) MCP servers, sign in with OAuth, serve Codewhale over MCP, and try code mode.",
|
||||
zh: "添加本地(stdio)或远程(HTTP)MCP 服务器、用 OAuth 登录、把 Codewhale 作为 MCP 服务器运行,并试用代码模式。",
|
||||
},
|
||||
repoSource: "docs/MCP.md",
|
||||
hasPage: true,
|
||||
category: "extending",
|
||||
},
|
||||
{
|
||||
id: "hooks",
|
||||
slug: "hooks",
|
||||
label: { en: "Run commands on events", zh: "在事件发生时运行命令" },
|
||||
description: {
|
||||
en: "Run your own scripts at session start, before a tool call, at turn end, or when Codewhale waits for you.",
|
||||
zh: "在会话开始、工具调用之前、回合结束或 Codewhale 等你回应时运行你自己的脚本。",
|
||||
},
|
||||
repoSource: ["docs/rfcs/1364-hooks-lifecycle.md", "docs/CONFIGURATION.md"],
|
||||
hasPage: true,
|
||||
category: "extending",
|
||||
},
|
||||
{
|
||||
id: "skills",
|
||||
slug: "skills",
|
||||
label: { en: "Add skills", zh: "添加技能" },
|
||||
description: {
|
||||
en: "Install, find, trust, and load reusable instruction packages.",
|
||||
zh: "安装、查找、信任并加载可复用的指令包。",
|
||||
},
|
||||
repoSource: "docs/SKILLS.md",
|
||||
hasPage: false,
|
||||
category: "extending",
|
||||
},
|
||||
{
|
||||
id: "plugins",
|
||||
slug: "plugins",
|
||||
label: { en: "Add plugins", zh: "添加插件" },
|
||||
description: {
|
||||
en: "Find, install, review, and trust plugin bundles.",
|
||||
zh: "查找、安装、审查并信任插件包。",
|
||||
},
|
||||
repoSource: ["docs/PLUGINS.md", "docs/PLUGIN_BUNDLES.md"],
|
||||
hasPage: false,
|
||||
category: "extending",
|
||||
},
|
||||
{
|
||||
id: "runtime-api",
|
||||
slug: "runtime-api",
|
||||
label: { en: "Runtime API", zh: "运行时 API" },
|
||||
label: { en: "Automate with the Runtime API", zh: "用 Runtime API 自动化" },
|
||||
description: {
|
||||
en: "Public HTTP API for integrations, bridges, and automation.",
|
||||
zh: "用于集成、桥接和自动化的公开 HTTP API。",
|
||||
en: "Run one-shot jobs from scripts, or drive threads, events, and approvals over the local HTTP API.",
|
||||
zh: "在脚本中运行一次性任务,或通过本地 HTTP API 驱动线程、事件和审批。",
|
||||
},
|
||||
repoSource: "docs/RUNTIME_API.md",
|
||||
hasPage: true,
|
||||
@@ -262,10 +249,10 @@ export const DOC_TOPICS: DocTopic[] = [
|
||||
{
|
||||
id: "web",
|
||||
slug: "web",
|
||||
label: { en: "Browser Client", zh: "浏览器客户端" },
|
||||
label: { en: "Open the browser client", zh: "打开浏览器客户端" },
|
||||
description: {
|
||||
en: "Run the embedded browser client on loopback, with its one-time bootstrap and session boundaries.",
|
||||
zh: "仅在本机回环地址运行内置浏览器客户端,了解一次性引导与会话边界。",
|
||||
en: "Work in a local browser tab, or continue a terminal session from the web app with /rc.",
|
||||
zh: "在本机浏览器标签页中工作,或用 /rc 在网页应用中接着使用终端会话。",
|
||||
},
|
||||
repoSource: "docs/WEB.md",
|
||||
hasPage: true,
|
||||
@@ -276,34 +263,22 @@ export const DOC_TOPICS: DocTopic[] = [
|
||||
{
|
||||
id: "computers",
|
||||
slug: "computers",
|
||||
label: { en: "Cloud Computers", zh: "云端计算机" },
|
||||
label: { en: "Send a task to the cloud", zh: "把任务发送到云端" },
|
||||
description: {
|
||||
en: "Propose, confirm, and track a Daytona cloud agent against an explicit forge — fail-closed credentials and what is not built yet.",
|
||||
zh: "向明确指定的代码托管平台提议、确认并跟踪 Daytona 云端 Agent——凭证缺失即拒绝,以及尚未实现的部分。",
|
||||
en: "Preview: propose, confirm, and track a cloud agent that opens a pull request on GitHub, CNB, or Gitee.",
|
||||
zh: "预览版:提议、确认并跟踪一个在 GitHub、CNB 或 Gitee 上提交拉取请求的云端 Agent。",
|
||||
},
|
||||
repoSource: ["docs/DAYTONA_CLOUD_DISPATCH.md", "docs/CODEWHALE_AGENT.md"],
|
||||
hasPage: true,
|
||||
category: "operations",
|
||||
},
|
||||
{
|
||||
id: "fleet",
|
||||
slug: "fleet",
|
||||
label: { en: "Fleet / Workflow", zh: "Fleet / Workflow" },
|
||||
description: {
|
||||
en: "Durable task execution, fleet roster management, and Workflow authoring.",
|
||||
zh: "持久任务执行、fleet 花名册管理和 Workflow 编写。",
|
||||
},
|
||||
repoSource: ["docs/FLEET.md", "docs/WORKFLOW_AUTHORING.md"],
|
||||
hasPage: true,
|
||||
category: "operations",
|
||||
category: "extending",
|
||||
},
|
||||
{
|
||||
id: "troubleshooting",
|
||||
slug: "troubleshooting",
|
||||
label: { en: "Troubleshooting", zh: "排障" },
|
||||
label: { en: "Fix a problem", zh: "排查问题" },
|
||||
description: {
|
||||
en: "Common issues, diagnostics, operations runbook, and Docker notes.",
|
||||
zh: "常见问题、诊断、运维手册和 Docker 说明。",
|
||||
en: "Diagnose in one command, then fix install, key, network, stuck-turn, session, and MCP problems.",
|
||||
zh: "用一条命令诊断,再解决安装、密钥、网络、回合卡住、会话和 MCP 等问题。",
|
||||
},
|
||||
repoSource: ["docs/OPERATIONS_RUNBOOK.md", "docs/DOCKER.md"],
|
||||
hasPage: true,
|
||||
@@ -312,10 +287,10 @@ export const DOC_TOPICS: DocTopic[] = [
|
||||
{
|
||||
id: "contribution",
|
||||
slug: "contribution",
|
||||
label: { en: "Contribution", zh: "贡献" },
|
||||
label: { en: "Contribute", zh: "参与贡献" },
|
||||
description: {
|
||||
en: "Contributing guide, agent ethos, contributor credits, and release process.",
|
||||
zh: "贡献指南、Agent 伦理、贡献者致谢和发布流程。",
|
||||
en: "The contributing guide, agent ethos, contributor credits, and release process.",
|
||||
zh: "贡献指南、Agent 准则、贡献者致谢和发布流程。",
|
||||
},
|
||||
repoSource: [
|
||||
"CONTRIBUTING.md",
|
||||
@@ -326,6 +301,43 @@ export const DOC_TOPICS: DocTopic[] = [
|
||||
hasPage: false,
|
||||
category: "operations",
|
||||
},
|
||||
{
|
||||
id: "vocabulary",
|
||||
slug: "vocabulary",
|
||||
label: { en: "Product terms", zh: "产品名词" },
|
||||
description: {
|
||||
en: "Fleet, Workflow, Lane, and Runtime; Plan, Work, and Operate — each in one sentence.",
|
||||
zh: "Fleet、Workflow、Lane 与 Runtime;Plan、Work 与 Operate——各用一句话说明。",
|
||||
},
|
||||
repoSource: ["docs/FLEET.md", "docs/MODES.md", "docs/public-surface-facts.json"],
|
||||
hasPage: true,
|
||||
category: "reference",
|
||||
},
|
||||
{
|
||||
id: "constitution",
|
||||
slug: "constitution",
|
||||
label: { en: "Constitution", zh: "宪章" },
|
||||
description: {
|
||||
en: "What the agent treats as law: built-in rules, your personal constitution, and a repository's own.",
|
||||
zh: "Agent 视为准则的内容:内置规则、你的个人宪章,以及仓库自己的宪章。",
|
||||
},
|
||||
repoSource: "docs/ARCHITECTURE.md",
|
||||
hasPage: true,
|
||||
sitePath: "constitution",
|
||||
category: "reference",
|
||||
},
|
||||
{
|
||||
id: "tools",
|
||||
slug: "tools",
|
||||
label: { en: "Built-in tools", zh: "内置工具" },
|
||||
description: {
|
||||
en: "The small set of tools every session starts with, and how Codewhale finds the rest.",
|
||||
zh: "每个会话默认携带的少量工具,以及 Codewhale 如何按需找到其余工具。",
|
||||
},
|
||||
repoSource: ["docs/TOOL_SURFACE.md", "docs/RUNTIME_SIMPLIFICATION_DESIGN.md"],
|
||||
hasPage: true,
|
||||
category: "reference",
|
||||
},
|
||||
];
|
||||
|
||||
/** Convenience lookup. */
|
||||
|
||||
+129
-96
@@ -32,21 +32,21 @@ export interface DocTask {
|
||||
export const DOC_TASKS: DocTask[] = [
|
||||
{
|
||||
id: "install",
|
||||
label: { en: "Install on macOS, Linux, or Windows", zh: "在 macOS、Linux 或 Windows 上安装" },
|
||||
label: { en: "Install Codewhale", zh: "安装 Codewhale" },
|
||||
description: {
|
||||
en: "Start with GitHub release binaries; npm, Cargo, Homebrew, Docker, and China mirrors are alternatives.",
|
||||
zh: "优先使用 GitHub Releases 二进制;npm、Cargo、Homebrew、Docker 与中国镜像为其他安装方式。",
|
||||
en: "One command on macOS or Linux; npm, the Homebrew tap, Cargo, and release binaries also work.",
|
||||
zh: "在 macOS 或 Linux 上只需一条命令;也可以用 npm、Homebrew tap、Cargo 或发布版二进制文件安装。",
|
||||
},
|
||||
href: "/install",
|
||||
topicId: "install",
|
||||
keywords: { en: "download setup brew cargo docker termux binary", zh: "下载 安装 二进制 镜像" },
|
||||
keywords: { en: "download setup brew npm cargo docker termux binary windows", zh: "下载 安装 二进制 镜像" },
|
||||
},
|
||||
{
|
||||
id: "first-session",
|
||||
label: { en: "Open a first session without a key", zh: "不用密钥打开第一个会话" },
|
||||
id: "first-task",
|
||||
label: { en: "Start your first task", zh: "开始第一个任务" },
|
||||
description: {
|
||||
en: "Start the runtime, look around in read-only Plan mode, then connect a provider.",
|
||||
zh: "启动 Runtime,在只读的 Plan 模式里看看,再连接提供商。",
|
||||
en: "Open Codewhale in a project, look around in Plan, then let it edit in Work.",
|
||||
zh: "在项目中打开 Codewhale,先在 Plan 里了解项目,再让它在 Work 里动手修改。",
|
||||
},
|
||||
href: "/docs/guide",
|
||||
topicId: "guide",
|
||||
@@ -54,142 +54,175 @@ export const DOC_TASKS: DocTask[] = [
|
||||
},
|
||||
{
|
||||
id: "connect-provider",
|
||||
label: { en: "Connect a provider key (BYOK)", zh: "连接提供商密钥(BYOK)" },
|
||||
label: { en: "Connect a provider", zh: "连接模型提供商" },
|
||||
description: {
|
||||
en: "DeepSeek, OpenAI, Anthropic, OpenRouter, or a local runtime — your key, your bill.",
|
||||
zh: "DeepSeek、OpenAI、Anthropic、OpenRouter 或本地运行时——你的密钥,你的账单。",
|
||||
},
|
||||
href: "/models",
|
||||
topicId: "providers",
|
||||
keywords: { en: "api key model switch ollama vllm sglang openai-compatible", zh: "密钥 模型 切换 本地模型" },
|
||||
},
|
||||
{
|
||||
id: "choose-mode",
|
||||
label: { en: "Choose Plan, Work, or Operate", zh: "选择 Plan、Work 或 Operate" },
|
||||
description: {
|
||||
en: "Read-only planning, execution, or Fleet orchestration — and the approval posture beside it.",
|
||||
zh: "只读规划、执行或 Fleet 编排——以及旁边独立的审批姿态。",
|
||||
},
|
||||
href: "/docs/modes",
|
||||
topicId: "modes",
|
||||
keywords: { en: "mode tab shift+tab ask auto-review full access", zh: "模式 审批 权限" },
|
||||
},
|
||||
{
|
||||
id: "approve-commands",
|
||||
label: { en: "Decide which commands may run", zh: "决定哪些命令可以执行" },
|
||||
description: {
|
||||
en: "Approval posture, the OS sandbox per platform, and why an approval is not a sandbox.",
|
||||
zh: "审批姿态、各平台的 OS 沙箱,以及为什么批准不等于沙箱。",
|
||||
},
|
||||
href: "/docs/sandbox",
|
||||
topicId: "sandbox",
|
||||
keywords: { en: "permission approval seatbelt bwrap policy", zh: "权限 审批 沙箱" },
|
||||
},
|
||||
{
|
||||
id: "sign-in",
|
||||
label: { en: "Sign in to a Codewhale account", zh: "登录 Codewhale 账户" },
|
||||
description: {
|
||||
en: "The optional account, where its session lives, and what needs no account at all.",
|
||||
zh: "可选的账户、会话存放位置,以及哪些操作完全不需要账户。",
|
||||
en: "Save a key for DeepSeek, OpenAI, Anthropic, OpenRouter, or another provider — or run a local model with no key.",
|
||||
zh: "为 DeepSeek、OpenAI、Anthropic、OpenRouter 等提供商保存密钥,或者不用密钥运行本地模型。",
|
||||
},
|
||||
href: "/docs/auth",
|
||||
topicId: "auth",
|
||||
keywords: { en: "login account keys vault profile logout register", zh: "登录 账户 密钥库 注册" },
|
||||
keywords: { en: "api key byok auth set login account ollama vllm sglang local", zh: "密钥 登录 账户 本地模型" },
|
||||
},
|
||||
{
|
||||
id: "cloud-computer",
|
||||
label: { en: "Dispatch a task to a cloud computer", zh: "把任务派发到云端计算机" },
|
||||
id: "choose-model",
|
||||
label: { en: "Choose or switch models", zh: "选择或切换模型" },
|
||||
description: {
|
||||
en: "Propose, confirm, and track a Daytona cloud agent against an explicit forge.",
|
||||
zh: "向明确指定的代码托管平台提议、确认并跟踪一个 Daytona 云端 Agent。",
|
||||
en: "Supported providers, switching mid-session, and local runners.",
|
||||
zh: "支持的提供商、在会话中切换模型,以及本地运行器。",
|
||||
},
|
||||
href: "/docs/computers",
|
||||
topicId: "computers",
|
||||
keywords: { en: "daytona dispatch cloud agent remote github cnb gitee", zh: "云端 派发 远程 计算机" },
|
||||
href: "/models",
|
||||
topicId: "providers",
|
||||
keywords: { en: "model switch provider openai-compatible", zh: "模型 切换 提供商" },
|
||||
},
|
||||
{
|
||||
id: "parallel-agents",
|
||||
label: { en: "Run sub-agents in parallel", zh: "并行运行子 Agent" },
|
||||
id: "set-approvals",
|
||||
label: { en: "Decide what runs without asking", zh: "决定哪些操作无需询问" },
|
||||
description: {
|
||||
en: "Roles, context forking, worktree isolation, and the concurrency caps.",
|
||||
zh: "角色、上下文分叉、工作树隔离与并发上限。",
|
||||
en: "Plan, Work, or Operate; Ask, Auto-Review, or Full Access; and how to answer a prompt.",
|
||||
zh: "Plan、Work 或 Operate;Ask、Auto-Review 或 Full Access;以及如何回应审批提示。",
|
||||
},
|
||||
href: "/docs/subagents",
|
||||
topicId: "subagents",
|
||||
keywords: { en: "agent worker scout reviewer worktree concurrency", zh: "子代理 并行 角色 工作树" },
|
||||
href: "/docs/modes",
|
||||
topicId: "modes",
|
||||
keywords: { en: "mode tab shift+tab ask auto-review full access permission", zh: "模式 审批 权限" },
|
||||
},
|
||||
{
|
||||
id: "mcp-server",
|
||||
label: { en: "Connect an MCP server", zh: "连接一个 MCP 服务器" },
|
||||
id: "review-changes",
|
||||
label: { en: "Review what changed", zh: "查看改动" },
|
||||
description: {
|
||||
en: "Consume tools over stdio or HTTP/SSE, or expose Codewhale as a server.",
|
||||
zh: "通过 stdio 或 HTTP/SSE 使用工具,或把 Codewhale 暴露为服务器。",
|
||||
en: "See every edit, roll files back to an earlier turn, and get a code review before you push.",
|
||||
zh: "查看每一处修改,把文件回滚到之前的回合,并在推送前做代码审查。",
|
||||
},
|
||||
href: "/docs/mcp",
|
||||
topicId: "mcp",
|
||||
keywords: { en: "model context protocol tools stdio sse", zh: "工具 协议 服务器" },
|
||||
href: "/docs/review",
|
||||
topicId: "review",
|
||||
keywords: { en: "diff undo restore rollback snapshot code review", zh: "差异 撤销 回滚 快照 审查" },
|
||||
},
|
||||
{
|
||||
id: "hooks",
|
||||
label: { en: "Run something before or after a tool", zh: "在工具执行前后运行自定义逻辑" },
|
||||
id: "receipts",
|
||||
label: { en: "Keep a receipt of a review", zh: "为审查保留收据" },
|
||||
description: {
|
||||
en: "Lifecycle hooks for tool execution, mode changes, and session events.",
|
||||
zh: "工具执行、模式切换和会话事件的生命周期钩子。",
|
||||
en: "Write a review receipt and check it before you push.",
|
||||
zh: "生成审查收据,并在推送前核对它。",
|
||||
},
|
||||
href: "/docs/hooks",
|
||||
topicId: "hooks",
|
||||
keywords: { en: "hook lifecycle pre post event", zh: "钩子 生命周期 事件" },
|
||||
href: "/docs/review",
|
||||
topicId: "review",
|
||||
keywords: { en: "receipt write-receipt check-receipt pre-push export", zh: "收据 推送 导出" },
|
||||
},
|
||||
{
|
||||
id: "fleet",
|
||||
label: { en: "Write a Workflow for a Fleet", zh: "为 fleet 编写 Workflow" },
|
||||
id: "run-workflow",
|
||||
label: { en: "Run a workflow", zh: "运行 Workflow" },
|
||||
description: {
|
||||
en: "Durable task execution, roster management, and Workflow authoring.",
|
||||
zh: "持久任务执行、成员管理和 Workflow 编写。",
|
||||
en: "Write a repeatable Workflow, run it as a Lane, and watch or stop it from any terminal.",
|
||||
zh: "编写可重复的 Workflow,把它作为 Lane 运行,并在任何终端查看或停止它。",
|
||||
},
|
||||
href: "/docs/fleet",
|
||||
topicId: "fleet",
|
||||
keywords: { en: "fleet workflow lane operate durable", zh: "编排 持久 工作流" },
|
||||
keywords: { en: "fleet workflow lane operate durable tasks.json", zh: "编排 持久 工作流" },
|
||||
},
|
||||
{
|
||||
id: "browser-client",
|
||||
label: { en: "Use the browser client", zh: "使用浏览器客户端" },
|
||||
id: "parallel-agents",
|
||||
label: { en: "Run agents in parallel", zh: "并行运行 Agent" },
|
||||
description: {
|
||||
en: "Run the embedded web client on loopback with its one-time bootstrap.",
|
||||
zh: "在本机回环地址运行内置网页客户端,了解一次性引导。",
|
||||
en: "Roles, separate worktrees for parallel edits, and how to watch sub-agents.",
|
||||
zh: "角色、为并行修改准备的独立工作树,以及如何查看子 Agent。",
|
||||
},
|
||||
href: "/docs/web",
|
||||
topicId: "web",
|
||||
keywords: { en: "web ui localhost loopback", zh: "网页 客户端 本机" },
|
||||
href: "/docs/subagents",
|
||||
topicId: "subagents",
|
||||
keywords: { en: "agent explore reviewer implement worktree concurrency subagents", zh: "子代理 并行 角色 工作树" },
|
||||
},
|
||||
{
|
||||
id: "mcp-server",
|
||||
label: { en: "Connect tools with MCP", zh: "用 MCP 连接工具" },
|
||||
description: {
|
||||
en: "Add a local or remote MCP server, sign in with OAuth, or serve Codewhale over MCP.",
|
||||
zh: "添加本地或远程 MCP 服务器、用 OAuth 登录,或把 Codewhale 作为 MCP 服务器提供。",
|
||||
},
|
||||
href: "/docs/mcp",
|
||||
topicId: "mcp",
|
||||
keywords: { en: "model context protocol tools stdio http oauth", zh: "工具 协议 服务器" },
|
||||
},
|
||||
{
|
||||
id: "code-mode",
|
||||
label: { en: "Try code mode", zh: "试用代码模式" },
|
||||
description: {
|
||||
en: "Let the model compose several read-only tool calls in one short program. Experimental.",
|
||||
zh: "让模型在一段简短的程序中组合多个只读工具调用。实验性功能。",
|
||||
},
|
||||
href: "/docs/mcp",
|
||||
topicId: "mcp",
|
||||
keywords: { en: "code_mode execute_tools javascript experimental", zh: "代码模式 实验" },
|
||||
},
|
||||
{
|
||||
id: "hooks",
|
||||
label: { en: "Run commands on events", zh: "在事件发生时运行命令" },
|
||||
description: {
|
||||
en: "Block a risky command, add context, or get notified when Codewhale waits for you.",
|
||||
zh: "拦下危险命令、补充上下文,或在 Codewhale 等你回应时收到提醒。",
|
||||
},
|
||||
href: "/docs/hooks",
|
||||
topicId: "hooks",
|
||||
keywords: { en: "hook lifecycle tool_call_before session_start event", zh: "钩子 生命周期 事件" },
|
||||
},
|
||||
{
|
||||
id: "sandbox",
|
||||
label: { en: "Limit what commands can touch", zh: "限制命令的访问范围" },
|
||||
description: {
|
||||
en: "See which OS sandbox your platform has and turn on bubblewrap on Linux.",
|
||||
zh: "了解你的平台有哪种操作系统沙箱,并在 Linux 上开启 bubblewrap。",
|
||||
},
|
||||
href: "/docs/sandbox",
|
||||
topicId: "sandbox",
|
||||
keywords: { en: "sandbox seatbelt bwrap bubblewrap workspace-write", zh: "沙箱 隔离" },
|
||||
},
|
||||
{
|
||||
id: "automate",
|
||||
label: { en: "Automate with the Runtime API", zh: "用运行时 API 做自动化" },
|
||||
label: { en: "Automate with the Runtime API", zh: "用 Runtime API 自动化" },
|
||||
description: {
|
||||
en: "The public HTTP API for integrations, bridges, and scripts.",
|
||||
zh: "用于集成、桥接和脚本的公开 HTTP API。",
|
||||
en: "Run one-shot jobs in CI, or drive threads and approvals over the local HTTP API.",
|
||||
zh: "在 CI 中运行一次性任务,或通过本地 HTTP API 驱动线程和审批。",
|
||||
},
|
||||
href: "/docs/runtime-api",
|
||||
topicId: "runtime-api",
|
||||
keywords: { en: "http api exec acp integration", zh: "接口 集成 脚本" },
|
||||
keywords: { en: "http api exec ci acp integration sse", zh: "接口 集成 脚本" },
|
||||
},
|
||||
{
|
||||
id: "browser-client",
|
||||
label: { en: "Open the browser client", zh: "打开浏览器客户端" },
|
||||
description: {
|
||||
en: "Work in a local browser tab, or continue a session from the web app with /rc.",
|
||||
zh: "在本机浏览器标签页中工作,或用 /rc 在网页应用中继续会话。",
|
||||
},
|
||||
href: "/docs/web",
|
||||
topicId: "web",
|
||||
keywords: { en: "web ui localhost loopback remote control rc", zh: "网页 客户端 本机 远程" },
|
||||
},
|
||||
{
|
||||
id: "cloud-computer",
|
||||
label: { en: "Send a task to the cloud", zh: "把任务发送到云端" },
|
||||
description: {
|
||||
en: "Preview: propose and confirm a cloud agent that opens a pull request.",
|
||||
zh: "预览版:提议并确认一个会提交拉取请求的云端 Agent。",
|
||||
},
|
||||
href: "/docs/computers",
|
||||
topicId: "computers",
|
||||
keywords: { en: "dispatch cloud agent daytona remote github cnb gitee", zh: "云端 派发 远程" },
|
||||
},
|
||||
{
|
||||
id: "troubleshoot",
|
||||
label: { en: "Fix a failing install or session", zh: "修复失败的安装或会话" },
|
||||
label: { en: "Fix a problem", zh: "排查问题" },
|
||||
description: {
|
||||
en: "Common incidents, diagnostics, the operations runbook, and Docker notes.",
|
||||
zh: "常见问题、诊断、运维手册和 Docker 说明。",
|
||||
en: "Diagnose in one command, then fix install, key, network, and stuck-turn problems.",
|
||||
zh: "用一条命令诊断,再解决安装、密钥、网络和回合卡住等问题。",
|
||||
},
|
||||
href: "/docs/troubleshooting",
|
||||
topicId: "troubleshooting",
|
||||
keywords: { en: "error crash doctor diagnose recover", zh: "错误 崩溃 诊断 恢复" },
|
||||
keywords: { en: "error crash doctor diagnose recover resume docker", zh: "错误 崩溃 诊断 恢复" },
|
||||
},
|
||||
{
|
||||
id: "trust",
|
||||
label: { en: "Understand what leaves your machine", zh: "了解哪些数据会离开你的机器" },
|
||||
label: { en: "See what leaves your machine", zh: "了解哪些数据会离开本机" },
|
||||
description: {
|
||||
en: "The hosted-provider boundary, the sandbox, telemetry field by field, and how to turn it off.",
|
||||
zh: "托管提供商边界、沙箱、逐项说明的遥测,以及如何关闭。",
|
||||
en: "What a provider receives, what usage counting sends, and how to turn it off.",
|
||||
zh: "提供商会收到什么、用量统计发送什么,以及如何关闭。",
|
||||
},
|
||||
href: "/docs/trust",
|
||||
topicId: "trust",
|
||||
|
||||
@@ -33,8 +33,10 @@ describe("Fleet is the canonical public surface", () => {
|
||||
const fleet = getTopic("fleet");
|
||||
expect(fleet?.hasPage).toBe(true);
|
||||
expect(fleet?.slug).toBe("fleet");
|
||||
expect(fleet?.label.en).toContain("Fleet");
|
||||
expect(fleet?.label.en).not.toContain("Pod");
|
||||
// The page is task-named ("Run a workflow"); the Fleet noun lives in
|
||||
// its description, never the retired Pod noun.
|
||||
expect(fleet?.description.en).toContain("Fleet");
|
||||
expect(`${fleet?.label.en} ${fleet?.description.en}`).not.toContain("Pod");
|
||||
expect(docTopicHref(fleet!, "en")).toBe("/en/docs/fleet");
|
||||
expect(DOC_TOPICS.filter((t) => t.id === "fleet")).toHaveLength(1);
|
||||
expect(DOC_TOPICS.map((t) => t.id)).not.toContain("pod");
|
||||
@@ -69,7 +71,7 @@ describe("Fleet is the canonical public surface", () => {
|
||||
const llms = buildLlmsTxt();
|
||||
expect(llms).toContain("/docs/fleet");
|
||||
expect(llms).not.toContain("/docs/pod");
|
||||
expect(llms).toContain("Fleet / Workflow");
|
||||
expect(llms).toContain(getTopic("fleet")!.label.en);
|
||||
});
|
||||
|
||||
it("resolves docs search on both nouns to the one Fleet page", () => {
|
||||
@@ -89,27 +91,25 @@ describe("Fleet is the canonical public surface", () => {
|
||||
});
|
||||
|
||||
it("keeps durable Fleet status separate from current-session workers", () => {
|
||||
const en = webText("lib/i18n/dictionaries/en/docs-fleet.ts");
|
||||
const zh = webText("lib/i18n/dictionaries/zh/docs-fleet.ts");
|
||||
const page = webText("app/[locale]/docs/fleet/page.tsx");
|
||||
expect(page).toContain('fleetWorkers: "/fleet workers"');
|
||||
for (const source of [en, zh]) {
|
||||
expect(source).toContain("{fleetStatusTui}");
|
||||
expect(source).toContain("{fleetStatusShell}");
|
||||
expect(source).toContain("{fleetWorkers}");
|
||||
expect(source).toContain("{subagents}");
|
||||
expect(page).toContain("getDocsFleet");
|
||||
for (const locale of ["en", "zh"]) {
|
||||
const source = webText(`lib/i18n/dictionaries/${locale}/docs-fleet.ts`);
|
||||
// Durable run history (shell) and this session's workers (TUI) are
|
||||
// named as two different surfaces in both languages.
|
||||
expect(source, locale).toContain("codewhale fleet status");
|
||||
expect(source, locale).toContain("/fleet workers");
|
||||
expect(source, locale).toContain("/subagents");
|
||||
}
|
||||
});
|
||||
|
||||
it("documents saved fleets separately from members and workers", () => {
|
||||
const page = webText("app/[locale]/docs/fleet/page.tsx");
|
||||
const en = webText("lib/i18n/dictionaries/en/docs-fleet.ts");
|
||||
const zh = webText("lib/i18n/dictionaries/zh/docs-fleet.ts");
|
||||
expect(page).toContain('fleetSaved: "/fleet saved"');
|
||||
for (const source of [en, zh]) {
|
||||
expect(source).toContain("{fleetSaved}");
|
||||
expect(source).toContain("/fleet setup");
|
||||
expect(source).toContain("/fleet");
|
||||
for (const locale of ["en", "zh"]) {
|
||||
const source = webText(`lib/i18n/dictionaries/${locale}/docs-fleet.ts`);
|
||||
expect(source, locale).toContain("/fleet saved");
|
||||
expect(source, locale).toContain("/fleet setup");
|
||||
expect(source, locale).toContain("codewhale workflow run");
|
||||
expect(source, locale).toContain("codewhale lane");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { existsSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
DICTIONARY_LOCALES,
|
||||
@@ -14,6 +15,7 @@ import {
|
||||
EN_DOCS_COMPUTERS,
|
||||
EN_DOCS_AUTH,
|
||||
EN_DOCS_TRUST,
|
||||
EN_DOCS_REVIEW,
|
||||
EN_COMPUTER_USE,
|
||||
EN_CHANGELOG,
|
||||
EN_DOCS_SHELL,
|
||||
@@ -40,6 +42,10 @@ import {
|
||||
getChangelog,
|
||||
getDocsShell,
|
||||
getDocsTroubleshooting,
|
||||
getDocsConfiguration,
|
||||
getDocsFleet,
|
||||
getDocsModes,
|
||||
getDocsReview,
|
||||
getDigest,
|
||||
getHome,
|
||||
getLegalPrivacy,
|
||||
@@ -275,6 +281,7 @@ describe("website dictionaries", () => {
|
||||
["docs-computers", getDocsComputers, EN_DOCS_COMPUTERS],
|
||||
["docs-auth", getDocsAuth, EN_DOCS_AUTH],
|
||||
["docs-trust", getDocsTrust, EN_DOCS_TRUST],
|
||||
["docs-review", getDocsReview, EN_DOCS_REVIEW],
|
||||
["changelog", getChangelog, EN_CHANGELOG],
|
||||
["legal-terms", getLegalTerms, EN_LEGAL_TERMS],
|
||||
["legal-privacy", getLegalPrivacy, EN_LEGAL_PRIVACY],
|
||||
@@ -312,55 +319,70 @@ describe("website dictionaries", () => {
|
||||
expect(getComputerUse("und")).toBe(EN_COMPUTER_USE);
|
||||
});
|
||||
|
||||
it("keeps the docs page lists structurally aligned", () => {
|
||||
for (const locale of [...DICTIONARY_LOCALES, "und"]) {
|
||||
expect(getDocsHooks(locale).events, `${locale} hook events`).toHaveLength(4);
|
||||
expect(
|
||||
getDocsTroubleshooting(locale).incidents,
|
||||
`${locale} triage entries`,
|
||||
).toHaveLength(5);
|
||||
expect(
|
||||
getDocsConstitution(locale).principles.map(([key]) => key),
|
||||
`${locale} constitution principles`,
|
||||
).toEqual(["userGlobal", "repoLocal", "runtime"]);
|
||||
expect(
|
||||
getDocsRuntimeApi(locale).entries.map(([key]) => key),
|
||||
`${locale} runtime entries`,
|
||||
).toEqual(["http", "mobile", "stdio", "web", "doctor", "acp", "exec"]);
|
||||
// The platform rows are keyed by their own translated name rather than a
|
||||
// code-owned key, so only the count is comparable across locales.
|
||||
expect(getDocsSandbox(locale).platforms, `${locale} sandbox platforms`).toHaveLength(4);
|
||||
// Role names are identifiers the page owns, so the keys are comparable
|
||||
// across locales rather than only the count.
|
||||
expect(
|
||||
getDocsSubagents(locale).roles.map(([key]) => key),
|
||||
`${locale} subagent roles`,
|
||||
).toEqual([
|
||||
"worker",
|
||||
"scout",
|
||||
"planner",
|
||||
"reviewer",
|
||||
"builder",
|
||||
"verifier",
|
||||
"consultant",
|
||||
"custom",
|
||||
]);
|
||||
it("keeps every docs task page aligned between English and Chinese", () => {
|
||||
const routeExists = (href: string) =>
|
||||
existsSync(new URL(`../../app/[locale]${href.split("#")[0]}/page.tsx`, import.meta.url));
|
||||
const internalLinks = (text: string) =>
|
||||
[...text.matchAll(/\]\((\/[^)\s]*)\)/g)].map((m) => m[1]);
|
||||
for (const [label, get] of [
|
||||
["docs-auth", getDocsAuth],
|
||||
["docs-computers", getDocsComputers],
|
||||
["docs-configuration", getDocsConfiguration],
|
||||
["docs-fleet", getDocsFleet],
|
||||
["docs-hooks", getDocsHooks],
|
||||
["docs-mcp", getDocsMcp],
|
||||
["docs-modes", getDocsModes],
|
||||
["docs-review", getDocsReview],
|
||||
["docs-runtime-api", getDocsRuntimeApi],
|
||||
["docs-sandbox", getDocsSandbox],
|
||||
["docs-subagents", getDocsSubagents],
|
||||
["docs-troubleshooting", getDocsTroubleshooting],
|
||||
["docs-trust", getDocsTrust],
|
||||
["docs-web", getDocsWeb],
|
||||
["docs-work", getDocsWork],
|
||||
] as const) {
|
||||
const en = get("en");
|
||||
const zh = get("zh");
|
||||
expect(zh, label).not.toBe(en);
|
||||
// Same sections, in the same order, with the same block shapes.
|
||||
expect(zh.sections.map((x) => x.id), label).toEqual(en.sections.map((x) => x.id));
|
||||
en.sections.forEach((section, i) => {
|
||||
const other = zh.sections[i].blocks;
|
||||
expect(other.map((b) => Object.keys(b)[0]), `${label}#${section.id}`).toEqual(
|
||||
section.blocks.map((b) => Object.keys(b)[0]),
|
||||
);
|
||||
section.blocks.forEach((block, j) => {
|
||||
const peer = other[j];
|
||||
// Commands are shown verbatim: a translation never edits one.
|
||||
if ("code" in block && "code" in peer) {
|
||||
expect(peer.code, `${label}#${section.id} code`).toBe(block.code);
|
||||
}
|
||||
if ("rows" in block && "rows" in peer) {
|
||||
expect(peer.rows.length, `${label}#${section.id} rows`).toBe(block.rows.length);
|
||||
if (block.codeTerms) {
|
||||
expect(peer.rows.map(([term]) => term)).toEqual(block.rows.map(([term]) => term));
|
||||
}
|
||||
}
|
||||
if ("steps" in block && "steps" in peer) {
|
||||
expect(peer.steps.length, `${label}#${section.id} steps`).toBe(block.steps.length);
|
||||
}
|
||||
if ("list" in block && "list" in peer) {
|
||||
expect(peer.list.length, `${label}#${section.id} list`).toBe(block.list.length);
|
||||
}
|
||||
});
|
||||
});
|
||||
expect(zh.next.map((n) => n.href), label).toEqual(en.next.map((n) => n.href));
|
||||
// Every internal link — inline or "next" — lands on a real page.
|
||||
for (const dict of [en, zh]) {
|
||||
const text = JSON.stringify(dict.sections);
|
||||
for (const href of [...internalLinks(text), ...dict.next.map((n) => n.href)]) {
|
||||
expect(routeExists(href), `${label}: ${href}`).toBe(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("carries every code-span token through the hooks intro for splitTokens()", () => {
|
||||
for (const locale of [...DICTIONARY_LOCALES, "und"]) {
|
||||
const parts = splitTokens(getDocsHooks(locale).configIntro);
|
||||
const tokens = parts.flatMap((part) => ("token" in part ? [part.token] : []));
|
||||
expect(tokens, `${locale} configIntro tokens`).toEqual([
|
||||
"hooksTable",
|
||||
"hooksCommand",
|
||||
"enabledKey",
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
it("carries every code-span token through the constitution and runtime-api copy", () => {
|
||||
it("carries every code-span token through the constitution copy", () => {
|
||||
const tokensOf = (template: string) =>
|
||||
splitTokens(template).flatMap((part) => ("token" in part ? [part.token] : []));
|
||||
for (const locale of [...DICTIONARY_LOCALES, "und"]) {
|
||||
@@ -375,90 +397,6 @@ describe("website dictionaries", () => {
|
||||
expect(tokensOf(constitution.authorityNote), `${locale} authorityNote`).toEqual([
|
||||
"configDocs",
|
||||
]);
|
||||
expect(tokensOf(getDocsRuntimeApi(locale).securityLead), `${locale} securityLead`).toEqual([
|
||||
"authToken",
|
||||
"runtimeTokenEnv",
|
||||
"legacyTokenEnv",
|
||||
"insecureFlag",
|
||||
"mobileFlag",
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
it("carries every code-span token through the subagents and mcp copy", () => {
|
||||
const tokensOf = (template: string) =>
|
||||
splitTokens(template).flatMap((part) => ("token" in part ? [part.token] : []));
|
||||
for (const locale of [...DICTIONARY_LOCALES, "und"]) {
|
||||
const subagents = getDocsSubagents(locale);
|
||||
expect(tokensOf(subagents.forkLead), `${locale} forkLead`).toEqual([
|
||||
"agentTool",
|
||||
"forkContext",
|
||||
]);
|
||||
expect(tokensOf(subagents.worktreeLead), `${locale} worktreeLead`).toEqual([
|
||||
"worktreeFlag",
|
||||
"branchPattern",
|
||||
"worktreeDir",
|
||||
"writeAuthority",
|
||||
"writeRoots",
|
||||
"exactFiles",
|
||||
"coordinationContracts",
|
||||
]);
|
||||
const mcp = getDocsMcp(locale);
|
||||
expect(tokensOf(mcp.overviewConfig), `${locale} mcp overviewConfig`).toEqual([
|
||||
"configPath",
|
||||
"legacyConfigPath",
|
||||
"configPathOption",
|
||||
"configEnvVar",
|
||||
"serversKey",
|
||||
]);
|
||||
expect(tokensOf(mcp.setupLead), `${locale} mcp setupLead`).toEqual([
|
||||
"initCommand",
|
||||
"mcpCommand",
|
||||
]);
|
||||
expect(tokensOf(mcp.toolsLead), `${locale} mcp toolsLead`).toEqual([
|
||||
"toolNamePattern",
|
||||
"gitServer",
|
||||
"statusTool",
|
||||
"gitStatusTool",
|
||||
]);
|
||||
expect(tokensOf(mcp.serverLead), `${locale} mcp serverLead`).toEqual([
|
||||
"serveMcp",
|
||||
"mcpServerCommand",
|
||||
"addSelfCommand",
|
||||
"serveHttp",
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
it("carries every code-span token through the sandbox and web copy", () => {
|
||||
const tokensOf = (template: string) =>
|
||||
splitTokens(template).flatMap((part) => ("token" in part ? [part.token] : []));
|
||||
for (const locale of [...DICTIONARY_LOCALES, "und"]) {
|
||||
// Two of these repeat, and the order is the sentence's, so this is a
|
||||
// stricter check than check-locales.mjs, which compares token sets.
|
||||
expect(tokensOf(getDocsSandbox(locale).policiesLead), `${locale} policiesLead`).toEqual([
|
||||
"sandboxMode",
|
||||
"readOnly",
|
||||
"workspaceWrite",
|
||||
"dangerFullAccess",
|
||||
"externalSandbox",
|
||||
"dangerFullAccess",
|
||||
"externalSandbox",
|
||||
]);
|
||||
const web = getDocsWeb(locale);
|
||||
expect(tokensOf(web.overviewLead), `${locale} web overviewLead`).toEqual([
|
||||
"webCommand",
|
||||
"loopbackHost",
|
||||
"defaultUrl",
|
||||
"portExample",
|
||||
]);
|
||||
expect(tokensOf(web.localLead), `${locale} localLead`).toEqual([
|
||||
"webCommand",
|
||||
"portFlag",
|
||||
"hostFlag",
|
||||
"mobileCommand",
|
||||
"httpFlag",
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,48 +1,125 @@
|
||||
import type { DocsAuthDict } from "../types";
|
||||
|
||||
/**
|
||||
* English reference dictionary for `app/[locale]/docs/auth/page.tsx`.
|
||||
* Statements trace to docs/CONFIGURATION.md ("codewhale login"), docs/
|
||||
* CODEWHALE_AGENT.md, and docs/PROVIDERS.md. Commands are code-owned
|
||||
* literals typeset by the page.
|
||||
* English reference dictionary for `app/[locale]/docs/auth/page.tsx`
|
||||
* ("Connect a provider"). Every command and lookup order is checked against
|
||||
* docs/INSTALL.md §8, docs/PROVIDERS.md (local models), and the CLI
|
||||
* definitions in crates/cli/src/lib.rs (`auth`, `login`) and
|
||||
* crates/cli/src/cloud.rs (`account`).
|
||||
*/
|
||||
export const docsAuth: DocsAuthDict = {
|
||||
metaTitle: "Account & keys · Codewhale Docs",
|
||||
metaTitle: "Connect a provider · Codewhale Docs",
|
||||
metaDescription:
|
||||
"Provider keys versus the optional Codewhale account: how each is set, where each is stored, and what needs no account at all.",
|
||||
"Give Codewhale a model: save a provider key, check which key is in use, or run a local model with no key. A Codewhale account is optional.",
|
||||
bodyClassName: "text-ink-soft leading-relaxed",
|
||||
overviewTitle: "Account & keys",
|
||||
overviewLead:
|
||||
"Codewhale holds two unrelated kinds of credential, and it helps to keep them apart. A provider key lets a model answer. A Codewhale account is optional and gates the managed surfaces — cloud agents and chat channels. Installing and running the local runtime needs neither an account nor a key.",
|
||||
credentials: [
|
||||
["Provider key (BYOK)", "Your own key for DeepSeek, OpenAI, Anthropic, OpenRouter, a local runtime, or any other route. Set with {authSet}. Required for model replies."],
|
||||
["Codewhale account", "A browser device-flow session started by {login}. Optional. Gates cloud agents and the Codewhale Agent surfaces; never required locally."],
|
||||
title: "Connect a provider",
|
||||
lede:
|
||||
"Codewhale needs a model to answer. Save a key for a hosted provider, or point Codewhale at a model running on your own machine. You pay the provider directly; no Codewhale account is involved.",
|
||||
sections: [
|
||||
{
|
||||
id: "save-key",
|
||||
title: "Save a provider key",
|
||||
blocks: [
|
||||
{
|
||||
p: "Get an API key from your provider, then save it. Codewhale asks for the key and does not echo it. DeepSeek is the default provider, so it is the example here.",
|
||||
},
|
||||
{
|
||||
code: `codewhale auth set --provider deepseek
|
||||
codewhale auth status --provider deepseek`,
|
||||
lang: "Terminal",
|
||||
},
|
||||
{
|
||||
p: "`auth status` names the source in use — config file, secret store, or environment variable — and shows only the last four characters. In a script, pipe the key in with `--api-key-stdin` instead of typing it.",
|
||||
},
|
||||
{
|
||||
p: "You can also connect from inside Codewhale: press F3 (or type `/provider`), choose a provider, paste the key, then pick a model.",
|
||||
},
|
||||
{
|
||||
note: "In v0.10.0, `auth set --provider deepseek` also switches your default model to DeepSeek Pro. Run `/model` if you want the faster, cheaper model back. Connecting through F3 keeps your current model.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "which-key",
|
||||
title: "Know which key is used",
|
||||
blocks: [
|
||||
{ p: "When a key is set in more than one place, the first match in this order wins:" },
|
||||
{
|
||||
steps: [
|
||||
"`--api-key` on the command line, for one run.",
|
||||
"`api_key` in `~/.codewhale/config.toml`.",
|
||||
"The secret store written by `codewhale auth set`.",
|
||||
"The provider's environment variable, such as `DEEPSEEK_API_KEY`.",
|
||||
],
|
||||
},
|
||||
{
|
||||
p: "Exporting a new environment variable therefore does not replace a key you saved earlier. If a rotated key keeps failing, run `auth status` to see which source is active, then save the new key or clear the stored one:",
|
||||
},
|
||||
{ code: "codewhale auth clear --provider deepseek", lang: "Terminal" },
|
||||
{
|
||||
p: "On Linux the secret store is a private file (mode 0600) under `~/.codewhale/secrets/`, not an OS keyring. `codewhale doctor --probe-api` makes one test call to confirm that the key and the network both work.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "other-providers",
|
||||
title: "Use another provider or a local model",
|
||||
blocks: [
|
||||
{
|
||||
p: "`codewhale auth list` shows every provider Codewhale knows and whether each one has a key. The pattern is the same for all of them: `codewhale auth set --provider <name>`, or that provider's environment variable.",
|
||||
},
|
||||
{
|
||||
p: "Local runners — Ollama, vLLM, and SGLang — need no key by default, and your prompts stay on your machine. Start the runner, then choose it:",
|
||||
},
|
||||
{
|
||||
code: `codewhale auth list
|
||||
codewhale --provider ollama --model <model-tag>`,
|
||||
lang: "Terminal",
|
||||
},
|
||||
{
|
||||
p: "The [models page](/models) lists providers, local setups, and how to switch models mid-session.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "account",
|
||||
title: "Sign in to a Codewhale account (optional)",
|
||||
blocks: [
|
||||
{
|
||||
p: "A provider key and a Codewhale account are different things. The account is for account features only, such as cloud agents and continuing a session from the web app. Installing Codewhale and working locally never need one.",
|
||||
},
|
||||
{
|
||||
rows: [
|
||||
["codewhale login", "Sign in through your browser with a one-time code."],
|
||||
["codewhale account status", "Show which account this profile is signed in to."],
|
||||
["codewhale account logout", "Remove this profile's session."],
|
||||
["codewhale account keys list", "List provider keys saved in your account. Values are never shown."],
|
||||
],
|
||||
codeTerms: true,
|
||||
},
|
||||
{
|
||||
p: "`codewhale login` does not accept provider keys; those always go through `codewhale auth set`. The session is kept in your operating system's credential manager when one is available, and in the private Codewhale secrets file otherwise — for example over SSH or in a container.",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
providerTitle: "Provider keys",
|
||||
providerLead:
|
||||
"{authSet} saves the key for one provider. A process-level {apiKeyFlag} still wins for a single run. {login} is not a provider-key command: provider credentials are configured exclusively through {authSet}.",
|
||||
accountTitle: "The account session",
|
||||
accountLead:
|
||||
"{login} is the same browser device flow as {accountLogin}. The session is scoped to the selected {profile}, and the older {cloud} spelling remains an alias.",
|
||||
accountCommands: [
|
||||
["codewhale login", "Sign in through the browser device flow."],
|
||||
["codewhale account status", "Show the session for the selected profile."],
|
||||
["codewhale account logout", "Remove that session."],
|
||||
next: [
|
||||
{
|
||||
href: "/docs/guide",
|
||||
label: "Start your first task",
|
||||
note: "Open Codewhale in a project and give it something concrete to do.",
|
||||
},
|
||||
{
|
||||
href: "/docs/modes",
|
||||
label: "Set modes and approvals",
|
||||
note: "Decide whether Codewhale asks before it runs commands.",
|
||||
},
|
||||
{
|
||||
href: "/docs/trust",
|
||||
label: "See what leaves your machine",
|
||||
note: "What the provider receives, what stays local, and how to turn usage counting off.",
|
||||
},
|
||||
],
|
||||
storageTitle: "Where sessions live",
|
||||
storageLead:
|
||||
"Account sessions prefer the operating system's credential manager and fall back automatically to the private, 0600 Codewhale secrets file when no credential manager is available — headless hosts, SSH, containers.",
|
||||
vaultTitle: "The account's own key vault",
|
||||
vaultLead:
|
||||
"{keys} manages the signed-in account's bring-your-own-key vault without ever displaying a secret value.",
|
||||
portableTitle: "Moving to another machine",
|
||||
portableLead:
|
||||
"{portable} writes a secret-free bundle: credential and machine-specific keys are dropped, never replaced with a redacted placeholder, so the file is safe to carry and nothing in it can sign in as you.",
|
||||
appTitle: "Sign in on the web",
|
||||
appLead:
|
||||
"The hosted Codewhale app carries the same account. Sign in or register there; the local runtime keeps working without it.",
|
||||
appSignIn: "Sign in →",
|
||||
appRegister: "Register →",
|
||||
sourceNote:
|
||||
"Source documents: docs/CONFIGURATION.md, docs/CODEWHALE_AGENT.md, docs/PROVIDERS.md · Update docs-map.ts when changing.",
|
||||
"Source documents: docs/INSTALL.md §8, docs/PROVIDERS.md, docs/CODEWHALE_AGENT.md · Update docs-map.ts when changing.",
|
||||
};
|
||||
@@ -1,67 +1,109 @@
|
||||
import type { DocsComputersDict } from "../types";
|
||||
|
||||
/**
|
||||
* English reference dictionary for `app/[locale]/docs/computers/page.tsx`.
|
||||
* Every statement is taken from docs/DAYTONA_CLOUD_DISPATCH.md and
|
||||
* docs/CODEWHALE_AGENT.md; the commands, env names, and job states are
|
||||
* code-owned literals typeset by the page.
|
||||
* English reference dictionary for `app/[locale]/docs/computers/page.tsx`
|
||||
* ("Send a task to the cloud"). Checked against docs/DAYTONA_CLOUD_DISPATCH.md
|
||||
* and `DispatchArgs` / `run_with` in crates/cli/src/dispatch.rs. The sandbox
|
||||
* vendor is Codewhale-operated infrastructure and is deliberately not named
|
||||
* in user copy (DAYTONA_CLOUD_DISPATCH.md: no provider brand on any surface).
|
||||
* The live network path is not yet smoke-tested, so the page says preview.
|
||||
*/
|
||||
export const docsComputers: DocsComputersDict = {
|
||||
metaTitle: "Cloud computers · Codewhale Docs",
|
||||
metaTitle: "Send a task to the cloud · Codewhale Docs",
|
||||
metaDescription:
|
||||
"How a local Codewhale session proposes, confirms, and tracks a cloud agent on a Daytona computer — explicit forges, fail-closed credentials, and what is not built yet.",
|
||||
"Hand a coding task to a Codewhale cloud agent that works on a branch and opens a pull request — proposed first, started only when you confirm.",
|
||||
bodyClassName: "text-ink-soft leading-relaxed",
|
||||
overviewTitle: "Cloud computers",
|
||||
overviewLead:
|
||||
"A local session can offload a coding task to a cloud computer the way an editor sends a cloud agent: the remote job raises a branch and is meant to open a pull request against an explicit forge. Local stays responsive. Spend and push never happen silently — a proposal is written first, and nothing creates a sandbox or pushes a branch until you confirm it.",
|
||||
proposeTitle: "Propose, then confirm",
|
||||
proposeLead:
|
||||
"The first command writes a proposal and exits. The second confirms it by id. The TUI has the same two steps as slash commands, and the {cloudAgent} spelling is an alias of {dispatch}.",
|
||||
jobsLead:
|
||||
"Cloud jobs are first-class on the existing jobs surface as {kind}. List, show, and cancel them from the TUI or the CLI:",
|
||||
remotesTitle: "Explicit forges",
|
||||
remotesLead:
|
||||
"A remote is never assumed to be GitHub. A remote named after a forge is that forge; any other remote is classified by its URL host. If more than one forge is present, pass {remoteFlag}.",
|
||||
remotes: [
|
||||
["github · cnb · gitee (by remote name)", "that forge, whatever the URL"],
|
||||
["origin or other → github.com", "github"],
|
||||
["origin or other → cnb.cool", "cnb"],
|
||||
["origin or other → gitee.com", "gitee"],
|
||||
title: "Send a task to the cloud",
|
||||
lede:
|
||||
"A cloud agent takes a task off your machine: it works in a fresh cloud sandbox, pushes a branch, and opens a pull request while you keep working locally. Nothing starts, costs money, or pushes until you confirm it.",
|
||||
sections: [
|
||||
{
|
||||
id: "status",
|
||||
title: "Know what is ready",
|
||||
blocks: [
|
||||
{
|
||||
note: "Cloud agents are a preview. The full lifecycle is covered by offline tests, but the live path — a real sandbox and a real pull request on each forge — has not been verified end to end yet. Private repositories are not supported yet.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "before",
|
||||
title: "Before you start",
|
||||
blocks: [
|
||||
{
|
||||
list: [
|
||||
"Sign in to your Codewhale account with `codewhale login`. Without it, a task can be proposed but not confirmed.",
|
||||
"Make an account API key available as `CODEWHALE_API_KEY`, so the agent in the sandbox runs as your account. Without it, confirming is refused before anything is spent.",
|
||||
"For GitHub, be signed in to the `gh` command-line tool; Codewhale uses that login to open the pull request.",
|
||||
],
|
||||
},
|
||||
{ code: "codewhale dispatch --status", lang: "Terminal" },
|
||||
{
|
||||
p: "`--status` shows the forges found in your git remotes and whether the required credentials are present. It never prints a secret.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "send",
|
||||
title: "Propose, then confirm",
|
||||
blocks: [
|
||||
{
|
||||
code: `codewhale dispatch "fix the flaky login test and open a PR" --remote github
|
||||
codewhale dispatch --confirm cloud_<id>`,
|
||||
lang: "Terminal",
|
||||
},
|
||||
{
|
||||
p: "The first command only writes a proposal and prints its id. The second starts it. Codewhale may propose a cloud task on its own, but it never confirms one. In a session, use `/dispatch <task>` and `/dispatch confirm <id>`.",
|
||||
},
|
||||
{
|
||||
p: "Once confirmed, the agent clones the repository into a new sandbox, does the work, pushes a new branch (never a force-push), and opens the pull request. The sandbox is deleted when the job finishes, fails, or is cancelled.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "track",
|
||||
title: "Track or cancel a job",
|
||||
blocks: [
|
||||
{
|
||||
code: `codewhale dispatch --list
|
||||
codewhale dispatch --show cloud_<id>
|
||||
codewhale dispatch --cancel cloud_<id>`,
|
||||
lang: "Terminal",
|
||||
},
|
||||
{
|
||||
p: "Cloud jobs also appear in `/jobs`. A job shows its progress, the branch, the pull request link once one exists, and how many minutes it ran — a runtime figure, not a bill. Cancelling tears the sandbox down right away.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "forge",
|
||||
title: "Choose where the pull request goes",
|
||||
blocks: [
|
||||
{
|
||||
p: "Codewhale supports GitHub, CNB, and Gitee, and never assumes `origin` is GitHub. A remote named `github`, `cnb`, or `gitee` is that forge; any other remote is identified by its host. If your repository has more than one forge, pass `--remote`.",
|
||||
},
|
||||
{
|
||||
p: "If a pull request cannot be opened — for example, a forge token is missing — the job is marked failed after the push and says that no pull request was opened. It never reports a link it does not have.",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
enableTitle: "Enable a Daytona computer",
|
||||
enableLead:
|
||||
"Credentials live in the process environment or in the Codewhale secret store — never in config.toml or models.toml, and never committed.",
|
||||
enableSteps: [
|
||||
["Create an API key", "In the Daytona dashboard under API keys."],
|
||||
[
|
||||
"Export it for the session",
|
||||
"{apiKey}, optionally {apiUrl} for a non-default endpoint.",
|
||||
],
|
||||
[
|
||||
"Or store it once",
|
||||
"In the Codewhale secret slot {slot} (OS keyring or the $CODEWHALE_HOME secrets file). The {alias} alias is also accepted.",
|
||||
],
|
||||
],
|
||||
cliNote:
|
||||
"An installed daytona CLI is not a credential. {status} and a bare {bare} report CLI presence separately from credential presence.",
|
||||
rulesTitle: "Fail-closed rules",
|
||||
rules: [
|
||||
["No confirm", "A {proposed} job is written; the command exits success; Daytona is not called; nothing is pushed."],
|
||||
["Confirm, no credentials", "A {refused} job is written; the command exits failure; no sandbox exists."],
|
||||
[
|
||||
"Confirm with credentials",
|
||||
"A Daytona sandbox is created, labelled with the job id and forge. This slice does not claim a GitHub, CNB, or Gitee PR URL, and a missing forge token fails closed the same way.",
|
||||
],
|
||||
],
|
||||
membershipTitle: "Who can dispatch",
|
||||
membershipLead:
|
||||
"Managed Agent surfaces authenticate to the same Codewhale membership — the {login} account session. Membership gates cloud agents, not local dispatch: `codewhale dispatch` with Daytona and forge credentials needs no account. Managed cloud agents do not name the infrastructure behind them; local dispatch names Daytona only because you bring your own Daytona key. Installing or running the local runtime needs no account at all.",
|
||||
leftoverTitle: "Not built yet",
|
||||
leftover: [
|
||||
["Live watch", "A log tail of a running sandbox."],
|
||||
["Cancel that tears down", "Cancelling a paid Daytona sandbox from the job surface."],
|
||||
["Auto-decide", "Codewhale may propose a dispatch; it must not confirm its own proposal."],
|
||||
["The remote runner", "The agent that actually raises the branch and opens the pull request."],
|
||||
next: [
|
||||
{
|
||||
href: "/docs/auth",
|
||||
label: "Connect a provider",
|
||||
note: "Sign in to your Codewhale account and manage keys.",
|
||||
},
|
||||
{
|
||||
href: "/docs/review",
|
||||
label: "Review what changed",
|
||||
note: "Review the agent's pull request before you merge it.",
|
||||
},
|
||||
{
|
||||
href: "/docs/fleet",
|
||||
label: "Run a workflow",
|
||||
note: "Run longer, multi-step work on your own machine instead.",
|
||||
},
|
||||
],
|
||||
sourceNote:
|
||||
"Source documents: docs/DAYTONA_CLOUD_DISPATCH.md, docs/CODEWHALE_AGENT.md · Update docs-map.ts when changing.",
|
||||
|
||||
@@ -1,26 +1,111 @@
|
||||
import type { DocsConfigurationDict } from "../types";
|
||||
|
||||
/**
|
||||
* English reference dictionary for `app/[locale]/docs/configuration/page.tsx`
|
||||
* ("Change settings"). Checked against `ConfigCommand` and the `--set`
|
||||
* runtime overrides in crates/cli/src/lib.rs, and docs/CONFIGURATION.md
|
||||
* (config path, `/config audit`, per-project overlay, legacy paths).
|
||||
*/
|
||||
export const docsConfiguration: DocsConfigurationDict = {
|
||||
metaTitle: "Configuration · Codewhale Docs",
|
||||
metaTitle: "Change settings · Codewhale Docs",
|
||||
metaDescription:
|
||||
"Where config.toml is read from, the per-project overlay, credential precedence, and legacy path migration.",
|
||||
"Find Codewhale's config file, change a setting from the session or the shell, try one for a single run, and see what a repository is allowed to override.",
|
||||
bodyClassName: "text-ink-soft leading-relaxed",
|
||||
overviewTitle: "Configuration",
|
||||
overviewLead:
|
||||
"Codewhale reads its configuration from ~/.codewhale/config.toml (the legacy ~/.deepseek/config.toml is still read as a fallback). The --config flag and the CODEWHALE_CONFIG_PATH environment variable can point elsewhere; --config wins when both are set, and environment variable overrides are applied after the file is loaded.",
|
||||
auditLead:
|
||||
"Inside the TUI, {auditCommand} shows which documented keys can change in the current session, which can also be persisted, and which stay file-only or restart-only — treat its “Command / reason” column as the source of truth before editing by hand.",
|
||||
overlayTitle: "Per-project overlay",
|
||||
overlayLead:
|
||||
"When a workspace contains a regular-file <workspace>/.codewhale/config.toml, the safe values it declares are merged on top of the global config (legacy <workspace>/.deepseek/config.toml files are still read when the Codewhale path is absent; symlinked project configs are rejected). This lets a repository suggest a model or tighten the local safety posture without touching the user's global config. Pass --no-project-config to skip the overlay for one launch.",
|
||||
overlayLimits:
|
||||
"The overlay is intentionally narrow: it supports model, reasoning_effort, approval_policy and sandbox_mode (tightening values only), notes_path, max_subagents (clamped to 1..=128), and allow_shell (false applies, true is ignored). Credentials, endpoints, provider selection, MCP config, hooks, skills, and instructions = [...] stay user-global — a repo-local config.toml that declares api_key, base_url, or provider is ignored, so a cloned repository cannot pick arbitrary local files into the prompt.",
|
||||
credentialsTitle: "Credential lookup",
|
||||
credentialsLead:
|
||||
"For the active provider, the API key resolves in this order, first match wins: the route's own auth contract (OAuth routes use their consented token; auth_mode = \"none\" sends no key), then an explicit {apiKey}, then the config file api_key, then an api_key_env binding, then the secret store written by codewhale auth set (a file under ~/.codewhale/secrets/ by default; the OS keyring only when CODEWHALE_SECRET_BACKEND=system), then the provider's own environment variable, which is only sent to that provider's official endpoint. {authStatus} inspects the active provider's config file, secret-store backend, environment variable, winning source, and last-four label without printing the key itself. Hosted, generic OpenAI-compatible, self-hosted, or native Anthropic routes are selected with {providerConfig} or {providerFlag}; the full registry lives on the Models & providers page and in docs/PROVIDERS.md.",
|
||||
legacyTitle: "Legacy .deepseek/ paths",
|
||||
legacyLead:
|
||||
"Codewhale was renamed from DeepSeek-TUI. To avoid breaking existing installs, the runtime reads state from the new ~/.codewhale/ location but falls back to ~/.deepseek/ when only the legacy directory exists, and always writes to ~/.codewhale/ — read-with-fallback, write-to-new. State-dir resolution is consolidated in resolve_state_dir / ensure_state_dir in crates/config/src/lib.rs, and every legacy path reference carries an audited keep decision.",
|
||||
title: "Change settings",
|
||||
lede:
|
||||
"Most settings can be changed without opening a file: from the session with `/config`, or from the shell with `codewhale config`. This page shows where settings live, how to change them, and what a repository can and cannot change for you.",
|
||||
sections: [
|
||||
{
|
||||
id: "file",
|
||||
title: "Find the config file",
|
||||
blocks: [
|
||||
{
|
||||
p: "Your settings live in `~/.codewhale/config.toml`. Print the exact path, or open the file in your editor:",
|
||||
},
|
||||
{ code: "codewhale config path\ncodewhale config edit", lang: "Terminal" },
|
||||
{
|
||||
p: "To use a different file, pass `--config <path>` or set `CODEWHALE_CONFIG_PATH`; the flag wins when both are set. Codewhale was once called DeepSeek-TUI: if only an old `~/.deepseek/` folder exists, it is still read, and new settings are always written to `~/.codewhale/`.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "change",
|
||||
title: "Change a setting",
|
||||
blocks: [
|
||||
{
|
||||
p: "Inside a session, `/config` opens the settings editor and `/settings` opens the settings screen. `/config audit` lists which settings can change in this session, which can be saved, and which only take effect after a restart — check it before editing by hand.",
|
||||
},
|
||||
{ p: "From the shell:" },
|
||||
{
|
||||
code: `codewhale config get tools
|
||||
codewhale config set telemetry false
|
||||
codewhale config unset telemetry
|
||||
codewhale config dump # the settings in effect, secrets redacted
|
||||
codewhale config doctor # unknown keys, empty secrets, malformed URLs`,
|
||||
lang: "Terminal",
|
||||
},
|
||||
{
|
||||
p: "`config set` handles its named keys; for anything else it tells you which TOML table to edit instead of guessing.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "one-run",
|
||||
title: "Try a setting for one run",
|
||||
blocks: [
|
||||
{
|
||||
p: "`--set KEY=VALUE` changes a setting for one launch and saves nothing. It accepts `provider`, `model`, `verbosity`, `approval_policy`, `sandbox_mode`, and `telemetry`, and can be repeated.",
|
||||
},
|
||||
{
|
||||
code: 'codewhale --set model=deepseek-v4-pro --set sandbox_mode=read-only',
|
||||
lang: "Terminal",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "project",
|
||||
title: "Share settings with a repository",
|
||||
blocks: [
|
||||
{
|
||||
p: "A repository can include `.codewhale/config.toml` to suggest settings to everyone who works in it. Only a few keys are honored, and safety settings can only get stricter:",
|
||||
},
|
||||
{
|
||||
rows: [
|
||||
["model", "The default model for this repository."],
|
||||
["reasoning_effort", "For example `\"high\"` for a complex codebase."],
|
||||
["approval_policy, sandbox_mode", "Only values stricter than yours."],
|
||||
["allow_shell", "`false` turns shell commands off; `true` is ignored."],
|
||||
["max_subagents", "Fewer parallel sub-agents, from 1 to 128."],
|
||||
["notes_path", "Keep notes in the repository."],
|
||||
],
|
||||
codeTerms: true,
|
||||
},
|
||||
{
|
||||
p: "Keys, endpoints, provider choice, MCP servers, hooks, skills, and extra instruction files always come from your own config, so a cloned repository cannot point Codewhale at its own server or files. Start with `--no-project-config` to ignore a repository's file for one launch.",
|
||||
},
|
||||
{
|
||||
p: "Project instructions — how an agent should work in this repository — belong in `AGENTS.md` instead. Run `/init` to create one.",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
next: [
|
||||
{
|
||||
href: "/docs/auth",
|
||||
label: "Connect a provider",
|
||||
note: "Save a key and see which one Codewhale is using.",
|
||||
},
|
||||
{
|
||||
href: "/docs/modes",
|
||||
label: "Set modes and approvals",
|
||||
note: "The settings most people change first.",
|
||||
},
|
||||
{
|
||||
href: "/docs/hooks",
|
||||
label: "Run commands on events",
|
||||
note: "Add your own scripts to the `[hooks]` table.",
|
||||
},
|
||||
],
|
||||
sourceNote:
|
||||
"Source documents: docs/CONFIGURATION.md, docs/LEGACY_PATHS.md · Update docs-map.ts when changing.",
|
||||
};
|
||||
@@ -1,26 +1,157 @@
|
||||
import type { DocsFleetDict } from "../types";
|
||||
|
||||
/**
|
||||
* English reference dictionary for `app/[locale]/docs/fleet/page.tsx`
|
||||
* ("Run a workflow"). Checked against `WorkflowCommand` and `LaneArgs` in
|
||||
* crates/cli/src/lib.rs, the Lane runtime backends in
|
||||
* crates/lane/src/runtime.rs, `FleetCommand` in crates/tui/src/lib.rs, and
|
||||
* docs/FLEET_WORKFLOW_TUTORIAL.md / docs/WORKFLOW_AUTHORING.md.
|
||||
*/
|
||||
export const docsFleet: DocsFleetDict = {
|
||||
metaTitle: "Fleet & Workflow · Codewhale Docs",
|
||||
metaTitle: "Run a workflow · Codewhale Docs",
|
||||
metaDescription:
|
||||
"The durable Agent roster and member-selection layer, plus the optional Workflow orchestration overlay.",
|
||||
"Save roles and models in a Fleet, write a repeatable Workflow, run it as a Lane you can watch and stop, and run batches of tasks with durable workers.",
|
||||
bodyClassName: "text-ink-soft leading-relaxed",
|
||||
overviewTitle: "Fleet & Workflow",
|
||||
overviewLead:
|
||||
"Fleet is the durable roster: who is available and which member is selected. It is not an execution or authority engine. Runtime launches and tracks the selected member as a headless codewhale exec run, owns retry and remote placement, and writes the durable receipts and ledger projection. The ledger file, saved rosters, config tables, and the workflow --fleet flag share the Fleet name.",
|
||||
runTitle: "Run a fleet",
|
||||
runLead:
|
||||
"The Runtime's fleet-run projection lives in the workspace's .codewhale/fleet.jsonl ledger, with worker logs under .codewhale/fleet/. codewhale fleet resume <run-id> asks Runtime to replay the ledger and reconcile stale leases; it is idempotent after a manager exit, laptop sleep, or runtime restart.",
|
||||
statusLead:
|
||||
"The TUI command {fleetStatusTui} and the shell command {fleetStatusShell} read the same durable fleet ledger. Use {fleetWorkers} (or {subagents}) for the sub-agents attached only to the current interactive session.",
|
||||
profilesTitle: "Saved fleets, roles, and /fleet setup",
|
||||
profilesLead:
|
||||
"{fleetSaved} opens the picker for named saved fleets; bare /fleet opens the selected fleet's member roster. In v0.9.11, /fleet setup edits the selected named fleet. With no named fleet selected, it opens profile setup: choose a role and model, adjust optional thinking settings, then review before saving. Profiles live in project scope (.codewhale/agents/<role>.toml) or personal scope ($CODEWHALE_HOME/agents/<role>.toml); a same-id project profile wins. Runtime separately owns trust, filesystem/network reach, secrets, approvals, sandboxing, and tools, so profile storage scope never widens execution authority.",
|
||||
workflowTitle: "Workflow orchestration",
|
||||
workflowLead:
|
||||
"Ordinary multi-agent work does not need Workflow: send normal messages in Operate and let Codewhale prefer background workers when parallelism, isolation, or duration makes delegation useful. Use Workflow when ordered phases, gates, shared budgets, replay, or deterministic fan-in matter. A Workflow script coordinates only: it selects fleet members but has no filesystem or shell; Runtime launches the real workers under live authority policy. Scripts use a declarative compile-only JS subset that lowers to a typed WorkflowSpec validated and executed by Rust; import, fetch, process, eval, and async/await are rejected.",
|
||||
workflowLimits:
|
||||
"Default validation bounds: up to 1,000 worker agents per Workflow run, Workflow IR structural nesting no deeper than 5, loops must declare max_iterations, and dynamic expand nodes must declare max_children plus a template. Runtime child delegation is a separate execution budget: it defaults to 3 levels and has an opt-in hard ceiling of 8. These are population and shape limits, not launch concurrency: Runtime admits at most 16 live workers for one run and queues the rest. Omitted or zero max_steps stays unbounded; only a positive value adds a model-turn ceiling.",
|
||||
title: "Run a workflow",
|
||||
lede:
|
||||
"For most multi-step work you only need to ask: in Operate, Codewhale plans the steps and runs independent ones in parallel. Write a Workflow when you want the same ordered plan every time — phases, parallel branches, and a summary — with a record of each run.",
|
||||
sections: [
|
||||
{
|
||||
id: "fleet",
|
||||
title: "Save roles in a Fleet",
|
||||
blocks: [
|
||||
{
|
||||
p: "Your Fleet is the list of roles Codewhale can hand work to, and the model each role uses. Set it up once inside a session:",
|
||||
},
|
||||
{ code: "/fleet setup\n/fleet\n/fleet saved", lang: "Codewhale" },
|
||||
{
|
||||
p: "`/fleet setup` walks you through a role, its model (or “use the session's model”), and where to save it: this project, or your personal profile for every repository. You review the exact file before it is written. `/fleet` shows the members of the selected Fleet, and `/fleet saved` switches between named Fleets.",
|
||||
},
|
||||
{
|
||||
p: "A Fleet only chooses who does the work. What a worker may read, write, or run still comes from your workspace trust, [approval setting](/docs/modes), and sandbox.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "write",
|
||||
title: "Write a workflow",
|
||||
blocks: [
|
||||
{
|
||||
p: "A Workflow is a JavaScript file in your repository's `workflows/` folder. It describes steps; it does not do the work itself. This one reviews two areas in parallel, then combines the findings. Save it as `workflows/docs_readiness.workflow.js`:",
|
||||
},
|
||||
{
|
||||
code: `export default workflow({
|
||||
"id": "docs-readiness",
|
||||
"goal": "Review the docs and code for gaps, then summarize the next edit",
|
||||
"nodes": [
|
||||
{
|
||||
"branch": {
|
||||
"id": "parallel-review",
|
||||
"parallel": true,
|
||||
"children": [
|
||||
{ "agent": { "id": "code-review", "prompt": "Inspect src/ for undocumented behavior.",
|
||||
"agent_type": "review", "mode": "read_only", "file_scope": ["src"] } },
|
||||
{ "agent": { "id": "docs-review", "prompt": "Inspect docs/ for stale or missing steps.",
|
||||
"agent_type": "review", "mode": "read_only", "file_scope": ["docs"] } }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"reduce": {
|
||||
"id": "summary",
|
||||
"inputs": ["code-review", "docs-review"],
|
||||
"prompt": "Combine the findings into the safest next edit."
|
||||
}
|
||||
}
|
||||
]
|
||||
});`,
|
||||
lang: "workflows/docs_readiness.workflow.js",
|
||||
},
|
||||
{
|
||||
p: "Steps can be `agent`, `branch`, `sequence`, `reduce`, `loop_until`, `cond`, `expand`, and `teacher_review`. A workflow file has no file, shell, or network access of its own, and `import`, `fetch`, `eval`, and `async` are rejected. The agents it starts do the real work, under your normal permissions.",
|
||||
},
|
||||
{
|
||||
note: "One run can start up to 1,000 agents, with at most 16 working at once; the rest wait for a slot. Loops must declare `max_iterations`.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "run",
|
||||
title: "Run it",
|
||||
blocks: [
|
||||
{
|
||||
code: `codewhale workflow run docs-readiness --runtime inline
|
||||
codewhale workflow run docs-readiness --goal "prepare the 1.2 release" --verify`,
|
||||
lang: "Terminal",
|
||||
},
|
||||
{
|
||||
p: "Codewhale finds `workflows/docs_readiness.workflow.js` from the name, checks it, and starts it. `--runtime inline` runs it in this terminal. The default, `tmux`, runs it in a detached tmux session that keeps going after you close the terminal. `--verify` runs the verification gates after a successful finish, and `--fleet <name>` uses a named Fleet instead of the built-in roles.",
|
||||
},
|
||||
{
|
||||
p: "To keep the work off your checkout, add `--worktree-repo . --branch <name>`: the run gets its own git worktree and branch.",
|
||||
},
|
||||
{
|
||||
p: "Inside a session, `/workflow` starts a workflow and `/workflows` lists or cancels the runs in that session.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "watch",
|
||||
title: "Watch and stop a run",
|
||||
blocks: [
|
||||
{ p: "Each run is a Lane. Lanes are saved to disk, so you can check on them from any terminal:" },
|
||||
{
|
||||
code: `codewhale lane list
|
||||
codewhale lane status <lane-id>
|
||||
codewhale lane logs <lane-id>
|
||||
codewhale lane attach <lane-id>
|
||||
codewhale lane interrupt <lane-id>`,
|
||||
lang: "Terminal",
|
||||
},
|
||||
{
|
||||
p: "`lane list`, `lane status`, and `lane interrupt` accept `--json` and print a machine-readable receipt. In a session, `/lane` offers the same controls with the same results.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "batch",
|
||||
title: "Run a batch of tasks",
|
||||
blocks: [
|
||||
{
|
||||
p: "When you have a list of separate tasks rather than one plan, write them as a task file and run them as a Fleet run. Each task names its goal, its role, and the paths it may write. [The tutorial](https://github.com/Hmbown/CodeWhale/blob/main/docs/FLEET_WORKFLOW_TUTORIAL.md) has a complete `tasks.json`.",
|
||||
},
|
||||
{
|
||||
code: `codewhale fleet init
|
||||
codewhale fleet run tasks.json --max-workers 4
|
||||
codewhale fleet status
|
||||
codewhale fleet logs <worker-id>
|
||||
codewhale fleet resume <run-id>
|
||||
codewhale fleet stop --all`,
|
||||
lang: "Terminal",
|
||||
},
|
||||
{
|
||||
p: "`fleet status` counts queued, running, finished, and failed work from this workspace's run record. `fleet resume` picks a run back up after the laptop slept or the manager exited, without starting a new one. For the agents attached to your current session only, use `/fleet workers` (or `/subagents`).",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
next: [
|
||||
{
|
||||
href: "/docs/subagents",
|
||||
label: "Run agents in parallel",
|
||||
note: "Hand independent pieces of one task to sub-agents without writing a workflow.",
|
||||
},
|
||||
{
|
||||
href: "/docs/review",
|
||||
label: "Review what changed",
|
||||
note: "Check the diff a run produced and get a review before you push.",
|
||||
},
|
||||
{
|
||||
href: "/docs/vocabulary",
|
||||
label: "Product terms",
|
||||
note: "Fleet, Workflow, Lane, and Runtime, each in one sentence.",
|
||||
},
|
||||
],
|
||||
sourceNote:
|
||||
"Source documents: docs/FLEET.md, docs/WORKFLOW_AUTHORING.md · Update docs-map.ts when changing.",
|
||||
"Source documents: docs/FLEET.md, docs/FLEET_WORKFLOW_TUTORIAL.md, docs/WORKFLOW_AUTHORING.md · Update docs-map.ts when changing.",
|
||||
};
|
||||
@@ -1,22 +1,21 @@
|
||||
import type { DocsGuideDict } from "../types";
|
||||
|
||||
/**
|
||||
* English reference dictionary for the docs "Getting started" page.
|
||||
* Copy moved verbatim from `app/[locale]/docs/guide/page.tsx` — any wording
|
||||
* change belongs in its own commit, never mixed into a structural move.
|
||||
* English reference dictionary for `/docs/guide` ("Start your first task").
|
||||
* The four steps themselves live in `web/lib/content/getting-started.ts`,
|
||||
* shared with the homepage.
|
||||
*/
|
||||
export const docsGuide: DocsGuideDict = {
|
||||
metaTitle: "Getting started · Codewhale Docs",
|
||||
metaTitle: "Start your first task · Codewhale Docs",
|
||||
metaDescription:
|
||||
"Install Codewhale, connect a model, and start your first task. Add a Fleet when you want a roster of models and roles.",
|
||||
"Install Codewhale, connect a model, and give it a task in your project. Add a Fleet later if you want several models and roles.",
|
||||
bodyClassName: "text-ink-soft leading-relaxed",
|
||||
overviewTitle: "Getting started",
|
||||
overviewTitle: "Start your first task",
|
||||
overviewLead:
|
||||
"Install Codewhale, connect your model, and give it a task. Fleet setup is optional.",
|
||||
"Four steps take you from nothing installed to a finished first task. Each step links to the page with the details; the Fleet step is optional.",
|
||||
sessionTitle: "Watch a real session",
|
||||
sessionLead:
|
||||
"Follow a task from the first request to the finished result.",
|
||||
nextTitle: "Where next",
|
||||
sessionLead: "Follow a task from the first request to the finished result.",
|
||||
nextTitle: "Next",
|
||||
sourceNote:
|
||||
"For more detail, see the user guide and keyboard shortcuts in the documentation.",
|
||||
"Source documents: docs/GUIDE.md, docs/INSTALL.md, docs/KEYBINDINGS.md · Update docs-map.ts when changing.",
|
||||
};
|
||||
@@ -1,41 +1,160 @@
|
||||
import type { DocsHooksDict } from "../types";
|
||||
|
||||
/**
|
||||
* English reference dictionary for `app/[locale]/docs/hooks/page.tsx`.
|
||||
* Copy moved verbatim from the page's `isZh` ternaries — any wording change
|
||||
* belongs in its own commit, never mixed into a structural move.
|
||||
* English reference dictionary for `app/[locale]/docs/hooks/page.tsx`
|
||||
* ("Run commands on events"). Checked against docs/HOOKS.md — the
|
||||
* authoritative event-by-event contract (15 events, steering events,
|
||||
* conditions, environment variables, project-hook approval).
|
||||
*/
|
||||
export const docsHooks: DocsHooksDict = {
|
||||
metaTitle: "Hooks · Codewhale Docs",
|
||||
metaTitle: "Run commands on events · Codewhale Docs",
|
||||
metaDescription:
|
||||
"The shipped lifecycle hooks: mutable message_submit, tool_call_before decisions, turn_end, and sub-agent observer events.",
|
||||
"Run your own scripts when a session starts, before a tool call, when a turn ends, or when Codewhale is waiting for you — to add context, enforce a rule, or get notified.",
|
||||
bodyClassName: "text-ink-soft leading-relaxed",
|
||||
overviewTitle: "Hooks",
|
||||
overviewLead:
|
||||
"Hooks attach your own commands to Codewhale's lifecycle: inject context before a message is submitted, enforce policy before a tool call, and audit turns or sub-agent activity. This page describes what currently ships; docs/rfcs/1364-hooks-lifecycle.md is the design RFC for this surface, and docs/CONFIGURATION.md carries the full configuration schema.",
|
||||
configIntro:
|
||||
"Hooks are configured under {hooksTable} entries in config.toml; run {hooksCommand} in the TUI to see every configured hook grouped by event — name, command preview, timeout, and condition — plus the global {enabledKey} state.",
|
||||
events: [
|
||||
[
|
||||
"message_submit (mutable)",
|
||||
"Runs before a submitted message is added to history or sent to the model. The hook receives JSON on stdin; exit 0 with stdout JSON carrying a non-empty text field replaces the submitted text, and exit 2 blocks the submission before the turn starts. Multiple hooks run serially in config order, each receiving the previous hook's output. Hooks marked background = true are observer-only and cannot transform or block.",
|
||||
],
|
||||
[
|
||||
"tool_call_before (decision)",
|
||||
"Runs before each tool call executes. Beyond the exit-2 hard deny (which always wins), a foreground hook may print a JSON decision on stdout with exit 0: allow / deny / ask, plus updatedInput to rewrite the tool input and additionalContext appended to the tool result the model sees. When several hooks match, precedence is deny > ask > allow; tool_name conditions support * globs (mcp__* matches every MCP tool). Full Access does not open tool-approval prompts, so ask does not downgrade that posture.",
|
||||
],
|
||||
[
|
||||
"turn_end (observer)",
|
||||
"Fires after each model turn ends, once usage, cost, notifications, receipts, and queue-recovery state have settled. The stdin JSON carries fields such as status, duration_ms, usage, totals, and queued_message_count. Stdout is ignored and failures are warn-only — the hook cannot block input, mutate the transcript, or change the next queued follow-up.",
|
||||
],
|
||||
[
|
||||
"subagent_spawn / subagent_complete (observer)",
|
||||
"Observe sub-agent start and completion with bounded JSON metadata on stdin (agent_id, status, truncated prompt/result previews). Failures are warn-only and never block scheduling or change prompts or results; use the transcript handle returned by agent when full detail is needed.",
|
||||
],
|
||||
title: "Run commands on events",
|
||||
lede:
|
||||
"Hooks run a command of yours at set moments in a Codewhale session. Use them to block a risky command, add context to a message, log what happened, or get told when Codewhale is waiting for you.",
|
||||
sections: [
|
||||
{
|
||||
id: "first-hook",
|
||||
title: "Add your first hook",
|
||||
blocks: [
|
||||
{ p: "Hooks live in `~/.codewhale/config.toml`. This one prints a line whenever a session starts:" },
|
||||
{
|
||||
code: `[hooks]
|
||||
enabled = true
|
||||
|
||||
[[hooks.hooks]]
|
||||
name = "announce"
|
||||
event = "session_start"
|
||||
command = "echo 'Codewhale session started'"`,
|
||||
lang: "config.toml",
|
||||
},
|
||||
{
|
||||
p: "Start a session, then run `/hooks` to see every configured hook, whether hooks are on, and any entry that was rejected. `/hooks events` lists the event names.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "gate",
|
||||
title: "Block a command before it runs",
|
||||
blocks: [
|
||||
{
|
||||
p: "A `tool_call_before` hook sees each tool call before it executes and can allow it, deny it, or force an approval prompt. This one refuses force-pushes. Save the script and make it executable:",
|
||||
},
|
||||
{
|
||||
code: `#!/bin/sh
|
||||
# ~/.codewhale/hooks/no-force-push.sh
|
||||
case "$DEEPSEEK_TOOL_ARGS" in
|
||||
*"push --force"*|*"push -f"*)
|
||||
echo '{"decision": "deny", "reason": "Force-push is blocked by a hook."}' ;;
|
||||
esac
|
||||
exit 0`,
|
||||
lang: "no-force-push.sh",
|
||||
},
|
||||
{
|
||||
code: `[[hooks.hooks]]
|
||||
name = "no-force-push"
|
||||
event = "tool_call_before"
|
||||
command = "~/.codewhale/hooks/no-force-push.sh"
|
||||
condition = { type = "tool_name", name = "bash" }`,
|
||||
lang: "config.toml",
|
||||
},
|
||||
{
|
||||
p: "The hook reads the call from environment variables and answers with JSON on standard output: `allow`, `deny`, or `ask`, plus an optional `reason`, a rewritten input (`updatedInput`), or extra context for the model (`additionalContext`). Exit code 2 always denies. When several hooks answer, deny beats ask, and ask beats allow.",
|
||||
},
|
||||
{
|
||||
note: "`ask` forces a prompt in Ask and Auto-Review. Full Access never shows approval prompts, so there `ask` does not add one.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "events",
|
||||
title: "Choose the moment",
|
||||
blocks: [
|
||||
{
|
||||
p: "Three events can change what happens next. The rest only observe; their output is ignored and a failure is a warning.",
|
||||
},
|
||||
{
|
||||
rows: [
|
||||
["message_submit", "Before your message reaches the model. Can replace the text or block it."],
|
||||
["tool_call_before", "Before each tool call. Can allow, deny, ask, rewrite the input, or add context."],
|
||||
["shell_env", "Before each shell command. Can add environment variables."],
|
||||
["session_start / session_end", "When a session opens or closes cleanly."],
|
||||
["turn_end", "After a turn finishes, with its status, duration, and token usage."],
|
||||
["tool_call_after", "After each tool result, with its exit code when there is one."],
|
||||
["waiting_for_user", "When Codewhale starts waiting for an approval, an answer, or a paused goal."],
|
||||
["session_idle / session_busy", "When the session settles or starts working again."],
|
||||
["session_error / on_error", "When a turn fails for good, or on any error or failed tool."],
|
||||
["mode_change", "When you switch between Plan, Work, and Operate."],
|
||||
["subagent_spawn / subagent_complete", "When a sub-agent starts or finishes."],
|
||||
],
|
||||
codeTerms: true,
|
||||
},
|
||||
{
|
||||
p: "A `condition` narrows when a hook fires: by tool name (with `*` globs), tool category, mode, or exit code, and combinations with `all` and `any`. A condition that can never match its event is rejected when the config loads, so a gate you think is armed never sits silently inert.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "options",
|
||||
title: "Set timeouts and failure behavior",
|
||||
blocks: [
|
||||
{
|
||||
rows: [
|
||||
["timeout_secs", "How long the hook may run. Default 30."],
|
||||
["continue_on_error", "`true` (default): a failing hook only warns. `false`: the failure blocks."],
|
||||
["background", "`true` runs the hook as an observer only; it cannot block or rewrite."],
|
||||
["working_dir", "Under `[hooks]`: where hooks run. Default: the session's workspace."],
|
||||
],
|
||||
codeTerms: true,
|
||||
},
|
||||
{
|
||||
note: "`[hooks] default_timeout_secs` replaces every hook's own `timeout_secs`, not just the missing ones. Leave it unset if you want per-hook timeouts.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "project",
|
||||
title: "Use hooks a repository ships",
|
||||
blocks: [
|
||||
{
|
||||
p: "A repository can include hooks in `.codewhale/hooks.toml`. Because they run commands on your machine, they load only after you trust the workspace and approve that exact file:",
|
||||
},
|
||||
{ code: "/hooks review\n/hooks approve <digest>\n/hooks revoke", lang: "Codewhale" },
|
||||
{
|
||||
p: "`/hooks review` shows the commands and a digest of the file; approving that digest enables those exact bytes from the next session. Any change to the file needs a new approval. Review the scripts the commands call, too.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "headless",
|
||||
title: "Use hooks in scripts and CI",
|
||||
blocks: [
|
||||
{
|
||||
p: "Hooks run in the interactive session. `codewhale exec` fires none by default; add `--hooks` to fire `tool_call_before` and `shell_env`. With no one to answer, an `ask` becomes a deny.",
|
||||
},
|
||||
{ code: 'codewhale exec --auto --hooks "run the test suite and fix the first failure"', lang: "Terminal" },
|
||||
],
|
||||
},
|
||||
],
|
||||
next: [
|
||||
{
|
||||
href: "/docs/modes",
|
||||
label: "Set modes and approvals",
|
||||
note: "How hook decisions combine with Ask, Auto-Review, and Full Access.",
|
||||
},
|
||||
{
|
||||
href: "/docs/mcp",
|
||||
label: "Connect tools with MCP",
|
||||
note: "Gate MCP tools with the same `tool_call_before` hooks.",
|
||||
},
|
||||
{
|
||||
href: "/docs/configuration",
|
||||
label: "Change settings",
|
||||
note: "Where `config.toml` lives and what a project may override.",
|
||||
},
|
||||
],
|
||||
projectTitle: "Project-local hooks",
|
||||
projectLead:
|
||||
'Repositories can ship policy in <workspace>/.codewhale/hooks.toml. Because project hooks are executable shell configuration, Codewhale loads them only after the workspace is trusted through the trust prompt or a trust_level = "trusted" entry in user-owned config — session /trust on and legacy .deepseek/trusted markers do not enable project hooks by themselves. Once trusted, project hooks are appended after the global hooks from config.toml, so they run last and win updatedInput ties. A malformed trusted project file logs a warning and startup falls back to global hooks only.',
|
||||
sourceNote:
|
||||
"Source documents: docs/rfcs/1364-hooks-lifecycle.md (design RFC), docs/CONFIGURATION.md (configuration schema) · Update docs-map.ts when changing.",
|
||||
"Source documents: docs/HOOKS.md (authoritative), docs/CONFIGURATION.md · Update docs-map.ts when changing.",
|
||||
};
|
||||
@@ -1,34 +1,157 @@
|
||||
import type { DocsMcpDict } from "../types";
|
||||
|
||||
/**
|
||||
* English reference dictionary for `app/[locale]/docs/mcp/page.tsx`.
|
||||
* Copy moved verbatim from the page's `isZh` ternaries — any wording change
|
||||
* belongs in its own commit, never mixed into a structural move.
|
||||
* English reference dictionary for `app/[locale]/docs/mcp/page.tsx`
|
||||
* ("Connect tools with MCP"). Commands and flags are checked against
|
||||
* `McpCommand` in crates/tui/src/lib.rs and docs/MCP.md; the code-mode
|
||||
* section against crates/tui/src/tools/codemode.rs and
|
||||
* crates/tui/src/features.rs (`code_mode`: Experimental, default off).
|
||||
*/
|
||||
export const docsMcp: DocsMcpDict = {
|
||||
metaTitle: "MCP · Codewhale Docs",
|
||||
metaTitle: "Connect tools with MCP · Codewhale Docs",
|
||||
metaDescription:
|
||||
"Consume external tool servers over the Model Context Protocol, or expose Codewhale itself as an MCP server.",
|
||||
"Add Model Context Protocol servers so Codewhale can use more tools, sign in to remote servers, run Codewhale itself as an MCP server, and try code mode.",
|
||||
bodyClassName: "text-ink-soft leading-relaxed",
|
||||
overviewLead:
|
||||
"Codewhale can load additional tools via MCP (Model Context Protocol). MCP servers can be local stdio processes that the TUI starts, or remote URL-based servers that speak Streamable HTTP with legacy SSE fallback. A successfully connected server registers its tools into the model catalog; a failed or disabled server is never presented as an available tool.",
|
||||
overviewConfig:
|
||||
"The config file defaults to {configPath} (the legacy {legacyConfigPath} is still read when the Codewhale file is absent), overridable with {configPathOption} or {configEnvVar}. The {serversKey} key used by other clients is accepted too.",
|
||||
setupTitle: "Setup and management",
|
||||
setupLead:
|
||||
"Bootstrap a starter config with {initCommand}; inside the TUI, {mcpCommand} opens a compact manager showing each server's enabled state, transport, command or URL, timeouts, and connection errors. Common commands:",
|
||||
setupReload:
|
||||
"Config edits made from the TUI are written immediately, but the model-visible MCP tool pool is not hot-reloaded — the manager marks it restart-required. /mcp validate and /mcp reload reconnect to refresh the on-screen snapshot.",
|
||||
authTitle: "Remote authentication",
|
||||
authLead:
|
||||
"URL-based servers can use static headers, env-derived env_headers, bearer_token_env_var, or OAuth. Precedence is conservative: headers and env_headers apply first; bearer_token_env_var adds an Authorization header only when one is not already set; OAuth login tokens likewise never override an explicit header. Avoid committing literal Authorization headers — prefer env_headers, bearer_token_env_var, or OAuth login so secrets stay outside the MCP file.",
|
||||
toolsTitle: "Tool naming and safety",
|
||||
toolsLead:
|
||||
"Discovered MCP tools are exposed to the model as {toolNamePattern} — a server named {gitServer} with a {statusTool} tool becomes {gitStatusTool}. MCP tools flow through the same approval framework as built-in tools: read-only MCP helpers can run without prompts when policy permits, side-effectful MCP tools require approval, and Full Access does not bypass hard policy holds.",
|
||||
toolsTrust:
|
||||
"Only configure MCP servers you trust, and treat MCP server configuration as equivalent to running code on your machine. Reviewed local plugin bundles can also contribute MCP servers: they reuse the same MCP manager, approval, and network-policy paths, appear under namespaced <plugin>-<server> identities, and are held to a stricter boundary than hand-written mcp.json.",
|
||||
serverTitle: "Codewhale as an MCP server",
|
||||
serverLead:
|
||||
"{serveMcp} runs Codewhale as an stdio MCP server so other sessions (or any MCP client) can call its tools; {mcpServerCommand} is the equivalent dispatcher entrypoint. {addSelfCommand} resolves the current binary path and writes the server into your MCP config. Keep the modes distinct: {serveHttp} is the runtime HTTP/SSE API, a separate surface.",
|
||||
sourceNote: "Source document: docs/MCP.md · Update docs-map.ts when changing.",
|
||||
title: "Connect tools with MCP",
|
||||
lede:
|
||||
"MCP servers give Codewhale more tools — a database, an issue tracker, a browser. Add a local server that Codewhale starts for you, or a remote server by URL. Its tools then go through the same approvals as built-in ones.",
|
||||
sections: [
|
||||
{
|
||||
id: "add",
|
||||
title: "Add a server",
|
||||
blocks: [
|
||||
{
|
||||
code: `codewhale mcp add git --command "uvx" --arg "mcp-server-git"
|
||||
codewhale mcp add docs --url "https://example.com/mcp"
|
||||
codewhale mcp list
|
||||
codewhale mcp validate`,
|
||||
lang: "Terminal",
|
||||
},
|
||||
{
|
||||
p: "`--command` starts a local server over stdio; repeat `--arg` for each argument. `--url` connects to a remote server over Streamable HTTP, with legacy SSE as a fallback. `mcp validate` checks the config and the servers you require.",
|
||||
},
|
||||
{
|
||||
p: "Inside a session, `/mcp` opens the MCP manager: each server's state, transport, timeouts, errors, and discovered tools. The same actions are available there, for example `/mcp add stdio <name> <command>` and `/mcp add http <name> <url>`.",
|
||||
},
|
||||
{
|
||||
note: "An MCP server runs with your permissions. Add only servers you trust, as you would any program you install.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "remote-auth",
|
||||
title: "Sign in to a remote server",
|
||||
blocks: [
|
||||
{
|
||||
p: "For a server that uses OAuth, add it by URL and log in. For a bearer token, keep the token in an environment variable instead of the config file:",
|
||||
},
|
||||
{
|
||||
code: `codewhale mcp login docs
|
||||
codewhale mcp add tracker --url "https://example.com/mcp" --bearer-token-env-var TRACKER_TOKEN`,
|
||||
lang: "Terminal",
|
||||
},
|
||||
{
|
||||
p: "An explicit Authorization header always wins: headers from config apply first, then the bearer-token variable, then a stored OAuth login. `codewhale mcp logout <name>` removes the stored login on this machine; the provider may keep its own grant until you revoke it there.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "config",
|
||||
title: "Edit the config file",
|
||||
blocks: [
|
||||
{
|
||||
p: "Servers live in `~/.codewhale/mcp.json`. `codewhale mcp init` writes a starter file. The `mcpServers` key used by other clients works too, so you can paste an existing entry.",
|
||||
},
|
||||
{
|
||||
code: `{
|
||||
"servers": {
|
||||
"example": {
|
||||
"command": "node",
|
||||
"args": ["./path/to/your-mcp-server.js"],
|
||||
"env": {},
|
||||
"disabled": false
|
||||
}
|
||||
}
|
||||
}`,
|
||||
lang: "mcp.json",
|
||||
},
|
||||
{
|
||||
p: "After editing the file, run `/mcp reload` in the session; no restart is needed. A server starts only when a turn needs one of its tools, unless you mark it `\"required\": true` to connect at startup.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "tool-names",
|
||||
title: "Find the tools",
|
||||
blocks: [
|
||||
{
|
||||
p: "Each tool appears to the model as `mcp_<server>_<tool>`: a server named `git` with a `status` tool becomes `mcp_git_status`. `codewhale mcp tools <server>` lists what a server offers. A server that fails to connect or is disabled never shows up as an available tool.",
|
||||
},
|
||||
{
|
||||
p: "MCP tools follow your [approval setting](/docs/modes): listing and reading a server's resources and prompts can run without a prompt when policy allows, and tools with side effects ask first. Full Access does not override repository rules or managed policy.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "serve",
|
||||
title: "Run Codewhale as an MCP server",
|
||||
blocks: [
|
||||
{
|
||||
p: "Other MCP clients — including another Codewhale session — can use Codewhale's tools. Register it once:",
|
||||
},
|
||||
{
|
||||
code: `codewhale mcp add-self
|
||||
codewhale mcp tools codewhale`,
|
||||
lang: "Terminal",
|
||||
},
|
||||
{
|
||||
p: "`add-self` writes an entry that runs `codewhale serve --mcp` over stdio. Each client starts its own process; no network port is opened. `codewhale serve --http` is a different thing — the [Runtime API](/docs/runtime-api) for apps.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "code-mode",
|
||||
title: "Compose tool calls with code mode (experimental)",
|
||||
blocks: [
|
||||
{
|
||||
p: "Code mode lets the model write one short JavaScript program that calls several tools, loops, and filters results, instead of making each call as a separate step. Only the program's final value goes back to the model, which keeps long lookups compact. It is off by default. Try it for one session, or turn it on in config:",
|
||||
},
|
||||
{
|
||||
code: `codewhale --enable code_mode
|
||||
|
||||
# ~/.codewhale/config.toml
|
||||
[features]
|
||||
code_mode = true`,
|
||||
lang: "Terminal / config.toml",
|
||||
},
|
||||
{
|
||||
list: [
|
||||
"Only read-only tools that need no approval can run inside a program. Anything that writes, runs a shell command, or would ask you stops the program and reports which call it refused.",
|
||||
"MCP tools cannot be called from a program yet. Use them as ordinary tool calls.",
|
||||
"Limits per program: 50 tool calls, 4 at a time, 30 seconds, and 16 KiB returned.",
|
||||
"Code mode is not available in Plan mode.",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
next: [
|
||||
{
|
||||
href: "/docs/hooks",
|
||||
label: "Run commands on events",
|
||||
note: "Check or rewrite a tool call before it runs, including MCP tools.",
|
||||
},
|
||||
{
|
||||
href: "/docs/modes",
|
||||
label: "Set modes and approvals",
|
||||
note: "Decide which MCP calls stop for your approval.",
|
||||
},
|
||||
{
|
||||
href: "/docs/runtime-api",
|
||||
label: "Automate with the Runtime API",
|
||||
note: "Drive Codewhale from your own app or script over HTTP.",
|
||||
},
|
||||
],
|
||||
sourceNote:
|
||||
"Source documents: docs/MCP.md, crates/tui/src/tools/codemode.rs · Update docs-map.ts when changing.",
|
||||
};
|
||||
@@ -1,28 +1,121 @@
|
||||
import type { DocsModesDict } from "../types";
|
||||
|
||||
/**
|
||||
* English reference dictionary for `app/[locale]/docs/modes/page.tsx`
|
||||
* ("Set modes and approvals"). Checked against docs/MODES.md (modes,
|
||||
* postures, persistence), docs/INSTALL.md §10 (approval keys, Ask applies
|
||||
* workspace edits), and docs/CONFIGURATION.md (`[approval]`).
|
||||
*/
|
||||
export const docsModes: DocsModesDict = {
|
||||
metaTitle: "Modes · Codewhale Docs",
|
||||
metaDescription: "Plan, Work, Operate modes and independent permission postures.",
|
||||
metaTitle: "Set modes and approvals · Codewhale Docs",
|
||||
metaDescription:
|
||||
"Choose Plan, Work, or Operate for the kind of work, and Ask, Auto-Review, or Full Access for how often Codewhale stops to ask you.",
|
||||
bodyClassName: "text-ink-soft leading-relaxed",
|
||||
overviewTitle: "Modes",
|
||||
overviewLead:
|
||||
"A mode decides how Codewhale handles the work. A permission posture decides how it handles consequential tool calls. They are separate controls.",
|
||||
modes: [
|
||||
["Plan", "Read-only investigation and planning. Codewhale can inspect the workspace, but it cannot run shell commands or edit files."],
|
||||
["Work", "Normal interactive coding. Codewhale can inspect, edit, and use tools; shell availability and approval prompts follow the active configuration and permission posture."],
|
||||
["Operate", "Multitask coordination from the same composer. The parent can inspect, edit, and use shell or MCP tools under the same permission posture, sandbox, and safety rules as Work. Fleet workers are preferred for independent, parallel, background, or long-running work, but delegation is not required for every executable step. Workflow is optional unless the work needs ordered phases, gates, or deterministic fan-in."],
|
||||
title: "Set modes and approvals",
|
||||
lede:
|
||||
"Two separate controls decide what Codewhale does on its own. The mode sets the kind of work — look, change, or coordinate. The approval setting decides when it stops to ask you. Change either one from the keyboard at any time between turns.",
|
||||
sections: [
|
||||
{
|
||||
id: "modes",
|
||||
title: "Pick a mode",
|
||||
blocks: [
|
||||
{
|
||||
rows: [
|
||||
[
|
||||
"Plan",
|
||||
"Look and plan only. Codewhale can read the workspace and research, but it cannot edit files or run shell commands. Use it to understand a codebase or agree on an approach first.",
|
||||
],
|
||||
[
|
||||
"Work",
|
||||
"Everyday coding. Codewhale reads, edits, and runs commands, within the approval setting you chose. This is the default.",
|
||||
],
|
||||
[
|
||||
"Operate",
|
||||
"Larger goals. Same permissions as Work, but Codewhale plans named steps, hands independent steps to sub-agents in parallel, and checks each result before it reports done.",
|
||||
],
|
||||
],
|
||||
},
|
||||
{
|
||||
p: "Press Tab with an empty composer to cycle Plan → Work → Operate, or type `/mode` to open the picker. You can also switch directly:",
|
||||
},
|
||||
{ code: "/mode plan\n/mode work\n/mode operate", lang: "Codewhale" },
|
||||
{
|
||||
p: "The mode you pick becomes the mode your next session starts in. Codewhale refuses mode changes while a turn is running; press Esc to stop the turn first.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "approvals",
|
||||
title: "Choose when it asks",
|
||||
blocks: [
|
||||
{
|
||||
p: "Press Shift+Tab to cycle Ask → Auto-Review → Full Access. Plan is always read-only, whatever you pick here.",
|
||||
},
|
||||
{
|
||||
rows: [
|
||||
[
|
||||
"Ask",
|
||||
"The default. File edits inside the workspace are applied and shown to you as a diff. Shell commands and other consequential tools stop for your approval.",
|
||||
],
|
||||
[
|
||||
"Auto-Review",
|
||||
"Never stops to ask. Calls that are provably safe run. Publishing and destructive background actions are always blocked. Anything else gets one independent model review; high-risk calls and failed reviews are denied, not run.",
|
||||
],
|
||||
[
|
||||
"Full Access",
|
||||
"No approval prompts. Repository rules and managed policy still block what they block. Use it only in a workspace you trust.",
|
||||
],
|
||||
],
|
||||
},
|
||||
{
|
||||
note: "Ask applies workspace file edits without asking. Commit or stash anything you care about before you start, and use [Review what changed](/docs/review) to check or roll back edits.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "prompt",
|
||||
title: "Answer an approval prompt",
|
||||
blocks: [
|
||||
{
|
||||
p: "When Codewhale stops at an approval, it shows the exact command. Answer with one key:",
|
||||
},
|
||||
{
|
||||
rows: [
|
||||
["y", "Allow this call once."],
|
||||
["a", "Allow it for the rest of this session."],
|
||||
["n", "Deny it. Codewhale is told the call was refused."],
|
||||
["Esc", "Stop the whole turn."],
|
||||
],
|
||||
codeTerms: true,
|
||||
},
|
||||
{
|
||||
p: "The highlighted option on a new prompt is Deny, so pressing Enter without reading refuses the call. To change that, or to deny prompts that wait too long, set it in `~/.codewhale/config.toml`:",
|
||||
},
|
||||
{
|
||||
code: `[approval]
|
||||
default_selection = "allow_once" # default: "deny"
|
||||
timeout_seconds = 300 # default: wait indefinitely`,
|
||||
lang: "config.toml",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
switchingTitle: "Switch modes",
|
||||
switchingLead:
|
||||
"When the composer is idle, press {tab} to cycle Plan → Work → Operate. When a completion menu is open, Tab accepts the completion; during an active turn, it can queue the current draft as the next follow-up.",
|
||||
switchingCommandLead: "Run /mode to open the picker, or switch directly:",
|
||||
permissionsTitle: "Permission postures",
|
||||
permissionsLead:
|
||||
"Plan is always Read Only. When the composer is idle in Work or Operate, press {shiftTab} to cycle Ask → Auto-Review → Full Access. Run {configCommand} to inspect or edit the current session permission; project or managed policy may lock or tighten it.",
|
||||
postures: [
|
||||
["Ask", "Ask before tools that can make consequential changes."],
|
||||
["Auto-Review", "Fully autonomous: it never stops to ask you. Proven-safe calls run, publish-like and destructive background actions are blocked, and anything else goes to a one-shot model review; high-risk calls and unresolved holds are denied rather than turned into a prompt."],
|
||||
["Full Access", "Run tools without approval prompts and enable trusted-workspace access. Repository rules and managed constraints still apply; use it only in a workspace you trust."],
|
||||
next: [
|
||||
{
|
||||
href: "/docs/review",
|
||||
label: "Review what changed",
|
||||
note: "See the diff for a session and roll files back to an earlier turn.",
|
||||
},
|
||||
{
|
||||
href: "/docs/sandbox",
|
||||
label: "Limit what commands can touch",
|
||||
note: "An approval is not a sandbox. See what the operating system enforces on each platform.",
|
||||
},
|
||||
{
|
||||
href: "/docs/subagents",
|
||||
label: "Run agents in parallel",
|
||||
note: "What Operate does with independent steps, and how to watch it.",
|
||||
},
|
||||
],
|
||||
sourceNote: "Source document: docs/MODES.md · Update docs-map.ts when changing.",
|
||||
sourceNote: "Source documents: docs/MODES.md, docs/INSTALL.md §10, docs/CONFIGURATION.md · Update docs-map.ts when changing.",
|
||||
};
|
||||
@@ -0,0 +1,140 @@
|
||||
import type { DocsReviewDict } from "../types";
|
||||
|
||||
/**
|
||||
* English reference dictionary for `app/[locale]/docs/review/page.tsx`
|
||||
* ("Review what changed"). Checked against the slash-command registry
|
||||
* (`/diff`, `/restore`, `/undo`, `/review`, `/export`), the workspace
|
||||
* snapshot module (crates/tui/src/snapshot/mod.rs), `ReviewArgs` in
|
||||
* crates/tui/src/lib.rs, and docs/RECEIPTS.md.
|
||||
*/
|
||||
export const docsReview: DocsReviewDict = {
|
||||
metaTitle: "Review what changed · Codewhale Docs",
|
||||
metaDescription:
|
||||
"See every file Codewhale changed in a session, roll the workspace back to an earlier turn, get a code review of a diff, and keep a receipt of that review.",
|
||||
bodyClassName: "text-ink-soft leading-relaxed",
|
||||
title: "Review what changed",
|
||||
lede:
|
||||
"Codewhale shows you each edit as it happens and keeps a snapshot of your workspace before and after every turn. Use this page to check what changed, put files back, and get a second opinion before you push.",
|
||||
sections: [
|
||||
{
|
||||
id: "diff",
|
||||
title: "See the changes",
|
||||
blocks: [
|
||||
{
|
||||
p: "Every file edit appears in the transcript as a diff when it is made. To see everything at once, run:",
|
||||
},
|
||||
{ code: "/diff", lang: "Codewhale" },
|
||||
{
|
||||
p: "`/diff` shows all changes since this session started. Your own git history is untouched, so `git diff` and `git status` work as usual.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "restore",
|
||||
title: "Roll files back to an earlier turn",
|
||||
blocks: [
|
||||
{
|
||||
p: "Before and after each turn, Codewhale snapshots the workspace into a separate git store. It never writes to your repository's own `.git`, and it works in folders that are not git repositories.",
|
||||
},
|
||||
{
|
||||
code: `/restore # list the 20 most recent snapshots
|
||||
/restore list 50 # list more (up to 100)
|
||||
/restore 1 # put files back to the newest snapshot`,
|
||||
lang: "Codewhale",
|
||||
},
|
||||
{
|
||||
p: "Restoring changes files, so it needs a trusted workspace (`/trust on`) or Full Access; anyone can list snapshots. You can also just ask — “undo your last edit” — and Codewhale can roll back its own turn.",
|
||||
},
|
||||
{
|
||||
p: "`/undo` is different: it removes the last exchange from the conversation. Use `/restore` when you want files back.",
|
||||
},
|
||||
{
|
||||
note: "Snapshots are kept for 7 days. A workspace larger than 2 GB skips snapshots and Codewhale tells you once; raise `[snapshots] max_workspace_gb` in config if you want them anyway. If git is missing or the disk is full, the turn still runs without a snapshot.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "code-review",
|
||||
title: "Get a code review",
|
||||
blocks: [
|
||||
{
|
||||
p: "Inside a session, `/review` runs a structured review of a file, a diff, or a pull request. From the shell, `codewhale review` reviews a git diff and prints findings:",
|
||||
},
|
||||
{
|
||||
code: `codewhale review # unstaged changes in the working tree
|
||||
codewhale review --staged # what you are about to commit
|
||||
codewhale review --base origin/main # everything on this branch
|
||||
codewhale review --pr 123 # a GitHub pull request (needs gh)`,
|
||||
lang: "Terminal",
|
||||
},
|
||||
{
|
||||
p: "Add `--path <file>` to review one path, `--model` to pick the reviewer, or `--json` for machine-readable output. A diff over 200,000 characters is refused rather than cut short; raise the limit with `--max-chars`.",
|
||||
},
|
||||
{
|
||||
note: "`--post` publishes the review as a comment on the pull request. Without it, nothing leaves your terminal except the model request.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "receipts",
|
||||
title: "Keep a receipt of the review",
|
||||
blocks: [
|
||||
{
|
||||
p: "A review receipt records what was reviewed and what the review found, so you can prove the diff you push is the diff that was reviewed.",
|
||||
},
|
||||
{
|
||||
code: `codewhale review --base origin/main --write-receipt
|
||||
codewhale review --base origin/main --check-receipt`,
|
||||
lang: "Terminal",
|
||||
},
|
||||
{
|
||||
list: [
|
||||
"`--write-receipt` saves a local JSON file after a successful review: a fingerprint of the diff, the provider and model, finding counts, unresolved risk, and a hash of the review text. It does not store the diff itself.",
|
||||
"`--check-receipt` makes no model call. It exits non-zero if the diff has changed since the receipt, if the review left unresolved risk, or if an attached check did not pass — so it works as a pre-push gate.",
|
||||
"`--receipt-path <file>` writes or reads a specific receipt instead of the latest one.",
|
||||
],
|
||||
},
|
||||
{
|
||||
note: "Receipts cover code reviews today. A receipt for an ordinary agent turn — every tool call, approval, and file change in one export — is designed but not built yet.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "export",
|
||||
title: "Save the whole session",
|
||||
blocks: [
|
||||
{
|
||||
p: "To keep or share the full record of a session, export it:",
|
||||
},
|
||||
{
|
||||
code: `/export clipboard # a redacted copy of this session
|
||||
/export file notes/session.md # the same, written to a file
|
||||
codewhale sessions export <id> # full archive: messages, tool calls, results, artifacts`,
|
||||
lang: "Codewhale / Terminal",
|
||||
},
|
||||
{
|
||||
p: "`codewhale sessions` lists saved sessions and their ids. The archive holds the complete context, so treat it like source code.",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
next: [
|
||||
{
|
||||
href: "/docs/modes",
|
||||
label: "Set modes and approvals",
|
||||
note: "Choose whether Codewhale asks before it edits or runs anything.",
|
||||
},
|
||||
{
|
||||
href: "/docs/fleet",
|
||||
label: "Run a workflow",
|
||||
note: "Repeatable, multi-step work with a record of every run.",
|
||||
},
|
||||
{
|
||||
href: "/docs/troubleshooting",
|
||||
label: "Fix a problem",
|
||||
note: "What to do when a turn stalls, a key fails, or a session will not resume.",
|
||||
},
|
||||
],
|
||||
sourceNote:
|
||||
"Source documents: docs/RECEIPTS.md, docs/CONFIGURATION.md ([snapshots]), crates/tui/src/snapshot/mod.rs · Update docs-map.ts when changing.",
|
||||
};
|
||||
@@ -1,43 +1,128 @@
|
||||
import type { DocsRuntimeApiDict } from "../types";
|
||||
|
||||
/**
|
||||
* English reference dictionary for `app/[locale]/docs/runtime-api/page.tsx`.
|
||||
* Copy moved verbatim from the page's `isZh` ternaries — any wording change
|
||||
* belongs in its own commit, never mixed into a structural move.
|
||||
* English reference dictionary for `app/[locale]/docs/runtime-api/page.tsx`
|
||||
* ("Automate with the Runtime API"). Checked against docs/RUNTIME_API.md
|
||||
* (entrypoints, defaults, auth, endpoints) and the handlers in
|
||||
* crates/tui/src/runtime_api.rs (`POST /v1/threads` → 201 ThreadRecord).
|
||||
*/
|
||||
export const docsRuntimeApi: DocsRuntimeApiDict = {
|
||||
metaTitle: "Runtime API · Codewhale Docs",
|
||||
metaTitle: "Automate with the Runtime API · Codewhale Docs",
|
||||
metaDescription:
|
||||
"Local HTTP/SSE, JSON-RPC stdio, and ACP entrypoints for integrations, bridges, and automation.",
|
||||
"Drive Codewhale from your own scripts and apps: run one-shot prompts in CI, or start the local HTTP API and send turns, stream events, and answer approvals.",
|
||||
bodyClassName: "text-ink-soft leading-relaxed",
|
||||
overviewTitle: "Runtime API",
|
||||
overviewLead:
|
||||
"codewhale app-server is the canonical local runtime API and control plane. Local SDKs, mobile/remote-control clients, and editor integrations talk to it instead of screen-scraping terminal output. The engine runs as a local-only process: every API binds to localhost by default — no hosted relay, no provider-token custody, no secret leakage. codewhale serve --http / --mobile remain compatibility aliases for app-server --http / --mobile and launch the identical server; new integrations should target app-server.",
|
||||
entries: [
|
||||
["http", "The full /v1/* HTTP/SSE runtime API (canonical entry), default 127.0.0.1:7878."],
|
||||
["mobile", "The runtime API plus the /mobile phone control page."],
|
||||
[
|
||||
"stdio",
|
||||
"Newline-delimited JSON-RPC 2.0 control transport with no listener, for local SDKs and probes.",
|
||||
],
|
||||
[
|
||||
"web",
|
||||
"The loopback-only browser client, embedded in the binary and opened in the default browser.",
|
||||
],
|
||||
["doctor", "Machine-readable health and capability report."],
|
||||
["acp", "ACP (Agent Client Protocol) stdio adapter for editors such as Zed."],
|
||||
[
|
||||
"exec",
|
||||
"The one-shot headless worker (stream-json, fleet subprocess, CI primitive) — not part of this API, but it shares the same runtime and event vocabulary.",
|
||||
],
|
||||
title: "Automate with the Runtime API",
|
||||
lede:
|
||||
"Scripts and apps can drive the same engine you use in the terminal. For a single job, use `codewhale exec`. For an app that needs threads, live events, and approvals, run the local Runtime API. Everything runs on your machine; there is no hosted relay.",
|
||||
sections: [
|
||||
{
|
||||
id: "exec",
|
||||
title: "Run one job from a script",
|
||||
blocks: [
|
||||
{
|
||||
code: `codewhale exec "Reply with exactly: pong"
|
||||
codewhale exec --auto "fix the failing test and run it again"
|
||||
codewhale exec --auto --output-format stream-json "update the changelog"`,
|
||||
lang: "Terminal",
|
||||
},
|
||||
{
|
||||
p: "Plain `exec` answers once without tools. `--auto` lets it use tools and approves them automatically, so use it only in a repository or container you trust; it never widens the [sandbox](/docs/sandbox). `--output-format stream-json` prints one JSON event per line and saves the session so `--continue` can pick it up. `--max-turns` and `--allowed-tools` put limits on a run.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "start",
|
||||
title: "Start the Runtime API",
|
||||
blocks: [
|
||||
{
|
||||
code: `export CODEWHALE_RUNTIME_TOKEN="$(openssl rand -hex 32)"
|
||||
codewhale app-server --http # http://127.0.0.1:7878`,
|
||||
lang: "Terminal",
|
||||
},
|
||||
{
|
||||
p: "Set the token yourself before starting; if you do not, Codewhale generates one for the process and does not print it. Every `/v1/*` request must send it as `Authorization: Bearer <token>`. `--port` changes the port.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "turn",
|
||||
title: "Send a turn and watch it",
|
||||
blocks: [
|
||||
{
|
||||
code: `API=http://127.0.0.1:7878
|
||||
AUTH="Authorization: Bearer $CODEWHALE_RUNTIME_TOKEN"
|
||||
|
||||
THREAD=$(curl -s -X POST "$API/v1/threads" -H "$AUTH" \\
|
||||
-H "Content-Type: application/json" -d '{}' | jq -r .id)
|
||||
|
||||
curl -s -X POST "$API/v1/threads/$THREAD/turns" -H "$AUTH" \\
|
||||
-H "Content-Type: application/json" -d '{"prompt": "Summarize README.md"}'
|
||||
|
||||
curl -N "$API/v1/threads/$THREAD/events?since_seq=0" -H "$AUTH"`,
|
||||
lang: "Terminal",
|
||||
},
|
||||
{
|
||||
p: "A thread is a conversation; a turn is one request and everything Codewhale does for it. The events stream replays from the sequence number you give and then stays open for new events, so a client that reconnects misses nothing.",
|
||||
},
|
||||
{
|
||||
rows: [
|
||||
["Stop a turn", "`POST /v1/threads/{id}/turns/{turn_id}/interrupt`"],
|
||||
["Answer an approval", "`POST /v1/approvals/{approval_id}`"],
|
||||
["Steer a running turn", "`POST /v1/threads/{id}/turns/{turn_id}/steer`"],
|
||||
["List saved sessions", "`GET /v1/sessions`"],
|
||||
],
|
||||
},
|
||||
{
|
||||
p: "[docs/RUNTIME_API.md](https://github.com/Hmbown/CodeWhale/blob/main/docs/RUNTIME_API.md) lists every route, request body, and event.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "other",
|
||||
title: "Pick another connection",
|
||||
blocks: [
|
||||
{
|
||||
rows: [
|
||||
["codewhale app-server --stdio", "JSON-RPC over standard input and output, with no network listener. Good for an SDK or a local probe."],
|
||||
["codewhale serve --acp", "Agent Client Protocol for editors such as Zed."],
|
||||
["codewhale serve --mcp", "Offer Codewhale's tools to another MCP client. See [Connect tools with MCP](/docs/mcp)."],
|
||||
["codewhale web", "The built-in [browser client](/docs/web), on the same API."],
|
||||
["codewhale doctor --json", "Health and capabilities as JSON, with no secrets."],
|
||||
],
|
||||
codeTerms: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "security",
|
||||
title: "Keep it private",
|
||||
blocks: [
|
||||
{
|
||||
list: [
|
||||
"The server listens on `127.0.0.1` by default. The token is a local guard, not a replacement for TLS or a VPN; do not expose the port to a network.",
|
||||
"`--insecure-no-auth` is accepted only on a loopback address.",
|
||||
"The API never returns your provider keys. Health and capability reports carry only metadata — no secrets, file contents, or messages.",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
next: [
|
||||
{
|
||||
href: "/docs/web",
|
||||
label: "Open the browser client",
|
||||
note: "A ready-made client for the same API.",
|
||||
},
|
||||
{
|
||||
href: "/docs/hooks",
|
||||
label: "Run commands on events",
|
||||
note: "React to session events without writing a client.",
|
||||
},
|
||||
{
|
||||
href: "/docs/fleet",
|
||||
label: "Run a workflow",
|
||||
note: "Durable, multi-step runs you can check from any terminal.",
|
||||
},
|
||||
],
|
||||
stdioTitle: "Probe without model tokens",
|
||||
stdioLead:
|
||||
"The stdio control transport can be probed without spending model tokens. capabilities returns the advertised method families (thread/*, app/*, prompt/*) and the full method list; the method set is pinned by a drift test in crates/app-server/src/lib.rs, so SDK and local integration clients can rely on it not changing silently.",
|
||||
interruptNote:
|
||||
"A live turn can be asked to stop with thread/interrupt (or POST /v1/threads/{id}/turns/{turn_id}/interrupt over HTTP); when no turn is streaming the reply carries interrupted: false — not an error, just nothing to stop.",
|
||||
securityTitle: "Security boundary",
|
||||
securityLead:
|
||||
"The runtime API token is read from {authToken}, then {runtimeTokenEnv}, then {legacyTokenEnv}; {insecureFlag} is only accepted with a loopback bind. Cross-origin browser requests are rejected by the CORS allow-list. Before selecting a non-loopback bind — especially {mobileFlag} — read the full deployment and authentication contract in docs/RUNTIME_API.md.",
|
||||
sourceNote: "Source document: docs/RUNTIME_API.md · Update docs-map.ts when changing.",
|
||||
};
|
||||
@@ -1,43 +1,105 @@
|
||||
import type { DocsSandboxDict } from "../types";
|
||||
|
||||
/**
|
||||
* English reference dictionary for `app/[locale]/docs/sandbox/page.tsx`.
|
||||
* Copy moved verbatim from the page's `isZh` ternaries — any wording change
|
||||
* belongs in its own commit, never mixed into a structural move.
|
||||
* English reference dictionary for `app/[locale]/docs/sandbox/page.tsx`
|
||||
* ("Limit what commands can touch"). Checked against docs/SANDBOX.md — which
|
||||
* describes only behavior wired into the command execution path — and
|
||||
* docs/CONFIGURATION.md (`sandbox_mode`, `prefer_bwrap`).
|
||||
*/
|
||||
export const docsSandbox: DocsSandboxDict = {
|
||||
metaTitle: "Sandbox & Approval · Codewhale Docs",
|
||||
metaTitle: "Limit what commands can touch · Codewhale Docs",
|
||||
metaDescription:
|
||||
"The honest boundary: macOS Seatbelt, opt-in Linux bubblewrap, platform gaps, and approval policy.",
|
||||
"See which operating-system sandbox wraps shell commands on macOS, Linux, and Windows, turn it on where it is optional, and choose how much a command may write.",
|
||||
bodyClassName: "text-ink-soft leading-relaxed",
|
||||
overviewTitle: "Sandbox & Approval",
|
||||
overviewLead:
|
||||
"Codewhale can launch shell commands proposed by a model. Approval policy, workspace-aware tools, and an operating-system command wrapper are separate controls: an approval is not a sandbox, and selecting workspace-write does not prove the current platform has an OS wrapper available. This page describes only behavior wired into the command execution path.",
|
||||
platforms: [
|
||||
[
|
||||
"macOS · Seatbelt",
|
||||
"Codewhale probes /usr/bin/sandbox-exec; when the probe succeeds and the policy requests a sandbox, the child command is wrapped in a generated Seatbelt profile: broad filesystem reads, policy-limited writes, and network only when the policy enables it. A failed probe is reported honestly as no OS sandbox.",
|
||||
],
|
||||
[
|
||||
"Linux · opt-in bubblewrap",
|
||||
"Linux command sandboxing is opt-in: set prefer_bwrap = true and keep /usr/bin/bwrap executable. The child gets a read-only root view with writable mounts derived from the resolved policy; the network namespace is isolated by default and --share-net is added only when the policy enables network access. Without the opt-in, Codewhale reports none.",
|
||||
],
|
||||
[
|
||||
"Windows · no OS sandbox",
|
||||
"The Windows command path currently reports no OS sandbox. Host permissions and approval policy still apply, but they are not a Codewhale OS command sandbox.",
|
||||
],
|
||||
[
|
||||
"External OpenSandbox execution",
|
||||
'With sandbox_backend = "opensandbox", shell execution is sent to the configured OpenSandbox-compatible HTTP endpoint instead of starting a local child. Isolation guarantees belong to the configured service and its operator.',
|
||||
],
|
||||
title: "Limit what commands can touch",
|
||||
lede:
|
||||
"Approving a command decides whether it runs. A sandbox decides what it can reach once it does. Codewhale uses the operating system's sandbox where one is available and tells you plainly when there is none.",
|
||||
sections: [
|
||||
{
|
||||
id: "platforms",
|
||||
title: "Check what your platform provides",
|
||||
blocks: [
|
||||
{
|
||||
rows: [
|
||||
["macOS", "Seatbelt, automatically, when its startup check succeeds. Commands get broad read access, writes limited by the sandbox mode, and network only when the mode allows it."],
|
||||
["Linux", "Bubblewrap, but only if you turn it on (below). Without it, commands run with no OS sandbox."],
|
||||
["Windows", "No OS sandbox today. Your approval setting and Windows permissions still apply."],
|
||||
["External service", "With `sandbox_backend = \"opensandbox\"`, shell commands run on an OpenSandbox-compatible service you configure; its isolation is that service's to guarantee."],
|
||||
],
|
||||
},
|
||||
{ p: "Ask Codewhale which one it found:" },
|
||||
{ code: "codewhale doctor\ncodewhale setup --status", lang: "Terminal" },
|
||||
{
|
||||
p: "Both report the sandbox that is actually available after your settings are applied. Codewhale never counts source code that is not wired in as a sandbox.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "linux",
|
||||
title: "Turn on the Linux sandbox",
|
||||
blocks: [
|
||||
{ p: "Install bubblewrap, then opt in with one line in `~/.codewhale/config.toml`:" },
|
||||
{
|
||||
code: `sudo apt install bubblewrap # Fedora: dnf install bubblewrap · Arch: pacman -S bubblewrap
|
||||
|
||||
# ~/.codewhale/config.toml
|
||||
prefer_bwrap = true`,
|
||||
lang: "Terminal / config.toml",
|
||||
},
|
||||
{
|
||||
p: "Codewhale uses `/usr/bin/bwrap` only when that file exists and is executable. Commands then see a read-only view of the system, write only where the sandbox mode allows, and have no network unless the mode enables it.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "mode",
|
||||
title: "Choose how much a command may write",
|
||||
blocks: [
|
||||
{ code: 'sandbox_mode = "workspace-write"', lang: "config.toml" },
|
||||
{
|
||||
rows: [
|
||||
["read-only", "Commands can read but not write."],
|
||||
["workspace-write", "Commands can write inside the workspace and temporary folders, and nowhere else."],
|
||||
["danger-full-access", "No OS sandbox. Use only on a machine or container you are prepared to lose."],
|
||||
["external-sandbox", "You are already running inside isolation, so Codewhale adds none of its own."],
|
||||
],
|
||||
codeTerms: true,
|
||||
},
|
||||
{
|
||||
p: "The first two are enforced only where a sandbox is available — on Linux without bubblewrap, and on Windows, they are settings without an OS wrapper behind them. A repository's own config can make the mode stricter, never looser. For one headless run, pass `--sandbox <mode>` to `codewhale exec`; `--auto` approves tools but never widens the sandbox.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "limits",
|
||||
title: "Know the limits",
|
||||
blocks: [
|
||||
{
|
||||
list: [
|
||||
"Availability is checked before a command starts, but the sandbox can still fail at launch because of host policy or container restrictions.",
|
||||
"A “Permission denied” from a command is not proof that the sandbox blocked it. Codewhale labels a denial as the sandbox's only when the sandbox itself reported it.",
|
||||
"No sandbox protects against kernel vulnerabilities or every kind of resource exhaustion.",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
policiesTitle: "Policies and fallbacks",
|
||||
policiesLead:
|
||||
"The local {sandboxMode} values are {readOnly}, {workspaceWrite}, {dangerFullAccess}, and {externalSandbox}. The first two are enforced by Seatbelt or bubblewrap only when that wrapper is selected and available; {dangerFullAccess} deliberately bypasses the local OS wrapper; {externalSandbox} declares that execution is already externally isolated. When no wrapper is selected, the shell command runs without Codewhale OS isolation — approval rules and workspace-aware native file tools remain separate controls.",
|
||||
diagnosticsTitle: "Diagnostics and limits",
|
||||
diagnosticsLead:
|
||||
"codewhale setup --status, codewhale doctor, codewhale doctor --json, and the diagnostics tool report the locally available wrapper after applying the resolved bubblewrap preference. Denial attribution is intentionally conservative: a child command's generic Permission denied is not by itself proof that Codewhale's sandbox blocked it, and unsandboxed command failures are never labeled sandbox denials.",
|
||||
diagnosticsLimits:
|
||||
"The limitations are stated just as plainly: availability is checked before launch, yet the selected wrapper can still fail because of host policy, container restrictions, or a race after the probe; bubblewrap ignores a configured writable root that is missing or not a directory; and no sandbox protects against kernel vulnerabilities or all resource-exhaustion and side-channel attacks.",
|
||||
sourceNote: "Source document: docs/SANDBOX.md · Update docs-map.ts when changing.",
|
||||
next: [
|
||||
{
|
||||
href: "/docs/modes",
|
||||
label: "Set modes and approvals",
|
||||
note: "Decide which commands stop for your approval.",
|
||||
},
|
||||
{
|
||||
href: "/docs/trust",
|
||||
label: "See what leaves your machine",
|
||||
note: "What a provider receives, what stays local, and what telemetry sends.",
|
||||
},
|
||||
{
|
||||
href: "/docs/configuration",
|
||||
label: "Change settings",
|
||||
note: "Where these keys live and what a repository may override.",
|
||||
},
|
||||
],
|
||||
sourceNote: "Source documents: docs/SANDBOX.md, docs/CONFIGURATION.md · Update docs-map.ts when changing.",
|
||||
};
|
||||
@@ -3,17 +3,17 @@ import type { DocsShellDict } from "../types";
|
||||
/**
|
||||
* English reference dictionary for the docs shell: the portal hero in
|
||||
* `app/[locale]/docs/layout.tsx`, the hub metadata, the task/topic search,
|
||||
* the sidebar and breadcrumb chrome, the release-truth band, and the
|
||||
* contextual help band that closes every docs page.
|
||||
* the sidebar and breadcrumb chrome, the release-truth band, the contextual
|
||||
* help band that closes every docs page, and the shared page-body chrome.
|
||||
*/
|
||||
export const docsShell: DocsShellDict = {
|
||||
metaTitle: "Docs · Codewhale",
|
||||
metaDescription:
|
||||
"Codewhale documentation: install, user guide, configuration, providers, account and keys, cloud computers, security and trust, core concepts, tools, MCP, skills, sandbox, runtime API, troubleshooting.",
|
||||
"Install Codewhale, connect a provider, and get work done: modes and approvals, reviewing changes, workflows, sub-agents, MCP tools, hooks, the Runtime API, and troubleshooting.",
|
||||
portalMark: "Codewhale documentation",
|
||||
heroTitle: "Find the guidance you need.",
|
||||
heroTitle: "Get something done with Codewhale.",
|
||||
heroLead:
|
||||
"Start from a task or a topic. Every page states which repository document it is drawn from and which release it describes.",
|
||||
"Start from what you want to do. Each page says what you need, shows commands you can run today, and points to the next step.",
|
||||
installCta: "Install Codewhale",
|
||||
sourceDocsCta: "Browse source docs ↗",
|
||||
|
||||
@@ -39,7 +39,7 @@ export const docsShell: DocsShellDict = {
|
||||
"Try a different word — searches match English and Chinese — or browse the complete docs directory on GitHub.",
|
||||
emptyCta: "GitHub docs directory ↗",
|
||||
indexNote:
|
||||
"Web guides stay on codewhale.net. Source docs open the complete reference in the GitHub repository. Tasks come from docs-tasks.ts and topics from docs-map.ts; both registries live in the repository.",
|
||||
"Web guides open on codewhale.net. Source docs open the full reference in the GitHub repository.",
|
||||
|
||||
sidebarHeading: "Documentation",
|
||||
sidebarAria: "Documentation index",
|
||||
@@ -49,10 +49,14 @@ export const docsShell: DocsShellDict = {
|
||||
|
||||
helpTitle: "Need more than this page?",
|
||||
helpLead:
|
||||
"Every guide is drawn from a repository document. If it is wrong or missing something, the fastest fix is to say so where the maintainer will see it.",
|
||||
"Every guide is checked against a document in the repository. If something is wrong or missing, say so where the maintainer will see it.",
|
||||
helpSource: "Source: {name}",
|
||||
helpTroubleshooting: "Troubleshooting",
|
||||
helpTroubleshooting: "Fix a problem",
|
||||
helpFaq: "FAQ",
|
||||
helpDiscord: "Ask on Discord ↗",
|
||||
helpIssue: "Report a docs problem ↗",
|
||||
|
||||
nextHeading: "Next",
|
||||
noteLabel: "Note:",
|
||||
onThisPage: "On this page",
|
||||
};
|
||||
@@ -1,47 +1,122 @@
|
||||
import type { DocsSubagentsDict } from "../types";
|
||||
|
||||
/**
|
||||
* English reference dictionary for `app/[locale]/docs/subagents/page.tsx`.
|
||||
* Copy moved verbatim from the page's `isZh` ternaries — any wording change
|
||||
* belongs in its own commit, never mixed into a structural move.
|
||||
* English reference dictionary for `app/[locale]/docs/subagents/page.tsx`
|
||||
* ("Run agents in parallel"). Roles follow the current Fleet role table in
|
||||
* docs/SUBAGENTS.md (general/explore/planner/reviewer/implement/test/
|
||||
* advisor/custom); limits follow crates/tui/src/config/subagent_limits.rs
|
||||
* and docs/CONFIGURATION.md (`max_subagents`, `[subagents]`).
|
||||
*/
|
||||
export const docsSubagents: DocsSubagentsDict = {
|
||||
metaTitle: "Sub-Agents · Codewhale Docs",
|
||||
metaTitle: "Run agents in parallel · Codewhale Docs",
|
||||
metaDescription:
|
||||
"The agent tool, fleet roles, context forking, worktree isolation, and concurrency caps.",
|
||||
"Let Codewhale hand independent parts of a task to sub-agents, choose their roles, keep their edits in separate worktrees, and watch them work.",
|
||||
bodyClassName: "text-ink-soft leading-relaxed",
|
||||
overviewTitle: "Sub-Agents",
|
||||
overviewLead:
|
||||
"A parent session launches one focused sub-agent through the agent tool and immediately gets back an agent_id, a compact receipt, and a transcript handle while the worker runs in the background. Sub-agents inherit the parent's tool registry by default, but they are leaf workers: they do not receive agent or nested lifecycle tools. agent launches detached background work — cancelling the parent turn stops the parent's wait path, but it does not kill already-opened child runs.",
|
||||
overviewFleetNote:
|
||||
"For work that must survive process restarts, sleep, or remote execution, prefer a fleet or a Workflow-backed fleet run over a short in-session agent call.",
|
||||
roles: [
|
||||
[
|
||||
"worker",
|
||||
"Flexible multi-step execution of the parent's brief; writes and shell allowed. The default role.",
|
||||
],
|
||||
["scout", "Read-only, maps the relevant code fast — “find every call site of Foo.”"],
|
||||
[
|
||||
"planner",
|
||||
"Analyse and produce a strategy without executing — “design the migration; don't run it.”",
|
||||
],
|
||||
["reviewer", "Read-and-grade with severity scores — “audit this PR for bugs.”"],
|
||||
["builder", "Land a specific change with minimal edits; writes and shell allowed."],
|
||||
["verifier", "Run tests and validation gates and report the outcome; no code edits."],
|
||||
[
|
||||
"consultant",
|
||||
"Read-only high-reasoning counsel for judgement calls and design critique.",
|
||||
],
|
||||
["custom", "An explicit narrow tool allowlist for locked-down dispatch."],
|
||||
title: "Run agents in parallel",
|
||||
lede:
|
||||
"Codewhale can hand a focused piece of work to a sub-agent and keep going while it runs. Use this when a task splits into independent parts — mapping a codebase, reviewing a change, running tests — so they happen at the same time.",
|
||||
sections: [
|
||||
{
|
||||
id: "ask",
|
||||
title: "Ask for it",
|
||||
blocks: [
|
||||
{
|
||||
p: "You do not start sub-agents with a command; you ask in plain words. In Operate, Codewhale does this on its own for independent steps. In Work, say what you want split up:",
|
||||
},
|
||||
{
|
||||
code: `Use three explore agents in parallel: one maps the auth code,
|
||||
one maps the billing code, one lists the tests that cover both.
|
||||
Then summarize what would break if we changed the session format.`,
|
||||
lang: "Prompt",
|
||||
},
|
||||
{
|
||||
p: "Each sub-agent starts in the background and reports back when it finishes. Your composer stays free, and the parent turn picks up the results.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "roles",
|
||||
title: "Pick a role",
|
||||
blocks: [
|
||||
{
|
||||
p: "A role is the stance a sub-agent takes. Name one in your request, or let Codewhale choose. A sub-agent never gets more permission than your session has.",
|
||||
},
|
||||
{
|
||||
rows: [
|
||||
["general", "Does whatever the brief says; may edit and run commands. The default."],
|
||||
["explore", "Read-only. Maps the relevant code fast — “find every caller of this function.”"],
|
||||
["planner", "Designs an approach without changing anything."],
|
||||
["reviewer", "Reads and grades a change, with a severity for each finding."],
|
||||
["implement", "Lands one specific change with the smallest edit."],
|
||||
["test", "Runs tests and checks, then reports pass or fail. Does not edit code."],
|
||||
["advisor", "Short, careful second opinion on a judgment call. No commands."],
|
||||
["custom", "Only the tools you list, for tightly limited jobs."],
|
||||
],
|
||||
codeTerms: true,
|
||||
},
|
||||
{
|
||||
p: "To give a role a specific model every time, save it with `/fleet setup` — see [Run a workflow](/docs/fleet).",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "worktrees",
|
||||
title: "Keep parallel edits apart",
|
||||
blocks: [
|
||||
{
|
||||
p: "When two sub-agents would edit the same repository, ask for each to work in its own worktree. Codewhale creates a fresh git worktree and branch for that agent beside your repository, under `.codewhale-worktrees/`, so your checkout stays clean until you merge.",
|
||||
},
|
||||
{
|
||||
p: "A worktree is isolation, not permission: an agent that should write still needs a writing role and the paths it may change. Two agents that claim the same files are stopped before either one edits anything.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "watch",
|
||||
title: "Watch and answer them",
|
||||
blocks: [
|
||||
{ code: "/subagents", lang: "Codewhale" },
|
||||
{
|
||||
p: "`/subagents` (the same view as `/fleet workers`) lists the agents attached to this session and what each one is doing. Select one to read its transcript.",
|
||||
},
|
||||
{
|
||||
p: "Sub-agents follow your [approval setting](/docs/modes). In Ask, a call that needs approval appears in your session as a normal prompt while the agent waits. In Auto-Review, each held call gets the same independent review, and nothing prompts you. Every decision you did not make yourself is written to the agent's transcript.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "limits",
|
||||
title: "Know the limits",
|
||||
blocks: [
|
||||
{
|
||||
rows: [
|
||||
["Running at once", "64 by default. Set `max_subagents` (up to 128) in `~/.codewhale/config.toml`."],
|
||||
["Queued plus running", "Up to 1,024; extra launches wait for a slot."],
|
||||
["Nesting", "A sub-agent may start its own, three levels deep by default, never more than eight."],
|
||||
],
|
||||
},
|
||||
{
|
||||
p: "These are ceilings, not targets. A few well-scoped agents with one clear summary beat many overlapping ones. For work that must survive a restart or a sleeping laptop, use a [Fleet run](/docs/fleet) instead.",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
forkTitle: "Context forking",
|
||||
forkLead:
|
||||
"{agentTool} starts fresh by default: the child gets its role prompt plus the task you pass. When the task depends on decisions, files, todos, or plan state already in the parent transcript, use {forkContext} — the runtime keeps the parent's request prefix byte-identical where available (preserving prefix-cache reuse), appends a structured state snapshot, then adds the sub-agent role instructions and task at the tail. Use fresh sessions for independent exploration and forked sessions for continuation, review, summarization, or compaction work.",
|
||||
worktreeTitle: "Worktree isolation",
|
||||
worktreeLead:
|
||||
"Launch parallel edit lanes with {worktreeFlag}: Codewhale creates a fresh git worktree and branch for the child (default {branchPattern}, checked out beside the parent repo under {worktreeDir}) so the parent checkout stays clean. Isolation is not write authority: a prompt-only worker starts read-only, and a writer also declares {writeAuthority} plus at least one normalized {writeRoots}, {exactFiles}, or {coordinationContracts} value. Overlapping shared write claims fail before any mutation.",
|
||||
capacityTitle: "Concurrency caps",
|
||||
capacityLead:
|
||||
"The sub-agent capacity source of truth is crates/tui/src/config/subagent_limits.rs: default configured concurrency is 64, maximum configured concurrency is 128, and maximum admitted running-plus-queued work is 1024. These are capacity ceilings, not advice to dispatch every slot — a manager should use the smallest useful fan-out, keep a single fan-in owner, and verify worker receipts before reporting combined completion.",
|
||||
sourceNote: "Source document: docs/SUBAGENTS.md · Update docs-map.ts when changing.",
|
||||
next: [
|
||||
{
|
||||
href: "/docs/fleet",
|
||||
label: "Run a workflow",
|
||||
note: "Turn a plan you repeat into a Workflow with phases and a record of each run.",
|
||||
},
|
||||
{
|
||||
href: "/docs/work",
|
||||
label: "Track progress",
|
||||
note: "How the To-do list shows what is done, in progress, and left.",
|
||||
},
|
||||
{
|
||||
href: "/docs/review",
|
||||
label: "Review what changed",
|
||||
note: "Check what the agents changed before you keep it.",
|
||||
},
|
||||
],
|
||||
sourceNote: "Source documents: docs/SUBAGENTS.md, docs/FLEET.md, docs/MODES.md · Update docs-map.ts when changing.",
|
||||
};
|
||||
@@ -1,46 +1,149 @@
|
||||
import type { DocsTroubleshootingDict } from "../types";
|
||||
|
||||
/**
|
||||
* English reference dictionary for
|
||||
* `app/[locale]/docs/troubleshooting/page.tsx`. Copy moved verbatim from the
|
||||
* page's `isZh` ternaries — any wording change belongs in its own commit,
|
||||
* never mixed into a structural move.
|
||||
* English reference dictionary for `app/[locale]/docs/troubleshooting/page.tsx`
|
||||
* ("Fix a problem"). Error texts and fixes come from docs/INSTALL.md §13
|
||||
* (every one was hit while writing that guide), docs/OPERATIONS_RUNBOOK.md,
|
||||
* docs/KEYBINDINGS.md (Ctrl-B), crates/tui/src/runtime_log.rs (log path),
|
||||
* and docs/DOCKER.md.
|
||||
*/
|
||||
export const docsTroubleshooting: DocsTroubleshootingDict = {
|
||||
metaTitle: "Troubleshooting · Codewhale Docs",
|
||||
metaTitle: "Fix a problem · Codewhale Docs",
|
||||
metaDescription:
|
||||
"Quick triage for common issues: hung turns, the offline queue, crash recovery, schema errors, MCP failures, and Docker notes.",
|
||||
"Diagnose Codewhale in one command, then fix the common problems: command not found, no reply, a rejected key, network errors, a stuck turn, a session that will not resume, and MCP servers.",
|
||||
bodyClassName: "text-ink-soft leading-relaxed",
|
||||
overviewTitle: "Troubleshooting",
|
||||
overviewLead:
|
||||
"Start with quick triage: confirm the binary and config (codewhale --version, ~/.codewhale/config.toml), enable verbose logs with RUST_LOG=codewhale_tui=debug when needed (RUST_LOG=codewhale_tui::client=debug for HTTP retries/reconnects; logs land in ~/.codewhale/logs/), and capture the current state of ~/.codewhale/sessions and ~/.codewhale/tasks.",
|
||||
incidents: [
|
||||
[
|
||||
"Turn hangs or the stream stops",
|
||||
"If a foreground shell command is still running, press Ctrl+B to move it to the background (the turn keeps running and the command becomes a background job under /jobs); use Esc or Ctrl+C to cancel the turn itself. Inspect codewhale_tui::client retry logs and endpoint connectivity, and after a restart confirm the previously in-flight turn shows as interrupted rather than running.",
|
||||
],
|
||||
[
|
||||
"Network outage / offline behavior",
|
||||
"New prompts queue while offline, persisted per session to ~/.codewhale/sessions/checkpoints/<session-id>.offline_queue.json (a legacy global offline_queue.json is adopted once on upgrade). Inspect with /queue list, restore connectivity, then re-send queued entries (/queue edit <n> plus Enter, or the normal input flow); the queue file clears when the queue empties.",
|
||||
],
|
||||
[
|
||||
"Crash recovery",
|
||||
"Each session checkpoints to ~/.codewhale/sessions/checkpoints/<session-id>.json (a legacy latest.json is still read but no longer written); startup begins a fresh session unless --resume/--continue is supplied. Resume explicitly with codewhale --resume <id> or Ctrl+R in the TUI; if the checkpoint schema is newer than the binary supports, upgrade the binary or remove the stale checkpoint.",
|
||||
],
|
||||
[
|
||||
"Persistent state schema errors",
|
||||
"Errors like schema vX is newer than supported vY affect sessions, runtime thread/turn/item records, and tasks. Confirm the binary version, back up the state directory before editing, then either run a newer compatible binary or archive the incompatible records and regenerate state.",
|
||||
],
|
||||
[
|
||||
"MCP / tool execution failures",
|
||||
"Validate the ~/.codewhale/mcp.json schema and server command paths, confirm the server process starts manually, and check sandbox denials in TUI history/logs. Use /mcp validate for diagnostics, temporarily disable a failing server to isolate the issue, and re-enable after verification.",
|
||||
],
|
||||
title: "Fix a problem",
|
||||
lede:
|
||||
"Start with one diagnostic command, then find your symptom below. Each fix names the exact message you will see.",
|
||||
sections: [
|
||||
{
|
||||
id: "diagnose",
|
||||
title: "Run the diagnostics",
|
||||
blocks: [
|
||||
{
|
||||
code: `codewhale --version
|
||||
codewhale doctor
|
||||
codewhale doctor --probe-api # one real test call to your provider
|
||||
codewhale auth status --provider deepseek # which key is in use`,
|
||||
lang: "Terminal",
|
||||
},
|
||||
{
|
||||
p: "`codewhale doctor --json` produces a diagnostics bundle without secrets, ready to attach to an issue. Plain `doctor` does not tell you which key is active and exits successfully even with no key; use `auth status` for that.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "install",
|
||||
title: "Install and update",
|
||||
blocks: [
|
||||
{
|
||||
rows: [
|
||||
["`codewhale: command not found`", "`~/.local/bin` is not on your PATH in this terminal. Add `export PATH=\"$HOME/.local/bin:$PATH\"` to your shell profile and open a new terminal."],
|
||||
["`npm error code EACCES`", "Your Node install is owned by the system. Do not use sudo: point npm at a folder you own with `npm config set prefix \"$HOME/.npm-global\"`, add its `bin` to your PATH, and install again."],
|
||||
["`refusing to replace existing ~/.local/bin/codewhale`", "A different version is already there. Run `codewhale update`, or remove the old binaries first."],
|
||||
["`checksum mismatch`", "The download was corrupted or altered, and nothing was installed. Try again; if it repeats, do not use a mirror."],
|
||||
["`The package-managed executable was not changed.`", "You installed with npm, Cargo, or Homebrew. Update with that tool, for example `npm install -g codewhale`."],
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "model",
|
||||
title: "No reply, or the key is rejected",
|
||||
blocks: [
|
||||
{
|
||||
rows: [
|
||||
["Your message appears but nothing answers", "No key is configured, and v0.10.0 does not warn you. Press F3, choose your provider, and paste the key."],
|
||||
["`API key not found`", "No key anywhere. Save one with `codewhale auth set --provider <name>`."],
|
||||
["`Authentication Fails … is invalid`", "The key is wrong or revoked. Run `auth status` to see which source is used — a saved key beats an environment variable — then save the right key or `codewhale auth clear --provider <name>`."],
|
||||
["`Network error: SSE stream request failed …`", "Usually no connection to the provider. Check with `curl -sI https://api.deepseek.com` (a 401 means it is reachable). Behind a proxy, export `HTTPS_PROXY`. On Windows or strict proxies, try `CODEWHALE_FORCE_HTTP1=1`."],
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "turn",
|
||||
title: "A turn is stuck",
|
||||
blocks: [
|
||||
{
|
||||
list: [
|
||||
"Press Esc to cancel the turn. Esc also closes menus first, so press it again if a menu was open.",
|
||||
"If a long shell command is holding the turn, press Ctrl-B to move it into the background. The turn continues, and `/jobs` shows the command.",
|
||||
"`/retry` sends the last request again.",
|
||||
],
|
||||
},
|
||||
{
|
||||
p: "For a detailed record, start Codewhale with `RUST_LOG=codewhale_tui=debug` (or `RUST_LOG=codewhale_tui::client=debug` for connection retries). Logs are written to `~/.codewhale/logs/`.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "sessions",
|
||||
title: "Resume a session",
|
||||
blocks: [
|
||||
{
|
||||
code: `codewhale sessions # list saved sessions
|
||||
codewhale resume <id> # an id or a unique prefix
|
||||
codewhale -c # the latest session in this folder`,
|
||||
lang: "Terminal",
|
||||
},
|
||||
{
|
||||
p: "Inside Codewhale, Ctrl-R opens the session picker. `No saved sessions found for workspace` after `codewhale exec --continue` means the earlier run was a plain `exec`, which is not saved; use `--output-format stream-json` for runs you want to continue.",
|
||||
},
|
||||
{
|
||||
p: "Messages you send while offline wait in a queue, saved with the session. `/queue list` shows them. When the connection is back, open one with `/queue edit <n>` and press Enter to send it.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "mcp",
|
||||
title: "MCP tools are missing",
|
||||
blocks: [
|
||||
{
|
||||
list: [
|
||||
"After changing `mcp.json` or a server's credentials, run `/mcp reload`. `/mcp validate` only refreshes what you see.",
|
||||
"Run the server's command yourself in a shell to confirm it starts.",
|
||||
"If the config file is missing or broken, `codewhale mcp init --force` writes a fresh one.",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "docker",
|
||||
title: "Run in Docker",
|
||||
blocks: [
|
||||
{
|
||||
code: `docker volume create codewhale-home
|
||||
docker run --rm -it \\
|
||||
-e DEEPSEEK_API_KEY="$DEEPSEEK_API_KEY" \\
|
||||
-v codewhale-home:/home/codewhale/.codewhale \\
|
||||
-v "$PWD:/workspace" -w /workspace \\
|
||||
ghcr.io/hmbown/codewhale:latest`,
|
||||
lang: "Terminal",
|
||||
},
|
||||
{
|
||||
p: "The image runs as a non-root user and keeps your settings and sessions in the named volume. Pin a release tag instead of `latest` for repeatable setups, use one volume per project, and never bake keys into an image.",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
next: [
|
||||
{
|
||||
href: "/docs/auth",
|
||||
label: "Connect a provider",
|
||||
note: "Save a key, check which one is used, or switch to a local model.",
|
||||
},
|
||||
{
|
||||
href: "/install",
|
||||
label: "Install Codewhale",
|
||||
note: "Every install method, with the output each step should print.",
|
||||
},
|
||||
{
|
||||
href: "/docs/review",
|
||||
label: "Review what changed",
|
||||
note: "Roll files back to the snapshot before a turn went wrong.",
|
||||
},
|
||||
],
|
||||
dockerTitle: "Docker notes",
|
||||
dockerLead:
|
||||
"Each release publishes a multi-arch Linux image to GitHub Container Registry. The default image is a conservative runtime image: it runs as the non-root codewhale user (UID/GID 1000:1000), grants no passwordless sudo, and keeps user state in a volume mounted at /home/codewhale/.codewhale. Pin a release tag instead of latest for reproducible installs.",
|
||||
dockerToolboxNote:
|
||||
"When a project needs apt-get, compiler toolchains, or package managers inside the container, do not change the default image contract — build an explicit toolbox image from docs/examples/Dockerfile.toolbox, and use one named state volume per project so sessions, config, and the offline queue do not bleed across workspaces. Never bake API keys or SSH private keys into custom images.",
|
||||
sourceNote:
|
||||
"Source documents: docs/OPERATIONS_RUNBOOK.md, docs/DOCKER.md · Update docs-map.ts when changing.",
|
||||
"Source documents: docs/INSTALL.md §13, docs/OPERATIONS_RUNBOOK.md, docs/DOCKER.md · Update docs-map.ts when changing.",
|
||||
};
|
||||
@@ -1,60 +1,108 @@
|
||||
import type { DocsTrustDict } from "../types";
|
||||
|
||||
/**
|
||||
* English reference dictionary for `app/[locale]/docs/trust/page.tsx`.
|
||||
* Statements trace to docs/SANDBOX.md, docs/AUTHORIZATION_ORDER.md,
|
||||
* docs/TELEMETRY.md, and the `trust` block of docs/public-surface-facts.json.
|
||||
* English reference dictionary for `app/[locale]/docs/trust/page.tsx`
|
||||
* ("See what leaves your machine"). Every claim traces to
|
||||
* docs/public-surface-facts.json (`trust`), docs/TELEMETRY.md, and
|
||||
* docs/SANDBOX.md. The security contact is code-owned in the page.
|
||||
*/
|
||||
export const docsTrust: DocsTrustDict = {
|
||||
metaTitle: "Security & trust · Codewhale Docs",
|
||||
metaTitle: "See what leaves your machine · Codewhale Docs",
|
||||
metaDescription:
|
||||
"What stays on your machine, what a hosted provider receives, how approvals and the OS sandbox differ, what telemetry sends and how to turn it off, and where to report a vulnerability.",
|
||||
"What stays local, what a model provider receives, what usage counting sends and how to turn it off, where the audit log lives, and how to report a vulnerability.",
|
||||
bodyClassName: "text-ink-soft leading-relaxed",
|
||||
overviewTitle: "Security & trust",
|
||||
overviewLead:
|
||||
"This page says what Codewhale does with your code, your commands, and your data — as implemented, not as intended. Each claim is drawn from a repository document that the facts gate checks on every build.",
|
||||
boundaryTitle: "Where your data goes",
|
||||
boundaries: [
|
||||
["Local runtime", "The runtime, workspace state, and audit log stay on your machine."],
|
||||
["Hosted provider", "The hosted provider you select receives the turn context required for inference. There is no mandatory Codewhale relay in between."],
|
||||
["Local inference", "A loopback local-model route (vLLM, Ollama, SGLang) can keep inference on your machine entirely."],
|
||||
["Account", "No account is required for the local runtime."],
|
||||
["Plan mode", "Plan is read-only."],
|
||||
title: "See what leaves your machine",
|
||||
lede:
|
||||
"Codewhale runs on your computer and talks to the model provider you choose. This page lists what goes where, what the anonymous usage count contains, and how to switch it off — as built today.",
|
||||
sections: [
|
||||
{
|
||||
id: "boundaries",
|
||||
title: "Where your work goes",
|
||||
blocks: [
|
||||
{
|
||||
rows: [
|
||||
["Stays on your machine", "The runtime, your workspace, session history, snapshots, and the audit log."],
|
||||
["Goes to your provider", "The context each turn needs — your messages, the files and tool results Codewhale reads for that turn — goes directly to the provider you selected. There is no Codewhale relay in between."],
|
||||
["Stays local entirely", "With a local model (Ollama, vLLM, SGLang), inference never leaves your machine."],
|
||||
["Needs no account", "Installing and running Codewhale locally needs no Codewhale account."],
|
||||
["Plan mode", "Cannot edit files or run shell commands. Research it is allowed to do may still contact outside services."],
|
||||
],
|
||||
},
|
||||
{
|
||||
p: "Tools you add can send data elsewhere: an MCP server, a web search, or a hook runs with your permissions and reaches whatever it reaches. Add only ones you trust.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "telemetry",
|
||||
title: "Know what usage counting sends",
|
||||
blocks: [
|
||||
{
|
||||
p: "Codewhale counts anonymous usage by default and says so the first time you launch it, naming Codewhale and PostHog. Here is exactly what it covers:",
|
||||
},
|
||||
{
|
||||
rows: [
|
||||
["Never sent", "Prompts, responses, code, diffs, file contents, file or repository or branch names, paths, model ids, MCP server names, API keys or tokens, error message text, keystrokes, or any per-turn or per-tool timeline."],
|
||||
["Sent while on", "Version and platform classes, session length and outcome, feature and error counts as fixed categories, and a random install id that changes every 90 days."],
|
||||
["Where it goes", "`https://telemetry.codewhale.net/v1/telemetry`, a first-party service whose source is in the repository. It stores no IP address, country, or location, keeps no request logs, and holds data for three months."],
|
||||
["PostHog", "Forwarding to PostHog happens only if the service operator configures it separately; it carries the same fields and nothing more."],
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "turn-off",
|
||||
title: "Turn usage counting off",
|
||||
blocks: [
|
||||
{
|
||||
code: `codewhale config set telemetry false # stop, and erase the local id and buffer
|
||||
CODEWHALE_TELEMETRY=0 codewhale # stop for this process, erase nothing`,
|
||||
lang: "Terminal",
|
||||
},
|
||||
{
|
||||
p: "The config setting is the lasting choice: later versions keep it, and a command-line flag or environment variable cannot turn it back on. You can also switch it in `/settings`. Turning it off deletes what was kept on your machine; rows already sent are keyed only to the random id you just erased, and expire with the three-month window.",
|
||||
},
|
||||
{
|
||||
p: "To see exactly what would be sent without sending anything, set `telemetry_endpoint = \"\"` in `~/.codewhale/config.toml`. Each batch is then written to `$CODEWHALE_HOME/telemetry/dryrun.jsonl` on your machine, byte for byte, and no network connection is made.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "audit",
|
||||
title: "Check the local audit log",
|
||||
blocks: [
|
||||
{
|
||||
p: "Credential, approval, and elevation events are appended to `$CODEWHALE_HOME/audit.log` (by default `~/.codewhale/audit.log`). Writing is best-effort: if a write fails, the failure is logged rather than hidden. The log never leaves your machine.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "report",
|
||||
title: "Report a vulnerability",
|
||||
blocks: [
|
||||
{
|
||||
p: "Please email security reports to the maintainer instead of opening a public issue. Include your Codewhale version (`codewhale --version`) and steps to reproduce if you have them.",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
approvalTitle: "Approvals are not a sandbox",
|
||||
approvalLead:
|
||||
"Approval posture — Ask, Auto-Review, Full Access — decides whether a proposed command is shown to you before it runs. An approval from one layer is never a universal bypass: a later layer can still require review or block the call, and an approval is not an operating-system sandbox grant. The full model tool-call pipeline is nine ordered layers, from effective configuration through hooks, typed permission rules, repository law, and human approval to the execution sandbox.",
|
||||
sandboxTitle: "The OS sandbox, per platform",
|
||||
sandboxLead:
|
||||
"Only behaviour wired into the command execution path is described here, and Codewhale reports the mechanism it actually selected.",
|
||||
sandboxes: [
|
||||
["macOS — Seatbelt", "Automatic when the runtime probe of sandbox-exec succeeds. Reported as {seatbelt}."],
|
||||
["Linux — bubblewrap", "Opt-in: {preferBwrap} and an executable /usr/bin/bwrap. Reported as {bwrap}."],
|
||||
["Linux without bwrap", "No OS wrapper by default. Reported as {none}."],
|
||||
["Windows", "No OS wrapper in the current implementation. Reported as {none}."],
|
||||
["External service", "{opensandbox} routes execution to an OpenSandbox-compatible service."],
|
||||
next: [
|
||||
{
|
||||
href: "/docs/sandbox",
|
||||
label: "Limit what commands can touch",
|
||||
note: "What the operating-system sandbox enforces on each platform.",
|
||||
},
|
||||
{
|
||||
href: "/docs/modes",
|
||||
label: "Set modes and approvals",
|
||||
note: "Decide what Codewhale may do without asking.",
|
||||
},
|
||||
{
|
||||
href: "/docs/auth",
|
||||
label: "Connect a provider",
|
||||
note: "Choose who receives your turns — or keep them local with a local model.",
|
||||
},
|
||||
],
|
||||
sandboxNote:
|
||||
"The repository also contains a seccomp module and a future Windows helper contract. Neither is wired into child-command launch, so Codewhale does not advertise them: source-only sandbox code is not evidence that a command was restricted.",
|
||||
telemetryTitle: "Telemetry, exactly",
|
||||
telemetryLead:
|
||||
"Codewhale 0.9.13 counts anonymous usage by default and tells you so at first launch; the earlier 0.9.11 release asked first. Turning it off is a saved choice that later versions keep, and an opt-out recorded under the earlier opt-in policy stays off. Showing the notice never records any acceptance on your behalf; Codewhale and PostHog are named as the processors.",
|
||||
telemetry: [
|
||||
["Never collected", "Conversations, code, prompts, files, file/repo/branch names, model content, credentials, or any per-turn or per-tool timeline."],
|
||||
["Sent while on", "Version and platform classes, session duration and outcome, feature and error counters, closed enums, and a random install id that rotates every 90 days."],
|
||||
["Endpoint", "{endpoint} — a first-party Cloudflare Worker whose source is in the repository under telemetry-ingest/."],
|
||||
["First-party storage", "No IP, country, or geo column. No request logs. Retention is a fixed three months."],
|
||||
["PostHog", "Optional forwarding requires separate operator configuration and verified IP-safe egress. PostHog retention is a separate project setting; source support does not mean the processor is active."],
|
||||
["Audit it yourself", "Set {dryRun}: batches are appended to {dryRunFile} on your machine, byte for byte what the server would have received, and no HTTP client is constructed."],
|
||||
["Turn it off", "{configOff} or {envOff}."],
|
||||
],
|
||||
auditTitle: "Local audit log",
|
||||
auditLead:
|
||||
"Sensitive events — credential, approval, and elevation events — append best-effort to {auditLog}. Write failures are logged rather than hidden. Provider token and cache usage is shown locally when available.",
|
||||
reportTitle: "Report a vulnerability",
|
||||
reportLead:
|
||||
"Send security reports by email to the maintainer rather than filing a public issue. Include the version from the masthead and a reproduction if you have one.",
|
||||
reportCta: "Email the maintainer",
|
||||
sourceNote:
|
||||
"Source documents: docs/SANDBOX.md, docs/AUTHORIZATION_ORDER.md, docs/TELEMETRY.md, docs/public-surface-facts.json · Update docs-map.ts when changing.",
|
||||
"Source documents: docs/public-surface-facts.json (trust), docs/TELEMETRY.md, docs/SANDBOX.md · Update docs-map.ts when changing.",
|
||||
};
|
||||
@@ -1,33 +1,103 @@
|
||||
import type { DocsWebDict } from "../types";
|
||||
|
||||
/**
|
||||
* English reference dictionary for `app/[locale]/docs/web/page.tsx`.
|
||||
* Copy moved verbatim from the page's `isZh` ternaries — any wording change
|
||||
* belongs in its own commit, never mixed into a structural move.
|
||||
* English reference dictionary for `app/[locale]/docs/web/page.tsx`
|
||||
* ("Open the browser client"). Checked against docs/WEB.md, `WebArgs` in
|
||||
* crates/cli/src/lib.rs (`--port`, default 7878), and the `/rc` command in
|
||||
* crates/tui/src/commands/groups/session/remote_control.rs.
|
||||
*/
|
||||
export const docsWeb: DocsWebDict = {
|
||||
metaTitle: "Browser Client · Codewhale Docs",
|
||||
metaTitle: "Open the browser client · Codewhale Docs",
|
||||
metaDescription:
|
||||
"The loopback-only embedded browser client — one-time bootstrap, session cookie, the local trust boundary — and remote control of a running local session from the signed-in web app with /rc.",
|
||||
"Work with Codewhale in a browser tab on your own machine, or continue a running terminal session from the Codewhale web app with /rc.",
|
||||
bodyClassName: "text-ink-soft leading-relaxed",
|
||||
overviewTitle: "Browser Client",
|
||||
overviewLead:
|
||||
"{webCommand} opens Codewhale's embedded browser client over the canonical Runtime API. It is a local surface: the server always binds to {loopbackHost}, cannot be rebound to a LAN address, and cannot run with Runtime authentication disabled. The default address is {defaultUrl}; on a port collision, pick another loopback port with {portExample}. Stop the process with Ctrl+C and the browser session ends with it.",
|
||||
overviewBody:
|
||||
"The current client provides a responsive thread and search rail, Runtime-owned session facts, transcript and tool receipts, and a composer. It can create, select, rename, and archive threads; start or steer turns; interrupt work; resolve approvals; and answer Runtime user-input requests. The browser is another view of the same local Runtime — it does not create a second cloud account, copy provider credentials into browser storage, or weaken the configured approval and sandbox policies.",
|
||||
authTitle: "Authentication boundary",
|
||||
authLead:
|
||||
"The browser-launch URL carries a random, short-lived, one-time bootstrap capability — never the Runtime bearer token. A loopback request exchanges it for an HttpOnly, SameSite=Strict, process-local session cookie and immediately invalidates the capability. Reused, expired, malformed, and non-loopback bootstrap attempts fail closed. The Runtime token is never placed in rendered HTML, browser storage, URL queries or fragments, or browser-launch arguments. Cookie-authenticated state-changing requests must also present the exact local web origin; cross-origin browser requests are rejected.",
|
||||
localTitle: "Local means local",
|
||||
localLead:
|
||||
"{webCommand} accepts only {portFlag} — there is no {hostFlag} and no insecure-auth option on this command. Do not treat it as a public website or expose its port through router forwarding, a public reverse proxy, or a tunnel. The separate {mobileCommand} and {httpFlag} modes carry different deployment and authentication contracts; read the Runtime API documentation before operating either one, especially before selecting a non-loopback bind.",
|
||||
remoteTitle: "Remote control from the web app",
|
||||
remoteLead:
|
||||
"Available now. To continue the exact running local session from the signed-in Codewhale web app, type /rc in that session or launch with codewhale rc, then approve the one-time code in your browser. While the lease is active the browser owns new prompts and approvals and the terminal stays a readable safety surface; interrupt remains available from both.",
|
||||
remoteBody:
|
||||
"Once connected, the banner and a transcript note show the live session link. /rc open opens it in your browser, /rc link prints it, /rc status shows who owns the session, and /rc stop returns it to the terminal. A dropped connection keeps local input locked until the last web lease expires, so two controllers never race. Every folder you enroll from one terminal shares a single stable device id, so the web app lists one computer per machine, not one per session. This is different from the loopback browser client above: /rc pairs a local session with your account; codewhale web serves a local page with no account at all.",
|
||||
troubleshootingTitle: "Troubleshooting",
|
||||
troubleshootingLead:
|
||||
"If port 7878 is occupied, pass an unused --port. If the browser cannot be opened, the command exits with an error rather than leaving a reusable bootstrap capability behind; check the OS default-browser setup and start again. If the page loads but a provider is unavailable, inspect codewhale doctor and /provider — the web command does not configure or move provider credentials. If a session expired, restart codewhale web to mint a new process-local session; reusing an old bootstrap URL is expected to fail.",
|
||||
title: "Open the browser client",
|
||||
lede:
|
||||
"Prefer a browser window to a terminal? Codewhale can serve its own client on your machine. It is another view of the same local session — same approvals, same sandbox, no account.",
|
||||
sections: [
|
||||
{
|
||||
id: "start",
|
||||
title: "Start it",
|
||||
blocks: [
|
||||
{ p: "Run this from the folder you want Codewhale to work in:" },
|
||||
{ code: "codewhale web\ncodewhale web --port 8788 # if 7878 is taken", lang: "Terminal" },
|
||||
{
|
||||
p: "Codewhale starts a local server at `http://127.0.0.1:7878`, prints a one-time link, and opens it in your default browser. If the browser does not open, use the printed link within ten minutes. Press Ctrl+C in the terminal to stop; the browser session ends with it.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "use",
|
||||
title: "Work in the browser",
|
||||
blocks: [
|
||||
{
|
||||
p: "The browser client lists and searches your threads, shows the transcript with each tool's result, and has a composer. You can start, steer, or interrupt a turn, answer approvals, and rename or archive threads. Your provider keys stay in Codewhale; nothing is copied into browser storage.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "local",
|
||||
title: "Keep it local",
|
||||
blocks: [
|
||||
{
|
||||
list: [
|
||||
"The server only listens on `127.0.0.1`. There is no option to open it to your network, and it cannot run without authentication.",
|
||||
"The link carries a single-use code, not your access token. Opening it swaps the code for a cookie tied to this process, and the code stops working.",
|
||||
"Do not forward the port through a router, a public proxy, or a tunnel. For a phone or another machine, see the [Runtime API](/docs/runtime-api) and read its authentication rules first.",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "remote",
|
||||
title: "Continue a session from the web app",
|
||||
blocks: [
|
||||
{
|
||||
p: "This is different: it hands a session already running in your terminal to the signed-in Codewhale web app, so you can keep going from another device. It needs a [Codewhale account](/docs/auth#account).",
|
||||
},
|
||||
{
|
||||
code: `/rc # in the running session; approve the one-time code in your browser
|
||||
/rc status # who controls the session now
|
||||
/rc link # print the session link
|
||||
/rc stop # hand control back to the terminal`,
|
||||
lang: "Codewhale",
|
||||
},
|
||||
{
|
||||
p: "While the web app holds the session, new prompts and approvals come from the browser, and the terminal stays readable. Either side can interrupt. You can also start a session this way with `codewhale rc`.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "fix",
|
||||
title: "If something goes wrong",
|
||||
blocks: [
|
||||
{
|
||||
rows: [
|
||||
["Port in use", "Pass a free port with `--port`."],
|
||||
["Browser did not open", "Copy the printed link into a browser on the same machine within ten minutes."],
|
||||
["Link expired or already used", "That is expected. Run `codewhale web` again for a new one."],
|
||||
["No model answers", "The web command does not set up providers. Check `codewhale doctor` and `/provider`."],
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
next: [
|
||||
{
|
||||
href: "/docs/runtime-api",
|
||||
label: "Automate with the Runtime API",
|
||||
note: "The local API the browser client is built on.",
|
||||
},
|
||||
{
|
||||
href: "/docs/auth",
|
||||
label: "Connect a provider",
|
||||
note: "Give the browser client a model to talk to.",
|
||||
},
|
||||
{
|
||||
href: "/docs/modes",
|
||||
label: "Set modes and approvals",
|
||||
note: "The same approvals apply in the browser.",
|
||||
},
|
||||
],
|
||||
sourceNote: "Source document: docs/WEB.md · Update docs-map.ts when changing.",
|
||||
};
|
||||
@@ -1,37 +1,106 @@
|
||||
import type { DocsWorkDict } from "../types";
|
||||
|
||||
/**
|
||||
* English reference dictionary for `app/[locale]/docs/work/page.tsx`.
|
||||
* Copy moved verbatim from the page's `isZh` ternaries — any wording change
|
||||
* belongs in its own commit, never mixed into a structural move.
|
||||
* English reference dictionary for `app/[locale]/docs/work/page.tsx`
|
||||
* ("Track progress"). Checked against docs/GUIDE.md (workbar), docs/MODES.md
|
||||
* (goals), docs/TOOL_SURFACE.md (the To-do list is the one progress ledger),
|
||||
* and the `/workbar`, `/goal`, and `/relay` command definitions.
|
||||
*/
|
||||
export const docsWork: DocsWorkDict = {
|
||||
metaTitle: "Work Surface · Codewhale Docs",
|
||||
metaTitle: "Track progress · Codewhale Docs",
|
||||
metaDescription:
|
||||
"The single To-do list, how the model sees it, and how one work state stays continuous.",
|
||||
"Follow a multi-step task in the workbar: the goal, the To-do list, and sub-agents. Set a goal that lasts across turns, and hand the work to a fresh session without losing your place.",
|
||||
bodyClassName: "text-ink-soft leading-relaxed",
|
||||
overviewTitle: "The Work surface",
|
||||
overviewLead:
|
||||
"The TUI sidebar has a Work area that shows live state for the current job. It is more than a visual to-do list: the same work state is maintained by model-visible tools, session relay, and sub-agent handoff. Codewhale has exactly one Work surface — the counted To-do execution ledger. update_plan is conversational reasoning, not a second progress surface.",
|
||||
checklistTitle: "To-do: the sole canonical ledger",
|
||||
checklistBody:
|
||||
"The To-do is the progress ledger for concrete work: a list of items with status (pending / in_progress / completed / cancelled), a completion percentage, and the item currently in progress. The model replaces this projection for the active thread or durable task through the canonical {todoWrite} tool — the model-visible progress surface. The legacy {checklistAlias} and {todoAlias} names remain hidden compatibility aliases: they stay dispatchable against the same To-do state so old transcripts replay, but they are not advertised to the model catalog.",
|
||||
strategyTitle: "Strategy is conversational reasoning: update_plan",
|
||||
strategyLead:
|
||||
"update_plan carries optional high-level strategy — it is not a second list. Its fields serve phase-level understanding: title, objective, context summary, explanation, sources, critical files, constraints, recommended approach, verification plan, risks and unknowns, a handoff packet, and a list of steps. It helps a parent session or a later worker understand the approach; concrete execution progress always belongs to the To-do list. The sidebar deliberately does not render strategy state as a second progress list, and neither does any To-do snapshot surface — plan state with an empty To-do produces no snapshot at all.",
|
||||
continuityTitle: "Continuity: one state, many surfaces",
|
||||
continuityLead:
|
||||
"The model learns the To-do from its own tool results: what todo_write returned is ordinary conversation history, so nothing re-states the list on each step. Only at a seam a person asked for does one renderer show the current To-do once: a forked (fork_context) sub-agent receives that body inside its structured state block, and /relay writes the same body into the handoff instruction. The To-do body is byte-identical in both, so a child agent and the next thread continue from the parent's real progress position instead of a paraphrased summary. The sidebar renders that same state live, in full.",
|
||||
captureTitle: "Terminal capture (faithful text)",
|
||||
captureLead:
|
||||
"This text block reproduces the sidebar Work area line-for-line from the rendering logic in crates/tui/src/tui/sidebar.rs: the goal row with its ◆ icon, elapsed time, and token budget bar, then the settled counter and the numbered status items.",
|
||||
captureLegend:
|
||||
"The item prefixes map to the four statuses: {pending} pending, {inProgress} in progress, {completed} completed, {cancelled} cancelled. When space runs out, the sidebar windows around the in-progress item and marks the omission with “+N more To-do items”.",
|
||||
modelFacingTitle: "What is model-facing vs. visual-only",
|
||||
modelFacingLead:
|
||||
"Three model-facing paths are implemented and covered by tests: the todo_write tool itself, which is active in the model catalog and whose tool result is how the model sees the list; the forked sub-agent's structured state block (the To-do section inside <codewhale:fork_state>, resolved at the moment of the fork); and /relay output. No request re-states the To-do on any step — a structural test asserts the real outbound request body does not contain the list. The sidebar rendering is a visual presentation — it informs the operator and is not injected into model context.",
|
||||
modelFacingBoundaries:
|
||||
"The boundaries are worth stating: because nothing is injected per step, the stable system-and-tool prefix is untouched by To-do changes and prefix caching is never invalidated by them. The one snapshot taken at a fork reads the authoritative state (the work graph's staged projection where one exists, not the not-yet-published legacy view), so a todo_write made earlier in the same turn is included. Item count and character count are both hard-bounded, the in-progress item is preserved preferentially, and elided content is marked. An empty To-do emits nothing at all. The renderer guarantees exactly three things — wrapper framing cannot be closed early, control characters cannot forge the line format, and the bounds hold. It does not vet what item text says, so arbitrary To-do content is not thereby made safe to follow as instructions.",
|
||||
title: "Track progress",
|
||||
lede:
|
||||
"When a task takes more than one step, Codewhale keeps a To-do list and shows it in the workbar, next to the goal and any sub-agents. You can see what is done, what is in progress, and what is left, while the transcript keeps scrolling.",
|
||||
sections: [
|
||||
{
|
||||
id: "workbar",
|
||||
title: "Read the workbar",
|
||||
blocks: [
|
||||
{
|
||||
p: "The workbar sits under the composer by default. It holds the active goal, the To-do list, and the sub-agents working for this session. Finished items stay visible as done instead of disappearing. Select a row, or press Enter on it, to open its detail.",
|
||||
},
|
||||
{
|
||||
rows: [
|
||||
["pending", "Not started yet."],
|
||||
["in progress", "What Codewhale is doing now — one item at a time."],
|
||||
["completed", "Done."],
|
||||
["cancelled", "Dropped, and kept in the list so you can see it was."],
|
||||
],
|
||||
},
|
||||
{ p: "Move it or change what it shows:" },
|
||||
{ code: "/workbar left\n/workbar bottom --save\n/workbar off", lang: "Codewhale" },
|
||||
{
|
||||
p: "Positions are `bottom`, `top`, `left`, `right`, and `off`. `--save` keeps your choice for future sessions.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "goal",
|
||||
title: "Set a goal that lasts across turns",
|
||||
blocks: [
|
||||
{
|
||||
p: "A goal keeps one objective in view until it is met. Set it yourself, or let Codewhale set one when you ask for a clear end state, such as “until the tests pass.” It then shows one line saying so, and you stay in control:",
|
||||
},
|
||||
{
|
||||
code: `/goal make the CLI tests pass on Windows budget: 200000
|
||||
/goal # show progress
|
||||
/goal pause
|
||||
/goal resume
|
||||
/goal done
|
||||
/goal clear`,
|
||||
lang: "Codewhale",
|
||||
},
|
||||
{
|
||||
p: "The optional `budget:` is a token limit for the goal. A goal does not change your mode, approval setting, or model.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "relay",
|
||||
title: "Continue in a fresh session",
|
||||
blocks: [
|
||||
{
|
||||
p: "When a session gets long, `/relay` writes a handoff for a new thread, including the current To-do list exactly as it stands, so the next session starts from where you really are rather than a summary of it. Add a focus to steer the handoff:",
|
||||
},
|
||||
{ code: "/relay finish the Windows test fixes", lang: "Codewhale" },
|
||||
{
|
||||
p: "Sub-agents started with the parent's context receive the same list, so they also know what is already done.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "one-list",
|
||||
title: "Know which list counts",
|
||||
blocks: [
|
||||
{
|
||||
p: "There is exactly one progress list: the To-do. A plan Codewhale writes out — its approach, risks, and checks — explains how it will work but does not track progress, and the workbar does not show it as a second list.",
|
||||
},
|
||||
{
|
||||
p: "The list in the workbar is for you. Codewhale sees its own updates as ordinary results in the conversation; the list is not re-sent to the model on every step.",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
next: [
|
||||
{
|
||||
href: "/docs/subagents",
|
||||
label: "Run agents in parallel",
|
||||
note: "Hand independent To-do items to sub-agents.",
|
||||
},
|
||||
{
|
||||
href: "/docs/modes",
|
||||
label: "Set modes and approvals",
|
||||
note: "Operate plans named steps and checks each result.",
|
||||
},
|
||||
{
|
||||
href: "/docs/review",
|
||||
label: "Review what changed",
|
||||
note: "Check the work behind each completed item.",
|
||||
},
|
||||
],
|
||||
sourceNote:
|
||||
"Source documents: docs/TOOL_SURFACE.md, docs/TOOL_LIFECYCLE.md · Update docs-map.ts when changing.",
|
||||
"Source documents: docs/GUIDE.md, docs/MODES.md, docs/TOOL_SURFACE.md, docs/TOOL_LIFECYCLE.md · Update docs-map.ts when changing.",
|
||||
};
|
||||
@@ -28,6 +28,7 @@ import type {
|
||||
DocsSandboxDict,
|
||||
DocsShellDict,
|
||||
DocsModesDict,
|
||||
DocsReviewDict,
|
||||
DocsSubagentsDict,
|
||||
DocsTroubleshootingDict,
|
||||
DocsTrustDict,
|
||||
@@ -75,6 +76,8 @@ import { docsAuth as enDocsAuth } from "./en/docs-auth";
|
||||
import { docsAuth as zhDocsAuth } from "./zh/docs-auth";
|
||||
import { docsTrust as enDocsTrust } from "./en/docs-trust";
|
||||
import { docsTrust as zhDocsTrust } from "./zh/docs-trust";
|
||||
import { docsReview as enDocsReview } from "./en/docs-review";
|
||||
import { docsReview as zhDocsReview } from "./zh/docs-review";
|
||||
import { computerUse as enComputerUse } from "./en/computer-use";
|
||||
import { computerUse as zhComputerUse } from "./zh/computer-use";
|
||||
import { computerUse as jaComputerUse } from "./ja/computer-use";
|
||||
@@ -292,6 +295,10 @@ const DOCS_TRUST: Record<string, DocsTrustDict> = {
|
||||
zh: zhDocsTrust,
|
||||
};
|
||||
|
||||
const DOCS_REVIEW: Record<string, DocsReviewDict> = {
|
||||
zh: zhDocsReview,
|
||||
};
|
||||
|
||||
/**
|
||||
* Shared surface states, the changelog page, the two legal pages and the
|
||||
* digest page follow the same optional per-locale rule as the docs page
|
||||
@@ -394,6 +401,10 @@ export function getDocsTrust(locale: string): DocsTrustDict {
|
||||
return DOCS_TRUST[locale] ?? enDocsTrust;
|
||||
}
|
||||
|
||||
export function getDocsReview(locale: string): DocsReviewDict {
|
||||
return DOCS_REVIEW[locale] ?? enDocsReview;
|
||||
}
|
||||
|
||||
export function getComputerUse(locale: string): ComputerUseDict {
|
||||
return COMPUTER_USE[locale] ?? enComputerUse;
|
||||
}
|
||||
@@ -454,6 +465,7 @@ export const EN_DOCS_WORK = enDocsWork;
|
||||
export const EN_DOCS_COMPUTERS = enDocsComputers;
|
||||
export const EN_DOCS_AUTH = enDocsAuth;
|
||||
export const EN_DOCS_TRUST = enDocsTrust;
|
||||
export const EN_DOCS_REVIEW = enDocsReview;
|
||||
export const EN_COMPUTER_USE = enComputerUse;
|
||||
export const EN_STATES = enStates;
|
||||
export const EN_CHANGELOG = enChangelog;
|
||||
|
||||
@@ -348,6 +348,14 @@ export interface DocsShellDict {
|
||||
helpFaq: string;
|
||||
helpDiscord: string;
|
||||
helpIssue: string;
|
||||
|
||||
// --- docs page bodies (app/[locale]/docs/_components/doc-article.tsx) ---
|
||||
/** Heading over each page's closing "where to go next" links. */
|
||||
nextHeading: string;
|
||||
/** Word (with its punctuation) that marks an aside, so it reads without color. */
|
||||
noteLabel: string;
|
||||
/** Accessible name of a page's table of contents. */
|
||||
onThisPage: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -394,104 +402,62 @@ export interface StatesDict {
|
||||
lastChecked: string;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Docs page bodies */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* `app/[locale]/docs/computers/page.tsx` — cloud computers and dispatch.
|
||||
* Every claim traces to docs/DAYTONA_CLOUD_DISPATCH.md and
|
||||
* docs/CODEWHALE_AGENT.md; commands stay code-owned literals in the page.
|
||||
* One content block on a docs page. Strings are prose: `code` in backticks
|
||||
* is typeset as inline code and `[label](/docs/x)` becomes a locale-aware
|
||||
* link (see `app/[locale]/docs/_components/doc-article.tsx`). Commands in
|
||||
* `code` blocks are shown verbatim and must match the engine source.
|
||||
*/
|
||||
export interface DocsComputersDict {
|
||||
metaTitle: string;
|
||||
metaDescription: string;
|
||||
bodyClassName: string;
|
||||
overviewTitle: string;
|
||||
overviewLead: string;
|
||||
proposeTitle: string;
|
||||
proposeLead: string;
|
||||
jobsLead: string;
|
||||
remotesTitle: string;
|
||||
remotesLead: string;
|
||||
/** `[remote name, forge]` rows of the explicit-forge table. */
|
||||
remotes: [string, string][];
|
||||
enableTitle: string;
|
||||
enableLead: string;
|
||||
/** `[step, detail]` rows. */
|
||||
enableSteps: [string, string][];
|
||||
cliNote: string;
|
||||
rulesTitle: string;
|
||||
/** `[situation, outcome]` rows of the fail-closed table. */
|
||||
rules: [string, string][];
|
||||
membershipTitle: string;
|
||||
membershipLead: string;
|
||||
leftoverTitle: string;
|
||||
/** `[item, note]` rows. */
|
||||
leftover: [string, string][];
|
||||
sourceNote: string;
|
||||
export type DocsBlock =
|
||||
| { p: string }
|
||||
/** A copyable command or config block; `lang` labels it (e.g. "Terminal"). */
|
||||
| { code: string; lang?: string }
|
||||
/** A `[term, detail]` table; `codeTerms` sets the term column as code. */
|
||||
| { rows: readonly (readonly [string, string])[]; codeTerms?: boolean }
|
||||
/** Numbered steps, in order. */
|
||||
| { steps: readonly string[] }
|
||||
/** An unordered list. */
|
||||
| { list: readonly string[] }
|
||||
/** A short aside: a limit, a caution, or something not built yet. */
|
||||
| { note: string };
|
||||
|
||||
export interface DocsSection {
|
||||
/** Stable anchor id. */
|
||||
id: string;
|
||||
title: string;
|
||||
blocks: readonly DocsBlock[];
|
||||
}
|
||||
|
||||
/**
|
||||
* `app/[locale]/docs/auth/page.tsx` — account, sign-in, and key storage.
|
||||
* Claims trace to docs/CONFIGURATION.md, docs/CODEWHALE_AGENT.md, and
|
||||
* docs/PROVIDERS.md.
|
||||
* The one shape every task page in `/docs` uses: what this page helps you
|
||||
* do (title + lede), how (sections), and where to go next.
|
||||
*/
|
||||
export interface DocsAuthDict {
|
||||
export interface DocsPageDict {
|
||||
metaTitle: string;
|
||||
metaDescription: string;
|
||||
/** Body-copy typography for this locale (CJK needs looser leading). */
|
||||
bodyClassName: string;
|
||||
overviewTitle: string;
|
||||
overviewLead: string;
|
||||
/** `[credential, role]` rows. */
|
||||
credentials: [string, string][];
|
||||
providerTitle: string;
|
||||
providerLead: string;
|
||||
accountTitle: string;
|
||||
accountLead: string;
|
||||
/** `[command, effect]` rows. */
|
||||
accountCommands: [string, string][];
|
||||
storageTitle: string;
|
||||
storageLead: string;
|
||||
vaultTitle: string;
|
||||
vaultLead: string;
|
||||
portableTitle: string;
|
||||
portableLead: string;
|
||||
appTitle: string;
|
||||
appLead: string;
|
||||
appSignIn: string;
|
||||
appRegister: string;
|
||||
title: string;
|
||||
/** What the reader can do here and why — two sentences at most. */
|
||||
lede: string;
|
||||
sections: readonly DocsSection[];
|
||||
/** Where to go next; `href` is locale-relative. */
|
||||
next: readonly { href: string; label: string; note: string }[];
|
||||
/** Maintainer pointer, kept out of the rendered copy. */
|
||||
sourceNote: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* `app/[locale]/docs/trust/page.tsx` — security and trust reference.
|
||||
* Claims trace to docs/SANDBOX.md, docs/AUTHORIZATION_ORDER.md,
|
||||
* docs/TELEMETRY.md, and docs/public-surface-facts.json.
|
||||
*/
|
||||
export interface DocsTrustDict {
|
||||
metaTitle: string;
|
||||
metaDescription: string;
|
||||
bodyClassName: string;
|
||||
overviewTitle: string;
|
||||
overviewLead: string;
|
||||
boundaryTitle: string;
|
||||
/** `[boundary, statement]` rows. */
|
||||
boundaries: [string, string][];
|
||||
approvalTitle: string;
|
||||
approvalLead: string;
|
||||
sandboxTitle: string;
|
||||
sandboxLead: string;
|
||||
/** `[platform, behaviour]` rows. */
|
||||
sandboxes: [string, string][];
|
||||
sandboxNote: string;
|
||||
telemetryTitle: string;
|
||||
telemetryLead: string;
|
||||
/** `[fact, detail]` rows. */
|
||||
telemetry: [string, string][];
|
||||
auditTitle: string;
|
||||
auditLead: string;
|
||||
reportTitle: string;
|
||||
reportLead: string;
|
||||
reportCta: string;
|
||||
sourceNote: string;
|
||||
}
|
||||
export type DocsReviewDict = DocsPageDict;
|
||||
|
||||
export type DocsComputersDict = DocsPageDict;
|
||||
|
||||
export type DocsAuthDict = DocsPageDict;
|
||||
|
||||
export type DocsTrustDict = DocsPageDict;
|
||||
|
||||
/** `app/[locale]/changelog/page.tsx` — version-aware release record. */
|
||||
export interface ChangelogDict {
|
||||
@@ -554,63 +520,11 @@ export interface DigestDict {
|
||||
lead: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* `app/[locale]/docs/hooks/page.tsx`.
|
||||
*
|
||||
* `configIntro` carries three `{token}` placeholders for the literal
|
||||
* `[[hooks.hooks]]`, `/hooks` and `[hooks].enabled` spans the page typesets
|
||||
* as inline `<code>`. They stay out of the prose because they are config
|
||||
* syntax rather than copy, and the token-parity half of `check-locales.mjs`
|
||||
* then guards the sentence for free.
|
||||
*/
|
||||
export interface DocsHooksDict {
|
||||
metaTitle: string;
|
||||
metaDescription: string;
|
||||
/** Body-copy typography for this locale (CJK needs looser leading). */
|
||||
bodyClassName: string;
|
||||
overviewTitle: string;
|
||||
overviewLead: string;
|
||||
configIntro: string;
|
||||
/** Lifecycle events as `[name, detail]`, in the order the page lists them. */
|
||||
events: [string, string][];
|
||||
projectTitle: string;
|
||||
projectLead: string;
|
||||
sourceNote: string;
|
||||
}
|
||||
export type DocsHooksDict = DocsPageDict;
|
||||
|
||||
/** `app/[locale]/docs/troubleshooting/page.tsx`. */
|
||||
export interface DocsTroubleshootingDict {
|
||||
metaTitle: string;
|
||||
metaDescription: string;
|
||||
/** Body-copy typography for this locale (CJK needs looser leading). */
|
||||
bodyClassName: string;
|
||||
overviewTitle: string;
|
||||
overviewLead: string;
|
||||
/** Triage entries as `[name, detail]`, in the order the page lists them. */
|
||||
incidents: [string, string][];
|
||||
dockerTitle: string;
|
||||
dockerLead: string;
|
||||
dockerToolboxNote: string;
|
||||
sourceNote: string;
|
||||
}
|
||||
export type DocsTroubleshootingDict = DocsPageDict;
|
||||
|
||||
/** `app/[locale]/docs/configuration/page.tsx`. */
|
||||
export interface DocsConfigurationDict {
|
||||
metaTitle: string;
|
||||
metaDescription: string;
|
||||
bodyClassName: string;
|
||||
overviewTitle: string;
|
||||
overviewLead: string;
|
||||
auditLead: string;
|
||||
overlayTitle: string;
|
||||
overlayLead: string;
|
||||
overlayLimits: string;
|
||||
credentialsTitle: string;
|
||||
credentialsLead: string;
|
||||
legacyTitle: string;
|
||||
legacyLead: string;
|
||||
sourceNote: string;
|
||||
}
|
||||
export type DocsConfigurationDict = DocsPageDict;
|
||||
|
||||
/**
|
||||
* `app/[locale]/docs/constitution/page.tsx`.
|
||||
@@ -638,165 +552,21 @@ export interface DocsConstitutionDict {
|
||||
sourceNote: string;
|
||||
}
|
||||
|
||||
/** `app/[locale]/docs/fleet/page.tsx` (the `DocsFleetDict` name is internal compatibility). */
|
||||
export interface DocsFleetDict {
|
||||
metaTitle: string;
|
||||
metaDescription: string;
|
||||
bodyClassName: string;
|
||||
overviewTitle: string;
|
||||
overviewLead: string;
|
||||
runTitle: string;
|
||||
runLead: string;
|
||||
statusLead: string;
|
||||
profilesTitle: string;
|
||||
profilesLead: string;
|
||||
workflowTitle: string;
|
||||
workflowLead: string;
|
||||
workflowLimits: string;
|
||||
sourceNote: string;
|
||||
}
|
||||
export type DocsFleetDict = DocsPageDict;
|
||||
|
||||
/** `app/[locale]/docs/mcp/page.tsx`. */
|
||||
export interface DocsMcpDict {
|
||||
metaTitle: string;
|
||||
metaDescription: string;
|
||||
/** Body-copy typography for this locale (CJK needs looser leading). */
|
||||
bodyClassName: string;
|
||||
overviewLead: string;
|
||||
overviewConfig: string;
|
||||
setupTitle: string;
|
||||
setupLead: string;
|
||||
setupReload: string;
|
||||
authTitle: string;
|
||||
authLead: string;
|
||||
toolsTitle: string;
|
||||
toolsLead: string;
|
||||
toolsTrust: string;
|
||||
serverTitle: string;
|
||||
serverLead: string;
|
||||
sourceNote: string;
|
||||
}
|
||||
export type DocsMcpDict = DocsPageDict;
|
||||
|
||||
/** `app/[locale]/docs/modes/page.tsx`. */
|
||||
export interface DocsModesDict {
|
||||
metaTitle: string;
|
||||
metaDescription: string;
|
||||
bodyClassName: string;
|
||||
overviewTitle: string;
|
||||
overviewLead: string;
|
||||
modes: [string, string][];
|
||||
switchingTitle: string;
|
||||
switchingLead: string;
|
||||
switchingCommandLead: string;
|
||||
permissionsTitle: string;
|
||||
permissionsLead: string;
|
||||
postures: [string, string][];
|
||||
sourceNote: string;
|
||||
}
|
||||
export type DocsModesDict = DocsPageDict;
|
||||
|
||||
/**
|
||||
* `app/[locale]/docs/runtime-api/page.tsx`.
|
||||
*
|
||||
* Only `securityLead` is tokenized. The command names elsewhere on the page
|
||||
* are set as prose rather than `<code>`, so they stay inside the sentences
|
||||
* exactly as the `isZh` ternaries had them.
|
||||
*/
|
||||
export interface DocsRuntimeApiDict {
|
||||
metaTitle: string;
|
||||
metaDescription: string;
|
||||
/** Body-copy typography for this locale (CJK needs looser leading). */
|
||||
bodyClassName: string;
|
||||
overviewTitle: string;
|
||||
overviewLead: string;
|
||||
/** Seven `[key, detail]` rows; the key selects the page's command literal. */
|
||||
entries: [string, string][];
|
||||
stdioTitle: string;
|
||||
stdioLead: string;
|
||||
interruptNote: string;
|
||||
securityTitle: string;
|
||||
securityLead: string;
|
||||
sourceNote: string;
|
||||
}
|
||||
export type DocsRuntimeApiDict = DocsPageDict;
|
||||
|
||||
export interface DocsSandboxDict {
|
||||
metaTitle: string;
|
||||
metaDescription: string;
|
||||
bodyClassName: string;
|
||||
overviewTitle: string;
|
||||
overviewLead: string;
|
||||
/** Four `[name, detail]` rows; the name is also the row's key. */
|
||||
platforms: [string, string][];
|
||||
policiesTitle: string;
|
||||
policiesLead: string;
|
||||
diagnosticsTitle: string;
|
||||
diagnosticsLead: string;
|
||||
diagnosticsLimits: string;
|
||||
sourceNote: string;
|
||||
}
|
||||
export type DocsSandboxDict = DocsPageDict;
|
||||
|
||||
/** `app/[locale]/docs/subagents/page.tsx`. */
|
||||
export interface DocsSubagentsDict {
|
||||
metaTitle: string;
|
||||
metaDescription: string;
|
||||
/** Body-copy typography for this locale (CJK needs looser leading). */
|
||||
bodyClassName: string;
|
||||
overviewTitle: string;
|
||||
overviewLead: string;
|
||||
overviewFleetNote: string;
|
||||
/** Eight `[key, detail]` rows; the key names the page's role literal. */
|
||||
roles: [string, string][];
|
||||
forkTitle: string;
|
||||
forkLead: string;
|
||||
worktreeTitle: string;
|
||||
worktreeLead: string;
|
||||
capacityTitle: string;
|
||||
capacityLead: string;
|
||||
sourceNote: string;
|
||||
}
|
||||
export type DocsSubagentsDict = DocsPageDict;
|
||||
|
||||
export interface DocsWebDict {
|
||||
metaTitle: string;
|
||||
metaDescription: string;
|
||||
bodyClassName: string;
|
||||
overviewTitle: string;
|
||||
overviewLead: string;
|
||||
overviewBody: string;
|
||||
authTitle: string;
|
||||
authLead: string;
|
||||
localTitle: string;
|
||||
localLead: string;
|
||||
/** Remote control of a running local session from the signed-in web app. */
|
||||
remoteTitle: string;
|
||||
remoteLead: string;
|
||||
remoteBody: string;
|
||||
troubleshootingTitle: string;
|
||||
troubleshootingLead: string;
|
||||
sourceNote: string;
|
||||
}
|
||||
export type DocsWebDict = DocsPageDict;
|
||||
|
||||
export interface DocsWorkDict {
|
||||
metaTitle: string;
|
||||
metaDescription: string;
|
||||
/** Body-copy typography for this locale (CJK needs looser leading). */
|
||||
bodyClassName: string;
|
||||
overviewTitle: string;
|
||||
overviewLead: string;
|
||||
checklistTitle: string;
|
||||
/** Carries the {todoWrite}, {checklistAlias} and {todoAlias} code spans. */
|
||||
checklistBody: string;
|
||||
strategyTitle: string;
|
||||
strategyLead: string;
|
||||
continuityTitle: string;
|
||||
continuityLead: string;
|
||||
captureTitle: string;
|
||||
captureLead: string;
|
||||
/** Carries the four status prefixes as {pending}/{inProgress}/{completed}/{cancelled}. */
|
||||
captureLegend: string;
|
||||
modelFacingTitle: string;
|
||||
modelFacingLead: string;
|
||||
modelFacingBoundaries: string;
|
||||
sourceNote: string;
|
||||
}
|
||||
export type DocsWorkDict = DocsPageDict;
|
||||
|
||||
/** Copy for `app/[locale]/computer-use/page.tsx` and the install page's Computer Use section. */
|
||||
export interface ComputerUseDict {
|
||||
|
||||
@@ -1,41 +1,119 @@
|
||||
import type { DocsAuthDict } from "../types";
|
||||
|
||||
/**
|
||||
* Simplified-Chinese dictionary for `app/[locale]/docs/auth/page.tsx`.
|
||||
* Statements trace to docs/CONFIGURATION.md, docs/CODEWHALE_AGENT.md, docs/PROVIDERS.md.
|
||||
*/
|
||||
/** 「连接模型提供商」页的简体中文词典;与 `en/docs-auth.ts` 逐段对应。 */
|
||||
export const docsAuth: DocsAuthDict = {
|
||||
metaTitle: "账户与密钥 · Codewhale 文档",
|
||||
metaDescription: "提供商密钥与可选的 Codewhale 账户:各自如何设置、存放在哪里,以及哪些操作完全不需要账户。",
|
||||
metaTitle: "连接模型提供商 · Codewhale 文档",
|
||||
metaDescription:
|
||||
"为 Codewhale 接上模型:保存提供商密钥、查看当前使用的是哪把密钥,或者不用密钥直接运行本地模型。Codewhale 账户是可选的。",
|
||||
bodyClassName: "text-ink-soft leading-[1.9] tracking-wide",
|
||||
overviewTitle: "账户与密钥",
|
||||
overviewLead:
|
||||
"Codewhale 持有两种互不相关的凭证,把它们分开会更清楚。提供商密钥让模型能够回答。Codewhale 账户是可选的,它决定能否使用托管界面——云端 Agent 和聊天渠道。安装和运行本地 Runtime 既不需要账户,也不需要密钥。",
|
||||
credentials: [
|
||||
["提供商密钥(BYOK)", "你自己的 DeepSeek、OpenAI、Anthropic、OpenRouter、本地运行时或任何其他路由的密钥。用 {authSet} 设置。模型回复需要它。"],
|
||||
["Codewhale 账户", "由 {login} 发起的浏览器设备授权会话。可选。决定能否使用云端 Agent 与 Codewhale Agent 界面;本地从不需要。"],
|
||||
title: "连接模型提供商",
|
||||
lede:
|
||||
"Codewhale 需要一个模型来回答问题。你可以为云端提供商保存一把密钥,也可以让它连接你本机运行的模型。费用由你直接付给提供商,不经过 Codewhale 账户。",
|
||||
sections: [
|
||||
{
|
||||
id: "save-key",
|
||||
title: "保存提供商密钥",
|
||||
blocks: [
|
||||
{
|
||||
p: "先在提供商那里申请 API 密钥,然后保存。Codewhale 会提示你输入密钥,输入内容不会回显。DeepSeek 是默认提供商,下面以它为例。",
|
||||
},
|
||||
{
|
||||
code: `codewhale auth set --provider deepseek
|
||||
codewhale auth status --provider deepseek`,
|
||||
lang: "终端",
|
||||
},
|
||||
{
|
||||
p: "`auth status` 会说明当前密钥来自哪里——配置文件、密钥存储还是环境变量——并且只显示最后四位。在脚本里可以用 `--api-key-stdin` 通过管道传入密钥,不必手动输入。",
|
||||
},
|
||||
{
|
||||
p: "也可以在 Codewhale 里连接:按 F3(或输入 `/provider`),选择提供商,粘贴密钥,再选一个模型。",
|
||||
},
|
||||
{
|
||||
note: "在 v0.10.0 中,`auth set --provider deepseek` 还会把默认模型切换为 DeepSeek Pro。想换回更快、更便宜的模型,运行 `/model` 即可。通过 F3 连接则会保留你当前的模型。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "which-key",
|
||||
title: "弄清用的是哪把密钥",
|
||||
blocks: [
|
||||
{ p: "同一个提供商的密钥如果设在多处,按下面的顺序取第一个找到的:" },
|
||||
{
|
||||
steps: [
|
||||
"命令行上的 `--api-key`,只对这一次运行生效。",
|
||||
"`~/.codewhale/config.toml` 中的 `api_key`。",
|
||||
"`codewhale auth set` 写入的密钥存储。",
|
||||
"提供商自己的环境变量,例如 `DEEPSEEK_API_KEY`。",
|
||||
],
|
||||
},
|
||||
{
|
||||
p: "因此,导出一个新的环境变量并不会替换你之前保存的密钥。如果换过的密钥一直报错,先用 `auth status` 看看生效的是哪个来源,再保存新密钥,或清除已存的旧密钥:",
|
||||
},
|
||||
{ code: "codewhale auth clear --provider deepseek", lang: "终端" },
|
||||
{
|
||||
p: "在 Linux 上,密钥存储是 `~/.codewhale/secrets/` 下的一个私有文件(权限 0600),而不是系统钥匙串。`codewhale doctor --probe-api` 会发起一次测试调用,确认密钥和网络都正常。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "other-providers",
|
||||
title: "使用其他提供商或本地模型",
|
||||
blocks: [
|
||||
{
|
||||
p: "`codewhale auth list` 会列出 Codewhale 支持的所有提供商,以及每个提供商是否已有密钥。所有提供商的用法都一样:`codewhale auth set --provider <名称>`,或者设置该提供商的环境变量。",
|
||||
},
|
||||
{
|
||||
p: "本地运行器——Ollama、vLLM 和 SGLang——默认不需要密钥,你的提示词也不会离开本机。先启动运行器,再选择它:",
|
||||
},
|
||||
{
|
||||
code: `codewhale auth list
|
||||
codewhale --provider ollama --model <model-tag>`,
|
||||
lang: "终端",
|
||||
},
|
||||
{
|
||||
p: "[模型页面](/models) 列出了各家提供商、本地部署方式,以及如何在会话中切换模型。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "account",
|
||||
title: "登录 Codewhale 账户(可选)",
|
||||
blocks: [
|
||||
{
|
||||
p: "提供商密钥和 Codewhale 账户是两回事。账户只用于账户相关的功能,比如云端 Agent、在网页应用里接着使用某个会话。安装 Codewhale 和在本地工作都不需要账户。",
|
||||
},
|
||||
{
|
||||
rows: [
|
||||
["codewhale login", "在浏览器中用一次性验证码登录。"],
|
||||
["codewhale account status", "查看当前配置档登录的是哪个账户。"],
|
||||
["codewhale account logout", "移除当前配置档的登录会话。"],
|
||||
["codewhale account keys list", "列出保存在账户里的提供商密钥,永远不会显示密钥内容。"],
|
||||
],
|
||||
codeTerms: true,
|
||||
},
|
||||
{
|
||||
p: "`codewhale login` 不接收提供商密钥,提供商密钥一律通过 `codewhale auth set` 保存。登录会话优先保存在操作系统的凭据管理器中;没有凭据管理器时(例如通过 SSH 或在容器里),则保存在 Codewhale 的私有密钥文件中。",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
providerTitle: "提供商密钥",
|
||||
providerLead:
|
||||
"{authSet} 保存某一个提供商的密钥。进程级的 {apiKeyFlag} 仍然在单次运行中优先。{login} 不是提供商密钥命令:提供商凭证只能通过 {authSet} 配置。",
|
||||
accountTitle: "账户会话",
|
||||
accountLead: "{login} 与 {accountLogin} 是同一个浏览器设备授权流程。会话按所选的 {profile} 区分,旧的 {cloud} 写法仍然是别名。",
|
||||
accountCommands: [
|
||||
["codewhale login", "通过浏览器设备授权流程登录。"],
|
||||
["codewhale account status", "显示所选 profile 的会话。"],
|
||||
["codewhale account logout", "移除该会话。"],
|
||||
next: [
|
||||
{
|
||||
href: "/docs/guide",
|
||||
label: "开始第一个任务",
|
||||
note: "在项目里打开 Codewhale,交给它一件具体的事。",
|
||||
},
|
||||
{
|
||||
href: "/docs/modes",
|
||||
label: "设置模式与审批",
|
||||
note: "决定 Codewhale 运行命令之前是否先问你。",
|
||||
},
|
||||
{
|
||||
href: "/docs/trust",
|
||||
label: "了解哪些数据会离开本机",
|
||||
note: "提供商会收到什么、哪些留在本地,以及如何关闭用量统计。",
|
||||
},
|
||||
],
|
||||
storageTitle: "会话存放在哪里",
|
||||
storageLead:
|
||||
"账户会话优先使用操作系统的凭证管理器;在没有凭证管理器的环境(无头主机、SSH、容器)中自动回退到权限为 0600 的私有 Codewhale 密钥文件。",
|
||||
vaultTitle: "账户自己的密钥库",
|
||||
vaultLead: "{keys} 管理已登录账户的自带密钥(BYOK)库,且永远不会显示密钥内容。",
|
||||
portableTitle: "迁移到另一台机器",
|
||||
portableLead:
|
||||
"{portable} 输出一个不含密钥的配置包:凭证和机器相关的键会被直接删除,而不是用打码占位符替代,因此这个文件可以安全携带,里面没有任何内容能以你的身份登录。",
|
||||
appTitle: "在网页上登录",
|
||||
appLead: "托管的 Codewhale 应用使用同一个账户。可以在那里登录或注册;本地 Runtime 没有它也照常工作。",
|
||||
appSignIn: "登录 →",
|
||||
appRegister: "注册 →",
|
||||
sourceNote: "源文档:docs/CONFIGURATION.md、docs/CODEWHALE_AGENT.md、docs/PROVIDERS.md · 修改时请同步更新 docs-map.ts。",
|
||||
sourceNote:
|
||||
"来源文档:docs/INSTALL.md §8、docs/PROVIDERS.md、docs/CODEWHALE_AGENT.md · 修改时同步更新 docs-map.ts。",
|
||||
};
|
||||
@@ -1,53 +1,102 @@
|
||||
import type { DocsComputersDict } from "../types";
|
||||
|
||||
/**
|
||||
* Simplified-Chinese dictionary for `app/[locale]/docs/computers/page.tsx`.
|
||||
* Statements trace to docs/DAYTONA_CLOUD_DISPATCH.md and docs/CODEWHALE_AGENT.md.
|
||||
*/
|
||||
/** 「把任务发送到云端」页的简体中文词典;与 `en/docs-computers.ts` 逐段对应。 */
|
||||
export const docsComputers: DocsComputersDict = {
|
||||
metaTitle: "云端计算机 · Codewhale 文档",
|
||||
metaTitle: "把任务发送到云端 · Codewhale 文档",
|
||||
metaDescription:
|
||||
"本地 Codewhale 会话如何在 Daytona 计算机上提议、确认并跟踪一个云端 Agent——显式指定代码托管平台、凭证缺失即拒绝,以及哪些部分尚未实现。",
|
||||
"把编码任务交给 Codewhale 云端 Agent,由它在一个分支上完成工作并提交拉取请求——先提议,只有你确认后才会开始。",
|
||||
bodyClassName: "text-ink-soft leading-[1.9] tracking-wide",
|
||||
overviewTitle: "云端计算机",
|
||||
overviewLead:
|
||||
"本地会话可以把一项编码任务卸载到云端计算机,就像编辑器派出一个云端 Agent:远程任务创建分支,并计划向明确指定的代码托管平台发起 pull request。本地保持响应。花费与推送不会悄悄发生——先写入提议,在你确认之前不会创建沙箱,也不会推送分支。",
|
||||
proposeTitle: "先提议,再确认",
|
||||
proposeLead:
|
||||
"第一条命令写入提议并退出。第二条按 id 确认。TUI 中有同样的两步斜杠命令,{cloudAgent} 是 {dispatch} 的别名。",
|
||||
jobsLead: "云端任务作为 {kind} 在现有的任务面板上是一等公民。可以在 TUI 或 CLI 中列出、查看和取消:",
|
||||
remotesTitle: "显式指定托管平台",
|
||||
remotesLead:
|
||||
"远程仓库不会被默认当作 GitHub。以平台命名的远程就是该平台;其他远程按 URL 主机判定。如果存在多个平台,请传入 {remoteFlag}。",
|
||||
remotes: [
|
||||
["github · cnb · gitee(按远程名)", "对应平台,与 URL 无关"],
|
||||
["origin 或其他 → github.com", "github"],
|
||||
["origin 或其他 → cnb.cool", "cnb"],
|
||||
["origin 或其他 → gitee.com", "gitee"],
|
||||
title: "把任务发送到云端",
|
||||
lede:
|
||||
"云端 Agent 会把任务从你的机器上接走:它在一个全新的云端沙箱中工作,推送一个分支并提交拉取请求,你则可以继续在本地工作。在你确认之前,不会启动任何东西、不会产生费用,也不会推送代码。",
|
||||
sections: [
|
||||
{
|
||||
id: "status",
|
||||
title: "了解目前的完成度",
|
||||
blocks: [
|
||||
{
|
||||
note: "云端 Agent 目前是预览版。完整流程已有离线测试覆盖,但真实链路——真实的沙箱,以及在各个代码托管平台上真实提交的拉取请求——尚未经过端到端验证。暂不支持私有仓库。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "before",
|
||||
title: "开始之前",
|
||||
blocks: [
|
||||
{
|
||||
list: [
|
||||
"用 `codewhale login` 登录你的 Codewhale 账户。未登录时,任务只能提议,无法确认。",
|
||||
"把账户 API 密钥设为 `CODEWHALE_API_KEY`,让沙箱中的 Agent 以你的账户身份运行。缺少它时,确认会在产生任何费用之前被拒绝。",
|
||||
"如果使用 GitHub,请先登录 `gh` 命令行工具;Codewhale 会用这个登录来提交拉取请求。",
|
||||
],
|
||||
},
|
||||
{ code: "codewhale dispatch --status", lang: "终端" },
|
||||
{
|
||||
p: "`--status` 会显示从你的 git 远程仓库中识别出的代码托管平台,以及所需的凭据是否齐全。它从不打印任何密钥。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "send",
|
||||
title: "先提议,再确认",
|
||||
blocks: [
|
||||
{
|
||||
code: `codewhale dispatch "fix the flaky login test and open a PR" --remote github
|
||||
codewhale dispatch --confirm cloud_<id>`,
|
||||
lang: "终端",
|
||||
},
|
||||
{
|
||||
p: "第一条命令只写入一份提议并打印其 id;第二条命令才真正启动。Codewhale 可以自己提议云端任务,但从不自行确认。在会话中,使用 `/dispatch <任务>` 和 `/dispatch confirm <id>`。",
|
||||
},
|
||||
{
|
||||
p: "确认之后,Agent 会把仓库克隆到一个新沙箱中完成工作,推送一个新分支(绝不强制推送),并提交拉取请求。任务完成、失败或被取消时,沙箱都会被删除。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "track",
|
||||
title: "跟踪或取消任务",
|
||||
blocks: [
|
||||
{
|
||||
code: `codewhale dispatch --list
|
||||
codewhale dispatch --show cloud_<id>
|
||||
codewhale dispatch --cancel cloud_<id>`,
|
||||
lang: "终端",
|
||||
},
|
||||
{
|
||||
p: "云端任务也会出现在 `/jobs` 中。每个任务会显示进度、分支、拉取请求链接(有了之后),以及运行了多少分钟——这是运行时长,不是账单。取消会立即拆除沙箱。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "forge",
|
||||
title: "选择拉取请求提交到哪里",
|
||||
blocks: [
|
||||
{
|
||||
p: "Codewhale 支持 GitHub、CNB 和 Gitee,且从不假定 `origin` 就是 GitHub。名为 `github`、`cnb` 或 `gitee` 的远程仓库即对应该平台;其他远程仓库按其主机名识别。如果仓库关联了多个平台,请传入 `--remote`。",
|
||||
},
|
||||
{
|
||||
p: "如果拉取请求无法提交——例如缺少代码托管平台的令牌——任务会在推送之后被标记为失败,并说明没有提交拉取请求。它绝不会报告一个并不存在的链接。",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
enableTitle: "启用 Daytona 计算机",
|
||||
enableLead: "凭证只存在于进程环境或 Codewhale 密钥存储中——绝不写进 config.toml 或 models.toml,也绝不提交到仓库。",
|
||||
enableSteps: [
|
||||
["创建 API 密钥", "在 Daytona 控制台的 API keys 页面。"],
|
||||
["为本次会话导出", "{apiKey};如需非默认端点,可选设置 {apiUrl}。"],
|
||||
["或一次性保存", "存入 Codewhale 密钥槽 {slot}(OS 钥匙串或 $CODEWHALE_HOME 密钥文件)。也接受 {alias} 别名。"],
|
||||
next: [
|
||||
{
|
||||
href: "/docs/auth",
|
||||
label: "连接模型提供商",
|
||||
note: "登录 Codewhale 账户并管理密钥。",
|
||||
},
|
||||
{
|
||||
href: "/docs/review",
|
||||
label: "查看改动",
|
||||
note: "合并之前,先审查 Agent 提交的拉取请求。",
|
||||
},
|
||||
{
|
||||
href: "/docs/fleet",
|
||||
label: "运行 Workflow",
|
||||
note: "改为在你自己的机器上运行更长的多步骤工作。",
|
||||
},
|
||||
],
|
||||
cliNote: "安装了 daytona CLI 并不等于有凭证。{status} 和不带参数的 {bare} 会分别报告 CLI 是否存在与凭证是否存在。",
|
||||
rulesTitle: "缺失即拒绝的规则",
|
||||
rules: [
|
||||
["未确认", "写入 {proposed} 任务;命令成功退出;不调用 Daytona;不推送任何内容。"],
|
||||
["已确认但无凭证", "写入 {refused} 任务;命令失败退出;不存在沙箱。"],
|
||||
["已确认且有凭证", "创建 Daytona 沙箱,并以任务 id 和平台打标签。此版本不会声称任何 GitHub、CNB 或 Gitee 的 PR 地址;缺少平台令牌时同样拒绝。"],
|
||||
],
|
||||
membershipTitle: "谁可以派发",
|
||||
membershipLead:
|
||||
"托管 Agent 界面使用同一个 Codewhale 会员身份认证——即 {login} 的账户会话。会员资格决定云端 Agent,而不是本地派发:只要有 Daytona 与托管平台凭证,`codewhale dispatch` 无需账户即可运行。提供商品牌保持内部不可见,安装和运行本地 Runtime 完全不需要账户。",
|
||||
leftoverTitle: "尚未实现",
|
||||
leftover: [
|
||||
["实时查看", "正在运行的沙箱的日志跟随。"],
|
||||
["真正的取消", "从任务面板销毁付费的 Daytona 沙箱。"],
|
||||
["自动决定", "Codewhale 可以提议派发,但不能自己确认自己的提议。"],
|
||||
["远程执行器", "真正创建分支并发起 pull request 的 Agent。"],
|
||||
],
|
||||
sourceNote: "源文档:docs/DAYTONA_CLOUD_DISPATCH.md、docs/CODEWHALE_AGENT.md · 修改时请同步更新 docs-map.ts。",
|
||||
sourceNote: "来源文档:docs/DAYTONA_CLOUD_DISPATCH.md、docs/CODEWHALE_AGENT.md · 修改时同步更新 docs-map.ts。",
|
||||
};
|
||||
@@ -1,24 +1,100 @@
|
||||
import type { DocsConfigurationDict } from "../types";
|
||||
|
||||
/** 「修改设置」页的简体中文词典;与 `en/docs-configuration.ts` 逐段对应。 */
|
||||
export const docsConfiguration: DocsConfigurationDict = {
|
||||
metaTitle: "配置 · Codewhale 文档",
|
||||
metaDescription: "config.toml 的查找顺序、项目级覆盖、凭据优先级和旧版路径迁移。",
|
||||
metaTitle: "修改设置 · Codewhale 文档",
|
||||
metaDescription:
|
||||
"找到 Codewhale 的配置文件,在会话或 shell 中修改设置,只对单次运行试用某个设置,并了解仓库可以覆盖哪些设置。",
|
||||
bodyClassName: "text-ink-soft leading-[1.9] tracking-wide",
|
||||
overviewTitle: "配置",
|
||||
overviewLead:
|
||||
"Codewhale 从 ~/.codewhale/config.toml 读取配置(旧版 ~/.deepseek/config.toml 仍作为回退读取)。--config 标志和 CODEWHALE_CONFIG_PATH 环境变量可以指定别的路径,两者同时设置时 --config 优先;文件加载之后再应用环境变量覆盖。",
|
||||
auditLead:
|
||||
"在 TUI 里运行 {auditCommand} 可以查看哪些文档化的键能在当前会话修改、哪些能持久化、哪些只能改文件或需要重启——改动前以它输出的“Command / reason”列为准。",
|
||||
overlayTitle: "项目级覆盖",
|
||||
overlayLead:
|
||||
"当工作区包含常规文件 <workspace>/.codewhale/config.toml 时,其中声明的安全取值会合并到全局配置之上(旧版 <workspace>/.deepseek/config.toml 在新路径缺失时仍会读取;符号链接的项目配置会被拒绝)。这让仓库可以建议模型或收紧本地安全姿态,而不动用户的全局配置。单次启动可用 --no-project-config 跳过覆盖。",
|
||||
overlayLimits:
|
||||
"覆盖层有意保持狭窄:支持 model、reasoning_effort、approval_policy 与 sandbox_mode(只能收紧)、notes_path、max_subagents(夹紧到 1..=128)、allow_shell(false 生效,true 被忽略)。凭据、端点、提供商选择、MCP 配置、hooks、skills 和 instructions = [...] 始终属于用户全局配置——仓库里的 config.toml 声明 api_key、base_url 或 provider 会被忽略,克隆的仓库无法借此选择任意本地文件进入提示词。",
|
||||
credentialsTitle: "凭据查找",
|
||||
credentialsLead:
|
||||
"当前提供商的 API 密钥按以下顺序解析,先命中者生效:路由自身的认证约定(OAuth 路由使用已授权的令牌;auth_mode = \"none\" 不发送密钥),然后是显式 {apiKey},然后是配置文件中的 api_key,然后是 api_key_env 绑定,然后是 codewhale auth set 写入的密钥存储(默认是 ~/.codewhale/secrets/ 下的文件;仅当 CODEWHALE_SECRET_BACKEND=system 时才使用系统 keyring),最后是提供商自己的环境变量(只会发送到该提供商的官方端点)。{authStatus} 可以查看当前提供商的配置文件、密钥存储后端、环境变量、生效来源和末四位标签,而不会打印密钥本身。托管、OpenAI 兼容、自托管或 Anthropic 原生路由用 {providerConfig} 或 {providerFlag} 选择;完整注册表见模型与提供商页和 docs/PROVIDERS.md。",
|
||||
legacyTitle: "旧版 .deepseek/ 路径",
|
||||
legacyLead:
|
||||
"Codewhale 由 DeepSeek-TUI 更名而来。为了不破坏既有安装,运行时从新的 ~/.codewhale/ 位置读取状态,但在只有旧目录存在时回退到 ~/.deepseek/,并且始终写入 ~/.codewhale/——读取带回退、写入新位置。状态目录解析集中在 crates/config/src/lib.rs 的 resolve_state_dir / ensure_state_dir 中,每一处旧路径引用都有审计过的保留决定。",
|
||||
sourceNote: "来源文档:docs/CONFIGURATION.md, docs/LEGACY_PATHS.md · 更新时请同步修改 docs-map.ts。",
|
||||
title: "修改设置",
|
||||
lede:
|
||||
"大多数设置无需打开文件就能修改:在会话中用 `/config`,在 shell 中用 `codewhale config`。这一页介绍设置保存在哪里、如何修改,以及仓库能替你改什么、不能改什么。",
|
||||
sections: [
|
||||
{
|
||||
id: "file",
|
||||
title: "找到配置文件",
|
||||
blocks: [
|
||||
{ p: "你的设置保存在 `~/.codewhale/config.toml`。可以打印确切路径,或直接用编辑器打开:" },
|
||||
{ code: "codewhale config path\ncodewhale config edit", lang: "终端" },
|
||||
{
|
||||
p: "想使用别的文件,传入 `--config <路径>` 或设置 `CODEWHALE_CONFIG_PATH`;两者同时存在时以命令行参数为准。Codewhale 曾名为 DeepSeek-TUI:如果只有旧的 `~/.deepseek/` 文件夹,仍会读取它,而新设置一律写入 `~/.codewhale/`。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "change",
|
||||
title: "修改设置",
|
||||
blocks: [
|
||||
{
|
||||
p: "在会话中,`/config` 打开设置编辑器,`/settings` 打开设置界面。`/config audit` 会列出哪些设置可以在本次会话中修改、哪些可以保存、哪些要重启后才生效——手动编辑之前先看一下。",
|
||||
},
|
||||
{ p: "在 shell 中:" },
|
||||
{
|
||||
code: `codewhale config get tools
|
||||
codewhale config set telemetry false
|
||||
codewhale config unset telemetry
|
||||
codewhale config dump # the settings in effect, secrets redacted
|
||||
codewhale config doctor # unknown keys, empty secrets, malformed URLs`,
|
||||
lang: "终端",
|
||||
},
|
||||
{
|
||||
p: "`config set` 只处理它认识的配置项;遇到其他配置项时,它会告诉你应该去编辑哪个 TOML 表,而不会自作主张。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "one-run",
|
||||
title: "只对一次运行试用设置",
|
||||
blocks: [
|
||||
{
|
||||
p: "`--set KEY=VALUE` 只在这一次启动中修改设置,不保存任何内容。它支持 `provider`、`model`、`verbosity`、`approval_policy`、`sandbox_mode` 和 `telemetry`,可以重复使用。",
|
||||
},
|
||||
{ code: "codewhale --set model=deepseek-v4-pro --set sandbox_mode=read-only", lang: "终端" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "project",
|
||||
title: "与仓库共享设置",
|
||||
blocks: [
|
||||
{
|
||||
p: "仓库可以附带 `.codewhale/config.toml`,为所有在其中工作的人建议一些设置。只有少数配置项会生效,而且安全相关的设置只能更严格:",
|
||||
},
|
||||
{
|
||||
rows: [
|
||||
["model", "这个仓库的默认模型。"],
|
||||
["reasoning_effort", "例如为复杂的代码库设置 `\"high\"`。"],
|
||||
["approval_policy, sandbox_mode", "只接受比你自己的设置更严格的值。"],
|
||||
["allow_shell", "`false` 会关闭 shell 命令;`true` 会被忽略。"],
|
||||
["max_subagents", "减少并行子 Agent 的数量,范围 1 到 128。"],
|
||||
["notes_path", "把笔记保存在仓库里。"],
|
||||
],
|
||||
codeTerms: true,
|
||||
},
|
||||
{
|
||||
p: "密钥、服务端点、提供商选择、MCP 服务器、钩子、技能和额外的说明文件,始终来自你自己的配置,因此克隆下来的仓库无法让 Codewhale 指向它自己的服务器或文件。启动时加上 `--no-project-config`,可以在这一次运行中忽略仓库的配置文件。",
|
||||
},
|
||||
{
|
||||
p: "项目说明——Agent 在这个仓库里应该如何工作——请写在 `AGENTS.md` 中。运行 `/init` 即可生成一份。",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
next: [
|
||||
{
|
||||
href: "/docs/auth",
|
||||
label: "连接模型提供商",
|
||||
note: "保存密钥,并查看 Codewhale 当前使用的是哪一把。",
|
||||
},
|
||||
{
|
||||
href: "/docs/modes",
|
||||
label: "设置模式与审批",
|
||||
note: "大多数人最先修改的设置。",
|
||||
},
|
||||
{
|
||||
href: "/docs/hooks",
|
||||
label: "在事件发生时运行命令",
|
||||
note: "把你自己的脚本加入 `[hooks]` 表。",
|
||||
},
|
||||
],
|
||||
sourceNote: "来源文档:docs/CONFIGURATION.md、docs/LEGACY_PATHS.md · 修改时同步更新 docs-map.ts。",
|
||||
};
|
||||
@@ -1,24 +1,151 @@
|
||||
import type { DocsFleetDict } from "../types";
|
||||
|
||||
/** 「运行 Workflow」页的简体中文词典;与 `en/docs-fleet.ts` 逐段对应。 */
|
||||
export const docsFleet: DocsFleetDict = {
|
||||
metaTitle: "Fleet 与 Workflow · Codewhale 文档",
|
||||
metaDescription: "持久 Agent 花名册与成员选择层,以及可选的 Workflow 编排层。",
|
||||
metaTitle: "运行 Workflow · Codewhale 文档",
|
||||
metaDescription:
|
||||
"在 Fleet 中保存角色和模型,编写可重复的 Workflow,把它作为可查看、可停止的 Lane 运行,并用持久的 worker 批量执行任务。",
|
||||
bodyClassName: "text-ink-soft leading-[1.9] tracking-wide",
|
||||
overviewTitle: "Fleet 与 Workflow",
|
||||
overviewLead:
|
||||
"Fleet 是持久花名册:记录有哪些成员以及选中了哪一位。它不是执行或权限引擎。Runtime 将选定成员作为无头 codewhale exec 运行来启动和跟踪,负责重试与远程放置,并写入持久收据和台账投影。台账文件、已保存花名册、配置表和 Workflow 的 --fleet 标志都使用 Fleet 名称。",
|
||||
runTitle: "运行一次 fleet",
|
||||
runLead:
|
||||
"Runtime 的 fleet 运行投影存放在工作区的 .codewhale/fleet.jsonl 台账中,worker 日志在 .codewhale/fleet/ 下。codewhale fleet resume <run-id> 会让 Runtime 重放台账并调和过期租约;该操作幂等,可在管理进程退出、笔记本睡眠或运行时重启后安全执行。",
|
||||
statusLead:
|
||||
"TUI 命令 {fleetStatusTui} 与 shell 命令 {fleetStatusShell} 读取同一份持久 fleet 台账。若要查看仅附着于当前交互会话的子 Agent,请使用 {fleetWorkers}(或 {subagents})。",
|
||||
profilesTitle: "已保存 fleet、角色与 /fleet setup",
|
||||
profilesLead:
|
||||
"{fleetSaved} 打开已命名保存 fleet 的选择器;裸 /fleet 打开当前所选 fleet 的成员花名册。在 v0.9.11 中,/fleet setup 编辑当前选中的命名 fleet。未选中命名 fleet 时,它打开角色档案设置:选择角色与模型、按需调整思考设置,然后核对并保存。档案可写在项目级(.codewhale/agents/<role>.toml)或个人级($CODEWHALE_HOME/agents/<role>.toml);同 ID 的项目档案优先。Runtime 另行负责信任、文件系统/网络范围、密钥、审批、沙箱和工具,因此档案存储范围不会扩大执行权限。",
|
||||
workflowTitle: "Workflow 编排",
|
||||
workflowLead:
|
||||
"普通多 Agent 工作不需要 Workflow:在 Operate 里直接发消息,需要并行、隔离或长时间工作时让 Codewhale 优先委派后台 worker 即可。只有当工作需要有序阶段、门禁、共享预算、回放或确定性汇总时才用 Workflow。Workflow 脚本只负责协调:它选择 fleet 成员,但没有自己的文件系统或 shell;Runtime 在实时权限策略下启动真正的 worker。脚本使用编译专用的声明式 JS 子集,降低到类型化 WorkflowSpec 后由 Rust 校验与执行;import、fetch、process、eval、async/await 会被拒绝。",
|
||||
workflowLimits:
|
||||
"默认校验边界:每次 Workflow 运行最多 1,000 个 worker Agent、Workflow IR 结构嵌套深度不超过 5、循环必须声明 max_iterations、动态 expand 节点必须声明 max_children 和模板。Runtime 的子委派是独立执行预算:默认 3 层,可选择启用的硬上限为 8 层。这些是数量与结构上限,而非并发要求:Runtime 每个运行最多接纳 16 个存活 worker,其余排队。省略 max_steps 或设为 0 都保持无界;只有正值才增加模型轮次上限。",
|
||||
sourceNote: "来源文档:docs/FLEET.md, docs/WORKFLOW_AUTHORING.md · 更新时请同步修改 docs-map.ts。",
|
||||
title: "运行 Workflow",
|
||||
lede:
|
||||
"大多数多步骤工作,直接提出来就行:在 Operate 模式下,Codewhale 会自己规划步骤,并把彼此独立的步骤并行执行。如果你希望每次都按同一套有序计划来做——分阶段、并行分支、最后汇总——并且每次运行都留有记录,就写一个 Workflow。",
|
||||
sections: [
|
||||
{
|
||||
id: "fleet",
|
||||
title: "在 Fleet 中保存角色",
|
||||
blocks: [
|
||||
{
|
||||
p: "Fleet 是 Codewhale 可以分派工作的角色列表,以及每个角色使用的模型。在会话中配置一次即可:",
|
||||
},
|
||||
{ code: "/fleet setup\n/fleet\n/fleet saved", lang: "Codewhale" },
|
||||
{
|
||||
p: "`/fleet setup` 会带你选定一个角色、它使用的模型(或“沿用会话的模型”),以及保存位置:当前项目,或者对所有仓库都生效的个人配置。写入之前你会看到完整的文件内容。`/fleet` 显示当前所选 Fleet 的成员,`/fleet saved` 用于在已命名的 Fleet 之间切换。",
|
||||
},
|
||||
{
|
||||
p: "Fleet 只决定由谁来做。一个 worker 能读、能写、能运行什么,仍然取决于你的工作区信任、[审批设置](/docs/modes)和沙箱。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "write",
|
||||
title: "编写 Workflow",
|
||||
blocks: [
|
||||
{
|
||||
p: "Workflow 是放在仓库 `workflows/` 文件夹中的一个 JavaScript 文件。它只描述步骤,本身不干活。下面这个例子先并行审查两个部分,再汇总结论。把它保存为 `workflows/docs_readiness.workflow.js`:",
|
||||
},
|
||||
{
|
||||
code: `export default workflow({
|
||||
"id": "docs-readiness",
|
||||
"goal": "Review the docs and code for gaps, then summarize the next edit",
|
||||
"nodes": [
|
||||
{
|
||||
"branch": {
|
||||
"id": "parallel-review",
|
||||
"parallel": true,
|
||||
"children": [
|
||||
{ "agent": { "id": "code-review", "prompt": "Inspect src/ for undocumented behavior.",
|
||||
"agent_type": "review", "mode": "read_only", "file_scope": ["src"] } },
|
||||
{ "agent": { "id": "docs-review", "prompt": "Inspect docs/ for stale or missing steps.",
|
||||
"agent_type": "review", "mode": "read_only", "file_scope": ["docs"] } }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"reduce": {
|
||||
"id": "summary",
|
||||
"inputs": ["code-review", "docs-review"],
|
||||
"prompt": "Combine the findings into the safest next edit."
|
||||
}
|
||||
}
|
||||
]
|
||||
});`,
|
||||
lang: "workflows/docs_readiness.workflow.js",
|
||||
},
|
||||
{
|
||||
p: "可用的步骤类型有 `agent`、`branch`、`sequence`、`reduce`、`loop_until`、`cond`、`expand` 和 `teacher_review`。Workflow 文件本身不能访问文件、shell 或网络,`import`、`fetch`、`eval` 和 `async` 都会被拒绝。真正干活的是它启动的 Agent,它们按你平常的权限运行。",
|
||||
},
|
||||
{
|
||||
note: "一次运行最多可启动 1,000 个 Agent,同时工作的最多 16 个,其余排队等待。循环必须声明 `max_iterations`。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "run",
|
||||
title: "运行",
|
||||
blocks: [
|
||||
{
|
||||
code: `codewhale workflow run docs-readiness --runtime inline
|
||||
codewhale workflow run docs-readiness --goal "prepare the 1.2 release" --verify`,
|
||||
lang: "终端",
|
||||
},
|
||||
{
|
||||
p: "Codewhale 会根据名字找到 `workflows/docs_readiness.workflow.js`,检查后启动。`--runtime inline` 在当前终端中运行;默认的 `tmux` 则在一个独立的 tmux 会话中运行,关闭终端后也会继续。`--verify` 会在成功完成后运行验证关卡,`--fleet <名称>` 则使用指定的 Fleet 而不是内置角色。",
|
||||
},
|
||||
{
|
||||
p: "如果不想动到当前的工作副本,加上 `--worktree-repo . --branch <名称>`:这次运行会拥有自己的 git 工作树和分支。",
|
||||
},
|
||||
{
|
||||
p: "在会话中,`/workflow` 用于启动 Workflow,`/workflows` 用于列出或取消本会话中的运行。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "watch",
|
||||
title: "查看和停止运行",
|
||||
blocks: [
|
||||
{ p: "每次运行都是一个 Lane。Lane 会保存到磁盘上,所以你可以在任何终端里查看:" },
|
||||
{
|
||||
code: `codewhale lane list
|
||||
codewhale lane status <lane-id>
|
||||
codewhale lane logs <lane-id>
|
||||
codewhale lane attach <lane-id>
|
||||
codewhale lane interrupt <lane-id>`,
|
||||
lang: "终端",
|
||||
},
|
||||
{
|
||||
p: "`lane list`、`lane status` 和 `lane interrupt` 都支持 `--json`,会输出一份机器可读的收据。在会话中,`/lane` 提供同样的操作,结果也完全一致。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "batch",
|
||||
title: "批量运行任务",
|
||||
blocks: [
|
||||
{
|
||||
p: "如果你手上是一串彼此独立的任务,而不是一套计划,就把它们写成任务文件,作为一次 Fleet 运行来执行。每个任务写明目标、角色和允许写入的路径。完整的 `tasks.json` 示例见[教程](https://github.com/Hmbown/CodeWhale/blob/main/docs/FLEET_WORKFLOW_TUTORIAL.md)。",
|
||||
},
|
||||
{
|
||||
code: `codewhale fleet init
|
||||
codewhale fleet run tasks.json --max-workers 4
|
||||
codewhale fleet status
|
||||
codewhale fleet logs <worker-id>
|
||||
codewhale fleet resume <run-id>
|
||||
codewhale fleet stop --all`,
|
||||
lang: "终端",
|
||||
},
|
||||
{
|
||||
p: "`fleet status` 会根据当前工作区的运行记录,统计排队中、运行中、已完成和失败的工作。笔记本休眠或管理进程退出后,`fleet resume` 能接着原来的运行继续,而不会新开一次。如果只想看挂在当前会话上的 Agent,用 `/fleet workers`(或 `/subagents`)。",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
next: [
|
||||
{
|
||||
href: "/docs/subagents",
|
||||
label: "并行运行 Agent",
|
||||
note: "不写 Workflow,也能把一个任务中彼此独立的部分交给子 Agent。",
|
||||
},
|
||||
{
|
||||
href: "/docs/review",
|
||||
label: "查看改动",
|
||||
note: "检查一次运行产生的 diff,并在推送前做一次审查。",
|
||||
},
|
||||
{
|
||||
href: "/docs/vocabulary",
|
||||
label: "产品名词",
|
||||
note: "用一句话分别说明 Fleet、Workflow、Lane 和 Runtime。",
|
||||
},
|
||||
],
|
||||
sourceNote:
|
||||
"来源文档:docs/FLEET.md、docs/FLEET_WORKFLOW_TUTORIAL.md、docs/WORKFLOW_AUTHORING.md · 修改时同步更新 docs-map.ts。",
|
||||
};
|
||||
@@ -1,22 +1,17 @@
|
||||
import type { DocsGuideDict } from "../types";
|
||||
|
||||
/**
|
||||
* Simplified-Chinese dictionary for the docs "Getting started" page.
|
||||
* Copy moved verbatim from the former `isZh` branches in
|
||||
* `app/[locale]/docs/guide/page.tsx`.
|
||||
*/
|
||||
/** 「开始第一个任务」页的简体中文词典;四个步骤见 `web/lib/content/getting-started.ts`。 */
|
||||
export const docsGuide: DocsGuideDict = {
|
||||
metaTitle: "新手指引 · Codewhale 文档",
|
||||
metaTitle: "开始第一个任务 · Codewhale 文档",
|
||||
metaDescription:
|
||||
"安装 Codewhale、连接模型并开始第一项任务。需要模型与角色列表时,再配置 Fleet。",
|
||||
"安装 Codewhale,连接模型,然后在你的项目里交给它一项任务。之后如果需要多个模型和角色,再配置 Fleet。",
|
||||
bodyClassName: "text-ink-soft leading-[1.9] tracking-wide",
|
||||
overviewTitle: "新手指引",
|
||||
overviewTitle: "开始第一个任务",
|
||||
overviewLead:
|
||||
"安装 Codewhale,连接模型,然后交给它一项任务。Fleet 配置为可选步骤。",
|
||||
"从什么都没装到完成第一个任务,只需四步。每一步都链接到详细说明页;配置 Fleet 这一步是可选的。",
|
||||
sessionTitle: "看一次真实会话",
|
||||
sessionLead:
|
||||
"查看一项任务从首次请求到完成的全过程。",
|
||||
nextTitle: "接下来",
|
||||
sessionLead: "查看一项任务从首次请求到完成的全过程。",
|
||||
nextTitle: "下一步",
|
||||
sourceNote:
|
||||
"更多细节见文档中的用户指南与快捷键说明。",
|
||||
"来源文档:docs/GUIDE.md、docs/INSTALL.md、docs/KEYBINDINGS.md · 修改时同步更新 docs-map.ts。",
|
||||
};
|
||||
@@ -1,37 +1,154 @@
|
||||
import type { DocsHooksDict } from "../types";
|
||||
|
||||
/** Simplified Chinese dictionary for `app/[locale]/docs/hooks/page.tsx`. */
|
||||
/** 「在事件发生时运行命令」页的简体中文词典;与 `en/docs-hooks.ts` 逐段对应。 */
|
||||
export const docsHooks: DocsHooksDict = {
|
||||
metaTitle: "钩子 · Codewhale 文档",
|
||||
metaTitle: "在事件发生时运行命令 · Codewhale 文档",
|
||||
metaDescription:
|
||||
"已发布的生命周期钩子:可变 message_submit、tool_call_before 决策、turn_end 与子 Agent 观察事件。",
|
||||
"在会话开始、工具调用之前、回合结束或 Codewhale 等你回应时运行你自己的脚本——用来补充上下文、执行规则或接收通知。",
|
||||
bodyClassName: "text-ink-soft leading-[1.9] tracking-wide",
|
||||
overviewTitle: "钩子",
|
||||
overviewLead:
|
||||
"钩子让你把自己的命令挂进 Codewhale 的生命周期:在消息提交前注入上下文、在工具调用前执行策略、在回合结束或子 Agent 启停时做审计。本页描述当前已发布的行为;docs/rfcs/1364-hooks-lifecycle.md 是这组能力的设计 RFC,完整配置 schema 见 docs/CONFIGURATION.md。",
|
||||
configIntro:
|
||||
"钩子配置在 config.toml 的 {hooksTable} 条目下;TUI 里运行 {hooksCommand} 可以按事件分组查看每个钩子的名称、命令预览、超时和条件,以及 {enabledKey} 的全局开关状态。",
|
||||
events: [
|
||||
[
|
||||
"message_submit(可变)",
|
||||
"在用户消息进入历史或发给模型之前运行。钩子从 stdin 收到 JSON;exit 0 且 stdout 打印含非空 text 字段的 JSON 时替换提交文本;exit 2 在回合开始前阻止提交。多个钩子按配置顺序串行执行,每个钩子收到上一个钩子的输出文本。标记 background = true 的钩子只能观察,不能改写或阻止。",
|
||||
],
|
||||
[
|
||||
"tool_call_before(决策)",
|
||||
"在每次工具调用执行前运行。除 exit 2 硬拒绝(始终生效)外,前台钩子可在 exit 0 时用 stdout JSON 给出决策:allow / deny / ask,并可附带 updatedInput 改写工具输入、additionalContext 追加进给模型的工具结果。多个钩子命中时优先级为 deny > ask > allow;tool_name 条件支持 * 通配(如 mcp__* 匹配所有 MCP 工具)。Full Access 不打开工具审批提示,因此 ask 不会降低该姿态。",
|
||||
],
|
||||
[
|
||||
"turn_end(观察)",
|
||||
"在每个模型回合结束后触发,此时用量、成本、通知、收据和队列恢复状态都已更新。stdin 收到包含 status、duration_ms、usage、totals、queued_message_count 等字段的 JSON。stdout 被忽略,失败只记警告——不能阻止输入、改写 transcript 或改变下一个排队消息。",
|
||||
],
|
||||
[
|
||||
"subagent_spawn / subagent_complete(观察)",
|
||||
"观察子 Agent 的启动与完成,stdin 收到有界的 JSON 元数据(agent_id、状态、截断后的 prompt/result 预览)。失败只记警告,不阻塞调度、不改 prompt 或结果;需要完整细节时使用 agent 返回的 transcript 句柄。",
|
||||
],
|
||||
title: "在事件发生时运行命令",
|
||||
lede:
|
||||
"钩子会在 Codewhale 会话的特定时刻运行你指定的命令。可以用它拦下危险的命令、给消息补充上下文、记录发生了什么,或者在 Codewhale 等你回应时提醒你。",
|
||||
sections: [
|
||||
{
|
||||
id: "first-hook",
|
||||
title: "添加第一个钩子",
|
||||
blocks: [
|
||||
{ p: "钩子写在 `~/.codewhale/config.toml` 中。下面这个钩子会在每次会话开始时打印一行文字:" },
|
||||
{
|
||||
code: `[hooks]
|
||||
enabled = true
|
||||
|
||||
[[hooks.hooks]]
|
||||
name = "announce"
|
||||
event = "session_start"
|
||||
command = "echo 'Codewhale session started'"`,
|
||||
lang: "config.toml",
|
||||
},
|
||||
{
|
||||
p: "启动一个会话,运行 `/hooks`,就能看到所有已配置的钩子、钩子总开关是否打开,以及被拒绝加载的条目。`/hooks events` 会列出所有事件名称。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "gate",
|
||||
title: "在命令执行前拦下它",
|
||||
blocks: [
|
||||
{
|
||||
p: "`tool_call_before` 钩子会在每次工具调用执行之前看到它,并可以放行、拒绝,或强制弹出审批提示。下面这个钩子会拒绝强制推送。保存脚本并赋予可执行权限:",
|
||||
},
|
||||
{
|
||||
code: `#!/bin/sh
|
||||
# ~/.codewhale/hooks/no-force-push.sh
|
||||
case "$DEEPSEEK_TOOL_ARGS" in
|
||||
*"push --force"*|*"push -f"*)
|
||||
echo '{"decision": "deny", "reason": "Force-push is blocked by a hook."}' ;;
|
||||
esac
|
||||
exit 0`,
|
||||
lang: "no-force-push.sh",
|
||||
},
|
||||
{
|
||||
code: `[[hooks.hooks]]
|
||||
name = "no-force-push"
|
||||
event = "tool_call_before"
|
||||
command = "~/.codewhale/hooks/no-force-push.sh"
|
||||
condition = { type = "tool_name", name = "bash" }`,
|
||||
lang: "config.toml",
|
||||
},
|
||||
{
|
||||
p: "钩子从环境变量中读取这次调用,并在标准输出上用 JSON 作答:`allow`、`deny` 或 `ask`,还可以附带 `reason`、改写后的输入(`updatedInput`)或给模型的补充上下文(`additionalContext`)。退出码 2 一律表示拒绝。多个钩子同时作答时,拒绝优先于询问,询问优先于放行。",
|
||||
},
|
||||
{
|
||||
note: "在 Ask 和 Auto-Review 下,`ask` 会强制弹出提示。Full Access 从不显示审批提示,因此在那里 `ask` 不会新增提示。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "events",
|
||||
title: "选择触发时机",
|
||||
blocks: [
|
||||
{
|
||||
p: "有三个事件能改变接下来发生的事,其余事件只做观察:它们的输出会被忽略,失败也只会产生警告。",
|
||||
},
|
||||
{
|
||||
rows: [
|
||||
["message_submit", "在你的消息发给模型之前。可以替换文本,或阻止发送。"],
|
||||
["tool_call_before", "在每次工具调用之前。可以放行、拒绝、询问、改写输入或补充上下文。"],
|
||||
["shell_env", "在每条 shell 命令运行之前。可以添加环境变量。"],
|
||||
["session_start / session_end", "会话打开时,或正常关闭时。"],
|
||||
["turn_end", "回合结束后,附带状态、耗时和 token 用量。"],
|
||||
["tool_call_after", "每个工具结果返回之后,有退出码时会附带退出码。"],
|
||||
["waiting_for_user", "Codewhale 开始等待你的审批、回答或暂停中的目标时。"],
|
||||
["session_idle / session_busy", "会话闲下来,或重新开始工作时。"],
|
||||
["session_error / on_error", "回合最终失败时,或发生任何错误、工具失败时。"],
|
||||
["mode_change", "在 Plan、Work、Operate 之间切换时。"],
|
||||
["subagent_spawn / subagent_complete", "子 Agent 启动或结束时。"],
|
||||
],
|
||||
codeTerms: true,
|
||||
},
|
||||
{
|
||||
p: "`condition` 可以缩小钩子的触发范围:按工具名(支持 `*` 通配)、工具类别、模式或退出码,也可以用 `all` 和 `any` 组合。永远不可能与所属事件匹配的条件,会在加载配置时直接被拒绝——这样你以为已经生效的拦截规则,不会悄无声息地失效。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "options",
|
||||
title: "设置超时与失败行为",
|
||||
blocks: [
|
||||
{
|
||||
rows: [
|
||||
["timeout_secs", "钩子最长可运行多久。默认 30 秒。"],
|
||||
["continue_on_error", "`true`(默认):钩子失败只发出警告。`false`:失败即阻止。"],
|
||||
["background", "`true` 表示只作为观察者运行,不能阻止或改写。"],
|
||||
["working_dir", "写在 `[hooks]` 下:钩子的运行目录。默认是会话的工作区。"],
|
||||
],
|
||||
codeTerms: true,
|
||||
},
|
||||
{
|
||||
note: "`[hooks] default_timeout_secs` 会替换每个钩子自己的 `timeout_secs`,而不只是补上未设置的那些。如果想让各钩子使用各自的超时,请不要设置它。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "project",
|
||||
title: "使用仓库自带的钩子",
|
||||
blocks: [
|
||||
{
|
||||
p: "仓库可以在 `.codewhale/hooks.toml` 中附带钩子。由于它们会在你的机器上运行命令,只有在你信任该工作区,并且批准了这份文件的确切内容之后,才会加载:",
|
||||
},
|
||||
{ code: "/hooks review\n/hooks approve <digest>\n/hooks revoke", lang: "Codewhale" },
|
||||
{
|
||||
p: "`/hooks review` 会显示其中的命令以及文件摘要;批准该摘要后,从下一次会话起启用这份确切的内容。文件有任何改动都需要重新批准。命令调用的脚本也请一并审阅。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "headless",
|
||||
title: "在脚本和 CI 中使用钩子",
|
||||
blocks: [
|
||||
{
|
||||
p: "钩子在交互式会话中运行。`codewhale exec` 默认不触发任何钩子;加上 `--hooks` 后会触发 `tool_call_before` 和 `shell_env`。由于没有人可以回应,`ask` 会被当作拒绝。",
|
||||
},
|
||||
{ code: 'codewhale exec --auto --hooks "run the test suite and fix the first failure"', lang: "终端" },
|
||||
],
|
||||
},
|
||||
],
|
||||
projectTitle: "项目级钩子",
|
||||
projectLead:
|
||||
'仓库可以在 <workspace>/.codewhale/hooks.toml 中携带策略。因为项目钩子是可执行的 shell 配置,Codewhale 只有在工作区通过信任提示或用户配置中的 trust_level = "trusted" 被信任后才加载它们——会话内的 /trust on 和旧版 .deepseek/trusted 标记都不会单独启用项目钩子。受信任后,项目钩子追加在 config.toml 的全局钩子之后运行,因此对 updatedInput 而言最后生效。格式错误但已受信任的项目文件会记警告并回退到只用全局钩子。',
|
||||
sourceNote:
|
||||
"来源文档:docs/rfcs/1364-hooks-lifecycle.md(设计 RFC), docs/CONFIGURATION.md(配置 schema)· 更新时请同步修改 docs-map.ts。",
|
||||
next: [
|
||||
{
|
||||
href: "/docs/modes",
|
||||
label: "设置模式与审批",
|
||||
note: "钩子的决定如何与 Ask、Auto-Review 和 Full Access 共同起作用。",
|
||||
},
|
||||
{
|
||||
href: "/docs/mcp",
|
||||
label: "用 MCP 连接工具",
|
||||
note: "用同样的 `tool_call_before` 钩子管控 MCP 工具。",
|
||||
},
|
||||
{
|
||||
href: "/docs/configuration",
|
||||
label: "修改设置",
|
||||
note: "`config.toml` 在哪里,以及项目可以覆盖哪些设置。",
|
||||
},
|
||||
],
|
||||
sourceNote: "来源文档:docs/HOOKS.md(权威)、docs/CONFIGURATION.md · 修改时同步更新 docs-map.ts。",
|
||||
};
|
||||
@@ -1,30 +1,151 @@
|
||||
import type { DocsMcpDict } from "../types";
|
||||
|
||||
/** 中文对照见 `en/docs-mcp.ts`,文案自页面的 `isZh` 三元逐字迁入。 */
|
||||
/** 「用 MCP 连接工具」页的简体中文词典;与 `en/docs-mcp.ts` 逐段对应。 */
|
||||
export const docsMcp: DocsMcpDict = {
|
||||
metaTitle: "MCP · Codewhale 文档",
|
||||
metaTitle: "用 MCP 连接工具 · Codewhale 文档",
|
||||
metaDescription:
|
||||
"通过 Model Context Protocol 消费外部工具服务器,或把 Codewhale 作为 MCP 服务器暴露。",
|
||||
"添加 Model Context Protocol 服务器让 Codewhale 使用更多工具,登录远程服务器,把 Codewhale 本身作为 MCP 服务器运行,并试用代码模式。",
|
||||
bodyClassName: "text-ink-soft leading-[1.9] tracking-wide",
|
||||
overviewLead:
|
||||
"Codewhale 可以通过 MCP(Model Context Protocol)加载额外的工具。MCP 服务器可以是由 TUI 启动的本地 stdio 进程,也可以是远程 URL 服务器(Streamable HTTP,带旧版 SSE 回退)。连接成功的服务器会把工具注册进模型目录;失败或被禁用的服务器不会作为可用工具呈现给模型。",
|
||||
overviewConfig:
|
||||
"配置文件默认在 {configPath}(新文件缺失时仍读取旧版 {legacyConfigPath}),可用 {configPathOption} 或 {configEnvVar} 覆盖。也兼容其他客户端使用的 {serversKey} 键名。",
|
||||
setupTitle: "配置与管理",
|
||||
setupLead:
|
||||
"用 {initCommand} 生成初始配置;TUI 内的 {mcpCommand} 打开紧凑管理器,显示每个服务器的启用状态、传输方式、命令或 URL、超时和连接错误。常用命令:",
|
||||
setupReload:
|
||||
"在 TUI 里做的配置编辑会立即写盘,但模型可见的 MCP 工具池不会热加载——管理器会把它标记为需要重启。/mcp validate 和 /mcp reload 会重新连接以刷新界面快照。",
|
||||
authTitle: "远程认证",
|
||||
authLead:
|
||||
"URL 服务器可以使用静态 headers、从环境变量派生的 env_headers、bearer_token_env_var 或 OAuth。优先级是保守的:先应用 headers 和 env_headers;bearer_token_env_var 只在尚未设置 Authorization 时添加;OAuth 登录获取的令牌同样不会覆盖已有的显式 header。应避免提交字面量 Authorization header——优先用 env_headers、bearer_token_env_var 或 OAuth 登录,让秘密留在 MCP 文件之外。",
|
||||
toolsTitle: "工具命名与安全",
|
||||
toolsLead:
|
||||
"发现的 MCP 工具以 {toolNamePattern} 的形式暴露给模型——例如名为 {gitServer} 的服务器的 {statusTool} 工具会变成 {gitStatusTool}。MCP 工具和内置工具走同一套审批框架:只读的 MCP 辅助工具在策略允许时可免提示运行,有副作用的 MCP 工具需要审批,Full Access 也不会绕过硬策略拦截。",
|
||||
toolsTrust:
|
||||
"只配置你信任的 MCP 服务器,并把 MCP 服务器配置视为等同于在本机运行代码。经过审查的本地插件包也可以贡献 MCP 服务器:它们复用同一个 MCP 管理器、审批和网络策略路径,以 <plugin>-<server> 的命名空间身份出现,边界比手写的 mcp.json 更严格。",
|
||||
serverTitle: "把 Codewhale 作为 MCP 服务器",
|
||||
serverLead:
|
||||
"{serveMcp} 会把 Codewhale 作为 stdio MCP 服务器运行,让其他会话(或任何 MCP 客户端)调用它的工具;{mcpServerCommand} 是 dispatcher 暴露的等价入口。{addSelfCommand} 会自动解析当前二进制路径并把服务器写进你的 MCP 配置。注意区分:{serveHttp} 是运行时 HTTP/SSE API,是另一种模式。",
|
||||
sourceNote: "来源文档:docs/MCP.md · 更新时请同步修改 docs-map.ts。",
|
||||
title: "用 MCP 连接工具",
|
||||
lede:
|
||||
"MCP 服务器能为 Codewhale 带来更多工具——数据库、问题跟踪系统、浏览器等。你可以添加一个由 Codewhale 替你启动的本地服务器,也可以通过 URL 添加远程服务器。这些工具与内置工具走同一套审批流程。",
|
||||
sections: [
|
||||
{
|
||||
id: "add",
|
||||
title: "添加服务器",
|
||||
blocks: [
|
||||
{
|
||||
code: `codewhale mcp add git --command "uvx" --arg "mcp-server-git"
|
||||
codewhale mcp add docs --url "https://example.com/mcp"
|
||||
codewhale mcp list
|
||||
codewhale mcp validate`,
|
||||
lang: "终端",
|
||||
},
|
||||
{
|
||||
p: "`--command` 通过 stdio 启动本地服务器,每个参数用一个 `--arg`。`--url` 通过 Streamable HTTP 连接远程服务器,必要时回退到旧版 SSE。`mcp validate` 会检查配置文件以及你标记为必需的服务器。",
|
||||
},
|
||||
{
|
||||
p: "在会话中,`/mcp` 会打开 MCP 管理器:显示每个服务器的状态、传输方式、超时设置、错误信息和已发现的工具。同样的操作在那里也能完成,例如 `/mcp add stdio <name> <command>` 和 `/mcp add http <name> <url>`。",
|
||||
},
|
||||
{
|
||||
note: "MCP 服务器以你的权限运行。只添加你信任的服务器,就像只安装你信任的程序一样。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "remote-auth",
|
||||
title: "登录远程服务器",
|
||||
blocks: [
|
||||
{
|
||||
p: "使用 OAuth 的服务器,先用 URL 添加再登录。使用 bearer token 的服务器,把令牌放在环境变量里,而不是写进配置文件:",
|
||||
},
|
||||
{
|
||||
code: `codewhale mcp login docs
|
||||
codewhale mcp add tracker --url "https://example.com/mcp" --bearer-token-env-var TRACKER_TOKEN`,
|
||||
lang: "终端",
|
||||
},
|
||||
{
|
||||
p: "显式设置的 Authorization 请求头始终优先:先应用配置中的请求头,其次是 bearer token 环境变量,最后才是已保存的 OAuth 登录。`codewhale mcp logout <name>` 会删除本机保存的登录信息;提供方那边的授权可能仍然有效,需要到提供方处撤销。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "config",
|
||||
title: "编辑配置文件",
|
||||
blocks: [
|
||||
{
|
||||
p: "服务器配置保存在 `~/.codewhale/mcp.json`,`codewhale mcp init` 会生成一个初始文件。其他客户端使用的 `mcpServers` 键同样可用,现成的配置可以直接粘贴进来。",
|
||||
},
|
||||
{
|
||||
code: `{
|
||||
"servers": {
|
||||
"example": {
|
||||
"command": "node",
|
||||
"args": ["./path/to/your-mcp-server.js"],
|
||||
"env": {},
|
||||
"disabled": false
|
||||
}
|
||||
}
|
||||
}`,
|
||||
lang: "mcp.json",
|
||||
},
|
||||
{
|
||||
p: "修改文件后,在会话中运行 `/mcp reload` 即可,无需重启。服务器只在某个回合需要它的工具时才启动;如果希望它在启动时就连接,把它标记为 `\"required\": true`。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "tool-names",
|
||||
title: "找到这些工具",
|
||||
blocks: [
|
||||
{
|
||||
p: "每个工具在模型眼中的名字是 `mcp_<server>_<tool>`:名为 `git` 的服务器提供的 `status` 工具,就叫 `mcp_git_status`。`codewhale mcp tools <server>` 会列出某个服务器提供的工具。连接失败或已禁用的服务器,其工具永远不会显示为可用。",
|
||||
},
|
||||
{
|
||||
p: "MCP 工具遵循你的[审批设置](/docs/modes):在策略允许时,列出和读取服务器的资源与提示词无需确认;有副作用的工具会先征求你的同意。Full Access 也不会绕过仓库规则或托管策略。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "serve",
|
||||
title: "把 Codewhale 作为 MCP 服务器运行",
|
||||
blocks: [
|
||||
{
|
||||
p: "其他 MCP 客户端——包括另一个 Codewhale 会话——都可以使用 Codewhale 的工具。只需注册一次:",
|
||||
},
|
||||
{
|
||||
code: `codewhale mcp add-self
|
||||
codewhale mcp tools codewhale`,
|
||||
lang: "终端",
|
||||
},
|
||||
{
|
||||
p: "`add-self` 会写入一条通过 stdio 运行 `codewhale serve --mcp` 的配置。每个客户端各自启动一个进程,不会打开任何网络端口。`codewhale serve --http` 则是另一回事——它是供应用使用的 [Runtime API](/docs/runtime-api)。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "code-mode",
|
||||
title: "用代码模式组合工具调用(实验性)",
|
||||
blocks: [
|
||||
{
|
||||
p: "代码模式让模型写一段简短的 JavaScript 程序,在其中调用多个工具、循环和筛选结果,而不必把每次调用都作为单独的一步。只有程序的最终结果会返回给模型,长时间的查找因此更紧凑。它默认关闭。可以只在一次会话中试用,也可以在配置中开启:",
|
||||
},
|
||||
{
|
||||
code: `codewhale --enable code_mode
|
||||
|
||||
# ~/.codewhale/config.toml
|
||||
[features]
|
||||
code_mode = true`,
|
||||
lang: "终端 / config.toml",
|
||||
},
|
||||
{
|
||||
list: [
|
||||
"程序中只能调用无需审批的只读工具。任何写入、运行 shell 命令或需要征求你同意的调用,都会让程序停止,并报告被拒绝的是哪一次调用。",
|
||||
"程序中暂时还不能调用 MCP 工具,请把它们作为普通工具调用来使用。",
|
||||
"每个程序的上限:50 次工具调用、同时最多 4 次、30 秒、返回 16 KiB。",
|
||||
"Plan 模式下不能使用代码模式。",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
next: [
|
||||
{
|
||||
href: "/docs/hooks",
|
||||
label: "在事件发生时运行命令",
|
||||
note: "在工具调用执行前检查或改写它,MCP 工具也不例外。",
|
||||
},
|
||||
{
|
||||
href: "/docs/modes",
|
||||
label: "设置模式与审批",
|
||||
note: "决定哪些 MCP 调用需要你批准。",
|
||||
},
|
||||
{
|
||||
href: "/docs/runtime-api",
|
||||
label: "用 Runtime API 自动化",
|
||||
note: "通过 HTTP 从你自己的应用或脚本驱动 Codewhale。",
|
||||
},
|
||||
],
|
||||
sourceNote:
|
||||
"来源文档:docs/MCP.md、crates/tui/src/tools/codemode.rs · 修改时同步更新 docs-map.ts。",
|
||||
};
|
||||
@@ -1,27 +1,114 @@
|
||||
import type { DocsModesDict } from "../types";
|
||||
|
||||
/** 「设置模式与审批」页的简体中文词典;与 `en/docs-modes.ts` 逐段对应。 */
|
||||
export const docsModes: DocsModesDict = {
|
||||
metaTitle: "模式 · Codewhale 文档",
|
||||
metaDescription: "Plan、Work、Operate 三种运行模式与独立的权限姿态。",
|
||||
metaTitle: "设置模式与审批 · Codewhale 文档",
|
||||
metaDescription:
|
||||
"用 Plan、Work、Operate 选择工作类型,用 Ask、Auto-Review、Full Access 决定 Codewhale 多久停下来问你一次。",
|
||||
bodyClassName: "text-ink-soft leading-[1.9] tracking-wide",
|
||||
overviewTitle: "模式",
|
||||
overviewLead: "模式决定 Codewhale 如何组织工作;权限姿态决定它如何处理具有后果的工具调用。两者相互独立。",
|
||||
modes: [
|
||||
["Plan", "用于只读调查与规划。Codewhale 可以检查工作区,但不能执行 Shell 命令或修改文件。"],
|
||||
["Work", "用于常规交互式编码。Codewhale 可以检查、编辑并使用工具;Shell 是否可用以及何时请求批准,取决于当前配置和权限姿态。"],
|
||||
["Operate", "用于从同一个输入区协调多项任务。父回合可以直接检查、编辑并使用 Shell 或 MCP 工具,其权限姿态、沙箱和安全规则与 Work 相同。独立、并行、后台或长时间工作会优先交给 fleet worker,但并非所有可执行步骤都必须委派。只有需要有序阶段、门禁或确定性汇总时才需要 Workflow。"],
|
||||
title: "设置模式与审批",
|
||||
lede:
|
||||
"有两个相互独立的开关决定 Codewhale 能自己做什么。模式决定工作类型——只看、动手改,还是统筹协调;审批设置决定它什么时候停下来问你。两者都可以在回合之间随时用键盘切换。",
|
||||
sections: [
|
||||
{
|
||||
id: "modes",
|
||||
title: "选择模式",
|
||||
blocks: [
|
||||
{
|
||||
rows: [
|
||||
[
|
||||
"Plan",
|
||||
"只看、只规划。Codewhale 可以阅读工作区、查资料,但不能修改文件,也不能运行 shell 命令。适合先弄懂代码库,或先商定做法。",
|
||||
],
|
||||
[
|
||||
"Work",
|
||||
"日常编码。Codewhale 会阅读、修改文件并运行命令,范围受你选择的审批设置约束。这是默认模式。",
|
||||
],
|
||||
[
|
||||
"Operate",
|
||||
"面向更大的目标。权限与 Work 相同,但 Codewhale 会先拆出有名字的步骤,把彼此独立的步骤并行交给子 Agent,并在汇报完成之前逐一核对结果。",
|
||||
],
|
||||
],
|
||||
},
|
||||
{
|
||||
p: "输入框为空时按 Tab,可以按 Plan → Work → Operate 的顺序切换;也可以输入 `/mode` 打开选择器,或者直接切换:",
|
||||
},
|
||||
{ code: "/mode plan\n/mode work\n/mode operate", lang: "Codewhale" },
|
||||
{
|
||||
p: "你选中的模式也会成为下次启动会话时的模式。回合进行中 Codewhale 不接受切换模式,请先按 Esc 停止当前回合。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "approvals",
|
||||
title: "决定它什么时候问你",
|
||||
blocks: [
|
||||
{
|
||||
p: "按 Shift+Tab 可以按 Ask → Auto-Review → Full Access 的顺序切换。无论这里选什么,Plan 模式始终是只读的。",
|
||||
},
|
||||
{
|
||||
rows: [
|
||||
[
|
||||
"Ask",
|
||||
"默认设置。工作区内的文件修改会直接写入,并以 diff 的形式展示给你;shell 命令和其他有实际影响的工具会停下来等你批准。",
|
||||
],
|
||||
[
|
||||
"Auto-Review",
|
||||
"从不停下来问你。能确证安全的调用直接执行;发布类操作和破坏性的后台操作一律拦截;其余调用交给一次独立的模型审查。高风险调用和审查失败的调用会被拒绝,而不是执行。",
|
||||
],
|
||||
[
|
||||
"Full Access",
|
||||
"不弹出审批提示。仓库规则和托管策略该拦的照样会拦。只在你信任的工作区里使用。",
|
||||
],
|
||||
],
|
||||
},
|
||||
{
|
||||
note: "Ask 模式下,工作区内的文件修改不会事先询问。开始之前请先提交或暂存重要的改动,并通过[查看改动](/docs/review)检查或回滚修改。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "prompt",
|
||||
title: "回应审批提示",
|
||||
blocks: [
|
||||
{ p: "Codewhale 停在审批提示时,会显示将要执行的完整命令。按一个键作答:" },
|
||||
{
|
||||
rows: [
|
||||
["y", "只允许这一次调用。"],
|
||||
["a", "在本次会话剩余时间内都允许。"],
|
||||
["n", "拒绝。Codewhale 会得知这次调用被拒绝了。"],
|
||||
["Esc", "停止整个回合。"],
|
||||
],
|
||||
codeTerms: true,
|
||||
},
|
||||
{
|
||||
p: "新提示默认选中的是“拒绝”,所以没看清就按 Enter 会拒绝这次调用。如果想改变这一点,或者让等待过久的提示自动拒绝,可以在 `~/.codewhale/config.toml` 中设置:",
|
||||
},
|
||||
{
|
||||
code: `[approval]
|
||||
default_selection = "allow_once" # default: "deny"
|
||||
timeout_seconds = 300 # default: wait indefinitely`,
|
||||
lang: "config.toml",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
switchingTitle: "切换模式",
|
||||
switchingLead:
|
||||
"输入区空闲时,按 {tab} 循环 Plan → Work → Operate。补全菜单打开时,Tab 接受补全;回合运行时,它可以把当前草稿排入下一个跟进消息。",
|
||||
switchingCommandLead: "运行 /mode 打开模式选择器,或使用以下命令直接切换:",
|
||||
permissionsTitle: "权限姿态",
|
||||
permissionsLead:
|
||||
"Plan 始终为只读。在 Work 或 Operate 中且输入区空闲时,按 {shiftTab} 循环 Ask → Auto-Review → Full Access。运行 {configCommand} 可查看或编辑当前会话权限;项目或托管策略可能会锁定或收紧它。",
|
||||
postures: [
|
||||
["Ask", "在可能产生重要后果的工具执行前询问你。"],
|
||||
["Auto-Review", "完全自主:从不停下来询问你。可证明安全的调用直接运行,发布类操作和破坏性的后台操作会被拦截,其余调用交给一次性模型审查;高风险调用和未决的拦截会被拒绝,而不是转成提示。"],
|
||||
["Full Access", "无需批准提示即可运行工具,并启用受信任工作区访问。仓库规则和托管约束仍然有效;仅在你信任的工作区中使用。"],
|
||||
next: [
|
||||
{
|
||||
href: "/docs/review",
|
||||
label: "查看改动",
|
||||
note: "查看本次会话的 diff,把文件回滚到之前某个回合。",
|
||||
},
|
||||
{
|
||||
href: "/docs/sandbox",
|
||||
label: "限制命令的访问范围",
|
||||
note: "批准不等于沙箱。了解各平台上操作系统实际限制了什么。",
|
||||
},
|
||||
{
|
||||
href: "/docs/subagents",
|
||||
label: "并行运行 Agent",
|
||||
note: "Operate 如何处理彼此独立的步骤,以及怎样查看进度。",
|
||||
},
|
||||
],
|
||||
sourceNote: "来源文档:docs/MODES.md · 更新时请同步修改 docs-map.ts。",
|
||||
sourceNote: "来源文档:docs/MODES.md、docs/INSTALL.md §10、docs/CONFIGURATION.md · 修改时同步更新 docs-map.ts。",
|
||||
};
|
||||
@@ -0,0 +1,130 @@
|
||||
import type { DocsReviewDict } from "../types";
|
||||
|
||||
/** 「查看改动」页的简体中文词典;与 `en/docs-review.ts` 逐段对应。 */
|
||||
export const docsReview: DocsReviewDict = {
|
||||
metaTitle: "查看改动 · Codewhale 文档",
|
||||
metaDescription:
|
||||
"查看 Codewhale 在一次会话中改过的每个文件,把工作区回滚到之前的回合,对 diff 做代码审查,并为审查保留一份收据。",
|
||||
bodyClassName: "text-ink-soft leading-[1.9] tracking-wide",
|
||||
title: "查看改动",
|
||||
lede:
|
||||
"Codewhale 每改一处都会当场展示给你,并在每个回合前后为工作区保存快照。这一页介绍如何查看改了什么、把文件恢复原样,以及在推送之前请它再审一遍。",
|
||||
sections: [
|
||||
{
|
||||
id: "diff",
|
||||
title: "查看改动",
|
||||
blocks: [
|
||||
{ p: "每次修改文件,都会以 diff 的形式出现在对话记录里。想一次看全,运行:" },
|
||||
{ code: "/diff", lang: "Codewhale" },
|
||||
{
|
||||
p: "`/diff` 会显示本次会话开始以来的全部改动。你自己的 git 历史不受影响,`git diff` 和 `git status` 照常可用。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "restore",
|
||||
title: "把文件回滚到之前的回合",
|
||||
blocks: [
|
||||
{
|
||||
p: "每个回合开始前和结束后,Codewhale 都会把工作区快照存进一个独立的 git 存储。它从不写入你仓库自己的 `.git`,在不是 git 仓库的文件夹里也能用。",
|
||||
},
|
||||
{
|
||||
code: `/restore # list the 20 most recent snapshots
|
||||
/restore list 50 # list more (up to 100)
|
||||
/restore 1 # put files back to the newest snapshot`,
|
||||
lang: "Codewhale",
|
||||
},
|
||||
{
|
||||
p: "恢复会改动文件,所以需要工作区已被信任(`/trust on`)或处于 Full Access;查看快照列表则不受限制。你也可以直接说“撤销你刚才的修改”,Codewhale 可以回滚它自己的那个回合。",
|
||||
},
|
||||
{
|
||||
p: "`/undo` 是另一回事:它只是从对话中删掉最后一轮问答。想恢复文件,请用 `/restore`。",
|
||||
},
|
||||
{
|
||||
note: "快照保留 7 天。超过 2 GB 的工作区不做快照,Codewhale 会提示你一次;如果仍想要快照,可以在配置中调大 `[snapshots] max_workspace_gb`。如果没有安装 git 或磁盘已满,回合照常运行,只是不做快照。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "code-review",
|
||||
title: "做一次代码审查",
|
||||
blocks: [
|
||||
{
|
||||
p: "在会话中,`/review` 可以对一个文件、一段 diff 或一个拉取请求做结构化审查。在 shell 中,`codewhale review` 会审查一段 git diff 并输出发现的问题:",
|
||||
},
|
||||
{
|
||||
code: `codewhale review # unstaged changes in the working tree
|
||||
codewhale review --staged # what you are about to commit
|
||||
codewhale review --base origin/main # everything on this branch
|
||||
codewhale review --pr 123 # a GitHub pull request (needs gh)`,
|
||||
lang: "终端",
|
||||
},
|
||||
{
|
||||
p: "加上 `--path <文件>` 只审查一个路径,用 `--model` 指定审查模型,用 `--json` 输出机器可读结果。超过 200,000 个字符的 diff 会被直接拒绝,而不是截断;需要时可以用 `--max-chars` 调高上限。",
|
||||
},
|
||||
{
|
||||
note: "`--post` 会把审查结果作为评论发布到拉取请求上。不加这个参数时,除了发给模型的请求,没有任何内容离开你的终端。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "receipts",
|
||||
title: "为审查保留收据",
|
||||
blocks: [
|
||||
{
|
||||
p: "审查收据记录了审查了什么、发现了什么,让你能证明推送出去的 diff 正是被审查过的那一份。",
|
||||
},
|
||||
{
|
||||
code: `codewhale review --base origin/main --write-receipt
|
||||
codewhale review --base origin/main --check-receipt`,
|
||||
lang: "终端",
|
||||
},
|
||||
{
|
||||
list: [
|
||||
"`--write-receipt` 在审查成功后保存一个本地 JSON 文件:diff 的指纹、提供商与模型、各类发现的数量、未解决的风险,以及审查文本的哈希。它不保存 diff 本身。",
|
||||
"`--check-receipt` 不调用模型。如果 diff 在收据生成后发生了变化、审查留下了未解决的风险,或者附带的检查没有通过,它都会以非零状态退出,因此可以用作推送前的检查关卡。",
|
||||
"`--receipt-path <文件>` 写入或读取指定的收据,而不是最新的那一份。",
|
||||
],
|
||||
},
|
||||
{
|
||||
note: "目前收据只覆盖代码审查。普通 Agent 回合的收据——把每次工具调用、审批和文件改动汇总成一份导出——已经设计好,但还没有实现。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "export",
|
||||
title: "保存整个会话",
|
||||
blocks: [
|
||||
{ p: "想保留或分享一次会话的完整记录,可以导出:" },
|
||||
{
|
||||
code: `/export clipboard # a redacted copy of this session
|
||||
/export file notes/session.md # the same, written to a file
|
||||
codewhale sessions export <id> # full archive: messages, tool calls, results, artifacts`,
|
||||
lang: "Codewhale / 终端",
|
||||
},
|
||||
{
|
||||
p: "`codewhale sessions` 会列出已保存的会话及其 id。完整归档包含全部上下文,请像对待源代码一样对待它。",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
next: [
|
||||
{
|
||||
href: "/docs/modes",
|
||||
label: "设置模式与审批",
|
||||
note: "决定 Codewhale 修改或运行任何东西之前是否先问你。",
|
||||
},
|
||||
{
|
||||
href: "/docs/fleet",
|
||||
label: "运行 Workflow",
|
||||
note: "可重复的多步骤工作,每次运行都有记录。",
|
||||
},
|
||||
{
|
||||
href: "/docs/troubleshooting",
|
||||
label: "排查问题",
|
||||
note: "回合卡住、密钥失效或会话无法恢复时该怎么办。",
|
||||
},
|
||||
],
|
||||
sourceNote:
|
||||
"来源文档:docs/RECEIPTS.md、docs/CONFIGURATION.md([snapshots])、crates/tui/src/snapshot/mod.rs · 修改时同步更新 docs-map.ts。",
|
||||
};
|
||||
@@ -1,32 +1,123 @@
|
||||
import type { DocsRuntimeApiDict } from "../types";
|
||||
|
||||
/** 中文对照见 `en/docs-runtime-api.ts`,文案自页面的 `isZh` 三元逐字迁入。 */
|
||||
/** 「用 Runtime API 自动化」页的简体中文词典;与 `en/docs-runtime-api.ts` 逐段对应。 */
|
||||
export const docsRuntimeApi: DocsRuntimeApiDict = {
|
||||
metaTitle: "运行时 API · Codewhale 文档",
|
||||
metaDescription: "面向集成、桥接和自动化的本地 HTTP/SSE、JSON-RPC stdio 与 ACP 入口。",
|
||||
metaTitle: "用 Runtime API 自动化 · Codewhale 文档",
|
||||
metaDescription:
|
||||
"用你自己的脚本和应用驱动 Codewhale:在 CI 中运行一次性任务,或启动本地 HTTP API 来发送回合、接收事件流并回应审批。",
|
||||
bodyClassName: "text-ink-soft leading-[1.9] tracking-wide",
|
||||
overviewTitle: "运行时 API",
|
||||
overviewLead:
|
||||
"codewhale app-server 是 canonical 的本地运行时 API 与控制平面。本地 SDK、移动/远控客户端和编辑器集成直接与它对话,而不是抓终端输出。引擎只作为本地进程运行:所有 API 默认绑定 localhost——没有托管中继,不托管 provider 令牌,不泄露秘密。codewhale serve --http / --mobile 保留为 app-server --http / --mobile 的兼容别名,启动的是同一个服务器;新集成应面向 app-server。",
|
||||
entries: [
|
||||
["http", "完整 /v1/* HTTP/SSE 运行时 API(canonical 入口),默认 127.0.0.1:7878。"],
|
||||
["mobile", "运行时 API 加 /mobile 手机控制页。"],
|
||||
["stdio", "换行分隔的 JSON-RPC 2.0 控制传输,无监听端口,适合本地 SDK 和探针。"],
|
||||
["web", "仅回环的浏览器客户端,内嵌于二进制并打开默认浏览器。"],
|
||||
["doctor", "机器可读的健康与能力报告。"],
|
||||
["acp", "面向 Zed 等编辑器的 ACP(Agent Client Protocol)stdio 适配器。"],
|
||||
[
|
||||
"exec",
|
||||
"一次性无头 worker(stream-json、fleet 子进程、CI 原语)——不属于本 API,但共享同一运行时与事件词汇。",
|
||||
],
|
||||
title: "用 Runtime API 自动化",
|
||||
lede:
|
||||
"脚本和应用可以驱动你在终端里使用的同一个引擎。单个任务用 `codewhale exec`;需要会话线程、实时事件和审批的应用,则运行本地的 Runtime API。一切都在你的机器上运行,没有托管中转。",
|
||||
sections: [
|
||||
{
|
||||
id: "exec",
|
||||
title: "在脚本中运行单个任务",
|
||||
blocks: [
|
||||
{
|
||||
code: `codewhale exec "Reply with exactly: pong"
|
||||
codewhale exec --auto "fix the failing test and run it again"
|
||||
codewhale exec --auto --output-format stream-json "update the changelog"`,
|
||||
lang: "终端",
|
||||
},
|
||||
{
|
||||
p: "普通的 `exec` 只回答一次,不使用工具。`--auto` 允许它使用工具并自动批准,所以只在你信任的仓库或容器中使用;它绝不会放宽[沙箱](/docs/sandbox)。`--output-format stream-json` 每行输出一个 JSON 事件,并保存会话,以便之后用 `--continue` 接着运行。`--max-turns` 和 `--allowed-tools` 可以为一次运行设定限制。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "start",
|
||||
title: "启动 Runtime API",
|
||||
blocks: [
|
||||
{
|
||||
code: `export CODEWHALE_RUNTIME_TOKEN="$(openssl rand -hex 32)"
|
||||
codewhale app-server --http # http://127.0.0.1:7878`,
|
||||
lang: "终端",
|
||||
},
|
||||
{
|
||||
p: "请在启动前自己设置令牌;如果不设置,Codewhale 会为该进程生成一个令牌,但不会打印出来。每个 `/v1/*` 请求都必须以 `Authorization: Bearer <token>` 的形式携带它。`--port` 可以更改端口。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "turn",
|
||||
title: "发送一个回合并观察它",
|
||||
blocks: [
|
||||
{
|
||||
code: `API=http://127.0.0.1:7878
|
||||
AUTH="Authorization: Bearer $CODEWHALE_RUNTIME_TOKEN"
|
||||
|
||||
THREAD=$(curl -s -X POST "$API/v1/threads" -H "$AUTH" \\
|
||||
-H "Content-Type: application/json" -d '{}' | jq -r .id)
|
||||
|
||||
curl -s -X POST "$API/v1/threads/$THREAD/turns" -H "$AUTH" \\
|
||||
-H "Content-Type: application/json" -d '{"prompt": "Summarize README.md"}'
|
||||
|
||||
curl -N "$API/v1/threads/$THREAD/events?since_seq=0" -H "$AUTH"`,
|
||||
lang: "终端",
|
||||
},
|
||||
{
|
||||
p: "线程就是一段对话;回合是一次请求,以及 Codewhale 为它所做的一切。事件流会从你给出的序号开始回放,然后保持连接等待新事件,因此重新连接的客户端不会漏掉任何内容。",
|
||||
},
|
||||
{
|
||||
rows: [
|
||||
["停止回合", "`POST /v1/threads/{id}/turns/{turn_id}/interrupt`"],
|
||||
["回应审批", "`POST /v1/approvals/{approval_id}`"],
|
||||
["引导正在运行的回合", "`POST /v1/threads/{id}/turns/{turn_id}/steer`"],
|
||||
["列出已保存的会话", "`GET /v1/sessions`"],
|
||||
],
|
||||
},
|
||||
{
|
||||
p: "[docs/RUNTIME_API.md](https://github.com/Hmbown/CodeWhale/blob/main/docs/RUNTIME_API.md) 列出了所有路由、请求体和事件。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "other",
|
||||
title: "选择其他连接方式",
|
||||
blocks: [
|
||||
{
|
||||
rows: [
|
||||
["codewhale app-server --stdio", "通过标准输入输出传输 JSON-RPC,不监听网络。适合 SDK 或本地探测。"],
|
||||
["codewhale serve --acp", "面向 Zed 等编辑器的 Agent Client Protocol。"],
|
||||
["codewhale serve --mcp", "把 Codewhale 的工具提供给其他 MCP 客户端。见[用 MCP 连接工具](/docs/mcp)。"],
|
||||
["codewhale web", "内置的[浏览器客户端](/docs/web),基于同一个 API。"],
|
||||
["codewhale doctor --json", "以 JSON 输出健康状况和能力信息,不含任何密钥。"],
|
||||
],
|
||||
codeTerms: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "security",
|
||||
title: "保持私密",
|
||||
blocks: [
|
||||
{
|
||||
list: [
|
||||
"服务默认只监听 `127.0.0.1`。令牌只是本地防护,不能替代 TLS 或 VPN;不要把端口暴露到网络上。",
|
||||
"`--insecure-no-auth` 只在回环地址上被接受。",
|
||||
"API 从不返回你的提供商密钥。健康和能力报告只包含元数据——没有密钥、文件内容或消息。",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
stdioTitle: "零成本探测",
|
||||
stdioLead:
|
||||
"stdio 控制传输可以不花模型 token 地探测。capabilities 返回声明的方法族(thread/*、app/*、prompt/*)和完整方法列表;方法集由 crates/app-server/src/lib.rs 中的漂移测试固定,SDK 和本地集成可以放心依赖它不会悄悄变化。",
|
||||
interruptNote:
|
||||
"进行中的回合可以用 thread/interrupt(或 HTTP 的 POST /v1/threads/{id}/turns/{turn_id}/interrupt)请求中断;没有正在流式输出的回合时返回 interrupted: false——这不是错误,只是没有可停的东西。",
|
||||
securityTitle: "安全边界",
|
||||
securityLead:
|
||||
"运行时 API 令牌按 {authToken}、{runtimeTokenEnv}、{legacyTokenEnv} 的顺序读取;{insecureFlag} 只允许与回环绑定一起使用。浏览器侧的跨源请求会被 CORS 允许列表拒绝。选择非回环绑定(尤其是 {mobileFlag})之前,请阅读 docs/RUNTIME_API.md 的完整部署与认证约定。",
|
||||
sourceNote: "来源文档:docs/RUNTIME_API.md · 更新时请同步修改 docs-map.ts。",
|
||||
next: [
|
||||
{
|
||||
href: "/docs/web",
|
||||
label: "打开浏览器客户端",
|
||||
note: "基于同一个 API 的现成客户端。",
|
||||
},
|
||||
{
|
||||
href: "/docs/hooks",
|
||||
label: "在事件发生时运行命令",
|
||||
note: "不写客户端,也能响应会话事件。",
|
||||
},
|
||||
{
|
||||
href: "/docs/fleet",
|
||||
label: "运行 Workflow",
|
||||
note: "可在任何终端查看的持久多步骤运行。",
|
||||
},
|
||||
],
|
||||
sourceNote: "来源文档:docs/RUNTIME_API.md · 修改时同步更新 docs-map.ts。",
|
||||
};
|
||||
@@ -1,38 +1,100 @@
|
||||
import type { DocsSandboxDict } from "../types";
|
||||
|
||||
/** 中文对照见 `en/docs-sandbox.ts`,文案自页面的 `isZh` 三元逐字迁入。 */
|
||||
/** 「限制命令的访问范围」页的简体中文词典;与 `en/docs-sandbox.ts` 逐段对应。 */
|
||||
export const docsSandbox: DocsSandboxDict = {
|
||||
metaTitle: "沙箱与审批 · Codewhale 文档",
|
||||
metaDescription: "macOS Seatbelt、Linux 可选 bubblewrap、平台缺口和审批策略的真实边界。",
|
||||
metaTitle: "限制命令的访问范围 · Codewhale 文档",
|
||||
metaDescription:
|
||||
"了解 macOS、Linux 和 Windows 上用哪种操作系统沙箱包裹 shell 命令,在可选的平台上开启它,并决定命令能写到哪里。",
|
||||
bodyClassName: "text-ink-soft leading-[1.9] tracking-wide",
|
||||
overviewTitle: "沙箱与审批",
|
||||
overviewLead:
|
||||
"Codewhale 可以启动由模型提出的 shell 命令。审批策略、感知工作区的文件工具和操作系统命令包装器是三个独立的控制:一次审批不是沙箱,选择 workspace-write 也不代表当前平台有可用的 OS 包装器。本页只描述已经接入命令执行路径的行为。",
|
||||
platforms: [
|
||||
[
|
||||
"macOS · Seatbelt",
|
||||
"Codewhale 探测 /usr/bin/sandbox-exec;探测成功且策略要求沙箱时,子命令会被包上运行时生成的 Seatbelt profile:广泛的文件系统读取、按策略限制的写入、仅在策略允许时放行网络。探测失败则如实报告无 OS 沙箱。",
|
||||
],
|
||||
[
|
||||
"Linux · 可选 bubblewrap",
|
||||
"Linux 命令沙箱是显式启用的:设置 prefer_bwrap = true,且 /usr/bin/bwrap 是可执行文件时才选用。子命令得到只读根视图,writable 挂载来自解析后的策略;默认隔离网络命名空间,仅在策略开启 network_access 时加 --share-net。未启用或未安装 bwrap 时报告 none。",
|
||||
],
|
||||
[
|
||||
"Windows · 无 OS 沙箱",
|
||||
"Windows 命令路径目前报告无 OS 沙箱。主机权限和审批策略仍然有效,但它们不是 Codewhale 的 OS 命令沙箱。",
|
||||
],
|
||||
[
|
||||
"外部 OpenSandbox 执行",
|
||||
'配置 sandbox_backend = "opensandbox" 后,shell 执行会发往配置的 OpenSandbox 兼容 HTTP 端点,而不是启动本地子进程。隔离保证属于所配置的服务及其运营者。',
|
||||
],
|
||||
title: "限制命令的访问范围",
|
||||
lede:
|
||||
"批准一条命令,决定的是它能不能运行;沙箱决定的是它运行之后能碰到什么。只要操作系统提供沙箱,Codewhale 就会使用它;没有沙箱时,也会如实告诉你。",
|
||||
sections: [
|
||||
{
|
||||
id: "platforms",
|
||||
title: "查看你的平台提供了什么",
|
||||
blocks: [
|
||||
{
|
||||
rows: [
|
||||
["macOS", "Seatbelt,启动检查通过后自动启用。命令可以广泛读取,写入范围由沙箱模式限定,只有模式允许时才能联网。"],
|
||||
["Linux", "bubblewrap,但需要你手动开启(见下文)。不开启时,命令在没有操作系统沙箱的情况下运行。"],
|
||||
["Windows", "目前没有操作系统沙箱。你的审批设置和 Windows 自身的权限仍然有效。"],
|
||||
["外部服务", "设置 `sandbox_backend = \"opensandbox\"` 后,shell 命令会在你配置的 OpenSandbox 兼容服务上运行;隔离效果由该服务负责保证。"],
|
||||
],
|
||||
},
|
||||
{ p: "问问 Codewhale 它找到了哪一种:" },
|
||||
{ code: "codewhale doctor\ncodewhale setup --status", lang: "终端" },
|
||||
{
|
||||
p: "两条命令报告的都是应用你的设置之后实际可用的沙箱。仓库里存在但没有接入执行路径的代码,Codewhale 从不把它算作沙箱。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "linux",
|
||||
title: "开启 Linux 沙箱",
|
||||
blocks: [
|
||||
{ p: "先安装 bubblewrap,再在 `~/.codewhale/config.toml` 中加一行来启用:" },
|
||||
{
|
||||
code: `sudo apt install bubblewrap # Fedora: dnf install bubblewrap · Arch: pacman -S bubblewrap
|
||||
|
||||
# ~/.codewhale/config.toml
|
||||
prefer_bwrap = true`,
|
||||
lang: "终端 / config.toml",
|
||||
},
|
||||
{
|
||||
p: "只有当 `/usr/bin/bwrap` 存在且可执行时,Codewhale 才会使用它。此后,命令看到的是只读的系统视图,只能写入沙箱模式允许的位置,除非模式允许,否则无法联网。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "mode",
|
||||
title: "决定命令能写到哪里",
|
||||
blocks: [
|
||||
{ code: 'sandbox_mode = "workspace-write"', lang: "config.toml" },
|
||||
{
|
||||
rows: [
|
||||
["read-only", "命令只能读,不能写。"],
|
||||
["workspace-write", "命令只能写入工作区和临时文件夹,其他地方都不行。"],
|
||||
["danger-full-access", "不使用操作系统沙箱。只在你不怕损坏的机器或容器里使用。"],
|
||||
["external-sandbox", "你已经运行在隔离环境里,Codewhale 不再额外加一层。"],
|
||||
],
|
||||
codeTerms: true,
|
||||
},
|
||||
{
|
||||
p: "前两种模式只有在沙箱可用时才会真正生效——在没有 bubblewrap 的 Linux 上,以及在 Windows 上,它们只是设置,背后没有操作系统沙箱。仓库自带的配置只能让模式更严格,不能更宽松。对单次无界面运行,可以给 `codewhale exec` 传 `--sandbox <模式>`;`--auto` 只会自动批准工具,绝不会放宽沙箱。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "limits",
|
||||
title: "了解局限",
|
||||
blocks: [
|
||||
{
|
||||
list: [
|
||||
"命令启动前会检查沙箱是否可用,但受主机策略或容器限制影响,沙箱仍可能在启动时失败。",
|
||||
"命令报出“Permission denied”并不能证明是沙箱拦下了它。只有沙箱自己报告的拒绝,Codewhale 才会标记为沙箱拒绝。",
|
||||
"任何沙箱都无法防御内核漏洞,也无法防住所有类型的资源耗尽。",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
policiesTitle: "策略与回退",
|
||||
policiesLead:
|
||||
"本地 {sandboxMode} 取值为 {readOnly}、{workspaceWrite}、{dangerFullAccess} 或 {externalSandbox}。前两者只在选中且可用的 Seatbelt 或 bubblewrap 包装器下被强制执行;{dangerFullAccess} 有意绕过本地 OS 包装器;{externalSandbox} 声明执行已被外部隔离。没有选中包装器时,shell 命令在没有 Codewhale OS 隔离的情况下运行——审批规则和感知工作区的原生文件工具仍是独立的控制。",
|
||||
diagnosticsTitle: "诊断与限制",
|
||||
diagnosticsLead:
|
||||
"codewhale setup --status、codewhale doctor、codewhale doctor --json 和 diagnostics 工具会报告应用 bubblewrap 偏好后本地可用的包装器。拒绝归因是保守的:子命令的通用 Permission denied 本身并不能证明是 Codewhale 的沙箱拦截了它,未沙箱化的命令失败永远不会被标记为沙箱拒绝。",
|
||||
diagnosticsLimits:
|
||||
"限制同样如实说明:可用性在启动前检查,选中的包装器仍可能因主机策略、容器限制或竞态而失败;bubblewrap 会忽略缺失或不是目录的可写根;没有任何沙箱能防御内核漏洞或所有资源耗尽与侧信道攻击。",
|
||||
sourceNote: "来源文档:docs/SANDBOX.md · 更新时请同步修改 docs-map.ts。",
|
||||
next: [
|
||||
{
|
||||
href: "/docs/modes",
|
||||
label: "设置模式与审批",
|
||||
note: "决定哪些命令需要停下来等你批准。",
|
||||
},
|
||||
{
|
||||
href: "/docs/trust",
|
||||
label: "了解哪些数据会离开本机",
|
||||
note: "提供商会收到什么、哪些留在本地,以及遥测会发送什么。",
|
||||
},
|
||||
{
|
||||
href: "/docs/configuration",
|
||||
label: "修改设置",
|
||||
note: "这些配置项写在哪里,以及仓库可以覆盖哪些。",
|
||||
},
|
||||
],
|
||||
sourceNote: "来源文档:docs/SANDBOX.md、docs/CONFIGURATION.md · 修改时同步更新 docs-map.ts。",
|
||||
};
|
||||
@@ -3,16 +3,16 @@ import type { DocsShellDict } from "../types";
|
||||
/**
|
||||
* Simplified-Chinese dictionary for the docs shell: portal hero, hub
|
||||
* metadata, task/topic search, sidebar and breadcrumb chrome, the
|
||||
* release-truth band, and the contextual help band on every docs page.
|
||||
* release-truth band, the contextual help band on every docs page, and the
|
||||
* shared page-body chrome.
|
||||
*/
|
||||
export const docsShell: DocsShellDict = {
|
||||
metaTitle: "文档 · Codewhale",
|
||||
metaDescription:
|
||||
"Codewhale 文档:安装、使用指南、配置、提供商、账户与密钥、云端计算机、安全与信任、核心概念、工具、MCP、技能、沙箱、运行时 API、排障。",
|
||||
"安装 Codewhale,连接模型提供商,然后把事情做完:模式与审批、查看改动、Workflow、子 Agent、MCP 工具、钩子、Runtime API 以及问题排查。",
|
||||
portalMark: "Codewhale 文档",
|
||||
heroTitle: "查找准确的使用说明。",
|
||||
heroLead:
|
||||
"从一个任务或一个主题开始。每一页都说明它取自仓库中的哪份文档、描述的是哪个版本。",
|
||||
heroTitle: "用 Codewhale 把事情做完。",
|
||||
heroLead: "从你想做的事开始。每一页都会说明你需要准备什么,给出今天就能运行的命令,并指向下一步。",
|
||||
installCta: "安装 Codewhale",
|
||||
sourceDocsCta: "浏览源文档 ↗",
|
||||
|
||||
@@ -35,8 +35,7 @@ export const docsShell: DocsShellDict = {
|
||||
emptyTitle: "没有匹配的条目",
|
||||
emptyBody: "换一个关键词试试——中英文都可以搜索——或浏览 GitHub 上的完整文档目录。",
|
||||
emptyCta: "GitHub 文档目录 ↗",
|
||||
indexNote:
|
||||
"“网页”条目提供站内指南;“源文档”条目直接打开 GitHub 仓库中的完整参考资料。任务来自 docs-tasks.ts,主题来自 docs-map.ts,两份注册表都在仓库中维护。",
|
||||
indexNote: "“网页”条目在 codewhale.net 上打开;“源文档”条目会打开 GitHub 仓库中的完整参考资料。",
|
||||
|
||||
sidebarHeading: "文档目录",
|
||||
sidebarAria: "文档目录",
|
||||
@@ -45,11 +44,14 @@ export const docsShell: DocsShellDict = {
|
||||
breadcrumbDocs: "文档",
|
||||
|
||||
helpTitle: "这一页还不够?",
|
||||
helpLead:
|
||||
"每份指南都取自仓库中的一份文档。如果它写错了或缺了什么,最快的办法是在维护者能看到的地方说出来。",
|
||||
helpLead: "每份指南都对照仓库中的一份文档核实过。如果它写错了或缺了什么,请在维护者能看到的地方告诉我们。",
|
||||
helpSource: "来源:{name}",
|
||||
helpTroubleshooting: "排障",
|
||||
helpTroubleshooting: "排查问题",
|
||||
helpFaq: "常见问题",
|
||||
helpDiscord: "到 Discord 提问 ↗",
|
||||
helpIssue: "报告文档问题 ↗",
|
||||
|
||||
nextHeading: "下一步",
|
||||
noteLabel: "注意:",
|
||||
onThisPage: "本页内容",
|
||||
};
|
||||
@@ -1,36 +1,116 @@
|
||||
import type { DocsSubagentsDict } from "../types";
|
||||
|
||||
/** 中文对照见 `en/docs-subagents.ts`,文案自页面的 `isZh` 三元逐字迁入。 */
|
||||
/** 「并行运行 Agent」页的简体中文词典;与 `en/docs-subagents.ts` 逐段对应。 */
|
||||
export const docsSubagents: DocsSubagentsDict = {
|
||||
metaTitle: "子 Agent · Codewhale 文档",
|
||||
metaDescription: "agent 工具、fleet 角色、上下文分叉、worktree 隔离和并发上限。",
|
||||
metaTitle: "并行运行 Agent · Codewhale 文档",
|
||||
metaDescription:
|
||||
"让 Codewhale 把任务中彼此独立的部分交给子 Agent,为它们选择角色,把它们的修改放在各自的工作树里,并随时查看进度。",
|
||||
bodyClassName: "text-ink-soft leading-[1.9] tracking-wide",
|
||||
overviewTitle: "子 Agent",
|
||||
overviewLead:
|
||||
"父会话通过 agent 工具启动一个有明确职责的子 Agent,并立即拿回 agent_id、compact 收据和 transcript 句柄;子 Agent 在后台运行。子 Agent 默认继承父级的工具注册表,但它们是叶子 worker:不会再拿到 agent 或嵌套生命周期工具。agent 启动的是分离的后台工作——取消父回合会停止父级的等待路径,但不会杀死已经启动的子运行。",
|
||||
overviewFleetNote:
|
||||
"对于必须跨进程重启、睡眠或远程执行存活的工作,优先选择 fleet 或 Workflow 支撑的 fleet 运行,而不是会话内的短寿命 agent 调用。",
|
||||
roles: [
|
||||
["worker", "灵活执行父级交代的多步任务;可写、可用 shell。默认角色。"],
|
||||
["scout", "只读,快速摸清相关代码——例如“找出 Foo 的所有调用点”。"],
|
||||
["planner", "分析并产出策略,不执行——“设计迁移方案,不要动手”。"],
|
||||
["reviewer", "只读审查并按严重度打分——“审一遍这个 PR 的 bug”。"],
|
||||
["builder", "以最小改动落地一个明确的变更;可写、可用 shell。"],
|
||||
["verifier", "运行测试和校验并汇报结果,不写代码。"],
|
||||
["consultant", "只读的高推理力度顾问,用于判断类问题和设计评审。"],
|
||||
["custom", "手工指定狭窄的工具白名单,用于锁定的派发。"],
|
||||
title: "并行运行 Agent",
|
||||
lede:
|
||||
"Codewhale 可以把一块聚焦的工作交给子 Agent,自己继续往下做。当一个任务能拆成彼此独立的几部分——梳理代码库、审查改动、跑测试——时,用这种方式让它们同时进行。",
|
||||
sections: [
|
||||
{
|
||||
id: "ask",
|
||||
title: "直接提出来",
|
||||
blocks: [
|
||||
{
|
||||
p: "启动子 Agent 不需要命令,用平常的话提出来就行。在 Operate 模式下,Codewhale 会自己把独立的步骤分派出去;在 Work 模式下,说清楚你想怎么拆分:",
|
||||
},
|
||||
{
|
||||
code: `Use three explore agents in parallel: one maps the auth code,
|
||||
one maps the billing code, one lists the tests that cover both.
|
||||
Then summarize what would break if we changed the session format.`,
|
||||
lang: "提示词",
|
||||
},
|
||||
{
|
||||
p: "每个子 Agent 都在后台启动,完成后回报结果。你的输入框始终可用,主回合会接着处理这些结果。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "roles",
|
||||
title: "选择角色",
|
||||
blocks: [
|
||||
{
|
||||
p: "角色决定了子 Agent 以什么姿态做事。你可以在请求里点名,也可以让 Codewhale 自己选。子 Agent 拿到的权限永远不会超过你当前会话的权限。",
|
||||
},
|
||||
{
|
||||
rows: [
|
||||
["general", "按任务说明做事,可以修改文件、运行命令。默认角色。"],
|
||||
["explore", "只读。快速梳理相关代码——比如“找出这个函数的所有调用方”。"],
|
||||
["planner", "设计方案,不做任何改动。"],
|
||||
["reviewer", "阅读并评估一处改动,为每个发现标注严重程度。"],
|
||||
["implement", "用最小的修改落实一项具体改动。"],
|
||||
["test", "运行测试和检查,报告通过或失败。不修改代码。"],
|
||||
["advisor", "针对需要判断的问题,给出简短而审慎的第二意见。不运行命令。"],
|
||||
["custom", "只能使用你列出的工具,适合权限需要严格收紧的任务。"],
|
||||
],
|
||||
codeTerms: true,
|
||||
},
|
||||
{
|
||||
p: "想让某个角色固定使用某个模型,用 `/fleet setup` 保存——见[运行 Workflow](/docs/fleet)。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "worktrees",
|
||||
title: "让并行修改互不干扰",
|
||||
blocks: [
|
||||
{
|
||||
p: "如果两个子 Agent 会修改同一个仓库,请让它们各自在自己的工作树里工作。Codewhale 会在你的仓库旁边的 `.codewhale-worktrees/` 下,为该 Agent 创建新的 git 工作树和分支;在你合并之前,你的工作副本保持干净。",
|
||||
},
|
||||
{
|
||||
p: "工作树只是隔离,不代表授权:需要写入的 Agent 仍然要有可写的角色,以及允许修改的路径。两个 Agent 如果声明要改同一批文件,会在任何一方动手之前就被拦下。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "watch",
|
||||
title: "查看进度并回应它们",
|
||||
blocks: [
|
||||
{ code: "/subagents", lang: "Codewhale" },
|
||||
{
|
||||
p: "`/subagents`(与 `/fleet workers` 是同一个视图)会列出挂在本次会话上的 Agent,以及每个 Agent 正在做什么。选中一个即可阅读它的对话记录。",
|
||||
},
|
||||
{
|
||||
p: "子 Agent 遵循你的[审批设置](/docs/modes)。在 Ask 下,需要审批的调用会像普通提示一样出现在你的会话里,Agent 则在一旁等待;在 Auto-Review 下,每个被拦下的调用都会经过同样的独立审查,不会打扰你。凡是不是由你亲自做出的决定,都会写进该 Agent 的对话记录。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "limits",
|
||||
title: "了解上限",
|
||||
blocks: [
|
||||
{
|
||||
rows: [
|
||||
["同时运行", "默认 64 个。可在 `~/.codewhale/config.toml` 中设置 `max_subagents`(最多 128)。"],
|
||||
["排队加运行", "最多 1,024 个;超出的启动请求会排队等待。"],
|
||||
["嵌套", "子 Agent 也可以再启动子 Agent,默认最多三层,绝不超过八层。"],
|
||||
],
|
||||
},
|
||||
{
|
||||
p: "这些只是上限,不是目标。少量边界清晰的 Agent 加一份清楚的汇总,胜过一大堆彼此重叠的 Agent。如果工作需要在重启或笔记本休眠后继续,请改用 [Fleet 运行](/docs/fleet)。",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
forkTitle: "上下文分叉",
|
||||
forkLead:
|
||||
"{agentTool} 默认开启全新会话:子 Agent 只拿到角色提示词和你给的任务。当任务依赖父 transcript 里已有的决定、文件、待办或计划状态时,用 {forkContext}——运行时在可用时保持父级前缀逐字节一致(保留前缀缓存复用),追加一份结构化状态快照,再把子 Agent 的角色说明和任务放在末尾。独立探索用新会话,延续、审查、总结或压缩类工作用分叉会话。",
|
||||
worktreeTitle: "Worktree 隔离",
|
||||
// No space before 值: the old JSX put a line break after the
|
||||
// `coordination_contracts` span, and JSX drops leading whitespace that
|
||||
// carries a newline. Reproduced so this stays a move, not a copy edit.
|
||||
worktreeLead:
|
||||
"并行编辑通道用 {worktreeFlag} 启动:Codewhale 为子 Agent 创建新的 git worktree 和分支(默认 {branchPattern},检出在父仓库旁的 {worktreeDir} 下),父检出保持干净。隔离不等于写权限:只带 prompt 的 worker 从只读开始;要写代码的子 Agent 还需声明 {writeAuthority} 和至少一个规范化的 {writeRoots}、{exactFiles} 或 {coordinationContracts}值;重叠的共享写声明会在任何改动之前失败。",
|
||||
capacityTitle: "并发上限",
|
||||
capacityLead:
|
||||
"子 Agent 容量的权威来源是 crates/tui/src/config/subagent_limits.rs:默认配置并发 64,最大配置并发 128,运行加排队的最大准入 1024。这些是容量上限,不是建议把每个槽位都派出去——管理者应使用最小的有效扇出,保持单一汇总负责人,并在汇报整体完成前验证 worker 收据。",
|
||||
sourceNote: "来源文档:docs/SUBAGENTS.md · 更新时请同步修改 docs-map.ts。",
|
||||
next: [
|
||||
{
|
||||
href: "/docs/fleet",
|
||||
label: "运行 Workflow",
|
||||
note: "把反复使用的计划写成 Workflow,分阶段执行,每次运行都有记录。",
|
||||
},
|
||||
{
|
||||
href: "/docs/work",
|
||||
label: "跟踪进度",
|
||||
note: "To-do 列表如何显示已完成、进行中和剩余的工作。",
|
||||
},
|
||||
{
|
||||
href: "/docs/review",
|
||||
label: "查看改动",
|
||||
note: "在保留之前,先检查这些 Agent 改了什么。",
|
||||
},
|
||||
],
|
||||
sourceNote: "来源文档:docs/SUBAGENTS.md、docs/FLEET.md、docs/MODES.md · 修改时同步更新 docs-map.ts。",
|
||||
};
|
||||
@@ -1,44 +1,141 @@
|
||||
import type { DocsTroubleshootingDict } from "../types";
|
||||
|
||||
/**
|
||||
* Simplified Chinese dictionary for
|
||||
* `app/[locale]/docs/troubleshooting/page.tsx`.
|
||||
*/
|
||||
/** 「排查问题」页的简体中文词典;与 `en/docs-troubleshooting.ts` 逐段对应,报错原文保持英文。 */
|
||||
export const docsTroubleshooting: DocsTroubleshootingDict = {
|
||||
metaTitle: "排障 · Codewhale 文档",
|
||||
metaTitle: "排查问题 · Codewhale 文档",
|
||||
metaDescription:
|
||||
"常见问题的快速分诊:挂起的回合、离线队列、崩溃恢复、schema 错误、MCP 故障与 Docker 说明。",
|
||||
"用一条命令诊断 Codewhale,再逐一解决常见问题:找不到命令、没有回复、密钥被拒、网络错误、回合卡住、会话无法恢复,以及 MCP 服务器问题。",
|
||||
bodyClassName: "text-ink-soft leading-[1.9] tracking-wide",
|
||||
overviewTitle: "排障",
|
||||
overviewLead:
|
||||
"先快速分诊:确认二进制与配置(codewhale --version、~/.codewhale/config.toml),需要更详细日志时用 RUST_LOG=codewhale_tui=debug 启动(HTTP 重试/重连用 RUST_LOG=codewhale_tui::client=debug;日志写入 ~/.codewhale/logs/),并看一眼 ~/.codewhale/sessions 与 ~/.codewhale/tasks 的当前状态。",
|
||||
incidents: [
|
||||
[
|
||||
"回合挂起或流停止",
|
||||
"前台 shell 命令还在跑时按 Ctrl+B 把它移到后台(回合继续,命令变成 /jobs 下的后台任务);想取消回合本身用 Esc 或 Ctrl+C。检查 codewhale_tui::client 的重试日志和端点连通性,重启后确认此前在途的回合被标记为中断,而不是停在运行态。",
|
||||
],
|
||||
[
|
||||
"网络中断 / 离线行为",
|
||||
"离线时新提示词会排队,队列按会话持久化在 ~/.codewhale/sessions/checkpoints/<session-id>.offline_queue.json(旧的全局 offline_queue.json 会在升级时被接管一次)。用 /queue list 查看,恢复连接后重新发送(/queue edit <n> 加回车,或走正常输入流程),队列清空后文件随之清除。",
|
||||
],
|
||||
[
|
||||
"崩溃恢复",
|
||||
"每个会话的检查点保存在 ~/.codewhale/sessions/checkpoints/<session-id>.json(旧的 latest.json 仍会读取,但不再写入);除非传入 --resume/--continue,启动会开新会话。用 codewhale --resume <id> 或 TUI 里的 Ctrl+R 显式恢复;若检查点 schema 比二进制新,升级二进制或移除过期检查点。",
|
||||
],
|
||||
[
|
||||
"持久状态 schema 错误",
|
||||
"形如 schema vX is newer than supported vY 的错误涉及 sessions、运行时 thread/turn/item 记录和 tasks。先确认二进制版本,编辑前备份状态目录,然后用更新的兼容二进制运行,或归档不兼容记录并重建状态。",
|
||||
],
|
||||
[
|
||||
"MCP / 工具执行失败",
|
||||
"校验 ~/.codewhale/mcp.json 的 schema 和服务器命令路径,手动确认服务器进程能启动,并在 TUI 历史/日志中检查沙箱拒绝。用 /mcp validate 诊断,可暂时禁用出问题的服务器隔离原因,验证后再启用。",
|
||||
],
|
||||
title: "排查问题",
|
||||
lede: "先运行一条诊断命令,再在下面找到你遇到的症状。每条解决办法都写明了你会看到的确切报错。",
|
||||
sections: [
|
||||
{
|
||||
id: "diagnose",
|
||||
title: "运行诊断",
|
||||
blocks: [
|
||||
{
|
||||
code: `codewhale --version
|
||||
codewhale doctor
|
||||
codewhale doctor --probe-api # one real test call to your provider
|
||||
codewhale auth status --provider deepseek # which key is in use`,
|
||||
lang: "终端",
|
||||
},
|
||||
{
|
||||
p: "`codewhale doctor --json` 会生成一份不含任何密钥的诊断包,可以直接附到 issue 上。单独运行 `doctor` 不会告诉你当前用的是哪把密钥,即使没有密钥也会正常退出;查密钥请用 `auth status`。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "install",
|
||||
title: "安装与更新",
|
||||
blocks: [
|
||||
{
|
||||
rows: [
|
||||
["`codewhale: command not found`", "当前终端的 PATH 中没有 `~/.local/bin`。把 `export PATH=\"$HOME/.local/bin:$PATH\"` 加入 shell 配置文件,然后打开一个新终端。"],
|
||||
["`npm error code EACCES`", "你的 Node 由系统管理。不要用 sudo:用 `npm config set prefix \"$HOME/.npm-global\"` 让 npm 安装到你自己的目录,把其中的 `bin` 加入 PATH,再重新安装。"],
|
||||
["`refusing to replace existing ~/.local/bin/codewhale`", "那里已经装了另一个版本。运行 `codewhale update`,或者先删除旧的二进制文件。"],
|
||||
["`checksum mismatch`", "下载的文件损坏或被篡改,因此没有安装任何东西。请重试;如果一再出现,不要使用镜像源。"],
|
||||
["`The package-managed executable was not changed.`", "你是通过 npm、Cargo 或 Homebrew 安装的,请用对应的工具更新,例如 `npm install -g codewhale`。"],
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "model",
|
||||
title: "没有回复,或密钥被拒",
|
||||
blocks: [
|
||||
{
|
||||
rows: [
|
||||
["消息发出后一直没有回复", "没有配置密钥,而 v0.10.0 不会提示你。按 F3,选择你的提供商,粘贴密钥。"],
|
||||
["`API key not found`", "哪里都找不到密钥。用 `codewhale auth set --provider <名称>` 保存一把。"],
|
||||
["`Authentication Fails … is invalid`", "密钥错误或已被吊销。运行 `auth status` 查看用的是哪个来源——已保存的密钥优先于环境变量——然后保存正确的密钥,或运行 `codewhale auth clear --provider <名称>`。"],
|
||||
["`Network error: SSE stream request failed …`", "通常是连不上提供商。用 `curl -sI https://api.deepseek.com` 检查(返回 401 说明能连通)。在代理后面时请导出 `HTTPS_PROXY`;在 Windows 或限制严格的代理环境下,可以试试 `CODEWHALE_FORCE_HTTP1=1`。"],
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "turn",
|
||||
title: "回合卡住了",
|
||||
blocks: [
|
||||
{
|
||||
list: [
|
||||
"按 Esc 取消当前回合。Esc 会先关闭已打开的菜单,所以如果开着菜单,需要再按一次。",
|
||||
"如果是一条耗时很长的 shell 命令拖住了回合,按 Ctrl-B 把它移到后台。回合会继续进行,`/jobs` 中可以看到这条命令。",
|
||||
"`/retry` 会重新发送上一次请求。",
|
||||
],
|
||||
},
|
||||
{
|
||||
p: "需要详细记录时,用 `RUST_LOG=codewhale_tui=debug` 启动 Codewhale(排查连接重试用 `RUST_LOG=codewhale_tui::client=debug`)。日志写在 `~/.codewhale/logs/` 下。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "sessions",
|
||||
title: "恢复会话",
|
||||
blocks: [
|
||||
{
|
||||
code: `codewhale sessions # list saved sessions
|
||||
codewhale resume <id> # an id or a unique prefix
|
||||
codewhale -c # the latest session in this folder`,
|
||||
lang: "终端",
|
||||
},
|
||||
{
|
||||
p: "在 Codewhale 中按 Ctrl-R 可以打开会话选择器。如果 `codewhale exec --continue` 报出 `No saved sessions found for workspace`,说明之前那次是普通的 `exec` 运行,它不会被保存;想接着运行的任务请使用 `--output-format stream-json`。",
|
||||
},
|
||||
{
|
||||
p: "离线时发送的消息会随会话一起保存在队列中,`/queue list` 可以查看。网络恢复后,用 `/queue edit <n>` 打开其中一条,按 Enter 发送。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "mcp",
|
||||
title: "MCP 工具不见了",
|
||||
blocks: [
|
||||
{
|
||||
list: [
|
||||
"修改 `mcp.json` 或服务器凭据后,运行 `/mcp reload`。`/mcp validate` 只会刷新界面上显示的内容。",
|
||||
"在 shell 中亲自运行一遍服务器命令,确认它能启动。",
|
||||
"如果配置文件丢失或损坏,`codewhale mcp init --force` 会重新生成一份。",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "docker",
|
||||
title: "在 Docker 中运行",
|
||||
blocks: [
|
||||
{
|
||||
code: `docker volume create codewhale-home
|
||||
docker run --rm -it \\
|
||||
-e DEEPSEEK_API_KEY="$DEEPSEEK_API_KEY" \\
|
||||
-v codewhale-home:/home/codewhale/.codewhale \\
|
||||
-v "$PWD:/workspace" -w /workspace \\
|
||||
ghcr.io/hmbown/codewhale:latest`,
|
||||
lang: "终端",
|
||||
},
|
||||
{
|
||||
p: "镜像以非 root 用户运行,把你的设置和会话保存在命名卷里。想要可重复的环境,请固定某个发布标签而不是 `latest`;每个项目使用单独的卷;永远不要把密钥打包进镜像。",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
dockerTitle: "Docker 说明",
|
||||
dockerLead:
|
||||
"每个发布都会向 GitHub Container Registry 推送多架构 Linux 镜像。默认镜像是保守的运行时镜像:以非 root 的 codewhale 用户(UID/GID 1000:1000)运行,不授予免密 sudo,用户状态放在挂载到 /home/codewhale/.codewhale 的卷里。可复现的安装请固定发布标签而不是 latest。",
|
||||
dockerToolboxNote:
|
||||
"需要在容器内使用 apt-get、编译工具链或包管理器时,不要改默认镜像约定——基于 docs/examples/Dockerfile.toolbox 构建显式的 toolbox 镜像,并为每个项目使用独立的命名状态卷,避免会话、配置和离线队列跨工作区串扰。不要把 API 密钥或 SSH 私钥烘进自定义镜像。",
|
||||
sourceNote:
|
||||
"来源文档:docs/OPERATIONS_RUNBOOK.md, docs/DOCKER.md · 更新时请同步修改 docs-map.ts。",
|
||||
next: [
|
||||
{
|
||||
href: "/docs/auth",
|
||||
label: "连接模型提供商",
|
||||
note: "保存密钥、查看当前使用的是哪一把,或改用本地模型。",
|
||||
},
|
||||
{
|
||||
href: "/install",
|
||||
label: "安装 Codewhale",
|
||||
note: "所有安装方式,以及每一步应有的输出。",
|
||||
},
|
||||
{
|
||||
href: "/docs/review",
|
||||
label: "查看改动",
|
||||
note: "把文件回滚到出问题之前那个回合的快照。",
|
||||
},
|
||||
],
|
||||
sourceNote: "来源文档:docs/INSTALL.md §13、docs/OPERATIONS_RUNBOOK.md、docs/DOCKER.md · 修改时同步更新 docs-map.ts。",
|
||||
};
|
||||
@@ -1,55 +1,103 @@
|
||||
import type { DocsTrustDict } from "../types";
|
||||
|
||||
/**
|
||||
* Simplified-Chinese dictionary for `app/[locale]/docs/trust/page.tsx`.
|
||||
* Statements trace to docs/SANDBOX.md, docs/AUTHORIZATION_ORDER.md,
|
||||
* docs/TELEMETRY.md, and docs/public-surface-facts.json.
|
||||
*/
|
||||
/** 「了解哪些数据会离开本机」页的简体中文词典;与 `en/docs-trust.ts` 逐段对应。 */
|
||||
export const docsTrust: DocsTrustDict = {
|
||||
metaTitle: "安全与信任 · Codewhale 文档",
|
||||
metaTitle: "了解哪些数据会离开本机 · Codewhale 文档",
|
||||
metaDescription:
|
||||
"哪些内容留在你的机器上、托管提供商会收到什么、审批与 OS 沙箱有何区别、遥测发送什么以及如何关闭,以及在哪里报告安全漏洞。",
|
||||
"哪些内容留在本地、模型提供商会收到什么、用量统计发送什么以及如何关闭、审计日志在哪里,以及如何报告安全漏洞。",
|
||||
bodyClassName: "text-ink-soft leading-[1.9] tracking-wide",
|
||||
overviewTitle: "安全与信任",
|
||||
overviewLead:
|
||||
"这一页说明 Codewhale 如何处理你的代码、命令和数据——按实际实现来写,而不是按意图来写。每一条都取自仓库中的文档,事实门禁会在每次构建时核对。",
|
||||
boundaryTitle: "你的数据去了哪里",
|
||||
boundaries: [
|
||||
["本地 Runtime", "Runtime、工作区状态和审计日志都留在你的机器上。"],
|
||||
["托管提供商", "你选择的托管提供商会收到本轮推理所需的上下文。中间没有强制经过 Codewhale 的中继。"],
|
||||
["本地推理", "回环地址的本地模型路由(vLLM、Ollama、SGLang)可以让推理完全留在本机。"],
|
||||
["账户", "本地 Runtime 不需要账户。"],
|
||||
["Plan 模式", "Plan 是只读的。"],
|
||||
title: "了解哪些数据会离开本机",
|
||||
lede:
|
||||
"Codewhale 在你的电脑上运行,只与你选择的模型提供商通信。这一页按目前的实际实现,列出数据分别去了哪里、匿名用量统计包含什么,以及如何关闭它。",
|
||||
sections: [
|
||||
{
|
||||
id: "boundaries",
|
||||
title: "你的工作内容去了哪里",
|
||||
blocks: [
|
||||
{
|
||||
rows: [
|
||||
["留在本机", "运行时、你的工作区、会话历史、快照和审计日志。"],
|
||||
["发给你的提供商", "每个回合所需的上下文——你的消息、Codewhale 为这一回合读取的文件和工具结果——直接发给你选定的提供商,中间没有 Codewhale 中转。"],
|
||||
["完全留在本地", "使用本地模型(Ollama、vLLM、SGLang)时,推理完全不离开你的机器。"],
|
||||
["无需账户", "在本地安装和运行 Codewhale 不需要 Codewhale 账户。"],
|
||||
["Plan 模式", "不能修改文件,也不能运行 shell 命令。但它被允许进行的资料查询仍可能访问外部服务。"],
|
||||
],
|
||||
},
|
||||
{
|
||||
p: "你添加的工具可能把数据发到别处:MCP 服务器、网页搜索或钩子都以你的权限运行,能访问到它们能访问的一切。只添加你信任的工具。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "telemetry",
|
||||
title: "了解用量统计会发送什么",
|
||||
blocks: [
|
||||
{
|
||||
p: "Codewhale 默认开启匿名用量统计,并在你第一次启动时明确告知,写明处理方是 Codewhale 和 PostHog。它具体包含:",
|
||||
},
|
||||
{
|
||||
rows: [
|
||||
["从不发送", "提示词、回复、代码、diff、文件内容、文件名/仓库名/分支名、路径、模型 id、MCP 服务器名称、API 密钥或令牌、错误信息文本、按键记录,以及任何按回合或按工具的时间线。"],
|
||||
["开启时发送", "版本与平台类别、会话时长与结果、以固定类别统计的功能与错误计数,以及一个每 90 天更换一次的随机安装 id。"],
|
||||
["发往哪里", "`https://telemetry.codewhale.net/v1/telemetry`,一个第一方服务,源码就在仓库中。它不存储 IP 地址、国家或位置,不保留请求日志,数据保存三个月。"],
|
||||
["PostHog", "只有在服务运营方另行配置后,才会转发给 PostHog;转发的字段完全相同,不会多出任何内容。"],
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "turn-off",
|
||||
title: "关闭用量统计",
|
||||
blocks: [
|
||||
{
|
||||
code: `codewhale config set telemetry false # stop, and erase the local id and buffer
|
||||
CODEWHALE_TELEMETRY=0 codewhale # stop for this process, erase nothing`,
|
||||
lang: "终端",
|
||||
},
|
||||
{
|
||||
p: "配置项是持久的选择:之后的版本都会沿用,命令行参数或环境变量也无法把它重新打开。你也可以在 `/settings` 中切换。关闭后,保存在你机器上的数据会被删除;已经发送的记录只与刚被删除的那个随机 id 关联,并会在三个月后过期。",
|
||||
},
|
||||
{
|
||||
p: "想确切看到会发送什么、但又不真的发送,可以在 `~/.codewhale/config.toml` 中设置 `telemetry_endpoint = \"\"`。此后每一批数据都会逐字节写入本机的 `$CODEWHALE_HOME/telemetry/dryrun.jsonl`,不会建立任何网络连接。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "audit",
|
||||
title: "查看本地审计日志",
|
||||
blocks: [
|
||||
{
|
||||
p: "凭据、审批和提权相关的事件会追加写入 `$CODEWHALE_HOME/audit.log`(默认是 `~/.codewhale/audit.log`)。写入是尽力而为的:写入失败时会记录失败,而不是隐瞒。这份日志从不离开你的机器。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "report",
|
||||
title: "报告安全漏洞",
|
||||
blocks: [
|
||||
{
|
||||
p: "请通过电子邮件把安全问题报告给维护者,不要公开提交 issue。请附上你的 Codewhale 版本(`codewhale --version`),如有复现步骤也请一并提供。",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
approvalTitle: "审批不是沙箱",
|
||||
approvalLead:
|
||||
"审批姿态——Ask、Auto-Review、Full Access——决定一条被提议的命令在执行前是否先展示给你。某一层的批准从来不是全局放行:后面的层仍然可以要求审查或阻止调用,而且批准也不等于操作系统级的沙箱授权。完整的模型工具调用流水线有九个有序的层,从有效配置、钩子、类型化权限规则、仓库法则、人工审批,直到执行沙箱。",
|
||||
sandboxTitle: "各平台的 OS 沙箱",
|
||||
sandboxLead: "这里只描述已接入命令执行路径的行为,Codewhale 会报告它实际选用的机制。",
|
||||
sandboxes: [
|
||||
["macOS — Seatbelt", "sandbox-exec 的运行时探测成功时自动启用。报告为 {seatbelt}。"],
|
||||
["Linux — bubblewrap", "需显式启用:{preferBwrap} 且 /usr/bin/bwrap 可执行。报告为 {bwrap}。"],
|
||||
["没有 bwrap 的 Linux", "默认没有 OS 包装层。报告为 {none}。"],
|
||||
["Windows", "当前实现没有 OS 包装层。报告为 {none}。"],
|
||||
["外部服务", "{opensandbox} 会把执行路由到兼容 OpenSandbox 的服务。"],
|
||||
next: [
|
||||
{
|
||||
href: "/docs/sandbox",
|
||||
label: "限制命令的访问范围",
|
||||
note: "各平台上操作系统沙箱实际限制了什么。",
|
||||
},
|
||||
{
|
||||
href: "/docs/modes",
|
||||
label: "设置模式与审批",
|
||||
note: "决定 Codewhale 不经询问可以做什么。",
|
||||
},
|
||||
{
|
||||
href: "/docs/auth",
|
||||
label: "连接模型提供商",
|
||||
note: "选择由谁接收你的回合内容——或者用本地模型让它留在本机。",
|
||||
},
|
||||
],
|
||||
sandboxNote:
|
||||
"仓库中还有一个 seccomp 模块和一份未来的 Windows 辅助程序契约。两者都没有接入子命令启动,所以 Codewhale 不会宣传它们:只存在于源码中的沙箱代码不能证明某条命令受到了限制。",
|
||||
telemetryTitle: "遥测,精确到每一项",
|
||||
telemetryLead: "Codewhale 0.9.13 默认统计匿名使用量,并在首次启动时告知你;早先的 0.9.11 版本会先询问。关闭是会被后续版本保留的选择,在早先的自愿开启政策下记录的关闭也始终有效。显示告知绝不会代你记录任何同意;处理方为 Codewhale 和 PostHog。",
|
||||
telemetry: [
|
||||
["永远不收集", "对话、代码、prompt、文件、文件/仓库/分支名、模型内容、凭据,以及任何逐轮或逐工具的时间线。"],
|
||||
["开启时发送", "版本与平台类别、会话时长与结果、功能与错误计数、封闭枚举,以及一个每 90 天轮换的随机安装 id。"],
|
||||
["端点", "{endpoint}——第一方 Cloudflare Worker,源码在仓库的 telemetry-ingest/ 目录下。"],
|
||||
["第一方存储", "没有 IP、国家或地理位置列。不记录请求日志。保留期固定为三个月。"],
|
||||
["PostHog", "可选转发需要运营方单独配置,并验证出口不会转发客户端 IP。PostHog 保留期由项目另行设置;源码支持不代表该处理方已启用。"],
|
||||
["自己审计", "设置 {dryRun}:批次会追加写入你机器上的 {dryRunFile},与服务器本应收到的内容逐字节一致,且不会构造任何 HTTP 客户端。"],
|
||||
["关闭", "{configOff} 或 {envOff}。"],
|
||||
],
|
||||
auditTitle: "本地审计日志",
|
||||
auditLead: "敏感事件——凭证、审批和提权事件——会尽力追加写入 {auditLog}。写入失败会被记录而不是隐藏。提供商的 token 与缓存用量在可用时会在本地显示。",
|
||||
reportTitle: "报告安全漏洞",
|
||||
reportLead: "安全报告请通过邮件发给维护者,而不是提交公开 issue。请附上页眉中的版本号,如有复现步骤也请一并提供。",
|
||||
reportCta: "给维护者发邮件",
|
||||
sourceNote: "源文档:docs/SANDBOX.md、docs/AUTHORIZATION_ORDER.md、docs/TELEMETRY.md、docs/public-surface-facts.json · 修改时请同步更新 docs-map.ts。",
|
||||
sourceNote:
|
||||
"来源文档:docs/public-surface-facts.json(trust)、docs/TELEMETRY.md、docs/SANDBOX.md · 修改时同步更新 docs-map.ts。",
|
||||
};
|
||||
@@ -1,28 +1,98 @@
|
||||
import type { DocsWebDict } from "../types";
|
||||
|
||||
/** 中文对照见 `en/docs-web.ts`,文案自页面的 `isZh` 三元逐字迁入。 */
|
||||
/** 「打开浏览器客户端」页的简体中文词典;与 `en/docs-web.ts` 逐段对应。 */
|
||||
export const docsWeb: DocsWebDict = {
|
||||
metaTitle: "浏览器客户端 · Codewhale 文档",
|
||||
metaDescription: "仅回环的内嵌浏览器客户端:一次性引导、会话 Cookie 与本地信任边界。",
|
||||
metaTitle: "打开浏览器客户端 · Codewhale 文档",
|
||||
metaDescription:
|
||||
"在你自己机器上的浏览器标签页里使用 Codewhale,或者用 /rc 在 Codewhale 网页应用中接着使用正在终端里运行的会话。",
|
||||
bodyClassName: "text-ink-soft leading-[1.9] tracking-wide",
|
||||
overviewTitle: "浏览器客户端",
|
||||
overviewLead:
|
||||
"{webCommand} 在 canonical 运行时 API 之上打开 Codewhale 内嵌的浏览器客户端。它是一个纯本地界面:服务器始终绑定 {loopbackHost},无法改绑到局域网地址,也无法在关闭运行时认证的情况下运行。默认地址是 {defaultUrl};端口冲突时用 {portExample} 换一个回环端口。Ctrl+C 停止进程,浏览器会话随之结束。",
|
||||
overviewBody:
|
||||
"当前客户端提供响应式的线程与搜索侧栏、由运行时持有的会话事实、transcript 与工具收据,以及输入区。它可以创建、选择、重命名和归档线程;发起或引导回合;中断工作;处理审批;回答运行时的用户输入请求。浏览器只是同一个本地运行时的另一视图——不会创建第二个云账号,不会把 provider 凭据复制进浏览器存储,也不会削弱已配置的审批与沙箱策略。",
|
||||
authTitle: "认证边界",
|
||||
authLead:
|
||||
"启动 URL 携带的是一个随机、短寿命、一次性的引导凭证——绝不是运行时 bearer 令牌。一次回环请求把它换成 HttpOnly、SameSite=Strict、进程本地的会话 Cookie,并立即使该凭证失效。重用、过期、畸形或非回环的引导尝试都会失败关闭。运行时令牌不会出现在渲染的 HTML、浏览器存储、URL 查询或片段、或浏览器启动参数中。携带 Cookie 的状态变更请求还必须出示精确的本地 web 源;跨源浏览器请求会被拒绝。",
|
||||
localTitle: "本地就是本地",
|
||||
localLead:
|
||||
"{webCommand} 只接受 {portFlag}——没有 {hostFlag},也没有关闭认证的选项。不要把它当公开网站,也不要通过路由器转发、公开反向代理或隧道暴露它的端口。单独的 {mobileCommand} 和 {httpFlag} 模式有不同的部署与认证约定,操作它们(尤其是选择非回环绑定)之前请阅读运行时 API 文档。",
|
||||
remoteTitle: "从网页应用远程控制",
|
||||
remoteLead:
|
||||
"现已可用。要在已登录的 Codewhale 网页应用中继续正在运行的本地会话,请在该会话里输入 /rc,或用 codewhale rc 启动,然后在浏览器中批准一次性代码。租约有效期间,浏览器接管新的提示与审批,终端保持为可读的安全界面;两端都仍可中断。",
|
||||
remoteBody:
|
||||
"连接后,横幅和一条文字稿说明会显示实时会话链接。/rc open 在浏览器中打开它,/rc link 打印它,/rc status 显示会话归属,/rc stop 把会话交还给终端。连接中断时,本地输入会保持锁定,直到最后一个网页租约过期,因此两个控制端永远不会争抢。从同一终端登记的每个文件夹共享一个稳定的设备 ID,所以网页应用按机器而不是按会话列出计算机。这与上面的本机浏览器客户端不同:/rc 把本地会话与你的账户配对;codewhale web 只是提供一个不需要账户的本地页面。",
|
||||
troubleshootingTitle: "常见问题",
|
||||
troubleshootingLead:
|
||||
"端口 7878 被占用时用 --port 换一个。浏览器无法打开时命令会报错退出,而不会留下可重用的引导凭证;检查系统默认浏览器设置后重新启动。页面能打开但 provider 不可用时,查 codewhale doctor 和 /provider——web 命令不配置也不迁移 provider 凭据。会话过期后重启 codewhale web 以签发新的进程本地会话;重用旧的引导 URL 本来就会失败。",
|
||||
sourceNote: "来源文档:docs/WEB.md · 更新时请同步修改 docs-map.ts。",
|
||||
title: "打开浏览器客户端",
|
||||
lede:
|
||||
"比起终端,更喜欢浏览器窗口?Codewhale 可以在你的机器上提供自己的客户端。它只是同一个本地会话的另一种视图——审批相同、沙箱相同,也不需要账户。",
|
||||
sections: [
|
||||
{
|
||||
id: "start",
|
||||
title: "启动",
|
||||
blocks: [
|
||||
{ p: "在你希望 Codewhale 工作的文件夹中运行:" },
|
||||
{ code: "codewhale web\ncodewhale web --port 8788 # if 7878 is taken", lang: "终端" },
|
||||
{
|
||||
p: "Codewhale 会在 `http://127.0.0.1:7878` 启动本地服务,打印一个一次性链接,并在默认浏览器中打开它。如果浏览器没有打开,请在十分钟内使用打印出来的链接。在终端按 Ctrl+C 即可停止,浏览器会话也随之结束。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "use",
|
||||
title: "在浏览器中工作",
|
||||
blocks: [
|
||||
{
|
||||
p: "浏览器客户端可以列出和搜索你的会话线程,显示对话记录及每个工具的结果,并提供输入框。你可以开始、引导或中断一个回合,回应审批,给线程改名或归档。你的提供商密钥始终留在 Codewhale 中,不会被复制到浏览器存储里。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "local",
|
||||
title: "只在本机使用",
|
||||
blocks: [
|
||||
{
|
||||
list: [
|
||||
"服务只监听 `127.0.0.1`。没有任何选项能把它开放到你的网络,也无法在不认证的情况下运行。",
|
||||
"链接中携带的是一次性代码,而不是你的访问令牌。打开链接时,这个代码会换成一个绑定到当前进程的 cookie,随即失效。",
|
||||
"不要通过路由器、公共代理或隧道转发这个端口。如果要在手机或另一台机器上使用,请参阅 [Runtime API](/docs/runtime-api),并先读一读它的认证规则。",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "remote",
|
||||
title: "在网页应用中接着使用会话",
|
||||
blocks: [
|
||||
{
|
||||
p: "这是另一回事:它把已经在终端里运行的会话交给已登录的 Codewhale 网页应用,让你可以换一台设备继续。这需要一个 [Codewhale 账户](/docs/auth#account)。",
|
||||
},
|
||||
{
|
||||
code: `/rc # in the running session; approve the one-time code in your browser
|
||||
/rc status # who controls the session now
|
||||
/rc link # print the session link
|
||||
/rc stop # hand control back to the terminal`,
|
||||
lang: "Codewhale",
|
||||
},
|
||||
{
|
||||
p: "网页应用接管会话期间,新的提示和审批都来自浏览器,终端仍可查看内容。任何一方都可以中断。你也可以用 `codewhale rc` 直接以这种方式启动会话。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "fix",
|
||||
title: "遇到问题时",
|
||||
blocks: [
|
||||
{
|
||||
rows: [
|
||||
["端口被占用", "用 `--port` 指定一个空闲端口。"],
|
||||
["浏览器没有打开", "在十分钟内,把打印出来的链接复制到同一台机器上的浏览器中打开。"],
|
||||
["链接已过期或已被使用", "这是预期行为。重新运行 `codewhale web` 获取新链接。"],
|
||||
["模型没有回复", "web 命令不负责配置提供商。请检查 `codewhale doctor` 和 `/provider`。"],
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
next: [
|
||||
{
|
||||
href: "/docs/runtime-api",
|
||||
label: "用 Runtime API 自动化",
|
||||
note: "浏览器客户端所基于的本地 API。",
|
||||
},
|
||||
{
|
||||
href: "/docs/auth",
|
||||
label: "连接模型提供商",
|
||||
note: "为浏览器客户端接上一个模型。",
|
||||
},
|
||||
{
|
||||
href: "/docs/modes",
|
||||
label: "设置模式与审批",
|
||||
note: "浏览器中同样适用这些审批设置。",
|
||||
},
|
||||
],
|
||||
sourceNote: "来源文档:docs/WEB.md · 修改时同步更新 docs-map.ts。",
|
||||
};
|
||||
@@ -1,41 +1,101 @@
|
||||
import type { DocsWorkDict } from "../types";
|
||||
|
||||
/**
|
||||
* Chinese dictionary for `app/[locale]/docs/work/page.tsx`.
|
||||
* Copy moved verbatim from the page's `isZh` ternaries.
|
||||
*
|
||||
* Spacing note: the spaces around `{todoWrite}`, `{checklistAlias}` and
|
||||
* `{todoAlias}` came from `{" "}` in the JSX, and the ones before `To-do`
|
||||
* and `回放` came from mid-sentence line breaks inside a single text node.
|
||||
* `{inProgress}`, `{completed}` and `{cancelled}` have no leading space
|
||||
* because JSX dropped the trailing whitespace of the text node that ended
|
||||
* in a newline. Do not "fix" either as a typo.
|
||||
*/
|
||||
/** 「跟踪进度」页的简体中文词典;与 `en/docs-work.ts` 逐段对应。 */
|
||||
export const docsWork: DocsWorkDict = {
|
||||
metaTitle: "工作面板 · Codewhale 文档",
|
||||
metaDescription: "唯一的 To-do 列表、模型如何看到它,以及同一份工作状态的延续路径。",
|
||||
metaTitle: "跟踪进度 · Codewhale 文档",
|
||||
metaDescription:
|
||||
"在工作栏中跟踪多步骤任务:目标、To-do 列表和子 Agent。设定一个跨回合持续的目标,并在不丢失进度的情况下把工作交给新的会话。",
|
||||
bodyClassName: "text-ink-soft leading-[1.9] tracking-wide",
|
||||
overviewTitle: "工作面板",
|
||||
overviewLead:
|
||||
"Codewhale 的 TUI 侧栏有一块 Work 区域,显示当前工作的实时状态。它不只是视觉上的待办清单:同一份工作状态同时由模型可见的工具、会话接力(relay)和子 Agent 交接共同维护。Codewhale 只有一个 Work 面板——带计数的 To-do 执行台账。update_plan 是对话式的推理笔记,不是第二个进度面板。",
|
||||
checklistTitle: "To-do:唯一的执行台账",
|
||||
checklistBody:
|
||||
"To-do 是具体工作的进度台账:一组带状态的条目(pending / in_progress / completed / cancelled),外加完成百分比和当前进行中的条目。模型通过 canonical 的 {todoWrite} 工具替换活动线程或持久任务的 To-do 投影——这是模型可见的进度表面。旧的 {checklistAlias} 和 {todoAlias} 名字仍是隐藏的兼容别名:它们对同一份 To-do 状态保持可派发,以便旧 transcript 回放,但不会出现在模型目录里。",
|
||||
strategyTitle: "策略是对话式推理:update_plan",
|
||||
strategyLead:
|
||||
"update_plan 承载的是可选的高层策略,不是第二份清单。它的字段面向阶段级理解:标题、目标、上下文摘要、说明、来源、关键文件、约束、推荐方案、验证计划、风险与未知、交接包,以及一组步骤。它帮助父会话或后续 worker 理解“为什么这么做”;具体执行进度始终属于 To-do 列表。侧栏有意不把策略状态渲染成第二条进度列表,各个 To-do 快照出口也不会包含它——只有 update_plan 而 To-do 为空时,不会产生任何 To-do 快照。",
|
||||
continuityTitle: "延续性:同一份状态流向各处",
|
||||
continuityLead:
|
||||
"模型通过自己的工具结果了解 To-do:todo_write 返回的结果就是普通的会话历史,因此无需在每一步重复注入清单。只有在有人明确要求的节点,才会用同一个渲染器展示一次当前 To-do:分叉(fork_context)的子 Agent 在其结构化状态块里收到该正文;/relay 把同样的正文写进交接指令。两处的 To-do 正文逐字节一致——子 Agent 与下一个线程因此从父级真实的进度位置继续,而不是从转述的摘要开始。侧栏的 To-do 区域则完整实时渲染同一份状态。",
|
||||
captureTitle: "终端实拍(文本复原)",
|
||||
captureLead:
|
||||
"下面的文本块按 crates/tui/src/tui/sidebar.rs 的渲染逻辑逐行复原侧栏 Work 区域:目标是带 ◆ 图标的 Goal 行、耗时、token 预算条;然后是完成度计数和带编号的状态条目。",
|
||||
captureLegend:
|
||||
"条目前缀对应四种状态:{pending} 待办、{inProgress} 进行中、{completed} 完成、{cancelled} 取消。空间不够时侧栏窗口化到进行中条目附近,并用 “+N more To-do items” 标注被省略的条目。",
|
||||
modelFacingTitle: "哪些是模型可见的,哪些只是界面",
|
||||
modelFacingLead:
|
||||
"已被实现和测试证实的模型可见路径有三条:todo_write 工具本身是模型目录里的活跃工具,它返回的工具结果就是模型看到清单的方式;分叉子 Agent 的结构化状态块(<codewhale:fork_state> 中的 To-do 小节,在真正 fork 的那一刻解析);以及 /relay 输出。没有任何一步请求会重复注入 To-do——一条结构化测试直接断言真实出站请求体里不含该清单。侧栏渲染是视觉呈现——它给人看,不注入模型上下文。",
|
||||
modelFacingBoundaries:
|
||||
"边界值得说清楚:因为没有逐步注入,稳定的系统与工具前缀完全不受 To-do 变化影响,前缀缓存也不会因此失效。fork 那一次的快照读取的是权威状态(有 work graph 时读它暂存的投影,而不是尚未发布的旧视图),所以同一回合里较早的一次 todo_write 也会被带上。条目数与字符数都有硬上限,进行中的条目优先保留,被省略的部分带省略标记。To-do 为空时不输出任何内容。渲染器只保证包裹结构、控制字符与上限这三件事——它不会审查条目文本的含义,任意 To-do 内容不因此变成可信指令。",
|
||||
sourceNote: "来源文档:docs/TOOL_SURFACE.md, docs/TOOL_LIFECYCLE.md · 更新时请同步修改 docs-map.ts。",
|
||||
title: "跟踪进度",
|
||||
lede:
|
||||
"当一个任务需要不止一步时,Codewhale 会维护一份 To-do 列表,并把它显示在工作栏里,旁边是目标和各个子 Agent。对话记录一路往下滚动的同时,你始终能看到哪些已完成、哪些正在进行、还剩哪些。",
|
||||
sections: [
|
||||
{
|
||||
id: "workbar",
|
||||
title: "看懂工作栏",
|
||||
blocks: [
|
||||
{
|
||||
p: "工作栏默认位于输入框下方,显示当前目标、To-do 列表,以及为本次会话工作的子 Agent。已完成的条目会以“已完成”的状态保留,而不是消失。选中某一行,或在该行上按 Enter,即可打开详情。",
|
||||
},
|
||||
{
|
||||
rows: [
|
||||
["pending", "尚未开始。"],
|
||||
["in progress", "Codewhale 正在做的事——一次只有一项。"],
|
||||
["completed", "已完成。"],
|
||||
["cancelled", "已放弃,但仍保留在列表中,让你知道它被放弃了。"],
|
||||
],
|
||||
},
|
||||
{ p: "移动工作栏,或更改它显示的内容:" },
|
||||
{ code: "/workbar left\n/workbar bottom --save\n/workbar off", lang: "Codewhale" },
|
||||
{
|
||||
p: "可选位置有 `bottom`、`top`、`left`、`right` 和 `off`。加上 `--save` 会在以后的会话中沿用你的选择。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "goal",
|
||||
title: "设定跨回合持续的目标",
|
||||
blocks: [
|
||||
{
|
||||
p: "目标会让一个任务始终保持在视线之内,直到它完成。你可以自己设定;当你提出一个明确的完成条件时(比如“直到测试全部通过”),Codewhale 也可能自行设定,并用一行提示告诉你。控制权始终在你手里:",
|
||||
},
|
||||
{
|
||||
code: `/goal make the CLI tests pass on Windows budget: 200000
|
||||
/goal # show progress
|
||||
/goal pause
|
||||
/goal resume
|
||||
/goal done
|
||||
/goal clear`,
|
||||
lang: "Codewhale",
|
||||
},
|
||||
{
|
||||
p: "可选的 `budget:` 是这个目标的 token 上限。设定目标不会改变你的模式、审批设置或模型。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "relay",
|
||||
title: "在新会话中继续",
|
||||
blocks: [
|
||||
{
|
||||
p: "会话变长之后,`/relay` 会为新线程写一份交接说明,其中原样附上当前的 To-do 列表,让下一个会话从你真正所处的位置接着做,而不是从一段概括开始。可以加上一个重点来引导交接内容:",
|
||||
},
|
||||
{ code: "/relay finish the Windows test fixes", lang: "Codewhale" },
|
||||
{
|
||||
p: "带着父会话上下文启动的子 Agent 也会收到同一份列表,因此它们同样知道哪些已经做完。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "one-list",
|
||||
title: "弄清哪份列表才算数",
|
||||
blocks: [
|
||||
{
|
||||
p: "进度列表只有一份:To-do。Codewhale 写出来的计划——做法、风险和验证方式——说明的是它打算怎么做,并不跟踪进度,工作栏也不会把它显示成第二份列表。",
|
||||
},
|
||||
{
|
||||
p: "工作栏里的列表是给你看的。Codewhale 是通过对话中的普通结果看到自己的更新,这份列表不会在每一步都重新发送给模型。",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
next: [
|
||||
{
|
||||
href: "/docs/subagents",
|
||||
label: "并行运行 Agent",
|
||||
note: "把彼此独立的 To-do 条目交给子 Agent。",
|
||||
},
|
||||
{
|
||||
href: "/docs/modes",
|
||||
label: "设置模式与审批",
|
||||
note: "Operate 会规划有名字的步骤,并逐一核对结果。",
|
||||
},
|
||||
{
|
||||
href: "/docs/review",
|
||||
label: "查看改动",
|
||||
note: "检查每个已完成条目背后的实际改动。",
|
||||
},
|
||||
],
|
||||
sourceNote:
|
||||
"来源文档:docs/GUIDE.md、docs/MODES.md、docs/TOOL_SURFACE.md、docs/TOOL_LIFECYCLE.md · 修改时同步更新 docs-map.ts。",
|
||||
};
|
||||
@@ -16,7 +16,7 @@ describe("public website copy contracts", () => {
|
||||
expect(layout).toContain("docs-portal-band");
|
||||
// The hero copy is dictionary-driven now (#5337), so assert it where the
|
||||
// string actually lives rather than in the TSX.
|
||||
expect(EN_DOCS_SHELL.heroTitle).toBe("Find the guidance you need.");
|
||||
expect(EN_DOCS_SHELL.heroTitle).toBe("Get something done with Codewhale.");
|
||||
expect(layout).not.toContain("Section 02");
|
||||
expect(layout).not.toContain("How Codewhale works: ego");
|
||||
expect(layout).not.toContain("<Seal");
|
||||
|
||||
Reference in new issue
Block a user