onex-cli 1.18.2__tar.gz → 1.19.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. {onex_cli-1.18.2 → onex_cli-1.19.0}/CHANGELOG.md +35 -0
  2. {onex_cli-1.18.2/onex_cli.egg-info → onex_cli-1.19.0}/PKG-INFO +1 -1
  3. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/__init__.py +1 -1
  4. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/__main__.py +3 -1
  5. onex_cli-1.19.0/onex/commands/executions.py +159 -0
  6. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/logs.py +27 -1
  7. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/mcp/server.py +184 -2
  8. {onex_cli-1.18.2 → onex_cli-1.19.0/onex_cli.egg-info}/PKG-INFO +1 -1
  9. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex_cli.egg-info/SOURCES.txt +1 -0
  10. {onex_cli-1.18.2 → onex_cli-1.19.0}/tests/test_mcp_logs_e2e.py +23 -2
  11. {onex_cli-1.18.2 → onex_cli-1.19.0}/LICENSE +0 -0
  12. {onex_cli-1.18.2 → onex_cli-1.19.0}/MANIFEST.in +0 -0
  13. {onex_cli-1.18.2 → onex_cli-1.19.0}/README.md +0 -0
  14. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/__init__.py +0 -0
  15. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/create.py +0 -0
  16. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/create_e2e.py +0 -0
  17. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/debug.py +0 -0
  18. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/deploy.py +0 -0
  19. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/dev.py +0 -0
  20. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/env.py +0 -0
  21. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/es.py +0 -0
  22. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/init.py +0 -0
  23. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/invoke.py +0 -0
  24. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/login.py +0 -0
  25. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/logout.py +0 -0
  26. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/mcp.py +0 -0
  27. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/platform.py +0 -0
  28. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/provision.py +0 -0
  29. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/reload.py +0 -0
  30. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/replay.py +0 -0
  31. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/s3.py +0 -0
  32. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/status.py +0 -0
  33. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/switch.py +0 -0
  34. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/test.py +0 -0
  35. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/trace.py +0 -0
  36. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/undeploy.py +0 -0
  37. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/validate.py +0 -0
  38. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/vpn.py +0 -0
  39. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/config.py +0 -0
  40. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/mcp/__init__.py +0 -0
  41. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/mcp/tools/__init__.py +0 -0
  42. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/mcp/tools/rocket_tools.py +0 -0
  43. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/runtime/__init__.py +0 -0
  44. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/runtime/local_runtime.py +0 -0
  45. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/schema/__init__.py +0 -0
  46. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/schema/service_descriptor.py +0 -0
  47. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/schema/validator.py +0 -0
  48. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/crud-service/README.md.j2 +0 -0
  49. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/crud-service/__init__.py.j2 +0 -0
  50. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/crud-service/service.yml.j2 +0 -0
  51. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/crud-service/src/__init__.py.j2 +0 -0
  52. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/crud-service/src/apis/__init__.py.j2 +0 -0
  53. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/crud-service/src/models/__init__.py.j2 +0 -0
  54. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/crud-service/src/models/schemas.py.j2 +0 -0
  55. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/crud-service/src/repositories/__init__.py.j2 +0 -0
  56. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/crud-service/src/services/__init__.py.j2 +0 -0
  57. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/e2e/data_service.yml.j2 +0 -0
  58. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/e2e/test_service_e2e.py.j2 +0 -0
  59. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/event-driven/README.md.j2 +0 -0
  60. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/event-driven/__init__.py.j2 +0 -0
  61. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/event-driven/service.yml.j2 +0 -0
  62. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/event-driven/src/__init__.py.j2 +0 -0
  63. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/event-driven/src/triggers/__init__.py.j2 +0 -0
  64. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/event-driven/src/triggers/events.py.j2 +0 -0
  65. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/event-driven/src/triggers/schedules.py.j2 +0 -0
  66. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/go-minimal/README.md.j2 +0 -0
  67. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/go-minimal/go.mod.j2 +0 -0
  68. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/go-minimal/main.go.j2 +0 -0
  69. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/go-minimal/service.yml.j2 +0 -0
  70. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/go-rest-api/README.md.j2 +0 -0
  71. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/go-rest-api/go.mod.j2 +0 -0
  72. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/go-rest-api/handlers/items.go.j2 +0 -0
  73. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/go-rest-api/main.go.j2 +0 -0
  74. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/go-rest-api/service.yml.j2 +0 -0
  75. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/minimal/README.md.j2 +0 -0
  76. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/minimal/__init__.py.j2 +0 -0
  77. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/minimal/service.yml.j2 +0 -0
  78. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/minimal/src/__init__.py.j2 +0 -0
  79. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/minimal/src/apis/__init__.py.j2 +0 -0
  80. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/minimal/src/apis/hello.py.j2 +0 -0
  81. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/rest-api/README.md.j2 +0 -0
  82. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/rest-api/__init__.py.j2 +0 -0
  83. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/rest-api/service.yml.j2 +0 -0
  84. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/rest-api/src/__init__.py.j2 +0 -0
  85. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/rest-api/src/apis/__init__.py.j2 +0 -0
  86. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/rest-api/src/apis/handlers.py.j2 +0 -0
  87. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/utils/__init__.py +0 -0
  88. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/utils/auth.py +0 -0
  89. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/utils/crypto.py +0 -0
  90. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/utils/email.py +0 -0
  91. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/utils/helpers.py +0 -0
  92. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/vpn/__init__.py +0 -0
  93. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/vpn/platform_detector.py +0 -0
  94. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/vpn/setup_vpn.py +0 -0
  95. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/vpn/wireguard_manager.py +0 -0
  96. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex_cli.egg-info/dependency_links.txt +0 -0
  97. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex_cli.egg-info/entry_points.txt +0 -0
  98. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex_cli.egg-info/requires.txt +0 -0
  99. {onex_cli-1.18.2 → onex_cli-1.19.0}/onex_cli.egg-info/top_level.txt +0 -0
  100. {onex_cli-1.18.2 → onex_cli-1.19.0}/setup.cfg +0 -0
  101. {onex_cli-1.18.2 → onex_cli-1.19.0}/setup.py +0 -0
  102. {onex_cli-1.18.2 → onex_cli-1.19.0}/tests/test_rocket_mcp.py +0 -0
