cdx-manager 0.20.12 → 0.20.13

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.20.12-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.20.13-4C8BF5) ![Python](https://img.shields.io/badge/python-3.9%2B-3776AB?logo=python&logoColor=white)
8
8
 
9
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.
10
10
 
@@ -164,7 +164,7 @@ or turn historical tool output into new authorization.
164
164
  - **Disk usage and cleanup.** `cdx disk` reports `CDX_HOME` usage, `cdx disk profiles --candidates` identifies reclaimable profile caches/logs with evidence, and `cdx clean profiles ...` applies explicit cleanup actions.
165
165
  - **Health and repair.** `cdx doctor` inspects dependencies, permissions, and orphan profiles; `cdx repair` plans or applies safe fixes.
166
166
  - **Portable bundles.** `cdx export` / `cdx import` move sessions between machines, with passphrase-encrypted auth data when you ask for it.
167
- - **Update prompts.** Periodic update checks surface `cdx update` directly in the `cdx`, `cdx status`, and launch output when a newer release is available. When `logics-manager` is installed, `cdx` can also suggest `logics-manager self-update`.
167
+ - **Update prompts.** Periodic update checks surface `cdx update` directly in the `cdx`, `cdx status`, and launch output when a newer release is available. When `logics-manager` is installed, `cdx` can also suggest `logics-manager update`.
168
168
  - **Logics viewer shortcut.** `cdx view` opens the Logics browser/focus viewer through `logics-manager view` when the companion CLI is installed. All viewer flags are forwarded: `--lan`, `--lan-rw`, `--focus <ref>`, `--read`, `--port`, `--host`, `--refresh-interval`, `--tls`, `--tls-cert`, `--tls-key`, `--open`, `--no-open`. `cdx view --json` reports availability and update diagnostics without opening the viewer.
169
169
 
170
170
  ---
@@ -504,7 +504,7 @@ cdx power all default
504
504
  cdx model provider:ollama default
505
505
  ```
506
506
 
507
- `--model` maps to Codex `--model`, Claude `--model`, and Ollama `ollama run <model>`. `--power` maps to Codex `model_reasoning_effort` and Claude `--effort`; supported values are `minimal`, `low`, `medium`, `high`, and `xhigh`. `--permission` maps to provider-native permission flags. Codex Fast mode is separate from reasoning effort: `--fast on` opts a Codex session into the Codex Fast service tier, while new sessions, `--fast off`, and default launches force the non-Fast `flex` tier. Use `--power low` when you want low reasoning effort without enabling Codex Fast credits. Existing legacy sessions that stored `fast=on` before this split continue to behave as low effort unless the user explicitly sets `--fast on` again. `--priority` is a 0..100 selector preference used as a tie-breaker after readiness and availability. `--rtk on` injects a launch instruction that encourages assistants to use RTK (`rtk <command>`) for noisy terminal commands when RTK is available, while keeping raw commands for exact output. Logics guidance is auto-enabled when `logics-manager` is available; use `--logics off` to disable that guidance for a session, or `--logics on` to pin it explicitly.
507
+ `--model` maps to Codex `--model`, Claude `--model`, and Ollama `ollama run <model>`. `--power` maps to Codex `model_reasoning_effort` and Claude `--effort`; supported values are `minimal`, `low`, `medium`, `high`, and `xhigh`. `--permission` maps to provider-native permission flags. Codex Fast mode is separate from reasoning effort: `--fast on` opts a Codex session into the Codex Fast service tier, while new sessions, `--fast off`, and default launches force the non-Fast `flex` tier. Use `--power low` when you want low reasoning effort without enabling Codex Fast credits. Existing legacy sessions that stored `fast=on` before this split continue to behave as low effort unless the user explicitly sets `--fast on` again. `--priority` is a 0..100 selector preference used as a tie-breaker after readiness and availability. `--rtk on` adds standing guidance that encourages assistants to use RTK (`rtk <command>`) for noisy terminal commands when RTK is available, while keeping raw commands for exact output. Logics guidance is auto-enabled when `logics-manager` is available; use `--logics off` to disable that guidance for a session, or `--logics on` to pin it explicitly. This guidance is passed as instructions, never as a first message: Claude receives it through `--append-system-prompt` and Codex through `-c developer_instructions=...`, appended after any `developer_instructions` already set in the profile's `config.toml` (on Python older than 3.11 without `tomli`, a profile that sets its own is left untouched and gets no cdx guidance). Launching or resuming a session therefore never submits a turn on your behalf. Antigravity and Ollama have no instruction channel and receive no guidance. If you also pass `--append-system-prompt` through `--extra-args`, Claude receives the option twice; prefer putting your own text in the profile's `CLAUDE.md`.
508
508
 
509
509
  ### Terminal Titles
510
510
 
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.20.12
1
+ 0.20.13
@@ -0,0 +1,32 @@
1
+ # CDX Manager 0.20.13
2
+
3
+ ## Launches no longer open with a message you never typed
4
+
5
+ - RTK and Logics guidance used to be passed as the provider's positional prompt,
6
+ which Claude Code and Codex submit as a user turn: a bare `cdx <session>`
7
+ opened with that guidance shown as your message plus a model reply to it, and
8
+ every resume sent it again as a new message.
9
+ - Claude now receives the guidance through `--append-system-prompt`, and Codex
10
+ through `-c developer_instructions=...`. Launch, resume, and headless runs are
11
+ all covered, and no turn is ever submitted on your behalf.
12
+ - Codex guidance is appended after any `developer_instructions` already set in
13
+ the profile's `config.toml` rather than replacing it. When that value cannot be
14
+ read (Python older than 3.11 without `tomli`), cdx leaves it untouched and adds
15
+ no guidance.
16
+ - Antigravity has no instruction channel and, like Ollama, no longer receives
17
+ the guidance.
18
+ - `cdx doctor --check-provider-flags` now verifies the options used to pass it.
19
+
20
+ ## Logics update hint
21
+
22
+ - The update notice now suggests `logics-manager update` instead of the legacy
23
+ `logics-manager self-update` alias.
24
+
25
+ ## Validation
26
+
27
+ - 1055 Python tests and project lint pass.
28
+ - Verified against real CLIs on macOS, Linux (WSL), and Windows: Claude Code
29
+ (2.1.177, 2.1.283) and Codex CLI (0.156.1, 0.157.1) receive the guidance, and
30
+ interactive launches and resumes no longer add a user turn to the provider
31
+ transcript, where 0.20.12 added one. Codex guidance also survives a resumed
32
+ thread and merges with a profile's own `developer_instructions`.
@@ -441,6 +441,17 @@
441
441
  "x86_64-pc-windows-msvc": "183d28516f764e9a9d494feeab4406de9ad8159330082e12bf88f360f65158ac",
442
442
  "x86_64-unknown-linux-musl": "25274c81ccb3048e4e7c96e7e6fd236bb03486dcf5b429be5c31e8dd6d841c5e"
443
443
  }
444
+ },
445
+ "v0.20.12": {
446
+ "github_tarball_sha256": "463efdea19f037c6e4f04d30d3cc34649ef84e18019f23ecf1752a131437ecba",
447
+ "github_zip_sha256": "43ddd669cac5ab0fc7aef146d18e84670710128bba56ec39911d179f6b98abdf",
448
+ "tray_assets": {
449
+ "aarch64-apple-darwin": "14bef88f478dda41ba8af4fbe54859ff5a9c420ab58cad0168b563d00cfa1385",
450
+ "aarch64-unknown-linux-musl": "0d78ef10ec531a02a43c5bd059b49142721059daf2803e3d36829886b478ab0c",
451
+ "x86_64-apple-darwin": "0ae9b837573e35144a7a9f871f030c5445044ba66e09f1d1958d635c2efb6d8f",
452
+ "x86_64-pc-windows-msvc": "0dad961e2f5e79970645db96ed14ef7a7fcb8b7ce0a4441a06310a3ab16b65a4",
453
+ "x86_64-unknown-linux-musl": "b317cf53c0c502321662538aef083690e7673d789d91d55181b1bcffbb6d0a2f"
454
+ }
444
455
  }
445
456
  }
446
457
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cdx-manager",
3
- "version": "0.20.12",
3
+ "version": "0.20.13",
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.20.12"
7
+ version = "0.20.13"
8
8
  description = "Terminal session manager for Codex and Claude accounts."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
package/src/health.py CHANGED
@@ -14,6 +14,7 @@ from .provider_runtime import (
14
14
  HEADLESS_CODEX_PERMISSION_ARGS,
15
15
  LAUNCH_FEATURE_ARGS,
16
16
  LAUNCH_PERMISSION_ARGS,
17
+ PREFERENCE_INSTRUCTION_ARGS,
17
18
  codex_auth_diagnostic,
18
19
  )
19
20
  from .status_source import _extract_account_identity
@@ -195,6 +196,8 @@ def _mapped_provider_flags(provider):
195
196
  for flag in LAUNCH_FEATURE_ARGS.get(provider, []):
196
197
  if flag.startswith("-"):
197
198
  mappings.setdefault("cdx feature flags", set()).add(flag)
199
+ for flag in PREFERENCE_INSTRUCTION_ARGS.get(provider, []):
200
+ mappings.setdefault("cdx feature flags", set()).add(flag)
198
201
  for permission, args in interactive.items():
199
202
  mappings.setdefault(permission, set()).update(
200
203
  arg for arg in args if arg.startswith("-")
@@ -71,6 +71,15 @@ LAUNCH_FEATURE_ARGS = {
71
71
  PROVIDER_OLLAMA: [OLLAMA_USAGE_FLAG],
72
72
  }
73
73
 
74
+ #: How each provider receives cdx's standing guidance (RTK, logics-manager) as
75
+ #: instructions rather than as a first user message. A provider absent here
76
+ #: gets no guidance. Declared as a table so the provider-flag health check can
77
+ #: verify these options exist in the installed CLI.
78
+ PREFERENCE_INSTRUCTION_ARGS = {
79
+ PROVIDER_CLAUDE: ["--append-system-prompt"],
80
+ PROVIDER_CODEX: ["-c"],
81
+ }
82
+
74
83
  LAUNCH_PERMISSION_ARGS = {
75
84
  PROVIDER_CLAUDE: {
76
85
  "review": ["--permission-mode", "plan"],
@@ -575,19 +584,71 @@ def _logics_enabled(session, env=None):
575
584
  return _logics_manager_available(path=env.get("PATH"))
576
585
 
577
586
 
578
- def _with_launch_preferences(session, initial_prompt=None, env=None):
579
- if session["provider"] == PROVIDER_OLLAMA:
580
- return initial_prompt
587
+ def _launch_preferences(session, env=None):
588
+ """The standing guidance cdx adds to a session, or None.
589
+
590
+ It goes to the provider as instructions, never as the conversation's
591
+ first message: a positional prompt is submitted as a user turn, so a bare
592
+ `cdx <session>` used to open with guidance the user never typed and a
593
+ model reply to it. Ollama has no instruction channel and gets none;
594
+ Antigravity has none either (`--prompt-interactive` is a user turn).
595
+ """
596
+ if session["provider"] not in PREFERENCE_INSTRUCTION_ARGS:
597
+ return None
581
598
  prompts = []
582
599
  if _rtk_enabled(session):
583
600
  prompts.append(RTK_PROMPT)
584
601
  if _logics_enabled(session, env=env):
585
602
  prompts.append(LOGICS_PROMPT)
586
- if not prompts:
587
- return initial_prompt
588
- if initial_prompt:
589
- prompts.append(initial_prompt)
590
- return "\n\n".join(prompts)
603
+ return "\n\n".join(prompts) or None
604
+
605
+
606
+ def _codex_configured_developer_instructions(auth_home):
607
+ """(readable, value) for `developer_instructions` in the profile's config.
608
+
609
+ A `-c developer_instructions=...` override replaces the configured value
610
+ outright, so cdx has to know it to append rather than erase it. `readable`
611
+ is False only when the key is present but cannot be parsed (no TOML reader
612
+ on Python < 3.11 without tomli): the caller then leaves it alone.
613
+ """
614
+ try:
615
+ with open(os.path.join(auth_home, "config.toml"), encoding="utf-8") as handle:
616
+ text = handle.read()
617
+ except OSError:
618
+ return True, None
619
+ if "developer_instructions" not in text:
620
+ return True, None
621
+ try:
622
+ import tomllib
623
+ except ImportError:
624
+ try:
625
+ import tomli as tomllib
626
+ except ImportError:
627
+ return False, None
628
+ try:
629
+ value = tomllib.loads(text).get("developer_instructions")
630
+ except ValueError:
631
+ # Codex refuses an unparsable config itself; there is nothing to keep.
632
+ return True, None
633
+ return True, value if isinstance(value, str) and value.strip() else None
634
+
635
+
636
+ def _launch_preference_args(session, env=None):
637
+ preferences = _launch_preferences(session, env=env)
638
+ _validate_initial_prompt(preferences, label="launch guidance")
639
+ if not preferences:
640
+ return []
641
+ if session["provider"] == PROVIDER_CLAUDE:
642
+ return ["--append-system-prompt", preferences]
643
+ readable, configured = _codex_configured_developer_instructions(_get_auth_home(session))
644
+ if not readable:
645
+ # Better to go without cdx's guidance than to erase the user's own.
646
+ return []
647
+ if configured:
648
+ preferences = f"{configured.rstrip()}\n\n{preferences}"
649
+ # json.dumps yields a valid TOML basic string, which is how codex parses
650
+ # a `-c` value.
651
+ return ["-c", f"developer_instructions={json.dumps(preferences)}"]
591
652
 
592
653
 
593
654
  def _build_launch_spec(session, cwd=None, env_override=None, initial_prompt=None, capture_transcript=True):
@@ -598,8 +659,7 @@ def _build_launch_spec(session, cwd=None, env_override=None, initial_prompt=None
598
659
  for key, value in _shared_tool_cache_overrides(env).items():
599
660
  env.setdefault(key, value)
600
661
  env.update(launch_notify_env(session, notifications_enabled(session)))
601
- initial_prompt = _with_launch_preferences(session, initial_prompt, env=env)
602
- _validate_initial_prompt(initial_prompt)
662
+ preference_args = _launch_preference_args(session, env=env)
603
663
  if session["provider"] == PROVIDER_CLAUDE:
604
664
  launch = session.get("launch") or {}
605
665
  args = ["--name", session["name"]]
@@ -609,6 +669,7 @@ def _build_launch_spec(session, cwd=None, env_override=None, initial_prompt=None
609
669
  if launch.get("model"):
610
670
  args += ["--model", _claude_cli_model(launch["model"])]
611
671
  args += _launch_config_args(session)
672
+ args += preference_args
612
673
  args += _extra_args(session)
613
674
  if initial_prompt:
614
675
  args.append(initial_prompt)
@@ -665,6 +726,7 @@ def _build_launch_spec(session, cwd=None, env_override=None, initial_prompt=None
665
726
  if launch.get("model"):
666
727
  args += ["--model", launch["model"]]
667
728
  args += _launch_config_args(session)
729
+ args += preference_args
668
730
  if initial_prompt:
669
731
  args.append(initial_prompt)
670
732
  return _wrap_launch_with_transcript(session, {
@@ -772,8 +834,7 @@ def _build_resume_spec(session, cwd=None, env_override=None, capture_transcript=
772
834
  if not capability["resumable"]:
773
835
  raise CdxError(f"Provider {session.get('provider')} does not support native resume through cdx.")
774
836
 
775
- resume_prompt = _with_launch_preferences(session, env=env)
776
- _validate_initial_prompt(resume_prompt)
837
+ preference_args = _launch_preference_args(session, env=env)
777
838
 
778
839
  if session["provider"] == PROVIDER_CLAUDE:
779
840
  launch = session.get("launch") or {}
@@ -792,9 +853,8 @@ def _build_resume_spec(session, cwd=None, env_override=None, capture_transcript=
792
853
  if launch.get("model"):
793
854
  args += ["--model", _claude_cli_model(launch["model"])]
794
855
  args += _launch_config_args(session)
856
+ args += preference_args
795
857
  args += _extra_args(session)
796
- if resume_prompt:
797
- args.append(resume_prompt)
798
858
  auth_home = _get_auth_home(session)
799
859
  claude_env = _claude_env(env, auth_home, own_terminal_title=True)
800
860
  oauth_token = _read_claude_launch_oauth_token(auth_home)
@@ -822,9 +882,8 @@ def _build_resume_spec(session, cwd=None, env_override=None, capture_transcript=
822
882
  if launch.get("model"):
823
883
  args += ["--model", launch["model"]]
824
884
  args += _launch_config_args(session)
885
+ args += preference_args
825
886
  args += _extra_args(session)
826
- if resume_prompt:
827
- args.append(resume_prompt)
828
887
  return _wrap_launch_with_transcript(session, {
829
888
  "command": "codex",
830
889
  "args": args,
@@ -835,12 +894,12 @@ def _build_resume_spec(session, cwd=None, env_override=None, capture_transcript=
835
894
  }, capture_transcript=capture_transcript, env=env)
836
895
 
837
896
 
838
- def _validate_initial_prompt(initial_prompt):
897
+ def _validate_initial_prompt(initial_prompt, label="initial_prompt"):
839
898
  if initial_prompt is not None:
840
899
  if not isinstance(initial_prompt, str):
841
- raise CdxError("initial_prompt must be a string.")
900
+ raise CdxError(f"{label} must be a string.")
842
901
  if len(initial_prompt) > 32768:
843
- raise CdxError("initial_prompt exceeds maximum allowed length.")
902
+ raise CdxError(f"{label} exceeds maximum allowed length.")
844
903
 
845
904
 
846
905
  def _build_headless_launch_spec(session, cwd=None, env_override=None, initial_prompt=None):
@@ -850,8 +909,7 @@ def _build_headless_launch_spec(session, cwd=None, env_override=None, initial_pr
850
909
  # Headless runs read the same home, and so the same hooks, as an interactive
851
910
  # launch. Their caller already learns of completion from the return value.
852
911
  env.update(launch_notify_env(session, enabled=False))
853
- initial_prompt = _with_launch_preferences(session, initial_prompt, env=env)
854
- _validate_initial_prompt(initial_prompt)
912
+ preference_args = _launch_preference_args(session, env=env)
855
913
  launch = session.get("launch") or {}
856
914
  power = _launch_power(session)
857
915
  permission = launch.get("permission")
@@ -872,6 +930,7 @@ def _build_headless_launch_spec(session, cwd=None, env_override=None, initial_pr
872
930
  if launch.get("fallback_model"):
873
931
  args += ["--fallback-model", _claude_fallback_model(launch["fallback_model"])]
874
932
  args += _launch_config_args(session)
933
+ args += preference_args
875
934
  args += _extra_args(session)
876
935
  if initial_prompt:
877
936
  args.append(initial_prompt)
@@ -897,6 +956,7 @@ def _build_headless_launch_spec(session, cwd=None, env_override=None, initial_pr
897
956
  args += _codex_fast_config_args(launch)
898
957
  if permission:
899
958
  args += HEADLESS_CODEX_PERMISSION_ARGS.get(permission, [])
959
+ args += preference_args
900
960
  if initial_prompt:
901
961
  args.append(initial_prompt)
902
962
  return {
@@ -229,7 +229,7 @@ def check_logics_manager_for_update(base_dir, env=None, now_fn=None, runner=None
229
229
  "tool": "logics-manager",
230
230
  "latest_version": latest_version,
231
231
  "current_version": current_version,
232
- "update_command": "logics-manager self-update",
232
+ "update_command": "logics-manager update",
233
233
  "url": cached.get("url"),
234
234
  "cached": True,
235
235
  }
@@ -255,7 +255,7 @@ def check_logics_manager_for_update(base_dir, env=None, now_fn=None, runner=None
255
255
  "tool": "logics-manager",
256
256
  "latest_version": latest_version,
257
257
  "current_version": current_version,
258
- "update_command": "logics-manager self-update",
258
+ "update_command": "logics-manager update",
259
259
  "url": payload["url"],
260
260
  "cached": False,
261
261
  }