codex-workspaces 0.2.0__tar.gz → 0.3.0__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.
- {codex_workspaces-0.2.0 → codex_workspaces-0.3.0}/CHANGELOG.md +2 -1
- {codex_workspaces-0.2.0/src/codex_workspaces.egg-info → codex_workspaces-0.3.0}/PKG-INFO +11 -1
- {codex_workspaces-0.2.0 → codex_workspaces-0.3.0}/README.MD +10 -0
- {codex_workspaces-0.2.0 → codex_workspaces-0.3.0}/README.zh-CN.md +10 -0
- {codex_workspaces-0.2.0 → codex_workspaces-0.3.0}/docs/RELEASE.zh-CN.md +2 -2
- {codex_workspaces-0.2.0 → codex_workspaces-0.3.0}/docs/TESTING.zh-CN.md +1 -1
- {codex_workspaces-0.2.0 → codex_workspaces-0.3.0}/pyproject.toml +1 -1
- {codex_workspaces-0.2.0 → codex_workspaces-0.3.0}/src/codex_workspaces/__init__.py +1 -1
- {codex_workspaces-0.2.0 → codex_workspaces-0.3.0}/src/codex_workspaces/cli.py +24 -0
- {codex_workspaces-0.2.0 → codex_workspaces-0.3.0}/src/codex_workspaces/core.py +122 -0
- {codex_workspaces-0.2.0 → codex_workspaces-0.3.0/src/codex_workspaces.egg-info}/PKG-INFO +11 -1
- {codex_workspaces-0.2.0 → codex_workspaces-0.3.0}/tests/test_cli.py +14 -0
- {codex_workspaces-0.2.0 → codex_workspaces-0.3.0}/tests/test_core.py +81 -3
- {codex_workspaces-0.2.0 → codex_workspaces-0.3.0}/MANIFEST.in +0 -0
- {codex_workspaces-0.2.0 → codex_workspaces-0.3.0}/docs/DESIGN.zh-CN.md +0 -0
- {codex_workspaces-0.2.0 → codex_workspaces-0.3.0}/setup.cfg +0 -0
- {codex_workspaces-0.2.0 → codex_workspaces-0.3.0}/src/codex_workspaces/__main__.py +0 -0
- {codex_workspaces-0.2.0 → codex_workspaces-0.3.0}/src/codex_workspaces/config.py +0 -0
- {codex_workspaces-0.2.0 → codex_workspaces-0.3.0}/src/codex_workspaces/errors.py +0 -0
- {codex_workspaces-0.2.0 → codex_workspaces-0.3.0}/src/codex_workspaces/platforms.py +0 -0
- {codex_workspaces-0.2.0 → codex_workspaces-0.3.0}/src/codex_workspaces/stats.py +0 -0
- {codex_workspaces-0.2.0 → codex_workspaces-0.3.0}/src/codex_workspaces/store.py +0 -0
- {codex_workspaces-0.2.0 → codex_workspaces-0.3.0}/src/codex_workspaces.egg-info/SOURCES.txt +0 -0
- {codex_workspaces-0.2.0 → codex_workspaces-0.3.0}/src/codex_workspaces.egg-info/dependency_links.txt +0 -0
- {codex_workspaces-0.2.0 → codex_workspaces-0.3.0}/src/codex_workspaces.egg-info/entry_points.txt +0 -0
- {codex_workspaces-0.2.0 → codex_workspaces-0.3.0}/src/codex_workspaces.egg-info/requires.txt +0 -0
- {codex_workspaces-0.2.0 → codex_workspaces-0.3.0}/src/codex_workspaces.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ All notable changes to `codex-workspaces` will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
This project follows a simple changelog format while it is still pre-release.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## 0.3.0 - 2026-07-04
|
|
8
8
|
|
|
9
9
|
### Added
|
|
10
10
|
|
|
@@ -18,6 +18,7 @@ This project follows a simple changelog format while it is still pre-release.
|
|
|
18
18
|
- Added the unified `~/.codex-workspaces/` root with workspace metadata, account snapshots, and default-account restore behavior.
|
|
19
19
|
- Added legacy workspace migration with `migrate`, `migrate --dry-run`, and `init <workspace> --migrate-current`.
|
|
20
20
|
- Added legacy account import with `accounts import-legacy` and workspace auth import with `accounts import-workspaces`.
|
|
21
|
+
- Added account `rename`, guarded `delete --force`, and account `note` management.
|
|
21
22
|
- Added `pyproject.toml`, package metadata, editable install support, and PyPI-ready build configuration.
|
|
22
23
|
- Added GitHub Actions CI for Linux, macOS, Windows, and Python 3.9/3.11/3.13.
|
|
23
24
|
- Added GitHub Actions Trusted Publishing workflow for PyPI releases.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codex-workspaces
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Cross-platform Codex workspace switcher with a preserved macOS shell workflow.
|
|
5
5
|
Author: blockchain-project-lives
|
|
6
6
|
Project-URL: Homepage, https://github.com/blockchain-project-lives/codex-workspaces
|
|
@@ -190,6 +190,16 @@ codex-workspaces accounts restore-default
|
|
|
190
190
|
|
|
191
191
|
`auth.json` contains credentials. Do not commit workspace directories, account snapshots, SQLite state, sessions, or shell snapshots to git; the project `.gitignore` excludes these patterns for local checkouts.
|
|
192
192
|
|
|
193
|
+
Manage account metadata and snapshots:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
codex-workspaces accounts note acct_research "lab account"
|
|
197
|
+
codex-workspaces accounts rename acct_research acct_lab
|
|
198
|
+
codex-workspaces accounts delete acct_lab --force
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Account deletion always requires `--force` and refuses to delete an account that is still configured as any workspace's default account.
|
|
202
|
+
|
|
193
203
|
Manage workspace metadata and lifecycle:
|
|
194
204
|
|
|
195
205
|
```bash
|
|
@@ -162,6 +162,16 @@ codex-workspaces accounts restore-default
|
|
|
162
162
|
|
|
163
163
|
`auth.json` contains credentials. Do not commit workspace directories, account snapshots, SQLite state, sessions, or shell snapshots to git; the project `.gitignore` excludes these patterns for local checkouts.
|
|
164
164
|
|
|
165
|
+
Manage account metadata and snapshots:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
codex-workspaces accounts note acct_research "lab account"
|
|
169
|
+
codex-workspaces accounts rename acct_research acct_lab
|
|
170
|
+
codex-workspaces accounts delete acct_lab --force
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Account deletion always requires `--force` and refuses to delete an account that is still configured as any workspace's default account.
|
|
174
|
+
|
|
165
175
|
Manage workspace metadata and lifecycle:
|
|
166
176
|
|
|
167
177
|
```bash
|
|
@@ -162,6 +162,16 @@ codex-workspaces accounts restore-default
|
|
|
162
162
|
|
|
163
163
|
`auth.json` 包含认证凭据,不要提交到 git。工作区目录、账号快照、SQLite 状态、sessions 和 shell snapshots 已在本项目 `.gitignore` 中排除。
|
|
164
164
|
|
|
165
|
+
管理账号备注和快照生命周期:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
codex-workspaces accounts note acct_research "实验室账号"
|
|
169
|
+
codex-workspaces accounts rename acct_research acct_lab
|
|
170
|
+
codex-workspaces accounts delete acct_lab --force
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
删除账号始终需要 `--force`;如果某个工作区仍把该账号设为默认账号,删除会被拒绝。
|
|
174
|
+
|
|
165
175
|
管理工作区备注和生命周期:
|
|
166
176
|
|
|
167
177
|
```bash
|
|
@@ -88,9 +88,9 @@ permissions:
|
|
|
88
88
|
1. 更新版本号和 `CHANGELOG.md`。
|
|
89
89
|
2. 本地执行测试和构建检查。
|
|
90
90
|
3. 合并到 `main`。
|
|
91
|
-
4. 创建 Git tag,例如 `v0.
|
|
91
|
+
4. 创建 Git tag,例如 `v0.3.0`,触发 `Publish to TestPyPI`。
|
|
92
92
|
5. 确认 TestPyPI 上传和安装正常。
|
|
93
|
-
6. 从同一个提交创建并推送正式发布分支,例如 `release/v0.
|
|
93
|
+
6. 从同一个提交创建并推送正式发布分支,例如 `release/v0.3.0`,触发 `Publish to PyPI`。
|
|
94
94
|
7. 如果 `pypi` Environment 配置了 Required reviewers,在 GitHub Actions 里批准部署。
|
|
95
95
|
8. 在 PyPI 页面确认 wheel、sdist 和 README 渲染正常。
|
|
96
96
|
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
- 文件系统行为:初始化统一目录工作区、切换链接、拒绝覆盖真实目录。
|
|
8
8
|
- CLI 行为:命令别名、工作区名快捷切换、错误路径、帮助输出。
|
|
9
9
|
- 管理行为:诊断、列表元信息、重命名、删除保护、备注读写和账号绑定。
|
|
10
|
-
-
|
|
10
|
+
- 账号行为:账号快照保存、列表、备注、重命名、删除保护、临时切换、默认账号恢复、默认账号设置。
|
|
11
11
|
- 迁移行为:旧 `~/.codex-<name>` 工作区迁移、旧 `~/.codex-accounts` 导入、dry-run 不落盘、迁移前备份。
|
|
12
12
|
- 统计行为:只读 `state_*.sqlite`,汇总 token、模型、最近会话和每日用量。
|
|
13
13
|
- 平台行为:macOS App 控制可注入,非 macOS 自动跳过 App 启停,Codex 内置 Terminal 阻止或转交危险操作。
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "codex-workspaces"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "Cross-platform Codex workspace switcher with a preserved macOS shell workflow."
|
|
9
9
|
readme = "README.MD"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -231,6 +231,30 @@ def run_accounts(args: Sequence[str], manager: WorkspaceManager) -> int:
|
|
|
231
231
|
)
|
|
232
232
|
manager.accounts_set_default(positional[0], positional[1], activate)
|
|
233
233
|
return 0
|
|
234
|
+
if command in {"rename", "mv"}:
|
|
235
|
+
if len(rest) != 2:
|
|
236
|
+
manager.fail(
|
|
237
|
+
"用法: codex-workspaces accounts rename <旧账号> <新账号>",
|
|
238
|
+
"Usage: codex-workspaces accounts rename <old-account> <new-account>",
|
|
239
|
+
)
|
|
240
|
+
manager.accounts_rename(rest[0], rest[1])
|
|
241
|
+
return 0
|
|
242
|
+
if command in {"delete", "remove", "rm"}:
|
|
243
|
+
if not rest:
|
|
244
|
+
manager.fail(
|
|
245
|
+
"用法: codex-workspaces accounts delete <账号> --force",
|
|
246
|
+
"Usage: codex-workspaces accounts delete <account> --force",
|
|
247
|
+
)
|
|
248
|
+
manager.accounts_delete(rest[0], rest[1:])
|
|
249
|
+
return 0
|
|
250
|
+
if command == "note":
|
|
251
|
+
if not rest:
|
|
252
|
+
manager.fail(
|
|
253
|
+
"用法: codex-workspaces accounts note <账号> [备注文本|--clear]",
|
|
254
|
+
"Usage: codex-workspaces accounts note <account> [note text|--clear]",
|
|
255
|
+
)
|
|
256
|
+
manager.accounts_note(rest[0], rest[1:])
|
|
257
|
+
return 0
|
|
234
258
|
if command == "import-workspaces":
|
|
235
259
|
if rest:
|
|
236
260
|
manager.fail(f"未知参数: {rest[0]}", f"Unknown option: {rest[0]}")
|
|
@@ -1139,6 +1139,122 @@ class WorkspaceManager:
|
|
|
1139
1139
|
self.store.write_account_meta(account_meta)
|
|
1140
1140
|
self.info(self.message(f"已设置默认账号: {clean_name} -> {account_id}", f"Set default account: {clean_name} -> {account_id}"))
|
|
1141
1141
|
|
|
1142
|
+
def workspace_account_references(self, account_id: str) -> tuple[list[str], list[str]]:
|
|
1143
|
+
default_refs: list[str] = []
|
|
1144
|
+
active_refs: list[str] = []
|
|
1145
|
+
for directory in self.workspace_dirs():
|
|
1146
|
+
name = strip_workspace_name(str(directory))
|
|
1147
|
+
meta = self.store.ensure_workspace_meta(name, directory)
|
|
1148
|
+
if meta.default_account_id == account_id:
|
|
1149
|
+
default_refs.append(name)
|
|
1150
|
+
if meta.active_account_id == account_id:
|
|
1151
|
+
active_refs.append(name)
|
|
1152
|
+
return default_refs, active_refs
|
|
1153
|
+
|
|
1154
|
+
def accounts_rename(self, old_account: str, new_account: str) -> None:
|
|
1155
|
+
self.store.ensure_layout()
|
|
1156
|
+
old_id = self.account_id_from_input(old_account)
|
|
1157
|
+
new_id = self.account_id_from_input(new_account)
|
|
1158
|
+
if old_id == new_id:
|
|
1159
|
+
self.fail("新旧账号名相同。", "Old and new account names are the same.")
|
|
1160
|
+
old_dir = self.store.account_dir(old_id)
|
|
1161
|
+
new_dir = self.store.account_dir(new_id)
|
|
1162
|
+
if not self.store.account_meta_path(old_id).is_file():
|
|
1163
|
+
self.fail(f"账号不存在: {old_id}", f"Account not found: {old_id}\nHint: run `codex-workspaces accounts list`")
|
|
1164
|
+
if new_dir.exists():
|
|
1165
|
+
self.fail(f"目标账号已存在: {new_id}", f"Target account already exists: {new_id}")
|
|
1166
|
+
|
|
1167
|
+
with self.store.lock():
|
|
1168
|
+
old_dir.rename(new_dir)
|
|
1169
|
+
meta = self.store.read_account_meta(new_id)
|
|
1170
|
+
meta.id = new_id
|
|
1171
|
+
meta.name = self.account_name_from_input(new_account)
|
|
1172
|
+
meta.updated_at = iso_now()
|
|
1173
|
+
self.store.write_account_meta(meta)
|
|
1174
|
+
old_meta_path = self.store.account_meta_path(old_id)
|
|
1175
|
+
if old_meta_path.exists():
|
|
1176
|
+
old_meta_path.unlink()
|
|
1177
|
+
|
|
1178
|
+
for directory in self.workspace_dirs():
|
|
1179
|
+
name = strip_workspace_name(str(directory))
|
|
1180
|
+
workspace_meta = self.store.ensure_workspace_meta(name, directory)
|
|
1181
|
+
changed = False
|
|
1182
|
+
if workspace_meta.default_account_id == old_id:
|
|
1183
|
+
workspace_meta.default_account_id = new_id
|
|
1184
|
+
changed = True
|
|
1185
|
+
if workspace_meta.active_account_id == old_id:
|
|
1186
|
+
workspace_meta.active_account_id = new_id
|
|
1187
|
+
changed = True
|
|
1188
|
+
if changed:
|
|
1189
|
+
workspace_meta.updated_at = iso_now()
|
|
1190
|
+
self.store.write_workspace_meta(directory, workspace_meta)
|
|
1191
|
+
self.info(self.message(f"已重命名账号: {old_id} -> {new_id}", f"Renamed account: {old_id} -> {new_id}"))
|
|
1192
|
+
|
|
1193
|
+
def accounts_delete(self, account: str, args: Sequence[str]) -> None:
|
|
1194
|
+
self.store.ensure_layout()
|
|
1195
|
+
force = False
|
|
1196
|
+
for arg in args:
|
|
1197
|
+
if arg == "--force":
|
|
1198
|
+
force = True
|
|
1199
|
+
else:
|
|
1200
|
+
self.fail(f"未知参数: {arg}", f"Unknown option: {arg}")
|
|
1201
|
+
if not force:
|
|
1202
|
+
self.fail(
|
|
1203
|
+
"删除账号需要 --force,避免误删认证快照。",
|
|
1204
|
+
"Deleting an account requires --force to avoid accidental credential loss.",
|
|
1205
|
+
)
|
|
1206
|
+
|
|
1207
|
+
account_id = self.account_id_from_input(account)
|
|
1208
|
+
account_dir = self.store.account_dir(account_id)
|
|
1209
|
+
if not self.store.account_meta_path(account_id).is_file():
|
|
1210
|
+
self.fail(f"账号不存在: {account_id}", f"Account not found: {account_id}\nHint: run `codex-workspaces accounts list`")
|
|
1211
|
+
|
|
1212
|
+
default_refs, active_refs = self.workspace_account_references(account_id)
|
|
1213
|
+
if default_refs:
|
|
1214
|
+
refs = ", ".join(default_refs)
|
|
1215
|
+
self.fail(
|
|
1216
|
+
f"不能删除默认账号 {account_id},仍被工作区使用: {refs}",
|
|
1217
|
+
f"Cannot delete default account {account_id}; still used by workspaces: {refs}\nHint: run `codex-workspaces accounts set-default <workspace> <account>` first.",
|
|
1218
|
+
)
|
|
1219
|
+
|
|
1220
|
+
with self.store.lock():
|
|
1221
|
+
shutil.rmtree(account_dir)
|
|
1222
|
+
for directory in self.workspace_dirs():
|
|
1223
|
+
name = strip_workspace_name(str(directory))
|
|
1224
|
+
workspace_meta = self.store.ensure_workspace_meta(name, directory)
|
|
1225
|
+
if workspace_meta.active_account_id == account_id:
|
|
1226
|
+
workspace_meta.active_account_id = None
|
|
1227
|
+
workspace_meta.updated_at = iso_now()
|
|
1228
|
+
self.store.write_workspace_meta(directory, workspace_meta)
|
|
1229
|
+
suffix = f" ({', '.join(active_refs)})" if active_refs else ""
|
|
1230
|
+
self.info(self.message(f"已删除账号: {account_id}{suffix}", f"Deleted account: {account_id}{suffix}"))
|
|
1231
|
+
|
|
1232
|
+
def accounts_note(self, account: str, args: Sequence[str]) -> None:
|
|
1233
|
+
self.store.ensure_layout()
|
|
1234
|
+
account_id = self.account_id_from_input(account)
|
|
1235
|
+
if not self.store.account_meta_path(account_id).is_file():
|
|
1236
|
+
self.fail(f"账号不存在: {account_id}", f"Account not found: {account_id}\nHint: run `codex-workspaces accounts list`")
|
|
1237
|
+
meta = self.store.read_account_meta(account_id)
|
|
1238
|
+
if not args:
|
|
1239
|
+
if meta.notes:
|
|
1240
|
+
self.info(meta.notes)
|
|
1241
|
+
else:
|
|
1242
|
+
self.info(self.message("未设置备注。", "No note set."))
|
|
1243
|
+
return
|
|
1244
|
+
if len(args) == 1 and args[0] == "--clear":
|
|
1245
|
+
meta.notes = ""
|
|
1246
|
+
meta.updated_at = iso_now()
|
|
1247
|
+
self.store.write_account_meta(meta)
|
|
1248
|
+
self.info(self.message(f"已清除账号备注: {account_id}", f"Cleared account note: {account_id}"))
|
|
1249
|
+
return
|
|
1250
|
+
text = " ".join(args).strip()
|
|
1251
|
+
if not text:
|
|
1252
|
+
self.fail("备注不能为空。", "Note cannot be empty.")
|
|
1253
|
+
meta.notes = text
|
|
1254
|
+
meta.updated_at = iso_now()
|
|
1255
|
+
self.store.write_account_meta(meta)
|
|
1256
|
+
self.info(self.message(f"已更新账号备注: {account_id}", f"Updated account note: {account_id}"))
|
|
1257
|
+
|
|
1142
1258
|
def accounts_import_workspaces(self) -> None:
|
|
1143
1259
|
self.store.ensure_layout()
|
|
1144
1260
|
imported: list[str] = []
|
|
@@ -1333,6 +1449,9 @@ def usage(lang: str) -> str:
|
|
|
1333
1449
|
codex-workspaces accounts use <账号>
|
|
1334
1450
|
codex-workspaces accounts restore-default [工作区]
|
|
1335
1451
|
codex-workspaces accounts set-default <工作区> <账号> [--activate]
|
|
1452
|
+
codex-workspaces accounts rename <旧账号> <新账号>
|
|
1453
|
+
codex-workspaces accounts delete <账号> --force
|
|
1454
|
+
codex-workspaces accounts note <账号> [备注文本|--clear]
|
|
1336
1455
|
codex-workspaces accounts import-workspaces
|
|
1337
1456
|
codex-workspaces accounts import-legacy <旧账号目录>
|
|
1338
1457
|
管理 auth.json 账号快照。accounts use 是临时切换,不修改工作区默认账号。
|
|
@@ -1411,6 +1530,9 @@ Usage:
|
|
|
1411
1530
|
codex-workspaces accounts use <account>
|
|
1412
1531
|
codex-workspaces accounts restore-default [workspace]
|
|
1413
1532
|
codex-workspaces accounts set-default <workspace> <account> [--activate]
|
|
1533
|
+
codex-workspaces accounts rename <old-account> <new-account>
|
|
1534
|
+
codex-workspaces accounts delete <account> --force
|
|
1535
|
+
codex-workspaces accounts note <account> [note text|--clear]
|
|
1414
1536
|
codex-workspaces accounts import-workspaces
|
|
1415
1537
|
codex-workspaces accounts import-legacy <legacy-accounts-dir>
|
|
1416
1538
|
Manage auth.json account snapshots. accounts use is temporary and does not change the workspace default account.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codex-workspaces
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Cross-platform Codex workspace switcher with a preserved macOS shell workflow.
|
|
5
5
|
Author: blockchain-project-lives
|
|
6
6
|
Project-URL: Homepage, https://github.com/blockchain-project-lives/codex-workspaces
|
|
@@ -190,6 +190,16 @@ codex-workspaces accounts restore-default
|
|
|
190
190
|
|
|
191
191
|
`auth.json` contains credentials. Do not commit workspace directories, account snapshots, SQLite state, sessions, or shell snapshots to git; the project `.gitignore` excludes these patterns for local checkouts.
|
|
192
192
|
|
|
193
|
+
Manage account metadata and snapshots:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
codex-workspaces accounts note acct_research "lab account"
|
|
197
|
+
codex-workspaces accounts rename acct_research acct_lab
|
|
198
|
+
codex-workspaces accounts delete acct_lab --force
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Account deletion always requires `--force` and refuses to delete an account that is still configured as any workspace's default account.
|
|
202
|
+
|
|
193
203
|
Manage workspace metadata and lifecycle:
|
|
194
204
|
|
|
195
205
|
```bash
|
|
@@ -114,6 +114,20 @@ class TestCliDispatch:
|
|
|
114
114
|
assert "acct_work" in output
|
|
115
115
|
assert "active=acct_work default=acct_work" in output
|
|
116
116
|
|
|
117
|
+
def test_account_lifecycle_dispatches(self, tmp_path: Path) -> None:
|
|
118
|
+
manager = manager_for(tmp_path)
|
|
119
|
+
|
|
120
|
+
assert run(["accounts", "init", "research"], manager) == 0
|
|
121
|
+
assert run(["accounts", "note", "research", "lab"], manager) == 0
|
|
122
|
+
assert run(["accounts", "rename", "research", "lab"], manager) == 0
|
|
123
|
+
assert run(["accounts", "delete", "lab", "--force"], manager) == 0
|
|
124
|
+
|
|
125
|
+
output = manager.stdout.getvalue()
|
|
126
|
+
assert "Initialized account: acct_research" in output
|
|
127
|
+
assert "Updated account note: acct_research" in output
|
|
128
|
+
assert "Renamed account: acct_research -> acct_lab" in output
|
|
129
|
+
assert "Deleted account: acct_lab" in output
|
|
130
|
+
|
|
117
131
|
def test_migrate_and_import_legacy_dispatch(self, tmp_path: Path) -> None:
|
|
118
132
|
manager = manager_for(tmp_path)
|
|
119
133
|
legacy = manager.config.home_dir / ".codex-work"
|
|
@@ -147,7 +147,7 @@ class TestWorkspaceNames:
|
|
|
147
147
|
|
|
148
148
|
|
|
149
149
|
class TestSystemPlatform:
|
|
150
|
-
def
|
|
150
|
+
def test_force_stop_waits_short_grace_period_before_killall(self, monkeypatch) -> None:
|
|
151
151
|
platform = StubbornMacPlatform()
|
|
152
152
|
stdout = io.StringIO()
|
|
153
153
|
calls = []
|
|
@@ -164,8 +164,8 @@ class TestSystemPlatform:
|
|
|
164
164
|
|
|
165
165
|
assert ["osascript", "-e", 'tell application "Codex" to quit'] in calls
|
|
166
166
|
assert ["killall", "Codex"] in calls
|
|
167
|
-
assert sleeps == [1
|
|
168
|
-
assert "did not exit within
|
|
167
|
+
assert sleeps == [1] * (platforms_module.FORCE_QUIT_GRACE_SECONDS + 1)
|
|
168
|
+
assert f"did not exit within {platforms_module.FORCE_QUIT_GRACE_SECONDS}s" in stdout.getvalue()
|
|
169
169
|
|
|
170
170
|
|
|
171
171
|
class TestWorkspaceManager:
|
|
@@ -565,3 +565,81 @@ class TestWorkspaceManager:
|
|
|
565
565
|
assert meta.active_account_id == "acct_work"
|
|
566
566
|
assert manager.store.account_auth_path("acct_work").read_text(encoding="utf-8") == '{"account":"work"}\n'
|
|
567
567
|
assert "Imported workspace default accounts: 1" in stdout.getvalue()
|
|
568
|
+
|
|
569
|
+
def test_accounts_note_sets_reads_and_clears_meta_notes(self, tmp_path: Path) -> None:
|
|
570
|
+
manager, stdout, _ = make_manager(tmp_path)
|
|
571
|
+
manager.accounts_init("research")
|
|
572
|
+
|
|
573
|
+
manager.accounts_note("research", ["lab", "account"])
|
|
574
|
+
manager.accounts_note("research", [])
|
|
575
|
+
manager.accounts_note("research", ["--clear"])
|
|
576
|
+
manager.accounts_note("research", [])
|
|
577
|
+
|
|
578
|
+
meta = manager.store.read_account_meta("acct_research")
|
|
579
|
+
output = stdout.getvalue()
|
|
580
|
+
assert meta.notes == ""
|
|
581
|
+
assert "Updated account note: acct_research" in output
|
|
582
|
+
assert "lab account" in output
|
|
583
|
+
assert "Cleared account note: acct_research" in output
|
|
584
|
+
assert "No note set." in output
|
|
585
|
+
|
|
586
|
+
def test_accounts_rename_updates_workspace_default_and_active_refs(self, tmp_path: Path) -> None:
|
|
587
|
+
manager, stdout, _ = make_manager(tmp_path)
|
|
588
|
+
manager.init_workspace("work", [])
|
|
589
|
+
manager.switch_workspace("work", ["--no-stop", "--no-start"], ["switch", "work"])
|
|
590
|
+
(manager.workspace_dir("work") / "auth.json").write_text('{"account":"work"}\n', encoding="utf-8")
|
|
591
|
+
manager.accounts_save("work")
|
|
592
|
+
manager.accounts_set_default("work", "work", activate=True)
|
|
593
|
+
|
|
594
|
+
manager.accounts_rename("work", "office")
|
|
595
|
+
|
|
596
|
+
meta = manager.store.read_workspace_meta(manager.workspace_dir("work"), "work")
|
|
597
|
+
account_meta = manager.store.read_account_meta("acct_office")
|
|
598
|
+
assert meta.default_account_id == "acct_office"
|
|
599
|
+
assert meta.active_account_id == "acct_office"
|
|
600
|
+
assert account_meta.id == "acct_office"
|
|
601
|
+
assert account_meta.name == "office"
|
|
602
|
+
assert not manager.store.account_dir("acct_work").exists()
|
|
603
|
+
assert manager.store.account_auth_path("acct_office").read_text(encoding="utf-8") == '{"account":"work"}\n'
|
|
604
|
+
assert "Renamed account: acct_work -> acct_office" in stdout.getvalue()
|
|
605
|
+
|
|
606
|
+
def test_accounts_delete_requires_force_and_refuses_default_account(self, tmp_path: Path) -> None:
|
|
607
|
+
manager, _, _ = make_manager(tmp_path)
|
|
608
|
+
manager.init_workspace("work", [])
|
|
609
|
+
manager.switch_workspace("work", ["--no-stop", "--no-start"], ["switch", "work"])
|
|
610
|
+
(manager.workspace_dir("work") / "auth.json").write_text('{"account":"work"}\n', encoding="utf-8")
|
|
611
|
+
manager.accounts_save("work")
|
|
612
|
+
manager.accounts_set_default("work", "work", activate=True)
|
|
613
|
+
|
|
614
|
+
with pytest.raises(CodexWorkspacesError, match="requires --force"):
|
|
615
|
+
manager.accounts_delete("work", [])
|
|
616
|
+
with pytest.raises(CodexWorkspacesError, match="Cannot delete default account"):
|
|
617
|
+
manager.accounts_delete("work", ["--force"])
|
|
618
|
+
|
|
619
|
+
assert manager.store.account_dir("acct_work").is_dir()
|
|
620
|
+
|
|
621
|
+
def test_accounts_delete_removes_non_default_and_clears_active_refs(self, tmp_path: Path) -> None:
|
|
622
|
+
manager, stdout, _ = make_manager(tmp_path)
|
|
623
|
+
manager.init_workspace("work", [])
|
|
624
|
+
manager.switch_workspace("work", ["--no-stop", "--no-start"], ["switch", "work"])
|
|
625
|
+
(manager.workspace_dir("work") / "auth.json").write_text('{"account":"work"}\n', encoding="utf-8")
|
|
626
|
+
manager.accounts_save("work")
|
|
627
|
+
manager.accounts_set_default("work", "work", activate=True)
|
|
628
|
+
personal_auth = tmp_path / "personal-auth.json"
|
|
629
|
+
personal_auth.write_text('{"account":"personal"}\n', encoding="utf-8")
|
|
630
|
+
manager.store.create_account(
|
|
631
|
+
"acct_personal",
|
|
632
|
+
name="personal",
|
|
633
|
+
source="manual",
|
|
634
|
+
bound_workspace=None,
|
|
635
|
+
auth_source=personal_auth,
|
|
636
|
+
)
|
|
637
|
+
manager.accounts_use("personal")
|
|
638
|
+
|
|
639
|
+
manager.accounts_delete("personal", ["--force"])
|
|
640
|
+
|
|
641
|
+
meta = manager.store.read_workspace_meta(manager.workspace_dir("work"), "work")
|
|
642
|
+
assert meta.default_account_id == "acct_work"
|
|
643
|
+
assert meta.active_account_id is None
|
|
644
|
+
assert not manager.store.account_dir("acct_personal").exists()
|
|
645
|
+
assert "Deleted account: acct_personal" in stdout.getvalue()
|
|
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
|
{codex_workspaces-0.2.0 → codex_workspaces-0.3.0}/src/codex_workspaces.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{codex_workspaces-0.2.0 → codex_workspaces-0.3.0}/src/codex_workspaces.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{codex_workspaces-0.2.0 → codex_workspaces-0.3.0}/src/codex_workspaces.egg-info/requires.txt
RENAMED
|
File without changes
|
{codex_workspaces-0.2.0 → codex_workspaces-0.3.0}/src/codex_workspaces.egg-info/top_level.txt
RENAMED
|
File without changes
|