@@ -5,6 +5,41 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.19.0] - 2026-04-14
9
+
10
+ ### Added
11
+ - **`onex logs --function`** - Filter logs by invoke handler name
12
+ (e.g. `--function invoice_automation_contract`). Matches the canonical
13
+ `function_name` field in shared-runtime and trigger-service structured
14
+ logs. Solves "my stream/schedule/event handler has no URL path so
15
+ `--path` doesn't help me find its logs."
16
+ - **`onex logs --invocation-source`** - Filter logs by WHY the handler
17
+ ran. One of `http`, `stream`, `event`, `schedule`, `s3`, `async_invoke`,
18
+ `manual`. Combines with `--function` to isolate e.g. only the
19
+ stream-triggered runs of a specific handler.
20
+ - **`onex executions`** - New command. Canonical "did my handler run,
21
+ when, and why" view, backed by MongoDB `invoke_executions` so it
22
+ survives Loki log retention. Accepts `--source`, `--status`,
23
+ `--trace-id`, `--last`, `--limit`, `--json`. Compact colored table
24
+ output with `invocation_detail` (e.g. `contract/update/<doc_id>` for
25
+ stream triggers, `schedule/<trigger_id>` for cron,
26
+ `<bucket>/<event>/<key>` for s3) under each row.
27
+ - **MCP tool `onex_executions`** - Same query surface as the CLI command,
28
+ exposed to Claude Code. Docstring includes rich examples so Claude
29
+ picks up the stream/schedule/manual patterns automatically.
30
+ - **MCP tool `onex_logs`** - Gained `function_name` and
31
+ `invocation_source` parameters to match the new CLI flags. Docstring
32
+ updated with examples covering invoke-handler tracing.
33
+
34
+ ### Notes
35
+ - Requires platform commits `52599d9` (unified invocation tracking) and
36
+ `11f10e6` (MONGODB_URI_PLATFORM env fix) to be deployed on the target
37
+ environment. Without those, `onex executions` will return empty rows
38
+ because `invoke_executions` isn't being populated with the new
39
+ top-level `invocation_source` field.
40
+ - Documented in `docs/service-developers/CLI_REFERENCE.md`,
41
+ `REQUEST_TRACING.md`, `LOGGING.md`, `TROUBLESHOOTING.md`, and `FAQ.md`.
42
+
8
43
  ## [1.13.0] - 2026-03-30
