cdx-manager 0.21.3 → 0.21.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 CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  <br clear="left"/>
6
6
 
7
- [![License](https://img.shields.io/badge/license-MIT-4C8BF5)](LICENSE) ![Version](https://img.shields.io/badge/version-v0.21.3-4C8BF5) ![Python](https://img.shields.io/badge/python-3.9%2B-3776AB?logo=python&logoColor=white)
7
+ [![License](https://img.shields.io/badge/license-MIT-4C8BF5)](LICENSE) ![Version](https://img.shields.io/badge/version-v0.21.4-4C8BF5) ![Python](https://img.shields.io/badge/python-3.9%2B-3776AB?logo=python&logoColor=white)
8
8
 
9
9
  **The right AI coding account, when you need it.** CDX Manager keeps your Codex and Claude accounts in separate profiles, shows which ones have quota, and helps you pick the next usable session from your terminal.
10
10
 
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.21.3
1
+ 0.21.4
@@ -0,0 +1,10 @@
1
+ # CDX Manager 0.21.4
2
+
3
+ ## Start a session on a first message
4
+
5
+ - `cdx <name> --prompt TEXT` and `cdx <name> --prompt-file PATH` launch an interactive session that starts on that message, for launcher scripts that open a session already pointed at a task. Both spellings `--flag value` and `--flag=value` work. They are refused before the provider starts when combined with each other, with `--resume` or `cdx resume`, with `--prompt-file -` (the session needs standard input), and for Ollama sessions (`ollama run` answers once and exits). A relative `--prompt-file` path is read from the current directory, not `--dir`. Contributed by Nicolas Rigaudy (#13).
6
+
7
+ ## Fixes
8
+
9
+ - Claude sessions started from inside a running Claude Code session (a hook, or a terminal or script it spawned) saved no transcript: they inherited the parent's session markers (`CLAUDECODE`, `CLAUDE_CODE_CHILD_SESSION`, ...) and ran as a child session, which broke resume and interactive usage backfill. CDX now drops those markers for every Claude spawn; user settings such as `CLAUDE_CODE_DISABLE_*` still pass through. Contributed by Nicolas Rigaudy (#14).
10
+ - A prompt starting with `-` (from `--prompt`, `cdx handoff` or `cdx run`) is now passed after `--`, so Claude, Codex and Ollama no longer read it as an option (for example `-h` printed the provider's help instead of starting).
@@ -507,6 +507,17 @@
507
507
  "x86_64-pc-windows-msvc": "856f9dc459182adb10f66f2836abc862be82feff1cc3f1cf859764336487d781",
508
508
  "x86_64-unknown-linux-musl": "f1832fdbe7114b4625da21d6a3a0f0c100f18d3a441796e809e568620e4cd345"
509
509
  }
510
+ },
511
+ "v0.21.3": {
512
+ "github_tarball_sha256": "4f38562539953131cd2d8aedffea71ba1e7b2d8ccbc899b6521a1ed8c7507cf2",
513
+ "github_zip_sha256": "06e95cf08fcdec0e978c16980f58f282b01daee0513ab8db1ea659b423e58dc6",
514
+ "tray_assets": {
515
+ "aarch64-apple-darwin": "086ec24d2d1ad2820394e74a5f100cec62da1ffe2df91ee470c247f5b1cb9bb0",
516
+ "aarch64-unknown-linux-musl": "0f32a168e097a4d18da86fe0ae028465d5fb43afae6344da39a76463ba99c0ea",
517
+ "x86_64-apple-darwin": "348ec8e5ed89c1181a800ab65b5d736a46b8118d7b83445847049cae95735af8",
518
+ "x86_64-pc-windows-msvc": "9729f245b46420ae4b11c1a50d3ba9cba682854866ef37a3f334dccbdd70ac89",
519
+ "x86_64-unknown-linux-musl": "182fb651dab29a6a21b3b8ef56c196e954fd75230891f8532c21b09ff5fbacee"
520
+ }
510
521
  }
511
522
  }
512
523
  }
package/docs/cli.md CHANGED
@@ -8,6 +8,7 @@
8
8
  | `cdx --json` | List all sessions as a machine-readable JSON payload |
9
9
  | `cdx <name>` | Launch a session (checks auth first) |
10
10
  | `cdx <name> [--json]` | Launch a session; `--json` returns a structured success payload after the interactive run ends |
11
+ | `cdx <name> --prompt TEXT` / `cdx <name> --prompt-file PATH` | Launch a session that starts on this first message (not with `--resume` or Ollama sessions; `--prompt-file -` is refused because the session needs standard input; a relative `--prompt-file` path is read from the current directory, not `--dir`) |
11
12
  | `cdx <name> -r` / `cdx <name> --resume` | Resume the provider-native conversation for a session when supported |
12
13
  | `cdx add [provider] <name> [--model MODEL] [--json]` | Register a new session (`provider`: `codex`, `claude`, `antigravity`, or `ollama`; Ollama requires `--model`) |
13
14
  | `cdx cp <source> <dest> [--json]` | Copy a session into another session name, overwriting the destination if it exists |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cdx-manager",
3
- "version": "0.21.3",
3
+ "version": "0.21.4",
4
4
  "description": "Terminal session manager for Codex and Claude accounts.",
5
5
  "license": "MIT",
6
6
  "author": "Alexandre Agostini",
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cdx-manager"
7
- version = "0.21.3"
7
+ version = "0.21.4"
8
8
  description = "Terminal session manager for Codex and Claude accounts."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
package/src/cli.py CHANGED
@@ -70,6 +70,7 @@ from .cli_render import (
70
70
  _visible_len,
71
71
  format_error,
72
72
  )
73
+ from .config import PROVIDER_OLLAMA
73
74
  from .errors import CdxError
74
75
  from .provider_runtime import (
75
76
  LOG_ROTATE_BYTES,
@@ -288,6 +289,7 @@ def _print_help(use_color=False):
288
289
  f" {_style('cdx set <name> --notify on', '36', use_color)} # agent finishes or needs attention",
289
290
  f" {_style('cdx ready', '36', use_color)} # next session to become ready",
290
291
  f" {_style('cdx <name> [--json]', '36', use_color)}",
292
+ f" {_style('cdx <name> --prompt TEXT|--prompt-file PATH', '36', use_color)} # start with a first message",
291
293
  f" {_style('cdx --help', '36', use_color)}",
292
294
  f" {_style('cdx --version', '36', use_color)}",
293
295
  ])
@@ -623,10 +625,18 @@ def main(argv, options=None):
623
625
  if handler:
624
626
  return handler(rest, ctx)
625
627
 
626
- from .commands.launch import _parse_launch_args
627
- directory, launch_args = _parse_launch_args(rest)
628
+ from .commands.launch import _parse_launch_args, _read_launch_prompt
629
+ directory, launch_args, prompt = _parse_launch_args(rest)
628
630
  if all(arg in ("--json", "-r", "--resume") for arg in launch_args):
629
- return handle_launch(command, ctx, resume=("-r" in launch_args or "--resume" in launch_args), directory=directory)
631
+ resume = "-r" in launch_args or "--resume" in launch_args
632
+ has_prompt = prompt["prompt"] is not None or prompt["prompt_file"] is not None
633
+ if resume and has_prompt:
634
+ raise CdxError("cdx <name>: --prompt and --prompt-file are not supported with --resume.")
635
+ # `ollama run MODEL PROMPT` answers once and exits instead of opening a session.
636
+ if has_prompt and (ctx["service"]["get_session"](command) or {}).get("provider") == PROVIDER_OLLAMA:
637
+ raise CdxError("cdx <name>: --prompt and --prompt-file are not supported for Ollama sessions.")
638
+ return handle_launch(command, ctx, initial_prompt=_read_launch_prompt(prompt), resume=resume,
639
+ directory=directory)
630
640
 
631
641
  raise CdxError(f"Unknown command: {command}. Use cdx --help.")
632
642
 
@@ -30,31 +30,45 @@ from ..provider_runtime import (
30
30
  _get_auth_home,
31
31
  _run_interactive_provider_command,
32
32
  )
33
+ from ..run_command import PROMPT_STDIN, read_run_prompt
33
34
  from ..run_usage import estimate_cost
34
35
 
36
+ LAUNCH_USAGE = "Usage: cdx <name> [--dir PATH] [--prompt TEXT|--prompt-file PATH] [--resume] [--json]"
37
+
35
38
 
36
39
  def _parse_launch_args(args):
37
- """Return the per-launch directory and ordinary launch flags."""
38
- directory = None
40
+ """Return the per-launch directory, initial prompt source and ordinary launch flags."""
41
+ values = {"--dir": None, "--prompt": None, "--prompt-file": None}
39
42
  cleaned = []
40
43
  index = 0
41
44
  while index < len(args):
42
45
  arg = args[index]
43
- if arg == "--dir":
44
- if directory is not None or index + 1 >= len(args):
45
- raise CdxError("Usage: cdx <name> [--dir PATH] [--resume] [--json]")
46
- directory = args[index + 1]
47
- index += 2
48
- continue
49
- if arg.startswith("--dir="):
50
- if directory is not None or not arg.split("=", 1)[1]:
51
- raise CdxError("Usage: cdx <name> [--dir PATH] [--resume] [--json]")
52
- directory = arg.split("=", 1)[1]
46
+ flag, sep, inline = arg.partition("=")
47
+ if flag not in values:
48
+ cleaned.append(arg)
53
49
  index += 1
54
50
  continue
55
- cleaned.append(arg)
56
- index += 1
57
- return directory, cleaned
51
+ if sep:
52
+ value, index = inline, index + 1
53
+ elif index + 1 < len(args):
54
+ value, index = args[index + 1], index + 2
55
+ else:
56
+ raise CdxError(LAUNCH_USAGE)
57
+ if values[flag] is not None or not value:
58
+ raise CdxError(LAUNCH_USAGE)
59
+ values[flag] = value
60
+ if values["--prompt"] is not None and values["--prompt-file"] is not None:
61
+ raise CdxError("cdx <name>: --prompt and --prompt-file are mutually exclusive.")
62
+ if values["--prompt-file"] == PROMPT_STDIN:
63
+ raise CdxError("cdx <name>: --prompt-file - is not supported; the interactive session needs standard input.")
64
+ prompt = {"prompt": values["--prompt"], "prompt_file": values["--prompt-file"]}
65
+ return values["--dir"], cleaned, prompt
66
+
67
+
68
+ def _read_launch_prompt(prompt):
69
+ if prompt["prompt"] is None and prompt["prompt_file"] is None:
70
+ return None
71
+ return read_run_prompt(prompt)
58
72
 
59
73
 
60
74
  def _launch_directory(session, ctx, explicit, json_flag):
@@ -154,7 +168,9 @@ def handle_can_resume(rest, ctx):
154
168
  return 0
155
169
 
156
170
  def handle_resume(rest, ctx):
157
- directory, rest = _parse_launch_args(rest)
171
+ directory, rest, prompt = _parse_launch_args(rest)
172
+ if prompt["prompt"] is not None or prompt["prompt_file"] is not None:
173
+ raise CdxError("cdx resume: --prompt and --prompt-file are not supported when resuming.")
158
174
  json_flag, args = _parse_json_flag(rest)
159
175
  if len(args) != 1:
160
176
  raise CdxError(RESUME_USAGE)
@@ -137,6 +137,21 @@ REDACTED_PROMPT_ARG = "[prompt redacted]"
137
137
  # Documented Claude Code switch that stops it from setting and clearing the
138
138
  # terminal title, leaving cdx's shared title keeper as the only writer.
139
139
  CLAUDE_DISABLE_TERMINAL_TITLE_ENV = "CLAUDE_CODE_DISABLE_TERMINAL_TITLE"
140
+ # Set by a running Claude Code session for its own child processes. When cdx is
141
+ # started from inside one (a hook, or a terminal or script it spawned), the new
142
+ # claude inherits them, treats itself as a child session and turns transcript
143
+ # saving off, which breaks resume and usage backfill.
144
+ CLAUDE_PARENT_SESSION_ENV = (
145
+ "CLAUDECODE",
146
+ "CLAUDE_CODE_CHILD_SESSION",
147
+ "CLAUDE_CODE_SESSION_ID",
148
+ "CLAUDE_CODE_SESSION_ATTENDED",
149
+ "CLAUDE_CODE_ENTRYPOINT",
150
+ "CLAUDE_CODE_EXECPATH",
151
+ "CLAUDE_CODE_MESSAGING_SOCKET",
152
+ "CLAUDE_CODE_MESSAGING_TOKEN",
153
+ "CLAUDE_PID",
154
+ )
140
155
  CLAUDE_CLI_MODEL_ALIASES = {
141
156
  "claude-sonnet": "sonnet",
142
157
  "claude-opus": "opus",
@@ -216,6 +231,8 @@ def _claude_env(base_env, auth_home, own_terminal_title=False):
216
231
  env = {**base_env, **_home_env_overrides(auth_home)}
217
232
  env.pop("CLAUDE_CONFIG_DIR", None)
218
233
  env.pop("CODEX_HOME", None)
234
+ for key in CLAUDE_PARENT_SESSION_ENV:
235
+ env.pop(key, None)
219
236
  env.setdefault("ANTHROPIC_PROFILE", _anthropic_profile_name())
220
237
  if own_terminal_title:
221
238
  env[CLAUDE_DISABLE_TERMINAL_TITLE_ENV] = "1"
@@ -672,7 +689,8 @@ def _build_launch_spec(session, cwd=None, env_override=None, initial_prompt=None
672
689
  args += preference_args
673
690
  args += _extra_args(session)
674
691
  if initial_prompt:
675
- args.append(initial_prompt)
692
+ # `--` keeps a prompt that starts with `-` from being read as an option.
693
+ args += ["--", initial_prompt]
676
694
  auth_home = _get_auth_home(session)
677
695
  claude_env = _claude_env(env, auth_home, own_terminal_title=True)
678
696
  oauth_token = _read_claude_launch_oauth_token(auth_home)
@@ -711,7 +729,8 @@ def _build_launch_spec(session, cwd=None, env_override=None, initial_prompt=None
711
729
  # failed only on the model pull.
712
730
  args = ["run", model] + _launch_config_args(session)
713
731
  if initial_prompt:
714
- args.append(initial_prompt)
732
+ # `--` keeps a prompt that starts with `-` from being read as an option.
733
+ args += ["--", initial_prompt]
715
734
  return _wrap_launch_with_transcript(session, {
716
735
  "command": "ollama",
717
736
  "args": args,
@@ -728,7 +747,8 @@ def _build_launch_spec(session, cwd=None, env_override=None, initial_prompt=None
728
747
  args += _launch_config_args(session)
729
748
  args += preference_args
730
749
  if initial_prompt:
731
- args.append(initial_prompt)
750
+ # `--` keeps a prompt that starts with `-` from being read as an option.
751
+ args += ["--", initial_prompt]
732
752
  return _wrap_launch_with_transcript(session, {
733
753
  "command": "codex",
734
754
  "args": args,
@@ -933,7 +953,8 @@ def _build_headless_launch_spec(session, cwd=None, env_override=None, initial_pr
933
953
  args += preference_args
934
954
  args += _extra_args(session)
935
955
  if initial_prompt:
936
- args.append(initial_prompt)
956
+ # `--` keeps a prompt that starts with `-` from being read as an option.
957
+ args += ["--", initial_prompt]
937
958
  auth_home = _get_auth_home(session)
938
959
  claude_env = _claude_env(env, auth_home)
939
960
  oauth_token = _read_claude_launch_oauth_token(auth_home)
@@ -958,7 +979,8 @@ def _build_headless_launch_spec(session, cwd=None, env_override=None, initial_pr
958
979
  args += HEADLESS_CODEX_PERMISSION_ARGS.get(permission, [])
959
980
  args += preference_args
960
981
  if initial_prompt:
961
- args.append(initial_prompt)
982
+ # `--` keeps a prompt that starts with `-` from being read as an option.
983
+ args += ["--", initial_prompt]
962
984
  return {
963
985
  "command": "codex",
964
986
  "args": args,