pingera-cli 0.1.5__tar.gz → 0.1.6__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 (50) hide show
  1. {pingera_cli-0.1.5/pingera_cli.egg-info → pingera_cli-0.1.6}/PKG-INFO +1 -1
  2. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/docs/cli-commands.md +93 -7
  3. pingera_cli-0.1.6/docs/example-check.json +23 -0
  4. pingera_cli-0.1.6/docs/example-check.yaml +21 -0
  5. pingera_cli-0.1.6/docs/example-synthetic-screenshot.json +16 -0
  6. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/__init__.py +1 -1
  7. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/commands/checks.py +132 -13
  8. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/commands/on_demand_checks.py +119 -9
  9. {pingera_cli-0.1.5 → pingera_cli-0.1.6/pingera_cli.egg-info}/PKG-INFO +1 -1
  10. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli.egg-info/SOURCES.txt +3 -0
  11. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/.gitignore +0 -0
  12. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/.replit +0 -0
  13. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/LICENSE +0 -0
  14. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/README.md +0 -0
  15. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/assert +0 -0
  16. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/__main__.py +0 -0
  17. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/commands/__init__.py +0 -0
  18. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/commands/auth.py +0 -0
  19. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/commands/base.py +0 -0
  20. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/commands/check_groups.py +0 -0
  21. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/commands/check_secrets.py +0 -0
  22. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/commands/secrets.py +0 -0
  23. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/formatters/__init__.py +0 -0
  24. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/formatters/base_formatter.py +0 -0
  25. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/formatters/generic_formatter.py +0 -0
  26. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/formatters/multistep_formatter.py +0 -0
  27. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/formatters/registry.py +0 -0
  28. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/formatters/ssl_formatter.py +0 -0
  29. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/formatters/synthetic_formatter.py +0 -0
  30. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/formatters/web_formatter.py +0 -0
  31. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/main.py +0 -0
  32. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/utils/__init__.py +0 -0
  33. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/utils/config.py +0 -0
  34. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli/utils/console.py +0 -0
  35. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli.egg-info/dependency_links.txt +0 -0
  36. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli.egg-info/entry_points.txt +0 -0
  37. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli.egg-info/requires.txt +0 -0
  38. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pingera_cli.egg-info/top_level.txt +0 -0
  39. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/pyproject.toml +0 -0
  40. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/replit.md +0 -0
  41. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/run_tests.py +0 -0
  42. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/setup.cfg +0 -0
  43. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/setup.py +0 -0
  44. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/tests/__init__.py +0 -0
  45. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/tests/conftest.py +0 -0
  46. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/tests/requirements.txt +0 -0
  47. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/tests/test_auth.py +0 -0
  48. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/tests/test_checks.py +0 -0
  49. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/tests/test_config.py +0 -0
  50. {pingera_cli-0.1.5 → pingera_cli-0.1.6}/tests/test_on_demand_checks.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pingera-cli
3
- Version: 0.1.5
3
+ Version: 0.1.6
4
4
  Summary: A beautiful Python CLI tool built with typer and rich, distributed via pip and based on Pingera SDK
5
5
  Home-page: https://github.com/pingera/pingera-cli
6
6
  Author: PingeraCLI Team
@@ -9,7 +9,7 @@ The `pngr` CLI provides a comprehensive interface to the Pingera monitoring plat
9
9
  These options are available for all commands:
10
10
 
