@oneciel-ai/ciel-runtime 0.2.25 → 0.2.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +3 -2
- package/ciel_runtime.py +24 -24
- package/ciel_runtime_support/cli_application_context.py +9 -0
- package/ciel_runtime_support/cli_dispatch.py +15 -3
- package/ciel_runtime_support/cli_parser.py +2 -0
- package/ciel_runtime_support/cli_usage.py +2 -1
- package/ciel_runtime_support/prelaunch.py +17 -0
- package/ciel_runtime_support/prelaunch_launch_panel.py +38 -0
- package/ciel_runtime_support/prelaunch_launch_preference.py +2 -1
- package/ciel_runtime_support/prelaunch_panel_context.py +26 -0
- package/ciel_runtime_support/prelaunch_shell_context.py +3 -3
- package/ciel_runtime_support/remote_instructions.py +2 -0
- package/ciel_runtime_support/runtime_adapters.py +11 -0
- package/ciel_runtime_support/runtime_constants.py +1 -1
- package/ciel_runtime_support/terminal_platform_io.py +1 -1
- package/ciel_runtime_support/zai_oauth.py +24 -0
- package/ciel_runtime_support/zcode_runtime_context.py +204 -0
- package/docs/CLI-Reference.md +17 -2
- package/docs/Installation.md +13 -0
- package/docs/journal/2026/08/24/zcode-runtime-oauth-windows-paste.okf +68 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,19 @@
|
|
|
3
3
|
This file records stable Ciel Runtime releases. Changes are grouped by user-visible
|
|
4
4
|
capability, followed by the complete commit ledger merged into each release.
|
|
5
5
|
|
|
6
|
+
## 0.2.26 — 2026-08-24
|
|
7
|
+
|
|
8
|
+
- Added ZCode as a first-class launch client in the interactive menu,
|
|
9
|
+
`ciel-runtime zcode`, `ciel-runtimectl launch-zcode`, and `--ca-runtime zcode`.
|
|
10
|
+
- Routes ZCode through the selected Ciel provider with a workspace-isolated
|
|
11
|
+
ZCode home and custom Anthropic-provider configuration, without overwriting
|
|
12
|
+
the user's normal ZCode configuration.
|
|
13
|
+
- Shares Z.AI OAuth Coding Plan credentials across ZCode, Claude, Codex, AGY,
|
|
14
|
+
and other routed clients; ZCode TUI OAuth results are imported after exit.
|
|
15
|
+
- Added Z.AI OAuth login/status/logout actions to the provider API-key panel.
|
|
16
|
+
- Restores Windows bracketed-paste mode on runtime exit to prevent literal
|
|
17
|
+
`ESC[200~` / `ESC[201~` markers from leaking into the parent prompt.
|
|
18
|
+
|
|
6
19
|
## 0.2.25 — 2026-08-23
|
|
7
20
|
|
|
8
21
|
- Added the ZCode wrapper's authorization-code contract as a cross-platform manual
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
**Run the coding agent you want on the model provider you choose.**
|
|
6
6
|
|
|
7
7
|
Ciel Runtime is a cross-platform launcher, local protocol router, and workspace
|
|
8
|
-
control plane for Claude Code, Codex, Codex App Server, AGY, Grok Build, and
|
|
8
|
+
control plane for Claude Code, Codex, Codex App Server, AGY, Grok Build, ZCode, and
|
|
9
9
|
compatible AI coding-agent CLIs.
|
|
10
10
|
|
|
11
11
|
[](https://www.npmjs.com/package/@oneciel-ai/ciel-runtime)
|
|
@@ -76,6 +76,7 @@ Launch another runtime while preserving its remaining CLI arguments:
|
|
|
76
76
|
ciel-runtime codex
|
|
77
77
|
ciel-runtime agy
|
|
78
78
|
ciel-runtime grok
|
|
79
|
+
ciel-runtime zcode
|
|
79
80
|
ciel-runtime --ca-runtime codex-app-server
|
|
80
81
|
```
|
|
81
82
|
|
|
@@ -91,7 +92,7 @@ ciel-runtimectl test
|
|
|
91
92
|
|
|
92
93
|
| Capability | What it provides |
|
|
93
94
|
|---|---|
|
|
94
|
-
| Runtime launch | Claude Code, Codex, Codex App Server, AGY,
|
|
95
|
+
| Runtime launch | Claude Code, Codex, Codex App Server, AGY, Grok Build, and ZCode from one entrypoint |
|
|
95
96
|
| Provider routing | Native connections where supported; otherwise a loopback HTTP router with provider-owned endpoint and authentication rules |
|
|
96
97
|
| Protocol adaptation | Anthropic Messages, OpenAI Chat, OpenAI Responses, Ollama Chat, tool calls, thinking blocks, and SSE streams |
|
|
97
98
|
| Model control | Provider catalogs, context/output limits, reasoning effort, sampling options, API-key rotation, and rate-limit handling |
|
package/ciel_runtime.py
CHANGED
|
@@ -23,6 +23,7 @@ from ciel_runtime_support import channel_cursor_repository as channel_cursor_sto
|
|
|
23
23
|
from ciel_runtime_support import channel_llm_context, claude_launch_assembly, cli_assembly, cli_dispatch, cli_parser, codex_launch_configuration, codex_turn_recovery, kimi_identity, llm_option_config, llm_presets, native_context_recovery
|
|
24
24
|
from ciel_runtime_support import ollama_catalog as ollama_catalog_policy
|
|
25
25
|
from ciel_runtime_support import prelaunch, prelaunch_assembly, provider_catalog_sources, provider_models, provider_network, rate_limit_policy, router_request_assembly, router_server_runtime, runtime_asset_assembly, runtime_launch, runtime_primitives, terminal_platform_io, windows_console_mode
|
|
26
|
+
from ciel_runtime_support.prelaunch_launch_panel import launch_panel_rows as project_launch_panel_rows
|
|
26
27
|
from ciel_runtime_support.advisor_client import AdvisorClient, AdvisorClientIO, AdvisorClientPolicy, ProviderChatExecutor, ProviderChatIO, ProviderChatPolicy
|
|
27
28
|
from ciel_runtime_support.advisor_policy import AdvisorDecisionServices, AdvisorServices, AdvisorShortcutController, AdvisorShortcutPorts, AdvisorTextServices
|
|
28
29
|
from ciel_runtime_support.advisor_policy import advisor_focus_for_message as project_advisor_focus
|
|
@@ -172,6 +173,7 @@ from ciel_runtime_support.zai_oauth import ZaiOAuthClient, ZaiOAuthHttp, ZaiOAut
|
|
|
172
173
|
from ciel_runtime_support.headless_config import HeadlessConfigCommands, HeadlessConfigServices, HeadlessEnvFileLoader, apply_headless_config
|
|
173
174
|
from ciel_runtime_support.http_response import ChannelDeliveryGuard, HttpResponseAdapter
|
|
174
175
|
from ciel_runtime_support.kimi_runtime_context import KimiConfigurationPorts, KimiIdentityPorts, KimiLifecyclePorts, KimiProcessPorts, KimiRuntimeCompatibilityApi, KimiRuntimeContext
|
|
176
|
+
from ciel_runtime_support.zcode_runtime_context import ZcodeConfigurationPorts, ZcodeLifecyclePorts, ZcodeProcessPorts, ZcodeRuntimeCompatibilityApi, ZcodeRuntimeContext
|
|
175
177
|
from ciel_runtime_support.launch_diagnostics import LaunchCommandDiagnostics, StderrCaptureAdapter
|
|
176
178
|
from ciel_runtime_support.launch_state import LaunchStateRepository
|
|
177
179
|
from ciel_runtime_support.launch_state import current_launch_cwd_key as project_current_launch_cwd_key
|
|
@@ -4006,6 +4008,21 @@ run_kimi_oauth_login = _KIMI_RUNTIME_API.oauth_login
|
|
|
4006
4008
|
run_kimi_oauth_action = _KIMI_RUNTIME_API.oauth_action
|
|
4007
4009
|
launch_kimi = _KIMI_RUNTIME_API.launch
|
|
4008
4010
|
launch_kimi = SynchronizedLaunch(launch_kimi, sync_remote_launch_assets, "kimi")
|
|
4011
|
+
|
|
4012
|
+
def zcode_runtime_context() -> ZcodeRuntimeContext:
|
|
4013
|
+
return ZcodeRuntimeContext(
|
|
4014
|
+
process=ZcodeProcessPorts(find_executable, subprocess.run, subprocess.call, print, os.environ, path_with_ciel_runtime_user_dirs),
|
|
4015
|
+
config=ZcodeConfigurationPorts(load_config, get_current_provider, current_alias, claude_code_router_auth_token, ROUTER_BASE, ZAI_ANTHROPIC_BASE_URL,
|
|
4016
|
+
WORKSPACE_STATE_DIR / "zcode-home" / ".zcode" / "cli" / "config.json",
|
|
4017
|
+
lambda path, data, purpose: json_artifact_repository(path).save(data, purpose), lambda key: zai_oauth_runtime().import_api_key(key, source="zcode")),
|
|
4018
|
+
lifecycle=ZcodeLifecyclePorts(run_zai_oauth_action, start_router_if_needed, run_with_router_lifetime, materialize_runtime_command,
|
|
4019
|
+
lambda provider, model: record_launch_state_for_cwd(current_launch_cwd_key(), provider, "zcode-routed", model)),
|
|
4020
|
+
)
|
|
4021
|
+
|
|
4022
|
+
_ZCODE_RUNTIME_API = ZcodeRuntimeCompatibilityApi(zcode_runtime_context)
|
|
4023
|
+
def install_zcode_if_missing() -> str: return _ZCODE_RUNTIME_API.install_if_missing()
|
|
4024
|
+
def launch_zcode(passthrough: list[str] | None = None, **_kwargs: Any) -> int: return _ZCODE_RUNTIME_API.launch(list(passthrough or []))
|
|
4025
|
+
launch_zcode = SynchronizedLaunch(launch_zcode, sync_remote_launch_assets, "zcode")
|
|
4009
4026
|
enable_ansi = enable_terminal_ansi
|
|
4010
4027
|
ansi = render_ansi
|
|
4011
4028
|
animated_ansi_text = render_animated_ansi_text
|
|
@@ -4064,25 +4081,7 @@ prompt_menu_value = _PRELAUNCH_SHELL_API.prompt_menu_value
|
|
|
4064
4081
|
_prompt_menu_multiline_value_raw = _PRELAUNCH_SHELL_API.prompt_menu_multiline_value_raw
|
|
4065
4082
|
prompt_menu_multiline_value = _PRELAUNCH_SHELL_API.prompt_menu_multiline_value
|
|
4066
4083
|
|
|
4067
|
-
def launch_panel_rows(cfg: dict[str, Any]) -> tuple[list[str], list[str]]:
|
|
4068
|
-
provider, pcfg = get_current_provider(cfg)
|
|
4069
|
-
family = provider_menu_label(provider, pcfg)
|
|
4070
|
-
claude_suffix = "" if claude_launch_enabled_for_provider(provider) else f" [disabled: {family} provider selected]"
|
|
4071
|
-
codex_suffix = "" if codex_launch_enabled_for_provider(provider) else f" [disabled: {family} provider selected]"
|
|
4072
|
-
agy_suffix = "" if agy_launch_enabled_for_provider(provider) else " [disabled: select AGY provider]"
|
|
4073
|
-
kimi_suffix = "" if provider == "kimi" else " [disabled: select Kimi provider]"
|
|
4074
|
-
return (
|
|
4075
|
-
[
|
|
4076
|
-
f"Claude{claude_suffix}",
|
|
4077
|
-
f"Codex{codex_suffix}",
|
|
4078
|
-
f"AGY{agy_suffix}",
|
|
4079
|
-
f"Kimi{kimi_suffix}",
|
|
4080
|
-
"Grok Build",
|
|
4081
|
-
f"Codex app server{codex_suffix}",
|
|
4082
|
-
"Back",
|
|
4083
|
-
],
|
|
4084
|
-
["launch", "launch-codex", "launch-agy", "launch-kimi", "launch-grok", "launch-codex-app-server", "back"],
|
|
4085
|
-
)
|
|
4084
|
+
def launch_panel_rows(cfg: dict[str, Any]) -> tuple[list[str], list[str]]: return project_launch_panel_rows(cfg, current_provider=get_current_provider, provider_label=provider_menu_label, claude_enabled=claude_launch_enabled_for_provider, codex_enabled=codex_launch_enabled_for_provider, agy_enabled=agy_launch_enabled_for_provider)
|
|
4086
4085
|
|
|
4087
4086
|
def request_limits_menu_service() -> RequestLimitsMenuService:
|
|
4088
4087
|
return RequestLimitsMenuService(load_config, save_config, ROUTER_WORKSPACE, os.environ)
|
|
@@ -4136,14 +4135,14 @@ def portable_prelaunch_menu(passthrough: list[str] | None = None) -> int:
|
|
|
4136
4135
|
config=prelaunch.PrelaunchConfig(clear_model_cache, current_provider_panel_choice, default_base_url, get_current_provider, load_config, preflight_lines,
|
|
4137
4136
|
provider_menu_label, save_config, settings_ready_except_api_key, read_model_list_cache),
|
|
4138
4137
|
launch_policy=prelaunch.PrelaunchLaunchPolicy(agy_launch_enabled_for_provider, claude_launch_enabled_for_provider, codex_launch_enabled_for_provider,
|
|
4139
|
-
launch_blockers_require_api_key, launch_readiness_errors, launch_kimi, launch_grok),
|
|
4138
|
+
launch_blockers_require_api_key, launch_readiness_errors, launch_kimi, launch_grok, launch_zcode),
|
|
4140
4139
|
panel_rows=prelaunch.PrelaunchPanelRows(advisor_model_panel_rows, api_key_panel_rows, base_url_panel_rows, context_setup_panel_rows, language_panel_rows,
|
|
4141
4140
|
llm_option_panel_rows, llm_preset_panel_rows, log_level_panel_rows, model_panel_rows, provider_panel_rows),
|
|
4142
4141
|
mutations=prelaunch.PrelaunchMutations(apply_context_setup_config, apply_llm_preset_config, apply_timeout_profile_to_provider,
|
|
4143
4142
|
set_advisor_model_config, set_base_url_config, set_llm_option_config, set_log_level_config,
|
|
4144
4143
|
set_model_config, set_provider_choice_config, request_limits_menu_service()),
|
|
4145
4144
|
secrets=prelaunch.PrelaunchSecrets(clear_api_key_config, mask_secret, parse_api_key_list, secret_fingerprint, store_api_key_input_config,
|
|
4146
|
-
store_api_keys_config, run_copilot_oauth_action, run_kimi_oauth_action),
|
|
4145
|
+
store_api_keys_config, run_copilot_oauth_action, run_kimi_oauth_action, run_zai_oauth_action),
|
|
4147
4146
|
options=prelaunch.PrelaunchOptions(llm_option_current_bool, llm_option_prompt_default, timeout_profile_panel_rows, web_backend_panel_rows,
|
|
4148
4147
|
set_web_backend_config, external_event_panel_rows, set_external_event_config,
|
|
4149
4148
|
launch_panel_rows, remote_instruction_panel_rows, set_remote_instruction_config,
|
|
@@ -4939,7 +4938,7 @@ def cli_services() -> cli_dispatch.CliServices:
|
|
|
4939
4938
|
core=cli_dispatch.CliCore(VERSION, cli_usage, find_executable, get_current_provider, load_config, pop_headless_env_file_args,
|
|
4940
4939
|
portable_provider_menu, run_external_menu, run_quiet_upgrade_and_exit),
|
|
4941
4940
|
runtime=cli_dispatch.CliRuntime(agy_passthrough_has_command, codex_passthrough_has_command, last_launch_runtime, launch_agy, launch_claude,
|
|
4942
|
-
launch_codex, launch_codex_app_server, native_agy_enabled, native_codex_enabled, launch_grok),
|
|
4941
|
+
launch_codex, launch_codex_app_server, native_agy_enabled, native_codex_enabled, launch_grok, launch_zcode),
|
|
4943
4942
|
provider_commands=cli_dispatch.CliProviderCommands(cmd_advisor_model, cmd_api_key, cmd_base_url, cmd_language, cmd_log_level, cmd_model,
|
|
4944
4943
|
cmd_models, cmd_provider, cmd_provider_options, cmd_set_api_key),
|
|
4945
4944
|
special_commands=cli_dispatch.CliSpecialCommands(cmd_ollama_catalog, cmd_ollama_native, cmd_ollama_options, cmd_web_fetch, cmd_web_search),
|
|
@@ -4949,7 +4948,7 @@ def cli_services() -> cli_dispatch.CliServices:
|
|
|
4949
4948
|
).services()
|
|
4950
4949
|
def cli_parser_services() -> cli_parser.CliParserServices:
|
|
4951
4950
|
return cli_assembly.CliParserAssembly(
|
|
4952
|
-
launch=cli_parser.CliParserLaunch(cmd_cli, cmd_launch, cmd_launch_codex, cmd_launch_codex_app_server, cmd_launch_agy, serve, cmd_launch_grok),
|
|
4951
|
+
launch=cli_parser.CliParserLaunch(cmd_cli, cmd_launch, cmd_launch_codex, cmd_launch_codex_app_server, cmd_launch_agy, serve, cmd_launch_grok, cmd_launch_zcode),
|
|
4953
4952
|
runtime=cli_parser.CliParserRuntime(cmd_version, cmd_status, cmd_env, cmd_stop, cmd_test),
|
|
4954
4953
|
settings=cli_parser.CliParserSettings(cmd_language, cmd_web_search, cmd_web_fetch, cmd_log_level, *event_settings_cli.handlers(event_settings_cli.EventSettingsCliPorts(load_config, save_config, external_event_receiver_service, lambda: set_remote_instruction_config('sync', ''), sync_all_remote_memories, print, lambda: USAGE_API_KEYS))),
|
|
4955
4954
|
provider=cli_parser.CliParserProvider(cmd_ollama_native, cmd_ollama_options, cmd_provider_options, cmd_ollama_catalog, cmd_provider,
|
|
@@ -4960,7 +4959,7 @@ def cli_parser_services() -> cli_parser.CliParserServices:
|
|
|
4960
4959
|
def cli_application_context() -> CliApplicationContext:
|
|
4961
4960
|
return CliApplicationContext(
|
|
4962
4961
|
dispatch=CliApplicationDispatchPorts(dispatch_cli, cli_services, launch_claude, launch_codex, launch_codex_app_server,
|
|
4963
|
-
launch_agy, launch_kimi, run_kimi_oauth_login, launch_grok),
|
|
4962
|
+
launch_agy, launch_kimi, run_kimi_oauth_login, launch_grok, launch_zcode),
|
|
4964
4963
|
presentation=CliApplicationPresentationPorts(build_cli_parser, cli_parser_services, VERSION, print, lambda: sys.argv),
|
|
4965
4964
|
)
|
|
4966
4965
|
|
|
@@ -4973,6 +4972,7 @@ cmd_launch_codex = _CLI_APPLICATION_API.cmd_launch_codex
|
|
|
4973
4972
|
cmd_launch_codex_app_server = _CLI_APPLICATION_API.cmd_launch_codex_app_server
|
|
4974
4973
|
cmd_launch_agy = _CLI_APPLICATION_API.cmd_launch_agy
|
|
4975
4974
|
cmd_launch_grok = _CLI_APPLICATION_API.cmd_launch_grok
|
|
4975
|
+
cmd_launch_zcode = _CLI_APPLICATION_API.cmd_launch_zcode
|
|
4976
4976
|
cmd_version = _CLI_APPLICATION_API.cmd_version
|
|
4977
4977
|
main = _CLI_APPLICATION_API.main
|
|
4978
4978
|
|
|
@@ -18,6 +18,7 @@ class CliApplicationDispatchPorts:
|
|
|
18
18
|
launch_kimi: Callable[[list[str]], int]
|
|
19
19
|
kimi_login: Callable[[], int]
|
|
20
20
|
launch_grok: Callable[[list[str]], int] = lambda _argv: 127
|
|
21
|
+
launch_zcode: Callable[[list[str]], int] = lambda _argv: 127
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
@dataclass(frozen=True, slots=True)
|
|
@@ -60,6 +61,9 @@ class CliApplicationContext:
|
|
|
60
61
|
def cmd_launch_grok(self, args: argparse.Namespace) -> None:
|
|
61
62
|
raise SystemExit(self.dispatch.launch_grok(args.argv))
|
|
62
63
|
|
|
64
|
+
def cmd_launch_zcode(self, args: argparse.Namespace) -> None:
|
|
65
|
+
raise SystemExit(self.dispatch.launch_zcode(args.argv))
|
|
66
|
+
|
|
63
67
|
def cmd_version(self, _: argparse.Namespace) -> None:
|
|
64
68
|
self.presentation.output(f"ciel-runtime {self.presentation.version}")
|
|
65
69
|
|
|
@@ -84,6 +88,8 @@ class CliApplicationContext:
|
|
|
84
88
|
"grok": self.dispatch.launch_grok,
|
|
85
89
|
"grok-build": self.dispatch.launch_grok,
|
|
86
90
|
"launch-grok": self.dispatch.launch_grok,
|
|
91
|
+
"zcode": self.dispatch.launch_zcode,
|
|
92
|
+
"launch-zcode": self.dispatch.launch_zcode,
|
|
87
93
|
}
|
|
88
94
|
if len(arguments) >= 2 and arguments[1] in routes:
|
|
89
95
|
raise SystemExit(routes[arguments[1]](arguments[2:]))
|
|
@@ -124,6 +130,9 @@ class CliApplicationCompatibilityApi:
|
|
|
124
130
|
def cmd_launch_grok(self, args: argparse.Namespace) -> None:
|
|
125
131
|
self.context().cmd_launch_grok(args)
|
|
126
132
|
|
|
133
|
+
def cmd_launch_zcode(self, args: argparse.Namespace) -> None:
|
|
134
|
+
self.context().cmd_launch_zcode(args)
|
|
135
|
+
|
|
127
136
|
def cmd_version(self, args: argparse.Namespace) -> None:
|
|
128
137
|
self.context().cmd_version(args)
|
|
129
138
|
|
|
@@ -34,6 +34,7 @@ class CliRuntime:
|
|
|
34
34
|
native_agy_enabled: Any
|
|
35
35
|
native_codex_enabled: Any
|
|
36
36
|
launch_grok: Any = lambda *_args, **_kwargs: 127
|
|
37
|
+
launch_zcode: Any = lambda *_args, **_kwargs: 127
|
|
37
38
|
|
|
38
39
|
|
|
39
40
|
@dataclass(frozen=True)
|
|
@@ -119,6 +120,7 @@ def dispatch_cli(argv: list[str], services: CliServices) -> int:
|
|
|
119
120
|
launch_codex = services.runtime.launch_codex
|
|
120
121
|
launch_codex_app_server = services.runtime.launch_codex_app_server
|
|
121
122
|
launch_grok = services.runtime.launch_grok
|
|
123
|
+
launch_zcode = services.runtime.launch_zcode
|
|
122
124
|
load_config = services.core.load_config
|
|
123
125
|
native_agy_enabled = services.runtime.native_agy_enabled
|
|
124
126
|
native_codex_enabled = services.runtime.native_codex_enabled
|
|
@@ -142,6 +144,8 @@ def dispatch_cli(argv: list[str], services: CliServices) -> int:
|
|
|
142
144
|
return launch_codex(rest)
|
|
143
145
|
if head in ("grok", "grok-build", "launch-grok"):
|
|
144
146
|
return launch_grok(rest)
|
|
147
|
+
if head in ("zcode", "launch-zcode"):
|
|
148
|
+
return launch_zcode(rest)
|
|
145
149
|
if head in ("version", "--version", "-v"):
|
|
146
150
|
print(f"ciel-runtime {VERSION}")
|
|
147
151
|
return 0
|
|
@@ -295,8 +299,8 @@ def dispatch_cli(argv: list[str], services: CliServices) -> int:
|
|
|
295
299
|
runtime = str(value or "").strip().lower()
|
|
296
300
|
if runtime in ("codex-app", "codex-appserver"):
|
|
297
301
|
runtime = "codex-app-server"
|
|
298
|
-
if runtime not in ("claude", "codex", "codex-app-server", "agy", "grok", "last"):
|
|
299
|
-
raise SystemExit("--ca-runtime must be claude, codex, codex-app-server, agy, grok, or last")
|
|
302
|
+
if runtime not in ("claude", "codex", "codex-app-server", "agy", "grok", "zcode", "last"):
|
|
303
|
+
raise SystemExit("--ca-runtime must be claude, codex, codex-app-server, agy, grok, zcode, or last")
|
|
300
304
|
elif arg in ("--ca-no-launch", "--ca-configure-only", "--ca-setup-only"):
|
|
301
305
|
configure_only = True
|
|
302
306
|
skip_menu = True
|
|
@@ -667,7 +671,7 @@ def dispatch_cli(argv: list[str], services: CliServices) -> int:
|
|
|
667
671
|
return 0
|
|
668
672
|
if runtime == "last":
|
|
669
673
|
remembered_runtime = str(last_launch_runtime() or "").strip().lower()
|
|
670
|
-
runtime = remembered_runtime if remembered_runtime in {"claude", "codex", "agy", "grok"} else "claude"
|
|
674
|
+
runtime = remembered_runtime if remembered_runtime in {"claude", "codex", "agy", "grok", "zcode"} else "claude"
|
|
671
675
|
if runtime == "agy":
|
|
672
676
|
return launch_agy(
|
|
673
677
|
passthrough,
|
|
@@ -700,6 +704,14 @@ def dispatch_cli(argv: list[str], services: CliServices) -> int:
|
|
|
700
704
|
update_check=update_check,
|
|
701
705
|
self_update_check=self_update_check,
|
|
702
706
|
)
|
|
707
|
+
if runtime == "zcode":
|
|
708
|
+
return launch_zcode(
|
|
709
|
+
passthrough,
|
|
710
|
+
skip_menu=skip_menu,
|
|
711
|
+
force_menu=force_menu,
|
|
712
|
+
update_check=update_check,
|
|
713
|
+
self_update_check=self_update_check,
|
|
714
|
+
)
|
|
703
715
|
return launch_claude(
|
|
704
716
|
passthrough,
|
|
705
717
|
skip_menu=skip_menu,
|
|
@@ -16,6 +16,7 @@ class CliParserLaunch:
|
|
|
16
16
|
launch_agy: CliHandler
|
|
17
17
|
serve: CliHandler
|
|
18
18
|
launch_grok: CliHandler = lambda _args: None
|
|
19
|
+
launch_zcode: CliHandler = lambda _args: None
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
@dataclass(frozen=True)
|
|
@@ -84,6 +85,7 @@ def build_cli_parser(services: CliParserServices) -> argparse.ArgumentParser:
|
|
|
84
85
|
)
|
|
85
86
|
_add_remainder_command(commands, "launch-agy", services.launch.launch_agy)
|
|
86
87
|
_add_remainder_command(commands, "launch-grok", services.launch.launch_grok)
|
|
88
|
+
_add_remainder_command(commands, "launch-zcode", services.launch.launch_zcode)
|
|
87
89
|
commands.add_parser("serve").set_defaults(func=services.launch.serve)
|
|
88
90
|
commands.add_parser("version").set_defaults(func=services.runtime.version)
|
|
89
91
|
commands.add_parser("status").set_defaults(func=services.runtime.status)
|
|
@@ -7,6 +7,7 @@ def cli_usage_text() -> str:
|
|
|
7
7
|
ciel-runtime codex-app-server [args...] Launch Codex app-server through ciel-runtime
|
|
8
8
|
ciel-runtime agy [args...] Launch Google Antigravity CLI through ciel-runtime
|
|
9
9
|
ciel-runtime grok [args...] Launch the official Grok Build CLI
|
|
10
|
+
ciel-runtime zcode [args...] Launch ZCode through ciel-runtime router
|
|
10
11
|
ciel-runtime resume Resume Codex/AGY when that runtime provider is selected
|
|
11
12
|
|
|
12
13
|
Control plane, runs before Claude Code and does not require LLM connectivity:
|
|
@@ -42,7 +43,7 @@ Headless setup flags, namespaced to avoid Claude CLI collisions:
|
|
|
42
43
|
Publish localhost through Tailscale Serve HTTPS
|
|
43
44
|
ciel-runtime --ca-provider PROVIDER Set provider, then launch
|
|
44
45
|
ciel-runtime --ca-env-file PATH Load CIEL_RUNTIME_* values from a .env file
|
|
45
|
-
ciel-runtime --ca-runtime claude|codex|codex-app-server|agy|grok
|
|
46
|
+
ciel-runtime --ca-runtime claude|codex|codex-app-server|agy|grok|zcode
|
|
46
47
|
Select Claude Code, Codex, Codex app-server, AGY, or Grok Build
|
|
47
48
|
ciel-runtime --ca-menu Apply setup values, then open the menu
|
|
48
49
|
ciel-runtime --ca-language en|ko|ja|zh
|
|
@@ -81,6 +81,7 @@ class PrelaunchLaunchPolicy:
|
|
|
81
81
|
launch_readiness_errors: Callable[..., Any]
|
|
82
82
|
launch_kimi: Callable[..., Any]
|
|
83
83
|
launch_grok: Callable[..., Any] = lambda *_args, **_kwargs: 0
|
|
84
|
+
launch_zcode: Callable[..., Any] = lambda *_args, **_kwargs: 0
|
|
84
85
|
|
|
85
86
|
|
|
86
87
|
@dataclass(frozen=True, slots=True)
|
|
@@ -135,6 +136,7 @@ class PrelaunchSecrets:
|
|
|
135
136
|
store_api_keys_config: Callable[..., Any]
|
|
136
137
|
copilot_oauth_action: Callable[..., Any]
|
|
137
138
|
kimi_oauth_action: Callable[..., Any]
|
|
139
|
+
zai_oauth_action: Callable[..., Any] = lambda _action: []
|
|
138
140
|
|
|
139
141
|
|
|
140
142
|
@dataclass(frozen=True, slots=True)
|
|
@@ -213,6 +215,7 @@ def run_prelaunch_menu(passthrough: list[str] | None = None,
|
|
|
213
215
|
launch_readiness_errors = services.launch_policy.launch_readiness_errors
|
|
214
216
|
launch_kimi = services.launch_policy.launch_kimi
|
|
215
217
|
launch_grok = services.launch_policy.launch_grok
|
|
218
|
+
launch_zcode = services.launch_policy.launch_zcode
|
|
216
219
|
llm_option_current_bool = services.options.llm_option_current_bool
|
|
217
220
|
llm_option_panel_rows = services.panel_rows.llm_option_panel_rows
|
|
218
221
|
llm_option_prompt_default = services.options.llm_option_prompt_default
|
|
@@ -247,6 +250,7 @@ def run_prelaunch_menu(passthrough: list[str] | None = None,
|
|
|
247
250
|
store_api_keys_config = services.secrets.store_api_keys_config
|
|
248
251
|
copilot_oauth_action = services.secrets.copilot_oauth_action
|
|
249
252
|
kimi_oauth_action = services.secrets.kimi_oauth_action
|
|
253
|
+
zai_oauth_action = services.secrets.zai_oauth_action
|
|
250
254
|
timeout_profile_panel_rows = services.options.timeout_profile_panel_rows
|
|
251
255
|
web_backend_panel_rows = services.options.web_backend_panel_rows
|
|
252
256
|
set_web_backend_config = services.options.set_web_backend_config
|
|
@@ -485,6 +489,10 @@ def run_prelaunch_menu(passthrough: list[str] | None = None,
|
|
|
485
489
|
persist_launch_action(action)
|
|
486
490
|
launch_grok([])
|
|
487
491
|
return PRELAUNCH_CANCEL
|
|
492
|
+
if action == "launch-zcode":
|
|
493
|
+
persist_launch_action(action)
|
|
494
|
+
launch_zcode([])
|
|
495
|
+
return PRELAUNCH_CANCEL
|
|
488
496
|
continue
|
|
489
497
|
if panel == "language" and value:
|
|
490
498
|
cfg["language"] = value
|
|
@@ -561,6 +569,15 @@ def run_prelaunch_menu(passthrough: list[str] | None = None,
|
|
|
561
569
|
provider, pcfg = get_current_provider(cfg)
|
|
562
570
|
panel_rows, panel_values = api_key_panel_rows(provider, pcfg)
|
|
563
571
|
panel_idx = 0
|
|
572
|
+
elif value.startswith("zai-oauth-"):
|
|
573
|
+
messages = zai_oauth_action(
|
|
574
|
+
value.removeprefix("zai-oauth-")
|
|
575
|
+
)
|
|
576
|
+
refresh_checks()
|
|
577
|
+
cfg = load_config()
|
|
578
|
+
provider, pcfg = get_current_provider(cfg)
|
|
579
|
+
panel_rows, panel_values = api_key_panel_rows(provider, pcfg)
|
|
580
|
+
panel_idx = 0
|
|
564
581
|
elif value.startswith("oauth-"):
|
|
565
582
|
messages = copilot_oauth_action(
|
|
566
583
|
value.removeprefix("oauth-")
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Runtime launch chooser projection."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Callable
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def launch_panel_rows(
|
|
9
|
+
config: dict[str, Any],
|
|
10
|
+
*,
|
|
11
|
+
current_provider: Callable[..., tuple[str, dict[str, Any]]],
|
|
12
|
+
provider_label: Callable[..., str],
|
|
13
|
+
claude_enabled: Callable[[str], bool],
|
|
14
|
+
codex_enabled: Callable[[str], bool],
|
|
15
|
+
agy_enabled: Callable[[str], bool],
|
|
16
|
+
) -> tuple[list[str], list[str]]:
|
|
17
|
+
provider, provider_config = current_provider(config)
|
|
18
|
+
family = provider_label(provider, provider_config)
|
|
19
|
+
claude_suffix = "" if claude_enabled(provider) else f" [disabled: {family} provider selected]"
|
|
20
|
+
codex_suffix = "" if codex_enabled(provider) else f" [disabled: {family} provider selected]"
|
|
21
|
+
agy_suffix = "" if agy_enabled(provider) else " [disabled: select AGY provider]"
|
|
22
|
+
kimi_suffix = "" if provider == "kimi" else " [disabled: select Kimi provider]"
|
|
23
|
+
return (
|
|
24
|
+
[
|
|
25
|
+
f"Claude{claude_suffix}",
|
|
26
|
+
f"Codex{codex_suffix}",
|
|
27
|
+
f"AGY{agy_suffix}",
|
|
28
|
+
f"Kimi{kimi_suffix}",
|
|
29
|
+
"Grok Build",
|
|
30
|
+
"ZCode",
|
|
31
|
+
f"Codex app server{codex_suffix}",
|
|
32
|
+
"Back",
|
|
33
|
+
],
|
|
34
|
+
["launch", "launch-codex", "launch-agy", "launch-kimi", "launch-grok", "launch-zcode", "launch-codex-app-server", "back"],
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
__all__ = ["launch_panel_rows"]
|
|
@@ -15,6 +15,7 @@ REMEMBERED_LAUNCH_ACTIONS = frozenset(
|
|
|
15
15
|
"launch-codex-app-server",
|
|
16
16
|
"launch-kimi",
|
|
17
17
|
"launch-grok",
|
|
18
|
+
"launch-zcode",
|
|
18
19
|
}
|
|
19
20
|
)
|
|
20
21
|
|
|
@@ -33,7 +34,7 @@ def preferred_launch_action(
|
|
|
33
34
|
return remembered
|
|
34
35
|
if remembered in {"launch-codex", "launch-codex-app-server"} and supports_codex(provider):
|
|
35
36
|
return remembered
|
|
36
|
-
if remembered
|
|
37
|
+
if remembered in {"launch-grok", "launch-zcode"}:
|
|
37
38
|
return remembered
|
|
38
39
|
return fallback(provider)
|
|
39
40
|
|
|
@@ -247,6 +247,32 @@ class PrelaunchPanelContext:
|
|
|
247
247
|
def api_key_panel_rows(
|
|
248
248
|
self, provider: str, provider_config: dict[str, Any] | None = None
|
|
249
249
|
) -> PanelRows:
|
|
250
|
+
if provider == "zai":
|
|
251
|
+
config = provider_config or {}
|
|
252
|
+
source = str(config.get("credential_source") or "").strip()
|
|
253
|
+
configured = bool(self.configuration.api_key_count(provider, config))
|
|
254
|
+
status = "connected" if configured and source == "zai-oauth" else (
|
|
255
|
+
"API key configured" if configured else "login required"
|
|
256
|
+
)
|
|
257
|
+
rows, values = self.configuration_panel_projection().api_key_rows(
|
|
258
|
+
provider, provider_config
|
|
259
|
+
)
|
|
260
|
+
return (
|
|
261
|
+
[
|
|
262
|
+
f"Z.AI OAuth: {status}",
|
|
263
|
+
"Login with Z.AI OAuth (shared by all runtimes)",
|
|
264
|
+
"Refresh Z.AI OAuth status",
|
|
265
|
+
"Logout shared Z.AI OAuth credential",
|
|
266
|
+
*rows,
|
|
267
|
+
],
|
|
268
|
+
[
|
|
269
|
+
"__info__",
|
|
270
|
+
"zai-oauth-login",
|
|
271
|
+
"zai-oauth-status",
|
|
272
|
+
"zai-oauth-logout",
|
|
273
|
+
*values,
|
|
274
|
+
],
|
|
275
|
+
)
|
|
250
276
|
if provider == "kimi":
|
|
251
277
|
status = (
|
|
252
278
|
"managed profile detected"
|
|
@@ -151,8 +151,8 @@ class PrelaunchShellContext:
|
|
|
151
151
|
|
|
152
152
|
def default_prelaunch_action(self, provider: str) -> str:
|
|
153
153
|
config = self.provider.load_config()
|
|
154
|
-
if str(config.get("last_launch_action") or "").strip()
|
|
155
|
-
return "
|
|
154
|
+
if str(config.get("last_launch_action") or "").strip() in {"launch-grok", "launch-zcode"}:
|
|
155
|
+
return str(config.get("last_launch_action")).strip()
|
|
156
156
|
if provider == "kimi":
|
|
157
157
|
remembered = str(config.get("last_launch_action") or "").strip()
|
|
158
158
|
if remembered in {"launch", "launch-kimi"}:
|
|
@@ -169,7 +169,7 @@ class PrelaunchShellContext:
|
|
|
169
169
|
)
|
|
170
170
|
|
|
171
171
|
def prelaunch_action_index(self, action: str) -> int:
|
|
172
|
-
if action in {"launch", "launch-codex", "launch-codex-app-server", "launch-agy", "launch-kimi", "launch-grok"}:
|
|
172
|
+
if action in {"launch", "launch-codex", "launch-codex-app-server", "launch-agy", "launch-kimi", "launch-grok", "launch-zcode"}:
|
|
173
173
|
action = "launch-menu"
|
|
174
174
|
try:
|
|
175
175
|
return self.main_menu_actions.index(action)
|
|
@@ -23,6 +23,8 @@ RUNTIME_FILES = {
|
|
|
23
23
|
"kimi": "AGENTS.md",
|
|
24
24
|
# Grok Build reads the AGENTS.md family, like Codex and Kimi.
|
|
25
25
|
"grok": "AGENTS.md",
|
|
26
|
+
# ZCode discovers workspace instructions from AGENTS.md.
|
|
27
|
+
"zcode": "AGENTS.md",
|
|
26
28
|
}
|
|
27
29
|
URL_KEYS = {
|
|
28
30
|
"claude": "claude_url",
|
|
@@ -107,11 +107,21 @@ class GrokRuntimeAdapter(CliRuntimeAdapter):
|
|
|
107
107
|
return RuntimeCommand(argv=tuple(argv), env=dict(self.environment), cwd=spec.cwd)
|
|
108
108
|
|
|
109
109
|
|
|
110
|
+
class ZcodeRuntimeAdapter(CliRuntimeAdapter):
|
|
111
|
+
"""ZCode command materialization for a launch-scoped storage root."""
|
|
112
|
+
|
|
113
|
+
def build_command(self, spec: LaunchSpec) -> RuntimeCommand:
|
|
114
|
+
argv = [str(spec.runtime.executable or self.executable)]
|
|
115
|
+
argv.extend(spec.passthrough)
|
|
116
|
+
return RuntimeCommand(argv=tuple(argv), env=dict(self.environment), cwd=spec.cwd)
|
|
117
|
+
|
|
118
|
+
|
|
110
119
|
RUNTIME_ADAPTERS: AdapterRegistry[RuntimeAdapter] = AdapterRegistry()
|
|
111
120
|
RUNTIME_ADAPTERS.register("claude", lambda **kwargs: ClaudeRuntimeAdapter(name="claude", **kwargs))
|
|
112
121
|
RUNTIME_ADAPTERS.register("codex", lambda **kwargs: CodexRuntimeAdapter(name="codex", **kwargs))
|
|
113
122
|
RUNTIME_ADAPTERS.register("agy", lambda **kwargs: AgyRuntimeAdapter(name="agy", **kwargs))
|
|
114
123
|
RUNTIME_ADAPTERS.register("grok", lambda **kwargs: GrokRuntimeAdapter(name="grok", **kwargs))
|
|
124
|
+
RUNTIME_ADAPTERS.register("zcode", lambda **kwargs: ZcodeRuntimeAdapter(name="zcode", **kwargs))
|
|
115
125
|
|
|
116
126
|
|
|
117
127
|
__all__ = [
|
|
@@ -121,4 +131,5 @@ __all__ = [
|
|
|
121
131
|
"CliRuntimeAdapter",
|
|
122
132
|
"CodexRuntimeAdapter",
|
|
123
133
|
"GrokRuntimeAdapter",
|
|
134
|
+
"ZcodeRuntimeAdapter",
|
|
124
135
|
]
|
|
@@ -399,6 +399,30 @@ class ZaiOAuthRuntime:
|
|
|
399
399
|
return ""
|
|
400
400
|
return str(provider.get("api_key") or "").strip()
|
|
401
401
|
|
|
402
|
+
def import_api_key(self, api_key: str, *, source: str = "zcode") -> list[str]:
|
|
403
|
+
key = str(api_key or "").strip()
|
|
404
|
+
if not key:
|
|
405
|
+
return ["Z.AI OAuth import skipped: no Coding Plan API key was found."]
|
|
406
|
+
config = self.ports.load_config()
|
|
407
|
+
provider = config.setdefault("providers", {}).setdefault("zai", {})
|
|
408
|
+
if (
|
|
409
|
+
str(provider.get("api_key") or "").strip() == key
|
|
410
|
+
and provider.get("credential_source") == "zai-oauth"
|
|
411
|
+
):
|
|
412
|
+
return ["Z.AI OAuth credential is already shared by all runtimes."]
|
|
413
|
+
provider["api_key"] = key
|
|
414
|
+
provider.pop("api_keys", None)
|
|
415
|
+
provider["credential_source"] = "zai-oauth"
|
|
416
|
+
provider["oauth_authenticated_at"] = datetime.now(timezone.utc).isoformat()
|
|
417
|
+
provider["oauth_import_source"] = str(source or "zcode")
|
|
418
|
+
config["current_provider"] = "zai"
|
|
419
|
+
self.ports.save_config(config)
|
|
420
|
+
self.ports.clear_model_cache()
|
|
421
|
+
return [
|
|
422
|
+
"Z.AI OAuth Coding Plan credential imported into Ciel Runtime for all clients.",
|
|
423
|
+
f"Credential: {self.ports.mask(key)}; fp {self.ports.fingerprint(key)}",
|
|
424
|
+
]
|
|
425
|
+
|
|
402
426
|
def action(self, action: str, *, no_browser: bool = False) -> list[str]:
|
|
403
427
|
if action == "status":
|
|
404
428
|
token = self.token()
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""ZCode installation, shared Z.AI OAuth, and routed launch context."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Callable
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def zcode_settings(*, model: str, base_url: str, api_key: str) -> dict[str, Any]:
|
|
12
|
+
"""Build the documented ZCode custom Anthropic-provider configuration."""
|
|
13
|
+
|
|
14
|
+
model_id = str(model or "model").strip() or "model"
|
|
15
|
+
return {
|
|
16
|
+
"provider": {
|
|
17
|
+
"zai": {
|
|
18
|
+
"kind": "anthropic",
|
|
19
|
+
"name": "Ciel Runtime",
|
|
20
|
+
"options": {
|
|
21
|
+
"apiKey": str(api_key or "not-used"),
|
|
22
|
+
"apiKeyRequired": True,
|
|
23
|
+
"baseURL": str(base_url).rstrip("/"),
|
|
24
|
+
},
|
|
25
|
+
"headers": {},
|
|
26
|
+
"models": {model_id: {"name": model_id}},
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"model": {"main": f"zai/{model_id}", "lite": f"zai/{model_id}"},
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True, slots=True)
|
|
34
|
+
class ZcodeProcessPorts:
|
|
35
|
+
find_executable: Callable[[str], str | None]
|
|
36
|
+
run: Callable[..., Any]
|
|
37
|
+
call: Callable[..., int]
|
|
38
|
+
print_line: Callable[..., None]
|
|
39
|
+
environment: dict[str, str]
|
|
40
|
+
augment_path: Callable[[dict[str, str]], str]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass(frozen=True, slots=True)
|
|
44
|
+
class ZcodeConfigurationPorts:
|
|
45
|
+
load: Callable[[], dict[str, Any]]
|
|
46
|
+
current_provider: Callable[[dict[str, Any]], tuple[str, dict[str, Any]]]
|
|
47
|
+
current_alias: Callable[[dict[str, Any]], str]
|
|
48
|
+
router_auth_token: Callable[[str, dict[str, Any]], str]
|
|
49
|
+
router_base: str
|
|
50
|
+
zai_anthropic_base_url: str
|
|
51
|
+
settings_path: Path
|
|
52
|
+
save_json: Callable[[Path, dict[str, Any], str], None]
|
|
53
|
+
import_oauth_api_key: Callable[[str], list[str]]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass(frozen=True, slots=True)
|
|
57
|
+
class ZcodeLifecyclePorts:
|
|
58
|
+
oauth_action: Callable[..., list[str]]
|
|
59
|
+
start_router: Callable[[], Any]
|
|
60
|
+
run_with_router: Callable[[Callable[[], int], bool], int]
|
|
61
|
+
materialize_command: Callable[..., tuple[list[str], dict[str, str]]]
|
|
62
|
+
record_launch: Callable[[str, str], None]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass(frozen=True, slots=True)
|
|
66
|
+
class ZcodeRuntimeContext:
|
|
67
|
+
process: ZcodeProcessPorts
|
|
68
|
+
config: ZcodeConfigurationPorts
|
|
69
|
+
lifecycle: ZcodeLifecyclePorts
|
|
70
|
+
|
|
71
|
+
def install_if_missing(self) -> str:
|
|
72
|
+
executable = self.process.find_executable("zcode")
|
|
73
|
+
if executable:
|
|
74
|
+
return executable
|
|
75
|
+
npm = self.process.find_executable("npm")
|
|
76
|
+
if not npm:
|
|
77
|
+
raise RuntimeError(
|
|
78
|
+
"ZCode CLI is missing; install zcode-app-cli (Node.js 22.19+)."
|
|
79
|
+
)
|
|
80
|
+
self.process.print_line(
|
|
81
|
+
"Installing ZCode CLI (zcode-app-cli)...", flush=True
|
|
82
|
+
)
|
|
83
|
+
result = self.process.run(
|
|
84
|
+
[npm, "install", "-g", "zcode-app-cli"], check=False
|
|
85
|
+
)
|
|
86
|
+
if result.returncode:
|
|
87
|
+
raise RuntimeError(
|
|
88
|
+
f"ZCode CLI installation failed (exit {result.returncode})."
|
|
89
|
+
)
|
|
90
|
+
executable = self.process.find_executable("zcode")
|
|
91
|
+
if not executable:
|
|
92
|
+
raise RuntimeError(
|
|
93
|
+
"ZCode CLI installed but 'zcode' is not available on PATH."
|
|
94
|
+
)
|
|
95
|
+
return executable
|
|
96
|
+
|
|
97
|
+
@staticmethod
|
|
98
|
+
def _shared_oauth_action(argv: list[str]) -> str | None:
|
|
99
|
+
if not argv:
|
|
100
|
+
return None
|
|
101
|
+
if argv[0] == "login" and (len(argv) == 1 or "--oauth" in argv):
|
|
102
|
+
return "login"
|
|
103
|
+
if argv[0] == "logout":
|
|
104
|
+
return "logout"
|
|
105
|
+
return None
|
|
106
|
+
|
|
107
|
+
def _run_shared_oauth(self, action: str, argv: list[str]) -> int:
|
|
108
|
+
try:
|
|
109
|
+
lines = self.lifecycle.oauth_action(
|
|
110
|
+
action, no_browser="--no-browser" in argv
|
|
111
|
+
)
|
|
112
|
+
except RuntimeError as exc:
|
|
113
|
+
self.process.print_line(f"Z.AI OAuth failed: {exc}", flush=True)
|
|
114
|
+
return 1
|
|
115
|
+
for line in lines:
|
|
116
|
+
self.process.print_line(line, flush=True)
|
|
117
|
+
return 0
|
|
118
|
+
|
|
119
|
+
def write_settings(
|
|
120
|
+
self, config: dict[str, Any], provider: str, provider_config: dict[str, Any]
|
|
121
|
+
) -> Path:
|
|
122
|
+
payload = zcode_settings(
|
|
123
|
+
model=self.config.current_alias(config),
|
|
124
|
+
base_url=self.config.router_base,
|
|
125
|
+
api_key=self.config.router_auth_token(provider, provider_config),
|
|
126
|
+
)
|
|
127
|
+
self.config.save_json(
|
|
128
|
+
self.config.settings_path, payload, "zcode routed settings"
|
|
129
|
+
)
|
|
130
|
+
return self.config.settings_path
|
|
131
|
+
|
|
132
|
+
def import_zcode_oauth_credential(self, settings_path: Path) -> list[str]:
|
|
133
|
+
try:
|
|
134
|
+
payload = json.loads(settings_path.read_text(encoding="utf-8"))
|
|
135
|
+
provider = payload.get("provider", {}).get("zai", {})
|
|
136
|
+
options = provider.get("options", {})
|
|
137
|
+
base_url = str(options.get("baseURL") or "").rstrip("/")
|
|
138
|
+
key = str(options.get("apiKey") or "").strip()
|
|
139
|
+
except (OSError, UnicodeError, json.JSONDecodeError, AttributeError):
|
|
140
|
+
return []
|
|
141
|
+
if base_url != self.config.zai_anthropic_base_url.rstrip("/") or not key:
|
|
142
|
+
return []
|
|
143
|
+
return self.config.import_oauth_api_key(key)
|
|
144
|
+
|
|
145
|
+
def launch(self, passthrough: list[str]) -> int:
|
|
146
|
+
argv = list(passthrough)
|
|
147
|
+
oauth_action = self._shared_oauth_action(argv)
|
|
148
|
+
if oauth_action:
|
|
149
|
+
return self._run_shared_oauth(oauth_action, argv)
|
|
150
|
+
executable = self.install_if_missing()
|
|
151
|
+
config = self.config.load()
|
|
152
|
+
provider, provider_config = self.config.current_provider(config)
|
|
153
|
+
settings_path = self.write_settings(config, provider, provider_config)
|
|
154
|
+
env = self.process.environment.copy()
|
|
155
|
+
env["PATH"] = self.process.augment_path(env)
|
|
156
|
+
launch_home = settings_path.parents[2]
|
|
157
|
+
env["USERPROFILE"] = str(launch_home)
|
|
158
|
+
env["HOME"] = str(launch_home)
|
|
159
|
+
env["ZCODE_HOME"] = str(launch_home / ".zcode")
|
|
160
|
+
env["ZCODE_STORAGE_DIR"] = str(launch_home / ".zcode")
|
|
161
|
+
command, child_env = self.lifecycle.materialize_command(
|
|
162
|
+
"zcode",
|
|
163
|
+
executable,
|
|
164
|
+
env,
|
|
165
|
+
provider,
|
|
166
|
+
provider_config,
|
|
167
|
+
mode="routed",
|
|
168
|
+
protocol="anthropic_messages",
|
|
169
|
+
cwd=Path.cwd(),
|
|
170
|
+
enable_channels=False,
|
|
171
|
+
passthrough=argv,
|
|
172
|
+
options={},
|
|
173
|
+
)
|
|
174
|
+
manage_router = bool(self.lifecycle.start_router())
|
|
175
|
+
if not argv or argv[0] not in {"--help", "-h", "--version", "-v", "version"}:
|
|
176
|
+
self.lifecycle.record_launch(provider, self.config.current_alias(config))
|
|
177
|
+
result = self.lifecycle.run_with_router(
|
|
178
|
+
lambda: self.process.call(command, env=child_env), manage_router
|
|
179
|
+
)
|
|
180
|
+
if result == 0:
|
|
181
|
+
for line in self.import_zcode_oauth_credential(settings_path):
|
|
182
|
+
self.process.print_line(line, flush=True)
|
|
183
|
+
return result
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@dataclass(frozen=True, slots=True)
|
|
187
|
+
class ZcodeRuntimeCompatibilityApi:
|
|
188
|
+
context: Callable[[], ZcodeRuntimeContext]
|
|
189
|
+
|
|
190
|
+
def install_if_missing(self) -> str:
|
|
191
|
+
return self.context().install_if_missing()
|
|
192
|
+
|
|
193
|
+
def launch(self, passthrough: list[str]) -> int:
|
|
194
|
+
return self.context().launch(passthrough)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
__all__ = [
|
|
198
|
+
"ZcodeConfigurationPorts",
|
|
199
|
+
"ZcodeLifecyclePorts",
|
|
200
|
+
"ZcodeProcessPorts",
|
|
201
|
+
"ZcodeRuntimeCompatibilityApi",
|
|
202
|
+
"ZcodeRuntimeContext",
|
|
203
|
+
"zcode_settings",
|
|
204
|
+
]
|
package/docs/CLI-Reference.md
CHANGED
|
@@ -7,11 +7,12 @@
|
|
|
7
7
|
|
|
8
8
|
## ciel-runtime
|
|
9
9
|
|
|
10
|
-
기본값은 Claude Code를 Router를 통해 실행한다.
|
|
10
|
+
기본값은 Claude Code를 Router를 통해 실행한다. 다른 런타임은 명시적으로 선택한다.
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
13
|
ciel-runtime [OPTIONS] [-- RUNTIME_ARGS...]
|
|
14
14
|
ciel-runtime codex [CODEX_ARGS...]
|
|
15
|
+
ciel-runtime zcode [ZCODE_ARGS...]
|
|
15
16
|
ciel-runtime --ca-runtime codex -- exec "hello"
|
|
16
17
|
```
|
|
17
18
|
|
|
@@ -23,7 +24,7 @@ ciel-runtime --ca-runtime codex -- exec "hello"
|
|
|
23
24
|
| `--ca-model <id>` | 현재 제공자 모델 설정 후 실행 |
|
|
24
25
|
| `--ca-base-url <url>` | 현재 제공자 기본 URL 설정 후 실행 |
|
|
25
26
|
| `--ca-api-key <key>` | 현재 제공자 API 키 설정 후 실행 |
|
|
26
|
-
| `--ca-runtime claude\|codex` | 실행 런타임 선택 |
|
|
27
|
+
| `--ca-runtime claude\|codex\|codex-app-server\|agy\|grok\|zcode` | 실행 런타임 선택 |
|
|
27
28
|
| `--ca-no-launch` | 설정만 적용하고 런타임 실행 생략 |
|
|
28
29
|
| `--` | 이후 인자를 선택된 런타임에 그대로 전달 |
|
|
29
30
|
|
|
@@ -37,6 +38,20 @@ ciel-runtime --ca-provider ollama --ca-runtime codex -- exec "작업 내용"
|
|
|
37
38
|
|
|
38
39
|
Codex 런처는 `~/.codex/config.toml`을 수정하지 않는다. 실행 시점에 `-c model_providers.ciel-runtime...` 오버라이드를 붙여 로컬 Router를 OpenAI Responses provider로 등록한다.
|
|
39
40
|
|
|
41
|
+
### ZCode 실행
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
ciel-runtime zcode
|
|
45
|
+
ciel-runtime --ca-runtime zcode -- --continue
|
|
46
|
+
ciel-runtimectl launch-zcode --prompt "작업 내용"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
ZCode는 워크스페이스별 Ciel 관리 홈과 `ZCODE_STORAGE_DIR`을 사용한다. 선택된
|
|
50
|
+
provider/model은 로컬 Anthropic Router 설정으로 투영되며 사용자의 일반
|
|
51
|
+
`%USERPROFILE%\.zcode` 또는 `~/.zcode` 설정은 덮어쓰지 않는다. `ciel-runtime
|
|
52
|
+
zcode login --oauth`와 API-key 패널의 Z.AI OAuth는 Ciel 공용 credential store에
|
|
53
|
+
Coding Plan 키를 저장하므로 다른 런타임도 같은 키를 사용한다.
|
|
54
|
+
|
|
40
55
|
---
|
|
41
56
|
|
|
42
57
|
## ciel-runtimectl
|
package/docs/Installation.md
CHANGED
|
@@ -118,6 +118,19 @@ Windows 설치 명령은 `irm https://x.ai/cli/install.ps1 | iex`이고 Linux/ma
|
|
|
118
118
|
xAI API key, 선택 모델, reasoning effort를 launch process에만 투영한다. Grok의
|
|
119
119
|
`~/.grok` 인증·세션·사용자 설정은 Ciel이 덮어쓰지 않는다.
|
|
120
120
|
|
|
121
|
+
### ZCode 실행
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
ciel-runtime zcode
|
|
125
|
+
ciel-runtime zcode --continue
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
ZCode가 없으면 `zcode-app-cli`를 npm으로 설치한다. 실행 시 워크스페이스별
|
|
129
|
+
Ciel 관리 홈을 사용하고 선택된 provider/model을 로컬 Router로 연결한다.
|
|
130
|
+
Z.AI OAuth는 `ciel-runtimectl zai-oauth login` 또는 `ciel-runtime zcode login
|
|
131
|
+
--oauth`로 등록하며, 최종 Coding Plan API key만 Ciel의 공용 provider 설정에
|
|
132
|
+
저장되어 Claude, Codex, AGY, ZCode 등 routed client가 함께 사용한다.
|
|
133
|
+
|
|
121
134
|
---
|
|
122
135
|
|
|
123
136
|
## Router 시작/중지
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
okf_version: "1.0"
|
|
2
|
+
knowledge:
|
|
3
|
+
task:
|
|
4
|
+
id: zcode-runtime-oauth-windows-paste-20260824
|
|
5
|
+
date: "2026-08-24"
|
|
6
|
+
objective: >-
|
|
7
|
+
Register ZCode as a supported Ciel runtime, share Z.AI OAuth credentials
|
|
8
|
+
across runtime clients, and repair leaked Windows bracketed-paste markers.
|
|
9
|
+
status: deployment-in-progress
|
|
10
|
+
|
|
11
|
+
evidence:
|
|
12
|
+
source_code:
|
|
13
|
+
terminal_reset:
|
|
14
|
+
file: ciel_runtime_support/terminal_platform_io.py
|
|
15
|
+
observation: >-
|
|
16
|
+
The reset sequence disabled mouse and focus modes but omitted DECSET
|
|
17
|
+
bracketed-paste disable ESC[?2004l.
|
|
18
|
+
runtime_registry:
|
|
19
|
+
file: ciel_runtime_support/runtime_adapters.py
|
|
20
|
+
observation: ZCode had no registered runtime adapter.
|
|
21
|
+
oauth_storage:
|
|
22
|
+
file: ciel_runtime_support/zai_oauth.py
|
|
23
|
+
observation: >-
|
|
24
|
+
The resolved Coding Plan API key is stored under providers.zai.api_key,
|
|
25
|
+
which is the shared provider credential consumed by routed clients.
|
|
26
|
+
upstream_runtime:
|
|
27
|
+
package: zcode-app-cli@3.8.1-15
|
|
28
|
+
runtime: zcode-runtime@0.16.3
|
|
29
|
+
observations:
|
|
30
|
+
- The help advertises --settings, but the bundled runtime rejected it as Unknown option.
|
|
31
|
+
- The bundled code resolves ZCODE_STORAGE_DIR and workspace AGENTS.md.
|
|
32
|
+
- A workspace-isolated USERPROFILE/HOME loaded the generated Ciel provider config.
|
|
33
|
+
|
|
34
|
+
implementation:
|
|
35
|
+
runtime:
|
|
36
|
+
registration:
|
|
37
|
+
commands: [ciel-runtime-zcode, launch-zcode, ca-runtime-zcode]
|
|
38
|
+
storage:
|
|
39
|
+
scope: workspace
|
|
40
|
+
config: zcode-home/.zcode/cli/config.json
|
|
41
|
+
routing:
|
|
42
|
+
protocol: anthropic_messages
|
|
43
|
+
provider_id: zai
|
|
44
|
+
endpoint: ciel-runtime-loopback-router
|
|
45
|
+
authentication:
|
|
46
|
+
shared_store: providers.zai.api_key
|
|
47
|
+
entrypoints: [provider-api-key-panel, zai-oauth-cli, zcode-login-oauth]
|
|
48
|
+
reverse_import: official-zcode-oauth-config-after-exit
|
|
49
|
+
terminal:
|
|
50
|
+
cleanup_sequence_added: ESC[?2004l
|
|
51
|
+
|
|
52
|
+
verification:
|
|
53
|
+
completed:
|
|
54
|
+
- Python compilation
|
|
55
|
+
- focused ZCode runtime tests
|
|
56
|
+
- focused Z.AI OAuth tests
|
|
57
|
+
- focused remote-memory tests
|
|
58
|
+
- actual zcode doctor execution
|
|
59
|
+
- actual ZCode TUI model-status rendering through the Ciel router
|
|
60
|
+
- full regression suite: 2739 passed and 136 skipped
|
|
61
|
+
- documentation validation: 23 Markdown files passed
|
|
62
|
+
- lint and architecture checks
|
|
63
|
+
- npm package dry run for version 0.2.26
|
|
64
|
+
- local install to C:/Users/djlov/.local/share/ciel-runtime
|
|
65
|
+
- local launcher reports ciel-runtime 0.2.26
|
|
66
|
+
- local ZCode launch reports zcode-runtime 0.16.3
|
|
67
|
+
pending:
|
|
68
|
+
- nightly publication
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oneciel-ai/ciel-runtime",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "Universal AI coding-agent runtime and model-routing layer for Claude, Codex, AGY, and compatible runtimes.",
|
|
3
|
+
"version": "0.2.26",
|
|
4
|
+
"description": "Universal AI coding-agent runtime and model-routing layer for Claude, Codex, AGY, ZCode, and compatible runtimes.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "One Ciel LLC",
|
|
7
7
|
"homepage": "https://github.com/OneCielAI/ciel-runtime#readme",
|