Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
35 changes: 35 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# One CLI 开发约定

本文件适用于整个仓库。

## 默认考虑多语言

新增或修改用户可见功能时,默认同时支持 `zh-CN` 和 `en-US`,在同一次变更中补齐两种语言。不要等用户反馈中英文混用后再补翻译。

### CLI 文案

- 复用 `packages/cli/internal/platform/i18n` 的 `T`、`Tf` 和 `Errorf`,同步维护 `locales/zh-CN.json` 与 `locales/en-US.json`。使用能表达用途的语义键,避免在业务代码中硬编码用户可见的中文或英文句子。
- 覆盖命令帮助、参数说明、交互选项、校验错误、进度、结果、恢复建议、TUI 状态与快捷键说明,以及内置模板的展示名称和说明。
- 用完整句子的格式模板表达动态文案,避免拼接译文片段。两种语言的格式参数必须匹配;错误包装继续使用 `%w`,保留错误链。
- Cobra 命令帮助使用 `MarkShort`、`MarkLong`、`MarkFlagUsage` 注册翻译键,使 `RefreshTree` 能在语言确定或切换后更新文案。参数数量校验和 flag 错误复用 i18n 中的公共处理逻辑。
- 沿用现有语言偏好与 `auto` 解析逻辑,不另设语言环境变量或检测流程。除可刷新的命令元数据外,不在包初始化或全局变量中缓存译文。切换语言后的确认信息使用新语言。
- 错误输出保留具体原因、项目名、路径和恢复建议;不要用错误码的通用描述覆盖具体错误信息。

### Dashboard 文案

- 复用 `apps/dashboard/src/lib/i18n.ts` 和现有 locale store,沿用现有字典结构及语言切换机制。
- 新增页面、组件、空状态、表单提示、通知、按钮、tooltip 和无障碍标签时,同步补齐中英文。
- 布局要容纳不同语言的文本长度,检查换行、截断、弹窗宽度和窄屏显示。

### 保持原样的内容

- 命令及 flag 名称、机器可读的 JSON 字段与状态值、错误码、模板及 provider ID 等稳定协议值保持不变,仅在展示层翻译其说明。
- 用户输入、项目名、路径、自定义模板文案等用户内容保持原样。
- 子进程及第三方工具的原始日志保留原语言、ANSI 颜色和控制台格式。One CLI 自己输出的说明、上下文和恢复建议跟随当前语言。

### 验证

- 两份语言字典的键必须完整对应、译文非空、格式占位符兼容。新增翻译键时确认实际使用处能够解析。
- 根据改动覆盖两种语言的帮助、交互、错误或结果输出,并检查语言切换后没有旧语言残留。涉及持久化偏好的测试使用临时 HOME/config,避免修改开发者设置。
- 修改提示组件或 TUI 时,检查中文终端显示宽度、快捷键提示和文字对比度,并确认子进程原始输出仍被保留。
- 文案变化需要审阅并更新相关帮助/输出快照;运行与改动相关的测试,并按仓库现有流程执行 `task check`。
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@

```bash
brew install go go-task node # macOS;Linux 用 apt / dnf 类比
npm i -g pnpm # 或 corepack enable && corepack prepare pnpm@10
npm i -g pnpm@10.14.0 # 与根 package.json 的 packageManager 一致
git clone https://github.com/1cli-team/one-cli
cd one-cli
task install # 打包 Dashboard + CLI,再创建当前平台的本地启动器
one --version # 验证装好
```

工具链:**Go 1.25+**、**Node 20+**、**pnpm 10+**。`go-task`(不是 GNU make)是任务总线,跨平台一致。
工具链:**Go 1.26+**、**pnpm 10.14.0**。Node 推荐使用 **24.x(至少 24.15.0)**,也支持 22.x(至少 22.22.2)或 26+;Dashboard 测试依赖的 jsdom 不再支持 Node 20。`go-task`(不是 GNU make)是任务总线,跨平台一致。

> **fresh-clone 提示**:`packages/cli/internal/resources/bundled/` 整个目录是 gitignore 的——
> registry / templates / dashboard dist 都由 `task sync-bundled` +
Expand Down
59 changes: 43 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ One CLI is useful when you want to:

- start from a clean project foundation
- add a frontend, backend, docs site, mobile app, desktop app, or library later
- keep local settings and deployment choices out of random notes
- keep environment configuration and local settings organized
- let an AI assistant help without guessing how the project is arranged
- use the same simple commands across different kinds of projects