11
11
  ```
12
- --api-key <key> API key for authentication (can also use PINGERA_API_KEY env var)
12
+ --api-key <key> API key for authentication (can also be used PINGERA_API_KEY env var)
13
13
  --base-url <url> API base URL (default: https://api.pingera.ru)
14
14
  --output <format> Output format: table, json, yaml (default: table)
15
15
  --page-id <id> Default page ID for status page operations (can also use PINGERA_PAGE_ID env var)
@@ -183,7 +183,7 @@ pngr heartbeats get <heartbeat-id>
183
183
  pngr heartbeats create --name <name> --interval <seconds> [--grace-period <seconds>]
184
184
 
185
185
  # Update heartbeat
186
- pngr heartbeats update <heartbeat-id> [--name <name>] [--interval <seconds>]
186
+ pngr heartbeats update <heartbeat-id> --name <name> --interval <seconds>
187
187
 
188
188
  # Delete heartbeat
189
189
  pngr heartbeats delete <heartbeat-id> [--confirm]
@@ -211,7 +211,7 @@ pngr pages get <page-id>
211
211
  pngr pages create --name <name> [--description <desc>] [--subdomain <sub>] [--theme <theme>]
212
212
 
213
213
  # Update status page
214
- pngr pages update <page-id> [--name <name>] [--description <desc>] [--theme <theme>]
214
+ pngr pages update <page-id> --name <name> --description <desc> --theme <theme>
215
215
 
216
216
  # Delete status page
217
217
  pngr pages delete <page-id> [--confirm]
@@ -231,7 +231,7 @@ pngr components get <component-id> [--page-id <id>]
231
231
  pngr components create --name <name> [--description <desc>] [--status <status>] [--page-id <id>]
232
232
 
233
233
  # Update component
234
- pngr components update <component-id> [--name <name>] [--description <desc>] [--page-id <id>]
234
+ pngr components update <component-id> --name <name> --description <desc> --page-id <id>
235
235
 
236
236
  # Delete component
237
237
  pngr components delete <component-id> [--page-id <id>] [--confirm]
@@ -260,7 +260,7 @@ pngr incidents get <incident-id> [--page-id <id>]
260
260
  pngr incidents create --name <name> --body <text> --status <status> --impact <impact> [--page-id <id>]
261
261
 
262
262
  # Update incident
263
- pngr incidents update <incident-id> [--name <name>] [--body <text>] [--status <status>] [--page-id <id>]
263
+ pngr incidents update <incident-id> --name <name> --body <text> --status <status> --page-id <id>
264
264
 
265
265
  # Delete incident
266
266
  pngr incidents delete <incident-id> [--page-id <id>] [--confirm]
@@ -273,7 +273,93 @@ pngr incidents updates update <incident-id> <update-id> --body <text> [--page-id
273
273
  pngr incidents updates delete <incident-id> <update-id> [--page-id <id>] [--confirm]
274
274
  ```
275
275
 
