apizit-cli 0.2.0b4__tar.gz → 0.3.0__tar.gz

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 (27) hide show
  1. apizit_cli-0.3.0/PKG-INFO +52 -0
  2. apizit_cli-0.3.0/README.md +35 -0
  3. {apizit_cli-0.2.0b4 → apizit_cli-0.3.0}/pyproject.toml +1 -2
  4. {apizit_cli-0.2.0b4 → apizit_cli-0.3.0}/src/apizit_cli/__init__.py +1 -1
  5. {apizit_cli-0.2.0b4 → apizit_cli-0.3.0}/src/apizit_cli/commands.py +55 -33
  6. {apizit_cli-0.2.0b4 → apizit_cli-0.3.0}/src/apizit_cli/context.py +78 -0
  7. {apizit_cli-0.2.0b4 → apizit_cli-0.3.0}/src/apizit_cli/main.py +17 -4
  8. {apizit_cli-0.2.0b4 → apizit_cli-0.3.0}/src/apizit_cli/operations.py +171 -49
  9. apizit_cli-0.3.0/src/apizit_cli/presentation.py +552 -0
  10. apizit_cli-0.3.0/src/apizit_cli/selection.py +60 -0
  11. apizit_cli-0.3.0/src/apizit_cli.egg-info/PKG-INFO +52 -0
  12. {apizit_cli-0.2.0b4 → apizit_cli-0.3.0}/src/apizit_cli.egg-info/SOURCES.txt +2 -0
  13. apizit_cli-0.2.0b4/PKG-INFO +0 -54
  14. apizit_cli-0.2.0b4/README.md +0 -36
  15. apizit_cli-0.2.0b4/src/apizit_cli.egg-info/PKG-INFO +0 -54
  16. {apizit_cli-0.2.0b4 → apizit_cli-0.3.0}/setup.cfg +0 -0
  17. {apizit_cli-0.2.0b4 → apizit_cli-0.3.0}/src/apizit_cli/authentication.py +0 -0
  18. {apizit_cli-0.2.0b4 → apizit_cli-0.3.0}/src/apizit_cli/configuration.py +0 -0
  19. {apizit_cli-0.2.0b4 → apizit_cli-0.3.0}/src/apizit_cli/credential_store.py +0 -0
  20. {apizit_cli-0.2.0b4 → apizit_cli-0.3.0}/src/apizit_cli/domains.py +0 -0
  21. {apizit_cli-0.2.0b4 → apizit_cli-0.3.0}/src/apizit_cli/errors.py +0 -0
  22. {apizit_cli-0.2.0b4 → apizit_cli-0.3.0}/src/apizit_cli/http.py +0 -0
  23. {apizit_cli-0.2.0b4 → apizit_cli-0.3.0}/src/apizit_cli/sources.py +0 -0
  24. {apizit_cli-0.2.0b4 → apizit_cli-0.3.0}/src/apizit_cli.egg-info/dependency_links.txt +0 -0
  25. {apizit_cli-0.2.0b4 → apizit_cli-0.3.0}/src/apizit_cli.egg-info/entry_points.txt +0 -0
  26. {apizit_cli-0.2.0b4 → apizit_cli-0.3.0}/src/apizit_cli.egg-info/requires.txt +0 -0
  27. {apizit_cli-0.2.0b4 → apizit_cli-0.3.0}/src/apizit_cli.egg-info/top_level.txt +0 -0
