codeer-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.
- {codeer_cli-0.1.0 → codeer_cli-0.1.1}/.gitignore +1 -0
- {codeer_cli-0.1.0 → codeer_cli-0.1.1}/API_REFERENCE.md +32 -0
- {codeer_cli-0.1.0 → codeer_cli-0.1.1}/PKG-INFO +43 -2
- {codeer_cli-0.1.0 → codeer_cli-0.1.1}/README.md +41 -0
- {codeer_cli-0.1.0 → codeer_cli-0.1.1}/pyproject.toml +7 -2
- {codeer_cli-0.1.0 → codeer_cli-0.1.1}/src/codeer_cli/cli.py +36 -2
- codeer_cli-0.1.1/src/codeer_cli/commands/_util.py +66 -0
- {codeer_cli-0.1.0 → codeer_cli-0.1.1}/src/codeer_cli/commands/agent.py +117 -10
- codeer_cli-0.1.1/src/codeer_cli/commands/check.py +107 -0
- {codeer_cli-0.1.0 → codeer_cli-0.1.1}/src/codeer_cli/commands/eval_cmd.py +232 -28
- {codeer_cli-0.1.0 → codeer_cli-0.1.1}/src/codeer_cli/commands/history.py +117 -12
- codeer_cli-0.1.1/src/codeer_cli/commands/kb.py +350 -0
- {codeer_cli-0.1.0 → codeer_cli-0.1.1}/src/codeer_cli/kb.py +52 -0
- codeer_cli-0.1.1/uv.lock +124 -0
- codeer_cli-0.1.0/src/codeer_cli/commands/_util.py +0 -12
- codeer_cli-0.1.0/src/codeer_cli/commands/check.py +0 -66
- codeer_cli-0.1.0/src/codeer_cli/commands/kb.py +0 -126
- codeer_cli-0.1.0/uv.lock +0 -91
- {codeer_cli-0.1.0 → codeer_cli-0.1.1}/src/codeer_cli/__init__.py +0 -0
- {codeer_cli-0.1.0 → codeer_cli-0.1.1}/src/codeer_cli/_validate.py +0 -0
- {codeer_cli-0.1.0 → codeer_cli-0.1.1}/src/codeer_cli/agents.py +0 -0
- {codeer_cli-0.1.0 → codeer_cli-0.1.1}/src/codeer_cli/chats.py +0 -0
- {codeer_cli-0.1.0 → codeer_cli-0.1.1}/src/codeer_cli/client.py +0 -0
- {codeer_cli-0.1.0 → codeer_cli-0.1.1}/src/codeer_cli/commands/__init__.py +0 -0
- {codeer_cli-0.1.0 → codeer_cli-0.1.1}/src/codeer_cli/commands/profile.py +0 -0
- {codeer_cli-0.1.0 → codeer_cli-0.1.1}/src/codeer_cli/constants.py +0 -0
- {codeer_cli-0.1.0 → codeer_cli-0.1.1}/src/codeer_cli/eval_.py +0 -0
- {codeer_cli-0.1.0 → codeer_cli-0.1.1}/src/codeer_cli/histories.py +0 -0
- {codeer_cli-0.1.0 → codeer_cli-0.1.1}/src/codeer_cli/parse.py +0 -0
|
@@ -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 |
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codeer-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: Command line tools for managing Codeer agents over the Codeer API.
|
|
5
5
|
Project-URL: Homepage, https://www.codeer.ai
|
|
6
6
|
Author: Codeer.AI
|
|
7
|
-
License:
|
|
7
|
+
License: MIT
|
|
8
8
|
Classifier: Development Status :: 3 - Alpha
|
|
9
9
|
Classifier: Environment :: Console
|
|
10
10
|
Classifier: Intended Audience :: Developers
|
|
@@ -106,3 +106,44 @@ Validate setup before API work:
|
|
|
106
106
|
```bash
|
|
107
107
|
codeer check
|
|
108
108
|
```
|
|
109
|
+
|
|
110
|
+
## Output policy for coding agents
|
|
111
|
+
|
|
112
|
+
The CLI is optimized for Codex, Claude Code, Claude Cowork, and similar coding
|
|
113
|
+
agents that keep command output in their LLM context. Default stdout is a
|
|
114
|
+
compact lifecycle summary, not the full server payload.
|
|
115
|
+
|
|
116
|
+
Use this pattern during agent lifecycle work:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
codeer agent list
|
|
120
|
+
codeer history list --agent <agent-id> --limit 50
|
|
121
|
+
codeer eval run --agent <agent-id> --evaluators <evaluator-id> --out .codeer/eval_run.json
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Flags:
|
|
125
|
+
|
|
126
|
+
- `--full` prints bounded extra detail for human inspection. It is still
|
|
127
|
+
intended to be safe for LLM context.
|
|
128
|
+
- `--out <path>` writes complete diagnostic artifacts to a local file. Use it
|
|
129
|
+
for raw eval results, full conversation turns, full rubric matrices, and
|
|
130
|
+
other data that can grow with cases, versions, or turns.
|
|
131
|
+
|
|
132
|
+
Avoid piping large raw JSON directly into agent chat. Prefer `--out`, then ask
|
|
133
|
+
the coding agent to inspect targeted summaries, IDs, failing cases, or selected
|
|
134
|
+
snippets from the saved file.
|
|
135
|
+
|
|
136
|
+
## Context Object FAQ
|
|
137
|
+
|
|
138
|
+
Use Context Object FAQ entries to route high-value questions to a canonical KB
|
|
139
|
+
file when semantic retrieval misses the right source. The FAQ target is a KB
|
|
140
|
+
file's `snapshot_object_id`, shown by `codeer kb files`.
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
codeer kb files --kb-id <kb-id>
|
|
144
|
+
codeer kb faq-list --context-object-id <snapshot-object-id>
|
|
145
|
+
codeer kb faq-create --context-object-id <snapshot-object-id> --question "..." --dry-run
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
After reviewing the dry-run output, rerun the create/update/delete command
|
|
149
|
+
without `--dry-run` to apply it.
|
|
@@ -88,3 +88,44 @@ Validate setup before API work:
|
|
|
88
88
|
```bash
|
|
89
89
|
codeer check
|
|
90
90
|
```
|
|
91
|
+
|
|
92
|
+
## Output policy for coding agents
|
|
93
|
+
|
|
94
|
+
The CLI is optimized for Codex, Claude Code, Claude Cowork, and similar coding
|
|
95
|
+
agents that keep command output in their LLM context. Default stdout is a
|
|
96
|
+
compact lifecycle summary, not the full server payload.
|
|
97
|
+
|
|
98
|
+
Use this pattern during agent lifecycle work:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
codeer agent list
|
|
102
|
+
codeer history list --agent <agent-id> --limit 50
|
|
103
|
+
codeer eval run --agent <agent-id> --evaluators <evaluator-id> --out .codeer/eval_run.json
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Flags:
|
|
107
|
+
|
|
108
|
+
- `--full` prints bounded extra detail for human inspection. It is still
|
|
109
|
+
intended to be safe for LLM context.
|
|
110
|
+
- `--out <path>` writes complete diagnostic artifacts to a local file. Use it
|
|
111
|
+
for raw eval results, full conversation turns, full rubric matrices, and
|
|
112
|
+
other data that can grow with cases, versions, or turns.
|
|
113
|
+
|
|
114
|
+
Avoid piping large raw JSON directly into agent chat. Prefer `--out`, then ask
|
|
115
|
+
the coding agent to inspect targeted summaries, IDs, failing cases, or selected
|
|
116
|
+
snippets from the saved file.
|
|
117
|
+
|
|
118
|
+
## Context Object FAQ
|
|
119
|
+
|
|
120
|
+
Use Context Object FAQ entries to route high-value questions to a canonical KB
|
|
121
|
+
file when semantic retrieval misses the right source. The FAQ target is a KB
|
|
122
|
+
file's `snapshot_object_id`, shown by `codeer kb files`.
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
codeer kb files --kb-id <kb-id>
|
|
126
|
+
codeer kb faq-list --context-object-id <snapshot-object-id>
|
|
127
|
+
codeer kb faq-create --context-object-id <snapshot-object-id> --question "..." --dry-run
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
After reviewing the dry-run output, rerun the create/update/delete command
|
|
131
|
+
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.
|
|
7
|
+
version = "0.1.1"
|
|
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 = "
|
|
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
|
|
|
@@ -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(
|
|
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
|
|
@@ -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}")
|
|
@@ -2,33 +2,48 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import difflib
|
|
4
4
|
import json
|
|
5
|
-
import sys
|
|
6
5
|
from pathlib import Path
|
|
7
6
|
from typing import Optional
|
|
8
7
|
|
|
9
8
|
from .. import agents as agents_mod
|
|
10
9
|
from ..client import CodeerClient
|
|
11
|
-
from ._util import log
|
|
10
|
+
from ._util import log, print_json, strip_noisy_fields, truncate, write_json
|
|
12
11
|
|
|
13
12
|
|
|
14
13
|
def register(subparsers):
|
|
15
|
-
agent = subparsers.add_parser("agent", help="Agent CRUD
|
|
14
|
+
agent = subparsers.add_parser("agent", help="Agent CRUD and versioning")
|
|
16
15
|
sub = agent.add_subparsers(dest="action", required=True)
|
|
17
16
|
|
|
18
17
|
# codeer agent list
|
|
19
|
-
p = sub.add_parser(
|
|
18
|
+
p = sub.add_parser(
|
|
19
|
+
"list",
|
|
20
|
+
help="List agents in workspace. Defaults to a lifecycle summary safe for Codex/Claude context.",
|
|
21
|
+
)
|
|
22
|
+
p.add_argument("--full", action="store_true",
|
|
23
|
+
help="Print bounded detail instead of the default lifecycle summary.")
|
|
24
|
+
p.add_argument("--out", default=None,
|
|
25
|
+
help="Write stripped full server detail to this file; stdout stays compact.")
|
|
20
26
|
p.set_defaults(func=run_list)
|
|
21
27
|
|
|
22
28
|
# codeer agent get <id>
|
|
23
|
-
p = sub.add_parser(
|
|
29
|
+
p = sub.add_parser(
|
|
30
|
+
"get",
|
|
31
|
+
help="Read one agent. Defaults to summary; use --full for prompt/tool detail or --out for an artifact.",
|
|
32
|
+
)
|
|
24
33
|
p.add_argument("agent_id")
|
|
34
|
+
p.add_argument("--full", action="store_true",
|
|
35
|
+
help="Print stripped full agent config, including system_prompt and tools.")
|
|
36
|
+
p.add_argument("--out", default=None,
|
|
37
|
+
help="Write stripped full agent config to this file.")
|
|
25
38
|
p.set_defaults(func=run_get)
|
|
26
39
|
|
|
27
40
|
# codeer agent apply --payload agent.json
|
|
28
|
-
p = sub.add_parser("apply", help="Create or update agent from JSON payload")
|
|
41
|
+
p = sub.add_parser("apply", help="Create or update agent from JSON payload; run --dry-run first")
|
|
29
42
|
p.add_argument("--payload", required=True, help="Path to agent payload JSON")
|
|
30
43
|
p.add_argument("--agent-id", default=None, help="If set, PUT (update). Else POST (create).")
|
|
31
44
|
p.add_argument("--note", default="", help="version_note for PUT")
|
|
45
|
+
p.add_argument("--dry-run", action="store_true",
|
|
46
|
+
help="Validate payload and print intended mutation without writing server state.")
|
|
32
47
|
p.add_argument("--out", default=None, help="Write result JSON to this file too")
|
|
33
48
|
p.set_defaults(func=run_apply)
|
|
34
49
|
|
|
@@ -43,27 +58,104 @@ def register(subparsers):
|
|
|
43
58
|
p.set_defaults(func=run_diff)
|
|
44
59
|
|
|
45
60
|
# codeer agent versions --agent <id>
|
|
46
|
-
p = sub.add_parser(
|
|
61
|
+
p = sub.add_parser(
|
|
62
|
+
"versions",
|
|
63
|
+
help="List version history. Defaults to version metadata only; use --out for full snapshots.",
|
|
64
|
+
)
|
|
47
65
|
p.add_argument("--agent", required=True)
|
|
66
|
+
p.add_argument("--full", action="store_true",
|
|
67
|
+
help="Add bounded prompt/tool size metadata; full snapshots still require --out.")
|
|
68
|
+
p.add_argument("--out", default=None,
|
|
69
|
+
help="Write stripped full version snapshots to this file; stdout stays compact.")
|
|
48
70
|
p.set_defaults(func=run_versions)
|
|
49
71
|
|
|
50
72
|
|
|
73
|
+
def _tool_summary(tools: list[dict] | None) -> list[dict]:
|
|
74
|
+
out = []
|
|
75
|
+
for t in tools or []:
|
|
76
|
+
form = t.get("custom_form_schema") if isinstance(t.get("custom_form_schema"), dict) else {}
|
|
77
|
+
out.append({
|
|
78
|
+
"id": t.get("id"),
|
|
79
|
+
"type": t.get("type"),
|
|
80
|
+
"name": t.get("name"),
|
|
81
|
+
"knowledge_node_count": len(t.get("knowledge_node_ids") or []),
|
|
82
|
+
"form_title": form.get("title"),
|
|
83
|
+
"invocation_preview": truncate(t.get("invocation_instruction") or "", 160),
|
|
84
|
+
})
|
|
85
|
+
return out
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _agent_summary(agent: dict, *, full: bool = False) -> dict:
|
|
89
|
+
tools = agent.get("unified_tools") or agent.get("tools") or []
|
|
90
|
+
row = {
|
|
91
|
+
"id": agent.get("id"),
|
|
92
|
+
"name": agent.get("name"),
|
|
93
|
+
"workspace": {
|
|
94
|
+
"id": agent.get("workspace_id") or (agent.get("workspace") or {}).get("id"),
|
|
95
|
+
"name": (agent.get("workspace") or {}).get("name"),
|
|
96
|
+
},
|
|
97
|
+
"publish_state": agent.get("publish_state"),
|
|
98
|
+
"version": agent.get("version"),
|
|
99
|
+
"latest_version_number": agent.get("latest_version_number"),
|
|
100
|
+
"published_version_number": agent.get("published_version_number"),
|
|
101
|
+
"publish_history_id": agent.get("publish_history_id"),
|
|
102
|
+
"llm_model": agent.get("llm_model"),
|
|
103
|
+
"agent_type": agent.get("agent_type"),
|
|
104
|
+
"updated_at": agent.get("updated_at"),
|
|
105
|
+
"tool_count": len(tools),
|
|
106
|
+
"system_prompt_chars": len(agent.get("system_prompt") or ""),
|
|
107
|
+
}
|
|
108
|
+
if full:
|
|
109
|
+
row["description"] = agent.get("description") or ""
|
|
110
|
+
row["use_search"] = agent.get("use_search")
|
|
111
|
+
row["suggested_questions"] = agent.get("suggested_questions") or []
|
|
112
|
+
row["tools"] = _tool_summary(tools)
|
|
113
|
+
row["system_prompt_preview"] = truncate(agent.get("system_prompt") or "", 1200)
|
|
114
|
+
return row
|
|
115
|
+
|
|
116
|
+
|
|
51
117
|
|
|
52
118
|
def run_list(args, client) -> int:
|
|
53
119
|
ws, org = client.resolve_scope()
|
|
54
120
|
result = agents_mod.list_all(client, workspace_id=ws, organization_id=org)
|
|
55
|
-
|
|
121
|
+
write_json(args.out, strip_noisy_fields(result))
|
|
122
|
+
print_json([_agent_summary(a, full=args.full) for a in result])
|
|
56
123
|
return 0
|
|
57
124
|
|
|
58
125
|
|
|
59
126
|
def run_get(args, client) -> int:
|
|
60
127
|
result = agents_mod.get(client, args.agent_id)
|
|
61
|
-
|
|
128
|
+
full_result = strip_noisy_fields(result)
|
|
129
|
+
write_json(args.out, full_result)
|
|
130
|
+
print_json(full_result if args.full else _agent_summary(result))
|
|
62
131
|
return 0
|
|
63
132
|
|
|
64
133
|
|
|
65
134
|
def run_apply(args, client) -> int:
|
|
66
135
|
body = json.loads(Path(args.payload).read_text())
|
|
136
|
+
missing = [field for field in ("name", "system_prompt") if not body.get(field)]
|
|
137
|
+
if missing:
|
|
138
|
+
log(f"error: payload missing required field(s): {', '.join(missing)}")
|
|
139
|
+
return 2
|
|
140
|
+
|
|
141
|
+
if args.dry_run:
|
|
142
|
+
operation = "update" if args.agent_id else "create"
|
|
143
|
+
result = {
|
|
144
|
+
"dry_run": True,
|
|
145
|
+
"operation": operation,
|
|
146
|
+
"agent_id": args.agent_id,
|
|
147
|
+
"payload": str(Path(args.payload)),
|
|
148
|
+
"name": body.get("name"),
|
|
149
|
+
"system_prompt_chars": len(body.get("system_prompt") or ""),
|
|
150
|
+
"tool_count": len(body.get("unified_tools") or []),
|
|
151
|
+
"use_search": body.get("use_search", False),
|
|
152
|
+
"llm_model": body.get("llm_model"),
|
|
153
|
+
"version_note": args.note if args.agent_id else None,
|
|
154
|
+
"would_write_server_state": True,
|
|
155
|
+
"next_step": "Review this summary, then rerun without --dry-run after approval.",
|
|
156
|
+
}
|
|
157
|
+
print_json(result)
|
|
158
|
+
return 0
|
|
67
159
|
|
|
68
160
|
if args.agent_id:
|
|
69
161
|
body.pop("workspace_id", None)
|
|
@@ -118,7 +210,22 @@ def run_apply(args, client) -> int:
|
|
|
118
210
|
|
|
119
211
|
def run_versions(args, client) -> int:
|
|
120
212
|
versions = agents_mod.list_versions(client, args.agent)
|
|
121
|
-
|
|
213
|
+
write_json(args.out, strip_noisy_fields(versions))
|
|
214
|
+
rows = []
|
|
215
|
+
for v in versions:
|
|
216
|
+
row = {
|
|
217
|
+
"id": v.get("id"),
|
|
218
|
+
"version_number": v.get("version_number"),
|
|
219
|
+
"status": v.get("status"),
|
|
220
|
+
"was_published": v.get("was_published"),
|
|
221
|
+
"version_note": v.get("version_note") or "",
|
|
222
|
+
"created_at": v.get("created_at"),
|
|
223
|
+
}
|
|
224
|
+
if args.full:
|
|
225
|
+
row["system_prompt_chars"] = len(v.get("system_prompt") or "")
|
|
226
|
+
row["tool_count"] = len(v.get("unified_tools") or v.get("tools") or [])
|
|
227
|
+
rows.append(row)
|
|
228
|
+
print_json(rows)
|
|
122
229
|
return 0
|
|
123
230
|
|
|
124
231
|
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
from ..client import AuthError, CodeerError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def register(subparsers):
|
|
10
|
+
p = subparsers.add_parser("check", help="Validate auth, workspace, and agent config")
|
|
11
|
+
p.add_argument("--json", action="store_true", help="Print machine-readable setup status")
|
|
12
|
+
p.set_defaults(func=run)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def run(args, client) -> int:
|
|
16
|
+
errors = []
|
|
17
|
+
report = {
|
|
18
|
+
"status": "ok",
|
|
19
|
+
"auth": {"ok": False},
|
|
20
|
+
"workspace": {"ok": False},
|
|
21
|
+
"organization": {"ok": False},
|
|
22
|
+
"agent": {"ok": False, "configured": False, "optional": True},
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
me = client.get_me()
|
|
27
|
+
except AuthError:
|
|
28
|
+
if args.json:
|
|
29
|
+
report["status"] = "fail"
|
|
30
|
+
report["auth"]["error"] = "API key missing, invalid, expired, or revoked (401/403)"
|
|
31
|
+
report["next_step"] = "Create an admin workspace API key and export CODEER_API_KEY before running codeer"
|
|
32
|
+
print(json.dumps(report, ensure_ascii=False, indent=2))
|
|
33
|
+
return 1
|
|
34
|
+
print("FAIL Auth: API key missing, invalid, expired, or revoked (401/403)", file=sys.stderr)
|
|
35
|
+
print(" Create an admin workspace API key and export CODEER_API_KEY before running codeer", file=sys.stderr)
|
|
36
|
+
return 1
|
|
37
|
+
except CodeerError as e:
|
|
38
|
+
if args.json:
|
|
39
|
+
report["status"] = "fail"
|
|
40
|
+
report["auth"]["error"] = str(e)
|
|
41
|
+
print(json.dumps(report, ensure_ascii=False, indent=2))
|
|
42
|
+
return 1
|
|
43
|
+
print(f"FAIL Auth: {e}", file=sys.stderr)
|
|
44
|
+
return 1
|
|
45
|
+
|
|
46
|
+
profile = me.get("profile", {})
|
|
47
|
+
email = me.get("email") or "(unknown)"
|
|
48
|
+
report["auth"] = {"ok": True, "email": email}
|
|
49
|
+
if not args.json:
|
|
50
|
+
print(f" OK Auth: logged in as {email}")
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
ws_id, org_id = client.resolve_scope()
|
|
54
|
+
except CodeerError as e:
|
|
55
|
+
errors.append(f"FAIL Scope: {e.message if hasattr(e, 'message') else str(e)}")
|
|
56
|
+
ws_id, org_id = None, None
|
|
57
|
+
|
|
58
|
+
if ws_id:
|
|
59
|
+
ws_name = _workspace_name(profile, ws_id)
|
|
60
|
+
report["workspace"] = {"ok": True, "id": ws_id, "name": ws_name}
|
|
61
|
+
if ws_name:
|
|
62
|
+
if not args.json:
|
|
63
|
+
print(f" OK Workspace: {ws_name} ({ws_id})")
|
|
64
|
+
else:
|
|
65
|
+
if not args.json:
|
|
66
|
+
print(f" OK Workspace: {ws_id}")
|
|
67
|
+
|
|
68
|
+
if org_id:
|
|
69
|
+
report["organization"] = {"ok": True, "id": org_id}
|
|
70
|
+
if not args.json:
|
|
71
|
+
print(f" OK Organization: {org_id}")
|
|
72
|
+
|
|
73
|
+
agent_id = client.agent_id
|
|
74
|
+
if agent_id:
|
|
75
|
+
report["agent"] = {"ok": False, "configured": True, "optional": True, "id": agent_id}
|
|
76
|
+
try:
|
|
77
|
+
agent = client.get(f"/external/agents/{agent_id}")
|
|
78
|
+
report["agent"].update({"ok": True, "name": agent.get("name", "(unnamed)")})
|
|
79
|
+
if not args.json:
|
|
80
|
+
print(f" OK Agent: {agent.get('name', '(unnamed)')} ({agent_id})")
|
|
81
|
+
except CodeerError:
|
|
82
|
+
errors.append(f"WARN Agent: ID {agent_id} could not be read (may be in a different workspace)")
|
|
83
|
+
else:
|
|
84
|
+
if not args.json:
|
|
85
|
+
print(" -- Agent: CODEER_AGENT_ID not set (optional)")
|
|
86
|
+
|
|
87
|
+
if errors:
|
|
88
|
+
report["messages"] = errors
|
|
89
|
+
if any(e.startswith("FAIL") for e in errors):
|
|
90
|
+
report["status"] = "fail"
|
|
91
|
+
elif report["status"] == "ok":
|
|
92
|
+
report["status"] = "warn"
|
|
93
|
+
|
|
94
|
+
if args.json:
|
|
95
|
+
print(json.dumps(report, ensure_ascii=False, indent=2))
|
|
96
|
+
else:
|
|
97
|
+
for err in errors:
|
|
98
|
+
print(err, file=sys.stderr)
|
|
99
|
+
|
|
100
|
+
return 1 if any(e.startswith("FAIL") for e in errors) else 0
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _workspace_name(profile: dict, workspace_id: str) -> str | None:
|
|
104
|
+
for ws in profile.get("workspaces", []) or []:
|
|
105
|
+
if str(ws.get("id")) == str(workspace_id):
|
|
106
|
+
return ws.get("name")
|
|
107
|
+
return None
|