276
- ## Secrets Management
276
+ ## 📄 Check Configuration Files
277
+
278
+ You can create checks from JSON or YAML configuration files using the `--from-file` option:
279
+
280
+ ### JSON Format
281
+ ```json
282
+ {
283
+ "name": "My API Check",
284
+ "type": "api",
285
+ "url": "https://api.example.com/health",
286
+ "interval": 300,
287
+ "timeout": 30,
288
+ "parameters": {
289
+ "regions": ["US", "EU"],
290
+ "http_request": {
291
+ "method": "POST",
292
+ "headers": {
293
+ "Authorization": "Bearer token123",
294
+ "Content-Type": "application/json"
295
+ },
296
+ "body": "{\"check\": \"health\"}"
297
+ }
298
+ }
299
+ }
300
+ ```
301
+
302
+ ### YAML Format
303
+ ```yaml
304
+ name: "My Synthetic Check"
305
+ type: "synthetic"
306
+ interval: 600
307
+ timeout: 60
308
+ parameters:
309
+ regions:
310
+ - "US"
311
+ - "EU"
312
+ pw_script: |
313
+ const { test, expect } = require('@playwright/test');
314
+ test('login test', async ({ page }) => {
315
+ await page.goto('https://example.com/login');
316
+ await page.fill('#username', 'testuser');
317
+ await page.fill('#password', 'password');
318
+ await page.click('button[type="submit"]');
319
+ await expect(page).toHaveURL(/dashboard/);
320
+ });
321
+ # Marketplace fields (ignored by CLI, used by web interface)
322
+ marketplace:
323
+ tags: ["authentication", "login"]
324
+ description: "Tests user login functionality"
325
+ category: "user-flows"
326
+ ```
327
+
328
+ ### Field Priority
329
+ When using `--from-file` with command line options:
330
+ - Command line options override file values
331
+ - File provides defaults for unspecified options
332
+ - Required validations still apply
333
+ - Non-SDK fields (like `marketplace`, custom fields) are ignored and not sent to the API
334
+ - CLI-specific fields like `pw_script_file` are processed but not sent to the SDK
335
+
336
+ ### Create a new check
337
+ ```bash
338
+ # From command line options
339
+ pngr checks create --name "My Website" --type web --url "https://example.com"
340
+
341
+ # From configuration file
342
+ pngr checks create --from-file check-config.json
343
+ pngr checks create --from-file check-config.yaml
344
+
345
+ # Combine file and command line (command line options override file values)
346
+ pngr checks create --from-file check-config.json --name "Override Name"
347
+ ```
348
+
349
+ ### Run on-demand checks
350
+ ```bash
351
+ # From command line options
352
+ pngr checks run custom --url https://example.com --type web --wait-for-result
353
+
354
+ # From configuration file
355
+ pngr checks run custom --from-file check-config.json --wait-for-result
356
+ pngr checks run custom --from-file check-config.yaml --wait-for-result
357
+
358
+ # Combine file and command line (command line options override file values)
359
+ pngr checks run custom --from-file check-config.json --name "Override Name" --wait-for-result
360
+ ```
361
+
362
+ ## 🔐 Secrets Management
277
363
 
278
364
  Manage organization secrets for use in monitoring checks:
279
365
 
@@ -400,7 +486,7 @@ pngr config set defaults.page_id your-page-id
400
486
  pngr checks list --status active
401
487
 
402
488
  # Create a web check
403
- pngr checks create --name "API Health" --type web --url https://api.example.com --interval 300
489
+ pngr checks create --name "My Website" --type web --url "https://example.com"
404
490
 
405
491
  # Create a TCP check
406
492
  pngr checks create --name "Database Connection" --type tcp --host db.example.com --port 5432
@@ -0,0 +1,23 @@
1
+
2
+ {
3
+ "name": "Example Web Check",
4
+ "type": "web",
5
+ "url": "https://example.com",
6
+ "interval": 300,
7
+ "timeout": 30,
8
+ "parameters": {
9
+ "regions": ["RU, Moscow", "EU, West"],
10
+ "search_text": "Welcome",
11
+ "http_request": {
12
+ "method": "GET",
13
+ "headers": {
14
+ "User-Agent": "PingeraCLI/1.0"
15
+ }
16
+ }
17
+ },
18
+ "marketplace": {
19
+ "tags": ["web", "basic"],
20
+ "name": "Simple website check with string search",
21
+ "description": "Queries website URL and searches for a string in the response",
22
+ }
23
+ }
@@ -0,0 +1,21 @@
1
+
2
+ name: "Example Synthetic Check"
3
+ type: "synthetic"
4
+ interval: 600
5
+ timeout: 60
6
+ parameters:
7
+ regions:
8
+ - "US"
9
+ - "EU"
10
+ pw_script: |
11
+ const { test, expect } = require('@playwright/test');
12
+
13
+ test('example test', async ({ page }) => {
14
+ await page.goto('https://example.com');
15
+ await expect(page).toHaveTitle(/Example/);
16
+ await page.click('text=Login');
17
+ await page.fill('input[name="username"]', 'testuser');
18
+ await page.fill('input[name="password"]', 'testpass');
19
+ await page.click('button[type="submit"]');
20
+ await expect(page).toHaveURL(/dashboard/);
21
+ });
@@ -0,0 +1,16 @@
1
+
2
+ {
3
+ "name": "Website Screenshot Monitor",
4
+ "type": "synthetic",
5
+ "interval": 1800,
6
+ "timeout": 30,
7
+ "parameters": {
8
+ "regions": ["RU, Moscow"],
9
+ "pw_script": "const { test, expect } = require('@playwright/test');\n\ntest('website screenshot test', async ({ page }) => {\n // Navigate to the website\n await page.goto('https://pingera.ru');\n \n // Wait for the page to fully load\n await page.waitForLoadState('load');\n \n // Additional wait to ensure all dynamic content is loaded\n await page.waitForTimeout(2000);\n \n // Take a full page screenshot\n await page.screenshot({ \n path: 'website-screenshot.png', \n fullPage: true \n });\n \n // Verify the page loaded successfully\n await expect(page).toHaveTitle(/Example/);\n \n // Check for key elements on the page\n await expect(page.locator('h1')).toBeVisible();\n \n console.log('Screenshot captured and page verified successfully');\n});",
10
+ },
11
+ "marketplace": {
12
+ "tags": ["screenshot", "visual-monitoring", "website-monitoring"],
13
+ "name": "Website Screenshot Monitor",
14
+ "description": "Captures full-page screenshots of websites and verifies page loading",
15
+ }
16
+ }
@@ -2,7 +2,7 @@
2
2
  Pingera CLI - A beautiful CLI tool for Pingera Platform