9
44
 
10
45
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: onex-cli
3
- Version: 1.18.2
3
+ Version: 1.19.0
4
4
  Summary: Official CLI for deploying and managing services on OneXEOS Platform (Windows, macOS, Linux)
5
5
  Home-page: https://github.com/onexeos/onex-cli
6
6
  Author: OneXEOS Platform Team
@@ -1,2 +1,2 @@
1
1
  # OneXEOS Services CLI
2
- __version__ = "1.18.2"
2
+ __version__ = "1.19.0"
@@ -17,6 +17,7 @@ Commands:
17
17
  provision - Manually provision resources
18
18
  invoke - Invoke handlers directly
19
19
  trace - Track requests through platform
20
+ executions - List invoke execution history (cross-source)
20
21
  replay - Re-send a previously captured request
21
22
  test - Run E2E tests against the platform
22
23
  create-e2e - Scaffold E2E tests for a service
@@ -30,7 +31,7 @@ Environments:
30
31
  """
31
32
 
32
33
  import click
33
- from onex.commands import deploy, undeploy, dev, debug, init, logs, status, provision, invoke, trace, validate, login, logout, env, platform, vpn, switch, mcp, create, replay, es, s3, test, create_e2e, reload
34
+ from onex.commands import deploy, undeploy, dev, debug, init, logs, status, provision, invoke, trace, validate, login, logout, env, platform, vpn, switch, mcp, create, replay, es, s3, test, create_e2e, reload, executions
34
35
 
35
36
 
36
37
  # Commands that don't require authentication
@@ -100,6 +101,7 @@ cli.add_command(reload.reload)
100
101
  cli.add_command(provision.provision)
101
102
  cli.add_command(invoke.invoke)
102
103
  cli.add_command(trace.trace)
104
+ cli.add_command(executions.executions)
103
105
  cli.add_command(replay.replay)
104
106
  cli.add_command(es.es)
105
107
  cli.add_command(s3.s3)
@@ -0,0 +1,159 @@
1
+ """
2
+ Executions command — query invoke execution history from invoke_executions.
3
+
4
+ Covers every invocation of a handler regardless of source: HTTP, stream
5
+ trigger, event, schedule, s3, async invoke, manual CLI. Backed by the
6
+ platform ExecutionTracker (MongoDB invoke_executions collection).
7
+ """
8
+
9
+ import json as _json
10
+
11
+ import click
12
+ import requests
13
+
14
+ from onex.config import get_config, get_access_token, get_active_environment
15
+ from onex.utils import print_error, print_info, print_warning
16
+ from onex.utils.auth import auto_refresh_token
17
+
18
+
19
+ VALID_SOURCES = ['http', 'stream', 'event', 'schedule', 's3', 'async_invoke', 'manual']
20
+ VALID_STATUSES = ['pending', 'running', 'succeeded', 'failed', 'retrying', 'dead_letter']
21
+
22
+
23
+ @click.command()
24
+ @click.argument('function_name', required=False)
25
+ @click.option('--source', 'invocation_source',
26
+ type=click.Choice(VALID_SOURCES),
27
+ help='Filter by invocation source (why the handler ran)')
28
+ @click.option('--status', type=click.Choice(VALID_STATUSES),
29
+ help='Filter by execution status')
30
+ @click.option('--trace-id', help='Filter by trace ID')
31
+ @click.option('--last', help='Time range (e.g., 1h, 30m, 24h, 7d)')
32
+ @click.option('--limit', default=20, help='Max rows to return (default: 20)')
33
+ @click.option('--offset', default=0, help='Pagination offset')
34
+ @click.option('--env', default=None, help='Environment (local/dev/staging/prod)')
35
+ @click.option('--platform-url', help='Override platform URL')
36
+ @click.option('--json', 'output_json', is_flag=True, help='Output as JSON')
37
+ def executions(function_name, invocation_source, status, trace_id, last,
38
+ limit, offset, env, platform_url, output_json):
39
+ """List invoke execution history for a handler.
40
+
41
+ Answers questions like "has my stream-triggered invoice handler actually
42
+ run in the last day, and did it succeed?" — data that Loki log retention
43
+ may have aged out but invoke_executions still has.
44
+
45
+ \b
46
+ Examples:
47
+ # Every run of a handler in the last 24h
48
+ onex executions automation:invoice_automation_contract --last 24h
49
+
50
+ # Only the stream-triggered runs
51
+ onex executions automation:invoice_automation_contract \\
52
+ --source stream --last 7d
53
+
54
+ # Failed manual (CLI) test runs
55
+ onex executions automation:invoice_automation_contract \\
56
+ --source manual --status failed --last 7d
57
+
58
+ # All scheduled runs across ALL handlers in the last hour
59
+ onex executions --source schedule --last 1h
60
+
61
+ # Everything tied to a specific trace_id (cross-source)
62
+ onex executions --trace-id abc-123-def
63
+ """
64
+ if env is None:
65
+ env = get_active_environment()
66
+
67
+ config = get_config()
68
+ platform_base_url = platform_url or config.get_platform_url(env)
69
+ url = f"{platform_base_url}/api/invoke/executions"
70
+
71
+ access_token = get_access_token(env)
72
+ headers = {}
73
+ if access_token:
74
+ headers['Authorization'] = f'Bearer {access_token}'
75
+
76
+ params = {'limit': limit, 'offset': offset}
77
+ if function_name:
78
+ params['function_name'] = function_name
79
+ if invocation_source:
80
+ params['invocation_source'] = invocation_source
81
+ if status:
82
+ params['status'] = status
83
+ if trace_id:
84
+ params['trace_id'] = trace_id
85
+ if last:
86
+ params['last'] = last
87
+
88
+ try:
89
+ response = requests.get(url, params=params, headers=headers, timeout=15)
90
+
91
+ if response.status_code == 401 and env != "local":
92
+ print_warning("Token expired, refreshing...")
93
+ if auto_refresh_token(env, silent=False):
94
+ access_token = get_access_token(env)
95
+ headers['Authorization'] = f'Bearer {access_token}'
96
+ response = requests.get(url, params=params, headers=headers, timeout=15)
97
+ else:
98
+ print_error("Authentication failed - could not refresh token")
99
+ print_info(f"Please login again: onex login --env {env}")
100
+ return
101
+
102
+ if response.status_code != 200:
103
+ print_error(f"Failed to fetch executions: HTTP {response.status_code}")
104
+ try:
105
+ print_error(response.json().get('detail', response.text[:200]))
106
+ except Exception:
107
+ print_error(response.text[:200])
108
+ return
109
+
110
+ data = response.json()
111
+ rows = data.get('executions', [])
112
+ total = data.get('total', 0)
113
+
114
+ if output_json:
115
+ click.echo(_json.dumps(data, indent=2, default=str))
116
+ return
117
+
118
+ if not rows:
119
+ click.echo(f"No executions found (total={total}).")
120
+ return
121
+
122
+ # Compact table
123
+ click.echo(f"\n{total} execution(s), showing {len(rows)}:\n")
124
+ header = f"{'STARTED':<20} {'STATUS':<10} {'SOURCE':<13} {'MS':>7} FUNCTION / DETAIL"
125
+ click.echo(header)
126
+ click.echo('-' * len(header))
127
+ for r in rows:
128
+ started = (r.get('started_at') or '')[:19].replace('T', ' ')
129
+ st = (r.get('status') or '?')[:10]
130
+ src = (r.get('invocation_source') or '-')[:13]
131
+ ms = r.get('execution_time_ms')
132
+ ms_str = f"{ms:7.0f}" if isinstance(ms, (int, float)) else " -"
133
+ fname = r.get('function_name') or '-'
134
+ detail = r.get('invocation_detail') or ''
135
+
136
+ # Color-code by status
137
+ if st == 'failed' or st == 'dead_letter':
138
+ line = click.style(f"{started:<20} {st:<10} {src:<13} {ms_str} {fname}", fg='red')
139
+ elif st == 'succeeded':
140
+ line = f"{started:<20} {click.style(st, fg='green'):<10} {src:<13} {ms_str} {fname}"
141
+ # click.style adds ANSI bytes so the format width is off; re-render:
142
+ line = f"{started:<20} {st:<10} {src:<13} {ms_str} {fname}"
143
+ else:
144
+ line = f"{started:<20} {st:<10} {src:<13} {ms_str} {fname}"
145
+ click.echo(line)
146
+ if detail:
147
+ click.echo(f"{'':<20} {'':<10} {'':<13} {'':>7} └─ {detail}")
148
+ err = r.get('error')
149
+ if err:
150
+ err_preview = err if len(err) < 120 else err[:117] + '...'
151
+ click.echo(click.style(f"{'':<20} {'':<10} {'':<13} {'':>7} ⚠ {err_preview}", fg='yellow'))
152
+
153
+ click.echo()
154
+
155
+ except requests.exceptions.ConnectionError:
156
+ print_error(f"Cannot connect to platform at {platform_base_url}")
157
+ print_info("Make sure platform is running")
158
+ except Exception as e:
159
+ print_error(f"Error fetching executions: {e}")
@@ -24,12 +24,18 @@ from onex.utils.auth import auto_refresh_token
24
24
  @click.option('--path', help='Filter by API path')
25
25
  @click.option('--method', help='Filter by HTTP method (GET, POST, etc.)')
26
26
  @click.option('--event', help='Filter by event type (service_ready, error, etc.)')
27
+ @click.option('--function', 'function_name',
28
+ help='Filter by invoke handler function name (e.g. invoice_automation_contract)')
29
+ @click.option('--invocation-source',
30
+ type=click.Choice(['http', 'stream', 'event', 'schedule', 's3', 'async_invoke', 'manual']),
31
+ help='Filter by why the handler ran (stream trigger, schedule, manual, etc.)')
27
32
  @click.option('--grep', help='Search message with regex pattern')
28
33
  @click.option('--last', help='Time range (e.g., 1h, 30m, 24h)')
29
34
  # Output options
30
35
  @click.option('--json', 'output_json', is_flag=True, help='Output as JSON')
31
36
  def logs(service_name, follow, lines, env, platform_url, level, trace_id, user_id,
32
- company_id, path, method, event, grep, last, output_json):
37
+ company_id, path, method, event, function_name, invocation_source,
38
+ grep, last, output_json):
33
39
  """View service logs from platform with powerful filtering.
