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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,28 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [2.1.2] - 2026-07-10

### Fixed

- Added the explicit `provider/model` bootstrap and matching secret-free
provider definition required by ZCode CLI 0.15.2, so every rendered setup can
create a desktop agent session before ZCode mounts the restored OAuth
credential.
- Moved optional API-key providers to distinct `custom:*` identities instead
of shadowing ZCode-owned `builtin:*` providers. Restored Z.ai OAuth state can
now reactivate its app-managed coding-plan provider without inheriting a
template-level `enabled: false` override.
- Made plan and apply fail closed when a setup omits its main model reference,
the referenced provider/base URL/model declaration, or assigns a custom
provider a reserved `builtin:*` identity.

### Changed

- Bumped the public build and `nddev-builder/core` component to 2.1.2. The
verified ZCode 3.3.4 application, CLI 0.15.2, runtime model, and native
artifact identity pins are unchanged.

## [2.1.1] - 2026-07-10

### Changed
Expand Down
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ changes.

- **Author:** Danil Silantyev (github:rldyourmnd), CEO NDDev
- **License:** AGPL-3.0-or-later
- **Build version:** 2.1.1
- **Build version:** 2.1.2
- **Verified ZCode runtime:** app 3.3.4, CLI 0.15.2, model GLM-5.2

## What this repository contains
Expand Down Expand Up @@ -57,10 +57,11 @@ cli-tools/scripts/install.sh status --json

