codeer-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 (32) hide show
  1. {codeer_cli-0.1.0 → codeer_cli-0.1.2}/.gitignore +2 -0
  2. {codeer_cli-0.1.0 → codeer_cli-0.1.2}/API_REFERENCE.md +32 -0
  3. codeer_cli-0.1.2/PKG-INFO +196 -0
  4. codeer_cli-0.1.2/README.md +178 -0
  5. {codeer_cli-0.1.0 → codeer_cli-0.1.2}/pyproject.toml +7 -2
  6. {codeer_cli-0.1.0 → codeer_cli-0.1.2}/src/codeer_cli/agents.py +5 -0
  7. {codeer_cli-0.1.0 → codeer_cli-0.1.2}/src/codeer_cli/cli.py +36 -2
  8. {codeer_cli-0.1.0 → codeer_cli-0.1.2}/src/codeer_cli/client.py +1 -2
  9. codeer_cli-0.1.2/src/codeer_cli/commands/_util.py +66 -0
  10. codeer_cli-0.1.2/src/codeer_cli/commands/agent.py +364 -0
  11. codeer_cli-0.1.2/src/codeer_cli/commands/check.py +107 -0
  12. {codeer_cli-0.1.0 → codeer_cli-0.1.2}/src/codeer_cli/commands/eval_cmd.py +232 -28
  13. {codeer_cli-0.1.0 → codeer_cli-0.1.2}/src/codeer_cli/commands/history.py +117 -12
  14. codeer_cli-0.1.2/src/codeer_cli/commands/kb.py +589 -0
  15. {codeer_cli-0.1.0 → codeer_cli-0.1.2}/src/codeer_cli/commands/profile.py +1 -1
  16. {codeer_cli-0.1.0 → codeer_cli-0.1.2}/src/codeer_cli/constants.py +1 -0
  17. {codeer_cli-0.1.0 → codeer_cli-0.1.2}/src/codeer_cli/kb.py +107 -1
  18. codeer_cli-0.1.2/uv.lock +124 -0
  19. codeer_cli-0.1.0/PKG-INFO +0 -108
  20. codeer_cli-0.1.0/README.md +0 -90
  21. codeer_cli-0.1.0/src/codeer_cli/commands/_util.py +0 -12
  22. codeer_cli-0.1.0/src/codeer_cli/commands/agent.py +0 -186
  23. codeer_cli-0.1.0/src/codeer_cli/commands/check.py +0 -66
  24. codeer_cli-0.1.0/src/codeer_cli/commands/kb.py +0 -126
  25. codeer_cli-0.1.0/uv.lock +0 -91
  26. {codeer_cli-0.1.0 → codeer_cli-0.1.2}/src/codeer_cli/__init__.py +0 -0
  27. {codeer_cli-0.1.0 → codeer_cli-0.1.2}/src/codeer_cli/_validate.py +0 -0
  28. {codeer_cli-0.1.0 → codeer_cli-0.1.2}/src/codeer_cli/chats.py +0 -0
  29. {codeer_cli-0.1.0 → codeer_cli-0.1.2}/src/codeer_cli/commands/__init__.py +0 -0
  30. {codeer_cli-0.1.0 → codeer_cli-0.1.2}/src/codeer_cli/eval_.py +0 -0
  31. {codeer_cli-0.1.0 → codeer_cli-0.1.2}/src/codeer_cli/histories.py +0 -0
  32. {codeer_cli-0.1.0 → codeer_cli-0.1.2}/src/codeer_cli/parse.py +0 -0
@@ -22,6 +22,7 @@ venv/
22
22
  env/
23
23
  .env
24
24
  .env.*
25
+ *.env
25
26
  !.env.example
26
27
  !.env.sample
27
28
  session.env
@@ -34,3 +35,4 @@ Thumbs.db
34
35
 
35
36
  # Local assistant/tool settings
36
37
  .claude/settings.local.json
38
+ .codeer/
@@ -66,6 +66,38 @@ Base path: `/organizations/{org_id}/workspaces/{ws_id}/knowledge_bases`
66
66
  Attach KB files to an agent by listing their node IDs in the agent's
67
67
  `unified_tools[].knowledge_node_ids`.
68
68
 
