aisc-cli 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 (92) hide show
  1. aisc/VERSION +1 -0
  2. aisc/__init__.py +49 -0
  3. aisc/__main__.py +6 -0
  4. aisc/adapters/__init__.py +1 -0
  5. aisc/adapters/config_reader.py +215 -0
  6. aisc/adapters/container_registry.py +718 -0
  7. aisc/adapters/data_root_store.py +296 -0
  8. aisc/adapters/docker_.py +1183 -0
  9. aisc/adapters/docker_gateway.py +1244 -0
  10. aisc/adapters/docker_sdk_backed.py +274 -0
  11. aisc/adapters/maintenance_lock.py +97 -0
  12. aisc/adapters/state_file.py +184 -0
  13. aisc/adapters/system.py +101 -0
  14. aisc/adapters/windows_config_reader.py +249 -0
  15. aisc/adapters/workspace_lease_store.py +189 -0
  16. aisc/application/__init__.py +1 -0
  17. aisc/application/artifact.py +265 -0
  18. aisc/application/bundle_fetch.py +836 -0
  19. aisc/application/cc_switch_provider.py +245 -0
  20. aisc/application/cc_switch_resolver.py +363 -0
  21. aisc/application/config_service.py +452 -0
  22. aisc/application/conversation.py +516 -0
  23. aisc/application/data_migration.py +440 -0
  24. aisc/application/data_root.py +275 -0
  25. aisc/application/docker_lifecycle.py +659 -0
  26. aisc/application/doctor.py +821 -0
  27. aisc/application/legacy_scan.py +252 -0
  28. aisc/application/network_subscription.py +642 -0
  29. aisc/application/profile_service.py +105 -0
  30. aisc/application/provider.py +92 -0
  31. aisc/application/repair.py +387 -0
  32. aisc/application/resources.py +270 -0
  33. aisc/application/runtime.py +1826 -0
  34. aisc/application/session.py +320 -0
  35. aisc/application/tool_versions.py +210 -0
  36. aisc/application/toolchain.py +91 -0
  37. aisc/application/update.py +286 -0
  38. aisc/application/usage.py +305 -0
  39. aisc/application/version.py +133 -0
  40. aisc/application/web_gateway.py +459 -0
  41. aisc/application/workspace_reconcile.py +318 -0
  42. aisc/applog.py +103 -0
  43. aisc/cli/__init__.py +1 -0
  44. aisc/cli/build_progress_parser.py +217 -0
  45. aisc/cli/commands/__init__.py +1 -0
  46. aisc/cli/commands/agents.py +117 -0
  47. aisc/cli/commands/artifact.py +210 -0
  48. aisc/cli/commands/build.py +566 -0
  49. aisc/cli/commands/bundle.py +107 -0
  50. aisc/cli/commands/cc_switch.py +158 -0
  51. aisc/cli/commands/config.py +104 -0
  52. aisc/cli/commands/container.py +787 -0
  53. aisc/cli/commands/conversation.py +72 -0
  54. aisc/cli/commands/data_root.py +84 -0
  55. aisc/cli/commands/logs.py +89 -0
  56. aisc/cli/commands/network.py +132 -0
  57. aisc/cli/commands/profile.py +52 -0
  58. aisc/cli/commands/provider.py +49 -0
  59. aisc/cli/commands/run.py +494 -0
  60. aisc/cli/commands/runs.py +125 -0
  61. aisc/cli/commands/runtime.py +450 -0
  62. aisc/cli/commands/serve.py +550 -0
  63. aisc/cli/commands/serve_fs.py +369 -0
  64. aisc/cli/commands/session.py +167 -0
  65. aisc/cli/commands/update.py +145 -0
  66. aisc/cli/commands/usage.py +74 -0
  67. aisc/cli/commands/wizard.py +130 -0
  68. aisc/cli/commands/workspaces.py +162 -0
  69. aisc/cli/help_i18n.py +385 -0
  70. aisc/cli/main.py +2926 -0
  71. aisc/cli/output.py +239 -0
  72. aisc/cli/tutorial.py +109 -0
  73. aisc/domain/__init__.py +1 -0
  74. aisc/domain/artifacts.py +279 -0
  75. aisc/domain/cc_switch_release.py +303 -0
  76. aisc/domain/config.py +64 -0
  77. aisc/domain/data_migration.py +259 -0
  78. aisc/domain/data_root.py +124 -0
  79. aisc/domain/docker_ownership.py +148 -0
  80. aisc/domain/gateway.py +168 -0
  81. aisc/domain/models.py +674 -0
  82. aisc/domain/toolchain.py +72 -0
  83. aisc/domain/web_services.py +355 -0
  84. aisc/domain/workspace_lease.py +139 -0
  85. aisc/schemas/__init__.py +1 -0
  86. aisc/schemas/config_schema.py +145 -0
  87. aisc_cli-0.1.0.dist-info/METADATA +420 -0
  88. aisc_cli-0.1.0.dist-info/RECORD +92 -0
  89. aisc_cli-0.1.0.dist-info/WHEEL +5 -0
  90. aisc_cli-0.1.0.dist-info/entry_points.txt +2 -0
  91. aisc_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
  92. aisc_cli-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,158 @@
