ccsilo 0.1.0__py3-none-any.whl
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.
- ccsilo/__init__.py +40 -0
- ccsilo/__main__.py +501 -0
- ccsilo/_utils.py +177 -0
- ccsilo/binary_patcher/__init__.py +44 -0
- ccsilo/binary_patcher/bun_compat.py +136 -0
- ccsilo/binary_patcher/codesign.py +37 -0
- ccsilo/binary_patcher/elf_resize.py +156 -0
- ccsilo/binary_patcher/index.py +207 -0
- ccsilo/binary_patcher/js_patch.py +104 -0
- ccsilo/binary_patcher/macho_resize.py +158 -0
- ccsilo/binary_patcher/pe_resize.py +74 -0
- ccsilo/binary_patcher/prompts.py +184 -0
- ccsilo/binary_patcher/repack.py +34 -0
- ccsilo/binary_patcher/replace_entry.py +73 -0
- ccsilo/binary_patcher/strip_bun_wrapper.py +28 -0
- ccsilo/binary_patcher/theme.py +131 -0
- ccsilo/binary_patcher/unpack_and_patch.py +249 -0
- ccsilo/bun_extract/__init__.py +19 -0
- ccsilo/bun_extract/constants.py +49 -0
- ccsilo/bun_extract/elf.py +12 -0
- ccsilo/bun_extract/extract.py +162 -0
- ccsilo/bun_extract/macho.py +148 -0
- ccsilo/bun_extract/parser.py +232 -0
- ccsilo/bun_extract/pe.py +46 -0
- ccsilo/bun_extract/replace.py +47 -0
- ccsilo/bun_extract/types.py +57 -0
- ccsilo/bundler.py +376 -0
- ccsilo/cli/__init__.py +11 -0
- ccsilo/cli/handlers.py +162 -0
- ccsilo/cli/parsers.py +214 -0
- ccsilo/cli/payloads.py +101 -0
- ccsilo/data/download-index.seed.json +95 -0
- ccsilo/download_index.py +119 -0
- ccsilo/download_picker.py +244 -0
- ccsilo/downloader.py +393 -0
- ccsilo/extractor.py +116 -0
- ccsilo/model_proxy.py +404 -0
- ccsilo/patch_workflow.py +255 -0
- ccsilo/patcher.py +434 -0
- ccsilo/patches/__init__.py +195 -0
- ccsilo/patches/_pinned_default.py +3 -0
- ccsilo/patches/_registry.py +93 -0
- ccsilo/patches/_versions.py +131 -0
- ccsilo/patches/agents_md.py +101 -0
- ccsilo/patches/allow_custom_agent_models.py +66 -0
- ccsilo/patches/auto_accept_plan_mode.py +49 -0
- ccsilo/patches/filter_scroll_escape_sequences.py +67 -0
- ccsilo/patches/hide_ctrl_g.py +28 -0
- ccsilo/patches/hide_startup_banner.py +50 -0
- ccsilo/patches/hide_startup_clawd.py +46 -0
- ccsilo/patches/input_border_box.py +76 -0
- ccsilo/patches/mcp_startup.py +59 -0
- ccsilo/patches/model_customizations.py +60 -0
- ccsilo/patches/opusplan1m.py +118 -0
- ccsilo/patches/patches_applied_indication.py +23 -0
- ccsilo/patches/prompt_overlays.py +31 -0
- ccsilo/patches/remember_skill.py +42 -0
- ccsilo/patches/session_memory.py +110 -0
- ccsilo/patches/show_more_items.py +47 -0
- ccsilo/patches/statusline_update_throttle.py +73 -0
- ccsilo/patches/suppress_line_numbers.py +89 -0
- ccsilo/patches/suppress_model_launch_notice.py +45 -0
- ccsilo/patches/suppress_native_installer_warning.py +36 -0
- ccsilo/patches/suppress_prompt_caching_warning.py +44 -0
- ccsilo/patches/suppress_rate_limit_options.py +30 -0
- ccsilo/patches/system_prompts.py +156 -0
- ccsilo/patches/themes.py +29 -0
- ccsilo/patches/thinking_visibility.py +30 -0
- ccsilo/patches/token_count_rounding.py +46 -0
- ccsilo/providers/__init__.py +63 -0
- ccsilo/providers/config.py +168 -0
- ccsilo/providers/loader.py +214 -0
- ccsilo/providers/mcp_catalog.py +150 -0
- ccsilo/providers/model_discovery.py +73 -0
- ccsilo/providers/registry/9router.json +55 -0
- ccsilo/providers/registry/alibaba.json +56 -0
- ccsilo/providers/registry/anthropic.json +56 -0
- ccsilo/providers/registry/ccr-oauth.json +68 -0
- ccsilo/providers/registry/ccrouter.json +67 -0
- ccsilo/providers/registry/cerebras.json +58 -0
- ccsilo/providers/registry/custom.json +52 -0
- ccsilo/providers/registry/deepseek.json +57 -0
- ccsilo/providers/registry/gatewayz.json +56 -0
- ccsilo/providers/registry/kimi.json +56 -0
- ccsilo/providers/registry/lmstudio.json +64 -0
- ccsilo/providers/registry/local-custom.json +60 -0
- ccsilo/providers/registry/minimax-cn.json +57 -0
- ccsilo/providers/registry/minimax.json +72 -0
- ccsilo/providers/registry/mirror.json +56 -0
- ccsilo/providers/registry/nanogpt.json +56 -0
- ccsilo/providers/registry/ollama.json +66 -0
- ccsilo/providers/registry/omlx.json +64 -0
- ccsilo/providers/registry/openrouter.json +56 -0
- ccsilo/providers/registry/poe.json +56 -0
- ccsilo/providers/registry/vercel.json +58 -0
- ccsilo/providers/registry/zai.json +94 -0
- ccsilo/providers/schema.py +331 -0
- ccsilo/tui/__init__.py +471 -0
- ccsilo/tui/_const.py +34 -0
- ccsilo/tui/_runtime.py +21 -0
- ccsilo/tui/busy.py +97 -0
- ccsilo/tui/dashboard.py +215 -0
- ccsilo/tui/dispatch.py +693 -0
- ccsilo/tui/keys.py +114 -0
- ccsilo/tui/nav.py +305 -0
- ccsilo/tui/options.py +6 -0
- ccsilo/tui/options_dashboard.py +217 -0
- ccsilo/tui/options_setup.py +214 -0
- ccsilo/tui/options_tweaks.py +303 -0
- ccsilo/tui/options_variant.py +527 -0
- ccsilo/tui/render_frame.py +429 -0
- ccsilo/tui/render_labels.py +963 -0
- ccsilo/tui/rendering.py +4 -0
- ccsilo/tui/setup_actions.py +951 -0
- ccsilo/tui/state.py +229 -0
- ccsilo/tui/themes.py +191 -0
- ccsilo/tui/variant_actions.py +224 -0
- ccsilo/variant_tweaks.py +30 -0
- ccsilo/variants/__init__.py +121 -0
- ccsilo/variants/ascii/cerebras.txt +31 -0
- ccsilo/variants/ascii/deepseek.txt +22 -0
- ccsilo/variants/ascii/minimax-cn.txt +14 -0
- ccsilo/variants/ascii/minimax.txt +14 -0
- ccsilo/variants/ascii/ollama.txt +40 -0
- ccsilo/variants/ascii/openrouter.txt +36 -0
- ccsilo/variants/ascii/zai.txt +21 -0
- ccsilo/variants/build.py +491 -0
- ccsilo/variants/builder.py +156 -0
- ccsilo/variants/ccrouter.py +385 -0
- ccsilo/variants/constants.py +24 -0
- ccsilo/variants/install.py +320 -0
- ccsilo/variants/lifecycle.py +541 -0
- ccsilo/variants/model.py +231 -0
- ccsilo/variants/model_updates.py +101 -0
- ccsilo/variants/splash.py +176 -0
- ccsilo/variants/tweaks.py +376 -0
- ccsilo/variants/wrapper.py +413 -0
- ccsilo/workspace/__init__.py +146 -0
- ccsilo/workspace/artifacts.py +424 -0
- ccsilo/workspace/models.py +70 -0
- ccsilo/workspace/patches.py +358 -0
- ccsilo/workspace/paths.py +183 -0
- ccsilo/workspace/settings.py +61 -0
- ccsilo-0.1.0.dist-info/METADATA +657 -0
- ccsilo-0.1.0.dist-info/RECORD +148 -0
- ccsilo-0.1.0.dist-info/WHEEL +5 -0
- ccsilo-0.1.0.dist-info/entry_points.txt +2 -0
- ccsilo-0.1.0.dist-info/top_level.txt +1 -0
ccsilo/__init__.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Standalone toolkit for extracting, patching, and repacking Claude Code binaries."""
|
|
2
|
+
|
|
3
|
+
from importlib import import_module
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"download_binary",
|
|
7
|
+
"download_npm",
|
|
8
|
+
"extract_all",
|
|
9
|
+
"pack_bundle",
|
|
10
|
+
"apply_patches",
|
|
11
|
+
"parse_bun_binary",
|
|
12
|
+
"replace_entry_js",
|
|
13
|
+
"replace_module",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
_EXPORTS = {
|
|
17
|
+
"download_binary": ".downloader",
|
|
18
|
+
"download_npm": ".downloader",
|
|
19
|
+
"extract_all": ".extractor",
|
|
20
|
+
"pack_bundle": ".bundler",
|
|
21
|
+
"apply_patches": ".binary_patcher",
|
|
22
|
+
"parse_bun_binary": ".bun_extract",
|
|
23
|
+
"replace_entry_js": ".binary_patcher",
|
|
24
|
+
"replace_module": ".bun_extract",
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def __getattr__(name):
|
|
29
|
+
module_name = _EXPORTS.get(name)
|
|
30
|
+
if module_name is None:
|
|
31
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
32
|
+
|
|
33
|
+
module = import_module(module_name, __name__)
|
|
34
|
+
value = getattr(module, name)
|
|
35
|
+
globals()[name] = value
|
|
36
|
+
return value
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def __dir__():
|
|
40
|
+
return sorted(set(globals()) | set(__all__))
|
ccsilo/__main__.py
ADDED
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
"""Top-level CLI entry point.
|
|
2
|
+
|
|
3
|
+
The argparse parser tree is built in :mod:`ccsilo.cli.parsers`; simple
|
|
4
|
+
non-variant subcommand handlers live in :mod:`ccsilo.cli.handlers`. The
|
|
5
|
+
variant subcommand dispatcher and the ``main`` entry point live here so test
|
|
6
|
+
fixtures can monkey-patch variant helpers (``create_variant``, ``load_variant``,
|
|
7
|
+
``doctor_variant``, ``remove_variant``, etc.) via ``ccsilo.__main__`` and
|
|
8
|
+
have those patches apply to the dispatch site.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import sys
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from shutil import which
|
|
15
|
+
|
|
16
|
+
from .cli import build_parser, inspect_binary # noqa: F401, re-exported for test imports
|
|
17
|
+
from .cli import handlers as _handlers
|
|
18
|
+
from .providers import get_provider, list_mcp_catalog, provider_default_variant_name
|
|
19
|
+
from .cli.payloads import (
|
|
20
|
+
install_result_payload,
|
|
21
|
+
model_overrides_from_args,
|
|
22
|
+
print_json,
|
|
23
|
+
tweak_options_from_args,
|
|
24
|
+
uninstall_result_payload,
|
|
25
|
+
variant_payload,
|
|
26
|
+
variant_result_payload,
|
|
27
|
+
)
|
|
28
|
+
from .variants import (
|
|
29
|
+
apply_variant,
|
|
30
|
+
create_variant,
|
|
31
|
+
doctor_variant,
|
|
32
|
+
install_variant_command,
|
|
33
|
+
list_variant_providers,
|
|
34
|
+
load_variant,
|
|
35
|
+
remove_variant,
|
|
36
|
+
run_variant,
|
|
37
|
+
scan_variants,
|
|
38
|
+
uninstall_workspace,
|
|
39
|
+
update_variants,
|
|
40
|
+
variant_id_from_name,
|
|
41
|
+
workspace_managed_install_records,
|
|
42
|
+
)
|
|
43
|
+
from .workspace import workspace_root
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
_SIMPLE_HANDLERS = {
|
|
47
|
+
"download": _handlers.cmd_download,
|
|
48
|
+
"extract": _handlers.cmd_extract,
|
|
49
|
+
"unpack": _handlers.cmd_unpack,
|
|
50
|
+
"inspect": _handlers.cmd_inspect,
|
|
51
|
+
"replace-entry": _handlers.cmd_replace_entry,
|
|
52
|
+
"apply-binary": _handlers.cmd_apply_binary,
|
|
53
|
+
"pack": _handlers.cmd_pack,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _provider_arg(args):
|
|
58
|
+
return getattr(args, "command_provider", None) or getattr(args, "provider", None)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _resolve_provider_variant(provider_key: str, *, required: bool):
|
|
62
|
+
provider = get_provider(provider_key)
|
|
63
|
+
default_name = provider_default_variant_name(provider.key)
|
|
64
|
+
default_id = variant_id_from_name(default_name)
|
|
65
|
+
try:
|
|
66
|
+
return load_variant(default_id)
|
|
67
|
+
except ValueError:
|
|
68
|
+
pass
|
|
69
|
+
matches = [
|
|
70
|
+
variant
|
|
71
|
+
for variant in scan_variants()
|
|
72
|
+
if ((variant.manifest.get("provider") or {}).get("key") == provider.key)
|
|
73
|
+
]
|
|
74
|
+
if len(matches) == 1:
|
|
75
|
+
return matches[0]
|
|
76
|
+
if len(matches) > 1:
|
|
77
|
+
names = ", ".join(variant.name for variant in matches)
|
|
78
|
+
raise ValueError(
|
|
79
|
+
f"Multiple setups use provider {provider.key}: {names}. "
|
|
80
|
+
"Use variant update/remove/install <name>."
|
|
81
|
+
)
|
|
82
|
+
if required:
|
|
83
|
+
raise ValueError(f"No setup found for provider {provider.key}. Run ccsilo --provider {provider.key} install.")
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _provider_next_steps(variant, provider_key: str, install_result=None):
|
|
88
|
+
provider = get_provider(provider_key)
|
|
89
|
+
manifest = variant.manifest or {}
|
|
90
|
+
paths = manifest.get("paths") or {}
|
|
91
|
+
credential = manifest.get("credential") or {}
|
|
92
|
+
credential_envs = []
|
|
93
|
+
if credential.get("mode") == "env" and credential.get("source"):
|
|
94
|
+
credential_envs.append(str(credential["source"]))
|
|
95
|
+
credential_envs.extend(str(item) for item in credential.get("targets") or [] if item)
|
|
96
|
+
if provider.credential_env:
|
|
97
|
+
credential_envs.append(provider.credential_env)
|
|
98
|
+
credential_envs = sorted(set(credential_envs))
|
|
99
|
+
|
|
100
|
+
next_steps = {
|
|
101
|
+
"command": getattr(install_result, "alias", None) or provider.key,
|
|
102
|
+
"workspace": str(workspace_root()),
|
|
103
|
+
"setupRoot": paths.get("root") or str(getattr(variant, "path", "")),
|
|
104
|
+
"wrapper": paths.get("wrapper") or "",
|
|
105
|
+
"configDir": paths.get("configDir") or "",
|
|
106
|
+
"credentialEnv": credential_envs,
|
|
107
|
+
"providerMcpServers": sorted(provider.mcp_servers),
|
|
108
|
+
"run": getattr(install_result, "alias", None) or paths.get("wrapper") or f"ccsilo variant run {variant.variant_id} --",
|
|
109
|
+
"doctor": f"ccsilo variant doctor {variant.variant_id}",
|
|
110
|
+
"warnings": [],
|
|
111
|
+
}
|
|
112
|
+
if install_result is not None:
|
|
113
|
+
next_steps["installPath"] = str(install_result.path)
|
|
114
|
+
next_steps["installTarget"] = str(install_result.target)
|
|
115
|
+
next_steps["installOnPath"] = bool(install_result.on_path)
|
|
116
|
+
if install_result.warning:
|
|
117
|
+
next_steps["warnings"].append(install_result.warning)
|
|
118
|
+
ccrouter = manifest.get("ccrouter")
|
|
119
|
+
if isinstance(ccrouter, dict):
|
|
120
|
+
next_steps["ccrouter"] = {
|
|
121
|
+
"configPath": str(ccrouter.get("configPath") or ""),
|
|
122
|
+
"homeDir": str(ccrouter.get("homeDir") or ""),
|
|
123
|
+
"runtimeDir": str(ccrouter.get("runtimeDir") or ""),
|
|
124
|
+
}
|
|
125
|
+
return next_steps
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _provider_payload(variant, provider_key: str, *, install_result=None):
|
|
129
|
+
payload = variant_payload(variant)
|
|
130
|
+
if install_result is not None:
|
|
131
|
+
payload["install"] = install_result_payload(install_result)
|
|
132
|
+
payload["nextSteps"] = _provider_next_steps(variant, provider_key, install_result)
|
|
133
|
+
return payload
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _print_provider_summary(action: str, variant, provider_key: str, *, install_result=None):
|
|
137
|
+
provider = get_provider(provider_key)
|
|
138
|
+
steps = _provider_next_steps(variant, provider.key, install_result)
|
|
139
|
+
print(f"[+] Provider setup {action}: {variant.variant_id}")
|
|
140
|
+
if install_result is not None:
|
|
141
|
+
print(f" command: {install_result.alias}")
|
|
142
|
+
print(f" installed: {install_result.path}")
|
|
143
|
+
print(f" target: {install_result.target}")
|
|
144
|
+
print(f" status: {install_result.status}")
|
|
145
|
+
print(f" on PATH: {'yes' if install_result.on_path else 'no'}")
|
|
146
|
+
print(f" setup: {steps['setupRoot']}")
|
|
147
|
+
print(f" workspace: {steps['workspace']}")
|
|
148
|
+
print(f" wrapper: {steps['wrapper']}")
|
|
149
|
+
print(f" config: {steps['configDir']}")
|
|
150
|
+
if steps["credentialEnv"]:
|
|
151
|
+
print(f" credential env: {', '.join(steps['credentialEnv'])}")
|
|
152
|
+
if steps["providerMcpServers"]:
|
|
153
|
+
print(f" provider MCP: {', '.join(steps['providerMcpServers'])} auto-configured")
|
|
154
|
+
ccrouter = steps.get("ccrouter")
|
|
155
|
+
if ccrouter:
|
|
156
|
+
if ccrouter.get("configPath"):
|
|
157
|
+
print(f" ccrouter config: {ccrouter['configPath']}")
|
|
158
|
+
if ccrouter.get("homeDir"):
|
|
159
|
+
print(f" ccrouter home: {ccrouter['homeDir']}")
|
|
160
|
+
if ccrouter.get("runtimeDir"):
|
|
161
|
+
print(f" ccrouter runtime: {ccrouter['runtimeDir']}")
|
|
162
|
+
if install_result is not None:
|
|
163
|
+
print(f" run: {install_result.alias}")
|
|
164
|
+
print(f" doctor: {steps['doctor']}")
|
|
165
|
+
if install_result.warning:
|
|
166
|
+
print(f" warning: {install_result.warning}")
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _print_provider_help(args):
|
|
170
|
+
provider_key = _provider_arg(args)
|
|
171
|
+
if not provider_key:
|
|
172
|
+
return False
|
|
173
|
+
provider = get_provider(provider_key)
|
|
174
|
+
print(f"{provider.key}: {provider.label}")
|
|
175
|
+
print("Provider shortcut commands:")
|
|
176
|
+
print(f" ccsilo --provider {provider.key} install")
|
|
177
|
+
print(f" ccsilo --provider {provider.key} update")
|
|
178
|
+
print(f" ccsilo --provider {provider.key} uninstall --yes")
|
|
179
|
+
if provider.credential_env:
|
|
180
|
+
print(f"Credential env: {provider.credential_env}")
|
|
181
|
+
return True
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def cmd_provider_install(args):
|
|
185
|
+
provider_key = _provider_arg(args)
|
|
186
|
+
if not provider_key:
|
|
187
|
+
raise ValueError("Pass --provider for the provider shortcut install command")
|
|
188
|
+
provider = get_provider(provider_key)
|
|
189
|
+
variant = _resolve_provider_variant(provider.key, required=False)
|
|
190
|
+
created = False
|
|
191
|
+
if variant is None:
|
|
192
|
+
result = create_variant(
|
|
193
|
+
name=provider_default_variant_name(provider.key),
|
|
194
|
+
provider_key=provider.key,
|
|
195
|
+
claude_version=args.claude_version,
|
|
196
|
+
credential_env=args.credential_env,
|
|
197
|
+
api_key=args.api_key,
|
|
198
|
+
store_secret=args.store_secret,
|
|
199
|
+
ccrouter_mode=args.ccrouter_mode,
|
|
200
|
+
ccrouter_config=args.ccrouter_config,
|
|
201
|
+
ccrouter_package=args.ccrouter_package,
|
|
202
|
+
ccrouter_port=args.ccrouter_port,
|
|
203
|
+
ccrouter_autostart=args.ccrouter_autostart,
|
|
204
|
+
)
|
|
205
|
+
variant = result.variant
|
|
206
|
+
created = True
|
|
207
|
+
install_result = install_variant_command(
|
|
208
|
+
variant,
|
|
209
|
+
alias=args.alias or provider.key,
|
|
210
|
+
bin_dir=args.bin_dir,
|
|
211
|
+
yes=args.yes,
|
|
212
|
+
)
|
|
213
|
+
if args.json:
|
|
214
|
+
print_json(_provider_payload(variant, provider.key, install_result=install_result))
|
|
215
|
+
else:
|
|
216
|
+
_print_provider_summary("created" if created else "installed", variant, provider.key, install_result=install_result)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def cmd_provider_update(args):
|
|
220
|
+
provider_key = _provider_arg(args)
|
|
221
|
+
if not provider_key:
|
|
222
|
+
raise ValueError("Pass --provider for the provider shortcut update command")
|
|
223
|
+
provider = get_provider(provider_key)
|
|
224
|
+
variant = _resolve_provider_variant(provider.key, required=True)
|
|
225
|
+
result = update_variants(variant.name, claude_version=args.claude_version)[0]
|
|
226
|
+
install_result = install_variant_command(result.variant, alias=provider.key, yes=args.yes)
|
|
227
|
+
if args.json:
|
|
228
|
+
print_json(_provider_payload(result.variant, provider.key, install_result=install_result))
|
|
229
|
+
else:
|
|
230
|
+
_print_provider_summary("updated", result.variant, provider.key, install_result=install_result)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def cmd_provider_uninstall(args):
|
|
234
|
+
provider_key = _provider_arg(args)
|
|
235
|
+
if not provider_key:
|
|
236
|
+
raise ValueError("Pass --provider for the provider shortcut uninstall command")
|
|
237
|
+
provider = get_provider(provider_key)
|
|
238
|
+
variant = _resolve_provider_variant(provider.key, required=True)
|
|
239
|
+
removed = remove_variant(variant.name, yes=args.yes)
|
|
240
|
+
if args.json:
|
|
241
|
+
print_json({"provider": provider.key, "variant": variant.variant_id, "removed": removed})
|
|
242
|
+
else:
|
|
243
|
+
print(f"[+] Removed provider setup: {variant.variant_id}" if removed else f"[*] No setup found: {variant.variant_id}")
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def cmd_variant(args, variant_parser):
|
|
247
|
+
"""Dispatch ``variant <subcommand>`` against the variant helpers above.
|
|
248
|
+
|
|
249
|
+
Names like ``create_variant`` are looked up via this module's globals so
|
|
250
|
+
test fixtures that ``monkeypatch.setattr(cli, "create_variant", fake)``
|
|
251
|
+
take effect.
|
|
252
|
+
"""
|
|
253
|
+
sub = args.variant_command
|
|
254
|
+
if sub == "providers":
|
|
255
|
+
providers = list_variant_providers()
|
|
256
|
+
if args.json:
|
|
257
|
+
print_json(providers)
|
|
258
|
+
elif args.ascii_art or args.quote_blocks:
|
|
259
|
+
art_key = "asciiArtQuoteBlock" if args.quote_blocks else "asciiArt"
|
|
260
|
+
for index, provider in enumerate(providers):
|
|
261
|
+
if index:
|
|
262
|
+
print()
|
|
263
|
+
print(f"{provider['key']}: {provider['label']}")
|
|
264
|
+
print(provider.get(art_key) or "")
|
|
265
|
+
else:
|
|
266
|
+
for provider in providers:
|
|
267
|
+
print(f"{provider['key']}: {provider['label']} - {provider['description']}")
|
|
268
|
+
elif sub == "mcp":
|
|
269
|
+
catalog = list_mcp_catalog(provider_key=args.provider or "")
|
|
270
|
+
if args.json:
|
|
271
|
+
print_json(catalog)
|
|
272
|
+
else:
|
|
273
|
+
print("Provider MCP servers:")
|
|
274
|
+
for item in catalog["providerMcpServers"]:
|
|
275
|
+
provider = item.get("providerKey") or "?"
|
|
276
|
+
print(f" {provider}:{item['id']} auto-enabled")
|
|
277
|
+
print("Optional MCP servers:")
|
|
278
|
+
for item in catalog["optionalMcpServers"]:
|
|
279
|
+
env = ", ".join(item.get("requiredEnv") or [])
|
|
280
|
+
suffix = f" env:{env}" if env else ""
|
|
281
|
+
print(f" {item['id']}: {item['name']}{suffix}")
|
|
282
|
+
print("Plugin recommendations:")
|
|
283
|
+
print(" " + ", ".join(catalog["pluginRecommendations"]))
|
|
284
|
+
elif sub == "create":
|
|
285
|
+
result = create_variant(
|
|
286
|
+
name=args.name,
|
|
287
|
+
provider_key=args.provider,
|
|
288
|
+
claude_version=args.claude_version,
|
|
289
|
+
patch_profile_id=args.patch_profile,
|
|
290
|
+
tweaks=args.tweak,
|
|
291
|
+
base_url=args.base_url,
|
|
292
|
+
credential_env=args.credential_env,
|
|
293
|
+
api_key=args.api_key,
|
|
294
|
+
store_secret=args.store_secret,
|
|
295
|
+
bin_dir=args.bin_dir,
|
|
296
|
+
force=args.force,
|
|
297
|
+
model_overrides=model_overrides_from_args(args),
|
|
298
|
+
extra_env=args.extra_env,
|
|
299
|
+
tweak_options=tweak_options_from_args(args),
|
|
300
|
+
mcp_ids=args.mcp,
|
|
301
|
+
ccrouter_mode=args.ccrouter_mode,
|
|
302
|
+
ccrouter_config=args.ccrouter_config,
|
|
303
|
+
ccrouter_package=args.ccrouter_package,
|
|
304
|
+
ccrouter_port=args.ccrouter_port,
|
|
305
|
+
ccrouter_autostart=args.ccrouter_autostart,
|
|
306
|
+
model_proxy=args.model_proxy,
|
|
307
|
+
model_proxy_port=args.model_proxy_port,
|
|
308
|
+
source_binary=args.source_binary,
|
|
309
|
+
source_platform=args.source_platform,
|
|
310
|
+
)
|
|
311
|
+
install_result = None
|
|
312
|
+
if args.install:
|
|
313
|
+
install_result = install_variant_command(result.variant)
|
|
314
|
+
if args.json:
|
|
315
|
+
payload = variant_result_payload(result)
|
|
316
|
+
if install_result is not None:
|
|
317
|
+
payload["install"] = install_result_payload(install_result)
|
|
318
|
+
print_json(payload)
|
|
319
|
+
else:
|
|
320
|
+
print(f"[+] Variant created: {result.variant.variant_id}")
|
|
321
|
+
print(f" binary: {result.binary_path}")
|
|
322
|
+
print(f" workspace: {workspace_root()}")
|
|
323
|
+
print(f" wrapper: {result.wrapper_path}")
|
|
324
|
+
print(f" run: ccsilo variant run {result.variant.variant_id} --")
|
|
325
|
+
print(f" doctor: ccsilo variant doctor {result.variant.variant_id}")
|
|
326
|
+
if install_result is not None:
|
|
327
|
+
print(f" installed: {install_result.path}")
|
|
328
|
+
print(f" run installed command: {install_result.alias}")
|
|
329
|
+
if install_result.warning:
|
|
330
|
+
print(f" warning: {install_result.warning}")
|
|
331
|
+
elif sub == "install":
|
|
332
|
+
variant = load_variant(variant_id_from_name(args.name))
|
|
333
|
+
result = install_variant_command(
|
|
334
|
+
variant,
|
|
335
|
+
alias=args.alias,
|
|
336
|
+
bin_dir=args.bin_dir,
|
|
337
|
+
yes=args.yes,
|
|
338
|
+
)
|
|
339
|
+
if args.json:
|
|
340
|
+
print_json(install_result_payload(result))
|
|
341
|
+
else:
|
|
342
|
+
print(f"[+] Installed command: {result.path}")
|
|
343
|
+
print(f" target: {result.target}")
|
|
344
|
+
print(f" status: {result.status}")
|
|
345
|
+
print(f" run: {result.alias}")
|
|
346
|
+
print(f" doctor: ccsilo variant doctor {variant.variant_id}")
|
|
347
|
+
if result.warning:
|
|
348
|
+
print(f" warning: {result.warning}")
|
|
349
|
+
elif sub == "list":
|
|
350
|
+
variants = scan_variants()
|
|
351
|
+
if args.json:
|
|
352
|
+
print_json([variant_payload(variant) for variant in variants])
|
|
353
|
+
else:
|
|
354
|
+
for variant in variants:
|
|
355
|
+
source = variant.manifest.get("source", {})
|
|
356
|
+
provider = variant.manifest.get("provider", {})
|
|
357
|
+
print(
|
|
358
|
+
f"{variant.variant_id}: {provider.get('key')} {source.get('version')} "
|
|
359
|
+
f"-> {variant.manifest.get('paths', {}).get('wrapper')}"
|
|
360
|
+
)
|
|
361
|
+
elif sub == "show":
|
|
362
|
+
variant = load_variant(args.name)
|
|
363
|
+
print_json(variant_payload(variant))
|
|
364
|
+
elif sub == "apply":
|
|
365
|
+
result = apply_variant(args.name)
|
|
366
|
+
if args.json:
|
|
367
|
+
print_json(variant_result_payload(result))
|
|
368
|
+
else:
|
|
369
|
+
print(f"[+] Variant applied: {result.variant.variant_id}")
|
|
370
|
+
print(f" wrapper: {result.wrapper_path}")
|
|
371
|
+
elif sub == "update":
|
|
372
|
+
results = update_variants(
|
|
373
|
+
args.name,
|
|
374
|
+
all_variants=args.all,
|
|
375
|
+
claude_version=args.claude_version,
|
|
376
|
+
source_binary=args.source_binary,
|
|
377
|
+
source_platform=args.source_platform,
|
|
378
|
+
)
|
|
379
|
+
if args.json:
|
|
380
|
+
print_json([variant_result_payload(result) for result in results])
|
|
381
|
+
else:
|
|
382
|
+
for result in results:
|
|
383
|
+
print(f"[+] Variant updated: {result.variant.variant_id}")
|
|
384
|
+
elif sub == "remove":
|
|
385
|
+
removed = remove_variant(args.name, yes=args.yes)
|
|
386
|
+
print(f"[+] Removed variant: {args.name}" if removed else f"[*] No variant found: {args.name}")
|
|
387
|
+
elif sub == "doctor":
|
|
388
|
+
report = doctor_variant(args.name, all_variants=args.all)
|
|
389
|
+
if args.json:
|
|
390
|
+
print_json(report)
|
|
391
|
+
else:
|
|
392
|
+
for item in report:
|
|
393
|
+
status = "ok" if item["ok"] else "failed"
|
|
394
|
+
print(f"{item['id']}: {status}")
|
|
395
|
+
for check in item["checks"]:
|
|
396
|
+
mark = "ok" if check["ok"] else "missing"
|
|
397
|
+
print(f" {check['name']}: {mark} {check['path']}")
|
|
398
|
+
elif sub == "run":
|
|
399
|
+
variant_args = list(args.variant_args or [])
|
|
400
|
+
if variant_args and variant_args[0] == "--":
|
|
401
|
+
variant_args = variant_args[1:]
|
|
402
|
+
sys.exit(run_variant(args.name, variant_args))
|
|
403
|
+
else:
|
|
404
|
+
variant_parser.print_help()
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def cmd_uninstall(args):
|
|
408
|
+
workspace = workspace_root()
|
|
409
|
+
planned = workspace_managed_install_records()
|
|
410
|
+
if not args.yes:
|
|
411
|
+
print("This will remove:")
|
|
412
|
+
for item in planned:
|
|
413
|
+
print(f" symlink: {item.path} -> {item.target}")
|
|
414
|
+
print(f" workspace: {workspace}")
|
|
415
|
+
response = input("Type uninstall to continue: ")
|
|
416
|
+
if response != "uninstall":
|
|
417
|
+
print("[*] Uninstall cancelled.")
|
|
418
|
+
return
|
|
419
|
+
result = uninstall_workspace(yes=True)
|
|
420
|
+
if args.json:
|
|
421
|
+
print_json(uninstall_result_payload(result))
|
|
422
|
+
else:
|
|
423
|
+
print(f"[+] Removed workspace: {result.workspace}" if result.removed_workspace else f"[*] Workspace already absent: {result.workspace}")
|
|
424
|
+
for item in result.removed_symlinks:
|
|
425
|
+
print(f"[+] Removed symlink: {item.path}")
|
|
426
|
+
for item in result.skipped_symlinks:
|
|
427
|
+
print(f"[*] Skipped symlink: {item.path} ({item.reason})")
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def cmd_paths(args):
|
|
431
|
+
command_path = which("ccsilo") or ""
|
|
432
|
+
if not command_path and sys.argv:
|
|
433
|
+
invoked = Path(sys.argv[0]).expanduser()
|
|
434
|
+
if invoked.name == "ccsilo" and (
|
|
435
|
+
invoked.is_absolute() or invoked.parent != Path(".")
|
|
436
|
+
):
|
|
437
|
+
command_path = str(invoked)
|
|
438
|
+
payload = {
|
|
439
|
+
"command": command_path,
|
|
440
|
+
"workspace": str(workspace_root()),
|
|
441
|
+
"workspaceOverride": bool(os.environ.get("CCSILO_WORKSPACE")),
|
|
442
|
+
"workspaceOverrideEnv": "CCSILO_WORKSPACE",
|
|
443
|
+
}
|
|
444
|
+
if args.json:
|
|
445
|
+
print_json(payload)
|
|
446
|
+
return
|
|
447
|
+
print(f"command: {command_path or 'not found on PATH'}")
|
|
448
|
+
print(f"workspace: {payload['workspace']}")
|
|
449
|
+
if payload["workspaceOverride"]:
|
|
450
|
+
print("workspace source: CCSILO_WORKSPACE")
|
|
451
|
+
else:
|
|
452
|
+
print("workspace source: platform user data directory")
|
|
453
|
+
print("override: set CCSILO_WORKSPACE=/path/to/workspace")
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def main():
|
|
457
|
+
parser, patch_parser, variant_parser = build_parser()
|
|
458
|
+
|
|
459
|
+
if len(sys.argv) == 1:
|
|
460
|
+
if sys.stdin.isatty() and sys.stdout.isatty():
|
|
461
|
+
from .tui import run_tui
|
|
462
|
+
|
|
463
|
+
try:
|
|
464
|
+
run_tui()
|
|
465
|
+
except Exception as exc:
|
|
466
|
+
print(f"[!] Error: {exc}")
|
|
467
|
+
sys.exit(1)
|
|
468
|
+
return
|
|
469
|
+
parser.print_help()
|
|
470
|
+
return
|
|
471
|
+
|
|
472
|
+
args = parser.parse_args()
|
|
473
|
+
|
|
474
|
+
try:
|
|
475
|
+
if args.command in _SIMPLE_HANDLERS:
|
|
476
|
+
_SIMPLE_HANDLERS[args.command](args)
|
|
477
|
+
elif args.command == "install":
|
|
478
|
+
cmd_provider_install(args)
|
|
479
|
+
elif args.command == "update":
|
|
480
|
+
cmd_provider_update(args)
|
|
481
|
+
elif args.command == "patch":
|
|
482
|
+
_handlers.cmd_patch(args, patch_parser)
|
|
483
|
+
elif args.command == "variant":
|
|
484
|
+
cmd_variant(args, variant_parser)
|
|
485
|
+
elif args.command == "uninstall" and _provider_arg(args):
|
|
486
|
+
cmd_provider_uninstall(args)
|
|
487
|
+
elif args.command == "uninstall":
|
|
488
|
+
cmd_uninstall(args)
|
|
489
|
+
elif args.command == "paths":
|
|
490
|
+
cmd_paths(args)
|
|
491
|
+
elif _print_provider_help(args):
|
|
492
|
+
return
|
|
493
|
+
else:
|
|
494
|
+
parser.print_help()
|
|
495
|
+
except Exception as exc:
|
|
496
|
+
print(f"[!] Error: {exc}")
|
|
497
|
+
sys.exit(1)
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
if __name__ == "__main__":
|
|
501
|
+
main()
|