codeer-cli 0.1.1__py3-none-any.whl → 0.1.3__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/client.py +1 -2
- codeer_cli/commands/agent.py +71 -0
- codeer_cli/commands/kb.py +267 -2
- codeer_cli/commands/profile.py +1 -1
- codeer_cli/constants.py +1 -0
- codeer_cli/kb.py +63 -5
- {codeer_cli-0.1.1.dist-info → codeer_cli-0.1.3.dist-info}/METADATA +60 -11
- {codeer_cli-0.1.1.dist-info → codeer_cli-0.1.3.dist-info}/RECORD +11 -11
- {codeer_cli-0.1.1.dist-info → codeer_cli-0.1.3.dist-info}/WHEEL +1 -1
- {codeer_cli-0.1.1.dist-info → codeer_cli-0.1.3.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/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/agent.py
CHANGED
|
@@ -69,6 +69,21 @@ def register(subparsers):
|
|
|
69
69
|
help="Write stripped full version snapshots to this file; stdout stays compact.")
|
|
70
70
|
p.set_defaults(func=run_versions)
|
|
71
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
|
+
|
|
72
87
|
|
|
73
88
|
def _tool_summary(tools: list[dict] | None) -> list[dict]:
|
|
74
89
|
out = []
|
|
@@ -229,6 +244,62 @@ def run_versions(args, client) -> int:
|
|
|
229
244
|
return 0
|
|
230
245
|
|
|
231
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)
|
|
300
|
+
return 0
|
|
301
|
+
|
|
302
|
+
|
|
232
303
|
|
|
233
304
|
# --- diff helpers ---
|
|
234
305
|
|
codeer_cli/commands/kb.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import argparse
|
|
3
4
|
import json
|
|
4
5
|
import time
|
|
5
6
|
from pathlib import Path
|
|
@@ -11,6 +12,42 @@ POLL_INTERVAL = 3
|
|
|
11
12
|
POLL_TIMEOUT = 600
|
|
12
13
|
|
|
13
14
|
|
|
15
|
+
def _add_crawl_config_args(parser):
|
|
16
|
+
parser.add_argument("--config-json", default=None, help="JSON object for crawl_config")
|
|
17
|
+
parser.add_argument("--limit", type=int, default=None, help="Maximum pages to crawl (backend max: 5000)")
|
|
18
|
+
parser.add_argument("--max-depth", type=int, default=None, help="Maximum crawl depth (backend max: 10)")
|
|
19
|
+
parser.add_argument(
|
|
20
|
+
"--include-path",
|
|
21
|
+
action="append",
|
|
22
|
+
dest="include_paths",
|
|
23
|
+
default=None,
|
|
24
|
+
help="Clean path pattern to include; repeatable. Supports * wildcards.",
|
|
25
|
+
)
|
|
26
|
+
parser.add_argument(
|
|
27
|
+
"--exclude-path",
|
|
28
|
+
action="append",
|
|
29
|
+
dest="exclude_paths",
|
|
30
|
+
default=None,
|
|
31
|
+
help="Clean path pattern to exclude; repeatable. Supports * wildcards.",
|
|
32
|
+
)
|
|
33
|
+
parser.add_argument("--allow-subdomains", action="store_true", default=None,
|
|
34
|
+
help="Allow crawling subdomains of the start URL host")
|
|
35
|
+
parser.add_argument("--allow-external-links", action="store_true", default=None,
|
|
36
|
+
help="Allow crawling links outside the start URL host")
|
|
37
|
+
parser.add_argument("--ignore-query-parameters", action="store_true", dest="ignore_query_parameters", default=None,
|
|
38
|
+
help="Treat URLs that differ only by query string as the same page")
|
|
39
|
+
parser.add_argument("--use-query-parameters", action="store_false", dest="ignore_query_parameters",
|
|
40
|
+
help="Treat URLs with different query strings as distinct pages")
|
|
41
|
+
parser.add_argument("--ignore-sitemap", action="store_true", dest="ignore_sitemap", default=None,
|
|
42
|
+
help="Skip sitemap discovery")
|
|
43
|
+
parser.add_argument("--use-sitemap", action="store_false", dest="ignore_sitemap",
|
|
44
|
+
help="Allow sitemap discovery")
|
|
45
|
+
parser.add_argument("--only-main-content", action="store_true", dest="only_main_content", default=None,
|
|
46
|
+
help="Extract only the main content area")
|
|
47
|
+
parser.add_argument("--include-page-chrome", action="store_false", dest="only_main_content",
|
|
48
|
+
help="Keep page navigation, footer, and other chrome")
|
|
49
|
+
|
|
50
|
+
|
|
14
51
|
def register(subparsers):
|
|
15
52
|
k = subparsers.add_parser("kb", help="Knowledge base operations")
|
|
16
53
|
sub = k.add_subparsers(dest="action", required=True)
|
|
@@ -70,6 +107,8 @@ def register(subparsers):
|
|
|
70
107
|
p.add_argument("--context-object-id", type=int, required=True,
|
|
71
108
|
help="KB file snapshot_object_id from `codeer kb files`")
|
|
72
109
|
p.add_argument("--question", required=True)
|
|
110
|
+
p.add_argument("--range", dest="ranges", action="append", type=_parse_faq_range, default=None,
|
|
111
|
+
help="Reserve matching chunks that overlap START_LINE:END_LINE; repeatable")
|
|
73
112
|
p.add_argument("--dry-run", action="store_true",
|
|
74
113
|
help="Print intended request without writing server state.")
|
|
75
114
|
p.add_argument("--out", default=None, help="Write result JSON to this file too")
|
|
@@ -80,6 +119,8 @@ def register(subparsers):
|
|
|
80
119
|
p.add_argument("--context-object-id", type=int, default=None,
|
|
81
120
|
help="Move FAQ to a different KB file snapshot_object_id")
|
|
82
121
|
p.add_argument("--question", default=None)
|
|
122
|
+
p.add_argument("--range", dest="ranges", action="append", type=_parse_faq_range, default=None,
|
|
123
|
+
help="Replace reserved ranges with START_LINE:END_LINE; repeatable")
|
|
83
124
|
p.add_argument("--dry-run", action="store_true",
|
|
84
125
|
help="Print intended request without writing server state.")
|
|
85
126
|
p.add_argument("--out", default=None, help="Write result JSON to this file too")
|
|
@@ -92,6 +133,51 @@ def register(subparsers):
|
|
|
92
133
|
p.add_argument("--out", default=None, help="Write result JSON to this file too")
|
|
93
134
|
p.set_defaults(func=run_faq_delete)
|
|
94
135
|
|
|
136
|
+
p = sub.add_parser("crawl-create", help="Create a website-crawler KB folder; run --dry-run first")
|
|
137
|
+
p.add_argument("--url", required=True, help="Starting URL to crawl")
|
|
138
|
+
p.add_argument("--folder-name", default=None, help="KB folder name")
|
|
139
|
+
_add_crawl_config_args(p)
|
|
140
|
+
p.add_argument("--dry-run", action="store_true",
|
|
141
|
+
help="Print intended request without writing server state.")
|
|
142
|
+
p.add_argument("--out", default=None, help="Write result JSON to this file too")
|
|
143
|
+
p.set_defaults(func=run_crawl_create)
|
|
144
|
+
|
|
145
|
+
p = sub.add_parser("crawl-update", help="Update a website crawl target; run --dry-run first")
|
|
146
|
+
p.add_argument("--target-id", type=int, required=True)
|
|
147
|
+
p.add_argument("--url", required=True, help="Updated starting URL")
|
|
148
|
+
_add_crawl_config_args(p)
|
|
149
|
+
p.add_argument("--dry-run", action="store_true",
|
|
150
|
+
help="Print intended request without writing server state.")
|
|
151
|
+
p.add_argument("--out", default=None, help="Write result JSON to this file too")
|
|
152
|
+
p.set_defaults(func=run_crawl_update)
|
|
153
|
+
|
|
154
|
+
p = sub.add_parser("crawl-state", help="Read website crawl state for a crawler folder")
|
|
155
|
+
p.add_argument("--folder-id", required=True, help="KnowledgeNode folder UUID")
|
|
156
|
+
p.add_argument("--out", default=None, help="Write result JSON to this file too")
|
|
157
|
+
p.set_defaults(func=run_crawl_state)
|
|
158
|
+
|
|
159
|
+
p = sub.add_parser("crawl-sync", help="Start a website crawl sync job; run --dry-run first")
|
|
160
|
+
p.add_argument("--target-id", type=int, required=True)
|
|
161
|
+
p.add_argument("--dry-run", action="store_true",
|
|
162
|
+
help="Print intended request without writing server state.")
|
|
163
|
+
p.add_argument("--out", default=None, help="Write result JSON to this file too")
|
|
164
|
+
p.set_defaults(func=run_crawl_sync)
|
|
165
|
+
|
|
166
|
+
p = sub.add_parser("crawl-cancel", help="Cancel the active website crawl job; run --dry-run first")
|
|
167
|
+
p.add_argument("--target-id", type=int, required=True)
|
|
168
|
+
p.add_argument("--dry-run", action="store_true",
|
|
169
|
+
help="Print intended request without writing server state.")
|
|
170
|
+
p.add_argument("--out", default=None, help="Write result JSON to this file too")
|
|
171
|
+
p.set_defaults(func=run_crawl_cancel)
|
|
172
|
+
|
|
173
|
+
p = sub.add_parser("crawl-failures", help="List failed pages for a website crawl job")
|
|
174
|
+
p.add_argument("--job-id", type=int, required=True)
|
|
175
|
+
p.add_argument("--status", default="DOWNLOAD_FAILED,FAILED")
|
|
176
|
+
p.add_argument("--limit", type=int, default=50)
|
|
177
|
+
p.add_argument("--offset", type=int, default=0)
|
|
178
|
+
p.add_argument("--out", default=None, help="Write result JSON to this file too")
|
|
179
|
+
p.set_defaults(func=run_crawl_failures)
|
|
180
|
+
|
|
95
181
|
|
|
96
182
|
def _node_summary(node: dict) -> dict:
|
|
97
183
|
return {
|
|
@@ -114,6 +200,7 @@ def _faq_summary(faq: dict) -> dict:
|
|
|
114
200
|
"id": faq.get("id"),
|
|
115
201
|
"context_object_id": faq.get("context_object_id"),
|
|
116
202
|
"question": faq.get("question"),
|
|
203
|
+
"ranges": faq.get("ranges") or [],
|
|
117
204
|
"has_question_embedding": faq.get("has_question_embedding"),
|
|
118
205
|
"updated_at": faq.get("updated_at"),
|
|
119
206
|
}
|
|
@@ -130,6 +217,54 @@ def _dry_run(path: str | None, result: dict) -> int:
|
|
|
130
217
|
return 0
|
|
131
218
|
|
|
132
219
|
|
|
220
|
+
def _parse_faq_range(value: str) -> dict[str, int]:
|
|
221
|
+
try:
|
|
222
|
+
start_raw, end_raw = value.split(":", 1)
|
|
223
|
+
start_line = int(start_raw)
|
|
224
|
+
end_line = int(end_raw)
|
|
225
|
+
except ValueError as exc:
|
|
226
|
+
raise argparse.ArgumentTypeError("expected START_LINE:END_LINE") from exc
|
|
227
|
+
if start_line < 1 or end_line < 1:
|
|
228
|
+
raise argparse.ArgumentTypeError("line numbers must be >= 1")
|
|
229
|
+
if end_line < start_line:
|
|
230
|
+
raise argparse.ArgumentTypeError("END_LINE must be >= START_LINE")
|
|
231
|
+
return {"start_line": start_line, "end_line": end_line}
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _parse_config_json(config_json: str | None) -> dict | None:
|
|
235
|
+
if not config_json:
|
|
236
|
+
return None
|
|
237
|
+
value = json.loads(config_json)
|
|
238
|
+
if not isinstance(value, dict):
|
|
239
|
+
raise SystemExit("--config-json must decode to a JSON object")
|
|
240
|
+
return value
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _crawl_config_from_args(args) -> dict | None:
|
|
244
|
+
config = _parse_config_json(args.config_json)
|
|
245
|
+
has_config = config is not None
|
|
246
|
+
if config is None:
|
|
247
|
+
config = {}
|
|
248
|
+
|
|
249
|
+
for key, attr in (
|
|
250
|
+
("limit", "limit"),
|
|
251
|
+
("maxDepth", "max_depth"),
|
|
252
|
+
("includePaths", "include_paths"),
|
|
253
|
+
("excludePaths", "exclude_paths"),
|
|
254
|
+
("allowSubdomains", "allow_subdomains"),
|
|
255
|
+
("allowExternalLinks", "allow_external_links"),
|
|
256
|
+
("ignoreQueryParameters", "ignore_query_parameters"),
|
|
257
|
+
("ignoreSitemap", "ignore_sitemap"),
|
|
258
|
+
("onlyMainContent", "only_main_content"),
|
|
259
|
+
):
|
|
260
|
+
value = getattr(args, attr)
|
|
261
|
+
if value is not None:
|
|
262
|
+
config[key] = value
|
|
263
|
+
has_config = True
|
|
264
|
+
|
|
265
|
+
return config if has_config else None
|
|
266
|
+
|
|
267
|
+
|
|
133
268
|
def run_list(args, client) -> int:
|
|
134
269
|
workspace_id, organization_id = client.resolve_scope()
|
|
135
270
|
nodes = kb_mod.list_nodes(
|
|
@@ -269,6 +404,8 @@ def run_faq_get(args, client) -> int:
|
|
|
269
404
|
|
|
270
405
|
def run_faq_create(args, client) -> int:
|
|
271
406
|
body = {"context_object_id": args.context_object_id, "question": args.question}
|
|
407
|
+
if args.ranges is not None:
|
|
408
|
+
body["ranges"] = args.ranges
|
|
272
409
|
if args.dry_run:
|
|
273
410
|
return _dry_run(
|
|
274
411
|
args.out,
|
|
@@ -288,6 +425,7 @@ def run_faq_create(args, client) -> int:
|
|
|
288
425
|
client,
|
|
289
426
|
context_object_id=args.context_object_id,
|
|
290
427
|
question=args.question,
|
|
428
|
+
ranges=args.ranges,
|
|
291
429
|
)
|
|
292
430
|
)
|
|
293
431
|
_print_and_write(args.out, faq)
|
|
@@ -295,8 +433,8 @@ def run_faq_create(args, client) -> int:
|
|
|
295
433
|
|
|
296
434
|
|
|
297
435
|
def run_faq_update(args, client) -> int:
|
|
298
|
-
if args.context_object_id is None and args.question is None:
|
|
299
|
-
log("error: provide --context-object-id or --
|
|
436
|
+
if args.context_object_id is None and args.question is None and args.ranges is None:
|
|
437
|
+
log("error: provide --context-object-id, --question, or --range")
|
|
300
438
|
return 2
|
|
301
439
|
|
|
302
440
|
body: dict[str, object] = {}
|
|
@@ -304,6 +442,8 @@ def run_faq_update(args, client) -> int:
|
|
|
304
442
|
body["context_object_id"] = args.context_object_id
|
|
305
443
|
if args.question is not None:
|
|
306
444
|
body["question"] = args.question
|
|
445
|
+
if args.ranges is not None:
|
|
446
|
+
body["ranges"] = args.ranges
|
|
307
447
|
|
|
308
448
|
if args.dry_run:
|
|
309
449
|
return _dry_run(
|
|
@@ -325,6 +465,7 @@ def run_faq_update(args, client) -> int:
|
|
|
325
465
|
faq_id=args.faq_id,
|
|
326
466
|
context_object_id=args.context_object_id,
|
|
327
467
|
question=args.question,
|
|
468
|
+
ranges=args.ranges,
|
|
328
469
|
)
|
|
329
470
|
)
|
|
330
471
|
_print_and_write(args.out, faq)
|
|
@@ -348,3 +489,127 @@ def run_faq_delete(args, client) -> int:
|
|
|
348
489
|
response = strip_noisy_fields(kb_mod.delete_context_obj_faq(client, faq_id=args.faq_id))
|
|
349
490
|
_print_and_write(args.out, response)
|
|
350
491
|
return 0
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
def run_crawl_create(args, client) -> int:
|
|
495
|
+
crawl_config = _crawl_config_from_args(args)
|
|
496
|
+
body: dict[str, object] = {"start_url": args.url}
|
|
497
|
+
if args.folder_name is not None:
|
|
498
|
+
body["folder_name"] = args.folder_name
|
|
499
|
+
if crawl_config is not None:
|
|
500
|
+
body["crawl_config"] = crawl_config
|
|
501
|
+
|
|
502
|
+
if args.dry_run:
|
|
503
|
+
return _dry_run(
|
|
504
|
+
args.out,
|
|
505
|
+
{
|
|
506
|
+
"dry_run": True,
|
|
507
|
+
"operation": "kb_crawl_create",
|
|
508
|
+
"method": "POST",
|
|
509
|
+
"path": "/external/knowledge-bases/website-crawls",
|
|
510
|
+
"body": body,
|
|
511
|
+
"would_write_server_state": True,
|
|
512
|
+
"next_step": "Review this summary, then rerun without --dry-run after approval.",
|
|
513
|
+
},
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
response = strip_noisy_fields(
|
|
517
|
+
kb_mod.create_website_crawl(
|
|
518
|
+
client,
|
|
519
|
+
start_url=args.url,
|
|
520
|
+
folder_name=args.folder_name,
|
|
521
|
+
crawl_config=crawl_config,
|
|
522
|
+
)
|
|
523
|
+
)
|
|
524
|
+
_print_and_write(args.out, response)
|
|
525
|
+
return 0
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def run_crawl_update(args, client) -> int:
|
|
529
|
+
crawl_config = _crawl_config_from_args(args)
|
|
530
|
+
body: dict[str, object] = {"start_url": args.url}
|
|
531
|
+
if crawl_config is not None:
|
|
532
|
+
body["crawl_config"] = crawl_config
|
|
533
|
+
|
|
534
|
+
if args.dry_run:
|
|
535
|
+
return _dry_run(
|
|
536
|
+
args.out,
|
|
537
|
+
{
|
|
538
|
+
"dry_run": True,
|
|
539
|
+
"operation": "kb_crawl_update",
|
|
540
|
+
"method": "PATCH",
|
|
541
|
+
"path": f"/external/knowledge-bases/website-crawls/{args.target_id}",
|
|
542
|
+
"body": body,
|
|
543
|
+
"would_write_server_state": True,
|
|
544
|
+
"next_step": "Review this summary, then rerun without --dry-run after approval.",
|
|
545
|
+
},
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
response = strip_noisy_fields(
|
|
549
|
+
kb_mod.update_website_crawl(
|
|
550
|
+
client,
|
|
551
|
+
target_id=args.target_id,
|
|
552
|
+
start_url=args.url,
|
|
553
|
+
crawl_config=crawl_config,
|
|
554
|
+
)
|
|
555
|
+
)
|
|
556
|
+
_print_and_write(args.out, response)
|
|
557
|
+
return 0
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
def run_crawl_state(args, client) -> int:
|
|
561
|
+
response = strip_noisy_fields(kb_mod.get_website_crawl_state(client, folder_id=args.folder_id))
|
|
562
|
+
_print_and_write(args.out, response)
|
|
563
|
+
return 0
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
def run_crawl_sync(args, client) -> int:
|
|
567
|
+
if args.dry_run:
|
|
568
|
+
return _dry_run(
|
|
569
|
+
args.out,
|
|
570
|
+
{
|
|
571
|
+
"dry_run": True,
|
|
572
|
+
"operation": "kb_crawl_sync",
|
|
573
|
+
"method": "POST",
|
|
574
|
+
"path": f"/external/knowledge-bases/website-crawls/{args.target_id}:sync",
|
|
575
|
+
"would_write_server_state": True,
|
|
576
|
+
"next_step": "Review this summary, then rerun without --dry-run after approval.",
|
|
577
|
+
},
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
response = strip_noisy_fields(kb_mod.sync_website_crawl(client, target_id=args.target_id))
|
|
581
|
+
_print_and_write(args.out, response)
|
|
582
|
+
return 0
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
def run_crawl_cancel(args, client) -> int:
|
|
586
|
+
if args.dry_run:
|
|
587
|
+
return _dry_run(
|
|
588
|
+
args.out,
|
|
589
|
+
{
|
|
590
|
+
"dry_run": True,
|
|
591
|
+
"operation": "kb_crawl_cancel",
|
|
592
|
+
"method": "POST",
|
|
593
|
+
"path": f"/external/knowledge-bases/website-crawls/{args.target_id}:cancel",
|
|
594
|
+
"would_write_server_state": True,
|
|
595
|
+
"next_step": "Review this summary, then rerun without --dry-run after approval.",
|
|
596
|
+
},
|
|
597
|
+
)
|
|
598
|
+
|
|
599
|
+
response = strip_noisy_fields(kb_mod.cancel_website_crawl(client, target_id=args.target_id))
|
|
600
|
+
_print_and_write(args.out, response)
|
|
601
|
+
return 0
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
def run_crawl_failures(args, client) -> int:
|
|
605
|
+
response = strip_noisy_fields(
|
|
606
|
+
kb_mod.get_website_crawl_failures(
|
|
607
|
+
client,
|
|
608
|
+
job_id=args.job_id,
|
|
609
|
+
status=args.status,
|
|
610
|
+
limit=args.limit,
|
|
611
|
+
offset=args.offset,
|
|
612
|
+
)
|
|
613
|
+
)
|
|
614
|
+
_print_and_write(args.out, response)
|
|
615
|
+
return 0
|
codeer_cli/commands/profile.py
CHANGED
codeer_cli/constants.py
CHANGED
codeer_cli/kb.py
CHANGED
|
@@ -36,7 +36,7 @@ def _guess_mime(filename: str) -> str:
|
|
|
36
36
|
return guess or "application/octet-stream"
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
def _base(organization_id: str, workspace_id: str) -> str:
|
|
39
|
+
def _base(organization_id: str = "", workspace_id: str = "") -> str:
|
|
40
40
|
return "/external/knowledge-bases"
|
|
41
41
|
|
|
42
42
|
|
|
@@ -252,11 +252,12 @@ def create_context_obj_faq(
|
|
|
252
252
|
*,
|
|
253
253
|
context_object_id: int,
|
|
254
254
|
question: str,
|
|
255
|
+
ranges: Optional[list[dict[str, int]]] = None,
|
|
255
256
|
) -> dict:
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
)
|
|
257
|
+
body: dict[str, Any] = {"context_object_id": context_object_id, "question": question}
|
|
258
|
+
if ranges is not None:
|
|
259
|
+
body["ranges"] = ranges
|
|
260
|
+
return client.post(_faq_base(), json=body)
|
|
260
261
|
|
|
261
262
|
|
|
262
263
|
def update_context_obj_faq(
|
|
@@ -265,14 +266,71 @@ def update_context_obj_faq(
|
|
|
265
266
|
faq_id: int,
|
|
266
267
|
context_object_id: Optional[int] = None,
|
|
267
268
|
question: Optional[str] = None,
|
|
269
|
+
ranges: Optional[list[dict[str, int]]] = None,
|
|
268
270
|
) -> dict:
|
|
269
271
|
body: dict[str, Any] = {}
|
|
270
272
|
if context_object_id is not None:
|
|
271
273
|
body["context_object_id"] = context_object_id
|
|
272
274
|
if question is not None:
|
|
273
275
|
body["question"] = question
|
|
276
|
+
if ranges is not None:
|
|
277
|
+
body["ranges"] = ranges
|
|
274
278
|
return client.patch(f"{_faq_base()}/{faq_id}", json=body)
|
|
275
279
|
|
|
276
280
|
|
|
277
281
|
def delete_context_obj_faq(client: CodeerClient, *, faq_id: int) -> dict:
|
|
278
282
|
return client.delete(f"{_faq_base()}/{faq_id}")
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def create_website_crawl(
|
|
286
|
+
client: CodeerClient,
|
|
287
|
+
*,
|
|
288
|
+
start_url: str,
|
|
289
|
+
folder_name: Optional[str] = None,
|
|
290
|
+
crawl_config: Optional[dict[str, Any]] = None,
|
|
291
|
+
) -> dict:
|
|
292
|
+
body: dict[str, Any] = {"start_url": start_url}
|
|
293
|
+
if folder_name is not None:
|
|
294
|
+
body["folder_name"] = folder_name
|
|
295
|
+
if crawl_config is not None:
|
|
296
|
+
body["crawl_config"] = crawl_config
|
|
297
|
+
return client.post(f"{_base()}/website-crawls", json=body)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def update_website_crawl(
|
|
301
|
+
client: CodeerClient,
|
|
302
|
+
*,
|
|
303
|
+
target_id: int,
|
|
304
|
+
start_url: str,
|
|
305
|
+
crawl_config: Optional[dict[str, Any]] = None,
|
|
306
|
+
) -> dict:
|
|
307
|
+
body: dict[str, Any] = {"start_url": start_url}
|
|
308
|
+
if crawl_config is not None:
|
|
309
|
+
body["crawl_config"] = crawl_config
|
|
310
|
+
return client.patch(f"{_base()}/website-crawls/{target_id}", json=body)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def sync_website_crawl(client: CodeerClient, *, target_id: int) -> dict:
|
|
314
|
+
return client.post(f"{_base()}/website-crawls/{target_id}:sync", json={})
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def cancel_website_crawl(client: CodeerClient, *, target_id: int) -> dict:
|
|
318
|
+
return client.post(f"{_base()}/website-crawls/{target_id}:cancel", json={})
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def get_website_crawl_state(client: CodeerClient, *, folder_id: str) -> dict:
|
|
322
|
+
return client.get(f"{_base()}/nodes/{folder_id}/website-crawl-state")
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def get_website_crawl_failures(
|
|
326
|
+
client: CodeerClient,
|
|
327
|
+
*,
|
|
328
|
+
job_id: int,
|
|
329
|
+
status: str = "DOWNLOAD_FAILED,FAILED",
|
|
330
|
+
limit: int = 50,
|
|
331
|
+
offset: int = 0,
|
|
332
|
+
) -> dict:
|
|
333
|
+
return client.get(
|
|
334
|
+
f"{_base()}/website-crawl-jobs/{job_id}/failures",
|
|
335
|
+
params={"status": status, "limit": limit, "offset": offset},
|
|
336
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codeer-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
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
|
|
@@ -22,25 +22,35 @@ Standalone CLI for managing Codeer agents over the Codeer API.
|
|
|
22
22
|
|
|
23
23
|
## User install
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
line tool:
|
|
25
|
+
Install the CLI from PyPI with `pipx`:
|
|
27
26
|
|
|
28
27
|
```bash
|
|
29
|
-
|
|
28
|
+
pipx install codeer-cli
|
|
30
29
|
```
|
|
31
30
|
|
|
32
|
-
|
|
31
|
+
Verify that the command is available:
|
|
33
32
|
|
|
34
33
|
```bash
|
|
35
|
-
|
|
34
|
+
codeer --help
|
|
36
35
|
```
|
|
37
36
|
|
|
38
|
-
|
|
37
|
+
If `pipx` is not installed:
|
|
39
38
|
|
|
40
|
-
|
|
39
|
+
```bash
|
|
40
|
+
python -m pip install --user pipx
|
|
41
|
+
python -m pipx ensurepath
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Then restart the terminal and run:
|
|
41
45
|
|
|
42
46
|
```bash
|
|
43
|
-
codeer
|
|
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
|
|
44
54
|
```
|
|
45
55
|
|
|
46
56
|
## Credentials
|
|
@@ -107,6 +117,21 @@ Validate setup before API work:
|
|
|
107
117
|
codeer check
|
|
108
118
|
```
|
|
109
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
|
+
|
|
110
135
|
## Output policy for coding agents
|
|
111
136
|
|
|
112
137
|
The CLI is optimized for Codex, Claude Code, Claude Cowork, and similar coding
|
|
@@ -133,16 +158,40 @@ Avoid piping large raw JSON directly into agent chat. Prefer `--out`, then ask
|
|
|
133
158
|
the coding agent to inspect targeted summaries, IDs, failing cases, or selected
|
|
134
159
|
snippets from the saved file.
|
|
135
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
|
+
|
|
136
183
|
## Context Object FAQ
|
|
137
184
|
|
|
138
185
|
Use Context Object FAQ entries to route high-value questions to a canonical KB
|
|
139
186
|
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`.
|
|
187
|
+
file's `snapshot_object_id`, shown by `codeer kb files`. Add `--range
|
|
188
|
+
START_LINE:END_LINE` when the route should reserve chunks overlapping a stable
|
|
189
|
+
line range inside that file.
|
|
141
190
|
|
|
142
191
|
```bash
|
|
143
192
|
codeer kb files --kb-id <kb-id>
|
|
144
193
|
codeer kb faq-list --context-object-id <snapshot-object-id>
|
|
145
|
-
codeer kb faq-create --context-object-id <snapshot-object-id> --question "..." --dry-run
|
|
194
|
+
codeer kb faq-create --context-object-id <snapshot-object-id> --question "..." --range 12:18 --dry-run
|
|
146
195
|
```
|
|
147
196
|
|
|
148
197
|
After reviewing the dry-run output, rerun the create/update/delete command
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
codeer_cli/__init__.py,sha256=-0gL8upoSsLAnXAfcRrwqZYJbwG0knzQoFf94O7Nc7c,1817
|
|
2
2
|
codeer_cli/_validate.py,sha256=pKUJa2TyTpERx5xmiYNZRn7tFqDxLZ2fF1rHAf1oz14,5415
|
|
3
|
-
codeer_cli/agents.py,sha256=
|
|
3
|
+
codeer_cli/agents.py,sha256=diodgiGhXlowEi8sbCzcSK1qSeCLF2fBe6QBs3Sq_x8,5617
|
|
4
4
|
codeer_cli/chats.py,sha256=YVrZJhoa-d67o6tzX6riGXsbA-ehyhOxrZ8zRCcJNro,2675
|
|
5
5
|
codeer_cli/cli.py,sha256=kaXTCBfzq64fLJDqKH39cN0ds7qv-F-eRxzUsGmCvv0,3901
|
|
6
|
-
codeer_cli/client.py,sha256
|
|
7
|
-
codeer_cli/constants.py,sha256=
|
|
6
|
+
codeer_cli/client.py,sha256=LpHVqf1IYNg1wFfIHnO9q4xg2h3IiGOitzCnvwB-Bcw,9809
|
|
7
|
+
codeer_cli/constants.py,sha256=D1pV3wCoqYybrKGKeoupYjjFWLfaFviKp1yL7oh6Qso,2323
|
|
8
8
|
codeer_cli/eval_.py,sha256=EsH8f8nT8x9MFlUhpHWSAU4aNPkNceD-Tw5GM15Ae1I,14157
|
|
9
9
|
codeer_cli/histories.py,sha256=tk28git_peX4x703CIDU8u72JtlGaytyrtlHfxlK-7A,5979
|
|
10
|
-
codeer_cli/kb.py,sha256
|
|
10
|
+
codeer_cli/kb.py,sha256=--0MvZJ2OsIbzLh-4TQ-wR7dVK42eaXw-L0qDGFOaxg,10417
|
|
11
11
|
codeer_cli/parse.py,sha256=qrjZn0MUTjGfucp4cwxy8Pt7WS-0x15kK5F7kWTY8Ps,21818
|
|
12
12
|
codeer_cli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
13
|
codeer_cli/commands/_util.py,sha256=VOB_HMWYzHFNY1ElLOED1HB6fpFpsniqH6Yx3VUlMrY,1644
|
|
14
|
-
codeer_cli/commands/agent.py,sha256=
|
|
14
|
+
codeer_cli/commands/agent.py,sha256=amvfVVrbPOkbYKCGvA6EJB30C-aY6WSdfR7u7FklXbs,14793
|
|
15
15
|
codeer_cli/commands/check.py,sha256=lTxolx1mIJ8jldPhJ5FXqie9nbCLVOO-sDPOHTSy1-w,3817
|
|
16
16
|
codeer_cli/commands/eval_cmd.py,sha256=yvqjPXMOE7V0Hv53iEW5HvIo610dgiIG4BpKGWzZY4I,46965
|
|
17
17
|
codeer_cli/commands/history.py,sha256=Jv7t0GhSZcbZ8OuIXZT34CixXt7ECEVP3nZ-WW_Ya9E,12026
|
|
18
|
-
codeer_cli/commands/kb.py,sha256=
|
|
19
|
-
codeer_cli/commands/profile.py,sha256=
|
|
20
|
-
codeer_cli-0.1.
|
|
21
|
-
codeer_cli-0.1.
|
|
22
|
-
codeer_cli-0.1.
|
|
23
|
-
codeer_cli-0.1.
|
|
18
|
+
codeer_cli/commands/kb.py,sha256=vXwgiGHYrLywm3O41-D__7m3d1uZUcw3YYuxKH-08i0,24528
|
|
19
|
+
codeer_cli/commands/profile.py,sha256=IdlXC_6cqobsfN3JRrAnt-1OgBUsIFneS9QtR4Un6Kc,6521
|
|
20
|
+
codeer_cli-0.1.3.dist-info/METADATA,sha256=lI76CTicsBTRGXZStaOs6I5kK98Pwf7-PV3XtIlxFsA,5291
|
|
21
|
+
codeer_cli-0.1.3.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
22
|
+
codeer_cli-0.1.3.dist-info/entry_points.txt,sha256=-nXIrlm5SR5r7gg3y8AS0tN66MwmvNHsrlwLNQNGD50,47
|
|
23
|
+
codeer_cli-0.1.3.dist-info/RECORD,,
|
|
File without changes
|