69
+ ### Context Object FAQ
70
+
71
+ Base path: `/external/context-object-faqs`
72
+
73
+ | Method & path | Purpose |
74
+ | --- | --- |
75
+ | `GET /context-object-faqs` | List FAQ entries, optionally filtered by `context_object_id` |
76
+ | `GET /context-object-faqs/{faq_id}` | Read one FAQ entry |
77
+ | `POST /context-object-faqs` | Create an FAQ entry |
78
+ | `PATCH /context-object-faqs/{faq_id}` | Update the linked context object and/or question |
79
+ | `DELETE /context-object-faqs/{faq_id}` | Delete an FAQ entry |
80
+
81
+ Create body:
82
+
83
+ ```json
84
+ {"context_object_id": 123, "question": "How do I reset billing?"}
85
+ ```
86
+
87
+ Update body accepts either or both fields:
88
+
89
+ ```json
90
+ {"context_object_id": 456, "question": "How do I update billing?"}
91
+ ```
92
+
93
+ `context_object_id` is the KB file's `snapshot_object_id` from the KB node
94
+ listing. The compact CLI output includes it:
95
+
96
+ ```bash
97
+ codeer kb files --kb-id <kb-id>
98
+ codeer kb faq-create --context-object-id <snapshot-object-id> --question "..." --dry-run
99
+ ```
100
+
69
101
  ## Stage 3 — Live Test on a specific version
70
102
 
71
103
  | Method & path | Purpose |
