computeid-cli 1.0.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.
cli.py ADDED
@@ -0,0 +1,505 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ ComputeID CLI — Command line tool for ComputeID identity management
4
+ Every GPU needs a passport. Every AI agent needs an identity.
5
+ """
6
+
7
+ import click
8
+ import requests
9
+ import json
10
+ import os
11
+ import sys
12
+ from datetime import datetime
13
+ from pathlib import Path
14
+
15
+ try:
16
+ from rich.console import Console
17
+ from rich.table import Table
18
+ from rich.panel import Panel
19
+ from rich.text import Text
20
+ from rich import box
21
+ from rich.style import Style
22
+ RICH = True
23
+ except ImportError:
24
+ RICH = False
25
+
26
+ API_URL = "https://api.aicomputeid.com"
27
+ CONFIG_FILE = Path.home() / ".computeid" / "config.json"
28
+
29
+ console = Console() if RICH else None
30
+
31
+ # ── HELPERS ───────────────────────────────────────────────────────────────────
32
+
33
+ def load_config():
34
+ if CONFIG_FILE.exists():
35
+ with open(CONFIG_FILE) as f:
36
+ return json.load(f)
37
+ return {}
38
+
39
+ def save_config(cfg):
40
+ CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True)
41
+ with open(CONFIG_FILE, 'w') as f:
42
+ json.dump(cfg, f, indent=2)
43
+
44
+ def get_token():
45
+ cfg = load_config()
46
+ return cfg.get("token")
47
+
48
+ def get_api_url():
49
+ cfg = load_config()
50
+ return cfg.get("api_url", API_URL)
51
+
52
+ def auth_headers():
53
+ token = get_token()
54
+ if not token:
55
+ error("Not logged in. Run: computeid login")
56
+ sys.exit(1)
57
+ return {
58
+ "Content-Type": "application/json",
59
+ "Authorization": f"Bearer {token}"
60
+ }
61
+
62
+ def success(msg):
63
+ if RICH:
64
+ console.print(f"[bold green]✓[/bold green] {msg}")
65
+ else:
66
+ print(f"✓ {msg}")
67
+
68
+ def error(msg):
69
+ if RICH:
70
+ console.print(f"[bold red]✗[/bold red] {msg}")
71
+ else:
72
+ print(f"✗ {msg}")
73
+
74
+ def info(msg):
75
+ if RICH:
76
+ console.print(f"[bold cyan]→[/bold cyan] {msg}")
77
+ else:
78
+ print(f"→ {msg}")
79
+
80
+ def warn(msg):
81
+ if RICH:
82
+ console.print(f"[bold yellow]⚠[/bold yellow] {msg}")
83
+ else:
84
+ print(f"⚠ {msg}")
85
+
86
+ def print_header():
87
+ if RICH:
88
+ console.print()
89
+ console.print(Panel.fit(
90
+ "[bold white]COMPUTE[/bold white][bold cyan]ID[/bold cyan] [dim]Every GPU needs a passport. We issue them.[/dim]",
91
+ border_style="cyan",
92
+ padding=(0, 2)
93
+ ))
94
+ console.print()
95
+
96
+ def fmt_status(status):
97
+ colors = {
98
+ "active": "[bold green]● active[/bold green]",
99
+ "pending": "[bold yellow]● pending[/bold yellow]",
100
+ "revoked": "[bold red]● revoked[/bold red]",
101
+ "expired": "[bold red]● expired[/bold red]",
102
+ }
103
+ return colors.get(status, status) if RICH else status
104
+
105
+ def fmt_time(ts):
106
+ try:
107
+ dt = datetime.fromisoformat(ts.replace("Z",""))
108
+ return dt.strftime("%Y-%m-%d %H:%M")
109
+ except:
110
+ return ts or "—"
111
+
112
+ # ── CLI ROOT ──────────────────────────────────────────────────────────────────
113
+
114
+ @click.group()
115
+ @click.version_option("1.0.0", prog_name="computeid")
116
+ def cli():
117
+ """
118
+ ComputeID CLI — Cryptographic identity for AI compute infrastructure.
119
+
120
+ Every GPU needs a passport. Every AI agent needs an identity.
121
+
122
+ Docs: compute-id.com
123
+ """
124
+ pass
125
+
126
+ # ── STATUS ────────────────────────────────────────────────────────────────────
127
+
128
+ @cli.command()
129
+ def status():
130
+ """Check API health and connection status."""
131
+ print_header()
132
+ api = get_api_url()
133
+ info(f"Connecting to {api}...")
134
+ try:
135
+ r = requests.get(f"{api}/health", timeout=10)
136
+ if r.ok:
137
+ data = r.json()
138
+ success(f"API is online")
139
+ if RICH:
140
+ t = Table(box=box.ROUNDED, border_style="cyan", show_header=False)
141
+ t.add_column("Key", style="bold cyan", width=20)
142
+ t.add_column("Value", style="white")
143
+ t.add_row("API URL", api)
144
+ t.add_row("Status", data.get("status", "running"))
145
+ t.add_row("Time", fmt_time(data.get("time", "")))
146
+ t.add_row("Logged in", "Yes" if get_token() else "No — run: computeid login")
147
+ console.print(t)
148
+ else:
149
+ print(f" API URL: {api}")
150
+ print(f" Status: {data.get('status')}")
151
+ else:
152
+ error(f"API returned {r.status_code}")
153
+ except Exception as e:
154
+ error(f"Cannot reach API: {e}")
155
+
156
+ # ── LOGIN ─────────────────────────────────────────────────────────────────────
157
+
158
+ @cli.command()
159
+ @click.option("--password", "-p", prompt=True, hide_input=True, help="Admin password")
160
+ def login(password):
161
+ """Login to ComputeID with your admin password."""
162
+ api = get_api_url()
163
+ try:
164
+ r = requests.post(f"{api}/api/admin/login",
165
+ json={"password": password},
166
+ headers={"Content-Type": "application/json"},
167
+ timeout=10)
168
+ data = r.json()
169
+ if r.ok:
170
+ cfg = load_config()
171
+ cfg["token"] = data["token"]
172
+ cfg["api_url"] = api
173
+ save_config(cfg)
174
+ success("Logged in successfully!")
175
+ info("Token saved to ~/.computeid/config.json")
176
+ else:
177
+ error(f"Login failed: {data.get('error', 'Invalid password')}")
178
+ except Exception as e:
179
+ error(f"Login error: {e}")
180
+
181
+ @cli.command()
182
+ def logout():
183
+ """Logout and clear saved credentials."""
184
+ cfg = load_config()
185
+ cfg.pop("token", None)
186
+ save_config(cfg)
187
+ success("Logged out successfully")
188
+
189
+ # ── DEVICE COMMANDS ───────────────────────────────────────────────────────────
190
+
191
+ @cli.group()
192
+ def device():
193
+ """Manage DevicePassports for GPUs and servers."""
194
+ pass
195
+
196
+ @device.command("list")
197
+ @click.option("--status", "-s", default=None, help="Filter by status: active, pending, revoked")
198
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
199
+ def device_list(status, as_json):
200
+ """List all registered devices."""
201
+ try:
202
+ r = requests.get(f"{get_api_url()}/api/devices", headers=auth_headers(), timeout=10)
203
+ if not r.ok:
204
+ error(f"Failed to fetch devices: {r.json().get('error')}")
205
+ return
206
+ devices = r.json()
207
+ if status:
208
+ devices = [d for d in devices if d.get("status") == status]
209
+ if as_json:
210
+ click.echo(json.dumps(devices, indent=2))
211
+ return
212
+ if not devices:
213
+ warn("No devices found")
214
+ return
215
+ if RICH:
216
+ t = Table(title=f"[bold]Devices ({len(devices)})[/bold]",
217
+ box=box.ROUNDED, border_style="cyan", show_lines=True)
218
+ t.add_column("Code", style="bold cyan", width=12)
219
+ t.add_column("Name", style="white", width=22)
220
+ t.add_column("Type", style="dim", width=10)
221
+ t.add_column("IP Address", style="dim", width=16)
222
+ t.add_column("Status", width=14)
223
+ t.add_column("Certificate", width=12)
224
+ t.add_column("Registered", width=16)
225
+ for d in devices:
226
+ t.add_row(
227
+ d.get("device_code", "—"),
228
+ d.get("name", "—"),
229
+ d.get("type", "—"),
230
+ d.get("ip_address", "—"),
231
+ fmt_status(d.get("status", "—")),
232
+ "[green]✓ Issued[/green]" if d.get("public_key") else "[dim]None[/dim]",
233
+ fmt_time(d.get("created_at", ""))
234
+ )
235
+ console.print(t)
236
+ else:
237
+ print(f"\nDevices ({len(devices)}):")
238
+ for d in devices:
239
+ print(f" {d.get('device_code')} | {d.get('name')} | {d.get('status')} | {d.get('ip_address')}")
240
+ except Exception as e:
241
+ error(f"Error: {e}")
242
+
243
+ @device.command("register")
244
+ @click.option("--name", "-n", required=True, help="Device name e.g. 'NVIDIA A100'")
245
+ @click.option("--ip", "-i", required=True, help="Device IP address")
246
+ @click.option("--type", "-t", "dtype", default="GPU",
247
+ type=click.Choice(["GPU","Server","TPU","FPGA"], case_sensitive=False),
248
+ help="Device type")
249
+ def device_register(name, ip, dtype):
250
+ """Register a new device and issue a DevicePassport."""
251
+ info(f"Registering {dtype}: {name} ({ip})...")
252
+ try:
253
+ r = requests.post(f"{get_api_url()}/api/devices/register",
254
+ json={"name": name, "type": dtype, "ip_address": ip},
255
+ headers={"Content-Type": "application/json"},
256
+ timeout=10)
257
+ data = r.json()
258
+ if r.ok:
259
+ success(f"Device registered successfully!")
260
+ if RICH:
261
+ t = Table(box=box.ROUNDED, border_style="green", show_header=False)
262
+ t.add_column("Key", style="bold cyan", width=20)
263
+ t.add_column("Value", style="white")
264
+ t.add_row("Device Code", data.get("device_code", "—"))
265
+ t.add_row("Name", data.get("name", "—"))
266
+ t.add_row("Type", data.get("type", "—"))
267
+ t.add_row("IP Address", data.get("ip_address", "—"))
268
+ t.add_row("Status", fmt_status(data.get("status", "pending")))
269
+ t.add_row("Certificate", "✓ Issued" if data.get("public_key") else "Pending approval")
270
+ console.print(t)
271
+ console.print()
272
+ warn("Device is pending admin approval. Run: computeid device approve " + data.get("device_code",""))
273
+ else:
274
+ print(f" Code: {data.get('device_code')}")
275
+ print(f" Status: pending — awaiting admin approval")
276
+ else:
277
+ error(f"Registration failed: {data.get('error')}")
278
+ except Exception as e:
279
+ error(f"Error: {e}")
280
+
281
+ @device.command("approve")
282
+ @click.argument("device_id")
283
+ def device_approve(device_id):
284
+ """Approve a pending device."""
285
+ info(f"Approving device {device_id}...")
286
+ try:
287
+ r = requests.patch(f"{get_api_url()}/api/devices/{device_id}/approve",
288
+ headers=auth_headers(), timeout=10)
289
+ if r.ok:
290
+ success(f"Device {device_id} approved and activated!")
291
+ else:
292
+ error(f"Failed: {r.json().get('error')}")
293
+ except Exception as e:
294
+ error(f"Error: {e}")
295
+
296
+ @device.command("revoke")
297
+ @click.argument("device_id")
298
+ @click.option("--force", is_flag=True, help="Skip confirmation")
299
+ def device_revoke(device_id, force):
300
+ """Revoke a device certificate immediately."""
301
+ if not force:
302
+ click.confirm(f"Revoke device {device_id}? This removes all access immediately.", abort=True)
303
+ info(f"Revoking device {device_id}...")
304
+ try:
305
+ r = requests.patch(f"{get_api_url()}/api/devices/{device_id}/revoke",
306
+ headers=auth_headers(), timeout=10)
307
+ if r.ok:
308
+ success(f"Device {device_id} revoked. Access removed immediately.")
309
+ else:
310
+ error(f"Failed: {r.json().get('error')}")
311
+ except Exception as e:
312
+ error(f"Error: {e}")
313
+
314
+ @device.command("authenticate")
315
+ @click.argument("device_code")
316
+ def device_authenticate(device_code):
317
+ """Authenticate a device and get a JWT token."""
318
+ info(f"Authenticating device {device_code}...")
319
+ try:
320
+ r = requests.post(f"{get_api_url()}/api/devices/authenticate",
321
+ json={"device_code": device_code},
322
+ headers={"Content-Type": "application/json"},
323
+ timeout=10)
324
+ data = r.json()
325
+ if r.ok:
326
+ success("Device authenticated!")
327
+ token = data.get("access_token", "")
328
+ if RICH:
329
+ console.print(f"\n[bold cyan]Access Token:[/bold cyan]")
330
+ console.print(f"[dim]{token[:60]}...[/dim]")
331
+ console.print(f"\n[dim]Valid for 1 hour. Use in Authorization: Bearer header.[/dim]")
332
+ else:
333
+ print(f"Token: {token[:60]}...")
334
+ else:
335
+ error(f"Authentication failed: {data.get('error')}")
336
+ except Exception as e:
337
+ error(f"Error: {e}")
338
+
339
+ # ── AGENT COMMANDS ────────────────────────────────────────────────────────────
340
+
341
+ @cli.group()
342
+ def agent():
343
+ """Manage AgentPassports for AI agents."""
344
+ pass
345
+
346
+ @agent.command("issue")
347
+ @click.option("--name", "-n", required=True, help="Agent name e.g. 'ResearchAgent'")
348
+ @click.option("--org", "-o", default="My Organisation", help="Organisation name")
349
+ @click.option("--email", "-e", default="admin@compute-id.com", help="Owner email")
350
+ @click.option("--trust", "-t", default="standard",
351
+ type=click.Choice(["restricted","standard","elevated","autonomous"], case_sensitive=False),
352
+ help="Trust level")
353
+ @click.option("--model", "-m", default="unknown", help="AI model e.g. claude-sonnet-4-5")
354
+ def agent_issue(name, org, email, trust, model):
355
+ """Issue a new AgentPassport for an AI agent."""
356
+ try:
357
+ from computeid import issue_agent_passport
358
+ info(f"Issuing AgentPassport for {name}...")
359
+ passport = issue_agent_passport(
360
+ agent_name=name,
361
+ owner_org=org,
362
+ owner_email=email,
363
+ trust_level=trust,
364
+ model=model
365
+ )
366
+ success(f"AgentPassport issued successfully!")
367
+ if RICH:
368
+ t = Table(box=box.ROUNDED, border_style="cyan", show_header=False)
369
+ t.add_column("Key", style="bold cyan", width=20)
370
+ t.add_column("Value", style="white")
371
+ t.add_row("Agent ID", passport.agent_id[:16] + "...")
372
+ t.add_row("Name", passport.agent_name)
373
+ t.add_row("Organisation", passport.owner_org)
374
+ t.add_row("Trust Level", trust)
375
+ t.add_row("Model", model)
376
+ t.add_row("Status", fmt_status(passport.status))
377
+ t.add_row("Fingerprint", passport._fingerprint)
378
+ t.add_row("Issued At", fmt_time(passport.issued_at))
379
+ t.add_row("Expires At", fmt_time(passport.expires_at))
380
+ console.print(t)
381
+ console.print()
382
+ info("Export passport with: computeid agent export --id " + passport.agent_id[:8])
383
+ else:
384
+ print(f" ID: {passport.agent_id[:16]}...")
385
+ print(f" Trust: {trust}")
386
+ print(f" Status: {passport.status}")
387
+ except ImportError:
388
+ error("computeid-sdk not installed. Run: pip install computeid-sdk")
389
+ except Exception as e:
390
+ error(f"Error: {e}")
391
+
392
+ # ── LOGS ──────────────────────────────────────────────────────────────────────
393
+
394
+ @cli.command()
395
+ @click.option("--limit", "-l", default=20, help="Number of log entries to show")
396
+ @click.option("--json", "as_json", is_flag=True, help="Output as JSON")
397
+ def logs(limit, as_json):
398
+ """View audit logs."""
399
+ try:
400
+ r = requests.get(f"{get_api_url()}/api/logs", headers=auth_headers(), timeout=10)
401
+ if not r.ok:
402
+ error(f"Failed: {r.json().get('error')}")
403
+ return
404
+ entries = r.json()[:limit]
405
+ if as_json:
406
+ click.echo(json.dumps(entries, indent=2))
407
+ return
408
+ if not entries:
409
+ warn("No audit logs found")
410
+ return
411
+ if RICH:
412
+ t = Table(title=f"[bold]Audit Logs (last {len(entries)})[/bold]",
413
+ box=box.ROUNDED, border_style="cyan", show_lines=True)
414
+ t.add_column("Time", style="dim", width=16)
415
+ t.add_column("Device ID", style="cyan", width=14)
416
+ t.add_column("Action", style="white", width=22)
417
+ t.add_column("Status", width=12)
418
+ t.add_column("IP", style="dim", width=16)
419
+ for e in entries:
420
+ action = e.get("action","").replace("_"," ").title()
421
+ s = e.get("status","")
422
+ sc = "[green]success[/green]" if s=="success" else "[red]denied[/red]" if s=="denied" else f"[yellow]{s}[/yellow]"
423
+ t.add_row(
424
+ fmt_time(e.get("created_at","")),
425
+ (e.get("device_id","") or "")[:10]+"...",
426
+ action,
427
+ sc,
428
+ e.get("ip_address","—") or "—"
429
+ )
430
+ console.print(t)
431
+ else:
432
+ print(f"\nAudit Logs ({len(entries)}):")
433
+ for e in entries:
434
+ print(f" {fmt_time(e.get('created_at',''))} | {e.get('action')} | {e.get('status')}")
435
+ except Exception as e:
436
+ error(f"Error: {e}")
437
+
438
+ # ── CONFIG ────────────────────────────────────────────────────────────────────
439
+
440
+ @cli.group()
441
+ def config():
442
+ """Manage CLI configuration."""
443
+ pass
444
+
445
+ @config.command("show")
446
+ def config_show():
447
+ """Show current configuration."""
448
+ cfg = load_config()
449
+ if RICH:
450
+ t = Table(box=box.ROUNDED, border_style="cyan", show_header=False)
451
+ t.add_column("Key", style="bold cyan", width=20)
452
+ t.add_column("Value", style="white")
453
+ t.add_row("API URL", cfg.get("api_url", API_URL))
454
+ t.add_row("Logged In", "Yes ✓" if cfg.get("token") else "No")
455
+ t.add_row("Config File", str(CONFIG_FILE))
456
+ console.print(t)
457
+ else:
458
+ print(f"API URL: {cfg.get('api_url', API_URL)}")
459
+ print(f"Logged in: {'Yes' if cfg.get('token') else 'No'}")
460
+
461
+ @config.command("set-url")
462
+ @click.argument("url")
463
+ def config_set_url(url):
464
+ """Set a custom API URL (for self-hosted ComputeID)."""
465
+ cfg = load_config()
466
+ cfg["api_url"] = url
467
+ save_config(cfg)
468
+ success(f"API URL set to {url}")
469
+
470
+ # ── QUICK START ───────────────────────────────────────────────────────────────
471
+
472
+ @cli.command()
473
+ def quickstart():
474
+ """Interactive quickstart guide."""
475
+ print_header()
476
+ if RICH:
477
+ steps = [
478
+ ("1", "Login", "computeid login"),
479
+ ("2", "Check status", "computeid status"),
480
+ ("3", "Register a GPU", "computeid device register --name 'NVIDIA A100' --ip 192.168.1.10"),
481
+ ("4", "List devices", "computeid device list"),
482
+ ("5", "Issue agent passport", "computeid agent issue --name 'MyAgent' --trust standard"),
483
+ ("6", "View audit logs", "computeid logs"),
484
+ ]
485
+ t = Table(title="[bold]ComputeID CLI Quick Start[/bold]",
486
+ box=box.ROUNDED, border_style="cyan")
487
+ t.add_column("Step", style="bold cyan", width=6)
488
+ t.add_column("Action", style="white", width=22)
489
+ t.add_column("Command", style="bold green", width=60)
490
+ for step, action, cmd in steps:
491
+ t.add_row(step, action, cmd)
492
+ console.print(t)
493
+ console.print()
494
+ console.print("[dim]Docs: compute-id.com | hello@compute-id.com[/dim]")
495
+ else:
496
+ print("ComputeID CLI Quick Start:")
497
+ print(" 1. computeid login")
498
+ print(" 2. computeid status")
499
+ print(" 3. computeid device register --name 'NVIDIA A100' --ip 192.168.1.10")
500
+ print(" 4. computeid device list")
501
+ print(" 5. computeid agent issue --name 'MyAgent' --trust standard")
502
+ print(" 6. computeid logs")
503
+
504
+ if __name__ == "__main__":
505
+ cli()
@@ -0,0 +1,91 @@
1
+ Metadata-Version: 2.4
2
+ Name: computeid-cli
3
+ Version: 1.0.0
4
+ Summary: CLI tool for ComputeID — cryptographic identity for AI compute infrastructure
5
+ Home-page: https://github.com/trustedaicompute-ops/computeid-sdk
6
+ Author: ComputeID
7
+ Author-email: hello@compute-id.com
8
+ Keywords: gpu identity certificates quantum-safe ai agents security cli computeid
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: Security :: Cryptography
14
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
15
+ Requires-Python: >=3.8
16
+ Description-Content-Type: text/markdown
17
+ Requires-Dist: click>=8.0.0
18
+ Requires-Dist: requests>=2.28.0
19
+ Requires-Dist: rich>=13.0.0
20
+ Dynamic: author
21
+ Dynamic: author-email
22
+ Dynamic: classifier
23
+ Dynamic: description
24
+ Dynamic: description-content-type
25
+ Dynamic: home-page
26
+ Dynamic: keywords
27
+ Dynamic: requires-dist
28
+ Dynamic: requires-python
29
+ Dynamic: summary
30
+
31
+ # ComputeID CLI
32
+
33
+ Command line tool for ComputeID — cryptographic identity for AI compute infrastructure.
34
+
35
+ ## Install
36
+
37
+ ```bash
38
+ pip install computeid-cli
39
+ ```
40
+
41
+ ## Quick Start
42
+
43
+ ```bash
44
+ # Login
45
+ computeid login
46
+
47
+ # Check status
48
+ computeid status
49
+
50
+ # Register a GPU
51
+ computeid device register --name "NVIDIA A100" --ip 192.168.1.10
52
+
53
+ # List devices
54
+ computeid device list
55
+
56
+ # Approve a device
57
+ computeid device approve GPU-001
58
+
59
+ # Revoke a device
60
+ computeid device revoke GPU-001
61
+
62
+ # Issue an agent passport
63
+ computeid agent issue --name "ResearchAgent" --trust standard
64
+
65
+ # View audit logs
66
+ computeid logs
67
+
68
+ # Get help
69
+ computeid --help
70
+ ```
71
+
72
+ ## Commands
73
+
74
+ | Command | Description |
75
+ |---------|-------------|
76
+ | `computeid status` | Check API health |
77
+ | `computeid login` | Login with admin password |
78
+ | `computeid logout` | Logout |
79
+ | `computeid device list` | List all devices |
80
+ | `computeid device register` | Register a new GPU or server |
81
+ | `computeid device approve` | Approve a pending device |
82
+ | `computeid device revoke` | Revoke a device certificate |
83
+ | `computeid device authenticate` | Get JWT token for a device |
84
+ | `computeid agent issue` | Issue an AgentPassport |
85
+ | `computeid logs` | View audit logs |
86
+ | `computeid config show` | Show configuration |
87
+ | `computeid quickstart` | Interactive quick start guide |
88
+
89
+ ## Docs
90
+
91
+ compute-id.com | hello@compute-id.com
@@ -0,0 +1,6 @@
1
+ cli.py,sha256=8utzmOQ54fa4MCkJPFnmpZcWln7eGOaaxFS3O_Szjws,19932
2
+ computeid_cli-1.0.0.dist-info/METADATA,sha256=KSakFHv7VRPmjBha8sBjmfoY3_f94tpVJC2FLiBii4o,2368
3
+ computeid_cli-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
4
+ computeid_cli-1.0.0.dist-info/entry_points.txt,sha256=ZqdabCC0NhFM63d7E14JbYd1sJXCKG_UDerjqiFigpg,38
5
+ computeid_cli-1.0.0.dist-info/top_level.txt,sha256=2ImG917oaVHlm0nP9oJE-Qrgs-fq_fGWgba2H1f8fpE,4
6
+ computeid_cli-1.0.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
+ computeid = cli:cli
@@ -0,0 +1 @@
1
+ cli