Expand Down Expand Up @@ -89,11 +89,11 @@ one add nestjs-api --name api
|---|---|
| `one create <workspace>` | Create an empty workspace |
| `one add <starter>` | Add another app, service, docs site, or library |
| `one dev [project]` | Run every project, or one selected project, locally |
| `one deploy [project]` | Choose a target on first deploy, then deploy |
| `one dev [projects...]` | Run all or selected projects; native output for one task, TUI for multiple tasks |
| `one build [projects...]` | Build all or selected projects in dependency order; optional bounded concurrency |
| `one env` | Review and manage environment variables |
| `one configure` | Manage local connections and preferences |
| `one serve` | Inspect Workspaces and Projects; manage local Profiles and bindings |
| `one login` | Sign in to Infisical with your browser |
| `one serve` | Inspect workspaces, manage the current account and shared credentials |
| `one ci [enable\|sync\|disable]` | Optionally manage generated GitHub Actions workflows |

Full command docs live at [1cli.dev](https://1cli.dev).
Expand Down Expand Up @@ -122,23 +122,17 @@ The assistant can read `one.manifest.json` and project README files, then use On

## Local Settings

Some projects need environment values, deployment accounts, or image registry settings. One CLI keeps those in your local user config, not inside the project files you share with the team.
One CLI manages local dotenv and Infisical variables. Run `one login` to sign in with your browser; the single session is stored in the OS keyring, with no plaintext fallback. Use `one whoami` to inspect status and `one logout` to remove the local session.

For a guided browser-based setup:
Run `one serve` for account settings, workspaces, and shared credentials. Workspace and project configuration changes share one reviewed, revision-checked Manifest draft. Remote variable edits take effect immediately; lists omit values and reveal/copy fetch plaintext only on demand.

```bash
one configure open
```

The page only binds to your local machine by default, so it is a better place for sensitive values than a chat window or a shared document. Workspace environment Backend and Project configuration edits remain browser drafts until the top-bar save action shows an exact diff. Project changes use atomic revision-checked Manifest patches; Backend changes use the revision-checked env switch workflow, including Infisical project binding initialization. Source files and non-allowlisted Manifest fields remain read-only. Backend changes do not migrate secret values between providers. Infisical workspaces also expose scoped secret CRUD: lists omit values, and cleartext is fetched one key at a time with no-store responses.

Profile definitions and credentials live in `~/.config/one/config.json` and `credentials.json`. They are machine-global, so Profile CRUD in Settings is not environment-scoped. The Dashboard UI offers Development, Preview, and Production binding contexts; those selections live separately in `~/.config/one/profile-bindings.json`, keyed by canonical Workspace root and environment. These local files never modify the repository manifest; only the explicit reviewed Project draft and environment Backend switch endpoints can do that.
Choose shared credential storage with `one env bind --global`. Agents discover environments and folders through `one env --global` and `one env list --global`, then execute with `one run --global --env dev --path /folder --keys KEY -- command`. Explicit scope and best-effort masking reduce accidental exposure; they do not isolate arbitrary programs running as the same OS user. Use least-privilege remote permissions.

## Project Map

Every One CLI project has a `one.manifest.json` file at the root. Most users do not need to edit it by hand.

Think of it as the project map. It records which parts exist, where they live, and which starter created them. One CLI reads it when you add, run, deploy, or inspect parts of the project. `one serve` writes it only after an explicit reviewed, revision-checked Dashboard action; other repository changes stay in the normal code-review workflow.
Think of it as the project map. It records which parts exist, where they live, and which starter created them. One CLI reads it when you add, run, build, or inspect parts of the project. `one serve` writes it only after an explicit reviewed, revision-checked Dashboard action; other repository changes stay in the normal code-review workflow.

## Repository Layout

Expand All @@ -150,7 +144,7 @@ If you want to work on One CLI itself, the repository is organized like this:
| `packages/templates` | Starters used by `one add` |
| `skills/one-cli` | Minimal workspace guidance installed by `one skills install` |
| `apps/docs` | Documentation website |
| `apps/dashboard` | Local Workspace, Project, and Profile Dashboard opened by `one serve` |
| `apps/dashboard` | Local workspace, account, and global-variable Dashboard opened by `one serve` |
| `assets` | Brand assets, including the logo |

Common contributor commands:
Expand All @@ -176,3 +170,36 @@ Read [CONTRIBUTING.md](./CONTRIBUTING.md) before opening a pull request.
## License

MIT.

### Development and build terminals

```sh
one dev web api # Run a selected set of projects in parallel
one dev --select # Search and select projects interactively
one dev web # Keep the project's native colors, progress, and input
one dev web api --keep-going # Keep peers running if a project exits
one dev web api --ui=stream # Use continuous prefixed logs
one build web api --concurrency=4 # Build ready tasks concurrently, respecting local dependencies
```

`--ui=auto` uses a native terminal for one task and a TUI for multiple tasks.
Override it with `raw`, `tui`, or `stream`. TUI and raw require an interactive
terminal with text output; CI, pipes, and JSON/YAML output use streaming logs.
Structured results remain on stdout and task logs go to stderr. `--dry-run`
only prints the selected execution plan.

In the TUI, use ↑/↓ to select a project, Enter to send it keyboard input, and
Ctrl+] to return to navigation. PgUp/PgDn scroll history, f resumes following,
/ searches projects, and h hides the project list. In dev, r restarts the selected
project and s stops it. Ctrl+C in navigation stops the session and its process
trees. Ctrl+C in input mode is sent to the selected application. By default any
dev process exiting stops the group; `--keep-going` keeps the other projects alive.