3
3
  """
4
4
 
5
- __version__ = "0.1.5"
5
+ __version__ = "0.1.6"
6
6
  __author__ = "Pingera Team"
7
7
  __description__ = "A beautiful Python CLI tool built with typer and rich, distributed via pip and based on Pingera SDK"
8
8
 
@@ -389,20 +389,126 @@ class ChecksCommand(BaseCommand):
389
389
  self.display_error(f"Failed to get check: {str(e)}")
390
390
  raise typer.Exit(1)
391
391
 
392
- def create_check(self, name: str, check_type: str, url: Optional[str] = None, host: Optional[str] = None, port: Optional[int] = None, interval: int = 300, timeout: int = 30, parameters: Optional[str] = None, pw_script_file: Optional[str] = None):
392
+ def _parse_check_file(self, file_path: str) -> dict:
393
+ """Parse check configuration from JSON or YAML file"""
394
+ import json
395
+ import os
396
+
397
+ if not os.path.exists(file_path):
398
+ self.display_error(f"Check file not found: {file_path}")
399
+ raise typer.Exit(1)
400
+
401
+ try:
402
+ with open(file_path, 'r', encoding='utf-8') as f:
403
+ content = f.read().strip()
404
+
405
+ if not content:
406
+ self.display_error(f"Check file is empty: {file_path}")
407
+ raise typer.Exit(1)
408
+
409
+ # Try to determine format from file extension or content
410
+ file_ext = os.path.splitext(file_path)[1].lower()
411
+
412
+ if file_ext in ['.yaml', '.yml']:
413
+ try:
414
+ import yaml
415
+ return yaml.safe_load(content)
416
+ except ImportError:
417
+ self.display_error("YAML support not available. Install with: pip install pyyaml")
418
+ raise typer.Exit(1)
419
+ except yaml.YAMLError as e:
420
+ self.display_error(f"Invalid YAML in file {file_path}: {str(e)}")
421
+ raise typer.Exit(1)
422
+ else:
423
+ # Default to JSON
424
+ try:
425
+ return json.loads(content)
426
+ except json.JSONDecodeError as e:
427
+ self.display_error(f"Invalid JSON in file {file_path}: {str(e)}")
428
+ raise typer.Exit(1)
429
+
430
+ except IOError as e:
431
+ self.display_error(f"Failed to read check file: {str(e)}")
432
+ raise typer.Exit(1)
433
+
434
+ def create_check(self, name: str, check_type: str, url: Optional[str] = None, host: Optional[str] = None, port: Optional[int] = None, interval: int = 300, timeout: int = 30, parameters: Optional[str] = None, pw_script_file: Optional[str] = None, from_file: Optional[str] = None):
393
435
  """Create a new monitoring check"""
394
436
  try:
395
437
  import json
396
438
  import os
397
439
  checks_api = self.get_client()
398
440
 
399
- # Build check data
400
- check_data = {
401
- "name": name,
402
- "type": check_type,
403
- "interval": interval,
404
- "timeout": timeout
405
- }
441
+ # If creating from file, parse file and merge with command line options
442
+ if from_file:
443
+ file_data = self._parse_check_file(from_file)
444
+
445
+ # Filter only SDK-recognized fields from file data
446
+ # This ignores marketplace-specific fields and other extensions
447
+ sdk_fields = ["name", "type", "url", "host", "port", "interval", "timeout", "parameters", "active"]
448
+ filtered_file_data = {k: v for k, v in file_data.items() if k in sdk_fields}
449
+
450
+ # Command line options take precedence over file data
451
+ check_data = {
452
+ "name": filtered_file_data.get("name", name),
453
+ "type": filtered_file_data.get("type", check_type),
454
+ "interval": filtered_file_data.get("interval", interval),
455
+ "timeout": filtered_file_data.get("timeout", timeout)
456
+ }
457
+
458
+ # Override with command line values if provided
459
+ if name != "": # Only override if name was explicitly provided
460
+ check_data["name"] = name
461
+ if check_type != "web": # Only override if type was explicitly provided
462
+ check_data["type"] = check_type
463
+ if interval != 300: # Only override if interval was explicitly provided
464
+ check_data["interval"] = interval
465
+ if timeout != 30: # Only override if timeout was explicitly provided
466
+ check_data["timeout"] = timeout
467
+
468
+ # Handle URL from file
469
+ if url is None and filtered_file_data.get("url"):
470
+ url = filtered_file_data["url"]
471
+ elif url is not None:
472
+ # Command line URL takes precedence
473
+ pass
474
+
475
+ # Handle host/port from file
476
+ if host is None and filtered_file_data.get("host"):
477
+ host = filtered_file_data["host"]
478
+ elif host is not None:
479
+ # Command line host takes precedence
480
+ pass
481
+
482
+ if port is None and filtered_file_data.get("port"):
483
+ port = filtered_file_data["port"]
484
+ elif port is not None:
485
+ # Command line port takes precedence
486
+ pass
487
+
488
+ # Handle parameters from file
489
+ file_parameters = filtered_file_data.get("parameters")
490
+ if file_parameters and parameters is None:
491
+ # Use file parameters if no command line parameters
492
+ parameters = json.dumps(file_parameters) if isinstance(file_parameters, dict) else file_parameters
493
+
494
+ # Handle pw_script_file from file (this is CLI-specific, not sent to SDK)
495
+ if pw_script_file is None and file_data.get("pw_script_file"):
496
+ pw_script_file = file_data["pw_script_file"]
497
+
498
+ # Show info about ignored fields if any
499
+ ignored_fields = [k for k in file_data.keys() if k not in sdk_fields and k != "pw_script_file"]
500
+ if ignored_fields:
501
+ self.display_info(f"Ignoring non-SDK fields from file: {', '.join(ignored_fields)}")
502
+
503
+ self.display_info(f"Creating check from file: {from_file}")
504
+ else:
505
+ # Build check data normally
506
+ check_data = {
507
+ "name": name,
508
+ "type": check_type,
509
+ "interval": interval,
510
+ "timeout": timeout
511
+ }
406
512
 
407
513
  # Add URL if provided (required for web, api, ssl checks)
408
514
  if url is not None:
@@ -1075,23 +1181,36 @@ def get_check(
1075
1181
 
1076
1182
  @app.command("create")
1077
1183
  def create_check(
1078
- name: str = typer.Option(..., "--name", "-n", help="Check name"),
1079
- check_type: str = typer.Option(..., "--type", "-t", help="Check type (web, api, tcp, ssl, synthetic, multistep)"),
1184
+ name: str = typer.Option("", "--name", "-n", help="Check name (required if not using --from-file)"),
1185
+ check_type: str = typer.Option("web", "--type", "-t", help="Check type (web, api, tcp, ssl, synthetic, multistep)"),
1080
1186
  url: Optional[str] = typer.Option(None, "--url", "-u", help="URL to monitor (required for web, api, ssl checks)"),
1081
1187
  host: Optional[str] = typer.Option(None, "--host", help="Hostname/IP for TCP/SSL checks (max 255 characters)"),
1082
1188
  port: Optional[int] = typer.Option(None, "--port", help="Port number for TCP checks (1-65535)"),
1083
1189
  interval: int = typer.Option(300, "--interval", "-i", help="Check interval in seconds"),
1084
1190
  timeout: int = typer.Option(30, "--timeout", help="Timeout in seconds"),
1085
1191
  parameters: Optional[str] = typer.Option(None, "--parameters", help="JSON string with check parameters (e.g., '{\"regions\": [\"US\", \"EU\"]}')"),
1086
- pw_script_file: Optional[str] = typer.Option(None, "--pw-script-file", help="Path to file containing Playwright script for synthetic/multistep checks")
1192
+ pw_script_file: Optional[str] = typer.Option(None, "--pw-script-file", help="Path to file containing Playwright script for synthetic/multistep checks"),
1193
+ from_file: Optional[str] = typer.Option(None, "--from-file", help="Path to JSON or YAML file containing check configuration")
1087
1194
  ):
1088
- """Create a new monitoring check. Parameters vary by check type:
1195
+ """Create a new monitoring check. Can be created from command line options or from a JSON/YAML file.
1196
+
1197
+ When using --from-file:
1198
+ - Command line options override file values
1199
+ - File should contain check configuration in JSON or YAML format
1200
+
1201
+ Parameters vary by check type:
1089
1202
  - web/api: --url required
1090
1203
  - tcp: --host required, --port optional
1091
1204
  - ssl: --url or --host required
1092
1205
  - synthetic/multistep: --pw-script-file or --parameters with pw_script required"""
