xsync-cli 0.1.0__tar.gz → 0.1.2__tar.gz
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.
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/PKG-INFO +32 -6
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/README.md +31 -5
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/pyproject.toml +1 -1
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/adapters/codex_config.py +15 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/adapters/codex_wiring.py +5 -5
- xsync_cli-0.1.2/src/xsync_cli/cli.py +541 -0
- xsync_cli-0.1.2/src/xsync_cli/core/term.py +115 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/tests/test_cli_codex.py +67 -1
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/tests/test_cli_profiles.py +80 -6
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/tests/test_codex_config.py +31 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/tests/test_codex_wiring.py +18 -2
- xsync_cli-0.1.2/tests/test_package_data.py +46 -0
- xsync_cli-0.1.2/tests/test_term.py +96 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/uv.lock +1 -1
- xsync_cli-0.1.0/src/xsync_cli/cli.py +0 -321
- xsync_cli-0.1.0/tests/test_package_data.py +0 -18
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/.gitignore +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/LICENSE +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/docs/superpowers/plans/2026-09-13-xsync-cli.md +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/docs/superpowers/specs/2026-09-13-xsync-design.md +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/docs/verification-2026-09-13.md +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/__init__.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/adapters/__init__.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/adapters/codex.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/adapters/prompts/__init__.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/adapters/prompts/generic-codex.md +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/adapters/prompts/generic-codex.messages.json +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/adapters/prompts/gpt-5.4.md +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/adapters/prompts/gpt-5.4.messages.json +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/adapters/prompts/gpt-5.5.md +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/adapters/prompts/gpt-5.5.messages.json +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/adapters/rules/__init__.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/adapters/rules/codex.toml +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/core/__init__.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/core/atomic.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/core/diff.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/core/filters.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/core/model.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/core/profiles.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/sources/__init__.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/sources/openai_compat.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/tests/__init__.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/tests/fixtures/__init__.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/tests/fixtures/ninerouter_models.json +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/tests/fixtures/plain_openai_models.json +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/tests/test_atomic.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/tests/test_codex_render.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/tests/test_diff.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/tests/test_filters.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/tests/test_model.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/tests/test_openai_compat.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/tests/test_profiles.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.2}/tools/extract_prompts.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: xsync-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Sync the model list of an OpenAI-compatible endpoint into the Codex model catalog.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -77,7 +77,8 @@ Run this command again when the endpoint changes.
|
|
|
77
77
|
| `xsync codex --profile <name>` | Use another profile for one run. |
|
|
78
78
|
| `xsync codex --init` | Connect Codex to the endpoint of the profile. |
|
|
79
79
|
| `xsync codex --reset` | Remove everything that xSync wrote. |
|
|
80
|
-
| `xsync codex --reset --force` | Reset when no state file exists. |
|
|
80
|
+
| `xsync codex --reset --force` | Reset when no state file exists. It asks first. |
|
|
81
|
+
| `xsync codex --reset --force --yes` | Reset with no question. Use this in a script. |
|
|
81
82
|
|
|
82
83
|
Exit codes: `0` for success, `1` for an error, `2` when the endpoint does not
|
|
83
84
|
answer.
|
|
@@ -120,8 +121,21 @@ A profile uses `api_key` or `api_key_env`, but not both. Use `api_key_env`
|
|
|
120
121
|
when you share the file. A profile with neither key sends no `Authorization`
|
|
121
122
|
header.
|
|
122
123
|
|
|
123
|
-
The
|
|
124
|
-
|
|
124
|
+
## The filters
|
|
125
|
+
|
|
126
|
+
A glob pattern selects a model by name. The sign `*` means any characters.
|
|
127
|
+
|
|
128
|
+
| Rule | Effect |
|
|
129
|
+
|---|---|
|
|
130
|
+
| `include = []` | Keep every model. This is the default. |
|
|
131
|
+
| `include = ["cx/*", "ed3n/*"]` | Keep only the models of those two providers. |
|
|
132
|
+
| `exclude = ["*embedding*"]` | Drop every model with `embedding` in the name. |
|
|
133
|
+
| `exclude = ["*-image-*", "*-tts-*"]` | Drop the image models and the speech models. |
|
|
134
|
+
|
|
135
|
+
The `exclude` list always wins over the `include` list. Use the filters when
|
|
136
|
+
an endpoint serves many models that you never choose in Codex.
|
|
137
|
+
|
|
138
|
+
The match ignores the letter case.
|
|
125
139
|
|
|
126
140
|
## Safety
|
|
127
141
|
|
|
@@ -136,9 +150,21 @@ list keeps every model. The `exclude` list always wins.
|
|
|
136
150
|
- `--reset` removes only what the state file `~/.codex/.xsync-state.json`
|
|
137
151
|
records. It never restores an old copy of `config.toml`, because Codex
|
|
138
152
|
adds project entries to that file over time.
|
|
153
|
+
- `--reset` keeps the catalog backup `<name>.bak`. The backup is the only
|
|
154
|
+
way back after a reset.
|
|
139
155
|
- With no state file, `--reset` prints the keys that it would remove and
|
|
140
|
-
then stops. Add `--force` to continue.
|
|
141
|
-
|
|
156
|
+
then stops. Add `--force` to continue. The command then asks a question
|
|
157
|
+
before it removes anything. Add `--yes` to answer the question in a
|
|
158
|
+
script.
|
|
159
|
+
- The setup hides the API key while you type it. It then shows the first
|
|
160
|
+
five characters only, and it never shows the length of the key.
|
|
161
|
+
- xSync never prints a whole API key.
|
|
162
|
+
|
|
163
|
+
## The color
|
|
164
|
+
|
|
165
|
+
xSync writes color for a terminal. It writes plain text for a pipe, for a
|
|
166
|
+
dumb terminal, and when `NO_COLOR` holds a value. Set `FORCE_COLOR=1` to
|
|
167
|
+
keep the color in a pipe.
|
|
142
168
|
|
|
143
169
|
## What xSync reads and what it writes
|
|
144
170
|
|
|
@@ -65,7 +65,8 @@ Run this command again when the endpoint changes.
|
|
|
65
65
|
| `xsync codex --profile <name>` | Use another profile for one run. |
|
|
66
66
|
| `xsync codex --init` | Connect Codex to the endpoint of the profile. |
|
|
67
67
|
| `xsync codex --reset` | Remove everything that xSync wrote. |
|
|
68
|
-
| `xsync codex --reset --force` | Reset when no state file exists. |
|
|
68
|
+
| `xsync codex --reset --force` | Reset when no state file exists. It asks first. |
|
|
69
|
+
| `xsync codex --reset --force --yes` | Reset with no question. Use this in a script. |
|
|
69
70
|
|
|
70
71
|
Exit codes: `0` for success, `1` for an error, `2` when the endpoint does not
|
|
71
72
|
answer.
|
|
@@ -108,8 +109,21 @@ A profile uses `api_key` or `api_key_env`, but not both. Use `api_key_env`
|
|
|
108
109
|
when you share the file. A profile with neither key sends no `Authorization`
|
|
109
110
|
header.
|
|
110
111
|
|
|
111
|
-
The
|
|
112
|
-
|
|
112
|
+
## The filters
|
|
113
|
+
|
|
114
|
+
A glob pattern selects a model by name. The sign `*` means any characters.
|
|
115
|
+
|
|
116
|
+
| Rule | Effect |
|
|
117
|
+
|---|---|
|
|
118
|
+
| `include = []` | Keep every model. This is the default. |
|
|
119
|
+
| `include = ["cx/*", "ed3n/*"]` | Keep only the models of those two providers. |
|
|
120
|
+
| `exclude = ["*embedding*"]` | Drop every model with `embedding` in the name. |
|
|
121
|
+
| `exclude = ["*-image-*", "*-tts-*"]` | Drop the image models and the speech models. |
|
|
122
|
+
|
|
123
|
+
The `exclude` list always wins over the `include` list. Use the filters when
|
|
124
|
+
an endpoint serves many models that you never choose in Codex.
|
|
125
|
+
|
|
126
|
+
The match ignores the letter case.
|
|
113
127
|
|
|
114
128
|
## Safety
|
|
115
129
|
|
|
@@ -124,9 +138,21 @@ list keeps every model. The `exclude` list always wins.
|
|
|
124
138
|
- `--reset` removes only what the state file `~/.codex/.xsync-state.json`
|
|
125
139
|
records. It never restores an old copy of `config.toml`, because Codex
|
|
126
140
|
adds project entries to that file over time.
|
|
141
|
+
- `--reset` keeps the catalog backup `<name>.bak`. The backup is the only
|
|
142
|
+
way back after a reset.
|
|
127
143
|
- With no state file, `--reset` prints the keys that it would remove and
|
|
128
|
-
then stops. Add `--force` to continue.
|
|
129
|
-
|
|
144
|
+
then stops. Add `--force` to continue. The command then asks a question
|
|
145
|
+
before it removes anything. Add `--yes` to answer the question in a
|
|
146
|
+
script.
|
|
147
|
+
- The setup hides the API key while you type it. It then shows the first
|
|
148
|
+
five characters only, and it never shows the length of the key.
|
|
149
|
+
- xSync never prints a whole API key.
|
|
150
|
+
|
|
151
|
+
## The color
|
|
152
|
+
|
|
153
|
+
xSync writes color for a terminal. It writes plain text for a pipe, for a
|
|
154
|
+
dumb terminal, and when `NO_COLOR` holds a value. Set `FORCE_COLOR=1` to
|
|
155
|
+
keep the color in a pipe.
|
|
130
156
|
|
|
131
157
|
## What xSync reads and what it writes
|
|
132
158
|
|
|
@@ -68,6 +68,21 @@ def check_provider_match(config: dict[str, Any], profile: Profile) -> str | None
|
|
|
68
68
|
return None
|
|
69
69
|
|
|
70
70
|
|
|
71
|
+
def wire_api_for_url(config: dict[str, Any], base_url: str) -> str | None:
|
|
72
|
+
"""The wire API that Codex already uses for one base URL.
|
|
73
|
+
|
|
74
|
+
The function gives None when no provider block holds that URL.
|
|
75
|
+
"""
|
|
76
|
+
wanted = base_url.rstrip("/")
|
|
77
|
+
for table in (config.get("model_providers") or {}).values():
|
|
78
|
+
if not isinstance(table, dict):
|
|
79
|
+
continue
|
|
80
|
+
if str(table.get("base_url", "")).rstrip("/") == wanted:
|
|
81
|
+
value = table.get("wire_api")
|
|
82
|
+
return str(value) if value else None
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
|
|
71
86
|
def catalog_path_for(profile: Profile, codex_home: Path) -> Path:
|
|
72
87
|
"""The catalog file of one profile."""
|
|
73
88
|
return codex_home / f"{profile.name}-models.json"
|
|
@@ -103,7 +103,11 @@ def init_config(
|
|
|
103
103
|
|
|
104
104
|
|
|
105
105
|
def reset_config(config_path: Path, state: State) -> list[str]:
|
|
106
|
-
"""Remove every item that the state file records.
|
|
106
|
+
"""Remove every item that the state file records.
|
|
107
|
+
|
|
108
|
+
The function keeps the backup file `<name>.bak`. The backup is the
|
|
109
|
+
only way back after a reset.
|
|
110
|
+
"""
|
|
107
111
|
before = file_sha256(config_path)
|
|
108
112
|
document = _load_document(config_path)
|
|
109
113
|
removed: list[str] = []
|
|
@@ -129,9 +133,5 @@ def reset_config(config_path: Path, state: State) -> list[str]:
|
|
|
129
133
|
if path.exists():
|
|
130
134
|
path.unlink()
|
|
131
135
|
removed.append(name)
|
|
132
|
-
backup = path.with_name(path.name + ".bak")
|
|
133
|
-
if backup.exists():
|
|
134
|
-
backup.unlink()
|
|
135
|
-
removed.append(str(backup))
|
|
136
136
|
|
|
137
137
|
return removed
|