guzli-cli 0.2.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 (53) hide show
  1. guzli/__init__.py +3 -0
  2. guzli/__main__.py +4 -0
  3. guzli/auth.py +182 -0
  4. guzli/cli.py +84 -0
  5. guzli/commands/__init__.py +1 -0
  6. guzli/commands/admin.py +421 -0
  7. guzli/commands/auth.py +502 -0
  8. guzli/commands/campaign_launch.py +188 -0
  9. guzli/commands/campaigns.py +571 -0
  10. guzli/commands/common.py +42 -0
  11. guzli/commands/conversations.py +404 -0
  12. guzli/commands/integrations.py +48 -0
  13. guzli/commands/telephony.py +274 -0
  14. guzli/commands/voice_calls.py +270 -0
  15. guzli/config.py +137 -0
  16. guzli/config_store.py +218 -0
  17. guzli/errors.py +47 -0
  18. guzli/http_client.py +213 -0
  19. guzli/mcp/__init__.py +1 -0
  20. guzli/mcp/server.py +320 -0
  21. guzli/models.py +173 -0
  22. guzli/oauth_callback.py +126 -0
  23. guzli/output.py +79 -0
  24. guzli/runtime.py +159 -0
  25. guzli/services/__init__.py +29 -0
  26. guzli/services/api_keys.py +105 -0
  27. guzli/services/calls.py +48 -0
  28. guzli/services/campaigns.py +193 -0
  29. guzli/services/cli_auth.py +103 -0
  30. guzli/services/conversations.py +112 -0
  31. guzli/services/integrations.py +18 -0
  32. guzli/services/replies.py +21 -0
  33. guzli/services/telephony.py +80 -0
  34. guzli/services/voice_profiles.py +56 -0
  35. guzli/services/webhooks.py +169 -0
  36. guzli/types.py +15 -0
  37. guzli/validators.py +83 -0
  38. guzli/workflows/__init__.py +13 -0
  39. guzli/workflows/campaign_launch.py +501 -0
  40. guzli_cli-0.2.0.data/data/share/guzli/CHANGELOG.md +16 -0
  41. guzli_cli-0.2.0.data/data/share/guzli/PROVENANCE.md +23 -0
  42. guzli_cli-0.2.0.data/data/share/guzli/SECURITY.md +18 -0
  43. guzli_cli-0.2.0.data/data/share/guzli/SECURITY_AUDIT.md +47 -0
  44. guzli_cli-0.2.0.data/data/share/guzli/SKILL.md +101 -0
  45. guzli_cli-0.2.0.data/data/share/guzli/examples/compliance-advisory-us.json +15 -0
  46. guzli_cli-0.2.0.data/data/share/guzli/examples/pizza-order-extraction.json +65 -0
  47. guzli_cli-0.2.0.data/data/share/guzli/references/api.md +245 -0
  48. guzli_cli-0.2.0.dist-info/METADATA +341 -0
  49. guzli_cli-0.2.0.dist-info/RECORD +53 -0
  50. guzli_cli-0.2.0.dist-info/WHEEL +5 -0
  51. guzli_cli-0.2.0.dist-info/entry_points.txt +3 -0
  52. guzli_cli-0.2.0.dist-info/licenses/LICENSE +201 -0
  53. guzli_cli-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,421 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+
