general-augment-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 (42) hide show
  1. general_augment_cli-0.1.0.dist-info/METADATA +180 -0
  2. general_augment_cli-0.1.0.dist-info/RECORD +42 -0
  3. general_augment_cli-0.1.0.dist-info/WHEEL +4 -0
  4. general_augment_cli-0.1.0.dist-info/entry_points.txt +2 -0
  5. platform_cli/__init__.py +5 -0
  6. platform_cli/branding.py +27 -0
  7. platform_cli/client.py +179 -0
  8. platform_cli/commands/__init__.py +1 -0
  9. platform_cli/commands/approvals.py +150 -0
  10. platform_cli/commands/auth.py +96 -0
  11. platform_cli/commands/billing.py +143 -0
  12. platform_cli/commands/channels.py +212 -0
  13. platform_cli/commands/deploy.py +72 -0
  14. platform_cli/commands/dev.py +38 -0
  15. platform_cli/commands/doctor.py +170 -0
  16. platform_cli/commands/identity.py +433 -0
  17. platform_cli/commands/init.py +55 -0
  18. platform_cli/commands/integrate.py +94 -0
  19. platform_cli/commands/keys.py +116 -0
  20. platform_cli/commands/logs.py +43 -0
  21. platform_cli/commands/mcp.py +258 -0
  22. platform_cli/commands/memory.py +316 -0
  23. platform_cli/commands/mock.py +30 -0
  24. platform_cli/commands/model_providers.py +226 -0
  25. platform_cli/commands/observability.py +174 -0
  26. platform_cli/commands/onboarding.py +72 -0
  27. platform_cli/commands/projects.py +302 -0
  28. platform_cli/commands/skills.py +116 -0
  29. platform_cli/commands/smoke.py +280 -0
  30. platform_cli/commands/status.py +49 -0
  31. platform_cli/commands/tools.py +179 -0
  32. platform_cli/commands/users.py +150 -0
  33. platform_cli/commands/validate.py +96 -0
  34. platform_cli/commands/verify.py +648 -0
  35. platform_cli/config.py +114 -0
  36. platform_cli/errors.py +103 -0
  37. platform_cli/local_mock.py +1392 -0
  38. platform_cli/main.py +130 -0
  39. platform_cli/openapi.py +1048 -0
  40. platform_cli/output.py +47 -0
  41. platform_cli/readiness.py +176 -0
  42. platform_cli/runtime.py +22 -0
