cdx-manager 0.18.3 → 0.18.4
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.
- package/README.md +7 -2
- package/changelogs/CHANGELOGS_0_18_4.md +78 -0
- package/checksums/release-archives.json +11 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/commands/maintenance.py +51 -0
- package/src/commands/tray.py +56 -1
- package/src/tray_contract.py +27 -0
- package/src/tray_install.py +28 -0
package/README.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
<img src="docs/assets/icon.png" alt="CDX Manager icon" width="64" align="left" />
|
|
2
|
+
|
|
1
3
|
# CDX Manager
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
<br clear="left"/>
|
|
6
|
+
|
|
7
|
+
[](LICENSE)  
|
|
4
8
|
|
|
5
9
|
**Stop guessing which AI account still has quota.** `cdx` tracks the rate-limit window of every Codex and Claude account you own, tells you which one is usable right now, and launches it with isolated auth — in one command.
|
|
6
10
|
|
|
@@ -515,8 +519,9 @@ cdx history --summary --from 2026-05-01 --to 2026-05-28
|
|
|
515
519
|
| `cdx status <name> [--json] [--refresh\|--cached] [--timeout SECONDS]` | Show detailed usage breakdown for one session; `--cached` avoids live provider refreshes |
|
|
516
520
|
| `cdx tray status [--json] [--refresh]` | Emit the tray snapshot: one icon state, one line per enabled session, and how fresh each figure is. Reads stored status by default; `--refresh` is the only path that probes a provider |
|
|
517
521
|
| `cdx tray launch [--json]` | Start the installed tray companion, detached. Reports `tray_companion_not_installed` when nothing is recorded, or `tray_companion_missing` when the recorded companion is gone. `CDX_TRAY_BIN` points it at a locally built one |
|
|
518
|
-
| `cdx tray install [--json]` | Download the companion for this OS and architecture, verify its published checksum before unpacking,
|
|
522
|
+
| `cdx tray install [--json] [--yes] [--no-autostart]` | Download the companion for this OS and architecture, verify its published checksum before unpacking, record what was written, and start it. Asks whether to launch it at login — `--yes` answers without a prompt, `--no-autostart` declines, and a non-interactive run declines rather than acquiring a login item by accident. Refuses when no checksum is published for the asset |
|
|
519
523
|
| `cdx tray uninstall [--json]` | Remove exactly what `install` recorded, and nothing else |
|
|
524
|
+
| — | `cdx update` also moves an installed companion to the new release, and says so. A companion already running keeps executing the build it started with, so it reports that too rather than leaving you to wonder why the menu looks unchanged |
|
|
520
525
|
| `cdx tray autostart [on\|off\|status] [--json]` | Start the companion at login, or stop doing so. Off until asked, idempotent, and the state is read back from the platform rather than from what CDX last intended |
|
|
521
526
|
| `cdx tray events [--json]` | List agent alerts a running companion has not shown yet. The companion reads these instead of the spool file, so a Windows tray can collect events from CDX in WSL over the same interop it already uses |
|
|
522
527
|
| `cdx tray ack <id>... [--json]` | Mark events as shown, idempotently |
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# CDX Manager 0.18.4
|
|
2
|
+
|
|
3
|
+
## Fixes
|
|
4
|
+
|
|
5
|
+
### Agent alerts arrive when they happen
|
|
6
|
+
|
|
7
|
+
An alert reached the tray on the next poll, so up to thirty seconds passed
|
|
8
|
+
between a turn finishing and the banner appearing — where the direct path had
|
|
9
|
+
been instant. The poll period is a cost budget, not a latency target, and it was
|
|
10
|
+
the wrong one for the single event a user actively waits for.
|
|
11
|
+
|
|
12
|
+
The companion now notices the spool changing between polls. No file watcher was
|
|
13
|
+
needed: the event loop already wakes five times a second, so a `stat` costs
|
|
14
|
+
microseconds at that rate. Measured at 0.57s from hook to consumption, including
|
|
15
|
+
the polling in the measurement itself.
|
|
16
|
+
|
|
17
|
+
Unchanged across the Windows-to-WSL boundary, and deliberately: a Windows
|
|
18
|
+
process cannot watch a path inside the Linux filesystem, so the poll period
|
|
19
|
+
remains the latency there.
|
|
20
|
+
|
|
21
|
+
### The menu bar icon stays visible after the first poll
|
|
22
|
+
|
|
23
|
+
`tray-icon`'s macOS `set_icon` clears the template flag unconditionally, so every
|
|
24
|
+
refresh dropped the setting that lets macOS invert the glyph for the current
|
|
25
|
+
theme. The icon started white and turned black about thirty seconds later, which
|
|
26
|
+
on a dark menu bar means invisible.
|
|
27
|
+
|
|
28
|
+
### `cdx update` brings the tray companion with it
|
|
29
|
+
|
|
30
|
+
The companion is a separate artifact on a separate release, so it drifted and
|
|
31
|
+
had to be reinstalled by hand. `cdx update` now moves it too, through the staged
|
|
32
|
+
path that verifies the asset and proves the replacement starts before the
|
|
33
|
+
working one is touched — and never fails the update if it cannot: the previous
|
|
34
|
+
companion stays installed and the reason is reported.
|
|
35
|
+
|
|
36
|
+
`cdx tray doctor` names the state now, rather than printing two version numbers
|
|
37
|
+
and leaving the comparison to you: `aligned` or `drifted`.
|
|
38
|
+
|
|
39
|
+
## Features
|
|
40
|
+
|
|
41
|
+
### The menu is readable at a dozen accounts
|
|
42
|
+
|
|
43
|
+
Rows are selectable rather than informational — macOS renders disabled items
|
|
44
|
+
grey, so a dozen arrived as a wall of grey text — and clicking one opens a
|
|
45
|
+
terminal on that session. The list is sorted by urgency, most constrained first;
|
|
46
|
+
it previously arrived in storage order, so an account at 1% could sit tenth
|
|
47
|
+
while the icon showed critical.
|
|
48
|
+
|
|
49
|
+
Sessions are grouped under their provider instead of repeating it on every row,
|
|
50
|
+
and each carries a gauge. On macOS the rows are drawn: name, a gauge coloured by
|
|
51
|
+
severity, and the figure aligned on a fixed column — a native menu font is
|
|
52
|
+
proportional, so text alone cannot align. Windows and Linux keep the text rows,
|
|
53
|
+
which say everything the drawing does.
|
|
54
|
+
|
|
55
|
+
Times are distances, not stamps: `↻ in 6d 23h` rather than `↻ Aug 18 03:23`, and
|
|
56
|
+
a stale row says how stale. The wording is `cdx status`'s own.
|
|
57
|
+
|
|
58
|
+
### A switch to silence agent alerts
|
|
59
|
+
|
|
60
|
+
In the tray menu and as `cdx tray alerts on|off|status`. Muting stops the
|
|
61
|
+
banner, not the record — events keep reaching a running companion, so the menu
|
|
62
|
+
shows what you missed — and it applies to the direct path too, so quitting the
|
|
63
|
+
tray does not silently un-mute you.
|
|
64
|
+
|
|
65
|
+
### `cdx tray install` starts what it installed
|
|
66
|
+
|
|
67
|
+
And asks whether to launch it at login. `--yes` answers without a prompt,
|
|
68
|
+
`--no-autostart` declines, and a non-interactive run declines too: a script must
|
|
69
|
+
not acquire a startup entry by running an install.
|
|
70
|
+
|
|
71
|
+
## Validation
|
|
72
|
+
|
|
73
|
+
- `npm run lint`, `npm test` — 790 Python tests
|
|
74
|
+
- `cargo test` — 52 Rust tests; `cargo clippy` clean with zero warnings on three
|
|
75
|
+
targets
|
|
76
|
+
- 143 Python tests on native Windows, plus the Windows and Linux companions
|
|
77
|
+
exercised on a managed host: grouped menu, gauges, alert switch, spool path,
|
|
78
|
+
and the unsupported-desktop path still refusing to draw
|
|
@@ -224,6 +224,17 @@
|
|
|
224
224
|
"x86_64-pc-windows-msvc": "7a40ffc6a1908e0cda5e484b50ab7c11b6cb01f4c370905eefe23e40a8afb7bd",
|
|
225
225
|
"x86_64-unknown-linux-musl": "2f4b95c661cdb65bf58c2aa0d2c7a45e03bb5a8ec3d20404c688887dba33afe4"
|
|
226
226
|
}
|
|
227
|
+
},
|
|
228
|
+
"v0.18.3": {
|
|
229
|
+
"github_tarball_sha256": "0099b87967b2a2f4f026c97888d2017473c0b58aa974b56dd35320231e67e248",
|
|
230
|
+
"github_zip_sha256": "f6f15a9f91a85696d4f865cf192bfb4b076be92937105df57329fbfc73147631",
|
|
231
|
+
"tray_assets": {
|
|
232
|
+
"aarch64-apple-darwin": "5bbd0d9764c0feae3fc60c24951fc8202f56c8cd69574440277a4b372ae93da5",
|
|
233
|
+
"aarch64-unknown-linux-musl": "c937af7a08701fbc102ff3d7e0458e488a57001e65f20acab7809221a0395310",
|
|
234
|
+
"x86_64-apple-darwin": "036dde97929ed4c3a5e0b29b4e8332caede15c124e907c573557d888439af748",
|
|
235
|
+
"x86_64-pc-windows-msvc": "e50250d21d11e6986a310a00d4f3ab5b1a13d2960684750c569926d5bda923f9",
|
|
236
|
+
"x86_64-unknown-linux-musl": "edbdb02b7e9df5709b5b9512233e2b25b51f7652bd1b78c68edb85dbfa65d708"
|
|
237
|
+
}
|
|
227
238
|
}
|
|
228
239
|
}
|
|
229
240
|
}
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
@@ -587,6 +587,51 @@ def handle_repair(rest, ctx):
|
|
|
587
587
|
ctx["out"](f"{_dim('Tip: run cdx repair --force to apply safe repairs.', ctx['use_color'])}\n")
|
|
588
588
|
return 0
|
|
589
589
|
|
|
590
|
+
def _align_tray_companion(ctx, target_version):
|
|
591
|
+
"""Move an installed tray companion to the release CDX just moved to.
|
|
592
|
+
|
|
593
|
+
Never raises and never fails the update: CDX has already been replaced
|
|
594
|
+
successfully by this point, and a companion that could not follow is a
|
|
595
|
+
warning, not a reason to call a good update bad.
|
|
596
|
+
"""
|
|
597
|
+
from ..tray_install import align_companion
|
|
598
|
+
from ..tray_instance import companion_instance
|
|
599
|
+
|
|
600
|
+
try:
|
|
601
|
+
base_dir = ctx["service"]["base_dir"]
|
|
602
|
+
result = align_companion(base_dir, target_version, download=ctx.get("download_asset"))
|
|
603
|
+
except Exception as error: # noqa: BLE001 - the update already succeeded
|
|
604
|
+
return [{"code": "tray_companion_not_aligned", "message": f"The tray companion was left as it was: {error}"}]
|
|
605
|
+
|
|
606
|
+
if not result["aligned"]:
|
|
607
|
+
if result.get("reason") in ("no companion is installed", "already aligned"):
|
|
608
|
+
return []
|
|
609
|
+
return [{
|
|
610
|
+
"code": "tray_companion_not_aligned",
|
|
611
|
+
"message": (
|
|
612
|
+
f"The tray companion is still on {result.get('previous') or 'its previous version'}: "
|
|
613
|
+
f"{result['reason']} Run: cdx tray install"
|
|
614
|
+
),
|
|
615
|
+
}]
|
|
616
|
+
|
|
617
|
+
warnings = [{
|
|
618
|
+
"code": "tray_companion_updated",
|
|
619
|
+
"message": f"Updated the tray companion to {target_version} as well.",
|
|
620
|
+
}]
|
|
621
|
+
# A running companion keeps executing the binary it started with. Saying so
|
|
622
|
+
# beats leaving someone to wonder why the menu still looks old, and beats
|
|
623
|
+
# quitting an application they did not ask us to touch.
|
|
624
|
+
try:
|
|
625
|
+
if companion_instance(env=ctx.get("env")).get("pid"):
|
|
626
|
+
warnings.append({
|
|
627
|
+
"code": "tray_companion_restart_pending",
|
|
628
|
+
"message": "The running tray is still the previous build. Quit it from its menu and run: cdx tray launch",
|
|
629
|
+
})
|
|
630
|
+
except Exception: # noqa: BLE001
|
|
631
|
+
pass
|
|
632
|
+
return warnings
|
|
633
|
+
|
|
634
|
+
|
|
590
635
|
def handle_update(rest, ctx):
|
|
591
636
|
parsed = _parse_update_args(rest)
|
|
592
637
|
if parsed["all"]:
|
|
@@ -704,6 +749,12 @@ def handle_update(rest, ctx):
|
|
|
704
749
|
if version_warning:
|
|
705
750
|
warnings.append(version_warning)
|
|
706
751
|
|
|
752
|
+
# The tray companion is a separate artifact on a separate release, so it
|
|
753
|
+
# drifts unless something moves it. Reported rather than silent either way:
|
|
754
|
+
# an install that did not happen is as worth knowing as one that did.
|
|
755
|
+
for tray_warning in _align_tray_companion(ctx, target_version):
|
|
756
|
+
warnings.append(tray_warning)
|
|
757
|
+
|
|
707
758
|
message = f"Updated cdx-manager to {target_version}"
|
|
708
759
|
if json_flag:
|
|
709
760
|
_write_json(ctx, _json_success(
|
package/src/commands/tray.py
CHANGED
|
@@ -27,6 +27,7 @@ from ..tray_events import (
|
|
|
27
27
|
acknowledge as ack_events,
|
|
28
28
|
)
|
|
29
29
|
from ..tray_events import (
|
|
30
|
+
events_path,
|
|
30
31
|
read_events,
|
|
31
32
|
write_heartbeat,
|
|
32
33
|
)
|
|
@@ -116,6 +117,11 @@ def _tray_status(args, ctx):
|
|
|
116
117
|
base_dir = ctx["service"]["base_dir"]
|
|
117
118
|
snapshot["events"] = read_events(base_dir)
|
|
118
119
|
snapshot["alerts_enabled"] = alerts_enabled(base_dir)
|
|
120
|
+
# Where the alerts land, so a companion on this machine can watch the file
|
|
121
|
+
# instead of waiting for its next poll. Meaningless across WSL — a Windows
|
|
122
|
+
# process cannot stat a path in the Linux filesystem — and the companion
|
|
123
|
+
# ignores it there rather than CDX pretending otherwise.
|
|
124
|
+
snapshot["spool_path"] = events_path(base_dir)
|
|
119
125
|
# `--beat` is what claims ownership of alerts, so it is opt-in: a caller
|
|
120
126
|
# that merely looks at the status must not make `cdx notify` believe a tray
|
|
121
127
|
# is listening.
|
|
@@ -199,10 +205,18 @@ def _tray_uninstall(args, ctx):
|
|
|
199
205
|
return 0
|
|
200
206
|
|
|
201
207
|
|
|
208
|
+
def _confirm_autostart():
|
|
209
|
+
answer = input("Start the CDX tray automatically at login? [Y/n] ")
|
|
210
|
+
return answer.strip().lower() in ("", "y", "yes")
|
|
211
|
+
|
|
212
|
+
|
|
202
213
|
def _tray_install(args, ctx):
|
|
203
214
|
parsed = _parse_flag_args(args, {
|
|
204
215
|
"--json": {"key": "json", "type": "bool", "default": False},
|
|
216
|
+
"--yes": {"key": "yes", "type": "bool", "default": False},
|
|
217
|
+
"--no-autostart": {"key": "no_autostart", "type": "bool", "default": False},
|
|
205
218
|
}, TRAY_USAGE, positionals_key="args", max_positionals=0)
|
|
219
|
+
env = ctx.get("env")
|
|
206
220
|
target = current_target()
|
|
207
221
|
if not target:
|
|
208
222
|
return _refuse(
|
|
@@ -222,14 +236,50 @@ def _tray_install(args, ctx):
|
|
|
222
236
|
raise CdxError(str(error)) from error
|
|
223
237
|
|
|
224
238
|
message = f"Installed the {state['target']} tray companion for CDX {state['cdx_version']}"
|
|
239
|
+
|
|
240
|
+
# Installing something you asked for should show you the thing. The tray is
|
|
241
|
+
# started here rather than left for a second command, and a failure to start
|
|
242
|
+
# is reported without undoing an install that worked.
|
|
243
|
+
started = False
|
|
244
|
+
try:
|
|
245
|
+
spawn = ctx.get("spawn_detached") or _spawn_detached
|
|
246
|
+
spawn(launch_command(state["executable"]))
|
|
247
|
+
started = True
|
|
248
|
+
except Exception as error: # noqa: BLE001 - the install itself succeeded
|
|
249
|
+
ctx_error = str(error)
|
|
250
|
+
else:
|
|
251
|
+
ctx_error = None
|
|
252
|
+
|
|
253
|
+
# Startup is asked for, never assumed. `req_038` AC1 originally forbade it
|
|
254
|
+
# outright, for a good reason — an install that quietly adds a login item is
|
|
255
|
+
# what people resent about desktop software. Asking keeps the reason and
|
|
256
|
+
# drops the friction; a non-interactive run declines rather than deciding.
|
|
257
|
+
autostart_on = False
|
|
258
|
+
autostart_reason = None
|
|
259
|
+
if parsed["no_autostart"]:
|
|
260
|
+
autostart_reason = "skipped with --no-autostart"
|
|
261
|
+
elif parsed["yes"] or (ctx["stdin_is_tty"] and not parsed["json"] and _confirm_autostart()):
|
|
262
|
+
try:
|
|
263
|
+
autostart_enable(state["executable"], env=env, run=ctx.get("spawn_sync"))
|
|
264
|
+
autostart_on = True
|
|
265
|
+
except Exception as error: # noqa: BLE001
|
|
266
|
+
autostart_reason = str(error)
|
|
267
|
+
else:
|
|
268
|
+
autostart_reason = "not enabled; run: cdx tray autostart on"
|
|
269
|
+
|
|
225
270
|
if parsed["json"]:
|
|
226
271
|
_write_json(ctx, _json_success(
|
|
227
272
|
"tray.install", message,
|
|
228
273
|
target=state["target"], executable=state["executable"],
|
|
229
274
|
sha256=state["sha256"], applied=True,
|
|
275
|
+
started=started, autostart=autostart_on,
|
|
230
276
|
))
|
|
231
277
|
return 0
|
|
232
278
|
ctx["out"](f"{message}\n{_dim(state['executable'], ctx['use_color'])}\n")
|
|
279
|
+
ctx["out"](f"{'Started it.' if started else f'It is installed but did not start: {ctx_error}'}\n")
|
|
280
|
+
ctx["out"](
|
|
281
|
+
f"{'It will start at login.' if autostart_on else f'Startup: {autostart_reason}'}\n"
|
|
282
|
+
)
|
|
233
283
|
return 0
|
|
234
284
|
|
|
235
285
|
|
|
@@ -308,7 +358,12 @@ def _tray_doctor(args, ctx):
|
|
|
308
358
|
executable or "run: cdx tray install"),
|
|
309
359
|
("executable", "present" if executable and os.path.exists(executable) else "missing",
|
|
310
360
|
executable or "-"),
|
|
311
|
-
|
|
361
|
+
# Named rather than left as two numbers to compare: drift is the state
|
|
362
|
+
# that makes someone reinstall by hand, and `cdx update` now fixes it,
|
|
363
|
+
# so the report should say which one you are in.
|
|
364
|
+
("cdx_version",
|
|
365
|
+
"aligned" if not state or state.get("cdx_version") == ctx["version"] else "drifted",
|
|
366
|
+
f"cdx {ctx['version']}, companion {(state or {}).get('cdx_version') or 'not installed'}"),
|
|
312
367
|
("target", (state or {}).get("target") or "-", (state or {}).get("sha256") or "-"),
|
|
313
368
|
("running", "yes" if instance.get("pid") else "no", str(instance.get("pid") or "-")),
|
|
314
369
|
("autostart", "on" if autostart["enabled"] else "off", autostart["artifact"] or "unsupported"),
|
package/src/tray_contract.py
CHANGED
|
@@ -19,6 +19,7 @@ from .session_status import (
|
|
|
19
19
|
CODEX_STATUS_CACHE_TTL_SECONDS,
|
|
20
20
|
STATUS_CACHE_TTL_SECONDS,
|
|
21
21
|
)
|
|
22
|
+
from .status_view import _format_reset_time
|
|
22
23
|
|
|
23
24
|
SCHEMA_NAME = "cdx.tray.snapshot"
|
|
24
25
|
SCHEMA_MAJOR = 1
|
|
@@ -96,6 +97,26 @@ def _eligible(row):
|
|
|
96
97
|
return row.get("enabled", True) is not False
|
|
97
98
|
|
|
98
99
|
|
|
100
|
+
def _ago(seconds):
|
|
101
|
+
"""How long ago, in the shortest form that stays unambiguous.
|
|
102
|
+
|
|
103
|
+
`None` when nothing was ever reported: "never" is a state the caller already
|
|
104
|
+
names, and inventing an age for it would be worse than saying nothing.
|
|
105
|
+
"""
|
|
106
|
+
if seconds is None:
|
|
107
|
+
return None
|
|
108
|
+
seconds = max(0, int(seconds))
|
|
109
|
+
if seconds < 60:
|
|
110
|
+
return "just now"
|
|
111
|
+
minutes = seconds // 60
|
|
112
|
+
if minutes < 60:
|
|
113
|
+
return f"{minutes}m ago"
|
|
114
|
+
hours = minutes // 60
|
|
115
|
+
if hours < 24:
|
|
116
|
+
return f"{hours}h ago"
|
|
117
|
+
return f"{hours // 24}d ago"
|
|
118
|
+
|
|
119
|
+
|
|
99
120
|
def menu_session(row, now):
|
|
100
121
|
"""One menu line. Everything the tray shows once the user opens it."""
|
|
101
122
|
freshness, age = session_freshness(row, now)
|
|
@@ -107,6 +128,12 @@ def menu_session(row, now):
|
|
|
107
128
|
"remaining_5h_pct": row.get("remaining_5h_pct"),
|
|
108
129
|
"remaining_week_pct": row.get("remaining_week_pct"),
|
|
109
130
|
"reset_at": row.get("reset_at"),
|
|
131
|
+
# The same wording `cdx status` uses — "in 5h", "passed 3h ago" — rather
|
|
132
|
+
# than a second vocabulary for the same idea. An absolute stamp costs a
|
|
133
|
+
# dozen characters per row and still makes the reader do the subtraction
|
|
134
|
+
# that matters: how long until it comes back.
|
|
135
|
+
"reset_in": _format_reset_time(row.get("reset_at")),
|
|
136
|
+
"updated_ago": _ago(age),
|
|
110
137
|
"active": bool(row.get("active")),
|
|
111
138
|
"state": icon_state_for_pct(row.get("available_pct")),
|
|
112
139
|
"freshness": freshness,
|
package/src/tray_install.py
CHANGED
|
@@ -251,6 +251,34 @@ def install(base_dir, version, download=None, ledger_path=CHECKSUM_LEDGER, targe
|
|
|
251
251
|
return state
|
|
252
252
|
|
|
253
253
|
|
|
254
|
+
def align_companion(base_dir, version, download=None, ledger_path=CHECKSUM_LEDGER, target=None, env=None, probe=None):
|
|
255
|
+
"""Bring an installed companion up to the CDX release that just landed.
|
|
256
|
+
|
|
257
|
+
`item_081` AC2 asks for the two to stay aligned, and until now nothing did
|
|
258
|
+
it: `cdx update` moved CDX and left the companion behind, so the user had to
|
|
259
|
+
notice the drift and reinstall by hand.
|
|
260
|
+
|
|
261
|
+
Returns a dict describing what happened, and never raises. The CDX update
|
|
262
|
+
has already succeeded by the time this runs — failing it because a companion
|
|
263
|
+
could not be replaced would undo a good outcome for a worse reason. Every
|
|
264
|
+
refusal is reported instead.
|
|
265
|
+
"""
|
|
266
|
+
state = read_state(base_dir)
|
|
267
|
+
if not state:
|
|
268
|
+
return {"aligned": False, "reason": "no companion is installed"}
|
|
269
|
+
if state.get("cdx_version") == version:
|
|
270
|
+
return {"aligned": False, "reason": "already aligned"}
|
|
271
|
+
try:
|
|
272
|
+
update(base_dir, version, download=download, ledger_path=ledger_path, target=target, probe=probe)
|
|
273
|
+
except TrayInstallError as error:
|
|
274
|
+
return {
|
|
275
|
+
"aligned": False,
|
|
276
|
+
"reason": str(error),
|
|
277
|
+
"previous": state.get("cdx_version"),
|
|
278
|
+
}
|
|
279
|
+
return {"aligned": True, "previous": state.get("cdx_version"), "version": version}
|
|
280
|
+
|
|
281
|
+
|
|
254
282
|
def _extract(archive, destination):
|
|
255
283
|
"""Unpack, refusing any member that would escape the destination.
|
|
256
284
|
|