@caddis/cli 0.2.0 → 0.2.1
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.
- package/README.md +15 -0
- package/bundles/antigravity-plugin/guard_agy.py +9 -20
- package/bundles/antigravity-plugin/plugin.json +1 -1
- package/bundles/antigravity-plugin/session_end_agy.py +2 -7
- package/bundles/antigravity-plugin/skills/cross-review/SKILL.md +1 -1
- package/bundles/antigravity-plugin/skills/handoff/SKILL.md +2 -2
- package/bundles/antigravity-plugin/skills/setup-project-ai/SKILL.md +2 -4
- package/bundles/antigravity-plugin/skills/use-model/SKILL.md +1 -1
- package/bundles/antigravity-plugin/warm_start_agy.py +3 -3
- package/bundles/manifest.json +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -121,6 +121,21 @@ caddis update --extras # add extras to an existing install
|
|
|
121
121
|
The first run. Detects, shows exactly what it is about to run, asks, then installs. `--yes` skips
|
|
122
122
|
the prompt for CI.
|
|
123
123
|
|
|
124
|
+
### Removing caddis
|
|
125
|
+
|
|
126
|
+
**There is no `caddis remove`.** This CLI installs and updates; it never uninstalls. Removal goes
|
|
127
|
+
through each agent's own command — the same principle as everything else here:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
claude plugin uninstall caddis@caddis # Claude Code
|
|
131
|
+
claude plugin uninstall caddis-extras@caddis
|
|
132
|
+
agy plugin uninstall caddis # agy
|
|
133
|
+
agy plugin uninstall caddis-extras
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`npm uninstall -g @caddis/cli` removes **only this CLI**, not the caddis plugins it installed into
|
|
137
|
+
your agents. Uninstall those first if you want caddis gone entirely.
|
|
138
|
+
|
|
124
139
|
---
|
|
125
140
|
|
|
126
141
|
## Flags
|
|
@@ -34,11 +34,9 @@ import os
|
|
|
34
34
|
import re
|
|
35
35
|
import sys
|
|
36
36
|
|
|
37
|
-
# Functional identity, mirrored from scripts/claudster_config.py.
|
|
38
|
-
|
|
39
|
-
_ARTIFACT_DIRS = (".caddis", ".claudster")
|
|
37
|
+
# Functional identity, mirrored from scripts/claudster_config.py.
|
|
38
|
+
_ARTIFACT_DIR = ".caddis"
|
|
40
39
|
_ENV_PREFIX = "CADDIS"
|
|
41
|
-
_LEGACY_ENV_PREFIX = "CLAUDSTER"
|
|
42
40
|
|
|
43
41
|
# ── protected-path rules (write → deny) ──────────────────────────────────────
|
|
44
42
|
_SECRET_BASENAMES = {"id_rsa", "id_dsa", "id_ecdsa", "id_ed25519", "credentials",
|
|
@@ -221,12 +219,9 @@ _TRUTHY = {"1", "true", "yes", "on"}
|
|
|
221
219
|
|
|
222
220
|
|
|
223
221
|
def _config_path(root: str) -> str | None:
|
|
224
|
-
"""The repo's
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
if os.path.isfile(cfg):
|
|
228
|
-
return cfg
|
|
229
|
-
return None
|
|
222
|
+
"""The repo's ``.caddis/config.toml``, or ``None`` if it doesn't exist."""
|
|
223
|
+
cfg = os.path.join(str(root), _ARTIFACT_DIR, "config.toml")
|
|
224
|
+
return cfg if os.path.isfile(cfg) else None
|
|
230
225
|
|
|
231
226
|
|
|
232
227
|
def _load_guard_config(root: str) -> dict:
|
|
@@ -253,11 +248,8 @@ def guard_disabled(root: str) -> bool:
|
|
|
253
248
|
"""True when the guard is turned off entirely (all tiers bypassed).
|
|
254
249
|
|
|
255
250
|
Precedence: the env var wins (global); otherwise the per-repo `[guard]` table — `enabled = false`
|
|
256
|
-
or `mode = "off"`. Any parse problem → not disabled (fail safe).
|
|
257
|
-
|
|
258
|
-
_disabled = os.environ.get(f"{_ENV_PREFIX}_GUARD_DISABLED")
|
|
259
|
-
if _disabled is None:
|
|
260
|
-
_disabled = os.environ.get(f"{_LEGACY_ENV_PREFIX}_GUARD_DISABLED", "")
|
|
251
|
+
or `mode = "off"`. Any parse problem → not disabled (fail safe). Env var: CADDIS_GUARD_DISABLED."""
|
|
252
|
+
_disabled = os.environ.get(f"{_ENV_PREFIX}_GUARD_DISABLED", "")
|
|
261
253
|
if _disabled.strip().lower() in _TRUTHY:
|
|
262
254
|
return True
|
|
263
255
|
section = _load_guard_config(root)
|
|
@@ -271,11 +263,8 @@ def guard_disabled(root: str) -> bool:
|
|
|
271
263
|
def deny_only(root: str) -> bool:
|
|
272
264
|
"""True when the guard keeps DENY (silent hard-block) but suppresses the ASK tier (ask -> allow =
|
|
273
265
|
silence), so it NEVER prompts — for autonomous / no-prompt workflows that still want a catastrophe
|
|
274
|
-
net. Env `CADDIS_GUARD_MODE=deny-only
|
|
275
|
-
|
|
276
|
-
_mode = os.environ.get(f"{_ENV_PREFIX}_GUARD_MODE")
|
|
277
|
-
if _mode is None:
|
|
278
|
-
_mode = os.environ.get(f"{_LEGACY_ENV_PREFIX}_GUARD_MODE", "")
|
|
266
|
+
net. Env `CADDIS_GUARD_MODE=deny-only`; config `[guard] mode = "deny-only"`. Read silently."""
|
|
267
|
+
_mode = os.environ.get(f"{_ENV_PREFIX}_GUARD_MODE", "")
|
|
279
268
|
if _mode.strip().lower() == "deny-only":
|
|
280
269
|
return True
|
|
281
270
|
section = _load_guard_config(root)
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"""caddis `session_end` hook for agy (Antigravity) — Stop event.
|
|
3
3
|
|
|
4
4
|
Ports claude-harness/hooks/session_end.py to agy's contract. On Stop, appends ONE session-end record to
|
|
5
|
-
the workspace's usage log (`<workspace>/.caddis/usage-log.jsonl
|
|
6
|
-
|
|
5
|
+
the workspace's usage log (`<workspace>/.caddis/usage-log.jsonl`). Self-contained (the agy bundle does not
|
|
6
|
+
ship claudster_config.py), stdlib-only, and fully defensive:
|
|
7
7
|
a Stop hook must never fail the turn, and must not print a non-JSON line to stdout (agy parses Stop-hook
|
|
8
8
|
stdout as a `{"decision":…}` object — a stray string trips its "unsupported hook decision" path). So this
|
|
9
9
|
does a pure side effect and prints NOTHING.
|
|
@@ -19,11 +19,6 @@ from datetime import datetime, timezone
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
def _artifact_root(root: str) -> str:
|
|
22
|
-
"""Write where the repo lives: prefer .caddis, keep .claudster if that's what the repo has."""
|
|
23
|
-
for name in (".caddis", ".claudster"):
|
|
24
|
-
path = os.path.join(str(root), name)
|
|
25
|
-
if os.path.isdir(path):
|
|
26
|
-
return path
|
|
27
22
|
return os.path.join(str(root), ".caddis")
|
|
28
23
|
|
|
29
24
|
|
|
@@ -12,7 +12,7 @@ complement to `/caddis:code-review`, not a replacement.
|
|
|
12
12
|
|
|
13
13
|
## Prerequisite (one-time)
|
|
14
14
|
A provider key must be resolvable. Precedence: `REVIEW_API_KEY` > the provider's own env var
|
|
15
|
-
(`DEEPSEEK_API_KEY` / `GLM_API_KEY`) > `OSS_API_KEY` > the keys file (`~/.
|
|
15
|
+
(`DEEPSEEK_API_KEY` / `GLM_API_KEY`) > `OSS_API_KEY` > the keys file (`~/.caddis/keys.env`,
|
|
16
16
|
overridable via `CADDIS_KEYS_FILE`). If your keys file is populated, no env setup is needed.
|
|
17
17
|
Details: the `coding/cross-review` skill and the **Providers & keys** guide.
|
|
18
18
|
|
|
@@ -46,8 +46,8 @@ plan A on the workstream stack — so its exact resume point survives and `/resu
|
|
|
46
46
|
> per-branch file automatically when it exists, then `.caddis/relay.md`, then the legacy
|
|
47
47
|
> `.claude/relay/<branch>.md` and root `relay.md` (back-compat during the migration).
|
|
48
48
|
>
|
|
49
|
-
>
|
|
50
|
-
>
|
|
49
|
+
> A repo still on the pre-rename `.claudster/` won't see this relay until it's converted —
|
|
50
|
+
> `/caddis:migrate-dir` does that.
|
|
51
51
|
|
|
52
52
|
```markdown
|
|
53
53
|
# Relay — <feature>
|
|
@@ -35,7 +35,7 @@ alias claude-glm="<path-to>/claude-harness/scripts/claude-oss.sh"
|
|
|
35
35
|
```
|
|
36
36
|
Resolve `<path-to>` per the same plugin-vs-source rule as the generator itself (`${CLAUDE_PLUGIN_ROOT}`
|
|
37
37
|
for a plugin install, the harness checkout path for caddis). Also set `CADDIS_KEYS_FILE`
|
|
38
|
-
(default `~/.
|
|
38
|
+
(default `~/.caddis/keys.env`) with the provider keys — see `docs/guide/providers-and-keys.md`.
|
|
39
39
|
|
|
40
40
|
## After the deterministic step — deploy private harness skills (optional)
|
|
41
41
|
|
|
@@ -45,9 +45,7 @@ such a source on disk; it is a no-op for everyone else. Point `CADDIS_HARNESS_SR
|
|
|
45
45
|
(the folder containing your private `skills/private/`) and run:
|
|
46
46
|
|
|
47
47
|
```powershell
|
|
48
|
-
|
|
49
|
-
$harnessSrc = if ($env:CADDIS_HARNESS_SRC) { $env:CADDIS_HARNESS_SRC } else { $env:CLAUDSTER_HARNESS_SRC }
|
|
50
|
-
$src = if ($harnessSrc) { Join-Path $harnessSrc "skills\private" } else { $null }
|
|
48
|
+
$src = if ($env:CADDIS_HARNESS_SRC) { Join-Path $env:CADDIS_HARNESS_SRC "skills\private" } else { $null }
|
|
51
49
|
$dest = ".github\skills\private"
|
|
52
50
|
if ($src -and (Test-Path $src)) {
|
|
53
51
|
New-Item -ItemType Directory -Force $dest | Out-Null
|
|
@@ -39,7 +39,7 @@ paired with `OSS_BASE_URL`/`OSS_MODEL`).
|
|
|
39
39
|
## Key resolution (never hardcoded)
|
|
40
40
|
Precedence, highest wins:
|
|
41
41
|
1. the provider's env var (`GLM_API_KEY` / `DEEPSEEK_API_KEY` / `OPENROUTER_API_KEY`) or generic `OSS_API_KEY`;
|
|
42
|
-
2. a keys file at `$CADDIS_KEYS_FILE` (default `~/.
|
|
42
|
+
2. a keys file at `$CADDIS_KEYS_FILE` (default `~/.caddis/keys.env`, `KEY=VALUE` lines, `#` comments);
|
|
43
43
|
3. missing → the launcher exits non-zero with an actionable message naming the exact env var to set.
|
|
44
44
|
|
|
45
45
|
The resolver is `claude-harness/scripts/oss_model.py` (`resolve(provider, env)`) — the one place a
|
|
@@ -25,7 +25,7 @@ import os
|
|
|
25
25
|
import subprocess
|
|
26
26
|
import sys
|
|
27
27
|
|
|
28
|
-
_ARTIFACT_DIRS = (".caddis",
|
|
28
|
+
_ARTIFACT_DIRS = (".caddis",)
|
|
29
29
|
|
|
30
30
|
INJECT_MAX_LINES = 120 # same budget as the Claude Code hook
|
|
31
31
|
INJECT_MAX_CHARS = 24000 # hard backstop: an ephemeralMessage should never be a context bomb
|
|
@@ -59,8 +59,8 @@ def resolve_relay(root: str) -> str:
|
|
|
59
59
|
"""First existing relay doc for `root`, else "".
|
|
60
60
|
|
|
61
61
|
Preference order mirrors inject_relay.py:
|
|
62
|
-
1.
|
|
63
|
-
2.
|
|
62
|
+
1. .caddis/relay/<branch>.md (per-branch team mode)
|
|
63
|
+
2. .caddis/relay.md (solo/default)
|
|
64
64
|
3. .claude/relay/<branch>.md (legacy per-branch)
|
|
65
65
|
4. relay.md (legacy repo root)
|
|
66
66
|
"""
|
package/bundles/manifest.json
CHANGED
package/package.json
CHANGED