@@ -0,0 +1,316 @@
1
+ """Memory management commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Annotated, Any
6
+
7
+ import typer
8
+
9
+ from platform_cli.client import encode_path_segment, resolve_project
10
+ from platform_cli.output import print_json, print_success, table
11
+ from platform_cli.runtime import Runtime
12
+
13
+ app = typer.Typer(help="Manage tenant user memory.")
14
+
15
+ MEMORY_FACT_TYPES = {"preference", "fact", "entity", "summary"}
16
+
17
+
18
+ @app.command("store")
19
+ def store_memory(
20
+ ctx: typer.Context,
21
+ fact: Annotated[str, typer.Argument(help="Durable memory fact to store.")],
22
+ user: Annotated[str, typer.Option("--user", help="Tenant app user id.")],
23
+ project: Annotated[
24
+ str | None,
25
+ typer.Option(help="Project id, slug, or name when using a management key."),
26
+ ] = None,
27
+ fact_type: Annotated[
28
+ str,
29
+ typer.Option("--fact-type", help="Memory fact type: preference, fact, entity, summary."),
30
+ ] = "fact",
31
+ importance: Annotated[
32
+ float,
33
+ typer.Option("--importance", min=0.0, max=1.0, help="Memory importance score."),
34
+ ] = 0.8,
35
+ source: Annotated[
36
+ str,
37
+ typer.Option(help="Non-secret source label for this memory write."),
38
+ ] = "genaug-cli-memory",
39
+ metadata: Annotated[
40
+ list[str] | None,
41
+ typer.Option("--metadata", help="Metadata as key=value. Repeatable."),
42
+ ] = None,
43
+ idempotency_key: Annotated[
44
+ str | None,
45
+ typer.Option(help="Replay-safe key for retryable memory writes."),
46
+ ] = None,
47
+ json_output: Annotated[
48
+ bool,
49
+ typer.Option("--json", help="Print machine-readable JSON."),
50
+ ] = False,
51
+ ) -> None:
52
+ """Store one explicit memory fact for a tenant app user."""
53
+ normalized_fact_type = _fact_type(fact_type)
54
+ runtime: Runtime = ctx.obj
55
+ payload = {
56
+ "user_id": user,
57
+ "fact": fact,
58
+ "fact_type": normalized_fact_type,
59
+ "importance_score": importance,
60
+ "source": source,
61
+ "metadata": _metadata_pairs(metadata or []),
62
+ }
63
+ if idempotency_key:
64
+ payload["idempotency_key"] = idempotency_key
65
+ with runtime.client() as client:
66
+ project_payload, headers = _project_context(client, project)
67
+ response = client.app(
68
+ "POST",
69
+ "/api/v1/agent/memory/store",
70
+ json=payload,
71
+ headers=headers,
72
+ )
73
+ if json_output:
74
+ print_json(response)
75
+ return
76
+ table(
77
+ "Stored memory",
78
+ ["Field", "Value"],
79
+ [
80
+ ["Project", _project_label(project_payload)],
81
+ ["User", user],
82
+ ["Memory ID", _value(response, "memory_id")],
83
+ ["Type", normalized_fact_type],
84
+ ["Source", _value(response, "source") or source],
85
+ ],
86
+ )
87
+
88
+
89
+ @app.command("search")
90
+ def search_memory(
91
+ ctx: typer.Context,
92
+ user: Annotated[str, typer.Option("--user", help="Tenant app user id.")],
93
+ project: Annotated[
94
+ str | None,
95
+ typer.Option(help="Project id, slug, or name when using a management key."),
96
+ ] = None,
97
+ query: Annotated[
98
+ str,
99
+ typer.Option("--query", "-q", help="Semantic memory query. Empty returns recent facts."),
100
+ ] = "",
101
+ limit: Annotated[int, typer.Option(min=1, max=50, help="Maximum facts to return.")] = 10,
102
+ min_similarity: Annotated[
103
+ float,
104
+ typer.Option("--min-similarity", min=0.0, max=1.0, help="Minimum semantic similarity."),
105
+ ] = 0.7,
106
+ fact_type: Annotated[
107
+ str | None,
108
+ typer.Option("--fact-type", help="Optional fact type filter."),
109
+ ] = None,
110
+ min_importance: Annotated[
111
+ float | None,
112
+ typer.Option("--min-importance", min=0.0, max=1.0, help="Optional importance filter."),
113
+ ] = None,
114
+ source: Annotated[
115
+ str | None,
116
+ typer.Option(help="Optional source filter."),
117
+ ] = None,
118
+ json_output: Annotated[
119
+ bool,
120
+ typer.Option("--json", help="Print machine-readable JSON."),
121
+ ] = False,
122
+ ) -> None:
123
+ """Search one tenant user's memory facts."""
124
+ payload: dict[str, Any] = {
125
+ "user_id": user,
126
+ "query": query,
127
+ "limit": limit,
128
+ "min_similarity": min_similarity,
129
+ }
130
+ if fact_type is not None:
131
+ payload["fact_type"] = _fact_type(fact_type)
132
+ if min_importance is not None:
133
+ payload["min_importance"] = min_importance
134
+ if source:
135
+ payload["source"] = source
136
+ runtime: Runtime = ctx.obj
137
+ with runtime.client() as client:
138
+ _, headers = _project_context(client, project)
139
+ response = client.app(
140
+ "POST",
141
+ "/api/v1/agent/memory/search",
142
+ json=payload,
143
+ headers=headers,
144
+ )
145
+ if json_output:
146
+ print_json(response)
147
+ return
148
+ facts = response.get("facts", []) if isinstance(response, dict) else []
149
+ rows = [
150
+ [
151
+ fact.get("id") or fact.get("memory_id") or "",
152
+ fact.get("fact_type", ""),
153
+ fact.get("content", ""),
154
+ fact.get("importance_score", ""),
155
+ fact.get("similarity", ""),
156
+ fact.get("source", ""),
157
+ ]
158
+ for fact in facts
159
+ if isinstance(fact, dict)
160
+ ]
161
+ table(
162
+ f"Memory for {user}",
163
+ ["ID", "Type", "Content", "Importance", "Similarity", "Source"],
164
+ rows,
165
+ )
166
+
167
+
168
+ @app.command("profile")
169
+ def memory_profile(
170
+ ctx: typer.Context,
171
+ user: Annotated[str, typer.Option("--user", help="Tenant app user id.")],
172
+ project: Annotated[
173
+ str | None,
174
+ typer.Option(help="Project id, slug, or name when using a management key."),
175
+ ] = None,
176
+ json_output: Annotated[
177
+ bool,
178
+ typer.Option("--json", help="Print machine-readable JSON."),
179
+ ] = False,
180
+ ) -> None:
181
+ """Show one tenant user's memory profile and recent facts."""
182
+ runtime: Runtime = ctx.obj
183
+ with runtime.client() as client:
184
+ _, headers = _project_context(client, project)
185
+ response = client.app(
186
+ "GET",
187
+ f"/api/v1/agent/memory/profile/{encode_path_segment(user)}",
188
+ headers=headers,
189
+ )
190
+ if json_output:
191
+ print_json(response)
192
+ return
193
+ profile = response.get("profile", {}) if isinstance(response, dict) else {}
194
+ recent = response.get("recent_facts", []) if isinstance(response, dict) else []
195
+ table(
196
+ f"Memory profile for {user}",
197
+ ["Field", "Value"],
198
+ [
199
+ ["General Augment user", _value(response, "general_augment_user_id")],
200
+ ["Total facts", _value(response, "total_facts")],
201
+ ["Profile keys", ", ".join(sorted(profile)) if isinstance(profile, dict) else ""],
202
+ ["Recent facts", len(recent) if isinstance(recent, list) else 0],
203
+ ],
204
+ )
205
+
206
+
207
+ @app.command("delete")
208
+ def delete_memory(
209
+ ctx: typer.Context,
210
+ memory_id: Annotated[str, typer.Argument(help="Memory fact id to delete.")],
211
+ user: Annotated[str, typer.Option("--user", help="Tenant app user id.")],
212
+ project: Annotated[
213
+ str | None,
214
+ typer.Option(help="Project id, slug, or name when using a management key."),
215
+ ] = None,
216
+ json_output: Annotated[
217
+ bool,
218
+ typer.Option("--json", help="Print machine-readable JSON."),
219
+ ] = False,
220
+ ) -> None:
221
+ """Delete one memory fact for one tenant app user."""
222
+ runtime: Runtime = ctx.obj
223
+ with runtime.client() as client:
224
+ _, headers = _project_context(client, project)
225
+ response = client.app(
226
+ "DELETE",
227
+ f"/api/v1/agent/memory/{encode_path_segment(memory_id)}",
228
+ params={"user_id": user},
229
+ headers=headers,
230
+ )
231
+ if json_output:
232
+ print_json(response)
233
+ return
234
+ status = response.get("status", "deleted") if isinstance(response, dict) else "deleted"
235
+ print_success(f"Memory {memory_id} {status}.")
236
+
237
+
238
+ @app.command("purge-user")
239
+ def purge_user_memory(
240
+ ctx: typer.Context,
241
+ user: Annotated[str, typer.Option("--user", help="Tenant app user id.")],
242
+ project: Annotated[
243
+ str | None,
244
+ typer.Option(help="Project id, slug, or name when using a management key."),
245
+ ] = None,
246
+ yes: Annotated[
247
+ bool,
248
+ typer.Option("--yes", help="Confirm purging all scoped memory for this app user."),
249
+ ] = False,
250
+ json_output: Annotated[
251
+ bool,
252
+ typer.Option("--json", help="Print machine-readable JSON."),
253
+ ] = False,
254
+ ) -> None:
255
+ """Purge all memory facts for one tenant app user."""
256
+ if not yes and not typer.confirm(f"Purge all memory for app user {user}?"):
257
+ raise typer.Exit(1)
258
+ runtime: Runtime = ctx.obj
259
+ with runtime.client() as client:
260
+ _, headers = _project_context(client, project)
261
+ response = client.app(
262
+ "DELETE",
263
+ f"/api/v1/agent/memory/user/{encode_path_segment(user)}",
264
+ headers=headers,
265
+ )
266
+ if json_output:
267
+ print_json(response)
268
+ return
269
+ deleted_count = response.get("deleted_count", 0) if isinstance(response, dict) else 0
270
+ print_success(f"Purged {deleted_count} memory fact(s) for {user}.")
271
+
272
+
273
+ def _project_context(
274
+ client: Any,
275
+ project: str | None,
276
+ ) -> tuple[dict[str, Any] | None, dict[str, str]]:
277
+ """Return project metadata and app-facing project context headers."""
278
+ if not project:
279
+ return None, {}
280
+ project_payload = resolve_project(client, project)
281
+ return project_payload, {"X-Project-ID": str(project_payload["id"])}
282
+
283
+
284
+ def _project_label(project: dict[str, Any] | None) -> str:
285
+ """Return a compact display label for project context."""
286
+ if not project:
287
+ return "configured project"
288
+ return str(project.get("slug") or project.get("name") or project.get("id") or "")
289
+
290
+
291
+ def _metadata_pairs(values: list[str]) -> dict[str, str]:
292
+ """Parse repeated key=value metadata flags."""
293
+ parsed: dict[str, str] = {}
294
+ for item in values:
295
+ key, separator, value = item.partition("=")
296
+ if not separator or not key.strip():
297
+ raise typer.BadParameter("--metadata values must use key=value.")
298
+ parsed[key.strip()] = value
299
+ return parsed
300
+
301
+
302
+ def _fact_type(value: str) -> str:
303
+ """Validate memory fact type for the public memory API."""
304
+ normalized = value.strip().lower()
305
+ if normalized not in MEMORY_FACT_TYPES:
306
+ raise typer.BadParameter(
307
+ "--fact-type must be one of: " + ", ".join(sorted(MEMORY_FACT_TYPES))
308
+ )
309
+ return normalized
310
+
311
+
312
+ def _value(payload: object, key: str) -> object:
313
+ """Safely read a value from a response mapping."""
314
+ if isinstance(payload, dict):
315
+ return payload.get(key, "")
316
+ return ""
@@ -0,0 +1,30 @@
1
+ """Local mock server command."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Annotated
6
+
7
+ import typer
8
+
9
+ from platform_cli.local_mock import DEFAULT_HOST, DEFAULT_PORT, run_server
10
+
11
+
12
+ def mock(
13
+ host: Annotated[
14
+ str,
15
+ typer.Option("--host", help="Interface to bind the local mock server to."),
16
+ ] = DEFAULT_HOST,
17
+ port: Annotated[
18
+ int,
19
+ typer.Option("--port", min=1, max=65535, help="TCP port for the local mock server."),
20
+ ] = DEFAULT_PORT,
21
+ quiet: Annotated[
22
+ bool,
23
+ typer.Option("--quiet", help="Suppress per-request HTTP access logs."),
24
+ ] = False,
25
+ ) -> None:
26
+ """Run the local General Augment HTTP mock for app contract tests."""
27
+ try:
28
+ run_server(host, port, quiet=quiet)
29
+ except KeyboardInterrupt:
30
+ typer.echo("\nStopped General Augment local mock.", err=True)
@@ -0,0 +1,226 @@
1
+ """Tenant-owned model provider credential commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from typing import Annotated
7
+
8
+ import typer
9
+
10
+ from platform_cli.client import encode_path_segment, resolve_project
11
+ from platform_cli.output import print_json, print_success, table
12
+ from platform_cli.runtime import Runtime
13
+
14
+ app = typer.Typer(help="Manage tenant-owned model provider keys.")
15
+
16
+
17
+ @app.command("list")
18
+ def list_model_providers(
19
+ ctx: typer.Context,
20
+ project: Annotated[str, typer.Option(help="Project id, slug, or name.")],
21
+ json_output: Annotated[
22
+ bool,
23
+ typer.Option("--json", help="Print machine-readable JSON."),
24
+ ] = False,
25
+ ) -> None:
26
+ """List model provider credentials without exposing secrets."""
27
+ runtime: Runtime = ctx.obj
28
+ with runtime.client() as client:
29
+ project_payload = resolve_project(client, project)
30
+ response = client.admin(
31
+ "GET",
32
+ f"/projects/{encode_path_segment(str(project_payload['id']))}/model-providers",
33
+ )
34
+ if json_output:
35
+ print_json(response)
36
+ return
37
+ items = response.get("items", []) if isinstance(response, dict) else []
38
+ rows = [_provider_row(item) for item in items if isinstance(item, dict)]
39
+ table(
40
+ "Model providers",
41
+ ["Provider", "Status", "API Mode", "Base URL", "Prefixes", "Validated At"],
42
+ rows,
43
+ )
44
+
45
+
46
+ @app.command("set")
47
+ def set_model_provider(
48
+ ctx: typer.Context,
49
+ provider: Annotated[str, typer.Argument(help="Provider id, such as openai or anthropic.")],
50
+ project: Annotated[str, typer.Option(help="Project id, slug, or name.")],
51
+ api_key: Annotated[
52
+ str | None,
53
+ typer.Option("--api-key", help="Provider API key. Omit to enter a hidden prompt."),
54
+ ] = None,
55
+ api_key_env: Annotated[
56
+ str | None,
57
+ typer.Option("--api-key-env", help="Read the provider API key from this env var."),
58
+ ] = None,
59
+ base_url: Annotated[str | None, typer.Option("--base-url", help="Optional base URL.")] = None,
60
+ api_mode: Annotated[str | None, typer.Option("--api-mode", help="Optional API mode.")] = None,
61
+ model_prefix: Annotated[
62
+ list[str] | None,
63
+ typer.Option("--model-prefix", help="Model prefix this key can serve. Repeatable."),
64
+ ] = None,
65
+ json_output: Annotated[
66
+ bool,
67
+ typer.Option("--json", help="Print machine-readable JSON."),
68
+ ] = False,
69
+ ) -> None:
70
+ """Store or rotate one tenant-owned model provider API key."""
71
+ secret = _provider_api_key(api_key=api_key, api_key_env=api_key_env)
72
+ payload: dict[str, object] = {"api_key": secret}
73
+ if base_url is not None:
74
+ payload["base_url"] = base_url
75
+ if api_mode is not None:
76
+ payload["api_mode"] = api_mode
77
+ if model_prefix:
78
+ payload["model_prefixes"] = list(model_prefix)
79
+ runtime: Runtime = ctx.obj
80
+ with runtime.client() as client:
81
+ project_payload = resolve_project(client, project)
82
+ response = client.admin(
83
+ "PUT",
84
+ (
85
+ f"/projects/{encode_path_segment(str(project_payload['id']))}/model-providers/"
86
+ f"{encode_path_segment(provider)}"
87
+ ),
88
+ json=payload,
89
+ )
90
+ if json_output:
91
+ print_json(response)
92
+ return
93
+ stored_provider = _value(response, "provider") or provider
94
+ print_success(f"Stored model provider credential for {stored_provider}.")
95
+ table(
96
+ "Model provider",
97
+ ["Field", "Value"],
98
+ [
99
+ ["Provider", _value(response, "provider") or provider],
100
+ ["Status", _value(response, "status")],
101
+ ["API Mode", _value(response, "api_mode")],
102
+ ["Base URL Configured", _value(response, "base_url_configured")],
103
+ ["Model Prefixes", _prefixes(response)],
104
+ ["Validated At", _value(response, "last_validated_at")],
105
+ ],
106
+ )
107
+
108
+
109
+ @app.command("health")
110
+ def check_model_provider_health(
111
+ ctx: typer.Context,
112
+ provider: Annotated[str, typer.Argument(help="Provider id, such as openai or anthropic.")],
113
+ project: Annotated[str, typer.Option(help="Project id, slug, or name.")],
114
+ json_output: Annotated[
115
+ bool,
116
+ typer.Option("--json", help="Print machine-readable JSON."),
117
+ ] = False,
118
+ ) -> None:
119
+ """Validate one stored model provider credential."""
120
+ runtime: Runtime = ctx.obj
121
+ with runtime.client() as client:
122
+ project_payload = resolve_project(client, project)
123
+ response = client.admin(
124
+ "POST",
125
+ (
126
+ f"/projects/{encode_path_segment(str(project_payload['id']))}/model-providers/"
127
+ f"{encode_path_segment(provider)}/health-check"
128
+ ),
129
+ )
130
+ if json_output:
131
+ print_json(response)
132
+ return
133
+ table(
134
+ "Model provider health",
135
+ ["Field", "Value"],
136
+ [
137
+ ["Provider", _value(response, "provider") or provider],
138
+ ["Status", _value(response, "status")],
139
+ ["Message", _value(response, "message")],
140
+ ["HTTP Status", _value(response, "status_code")],
141
+ ["Retryable", _value(response, "retryable")],
142
+ ["Latency MS", _value(response, "latency_ms")],
143
+ ["Validated At", _value(response, "last_validated_at")],
144
+ ],
145
+ )
146
+
147
+
148
+ @app.command("revoke")
149
+ def revoke_model_provider(
150
+ ctx: typer.Context,
151
+ provider: Annotated[str, typer.Argument(help="Provider id, such as openai or anthropic.")],
152
+ project: Annotated[str, typer.Option(help="Project id, slug, or name.")],
153
+ yes: Annotated[
154
+ bool,
155
+ typer.Option("--yes", help="Confirm revoking this tenant-owned provider key."),
156
+ ] = False,
157
+ json_output: Annotated[
158
+ bool,
159
+ typer.Option("--json", help="Print machine-readable JSON."),
160
+ ] = False,
161
+ ) -> None:
162
+ """Revoke one tenant-owned model provider credential."""
163
+ if not yes and not typer.confirm(f"Revoke model provider credential for {provider}?"):
164
+ raise typer.Exit(1)
165
+ runtime: Runtime = ctx.obj
166
+ with runtime.client() as client:
167
+ project_payload = resolve_project(client, project)
168
+ response = client.admin(
169
+ "DELETE",
170
+ (
171
+ f"/projects/{encode_path_segment(str(project_payload['id']))}/model-providers/"
172
+ f"{encode_path_segment(provider)}"
173
+ ),
174
+ )
175
+ if json_output:
176
+ print_json(response)
177
+ return
178
+ revoked_provider = _value(response, "provider") or provider
179
+ print_success(f"Revoked model provider credential for {revoked_provider}.")
180
+
181
+
182
+ def _provider_api_key(*, api_key: str | None, api_key_env: str | None) -> str:
183
+ """Resolve a provider API key from an option, env var, or hidden prompt."""
184
+
185
+ if api_key and api_key_env:
186
+ raise typer.BadParameter("Use only one of --api-key or --api-key-env.")
187
+ if api_key_env:
188
+ value = os.getenv(api_key_env)
189
+ if not value:
190
+ raise typer.BadParameter(f"Environment variable {api_key_env} is not set.")
191
+ return value
192
+ if api_key:
193
+ return api_key
194
+ return str(typer.prompt("Provider API key", hide_input=True))
195
+
196
+
197
+ def _provider_row(item: dict[str, object]) -> list[object]:
198
+ """Return one model provider list row."""
199
+
200
+ return [
201
+ item.get("provider", ""),
202
+ item.get("status", ""),
203
+ item.get("api_mode", "") or "",
204
+ item.get("base_url_configured", ""),
205
+ _prefixes(item),
206
+ item.get("last_validated_at", "") or "",
207
+ ]
208
+
209
+
210
+ def _prefixes(payload: object) -> str:
211
+ """Return model prefixes as a printable string."""
212
+
213
+ if not isinstance(payload, dict):
214
+ return ""
215
+ prefixes = payload.get("model_prefixes")
216
+ if not isinstance(prefixes, list):
217
+ return ""
218
+ return ", ".join(str(prefix) for prefix in prefixes)
219
+
220
+
221
+ def _value(payload: object, key: str) -> object:
222
+ """Safely read one response value."""
223
+
224
+ if isinstance(payload, dict):
225
+ return payload.get(key, "")
226
+ return ""