@@ -0,0 +1,52 @@
1
+ Metadata-Version: 2.4
2
+ Name: apizit-cli
3
+ Version: 0.3.0
4
+ Summary: Launch supported Python, JavaScript, TypeScript or Java APIs from your terminal or CI.
5
+ Author: APIZIT
6
+ Classifier: Environment :: Console
7
+ Classifier: Operating System :: OS Independent
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.12
10
+ Requires-Python: >=3.12
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: requests<3,>=2.32.0
13
+ Requires-Dist: pydantic<3,>=2.12.5
14
+ Requires-Dist: pyyaml<7,>=6.0.3
15
+ Requires-Dist: keyring<26,>=25.7
16
+ Requires-Dist: pathspec<2,>=1
17
+
18
+ # APIZIT CLI
19
+
20
+ Check a project, launch its analyzed source and get its live API URL from your terminal.
21
+ Managed adapters cover Flask, FastAPI, Django, Express, NestJS and Spring Boot;
22
+ APIZIT Linking provides declarative Python routes. Scan to confirm compatibility.
23
+
24
+ Python 3.12 or later is required. Install with pip (or py -m pip on Windows):
25
+
26
+ ```sh
27
+ python -m pip install apizit-cli
28
+ apizit login
29
+ apizit workspace use
30
+ apizit scan .
31
+ apizit deploy --from-scan latest
32
+ apizit api show
33
+ ```
34
+
35
+ Use `python -m pip install --upgrade apizit-cli` to update.
36
+ `pipx install apizit-cli` is an isolated alternative. No AWS account, Docker or backend checkout is needed.
37
+
38
+ The default view summarizes outcomes. `--verbose` adds technical details; `--json` preserves
39
+ the complete versioned result and takes priority. Existing IDs and CI scripts remain supported.
40
+ Names and slugs resolve accessible resources only; ambiguous names need IDs.
41
+ `apizit api use "weather-api"` links an existing API without replacing another binding.
42
+
43
+ Interactive operations save non-secret references in `.apizit/operations.json`, scoped by folder,
44
+ endpoint and workspace. `latest` resumes the recorded operation. `deploy --from-scan latest`
45
+ reuses the exact scanned snapshot, excluding later edits. `deploy .` uploads current bytes.
46
+
47
+ APIZIT remains prelaunch. The default endpoint is `https://api.apizit.com/v1` (protected main domain).
48
+ For TEST, pass `--endpoint https://api-test.apizit.com/v1` or set `APIZIT_API_URL` on every command.
49
+ Sessions stay separate by endpoint. Configuration keeps its strict version 2 schema.
50
+
51
+ Run `apizit --help` or `apizit docs`. CI and AI agents use `--json --non-interactive` with
52
+ explicit IDs, stable idempotency keys and an expiring token. Store secrets in the workspace vault.
@@ -0,0 +1,35 @@
1
+ # APIZIT CLI
2
+
3
+ Check a project, launch its analyzed source and get its live API URL from your terminal.
4
+ Managed adapters cover Flask, FastAPI, Django, Express, NestJS and Spring Boot;
5
+ APIZIT Linking provides declarative Python routes. Scan to confirm compatibility.
6
+
7
+ Python 3.12 or later is required. Install with pip (or py -m pip on Windows):
8
+
9
+ ```sh
10
+ python -m pip install apizit-cli
11
+ apizit login
12
+ apizit workspace use
13
+ apizit scan .
14
+ apizit deploy --from-scan latest
15
+ apizit api show
16
+ ```
17
+
18
+ Use `python -m pip install --upgrade apizit-cli` to update.
19
+ `pipx install apizit-cli` is an isolated alternative. No AWS account, Docker or backend checkout is needed.
20
+
21
+ The default view summarizes outcomes. `--verbose` adds technical details; `--json` preserves
22
+ the complete versioned result and takes priority. Existing IDs and CI scripts remain supported.
23
+ Names and slugs resolve accessible resources only; ambiguous names need IDs.
24
+ `apizit api use "weather-api"` links an existing API without replacing another binding.
25
+
26
+ Interactive operations save non-secret references in `.apizit/operations.json`, scoped by folder,
27
+ endpoint and workspace. `latest` resumes the recorded operation. `deploy --from-scan latest`
28
+ reuses the exact scanned snapshot, excluding later edits. `deploy .` uploads current bytes.
29
+
30
+ APIZIT remains prelaunch. The default endpoint is `https://api.apizit.com/v1` (protected main domain).
31
+ For TEST, pass `--endpoint https://api-test.apizit.com/v1` or set `APIZIT_API_URL` on every command.
32
+ Sessions stay separate by endpoint. Configuration keeps its strict version 2 schema.
33
+
34
+ Run `apizit --help` or `apizit docs`. CI and AI agents use `--json --non-interactive` with
35
+ explicit IDs, stable idempotency keys and an expiring token. Store secrets in the workspace vault.
@@ -4,13 +4,12 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "apizit-cli"
7
- version = "0.2.0b4"
7
+ version = "0.3.0"
8
8
  description = "Launch supported Python, JavaScript, TypeScript or Java APIs from your terminal or CI."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
11
11
  authors = [{name = "APIZIT"}]
