cdx-manager 0.2.1 → 0.3.3
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 +34 -8
- package/changelogs/CHANGELOGS_0_3_0.md +32 -0
- package/changelogs/CHANGELOGS_0_3_1.md +27 -0
- package/changelogs/CHANGELOGS_0_3_2.md +21 -0
- package/changelogs/CHANGELOGS_0_3_3.md +27 -0
- package/install.sh +61 -0
- package/package.json +3 -1
- package/pyproject.toml +44 -0
- package/src/cli.py +34 -2
- package/src/notify.py +26 -1
- package/src/provider_runtime.py +27 -5
- package/src/session_store.py +30 -13
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ If you use AI coding tools at scale ; multiple accounts, multiple providers : yo
|
|
|
6
6
|
|
|
7
7
|
One command to launch any session. Zero auth juggling.
|
|
8
8
|
|
|
9
|
-
[](LICENSE) ](LICENSE)  
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
@@ -69,6 +69,38 @@ One command to launch any session. Zero auth juggling.
|
|
|
69
69
|
|
|
70
70
|
### Install
|
|
71
71
|
|
|
72
|
+
From npm:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
npm install -g cdx-manager
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
With pipx:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
pipx install cdx-manager
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
With uv:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
uv tool install cdx-manager
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
With the standalone GitHub installer:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
curl -fsSL https://raw.githubusercontent.com/AlexAgo83/cdx-manager/main/install.sh | sh
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
For a specific version:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
curl -fsSL https://raw.githubusercontent.com/AlexAgo83/cdx-manager/main/install.sh | CDX_VERSION=v0.3.3 sh
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
From source:
|
|
103
|
+
|
|
72
104
|
```bash
|
|
73
105
|
git clone <repo>
|
|
74
106
|
cd cdx-manager
|
|
@@ -83,18 +115,12 @@ To uninstall:
|
|
|
83
115
|
make uninstall
|
|
84
116
|
```
|
|
85
117
|
|
|
86
|
-
Alternatively, for a non-symlinked global install:
|
|
118
|
+
Alternatively, for a non-symlinked global source install:
|
|
87
119
|
|
|
88
120
|
```bash
|
|
89
121
|
npm install -g .
|
|
90
122
|
```
|
|
91
123
|
|
|
92
|
-
Once published to npm:
|
|
93
|
-
|
|
94
|
-
```bash
|
|
95
|
-
npm install -g cdx-manager
|
|
96
|
-
```
|
|
97
|
-
|
|
98
124
|
### Environment
|
|
99
125
|
|
|
100
126
|
By default, `cdx` stores all data under `~/.cdx/`. Override with:
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# CHANGELOGS_0_3_0
|
|
2
|
+
|
|
3
|
+
Release date: 2026-04-16
|
|
4
|
+
|
|
5
|
+
## CDX Manager 0.3.0
|
|
6
|
+
|
|
7
|
+
CDX Manager 0.3.0 adds Python-native and standalone installation paths in addition to npm.
|
|
8
|
+
|
|
9
|
+
### Packaging
|
|
10
|
+
|
|
11
|
+
- Added `pyproject.toml` so the CLI can be installed with `pipx`, `pip`, or `uv tool`.
|
|
12
|
+
- Added the Python console entrypoint `cdx = "src.cli:cli_entry"`.
|
|
13
|
+
- Added `install.sh` for GitHub Release based installs into `~/.local/share/cdx-manager` with a symlink in `~/.local/bin`.
|
|
14
|
+
- Included `install.sh` and `pyproject.toml` in the npm package file list.
|
|
15
|
+
- Documented npm, pipx, uv, curl installer, and source installation paths.
|
|
16
|
+
- Added GitHub Actions automation for PyPI publication when a GitHub Release is published.
|
|
17
|
+
|
|
18
|
+
### Validation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm run lint
|
|
22
|
+
npm test
|
|
23
|
+
python3 -m venv /tmp/cdx-pyinstall
|
|
24
|
+
/tmp/cdx-pyinstall/bin/pip install --no-build-isolation .
|
|
25
|
+
/tmp/cdx-pyinstall/bin/cdx --version
|
|
26
|
+
npm --cache /tmp/cdx-npm-cache publish --dry-run
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Notes
|
|
30
|
+
|
|
31
|
+
- PyPI publication is now technically possible, but still requires PyPI credentials and an explicit publish step.
|
|
32
|
+
- The standalone installer defaults to the latest GitHub Release and supports `CDX_VERSION=vX.Y.Z` for pinned installs.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# CHANGELOGS_0_3_1
|
|
2
|
+
|
|
3
|
+
Release date: 2026-04-16
|
|
4
|
+
|
|
5
|
+
## CDX Manager 0.3.1
|
|
6
|
+
|
|
7
|
+
CDX Manager 0.3.1 is a release-channel synchronization update.
|
|
8
|
+
|
|
9
|
+
### Packaging
|
|
10
|
+
|
|
11
|
+
- Uses npm Trusted Publishing through GitHub Actions OIDC instead of long-lived npm tokens.
|
|
12
|
+
- Keeps npm, PyPI, GitHub Releases, pipx, uv, and the standalone installer aligned on the same release version.
|
|
13
|
+
- Retains the Python-native packaging and standalone install support introduced in 0.3.0.
|
|
14
|
+
|
|
15
|
+
### Validation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm run lint
|
|
19
|
+
npm test
|
|
20
|
+
npm --cache /tmp/cdx-npm-cache publish --dry-run
|
|
21
|
+
python -m build
|
|
22
|
+
python -m twine check dist/*
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Notes
|
|
26
|
+
|
|
27
|
+
- This release exists because the npm Trusted Publishing workflow was added after the `v0.3.0` tag. A fresh release is required for GitHub Actions to run the updated workflow definition for npm publishing.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# CHANGELOGS_0_3_2
|
|
2
|
+
|
|
3
|
+
Release date: 2026-04-16
|
|
4
|
+
|
|
5
|
+
## CDX Manager 0.3.2
|
|
6
|
+
|
|
7
|
+
CDX Manager 0.3.2 updates the npm release workflow to use the npm CLI version required for Trusted Publishing.
|
|
8
|
+
|
|
9
|
+
### Packaging
|
|
10
|
+
|
|
11
|
+
- Runs the npm publish workflow on Node 24.
|
|
12
|
+
- Installs npm 11 before publishing so GitHub Actions OIDC trusted publishing is supported.
|
|
13
|
+
- Keeps npm, PyPI, GitHub Releases, pipx, uv, and the standalone installer aligned on the same release version.
|
|
14
|
+
|
|
15
|
+
### Validation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm run lint
|
|
19
|
+
npm test
|
|
20
|
+
npm --cache /tmp/cdx-npm-cache publish --dry-run
|
|
21
|
+
```
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# CHANGELOGS_0_3_3
|
|
2
|
+
|
|
3
|
+
Release date: 2026-04-16
|
|
4
|
+
|
|
5
|
+
## CDX Manager 0.3.3
|
|
6
|
+
|
|
7
|
+
CDX Manager 0.3.3 adds Windows compatibility across the full codebase.
|
|
8
|
+
|
|
9
|
+
### Windows support
|
|
10
|
+
|
|
11
|
+
- **Session store locking**: replaced `fcntl.flock` (Unix-only) with `msvcrt.locking` on Windows, with `seek(0)` to ensure consistent byte-range locking.
|
|
12
|
+
- **Signal handling**: guarded `signal.SIGHUP` references behind `hasattr` checks — `SIGHUP` does not exist on Windows.
|
|
13
|
+
- **Profile isolation**: added `_home_env_overrides()` helper that sets `USERPROFILE`, `HOMEDRIVE`, and `HOMEPATH` in addition to `HOME` when launching the `claude` CLI on Windows, so Node.js `os.homedir()` resolves to the correct session profile.
|
|
14
|
+
- **Desktop notifications**: `cdx notify` now sends a notification via PowerShell `System.Windows.Forms.MessageBox` on Windows (falls back silently if PowerShell is unavailable).
|
|
15
|
+
- **ANSI colors**: `cli_entry` enables VT processing via `ctypes.windll.kernel32.SetConsoleMode` on Windows so color output works in terminals that support it.
|
|
16
|
+
- **Console encoding**: `cli_entry` reconfigures `stdout`/`stderr` to UTF-8 on Windows to prevent `UnicodeEncodeError` on non-ASCII session names.
|
|
17
|
+
|
|
18
|
+
### Maintenance
|
|
19
|
+
|
|
20
|
+
- Expanded `.gitignore` with standard Python build artifacts (`__pycache__/`, `*.egg-info/`, `dist/`, `build/`), virtual environments, coverage output, and OS-specific files (`.DS_Store`, `Thumbs.db`, `desktop.ini`).
|
|
21
|
+
|
|
22
|
+
### Validation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm run lint
|
|
26
|
+
npm test
|
|
27
|
+
```
|
package/install.sh
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
set -eu
|
|
3
|
+
|
|
4
|
+
REPO="AlexAgo83/cdx-manager"
|
|
5
|
+
VERSION="${CDX_VERSION:-}"
|
|
6
|
+
PREFIX="${PREFIX:-$HOME/.local}"
|
|
7
|
+
BIN_DIR="${BIN_DIR:-$PREFIX/bin}"
|
|
8
|
+
INSTALL_ROOT="${CDX_INSTALL_ROOT:-$PREFIX/share/cdx-manager}"
|
|
9
|
+
|
|
10
|
+
need() {
|
|
11
|
+
if ! command -v "$1" >/dev/null 2>&1; then
|
|
12
|
+
echo "cdx install: missing required command: $1" >&2
|
|
13
|
+
exit 1
|
|
14
|
+
fi
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
need curl
|
|
18
|
+
need tar
|
|
19
|
+
need python3
|
|
20
|
+
|
|
21
|
+
if [ -z "$VERSION" ]; then
|
|
22
|
+
VERSION="$(
|
|
23
|
+
curl -fsSL "https://api.github.com/repos/$REPO/releases/latest" |
|
|
24
|
+
python3 -c 'import json, sys; print(json.load(sys.stdin)["tag_name"])'
|
|
25
|
+
)"
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
case "$VERSION" in
|
|
29
|
+
v*) TAG="$VERSION" ;;
|
|
30
|
+
*) TAG="v$VERSION" ;;
|
|
31
|
+
esac
|
|
32
|
+
|
|
33
|
+
TMP_DIR="$(mktemp -d)"
|
|
34
|
+
cleanup() {
|
|
35
|
+
rm -rf "$TMP_DIR"
|
|
36
|
+
}
|
|
37
|
+
trap cleanup EXIT INT TERM
|
|
38
|
+
|
|
39
|
+
ARCHIVE_URL="https://github.com/$REPO/archive/refs/tags/$TAG.tar.gz"
|
|
40
|
+
curl -fsSL "$ARCHIVE_URL" -o "$TMP_DIR/cdx-manager.tar.gz"
|
|
41
|
+
tar -xzf "$TMP_DIR/cdx-manager.tar.gz" -C "$TMP_DIR"
|
|
42
|
+
|
|
43
|
+
SRC_DIR="$(find "$TMP_DIR" -mindepth 1 -maxdepth 1 -type d | head -n 1)"
|
|
44
|
+
TARGET_DIR="$INSTALL_ROOT/${TAG#v}"
|
|
45
|
+
|
|
46
|
+
mkdir -p "$INSTALL_ROOT" "$BIN_DIR"
|
|
47
|
+
rm -rf "$TARGET_DIR"
|
|
48
|
+
mkdir -p "$TARGET_DIR"
|
|
49
|
+
|
|
50
|
+
cp -R "$SRC_DIR"/. "$TARGET_DIR"/
|
|
51
|
+
chmod +x "$TARGET_DIR/bin/cdx"
|
|
52
|
+
ln -sfn "$TARGET_DIR/bin/cdx" "$BIN_DIR/cdx"
|
|
53
|
+
|
|
54
|
+
echo "Installed cdx $TAG to $TARGET_DIR"
|
|
55
|
+
echo "Linked $BIN_DIR/cdx"
|
|
56
|
+
case ":$PATH:" in
|
|
57
|
+
*":$BIN_DIR:"*) ;;
|
|
58
|
+
*)
|
|
59
|
+
echo "Add $BIN_DIR to PATH to run cdx from anywhere." >&2
|
|
60
|
+
;;
|
|
61
|
+
esac
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cdx-manager",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "Terminal session manager for Codex and Claude accounts.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Alexandre Agostini",
|
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
"bin",
|
|
28
28
|
"changelogs",
|
|
29
29
|
"src",
|
|
30
|
+
"install.sh",
|
|
31
|
+
"pyproject.toml",
|
|
30
32
|
"README.md",
|
|
31
33
|
"LICENSE"
|
|
32
34
|
],
|
package/pyproject.toml
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "cdx-manager"
|
|
7
|
+
version = "0.3.3"
|
|
8
|
+
description = "Terminal session manager for Codex and Claude accounts."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Alexandre Agostini" }
|
|
14
|
+
]
|
|
15
|
+
keywords = ["codex", "claude", "cli", "terminal", "session-manager", "ai-tools"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Environment :: Console",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.9",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Programming Language :: Python :: 3.13",
|
|
26
|
+
"Topic :: Software Development",
|
|
27
|
+
"Topic :: Terminals",
|
|
28
|
+
]
|
|
29
|
+
dependencies = []
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://github.com/AlexAgo83/cdx-manager"
|
|
33
|
+
Repository = "https://github.com/AlexAgo83/cdx-manager.git"
|
|
34
|
+
Issues = "https://github.com/AlexAgo83/cdx-manager/issues"
|
|
35
|
+
Changelog = "https://github.com/AlexAgo83/cdx-manager/tree/main/changelogs"
|
|
36
|
+
|
|
37
|
+
[project.scripts]
|
|
38
|
+
cdx = "cdx_manager.cli:cli_entry"
|
|
39
|
+
|
|
40
|
+
[tool.setuptools]
|
|
41
|
+
packages = ["cdx_manager"]
|
|
42
|
+
|
|
43
|
+
[tool.setuptools.package-dir]
|
|
44
|
+
cdx_manager = "src"
|
package/src/cli.py
CHANGED
|
@@ -39,7 +39,7 @@ from .status_view import (
|
|
|
39
39
|
_format_status_rows,
|
|
40
40
|
)
|
|
41
41
|
|
|
42
|
-
VERSION = "0.
|
|
42
|
+
VERSION = "0.3.3"
|
|
43
43
|
|
|
44
44
|
|
|
45
45
|
# ---------------------------------------------------------------------------
|
|
@@ -180,9 +180,41 @@ def main(argv, options=None):
|
|
|
180
180
|
raise CdxError(f"Unknown command: {command}. Use cdx --help.")
|
|
181
181
|
|
|
182
182
|
|
|
183
|
-
|
|
183
|
+
def _enable_windows_ansi():
|
|
184
|
+
if sys.platform != "win32":
|
|
185
|
+
return
|
|
186
|
+
try:
|
|
187
|
+
import ctypes
|
|
188
|
+
kernel32 = ctypes.windll.kernel32
|
|
189
|
+
for handle_id in (-10, -11, -12): # stdin, stdout, stderr
|
|
190
|
+
handle = kernel32.GetStdHandle(handle_id)
|
|
191
|
+
mode = ctypes.c_ulong()
|
|
192
|
+
if kernel32.GetConsoleMode(handle, ctypes.byref(mode)):
|
|
193
|
+
kernel32.SetConsoleMode(handle, mode.value | 0x0004)
|
|
194
|
+
except Exception:
|
|
195
|
+
pass
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _configure_windows_encoding():
|
|
199
|
+
if sys.platform != "win32":
|
|
200
|
+
return
|
|
201
|
+
for stream in (sys.stdout, sys.stderr):
|
|
202
|
+
try:
|
|
203
|
+
if hasattr(stream, "reconfigure"):
|
|
204
|
+
stream.reconfigure(encoding="utf-8", errors="replace")
|
|
205
|
+
except Exception:
|
|
206
|
+
pass
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def cli_entry():
|
|
210
|
+
_enable_windows_ansi()
|
|
211
|
+
_configure_windows_encoding()
|
|
184
212
|
try:
|
|
185
213
|
raise SystemExit(main(sys.argv[1:]))
|
|
186
214
|
except CdxError as error:
|
|
187
215
|
sys.stderr.write(f"{format_error(error)}\n")
|
|
188
216
|
raise SystemExit(error.exit_code)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
if __name__ == "__main__":
|
|
220
|
+
cli_entry()
|
package/src/notify.py
CHANGED
|
@@ -114,13 +114,38 @@ def _event(ready, title, message, session_name, target_timestamp=None):
|
|
|
114
114
|
|
|
115
115
|
|
|
116
116
|
def send_desktop_notification(title, message, spawn_sync=None, env=None):
|
|
117
|
+
import sys
|
|
117
118
|
spawn_sync = spawn_sync or subprocess.run
|
|
118
119
|
env = env or os.environ
|
|
119
|
-
if
|
|
120
|
+
if sys.platform == "win32":
|
|
121
|
+
_send_windows_notification(title, message, spawn_sync, env)
|
|
122
|
+
elif shutil_which("osascript", env):
|
|
120
123
|
script = f'display notification "{_escape_applescript(message)}" with title "{_escape_applescript(title)}"'
|
|
121
124
|
spawn_sync(["osascript", "-e", script], env=env, capture_output=True, text=True)
|
|
122
125
|
|
|
123
126
|
|
|
127
|
+
def _send_windows_notification(title, message, spawn_sync, env):
|
|
128
|
+
title_escaped = _escape_powershell(title)
|
|
129
|
+
message_escaped = _escape_powershell(message)
|
|
130
|
+
script = (
|
|
131
|
+
"Add-Type -AssemblyName System.Windows.Forms; "
|
|
132
|
+
f"[System.Windows.Forms.MessageBox]::Show('{message_escaped}', '{title_escaped}')"
|
|
133
|
+
)
|
|
134
|
+
try:
|
|
135
|
+
spawn_sync(
|
|
136
|
+
["powershell", "-NoProfile", "-NonInteractive", "-Command", script],
|
|
137
|
+
env=env,
|
|
138
|
+
capture_output=True,
|
|
139
|
+
text=True,
|
|
140
|
+
)
|
|
141
|
+
except (FileNotFoundError, OSError):
|
|
142
|
+
pass
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _escape_powershell(value):
|
|
146
|
+
return str(value).replace("'", "''")
|
|
147
|
+
|
|
148
|
+
|
|
124
149
|
def shutil_which(command, env):
|
|
125
150
|
import shutil
|
|
126
151
|
return shutil.which(command, path=env.get("PATH"))
|
package/src/provider_runtime.py
CHANGED
|
@@ -3,6 +3,7 @@ import os
|
|
|
3
3
|
import signal
|
|
4
4
|
import shlex
|
|
5
5
|
import subprocess
|
|
6
|
+
import sys
|
|
6
7
|
from datetime import datetime, timezone
|
|
7
8
|
|
|
8
9
|
from .errors import CdxError
|
|
@@ -11,6 +12,21 @@ from .errors import CdxError
|
|
|
11
12
|
LOG_ROTATE_BYTES = 10 * 1024 * 1024 # 10 MB
|
|
12
13
|
|
|
13
14
|
|
|
15
|
+
def _home_env_overrides(auth_home):
|
|
16
|
+
"""Return env vars that point the claude CLI to the given home directory.
|
|
17
|
+
|
|
18
|
+
On Unix, only HOME is needed. On Windows, Node.js resolves the home
|
|
19
|
+
directory via USERPROFILE (and falls back to HOMEDRIVE+HOMEPATH), so we
|
|
20
|
+
set all three to ensure profile isolation works regardless of the platform.
|
|
21
|
+
"""
|
|
22
|
+
overrides = {"HOME": auth_home}
|
|
23
|
+
if sys.platform == "win32":
|
|
24
|
+
overrides["USERPROFILE"] = auth_home
|
|
25
|
+
overrides["HOMEDRIVE"] = os.path.splitdrive(auth_home)[0] or "C:"
|
|
26
|
+
overrides["HOMEPATH"] = os.path.splitdrive(auth_home)[1] or auth_home
|
|
27
|
+
return overrides
|
|
28
|
+
|
|
29
|
+
|
|
14
30
|
def _get_auth_home(session):
|
|
15
31
|
return session.get("authHome") or session.get("sessionRoot") or session.get("codexHome", "")
|
|
16
32
|
|
|
@@ -91,7 +107,7 @@ def _build_launch_spec(session, cwd=None, env_override=None):
|
|
|
91
107
|
"args": ["--name", session["name"]],
|
|
92
108
|
"options": {
|
|
93
109
|
"cwd": cwd,
|
|
94
|
-
"env": {**env,
|
|
110
|
+
"env": {**env, **_home_env_overrides(_get_auth_home(session))},
|
|
95
111
|
},
|
|
96
112
|
"label": "claude",
|
|
97
113
|
}
|
|
@@ -108,7 +124,7 @@ def _build_launch_spec(session, cwd=None, env_override=None):
|
|
|
108
124
|
def _build_login_status_spec(session, env_override=None):
|
|
109
125
|
env = {**os.environ, **(env_override or {})}
|
|
110
126
|
if session["provider"] == "claude":
|
|
111
|
-
env
|
|
127
|
+
env.update(_home_env_overrides(_get_auth_home(session)))
|
|
112
128
|
|
|
113
129
|
def parser(output):
|
|
114
130
|
try:
|
|
@@ -133,7 +149,7 @@ def _build_auth_action_spec(session, action, cwd=None, env_override=None):
|
|
|
133
149
|
cwd = cwd or os.getcwd()
|
|
134
150
|
env = {**os.environ, **(env_override or {})}
|
|
135
151
|
if session["provider"] == "claude":
|
|
136
|
-
env
|
|
152
|
+
env.update(_home_env_overrides(_get_auth_home(session)))
|
|
137
153
|
return {"command": "claude", "args": ["auth", action],
|
|
138
154
|
"options": {"cwd": cwd, "env": env}, "label": f"claude auth {action}"}
|
|
139
155
|
env["CODEX_HOME"] = _get_auth_home(session)
|
|
@@ -165,7 +181,10 @@ def _probe_provider_auth(session, spawn_sync=None, env_override=None):
|
|
|
165
181
|
|
|
166
182
|
|
|
167
183
|
def _signal_exit_code(sig):
|
|
168
|
-
|
|
184
|
+
mapping = {signal.SIGINT: 130, signal.SIGTERM: 143}
|
|
185
|
+
if hasattr(signal, "SIGHUP"):
|
|
186
|
+
mapping[signal.SIGHUP] = 129
|
|
187
|
+
return mapping.get(sig, 1)
|
|
169
188
|
|
|
170
189
|
|
|
171
190
|
def _run_interactive_provider_command(session, action, spawn=None, cwd=None,
|
|
@@ -210,7 +229,10 @@ def _run_interactive_provider_command(session, action, spawn=None, cwd=None,
|
|
|
210
229
|
handlers.append((sig, handler))
|
|
211
230
|
signal_emitter.on(sig, handler)
|
|
212
231
|
else:
|
|
213
|
-
|
|
232
|
+
_forward_sigs = [signal.SIGINT, signal.SIGTERM]
|
|
233
|
+
if hasattr(signal, "SIGHUP"):
|
|
234
|
+
_forward_sigs.append(signal.SIGHUP)
|
|
235
|
+
for sig in _forward_sigs:
|
|
214
236
|
try:
|
|
215
237
|
original_handlers[sig] = signal.signal(sig, forward)
|
|
216
238
|
except (OSError, ValueError):
|
package/src/session_store.py
CHANGED
|
@@ -54,23 +54,40 @@ def _fsync_directory(directory):
|
|
|
54
54
|
|
|
55
55
|
@contextmanager
|
|
56
56
|
def _file_lock(lock_path):
|
|
57
|
+
import sys
|
|
57
58
|
_ensure_dir(os.path.dirname(lock_path))
|
|
58
59
|
with open(lock_path, "a", encoding="utf-8") as lock:
|
|
59
|
-
|
|
60
|
-
import
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
60
|
+
if sys.platform == "win32":
|
|
61
|
+
import msvcrt
|
|
62
|
+
try:
|
|
63
|
+
lock.seek(0)
|
|
64
|
+
msvcrt.locking(lock.fileno(), msvcrt.LK_LOCK, 1)
|
|
65
|
+
except OSError as error:
|
|
66
|
+
raise CdxError(f"Failed to lock session store: {error}") from error
|
|
67
|
+
try:
|
|
68
|
+
yield
|
|
69
|
+
finally:
|
|
70
|
+
try:
|
|
71
|
+
lock.seek(0)
|
|
72
|
+
msvcrt.locking(lock.fileno(), msvcrt.LK_UNLCK, 1)
|
|
73
|
+
except OSError as error:
|
|
74
|
+
raise CdxError(f"Failed to unlock session store: {error}") from error
|
|
75
|
+
else:
|
|
70
76
|
try:
|
|
71
|
-
|
|
77
|
+
import fcntl
|
|
78
|
+
except ImportError as error:
|
|
79
|
+
raise CdxError("Session store locking requires fcntl on this platform") from error
|
|
80
|
+
try:
|
|
81
|
+
fcntl.flock(lock.fileno(), fcntl.LOCK_EX)
|
|
72
82
|
except OSError as error:
|
|
73
|
-
raise CdxError(f"Failed to
|
|
83
|
+
raise CdxError(f"Failed to lock session store: {error}") from error
|
|
84
|
+
try:
|
|
85
|
+
yield
|
|
86
|
+
finally:
|
|
87
|
+
try:
|
|
88
|
+
fcntl.flock(lock.fileno(), fcntl.LOCK_UN)
|
|
89
|
+
except OSError as error:
|
|
90
|
+
raise CdxError(f"Failed to unlock session store: {error}") from error
|
|
74
91
|
|
|
75
92
|
|
|
76
93
|
def create_session_store(base_dir):
|