@@ -0,0 +1,196 @@
1
+ Metadata-Version: 2.4
2
+ Name: codeer-cli
3
+ Version: 0.1.2
4
+ Summary: Command line tools for managing Codeer agents over the Codeer API.
5
+ Project-URL: Homepage, https://www.codeer.ai
6
+ Author: Codeer.AI
7
+ License: MIT
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Environment :: Console
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Topic :: Software Development
15
+ Requires-Python: >=3.11
16
+ Requires-Dist: httpx>=0.27
17
+ Description-Content-Type: text/markdown
18
+
19
+ # codeer-cli
20
+
21
+ Standalone CLI for managing Codeer agents over the Codeer API.
22
+
23
+ ## User install
24
+
25
+ Install the CLI from PyPI with `pipx`:
26
+
27
+ ```bash
28
+ pipx install codeer-cli
29
+ ```
30
+
31
+ Verify that the command is available:
32
+
33
+ ```bash
34
+ codeer --help
35
+ ```
36
+
37
+ If `pipx` is not installed:
38
+
39
+ ```bash
40
+ python -m pip install --user pipx
41
+ python -m pipx ensurepath
42
+ ```
43
+
44
+ Then restart the terminal and run:
45
+
46
+ ```bash
47
+ pipx install codeer-cli
48
+ ```
49
+
50
+ As a fallback, you can install into your user Python environment:
51
+
52
+ ```bash
53
+ python -m pip install --user codeer-cli
54
+ ```
55
+
56
+ ## Credentials
57
+
58
+ The CLI expects credentials to be configured outside any skill workspace. Add a
59
+ named profile, select it, then verify the setup:
60
+
61
+ ```bash
62
+ codeer profile add work
63
+ codeer profile use work
64
+ codeer check
65
+ ```
66
+
67
+ `codeer profile add` prompts for the API key without echoing it. The local
68
+ project stores only the selected profile name in `.codeer/profile`; API keys
69
+ remain in the user-level config file.
70
+
71
+ For a one-off shell session, you can also export an API key directly:
72
+
73
+ ```bash
74
+ export CODEER_API_KEY=<admin-workspace-api-key>
75
+ codeer check
76
+ ```
77
+
78
+ `CODEER_API_BASE` defaults to `https://api.codeer.ai`. Override it only for
79
+ local, beta, or preview environments:
80
+
81
+ ```bash
82
+ export CODEER_API_BASE=http://localhost:8000
83
+ ```
84
+
85
+ The CLI intentionally does not read repo-root credential files or caller CWD
86
+ `.env`, because those files are often visible to LLM workspace context. Do not
87
+ paste the API key into agent chat or commit it to the repository.
88
+
89
+ Workspace and organization scope are inferred from the workspace API-key
90
+ virtual user's profile. `--workspace`, `--org`, `CODEER_WORKSPACE_ID`, and
91
+ `CODEER_ORGANIZATION_ID` are not used by the CLI.
92
+
93
+ Agent scope is optional and can be set as a non-secret environment variable:
94
+
95
+ ```bash
96
+ CODEER_AGENT_ID=<agent-id>
97
+ ```
98
+
99
+ ## Development install
100
+
101
+ Use an editable install while the CLI is changing quickly:
102
+
103
+ ```bash
104
+ cd /path/to/codeer-skills/codeer-cli
105
+ uv tool install --editable .
106
+ ```
107
+
108
+ Reinstall only when dependencies, entry points, or package metadata change:
109
+
110
+ ```bash
111
+ uv tool install --reinstall --editable /path/to/codeer-skills/codeer-cli
112
+ ```
113
+
114
+ Validate setup before API work:
115
+
116
+ ```bash
117
+ codeer check
118
+ ```
119
+
120
+ ## Upgrade and uninstall
121
+
122
+ Upgrade the CLI:
123
+
124
+ ```bash
125
+ pipx upgrade codeer-cli
126
+ codeer check
127
+ ```
128
+
129
+ Remove the CLI:
130
+
131
+ ```bash
132
+ pipx uninstall codeer-cli
133
+ ```
134
+
135
+ ## Output policy for coding agents
136
+
137
+ The CLI is optimized for Codex, Claude Code, Claude Cowork, and similar coding
138
+ agents that keep command output in their LLM context. Default stdout is a
139
+ compact lifecycle summary, not the full server payload.
140
+
141
+ Use this pattern during agent lifecycle work:
142
+
143
+ ```bash
144
+ codeer agent list
145
+ codeer history list --agent <agent-id> --limit 50
146
+ codeer eval run --agent <agent-id> --evaluators <evaluator-id> --out .codeer/eval_run.json
147
+ ```
148
+
149
+ Flags:
150
+
151
+ - `--full` prints bounded extra detail for human inspection. It is still
152
+ intended to be safe for LLM context.
153
+ - `--out <path>` writes complete diagnostic artifacts to a local file. Use it
154
+ for raw eval results, full conversation turns, full rubric matrices, and
155
+ other data that can grow with cases, versions, or turns.
156
+
157
+ Avoid piping large raw JSON directly into agent chat. Prefer `--out`, then ask
158
+ the coding agent to inspect targeted summaries, IDs, failing cases, or selected
159
+ snippets from the saved file.
160
+
161
+ ## Website crawler KBs
162
+
163
+ Website-backed KB folders can be created and updated with `codeer kb crawl-*`.
164
+ Always preview crawler mutations with `--dry-run` first:
165
+
166
+ ```bash
167
+ codeer kb crawl-create \
168
+ --url https://example.com/docs \
169
+ --folder-name "Product Docs" \
170
+ --include-path "/docs*" \
171
+ --exclude-path "/docs/private*" \
172
+ --limit 250 \
173
+ --max-depth 3 \
174
+ --only-main-content \
175
+ --dry-run
176
+ ```
177
+
178
+ `--include-path` and `--exclude-path` are repeatable clean path patterns. Quote
179
+ paths containing `*` so the shell passes the wildcard to the CLI. Advanced
180
+ settings can still be passed through `--config-json`; explicit crawler flags
181
+ override matching JSON keys.
182
+
183
+ ## Context Object FAQ
184
+
185
+ Use Context Object FAQ entries to route high-value questions to a canonical KB
186
+ file when semantic retrieval misses the right source. The FAQ target is a KB
187
+ file's `snapshot_object_id`, shown by `codeer kb files`.
188
+
189
+ ```bash
190
+ codeer kb files --kb-id <kb-id>
191
+ codeer kb faq-list --context-object-id <snapshot-object-id>
192
+ codeer kb faq-create --context-object-id <snapshot-object-id> --question "..." --dry-run
193
+ ```
194
+
195
+ After reviewing the dry-run output, rerun the create/update/delete command
196
+ without `--dry-run` to apply it.
@@ -0,0 +1,178 @@
1
+ # codeer-cli
2
+
3
+ Standalone CLI for managing Codeer agents over the Codeer API.
4
+
5
+ ## User install
6
+
7
+ Install the CLI from PyPI with `pipx`:
8
+
9
+ ```bash
10
+ pipx install codeer-cli
11
+ ```
12
+
13
+ Verify that the command is available:
14
+
15
+ ```bash
16
+ codeer --help
17
+ ```
18
+
19
+ If `pipx` is not installed:
20
+
21
+ ```bash
22
+ python -m pip install --user pipx
23
+ python -m pipx ensurepath
24
+ ```
25
+
26
+ Then restart the terminal and run:
27
+
28
+ ```bash
29
+ pipx install codeer-cli
30
+ ```
31
+
32
+ As a fallback, you can install into your user Python environment:
33
+
34
+ ```bash
35
+ python -m pip install --user codeer-cli
36
+ ```
37
+
38
+ ## Credentials
39
+
40
+ The CLI expects credentials to be configured outside any skill workspace. Add a
41
+ named profile, select it, then verify the setup:
42
+
43
+ ```bash
44
+ codeer profile add work
45
+ codeer profile use work
46
+ codeer check
47
+ ```
48
+
49
+ `codeer profile add` prompts for the API key without echoing it. The local
50
+ project stores only the selected profile name in `.codeer/profile`; API keys
51
+ remain in the user-level config file.
52
+
53
+ For a one-off shell session, you can also export an API key directly:
54
+
55
+ ```bash
56
+ export CODEER_API_KEY=<admin-workspace-api-key>
57
+ codeer check
58
+ ```
59
+
60
+ `CODEER_API_BASE` defaults to `https://api.codeer.ai`. Override it only for
61
+ local, beta, or preview environments:
62
+
63
+ ```bash
64
+ export CODEER_API_BASE=http://localhost:8000
65
+ ```
66
+
67
+ The CLI intentionally does not read repo-root credential files or caller CWD
68
+ `.env`, because those files are often visible to LLM workspace context. Do not
69
+ paste the API key into agent chat or commit it to the repository.
70
+
71
+ Workspace and organization scope are inferred from the workspace API-key
72
+ virtual user's profile. `--workspace`, `--org`, `CODEER_WORKSPACE_ID`, and
73
+ `CODEER_ORGANIZATION_ID` are not used by the CLI.
74
+
75
+ Agent scope is optional and can be set as a non-secret environment variable:
76
+
77
+ ```bash
78
+ CODEER_AGENT_ID=<agent-id>
79
+ ```
80
+
81
+ ## Development install
82
+
83
+ Use an editable install while the CLI is changing quickly:
84
+
85
+ ```bash
86
+ cd /path/to/codeer-skills/codeer-cli
87
+ uv tool install --editable .
88
+ ```
89
+
90
+ Reinstall only when dependencies, entry points, or package metadata change:
91
+
92
+ ```bash
93
+ uv tool install --reinstall --editable /path/to/codeer-skills/codeer-cli
94
+ ```
95
+
96
+ Validate setup before API work:
97
+
98
+ ```bash
99
+ codeer check
100
+ ```
101
+
102
+ ## Upgrade and uninstall
103
+
104
+ Upgrade the CLI:
105
+
106
+ ```bash
107
+ pipx upgrade codeer-cli
108
+ codeer check
109
+ ```
110
+
111
+ Remove the CLI:
112
+
113
+ ```bash
114
+ pipx uninstall codeer-cli
115
+ ```
116
+
117
+ ## Output policy for coding agents
118
+
119
+ The CLI is optimized for Codex, Claude Code, Claude Cowork, and similar coding
120
+ agents that keep command output in their LLM context. Default stdout is a
121
+ compact lifecycle summary, not the full server payload.
122
+
123
+ Use this pattern during agent lifecycle work:
124
+
125
+ ```bash
126
+ codeer agent list
127
+ codeer history list --agent <agent-id> --limit 50
128
+ codeer eval run --agent <agent-id> --evaluators <evaluator-id> --out .codeer/eval_run.json
129
+ ```
130
+
131
+ Flags:
132
+
133
+ - `--full` prints bounded extra detail for human inspection. It is still
134
+ intended to be safe for LLM context.
135
+ - `--out <path>` writes complete diagnostic artifacts to a local file. Use it
136
+ for raw eval results, full conversation turns, full rubric matrices, and
137
+ other data that can grow with cases, versions, or turns.
138
+
139
+ Avoid piping large raw JSON directly into agent chat. Prefer `--out`, then ask
140
+ the coding agent to inspect targeted summaries, IDs, failing cases, or selected
141
+ snippets from the saved file.
142
+
143
+ ## Website crawler KBs
144
+
145
+ Website-backed KB folders can be created and updated with `codeer kb crawl-*`.
146
+ Always preview crawler mutations with `--dry-run` first:
147
+
148
+ ```bash
149
+ codeer kb crawl-create \
150
+ --url https://example.com/docs \
151
+ --folder-name "Product Docs" \
152
+ --include-path "/docs*" \
153
+ --exclude-path "/docs/private*" \
154
+ --limit 250 \
155
+ --max-depth 3 \
156
+ --only-main-content \
157
+ --dry-run
158
+ ```
159
+
160
+ `--include-path` and `--exclude-path` are repeatable clean path patterns. Quote
161
+ paths containing `*` so the shell passes the wildcard to the CLI. Advanced
162
+ settings can still be passed through `--config-json`; explicit crawler flags
163
+ override matching JSON keys.
164
+
165
+ ## Context Object FAQ
166
+
167
+ Use Context Object FAQ entries to route high-value questions to a canonical KB
168
+ file when semantic retrieval misses the right source. The FAQ target is a KB
169
+ file's `snapshot_object_id`, shown by `codeer kb files`.
170
+
171
+ ```bash
172
+ codeer kb files --kb-id <kb-id>
173
+ codeer kb faq-list --context-object-id <snapshot-object-id>
174
+ codeer kb faq-create --context-object-id <snapshot-object-id> --question "..." --dry-run
175
+ ```
176
+
177
+ After reviewing the dry-run output, rerun the create/update/delete command
178
+ without `--dry-run` to apply it.
@@ -4,12 +4,12 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "codeer-cli"
7
- version = "0.1.0"
7
+ version = "0.1.2"
8
8
  description = "Command line tools for managing Codeer agents over the Codeer API."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