Build concurrency defaults to 1. Selected local Node dependencies run first;
project selection does not implicitly add unselected dependencies. Failed builds
stop new scheduling, finish already running independent builds, and block tasks
that depend on the failure. Build sessions return to the shell automatically.

Interactive task terminals currently support Unix (including Linux and macOS).
Windows supports native single-task output and streaming multiple tasks; auto
falls back to streaming until a ConPTY adapter is available.
11 changes: 5 additions & 6 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ tasks:
- 'packages/kernel/**/*.go'
- 'packages/cli/cmd/**/*.go'
- 'packages/cli/internal/**/*.go'
- 'packages/cli/internal/adapters/deploy/kustomize/templates/*'
- 'packages/cli/internal/platform/i18n/locales/*.json'
- 'packages/cli/internal/resources/bundled/**/*'
- 'packages/cli/pkg/**/*.go'
Expand Down Expand Up @@ -95,12 +94,12 @@ tasks:
- 'packages/templates/*/README.md.hbs'

verify-preset-codes:
desc: Lock the v1 preset code table (template + deploy + env + container) — codes are append-only and never re-used
desc: Lock the v1 preset code table (template + env) — codes are append-only and never re-used
# The test package imports internal/core/template → internal/resources/bundled, so it
# needs the embed sources present. Mirrors verify-cli-references.
deps: [sync-bundled, sync-web]
cmds:
- go -C '{{.CLI_DIR}}' test -count=1 -run 'TestTemplateCodesMatchGoldenAndRegistry|TestDeployCodesMatchGolden|TestEnvCodesMatchGolden|TestContainerCodesMatchGolden|TestGoldenSortedByCode' ./internal/modules/preset/...
- go -C '{{.CLI_DIR}}' test -count=1 -run 'TestTemplateCodesMatchGoldenAndRegistry|TestEnvCodesMatchGolden|TestGoldenSortedByCode' ./internal/modules/preset/...
sources:
- 'packages/cli/internal/modules/preset/**/*.go'
- 'packages/cli/testdata/preset/v1_codes.json'
Expand Down Expand Up @@ -315,8 +314,8 @@ tasks:
Dashboard on http://localhost:5173. Open that Vite URL directly.

Workspace and Project data comes from the checked-in Dashboard fixture.
Profile CRUD still reads and writes this machine's real One config;
Profile bindings are real and scoped to the fixture Workspace root.
Login and global-variable operations use the real local Infisical session.
Workspace configuration is scoped to the fixture Workspace root.

Both processes stop together when you press Ctrl-C.
deps: [sync-bundled, sync-web]
Expand All @@ -331,7 +330,7 @@ tasks:
dev:dashboard:
env:
VITE_DEV_API_TARGET: http://127.0.0.1:5174
VITE_DEV_DATA_MODE: fixture-live-profiles
VITE_DEV_DATA_MODE: fixture-live-session
cmds:
- pnpm --filter one-serve-web dev

