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,170 @@
1
+ """CLI environment and platform preflight checks."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Annotated, Any
6
+
7
+ import typer
8
+
9
+ from platform_cli.errors import CLIError
10
+ from platform_cli.output import print_json, table
11
+ from platform_cli.runtime import Runtime
12
+
13
+
14
+ def doctor(
15
+ ctx: typer.Context,
16
+ raw: Annotated[bool, typer.Option("--json", help="Print machine-readable results.")] = False,
17
+ ) -> None:
18
+ """Check local CLI config, API reachability, and auth."""
19
+ runtime: Runtime = ctx.obj
20
+ checks: list[dict[str, str]] = []
21
+
22
+ checks.append(_config_check(runtime))
23
+ checks.append(_base_url_check(runtime))
24
+ checks.append(_api_key_check(runtime))
25
+
26
+ with runtime.client() as client:
27
+ try:
28
+ ready = client.public("GET", "/health/ready")
29
+ checks.append(
30
+ _check(
31
+ "api_ready",
32
+ "PASS",
33
+ _status_detail(ready),
34
+ "The platform API answered /health/ready.",
35
+ )
36
+ )
37
+ except CLIError as exc:
38
+ checks.append(
39
+ _check(
40
+ "api_ready",
41
+ "FAIL",
42
+ str(exc),
43
+ "Check --base-url or GENAUG_ADMIN_BASE_URL, then retry.",
44
+ )
45
+ )
46
+
47
+ if runtime.config.api_key:
48
+ try:
49
+ identity = client.admin("GET", "/me")
50
+ project_ids = identity.get("project_ids", []) if isinstance(identity, dict) else []
51
+ detail = (
52
+ f"auth_method={identity.get('auth_method', 'unknown')}, "
53
+ f"projects={len(project_ids or [])}"
54
+ if isinstance(identity, dict)
55
+ else "authenticated"
56
+ )
57
+ checks.append(
58
+ _check(
59
+ "auth",
60
+ "PASS",
61
+ detail,
62
+ "The configured key can call the admin API.",
63
+ )
64
+ )
65
+ except CLIError as exc:
66
+ checks.append(
67
+ _check(
68
+ "auth",
69
+ "FAIL",
70
+ str(exc),
71
+ "Run genaug auth login with a valid key or fix API key env overrides.",
72
+ )
73
+ )
74
+ else:
75
+ checks.append(
76
+ _check(
77
+ "auth",
78
+ "FAIL",
79
+ "No API key configured.",
80
+ "Run genaug auth login or set GENAUG_ADMIN_API_KEY.",
81
+ )
82
+ )
83
+
84
+ summary = {"verdict": _verdict(checks), "checks": checks}
85
+ if raw:
86
+ print_json(summary)
87
+ else:
88
+ table(
89
+ "General Augment Doctor",
90
+ ["Check", "Status", "Detail", "Next action"],
91
+ [
92
+ [item["name"], item["status"], item["detail"], item["next_action"]]
93
+ for item in checks
94
+ ],
95
+ )
96
+
97
+ if summary["verdict"] == "FAIL":
98
+ raise typer.Exit(1)
99
+
100
+
101
+ def _config_check(runtime: Runtime) -> dict[str, str]:
102
+ """Return the config-file check."""
103
+ if runtime.loaded_config_path.exists():
104
+ return _check(
105
+ "config",
106
+ "PASS",
107
+ f"loaded={runtime.loaded_config_path}",
108
+ "No action needed.",
109
+ )
110
+ return _check(
111
+ "config",
112
+ "WARN",
113
+ f"no saved config at {runtime.loaded_config_path}",
114
+ "Run genaug auth login to persist config, or keep using env overrides.",
115
+ )
116
+
117
+
118
+ def _base_url_check(runtime: Runtime) -> dict[str, str]:
119
+ """Return a base URL sanity check."""
120
+ base_url = runtime.config.base_url.rstrip("/")
121
+ if base_url.startswith(("http://", "https://")):
122
+ return _check("base_url", "PASS", base_url, "No action needed.")
123
+ return _check(
124
+ "base_url",
125
+ "FAIL",
126
+ base_url or "<empty>",
127
+ "Set --base-url or GENAUG_ADMIN_BASE_URL to an http(s) URL.",
128
+ )
129
+
130
+
131
+ def _api_key_check(runtime: Runtime) -> dict[str, str]:
132
+ """Return an API-key presence check without printing the key."""
133
+ if runtime.config.api_key:
134
+ return _check("api_key", "PASS", "configured", "No action needed.")
135
+ return _check(
136
+ "api_key",
137
+ "FAIL",
138
+ "missing",
139
+ "Run genaug auth login or set GENAUG_ADMIN_API_KEY.",
140
+ )
141
+
142
+
143
+ def _status_detail(payload: Any) -> str:
144
+ """Format a compact health-check detail."""
145
+ if isinstance(payload, dict):
146
+ status = payload.get("status") or payload.get("state") or "unknown"
147
+ db = payload.get("db")
148
+ redis = payload.get("redis")
149
+ dependencies = ", ".join(str(item) for item in (db, redis) if item)
150
+ return f"status={status}" + (f", {dependencies}" if dependencies else "")
151
+ return str(payload)
152
+
153
+
154
+ def _check(name: str, status: str, detail: str, next_action: str) -> dict[str, str]:
155
+ """Return one doctor check row."""
156
+ return {
157
+ "name": name,
158
+ "status": status,
159
+ "detail": detail,
160
+ "next_action": next_action,
161
+ }
162
+
163
+
164
+ def _verdict(checks: list[dict[str, str]]) -> str:
165
+ """Return the aggregate doctor verdict."""
166
+ if any(item["status"] == "FAIL" for item in checks):
167
+ return "FAIL"
168
+ if any(item["status"] == "WARN" for item in checks):
169
+ return "WARN"
170
+ return "PASS"
@@ -0,0 +1,433 @@
1
+ """Tenant identity-link 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 identity links.")
14
+
15
+
16
+ @app.command("list")
17
+ def list_identity_links(
18
+ ctx: typer.Context,
19
+ project: Annotated[str, typer.Option(help="Project id, slug, or name.")],
20
+ limit: Annotated[int, typer.Option(min=1, max=1000, help="Maximum links to return.")] = 100,
21
+ offset: Annotated[int, typer.Option(min=0, help="Pagination offset.")] = 0,
22
+ json_output: Annotated[
23
+ bool,
24
+ typer.Option("--json", help="Print machine-readable JSON."),
25
+ ] = False,
26
+ ) -> None:
27
+ """List verified and pending identity links for one project."""
28
+ runtime: Runtime = ctx.obj
29
+ with runtime.client() as client:
30
+ project_payload = resolve_project(client, project)
31
+ response = client.admin(
32
+ "GET",
33
+ f"/projects/{encode_path_segment(str(project_payload['id']))}/identity-links",
34
+ params={"limit": limit, "offset": offset},
35
+ )
36
+ if json_output:
37
+ print_json(response)
38
+ return
39
+ items = response.get("items", []) if isinstance(response, dict) else []
40
+ rows = [_identity_row(item) for item in items if isinstance(item, dict)]
41
+ table(
42
+ "Identity links",
43
+ ["Phone", "Provider", "Provider User", "Verified", "Linked At"],
44
+ rows,
45
+ )
46
+
47
+
48
+ @app.command("create-test")
49
+ def create_test_identity_link(
50
+ ctx: typer.Context,
51
+ project: Annotated[str, typer.Option(help="Project id, slug, or name.")],
52
+ phone: Annotated[str, typer.Option("--phone", help="Phone number to link.")],
53
+ provider_user_id: Annotated[
54
+ str,
55
+ typer.Option("--provider-user-id", help="Tenant app user id for this link."),
56
+ ],
57
+ provider_name: Annotated[
58
+ str,
59
+ typer.Option("--provider-name", help="Tenant identity provider name."),
60
+ ] = "app",
61
+ metadata: Annotated[
62
+ list[str] | None,
63
+ typer.Option("--metadata", help="Metadata as key=value. Repeatable."),
64
+ ] = None,
65
+ json_output: Annotated[
66
+ bool,
67
+ typer.Option("--json", help="Print machine-readable JSON."),
68
+ ] = False,
69
+ ) -> None:
70
+ """Create or update one verified test identity link."""
71
+ payload = {
72
+ "phone_e164": phone,
73
+ "provider_user_id": provider_user_id,
74
+ "provider_name": provider_name,
75
+ "metadata": _metadata_pairs(metadata or []),
76
+ }
77
+ runtime: Runtime = ctx.obj
78
+ with runtime.client() as client:
79
+ project_payload = resolve_project(client, project)
80
+ response = client.admin(
81
+ "POST",
82
+ f"/projects/{encode_path_segment(str(project_payload['id']))}/identity-links/test",
83
+ json=payload,
84
+ )
85
+ if json_output:
86
+ print_json(response)
87
+ return
88
+ table(
89
+ "Created test identity link",
90
+ ["Field", "Value"],
91
+ [
92
+ ["Phone", _value(response, "phone_e164") or phone],
93
+ ["Provider", _value(response, "provider_name") or provider_name],
94
+ ["Provider User", _value(response, "provider_user_id") or provider_user_id],
95
+ ["Verified", _value(response, "verified")],
96
+ ["Linked At", _value(response, "linked_at")],
97
+ ],
98
+ )
99
+
100
+
101
+ @app.command("link-user")
102
+ def link_user(
103
+ ctx: typer.Context,
104
+ project: Annotated[str, typer.Option(help="Project id, slug, or name.")],
105
+ phone: Annotated[str, typer.Option("--phone", help="Phone number to link.")],
106
+ provider_user_id: Annotated[
107
+ str,
108
+ typer.Option("--provider-user-id", help="Tenant app user id for this link."),
109
+ ],
110
+ provider_name: Annotated[
111
+ str,
112
+ typer.Option("--provider-name", help="Tenant identity provider name."),
113
+ ] = "app",
114
+ metadata: Annotated[
115
+ list[str] | None,
116
+ typer.Option("--metadata", help="Metadata as key=value. Repeatable."),
117
+ ] = None,
118
+ json_output: Annotated[
119
+ bool,
120
+ typer.Option("--json", help="Print machine-readable JSON."),
121
+ ] = False,
122
+ ) -> None:
123
+ """Create an app-initiated OTP identity-link challenge."""
124
+ response = _identity_challenge(
125
+ ctx,
126
+ project=project,
127
+ endpoint="link-user",
128
+ payload={
129
+ "phone_e164": phone,
130
+ "provider_user_id": provider_user_id,
131
+ "provider_name": provider_name,
132
+ "metadata": _metadata_pairs(metadata or []),
133
+ },
134
+ )
135
+ _print_challenge(
136
+ response,
137
+ title="Identity link challenge",
138
+ fallback_phone=phone,
139
+ fallback_provider=provider_name,
140
+ fallback_provider_user_id=provider_user_id,
141
+ json_output=json_output,
142
+ )
143
+
144
+
145
+ @app.command("verification-code")
146
+ def verification_code(
147
+ ctx: typer.Context,
148
+ project: Annotated[str, typer.Option(help="Project id, slug, or name.")],
149
+ phone: Annotated[str, typer.Option("--phone", help="Phone number to link.")],
150
+ provider_user_id: Annotated[
151
+ str,
152
+ typer.Option("--provider-user-id", help="Tenant app user id for this link."),
153
+ ],
154
+ provider_name: Annotated[
155
+ str,
156
+ typer.Option("--provider-name", help="Tenant identity provider name."),
157
+ ] = "app",
158
+ metadata: Annotated[
159
+ list[str] | None,
160
+ typer.Option("--metadata", help="Metadata as key=value. Repeatable."),
161
+ ] = None,
162
+ json_output: Annotated[
163
+ bool,
164
+ typer.Option("--json", help="Print machine-readable JSON."),
165
+ ] = False,
166
+ ) -> None:
167
+ """Create a code that the tenant app can show to the user."""
168
+ response = _identity_challenge(
169
+ ctx,
170
+ project=project,
171
+ endpoint="verification-code",
172
+ payload={
173
+ "phone_e164": phone,
174
+ "provider_user_id": provider_user_id,
175
+ "provider_name": provider_name,
176
+ "metadata": _metadata_pairs(metadata or []),
177
+ },
178
+ )
179
+ _print_challenge(
180
+ response,
181
+ title="Identity verification code",
182
+ fallback_phone=phone,
183
+ fallback_provider=provider_name,
184
+ fallback_provider_user_id=provider_user_id,
185
+ json_output=json_output,
186
+ )
187
+
188
+
189
+ @app.command("magic-link")
190
+ def magic_link(
191
+ ctx: typer.Context,
192
+ project: Annotated[str, typer.Option(help="Project id, slug, or name.")],
193
+ phone: Annotated[str, typer.Option("--phone", help="Phone number to link.")],
194
+ user_identifier: Annotated[
195
+ str,
196
+ typer.Option("--user-identifier", help="Email or app username to prefill."),
197
+ ],
198
+ provider_name: Annotated[
199
+ str,
200
+ typer.Option("--provider-name", help="Tenant identity provider name."),
201
+ ] = "app",
202
+ channel: Annotated[
203
+ str,
204
+ typer.Option("--channel", help="Delivery channel: whatsapp, sms, or telegram."),
205
+ ] = "whatsapp",
206
+ metadata: Annotated[
207
+ list[str] | None,
208
+ typer.Option("--metadata", help="Metadata as key=value. Repeatable."),
209
+ ] = None,
210
+ json_output: Annotated[
211
+ bool,
212
+ typer.Option("--json", help="Print machine-readable JSON."),
213
+ ] = False,
214
+ ) -> None:
215
+ """Create and optionally deliver an agent-initiated magic-link challenge."""
216
+ response = _identity_challenge(
217
+ ctx,
218
+ project=project,
219
+ endpoint="magic-link",
220
+ payload={
221
+ "phone_e164": phone,
222
+ "user_identifier": user_identifier,
223
+ "provider_name": provider_name,
224
+ "channel": channel,
225
+ "metadata": _metadata_pairs(metadata or []),
226
+ },
227
+ )
228
+ _print_challenge(
229
+ response,
230
+ title="Identity magic link",
231
+ fallback_phone=phone,
232
+ fallback_provider=provider_name,
233
+ fallback_provider_user_id=user_identifier,
234
+ json_output=json_output,
235
+ )
236
+
237
+
238
+ @app.command("verify")
239
+ def verify_identity(
240
+ ctx: typer.Context,
241
+ project: Annotated[str, typer.Option(help="Project id, slug, or name.")],
242
+ phone: Annotated[str, typer.Option("--phone", help="Phone number to verify.")],
243
+ code: Annotated[str, typer.Option("--code", help="OTP, texted code, or magic-link state.")],
244
+ provider_name: Annotated[
245
+ str,
246
+ typer.Option("--provider-name", help="Tenant identity provider name."),
247
+ ] = "app",
248
+ provider_user_id: Annotated[
249
+ str | None,
250
+ typer.Option("--provider-user-id", help="Override app user id after Auth0 callback."),
251
+ ] = None,
252
+ json_output: Annotated[
253
+ bool,
254
+ typer.Option("--json", help="Print machine-readable JSON."),
255
+ ] = False,
256
+ ) -> None:
257
+ """Verify a pending identity-link challenge."""
258
+ runtime: Runtime = ctx.obj
259
+ with runtime.client() as client:
260
+ project_payload = resolve_project(client, project)
261
+ payload = {
262
+ "phone_e164": phone,
263
+ "provider_name": provider_name,
264
+ "code": code,
265
+ }
266
+ if provider_user_id:
267
+ payload["provider_user_id"] = provider_user_id
268
+ response = client.integrations(
269
+ "POST",
270
+ f"/{encode_path_segment(str(project_payload['id']))}/verify",
271
+ json=payload,
272
+ )
273
+ _print_resolution(response, json_output=json_output, success_message="Identity verified")
274
+
275
+
276
+ @app.command("resolve")
277
+ def resolve_identity(
278
+ ctx: typer.Context,
279
+ project: Annotated[str, typer.Option(help="Project id, slug, or name.")],
280
+ phone: Annotated[str, typer.Option("--phone", help="Phone number to resolve.")],
281
+ provider_name: Annotated[
282
+ str | None,
283
+ typer.Option("--provider-name", help="Optional tenant identity provider name."),
284
+ ] = None,
285
+ json_output: Annotated[
286
+ bool,
287
+ typer.Option("--json", help="Print machine-readable JSON."),
288
+ ] = False,
289
+ ) -> None:
290
+ """Resolve a verified phone-to-app-account identity link."""
291
+ runtime: Runtime = ctx.obj
292
+ with runtime.client() as client:
293
+ project_payload = resolve_project(client, project)
294
+ params = {"provider_name": provider_name} if provider_name else None
295
+ response = client.integrations(
296
+ "GET",
297
+ f"/{encode_path_segment(str(project_payload['id']))}/resolve/{encode_path_segment(phone)}",
298
+ params=params,
299
+ )
300
+ _print_resolution(response, json_output=json_output, success_message="Identity resolved")
301
+
302
+
303
+ @app.command("unlink")
304
+ def unlink_identity(
305
+ ctx: typer.Context,
306
+ project: Annotated[str, typer.Option(help="Project id, slug, or name.")],
307
+ phone: Annotated[str, typer.Option("--phone", help="Phone number to unlink.")],
308
+ provider_name: Annotated[
309
+ str,
310
+ typer.Option("--provider-name", help="Tenant identity provider name."),
311
+ ],
312
+ yes: Annotated[
313
+ bool,
314
+ typer.Option("--yes", help="Confirm unlinking this identity mapping."),
315
+ ] = False,
316
+ json_output: Annotated[
317
+ bool,
318
+ typer.Option("--json", help="Print machine-readable JSON."),
319
+ ] = False,
320
+ ) -> None:
321
+ """Remove one phone-to-app-account identity link."""
322
+ if not yes and not typer.confirm(f"Unlink {phone} from {provider_name}?"):
323
+ raise typer.Exit(1)
324
+ runtime: Runtime = ctx.obj
325
+ with runtime.client() as client:
326
+ project_payload = resolve_project(client, project)
327
+ response = client.integrations(
328
+ "DELETE",
329
+ f"/{encode_path_segment(str(project_payload['id']))}/unlink/{encode_path_segment(phone)}",
330
+ params={"provider_name": provider_name},
331
+ )
332
+ if json_output:
333
+ print_json(response)
334
+ return
335
+ message = "Identity unlinked" if _value(response, "unlinked") else "Identity link not found"
336
+ print_success(message)
337
+
338
+
339
+ def _identity_row(link: dict[str, Any]) -> list[object]:
340
+ """Return a table row for one identity link."""
341
+ return [
342
+ link.get("phone_e164", ""),
343
+ link.get("provider_name", ""),
344
+ link.get("provider_user_id", ""),
345
+ "yes" if link.get("verified") else "no",
346
+ link.get("linked_at", ""),
347
+ ]
348
+
349
+
350
+ def _identity_challenge(
351
+ ctx: typer.Context,
352
+ *,
353
+ project: str,
354
+ endpoint: str,
355
+ payload: dict[str, Any],
356
+ ) -> object:
357
+ """Create one identity challenge through the integration API."""
358
+ runtime: Runtime = ctx.obj
359
+ with runtime.client() as client:
360
+ project_payload = resolve_project(client, project)
361
+ return client.integrations(
362
+ "POST",
363
+ f"/{encode_path_segment(str(project_payload['id']))}/{endpoint}",
364
+ json=payload,
365
+ )
366
+
367
+
368
+ def _print_challenge(
369
+ response: object,
370
+ *,
371
+ title: str,
372
+ fallback_phone: str,
373
+ fallback_provider: str,
374
+ fallback_provider_user_id: str,
375
+ json_output: bool,
376
+ ) -> None:
377
+ """Print an identity challenge response."""
378
+ if json_output:
379
+ print_json(response)
380
+ return
381
+ table(
382
+ title,
383
+ ["Field", "Value"],
384
+ [
385
+ ["Phone", _value(response, "phone_e164") or fallback_phone],
386
+ ["Provider", _value(response, "provider_name") or fallback_provider],
387
+ ["Provider User", _value(response, "provider_user_id") or fallback_provider_user_id],
388
+ ["Expires", _value(response, "verification_expires_at")],
389
+ ["Magic Link", _value(response, "magic_link")],
390
+ ["Debug Code", _value(response, "debug_verification_code")],
391
+ ],
392
+ )
393
+
394
+
395
+ def _print_resolution(
396
+ response: object,
397
+ *,
398
+ json_output: bool,
399
+ success_message: str,
400
+ ) -> None:
401
+ """Print an identity resolution response."""
402
+ if json_output:
403
+ print_json(response)
404
+ return
405
+ print_success(success_message)
406
+ table(
407
+ "Identity resolution",
408
+ ["Field", "Value"],
409
+ [
410
+ ["Phone", _value(response, "phone_e164")],
411
+ ["Provider", _value(response, "provider_name")],
412
+ ["Provider User", _value(response, "provider_user_id")],
413
+ ["Linked At", _value(response, "linked_at")],
414
+ ],
415
+ )
416
+
417
+
418
+ def _metadata_pairs(values: list[str]) -> dict[str, str]:
419
+ """Parse repeated key=value metadata flags."""
420
+ parsed: dict[str, str] = {}
421
+ for item in values:
422
+ key, separator, value = item.partition("=")
423
+ if not separator or not key.strip():
424
+ raise typer.BadParameter("--metadata values must use key=value.")
425
+ parsed[key.strip()] = value
426
+ return parsed
427
+
428
+
429
+ def _value(payload: object, key: str) -> object:
430
+ """Safely read a value from a response mapping."""
431
+ if isinstance(payload, dict):
432
+ return payload.get(key, "")
433
+ return ""
@@ -0,0 +1,55 @@
1
+ """Starter agent scaffold command."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Annotated
7
+
8
+ import typer
9
+
10
+ from platform_cli.errors import CLIError
11
+ from platform_cli.openapi import scaffold_basic_agent
12
+ from platform_cli.output import print_success, print_warning, table
13
+
14
+
15
+ def init(
16
+ name: Annotated[str, typer.Argument(help="Agent/project name, such as dayplan.")],
17
+ output_dir: Annotated[Path | None, typer.Option(help="Output directory.")] = None,
18
+ display_name: Annotated[str | None, typer.Option(help="Tenant-facing display name.")] = None,
19
+ description: Annotated[
20
+ str | None,
21
+ typer.Option(help="Agent purpose shown in SOUL.md and the handoff prompt."),
22
+ ] = None,
23
+ tool: Annotated[
24
+ list[str] | None,
25
+ typer.Option("--tool", help="Builtin tool ID to enable, for example web_search."),
26
+ ] = None,
27
+ force: Annotated[bool, typer.Option(help="Overwrite existing starter files.")] = False,
28
+ ) -> None:
29
+ """Create a starter genaug-agent.yaml workspace without an OpenAPI spec."""
30
+ try:
31
+ result = scaffold_basic_agent(
32
+ name=name,
33
+ output_dir=output_dir,
34
+ display_name=display_name,
35
+ description=description,
36
+ builtin_tools=tool,
37
+ force=force,
38
+ )
39
+ except FileExistsError as exc:
40
+ raise CLIError(str(exc)) from exc
41
+ rows: list[list[object]] = [
42
+ ["Manifest", result.config_path],
43
+ ["Personality", result.soul_path],
44
+ ["Skills", result.skills_dir],
45
+ ["Tools", result.tools_dir],
46
+ ["Handoff", result.agent_prompt_path],
47
+ ]
48
+ table("Starter agent scaffold", ["File", "Path"], rows)
49
+ print_success(f"Generated starter agent in {result.root}")
50
+ if result.builtin_tools:
51
+ print_success(f"Enabled builtin tools: {', '.join(result.builtin_tools)}")
52
+ else:
53
+ print_warning("No builtin tools enabled yet. Use --tool or genaug tools toggle later.")
54
+ typer.echo(f"Next: genaug dev {result.config_path} --message \"What can you help me with?\"")
55
+ typer.echo(f"Then: genaug deploy {result.config_path}")