1206
+
1207
+ # Validate that we have either from_file or required parameters
1208
+ if not from_file and not name:
1209
+ typer.echo("Error: --name is required when not using --from-file", err=True)
1210
+ raise typer.Exit(1)
1211
+
1093
1212
  checks_cmd = ChecksCommand(get_output_format())
1094
- checks_cmd.create_check(name, check_type, url, host, port, interval, timeout, parameters, pw_script_file)
1213
+ checks_cmd.create_check(name, check_type, url, host, port, interval, timeout, parameters, pw_script_file, from_file)
1095
1214
 
1096
1215
 
1097
1216
  @app.command("update")
@@ -21,6 +21,48 @@ class OnDemandChecksCommand(BaseCommand):
21
21
 
22
22
  def __init__(self, output_format: Optional[str] = None):
23
23
  super().__init__(output_format)
24
+
25
+ def _parse_check_file(self, file_path: str) -> dict:
26
+ """Parse check configuration from JSON or YAML file"""
27
+ import json
28
+ import os
29
+
30
+ if not os.path.exists(file_path):
31
+ self.display_error(f"Check file not found: {file_path}")
32
+ raise typer.Exit(1)
33
+
34
+ try:
35
+ with open(file_path, 'r', encoding='utf-8') as f:
36
+ content = f.read().strip()
37
+
38
+ if not content:
39
+ self.display_error(f"Check file is empty: {file_path}")
40
+ raise typer.Exit(1)
41
+
42
+ # Try to determine format from file extension or content
43
+ file_ext = os.path.splitext(file_path)[1].lower()
44
+
45
+ if file_ext in ['.yaml', '.yml']:
46
+ try:
47
+ import yaml
48
+ return yaml.safe_load(content)
49
+ except ImportError:
50
+ self.display_error("YAML support not available. Install with: pip install pyyaml")
51
+ raise typer.Exit(1)
52
+ except yaml.YAMLError as e:
53
+ self.display_error(f"Invalid YAML in file {file_path}: {str(e)}")
54
+ raise typer.Exit(1)
55
+ else:
56
+ # Default to JSON
57
+ try:
58
+ return json.loads(content)
59
+ except json.JSONDecodeError as e:
60
+ self.display_error(f"Invalid JSON in file {file_path}: {str(e)}")
61
+ raise typer.Exit(1)
62
+
63
+ except IOError as e:
64
+ self.display_error(f"Failed to read check file: {str(e)}")
65
+ raise typer.Exit(1)
24
66
 
