codebase-explorer 2.2.0__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.
- cbe/__init__.py +9 -0
- cbe/__main__.py +4 -0
- cbe/aggregation.py +183 -0
- cbe/api_completion.py +410 -0
- cbe/batching.py +283 -0
- cbe/catalog_query.py +376 -0
- cbe/checks.py +390 -0
- cbe/cli.py +434 -0
- cbe/concrete_facts.py +321 -0
- cbe/data/LICENSE.tiktoken +30 -0
- cbe/data/o200k_base.tiktoken +199998 -0
- cbe/declarations.py +537 -0
- cbe/docs.py +929 -0
- cbe/generate.py +316 -0
- cbe/grammars.py +741 -0
- cbe/graph.py +857 -0
- cbe/graph_backends/__init__.py +43 -0
- cbe/graph_backends/_calls.py +262 -0
- cbe/graph_backends/_paths.py +42 -0
- cbe/graph_backends/identity.py +93 -0
- cbe/graph_backends/imports.py +256 -0
- cbe/graph_backends/joern.py +1021 -0
- cbe/graph_backends/resolution.py +288 -0
- cbe/graph_backends/toolchain.py +396 -0
- cbe/grounding.py +57 -0
- cbe/host_run.py +1752 -0
- cbe/model_settings.py +80 -0
- cbe/projection.py +305 -0
- cbe/prompts.py +316 -0
- cbe/records.py +200 -0
- cbe/skeleton.py +474 -0
- cbe/source_context.py +709 -0
- cbe/symbols.py +136 -0
- cbe/token_budget.py +68 -0
- cbe/treename.py +398 -0
- cbe/usage.py +300 -0
- cbe_install/__init__.py +11 -0
- cbe_install/cli.py +434 -0
- cbe_install/resources/agents/cbe-writer.md +16 -0
- cbe_install/resources/codebase-explorer/SKILL.md +101 -0
- cbe_install/resources/codebase-explorer/references/claude-code.md +63 -0
- cbe_install/resources/codebase-explorer/references/codex.md +37 -0
- codebase_explorer-2.2.0.dist-info/METADATA +233 -0
- codebase_explorer-2.2.0.dist-info/RECORD +47 -0
- codebase_explorer-2.2.0.dist-info/WHEEL +4 -0
- codebase_explorer-2.2.0.dist-info/entry_points.txt +3 -0
- codebase_explorer-2.2.0.dist-info/licenses/LICENSE +21 -0
cbe/usage.py
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
"""Token usage and cost: exact harness records where they exist, labelled estimates otherwise."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import csv
|
|
6
|
+
import json
|
|
7
|
+
import math
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from cbe.host_run import HostRunError, epoch, log_event, state_dir, locked_state, write_json
|
|
14
|
+
from cbe.token_budget import count_text_tokens
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# USD per million tokens: input, output, cache write (5 minute), cache read.
|
|
18
|
+
# Anthropic first-party list prices as of 2026-09-25; pass --price to override.
|
|
19
|
+
PRICES = {
|
|
20
|
+
"claude-fable-5-1": (10.0, 50.0, 12.5, 0.25),
|
|
21
|
+
"claude-opus-5-5": (4.0, 20.0, 5.0, 0.20),
|
|
22
|
+
"claude-sonnet-5-5": (2.0, 10.0, 2.5, 0.20),
|
|
23
|
+
"claude-haiku-4-5": (1.0, 5.0, 1.25, 0.10),
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# Rough per-subagent harness overhead (system prompt and tool definitions) for estimates.
|
|
28
|
+
DEFAULT_OVERHEAD_TOKENS = 20_000
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _claude_transcript_usage(path: Path) -> dict[str, Any]:
|
|
32
|
+
"""Sum per-message usage; streamed messages repeat, so keep the max per id."""
|
|
33
|
+
per_message: dict[str, dict[str, int]] = {}
|
|
34
|
+
models: set[str] = set()
|
|
35
|
+
for line in path.read_text(encoding="utf-8", errors="replace").splitlines():
|
|
36
|
+
try:
|
|
37
|
+
record = json.loads(line)
|
|
38
|
+
except ValueError:
|
|
39
|
+
continue
|
|
40
|
+
message = record.get("message") if isinstance(record, dict) else None
|
|
41
|
+
if not isinstance(message, dict) or not isinstance(message.get("usage"), dict):
|
|
42
|
+
continue
|
|
43
|
+
key = message.get("id") or record.get("uuid") or str(len(per_message))
|
|
44
|
+
if message.get("model"):
|
|
45
|
+
models.add(message["model"])
|
|
46
|
+
usage = message["usage"]
|
|
47
|
+
seen = per_message.setdefault(key, {})
|
|
48
|
+
for field in ("input_tokens", "cache_creation_input_tokens", "cache_read_input_tokens", "output_tokens"):
|
|
49
|
+
if isinstance(usage.get(field), int):
|
|
50
|
+
seen[field] = max(seen.get(field, 0), usage[field])
|
|
51
|
+
total = {field: sum(row.get(field, 0) for row in per_message.values())
|
|
52
|
+
for field in ("input_tokens", "cache_creation_input_tokens", "cache_read_input_tokens", "output_tokens")}
|
|
53
|
+
return {"input_tokens": total["input_tokens"], "cache_write_tokens": total["cache_creation_input_tokens"],
|
|
54
|
+
"cache_read_tokens": total["cache_read_input_tokens"], "output_tokens": total["output_tokens"],
|
|
55
|
+
"turns": len(per_message), "model": ",".join(sorted(models))}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _codex_rollout_usage(path: Path) -> tuple[str | None, dict[str, Any] | None]:
|
|
59
|
+
agent_path, model, usage = None, None, None
|
|
60
|
+
with path.open(encoding="utf-8", errors="replace") as stream:
|
|
61
|
+
for number, line in enumerate(stream):
|
|
62
|
+
try:
|
|
63
|
+
record = json.loads(line)
|
|
64
|
+
except ValueError:
|
|
65
|
+
continue
|
|
66
|
+
payload = record.get("payload") or {}
|
|
67
|
+
if record.get("type") == "session_meta":
|
|
68
|
+
spawn = ((payload.get("source") or {}).get("subagent") or {}) if isinstance(
|
|
69
|
+
payload.get("source"), dict) else {}
|
|
70
|
+
agent_path = (spawn.get("thread_spawn") or {}).get("agent_path")
|
|
71
|
+
if not agent_path:
|
|
72
|
+
return None, None
|
|
73
|
+
elif number == 0:
|
|
74
|
+
return None, None
|
|
75
|
+
if record.get("type") == "turn_context" and payload.get("model"):
|
|
76
|
+
model = payload["model"]
|
|
77
|
+
if payload.get("type") == "token_count" and isinstance(payload.get("info"), dict):
|
|
78
|
+
usage = payload["info"].get("total_token_usage") or usage
|
|
79
|
+
if not agent_path or not usage:
|
|
80
|
+
return agent_path, None
|
|
81
|
+
cached = int(usage.get("cached_input_tokens") or 0)
|
|
82
|
+
return agent_path, {"input_tokens": int(usage.get("input_tokens") or 0) - cached,
|
|
83
|
+
"cache_write_tokens": int(usage.get("cache_write_input_tokens") or 0),
|
|
84
|
+
"cache_read_tokens": cached, "output_tokens": int(usage.get("output_tokens") or 0),
|
|
85
|
+
"reasoning_output_tokens": int(usage.get("reasoning_output_tokens") or 0),
|
|
86
|
+
"model": model or ""}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def import_usage(run_dir: Path, harness: str, root: Path | None = None) -> dict[str, Any]:
|
|
90
|
+
"""Read exact per-subagent usage that Claude Code or Codex already recorded."""
|
|
91
|
+
with locked_state(run_dir) as state:
|
|
92
|
+
since = epoch(state["created_at"]) - 60
|
|
93
|
+
tag = state["run_tag"]
|
|
94
|
+
found: dict[str, list[dict[str, Any]]] = {}
|
|
95
|
+
if harness == "claude-code":
|
|
96
|
+
base = root or Path.home() / ".claude" / "projects"
|
|
97
|
+
pattern = re.compile(rf"\bcbe {tag} (\S+)")
|
|
98
|
+
metas = base.rglob("agent-*.meta.json") if root else base.glob("*/*/subagents/agent-*.meta.json")
|
|
99
|
+
for meta in metas:
|
|
100
|
+
# Other sessions may delete their transcripts while we scan.
|
|
101
|
+
try:
|
|
102
|
+
if meta.stat().st_mtime < since:
|
|
103
|
+
continue
|
|
104
|
+
description = json.loads(meta.read_text(encoding="utf-8")).get("description") or ""
|
|
105
|
+
match = pattern.search(description)
|
|
106
|
+
transcript = meta.with_name(meta.name.replace(".meta.json", ".jsonl"))
|
|
107
|
+
if match and match.group(1) in state["tasks"] and transcript.is_file():
|
|
108
|
+
row = _claude_transcript_usage(transcript)
|
|
109
|
+
else:
|
|
110
|
+
continue
|
|
111
|
+
except (OSError, ValueError):
|
|
112
|
+
continue
|
|
113
|
+
row.update(agent=transcript.stem, source="claude-code subagent transcript")
|
|
114
|
+
found.setdefault(match.group(1), []).append(row)
|
|
115
|
+
elif harness == "codex":
|
|
116
|
+
base = root or Path(os.environ.get("CODEX_HOME", Path.home() / ".codex")) / "sessions"
|
|
117
|
+
pattern = re.compile(rf"cbe_{tag}_(\S+)$")
|
|
118
|
+
for rollout in base.rglob("rollout-*.jsonl"):
|
|
119
|
+
try:
|
|
120
|
+
if rollout.stat().st_mtime < since:
|
|
121
|
+
continue
|
|
122
|
+
agent_path, row = _codex_rollout_usage(rollout)
|
|
123
|
+
except OSError:
|
|
124
|
+
continue
|
|
125
|
+
match = pattern.search((agent_path or "").rsplit("/", 1)[-1])
|
|
126
|
+
if match and row and match.group(1) in state["tasks"]:
|
|
127
|
+
row.update(agent=rollout.stem, source="codex subagent rollout")
|
|
128
|
+
found.setdefault(match.group(1), []).append(row)
|
|
129
|
+
else:
|
|
130
|
+
raise HostRunError("harness must be claude-code or codex")
|
|
131
|
+
for task_id, rows in found.items():
|
|
132
|
+
state["tasks"][task_id]["exact_usage"] = rows
|
|
133
|
+
log_event(run_dir, "usage", f"imported exact {harness} usage for {sorted(found)}",
|
|
134
|
+
harness=harness, tasks=sorted(found))
|
|
135
|
+
missing = [task_id for task_id, task in state["tasks"].items()
|
|
136
|
+
if task["attempts"] and not task.get("exact_usage")]
|
|
137
|
+
return {"harness": harness, "tasks_with_exact_usage": len(found),
|
|
138
|
+
"subagent_transcripts": sum(len(rows) for rows in found.values()),
|
|
139
|
+
"tasks_without_exact_usage": missing}
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _price_for(model: str, override: tuple[float, float, float, float] | None) -> tuple[tuple[float, ...] | None, str]:
|
|
143
|
+
if override is not None:
|
|
144
|
+
return override, "price given on the command line"
|
|
145
|
+
for name, price in PRICES.items():
|
|
146
|
+
if model == name or model.startswith(name + "-"):
|
|
147
|
+
return price, f"{name} list price (2026-09-25)"
|
|
148
|
+
return None, f"price unknown: {model or 'model not recorded'}"
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _dollars(row: dict[str, Any], price: tuple[float, ...] | None) -> float | None:
|
|
152
|
+
if price is None:
|
|
153
|
+
return None
|
|
154
|
+
return round((row.get("input_tokens", 0) * price[0] + row.get("output_tokens", 0) * price[1]
|
|
155
|
+
+ row.get("cache_write_tokens", 0) * price[2] + row.get("cache_read_tokens", 0) * price[3])
|
|
156
|
+
/ 1_000_000, 4)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _sum_dollars(values: list[float | None]) -> float | None:
|
|
160
|
+
if any(value is None for value in values):
|
|
161
|
+
return None
|
|
162
|
+
return round(sum(value for value in values if value is not None), 4)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _estimate(run_dir: Path, state: dict[str, Any], task: dict[str, Any], overhead: int) -> dict[str, Any]:
|
|
166
|
+
"""A deliberately simple agent-loop model; label every number as an estimate.
|
|
167
|
+
|
|
168
|
+
A subagent reads its task, reads the listed files (usually in parallel),
|
|
169
|
+
writes the result, and replies. Each turn re-reads the cached context. When
|
|
170
|
+
the harness reported the subagent's final context size, that anchors the
|
|
171
|
+
model; otherwise the context is overhead + task + source + result.
|
|
172
|
+
"""
|
|
173
|
+
prompt_path = state_dir(run_dir) / "tasks" / f"{task['id']}.md"
|
|
174
|
+
prompt = count_text_tokens(prompt_path.read_text(encoding="utf-8")) if prompt_path.is_file() else 0
|
|
175
|
+
source, files = 0, 0
|
|
176
|
+
if task["kind"] in {"module", "repair", "review"}:
|
|
177
|
+
paths = set(state["modules"][task["module"]]["files"])
|
|
178
|
+
source = sum(row["tokens"] for row in state["files"] if row["path"] in paths)
|
|
179
|
+
files = len(paths)
|
|
180
|
+
accepted = state_dir(run_dir) / "accepted" / f"{task['id']}.json"
|
|
181
|
+
output = count_text_tokens(accepted.read_text(encoding="utf-8")) if accepted.is_file() else 1_000
|
|
182
|
+
turns = 3 if not files else 4 + (files > 4)
|
|
183
|
+
reported = [entry["total_tokens"] for entry in task["usage"] if entry.get("total_tokens")]
|
|
184
|
+
final_context = (sum(reported) // len(reported)) if reported else overhead + prompt + source + output
|
|
185
|
+
start_context = max(final_context - source - output, prompt)
|
|
186
|
+
attempts = max(1, task["attempts"])
|
|
187
|
+
return {"input_tokens": 0, "cache_write_tokens": final_context * attempts,
|
|
188
|
+
"cache_read_tokens": (turns - 1) * (start_context + final_context) // 2 * attempts,
|
|
189
|
+
"output_tokens": output * 2 * attempts, "turns": turns * attempts,
|
|
190
|
+
"anchored": bool(reported)}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def cost(run_dir: Path, *, model: str | None = None, price: tuple[float, float, float, float] | None = None,
|
|
194
|
+
overhead: int = DEFAULT_OVERHEAD_TOKENS) -> dict[str, Any]:
|
|
195
|
+
if price is not None and (len(price) != 4 or any(not math.isfinite(value) or value < 0 for value in price)):
|
|
196
|
+
raise HostRunError("--price needs four finite, nonnegative rates: input,output,cache_write,cache_read")
|
|
197
|
+
with locked_state(run_dir) as state:
|
|
198
|
+
rows, totals = [], {"input_tokens": 0, "cache_write_tokens": 0, "cache_read_tokens": 0,
|
|
199
|
+
"output_tokens": 0}
|
|
200
|
+
bases: set[str] = set()
|
|
201
|
+
price_notes: set[str] = set()
|
|
202
|
+
for task in state["tasks"].values():
|
|
203
|
+
if not task["attempts"]:
|
|
204
|
+
continue
|
|
205
|
+
estimate = _estimate(run_dir, state, task, overhead)
|
|
206
|
+
if task.get("exact_usage"):
|
|
207
|
+
parts = task["exact_usage"]
|
|
208
|
+
basis = "exact"
|
|
209
|
+
else:
|
|
210
|
+
parts = [{**estimate, "model": model or ""}]
|
|
211
|
+
basis = "estimate"
|
|
212
|
+
effective_models = {part.get("model") or model or "" for part in parts}
|
|
213
|
+
estimate_model = next(iter(effective_models)) if len(effective_models) == 1 else ""
|
|
214
|
+
estimate_usd = _dollars(estimate, _price_for(estimate_model, price)[0])
|
|
215
|
+
bases.add(basis)
|
|
216
|
+
amounts = []
|
|
217
|
+
for part in parts:
|
|
218
|
+
chosen, note = _price_for(part.get("model") or model or "", price)
|
|
219
|
+
price_notes.add(note)
|
|
220
|
+
amounts.append(_dollars(part, chosen))
|
|
221
|
+
row = {"task": task["id"], "basis": basis, "subagents": len(parts) if basis == "exact" else task["attempts"],
|
|
222
|
+
**{field: sum(part.get(field, 0) for part in parts) for field in totals},
|
|
223
|
+
"usd_api_equivalent": _sum_dollars(amounts), "estimate_usd": estimate_usd,
|
|
224
|
+
"estimate_anchored_on_reported_context": estimate["anchored"],
|
|
225
|
+
"models": sorted({part.get("model") or "" for part in parts} - {""})}
|
|
226
|
+
reported = [entry for entry in task["usage"] if entry.get("total_tokens")]
|
|
227
|
+
if reported:
|
|
228
|
+
row["harness_reported_total_tokens"] = sum(entry["total_tokens"] for entry in reported)
|
|
229
|
+
rows.append(row)
|
|
230
|
+
for field in totals:
|
|
231
|
+
totals[field] += row[field]
|
|
232
|
+
totals["usd"] = _sum_dollars([row["usd_api_equivalent"] for row in rows])
|
|
233
|
+
basis = "exact" if bases == {"exact"} else "estimate" if bases == {"estimate"} else "mixed"
|
|
234
|
+
report = {
|
|
235
|
+
"basis": basis, "tasks": rows, "totals": totals,
|
|
236
|
+
"subagent_runs": sum(row["subagents"] for row in rows),
|
|
237
|
+
"estimate_usd": _sum_dollars([row["estimate_usd"] for row in rows]),
|
|
238
|
+
"price_basis": sorted(price_notes),
|
|
239
|
+
"statement": _statement(basis, totals["usd"] is not None),
|
|
240
|
+
}
|
|
241
|
+
write_json(state_dir(run_dir) / "cost.json", report)
|
|
242
|
+
(state_dir(run_dir) / "COST.md").write_text(_cost_markdown(report), encoding="utf-8")
|
|
243
|
+
return report
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _statement(basis: str, priced: bool) -> str:
|
|
247
|
+
tokens = {"exact": "Token counts are exact, read from the harness's own per-subagent usage records.",
|
|
248
|
+
"estimate": "Token counts are ESTIMATES from prompt and source sizes; the harness did not "
|
|
249
|
+
"provide exact per-subagent usage.",
|
|
250
|
+
"mixed": "Some token counts are exact (harness records) and the rest are ESTIMATES."}[basis]
|
|
251
|
+
pricing = ("Dollar figures apply the rates identified below." if priced else
|
|
252
|
+
"One or more model prices are unknown, so dollar totals are unknown. "
|
|
253
|
+
"Pass --price with current rates to calculate a price equivalent.")
|
|
254
|
+
return (tokens + " " + pricing + " This is not a bill: a subscription uses plan quota. "
|
|
255
|
+
"The token-use estimate excludes graph evidence files read by host authors. "
|
|
256
|
+
"The host agent's own orchestration turns are not included; quote the harness session total for those.")
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _money(value: float | None, decimals: int = 2) -> str:
|
|
260
|
+
return "unknown" if value is None else f"${value:.{decimals}f}"
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def _cost_markdown(report: dict[str, Any]) -> str:
|
|
264
|
+
totals = report["totals"]
|
|
265
|
+
lines = ["# CBE run cost", "", report["statement"], "",
|
|
266
|
+
f"- Basis: **{report['basis']}**; subagent runs: {report['subagent_runs']}",
|
|
267
|
+
f"- Tokens: input {totals['input_tokens']:,}, cache write {totals['cache_write_tokens']:,}, "
|
|
268
|
+
f"cache read {totals['cache_read_tokens']:,}, output {totals['output_tokens']:,}",
|
|
269
|
+
f"- Price equivalent: {_money(totals['usd'])} ({'; '.join(report['price_basis'])})",
|
|
270
|
+
f"- Price of the token-use estimate: {_money(report['estimate_usd'])}", "",
|
|
271
|
+
"| Task | Basis | Subagents | Input | Cache write | Cache read | Output | USD equiv. |",
|
|
272
|
+
"|---|---|---:|---:|---:|---:|---:|---:|"]
|
|
273
|
+
for row in report["tasks"]:
|
|
274
|
+
lines.append(f"| {row['task']} | {row['basis']} | {row['subagents']} | {row['input_tokens']:,} | "
|
|
275
|
+
f"{row['cache_write_tokens']:,} | {row['cache_read_tokens']:,} | {row['output_tokens']:,} | "
|
|
276
|
+
f"{_money(row['usd_api_equivalent'], 4)} |")
|
|
277
|
+
return "\n".join(lines) + "\n"
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def script_cost(run_dir: Path, *, price: list[float] | None = None) -> dict[str, Any]:
|
|
281
|
+
with (run_dir / "calls.csv").open(newline="", encoding="utf-8") as stream:
|
|
282
|
+
rows = list(csv.DictReader(stream))
|
|
283
|
+
fields = ("input_tokens", "output_tokens", "cache_tokens")
|
|
284
|
+
complete = {field: all(row[field] != "" for row in rows) for field in fields}
|
|
285
|
+
totals = {field: sum(int(row[field]) for row in rows if row[field]) for field in fields}
|
|
286
|
+
known = all(complete.values())
|
|
287
|
+
input_tokens, output_tokens, cache_tokens = (totals[field] for field in fields)
|
|
288
|
+
rates = price
|
|
289
|
+
return {"calls": len(rows),
|
|
290
|
+
**{field: totals[field] if complete[field] or field != "cache_tokens" else None for field in fields},
|
|
291
|
+
"token_totals_basis": "observed calls; missing usage is excluded, never estimated",
|
|
292
|
+
"calls_with_unknown_input": sum(row["input_tokens"] == "" for row in rows),
|
|
293
|
+
"calls_with_unknown_output": sum(row["output_tokens"] == "" for row in rows),
|
|
294
|
+
"observed_cache_tokens": cache_tokens,
|
|
295
|
+
"calls_with_unknown_cache": sum(row["cache_tokens"] == "" for row in rows),
|
|
296
|
+
"priced_usd": round((input_tokens * rates[0] + output_tokens * rates[1] + cache_tokens * rates[2]) / 1_000_000, 6)
|
|
297
|
+
if known and rates is not None else None,
|
|
298
|
+
"price_basis": ("configured USD per million input,output,cache tokens" if price is not None
|
|
299
|
+
else "unknown; configure API price_per_million: [input, output, cache]"),
|
|
300
|
+
"usage_complete": known, "seconds": round(sum(float(row["seconds"]) for row in rows), 3)}
|
cbe_install/__init__.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Install the CBE skill and its Claude Code subagent from the self-contained distribution."""
|
|
2
|
+
|
|
3
|
+
PRODUCT = "codebase-explorer"
|
|
4
|
+
BUSINESS_MODULE = "cbe.cli"
|
|
5
|
+
SKILLS = ("codebase-explorer",)
|
|
6
|
+
SOURCE_PARENT_LEVEL = 2
|
|
7
|
+
SOURCE_SKILL_BASE = "plugin/skills"
|
|
8
|
+
SOURCE_AGENT_BASE = "plugin/agents"
|
|
9
|
+
AGENTS_RESOURCE = "agents"
|
|
10
|
+
RUNTIME_SKILLS = ("codebase-explorer",)
|
|
11
|
+
RUNTIME_MODULES = ("cbe",)
|