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.
Files changed (50) hide show
  1. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/PKG-INFO +1 -1
  2. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/pyproject.toml +1 -1
  3. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/cli.py +26 -1
  4. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/tests/test_cli_profiles.py +17 -0
  5. xsync_cli-0.1.1/tests/test_package_data.py +46 -0
  6. xsync_cli-0.1.0/tests/test_package_data.py +0 -18
  7. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/.gitignore +0 -0
  8. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/LICENSE +0 -0
  9. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/README.md +0 -0
  10. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/docs/superpowers/plans/2026-09-13-xsync-cli.md +0 -0
  11. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/docs/superpowers/specs/2026-09-13-xsync-design.md +0 -0
  12. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/docs/verification-2026-09-13.md +0 -0
  13. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/__init__.py +0 -0
  14. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/adapters/__init__.py +0 -0
  15. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/adapters/codex.py +0 -0
  16. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/adapters/codex_config.py +0 -0
  17. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/adapters/codex_wiring.py +0 -0
  18. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/adapters/prompts/__init__.py +0 -0
  19. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/adapters/prompts/generic-codex.md +0 -0
  20. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/adapters/prompts/generic-codex.messages.json +0 -0
  21. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/adapters/prompts/gpt-5.4.md +0 -0
  22. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/adapters/prompts/gpt-5.4.messages.json +0 -0
  23. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/adapters/prompts/gpt-5.5.md +0 -0
  24. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/adapters/prompts/gpt-5.5.messages.json +0 -0
  25. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/adapters/rules/__init__.py +0 -0
  26. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/adapters/rules/codex.toml +0 -0
  27. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/core/__init__.py +0 -0
  28. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/core/atomic.py +0 -0
  29. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/core/diff.py +0 -0
  30. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/core/filters.py +0 -0
  31. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/core/model.py +0 -0
  32. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/core/profiles.py +0 -0
  33. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/sources/__init__.py +0 -0
  34. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/src/xsync_cli/sources/openai_compat.py +0 -0
  35. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/tests/__init__.py +0 -0
  36. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/tests/fixtures/__init__.py +0 -0
  37. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/tests/fixtures/ninerouter_models.json +0 -0
  38. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/tests/fixtures/plain_openai_models.json +0 -0
  39. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/tests/test_atomic.py +0 -0
  40. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/tests/test_cli_codex.py +0 -0
  41. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/tests/test_codex_config.py +0 -0
  42. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/tests/test_codex_render.py +0 -0
  43. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/tests/test_codex_wiring.py +0 -0
  44. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/tests/test_diff.py +0 -0
  45. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/tests/test_filters.py +0 -0
  46. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/tests/test_model.py +0 -0
  47. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/tests/test_openai_compat.py +0 -0
  48. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/tests/test_profiles.py +0 -0
  49. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/tools/extract_prompts.py +0 -0
  50. {xsync_cli-0.1.0 → xsync_cli-0.1.1}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: xsync-cli
3
- Version: 0.1.0
3
+ Version: 0.1.1
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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "xsync-cli"
7
- version = "0.1.0"
7
+ version = "0.1.1"
8
8
  description = "Sync the model list of an OpenAI-compatible endpoint into the Codex model catalog."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -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", required=True)
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