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.
- {onex_cli-1.18.2 → onex_cli-1.19.0}/CHANGELOG.md +35 -0
- {onex_cli-1.18.2/onex_cli.egg-info → onex_cli-1.19.0}/PKG-INFO +1 -1
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/__init__.py +1 -1
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/__main__.py +3 -1
- onex_cli-1.19.0/onex/commands/executions.py +159 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/logs.py +27 -1
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/mcp/server.py +184 -2
- {onex_cli-1.18.2 → onex_cli-1.19.0/onex_cli.egg-info}/PKG-INFO +1 -1
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex_cli.egg-info/SOURCES.txt +1 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/tests/test_mcp_logs_e2e.py +23 -2
- {onex_cli-1.18.2 → onex_cli-1.19.0}/LICENSE +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/MANIFEST.in +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/README.md +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/__init__.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/create.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/create_e2e.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/debug.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/deploy.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/dev.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/env.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/es.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/init.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/invoke.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/login.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/logout.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/mcp.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/platform.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/provision.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/reload.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/replay.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/s3.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/status.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/switch.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/test.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/trace.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/undeploy.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/validate.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/commands/vpn.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/config.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/mcp/__init__.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/mcp/tools/__init__.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/mcp/tools/rocket_tools.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/runtime/__init__.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/runtime/local_runtime.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/schema/__init__.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/schema/service_descriptor.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/schema/validator.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/crud-service/README.md.j2 +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/crud-service/__init__.py.j2 +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/crud-service/service.yml.j2 +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/crud-service/src/__init__.py.j2 +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/crud-service/src/apis/__init__.py.j2 +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/crud-service/src/models/__init__.py.j2 +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/crud-service/src/models/schemas.py.j2 +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/crud-service/src/repositories/__init__.py.j2 +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/crud-service/src/services/__init__.py.j2 +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/e2e/data_service.yml.j2 +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/e2e/test_service_e2e.py.j2 +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/event-driven/README.md.j2 +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/event-driven/__init__.py.j2 +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/event-driven/service.yml.j2 +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/event-driven/src/__init__.py.j2 +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/event-driven/src/triggers/__init__.py.j2 +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/event-driven/src/triggers/events.py.j2 +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/event-driven/src/triggers/schedules.py.j2 +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/go-minimal/README.md.j2 +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/go-minimal/go.mod.j2 +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/go-minimal/main.go.j2 +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/go-minimal/service.yml.j2 +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/go-rest-api/README.md.j2 +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/go-rest-api/go.mod.j2 +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/go-rest-api/handlers/items.go.j2 +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/go-rest-api/main.go.j2 +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/go-rest-api/service.yml.j2 +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/minimal/README.md.j2 +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/minimal/__init__.py.j2 +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/minimal/service.yml.j2 +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/minimal/src/__init__.py.j2 +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/minimal/src/apis/__init__.py.j2 +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/minimal/src/apis/hello.py.j2 +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/rest-api/README.md.j2 +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/rest-api/__init__.py.j2 +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/rest-api/service.yml.j2 +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/rest-api/src/__init__.py.j2 +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/rest-api/src/apis/__init__.py.j2 +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/rest-api/src/apis/handlers.py.j2 +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/utils/__init__.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/utils/auth.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/utils/crypto.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/utils/email.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/utils/helpers.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/vpn/__init__.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/vpn/platform_detector.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/vpn/setup_vpn.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex/vpn/wireguard_manager.py +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex_cli.egg-info/dependency_links.txt +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex_cli.egg-info/entry_points.txt +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex_cli.egg-info/requires.txt +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/onex_cli.egg-info/top_level.txt +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/setup.cfg +0 -0
- {onex_cli-1.18.2 → onex_cli-1.19.0}/setup.py +0 -0
- {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,2 +1,2 @@
|
|
|
1
1
|
# OneXEOS Services CLI
|
|
2
|
-
__version__ = "1.
|
|
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,
|
|
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
|
-
|
|
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,
|
|
@@ -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",
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/crud-service/src/repositories/__init__.py.j2
RENAMED
|
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
|
{onex_cli-1.18.2 → onex_cli-1.19.0}/onex/templates/event-driven/src/triggers/schedules.py.j2
RENAMED
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|