One CLI is a Go multi-module monorepo. packages/kernel provides a small,
Cordis-inspired context and effect-ownership runtime; packages/cli owns the
product domains and executable. The kernel is a compiled dependency, not a
dynamic plugin runtime, and the migration preserves the current binary,
manifest, profile storage, and Dashboard contracts.
The design has two goals: backend capabilities must be composable, and every temporary side effect must have an owner and a cleanup path.
The public product vocabulary is intentionally small and hierarchical:
- Runtime: Workspace, Project, Environment, Backend, Profile
- Creation: Template
- Profile data: Credential
The runtime concepts are:
- Workspace
- Project
- Environment
- Backend
- Profile
Template exists only while a Project is created. Credential is a sensitive,
typed value inside a Profile; it is not a peer runtime concept.
kind in one.manifest.json, --provider flags, and existing PLUGIN_*
error codes are compatibility surfaces. New code and documentation use
backend as the canonical term.
The built-in catalog currently contains 16 backends:
| Domain | Backends | Count |
|---|---|---|
| Environment | dotenv, infisical | 2 |
| Deploy | aliyun-oss, tencent-cos, aws-s3, minio, rustfs, r2, kustomize, vercel, cloudflare, edgeone | 10 |
| Container | docker, dockerhub, ghcr, acr | 4 |
Cordis concepts are translated into explicit Go constructs:
| Cordis | One CLI |
|---|---|
| Context | kernel.Context ownership plus an immutable execution Scope |
| Component | a compiled-in backend adapter or a cohesive feature module |
| Service | an application service or a narrow contract under internal/ports |
| Inject / coeffect | backend requirements declared in the catalog |
| Effect / fiber | kernel.Lifecycle, an owned, idempotent cleanup stack |
The implementation deliberately does not provide a string-keyed service locator, runtime package loading, hot module replacement, proxy properties, or a generic event bus.
packages/cli/cmd/one
-> bootstrap unique process composition root
|-> transport Cobra and Dashboard HTTP boundaries
|-> modules cohesive feature slices
|-> application stable transport-neutral use cases
|-> adapters Docker/cloud/environment/CI implementations
|-> ports provider and runtime contracts
|-> core backend/profile/template/workspace model
|-> platform shared technical capabilities
`-> resources immutable embedded assets
packages/cli -> packages/kernel context and effect ownership only
packages/kernel -> Go standard library
transport -> modules / application / ports / core / platform / resources
modules -> application / adapters / ports / core / platform / resources
application -> ports / core / platform
adapters -> ports / core / platform
ports -> core / platform
core -> platform / resources
platform and resources are leaves. bootstrap is the only layer allowed
to compose the complete CLI graph. kernel is independent of CLI domains,
transports, adapters, and vendor SDKs.
The physical Go workspace is:
go.work activates the CLI and kernel modules
packages/kernel/
go.mod independent kernel module
README.md package purpose and dependency boundary
docs/ architecture decisions and extension guidance
internal/effect/ private cleanup-stack implementation
pkg/kernel/ public Context and Lifecycle API
packages/cli/internal/
bootstrap/
cli/ constructs adapters and injects all dependencies
core/
backend/ backend identity, capabilities, profile type, and form policy
container/ OCI image workflow inputs, results, and registry value
profile/ machine profile model and persistence primitives
template/ template registry and selection model
workspace/ manifest and workspace model
ports/
deploy/ deployment provider contract
secrets/ command environment loader contract
application/
ci/ configure/ deployment/ execution/ workspace/
adapters/
ci/ container/ deploy/ env/ shared/ toolchain/
modules/
container/ compiled Docker/OCI workflow and manifest publication
creation/ Template-to-Workspace/Project materialisation
development/ local development process orchestration
environment/ dotenv/Infisical workflows and workspace setup
preset/ pure preset encoding, parsing, and resolution
skills/ bundled one-cli skill installation into agent user directories
platform/
errors/ helpui/ i18n/ output/ preferences/ process/ prompt/ updatecheck/
resources/
bundled/ embedded templates, registry, one-cli skill, and Dashboard
transport/
cobra/ one directory per command family
http/ local Dashboard API
packages/cli/
cmd/one/ thin executable entry point
pkg/ intentionally public Go packages
testdata/ stable fixtures and compatibility snapshots
tests/e2e/ binary and full-command contract tests
tools/ repository verification programs
apps/dashboard/src/
api/ typed HTTP boundary and SWR keys
architecture/ executable frontend dependency rules
features/
profile-editor/ Catalog-driven profile editing workflow
project-settings/ Desktop project matrix and project inspector workflow
pages/ route composition and page-specific presentation
Rules:
- Transport packages own input parsing, prompts, and rendering. Reusable backend selection and execution policy belongs in application services or cohesive feature modules. Transports never import concrete adapters.
- Application packages own use cases;
internal/portsowns the narrow contracts implemented by adapters. - Adapters contain vendor and operating-system details. They do not import application, modules, transport, or bootstrap. Compatibility result and error types may use platform helpers.
- Modules are vertical, compiled-in feature slices. They may orchestrate ports and adapters, but they do not depend on transport or bootstrap.
- Platform packages contain process-wide technical concerns, not product feature policy. Resources contain immutable embedded data and import no other internal layer.
- Execution scopes carry request state, not arbitrary services or
credentials.
application/execution.Workspaceresolves the workspace boundary and manifest once per command; project selection reuses that snapshot instead of reading the manifest in each transport helper. - Credentials remain typed and are resolved immediately before the adapter call that needs them.
- The backend catalog is the only source for backend identity, capabilities,
requirements, profile type, credential-form metadata, safe project-setting
fields, and secret-field disclosure policy.
core/profiledispatches typed schema access by profile type; application workflows never maintain a second codec or project-form registry. - Cleanup is LIFO, idempotent, and best-effort. It covers local temporary resources; it does not pretend that an external cloud deployment is a reversible transaction.
- Command, deploy, secrets, and CI provider sets are constructed explicitly.
Provider packages do not register themselves through
init(). internal/architecture/dependencies_test.goenforces these boundaries for production Go files, including leaf-layer and transport/adapter rules.packages/kernelimports only the Go standard library. It never owns Workspace, Project, Environment, Backend, Profile, or Template policy.- Workspace modules are listed explicitly in
go.work; embedded Go template modules underpackages/templatesremain standalone template fixtures. packages/kernelfollows the applicable parts of the Go project-layout convention: public APIs live underpkg/kernel, private implementation lives underinternal, and design notes live underdocs. Executable and deployment directories are intentionally absent because Kernel is a library, not a standalone service.
Cobra / HTTP / Dashboard
|
v
application service / feature module
|
+---- validates Backend Catalog capability
|
+---- real extension seam --> typed port / provider registry
|
`---- compiled feature ----> built-in adapter
The root harness creates an immutable execution.Scope containing the process
context, working directory, and Kernel lifecycle. A command that requires a
workspace resolves that scope through execution.ResolveWorkspace:
command context
|
v
execution.Scope working directory + lifecycle
|
v
execution.ResolveWorkspace walk up to one.manifest.json once
|
v
execution.Workspace root + manifest snapshot + project lookup
|
+--> select Project by name or relative path
`--> infer Project from the command working directory
Deploy, container, dev, CI, configure, run, add, and environment commands use
this same boundary. Helpers receive the snapshot rather than a root string that
would let them rediscover the workspace. A workflow that intentionally writes
one.manifest.json must call Workspace.Reload before relying on the new
state. The bare root command keeps optional discovery because running one
outside a workspace renders help instead of producing a workspace error.
Creation is one Template-driven compiled workflow:
modules/creation.Serviceis the single mutation boundary shared by ordinaryone create,one create --preset,one add, and first-deploy artifact configuration;- workspace target revalidation, skeleton generation, Backend selection, environment preparation, Template rendering, manifest publication, project artifact generation and best-effort Git initialization stay behind that boundary;
- its private
syncProjectstep owns container artifacts, the persisted dev command, deploy configuration, and environment safety rules in dependency order; modules/presetis a pure plan format: it owns only preset codes, parsing, canonical encoding, registry resolution, and flag-conflict validation;- there is no top-level
modules/scaffold: workspace-file generation is an implementation detail of creation, not another product concept; - there is no cross-adapter
projectsyncpackage: orchestration stays beside the workflow that supplies its complete input; - workload-name and kebab-case policy come from
core/workspace, rather than being duplicated by adapter helper packages.
Dashboard Workspace reads and machine-local Profile selections enter through
application/workspace.Service. The service owns Overview construction,
Backend validation, Project lookup, Template/deployment compatibility, and
Profile-binding policy, but has no manifest-publication capability.
one.manifest.json is a read-only fact source for that projection service:
the Project projection exposes its values, a SHA-256 revision, and resolved
Profile names/sources, never Profile values or credentials. Confirmed
Dashboard publication enters through the separate application/manifest.Service.
It accepts only typed, allowlisted Project patches, compares the submitted
revision with the current file, validates Backend/config compatibility, and
publishes the complete candidate through the existing atomic Manifest writer.
Workspace environment Backend changes instead enter through the revision-checked
HTTP switch endpoint and modules/environment.Service, so selecting Infisical
initializes and persists its remote project binding before the request succeeds.
Changing the Workspace environment Backend does not migrate secret values
between providers.
Stale drafts fail with SERVE_MANIFEST_CONFLICT; browser clients never submit
a replacement Manifest document. Environment-aware Workspace and Project
selections are stored in XDG-aware
~/.config/one/profile-bindings.json v1. The store is keyed by canonical
Workspace root and safe environment id, and its Workspace/Project maps contain
only domain/backend -> Profile name selections. Keeping the canonical root
in the key isolates two repository copies even when their manifests share one
Workspace id. The Dashboard UI exposes dev, preview, and prod as URL
state (?env=), not as a manifest migration; the core/API contract also
accepts safe custom environment ids for non-UI workflows. HTTP handlers only
decode requests, resolve the trusted Workspace root, map application errors,
and render application envelopes.
Historical manifest-mutation route paths fail closed with HTTP 409 and
SERVE_REPOSITORY_READ_ONLY; they never silently ignore a requested write.
Workspace discovery across invocations is a separate machine-local registry,
not Profile state and not Kernel state. one create observes a Workspace only
after successful creation; one serve observes the nearest manifest found by
walking up from its launch directory. Both update the XDG-aware
workspaces.json through application/workspace.RegistryService and the local
registry adapter. The registry stores only an opaque local entry id, manifest
identity, canonical root, display name, and observation timestamps. Projects,
Backend configuration, Profile values, and credentials are always read from
their authoritative stores.
The opaque local entry id is the Dashboard routing identity. Manifest
workspace.id cannot fill that role because copying a repository also copies
its manifest identity. Repeated observations of one canonical root are
idempotent; two live roots with one manifest identity remain separate and are
reported as a conflict instead of being silently re-keyed. Missing paths stay
visible until an explicit Forget operation. Plural /api/workspaces/* routes
resolve that opaque id server-side and revalidate the manifest before every
read, local Profile-binding mutation, Manifest publication, or secret mutation;
clients never submit an arbitrary filesystem root. Existing singular
/api/workspace/* routes remain pinned to
the launch Workspace for wire compatibility.
CI is an application workflow with a public provider compatibility seam:
application/ci.Serviceowns workspace/project selection, provider validation, workflow path and enabled-state detection, enable/sync/disable execution, result construction, and delete-confirmation enforcement;PlanDisableexposes only the enabled count needed for a prompt and carries its workspace snapshot privately intoDisable;Disablerechecks the current workflow files so a stale plan cannot bypass confirmation;pkg/ci.Providerremains the stable out-of-tree rendering contract, while bootstrap constructs the instance registry used by the service;- Cobra owns positional/flag conflict parsing, confirmation prompts, and TTY rendering. Workflow files remain the CI state; the manifest is not mutated.
Deployment is the reference deep module for this flow:
application/deploymentowns configured-target discovery, project/backend target planning, template compatibility, environment validation and overrides, profile resolution, secret injection ordering, pre-deploy build ordering, provider dispatch, and result publication;- target discovery and compatibility helpers stay package-private; transports
enter through the
PlanTargetsandExecuteworkflow surfaces instead of assembling deployment policy from low-level functions; ports/deploy.Providerandports/deploy.Builderare the narrow execution seams used by the workflow;- deploy adapters own vendor commands and the local project build process;
- Cobra consumes the application's ready/setup/choose-project plan and owns only flag/argument parsing, interactive project/backend/profile choices, progress presentation, and dry-run/result rendering.
This boundary is intentional: adding another deployment transport must be able to reuse the same workflow without importing Cobra, while adding another deploy backend must not modify the workflow.
Container is a vertical compiled module rather than a provider ecosystem:
docker,dockerhub,ghcr, andacrremain four user-visible Backend identities because they have different profile and registry-host policy;- all four use one Docker/OCI execution implementation, so
modules/container.Servicevalidates Catalog capabilities, resolves registry profiles, invokes the Docker adapter, and publishes image/build state back to the manifest; core/containercontains only transport-neutral values and result envelopes;- Cobra owns project selection, version prompts, flags, dry-run display, and result rendering;
- there is deliberately no
container.ProviderorProviderRegistry. A real execution seam should be introduced only when a second independent engine exists, not when another registry profile is added.
Environment is a vertical deep module because its two built-in backends are compiled implementation components rather than independently distributed plugins:
modules/environment.Serviceowns environment/backend resolution, profile resolution, project/path targeting, set planning, get/list/set/pull, backend switching, manifest bookkeeping, and create-time environment setup;- the module composes the concrete dotenv and Infisical adapters directly;
- create enters through
PrepareWorkspace; Cobra does not sequence backend sync/bind functions, and the Infisical adapter exposes no no-opSyncAPI; env setenters throughPlanSetandSet; the workspace resolution carried between those operations is private to the module;- Cobra owns value/scope confirmations, flags, spinners, and rendering;
- there is deliberately no broad
environment.Runtimeor pass-throughWorkspaceSetupport. A real extension point should be introduced only when a separately replaceable backend implementation exists.
Profile configuration uses the Backend Catalog as its identity-to-schema boundary:
- every backend declares one internal
ProfileTypeplus its JSON fields; - each Catalog field owns its stable input name, requiredness, default, placeholder, control type, and disclosure type; Cobra derives flags and interactive inputs from those fields instead of repeating Backend switches;
core/profilebuilds one typed schema-v1 section-policy table in persistedConfigfield order, validates it against every profile-bearing Catalog backend, and reuses it for CRUD, resolution, deterministic JSON emission, credential split/merge/stripping, section inspection, payload decoding, and credential-source access;- Profile definitions/defaults remain in
config.json, secrets remain incredentials.json, and legacy Workspace/Project selections inconfig.json#workspacesremain readable. The additiveprofile-bindings.jsonstore does not change either schema-v1 file orone.manifest.json; - resolution is deterministic: one-shot flag, environment-aware Project selection, environment-aware Workspace selection, legacy Project selection, legacy Workspace selection, then machine default. Environment-aware keys use canonical Workspace root rather than manifest Workspace id;
application/configureowns profile use cases, disclosure masking, and masked-secret preservation, but consumes the typedcore/profileschema API instead of registering another codec table;- HTTP masking follows fields marked
secretin the Catalog, while the stricter CLI view masks every Catalog field belowcredentials/; - application startup validates every configurable Catalog entry against the schema-v1 Go type and JSON field paths, so catalog/profile drift fails during composition instead of leaking into a request;
- kubeconfig context discovery is the only profile-form specialization in
Cobra because its choices depend on a local file; it dispatches by
ProfileType, then returns to the Catalog-driven typed decode path; - adding a backend that reuses an existing profile type does not add Configure
or profile-workflow switches. Extending the static profile-storage schema
still requires its typed
Config/CredentialsFilefields; introducing a genuinely new profile shape requires one schema policy factory.
The Dashboard loads GET /api/catalog once through an immutable SWR cache and
derives credential and project configuration fields from that response. Adding
a backend no longer requires duplicating backend lists and form switches across
the UI. features/project-settings owns the desktop project matrix, lazy
project-detail read, right-side inspector, Manifest draft inputs, and Project
Profile binding controls. features/manifest-draft keeps per-Workspace typed
patches and human-readable differences in memory; the top bar is the only
publish affordance and requires a confirmation review. Profile binding saves
remain independent machine-local writes. Backend choices and backend-specific
Project fields come from the Catalog, while the server repeats all allowlist
and compatibility validation before publication.
features/secrets manages only env/infisical values at the server-derived
Workspace or Project folder. Lists contain key names without values; a value is
retrieved individually into component-local state and every response uses
Cache-Control: no-store. The error-state retry action may call the explicit
Backend initialization endpoint to repair a missing Infisical project binding
created by an older Dashboard or a hand-edited Manifest; secret reads and
writes never auto-bind Infisical, register a Manifest key, or participate in a
Manifest draft transaction.
The router preserves ?env=dev|preview|prod across Workspace and Project links.
The global Settings page hides the selector because Profile definitions/CRUD
are machine-global rather than environment-scoped; preserved query state still
returns users to the same Workspace/Project binding namespace. The query does
not require or create a manifest environment.
features/profile-editor owns nested profile values, the Catalog-driven form,
dialog lifetime, upsert/toast behavior, and save notification. Routed pages
provide only an editor target and their own post-save cache refresh, so
Overview never imports another routed page.
Dashboard dependencies point inward: router composes pages, pages compose
features, and features may use API wrappers and shared UI primitives. Only the
router imports routed pages; features never import pages or the router. API
wrappers do not import presentation code, and components/ui remains a leaf
view layer. src/architecture/dependencies.test.ts enforces these rules and
rejects local TypeScript barrel entrypoints so imports stay directly
analyzable.
Repository verification has one public contract: task check (also exposed as
root pnpm check). It composes check:static and check:test; CI runs those
same two subtasks in parallel. task pre-push adds Go race detection without
creating a separate definition of the PR gate.
Node dependency resolution is likewise repository-owned: root
pnpm-workspace.yaml, package.json, and pnpm-lock.yaml are the only
workspace definitions for apps/*. Application packages declare their own
dependencies but do not carry a second lockfile or packageManager version.
Template packages are excluded from this rule because generated projects must
remain independently installable after leaving this repository.
Internal migration must preserve:
- current command names, flags, help contracts, and exit behavior;
one.manifest.jsonv1;~/.config/one/config.jsonandcredentials.jsonv1;- the legacy profile resolution order, extended ahead of it by optional
Project+Environment and Workspace+Environment bindings from the additive
machine-local
profile-bindings.jsonv1 store; - structured success envelopes and
one-cli/error/v1error codes; - existing public packages under
packages/cli/pkg.
New HTTP endpoints may be added. Existing read and Profile-management payloads remain compatible; historical Dashboard routes that wrote a repository are a deliberate safety exception and still return an explicit read-only error. The supported repository writes are the typed, revision-checked Project Manifest draft endpoint and the revision-checked environment Backend switch endpoint.