Expand Down
54 changes: 27 additions & 27 deletions apps/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "one-serve-web",
"version": "0.1.0",
"private": true,
"description": "Embedded `one serve` Dashboard for Workspaces, Projects, and machine-level Profiles. Built into the Go binary via go:embed.",
"description": "Embedded `one serve` Dashboard for Workspaces, Projects, and single-account Infisical credentials. Built into the Go binary via go:embed.",
"type": "module",
"scripts": {
"dev": "vite",
Expand All @@ -18,38 +18,38 @@
"check:fix": "pnpm run lint:fix && pnpm run format:fix"
},
"dependencies": {
"@tailwindcss/vite": "^4.2.2",
"axios": "^1.13.5",
"@tailwindcss/vite": "^4.3.3",
"axios": "^1.20.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"i18next": "^25.6.0",
"lucide-react": "^0.563.0",
"i18next": "^26.4.2",
"lucide-react": "^1.48.0",
"radix-ui": "^1.6.7",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-i18next": "^16.1.1",
"react-router-dom": "^7.13.0",
"sonner": "^2.0.7",
"swr": "^2.4.0",
"tailwind-merge": "^3.4.0",
"tailwindcss": "^4.1.18",
"react": "^19.3.0",
"react-dom": "^19.3.0",
"react-i18next": "^17.0.15",
"react-router-dom": "^7.18.4",
"sonner": "^2.0.8",
"swr": "^2.5.1",
"tailwind-merge": "^3.7.0",
"tailwindcss": "^4.3.3",
"tw-animate-css": "^1.4.0",
"zustand": "^5.0.11"
"zustand": "^5.0.15"
},
"devDependencies": {
"@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1",
"@types/node": "^25.2.2",
"@types/react": "^19.2.13",
"@types/react-dom": "^19.2.3",
"@testing-library/react": "^16.3.3",
"@testing-library/user-event": "^14.6.7",
"@types/node": "^26.6.3",
"@types/react": "^19.3.0",
"@types/react-dom": "^19.3.0",
"@vitejs/plugin-react": "^6.1.1",
"globals": "^17.3.0",
"jsdom": "^25.0.1",
"msw": "^2.14.6",
"oxfmt": "^0.45.0",
"oxlint": "^1.56.0",
"typescript": "^5.9.3",
"vite": "^8.0.1",
"vitest": "^4.1.0"
"globals": "^17.12.0",
"jsdom": "^30.1.1",
"msw": "^2.15.0",
"oxfmt": "^0.70.0",
"oxlint": "^1.85.0",
"typescript": "^7.0.2",
"vite": "^8.3.1",
"vitest": "^5.0.2"
}
}
30 changes: 18 additions & 12 deletions apps/dashboard/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type React from "react";
import { useMatch } from "react-router-dom";
import { AppSidebar } from "@/components/AppSidebar";
import { TopBar } from "@/components/TopBar";
import { AppRoutes } from "@/router/routes";
import { cn } from "@/lib/utils";
Expand All @@ -8,18 +9,23 @@ export const App: React.FC = () => {
const workspaceMode = Boolean(useMatch("/workspace/:entryId"));

return (
<div className="flex h-dvh min-w-[960px] flex-col overflow-hidden bg-background text-foreground">
{workspaceMode ? null : <TopBar />}
<main
className={cn(
"min-h-0 flex-1",
workspaceMode ? "overflow-hidden" : "overflow-y-auto px-6 py-6",
)}
>
<div className={cn("w-full", workspaceMode ? "h-full min-h-0" : "mx-auto max-w-[1480px]")}>
<AppRoutes />
</div>
</main>
<div className="flex h-dvh min-w-0 overflow-hidden bg-background text-foreground">
<AppSidebar />
<div className="flex min-w-0 flex-1 flex-col">
<TopBar />
<main
className={cn(
"min-h-0 min-w-0 flex-1",
workspaceMode ? "overflow-hidden" : "overflow-y-auto p-4 ud-md:p-6",
)}
>
<div
className={cn("w-full", workspaceMode ? "h-full min-h-0" : "mx-auto max-w-[1600px]")}
>
<AppRoutes />
</div>
</main>
</div>
</div>
);
};
15 changes: 1 addition & 14 deletions apps/dashboard/src/api/catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ import http from "@/lib/http";
import type { BackendDomain, BackendSpec, CatalogResponse, SectionKey } from "@/types/api";

export const catalogKey = "/catalog";
export const BACKEND_DOMAINS: readonly BackendDomain[] = ["env", "deploy", "container"];
export const BACKEND_DOMAINS: readonly BackendDomain[] = ["env"];

const EMPTY_BACKENDS: readonly BackendSpec[] = [];
const CONTAINER_ARTIFACT_CAPABILITIES = new Set(["container/build", "container/push"]);

export async function getCatalog(): Promise<CatalogResponse> {
return http.get<CatalogResponse>(catalogKey);
Expand All @@ -24,17 +23,6 @@ export function humanizeBackendName(name: string): string {
.join(" ");
}

export function backendRequiresContainerArtifact(backend?: BackendSpec): boolean {
return Boolean(
backend?.requirements?.some(
(requirement) =>
requirement.kind === "capability" &&
!requirement.optional &&
CONTAINER_ARTIFACT_CAPABILITIES.has(requirement.name),
),
);
}

export function useBackendCatalog() {
const result = useSWRImmutable(catalogKey, getCatalog);
const backends = result.data?.backends ?? EMPTY_BACKENDS;
Expand All @@ -49,7 +37,6 @@ export function useBackendCatalog() {
return {
byID,
byDomain,
configurable: backends.filter((backend) => backend.profile.configurable),
};
}, [backends]);

Expand Down
Loading
Loading