codeer-cli 0.1.0__py3-none-any.whl → 0.1.2__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/agents.py +5 -0
- codeer_cli/cli.py +36 -2
- codeer_cli/client.py +1 -2
- codeer_cli/commands/_util.py +54 -0
- codeer_cli/commands/agent.py +188 -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 +469 -6
- codeer_cli/commands/profile.py +1 -1
- codeer_cli/constants.py +1 -0
- codeer_cli/kb.py +107 -1
- codeer_cli-0.1.2.dist-info/METADATA +196 -0
- codeer_cli-0.1.2.dist-info/RECORD +23 -0
- {codeer_cli-0.1.0.dist-info → codeer_cli-0.1.2.dist-info}/WHEEL +1 -1
- codeer_cli-0.1.0.dist-info/METADATA +0 -108
- codeer_cli-0.1.0.dist-info/RECORD +0 -23
- {codeer_cli-0.1.0.dist-info → codeer_cli-0.1.2.dist-info}/entry_points.txt +0 -0
codeer_cli/agents.py
CHANGED
|
@@ -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={})
|
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/client.py
CHANGED
|
@@ -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)
|
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,119 @@ 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
|
|
|
72
|
+
p = sub.add_parser("impact", help="Check downstream agents affected by this agent")
|
|
73
|
+
p.add_argument("--agent", required=True)
|
|
74
|
+
p.add_argument("--out", default=None, help="Write full impact detail to this file too")
|
|
75
|
+
p.set_defaults(func=run_impact)
|
|
76
|
+
|
|
77
|
+
p = sub.add_parser("publish", help="Publish an agent version; run --dry-run first")
|
|
78
|
+
p.add_argument("--agent", required=True)
|
|
79
|
+
g = p.add_mutually_exclusive_group(required=True)
|
|
80
|
+
g.add_argument("--history", default=None, help="AgentHistory UUID to publish")
|
|
81
|
+
g.add_argument("--version", type=int, default=None, help="AgentHistory version_number to publish")
|
|
82
|
+
p.add_argument("--dry-run", action="store_true",
|
|
83
|
+
help="Resolve target version and print intended mutation without writing server state.")
|
|
84
|
+
p.add_argument("--out", default=None, help="Write result JSON to this file too")
|
|
85
|
+
p.set_defaults(func=run_publish)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _tool_summary(tools: list[dict] | None) -> list[dict]:
|
|
89
|
+
out = []
|
|
90
|
+
for t in tools or []:
|
|
91
|
+
form = t.get("custom_form_schema") if isinstance(t.get("custom_form_schema"), dict) else {}
|
|
92
|
+
out.append({
|
|
93
|
+
"id": t.get("id"),
|
|
94
|
+
"type": t.get("type"),
|
|
95
|
+
"name": t.get("name"),
|
|
96
|
+
"knowledge_node_count": len(t.get("knowledge_node_ids") or []),
|
|
97
|
+
"form_title": form.get("title"),
|
|
98
|
+
"invocation_preview": truncate(t.get("invocation_instruction") or "", 160),
|
|
99
|
+
})
|
|
100
|
+
return out
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _agent_summary(agent: dict, *, full: bool = False) -> dict:
|
|
104
|
+
tools = agent.get("unified_tools") or agent.get("tools") or []
|
|
105
|
+
row = {
|
|
106
|
+
"id": agent.get("id"),
|
|
107
|
+
"name": agent.get("name"),
|
|
108
|
+
"workspace": {
|
|
109
|
+
"id": agent.get("workspace_id") or (agent.get("workspace") or {}).get("id"),
|
|
110
|
+
"name": (agent.get("workspace") or {}).get("name"),
|
|
111
|
+
},
|
|
112
|
+
"publish_state": agent.get("publish_state"),
|
|
113
|
+
"version": agent.get("version"),
|
|
114
|
+
"latest_version_number": agent.get("latest_version_number"),
|
|
115
|
+
"published_version_number": agent.get("published_version_number"),
|
|
116
|
+
"publish_history_id": agent.get("publish_history_id"),
|
|
117
|
+
"llm_model": agent.get("llm_model"),
|
|
118
|
+
"agent_type": agent.get("agent_type"),
|
|
119
|
+
"updated_at": agent.get("updated_at"),
|
|
120
|
+
"tool_count": len(tools),
|
|
121
|
+
"system_prompt_chars": len(agent.get("system_prompt") or ""),
|
|
122
|
+
}
|
|
123
|
+
if full:
|
|
124
|
+
row["description"] = agent.get("description") or ""
|
|
125
|
+
row["use_search"] = agent.get("use_search")
|
|
126
|
+
row["suggested_questions"] = agent.get("suggested_questions") or []
|
|
127
|
+
row["tools"] = _tool_summary(tools)
|
|
128
|
+
row["system_prompt_preview"] = truncate(agent.get("system_prompt") or "", 1200)
|
|
129
|
+
return row
|
|
130
|
+
|
|
50
131
|
|
|
51
132
|
|
|
52
133
|
def run_list(args, client) -> int:
|
|
53
134
|
ws, org = client.resolve_scope()
|
|
54
135
|
result = agents_mod.list_all(client, workspace_id=ws, organization_id=org)
|
|
55
|
-
|
|
136
|
+
write_json(args.out, strip_noisy_fields(result))
|
|
137
|
+
print_json([_agent_summary(a, full=args.full) for a in result])
|
|
56
138
|
return 0
|
|
57
139
|
|
|
58
140
|
|
|
59
141
|
def run_get(args, client) -> int:
|
|
60
142
|
result = agents_mod.get(client, args.agent_id)
|
|
61
|
-
|
|
143
|
+
full_result = strip_noisy_fields(result)
|
|
144
|
+
write_json(args.out, full_result)
|
|
145
|
+
print_json(full_result if args.full else _agent_summary(result))
|
|
62
146
|
return 0
|
|
63
147
|
|
|
64
148
|
|
|
65
149
|
def run_apply(args, client) -> int:
|
|
66
150
|
body = json.loads(Path(args.payload).read_text())
|
|
151
|
+
missing = [field for field in ("name", "system_prompt") if not body.get(field)]
|
|
152
|
+
if missing:
|
|
153
|
+
log(f"error: payload missing required field(s): {', '.join(missing)}")
|
|
154
|
+
return 2
|
|
155
|
+
|
|
156
|
+
if args.dry_run:
|
|
157
|
+
operation = "update" if args.agent_id else "create"
|
|
158
|
+
result = {
|
|
159
|
+
"dry_run": True,
|
|
160
|
+
"operation": operation,
|
|
161
|
+
"agent_id": args.agent_id,
|
|
162
|
+
"payload": str(Path(args.payload)),
|
|
163
|
+
"name": body.get("name"),
|
|
164
|
+
"system_prompt_chars": len(body.get("system_prompt") or ""),
|
|
165
|
+
"tool_count": len(body.get("unified_tools") or []),
|
|
166
|
+
"use_search": body.get("use_search", False),
|
|
167
|
+
"llm_model": body.get("llm_model"),
|
|
168
|
+
"version_note": args.note if args.agent_id else None,
|
|
169
|
+
"would_write_server_state": True,
|
|
170
|
+
"next_step": "Review this summary, then rerun without --dry-run after approval.",
|
|
171
|
+
}
|
|
172
|
+
print_json(result)
|
|
173
|
+
return 0
|
|
67
174
|
|
|
68
175
|
if args.agent_id:
|
|
69
176
|
body.pop("workspace_id", None)
|
|
@@ -118,7 +225,78 @@ def run_apply(args, client) -> int:
|
|
|
118
225
|
|
|
119
226
|
def run_versions(args, client) -> int:
|
|
120
227
|
versions = agents_mod.list_versions(client, args.agent)
|
|
121
|
-
|
|
228
|
+
write_json(args.out, strip_noisy_fields(versions))
|
|
229
|
+
rows = []
|
|
230
|
+
for v in versions:
|
|
231
|
+
row = {
|
|
232
|
+
"id": v.get("id"),
|
|
233
|
+
"version_number": v.get("version_number"),
|
|
234
|
+
"status": v.get("status"),
|
|
235
|
+
"was_published": v.get("was_published"),
|
|
236
|
+
"version_note": v.get("version_note") or "",
|
|
237
|
+
"created_at": v.get("created_at"),
|
|
238
|
+
}
|
|
239
|
+
if args.full:
|
|
240
|
+
row["system_prompt_chars"] = len(v.get("system_prompt") or "")
|
|
241
|
+
row["tool_count"] = len(v.get("unified_tools") or v.get("tools") or [])
|
|
242
|
+
rows.append(row)
|
|
243
|
+
print_json(rows)
|
|
244
|
+
return 0
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def run_impact(args, client) -> int:
|
|
248
|
+
result = strip_noisy_fields(agents_mod.check_impact(client, args.agent))
|
|
249
|
+
write_json(args.out, result)
|
|
250
|
+
print_json(result)
|
|
251
|
+
return 0
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _resolve_history_for_publish(
|
|
255
|
+
client: CodeerClient,
|
|
256
|
+
agent_id: str,
|
|
257
|
+
history_id: Optional[str],
|
|
258
|
+
version: Optional[int],
|
|
259
|
+
) -> dict:
|
|
260
|
+
if history_id:
|
|
261
|
+
return agents_mod.get_version(client, agent_id, history_id)
|
|
262
|
+
if version is not None:
|
|
263
|
+
for candidate in agents_mod.list_versions(client, agent_id):
|
|
264
|
+
if candidate.get("version_number") == version:
|
|
265
|
+
return agents_mod.get_version(client, agent_id, candidate["id"])
|
|
266
|
+
raise SystemExit(f"no version {version} on agent {agent_id}")
|
|
267
|
+
raise SystemExit("must pass --history or --version")
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def run_publish(args, client) -> int:
|
|
271
|
+
history = _resolve_history_for_publish(client, args.agent, args.history, args.version)
|
|
272
|
+
history_id = history["id"]
|
|
273
|
+
summary = {
|
|
274
|
+
"agent_id": args.agent,
|
|
275
|
+
"history_id": history_id,
|
|
276
|
+
"version_number": history.get("version_number"),
|
|
277
|
+
"status": history.get("status"),
|
|
278
|
+
"was_published": history.get("was_published"),
|
|
279
|
+
"version_note": history.get("version_note") or "",
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if args.dry_run:
|
|
283
|
+
result = {
|
|
284
|
+
"dry_run": True,
|
|
285
|
+
"operation": "agent_publish",
|
|
286
|
+
"method": "POST",
|
|
287
|
+
"path": f"/external/agents/{args.agent}/versions/{history_id}:publish",
|
|
288
|
+
"target": summary,
|
|
289
|
+
"would_write_server_state": True,
|
|
290
|
+
"next_step": "Review this summary, then rerun without --dry-run after approval.",
|
|
291
|
+
}
|
|
292
|
+
print_json(result)
|
|
293
|
+
write_json(args.out, result)
|
|
294
|
+
return 0
|
|
295
|
+
|
|
296
|
+
result = strip_noisy_fields(agents_mod.publish_version(client, args.agent, history_id))
|
|
297
|
+
output = {"target": summary, "response": result}
|
|
298
|
+
print_json(output)
|
|
299
|
+
write_json(args.out, output)
|
|
122
300
|
return 0
|
|
123
301
|
|
|
124
302
|
|
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
|
|