11
11
  authors = [{ name = "Codeer.AI" }]
12
- license = { text = "Proprietary" }
12
+ license = { text = "MIT" }
13
13
  classifiers = [
14
14
  "Development Status :: 3 - Alpha",
15
15
  "Environment :: Console",
@@ -23,6 +23,11 @@ dependencies = [
23
23
  "httpx>=0.27",
24
24
  ]
25
25
 
26
+ [dependency-groups]
27
+ dev = [
28
+ "ruff>=0.11",
29
+ ]
30
+
26
31
  [project.urls]
27
32
  Homepage = "https://www.codeer.ai"
28
33
 
@@ -153,3 +153,8 @@ def get_version(client: CodeerClient, agent_id: str, history_id: str) -> dict:
153
153
  def check_impact(client: CodeerClient, agent_id: str) -> dict:
154
154
  """List downstream agents that call this one. Call before publishing breaking changes."""
155
155
  return client.get(f"/external/agents/{agent_id}/impact")
156
+
157
+
158
+ def publish_version(client: CodeerClient, agent_id: str, history_id: str) -> dict:
159
+ """Promote one AgentHistory version to the published runtime version."""
160
+ return client.post(f"/external/agents/{agent_id}/versions/{history_id}:publish", json={})
@@ -2,7 +2,7 @@
2
2
 
3
3
  codeer check
4
4
  codeer agent list|get|apply|diff|versions
5
- codeer kb list|files|upload
5
+ codeer kb list|files|upload|faq-list|faq-get|faq-create|faq-update|faq-delete
6
6
  codeer eval list|evaluators|evaluator-create|evaluator-update|run|export|reconcile|cases-apply|rubrics|rubrics-apply
7
7
  codeer history list|get|conversations|negative-feedback
8
8
  """
@@ -18,7 +18,31 @@ from .commands import check
18
18
 
19
19
 
20
20
  def main(argv: list[str] | None = None) -> int:
21
- parser = argparse.ArgumentParser(prog="codeer")
21
+ parser = argparse.ArgumentParser(
22
+ prog="codeer",
23
+ formatter_class=argparse.RawDescriptionHelpFormatter,
24
+ description="Codeer CLI — self-describing agent lifecycle tools.",
25
+ epilog="""\
26
+ Safe workflow for coding agents:
27
+ codeer check --json
28
+ codeer agent list
29
+ codeer agent get <agent-id> --full
30
+ codeer kb list
31
+ codeer eval list --agent <agent-id>
32
+ codeer eval evaluators
33
+ codeer agent diff --agent <agent-id> --from-version <n> --to-version <n>
34
+ codeer eval reconcile --agent <agent-id> --manifest .codeer/eval_cases.json
35
+
36
+ Preview mutations before applying:
37
+ codeer agent apply --payload agent.json --dry-run
38
+ codeer eval cases-apply --agent <agent-id> --cases eval_cases.json --dry-run
39
+ codeer eval rubrics-apply --rubrics rubrics.json --dry-run
40
+ codeer kb upload --dir kb --name "Product KB" --dry-run
41
+ codeer kb faq-create --context-object-id <snapshot-object-id> --question "..." --dry-run
42
+
43
+ Use --out <path> for large raw artifacts; stdout defaults to compact summaries.
44
+ """,
45
+ )
22
46
  sub = parser.add_subparsers(dest="group")
23
47
 
24
48
  check.register(sub)
@@ -67,6 +91,16 @@ def main(argv: list[str] | None = None) -> int:
67
91
  client = CodeerClient.from_env()
68
92
  except AuthError as e:
69
93
  if args.group == "check":
94
+ if getattr(args, "json", False):
95
+ print(json.dumps({
96
+ "status": "fail",
97
+ "auth": {
98
+ "ok": False,
99
+ "error": str(e),
100
+ },
101
+ "next_step": "Configure CODEER_API_KEY or a codeer profile",
102
+ }, ensure_ascii=False, indent=2))
103
+ return 1
70
104
  print(f"FAIL Auth: {e}", file=sys.stderr)
71
105
  print(" Configure CODEER_API_KEY or a codeer profile", file=sys.stderr)
72
106
  return 1
@@ -85,8 +85,7 @@ class CodeerClient:
85
85
  if not api_key:
86
86
  raise AuthError(
87
87
  0,
88
- "Missing API key. Export CODEER_API_KEY or run `codeer profile add <name>` "
89
- "and `codeer profile use <name>`.",
88
+ "Missing API key. Export CODEER_API_KEY or run `codeer profile add <name>`.",
90
89
  )
91
90
 
92
91
  overrides.pop("workspace_id", None)
@@ -0,0 +1,66 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import sys
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+
9
+ def log(*args, **kwargs):
10
+ print(*args, file=sys.stderr, **kwargs)
11
+
12
+
13
+ def truncate(text: str, n: int = 60) -> str:
14
+ text = text.replace("\n", " ").strip()
15
+ return text[:n] + "..." if len(text) > n else text
16
+
17
+
18
+ NOISY_KEYS = {
19
+ "avatar",
20
+ "brand",
21
+ "creator",
22
+ "default_organization_id",
23
+ "default_scopes",
24
+ "default_workspace_id",
25
+ "is_owner",
26
+ "members",
27
+ "my_permissions",
28
+ "owner",
29
+ "profile",
30
+ "source_creator",
31
+ "user_role",
32
+ "workspace_organization_map",
33
+ }
34
+
35
+
36
+ def strip_noisy_fields(value: Any) -> Any:
37
+ """Remove server/account metadata that is not useful for agent lifecycle work."""
38
+ if isinstance(value, list):
39
+ return [strip_noisy_fields(item) for item in value]
40
+ if not isinstance(value, dict):
41
+ return value
42
+
43
+ out = {}
44
+ for key, item in value.items():
45
+ if key in NOISY_KEYS:
46
+ continue
47
+ if key == "workspace" and isinstance(item, dict):
48
+ out[key] = {
49
+ k: item.get(k)
50
+ for k in ("id", "name", "organization_id")
51
+ if item.get(k) is not None
52
+ }
53
+ continue
54
+ out[key] = strip_noisy_fields(item)
55
+ return out
56
+
57
+
58
+ def print_json(value: Any) -> None:
59
+ print(json.dumps(value, ensure_ascii=False, indent=2, default=str))
60
+
61
+
62
+ def write_json(path: str | None, value: Any) -> None:
63
+ if not path:
64
+ return
65
+ Path(path).write_text(json.dumps(value, ensure_ascii=False, indent=2, default=str) + "\n")
66
+ log(f"wrote full detail to {path}")