5
+ from guzli.commands.common import add_json_flag as _add_json_flag
6
+ from guzli.commands.common import add_org_arg as _add_org_arg
7
+ from guzli.commands.common import require_org_id as _require_org_id
8
+ from guzli.errors import ValidationError
9
+ from guzli.runtime import RuntimeContext
10
+
11
+
12
+ def _add_api_keys_list(sub: argparse._SubParsersAction) -> None:
13
+ parser = sub.add_parser("list", help="List workspace API keys")
14
+ _add_org_arg(parser)
15
+ _add_json_flag(parser)
16
+ parser.set_defaults(handler=handle_api_keys_list)
17
+
18
+
19
+ def _add_api_keys_create(sub: argparse._SubParsersAction) -> None:
20
+ parser = sub.add_parser("create", help="Create a workspace API key (secret returned once)")
21
+ _add_org_arg(parser)
22
+ parser.add_argument("--name", required=True, help="Human-readable name")
23
+ parser.add_argument(
24
+ "--usage-type",
25
+ choices=["server", "client"],
26
+ default="server",
27
+ help="server = signing/automation; client = browser/widget (default: server)",
28
+ )
29
+ parser.add_argument("--environment", default="production")
30
+ parser.add_argument(
31
+ "--allowed-domains",
32
+ nargs="*",
33
+ default=[],
34
+ help="Domain bindings for client-type keys (e.g. example.com app.example.com)",
35
+ )
36
+ parser.add_argument("--rate-limit", type=int, dest="rate_limit")
37
+ parser.add_argument(
38
+ "--expires-at",
39
+ dest="expires_at",
40
+ help="ISO-8601 expiry. API-created server keys cap at created_at + 365d.",
41
+ )
42
+ parser.add_argument(
43
+ "--allow-identity-signing", action="store_true", dest="allow_identity_signing"
44
+ )
45
+ _add_json_flag(parser)
46
+ parser.set_defaults(handler=handle_api_keys_create)
47
+
48
+
49
+ def _add_api_keys_update(sub: argparse._SubParsersAction) -> None:
50
+ parser = sub.add_parser("update", help="Update a workspace API key")
51
+ _add_org_arg(parser)
52
+ parser.add_argument("api_key_id")
53
+ parser.add_argument("--name")
54
+ parser.add_argument("--allowed-domains", nargs="*", dest="allowed_domains")
55
+ parser.add_argument("--rate-limit", type=int, dest="rate_limit")
56
+ parser.add_argument("--is-active", dest="is_active", choices=["true", "false"])
57
+ parser.add_argument("--expires-at", dest="expires_at")
58
+ parser.add_argument("--usage-type", dest="usage_type", choices=["server", "client"])
59
+ parser.add_argument(
60
+ "--allow-identity-signing",
61
+ dest="allow_identity_signing",
62
+ choices=["true", "false"],
63
+ )
64
+ _add_json_flag(parser)
65
+ parser.set_defaults(handler=handle_api_keys_update)
66
+
67
+
68
+ def _add_api_keys_delete(sub: argparse._SubParsersAction) -> None:
69
+ parser = sub.add_parser("delete", help="Soft-deactivate a workspace API key")
70
+ _add_org_arg(parser)
71
+ parser.add_argument("api_key_id")
72
+ parser.add_argument("--confirm", action="store_true", help="Required to proceed")
73
+ parser.set_defaults(handler=handle_api_keys_delete)
74
+
75
+
76
+ def handle_api_keys_list(context: RuntimeContext, args: argparse.Namespace) -> None:
77
+ org = _require_org_id(context, args)
78
+ payload = context.api_keys.list_keys(org)
79
+ if getattr(args, "json", False):
80
+ context.renderer.print_json(payload)
81
+ return
82
+ items = payload.get("items", []) if isinstance(payload, dict) else []
83
+ if not items:
84
+ print("No API keys.")
85
+ return
86
+ for k in items:
87
+ expires = k.get("expires_at") or "(never)"
88
+ status_tag = "active" if k.get("is_active") else "inactive"
89
+ print(
90
+ f"{k.get('id')} {k.get('name')} [{k.get('usage_type')}] "
91
+ f"prefix={k.get('key_prefix')} expires={expires} {status_tag}"
92
+ )
93
+
94
+
95
+ def handle_api_keys_create(context: RuntimeContext, args: argparse.Namespace) -> None:
96
+ org = _require_org_id(context, args)
97
+ result = context.api_keys.create_key(
98
+ org,
99
+ name=args.name,
100
+ environment=args.environment,
101
+ allowed_domains=args.allowed_domains or None,
102
+ rate_limit=args.rate_limit,
103
+ expires_at=args.expires_at,
104
+ usage_type=args.usage_type,
105
+ allow_identity_signing=args.allow_identity_signing,
106
+ )
107
+ if getattr(args, "json", False):
108
+ context.renderer.print_json(result)
109
+ return
110
+ secret = result.get("secret") if isinstance(result, dict) else None
111
+ key = result.get("key", {}) if isinstance(result, dict) else {}
112
+ print("IMPORTANT: secret_value shown ONCE — save it now.")
113
+ print(f"secret_value: {secret}")
114
+ print(f"id: {key.get('id')}")
115
+ print(f"name: {key.get('name')}")
116
+ print(f"usage_type: {key.get('usage_type')}")
117
+ print(f"key_prefix: {key.get('key_prefix')}")
118
+ print(f"expires_at: {key.get('expires_at') or '(never)'}")
119
+
120
+
121
+ def handle_api_keys_update(context: RuntimeContext, args: argparse.Namespace) -> None:
122
+ org = _require_org_id(context, args)
123
+ updates: dict[str, object] = {}
124
+ if args.name is not None:
125
+ updates["name"] = args.name
126
+ if args.allowed_domains is not None:
127
+ updates["allowed_domains"] = args.allowed_domains
128
+ if args.rate_limit is not None:
129
+ updates["rate_limit"] = args.rate_limit
130
+ if args.is_active is not None:
131
+ updates["is_active"] = args.is_active == "true"
132
+ if args.expires_at is not None:
133
+ updates["expires_at"] = args.expires_at
134
+ if args.usage_type is not None:
135
+ updates["usage_type"] = args.usage_type
136
+ if args.allow_identity_signing is not None:
137
+ updates["allow_identity_signing"] = args.allow_identity_signing == "true"
138
+ result = context.api_keys.update_key(org, args.api_key_id, **updates) # type: ignore[arg-type]
139
+ if getattr(args, "json", False):
140
+ context.renderer.print_json(result)
141
+ else:
142
+ print(f"Updated {args.api_key_id}")
143
+
144
+
145
+ def handle_api_keys_delete(context: RuntimeContext, args: argparse.Namespace) -> None:
146
+ if not args.confirm:
147
+ raise ValidationError("--confirm flag required to delete")
148
+ org = _require_org_id(context, args)
149
+ context.api_keys.delete_key(org, args.api_key_id)
150
+ print(f"Deactivated {args.api_key_id}")
151
+
152
+
153
+ def _add_webhooks_list(sub: argparse._SubParsersAction) -> None:
154
+ parser = sub.add_parser("list", help="List webhook integrations for the org")
155
+ _add_org_arg(parser)
156
+ _add_json_flag(parser)
157
+ parser.set_defaults(handler=handle_webhooks_list)
158
+
159
+
160
+ def _add_webhooks_get(sub: argparse._SubParsersAction) -> None:
161
+ parser = sub.add_parser("get", help="Get one webhook integration")
162
+ _add_org_arg(parser)
163
+ parser.add_argument("integration_id")
164
+ _add_json_flag(parser)
165
+ parser.set_defaults(handler=handle_webhooks_get)
166
+
167
+
168
+ def _add_webhooks_create(sub: argparse._SubParsersAction) -> None:
169
+ parser = sub.add_parser("create", help="Create a webhook integration")
170
+ _add_org_arg(parser)
171
+ parser.add_argument("--name", required=True)
172
+ parser.add_argument("--agent-id", dest="webhook_agent_id", required=True, help="Agent UUID")
173
+ parser.add_argument(
174
+ "--url", required=True, help="Receiver URL (must match org domain allowlist if set)"
175
+ )
176
+ parser.add_argument(
177
+ "--api-key-id",
178
+ dest="api_key_id",
179
+ required=True,
180
+ help="Workspace server API key id used to sign deliveries",
181
+ )
182
+ parser.add_argument(
183
+ "--events",
184
+ nargs="+",
185
+ default=[],
186
+ help="Event names to subscribe (e.g. lead_tagged voice_session_status)",
187
+ )
188
+ parser.add_argument("--signature-version", dest="signature_version", default="v1")
189
+ parser.add_argument("--status", default="active", choices=["active", "inactive"])
190
+ _add_json_flag(parser)
191
+ parser.set_defaults(handler=handle_webhooks_create)
192
+
193
+
194
+ def _add_webhooks_update(sub: argparse._SubParsersAction) -> None:
195
+ parser = sub.add_parser("update", help="Update a webhook integration")
196
+ _add_org_arg(parser)
197
+ parser.add_argument("integration_id")
198
+ parser.add_argument("--name")
199
+ parser.add_argument("--agent-id", dest="webhook_agent_id")
200
+ parser.add_argument("--url")
201
+ parser.add_argument("--api-key-id", dest="api_key_id")
202
+ parser.add_argument("--events", nargs="+")
203
+ parser.add_argument("--signature-version", dest="signature_version")
204
+ parser.add_argument("--status", choices=["active", "inactive"])
205
+ _add_json_flag(parser)
206
+ parser.set_defaults(handler=handle_webhooks_update)
207
+
208
+
209
+ def _add_webhooks_delete(sub: argparse._SubParsersAction) -> None:
210
+ parser = sub.add_parser("delete", help="Soft-deactivate a webhook integration")
211
+ _add_org_arg(parser)
212
+ parser.add_argument("integration_id")
213
+ parser.add_argument("--confirm", action="store_true", help="Required to proceed")
214
+ parser.set_defaults(handler=handle_webhooks_delete)
215
+
216
+
217
+ def handle_webhooks_list(context: RuntimeContext, args: argparse.Namespace) -> None:
218
+ org = _require_org_id(context, args)
219
+ payload = context.webhooks.list(org)
220
+ if getattr(args, "json", False):
221
+ context.renderer.print_json(payload)
222
+ return
223
+ items = payload.get("items", []) if isinstance(payload, dict) else []
224
+ if not items:
225
+ print("No webhook integrations.")
226
+ return
227
+ for w in items:
228
+ print(
229
+ f"{w.get('id')} {w.get('name')} status={w.get('status')} "
230
+ f"url={w.get('url')} events={','.join(w.get('events', []))}"
231
+ )
232
+
233
+
234
+ def handle_webhooks_get(context: RuntimeContext, args: argparse.Namespace) -> None:
235
+ org = _require_org_id(context, args)
236
+ payload = context.webhooks.get(org, args.integration_id)
237
+ if getattr(args, "json", False):
238
+ context.renderer.print_json(payload)
239
+ else:
240
+ context.renderer.print_json(payload)
241
+
242
+
243
+ def handle_webhooks_create(context: RuntimeContext, args: argparse.Namespace) -> None:
244
+ org = _require_org_id(context, args)
245
+ result = context.webhooks.create(
246
+ org,
247
+ name=args.name,
248
+ agent_id=args.webhook_agent_id,
249
+ url=args.url,
250
+ api_key_id=args.api_key_id,
251
+ events=args.events,
252
+ signature_version=args.signature_version,
253
+ status=args.status,
254
+ )
255
+ if getattr(args, "json", False):
256
+ context.renderer.print_json(result)
257
+ return
258
+ if isinstance(result, dict):
259
+ print(f"Created webhook integration {result.get('id')}")
260
+ print(f" url: {result.get('url')}")
261
+ print(f" events: {','.join(result.get('events', []))}")
262
+ print(f" health: {result.get('health_reason') or 'ok'}")
263
+
264
+
265
+ def handle_webhooks_update(context: RuntimeContext, args: argparse.Namespace) -> None:
266
+ org = _require_org_id(context, args)
267
+ result = context.webhooks.update(
268
+ org,
269
+ args.integration_id,
270
+ name=args.name,
271
+ agent_id=args.webhook_agent_id,
272
+ url=args.url,
273
+ api_key_id=args.api_key_id,
274
+ events=args.events,
275
+ signature_version=args.signature_version,
276
+ status=args.status,
277
+ )
278
+ if getattr(args, "json", False):
279
+ context.renderer.print_json(result)
280
+ else:
281
+ print(f"Updated {args.integration_id}")
282
+
283
+
284
+ def handle_webhooks_delete(context: RuntimeContext, args: argparse.Namespace) -> None:
285
+ if not args.confirm:
286
+ raise ValidationError("--confirm flag required to delete")
287
+ org = _require_org_id(context, args)
288
+ context.webhooks.delete(org, args.integration_id)
289
+ print(f"Deactivated {args.integration_id}")
290
+
291
+
292
+ def _add_webhook_domain_policy_get(sub: argparse._SubParsersAction) -> None:
293
+ parser = sub.add_parser(
294
+ "get", help="Show the current allowed-domains list (empty = permissive)"
295
+ )
296
+ _add_org_arg(parser)
297
+ _add_json_flag(parser)
298
+ parser.set_defaults(handler=handle_webhook_domain_policy_get)
299
+
300
+
301
+ def _add_webhook_domain_policy_set(sub: argparse._SubParsersAction) -> None:
302
+ parser = sub.add_parser("set", help="Replace the allowed-domains list")
303
+ _add_org_arg(parser)
304
+ parser.add_argument(
305
+ "domains",
306
+ nargs="*",
307
+ help="Hostnames or *.wildcard patterns (e.g. campaigns.guzli.ai *.example.com). Empty = permissive.",
308
+ )
309
+ _add_json_flag(parser)
310
+ parser.set_defaults(handler=handle_webhook_domain_policy_set)
311
+
312
+
313
+ def handle_webhook_domain_policy_get(context: RuntimeContext, args: argparse.Namespace) -> None:
314
+ org = _require_org_id(context, args)
315
+ payload = context.webhook_domain_policy.get(org)
316
+ if getattr(args, "json", False):
317
+ context.renderer.print_json(payload)
318
+ return
319
+ if isinstance(payload, dict):
320
+ domains = payload.get("allowed_domains") or []
321
+ if not domains:
322
+ print("(permissive — no allowlist set, all webhook URLs accepted)")
323
+ else:
324
+ print("Allowed domains:")
325
+ for d in domains:
326
+ print(f" - {d}")
327
+
328
+
329
+ def handle_webhook_domain_policy_set(context: RuntimeContext, args: argparse.Namespace) -> None:
330
+ org = _require_org_id(context, args)
331
+ result = context.webhook_domain_policy.set(org, list(args.domains))
332
+ if getattr(args, "json", False):
333
+ context.renderer.print_json(result)
334
+ else:
335
+ print("Policy updated.")
336
+
337
+
338
+ def _add_audit_list(sub: argparse._SubParsersAction) -> None:
339
+ parser = sub.add_parser("list", help="List integration security audit events")
340
+ _add_org_arg(parser)
341
+ parser.add_argument(
342
+ "--entity-type",
343
+ dest="entity_type",
344
+ help="Filter (e.g. api_key, webhook_integration)",
345
+ )
346
+ parser.add_argument("--entity-id", dest="entity_id")
347
+ parser.add_argument(
348
+ "--event-type",
349
+ dest="event_type",
350
+ help="Filter (e.g. api_key_created, webhook_integration_deleted)",
351
+ )
352
+ parser.add_argument("--limit", type=int)
353
+ _add_json_flag(parser)
354
+ parser.set_defaults(handler=handle_audit_list)
355
+
356
+
357
+ def handle_audit_list(context: RuntimeContext, args: argparse.Namespace) -> None:
358
+ org = _require_org_id(context, args)
359
+ payload = context.audit.list(
360
+ org,
361
+ entity_type=args.entity_type,
362
+ entity_id=args.entity_id,
363
+ event_type=args.event_type,
364
+ limit=args.limit,
365
+ )
366
+ if getattr(args, "json", False):
367
+ context.renderer.print_json(payload)
368
+ return
369
+ items = payload.get("items", []) if isinstance(payload, dict) else []
370
+ if not items:
371
+ print("No audit events.")
372
+ return
373
+ for e in items:
374
+ actor = e.get("actor_user_id") or e.get("actor_api_key_id") or "(unknown)"
375
+ summary = e.get("webhook_url_host") or e.get("api_key_prefix") or e.get("entity_id") or ""
376
+ # auth_source is more precise than auth_mode: distinguishes dashboard_supabase_jwt
377
+ # vs cli_oauth_session vs api_key vs engine_access_token. Fall back to auth_mode
378
+ # for older audit rows written before the source column existed.
379
+ source = e.get("auth_source") or e.get("auth_mode") or "?"
380
+ cli_sess = e.get("cli_session_id")
381
+ suffix = f" cli_sess={cli_sess}" if cli_sess else ""
382
+ print(
383
+ f"{e.get('created_at')} {source:24} {e.get('event_type'):32} "
384
+ f"actor={actor} {summary}{suffix}"
385
+ )
386
+
387
+
388
+ def register(subparsers: argparse._SubParsersAction) -> None:
389
+ api_keys = subparsers.add_parser(
390
+ "api-keys",
391
+ help="Manage workspace API keys (requires JWT bearer; api-key auth rejected)",
392
+ )
393
+ api_keys_sub = api_keys.add_subparsers(dest="api_keys_command", required=True)
394
+ _add_api_keys_list(api_keys_sub)
395
+ _add_api_keys_create(api_keys_sub)
396
+ _add_api_keys_update(api_keys_sub)
397
+ _add_api_keys_delete(api_keys_sub)
398
+
399
+ webhooks = subparsers.add_parser(
400
+ "webhooks", help="Manage webhook integrations (requires JWT bearer)"
401
+ )
402
+ webhooks_sub = webhooks.add_subparsers(dest="webhooks_command", required=True)
403
+ _add_webhooks_list(webhooks_sub)
404
+ _add_webhooks_get(webhooks_sub)
405
+ _add_webhooks_create(webhooks_sub)
406
+ _add_webhooks_update(webhooks_sub)
407
+ _add_webhooks_delete(webhooks_sub)
408
+
409
+ domain_policy = subparsers.add_parser(
410
+ "webhook-domain-policy",
411
+ help="Get / set the per-org webhook URL domain allowlist (requires JWT bearer)",
412
+ )
413
+ policy_sub = domain_policy.add_subparsers(dest="webhook_domain_policy_command", required=True)
414
+ _add_webhook_domain_policy_get(policy_sub)
415
+ _add_webhook_domain_policy_set(policy_sub)
416
+
417
+ audit = subparsers.add_parser(
418
+ "audit", help="Inspect the integration security audit log (requires JWT bearer)"
419
+ )
420
+ audit_sub = audit.add_subparsers(dest="audit_command", required=True)
421
+ _add_audit_list(audit_sub)