25
67
  def get_client(self):
26
68
  """Get Pingera SDK client with authentication for on-demand checks"""
@@ -48,7 +90,7 @@ class OnDemandChecksCommand(BaseCommand):
48
90
  self.display_error(f"Failed to initialize client: {str(e)}")
49
91
  raise typer.Exit(1)
50
92
 
51
- def execute_custom_check(self, url: Optional[str] = None, check_type: str = "web", host: Optional[str] = None, port: Optional[int] = None, timeout: int = 30, name: str = "On-demand check", parameters: Optional[str] = None, pw_script_file: Optional[str] = None, wait_for_result: bool = False):
93
+ def execute_custom_check(self, url: Optional[str] = None, check_type: str = "web", host: Optional[str] = None, port: Optional[int] = None, timeout: int = 30, name: str = "On-demand check", parameters: Optional[str] = None, pw_script_file: Optional[str] = None, from_file: Optional[str] = None, wait_for_result: bool = False):
52
94
  """Execute custom on-demand check"""
53
95
  try:
54
96
  import json
@@ -58,12 +100,73 @@ class OnDemandChecksCommand(BaseCommand):
58
100
  # Import the SDK models
59
101
  from pingera.models import ExecuteCustomCheckRequest
60
102
 
61
- # Build check data
62
- check_data = {
63
- "name": name,
64
- "type": check_type,
65
- "timeout": timeout
66
- }
103
+ # If creating from file, parse file and merge with command line options
104
+ if from_file:
105
+ file_data = self._parse_check_file(from_file)
106
+
107
+ # Filter only SDK-recognized fields from file data
108
+ # This ignores marketplace-specific fields and other extensions
109
+ sdk_fields = ["name", "type", "url", "host", "port", "timeout", "parameters"]
110
+ filtered_file_data = {k: v for k, v in file_data.items() if k in sdk_fields}
111
+
112
+ # Command line options take precedence over file data
113
+ check_data = {
114
+ "name": filtered_file_data.get("name", name),
115
+ "type": filtered_file_data.get("type", check_type),
116
+ "timeout": filtered_file_data.get("timeout", timeout)
117
+ }
118
+
119
+ # Override with command line values if provided
120
+ if name != "On-demand check": # Only override if name was explicitly provided
121
+ check_data["name"] = name
122
+ if check_type != "web": # Only override if type was explicitly provided
123
+ check_data["type"] = check_type
124
+ if timeout != 30: # Only override if timeout was explicitly provided
125
+ check_data["timeout"] = timeout
126
+
127
+ # Handle URL from file
128
+ if url is None and filtered_file_data.get("url"):
129
+ url = filtered_file_data["url"]
130
+ elif url is not None:
131
+ # Command line URL takes precedence
132
+ pass
133
+
134
+ # Handle host/port from file
135
+ if host is None and filtered_file_data.get("host"):
136
+ host = filtered_file_data["host"]
137
+ elif host is not None:
138
+ # Command line host takes precedence
139
+ pass
140
+
141
+ if port is None and filtered_file_data.get("port"):
142
+ port = filtered_file_data["port"]
143
+ elif port is not None:
144
+ # Command line port takes precedence
145
+ pass
146
+
147
+ # Handle parameters from file
148
+ file_parameters = filtered_file_data.get("parameters")
149
+ if file_parameters and parameters is None:
150
+ # Use file parameters if no command line parameters
151
+ parameters = json.dumps(file_parameters) if isinstance(file_parameters, dict) else file_parameters
152
+
153
+ # Handle pw_script_file from file (this is CLI-specific, not sent to SDK)
154
+ if pw_script_file is None and file_data.get("pw_script_file"):
155
+ pw_script_file = file_data["pw_script_file"]
156
+
157
+ # Show info about ignored fields if any
158
+ ignored_fields = [k for k in file_data.keys() if k not in sdk_fields and k != "pw_script_file"]
159
+ if ignored_fields:
160
+ self.display_info(f"Ignoring non-SDK fields from file: {', '.join(ignored_fields)}")
161
+
162
+ self.display_info(f"Creating on-demand check from file: {from_file}")
163
+ else:
164
+ # Build check data normally
165
+ check_data = {
166
+ "name": name,
167
+ "type": check_type,
168
+ "timeout": timeout
169
+ }
67
170
 
