lighthouse-cli 0.16.0__tar.gz → 0.17.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.
- {lighthouse_cli-0.16.0/lighthouse_cli.egg-info → lighthouse_cli-0.17.0}/PKG-INFO +1 -3
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/README.md +15 -17
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/__init__.py +1 -1
- lighthouse_cli-0.17.0/lighthouse/commands/offline.py +176 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/commands/runs.py +44 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/http.py +23 -3
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/locales/ar.json +23 -1
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/locales/de.json +23 -1
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/locales/en.json +23 -1
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/locales/es.json +23 -1
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/locales/fr.json +23 -1
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/locales/ga.json +23 -1
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/locales/it.json +23 -1
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/locales/ja.json +23 -1
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/locales/nl.json +23 -1
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/locales/pt.json +23 -1
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/locales/zh-Hans.json +23 -1
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/locales/zh-Hant.json +23 -1
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/main.py +2 -1
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0/lighthouse_cli.egg-info}/PKG-INFO +1 -3
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse_cli.egg-info/SOURCES.txt +2 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse_cli.egg-info/requires.txt +0 -3
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/pyproject.toml +2 -4
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/tests/test_commands.py +52 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/tests/test_golden.py +4 -0
- lighthouse_cli-0.17.0/tests/test_offline.py +125 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/LICENSE +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/NOTICE +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/__main__.py +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/commands/__init__.py +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/commands/agents.py +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/commands/builds.py +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/commands/collectors.py +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/commands/connect.py +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/commands/connections.py +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/commands/evals.py +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/commands/kb.py +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/commands/language.py +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/commands/limits.py +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/commands/login.py +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/commands/models.py +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/commands/spend.py +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/commands/stack.py +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/commands/terms.py +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/commands/whoami.py +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/config.py +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/credentials.py +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/device.py +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/exits.py +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/flags.py +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/i18n.py +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/out.py +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/py.typed +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse/sealing.py +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse_cli.egg-info/dependency_links.txt +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse_cli.egg-info/entry_points.txt +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/lighthouse_cli.egg-info/top_level.txt +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/setup.cfg +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/tests/test_ci_credential.py +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/tests/test_collectors.py +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/tests/test_connect_erp_hr.py +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/tests/test_connect_finav.py +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/tests/test_connect_library.py +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/tests/test_dev_surface.py +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/tests/test_fixtures.py +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/tests/test_i18n.py +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/tests/test_installers.py +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/tests/test_plumbing.py +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/tests/test_sealing.py +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/tests/test_stack.py +0 -0
- {lighthouse_cli-0.16.0 → lighthouse_cli-0.17.0}/tests/test_version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lighthouse-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.17.0
|
|
4
4
|
Summary: The LightHouse command line: sign in, connect your estate, deploy agents, watch runs
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -18,6 +18,4 @@ Requires-Dist: ruff==0.16.10; extra == "dev"
|
|
|
18
18
|
Requires-Dist: mypy==2.4.0; extra == "dev"
|
|
19
19
|
Requires-Dist: bandit[toml]==1.9.4; extra == "dev"
|
|
20
20
|
Requires-Dist: jsonschema==4.25.1; extra == "dev"
|
|
21
|
-
Provides-Extra: build
|
|
22
|
-
Requires-Dist: pyinstaller==6.22.3; extra == "build"
|
|
23
21
|
Dynamic: license-file
|
|
@@ -23,25 +23,20 @@ evidence:
|
|
|
23
23
|
| **The control plane is Python.** The service, its OpenAPI generator and its MCP server are all `aal_control`. The CLI's integration tests therefore run **in process against the very application that is deployed** (`service/tests/test_cli_integration.py`) — the real routes, the real OAuth, the real row-level security, no stack to boot and no stub to drift. No other choice gets us that. | decisive |
|
|
24
24
|
| **No new toolchain in any pipeline.** Every job already runs `python:3.13-slim`; a Go CLI would add a compiler, a cross-build matrix and a second dependency-audit path to all 21 repositories. | heavy |
|
|
25
25
|
| **`keyring` gives the OS keychain on all three platforms** — macOS Keychain, Secret Service, Windows Credential Locker — with no native code to sign or notarise. | heavy |
|
|
26
|
-
| Against:
|
|
26
|
+
| Against: a runtime-free binary. | the cost |
|
|
27
27
|
|
|
28
|
-
That last line is the real cost, and it is paid rather than hidden:
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
If the npm and curl routes ever become the main way people install this, the
|
|
35
|
-
decision is worth revisiting — the commands, their output and their tests are
|
|
36
|
-
the valuable part and none of it is language-specific.
|
|
28
|
+
That last line is the real cost, and it is paid rather than hidden: the CLI
|
|
29
|
+
installs from source as a package, so it needs Python (`pipx` brings its own
|
|
30
|
+
isolated environment; Homebrew brings its own virtualenv). There is no
|
|
31
|
+
stand-alone binary: the stand-alone installer was retired on 11 November 2026
|
|
32
|
+
and the CLI ships as packages only.
|
|
37
33
|
|
|
38
34
|
## Install
|
|
39
35
|
|
|
40
|
-
| Route | Command |
|
|
36
|
+
| Route | Command | Platforms |
|
|
41
37
|
|---|---|---|
|
|
42
|
-
|
|
|
43
|
-
|
|
|
44
|
-
| curl | `curl -fsSL https://get.atlanticagenticlabs.com/lighthouse \| sh` | the single-file build (`installers/install.sh`) |
|
|
38
|
+
| pipx or pip | `pipx install lighthouse-cli` (or `pip install lighthouse-cli`) | Linux, macOS, Windows |
|
|
39
|
+
| Homebrew | `brew install atlantic-agentic-labs/tap/lighthouse` | macOS, Linux (`installers/lighthouse.rb`) |
|
|
45
40
|
|
|
46
41
|
**Nothing is published yet.** From a clone:
|
|
47
42
|
|
|
@@ -49,9 +44,9 @@ the valuable part and none of it is language-specific.
|
|
|
49
44
|
cd cli && pip install -e ".[dev]"
|
|
50
45
|
```
|
|
51
46
|
|
|
52
|
-
What has to exist before
|
|
53
|
-
|
|
54
|
-
|
|
47
|
+
What has to exist before either route works is in `installers/README.md`: a
|
|
48
|
+
PyPI project with a trusted publisher and a Homebrew tap. No registry
|
|
49
|
+
credential is stored for PyPI.
|
|
55
50
|
|
|
56
51
|
## Signing in
|
|
57
52
|
|
|
@@ -158,6 +153,9 @@ a terminal, and when `CI`, `NO_COLOR` or `TERM=dumb` is set.
|
|
|
158
153
|
| `evals list`, `evals list --fail-below N` | evaluation results, as a release gate on one named metric (`--metric`, default `f1`); fails closed when a row has no such number |
|
|
159
154
|
| `evals run <pack:card> --cap N [--set S] [--wait] [--fail-below N]` | run the agent's scheduled evaluation once under a spend cap (the `deploy` scope, or a person); with `--wait`, gate on the new results |
|
|
160
155
|
| `runs`, `runs show`, `runs tail` | what ran, and what is running now |
|
|
156
|
+
| `runs export <run> [--format json\|pdf] [-o file]` | one run's content-free export, as JSON or a branded PDF (contract 1.32); audited |
|
|
157
|
+
| `telemetry import <bundle>`, `telemetry status --deployment D` | upload a telemetry bundle from a deployment with no network path, and see the batches held and missing (contract 1.33; client_admin or the `offline` scope) |
|
|
158
|
+
| `commands export --deployment D --out <file>` | the waiting commands as a signed command file for the deployment's `apply-file` (contract 1.33); audited |
|
|
161
159
|
| `spend` | spend by day, model, pack, agent or connection; the forecast; the estimated savings |
|
|
162
160
|
| `limits` | the limits in force, and whether the engines applied them |
|
|
163
161
|
| `terms` | the commercial terms in force |
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# Copyright 2026 Atlantic Agentic Labs. All Rights Reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""`lighthouse telemetry` and `lighthouse commands`: a deployment with no
|
|
4
|
+
network path (contract 1.33).
|
|
5
|
+
|
|
6
|
+
lighthouse telemetry import <bundle.tar.gz> [--deployment D]
|
|
7
|
+
lighthouse telemetry status --deployment D
|
|
8
|
+
lighthouse commands export --deployment D --out commands.json
|
|
9
|
+
|
|
10
|
+
A deployment that cannot reach LightHouse writes its telemetry into signed
|
|
11
|
+
bundle files (`python -m aal_engine.telemetry export` on the deployment's
|
|
12
|
+
side). `telemetry import` uploads one, as it was carried: LightHouse checks
|
|
13
|
+
its signatures, its manifest and its sequence, and stores what is new
|
|
14
|
+
through the same intake as live telemetry. The deployment is read from the
|
|
15
|
+
bundle's own manifest unless `--deployment` says otherwise; the service
|
|
16
|
+
refuses a bundle that names another. `telemetry status` shows the last batch
|
|
17
|
+
number held for each epoch, which is the `--since` for the next export, and
|
|
18
|
+
the numbers missing.
|
|
19
|
+
|
|
20
|
+
`commands export` writes every command waiting for the deployment into a
|
|
21
|
+
file of signed frames, for `python -m aal_engine.handler apply-file` on the
|
|
22
|
+
other side. The commands are marked delivered when the file is written, so
|
|
23
|
+
the same command is never exported twice.
|
|
24
|
+
|
|
25
|
+
Each needs a client administrator: at the keyboard, or through a token with
|
|
26
|
+
the `offline` scope. Each is audited by the service.
|
|
27
|
+
"""
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import argparse
|
|
31
|
+
import io
|
|
32
|
+
import json
|
|
33
|
+
import os
|
|
34
|
+
import tarfile
|
|
35
|
+
from pathlib import Path
|
|
36
|
+
from typing import Any
|
|
37
|
+
|
|
38
|
+
from lighthouse.exits import FAILED, USAGE, CliError
|
|
39
|
+
from lighthouse.flags import inherited
|
|
40
|
+
from lighthouse.main import Command, Context
|
|
41
|
+
from lighthouse.out import Out
|
|
42
|
+
|
|
43
|
+
# The largest upload the service takes (telemetry_bundles.UPLOAD_MAX).
|
|
44
|
+
UPLOAD_MAX = 48 * 1024 * 1024
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _telemetry_configure(ap: argparse.ArgumentParser) -> None:
|
|
48
|
+
sub = ap.add_subparsers(dest="telemetry_command", metavar="<subcommand>")
|
|
49
|
+
imp = sub.add_parser("import", parents=[inherited()],
|
|
50
|
+
help="upload a telemetry bundle from an offline deployment")
|
|
51
|
+
imp.add_argument("file", help="the bundle (.tar.gz), as the deployment wrote it")
|
|
52
|
+
imp.add_argument("--deployment", help="the deployment (default: the one the bundle names)")
|
|
53
|
+
status = sub.add_parser("status", parents=[inherited()],
|
|
54
|
+
help="the batches held for a deployment, and any missing")
|
|
55
|
+
status.add_argument("--deployment", required=True, help="the deployment")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _commands_configure(ap: argparse.ArgumentParser) -> None:
|
|
59
|
+
sub = ap.add_subparsers(dest="commands_command", metavar="<subcommand>")
|
|
60
|
+
ex = sub.add_parser("export", parents=[inherited()],
|
|
61
|
+
help="write the waiting commands into a signed command file")
|
|
62
|
+
ex.add_argument("--deployment", required=True, help="the deployment")
|
|
63
|
+
ex.add_argument("--out", required=True, help="the file to write (- for standard output)")
|
|
64
|
+
ex.add_argument("--force", action="store_true", help="overwrite the file if it exists")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def bundle_deployment(data: bytes) -> str | None:
|
|
68
|
+
"""The deployment a bundle's manifest names (its part after the tenant),
|
|
69
|
+
read locally so the right route is called. None if it cannot be read: the
|
|
70
|
+
service, which verifies the whole bundle, has the last word either way."""
|
|
71
|
+
try:
|
|
72
|
+
with tarfile.open(fileobj=io.BytesIO(data), mode="r:gz") as tar:
|
|
73
|
+
member = tar.getmember("manifest.json")
|
|
74
|
+
if not member.isfile() or member.size > 4 * 1024 * 1024:
|
|
75
|
+
return None
|
|
76
|
+
fh = tar.extractfile(member)
|
|
77
|
+
manifest = json.loads(fh.read() if fh is not None else b"")
|
|
78
|
+
except (tarfile.TarError, OSError, EOFError, KeyError, ValueError):
|
|
79
|
+
return None
|
|
80
|
+
named = manifest.get("deployment") if isinstance(manifest, dict) else None
|
|
81
|
+
if not isinstance(named, str) or "/" not in named:
|
|
82
|
+
return None
|
|
83
|
+
return named.split("/", 1)[1] or None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def telemetry(ctx: Context) -> dict[str, Any]:
|
|
87
|
+
which = getattr(ctx.args, "telemetry_command", None)
|
|
88
|
+
if which == "status":
|
|
89
|
+
body = ctx.client().get(f"/api/v1/deployments/{ctx.args.deployment}/telemetry-bundles")
|
|
90
|
+
return {"ok": True, **body}
|
|
91
|
+
if which != "import":
|
|
92
|
+
raise CliError("say what to do: `lighthouse telemetry import <file>` or "
|
|
93
|
+
"`lighthouse telemetry status --deployment <name>`", code=USAGE,
|
|
94
|
+
error="usage")
|
|
95
|
+
path = Path(ctx.args.file).expanduser()
|
|
96
|
+
try:
|
|
97
|
+
size = path.stat().st_size
|
|
98
|
+
if size > UPLOAD_MAX:
|
|
99
|
+
raise CliError(f"{path} is over the 48 MB a bundle may be", error="too_large",
|
|
100
|
+
hint="export again with a smaller --max-bytes")
|
|
101
|
+
data = path.read_bytes()
|
|
102
|
+
except OSError as e:
|
|
103
|
+
raise CliError(f"{path} could not be read: {e.strerror or e}", code=USAGE,
|
|
104
|
+
error="unreadable") from None
|
|
105
|
+
deployment = ctx.args.deployment or bundle_deployment(data)
|
|
106
|
+
if not deployment:
|
|
107
|
+
raise CliError(f"{path} names no deployment that can be read", code=USAGE,
|
|
108
|
+
error="usage", hint="pass --deployment <name>")
|
|
109
|
+
body = ctx.client().post_bytes(f"/api/v1/deployments/{deployment}/telemetry-bundles",
|
|
110
|
+
data, "application/gzip")
|
|
111
|
+
return {"ok": True, "file": str(path), **body}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def commands_export(ctx: Context) -> dict[str, Any]:
|
|
115
|
+
if getattr(ctx.args, "commands_command", None) != "export":
|
|
116
|
+
raise CliError("say what to do: `lighthouse commands export --deployment <name> "
|
|
117
|
+
"--out <file>`", code=USAGE, error="usage")
|
|
118
|
+
out = Path(ctx.args.out).expanduser()
|
|
119
|
+
if ctx.args.out != "-" and out.exists() and not ctx.args.force:
|
|
120
|
+
raise CliError(f"{out} already exists", code=USAGE, error="exists",
|
|
121
|
+
hint="pass --force to overwrite it, or choose another --out")
|
|
122
|
+
doc = ctx.client().post(f"/api/v1/deployments/{ctx.args.deployment}/command-file")
|
|
123
|
+
if not isinstance(doc, dict) or not isinstance(doc.get("commands"), list):
|
|
124
|
+
raise CliError("the service did not answer with a command file", code=FAILED,
|
|
125
|
+
error="bad_response")
|
|
126
|
+
if ctx.args.out == "-":
|
|
127
|
+
ctx.out.streamed = True
|
|
128
|
+
ctx.out.stdout.write(json.dumps(doc, indent=2, sort_keys=True) + "\n")
|
|
129
|
+
return {"ok": True, "streamed": True}
|
|
130
|
+
fd = os.open(out, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
|
|
131
|
+
with os.fdopen(fd, "w", encoding="utf-8") as fh:
|
|
132
|
+
fh.write(json.dumps(doc, indent=2, sort_keys=True) + "\n")
|
|
133
|
+
return {"ok": True, "deployment": doc.get("deployment"), "path": str(out),
|
|
134
|
+
"commands": len(doc["commands"]),
|
|
135
|
+
"ids": [c.get("id") for c in doc["commands"] if isinstance(c, dict)]}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _telemetry_show(out: Out, body: dict[str, Any]) -> None:
|
|
139
|
+
if "imports" in body:
|
|
140
|
+
seq = body.get("sequence") or []
|
|
141
|
+
if not seq:
|
|
142
|
+
out.say("no bundle imported yet: export with --since 0")
|
|
143
|
+
return
|
|
144
|
+
out.table(["epoch", "held through", "highest", "missing"],
|
|
145
|
+
[[s["epoch"][:8], s["held_through"], s["highest_seq"], s["missing"]]
|
|
146
|
+
for s in seq])
|
|
147
|
+
return
|
|
148
|
+
b, r, s = body["batches"], body["records"], body["sequence"]
|
|
149
|
+
out.done(f"imported batches {body['first_seq']} to {body['last_seq']} for "
|
|
150
|
+
f"{body['deployment']}: {b['imported']} new, {b['duplicate']} already held")
|
|
151
|
+
out.pairs([("records stored", r["stored"]), ("records already held", r["duplicate"]),
|
|
152
|
+
("spans", body["spans"]), ("held through", s["held_through"])])
|
|
153
|
+
for item in body.get("refused") or []:
|
|
154
|
+
out.warn(f"batch {item['seq']} not imported: {item['error']}")
|
|
155
|
+
if s.get("gaps"):
|
|
156
|
+
out.warn("missing batches: " + ", ".join(
|
|
157
|
+
str(a) if a == z else f"{a}-{z}" for a, z in s["gaps"]))
|
|
158
|
+
out.note(f"next export: --since {s['held_through']}")
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _commands_show(out: Out, body: dict[str, Any]) -> None:
|
|
162
|
+
if body.get("streamed"):
|
|
163
|
+
return
|
|
164
|
+
n = body["commands"]
|
|
165
|
+
out.done(f"{n} command{'' if n == 1 else 's'} written to {body['path']}")
|
|
166
|
+
if n:
|
|
167
|
+
out.note("apply it on the deployment: python -m aal_engine.handler apply-file "
|
|
168
|
+
f"{Path(body['path']).name}")
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
COMMANDS = (
|
|
172
|
+
Command("telemetry", "telemetry bundles from a deployment with no network path",
|
|
173
|
+
_telemetry_configure, telemetry, _telemetry_show),
|
|
174
|
+
Command("commands", "command files for a deployment with no network path",
|
|
175
|
+
_commands_configure, commands_export, _commands_show),
|
|
176
|
+
)
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
lighthouse runs tail # live, one line per change
|
|
7
7
|
lighthouse runs tail --once --json # one snapshot, for a pipeline
|
|
8
8
|
lighthouse runs show <run_id_hash>
|
|
9
|
+
lighthouse runs export <run_id_hash> [--format json|pdf] [-o file]
|
|
9
10
|
|
|
10
11
|
`tail` follows `GET /api/v1/runs/active` (contract 1.12): the live runs, each
|
|
11
12
|
with its current step, its tokens so far and its estimated cost. It polls at
|
|
@@ -22,12 +23,21 @@ step wants. Both are covered by the golden test.
|
|
|
22
23
|
`--fail-on-stall` makes it exit 1 if a run has not been seen for
|
|
23
24
|
`stall_after_s`, which is the useful thing to put in a smoke job after a
|
|
24
25
|
deploy.
|
|
26
|
+
|
|
27
|
+
`export` (contract 1.32) writes one run's content-free export, the run map as
|
|
28
|
+
JSON or as a PDF carrying the organisation's branding, to `-o` (by default
|
|
29
|
+
`run-<first 12 of the hash>.<format>` here; `-o -` is standard output). The
|
|
30
|
+
file is written exactly as the service sent it, and the service records the
|
|
31
|
+
export in the organisation's audit log. A machine key may export, so a
|
|
32
|
+
pipeline can attach a run's record to its own report.
|
|
25
33
|
"""
|
|
26
34
|
from __future__ import annotations
|
|
27
35
|
|
|
28
36
|
import argparse
|
|
29
37
|
import json
|
|
38
|
+
import re
|
|
30
39
|
import time
|
|
40
|
+
from pathlib import Path
|
|
31
41
|
from typing import Any
|
|
32
42
|
|
|
33
43
|
from lighthouse.exits import FAILED, CliError
|
|
@@ -46,6 +56,13 @@ def _configure(ap: argparse.ArgumentParser) -> None:
|
|
|
46
56
|
show.add_argument("run", help="its run_id_hash")
|
|
47
57
|
show.add_argument("--map", action="store_true", dest="as_map",
|
|
48
58
|
help="the run map: every step, model call and retrieval")
|
|
59
|
+
export = sub.add_parser("export", parents=[inherited()],
|
|
60
|
+
help="one run's content-free export, as JSON or PDF")
|
|
61
|
+
export.add_argument("run", help="its run_id_hash")
|
|
62
|
+
export.add_argument("--format", choices=("json", "pdf"), default="json", dest="export_format",
|
|
63
|
+
help="json (the default) or pdf")
|
|
64
|
+
export.add_argument("-o", "--output", dest="output", default=None,
|
|
65
|
+
help="the file to write (default run-<hash>.<format>; - for standard output)")
|
|
49
66
|
tail = sub.add_parser("tail", parents=[inherited()], help="follow the live runs")
|
|
50
67
|
tail.add_argument("--interval", type=float, default=5.0, help="seconds between polls")
|
|
51
68
|
tail.add_argument("--once", action="store_true", help="one snapshot, then stop")
|
|
@@ -119,10 +136,37 @@ def tail(ctx: Context) -> dict[str, Any]:
|
|
|
119
136
|
"live": last.get("live"), "runs": last.get("runs") or [], "stalled": stalled}
|
|
120
137
|
|
|
121
138
|
|
|
139
|
+
def export(ctx: Context) -> dict[str, Any]:
|
|
140
|
+
"""Fetch the export and write it where asked. The bytes are the service's
|
|
141
|
+
own: nothing is re-encoded, so the file is the one the audit log names."""
|
|
142
|
+
run, fmt = str(ctx.args.run), ctx.args.export_format
|
|
143
|
+
body = ctx.client().get_bytes(f"/api/v1/runs/{run}/export", format=fmt)
|
|
144
|
+
target = ctx.args.output or f"run-{re.sub(r'[^A-Za-z0-9_-]', '', run)[:12]}.{fmt}"
|
|
145
|
+
if target == "-":
|
|
146
|
+
ctx.out.streamed = True
|
|
147
|
+
if fmt == "json":
|
|
148
|
+
ctx.out.stdout.write(body.decode("utf-8"))
|
|
149
|
+
else:
|
|
150
|
+
buffer = getattr(ctx.out.stdout, "buffer", None)
|
|
151
|
+
if buffer is None:
|
|
152
|
+
raise CliError("standard output cannot take a PDF here; use -o <file>",
|
|
153
|
+
error="not_binary")
|
|
154
|
+
buffer.write(body)
|
|
155
|
+
buffer.flush()
|
|
156
|
+
return {"ok": True, "streamed": True}
|
|
157
|
+
path = Path(target).expanduser()
|
|
158
|
+
if path.is_dir():
|
|
159
|
+
path = path / f"run-{re.sub(r'[^A-Za-z0-9_-]', '', run)[:12]}.{fmt}"
|
|
160
|
+
path.write_bytes(body)
|
|
161
|
+
return {"ok": True, "run_id_hash": run, "format": fmt, "path": str(path), "bytes": len(body)}
|
|
162
|
+
|
|
163
|
+
|
|
122
164
|
def runs(ctx: Context) -> dict[str, Any]:
|
|
123
165
|
which = getattr(ctx.args, "runs_command", None) or "list"
|
|
124
166
|
if which == "tail":
|
|
125
167
|
return tail(ctx)
|
|
168
|
+
if which == "export":
|
|
169
|
+
return export(ctx)
|
|
126
170
|
client = ctx.client()
|
|
127
171
|
if which == "show":
|
|
128
172
|
path = f"/api/v1/runs/{ctx.args.run}"
|
|
@@ -234,8 +234,12 @@ class Client:
|
|
|
234
234
|
# -- requests ---------------------------------------------------------------------
|
|
235
235
|
|
|
236
236
|
def request(self, method: str, path: str, *, params: dict[str, Any] | None = None,
|
|
237
|
-
json_body: Any = None, idempotency: str | None = None
|
|
238
|
-
|
|
237
|
+
json_body: Any = None, idempotency: str | None = None, raw: bool = False,
|
|
238
|
+
content: bytes | None = None, content_type: str | None = None) -> Any:
|
|
239
|
+
"""One logical request, retried where retrying is safe. With `raw`, a
|
|
240
|
+
success is returned as the body's bytes, as sent (a file: `runs export`).
|
|
241
|
+
`content` is a body sent as it is, with its `content_type` (a file:
|
|
242
|
+
`telemetry import`), in place of a JSON body."""
|
|
239
243
|
method = method.upper()
|
|
240
244
|
if idempotency is None and method in WRITES:
|
|
241
245
|
idempotency = str(uuid.uuid4())
|
|
@@ -245,9 +249,13 @@ class Client:
|
|
|
245
249
|
last: CliError | None = None
|
|
246
250
|
for attempt in range(self.attempts):
|
|
247
251
|
try:
|
|
252
|
+
headers = self._headers(idempotency)
|
|
253
|
+
if content is not None:
|
|
254
|
+
headers["Content-Type"] = content_type or "application/octet-stream"
|
|
248
255
|
reply = self._http.request(
|
|
249
256
|
method, path, params={k: v for k, v in (params or {}).items() if v is not None},
|
|
250
|
-
json=json_body,
|
|
257
|
+
json=json_body if content is None else None, content=content,
|
|
258
|
+
headers=headers)
|
|
251
259
|
except httpx.TimeoutException as e:
|
|
252
260
|
last = CliError(t("err.timeout", method=method, path=path), code=RETRY, error="timeout")
|
|
253
261
|
log.debug("timeout: %s", e)
|
|
@@ -262,6 +270,8 @@ class Client:
|
|
|
262
270
|
refreshed = True
|
|
263
271
|
continue
|
|
264
272
|
if 200 <= reply.status_code < 300:
|
|
273
|
+
if raw:
|
|
274
|
+
return reply.content
|
|
265
275
|
if not reply.content:
|
|
266
276
|
return {}
|
|
267
277
|
try:
|
|
@@ -303,9 +313,19 @@ class Client:
|
|
|
303
313
|
def get(self, path: str, **params: Any) -> Any:
|
|
304
314
|
return self.request("GET", path, params=params)
|
|
305
315
|
|
|
316
|
+
def get_bytes(self, path: str, **params: Any) -> bytes:
|
|
317
|
+
"""A GET whose answer is a file, returned exactly as the service sent it."""
|
|
318
|
+
body: bytes = self.request("GET", path, params=params, raw=True)
|
|
319
|
+
return body
|
|
320
|
+
|
|
306
321
|
def post(self, path: str, body: Any = None, **params: Any) -> Any:
|
|
307
322
|
return self.request("POST", path, params=params, json_body=body)
|
|
308
323
|
|
|
324
|
+
def post_bytes(self, path: str, content: bytes, content_type: str, **params: Any) -> Any:
|
|
325
|
+
"""A POST whose body is a file, sent exactly as read."""
|
|
326
|
+
return self.request("POST", path, params=params, content=content,
|
|
327
|
+
content_type=content_type)
|
|
328
|
+
|
|
309
329
|
def delete(self, path: str, **params: Any) -> Any:
|
|
310
330
|
return self.request("DELETE", path, params=params)
|
|
311
331
|
|
|
@@ -209,6 +209,10 @@
|
|
|
209
209
|
"help.runs.tail.duration": "توقف بعد هذا العدد من الثواني",
|
|
210
210
|
"help.runs.tail.deployment": "تشغيلات عملية النشر هذه فقط",
|
|
211
211
|
"help.runs.tail.fail_on_stall": "اخرج بالرمز 1 إذا تعثّر تشغيل",
|
|
212
|
+
"help.runs.export._help": "تصدير خالٍ من المحتوى لتشغيل واحد، بصيغة JSON أو PDF",
|
|
213
|
+
"help.runs.export.run": "قيمة run_id_hash الخاصة به",
|
|
214
|
+
"help.runs.export.export_format": "json (الافتراضي) أو pdf",
|
|
215
|
+
"help.runs.export.output": "الملف المراد كتابته (الافتراضي run-<hash>.<format>؛ - للإخراج القياسي)",
|
|
212
216
|
"help.spend.by": "كيفية التجميع",
|
|
213
217
|
"help.spend.month": "YYYY-MM؛ الشهر الحالي افتراضياً",
|
|
214
218
|
"help.spend.forecast": "وإسقاط نهاية الشهر مقابل الحدّ الأقصى أيضاً",
|
|
@@ -339,5 +343,23 @@
|
|
|
339
343
|
"label.evals": "التقييمات",
|
|
340
344
|
"label.calls_month": "الاستدعاءات (الشهر)",
|
|
341
345
|
"label.input_m": "الإدخال /M",
|
|
342
|
-
"label.output_m": "الإخراج /M"
|
|
346
|
+
"label.output_m": "الإخراج /M",
|
|
347
|
+
"help.telemetry._help": "حزم القياس عن بُعد من نشر بلا مسار شبكي",
|
|
348
|
+
"help.commands._help": "ملفات الأوامر لنشر بلا مسار شبكي",
|
|
349
|
+
"help.telemetry.import._help": "رفع حزمة قياس عن بُعد من نشر غير متصل",
|
|
350
|
+
"help.telemetry.status._help": "الدفعات المحفوظة لنشر ما، والدفعات المفقودة",
|
|
351
|
+
"help.telemetry.import.file": "الحزمة (.tar.gz) كما كتبها النشر",
|
|
352
|
+
"help.telemetry.import.deployment": "النشر (الافتراضي: النشر المذكور في الحزمة)",
|
|
353
|
+
"help.telemetry.status.deployment": "النشر",
|
|
354
|
+
"help.commands.export._help": "كتابة الأوامر المنتظرة في ملف أوامر موقّع",
|
|
355
|
+
"help.commands.export.deployment": "النشر",
|
|
356
|
+
"help.commands.export.out": "الملف المراد كتابته (- للإخراج القياسي)",
|
|
357
|
+
"help.commands.export.force": "استبدال الملف إن وُجد",
|
|
358
|
+
"label.epoch": "الحقبة",
|
|
359
|
+
"label.held_through": "مكتمل حتى",
|
|
360
|
+
"label.highest": "الأعلى",
|
|
361
|
+
"label.missing": "المفقود",
|
|
362
|
+
"label.records_stored": "السجلات المخزنة",
|
|
363
|
+
"label.records_already_held": "السجلات المحفوظة مسبقًا",
|
|
364
|
+
"label.spans": "الامتدادات"
|
|
343
365
|
}
|
|
@@ -209,6 +209,10 @@
|
|
|
209
209
|
"help.runs.tail.duration": "nach so vielen Sekunden beenden",
|
|
210
210
|
"help.runs.tail.deployment": "nur die Läufe dieser Bereitstellung",
|
|
211
211
|
"help.runs.tail.fail_on_stall": "mit Exit 1 beenden, wenn ein Lauf festhängt",
|
|
212
|
+
"help.runs.export._help": "der inhaltsfreie Export eines Laufs, als JSON oder PDF",
|
|
213
|
+
"help.runs.export.run": "sein run_id_hash",
|
|
214
|
+
"help.runs.export.export_format": "json (Standard) oder pdf",
|
|
215
|
+
"help.runs.export.output": "die zu schreibende Datei (Standard run-<hash>.<format>; - für die Standardausgabe)",
|
|
212
216
|
"help.spend.by": "wie gruppiert wird",
|
|
213
217
|
"help.spend.month": "JJJJ-MM; standardmäßig der aktuelle Monat",
|
|
214
218
|
"help.spend.forecast": "auch die Monatsend-Prognose gegenüber dem Limit",
|
|
@@ -339,5 +343,23 @@
|
|
|
339
343
|
"label.evals": "Evaluierungen",
|
|
340
344
|
"label.calls_month": "Aufrufe (Monat)",
|
|
341
345
|
"label.input_m": "Eingabe /M",
|
|
342
|
-
"label.output_m": "Ausgabe /M"
|
|
346
|
+
"label.output_m": "Ausgabe /M",
|
|
347
|
+
"help.telemetry._help": "Telemetriepakete einer Bereitstellung ohne Netzwerkpfad",
|
|
348
|
+
"help.commands._help": "Befehlsdateien für eine Bereitstellung ohne Netzwerkpfad",
|
|
349
|
+
"help.telemetry.import._help": "ein Telemetriepaket einer Offline-Bereitstellung hochladen",
|
|
350
|
+
"help.telemetry.status._help": "die für eine Bereitstellung gehaltenen Pakete und fehlende",
|
|
351
|
+
"help.telemetry.import.file": "das Paket (.tar.gz), wie die Bereitstellung es geschrieben hat",
|
|
352
|
+
"help.telemetry.import.deployment": "die Bereitstellung (Standard: die im Paket genannte)",
|
|
353
|
+
"help.telemetry.status.deployment": "die Bereitstellung",
|
|
354
|
+
"help.commands.export._help": "die wartenden Befehle in eine signierte Befehlsdatei schreiben",
|
|
355
|
+
"help.commands.export.deployment": "die Bereitstellung",
|
|
356
|
+
"help.commands.export.out": "die zu schreibende Datei (- für die Standardausgabe)",
|
|
357
|
+
"help.commands.export.force": "die Datei überschreiben, falls vorhanden",
|
|
358
|
+
"label.epoch": "Epoche",
|
|
359
|
+
"label.held_through": "lückenlos bis",
|
|
360
|
+
"label.highest": "höchste",
|
|
361
|
+
"label.missing": "fehlend",
|
|
362
|
+
"label.records_stored": "gespeicherte Datensätze",
|
|
363
|
+
"label.records_already_held": "bereits gehaltene Datensätze",
|
|
364
|
+
"label.spans": "Spans"
|
|
343
365
|
}
|
|
@@ -209,6 +209,10 @@
|
|
|
209
209
|
"help.runs.tail.duration": "stop after this many seconds",
|
|
210
210
|
"help.runs.tail.deployment": "only this deployment's runs",
|
|
211
211
|
"help.runs.tail.fail_on_stall": "exit 1 if a run has stalled",
|
|
212
|
+
"help.runs.export._help": "one run's content-free export, as JSON or PDF",
|
|
213
|
+
"help.runs.export.run": "its run_id_hash",
|
|
214
|
+
"help.runs.export.export_format": "json (the default) or pdf",
|
|
215
|
+
"help.runs.export.output": "the file to write (default run-<hash>.<format>; - for standard output)",
|
|
212
216
|
"help.spend.by": "how to group",
|
|
213
217
|
"help.spend.month": "YYYY-MM; the current month by default",
|
|
214
218
|
"help.spend.forecast": "the month-end projection against the cap as well",
|
|
@@ -339,5 +343,23 @@
|
|
|
339
343
|
"label.evals": "evals",
|
|
340
344
|
"label.calls_month": "calls (month)",
|
|
341
345
|
"label.input_m": "input /M",
|
|
342
|
-
"label.output_m": "output /M"
|
|
346
|
+
"label.output_m": "output /M",
|
|
347
|
+
"help.telemetry._help": "telemetry bundles from a deployment with no network path",
|
|
348
|
+
"help.commands._help": "command files for a deployment with no network path",
|
|
349
|
+
"help.telemetry.import._help": "upload a telemetry bundle from an offline deployment",
|
|
350
|
+
"help.telemetry.status._help": "the batches held for a deployment, and any missing",
|
|
351
|
+
"help.telemetry.import.file": "the bundle (.tar.gz), as the deployment wrote it",
|
|
352
|
+
"help.telemetry.import.deployment": "the deployment (default: the one the bundle names)",
|
|
353
|
+
"help.telemetry.status.deployment": "the deployment",
|
|
354
|
+
"help.commands.export._help": "write the waiting commands into a signed command file",
|
|
355
|
+
"help.commands.export.deployment": "the deployment",
|
|
356
|
+
"help.commands.export.out": "the file to write (- for standard output)",
|
|
357
|
+
"help.commands.export.force": "overwrite the file if it exists",
|
|
358
|
+
"label.epoch": "epoch",
|
|
359
|
+
"label.held_through": "held through",
|
|
360
|
+
"label.highest": "highest",
|
|
361
|
+
"label.missing": "missing",
|
|
362
|
+
"label.records_stored": "records stored",
|
|
363
|
+
"label.records_already_held": "records already held",
|
|
364
|
+
"label.spans": "spans"
|
|
343
365
|
}
|
|
@@ -209,6 +209,10 @@
|
|
|
209
209
|
"help.runs.tail.duration": "parar tras este número de segundos",
|
|
210
210
|
"help.runs.tail.deployment": "solo las ejecuciones de este despliegue",
|
|
211
211
|
"help.runs.tail.fail_on_stall": "salir con 1 si una ejecución se ha atascado",
|
|
212
|
+
"help.runs.export._help": "la exportación sin contenido de una ejecución, en JSON o PDF",
|
|
213
|
+
"help.runs.export.run": "su run_id_hash",
|
|
214
|
+
"help.runs.export.export_format": "json (por defecto) o pdf",
|
|
215
|
+
"help.runs.export.output": "el archivo que se escribe (por defecto run-<hash>.<format>; - para la salida estándar)",
|
|
212
216
|
"help.spend.by": "cómo agrupar",
|
|
213
217
|
"help.spend.month": "AAAA-MM; por defecto, el mes actual",
|
|
214
218
|
"help.spend.forecast": "también la proyección de fin de mes frente al límite",
|
|
@@ -339,5 +343,23 @@
|
|
|
339
343
|
"label.evals": "evaluaciones",
|
|
340
344
|
"label.calls_month": "llamadas (mes)",
|
|
341
345
|
"label.input_m": "entrada /M",
|
|
342
|
-
"label.output_m": "salida /M"
|
|
346
|
+
"label.output_m": "salida /M",
|
|
347
|
+
"help.telemetry._help": "paquetes de telemetría de un despliegue sin ruta de red",
|
|
348
|
+
"help.commands._help": "archivos de comandos para un despliegue sin ruta de red",
|
|
349
|
+
"help.telemetry.import._help": "subir un paquete de telemetría de un despliegue sin conexión",
|
|
350
|
+
"help.telemetry.status._help": "los lotes conservados para un despliegue, y los que faltan",
|
|
351
|
+
"help.telemetry.import.file": "el paquete (.tar.gz), tal como lo escribió el despliegue",
|
|
352
|
+
"help.telemetry.import.deployment": "el despliegue (por defecto: el que nombra el paquete)",
|
|
353
|
+
"help.telemetry.status.deployment": "el despliegue",
|
|
354
|
+
"help.commands.export._help": "escribir los comandos pendientes en un archivo de comandos firmado",
|
|
355
|
+
"help.commands.export.deployment": "el despliegue",
|
|
356
|
+
"help.commands.export.out": "el archivo que se escribe (- para la salida estándar)",
|
|
357
|
+
"help.commands.export.force": "sobrescribir el archivo si existe",
|
|
358
|
+
"label.epoch": "época",
|
|
359
|
+
"label.held_through": "completo hasta",
|
|
360
|
+
"label.highest": "más alto",
|
|
361
|
+
"label.missing": "faltan",
|
|
362
|
+
"label.records_stored": "registros almacenados",
|
|
363
|
+
"label.records_already_held": "registros ya conservados",
|
|
364
|
+
"label.spans": "spans"
|
|
343
365
|
}
|
|
@@ -209,6 +209,10 @@
|
|
|
209
209
|
"help.runs.tail.duration": "s'arrêter après ce nombre de secondes",
|
|
210
210
|
"help.runs.tail.deployment": "uniquement les exécutions de ce déploiement",
|
|
211
211
|
"help.runs.tail.fail_on_stall": "quitter avec le code 1 si une exécution est bloquée",
|
|
212
|
+
"help.runs.export._help": "l'export sans contenu d'une exécution, en JSON ou en PDF",
|
|
213
|
+
"help.runs.export.run": "son run_id_hash",
|
|
214
|
+
"help.runs.export.export_format": "json (par défaut) ou pdf",
|
|
215
|
+
"help.runs.export.output": "le fichier à écrire (par défaut run-<hash>.<format> ; - pour la sortie standard)",
|
|
212
216
|
"help.spend.by": "comment regrouper",
|
|
213
217
|
"help.spend.month": "AAAA-MM ; le mois en cours par défaut",
|
|
214
218
|
"help.spend.forecast": "aussi la projection de fin de mois par rapport au plafond",
|
|
@@ -339,5 +343,23 @@
|
|
|
339
343
|
"label.evals": "évaluations",
|
|
340
344
|
"label.calls_month": "appels (mois)",
|
|
341
345
|
"label.input_m": "entrée /M",
|
|
342
|
-
"label.output_m": "sortie /M"
|
|
346
|
+
"label.output_m": "sortie /M",
|
|
347
|
+
"help.telemetry._help": "lots de télémétrie d'un déploiement sans accès réseau",
|
|
348
|
+
"help.commands._help": "fichiers de commandes pour un déploiement sans accès réseau",
|
|
349
|
+
"help.telemetry.import._help": "téléverser un lot de télémétrie d'un déploiement hors ligne",
|
|
350
|
+
"help.telemetry.status._help": "les lots détenus pour un déploiement, et ceux qui manquent",
|
|
351
|
+
"help.telemetry.import.file": "le lot (.tar.gz), tel que le déploiement l'a écrit",
|
|
352
|
+
"help.telemetry.import.deployment": "le déploiement (par défaut : celui que le lot nomme)",
|
|
353
|
+
"help.telemetry.status.deployment": "le déploiement",
|
|
354
|
+
"help.commands.export._help": "écrire les commandes en attente dans un fichier de commandes signé",
|
|
355
|
+
"help.commands.export.deployment": "le déploiement",
|
|
356
|
+
"help.commands.export.out": "le fichier à écrire (- pour la sortie standard)",
|
|
357
|
+
"help.commands.export.force": "écraser le fichier s'il existe",
|
|
358
|
+
"label.epoch": "époque",
|
|
359
|
+
"label.held_through": "détenu jusqu'à",
|
|
360
|
+
"label.highest": "le plus élevé",
|
|
361
|
+
"label.missing": "manquants",
|
|
362
|
+
"label.records_stored": "enregistrements stockés",
|
|
363
|
+
"label.records_already_held": "enregistrements déjà détenus",
|
|
364
|
+
"label.spans": "spans"
|
|
343
365
|
}
|
|
@@ -209,6 +209,10 @@
|
|
|
209
209
|
"help.runs.tail.duration": "stop tar éis an méid soicindí seo",
|
|
210
210
|
"help.runs.tail.deployment": "ritheanna an imscartha seo amháin",
|
|
211
211
|
"help.runs.tail.fail_on_stall": "scoir le 1 má tá rith greamaithe",
|
|
212
|
+
"help.runs.export._help": "easpórtáil gan ábhar de rith amháin, mar JSON nó PDF",
|
|
213
|
+
"help.runs.export.run": "a run_id_hash",
|
|
214
|
+
"help.runs.export.export_format": "json (an réamhshocrú) nó pdf",
|
|
215
|
+
"help.runs.export.output": "an comhad le scríobh (réamhshocrú run-<hash>.<format>; - don aschur caighdeánach)",
|
|
212
216
|
"help.spend.by": "conas grúpáil",
|
|
213
217
|
"help.spend.month": "BBBB-MM; an mhí reatha de réir réamhshocraithe",
|
|
214
218
|
"help.spend.forecast": "réamh-mheastachán dheireadh na míosa i gcomparáid leis an uasteorainn freisin",
|
|
@@ -339,5 +343,23 @@
|
|
|
339
343
|
"label.evals": "meastóireachtaí",
|
|
340
344
|
"label.calls_month": "glaonna (mí)",
|
|
341
345
|
"label.input_m": "ionchur /M",
|
|
342
|
-
"label.output_m": "aschur /M"
|
|
346
|
+
"label.output_m": "aschur /M",
|
|
347
|
+
"help.telemetry._help": "beartáin teiliméadrachta ó imscaradh gan chonair líonra",
|
|
348
|
+
"help.commands._help": "comhaid ordaithe d'imscaradh gan chonair líonra",
|
|
349
|
+
"help.telemetry.import._help": "beart teiliméadrachta ó imscaradh as líne a uaslódáil",
|
|
350
|
+
"help.telemetry.status._help": "na baisceanna atá ar coimeád d'imscaradh, agus a bhfuil in easnamh",
|
|
351
|
+
"help.telemetry.import.file": "an beart (.tar.gz), mar a scríobh an t-imscaradh é",
|
|
352
|
+
"help.telemetry.import.deployment": "an t-imscaradh (réamhshocrú: an ceann a ainmníonn an beart)",
|
|
353
|
+
"help.telemetry.status.deployment": "an t-imscaradh",
|
|
354
|
+
"help.commands.export._help": "na horduithe atá ag fanacht a scríobh i gcomhad ordaithe sínithe",
|
|
355
|
+
"help.commands.export.deployment": "an t-imscaradh",
|
|
356
|
+
"help.commands.export.out": "an comhad le scríobh (- don aschur caighdeánach)",
|
|
357
|
+
"help.commands.export.force": "forscríobh an comhad má tá sé ann",
|
|
358
|
+
"label.epoch": "ré",
|
|
359
|
+
"label.held_through": "iomlán go dtí",
|
|
360
|
+
"label.highest": "is airde",
|
|
361
|
+
"label.missing": "in easnamh",
|
|
362
|
+
"label.records_stored": "taifid stóráilte",
|
|
363
|
+
"label.records_already_held": "taifid ar coimeád cheana",
|
|
364
|
+
"label.spans": "réisí"
|
|
343
365
|
}
|