compshare-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.
@@ -0,0 +1,124 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import sys
5
+ from datetime import datetime
6
+ from typing import Any, Dict, Iterable, Optional, Sequence, Tuple
7
+
8
+ from rich.console import Console
9
+ from rich.panel import Panel
10
+ from rich.table import Table
11
+
12
+ from compshare_cli.i18n import tr
13
+
14
+ SENSITIVE_KEYS = {
15
+ "privatekey",
16
+ "private_key",
17
+ }
18
+
19
+
20
+ def sanitized(value: Any) -> Any:
21
+ if isinstance(value, dict):
22
+ return {
23
+ key: "***" if key.casefold() in SENSITIVE_KEYS else sanitized(item)
24
+ for key, item in value.items()
25
+ }
26
+ if isinstance(value, list):
27
+ return [sanitized(item) for item in value]
28
+ return value
29
+
30
+
31
+ class Renderer:
32
+ def __init__(self, json_output: bool) -> None:
33
+ self.json_output = json_output
34
+ self.console = Console()
35
+
36
+ def data(
37
+ self,
38
+ response: Dict[str, Any],
39
+ *,
40
+ rows: Optional[Iterable[Dict[str, Any]]] = None,
41
+ columns: Optional[Sequence[Tuple[str, str]]] = None,
42
+ ) -> None:
43
+ safe = sanitized(response)
44
+ if self.json_output:
45
+ sys.stdout.write(json.dumps(safe, ensure_ascii=False, separators=(",", ":")) + "\n")
46
+ return
47
+ if rows is not None and columns:
48
+ self.table(rows, columns)
49
+ return
50
+ self.console.print_json(json.dumps(safe, ensure_ascii=False, default=str))
51
+
52
+ def table(
53
+ self,
54
+ rows: Iterable[Dict[str, Any]],
55
+ columns: Sequence[Tuple[str, str]],
56
+ ) -> None:
57
+ table = Table(show_header=True, header_style="bold")
58
+ for key, label in columns:
59
+ justify = "right" if key in {"CPU", "GPU", "Size", "Price", "InstancePrice"} else "left"
60
+ table.add_column(tr(label), justify=justify)
61
+ count = 0
62
+ for row in rows:
63
+ count += 1
64
+ table.add_row(*(self._cell(row.get(key), key=key) for key, _ in columns))
65
+ if count:
66
+ self.console.print(table)
67
+ else:
68
+ self.console.print(
69
+ tr("No results. Try adjusting the filters or checking the selected region.")
70
+ )
71
+
72
+ def success(self, message: str, response: Dict[str, Any]) -> None:
73
+ if self.json_output:
74
+ self.data(response)
75
+ else:
76
+ self.console.print(f"[green]✓[/green] {message}")
77
+
78
+ def details(
79
+ self,
80
+ title: str,
81
+ fields: Sequence[Tuple[str, Any]],
82
+ *,
83
+ response: Optional[Dict[str, Any]] = None,
84
+ ) -> None:
85
+ """Render a compact human detail card while preserving raw JSON output."""
86
+ if self.json_output:
87
+ self.data(response or {key: value for key, value in fields})
88
+ return
89
+ grid = Table.grid(padding=(0, 2))
90
+ grid.add_column(style="bold cyan", no_wrap=True)
91
+ grid.add_column()
92
+ for label, value in fields:
93
+ grid.add_row(tr(label), self._cell(value, key=label))
94
+ self.console.print(Panel(grid, title=tr(title), border_style="blue"))
95
+
96
+ def error(self, message: str, *, details: Optional[Dict[str, Any]] = None) -> None:
97
+ if self.json_output:
98
+ payload: Dict[str, Any] = {"ok": False, "error": message}
99
+ if details:
100
+ payload["details"] = sanitized(details)
101
+ sys.stdout.write(json.dumps(payload, ensure_ascii=False, separators=(",", ":")) + "\n")
102
+ else:
103
+ Console(stderr=True).print(f"[red]{tr('Error')}:[/red] {message}")
104
+
105
+ @staticmethod
106
+ def _cell(value: Any, *, key: Optional[str] = None) -> str:
107
+ if value is None:
108
+ return "-"
109
+ if isinstance(value, bool):
110
+ return tr("yes") if value else tr("no")
111
+ if isinstance(value, (dict, list)):
112
+ return json.dumps(value, ensure_ascii=False, separators=(",", ":"))
113
+ if key and "time" in key.casefold() and isinstance(value, (int, float)):
114
+ return datetime.fromtimestamp(value).astimezone().strftime("%Y-%m-%d %H:%M:%S")
115
+ if key and key.casefold() in {"state", "status"}:
116
+ state = str(value)
117
+ normalized = state.casefold()
118
+ if normalized in {"running", "available", "success", "succeeded"}:
119
+ return f"[green]{state}[/green]"
120
+ if normalized in {"failed", "error", "terminated"}:
121
+ return f"[red]{state}[/red]"
122
+ if normalized not in {"stopped", "closed"}:
123
+ return f"[yellow]{state}[/yellow]"
124
+ return str(value)
@@ -0,0 +1,93 @@
1
+ from __future__ import annotations
2
+
3
+ import base64
4
+ import re
5
+ import time
6
+ from datetime import datetime, timezone
7
+ from pathlib import Path
8
+ from typing import Any, Dict, Iterable, List, Optional
9
+
10
+ from compshare_cli.errors import UsageError
11
+ from compshare_cli.i18n import tr
12
+
13
+ _SIZE_RE = re.compile(r"^\s*(\d+)\s*(mib|mb|gib|gb|m|g)?\s*$", re.IGNORECASE)
14
+
15
+
16
+ def memory_mib(value: str) -> int:
17
+ match = _SIZE_RE.match(value)
18
+ if not match:
19
+ raise UsageError(tr("Invalid memory size: {value}. Example: 64GiB", value=value))
20
+ amount = int(match.group(1))
21
+ unit = (match.group(2) or "gib").lower()
22
+ result = amount if unit in {"mib", "mb", "m"} else amount * 1024
23
+ if result <= 0 or result % 1024 != 0:
24
+ raise UsageError(tr("Memory must be positive and resolve to whole GiB."))
25
+ return result
26
+
27
+
28
+ def disk_gib(value: str) -> int:
29
+ match = _SIZE_RE.match(value)
30
+ if not match:
31
+ raise UsageError(tr("Invalid disk size: {value}. Example: 100GiB", value=value))
32
+ amount = int(match.group(1))
33
+ unit = (match.group(2) or "gib").lower()
34
+ if unit in {"mib", "mb", "m"}:
35
+ if amount % 1024:
36
+ raise UsageError(tr("Disk MiB must resolve to whole GiB."))
37
+ amount //= 1024
38
+ if amount <= 0:
39
+ raise UsageError(tr("Disk size must be positive."))
40
+ return amount
41
+
42
+
43
+ def encode_password(value: str) -> str:
44
+ return base64.b64encode(value.encode("utf-8")).decode("ascii")
45
+
46
+
47
+ def timestamp(value: str) -> int:
48
+ relative = re.fullmatch(r"\s*(\d+)\s*([mhd])\s*", value, re.IGNORECASE)
49
+ if relative:
50
+ amount = int(relative.group(1))
51
+ seconds = {"m": 60, "h": 3600, "d": 86400}[relative.group(2).lower()]
52
+ return int(time.time()) + amount * seconds
53
+ if value.isdigit():
54
+ return int(value)
55
+ normalized = value.replace("Z", "+00:00")
56
+ try:
57
+ parsed = datetime.fromisoformat(normalized)
58
+ except ValueError as exc:
59
+ raise UsageError(
60
+ tr("Time must be a Unix timestamp, ISO 8601 value, or relative value like 30m or 2h.")
61
+ ) from exc
62
+ if parsed.tzinfo is None:
63
+ parsed = parsed.replace(tzinfo=timezone.utc)
64
+ return int(parsed.timestamp())
65
+
66
+
67
+ def compact(values: Dict[str, Any]) -> Dict[str, Any]:
68
+ return {key: value for key, value in values.items() if value is not None}
69
+
70
+
71
+ def split_csv(values: Iterable[str]) -> List[str]:
72
+ result: List[str] = []
73
+ for value in values:
74
+ result.extend(item.strip() for item in value.split(",") if item.strip())
75
+ return result
76
+
77
+
78
+ def read_text(path: Optional[Path]) -> Optional[str]:
79
+ if path is None:
80
+ return None
81
+ try:
82
+ return path.read_text(encoding="utf-8")
83
+ except OSError as exc:
84
+ raise UsageError(tr("Unable to read file {path}: {error}", path=path, error=exc)) from exc
85
+
86
+
87
+ def read_base64(path: Optional[Path]) -> Optional[str]:
88
+ if path is None:
89
+ return None
90
+ try:
91
+ return base64.b64encode(path.read_bytes()).decode("ascii")
92
+ except OSError as exc:
93
+ raise UsageError(tr("Unable to read file {path}: {error}", path=path, error=exc)) from exc
@@ -0,0 +1,34 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from dataclasses import dataclass
5
+ from typing import Optional
6
+
7
+ from compshare_cli.config import DEFAULT_REGION, DEFAULT_ZONE, ConfigStore, Profile
8
+
9
+
10
+ @dataclass
11
+ class Runtime:
12
+ json_output: bool = False
13
+ profile_name: Optional[str] = None
14
+ _profile: Optional[Profile] = None
15
+
16
+ @property
17
+ def profile(self) -> Profile:
18
+ if self._profile is None:
19
+ self._profile = ConfigStore().load_profile(self.profile_name)
20
+ return self._profile
21
+
22
+ @property
23
+ def region(self) -> str:
24
+ explicit = os.environ.get("COMPSHARE_REGION")
25
+ if explicit:
26
+ return explicit
27
+ explicit_zone = os.environ.get("COMPSHARE_ZONE")
28
+ if explicit_zone and "-" in explicit_zone:
29
+ return explicit_zone.rsplit("-", 1)[0]
30
+ return DEFAULT_REGION
31
+
32
+ @property
33
+ def zone(self) -> str:
34
+ return os.environ.get("COMPSHARE_ZONE") or DEFAULT_ZONE
compshare_cli/sdk.py ADDED
@@ -0,0 +1,27 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from typing import Any, Dict
5
+
6
+ from ucloud.client import Client
7
+
8
+ from compshare_cli.config import Profile
9
+
10
+
11
+ class CompShareSDK:
12
+ """Thin adapter around the official UCloud Python SDK.
13
+
14
+ The generic invoke path is intentional. Generated UCompShare request schemas can
15
+ lag behind the public API and silently discard newer fields.
16
+ """
17
+
18
+ def __init__(self, profile: Profile, region: str) -> None:
19
+ logger = logging.getLogger("compshare_cli.ucloud")
20
+ logger.handlers.clear()
21
+ logger.addHandler(logging.NullHandler())
22
+ logger.setLevel(logging.CRITICAL)
23
+ logger.propagate = False
24
+ self._service = Client(profile.sdk_config(region), logger=logger).ucompshare()
25
+
26
+ def invoke(self, action: str, params: Dict[str, Any]) -> Dict[str, Any]:
27
+ return self._service.invoke(action, params)
@@ -0,0 +1,283 @@
1
+ Metadata-Version: 2.4
2
+ Name: compshare-cli
3
+ Version: 0.1.0
4
+ Summary: Command-line client for CompShare GPU compute
5
+ Author: CompShare
6
+ License: Apache-2.0
7
+ Requires-Python: >=3.9
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: rich<15,>=13
10
+ Requires-Dist: typer<1,>=0.12
11
+ Requires-Dist: ucloud-sdk-python3<0.12,>=0.11.103
12
+ Requires-Dist: urllib3<2,>=1.26.18; python_version < "3.10" and platform_system == "Darwin"
13
+ Provides-Extra: dev
14
+ Requires-Dist: pytest<10,>=8; extra == "dev"
15
+ Requires-Dist: pytest-cov<8,>=5; extra == "dev"
16
+ Requires-Dist: ruff<1,>=0.6; extra == "dev"
17
+
18
+ # CompShare CLI
19
+
20
+ 在终端中管理优云智算 GPU 实例、实例镜像、云盘与 US3 挂载。
21
+
22
+ 第一阶段覆盖 `compshare-docs` 中三个公开 API 目录的 51 个 Action:
23
+
24
+ - GPU 实例:27 个 Action
25
+ - 实例镜像:17 个 Action
26
+ - 磁盘与云存储:7 个 Action
27
+
28
+ CLI 使用官方 [`ucloud-sdk-python3`](https://github.com/ucloud/ucloud-sdk-python3) 完成鉴权、参数编码、请求传输与重试。调用层使用 SDK 的通用 `invoke` 接口,因为 CompShare 的公开 API 更新可能早于 SDK 的生成式请求模型;这样不会静默丢弃新参数。
29
+
30
+ ## 安装
31
+
32
+ 要求 Python 3.9 或更高版本。
33
+
34
+ ```bash
35
+ python -m pip install .
36
+ compshare --help
37
+ ```
38
+
39
+ 开发环境:
40
+
41
+ ```bash
42
+ python -m venv .venv
43
+ .venv/bin/python -m pip install --upgrade pip
44
+ .venv/bin/python -m pip install -e '.[dev]'
45
+ .venv/bin/pytest
46
+ ```
47
+
48
+ ## 配置
49
+
50
+ 交互式保存 API 密钥:
51
+
52
+ ```bash
53
+ compshare config --name production
54
+ ```
55
+
56
+ 每个凭证 profile 只保存名称、公钥和私钥,不绑定地域、可用区或项目。配置默认保存到 `~/.config/compshare/config.json`,目录权限为 `0700`,文件权限为 `0600`。支持多个凭证 profile:
57
+
58
+ ```bash
59
+ compshare --profile production instance list
60
+ compshare config list
61
+ compshare config use production
62
+ compshare config delete staging
63
+ compshare config path
64
+ ```
65
+
66
+ 也可以完全通过环境变量运行:
67
+
68
+ ```bash
69
+ export COMPSHARE_PUBLIC_KEY='...'
70
+ export COMPSHARE_PRIVATE_KEY='...'
71
+ export COMPSHARE_REGION='cn-wlcb'
72
+ export COMPSHARE_ZONE='cn-wlcb-01'
73
+ ```
74
+
75
+ 其中公私钥属于凭证;Region 和 Zone 只是请求位置。支持的环境变量还有 `COMPSHARE_PROFILE` 和 `COMPSHARE_CONFIG_FILE`。
76
+
77
+ ## 全局参数
78
+
79
+ ```text
80
+ --profile 选择凭证 profile
81
+ --json 输出适合脚本处理的 JSON
82
+ ```
83
+
84
+ 地域和可用区是资源参数,不是全局选项。它们会出现在真正需要位置的子命令后面:
85
+
86
+ ```bash
87
+ compshare instance list --region cn-sh2
88
+ compshare instance create --zone cn-sh2-02
89
+ ```
90
+
91
+ 实例和已挂载云盘的生命周期命令会根据资源 ID 自动查找 Region 和 Zone。列表命令未指定 `--region` 时会聚合所有支持地域。
92
+
93
+ `ProjectId` 不是账户凭证的一部分。定时关机会通过 `GetProjectList` 自动选择默认项目;只有需要覆盖自动选择结果时才传 `--project-id`。
94
+
95
+ `--json` 和 `--profile` 是全局选项,必须放在子命令前:
96
+
97
+ ```bash
98
+ compshare --json instance list
99
+ compshare --profile production instance list
100
+ ```
101
+
102
+ 成功时 JSON 模式直接输出 API 响应;失败时输出 `{"ok":false,"error":"..."}` 并返回非零退出码。实例密码和 FileBrowser 密码保留 API 原值,API 私钥仍会脱敏。
103
+
104
+ CLI 默认显示中文帮助描述。使用一级命令持久切换语言,配置会写入本地配置文件,但不属于任何凭证 profile:
105
+
106
+ ```bash
107
+ compshare --help
108
+ compshare lang en
109
+ compshare instance create --help
110
+ compshare lang zh
111
+ compshare lang # 查看当前语言
112
+ ```
113
+
114
+ 也可以通过 `COMPSHARE_LANG=en` 覆盖当前终端会话的帮助语言。当前版本不安装 Shell completion,仅提供 `-h/--help`。
115
+
116
+ ## 实例
117
+
118
+ ### 查规格和库存
119
+
120
+ 不带镜像时,`search` 展示合法的 GPU、CPU 和内存组合:
121
+
122
+ ```bash
123
+ compshare instance search
124
+ compshare instance search --gpu 4090 --gpu A100
125
+ ```
126
+
127
+ 库存不是机型的固定属性。它还取决于镜像、磁盘、计费方式和 CPU 平台,因此检查真实库存时需要指定镜像:
128
+
129
+ ```bash
130
+ compshare instance search \
131
+ --gpu 4090 \
132
+ --image compshareImage-xxxx \
133
+ --disk 100GiB \
134
+ --disk-type CLOUD_SSD \
135
+ --charge Postpay \
136
+ --available
137
+ ```
138
+
139
+ 该命令先调用 `DescribeAvailableCompShareInstanceTypes` 获取合法规格,再按匹配的 GPU 型号调用 `CheckCompShareResourceCapacity`。`--available` 不会用 Describe 接口的状态冒充库存。
140
+
141
+ ### 创建
142
+
143
+ 直接运行不带规格参数的命令会进入交互向导:
144
+
145
+ ```bash
146
+ compshare instance create
147
+ ```
148
+
149
+ 向导会依次读取可用区、GPU 机型和镜像;选择计费方式与磁盘后,调用真实库存接口,只展示当前可创建的 GPU、CPU、内存组合。最后查询完整价格并要求确认。镜像来源支持 `platform`、`custom`、`community` 和 `shared`。
150
+
151
+ 也可以预先指定部分选项,让向导只补齐剩余内容:
152
+
153
+ ```bash
154
+ compshare instance create --gpu 3080Ti --image-source platform
155
+ ```
156
+
157
+ 自动化场景使用完整参数模式:
158
+
159
+ ```bash
160
+ compshare instance create \
161
+ --gpu 4090 \
162
+ --count 1 \
163
+ --cpu 16 \
164
+ --memory 64GiB \
165
+ --image compshareImage-xxxx \
166
+ --disk 100GiB \
167
+ --data-disk 200GiB:CLOUD_SSD \
168
+ --charge Postpay
169
+ ```
170
+
171
+ 创建前会依次检查目标组合库存和价格,得到确认后才创建。自动化中可使用 `--yes` 跳过最终确认。`--json` 不会启动交互向导,因此必须同时提供 `--gpu`、`--count`、`--cpu`、`--memory` 和 `--image`。
172
+
173
+ 可以先检查库存、价格和最终请求,但不创建资源:
174
+
175
+ ```bash
176
+ compshare instance create ... --dry-run --json
177
+ ```
178
+
179
+ 人类交互终端默认等待创建和生命周期操作进入稳定状态,脚本使用的 JSON 模式默认提交后立即返回。可以用 `--wait`、`--no-wait` 和 `--timeout` 显式控制;创建镜像也支持相同选项。
180
+
181
+ ### 命令概览
182
+
183
+ ```text
184
+ compshare instance search
185
+ compshare instance zones
186
+ compshare instance families
187
+ compshare instance list
188
+ compshare instance show INSTANCE
189
+ compshare instance create
190
+ compshare instance start INSTANCE
191
+ compshare instance stop INSTANCE
192
+ compshare instance reboot INSTANCE
193
+ compshare instance delete INSTANCE
194
+ compshare instance rename INSTANCE NAME
195
+ compshare instance password INSTANCE
196
+ compshare instance reinstall INSTANCE
197
+ compshare instance resize INSTANCE
198
+ compshare instance price
199
+ compshare instance resize-price INSTANCE
200
+ compshare instance billing
201
+ compshare instance refund INSTANCE...
202
+ compshare instance monitor [INSTANCE...]
203
+ compshare instance charge INSTANCE --to Month
204
+ compshare instance network
205
+ compshare instance models
206
+ compshare instance ssh INSTANCE
207
+ compshare instance ports list
208
+ compshare instance ports update INSTANCE
209
+ compshare instance schedule set INSTANCE --at 2h
210
+ compshare instance schedule cancel INSTANCE
211
+ compshare instance software list
212
+ compshare instance software url INSTANCE JupyterLab
213
+ ```
214
+
215
+ `ssh` 会在连接前显示 API 返回的实例密码;使用 `ssh INSTANCE --print` 时会同时输出 SSH 命令和密码。
216
+
217
+ `resize` 明确区分计算规格调整和磁盘扩容,两者不能放在同一次请求中。执行删除、关机、重启、重装、改配等高影响操作时默认要求确认。
218
+
219
+ `instance list` 支持 `--name`、`--status`、`--gpu` 和 `--billing` 组合筛选。`instance show` 默认显示重点字段卡片,使用 `--json` 时保留完整 API 响应。旧命令 `upgrade-price` 仍兼容,但帮助中统一使用 `resize-price`。
220
+
221
+ 生产实测中 `monitor` 和 `software url` 对应接口当前不可用,CLI 会在帮助和 API 错误提示中明确标记;命令仍保留,便于服务端恢复后直接使用。
222
+
223
+ ## 镜像
224
+
225
+ 用 `--source` 在不同镜像来源间切换:
226
+
227
+ ```bash
228
+ compshare image list --source platform
229
+ compshare image list --source custom
230
+ compshare image list --source community --query pytorch --tag LLM
231
+ compshare image list --source shared
232
+ compshare image list --source published
233
+ compshare image list --source user --user 12345
234
+ ```
235
+
236
+ 完整命令:
237
+
238
+ ```text
239
+ compshare image list
240
+ compshare image show IMAGE
241
+ compshare image create --instance INSTANCE --name NAME
242
+ compshare image progress IMAGE
243
+ compshare image update IMAGE
244
+ compshare image delete IMAGE
245
+ compshare image shares IMAGE
246
+ compshare image share IMAGE ACCOUNT...
247
+ compshare image unshare IMAGE ACCOUNT...
248
+ compshare image publish IMAGE --version v1.0
249
+ compshare image favorite IMAGE
250
+ compshare image unfavorite IMAGE
251
+ compshare image tags
252
+ ```
253
+
254
+ 社区镜像列表支持名称、作者、模糊查询、标签、免费/付费、官方/非官方、自启动和排序筛选。封面文件由 CLI 转成 Base64,README 文件按 UTF-8 读取。
255
+
256
+ 创建镜像时可使用 `--wait` 跟踪 `GetCompShareImageCreateProgress`,或继续使用独立的 `image progress` 命令。
257
+
258
+ ## 磁盘与云存储
259
+
260
+ ```text
261
+ compshare storage disk list
262
+ compshare storage disk create --instance INSTANCE --size 100GiB --name DATA
263
+ compshare storage disk attach DISK --instance INSTANCE
264
+ compshare storage disk detach DISK --instance INSTANCE --device /dev/vdb
265
+ compshare storage disk price DISK --instance INSTANCE --size 200GiB
266
+ compshare storage disk resize DISK --size 200GiB
267
+ compshare storage disk delete DISK
268
+ compshare storage us3 attach --instance INSTANCE
269
+ ```
270
+
271
+ `storage us3 attach` 覆盖 CompShare 的 US3 挂载 Action。对象上传、下载、Bucket 管理继续使用独立的 `us3cli`,避免在本 CLI 中复制另一套成熟工具的能力。
272
+
273
+ 云盘删除会对“仍在卸载中”的暂时性错误自动重试;其他已知生产错误会附带可执行的处理建议。
274
+
275
+ ## 开发校验
276
+
277
+ ```bash
278
+ .venv/bin/ruff check src tests
279
+ .venv/bin/ruff format --check src tests
280
+ .venv/bin/pytest
281
+ ```
282
+
283
+ 公开 Action 清单维护在 `compshare_cli.actions` 中,测试会校验 27/17/7 的领域数量以及命令实现是否包含全部 51 个 Action。
@@ -0,0 +1,23 @@
1
+ compshare_cli/__init__.py,sha256=oAiCUU26yh1yGt7oqqVSJ2MC4-hDOllaTbE1sac2cso,60
2
+ compshare_cli/__main__.py,sha256=ftkB0R9jLZejzr717131qktgfFhTe12noFeGqQsVZMc,74
3
+ compshare_cli/actions.py,sha256=Ps-oWam3kGx29pt3SgJjhifQgqTRy1cXJ7_ZW5J5Rck,2281
4
+ compshare_cli/api.py,sha256=COz5crmyJAgc_Pi9v9xcRn2ID1PviAtaI5EOQoAtwiI,4866
5
+ compshare_cli/cli.py,sha256=I2_K5WQAGnKKReM00prjNvV8dqORLkp4hJZbXYSKuTo,7086
6
+ compshare_cli/config.py,sha256=-cd5hn3lDP4AUICggot7QVD26ZElxy6xAkeUJWUPSnU,4521
7
+ compshare_cli/errors.py,sha256=ZVVxqJnmaBFfV4k-Q7wuLvxA83iQhmFZ_Bx5w6o9EN4,262
8
+ compshare_cli/i18n.py,sha256=3mcnujMfZMfrDJDXWYAIjN1D1MQdQaRTdLjGtsW4WZE,27614
9
+ compshare_cli/location.py,sha256=TC7P5JheKeC5ahJepnu6lQrE2wSfQou2T42TPI-pKUc,3021
10
+ compshare_cli/output.py,sha256=2JgOW23jv4t7zyD2KjiTWxaybIAPNe0E-LmEScNLTa8,4483
11
+ compshare_cli/parsing.py,sha256=ArlUe2HhUhl26kpWCU99XGrbRgOXrnHrB7s-PWN4WkU,3103
12
+ compshare_cli/runtime.py,sha256=OWR9UdCBty0g57hNi1R8IhbbfWvFWvTm33bShc00Yvo,957
13
+ compshare_cli/sdk.py,sha256=aF3ksxldKzYJho6QUiUILzRtt49qSVek_xrmqpOhCV4,892
14
+ compshare_cli/commands/__init__.py,sha256=Rb-d2e6JpWZ5pPUP-TjKMWGtNCwpZCMYc0GulfEXbRs,26
15
+ compshare_cli/commands/common.py,sha256=14ukEjfQS-eL0s7pfTa9yhtidRAF7G67bzqWnPFCMrw,1445
16
+ compshare_cli/commands/image.py,sha256=G1x0EDp-4KsDz8ip4g1BB8KNm2e3HjK3NiGvmE6CpZ0,20149
17
+ compshare_cli/commands/instance.py,sha256=YWIj8dAs7zLG-pbjHgpzUx9QZvl_wi6vZ3rbA-N3NHM,53451
18
+ compshare_cli/commands/storage.py,sha256=ooBuacS5cpBhM4mqLbJWA3lPPzeYu3oB7tcMdsud2Rk,9483
19
+ compshare_cli-0.1.0.dist-info/METADATA,sha256=IirccpCX1IzIWAwkOCYYDUbWHzTLyqqF70hWowwLRYw,9855
20
+ compshare_cli-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
21
+ compshare_cli-0.1.0.dist-info/entry_points.txt,sha256=HXeFDKxnznV7eT1KRupBkzOIpSkYnm3MUqgyVk0FAUA,53
22
+ compshare_cli-0.1.0.dist-info/top_level.txt,sha256=ROBw509DOLNt7Z-xwrArjK066G7s4IH0jTcGiaKyMNw,14
23
+ compshare_cli-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ compshare = compshare_cli.cli:main
@@ -0,0 +1 @@
1
+ compshare_cli