34
40
 
35
41
  \b
@@ -51,6 +57,22 @@ def logs(service_name, follow, lines, env, platform_url, level, trace_id, user_i
51
57
 
52
58
  # Multiple filters combined
53
59
  onex logs product --level ERROR,WARNING --company-id comp123 --last 2h
60
+
61
+ \b
62
+ Invoke handler tracing (stream/schedule/event/s3 triggers):
63
+ # Every run of a specific invoke handler in the last 24h
64
+ onex logs automation --function invoice_automation_contract --last 24h
65
+
66
+ # Only the stream-triggered runs (e.g. contract updates)
67
+ onex logs automation --function invoice_automation_contract \\
68
+ --invocation-source stream --last 24h
69
+
70
+ # Only the manual (CLI/MCP) test runs that failed
71
+ onex logs automation --function invoice_automation_contract \\
72
+ --invocation-source manual --level ERROR --last 7d
73
+
74
+ # All scheduled (cron) invocations across a service
75
+ onex logs automation --invocation-source schedule --last 1h
54
76
  """
55
77
  from onex.config import get_active_environment
56
78
 
@@ -90,6 +112,10 @@ def logs(service_name, follow, lines, env, platform_url, level, trace_id, user_i
90
112
  params['method'] = method
91
113
  if event:
92
114
  params['event'] = event
115
+ if function_name:
116
+ params['function_name'] = function_name
117
+ if invocation_source:
118
+ params['invocation_source'] = invocation_source
93
119
  if grep:
94
120
  params['grep'] = grep
95
121
  if last:
@@ -678,6 +678,8 @@ async def onex_logs(
678
678
  path: str = None,
679
679
  method: str = None,
680
680
  event: str = None,
681
+ function_name: str = None,
682
+ invocation_source: str = None,
681
683
  grep: str = None,
682
684
  last: str = None
683
685
  ) -> str:
@@ -686,6 +688,10 @@ async def onex_logs(
686
688
  Fetches recent logs for a deployed service with advanced filtering options.
687
689
  Perfect for debugging specific issues, tracking user requests, and finding errors.
688
690
 
691
+ For invoke handlers triggered by non-HTTP sources (stream/schedule/event/s3),
692
+ use function_name + invocation_source to isolate those runs — they have no
693
+ URL path so path/method filters don't help.
694
+
689
695
  Args:
690
696
  service_name: Name of the service (e.g., 'email-service', 'product', 'auth')
691
697
  env: Environment (local/dev/staging/prod). Default: active environment
@@ -697,6 +703,11 @@ async def onex_logs(
697
703
  path: Filter by API path (e.g., "/auth/login")
698
704
  method: Filter by HTTP method (GET, POST, etc.)
699
705
  event: Filter by event type (service_ready, error, etc.)
706
+ function_name: Filter by invoke handler function name (e.g.
707
+ "invoice_automation_contract"). Matches the canonical
708
+ `function_name` field in shared-runtime / trigger logs.
709
+ invocation_source: Filter by WHY the handler ran. One of:
710
+ http, stream, event, schedule, s3, async_invoke, manual.
700
711
  grep: Search message with regex pattern
701
712
  last: Time range (e.g., "1h", "30m", "24h")
702
713
 
@@ -721,6 +732,20 @@ async def onex_logs(
721
732
 
722
733
  User: "Track user123's activity in the last 6 hours"
723
734
  → onex_logs(service_name="auth", user_id="user123", last="6h")
735
+
736
+ User: "Has my invoice_automation_contract handler run at all today?"
737
+ → onex_logs(service_name="automation",
738
+ function_name="invoice_automation_contract",
739
+ last="24h")
740
+
741
+ User: "Show me only the stream-triggered runs of invoice_automation_contract"
742
+ → onex_logs(service_name="automation",
743
+ function_name="invoice_automation_contract",
744
+ invocation_source="stream", last="24h")
745
+
746
+ User: "Did any scheduled jobs fail in the last hour?"
747
+ → onex_logs(service_name="automation",
748
+ invocation_source="schedule", level="ERROR", last="1h")
724
749
  """
725
750
  if env is None:
726
751
  env = get_active_environment()
@@ -740,6 +765,11 @@ async def onex_logs(
740
765
  if trace_id:
741
766
  # Query across platform services, filter by trace_id in log line
742
767
  label_filters.append('service=~"gateway|shared-runtime|trigger-scheduler|event-bridge|stream-trigger|s3-trigger|deployment-controller"')
768
+ elif function_name or invocation_source:
769
+ # Invoke-handler mode: widen to include the trigger services so we
770
+ # also see the "stream_trigger_executing" / "schedule_trigger_executing"
771
+ # log lines emitted upstream of shared-runtime.
772
+ label_filters.append('service=~"shared-runtime|trigger-scheduler|event-bridge|stream-trigger|s3-trigger"')
743
773
  else:
744
774
  # Use container label + path filter for service isolation
745
775
  label_filters.append('container="shared-runtime"')
@@ -750,8 +780,11 @@ async def onex_logs(
750
780
  pipeline = []
751
781
  if trace_id:
752
782
  pipeline.append(f'|~ "{trace_id}"')
753
- # Filter by service name via path prefix (e.g. /product/... or /manufacturing/...)
754
- if not trace_id:
783
+ # Filter by service name via path prefix (e.g. /product/... or /manufacturing/...).
784
+ # Skip this when the caller is asking about invoke handlers by
785
+ # function_name / invocation_source — those lines have no URL path
786
+ # and would be filtered out incorrectly.
787
+ if not trace_id and not function_name and not invocation_source:
755
788
  pipeline.append(f'|~ "/{service_name}/"')
756
789
  if level:
757
790
  level_pattern = '|'.join(l.strip().lower() for l in level.split(','))
@@ -766,6 +799,16 @@ async def onex_logs(
766
799
  pipeline.append(f'|~ "{company_id}"')
767
800
  if event:
768
801
  pipeline.append(f'|~ "{event}"')
802
+ if function_name:
803
+ # Match the canonical JSON field in structured logs. Back-compat
804
+ # `function` field is still accepted during transition window.
805
+ pipeline.append(
806
+ f'|~ "\\"function_name\\":\\"{function_name}\\"|\\"function\\":\\"{function_name}\\""'
807
+ )
808
+ if invocation_source:
809
+ pipeline.append(
810
+ f'|~ "\\"invocation_source\\":\\"{invocation_source}\\""'
811
+ )
769
812
  if grep:
770
813
  pipeline.append(f'|~ "{grep}"')
771
814
 
@@ -873,6 +916,145 @@ async def onex_logs(
873
916
  return f"Error fetching logs: {e}"
874
917
 
875
918
 
919
+ @mcp.tool()
920
+ async def onex_executions(
921
+ function_name: Optional[str] = None,
922
+ invocation_source: Optional[str] = None,
923
+ status: Optional[str] = None,
924
+ trace_id: Optional[str] = None,
925
+ last: Optional[str] = None,
926
+ limit: int = 20,
927
+ env: str = None,
928
+ ) -> str:
929
+ """List invoke handler execution history from invoke_executions.
930
+
931
+ This is the canonical "did my handler run, when, and why" view. It
932
+ covers EVERY invocation of a handler regardless of source: HTTP,
933
+ stream trigger, event, schedule, s3, async invoke, manual CLI. Backed
934
+ by MongoDB (invoke_executions collection) so it survives Loki log
935
+ retention.
936
+
937
+ Use this when log queries are timing out or when you need to answer
938
+ "has this handler ever run" over a long window. For the actual log
939
+ content of a specific run, follow up with onex_logs(trace_id=...) or
940
+ onex_trace(trace_id=...).
941
+
942
+ Args:
943
+ function_name: Filter by handler name (e.g.
944
+ "automation:invoice_automation_contract")
945
+ invocation_source: Filter by WHY the handler ran. One of:
946
+ http, stream, event, schedule, s3, async_invoke, manual.
947
+ status: Filter by execution status (pending, running, succeeded,
948
+ failed, retrying, dead_letter)
949
+ trace_id: Filter by trace ID
950
+ last: Time range (e.g., "1h", "24h", "7d")
951
+ limit: Max rows to return. Default: 20
952
+ env: Environment (local/dev/staging/prod). Default: active environment
953
+
954
+ Returns:
955
+ Compact table of executions with start time, status, invocation
956
+ source, duration, function name, and the invocation_detail
957
+ (the "why" — e.g. "contract/update/<doc_id>").
958
+
959
+ Examples:
960
+ User: "Has invoice_automation_contract actually run in the last day?"
961
+ → onex_executions(
962
+ function_name="automation:invoice_automation_contract",
963
+ last="24h"
964
+ )
965
+
966
+ User: "Show me only the stream-triggered runs of invoice_automation_contract in the last week"
967
+ → onex_executions(
968
+ function_name="automation:invoice_automation_contract",
969
+ invocation_source="stream", last="7d"
970
+ )
971
+
972
+ User: "Which manual test invokes failed for this handler?"
973
+ → onex_executions(
974
+ function_name="automation:invoice_automation_contract",
975
+ invocation_source="manual", status="failed", last="7d"
976
+ )
977
+
978
+ User: "Did any scheduled jobs fail in the last hour?"
979
+ → onex_executions(invocation_source="schedule", status="failed", last="1h")
980
+
981
+ User: "Everything tied to this trace"
982
+ → onex_executions(trace_id="abc-123-def")
983
+ """
984
+ if env is None:
985
+ env = get_active_environment()
986
+
987
+ platform_url = _get_platform_url(env)
988
+ url = f"{platform_url}/api/invoke/executions"
989
+
990
+ params = {'limit': limit}
991
+ if function_name:
992
+ params['function_name'] = function_name
993
+ if invocation_source:
994
+ params['invocation_source'] = invocation_source
995
+ if status:
996
+ params['status'] = status
997
+ if trace_id:
998
+ params['trace_id'] = trace_id
999
+ if last:
1000
+ params['last'] = last
1001
+
1002
+ try:
1003
+ async with httpx.AsyncClient(timeout=15) as client:
1004
+ resp = await client.get(url, params=params)
1005
+
1006
+ if resp.status_code != 200:
1007
+ return f"Executions query failed: HTTP {resp.status_code} - {resp.text[:200]}"
1008
+
1009
+ data = resp.json()
1010
+ rows = data.get('executions', [])
1011
+ total = data.get('total', 0)
1012
+
1013
+ if not rows:
1014
+ filters_desc = []
1015
+ if function_name:
1016
+ filters_desc.append(f"function={function_name}")
1017
+ if invocation_source:
1018
+ filters_desc.append(f"source={invocation_source}")
1019
+ if status:
1020
+ filters_desc.append(f"status={status}")
1021
+ if last:
1022
+ filters_desc.append(f"last={last}")
1023
+ filter_str = ", ".join(filters_desc) if filters_desc else "no filters"
1024
+ return f"No executions found ({filter_str}). Total matching: {total}"
1025
+
1026
+ out = [f"Found {total} execution(s), showing {len(rows)}:\n"]
1027
+ out.append(f"{'STARTED':<20} {'STATUS':<11} {'SOURCE':<13} {'MS':>8} FUNCTION")
1028
+ out.append("-" * 90)
1029
+ for r in rows:
1030
+ started = (r.get('started_at') or '')[:19].replace('T', ' ')
1031
+ st = (r.get('status') or '?')[:11]
1032
+ src = (r.get('invocation_source') or '-')[:13]
1033
+ ms = r.get('execution_time_ms')
1034
+ ms_str = f"{ms:8.0f}" if isinstance(ms, (int, float)) else " -"
1035
+ fname = r.get('function_name') or '-'
1036
+ out.append(f"{started:<20} {st:<11} {src:<13} {ms_str} {fname}")
1037
+ detail = r.get('invocation_detail')
1038
+ if detail:
1039
+ out.append(f" └─ {detail}")
1040
+ err = r.get('error')
1041
+ if err:
1042
+ err_preview = err if len(err) < 150 else err[:147] + '...'
1043
+ out.append(f" ⚠ {err_preview}")
1044
+ tid = r.get('trace_id')
1045
+ if tid:
1046
+ out.append(f" trace_id: {tid}")
1047
+
1048
+ return "\n".join(out)
1049
+
1050
+ except httpx.ConnectError:
1051
+ return f"Cannot connect to platform at {platform_url}"
1052
+ except httpx.TimeoutException:
1053
+ return "Executions query timed out"
1054
+ except Exception as e:
1055
+ return f"Error fetching executions: {e}"
1056
+
1057
+
876
1058
  @mcp.tool()
877
1059
  async def onex_status(
878
1060
  service_name: Optional[str] = None,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: onex-cli
3
- Version: 1.18.2
3
+ Version: 1.19.0
4
4
  Summary: Official CLI for deploying and managing services on OneXEOS Platform (Windows, macOS, Linux)
5
5
  Home-page: https://github.com/onexeos/onex-cli
6
6
  Author: OneXEOS Platform Team
@@ -14,6 +14,7 @@ onex/commands/deploy.py
14
14
  onex/commands/dev.py
15
15
  onex/commands/env.py
16
16
  onex/commands/es.py
17
+ onex/commands/executions.py
17
18
  onex/commands/init.py
18
19
  onex/commands/invoke.py
19
20
  onex/commands/login.py
@@ -15,7 +15,7 @@ import re
15
15
  import httpx
16
16
  import pytest
17
17
 
18
- from onex.mcp.server import onex_logs, _get_platform_url, _auth_headers
18
+ from onex.mcp.server import onex_logs, onex_executions, _get_platform_url, _auth_headers
19
19
 
20
20
 
21
21
  # ---------------------------------------------------------------------------
@@ -47,11 +47,32 @@ class TestSignature:
47
47
  sig = inspect.signature(onex_logs)
48
48
  expected = [
49
49
  "service_name", "env", "lines", "level", "trace_id",
50
- "user_id", "company_id", "path", "method", "event", "grep", "last",
50
+ "user_id", "company_id", "path", "method", "event",
51
+ "function_name", "invocation_source", "grep", "last",
51
52
  ]
52
53
  for param in expected:
53
54
  assert param in sig.parameters, f"Missing parameter: {param}"
54
55
 
56
+ def test_executions_signature(self):
57
+ """onex_executions exposes the expected filter parameters."""
58
+ sig = inspect.signature(onex_executions)
59
+ expected = [
60
+ "function_name", "invocation_source", "status",
61
+ "trace_id", "last", "limit", "env",
62
+ ]
63
+ for param in expected:
64
+ assert param in sig.parameters, f"Missing parameter: {param}"
65
+
66
+ def test_executions_docstring_has_examples(self):
67
+ """The tool description Claude sees must include usage examples."""
68
+ doc = onex_executions.__doc__ or ""
69
+ assert "invoice_automation_contract" in doc, (
70
+ "docstring should include the canonical stream-trigger example "
71
+ "so Claude picks up the pattern"
72
+ )
73
+ assert "invocation_source=\"stream\"" in doc
74
+ assert "invocation_source=\"schedule\"" in doc
75
+
55
76
 
56
77
  # ---------------------------------------------------------------------------
57
78
  # 2. Auth headers — sent on every request
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