68
171
  # Add URL if provided (required for web, api, ssl checks)
69
172
  if url is not None:
@@ -606,15 +709,22 @@ def run_custom_check(
606
709
  name: str = typer.Option("On-demand check", "--name", "-n", help="Check name"),
607
710
  parameters: Optional[str] = typer.Option(None, "--parameters", help="JSON string with check parameters (e.g., '{\"regions\": [\"US\", \"EU\"]}')"),
608
711
  pw_script_file: Optional[str] = typer.Option(None, "--pw-script-file", help="Path to file containing Playwright script for synthetic/multistep checks"),
712
+ from_file: Optional[str] = typer.Option(None, "--from-file", help="Path to JSON or YAML file containing check configuration"),
609
713
  wait_for_result: bool = typer.Option(False, "--wait-for-result", help="Wait for job completion and show result immediately (max 5 minutes)"),
610
714
  ):
611
- """Execute custom on-demand check. Parameters vary by check type:
715
+ """Execute custom on-demand check. Can be executed from command line options or from a JSON/YAML file.
716
+
717
+ When using --from-file:
718
+ - Command line options override file values
719
+ - File should contain check configuration in JSON or YAML format
720
+
721
+ Parameters vary by check type:
612
722
  - web/api: --url required
613
723
  - tcp: --host required, --port optional