1
+ """``aisc cc-switch`` command layer (Stage 8d).
2
+
3
+ Thin wrappers over ``aisc.application.cc_switch_provider``: assemble the
4
+ request document (secrets from STDIN per the contract's ``--secret-stdin`` /
5
+ ``--patch-stdin`` semantics — one JSON document on stdin carries both the
6
+ patch fields and the optional ``api_key``), then print the redacted snapshot.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import sys
13
+ from typing import Any, Dict, Optional
14
+
15
+ from aisc.domain.models import CliError
16
+
17
+
18
+ def _read_stdin_request(*, required: bool) -> Dict[str, Any]:
19
+ raw = sys.stdin.read()
20
+ if not raw.strip():
21
+ if required:
22
+ raise CliError(
23
+ message="a JSON request document on stdin is required "
24
+ "(--secret-stdin/--patch-stdin semantics)",
25
+ exit_code=2,
26
+ error_code="AISC_ERR_USAGE",
27
+ )
28
+ return {}
29
+ try:
30
+ data = json.loads(raw)
31
+ except json.JSONDecodeError as exc:
32
+ raise CliError(
33
+ message=f"stdin request is not valid JSON: {exc}",
34
+ exit_code=2,
35
+ error_code="AISC_ERR_USAGE",
36
+ ) from exc
37
+ if not isinstance(data, dict):
38
+ raise CliError(
39
+ message="stdin request must be a JSON object",
40
+ exit_code=2,
41
+ error_code="AISC_ERR_USAGE",
42
+ )
43
+ return data
44
+
45
+
46
+ def cmd_cc_switch_list(args: Any) -> Dict[str, Any]:
47
+ from aisc.application.cc_switch_provider import list_providers
48
+
49
+ return list_providers(
50
+ runtime_id=args.runtime_id,
51
+ agent=args.agent,
52
+ workspace=args.workspace,
53
+ executor=None,
54
+ reveal_id=getattr(args, "reveal_id", None),
55
+ )
56
+
57
+
58
+ def cmd_cc_switch_add(args: Any) -> Dict[str, Any]:
59
+ from aisc.application.cc_switch_provider import add_provider
60
+
61
+ request = _read_stdin_request(required=True)
62
+ request["mode"] = getattr(args, "mode", None) or request.get("mode") or "simple"
63
+ if getattr(args, "provider", None):
64
+ request["provider"] = args.provider
65
+ if getattr(args, "new_id", None):
66
+ request["id"] = args.new_id
67
+ return add_provider(
68
+ runtime_id=args.runtime_id,
69
+ agent=args.agent,
70
+ request=request,
71
+ workspace=args.workspace,
72
+ executor=None,
73
+ )
74
+
75
+
76
+ def cmd_cc_switch_edit(args: Any) -> Dict[str, Any]:
77
+ from aisc.application.cc_switch_provider import edit_provider
78
+
79
+ request = _read_stdin_request(required=True)
80
+ request.setdefault("patch", {})
81
+ if not isinstance(request["patch"], dict):
82
+ raise CliError(
83
+ message="request.patch must be a JSON object",
84
+ exit_code=2,
85
+ error_code="AISC_ERR_USAGE",
86
+ )
87
+ return edit_provider(
88
+ runtime_id=args.runtime_id,
89
+ agent=args.agent,
90
+ provider_id=args.provider_id,
91
+ request=request,
92
+ workspace=args.workspace,
93
+ executor=None,
94
+ )
95
+
96
+
97
+ def cmd_cc_switch_switch(args: Any) -> Dict[str, Any]:
98
+ from aisc.application.cc_switch_provider import switch_provider
99
+
100
+ return switch_provider(
101
+ runtime_id=args.runtime_id,
102
+ agent=args.agent,
103
+ provider_id=args.provider_id,
104
+ workspace=args.workspace,
105
+ executor=None,
106
+ )
107
+
108
+
109
+ def cmd_cc_switch_delete(args: Any) -> Dict[str, Any]:
110
+ from aisc.application.cc_switch_provider import delete_provider
111
+
112
+ if not getattr(args, "confirm", False):
113
+ raise CliError(
114
+ message="delete requires --confirm",
115
+ exit_code=2,
116
+ error_code="AISC_ERR_USAGE",
117
+ )
118
+ return delete_provider(
119
+ runtime_id=args.runtime_id,
120
+ agent=args.agent,
121
+ provider_id=args.provider_id,
122
+ workspace=args.workspace,
123
+ executor=None,
124
+ )
125
+
126
+
127
+ def cmd_cc_switch_fetch_models(args: Any) -> Dict[str, Any]:
128
+ from aisc.application.cc_switch_provider import fetch_models
129
+
130
+ # PP r3: an optional stdin document may carry the form's unsaved
131
+ # api_key — forwarded to the adapter inside the same stdin channel
132
+ # (never argv, never logged).
133
+ request = _read_stdin_request(required=False)
134
+ api_key = str(request.get("api_key") or "")
135
+ # 手测 r2#3: add-mode inline probe — the form's base_url rides the same
136
+ # stdin channel when no provider row exists yet.
137
+ base_url = str(request.get("base_url") or "")
138
+ if base_url:
139
+ request["base_url"] = base_url
140
+ return fetch_models(
141
+ runtime_id=args.runtime_id,
142
+ agent=args.agent,
143
+ provider_id=args.provider_id,
144
+ workspace=args.workspace,
145
+ executor=None,
146
+ request={"api_key": api_key} if api_key else None,
147
+ )
148
+
149
+
150
+ def print_cc_switch_text(data: Optional[Dict[str, Any]]) -> None:
151
+ """Human-readable output: one line per provider (secret-free)."""
152
+ if not isinstance(data, dict):
153
+ return
154
+ for p in data.get("providers", []):
155
+ current = " →" if p.get("is_current") else " "
156
+ key = p.get("api_key_mask") or "no-key"
157
+ print(f"{current} {p.get('id', ''):24} {p.get('name', ''):20} "
158
+ f"{p.get('base_url', '')} | {p.get('model', '')} | {key}")
@@ -0,0 +1,104 @@
1
+ """CLI command implementations for ``aisc config validate`` and ``aisc config effective``.
2
+
3
+ Thin wrapper — all classification logic lives in config_service.ServiceResult.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import json as _json
9
+ import sys
10
+ from typing import Any, Dict, Optional
11
+
12
+ from aisc.application.config_service import (
13
+ run_config_validate, run_config_effective, ServiceResult,
14
+ ERR_CONFIG_INVALID, ERR_CONFIG_MISSING, ERR_PERMISSION_DENIED, ERR_GENERAL,
15
+ )
16
+
17
+
18
+ def cmd_config_validate(*, explicit_config=None, workspace=None, home=None,
19
+ env=None, platform=None) -> ServiceResult:
20
+ return run_config_validate(explicit_config=explicit_config, workspace=workspace,
21
+ home=home, env=env, platform_name=platform)
22
+
23
+
24
+ def cmd_config_effective(*, explicit_config=None, workspace=None, home=None,
25
+ env=None, platform=None) -> ServiceResult:
26
+ return run_config_effective(explicit_config=explicit_config, workspace=workspace,
27
+ home=home, env=env, platform_name=platform)
28
+
29
+
30
+ # ---------------------------------------------------------------------------
31
+ # Text output helpers
32
+ # ---------------------------------------------------------------------------
33
+
34
+ def _escape_path(p: str) -> str:
35
+ """Escape control characters in path for safe text output."""
36
+ if not p:
37
+ return ""
38
+ out = []
39
+ for ch in p:
40
+ cp = ord(ch)
41
+ if cp < 0x20 or cp == 0x7f:
42
+ out.append(f"\\x{cp:02x}")
43
+ elif cp == 0x5c:
44
+ out.append("\\\\")
45
+ else:
46
+ out.append(ch)
47
+ return "".join(out)
48
+
49
+
50
+ def print_validate_text(result: ServiceResult) -> None:
51
+ """Print validate result (stdout). Non-zero also prints stderr summary."""
52
+ print("=== Config Validate ===")
53
+ print()
54
+ for s in result.data.get("sources", []):
55
+ label = s.get("status", "?").upper()
56
+ print(f" [{label}] {_escape_path(s.get('path', ''))}")
57
+ if s.get("error"):
58
+ print(f" {s['error']}")
59
+ print()
60
+ print(f"Valid: {'yes' if result.valid else 'NO'}")
61
+ issues = result.data.get("issues", [])
62
+ if issues:
63
+ print()
64
+ print("Issues:")
65
+ for i in issues:
66
+ sev = i["severity"].upper()
67
+ print(f" [{sev}] {i['source']}:{i['path']}: {i['message']}")
68
+ print()
69
+
70
+ if result.exit_code != 0:
71
+ print(f"Error: {result.error_message}", file=sys.stderr)
72
+
73
+
74
+ def print_effective_text(result: ServiceResult) -> None:
75
+ """Print effective result (stdout). Non-zero also prints stderr summary."""
76
+ print("=== Config Effective ===")
77
+ print()
78
+ for s in result.data.get("sources", []):
79
+ label = s.get("status", "?").upper()
80
+ print(f" [{label}] {_escape_path(s.get('path', ''))}")
81
+ print()
82
+ eff = result.data.get("effective")
83
+ if eff is None:
84
+ print("Effective: (none)")
85
+ else:
86
+ print("Effective:")
87
+ print(_json.dumps(eff, indent=2, ensure_ascii=False))
88
+ prov = result.data.get("provenance", {})
89
+ if prov:
90
+ print()
91
+ print("Provenance:")
92
+ for k, v in sorted(prov.items()):
93
+ print(f" {k}: {v}")
94
+ issues = result.data.get("issues", [])
95
+ if issues:
96
+ print()
97
+ print("Issues:")
98
+ for i in issues:
99
+ sev = i["severity"].upper()
100
+ print(f" [{sev}] {i['source']}:{i['path']}: {i['message']}")
101
+ print()
102
+
103
+ if result.exit_code != 0:
104
+ print(f"Error: {result.error_message}", file=sys.stderr)