12
12
  classifiers = [
13
- "Development Status :: 4 - Beta",
14
13
  "Environment :: Console",
15
14
  "Operating System :: OS Independent",
16
15
  "Programming Language :: Python :: 3",
@@ -1,3 +1,3 @@
1
1
  """The APIZIT HTTP client. Importing this package never initializes a session."""
2
2
 
3
- __version__ = "0.2.0b4"
3
+ __version__ = "0.3.0"
@@ -33,8 +33,13 @@ def flag(name, help, **kwargs):
33
33
 
34
34
  GLOBAL_OPTIONS = (
35
35
  flag("--json", "Write one versioned JSON result to stdout.", action="store_true"),
36
- flag("--workspace", "Workspace ID; overrides environment and saved context."),
37
- flag("--endpoint", "APIZIT HTTPS endpoint; defaults to the development beta."),
36
+ flag(
37
+ "--verbose",
38
+ "Include readable technical details; --json takes priority.",
39
+ action="store_true",
40
+ ),
41
+ flag("--workspace", "Exact workspace name, slug or ID; overrides saved context."),
42
+ flag("--endpoint", "APIZIT HTTPS endpoint; use api-test.apizit.com/v1 for TEST."),
38
43
  flag("--non-interactive", "Never prompt or open a browser.", action="store_true"),
39
44
  flag("--token-stdin", "Read a CI token from stdin without saving it.", action="store_true"),
40
45
  )
@@ -53,7 +58,7 @@ FORMAT = flag(
53
58
  "--format", "Launch format.", choices=("automatic", "standard", "expanded", "max_power")
54
59
  )
55
60
  REGION = flag("--region", "Logical region, as returned by apizit regions.")
56
- API = flag("--api", "API ID; defaults to the API linked to this local project.")
61
+ API = flag("--api", "Exact API name, slug or ID; defaults to the API linked to this folder.")
57
62
  REF = flag("--ref", "Git branch, tag or exact revision to verify on the server.")
58
63
  SOURCE = argument("source", "HTTPS Git URL or local folder.", optional=True)
59
64
  SCAN_OPTIONS = (REF, FORMAT, REGION) + WAIT_OPTIONS + WRITE_OPTIONS
@@ -72,7 +77,13 @@ COMMANDS = (
72
77
  "workspace use",
73
78
  "Select and verify a workspace.",
74
79
  "read",
75
- (argument("id", "Workspace ID."),),
80
+ (
81
+ argument(
82
+ "id",
83
+ "Exact workspace name, slug or ID; otherwise select interactively.",
84
+ optional=True,
85
+ ),
86
+ ),
76
87
  ),
77
88
  Command("workspace show", "Show the selected workspace and its current capabilities.", "read"),
78
89
  Command(
@@ -138,6 +149,16 @@ COMMANDS = (
138
149
  "apizit deploy . --wait",
139
150
  ),
140
151
  Command("api list", "List APIs in the workspace.", "read"),
152
+ Command(
153
+ "api use",
154
+ "Link an accessible API to this folder without replacing an existing link.",
155
+ "read",
156
+ (
157
+ argument(
158
+ "id", "Exact API name, slug or ID; otherwise select interactively.", optional=True
159
+ ),
160
+ ),
161
+ ),
141
162
  Command(
142
163
  "api show",
143
164
  "Show an API, its live URL and releases.",
@@ -283,42 +304,43 @@ COMMANDS = (
283
304
  _EXAMPLES = {
284
305
  "login": "apizit login",
285
306
  "logout": "apizit logout",
286
- "whoami": "apizit whoami --json",
287
- "workspace list": "apizit workspace list --json",
288
- "workspace use": "apizit workspace use ws_YOUR_WORKSPACE",
289
- "workspace show": "apizit workspace show --json",
307
+ "whoami": "apizit whoami",
308
+ "workspace list": "apizit workspace list",
309
+ "workspace use": "apizit workspace use",
310
+ "api use": 'apizit api use "weather-api"',
311
+ "workspace show": "apizit workspace show",
290
312
  "workspace create": 'apizit workspace create "Weather APIs"',
291
313
  "init": "apizit init . --name weather-api",
292
- "config validate": "apizit config validate . --json",
293
- "config show": "apizit config show . --region europe --json",
294
- "config schema": "apizit config schema --json",
295
- "api list": "apizit api list --json",
296
- "api show": "apizit api show api_YOUR_API --json",
297
- "api logs": "apizit api logs api_YOUR_API --level error --limit 50 --json",
298
- "api usage": "apizit api usage api_YOUR_API --json",
299
- "api openapi": "apizit api openapi api_YOUR_API --output openapi.json",
300
- "api key": "apizit api key api_YOUR_API --json",
301
- "api delete": "apizit api delete api_YOUR_API --yes --wait --json",
302
- "release list": "apizit release list --api api_YOUR_API --json",
303
- "release show": "apizit release show dep_YOUR_RELEASE --json",
304
- "release promote": "apizit release promote dep_YOUR_CANDIDATE --api api_YOUR_API --idempotency-key promote-release-001 --yes --json",
305
- "release rollback": "apizit release rollback --api api_YOUR_API --idempotency-key rollback-release-001 --yes --json",
306
- "release discard": "apizit release discard dep_YOUR_OLD_RELEASE --yes --wait --json",
307
- "job show": "apizit job show job_YOUR_JOB --json",
308
- "job wait": "apizit job wait job_YOUR_JOB --timeout 900 --json",
309
- "job cancel": "apizit job cancel job_YOUR_JOB --yes --json",
310
- "regions": "apizit regions --format standard --json",
311
- "plans": "apizit plans --json",
312
- "usage": "apizit usage --json",
313
- "capabilities": "apizit capabilities --json",
314
+ "config validate": "apizit config validate .",
315
+ "config show": "apizit config show . --region europe",
316
+ "config schema": "apizit config schema",
317
+ "api list": "apizit api list",
318
+ "api show": "apizit api show",
319
+ "api logs": "apizit api logs --level error --limit 50",
320
+ "api usage": "apizit api usage",
321
+ "api openapi": "apizit api openapi --output openapi.json",
322
+ "api key": "apizit api key",
323
+ "api delete": 'apizit api delete "weather-api" --wait',
324
+ "release list": "apizit release list --verbose",
325
+ "release show": "apizit release show dep_YOUR_RELEASE",
326
+ "release promote": "apizit release promote dep_YOUR_CANDIDATE --idempotency-key promote-release-001 --yes",
327
+ "release rollback": "apizit release rollback --idempotency-key rollback-release-001 --yes",
328
+ "release discard": "apizit release discard dep_YOUR_OLD_RELEASE --yes --wait",
329
+ "job show": "apizit job show latest",
330
+ "job wait": "apizit job wait latest --timeout 900",
331
+ "job cancel": "apizit job cancel job_YOUR_JOB --yes",
332
+ "regions": "apizit regions --format standard",
333
+ "plans": "apizit plans",
334
+ "usage": "apizit usage",
335
+ "capabilities": "apizit capabilities",
314
336
  "token create": 'apizit token create "Release CI" --scope read --scope scan --scope deploy --expires-days 30',
315
- "token list": "apizit token list --json",
337
+ "token list": "apizit token list",
316
338
  "token revoke": "apizit token revoke YOUR_TOKEN_ID --yes",
317
339
  "secret set": "apizit secret set weather_api_key --from-env WEATHER_API_KEY",
318
- "secret list": "apizit secret list --json",
340
+ "secret list": "apizit secret list",
319
341
  "secret delete": "apizit secret delete weather_api_key --yes",
320
342
  "docs": 'apizit docs "release promote"',
321
- "doctor": "apizit doctor --json",
343
+ "doctor": "apizit doctor",
322
344
  }
323
345
  COMMANDS = tuple(
324
346
  replace(command, example=command.example or _EXAMPLES[command.path]) for command in COMMANDS
@@ -93,6 +93,7 @@ class ClientContext:
93
93
  self.root = Path(root or Path.cwd()).resolve()
94
94
  self.path = home_directory() / "context.json"
95
95
  self.project_path = self.root / ".apizit" / "project.json"
96
+ self.operations_path = self.root / ".apizit" / "operations.json"
96
97
  self.preferences = read_json(self.path)
97
98
  self.project = read_json(self.project_path)
98
99
  self.origin = normalize_origin(
@@ -138,6 +139,83 @@ class ClientContext:
138
139
  write_json(self.path, self.preferences)
139
140
  self.workspace_id = workspace_id
140
141
 
142
+ def record_operation(self, kind, resource_id, *, source_kind=None, snapshot_id=None):
143
+ """Only selected non-secret references, scoped to this directory and endpoint."""
144
+ import re
145
+
146
+ if kind not in {"scan", "job"} or not re.fullmatch(
147
+ rf"{kind}_[A-Za-z0-9_-]{{1,128}}", resource_id
148
+ ):
149
+ raise CliError("Invalid operation reference.", code="INVALID_RESPONSE")
150
+ if kind == "scan" and (
151
+ source_kind not in {"local", "git"}
152
+ or (
153
+ snapshot_id is not None
154
+ and (
155
+ not isinstance(snapshot_id, str)
156
+ or not re.fullmatch(r"src_[A-Za-z0-9_-]{1,128}", snapshot_id)
157
+ )
158
+ )
159
+ ):
160
+ raise CliError("Invalid source reference.", code="INVALID_RESPONSE")
161
+ state = read_json(self.operations_path)
162
+ if state and state.get("schema_version") != 1:
163
+ raise CliError(
164
+ "Unsupported operations context.", code="CONTEXT_INVALID", exit_code=EXIT_USAGE
165
+ )
166
+ state.setdefault("schema_version", 1)
167
+ endpoints = state.setdefault("endpoints", {})
168
+ if not isinstance(endpoints, dict):
169
+ raise CliError(
170
+ "Invalid operations context.", code="CONTEXT_INVALID", exit_code=EXIT_USAGE
171
+ )
172
+ workspaces = endpoints.setdefault(self.origin, {})
173
+ if not isinstance(workspaces, dict):
174
+ raise CliError(
175
+ "Invalid operations context.", code="CONTEXT_INVALID", exit_code=EXIT_USAGE
176
+ )
177
+ scope = workspaces.setdefault(self.require_workspace(), {})
178
+ if not isinstance(scope, dict):
179
+ raise CliError(
180
+ "Invalid operations context.", code="CONTEXT_INVALID", exit_code=EXIT_USAGE
181
+ )
182
+ scope[kind] = {"id": resource_id}
183
+ if kind == "scan":
184
+ scope[kind].update(source_kind=source_kind, source_snapshot_id=snapshot_id)
185
+ write_json(self.operations_path, state)
186
+ self.ignore_context()
187
+
188
+ def latest_operation(self, kind):
189
+ import re
190
+
191
+ state = read_json(self.operations_path)
192
+ try:
193
+ value = state["endpoints"][self.origin][self.require_workspace()][kind]
194
+ valid = (
195
+ state["schema_version"] == 1
196
+ and isinstance(value, dict)
197
+ and re.fullmatch(rf"{kind}_[A-Za-z0-9_-]{{1,128}}", value.get("id", ""))
198
+ )
199
+ except (KeyError, TypeError, AttributeError):
200
+ valid = False
201
+ if not valid:
202
+ raise CliError(
203
+ f"No latest {kind} is recorded for this folder, endpoint and workspace. Use an explicit ID or start an interactive operation here.",
204
+ code="LATEST_NOT_FOUND",
205
+ exit_code=EXIT_USAGE,
206
+ )
207
+ return value
208
+
209
+ def ignore_context(self):
210
+ ignore = self.root / ".gitignore"
211
+ assert_safe_path(ignore)
212
+ previous = ignore.read_text(encoding="utf-8") if ignore.exists() else ""
213
+ if "/.apizit/" not in previous.splitlines():
214
+ with ignore.open("a", encoding="utf-8") as stream:
215
+ stream.write(
216
+ ("\n" if previous and not previous.endswith("\n") else "") + "/.apizit/\n"
217
+ )
218
+
141
219
  def bind_api(self, api_id):
142
220
  ignore = self.root / ".gitignore"
143
221
  assert_safe_path(ignore)
@@ -53,7 +53,7 @@ def build_parser():
53
53
  prog="apizit",
54
54
  description=(
55
55
  "Launch supported Python, JavaScript, TypeScript or Java projects as managed public "
56
- "APIs. Framework support follows the APIZIT compatibility guide. APIZIT prelaunch beta."
56
+ "APIs. Framework support follows the APIZIT compatibility guide. APIZIT is prelaunch."
57
57
  ),
58
58
  epilog=(
59
59
  "Managed frameworks: Flask, FastAPI, Django, Express, NestJS and Spring Boot. "
@@ -113,10 +113,12 @@ def main(argv=None):
113
113
  json_mode = "--json" in argv
114
114
  runner = None
115
115
  command = None
116
+ verbose = False
116
117
  exit_code = 0
117
118
  try:
118
119
  args = build_parser().parse_args(argv)
119
120
  json_mode = bool(getattr(args, "json", False))
121
+ verbose = bool(getattr(args, "verbose", False))
120
122
  command = args.command_path
121
123
  from apizit_cli.operations import CommandRunner
122
124
 
@@ -161,10 +163,21 @@ def main(argv=None):
161
163
  if json_mode:
162
164
  print(json.dumps(result, ensure_ascii=True, separators=(",", ":")))
163
165
  elif result["ok"]:
164
- # JSON quoting prevents untrusted API names or logs from executing terminal escapes.
165
- print(json.dumps(result["data"], ensure_ascii=True, indent=2))
166
+ from apizit_cli.presentation import render_success
167
+
168
+ print(
169
+ render_success(
170
+ command,
171
+ result["data"],
172
+ verbose=verbose,
173
+ latest=getattr(runner, "recorded_scan", False)
174
+ or getattr(runner, "recorded_job", False),
175
+ )
176
+ )
166
177
  else:
167
- print(json.dumps(result["error"], ensure_ascii=True, indent=2), file=sys.stderr)
178
+ from apizit_cli.presentation import render_error
179
+
180
+ print(render_error(result["error"], verbose=verbose), file=sys.stderr)
168
181
  return exit_code
169
182
 
170
183