release: Codewhale v0.9.12 (#5884)

release: Codewhale v0.9.12 candidate
This commit is contained in:
Hunter Bown authored and GitHub committed 2026-09-05 01:21:59 -07:00
commit dcd4c200f7
475 files changed
+39918 -22961

No files matched your search

+9 -4
View File
@@ -4,6 +4,9 @@ cd "$(dirname "$0")/../.."
# shellcheck source=/dev/null
. .buildkite/steps/common.sh
echo "--- hermetic test-home boundary"
sh scripts/with-hermetic-test-home.test.sh
# nextest profile `ci` lives in .config/nextest.toml alongside the test-group
# bounds that serialize the binary-spawning integration suites.
if ! command -v cargo-nextest >/dev/null 2>&1; then
@@ -24,9 +27,9 @@ fi
# exercise them is a weaker gate reporting green.
run_suite() {
echo "--- workspace tests"
cargo nextest run --workspace --all-features --locked --profile ci
scripts/with-hermetic-test-home.sh cargo nextest run --workspace --all-features --locked --profile ci
echo "--- doctests"
cargo test --workspace --all-features --locked --doc
scripts/with-hermetic-test-home.sh cargo test --workspace --all-features --locked --doc
}
if [ "$(id -u)" = "0" ] && [ "$(uname -s)" = "Linux" ]; then
@@ -35,6 +38,8 @@ if [ "$(id -u)" = "0" ] && [ "$(uname -s)" = "Linux" ]; then
# both must belong to the user that will actually run the suite.
chown -R builder:builder . "$CARGO_HOME" "$RUSTUP_HOME" 2>/dev/null || true
echo "--- re-exec as unprivileged user (root ignores permission bits)"
# The suite is expanded by the unprivileged child shell.
# shellcheck disable=SC2016
exec runuser -u builder -- env \
HOME=/home/builder \
PATH="$PATH" CARGO_HOME="$CARGO_HOME" RUSTUP_HOME="$RUSTUP_HOME" \
@@ -44,9 +49,9 @@ if [ "$(id -u)" = "0" ] && [ "$(uname -s)" = "Linux" ]; then
bash -eo pipefail -c '
cd "$1"
echo "--- workspace tests (uid $(id -u))"
cargo nextest run --workspace --all-features --locked --profile ci
scripts/with-hermetic-test-home.sh cargo nextest run --workspace --all-features --locked --profile ci
echo "--- doctests"
cargo test --workspace --all-features --locked --doc
scripts/with-hermetic-test-home.sh cargo test --workspace --all-features --locked --doc
' _ "$PWD"
fi
+4
View File
@@ -55,6 +55,10 @@ test-group = 'spawns-binaries'
# slow test in full so the run log is enough to diagnose without rerunning.
[profile.ci]
retries = 0
# A test that runs past ten minutes is a hang, not a slow test: terminate it
# and surface its captured output instead of letting the job die at the
# runner's 90-minute limit with no diagnostics (v0.9.12 Windows candidate).
slow-timeout = { period = "60s", terminate-after = 10 }
fail-fast = false
final-status-level = "slow"
failure-output = "immediate-final"
+11
View File
@@ -230,3 +230,14 @@ [email protected] = Alex Musichen <[email protected]
M-Maciej = M-Maciej <[email protected]>
serephus = Serephus <[email protected]>
# [email protected] = UNRESOLVED — 21 commits on main via #704; GitHub login not yet identified
qiuYliangM = 秋月凉梦 <[email protected]>
[email protected] = 秋月凉梦 <[email protected]>
whp233 = whp233 <[email protected]>
[email protected] = whp233 <[email protected]>
Gabriel-Degret = Gabriel Degret <[email protected]>
[email protected] = Gabriel Degret <[email protected]>
huangxianzhan = huangxianzhan <[email protected]>
[email protected] = huangxianzhan <[email protected]>
zhuowp = zhuowp <[email protected]>
[email protected] = zhuowp <[email protected]>
[email protected] = 秋月凉梦 <[email protected]>
+65 -2
View File
@@ -3,6 +3,7 @@
const assert = require("node:assert/strict");
const fs = require("node:fs");
const path = require("node:path");
const vm = require("node:vm");
const repoRoot = path.resolve(__dirname, "..", "..");
const {
@@ -59,6 +60,60 @@ assert.match(
"the non-PR CNB fallback must be named explicitly",
);
const npmSmokeJob = ci.match(/^ npm-wrapper-smoke:\n([\s\S]*?)(?=^ \S)/m)?.[1];
assert.ok(npmSmokeJob, "CI must retain the required npm-wrapper job");
assert.match(
namedStep(npmSmokeJob, "Build wrapper binaries"),
/run: cargo build --release --locked -p codewhale-cli -p codewhale-tui/,
);
assert.match(
namedStep(npmSmokeJob, "Smoke wrapper install and delegated entrypoints"),
/run: node scripts\/release\/npm-wrapper-smoke\.js/,
);
const npmSmokeSteps = npmSmokeJob.split(/(?=^ - )/m).slice(1);
// Exercise the workflow's actual Boolean guards. A successful location echo
// must never substitute for the build/install smoke on a heavy pull request.
const npmSmokeCases = [
// name, event, heavy, OS, trusted, cache success, execute, Linux deps, CNB
["own PR", "pull_request", true, "ubuntu-latest", true, true, true, true, false],
["fork PR", "pull_request", true, "ubuntu-latest", false, true, true, true, false],
["PR cache failure", "pull_request", true, "ubuntu-latest", false, false, true, true, false],
["light PR", "pull_request", false, "ubuntu-latest", true, true, false, false, false],
["manual Ubuntu", "workflow_dispatch", true, "ubuntu-latest", true, true, true, true, false],
["main Ubuntu", "push", true, "ubuntu-latest", true, true, false, false, true],
["main macOS", "push", true, "macos-latest", true, true, true, false, false],
["main Windows", "push", true, "windows-latest", true, true, true, false, false],
["light main", "push", false, "ubuntu-latest", true, true, false, false, false],
["schedule", "schedule", true, "ubuntu-latest", true, true, false, false, false],
];
for (const [label, event, heavy, os, trusted, cache, execute, linuxDeps, cnb] of npmSmokeCases) {
const context = {
needs: { changes: { outputs: { heavy: String(heavy), trusted: String(trusted) } } },
github: { event_name: event },
matrix: { os },
steps: { sccache: { outcome: cache ? "success" : "failure" } },
};
const jobGuard = npmSmokeJob.match(/^ if: (.+)$/m)?.[1];
assert.ok(jobGuard, "the wrapper job must retain its event guard");
const jobEnabled = vm.runInNewContext(jobGuard, context);
for (const step of npmSmokeSteps) {
const name = step.match(/^ - (?:name|uses): (.+)$/m)?.[1];
const guard = step.match(/^ if: (.+)$/m)?.[1];
assert.ok(name && guard, "every wrapper step must have an explicit guard");
let expected = execute;
if (name === "Skip npm wrapper smoke for light change") expected = !heavy;
else if (name === "Install Linux system dependencies") expected = linuxDeps;
else if (name === "Linux smoke location") expected = cnb;
else if (name === "Enable sccache" || name === "sccache stats") expected = execute && cache;
assert.equal(
Boolean(jobEnabled && vm.runInNewContext(guard, context)),
expected,
`${label}: ${name} must ${expected ? "execute" : "stay skipped"}`,
);
}
}
console.log(`Wrapper CI guards OK: ${npmSmokeCases.length} event cases, ${npmSmokeSteps.length} steps each.`);
assert.match(ci, /^ workflow_dispatch:\n inputs:\n expected_sha:/m);
const manualForceBlock = ci.match(
/if \[\[ "\$\{EVENT_NAME\}" == "workflow_dispatch" \]\]; then([\s\S]*?)\n\s+if \[\[ "\$\{EVENT_NAME\}" == "schedule" \]\]; then/,
@@ -501,10 +556,18 @@ assert.equal(
2,
"both glibc recovery branches must name codewhale-cli",
);
// The archive installer never overwrites an existing command: it validates the
// retired TUI path against the consolidated bytes and leaves upgrades to
// `codewhale update`, which migrates `codewhale-tui` beside the canonical pair.
assert.match(
archiveInstaller,
/legacy_tui="\$BIN_DIR\/codewhale-tui"[\s\S]*install_binary "\$SCRIPT_DIR\/codewhale" "\$legacy_tui"/,
"archive upgrades must refresh the retired TUI path from consolidated bytes",
/legacy_tui="\$BIN_DIR\/codewhale-tui"[\s\S]*check_destination "\$SCRIPT_DIR\/codewhale" "\$legacy_tui"/,
"archive installs must validate the retired TUI path against consolidated bytes",
);
assert.doesNotMatch(
archiveInstaller,
/install_binary "\$SCRIPT_DIR\/codewhale" "\$legacy_tui"/,
"archive installs must not overwrite an existing retired TUI command",
);
assert.doesNotMatch(
cliDispatcher,
+20 -13
View File
@@ -245,6 +245,12 @@ jobs:
(cd "integrations/${bridge}" && npm test)
done
- name: Run computer-use plugin suites
# The bundled plugin is dependency-free too; its suites cover the
# manifest contract, the registry, the exec/ssh transport, the four
# platform backends, and the MCP stdio protocol. No GUI input runs.
run: (cd plugins/computer-use && npm test)
safety-gate:
name: Safety gate
needs: changes
@@ -640,7 +646,8 @@ jobs:
# Same ternary rationale as the Test job: light legs only echo, so keep
# them off macOS/Windows runners. On pull_request the matrix is
# ubuntu-only, so the required "npm wrapper smoke (ubuntu-latest)"
# context is unaffected.
# context is unaffected. Heavy pull requests execute the Ubuntu smoke
# here; their branches may not be mirrored to CNB.
timeout-minutes: 30
runs-on: ${{ needs.changes.outputs.heavy == 'true' && matrix.os || 'ubuntu-latest' }}
strategy:
@@ -651,26 +658,26 @@ jobs:
if: needs.changes.outputs.heavy != 'true'
run: echo "No executable Rust changes detected; preserving required npm wrapper smoke context."
- uses: actions/checkout@v7
if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch')
if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request')
- uses: dtolnay/rust-toolchain@stable
if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch')
if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request')
- uses: mozilla-actions/[email protected]
id: sccache
continue-on-error: true
if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch')
if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request')
- name: Enable sccache
if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch') && steps.sccache.outcome == 'success'
if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request') && steps.sccache.outcome == 'success'
shell: bash
run: |
echo "SCCACHE_GHA_ENABLED=true" >> "${GITHUB_ENV}"
echo "RUSTC_WRAPPER=sccache" >> "${GITHUB_ENV}"
echo "SCCACHE_IGNORE_SERVER_IO_ERROR=1" >> "${GITHUB_ENV}"
- uses: actions/setup-node@v7
if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch')
if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request')
with:
node-version: 20
- name: Install Linux system dependencies
if: needs.changes.outputs.heavy == 'true' && matrix.os == 'ubuntu-latest' && github.event_name == 'workflow_dispatch'
if: needs.changes.outputs.heavy == 'true' && matrix.os == 'ubuntu-latest' && (github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request')
run: |
for i in 1 2 3 4 5; do
sudo apt-get update && break
@@ -679,12 +686,12 @@ jobs:
done
sudo apt-get install -y libdbus-1-dev pkg-config
- uses: Swatinem/rust-cache@v2
if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch')
if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request')
with:
cache-bin: false
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Build wrapper binaries
if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch')
if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request')
# The smoke validates wrapper install/delegation plumbing, not
# codegen quality, so skip fat LTO + codegen-units=1 for a much
# cheaper release build. Shipped binaries keep the real profile via
@@ -694,16 +701,16 @@ jobs:
CARGO_PROFILE_RELEASE_CODEGEN_UNITS: '16'
run: cargo build --release --locked -p codewhale-cli -p codewhale-tui
- name: Smoke wrapper install and delegated entrypoints
if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch')
if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request')
run: node scripts/release/npm-wrapper-smoke.js
- name: sccache stats
if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch') && steps.sccache.outcome == 'success'
if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request') && steps.sccache.outcome == 'success'
continue-on-error: true
shell: bash
run: sccache --show-stats
- name: Linux smoke location
if: needs.changes.outputs.heavy == 'true' && matrix.os == 'ubuntu-latest' && github.event_name != 'workflow_dispatch'
run: echo "Linux npm wrapper smoke runs on CNB for mirrored first-party branches."
if: needs.changes.outputs.heavy == 'true' && matrix.os == 'ubuntu-latest' && github.event_name != 'workflow_dispatch' && github.event_name != 'pull_request'
run: echo "Linux npm wrapper smoke runs on CNB for non-PR release/main pushes; pull requests run directly on Ubuntu."
mobile-smoke:
name: Mobile runtime smoke
+3
View File
@@ -159,3 +159,6 @@ CODEWHALE_0_9_0_CUTOVER.md
# Local playwright MCP artifacts
.playwright-mcp/
# computer-use plugin smoke receipts (per-run local evidence)
plugins/computer-use/receipts/
@@ -0,0 +1,67 @@
---
target: Website install step
total_score: 21
max_score: 28
na_heuristics: 1,3,9
p0_count: 0
p1_count: 0
target_identity: "file:/Volumes/VIXinSSD/CW/worktrees/cw-site-github-install-guide-20260904/web/components/getting-started-steps.tsx"
target_fingerprint: "sha256:f5180f0101eebebe501c90cb467dccca62da8c3ae726a8f1ca59bc2ab5cbe50f"
target_path: /Volumes/VIXinSSD/CW/worktrees/cw-site-github-install-guide-20260904/web/components/getting-started-steps.tsx
timestamp: 2026-09-05T00-42-39Z
slug: web-components-getting-started-steps-tsx
closed: true
---
Method: dual-agent (A: /root/critique_design · B: /root/critique_detector)
# Website install step — independent Assessment A
Scope: only the changed install step in `web/lib/content/getting-started.ts`, rendered by `web/components/getting-started-steps.tsx`. Read the workspace, repository, and `web/AGENTS.md` guidance. No context script, detector, installation, or external request was run. No detector findings were seen. No source files were edited.
## Verdict
The copy achieves the intended hierarchy: published GitHub release on macOS/Linux first; `codewhale update` for later releases; npm/Cargo available through the full guide; local development builds explicitly separate. English and Chinese convey the same four facts. No critical copy defect found. This is a successful narrow correction, not an assessment of the complete guide or website.
The wording is concrete and restrained. It belongs to a technical product and does not need a broader visual redesign. The shared content source preserves consistency between its consumers and avoids a page-specific localization fork.
## Actual observations
- Opened the built `/en/docs/guide` and `/zh/docs/guide` in a fresh native CUA Chrome Guest tab. Both displayed the changed paragraph, installer command, `codewhale doctor`, and descriptive localized Full install guide link.
- The complete installer command is present in the accessibility text in both locales.
- In the inspected desktop layout, the new long command extends beyond the narrow first-column code box and requires horizontal scrolling. The visible first line stops partway through the URL; the full release-installer destination and pipe are not visible together. This is contained code-block overflow, not established page-level overflow or lost text.
- Source inspection confirms `<pre><code>` semantics, `overflow-x: auto` and `white-space: pre`. The renderer offers no copy control. Full keyboard scrolling, text selection, screen-reader announcements, and contrast were not tested.
- Local installer source selects GitHub release assets and handles Darwin/Linux; local docs show this same installer and the later updater. These are source receipts, not proof of current public downloads or successful installation.
## Priority issue
**P2 — The longer primary install command is awkward to inspect and copy at the rendered desktop width.** The new default command makes the existing horizontal-scroll presentation more consequential: a first-time reader cannot see the complete command in one glance. Make a narrowly scoped improvement for this install command: permit soft wrapping without changing its text, or reuse an existing accessible copy control. If keeping horizontal scrolling, verify keyboard reachability and full-command selection. Do not expand this into a redesign of the four-step grid or the untouched steps.
No P0/P1 issue found within this copy slice. The release/update/alternative/local-build wording needs no additional prerequisite text or warning banner.
## Applicable heuristic scores
| Heuristic | Score | Scope-specific basis |
|---|---:|---|
| System status | n/a | Static instruction; no installation was performed. |
| Match to real world | 4 | Clear platform, release provenance, later-update instruction. |
| User control/freedom | n/a | No stateful operation in this slice. |
| Consistency | 4 | Shared EN/ZH content, matching local installer/docs. |
| Error prevention | 3 | Published release and local development build are distinguished. |
| Recognition over recall | 2 | Complete command requires horizontal inspection. |
| Flexibility/efficiency | 2 | Long command has no direct copy action; keyboard scrolling unverified. |
| Aesthetic/minimalism | 3 | Short, focused paragraph; one primary route with alternatives deferred. |
| Error recovery | n/a | Installer outcome/recovery is outside the reviewed copy. |
| Help/documentation | 3 | Clear localized link to deeper installation guidance. |
| **Total** | **21/28** | **Good; narrow command usability improvement remains.** |
Cognitive load is low for the changed decision: one recommended installation path, one validation command, and one deeper-reading link. Alternatives are named without presenting competing command menus. The long command adds interaction effort rather than conceptual ambiguity.
## Evidence limits
The CUA browser provider was unavailable; native CUA Chrome supplied a fresh Guest tab. EN/ZH desktop screenshots were observed without changing viewport/window size; output image size was 768×930, exact CSS viewport unmeasured. The Guest tab was closed, then browser ownership was returned to Assessment B. No mobile inspection, homepage rendering, external GitHub/install URL validation, install/update execution, broad website audit, or test suite was performed by Assessment A. Untouched steps and full-guide content were not assessed.
Questions skipped: the user already authorized implementation.
## Independent responsive evidence
B ran the narrow markup detector once: exit 0, zero findings. Native Chrome at 390px confirmed EN/ZH command-container overflow beyond the grid, and keyboard Tab reached the localized Full install guide link with a visible outline. Root independently measured the same intrinsic-width problem in CUA: a 343px grid allocated a 382.6px track, extending the command box beyond the viewport. This strengthens A’s scoped P2 without changing its provisional 21/28 score. No P0/P1 established. Both assessments were isolated, and A completed before B findings reached synthesis. No mutable overlay was available; native screenshots and read-only DOM geometry supplied the evidence. No installer or updater was executed.
@@ -0,0 +1,49 @@
---
score: 23
score_max: 32
p0: 0
p1: 1
p2: 2
p3: 2
target_identity: "file:/Volumes/VIXinSSD/CW/worktrees/cw-website-app-alignment-20260904/web/app/[locale]/page.tsx"
target_fingerprint: "sha256:16203671072c07e4e9ea8e679a5fe3482c6d78e6518cb7b53a3f0c18768866b1"
target_path: /Volumes/VIXinSSD/CW/worktrees/cw-website-app-alignment-20260904/web/app/[locale]/page.tsx
timestamp: 2026-09-05T01-31-05Z
slug: web-app-locale-page-tsx
closed: true
---
# Integrated website critique and polish
Mode: Persuade (home), Read (guide). Two isolated assessments; A completed
before B findings were read. A scored 23/32 applicable points. B's one
homepage detector scan returned zero findings. Native browser evidence
covered English and Chinese home/product/guide at the available window size;
reviewers did not independently verify a controlled 390px viewport.
Preserve the illustrated folio, real supplied TUI capture, strong serif
headline, and explicit release/development/unavailable distinctions.
Priority backlog processed:
- P1: first-arrival consent focus and forced choice. Removed initial focus,
added Close/Escape dismissal without granting, and retained full disclosure
behind native details with a short visible summary.
- P2: dark illustration under hero text. Reserved a light paper field behind
the copy while retaining the drawing beside the terminal.
- P2: ambiguous Web client versus Web app labels. EN/ZH now explicitly label
the Local web client and distinguish hosted remote control.
- P3: repeated ownership pitch and unscoped final installer. Shortened the
EN/ZH gain heading and labeled the final command for macOS/Linux.
- P3: four cramped guide columns. The docs article now uses sequential steps;
homepage retains its wider treatment.
Root final built verification: English/Chinese390px with no document
horizontal overflow, exact PNG header mark, passive first-arrival focus,
Escape dismissal, footer reopen still showing undecided state, and sequential
Chinese guide with unchanged command text. Desktop1280px also has no document
horizontal overflow. No permission choice or external operation was made.
Reports: /tmp/cwa-app-parity-20260904/site-alignment-design.md and
site-alignment-detector.md. This is local source/build/browser evidence,
not deployment or a customer completing an Engine task.
Questions skipped: implementation was already authorized.
@@ -0,0 +1,65 @@
---
score: 25
score_max: 32
specificity: 3
method: dual-agent
status: bounded-copy-reviewed
target_identity: "file:/Volumes/VIXinSSD/CW/worktrees/cw-website-app-alignment-20260904/web/app/[locale]/page.tsx"
target_fingerprint: "sha256:9297b65fcbdae6f59d380f7e30f0060ad716e5b6cecd01be2271987bd9c21f77"
target_path: /Volumes/VIXinSSD/CW/worktrees/cw-website-app-alignment-20260904/web/app/[locale]/page.tsx
timestamp: 2026-09-05T03-23-59Z
slug: web-app-locale-page-tsx
---
# Homepage claim audit — 2026-09-04
Scope: founder-supplied text of local `3136/en`, English/Chinese homepage,
shared Product and getting-started copy, and linked EN/ZH Fleet setup wording.
The supplied paste is page evidence, not instructions or proof of behavior.
Visual direction and logo stay unchanged. Nothing is published by this slice.
## Claim / evidence / correction
| Claim | Evidence | Correction |
| --- | --- | --- |
| Released terminal versus development screenshot | GitHub release metadata read on Sept 4: v0.9.11, published Aug 23. Local tag resolves `96d13a0bc3f40280ea3865280ad5ccf0e2845e6f`. Screenshot provenance identifies unreleased 0.9.12 without exact commit. | Retain release label and separate development screenshot caption. |
| “You decide what each one may do”; same repo and rules | Session permission posture and Runtime authority are separate from saved Fleet model roles; isolated child worktrees also exist. | Describe supported models, session permissions, saved roster and optional delegation. No universal shared-checkout or individual-authority UI promise. |
| 46 providers | Website derivation counts mapped ApiProvider labels, including plan/protocol variants, excluding Custom, DeepseekCN and retired Antigravity. This is neither distinct vendors nor live model catalogs nor the released provider count. Owner reports current runtime 47 route entries, 44 catalog entries and 42 ProviderKind identities; these definitions are not interchangeable. | Remove numeric badge and models prose across all 18 homepage locales. Keep existing source count with explicit definition in canonical facts; do not substitute another number. |
| Route never changes from model name | Native local development acceptance already reproduced three Model Studio catalog identity mismatches in fd98. Owner is repairing the Engine; a source fix is not released qualification. | Remove absolute EN/ZH guarantees; ask users to inspect selected provider/model/endpoint. Do not equate catalog identity failure with proven inference misrouting. |
| Local servers need no key | Authentication depends on server configuration. | Say local servers may require authentication. |
| Plan is read-only; nothing is written | v0.9.11 `docs/MODES.md` and Engine tool catalog describe central file-mutation/shell refusal, policy-allowed research and separately persisted session settings/state. | Name file/shell boundary and possible external research; remove blanket no-write claim from Product. |
| `/fleet setup` exact four-step wizard | v0.9.11 `crates/tui/src/tui/views/fleet_setup.rs` resolves `SelectedFleet` or `LegacyProfiles`. Selected named Fleet opens its editor. Legacy path chooses role/model, with optional thinking on review. | Keep command, make step optional and accurately describe both destinations. Shared homepage/guide and EN/ZH Fleet docs updated. No published binary interaction claimed. |
| Install/auth commands | v0.9.11 source contains auth set provider handling and Fleet setup; GitHub assets include terminal Linux/macOS/Windows archives. | Keep commands and released terminal target statement. No fresh install, key write or provider call performed. |
| Public web sign-in and same-session remote control available | Source routes and local fixtures exist. This audit has no deployed authenticated same-session task acceptance. | EN/ZH homepage/Product say development preview and explicitly unverified public remote control. |
| Desktop alpha builds exist on three platforms | Local macOS Dogfood startup has a receipt; Linux/Windows downloadable Tauri artifacts are not established. Windows terminal installer is not Tauri desktop proof. | Describe tested local macOS development build and no released desktop app download. |
| Nothing on this site can charge you | An absolute statement exceeds evidence and obscures provider billing. | State terminal needs no Codewhale account; provider usage belongs to the user's provider account; account creation does not purchase model access. |
| Ask/Auto-Review/Full Access absolutes | Approval rules, saved posture and hard policy boundaries affect actual execution. | Product describes rule-based prompts/review and persistent hard boundaries; removes universal every-write and never-default claims. |
## Evidence boundaries
Read-only release metadata and asset inventory:
`/tmp/cwa-app-parity-20260904/site-release-claims-v0911.json`.
No release artifact was downloaded or executed during this slice. Git tag source
is source evidence, not a fresh installed task acceptance. No authenticated
public web flow, provider request, deployment, billing action or customer task
was performed. Source/provider integration remains independently qualified by
its owner.
Numeric models prose is corrected in all homepage languages. Broader hero,
availability and linked documentation claims outside EN/ZH remain unaudited in
this slice and must not be treated as reviewed translations. The English and
Chinese scope is deliberate; a passing locale shape check does not establish
translation accuracy or truth across the rest of the site.
## Verification
Initial tests: 405 passed, 2 failed (old keyless-launch phrasing assertion and
models-body placeholder parity). The old assertion now checks the narrower
behavior; every homepage models paragraph no longer uses the ambiguous count.
Final tests: 407 passed across 47 files, 0 failed.
Further build/browser/critique receipts are recorded below when complete.
Final validation: facts, 23-topic docs, locale/catalog checks passed; lint 0 errors and 2 pre-existing logo-image warnings; webpack production build generated785 pages. Parent inspected EN desktop and saved `/tmp/cwa-app-parity-20260904/screenshots/site-claims-en-desktop.png`.
Isolated Impeccable A:25/32 applicable, specificity3/4. B ran the actual detector:0 findings; inspected EN desktop and390 screenshot. A had mobile screenshot timeouts and collected EN/ZH DOM evidence only; B did not complete Chinese browser verification. No complete bilingual visual QA claim. Reports: `/tmp/cwa-app-parity-20260904/site-claims-design.md` and `site-claims-detector.md`.
Two bounded P2 refinements remain for the founder's newly requested hero-scale slice: mark the Fleet heading visibly optional and verify/fix the narrow install Copy control's out-of-bounds geometry. The copy audit is committed separately to release canonical facts ownership; this is not a declaration that the full homepage is polished. The newly requested replacement terminal capture and default-on/opt-out analytics policy are separate in-progress changes, not included in this commit.
@@ -0,0 +1,44 @@
---
target_identity: "file:/Volumes/VIXinSSD/CW/worktrees/cw-website-app-alignment-20260904/web/app/[locale]/page.tsx"
target_fingerprint: "sha256:f6b255122ad152248255f391aaa3034bb30f620c279bec26d3d88ef465a51c2f"
target_path: /Volumes/VIXinSSD/CW/worktrees/cw-website-app-alignment-20260904/web/app/[locale]/page.tsx
timestamp: 2026-09-05T03-51-38Z
slug: web-app-locale-page-tsx
closed: true
---
Method: dual-agent (A: critique_design; B: critique_detector).
# Terminal hero scale — 2026-09-04
The founder-provided terminal capture now spans the hero width beneath the
headline and actions. Mobile stacks the same content. Fleet setup is explicitly
optional in EN/ZH. Install wrappers allow shrinking so the command scrolls
inside its own box and the Copy button stays within the page.
Source asset: codewhale-ops/design/website-20260904/founder-tui-15fe-20260904.png,
2760x1494, SHA256 5a762fcee58428b9745710a459b3f0ad2ccadf37406d271473701b5f065de762.
It is a development capture, not evidence of a new published release.
Verification:407 tests passed across47 files; production webpack build785
pages. Final lint0 errors,2 existing logo img warnings; locale/catalog checks
passed. Logs: /tmp/cwa-app-parity-20260904/site-scale-{tests,build,final-lint,final-locales}.log.
Direct in-app browser inspection: EN/ZH at390x844 and1280x900. At390, both
pages report innerWidth390, clientWidth375 and scrollWidth375 (scrollbar uses
15px). Copy bounds EN290.53–347, ZH296.31–347: both contained. Keyboard
focus on EN Copy is visible; command scrolling is independent of page width.
Clipboard submission itself was not exercised. Screenshots under
/tmp/cwa-app-parity-20260904/screenshots/:
site-scale-en-390.png, site-scale-zh-390.png,
site-scale-install-en-390.png, site-scale-en-desktop.png,
site-scale-zh-desktop.png. Temporary viewport override reset and QA tab closed.
Independent review: A27/32, specificity3/4; B zero findings across its scoped
TSX scans. Reports /tmp/cwa-app-parity-20260904/scale-voice-design.md and
scale-voice-detector.md. Reviewers could not complete narrow browser checks;
parent verification above supplies that missing evidence. Optional Fleet
framing and narrow install containment findings are resolved for this slice.
No hosted CI, deployment, provider call, or customer task proof. Default-on
usage migration and moving website usage controls to Privacy remain pending
and are deliberately not claimed by this visual slice.
+120 -4
View File
@@ -7,16 +7,56 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.9.12] - 2026-09-04
Codewhale v0.9.12 puts computer use in the binary, opens two new routes —
Alibaba Model Studio through the data-driven provider table and Concentrate
as an opt-in BYOK Responses gateway — and adds cloud dispatch plus a
config-gated per-session control socket. The shell is the other half of the
release: a new launch card, the work surface docked under the composer, one
focus owner for Tab, tool cells that carry their own state, and `fleet` as
the public word for the live collective. The complete item-level change
record is retained below the categorized release highlights.
### Added
- Computer use ships with the binary. The `computer-use` plugin — 38 tools
across macOS, Windows, Linux and HarmonyOS, accessibility-first observation
with pixel fallback, screenshots, zoom, screen recording, and registered
remote computers over ssh and hdc — is embedded in Codewhale and written to
`$CODEWHALE_HOME/builtin-plugins` on first run, so every install channel
carries it. It lists as `builtin · not-reviewed` and stays disabled until
you review and enable it: shipping it is not consenting to it. The
stdlib-Python computer-use server it replaces is gone.
- Alibaba Model Studio joins the data-driven provider table as an
`openai-compatible` descriptor: international compatible-mode endpoint,
`DASHSCOPE_API_KEY` credential, live `/v1/models` discovery. Qwen 3.8
Flash and Qwen 3.8 Max arrive through the catalog authority — never a
hard-coded id.
- Concentrate: first-class opt-in BYOK Responses gateway with live models
discovery and typed SSE streaming (#5725).
- Cloud dispatch: remote runner offloads coding agent tasks to isolated
cloud sandboxes with machine token auth and structured job tracking
(#5701, #5712).
- Per-session control socket: config-gated `[control_socket]` table binds
`<sessions-dir>/<session-id>/control.sock` per running session, exposing
message, interrupt, relaunch, and status JSON-RPC verbs (#5533, #5831).
### Changed
- **Anonymous usage counting is on by default.** The 0.9.11 release asked
first; 0.9.12 counts the same aggregate version/platform, session, feature
and error totals unless you turn it off, and says so once at first launch
(policy notice version 5, schema 3, `notice_version` replacing
`consent_version`). Every recorded opt-out stays off: a durable
`telemetry = false`, a decline recorded under the old opt-in notice,
unreadable privacy state, and the `CODEWHALE_TELEMETRY=0` / `--telemetry
false` kill switches. Showing the disclosure records only that it was
shown, never an acceptance. `codewhale config set telemetry false` turns
it off and wipes queued counts; `codewhale config set telemetry true`
deliberately re-enables it. Nothing new is collected: no conversations,
code, prompts, files, names, model content, credentials, or IP.
- The launch screen is our own card take: a thin top line
`⑂ branch path`; a centred bordered card with the whale mark,
`Codewhale` + version, one announcement line only when it is true (the
@@ -24,7 +64,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
session / Changelog / Quit with their real chords right-aligned. Enter
runs the highlighted entry, Up/Down move it, and typing goes straight to
the composer. The card dissolves on the first keystroke or command
(≤240 ms, instant under reduced motion).
(≤240 ms, instant under reduced motion) (#5826, #5801, #5815, #5286).
- The work surface sits under the composer by default, keeping history
readable and leaving the stage unencumbered (#5809).
- Skills command shapes: FEAT-022 command shapes and retained-host
validation (#5825, #5829).
- After the card dissolves, the working screen shows `⑂ branch path` with
`⋮ MCP n/m` on the right, the transcript starts with the
`◆ session_start` receipt (naming the configured session-start hooks),
@@ -101,12 +145,83 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- TUI/CLI: Pod is the public roster surface. User-facing Fleet wording
moves to Pod; durable receipt keys stay compatible (#5776).
### Contributors
- **hexin ([@h3c-hexin](https://github.com/h3c-hexin))** — provider-native web
search across four routes (#5682, #5683, #5685, #5687), authoritative
edit-last-turn boundaries (#5621), Kimi Code k3-256k (#5622),
post-compaction input-token reporting (#5623), and preserving the scheduled
model selection in automations (#5650).
- **秋月凉梦 ([@qiuYliangM](https://github.com/qiuYliangM))** — co-authored the
edit-last-turn boundary fix (#5621), Kimi Code k3-256k support (#5622), and
post-compaction input-token reporting (#5623).
- **Isabel Wu ([@wuisabel-gif](https://github.com/wuisabel-gif))** — live
session token totals (#5624), persisted context-pressure warnings (#5629),
discoverable Fleet roster editing (#5604), the capability-gated cursor
accent (#5599), and `/copy` for the latest completed response (#5692).
- **Paulo Aboim Pinto ([@aboimpinto](https://github.com/aboimpinto))** —
Windows verbatim-path operands preserved through POSIX word splitting
(#5610), the plugins group moved onto the command shapes (#5657), FEAT-022
skills command shapes with retained-host validation (#5825), and FEAT-020
plugin command shapes re-landed on main (#5865).
- **Alex Musichen ([@musichen](https://github.com/musichen))** — a stable
DeepSeek heading in the configured-view model picker, keeping every official
catalog model for the active provider visible (#5689).
- **[@gaord](https://github.com/gaord)** — the `GET /v1/fleet/profiles`
runtime API endpoint, reusing the FleetManager validation path (#5688).
- **Sh1Zuku ([@SparkofSpike](https://github.com/SparkofSpike))** — corrected
English documentation inaccuracies and the first zh_hans translations for
the Tier-2 docs (#5613).
- **[@M-Maciej](https://github.com/M-Maciej)** — goal continuation cadence
(#5591) and the per-session control socket (#5533, #5831).
- **Serephus ([@serephus](https://github.com/serephus))** — nixpkgs update
(#5669).
- **[@whp233](https://github.com/whp233)** — `wire = responses|anthropic` for
openai-compatible custom routes and opencode-zen muse-spark (#5716, landed
as #5719).
- **Gabriel Degret ([@Gabriel-Degret](https://github.com/Gabriel-Degret))** —
found the reasoning-only retry gap and built the first fix; landed as the
`[reasoning_only]` retry ceiling with a request-scoped nudge (#5867).
- **[@huangxianzhan](https://github.com/huangxianzhan)** — the
`x-opencode-session` header for OpenCode Go and Zen gateways (#5868).
- **[@zhuowp](https://github.com/zhuowp)** — task origin preserved in job
snapshots (#5869).
- **AdityaG ([@AdityaVG13](https://github.com/AdityaVG13))** — a ten-commit
performance pass: a zero-copy LaTeX fast path for streaming render,
single-pass token accounting on the per-turn pressure paths, memoized
provider resolution in the catalog cutline, parsing the bundled and
on-disk models.dev catalogs once per process (interactive boot -70%),
adaptive poll cadence for foreground shell completion, and worker caps
across the read-only diagnostic family.
- **Nightt ([@nightt5879](https://github.com/nightt5879))** — isolated remote
recovery lease generations (#5790).
- **[@yiheng-kkk](https://github.com/yiheng-kkk)** — replaced stale todo
transcript snapshots so a rewritten todo list renders its current tasks
(#5871, #5873).
- **[@Lfanxing](https://github.com/Lfanxing)** — Moonshot routes now degrade
incompatible tool definitions per request instead of failing the turn.
- **WissssleyL ([@Lstarsky0](https://github.com/Lstarsky0))** — moved
`docs/subagents` and `docs/mcp` onto the dictionary spine (#5337), with the
metaTitle probe that keeps them there.
Reports and reproductions that shaped this release:
- **[@slowly247](https://github.com/slowly247)** — reported the Ollama input
budget collapsing to 1,024 tokens on 32K local models (#5820).
- **[@ronohara](https://github.com/ronohara)** — reported the engine stopping
after recoverable network errors, with the reproduction that pinned the
discarded approval (#5769).
- **[@Lujc0523](https://github.com/Lujc0523)** — asked for ACP session
configuration of mode and model (#5863).
- **[@senka9h](https://github.com/senka9h)** — reported that `serve --acp`
lacked `session/list` and `session/load` (#5864).
### Added
- Native ChatGPT sign-in for the `openai-codex` route: `codewhale auth
chatgpt` opens a browser PKCE flow and stores refreshable tokens in
Codewhale-owned credentials — no Codex CLI install required.
`/auth chatgpt-revoke` clears them off the event loop (#5784).
`/auth chatgpt-revoke` clears them off the event loop (#5784, #5778).
- MCP servers and plugins can be connected self-serve from the session: a
unified auth flow with rotation-safe token handling, a spoken authorization
URL, and catalog refresh when stored credentials stop working (#5747).
@@ -136,7 +251,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Compaction publishes a structured survival contract for session-tree
journal entry types (`crates/tui/src/compaction/SURVIVAL_CONTRACT.md`) and
fails closed when the last user round, tool results, `/anchor` text, or
checkpoint receipt would vanish (#4394).
checkpoint receipt would vanish (#4394, #5782).
- Internal: `codewhale-config` gains `RouteAuthoritySnapshot`, one immutable
authority that owns a compiled provider catalog together with the route
resolver projected from it, so a picker, a readiness view, and an execution
@@ -7801,7 +7916,8 @@ overflow report and `/theme` picker edge-wrapping patch in #1814.
Older releases (v0.8.39 and earlier) are archived in [docs/CHANGELOG_ARCHIVE.md](docs/CHANGELOG_ARCHIVE.md).
[Unreleased]: https://github.com/Hmbown/CodeWhale/compare/v0.9.11...HEAD
[Unreleased]: https://github.com/Hmbown/CodeWhale/compare/v0.9.12...HEAD
[0.9.12]: https://github.com/Hmbown/CodeWhale/compare/v0.9.11...v0.9.12
[0.9.11]: https://github.com/Hmbown/CodeWhale/compare/v0.9.10...v0.9.11
[0.9.10]: https://github.com/Hmbown/CodeWhale/compare/v0.9.9...v0.9.10
[0.9.9]: https://github.com/Hmbown/CodeWhale/compare/v0.9.8...v0.9.9
Generated
+21 -66
View File
@@ -764,7 +764,7 @@ dependencies = [
[[package]]
name = "codewhale-agent"
version = "0.9.11"
version = "0.9.12"
dependencies = [
"codewhale-config",
"serde",
@@ -772,7 +772,7 @@ dependencies = [
[[package]]
name = "codewhale-app-server"
version = "0.9.11"
version = "0.9.12"
dependencies = [
"anyhow",
"axum",
@@ -802,11 +802,11 @@ dependencies = [
[[package]]
name = "codewhale-build-support"
version = "0.9.11"
version = "0.9.12"
[[package]]
name = "codewhale-cli"
version = "0.9.11"
version = "0.9.12"
dependencies = [
"anyhow",
"chrono",
@@ -847,14 +847,14 @@ dependencies = [
[[package]]
name = "codewhale-command-contract"
version = "0.9.11"
version = "0.9.12"
dependencies = [
"codewhale-core",
]
[[package]]
name = "codewhale-config"
version = "0.9.11"
version = "0.9.12"
dependencies = [
"anyhow",
"codewhale-execpolicy",
@@ -874,7 +874,7 @@ dependencies = [
[[package]]
name = "codewhale-core"
version = "0.9.11"
version = "0.9.12"
dependencies = [
"anyhow",
"async-trait",
@@ -899,7 +899,7 @@ dependencies = [
[[package]]
name = "codewhale-execpolicy"
version = "0.9.11"
version = "0.9.12"
dependencies = [
"anyhow",
"codewhale-protocol",
@@ -908,7 +908,7 @@ dependencies = [
[[package]]
name = "codewhale-hooks"
version = "0.9.11"
version = "0.9.12"
dependencies = [
"anyhow",
"async-trait",
@@ -926,7 +926,7 @@ dependencies = [
[[package]]
name = "codewhale-lane"
version = "0.9.11"
version = "0.9.12"
dependencies = [
"anyhow",
"chrono",
@@ -941,7 +941,7 @@ dependencies = [
[[package]]
name = "codewhale-mcp"
version = "0.9.11"
version = "0.9.12"
dependencies = [
"anyhow",
"serde",
@@ -951,14 +951,14 @@ dependencies = [
[[package]]
name = "codewhale-paths"
version = "0.9.11"
version = "0.9.12"
dependencies = [
"dirs",
]
[[package]]
name = "codewhale-protocol"
version = "0.9.11"
version = "0.9.12"
dependencies = [
"chrono",
"serde",
@@ -968,7 +968,7 @@ dependencies = [
[[package]]
name = "codewhale-release"
version = "0.9.11"
version = "0.9.12"
dependencies = [
"anyhow",
"reqwest 0.13.4",
@@ -982,7 +982,7 @@ dependencies = [
[[package]]
name = "codewhale-secrets"
version = "0.9.11"
version = "0.9.12"
dependencies = [
"chrono",
"codewhale-paths",
@@ -997,7 +997,7 @@ dependencies = [
[[package]]
name = "codewhale-state"
version = "0.9.11"
version = "0.9.12"
dependencies = [
"anyhow",
"chrono",
@@ -1012,7 +1012,7 @@ dependencies = [
[[package]]
name = "codewhale-telemetry"
version = "0.9.11"
version = "0.9.12"
dependencies = [
"anyhow",
"chrono",
@@ -1032,7 +1032,7 @@ dependencies = [
[[package]]
name = "codewhale-tools"
version = "0.9.11"
version = "0.9.12"
dependencies = [
"anyhow",
"async-trait",
@@ -1046,7 +1046,7 @@ dependencies = [
[[package]]
name = "codewhale-tui"
version = "0.9.11"
version = "0.9.12"
dependencies = [
"ahash",
"ansi-to-tui",
@@ -1107,7 +1107,6 @@ dependencies = [
"rust-i18n",
"rustls",
"schemars",
"schemaui",
"semver",
"serde",
"serde_json",
@@ -1143,7 +1142,7 @@ dependencies = [
[[package]]
name = "codewhale-workflow"
version = "0.9.11"
version = "0.9.12"
dependencies = [
"serde",
"serde_json",
@@ -1155,7 +1154,7 @@ dependencies = [
[[package]]
name = "codewhale-workflow-js"
version = "0.9.11"
version = "0.9.12"
dependencies = [
"async-trait",
"jsonschema",
@@ -2637,25 +2636,6 @@ dependencies = [
"quick-error",
]
[[package]]
name = "include_dir"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd"
dependencies = [
"include_dir_macros",
]
[[package]]
name = "include_dir_macros"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75"
dependencies = [
"proc-macro2",
"quote",
]
[[package]]
name = "indexmap"
version = "2.14.0"
@@ -2664,8 +2644,6 @@ checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
dependencies = [
"equivalent",
"hashbrown 0.17.1",
"serde",
"serde_core",
]
[[package]]
@@ -4748,29 +4726,6 @@ dependencies = [
"syn 3.0.3",
]
[[package]]
name = "schemaui"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfd31406aa5c4778587e7d7ed82bada4131fbf1e69d0212fda60d6a2c5f5b5e6"
dependencies = [
"anyhow",
"axum",
"crossterm",
"include_dir",
"indexmap",
"jsonschema",
"percent-encoding",
"ratatui",
"regex",
"serde",
"serde_json",
"sha2 0.11.0",
"tokio",
"toml 1.1.4+spec-1.1.0",
"unicode-width",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
+3 -4
View File
@@ -26,7 +26,7 @@ default-members = ["crates/cli"]
resolver = "2"
[workspace.package]
version = "0.9.11"
version = "0.9.12"
edition = "2024"
# Rust 1.88 stabilized `let_chains` in `if`/`while` conditions, which the
# codebase relies on extensively. Cargo enforces this so users on older
@@ -52,9 +52,8 @@ clap = { version = "4.5.54", features = ["derive"] }
clap_complete = "4.5"
dirs = "6.0.0"
encoding_rs = "0.8.35"
# Pinned to the jsonschema line schemaui 0.12 requires (^0.46) so the graph
# carries one jsonschema/jsonschema-regex/referencing/fancy-regex stack.
# Move both together when schemaui catches up (dependabot: keep in step).
# Sole user is crates/workflow-js (schemaui is gone); keep the graph on one
# jsonschema/jsonschema-regex/referencing/fancy-regex stack.
jsonschema = { version = "0.46", default-features = false }
reqwest = { version = "0.13.1", default-features = false, features = ["json", "rustls-no-provider", "socks"] }
# NOT "parallel": the Workflow VM stays single-threaded and bridges to the
+161 -190
View File
@@ -1,67 +1,71 @@
---
name: Codewhale
description: Quiet, dense, navy-and-blue design system for Codewhale — the terminal shell, the website, and the docs.
description: Tidal Folio — a paper sheet read under the sea. Ivory paper and a serif title voice above the waterline, the whale's navy and blue below; one palette owned by the TUI tokens.
colors:
# brand constants (brand/*.svg, shared with the TUI palette)
brand-black: "#000000"
brand-ink: "#070c1d"
brand-navy: "#0c1531"
brand-stage: "#142352"
brand-ivory: "#ffffff"
brand-ice: "#ddeef9"
brand-cobalt: "#1535b2"
brand-blue: "#6aa6dc"
brand-cyan: "#78bce8"
ombre-start: "#1535B2"
ombre-end: "#6AA6DC"
# surface tokens — crates/tui/src/palette/tokens.rs, exported to web/app/tokens.css (generated, never hand-edit)
bg: "#070c1d" # ink
chrome: "#0c1531" # navy
# the sheet — crates/tui/src/palette/tokens.rs, exported to web/app/tokens.css (generated, never hand-edit)
paper: "#f6f2e8" # WHALE_TEXT_BODY, Whale Ivory — the page above the waterline
paper-deep: "#e8eef8" # LIGHT_ELEVATED — the shallows; cards and code-adjacent plates
paper-card: "#fffdf8" # LIGHT_PANEL — a raised sheet on the paper
paper-edge: "#a9b8cf" # LIGHT_BORDER
ink: "#14213a" # LIGHT_TEXT_BODY
ink-soft: "#455168" # LIGHT_TEXT_SOFT
ink-mute: "#5b6780" # LIGHT_TEXT_MUTED
action: "#1535b2" # WHALE_COBALT — links and controls on paper
action-deep: "#142352" # WHALE_COMPOSER — primary button fill, hover for links
human: "#7a5500" # LIGHT_HUMAN — Signal Gold at AA on ivory
live: "#08766d" # LIGHT_LIVE
# the water — the same tokens, dark side
bg: "#070c1d" # WHALE_BG, the deep field and the footer seabed
chrome: "#0c1531" # WHALE_CHROME, terminal plates on either side of the waterline
panel: "#101c40"
composer: "#142352" # stage — the raised input plate
composer: "#142352"
elevated: "#1a2c63"
selection: "#1e3c8f"
border: "#2a3f72"
text-body: "#f6f2e8"
text-soft: "#b6c0d4"
text-muted: "#93a0b8"
action: "#6aa6dc" # sky — the ombre's light end
action-hover: "#8fc4f8"
cobalt: "#1535b2" # the ombre's dark end
action-on-dark: "#6aa6dc" # WHALE_ACTION, the sky end of the ombre
ice: "#ddeef9"
cyan: "#78bce8"
success: "#9bd66f"
warning: "#ff7a59"
error: "#ff86b2"
human: "#f6c453"
gold: "#f6c453" # WHALE_HUMAN, the one gold thread in the water
typography:
display:
fontFamily: "IBM Plex Sans Condensed, ui-sans-serif, system-ui, sans-serif"
fontSize: "clamp(2.25rem, 4.8vw, 4.25rem)"
fontWeight: 600
lineHeight: 1
letterSpacing: "-0.01em"
fontFamily: "Newsreader, Georgia, 'Times New Roman', serif"
fontSize: "clamp(2.75rem, 6.4vw, 5.75rem)"
fontWeight: 500
lineHeight: 0.98
letterSpacing: "-0.022em"
heading:
fontFamily: "IBM Plex Sans Condensed, ui-sans-serif, system-ui, sans-serif"
fontSize: "clamp(1.45rem, 2.6vw, 2.35rem)"
fontWeight: 600
lineHeight: 1.08
letterSpacing: "-0.01em"
fontFamily: "Newsreader, Georgia, 'Times New Roman', serif"
fontSize: "clamp(1.6rem, 2.9vw, 2.6rem)"
fontWeight: 500
lineHeight: 1.1
letterSpacing: "-0.022em"
subheading:
fontFamily: "IBM Plex Sans Condensed, ui-sans-serif, system-ui, sans-serif"
fontSize: "1.12rem"
fontWeight: 500
fontWeight: 600
lineHeight: 1.25
body:
fontFamily: "IBM Plex Sans, ui-sans-serif, system-ui, sans-serif"
fontSize: "1rem"
fontWeight: 400
lineHeight: 1.6
meta:
rubric:
fontFamily: "JetBrains Mono, ui-monospace, monospace"
fontSize: "0.75rem"
fontWeight: 500
letterSpacing: "0.08em"
letterSpacing: "0.12em"
code:
fontFamily: "JetBrains Mono, ui-monospace, monospace"
fontSize: "0.85rem"
@@ -69,8 +73,9 @@ typography:
lineHeight: 1.55
rounded:
none: "0px"
sm: "4px"
sm: "5px"
md: "6px"
plate: "8px"
pill: "999px"
spacing:
xs: "4px"
@@ -78,203 +83,169 @@ spacing:
md: "16px"
lg: "24px"
xl: "40px"
section: "clamp(3rem, 7vw, 6rem)"
section: "clamp(3.5rem, 7vw, 6rem)"
components:
button-primary:
backgroundColor: "{colors.action}"
textColor: "{colors.bg}"
backgroundColor: "{colors.action-deep}"
textColor: "{colors.paper}"
rounded: "{rounded.sm}"
padding: "10px 16px"
typography: "{typography.meta}"
button-primary-hover:
backgroundColor: "{colors.action-hover}"
textColor: "{colors.bg}"
padding: "12px 22px"
typography: "{typography.body}"
button-secondary:
backgroundColor: "{colors.panel}"
textColor: "{colors.text-body}"
backgroundColor: "transparent"
textColor: "{colors.action-deep}"
border: "1px solid {colors.action-deep}"
rounded: "{rounded.sm}"
padding: "10px 16px"
card:
backgroundColor: "{colors.panel}"
textColor: "{colors.text-body}"
rounded: "{rounded.md}"
padding: "16px 18px"
nav:
terminal-plate:
backgroundColor: "{colors.bg}"
textColor: "{colors.text-soft}"
border: "1px solid rgb(221 238 249 / 0.22)"
rounded: "{rounded.plate}"
shadow: "0 30px 60px -24px rgb(7 12 29 / 0.75)"
nav:
backgroundColor: "rgb(246 242 232 / 0.94)"
textColor: "{colors.ink}"
height: "62px"
---
## Overview
Codewhale is an open-source Rust coding agent and TUI. Everything it shows —
the shell, the website, the docs — is built to feel like one tool: a flat navy
field, one blue action colour, white type, thin hairlines, and nothing
decorative. The direction in one line: **it doesn't need to look special — it
needs to look like Codewhale.** Density over drama, facts over claims,
documentation-grade restraint on every surface including the landing page and
the launch hero.
The website is a folio read under the sea. The top of every page is paper —
Whale Ivory, the very ink the terminal paints on its dark stage — set with a
large serif title voice and thin navy rules. Every page descends: the
homepage through a waterline into the whale's navy for the surfaces, the
composer, and the community; every other page through a shorter waterline
into the footer, which is the seabed. The terminal capture floats at the
waterline like a lantern. The direction in one line, from the founder:
**"like it's a scroll we're reading under the sea."** Serious product,
manuscript character, the product's own ocean ombre.
This file is shared design context for every 0.9.12 lane (web, docs, TUI). The
web-specific product truth is in `PRODUCT.md` next to it.
This replaces the all-dark "Tideline stage" website of 2026-09-01. The TUI
and the signed-in app keep their dense dark workbench; the website is the
paper the product is read from. The web-specific product truth is in
`PRODUCT.md` next to this file.
## Anti-slop rules
These are hard rules, not taste notes. A surface that breaks one is wrong even
if it looks fine in a screenshot.
Hard rules, not taste notes.
1. **No gradients for decoration.** The ombre `#1535B2 → #6AA6DC` lives in the
mark and the wordmark only. No gradient backgrounds, gradient text,
gradient rules, radial "spotlight" glows, or gradient borders anywhere else.
2. **No glassmorphism.** No `backdrop-filter`, no translucent frosted panels,
no blur. Surfaces are opaque tokens from the palette.
3. **No generic SaaS hero.** No three-column feature grid with icons, no logo
wall, no testimonials, no floating product mock at an angle, no "trusted by",
no neon eyebrow line. The landing page states what the tool is, shows one
real screenshot, and gives the install command.
4. **No fabricated evidence.** No invented terminal transcripts, reasoning
traces, benchmarks, or screenshots of a UI that has not shipped. Pending
media is labelled pending.
5. **No shadow theatre.** No drop shadows on cards, buttons, or figures; depth is
one surface step and one hairline.
6. **No second accent.** One action blue. Cyan is bounded to the composer
prompt glyph; state colours carry state only; violet is 1px rules, never
text.
7. **No status-chip soup, no emoji, no all-caps headings.** Labels are
letterspaced mono at ≥ 0.7rem and there are few of them.
8. **No text below the floors** (12px functional, 11.2px labels) and no
text/background pair under 4.5:1.
1. **One drawn thing.** The water in `web/components/strata.tsx` is the only
illustration on the site: four translucent strata of the palette, softened
like an ink wash, with a few fine current lines. No photographs of water,
no stock imagery, no second illustration, no turbulence or grain filters.
The strata are geometry drawn from tokens — never a hex of their own.
2. **No gradients as decoration elsewhere.** The ombre `#1535B2 → #6AA6DC`
lives in the mark, the wordmark, and the water. The ocean column's field is
the TUI's own chrome → bg descent; no spotlight glows, gradient text, or
gradient rules on paper.
3. **One shadow.** The terminal plate at the waterline casts one soft, offset,
blue shadow. Nothing else on the site has a drop shadow.
4. **No generic SaaS scaffolding.** No icon-card grids, logo walls,
testimonials, hero metrics, or badge soup. Sections are ruled columns and
fact lists on paper.
5. **No fabricated evidence.** The one screenshot is the founder's own capture
of the v0.9.12 development build, captioned as exactly that. No invented
transcripts, benchmarks, or mockups; pending media stays `pending`.
6. **No cloud claims.** Availability is stated per surface as it is today —
terminal released, web app account sign-in available with the workbench a
development preview, desktop a development build, cloud computers not
available yet — and the page changes when the state does.
7. **Two dials, exact names.** Plan / Work / Operate and Ask / Auto-Review /
Full Access are typeset literally and never ranked; Full Access is a
choice, never described as a default.
8. **No text below the floors** (12px functional, 11.2px rubrics) and no
text/background pair under 4.5:1 on either side of the waterline.
## Colors
One palette, owned by `crates/tui/src/palette/tokens.rs`, exported to
`web/app/tokens.css` by `scripts/export-design-tokens.py` (regenerate, never
edit). The brand-name aliases used in conversation map onto it: black `#000`,
ink `#070c1d` = `bg`, navy `#0c1531` = `chrome`, stage `#142352` = `composer`,
ivory `#fff`, ice `#ddeef9`, cobalt `#1535b2`, blue/sky `#6aa6dc` = `action`,
cyan `#78bce8`. The TUI uses the same tokens through `palette::grammar` for
status colours. `web/app/globals.css` maps them to semantic names: `--paper`
(bg), `--paper-deep` (panel), `--ink` (text), `--ink-soft`, `--ink-mute`,
`--indigo` (action), `--hairline` (action at 20 % alpha).
One palette, owned by `crates/tui/src/palette/tokens.rs` and exported to
`web/app/tokens.css` by `scripts/export-design-tokens.py` — both the
`WHALE_*` dark tokens (`--whale-*`) and the Blue Stage light preset's
`LIGHT_*` tokens (`--light-*`). `web/app/globals.css` maps them to the site's
semantic names and never repeats a hex:
- **Field:** on the web, `bg` for the page and the transcript and `chrome`
for bars and the dock; in the TUI the ground is the terminal's own
background (`Color::Reset`), and the navy field is painted only under the
opt-in `ocean_treatment = "deepsea"` (alias `underwater`) column. `panel`
for cards and code, `composer` for the raised input plate, `elevated` only
for overlays. Never stack more than two surface steps in one view.
- **Type:** `text-body` for copy, `text-soft` for secondary, `text-muted` for
meta. `text-dim` (#697791) is for borders only — it fails AA on the panels.
- **Action:** one blue (`action`) for links, buttons, and focus rings; hover
lifts to `action-hover`. `cyan` is a bounded accent (eyebrow chrome, composer
prompt) — never body text, never fills.
- **Brand ombre** `#1535B2 → #6AA6DC` exists only in the mark and wordmark.
Do not paint UI with it.
- **State colours** (`success`, `warning`, `error`, `human`) carry meaning; do
not use them decoratively, and never convey state by colour alone.
Contrast: every text/background pair in use is ≥ 4.5:1 (`text-muted` on
`panel` 6.3:1, on `composer` 5.7:1, on `bg` 7.4:1; `action` on `bg` 7.5:1).
- **Above the waterline (`:root`)** — `--paper` is Whale Ivory
(`WHALE_TEXT_BODY`), `--ink` the light preset's navy, `--indigo` cobalt for
links and outlines, `--indigo-deep` brand navy for the primary fill and
hover, `--signal-gold` and `--jade` at their light-preset AA values.
- **Below the waterline** — one rule (`.ocean-column, .site-footer,
html[data-theme="dark"] .docs-theme`) re-inks the same names with the dark
whale tokens, so a component is written once and reads correctly on either
side. `--indigo` becomes the sky blue `WHALE_ACTION`; the mark becomes the
white silhouette.
- **Terminal plates** (`pre.code-block`, the screenshot frame, the install
composer) are always the terminal's own navy, on paper or in water.
- The brand ombre exists in the mark, the wordmark, and the water only.
- State colours (`success`, `warning`, `error`, `human`) carry meaning and
never convey state alone.
## Typography
Three faces, one job each:
Four faces, one job each:
- **IBM Plex Sans Condensed 600** — display and all headings (`--font-display`).
Also the wordmark: "codewhale" in Plex Sans Condensed SemiBold, outlined to
paths, letter-spacing −0.01em. Tight leading (1.0–1.08), no all-caps headings.
- **IBM Plex Sans 400/500/600** — body (`--font-body`). Measure ≤ 70ch.
- **JetBrains Mono 400/500** — code and the mono meta rows (`--font-mono`).
Letterspaced uppercase mono is the only "label" style.
- **Newsreader 400/500 (+ italic)** — the display voice: `h1`, `h2`, the
gain columns' titles, the chapter title on the water. Book weight, tracking
−0.022em, `text-wrap: balance`. Loaded through `next/font/google` as
`--font-serif`. Never used below 1.3rem.
- **IBM Plex Sans Condensed 500/600** — `h3`/`h4` and the small headings:
the product's own label face, `--font-display`.
- **IBM Plex Sans 400/500/600** — body, buttons, links (`--font-body`).
Measure ≤ 70ch.
- **JetBrains Mono 400/500** — code, the `cw` dot chain, the plate's rubric
(`AGENTIC COMPUTING, ON YOUR TERMS`), the running heads (`02 / YOUR MODELS`).
These rubrics are the only tracked uppercase on the site.
Floors: functional text (links, nav, labels, meta, footer) never below
**12px (0.75rem)**; letterspaced micro-labels never below **11.2px (0.7rem)**;
legal smallprint never below 10px. Heading outline is strict: h1 → h2 → h3, no
skipped levels; use CSS, not a lower heading tag, to make something smaller.
Han locales drop the tracking and set the serif slots in the CJK serif stack.
## Shell (TUI) direction
## Layout
The redesigned 0.9.12 shell is the reference the website mirrors. Top to
bottom (founder decisions, 2026-09-01/02 — see
`codewhale-ops/design/SHELL-DESIGN-20260901.md` for the dated record):
- One container (`--container: min(100% - 2rem, 76rem)`); every gutter aligns
with the nav.
- **The plate** (`.folio-hero`): two columns at ≥ 1050px — copy left, water
right; the terminal spans the left column's second row, the chapter marker
sits on the water bottom-right. Below 1050px it stacks: copy, terminal,
marker; the water still rises from the plate's floor.
- **Reading sections** (`.folio-section`): serif `h2` (max 24ch), an optional
lede (max 40rem), then either three ruled columns (`.folio-gain-grid`) or a
two-column chapter (`.folio-chapter-grid`) with a fact list on the right.
- **The waterline** (`.folio-waterline`, and `.site-footer-waterline` on every
other page): a band of the water, paper above, deep below.
- **The ocean column**: the surfaces list, the composer install band (bracketed
by Signal Gold and Operate violet, as in the TUI), community, footer.
- Breakpoints: 1050px (plate stacks), 760px (columns stack), 520px (compact
nav, full-width buttons). No horizontal overflow at 390px, ever.
1. **Transcript first.** The conversation owns the screen: no permanent side
chrome, no top bar. Turns are plain text on the terminal's own ground;
tool output is `text-soft` on `panel`; reasoning is the reasoning tint,
collapsed by default.
2. **Launch is a card, not a hero.** A thin top line (`⑂ branch path`);
a centred bordered card: the whale mark at left, `Codewhale` + version,
one announcement line only when it is true, then the menu **New
worktree · Resume session · Changelog · Quit** with real chords
right-aligned. Enter runs the highlighted entry; typing goes straight to
the composer; the card dissolves on the first keystroke or command.
3. **Composer.** The raised `composer` plate directly under the transcript,
cyan `❯` prompt glyph. While the card is up its bottom rule carries
`model (effort) · permission` — the route's one launch reading.
4. **Posture bar + metrics line** under the composer, once a session
exists: `▶▶ ask (Shift+Tab) · work (Tab) · 2 agents · Esc to interrupt …`
and `model · ctx NN% · $cost · ttft · tok/s · ↓ tokens … Ctrl+/ help`.
Context % lives in the metrics line only.
5. **Bottom views, not a dock.** One region under the composer cycles
through agents → tasks → background → files → notepad → context → git →
price (Ctrl+Tab forward, Ctrl+Shift+Tab back where the terminal
delivers it). Zero rows when idle with nothing to show. There is no
rail, no sidebar, no tab band.
## Motion
## Layout (web)
The page is complete and static. Motion answers a person's action: a hover
draws a rule, a press stamps the copy button, the compact sheet settles in.
The one ambient moment is the ocean column's 90-second breath, opacity only,
gated on `prefers-reduced-motion: no-preference`. No scroll-reveal.
- Single content column, `.product-container` max 72rem, 1rem side padding at
390px. The landing has no permanent side chrome; docs have a left contents
rail at ≥ 1050px that collapses into a top list below.
- Sections are separated by one hairline and generous vertical space
(`spacing.section`), not by background colour changes.
- Prose measure ≤ 70ch on docs; wide code blocks scroll horizontally inside
their panel rather than widening the column.
- Breakpoints in use: 640px (hero stacks, h1 drops to `clamp(2rem, 10vw, 3rem)`),
900px (hero two-column), 1050px (docs rail).
- No horizontal overflow at 390px, ever.
## Components
## Elevation & Depth
Flat. Depth is expressed by one surface step (`bg` → `panel`) and one hairline.
No drop shadows on cards or buttons; the only shadow is the raised composer
plate in the TUI screenshot itself. No blur, no glass, no glow.
## Shapes
Small radii: 4px on controls, 6px on cards and code panels, 999px only on the
GitHub-stars pill. No rounded-2xl, no circles as decoration. Whale mark is the
only curved form.
## Components (web)
- **Nav:** 62px bar, `bg`, hairline below. Left: whale mark (22px) + wordmark
(20px) as one link labelled "Codewhale home". Centre: text links in body
face. Right: theme, locale, stars pill, sign in / register (mono meta), one
filled Install button. Collapses to a menu button below 900px.
- **Buttons:** filled action blue (primary), panel with hairline (secondary),
text-only (ghost). Mono meta type, uppercase, 0.08em tracking. Visible focus
ring in `action` on every control.
- **Cards / steps:** `panel` bg, hairline border, 16–18px padding, number in
`action` mono, h3 in display face, body in `text-soft`.
- **Code blocks:** `panel` bg, JetBrains Mono 0.85rem, copy button top-right,
scroll-x inside.
- **Eyebrow:** mono, uppercase, 0.7rem, `cyan` or `text-muted`.
- **Footer:** `bg`, hairline top, inverted wordmark, column links at 0.75rem
mono; legal line at 0.7rem.
- **Media slots:** real assets only; a `pending` slot renders a labelled empty
panel, never a mock.
- **Nav**: paper at 94%, hairline below. Left: navy mark + navy wordmark as
one link. Centre: Product · Models · Pricing · Docs. Right: theme (docs
only), locale, stars, Sign in / Create account, one filled Install button.
The compact sheet adds Start · Install · FAQ · Community · Contribute.
- **Buttons**: `.folio-button` — brand-navy fill (primary) or navy outline
(secondary), body face, sentence case. Portal buttons on secondary pages
keep their mono meta style but use the same inks.
- **Fact lists** (`.folio-fact-list`, `.folio-availability-list`): hairline
rows, mono term on paper / serif term in the water, body description.
- **Terminal plate**: the screenshot at native 1136×698 with a chrome-navy
caption carrying the build line and the `cw` dot chain.
- **Footer**: the waterline band, then the seabed with the inverted wordmark.
## Do's and Don'ts
Do
- Derive every fact from the repo; one owner per number.
- Write a component once and let the below-the-waterline rule re-ink it.
- Keep the whale mark and wordmark together in the nav; wordmark alone in the footer.
- Use the display face for headings and the wordmark, nothing else.
- Meet AA and the 12px floor before shipping any new surface.
- Meet AA and the 12px floor on paper and in the water before shipping.
Don't
- Fabricate terminal output, reasoning traces, testimonials, or screenshots.
- Introduce a second accent colour, gradients on UI, shadows, or glass.
- Add a UI library or page-local copy; extend `lib/content/` and the dictionaries.
- Use `text-dim` for text, skip heading levels, or shrink type below the floors.
- Add a second illustration, a photograph, a shadow, or a gradient on paper.
- Claim cloud execution, a released desktop app, or a default of Full Access.
- Add page-local copy; extend `lib/content/` and the dictionaries.
- Restore the scroll-reveal or any per-section entrance motion.
+14 -8
View File
@@ -37,8 +37,12 @@ no hosted runtime to sell.
## Operating Context
- Install: `npm install -g codewhale` (Node 18+, no Rust toolchain); source
builds via cargo. Facts (version, provider count, tool count, license) are
- Install: official GitHub Releases first. New macOS/Linux installs use
`curl -fsSL https://codewhale.net/install.sh | sh`; Windows uses the matching
GitHub installer/archive. Existing direct installs use `codewhale update`.
npm and Cargo are secondary packaging routes; migration and PATH handling
follow `docs/INSTALL.md`. `latest` selects a published release, not the source
candidate. Facts (version, provider count, tool count, license) are
derived from the repository by `npm run prebuild` into
`web/lib/facts.generated.ts` and must never be hand-edited.
- Docs pages mirror `docs/*.md` in the repository; `npm run check:docs`
@@ -57,10 +61,12 @@ no hosted runtime to sell.
compatibility identifiers (GitHub org/repo, package scopes).
- Provider and model names are first-class and neutral; never rank providers
in copy.
- The 0.9.12 shell is not yet released: there are no real screenshots or
video of it. `web/lib/media-manifest.ts` marks such media `pending`; the
site must not ship mockups as screenshots. The one real screenshot on hand is
`web/public/codewhale-tui.webp` (0.9.11).
- The 0.9.12 shell is not yet released. `web/lib/media-manifest.ts` marks
session video `pending`; the site must not ship mockups as screenshots. The
one real screenshot on hand is `web/public/codewhale-tui.png` — the
founder's 2026-09-04 capture of the v0.9.12 development build (new session,
braille C-curl whale, Work mode, Full Access). It is captioned as a
development build, never as a release.
- `/context-window` does not exist on the current base; do not document it.
- Subagent role identifiers are those the code accepts (`general`, `explore`,
`planner`, `reviewer`, `implement`, `test`, `advisor`, `custom`); the older
@@ -85,9 +91,9 @@ no hosted runtime to sell.
## Evidence on Hand
- Real: GitHub stars (live), release version and changelog (generated),
provider/tool counts (generated), the 0.9.11 TUI screenshot.
provider/tool counts (generated), the v0.9.12 development-build screenshot.
- Absent, do not fabricate: testimonials, customer logos, benchmarks,
pricing, 0.9.12 TUI media.
pricing, session video, or media of a published 0.9.12 release.
## Product Principles
+7 -3
View File
@@ -1,4 +1,4 @@
<!-- source: README.md sha256:e421b94e9f7e -->
<!-- source: README.md sha256:dcbd5aa09403 -->
# Codewhale
Codewhale وكيل مفتوح المصدر للبرمجة عبر الطرفية، مبني بلغة Rust ويتطور علنًا بالتعاون مع الأشخاص الذين يستخدمونه.
@@ -14,11 +14,15 @@ Codewhale وكيل مفتوح المصدر للبرمجة عبر الطرفية
## التثبيت
للتثبيت الجديد على macOS أو Linux، استخدم الإصدار الرسمي من GitHub:
```bash
npm install -g codewhale
codewhale
curl -fsSL https://codewhale.net/install.sh | sh
"$HOME/.local/bin/codewhale"
```
على Windows، نزّل المثبّت أو الأرشيف المناسب من [GitHub Releases](https://github.com/Hmbown/CodeWhale/releases/latest). لتحديث تثبيت مباشر موجود، شغّل `codewhale update`، أو `codewhale update --check` للفحص فقط. يعرض المحدّث مسار الملف التنفيذي ويحتفظ بالبنيات الأحدث. npm وCargo خياران ثانويان؛ راجع [دليل التثبيت](docs/INSTALL.md) للانتقال من تثبيت يديره مدير حزم وإعداد PATH.
يساعدك Codewhale عند التشغيل الأول على الاتصال بموفّر أو البقاء دون اتصال. ويدعم أيضًا Cargo وDocker وNix وScoop والأرشيفات المبنية مسبقًا وAndroid/Termux ومرآة CNB. راجع [دليل التثبيت](docs/INSTALL.md).
يمكن تفعيل الإكمال بمفتاح Tab بأمر واحد لكل واجهة أوامر — `codewhale completion bash|zsh|fish|powershell|elvish`. راجع [إكمال واجهة الأوامر](docs/INSTALL.md#8-shell-completions).
+7 -3
View File
@@ -1,4 +1,4 @@
<!-- source: README.md sha256:e421b94e9f7e -->
<!-- source: README.md sha256:dcbd5aa09403 -->
# Codewhale
Codewhale és un agent de programació de codi obert per al terminal, desenvolupat amb Rust i millorat públicament amb les persones que l’utilitzen.
@@ -14,11 +14,15 @@ Codewhale és un agent de programació de codi obert per al terminal, desenvolup
## Instal·lació
Per a una instal·lació nova a macOS o Linux, fes servir la versió oficial de GitHub:
```bash
npm install -g codewhale
codewhale
curl -fsSL https://codewhale.net/install.sh | sh
"$HOME/.local/bin/codewhale"
```
A Windows, descarrega l’instal·lador o l’arxiu corresponent de [GitHub Releases](https://github.com/Hmbown/CodeWhale/releases/latest). Per actualitzar una instal·lació directa existent, executa `codewhale update`, o `codewhale update --check` només per comprovar-la. L’actualitzador mostra el camí de l’executable i conserva les compilacions més noves. npm i Cargo són opcions secundàries; consulta la [guia d’instal·lació](docs/INSTALL.md) per migrar una instal·lació gestionada per paquets i configurar PATH.
En la primera execució, Codewhale t’ajuda a connectar un proveïdor o a continuar sense connexió. També admet Cargo, Docker, Nix, Scoop, arxius precompilats, Android/Termux i un mirall CNB. Consulta la [guia d’instal·lació](docs/INSTALL.md).
L’autocompleció amb Tab s’activa amb una sola ordre per shell — `codewhale completion bash|zsh|fish|powershell|elvish`. Consulta [l’autocompleció del shell](docs/INSTALL.md#8-shell-completions).
+7 -3
View File
@@ -1,4 +1,4 @@
<!-- source: README.md sha256:e421b94e9f7e -->
<!-- source: README.md sha256:dcbd5aa09403 -->
# Codewhale
Codewhale ist ein in Rust entwickelter Open-Source-Coding-Agent für dein Terminal, der gemeinsam mit seinen Nutzerinnen und Nutzern öffentlich weiterentwickelt wird.
@@ -14,11 +14,15 @@ Codewhale ist ein in Rust entwickelter Open-Source-Coding-Agent für dein Termin
## Installation
Für eine neue Installation unter macOS oder Linux verwende die offizielle GitHub-Version:
```bash
npm install -g codewhale
codewhale
curl -fsSL https://codewhale.net/install.sh | sh
"$HOME/.local/bin/codewhale"
```
Unter Windows lade das passende Installationsprogramm oder Archiv von [GitHub Releases](https://github.com/Hmbown/CodeWhale/releases/latest) herunter. Bestehende direkte Installationen aktualisierst du mit `codewhale update`; `codewhale update --check` prüft nur. Der Updater zeigt den Pfad der ausführbaren Datei und behält neuere Builds bei. npm und Cargo sind nachrangige Paketoptionen. Hinweise zur Migration aus einer Paketverwaltung und zu PATH stehen in der [Installationsanleitung](docs/INSTALL.md).
Beim ersten Start hilft dir Codewhale, einen Anbieter zu verbinden oder offline zu bleiben. Außerdem werden Cargo, Docker, Nix, Scoop, vorgefertigte Archive, Android/Termux und ein CNB-Spiegel unterstützt. Siehe [Installationsanleitung](docs/INSTALL.md).
Die Tab-Vervollständigung lässt sich für jede Shell mit einem einzigen Befehl aktivieren — `codewhale completion bash|zsh|fish|powershell|elvish`. Siehe [Shell-Vervollständigung](docs/INSTALL.md#8-shell-completions).
+7 -3
View File
@@ -1,4 +1,4 @@
<!-- source: README.md sha256:e421b94e9f7e -->
<!-- source: README.md sha256:dcbd5aa09403 -->
# Codewhale
Codewhale es un agente de programación de código abierto para tu terminal, desarrollado en Rust y mejorado públicamente junto con las personas que lo usan.
@@ -14,11 +14,15 @@ Codewhale es un agente de programación de código abierto para tu terminal, des
## Instalación
Para una instalación nueva en macOS o Linux, usa la versión oficial de GitHub:
```bash
npm install -g codewhale
codewhale
curl -fsSL https://codewhale.net/install.sh | sh
"$HOME/.local/bin/codewhale"
```
En Windows, descarga el instalador o archivo correspondiente de [GitHub Releases](https://github.com/Hmbown/CodeWhale/releases/latest). Para actualizar una instalación directa existente, ejecuta `codewhale update`, o `codewhale update --check` para consultar sin instalar. El actualizador muestra la ruta del ejecutable y conserva las compilaciones más recientes. npm y Cargo son opciones secundarias; consulta la [guía de instalación](docs/INSTALL.md) para migrar desde un gestor de paquetes y configurar PATH.
La primera vez que se ejecuta, Codewhale te ayuda a conectar un proveedor o a seguir sin conexión. También admite Cargo, Docker, Nix, Scoop, archivos precompilados, Android/Termux y un espejo de CNB. Consulta la [guía de instalación](docs/INSTALL.md).
El completado con Tab se configura con un comando por shell — `codewhale completion bash|zsh|fish|powershell|elvish`. Consulta el [completado de shell](docs/INSTALL.md#8-shell-completions).
+7 -3
View File
@@ -1,4 +1,4 @@
<!-- source: README.md sha256:e421b94e9f7e -->
<!-- source: README.md sha256:dcbd5aa09403 -->
# Codewhale
Codewhale est un agent de programmation open source pour votre terminal, développé en Rust et amélioré publiquement avec les personnes qui l’utilisent.
@@ -14,11 +14,15 @@ Codewhale est un agent de programmation open source pour votre terminal, dévelo
## Installation
Pour une nouvelle installation sur macOS ou Linux, utilisez la version officielle de GitHub :
```bash
npm install -g codewhale
codewhale
curl -fsSL https://codewhale.net/install.sh | sh
"$HOME/.local/bin/codewhale"
```
Sur Windows, téléchargez l’installeur ou l’archive adaptés depuis [GitHub Releases](https://github.com/Hmbown/CodeWhale/releases/latest). Pour une installation directe existante, lancez `codewhale update`, ou `codewhale update --check` pour vérifier sans installer. L’outil affiche le chemin de l’exécutable et conserve les versions de développement plus récentes. npm et Cargo sont des options secondaires ; consultez le [guide d’installation](docs/INSTALL.md) pour migrer depuis un gestionnaire de paquets et configurer PATH.
Au premier lancement, Codewhale vous aide à connecter un fournisseur ou à rester hors ligne. Il prend également en charge Cargo, Docker, Nix, Scoop, les archives précompilées, Android/Termux et un miroir CNB. Consultez le [guide d’installation](docs/INSTALL.md).
L’autocomplétion avec Tab s’active avec une commande par shell — `codewhale completion bash|zsh|fish|powershell|elvish`. Consultez [l’autocomplétion du shell](docs/INSTALL.md#8-shell-completions).
+7 -3
View File
@@ -1,4 +1,4 @@
<!-- source: README.md sha256:e421b94e9f7e -->
<!-- source: README.md sha256:dcbd5aa09403 -->
# Codewhale
Codewhale आपके टर्मिनल के लिए Rust में बना एक ओपन सोर्स कोडिंग एजेंट है, जिसे इसके उपयोगकर्ताओं के साथ सार्वजनिक रूप से बेहतर बनाया जाता है।
@@ -14,11 +14,15 @@ Codewhale आपके टर्मिनल के लिए Rust में ब
## इंस्टॉल करें
macOS या Linux पर नए इंस्टॉलेशन के लिए आधिकारिक GitHub रिलीज़ इस्तेमाल करें:
```bash
npm install -g codewhale
codewhale
curl -fsSL https://codewhale.net/install.sh | sh
"$HOME/.local/bin/codewhale"
```
Windows पर [GitHub Releases](https://github.com/Hmbown/CodeWhale/releases/latest) से उपयुक्त इंस्टॉलर या आर्काइव डाउनलोड करें। मौजूदा सीधे इंस्टॉलेशन को अपडेट करने के लिए `codewhale update` चलाएँ; केवल जाँच के लिए `codewhale update --check` इस्तेमाल करें। अपडेटर executable का पथ दिखाता है और नए बिल्ड सुरक्षित रखता है। npm और Cargo वैकल्पिक पैकेजिंग तरीके हैं। पैकेज मैनेजर वाले इंस्टॉलेशन से माइग्रेशन और PATH के लिए [इंस्टॉलेशन गाइड](docs/INSTALL.md) देखें।
पहली बार चलाने पर Codewhale आपको किसी प्रोवाइडर से जुड़ने या ऑफ़लाइन बने रहने में मदद करता है। यह Cargo, Docker, Nix, Scoop, पहले से बने आर्काइव, Android/Termux और CNB मिरर का भी समर्थन करता है। [इंस्टॉलेशन गाइड](docs/INSTALL.md) देखें।
हर शेल में Tab completion के लिए केवल एक कमांड चाहिए — `codewhale completion bash|zsh|fish|powershell|elvish`। [शेल कंप्लीशन](docs/INSTALL.md#8-shell-completions) देखें।
+7 -3
View File
@@ -1,4 +1,4 @@
<!-- source: README.md sha256:e421b94e9f7e -->
<!-- source: README.md sha256:dcbd5aa09403 -->
# Codewhale
Codewhale adalah agen pemrograman sumber terbuka untuk terminal Anda, dibuat dengan Rust dan dikembangkan secara terbuka bersama orang-orang yang menggunakannya.
@@ -14,11 +14,15 @@ Codewhale adalah agen pemrograman sumber terbuka untuk terminal Anda, dibuat den
## Instalasi
Untuk instalasi baru di macOS atau Linux, gunakan rilis resmi GitHub:
```bash
npm install -g codewhale
codewhale
curl -fsSL https://codewhale.net/install.sh | sh
"$HOME/.local/bin/codewhale"
```
Di Windows, unduh installer atau arsip yang sesuai dari [GitHub Releases](https://github.com/Hmbown/CodeWhale/releases/latest). Untuk instalasi biner langsung yang sudah ada, jalankan `codewhale update`, atau `codewhale update --check` untuk memeriksa tanpa memasang. Updater menampilkan jalur executable dan mempertahankan build yang lebih baru. npm dan Cargo adalah pilihan sekunder; lihat [panduan instalasi](docs/INSTALL.md) untuk migrasi dari pengelola paket dan pengaturan PATH.
Saat pertama dijalankan, Codewhale membantu Anda menghubungkan penyedia atau tetap bekerja secara luring. Codewhale juga mendukung Cargo, Docker, Nix, Scoop, arsip siap pakai, Android/Termux, dan mirror CNB. Lihat [panduan instalasi](docs/INSTALL.md).
Penyelesaian Tab cukup diaktifkan dengan satu perintah per shell — `codewhale completion bash|zsh|fish|powershell|elvish`. Lihat [penyelesaian shell](docs/INSTALL.md#8-shell-completions).
+7 -3
View File
@@ -1,4 +1,4 @@
<!-- source: README.md sha256:e421b94e9f7e -->
<!-- source: README.md sha256:dcbd5aa09403 -->
# Codewhale
Codewhale è un agente di programmazione open source per il terminale, sviluppato in Rust e migliorato pubblicamente insieme alle persone che lo utilizzano.
@@ -14,11 +14,15 @@ Codewhale è un agente di programmazione open source per il terminale, sviluppat
## Installazione
Per una nuova installazione su macOS o Linux, usa la versione ufficiale di GitHub:
```bash
npm install -g codewhale
codewhale
curl -fsSL https://codewhale.net/install.sh | sh
"$HOME/.local/bin/codewhale"
```
Su Windows, scarica l’installer o l’archivio adatto da [GitHub Releases](https://github.com/Hmbown/CodeWhale/releases/latest). Per aggiornare un’installazione diretta esistente, esegui `codewhale update`, oppure `codewhale update --check` per la sola verifica. L’aggiornamento mostra il percorso dell’eseguibile e conserva le build più recenti. npm e Cargo sono opzioni secondarie; consulta la [guida all’installazione](docs/INSTALL.md) per migrare da un gestore di pacchetti e configurare PATH.
Al primo avvio, Codewhale ti aiuta a collegare un provider oppure a rimanere offline. Supporta inoltre Cargo, Docker, Nix, Scoop, archivi precompilati, Android/Termux e un mirror CNB. Consulta la [guida all’installazione](docs/INSTALL.md).
Il completamento con Tab si attiva con un solo comando per ogni shell — `codewhale completion bash|zsh|fish|powershell|elvish`. Consulta il [completamento della shell](docs/INSTALL.md#8-shell-completions).
+7 -3
View File
@@ -1,4 +1,4 @@
<!-- source: README.md sha256:e421b94e9f7e -->
<!-- source: README.md sha256:dcbd5aa09403 -->
# Codewhale
Codewhale は Rust で構築された、ターミナル向けのオープンソース・コーディングエージェントです。利用者とともに、公開の場で改善を続けています。
@@ -14,11 +14,15 @@ Codewhale は Rust で構築された、ターミナル向けのオープンソ
## インストール
macOS または Linux に新規インストールする場合は、公式 GitHub Release を使います。
```bash
npm install -g codewhale
codewhale
curl -fsSL https://codewhale.net/install.sh | sh
"$HOME/.local/bin/codewhale"
```
Windows では [GitHub Releases](https://github.com/Hmbown/CodeWhale/releases/latest) から対応するインストーラーまたはアーカイブを入手してください。既存の直接インストールは `codewhale update` で更新できます。確認だけなら `codewhale update --check` を使います。更新対象の実行ファイルのパスが表示され、より新しいビルドは保持されます。npm と Cargo は補助的なパッケージ導入方法です。パッケージ管理からの移行や PATH の設定は[インストールガイド](docs/INSTALL.md)を参照してください。
初回起動時にプロバイダーへの接続を案内しますが、オフラインのまま使うこともできます。Codewhale は Cargo、Docker、Nix、Scoop、ビルド済みアーカイブ、Android/Termux、CNB ミラーにも対応しています。詳しくは[インストールガイド](docs/INSTALL.md)をご覧ください。
各シェルの Tab 補完はコマンド一つで設定できます — `codewhale completion bash|zsh|fish|powershell|elvish`。詳しくは[シェル補完](docs/INSTALL.md#8-shell-completions)をご覧ください。
+7 -3
View File
@@ -1,4 +1,4 @@
<!-- source: README.md sha256:e421b94e9f7e -->
<!-- source: README.md sha256:dcbd5aa09403 -->
# Codewhale
Codewhale은 Rust로 만든 터미널용 오픈 소스 코딩 에이전트로, 사용자들과 함께 공개적으로 개선해 나갑니다.
@@ -14,11 +14,15 @@ Codewhale은 Rust로 만든 터미널용 오픈 소스 코딩 에이전트로,
## 설치
macOS 또는 Linux에 새로 설치할 때는 공식 GitHub 릴리스를 사용하세요.
```bash
npm install -g codewhale
codewhale
curl -fsSL https://codewhale.net/install.sh | sh
"$HOME/.local/bin/codewhale"
```
Windows에서는 [GitHub Releases](https://github.com/Hmbown/CodeWhale/releases/latest)에서 맞는 설치 프로그램이나 아카이브를 받으세요. 기존 직접 설치는 `codewhale update`로 업데이트하고, 확인만 하려면 `codewhale update --check`를 사용하세요. 업데이트 도구는 실행 파일 경로를 표시하며 더 최신인 빌드는 유지합니다. npm과 Cargo는 보조 패키지 설치 방법입니다. 패키지 관리자 설치에서 이전하거나 PATH를 설정하려면 [설치 안내서](docs/INSTALL.md)를 참조하세요.
처음 실행하면 공급자 연결 과정을 안내하며, 오프라인 상태로 계속 사용할 수도 있습니다. Codewhale은 Cargo, Docker, Nix, Scoop, 사전 빌드 아카이브, Android/Termux, CNB 미러도 지원합니다. [설치 안내서](docs/INSTALL.md)를 참조하세요.
각 셸에서 Tab 자동 완성은 명령 한 줄로 설정할 수 있습니다 — `codewhale completion bash|zsh|fish|powershell|elvish`. [셸 자동 완성](docs/INSTALL.md#8-shell-completions)을 참조하세요.
+13 -4
View File
@@ -22,14 +22,23 @@ improved in public with the people who use it.
## Install
macOS / Linux — install the official GitHub release:
```bash
npm install -g codewhale
codewhale
curl -fsSL https://codewhale.net/install.sh | sh
"$HOME/.local/bin/codewhale"
```
Windows: download the matching installer or archive from
[GitHub Releases](https://github.com/Hmbown/CodeWhale/releases/latest).
For an existing direct install, run `codewhale update` (or `codewhale update --check`
to inspect it). The updater prints the executable path and keeps newer builds.
The first run helps you connect a provider or stay offline. Codewhale also
supports Cargo, Docker, Nix, Scoop, prebuilt archives, Android/Termux, and a CNB
mirror. See [the installation guide](docs/INSTALL.md).
supports npm and Cargo as secondary packaging routes, plus Docker, Nix, Scoop,
Android/Termux, and an optional CNB mirror. Existing package-managed installs
receive migration instructions. See [installation and PATH help](docs/INSTALL.md).
Tab completion is one command per shell — `codewhale completion bash|zsh|fish|powershell|elvish`.
See [shell completions](docs/INSTALL.md#8-shell-completions).
+7 -3
View File
@@ -1,4 +1,4 @@
<!-- source: README.md sha256:e421b94e9f7e -->
<!-- source: README.md sha256:dcbd5aa09403 -->
# Codewhale
Codewhale to agent programistyczny o otwartym kodzie źródłowym do terminala, napisany w Rust i rozwijany publicznie wspólnie z osobami, które go używają.
@@ -14,11 +14,15 @@ Codewhale to agent programistyczny o otwartym kodzie źródłowym do terminala,
## Instalacja
Przy nowej instalacji na macOS lub Linuksie użyj oficjalnego wydania z GitHuba:
```bash
npm install -g codewhale
codewhale
curl -fsSL https://codewhale.net/install.sh | sh
"$HOME/.local/bin/codewhale"
```
Na Windows pobierz odpowiedni instalator lub archiwum z [GitHub Releases](https://github.com/Hmbown/CodeWhale/releases/latest). Istniejącą instalację bezpośrednią zaktualizujesz poleceniem `codewhale update`; `codewhale update --check` służy tylko do sprawdzenia. Aktualizator pokazuje ścieżkę pliku wykonywalnego i zachowuje nowsze kompilacje. npm i Cargo to opcje dodatkowe. Migrację z menedżera pakietów i konfigurację PATH opisuje [instrukcja instalacji](docs/INSTALL.md).
Przy pierwszym uruchomieniu Codewhale pomaga połączyć się z dostawcą lub pozostać w trybie offline. Obsługuje też Cargo, Docker, Nix, Scoop, gotowe archiwa, Android/Termux oraz serwer lustrzany CNB. Zobacz [instrukcję instalacji](docs/INSTALL.md).
Uzupełnianie klawiszem Tab można włączyć jednym poleceniem dla każdej powłoki — `codewhale completion bash|zsh|fish|powershell|elvish`. Zobacz [uzupełnianie powłoki](docs/INSTALL.md#8-shell-completions).
+7 -3
View File
@@ -1,4 +1,4 @@
<!-- source: README.md sha256:e421b94e9f7e -->
<!-- source: README.md sha256:dcbd5aa09403 -->
# Codewhale
Codewhale é um agente de programação de código aberto para o seu terminal, desenvolvido em Rust e aprimorado publicamente com as pessoas que o utilizam.
@@ -14,11 +14,15 @@ Codewhale é um agente de programação de código aberto para o seu terminal, d
## Instalação
Para uma nova instalação no macOS ou Linux, use a versão oficial do GitHub:
```bash
npm install -g codewhale
codewhale
curl -fsSL https://codewhale.net/install.sh | sh
"$HOME/.local/bin/codewhale"
```
No Windows, baixe o instalador ou arquivo correspondente em [GitHub Releases](https://github.com/Hmbown/CodeWhale/releases/latest). Para atualizar uma instalação direta existente, execute `codewhale update`, ou `codewhale update --check` apenas para verificar. O atualizador mostra o caminho do executável e preserva builds mais recentes. npm e Cargo são opções secundárias; consulte o [guia de instalação](docs/INSTALL.md) para migrar de um gerenciador de pacotes e configurar PATH.
Na primeira execução, o Codewhale ajuda você a conectar um provedor ou a continuar offline. Ele também oferece suporte a Cargo, Docker, Nix, Scoop, arquivos pré-compilados, Android/Termux e um espelho CNB. Consulte o [guia de instalação](docs/INSTALL.md).
O preenchimento automático com Tab é ativado com um comando por shell — `codewhale completion bash|zsh|fish|powershell|elvish`. Consulte o [preenchimento automático do shell](docs/INSTALL.md#8-shell-completions).
+7 -3
View File
@@ -1,4 +1,4 @@
<!-- source: README.md sha256:e421b94e9f7e -->
<!-- source: README.md sha256:dcbd5aa09403 -->
# Codewhale
Codewhale — это агент для программирования с открытым исходным кодом, работающий в терминале. Он написан на Rust и открыто развивается вместе со своими пользователями.
@@ -14,11 +14,15 @@ Codewhale — это агент для программирования с от
## Установка
Для новой установки на macOS или Linux используйте официальный выпуск с GitHub:
```bash
npm install -g codewhale
codewhale
curl -fsSL https://codewhale.net/install.sh | sh
"$HOME/.local/bin/codewhale"
```
В Windows скачайте подходящий установщик или архив из [GitHub Releases](https://github.com/Hmbown/CodeWhale/releases/latest). Для обновления существующей прямой установки запустите `codewhale update`; для проверки без установки — `codewhale update --check`. Обновление показывает путь к исполняемому файлу и сохраняет более новые сборки. npm и Cargo — дополнительные способы установки. Переход с менеджера пакетов и настройка PATH описаны в [руководстве по установке](docs/INSTALL.md).
При первом запуске Codewhale поможет подключить провайдера или остаться в автономном режиме. Он также поддерживает Cargo, Docker, Nix, Scoop, готовые архивы, Android/Termux и зеркало CNB. См. [руководство по установке](docs/INSTALL.md).
Для автодополнения по Tab достаточно одной команды для каждой оболочки — `codewhale completion bash|zsh|fish|powershell|elvish`. См. [автодополнение оболочки](docs/INSTALL.md#8-shell-completions).
+7 -3
View File
@@ -1,4 +1,4 @@
<!-- source: README.md sha256:e421b94e9f7e -->
<!-- source: README.md sha256:dcbd5aa09403 -->
# Codewhale
Codewhale, terminaliniz için Rust ile geliştirilmiş ve kullanıcılarıyla birlikte açık biçimde iyileştirilen açık kaynaklı bir kodlama ajanıdır.
@@ -14,11 +14,15 @@ Codewhale, terminaliniz için Rust ile geliştirilmiş ve kullanıcılarıyla bi
## Kurulum
macOS veya Linux üzerinde yeni kurulum için resmî GitHub sürümünü kullanın:
```bash
npm install -g codewhale
codewhale
curl -fsSL https://codewhale.net/install.sh | sh
"$HOME/.local/bin/codewhale"
```
Windows’ta [GitHub Releases](https://github.com/Hmbown/CodeWhale/releases/latest) üzerinden uygun yükleyiciyi veya arşivi indirin. Mevcut doğrudan kurulumu güncellemek için `codewhale update`, yalnızca kontrol etmek için `codewhale update --check` çalıştırın. Güncelleyici çalıştırılabilir dosyanın yolunu gösterir ve daha yeni derlemeleri korur. npm ve Cargo ikincil paketleme seçenekleridir. Paket yöneticisinden geçiş ve PATH ayarları için [kurulum kılavuzuna](docs/INSTALL.md) bakın.
Codewhale ilk çalıştırmada bir sağlayıcıya bağlanmanıza veya çevrimdışı kalmanıza yardımcı olur. Cargo, Docker, Nix, Scoop, önceden derlenmiş arşivler, Android/Termux ve CNB aynasını da destekler. [Kurulum kılavuzuna](docs/INSTALL.md) bakın.
Her kabukta Tab tamamlama tek bir komutla etkinleştirilir — `codewhale completion bash|zsh|fish|powershell|elvish`. [Kabuk tamamlamalarına](docs/INSTALL.md#8-shell-completions) bakın.
+7 -3
View File
@@ -1,4 +1,4 @@
<!-- source: README.md sha256:e421b94e9f7e -->
<!-- source: README.md sha256:dcbd5aa09403 -->
# Codewhale
Codewhale — це агент програмування з відкритим кодом для вашого термінала, створений на Rust і вдосконалюваний публічно разом із людьми, які ним користуються.
@@ -14,11 +14,15 @@ Codewhale — це агент програмування з відкритим
## Встановлення
Для нового встановлення на macOS або Linux використовуйте офіційний випуск із GitHub:
```bash
npm install -g codewhale
codewhale
curl -fsSL https://codewhale.net/install.sh | sh
"$HOME/.local/bin/codewhale"
```
У Windows завантажте відповідний інсталятор або архів із [GitHub Releases](https://github.com/Hmbown/CodeWhale/releases/latest). Для оновлення наявного прямого встановлення запустіть `codewhale update`; для перевірки без встановлення — `codewhale update --check`. Оновлювач показує шлях до виконуваного файлу та зберігає новіші збірки. npm і Cargo — додаткові способи встановлення. Перехід із менеджера пакетів і налаштування PATH описано в [посібнику зі встановлення](docs/INSTALL.md).
Під час першого запуску Codewhale допоможе під’єднати провайдера або залишитися в автономному режимі. Він також підтримує Cargo, Docker, Nix, Scoop, готові архіви, Android/Termux і дзеркало CNB. Див. [посібник зі встановлення](docs/INSTALL.md).
Для автодоповнення за Tab достатньо однієї команди для кожної оболонки — `codewhale completion bash|zsh|fish|powershell|elvish`. Див. [автодоповнення оболонки](docs/INSTALL.md#8-shell-completions).
+7 -3
View File
@@ -1,4 +1,4 @@
<!-- source: README.md sha256:e421b94e9f7e -->
<!-- source: README.md sha256:dcbd5aa09403 -->
# Codewhale
Codewhale là tác nhân lập trình mã nguồn mở dành cho terminal, được xây dựng bằng Rust và được cải thiện công khai cùng những người sử dụng nó.
@@ -14,11 +14,15 @@ Codewhale là tác nhân lập trình mã nguồn mở dành cho terminal, đư
## Cài đặt
Để cài mới trên macOS hoặc Linux, hãy dùng bản phát hành chính thức từ GitHub:
```bash
npm install -g codewhale
codewhale
curl -fsSL https://codewhale.net/install.sh | sh
"$HOME/.local/bin/codewhale"
```
Trên Windows, tải bộ cài hoặc gói lưu trữ phù hợp từ [GitHub Releases](https://github.com/Hmbown/CodeWhale/releases/latest). Với bản cài trực tiếp đã có, chạy `codewhale update`; dùng `codewhale update --check` nếu chỉ muốn kiểm tra. Trình cập nhật hiển thị đường dẫn tệp thực thi và giữ lại các bản dựng mới hơn. npm và Cargo là lựa chọn phụ; xem [hướng dẫn cài đặt](docs/INSTALL.md) để chuyển từ trình quản lý gói và thiết lập PATH.
Trong lần chạy đầu tiên, Codewhale sẽ giúp bạn kết nối với nhà cung cấp hoặc tiếp tục làm việc ngoại tuyến. Codewhale cũng hỗ trợ Cargo, Docker, Nix, Scoop, các gói dựng sẵn, Android/Termux và bản sao CNB. Xem [hướng dẫn cài đặt](docs/INSTALL.md).
Mỗi shell chỉ cần một lệnh để bật tính năng hoàn thành bằng phím Tab — `codewhale completion bash|zsh|fish|powershell|elvish`. Xem [tính năng hoàn thành của shell](docs/INSTALL.md#8-shell-completions).
+10 -3
View File
@@ -1,4 +1,4 @@
<!-- source: README.md sha256:e421b94e9f7e -->
<!-- source: README.md sha256:dcbd5aa09403 -->
# Codewhale
Codewhale 是一款面向终端的开源编程智能体,使用 Rust 构建,并与用户一起在公开协作中不断改进。
@@ -14,11 +14,18 @@ Codewhale 是一款面向终端的开源编程智能体,使用 Rust 构建,
## 安装
macOS / Linux:推荐安装官方 GitHub Release。
```bash
npm install -g codewhale
codewhale
curl -fsSL https://codewhale.net/install.sh | sh
"$HOME/.local/bin/codewhale"
```
Windows 请使用 [GitHub Releases](https://github.com/Hmbown/CodeWhale/releases/latest)
中的安装器或压缩包。已有的直接安装使用 `codewhale update`;它会显示当前可执行文件路径,
并保留比已发布版本更新的构建。npm 和 Cargo 是次要打包选项。
迁移与 PATH 排查见[安装指南](docs/zh_hans/INSTALL.md)。
首次运行会帮助你连接提供商,也可以选择保持离线。Codewhale 还支持 Cargo、Docker、Nix、Scoop、预构建压缩包、Android/Termux 和 CNB 镜像。请参阅[安装指南](docs/INSTALL.md)。
每种 shell 只需一条命令即可启用 Tab 补全——`codewhale completion bash|zsh|fish|powershell|elvish`。请参阅 [shell 补全](docs/INSTALL.md#8-shell-completions)。
+7 -3
View File
@@ -1,4 +1,4 @@
<!-- source: README.md sha256:e421b94e9f7e -->
<!-- source: README.md sha256:dcbd5aa09403 -->
# Codewhale
Codewhale 是一款在終端機中使用的開源程式設計代理,以 Rust 打造,並與使用者一起透過公開協作持續改進。
@@ -14,11 +14,15 @@ Codewhale 是一款在終端機中使用的開源程式設計代理,以 Rust
## 安裝
在 macOS 或 Linux 上首次安裝時,請使用官方 GitHub Release:
```bash
npm install -g codewhale
codewhale
curl -fsSL https://codewhale.net/install.sh | sh
"$HOME/.local/bin/codewhale"
```
Windows 請從 [GitHub Releases](https://github.com/Hmbown/CodeWhale/releases/latest) 下載對應的安裝程式或封存檔。已有的直接安裝使用 `codewhale update`;若只想檢查,使用 `codewhale update --check`。更新器會顯示執行檔路徑,並保留較新的建置版本。npm 和 Cargo 是次要套件安裝方式;套件管理器安裝的遷移與 PATH 設定請參閱[安裝指南](docs/INSTALL.md)。
第一次執行時,系統會協助你連線至供應商,也可以選擇保持離線。Codewhale 亦支援 Cargo、Docker、Nix、Scoop、預先建置的封存檔、Android/Termux 與 CNB 映像。請參閱[安裝指南](docs/INSTALL.md)。
每種 shell 只需一個指令即可啟用 Tab 自動完成——`codewhale completion bash|zsh|fish|powershell|elvish`。請參閱 [shell 自動完成](docs/INSTALL.md#8-shell-completions)。
Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 KiB

After

Width:  |  Height:  |  Size: 35 KiB

+20
View File
@@ -196,6 +196,16 @@ memory_path = "~/.codewhale/memory.md"
[speech]
# output_dir = "./speech"
# ─────────────────────────────────────────────────────────────────────────────────
# Reasoning-only recovery
# ─────────────────────────────────────────────────────────────────────────────────
# When a reasoning model returns only hidden thinking without any answer text
# or tool call, the engine can re-request the answer automatically.
# Set max_reprompts = 0 to disable automatic recovery entirely.
# [reasoning_only]
# max_reprompts = 2
# reprompt_message = "So, what's up ? Keep running !"
# Native tool catalog controls (#2076). By default only the core tool surface
# is loaded into the model context; less common native tools are discoverable
# through ToolSearch and loaded on first use.
@@ -822,6 +832,16 @@ max_subagents = 10 # optional (default 64, clamped to 1-128)
# base_url = "https://api.mistral.ai/v1"
# model = "mistral-code-latest" # or mistral-medium-latest, mistral-small-latest, mistral-large-latest
# Google Gemini — Google AI Studio (https://aistudio.google.com/apikey)
# OpenAI-compatible Chat Completions route on the official Gemini endpoint;
# this is the supported Gemini path (see docs/PROVIDERS.md).
# Provider aliases: google, gemini, google-gemini, ai-studio
# Env var aliases: GEMINI_API_KEY, GOOGLE_API_KEY, GEMINI_BASE_URL, GOOGLE_BASE_URL
[providers.google]
# api_key = "YOUR_GEMINI_API_KEY"
# base_url = "https://generativelanguage.googleapis.com/v1beta/openai/"
# model = "gemini-3.1-pro-preview"
# ─────────────────────────────────────────────────────────────────────────────────
# Alibaba Cloud Model Studio — Token Plan
# (https://bailian.console.aliyun.com/)
+1 -1
View File
@@ -11,5 +11,5 @@ description = "Model/provider registry and fallback strategy for Codewhale"
workspace = true
[dependencies]
codewhale-config = { path = "../config", version = "0.9.11" }
codewhale-config = { path = "../config", version = "0.9.12" }
serde.workspace = true
+11 -11
View File
@@ -15,17 +15,17 @@ workspace = true
[dependencies]
anyhow.workspace = true
axum.workspace = true
codewhale-agent = { path = "../agent", version = "0.9.11" }
codewhale-config = { path = "../config", version = "0.9.11" }
codewhale-core = { path = "../core", version = "0.9.11" }
codewhale-execpolicy = { path = "../execpolicy", version = "0.9.11" }
codewhale-hooks = { path = "../hooks", version = "0.9.11" }
codewhale-mcp = { path = "../mcp", version = "0.9.11" }
codewhale-paths = { path = "../paths", version = "0.9.11" }
codewhale-protocol = { path = "../protocol", version = "0.9.11" }
codewhale-release = { path = "../release", version = "0.9.11" }
codewhale-state = { path = "../state", version = "0.9.11" }
codewhale-tools = { path = "../tools", version = "0.9.11" }
codewhale-agent = { path = "../agent", version = "0.9.12" }
codewhale-config = { path = "../config", version = "0.9.12" }
codewhale-core = { path = "../core", version = "0.9.12" }
codewhale-execpolicy = { path = "../execpolicy", version = "0.9.12" }
codewhale-hooks = { path = "../hooks", version = "0.9.12" }
codewhale-mcp = { path = "../mcp", version = "0.9.12" }
codewhale-paths = { path = "../paths", version = "0.9.12" }
codewhale-protocol = { path = "../protocol", version = "0.9.12" }
codewhale-release = { path = "../release", version = "0.9.12" }
codewhale-state = { path = "../state", version = "0.9.12" }
codewhale-tools = { path = "../tools", version = "0.9.12" }
serde.workspace = true
serde_json.workspace = true
rustls.workspace = true
+14 -14
View File
@@ -18,19 +18,19 @@ path = "src/main.rs"
anyhow.workspace = true
clap.workspace = true
clap_complete.workspace = true
codewhale-tui = { path = "../tui", version = "0.9.11" }
codewhale-agent = { path = "../agent", version = "0.9.11" }
codewhale-app-server = { path = "../app-server", version = "0.9.11" }
codewhale-config = { path = "../config", version = "0.9.11" }
codewhale-lane = { path = "../lane", version = "0.9.11" }
codewhale-workflow = { path = "../workflow", version = "0.9.11" }
codewhale-execpolicy = { path = "../execpolicy", version = "0.9.11" }
codewhale-mcp = { path = "../mcp", version = "0.9.11" }
codewhale-paths = { path = "../paths", version = "0.9.11" }
codewhale-release = { path = "../release", version = "0.9.11" }
codewhale-secrets = { path = "../secrets", version = "0.9.11" }
codewhale-state = { path = "../state", version = "0.9.11" }
codewhale-telemetry = { path = "../telemetry", version = "0.9.11" }
codewhale-tui = { path = "../tui", version = "0.9.12" }
codewhale-agent = { path = "../agent", version = "0.9.12" }
codewhale-app-server = { path = "../app-server", version = "0.9.12" }
codewhale-config = { path = "../config", version = "0.9.12" }
codewhale-lane = { path = "../lane", version = "0.9.12" }
codewhale-workflow = { path = "../workflow", version = "0.9.12" }
codewhale-execpolicy = { path = "../execpolicy", version = "0.9.12" }
codewhale-mcp = { path = "../mcp", version = "0.9.12" }
codewhale-paths = { path = "../paths", version = "0.9.12" }
codewhale-release = { path = "../release", version = "0.9.12" }
codewhale-secrets = { path = "../secrets", version = "0.9.12" }
codewhale-state = { path = "../state", version = "0.9.12" }
codewhale-telemetry = { path = "../telemetry", version = "0.9.12" }
chrono.workspace = true
console = "0.16.3"
dirs.workspace = true
@@ -49,7 +49,7 @@ webbrowser = "1.0"
zeroize = "1.8.2"
[build-dependencies]
codewhale-build-support = { path = "../build-support", version = "0.9.11" }
codewhale-build-support = { path = "../build-support", version = "0.9.12" }
# Parent-death cleanup for delegated server children (#3259): on Linux the
# dispatcher sets PR_SET_PDEATHSIG so the child is signalled if the dispatcher
+890 -39
View File
File diff suppressed because it is too large. Load diff
+386 -289
View File
@@ -14,6 +14,7 @@ use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use anyhow::{Context, Result, anyhow, bail};
use codewhale_release::install::GITHUB_MIGRATION_HELP;
use codewhale_release::{
CHECKSUM_MANIFEST_ASSET, InstallMethod, ReleaseChannel, ReleaseQuery, UPDATE_USER_AGENT,
cnb_mirror_override_active, cnb_mirror_supports_target, cnb_release_base_url,
@@ -23,8 +24,6 @@ use codewhale_release::{
use reqwest::Proxy;
use std::io::Write;
use std::sync::Arc;
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
const GITHUB_LATEST_RELEASE_PAGE_URL: &str = "https://github.com/Hmbown/CodeWhale/releases/latest";
@@ -37,9 +36,9 @@ const UPDATE_HTTP_RETRY_DELAY_MS: u64 = 100;
const UPDATE_DOWNLOAD_TIMEOUT: Duration = Duration::from_secs(5 * 60);
/// Ceiling for one checksum-manifest probe. The manifest is a few hundred
/// bytes, so this is only a backstop against a source that accepts the
/// connection and then stalls — the winner is whichever probe *returns* first,
/// never whichever one outlasts a timeout.
const MANIFEST_PROBE_TIMEOUT: Duration = Duration::from_secs(20);
/// connection and then stalls. GitHub gets the first attempt; an unavailable
/// manifest falls back to the supported mirror without waiting for a binary.
const MANIFEST_PROBE_TIMEOUT: Duration = Duration::from_secs(10);
#[cfg(target_os = "android")]
const ANDROID_PROC_SELF_MAPS: &str = "/proc/self/maps";
@@ -49,6 +48,8 @@ const ANDROID_PROC_SELF_MAPS: &str = "/proc/self/maps";
pub fn run_update(beta: bool, check_only: bool, proxy_arg: Option<String>) -> Result<()> {
let executable_identity = update_executable_identity()?;
let current_exe = executable_identity.path.clone();
let install_method = InstallMethod::detect(&current_exe);
let protected_location = protected_update_path(&current_exe);
let legacy_binary = is_legacy_binary(&current_exe);
ensure_supported_release_target(std::env::consts::OS, std::env::consts::ARCH)?;
@@ -67,9 +68,20 @@ pub fn run_update(beta: bool, check_only: bool, proxy_arg: Option<String>) -> Re
println!();
println!("{}", legacy_binary_message(&current_exe));
}
if let Some(warning) = managed_install_warning(InstallMethod::detect(&current_exe)) {
if let Some(warning) = managed_install_warning(install_method) {
println!();
println!("{warning}");
if !check_only {
bail!("The package-managed executable was not changed.");
}
}
if protected_location {
println!(
"System/package directory: in-place self-update is disabled.\n\n{GITHUB_MIGRATION_HELP}"
);
if !check_only {
bail!("The system/package executable was not changed.");
}
}
if check_only {
@@ -78,7 +90,19 @@ pub fn run_update(beta: bool, check_only: bool, proxy_arg: Option<String>) -> Re
let latest_tag = &fetched.release.tag_name;
println!("Latest {} release: {latest_tag}", channel.label());
if update_is_needed(channel, current_version, latest_tag)? {
println!("Update available. Run `codewhale update` to install {latest_tag}.");
if install_method.supports_self_update() && !protected_location {
println!(
"Update available. Run `{} update` to install {latest_tag}.",
current_exe.display()
);
} else if !install_method.supports_self_update() {
println!(
"Update available. Use the GitHub installation instructions above, or `{}` for this package-managed copy.",
install_method.update_command()
);
} else {
println!("Update available. Use the GitHub installation instructions above.");
}
println!(
"Release source: {}",
describe_release_source_for_check(&fetched, &plan.asset_stem, proxy.as_ref())
@@ -103,21 +127,30 @@ pub fn run_update(beta: bool, check_only: bool, proxy_arg: Option<String>) -> Re
let latest_tag = &release.tag_name;
println!("Latest {} release: {latest_tag}", channel.label());
if fetched.source.is_pinned_mirror() {
if channel == ReleaseChannel::Beta {
if fetched.source.is_pinned_mirror() && channel == ReleaseChannel::Beta {
println!(
"Using {}; --beta does not select GitHub beta releases in mirror mode.",
fetched.source.describe()
);
}
if !update_is_needed(channel, current_version, latest_tag)? {
if compare_release_versions(current_version, latest_tag)? == Ordering::Greater {
println!(
"Using {}; --beta does not select GitHub beta releases in mirror mode.",
fetched.source.describe()
"Current build is newer than the latest published release; keeping v{current_version}. No downgrade or download performed."
);
} else {
println!("Already up to date; no download needed.");
}
} else if !update_is_needed(channel, current_version, latest_tag)? {
println!("Already up to date; no download needed.");
return Ok(());
}
// Step 2: Lock the checksum manifest and the binary to a single source. On
// targets the CNB mirror publishes, this races the two tiny manifests so a
// GitHub-blocked network never has to wait out a stalled asset download.
// Reject unrelated command paths before downloads or any sibling changes.
for target in &plan.target_paths {
validate_update_target(target, &executable_identity)?;
}
// Step 2: Prefer GitHub, then a supported mirror if its manifest is
// unavailable. Keep the manifest and binary locked to the same source.
let download = resolve_download_plan(&fetched, &plan.asset_stem, proxy.as_ref())?;
println!("Release source: {}", download.source.describe());
@@ -146,8 +179,9 @@ pub fn run_update(beta: bool, check_only: bool, proxy_arg: Option<String>) -> Re
// Step 4: Replace command paths only after the download and the running
// executable identity verify. The preflight happens before a colocated
// compatibility path can change, then the identity is checked just in time.
replace_verified_downloads(&plan.target_paths, &bytes, || {
validate_primary_update_identity(&executable_identity)
replace_verified_downloads(&plan.target_paths, &bytes, |target| {
validate_primary_update_identity(&executable_identity)?;
validate_update_target(target, &executable_identity)
})?;
println!(
@@ -194,22 +228,15 @@ fn verify_downloaded_asset(download: &DownloadPlan, bytes: &[u8]) -> Result<()>
Ok(())
}
/// Warn when self-update would overwrite a binary a package manager owns.
///
/// We warn rather than refuse: the download still produces a working newer
/// binary, and refusing would break workflows that have been doing this for
/// releases. But the manager's metadata will then describe a version that is
/// no longer on disk, and its next upgrade silently reverts the user — so say
/// so, and name the command that would have done this properly.
/// Explain how to move to GitHub releases without overwriting managed files.
fn managed_install_warning(method: InstallMethod) -> Option<String> {
if method.supports_self_update() {
return None;
}
Some(format!(
"Warning: this binary looks like a {label} install.\n \
`{command}` is the command that updates it cleanly.\n \
Self-updating in place still works, but leaves {label} describing a version\n \
that is no longer on disk, and its next upgrade will revert this update.",
"This executable is managed by {label}; in-place self-update is disabled.\n\n\
{GITHUB_MIGRATION_HELP}\n\n\
To retain this secondary {label} installation, run `{command}`.",
label = method.label(),
command = method.update_command()
))
@@ -225,6 +252,7 @@ fn managed_install_warning(method: InstallMethod) -> Option<String> {
#[derive(Debug, Clone)]
struct UpdateExecutableIdentity {
path: PathBuf,
file_hash: String,
#[cfg(target_os = "android")]
android_proof: AndroidExecutableProof,
}
@@ -232,13 +260,17 @@ struct UpdateExecutableIdentity {
#[cfg(not(target_os = "android"))]
fn update_executable_identity() -> Result<UpdateExecutableIdentity> {
let path = std::env::current_exe().context("failed to determine current executable path")?;
Ok(UpdateExecutableIdentity { path })
let file_hash = sha256_hex(&std::fs::read(&path).context("failed to identify updater binary")?);
Ok(UpdateExecutableIdentity { path, file_hash })
}
#[cfg(target_os = "android")]
fn update_executable_identity() -> Result<UpdateExecutableIdentity> {
let android_proof = android_loaded_executable_proof()?;
Ok(UpdateExecutableIdentity {
file_hash: sha256_hex(
&std::fs::read(&android_proof.path).context("failed to identify updater binary")?,
),
path: android_proof.path.clone(),
android_proof,
})
@@ -518,34 +550,93 @@ fn validate_primary_update_identity(identity: &UpdateExecutableIdentity) -> Resu
fresh
);
}
return Ok(());
}
let bytes = std::fs::read(&identity.path).context("failed to recheck updater binary")?;
if sha256_hex(&bytes) != identity.file_hash {
bail!(
"The running executable path changed during the update; no further files were replaced. Run the intended executable again by its full path."
);
}
Ok(())
}
#[cfg(not(target_os = "android"))]
{
let _ = identity;
Ok(())
/// Only the running binary and copies of those exact bytes are ours to update.
/// Command names alone do not establish ownership of an existing sibling.
fn validate_update_target(target: &Path, identity: &UpdateExecutableIdentity) -> Result<()> {
if !InstallMethod::from_path(target).supports_self_update() || protected_update_path(target) {
bail!(
"Refusing to replace managed/system path {}.\n\n{GITHUB_MIGRATION_HELP}",
target.display()
);
}
let metadata = match std::fs::symlink_metadata(target) {
Ok(metadata) => metadata,
Err(error) if error.kind() == std::io::ErrorKind::NotFound && target != identity.path => {
return Ok(());
}
Err(error) => {
return Err(error)
.with_context(|| format!("failed to inspect update target {}", target.display()));
}
};
if !metadata.is_file() || metadata.is_symlink() {
bail!(
"Refusing to replace {}: the update target is not a regular file.\n\n{GITHUB_MIGRATION_HELP}",
target.display()
);
}
let bytes = std::fs::read(target)
.with_context(|| format!("failed to identify update target {}", target.display()))?;
if sha256_hex(&bytes) != identity.file_hash {
bail!(
"Refusing to replace {}: its bytes differ from the running executable. This may be another installation or an unrelated command. No command is removed automatically.\n\n{GITHUB_MIGRATION_HELP}",
target.display()
);
}
Ok(())
}
fn protected_update_path(path: &Path) -> bool {
[
"/usr/bin",
"/usr/sbin",
"/bin",
"/sbin",
"/nix/store",
"/gnu/store",
]
.iter()
.any(|prefix| path.starts_with(prefix))
|| path.components().any(|component| {
component.as_os_str().to_str().is_some_and(|name| {
name.eq_ignore_ascii_case("Windows")
|| name.eq_ignore_ascii_case("WindowsApps")
|| name.eq_ignore_ascii_case("scoop")
|| name.eq_ignore_ascii_case("chocolatey")
})
})
}
fn replace_verified_downloads<F>(
target_paths: &[PathBuf],
verified_bytes: &[u8],
validate_primary_identity: F,
validate_target: F,
) -> Result<()>
where
F: Fn() -> Result<()>,
F: Fn(&Path) -> Result<()>,
{
// Fail before mutating a sibling if the primary pathname no longer names
// the process image that initiated this update.
validate_primary_identity()?;
for path in target_paths {
validate_target(path)?;
}
for path in target_paths.iter().rev() {
replace_binary_with_validation(path, verified_bytes, || {
// Re-check after each temp file is fully staged and immediately
// before every destructive rename. The running command is first
// in the plan and therefore replaced last, after its colocated
// compatibility names have received the same verified bytes.
validate_primary_identity()
validate_target(path)
})?;
}
Ok(())
@@ -639,8 +730,8 @@ impl UpdateReleaseSource {
}
/// True when an environment override, not a probe, chose this source. Such
/// a source also carries the pinned version, so the "already up to date"
/// shortcut does not apply to it.
/// a source also carries the pinned version. The same no-downgrade and
/// already-current checks apply before downloading from any source.
fn is_pinned_mirror(&self) -> bool {
!matches!(self, Self::GitHub)
}
@@ -799,7 +890,7 @@ fn describe_release_source_for_check(
}
}
/// Resolve a source that does not participate in the Linux-x64 race.
/// Resolve an explicit source or a platform without a supported fallback.
///
/// This path is still fail-closed: every platform and every explicit mirror
/// must publish a valid manifest from the same source that covers the selected
@@ -833,15 +924,19 @@ fn single_source_download_plan(
)
})?;
println!("Downloading {}...", checksum_asset.name);
let checksum_bytes =
download_url(&checksum_asset.browser_download_url, proxy).with_context(|| {
format!(
"failed to download {} from {}\n{}",
checksum_asset.name,
fetched.source.describe(),
update_network_fallback_hint()
)
})?;
let checksum_bytes = download_url_with_timeout(
&checksum_asset.browser_download_url,
proxy,
MANIFEST_PROBE_TIMEOUT,
)
.with_context(|| {
format!(
"failed to download {} from {}\n{}",
checksum_asset.name,
fetched.source.describe(),
update_network_fallback_hint()
)
})?;
let checksum_text = std::str::from_utf8(&checksum_bytes)
.with_context(|| format!("{} is not valid UTF-8", checksum_asset.name))?;
let checksums = parse_checksum_manifest(checksum_text).with_context(|| {
@@ -879,14 +974,9 @@ fn manifest_probe_fetcher(proxy: Option<&Proxy>) -> Arc<ManifestFetcher> {
})
}
/// Probe every candidate at once and take the first one that answers with a
/// usable manifest.
///
/// "First" means first to *return*, not first in the list and not whichever one
/// survives a timeout: a source that is slow or unreachable simply loses, and a
/// source that answers with a manifest that does not cover this platform's
/// binary loses too. Once a winner is chosen its receiver is dropped, so a
/// straggler's result has nowhere to land and is ignored.
/// Try the official GitHub manifest first. Only an unavailable or unusable
/// manifest admits the next configured source; a faster mirror never races
/// GitHub. Each network probe has its own bounded timeout and retry policy.
fn select_release_source(
candidates: Vec<ReleaseSourceCandidate>,
fetch_manifest: Arc<ManifestFetcher>,
@@ -895,20 +985,9 @@ fn select_release_source(
bail!("no release source publishes an asset for this platform");
}
let (result_tx, result_rx) = mpsc::channel();
for candidate in candidates {
let result_tx = result_tx.clone();
let fetch_manifest = Arc::clone(&fetch_manifest);
thread::spawn(move || {
let outcome = probe_release_source(&candidate, &*fetch_manifest);
let _ = result_tx.send((candidate, outcome));
});
}
drop(result_tx);
let mut failures = Vec::new();
while let Ok((candidate, outcome)) = result_rx.recv() {
match outcome {
for candidate in candidates {
match probe_release_source(&candidate, &*fetch_manifest) {
Ok(checksums) => {
return Ok(DownloadPlan {
source: candidate.source,
@@ -981,32 +1060,15 @@ fn legacy_binary_message(current_exe: &Path) -> String {
"\
this binary ({exe}) is using the legacy deepseek/deepseek-tui command name.
The package has been renamed to `codewhale`. This update will install the
canonical `codewhale` command and refresh any existing `codew` or
`codewhale-tui` compatibility command from the same binary beside the legacy
command when the install directory is writable.
The package has been renamed to `codewhale`. A supported direct update can
install the canonical `codewhale` command beside this legacy command when a
newer verified release is available and the destination paths are safe to use.
DeepSeek provider support is unchanged.
If this update cannot write to the install directory, reinstall using your
original install method:
{GITHUB_MIGRATION_HELP}
npm:
npm uninstall -g deepseek-tui
npm install -g codewhale
Cargo:
cargo uninstall deepseek-tui-cli 2>/dev/null || true
cargo uninstall deepseek-tui 2>/dev/null || true
cargo install codewhale-cli --locked
Homebrew:
brew upgrade codewhale
# existing Cellar/deepseek-tui installs can still:
brew upgrade deepseek-tui
Manual binary:
download the matched codewhale asset from
https://github.com/Hmbown/CodeWhale/releases/latest
Existing npm, Cargo, Homebrew, or system-managed commands are left to their
package manager. See docs/INSTALL.md for secondary package routes.
Once `codewhale` is on your PATH, run `codewhale update` for future updates.",
exe = current_exe.display(),
@@ -1043,6 +1105,16 @@ fn push_unique_path(paths: &mut Vec<PathBuf>, path: PathBuf) {
}
}
fn push_update_path(paths: &mut Vec<PathBuf>, path: PathBuf, current_exe: &Path) {
// Keep a same-target symlink as a symlink. Replacing its target refreshes
// the alias too. Foreign/broken links stay in the plan and fail validation.
let same_target_link = std::fs::symlink_metadata(&path).is_ok_and(|m| m.is_symlink())
&& std::fs::canonicalize(&path).is_ok_and(|resolved| resolved == current_exe);
if !same_target_link {
push_unique_path(paths, path);
}
}
fn legacy_tui_command_exists_beside(current_exe: &Path) -> bool {
command_name_for_exe(current_exe) == "deepseek-tui"
|| command_path_beside(current_exe, "deepseek-tui").exists()
@@ -1066,15 +1138,18 @@ fn update_plan_for_exe(current_exe: &Path) -> UpdatePlan {
}
let primary = installed_command_path(current_exe, "codewhale");
push_unique_path(&mut target_paths, primary);
push_update_path(&mut target_paths, primary, current_exe);
for alias in ["codew", "codewhale-tui"] {
let alias_path = installed_command_path(current_exe, alias);
let migrate_legacy_tui = alias == "codewhale-tui"
&& is_legacy_binary(current_exe)
&& legacy_tui_command_exists_beside(current_exe);
if alias_path.exists() || command_name_for_exe(current_exe) == alias || migrate_legacy_tui {
push_unique_path(&mut target_paths, alias_path);
if std::fs::symlink_metadata(&alias_path).is_ok()
|| command_name_for_exe(current_exe) == alias
|| migrate_legacy_tui
{
push_update_path(&mut target_paths, alias_path, current_exe);
}
}
@@ -1888,7 +1963,7 @@ mod tests {
use std::io::{Read, Write};
use std::net::TcpListener;
use std::sync::mpsc;
use std::sync::{Condvar, Mutex, MutexGuard};
use std::sync::{Mutex, MutexGuard};
use std::thread;
/// Release-source environment variables are process-wide, so the tests that
@@ -1900,7 +1975,9 @@ mod tests {
codewhale_release::DEEPSEEK_RELEASE_BASE_URL_ENV,
codewhale_release::CNB_MIRROR_ENV,
codewhale_release::UPDATE_VERSION_ENV,
codewhale_release::LEGACY_TUI_UPDATE_VERSION_ENV,
codewhale_release::LEGACY_UPDATE_VERSION_ENV,
codewhale_release::install::INSTALL_METHOD_ENV,
];
struct UpdateEnvGuard {
@@ -2310,7 +2387,7 @@ mod tests {
std::fs::rename(&swapped_primary, &primary).unwrap();
let target_paths = vec![primary.clone(), sibling.clone()];
let error = replace_verified_downloads(&target_paths, b"downloaded binary", || {
let error = replace_verified_downloads(&target_paths, b"downloaded binary", |_| {
resolve_android_loaded_executable_report(&maps, TEST_ANDROID_MARKER, &primary)
.map(|_| ())
})
@@ -2346,10 +2423,10 @@ mod tests {
let target_paths = vec![primary.clone(), sibling.clone()];
let validation_calls = Cell::new(0);
let error = replace_verified_downloads(&target_paths, b"downloaded binary", || {
let error = replace_verified_downloads(&target_paths, b"downloaded binary", |_| {
let call = validation_calls.get() + 1;
validation_calls.set(call);
if call == 1 {
if call <= target_paths.len() {
return Ok(());
}
std::fs::rename(&swapped_primary, &primary).unwrap();
@@ -2358,7 +2435,7 @@ mod tests {
})
.expect_err("identity mismatch must fail before the staged sibling persists");
assert_eq!(validation_calls.get(), 2);
assert_eq!(validation_calls.get(), 3);
assert!(
error.to_string().contains("loaded-image identity changed"),
"unexpected error: {error:#}"
@@ -2386,7 +2463,7 @@ mod tests {
let target_paths = vec![primary.clone()];
let validation_calls = Cell::new(0);
let error = replace_verified_downloads(&target_paths, b"downloaded binary", || {
let error = replace_verified_downloads(&target_paths, b"downloaded binary", |_| {
let call = validation_calls.get() + 1;
validation_calls.set(call);
if call == 1 {
@@ -2454,10 +2531,12 @@ mod tests {
}
#[test]
fn managed_installs_are_warned_before_self_update_overwrites_them() {
fn managed_installs_offer_github_migration_and_secondary_manager_command() {
let npm = managed_install_warning(InstallMethod::Npm).expect("npm is package-managed");
assert!(npm.contains("npm install -g codewhale@latest"));
assert!(npm.contains("revert this update"));
assert!(npm.contains("in-place self-update is disabled"));
assert!(npm.contains("https://codewhale.net/install.sh"));
assert!(npm.contains("command -v codewhale codew"));
let brew =
managed_install_warning(InstallMethod::Homebrew).expect("brew is package-managed");
@@ -2473,6 +2552,144 @@ mod tests {
assert!(managed_install_warning(InstallMethod::Binary).is_none());
}
#[test]
fn binary_override_cannot_authorize_a_known_package_install() {
let _env = UpdateEnvGuard::clear();
set_update_env(codewhale_release::install::INSTALL_METHOD_ENV, "binary");
for (path, expected) in [
(
"/usr/local/lib/node_modules/codewhale/bin/codewhale",
InstallMethod::Npm,
),
(
"/opt/homebrew/Cellar/codewhale/0.9.11/bin/codewhale",
InstallMethod::Homebrew,
),
("/home/u/.cargo/bin/codewhale", InstallMethod::Cargo),
] {
assert_eq!(InstallMethod::detect(Path::new(path)), expected);
}
}
#[test]
fn explicit_mirror_cannot_downgrade_or_download_an_older_release() {
let _env = UpdateEnvGuard::clear();
set_update_env(
codewhale_release::RELEASE_BASE_URL_ENV,
"http://127.0.0.1:0",
);
set_update_env(codewhale_release::UPDATE_VERSION_ENV, "0.0.1");
for beta in [false, true] {
run_update(beta, false, None)
.expect("an older pinned version must return before any download");
}
}
#[test]
fn system_paths_are_protected_but_user_release_paths_are_allowed() {
for path in [
"/usr/bin/codewhale",
"/usr/sbin/codewhale",
"/bin/codewhale",
"/nix/store/pkg/bin/codewhale",
"/gnu/store/pkg/bin/codewhale",
"/Users/u/scoop/apps/codewhale/codewhale.exe",
"/Windows/System32/codewhale.exe",
] {
assert!(protected_update_path(Path::new(path)), "{path}");
}
for path in [
"/usr/local/bin/codewhale",
"/home/u/.local/bin/codewhale",
"/data/data/com.termux/files/usr/bin/codewhale",
] {
assert!(!protected_update_path(Path::new(path)), "{path}");
}
}
#[cfg(not(target_os = "android"))]
fn test_update_identity(path: &Path) -> UpdateExecutableIdentity {
UpdateExecutableIdentity {
path: path.to_path_buf(),
file_hash: sha256_hex(&std::fs::read(path).unwrap()),
}
}
#[cfg(not(target_os = "android"))]
#[test]
fn unrelated_alias_fails_before_any_command_is_replaced() {
let dir = tempfile::TempDir::new().unwrap();
let primary = dir.path().join("codewhale");
let alias = dir.path().join("codew");
std::fs::write(&primary, b"running bytes").unwrap();
std::fs::write(&alias, b"unrelated executable").unwrap();
let identity = test_update_identity(&primary);
let error =
replace_verified_downloads(&[primary.clone(), alias.clone()], b"new bytes", |target| {
validate_primary_update_identity(&identity)?;
validate_update_target(target, &identity)
})
.unwrap_err();
assert!(error.to_string().contains("bytes differ"), "{error:#}");
assert_eq!(std::fs::read(primary).unwrap(), b"running bytes");
assert_eq!(std::fs::read(alias).unwrap(), b"unrelated executable");
}
#[cfg(not(target_os = "android"))]
#[test]
fn desktop_primary_swap_is_detected_before_siblings_change() {
let dir = tempfile::TempDir::new().unwrap();
let primary = dir.path().join("codewhale");
let alias = dir.path().join("codew");
for path in [&primary, &alias] {
std::fs::write(path, b"running bytes").unwrap();
}
let identity = test_update_identity(&primary);
std::fs::write(&primary, b"a different build").unwrap();
let error =
replace_verified_downloads(&[primary.clone(), alias.clone()], b"new bytes", |target| {
validate_primary_update_identity(&identity)?;
validate_update_target(target, &identity)
})
.unwrap_err();
assert!(error.to_string().contains("path changed"), "{error:#}");
assert_eq!(std::fs::read(primary).unwrap(), b"a different build");
assert_eq!(std::fs::read(alias).unwrap(), b"running bytes");
}
#[cfg(all(unix, not(target_os = "android")))]
#[test]
fn same_target_symlink_survives_and_foreign_or_broken_links_are_refused() {
use std::os::unix::fs::symlink;
let dir = tempfile::TempDir::new().unwrap();
let primary = dir.path().canonicalize().unwrap().join("codewhale");
let alias = primary.with_file_name("codew");
write_installed_binary(&primary, b"running bytes");
symlink("codewhale", &alias).unwrap();
let identity = test_update_identity(&primary);
let plan = update_plan_for_exe(&primary);
assert_eq!(plan.target_paths.as_slice(), std::slice::from_ref(&primary));
replace_verified_downloads(&plan.target_paths, b"new bytes", |target| {
validate_primary_update_identity(&identity)?;
validate_update_target(target, &identity)
})
.unwrap();
assert!(std::fs::symlink_metadata(&alias).unwrap().is_symlink());
assert_eq!(std::fs::read(&alias).unwrap(), b"new bytes");
std::fs::remove_file(&alias).unwrap();
symlink("foreign", &alias).unwrap();
for exists in [false, true] {
if exists {
std::fs::write(primary.with_file_name("foreign"), b"other").unwrap();
}
let plan = update_plan_for_exe(&primary);
assert!(plan.target_paths.contains(&alias));
let identity = test_update_identity(&primary);
assert!(validate_update_target(&alias, &identity).is_err());
assert!(std::fs::symlink_metadata(&alias).unwrap().is_symlink());
}
}
#[test]
fn legacy_binary_message_gives_copy_pasteable_migration_steps() {
let message = legacy_binary_message(Path::new("/usr/local/bin/deepseek-tui"));
@@ -2481,14 +2698,11 @@ mod tests {
assert!(message.contains("canonical `codewhale` command"));
assert!(message.contains("DeepSeek provider support"));
assert!(message.contains("is unchanged"));
assert!(message.contains("npm uninstall -g deepseek-tui"));
assert!(message.contains("npm install -g codewhale"));
assert!(message.contains("cargo uninstall deepseek-tui-cli 2>/dev/null || true"));
assert!(message.contains("cargo uninstall deepseek-tui 2>/dev/null || true"));
assert!(message.contains("cargo install codewhale-cli --locked"));
assert!(!message.contains("cargo install codewhale-tui --locked"));
assert!(message.contains("brew upgrade codewhale"));
assert!(message.contains("brew upgrade deepseek-tui"));
assert!(message.contains(GITHUB_MIGRATION_HELP));
assert!(!message.contains("This update will install"));
assert!(message.contains("command -v codewhale codew"));
assert!(message.contains("package manager"));
assert!(!message.contains("uninstall"));
assert!(message.contains("https://github.com/Hmbown/CodeWhale/releases/latest"));
}
@@ -2625,7 +2839,8 @@ mod tests {
assert!(plan.asset_stem.starts_with("codewhale-"));
assert!(!plan.asset_stem.contains("codewhale-tui"));
replace_verified_downloads(&plan.target_paths, b"v0.9.5 primary bytes", || Ok(())).unwrap();
replace_verified_downloads(&plan.target_paths, b"v0.9.5 primary bytes", |_| Ok(()))
.unwrap();
for path in [&primary, &codew, &legacy_tui] {
assert_eq!(std::fs::read(path).unwrap(), b"v0.9.5 primary bytes");
@@ -2657,7 +2872,7 @@ mod tests {
assert!(plan.asset_stem.starts_with("codewhale-"));
assert!(!plan.asset_stem.starts_with("codewhale-tui-"));
replace_verified_downloads(&plan.target_paths, b"new primary bytes", || Ok(()))
replace_verified_downloads(&plan.target_paths, b"new primary bytes", |_| Ok(()))
.unwrap();
for path in [&primary, &codew, &legacy_tui] {
assert_eq!(std::fs::read(path).unwrap(), b"new primary bytes");
@@ -3299,90 +3514,19 @@ E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 *codewhale-win
serve_http_responses(vec![(status, content_type, body)])
}
// ---------------------------------------------------------------------
// Proactive first-party source selection.
//
// Every test here is deterministic and offline. Probe order is fixed by a
// gate rather than by sleeping, because the contract under test is "the
// first source to *answer* wins" — a timing-based test would be asserting
// the scheduler, not the selector.
// ---------------------------------------------------------------------
/// A one-shot gate that holds one probe until another has answered.
#[derive(Default)]
struct ProbeGate {
opened: Mutex<bool>,
ready: Condvar,
}
impl ProbeGate {
fn open(&self) {
*self.opened.lock().unwrap_or_else(|err| err.into_inner()) = true;
self.ready.notify_all();
}
fn wait(&self) {
let mut opened = self.opened.lock().unwrap_or_else(|err| err.into_inner());
while !*opened {
opened = self
.ready
.wait(opened)
.unwrap_or_else(|err| err.into_inner());
}
}
}
/// One source's scripted response, plus when it is allowed to answer.
struct ScriptedProbe {
wait_for: Option<Arc<ProbeGate>>,
then_open: Option<Arc<ProbeGate>>,
answer: Result<String, String>,
}
impl ScriptedProbe {
fn ready(answer: Result<String, String>) -> Self {
Self {
wait_for: None,
then_open: None,
answer,
}
}
fn held(gate: &Arc<ProbeGate>, answer: Result<String, String>) -> Self {
Self {
wait_for: Some(Arc::clone(gate)),
then_open: None,
answer,
}
}
fn opening(mut self, gate: &Arc<ProbeGate>) -> Self {
self.then_open = Some(Arc::clone(gate));
self
}
}
// Ordered source selection is deterministic and offline. A successful
// GitHub manifest must not cause any mirror request.
fn scripted_manifest_fetcher(
script: Vec<(&'static str, ScriptedProbe)>,
script: Vec<(&'static str, Result<String, String>)>,
) -> Arc<ManifestFetcher> {
let script: HashMap<&'static str, ScriptedProbe> = script.into_iter().collect();
let script: HashMap<_, _> = script.into_iter().collect();
Arc::new(move |candidate: &ReleaseSourceCandidate| {
let label = candidate.source.label();
let probe = script
.get(label)
.unwrap_or_else(|| panic!("no scripted probe for {label}"));
if let Some(gate) = &probe.wait_for {
gate.wait();
}
let answer = probe
.answer
script
.get(candidate.source.label())
.expect("unexpected source request")
.clone()
.map(String::into_bytes)
.map_err(|message| anyhow!(message));
if let Some(gate) = &probe.then_open {
gate.open();
}
answer
.map_err(|message| anyhow!(message))
})
}
@@ -3432,68 +3576,20 @@ E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 *codewhale-win
fn linux_x64_candidates(tag_name: &str) -> Vec<ReleaseSourceCandidate> {
candidates_for(&github_fetched_release(tag_name), "linux", "x86_64")
.expect("linux x64 must race GitHub against the CNB mirror")
.expect("linux x64 must try GitHub before the CNB mirror")
}
#[test]
fn cnb_wins_when_its_manifest_answers_first() {
let github_gate = Arc::new(ProbeGate::default());
let fetch = scripted_manifest_fetcher(vec![
(
"GitHub Releases",
ScriptedProbe::held(&github_gate, Ok(manifest_covering_linux_x64())),
),
(
"CNB mirror",
ScriptedProbe::ready(Ok(manifest_covering_linux_x64())),
),
]);
let plan = select_release_source(linux_x64_candidates("v9.9.9"), fetch)
.expect("a source must win");
github_gate.open();
assert_eq!(
plan.source,
UpdateReleaseSource::Cnb {
base_url: cnb_release_base_url("v9.9.9"),
}
);
assert_eq!(
plan.binary_url,
"https://cnb.cool/codewhale.net/codewhale/-/releases/download/v9.9.9/codewhale-linux-x64",
"the binary must come from the source whose manifest won"
);
assert_eq!(plan.binary_name, "codewhale-linux-x64");
assert!(
plan.checksums.contains_key("codewhale-linux-x64"),
"the winning manifest must be carried forward, not refetched"
);
}
#[test]
fn github_wins_when_its_manifest_answers_first() {
let cnb_gate = Arc::new(ProbeGate::default());
let fetch = scripted_manifest_fetcher(vec![
(
"GitHub Releases",
ScriptedProbe::ready(Ok(manifest_covering_linux_x64())),
),
(
"CNB mirror",
ScriptedProbe::held(&cnb_gate, Ok(manifest_covering_linux_x64())),
),
]);
let plan = select_release_source(linux_x64_candidates("v9.9.9"), fetch)
.expect("a source must win");
// The losing worker may finish now that the selector has observed the
// GitHub result. Opening this gate from inside GitHub's fetch closure
// was too early: CNB could parse and send first after both fetches had
// returned, making the test assert scheduler luck rather than arrival.
cnb_gate.open();
fn github_is_preferred_without_contacting_a_healthy_mirror() {
let requested = Arc::new(Mutex::new(Vec::new()));
let captured = Arc::clone(&requested);
let fetch: Arc<ManifestFetcher> = Arc::new(move |candidate| {
captured.lock().unwrap().push(candidate.source.label());
Ok(manifest_covering_linux_x64().into_bytes())
});
let plan = select_release_source(linux_x64_candidates("v9.9.9"), fetch).unwrap();
assert_eq!(plan.source, UpdateReleaseSource::GitHub);
assert_eq!(*requested.lock().unwrap(), ["GitHub Releases"]);
assert_eq!(
plan.binary_url,
"https://github.com/Hmbown/CodeWhale/releases/download/v9.9.9/codewhale-linux-x64"
@@ -3501,37 +3597,44 @@ E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 *codewhale-win
}
#[test]
fn a_source_that_answers_first_but_cannot_serve_this_platform_loses() {
// GitHub answers first every time here; it just answers with something
// unusable. The loser of a race is decided by the manifest, not by
// arrival order alone.
fn cnb_is_used_only_after_github_manifest_failure() {
for first_answer in [
Err("connection refused".to_string()),
Err("connection timed out".to_string()),
Ok(manifest_missing_linux_x64()),
Ok("not a checksum manifest".to_string()),
Ok(String::new()),
] {
let cnb_gate = Arc::new(ProbeGate::default());
let fetch = scripted_manifest_fetcher(vec![
(
"GitHub Releases",
ScriptedProbe::ready(first_answer.clone()).opening(&cnb_gate),
),
(
"CNB mirror",
ScriptedProbe::held(&cnb_gate, Ok(manifest_covering_linux_x64())),
),
]);
let plan = select_release_source(linux_x64_candidates("v9.9.9"), fetch)
.unwrap_or_else(|err| panic!("CNB must win over {first_answer:?}: {err:#}"));
let requested = Arc::new(Mutex::new(Vec::new()));
let captured = Arc::clone(&requested);
let fetch: Arc<ManifestFetcher> = Arc::new(move |candidate| {
captured.lock().unwrap().push(candidate.source.label());
if candidate.source == UpdateReleaseSource::GitHub {
first_answer
.clone()
.map(String::into_bytes)
.map_err(|err| anyhow!(err))
} else {
Ok(manifest_covering_linux_x64().into_bytes())
}
});
let plan = select_release_source(linux_x64_candidates("v9.9.9"), fetch).unwrap();
assert_eq!(
*requested.lock().unwrap(),
["GitHub Releases", "CNB mirror"]
);
assert_eq!(
plan.source,
UpdateReleaseSource::Cnb {
base_url: cnb_release_base_url("v9.9.9"),
},
"unusable first answer {first_answer:?} must not win"
}
);
assert_eq!(
plan.binary_url,
"https://cnb.cool/codewhale.net/codewhale/-/releases/download/v9.9.9/codewhale-linux-x64"
);
assert_eq!(
plan.checksums.get("codewhale-linux-x64"),
Some(&"a".repeat(64))
);
}
}
@@ -3539,14 +3642,8 @@ E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 *codewhale-win
#[test]
fn selection_fails_closed_when_no_source_is_usable() {
let fetch = scripted_manifest_fetcher(vec![
(
"GitHub Releases",
ScriptedProbe::ready(Err("dns failure".to_string())),
),
(
"CNB mirror",
ScriptedProbe::ready(Ok(manifest_missing_linux_x64())),
),
("GitHub Releases", Err("dns failure".to_string())),
("CNB mirror", Ok(manifest_missing_linux_x64())),
]);
let err = select_release_source(linux_x64_candidates("v9.9.9"), fetch)
@@ -3572,7 +3669,7 @@ E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 *codewhale-win
}
#[test]
fn only_linux_x64_races_the_cnb_mirror() {
fn only_supported_targets_have_cnb_as_a_fallback() {
let fetched = github_fetched_release("v9.9.9");
for (os, arch) in [
@@ -3589,7 +3686,7 @@ E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 *codewhale-win
);
}
let raced = candidates_for(&fetched, "linux", "x86_64").expect("linux x64 races");
let raced = candidates_for(&fetched, "linux", "x86_64").expect("linux x64 fallback");
assert_eq!(raced.len(), 2);
}
@@ -3795,7 +3892,7 @@ E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 *codewhale-win
source: UpdateReleaseSource::GitHub,
};
let candidates = candidates_for(&fetched, "linux", "x86_64").expect("linux x64 races");
let candidates = candidates_for(&fetched, "linux", "x86_64").expect("linux x64 fallback");
assert_eq!(candidates.len(), 1);
assert!(matches!(
@@ -3805,7 +3902,7 @@ E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 *codewhale-win
}
#[test]
fn beta_tags_race_the_same_two_sources_as_stable_tags() {
fn beta_tags_use_the_same_ordered_sources_as_stable_tags() {
let candidates = linux_x64_candidates("v0.9.0-beta.2");
assert_eq!(candidates[0].source, UpdateReleaseSource::GitHub);
@@ -3840,7 +3937,7 @@ E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 *codewhale-win
assert!(fetched.source.is_pinned_mirror());
assert!(
candidates_for(&fetched, "linux", "x86_64").is_none(),
"an explicit base URL must never be raced against CNB"
"an explicit base URL must never fall back to CNB"
);
assert_eq!(
describe_release_source_for_check(&fetched, "codewhale-linux-x64", None),
@@ -3865,7 +3962,7 @@ E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 *codewhale-win
);
assert!(
candidates_for(&fetched, "linux", "x86_64").is_none(),
"an explicit CNB request must not be raced back against GitHub"
"an explicit CNB request must not fall back to GitHub"
);
}
@@ -135,7 +135,15 @@ fn installed_unix_dispatcher_settles_a_closed_pipe_cleanly() {
output.status,
String::from_utf8_lossy(&output.stderr)
);
assert!(output.stderr.is_empty());
// A sealed fresh home has never seen the usage policy, so the only thing
// allowed on stderr is that one-time disclosure. Nothing else may leak.
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.is_empty()
|| stderr == format!("{}\n", codewhale_telemetry::notice::STARTUP_DISCLOSURE),
"unexpected stderr: {stderr}"
);
assert!(!stderr.contains(SENTINEL));
}
fn isolated_command(home: &Path, codewhale_home: Option<&Path>) -> Command {
+9 -2
View File
@@ -6,6 +6,8 @@
//! '--resume <id>' found`, exit 2). A non-TTY launch must now get past
//! argument parsing and fail only on the interactive-terminal contract, and
//! must not create session state under a sealed HOME on the way out.
//! Fresh-session and mouse flags exercise the same boundary: an unrecognized
//! `--fresh` previously swallowed the remaining flags as one prompt value.
#![cfg(unix)]
@@ -39,6 +41,7 @@ fn isolated_command(home: &Path, codewhale_home: &Path, workspace: &Path) -> Com
.env("CODEWHALE_HOME", codewhale_home)
.env("CODEWHALE_SECRET_BACKEND", "file")
.env("CODEWHALE_NO_UPDATE_CHECK", "1")
.env("CODEWHALE_DISABLE_MODELS_DEV_FETCH", "1")
.env("CODEWHALE_TELEMETRY", "0")
.env("NO_COLOR", "1")
.current_dir(workspace)
@@ -49,22 +52,26 @@ fn isolated_command(home: &Path, codewhale_home: &Path, workspace: &Path) -> Com
}
#[test]
fn root_resume_flags_reach_the_tui_launch_contract_without_touching_state() {
fn root_session_flags_reach_the_tui_launch_contract_without_touching_state() {
for argv in [
&["--resume", "800596e6"][..],
&["--resume=800596e6"][..],
&["-r", "800596e6"][..],
&["--session-id", "800596e6"][..],
&["--continue"][..],
&["--no-project-config", "--fresh", "--mouse-capture"][..],
&["--no-project-config", "--mouse-capture", "--fresh"][..],
] {
let fixture = TempDir::new().expect("fixture root");
let home = fixture.path().join("sealed-home");
let codewhale_home = fixture.path().join("sealed-codewhale-home");
let workspace = fixture.path().join("workspace");
let workspace = fixture.path().join("workspace with spaces");
std::fs::create_dir_all(&home).expect("home");
std::fs::create_dir_all(&workspace).expect("workspace");
let output = isolated_command(&home, &codewhale_home, &workspace)
.arg("--workspace")
.arg(&workspace)
.args(argv)
.output()
.expect("run codewhale");
@@ -27,7 +27,7 @@ fn env_off_beats_cli_on_end_to_end() {
let on = dispatch_and_read_telemetry(None);
let dry_run = on
.dry_run
.expect("`--telemetry true` must write the dry-run sink");
.expect("current consent plus `--telemetry true` must write the dry-run sink");
assert!(
dry_run.contains("\"event\":\"session_start\"")
&& dry_run.contains("\"event\":\"session_end\""),
@@ -45,7 +45,7 @@ fn env_off_beats_cli_on_end_to_end() {
/// network latency. The next interactive session owns delivery.
#[test]
fn short_cli_exit_persists_without_network_delivery() {
let evidence = dispatch_and_read_telemetry_with_endpoint(None, None);
let evidence = dispatch_and_read_telemetry_with_endpoint(None, None, None, false);
let pending = evidence
.pending
.expect("short CLI must seal its pending telemetry before process exit");
@@ -71,10 +71,10 @@ fn an_unparseable_telemetry_env_value_keeps_the_in_process_runtime_off() {
);
}
/// Re-enabling through the documented settings command must clear a decline
/// recorded by the former opt-in notice as well as the config-file floor.
/// An explicit durable enable uses the Settings transition to clear old no.
/// The optional versioned compatibility command still rejects old versions.
#[test]
fn config_set_true_reenables_a_historical_decline() {
fn config_set_true_reenables_a_historical_decline_through_settings() {
let fixture = TempDir::new().expect("fixture root");
let home = fixture.path().join("home");
let codewhale_home = fixture.path().join("codewhale-home");
@@ -91,24 +91,37 @@ fn config_set_true_reenables_a_historical_decline() {
.expect("write historical decline");
let config_path = fixture.path().join("config.toml");
fs::write(&config_path, "telemetry = false\n").expect("write config");
fs::write(&config_path, "telemetry_endpoint = \"\"\n").expect("write config");
let output = Command::new(codewhale_binary())
.current_dir(&workspace)
.env_clear()
.env("PATH", std::env::var_os("PATH").expect("PATH"))
.env("HOME", &home)
.env("USERPROFILE", &home)
.env("CODEWHALE_HOME", &codewhale_home)
.env("CODEWHALE_SECRET_BACKEND", "file")
.args([
"--config",
config_path.to_str().expect("config path"),
"config",
"set",
"telemetry",
"true",
])
let command = || {
let mut command = Command::new(codewhale_binary());
command
.current_dir(&workspace)
.env_clear()
.env("PATH", std::env::var_os("PATH").expect("PATH"))
.env("HOME", &home)
.env("USERPROFILE", &home)
.env("CODEWHALE_HOME", &codewhale_home)
.env("CODEWHALE_SECRET_BACKEND", "file")
.arg("--config")
.arg(&config_path);
command
};
let before = fs::read(&state_path).unwrap();
let declined = command()
.env("CODEWHALE_TELEMETRY", "true")
.args(["--telemetry", "true", "features", "list"])
.output()
.expect("run with a historical sidecar-only decline");
assert!(declined.status.success());
assert!(!codewhale_home.join("telemetry").exists());
assert_eq!(
fs::read(&state_path).unwrap(),
before,
"default-on and run-scoped on must preserve the old explicit no"
);
let output = command()
.args(["config", "set", "telemetry", "true"])
.output()
.expect("run config set");
assert!(
@@ -125,23 +138,95 @@ fn config_set_true_reenables_a_historical_decline() {
let state = SetupState::load_from(&state_path).expect("read setup state");
assert!(state.telemetry_accepted(TELEMETRY_NOTICE_VERSION));
assert!(!state.telemetry_opted_out());
let outdated = command()
.args(["config", "telemetry", "--accept-notice", "3"])
.output()
.expect("attempt outdated consent");
assert!(!outdated.status.success());
assert!(
!SetupState::load_from(&state_path)
.expect("read enabled state")
.telemetry_opted_out()
);
let accepted = command()
.args([
"config",
"telemetry",
"--accept-notice",
TELEMETRY_NOTICE_VERSION,
])
.output()
.expect("accept current notice");
assert!(
accepted.status.success(),
"current notice acceptance failed: {}",
String::from_utf8_lossy(&accepted.stderr)
);
let state = SetupState::load_from(&state_path).expect("read accepted state");
assert!(state.telemetry_accepted(TELEMETRY_NOTICE_VERSION));
assert!(!state.telemetry_opted_out());
}
#[test]
fn missing_preference_defaults_on_without_inventing_acceptance() {
for version in [None, Some("1"), Some("4")] {
let evidence = dispatch_and_read_telemetry_with_endpoint(None, Some(""), version, false);
let batch: serde_json::Value = serde_json::from_str(
evidence
.dry_run
.as_ref()
.expect("default-on writes dry run")
.lines()
.next()
.unwrap(),
)
.unwrap();
assert_eq!(batch["schema_version"], 3);
assert_eq!(batch["notice_version"], 5);
assert!(batch.get("consent_version").is_none());
let state = evidence.setup.expect("disclosure display marker");
assert_eq!(
state.telemetry_notice_shown_for.as_deref(),
Some(TELEMETRY_NOTICE_VERSION)
);
assert_eq!(state.telemetry_notice_decided_for.as_deref(), version);
assert!(!state.telemetry_accepted(TELEMETRY_NOTICE_VERSION));
assert!(evidence.stderr.contains("on by default"));
assert!(evidence.stderr.contains("Codewhale and PostHog"));
assert!(
evidence
.stderr
.contains("codewhale config set telemetry false")
);
}
}
struct DispatchEvidence {
telemetry_dir_exists: bool,
dry_run: Option<String>,
pending: Option<String>,
setup: Option<SetupState>,
stderr: String,
}
/// Run the real dispatcher into a keyless in-process command and report the
/// telemetry state it actually left behind.
fn dispatch_and_read_telemetry(telemetry_env: Option<&str>) -> DispatchEvidence {
dispatch_and_read_telemetry_with_endpoint(telemetry_env, Some(""))
dispatch_and_read_telemetry_with_endpoint(
telemetry_env,
Some(""),
Some(TELEMETRY_NOTICE_VERSION),
true,
)
}
fn dispatch_and_read_telemetry_with_endpoint(
telemetry_env: Option<&str>,
endpoint: Option<&str>,
consent_version: Option<&str>,
cli_on: bool,
) -> DispatchEvidence {
let fixture = TempDir::new().expect("fixture root");
let home = fixture.path().join("home");
@@ -151,8 +236,16 @@ fn dispatch_and_read_telemetry_with_endpoint(
fs::create_dir_all(dir).expect("create fixture dir");
}
if let Some(version) = consent_version {
let mut state = SetupState::default();
state.record_telemetry_notice(version, true);
state
.save_to(&codewhale_home.join("setup_state.json"))
.expect("write fixture consent");
}
let config_path = fixture.path().join("config.toml");
let mut config = "telemetry = true\n".to_string();
let mut config = String::new();
if let Some(endpoint) = endpoint {
config.push_str(&format!("telemetry_endpoint = {endpoint:?}\n"));
}
@@ -172,8 +265,11 @@ fn dispatch_and_read_telemetry_with_endpoint(
"https://example.invalid/releases",
)
.arg("--config")
.arg(&config_path)
.args(["--telemetry", "true", "features", "list"]);
.arg(&config_path);
if cli_on {
command.args(["--telemetry", "true"]);
}
command.args(["features", "list"]);
if let Some(value) = telemetry_env {
command.env("CODEWHALE_TELEMETRY", value);
}
@@ -206,6 +302,8 @@ fn dispatch_and_read_telemetry_with_endpoint(
telemetry_dir_exists: telemetry_dir.exists(),
dry_run,
pending,
setup: SetupState::load_from(&codewhale_home.join("setup_state.json")),
stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
}
}
+1 -1
View File
@@ -11,4 +11,4 @@ description = "Prototype command capability and dispatch shapes for the staged T
workspace = true
[dependencies]
codewhale-core = { path = "../core", version = "0.9.11" }
codewhale-core = { path = "../core", version = "0.9.12" }
+341
View File
@@ -329,6 +329,347 @@ pub trait CommandMemoryContext {
fn delete_workspace(&self, workspace: &Path) -> Result<MemoryDelete, String>;
}
// ---------------------------------------------------------------------------
// Plugin (FEAT-020 D1/D2/D10/D11)
// ---------------------------------------------------------------------------
/// Portable plugin diagnostic level (FEAT-020 D2).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PluginDiagnosticLevel {
Warning,
Error,
}
/// Portable plugin diagnostic entry.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PluginDiagnostic {
pub level: PluginDiagnosticLevel,
pub code: String,
pub message: String,
pub path: Option<PathBuf>,
}
/// Portable MCP transport classification for the capability review body.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PluginMcpTransport {
Stdio,
Http,
Invalid,
}
/// Portable MCP server detail for the capability review body (FEAT-020 D2).
///
/// Carries only the semantic fields `render_mcp_inventory` consumes:
/// transport, command/url, argv, cwd, env provenance, timeouts, required,
/// enabled/disabled tool lists, and the enabled flag. Host `McpServerConfig`
/// never crosses the boundary.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PluginMcpServerDetail {
pub name: String,
pub transport: PluginMcpTransport,
pub command: Option<String>,
pub argv: Vec<String>,
pub cwd: Option<PathBuf>,
pub env: Vec<(String, String)>,
pub url: Option<String>,
pub env_headers: Vec<(String, String)>,
pub bearer_token_env_var: Option<String>,
pub connect_timeout_secs: Option<u64>,
pub execute_timeout_secs: Option<u64>,
pub read_timeout_secs: Option<u64>,
pub required: bool,
pub enabled_tools: Vec<String>,
pub disabled_tools: Vec<String>,
pub enabled: bool,
}
/// Portable summary of one loaded plugin bundle (list output, FEAT-020 D2).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PluginSummary {
pub name: String,
pub id: String,
pub state_label: String,
pub scope: String,
pub trust_status: String,
pub compatibility: String,
pub inventory: String,
pub active: bool,
pub trusted: bool,
pub enabled: bool,
}
/// Portable full bundle detail for show/review/validate rendering (FEAT-020 D2).
///
/// Carries every semantic value the render helpers consume. The complete
/// `LoadedPlugin` never crosses the boundary; only branch-consumed fields are
/// projected here (D10).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PluginDetail {
/// Inventory summary string (host-computed, e.g. `skills=1 mcp=0`).
pub inventory_summary: String,
pub name: String,
pub id: String,
pub version: String,
pub origin: String,
pub scope: String,
pub state_label: String,
pub trust_status: String,
pub compatibility: String,
pub content_hash: String,
pub capability_hash: String,
pub canonical_root: PathBuf,
pub active: bool,
pub trusted: bool,
pub enabled: bool,
pub unsupported_labels: Vec<String>,
pub supported_labels: Vec<String>,
pub skills: Vec<String>,
pub filesystem_roots: Vec<String>,
pub network_hosts: Vec<String>,
pub stdio_mcp_servers: usize,
pub lifecycle_mutation: bool,
pub mcp_servers: Vec<PluginMcpServerDetail>,
pub diagnostics: Vec<PluginDiagnostic>,
}
/// Portable outcome of a plugin mutation (FEAT-020 D2/D11).
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PluginMutationOutcome {
Installed,
Updated,
NoChange,
Uninstalled,
NeedsApproval(String),
NetworkDenied(String),
}
/// Portable mutation receipt returned synchronously by the facet (FEAT-020 D11).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PluginMutationReceipt {
pub name: String,
pub path: Option<PathBuf>,
pub content_hash: Option<String>,
pub installed_content_hash: Option<String>,
pub outcome: PluginMutationOutcome,
}
/// Portable bundle export receipt (FEAT-020 D2).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PluginExportReceipt {
pub exported_name: String,
pub target: PathBuf,
pub display_name: Option<String>,
pub wrote_mcp_json: bool,
pub files_copied: u64,
pub skills_normalized: bool,
}
/// Portable legacy executable-tool detail (FEAT-020 D2).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PluginLegacyTool {
pub name: String,
pub description: String,
pub approval: String,
pub input_schema: Option<String>,
pub path: PathBuf,
}
/// Portable legacy-tool scan result: directory and discovered tools.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PluginLegacyScan {
pub dir: PathBuf,
pub tools: Vec<PluginLegacyTool>,
}
/// Portable Kimi managed-plugin candidate (FEAT-020 D2).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PluginManagedCandidate {
pub name: String,
pub version: String,
pub license: Option<String>,
pub canonical_path: PathBuf,
pub content_hash: String,
pub capability_hash: String,
pub inventory: String,
pub applicable: bool,
}
/// Portable Kimi managed-scan result (FEAT-020 D2).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PluginManagedScan {
pub root: PathBuf,
pub candidates: Vec<PluginManagedCandidate>,
pub rejected: Vec<String>,
}
/// Portable marketplace candidate install plan (FEAT-020 D2).
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PluginMarketplaceInstallPlan {
Supported { spec: String, source_kind: String },
Unsupported { reason: String },
}
/// Portable marketplace candidate (FEAT-020 D2).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PluginMarketplaceCandidate {
pub name: String,
pub display_name: Option<String>,
pub version: Option<String>,
pub tier: String,
pub compatibility: Option<String>,
pub install_plan: PluginMarketplaceInstallPlan,
pub description: Option<String>,
pub homepage: Option<String>,
pub repository: Option<String>,
pub author: Option<String>,
pub license: Option<String>,
pub keywords: Vec<String>,
pub when: Option<String>,
pub diagnostics: Vec<PluginDiagnostic>,
pub has_errors: bool,
}
/// Portable marketplace catalog (FEAT-020 D2).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PluginMarketplaceCatalog {
pub id: String,
/// Source document path (for the `show` provenance line).
pub source_path: Option<String>,
pub display_name: Option<String>,
pub description: Option<String>,
pub format: String,
pub tier: String,
pub publisher: Option<String>,
pub total_candidates: usize,
pub warning_count: usize,
pub candidates: Vec<PluginMarketplaceCandidate>,
pub diagnostics: Vec<PluginDiagnostic>,
}
/// Portable marketplace add receipt (FEAT-020 D2).
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PluginMarketplaceAddReceipt {
pub name: String,
pub candidate_count: usize,
pub warning_count: usize,
pub catalog: PluginMarketplaceCatalog,
}
/// Portable marketplace state: stored catalogs plus an optional host-provided
/// built-in `official` catalog.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PluginMarketplaceState {
/// Optional host-provided built-in catalog. Current main provides none;
/// retaining the option keeps the portable boundary future-compatible
/// without inventing a catalog in the handler.
pub official: Option<PluginMarketplaceCatalog>,
pub stored: Vec<PluginMarketplaceCatalog>,
}
/// Portable suggestion for the `/plugin suggest` recommendation output.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PluginSuggestion {
pub name: String,
/// State label rendered beside the plugin name (active/not-reviewed/…).
pub state_label: String,
pub description: String,
pub why: Vec<String>,
/// The actionable next step rendered under the suggestion.
pub next_step: String,
}
/// Host plugin data for the plugin command group (FEAT-020 D1).
///
/// One object-safe, synchronous facet exposing the exact-minimum typed
/// operations the live `/plugin` branch closure consumes. Registry reads and
/// mutations, async-bridged install/update/uninstall (returning synchronous
/// portable receipts), export, legacy executable-tool scan, Kimi managed
/// import, and marketplace operations are all represented. The handler never
/// names `crate::plugins`, `PluginRegistry`, `LoadedPlugin`, `Config`, or
/// another concrete host service; implementation errors cross as safe text.
///
/// Post-mutation side effects (rediscovery, skill-cache refresh, active-skill
/// reset) happen host-side inside the facet implementation; the handler only
/// renders the returned receipt (D11).
pub trait CommandPluginContext {
/// Read-only: registry summaries for list output.
fn summaries(&self) -> Result<Vec<PluginSummary>, String>;
/// Read-only: full portable detail for show/review/validate.
fn detail(&self, selector: &str) -> Result<PluginDetail, String>;
/// Read-only: registry-level diagnostics.
fn registry_diagnostics(&self) -> Vec<PluginDiagnostic>;
/// Read-only: whether validation reports no errors.
fn validation_is_clean(&self) -> bool;
/// Read-only: registry length (used by list/reload empty branches).
fn len(&self) -> usize;
/// Mutation: rediscover the workspace registry and refresh the skill
/// cache; returns the new registry length for the reload message.
fn reload(&mut self) -> Result<usize, String>;
/// Read-only: whether the registry is empty.
fn is_empty(&self) -> bool;
/// Return the one-shot on-disk-change nudge, if the host detects one.
/// The host owns the mutable catalog-stamp state; handlers only render.
fn reload_nudge(&mut self) -> Option<String>;
/// Read-only: persistence store path for marketplace state.
fn state_path(&self) -> Option<PathBuf>;
/// Read-only: recommend installed bundles for a task without side effects.
fn suggest(&self, task: &str) -> Result<Vec<PluginSuggestion>, String>;
/// Mutation: trust a bundle by exact review token. Success means the
/// mutation was applied; the handler renders the action word from its own
/// dispatch arm and may re-read `detail` for post-mutation state.
fn trust(&mut self, selector: &str, token: &str) -> Result<(), String>;
/// Mutation: enable a bundle. Success means enabled; re-read `detail` for
/// the post-mutation compatibility note.
fn enable(&mut self, selector: &str) -> Result<(), String>;
/// Mutation: disable a bundle.
fn disable(&mut self, selector: &str) -> Result<(), String>;
/// Mutation: revoke trust.
fn revoke_trust(&mut self, selector: &str) -> Result<(), String>;
/// Async-bridged install; returns a synchronous portable receipt (D11).
fn install(
&mut self,
source: &str,
expected_content_hash: Option<&str>,
) -> Result<PluginMutationReceipt, String>;
/// Async-bridged update; returns a synchronous portable receipt (D11).
fn update(&mut self, selector: &str) -> Result<PluginMutationReceipt, String>;
/// Async-bridged uninstall; returns a synchronous portable receipt (D11).
fn uninstall(&mut self, selector: &str) -> Result<PluginMutationReceipt, String>;
/// File-level removal of a just-installed bundle whose content hash
/// mismatched (rollback). Unlike [`Self::uninstall`] it does not resolve a
/// registry selector and triggers no rediscovery or skill-cache side
/// effects; the host adapter owns the `crate::plugins` call (D1).
fn uninstall_path(&mut self, name: &str, plugins_dir: &Path) -> Result<(), String>;
/// Read-only: export a loaded bundle to a target directory.
fn export(&self, selector: &str, target: &Path) -> Result<PluginExportReceipt, String>;
/// Read-only: scan legacy executable plugin tools.
fn legacy_scan(&self) -> Result<Option<PluginLegacyScan>, String>;
/// Read-only: Kimi managed-plugin directory scan.
fn managed_scan(&self, home_override: Option<&Path>) -> Result<PluginManagedScan, String>;
/// Mutation: install a Kimi managed candidate by exact content hash.
fn managed_install(
&mut self,
canonical_path: &Path,
expected_content_hash: &str,
) -> Result<PluginMutationReceipt, String>;
/// Read-only: marketplace state (optional host catalog + stored catalogs).
fn marketplace_state(&self) -> Result<PluginMarketplaceState, String>;
/// Mutation: add a local catalog document to the marketplace store.
fn marketplace_add(
&mut self,
name: &str,
path: &Path,
) -> Result<PluginMarketplaceAddReceipt, String>;
/// Mutation: remove a stored marketplace catalog.
fn marketplace_remove(&mut self, name: &str) -> Result<bool, String>;
/// Mutation: install a marketplace candidate through the reviewed installer.
fn marketplace_install(
&mut self,
catalog: &str,
candidate: &str,
) -> Result<PluginMutationReceipt, String>;
}
// ---------------------------------------------------------------------------
// Skill group (FEAT-022 D1)
// ---------------------------------------------------------------------------
+17 -3
View File
@@ -6,9 +6,9 @@
use crate::facets::{
CommandCostContext, CommandMediaContext, CommandMemoryContext, CommandModePolicyContext,
CommandModelContext, CommandPresentationContext, CommandProjectContext, CommandSessionContext,
CommandSkillGroupContext, CommandSkillsContext, CommandSystemPromptContext,
CommandWorkspaceContext,
CommandModelContext, CommandPluginContext, CommandPresentationContext, CommandProjectContext,
CommandSessionContext, CommandSkillGroupContext, CommandSkillsContext,
CommandSystemPromptContext, CommandWorkspaceContext,
};
/// Exact host capabilities exposed to one contextual command handler.
@@ -36,6 +36,8 @@ impl CommandCapabilities {
pub const PROJECT: Self = Self(1 << 10);
/// Skills-group host data (FEAT-022 D1).
pub const SKILL_GROUP: Self = Self(1 << 11);
/// Plugin-group host data (FEAT-020 D1), appended after current main capabilities.
pub const PLUGIN: Self = Self(1 << 12);
pub const fn union(self, other: Self) -> Self {
Self(self.0 | other.0)
@@ -82,6 +84,7 @@ pub struct CommandContexts<'a> {
memory: Option<&'a mut dyn CommandMemoryContext>,
project: Option<&'a mut dyn CommandProjectContext>,
skill_group: Option<&'a mut dyn CommandSkillGroupContext>,
plugin: Option<&'a mut dyn CommandPluginContext>,
}
/// Consumed envelope used when one handler needs several independent facets.
@@ -98,6 +101,7 @@ pub struct ContextParts<'a> {
pub memory: Option<&'a mut dyn CommandMemoryContext>,
pub project: Option<&'a mut dyn CommandProjectContext>,
pub skill_group: Option<&'a mut dyn CommandSkillGroupContext>,
pub plugin: Option<&'a mut dyn CommandPluginContext>,
}
impl<'a> CommandContexts<'a> {
@@ -115,6 +119,7 @@ impl<'a> CommandContexts<'a> {
memory: None,
project: None,
skill_group: None,
plugin: None,
}
}
@@ -132,6 +137,7 @@ impl<'a> CommandContexts<'a> {
memory: self.memory,
project: self.project,
skill_group: self.skill_group,
plugin: self.plugin,
}
}
@@ -227,6 +233,14 @@ impl<'a> CommandContexts<'a> {
);
self
}
pub fn with_plugin(mut self, value: &'a mut dyn CommandPluginContext) -> Self {
assert!(
self.plugin.replace(value).is_none(),
"plugin facet already set"
);
self
}
}
impl Default for CommandContexts<'_> {
+446
View File
@@ -896,6 +896,452 @@ fn envelope_rejects_duplicate_memory_slot_deterministically() {
assert!(result.is_err(), "duplicate memory slot must assert");
}
// ---------------------------------------------------------------------------
// FEAT-020: plugin capability, portable DTOs, and envelope slot (D1-D11)
// ---------------------------------------------------------------------------
/// Deterministic fake plugin facet over portable values only.
struct FakePlugin {
summaries: Vec<PluginSummary>,
detail: Option<PluginDetail>,
installed: bool,
managed_candidates: Vec<PluginManagedCandidate>,
}
impl FakePlugin {
fn new() -> Self {
Self {
summaries: vec![PluginSummary {
name: "demo".to_string(),
id: "[email protected]".to_string(),
state_label: "active".to_string(),
scope: "user".to_string(),
trust_status: "trusted".to_string(),
compatibility: "full".to_string(),
inventory: "skills=1 mcp=0".to_string(),
active: true,
trusted: true,
enabled: true,
}],
detail: Some(PluginDetail {
name: "demo".to_string(),
id: "[email protected]".to_string(),
inventory_summary: "skills=1 mcp=0".to_string(),
version: "1.0.0".to_string(),
origin: "local".to_string(),
scope: "user".to_string(),
state_label: "active".to_string(),
trust_status: "trusted".to_string(),
compatibility: "full".to_string(),
content_hash: "abc".to_string(),
capability_hash: "def".to_string(),
canonical_root: PathBuf::from("/plugins/demo"),
active: true,
trusted: true,
enabled: true,
unsupported_labels: Vec::new(),
supported_labels: vec!["skills".to_string()],
skills: vec!["demo:demo-skill".to_string()],
filesystem_roots: Vec::new(),
network_hosts: Vec::new(),
stdio_mcp_servers: 0,
lifecycle_mutation: false,
mcp_servers: Vec::new(),
diagnostics: Vec::new(),
}),
installed: false,
managed_candidates: Vec::new(),
}
}
}
impl CommandPluginContext for FakePlugin {
fn summaries(&self) -> Result<Vec<PluginSummary>, String> {
Ok(self.summaries.clone())
}
fn detail(&self, selector: &str) -> Result<PluginDetail, String> {
if selector == "demo" {
self.detail
.clone()
.ok_or_else(|| "missing detail".to_string())
} else {
Err(format!("no plugin named {selector}"))
}
}
fn registry_diagnostics(&self) -> Vec<PluginDiagnostic> {
Vec::new()
}
fn validation_is_clean(&self) -> bool {
true
}
fn len(&self) -> usize {
self.summaries.len()
}
fn reload(&mut self) -> Result<usize, String> {
Ok(self.summaries.len())
}
fn is_empty(&self) -> bool {
self.summaries.is_empty()
}
fn reload_nudge(&mut self) -> Option<String> {
None
}
fn state_path(&self) -> Option<PathBuf> {
Some(PathBuf::from("/plugins/state.json"))
}
fn suggest(&self, task: &str) -> Result<Vec<PluginSuggestion>, String> {
if task.len() < 3 {
return Err("task too short".to_string());
}
Ok(vec![PluginSuggestion {
name: "demo".to_string(),
state_label: "active".to_string(),
description: "Demo bundle".to_string(),
why: vec![task.to_string()],
next_step: "Already active: /plugin show demo".to_string(),
}])
}
fn trust(&mut self, _selector: &str, token: &str) -> Result<(), String> {
if token == "abc.def" {
Ok(())
} else {
Err("Review token does not match this bundle content and capability set".to_string())
}
}
fn enable(&mut self, _selector: &str) -> Result<(), String> {
Ok(())
}
fn disable(&mut self, _selector: &str) -> Result<(), String> {
Ok(())
}
fn revoke_trust(&mut self, _selector: &str) -> Result<(), String> {
Ok(())
}
fn install(
&mut self,
_source: &str,
expected_content_hash: Option<&str>,
) -> Result<PluginMutationReceipt, String> {
if let Some(expected) = expected_content_hash
&& expected != "abc"
{
return Err("content hash mismatch".to_string());
}
self.installed = true;
Ok(PluginMutationReceipt {
name: "demo".to_string(),
path: Some(PathBuf::from("/plugins/demo")),
content_hash: Some("abc".to_string()),
installed_content_hash: Some("abc".to_string()),
outcome: PluginMutationOutcome::Installed,
})
}
fn update(&mut self, _selector: &str) -> Result<PluginMutationReceipt, String> {
Ok(PluginMutationReceipt {
name: "demo".to_string(),
path: None,
content_hash: None,
installed_content_hash: None,
outcome: PluginMutationOutcome::NoChange,
})
}
fn uninstall(&mut self, _selector: &str) -> Result<PluginMutationReceipt, String> {
Ok(PluginMutationReceipt {
name: "demo".to_string(),
path: None,
content_hash: None,
installed_content_hash: None,
outcome: PluginMutationOutcome::Uninstalled,
})
}
fn uninstall_path(&mut self, _name: &str, _plugins_dir: &Path) -> Result<(), String> {
Ok(())
}
fn export(&self, _selector: &str, target: &Path) -> Result<PluginExportReceipt, String> {
Ok(PluginExportReceipt {
exported_name: "demo".to_string(),
target: target.to_path_buf(),
display_name: Some("Demo Bundle".to_string()),
wrote_mcp_json: false,
files_copied: 2,
skills_normalized: false,
})
}
fn legacy_scan(&self) -> Result<Option<PluginLegacyScan>, String> {
Ok(None)
}
fn managed_scan(&self, _home_override: Option<&Path>) -> Result<PluginManagedScan, String> {
Ok(PluginManagedScan {
root: PathBuf::from("/kimi/managed"),
candidates: self.managed_candidates.clone(),
rejected: Vec::new(),
})
}
fn managed_install(
&mut self,
canonical_path: &Path,
expected_content_hash: &str,
) -> Result<PluginMutationReceipt, String> {
if expected_content_hash != "abc" {
return Err("Kimi candidate changed".to_string());
}
Ok(PluginMutationReceipt {
name: "kimi-demo".to_string(),
path: Some(canonical_path.to_path_buf()),
content_hash: Some("abc".to_string()),
installed_content_hash: Some("abc".to_string()),
outcome: PluginMutationOutcome::Installed,
})
}
fn marketplace_state(&self) -> Result<PluginMarketplaceState, String> {
Ok(PluginMarketplaceState {
official: Some(PluginMarketplaceCatalog {
id: "official".to_string(),
source_path: None,
display_name: None,
description: Some("Built into this release".to_string()),
format: "codewhale".to_string(),
tier: "official".to_string(),
publisher: Some("Codewhale".to_string()),
total_candidates: 1,
warning_count: 0,
candidates: Vec::new(),
diagnostics: Vec::new(),
}),
stored: Vec::new(),
})
}
fn marketplace_add(
&mut self,
name: &str,
_path: &Path,
) -> Result<PluginMarketplaceAddReceipt, String> {
if name == "official" {
return Err(
"`official` is the catalog built into Codewhale; pick another name.".to_string(),
);
}
Ok(PluginMarketplaceAddReceipt {
name: name.to_string(),
candidate_count: 0,
warning_count: 0,
catalog: PluginMarketplaceCatalog {
id: name.to_string(),
source_path: None,
display_name: None,
description: None,
format: "kimi".to_string(),
tier: "community".to_string(),
publisher: None,
total_candidates: 0,
warning_count: 0,
candidates: Vec::new(),
diagnostics: Vec::new(),
},
})
}
fn marketplace_remove(&mut self, _name: &str) -> Result<bool, String> {
Ok(true)
}
fn marketplace_install(
&mut self,
_catalog: &str,
_candidate: &str,
) -> Result<PluginMutationReceipt, String> {
Ok(PluginMutationReceipt {
name: "market-demo".to_string(),
path: None,
content_hash: None,
installed_content_hash: None,
outcome: PluginMutationOutcome::Installed,
})
}
}
#[test]
fn plugin_facet_is_object_safe_and_typed() {
fn plugin(_: &dyn CommandPluginContext) {}
plugin(&FakePlugin::new());
let plugin = FakePlugin::new();
assert_eq!(plugin.len(), 1);
assert!(!plugin.is_empty());
assert!(plugin.validation_is_clean());
let summaries = plugin.summaries().unwrap();
assert_eq!(summaries[0].name, "demo");
assert_eq!(summaries[0].state_label, "active");
}
#[test]
fn plugin_detail_preserves_semantic_values() {
let plugin = FakePlugin::new();
let detail = plugin.detail("demo").unwrap();
assert_eq!(detail.content_hash, "abc");
assert_eq!(detail.capability_hash, "def");
assert_eq!(detail.compatibility, "full");
assert!(detail.active);
assert_eq!(detail.skills, vec!["demo:demo-skill"]);
// Unknown selector fails safely.
assert!(plugin.detail("nope").is_err());
}
#[test]
fn plugin_mutation_receipts_distinguish_outcomes() {
let mut plugin = FakePlugin::new();
let installed = plugin.install("path:/demo", Some("abc")).unwrap();
assert_eq!(installed.outcome, PluginMutationOutcome::Installed);
assert_eq!(installed.installed_content_hash.as_deref(), Some("abc"));
// Exact-hash mismatch fails before any install side effect.
let err = plugin.install("path:/demo", Some("wrong")).unwrap_err();
assert!(err.contains("content hash mismatch"));
let uninstalled = plugin.uninstall("demo").unwrap();
assert_eq!(uninstalled.outcome, PluginMutationOutcome::Uninstalled);
plugin.trust("demo", "abc.def").unwrap();
let trust_err = plugin.trust("demo", "bad.token").unwrap_err();
assert!(trust_err.contains("Review token does not match"));
}
#[test]
fn plugin_managed_and_marketplace_values_are_portable() {
let mut plugin = FakePlugin::new();
let scan = plugin.managed_scan(None).unwrap();
assert_eq!(scan.root, PathBuf::from("/kimi/managed"));
assert!(scan.candidates.is_empty());
plugin.managed_candidates.push(PluginManagedCandidate {
name: "kimi-demo".to_string(),
version: "1.0.0".to_string(),
license: Some("MIT".to_string()),
canonical_path: PathBuf::from("/kimi/managed/kimi-demo"),
content_hash: "abc".to_string(),
capability_hash: "def".to_string(),
inventory: "skills=1".to_string(),
applicable: true,
});
let scan = plugin.managed_scan(None).unwrap();
assert_eq!(scan.candidates[0].name, "kimi-demo");
assert_eq!(scan.candidates[0].license.as_deref(), Some("MIT"));
let state = plugin.marketplace_state().unwrap();
let official = state.official.as_ref().expect("fake official catalog");
assert_eq!(official.id, "official");
assert_eq!(official.tier, "official");
assert!(state.stored.is_empty());
let add = plugin
.marketplace_add("custom", Path::new("/catalog.json"))
.unwrap();
assert_eq!(add.name, "custom");
assert_eq!(add.catalog.format, "kimi");
let err = plugin
.marketplace_add("official", Path::new("/x.json"))
.unwrap_err();
assert!(err.contains("built into Codewhale"));
}
#[test]
fn plugin_suggest_is_read_only_and_safe() {
let plugin = FakePlugin::new();
let err = plugin.suggest("ab").unwrap_err();
assert!(err.contains("too short"));
let suggestions = plugin.suggest("translate").unwrap();
assert_eq!(suggestions[0].name, "demo");
assert_eq!(
suggestions[0].next_step,
"Already active: /plugin show demo"
);
}
#[test]
fn plugin_facet_transports_through_envelope_when_declared() {
let mut plugin = FakePlugin::new();
let parts = CommandContexts::empty()
.with_plugin(&mut plugin)
.into_parts();
assert!(parts.plugin.is_some());
assert!(parts.session.is_none());
assert!(parts.memory.is_none());
// Undeclared slots stay absent when the plugin facet is carried alone.
let mut workspace = Workspace;
let parts = CommandContexts::empty()
.with_plugin(&mut plugin)
.with_workspace(&mut workspace)
.into_parts();
assert!(parts.plugin.is_some());
assert!(parts.workspace.is_some());
assert!(parts.presentation.is_none());
}
#[test]
fn envelope_rejects_duplicate_plugin_slot_deterministically() {
let mut a = FakePlugin::new();
let mut b = FakePlugin::new();
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
CommandContexts::empty()
.with_plugin(&mut a)
.with_plugin(&mut b);
}));
assert!(result.is_err(), "duplicate plugin slot must assert");
}
#[test]
fn plugin_capability_bit_is_stable_and_distinct() {
let plugin = CommandCapabilities::PLUGIN;
assert_eq!(plugin, CommandCapabilities::PLUGIN);
assert!(plugin.contains(CommandCapabilities::PLUGIN));
assert!(!plugin.contains(CommandCapabilities::MEMORY));
assert!(!plugin.contains(CommandCapabilities::PROJECT));
assert!(!plugin.contains(CommandCapabilities::SKILL_GROUP));
assert!(!plugin.contains(CommandCapabilities::WORKSPACE));
let plugin_workspace = CommandCapabilities::PLUGIN.union(CommandCapabilities::WORKSPACE);
assert!(plugin_workspace.contains(CommandCapabilities::PLUGIN));
assert!(plugin_workspace.contains(CommandCapabilities::WORKSPACE));
assert!(!plugin_workspace.contains(CommandCapabilities::MEMORY));
// The plugin group declares exactly WORKSPACE | PRESENTATION | PLUGIN.
let exact = CommandCapabilities::WORKSPACE
.union(CommandCapabilities::PRESENTATION)
.union(CommandCapabilities::PLUGIN);
assert!(exact.contains(CommandCapabilities::PLUGIN));
assert!(exact.contains(CommandCapabilities::PRESENTATION));
assert!(!exact.contains(CommandCapabilities::MEDIA));
assert!(!exact.contains(CommandCapabilities::MEMORY));
assert!(!exact.contains(CommandCapabilities::PROJECT));
assert!(!exact.contains(CommandCapabilities::SKILL_GROUP));
assert!(!exact.contains(CommandCapabilities::SKILLS));
}
// FEAT-022: skill-group facet (CommandSkillGroupContext)
// ---------------------------------------------------------------------------
+3 -3
View File
@@ -12,9 +12,9 @@ workspace = true
[dependencies]
anyhow.workspace = true
codewhale-execpolicy = { path = "../execpolicy", version = "0.9.11" }
codewhale-paths = { path = "../paths", version = "0.9.11" }
codewhale-secrets = { path = "../secrets", version = "0.9.11" }
codewhale-execpolicy = { path = "../execpolicy", version = "0.9.12" }
codewhale-paths = { path = "../paths", version = "0.9.12" }
codewhale-secrets = { path = "../secrets", version = "0.9.12" }
fd-lock = "4.0.4"
libc = "0.2"
serde.workspace = true
+3 -42
View File
@@ -177,7 +177,8 @@ pub enum ExternalCredentialSource {
GrokCli,
/// Official DeepSeek Harness (`dsh`) `$DSH_HOME/.credentials.yaml`.
DshCli,
/// Official Antigravity CLI (`agy`) `state.vscdb` OAuth token.
/// Legacy tombstone retained only so old Codewhale consent records can
/// deserialize and be cleared. No runtime may resolve or read this source.
AgyCli,
}
@@ -197,46 +198,6 @@ pub fn default_dsh_credentials_path() -> PathBuf {
home.join(".credentials.yaml")
}
/// Default Antigravity credential store, resolved without probing: the
/// official `agy` CLI persists its OAuth token in the Antigravity app's
/// VSCode-style `state.vscdb` under the user profile. Consent is pinned to
/// this exact path; an ambient move is reported, never followed.
#[must_use]
pub fn default_agy_credentials_path() -> PathBuf {
let base = match std::env::var_os("ANTIGRAVITY_STATE_DIR") {
Some(value) if !value.is_empty() => PathBuf::from(value),
_ => agy_profile_base(),
};
base.join("User").join("globalStorage").join("state.vscdb")
}
#[cfg(target_os = "macos")]
fn agy_profile_base() -> PathBuf {
codewhale_paths::user_home()
.unwrap_or_else(|| PathBuf::from("."))
.join("Library/Application Support/Antigravity")
}
#[cfg(all(unix, not(target_os = "macos")))]
fn agy_profile_base() -> PathBuf {
match std::env::var_os("XDG_CONFIG_HOME") {
Some(value) if !value.is_empty() => PathBuf::from(value),
_ => codewhale_paths::user_home()
.unwrap_or_else(|| PathBuf::from("."))
.join(".config"),
}
.join("Antigravity")
}
#[cfg(windows)]
fn agy_profile_base() -> PathBuf {
match std::env::var_os("APPDATA") {
Some(value) if !value.is_empty() => PathBuf::from(value),
_ => codewhale_paths::user_home().unwrap_or_else(|| PathBuf::from(".")),
}
.join("Antigravity")
}
impl ExternalCredentialSource {
#[must_use]
pub const fn as_str(self) -> &'static str {
@@ -257,7 +218,7 @@ impl ExternalCredentialSource {
Self::KimiCodeCli => "Kimi Code CLI",
Self::GrokCli => "Grok CLI",
Self::DshCli => "DeepSeek Harness",
Self::AgyCli => "Antigravity CLI",
Self::AgyCli => "retired Antigravity consent",
}
}
}
+126 -28
View File
@@ -14,6 +14,7 @@ pub mod provider;
mod provider_defaults;
mod provider_kind;
pub mod provider_templates;
pub mod resolve;
pub mod route;
pub mod settings_schema;
pub mod setup_state;
@@ -83,9 +84,8 @@ pub use codewhale_secrets::Secrets;
pub use external_credentials::{
EXTERNAL_CREDENTIAL_CONSENT_VERSION, EXTERNAL_CREDENTIAL_READ_ONLY_SEMANTICS,
ExternalCredentialAccess, ExternalCredentialConsentStatus, ExternalCredentialConsentToml,
ExternalCredentialReadGrant, ExternalCredentialSource, default_agy_credentials_path,
default_dsh_credentials_path, external_credential_consent_status, quote_os_path,
resolve_external_credential_path,
ExternalCredentialReadGrant, ExternalCredentialSource, default_dsh_credentials_path,
external_credential_consent_status, quote_os_path, resolve_external_credential_path,
};
use serde::{Deserialize, Serialize};
use sha2::{Digest as _, Sha256};
@@ -95,6 +95,7 @@ use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};
pub const CONFIG_FILE_NAME: &str = "config.toml";
pub const PERMISSIONS_FILE_NAME: &str = "permissions.toml";
pub const LEGACY_ANTIGRAVITY_TOMBSTONE_MESSAGE: &str = "Antigravity is a retired, non-runnable legacy provider. Clear Codewhale-owned legacy state with `codewhale auth clear --provider antigravity`; this does not alter Google or Antigravity sessions. For Gemini use provider `google` with `GEMINI_API_KEY`.";
/// Secret-store routing metadata; never credential material.
pub const API_KEYRING_SENTINEL: &str = "__KEYRING__";
@@ -447,8 +448,8 @@ pub struct ProvidersToml {
alias = "gemini"
)]
pub google: ProviderConfigToml,
/// Google Antigravity (`agy`) — consent-gated credential import only;
/// sends fail closed until the cloud-code wire protocol exists.
/// Retired Antigravity configuration. This table exists only so old
/// Codewhale-owned state can deserialize and be cleared safely.
#[serde(
default,
skip_serializing_if = "ProviderConfigToml::is_empty",
@@ -659,6 +660,7 @@ impl ProvidersToml {
&& ProviderKind::all()
.iter()
.all(|provider| self.for_provider(*provider).is_empty())
&& self.antigravity.is_empty()
}
#[must_use]
@@ -1039,6 +1041,9 @@ fn set_provider_config_value(
field: ProviderConfigField,
value: &str,
) -> Result<()> {
if provider == ProviderKind::Antigravity {
bail!(LEGACY_ANTIGRAVITY_TOMBSTONE_MESSAGE);
}
match field {
ProviderConfigField::ApiKey => {
let value = value.to_string();
@@ -2882,10 +2887,9 @@ impl ConfigToml {
}
if key == "telemetry" {
// #5441: telemetry resolves ON by default, so an unset file key
// must not read "key not found" (or as a bare file value) on a
// machine whose batches ship. Show the resolved consent with its
// source — a truth change, not a behaviour change.
// Report the resolved configuration preference even when the key is
// absent. The telemetry owner also preserves recorded opt-outs
// and fails closed when existing privacy state is unreadable.
let (on, source) = resolved_telemetry_consent(self.telemetry);
return Some(format!(
"{} ({})",
@@ -2938,6 +2942,11 @@ impl ConfigToml {
}
pub fn set_value(&mut self, key: &str, value: &str) -> Result<()> {
if parse_custom_provider_config_key(key).is_some_and(|(provider_id, _)| {
ProviderKind::parse_config_identity(provider_id) == Some(ProviderKind::Antigravity)
}) {
bail!(LEGACY_ANTIGRAVITY_TOMBSTONE_MESSAGE);
}
if let Some((provider, field)) = parse_provider_config_key(key) {
return set_provider_config_value(self, provider, field, value);
}
@@ -2948,6 +2957,9 @@ impl ConfigToml {
match key {
"provider" => {
if let Some(provider) = ProviderKind::parse_config_identity(value) {
if provider == ProviderKind::Antigravity {
bail!(LEGACY_ANTIGRAVITY_TOMBSTONE_MESSAGE);
}
self.provider = provider;
self.selected_provider_id = None;
} else {
@@ -3396,10 +3408,10 @@ impl ConfigToml {
.clone()
.or_else(|| env.log_level.clone())
.or_else(|| self.log_level.clone());
// Telemetry consent resolves once, in the shared core behind
// [`resolved_telemetry_consent`], so the runtime and the
// doctor/config provenance surfaces cannot disagree about what is
// shipping (#5441). The comments that matter live there: the
// The telemetry preference resolves once in the shared core behind
// [`resolved_telemetry_consent`]. The telemetry owner also checks old
// durable declines and unreadable privacy state. The
// comments that matter live there: the
// environment/file/default chain, and why every kill switch is a
// floor (`telemetry = false` persisted in the file is the *persistent*
// off switch; an explicit env "off", an unreadable env value, or a
@@ -3515,8 +3527,8 @@ fn merge_project_provider_config(target: &mut ProviderConfigToml, source: &Provi
/// Where an enabled session's batches go when nobody has said otherwise.
///
/// The first-party ingest service — a Cloudflare Worker that appends to Workers
/// Analytics Engine and stores nothing else. See `docs/TELEMETRY.md` for what a
/// batch contains and `telemetry-ingest/` for the handler.
/// Analytics Engine, with an optional disclosed PostHog sink. See
/// `docs/TELEMETRY.md` for what a batch contains and `telemetry-ingest/` for the handler.
///
/// This is a *default*, not a floor, and it changes nothing about permission:
/// `CODEWHALE_TELEMETRY=0`, `telemetry = false`, and a recorded decline all
@@ -3582,7 +3594,7 @@ pub enum TelemetrySource {
Env,
/// `telemetry = …` written to the config file.
Config,
/// Nobody said anything; the shipped default (`on`) applies silently.
/// Nobody said anything; the shipped preference is on.
Default,
}
@@ -3617,7 +3629,7 @@ fn read_telemetry_env() -> (Option<bool>, bool) {
Ok(value) => (Some(value), false),
Err(_) => {
tracing::warn!(
"Invalid CODEWHALE_TELEMETRY/DEEPSEEK_TELEMETRY value '{raw}'; expected one of \
"Invalid CODEWHALE_TELEMETRY/DEEPSEEK_TELEMETRY value; expected one of \
1/0, true/false, yes/no, on/off, enabled/disabled. Telemetry is forced off."
);
(None, true)
@@ -3634,7 +3646,8 @@ fn read_telemetry_env() -> (Option<bool>, bool) {
/// unreadable one, or a dispatcher floor), then the file, then the shipped
/// default of `on`; a persisted `telemetry = false` is a floor no later
/// term can climb over. The runtime resolver calls this directly, so the
/// surfaces and the shipped batches can never disagree.
/// preference surfaces agree. The telemetry owner also checks historical
/// durable declines and fails closed on unreadable privacy state.
#[must_use]
pub fn resolved_telemetry_consent(file_telemetry: Option<bool>) -> (bool, TelemetrySource) {
let (env_telemetry, env_invalid) = read_telemetry_env();
@@ -5445,6 +5458,73 @@ pub fn scrub_plaintext_api_keys_from_config_backup(path: &Path) -> Result<()> {
Ok(())
}
/// Remove only retired Antigravity state from Codewhale's one-time config
/// backup. This never resolves, reads, writes, or revokes any external Google
/// or Antigravity session; it edits only the checked sibling `.bak` file owned
/// by Codewhale.
pub fn scrub_legacy_antigravity_from_config_backup(path: &Path) -> Result<()> {
let backup = checked_config_backup_path(path)?;
if !backup.exists() {
return Ok(());
}
let raw = read_checked_toml_file(&backup, "config backup")?;
let scrubbed = config_toml_without_legacy_antigravity(&raw).with_context(|| {
format!(
"failed to clear retired provider state from config backup {}",
backup.display()
)
})?;
if scrubbed != raw {
persistence::atomic_write(&backup, scrubbed.as_bytes()).with_context(|| {
format!(
"failed to write retired-provider-free config backup {}",
backup.display()
)
})?;
}
Ok(())
}
fn config_toml_without_legacy_antigravity(raw: &str) -> Result<String> {
let mut document = raw.parse::<toml_edit::DocumentMut>().map_err(|_| {
anyhow::anyhow!(
"failed to parse config TOML while clearing retired provider state; file contents were omitted"
)
})?;
let root = document.as_table_mut();
if root
.get("provider")
.and_then(toml_edit::Item::as_str)
.is_some_and(is_legacy_antigravity_name)
{
root.remove("provider");
}
if let Some(fallbacks) = root
.get_mut("fallback_providers")
.and_then(toml_edit::Item::as_array_mut)
{
fallbacks.retain(|value| !value.as_str().is_some_and(is_legacy_antigravity_name));
if fallbacks.is_empty() {
root.remove("fallback_providers");
}
}
if let Some(providers) = root
.get_mut("providers")
.and_then(toml_edit::Item::as_table_like_mut)
{
providers.remove("antigravity");
providers.remove("agy");
}
Ok(document.to_string())
}
fn is_legacy_antigravity_name(value: &str) -> bool {
value.eq_ignore_ascii_case("antigravity") || value.eq_ignore_ascii_case("agy")
}
fn write_one_time_config_backup(path: &Path) -> Result<()> {
let backup = checked_config_backup_path(path)?;
if backup.exists() {
@@ -6615,6 +6695,32 @@ fn redact_toml_value_for_display(key: &str, value: &toml::Value) -> String {
redact_toml_value_for_display_inner(key, false, value).to_string()
}
impl ConfigToml {
/// Redacted TOML rendering of the effective config for `config dump`.
/// Structure is preserved; strings under sensitive key names (api_key,
/// token, secret, … — see `is_sensitive_config_key`, nested tables
/// inherit sensitivity from their ancestors) are redacted. Redaction is
/// name-based: an unrecognized key holding a secret-looking value would
/// pass through, so pasting dump output still deserves a glance.
#[must_use]
pub fn redacted_toml_value(&self) -> toml::Value {
let value =
toml::Value::try_from(self).expect("ConfigToml derives Serialize, so this holds");
match value {
toml::Value::Table(table) => toml::Value::Table(
table
.into_iter()
.map(|(key, value)| {
let redacted = redact_toml_value_for_display_inner(&key, false, &value);
(key, redacted)
})
.collect(),
),
other => other,
}
}
}
fn toml_value_as_u64(value: &toml::Value) -> Option<u64> {
match value {
toml::Value::Integer(value) => u64::try_from(*value).ok(),
@@ -6890,8 +6996,6 @@ struct EnvRuntimeOverrides {
mistral_model: Option<String>,
google_base_url: Option<String>,
google_model: Option<String>,
antigravity_base_url: Option<String>,
antigravity_model: Option<String>,
telecomjs_base_url: Option<String>,
telecomjs_model: Option<String>,
edenai_base_url: Option<String>,
@@ -7216,12 +7320,6 @@ impl EnvRuntimeOverrides {
xai_model: std::env::var("XAI_MODEL")
.ok()
.filter(|v| !v.trim().is_empty()),
antigravity_base_url: std::env::var("ANTIGRAVITY_BASE_URL")
.ok()
.filter(|v| !v.trim().is_empty()),
antigravity_model: std::env::var("ANTIGRAVITY_MODEL")
.ok()
.filter(|v| !v.trim().is_empty()),
google_base_url: std::env::var("GOOGLE_BASE_URL")
.ok()
.filter(|v| !v.trim().is_empty())
@@ -7341,7 +7439,7 @@ impl EnvRuntimeOverrides {
ProviderKind::Xai => self.xai_base_url.clone(),
ProviderKind::Mistral => self.mistral_base_url.clone(),
ProviderKind::Google => self.google_base_url.clone(),
ProviderKind::Antigravity => self.antigravity_base_url.clone(),
ProviderKind::Antigravity => None,
ProviderKind::Telecomjs => self.telecomjs_base_url.clone(),
ProviderKind::Edenai => self.edenai_base_url.clone(),
ProviderKind::Concentrate => self.concentrate_base_url.clone(),
@@ -7389,7 +7487,7 @@ impl EnvRuntimeOverrides {
ProviderKind::Xai => self.xai_model.clone(),
ProviderKind::Mistral => self.mistral_model.clone(),
ProviderKind::Google => self.google_model.clone(),
ProviderKind::Antigravity => self.antigravity_model.clone(),
ProviderKind::Antigravity => None,
ProviderKind::Telecomjs => self.telecomjs_model.clone(),
ProviderKind::Edenai => self.edenai_model.clone(),
ProviderKind::Concentrate => self.concentrate_model.clone(),
+57 -18
View File
@@ -455,10 +455,10 @@ pub const fn credential_help(kind: ProviderKind) -> CredentialHelp {
guidance: "Sign in to Alibaba Cloud Model Studio (Bailian console), create or copy an API key, and select the plan endpoint matching your subscription (Token Plan or Coding Plan).",
},
ProviderKind::Antigravity => CredentialHelp {
acquisition: OAuth,
acquisition: Configuration,
credential_url: None,
docs_url: Some("https://antigravity.google/docs/cli/reference"),
guidance: "Sign in with the official agy CLI (1.1.13). Codewhale can read that login's token read-only from the exact pinned state.vscdb after `codewhale auth external-consent`; it never writes or refreshes it. An ANTIGRAVITY_API_KEY or AGY_ADC_AUTH in the process wins over the file.",
docs_url: None,
guidance: "Legacy configuration only; this route is disabled. Run `codewhale auth clear --provider antigravity` to clear only Codewhale-owned legacy state, then use provider `google` with `GEMINI_API_KEY` for Gemini.",
},
ProviderKind::Google => CredentialHelp {
acquisition: ApiKey,
@@ -1050,10 +1050,10 @@ provider!(
Antigravity,
Antigravity,
"antigravity",
"Google Antigravity",
"Antigravity (legacy, disabled)",
DEFAULT_ANTIGRAVITY_BASE_URL,
DEFAULT_ANTIGRAVITY_MODEL,
["ANTIGRAVITY_API_KEY"],
[],
"antigravity",
aliases: ["agy"]
);
@@ -1821,11 +1821,11 @@ static PROVIDER_REGISTRY: [&dyn Provider; 48] = [
&CUSTOM,
];
/// Return all built-in provider metadata entries in `ProviderKind::ALL` order.
/// Return all built-in and legacy provider metadata entries.
///
/// This insertion order is the stable order used for internal parsing and
/// default selection. It is intentionally NOT the order user-facing UI should
/// render; for browsing/picker surfaces use [`providers_sorted_for_display`].
/// The full registry retains legacy entries needed to read old configuration.
/// It is intentionally NOT a user-facing provider list; for browsing/picker
/// surfaces use [`providers_sorted_for_display`].
#[must_use]
pub fn all_providers() -> &'static [&'static dyn Provider] {
&PROVIDER_REGISTRY
@@ -1839,16 +1839,22 @@ pub fn all_providers() -> &'static [&'static dyn Provider] {
/// happens to sit first in [`ProviderKind::ALL`] (historically DeepSeek). The
/// ordering policy intentionally differs from internal parsing/default order:
///
/// - [`all_providers`] / [`ProviderKind::ALL`] — stable order for internal
/// matching, parsing, and default selection. Do not reorder.
/// - [`all_providers`] — full compatibility registry for internal identity
/// matching, including legacy entries.
/// - [`ProviderKind::ALL`] — stable selectable catalog order. Do not reorder.
/// - [`providers_sorted_for_display`] — neutral alphabetical order for UI
/// browsing. DeepSeek stays present and searchable but is not hard-coded
/// first; a caller may still highlight/pin the active provider separately.
/// browsing, with legacy tombstones omitted. DeepSeek stays present and
/// searchable but is not hard-coded first; a caller may still highlight/pin
/// the active provider separately.
///
/// Returns an owned `Vec` because the sorted order is computed, not static.
#[must_use]
pub fn providers_sorted_for_display() -> Vec<&'static dyn Provider> {
let mut providers = all_providers().to_vec();
let mut providers: Vec<_> = all_providers()
.iter()
.copied()
.filter(|provider| provider.kind() != ProviderKind::Antigravity)
.collect();
providers.sort_by(|a, b| {
a.display_name()
.to_ascii_lowercase()
@@ -2219,6 +2225,26 @@ mod tests {
);
}
#[test]
fn antigravity_registry_entry_is_a_non_runnable_legacy_tombstone() {
let legacy = provider_for_kind(ProviderKind::Antigravity);
assert_eq!(legacy.id(), "antigravity");
assert!(legacy.env_vars().is_empty());
assert!(legacy.default_base_url().ends_with(".invalid"));
assert_eq!(legacy.default_model(), "legacy-antigravity-disabled");
let help = legacy.credential_help();
assert_eq!(help.acquisition, CredentialAcquisition::Configuration);
assert_eq!(help.credential_url, None);
assert_eq!(help.docs_url, None);
assert!(
help.guidance
.contains("codewhale auth clear --provider antigravity")
);
assert!(help.guidance.contains("provider `google`"));
assert!(help.guidance.contains("GEMINI_API_KEY"));
}
#[test]
fn live_verified_console_replacements_do_not_regress_to_404_links() {
let openmodel = provider_for_kind(ProviderKind::Openmodel).credential_help();
@@ -2280,7 +2306,7 @@ mod tests {
#[test]
fn display_order_differs_from_internal_all_order() {
// The whole point of the helper is that UI ordering is NOT the
// internal ProviderKind::ALL / all_providers() insertion order.
// internal compatibility-registry insertion order.
let display_ids: Vec<&str> = providers_sorted_for_display()
.iter()
.map(|p| p.id())
@@ -2294,12 +2320,25 @@ mod tests {
#[test]
fn display_order_is_complete_and_unique() {
// No provider is dropped or duplicated by the sort.
// Every selectable provider is retained exactly once; legacy
// configuration tombstones stay in the internal registry only.
let display = providers_sorted_for_display();
assert_eq!(
display.len(),
all_providers().len(),
"display order must include every built-in provider"
all_providers().len() - 1,
"display order must include every selectable built-in provider"
);
assert!(
all_providers()
.iter()
.any(|provider| provider.kind() == ProviderKind::Antigravity),
"legacy config identity must remain in the internal registry"
);
assert!(
display
.iter()
.all(|provider| provider.kind() != ProviderKind::Antigravity),
"legacy Antigravity tombstone must not appear in provider pickers"
);
let mut ids: Vec<&str> = display.iter().map(|p| p.id()).collect();
ids.sort_unstable();
+7 -5
View File
@@ -225,8 +225,10 @@ pub const DEFAULT_GOOGLE_BASE_URL: &str =
/// Default Gemini model for the Google provider (preview flagship, 2026-08).
pub const DEFAULT_GOOGLE_MODEL: &str = "gemini-3.1-pro-preview";
/// Antigravity cloud-code internal endpoint (credential plane only; the
/// wire protocol is not implemented and sends fail closed).
pub const DEFAULT_ANTIGRAVITY_BASE_URL: &str = "https://cloudcode-pa.googleapis.com/v1internal";
/// Placeholder model id; never sent — the route fails closed before transport.
pub const DEFAULT_ANTIGRAVITY_MODEL: &str = "gemini-3-pro-preview";
/// Non-network endpoint for the legacy Antigravity configuration tombstone.
///
/// `.invalid` is reserved and cannot resolve. Existing configuration remains
/// readable solely so Codewhale can guide the user to clear it safely.
pub const DEFAULT_ANTIGRAVITY_BASE_URL: &str = "https://legacy-antigravity.invalid";
/// Non-runnable model marker for the legacy Antigravity tombstone.
pub const DEFAULT_ANTIGRAVITY_MODEL: &str = "legacy-antigravity-disabled";
+32 -12
View File
@@ -191,9 +191,12 @@ pub enum ProviderKind {
alias = "alibaba-coding-plan-anthropic"
)]
ModelstudioCodingPlanAnthropic,
/// Google Antigravity (`agy` CLI) — consent-gated read-only credential
/// import only; the cloud-code wire protocol is not implemented and
/// requests fail closed with an actionable message.
/// Legacy Antigravity configuration identity.
///
/// Kept only so existing configuration can be read and cleared. It is not
/// a selectable or runnable provider; Gemini users should use [`Google`].
///
/// [`Google`]: Self::Google
#[serde(alias = "agy")]
Antigravity,
/// Google — Gemini OpenAI-compatible endpoint. Its own backend, not an
@@ -247,7 +250,7 @@ impl ProviderKind {
/// stay on the enum for serde and `provider_for_kind`, but they are not
/// first-class catalog rows. Plan is `mode` / base_url; dialect is
/// `wire = openai|anthropic` on the primary provider config.
pub const ALL: [Self; 43] = [
pub const ALL: [Self; 42] = [
Self::Deepseek,
Self::NvidiaNim,
Self::Openai,
@@ -287,7 +290,6 @@ impl ProviderKind {
Self::Telecomjs,
Self::ModelstudioTokenPlan,
Self::Google,
Self::Antigravity,
Self::Edenai,
Self::Concentrate,
Self::Custom,
@@ -315,13 +317,14 @@ impl ProviderKind {
#[must_use]
pub fn parse(value: &str) -> Option<Self> {
let trimmed = value.trim();
provider::all_providers()
.iter()
.find(|p| {
trimmed.eq_ignore_ascii_case(p.id())
|| p.aliases().iter().any(|a| trimmed.eq_ignore_ascii_case(a))
})
.map(|p| p.kind())
// Gated through `ALL`, not the full registry: the registry still holds
// retired tombstones so old config deserializes, and a selection
// surface must never be able to resolve one back into a route.
Self::all().iter().copied().find(|kind| {
let p = kind.provider();
trimmed.eq_ignore_ascii_case(p.id())
|| p.aliases().iter().any(|a| trimmed.eq_ignore_ascii_case(a))
})
}
/// Parse a provider identifier for **config-table identity** — the kind
@@ -353,6 +356,23 @@ impl ProviderKind {
})
.map(|p| p.kind())
.or_else(|| Self::parse(trimmed))
.or_else(|| Self::parse_retired_alias(trimmed))
}
/// Alias lookup restricted to registry entries that are *not* in the
/// selectable catalog. Catalog aliases are handled by [`parse`](Self::parse)
/// and always take precedence.
///
/// Retired kinds still need to answer to their aliases here so a selection
/// surface can *name* the tombstone and refuse it, instead of failing to
/// recognize `agy` and minting a fresh `[providers.agy]` table that serde
/// would fold straight back onto the legacy one.
fn parse_retired_alias(trimmed: &str) -> Option<Self> {
provider::all_providers()
.iter()
.filter(|p| !Self::all().contains(&p.kind()))
.find(|p| p.aliases().iter().any(|a| trimmed.eq_ignore_ascii_case(a)))
.map(|p| p.kind())
}
#[must_use]
+214
View File
@@ -0,0 +1,214 @@
//! Ranked settings resolution with provenance.
//!
//! One rank table for every setting: the highest-ranked layer that names a
//! scalar wins outright (scalars replace, never merge); map layers
//! deep-merge with the highest rank winning per leaf; secrets travel by
//! reference only — a [`SecretRef`] names the source and id, never the
//! value, so a resolved row can say *which* secret won without carrying it.
//!
//! Layers with no producer today (managed policy files, project settings)
//! simply contribute no entries; the rank is the contract, and an empty
//! layer abstains rather than vetoing.
use std::collections::BTreeMap;
/// Where a setting value came from, highest rank first.
///
/// The order is the product rule: policy beats the CLI flag, the flag beats
/// a session override, overrides beat files, and files beat the default.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Layer {
/// Organization / managed policy. No file producer yet.
ManagedPolicy,
/// Per-run `-c key=value` CLI flag (lands with the config CLI slice).
CliFlag,
/// Session override (`/set`, `/config <key> <value>` without `--save`).
SessionOverride,
/// Project settings. No file producer yet.
ProjectConfig,
/// `settings.toml` (user config).
UserConfig,
/// The schema default.
Default,
}
impl Layer {
/// Stable wire name for receipts and row labels.
#[must_use]
pub fn name(self) -> &'static str {
match self {
Layer::ManagedPolicy => "policy",
Layer::CliFlag => "cli",
Layer::SessionOverride => "session",
Layer::ProjectConfig => "project",
Layer::UserConfig => "user",
Layer::Default => "default",
}
}
}
/// A secret by reference: the layer that resolved it plus where to find it.
/// The value itself never enters the resolver, a row, or a receipt.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SecretRef {
/// Which layer named this secret.
pub layer: Layer,
/// Where it lives: `env:DASHSCOPE_API_KEY`, `keychain:codewhale/...`, …
pub reference: String,
}
impl SecretRef {
#[must_use]
pub fn new(layer: Layer, reference: impl Into<String>) -> Self {
Self {
layer,
reference: reference.into(),
}
}
}
/// The winning layer for one scalar key, with every layer that named it.
/// Empty input means the schema default wins unopposed.
#[must_use]
pub fn resolve_scalar<'a>(
candidates: impl IntoIterator<Item = (Layer, &'a str)>,
) -> (Layer, Vec<Layer>) {
let mut best: Option<Layer> = None;
let mut named: Vec<Layer> = Vec::new();
for (layer, _) in candidates {
if !named.contains(&layer) {
named.push(layer);
}
if best.is_none_or(|top| layer < top) {
best = Some(layer);
}
}
named.sort();
(best.unwrap_or(Layer::Default), named)
}
/// Deep-merge map layers: every key present anywhere survives, and per key
/// the highest-ranked layer's value wins. Lower layers contribute only the
/// keys nobody above them named.
#[must_use]
pub fn merge_maps(layers: &BTreeMap<Layer, BTreeMap<String, String>>) -> BTreeMap<String, String> {
let mut merged = BTreeMap::new();
let mut order: Vec<Layer> = layers.keys().copied().collect();
order.sort();
for layer in order {
if let Some(map) = layers.get(&layer) {
for (key, value) in map {
merged.entry(key.clone()).or_insert_with(|| value.clone());
}
}
}
merged
}
/// Which layer supplied each merged key. Same walk as [`merge_maps`], kept
/// as a separate pass so a row can name the winner per key.
#[must_use]
pub fn merge_provenance(
layers: &BTreeMap<Layer, BTreeMap<String, String>>,
) -> BTreeMap<String, Layer> {
let mut provenance = BTreeMap::new();
let mut order: Vec<Layer> = layers.keys().copied().collect();
order.sort();
for layer in order {
if let Some(map) = layers.get(&layer) {
for key in map.keys() {
provenance.entry(key.clone()).or_insert(layer);
}
}
}
provenance
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rank_order_is_policy_over_cli_over_session_over_files_over_default() {
let layers = [
Layer::ManagedPolicy,
Layer::CliFlag,
Layer::SessionOverride,
Layer::ProjectConfig,
Layer::UserConfig,
Layer::Default,
];
let mut sorted = layers;
sorted.sort();
assert_eq!(sorted, layers);
}
#[test]
fn scalar_resolution_picks_the_highest_ranked_layer() {
let (winner, named) = resolve_scalar([
(Layer::Default, "bottom"),
(Layer::UserConfig, "user"),
(Layer::SessionOverride, "session"),
]);
assert_eq!(winner, Layer::SessionOverride);
assert_eq!(
named,
vec![Layer::SessionOverride, Layer::UserConfig, Layer::Default]
);
}
#[test]
fn scalar_resolution_with_no_candidates_falls_back_to_default() {
let (winner, named) = resolve_scalar([]);
assert_eq!(winner, Layer::Default);
assert!(named.is_empty());
}
#[test]
fn scalar_values_replace_never_merge() {
// The loser's text is evidence only; the winner's value is untouched.
let (winner, _) = resolve_scalar([(Layer::UserConfig, "user"), (Layer::CliFlag, "cli")]);
assert_eq!(winner, Layer::CliFlag);
}
#[test]
fn map_merge_keeps_every_key_with_highest_rank_winning_per_key() {
let layers = BTreeMap::from([
(
Layer::UserConfig,
BTreeMap::from([
("a".to_string(), "user-a".to_string()),
("b".to_string(), "user-b".to_string()),
]),
),
(
Layer::ProjectConfig,
BTreeMap::from([("b".to_string(), "project-b".to_string())]),
),
]);
let merged = merge_maps(&layers);
assert_eq!(merged.get("a").map(String::as_str), Some("user-a"));
assert_eq!(merged.get("b").map(String::as_str), Some("project-b"));
let provenance = merge_provenance(&layers);
assert_eq!(provenance.get("a"), Some(&Layer::UserConfig));
assert_eq!(provenance.get("b"), Some(&Layer::ProjectConfig));
}
#[test]
fn secret_refs_carry_no_value() {
let secret = SecretRef::new(Layer::UserConfig, "env:DASHSCOPE_API_KEY");
let debug = format!("{secret:?}");
assert!(debug.contains("env:DASHSCOPE_API_KEY"));
assert_eq!(secret.layer, Layer::UserConfig);
}
#[test]
fn layer_names_are_stable() {
assert_eq!(Layer::ManagedPolicy.name(), "policy");
assert_eq!(Layer::CliFlag.name(), "cli");
assert_eq!(Layer::SessionOverride.name(), "session");
assert_eq!(Layer::ProjectConfig.name(), "project");
assert_eq!(Layer::UserConfig.name(), "user");
assert_eq!(Layer::Default.name(), "default");
}
}
+4 -1
View File
@@ -142,7 +142,7 @@ pub enum TransportKind {
ModelAware,
/// ChatGPT Codex OAuth route.
Codex,
/// Google Antigravity consent-gated OAuth.
/// Retired Antigravity identity retained for legacy config inspection.
Antigravity,
/// Local runtime (Ollama / vLLM / SGLang).
LocalRuntime,
@@ -200,6 +200,9 @@ pub fn family_for(kind: ProviderKind) -> &'static str {
/// Auth methods declared for a provider kind. OAuth is a type, not an adapter.
#[must_use]
pub fn auth_methods_for(kind: ProviderKind) -> &'static [AuthMethod] {
if kind == ProviderKind::Antigravity {
return &[];
}
match kind.provider().credential_help().acquisition {
CredentialAcquisition::ApiKey => &[AuthMethod::API_KEY],
CredentialAcquisition::ApiKeyOrOAuth => &[AuthMethod::API_KEY, AuthMethod::OAUTH],
+19 -12
View File
@@ -76,6 +76,7 @@ impl ProvidersExport {
let offerings = bundled_catalog_offerings();
let mut routes: Vec<RouteExport> = provider::all_providers()
.iter()
.filter(|entry| entry.kind() != ProviderKind::Antigravity)
.map(|entry| {
let descriptor = ProviderDescriptor::for_kind(entry.kind());
route_export(&descriptor, &offerings)
@@ -152,17 +153,13 @@ pub fn parse_route_kind(value: &str) -> Option<ProviderKind> {
if trimmed.is_empty() {
return None;
}
if let Some(kind) = exact_config_identity(trimmed) {
return Some(kind);
}
let folded = trimmed.replace('_', "-");
if let Some(kind) = exact_config_identity(&folded) {
return Some(kind);
}
if let Some(kind) = clap_compat_alias(trimmed) {
return Some(kind);
}
ProviderKind::parse(trimmed).or_else(|| ProviderKind::parse(&folded.to_ascii_lowercase()))
exact_config_identity(trimmed)
.or_else(|| exact_config_identity(&folded))
.or_else(|| clap_compat_alias(trimmed))
.or_else(|| ProviderKind::parse(trimmed))
.or_else(|| ProviderKind::parse(&folded.to_ascii_lowercase()))
.filter(|kind| *kind != ProviderKind::Antigravity)
}
/// Exact id / config-table key only. Does not collapse dialect aliases onto
@@ -181,7 +178,6 @@ fn exact_config_identity(value: &str) -> Option<ProviderKind> {
fn clap_compat_alias(value: &str) -> Option<ProviderKind> {
let key = value.replace('_', "-").to_ascii_lowercase();
Some(match key.as_str() {
"agy" => ProviderKind::Antigravity,
"opencodego" | "opencode-go" => ProviderKind::OpencodeGo,
"ollama-cloud" => ProviderKind::OllamaCloud,
"mini-max-anthropic" => ProviderKind::MinimaxAnthropic,
@@ -213,9 +209,10 @@ mod tests {
let ids = export.route_ids();
let unique: std::collections::BTreeSet<_> = ids.iter().copied().collect();
assert_eq!(unique.len(), ids.len(), "route ids must be unique");
assert_eq!(ids.len(), provider::all_providers().len());
assert_eq!(ids.len(), provider::all_providers().len() - 1);
assert!(ids.contains(&"deepseek"));
assert!(ids.contains(&"custom"));
assert!(!ids.contains(&"antigravity"));
}
#[test]
@@ -248,6 +245,16 @@ mod tests {
parse_route_kind("mini-max-anthropic"),
Some(ProviderKind::MinimaxAnthropic)
);
assert_eq!(parse_route_kind("antigravity"), None);
assert_eq!(parse_route_kind("agy"), None);
assert_eq!(
ProviderKind::parse_config_identity("antigravity"),
Some(ProviderKind::Antigravity)
);
assert_eq!(
ProviderKind::parse_config_identity("agy"),
Some(ProviderKind::Antigravity)
);
assert_eq!(parse_route_kind(""), None);
assert_eq!(parse_route_kind("not-a-provider"), None);
}
@@ -2,7 +2,6 @@
# Source: ProviderDescriptor registry via ProvidersExport::from_registry.
# New ids may be appended (sorted); never recycle a retired spelling.
anthropic
antigravity
arcee
atlascloud
concentrate
@@ -41,24 +41,6 @@
],
"transport": "anthropic-messages"
},
{
"id": "antigravity",
"family": "antigravity",
"label": "Google Antigravity",
"endpoint": "https://cloudcode-pa.googleapis.com/v1internal",
"wire": "chat-completions",
"defaultModel": "gemini-3-pro-preview",
"envVars": [
"ANTIGRAVITY_API_KEY"
],
"auth": [
{
"kind": "oauth",
"label": "OAuth"
}
],
"transport": "antigravity"
},
{
"id": "arcee",
"family": "arcee",
+5
View File
@@ -148,6 +148,11 @@ impl RouteResolver {
// 1. Provider scope from explicit choice only; default otherwise.
// The provider is NEVER inferred from a model prefix.
let provider_kind = req.explicit_provider.unwrap_or_default();
if provider_kind == ProviderKind::Antigravity {
return Err(RouteError::InvalidProvider(
crate::LEGACY_ANTIGRAVITY_TOMBSTONE_MESSAGE.to_string(),
));
}
let descriptor = ProviderDescriptor::for_kind(provider_kind);
let provider_id = descriptor.id();
let default_offering = self.default_offering(&provider_id);
+16
View File
@@ -1257,6 +1257,22 @@ fn resolver_empty_string_selector_is_empty_model_error() {
assert!(matches!(out, Err(RouteError::EmptyModel)));
}
#[test]
fn resolver_rejects_retired_antigravity_before_minting_a_route() {
let error = RouteResolver::new()
.resolve(&req(Some(ProviderKind::Antigravity), Some("gemini-3-pro")))
.expect_err("the legacy tombstone must never produce an executable route");
let rendered = error.to_string();
assert!(matches!(error, RouteError::InvalidProvider(_)));
assert!(rendered.contains("non-runnable"), "{rendered}");
assert!(
rendered.contains("auth clear --provider antigravity"),
"{rendered}"
);
assert!(rendered.contains("google"), "{rendered}");
assert!(rendered.contains("GEMINI_API_KEY"), "{rendered}");
}
#[test]
fn resolver_empty_saved_provider_model_is_empty_model_error() {
// An empty selector from the saved-model fallback must be rejected too, not
+47
View File
@@ -1065,6 +1065,53 @@ pub const SETTINGS_SCHEMA: &[SettingDef] = &[
"ConfigHintFeatureExecPolicy",
),
),
// ── persisted field names without a row ─────────────────────────────
// Every field `Settings` serializes to settings.toml has a declaration;
// these have `ui: None` because their value is owned elsewhere:
// route pickers, `/set` aliases with their own canonical row, or
// internal one-way flags. Declaration order is render order, and hidden
// entries render nothing, so they live together at the end.
//
// Canonical names behind a `/set` alias row: `set()` accepts both
// spellings, the row carries the alias.
def("tool_collapse_mode", SettingKind::String, "compact", None),
def("max_input_history", SettingKind::Int, "100", None),
// Written by the route pickers, not by a settings row (the provider /
// model rows persist to config.toml via `set_config_value`).
def("default_provider", SettingKind::String, "", None),
// Trust posture with a `/set` entry point but no row; surfaced where
// sandboxing acts, not as a settings sentence.
def("sandbox_mode", SettingKind::String, "", None),
// Route memory written by the pickers: per-provider defaults, enabled
// chooser sets, and pinned routes. Structured values no row could edit.
def("provider_models", SettingKind::String, "", None),
def("enabled_models", SettingKind::String, "", None),
def("pinned_models", SettingKind::String, "", None),
// One-way internal flags: shown an intro, shown a deprecation, counted
// tip impressions. Read back to suppress repeats, never edited.
def(
"feature_intro_shown",
SettingKind::Bool(ON_OFF),
"false",
None,
),
def(
"yolo_deprecation_shown",
SettingKind::Bool(ON_OFF),
"false",
None,
),
// Round 3 work-bar placement migration ran once (`top` → `bottom`).
def(
"work_surface_bottom_migrated",
SettingKind::Bool(ON_OFF),
"false",
None,
),
def("behavioral_tip_impressions", SettingKind::String, "", None),
// Footer key-hint use counts: a hint retires to its bare state once its
// binding has been used enough times. Written by the footer, never edited.
def("footer_hint_uses", SettingKind::String, "", None),
];
/// The declaration for `key`, if the shell knows it.
+49 -18
View File
@@ -39,13 +39,13 @@ pub const SETUP_STATE_FILE_NAME: &str = "setup_state.json";
/// Version of the *telemetry notice content* — not the app version.
///
/// The notice is owed whenever
/// [`SetupState::telemetry_notice_decided_for`] does not match this string.
/// [`SetupState::needs_telemetry_notice`] reports that it has not been shown.
/// Bumping it re-shows the disclosure to prior acceptors and unanswered users,
/// so it is bumped only
/// when the collection policy, schema, or disclosure materially changes. Prior
/// declines remain off. Keying it to the app version would re-prompt every
/// release, which is nagging with extra steps.
pub const TELEMETRY_NOTICE_VERSION: &str = "3";
pub const TELEMETRY_NOTICE_VERSION: &str = "5";
/// Canonical setup step ids. The ordering matches the first-run spine so a
/// `BTreeMap<SetupStep, _>` renders in wizard order.
@@ -331,14 +331,9 @@ pub struct SetupState {
pub inherited: bool,
// ── Telemetry notice ────────────────────────────────────────────────
/// [`TELEMETRY_NOTICE_VERSION`] whose telemetry disclosure was shown.
/// `None` means the notice is still owed.
///
/// Never auto-completed and never deferred-completed: unlike the
/// constitution checkpoint, which records a `Deferred` completion on the
/// skip-onboarding path, a telemetry notice that was not rendered leaves
/// this `None`. Collection follows the documented default
/// while the notice remains owed on the next interactive launch.
/// [`TELEMETRY_NOTICE_VERSION`] whose telemetry processor disclosure was explicitly accepted or declined.
/// `None` means no explicit preference was recorded. Usage defaults on;
/// presenting a disclosure never changes these preference fields.
///
/// These are *fields* rather than a new [`SetupStep`] variant on purpose:
/// an unknown enum variant fails the whole record parse and silently drops
@@ -348,9 +343,13 @@ pub struct SetupState {
pub telemetry_notice_decided_for: Option<String>,
/// The privacy preference recorded with the notice. `false` with any
/// recorded notice version is a durable opt-out; `true` records that the
/// default-on disclosure was shown for that version.
/// processor disclosure was explicitly accepted for that version.
#[serde(default, skip_serializing_if = "is_false")]
pub telemetry_opt_in: bool,
/// Most recent telemetry policy disclosure actually presented locally.
/// This is display bookkeeping, never evidence of human acceptance.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub telemetry_notice_shown_for: Option<String>,
}
#[allow(clippy::trivially_copy_pass_by_ref)]
@@ -376,6 +375,7 @@ impl Default for SetupState {
inherited: false,
telemetry_notice_decided_for: None,
telemetry_opt_in: false,
telemetry_notice_shown_for: None,
}
}
}
@@ -489,12 +489,44 @@ impl SetupState {
/// count: the content changed, so the disclosure is owed again.
#[must_use]
pub fn needs_telemetry_notice(&self, version: &str) -> bool {
self.telemetry_notice_decided_for.as_deref() != Some(version)
self.telemetry_notice_shown_for.as_deref() != Some(version)
&& self.telemetry_notice_decided_for.as_deref() != Some(version)
}
/// Record presentation without inventing a privacy preference.
pub fn record_telemetry_notice_shown(&mut self, version: impl Into<String>) -> &mut Self {
self.telemetry_notice_shown_for = Some(version.into());
self
}
/// Update only telemetry metadata from the latest readable sidecar.
///
/// Disclosure bookkeeping and explicit preference writes share this lock:
/// a stale disclosure write must never erase a concurrently saved decline.
/// Missing state is fresh; corrupt state is never replaced with defaults.
pub fn update_telemetry_at(path: &Path, update: impl FnOnce(&mut Self)) -> Result<()> {
let parent = path.parent().context("setup-state path has no parent")?;
std::fs::create_dir_all(parent)?;
let file = std::fs::OpenOptions::new()
.create(true)
.truncate(false)
.read(true)
.write(true)
.open(path.with_extension("telemetry.lock"))?;
let mut lock = fd_lock::RwLock::new(file);
let _guard = lock.try_write()?;
let mut state = if path.try_exists()? {
Self::load_from(path).context("setup state could not be read")?
} else {
Self::default()
};
update(&mut state);
state.save_to(path)
}
/// Record the privacy preference associated with the telemetry notice.
///
/// Default-on may be recorded only after the notice was actually rendered;
/// Acceptance is recorded only after an explicit choice to share counts;
/// an explicit opt-out may also arrive from Settings. Deferral,
/// skip-onboarding, and non-interactive surfaces leave it untouched.
pub fn record_telemetry_notice(
@@ -507,10 +539,10 @@ impl SetupState {
self
}
/// True when the current default-on disclosure was shown and remains on.
/// True when the current processor disclosure was explicitly accepted.
#[must_use]
pub fn telemetry_accepted(&self, version: &str) -> bool {
!self.needs_telemetry_notice(version) && self.telemetry_opt_in
self.telemetry_notice_decided_for.as_deref() == Some(version) && self.telemetry_opt_in
}
/// True when the current notice record contains an explicit opt-out.
@@ -519,13 +551,12 @@ impl SetupState {
/// only an opt-out may be acted on destructively.
#[must_use]
pub fn telemetry_declined(&self, version: &str) -> bool {
!self.needs_telemetry_notice(version) && !self.telemetry_opt_in
self.telemetry_notice_decided_for.as_deref() == Some(version) && !self.telemetry_opt_in
}
/// Whether any recorded telemetry notice was explicitly declined.
///
/// Declines recorded by the former opt-in notice remain durable opt-outs
/// after telemetry becomes default-on. A notice-version bump may explain a
/// Declines recorded by any former notice remain durable opt-outs. A notice-version bump may explain a
/// changed policy, but it must never erase a user's earlier "no".
#[must_use]
pub fn telemetry_opted_out(&self) -> bool {
+98 -4
View File
@@ -2193,6 +2193,27 @@ fn list_values_fully_redacts_short_api_key() {
assert_eq!(values.get("api_key").map(String::as_str), Some("********"));
}
#[test]
fn redacted_toml_value_keeps_shape_but_not_secret_bytes() {
let mut config = ConfigToml {
api_key: Some("sk-deepseek-secret-value".to_string()),
model: Some("deepseek-v4-pro".to_string()),
..ConfigToml::default()
};
config.providers.openrouter.api_key = Some("openrouter-secret-value".to_string());
let value = config.redacted_toml_value();
let table = value.as_table().expect("dump renders a table");
let api_key = table
.get("api_key")
.and_then(toml::Value::as_str)
.expect("api_key keeps its slot");
assert!(!api_key.contains("secret"), "{api_key}");
let rendered = toml::to_string_pretty(&value).expect("dump serializes");
assert!(!rendered.contains("secret-value"), "{rendered}");
assert!(rendered.contains("deepseek-v4-pro"), "{rendered}");
}
#[test]
fn get_display_value_redacts_sensitive_keys() {
let mut config = ConfigToml {
@@ -5227,8 +5248,15 @@ fn provider_metadata_registry_covers_every_provider_kind_once() {
let providers = provider::all_providers();
// Full registry keeps legacy dialect/plan kinds for provider_for_kind.
assert_eq!(providers.len(), 48);
// Catalog surface is one identity per vendor (no dual-wire / plan rows).
assert_eq!(ProviderKind::ALL.len(), 43);
// Catalog surface is one identity per vendor (no dual-wire / plan rows),
// and never a retired tombstone: Antigravity stays in the full registry
// so old config parses and can be cleared, but it left `ALL` when it
// stopped being selectable (PRD §4.4 PROD-002).
assert_eq!(ProviderKind::ALL.len(), 42);
assert!(
!ProviderKind::ALL.contains(&ProviderKind::Antigravity),
"a tombstone must never be offered as a selectable provider"
);
assert!(ProviderKind::ALL.len() < providers.len());
let mut ids = std::collections::BTreeSet::new();
@@ -8939,8 +8967,7 @@ fn an_unconfigured_endpoint_resolves_to_the_shipped_default() {
"an unconfigured endpoint must resolve to the shipped default"
);
// …and the product default is anonymous usage counting on unless one of
// the documented kill switches says otherwise.
// The shipped usage preference is on without an acceptance prerequisite.
assert!(resolved.telemetry);
assert!(!resolved.telemetry_explicit_off);
}
@@ -9212,3 +9239,70 @@ fn telemetry_notice_fields_round_trip_and_stay_absent_when_unanswered() {
assert_eq!(legacy.telemetry_notice_decided_for, None);
assert!(!legacy.telemetry_opt_in);
}
#[test]
fn telemetry_disclosure_records_presentation_without_acceptance_or_erasing_old_declines() {
for version in [None, Some("1"), Some("4")] {
for enabled in [false, true] {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("setup_state.json");
let mut state = SetupState {
constitution_preview_version: 12,
..Default::default()
};
if let Some(version) = version {
state.record_telemetry_notice(version, enabled);
}
state.save_to(&path).unwrap();
SetupState::update_telemetry_at(&path, |latest| {
latest.record_telemetry_notice_shown(TELEMETRY_NOTICE_VERSION);
})
.unwrap();
let shown = SetupState::load_from(&path).unwrap();
assert!(!shown.needs_telemetry_notice(TELEMETRY_NOTICE_VERSION));
assert!(!shown.telemetry_accepted(TELEMETRY_NOTICE_VERSION));
assert!(!shown.telemetry_declined(TELEMETRY_NOTICE_VERSION));
assert_eq!(shown.telemetry_opted_out(), version.is_some() && !enabled);
assert_eq!(
shown.telemetry_notice_decided_for,
state.telemetry_notice_decided_for
);
assert_eq!(shown.telemetry_opt_in, state.telemetry_opt_in);
assert_eq!(shown.constitution_preview_version, 12);
}
}
}
#[test]
fn telemetry_metadata_update_refuses_corrupt_or_busy_state_and_reloads_the_saved_decline() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("setup_state.json");
std::fs::write(&path, "not-json").unwrap();
assert!(SetupState::update_telemetry_at(&path, |_| {}).is_err());
assert_eq!(std::fs::read_to_string(&path).unwrap(), "not-json");
SetupState::default().save_to(&path).unwrap();
let file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.open(path.with_extension("telemetry.lock"))
.unwrap();
let mut lock = fd_lock::RwLock::new(file);
let guard = lock.write().unwrap();
assert!(
SetupState::update_telemetry_at(&path, |state| {
state.record_telemetry_notice_shown(TELEMETRY_NOTICE_VERSION);
})
.is_err(),
"display bookkeeping must never block startup"
);
drop(guard);
SetupState::update_telemetry_at(&path, |state| {
state.record_telemetry_notice("4", false);
})
.unwrap();
SetupState::update_telemetry_at(&path, |state| {
state.record_telemetry_notice_shown(TELEMETRY_NOTICE_VERSION);
})
.unwrap();
assert!(SetupState::load_from(&path).unwrap().telemetry_opted_out());
}
+8 -8
View File
@@ -15,14 +15,14 @@ anyhow.workspace = true
chrono.workspace = true
serde.workspace = true
thiserror.workspace = true
codewhale-agent = { path = "../agent", version = "0.9.11" }
codewhale-config = { path = "../config", version = "0.9.11" }
codewhale-execpolicy = { path = "../execpolicy", version = "0.9.11" }
codewhale-hooks = { path = "../hooks", version = "0.9.11" }
codewhale-mcp = { path = "../mcp", version = "0.9.11" }
codewhale-protocol = { path = "../protocol", version = "0.9.11" }
codewhale-state = { path = "../state", version = "0.9.11" }
codewhale-tools = { path = "../tools", version = "0.9.11" }
codewhale-agent = { path = "../agent", version = "0.9.12" }
codewhale-config = { path = "../config", version = "0.9.12" }
codewhale-execpolicy = { path = "../execpolicy", version = "0.9.12" }
codewhale-hooks = { path = "../hooks", version = "0.9.12" }
codewhale-mcp = { path = "../mcp", version = "0.9.12" }
codewhale-protocol = { path = "../protocol", version = "0.9.12" }
codewhale-state = { path = "../state", version = "0.9.12" }
codewhale-tools = { path = "../tools", version = "0.9.12" }
regex = "1.11"
serde_json = { workspace = true, features = ["preserve_order"] }
tokio = { workspace = true, features = ["time"] }
+1 -1
View File
@@ -12,5 +12,5 @@ workspace = true
[dependencies]
anyhow.workspace = true
codewhale-protocol = { path = "../protocol", version = "0.9.11" }
codewhale-protocol = { path = "../protocol", version = "0.9.12" }
serde.workspace = true
+2 -2
View File
@@ -14,8 +14,8 @@ workspace = true
anyhow.workspace = true
async-trait.workspace = true
chrono.workspace = true
codewhale-protocol = { path = "../protocol", version = "0.9.11" }
codewhale-release = { path = "../release", version = "0.9.11" }
codewhale-protocol = { path = "../protocol", version = "0.9.12" }
codewhale-release = { path = "../release", version = "0.9.12" }
reqwest.workspace = true
serde.workspace = true
serde_json.workspace = true
+129 -8
View File
@@ -36,11 +36,13 @@
//! If no tokio runtime is available the event is dropped with a warning.
//! Webhook POSTs (`{"at": …, "event": …}`) are attempted after the local
//! append; failures are logged and dropped, never retried into the agent
//! loop.
//! loop. Process owners call [`LifecycleOutbox::flush`] before runtime teardown
//! to give queued terminal events a bounded opportunity to finish delivery.
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::time::Duration;
use anyhow::{Context, Result};
use chrono::Utc;
@@ -48,6 +50,7 @@ use codewhale_protocol::runtime::{RUNTIME_EVENT_ENVELOPE_SCHEMA_VERSION, Runtime
use serde_json::{Value, json};
use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
use tokio::sync::oneshot;
use crate::WebhookHookSink;
@@ -157,18 +160,41 @@ impl LifecycleOutbox {
let Some(inner) = self.inner.clone() else {
return;
};
if let Err(error) = inner.enqueue(event) {
if let Err(error) = inner.enqueue(OutboxCommand::Event(event)) {
tracing::warn!(target: "lifecycle_outbox", %error, "lifecycle event dropped");
}
}
/// Wait for events queued before this call to finish their delivery attempt.
///
/// Local writes are flushed before the writer acknowledges this barrier.
/// Delivery errors remain logged and best effort; a stalled writer/webhook
/// or a stopped runtime returns an error within `timeout` instead of holding
/// process teardown indefinitely. This does not close other cloned handles.
pub async fn flush(&self, timeout: Duration) -> Result<()> {
let Some(inner) = self.inner.as_ref() else {
return Ok(());
};
let (reply, completed) = oneshot::channel();
inner.enqueue(OutboxCommand::Flush(reply))?;
tokio::time::timeout(timeout, completed)
.await
.context("lifecycle outbox flush timed out; queued events may be incomplete")?
.context("lifecycle outbox writer stopped before flush completed")
}
}
enum OutboxCommand {
Event(LifecycleEvent),
Flush(oneshot::Sender<()>),
}
struct OutboxInner {
path: PathBuf,
webhook: Option<WebhookHookSink>,
sender: UnboundedSender<LifecycleEvent>,
sender: UnboundedSender<OutboxCommand>,
/// The writer task's receive half. Taken exactly once by the writer task.
receiver: Mutex<Option<UnboundedReceiver<LifecycleEvent>>>,
receiver: Mutex<Option<UnboundedReceiver<OutboxCommand>>>,
writer_spawned: AtomicBool,
/// Serializes the lazy writer-task spawn so two racing first emits cannot
/// start two writers.
@@ -180,9 +206,9 @@ impl OutboxInner {
///
/// Ordering: `send` happens before the spawn so events queued before the
/// writer starts are drained first, preserving enqueue order.
fn enqueue(self: &Arc<Self>, event: LifecycleEvent) -> Result<()> {
fn enqueue(self: &Arc<Self>, command: OutboxCommand) -> Result<()> {
self.sender
.send(event)
.send(command)
.map_err(|_| anyhow::anyhow!("lifecycle outbox writer task is gone"))?;
self.ensure_writer_spawned();
Ok(())
@@ -235,13 +261,20 @@ struct WriterState {
/// Next seq to assign; filled in by [`Self::recover_seq`] on first use.
next_seq: u64,
recovered: bool,
receiver: UnboundedReceiver<LifecycleEvent>,
receiver: UnboundedReceiver<OutboxCommand>,
}
impl WriterState {
/// Drain the queue until every sender is dropped, then exit.
async fn run(&mut self) {
while let Some(event) = self.receiver.recv().await {
while let Some(command) = self.receiver.recv().await {
let event = match command {
OutboxCommand::Event(event) => event,
OutboxCommand::Flush(reply) => {
let _ = reply.send(());
continue;
}
};
if let Err(error) = self.deliver(event).await {
tracing::warn!(
target: "lifecycle_outbox",
@@ -453,6 +486,94 @@ mod tests {
.collect()
}
#[test]
fn flush_persists_queued_turn_boundaries_before_runtime_teardown() {
let (_dir, path) = temp_outbox_path("shutdown.jsonl");
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("runtime");
runtime.block_on(async {
let outbox = LifecycleOutbox::new(Some(path.clone()), None, None);
outbox.emit(event("turn_start", "turn.started"));
let clone = outbox.clone();
clone.emit(event("turn_end", "turn.completed"));
outbox.flush(Duration::from_secs(2)).await.expect("flush");
});
drop(runtime);
let contents = std::fs::read_to_string(path).expect("outbox persisted before exit");
let lines: Vec<Value> = contents
.lines()
.map(|line| serde_json::from_str(line).expect("complete JSONL record"))
.collect();
assert_eq!(lines.len(), 2);
assert_eq!(lines[0]["event"], "turn_start");
assert_eq!(lines[0]["seq"], 1);
assert_eq!(lines[1]["event"], "turn_end");
assert_eq!(lines[1]["seq"], 2);
}
#[tokio::test]
async fn flush_disabled_or_unused_outbox_creates_no_file() {
let (_dir, path) = temp_outbox_path("unused.jsonl");
LifecycleOutbox::disabled()
.flush(Duration::ZERO)
.await
.expect("disabled flush");
LifecycleOutbox::new(Some(path.clone()), None, None)
.flush(Duration::from_secs(2))
.await
.expect("unused flush");
assert!(!path.exists());
}
#[tokio::test]
async fn flush_is_bounded_when_webhook_delivery_stalls() {
let (_dir, path) = temp_outbox_path("stalled.jsonl");
let server = wiremock::MockServer::start().await;
wiremock::Mock::given(wiremock::matchers::method("POST"))
.respond_with(wiremock::ResponseTemplate::new(200).set_delay(Duration::from_secs(5)))
.mount(&server)
.await;
let outbox = LifecycleOutbox::new(Some(path), Some(server.uri()), None);
outbox.emit(event("turn_start", "turn.started"));
let error = tokio::time::timeout(
Duration::from_secs(1),
outbox.flush(Duration::from_millis(20)),
)
.await
.expect("the flush deadline must bound a blocked writer")
.expect_err("stalled delivery must report incomplete drain");
assert!(error.to_string().contains("flush timed out"));
}
#[test]
fn flush_reports_a_writer_lost_with_its_runtime() {
let (_dir, path) = temp_outbox_path("stopped.jsonl");
let outbox = LifecycleOutbox::new(Some(path), None, None);
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("first runtime");
runtime.block_on(async {
outbox.emit(event("turn_start", "turn.started"));
outbox
.flush(Duration::from_secs(2))
.await
.expect("first flush");
});
drop(runtime);
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("second runtime");
let error = runtime
.block_on(outbox.flush(Duration::from_secs(2)))
.expect_err("a closed writer cannot acknowledge delivery");
assert!(error.to_string().contains("writer task is gone"));
}
#[tokio::test]
async fn appends_one_jsonl_line_per_event_with_envelope_schema() {
let (_dir, path) = temp_outbox_path("schema.jsonl");
+1 -1
View File
@@ -13,7 +13,7 @@ workspace = true
[dependencies]
anyhow.workspace = true
chrono.workspace = true
codewhale-config = { path = "../config", version = "0.9.11" }
codewhale-config = { path = "../config", version = "0.9.12" }
fd-lock = "4.0.4"
serde.workspace = true
serde_json.workspace = true
+24 -2
View File
@@ -21,9 +21,25 @@ use std::process::{Command, Stdio};
/// Accepts `npm`, `homebrew` (or `brew`), `cargo`, `omarchy`, and `binary`.
/// Anything else is ignored and detection falls back to automatic detection.
/// Packagers who relocate the binary somewhere the heuristics cannot read —
/// and users debugging a wrong guess — set this.
/// and users debugging a wrong guess — set this. Recognized managed paths
/// take precedence; `binary` cannot authorize overwriting a package-owned file.
pub const INSTALL_METHOD_ENV: &str = "CODEWHALE_INSTALL_METHOD";
/// Shared migration instructions for every runtime update surface. The new
/// directory avoids guessing ownership or overwriting a mixed installation.
pub const GITHUB_MIGRATION_HELP: &str = r#"Install the official GitHub release into a fresh user directory (macOS/Linux):
mkdir -p "$HOME/.local"
codewhale_install_dir="$(mktemp -d "$HOME/.local/codewhale-release.XXXXXX")"
curl -fsSL https://codewhale.net/install.sh | CODEWHALE_INSTALL_DIR="$codewhale_install_dir" sh
"$codewhale_install_dir/codewhale" --version
export PATH="$codewhale_install_dir:$PATH"
hash -r
command -v codewhale codew
Future updates: "$codewhale_install_dir/codewhale" update
Keep the chosen PATH directory in your shell profile after verifying it.
Windows: https://github.com/Hmbown/CodeWhale/releases/latest
PATH and migration: https://github.com/Hmbown/CodeWhale/blob/main/docs/INSTALL.md"#;
/// The package manager (if any) that owns the running executable.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum InstallMethod {
@@ -49,13 +65,19 @@ impl InstallMethod {
/// `Cellar` rather than in the manager's flat `bin` shim directory.
#[must_use]
pub fn detect(exe: &Path) -> Self {
let detected = Self::detect_with_omarchy_probe(exe, omarchy_package_owns);
// An override can identify a relocated package, but cannot authorize
// replacing a file that a known package manager owns.
if !detected.supports_self_update() {
return detected;
}
if let Some(forced) = std::env::var(INSTALL_METHOD_ENV)
.ok()
.and_then(|raw| Self::from_token(&raw))
{
return forced;
}
Self::detect_with_omarchy_probe(exe, omarchy_package_owns)
detected
}
fn detect_with_omarchy_probe(exe: &Path, owns_package: impl FnOnce(&Path) -> bool) -> Self {
+31 -7
View File
@@ -42,7 +42,10 @@ pub const DEEPSEEK_RELEASE_BASE_URL_ENV: &str = "DEEPSEEK_RELEASE_BASE_URL";
pub const CNB_MIRROR_ENV: &str = "CODEWHALE_USE_CNB_MIRROR";
/// Environment variable that pins the update target version.
pub const UPDATE_VERSION_ENV: &str = "DEEPSEEK_TUI_VERSION";
pub const UPDATE_VERSION_ENV: &str = "CODEWHALE_VERSION";
/// Pre-rebrand environment variable (alias for [`UPDATE_VERSION_ENV`]).
pub const LEGACY_TUI_UPDATE_VERSION_ENV: &str = "DEEPSEEK_TUI_VERSION";
/// Legacy environment variable (alias for [`UPDATE_VERSION_ENV`]).
pub const LEGACY_UPDATE_VERSION_ENV: &str = "DEEPSEEK_VERSION";
@@ -212,13 +215,20 @@ pub fn cnb_release_base_url(version: &str) -> String {
}
/// Returns the pinned update version from environment variables, or `None`
/// if neither `DEEPSEEK_TUI_VERSION` nor `DEEPSEEK_VERSION` is set.
/// if `CODEWHALE_VERSION` and its two legacy aliases are unset or empty.
pub fn update_version_from_env() -> Option<String> {
std::env::var(UPDATE_VERSION_ENV)
.ok()
.or_else(|| std::env::var(LEGACY_UPDATE_VERSION_ENV).ok())
.map(|value| value.trim().trim_start_matches('v').to_string())
.filter(|value| !value.is_empty())
[
UPDATE_VERSION_ENV,
LEGACY_TUI_UPDATE_VERSION_ENV,
LEGACY_UPDATE_VERSION_ENV,
]
.into_iter()
.find_map(|name| {
std::env::var(name)
.ok()
.map(|value| value.trim().trim_start_matches('v').to_string())
.filter(|value| !value.is_empty())
})
}
/// Joins a mirror base URL with an asset filename to produce a full download URL.
@@ -442,6 +452,7 @@ mod tests {
DEEPSEEK_RELEASE_BASE_URL_ENV,
CNB_MIRROR_ENV,
UPDATE_VERSION_ENV,
LEGACY_TUI_UPDATE_VERSION_ENV,
LEGACY_UPDATE_VERSION_ENV,
];
@@ -688,6 +699,19 @@ mod tests {
assert_eq!(update_version_from_env(), None);
}
#[test]
fn canonical_version_pin_outranks_both_preserved_legacy_aliases() {
let _env = ReleaseEnvGuard::clear();
set_release_env("CODEWHALE_VERSION", "v0.9.11");
set_release_env("DEEPSEEK_TUI_VERSION", "v0.9.10");
set_release_env("DEEPSEEK_VERSION", "v0.9.9");
assert_eq!(update_version_from_env().as_deref(), Some("0.9.11"));
set_release_env("CODEWHALE_VERSION", " ");
assert_eq!(update_version_from_env().as_deref(), Some("0.9.10"));
set_release_env("DEEPSEEK_TUI_VERSION", " ");
assert_eq!(update_version_from_env().as_deref(), Some("0.9.9"));
}
#[test]
fn update_network_fallback_hint_mentions_required_mirror_inputs() {
let hint = update_network_fallback_hint();
+1 -1
View File
@@ -11,7 +11,7 @@ description = "Secret storage backends for Codewhale, with OS keyring and file f
workspace = true
[dependencies]
codewhale-paths = { path = "../paths", version = "0.9.11" }
codewhale-paths = { path = "../paths", version = "0.9.12" }
chrono.workspace = true
serde = { workspace = true }
serde_json = { workspace = true }
+2 -2
View File
@@ -13,8 +13,8 @@ workspace = true
[dependencies]
anyhow.workspace = true
chrono.workspace = true
codewhale-paths = { path = "../paths", version = "0.9.11" }
codewhale-protocol = { path = "../protocol", version = "0.9.11" }
codewhale-paths = { path = "../paths", version = "0.9.12" }
codewhale-protocol = { path = "../protocol", version = "0.9.12" }
fd-lock = "4.0.4"
rusqlite.workspace = true
serde.workspace = true
+5 -5
View File
@@ -13,9 +13,9 @@ workspace = true
[dependencies]
anyhow.workspace = true
chrono.workspace = true
codewhale-config = { path = "../config", version = "0.9.11" }
codewhale-paths = { path = "../paths", version = "0.9.11" }
codewhale-release = { path = "../release", version = "0.9.11" }
codewhale-config = { path = "../config", version = "0.9.12" }
codewhale-paths = { path = "../paths", version = "0.9.12" }
codewhale-release = { path = "../release", version = "0.9.12" }
fd-lock = "4.0.4"
reqwest = { workspace = true, features = ["blocking"] }
serde.workspace = true
@@ -25,9 +25,9 @@ tracing.workspace = true
uuid.workspace = true
[build-dependencies]
codewhale-build-support = { path = "../build-support", version = "0.9.11" }
codewhale-build-support = { path = "../build-support", version = "0.9.12" }
[dev-dependencies]
# Used as an *assertion*, never as a filter: every string leaf of a serialized
# batch is run through `redact_for_disclosure` and must come back unredacted.
codewhale-workflow = { path = "../workflow", version = "0.9.11" }
codewhale-workflow = { path = "../workflow", version = "0.9.12" }
+39
View File
@@ -218,6 +218,7 @@ fn flush(context: &Context) -> FlushOutcome {
let batch = Batch {
schema_version: SCHEMA_VERSION,
notice_version: crate::event::NOTICE_VERSION,
sent_at: envelope::now_rfc3339(),
install_id: install.install_id,
app_version: context.app_version.clone(),
@@ -265,3 +266,41 @@ pub(crate) fn parse_events(lines: &[String]) -> Vec<Event> {
}
events
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn local_persistence_times_out_behind_a_stalled_writer() {
// Hold the receiver alive without consuming it: the writer cannot
// acknowledge the FIFO barrier. No sleep or worker scheduling race.
let (tx, rx) = channel();
let handle = Handle { tx };
handle.record(Event::SessionStart {
source: crate::SessionSource::Unknown,
});
let started = std::time::Instant::now();
assert_eq!(
handle.persist_local(crate::CLI_PERSIST_TIMEOUT),
FlushOutcome::TimedOut
);
assert!(started.elapsed() >= crate::CLI_PERSIST_TIMEOUT);
// A generous liveness watchdog, separate from the unchanged 250ms
// production budget, avoids timing a busy test scheduler as the writer.
assert!(started.elapsed() < Duration::from_secs(5));
assert!(matches!(rx.try_recv(), Ok(Message::Event(_))));
let Message::PersistLocal(ack) = rx.try_recv().expect("local persistence request") else {
panic!("short CLI persistence must not request a network shutdown flush");
};
assert!(
ack.send(FlushOutcome::Buffered).is_err(),
"a late acknowledgement must not keep the exiting caller alive"
);
assert!(
rx.try_recv().is_err(),
"the timeout must not enqueue a retry"
);
}
}
+12 -8
View File
@@ -10,7 +10,7 @@
use std::path::{Path, PathBuf};
use codewhale_config::{ResolvedRuntimeOptions, SetupState};
use codewhale_config::{ResolvedRuntimeOptions, SetupState, TelemetrySource};
use crate::buffer;
use crate::event::Surface;
@@ -214,7 +214,7 @@ pub fn decide(
/// A genuinely missing record is a fresh installation and therefore uses the
/// documented default. An existing record that cannot be read or parsed may
/// contain a durable decline, so it fails closed instead of being replaced by
/// a default-on value.
/// a fresh default.
#[must_use]
pub fn load_setup_state_for_decision() -> Option<SetupState> {
let path = SetupState::path().ok()?;
@@ -242,11 +242,10 @@ pub fn load_setup_state_for_decision_at(path: &Path) -> Option<SetupState> {
/// by the former opt-in notice.
/// 3. No resolvable home → `ForcedOff`.
/// 4. Endpoint configured but refused by [`validate_endpoint`] → `ForcedOff`.
/// 5. Otherwise `Enabled`.
/// 5. Otherwise `Enabled`, including an absent configuration preference.
///
/// The notice is only ever *rendered* on a TTY. The interactive TUI draws a
/// localized nonblocking disclosure before telemetry is armed; headless
/// surfaces use the same documented default and kill switches.
/// The disclosure is policy metadata, not a consent gate. Every armed surface
/// presents a startup disclosure; the TUI also has a localized notice.
pub fn decide_in_home(
home: Option<&Path>,
resolved: &ResolvedRuntimeOptions,
@@ -285,7 +284,12 @@ fn evaluate_in_home(
// 1. An explicit persistent "off" is an opt-out and wipes. Run-scoped or
// invalid-value false is only a kill switch and leaves disk alone.
if !resolved.telemetry {
if resolved.telemetry_explicit_off {
if resolved.telemetry_explicit_off
|| (resolved.telemetry_source == TelemetrySource::Default
&& setup.telemetry_opted_out())
{
// A run-scoped preference must not hide a durable decline in
// the sidecar when the config register was never written.
return TelemetryEvaluation::OptedOut(root);
}
return TelemetryEvaluation::ForcedOff;
@@ -297,7 +301,7 @@ fn evaluate_in_home(
return TelemetryEvaluation::OptedOut(root);
}
// 3. Nowhere to keep an install id or a buffer.
// Nowhere to keep an install id or a buffer.
let Some(root) = root else {
return TelemetryEvaluation::ForcedOff;
};
+100 -2
View File
@@ -33,7 +33,10 @@ use serde::{Deserialize, Serialize};
/// Wire schema version. Bumped on any field add, remove, or retype; never
/// reused. `crates/telemetry/tests/golden/v1.json` pins what v1 was.
pub const SCHEMA_VERSION: u32 = 1;
pub const SCHEMA_VERSION: u32 = 3;
/// Default-on policy disclosure version. This is not human consent.
pub const NOTICE_VERSION: u32 = 5;
/// Which product surface produced a batch.
///
@@ -56,6 +59,14 @@ pub enum Surface {
McpServer,
/// `codewhale serve`.
Serve,
/// Public website product interactions.
Website,
/// Browser application interactions.
WebApp,
/// Native desktop application interactions.
Desktop,
/// Control-plane application interactions.
ControlPlane,
}
impl Surface {
@@ -67,6 +78,10 @@ impl Surface {
Self::AppServer,
Self::McpServer,
Self::Serve,
Self::Website,
Self::WebApp,
Self::Desktop,
Self::ControlPlane,
];
/// The wire spelling.
@@ -79,6 +94,10 @@ impl Surface {
Self::AppServer => "app-server",
Self::McpServer => "mcp-server",
Self::Serve => "serve",
Self::Website => "website",
Self::WebApp => "web-app",
Self::Desktop => "desktop",
Self::ControlPlane => "control-plane",
}
}
}
@@ -449,6 +468,58 @@ impl Counters {
];
}
/// Closed aggregate product counters shared with browser and app clients.
/// Runtime sessions keep their existing counters and add no second collector.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ProductCounters {
/// Aggregate page view count.
pub page_view: u32,
/// Aggregate docs view count.
pub docs_view: u32,
/// Aggregate install copy count.
pub install_copy: u32,
/// Aggregate download count.
pub download: u32,
/// Aggregate signup count.
pub signup: u32,
/// Aggregate login count.
pub login: u32,
/// Aggregate session create count.
pub session_create: u32,
/// Aggregate session resume count.
pub session_resume: u32,
/// Aggregate turn submit count.
pub turn_submit: u32,
/// Aggregate turn complete count.
pub turn_complete: u32,
/// Aggregate settings open count.
pub settings_open: u32,
/// Aggregate integration connect count.
pub integration_connect: u32,
/// Aggregate error shown count.
pub error_shown: u32,
}
impl ProductCounters {
/// Closed wire field names.
pub const FIELDS: &'static [&'static str] = &[
"page_view",
"docs_view",
"install_copy",
"download",
"signup",
"login",
"session_create",
"session_resume",
"turn_submit",
"turn_complete",
"settings_open",
"integration_connect",
"error_shown",
];
}
/// Error counts for one session.
///
/// Every value is a count of a **variant discriminant**, never of an
@@ -565,6 +636,26 @@ pub enum Event {
/// this tree slices user and model text in dozens of places.
site: String,
},
/// Anonymous service health aggregates under explicit operator consent.
OperationsSummary {
/// Aggregate requests.
requests: u32,
/// Aggregate errors.
errors: u32,
/// Aggregate duration ms total.
duration_ms_total: u32,
/// Aggregate duration ms max.
duration_ms_max: u32,
/// Aggregate probes.
probes: u32,
/// Aggregate probes failed.
probes_failed: u32,
},
/// Aggregate browser or application interactions without a timeline.
ProductUsage {
/// Closed counts; never a URL, user identity, or work content.
counters: ProductCounters,
},
}
impl Event {
@@ -576,6 +667,8 @@ impl Event {
Self::SessionStart { .. } => "session_start",
Self::SessionEnd { .. } => "session_end",
Self::Panic { .. } => "panic",
Self::ProductUsage { .. } => "product_usage",
Self::OperationsSummary { .. } => "operations_summary",
}
}
@@ -597,7 +690,9 @@ impl Event {
#[must_use]
pub fn is_bounded(&self) -> bool {
match self {
Self::SessionStart { .. } => true,
Self::SessionStart { .. }
| Self::ProductUsage { .. }
| Self::OperationsSummary { .. } => true,
Self::InstallOrUpgrade {
previous_version, ..
} => previous_version
@@ -679,6 +774,8 @@ pub fn is_known_provider_id(value: &str) -> bool {
pub struct Batch {
/// [`SCHEMA_VERSION`].
pub schema_version: u32,
/// [`NOTICE_VERSION`], policy metadata rather than proof of acceptance.
pub notice_version: u32,
/// RFC3339 UTC, second precision. Per-**batch** only — events carry no
/// timestamps at all.
pub sent_at: String,
@@ -707,6 +804,7 @@ impl Batch {
/// Envelope field names in declaration order, for the doc-match test.
pub const FIELDS: &'static [&'static str] = &[
"schema_version",
"notice_version",
"sent_at",
"install_id",
"app_version",
+4 -2
View File
@@ -1,4 +1,4 @@
//! Default-on, user-disableable anonymous product usage counting for Codewhale.
//! Default-on, user-disableable aggregate product usage counting for Codewhale.
//!
//! The whole of what this crate may ever send is [`event`]. The whole of what
//! decides whether it may send anything is [`decision`]. Nothing else in the
@@ -58,7 +58,7 @@ pub use decision::{
pub use envelope::reduce_panic_site;
pub use event::{
Arch, Batch, ColdStartBucket, Counters, DurationBucket, Errors, Event, ExitClass, InstallKind,
Libc, Os, SCHEMA_VERSION, SessionSource, Surface, TurnWall,
Libc, NOTICE_VERSION, Os, ProductCounters, SCHEMA_VERSION, SessionSource, Surface, TurnWall,
};
/// How long the shutdown flush may hold the process.
@@ -110,6 +110,8 @@ pub fn init(consent: TelemetryConsent) {
return;
}
notice::show_startup_disclosure(consent.surface());
let context = actor::Context {
root: root.clone(),
endpoint: consent.endpoint().map(str::to_string),
+37 -5
View File
@@ -7,15 +7,14 @@
//!
//! Two properties of the wording are deliberate and load-bearing:
//!
//! 1. **The default is stated plainly and the opt-out is immediate.** The
//! native TUI starts on the yes choice and makes the opt-out equally
//! reachable.
//! 1. **Usage defaults on with durable opt-out.** Disclosure presentation
//! records only that it was shown, never fictional human acceptance.
//! 2. **The red lines are stated as "not collected", not as "anonymized".**
//! Sampling and hashing are not the same promise, and a notice that implies
//! them when neither is true is worse than no notice.
/// Headline shown above [`NOTICE_BODY`].
pub const NOTICE_HEADLINE: &str = "Help improve Codewhale?";
pub const NOTICE_HEADLINE: &str = "Codewhale usage reporting";
/// The notice itself.
///
@@ -33,4 +32,37 @@ You are identified only by a random ID stored on this machine, replaced
every 90 days. Change your mind any time:
codewhale config set telemetry false
Full schema, field by field: docs/TELEMETRY.md";
Full schema, field by field: docs/TELEMETRY.md
Usage reporting is on by default. Codewhale and PostHog process these
counts when delivery is configured. No IP is collected.";
/// Concise disclosure for every armed runtime surface, including headless CLI.
pub const STARTUP_DISCLOSURE: &str = "Usage reporting is on by default: Codewhale and PostHog process aggregate version/platform, session, feature and error counts when delivery is configured. No content or IP. Turn off: codewhale config set telemetry false. Details: codewhale config telemetry";
/// Present the policy once per revision without recording human acceptance.
/// Failure to save the display marker only causes a later repeat disclosure.
/// The interactive TUI draws its own localized notice and records its own
/// presentation, so it never gets a stray stderr line before the first frame.
pub(crate) fn show_startup_disclosure(surface: crate::event::Surface) {
if surface == crate::event::Surface::Tui {
return;
}
let Ok(path) = codewhale_config::SetupState::path() else {
return;
};
let Some(state) = crate::load_setup_state_for_decision_at(&path) else {
return;
};
if state.telemetry_opted_out()
|| !state.needs_telemetry_notice(codewhale_config::TELEMETRY_NOTICE_VERSION)
{
return;
}
use std::io::Write;
if writeln!(std::io::stderr().lock(), "{STARTUP_DISCLOSURE}").is_ok() {
let _ = codewhale_config::SetupState::update_telemetry_at(&path, |latest| {
latest.record_telemetry_notice_shown(codewhale_config::TELEMETRY_NOTICE_VERSION);
});
}
}
+60 -14
View File
@@ -158,6 +158,20 @@ fn every_event() -> Vec<Event> {
Event::Panic {
site: "crates/tui/src/tui/ui.rs:8801:17".to_string(),
},
Event::ProductUsage {
counters: ProductCounters {
page_view: 1,
..ProductCounters::default()
},
},
Event::OperationsSummary {
requests: 10,
errors: 1,
duration_ms_total: 1500,
duration_ms_max: 300,
probes: 2,
probes_failed: 0,
},
]
}
@@ -165,11 +179,12 @@ fn every_event() -> Vec<Event> {
pub(crate) fn every_field_batch() -> Batch {
Batch {
schema_version: SCHEMA_VERSION,
notice_version: NOTICE_VERSION,
sent_at: "2026-08-03T18:04:11Z".to_string(),
install_id: "3f2a9c1e-0000-4000-8000-000000000001".to_string(),
app_version: "0.9.4".to_string(),
git_sha: Some("abcdef012345".to_string()),
surface: Surface::Tui,
surface: Surface::ControlPlane,
os: Os::Macos,
arch: Arch::Aarch64,
libc: Libc::None,
@@ -557,7 +572,7 @@ fn decision_matrix_is_exhaustive() {
let home = temp_home();
let path = home.path();
// Row: nobody has said anything. Anonymous usage counting is default-on.
// Row: nobody has said anything. No acceptance is inferred.
assert!(matches!(
decide_in_home(
Some(path),
@@ -579,7 +594,7 @@ fn decision_matrix_is_exhaustive() {
TelemetryDecision::OptedOut
));
// Row: on, notice not yet shown. Headless/default-on still works.
// Row: config on with no notice record also uses the default-on policy.
assert!(matches!(
decide_in_home(
Some(path),
@@ -601,8 +616,7 @@ fn decision_matrix_is_exhaustive() {
TelemetryDecision::OptedOut
));
// Row: on, but the notice content changed since they answered yes. A
// disclosure refresh does not pause usage counting.
// Row: old explicit yes remains on under the current default-on policy.
assert!(matches!(
decide_in_home(
Some(path),
@@ -674,7 +688,7 @@ fn decision_matrix_is_exhaustive() {
*surface
)
.is_enabled(),
"{surface:?} must be able to emit by default"
"{surface:?} uses default-on without inferring acceptance"
);
}
}
@@ -760,6 +774,35 @@ fn only_opt_out_touches_disk() {
assert!(!buffer::state_path(&root).exists());
}
#[test]
fn default_on_does_not_hide_a_durable_sidecar_decline() {
let home = temp_home();
let root = root_of(&home);
buffer::ensure_dir(&root).expect("create root");
let before = seed_consenting_home(&root);
let mut options = resolved(false, false, None);
// A run-scoped kill switch still preserves the preexisting ordering.
options.telemetry_source = codewhale_config::TelemetrySource::Env;
assert!(matches!(
decide_in_home(Some(home.path()), &options, &declined_setup(), Surface::Tui),
TelemetryDecision::ForcedOff
));
assert_eq!(snapshot(&root), before);
// Once that temporary switch is gone, the durable decline must wipe even
// though the new shipped configuration preference is on.
options.telemetry = true;
options.telemetry_source = codewhale_config::TelemetrySource::Default;
assert!(matches!(
decide_in_home(Some(home.path()), &options, &declined_setup(), Surface::Tui),
TelemetryDecision::OptedOut
));
assert!(buffer::tombstone_present(&root));
assert!(!buffer::install_id_path(&root).exists());
assert!(buffer::read_lines(&buffer::buffer_path(&root)).is_empty());
}
#[test]
fn the_tombstone_outlives_every_run_the_opt_out_covers() {
// `docs/TELEMETRY.md` says the opt-out's tombstone survives, and an
@@ -1499,7 +1542,7 @@ fn no_public_api_accepts_a_bare_bool() {
decide_in_home(
Some(home.path()),
&resolved(true, false, None),
&SetupState::default(),
&accepted_setup(),
Surface::Cli
)
.is_enabled()
@@ -1509,7 +1552,7 @@ fn no_public_api_accepts_a_bare_bool() {
// ------------------------------------------------- docs and code are welded --
const TELEMETRY_DOC: &str = include_str!("../../../docs/TELEMETRY.md");
const GOLDEN_V1: &str = include_str!("../tests/golden/v1.json");
const GOLDEN_V3: &str = include_str!("../tests/golden/v3.json");
/// Extract the fenced ```jsonc blocks from the schema doc, in order.
fn jsonc_blocks(doc: &str) -> Vec<String> {
@@ -1635,7 +1678,7 @@ fn event_field_names_match_documented_schema() {
let blocks = jsonc_blocks(TELEMETRY_DOC);
assert_eq!(
blocks.len(),
5,
7,
"expected one jsonc block for the envelope and one per event variant; \
a parse miss must fail rather than silently pass"
);
@@ -1715,8 +1758,9 @@ fn event_field_names_match_documented_schema() {
}
#[test]
fn golden_payload_v1() {
// `crates/telemetry/tests/golden/v1.json` is one fully-populated instance of
fn golden_payload_v3() {
assert_eq!(NOTICE_VERSION.to_string(), TELEMETRY_NOTICE_VERSION);
// `crates/telemetry/tests/golden/v3.json` is one fully-populated instance of
// the envelope and every event. Any field add, remove, or retype fails here
// until the developer re-blesses it under a bumped `SCHEMA_VERSION` — and it
// is also the artifact a future receiver author reads to know exactly what
@@ -1732,15 +1776,15 @@ fn golden_payload_v1() {
rendered.push('\n');
if std::env::var("CODEWHALE_BLESS_TELEMETRY_GOLDEN").is_ok() {
let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/golden/v1.json");
let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/golden/v3.json");
std::fs::create_dir_all(path.parent().expect("parent")).expect("create golden dir");
std::fs::write(&path, &rendered).expect("write golden");
return;
}
assert_eq!(
rendered, GOLDEN_V1,
"the v1 payload changed; bump SCHEMA_VERSION and re-bless the golden file"
rendered, GOLDEN_V3,
"the v3 payload changed; bump SCHEMA_VERSION and re-bless the golden file"
);
}
@@ -1809,6 +1853,8 @@ fn the_notice_summarizes_what_the_schema_collects_and_states_every_red_line() {
"aggregate feature and error counters",
"random ID stored on this machine",
"every 90 days",
"on by default",
"PostHog",
] {
assert!(
flat.contains(&claim.split_whitespace().collect::<String>()),
+91
View File
@@ -0,0 +1,91 @@
{
"schema_version": 2,
"consent_version": 4,
"sent_at": "2026-08-03T18:04:11Z",
"install_id": "3f2a9c1e-0000-4000-8000-000000000001",
"app_version": "0.9.4",
"git_sha": "abcdef012345",
"surface": "control-plane",
"os": "macos",
"arch": "aarch64",
"libc": "none",
"tty": true,
"events": [
{
"event": "install_or_upgrade",
"kind": "upgrade",
"previous_version": "0.9.3-rc.1"
},
{
"event": "session_start",
"source": "resume"
},
{
"event": "session_end",
"duration_bucket": "1m_10m",
"exit_class": "panic",
"cold_start_bucket": "250_1000",
"providers": [
"custom",
"deepseek"
],
"counters": {
"turns": 14,
"tool_calls": 61,
"fleet_dispatch": 0,
"workflow_run": 0,
"subagent_spawn": 2,
"mcp_server_connected": 0,
"memory_search": 0,
"approval_modal_shown": 0,
"approval_auto_allowed": 0,
"command_palette_open": 3
},
"errors": {
"auth_preflight_failed": 0,
"provider_http_4xx": 0,
"provider_http_5xx": 1,
"tool_denied_by_policy": 0,
"tool_timeout": 0,
"network_error": 0
},
"turn_wall": {
"lt_5s": 9,
"5_30s": 4,
"30_120s": 1,
"gte_120s": 0
}
},
{
"event": "panic",
"site": "crates/tui/src/tui/ui.rs:8801:17"
},
{
"event": "product_usage",
"counters": {
"page_view": 1,
"docs_view": 0,
"install_copy": 0,
"download": 0,
"signup": 0,
"login": 0,
"session_create": 0,
"session_resume": 0,
"turn_submit": 0,
"turn_complete": 0,
"settings_open": 0,
"integration_connect": 0,
"error_shown": 0
}
},
{
"event": "operations_summary",
"requests": 10,
"errors": 1,
"duration_ms_total": 1500,
"duration_ms_max": 300,
"probes": 2,
"probes_failed": 0
}
]
}
+91
View File
@@ -0,0 +1,91 @@
{
"schema_version": 3,
"notice_version": 5,
"sent_at": "2026-08-03T18:04:11Z",
"install_id": "3f2a9c1e-0000-4000-8000-000000000001",
"app_version": "0.9.4",
"git_sha": "abcdef012345",
"surface": "control-plane",
"os": "macos",
"arch": "aarch64",
"libc": "none",
"tty": true,
"events": [
{
"event": "install_or_upgrade",
"kind": "upgrade",
"previous_version": "0.9.3-rc.1"
},
{
"event": "session_start",
"source": "resume"
},
{
"event": "session_end",
"duration_bucket": "1m_10m",
"exit_class": "panic",
"cold_start_bucket": "250_1000",
"providers": [
"custom",
"deepseek"
],
"counters": {
"turns": 14,
"tool_calls": 61,
"fleet_dispatch": 0,
"workflow_run": 0,
"subagent_spawn": 2,
"mcp_server_connected": 0,
"memory_search": 0,
"approval_modal_shown": 0,
"approval_auto_allowed": 0,
"command_palette_open": 3
},
"errors": {
"auth_preflight_failed": 0,
"provider_http_4xx": 0,
"provider_http_5xx": 1,
"tool_denied_by_policy": 0,
"tool_timeout": 0,
"network_error": 0
},
"turn_wall": {
"lt_5s": 9,
"5_30s": 4,
"30_120s": 1,
"gte_120s": 0
}
},
{
"event": "panic",
"site": "crates/tui/src/tui/ui.rs:8801:17"
},
{
"event": "product_usage",
"counters": {
"page_view": 1,
"docs_view": 0,
"install_copy": 0,
"download": 0,
"signup": 0,
"login": 0,
"session_create": 0,
"session_resume": 0,
"turn_submit": 0,
"turn_complete": 0,
"settings_open": 0,
"integration_connect": 0,
"error_shown": 0
}
},
{
"event": "operations_summary",
"requests": 10,
"errors": 1,
"duration_ms_total": 1500,
"duration_ms_max": 300,
"probes": 2,
"probes_failed": 0
}
]
}
+1 -1
View File
@@ -13,7 +13,7 @@ workspace = true
[dependencies]
anyhow.workspace = true
async-trait.workspace = true
codewhale-protocol = { path = "../protocol", version = "0.9.11" }
codewhale-protocol = { path = "../protocol", version = "0.9.12" }
serde.workspace = true
serde_json.workspace = true
thiserror.workspace = true
+118 -127
View File
@@ -7,16 +7,56 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.9.12] - 2026-09-04
Codewhale v0.9.12 puts computer use in the binary, opens two new routes —
Alibaba Model Studio through the data-driven provider table and Concentrate
as an opt-in BYOK Responses gateway — and adds cloud dispatch plus a
config-gated per-session control socket. The shell is the other half of the
release: a new launch card, the work surface docked under the composer, one
focus owner for Tab, tool cells that carry their own state, and `fleet` as
the public word for the live collective. The complete item-level change
record is retained below the categorized release highlights.
### Added
- Computer use ships with the binary. The `computer-use` plugin — 38 tools
across macOS, Windows, Linux and HarmonyOS, accessibility-first observation
with pixel fallback, screenshots, zoom, screen recording, and registered
remote computers over ssh and hdc — is embedded in Codewhale and written to
`$CODEWHALE_HOME/builtin-plugins` on first run, so every install channel
carries it. It lists as `builtin · not-reviewed` and stays disabled until
you review and enable it: shipping it is not consenting to it. The
stdlib-Python computer-use server it replaces is gone.
- Alibaba Model Studio joins the data-driven provider table as an
`openai-compatible` descriptor: international compatible-mode endpoint,
`DASHSCOPE_API_KEY` credential, live `/v1/models` discovery. Qwen 3.8
Flash and Qwen 3.8 Max arrive through the catalog authority — never a
hard-coded id.
- Concentrate: first-class opt-in BYOK Responses gateway with live models
discovery and typed SSE streaming (#5725).
- Cloud dispatch: remote runner offloads coding agent tasks to isolated
cloud sandboxes with machine token auth and structured job tracking
(#5701, #5712).
- Per-session control socket: config-gated `[control_socket]` table binds
`<sessions-dir>/<session-id>/control.sock` per running session, exposing
message, interrupt, relaunch, and status JSON-RPC verbs (#5533, #5831).
### Changed
- **Anonymous usage counting is on by default.** The 0.9.11 release asked
first; 0.9.12 counts the same aggregate version/platform, session, feature
and error totals unless you turn it off, and says so once at first launch
(policy notice version 5, schema 3, `notice_version` replacing
`consent_version`). Every recorded opt-out stays off: a durable
`telemetry = false`, a decline recorded under the old opt-in notice,
unreadable privacy state, and the `CODEWHALE_TELEMETRY=0` / `--telemetry
false` kill switches. Showing the disclosure records only that it was
shown, never an acceptance. `codewhale config set telemetry false` turns
it off and wipes queued counts; `codewhale config set telemetry true`
deliberately re-enables it. Nothing new is collected: no conversations,
code, prompts, files, names, model content, credentials, or IP.
- The launch screen is our own card take: a thin top line
`⑂ branch path`; a centred bordered card with the whale mark,
`Codewhale` + version, one announcement line only when it is true (the
@@ -24,7 +64,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
session / Changelog / Quit with their real chords right-aligned. Enter
runs the highlighted entry, Up/Down move it, and typing goes straight to
the composer. The card dissolves on the first keystroke or command
(≤240 ms, instant under reduced motion).
(≤240 ms, instant under reduced motion) (#5826, #5801, #5815, #5286).
- The work surface sits under the composer by default, keeping history
readable and leaving the stage unencumbered (#5809).
- Skills command shapes: FEAT-022 command shapes and retained-host
validation (#5825, #5829).
- After the card dissolves, the working screen shows `⑂ branch path` with
`⋮ MCP n/m` on the right, the transcript starts with the
`◆ session_start` receipt (naming the configured session-start hooks),
@@ -101,12 +145,83 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- TUI/CLI: Pod is the public roster surface. User-facing Fleet wording
moves to Pod; durable receipt keys stay compatible (#5776).
### Contributors
- **hexin ([@h3c-hexin](https://github.com/h3c-hexin))** — provider-native web
search across four routes (#5682, #5683, #5685, #5687), authoritative
edit-last-turn boundaries (#5621), Kimi Code k3-256k (#5622),
post-compaction input-token reporting (#5623), and preserving the scheduled
model selection in automations (#5650).
- **秋月凉梦 ([@qiuYliangM](https://github.com/qiuYliangM))** — co-authored the
edit-last-turn boundary fix (#5621), Kimi Code k3-256k support (#5622), and
post-compaction input-token reporting (#5623).
- **Isabel Wu ([@wuisabel-gif](https://github.com/wuisabel-gif))** — live
session token totals (#5624), persisted context-pressure warnings (#5629),
discoverable Fleet roster editing (#5604), the capability-gated cursor
accent (#5599), and `/copy` for the latest completed response (#5692).
- **Paulo Aboim Pinto ([@aboimpinto](https://github.com/aboimpinto))** —
Windows verbatim-path operands preserved through POSIX word splitting
(#5610), the plugins group moved onto the command shapes (#5657), FEAT-022
skills command shapes with retained-host validation (#5825), and FEAT-020
plugin command shapes re-landed on main (#5865).
- **Alex Musichen ([@musichen](https://github.com/musichen))** — a stable
DeepSeek heading in the configured-view model picker, keeping every official
catalog model for the active provider visible (#5689).
- **[@gaord](https://github.com/gaord)** — the `GET /v1/fleet/profiles`
runtime API endpoint, reusing the FleetManager validation path (#5688).
- **Sh1Zuku ([@SparkofSpike](https://github.com/SparkofSpike))** — corrected
English documentation inaccuracies and the first zh_hans translations for
the Tier-2 docs (#5613).
- **[@M-Maciej](https://github.com/M-Maciej)** — goal continuation cadence
(#5591) and the per-session control socket (#5533, #5831).
- **Serephus ([@serephus](https://github.com/serephus))** — nixpkgs update
(#5669).
- **[@whp233](https://github.com/whp233)** — `wire = responses|anthropic` for
openai-compatible custom routes and opencode-zen muse-spark (#5716, landed
as #5719).
- **Gabriel Degret ([@Gabriel-Degret](https://github.com/Gabriel-Degret))** —
found the reasoning-only retry gap and built the first fix; landed as the
`[reasoning_only]` retry ceiling with a request-scoped nudge (#5867).
- **[@huangxianzhan](https://github.com/huangxianzhan)** — the
`x-opencode-session` header for OpenCode Go and Zen gateways (#5868).
- **[@zhuowp](https://github.com/zhuowp)** — task origin preserved in job
snapshots (#5869).
- **AdityaG ([@AdityaVG13](https://github.com/AdityaVG13))** — a ten-commit
performance pass: a zero-copy LaTeX fast path for streaming render,
single-pass token accounting on the per-turn pressure paths, memoized
provider resolution in the catalog cutline, parsing the bundled and
on-disk models.dev catalogs once per process (interactive boot -70%),
adaptive poll cadence for foreground shell completion, and worker caps
across the read-only diagnostic family.
- **Nightt ([@nightt5879](https://github.com/nightt5879))** — isolated remote
recovery lease generations (#5790).
- **[@yiheng-kkk](https://github.com/yiheng-kkk)** — replaced stale todo
transcript snapshots so a rewritten todo list renders its current tasks
(#5871, #5873).
- **[@Lfanxing](https://github.com/Lfanxing)** — Moonshot routes now degrade
incompatible tool definitions per request instead of failing the turn.
- **WissssleyL ([@Lstarsky0](https://github.com/Lstarsky0))** — moved
`docs/subagents` and `docs/mcp` onto the dictionary spine (#5337), with the
metaTitle probe that keeps them there.
Reports and reproductions that shaped this release:
- **[@slowly247](https://github.com/slowly247)** — reported the Ollama input
budget collapsing to 1,024 tokens on 32K local models (#5820).
- **[@ronohara](https://github.com/ronohara)** — reported the engine stopping
after recoverable network errors, with the reproduction that pinned the
discarded approval (#5769).
- **[@Lujc0523](https://github.com/Lujc0523)** — asked for ACP session
configuration of mode and model (#5863).
- **[@senka9h](https://github.com/senka9h)** — reported that `serve --acp`
lacked `session/list` and `session/load` (#5864).
### Added
- Native ChatGPT sign-in for the `openai-codex` route: `codewhale auth
chatgpt` opens a browser PKCE flow and stores refreshable tokens in
Codewhale-owned credentials — no Codex CLI install required.
`/auth chatgpt-revoke` clears them off the event loop (#5784).
`/auth chatgpt-revoke` clears them off the event loop (#5784, #5778).
- MCP servers and plugins can be connected self-serve from the session: a
unified auth flow with rotation-safe token handling, a spoken authorization
URL, and catalog refresh when stored credentials stop working (#5747).
@@ -136,7 +251,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Compaction publishes a structured survival contract for session-tree
journal entry types (`crates/tui/src/compaction/SURVIVAL_CONTRACT.md`) and
fails closed when the last user round, tool results, `/anchor` text, or
checkpoint receipt would vanish (#4394).
checkpoint receipt would vanish (#4394, #5782).
- Internal: `codewhale-config` gains `RouteAuthoritySnapshot`, one immutable
authority that owns a compiled provider catalog together with the route
resolver projected from it, so a picker, a readiness view, and an execution
@@ -5186,130 +5301,6 @@ reproductions shaped v0.9.0:
unused execpolicy amend module, and dead MCP/client retry helpers.
- Retired the deprecated `WHALE.md` context fallback (#3798).
## [0.8.66] - 2026-06-29
### Added
- Added `codewhale doctor` / `codewhale doctor --json` legacy-state
diagnostics that compare known `~/.deepseek` state paths with their
`~/.codewhale` counterparts and flag unmigrated or dual-root data (#3727).
- Added Sakana AI Fugu as a first-class OpenAI-compatible provider with
`sakana`/`fugu` aliases, `FUGU_API_KEY` / `SAKANA_API_KEY` discovery,
provider-picker wiring, model completions, and provider docs. Harvested from
#3748 by @lerugray.
- Added WhaleFlow-to-Fleet launch-shape validation: the default Fleet workflow
contract allows up to 100 total agents and 5 recursive rings, requires
bounded loops/expands before launch, and preserves per-slot model selection.
- Added a read-only `/config ask-rules` view for the resolved
`permissions.toml` path, file status, rule count, and configured
tool/command/path ask rules. Merged from #3569 by @greyfreedom.
- Added provider-level `context_window` overrides so OpenAI-compatible
gateways and self-hosted providers can budget against their real model
context window (#3545).
- Added the native `codew` shim to release archives, Windows installer inputs,
local release-asset preparation, and checksum verification so manual installs
receive the same short command that Cargo installs build.
- Added OpenModel as a first-class Anthropic Messages provider, with config,
CLI, provider picker, docs, and registry coverage. Harvested from #3585 by
@noaft.
- Added WeCom Bridge deployment and security documentation, with shipped
runtime/bridge commands and approval-timeout environment guidance. Harvested
from #3640 by @pkeging.
- Added a token/cache/cost `scorecard` command for offline release gating,
baseline regression checks, and per-turn cost visibility (#3388). Stream-JSON
exec metadata now also reports conservative `input_analysis` and
`visible_final_answer_chars`, so benchmark harnesses can measure transcript
growth and final-answer bloat without guessing (#2956, #2957).
- Added a release evidence ledger for v0.8.66 and opened the external ACP
registry submission for CodeWhale after validating the published
`[email protected]` ACP auth handshake against the upstream registry checker
(#3192).
- Added a typed `[verifier]` config table for the verifier-preview lane, with
`enabled` and the shipped `verdict_policy = "hunt"` mapping documented and
validated (#2093).
- Added Hotbar `Alt+1`–`Alt+8` quick-slot switching with decision-card key
disambiguation, plus an introductory card that explains and can dismiss the
Hotbar (#3796, #3788).
- Release/docs hygiene: guarded public install/version snippets and the npm
`codewhaleBinaryVersion` pointer against drift, made `check-docs`/`check-facts`
fail on stale snippets or unmapped providers, and stopped `sync-changelog`
from dropping a release when only `[Unreleased]` exists (#3767, #3768, #3769,
#3770, #3771, #3772).
### Changed
- Deferred Auto mode from the user-facing mode picker, cycle, hotbar, `/mode`
command, and runtime-thread mode overrides until it has a distinct prompt and
auto-review behavior; existing `auto` mode text now folds back to Agent
instead of selecting a hollow mode, and approval modal copy no longer implies
the current mode is YOLO (#3730, #3733).
- Clarified the Fleet setup surface and docs so Fleet is treated as the durable
sub-agent configuration layer while WhaleFlow is the agent-authored
orchestration plan that selects and monitors Fleet slots.
- Slimmed the default Constitution prompt while keeping its required structural
anchors under regression coverage, reducing the static prompt footprint for
cache-sensitive turns (#2953).
- Made the approval prompt inline and bottom-anchored instead of a full-screen
takeover, so context and controls stay visible while a tool awaits a decision
(#3799).
- The Hotbar is now hidden by default until explicit setup opt-in (#3807); the
interactive Agent shell also defaults to approval-gated on with a shared
baseline (#3756).
- Mode authority now resolves approval prompts through a single authority
source instead of per-surface checks (#3795).
### Fixed
- Surfaced legacy state relocation with a user-visible migration notice whenever
`~/.deepseek/<state>` is moved or copied into `~/.codewhale/<state>`, so
upgraded users know their data was preserved and where the canonical state
now lives (#3726).
- Restored legacy `.deepseek/sessions` visibility for upgraded installs where
an empty `~/.codewhale/sessions` directory already existed, by copying
missing legacy session entries into the primary CodeWhale session store
without overwriting newer data (#3724).
- Calmed approval risk classification for read-only shell commands such as
`codewhale --version`, `codewhale --help`, and `git status --porcelain` so
the modal no longer labels proven read-only shell as destructive (#3730).
- Added provider/model route columns to `/cache` turn telemetry so DeepSeek
cache-hit regressions can be correlated with Auto route changes (#3738).
- Fixed runtime API approval handling so workspace trust no longer auto-resolves
ordinary tool approvals; trust now only participates in full-access retry
decisions while YOLO/auto-approve remains the approval bypass (#3736).
- Fixed modal surfaces so the shared view stack paints an opaque backdrop before
any overlay, while Plan/request-input popup interiors stay opaque and the Plan
confirmation footer keeps action choices visible on narrow terminals (#3732).
- Added a turn-loop Plan-mode guard for file-writing tools and write-capable MCP
tools so Plan's "no writes" promise is enforced before approval or execution,
not only by the sandbox/catalog layer (#3734).
- Preserved the durable review safety floor for publish-like shell actions in
YOLO mode, so `cargo publish`, `npm publish`, and tag/release pushes force
approval instead of silently auto-approving (#3735).
- Fixed Ctrl+O external-editor freezes where CodeWhale's terminal input pump
could keep reading keys while Vim/editor owned the terminal, especially in
Windows mintty/cygwin shells. Thanks @buko for the precise repro (#3657).
- Hardened the OHOS dependency drift check against transient Cargo registry EOFs
by retrying the dependency graph probe before failing CI.
- Updated the `/links` provider fallback to the current CodeWhale docs URL and
added a Baidu Qianfan docs link. Harvested from #3621 by @noaft.
- Hardened `CODEWHALE_TOOL_SURFACE=shell-only` for benchmark/exec runs: the
shell-only surface hides native tools from the model-visible catalog, and
unknown `CODEWHALE_TOOL_SURFACE` values now warn instead of silently falling
back to the full tool surface (#2954).
- Sub-agent fanout and lock hot paths: preserved event-channel headroom for
progress events (#3783, thanks @cyq1017), let independent sub-agent starts
join a single parallel dispatch batch instead of serializing (#3801), rendered
the sub-agent sidebar/ListSubAgents from a read-only snapshot with bounded
cleanup (#3803), used nonblocking best-effort sends for ListSubAgents refresh
while still awaiting critical events (#3802), moved sub-agent state
persistence disk I/O off the manager write lock (#3805), and used `try_lock`
for shell-manager refresh in async UI paths (#3804).
- Provenance: runtime continuations and `SubAgentHandoff` now inherit standing
YOLO authority, while `MemoryRecall`, `ImportedTranscript`, and
`AssistantGenerated` inputs remain guarded (#3817).
- Approval honesty: labeled session-scoped approvals accurately instead of
"always", and surfaced approval decisions in tool results (#3766).
---
Older releases: [CHANGELOG.md](https://github.com/Hmbown/CodeWhale/blob/main/CHANGELOG.md) and [docs/CHANGELOG_ARCHIVE.md](https://github.com/Hmbown/CodeWhale/blob/main/docs/CHANGELOG_ARCHIVE.md).
+16 -21
View File
@@ -12,11 +12,7 @@ default-run = "codewhale-tui"
workspace = true
[features]
default = ["tui", "json", "toml"]
tui = ["dep:schemaui", "schemaui/tui", "json", "toml"]
web = ["dep:schemaui", "schemaui/web", "json", "toml"]
json = ["schemaui/json"]
toml = ["schemaui/toml"]
default = []
long-running-tests = []
[lib]
@@ -30,21 +26,20 @@ path = "src/main.rs"
[dependencies]
ahash = "0.8"
anyhow.workspace = true
codewhale-config = { path = "../config", version = "0.9.11" }
codewhale-command-contract = { path = "../command-contract", version = "0.9.11" }
codewhale-core = { path = "../core", version = "0.9.11" }
codewhale-execpolicy = { path = "../execpolicy", version = "0.9.11" }
codewhale-hooks = { path = "../hooks", version = "0.9.11" }
codewhale-lane = { path = "../lane", version = "0.9.11" }
codewhale-paths = { path = "../paths", version = "0.9.11" }
codewhale-protocol = { path = "../protocol", version = "0.9.11" }
codewhale-release = { path = "../release", version = "0.9.11" }
codewhale-secrets = { path = "../secrets", version = "0.9.11" }
codewhale-telemetry = { path = "../telemetry", version = "0.9.11" }
codewhale-tools = { path = "../tools", version = "0.9.11" }
codewhale-workflow = { path = "../workflow", version = "0.9.11" }
codewhale-workflow-js = { path = "../workflow-js", version = "0.9.11" }
schemaui = { version = "0.12.0", default-features = false, optional = true }
codewhale-config = { path = "../config", version = "0.9.12" }
codewhale-command-contract = { path = "../command-contract", version = "0.9.12" }
codewhale-core = { path = "../core", version = "0.9.12" }
codewhale-execpolicy = { path = "../execpolicy", version = "0.9.12" }
codewhale-hooks = { path = "../hooks", version = "0.9.12" }
codewhale-lane = { path = "../lane", version = "0.9.12" }
codewhale-paths = { path = "../paths", version = "0.9.12" }
codewhale-protocol = { path = "../protocol", version = "0.9.12" }
codewhale-release = { path = "../release", version = "0.9.12" }
codewhale-secrets = { path = "../secrets", version = "0.9.12" }
codewhale-telemetry = { path = "../telemetry", version = "0.9.12" }
codewhale-tools = { path = "../tools", version = "0.9.12" }
codewhale-workflow = { path = "../workflow", version = "0.9.12" }
codewhale-workflow-js = { path = "../workflow-js", version = "0.9.12" }
async-stream = "0.3.6"
async-trait.workspace = true
base64 = "0.22.1"
@@ -111,7 +106,7 @@ shell-words = "1.1.1"
mimalloc.workspace = true
[build-dependencies]
codewhale-build-support = { path = "../build-support", version = "0.9.11" }
codewhale-build-support = { path = "../build-support", version = "0.9.12" }
[dev-dependencies]
cucumber = "0.23.0"
+98 -34
View File
@@ -1,11 +1,15 @@
{
"ExtensionsActionAdd": "afegeix",
"ExtensionsActionEnable": "activa",
"ExtensionsActionEdit": "Edita",
"ExtensionsHooksAddLabel": "Afegeix un ganxo",
"ExtensionsHooksAddDescription": "Obre el fitxer de ganxos d'aquest projecte al teu editor",
"ExtensionsActionReload": "torna a carregar",
"ExtensionsActionConnect": "connecta",
"ExtensionsActionReconnect": "torna a connectar",
"ExtensionsActionReauth": "reautentica",
"ExtensionsActionDiagnose": "diagnostica",
"ExtensionsActionManage": "gestiona",
"ExtensionsActionFocus": "focus",
"ExtensionsActionFold": "plega",
"ExtensionsActionTabs": "pestanyes",
@@ -19,6 +23,7 @@
"ExtensionsGroupProblems": "Problemes",
"ExtensionsGroupRecommended": "Recomanat",
"ExtensionsGroupServers": "Servidors",
"ExtensionsGroupNeedsAttention": "Necessita atenció",
"ExtensionsGroupStatus": "{count} elements · Enter plega",
"ExtensionsGroupUser": "Usuari",
"ExtensionsGroupWorkspace": "Espai de treball",
@@ -45,6 +50,8 @@
"ExtensionsProductCuaDescription": "Entorn local d'ús de l'ordinador amb controlador natiu de macOS.",
"ExtensionsProductDetail": "editor: {publisher} · components: {components} · origen: {source}",
"ExtensionsProductPlaywrightDescription": "Automatització del navegador basada en el Playwright MCP de Microsoft.",
"ExtensionsProductCodewhaleComputerUseDescription": "Veu la pantalla i la controla: control amb accessibilitat primer, captures, zoom i gravació de pantalla, més ordinadors remots per ssh.",
"ExtensionsStateFirstParty": "Inclòs amb Codewhale",
"ExtensionsProductSandboxDescription": "Capa d'aïllament de processos per a ordres locals proporcionades per connectors.",
"ExtensionsSearchLabel": "Cerca: ",
"ExtensionsSkillRootCompatibleGlobal": "Global compatible ({harness})",
@@ -123,9 +130,10 @@
"HistoryHintRestore": "Esc restaurar",
"HistoryNoMatches": " Cap coincidència",
"TranscriptReasoningExpand": "amplia",
"TelemetryNoticeHeadline": "Vols ajudar a millorar Codewhale?",
"TelemetryNoticeBody": "Codewhale compta: la versió que executes, el sistema operatiu i la família\nde CPU, la durada i el resultat de la sessió, i recomptes agregats de funcions\ni errors.\n\nNo recopila mai les teves converses, codi, prompts, fitxers, noms de\nfitxers, repositoris o branques, contingut del model ni credencials — i mai no\nenvia una cronologia de l'activitat de l'agent per torn o eina.\n\nNomés t'identifica un identificador aleatori desat en aquesta màquina, que se\nsubstitueix cada 90 dies. Pots canviar d'opinió en qualsevol moment:\n codewhale config set telemetry false\n\nEsquema complet, camp per camp: docs/TELEMETRY.md",
"TelemetryNoticeCompactBody": "Compta: versió, SO/CPU, temps/resultat, funcions/errors.\nID local rota/90d.\nMai: xats/codi/prompts/fitxers/noms; contingut del model/credencials; torns/eines.\nVegeu: docs/TELEMETRY.md\nDesact.: codewhale config set telemetry false",
"TelemetryNoticeDefaultOn": "Les estadístiques d’ús estan activades per defecte. Compartir totals de versió, plataforma, sessions, funcions i errors amb Codewhale i l’encarregat del tractament PostHog. Sense contingut ni IP. docs/TELEMETRY.md Desactiva: codewhale config set telemetry false",
"TelemetryNoticeHeadline": "Estadístiques d’ús de Codewhale",
"TelemetryNoticeBody": "Codewhale compta: la versió que executes, el sistema operatiu i la família\nde CPU, la durada i el resultat de la sessió, i recomptes agregats de funcions\ni errors.\n\nNo recopila mai les teves converses, codi, prompts, fitxers, noms de\nfitxers, repositoris o branques, contingut del model ni credencials — i mai no\nenvia una cronologia de l'activitat de l'agent per torn o eina.\n\nNomés t'identifica un identificador aleatori desat en aquesta màquina, que se\nsubstitueix cada 90 dies. Pots canviar d'opinió en qualsevol moment:\n codewhale config set telemetry false\n\nEsquema complet, camp per camp: docs/TELEMETRY.md\n\nLes estadístiques d’ús estan activades per defecte. Codewhale i PostHog tracten aquests totals quan l’enviament està configurat. No es recopila cap IP.",
"TelemetryNoticeCompactBody": "Compta: versió, SO/CPU, temps/resultat, funcions/errors.\nID local rota/90d.\nMai: xats/codi/prompts/fitxers/noms; contingut del model/credencials; torns/eines.\nVegeu: docs/TELEMETRY.md\nDesact.: codewhale config set telemetry false\nLes estadístiques d’ús estan activades per defecte. Codewhale i PostHog tracten aquests totals quan l’enviament està configurat. No es recopila cap IP.",
"TelemetryNoticeChoiceKeep": "Sí, mantenir el recompte anònim",
"TelemetryNoticeChoiceDisable": "No, desactivar el seguiment",
"TelemetryNoticeActionChoose": "triar",
@@ -256,9 +264,9 @@
"ConfigLabelAllowShell": "Accés al shell",
"ConfigLabelManagedAllowShell": "Accés al shell (gestionat)",
"ConfigLabelTelemetry": "Recompte anònim d'ús",
"ConfigHintTelemetry": "Preferència desada per a recomptes anònims de funcions i errors; mai converses ni codi",
"ConfigValueTelemetryOn": "Activat (desat)",
"ConfigValueTelemetryOff": "Desactivat (desat)",
"ConfigHintTelemetry": "Les estadístiques d’ús estan activades per defecte. Compartir totals de versió, plataforma, sessions, funcions i errors amb Codewhale i l’encarregat del tractament PostHog. Sense contingut ni IP. docs/TELEMETRY.md",
"ConfigValueTelemetryOn": "Activat",
"ConfigValueTelemetryOff": "Desactivat",
"ConfigLabelStreamTimeout": "Temps d'espera del flux",
"ConfigLabelTheme": "Tema",
"ConfigLabelLocale": "Idioma",
@@ -324,7 +332,6 @@
"ModelPickerAutoLocalHint": "per torn · heurística local · sense petició al router",
"ModelPickerAutoLastRoute": "última {provider} · {model}",
"AutoRouteSelectedToast": "Auto: {provider} / {model} via {source} · Ctrl+O: detalls de la ruta",
"CloudCodeSystemPromptUnsupported": "Antigravity cloud-code no pot enviar aquest torn de manera segura perquè aquesta connexió encara no admet instruccions del sistema. No s’ha enviat res; tria un altre proveïdor.",
"HelpTitle": "Ajuda",
"HelpFilterPlaceholder": "Escriu per filtrar",
"HelpFilterPrefix": "Filtre: ",
@@ -477,6 +484,12 @@
"CmdModeDescription": "Canvia els permisos o obre el selector de mode",
"CmdModelDescription": "Canvia o mostra el model actual",
"CmdModelsDescription": "Obtén els IDs de model en viu de l'API activa",
"ModelsListHeader": "Models de {provider} (predeterminat: {model})",
"ModelsListHint": "Catàleg en memòria cau; executa `codewhale models --update` per actualitzar els proveïdors configurats.",
"ModelsUpdateSummary": "Catàlegs de models: {updated} actualitzats, {loaded} carregats des de Codex, {failed} amb errors, {skipped} omesos.",
"ModelsUpdatePartial": "No s’han pogut actualitzar alguns catàlegs. Els models anteriors i configurats continuen disponibles.",
"ModelsCodexHint": "Els models de ChatGPT es carreguen a través de Codex. L’hora de consulta no confirma una actualització del catàleg del proveïdor.",
"ModelsSourceFallback": "Models inclosos o configurats; disponibilitat no verificada.",
"CmdModelDbDescription": "Explora la base de dades de models inclosa",
"CmdNetworkDescription": "Gestiona les regles de xarxa de permís i denegació",
"CmdUpdateDescription": "Comprova i instal·la una nova versió de Codewhale",
@@ -544,7 +557,7 @@
"CmdStructcopyClipboardAccepted": "La còpia estructural ({kind}, {bytes} bytes) s'ha lliurat al porta-retalls; si no hi havia cap porta-retalls natiu accessible, s'ha posat a la cua una escriptura al terminal",
"CmdStructcopyClipboardFailed": "Ha fallat la còpia al porta-retalls: {error}. No s'ha escrit res; torna-ho a executar amb `stdout` per a una vista de text explícita",
"CmdStructcopyReceiptTooLarge": "Les metadades del rebut de còpia estructural superen el límit de sortida de {bytes} bytes; no s'emetran",
"CmdFleetDescription": "Inspecciona i configura els membres de Fleet i l'estat d'orquestració",
"CmdFleetDescription": "Inspecciona i configura els membres de l'equip i l'estat d'orquestració",
"CmdWorkflowDescription": "Executar un script multiagent quan importen l'ordre o el fan-out",
"CmdWorkflowsDescription": "Mostra les execucions de flux de treball d'aquest espai de treball (llistar, cancel·lar)",
"CmdHotbarDescription": "Obre la configuració de Hotbar",
@@ -583,6 +596,7 @@
"FooterBalancePrefix": "saldo",
"HelpSectionActions": "Accions",
"HelpSectionClipboard": "Porta-retalls",
"HelpSectionPointer": "Punter",
"HelpSectionEditing": "Edició d'entrada",
"HelpSectionHelp": "Ajuda",
"HelpSectionModes": "Modes",
@@ -643,14 +657,22 @@
"KbCopySelection": "Copia la selecció actual de Codewhale",
"ClipboardSshPasteHint": "L'enganxada per SSH usa el teu terminal local: prem Cmd+V a macOS o Ctrl+Shift+V a Linux/Windows.",
"KbContextMenu": "Obre les accions de context per enganxar, selecció, detalls del missatge, context i ajuda",
"KbPointerScroll": "Desplaça la transcripció; sobre el redactor, mou el cursor línia a línia",
"KbPointerClick": "Activa la fila, el xip o el bloc d'eina sota el punter",
"KbPointerDrag": "Selecciona text de la transcripció, o arrossega la barra de desplaçament",
"KbAttachPath": "Afegeix un fitxer de text o directori local al context",
"KbHelpOverlay": "Obre aquesta superposició d'ajuda (quan l'entrada és buida)",
"KbCycleWorkDock": "Recorre el moll de treball (tasques, agents, feines, segon pla)",
"KbCycleWorkDockBack": "Recorre el moll de treball enrere",
"KbToggleHelp": "Commuta la superposició d'ajuda",
"KbToggleHelpSlash": "Commuta la superposició d'ajuda",
"HelpUsageLabel": "Ús:",
"HelpAliasesLabel": "Àlies:",
"SettingsTitle": "Configuració:",
"SettingsConfigFile": "Fitxer de configuració:",
"SettingsTuiPrefsFolded": "Hem migrat {key} '{value}' de tui.toml — ara settings.toml n'és el propietari.",
"SettingsTuiPrefsKept": "tui.toml definia {key} com a '{prefs}', però settings.toml ja el defineix com a '{settings}' — guanya settings.toml.",
"SettingsTuiPrefsQuarantined": "Les claus de tui.toml sense cap opció s'han conservat a {path}: {keys}",
"ClearConversation": "Conversació esborrada",
"ClearConversationBusy": "No s'ha esborrat res (l'estat de treball o el runtime està ocupat; espera i torna a provar /clear)",
"ModelChanged": "Model canviat: {old} →, {new}",
@@ -1128,8 +1150,8 @@
"NotificationSubagentCancelled": "Subagent cancel·lat",
"NotificationSubagentBudgetExhausted": "Pressupost del subagent esgotat",
"FooterWorkedChip": "ha treballat {duration}",
"FleetDraftTitle": "Perfil de Fleet — esborrany de {model_label} (g desa)",
"FleetDraftHeader": "# .codewhale/agents/{name}\n# Esborrany de {model_label}, validat i limitat per Codewhale.\n# Els permisos es mantenen al mínim del Fleet: sense shell, sense confiança, aprovació requerida.\n# No es desa res fins que premis g a l'assistent.\n\n",
"FleetDraftTitle": "Perfil d'equip — esborrany de {model_label} (g desa)",
"FleetDraftHeader": "# .codewhale/agents/{name}\n# Esborrany de {model_label}, validat i limitat per Codewhale.\n# Els permisos es mantenen al mínim de l'equip: sense shell, sense confiança, aprovació requerida.\n# No es desa res fins que premis g a l'assistent.\n\n",
"SetupRemoteOnRampText": "Incorporació al runtime remot\n\n/setup només mostra dades del runtime remot. No genera paquets de desplegament, no escriu credencials, no crida CLI de núvol ni executa `remote-setup`.\n\nDades actuals:\n- Núvols: {clouds_result}\n- Ponts de xat: {bridges_result}\n- Proveïdors: {providers_result}\n- Mode: {mode_result}\n\nPer generar un paquet de desplegament, executa explícitament en un terminal normal:\n\n```sh\n{command}\n```\n\nEl RUNBOOK generat inclou passos de l'amfitrió per a revisió humana. `--apply` continua sense implementar; no ho tractis com un desplegament automàtic.",
"ApprovalDescSafe": "Sol·licita una operació segura/de només lectura.",
"ApprovalDescFileWrite": "Sol·licita modificar un fitxer. Confirma el camí i el contingut.",
@@ -1211,6 +1233,11 @@
"CommandPaletteSubtitle": "Cerca i executa una acció",
"ConfigSubtitle": "Configuració primer; claus en brut sota detall avançat",
"LaunchStartTitle": "Comença aquí",
"LaunchResumeConfirm": "Reprendre {title}? Torna a fer clic o prem Retorn per confirmar.",
"LaunchResumeConfirmTitle": "Reprendre la sessió",
"LaunchResumeConfirmBody": "Això substitueix el context actual per l'historial d'aquella sessió.",
"LaunchResumeConfirmResume": "reprèn",
"LaunchResumeConfirmCancel": "cancel·la",
"LaunchWorkDescription": "Utilitza aquesta carpeta amb eines locals; els canvis segueixen la política d’aprovació.",
"LaunchChatDescription": "Només conversa i planificació; cap canvi als fitxers.",
"LaunchWorkspaceGitReady": "Espai de treball · {name} · espai Git",
@@ -1236,7 +1263,6 @@
"LaunchWorktreeFailed": "El worktree ha fallat: {error}",
"LaunchWorktreeCreated": "Nou worktree {path} a la branca {branch}",
"LaunchNoSavedSessions": "No hi ha sessions desades per a aquest espai de treball.",
"LaunchComposerHint": "Enter envia · Shift+Enter línia nova · Esc torna",
"LaunchNoModelConnected": "cap model connectat",
"LaunchRunCommand": "executa {command}",
"LaunchMcpConnectedOne": "1 servidor MCP connectat",
@@ -1435,7 +1461,7 @@
"ProviderExternalRevokedToast": "Accés a credencials externes revocat per a {provider}",
"ProviderExternalRevokeFailedToast": "No s'ha pogut revocar l'accés a credencials externes: {error}",
"ThemeSurfaceTitle": "tema · previsualització en directe",
"FleetRosterHeaderLabel": "fleet",
"FleetRosterHeaderLabel": "equip",
"FleetRosterTabRoster": "membres",
"FleetRosterTabSetup": "configuració",
"FleetRosterWorkers": "treballadors",
@@ -1453,33 +1479,34 @@
"FleetProfileIdentityVerifyFailed": "No s'han pogut verificar les identitats de perfil existents ({error}); arregla el fitxer anomenat abans de desar.",
"FleetProfileIdConflict": "L'id de perfil `{id}` ja l'usa {path}; torna a redactar amb un rol diferent o elimina primer el fitxer antic.",
"FleetProfileProviderUnconfigured": "El perfil fixa el proveïdor `{provider}`, que no té credencials configurades ({env}); configura'l a /provider abans de desar.",
"FleetModelAdded": "S'ha afegit {route} al fleet `{fleet}`",
"FleetModelAddedAs": "S'ha afegit {route} com a {roles} al fleet `{fleet}`",
"FleetModelAddedCreatedNote": " (fleet personal nou, ara seleccionat)",
"FleetModelAdded": "S'ha afegit {route} a l'equip `{fleet}`",
"FleetModelAddedAs": "S'ha afegit {route} com a {roles} a l'equip `{fleet}`",
"FleetModelAddedCreatedNote": " (equip personal nou, ara seleccionat)",
"FleetModelAddedSelectedNote": " (ara seleccionat)",
"FleetModelRemoved": "S'ha eliminat {route} del fleet `{fleet}`",
"FleetModelRemovedRoles": "S'ha eliminat {route} ({roles}) del fleet `{fleet}`",
"FleetModelUnchanged": "{route} es manté al fleet `{fleet}`: {reason}",
"FleetModelReasonOperatorRoute": "aquest és el teu model actual (la ruta de l'operador del fleet); canvia de model o fes servir /fleet save per modificar-la",
"FleetModelReasonAlreadyPresent": "ja és al fleet",
"FleetModelErrorNeedsRoute": "un model del fleet necessita un id de provider i un id de model",
"FleetModelErrorNoSelection": "no hi ha cap fleet seleccionat; el teu fleet és només el model de la sessió",
"FleetModelErrorOperatorRoute": "{route} és la ruta de l'operador del fleet `{fleet}`; canvia-la amb /fleet save, no amb remove",
"FleetModelErrorNotInFleet": "{route} no és al fleet `{fleet}`",
"FleetModelsEmpty": "El teu fleet és només el model de la sessió. Afegeix-ne un: /fleet add <provider> <model> [role…] (o ⇧F en una fila de /model).",
"FleetModelsHeader": "El teu fleet `{fleet}` ({count} models)",
"FleetModelsBroken": "No s'ha pogut carregar el fleet seleccionat: {error}. Corregeix el fitxer o tria'n un altre amb /fleet fleets.",
"FleetModelRemoved": "S'ha eliminat {route} de l'equip `{fleet}`",
"FleetModelRemovedRoles": "S'ha eliminat {route} ({roles}) de l'equip `{fleet}`",
"FleetModelUnchanged": "{route} es manté a l'equip `{fleet}`: {reason}",
"FleetModelReasonOperatorRoute": "aquest és el teu model actual (la ruta de l'operador de l'equip); canvia de model o fes servir /fleet save per modificar-la",
"FleetModelReasonAlreadyPresent": "ja és a l'equip",
"FleetModelErrorNeedsRoute": "un model de l'equip necessita un id de provider i un id de model",
"FleetModelErrorNeedsRole": "un membre de l'equip és un rol; indica'n un (per exemple `/fleet add {route} scout`)",
"FleetModelErrorNoSelection": "no hi ha cap equip seleccionat; el teu equip és només el model de la sessió",
"FleetModelErrorOperatorRoute": "{route} és la ruta de l'operador de l'equip `{fleet}`; canvia-la amb /fleet save, no amb remove",
"FleetModelErrorNotInFleet": "{route} no és a l'equip `{fleet}`",
"FleetModelsEmpty": "El teu equip és només el model de la sessió. Afegeix-ne un: /fleet add <provider> <model> [role…] (o ⇧F en una fila de /model).",
"FleetModelsHeader": "El teu equip `{fleet}` ({count} models)",
"FleetModelsBroken": "No s'ha pogut carregar l'equip seleccionat: {error}. Corregeix el fitxer o tria'n un altre amb /fleet teams.",
"FleetModelsFooter": "Afegir: /fleet add <provider> <model> [role…] · Eliminar: /fleet remove <provider> <model>",
"FleetModelsFactPrice": "${input}/{output} per M",
"FleetModelsFactContext": "{context}k ctx",
"FleetModelsFactTools": "eines",
"FleetAddUsage": "Ús: /fleet add <provider> <model> [role…] — p. ex. /fleet add openrouter z-ai/glm-5.3-flash explore",
"FleetAddProviderUnconfigured": "`{provider}` no és un provider configurat. Configura'l a ~/.codewhale/config.toml o canvia-hi amb /provider abans d'afegir-lo al fleet.",
"FleetAddProviderUnconfigured": "`{provider}` no és un provider configurat. Configura'l a ~/.codewhale/config.toml o canvia-hi amb /provider abans d'afegir-lo a l'equip.",
"FleetAddModelNotServed": "{provider} no serveix `{model}` al catàleg actual; executa /models per veure què serveix, o /fleet add amb l'id exacte que llista.",
"FleetAddFailed": "No s'ha pogut afegir al fleet: {error}",
"FleetAddFailed": "No s'ha pogut afegir a l'equip: {error}",
"FleetRemoveUsage": "Ús: /fleet remove <provider> <model>",
"FleetRemoveFailed": "No s'ha pogut eliminar del fleet: {error}",
"FleetToggleFailed": "No s'ha pogut actualitzar el fleet: {error}",
"FleetRemoveFailed": "No s'ha pogut eliminar de l'equip: {error}",
"FleetToggleFailed": "No s'ha pogut actualitzar l'equip: {error}",
"FleetDestStepTitle": "On s'ha de desar aquest perfil?",
"FleetDestStepSubtitle": "No s'escriu res fins que ho confirmis a l'últim pas.",
"FleetDestProjectLabel": "Aquest projecte",
@@ -1640,12 +1667,13 @@
"KbTurnInspector": "Obre l'inspector del torn",
"CmdTurnInspectDescription": "Open the whole-turn inspector",
"CmdAutomationDescription": "Gestiona les automatitzacions programades duradores",
"AutomationUsage": "Ús: /automation [list|show <id>|pause <id>|resume <id>|delete <id> [--confirm <token>]|run <id>]",
"AutomationUsage": "Ús: /automation [list|show <id>|print <id>|pause <id>|resume <id>|delete <id> [--confirm <token>]|run <id>]",
"AutomationManagerUnavailable": "El gestor d’automatitzacions no està disponible en aquesta sessió.",
"AutomationListFailed": "No s’han pogut llistar les automatitzacions: {error}",
"AutomationActionFailed": "No s’ha pogut {action} l’automatització {id}: {error}",
"AutomationEmpty": "No hi ha automatitzacions programades. Utilitza l’eina `automation` per crear-ne una.",
"AutomationEmpty": "No hi ha automatitzacions programades. Tria Automatització nova.",
"AutomationListHeading": "Automatitzacions programades",
"AutomationScopeNote": "Les automatitzacions són teves, no d'aquest repositori: et segueixen a tots els repositoris.",
"AutomationNoun": "Automatització",
"AutomationStatusLabel": "estat",
"AutomationStatusActive": "activa",
@@ -1660,6 +1688,7 @@
"AutomationActionResume": "reprendre",
"AutomationActionDelete": "suprimir",
"AutomationActionRun": "executar",
"AutomationActionCancel": "cancel·la l'execució",
"AutomationActionPaused": "posada en pausa",
"AutomationActionResumed": "represa",
"AutomationNextLabel": "propera",
@@ -2015,8 +2044,43 @@
"LaunchNoticeClaude": "Claude Code detectat. Executa /import-claude per revisar què es pot importar: servidors MCP, valors segurs i permisos. Res s'aplica sense el teu consentiment.",
"LaunchNewSession": "Nova sessió",
"LaunchRecentHeading": "Recents",
"LaunchHelpLine": "/help per a ordres · {dock} per a la barra de treball",
"LaunchSeeAllSessions": "Mostra totes les sessions…",
"LaunchNoRecentSessions": "Encara no hi ha sessions recents — escriu a sota per començar.",
"LaunchResumeFailed": "La represa ha fallat: {error}",
"ReceiptSessionHooks": "hooks {count}"
"ReceiptSessionHooks": "hooks {count}",
"AutomationEditorNew": "Automatització nova",
"AutomationEditorEdit": "Edita l’automatització",
"AutomationEditorSchedule": "Programació",
"AutomationEditorDaily": "Diària",
"AutomationEditorWeekly": "Setmanal",
"AutomationEditorHourly": "Cada hora",
"AutomationEditorOnce": "Una vegada",
"AutomationEditorCustom": "Personalitzada",
"AutomationEditorTime": "Hora (HH:MM)",
"AutomationEditorDate": "Data (YYYY-MM-DD)",
"AutomationEditorDays": "Dies",
"AutomationEditorLocalTime": "Fus horari: local ({zone})",
"AutomationEditorProvider": "El model fixa el proveïdor. Configuració actual: {provider}.",
"AutomationEditorDefaultModel": "Model per defecte",
"AutomationEditorInheritedProvider": "valors per defecte del sistema",
"AutomationEditorEnabled": "Activada",
"AutomationEditorControls": "Tab camps · Enter edita · ↑↓ ajusta · Ctrl+A selecciona-ho tot",
"AutomationEditorPromptControls": "Enter salt de línia · Tab camp següent · Ctrl+A selecciona-ho tot",
"AutomationEditorSaveFailed": "No s’ha pogut desar: {error}",
"AutomationEditorBusy": "El magatzem està ocupat. Torna a desar.",
"AutomationEditorSaved": "S’ha desat {name}",
"AutomationEditorPreviewUnavailable": "No es pot calcular l’execució següent; es mantindrà la programació",
"AutomationEditorConflict": "L’automatització ha canviat. Cancel·la i torna-la a obrir.",
"AutomationEditorInvalidWorkspace": "Tria un directori de treball existent.",
"AutomationEditorModelControls": "Escriu per filtrar · ↑↓ mou · Enter tria · Esc enrere",
"AutomationEditorPausedPreview": "En pausa · cap execució programada",
"AutomationEditorInvalidSchedule": "Programació no vàlida: {error}",
"AutomationEditorMonday": "Dl",
"AutomationEditorTuesday": "Dt",
"AutomationEditorWednesday": "Dc",
"AutomationEditorThursday": "Dj",
"AutomationEditorFriday": "Dv",
"AutomationEditorSaturday": "Ds",
"AutomationEditorSunday": "Dg"
}
+98 -34
View File
@@ -1,11 +1,15 @@
{
"ExtensionsActionAdd": "hinzufügen",
"ExtensionsActionEnable": "aktivieren",
"ExtensionsActionEdit": "Bearbeiten",
"ExtensionsHooksAddLabel": "Hook hinzufügen",
"ExtensionsHooksAddDescription": "Die Hook-Datei dieses Projekts im Editor öffnen",
"ExtensionsActionReload": "neu laden",
"ExtensionsActionConnect": "verbinden",
"ExtensionsActionReconnect": "neu verbinden",
"ExtensionsActionReauth": "neu anmelden",
"ExtensionsActionDiagnose": "diagnostizieren",
"ExtensionsActionManage": "verwalten",
"ExtensionsActionFocus": "Fokus",
"ExtensionsActionFold": "einklappen",
"ExtensionsActionTabs": "Tabs",
@@ -19,6 +23,7 @@
"ExtensionsGroupProblems": "Probleme",
"ExtensionsGroupRecommended": "Empfohlen",
"ExtensionsGroupServers": "Server",
"ExtensionsGroupNeedsAttention": "Braucht Aufmerksamkeit",
"ExtensionsGroupStatus": "{count} Einträge · Enter klappt ein",
"ExtensionsGroupUser": "Benutzer",
"ExtensionsGroupWorkspace": "Arbeitsbereich",
@@ -45,6 +50,8 @@
"ExtensionsProductCuaDescription": "Lokale Computersteuerungs-Laufzeit mit nativem macOS-Treiber.",
"ExtensionsProductDetail": "Herausgeber: {publisher} · Komponenten: {components} · Quelle: {source}",
"ExtensionsProductPlaywrightDescription": "Browser-Automatisierung auf Basis von Microsofts Playwright MCP.",
"ExtensionsProductCodewhaleComputerUseDescription": "Den Bildschirm sehen und bedienen: Steuerung über Bedienungshilfen, Screenshots, Zoom und Bildschirmaufnahme, dazu entfernte Rechner über ssh.",
"ExtensionsStateFirstParty": "Mit Codewhale ausgeliefert",
"ExtensionsProductSandboxDescription": "Prozessisolierung für lokale Befehle, die Plugins bereitstellen.",
"ExtensionsSearchLabel": "Suchen: ",
"ExtensionsSkillRootCompatibleGlobal": "Kompatibel global ({harness})",
@@ -123,9 +130,10 @@
"HistoryHintRestore": "Esc wiederherstellen",
"HistoryNoMatches": " Keine Treffer",
"TranscriptReasoningExpand": "erweitern",
"TelemetryNoticeHeadline": "Helfen Sie mit, Codewhale zu verbessern?",
"TelemetryNoticeBody": "Codewhale zählt: die ausgeführte Version, Betriebssystem und Prozessorfamilie,\nSitzungsdauer und -ergebnis sowie zusammengefasste Funktions- und\nFehlerzähler.\n\nEs erfasst nie Unterhaltungen, Code, Prompts, Dateien, Datei-, Repository-\noder Branch-Namen, Modellinhalte oder Zugangsdaten — und sendet nie eine\nAktivitätschronik des Agenten pro Runde oder Werkzeug.\n\nZur Identifikation dient nur eine zufällige, auf diesem Gerät gespeicherte\nKennung, die alle 90 Tage ersetzt wird. Sie können Ihre Meinung jederzeit\nändern:\n codewhale config set telemetry false\n\nVollständiges Schema, Feld für Feld: docs/TELEMETRY.md",
"TelemetryNoticeCompactBody": "Zählt: Version, OS/CPU, Dauer/Ergebnis, Funktionen/Fehler.\nLokale Zufalls-ID wechselt/90T.\nNie: Chat/Code/Prompts/Dateien/Namen; Modellinhalt/Zugangsdaten; Runden/Werkzeuge.\nSchema: docs/TELEMETRY.md\nAus: codewhale config set telemetry false",
"TelemetryNoticeDefaultOn": "Nutzungsstatistiken sind standardmäßig aktiviert. Aggregierte Versions-, Plattform-, Sitzungs-, Funktions- und Fehlerzahlen mit Codewhale und dem Auftragsverarbeiter PostHog teilen. Keine Inhalte oder IPs. docs/TELEMETRY.md Ausschalten: codewhale config set telemetry false",
"TelemetryNoticeHeadline": "Codewhale-Nutzungsstatistiken",
"TelemetryNoticeBody": "Codewhale zählt: die ausgeführte Version, Betriebssystem und Prozessorfamilie,\nSitzungsdauer und -ergebnis sowie zusammengefasste Funktions- und\nFehlerzähler.\n\nEs erfasst nie Unterhaltungen, Code, Prompts, Dateien, Datei-, Repository-\noder Branch-Namen, Modellinhalte oder Zugangsdaten — und sendet nie eine\nAktivitätschronik des Agenten pro Runde oder Werkzeug.\n\nZur Identifikation dient nur eine zufällige, auf diesem Gerät gespeicherte\nKennung, die alle 90 Tage ersetzt wird. Sie können Ihre Meinung jederzeit\nändern:\n codewhale config set telemetry false\n\nVollständiges Schema, Feld für Feld: docs/TELEMETRY.md\n\nNutzungsstatistiken sind standardmäßig aktiviert. Codewhale und PostHog verarbeiten diese Zahlen, wenn die Übermittlung konfiguriert ist. IPs werden nicht erfasst.",
"TelemetryNoticeCompactBody": "Zählt: Version, OS/CPU, Dauer/Ergebnis, Funktionen/Fehler.\nLokale Zufalls-ID wechselt/90T.\nNie: Chat/Code/Prompts/Dateien/Namen; Modellinhalt/Zugangsdaten; Runden/Werkzeuge.\nSchema: docs/TELEMETRY.md\nAus: codewhale config set telemetry false\nNutzungsstatistiken sind standardmäßig aktiviert. Codewhale und PostHog verarbeiten diese Zahlen, wenn die Übermittlung konfiguriert ist. IPs werden nicht erfasst.",
"TelemetryNoticeChoiceKeep": "Ja, anonyme Zählung beibehalten",
"TelemetryNoticeChoiceDisable": "Nein, Nachverfolgung abschalten",
"TelemetryNoticeActionChoose": "wählen",
@@ -256,9 +264,9 @@
"ConfigLabelAllowShell": "Shell-Zugriff",
"ConfigLabelManagedAllowShell": "Shell-Zugriff (verwaltet)",
"ConfigLabelTelemetry": "Anonyme Nutzungszählung",
"ConfigHintTelemetry": "Gespeicherte Einstellung für anonyme Funktions- und Fehlerzählungen; nie Unterhaltungen oder Code",
"ConfigValueTelemetryOn": "An (gespeichert)",
"ConfigValueTelemetryOff": "Aus (gespeichert)",
"ConfigHintTelemetry": "Nutzungsstatistiken sind standardmäßig aktiviert. Aggregierte Versions-, Plattform-, Sitzungs-, Funktions- und Fehlerzahlen mit Codewhale und dem Auftragsverarbeiter PostHog teilen. Keine Inhalte oder IPs. docs/TELEMETRY.md",
"ConfigValueTelemetryOn": "An",
"ConfigValueTelemetryOff": "Aus",
"ConfigLabelStreamTimeout": "Stream-Timeout",
"ConfigLabelTheme": "Theme",
"ConfigLabelLocale": "Sprache",
@@ -324,7 +332,6 @@
"ModelPickerAutoLocalHint": "pro Zug · lokale Heuristik · keine Router-Anfrage",
"ModelPickerAutoLastRoute": "zuletzt {provider} · {model}",
"AutoRouteSelectedToast": "Auto: {provider} / {model} via {source} · Ctrl+O: Routendetails",
"CloudCodeSystemPromptUnsupported": "Antigravity cloud-code kann diesen Turn nicht sicher senden, da diese Verbindung noch keine Systemanweisungen unterstützt. Nichts wurde gesendet; wähle einen anderen Anbieter.",
"HelpTitle": "Hilfe",
"HelpFilterPlaceholder": "Zum Filtern tippen",
"HelpFilterPrefix": "Filter: ",
@@ -477,6 +484,12 @@
"CmdModeDescription": "Berechtigungen wechseln oder die Modusauswahl öffnen",
"CmdModelDescription": "Modell wechseln oder aktuelles anzeigen",
"CmdModelsDescription": "Live-Modell-IDs von der aktiven API abrufen",
"ModelsListHeader": "Modelle von {provider} (Standard: {model})",
"ModelsListHint": "Zwischengespeicherter Katalog; mit `codewhale models --update` die eingerichteten Anbieter aktualisieren.",
"ModelsUpdateSummary": "Modellkataloge: {updated} aktualisiert, {loaded} aus Codex geladen, {failed} fehlgeschlagen, {skipped} übersprungen.",
"ModelsUpdatePartial": "Einige Kataloge konnten nicht aktualisiert werden. Bisherige und konfigurierte Modelle bleiben verfügbar.",
"ModelsCodexHint": "ChatGPT-Modelle werden über Codex geladen. Der Abfragezeitpunkt bestätigt keine Aktualisierung des Anbieterkatalogs.",
"ModelsSourceFallback": "Mitgelieferte oder konfigurierte Modelle; Verfügbarkeit nicht geprüft.",
"CmdModelDbDescription": "Mitgelieferte Modelldatenbank durchsuchen",
"CmdNetworkDescription": "Netzwerk-Allow- und Deny-Regeln verwalten",
"CmdUpdateDescription": "Neue Codewhale-Version suchen und installieren",
@@ -544,7 +557,7 @@
"CmdStructcopyClipboardAccepted": "Strukturelle Kopie ({kind}, {bytes} Bytes) wurde an die Zwischenablage übergeben; war keine native Zwischenablage erreichbar, wurde stattdessen eine Terminal-Ausgabe eingereiht",
"CmdStructcopyClipboardFailed": "Kopieren in die Zwischenablage fehlgeschlagen: {error}. Es wurde nichts geschrieben; für eine explizite Textansicht erneut mit `stdout` ausführen",
"CmdStructcopyReceiptTooLarge": "Die Beleg-Metadaten der strukturellen Kopie überschreiten das Ausgabelimit von {bytes} Bytes; Ausgabe wird verweigert",
"CmdFleetDescription": "Fleet-Mitglieder und Orchestrierungsstatus ansehen und einrichten",
"CmdFleetDescription": "Team-Mitglieder und Orchestrierungsstatus ansehen und einrichten",
"CmdWorkflowDescription": "Multi-Agent-Skript ausführen, wenn Reihenfolge oder Fan-out zählen",
"CmdWorkflowsDescription": "Workflow-Läufe in diesem Arbeitsbereich anzeigen (auflisten, abbrechen)",
"CmdHotbarDescription": "Hotbar-Setup öffnen",
@@ -583,6 +596,7 @@
"FooterBalancePrefix": "Guthaben",
"HelpSectionActions": "Aktionen",
"HelpSectionClipboard": "Zwischenablage",
"HelpSectionPointer": "Zeiger",
"HelpSectionEditing": "Eingabebearbeitung",
"HelpSectionHelp": "Hilfe",
"HelpSectionModes": "Modi",
@@ -643,14 +657,22 @@
"KbCopySelection": "Aktuelle Codewhale-Auswahl kopieren",
"ClipboardSshPasteHint": "SSH-Einfügen nutzt dein lokales Terminal: Cmd+V auf macOS oder Ctrl+Shift+V auf Linux/Windows.",
"KbContextMenu": "Kontextaktionen für Einfügen, Auswahl, Nachrichtendetails, Kontext und Hilfe öffnen",
"KbPointerScroll": "Transkript scrollen; über dem Editor den Cursor zeilenweise bewegen",
"KbPointerClick": "Zeile, Chip oder Werkzeugblock unter dem Zeiger aktivieren",
"KbPointerDrag": "Transkripttext auswählen oder die Bildlaufleiste ziehen",
"KbAttachPath": "Lokale Textdatei oder Ordner zum Kontext hinzufügen",
"KbHelpOverlay": "Dieses Hilfe-Overlay öffnen (bei leerer Eingabe)",
"KbCycleWorkDock": "Durch die Arbeitsleiste blättern (Todo, Agenten, Jobs, Hintergrund)",
"KbCycleWorkDockBack": "Rückwärts durch die Arbeitsleiste blättern",
"KbToggleHelp": "Hilfe-Overlay umschalten",
"KbToggleHelpSlash": "Hilfe-Overlay umschalten",
"HelpUsageLabel": "Verwendung:",
"HelpAliasesLabel": "Aliase:",
"SettingsTitle": "Einstellungen:",
"SettingsConfigFile": "Konfigdatei:",
"SettingsTuiPrefsFolded": "{key} '{value}' aus tui.toml migriert — settings.toml verwaltet es jetzt.",
"SettingsTuiPrefsKept": "tui.toml setzte {key} auf '{prefs}', aber settings.toml setzt bereits '{settings}' — settings.toml gewinnt.",
"SettingsTuiPrefsQuarantined": "tui.toml-Schlüssel ohne passende Einstellung wurden in {path} aufbewahrt: {keys}",
"ClearConversation": "Gespräch gelöscht",
"ClearConversationBusy": "Nichts gelöscht (Work-State oder Laufzeitarbeit beschäftigt; warten, dann /clear erneut versuchen)",
"ModelChanged": "Modell gewechselt: {old} →, {new}",
@@ -1128,8 +1150,8 @@
"NotificationSubagentCancelled": "Subagent abgebrochen",
"NotificationSubagentBudgetExhausted": "Subagent-Budget erschöpft",
"FooterWorkedChip": "{duration} gearbeitet",
"FleetDraftTitle": "Fleet-Profil — Entwurf von {model_label} (g speichert)",
"FleetDraftHeader": "# .codewhale/agents/{name}\n# Entwurf von {model_label}, validiert und begrenzt durch Codewhale.\n# Berechtigungen bleiben auf Fleet-Minimum: keine Shell, kein Vertrauen, Freigabe erforderlich.\n# Es wird nichts gespeichert, bis Sie im Wizard g drücken.\n\n",
"FleetDraftTitle": "Team-Profil — Entwurf von {model_label} (g speichert)",
"FleetDraftHeader": "# .codewhale/agents/{name}\n# Entwurf von {model_label}, validiert und begrenzt durch Codewhale.\n# Berechtigungen bleiben auf Team-Minimum: keine Shell, kein Vertrauen, Freigabe erforderlich.\n# Es wird nichts gespeichert, bis Sie im Wizard g drücken.\n\n",
"SetupRemoteOnRampText": "Remote Runtime On-Ramp\n\n/setup zeigt nur Fakten zur Remote-Runtime. Es erzeugt keine Deploy-Bundles, schreibt keine Zugangsdaten, ruft keine Cloud-CLIs auf und führt `remote-setup` nicht aus.\n\nAktuelle Fakten:\n- Clouds: {clouds_result}\n- Chat-Bridges: {bridges_result}\n- Provider: {providers_result}\n- Modus: {mode_result}\n\nUm ein Deploy-Bundle zu erzeugen, explizit in einem normalen Terminal ausführen:\n\n```sh\n{command}\n```\n\nDas erzeugte RUNBOOK enthält Host-Schritte zur menschlichen Prüfung. `--apply` bleibt unimplementiert; nicht als Auto-Deploy behandeln.",
"ApprovalDescSafe": "Fordert eine sichere/Read-only-Operation an.",
"ApprovalDescFileWrite": "Fordert an, eine Datei zu ändern. Bitte Pfad und Inhalt bestätigen.",
@@ -1211,6 +1233,11 @@
"CommandPaletteSubtitle": "Eine Aktion finden und ausführen",
"ConfigSubtitle": "Einstellungen zuerst; Rohschlüssel im erweiterten Detail",
"LaunchStartTitle": "Hier starten",
"LaunchResumeConfirm": "{title} fortsetzen? Erneut klicken oder Enter drücken zum Bestätigen.",
"LaunchResumeConfirmTitle": "Sitzung fortsetzen",
"LaunchResumeConfirmBody": "Das ersetzt den aktuellen Kontext durch den Verlauf jener Sitzung.",
"LaunchResumeConfirmResume": "fortsetzen",
"LaunchResumeConfirmCancel": "abbrechen",
"LaunchWorkDescription": "Diesen Ordner mit lokalen Werkzeugen verwenden; Änderungen folgen der Genehmigungsrichtlinie.",
"LaunchChatDescription": "Nur Unterhaltung und Planung; keine Dateiänderungen.",
"LaunchWorkspaceGitReady": "Arbeitsbereich · {name} · Git-Arbeitsbereich",
@@ -1236,7 +1263,6 @@
"LaunchWorktreeFailed": "Worktree fehlgeschlagen: {error}",
"LaunchWorktreeCreated": "Neuer Worktree {path} auf Branch {branch}",
"LaunchNoSavedSessions": "Keine gespeicherten Sitzungen für diesen Workspace.",
"LaunchComposerHint": "Enter senden · Shift+Enter neue Zeile · Esc zurück",
"LaunchNoModelConnected": "kein Modell verbunden",
"LaunchRunCommand": "{command} ausführen",
"LaunchMcpConnectedOne": "1 MCP-Server verbunden",
@@ -1435,7 +1461,7 @@
"ProviderExternalRevokedToast": "Externer Zugangsdaten-Zugriff für {provider} widerrufen",
"ProviderExternalRevokeFailedToast": "Externer Zugangsdaten-Zugriff wurde nicht widerrufen: {error}",
"ThemeSurfaceTitle": "Theme · Live-Vorschau",
"FleetRosterHeaderLabel": "Fleet",
"FleetRosterHeaderLabel": "Team",
"FleetRosterTabRoster": "Mitglieder",
"FleetRosterTabSetup": "Setup",
"FleetRosterWorkers": "Worker",
@@ -1453,33 +1479,34 @@
"FleetProfileIdentityVerifyFailed": "Bestehende Profil-Identitäten konnten nicht verifiziert werden ({error}); die benannte Datei vor dem Speichern korrigieren.",
"FleetProfileIdConflict": "Profil-ID `{id}` wird bereits von {path} genutzt; mit einer anderen Rolle neu entwerfen oder zuerst die alte Datei entfernen.",
"FleetProfileProviderUnconfigured": "Profil pinnt Provider `{provider}`, für den keine Zugangsdaten konfiguriert sind ({env}); vor dem Speichern in /provider einrichten.",
"FleetModelAdded": "{route} zum Fleet `{fleet}` hinzugefügt",
"FleetModelAddedAs": "{route} als {roles} zum Fleet `{fleet}` hinzugefügt",
"FleetModelAddedCreatedNote": " (neues persönliches Fleet, jetzt ausgewählt)",
"FleetModelAdded": "{route} zum Team `{fleet}` hinzugefügt",
"FleetModelAddedAs": "{route} als {roles} zum Team `{fleet}` hinzugefügt",
"FleetModelAddedCreatedNote": " (neues persönliches Team, jetzt ausgewählt)",
"FleetModelAddedSelectedNote": " (jetzt ausgewählt)",
"FleetModelRemoved": "{route} aus dem Fleet `{fleet}` entfernt",
"FleetModelRemovedRoles": "{route} ({roles}) aus dem Fleet `{fleet}` entfernt",
"FleetModelUnchanged": "{route} bleibt im Fleet `{fleet}`: {reason}",
"FleetModelReasonOperatorRoute": "das ist dein aktuelles Modell (die Operator-Route des Fleets); wechsle das Modell oder ändere sie mit /fleet save",
"FleetModelReasonAlreadyPresent": "bereits im Fleet",
"FleetModelErrorNeedsRoute": "ein Fleet-Modell braucht sowohl eine Provider-ID als auch eine Modell-ID",
"FleetModelErrorNoSelection": "kein Fleet ausgewählt; dein Fleet ist nur das Sitzungsmodell",
"FleetModelErrorOperatorRoute": "{route} ist die Operator-Route des Fleets `{fleet}`; ändere sie mit /fleet save, nicht mit remove",
"FleetModelErrorNotInFleet": "{route} ist nicht im Fleet `{fleet}`",
"FleetModelsEmpty": "Dein Fleet ist nur das Sitzungsmodell. Füge eines hinzu: /fleet add <provider> <model> [role…] (oder ⇧F auf einer Zeile in /model).",
"FleetModelsHeader": "Dein Fleet `{fleet}` ({count} Modelle)",
"FleetModelsBroken": "Das ausgewählte Fleet konnte nicht geladen werden: {error}. Repariere die Datei oder wähle mit /fleet fleets ein anderes.",
"FleetModelRemoved": "{route} aus dem Team `{fleet}` entfernt",
"FleetModelRemovedRoles": "{route} ({roles}) aus dem Team `{fleet}` entfernt",
"FleetModelUnchanged": "{route} bleibt im Team `{fleet}`: {reason}",
"FleetModelReasonOperatorRoute": "das ist dein aktuelles Modell (die Operator-Route des Teams); wechsle das Modell oder ändere sie mit /fleet save",
"FleetModelReasonAlreadyPresent": "bereits im Team",
"FleetModelErrorNeedsRoute": "ein Team-Modell braucht sowohl eine Provider-ID als auch eine Modell-ID",
"FleetModelErrorNeedsRole": "ein Teammitglied ist eine Rolle; nenne eine (zum Beispiel `/fleet add {route} scout`)",
"FleetModelErrorNoSelection": "kein Team ausgewählt; dein Team ist nur das Sitzungsmodell",
"FleetModelErrorOperatorRoute": "{route} ist die Operator-Route des Teams `{fleet}`; ändere sie mit /fleet save, nicht mit remove",
"FleetModelErrorNotInFleet": "{route} ist nicht im Team `{fleet}`",
"FleetModelsEmpty": "Dein Team ist nur das Sitzungsmodell. Füge eines hinzu: /fleet add <provider> <model> [role…] (oder ⇧F auf einer Zeile in /model).",
"FleetModelsHeader": "Dein Team `{fleet}` ({count} Modelle)",
"FleetModelsBroken": "Das ausgewählte Team konnte nicht geladen werden: {error}. Repariere die Datei oder wähle mit /fleet teams ein anderes.",
"FleetModelsFooter": "Hinzufügen: /fleet add <provider> <model> [role…] · Entfernen: /fleet remove <provider> <model>",
"FleetModelsFactPrice": "${input}/{output} pro M",
"FleetModelsFactContext": "{context}k ctx",
"FleetModelsFactTools": "Tools",
"FleetAddUsage": "Verwendung: /fleet add <provider> <model> [role…] — z. B. /fleet add openrouter z-ai/glm-5.3-flash explore",
"FleetAddProviderUnconfigured": "`{provider}` ist kein konfigurierter Provider. Konfiguriere ihn in ~/.codewhale/config.toml oder wechsle mit /provider zu ihm, bevor du ihn zum Fleet hinzufügst.",
"FleetAddProviderUnconfigured": "`{provider}` ist kein konfigurierter Provider. Konfiguriere ihn in ~/.codewhale/config.toml oder wechsle mit /provider zu ihm, bevor du ihn zum Team hinzufügst.",
"FleetAddModelNotServed": "{provider} bietet `{model}` im aktuellen Katalog nicht an; führe /models aus, um das Angebot zu sehen, oder /fleet add mit der exakt gelisteten ID.",
"FleetAddFailed": "Konnte nicht zum Fleet hinzufügen: {error}",
"FleetAddFailed": "Konnte nicht zum Team hinzufügen: {error}",
"FleetRemoveUsage": "Verwendung: /fleet remove <provider> <model>",
"FleetRemoveFailed": "Konnte nicht aus dem Fleet entfernen: {error}",
"FleetToggleFailed": "Konnte das Fleet nicht aktualisieren: {error}",
"FleetRemoveFailed": "Konnte nicht aus dem Team entfernen: {error}",
"FleetToggleFailed": "Konnte das Team nicht aktualisieren: {error}",
"FleetDestStepTitle": "Wo soll dieses Profil gespeichert werden?",
"FleetDestStepSubtitle": "Es wird nichts geschrieben, bis du im letzten Schritt bestätigst.",
"FleetDestProjectLabel": "Dieses Projekt",
@@ -1640,12 +1667,13 @@
"KbTurnInspector": "Turn Inspector öffnen",
"CmdTurnInspectDescription": "Open the whole-turn inspector",
"CmdAutomationDescription": "Dauerhafte geplante Automatisierungen verwalten",
"AutomationUsage": "Verwendung: /automation [list|show <id>|pause <id>|resume <id>|delete <id> [--confirm <token>]|run <id>]",
"AutomationUsage": "Verwendung: /automation [list|show <id>|print <id>|pause <id>|resume <id>|delete <id> [--confirm <token>]|run <id>]",
"AutomationManagerUnavailable": "Der Automatisierungsmanager ist in dieser Sitzung nicht verfügbar.",
"AutomationListFailed": "Automatisierungen konnten nicht aufgelistet werden: {error}",
"AutomationActionFailed": "Automatisierung {id} konnte nicht {action} werden: {error}",
"AutomationEmpty": "Keine geplanten Automatisierungen. Mit dem Werkzeug `automation` kann eine erstellt werden.",
"AutomationEmpty": "Keine geplanten Automationen. Neue Automation auswählen.",
"AutomationListHeading": "Geplante Automatisierungen",
"AutomationScopeNote": "Automationen gehören dir, nicht diesem Repository – sie folgen dir in jedes Repository.",
"AutomationNoun": "Automatisierung",
"AutomationStatusLabel": "Status",
"AutomationStatusActive": "aktiv",
@@ -1660,6 +1688,7 @@
"AutomationActionResume": "fortgesetzt",
"AutomationActionDelete": "gelöscht",
"AutomationActionRun": "ausgeführt",
"AutomationActionCancel": "Lauf abbrechen",
"AutomationActionPaused": "pausiert",
"AutomationActionResumed": "fortgesetzt",
"AutomationNextLabel": "nächste",
@@ -2015,8 +2044,43 @@
"LaunchNoticeClaude": "Claude Code erkannt. Führe /import-claude aus, um zu prüfen, was übernommen werden kann: MCP-Server, sichere Einstellungen und Berechtigungen. Nichts wird ohne deine Zustimmung angewendet.",
"LaunchNewSession": "Neue Sitzung",
"LaunchRecentHeading": "Zuletzt",
"LaunchHelpLine": "/help für Befehle · {dock} für die Arbeitsleiste",
"LaunchSeeAllSessions": "Alle Sitzungen anzeigen…",
"LaunchNoRecentSessions": "Noch keine aktuellen Sitzungen — unten tippen, um zu starten.",
"LaunchResumeFailed": "Fortsetzen fehlgeschlagen: {error}",
"ReceiptSessionHooks": "Hooks {count}"
"ReceiptSessionHooks": "Hooks {count}",
"AutomationEditorNew": "Neue Automation",
"AutomationEditorEdit": "Automation bearbeiten",
"AutomationEditorSchedule": "Zeitplan",
"AutomationEditorDaily": "Täglich",
"AutomationEditorWeekly": "Wöchentlich",
"AutomationEditorHourly": "Stündlich",
"AutomationEditorOnce": "Einmal",
"AutomationEditorCustom": "Benutzerdefiniert",
"AutomationEditorTime": "Uhrzeit (HH:MM)",
"AutomationEditorDate": "Datum (YYYY-MM-DD)",
"AutomationEditorDays": "Tage",
"AutomationEditorLocalTime": "Zeitzone: lokal ({zone})",
"AutomationEditorProvider": "Die Modellwahl bindet den Anbieter. Aktuelle Konfiguration: {provider}.",
"AutomationEditorDefaultModel": "Standardmodell",
"AutomationEditorInheritedProvider": "Laufzeitvorgaben",
"AutomationEditorEnabled": "Aktiviert",
"AutomationEditorControls": "Tab Felder · Enter bearbeiten · ↑↓ anpassen · Ctrl+A alles auswählen",
"AutomationEditorPromptControls": "Enter neue Zeile · Tab nächstes Feld · Ctrl+A alles auswählen",
"AutomationEditorSaveFailed": "Speichern fehlgeschlagen: {error}",
"AutomationEditorBusy": "Automationsspeicher ist belegt. Erneut speichern.",
"AutomationEditorSaved": "{name} gespeichert",
"AutomationEditorPreviewUnavailable": "Nächster Lauf unbekannt; vorhandener Zeitplan bleibt erhalten",
"AutomationEditorConflict": "Automation wurde geändert. Abbrechen und erneut öffnen.",
"AutomationEditorInvalidWorkspace": "Ein vorhandenes Arbeitsverzeichnis auswählen.",
"AutomationEditorModelControls": "Tippen zum Filtern · ↑↓ bewegen · Enter wählen · Esc zurück",
"AutomationEditorPausedPreview": "Pausiert · kein geplanter Lauf",
"AutomationEditorInvalidSchedule": "Ungültiger Zeitplan: {error}",
"AutomationEditorMonday": "Mo",
"AutomationEditorTuesday": "Di",
"AutomationEditorWednesday": "Mi",
"AutomationEditorThursday": "Do",
"AutomationEditorFriday": "Fr",
"AutomationEditorSaturday": "Sa",
"AutomationEditorSunday": "So"
}
+98 -34
View File
@@ -1,11 +1,15 @@
{
"ExtensionsActionAdd": "add",
"ExtensionsActionEnable": "enable",
"ExtensionsActionEdit": "Edit",
"ExtensionsHooksAddLabel": "Add a hook",
"ExtensionsHooksAddDescription": "Open this project's hooks file in your editor",
"ExtensionsActionReload": "reload",
"ExtensionsActionConnect": "connect",
"ExtensionsActionReconnect": "reconnect",
"ExtensionsActionReauth": "re-auth",
"ExtensionsActionDiagnose": "diagnose",
"ExtensionsActionManage": "manage",
"ExtensionsActionFocus": "focus",
"ExtensionsActionFold": "fold",
"ExtensionsActionTabs": "tabs",
@@ -19,6 +23,7 @@
"ExtensionsGroupProblems": "Problems",
"ExtensionsGroupRecommended": "Recommended",
"ExtensionsGroupServers": "Servers",
"ExtensionsGroupNeedsAttention": "Needs attention",
"ExtensionsGroupStatus": "{count} items · Enter folds",
"ExtensionsGroupUser": "User",
"ExtensionsGroupWorkspace": "Workspace",
@@ -45,6 +50,8 @@
"ExtensionsProductCuaDescription": "Local computer-use runtime with a native macOS driver.",
"ExtensionsProductDetail": "publisher: {publisher} · components: {components} · source: {source}",
"ExtensionsProductPlaywrightDescription": "Browser automation built around Microsoft's Playwright MCP.",
"ExtensionsProductCodewhaleComputerUseDescription": "See the screen and operate it: accessibility-first control, screenshots, zoom, and screen recording, plus remote computers over ssh.",
"ExtensionsStateFirstParty": "Ships with Codewhale",
"ExtensionsProductSandboxDescription": "Process isolation layer for plugin-provided local commands.",
"ExtensionsSearchLabel": "Search: ",
"ExtensionsSkillRootCompatibleGlobal": "Compatible global ({harness})",
@@ -123,9 +130,10 @@
"HistoryHintRestore": "Esc restore",
"HistoryNoMatches": " No matches",
"TranscriptReasoningExpand": "expand",
"TelemetryNoticeHeadline": "Help improve Codewhale?",
"TelemetryNoticeBody": "Codewhale counts: which version you run, OS and CPU family, session\nduration and outcome, and aggregate feature and error counters.\n\nIt never collects your conversations, code, prompts, files, repo or\nbranch names, model content, or credentials — and it never sends a\nper-turn or per-tool timeline of agent activity.\n\nYou are identified only by a random ID stored on this machine, replaced\nevery 90 days. Change your mind any time:\n codewhale config set telemetry false\n\nFull schema, field by field: docs/TELEMETRY.md",
"TelemetryNoticeCompactBody": "Counts: version, OS/CPU, session\ntime/result, feature/error totals.\nRandom local ID rotates every 90d.\nNever: chat/code/prompts/files/names;\nmodel content/credentials; turn/tools.\nSchema: docs/TELEMETRY.md\nOff: codewhale config set telemetry\nfalse",
"TelemetryNoticeDefaultOn": "Usage reporting is on by default. Share aggregate version/platform, session, feature and error counts with Codewhale and PostHog (processor). No content or IP. docs/TELEMETRY.md Turn off: codewhale config set telemetry false",
"TelemetryNoticeHeadline": "Codewhale usage reporting",
"TelemetryNoticeBody": "Codewhale counts: which version you run, OS and CPU family, session\nduration and outcome, and aggregate feature and error counters.\n\nIt never collects your conversations, code, prompts, files, repo or\nbranch names, model content, or credentials — and it never sends a\nper-turn or per-tool timeline of agent activity.\n\nYou are identified only by a random ID stored on this machine, replaced\nevery 90 days. Change your mind any time:\n codewhale config set telemetry false\n\nFull schema, field by field: docs/TELEMETRY.md\n\nUsage reporting is on by default. Codewhale and PostHog process these counts when delivery is configured. No IP is collected.",
"TelemetryNoticeCompactBody": "Counts: version, OS/CPU, session\ntime/result, feature/error totals.\nRandom local ID rotates every 90d.\nNever: chat/code/prompts/files/names;\nmodel content/credentials; turn/tools.\nSchema: docs/TELEMETRY.md\nOff: codewhale config set telemetry\nfalse\nUsage reporting is on by default. Codewhale and PostHog process these counts when delivery is configured. No IP is collected.",
"TelemetryNoticeChoiceKeep": "Yes, keep anonymous counts",
"TelemetryNoticeChoiceDisable": "No, turn off tracking",
"TelemetryNoticeActionChoose": "choose",
@@ -257,9 +265,9 @@
"ConfigLabelAllowShell": "Shell access",
"ConfigLabelManagedAllowShell": "Shell access (managed)",
"ConfigLabelTelemetry": "Anonymous usage counts",
"ConfigHintTelemetry": "Saved preference for anonymous feature and error counts; never conversations or code",
"ConfigValueTelemetryOn": "On (saved)",
"ConfigValueTelemetryOff": "Off (saved)",
"ConfigHintTelemetry": "Usage reporting is on by default. Share aggregate version/platform, session, feature and error counts with Codewhale and PostHog (processor). No content or IP. docs/TELEMETRY.md",
"ConfigValueTelemetryOn": "On",
"ConfigValueTelemetryOff": "Off",
"ConfigLabelStreamTimeout": "Stream timeout",
"ConfigLabelTheme": "Theme",
"ConfigLabelLocale": "Language",
@@ -327,7 +335,6 @@
"ModelPickerAutoLocalHint": "per turn · local heuristic · no router request",
"ModelPickerAutoLastRoute": "last {provider} · {model}",
"AutoRouteSelectedToast": "Auto: {provider} / {model} via {source} · Ctrl+O: route details",
"CloudCodeSystemPromptUnsupported": "Antigravity cloud-code cannot safely send this turn because this wire does not support system instructions yet. Nothing was sent; choose another provider.",
"HelpTitle": "Help",
"HelpFilterPlaceholder": "Type to filter",
"HelpFilterPrefix": "Filter: ",
@@ -480,6 +487,12 @@
"CmdModeDescription": "Switch permissions or open the mode picker",
"CmdModelDescription": "Switch or view current model",
"CmdModelsDescription": "Fetch live model IDs from the active API",
"ModelsListHeader": "{provider} models (default: {model})",
"ModelsListHint": "Cached catalog; run `codewhale models --update` to refresh configured providers.",
"ModelsUpdateSummary": "Model catalogs: {updated} updated, {loaded} loaded from Codex, {failed} failed, {skipped} skipped.",
"ModelsUpdatePartial": "Some catalogs could not be refreshed. Previous and configured models remain available.",
"ModelsCodexHint": "ChatGPT models are loaded through Codex. The lookup time does not confirm an upstream catalog refresh.",
"ModelsSourceFallback": "Bundled or configured models; availability not verified.",
"CmdModelDbDescription": "Browse the bundled model database",
"CmdNetworkDescription": "Manage network allow and deny rules",
"CmdUpdateDescription": "Check for and install a new Codewhale release",
@@ -547,7 +560,7 @@
"CmdStructcopyClipboardAccepted": "Structural copy ({kind}, {bytes} bytes) was handed to the clipboard; if no native clipboard was reachable, a terminal write was queued instead",
"CmdStructcopyClipboardFailed": "Clipboard copy failed: {error}. Nothing was written; run again with `stdout` for an explicit text view",
"CmdStructcopyReceiptTooLarge": "Structural-copy receipt metadata exceeds the {bytes}-byte output cap; refusing to emit it",
"CmdFleetDescription": "Inspect and set up fleet members and orchestration state",
"CmdFleetDescription": "Inspect and set up team members and orchestration state",
"CmdLaneDescription": "Inspect and control durable Lanes (running Workflows)",
"CmdWorkflowDescription": "Run a repeatable workflow when order or fan-out matters",
"CmdWorkflowsDescription": "Show workflow runs in this workspace (list, cancel)",
@@ -600,6 +613,7 @@
"FooterBalancePrefix": "balance",
"HelpSectionActions": "Actions",
"HelpSectionClipboard": "Clipboard",
"HelpSectionPointer": "Pointer",
"HelpSectionEditing": "Input editing",
"HelpSectionHelp": "Help",
"HelpSectionModes": "Modes",
@@ -660,14 +674,22 @@
"KbCopySelection": "Copy the current Codewhale selection",
"ClipboardSshPasteHint": "SSH paste uses your local terminal: press Cmd+V on macOS or Ctrl+Shift+V on Linux/Windows.",
"KbContextMenu": "Open context actions for paste, selection, message details, context, and help",
"KbPointerScroll": "Scroll the transcript; over the composer, move the cursor a line at a time",
"KbPointerClick": "Activate the row, chip, or tool block under the pointer",
"KbPointerDrag": "Select transcript text, or drag the scrollbar",
"KbAttachPath": "Add a local text file or directory to context",
"KbHelpOverlay": "Open this help overlay (when input is empty)",
"KbCycleWorkDock": "Cycle the work dock (todo, agents, jobs, background)",
"KbCycleWorkDockBack": "Cycle the work dock backwards",
"KbToggleHelp": "Toggle help overlay",
"KbToggleHelpSlash": "Toggle help overlay",
"HelpUsageLabel": "Usage:",
"HelpAliasesLabel": "Aliases:",
"SettingsTitle": "Settings:",
"SettingsConfigFile": "Config file:",
"SettingsTuiPrefsFolded": "Migrated {key} '{value}' from tui.toml — settings.toml now owns it.",
"SettingsTuiPrefsKept": "tui.toml set {key} '{prefs}', but settings.toml already sets '{settings}' — settings.toml wins.",
"SettingsTuiPrefsQuarantined": "tui.toml keys with no setting were kept in {path}: {keys}",
"ClearConversation": "Conversation cleared",
"ClearConversationBusy": "Nothing cleared (Work state or runtime work busy; wait, then try /clear again)",
"ModelChanged": "Model changed: {old} →, {new}",
@@ -1151,8 +1173,8 @@
"NotificationSubagentCancelled": "Sub-agent cancelled",
"NotificationSubagentBudgetExhausted": "Sub-agent budget exhausted",
"FooterWorkedChip": "worked {duration}",
"FleetDraftTitle": "fleet profile — draft by {model_label} (g saves)",
"FleetDraftHeader": "# .codewhale/agents/{name}\n# Drafted by {model_label}, validated and bounded by Codewhale.\n# Permissions stay at the fleet floor: no shell, no trust, approval required.\n# Nothing is saved until you press g in the wizard.\n\n",
"FleetDraftTitle": "team profile — draft by {model_label} (g saves)",
"FleetDraftHeader": "# .codewhale/agents/{name}\n# Drafted by {model_label}, validated and bounded by Codewhale.\n# Permissions stay at the team floor: no shell, no trust, approval required.\n# Nothing is saved until you press g in the wizard.\n\n",
"SetupRemoteOnRampText": "Remote Runtime On-Ramp\n\n/setup shows remote runtime facts only. It does not generate deploy bundles, write credentials, call cloud CLIs, or run `remote-setup`.\n\nCurrent facts:\n- Clouds: {clouds_result}\n- Chat bridges: {bridges_result}\n- Providers: {providers_result}\n- Mode: {mode_result}\n\nTo generate a deploy bundle, run explicitly in a normal terminal:\n\n```sh\n{command}\n```\n\nThe generated RUNBOOK includes host steps for human review. `--apply` remains unimplemented; do not treat this as auto-deploy.",
"ApprovalDescSafe": "Requesting a safe/read-only operation.",
"ApprovalDescFileWrite": "Requesting to modify a file. Please confirm path and content.",
@@ -1234,6 +1256,11 @@
"CommandPaletteSubtitle": "Find and run one action",
"ConfigSubtitle": "Settings first; raw keys under advanced detail",
"LaunchStartTitle": "Start here",
"LaunchResumeConfirm": "Resume {title}? Click again or press Enter to confirm.",
"LaunchResumeConfirmTitle": "Resume session",
"LaunchResumeConfirmBody": "This replaces the current context with that session's history.",
"LaunchResumeConfirmResume": "resume",
"LaunchResumeConfirmCancel": "cancel",
"LaunchWorkDescription": "Use this folder with local tools; changes follow your approval policy.",
"LaunchChatDescription": "Conversation and planning only; no file changes.",
"LaunchWorkspaceGitReady": "Workspace · {name} · Git workspace",
@@ -1259,7 +1286,6 @@
"LaunchWorktreeFailed": "Worktree failed: {error}",
"LaunchWorktreeCreated": "New worktree {path} on branch {branch}",
"LaunchNoSavedSessions": "No saved sessions for this workspace.",
"LaunchComposerHint": "Enter send · Shift+Enter new line · Esc back",
"LaunchNoModelConnected": "no model connected",
"LaunchRunCommand": "run {command}",
"LaunchMcpConnectedOne": "1 MCP server connected",
@@ -1458,7 +1484,7 @@
"ProviderExternalRevokedToast": "External credential access revoked for {provider}",
"ProviderExternalRevokeFailedToast": "External credential access was not revoked: {error}",
"ThemeSurfaceTitle": "theme · live preview",
"FleetRosterHeaderLabel": "fleet",
"FleetRosterHeaderLabel": "team",
"FleetRosterTabRoster": "members",
"FleetRosterTabSetup": "setup",
"FleetRosterWorkers": "workers",
@@ -1476,33 +1502,34 @@
"FleetProfileIdentityVerifyFailed": "Could not verify existing profile identities ({error}); fix the named file before saving.",
"FleetProfileIdConflict": "Profile id `{id}` is already used by {path}; redraft with a different role or remove the old file first.",
"FleetProfileProviderUnconfigured": "Profile pins provider `{provider}`, which has no configured credentials ({env}); set it up in /provider before saving.",
"FleetModelAdded": "Added {route} to the fleet `{fleet}`",
"FleetModelAddedAs": "Added {route} as {roles} to the fleet `{fleet}`",
"FleetModelAddedCreatedNote": " (new personal fleet, now selected)",
"FleetModelAdded": "Added {route} to the team `{fleet}`",
"FleetModelAddedAs": "Added {route} as {roles} to the team `{fleet}`",
"FleetModelAddedCreatedNote": " (new personal team, now selected)",
"FleetModelAddedSelectedNote": " (now selected)",
"FleetModelRemoved": "Removed {route} from the fleet `{fleet}`",
"FleetModelRemovedRoles": "Removed {route} ({roles}) from the fleet `{fleet}`",
"FleetModelUnchanged": "{route} stays in the fleet `{fleet}`: {reason}",
"FleetModelReasonOperatorRoute": "this is your current model (the fleet's operator route); switch models or use /fleet save to change it",
"FleetModelReasonAlreadyPresent": "already in the fleet",
"FleetModelErrorNeedsRoute": "a fleet model needs both a provider id and a model id",
"FleetModelErrorNoSelection": "no fleet is selected; your fleet is the session model only",
"FleetModelErrorOperatorRoute": "{route} is the operator route of the fleet `{fleet}`; change it with /fleet save, not remove",
"FleetModelErrorNotInFleet": "{route} is not in the fleet `{fleet}`",
"FleetModelsEmpty": "Your fleet is the session model only. Add one: /fleet add <provider> <model> [role…] (or ⇧F on a row in /model).",
"FleetModelsHeader": "Your fleet `{fleet}` ({count} models)",
"FleetModelsBroken": "Your selected fleet could not be loaded: {error}. Fix the file or pick another with /fleet fleets.",
"FleetModelRemoved": "Removed {route} from the team `{fleet}`",
"FleetModelRemovedRoles": "Removed {route} ({roles}) from the team `{fleet}`",
"FleetModelUnchanged": "{route} stays on the team `{fleet}`: {reason}",
"FleetModelReasonOperatorRoute": "this is your current model (the team's operator route); switch models or use /fleet save to change it",
"FleetModelReasonAlreadyPresent": "already on the team",
"FleetModelErrorNeedsRoute": "a team model needs both a provider id and a model id",
"FleetModelErrorNeedsRole": "a team member is a role; name one (for example `/fleet add {route} scout`)",
"FleetModelErrorNoSelection": "no team is selected; your team is the session model only",
"FleetModelErrorOperatorRoute": "{route} is the operator route of the team `{fleet}`; change it with /fleet save, not remove",
"FleetModelErrorNotInFleet": "{route} is not on the team `{fleet}`",
"FleetModelsEmpty": "Your team is the session model only. Add one: /fleet add <provider> <model> [role…] (or ⇧F on a row in /model).",
"FleetModelsHeader": "Your team `{fleet}` ({count} models)",
"FleetModelsBroken": "Your selected team could not be loaded: {error}. Fix the file or pick another with /fleet teams.",
"FleetModelsFooter": "Add: /fleet add <provider> <model> [role…] · Remove: /fleet remove <provider> <model>",
"FleetModelsFactPrice": "${input}/{output} per M",
"FleetModelsFactContext": "{context}k ctx",
"FleetModelsFactTools": "tools",
"FleetAddUsage": "Usage: /fleet add <provider> <model> [role…] — e.g. /fleet add openrouter z-ai/glm-5.3-flash explore",
"FleetAddProviderUnconfigured": "`{provider}` is not a configured provider. Configure it in ~/.codewhale/config.toml or switch to it with /provider before adding it to the fleet.",
"FleetAddProviderUnconfigured": "`{provider}` is not a configured provider. Configure it in ~/.codewhale/config.toml or switch to it with /provider before adding it to the team.",
"FleetAddModelNotServed": "{provider} does not serve `{model}` in the current catalog; run /models to see what it serves, or /fleet add with the exact id it lists.",
"FleetAddFailed": "Could not add to the fleet: {error}",
"FleetAddFailed": "Could not add to the team: {error}",
"FleetRemoveUsage": "Usage: /fleet remove <provider> <model>",
"FleetRemoveFailed": "Could not remove from the fleet: {error}",
"FleetToggleFailed": "Could not update the fleet: {error}",
"FleetRemoveFailed": "Could not remove from the team: {error}",
"FleetToggleFailed": "Could not update the team: {error}",
"FleetDestStepTitle": "Where should this profile live?",
"FleetDestStepSubtitle": "Nothing is written until you confirm on the last step.",
"FleetDestProjectLabel": "This project",
@@ -1640,12 +1667,13 @@
"KbTurnInspector": "Open Turn Inspector",
"CmdTurnInspectDescription": "Open the whole-turn inspector",
"CmdAutomationDescription": "Manage durable scheduled automations",
"AutomationUsage": "Usage: /automation [list|show <id>|pause <id>|resume <id>|delete <id> [--confirm <token>]|run <id>]",
"AutomationUsage": "Usage: /automation [list|show <id>|print <id>|pause <id>|resume <id>|delete <id> [--confirm <token>]|run <id>]",
"AutomationManagerUnavailable": "Automation manager is not available in this session.",
"AutomationListFailed": "Could not list automations: {error}",
"AutomationActionFailed": "Could not {action} automation {id}: {error}",
"AutomationEmpty": "No scheduled automations. Use the `automation` tool to create one.",
"AutomationEmpty": "No scheduled automations. Choose New automation.",
"AutomationListHeading": "Scheduled automations",
"AutomationScopeNote": "Automations are yours, not this repository's — they follow you into every repository.",
"AutomationNoun": "Automation",
"AutomationStatusLabel": "status",
"AutomationStatusActive": "active",
@@ -1660,6 +1688,7 @@
"AutomationActionResume": "resume",
"AutomationActionDelete": "delete",
"AutomationActionRun": "run",
"AutomationActionCancel": "cancel run",
"AutomationActionPaused": "paused",
"AutomationActionResumed": "resumed",
"AutomationNextLabel": "next",
@@ -2015,8 +2044,43 @@
"LaunchNoticeClaude": "Claude Code detected. Run /import-claude to review what can come over: MCP servers, safe settings, and permissions. Nothing is applied without your approval.",
"LaunchNewSession": "New session",
"LaunchRecentHeading": "Recent",
"LaunchHelpLine": "/help for commands · {dock} for the work bar",
"LaunchSeeAllSessions": "See all sessions…",
"LaunchNoRecentSessions": "No recent sessions yet — type below to start.",
"LaunchResumeFailed": "Resume failed: {error}",
"ReceiptSessionHooks": "hooks {count}"
"ReceiptSessionHooks": "hooks {count}",
"AutomationEditorNew": "New automation",
"AutomationEditorEdit": "Edit automation",
"AutomationEditorSchedule": "Schedule",
"AutomationEditorDaily": "Daily",
"AutomationEditorWeekly": "Weekly",
"AutomationEditorHourly": "Hourly",
"AutomationEditorOnce": "Once",
"AutomationEditorCustom": "Custom",
"AutomationEditorTime": "Time (HH:MM)",
"AutomationEditorDate": "Date (YYYY-MM-DD)",
"AutomationEditorDays": "Days",
"AutomationEditorLocalTime": "Time zone: local ({zone})",
"AutomationEditorProvider": "Model choices pin their provider. Current configuration: {provider}.",
"AutomationEditorDefaultModel": "Default model",
"AutomationEditorInheritedProvider": "runtime defaults",
"AutomationEditorEnabled": "Enabled",
"AutomationEditorControls": "Tab fields · Enter edit · ↑↓ adjust · Ctrl+A select all",
"AutomationEditorPromptControls": "Enter newline · Tab next field · Ctrl+A select all",
"AutomationEditorSaveFailed": "Could not save: {error}",
"AutomationEditorBusy": "Automation store is busy. Retry Save.",
"AutomationEditorSaved": "Saved {name}",
"AutomationEditorPreviewUnavailable": "Next run unavailable; existing schedule will be kept",
"AutomationEditorConflict": "Automation changed since opening. Cancel and reopen.",
"AutomationEditorInvalidWorkspace": "Choose an existing workspace directory.",
"AutomationEditorModelControls": "Type to filter · ↑↓ move · Enter select · Esc back",
"AutomationEditorPausedPreview": "Paused · no scheduled run",
"AutomationEditorInvalidSchedule": "Invalid schedule: {error}",
"AutomationEditorMonday": "Mon",
"AutomationEditorTuesday": "Tue",
"AutomationEditorWednesday": "Wed",
"AutomationEditorThursday": "Thu",
"AutomationEditorFriday": "Fri",
"AutomationEditorSaturday": "Sat",
"AutomationEditorSunday": "Sun"
}
+98 -34
View File
@@ -1,11 +1,15 @@
{
"ExtensionsActionAdd": "agregar",
"ExtensionsActionEnable": "habilitar",
"ExtensionsActionEdit": "Editar",
"ExtensionsHooksAddLabel": "Agregar un hook",
"ExtensionsHooksAddDescription": "Abre el archivo de hooks de este proyecto en tu editor",
"ExtensionsActionReload": "recargar",
"ExtensionsActionConnect": "conectar",
"ExtensionsActionReconnect": "reconectar",
"ExtensionsActionReauth": "reautenticar",
"ExtensionsActionDiagnose": "diagnosticar",
"ExtensionsActionManage": "gestionar",
"ExtensionsActionFocus": "foco",
"ExtensionsActionFold": "plegar",
"ExtensionsActionTabs": "pestañas",
@@ -19,6 +23,7 @@
"ExtensionsGroupProblems": "Problemas",
"ExtensionsGroupRecommended": "Recomendado",
"ExtensionsGroupServers": "Servidores",
"ExtensionsGroupNeedsAttention": "Necesita atención",
"ExtensionsGroupStatus": "{count} elementos · Enter pliega",
"ExtensionsGroupUser": "Usuario",
"ExtensionsGroupWorkspace": "Espacio de trabajo",
@@ -45,6 +50,8 @@
"ExtensionsProductCuaDescription": "Entorno local de uso de computadora con controlador nativo de macOS.",
"ExtensionsProductDetail": "publicador: {publisher} · componentes: {components} · fuente: {source}",
"ExtensionsProductPlaywrightDescription": "Automatización del navegador basada en Playwright MCP de Microsoft.",
"ExtensionsProductCodewhaleComputerUseDescription": "Ve la pantalla y la opera: control basado en accesibilidad, capturas, zoom y grabación de pantalla, además de equipos remotos por ssh.",
"ExtensionsStateFirstParty": "Incluido con Codewhale",
"ExtensionsProductSandboxDescription": "Capa de aislamiento de procesos para comandos locales provistos por plugins.",
"ExtensionsSearchLabel": "Buscar: ",
"ExtensionsSkillRootCompatibleGlobal": "Global compatible ({harness})",
@@ -123,9 +130,10 @@
"HistoryHintRestore": "Esc restaurar",
"HistoryNoMatches": " Sin resultados",
"TranscriptReasoningExpand": "expandir",
"TelemetryNoticeHeadline": "¿Ayudas a mejorar Codewhale?",
"TelemetryNoticeBody": "Codewhale cuenta: la versión que ejecutas, el sistema operativo y la familia\nde CPU, la duración y el resultado de la sesión, y conteos agregados de\nfunciones y errores.\n\nNunca recopila tus conversaciones, código, prompts, archivos, nombres de\narchivos, repositorios o ramas, contenido del modelo ni credenciales — y nunca\nenvía una cronología de la actividad del agente por turno o herramienta.\n\nSolo te identifica un ID aleatorio almacenado en esta máquina, que se\nreemplaza cada 90 días. Cambia de opinión cuando quieras:\n codewhale config set telemetry false\n\nEsquema completo, campo por campo: docs/TELEMETRY.md",
"TelemetryNoticeCompactBody": "Cuenta: versión, SO/CPU, tiempo/resultado, funciones/errores.\nID local rota/90d.\nNunca: chats/código/prompts/archivos/nombres; datos del modelo/credenciales; turnos/herramientas.\nVer: docs/TELEMETRY.md\nApag.: codewhale config set telemetry false",
"TelemetryNoticeDefaultOn": "Las estadísticas de uso están activadas de forma predeterminada. Compartir totales de versión, plataforma, sesiones, funciones y errores con Codewhale y su procesador PostHog. Sin contenido ni IP. docs/TELEMETRY.md Desactivar: codewhale config set telemetry false",
"TelemetryNoticeHeadline": "Estadísticas de uso de Codewhale",
"TelemetryNoticeBody": "Codewhale cuenta: la versión que ejecutas, el sistema operativo y la familia\nde CPU, la duración y el resultado de la sesión, y conteos agregados de\nfunciones y errores.\n\nNunca recopila tus conversaciones, código, prompts, archivos, nombres de\narchivos, repositorios o ramas, contenido del modelo ni credenciales — y nunca\nenvía una cronología de la actividad del agente por turno o herramienta.\n\nSolo te identifica un ID aleatorio almacenado en esta máquina, que se\nreemplaza cada 90 días. Cambia de opinión cuando quieras:\n codewhale config set telemetry false\n\nEsquema completo, campo por campo: docs/TELEMETRY.md\n\nLas estadísticas de uso están activadas de forma predeterminada. Codewhale y PostHog procesan estos totales cuando el envío está configurado. No se recopilan IP.",
"TelemetryNoticeCompactBody": "Cuenta: versión, SO/CPU, tiempo/resultado, funciones/errores.\nID local rota/90d.\nNunca: chats/código/prompts/archivos/nombres; datos del modelo/credenciales; turnos/herramientas.\nVer: docs/TELEMETRY.md\nApag.: codewhale config set telemetry false\nLas estadísticas de uso están activadas de forma predeterminada. Codewhale y PostHog procesan estos totales cuando el envío está configurado. No se recopilan IP.",
"TelemetryNoticeChoiceKeep": "Sí, mantener el conteo anónimo",
"TelemetryNoticeChoiceDisable": "No, desactivar el seguimiento",
"TelemetryNoticeActionChoose": "elegir",
@@ -256,9 +264,9 @@
"ConfigLabelAllowShell": "Acceso al shell",
"ConfigLabelManagedAllowShell": "Acceso al shell (administrado)",
"ConfigLabelTelemetry": "Conteo anónimo de uso",
"ConfigHintTelemetry": "Preferencia guardada para conteos anónimos de funciones y errores; nunca conversaciones ni código",
"ConfigValueTelemetryOn": "Activado (guardado)",
"ConfigValueTelemetryOff": "Desactivado (guardado)",
"ConfigHintTelemetry": "Las estadísticas de uso están activadas de forma predeterminada. Compartir totales de versión, plataforma, sesiones, funciones y errores con Codewhale y su procesador PostHog. Sin contenido ni IP. docs/TELEMETRY.md",
"ConfigValueTelemetryOn": "Activado",
"ConfigValueTelemetryOff": "Desactivado",
"ConfigLabelStreamTimeout": "Tiempo límite del flujo",
"ConfigLabelTheme": "Tema",
"ConfigLabelLocale": "Idioma",
@@ -327,7 +335,6 @@
"ModelPickerAutoLocalHint": "por turno · heurística local · sin solicitud al enrutador",
"ModelPickerAutoLastRoute": "última {provider} · {model}",
"AutoRouteSelectedToast": "Auto: {provider} / {model} mediante {source} · Ctrl+O: detalles de la ruta",
"CloudCodeSystemPromptUnsupported": "Antigravity cloud-code no puede enviar este turno de forma segura porque esta conexión aún no admite instrucciones del sistema. No se envió nada; elige otro proveedor.",
"HelpTitle": "Ayuda",
"HelpFilterPlaceholder": "Escribe para filtrar",
"HelpFilterPrefix": "Filtro: ",
@@ -480,6 +487,12 @@
"CmdModeDescription": "Cambiar permisos o abrir el selector de modo",
"CmdModelDescription": "Cambiar o mostrar el modelo actual",
"CmdModelsDescription": "Obtener IDs de modelo en vivo de la API activa",
"ModelsListHeader": "Modelos de {provider} (predeterminado: {model})",
"ModelsListHint": "Catálogo en caché; ejecuta `codewhale models --update` para actualizar los proveedores configurados.",
"ModelsUpdateSummary": "Catálogos de modelos: {updated} actualizados, {loaded} cargados desde Codex, {failed} fallidos, {skipped} omitidos.",
"ModelsUpdatePartial": "No se pudieron actualizar algunos catálogos. Los modelos anteriores y configurados siguen disponibles.",
"ModelsCodexHint": "Los modelos de ChatGPT se cargan mediante Codex. La hora de consulta no confirma una actualización del catálogo del proveedor.",
"ModelsSourceFallback": "Modelos incluidos o configurados; disponibilidad sin verificar.",
"CmdModelDbDescription": "Explorar la base de datos de modelos integrada",
"CmdNetworkDescription": "Gestionar reglas de red permitidas y bloqueadas",
"CmdUpdateDescription": "Buscar e instalar una nueva versión de Codewhale",
@@ -547,7 +560,7 @@
"CmdStructcopyClipboardAccepted": "La copia estructural ({kind}, {bytes} bytes) se entregó al portapapeles; si no había un portapapeles nativo disponible, se puso en cola una escritura de terminal",
"CmdStructcopyClipboardFailed": "Falló la copia al portapapeles: {error}. No se escribió nada; vuelve a ejecutar con `stdout` para obtener una vista de texto explícita",
"CmdStructcopyReceiptTooLarge": "Los metadatos del recibo de copia estructural superan el límite de salida de {bytes} bytes; se rechaza la emisión",
"CmdFleetDescription": "Inspeccionar y configurar miembros de Fleet y orquestación",
"CmdFleetDescription": "Inspeccionar y configurar miembros del equipo y orquestación",
"CmdLaneDescription": "Inspeccionar y controlar Lanes duraderos (Workflows en ejecución)",
"CmdWorkflowDescription": "Ejecutar un script multiagente cuando importan el orden o el fan-out",
"CmdWorkflowsDescription": "Muestra las ejecuciones de flujo de trabajo de este workspace (listar, cancelar)",
@@ -600,6 +613,7 @@
"FooterBalancePrefix": "saldo",
"HelpSectionActions": "Acciones",
"HelpSectionClipboard": "Portapapeles",
"HelpSectionPointer": "Puntero",
"HelpSectionEditing": "Edición de entrada",
"HelpSectionHelp": "Ayuda",
"HelpSectionModes": "Modos",
@@ -660,14 +674,22 @@
"KbCopySelection": "Copiar la selección actual de Codewhale",
"ClipboardSshPasteHint": "El pegado por SSH lo gestiona tu terminal local: usa Cmd+V en macOS o Ctrl+Shift+V en Linux/Windows.",
"KbContextMenu": "Abrir acciones de contexto para pegar, selección, detalles, contexto y ayuda",
"KbPointerScroll": "Desplaza la transcripción; sobre el redactor, mueve el cursor línea por línea",
"KbPointerClick": "Activa la fila, el chip o el bloque de herramienta bajo el puntero",
"KbPointerDrag": "Selecciona texto de la transcripción, o arrastra la barra de desplazamiento",
"KbAttachPath": "Agregar archivo o directorio local al contexto",
"KbHelpOverlay": "Abrir esta superposición de ayuda (cuando la entrada está vacía)",
"KbCycleWorkDock": "Recorrer la barra de trabajo (tareas, agentes, trabajos, segundo plano)",
"KbCycleWorkDockBack": "Recorrer la barra de trabajo hacia atrás",
"KbToggleHelp": "Alternar superposición de ayuda",
"KbToggleHelpSlash": "Alternar superposición de ayuda",
"HelpUsageLabel": "Uso:",
"HelpAliasesLabel": "Alias:",
"SettingsTitle": "Configuraciones:",
"SettingsConfigFile": "Archivo de configuración:",
"SettingsTuiPrefsFolded": "Migramos {key} '{value}' desde tui.toml — ahora settings.toml es el dueño.",
"SettingsTuiPrefsKept": "tui.toml definía {key} como '{prefs}', pero settings.toml ya define '{settings}' — gana settings.toml.",
"SettingsTuiPrefsQuarantined": "Las claves de tui.toml sin ninguna opción se conservaron en {path}: {keys}",
"ClearConversation": "Conversación limpia",
"ClearConversationBusy": "No se borró nada (el estado de Trabajo o la ejecución está ocupada; espera y vuelve a intentar /clear)",
"ModelChanged": "Modelo cambiado: {old} →, {new}",
@@ -1149,8 +1171,8 @@
"NotificationSubagentCancelled": "Subagente cancelado",
"NotificationSubagentBudgetExhausted": "Presupuesto del subagente agotado",
"FooterWorkedChip": "trabajó {duration}",
"FleetDraftTitle": "Perfil de Fleet — borrador de {model_label} (g para guardar)",
"FleetDraftHeader": "# .codewhale/agents/{name}\n# Borrador por {model_label}, validado y limitado por Codewhale.\n# Permisos en el piso de Fleet: sin shell, sin confianza, aprobación requerida.\n# Nada se guarda hasta que presione g en el asistente.\n\n",
"FleetDraftTitle": "Perfil de equipo — borrador de {model_label} (g para guardar)",
"FleetDraftHeader": "# .codewhale/agents/{name}\n# Borrador por {model_label}, validado y limitado por Codewhale.\n# Permisos en el piso del equipo: sin shell, sin confianza, aprobación requerida.\n# Nada se guarda hasta que presione g en el asistente.\n\n",
"SetupRemoteOnRampText": "Remote Runtime On-Ramp\n\n/setup solo muestra datos del runtime remoto. No genera bundles, no escribe credenciales, no llama CLIs de cloud ni ejecuta `remote-setup`.\n\nDatos actuales:\n- Clouds: {clouds_result}\n- Puentes: {bridges_result}\n- Proveedores: {providers_result}\n- Modo: {mode_result}\n\nPara generar un bundle de deploy, ejecute explícitamente en un terminal normal:\n\n```sh\n{command}\n```\n\nEl RUNBOOK generado contiene los pasos de host para revisión humana. `--apply` sigue sin implementarse; no trate esto como auto-deploy.",
"ApprovalDescSafe": "Solicitando una operación segura/solo lectura.",
"ApprovalDescFileWrite": "Solicitando modificar un archivo. Confirme ruta y contenido.",
@@ -1234,6 +1256,11 @@
"CommandPaletteSubtitle": "Encuentra y ejecuta una acción",
"ConfigSubtitle": "Ajustes primero; claves crudas en detalle avanzado",
"LaunchStartTitle": "Empieza aquí",
"LaunchResumeConfirm": "¿Reanudar {title}? Haz clic otra vez o pulsa Enter para confirmar.",
"LaunchResumeConfirmTitle": "Reanudar sesión",
"LaunchResumeConfirmBody": "Esto reemplaza el contexto actual con el historial de esa sesión.",
"LaunchResumeConfirmResume": "reanudar",
"LaunchResumeConfirmCancel": "cancelar",
"LaunchWorkDescription": "Usa esta carpeta con herramientas locales; los cambios siguen tu política de aprobación.",
"LaunchChatDescription": "Solo conversación y planificación; no se modifican archivos.",
"LaunchWorkspaceGitReady": "Espacio de trabajo · {name} · espacio Git",
@@ -1259,7 +1286,6 @@
"LaunchWorktreeFailed": "El worktree falló: {error}",
"LaunchWorktreeCreated": "Nuevo worktree {path} en la rama {branch}",
"LaunchNoSavedSessions": "No hay sesiones guardadas para este workspace.",
"LaunchComposerHint": "Enter envía · Shift+Enter nueva línea · Esc vuelve",
"LaunchNoModelConnected": "ningún modelo conectado",
"LaunchRunCommand": "ejecuta {command}",
"LaunchMcpConnectedOne": "1 servidor MCP conectado",
@@ -1458,7 +1484,7 @@
"ProviderExternalRevokedToast": "Se revocó el acceso a credenciales externas para {provider}",
"ProviderExternalRevokeFailedToast": "No se revocó el acceso a credenciales externas: {error}",
"ThemeSurfaceTitle": "tema · vista previa en vivo",
"FleetRosterHeaderLabel": "fleet",
"FleetRosterHeaderLabel": "equipo",
"FleetRosterTabRoster": "miembros",
"FleetRosterTabSetup": "setup",
"FleetRosterWorkers": "workers",
@@ -1476,33 +1502,34 @@
"FleetProfileIdentityVerifyFailed": "No se pudieron verificar las identidades de perfiles existentes ({error}); corrige el archivo indicado antes de guardar.",
"FleetProfileIdConflict": "El id de perfil `{id}` ya está en uso por {path}; redacta de nuevo con otro rol o elimina primero el archivo antiguo.",
"FleetProfileProviderUnconfigured": "El perfil fija el proveedor `{provider}`, que no tiene credenciales configuradas ({env}); configúralo en /provider antes de guardar.",
"FleetModelAdded": "{route} agregado al fleet `{fleet}`",
"FleetModelAddedAs": "{route} agregado como {roles} al fleet `{fleet}`",
"FleetModelAddedCreatedNote": " (nuevo fleet personal, ahora seleccionado)",
"FleetModelAdded": "{route} agregado al equipo `{fleet}`",
"FleetModelAddedAs": "{route} agregado como {roles} al equipo `{fleet}`",
"FleetModelAddedCreatedNote": " (nuevo equipo personal, ahora seleccionado)",
"FleetModelAddedSelectedNote": " (ahora seleccionado)",
"FleetModelRemoved": "{route} quitado del fleet `{fleet}`",
"FleetModelRemovedRoles": "{route} ({roles}) quitado del fleet `{fleet}`",
"FleetModelUnchanged": "{route} se mantiene en el fleet `{fleet}`: {reason}",
"FleetModelReasonOperatorRoute": "este es tu modelo actual (la ruta del operador del fleet); cambia de modelo o usa /fleet save para modificarla",
"FleetModelReasonAlreadyPresent": "ya está en el fleet",
"FleetModelErrorNeedsRoute": "un modelo del fleet necesita un id de provider y un id de modelo",
"FleetModelErrorNoSelection": "no hay ningún fleet seleccionado; tu fleet es solo el modelo de la sesión",
"FleetModelErrorOperatorRoute": "{route} es la ruta del operador del fleet `{fleet}`; cámbiala con /fleet save, no con remove",
"FleetModelErrorNotInFleet": "{route} no está en el fleet `{fleet}`",
"FleetModelsEmpty": "Tu fleet es solo el modelo de la sesión. Agrega uno: /fleet add <provider> <model> [role…] (o ⇧F en una fila de /model).",
"FleetModelsHeader": "Tu fleet `{fleet}` ({count} modelos)",
"FleetModelsBroken": "No se pudo cargar el fleet seleccionado: {error}. Corrige el archivo o elige otro con /fleet fleets.",
"FleetModelRemoved": "{route} quitado del equipo `{fleet}`",
"FleetModelRemovedRoles": "{route} ({roles}) quitado del equipo `{fleet}`",
"FleetModelUnchanged": "{route} se mantiene en el equipo `{fleet}`: {reason}",
"FleetModelReasonOperatorRoute": "este es tu modelo actual (la ruta del operador del equipo); cambia de modelo o usa /fleet save para modificarla",
"FleetModelReasonAlreadyPresent": "ya está en el equipo",
"FleetModelErrorNeedsRoute": "un modelo del equipo necesita un id de provider y un id de modelo",
"FleetModelErrorNeedsRole": "un miembro del equipo es un rol; indica uno (por ejemplo `/fleet add {route} scout`)",
"FleetModelErrorNoSelection": "no hay ningún equipo seleccionado; tu equipo es solo el modelo de la sesión",
"FleetModelErrorOperatorRoute": "{route} es la ruta del operador del equipo `{fleet}`; cámbiala con /fleet save, no con remove",
"FleetModelErrorNotInFleet": "{route} no está en el equipo `{fleet}`",
"FleetModelsEmpty": "Tu equipo es solo el modelo de la sesión. Agrega uno: /fleet add <provider> <model> [role…] (o ⇧F en una fila de /model).",
"FleetModelsHeader": "Tu equipo `{fleet}` ({count} modelos)",
"FleetModelsBroken": "No se pudo cargar el equipo seleccionado: {error}. Corrige el archivo o elige otro con /fleet teams.",
"FleetModelsFooter": "Agregar: /fleet add <provider> <model> [role…] · Quitar: /fleet remove <provider> <model>",
"FleetModelsFactPrice": "${input}/{output} por M",
"FleetModelsFactContext": "{context}k ctx",
"FleetModelsFactTools": "herramientas",
"FleetAddUsage": "Uso: /fleet add <provider> <model> [role…] — p. ej. /fleet add openrouter z-ai/glm-5.3-flash explore",
"FleetAddProviderUnconfigured": "`{provider}` no es un provider configurado. Configúralo en ~/.codewhale/config.toml o cambia a él con /provider antes de agregarlo al fleet.",
"FleetAddProviderUnconfigured": "`{provider}` no es un provider configurado. Configúralo en ~/.codewhale/config.toml o cambia a él con /provider antes de agregarlo al equipo.",
"FleetAddModelNotServed": "{provider} no sirve `{model}` en el catálogo actual; ejecuta /models para ver qué sirve, o /fleet add con el id exacto que lista.",
"FleetAddFailed": "No se pudo agregar al fleet: {error}",
"FleetAddFailed": "No se pudo agregar al equipo: {error}",
"FleetRemoveUsage": "Uso: /fleet remove <provider> <model>",
"FleetRemoveFailed": "No se pudo quitar del fleet: {error}",
"FleetToggleFailed": "No se pudo actualizar el fleet: {error}",
"FleetRemoveFailed": "No se pudo quitar del equipo: {error}",
"FleetToggleFailed": "No se pudo actualizar el equipo: {error}",
"FleetDestStepTitle": "¿Dónde debe guardarse este perfil?",
"FleetDestStepSubtitle": "No se escribe nada hasta que confirmes en el último paso.",
"FleetDestProjectLabel": "Este proyecto",
@@ -1640,12 +1667,13 @@
"KbTurnInspector": "Abrir Inspector de turno",
"CmdTurnInspectDescription": "Open the whole-turn inspector",
"CmdAutomationDescription": "Administrar automatizaciones programadas duraderas",
"AutomationUsage": "Uso: /automation [list|show <id>|pause <id>|resume <id>|delete <id> [--confirm <token>]|run <id>]",
"AutomationUsage": "Uso: /automation [list|show <id>|print <id>|pause <id>|resume <id>|delete <id> [--confirm <token>]|run <id>]",
"AutomationManagerUnavailable": "El administrador de automatizaciones no está disponible en esta sesión.",
"AutomationListFailed": "No se pudieron listar las automatizaciones: {error}",
"AutomationActionFailed": "No se pudo {action} la automatización {id}: {error}",
"AutomationEmpty": "No hay automatizaciones programadas. Usa la herramienta `automation` para crear una.",
"AutomationEmpty": "No hay automatizaciones programadas. Elige Nueva automatización.",
"AutomationListHeading": "Automatizaciones programadas",
"AutomationScopeNote": "Las automatizaciones son tuyas, no de este repositorio: te siguen a todos los repositorios.",
"AutomationNoun": "Automatización",
"AutomationStatusLabel": "estado",
"AutomationStatusActive": "activa",
@@ -1660,6 +1688,7 @@
"AutomationActionResume": "reanudar",
"AutomationActionDelete": "eliminar",
"AutomationActionRun": "ejecutar",
"AutomationActionCancel": "cancelar ejecución",
"AutomationActionPaused": "pausada",
"AutomationActionResumed": "reanudada",
"AutomationNextLabel": "próxima",
@@ -2015,8 +2044,43 @@
"LaunchNoticeClaude": "Claude Code detectado. Ejecuta /import-claude para revisar qué puede importarse: servidores MCP, ajustes seguros y permisos. Nada se aplica sin tu aprobación.",
"LaunchNewSession": "Nueva sesión",
"LaunchRecentHeading": "Recientes",
"LaunchHelpLine": "/help para comandos · {dock} para la barra de trabajo",
"LaunchSeeAllSessions": "Ver todas las sesiones…",
"LaunchNoRecentSessions": "Aún no hay sesiones recientes — escribe abajo para empezar.",
"LaunchResumeFailed": "No se pudo reanudar: {error}",
"ReceiptSessionHooks": "hooks {count}"
"ReceiptSessionHooks": "hooks {count}",
"AutomationEditorNew": "Nueva automatización",
"AutomationEditorEdit": "Editar automatización",
"AutomationEditorSchedule": "Programación",
"AutomationEditorDaily": "Diaria",
"AutomationEditorWeekly": "Semanal",
"AutomationEditorHourly": "Cada hora",
"AutomationEditorOnce": "Una vez",
"AutomationEditorCustom": "Personalizada",
"AutomationEditorTime": "Hora (HH:MM)",
"AutomationEditorDate": "Fecha (YYYY-MM-DD)",
"AutomationEditorDays": "Días",
"AutomationEditorLocalTime": "Zona horaria: local ({zone})",
"AutomationEditorProvider": "El modelo fija su proveedor. Configuración actual: {provider}.",
"AutomationEditorDefaultModel": "Modelo predeterminado",
"AutomationEditorInheritedProvider": "valores predeterminados del sistema",
"AutomationEditorEnabled": "Habilitada",
"AutomationEditorControls": "Tab campos · Enter editar · ↑↓ ajustar · Ctrl+A seleccionar todo",
"AutomationEditorPromptControls": "Enter nueva línea · Tab siguiente campo · Ctrl+A seleccionar todo",
"AutomationEditorSaveFailed": "No se pudo guardar: {error}",
"AutomationEditorBusy": "El almacén está ocupado. Vuelve a guardar.",
"AutomationEditorSaved": "Se guardó {name}",
"AutomationEditorPreviewUnavailable": "Próxima ejecución no disponible; se conservará la programación",
"AutomationEditorConflict": "La automatización cambió. Cancela y vuelve a abrirla.",
"AutomationEditorInvalidWorkspace": "Elige un directorio de trabajo existente.",
"AutomationEditorModelControls": "Escribe para filtrar · ↑↓ mover · Enter elegir · Esc volver",
"AutomationEditorPausedPreview": "En pausa · sin ejecución programada",
"AutomationEditorInvalidSchedule": "Programación no válida: {error}",
"AutomationEditorMonday": "Lun",
"AutomationEditorTuesday": "Mar",
"AutomationEditorWednesday": "Mié",
"AutomationEditorThursday": "Jue",
"AutomationEditorFriday": "Vie",
"AutomationEditorSaturday": "Sáb",
"AutomationEditorSunday": "Dom"
}
+98 -34
View File
@@ -1,11 +1,15 @@
{
"ExtensionsActionAdd": "ajouter",
"ExtensionsActionEnable": "activer",
"ExtensionsActionEdit": "Modifier",
"ExtensionsHooksAddLabel": "Ajouter un hook",
"ExtensionsHooksAddDescription": "Ouvre le fichier de hooks de ce projet dans votre éditeur",
"ExtensionsActionReload": "recharger",
"ExtensionsActionConnect": "connecter",
"ExtensionsActionReconnect": "reconnecter",
"ExtensionsActionReauth": "réauthentifier",
"ExtensionsActionDiagnose": "diagnostiquer",
"ExtensionsActionManage": "gérer",
"ExtensionsActionFocus": "focus",
"ExtensionsActionFold": "replier",
"ExtensionsActionTabs": "onglets",
@@ -19,6 +23,7 @@
"ExtensionsGroupProblems": "Problèmes",
"ExtensionsGroupRecommended": "Recommandés",
"ExtensionsGroupServers": "Serveurs",
"ExtensionsGroupNeedsAttention": "Nécessite votre attention",
"ExtensionsGroupStatus": "{count} éléments · Entrée replie",
"ExtensionsGroupUser": "Utilisateur",
"ExtensionsGroupWorkspace": "Espace de travail",
@@ -45,6 +50,8 @@
"ExtensionsProductCuaDescription": "Environnement local d'utilisation de l'ordinateur avec pilote macOS natif.",
"ExtensionsProductDetail": "éditeur : {publisher} · composants : {components} · source : {source}",
"ExtensionsProductPlaywrightDescription": "Automatisation du navigateur fondée sur le Playwright MCP de Microsoft.",
"ExtensionsProductCodewhaleComputerUseDescription": "Voit l'écran et le pilote : contrôle par accessibilité, captures, zoom et enregistrement d'écran, plus des machines distantes en ssh.",
"ExtensionsStateFirstParty": "Fourni avec Codewhale",
"ExtensionsProductSandboxDescription": "Couche d'isolation des processus pour les commandes locales fournies par les plugins.",
"ExtensionsSearchLabel": "Rechercher : ",
"ExtensionsSkillRootCompatibleGlobal": "Global compatible ({harness})",
@@ -123,9 +130,10 @@
"HistoryHintRestore": "Esc restaurer",
"HistoryNoMatches": " Aucun résultat",
"TranscriptReasoningExpand": "développer",
"TelemetryNoticeHeadline": "Aidez-nous à améliorer Codewhale ?",
"TelemetryNoticeBody": "Codewhale compte : la version exécutée, le système d'exploitation et la\nfamille du processeur, la durée et le résultat de la session, ainsi que les\ntotaux de fonctions et d'erreurs.\n\nIl ne collecte jamais vos conversations, votre code, vos prompts, fichiers,\nnoms de fichiers, de dépôts ou de branches, contenus du modèle ou identifiants\n— et n'envoie jamais de chronologie de l'activité par tour ou par outil.\n\nVous êtes identifié uniquement par un identifiant aléatoire stocké sur cette\nmachine, remplacé tous les 90 jours. Changez d'avis à tout moment :\n codewhale config set telemetry false\n\nSchéma complet, champ par champ : docs/TELEMETRY.md",
"TelemetryNoticeCompactBody": "Compte: version, OS/CPU, durée/résultat, fonctions/erreurs.\nID local aléatoire change/90j.\nJamais: chats/code/prompts/fichiers/noms; contenu modèle/identifiants; tours/outils.\nVoir: docs/TELEMETRY.md\nArrêt: codewhale config set telemetry false",
"TelemetryNoticeDefaultOn": "Les statistiques d’utilisation sont activées par défaut. Partager les totaux de versions, plateformes, sessions, fonctions et erreurs avec Codewhale et son sous-traitant PostHog. Aucun contenu ni IP. docs/TELEMETRY.md Désactiver : codewhale config set telemetry false",
"TelemetryNoticeHeadline": "Statistiques d’utilisation de Codewhale",
"TelemetryNoticeBody": "Codewhale compte : la version exécutée, le système d'exploitation et la\nfamille du processeur, la durée et le résultat de la session, ainsi que les\ntotaux de fonctions et d'erreurs.\n\nIl ne collecte jamais vos conversations, votre code, vos prompts, fichiers,\nnoms de fichiers, de dépôts ou de branches, contenus du modèle ou identifiants\n— et n'envoie jamais de chronologie de l'activité par tour ou par outil.\n\nVous êtes identifié uniquement par un identifiant aléatoire stocké sur cette\nmachine, remplacé tous les 90 jours. Changez d'avis à tout moment :\n codewhale config set telemetry false\n\nSchéma complet, champ par champ : docs/TELEMETRY.md\n\nLes statistiques d’utilisation sont activées par défaut. Codewhale et PostHog traitent ces statistiques lorsque l’envoi est configuré. Aucune IP n’est collectée.",
"TelemetryNoticeCompactBody": "Compte: version, OS/CPU, durée/résultat, fonctions/erreurs.\nID local aléatoire change/90j.\nJamais: chats/code/prompts/fichiers/noms; contenu modèle/identifiants; tours/outils.\nVoir: docs/TELEMETRY.md\nArrêt: codewhale config set telemetry false\nLes statistiques d’utilisation sont activées par défaut. Codewhale et PostHog traitent ces statistiques lorsque l’envoi est configuré. Aucune IP n’est collectée.",
"TelemetryNoticeChoiceKeep": "Oui, conserver le comptage anonyme",
"TelemetryNoticeChoiceDisable": "Non, désactiver le suivi",
"TelemetryNoticeActionChoose": "choisir",
@@ -256,9 +264,9 @@
"ConfigLabelAllowShell": "Accès au shell",
"ConfigLabelManagedAllowShell": "Accès au shell (géré)",
"ConfigLabelTelemetry": "Comptage anonyme de l'utilisation",
"ConfigHintTelemetry": "Préférence enregistrée pour les comptes anonymes de fonctions et d'erreurs ; jamais les conversations ni le code",
"ConfigValueTelemetryOn": "Activé (enregistré)",
"ConfigValueTelemetryOff": "Désactivé (enregistré)",
"ConfigHintTelemetry": "Les statistiques d’utilisation sont activées par défaut. Partager les totaux de versions, plateformes, sessions, fonctions et erreurs avec Codewhale et son sous-traitant PostHog. Aucun contenu ni IP. docs/TELEMETRY.md",
"ConfigValueTelemetryOn": "Activé",
"ConfigValueTelemetryOff": "Désactivé",
"ConfigLabelStreamTimeout": "Délai du flux",
"ConfigLabelTheme": "Thème",
"ConfigLabelLocale": "Langue",
@@ -324,7 +332,6 @@
"ModelPickerAutoLocalHint": "par tour · heuristique locale · pas de requête au routeur",
"ModelPickerAutoLastRoute": "dernière {provider} · {model}",
"AutoRouteSelectedToast": "Auto : {provider} / {model} via {source} · Ctrl+O : détails de la route",
"CloudCodeSystemPromptUnsupported": "Antigravity cloud-code ne peut pas envoyer ce tour en toute sécurité, car cette connexion ne prend pas encore en charge les instructions système. Rien n’a été envoyé ; choisissez un autre fournisseur.",
"HelpTitle": "Aide",
"HelpFilterPlaceholder": "Taper pour filtrer",
"HelpFilterPrefix": "Filtre : ",
@@ -477,6 +484,12 @@
"CmdModeDescription": "Changer les permissions ou ouvrir le sélecteur de mode",
"CmdModelDescription": "Changer ou afficher le modèle actuel",
"CmdModelsDescription": "Récupérer les IDs de modèles en direct depuis l'API active",
"ModelsListHeader": "Modèles de {provider} (par défaut : {model})",
"ModelsListHint": "Catalogue en cache ; lancez `codewhale models --update` pour actualiser les fournisseurs configurés.",
"ModelsUpdateSummary": "Catalogues de modèles : {updated} actualisés, {loaded} chargés depuis Codex, {failed} en échec, {skipped} ignorés.",
"ModelsUpdatePartial": "Certains catalogues n’ont pas pu être actualisés. Les modèles précédents et configurés restent disponibles.",
"ModelsCodexHint": "Les modèles ChatGPT sont chargés via Codex. L’heure de consultation ne confirme pas une actualisation du catalogue du fournisseur.",
"ModelsSourceFallback": "Modèles fournis ou configurés ; disponibilité non vérifiée.",
"CmdModelDbDescription": "Parcourir la base de modèles intégrée",
"CmdNetworkDescription": "Gérer les règles réseau d'autorisation et de refus",
"CmdUpdateDescription": "Rechercher et installer une nouvelle version de Codewhale",
@@ -544,7 +557,7 @@
"CmdStructcopyClipboardAccepted": "La copie structurelle ({kind}, {bytes} octets) a été remise au presse-papiers ; si aucun presse-papiers natif n'était joignable, une écriture terminal a été mise en file d'attente à la place",
"CmdStructcopyClipboardFailed": "Échec de la copie vers le presse-papiers : {error}. Rien n'a été écrit ; relancez avec `stdout` pour un affichage texte explicite",
"CmdStructcopyReceiptTooLarge": "Les métadonnées du reçu de copie structurelle dépassent la limite de sortie de {bytes} octets ; émission refusée",
"CmdFleetDescription": "Inspecter et configurer les membres du Fleet et l'état d'orchestration",
"CmdFleetDescription": "Inspecter et configurer les membres de l'équipe et l'état d'orchestration",
"CmdWorkflowDescription": "Lancer un script multi-agents quand l'ordre ou le fan-out compte",
"CmdWorkflowsDescription": "Afficher les exécutions de workflow de cet espace de travail (lister, annuler)",
"CmdHotbarDescription": "Ouvrir la configuration Hotbar",
@@ -583,6 +596,7 @@
"FooterBalancePrefix": "solde",
"HelpSectionActions": "Actions",
"HelpSectionClipboard": "Presse-papiers",
"HelpSectionPointer": "Pointeur",
"HelpSectionEditing": "Édition de la saisie",
"HelpSectionHelp": "Aide",
"HelpSectionModes": "Modes",
@@ -643,14 +657,22 @@
"KbCopySelection": "Copier la sélection Codewhale actuelle",
"ClipboardSshPasteHint": "Le collage SSH passe par votre terminal local : Cmd+V sur macOS ou Ctrl+Shift+V sur Linux/Windows.",
"KbContextMenu": "Ouvrir les actions contextuelles : collage, sélection, détails du message, contexte et aide",
"KbPointerScroll": "Fait défiler la transcription ; sur le compositeur, déplace le curseur ligne par ligne",
"KbPointerClick": "Active la ligne, la puce ou le bloc d'outil sous le pointeur",
"KbPointerDrag": "Sélectionne du texte de la transcription, ou fait glisser la barre de défilement",
"KbAttachPath": "Ajouter un fichier texte ou dossier local au contexte",
"KbHelpOverlay": "Ouvrir cette aide (quand la saisie est vide)",
"KbCycleWorkDock": "Parcourir la barre de travail (tâches, agents, jobs, arrière-plan)",
"KbCycleWorkDockBack": "Parcourir la barre de travail en arrière",
"KbToggleHelp": "Basculer l'aide",
"KbToggleHelpSlash": "Basculer l'aide",
"HelpUsageLabel": "Usage :",
"HelpAliasesLabel": "Alias :",
"SettingsTitle": "Paramètres :",
"SettingsConfigFile": "Fichier de config :",
"SettingsTuiPrefsFolded": "{key} '{value}' migré depuis tui.toml — settings.toml le gère désormais.",
"SettingsTuiPrefsKept": "tui.toml définissait {key} sur '{prefs}', mais settings.toml définit déjà '{settings}' — settings.toml l'emporte.",
"SettingsTuiPrefsQuarantined": "Les clés de tui.toml sans réglage correspondant ont été conservées dans {path} : {keys}",
"ClearConversation": "Conversation effacée",
"ClearConversationBusy": "Rien n'a été effacé (état Work ou exécution occupée ; patientez, puis réessayez /clear)",
"ModelChanged": "Modèle changé : {old} →, {new}",
@@ -1128,8 +1150,8 @@
"NotificationSubagentCancelled": "Sous-agent annulé",
"NotificationSubagentBudgetExhausted": "Budget du sous-agent épuisé",
"FooterWorkedChip": "a travaillé {duration}",
"FleetDraftTitle": "Profil Fleet — brouillon par {model_label} (g enregistre)",
"FleetDraftHeader": "# .codewhale/agents/{name}\n# Rédigé par {model_label}, validé et borné par Codewhale.\n# Les permissions restent au plancher du Fleet : pas de shell, pas de confiance, approbation requise.\n# Rien n'est enregistré tant que vous n'appuyez pas sur g dans l'assistant.\n\n",
"FleetDraftTitle": "Profil d'équipe — brouillon par {model_label} (g enregistre)",
"FleetDraftHeader": "# .codewhale/agents/{name}\n# Rédigé par {model_label}, validé et borné par Codewhale.\n# Les permissions restent au plancher de l'équipe : pas de shell, pas de confiance, approbation requise.\n# Rien n'est enregistré tant que vous n'appuyez pas sur g dans l'assistant.\n\n",
"SetupRemoteOnRampText": "Amorçage du runtime distant\n\n/setup n'affiche que les faits du runtime distant. Il ne génère pas de bundle de déploiement, n'écrit pas d'identifiants, n'appelle pas de CLI cloud et n'exécute pas `remote-setup`.\n\nFaits actuels :\n- Clouds : {clouds_result}\n- Ponts de chat : {bridges_result}\n- Fournisseurs : {providers_result}\n- Mode : {mode_result}\n\nPour générer un bundle de déploiement, exécutez explicitement dans un terminal normal :\n\n```sh\n{command}\n```\n\nLe RUNBOOK généré inclut les étapes hôte pour revue humaine. `--apply` reste non implémenté ; ne le considérez pas comme un déploiement automatique.",
"ApprovalDescSafe": "Demande une opération sûre/en lecture seule.",
"ApprovalDescFileWrite": "Demande la modification d'un fichier. Veuillez confirmer le chemin et le contenu.",
@@ -1211,6 +1233,11 @@
"CommandPaletteSubtitle": "Trouvez et exécutez une action",
"ConfigSubtitle": "Réglages d'abord ; clés brutes dans le détail avancé",
"LaunchStartTitle": "Commencer ici",
"LaunchResumeConfirm": "Reprendre {title} ? Cliquez à nouveau ou appuyez sur Entrée pour confirmer.",
"LaunchResumeConfirmTitle": "Reprendre la session",
"LaunchResumeConfirmBody": "Cela remplace le contexte actuel par l'historique de cette session.",
"LaunchResumeConfirmResume": "reprendre",
"LaunchResumeConfirmCancel": "annuler",
"LaunchWorkDescription": "Utilisez ce dossier avec les outils locaux ; les modifications suivent votre politique d’approbation.",
"LaunchChatDescription": "Discussion et planification uniquement ; aucun fichier n’est modifié.",
"LaunchWorkspaceGitReady": "Espace de travail · {name} · espace Git",
@@ -1236,7 +1263,6 @@
"LaunchWorktreeFailed": "Échec du worktree : {error}",
"LaunchWorktreeCreated": "Nouveau worktree {path} sur la branche {branch}",
"LaunchNoSavedSessions": "Aucune session enregistrée pour ce workspace.",
"LaunchComposerHint": "Entrée envoie · Maj+Entrée nouvelle ligne · Échap retour",
"LaunchNoModelConnected": "aucun modèle connecté",
"LaunchRunCommand": "lancez {command}",
"LaunchMcpConnectedOne": "1 serveur MCP connecté",
@@ -1435,7 +1461,7 @@
"ProviderExternalRevokedToast": "Accès aux identifiants externes révoqué pour {provider}",
"ProviderExternalRevokeFailedToast": "L'accès aux identifiants externes n'a pas été révoqué : {error}",
"ThemeSurfaceTitle": "thème · aperçu en direct",
"FleetRosterHeaderLabel": "fleet",
"FleetRosterHeaderLabel": "équipe",
"FleetRosterTabRoster": "membres",
"FleetRosterTabSetup": "setup",
"FleetRosterWorkers": "workers",
@@ -1453,33 +1479,34 @@
"FleetProfileIdentityVerifyFailed": "Impossible de vérifier les identités des profils existants ({error}) ; corrigez le fichier indiqué avant d'enregistrer.",
"FleetProfileIdConflict": "L'id de profil `{id}` est déjà utilisé par {path} ; rédigez un nouveau brouillon avec un rôle différent ou supprimez d'abord l'ancien fichier.",
"FleetProfileProviderUnconfigured": "Le profil épingle le fournisseur `{provider}`, qui n'a pas d'identifiants configurés ({env}) ; configurez-le dans /provider avant d'enregistrer.",
"FleetModelAdded": "{route} ajouté au fleet `{fleet}`",
"FleetModelAddedAs": "{route} ajouté en tant que {roles} au fleet `{fleet}`",
"FleetModelAddedCreatedNote": " (nouveau fleet personnel, désormais sélectionné)",
"FleetModelAdded": "{route} ajouté à l'équipe `{fleet}`",
"FleetModelAddedAs": "{route} ajouté en tant que {roles} à l'équipe `{fleet}`",
"FleetModelAddedCreatedNote": " (nouvelle équipe personnelle, désormais sélectionnée)",
"FleetModelAddedSelectedNote": " (désormais sélectionné)",
"FleetModelRemoved": "{route} retiré du fleet `{fleet}`",
"FleetModelRemovedRoles": "{route} ({roles}) retiré du fleet `{fleet}`",
"FleetModelUnchanged": "{route} reste dans le fleet `{fleet}` : {reason}",
"FleetModelReasonOperatorRoute": "c'est votre modèle actuel (la route opérateur du fleet) ; changez de modèle ou utilisez /fleet save pour la modifier",
"FleetModelReasonAlreadyPresent": "déjà dans le fleet",
"FleetModelErrorNeedsRoute": "un modèle du fleet a besoin d'un id de provider et d'un id de modèle",
"FleetModelErrorNoSelection": "aucun fleet n'est sélectionné ; votre fleet se limite au modèle de la session",
"FleetModelErrorOperatorRoute": "{route} est la route opérateur du fleet `{fleet}` ; modifiez-la avec /fleet save, pas avec remove",
"FleetModelErrorNotInFleet": "{route} n'est pas dans le fleet `{fleet}`",
"FleetModelsEmpty": "Votre fleet se limite au modèle de la session. Ajoutez-en un : /fleet add <provider> <model> [role…] (ou ⇧F sur une ligne de /model).",
"FleetModelsHeader": "Votre fleet `{fleet}` ({count} modèles)",
"FleetModelsBroken": "Impossible de charger le fleet sélectionné : {error}. Corrigez le fichier ou choisissez-en un autre avec /fleet fleets.",
"FleetModelRemoved": "{route} retiré de l'équipe `{fleet}`",
"FleetModelRemovedRoles": "{route} ({roles}) retiré de l'équipe `{fleet}`",
"FleetModelUnchanged": "{route} reste dans l'équipe `{fleet}` : {reason}",
"FleetModelReasonOperatorRoute": "c'est votre modèle actuel (la route opérateur de l'équipe) ; changez de modèle ou utilisez /fleet save pour la modifier",
"FleetModelReasonAlreadyPresent": "déjà dans l'équipe",
"FleetModelErrorNeedsRoute": "un modèle de l'équipe a besoin d'un id de provider et d'un id de modèle",
"FleetModelErrorNeedsRole": "un membre de l'équipe est un rôle ; indiquez-en un (par exemple `/fleet add {route} scout`)",
"FleetModelErrorNoSelection": "aucune équipe n'est sélectionnée ; votre équipe se limite au modèle de la session",
"FleetModelErrorOperatorRoute": "{route} est la route opérateur de l'équipe `{fleet}` ; modifiez-la avec /fleet save, pas avec remove",
"FleetModelErrorNotInFleet": "{route} n'est pas dans l'équipe `{fleet}`",
"FleetModelsEmpty": "Votre équipe se limite au modèle de la session. Ajoutez-en un : /fleet add <provider> <model> [role…] (ou ⇧F sur une ligne de /model).",
"FleetModelsHeader": "Votre équipe `{fleet}` ({count} modèles)",
"FleetModelsBroken": "Impossible de charger l'équipe sélectionnée : {error}. Corrigez le fichier ou choisissez-en une autre avec /fleet teams.",
"FleetModelsFooter": "Ajouter : /fleet add <provider> <model> [role…] · Retirer : /fleet remove <provider> <model>",
"FleetModelsFactPrice": "${input}/{output} par M",
"FleetModelsFactContext": "{context}k ctx",
"FleetModelsFactTools": "outils",
"FleetAddUsage": "Usage : /fleet add <provider> <model> [role…] — ex. /fleet add openrouter z-ai/glm-5.3-flash explore",
"FleetAddProviderUnconfigured": "`{provider}` n'est pas un provider configuré. Configurez-le dans ~/.codewhale/config.toml ou basculez dessus avec /provider avant de l'ajouter au fleet.",
"FleetAddProviderUnconfigured": "`{provider}` n'est pas un provider configuré. Configurez-le dans ~/.codewhale/config.toml ou basculez dessus avec /provider avant de l'ajouter à l'équipe.",
"FleetAddModelNotServed": "{provider} ne sert pas `{model}` dans le catalogue actuel ; lancez /models pour voir ce qu'il sert, ou /fleet add avec l'id exact listé.",
"FleetAddFailed": "Impossible d'ajouter au fleet : {error}",
"FleetAddFailed": "Impossible d'ajouter à l'équipe : {error}",
"FleetRemoveUsage": "Usage : /fleet remove <provider> <model>",
"FleetRemoveFailed": "Impossible de retirer du fleet : {error}",
"FleetToggleFailed": "Impossible de mettre à jour le fleet : {error}",
"FleetRemoveFailed": "Impossible de retirer de l'équipe : {error}",
"FleetToggleFailed": "Impossible de mettre à jour l'équipe : {error}",
"FleetDestStepTitle": "Où ce profil doit-il être enregistré ?",
"FleetDestStepSubtitle": "Rien n'est écrit tant que vous ne confirmez pas à la dernière étape.",
"FleetDestProjectLabel": "Ce projet",
@@ -1640,12 +1667,13 @@
"KbTurnInspector": "Ouvrir l'inspecteur de tour",
"CmdTurnInspectDescription": "Open the whole-turn inspector",
"CmdAutomationDescription": "Gérer les automatisations planifiées durables",
"AutomationUsage": "Utilisation : /automation [list|show <id>|pause <id>|resume <id>|delete <id> [--confirm <token>]|run <id>]",
"AutomationUsage": "Utilisation : /automation [list|show <id>|print <id>|pause <id>|resume <id>|delete <id> [--confirm <token>]|run <id>]",
"AutomationManagerUnavailable": "Le gestionnaire d’automatisations n’est pas disponible dans cette session.",
"AutomationListFailed": "Impossible de lister les automatisations : {error}",
"AutomationActionFailed": "Impossible de {action} l’automatisation {id} : {error}",
"AutomationEmpty": "Aucune automatisation planifiée. Utilisez l’outil `automation` pour en créer une.",
"AutomationEmpty": "Aucune automatisation planifiée. Choisissez Nouvelle automatisation.",
"AutomationListHeading": "Automatisations planifiées",
"AutomationScopeNote": "Les automatisations sont à vous, pas à ce dépôt : elles vous suivent dans chaque dépôt.",
"AutomationNoun": "Automatisation",
"AutomationStatusLabel": "état",
"AutomationStatusActive": "active",
@@ -1660,6 +1688,7 @@
"AutomationActionResume": "reprendre",
"AutomationActionDelete": "supprimer",
"AutomationActionRun": "exécuter",
"AutomationActionCancel": "annuler l'exécution",
"AutomationActionPaused": "mise en pause",
"AutomationActionResumed": "reprise",
"AutomationNextLabel": "prochaine",
@@ -2015,8 +2044,43 @@
"LaunchNoticeClaude": "Claude Code détecté. Lancez /import-claude pour examiner ce qui peut être repris : serveurs MCP, réglages sûrs et permissions. Rien n'est appliqué sans votre accord.",
"LaunchNewSession": "Nouvelle session",
"LaunchRecentHeading": "Récent",
"LaunchHelpLine": "/help pour les commandes · {dock} pour la barre de travail",
"LaunchSeeAllSessions": "Voir toutes les sessions…",
"LaunchNoRecentSessions": "Aucune session récente — écrivez ci-dessous pour commencer.",
"LaunchResumeFailed": "Échec de la reprise : {error}",
"ReceiptSessionHooks": "hooks {count}"
"ReceiptSessionHooks": "hooks {count}",
"AutomationEditorNew": "Nouvelle automatisation",
"AutomationEditorEdit": "Modifier l’automatisation",
"AutomationEditorSchedule": "Planification",
"AutomationEditorDaily": "Quotidienne",
"AutomationEditorWeekly": "Hebdomadaire",
"AutomationEditorHourly": "Toutes les heures",
"AutomationEditorOnce": "Une fois",
"AutomationEditorCustom": "Personnalisée",
"AutomationEditorTime": "Heure (HH:MM)",
"AutomationEditorDate": "Date (YYYY-MM-DD)",
"AutomationEditorDays": "Jours",
"AutomationEditorLocalTime": "Fuseau horaire : local ({zone})",
"AutomationEditorProvider": "Le modèle fixe son fournisseur. Configuration actuelle : {provider}.",
"AutomationEditorDefaultModel": "Modèle par défaut",
"AutomationEditorInheritedProvider": "réglages par défaut du système",
"AutomationEditorEnabled": "Activée",
"AutomationEditorControls": "Tab champs · Enter modifier · ↑↓ ajuster · Ctrl+A tout sélectionner",
"AutomationEditorPromptControls": "Enter nouvelle ligne · Tab champ suivant · Ctrl+A tout sélectionner",
"AutomationEditorSaveFailed": "Impossible d’enregistrer : {error}",
"AutomationEditorBusy": "Le stockage est occupé. Réessayez d’enregistrer.",
"AutomationEditorSaved": "{name} enregistré",
"AutomationEditorPreviewUnavailable": "Prochaine exécution indisponible ; la planification sera conservée",
"AutomationEditorConflict": "L’automatisation a changé. Annulez puis rouvrez-la.",
"AutomationEditorInvalidWorkspace": "Choisissez un dossier de travail existant.",
"AutomationEditorModelControls": "Saisir pour filtrer · ↑↓ déplacer · Enter choisir · Esc retour",
"AutomationEditorPausedPreview": "En pause · aucune exécution prévue",
"AutomationEditorInvalidSchedule": "Planification invalide : {error}",
"AutomationEditorMonday": "Lun",
"AutomationEditorTuesday": "Mar",
"AutomationEditorWednesday": "Mer",
"AutomationEditorThursday": "Jeu",
"AutomationEditorFriday": "Ven",
"AutomationEditorSaturday": "Sam",
"AutomationEditorSunday": "Dim"
}
+98 -34
View File
@@ -1,11 +1,15 @@
{
"ExtensionsActionAdd": "जोड़ें",
"ExtensionsActionEnable": "सक्षम करें",
"ExtensionsActionEdit": "संपादित करें",
"ExtensionsHooksAddLabel": "हुक जोड़ें",
"ExtensionsHooksAddDescription": "इस प्रोजेक्ट की हुक फ़ाइल अपने एडिटर में खोलें",
"ExtensionsActionReload": "पुनः लोड करें",
"ExtensionsActionConnect": "कनेक्ट करें",
"ExtensionsActionReconnect": "फिर कनेक्ट करें",
"ExtensionsActionReauth": "फिर प्रमाणित करें",
"ExtensionsActionDiagnose": "निदान करें",
"ExtensionsActionManage": "प्रबंधित करें",
"ExtensionsActionFocus": "फ़ोकस",
"ExtensionsActionFold": "समेटें",
"ExtensionsActionTabs": "टैब",
@@ -19,6 +23,7 @@
"ExtensionsGroupProblems": "समस्याएँ",
"ExtensionsGroupRecommended": "अनुशंसित",
"ExtensionsGroupServers": "सर्वर",
"ExtensionsGroupNeedsAttention": "ध्यान चाहिए",
"ExtensionsGroupStatus": "{count} आइटम · समेटने के लिए Enter",
"ExtensionsGroupUser": "उपयोगकर्ता",
"ExtensionsGroupWorkspace": "कार्यस्थान",
@@ -45,6 +50,8 @@
"ExtensionsProductCuaDescription": "मूल macOS ड्राइवर वाला स्थानीय कंप्यूटर-उपयोग रनटाइम।",
"ExtensionsProductDetail": "प्रकाशक: {publisher} · घटक: {components} · स्रोत: {source}",
"ExtensionsProductPlaywrightDescription": "Microsoft के Playwright MCP पर आधारित ब्राउज़र स्वचालन।",
"ExtensionsProductCodewhaleComputerUseDescription": "स्क्रीन देखें और चलाएँ: एक्सेसिबिलिटी-आधारित नियंत्रण, स्क्रीनशॉट, ज़ूम और स्क्रीन रिकॉर्डिंग, साथ ही ssh से दूरस्थ कंप्यूटर।",
"ExtensionsStateFirstParty": "Codewhale के साथ शामिल",
"ExtensionsProductSandboxDescription": "प्लगइन द्वारा दिए स्थानीय कमांड के लिए प्रक्रिया अलगाव परत।",
"ExtensionsSearchLabel": "खोजें: ",
"ExtensionsSkillRootCompatibleGlobal": "संगत वैश्विक ({harness})",
@@ -123,9 +130,10 @@
"HistoryHintRestore": "Esc पुनर्स्थापित",
"HistoryNoMatches": " कोई मिलान नहीं",
"TranscriptReasoningExpand": "विस्तार",
"TelemetryNoticeHeadline": "क्या आप Codewhale को बेहतर बनाने में मदद करेंगे?",
"TelemetryNoticeBody": "Codewhale गणना करता है: चल रहा संस्करण, ऑपरेटिंग सिस्टम व प्रोसेसर परिवार,\nसत्र की अवधि व परिणाम, और सुविधाओं व त्रुटियों की कुल गणना।\n\nयह आपकी बातचीत, कोड, प्रॉम्प्ट, फ़ाइलें, फ़ाइल, भंडार या शाखा के नाम,\nमॉडल की सामग्री या परिचय प्रमाण कभी एकत्र नहीं करता, और प्रत्येक चरण या\nऔज़ार की एजेंट गतिविधि का क्रम भी कभी नहीं भेजता।\n\nपहचान के लिए केवल इस मशीन पर रखी एक आकस्मिक पहचान संख्या उपयोग होती है,\nजो हर 90 दिन में बदल दी जाती है। कभी भी अपना निर्णय बदलें:\n codewhale config set telemetry false\n\nहर क्षेत्र सहित पूरा प्रारूप: docs/TELEMETRY.md",
"TelemetryNoticeCompactBody": "गणना: संस्करण, सिस्टम/प्रोसेसर, सत्र अवधि/परिणाम और सुविधा/त्रुटि योग।\nआकस्मिक स्थानीय पहचान हर 90 दिन में बदलती है।\nकभी नहीं: बातचीत/कोड/प्रॉम्प्ट/फ़ाइल/नाम, मॉडल सामग्री/परिचय प्रमाण, चरण/औज़ार।\nप्रारूप: docs/TELEMETRY.md\nबंद: codewhale config set telemetry false",
"TelemetryNoticeDefaultOn": "उपयोग आँकड़ों की रिपोर्टिंग डिफ़ॉल्ट रूप से चालू है। संस्करण, प्लेटफ़ॉर्म, सत्र, सुविधा और त्रुटि के कुल आँकड़े Codewhale और प्रोसेसर PostHog से साझा करें। सामग्री या IP नहीं। docs/TELEMETRY.md बंद करें: codewhale config set telemetry false",
"TelemetryNoticeHeadline": "Codewhale उपयोग आँकड़े",
"TelemetryNoticeBody": "Codewhale गणना करता है: चल रहा संस्करण, ऑपरेटिंग सिस्टम व प्रोसेसर परिवार,\nसत्र की अवधि व परिणाम, और सुविधाओं व त्रुटियों की कुल गणना।\n\nयह आपकी बातचीत, कोड, प्रॉम्प्ट, फ़ाइलें, फ़ाइल, भंडार या शाखा के नाम,\nमॉडल की सामग्री या परिचय प्रमाण कभी एकत्र नहीं करता, और प्रत्येक चरण या\nऔज़ार की एजेंट गतिविधि का क्रम भी कभी नहीं भेजता।\n\nपहचान के लिए केवल इस मशीन पर रखी एक आकस्मिक पहचान संख्या उपयोग होती है,\nजो हर 90 दिन में बदल दी जाती है। कभी भी अपना निर्णय बदलें:\n codewhale config set telemetry false\n\nहर क्षेत्र सहित पूरा प्रारूप: docs/TELEMETRY.md\n\nउपयोग आँकड़ों की रिपोर्टिंग डिफ़ॉल्ट रूप से चालू है। भेजने की व्यवस्था कॉन्फ़िगर होने पर Codewhale और PostHog इन आँकड़ों को संसाधित करते हैं। IP एकत्र नहीं किया जाता।",
"TelemetryNoticeCompactBody": "गणना: संस्करण, सिस्टम/प्रोसेसर, सत्र अवधि/परिणाम और सुविधा/त्रुटि योग।\nआकस्मिक स्थानीय पहचान हर 90 दिन में बदलती है।\nकभी नहीं: बातचीत/कोड/प्रॉम्प्ट/फ़ाइल/नाम, मॉडल सामग्री/परिचय प्रमाण, चरण/औज़ार।\nप्रारूप: docs/TELEMETRY.md\nबंद: codewhale config set telemetry false\nउपयोग आँकड़ों की रिपोर्टिंग डिफ़ॉल्ट रूप से चालू है। भेजने की व्यवस्था कॉन्फ़िगर होने पर Codewhale और PostHog इन आँकड़ों को संसाधित करते हैं। IP एकत्र नहीं किया जाता।",
"TelemetryNoticeChoiceKeep": "हाँ, अनाम गणना जारी रखें",
"TelemetryNoticeChoiceDisable": "नहीं, ट्रैकिंग बंद करें",
"TelemetryNoticeActionChoose": "चुनें",
@@ -256,9 +264,9 @@
"ConfigLabelAllowShell": "शेल एक्सेस",
"ConfigLabelManagedAllowShell": "शेल एक्सेस (प्रबंधित)",
"ConfigLabelTelemetry": "अनाम उपयोग गणना",
"ConfigHintTelemetry": "अनाम सुविधा और त्रुटि गणना की सहेजी गई प्राथमिकता; बातचीत या कोड कभी नहीं",
"ConfigValueTelemetryOn": "चालू (सहेजा गया)",
"ConfigValueTelemetryOff": "बंद (सहेजा गया)",
"ConfigHintTelemetry": "उपयोग आँकड़ों की रिपोर्टिंग डिफ़ॉल्ट रूप से चालू है। संस्करण, प्लेटफ़ॉर्म, सत्र, सुविधा और त्रुटि के कुल आँकड़े Codewhale और प्रोसेसर PostHog से साझा करें। सामग्री या IP नहीं। docs/TELEMETRY.md",
"ConfigValueTelemetryOn": "चालू",
"ConfigValueTelemetryOff": "बंद",
"ConfigLabelStreamTimeout": "स्ट्रीम टाइमआउट",
"ConfigLabelTheme": "थीम",
"ConfigLabelLocale": "भाषा",
@@ -324,7 +332,6 @@
"ModelPickerAutoLocalHint": "प्रति टर्न · लोकल ह्यूरिस्टिक · कोई राउटर अनुरोध नहीं",
"ModelPickerAutoLastRoute": "अंतिम {provider} · {model}",
"AutoRouteSelectedToast": "Auto: {provider} / {model} ({source} द्वारा) · Ctrl+O: रूट विवरण",
"CloudCodeSystemPromptUnsupported": "यह कनेक्शन अभी सिस्टम निर्देश समर्थित नहीं करता, इसलिए Antigravity cloud-code इस टर्न को सुरक्षित रूप से नहीं भेज सकता। कुछ नहीं भेजा गया; कोई अन्य प्रदाता चुनें।",
"HelpTitle": "मदद",
"HelpFilterPlaceholder": "फ़िल्टर करने के लिए टाइप करें",
"HelpFilterPrefix": "फ़िल्टर: ",
@@ -477,6 +484,12 @@
"CmdModeDescription": "अनुमतियाँ बदलें या मोड पिकर खोलें",
"CmdModelDescription": "वर्तमान मॉडल बदलें या देखें",
"CmdModelsDescription": "सक्रिय API से लाइव मॉडल ID लाएँ",
"ModelsListHeader": "{provider} के मॉडल (डिफ़ॉल्ट: {model})",
"ModelsListHint": "कैश में सहेजी सूची; कॉन्फ़िगर किए प्रदाताओं को अपडेट करने के लिए `codewhale models --update` चलाएँ।",
"ModelsUpdateSummary": "मॉडल कैटलॉग: {updated} अपडेट हुए, {loaded} Codex से लोड हुए, {failed} विफल हुए, {skipped} छोड़े गए।",
"ModelsUpdatePartial": "कुछ सूचियाँ अपडेट नहीं हो सकीं। पिछले और कॉन्फ़िगर किए मॉडल उपलब्ध हैं।",
"ModelsCodexHint": "ChatGPT मॉडल Codex के ज़रिए लोड होते हैं। खोज का समय प्रदाता के कैटलॉग के अपडेट होने की पुष्टि नहीं करता।",
"ModelsSourceFallback": "शामिल या कॉन्फ़िगर किए मॉडल; उपलब्धता सत्यापित नहीं है।",
"CmdModelDbDescription": "बंडल किया मॉडल डेटाबेस ब्राउज़ करें",
"CmdNetworkDescription": "नेटवर्क अनुमति और निषेध नियम प्रबंधित करें",
"CmdUpdateDescription": "नया Codewhale रिलीज़ जाँचें और इंस्टॉल करें",
@@ -544,7 +557,7 @@
"CmdStructcopyClipboardAccepted": "संरचनात्मक कॉपी ({kind}, {bytes} बाइट) क्लिपबोर्ड को सौंपी गई; यदि कोई मूल क्लिपबोर्ड उपलब्ध नहीं था, तो इसके बजाय टर्मिनल लेखन कतार में रखा गया",
"CmdStructcopyClipboardFailed": "क्लिपबोर्ड कॉपी विफल: {error}. कुछ भी नहीं लिखा गया; स्पष्ट पाठ दृश्य के लिए `stdout` के साथ फिर चलाएँ",
"CmdStructcopyReceiptTooLarge": "संरचनात्मक-कॉपी रसीद मेटाडेटा {bytes}-बाइट आउटपुट सीमा से अधिक है; इसे भेजने से इनकार",
"CmdFleetDescription": "Fleet सदस्यों और ऑर्केस्ट्रेशन स्थिति देखें और सेटअप करें",
"CmdFleetDescription": "टीम सदस्यों और ऑर्केस्ट्रेशन स्थिति देखें और सेटअप करें",
"CmdWorkflowDescription": "क्रम या फैन-आउट ज़रूरी हो तो मल्टी-एजेंट स्क्रिप्ट चलाएँ",
"CmdWorkflowsDescription": "इस वर्कस्पेस के वर्कफ़्लो रन दिखाएँ (सूची, रद्द करें)",
"CmdHotbarDescription": "Hotbar सेटअप खोलें",
@@ -583,6 +596,7 @@
"FooterBalancePrefix": "शेष",
"HelpSectionActions": "क्रियाएँ",
"HelpSectionClipboard": "क्लिपबोर्ड",
"HelpSectionPointer": "पॉइंटर",
"HelpSectionEditing": "इनपुट संपादन",
"HelpSectionHelp": "मदद",
"HelpSectionModes": "मोड",
@@ -643,14 +657,22 @@
"KbCopySelection": "वर्तमान Codewhale चयन कॉपी करें",
"ClipboardSshPasteHint": "SSH पेस्ट आपके लोकल टर्मिनल का उपयोग करता है: macOS पर Cmd+V या Linux/Windows पर Ctrl+Shift+V दबाएँ।",
"KbContextMenu": "पेस्ट, चयन, संदेश विवरण, संदर्भ और मदद के लिए संदर्भ क्रियाएँ खोलें",
"KbPointerScroll": "ट्रांसक्रिप्ट स्क्रॉल करें; कंपोज़र पर, कर्सर एक पंक्ति खिसकाएँ",
"KbPointerClick": "पॉइंटर के नीचे की पंक्ति, चिप या टूल ब्लॉक सक्रिय करें",
"KbPointerDrag": "ट्रांसक्रिप्ट टेक्स्ट चुनें, या स्क्रॉलबार खींचें",
"KbAttachPath": "संदर्भ में लोकल टेक्स्ट फ़ाइल या डायरेक्टरी जोड़ें",
"KbHelpOverlay": "यह मदद ओवरले खोलें (जब इनपुट खाली हो)",
"KbCycleWorkDock": "वर्क डॉक बदलें (टूडू, एजेंट, जॉब, बैकग्राउंड)",
"KbCycleWorkDockBack": "वर्क डॉक पीछे की ओर बदलें",
"KbToggleHelp": "मदद ओवरले टॉगल करें",
"KbToggleHelpSlash": "मदद ओवरले टॉगल करें",
"HelpUsageLabel": "उपयोग:",
"HelpAliasesLabel": "उपनाम:",
"SettingsTitle": "सेटिंग्स:",
"SettingsConfigFile": "कॉन्फ़िग फ़ाइल:",
"SettingsTuiPrefsFolded": "tui.toml से {key} '{value}' माइग्रेट किया — अब settings.toml इसका मालिक है।",
"SettingsTuiPrefsKept": "tui.toml ने {key} को '{prefs}' सेट किया था, पर settings.toml पहले से '{settings}' सेट करता है — settings.toml जीतता है।",
"SettingsTuiPrefsQuarantined": "जिन tui.toml कुंजियों की कोई सेटिंग नहीं है, वे {path} में रखी गई हैं: {keys}",
"ClearConversation": "वार्तालाप साफ़ किया गया",
"ClearConversationBusy": "कुछ साफ़ नहीं हुआ (कार्य स्थिति या रनटाइम कार्य व्यस्त है; प्रतीक्षा करें, फिर /clear दोबारा आज़माएँ)",
"ModelChanged": "मॉडल बदला गया: {old} →, {new}",
@@ -1128,8 +1150,8 @@
"NotificationSubagentCancelled": "सब-एजेंट रद्द",
"NotificationSubagentBudgetExhausted": "सब-एजेंट बजट समाप्त",
"FooterWorkedChip": "{duration} काम किया",
"FleetDraftTitle": "Fleet प्रोफ़ाइल — {model_label} का मसौदा (g से सहेजें)",
"FleetDraftHeader": "# .codewhale/agents/{name}\n# {model_label} द्वारा मसौदा, Codewhale द्वारा सत्यापित और सीमाबद्ध।\n# अनुमतियाँ Fleet न्यूनतम स्तर पर रहती हैं: कोई शेल नहीं, कोई ट्रस्ट नहीं, अनुमति आवश्यक।\n# जब तक आप विज़ार्ड में g नहीं दबाते, कुछ सहेजा नहीं जाता।\n\n",
"FleetDraftTitle": "टीम प्रोफ़ाइल — {model_label} का मसौदा (g से सहेजें)",
"FleetDraftHeader": "# .codewhale/agents/{name}\n# {model_label} द्वारा मसौदा, Codewhale द्वारा सत्यापित और सीमाबद्ध।\n# अनुमतियाँ टीम न्यूनतम स्तर पर रहती हैं: कोई शेल नहीं, कोई ट्रस्ट नहीं, अनुमति आवश्यक।\n# जब तक आप विज़ार्ड में g नहीं दबाते, कुछ सहेजा नहीं जाता।\n\n",
"SetupRemoteOnRampText": "रिमोट रनटाइम ऑन-रैंप\n\n/setup केवल रिमोट रनटाइम तथ्य दिखाता है। यह डिप्लॉय बंडल नहीं बनाता, क्रेडेंशल नहीं लिखता, क्लाउड CLI नहीं बुलाता, और `remote-setup` नहीं चलाता।\n\nवर्तमान तथ्य:\n- क्लाउड: {clouds_result}\n- चैट ब्रिज: {bridges_result}\n- प्रोवाइडर: {providers_result}\n- मोड: {mode_result}\n\nडिप्लॉय बंडल बनाने के लिए, सामान्य टर्मिनल में स्पष्ट रूप से चलाएँ:\n\n```sh\n{command}\n```\n\nबनाया गया RUNBOOK मानव समीक्षा के लिए होस्ट चरण शामिल करता है। `--apply` अभी लागू नहीं है; इसे ऑटो-डिप्लॉय न समझें।",
"ApprovalDescSafe": "सुरक्षित/रीड-ओनली ऑपरेशन का अनुरोध।",
"ApprovalDescFileWrite": "फ़ाइल बदलने का अनुरोध। पथ और सामग्री की पुष्टि करें।",
@@ -1211,6 +1233,11 @@
"CommandPaletteSubtitle": "एक कार्रवाई खोजें और चलाएँ",
"ConfigSubtitle": "पहले सेटिंग्स; रॉ कुंजियाँ उन्नत विवरण में",
"LaunchStartTitle": "यहाँ से शुरू करें",
"LaunchResumeConfirm": "{title} फिर से शुरू करें? पुष्टि के लिए दोबारा क्लिक करें या Enter दबाएँ।",
"LaunchResumeConfirmTitle": "सत्र फिर से शुरू करें",
"LaunchResumeConfirmBody": "इससे मौजूदा संदर्भ उस सत्र के इतिहास से बदल जाएगा।",
"LaunchResumeConfirmResume": "जारी रखें",
"LaunchResumeConfirmCancel": "रद्द करें",
"LaunchWorkDescription": "इस फ़ोल्डर में स्थानीय टूल इस्तेमाल करें; बदलाव आपकी स्वीकृति नीति के अनुसार होंगे।",
"LaunchChatDescription": "सिर्फ़ बातचीत और योजना; फ़ाइलों में कोई बदलाव नहीं।",
"LaunchWorkspaceGitReady": "वर्कस्पेस · {name} · Git वर्कस्पेस",
@@ -1236,7 +1263,6 @@
"LaunchWorktreeFailed": "worktree विफल: {error}",
"LaunchWorktreeCreated": "नया worktree {path}, ब्रांच {branch} पर",
"LaunchNoSavedSessions": "इस वर्कस्पेस के लिए कोई सहेजा सत्र नहीं।",
"LaunchComposerHint": "Enter भेजें · Shift+Enter नई पंक्ति · Esc वापस",
"LaunchNoModelConnected": "कोई मॉडल कनेक्ट नहीं",
"LaunchRunCommand": "{command} चलाएँ",
"LaunchMcpConnectedOne": "1 MCP सर्वर कनेक्टेड",
@@ -1435,7 +1461,7 @@
"ProviderExternalRevokedToast": "{provider} के लिए बाहरी क्रेडेंशल एक्सेस रद्द",
"ProviderExternalRevokeFailedToast": "बाहरी क्रेडेंशल एक्सेस रद्द नहीं हुआ: {error}",
"ThemeSurfaceTitle": "थीम · लाइव प्रीव्यू",
"FleetRosterHeaderLabel": "fleet",
"FleetRosterHeaderLabel": "टीम",
"FleetRosterTabRoster": "सदस्य",
"FleetRosterTabSetup": "सेटअप",
"FleetRosterWorkers": "वर्कर",
@@ -1453,33 +1479,34 @@
"FleetProfileIdentityVerifyFailed": "मौजूदा प्रोफ़ाइल पहचान सत्यापित नहीं हो सकी ({error}); सहेजने से पहले नामित फ़ाइल ठीक करें।",
"FleetProfileIdConflict": "प्रोफ़ाइल id `{id}` पहले से {path} इस्तेमाल कर रहा है; दूसरी भूमिका से फिर मसौदा बनाएँ या पहले पुरानी फ़ाइल हटाएँ।",
"FleetProfileProviderUnconfigured": "प्रोफ़ाइल प्रोवाइडर `{provider}` पिन करती है, जिसके क्रेडेंशल कॉन्फ़िगर नहीं ({env}); सहेजने से पहले /provider में सेटअप करें।",
"FleetModelAdded": "{route} को fleet `{fleet}` में जोड़ा गया",
"FleetModelAddedAs": "{route} को {roles} के रूप में fleet `{fleet}` में जोड़ा गया",
"FleetModelAddedCreatedNote": " (नया व्यक्तिगत fleet, अब चयनित)",
"FleetModelAdded": "{route} को टीम `{fleet}` में जोड़ा गया",
"FleetModelAddedAs": "{route} को {roles} के रूप में टीम `{fleet}` में जोड़ा गया",
"FleetModelAddedCreatedNote": " (नई व्यक्तिगत टीम, अब चयनित)",
"FleetModelAddedSelectedNote": " (अब चयनित)",
"FleetModelRemoved": "{route} को fleet `{fleet}` से हटाया गया",
"FleetModelRemovedRoles": "{route} ({roles}) को fleet `{fleet}` से हटाया गया",
"FleetModelUnchanged": "{route} fleet `{fleet}` में बना रहता है: {reason}",
"FleetModelReasonOperatorRoute": "यह आपका वर्तमान मॉडल है (fleet का operator रूट); मॉडल बदलें या इसे बदलने के लिए /fleet save का उपयोग करें",
"FleetModelReasonAlreadyPresent": "पहले से fleet में है",
"FleetModelErrorNeedsRoute": "fleet मॉडल के लिए provider id और model id दोनों ज़रूरी हैं",
"FleetModelErrorNoSelection": "कोई fleet चयनित नहीं है; आपका fleet केवल सत्र मॉडल है",
"FleetModelErrorOperatorRoute": "{route} fleet `{fleet}` का operator रूट है; इसे remove से नहीं, /fleet save से बदलें",
"FleetModelErrorNotInFleet": "{route} fleet `{fleet}` में नहीं है",
"FleetModelsEmpty": "आपका fleet केवल सत्र मॉडल है। एक जोड़ें: /fleet add <provider> <model> [role…] (या /model की किसी पंक्ति पर ⇧F)।",
"FleetModelsHeader": "आपका fleet `{fleet}` ({count} मॉडल)",
"FleetModelsBroken": "चयनित fleet लोड नहीं हो सका: {error}। फ़ाइल ठीक करें या /fleet fleets से दूसरा चुनें।",
"FleetModelRemoved": "{route} को टीम `{fleet}` से हटाया गया",
"FleetModelRemovedRoles": "{route} ({roles}) को टीम `{fleet}` से हटाया गया",
"FleetModelUnchanged": "{route} टीम `{fleet}` में बना रहता है: {reason}",
"FleetModelReasonOperatorRoute": "यह आपका वर्तमान मॉडल है (टीम का operator रूट); मॉडल बदलें या इसे बदलने के लिए /fleet save का उपयोग करें",
"FleetModelReasonAlreadyPresent": "पहले से टीम में है",
"FleetModelErrorNeedsRoute": "टीम मॉडल के लिए provider id और model id दोनों ज़रूरी हैं",
"FleetModelErrorNeedsRole": "टीम का सदस्य एक रोल है; कोई एक बताएँ (उदाहरण `/fleet add {route} scout`)",
"FleetModelErrorNoSelection": "कोई टीम चयनित नहीं है; आपकी टीम केवल सत्र मॉडल है",
"FleetModelErrorOperatorRoute": "{route} टीम `{fleet}` का operator रूट है; इसे remove से नहीं, /fleet save से बदलें",
"FleetModelErrorNotInFleet": "{route} टीम `{fleet}` में नहीं है",
"FleetModelsEmpty": "आपकी टीम केवल सत्र मॉडल है। एक जोड़ें: /fleet add <provider> <model> [role…] (या /model की किसी पंक्ति पर ⇧F)।",
"FleetModelsHeader": "आपकी टीम `{fleet}` ({count} मॉडल)",
"FleetModelsBroken": "चयनित टीम लोड नहीं हो सकी: {error}। फ़ाइल ठीक करें या /fleet teams से दूसरा चुनें।",
"FleetModelsFooter": "जोड़ें: /fleet add <provider> <model> [role…] · हटाएँ: /fleet remove <provider> <model>",
"FleetModelsFactPrice": "${input}/{output} प्रति M",
"FleetModelsFactContext": "{context}k ctx",
"FleetModelsFactTools": "टूल",
"FleetAddUsage": "उपयोग: /fleet add <provider> <model> [role…] — जैसे /fleet add openrouter z-ai/glm-5.3-flash explore",
"FleetAddProviderUnconfigured": "`{provider}` कोई कॉन्फ़िगर किया गया provider नहीं है। इसे fleet में जोड़ने से पहले ~/.codewhale/config.toml में कॉन्फ़िगर करें या /provider से इस पर स्विच करें।",
"FleetAddProviderUnconfigured": "`{provider}` कोई कॉन्फ़िगर किया गया provider नहीं है। इसे टीम में जोड़ने से पहले ~/.codewhale/config.toml में कॉन्फ़िगर करें या /provider से इस पर स्विच करें।",
"FleetAddModelNotServed": "{provider} वर्तमान कैटलॉग में `{model}` उपलब्ध नहीं कराता; यह क्या देता है देखने के लिए /models चलाएँ, या सूची में दिए सटीक id के साथ /fleet add करें।",
"FleetAddFailed": "fleet में जोड़ा नहीं जा सका: {error}",
"FleetAddFailed": "टीम में जोड़ा नहीं जा सका: {error}",
"FleetRemoveUsage": "उपयोग: /fleet remove <provider> <model>",
"FleetRemoveFailed": "fleet से हटाया नहीं जा सका: {error}",
"FleetToggleFailed": "fleet अपडेट नहीं हो सका: {error}",
"FleetRemoveFailed": "टीम से हटाया नहीं जा सका: {error}",
"FleetToggleFailed": "टीम अपडेट नहीं हो सकी: {error}",
"FleetDestStepTitle": "यह प्रोफ़ाइल कहाँ सहेजी जाए?",
"FleetDestStepSubtitle": "अंतिम चरण में पुष्टि करने तक कुछ भी नहीं लिखा जाता।",
"FleetDestProjectLabel": "यह प्रोजेक्ट",
@@ -1640,12 +1667,13 @@
"KbTurnInspector": "Turn Inspector खोलें",
"CmdTurnInspectDescription": "Open the whole-turn inspector",
"CmdAutomationDescription": "स्थायी अनुसूचित स्वचालन प्रबंधित करें",
"AutomationUsage": "उपयोग: /automation [list|show <id>|pause <id>|resume <id>|delete <id> [--confirm <token>]|run <id>]",
"AutomationUsage": "उपयोग: /automation [list|show <id>|print <id>|pause <id>|resume <id>|delete <id> [--confirm <token>]|run <id>]",
"AutomationManagerUnavailable": "इस सत्र में स्वचालन प्रबंधक उपलब्ध नहीं है।",
"AutomationListFailed": "स्वचालनों की सूची नहीं मिल सकी: {error}",
"AutomationActionFailed": "स्वचालन {id} को {action} नहीं किया जा सका: {error}",
"AutomationEmpty": "कोई अनुसूचित स्वचालन नहीं है। नया बनाने के लिए `automation` उपकरण का उपयोग करें।",
"AutomationEmpty": "कोई स्वचालन निर्धारित नहीं है। नया स्वचालन चुनें।",
"AutomationListHeading": "अनुसूचित स्वचालन",
"AutomationScopeNote": "ऑटोमेशन आपके हैं, इस रिपॉज़िटरी के नहीं — वे हर रिपॉज़िटरी में आपके साथ चलते हैं।",
"AutomationNoun": "स्वचालन",
"AutomationStatusLabel": "स्थिति",
"AutomationStatusActive": "सक्रिय",
@@ -1660,6 +1688,7 @@
"AutomationActionResume": "फिर चलाने",
"AutomationActionDelete": "हटाने",
"AutomationActionRun": "चलाने",
"AutomationActionCancel": "रन रद्द करें",
"AutomationActionPaused": "रोका गया",
"AutomationActionResumed": "फिर चलाया गया",
"AutomationNextLabel": "अगला",
@@ -2015,8 +2044,43 @@
"LaunchNoticeClaude": "Claude Code मिला। क्या लाया जा सकता है यह देखने के लिए /import-claude चलाएँ: MCP सर्वर, सुरक्षित सेटिंग्स और अनुमतियाँ। आपकी स्वीकृति के बिना कुछ लागू नहीं होता।",
"LaunchNewSession": "नया सत्र",
"LaunchRecentHeading": "हाल के",
"LaunchHelpLine": "कमांड के लिए /help · वर्क बार के लिए {dock}",
"LaunchSeeAllSessions": "सभी सत्र देखें…",
"LaunchNoRecentSessions": "अभी कोई हालिया सत्र नहीं — शुरू करने के लिए नीचे लिखें।",
"LaunchResumeFailed": "फिर से शुरू विफल: {error}",
"ReceiptSessionHooks": "हुक {count}"
"ReceiptSessionHooks": "हुक {count}",
"AutomationEditorNew": "नया स्वचालन",
"AutomationEditorEdit": "स्वचालन संपादित करें",
"AutomationEditorSchedule": "समय-सारणी",
"AutomationEditorDaily": "रोज़ाना",
"AutomationEditorWeekly": "साप्ताहिक",
"AutomationEditorHourly": "हर घंटे",
"AutomationEditorOnce": "एक बार",
"AutomationEditorCustom": "कस्टम",
"AutomationEditorTime": "समय (HH:MM)",
"AutomationEditorDate": "तारीख (YYYY-MM-DD)",
"AutomationEditorDays": "दिन",
"AutomationEditorLocalTime": "समय क्षेत्र: स्थानीय ({zone})",
"AutomationEditorProvider": "मॉडल का प्रदाता तय रहेगा। मौजूदा कॉन्फ़िगरेशन: {provider}।",
"AutomationEditorDefaultModel": "डिफ़ॉल्ट मॉडल",
"AutomationEditorInheritedProvider": "रनटाइम के डिफ़ॉल्ट",
"AutomationEditorEnabled": "सक्षम",
"AutomationEditorControls": "Tab फ़ील्ड · Enter संपादन · ↑↓ समायोजन · Ctrl+A सब चुनें",
"AutomationEditorPromptControls": "Enter नई पंक्ति · Tab अगला फ़ील्ड · Ctrl+A सब चुनें",
"AutomationEditorSaveFailed": "सहेजा नहीं जा सका: {error}",
"AutomationEditorBusy": "स्वचालन संग्रह व्यस्त है। फिर सहेजें।",
"AutomationEditorSaved": "{name} सहेजा गया",
"AutomationEditorPreviewUnavailable": "अगला रन उपलब्ध नहीं; मौजूदा समय-सारणी बनी रहेगी",
"AutomationEditorConflict": "स्वचालन बदल गया है। रद्द करके फिर खोलें।",
"AutomationEditorInvalidWorkspace": "मौजूदा कार्यस्थान फ़ोल्डर चुनें।",
"AutomationEditorModelControls": "फ़िल्टर के लिए लिखें · ↑↓ जाएँ · Enter चुनें · Esc वापस",
"AutomationEditorPausedPreview": "रुका हुआ · कोई रन निर्धारित नहीं",
"AutomationEditorInvalidSchedule": "अमान्य समय-सारणी: {error}",
"AutomationEditorMonday": "सोम",
"AutomationEditorTuesday": "मंगल",
"AutomationEditorWednesday": "बुध",
"AutomationEditorThursday": "गुरु",
"AutomationEditorFriday": "शुक्र",
"AutomationEditorSaturday": "शनि",
"AutomationEditorSunday": "रवि"
}
+98 -34
View File
@@ -1,11 +1,15 @@
{
"ExtensionsActionAdd": "tambahkan",
"ExtensionsActionEnable": "aktifkan",
"ExtensionsActionEdit": "Ubah",
"ExtensionsHooksAddLabel": "Tambah hook",
"ExtensionsHooksAddDescription": "Buka berkas hook proyek ini di editor Anda",
"ExtensionsActionReload": "muat ulang",
"ExtensionsActionConnect": "hubungkan",
"ExtensionsActionReconnect": "hubungkan ulang",
"ExtensionsActionReauth": "otentikasi ulang",
"ExtensionsActionDiagnose": "diagnosa",
"ExtensionsActionManage": "kelola",
"ExtensionsActionFocus": "fokus",
"ExtensionsActionFold": "lipat",
"ExtensionsActionTabs": "tab",
@@ -19,6 +23,7 @@
"ExtensionsGroupProblems": "Masalah",
"ExtensionsGroupRecommended": "Disarankan",
"ExtensionsGroupServers": "Server",
"ExtensionsGroupNeedsAttention": "Perlu perhatian",
"ExtensionsGroupStatus": "{count} item · Enter untuk melipat",
"ExtensionsGroupUser": "Pengguna",
"ExtensionsGroupWorkspace": "Ruang kerja",
@@ -45,6 +50,8 @@
"ExtensionsProductCuaDescription": "Runtime penggunaan komputer lokal dengan driver macOS native.",
"ExtensionsProductDetail": "penerbit: {publisher} · komponen: {components} · sumber: {source}",
"ExtensionsProductPlaywrightDescription": "Otomatisasi browser yang dibangun dengan Playwright MCP Microsoft.",
"ExtensionsProductCodewhaleComputerUseDescription": "Melihat layar dan mengoperasikannya: kendali berbasis aksesibilitas, tangkapan layar, zoom, dan perekaman layar, plus komputer jarak jauh lewat ssh.",
"ExtensionsStateFirstParty": "Disertakan dengan Codewhale",
"ExtensionsProductSandboxDescription": "Lapisan isolasi proses untuk perintah lokal yang disediakan plugin.",
"ExtensionsSearchLabel": "Cari: ",
"ExtensionsSkillRootCompatibleGlobal": "Global kompatibel ({harness})",
@@ -123,9 +130,10 @@
"HistoryHintRestore": "Esc pulihkan",
"HistoryNoMatches": " Tidak ada hasil",
"TranscriptReasoningExpand": "perluas",
"TelemetryNoticeHeadline": "Bantu tingkatkan Codewhale?",
"TelemetryNoticeBody": "Codewhale menghitung: versi yang dijalankan, sistem operasi dan keluarga CPU,\ndurasi dan hasil sesi, serta hitungan agregat fitur dan galat.\n\nIa tidak pernah mengumpulkan percakapan, kode, prompt, berkas, nama berkas,\nrepositori atau cabang, konten model, maupun kredensial Anda — dan tidak pernah\nmengirim linimasa aktivitas agen per giliran atau per alat.\n\nAnda hanya dikenali melalui ID acak yang disimpan di mesin ini, diganti setiap\n90 hari. Ubah keputusan kapan saja:\n codewhale config set telemetry false\n\nSkema lengkap, per bidang: docs/TELEMETRY.md",
"TelemetryNoticeCompactBody": "Hitungan: versi, sistem/CPU, durasi/hasil, serta total fitur/galat.\nID lokal acak diganti setiap 90 hari.\nTidak pernah: percakapan/kode/prompt/berkas/nama, konten model/kredensial, giliran/alat.\nSkema: docs/TELEMETRY.md\nNonaktifkan: codewhale config set telemetry false",
"TelemetryNoticeDefaultOn": "Pelaporan penggunaan aktif secara bawaan. Bagikan total versi, platform, sesi, fitur, dan galat dengan Codewhale dan pemroses PostHog. Tanpa konten atau IP. docs/TELEMETRY.md Nonaktifkan: codewhale config set telemetry false",
"TelemetryNoticeHeadline": "Pelaporan penggunaan Codewhale",
"TelemetryNoticeBody": "Codewhale menghitung: versi yang dijalankan, sistem operasi dan keluarga CPU,\ndurasi dan hasil sesi, serta hitungan agregat fitur dan galat.\n\nIa tidak pernah mengumpulkan percakapan, kode, prompt, berkas, nama berkas,\nrepositori atau cabang, konten model, maupun kredensial Anda — dan tidak pernah\nmengirim linimasa aktivitas agen per giliran atau per alat.\n\nAnda hanya dikenali melalui ID acak yang disimpan di mesin ini, diganti setiap\n90 hari. Ubah keputusan kapan saja:\n codewhale config set telemetry false\n\nSkema lengkap, per bidang: docs/TELEMETRY.md\n\nPelaporan penggunaan aktif secara bawaan. Codewhale dan PostHog memproses hitungan ini jika pengiriman dikonfigurasi. IP tidak dikumpulkan.",
"TelemetryNoticeCompactBody": "Hitungan: versi, sistem/CPU, durasi/hasil, serta total fitur/galat.\nID lokal acak diganti setiap 90 hari.\nTidak pernah: percakapan/kode/prompt/berkas/nama, konten model/kredensial, giliran/alat.\nSkema: docs/TELEMETRY.md\nNonaktifkan: codewhale config set telemetry false\nPelaporan penggunaan aktif secara bawaan. Codewhale dan PostHog memproses hitungan ini jika pengiriman dikonfigurasi. IP tidak dikumpulkan.",
"TelemetryNoticeChoiceKeep": "Ya, pertahankan penghitungan anonim",
"TelemetryNoticeChoiceDisable": "Tidak, matikan pelacakan",
"TelemetryNoticeActionChoose": "pilih",
@@ -256,9 +264,9 @@
"ConfigLabelAllowShell": "Akses shell",
"ConfigLabelManagedAllowShell": "Akses shell (terkelola)",
"ConfigLabelTelemetry": "Penghitungan penggunaan anonim",
"ConfigHintTelemetry": "Preferensi tersimpan untuk hitungan fitur dan kesalahan anonim; tidak pernah percakapan atau kode",
"ConfigValueTelemetryOn": "Aktif (tersimpan)",
"ConfigValueTelemetryOff": "Nonaktif (tersimpan)",
"ConfigHintTelemetry": "Pelaporan penggunaan aktif secara bawaan. Bagikan total versi, platform, sesi, fitur, dan galat dengan Codewhale dan pemroses PostHog. Tanpa konten atau IP. docs/TELEMETRY.md",
"ConfigValueTelemetryOn": "Aktif",
"ConfigValueTelemetryOff": "Nonaktif",
"ConfigLabelStreamTimeout": "Batas waktu stream",
"ConfigLabelTheme": "Tema",
"ConfigLabelLocale": "Bahasa",
@@ -324,7 +332,6 @@
"ModelPickerAutoLocalHint": "per giliran · heuristik lokal · tanpa permintaan router",
"ModelPickerAutoLastRoute": "terakhir {provider} · {model}",
"AutoRouteSelectedToast": "Auto: {provider} / {model} via {source} · Ctrl+O: detail rute",
"CloudCodeSystemPromptUnsupported": "Antigravity cloud-code tidak dapat mengirim giliran ini dengan aman karena koneksi ini belum mendukung instruksi sistem. Tidak ada yang dikirim; pilih penyedia lain.",
"HelpTitle": "Bantuan",
"HelpFilterPlaceholder": "Ketik untuk menyaring",
"HelpFilterPrefix": "Saring: ",
@@ -477,6 +484,12 @@
"CmdModeDescription": "Ganti izin atau buka pemilih mode",
"CmdModelDescription": "Ganti atau lihat model saat ini",
"CmdModelsDescription": "Ambil ID model langsung dari API aktif",
"ModelsListHeader": "Model {provider} (bawaan: {model})",
"ModelsListHint": "Katalog tersimpan; jalankan `codewhale models --update` untuk memperbarui penyedia yang dikonfigurasi.",
"ModelsUpdateSummary": "Katalog model: {updated} diperbarui, {loaded} dimuat dari Codex, {failed} gagal, {skipped} dilewati.",
"ModelsUpdatePartial": "Beberapa katalog tidak dapat diperbarui. Model sebelumnya dan yang dikonfigurasi tetap tersedia.",
"ModelsCodexHint": "Model ChatGPT dimuat melalui Codex. Waktu pencarian tidak memastikan katalog penyedia telah diperbarui.",
"ModelsSourceFallback": "Model bawaan atau yang dikonfigurasi; ketersediaan belum diverifikasi.",
"CmdModelDbDescription": "Telusuri database model bawaan",
"CmdNetworkDescription": "Kelola aturan izinkan dan tolak jaringan",
"CmdUpdateDescription": "Periksa dan pasang rilis Codewhale baru",
@@ -544,7 +557,7 @@
"CmdStructcopyClipboardAccepted": "Salinan struktural ({kind}, {bytes} bita) telah diserahkan ke papan klip; jika tidak ada papan klip asli yang terjangkau, penulisan terminal diantrekan sebagai gantinya",
"CmdStructcopyClipboardFailed": "Penyalinan ke papan klip gagal: {error}. Tidak ada yang ditulis; jalankan lagi dengan `stdout` untuk tampilan teks eksplisit",
"CmdStructcopyReceiptTooLarge": "Metadata tanda terima salinan struktural melampaui batas keluaran {bytes} bita; penerbitannya ditolak",
"CmdFleetDescription": "Periksa dan siapkan anggota Fleet serta status orkestrasi",
"CmdFleetDescription": "Periksa dan siapkan anggota tim serta status orkestrasi",
"CmdWorkflowDescription": "Jalankan skrip multi-agen saat urutan atau fan-out penting",
"CmdWorkflowsDescription": "Tampilkan eksekusi alur kerja di workspace ini (daftar, batalkan)",
"CmdHotbarDescription": "Buka penyiapan Hotbar",
@@ -583,6 +596,7 @@
"FooterBalancePrefix": "saldo",
"HelpSectionActions": "Aksi",
"HelpSectionClipboard": "Papan klip",
"HelpSectionPointer": "Penunjuk",
"HelpSectionEditing": "Pengeditan input",
"HelpSectionHelp": "Bantuan",
"HelpSectionModes": "Mode",
@@ -643,14 +657,22 @@
"KbCopySelection": "Salin pilihan Codewhale saat ini",
"ClipboardSshPasteHint": "Tempel via SSH memakai terminal lokal Anda: tekan Cmd+V di macOS atau Ctrl+Shift+V di Linux/Windows.",
"KbContextMenu": "Buka aksi konteks untuk tempel, pilihan, detail pesan, konteks, dan bantuan",
"KbPointerScroll": "Gulir transkrip; di atas komposer, pindahkan kursor satu baris",
"KbPointerClick": "Aktifkan baris, chip, atau blok alat di bawah penunjuk",
"KbPointerDrag": "Pilih teks transkrip, atau seret bilah gulir",
"KbAttachPath": "Tambahkan file teks atau direktori lokal ke konteks",
"KbHelpOverlay": "Buka overlay bantuan ini (saat input kosong)",
"KbCycleWorkDock": "Putar bilah kerja (todo, agen, pekerjaan, latar belakang)",
"KbCycleWorkDockBack": "Putar bilah kerja mundur",
"KbToggleHelp": "Alihkan overlay bantuan",
"KbToggleHelpSlash": "Alihkan overlay bantuan",
"HelpUsageLabel": "Penggunaan:",
"HelpAliasesLabel": "Alias:",
"SettingsTitle": "Pengaturan:",
"SettingsConfigFile": "File konfig:",
"SettingsTuiPrefsFolded": "Memindahkan {key} '{value}' dari tui.toml — sekarang settings.toml yang memilikinya.",
"SettingsTuiPrefsKept": "tui.toml menyetel {key} ke '{prefs}', tetapi settings.toml sudah menyetel '{settings}' — settings.toml menang.",
"SettingsTuiPrefsQuarantined": "Kunci tui.toml tanpa pengaturan yang cocok disimpan di {path}: {keys}",
"ClearConversation": "Percakapan dibersihkan",
"ClearConversationBusy": "Tidak ada yang dibersihkan (status Work atau pekerjaan runtime sibuk; tunggu, lalu coba /clear lagi)",
"ModelChanged": "Model diubah: {old} → {new}",
@@ -1128,8 +1150,8 @@
"NotificationSubagentCancelled": "Sub-agent dibatalkan",
"NotificationSubagentBudgetExhausted": "Anggaran sub-agent habis",
"FooterWorkedChip": "bekerja {duration}",
"FleetDraftTitle": "Profil Fleet — draf oleh {model_label} (g menyimpan)",
"FleetDraftHeader": "# .codewhale/agents/{name}\n# Draf oleh {model_label}, divalidasi dan dibatasi oleh Codewhale.\n# Izin tetap di batas dasar Fleet: tanpa shell, tanpa trust, perlu persetujuan.\n# Tidak ada yang disimpan sampai Anda menekan g di wizard.\n\n",
"FleetDraftTitle": "Profil tim — draf oleh {model_label} (g menyimpan)",
"FleetDraftHeader": "# .codewhale/agents/{name}\n# Draf oleh {model_label}, divalidasi dan dibatasi oleh Codewhale.\n# Izin tetap di batas dasar tim: tanpa shell, tanpa trust, perlu persetujuan.\n# Tidak ada yang disimpan sampai Anda menekan g di wizard.\n\n",
"SetupRemoteOnRampText": "Jalur Awal Runtime Remote\n\n/setup hanya menampilkan fakta runtime remote. Tidak membuat bundle deploy, menulis kredensial, memanggil CLI cloud, atau menjalankan `remote-setup`.\n\nFakta saat ini:\n- Cloud: {clouds_result}\n- Bridge chat: {bridges_result}\n- Provider: {providers_result}\n- Mode: {mode_result}\n\nUntuk membuat bundle deploy, jalankan secara eksplisit di terminal normal:\n\n```sh\n{command}\n```\n\nRUNBOOK yang dihasilkan mencakup langkah host untuk ditinjau manusia. `--apply` belum diimplementasikan; jangan anggap ini auto-deploy.",
"ApprovalDescSafe": "Meminta operasi aman/baca-saja.",
"ApprovalDescFileWrite": "Meminta untuk mengubah file. Harap konfirmasi path dan konten.",
@@ -1211,6 +1233,11 @@
"CommandPaletteSubtitle": "Temukan dan jalankan satu aksi",
"ConfigSubtitle": "Pengaturan dulu; kunci mentah di detail lanjutan",
"LaunchStartTitle": "Mulai di sini",
"LaunchResumeConfirm": "Lanjutkan {title}? Klik lagi atau tekan Enter untuk mengonfirmasi.",
"LaunchResumeConfirmTitle": "Lanjutkan sesi",
"LaunchResumeConfirmBody": "Ini mengganti konteks saat ini dengan riwayat sesi tersebut.",
"LaunchResumeConfirmResume": "lanjutkan",
"LaunchResumeConfirmCancel": "batal",
"LaunchWorkDescription": "Gunakan folder ini dengan alat lokal; perubahan mengikuti kebijakan persetujuan Anda.",
"LaunchChatDescription": "Hanya percakapan dan perencanaan; tidak ada perubahan file.",
"LaunchWorkspaceGitReady": "Ruang kerja · {name} · ruang kerja Git",
@@ -1236,7 +1263,6 @@
"LaunchWorktreeFailed": "Worktree gagal: {error}",
"LaunchWorktreeCreated": "Worktree baru {path} di branch {branch}",
"LaunchNoSavedSessions": "Tidak ada sesi tersimpan untuk workspace ini.",
"LaunchComposerHint": "Enter kirim · Shift+Enter baris baru · Esc kembali",
"LaunchNoModelConnected": "tidak ada model terhubung",
"LaunchRunCommand": "jalankan {command}",
"LaunchMcpConnectedOne": "1 server MCP terhubung",
@@ -1435,7 +1461,7 @@
"ProviderExternalRevokedToast": "Akses kredensial eksternal dicabut untuk {provider}",
"ProviderExternalRevokeFailedToast": "Akses kredensial eksternal tidak tercabut: {error}",
"ThemeSurfaceTitle": "tema · pratinjau langsung",
"FleetRosterHeaderLabel": "fleet",
"FleetRosterHeaderLabel": "tim",
"FleetRosterTabRoster": "anggota",
"FleetRosterTabSetup": "setup",
"FleetRosterWorkers": "worker",
@@ -1453,33 +1479,34 @@
"FleetProfileIdentityVerifyFailed": "Tidak dapat memverifikasi identitas profil yang ada ({error}); perbaiki file yang disebutkan sebelum menyimpan.",
"FleetProfileIdConflict": "Id profil `{id}` sudah dipakai oleh {path}; buat ulang draf dengan peran berbeda atau hapus file lama dulu.",
"FleetProfileProviderUnconfigured": "Profil mengunci provider `{provider}` yang belum punya kredensial terkonfigurasi ({env}); atur di /provider sebelum menyimpan.",
"FleetModelAdded": "{route} ditambahkan ke fleet `{fleet}`",
"FleetModelAddedAs": "{route} ditambahkan sebagai {roles} ke fleet `{fleet}`",
"FleetModelAddedCreatedNote": " (fleet pribadi baru, kini dipilih)",
"FleetModelAdded": "{route} ditambahkan ke tim `{fleet}`",
"FleetModelAddedAs": "{route} ditambahkan sebagai {roles} ke tim `{fleet}`",
"FleetModelAddedCreatedNote": " (tim pribadi baru, kini dipilih)",
"FleetModelAddedSelectedNote": " (kini dipilih)",
"FleetModelRemoved": "{route} dihapus dari fleet `{fleet}`",
"FleetModelRemovedRoles": "{route} ({roles}) dihapus dari fleet `{fleet}`",
"FleetModelUnchanged": "{route} tetap di fleet `{fleet}`: {reason}",
"FleetModelReasonOperatorRoute": "ini model Anda saat ini (rute operator fleet); ganti model atau gunakan /fleet save untuk mengubahnya",
"FleetModelReasonAlreadyPresent": "sudah ada di fleet",
"FleetModelErrorNeedsRoute": "model fleet memerlukan id provider dan id model",
"FleetModelErrorNoSelection": "tidak ada fleet yang dipilih; fleet Anda hanya model sesi",
"FleetModelErrorOperatorRoute": "{route} adalah rute operator fleet `{fleet}`; ubah dengan /fleet save, bukan remove",
"FleetModelErrorNotInFleet": "{route} tidak ada di fleet `{fleet}`",
"FleetModelsEmpty": "Fleet Anda hanya model sesi. Tambahkan satu: /fleet add <provider> <model> [role…] (atau ⇧F pada baris di /model).",
"FleetModelsHeader": "Fleet Anda `{fleet}` ({count} model)",
"FleetModelsBroken": "Fleet yang dipilih tidak dapat dimuat: {error}. Perbaiki berkasnya atau pilih yang lain dengan /fleet fleets.",
"FleetModelRemoved": "{route} dihapus dari tim `{fleet}`",
"FleetModelRemovedRoles": "{route} ({roles}) dihapus dari tim `{fleet}`",
"FleetModelUnchanged": "{route} tetap di tim `{fleet}`: {reason}",
"FleetModelReasonOperatorRoute": "ini model Anda saat ini (rute operator tim); ganti model atau gunakan /fleet save untuk mengubahnya",
"FleetModelReasonAlreadyPresent": "sudah ada di tim",
"FleetModelErrorNeedsRoute": "model tim memerlukan id provider dan id model",
"FleetModelErrorNeedsRole": "anggota tim adalah sebuah peran; sebutkan satu (misalnya `/fleet add {route} scout`)",
"FleetModelErrorNoSelection": "tidak ada tim yang dipilih; tim Anda hanya model sesi",
"FleetModelErrorOperatorRoute": "{route} adalah rute operator tim `{fleet}`; ubah dengan /fleet save, bukan remove",
"FleetModelErrorNotInFleet": "{route} tidak ada di tim `{fleet}`",
"FleetModelsEmpty": "Tim Anda hanya model sesi. Tambahkan satu: /fleet add <provider> <model> [role…] (atau ⇧F pada baris di /model).",
"FleetModelsHeader": "Tim Anda `{fleet}` ({count} model)",
"FleetModelsBroken": "Tim yang dipilih tidak dapat dimuat: {error}. Perbaiki berkasnya atau pilih yang lain dengan /fleet teams.",
"FleetModelsFooter": "Tambah: /fleet add <provider> <model> [role…] · Hapus: /fleet remove <provider> <model>",
"FleetModelsFactPrice": "${input}/{output} per M",
"FleetModelsFactContext": "{context}k ctx",
"FleetModelsFactTools": "alat",
"FleetAddUsage": "Penggunaan: /fleet add <provider> <model> [role…] — mis. /fleet add openrouter z-ai/glm-5.3-flash explore",
"FleetAddProviderUnconfigured": "`{provider}` bukan provider yang dikonfigurasi. Konfigurasikan di ~/.codewhale/config.toml atau beralih ke sana dengan /provider sebelum menambahkannya ke fleet.",
"FleetAddProviderUnconfigured": "`{provider}` bukan provider yang dikonfigurasi. Konfigurasikan di ~/.codewhale/config.toml atau beralih ke sana dengan /provider sebelum menambahkannya ke tim.",
"FleetAddModelNotServed": "{provider} tidak menyediakan `{model}` di katalog saat ini; jalankan /models untuk melihat yang tersedia, atau /fleet add dengan id persis yang tercantum.",
"FleetAddFailed": "Tidak dapat menambahkan ke fleet: {error}",
"FleetAddFailed": "Tidak dapat menambahkan ke tim: {error}",
"FleetRemoveUsage": "Penggunaan: /fleet remove <provider> <model>",
"FleetRemoveFailed": "Tidak dapat menghapus dari fleet: {error}",
"FleetToggleFailed": "Tidak dapat memperbarui fleet: {error}",
"FleetRemoveFailed": "Tidak dapat menghapus dari tim: {error}",
"FleetToggleFailed": "Tidak dapat memperbarui tim: {error}",
"FleetDestStepTitle": "Di mana profil ini harus disimpan?",
"FleetDestStepSubtitle": "Tidak ada yang ditulis sampai Anda mengonfirmasi di langkah terakhir.",
"FleetDestProjectLabel": "Proyek ini",
@@ -1640,12 +1667,13 @@
"KbTurnInspector": "Buka Turn Inspector",
"CmdTurnInspectDescription": "Open the whole-turn inspector",
"CmdAutomationDescription": "Kelola otomatisasi terjadwal yang tahan lama",
"AutomationUsage": "Penggunaan: /automation [list|show <id>|pause <id>|resume <id>|delete <id> [--confirm <token>]|run <id>]",
"AutomationUsage": "Penggunaan: /automation [list|show <id>|print <id>|pause <id>|resume <id>|delete <id> [--confirm <token>]|run <id>]",
"AutomationManagerUnavailable": "Pengelola otomatisasi tidak tersedia dalam sesi ini.",
"AutomationListFailed": "Tidak dapat menampilkan daftar otomatisasi: {error}",
"AutomationActionFailed": "Tidak dapat {action} otomatisasi {id}: {error}",
"AutomationEmpty": "Tidak ada otomatisasi terjadwal. Gunakan alat `automation` untuk membuatnya.",
"AutomationEmpty": "Tidak ada otomasi terjadwal. Pilih Otomasi baru.",
"AutomationListHeading": "Otomatisasi terjadwal",
"AutomationScopeNote": "Otomatisasi milik Anda, bukan repositori ini — mengikuti Anda ke setiap repositori.",
"AutomationNoun": "Otomatisasi",
"AutomationStatusLabel": "status",
"AutomationStatusActive": "aktif",
@@ -1660,6 +1688,7 @@
"AutomationActionResume": "melanjutkan",
"AutomationActionDelete": "menghapus",
"AutomationActionRun": "menjalankan",
"AutomationActionCancel": "batalkan run",
"AutomationActionPaused": "dijeda",
"AutomationActionResumed": "dilanjutkan",
"AutomationNextLabel": "berikutnya",
@@ -2015,8 +2044,43 @@
"LaunchNoticeClaude": "Claude Code terdeteksi. Jalankan /import-claude untuk meninjau apa yang bisa dibawa: server MCP, pengaturan aman, dan izin. Tidak ada yang diterapkan tanpa persetujuan Anda.",
"LaunchNewSession": "Sesi baru",
"LaunchRecentHeading": "Terkini",
"LaunchHelpLine": "/help untuk perintah · {dock} untuk bilah kerja",
"LaunchSeeAllSessions": "Lihat semua sesi…",
"LaunchNoRecentSessions": "Belum ada sesi terkini — ketik di bawah untuk memulai.",
"LaunchResumeFailed": "Melanjutkan gagal: {error}",
"ReceiptSessionHooks": "hook {count}"
"ReceiptSessionHooks": "hook {count}",
"AutomationEditorNew": "Otomasi baru",
"AutomationEditorEdit": "Edit otomasi",
"AutomationEditorSchedule": "Jadwal",
"AutomationEditorDaily": "Harian",
"AutomationEditorWeekly": "Mingguan",
"AutomationEditorHourly": "Setiap jam",
"AutomationEditorOnce": "Sekali",
"AutomationEditorCustom": "Kustom",
"AutomationEditorTime": "Waktu (HH:MM)",
"AutomationEditorDate": "Tanggal (YYYY-MM-DD)",
"AutomationEditorDays": "Hari",
"AutomationEditorLocalTime": "Zona waktu: lokal ({zone})",
"AutomationEditorProvider": "Pilihan model mengunci penyedianya. Konfigurasi saat ini: {provider}.",
"AutomationEditorDefaultModel": "Model bawaan",
"AutomationEditorInheritedProvider": "bawaan runtime",
"AutomationEditorEnabled": "Diaktifkan",
"AutomationEditorControls": "Tab kolom · Enter edit · ↑↓ sesuaikan · Ctrl+A pilih semua",
"AutomationEditorPromptControls": "Enter baris baru · Tab kolom berikutnya · Ctrl+A pilih semua",
"AutomationEditorSaveFailed": "Tidak dapat menyimpan: {error}",
"AutomationEditorBusy": "Penyimpanan otomasi sibuk. Coba simpan lagi.",
"AutomationEditorSaved": "{name} disimpan",
"AutomationEditorPreviewUnavailable": "Eksekusi berikutnya tidak tersedia; jadwal lama dipertahankan",
"AutomationEditorConflict": "Otomasi telah berubah. Batalkan dan buka kembali.",
"AutomationEditorInvalidWorkspace": "Pilih direktori ruang kerja yang ada.",
"AutomationEditorModelControls": "Ketik untuk memfilter · ↑↓ pindah · Enter pilih · Esc kembali",
"AutomationEditorPausedPreview": "Dijeda · tidak ada eksekusi terjadwal",
"AutomationEditorInvalidSchedule": "Jadwal tidak valid: {error}",
"AutomationEditorMonday": "Sen",
"AutomationEditorTuesday": "Sel",
"AutomationEditorWednesday": "Rab",
"AutomationEditorThursday": "Kam",
"AutomationEditorFriday": "Jum",
"AutomationEditorSaturday": "Sab",
"AutomationEditorSunday": "Min"
}
+98 -34
View File
@@ -1,11 +1,15 @@
{
"ExtensionsActionAdd": "追加",
"ExtensionsActionEnable": "有効化",
"ExtensionsActionEdit": "編集",
"ExtensionsHooksAddLabel": "フックを追加",
"ExtensionsHooksAddDescription": "このプロジェクトのフックファイルをエディタで開く",
"ExtensionsActionReload": "再読み込み",
"ExtensionsActionConnect": "接続",
"ExtensionsActionReconnect": "再接続",
"ExtensionsActionReauth": "再認証",
"ExtensionsActionDiagnose": "診断",
"ExtensionsActionManage": "管理",
"ExtensionsActionFocus": "フォーカス",
"ExtensionsActionFold": "折りたたむ",
"ExtensionsActionTabs": "タブ",
@@ -19,6 +23,7 @@
"ExtensionsGroupProblems": "問題",
"ExtensionsGroupRecommended": "おすすめ",
"ExtensionsGroupServers": "サーバー",
"ExtensionsGroupNeedsAttention": "要対応",
"ExtensionsGroupStatus": "{count} 件 · Enter で折りたたみ",
"ExtensionsGroupUser": "ユーザー",
"ExtensionsGroupWorkspace": "ワークスペース",
@@ -45,6 +50,8 @@
"ExtensionsProductCuaDescription": "macOS ネイティブドライバーを備えたローカルのコンピューター操作ランタイム。",
"ExtensionsProductDetail": "発行元: {publisher} · コンポーネント: {components} · ソース: {source}",
"ExtensionsProductPlaywrightDescription": "Microsoft の Playwright MCP を基盤とするブラウザ自動化。",
"ExtensionsProductCodewhaleComputerUseDescription": "画面を見て操作する。アクセシビリティ優先の操作、スクリーンショット、ズーム、画面録画に加え、ssh 経由のリモートコンピューター。",
"ExtensionsStateFirstParty": "Codewhale に同梱",
"ExtensionsProductSandboxDescription": "プラグインが提供するローカルコマンド用のプロセス分離レイヤー。",
"ExtensionsSearchLabel": "検索: ",
"ExtensionsSkillRootCompatibleGlobal": "互換グローバル ({harness})",
@@ -123,9 +130,10 @@
"HistoryHintRestore": "Esc 復元",
"HistoryNoMatches": " 一致なし",
"TranscriptReasoningExpand": "展開",
"TelemetryNoticeHeadline": "Codewhale の改善にご協力いただけますか?",
"TelemetryNoticeBody": "Codewhale が集計するのは次の情報です。実行中のバージョン、OS と CPU の種類、\nセッション時間と結果、機能およびエラーの集計カウンター。\n\n会話、コード、プロンプト、ファイル、リポジトリ名やブランチ名、モデルの内容、\n認証情報は収集しません。また、ターンごと、ツールごとのエージェント活動の\n履歴も送信しません。\n\n識別には、このマシンに保存されたランダム ID のみを使用します。この ID は\n90 日ごとに置き換えられます。あとからいつでも変更できます:\n codewhale config set telemetry false\n\n全フィールドのスキーマ: docs/TELEMETRY.md",
"TelemetryNoticeCompactBody": "集計: 版、OS/CPU、時間/結果、機能/エラー。\nローカルのランダムIDは90日ごとに更新。\n収集なし: 会話/コード/プロンプト/ファイル/名前、モデル内容/認証情報、ターン/ツール。\nスキーマ: docs/TELEMETRY.md\n無効: codewhale config set telemetry false",
"TelemetryNoticeDefaultOn": "使用統計の送信は既定でオンです。 バージョン・環境・セッション・機能・エラーの集計値を Codewhale と処理委託先 PostHog に共有。内容や IP は収集しません。docs/TELEMETRY.md 無効にする: codewhale config set telemetry false",
"TelemetryNoticeHeadline": "Codewhale の使用統計",
"TelemetryNoticeBody": "Codewhale が集計するのは次の情報です。実行中のバージョン、OS と CPU の種類、\nセッション時間と結果、機能およびエラーの集計カウンター。\n\n会話、コード、プロンプト、ファイル、リポジトリ名やブランチ名、モデルの内容、\n認証情報は収集しません。また、ターンごと、ツールごとのエージェント活動の\n履歴も送信しません。\n\n識別には、このマシンに保存されたランダム ID のみを使用します。この ID は\n90 日ごとに置き換えられます。あとからいつでも変更できます:\n codewhale config set telemetry false\n\n全フィールドのスキーマ: docs/TELEMETRY.md\n\n使用統計の送信は既定でオンです。 送信先が設定されている場合、Codewhale と PostHog が集計値を処理します。IP は収集しません。",
"TelemetryNoticeCompactBody": "集計: 版、OS/CPU、時間/結果、機能/エラー。\nローカルのランダムIDは90日ごとに更新。\n収集なし: 会話/コード/プロンプト/ファイル/名前、モデル内容/認証情報、ターン/ツール。\nスキーマ: docs/TELEMETRY.md\n無効: codewhale config set telemetry false\n使用統計の送信は既定でオンです。 送信先が設定されている場合、Codewhale と PostHog が集計値を処理します。IP は収集しません。",
"TelemetryNoticeChoiceKeep": "はい、匿名の利用状況集計を続けます",
"TelemetryNoticeChoiceDisable": "いいえ、利用状況集計をオフにします",
"TelemetryNoticeActionChoose": "選択",
@@ -256,9 +264,9 @@
"ConfigLabelAllowShell": "シェルアクセス",
"ConfigLabelManagedAllowShell": "シェルアクセス(管理)",
"ConfigLabelTelemetry": "匿名の利用状況集計",
"ConfigHintTelemetry": "匿名の機能・エラー件数に関する保存済み設定。会話やコードは対象外",
"ConfigValueTelemetryOn": "オン(保存済み)",
"ConfigValueTelemetryOff": "オフ(保存済み)",
"ConfigHintTelemetry": "使用統計の送信は既定でオンです。 バージョン・環境・セッション・機能・エラーの集計値を Codewhale と処理委託先 PostHog に共有。内容や IP は収集しません。docs/TELEMETRY.md",
"ConfigValueTelemetryOn": "オン",
"ConfigValueTelemetryOff": "オフ",
"ConfigLabelStreamTimeout": "ストリームタイムアウト",
"ConfigLabelTheme": "テーマ",
"ConfigLabelLocale": "言語",
@@ -327,7 +335,6 @@
"ModelPickerAutoLocalHint": "ターンごと · ローカル判定 · ルーター送信なし",
"ModelPickerAutoLastRoute": "前回 {provider} · {model}",
"AutoRouteSelectedToast": "Auto: {provider} / {model}({source})· Ctrl+O: ルート詳細",
"CloudCodeSystemPromptUnsupported": "この接続はシステム指示にまだ対応していないため、Antigravity cloud-code はこのターンを安全に送信できません。何も送信されていません。別のプロバイダーを選んでください。",
"HelpTitle": "ヘルプ",
"HelpFilterPlaceholder": "入力して絞り込み",
"HelpFilterPrefix": "絞り込み: ",
@@ -480,6 +487,12 @@
"CmdModeDescription": "権限を切り替えるか、モードピッカーを開く",
"CmdModelDescription": "現在のモデルを切り替え・確認",
"CmdModelsDescription": "アクティブな API からライブのモデル ID を取得する",
"ModelsListHeader": "{provider} のモデル(既定: {model})",
"ModelsListHint": "キャッシュ済みカタログ。設定済みプロバイダーを更新するには `codewhale models --update` を実行してください。",
"ModelsUpdateSummary": "モデルカタログ: {updated} 件更新、{loaded} 件を Codex から読み込み、{failed} 件失敗、{skipped} 件スキップ。",
"ModelsUpdatePartial": "一部のカタログを更新できませんでした。以前のモデルと設定済みモデルは引き続き利用できます。",
"ModelsCodexHint": "ChatGPT モデルは Codex 経由で読み込みます。照会時刻はプロバイダーのカタログ更新を保証するものではありません。",
"ModelsSourceFallback": "同梱または設定済みのモデル。利用可能かどうかは未確認です。",
"CmdModelDbDescription": "内蔵のモデルデータベースを閲覧する",
"CmdNetworkDescription": "ネットワーク許可・拒否ルールを管理",
"CmdUpdateDescription": "新しい Codewhale リリースを確認してインストールします",
@@ -547,7 +560,7 @@
"CmdStructcopyClipboardAccepted": "構造化コピー({kind}、{bytes}バイト)をクリップボードへ渡しました。ネイティブクリップボードを利用できない場合は、端末への書き込みがキューされています",
"CmdStructcopyClipboardFailed": "クリップボードへのコピーに失敗しました: {error}。何も書き込まれていません。明示的なテキスト表示には `stdout` を付けて再実行してください",
"CmdStructcopyReceiptTooLarge": "構造化コピーのレシートメタデータが出力上限の{bytes}バイトを超えたため、出力を拒否しました",
"CmdFleetDescription": "Fleet メンバーとオーケストレーション状態を確認・設定",
"CmdFleetDescription": "チーム メンバーとオーケストレーション状態を確認・設定",
"CmdLaneDescription": "永続化された Lane(実行中の Workflow)を確認・制御",
"CmdWorkflowDescription": "順序や並列が必要なときにマルチエージェント脚本を実行する",
"CmdWorkflowsDescription": "このワークスペースのワークフロー実行を表示(一覧・キャンセル)",
@@ -600,6 +613,7 @@
"FooterBalancePrefix": "残高",
"HelpSectionActions": "操作",
"HelpSectionClipboard": "クリップボード",
"HelpSectionPointer": "ポインター",
"HelpSectionEditing": "入力編集",
"HelpSectionHelp": "ヘルプ",
"HelpSectionModes": "モード",
@@ -660,14 +674,22 @@
"KbCopySelection": "Codewhale の現在の選択をコピー",
"ClipboardSshPasteHint": "SSH での貼り付けはローカル端末が処理します。macOS では Cmd+V、Linux/Windows では Ctrl+Shift+V を押してください。",
"KbContextMenu": "貼り付け、選択、メッセージ詳細、コンテキスト、ヘルプのコンテキスト操作を開く",
"KbPointerScroll": "トランスクリプトをスクロール。コンポーザー上ではカーソルを1行ずつ移動",
"KbPointerClick": "ポインターの下にある行・チップ・ツールブロックを実行",
"KbPointerDrag": "トランスクリプトのテキストを選択、またはスクロールバーをドラッグ",
"KbAttachPath": "ローカルのテキストファイルまたはディレクトリをコンテキストに追加",
"KbHelpOverlay": "このヘルプオーバーレイを開く(入力が空の時)",
"KbCycleWorkDock": "ワークバーを切り替え(TODO・エージェント・ジョブ・バックグラウンド)",
"KbCycleWorkDockBack": "ワークバーを逆順に切り替え",
"KbToggleHelp": "ヘルプオーバーレイを切り替え",
"KbToggleHelpSlash": "ヘルプオーバーレイを切り替え",
"HelpUsageLabel": "使い方:",
"HelpAliasesLabel": "エイリアス:",
"SettingsTitle": "設定:",
"SettingsConfigFile": "設定ファイル:",
"SettingsTuiPrefsFolded": "tui.toml の {key} '{value}' を移行しました — 今後は settings.toml が管理します。",
"SettingsTuiPrefsKept": "tui.toml は {key} に '{prefs}' を設定していますが、settings.toml が既に '{settings}' を設定しています — settings.toml が優先されます。",
"SettingsTuiPrefsQuarantined": "対応する設定がない tui.toml のキーは {path} に保管しました: {keys}",
"ClearConversation": "会話履歴をクリアしました",
"ClearConversationBusy": "何もクリアされませんでした(Work 状態またはランタイム処理が実行中です。待ってから /clear を再実行してください)",
"ModelChanged": "モデルを変更しました: {old} → {new}",
@@ -1149,8 +1171,8 @@
"NotificationSubagentCancelled": "サブエージェントキャンセル",
"NotificationSubagentBudgetExhausted": "サブエージェントの予算上限到達",
"FooterWorkedChip": "経過{duration}",
"FleetDraftTitle": "Fleet 設定 — {model_label} によるドラフト(g で保存)",
"FleetDraftHeader": "# .codewhale/agents/{name}\n# {model_label} が作成し、Codewhale が検証・制限を適用しました。\n# 権限は Fleet の下限に維持されます:シェル不可、信頼不可、承認必須。\n# ウィザードで g を押すまで何も保存されません。\n\n",
"FleetDraftTitle": "チーム 設定 — {model_label} によるドラフト(g で保存)",
"FleetDraftHeader": "# .codewhale/agents/{name}\n# {model_label} が作成し、Codewhale が検証・制限を適用しました。\n# 権限は チーム の下限に維持されます:シェル不可、信頼不可、承認必須。\n# ウィザードで g を押すまで何も保存されません。\n\n",
"SetupRemoteOnRampText": "Remote Runtime On-Ramp\n\n/setup はリモートランタイムの事実だけを表示します。デプロイバンドルの生成、認証情報の書き込み、クラウド CLI の呼び出し、`remote-setup` の実行は行いません。\n\n現在の事実:\n- クラウド: {clouds_result}\n- ブリッジ: {bridges_result}\n- プロバイダー: {providers_result}\n- モード: {mode_result}\n\nデプロイバンドルを生成する場合は、通常の端末で明示的に実行してください:\n\n```sh\n{command}\n```\n\n生成された RUNBOOK には人間が確認するホスト手順が含まれます。`--apply` は未実装です。自動デプロイとして扱わないでください。",
"ApprovalDescSafe": "安全/読み取り専用操作をリクエストしています。",
"ApprovalDescFileWrite": "ファイルの変更をリクエストしています。パスと内容を確認してください。",
@@ -1234,6 +1256,11 @@
"CommandPaletteSubtitle": "操作を見つけて実行",
"ConfigSubtitle": "設定を先に。生キーは高度な詳細へ",
"LaunchStartTitle": "ここから始める",
"LaunchResumeConfirm": "{title} を再開しますか?もう一度クリックするか Enter で確定します。",
"LaunchResumeConfirmTitle": "セッションを再開",
"LaunchResumeConfirmBody": "現在のコンテキストがそのセッションの履歴に置き換わります。",
"LaunchResumeConfirmResume": "再開",
"LaunchResumeConfirmCancel": "キャンセル",
"LaunchWorkDescription": "このフォルダーでローカルツールを使います。変更は承認ポリシーに従います。",
"LaunchChatDescription": "会話と計画のみ。ファイルは変更しません。",
"LaunchWorkspaceGitReady": "ワークスペース · {name} · Git ワークスペース",
@@ -1259,7 +1286,6 @@
"LaunchWorktreeFailed": "ワークツリーの作成に失敗: {error}",
"LaunchWorktreeCreated": "新しいワークツリー {path}(ブランチ {branch})",
"LaunchNoSavedSessions": "このワークスペースには保存済みセッションがありません。",
"LaunchComposerHint": "Enter 送信 · Shift+Enter 改行 · Esc 戻る",
"LaunchNoModelConnected": "モデル未接続",
"LaunchRunCommand": "{command} を実行",
"LaunchMcpConnectedOne": "MCP サーバー 1 台接続済み",
@@ -1458,7 +1484,7 @@
"ProviderExternalRevokedToast": "{provider} の外部認証情報へのアクセスを取り消しました",
"ProviderExternalRevokeFailedToast": "外部認証情報へのアクセスを取り消せませんでした: {error}",
"ThemeSurfaceTitle": "テーマ · ライブプレビュー",
"FleetRosterHeaderLabel": "fleet",
"FleetRosterHeaderLabel": "チーム",
"FleetRosterTabRoster": "メンバー",
"FleetRosterTabSetup": "セットアップ",
"FleetRosterWorkers": "ワーカー",
@@ -1476,33 +1502,34 @@
"FleetProfileIdentityVerifyFailed": "既存プロファイルの識別情報を確認できません({error})。記載のファイルを修正してから保存してください。",
"FleetProfileIdConflict": "プロファイル id `{id}` は {path} で既に使用されています。別のロールで作り直すか、先に古いファイルを削除してください。",
"FleetProfileProviderUnconfigured": "プロファイルはプロバイダー `{provider}` を指定していますが、認証情報が未設定です({env})。保存する前に /provider で設定してください。",
"FleetModelAdded": "{route} を fleet `{fleet}` に追加しました",
"FleetModelAddedAs": "{route} を {roles} として fleet `{fleet}` に追加しました",
"FleetModelAddedCreatedNote": "(新しい個人 fleet、選択済み)",
"FleetModelAdded": "{route} を チーム `{fleet}` に追加しました",
"FleetModelAddedAs": "{route} を {roles} として チーム `{fleet}` に追加しました",
"FleetModelAddedCreatedNote": "(新しい個人 チーム、選択済み)",
"FleetModelAddedSelectedNote": "(選択済み)",
"FleetModelRemoved": "{route} を fleet `{fleet}` から削除しました",
"FleetModelRemovedRoles": "{route}({roles})を fleet `{fleet}` から削除しました",
"FleetModelUnchanged": "{route} は fleet `{fleet}` に残ります: {reason}",
"FleetModelReasonOperatorRoute": "これは現在のモデル(fleet のオペレーター経路)です。モデルを切り替えるか /fleet save で変更してください",
"FleetModelReasonAlreadyPresent": "すでに fleet に含まれています",
"FleetModelErrorNeedsRoute": "fleet のモデルにはプロバイダー ID とモデル ID の両方が必要です",
"FleetModelErrorNoSelection": "fleet が選択されていません。あなたの fleet はセッションのモデルのみです",
"FleetModelErrorOperatorRoute": "{route} は fleet `{fleet}` のオペレーター経路です。削除ではなく /fleet save で変更してください",
"FleetModelErrorNotInFleet": "{route} は fleet `{fleet}` に含まれていません",
"FleetModelsEmpty": "あなたの fleet はセッションのモデルのみです。追加: /fleet add <provider> <model> [role…](または /model の行で ⇧F)。",
"FleetModelsHeader": "あなたの fleet `{fleet}`({count} モデル)",
"FleetModelsBroken": "選択中の fleet を読み込めません: {error}。ファイルを修正するか /fleet fleets で別の fleet を選んでください。",
"FleetModelRemoved": "{route} を チーム `{fleet}` から削除しました",
"FleetModelRemovedRoles": "{route}({roles})を チーム `{fleet}` から削除しました",
"FleetModelUnchanged": "{route} は チーム `{fleet}` に残ります: {reason}",
"FleetModelReasonOperatorRoute": "これは現在のモデル(チーム のオペレーター経路)です。モデルを切り替えるか /fleet save で変更してください",
"FleetModelReasonAlreadyPresent": "すでに チーム に含まれています",
"FleetModelErrorNeedsRoute": "チーム のモデルにはプロバイダー ID とモデル ID の両方が必要です",
"FleetModelErrorNeedsRole": "チームのメンバーはロールです。ロールを指定してください(例: `/fleet add {route} scout`)",
"FleetModelErrorNoSelection": "チーム が選択されていません。あなたの チーム はセッションのモデルのみです",
"FleetModelErrorOperatorRoute": "{route} は チーム `{fleet}` のオペレーター経路です。削除ではなく /fleet save で変更してください",
"FleetModelErrorNotInFleet": "{route} は チーム `{fleet}` に含まれていません",
"FleetModelsEmpty": "あなたの チーム はセッションのモデルのみです。追加: /fleet add <provider> <model> [role…](または /model の行で ⇧F)。",
"FleetModelsHeader": "あなたの チーム `{fleet}`({count} モデル)",
"FleetModelsBroken": "選択中の チーム を読み込めません: {error}。ファイルを修正するか /fleet teams で別の チーム を選んでください。",
"FleetModelsFooter": "追加: /fleet add <provider> <model> [role…] · 削除: /fleet remove <provider> <model>",
"FleetModelsFactPrice": "${input}/{output} / 100万トークン",
"FleetModelsFactContext": "{context}k ctx",
"FleetModelsFactTools": "ツール",
"FleetAddUsage": "使い方: /fleet add <provider> <model> [role…] — 例: /fleet add openrouter z-ai/glm-5.3-flash explore",
"FleetAddProviderUnconfigured": "`{provider}` は設定済みのプロバイダーではありません。fleet に追加する前に ~/.codewhale/config.toml で設定するか、/provider で切り替えてください。",
"FleetAddProviderUnconfigured": "`{provider}` は設定済みのプロバイダーではありません。チーム に追加する前に ~/.codewhale/config.toml で設定するか、/provider で切り替えてください。",
"FleetAddModelNotServed": "{provider} は現在のカタログで `{model}` を提供していません。/models で提供モデルを確認するか、表示される正確な ID で /fleet add してください。",
"FleetAddFailed": "fleet に追加できませんでした: {error}",
"FleetAddFailed": "チーム に追加できませんでした: {error}",
"FleetRemoveUsage": "使い方: /fleet remove <provider> <model>",
"FleetRemoveFailed": "fleet から削除できませんでした: {error}",
"FleetToggleFailed": "fleet を更新できませんでした: {error}",
"FleetRemoveFailed": "チーム から削除できませんでした: {error}",
"FleetToggleFailed": "チーム を更新できませんでした: {error}",
"FleetDestStepTitle": "このプロファイルをどこに保存しますか?",
"FleetDestStepSubtitle": "最後のステップで確定するまで何も書き込まれません。",
"FleetDestProjectLabel": "このプロジェクト",
@@ -1640,12 +1667,13 @@
"KbTurnInspector": "ターン インスペクターを開く",
"CmdTurnInspectDescription": "Open the whole-turn inspector",
"CmdAutomationDescription": "永続的なスケジュール自動化を管理",
"AutomationUsage": "使い方: /automation [list|show <id>|pause <id>|resume <id>|delete <id> [--confirm <token>]|run <id>]",
"AutomationUsage": "使い方: /automation [list|show <id>|print <id>|pause <id>|resume <id>|delete <id> [--confirm <token>]|run <id>]",
"AutomationManagerUnavailable": "このセッションでは自動化マネージャーを利用できません。",
"AutomationListFailed": "自動化の一覧を取得できませんでした: {error}",
"AutomationActionFailed": "自動化 {id} を{action}できませんでした: {error}",
"AutomationEmpty": "スケジュール済みの自動化はありません。`automation` ツールで作成できます。",
"AutomationEmpty": "実行予定の自動化はありません。「自動化を作成」を選んでください。",
"AutomationListHeading": "スケジュール済みの自動化",
"AutomationScopeNote": "オートメーションはこのリポジトリではなくあなたのものです。すべてのリポジトリに付いてきます。",
"AutomationNoun": "自動化",
"AutomationStatusLabel": "状態",
"AutomationStatusActive": "有効",
@@ -1660,6 +1688,7 @@
"AutomationActionResume": "再開",
"AutomationActionDelete": "削除",
"AutomationActionRun": "実行",
"AutomationActionCancel": "実行をキャンセル",
"AutomationActionPaused": "一時停止しました",
"AutomationActionResumed": "再開しました",
"AutomationNextLabel": "次回",
@@ -2015,8 +2044,43 @@
"LaunchNoticeClaude": "Claude Code を検出しました。/import-claude を実行すると、MCP サーバー・安全な設定・権限のうち引き継げるものを確認できます。承認なしには何も適用されません。",
"LaunchNewSession": "新しいセッション",
"LaunchRecentHeading": "最近",
"LaunchHelpLine": "コマンドは /help · ワークバーは {dock}",
"LaunchSeeAllSessions": "すべてのセッションを表示…",
"LaunchNoRecentSessions": "最近のセッションはまだありません — 下に入力して開始します。",
"LaunchResumeFailed": "再開に失敗しました:{error}",
"ReceiptSessionHooks": "フック {count}"
"ReceiptSessionHooks": "フック {count}",
"AutomationEditorNew": "自動化を作成",
"AutomationEditorEdit": "自動化を編集",
"AutomationEditorSchedule": "スケジュール",
"AutomationEditorDaily": "毎日",
"AutomationEditorWeekly": "毎週",
"AutomationEditorHourly": "毎時",
"AutomationEditorOnce": "1回のみ",
"AutomationEditorCustom": "カスタム",
"AutomationEditorTime": "時刻 (HH:MM)",
"AutomationEditorDate": "日付 (YYYY-MM-DD)",
"AutomationEditorDays": "曜日",
"AutomationEditorLocalTime": "タイムゾーン:ローカル ({zone})",
"AutomationEditorProvider": "モデル選択でプロバイダーを固定します。現在の設定:{provider}。",
"AutomationEditorDefaultModel": "既定のモデル",
"AutomationEditorInheritedProvider": "ランタイムの既定値",
"AutomationEditorEnabled": "有効",
"AutomationEditorControls": "Tab 項目移動 · Enter 編集 · ↑↓ 調整 · Ctrl+A 全選択",
"AutomationEditorPromptControls": "Enter 改行 · Tab 次の項目 · Ctrl+A 全選択",
"AutomationEditorSaveFailed": "保存できませんでした:{error}",
"AutomationEditorBusy": "自動化の保存先が使用中です。再度保存してください。",
"AutomationEditorSaved": "{name} を保存しました",
"AutomationEditorPreviewUnavailable": "次回実行は不明です。既存のスケジュールを保持します",
"AutomationEditorConflict": "自動化が変更されました。キャンセルして開き直してください。",
"AutomationEditorInvalidWorkspace": "既存の作業ディレクトリを選んでください。",
"AutomationEditorModelControls": "入力して絞り込み · ↑↓ 移動 · Enter 選択 · Esc 戻る",
"AutomationEditorPausedPreview": "一時停止中 · 実行予定なし",
"AutomationEditorInvalidSchedule": "無効なスケジュール:{error}",
"AutomationEditorMonday": "月",
"AutomationEditorTuesday": "火",
"AutomationEditorWednesday": "水",
"AutomationEditorThursday": "木",
"AutomationEditorFriday": "金",
"AutomationEditorSaturday": "土",
"AutomationEditorSunday": "日"
}
+98 -34
View File
@@ -1,11 +1,15 @@
{
"ExtensionsActionAdd": "추가",
"ExtensionsActionEnable": "활성화",
"ExtensionsActionEdit": "편집",
"ExtensionsHooksAddLabel": "훅 추가",
"ExtensionsHooksAddDescription": "이 프로젝트의 훅 파일을 편집기에서 엽니다",
"ExtensionsActionReload": "다시 로드",
"ExtensionsActionConnect": "연결",
"ExtensionsActionReconnect": "다시 연결",
"ExtensionsActionReauth": "재인증",
"ExtensionsActionDiagnose": "진단",
"ExtensionsActionManage": "관리",
"ExtensionsActionFocus": "포커스",
"ExtensionsActionFold": "접기",
"ExtensionsActionTabs": "탭",
@@ -19,6 +23,7 @@
"ExtensionsGroupProblems": "문제",
"ExtensionsGroupRecommended": "추천",
"ExtensionsGroupServers": "서버",
"ExtensionsGroupNeedsAttention": "조치 필요",
"ExtensionsGroupStatus": "{count}개 항목 · Enter로 접기",
"ExtensionsGroupUser": "사용자",
"ExtensionsGroupWorkspace": "작업 공간",
@@ -45,6 +50,8 @@
"ExtensionsProductCuaDescription": "네이티브 macOS 드라이버를 갖춘 로컬 컴퓨터 사용 런타임입니다.",
"ExtensionsProductDetail": "게시자: {publisher} · 구성 요소: {components} · 소스: {source}",
"ExtensionsProductPlaywrightDescription": "Microsoft의 Playwright MCP를 기반으로 한 브라우저 자동화입니다.",
"ExtensionsProductCodewhaleComputerUseDescription": "화면을 보고 조작합니다. 접근성 우선 제어, 스크린샷, 확대, 화면 녹화에 ssh 원격 컴퓨터까지.",
"ExtensionsStateFirstParty": "Codewhale에 기본 포함",
"ExtensionsProductSandboxDescription": "플러그인이 제공하는 로컬 명령을 위한 프로세스 격리 계층입니다.",
"ExtensionsSearchLabel": "검색: ",
"ExtensionsSkillRootCompatibleGlobal": "호환 전역 ({harness})",
@@ -123,9 +130,10 @@
"HistoryHintRestore": "Esc 복원",
"HistoryNoMatches": " 일치 항목 없음",
"TranscriptReasoningExpand": "펼치기",
"TelemetryNoticeHeadline": "Codewhale 개선을 도와주시겠어요?",
"TelemetryNoticeBody": "Codewhale이 집계하는 항목: 실행 중인 버전, 운영 체제와 CPU 계열, 세션\n시간과 결과, 기능 및 오류의 집계 수치입니다.\n\n대화, 코드, 프롬프트, 파일, 파일 이름, 저장소 또는 브랜치 이름, 모델\n내용이나 자격 증명은 절대 수집하지 않으며, 턴별 또는 도구별 에이전트 활동\n기록도 전송하지 않습니다.\n\n이 컴퓨터에 저장된 임의의 ID로만 식별되며, 90일마다 새 ID로 교체됩니다.\n언제든지 변경할 수 있습니다:\n codewhale config set telemetry false\n\n필드별 전체 스키마: docs/TELEMETRY.md",
"TelemetryNoticeCompactBody": "집계: 버전, 시스템/CPU, 세션 시간/결과, 기능/오류 합계.\n임의의 로컬 식별자는 90일마다 교체.\n수집 안 함: 대화/코드/프롬프트/파일/이름, 모델 내용/자격 증명, 턴/도구 활동.\n스키마: docs/TELEMETRY.md\n끄기: codewhale config set telemetry false",
"TelemetryNoticeDefaultOn": "사용 통계 보고는 기본적으로 켜져 있습니다. 버전·플랫폼·세션·기능·오류의 집계 수치를 Codewhale 및 처리업체 PostHog와 공유합니다. 콘텐츠나 IP는 수집하지 않습니다. docs/TELEMETRY.md 끄기: codewhale config set telemetry false",
"TelemetryNoticeHeadline": "Codewhale 사용 통계",
"TelemetryNoticeBody": "Codewhale이 집계하는 항목: 실행 중인 버전, 운영 체제와 CPU 계열, 세션\n시간과 결과, 기능 및 오류의 집계 수치입니다.\n\n대화, 코드, 프롬프트, 파일, 파일 이름, 저장소 또는 브랜치 이름, 모델\n내용이나 자격 증명은 절대 수집하지 않으며, 턴별 또는 도구별 에이전트 활동\n기록도 전송하지 않습니다.\n\n이 컴퓨터에 저장된 임의의 ID로만 식별되며, 90일마다 새 ID로 교체됩니다.\n언제든지 변경할 수 있습니다:\n codewhale config set telemetry false\n\n필드별 전체 스키마: docs/TELEMETRY.md\n\n사용 통계 보고는 기본적으로 켜져 있습니다. 전송이 설정된 경우 Codewhale과 PostHog가 이 수치를 처리합니다. IP는 수집하지 않습니다.",
"TelemetryNoticeCompactBody": "집계: 버전, 시스템/CPU, 세션 시간/결과, 기능/오류 합계.\n임의의 로컬 식별자는 90일마다 교체.\n수집 안 함: 대화/코드/프롬프트/파일/이름, 모델 내용/자격 증명, 턴/도구 활동.\n스키마: docs/TELEMETRY.md\n끄기: codewhale config set telemetry false\n사용 통계 보고는 기본적으로 켜져 있습니다. 전송이 설정된 경우 Codewhale과 PostHog가 이 수치를 처리합니다. IP는 수집하지 않습니다.",
"TelemetryNoticeChoiceKeep": "네, 익명 집계를 유지합니다",
"TelemetryNoticeChoiceDisable": "아니요, 추적을 끄습니다",
"TelemetryNoticeActionChoose": "선택",
@@ -256,9 +264,9 @@
"ConfigLabelAllowShell": "셸 접근",
"ConfigLabelManagedAllowShell": "셸 접근 (관리됨)",
"ConfigLabelTelemetry": "익명 사용량 집계",
"ConfigHintTelemetry": "익명 기능 및 오류 집계의 저장된 설정; 대화나 코드는 제외",
"ConfigValueTelemetryOn": "켬 (저장됨)",
"ConfigValueTelemetryOff": "끔 (저장됨)",
"ConfigHintTelemetry": "사용 통계 보고는 기본적으로 켜져 있습니다. 버전·플랫폼·세션·기능·오류의 집계 수치를 Codewhale 및 처리업체 PostHog와 공유합니다. 콘텐츠나 IP는 수집하지 않습니다. docs/TELEMETRY.md",
"ConfigValueTelemetryOn": "켬",
"ConfigValueTelemetryOff": "끔",
"ConfigLabelStreamTimeout": "스트림 시간 제한",
"ConfigLabelTheme": "테마",
"ConfigLabelLocale": "언어",
@@ -327,7 +335,6 @@
"ModelPickerAutoLocalHint": "턴별 · 로컬 휴리스틱 · 라우터 요청 없음",
"ModelPickerAutoLastRoute": "최근 {provider} · {model}",
"AutoRouteSelectedToast": "Auto: {provider} / {model} ({source}) · Ctrl+O: 경로 세부 정보",
"CloudCodeSystemPromptUnsupported": "이 연결은 아직 시스템 지시를 지원하지 않으므로 Antigravity cloud-code가 이 턴을 안전하게 보낼 수 없습니다. 아무것도 보내지 않았습니다. 다른 공급자를 선택하세요.",
"HelpTitle": "도움말",
"HelpFilterPlaceholder": "입력하여 필터링",
"HelpFilterPrefix": "필터: ",
@@ -480,6 +487,12 @@
"CmdModeDescription": "권한 전환 또는 모드 선택기 열기",
"CmdModelDescription": "현재 모델을 전환하거나 확인합니다",
"CmdModelsDescription": "현재 API에서 실시간 모델 ID를 가져옵니다",
"ModelsListHeader": "{provider} 모델 (기본값: {model})",
"ModelsListHint": "캐시된 카탈로그입니다. 설정된 제공업체를 갱신하려면 `codewhale models --update`를 실행하세요.",
"ModelsUpdateSummary": "모델 카탈로그: {updated}개 업데이트, {loaded}개 Codex에서 불러옴, {failed}개 실패, {skipped}개 건너뜀.",
"ModelsUpdatePartial": "일부 카탈로그를 갱신하지 못했습니다. 이전 모델과 설정된 모델은 계속 사용할 수 있습니다.",
"ModelsCodexHint": "ChatGPT 모델은 Codex를 통해 불러옵니다. 조회 시각이 공급자 카탈로그의 갱신을 보장하지는 않습니다.",
"ModelsSourceFallback": "기본 제공 또는 설정된 모델입니다. 사용 가능 여부는 확인되지 않았습니다.",
"CmdModelDbDescription": "내장 모델 데이터베이스를 조회합니다",
"CmdNetworkDescription": "네트워크 허용/차단 규칙을 관리합니다",
"CmdUpdateDescription": "새 Codewhale 릴리스를 확인하고 설치합니다",
@@ -547,7 +560,7 @@
"CmdStructcopyClipboardAccepted": "구조적 복사({kind}, {bytes}바이트)를 클립보드에 전달했습니다. 네이티브 클립보드에 접근할 수 없으면 터미널 쓰기가 대기열에 들어갔습니다",
"CmdStructcopyClipboardFailed": "클립보드 복사에 실패했습니다: {error}. 아무것도 쓰지 않았습니다. 명시적 텍스트 보기에는 `stdout`을 지정해 다시 실행하세요",
"CmdStructcopyReceiptTooLarge": "구조적 복사 영수증 메타데이터가 {bytes}바이트 출력 상한을 초과하여 출력을 거부했습니다",
"CmdFleetDescription": "Fleet 멤버와 오케스트레이션 상태를 확인하고 설정합니다",
"CmdFleetDescription": "팀 멤버와 오케스트레이션 상태를 확인하고 설정합니다",
"CmdLaneDescription": "지속되는 Lane(실행 중인 Workflow)을 확인하고 제어합니다",
"CmdWorkflowDescription": "순서나 병렬 분기가 필요할 때 멀티 에이전트 스크립트를 실행합니다",
"CmdWorkflowsDescription": "이 작업 공간의 워크플로 실행 표시 (목록, 취소)",
@@ -600,6 +613,7 @@
"FooterBalancePrefix": "잔액",
"HelpSectionActions": "동작",
"HelpSectionClipboard": "클립보드",
"HelpSectionPointer": "포인터",
"HelpSectionEditing": "입력 편집",
"HelpSectionHelp": "도움말",
"HelpSectionModes": "모드",
@@ -660,14 +674,22 @@
"KbCopySelection": "현재 Codewhale 선택 영역 복사",
"ClipboardSshPasteHint": "SSH 붙여넣기는 로컬 터미널에서 처리합니다. macOS에서는 Cmd+V, Linux/Windows에서는 Ctrl+Shift+V를 누르세요.",
"KbContextMenu": "붙여넣기, 선택, 메시지 세부 정보, 컨텍스트, 도움말에 대한 컨텍스트 메뉴 열기",
"KbPointerScroll": "트랜스크립트 스크롤. 작성창 위에서는 커서를 한 줄씩 이동",
"KbPointerClick": "포인터 아래의 행, 칩 또는 도구 블록 실행",
"KbPointerDrag": "트랜스크립트 텍스트 선택 또는 스크롤바 드래그",
"KbAttachPath": "로컬 텍스트 파일이나 디렉터리를 컨텍스트에 추가",
"KbHelpOverlay": "이 도움말 오버레이 열기 (입력창이 비어 있을 때)",
"KbCycleWorkDock": "작업 바 순환 (할 일, 에이전트, 작업, 백그라운드)",
"KbCycleWorkDockBack": "작업 바 역순 순환",
"KbToggleHelp": "도움말 오버레이 전환",
"KbToggleHelpSlash": "도움말 오버레이 전환",
"HelpUsageLabel": "사용법:",
"HelpAliasesLabel": "별칭:",
"SettingsTitle": "설정:",
"SettingsConfigFile": "설정 파일:",
"SettingsTuiPrefsFolded": "tui.toml의 {key} '{value}'을(를) 이전했습니다 — 이제 settings.toml이 관리합니다.",
"SettingsTuiPrefsKept": "tui.toml은 {key}을(를) '{prefs}'로 설정했지만 settings.toml이 이미 '{settings}'로 설정했습니다 — settings.toml이 우선합니다.",
"SettingsTuiPrefsQuarantined": "대응하는 설정이 없는 tui.toml 키는 {path}에 보관했습니다: {keys}",
"ClearConversation": "대화가 지워졌습니다",
"ClearConversationBusy": "지우지 못했습니다 (작업 상태 또는 런타임 작업이 진행 중입니다. 잠시 후 /clear를 다시 시도하세요)",
"ModelChanged": "모델 변경: {old} →, {new}",
@@ -1151,8 +1173,8 @@
"NotificationSubagentCancelled": "서브 에이전트 취소",
"NotificationSubagentBudgetExhausted": "서브 에이전트 예산 소진",
"FooterWorkedChip": "작업 시간 {duration}",
"FleetDraftTitle": "Fleet 프로필 — {model_label} 초안 (g로 저장)",
"FleetDraftHeader": "# .codewhale/agents/{name}\n# {model_label}이(가) 작성한 초안이며, Codewhale이 검증하고 범위를 제한했습니다.\n# 권한은 Fleet 최저 기준을 유지합니다: 셸 없음, 신뢰 없음, 승인 필수.\n# 마법사에서 g를 누르기 전까지는 아무것도 저장되지 않습니다.\n\n",
"FleetDraftTitle": "팀 프로필 — {model_label} 초안 (g로 저장)",
"FleetDraftHeader": "# .codewhale/agents/{name}\n# {model_label}이(가) 작성한 초안이며, Codewhale이 검증하고 범위를 제한했습니다.\n# 권한은 팀 최저 기준을 유지합니다: 셸 없음, 신뢰 없음, 승인 필수.\n# 마법사에서 g를 누르기 전까지는 아무것도 저장되지 않습니다.\n\n",
"SetupRemoteOnRampText": "원격 런타임 온램프\n\n/setup은 원격 런타임 정보만 보여줍니다. 배포 번들을 생성하거나, 자격 증명을 쓰거나, 클라우드 CLI를 호출하거나, `remote-setup`을 실행하지 않습니다.\n\n현재 정보:\n- 클라우드: {clouds_result}\n- 채팅 브리지: {bridges_result}\n- 프로바이더: {providers_result}\n- 모드: {mode_result}\n\n배포 번들을 생성하려면 일반 터미널에서 다음을 직접 실행하세요:\n\n```sh\n{command}\n```\n\n생성된 RUNBOOK에는 사람이 검토할 호스트 단계가 포함되어 있습니다. `--apply`는 아직 구현되지 않았으니 이를 자동 배포로 취급하지 마세요.",
"ApprovalDescSafe": "안전한/읽기 전용 작업을 요청하고 있습니다.",
"ApprovalDescFileWrite": "파일 수정을 요청하고 있습니다. 경로와 내용을 확인해 주세요.",
@@ -1234,6 +1256,11 @@
"CommandPaletteSubtitle": "동작을 찾아 실행하세요",
"ConfigSubtitle": "설정을 우선 표시하고, 원시 키는 고급 정보에서 확인",
"LaunchStartTitle": "여기서 시작",
"LaunchResumeConfirm": "{title}을(를) 이어서 진행할까요? 다시 클릭하거나 Enter를 눌러 확인하세요.",
"LaunchResumeConfirmTitle": "세션 이어하기",
"LaunchResumeConfirmBody": "현재 컨텍스트가 해당 세션의 기록으로 대체됩니다.",
"LaunchResumeConfirmResume": "이어하기",
"LaunchResumeConfirmCancel": "취소",
"LaunchWorkDescription": "이 폴더에서 로컬 도구를 사용합니다. 변경은 승인 정책을 따릅니다.",
"LaunchChatDescription": "대화와 계획만 수행하며 파일은 변경하지 않습니다.",
"LaunchWorkspaceGitReady": "작업 공간 · {name} · Git 작업 공간",
@@ -1259,7 +1286,6 @@
"LaunchWorktreeFailed": "워크트리 생성 실패: {error}",
"LaunchWorktreeCreated": "새 워크트리 {path} (브랜치 {branch})",
"LaunchNoSavedSessions": "이 작업 공간에는 저장된 세션이 없습니다.",
"LaunchComposerHint": "Enter 전송 · Shift+Enter 새 줄 · Esc 뒤로",
"LaunchNoModelConnected": "연결된 모델 없음",
"LaunchRunCommand": "{command} 실행",
"LaunchMcpConnectedOne": "MCP 서버 1개 연결됨",
@@ -1458,7 +1484,7 @@
"ProviderExternalRevokedToast": "{provider}의 외부 자격 증명 접근을 취소했습니다",
"ProviderExternalRevokeFailedToast": "외부 자격 증명 접근을 취소하지 못했습니다: {error}",
"ThemeSurfaceTitle": "테마 · 실시간 미리보기",
"FleetRosterHeaderLabel": "Fleet",
"FleetRosterHeaderLabel": "팀",
"FleetRosterTabRoster": "멤버",
"FleetRosterTabSetup": "설정",
"FleetRosterWorkers": "워커",
@@ -1476,33 +1502,34 @@
"FleetProfileIdentityVerifyFailed": "기존 프로필 식별 정보를 확인할 수 없습니다({error}). 표시된 파일을 수정한 뒤 저장하세요.",
"FleetProfileIdConflict": "프로필 id `{id}`는 이미 {path}에서 사용 중입니다. 다른 역할로 다시 작성하거나 이전 파일을 먼저 제거하세요.",
"FleetProfileProviderUnconfigured": "프로필이 자격 증명이 설정되지 않은 공급자 `{provider}`를 지정합니다({env}). 저장하기 전에 /provider에서 설정하세요.",
"FleetModelAdded": "{route}을(를) fleet `{fleet}`에 추가했습니다",
"FleetModelAddedAs": "{route}을(를) {roles} 역할로 fleet `{fleet}`에 추가했습니다",
"FleetModelAddedCreatedNote": " (새 개인 fleet, 현재 선택됨)",
"FleetModelAdded": "{route}을(를) 팀 `{fleet}`에 추가했습니다",
"FleetModelAddedAs": "{route}을(를) {roles} 역할로 팀 `{fleet}`에 추가했습니다",
"FleetModelAddedCreatedNote": " (새 개인 팀, 현재 선택됨)",
"FleetModelAddedSelectedNote": " (현재 선택됨)",
"FleetModelRemoved": "{route}을(를) fleet `{fleet}`에서 제거했습니다",
"FleetModelRemovedRoles": "{route}({roles})을(를) fleet `{fleet}`에서 제거했습니다",
"FleetModelUnchanged": "{route}은(는) fleet `{fleet}`에 그대로 남습니다: {reason}",
"FleetModelReasonOperatorRoute": "현재 사용 중인 모델(fleet의 operator 경로)입니다. 모델을 전환하거나 /fleet save로 변경하세요",
"FleetModelReasonAlreadyPresent": "이미 fleet에 있습니다",
"FleetModelErrorNeedsRoute": "fleet 모델에는 provider ID와 모델 ID가 모두 필요합니다",
"FleetModelErrorNoSelection": "선택된 fleet이 없습니다. 현재 fleet은 세션 모델뿐입니다",
"FleetModelErrorOperatorRoute": "{route}은(는) fleet `{fleet}`의 operator 경로입니다. remove가 아니라 /fleet save로 변경하세요",
"FleetModelErrorNotInFleet": "{route}은(는) fleet `{fleet}`에 없습니다",
"FleetModelsEmpty": "현재 fleet은 세션 모델뿐입니다. 추가: /fleet add <provider> <model> [role…] (또는 /model의 행에서 ⇧F).",
"FleetModelsHeader": "내 fleet `{fleet}` (모델 {count}개)",
"FleetModelsBroken": "선택된 fleet을 불러올 수 없습니다: {error}. 파일을 고치거나 /fleet fleets로 다른 fleet을 선택하세요.",
"FleetModelRemoved": "{route}을(를) 팀 `{fleet}`에서 제거했습니다",
"FleetModelRemovedRoles": "{route}({roles})을(를) 팀 `{fleet}`에서 제거했습니다",
"FleetModelUnchanged": "{route}은(는) 팀 `{fleet}`에 그대로 남습니다: {reason}",
"FleetModelReasonOperatorRoute": "현재 사용 중인 모델(팀의 operator 경로)입니다. 모델을 전환하거나 /fleet save로 변경하세요",
"FleetModelReasonAlreadyPresent": "이미 팀에 있습니다",
"FleetModelErrorNeedsRoute": "팀 모델에는 provider ID와 모델 ID가 모두 필요합니다",
"FleetModelErrorNeedsRole": "팀 멤버는 역할입니다. 역할을 지정하세요 (예: `/fleet add {route} scout`)",
"FleetModelErrorNoSelection": "선택된 팀이 없습니다. 현재 팀은 세션 모델뿐입니다",
"FleetModelErrorOperatorRoute": "{route}은(는) 팀 `{fleet}`의 operator 경로입니다. remove가 아니라 /fleet save로 변경하세요",
"FleetModelErrorNotInFleet": "{route}은(는) 팀 `{fleet}`에 없습니다",
"FleetModelsEmpty": "현재 팀은 세션 모델뿐입니다. 추가: /fleet add <provider> <model> [role…] (또는 /model의 행에서 ⇧F).",
"FleetModelsHeader": "내 팀 `{fleet}` (모델 {count}개)",
"FleetModelsBroken": "선택된 팀을 불러올 수 없습니다: {error}. 파일을 고치거나 /fleet teams로 다른 팀을 선택하세요.",
"FleetModelsFooter": "추가: /fleet add <provider> <model> [role…] · 제거: /fleet remove <provider> <model>",
"FleetModelsFactPrice": "${input}/{output} / 100만 토큰",
"FleetModelsFactContext": "{context}k ctx",
"FleetModelsFactTools": "도구",
"FleetAddUsage": "사용법: /fleet add <provider> <model> [role…] — 예: /fleet add openrouter z-ai/glm-5.3-flash explore",
"FleetAddProviderUnconfigured": "`{provider}`은(는) 설정된 provider가 아닙니다. fleet에 추가하기 전에 ~/.codewhale/config.toml에서 설정하거나 /provider로 전환하세요.",
"FleetAddProviderUnconfigured": "`{provider}`은(는) 설정된 provider가 아닙니다. 팀에 추가하기 전에 ~/.codewhale/config.toml에서 설정하거나 /provider로 전환하세요.",
"FleetAddModelNotServed": "{provider}은(는) 현재 카탈로그에서 `{model}`을(를) 제공하지 않습니다. /models로 제공 모델을 확인하거나, 표시된 정확한 ID로 /fleet add 하세요.",
"FleetAddFailed": "fleet에 추가하지 못했습니다: {error}",
"FleetAddFailed": "팀에 추가하지 못했습니다: {error}",
"FleetRemoveUsage": "사용법: /fleet remove <provider> <model>",
"FleetRemoveFailed": "fleet에서 제거하지 못했습니다: {error}",
"FleetToggleFailed": "fleet을 업데이트하지 못했습니다: {error}",
"FleetRemoveFailed": "팀에서 제거하지 못했습니다: {error}",
"FleetToggleFailed": "팀을 업데이트하지 못했습니다: {error}",
"FleetDestStepTitle": "이 프로필을 어디에 저장할까요?",
"FleetDestStepSubtitle": "마지막 단계에서 확인하기 전까지는 아무것도 기록되지 않습니다.",
"FleetDestProjectLabel": "이 프로젝트",
@@ -1640,12 +1667,13 @@
"KbTurnInspector": "턴 인스펙터 열기",
"CmdTurnInspectDescription": "Open the whole-turn inspector",
"CmdAutomationDescription": "영구 예약 자동화 관리",
"AutomationUsage": "사용법: /automation [list|show <id>|pause <id>|resume <id>|delete <id> [--confirm <token>]|run <id>]",
"AutomationUsage": "사용법: /automation [list|show <id>|print <id>|pause <id>|resume <id>|delete <id> [--confirm <token>]|run <id>]",
"AutomationManagerUnavailable": "이 세션에서는 자동화 관리자를 사용할 수 없습니다.",
"AutomationListFailed": "자동화 목록을 가져올 수 없습니다: {error}",
"AutomationActionFailed": "자동화 {id}을(를) {action}할 수 없습니다: {error}",
"AutomationEmpty": "예약된 자동화가 없습니다. `automation` 도구로 만들 수 있습니다.",
"AutomationEmpty": "예약된 자동화가 없습니다. 자동화 만들기를 선택하세요.",
"AutomationListHeading": "예약된 자동화",
"AutomationScopeNote": "자동화는 이 저장소가 아니라 사용자의 것입니다. 모든 저장소에서 함께 따라옵니다.",
"AutomationNoun": "자동화",
"AutomationStatusLabel": "상태",
"AutomationStatusActive": "활성",
@@ -1660,6 +1688,7 @@
"AutomationActionResume": "재개",
"AutomationActionDelete": "삭제",
"AutomationActionRun": "실행",
"AutomationActionCancel": "실행 취소",
"AutomationActionPaused": "일시 중지됨",
"AutomationActionResumed": "재개됨",
"AutomationNextLabel": "다음",
@@ -2015,8 +2044,43 @@
"LaunchNoticeClaude": "Claude Code가 감지되었습니다. /import-claude를 실행해 가져올 수 있는 항목(MCP 서버, 안전한 설정, 권한)을 검토하세요. 승인 없이는 아무것도 적용되지 않습니다.",
"LaunchNewSession": "새 세션",
"LaunchRecentHeading": "최근",
"LaunchHelpLine": "명령은 /help · 작업 바는 {dock}",
"LaunchSeeAllSessions": "모든 세션 보기…",
"LaunchNoRecentSessions": "최근 세션이 아직 없습니다 — 아래에 입력하여 시작하세요.",
"LaunchResumeFailed": "다시 시작 실패: {error}",
"ReceiptSessionHooks": "훅 {count}"
"ReceiptSessionHooks": "훅 {count}",
"AutomationEditorNew": "자동화 만들기",
"AutomationEditorEdit": "자동화 편집",
"AutomationEditorSchedule": "일정",
"AutomationEditorDaily": "매일",
"AutomationEditorWeekly": "매주",
"AutomationEditorHourly": "매시간",
"AutomationEditorOnce": "한 번",
"AutomationEditorCustom": "사용자 지정",
"AutomationEditorTime": "시간 (HH:MM)",
"AutomationEditorDate": "날짜 (YYYY-MM-DD)",
"AutomationEditorDays": "요일",
"AutomationEditorLocalTime": "시간대: 로컬 ({zone})",
"AutomationEditorProvider": "모델을 선택하면 제공자가 고정됩니다. 현재 설정: {provider}.",
"AutomationEditorDefaultModel": "기본 모델",
"AutomationEditorInheritedProvider": "런타임 기본값",
"AutomationEditorEnabled": "사용",
"AutomationEditorControls": "Tab 필드 이동 · Enter 편집 · ↑↓ 조정 · Ctrl+A 전체 선택",
"AutomationEditorPromptControls": "Enter 줄바꿈 · Tab 다음 필드 · Ctrl+A 전체 선택",
"AutomationEditorSaveFailed": "저장할 수 없습니다: {error}",
"AutomationEditorBusy": "자동화 저장소가 사용 중입니다. 다시 저장하세요.",
"AutomationEditorSaved": "{name} 저장됨",
"AutomationEditorPreviewUnavailable": "다음 실행을 알 수 없어 기존 일정을 유지합니다",
"AutomationEditorConflict": "자동화가 변경되었습니다. 취소한 뒤 다시 여세요.",
"AutomationEditorInvalidWorkspace": "기존 작업 디렉터리를 선택하세요.",
"AutomationEditorModelControls": "입력하여 필터 · ↑↓ 이동 · Enter 선택 · Esc 뒤로",
"AutomationEditorPausedPreview": "일시 정지 · 예약된 실행 없음",
"AutomationEditorInvalidSchedule": "잘못된 일정: {error}",
"AutomationEditorMonday": "월",
"AutomationEditorTuesday": "화",
"AutomationEditorWednesday": "수",
"AutomationEditorThursday": "목",
"AutomationEditorFriday": "금",
"AutomationEditorSaturday": "토",
"AutomationEditorSunday": "일"
}
+98 -34
View File
@@ -1,11 +1,15 @@
{
"ExtensionsActionAdd": "adicionar",
"ExtensionsActionEnable": "ativar",
"ExtensionsActionEdit": "Editar",
"ExtensionsHooksAddLabel": "Adicionar um hook",
"ExtensionsHooksAddDescription": "Abre o arquivo de hooks deste projeto no seu editor",
"ExtensionsActionReload": "recarregar",
"ExtensionsActionConnect": "conectar",
"ExtensionsActionReconnect": "reconectar",
"ExtensionsActionReauth": "reautenticar",
"ExtensionsActionDiagnose": "diagnosticar",
"ExtensionsActionManage": "gerenciar",
"ExtensionsActionFocus": "foco",
"ExtensionsActionFold": "recolher",
"ExtensionsActionTabs": "abas",
@@ -19,6 +23,7 @@
"ExtensionsGroupProblems": "Problemas",
"ExtensionsGroupRecommended": "Recomendados",
"ExtensionsGroupServers": "Servidores",
"ExtensionsGroupNeedsAttention": "Precisa de atenção",
"ExtensionsGroupStatus": "{count} itens · Enter recolhe",
"ExtensionsGroupUser": "Usuário",
"ExtensionsGroupWorkspace": "Área de trabalho",
@@ -45,6 +50,8 @@
"ExtensionsProductCuaDescription": "Runtime local de uso do computador com driver nativo do macOS.",
"ExtensionsProductDetail": "publicador: {publisher} · componentes: {components} · origem: {source}",
"ExtensionsProductPlaywrightDescription": "Automação de navegador criada em torno do Playwright MCP da Microsoft.",
"ExtensionsProductCodewhaleComputerUseDescription": "Vê a tela e a opera: controle por acessibilidade, capturas, zoom e gravação de tela, além de computadores remotos por ssh.",
"ExtensionsStateFirstParty": "Já vem no Codewhale",
"ExtensionsProductSandboxDescription": "Camada de isolamento de processos para comandos locais fornecidos por plugins.",
"ExtensionsSearchLabel": "Buscar: ",
"ExtensionsSkillRootCompatibleGlobal": "Global compatível ({harness})",
@@ -123,9 +130,10 @@
"HistoryHintRestore": "Esc restaura",
"HistoryNoMatches": " Sem resultados",
"TranscriptReasoningExpand": "expandir",
"TelemetryNoticeHeadline": "Ajude a melhorar o Codewhale?",
"TelemetryNoticeBody": "O Codewhale conta: a versão em execução, a família do sistema operacional e da\nCPU, a duração e o resultado da sessão, e contagens agregadas de recursos e\nerros.\n\nEle nunca coleta suas conversas, código, prompts, arquivos, nomes de\narquivos, repositórios ou ramos, conteúdo do modelo nem credenciais — e nunca\nenvia uma cronologia da atividade do agente por turno ou ferramenta.\n\nVocê é identificado apenas por um ID aleatório armazenado nesta máquina,\nsubstituído a cada 90 dias. Mude de ideia a qualquer momento:\n codewhale config set telemetry false\n\nEsquema completo, campo por campo: docs/TELEMETRY.md",
"TelemetryNoticeCompactBody": "Conta: versão, OS/CPU, tempo/resultado, funções/erros.\nID local: rotação 90d.\nNunca: chat/código/prompts/arquivos/nomes; conteúdo do modelo/credenciais; turnos/ferramentas.\nEsquema: docs/TELEMETRY.md\nDesl.: codewhale config set telemetry false",
"TelemetryNoticeDefaultOn": "As estatísticas de uso estão ativadas por padrão. Compartilhar totais de versão, plataforma, sessões, funções e erros com Codewhale e o operador PostHog. Sem conteúdo ou IP. docs/TELEMETRY.md Desativar: codewhale config set telemetry false",
"TelemetryNoticeHeadline": "Estatísticas de uso do Codewhale",
"TelemetryNoticeBody": "O Codewhale conta: a versão em execução, a família do sistema operacional e da\nCPU, a duração e o resultado da sessão, e contagens agregadas de recursos e\nerros.\n\nEle nunca coleta suas conversas, código, prompts, arquivos, nomes de\narquivos, repositórios ou ramos, conteúdo do modelo nem credenciais — e nunca\nenvia uma cronologia da atividade do agente por turno ou ferramenta.\n\nVocê é identificado apenas por um ID aleatório armazenado nesta máquina,\nsubstituído a cada 90 dias. Mude de ideia a qualquer momento:\n codewhale config set telemetry false\n\nEsquema completo, campo por campo: docs/TELEMETRY.md\n\nAs estatísticas de uso estão ativadas por padrão. Codewhale e PostHog processam esses totais quando o envio está configurado. Nenhum IP é coletado.",
"TelemetryNoticeCompactBody": "Conta: versão, OS/CPU, tempo/resultado, funções/erros.\nID local: rotação 90d.\nNunca: chat/código/prompts/arquivos/nomes; conteúdo do modelo/credenciais; turnos/ferramentas.\nEsquema: docs/TELEMETRY.md\nDesl.: codewhale config set telemetry false\nAs estatísticas de uso estão ativadas por padrão. Codewhale e PostHog processam esses totais quando o envio está configurado. Nenhum IP é coletado.",
"TelemetryNoticeChoiceKeep": "Sim, manter as contagens anônimas",
"TelemetryNoticeChoiceDisable": "Não, desativar o rastreamento",
"TelemetryNoticeActionChoose": "escolher",
@@ -256,9 +264,9 @@
"ConfigLabelAllowShell": "Acesso ao shell",
"ConfigLabelManagedAllowShell": "Acesso ao shell (gerenciado)",
"ConfigLabelTelemetry": "Contagem anônima de uso",
"ConfigHintTelemetry": "Preferência salva para contagens anônimas de recursos e erros; nunca conversas ou código",
"ConfigValueTelemetryOn": "Ativada (salva)",
"ConfigValueTelemetryOff": "Desativada (salva)",
"ConfigHintTelemetry": "As estatísticas de uso estão ativadas por padrão. Compartilhar totais de versão, plataforma, sessões, funções e erros com Codewhale e o operador PostHog. Sem conteúdo ou IP. docs/TELEMETRY.md",
"ConfigValueTelemetryOn": "Ativada",
"ConfigValueTelemetryOff": "Desativada",
"ConfigLabelStreamTimeout": "Tempo limite do fluxo",
"ConfigLabelTheme": "Tema",
"ConfigLabelLocale": "Idioma",
@@ -327,7 +335,6 @@
"ModelPickerAutoLocalHint": "por turno · heurística local · sem solicitação ao roteador",
"ModelPickerAutoLastRoute": "última {provider} · {model}",
"AutoRouteSelectedToast": "Auto: {provider} / {model} via {source} · Ctrl+O: detalhes da rota",
"CloudCodeSystemPromptUnsupported": "O Antigravity cloud-code não pode enviar este turno com segurança porque esta conexão ainda não aceita instruções de sistema. Nada foi enviado; escolha outro provedor.",
"HelpTitle": "Ajuda",
"HelpFilterPlaceholder": "Digite para filtrar",
"HelpFilterPrefix": "Filtro: ",
@@ -480,6 +487,12 @@
"CmdModeDescription": "Alternar permissões ou abrir o seletor de modo",
"CmdModelDescription": "Trocar ou exibir o modelo atual",
"CmdModelsDescription": "Buscar IDs de modelo ao vivo da API ativa",
"ModelsListHeader": "Modelos de {provider} (padrão: {model})",
"ModelsListHint": "Catálogo em cache; execute `codewhale models --update` para atualizar os provedores configurados.",
"ModelsUpdateSummary": "Catálogos de modelos: {updated} atualizados, {loaded} carregados do Codex, {failed} com falha, {skipped} ignorados.",
"ModelsUpdatePartial": "Não foi possível atualizar alguns catálogos. Os modelos anteriores e configurados continuam disponíveis.",
"ModelsCodexHint": "Os modelos do ChatGPT são carregados pelo Codex. O horário da consulta não confirma uma atualização do catálogo do provedor.",
"ModelsSourceFallback": "Modelos incluídos ou configurados; disponibilidade não verificada.",
"CmdModelDbDescription": "Navegar pelo banco de dados de modelos integrado",
"CmdNetworkDescription": "Gerenciar regras de rede permitidas e bloqueadas",
"CmdUpdateDescription": "Verificar e instalar uma nova versão do Codewhale",
@@ -547,7 +560,7 @@
"CmdStructcopyClipboardAccepted": "A cópia estrutural ({kind}, {bytes} bytes) foi entregue ao clipboard; se nenhum clipboard nativo estava acessível, uma gravação pelo terminal entrou na fila",
"CmdStructcopyClipboardFailed": "Falha ao copiar para o clipboard: {error}. Nada foi gravado; execute novamente com `stdout` para uma visualização de texto explícita",
"CmdStructcopyReceiptTooLarge": "Os metadados do recibo de cópia estrutural excedem o limite de saída de {bytes} bytes; a emissão foi recusada",
"CmdFleetDescription": "Inspecionar e configurar membros do Fleet e orquestração",
"CmdFleetDescription": "Inspecionar e configurar membros da equipe e orquestração",
"CmdLaneDescription": "Inspecionar e controlar Lanes duráveis (Workflows em execução)",
"CmdWorkflowDescription": "Executar um script multiagente quando ordem ou fan-out importam",
"CmdWorkflowsDescription": "Mostrar as execuções de fluxo de trabalho deste workspace (listar, cancelar)",
@@ -600,6 +613,7 @@
"FooterBalancePrefix": "saldo",
"HelpSectionActions": "Ações",
"HelpSectionClipboard": "Área de transferência",
"HelpSectionPointer": "Ponteiro",
"HelpSectionEditing": "Edição de entrada",
"HelpSectionHelp": "Ajuda",
"HelpSectionModes": "Modos",
@@ -660,14 +674,22 @@
"KbCopySelection": "Copiar a seleção atual do Codewhale",
"ClipboardSshPasteHint": "A colagem via SSH é feita pelo terminal local: use Cmd+V no macOS ou Ctrl+Shift+V no Linux/Windows.",
"KbContextMenu": "Abrir ações de contexto para colar, seleção, detalhes, contexto e ajuda",
"KbPointerScroll": "Rola a transcrição; sobre o compositor, move o cursor linha a linha",
"KbPointerClick": "Ativa a linha, o chip ou o bloco de ferramenta sob o ponteiro",
"KbPointerDrag": "Seleciona texto da transcrição, ou arrasta a barra de rolagem",
"KbAttachPath": "Adicionar arquivo ou diretório local ao contexto",
"KbHelpOverlay": "Abrir esta sobreposição de ajuda (quando entrada vazia)",
"KbCycleWorkDock": "Percorrer a barra de trabalho (tarefas, agentes, jobs, segundo plano)",
"KbCycleWorkDockBack": "Percorrer a barra de trabalho para trás",
"KbToggleHelp": "Alternar sobreposição de ajuda",
"KbToggleHelpSlash": "Alternar sobreposição de ajuda",
"HelpUsageLabel": "Uso:",
"HelpAliasesLabel": "Apelidos:",
"SettingsTitle": "Configurações:",
"SettingsConfigFile": "Arquivo de configuração:",
"SettingsTuiPrefsFolded": "Migramos {key} '{value}' de tui.toml — agora settings.toml é o dono.",
"SettingsTuiPrefsKept": "tui.toml definia {key} como '{prefs}', mas settings.toml já define '{settings}' — settings.toml vence.",
"SettingsTuiPrefsQuarantined": "Chaves de tui.toml sem configuração correspondente foram mantidas em {path}: {keys}",
"ClearConversation": "Conversa limpa",
"ClearConversationBusy": "Nada foi limpo (estado de Trabalho ou execução ativa ocupada; aguarde e tente /clear novamente)",
"ModelChanged": "Modelo alterado: {old} →, {new}",
@@ -1149,8 +1171,8 @@
"NotificationSubagentCancelled": "Subagente cancelado",
"NotificationSubagentBudgetExhausted": "Orçamento do subagente esgotado",
"FooterWorkedChip": "trabalhou {duration}",
"FleetDraftTitle": "Perfil do Fleet — rascunho de {model_label} (g para salvar)",
"FleetDraftHeader": "# .codewhale/agents/{name}\n# Rascunhado por {model_label}, validado e limitado pela Codewhale.\n# Permissões no piso do Fleet: sem shell, sem confiança, aprovação necessária.\n# Nada é salvo até que você pressione g no assistente.\n\n",
"FleetDraftTitle": "Perfil da equipe — rascunho de {model_label} (g para salvar)",
"FleetDraftHeader": "# .codewhale/agents/{name}\n# Rascunhado por {model_label}, validado e limitado pela Codewhale.\n# Permissões no piso da equipe: sem shell, sem confiança, aprovação necessária.\n# Nada é salvo até que você pressione g no assistente.\n\n",
"SetupRemoteOnRampText": "Remote Runtime On-Ramp\n\n/setup apenas mostra fatos do runtime remoto. Ele não gera bundles, grava credenciais, chama CLIs de cloud nem executa `remote-setup`.\n\nFatos atuais:\n- Clouds: {clouds_result}\n- Pontes: {bridges_result}\n- Provedores: {providers_result}\n- Modo: {mode_result}\n\nPara gerar um bundle de deploy, execute explicitamente em um terminal normal:\n\n```sh\n{command}\n```\n\nO RUNBOOK gerado contém os passos de host para revisão humana. `--apply` continua não implementado; não trate isso como auto-deploy.",
"ApprovalDescSafe": "Solicitando uma operação segura/somente leitura.",
"ApprovalDescFileWrite": "Solicitando modificação de arquivo. Confirme caminho e conteúdo.",
@@ -1234,6 +1256,11 @@
"CommandPaletteSubtitle": "Encontre e execute uma ação",
"ConfigSubtitle": "Configurações primeiro; chaves brutas no detalhe avançado",
"LaunchStartTitle": "Comece aqui",
"LaunchResumeConfirm": "Retomar {title}? Clique novamente ou pressione Enter para confirmar.",
"LaunchResumeConfirmTitle": "Retomar sessão",
"LaunchResumeConfirmBody": "Isso substitui o contexto atual pelo histórico dessa sessão.",
"LaunchResumeConfirmResume": "retomar",
"LaunchResumeConfirmCancel": "cancelar",
"LaunchWorkDescription": "Use esta pasta com ferramentas locais; as alterações seguem sua política de aprovação.",
"LaunchChatDescription": "Somente conversa e planejamento; nenhum arquivo é alterado.",
"LaunchWorkspaceGitReady": "Workspace · {name} · workspace Git",
@@ -1259,7 +1286,6 @@
"LaunchWorktreeFailed": "Falha no worktree: {error}",
"LaunchWorktreeCreated": "Novo worktree {path} na branch {branch}",
"LaunchNoSavedSessions": "Nenhuma sessão salva para este workspace.",
"LaunchComposerHint": "Enter envia · Shift+Enter nova linha · Esc volta",
"LaunchNoModelConnected": "nenhum modelo conectado",
"LaunchRunCommand": "execute {command}",
"LaunchMcpConnectedOne": "1 servidor MCP conectado",
@@ -1458,7 +1484,7 @@
"ProviderExternalRevokedToast": "Acesso à credencial externa revogado para {provider}",
"ProviderExternalRevokeFailedToast": "O acesso à credencial externa não foi revogado: {error}",
"ThemeSurfaceTitle": "tema · prévia ao vivo",
"FleetRosterHeaderLabel": "fleet",
"FleetRosterHeaderLabel": "equipe",
"FleetRosterTabRoster": "membros",
"FleetRosterTabSetup": "setup",
"FleetRosterWorkers": "workers",
@@ -1476,33 +1502,34 @@
"FleetProfileIdentityVerifyFailed": "Não foi possível verificar as identidades dos perfis existentes ({error}); corrija o arquivo indicado antes de salvar.",
"FleetProfileIdConflict": "O id de perfil `{id}` já está em uso por {path}; redija novamente com outro papel ou remova o arquivo antigo primeiro.",
"FleetProfileProviderUnconfigured": "O perfil fixa o provedor `{provider}`, que não tem credenciais configuradas ({env}); configure-o em /provider antes de salvar.",
"FleetModelAdded": "{route} adicionado ao fleet `{fleet}`",
"FleetModelAddedAs": "{route} adicionado como {roles} ao fleet `{fleet}`",
"FleetModelAddedCreatedNote": " (novo fleet pessoal, agora selecionado)",
"FleetModelAdded": "{route} adicionado à equipe `{fleet}`",
"FleetModelAddedAs": "{route} adicionado como {roles} à equipe `{fleet}`",
"FleetModelAddedCreatedNote": " (nova equipe pessoal, agora selecionada)",
"FleetModelAddedSelectedNote": " (agora selecionado)",
"FleetModelRemoved": "{route} removido do fleet `{fleet}`",
"FleetModelRemovedRoles": "{route} ({roles}) removido do fleet `{fleet}`",
"FleetModelUnchanged": "{route} permanece no fleet `{fleet}`: {reason}",
"FleetModelReasonOperatorRoute": "este é o seu modelo atual (a rota do operador do fleet); troque de modelo ou use /fleet save para alterá-la",
"FleetModelReasonAlreadyPresent": "já está no fleet",
"FleetModelErrorNeedsRoute": "um modelo do fleet precisa de um id de provider e de um id de modelo",
"FleetModelErrorNoSelection": "nenhum fleet está selecionado; seu fleet é apenas o modelo da sessão",
"FleetModelErrorOperatorRoute": "{route} é a rota do operador do fleet `{fleet}`; altere-a com /fleet save, não com remove",
"FleetModelErrorNotInFleet": "{route} não está no fleet `{fleet}`",
"FleetModelsEmpty": "Seu fleet é apenas o modelo da sessão. Adicione um: /fleet add <provider> <model> [role…] (ou ⇧F em uma linha de /model).",
"FleetModelsHeader": "Seu fleet `{fleet}` ({count} modelos)",
"FleetModelsBroken": "Não foi possível carregar o fleet selecionado: {error}. Corrija o arquivo ou escolha outro com /fleet fleets.",
"FleetModelRemoved": "{route} removido da equipe `{fleet}`",
"FleetModelRemovedRoles": "{route} ({roles}) removido da equipe `{fleet}`",
"FleetModelUnchanged": "{route} permanece na equipe `{fleet}`: {reason}",
"FleetModelReasonOperatorRoute": "este é o seu modelo atual (a rota do operador da equipe); troque de modelo ou use /fleet save para alterá-la",
"FleetModelReasonAlreadyPresent": "já está na equipe",
"FleetModelErrorNeedsRoute": "um modelo da equipe precisa de um id de provider e de um id de modelo",
"FleetModelErrorNeedsRole": "um membro da equipe é um papel; informe um (por exemplo `/fleet add {route} scout`)",
"FleetModelErrorNoSelection": "nenhuma equipe está selecionada; sua equipe é apenas o modelo da sessão",
"FleetModelErrorOperatorRoute": "{route} é a rota do operador da equipe `{fleet}`; altere-a com /fleet save, não com remove",
"FleetModelErrorNotInFleet": "{route} não está na equipe `{fleet}`",
"FleetModelsEmpty": "Sua equipe é apenas o modelo da sessão. Adicione um: /fleet add <provider> <model> [role…] (ou ⇧F em uma linha de /model).",
"FleetModelsHeader": "Sua equipe `{fleet}` ({count} modelos)",
"FleetModelsBroken": "Não foi possível carregar a equipe selecionada: {error}. Corrija o arquivo ou escolha outra com /fleet teams.",
"FleetModelsFooter": "Adicionar: /fleet add <provider> <model> [role…] · Remover: /fleet remove <provider> <model>",
"FleetModelsFactPrice": "${input}/{output} por M",
"FleetModelsFactContext": "{context}k ctx",
"FleetModelsFactTools": "ferramentas",
"FleetAddUsage": "Uso: /fleet add <provider> <model> [role…] — ex.: /fleet add openrouter z-ai/glm-5.3-flash explore",
"FleetAddProviderUnconfigured": "`{provider}` não é um provider configurado. Configure-o em ~/.codewhale/config.toml ou mude para ele com /provider antes de adicioná-lo ao fleet.",
"FleetAddProviderUnconfigured": "`{provider}` não é um provider configurado. Configure-o em ~/.codewhale/config.toml ou mude para ele com /provider antes de adicioná-lo à equipe.",
"FleetAddModelNotServed": "{provider} não serve `{model}` no catálogo atual; execute /models para ver o que ele serve, ou /fleet add com o id exato listado.",
"FleetAddFailed": "Não foi possível adicionar ao fleet: {error}",
"FleetAddFailed": "Não foi possível adicionar à equipe: {error}",
"FleetRemoveUsage": "Uso: /fleet remove <provider> <model>",
"FleetRemoveFailed": "Não foi possível remover do fleet: {error}",
"FleetToggleFailed": "Não foi possível atualizar o fleet: {error}",
"FleetRemoveFailed": "Não foi possível remover da equipe: {error}",
"FleetToggleFailed": "Não foi possível atualizar a equipe: {error}",
"FleetDestStepTitle": "Onde este perfil deve ficar?",
"FleetDestStepSubtitle": "Nada é gravado até você confirmar no último passo.",
"FleetDestProjectLabel": "Este projeto",
@@ -1640,12 +1667,13 @@
"KbTurnInspector": "Abrir Inspetor de turno",
"CmdTurnInspectDescription": "Open the whole-turn inspector",
"CmdAutomationDescription": "Gerenciar automações agendadas duráveis",
"AutomationUsage": "Uso: /automation [list|show <id>|pause <id>|resume <id>|delete <id> [--confirm <token>]|run <id>]",
"AutomationUsage": "Uso: /automation [list|show <id>|print <id>|pause <id>|resume <id>|delete <id> [--confirm <token>]|run <id>]",
"AutomationManagerUnavailable": "O gerenciador de automações não está disponível nesta sessão.",
"AutomationListFailed": "Não foi possível listar as automações: {error}",
"AutomationActionFailed": "Não foi possível {action} a automação {id}: {error}",
"AutomationEmpty": "Não há automações agendadas. Use a ferramenta `automation` para criar uma.",
"AutomationEmpty": "Nenhuma automação agendada. Escolha Nova automação.",
"AutomationListHeading": "Automações agendadas",
"AutomationScopeNote": "As automações são suas, não deste repositório — elas seguem você em todos os repositórios.",
"AutomationNoun": "Automação",
"AutomationStatusLabel": "status",
"AutomationStatusActive": "ativa",
@@ -1660,6 +1688,7 @@
"AutomationActionResume": "retomar",
"AutomationActionDelete": "excluir",
"AutomationActionRun": "executar",
"AutomationActionCancel": "cancelar execução",
"AutomationActionPaused": "pausada",
"AutomationActionResumed": "retomada",
"AutomationNextLabel": "próxima",
@@ -2015,8 +2044,43 @@
"LaunchNoticeClaude": "Claude Code detectado. Execute /import-claude para revisar o que pode ser trazido: servidores MCP, configurações seguras e permissões. Nada é aplicado sem a sua aprovação.",
"LaunchNewSession": "Nova sessão",
"LaunchRecentHeading": "Recentes",
"LaunchHelpLine": "/help para comandos · {dock} para a barra de trabalho",
"LaunchSeeAllSessions": "Ver todas as sessões…",
"LaunchNoRecentSessions": "Ainda não há sessões recentes — digite abaixo para começar.",
"LaunchResumeFailed": "Falha ao retomar: {error}",
"ReceiptSessionHooks": "hooks {count}"
"ReceiptSessionHooks": "hooks {count}",
"AutomationEditorNew": "Nova automação",
"AutomationEditorEdit": "Editar automação",
"AutomationEditorSchedule": "Agendamento",
"AutomationEditorDaily": "Diário",
"AutomationEditorWeekly": "Semanal",
"AutomationEditorHourly": "A cada hora",
"AutomationEditorOnce": "Uma vez",
"AutomationEditorCustom": "Personalizado",
"AutomationEditorTime": "Hora (HH:MM)",
"AutomationEditorDate": "Data (YYYY-MM-DD)",
"AutomationEditorDays": "Dias",
"AutomationEditorLocalTime": "Fuso horário: local ({zone})",
"AutomationEditorProvider": "O modelo fixa seu provedor. Configuração atual: {provider}.",
"AutomationEditorDefaultModel": "Modelo padrão",
"AutomationEditorInheritedProvider": "padrões do sistema",
"AutomationEditorEnabled": "Ativada",
"AutomationEditorControls": "Tab campos · Enter editar · ↑↓ ajustar · Ctrl+A selecionar tudo",
"AutomationEditorPromptControls": "Enter nova linha · Tab próximo campo · Ctrl+A selecionar tudo",
"AutomationEditorSaveFailed": "Não foi possível salvar: {error}",
"AutomationEditorBusy": "O armazenamento está ocupado. Tente salvar novamente.",
"AutomationEditorSaved": "{name} salvo",
"AutomationEditorPreviewUnavailable": "Próxima execução indisponível; o agendamento atual será mantido",
"AutomationEditorConflict": "A automação mudou. Cancele e abra novamente.",
"AutomationEditorInvalidWorkspace": "Escolha um diretório de trabalho existente.",
"AutomationEditorModelControls": "Digite para filtrar · ↑↓ mover · Enter escolher · Esc voltar",
"AutomationEditorPausedPreview": "Pausada · nenhuma execução agendada",
"AutomationEditorInvalidSchedule": "Agendamento inválido: {error}",
"AutomationEditorMonday": "Seg",
"AutomationEditorTuesday": "Ter",
"AutomationEditorWednesday": "Qua",
"AutomationEditorThursday": "Qui",
"AutomationEditorFriday": "Sex",
"AutomationEditorSaturday": "Sáb",
"AutomationEditorSunday": "Dom"
}
+98 -34
View File
@@ -1,11 +1,15 @@
{
"ExtensionsActionAdd": "добавить",
"ExtensionsActionEnable": "включить",
"ExtensionsActionEdit": "Изменить",
"ExtensionsHooksAddLabel": "Добавить хук",
"ExtensionsHooksAddDescription": "Открыть файл хуков этого проекта в вашем редакторе",
"ExtensionsActionReload": "перезагрузить",
"ExtensionsActionConnect": "подключить",
"ExtensionsActionReconnect": "переподключить",
"ExtensionsActionReauth": "повторно войти",
"ExtensionsActionDiagnose": "диагностика",
"ExtensionsActionManage": "управлять",
"ExtensionsActionFocus": "фокус",
"ExtensionsActionFold": "свернуть",
"ExtensionsActionTabs": "вкладки",
@@ -19,6 +23,7 @@
"ExtensionsGroupProblems": "Проблемы",
"ExtensionsGroupRecommended": "Рекомендуемые",
"ExtensionsGroupServers": "Серверы",
"ExtensionsGroupNeedsAttention": "Требует внимания",
"ExtensionsGroupStatus": "Элементов: {count} · Enter сворачивает",
"ExtensionsGroupUser": "Пользователь",
"ExtensionsGroupWorkspace": "Рабочая область",
@@ -45,6 +50,8 @@
"ExtensionsProductCuaDescription": "Локальная среда управления компьютером с нативным драйвером macOS.",
"ExtensionsProductDetail": "издатель: {publisher} · компоненты: {components} · источник: {source}",
"ExtensionsProductPlaywrightDescription": "Автоматизация браузера на основе Playwright MCP от Microsoft.",
"ExtensionsProductCodewhaleComputerUseDescription": "Видит экран и управляет им: управление через специальные возможности, снимки, масштаб и запись экрана, а также удалённые компьютеры по ssh.",
"ExtensionsStateFirstParty": "Входит в Codewhale",
"ExtensionsProductSandboxDescription": "Слой изоляции процессов для локальных команд, предоставляемых плагинами.",
"ExtensionsSearchLabel": "Поиск: ",
"ExtensionsSkillRootCompatibleGlobal": "Совместимые глобальные ({harness})",
@@ -123,9 +130,10 @@
"HistoryHintRestore": "Esc вернуть",
"HistoryNoMatches": " Нет совпадений",
"TranscriptReasoningExpand": "развернуть",
"TelemetryNoticeHeadline": "Поможете улучшить Codewhale?",
"TelemetryNoticeBody": "Codewhale считает: версию, операционную систему и семейство процессора,\nдлительность и результат сеанса, а также сводные счётчики функций и ошибок.\n\nОн никогда не собирает ваши разговоры, код, запросы, файлы, имена файлов,\nрепозиториев или веток, содержимое модели и учётные данные — и никогда не\nотправляет хронологию действий агента по отдельным ходам или инструментам.\n\nДля идентификации служит только случайный идентификатор на этом устройстве,\nкоторый заменяется каждые 90 дней. Передумать можно в любое время:\n codewhale config set telemetry false\n\nПолная схема по полям: docs/TELEMETRY.md",
"TelemetryNoticeCompactBody": "Счёт: версия, ОС/CPU, время/итог сеанса, функции/ошибки. Локальный случайный ID меняется каждые 90 дней. Никогда: чаты/код/запросы/файлы/имена, данные модели/учётные данные, ходы/инструменты. Схема: docs/TELEMETRY.md Выкл.: codewhale config set telemetry false",
"TelemetryNoticeDefaultOn": "Отправка статистики использования включена по умолчанию. Передавать сводные счётчики версий, платформ, сеансов, функций и ошибок Codewhale и обработчику PostHog. Без содержимого и IP. docs/TELEMETRY.md Отключить: codewhale config set telemetry false",
"TelemetryNoticeHeadline": "Статистика использования Codewhale",
"TelemetryNoticeBody": "Codewhale считает: версию, операционную систему и семейство процессора,\nдлительность и результат сеанса, а также сводные счётчики функций и ошибок.\n\nОн никогда не собирает ваши разговоры, код, запросы, файлы, имена файлов,\nрепозиториев или веток, содержимое модели и учётные данные — и никогда не\nотправляет хронологию действий агента по отдельным ходам или инструментам.\n\nДля идентификации служит только случайный идентификатор на этом устройстве,\nкоторый заменяется каждые 90 дней. Передумать можно в любое время:\n codewhale config set telemetry false\n\nПолная схема по полям: docs/TELEMETRY.md\n\nОтправка статистики использования включена по умолчанию. Codewhale и PostHog обрабатывают эти счётчики, если отправка настроена. IP не собираются.",
"TelemetryNoticeCompactBody": "Счёт: версия, ОС/CPU, время/итог сеанса, функции/ошибки. Локальный случайный ID меняется каждые 90 дней. Никогда: чаты/код/запросы/файлы/имена, данные модели/учётные данные, ходы/инструменты. Схема: docs/TELEMETRY.md Выкл.: codewhale config set telemetry false\nОтправка статистики использования включена по умолчанию. Codewhale и PostHog обрабатывают эти счётчики, если отправка настроена. IP не собираются.",
"TelemetryNoticeChoiceKeep": "Да, оставить анонимный подсчёт",
"TelemetryNoticeChoiceDisable": "Нет, отключить отслеживание",
"TelemetryNoticeActionChoose": "выбрать",
@@ -256,9 +264,9 @@
"ConfigLabelAllowShell": "Доступ к шеллу",
"ConfigLabelManagedAllowShell": "Доступ к шеллу (управляемый)",
"ConfigLabelTelemetry": "Анонимный подсчёт использования",
"ConfigHintTelemetry": "Сохранённая настройка анонимных счётчиков функций и ошибок; никогда разговоры или код",
"ConfigValueTelemetryOn": "Вкл. (сохранено)",
"ConfigValueTelemetryOff": "Выкл. (сохранено)",
"ConfigHintTelemetry": "Отправка статистики использования включена по умолчанию. Передавать сводные счётчики версий, платформ, сеансов, функций и ошибок Codewhale и обработчику PostHog. Без содержимого и IP. docs/TELEMETRY.md",
"ConfigValueTelemetryOn": "Вкл.",
"ConfigValueTelemetryOff": "Выкл.",
"ConfigLabelStreamTimeout": "Тайм-аут потока",
"ConfigLabelTheme": "Тема",
"ConfigLabelLocale": "Язык",
@@ -324,7 +332,6 @@
"ModelPickerAutoLocalHint": "на ход · локальная эвристика · без запроса к маршрутизатору",
"ModelPickerAutoLastRoute": "последний {provider} · {model}",
"AutoRouteSelectedToast": "Auto: {provider} / {model} через {source} · Ctrl+O: детали маршрута",
"CloudCodeSystemPromptUnsupported": "Antigravity cloud-code не может безопасно отправить этот ход: это подключение пока не поддерживает системные инструкции. Ничего не отправлено; выберите другого провайдера.",
"HelpTitle": "Справка",
"HelpFilterPlaceholder": "Введите для фильтра",
"HelpFilterPrefix": "Фильтр: ",
@@ -477,6 +484,12 @@
"CmdModeDescription": "Переключить разрешения или открыть выбор режима",
"CmdModelDescription": "Сменить или показать текущую модель",
"CmdModelsDescription": "Получить актуальные ID моделей из активного API",
"ModelsListHeader": "Модели {provider} (по умолчанию: {model})",
"ModelsListHint": "Сохранённый каталог; выполните `codewhale models --update`, чтобы обновить настроенных провайдеров.",
"ModelsUpdateSummary": "Каталоги моделей: обновлено {updated}, загружено из Codex {loaded}, ошибок {failed}, пропущено {skipped}.",
"ModelsUpdatePartial": "Не удалось обновить некоторые каталоги. Предыдущие и настроенные модели остаются доступны.",
"ModelsCodexHint": "Модели ChatGPT загружаются через Codex. Время запроса не подтверждает обновление каталога у провайдера.",
"ModelsSourceFallback": "Встроенные или настроенные модели; доступность не проверена.",
"CmdModelDbDescription": "Просмотр встроенной базы моделей",
"CmdNetworkDescription": "Управление правилами разрешения и запрета сети",
"CmdUpdateDescription": "Проверить и установить новый выпуск Codewhale",
@@ -544,7 +557,7 @@
"CmdStructcopyClipboardAccepted": "Структурная копия ({kind}, {bytes} байт) передана в буфер обмена; если системный буфер был недоступен, вместо этого поставлена в очередь запись в терминал",
"CmdStructcopyClipboardFailed": "Копирование в буфер обмена не удалось: {error}. Ничего не записано; запустите снова с `stdout` для явного текстового вывода",
"CmdStructcopyReceiptTooLarge": "Метаданные квитанции структурного копирования превышают предел вывода в {bytes} байт; вывод отклонён",
"CmdFleetDescription": "Просмотр и настройка участников Fleet и состояния оркестрации",
"CmdFleetDescription": "Просмотр и настройка участников команды и состояния оркестрации",
"CmdWorkflowDescription": "Запустить мультиагентный скрипт, когда важны порядок или fan-out",
"CmdWorkflowsDescription": "Показать запуски воркфлоу в этой рабочей области (список, отмена)",
"CmdHotbarDescription": "Открыть настройку Hotbar",
@@ -583,6 +596,7 @@
"FooterBalancePrefix": "баланс",
"HelpSectionActions": "Действия",
"HelpSectionClipboard": "Буфер обмена",
"HelpSectionPointer": "Указатель",
"HelpSectionEditing": "Редактирование ввода",
"HelpSectionHelp": "Справка",
"HelpSectionModes": "Режимы",
@@ -643,14 +657,22 @@
"KbCopySelection": "Скопировать текущее выделение Codewhale",
"ClipboardSshPasteHint": "Вставка по SSH идёт через локальный терминал: нажмите Cmd+V на macOS или Ctrl+Shift+V на Linux/Windows.",
"KbContextMenu": "Открыть контекстные действия: вставка, выделение, детали сообщения, контекст и справка",
"KbPointerScroll": "Прокручивает расшифровку; над полем ввода перемещает курсор построчно",
"KbPointerClick": "Активирует строку, чип или блок инструмента под указателем",
"KbPointerDrag": "Выделяет текст расшифровки или перетаскивает полосу прокрутки",
"KbAttachPath": "Добавить локальный текстовый файл или каталог в контекст",
"KbHelpOverlay": "Открыть эту справку поверх (при пустом вводе)",
"KbCycleWorkDock": "Переключать рабочую панель (задачи, агенты, задания, фон)",
"KbCycleWorkDockBack": "Переключать рабочую панель назад",
"KbToggleHelp": "Переключить справку поверх",
"KbToggleHelpSlash": "Переключить справку поверх",
"HelpUsageLabel": "Использование:",
"HelpAliasesLabel": "Псевдонимы:",
"SettingsTitle": "Настройки:",
"SettingsConfigFile": "Файл конфигурации:",
"SettingsTuiPrefsFolded": "Перенесли {key} '{value}' из tui.toml — теперь им владеет settings.toml.",
"SettingsTuiPrefsKept": "tui.toml задавал {key} как '{prefs}', но settings.toml уже задаёт '{settings}' — побеждает settings.toml.",
"SettingsTuiPrefsQuarantined": "Ключи tui.toml без соответствующей настройки сохранены в {path}: {keys}",
"ClearConversation": "Диалог очищен",
"ClearConversationBusy": "Ничего не очищено (состояние Work или фоновая работа заняты; подождите и повторите /clear)",
"ModelChanged": "Модель изменена: {old} →, {new}",
@@ -1128,8 +1150,8 @@
"NotificationSubagentCancelled": "Субагент отменён",
"NotificationSubagentBudgetExhausted": "Бюджет субагента исчерпан",
"FooterWorkedChip": "работал {duration}",
"FleetDraftTitle": "Профиль Fleet — черновик от {model_label} (g сохраняет)",
"FleetDraftHeader": "# .codewhale/agents/{name}\n# Черновик от {model_label}, проверен и ограничен Codewhale.\n# Права остаются на минимуме Fleet: без shell, без доверия, требуется одобрение.\n# Ничего не сохраняется, пока вы не нажмёте g в мастере.\n\n",
"FleetDraftTitle": "Профиль команды — черновик от {model_label} (g сохраняет)",
"FleetDraftHeader": "# .codewhale/agents/{name}\n# Черновик от {model_label}, проверен и ограничен Codewhale.\n# Права остаются на минимуме команды: без shell, без доверия, требуется одобрение.\n# Ничего не сохраняется, пока вы не нажмёте g в мастере.\n\n",
"SetupRemoteOnRampText": "Запуск удалённой среды\n\n/setup только показывает факты об удалённой среде выполнения. Он не генерирует пакеты развёртывания, не записывает учётные данные, не вызывает облачные CLI и не запускает `remote-setup`.\n\nТекущие факты:\n- Облака: {clouds_result}\n- Мосты чатов: {bridges_result}\n- Провайдеры: {providers_result}\n- Режим: {mode_result}\n\nЧтобы сгенерировать пакет развёртывания, выполните явно в обычном терминале:\n\n```sh\n{command}\n```\n\nСгенерированный RUNBOOK включает шаги для проверки человеком. `--apply` не реализован; не воспринимайте это как авторазвёртывание.",
"ApprovalDescSafe": "Запрашивается безопасная операция только для чтения.",
"ApprovalDescFileWrite": "Запрашивается изменение файла. Проверьте путь и содержимое.",
@@ -1211,6 +1233,11 @@
"CommandPaletteSubtitle": "Найти и выполнить одно действие",
"ConfigSubtitle": "Сначала настройки; сырые ключи — в расширенных деталях",
"LaunchStartTitle": "Начните здесь",
"LaunchResumeConfirm": "Возобновить {title}? Нажмите ещё раз или Enter для подтверждения.",
"LaunchResumeConfirmTitle": "Возобновить сессию",
"LaunchResumeConfirmBody": "Текущий контекст будет заменён историей той сессии.",
"LaunchResumeConfirmResume": "возобновить",
"LaunchResumeConfirmCancel": "отмена",
"LaunchWorkDescription": "Используйте локальные инструменты в этой папке; изменения подчиняются политике подтверждений.",
"LaunchChatDescription": "Только разговор и планирование; файлы не изменяются.",
"LaunchWorkspaceGitReady": "Рабочая область · {name} · область Git",
@@ -1236,7 +1263,6 @@
"LaunchWorktreeFailed": "Ошибка worktree: {error}",
"LaunchWorktreeCreated": "Новый worktree {path} в ветке {branch}",
"LaunchNoSavedSessions": "Нет сохранённых сессий для этой рабочей области.",
"LaunchComposerHint": "Enter — отправить · Shift+Enter — новая строка · Esc — назад",
"LaunchNoModelConnected": "модель не подключена",
"LaunchRunCommand": "выполните {command}",
"LaunchMcpConnectedOne": "подключён 1 сервер MCP",
@@ -1435,7 +1461,7 @@
"ProviderExternalRevokedToast": "Доступ к внешним учётным данным отозван для {provider}",
"ProviderExternalRevokeFailedToast": "Доступ к внешним учётным данным не отозван: {error}",
"ThemeSurfaceTitle": "тема · живой предпросмотр",
"FleetRosterHeaderLabel": "fleet",
"FleetRosterHeaderLabel": "команда",
"FleetRosterTabRoster": "участники",
"FleetRosterTabSetup": "настройка",
"FleetRosterWorkers": "воркеры",
@@ -1453,33 +1479,34 @@
"FleetProfileIdentityVerifyFailed": "Не удалось проверить существующие идентификаторы профилей ({error}); исправьте указанный файл перед сохранением.",
"FleetProfileIdConflict": "Идентификатор профиля `{id}` уже используется в {path}; пересоздайте черновик с другой ролью или сначала удалите старый файл.",
"FleetProfileProviderUnconfigured": "Профиль закрепляет провайдера `{provider}`, для которого нет настроенных учётных данных ({env}); настройте его в /provider перед сохранением.",
"FleetModelAdded": "{route} добавлен во fleet `{fleet}`",
"FleetModelAddedAs": "{route} добавлен как {roles} во fleet `{fleet}`",
"FleetModelAddedCreatedNote": " (новый личный fleet, теперь выбран)",
"FleetModelAdded": "{route} добавлен в команду `{fleet}`",
"FleetModelAddedAs": "{route} добавлен как {roles} в команду `{fleet}`",
"FleetModelAddedCreatedNote": " (новая личная команда, теперь выбрана)",
"FleetModelAddedSelectedNote": " (теперь выбран)",
"FleetModelRemoved": "{route} удалён из fleet `{fleet}`",
"FleetModelRemovedRoles": "{route} ({roles}) удалён из fleet `{fleet}`",
"FleetModelUnchanged": "{route} остаётся во fleet `{fleet}`: {reason}",
"FleetModelReasonOperatorRoute": "это ваша текущая модель (маршрут оператора fleet); переключите модель или измените его через /fleet save",
"FleetModelReasonAlreadyPresent": "уже во fleet",
"FleetModelErrorNeedsRoute": "модели fleet нужны и id провайдера, и id модели",
"FleetModelErrorNoSelection": "fleet не выбран; ваш fleet — только модель сессии",
"FleetModelErrorOperatorRoute": "{route} — маршрут оператора fleet `{fleet}`; меняйте его через /fleet save, а не remove",
"FleetModelErrorNotInFleet": "{route} нет во fleet `{fleet}`",
"FleetModelsEmpty": "Ваш fleet — только модель сессии. Добавьте модель: /fleet add <provider> <model> [role…] (или ⇧F на строке в /model).",
"FleetModelsHeader": "Ваш fleet `{fleet}` (моделей: {count})",
"FleetModelsBroken": "Не удалось загрузить выбранный fleet: {error}. Исправьте файл или выберите другой через /fleet fleets.",
"FleetModelRemoved": "{route} удалён из команды `{fleet}`",
"FleetModelRemovedRoles": "{route} ({roles}) удалён из команды `{fleet}`",
"FleetModelUnchanged": "{route} остаётся в команде `{fleet}`: {reason}",
"FleetModelReasonOperatorRoute": "это ваша текущая модель (маршрут оператора команды); переключите модель или измените его через /fleet save",
"FleetModelReasonAlreadyPresent": "уже в команде",
"FleetModelErrorNeedsRoute": "модели команды нужны и id провайдера, и id модели",
"FleetModelErrorNeedsRole": "участник команды — это роль; укажите её (например `/fleet add {route} scout`)",
"FleetModelErrorNoSelection": "команда не выбрана; ваша команда — только модель сессии",
"FleetModelErrorOperatorRoute": "{route} — маршрут оператора команды `{fleet}`; меняйте его через /fleet save, а не remove",
"FleetModelErrorNotInFleet": "{route} нет в команде `{fleet}`",
"FleetModelsEmpty": "Ваша команда — только модель сессии. Добавьте модель: /fleet add <provider> <model> [role…] (или ⇧F на строке в /model).",
"FleetModelsHeader": "Ваша команда `{fleet}` (моделей: {count})",
"FleetModelsBroken": "Не удалось загрузить выбранную команду: {error}. Исправьте файл или выберите другой через /fleet teams.",
"FleetModelsFooter": "Добавить: /fleet add <provider> <model> [role…] · Удалить: /fleet remove <provider> <model>",
"FleetModelsFactPrice": "${input}/{output} за M",
"FleetModelsFactContext": "{context}k ctx",
"FleetModelsFactTools": "инструменты",
"FleetAddUsage": "Использование: /fleet add <provider> <model> [role…] — например /fleet add openrouter z-ai/glm-5.3-flash explore",
"FleetAddProviderUnconfigured": "`{provider}` не является настроенным провайдером. Настройте его в ~/.codewhale/config.toml или переключитесь на него через /provider, прежде чем добавлять во fleet.",
"FleetAddProviderUnconfigured": "`{provider}` не является настроенным провайдером. Настройте его в ~/.codewhale/config.toml или переключитесь на него через /provider, прежде чем добавлять в команду.",
"FleetAddModelNotServed": "{provider} не предоставляет `{model}` в текущем каталоге; выполните /models, чтобы увидеть доступные модели, или /fleet add с точным id из списка.",
"FleetAddFailed": "Не удалось добавить во fleet: {error}",
"FleetAddFailed": "Не удалось добавить в команду: {error}",
"FleetRemoveUsage": "Использование: /fleet remove <provider> <model>",
"FleetRemoveFailed": "Не удалось удалить из fleet: {error}",
"FleetToggleFailed": "Не удалось обновить fleet: {error}",
"FleetRemoveFailed": "Не удалось удалить из команды: {error}",
"FleetToggleFailed": "Не удалось обновить команду: {error}",
"FleetDestStepTitle": "Где сохранить этот профиль?",
"FleetDestStepSubtitle": "Ничего не записывается, пока вы не подтвердите на последнем шаге.",
"FleetDestProjectLabel": "Этот проект",
@@ -1640,12 +1667,13 @@
"KbTurnInspector": "Открыть инспектор хода",
"CmdTurnInspectDescription": "Open the whole-turn inspector",
"CmdAutomationDescription": "Управление постоянными запланированными автоматизациями",
"AutomationUsage": "Использование: /automation [list|show <id>|pause <id>|resume <id>|delete <id> [--confirm <token>]|run <id>]",
"AutomationUsage": "Использование: /automation [list|show <id>|print <id>|pause <id>|resume <id>|delete <id> [--confirm <token>]|run <id>]",
"AutomationManagerUnavailable": "Менеджер автоматизаций недоступен в этом сеансе.",
"AutomationListFailed": "Не удалось получить список автоматизаций: {error}",
"AutomationActionFailed": "Не удалось {action} автоматизацию {id}: {error}",
"AutomationEmpty": "Нет запланированных автоматизаций. Создать новую можно с помощью инструмента `automation`.",
"AutomationEmpty": "Нет запланированных автоматизаций. Выберите «Новая автоматизация».",
"AutomationListHeading": "Запланированные автоматизации",
"AutomationScopeNote": "Автоматизации принадлежат вам, а не этому репозиторию — они следуют за вами во все репозитории.",
"AutomationNoun": "Автоматизация",
"AutomationStatusLabel": "состояние",
"AutomationStatusActive": "активна",
@@ -1660,6 +1688,7 @@
"AutomationActionResume": "возобновить",
"AutomationActionDelete": "удалить",
"AutomationActionRun": "запустить",
"AutomationActionCancel": "отменить запуск",
"AutomationActionPaused": "приостановлена",
"AutomationActionResumed": "возобновлена",
"AutomationNextLabel": "следующая",
@@ -2015,8 +2044,43 @@
"LaunchNoticeClaude": "Обнаружен Claude Code. Выполните /import-claude, чтобы посмотреть, что можно перенести: MCP-серверы, безопасные настройки и разрешения. Ничего не применяется без вашего согласия.",
"LaunchNewSession": "Новая сессия",
"LaunchRecentHeading": "Недавние",
"LaunchHelpLine": "/help — команды · {dock} — рабочая панель",
"LaunchSeeAllSessions": "Показать все сессии…",
"LaunchNoRecentSessions": "Пока нет недавних сессий — введите ниже, чтобы начать.",
"LaunchResumeFailed": "Не удалось возобновить: {error}",
"ReceiptSessionHooks": "хуков: {count}"
"ReceiptSessionHooks": "хуков: {count}",
"AutomationEditorNew": "Новая автоматизация",
"AutomationEditorEdit": "Изменить автоматизацию",
"AutomationEditorSchedule": "Расписание",
"AutomationEditorDaily": "Ежедневно",
"AutomationEditorWeekly": "Еженедельно",
"AutomationEditorHourly": "Каждый час",
"AutomationEditorOnce": "Один раз",
"AutomationEditorCustom": "Другое",
"AutomationEditorTime": "Время (HH:MM)",
"AutomationEditorDate": "Дата (YYYY-MM-DD)",
"AutomationEditorDays": "Дни",
"AutomationEditorLocalTime": "Часовой пояс: местный ({zone})",
"AutomationEditorProvider": "Выбор модели закрепляет провайдера. Текущая конфигурация: {provider}.",
"AutomationEditorDefaultModel": "Модель по умолчанию",
"AutomationEditorInheritedProvider": "настройки среды выполнения",
"AutomationEditorEnabled": "Включено",
"AutomationEditorControls": "Tab поля · Enter правка · ↑↓ настройка · Ctrl+A выделить всё",
"AutomationEditorPromptControls": "Enter новая строка · Tab следующее поле · Ctrl+A выделить всё",
"AutomationEditorSaveFailed": "Не удалось сохранить: {error}",
"AutomationEditorBusy": "Хранилище автоматизаций занято. Повторите сохранение.",
"AutomationEditorSaved": "Сохранено: {name}",
"AutomationEditorPreviewUnavailable": "Следующий запуск неизвестен; текущее расписание сохранится",
"AutomationEditorConflict": "Автоматизация изменилась. Отмените и откройте заново.",
"AutomationEditorInvalidWorkspace": "Выберите существующий рабочий каталог.",
"AutomationEditorModelControls": "Ввод для фильтра · ↑↓ перемещение · Enter выбор · Esc назад",
"AutomationEditorPausedPreview": "Приостановлено · запуск не запланирован",
"AutomationEditorInvalidSchedule": "Неверное расписание: {error}",
"AutomationEditorMonday": "Пн",
"AutomationEditorTuesday": "Вт",
"AutomationEditorWednesday": "Ср",
"AutomationEditorThursday": "Чт",
"AutomationEditorFriday": "Пт",
"AutomationEditorSaturday": "Сб",
"AutomationEditorSunday": "Вс"
}
+99 -35
View File
@@ -1,11 +1,15 @@
{
"ExtensionsActionAdd": "додати",
"ExtensionsActionEnable": "увімкнути",
"ExtensionsActionEdit": "Змінити",
"ExtensionsHooksAddLabel": "Додати хук",
"ExtensionsHooksAddDescription": "Відкрити файл хуків цього проєкту у вашому редакторі",
"ExtensionsActionReload": "перезавантажити",
"ExtensionsActionConnect": "підключити",
"ExtensionsActionReconnect": "перепідключити",
"ExtensionsActionReauth": "повторно увійти",
"ExtensionsActionDiagnose": "діагностика",
"ExtensionsActionManage": "керувати",
"ExtensionsActionFocus": "фокус",
"ExtensionsActionFold": "згорнути",
"ExtensionsActionTabs": "вкладки",
@@ -19,6 +23,7 @@
"ExtensionsGroupProblems": "Проблеми",
"ExtensionsGroupRecommended": "Рекомендовані",
"ExtensionsGroupServers": "Сервери",
"ExtensionsGroupNeedsAttention": "Потребує уваги",
"ExtensionsGroupStatus": "Елементів: {count} · Enter згортає",
"ExtensionsGroupUser": "Користувач",
"ExtensionsGroupWorkspace": "Робочий простір",
@@ -45,6 +50,8 @@
"ExtensionsProductCuaDescription": "Локальне середовище керування комп'ютером із нативним драйвером macOS.",
"ExtensionsProductDetail": "видавець: {publisher} · компоненти: {components} · джерело: {source}",
"ExtensionsProductPlaywrightDescription": "Автоматизація браузера на основі Playwright MCP від Microsoft.",
"ExtensionsProductCodewhaleComputerUseDescription": "Бачить екран і керує ним: керування через спеціальні можливості, знімки, масштаб і запис екрана, а також віддалені комп'ютери через ssh.",
"ExtensionsStateFirstParty": "Входить до Codewhale",
"ExtensionsProductSandboxDescription": "Шар ізоляції процесів для локальних команд, що надаються плагінами.",
"ExtensionsSearchLabel": "Пошук: ",
"ExtensionsSkillRootCompatibleGlobal": "Сумісні глобальні ({harness})",
@@ -123,9 +130,10 @@
"HistoryHintRestore": "Esc — відновити",
"HistoryNoMatches": " Збігів немає",
"TranscriptReasoningExpand": "розгорнути",
"TelemetryNoticeHeadline": "Допоможете покращити Codewhale?",
"TelemetryNoticeBody": "Codewhale рахує: версію, операційну систему й сімейство процесора,\nтривалість і результат сеансу, а також зведені лічильники функцій і помилок.\n\nВін ніколи не збирає ваші розмови, код, запити, файли, назви файлів,\nсховищ або гілок, вміст моделі чи облікові дані — і ніколи не надсилає\nхронологію дій агента за окремими кроками або інструментами.\n\nДля розпізнавання використовується лише випадковий ідентифікатор на цьому\nпристрої, який замінюють кожні 90 днів. Передумати можна будь-коли:\n codewhale config set telemetry false\n\nПовна схема за полями: docs/TELEMETRY.md",
"TelemetryNoticeCompactBody": "Лічба: версія, ОС/CPU, час/результат сеансу, функції/помилки. Випадковий локальний ID змінюється кожні 90 днів. Ніколи: чати/код/запити/файли/назви, дані моделі/облікові дані, кроки/інструменти. Схема: docs/TELEMETRY.md Вимк.: codewhale config set telemetry false",
"TelemetryNoticeDefaultOn": "Надсилання статистики використання ввімкнено за замовчуванням. Передавати зведені лічильники версій, платформ, сеансів, функцій і помилок Codewhale та обробнику PostHog. Без вмісту та IP. docs/TELEMETRY.md Вимкнути: codewhale config set telemetry false",
"TelemetryNoticeHeadline": "Статистика використання Codewhale",
"TelemetryNoticeBody": "Codewhale рахує: версію, операційну систему й сімейство процесора,\nтривалість і результат сеансу, а також зведені лічильники функцій і помилок.\n\nВін ніколи не збирає ваші розмови, код, запити, файли, назви файлів,\nсховищ або гілок, вміст моделі чи облікові дані — і ніколи не надсилає\nхронологію дій агента за окремими кроками або інструментами.\n\nДля розпізнавання використовується лише випадковий ідентифікатор на цьому\nпристрої, який замінюють кожні 90 днів. Передумати можна будь-коли:\n codewhale config set telemetry false\n\nПовна схема за полями: docs/TELEMETRY.md\n\nНадсилання статистики використання ввімкнено за замовчуванням. Codewhale і PostHog обробляють ці лічильники, якщо надсилання налаштовано. IP не збираються.",
"TelemetryNoticeCompactBody": "Лічба: версія, ОС/CPU, час/результат сеансу, функції/помилки. Випадковий локальний ID змінюється кожні 90 днів. Ніколи: чати/код/запити/файли/назви, дані моделі/облікові дані, кроки/інструменти. Схема: docs/TELEMETRY.md Вимк.: codewhale config set telemetry false\nНадсилання статистики використання ввімкнено за замовчуванням. Codewhale і PostHog обробляють ці лічильники, якщо надсилання налаштовано. IP не збираються.",
"TelemetryNoticeChoiceKeep": "Так, лишити анонімний підрахунок",
"TelemetryNoticeChoiceDisable": "Ні, вимкнути відстеження",
"TelemetryNoticeActionChoose": "вибрати",
@@ -256,9 +264,9 @@
"ConfigLabelAllowShell": "Доступ до оболонки",
"ConfigLabelManagedAllowShell": "Доступ до оболонки (керовано)",
"ConfigLabelTelemetry": "Анонімний підрахунок використання",
"ConfigHintTelemetry": "Збережене налаштування анонімних лічильників функцій і помилок; ніколи розмови чи код",
"ConfigValueTelemetryOn": "Увімк. (збережено)",
"ConfigValueTelemetryOff": "Вимк. (збережено)",
"ConfigHintTelemetry": "Надсилання статистики використання ввімкнено за замовчуванням. Передавати зведені лічильники версій, платформ, сеансів, функцій і помилок Codewhale та обробнику PostHog. Без вмісту та IP. docs/TELEMETRY.md",
"ConfigValueTelemetryOn": "Увімк.",
"ConfigValueTelemetryOff": "Вимк.",
"ConfigLabelStreamTimeout": "Тайм-аут потоку",
"ConfigLabelTheme": "Тема",
"ConfigLabelLocale": "Мова",
@@ -324,7 +332,6 @@
"ModelPickerAutoLocalHint": "за крок · локальна евристика · без запиту до маршрутизатора",
"ModelPickerAutoLastRoute": "останній {provider} · {model}",
"AutoRouteSelectedToast": "Auto: {provider} / {model} через {source} · Ctrl+O: деталі маршруту",
"CloudCodeSystemPromptUnsupported": "Antigravity cloud-code не може безпечно надіслати цей хід: це з’єднання ще не підтримує системні інструкції. Нічого не надіслано; виберіть іншого провайдера.",
"HelpTitle": "Довідка",
"HelpFilterPlaceholder": "Вводьте для фільтра",
"HelpFilterPrefix": "Фільтр: ",
@@ -477,6 +484,12 @@
"CmdModeDescription": "Перемкнути дозволи або відкрити вибір режиму",
"CmdModelDescription": "Змінити або переглянути поточну модель",
"CmdModelsDescription": "Отримати актуальні ID моделей з активного API",
"ModelsListHeader": "Моделі {provider} (типова: {model})",
"ModelsListHint": "Збережений каталог; виконайте `codewhale models --update`, щоб оновити налаштованих провайдерів.",
"ModelsUpdateSummary": "Каталоги моделей: оновлено {updated}, завантажено з Codex {loaded}, помилок {failed}, пропущено {skipped}.",
"ModelsUpdatePartial": "Не вдалося оновити деякі каталоги. Попередні та налаштовані моделі залишаються доступними.",
"ModelsCodexHint": "Моделі ChatGPT завантажуються через Codex. Час запиту не підтверджує оновлення каталогу постачальника.",
"ModelsSourceFallback": "Вбудовані або налаштовані моделі; доступність не перевірено.",
"CmdModelDbDescription": "Переглянути вбудовану базу моделей",
"CmdNetworkDescription": "Керувати мережевими правилами дозволу та заборони",
"CmdUpdateDescription": "Перевірити та встановити новий випуск Codewhale",
@@ -544,7 +557,7 @@
"CmdStructcopyClipboardAccepted": "Структурну копію ({kind}, {bytes} байтів) передано до буфера обміну; якщо системний буфер був недосяжним, натомість поставлено в чергу запис у термінал",
"CmdStructcopyClipboardFailed": "Копіювання до буфера обміну не вдалося: {error}. Нічого не записано; запустіть знову з `stdout` для явного текстового вигляду",
"CmdStructcopyReceiptTooLarge": "Метадані квитанції структурного копіювання перевищують ліміт виводу в {bytes} байтів; вивід відхилено",
"CmdFleetDescription": "Переглянути й налаштувати учасників Fleet та стан оркестрації",
"CmdFleetDescription": "Переглянути й налаштувати учасників команди та стан оркестрації",
"CmdWorkflowDescription": "Запустити мультиагентний скрипт, коли важливі порядок або fan-out",
"CmdWorkflowsDescription": "Показати запуски робочого процесу в цьому робочому просторі (список, скасувати)",
"CmdHotbarDescription": "Відкрити налаштування Hotbar",
@@ -583,6 +596,7 @@
"FooterBalancePrefix": "баланс",
"HelpSectionActions": "Дії",
"HelpSectionClipboard": "Буфер обміну",
"HelpSectionPointer": "Вказівник",
"HelpSectionEditing": "Редагування вводу",
"HelpSectionHelp": "Довідка",
"HelpSectionModes": "Режими",
@@ -643,14 +657,22 @@
"KbCopySelection": "Скопіювати поточне виділення Codewhale",
"ClipboardSshPasteHint": "Вставлення через SSH використовує локальний термінал: натисніть Cmd+V на macOS або Ctrl+Shift+V на Linux/Windows.",
"KbContextMenu": "Відкрити контекстні дії: вставлення, виділення, деталі повідомлення, контекст і довідка",
"KbPointerScroll": "Прокручує розшифровку; над полем вводу переміщує курсор по рядках",
"KbPointerClick": "Активує рядок, чип або блок інструмента під вказівником",
"KbPointerDrag": "Виділяє текст розшифровки або перетягує смугу прокручування",
"KbAttachPath": "Додати локальний текстовий файл або каталог до контексту",
"KbHelpOverlay": "Відкрити це накладення довідки (коли ввід порожній)",
"KbCycleWorkDock": "Перемикати робочу панель (задачі, агенти, завдання, фон)",
"KbCycleWorkDockBack": "Перемикати робочу панель назад",
"KbToggleHelp": "Перемкнути накладення довідки",
"KbToggleHelpSlash": "Перемкнути накладення довідки",
"HelpUsageLabel": "Використання:",
"HelpAliasesLabel": "Псевдоніми:",
"SettingsTitle": "Налаштування:",
"SettingsConfigFile": "Файл конфігурації:",
"SettingsTuiPrefsFolded": "Перенесли {key} '{value}' із tui.toml — тепер ним керує settings.toml.",
"SettingsTuiPrefsKept": "tui.toml задавав {key} як '{prefs}', але settings.toml уже задає '{settings}' — перемагає settings.toml.",
"SettingsTuiPrefsQuarantined": "Ключі tui.toml без відповідного налаштування збережено в {path}: {keys}",
"ClearConversation": "Розмову очищено",
"ClearConversationBusy": "Нічого не очищено (стан роботи або виконання зайняті; зачекайте й повторіть /clear)",
"ModelChanged": "Модель змінено: {old} →, {new}",
@@ -1128,8 +1150,8 @@
"NotificationSubagentCancelled": "Субагента скасовано",
"NotificationSubagentBudgetExhausted": "Бюджет субагента вичерпано",
"FooterWorkedChip": "працював {duration}",
"FleetDraftTitle": "Профіль Fleet — чернетка від {model_label} (g зберігає)",
"FleetDraftHeader": "# .codewhale/agents/{name}\n# Чернетка від {model_label}, перевірена й обмежена Codewhale.\n# Дозволи залишаються на базовому рівні Fleet: без shell, без довіри, потрібне схвалення.\n# Нічого не буде збережено, доки ви не натиснете g у майстрі.\n\n",
"FleetDraftTitle": "Профіль команди — чернетка від {model_label} (g зберігає)",
"FleetDraftHeader": "# .codewhale/agents/{name}\n# Чернетка від {model_label}, перевірена й обмежена Codewhale.\n# Дозволи залишаються на базовому рівні команди: без shell, без довіри, потрібне схвалення.\n# Нічого не буде збережено, доки ви не натиснете g у майстрі.\n\n",
"SetupRemoteOnRampText": "Безпечний старт віддаленого середовища виконання\n\n/setup лише показує факти про віддалене середовище виконання. Він не генерує пакети розгортання, не записує облікові дані, не викликає хмарні CLI й не запускає `remote-setup`.\n\nПоточні факти:\n- Хмари: {clouds_result}\n- Мости чатів: {bridges_result}\n- Провайдери: {providers_result}\n- Режим: {mode_result}\n\nЩоб згенерувати пакет розгортання, виконайте явно у звичайному терміналі:\n\n```sh\n{command}\n```\n\nЗгенерований RUNBOOK містить кроки для хоста, призначені для перегляду людиною. `--apply` лишається нереалізованим; не вважайте це авторозгортанням.",
"ApprovalDescSafe": "Запит на безпечну операцію лише для читання.",
"ApprovalDescFileWrite": "Запит на змінення файлу. Підтвердьте шлях і вміст.",
@@ -1211,6 +1233,11 @@
"CommandPaletteSubtitle": "Знайдіть і виконайте одну дію",
"ConfigSubtitle": "Спочатку налаштування; сирі ключі — в розширених деталях",
"LaunchStartTitle": "Почніть тут",
"LaunchResumeConfirm": "Відновити {title}? Натисніть ще раз або Enter для підтвердження.",
"LaunchResumeConfirmTitle": "Відновити сесію",
"LaunchResumeConfirmBody": "Поточний контекст буде замінено історією тієї сесії.",
"LaunchResumeConfirmResume": "відновити",
"LaunchResumeConfirmCancel": "скасувати",
"LaunchWorkDescription": "Використовуйте локальні інструменти в цій папці; зміни підпорядковуються політиці схвалення.",
"LaunchChatDescription": "Лише розмова й планування; файли не змінюються.",
"LaunchWorkspaceGitReady": "Робочий простір · {name} · простір Git",
@@ -1236,7 +1263,6 @@
"LaunchWorktreeFailed": "Worktree не вдався: {error}",
"LaunchWorktreeCreated": "Новий worktree {path} у гілці {branch}",
"LaunchNoSavedSessions": "Немає збережених сесій для цього робочого простору.",
"LaunchComposerHint": "Enter — надіслати · Shift+Enter — новий рядок · Esc — назад",
"LaunchNoModelConnected": "модель не підключено",
"LaunchRunCommand": "виконайте {command}",
"LaunchMcpConnectedOne": "підключено 1 сервер MCP",
@@ -1435,7 +1461,7 @@
"ProviderExternalRevokedToast": "Доступ до зовнішніх облікових даних відкликано для {provider}",
"ProviderExternalRevokeFailedToast": "Доступ до зовнішніх облікових даних не відкликано: {error}",
"ThemeSurfaceTitle": "тема · живий перегляд",
"FleetRosterHeaderLabel": "fleet",
"FleetRosterHeaderLabel": "команда",
"FleetRosterTabRoster": "учасники",
"FleetRosterTabSetup": "налаштування",
"FleetRosterWorkers": "воркери",
@@ -1453,33 +1479,34 @@
"FleetProfileIdentityVerifyFailed": "Не вдалося перевірити наявні ідентичності профілів ({error}); виправте названий файл перед збереженням.",
"FleetProfileIdConflict": "Ідентифікатор профілю `{id}` уже використовується в {path}; створіть чернетку з іншою роллю або спочатку видаліть старий файл.",
"FleetProfileProviderUnconfigured": "Профіль закріплює провайдера `{provider}`, для якого не налаштовано облікові дані ({env}); налаштуйте його в /provider перед збереженням.",
"FleetModelAdded": "{route} додано до fleet `{fleet}`",
"FleetModelAddedAs": "{route} додано як {roles} до fleet `{fleet}`",
"FleetModelAddedCreatedNote": " (новий особистий fleet, тепер вибрано)",
"FleetModelAddedSelectedNote": " (тепер вибрано)",
"FleetModelRemoved": "{route} вилучено з fleet `{fleet}`",
"FleetModelRemovedRoles": "{route} ({roles}) вилучено з fleet `{fleet}`",
"FleetModelUnchanged": "{route} залишається у fleet `{fleet}`: {reason}",
"FleetModelReasonOperatorRoute": "це ваша поточна модель (маршрут оператора fleet); перемкніть модель або змініть його через /fleet save",
"FleetModelReasonAlreadyPresent": "уже у fleet",
"FleetModelErrorNeedsRoute": "моделі fleet потрібні і id провайдера, і id моделі",
"FleetModelErrorNoSelection": "fleet не вибрано; ваш fleet — лише модель сесії",
"FleetModelErrorOperatorRoute": "{route} — маршрут оператора fleet `{fleet}`; змінюйте його через /fleet save, а не remove",
"FleetModelErrorNotInFleet": "{route} немає у fleet `{fleet}`",
"FleetModelsEmpty": "Ваш fleet — лише модель сесії. Додайте модель: /fleet add <provider> <model> [role…] (або ⇧F на рядку в /model).",
"FleetModelsHeader": "Ваш fleet `{fleet}` (моделей: {count})",
"FleetModelsBroken": "Не вдалося завантажити вибраний fleet: {error}. Виправте файл або виберіть інший через /fleet fleets.",
"FleetModelAdded": "{route} додано до команди `{fleet}`",
"FleetModelAddedAs": "{route} додано як {roles} до команди `{fleet}`",
"FleetModelAddedCreatedNote": " (нова особиста команда, тепер вибрана)",
"FleetModelAddedSelectedNote": " (тепер вибрана)",
"FleetModelRemoved": "{route} вилучено з команди `{fleet}`",
"FleetModelRemovedRoles": "{route} ({roles}) вилучено з команди `{fleet}`",
"FleetModelUnchanged": "{route} залишається у команді `{fleet}`: {reason}",
"FleetModelReasonOperatorRoute": "це ваша поточна модель (маршрут оператора команди); перемкніть модель або змініть його через /fleet save",
"FleetModelReasonAlreadyPresent": "уже у команді",
"FleetModelErrorNeedsRoute": "моделі команди потрібні і id провайдера, і id моделі",
"FleetModelErrorNeedsRole": "учасник команди — це роль; вкажіть її (наприклад `/fleet add {route} scout`)",
"FleetModelErrorNoSelection": "команда не вибрана; ваша команда — лише модель сесії",
"FleetModelErrorOperatorRoute": "{route} — маршрут оператора команди `{fleet}`; змінюйте його через /fleet save, а не remove",
"FleetModelErrorNotInFleet": "{route} немає у команді `{fleet}`",
"FleetModelsEmpty": "Ваша команда — лише модель сесії. Додайте модель: /fleet add <provider> <model> [role…] (або ⇧F на рядку в /model).",
"FleetModelsHeader": "Ваша команда `{fleet}` (моделей: {count})",
"FleetModelsBroken": "Не вдалося завантажити вибрану команду: {error}. Виправте файл або виберіть іншу через /fleet teams.",
"FleetModelsFooter": "Додати: /fleet add <provider> <model> [role…] · Вилучити: /fleet remove <provider> <model>",
"FleetModelsFactPrice": "${input}/{output} за M",
"FleetModelsFactContext": "{context}k ctx",
"FleetModelsFactTools": "інструменти",
"FleetAddUsage": "Використання: /fleet add <provider> <model> [role…] — наприклад /fleet add openrouter z-ai/glm-5.3-flash explore",
"FleetAddProviderUnconfigured": "`{provider}` не є налаштованим провайдером. Налаштуйте його в ~/.codewhale/config.toml або перемкніться на нього через /provider, перш ніж додавати до fleet.",
"FleetAddProviderUnconfigured": "`{provider}` не є налаштованим провайдером. Налаштуйте його в ~/.codewhale/config.toml або перемкніться на нього через /provider, перш ніж додавати до команди.",
"FleetAddModelNotServed": "{provider} не надає `{model}` у поточному каталозі; виконайте /models, щоб побачити доступні моделі, або /fleet add із точним id зі списку.",
"FleetAddFailed": "Не вдалося додати до fleet: {error}",
"FleetAddFailed": "Не вдалося додати до команди: {error}",
"FleetRemoveUsage": "Використання: /fleet remove <provider> <model>",
"FleetRemoveFailed": "Не вдалося вилучити з fleet: {error}",
"FleetToggleFailed": "Не вдалося оновити fleet: {error}",
"FleetRemoveFailed": "Не вдалося вилучити з команди: {error}",
"FleetToggleFailed": "Не вдалося оновити команду: {error}",
"FleetDestStepTitle": "Де зберегти цей профіль?",
"FleetDestStepSubtitle": "Нічого не записується, доки ви не підтвердите на останньому кроці.",
"FleetDestProjectLabel": "Цей проєкт",
@@ -1640,12 +1667,13 @@
"KbTurnInspector": "Відкрити інспектор кроку",
"CmdTurnInspectDescription": "Open the whole-turn inspector",
"CmdAutomationDescription": "Керування постійними запланованими автоматизаціями",
"AutomationUsage": "Використання: /automation [list|show <id>|pause <id>|resume <id>|delete <id> [--confirm <token>]|run <id>]",
"AutomationUsage": "Використання: /automation [list|show <id>|print <id>|pause <id>|resume <id>|delete <id> [--confirm <token>]|run <id>]",
"AutomationManagerUnavailable": "Менеджер автоматизацій недоступний у цьому сеансі.",
"AutomationListFailed": "Не вдалося отримати список автоматизацій: {error}",
"AutomationActionFailed": "Не вдалося {action} автоматизацію {id}: {error}",
"AutomationEmpty": "Немає запланованих автоматизацій. Створити нову можна за допомогою інструмента `automation`.",
"AutomationEmpty": "Немає запланованих автоматизацій. Виберіть «Нова автоматизація».",
"AutomationListHeading": "Заплановані автоматизації",
"AutomationScopeNote": "Автоматизації належать вам, а не цьому репозиторію — вони йдуть за вами в кожен репозиторій.",
"AutomationNoun": "Автоматизація",
"AutomationStatusLabel": "стан",
"AutomationStatusActive": "активна",
@@ -1660,6 +1688,7 @@
"AutomationActionResume": "відновити",
"AutomationActionDelete": "видалити",
"AutomationActionRun": "запустити",
"AutomationActionCancel": "скасувати запуск",
"AutomationActionPaused": "призупинена",
"AutomationActionResumed": "відновлена",
"AutomationNextLabel": "наступна",
@@ -2015,8 +2044,43 @@
"LaunchNoticeClaude": "Виявлено Claude Code. Виконайте /import-claude, щоб переглянути, що можна перенести: MCP-сервери, безпечні налаштування та дозволи. Нічого не застосовується без вашої згоди.",
"LaunchNewSession": "Нова сесія",
"LaunchRecentHeading": "Нещодавні",
"LaunchHelpLine": "/help — команди · {dock} — робоча панель",
"LaunchSeeAllSessions": "Показати всі сесії…",
"LaunchNoRecentSessions": "Поки немає нещодавніх сесій — введіть нижче, щоб почати.",
"LaunchResumeFailed": "Не вдалося відновити: {error}",
"ReceiptSessionHooks": "гачків: {count}"
"ReceiptSessionHooks": "гачків: {count}",
"AutomationEditorNew": "Нова автоматизація",
"AutomationEditorEdit": "Редагувати автоматизацію",
"AutomationEditorSchedule": "Розклад",
"AutomationEditorDaily": "Щодня",
"AutomationEditorWeekly": "Щотижня",
"AutomationEditorHourly": "Щогодини",
"AutomationEditorOnce": "Один раз",
"AutomationEditorCustom": "Власний",
"AutomationEditorTime": "Час (HH:MM)",
"AutomationEditorDate": "Дата (YYYY-MM-DD)",
"AutomationEditorDays": "Дні",
"AutomationEditorLocalTime": "Часовий пояс: місцевий ({zone})",
"AutomationEditorProvider": "Вибір моделі закріплює провайдера. Поточна конфігурація: {provider}.",
"AutomationEditorDefaultModel": "Типова модель",
"AutomationEditorInheritedProvider": "типові параметри середовища",
"AutomationEditorEnabled": "Увімкнено",
"AutomationEditorControls": "Tab поля · Enter редагувати · ↑↓ змінити · Ctrl+A вибрати все",
"AutomationEditorPromptControls": "Enter новий рядок · Tab наступне поле · Ctrl+A вибрати все",
"AutomationEditorSaveFailed": "Не вдалося зберегти: {error}",
"AutomationEditorBusy": "Сховище автоматизацій зайняте. Спробуйте зберегти ще раз.",
"AutomationEditorSaved": "Збережено {name}",
"AutomationEditorPreviewUnavailable": "Наступний запуск невідомий; поточний розклад буде збережено",
"AutomationEditorConflict": "Автоматизація змінилася. Скасуйте та відкрийте знову.",
"AutomationEditorInvalidWorkspace": "Виберіть наявний робочий каталог.",
"AutomationEditorModelControls": "Введення для фільтра · ↑↓ рух · Enter вибір · Esc назад",
"AutomationEditorPausedPreview": "Призупинено · запуск не заплановано",
"AutomationEditorInvalidSchedule": "Неприпустимий розклад: {error}",
"AutomationEditorMonday": "Пн",
"AutomationEditorTuesday": "Вт",
"AutomationEditorWednesday": "Ср",
"AutomationEditorThursday": "Чт",
"AutomationEditorFriday": "Пт",
"AutomationEditorSaturday": "Сб",
"AutomationEditorSunday": "Нд"
}
+98 -34
View File
@@ -1,11 +1,15 @@
{
"ExtensionsActionAdd": "thêm",
"ExtensionsActionEnable": "bật",
"ExtensionsActionEdit": "Sửa",
"ExtensionsHooksAddLabel": "Thêm hook",
"ExtensionsHooksAddDescription": "Mở tệp hook của dự án này trong trình soạn thảo",
"ExtensionsActionReload": "tải lại",
"ExtensionsActionConnect": "kết nối",
"ExtensionsActionReconnect": "kết nối lại",
"ExtensionsActionReauth": "xác thực lại",
"ExtensionsActionDiagnose": "chẩn đoán",
"ExtensionsActionManage": "quản lý",
"ExtensionsActionFocus": "tiêu điểm",
"ExtensionsActionFold": "thu gọn",
"ExtensionsActionTabs": "thẻ",
@@ -19,6 +23,7 @@
"ExtensionsGroupProblems": "Sự cố",
"ExtensionsGroupRecommended": "Đề xuất",
"ExtensionsGroupServers": "Máy chủ",
"ExtensionsGroupNeedsAttention": "Cần chú ý",
"ExtensionsGroupStatus": "{count} mục · Enter để thu gọn",
"ExtensionsGroupUser": "Người dùng",
"ExtensionsGroupWorkspace": "Không gian làm việc",
@@ -45,6 +50,8 @@
"ExtensionsProductCuaDescription": "Môi trường chạy điều khiển máy tính cục bộ với trình điều khiển macOS gốc.",
"ExtensionsProductDetail": "nhà phát hành: {publisher} · thành phần: {components} · nguồn: {source}",
"ExtensionsProductPlaywrightDescription": "Tự động hóa trình duyệt dựa trên Playwright MCP của Microsoft.",
"ExtensionsProductCodewhaleComputerUseDescription": "Nhìn màn hình và điều khiển: điều khiển ưu tiên trợ năng, ảnh chụp, thu phóng và ghi màn hình, cùng máy tính từ xa qua ssh.",
"ExtensionsStateFirstParty": "Có sẵn trong Codewhale",
"ExtensionsProductSandboxDescription": "Lớp cô lập tiến trình cho các lệnh cục bộ do plugin cung cấp.",
"ExtensionsSearchLabel": "Tìm kiếm: ",
"ExtensionsSkillRootCompatibleGlobal": "Toàn cục tương thích ({harness})",
@@ -123,9 +130,10 @@
"HistoryHintRestore": "Esc để khôi phục",
"HistoryNoMatches": " Không tìm thấy kết quả",
"TranscriptReasoningExpand": "mở rộng",
"TelemetryNoticeHeadline": "Giúp cải thiện Codewhale không?",
"TelemetryNoticeBody": "Codewhale đếm: phiên bản bạn chạy, hệ điều hành và họ CPU, thời lượng và kết\nquả phiên, cùng số liệu tổng hợp về tính năng và lỗi.\n\nHệ thống không bao giờ thu thập cuộc trò chuyện, mã nguồn, prompt, tệp, tên\ntệp, kho mã hoặc nhánh, nội dung mô hình hay thông tin đăng nhập của bạn — và\nkhông bao giờ gửi dòng thời gian hoạt động của tác nhân theo lượt hoặc công cụ.\n\nBạn chỉ được nhận diện bằng một ID ngẫu nhiên lưu trên máy này, được thay\nmới sau mỗi 90 ngày. Đổi ý bất cứ lúc nào:\n codewhale config set telemetry false\n\nLược đồ đầy đủ theo từng trường: docs/TELEMETRY.md",
"TelemetryNoticeCompactBody": "Số liệu: phiên bản, hệ thống/CPU, thời lượng/kết quả và tổng tính năng/lỗi.\nID cục bộ ngẫu nhiên đổi mỗi 90 ngày.\nKhông bao giờ: trò chuyện/mã/prompt/tệp/tên, nội dung mô hình/thông tin đăng nhập, lượt/công cụ.\nLược đồ: docs/TELEMETRY.md\nTắt: codewhale config set telemetry false",
"TelemetryNoticeDefaultOn": "Báo cáo sử dụng được bật theo mặc định. Chia sẻ tổng số phiên bản, nền tảng, phiên, tính năng và lỗi với Codewhale và bên xử lý PostHog. Không thu thập nội dung hay IP. docs/TELEMETRY.md Tắt: codewhale config set telemetry false",
"TelemetryNoticeHeadline": "Báo cáo sử dụng Codewhale",
"TelemetryNoticeBody": "Codewhale đếm: phiên bản bạn chạy, hệ điều hành và họ CPU, thời lượng và kết\nquả phiên, cùng số liệu tổng hợp về tính năng và lỗi.\n\nHệ thống không bao giờ thu thập cuộc trò chuyện, mã nguồn, prompt, tệp, tên\ntệp, kho mã hoặc nhánh, nội dung mô hình hay thông tin đăng nhập của bạn — và\nkhông bao giờ gửi dòng thời gian hoạt động của tác nhân theo lượt hoặc công cụ.\n\nBạn chỉ được nhận diện bằng một ID ngẫu nhiên lưu trên máy này, được thay\nmới sau mỗi 90 ngày. Đổi ý bất cứ lúc nào:\n codewhale config set telemetry false\n\nLược đồ đầy đủ theo từng trường: docs/TELEMETRY.md\n\nBáo cáo sử dụng được bật theo mặc định. Codewhale và PostHog xử lý các số liệu này khi việc gửi được cấu hình. Không thu thập IP.",
"TelemetryNoticeCompactBody": "Số liệu: phiên bản, hệ thống/CPU, thời lượng/kết quả và tổng tính năng/lỗi.\nID cục bộ ngẫu nhiên đổi mỗi 90 ngày.\nKhông bao giờ: trò chuyện/mã/prompt/tệp/tên, nội dung mô hình/thông tin đăng nhập, lượt/công cụ.\nLược đồ: docs/TELEMETRY.md\nTắt: codewhale config set telemetry false\nBáo cáo sử dụng được bật theo mặc định. Codewhale và PostHog xử lý các số liệu này khi việc gửi được cấu hình. Không thu thập IP.",
"TelemetryNoticeChoiceKeep": "Có, tiếp tục đếm ẩn danh",
"TelemetryNoticeChoiceDisable": "Không, tắt theo dõi",
"TelemetryNoticeActionChoose": "chọn",
@@ -256,9 +264,9 @@
"ConfigLabelAllowShell": "Quyền truy cập shell",
"ConfigLabelManagedAllowShell": "Quyền truy cập shell (được quản lý)",
"ConfigLabelTelemetry": "Thống kê sử dụng ẩn danh",
"ConfigHintTelemetry": "Tùy chọn đã lưu cho số liệu ẩn danh về tính năng và lỗi; không bao giờ gồm hội thoại hay mã nguồn",
"ConfigValueTelemetryOn": "Bật (đã lưu)",
"ConfigValueTelemetryOff": "Tắt (đã lưu)",
"ConfigHintTelemetry": "Báo cáo sử dụng được bật theo mặc định. Chia sẻ tổng số phiên bản, nền tảng, phiên, tính năng và lỗi với Codewhale và bên xử lý PostHog. Không thu thập nội dung hay IP. docs/TELEMETRY.md",
"ConfigValueTelemetryOn": "Bật",
"ConfigValueTelemetryOff": "Tắt",
"ConfigLabelStreamTimeout": "Thời gian chờ luồng",
"ConfigLabelTheme": "Chủ đề",
"ConfigLabelLocale": "Ngôn ngữ",
@@ -327,7 +335,6 @@
"ModelPickerAutoLocalHint": "mỗi lượt · heuristic cục bộ · không gửi yêu cầu định tuyến",
"ModelPickerAutoLastRoute": "lần trước {provider} · {model}",
"AutoRouteSelectedToast": "Auto: {provider} / {model} qua {source} · Ctrl+O: chi tiết tuyến",
"CloudCodeSystemPromptUnsupported": "Antigravity cloud-code không thể gửi lượt này an toàn vì kết nối này chưa hỗ trợ chỉ dẫn hệ thống. Chưa gửi gì; hãy chọn nhà cung cấp khác.",
"HelpTitle": "Trợ giúp",
"HelpFilterPlaceholder": "Nhập để lọc",
"HelpFilterPrefix": "Bộ lọc: ",
@@ -480,6 +487,12 @@
"CmdModeDescription": "Chuyển quyền hạn hoặc mở trình chọn chế độ",
"CmdModelDescription": "Chuyển đổi hoặc xem mô hình AI hiện tại",
"CmdModelsDescription": "Lấy ID mô hình trực tiếp từ API đang dùng",
"ModelsListHeader": "Mô hình của {provider} (mặc định: {model})",
"ModelsListHint": "Danh mục đã lưu; chạy `codewhale models --update` để cập nhật các nhà cung cấp đã cấu hình.",
"ModelsUpdateSummary": "Danh mục mô hình: {updated} đã cập nhật, {loaded} đã tải từ Codex, {failed} thất bại, {skipped} đã bỏ qua.",
"ModelsUpdatePartial": "Không thể cập nhật một số danh mục. Các mô hình trước đó và đã cấu hình vẫn có sẵn.",
"ModelsCodexHint": "Các mô hình ChatGPT được tải qua Codex. Thời điểm truy vấn không xác nhận danh mục của nhà cung cấp đã được cập nhật.",
"ModelsSourceFallback": "Mô hình đi kèm hoặc đã cấu hình; chưa xác minh tính khả dụng.",
"CmdModelDbDescription": "Duyệt cơ sở dữ liệu mô hình tích hợp",
"CmdNetworkDescription": "Quản lý các quy tắc cho phép và từ chối mạng",
"CmdUpdateDescription": "Kiểm tra và cài đặt bản phát hành Codewhale mới",
@@ -547,7 +560,7 @@
"CmdStructcopyClipboardAccepted": "Bản sao cấu trúc ({kind}, {bytes} byte) đã được chuyển cho clipboard; nếu không thể truy cập clipboard gốc, một lần ghi qua terminal đã được xếp hàng",
"CmdStructcopyClipboardFailed": "Không thể sao chép vào clipboard: {error}. Không có gì được ghi; chạy lại với `stdout` để xem văn bản rõ ràng",
"CmdStructcopyReceiptTooLarge": "Siêu dữ liệu biên nhận sao chép cấu trúc vượt quá giới hạn đầu ra {bytes} byte; từ chối xuất dữ liệu",
"CmdFleetDescription": "Xem và thiết lập thành viên Fleet cùng trạng thái điều phối",
"CmdFleetDescription": "Xem và thiết lập thành viên nhóm cùng trạng thái điều phối",
"CmdLaneDescription": "Xem và điều khiển các Lane bền vững (Workflow đang chạy)",
"CmdWorkflowDescription": "Chạy script đa tác nhân khi thứ tự hoặc fan-out quan trọng",
"CmdWorkflowsDescription": "Hiển thị các lần chạy quy trình làm việc trong không gian làm việc này (danh sách, hủy)",
@@ -600,6 +613,7 @@
"FooterBalancePrefix": "số dư",
"HelpSectionActions": "Hành động",
"HelpSectionClipboard": "Bộ nhớ tạm",
"HelpSectionPointer": "Con trỏ",
"HelpSectionEditing": "Chỉnh sửa đầu vào",
"HelpSectionHelp": "Trợ giúp",
"HelpSectionModes": "Chế độ",
@@ -660,14 +674,22 @@
"KbCopySelection": "Sao chép vùng chọn Codewhale hiện tại",
"ClipboardSshPasteHint": "Dán qua SSH do terminal cục bộ xử lý: nhấn Cmd+V trên macOS hoặc Ctrl+Shift+V trên Linux/Windows.",
"KbContextMenu": "Mở các hành động ngữ cảnh cho dán, vùng chọn, chi tiết tin nhắn, ngữ cảnh và trợ giúp",
"KbPointerScroll": "Cuộn bản ghi; trên khung soạn thảo, di chuyển con trỏ từng dòng",
"KbPointerClick": "Kích hoạt hàng, chip hoặc khối công cụ dưới con trỏ",
"KbPointerDrag": "Chọn văn bản bản ghi, hoặc kéo thanh cuộn",
"KbAttachPath": "Thêm một tệp văn bản cục bộ hoặc thư mục vào ngữ cảnh",
"KbHelpOverlay": "Mở lớp phủ trợ giúp này (khi khung nhập trống)",
"KbCycleWorkDock": "Chuyển thanh làm việc (todo, tác nhân, job, nền)",
"KbCycleWorkDockBack": "Chuyển thanh làm việc ngược lại",
"KbToggleHelp": "Bật/Tắt lớp phủ trợ giúp",
"KbToggleHelpSlash": "Bật/Tắt lớp phủ trợ giúp",
"HelpUsageLabel": "Sử dụng:",
"HelpAliasesLabel": "Bí danh:",
"SettingsTitle": "Cài đặt:",
"SettingsConfigFile": "Tệp cấu hình:",
"SettingsTuiPrefsFolded": "Đã chuyển {key} '{value}' từ tui.toml — settings.toml giờ quản lý nó.",
"SettingsTuiPrefsKept": "tui.toml đặt {key} là '{prefs}', nhưng settings.toml đã đặt '{settings}' — settings.toml thắng.",
"SettingsTuiPrefsQuarantined": "Các khóa tui.toml không có thiết lập tương ứng được giữ trong {path}: {keys}",
"ClearConversation": "Đã xóa cuộc trò chuyện",
"ClearConversationBusy": "Chưa xóa nội dung nào (trạng thái Công việc hoặc tác vụ thời gian chạy đang bận; hãy chờ rồi thử lại /clear)",
"ModelChanged": "Đã thay đổi mô hình: {old} → {new}",
@@ -1149,8 +1171,8 @@
"NotificationSubagentCancelled": "Sub-agent đã hủy",
"NotificationSubagentBudgetExhausted": "Sub-agent đã hết ngân sách",
"FooterWorkedChip": "đã chạy {duration}",
"FleetDraftTitle": "Hồ sơ Fleet — bản nháp của {model_label} (nhấn g để lưu)",
"FleetDraftHeader": "# .codewhale/agents/{name}\n# Được soạn bởi {model_label}, xác thực và giới hạn bởi Codewhale.\n# Quyền giữ ở mức sàn Fleet: không shell, không tin cậy, cần phê duyệt.\n# Không có gì được lưu cho đến khi bạn nhấn g trong trình hướng dẫn.\n\n",
"FleetDraftTitle": "Hồ sơ nhóm — bản nháp của {model_label} (nhấn g để lưu)",
"FleetDraftHeader": "# .codewhale/agents/{name}\n# Được soạn bởi {model_label}, xác thực và giới hạn bởi Codewhale.\n# Quyền giữ ở mức sàn nhóm: không shell, không tin cậy, cần phê duyệt.\n# Không có gì được lưu cho đến khi bạn nhấn g trong trình hướng dẫn.\n\n",
"SetupRemoteOnRampText": "Remote Runtime On-Ramp\n\n/setup chỉ hiển thị dữ kiện runtime từ xa. Nó không tạo gói triển khai, ghi thông tin xác thực, gọi CLI đám mây hay chạy `remote-setup`.\n\nDữ kiện hiện tại:\n- Đám mây: {clouds_result}\n- Cầu nối: {bridges_result}\n- Nhà cung cấp: {providers_result}\n- Chế độ: {mode_result}\n\nĐể tạo gói triển khai, hãy chạy rõ ràng trong terminal thông thường:\n\n```sh\n{command}\n```\n\nRUNBOOK được tạo bao gồm các bước máy chủ cần xem xét thủ công. `--apply` vẫn chưa được triển khai; đừng coi đây là tự động triển khai.",
"ApprovalDescSafe": "Yêu cầu thao tác an toàn/chỉ đọc.",
"ApprovalDescFileWrite": "Yêu cầu sửa đổi tệp. Vui lòng xác nhận đường dẫn và nội dung.",
@@ -1234,6 +1256,11 @@
"CommandPaletteSubtitle": "Tìm và chạy một thao tác",
"ConfigSubtitle": "Cài đặt trước; khóa thô ở chi tiết nâng cao",
"LaunchStartTitle": "Bắt đầu tại đây",
"LaunchResumeConfirm": "Tiếp tục {title}? Nhấp lại hoặc nhấn Enter để xác nhận.",
"LaunchResumeConfirmTitle": "Tiếp tục phiên",
"LaunchResumeConfirmBody": "Thao tác này thay ngữ cảnh hiện tại bằng lịch sử của phiên đó.",
"LaunchResumeConfirmResume": "tiếp tục",
"LaunchResumeConfirmCancel": "huỷ",
"LaunchWorkDescription": "Dùng công cụ cục bộ trong thư mục này; mọi thay đổi tuân theo chính sách phê duyệt của bạn.",
"LaunchChatDescription": "Chỉ trò chuyện và lập kế hoạch; không thay đổi tệp.",
"LaunchWorkspaceGitReady": "Không gian làm việc · {name} · không gian Git",
@@ -1259,7 +1286,6 @@
"LaunchWorktreeFailed": "Worktree thất bại: {error}",
"LaunchWorktreeCreated": "Worktree mới {path} trên nhánh {branch}",
"LaunchNoSavedSessions": "Không có phiên đã lưu cho workspace này.",
"LaunchComposerHint": "Enter gửi · Shift+Enter dòng mới · Esc quay lại",
"LaunchNoModelConnected": "chưa kết nối mô hình",
"LaunchRunCommand": "chạy {command}",
"LaunchMcpConnectedOne": "1 máy chủ MCP đã kết nối",
@@ -1458,7 +1484,7 @@
"ProviderExternalRevokedToast": "Đã thu hồi quyền truy cập thông tin xác thực ngoài cho {provider}",
"ProviderExternalRevokeFailedToast": "Không thể thu hồi quyền truy cập thông tin xác thực ngoài: {error}",
"ThemeSurfaceTitle": "giao diện · xem trước trực tiếp",
"FleetRosterHeaderLabel": "fleet",
"FleetRosterHeaderLabel": "nhóm",
"FleetRosterTabRoster": "thành viên",
"FleetRosterTabSetup": "thiết lập",
"FleetRosterWorkers": "worker",
@@ -1476,33 +1502,34 @@
"FleetProfileIdentityVerifyFailed": "Không thể xác minh danh tính hồ sơ hiện có ({error}); hãy sửa tệp được nêu trước khi lưu.",
"FleetProfileIdConflict": "Id hồ sơ `{id}` đã được {path} sử dụng; hãy soạn lại với vai trò khác hoặc xóa tệp cũ trước.",
"FleetProfileProviderUnconfigured": "Hồ sơ chỉ định nhà cung cấp `{provider}` chưa có thông tin xác thực ({env}); hãy thiết lập trong /provider trước khi lưu.",
"FleetModelAdded": "Đã thêm {route} vào fleet `{fleet}`",
"FleetModelAddedAs": "Đã thêm {route} với vai trò {roles} vào fleet `{fleet}`",
"FleetModelAddedCreatedNote": " (fleet cá nhân mới, đã được chọn)",
"FleetModelAdded": "Đã thêm {route} vào nhóm `{fleet}`",
"FleetModelAddedAs": "Đã thêm {route} với vai trò {roles} vào nhóm `{fleet}`",
"FleetModelAddedCreatedNote": " (nhóm cá nhân mới, đã được chọn)",
"FleetModelAddedSelectedNote": " (đã được chọn)",
"FleetModelRemoved": "Đã gỡ {route} khỏi fleet `{fleet}`",
"FleetModelRemovedRoles": "Đã gỡ {route} ({roles}) khỏi fleet `{fleet}`",
"FleetModelUnchanged": "{route} vẫn ở trong fleet `{fleet}`: {reason}",
"FleetModelReasonOperatorRoute": "đây là mô hình hiện tại của bạn (tuyến operator của fleet); hãy đổi mô hình hoặc dùng /fleet save để thay đổi",
"FleetModelReasonAlreadyPresent": "đã có trong fleet",
"FleetModelErrorNeedsRoute": "một mô hình trong fleet cần cả id provider lẫn id mô hình",
"FleetModelErrorNoSelection": "chưa chọn fleet nào; fleet của bạn chỉ gồm mô hình của phiên",
"FleetModelErrorOperatorRoute": "{route} là tuyến operator của fleet `{fleet}`; hãy thay đổi bằng /fleet save, không phải remove",
"FleetModelErrorNotInFleet": "{route} không có trong fleet `{fleet}`",
"FleetModelsEmpty": "Fleet của bạn chỉ gồm mô hình của phiên. Thêm một mô hình: /fleet add <provider> <model> [role…] (hoặc ⇧F trên một hàng trong /model).",
"FleetModelsHeader": "Fleet `{fleet}` của bạn ({count} mô hình)",
"FleetModelsBroken": "Không thể tải fleet đã chọn: {error}. Hãy sửa tệp hoặc chọn fleet khác bằng /fleet fleets.",
"FleetModelRemoved": "Đã gỡ {route} khỏi nhóm `{fleet}`",
"FleetModelRemovedRoles": "Đã gỡ {route} ({roles}) khỏi nhóm `{fleet}`",
"FleetModelUnchanged": "{route} vẫn ở trong nhóm `{fleet}`: {reason}",
"FleetModelReasonOperatorRoute": "đây là mô hình hiện tại của bạn (tuyến operator của nhóm); hãy đổi mô hình hoặc dùng /fleet save để thay đổi",
"FleetModelReasonAlreadyPresent": "đã có trong nhóm",
"FleetModelErrorNeedsRoute": "một mô hình trong nhóm cần cả id provider lẫn id mô hình",
"FleetModelErrorNeedsRole": "một thành viên trong nhóm là một vai trò; hãy nêu một vai trò (ví dụ `/fleet add {route} scout`)",
"FleetModelErrorNoSelection": "chưa chọn nhóm nào; nhóm của bạn chỉ gồm mô hình của phiên",
"FleetModelErrorOperatorRoute": "{route} là tuyến operator của nhóm `{fleet}`; hãy thay đổi bằng /fleet save, không phải remove",
"FleetModelErrorNotInFleet": "{route} không có trong nhóm `{fleet}`",
"FleetModelsEmpty": "Nhóm của bạn chỉ gồm mô hình của phiên. Thêm một mô hình: /fleet add <provider> <model> [role…] (hoặc ⇧F trên một hàng trong /model).",
"FleetModelsHeader": "Nhóm `{fleet}` của bạn ({count} mô hình)",
"FleetModelsBroken": "Không thể tải nhóm đã chọn: {error}. Hãy sửa tệp hoặc chọn nhóm khác bằng /fleet teams.",
"FleetModelsFooter": "Thêm: /fleet add <provider> <model> [role…] · Gỡ: /fleet remove <provider> <model>",
"FleetModelsFactPrice": "${input}/{output} mỗi M",
"FleetModelsFactContext": "{context}k ctx",
"FleetModelsFactTools": "công cụ",
"FleetAddUsage": "Cách dùng: /fleet add <provider> <model> [role…] — ví dụ /fleet add openrouter z-ai/glm-5.3-flash explore",
"FleetAddProviderUnconfigured": "`{provider}` không phải provider đã cấu hình. Hãy cấu hình trong ~/.codewhale/config.toml hoặc chuyển sang nó bằng /provider trước khi thêm vào fleet.",
"FleetAddProviderUnconfigured": "`{provider}` không phải provider đã cấu hình. Hãy cấu hình trong ~/.codewhale/config.toml hoặc chuyển sang nó bằng /provider trước khi thêm vào nhóm.",
"FleetAddModelNotServed": "{provider} không cung cấp `{model}` trong danh mục hiện tại; chạy /models để xem nó cung cấp gì, hoặc /fleet add với đúng id được liệt kê.",
"FleetAddFailed": "Không thể thêm vào fleet: {error}",
"FleetAddFailed": "Không thể thêm vào nhóm: {error}",
"FleetRemoveUsage": "Cách dùng: /fleet remove <provider> <model>",
"FleetRemoveFailed": "Không thể gỡ khỏi fleet: {error}",
"FleetToggleFailed": "Không thể cập nhật fleet: {error}",
"FleetRemoveFailed": "Không thể gỡ khỏi nhóm: {error}",
"FleetToggleFailed": "Không thể cập nhật nhóm: {error}",
"FleetDestStepTitle": "Hồ sơ này nên được lưu ở đâu?",
"FleetDestStepSubtitle": "Không có gì được ghi cho đến khi bạn xác nhận ở bước cuối.",
"FleetDestProjectLabel": "Dự án này",
@@ -1640,12 +1667,13 @@
"KbTurnInspector": "Mở Trình kiểm tra lượt (Turn Inspector)",
"CmdTurnInspectDescription": "Open the whole-turn inspector",
"CmdAutomationDescription": "Quản lý tự động hóa theo lịch bền vững",
"AutomationUsage": "Cách dùng: /automation [list|show <id>|pause <id>|resume <id>|delete <id> [--confirm <token>]|run <id>]",
"AutomationUsage": "Cách dùng: /automation [list|show <id>|print <id>|pause <id>|resume <id>|delete <id> [--confirm <token>]|run <id>]",
"AutomationManagerUnavailable": "Trình quản lý tự động hóa không khả dụng trong phiên này.",
"AutomationListFailed": "Không thể liệt kê các tự động hóa: {error}",
"AutomationActionFailed": "Không thể {action} tự động hóa {id}: {error}",
"AutomationEmpty": "Không có tự động hóa nào đã lên lịch. Dùng công cụ `automation` để tạo một tự động hóa.",
"AutomationEmpty": "Chưa có tự động hóa được lên lịch. Chọn Tạo tự động hóa.",
"AutomationListHeading": "Tự động hóa đã lên lịch",
"AutomationScopeNote": "Các tự động hóa là của bạn, không phải của kho này — chúng theo bạn vào mọi kho.",
"AutomationNoun": "Tự động hóa",
"AutomationStatusLabel": "trạng thái",
"AutomationStatusActive": "đang hoạt động",
@@ -1660,6 +1688,7 @@
"AutomationActionResume": "tiếp tục",
"AutomationActionDelete": "xóa",
"AutomationActionRun": "chạy",
"AutomationActionCancel": "hủy lượt chạy",
"AutomationActionPaused": "đã tạm dừng",
"AutomationActionResumed": "đã tiếp tục",
"AutomationNextLabel": "lần tới",
@@ -2015,8 +2044,43 @@
"LaunchNoticeClaude": "Đã phát hiện Claude Code. Chạy /import-claude để xem những gì có thể chuyển sang: máy chủ MCP, cài đặt an toàn và quyền. Không có gì được áp dụng nếu bạn chưa duyệt.",
"LaunchNewSession": "Phiên mới",
"LaunchRecentHeading": "Gần đây",
"LaunchHelpLine": "/help để xem lệnh · {dock} để mở thanh làm việc",
"LaunchSeeAllSessions": "Xem tất cả các phiên…",
"LaunchNoRecentSessions": "Chưa có phiên gần đây — nhập bên dưới để bắt đầu.",
"LaunchResumeFailed": "Tiếp tục thất bại: {error}",
"ReceiptSessionHooks": "hook {count}"
"ReceiptSessionHooks": "hook {count}",
"AutomationEditorNew": "Tạo tự động hóa",
"AutomationEditorEdit": "Sửa tự động hóa",
"AutomationEditorSchedule": "Lịch chạy",
"AutomationEditorDaily": "Hằng ngày",
"AutomationEditorWeekly": "Hằng tuần",
"AutomationEditorHourly": "Hằng giờ",
"AutomationEditorOnce": "Một lần",
"AutomationEditorCustom": "Tùy chỉnh",
"AutomationEditorTime": "Giờ (HH:MM)",
"AutomationEditorDate": "Ngày (YYYY-MM-DD)",
"AutomationEditorDays": "Các ngày",
"AutomationEditorLocalTime": "Múi giờ: cục bộ ({zone})",
"AutomationEditorProvider": "Chọn mô hình sẽ cố định nhà cung cấp. Cấu hình hiện tại: {provider}.",
"AutomationEditorDefaultModel": "Mô hình mặc định",
"AutomationEditorInheritedProvider": "mặc định của môi trường chạy",
"AutomationEditorEnabled": "Bật",
"AutomationEditorControls": "Tab trường · Enter sửa · ↑↓ điều chỉnh · Ctrl+A chọn tất cả",
"AutomationEditorPromptControls": "Enter xuống dòng · Tab trường tiếp theo · Ctrl+A chọn tất cả",
"AutomationEditorSaveFailed": "Không thể lưu: {error}",
"AutomationEditorBusy": "Kho tự động hóa đang bận. Hãy lưu lại.",
"AutomationEditorSaved": "Đã lưu {name}",
"AutomationEditorPreviewUnavailable": "Chưa biết lần chạy tiếp theo; sẽ giữ nguyên lịch hiện tại",
"AutomationEditorConflict": "Tự động hóa đã thay đổi. Hủy rồi mở lại.",
"AutomationEditorInvalidWorkspace": "Chọn thư mục làm việc đã tồn tại.",
"AutomationEditorModelControls": "Nhập để lọc · ↑↓ di chuyển · Enter chọn · Esc quay lại",
"AutomationEditorPausedPreview": "Đã tạm dừng · không có lần chạy đã lên lịch",
"AutomationEditorInvalidSchedule": "Lịch không hợp lệ: {error}",
"AutomationEditorMonday": "T2",
"AutomationEditorTuesday": "T3",
"AutomationEditorWednesday": "T4",
"AutomationEditorThursday": "T5",
"AutomationEditorFriday": "T6",
"AutomationEditorSaturday": "T7",
"AutomationEditorSunday": "CN"
}
+98 -34
View File
@@ -1,11 +1,15 @@
{
"ExtensionsActionAdd": "添加",
"ExtensionsActionEnable": "启用",
"ExtensionsActionEdit": "编辑",
"ExtensionsHooksAddLabel": "添加钩子",
"ExtensionsHooksAddDescription": "在你的编辑器中打开本项目的钩子文件",
"ExtensionsActionReload": "重新加载",
"ExtensionsActionConnect": "连接",
"ExtensionsActionReconnect": "重新连接",
"ExtensionsActionReauth": "重新登录",
"ExtensionsActionDiagnose": "诊断",
"ExtensionsActionManage": "管理",
"ExtensionsActionFocus": "焦点",
"ExtensionsActionFold": "折叠",
"ExtensionsActionTabs": "标签页",
@@ -19,6 +23,7 @@
"ExtensionsGroupProblems": "问题",
"ExtensionsGroupRecommended": "推荐",
"ExtensionsGroupServers": "服务器",
"ExtensionsGroupNeedsAttention": "需要处理",
"ExtensionsGroupStatus": "{count} 项 · 按 Enter 折叠",
"ExtensionsGroupUser": "用户",
"ExtensionsGroupWorkspace": "工作区",
@@ -45,6 +50,8 @@
"ExtensionsProductCuaDescription": "带有原生 macOS 驱动的本地计算机操作运行时。",
"ExtensionsProductDetail": "发布者:{publisher} · 组件:{components} · 来源:{source}",
"ExtensionsProductPlaywrightDescription": "基于 Microsoft Playwright MCP 的浏览器自动化。",
"ExtensionsProductCodewhaleComputerUseDescription": "看见屏幕并操作它:以无障碍接口为先的控制、截图、缩放与录屏,还可通过 ssh 操作远程计算机。",
"ExtensionsStateFirstParty": "随 Codewhale 内置",
"ExtensionsProductSandboxDescription": "用于隔离插件所提供本地命令的进程层。",
"ExtensionsSearchLabel": "搜索:",
"ExtensionsSkillRootCompatibleGlobal": "兼容的全局目录({harness})",
@@ -123,9 +130,10 @@
"HistoryHintRestore": "Esc 还原",
"HistoryNoMatches": " 无匹配",
"TranscriptReasoningExpand": "展开",
"TelemetryNoticeHeadline": "帮助改进 Codewhale 吗?",
"TelemetryNoticeBody": "Codewhale 只统计:您运行的版本、操作系统和 CPU 系列、会话时长和结果,\n以及汇总的功能和错误计数。\n\n它绝不会收集您的对话、代码、提示词、文件、仓库名或分支名、模型内容\n或凭据,也绝不会发送按轮次或按工具记录的代理活动。\n\n系统只使用存储在本机上的随机 ID 来识别您,该 ID 每 90 天更换一次。\n您可以随时更改:\n codewhale config set telemetry false\n\n逐字段完整架构:docs/TELEMETRY.md",
"TelemetryNoticeCompactBody": "统计:版本、系统/处理器、会话时长/结果、功能/错误总数。\n随机本地标识每 90 天更换。\n绝不收集:对话/代码/提示词/文件/名称、模型内容/凭据、按轮次/工具记录的活动。\n架构:docs/TELEMETRY.md\n关闭:codewhale config set telemetry false",
"TelemetryNoticeDefaultOn": "使用统计默认开启。 向 Codewhale 和数据处理方 PostHog 分享版本/平台、会话、功能和错误的汇总计数。不收集内容或 IP。docs/TELEMETRY.md 关闭: codewhale config set telemetry false",
"TelemetryNoticeHeadline": "Codewhale 使用统计",
"TelemetryNoticeBody": "Codewhale 只统计:您运行的版本、操作系统和 CPU 系列、会话时长和结果,\n以及汇总的功能和错误计数。\n\n它绝不会收集您的对话、代码、提示词、文件、仓库名或分支名、模型内容\n或凭据,也绝不会发送按轮次或按工具记录的代理活动。\n\n系统只使用存储在本机上的随机 ID 来识别您,该 ID 每 90 天更换一次。\n您可以随时更改:\n codewhale config set telemetry false\n\n逐字段完整架构:docs/TELEMETRY.md\n\n使用统计默认开启。 配置发送后,Codewhale 和 PostHog 会处理这些计数。不收集 IP。",
"TelemetryNoticeCompactBody": "统计:版本、系统/处理器、会话时长/结果、功能/错误总数。\n随机本地标识每 90 天更换。\n绝不收集:对话/代码/提示词/文件/名称、模型内容/凭据、按轮次/工具记录的活动。\n架构:docs/TELEMETRY.md\n关闭:codewhale config set telemetry false\n使用统计默认开启。 配置发送后,Codewhale 和 PostHog 会处理这些计数。不收集 IP。",
"TelemetryNoticeChoiceKeep": "是,保留匿名统计",
"TelemetryNoticeChoiceDisable": "不,关闭匿名统计",
"TelemetryNoticeActionChoose": "选择",
@@ -256,9 +264,9 @@
"ConfigLabelAllowShell": "Shell 访问",
"ConfigLabelManagedAllowShell": "Shell 访问(受管理)",
"ConfigLabelTelemetry": "匿名使用统计",
"ConfigHintTelemetry": "匿名功能和错误计数的已保存偏好;绝不包含对话或代码",
"ConfigValueTelemetryOn": "开启(已保存)",
"ConfigValueTelemetryOff": "关闭(已保存)",
"ConfigHintTelemetry": "使用统计默认开启。 向 Codewhale 和数据处理方 PostHog 分享版本/平台、会话、功能和错误的汇总计数。不收集内容或 IP。docs/TELEMETRY.md",
"ConfigValueTelemetryOn": "开启",
"ConfigValueTelemetryOff": "关闭",
"ConfigLabelStreamTimeout": "流式超时",
"ConfigLabelTheme": "主题",
"ConfigLabelLocale": "语言",
@@ -327,7 +335,6 @@
"ModelPickerAutoLocalHint": "每轮 · 本地启发式 · 不发送路由请求",
"ModelPickerAutoLastRoute": "上次 {provider} · {model}",
"AutoRouteSelectedToast": "Auto: {provider} / {model}({source})· Ctrl+O:路由详情",
"CloudCodeSystemPromptUnsupported": "此连接尚不支持系统指令,因此 Antigravity cloud-code 无法安全发送本轮。未发送任何内容;请选择其他提供商。",
"HelpTitle": "帮助",
"HelpFilterPlaceholder": "输入以筛选",
"HelpFilterPrefix": "筛选: ",
@@ -480,6 +487,12 @@
"CmdModeDescription": "切换权限或打开模式选择器",
"CmdModelDescription": "切换或查看当前模型",
"CmdModelsDescription": "工具箱:从当前 API 获取实时模型 ID",
"ModelsListHeader": "{provider} 的模型(默认:{model})",
"ModelsListHint": "已缓存的目录;运行 `codewhale models --update` 可更新已配置的提供商。",
"ModelsUpdateSummary": "模型目录:{updated} 个已更新,{loaded} 个从 Codex 加载,{failed} 个失败,{skipped} 个已跳过。",
"ModelsUpdatePartial": "部分目录无法更新。此前的模型和已配置的模型仍然可用。",
"ModelsCodexHint": "ChatGPT 模型通过 Codex 加载。查询时间并不代表提供商已刷新模型目录。",
"ModelsSourceFallback": "内置或已配置的模型;尚未验证可用性。",
"CmdModelDbDescription": "参考:浏览内置的模型数据库",
"CmdNetworkDescription": "管理网络允许和拒绝规则",
"CmdUpdateDescription": "检查并安装新的 Codewhale 版本",
@@ -547,7 +560,7 @@
"CmdStructcopyClipboardAccepted": "结构化副本({kind},{bytes} 字节)已交给剪贴板;若无法访问原生剪贴板,则已改为加入终端写入队列",
"CmdStructcopyClipboardFailed": "复制到剪贴板失败:{error}。未写入任何内容;如需显式文本视图,请使用 `stdout` 重新运行",
"CmdStructcopyReceiptTooLarge": "结构化复制回执元数据超过 {bytes} 字节的输出上限;拒绝输出",
"CmdFleetDescription": "查看与配置 Fleet 成员及编排状态",
"CmdFleetDescription": "查看与配置 团队 成员及编排状态",
"CmdLaneDescription": "查看与控制持久化的 Lane(运行中的工作流)",
"CmdWorkflowDescription": "当需要阶段、顺序或多路并行时运行多智能体脚本",
"CmdWorkflowsDescription": "显示此工作区的工作流运行(列表、取消)",
@@ -600,6 +613,7 @@
"FooterBalancePrefix": "余额",
"HelpSectionActions": "操作",
"HelpSectionClipboard": "剪贴板",
"HelpSectionPointer": "指针",
"HelpSectionEditing": "输入编辑",
"HelpSectionHelp": "帮助",
"HelpSectionModes": "模式",
@@ -660,14 +674,22 @@
"KbCopySelection": "复制 Codewhale 当前选中内容",
"ClipboardSshPasteHint": "SSH 粘贴由本地终端处理:macOS 按 Cmd+V,Linux/Windows 按 Ctrl+Shift+V。",
"KbContextMenu": "打开上下文操作菜单,用于粘贴、选择、消息详情、上下文和帮助",
"KbPointerScroll": "滚动记录;在输入框上方逐行移动光标",
"KbPointerClick": "激活指针下的行、标签或工具块",
"KbPointerDrag": "选择记录文本,或拖动滚动条",
"KbAttachPath": "添加本地文本文件或目录到上下文",
"KbHelpOverlay": "打开此帮助覆盖层(输入框为空时)",
"KbCycleWorkDock": "切换工作栏(待办、代理、作业、后台)",
"KbCycleWorkDockBack": "反向切换工作栏",
"KbToggleHelp": "切换帮助覆盖层",
"KbToggleHelpSlash": "切换帮助覆盖层",
"HelpUsageLabel": "用法:",
"HelpAliasesLabel": "别名:",
"SettingsTitle": "设置:",
"SettingsConfigFile": "配置文件:",
"SettingsTuiPrefsFolded": "已从 tui.toml 迁移 {key} '{value}' — 现在由 settings.toml 管理。",
"SettingsTuiPrefsKept": "tui.toml 将 {key} 设为 '{prefs}',但 settings.toml 已设为 '{settings}' — 以 settings.toml 为准。",
"SettingsTuiPrefsQuarantined": "没有对应设置的 tui.toml 键已保留在 {path}: {keys}",
"ClearConversation": "对话已清空",
"ClearConversationBusy": "未清空任何内容(工作状态或运行时任务正忙;请等待后重试 /clear)",
"ModelChanged": "模型已切换:{old} → {new}",
@@ -1149,8 +1171,8 @@
"NotificationSubagentCancelled": "子代理已取消",
"NotificationSubagentBudgetExhausted": "子代理预算已用尽",
"FooterWorkedChip": "已运行{duration}",
"FleetDraftTitle": "Fleet 配置 — 由 {model_label} 起草(按 g 保存)",
"FleetDraftHeader": "# .codewhale/agents/{name}\n# 由 {model_label} 起草,并由 Codewhale 校验与限界。\n# 权限保持在 Fleet 底线:无 shell、无 trust、需审批。\n# 在向导中按 g 之前不会保存任何内容。\n\n",
"FleetDraftTitle": "团队 配置 — 由 {model_label} 起草(按 g 保存)",
"FleetDraftHeader": "# .codewhale/agents/{name}\n# 由 {model_label} 起草,并由 Codewhale 校验与限界。\n# 权限保持在 团队 底线:无 shell、无 trust、需审批。\n# 在向导中按 g 之前不会保存任何内容。\n\n",
"SetupRemoteOnRampText": "Remote Runtime On-Ramp\n\n/setup 只展示远程运行时事实,不会生成部署包、写入凭据、调用云 CLI 或运行 `remote-setup`。\n\n当前事实:\n- 云目标:{clouds_result}\n- 聊天桥:{bridges_result}\n- 提供商:{providers_result}\n- 模式:{mode_result}\n\n生成部署包时,请在普通终端显式运行:\n\n```sh\n{command}\n```\n\n生成的 RUNBOOK 会包含需要人工复核的主机步骤。`--apply` 仍未实现;不要把它当成自动部署。",
"ApprovalDescSafe": "请求执行只读操作。",
"ApprovalDescFileWrite": "请求修改文件。请确认路径和内容符合预期。",
@@ -1234,6 +1256,11 @@
"CommandPaletteSubtitle": "查找并执行一个操作",
"ConfigSubtitle": "设置优先;原始键在高级详情",
"LaunchStartTitle": "从这里开始",
"LaunchResumeConfirm": "恢复 {title}?再次点击或按 Enter 确认。",
"LaunchResumeConfirmTitle": "恢复会话",
"LaunchResumeConfirmBody": "这会用该会话的历史替换当前上下文。",
"LaunchResumeConfirmResume": "恢复",
"LaunchResumeConfirmCancel": "取消",
"LaunchWorkDescription": "在此文件夹中使用本地工具;更改遵循你的审批策略。",
"LaunchChatDescription": "仅对话和规划;不会更改文件。",
"LaunchWorkspaceGitReady": "工作区 · {name} · Git 工作区",
@@ -1259,7 +1286,6 @@
"LaunchWorktreeFailed": "工作树创建失败:{error}",
"LaunchWorktreeCreated": "新工作树 {path},分支 {branch}",
"LaunchNoSavedSessions": "此工作区没有已保存的会话。",
"LaunchComposerHint": "Enter 发送 · Shift+Enter 换行 · Esc 返回",
"LaunchNoModelConnected": "未连接模型",
"LaunchRunCommand": "运行 {command}",
"LaunchMcpConnectedOne": "已连接 1 个 MCP 服务器",
@@ -1458,7 +1484,7 @@
"ProviderExternalRevokedToast": "已撤销{provider}的外部凭据访问权限",
"ProviderExternalRevokeFailedToast": "未能撤销外部凭据访问权限:{error}",
"ThemeSurfaceTitle": "主题 · 实时预览",
"FleetRosterHeaderLabel": "fleet",
"FleetRosterHeaderLabel": "团队",
"FleetRosterTabRoster": "成员",
"FleetRosterTabSetup": "设置",
"FleetRosterWorkers": "工作器",
@@ -1476,33 +1502,34 @@
"FleetProfileIdentityVerifyFailed": "无法校验现有配置标识({error});请先修复列出的文件再保存。",
"FleetProfileIdConflict": "配置 id `{id}` 已被 {path} 占用;请重新起草为不同的角色或先移除旧文件。",
"FleetProfileProviderUnconfigured": "配置指定的 provider `{provider}` 尚未配置凭据({env});请先在 /provider 中设置,再保存。",
"FleetModelAdded": "已将 {route} 添加到 fleet `{fleet}`",
"FleetModelAddedAs": "已将 {route} 作为 {roles} 添加到 fleet `{fleet}`",
"FleetModelAddedCreatedNote": "(新的个人 fleet,已选中)",
"FleetModelAdded": "已将 {route} 添加到 团队 `{fleet}`",
"FleetModelAddedAs": "已将 {route} 作为 {roles} 添加到 团队 `{fleet}`",
"FleetModelAddedCreatedNote": "(新的个人 团队,已选中)",
"FleetModelAddedSelectedNote": "(已选中)",
"FleetModelRemoved": "已将 {route} 从 fleet `{fleet}` 中移除",
"FleetModelRemovedRoles": "已将 {route}({roles})从 fleet `{fleet}` 中移除",
"FleetModelUnchanged": "{route} 保留在 fleet `{fleet}` 中:{reason}",
"FleetModelReasonOperatorRoute": "这是你当前的模型(fleet 的操作员路由);请切换模型或用 /fleet save 更改",
"FleetModelReasonAlreadyPresent": "已在 fleet 中",
"FleetModelErrorNeedsRoute": "fleet 模型需要同时提供 provider ID 和模型 ID",
"FleetModelErrorNoSelection": "未选择 fleet;你的 fleet 仅包含会话模型",
"FleetModelErrorOperatorRoute": "{route} 是 fleet `{fleet}` 的操作员路由;请用 /fleet save 更改,而不是移除",
"FleetModelErrorNotInFleet": "{route} 不在 fleet `{fleet}` 中",
"FleetModelsEmpty": "你的 fleet 仅包含会话模型。添加一个:/fleet add <provider> <model> [role…](或在 /model 的某一行按 ⇧F)。",
"FleetModelsHeader": "你的 fleet `{fleet}`({count} 个模型)",
"FleetModelsBroken": "无法加载所选 fleet:{error}。请修复文件,或用 /fleet fleets 选择另一个。",
"FleetModelRemoved": "已将 {route} 从 团队 `{fleet}` 中移除",
"FleetModelRemovedRoles": "已将 {route}({roles})从 团队 `{fleet}` 中移除",
"FleetModelUnchanged": "{route} 保留在 团队 `{fleet}` 中:{reason}",
"FleetModelReasonOperatorRoute": "这是你当前的模型(团队 的操作员路由);请切换模型或用 /fleet save 更改",
"FleetModelReasonAlreadyPresent": "已在 团队 中",
"FleetModelErrorNeedsRoute": "团队 模型需要同时提供 provider ID 和模型 ID",
"FleetModelErrorNeedsRole": "团队成员是一个角色;请指定一个(例如 `/fleet add {route} scout`)",
"FleetModelErrorNoSelection": "未选择 团队;你的 团队 仅包含会话模型",
"FleetModelErrorOperatorRoute": "{route} 是 团队 `{fleet}` 的操作员路由;请用 /fleet save 更改,而不是移除",
"FleetModelErrorNotInFleet": "{route} 不在 团队 `{fleet}` 中",
"FleetModelsEmpty": "你的 团队 仅包含会话模型。添加一个:/fleet add <provider> <model> [role…](或在 /model 的某一行按 ⇧F)。",
"FleetModelsHeader": "你的 团队 `{fleet}`({count} 个模型)",
"FleetModelsBroken": "无法加载所选 团队:{error}。请修复文件,或用 /fleet 团队s 选择另一个。",
"FleetModelsFooter": "添加:/fleet add <provider> <model> [role…] · 移除:/fleet remove <provider> <model>",
"FleetModelsFactPrice": "${input}/{output} 每百万 token",
"FleetModelsFactContext": "{context}k ctx",
"FleetModelsFactTools": "工具",
"FleetAddUsage": "用法:/fleet add <provider> <model> [role…] — 例如 /fleet add openrouter z-ai/glm-5.3-flash explore",
"FleetAddProviderUnconfigured": "`{provider}` 不是已配置的 provider。请先在 ~/.codewhale/config.toml 中配置,或用 /provider 切换到它,再添加到 fleet。",
"FleetAddProviderUnconfigured": "`{provider}` 不是已配置的 provider。请先在 ~/.codewhale/config.toml 中配置,或用 /provider 切换到它,再添加到 团队。",
"FleetAddModelNotServed": "{provider} 在当前目录中不提供 `{model}`;运行 /models 查看它提供的模型,或用其列出的精确 ID 执行 /fleet add。",
"FleetAddFailed": "无法添加到 fleet:{error}",
"FleetAddFailed": "无法添加到 团队:{error}",
"FleetRemoveUsage": "用法:/fleet remove <provider> <model>",
"FleetRemoveFailed": "无法从 fleet 移除:{error}",
"FleetToggleFailed": "无法更新 fleet:{error}",
"FleetRemoveFailed": "无法从 团队 移除:{error}",
"FleetToggleFailed": "无法更新 团队:{error}",
"FleetDestStepTitle": "这个配置文件应保存在哪里?",
"FleetDestStepSubtitle": "在最后一步确认之前不会写入任何内容。",
"FleetDestProjectLabel": "此项目",
@@ -1640,12 +1667,13 @@
"KbTurnInspector": "打开回合检查器 (Turn Inspector)",
"CmdTurnInspectDescription": "Open the whole-turn inspector",
"CmdAutomationDescription": "管理持久化的计划自动化",
"AutomationUsage": "用法: /automation [list|show <id>|pause <id>|resume <id>|delete <id> [--confirm <token>]|run <id>]",
"AutomationUsage": "用法: /automation [list|show <id>|print <id>|pause <id>|resume <id>|delete <id> [--confirm <token>]|run <id>]",
"AutomationManagerUnavailable": "此会话无法使用自动化管理器。",
"AutomationListFailed": "无法列出自动化: {error}",
"AutomationActionFailed": "无法{action}自动化 {id}: {error}",
"AutomationEmpty": "没有已计划的自动化。可使用 `automation` 工具创建。",
"AutomationEmpty": "暂无计划的自动化。请选择“新建自动化”。",
"AutomationListHeading": "已计划的自动化",
"AutomationScopeNote": "自动化属于你,而不是这个仓库——它们会跟随你进入每个仓库。",
"AutomationNoun": "自动化",
"AutomationStatusLabel": "状态",
"AutomationStatusActive": "已启用",
@@ -1660,6 +1688,7 @@
"AutomationActionResume": "恢复",
"AutomationActionDelete": "删除",
"AutomationActionRun": "运行",
"AutomationActionCancel": "取消运行",
"AutomationActionPaused": "已暂停",
"AutomationActionResumed": "已恢复",
"AutomationNextLabel": "下次",
@@ -2015,8 +2044,43 @@
"LaunchNoticeClaude": "检测到 Claude Code。运行 /import-claude 查看可迁移的内容:MCP 服务器、安全设置和权限。未经你确认不会应用任何更改。",
"LaunchNewSession": "新建会话",
"LaunchRecentHeading": "最近",
"LaunchHelpLine": "/help 查看命令 · {dock} 打开工作栏",
"LaunchSeeAllSessions": "查看全部会话…",
"LaunchNoRecentSessions": "暂无最近会话 — 在下方输入以开始。",
"LaunchResumeFailed": "恢复失败:{error}",
"ReceiptSessionHooks": "钩子 {count}"
"ReceiptSessionHooks": "钩子 {count}",
"AutomationEditorNew": "新建自动化",
"AutomationEditorEdit": "编辑自动化",
"AutomationEditorSchedule": "运行计划",
"AutomationEditorDaily": "每天",
"AutomationEditorWeekly": "每周",
"AutomationEditorHourly": "每小时",
"AutomationEditorOnce": "仅一次",
"AutomationEditorCustom": "自定义",
"AutomationEditorTime": "时间 (HH:MM)",
"AutomationEditorDate": "日期 (YYYY-MM-DD)",
"AutomationEditorDays": "星期",
"AutomationEditorLocalTime": "时区:本地 ({zone})",
"AutomationEditorProvider": "选择模型会固定其提供商。当前配置:{provider}。",
"AutomationEditorDefaultModel": "默认模型",
"AutomationEditorInheritedProvider": "运行时默认值",
"AutomationEditorEnabled": "启用",
"AutomationEditorControls": "Tab 切换字段 · Enter 编辑 · ↑↓ 调整 · Ctrl+A 全选",
"AutomationEditorPromptControls": "Enter 换行 · Tab 下一字段 · Ctrl+A 全选",
"AutomationEditorSaveFailed": "无法保存:{error}",
"AutomationEditorBusy": "自动化存储正忙。请重新保存。",
"AutomationEditorSaved": "已保存 {name}",
"AutomationEditorPreviewUnavailable": "下次运行时间不可用;将保留现有计划",
"AutomationEditorConflict": "自动化已发生更改。请取消后重新打开。",
"AutomationEditorInvalidWorkspace": "请选择已有的工作目录。",
"AutomationEditorModelControls": "输入筛选 · ↑↓ 移动 · Enter 选择 · Esc 返回",
"AutomationEditorPausedPreview": "已暂停 · 无计划运行",
"AutomationEditorInvalidSchedule": "无效的计划:{error}",
"AutomationEditorMonday": "周一",
"AutomationEditorTuesday": "周二",
"AutomationEditorWednesday": "周三",
"AutomationEditorThursday": "周四",
"AutomationEditorFriday": "周五",
"AutomationEditorSaturday": "周六",
"AutomationEditorSunday": "周日"
}
+98 -34
View File
@@ -1,11 +1,15 @@
{
"ExtensionsActionAdd": "新增",
"ExtensionsActionEnable": "啟用",
"ExtensionsActionEdit": "編輯",
"ExtensionsHooksAddLabel": "新增掛鉤",
"ExtensionsHooksAddDescription": "在你的編輯器中開啟本專案的掛鉤檔案",
"ExtensionsActionReload": "重新載入",
"ExtensionsActionConnect": "連線",
"ExtensionsActionReconnect": "重新連線",
"ExtensionsActionReauth": "重新登入",
"ExtensionsActionDiagnose": "診斷",
"ExtensionsActionManage": "管理",
"ExtensionsActionFocus": "焦點",
"ExtensionsActionFold": "摺疊",
"ExtensionsActionTabs": "分頁",
@@ -19,6 +23,7 @@
"ExtensionsGroupProblems": "問題",
"ExtensionsGroupRecommended": "推薦",
"ExtensionsGroupServers": "伺服器",
"ExtensionsGroupNeedsAttention": "需要處理",
"ExtensionsGroupStatus": "{count} 項 · 按 Enter 摺疊",
"ExtensionsGroupUser": "使用者",
"ExtensionsGroupWorkspace": "工作區",
@@ -45,6 +50,8 @@
"ExtensionsProductCuaDescription": "具備原生 macOS 驅動程式的本機電腦操作執行環境。",
"ExtensionsProductDetail": "發行者:{publisher} · 元件:{components} · 來源:{source}",
"ExtensionsProductPlaywrightDescription": "以 Microsoft Playwright MCP 為基礎的瀏覽器自動化。",
"ExtensionsProductCodewhaleComputerUseDescription": "看見螢幕並操作它:以無障礙介面為先的控制、螢幕擷取、縮放與錄影,還可透過 ssh 操作遠端電腦。",
"ExtensionsStateFirstParty": "隨 Codewhale 內建",
"ExtensionsProductSandboxDescription": "隔離外掛所提供本機指令的程序層。",
"ExtensionsSearchLabel": "搜尋:",
"ExtensionsSkillRootCompatibleGlobal": "相容的全域目錄({harness})",
@@ -163,7 +170,6 @@
"ApprovalTruncationHint": " … 已截斷 · 按 {details} 查看完整內容",
"AutoReviewQuestionSkipped": "Auto-Review 已略過使用者問題並自主繼續",
"AutoRouteSelectedToast": "Auto: {provider} / {model}({source})· Ctrl+O:路由詳情",
"CloudCodeSystemPromptUnsupported": "此連線尚不支援系統指令,因此 Antigravity cloud-code 無法安全傳送本輪。未傳送任何內容;請選擇其他供應商。",
"BehavioralTipBackgroundReceipt": "回執在 Work 面板中 — 按 {key} 開啟檢查器",
"BehavioralTipClearedInput": "已清空 · 按 {chord} 還原",
"BehavioralTipMcpValidation": "{command} 會啟動伺服器並顯示原因",
@@ -188,12 +194,13 @@
"CmdAuthDescription": "管理提供者驗證流程",
"CmdLoginDescription": "登入 Codewhale 或儲存供應商金鑰",
"CmdAutomationDescription": "管理持久化的計劃自動化",
"AutomationUsage": "用法: /automation [list|show <id>|pause <id>|resume <id>|delete <id> [--confirm <token>]|run <id>]",
"AutomationUsage": "用法: /automation [list|show <id>|print <id>|pause <id>|resume <id>|delete <id> [--confirm <token>]|run <id>]",
"AutomationManagerUnavailable": "此工作階段無法使用自動化管理器。",
"AutomationListFailed": "無法列出自動化: {error}",
"AutomationActionFailed": "無法{action}自動化 {id}: {error}",
"AutomationEmpty": "沒有已計劃的自動化。可使用 `automation` 工具建立。",
"AutomationEmpty": "目前沒有排定的自動化。請選擇「新增自動化」。",
"AutomationListHeading": "已計劃的自動化",
"AutomationScopeNote": "自動化屬於你,而不是這個儲存庫——它們會跟著你進入每個儲存庫。",
"AutomationNoun": "自動化",
"AutomationStatusLabel": "狀態",
"AutomationStatusActive": "已啟用",
@@ -208,6 +215,7 @@
"AutomationActionResume": "恢復",
"AutomationActionDelete": "刪除",
"AutomationActionRun": "執行",
"AutomationActionCancel": "取消執行",
"AutomationActionPaused": "已暫停",
"AutomationActionResumed": "已恢復",
"AutomationNextLabel": "下次",
@@ -283,7 +291,7 @@
"CmdCopyFailed": "複製到剪貼簿失敗:{error}。回覆已儲存到 {path}",
"CmdCopyFailedNoBackup": "複製到剪貼簿失敗:{error}。無法寫入復原檔案;請使用 `/export file <path>` 選擇儲存位置",
"CmdFeedbackDescription": "生成 GitHub 意見回饋鏈接",
"CmdFleetDescription": "檢視與設定 Fleet 成員及編排狀態",
"CmdFleetDescription": "檢視與設定 團隊 成員及編排狀態",
"CmdForkDescription": "將目前對話分叉為兄弟工作階段",
"CmdTreeDescription": "以樹狀結構顯示工作階段歷史(葉節點為目前分支)",
"CmdBranchDescription": "將目前分支移至現有工作階段項目,不重寫歷史記錄",
@@ -351,6 +359,12 @@
"CmdModelDbDescription": "參考:瀏覽內置的模型資料庫",
"CmdModelDescription": "切換或檢視目前模型",
"CmdModelsDescription": "工具箱:從目前 API 取得實時模型 ID",
"ModelsListHeader": "{provider} 的模型(預設:{model})",
"ModelsListHint": "已快取的目錄;執行 `codewhale models --update` 可更新已設定的提供者。",
"ModelsUpdateSummary": "模型目錄:{updated} 個已更新,{loaded} 個從 Codex 載入,{failed} 個失敗,{skipped} 個已略過。",
"ModelsUpdatePartial": "部分目錄無法更新。先前的模型和已設定的模型仍然可用。",
"ModelsCodexHint": "ChatGPT 模型透過 Codex 載入。查詢時間並不代表供應商已重新整理模型目錄。",
"ModelsSourceFallback": "內建或已設定的模型;尚未驗證可用性。",
"CmdNetworkDescription": "管理網路允許和拒絕規則",
"CmdUpdateDescription": "檢查並安裝新的 Codewhale 版本",
"CmdNewDescription": "開始一個新的已儲存工作階段",
@@ -559,9 +573,9 @@
"ConfigLabelLowMotion": "減少動態效果",
"ConfigLabelManagedAllowShell": "Shell 存取(受管理)",
"ConfigLabelTelemetry": "匿名使用統計",
"ConfigHintTelemetry": "匿名功能和錯誤計數的已儲存偏好;絕不包含對話或程式碼",
"ConfigValueTelemetryOn": "開啟(已儲存)",
"ConfigValueTelemetryOff": "關閉(已儲存)",
"ConfigHintTelemetry": "使用統計預設開啟。 向 Codewhale 和資料處理方 PostHog 分享版本/平台、工作階段、功能和錯誤的彙總計數。不收集內容或 IP。docs/TELEMETRY.md",
"ConfigValueTelemetryOn": "開啟",
"ConfigValueTelemetryOff": "關閉",
"ConfigLabelManagedApprovalPolicy": "新工作階段權限(受管理)",
"ConfigLabelMaxHistory": "輸入歷史",
"ConfigLabelMcpConfigPath": "MCP 設定路徑",
@@ -807,38 +821,39 @@
"FilePickerMatchSingular": "@ 附加 · 1 個相符",
"FilePickerMatchesPlural": "@ 附加 · {count} 個相符",
"FilePickerScanning": "正在掃描工作區…",
"FleetDraftHeader": "# .codewhale/agents/{name}\n# 由 {model_label} 起草,並由 Codewhale 校驗與限界。\n# 權限保持在 Fleet 底線:無 shell、無 trust、需審批。\n# 在向導中按 g 之前不會儲存任何內容。\n\n",
"FleetDraftTitle": "Fleet 設定 — 由 {model_label} 起草(按 g 儲存)",
"FleetDraftHeader": "# .codewhale/agents/{name}\n# 由 {model_label} 起草,並由 Codewhale 校驗與限界。\n# 權限保持在 團隊 底線:無 shell、無 trust、需審批。\n# 在向導中按 g 之前不會儲存任何內容。\n\n",
"FleetDraftTitle": "團隊 設定 — 由 {model_label} 起草(按 g 儲存)",
"FleetProfileIdConflict": "設定 id `{id}` 已被 {path} 佔用;請重新起草為不同的角色或先移除舊檔案。",
"FleetProfileIdentityVerifyFailed": "無法校驗現有設定標識({error});請先修復列出的檔案再儲存。",
"FleetProfileProviderUnconfigured": "設定指定的 provider `{provider}` 尚未設定憑據({env});請先在 /provider 中設定,再儲存。",
"FleetModelAdded": "已將 {route} 加入 fleet `{fleet}`",
"FleetModelAddedAs": "已將 {route} 作為 {roles} 加入 fleet `{fleet}`",
"FleetModelAddedCreatedNote": "(新的個人 fleet,已選取)",
"FleetModelAdded": "已將 {route} 加入 團隊 `{fleet}`",
"FleetModelAddedAs": "已將 {route} 作為 {roles} 加入 團隊 `{fleet}`",
"FleetModelAddedCreatedNote": "(新的個人 團隊,已選取)",
"FleetModelAddedSelectedNote": "(已選取)",
"FleetModelRemoved": "已將 {route} 從 fleet `{fleet}` 移除",
"FleetModelRemovedRoles": "已將 {route}({roles})從 fleet `{fleet}` 移除",
"FleetModelUnchanged": "{route} 保留在 fleet `{fleet}` 中:{reason}",
"FleetModelReasonOperatorRoute": "這是你目前的模型(fleet 的操作員路由);請切換模型或用 /fleet save 變更",
"FleetModelReasonAlreadyPresent": "已在 fleet 中",
"FleetModelErrorNeedsRoute": "fleet 模型需要同時提供 provider ID 和模型 ID",
"FleetModelErrorNoSelection": "未選取 fleet;你的 fleet 僅包含工作階段模型",
"FleetModelErrorOperatorRoute": "{route} 是 fleet `{fleet}` 的操作員路由;請用 /fleet save 變更,而不是移除",
"FleetModelErrorNotInFleet": "{route} 不在 fleet `{fleet}` 中",
"FleetModelsEmpty": "你的 fleet 僅包含工作階段模型。新增一個:/fleet add <provider> <model> [role…](或在 /model 的某一列按 ⇧F)。",
"FleetModelsHeader": "你的 fleet `{fleet}`({count} 個模型)",
"FleetModelsBroken": "無法載入所選的 fleet:{error}。請修正檔案,或用 /fleet fleets 選擇另一個。",
"FleetModelRemoved": "已將 {route} 從 團隊 `{fleet}` 移除",
"FleetModelRemovedRoles": "已將 {route}({roles})從 團隊 `{fleet}` 移除",
"FleetModelUnchanged": "{route} 保留在 團隊 `{fleet}` 中:{reason}",
"FleetModelReasonOperatorRoute": "這是你目前的模型(團隊 的操作員路由);請切換模型或用 /fleet save 變更",
"FleetModelReasonAlreadyPresent": "已在 團隊 中",
"FleetModelErrorNeedsRoute": "團隊 模型需要同時提供 provider ID 和模型 ID",
"FleetModelErrorNeedsRole": "團隊成員是一個角色;請指定一個(例如 `/fleet add {route} scout`)",
"FleetModelErrorNoSelection": "未選取 團隊;你的 團隊 僅包含工作階段模型",
"FleetModelErrorOperatorRoute": "{route} 是 團隊 `{fleet}` 的操作員路由;請用 /fleet save 變更,而不是移除",
"FleetModelErrorNotInFleet": "{route} 不在 團隊 `{fleet}` 中",
"FleetModelsEmpty": "你的 團隊 僅包含工作階段模型。新增一個:/fleet add <provider> <model> [role…](或在 /model 的某一列按 ⇧F)。",
"FleetModelsHeader": "你的 團隊 `{fleet}`({count} 個模型)",
"FleetModelsBroken": "無法載入所選的 團隊:{error}。請修正檔案,或用 /fleet 團隊s 選擇另一個。",
"FleetModelsFooter": "新增:/fleet add <provider> <model> [role…] · 移除:/fleet remove <provider> <model>",
"FleetModelsFactPrice": "${input}/{output} 每百萬 token",
"FleetModelsFactContext": "{context}k ctx",
"FleetModelsFactTools": "工具",
"FleetAddUsage": "用法:/fleet add <provider> <model> [role…] — 例如 /fleet add openrouter z-ai/glm-5.3-flash explore",
"FleetAddProviderUnconfigured": "`{provider}` 不是已設定的 provider。請先在 ~/.codewhale/config.toml 中設定,或用 /provider 切換到它,再加入 fleet。",
"FleetAddProviderUnconfigured": "`{provider}` 不是已設定的 provider。請先在 ~/.codewhale/config.toml 中設定,或用 /provider 切換到它,再加入 團隊。",
"FleetAddModelNotServed": "{provider} 在目前的目錄中不提供 `{model}`;執行 /models 查看它提供的模型,或用其列出的精確 ID 執行 /fleet add。",
"FleetAddFailed": "無法加入 fleet:{error}",
"FleetAddFailed": "無法加入 團隊:{error}",
"FleetRemoveUsage": "用法:/fleet remove <provider> <model>",
"FleetRemoveFailed": "無法從 fleet 移除:{error}",
"FleetToggleFailed": "無法更新 fleet:{error}",
"FleetRemoveFailed": "無法從 團隊 移除:{error}",
"FleetToggleFailed": "無法更新 團隊:{error}",
"FleetDestStepTitle": "這個設定檔應儲存在哪裡?",
"FleetDestStepSubtitle": "在最後一步確認之前不會寫入任何內容。",
"FleetDestProjectLabel": "此專案",
@@ -869,7 +884,7 @@
"FleetModelRowBlockedNotice": "無法選擇: {reason}。請在 /provider 中設定或選擇其他列。",
"FleetDestProjectDisabledSave": "本次工作階段已停用專案設定檔 (--no-project-config);未儲存任何內容。請選擇個人,或不帶該參數重新啟動。",
"FleetReadyNotice": "Fleet 已就緒 · /fleet 開啟角色 · /fleet setup 調整成員模型",
"FleetRosterHeaderLabel": "fleet",
"FleetRosterHeaderLabel": "團隊",
"FleetRosterMembersCount": "{count} 個成員",
"FleetRosterOperatorFirst": "協調者統領 · 工作階段模型驅動此 Fleet",
"FleetRosterOperatorRow": "協調者 · 領隊",
@@ -910,6 +925,7 @@
"HelpNoMatches": " 無相符。",
"HelpSectionActions": "操作",
"HelpSectionClipboard": "剪貼板",
"HelpSectionPointer": "指標",
"HelpSectionEditing": "輸入編輯",
"HelpSectionHelp": "說明",
"HelpSectionModes": "模式",
@@ -927,9 +943,10 @@
"HistoryHintRestore": "Esc 還原",
"HistoryNoMatches": " 無相符",
"TranscriptReasoningExpand": "展開",
"TelemetryNoticeHeadline": "協助改善 Codewhale 嗎?",
"TelemetryNoticeBody": "Codewhale 只統計:您執行的版本、作業系統和 CPU 系列、工作階段時間和結果,\n以及徽總的功能和錯誤計數。\n\n它絕不會收集您的對話、程式碼、提示詞、檔案、儲存庫名稱或分支名稱、\n模型內容或憑證,也絕不會傳送逐回合或逐工具記錄的代理活動。\n\n系統只使用儲存在此機器上的隨機 ID 來識別您,該 ID 每 90 天更換一次。\n您可以隨時更改:\n codewhale config set telemetry false\n\n逐欄位完整結構:docs/TELEMETRY.md",
"TelemetryNoticeCompactBody": "統計:版本、系統/處理器、工作階段時間/結果、功能/錯誤總數。\n隨機本機識別碼每 90 天更換。\n絕不收集:對話/程式碼/提示詞/檔案/名稱、模型內容/憑證、逐回合/工具記錄的活動。\n結構:docs/TELEMETRY.md\n關閉:codewhale config set telemetry false",
"TelemetryNoticeDefaultOn": "使用統計預設開啟。 向 Codewhale 和資料處理方 PostHog 分享版本/平台、工作階段、功能和錯誤的彙總計數。不收集內容或 IP。docs/TELEMETRY.md 關閉: codewhale config set telemetry false",
"TelemetryNoticeHeadline": "Codewhale 使用統計",
"TelemetryNoticeBody": "Codewhale 只統計:您執行的版本、作業系統和 CPU 系列、工作階段時間和結果,\n以及徽總的功能和錯誤計數。\n\n它絕不會收集您的對話、程式碼、提示詞、檔案、儲存庫名稱或分支名稱、\n模型內容或憑證,也絕不會傳送逐回合或逐工具記錄的代理活動。\n\n系統只使用儲存在此機器上的隨機 ID 來識別您,該 ID 每 90 天更換一次。\n您可以隨時更改:\n codewhale config set telemetry false\n\n逐欄位完整結構:docs/TELEMETRY.md\n\n使用統計預設開啟。 設定傳送後,Codewhale 和 PostHog 會處理這些計數。不收集 IP。",
"TelemetryNoticeCompactBody": "統計:版本、系統/處理器、工作階段時間/結果、功能/錯誤總數。\n隨機本機識別碼每 90 天更換。\n絕不收集:對話/程式碼/提示詞/檔案/名稱、模型內容/憑證、逐回合/工具記錄的活動。\n結構:docs/TELEMETRY.md\n關閉:codewhale config set telemetry false\n使用統計預設開啟。 設定傳送後,Codewhale 和 PostHog 會處理這些計數。不收集 IP。",
"TelemetryNoticeChoiceKeep": "是,保留匿名使用統計",
"TelemetryNoticeChoiceDisable": "不,關閉匿名統計",
"TelemetryNoticeActionChoose": "選擇",
@@ -1034,6 +1051,9 @@
"KbCompactContext": "壓縮對話上下文",
"KbCompleteCycleModes": "補全 /command,或在輸入框為空時切換模式(Plan → Work → Operate)",
"KbContextMenu": "開啟上下文操作選單,用於粘貼、選擇、訊息詳情、上下文和說明",
"KbPointerScroll": "捲動記錄;在輸入框上方逐行移動游標",
"KbPointerClick": "啟動指標下的列、標籤或工具區塊",
"KbPointerDrag": "選取記錄文字,或拖曳捲軸",
"KbCopySelection": "複製 Codewhale 目前選中內容",
"KbCyclePermissions": "切換存取層級:詢問 → 自動審查 → 完整存取(Shift+Tab)",
"KbCycleThinking": "輪換目前提供商的推理強度 (Ctrl+T)",
@@ -1046,6 +1066,8 @@
"KbFocusSidebar": "聚焦 Pinned / Activity / 代理 / Context / 自動 / 隱藏側邊欄; Ctrl+Alt+0 切換固定側邊欄",
"KbFuzzyFilePicker": "開啟模糊檔案選擇器(按 Enter 插入 @path)",
"KbHelpOverlay": "開啟此說明涵蓋層(輸入框為空時)",
"KbCycleWorkDock": "切換工作列(待辦、代理、作業、背景)",
"KbCycleWorkDockBack": "反向切換工作列",
"KbInsertNewline": "在輸入框中插入換行",
"KbJumpLineStartEnd": "跳轉到行首/行尾",
"KbJumpPlanAgentYolo": "觸發 Hotbar 槽位",
@@ -1087,6 +1109,11 @@
"LaunchHintOpen": "開啟",
"LaunchMenuChangelog": "更新日誌",
"LaunchStartTitle": "從這裡開始",
"LaunchResumeConfirm": "恢復 {title}?再次點擊或按 Enter 確認。",
"LaunchResumeConfirmTitle": "恢復工作階段",
"LaunchResumeConfirmBody": "這會以該工作階段的歷史取代目前的上下文。",
"LaunchResumeConfirmResume": "恢復",
"LaunchResumeConfirmCancel": "取消",
"LaunchWorkDescription": "在此資料夾中使用本機工具;變更遵循你的核准政策。",
"LaunchChatDescription": "僅對話和規劃;不會變更檔案。",
"LaunchWorkspaceGitReady": "工作區 · {name} · Git 工作區",
@@ -1100,7 +1127,6 @@
"LaunchProviderConfiguredShort": "供應商已設定",
"LaunchProviderSetupShort": "設定供應商",
"LaunchNoSavedSessions": "此工作區沒有已儲存的工作階段。",
"LaunchComposerHint": "Enter 傳送 · Shift+Enter 換行 · Esc 返回",
"LaunchNoModelConnected": "未連線模型",
"LaunchRunCommand": "執行 {command}",
"LaunchMcpConnectedOne": "已連線 1 個 MCP 伺服器",
@@ -1351,6 +1377,9 @@
"SettingSubjectProvider": "提供商",
"SettingSubjectThinking": "思考",
"SettingsConfigFile": "設定檔案:",
"SettingsTuiPrefsFolded": "已從 tui.toml 遷移 {key} '{value}' — 現在由 settings.toml 管理。",
"SettingsTuiPrefsKept": "tui.toml 將 {key} 設為 '{prefs}',但 settings.toml 已設為 '{settings}' — 以 settings.toml 為準。",
"SettingsTuiPrefsQuarantined": "沒有對應設定的 tui.toml 鍵已保留在 {path}: {keys}",
"SettingsTitle": "設定:",
"SetupActionApplyRuntimePreset": "預覽/套用",
"SetupActionBack": "返回",
@@ -2015,8 +2044,43 @@
"LaunchNoticeClaude": "偵測到 Claude Code。執行 /import-claude 查看可遷移的內容:MCP 伺服器、安全設定與權限。未經你確認不會套用任何變更。",
"LaunchNewSession": "新增會話",
"LaunchRecentHeading": "最近",
"LaunchHelpLine": "/help 檢視指令 · {dock} 開啟工作列",
"LaunchSeeAllSessions": "查看全部會話…",
"LaunchNoRecentSessions": "暫無最近會話 — 在下方輸入以開始。",
"LaunchResumeFailed": "恢復失敗:{error}",
"ReceiptSessionHooks": "掛鉤 {count}"
"ReceiptSessionHooks": "掛鉤 {count}",
"AutomationEditorNew": "新增自動化",
"AutomationEditorEdit": "編輯自動化",
"AutomationEditorSchedule": "執行排程",
"AutomationEditorDaily": "每天",
"AutomationEditorWeekly": "每週",
"AutomationEditorHourly": "每小時",
"AutomationEditorOnce": "僅一次",
"AutomationEditorCustom": "自訂",
"AutomationEditorTime": "時間 (HH:MM)",
"AutomationEditorDate": "日期 (YYYY-MM-DD)",
"AutomationEditorDays": "星期",
"AutomationEditorLocalTime": "時區:本機 ({zone})",
"AutomationEditorProvider": "選擇模型會固定其供應商。目前設定:{provider}。",
"AutomationEditorDefaultModel": "預設模型",
"AutomationEditorInheritedProvider": "執行階段預設值",
"AutomationEditorEnabled": "啟用",
"AutomationEditorControls": "Tab 切換欄位 · Enter 編輯 · ↑↓ 調整 · Ctrl+A 全選",
"AutomationEditorPromptControls": "Enter 換行 · Tab 下一欄位 · Ctrl+A 全選",
"AutomationEditorSaveFailed": "無法儲存:{error}",
"AutomationEditorBusy": "自動化儲存空間忙碌中。請重新儲存。",
"AutomationEditorSaved": "已儲存 {name}",
"AutomationEditorPreviewUnavailable": "下次執行時間不明;將保留現有排程",
"AutomationEditorConflict": "自動化已變更。請取消後重新開啟。",
"AutomationEditorInvalidWorkspace": "請選擇現有的工作目錄。",
"AutomationEditorModelControls": "輸入篩選 · ↑↓ 移動 · Enter 選擇 · Esc 返回",
"AutomationEditorPausedPreview": "已暫停 · 無排定的執行",
"AutomationEditorInvalidSchedule": "無效的排程:{error}",
"AutomationEditorMonday": "週一",
"AutomationEditorTuesday": "週二",
"AutomationEditorWednesday": "週三",
"AutomationEditorThursday": "週四",
"AutomationEditorFriday": "週五",
"AutomationEditorSaturday": "週六",
"AutomationEditorSunday": "週日"
}
Loaded 100 of 475 files, more files were not shown because too many files have changed in this diff. Show more