Source-built ZCode CLI extension. Requires Pointman node 0.4.2 or newer
for its stream protocol, run environment, platform assets and MCP settings.
The manifest uses zcode-stream, one headless
process per wake, explicit session resume, and a separate home for each agent.
This repository has not yet published a release. Real-account runs are untested.
Run /zcode add in Pointman once on the machine and paste your Z.ai GLM
Coding Plan key into its private form. Get the key from
Z.ai's API key page. The app seals it
as zcode.key in the vault. The slash command opens a form; no extension
process receives the pasted value.
Before each agent run, the node reads that vault item and writes a private
provider file in the agent's isolated home at .zcode/v2/pointman-provider.json.
It merges client.key.settings, then inserts the key at the JSON Pointer
/config/providerConfigRules/providerRules/0/config/access/apiKey.
Only a present vault key overrides ZCODE_PERSONAL_PROVIDER_CONFIG_FILE to this
managed file. With no vault key, the node removes the managed file and uses the
ordinary isolated .zcode/v2/provider_config.json instead, preserving manual
sign-in. The key is plaintext in the private runtime file; do not share or
commit that file.
The skeleton follows the immutable upstream pin's zai-api template:
| Setting | Value |
|---|---|
| Personal instance ID | pointman-zai-coding-plan |
| Template | zai-api |
| Access type | zhipu-coding-plan-api-key |
| API type | anthropic-messages |
| Base URL | https://api.z.ai/api/anthropic |
| Default model | GLM-5.3 |
The instance ID is ours; template, access type, endpoint and model are upstream's.
It uses schema version 1, config.providerConfigRules.providerRules and
config.defaultModelSelection, plus the two empty personal model-rule arrays.
Do not use an account:* provider for a pasted key: upstream rejects personal
access overrides on those providers. Do not substitute the standard pay-as-you-go
endpoint or put the model endpoint in ZCODE_BASE_URL.
Sources at the build pin: provider templates, personal provider rules, provider data schema, file codec.
Without a vault key, run zcode login with the same isolated HOME and explicit
environment paths as that agent's client.run.env. Each agent needs its own
login; signing in under your usual shell HOME does not configure an agent.
Manual login writes .zcode/v2/provider_config.json; the node keeps this file
when removing a managed vault-key file. To use manual login again, remove the
machine's zcode.key vault item before the next run. A configured vault key
takes precedence. No real key or authenticated model call was used to develop
or test this extension.
upstream.json pins public zai-org/ZCode commit
3c5b227649371507ddc4eb46ac5133afe6db1cab.
The SHA was verified from the first line of the official
commit patch.
The upstream commit message's Source-Commit is an internal sync source, not
the public checkout SHA. Never substitute it.
CI uses Node 24.14.0 and pnpm 10.33.2, installs the frozen workspace lockfile,
then follows the upstream CLI package's build:sea dependency and bundle steps:
pnpm exec turbo --skip-infer --cwd apps/zcode-cli run build --filter=!@zcode/cli --force
pnpm --filter @zcode/cli build
pnpm --filter @zcode/cli sea --target darwin-arm64 # or linux-x64These commands run at the upstream repository root. Building only the CLI's
TypeScript is insufficient: the SEA embeds TUI assets, official plugins, bundled
skills, native search tools, Playwright assets and provider configuration.
The SEA builder locates postject in installed dependencies, creates its blob
with Node's SEA API, downloads the target Node binary, injects the blob, and
ad-hoc signs the macOS executable with codesign. Native runners avoid cross
platform native dependency mismatches. Optional browser runtime downloads remain
upstream's responsibility; headless browser use is not enabled by this manifest.
Outputs come from apps/zcode-cli/packages/cli/dist/zcode-darwin-arm64 and
apps/zcode-cli/packages/cli/dist/zcode-linux-x64, as resolved by upstream's
build-sea.mjs and sea-targets.mjs. CI uploads both archives, checksums and
upstream license notices as artifacts. A v* tag additionally creates a draft
GitHub release. Publish it only after inspecting both builds. Pointman's installer
uses GitHub's asset digest; sidecar checksums are supplementary.
The archives contain one executable with the archive's basename, plus
licenses/zcode/LICENSE, licenses/zcode/NOTICE.md, and
licenses/zcode/THIRD-PARTY-NOTICES.md, copied byte-for-byte from the pinned
checkout. They also contain this extension's licenses/pointman-zcode/LICENSE
and upstream.json. Packaging fails if any upstream notice is missing or empty.
Pointman's fetch::unpack_file selects the executable by basename and skips
other files, so these notices need no extractor change. They remain inside the
downloaded archive; the current extractor installs only the executable.
Assets are named
zcode-darwin-arm64.tar.gz and zcode-linux-x64.tar.gz. The manifest requires
node support for {platform} in release assets; the existing {triple} path
always selects musl on Linux and is unsuitable for these upstream binaries.
The embedded official Node binary uses glibc, not musl. NixOS execution needs an actual loader strategy. To meet installation without preinstalled tools, the node must provision a compatible glibc loader/library closure and launch the SEA through it, or package a Nix derivation that patches the ELF interpreter and RPATH while retaining its glibc dependencies. A documented requirement to enable nix-ld manually would not satisfy that acceptance criterion. This repository includes a source-reviewed FHS wrapper candidate below; node provisioning and pristine NixOS execution remain unverified. Do not advertise this asset as static or as a working NixOS build before that check.
The keeper must use the wake's working directory as the child process cwd.
ZCode supports --cwd, but the manifest has no cwd placeholder; the process cwd
provides the same effective directory. Arguments use --prompt, --output-format stream-json, --no-color, and --resume <sessionId> on later wakes. New runs
let ZCode allocate the session ID. There is no supported --model override here.
default, plan, acceptEdits, and auto map to ZCode's build, plan, edit, and
yolo modes. Yolo does not guarantee every tool's approval gate can be answered.
Headless approval requests normally fail through ZCode's deny broker.
The primary setup is /zcode add; isolated zcode login remains the alternative
described above. No automated sign-in check or authenticated model call is claimed.
MCP configuration merges into .zcode/cli/config.json at mcp.servers, with
features.mcp=true, preserving other provider/runtime settings. Instructions go
in .zcode/AGENTS.md. Both paths are relative to the isolated HOME.
This requires Pointman's dotted MCP keys and settings merge support.
client.run.env pins HOME, USERPROFILE and ZCODE_DATA_BASE_DIR to {home}.
The source layouts are pinned explicitly as well:
| Environment key | Value |
|---|---|
| ZCODE_STORAGE_DIR | {home}/.zcode |
| ZCODE_SESSION_DB_PATH | {home}/.zcode/cli/db/db.sqlite |
| ZCODE_SESSION_DB | {home}/.zcode/cli/db/db.sqlite |
| ZCODE_PERSONAL_PROVIDER_CONFIG_FILE | {home}/.zcode/v2/provider_config.json |
| ZCODE_LOG_DIR | {home}/.zcode/cli/log |
When a vault key is present, key delivery runs after this environment is set and
overrides the personal provider path to {home}/.zcode/v2/pointman-provider.json.
The other paths and the manual provider file remain isolated as above.
Both session database names are accepted by env-config.adapter.ts; setting
only one lets a hostile inherited alias win according to environment iteration
order. Storage and logger defaults were verified in contracts/src/config/index.ts
and adapters/src/logging/index.ts. Storage is .zcode, not .zcode/cli.
The two built-in provider override keys are set to empty strings. The CLI's
prepareCliProviderRuntimeEnv treats blank overrides as absent and materializes
its embedded provider config under the isolated data base directory. Setting
only the personal path is supported by this CLI preparation step; its later
provider runtime receives the completed pair. Do not point the built-in override
at an invented empty file. The BFS, ripgrep and ugrep override keys are also
blanked so the SEA verifies and extracts those binaries into its isolated cache.
These explicit settings must overwrite inherited environment values before
launch. Prompt runs load the first ancestor .env with override:false, so
removing inherited keys alone is insufficient: that file could restore them.
The offline contract checks exact source layouts and placeholder expansion for
two agent homes against hostile inherited values. Actual node integration still
needs tests with ancestor .env, two agents, provider setup and session resume.
nix/sea-wrapper.nix wraps an already verified SEA in Nixpkgs' existing free
Steam FHS runtime. It supplies no Steam client. This is smaller implementation
work than building a bespoke loader closure and leaves the binary named
linux-x64. The runtime footprint is broader than a custom dependency closure.
The wrapper uses (pkgs.steam.override { privateTmp = false; }).run-free, then
executes bin/steam-run <SEA> "$@". Keeping the host temporary directory visible
preserves node/MCP sockets. Nix interpolation copies the verified SEA into the
store; the caller passes its own pinned package set and keeps the output link
as a garbage collection root. For example, with real paths substituted:
nix-build --expr 'let pkgs = import /absolute/pinned/nixpkgs {}; in
import ./nix/sea-wrapper.nix {
inherit pkgs;
sea = /absolute/verified/zcode-linux-x64;
}' --out-link /node/state/runtimes/zcode-fhs
/node/state/runtimes/zcode-fhs/bin/zcode --versionThe node would provision this output on first install and launch its bin/zcode.
No system nix-ld setting or user-installed Node/pnpm is needed. Nix itself is
part of the NixOS environment. This is a source-reviewed candidate, not a
verified working install: this machine has no Nix and no Linux SEA to test.
It needs permitted unprivileged namespaces and a runtime smoke test that covers
SEA self re-exec, extracted search tools, native plugin libraries, working
folder, isolated HOME, and the Pointman MCP connection.
An interpreter patch on the SEA alone is insufficient evidence: the CLI also extracts native search tools at runtime, and native libraries/plugins may have separate loader requirements. An FHS environment applies to that whole process tree. This candidate does not enable optional browser or desktop CUA features.
Source details: storage defaults, database environment aliases, logger directory, provider preparation, SEA tool extraction.
Nix sources: buildFHSEnv manual, Steam free runtime definition, free runtime package alias.
The accompanying node change owns only node/src/clientstream/zcode.rs.
Events have a camelCase sessionId, type, and nested payload:
| ZCode event | Pointman event |
|---|---|
| session.created / session.resumed | Started |
| turn.completed response | Message |
| tool.updated scheduled / started | ToolUse |
| turn.failed with structured limit fields | Limit, error Result |
| turn.failed / unsuccessful turn.completed | error Result |
| result with response and sessionId | Result |
Reasoning, user/system messages and streaming text deltas are ignored to avoid
private text and duplicate message snapshots. Final summary response is kept
in Outcome, not emitted as another Message. Cost is unknown, not invented.
Unknown lines and malformed summaries yield no events.
The keeper must preserve raw NDJSON, require a literal valid type=result
summary, and synthesize failure on a missing summary or bad exit. It must also
latch any earlier error Result: upstream's closing summary omits resultType,
so it must not erase a preceding turn failure. Exceptions are written to stderr
with exit 1; they need not produce any JSON error event. Limits are recognized
only from structured error type/code, never arbitrary assistant/tool prose.
Run python3 -m unittest discover -s tests -v. Decoder unit tests live inside the
Rust decoder file and use upstream schema-shaped examples (not real-account
captures). The standalone test harness extracts Event and Outcome from the
parent's actual clientspec.rs without editing shared files.
The offline suite checks the vault form, key pointer through its provider-rule
array, explicit Coding Plan transport, separate manual config path, notices
inside both target archives, and failure on missing/empty notices. With the
pinned upstream checkout and frozen dependencies installed, run
python3 scripts/check-provider.py --upstream upstream to validate fake-key
settings with upstream's actual decoder and compare the template, endpoint and
model. Build CI runs this check; it makes no model calls.
Not checked here: full pnpm/SEA build (shell GitHub DNS unavailable), GitHub CI, published release install, NixOS dynamic loader compatibility, authenticated first wake/resume, MCP tool execution, and real quota failures. The task remains incomplete until integration and real-account acceptance are performed. Nix is not installed on this development Mac, so the wrapper has not been evaluated.
Sources read on 2026-10-28:
- CLI package scripts
- SEA builder and target filenames
- Event mapper and event schema
- Prompt runner
- CLI config and automation
- Runtime environment sanitizer
- BigModel failures and Start Plan failures
Except for the package script and public SHA evidence, web retrieval resolved
main snapshots; full source checkout at the pin was blocked. CI must establish
the complete build against the immutable checkout before release.