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.
Files changed (148) hide show
  1. ccsilo/__init__.py +40 -0
  2. ccsilo/__main__.py +501 -0
  3. ccsilo/_utils.py +177 -0
  4. ccsilo/binary_patcher/__init__.py +44 -0
  5. ccsilo/binary_patcher/bun_compat.py +136 -0
  6. ccsilo/binary_patcher/codesign.py +37 -0
  7. ccsilo/binary_patcher/elf_resize.py +156 -0
  8. ccsilo/binary_patcher/index.py +207 -0
  9. ccsilo/binary_patcher/js_patch.py +104 -0
  10. ccsilo/binary_patcher/macho_resize.py +158 -0
  11. ccsilo/binary_patcher/pe_resize.py +74 -0
  12. ccsilo/binary_patcher/prompts.py +184 -0
  13. ccsilo/binary_patcher/repack.py +34 -0
  14. ccsilo/binary_patcher/replace_entry.py +73 -0
  15. ccsilo/binary_patcher/strip_bun_wrapper.py +28 -0
  16. ccsilo/binary_patcher/theme.py +131 -0
  17. ccsilo/binary_patcher/unpack_and_patch.py +249 -0
  18. ccsilo/bun_extract/__init__.py +19 -0
  19. ccsilo/bun_extract/constants.py +49 -0
  20. ccsilo/bun_extract/elf.py +12 -0
  21. ccsilo/bun_extract/extract.py +162 -0
  22. ccsilo/bun_extract/macho.py +148 -0
  23. ccsilo/bun_extract/parser.py +232 -0
  24. ccsilo/bun_extract/pe.py +46 -0
  25. ccsilo/bun_extract/replace.py +47 -0
  26. ccsilo/bun_extract/types.py +57 -0
  27. ccsilo/bundler.py +376 -0
  28. ccsilo/cli/__init__.py +11 -0
  29. ccsilo/cli/handlers.py +162 -0
  30. ccsilo/cli/parsers.py +214 -0
  31. ccsilo/cli/payloads.py +101 -0
  32. ccsilo/data/download-index.seed.json +95 -0
  33. ccsilo/download_index.py +119 -0
  34. ccsilo/download_picker.py +244 -0
  35. ccsilo/downloader.py +393 -0
  36. ccsilo/extractor.py +116 -0
  37. ccsilo/model_proxy.py +404 -0
  38. ccsilo/patch_workflow.py +255 -0
  39. ccsilo/patcher.py +434 -0
  40. ccsilo/patches/__init__.py +195 -0
  41. ccsilo/patches/_pinned_default.py +3 -0
  42. ccsilo/patches/_registry.py +93 -0
  43. ccsilo/patches/_versions.py +131 -0
  44. ccsilo/patches/agents_md.py +101 -0
  45. ccsilo/patches/allow_custom_agent_models.py +66 -0
  46. ccsilo/patches/auto_accept_plan_mode.py +49 -0
  47. ccsilo/patches/filter_scroll_escape_sequences.py +67 -0
  48. ccsilo/patches/hide_ctrl_g.py +28 -0
  49. ccsilo/patches/hide_startup_banner.py +50 -0
  50. ccsilo/patches/hide_startup_clawd.py +46 -0
  51. ccsilo/patches/input_border_box.py +76 -0
  52. ccsilo/patches/mcp_startup.py +59 -0
  53. ccsilo/patches/model_customizations.py +60 -0
  54. ccsilo/patches/opusplan1m.py +118 -0
  55. ccsilo/patches/patches_applied_indication.py +23 -0
  56. ccsilo/patches/prompt_overlays.py +31 -0
  57. ccsilo/patches/remember_skill.py +42 -0
  58. ccsilo/patches/session_memory.py +110 -0
  59. ccsilo/patches/show_more_items.py +47 -0
  60. ccsilo/patches/statusline_update_throttle.py +73 -0
  61. ccsilo/patches/suppress_line_numbers.py +89 -0
  62. ccsilo/patches/suppress_model_launch_notice.py +45 -0
  63. ccsilo/patches/suppress_native_installer_warning.py +36 -0
  64. ccsilo/patches/suppress_prompt_caching_warning.py +44 -0
  65. ccsilo/patches/suppress_rate_limit_options.py +30 -0
  66. ccsilo/patches/system_prompts.py +156 -0
  67. ccsilo/patches/themes.py +29 -0
  68. ccsilo/patches/thinking_visibility.py +30 -0
  69. ccsilo/patches/token_count_rounding.py +46 -0
  70. ccsilo/providers/__init__.py +63 -0
  71. ccsilo/providers/config.py +168 -0
  72. ccsilo/providers/loader.py +214 -0
  73. ccsilo/providers/mcp_catalog.py +150 -0
  74. ccsilo/providers/model_discovery.py +73 -0
  75. ccsilo/providers/registry/9router.json +55 -0
  76. ccsilo/providers/registry/alibaba.json +56 -0
  77. ccsilo/providers/registry/anthropic.json +56 -0
  78. ccsilo/providers/registry/ccr-oauth.json +68 -0
  79. ccsilo/providers/registry/ccrouter.json +67 -0
  80. ccsilo/providers/registry/cerebras.json +58 -0
  81. ccsilo/providers/registry/custom.json +52 -0
  82. ccsilo/providers/registry/deepseek.json +57 -0
  83. ccsilo/providers/registry/gatewayz.json +56 -0
  84. ccsilo/providers/registry/kimi.json +56 -0
  85. ccsilo/providers/registry/lmstudio.json +64 -0
  86. ccsilo/providers/registry/local-custom.json +60 -0
  87. ccsilo/providers/registry/minimax-cn.json +57 -0
  88. ccsilo/providers/registry/minimax.json +72 -0
  89. ccsilo/providers/registry/mirror.json +56 -0
  90. ccsilo/providers/registry/nanogpt.json +56 -0
  91. ccsilo/providers/registry/ollama.json +66 -0
  92. ccsilo/providers/registry/omlx.json +64 -0
  93. ccsilo/providers/registry/openrouter.json +56 -0
  94. ccsilo/providers/registry/poe.json +56 -0
  95. ccsilo/providers/registry/vercel.json +58 -0
  96. ccsilo/providers/registry/zai.json +94 -0
  97. ccsilo/providers/schema.py +331 -0
  98. ccsilo/tui/__init__.py +471 -0
  99. ccsilo/tui/_const.py +34 -0
  100. ccsilo/tui/_runtime.py +21 -0
  101. ccsilo/tui/busy.py +97 -0
  102. ccsilo/tui/dashboard.py +215 -0
  103. ccsilo/tui/dispatch.py +693 -0
  104. ccsilo/tui/keys.py +114 -0
  105. ccsilo/tui/nav.py +305 -0
  106. ccsilo/tui/options.py +6 -0
  107. ccsilo/tui/options_dashboard.py +217 -0
  108. ccsilo/tui/options_setup.py +214 -0
  109. ccsilo/tui/options_tweaks.py +303 -0
  110. ccsilo/tui/options_variant.py +527 -0
  111. ccsilo/tui/render_frame.py +429 -0
  112. ccsilo/tui/render_labels.py +963 -0
  113. ccsilo/tui/rendering.py +4 -0
  114. ccsilo/tui/setup_actions.py +951 -0
  115. ccsilo/tui/state.py +229 -0
  116. ccsilo/tui/themes.py +191 -0
  117. ccsilo/tui/variant_actions.py +224 -0
  118. ccsilo/variant_tweaks.py +30 -0
  119. ccsilo/variants/__init__.py +121 -0
  120. ccsilo/variants/ascii/cerebras.txt +31 -0
  121. ccsilo/variants/ascii/deepseek.txt +22 -0
  122. ccsilo/variants/ascii/minimax-cn.txt +14 -0
  123. ccsilo/variants/ascii/minimax.txt +14 -0
  124. ccsilo/variants/ascii/ollama.txt +40 -0
  125. ccsilo/variants/ascii/openrouter.txt +36 -0
  126. ccsilo/variants/ascii/zai.txt +21 -0
  127. ccsilo/variants/build.py +491 -0
  128. ccsilo/variants/builder.py +156 -0
  129. ccsilo/variants/ccrouter.py +385 -0
  130. ccsilo/variants/constants.py +24 -0
  131. ccsilo/variants/install.py +320 -0
  132. ccsilo/variants/lifecycle.py +541 -0
  133. ccsilo/variants/model.py +231 -0
  134. ccsilo/variants/model_updates.py +101 -0
  135. ccsilo/variants/splash.py +176 -0
  136. ccsilo/variants/tweaks.py +376 -0
  137. ccsilo/variants/wrapper.py +413 -0
  138. ccsilo/workspace/__init__.py +146 -0
  139. ccsilo/workspace/artifacts.py +424 -0
  140. ccsilo/workspace/models.py +70 -0
  141. ccsilo/workspace/patches.py +358 -0
  142. ccsilo/workspace/paths.py +183 -0
  143. ccsilo/workspace/settings.py +61 -0
  144. ccsilo-0.1.0.dist-info/METADATA +657 -0
  145. ccsilo-0.1.0.dist-info/RECORD +148 -0
  146. ccsilo-0.1.0.dist-info/WHEEL +5 -0
  147. ccsilo-0.1.0.dist-info/entry_points.txt +2 -0
  148. 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()