xsync-cli 0.1.0__tar.gz → 0.1.1__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.1}/PKG-INFO +1 -1
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/pyproject.toml +1 -1
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/cli.py +26 -1
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/tests/test_cli_profiles.py +17 -0
- xsync_cli-0.1.1/tests/test_package_data.py +46 -0
- xsync_cli-0.1.0/tests/test_package_data.py +0 -18
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/.gitignore +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/LICENSE +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/README.md +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/docs/superpowers/plans/2026-09-13-xsync-cli.md +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/docs/superpowers/specs/2026-09-13-xsync-design.md +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/docs/verification-2026-09-13.md +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/__init__.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/adapters/__init__.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/adapters/codex.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/adapters/codex_config.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/adapters/codex_wiring.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/adapters/prompts/__init__.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/adapters/prompts/generic-codex.md +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/adapters/prompts/generic-codex.messages.json +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/adapters/prompts/gpt-5.4.md +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/adapters/prompts/gpt-5.4.messages.json +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/adapters/prompts/gpt-5.5.md +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/adapters/prompts/gpt-5.5.messages.json +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/adapters/rules/__init__.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/adapters/rules/codex.toml +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/core/__init__.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/core/atomic.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/core/diff.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/core/filters.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/core/model.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/core/profiles.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/sources/__init__.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/sources/openai_compat.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/tests/__init__.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/tests/fixtures/__init__.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/tests/fixtures/ninerouter_models.json +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/tests/fixtures/plain_openai_models.json +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/tests/test_atomic.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/tests/test_cli_codex.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/tests/test_codex_config.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/tests/test_codex_render.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/tests/test_codex_wiring.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/tests/test_diff.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/tests/test_filters.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/tests/test_model.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/tests/test_openai_compat.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/tests/test_profiles.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/tools/extract_prompts.py +0 -0
- {xsync_cli-0.1.0 → xsync_cli-0.1.1}/uv.lock +0 -0
|
@@ -280,12 +280,34 @@ def cmd_codex(args: argparse.Namespace) -> int:
|
|
|
280
280
|
return EXIT_OK
|
|
281
281
|
|
|
282
282
|
|
|
283
|
+
EPILOG = """\
|
|
284
|
+
examples:
|
|
285
|
+
xsync setup make a profile, and read the model list
|
|
286
|
+
xsync list show the profiles. The active one has a star
|
|
287
|
+
xsync use openrouter set the active profile
|
|
288
|
+
xsync codex --init connect Codex to the endpoint of the profile
|
|
289
|
+
xsync codex --dry-run show the difference. Write nothing
|
|
290
|
+
xsync codex sync the models into the Codex catalog
|
|
291
|
+
xsync codex --reset remove everything that xsync wrote
|
|
292
|
+
|
|
293
|
+
files:
|
|
294
|
+
~/.config/xsync/profiles.toml the profiles. `xsync setup` writes this file
|
|
295
|
+
~/.codex/<profile>-models.json the Codex catalog that `xsync codex` writes
|
|
296
|
+
~/.codex/config.toml only `--init` and `--reset` write this file
|
|
297
|
+
|
|
298
|
+
exit codes:
|
|
299
|
+
0 success 1 error 2 the endpoint does not answer
|
|
300
|
+
"""
|
|
301
|
+
|
|
302
|
+
|
|
283
303
|
def build_parser() -> argparse.ArgumentParser:
|
|
284
304
|
parser = argparse.ArgumentParser(
|
|
285
305
|
prog="xsync",
|
|
286
306
|
description="Sync the model list of an OpenAI-compatible endpoint into a harness.",
|
|
307
|
+
epilog=EPILOG,
|
|
308
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
287
309
|
)
|
|
288
|
-
sub = parser.add_subparsers(dest="command",
|
|
310
|
+
sub = parser.add_subparsers(dest="command", metavar="command")
|
|
289
311
|
|
|
290
312
|
sub.add_parser("setup", help="make a profile").set_defaults(func=cmd_setup)
|
|
291
313
|
sub.add_parser("list", help="show the profiles").set_defaults(func=cmd_list)
|
|
@@ -314,6 +336,9 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
314
336
|
def main(argv: list[str] | None = None) -> int:
|
|
315
337
|
parser = build_parser()
|
|
316
338
|
args = parser.parse_args(argv)
|
|
339
|
+
if not getattr(args, "command", None):
|
|
340
|
+
parser.print_help()
|
|
341
|
+
return EXIT_OK
|
|
317
342
|
try:
|
|
318
343
|
return args.func(args)
|
|
319
344
|
except (ProfileError, ConfigError) as error:
|
|
@@ -100,3 +100,20 @@ def test_remove_deletes_the_profile(store_path):
|
|
|
100
100
|
store_path.write_text('[profiles.a]\nbase_url = "http://a/v1"\nwire_api = "chat"\n')
|
|
101
101
|
assert cli.main(["remove", "a"]) == 0
|
|
102
102
|
assert "profiles.a" not in store_path.read_text()
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def test_a_bare_command_shows_the_help(capsys):
|
|
106
|
+
assert cli.main([]) == 0
|
|
107
|
+
out = capsys.readouterr().out
|
|
108
|
+
for name in ("setup", "list", "use", "remove", "codex"):
|
|
109
|
+
assert name in out
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_the_help_names_the_profile_file(capsys):
|
|
113
|
+
cli.main([])
|
|
114
|
+
assert "profiles.toml" in capsys.readouterr().out
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def test_the_help_shows_an_example(capsys):
|
|
118
|
+
cli.main([])
|
|
119
|
+
assert "xsync setup" in capsys.readouterr().out
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from importlib.resources import files
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_the_rules_file_is_package_data():
|
|
5
|
+
text = files("xsync_cli.adapters.rules").joinpath("codex.toml").read_text()
|
|
6
|
+
assert "[defaults]" in text
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_the_generic_prompt_is_package_data():
|
|
10
|
+
text = files("xsync_cli.adapters.prompts").joinpath("generic-codex.md").read_text()
|
|
11
|
+
assert text.startswith("You are Codex")
|
|
12
|
+
assert len(text) > 5000
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_each_named_prompt_exists():
|
|
16
|
+
for name in ("gpt-5.5.md", "gpt-5.4.md"):
|
|
17
|
+
text = files("xsync_cli.adapters.prompts").joinpath(name).read_text()
|
|
18
|
+
assert text.startswith("You are Codex")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_a_built_wheel_holds_every_data_file():
|
|
22
|
+
"""The wheel must carry the rules file and every prompt.
|
|
23
|
+
|
|
24
|
+
The test skips when no wheel exists. Run `uv build` first.
|
|
25
|
+
"""
|
|
26
|
+
import pathlib
|
|
27
|
+
import zipfile
|
|
28
|
+
|
|
29
|
+
import pytest
|
|
30
|
+
|
|
31
|
+
wheels = sorted(pathlib.Path("dist").glob("*.whl"))
|
|
32
|
+
if not wheels:
|
|
33
|
+
pytest.skip("no wheel in dist/. Run `uv build` first.")
|
|
34
|
+
|
|
35
|
+
names = zipfile.ZipFile(wheels[-1]).namelist()
|
|
36
|
+
required = (
|
|
37
|
+
"adapters/rules/codex.toml",
|
|
38
|
+
"adapters/prompts/generic-codex.md",
|
|
39
|
+
"adapters/prompts/gpt-5.5.md",
|
|
40
|
+
"adapters/prompts/gpt-5.4.md",
|
|
41
|
+
"adapters/prompts/generic-codex.messages.json",
|
|
42
|
+
"adapters/prompts/gpt-5.5.messages.json",
|
|
43
|
+
"adapters/prompts/gpt-5.4.messages.json",
|
|
44
|
+
)
|
|
45
|
+
for item in required:
|
|
46
|
+
assert any(item in name for name in names), f"{item} is absent from the wheel"
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
from importlib.resources import files
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
def test_the_rules_file_is_package_data():
|
|
5
|
-
text = files("xsync_cli.adapters.rules").joinpath("codex.toml").read_text()
|
|
6
|
-
assert "[defaults]" in text
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def test_the_generic_prompt_is_package_data():
|
|
10
|
-
text = files("xsync_cli.adapters.prompts").joinpath("generic-codex.md").read_text()
|
|
11
|
-
assert text.startswith("You are Codex")
|
|
12
|
-
assert len(text) > 5000
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def test_each_named_prompt_exists():
|
|
16
|
-
for name in ("gpt-5.5.md", "gpt-5.4.md"):
|
|
17
|
-
text = files("xsync_cli.adapters.prompts").joinpath(name).read_text()
|
|
18
|
-
assert text.startswith("You are Codex")
|
|
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
|
{xsync_cli-0.1.0 → xsync_cli-0.1.1}/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
|
|
File without changes
|