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.
Files changed (53) hide show
  1. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/PKG-INFO +32 -6
  2. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/README.md +31 -5
  3. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/pyproject.toml +1 -1
  4. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/adapters/codex_config.py +15 -0
  5. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/adapters/codex_wiring.py +5 -5
  6. xsync_cli-0.1.2/src/xsync_cli/cli.py +541 -0
  7. xsync_cli-0.1.2/src/xsync_cli/core/term.py +115 -0
  8. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/tests/test_cli_codex.py +67 -1
  9. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/tests/test_cli_profiles.py +80 -6
  10. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/tests/test_codex_config.py +31 -0
  11. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/tests/test_codex_wiring.py +18 -2
  12. xsync_cli-0.1.2/tests/test_package_data.py +46 -0
  13. xsync_cli-0.1.2/tests/test_term.py +96 -0
  14. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/uv.lock +1 -1
  15. xsync_cli-0.1.0/src/xsync_cli/cli.py +0 -321
  16. xsync_cli-0.1.0/tests/test_package_data.py +0 -18
  17. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/.gitignore +0 -0
  18. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/LICENSE +0 -0
  19. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/docs/superpowers/plans/2026-09-13-xsync-cli.md +0 -0
  20. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/docs/superpowers/specs/2026-09-13-xsync-design.md +0 -0
  21. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/docs/verification-2026-09-13.md +0 -0
  22. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/__init__.py +0 -0
  23. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/adapters/__init__.py +0 -0
  24. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/adapters/codex.py +0 -0
  25. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/adapters/prompts/__init__.py +0 -0
  26. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/adapters/prompts/generic-codex.md +0 -0
  27. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/adapters/prompts/generic-codex.messages.json +0 -0
  28. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/adapters/prompts/gpt-5.4.md +0 -0
  29. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/adapters/prompts/gpt-5.4.messages.json +0 -0
  30. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/adapters/prompts/gpt-5.5.md +0 -0
  31. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/adapters/prompts/gpt-5.5.messages.json +0 -0
  32. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/adapters/rules/__init__.py +0 -0
  33. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/adapters/rules/codex.toml +0 -0
  34. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/core/__init__.py +0 -0
  35. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/core/atomic.py +0 -0
  36. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/core/diff.py +0 -0
  37. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/core/filters.py +0 -0
  38. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/core/model.py +0 -0
  39. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/core/profiles.py +0 -0
  40. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/sources/__init__.py +0 -0
  41. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/src/xsync_cli/sources/openai_compat.py +0 -0
  42. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/tests/__init__.py +0 -0
  43. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/tests/fixtures/__init__.py +0 -0
  44. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/tests/fixtures/ninerouter_models.json +0 -0
  45. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/tests/fixtures/plain_openai_models.json +0 -0
  46. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/tests/test_atomic.py +0 -0
  47. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/tests/test_codex_render.py +0 -0
  48. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/tests/test_diff.py +0 -0
  49. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/tests/test_filters.py +0 -0
  50. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/tests/test_model.py +0 -0
  51. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/tests/test_openai_compat.py +0 -0
  52. {xsync_cli-0.1.0 → xsync_cli-0.1.2}/tests/test_profiles.py +0 -0
  53. {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.0
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 `include` and `exclude` lists hold glob patterns. An empty `include`
124
- list keeps every model. The `exclude` list always wins.
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
- - xSync never prints an API key.
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 `include` and `exclude` lists hold glob patterns. An empty `include`
112
- list keeps every model. The `exclude` list always wins.
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
- - xSync never prints an API key.
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
 
@@ -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.2"
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"
@@ -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