Plan mode performs no writes and does not invoke a locally installed `zcode`
binary. It still parses and merges config, setting, provider, MCP, and hook
inputs and rejects unresolved active placeholders in keys or values. Setup
installation in apply mode performs a bounded live runtime probe and rejects
open task/session databases or SQLite recovery sidecars before it changes the
target.
inputs, requires the explicit CLI model/provider bootstrap expected by ZCode
0.15.2, rejects custom providers that reuse reserved `builtin:*` identities,
and rejects unresolved active placeholders in keys or values. Setup installation
in apply mode performs a bounded live runtime probe and rejects open
task/session databases or SQLite recovery sidecars before it changes the target.
See [docs/install.md](docs/install.md) for install, update, switch, backup,
restore, remove, and custom-target usage.

Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.1.1
2.1.2
8 changes: 7 additions & 1 deletion build/manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "nddev-zcode-app",
"build_version": "2.1.1",
"build_version": "2.1.2",
"source_root": "zcode_tools/marketplaces/",
"installer": "cli-tools/scripts/install.sh install --setup <id>",
"layout": {
Expand Down Expand Up @@ -55,6 +55,12 @@
"advisory_failure": "timeout, nonzero exit, excessive output, decode/runtime error, or missing executable becomes unknown/not-installed without aborting install",
"bootstrap_postcondition": "bootstrap remains strict and rejects an unknown or mismatched CLI version"
},
"model_provider_policy": {
"cli_bootstrap": "every setup declares an explicit provider/model main reference and matching provider kind, options.baseURL, and model metadata",
"oauth_boundary": "the CLI bootstrap is secret-free; ZCode supplies restored account OAuth credentials through its runtime provider registry",
"custom_provider_identity": "explicit API-key providers use custom:* identities and never reuse ZCode-owned builtin:* ids",
"validation": "plan and apply fail closed on a missing or inconsistent CLI model/provider bootstrap or a reserved builtin:* identity on a custom provider"
},
"version_policy": {
"syntax": "SemVer 2.0.0 for module, runtime, managed-stamp, adopted-envelope, and backup-name versions",
"release_lockstep": [
Expand Down
2 changes: 1 addition & 1 deletion build/version.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"build_version": "2.1.1",
"build_version": "2.1.2",
"zcode_app_version": "3.3.4",
"zcode_cli_version": "0.15.2",
"zcode_runtime": "GLM-5.2",
Expand Down
63 changes: 63 additions & 0 deletions cli-tools/scripts/lib/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1021,6 +1021,66 @@ def unresolved_values(value, path):
failures.extend(unresolved_values(item, child_path(path, key)))
return failures

def validate_cli_model_provider(value):
model = value.get("model")
if isinstance(model, str):
main = model.strip()
elif isinstance(model, dict) and isinstance(model.get("main"), str):
main = model["main"].strip()
else:
raise SystemExit("cli.model must declare an explicit main provider/model reference")

provider_id, separator, model_id = main.partition("/")
if not separator or not provider_id.strip() or not model_id.strip():
raise SystemExit("cli.model main reference must use provider/model format")

provider_map = value.get("provider")
if not isinstance(provider_map, dict):
raise SystemExit("cli.provider must be a JSON object")
provider = provider_map.get(provider_id)
if not isinstance(provider, dict):
raise SystemExit(f"cli.provider is missing the configured model provider: {provider_id}")

kind = provider.get("kind")
if kind not in {"anthropic", "openai", "openai-compatible"}:
raise SystemExit(f"cli.provider.{provider_id}.kind is unsupported")
options = provider.get("options")
if not isinstance(options, dict) or not isinstance(options.get("baseURL"), str):
raise SystemExit(f"cli.provider.{provider_id}.options.baseURL is required")
if not options["baseURL"].strip():
raise SystemExit(f"cli.provider.{provider_id}.options.baseURL must not be empty")

models = provider.get("models")
if not isinstance(models, dict):
raise SystemExit(f"cli.provider.{provider_id}.models must be a JSON object")
declared = models.get(model_id)
if declared is None:
declared = next(
(
item
for item in models.values()
if isinstance(item, dict) and item.get("id") == model_id
),
None,
)
if not isinstance(declared, dict):
raise SystemExit(
f"cli.provider.{provider_id}.models does not declare the configured model: {model_id}"
)

def validate_custom_provider_identities(value):
provider_map = value.get("provider")
if not isinstance(provider_map, dict):
raise SystemExit("provider-config.provider must be a JSON object")
for provider_id, provider in provider_map.items():
if not isinstance(provider, dict):
raise SystemExit(f"provider-config.provider.{provider_id} must be a JSON object")
if provider.get("source") == "custom" and provider_id.startswith("builtin:"):
raise SystemExit(
"custom provider identities must not reuse ZCode-owned builtin:* ids: "
+ provider_id
)

cli = substitute(load("cli-config.template.json"))
providers = substitute(load("v2-config.template.json"))
settings = substitute(load("v2-setting.template.json"))
Expand Down Expand Up @@ -1051,6 +1111,9 @@ if mcp is not None:
raise SystemExit("mcp.servers must be a JSON object")
configured.update(servers)

validate_cli_model_provider(cli)
validate_custom_provider_identities(providers)

failures = []
for root_name, value in (
("provider-config", providers),
Expand Down
7 changes: 7 additions & 0 deletions config/nddev-contract.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@
"marketplace_root": "zcode_tools/marketplaces/<name>/marketplace.json",
"plugin_manifest": "plugins/<name>/.zcode-plugin/plugin.json",
"plugin_components_convention": true,
"cli_model_reference": "model: provider/model",
"cli_provider_map": "provider",
"cli_provider_base_url": "provider.<id>.options.baseURL",
"cli_provider_model_map": "provider.<id>.models",
"custom_provider_id_prefix": "custom:",
"reserved_provider_id_prefix": "builtin:",
"plugin_mcp_key": "mcpServers",
"installed_mcp_key": "mcp.servers",
"hooks_require_enabled_flag": true,
Expand Down Expand Up @@ -63,6 +69,7 @@
"bootstrap_policy_ref": "build/manifest.json:bootstrap_policy",
"command_option_policy_ref": "build/manifest.json:command_option_policy",
"runtime_probe_policy_ref": "build/manifest.json:runtime_probe_policy",
"model_provider_policy_ref": "build/manifest.json:model_provider_policy",
"version_policy_ref": "build/manifest.json:version_policy",
"transaction_policy_ref": "build/manifest.json:transaction_policy",
"adoption_policy_ref": "build/manifest.json:adoption_policy",
Expand Down
18 changes: 14 additions & 4 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ templates never contain real credentials. Rendered `.env`, provider configs,
MCP configs, credentials, and backups are runtime secrets and remain private to
the current user.

Every CLI template declares one explicit `provider/model` main-model reference,
the matching provider kind and base URL, and the referenced model metadata.
ZCode CLI 0.15.2 requires that bootstrap contract before it can create a
desktop agent session. The bootstrap provider entry contains no credential;
ZCode mounts the restored OAuth credential through its runtime provider
registry after the CLI adapter has initialized.

The local env file is accepted only when it is a current-user-owned regular
non-symlink with no group/world permissions. Existing environment variables win
over file values. No shell expansion occurs; only `ZCODE_TARGET` and
Expand All @@ -59,6 +66,8 @@ objects in `v2/config.json` are a separate explicit API-key contract: Z.ai uses
`https://api.z.ai/api/anthropic`; BigModel uses
`https://open.bigmodel.cn/api/anthropic`. Both API-key providers are disabled
by default and must be enabled deliberately after their secret is configured.
Their `custom:*` identities never reuse ZCode-owned `builtin:*` provider IDs,
so rendering a setup cannot disable or replace the app-managed OAuth provider.

### Installer

Expand All @@ -80,10 +89,11 @@ shared libraries and execute the same lifecycle:
and selectively restore credentials, certificates, the desktop task index,
legacy session snapshots, bot definitions, CLI session databases, and
runtime artifacts into the stage,
5. reject unresolved placeholders in keys or values across active
config/setting/provider/MCP/hook branches, symlinks, special files, and
hardlink aliases; normalize private permissions, verify the complete staged
result, and fsync it before commit,
5. reject a missing or inconsistent CLI model/provider bootstrap, reserved
`builtin:*` identities on custom providers, unresolved placeholders in keys
or values across active config/setting/provider/MCP/hook branches, symlinks,
special files, and hardlink aliases; normalize private permissions, verify
the complete staged result, and fsync it before commit,
6. hold any occupied rotation slot, move the previous live target into its
backup, and atomically rename the verified stage into place,
7. roll back both the live target and held backup occupant on errors or handled
Expand Down
7 changes: 4 additions & 3 deletions docs/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,10 +277,11 @@ you stay logged in. If the auth token expired, re-authenticate in the app.

The verified preference uses Z.ai account OAuth. To use an explicit Z.ai API
key instead, populate `ZAI_API_KEY`, set that custom provider's `enabled` field
to `true`, reinstall, and select the provider backed by
to `true`, reinstall, and select `custom:zai-api-key`, which is backed by
`https://api.z.ai/api/anthropic`. BigModel API-key access remains a separate,
disabled-by-default provider backed by `https://open.bigmodel.cn/api/anthropic`;
enable it only after populating `BIGMODEL_API_KEY`.
disabled-by-default `custom:bigmodel-api-key` provider backed by
`https://open.bigmodel.cn/api/anthropic`; enable it only after populating
`BIGMODEL_API_KEY`.

### Runtime version probe

Expand Down
15 changes: 11 additions & 4 deletions docs/secrets.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,18 @@ Z.ai account authentication and explicit provider API keys are separate:
- `modelProviderFamilyModes.zai: oauth` in `v2/setting.json` is the verified
ZCode 3.3.4 default for account login. It does not require `ZAI_API_KEY`.
- `ZAI_API_KEY` configures the explicit custom Z.ai provider at
`https://api.z.ai/api/anthropic`. That provider is disabled by default; set
its `enabled` field to `true` only after supplying the key.
`https://api.z.ai/api/anthropic` under `custom:zai-api-key`. That provider is
disabled by default; set its `enabled` field to `true` only after supplying
the key.
- `BIGMODEL_API_KEY` configures the separate BigModel provider at
`https://open.bigmodel.cn/api/anthropic`; that provider is also disabled by
default and must be enabled deliberately after supplying the key.
`https://open.bigmodel.cn/api/anthropic` under
`custom:bigmodel-api-key`; that provider is also disabled by default and must
be enabled deliberately after supplying the key.

The secret-free `builtin:zai-coding-plan` entry in `cli/config.json` is only the
explicit provider/model bootstrap required by CLI 0.15.2. It does not contain
an API key and must not be repurposed as a custom provider. ZCode owns that
identity and supplies the restored OAuth credential at runtime.

Provider secrets are rendered into `v2/config.json`. MCP secrets referenced in
`mcp.json` are rendered before their entries are merged into `cli/config.json`.
Expand Down
11 changes: 8 additions & 3 deletions references/zcode-baseline.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"baseline_version": 1,
"_comment": "ZCode runtime baseline for this build. The app and CLI versions are pinned in build/version.json, compared during setup installation, and enforced as strict bootstrap postconditions. Account-mode Z.ai authentication uses the verified OAuth preference; explicit API-key providers are separate custom provider entries. Update verified_date and versions when the baseline changes.",
"baseline_version": 2,
"_comment": "ZCode runtime baseline for this build. The app and CLI versions are pinned in build/version.json, compared during setup installation, and enforced as strict bootstrap postconditions. CLI 0.15.2 requires an explicit provider/model reference and matching provider base URL before desktop session creation. Account-mode Z.ai authentication uses the verified OAuth preference; explicit API-key providers are separate custom provider entries. Update verified_date and versions when the baseline changes.",
"zcode": {
"app_version": "3.3.4",
"app_build": "3.3.4.2877",
Expand All @@ -12,7 +12,12 @@
"provider_domain_default": "zai",
"provider_kind": "anthropic",
"provider_account_auth_mode": "oauth",
"cli_model_ref": "builtin:zai-coding-plan/GLM-5.2",
"cli_model_provider_id": "builtin:zai-coding-plan",
"cli_model_provider_base_url": "https://api.z.ai/api/anthropic",
"provider_api_key_source": "custom",
"provider_zai_api_key_id": "custom:zai-api-key",
"provider_bigmodel_api_key_id": "custom:bigmodel-api-key",
"provider_zai_api_key_base_url": "https://api.z.ai/api/anthropic",
"provider_bigmodel_api_key_base_url": "https://open.bigmodel.cn/api/anthropic",
"marketplace_format": "root marketplace.json (name + owner + plugins[])",
Expand All @@ -37,5 +42,5 @@
"artifact_contract": "build/version.json:zcode_download_artifacts"
},
"verified_date": "2026-07-10",
"verified_against": "ZCode app 3.3.4 (build 3.3.4.2877), CLI 0.15.2, all six native distribution artifacts, and the task/session storage layout"
"verified_against": "ZCode app 3.3.4 (build 3.3.4.2877), CLI 0.15.2 config parser and desktop model-selection adapter, all six native distribution artifacts, and the task/session storage layout"
}
7 changes: 5 additions & 2 deletions zcode_tools/marketplaces/nddev-builder/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,11 @@ the primary workflow for changes to ZCode extension sources.
- `skills/`, `commands/`, and `agents/` hold optional user-scope components.
- `marketplaces/nddev-builder/` contains the installed marketplace and its
self-contained plugin bundles.
- `cli/config.json` contains plugin state, hooks, and MCP server definitions.
- `v2/config.json` contains explicit API-key providers.
- `cli/config.json` contains the required secret-free model/provider bootstrap,
plugin state, hooks, and MCP server definitions. ZCode supplies the restored
OAuth credential at runtime.
- `v2/config.json` contains optional explicit API-key providers under
`custom:*` identities; they never reuse ZCode-owned `builtin:*` identities.
- `v2/setting.json` contains desktop preferences, including the verified Z.ai
account OAuth mode.
- `.env`, provider configs, MCP configuration, `v2/credentials.json`, and
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
{
"_comment": "Config-file hooks shape: events live under hooks.events.<Event> (NOT hooks.<Event>). hooks.enabled must be true. See the official diagnosing-hooks skill.",
"_comment": "ZCode CLI 0.15.2 requires an explicit model reference and matching provider bootstrap. The provider entry is secret-free: ZCode mounts the restored Z.ai OAuth credential at runtime. Config-file hook events live under hooks.events.<Event> and hooks.enabled must be true.",
"model": "builtin:zai-coding-plan/GLM-5.2",
"provider": {
"builtin:zai-coding-plan": {
"name": "Z.ai - Coding Plan",
"kind": "anthropic",
"options": {
"baseURL": "https://api.z.ai/api/anthropic"
},
"models": {
"GLM-5.2": {
"limit": { "context": 1000000 },
"modalities": {
"input": ["text"],
"output": ["text"]
}
}
}
}
},
"plugins": {
"enabled": true,
"enabledPlugins": {
Expand Down
2 changes: 1 addition & 1 deletion zcode_tools/marketplaces/nddev-builder/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"name": "core",
"source": "./plugins/core",
"description": "Reusable toolkit: skills, slash commands, and a reviewer subagent for building plugins, managing MCP servers and providers, authoring skills, and listing or removing components.",
"version": "2.1.1",
"version": "2.1.2",
"author": {
"name": "Danil Silantyev (github:rldyourmnd), CEO NDDev"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "core",
"version": "2.1.1",
"version": "2.1.2",
"description": "Reusable ZCode toolkit: skills, slash commands, and a reviewer subagent for building plugins, managing MCP servers and providers, authoring skills, and listing or removing components.",
"author": {
"name": "Danil Silantyev (github:rldyourmnd), CEO NDDev",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@ Follow the `add-marketplace` skill exactly:
- `marketplace.json` → set `name` and `description`.
- `AGENTS.md` → set the `<!-- <name>:begin -->` marker and write substantive,
purpose-specific operating instructions.
- Keep `recentProjects: []`; update providers and enabled plugins
deliberately. Empty plugins/hooks/MCP are valid only when the profile
documents why workspace-specific tooling is intentional.
- Retain an explicit CLI `provider/model` bootstrap with a matching
secret-free provider/base URL/model declaration.
- Keep `recentProjects: []`; define optional API-key providers only under
`custom:*` identities, and update enabled plugins deliberately. Empty
plugins/hooks/MCP are valid only when the profile documents why
workspace-specific tooling is intentional.
4. Validate by running: `cli-tools/scripts/install.sh install --marketplace <name> --plan` — it must pass `validate_marketplace`.
5. Confirm it appears in `install.sh list`.
6. Remind to bump the build version if this is a release change.
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ Follow the `add-provider` skill exactly:

1. Ask the user for: provider name, API kind (anthropic/openai), base URL, API key env var name, model ID(s), and enabled status.
2. Add the provider entry to `v2-config.template.json` under `provider`.
Explicit API-key providers use `source: custom`. For Z.ai, use
`https://api.z.ai/api/anthropic` and preserve the separate `zai: oauth`
account preference; BigModel uses `https://open.bigmodel.cn/api/anthropic`.
Explicit API-key providers use `source: custom` and a `custom:*` identity;
they never reuse ZCode-owned `builtin:*` identities. For Z.ai, use
`https://api.z.ai/api/anthropic` and preserve both the separate `zai: oauth`
account preference and the secret-free CLI model bootstrap. BigModel uses
`https://open.bigmodel.cn/api/anthropic`.
3. Add the `${API_KEY_VAR}` placeholder to `build/.env.example` (empty value).
4. Remind the user to fill the real value in `build/.env` (gitignored).
5. Validate JSON and run `install --plan`.
5. Validate JSON and run `install --plan`; it must retain a valid explicit
`provider/model` bootstrap in `cli-config.template.json`.
Loading
Loading