xsync-cli 0.1.2__tar.gz → 0.1.3__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.2 → xsync_cli-0.1.3}/PKG-INFO +2 -2
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/README.md +1 -1
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/pyproject.toml +1 -1
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/src/xsync_cli/adapters/codex.py +6 -4
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/src/xsync_cli/adapters/rules/codex.toml +1 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/src/xsync_cli/cli.py +2 -1
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/tests/test_cli_profiles.py +4 -4
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/tests/test_codex_render.py +33 -1
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/tests/test_term.py +1 -1
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/uv.lock +1 -1
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/.gitignore +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/LICENSE +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/docs/superpowers/plans/2026-09-13-xsync-cli.md +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/docs/superpowers/specs/2026-09-13-xsync-design.md +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/docs/verification-2026-09-13.md +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/src/xsync_cli/__init__.py +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/src/xsync_cli/adapters/__init__.py +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/src/xsync_cli/adapters/codex_config.py +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/src/xsync_cli/adapters/codex_wiring.py +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/src/xsync_cli/adapters/prompts/__init__.py +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/src/xsync_cli/adapters/prompts/generic-codex.md +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/src/xsync_cli/adapters/prompts/generic-codex.messages.json +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/src/xsync_cli/adapters/prompts/gpt-5.4.md +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/src/xsync_cli/adapters/prompts/gpt-5.4.messages.json +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/src/xsync_cli/adapters/prompts/gpt-5.5.md +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/src/xsync_cli/adapters/prompts/gpt-5.5.messages.json +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/src/xsync_cli/adapters/rules/__init__.py +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/src/xsync_cli/core/__init__.py +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/src/xsync_cli/core/atomic.py +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/src/xsync_cli/core/diff.py +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/src/xsync_cli/core/filters.py +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/src/xsync_cli/core/model.py +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/src/xsync_cli/core/profiles.py +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/src/xsync_cli/core/term.py +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/src/xsync_cli/sources/__init__.py +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/src/xsync_cli/sources/openai_compat.py +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/tests/__init__.py +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/tests/fixtures/__init__.py +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/tests/fixtures/ninerouter_models.json +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/tests/fixtures/plain_openai_models.json +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/tests/test_atomic.py +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/tests/test_cli_codex.py +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/tests/test_codex_config.py +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/tests/test_codex_wiring.py +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/tests/test_diff.py +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/tests/test_filters.py +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/tests/test_model.py +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/tests/test_openai_compat.py +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/tests/test_package_data.py +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/tests/test_profiles.py +0 -0
- {xsync_cli-0.1.2 → xsync_cli-0.1.3}/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.3
|
|
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
|
|
@@ -128,7 +128,7 @@ A glob pattern selects a model by name. The sign `*` means any characters.
|
|
|
128
128
|
| Rule | Effect |
|
|
129
129
|
|---|---|
|
|
130
130
|
| `include = []` | Keep every model. This is the default. |
|
|
131
|
-
| `include = ["
|
|
131
|
+
| `include = ["openai/*", "anthropic/*"]` | Keep only the models of those two providers. |
|
|
132
132
|
| `exclude = ["*embedding*"]` | Drop every model with `embedding` in the name. |
|
|
133
133
|
| `exclude = ["*-image-*", "*-tts-*"]` | Drop the image models and the speech models. |
|
|
134
134
|
|
|
@@ -116,7 +116,7 @@ A glob pattern selects a model by name. The sign `*` means any characters.
|
|
|
116
116
|
| Rule | Effect |
|
|
117
117
|
|---|---|
|
|
118
118
|
| `include = []` | Keep every model. This is the default. |
|
|
119
|
-
| `include = ["
|
|
119
|
+
| `include = ["openai/*", "anthropic/*"]` | Keep only the models of those two providers. |
|
|
120
120
|
| `exclude = ["*embedding*"]` | Drop every model with `embedding` in the name. |
|
|
121
121
|
| `exclude = ["*-image-*", "*-tts-*"]` | Drop the image models and the speech models. |
|
|
122
122
|
|
|
@@ -103,7 +103,8 @@ def render_entry(model: Model, rules: Rules) -> dict[str, Any]:
|
|
|
103
103
|
"slug": model.slug,
|
|
104
104
|
"display_name": values.get("display_name") or _display_name(model),
|
|
105
105
|
"description": str(values["description_template"]).format(slug=model.slug),
|
|
106
|
-
|
|
106
|
+
# Codex accepts a string here. A null stops Codex at startup.
|
|
107
|
+
"default_reasoning_level": values["default_reasoning_level"],
|
|
107
108
|
"supported_reasoning_levels": (
|
|
108
109
|
[dict(level) for level in REASONING_LEVELS] if model.reasoning else []
|
|
109
110
|
),
|
|
@@ -127,9 +128,10 @@ def render_entry(model: Model, rules: Rules) -> dict[str, Any]:
|
|
|
127
128
|
"support_verbosity": values["support_verbosity"],
|
|
128
129
|
"default_verbosity": values["default_verbosity"],
|
|
129
130
|
"apply_patch_tool_type": values["apply_patch_tool_type"],
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
131
|
+
# Codex accepts a string or a map here. A null stops Codex at
|
|
132
|
+
# startup. The flag supports_search_tool carries the fact from
|
|
133
|
+
# the endpoint.
|
|
134
|
+
"web_search_tool_type": values["web_search_tool_type"],
|
|
133
135
|
"truncation_policy": values["truncation_policy"],
|
|
134
136
|
"supports_parallel_tool_calls": model.tools,
|
|
135
137
|
"supports_image_detail_original": values["supports_image_detail_original"],
|
|
@@ -20,6 +20,7 @@ shell_type = "shell_command"
|
|
|
20
20
|
supported_in_api = true
|
|
21
21
|
apply_patch_tool_type = "freeform"
|
|
22
22
|
web_search_tool_type = "text_and_image"
|
|
23
|
+
default_reasoning_level = "medium"
|
|
23
24
|
supports_reasoning_summaries = true
|
|
24
25
|
default_reasoning_summary = "none"
|
|
25
26
|
support_verbosity = true
|
|
@@ -125,7 +125,8 @@ def cmd_setup(args: argparse.Namespace) -> int:
|
|
|
125
125
|
)
|
|
126
126
|
)
|
|
127
127
|
include = _ask_list(
|
|
128
|
-
"Include only these",
|
|
128
|
+
"Include only these",
|
|
129
|
+
hint="for example openai/*, *-mini (empty keeps every model)",
|
|
129
130
|
)
|
|
130
131
|
exclude = _ask_list(
|
|
131
132
|
"Exclude these", hint="for example *embedding*, *-image-*"
|
|
@@ -129,7 +129,7 @@ def test_setup_hides_the_key_while_the_user_types(store_path, monkeypatch, capsy
|
|
|
129
129
|
|
|
130
130
|
def fake_getpass(prompt=""):
|
|
131
131
|
asked["prompt"] = prompt
|
|
132
|
-
return "sk-
|
|
132
|
+
return "sk-test-000000000000"
|
|
133
133
|
|
|
134
134
|
answers = iter(["p", "http://h/v1", "", "", "chat"])
|
|
135
135
|
monkeypatch.setattr("builtins.input", lambda prompt="": next(answers))
|
|
@@ -140,14 +140,14 @@ def test_setup_hides_the_key_while_the_user_types(store_path, monkeypatch, capsy
|
|
|
140
140
|
assert "API key" in asked["prompt"]
|
|
141
141
|
|
|
142
142
|
out = capsys.readouterr().out
|
|
143
|
-
assert "sk-
|
|
144
|
-
assert "sk-
|
|
143
|
+
assert "sk-te…" in out
|
|
144
|
+
assert "sk-test-000000000000" not in out
|
|
145
145
|
|
|
146
146
|
|
|
147
147
|
def test_setup_never_shows_the_key_length(store_path, monkeypatch, capsys):
|
|
148
148
|
answers = iter(["p", "http://h/v1", "", "", "chat"])
|
|
149
149
|
monkeypatch.setattr("builtins.input", lambda prompt="": next(answers))
|
|
150
|
-
monkeypatch.setattr(cli, "getpass", lambda prompt="": "sk-
|
|
150
|
+
monkeypatch.setattr(cli, "getpass", lambda prompt="": "sk-test-000000000000")
|
|
151
151
|
monkeypatch.setattr(cli, "fetch_models", fake_fetch(["a/one"]))
|
|
152
152
|
|
|
153
153
|
cli.main(["setup"])
|
|
@@ -45,7 +45,8 @@ def test_a_reasoning_model_gets_four_levels():
|
|
|
45
45
|
def test_a_plain_model_gets_no_level():
|
|
46
46
|
entry = render_entry(model(reasoning=False), RULES)
|
|
47
47
|
assert entry["supported_reasoning_levels"] == []
|
|
48
|
-
|
|
48
|
+
# The level stays a string. Codex stops on a null here.
|
|
49
|
+
assert entry["default_reasoning_level"] == "medium"
|
|
49
50
|
|
|
50
51
|
|
|
51
52
|
def test_a_vision_model_accepts_an_image():
|
|
@@ -180,3 +181,34 @@ def test_only_gpt_5_5_asks_for_the_skills_instructions():
|
|
|
180
181
|
assert render_entry(model(slug="cx/gpt-5.4"), RULES)[
|
|
181
182
|
"include_skills_usage_instructions"
|
|
182
183
|
] is False
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def test_the_search_tool_type_is_never_null():
|
|
187
|
+
"""Codex accepts a string or a map here. A null stops Codex."""
|
|
188
|
+
for search in (True, False):
|
|
189
|
+
entry = render_entry(model(search=search), RULES)
|
|
190
|
+
assert entry["web_search_tool_type"] == "text_and_image"
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def test_the_reasoning_level_is_never_null():
|
|
194
|
+
"""Codex accepts a string here. A null stops Codex."""
|
|
195
|
+
for reasoning in (True, False):
|
|
196
|
+
entry = render_entry(model(reasoning=reasoning), RULES)
|
|
197
|
+
assert isinstance(entry["default_reasoning_level"], str)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def test_only_the_known_fields_may_hold_a_null():
|
|
201
|
+
"""The working catalog holds a null in two fields only."""
|
|
202
|
+
allowed = {"availability_nux", "upgrade"}
|
|
203
|
+
for slug in ("cmc/a/b", "cx/gpt-5.5"):
|
|
204
|
+
for reasoning in (True, False):
|
|
205
|
+
for search in (True, False):
|
|
206
|
+
entry = render_entry(
|
|
207
|
+
model(slug=slug, reasoning=reasoning, search=search), RULES
|
|
208
|
+
)
|
|
209
|
+
nulls = {k for k, v in entry.items() if v is None}
|
|
210
|
+
assert nulls <= allowed, f"{slug} has an unexpected null: {nulls}"
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def test_a_plain_model_still_reports_no_search():
|
|
214
|
+
assert render_entry(model(search=False), RULES)["supports_search_tool"] is False
|
|
@@ -62,7 +62,7 @@ def test_the_width_of_a_styled_string_ignores_the_codes(monkeypatch):
|
|
|
62
62
|
|
|
63
63
|
|
|
64
64
|
def test_mask_key_keeps_the_first_five_characters():
|
|
65
|
-
assert term.mask_key("sk-
|
|
65
|
+
assert term.mask_key("sk-test-000000000000") == "sk-te…"
|
|
66
66
|
|
|
67
67
|
|
|
68
68
|
def test_mask_key_of_a_short_key_shows_no_character():
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{xsync_cli-0.1.2 → xsync_cli-0.1.3}/src/xsync_cli/adapters/prompts/generic-codex.messages.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|