614
724
  - ssl: --url or --host required
615
725
  - synthetic/multistep: --pw-script-file or --parameters with pw_script required"""
616
726
  on_demand_cmd = OnDemandChecksCommand(get_output_format())
617
- on_demand_cmd.execute_custom_check(url, check_type, host, port, timeout, name, parameters, pw_script_file, wait_for_result)
727
+ on_demand_cmd.execute_custom_check(url, check_type, host, port, timeout, name, parameters, pw_script_file, from_file, wait_for_result)
618
728
 
619
729
 
620
730
  @run_app.command("existing")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pingera-cli
3
- Version: 0.1.5
3
+ Version: 0.1.6
4
4
  Summary: A beautiful Python CLI tool built with typer and rich, distributed via pip and based on Pingera SDK
5
5
  Home-page: https://github.com/pingera/pingera-cli
6
6
  Author: PingeraCLI Team
@@ -8,6 +8,9 @@ replit.md
8
8
  run_tests.py
9
9
  setup.py
10
10
  docs/cli-commands.md
11
+ docs/example-check.json
12
+ docs/example-check.yaml
13
+ docs/example-synthetic-screenshot.json
11
14
  pingera_cli/__init__.py
12
15
  pingera_cli/__main__.py
13
16
  pingera_cli/main.py
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes