codeer-cli 0.1.0__py3-none-any.whl → 0.1.1__py3-none-any.whl
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/cli.py +36 -2
- codeer_cli/commands/_util.py +54 -0
- codeer_cli/commands/agent.py +117 -10
- codeer_cli/commands/check.py +50 -9
- codeer_cli/commands/eval_cmd.py +232 -28
- codeer_cli/commands/history.py +117 -12
- codeer_cli/commands/kb.py +230 -6
- codeer_cli/kb.py +52 -0
- {codeer_cli-0.1.0.dist-info → codeer_cli-0.1.1.dist-info}/METADATA +43 -2
- {codeer_cli-0.1.0.dist-info → codeer_cli-0.1.1.dist-info}/RECORD +12 -12
- {codeer_cli-0.1.0.dist-info → codeer_cli-0.1.1.dist-info}/WHEEL +0 -0
- {codeer_cli-0.1.0.dist-info → codeer_cli-0.1.1.dist-info}/entry_points.txt +0 -0
codeer_cli/cli.py
CHANGED
|
@@ -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
|
codeer_cli/commands/_util.py
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import json
|
|
3
4
|
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
4
7
|
|
|
5
8
|
|
|
6
9
|
def log(*args, **kwargs):
|
|
@@ -10,3 +13,54 @@ def log(*args, **kwargs):
|
|
|
10
13
|
def truncate(text: str, n: int = 60) -> str:
|
|
11
14
|
text = text.replace("\n", " ").strip()
|
|
12
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}")
|
codeer_cli/commands/agent.py
CHANGED
|
@@ -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
|
|
codeer_cli/commands/check.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import json
|
|
3
4
|
import sys
|
|
4
5
|
|
|
5
6
|
from ..client import AuthError, CodeerError
|
|
@@ -7,25 +8,46 @@ from ..client import AuthError, CodeerError
|
|
|
7
8
|
|
|
8
9
|
def register(subparsers):
|
|
9
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")
|
|
10
12
|
p.set_defaults(func=run)
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
def run(args, client) -> int:
|
|
14
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
|
+
}
|
|
15
24
|
|
|
16
25
|
try:
|
|
17
26
|
me = client.get_me()
|
|
18
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
|
|
19
34
|
print("FAIL Auth: API key missing, invalid, expired, or revoked (401/403)", file=sys.stderr)
|
|
20
35
|
print(" Create an admin workspace API key and export CODEER_API_KEY before running codeer", file=sys.stderr)
|
|
21
36
|
return 1
|
|
22
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
|
|
23
43
|
print(f"FAIL Auth: {e}", file=sys.stderr)
|
|
24
44
|
return 1
|
|
25
45
|
|
|
26
46
|
profile = me.get("profile", {})
|
|
27
47
|
email = me.get("email") or "(unknown)"
|
|
28
|
-
|
|
48
|
+
report["auth"] = {"ok": True, "email": email}
|
|
49
|
+
if not args.json:
|
|
50
|
+
print(f" OK Auth: logged in as {email}")
|
|
29
51
|
|
|
30
52
|
try:
|
|
31
53
|
ws_id, org_id = client.resolve_scope()
|
|
@@ -35,26 +57,45 @@ def run(args, client) -> int:
|
|
|
35
57
|
|
|
36
58
|
if ws_id:
|
|
37
59
|
ws_name = _workspace_name(profile, ws_id)
|
|
60
|
+
report["workspace"] = {"ok": True, "id": ws_id, "name": ws_name}
|
|
38
61
|
if ws_name:
|
|
39
|
-
|
|
62
|
+
if not args.json:
|
|
63
|
+
print(f" OK Workspace: {ws_name} ({ws_id})")
|
|
40
64
|
else:
|
|
41
|
-
|
|
65
|
+
if not args.json:
|
|
66
|
+
print(f" OK Workspace: {ws_id}")
|
|
42
67
|
|
|
43
68
|
if org_id:
|
|
44
|
-
|
|
69
|
+
report["organization"] = {"ok": True, "id": org_id}
|
|
70
|
+
if not args.json:
|
|
71
|
+
print(f" OK Organization: {org_id}")
|
|
45
72
|
|
|
46
73
|
agent_id = client.agent_id
|
|
47
74
|
if agent_id:
|
|
75
|
+
report["agent"] = {"ok": False, "configured": True, "optional": True, "id": agent_id}
|
|
48
76
|
try:
|
|
49
77
|
agent = client.get(f"/external/agents/{agent_id}")
|
|
50
|
-
|
|
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})")
|
|
51
81
|
except CodeerError:
|
|
52
82
|
errors.append(f"WARN Agent: ID {agent_id} could not be read (may be in a different workspace)")
|
|
53
83
|
else:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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)
|
|
58
99
|
|
|
59
100
|
return 1 if any(e.startswith("FAIL") for e in errors) else 0
|
|
60
101
|
|