claude-multiacc 2.0.58 → 2.0.60
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 +125 -4
- package/bin/claude +389 -94
- package/bin/claude-accounts +276 -42
- package/bin/codex +69 -5
- package/bin/codex-accounts +33 -4
- package/docs/ACCOUNT_OPERATIONS.md +62 -2
- package/docs/AUTORESUME-HANDOFF.md +28 -3
- package/docs/AUTORESUME.md +275 -18
- package/docs/CODEX.md +1 -0
- package/docs/INSTALLATION.md +7 -1
- package/docs/VERIFICATION.md +4 -0
- package/lib/__pycache__/audit.cpython-312.pyc +0 -0
- package/lib/__pycache__/autoresume.cpython-312.pyc +0 -0
- package/lib/__pycache__/autoresume_cross.cpython-312.pyc +0 -0
- package/lib/__pycache__/autoresume_cross_dispatch.cpython-312.pyc +0 -0
- package/lib/__pycache__/autoresume_cross_receipt.cpython-312.pyc +0 -0
- package/lib/__pycache__/autoresume_handoff.cpython-312.pyc +0 -0
- package/lib/__pycache__/autoresume_probe.cpython-312.pyc +0 -0
- package/lib/__pycache__/autoresume_receipt.cpython-312.pyc +0 -0
- package/lib/__pycache__/claude_client_error.cpython-312.pyc +0 -0
- package/lib/__pycache__/claude_reset.cpython-312.pyc +0 -0
- package/lib/__pycache__/claude_token_identity.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_autoresume_mode.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_config_edit.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_credit_controls.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_credit_local_scope.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_credit_selection.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_credits.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_limits_lock.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_python.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_trust.cpython-312.pyc +0 -0
- package/lib/__pycache__/codex_usage_fetch.cpython-312.pyc +0 -0
- package/lib/__pycache__/keychain.cpython-312.pyc +0 -0
- package/lib/__pycache__/mcp_oauth.cpython-312.pyc +0 -0
- package/lib/__pycache__/mcp_registry.cpython-312.pyc +0 -0
- package/lib/__pycache__/model_catalog.cpython-312.pyc +0 -0
- package/lib/__pycache__/model_match.cpython-312.pyc +0 -0
- package/lib/__pycache__/report.cpython-312.pyc +0 -0
- package/lib/__pycache__/selector_credit_scope.cpython-312.pyc +0 -0
- package/lib/__pycache__/selector_credits.cpython-312.pyc +0 -0
- package/lib/__pycache__/selector_funding.cpython-312.pyc +0 -0
- package/lib/__pycache__/selector_policy.cpython-312.pyc +0 -0
- package/lib/__pycache__/selector_primitives.cpython-312.pyc +0 -0
- package/lib/__pycache__/session_settings.cpython-312.pyc +0 -0
- package/lib/__pycache__/shim_path.cpython-312.pyc +0 -0
- package/lib/audit.py +88 -22
- package/lib/autoresume.py +1197 -82
- package/lib/autoresume_cross.py +3 -1
- package/lib/autoresume_handoff.py +81 -9
- package/lib/autoresume_probe.py +2 -2
- package/lib/claude_client_error.py +9 -4
- package/lib/codex_trust.py +263 -0
- package/lib/common.sh +146 -3
- package/lib/credential.py +5 -2
- package/lib/install_actions.sh +19 -0
- package/lib/keychain.py +180 -36
- package/lib/mcp_oauth.py +1748 -0
- package/lib/mcp_registry.py +230 -8
- package/lib/model_catalog.json +404 -0
- package/lib/model_catalog.py +201 -0
- package/lib/model_match.py +104 -0
- package/lib/report.py +8 -5
- package/lib/session_settings.py +123 -0
- package/package.json +2 -2
- package/tests/__pycache__/autoresume_cross_receipt_support.cpython-312.pyc +0 -0
- package/tests/__pycache__/autoresume_cross_tmux_environment.cpython-312.pyc +0 -0
- package/tests/__pycache__/autoresume_cross_tmux_support.cpython-312.pyc +0 -0
- package/tests/__pycache__/autoresume_cross_unit_support.cpython-312.pyc +0 -0
- package/tests/__pycache__/autoresume_tmux_support.cpython-312.pyc +0 -0
- package/tests/__pycache__/packaged_command_support.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_catalog.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_catalog_cli.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_codex_records.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_codex_trust.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_claude_holders.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_guards.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_holders.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_origin.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_policy.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_receipt.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_tmux.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_tmux_races.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cross_tokens.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_cursor.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_fallback.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_handoff_argv.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_handoff_history.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_handoff_origins.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_memory.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_memory_tmux.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_mode.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_probe.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_receipt.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_relaunch_settings.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_settings.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_sidechain.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_sidechain_tmux.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_tmux.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_tmux_modes.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_autoresume_trust_tmux.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_claude_reset.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_claude_token_identity.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_credits.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_credits_polling.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_credits_report.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_fetch.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_polling.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_reporting.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_windows.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_selector.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_selector_cached_credits.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_selector_credit_scope.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_selector_credit_shim.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_selector_credits.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_selector_spread.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_token_identity_ceremony.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_token_identity_integration.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_token_identity_retry.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_token_identity_sync.cpython-312.pyc +0 -0
- package/tests/fake_codex_tui.py +25 -0
- package/tests/fake_security.sh +24 -2
- package/tests/fixtures/cross_provider_cli.py +109 -0
- package/tests/run-tests.sh +1171 -23
- package/tests/test_autoresume.py +215 -8
- package/tests/test_autoresume_catalog.py +226 -0
- package/tests/test_autoresume_catalog_cli.py +185 -0
- package/tests/test_autoresume_codex_records.py +16 -0
- package/tests/test_autoresume_codex_trust.py +247 -0
- package/tests/test_autoresume_cross_guards.py +28 -0
- package/tests/test_autoresume_cross_tmux.py +27 -2
- package/tests/test_autoresume_cross_tokens.py +32 -2
- package/tests/test_autoresume_handoff_argv.py +120 -16
- package/tests/test_autoresume_handoff_origins.py +14 -0
- package/tests/test_autoresume_memory.py +1141 -0
- package/tests/test_autoresume_memory_tmux.py +94 -0
- package/tests/test_autoresume_probe.py +2 -1
- package/tests/test_autoresume_relaunch_settings.py +317 -0
- package/tests/test_autoresume_settings.py +135 -0
- package/tests/test_autoresume_sidechain.py +1518 -0
- package/tests/test_autoresume_sidechain_tmux.py +79 -0
- package/tests/test_autoresume_trust_tmux.py +63 -0
- package/tests/test_claude_client_error.py +10 -7
- package/tests/test_claude_org_block.py +1 -0
- package/tests/test_claude_pool_prepare.py +4 -1
- package/tests/test_claude_reset.py +3 -0
- package/tests/test_keychain_nologin.py +340 -0
- package/tests/test_mcp_oauth.py +1414 -0
- package/tests/test_mcp_registry.py +234 -3
- package/tests/test_packaged_commands.py +41 -0
package/tests/fake_codex_tui.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
import os
|
|
5
|
+
import re
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
import select
|
|
7
8
|
import signal
|
|
@@ -50,6 +51,29 @@ def initialize_rollout():
|
|
|
50
51
|
return rollout
|
|
51
52
|
|
|
52
53
|
|
|
54
|
+
def require_resume_trust():
|
|
55
|
+
if os.environ.get("FAKE_REQUIRE_TRUST") != "1" or "resume" not in sys.argv:
|
|
56
|
+
return
|
|
57
|
+
try:
|
|
58
|
+
import tomllib
|
|
59
|
+
except ImportError: # stock python: the same answer from the written line itself
|
|
60
|
+
tomllib = None
|
|
61
|
+
|
|
62
|
+
path = Path(os.environ["CODEX_HOME"]) / "config.toml"
|
|
63
|
+
text = path.read_text() if path.exists() else ""
|
|
64
|
+
if tomllib:
|
|
65
|
+
trusted = tomllib.loads(text).get("projects", {}).get(os.getcwd(), {}).get("trust_level") == "trusted"
|
|
66
|
+
else:
|
|
67
|
+
table = re.escape(json.dumps(os.getcwd()))
|
|
68
|
+
trusted = re.search(r'^\s*\[projects\.' + table + r'\]\s*\n(?:(?!\s*\[).*\n)*?\s*trust_level\s*=\s*"trusted"',
|
|
69
|
+
text, re.M) is not None
|
|
70
|
+
record("trust", trusted=trusted)
|
|
71
|
+
if not trusted:
|
|
72
|
+
print("Folder access\nTrust this folder?\n› 1. Trust and continue\n 2. Quit", flush=True)
|
|
73
|
+
while True:
|
|
74
|
+
time.sleep(0.1)
|
|
75
|
+
|
|
76
|
+
|
|
53
77
|
def main():
|
|
54
78
|
if sys.argv[1:] == ["--help"]:
|
|
55
79
|
print("Usage: codex [OPTIONS]\n" + (" --no-daemon Run locally" if "FAKE_LEGACY" not in os.environ else ""))
|
|
@@ -61,6 +85,7 @@ def main():
|
|
|
61
85
|
settings = termios.tcgetattr(0)
|
|
62
86
|
signal.signal(signal.SIGTERM, stop)
|
|
63
87
|
signal.signal(signal.SIGHUP, stop)
|
|
88
|
+
require_resume_trust()
|
|
64
89
|
rollout = initialize_rollout()
|
|
65
90
|
try:
|
|
66
91
|
tty.setraw(0)
|
package/tests/fake_security.sh
CHANGED
|
@@ -19,7 +19,28 @@
|
|
|
19
19
|
# cannot open the keychain (ssh/tmux/background). FAKE_SECURITY_LOG=<file> appends one
|
|
20
20
|
# line per call — verb, -a name ("-" without -a), whether the secret was asked for —
|
|
21
21
|
# so a test can count calls; the secret and the -X payload are never logged.
|
|
22
|
+
#
|
|
23
|
+
# `security -i` (how Claude Code writes its item, so the secret stays off argv) runs one
|
|
24
|
+
# command per stdin line, its words split the way the tool's own reader splits them
|
|
25
|
+
# (blanks, "double quotes", backslash escapes), and exits with the LAST command's status,
|
|
26
|
+
# as the real tool does. Its reader takes a line into a 4096-byte buffer, so a longer
|
|
27
|
+
# line is refused here (the real tool would cut it into garbage commands) — which is why
|
|
28
|
+
# the client, and lib/keychain.py write_doc, switch to argv above 4032 bytes. Each such
|
|
29
|
+
# command is logged like an argv call, plus ` via=stdin`.
|
|
22
30
|
KC="${FAKE_KEYCHAIN_DIR:-/nonexistent-keychain}"
|
|
31
|
+
if [ "${1:-}" = "-i" ]; then
|
|
32
|
+
rc=0
|
|
33
|
+
while IFS= read -r line || [ -n "$line" ]; do
|
|
34
|
+
[ -n "$line" ] || continue
|
|
35
|
+
if [ "${#line}" -ge 4096 ]; then rc=1; line=""; continue; fi
|
|
36
|
+
words=()
|
|
37
|
+
while IFS= read -r -d '' w; do words+=("$w"); done < <(printf '%s' "$line" \
|
|
38
|
+
| python3 -c 'import shlex, sys; sys.stdout.write("".join(w + "\0" for w in shlex.split(sys.stdin.read())))')
|
|
39
|
+
FAKE_SECURITY_VIA=stdin "$0" ${words[@]+"${words[@]}"} </dev/null; rc=$?
|
|
40
|
+
line=""
|
|
41
|
+
done
|
|
42
|
+
exit "$rc"
|
|
43
|
+
fi
|
|
23
44
|
cmd="${1:-}"; shift || true
|
|
24
45
|
svc=""; acct=""; have_acct=0; want_pw=0; update=0; hexdata=""
|
|
25
46
|
while [ $# -gt 0 ]; do
|
|
@@ -32,8 +53,9 @@ while [ $# -gt 0 ]; do
|
|
|
32
53
|
*) shift ;;
|
|
33
54
|
esac
|
|
34
55
|
done
|
|
35
|
-
[ -n "${FAKE_SECURITY_LOG:-}" ] && printf '%s a=%s w=%s\n' "$cmd" \
|
|
36
|
-
"$([ "$have_acct" = "1" ] && printf '%s' "$acct" || printf -- '-')" "$want_pw"
|
|
56
|
+
[ -n "${FAKE_SECURITY_LOG:-}" ] && printf '%s a=%s w=%s%s\n' "$cmd" \
|
|
57
|
+
"$([ "$have_acct" = "1" ] && printf '%s' "$acct" || printf -- '-')" "$want_pw" \
|
|
58
|
+
"${FAKE_SECURITY_VIA:+ via=$FAKE_SECURITY_VIA}" >> "$FAKE_SECURITY_LOG"
|
|
37
59
|
locked() { [ -n "${FAKE_KEYCHAIN_LOCKED:-}" ]; }
|
|
38
60
|
item_dir() { # the item a lookup names: -a picks its own, no -a picks the oldest
|
|
39
61
|
local d best="" bestseq="" s
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
import os
|
|
5
|
+
import re
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
import select
|
|
7
8
|
import signal
|
|
@@ -14,6 +15,10 @@ import uuid
|
|
|
14
15
|
PROVIDER = Path(sys.argv[0]).name
|
|
15
16
|
HOME = Path(os.environ["FAKE_HOME"])
|
|
16
17
|
SIDS = {"codex": "01a0cb0f-a719-7461-bba6-a59bdf2412cc", "claude": "f8470bf4-f810-4492-b564-1712a7c4930e"}
|
|
18
|
+
# This launch's transcript (claude) or rollout (codex), once it is open.
|
|
19
|
+
HISTORY = None
|
|
20
|
+
# The model/effort a launch without pins runs with; a pinned launch records its own pins.
|
|
21
|
+
DEFAULT_SETTINGS = {"codex": ("gpt-6-astra", "ultra"), "claude": ("claude-fable-5-1", "max")}
|
|
17
22
|
|
|
18
23
|
|
|
19
24
|
def record(kind, **fields):
|
|
@@ -23,14 +28,60 @@ def record(kind, **fields):
|
|
|
23
28
|
|
|
24
29
|
def stop(signum, _frame):
|
|
25
30
|
record("signal", signal=signum)
|
|
31
|
+
if signum == signal.SIGTERM and os.environ.get("FAKE_IGNORE_TERM") == PROVIDER:
|
|
32
|
+
return # a client SIGTERM does not stop (the 8.3GB claude of 2026-10-20): only KILL
|
|
26
33
|
target = os.environ.get("FAKE_PARK_TARGET_ON_TERM")
|
|
27
34
|
if signum == signal.SIGTERM and target in SIDS and target != PROVIDER:
|
|
28
35
|
for account in (HOME / (target + "-pool")).glob("acct-*"):
|
|
29
36
|
(account / ".limited").write_text(
|
|
30
37
|
f"{int(time.time()) + 3600}\nbucket=error-cooldown reason=error-cooldown\n")
|
|
38
|
+
agent = os.environ.get("FAKE_AGENT_LIMIT_ON_TERM")
|
|
39
|
+
if signum == signal.SIGTERM and agent and PROVIDER == "claude" and HISTORY:
|
|
40
|
+
agent_limit(agent)
|
|
31
41
|
raise SystemExit(0)
|
|
32
42
|
|
|
33
43
|
|
|
44
|
+
def agent_limit(agent):
|
|
45
|
+
"""FAKE_AGENT_LIMIT_ON_TERM=<agent id>: one more of this session's workflow agents meets
|
|
46
|
+
the five-hour limit while the client is being stopped, recorded to the millisecond in that
|
|
47
|
+
agent's own transcript, as claude 2.1.303 records it."""
|
|
48
|
+
now = time.time()
|
|
49
|
+
path = (Path(HISTORY).with_suffix("") / "subagents" / "workflows" / "wf_term"
|
|
50
|
+
/ ("agent-" + agent + ".jsonl"))
|
|
51
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
52
|
+
stamp = time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime(now)) + ".%03dZ" % int(now % 1 * 1000)
|
|
53
|
+
rejection = {"type": "assistant", "isSidechain": True, "agentId": agent, "timestamp": stamp,
|
|
54
|
+
"isApiErrorMessage": True, "error": "rate_limit",
|
|
55
|
+
"apiError": "usage_limit_reached", "apiErrorStatus": 429,
|
|
56
|
+
"quotaLimits": {"status": "rejected", "resetsAt": int(now) + 3600,
|
|
57
|
+
"rateLimitType": "five_hour"},
|
|
58
|
+
"message": {"model": "<synthetic>", "role": "assistant", "content": [
|
|
59
|
+
{"type": "text", "text": "You've hit your session limit"}]}}
|
|
60
|
+
with path.open("a") as target:
|
|
61
|
+
target.write(json.dumps(rejection, separators=(",", ":")) + "\n")
|
|
62
|
+
record("agent-limit", agent=agent, timestamp=stamp)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def draw_prompt():
|
|
66
|
+
"""FAKE_MEMORY_BANNER=<provider>: draw the prompt the way claude 2.1.304 does -- its box
|
|
67
|
+
between two rules, its row the mode indicator and a no-break space, the footer below -- a
|
|
68
|
+
first launch with the client's critical-memory banner at the footer's right, a resumed one
|
|
69
|
+
without it. Drawn before the session registry exists, so no watcher can read the screen
|
|
70
|
+
before it is there."""
|
|
71
|
+
if os.environ.get("FAKE_MEMORY_BANNER") != PROVIDER:
|
|
72
|
+
return
|
|
73
|
+
banner = not any(flag in sys.argv[1:] for flag in ("--resume", "-r"))
|
|
74
|
+
rule = "─" * 60
|
|
75
|
+
footer = " ⏵⏵ bypass permissions on"
|
|
76
|
+
if banner:
|
|
77
|
+
footer += (" Critical memory usage (8.3GB) — restart and resume with claude "
|
|
78
|
+
"--continue, or run /compact")
|
|
79
|
+
text = "\n".join(["⏺ Started the release workflows.", "", rule, "❯\xa0", rule, footer])
|
|
80
|
+
sys.stdout.buffer.write((text + "\n").encode("utf-8"))
|
|
81
|
+
sys.stdout.flush()
|
|
82
|
+
record("screen", banner=banner)
|
|
83
|
+
|
|
84
|
+
|
|
34
85
|
def timestamp():
|
|
35
86
|
return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
36
87
|
|
|
@@ -58,6 +109,36 @@ def user_record(sid, prompt):
|
|
|
58
109
|
"message": {"role": "user", "content": prompt}}
|
|
59
110
|
|
|
60
111
|
|
|
112
|
+
def session_settings():
|
|
113
|
+
"""The model and effort this launch asked for (``--model``/``-m``, ``--effort`` or
|
|
114
|
+
``-c model_reasoning_effort=``), else the fixture defaults; never a prompt or a session id."""
|
|
115
|
+
model, effort = DEFAULT_SETTINGS[PROVIDER]
|
|
116
|
+
arguments = sys.argv[1:-1] if len(sys.argv) > 1 and " " in sys.argv[-1] else sys.argv[1:]
|
|
117
|
+
for index, flag in enumerate(arguments):
|
|
118
|
+
value = arguments[index + 1] if index + 1 < len(arguments) else ""
|
|
119
|
+
if flag in ("--model", "-m"):
|
|
120
|
+
model = value
|
|
121
|
+
elif flag.startswith("--model="):
|
|
122
|
+
model = flag.split("=", 1)[1]
|
|
123
|
+
elif flag == "--effort" and PROVIDER == "claude":
|
|
124
|
+
effort = value
|
|
125
|
+
elif flag == "-c" and value.startswith("model_reasoning_effort=") and PROVIDER == "codex":
|
|
126
|
+
effort = value.split("=", 1)[1]
|
|
127
|
+
return model, effort
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def settings_record(sid):
|
|
131
|
+
"""What the native CLI persists about its model: a Codex turn context, a Claude reply."""
|
|
132
|
+
model, effort = session_settings()
|
|
133
|
+
if PROVIDER == "codex":
|
|
134
|
+
return {"timestamp": timestamp(), "type": "turn_context", "payload": {
|
|
135
|
+
"cwd": os.getcwd(), "approval_policy": "on-request", "model": model, "effort": effort}}
|
|
136
|
+
return {"timestamp": timestamp(), "type": "assistant", "sessionId": sid, "cwd": os.getcwd(),
|
|
137
|
+
"isSidechain": False, "uuid": "7c2b5a51-4f0e-4f43-9d1f-5f3f7d0c2a11", "effort": effort,
|
|
138
|
+
"message": {"role": "assistant", "model": model, "type": "message",
|
|
139
|
+
"content": [{"type": "text", "text": "Working on the synthetic task."}]}}
|
|
140
|
+
|
|
141
|
+
|
|
61
142
|
def initialize_history(account, sid):
|
|
62
143
|
if PROVIDER == "codex":
|
|
63
144
|
directory = account / "sessions/2026/10/11"
|
|
@@ -78,6 +159,7 @@ def initialize_history(account, sid):
|
|
|
78
159
|
"cwd": os.getcwd(), "source": os.environ.get("FAKE_CODEX_SOURCE", "cli"),
|
|
79
160
|
"thread_source": "user", "originator": "codex-tui"}}) + "\n")
|
|
80
161
|
history.write(json.dumps(user_record(sid, "Complete the synthetic acceptance task.")) + "\n")
|
|
162
|
+
history.write(json.dumps(settings_record(sid)) + "\n")
|
|
81
163
|
prompt = sys.argv[-1] if len(sys.argv) > 1 and " " in sys.argv[-1] else ""
|
|
82
164
|
if prompt and os.environ.get("FAKE_DROP_PROMPT") != PROVIDER:
|
|
83
165
|
user = user_record(sid, prompt)
|
|
@@ -89,6 +171,29 @@ def initialize_history(account, sid):
|
|
|
89
171
|
return history
|
|
90
172
|
|
|
91
173
|
|
|
174
|
+
def require_codex_trust(account):
|
|
175
|
+
if PROVIDER != "codex" or os.environ.get("FAKE_REQUIRE_TRUST") != "1":
|
|
176
|
+
return
|
|
177
|
+
try:
|
|
178
|
+
import tomllib
|
|
179
|
+
except ImportError: # stock python: the same answer from the written line itself
|
|
180
|
+
tomllib = None
|
|
181
|
+
|
|
182
|
+
path = account / "config.toml"
|
|
183
|
+
text = path.read_text() if path.exists() else ""
|
|
184
|
+
if tomllib:
|
|
185
|
+
trusted = tomllib.loads(text).get("projects", {}).get(os.getcwd(), {}).get("trust_level") == "trusted"
|
|
186
|
+
else:
|
|
187
|
+
table = re.escape(json.dumps(os.getcwd()))
|
|
188
|
+
trusted = re.search(r'^\s*\[projects\.' + table + r'\]\s*\n(?:(?!\s*\[).*\n)*?\s*trust_level\s*=\s*"trusted"',
|
|
189
|
+
text, re.M) is not None
|
|
190
|
+
record("trust", trusted=trusted)
|
|
191
|
+
if not trusted:
|
|
192
|
+
print("Folder access\nTrust this folder?\n› 1. Trust and continue\n 2. Quit", flush=True)
|
|
193
|
+
while True:
|
|
194
|
+
time.sleep(0.1)
|
|
195
|
+
|
|
196
|
+
|
|
92
197
|
def main():
|
|
93
198
|
if sys.argv[1:] == ["--help"]:
|
|
94
199
|
print("Usage: " + PROVIDER + " [OPTIONS]\n --no-daemon Run locally\n --session-id UUID")
|
|
@@ -102,7 +207,11 @@ def main():
|
|
|
102
207
|
settings = termios.tcgetattr(0)
|
|
103
208
|
signal.signal(signal.SIGTERM, stop)
|
|
104
209
|
signal.signal(signal.SIGHUP, stop)
|
|
210
|
+
require_codex_trust(account)
|
|
211
|
+
draw_prompt()
|
|
105
212
|
history = initialize_history(account, sid)
|
|
213
|
+
global HISTORY
|
|
214
|
+
HISTORY = history.name
|
|
106
215
|
try:
|
|
107
216
|
tty.setraw(0)
|
|
108
217
|
record("ready", sid=sid, path=str(Path(history.name).resolve()))
|