@caddis/cli 0.4.50 → 0.4.51
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/bundles/antigravity-plugin/plugin.json +1 -1
- package/bundles/antigravity-plugin/scripts/caddis_routing.py +4 -0
- package/bundles/antigravity-plugin/session_end_agy.py +36 -2
- package/bundles/antigravity-plugin/skills/flow-studio/SKILL.md +3 -1
- package/bundles/antigravity-plugin/skills/flow-studio/references/patterns.md +1 -1
- package/bundles/antigravity-plugin/skills/flow-studio/references/ux-rules.md +20 -2
- package/bundles/antigravity-plugin-extras/plugin.json +1 -1
- package/bundles/antigravity-plugin-extras/skills/agent-orchestration/SKILL.md +38 -0
- package/bundles/codex/.codex/skills/frontend/flow-studio/SKILL.md +3 -1
- package/bundles/codex/.codex/skills/frontend/flow-studio/references/patterns.md +1 -1
- package/bundles/codex/.codex/skills/frontend/flow-studio/references/ux-rules.md +20 -2
- package/bundles/manifest.json +3 -3
- package/dist/cli.js +94 -1
- package/package.json +1 -1
|
@@ -14,6 +14,10 @@ from typing import Callable, Mapping
|
|
|
14
14
|
import oss_model
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
# agy is Flash-only on some machines and is good only at small, exact, mechanical cards (112 of 114 passed
|
|
18
|
+
# in the 2026-10-08 UI-upgrade run) and CI watching. Give design-fidelity, cross-file refactor and judgment
|
|
19
|
+
# work to codex or claude. See .caddis/kb/agy-headless-implementer-limits.md. A separate `design` role is an
|
|
20
|
+
# owner decision: it changes the routing line every session prints.
|
|
17
21
|
DEFAULT_ROUTING: dict[str, list[str]] = {
|
|
18
22
|
"critical": ["opus"],
|
|
19
23
|
"light": ["sonnet"],
|
|
@@ -19,6 +19,7 @@ agy Stop stdin (camelCase protojson):
|
|
|
19
19
|
"""
|
|
20
20
|
import json
|
|
21
21
|
import os
|
|
22
|
+
import subprocess
|
|
22
23
|
import sys
|
|
23
24
|
from datetime import datetime, timezone
|
|
24
25
|
|
|
@@ -27,6 +28,30 @@ def _artifact_root(root: str) -> str:
|
|
|
27
28
|
return os.path.join(str(root), ".caddis")
|
|
28
29
|
|
|
29
30
|
|
|
31
|
+
def _repo_root(path: str):
|
|
32
|
+
"""The git top-level folder that holds `path`, or None when `path` is not inside a repository.
|
|
33
|
+
|
|
34
|
+
agy can run with its working folder in a SUB-folder of the repo (a monorepo app). The artifact
|
|
35
|
+
folder belongs at the repo root, where the project's ignore rule (`/.caddis/`) covers it. Creating
|
|
36
|
+
it in the sub-folder left untracked files that every lane had to delete before staging
|
|
37
|
+
(UI-upgrade run, 2026-10-08).
|
|
38
|
+
"""
|
|
39
|
+
try:
|
|
40
|
+
done = subprocess.run(
|
|
41
|
+
["git", "-C", str(path), "rev-parse", "--show-toplevel"],
|
|
42
|
+
capture_output=True, text=True, timeout=5,
|
|
43
|
+
)
|
|
44
|
+
top = done.stdout.strip()
|
|
45
|
+
return os.path.normpath(top) if done.returncode == 0 and top else None
|
|
46
|
+
except Exception:
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _user_artifact_root() -> str:
|
|
51
|
+
"""Where usage goes when the workspace is in no repository: the user's own folder, never the cwd."""
|
|
52
|
+
return _artifact_root(os.path.expanduser("~"))
|
|
53
|
+
|
|
54
|
+
|
|
30
55
|
|
|
31
56
|
def _hook_note(feature, exc, root=None):
|
|
32
57
|
"""Record a swallowed failure in the caddis hook ledger. Never raises, never prints.
|
|
@@ -82,13 +107,22 @@ def main() -> None:
|
|
|
82
107
|
return
|
|
83
108
|
try:
|
|
84
109
|
roots = _workspace_roots(data) or [os.getcwd()]
|
|
110
|
+
# A workspace folder may sit inside a repo: the artifact dir is the REPO ROOT's, not the sub-folder's.
|
|
111
|
+
resolved = [(_repo_root(r) or str(r)) for r in roots]
|
|
85
112
|
# Write the record to every root that ALREADY has a .caddis dir, rather than to
|
|
86
113
|
# whichever root happened to be first. A multi-root session's usage belongs to each
|
|
87
114
|
# repo that participates in it, and creating .caddis in a repo that never opted in
|
|
88
115
|
# would be caddis littering someone else's tree.
|
|
89
|
-
targets = [
|
|
116
|
+
targets = []
|
|
117
|
+
for root in resolved:
|
|
118
|
+
art = _artifact_root(root)
|
|
119
|
+
if os.path.isdir(art) and art not in targets:
|
|
120
|
+
targets.append(art)
|
|
90
121
|
if not targets:
|
|
91
|
-
|
|
122
|
+
# Nothing has opted in. In a repo, the first root's repo root is the default. Outside any
|
|
123
|
+
# repo there is no project to attach usage to, so it goes to the user's folder, not the cwd.
|
|
124
|
+
first_repo = _repo_root(roots[0])
|
|
125
|
+
targets = [_artifact_root(first_repo) if first_repo else _user_artifact_root()]
|
|
92
126
|
record = {
|
|
93
127
|
"ts": datetime.now(timezone.utc).isoformat(),
|
|
94
128
|
"event": "session_end",
|
|
@@ -70,7 +70,9 @@ In either route, derive node and link IDs from immutable business keys. Preserve
|
|
|
70
70
|
8. **Short titles**: titles fit one line at column width. Long descriptions belong in subtitles or member lists.
|
|
71
71
|
9. **Fixed node dimensions**: node kinds use fixed box sizes so font rendering does not shift layout.
|
|
72
72
|
10. **Reduced motion**: removes all animations while preserving static borders and status glows.
|
|
73
|
-
11. **
|
|
73
|
+
11. **The lane is the owner**: a lane is the app, team or system that owns its row. Never add a column that repeats the owner (an "Apps" or "Owner" column); it adds a block between the real stages and the pages and says nothing new.
|
|
74
|
+
12. **Draw a shared node once**: when several rows use the same database, job or table, draw one node, centre it on the rows it serves, and link it to each node downstream. A chip (`kind: "chip"` with `ref`) is only for a node that lives in another lane, never a copy inside the same lane.
|
|
75
|
+
13. **Thin links, dotted canvas**: links are hairlines (about 0.8 px, with a 6 px arrowhead) and the canvas background is faint dots, not a grid. Both come from tokens (`--edge-w`, `--dot`, ...), so an app can change them with one CSS line; see `references/design-language.md`.
|
|
74
76
|
|
|
75
77
|
## Model summary
|
|
76
78
|
|
|
@@ -21,7 +21,7 @@ You can start from these base states and adjust words to match your domain:
|
|
|
21
21
|
|
|
22
22
|
- **Example file:** `examples/lineage.json`
|
|
23
23
|
- **Scenario:** Trace data assets from ingestion sources through storage, pipelines, transformations, to consuming dashboards and applications.
|
|
24
|
-
- **Typical columns:** Source, Host, Database, Pipeline, Tables, Refresher, Views, App
|
|
24
|
+
- **Typical columns:** Source, Host, Database, Pipeline, Tables, Refresher, Views, Dashboard. Do not add an App or Owner column: the lane is the app (ux-rules 11).
|
|
25
25
|
- **Typical lanes:** Customer Portal, Order Service, Data Warehouse, Reporting Platform. A lane without documentation uses `gap: true`.
|
|
26
26
|
- **Node count guideline:** 15 to 30 nodes.
|
|
27
27
|
- **Recommended inspector relations:**
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# UX and design rules
|
|
2
2
|
|
|
3
|
-
The
|
|
3
|
+
The 12 rules ensure flow-studio pages remain readable, informative, accessible, and fast.
|
|
4
4
|
|
|
5
|
-
## The
|
|
5
|
+
## The 12 rules
|
|
6
6
|
|
|
7
7
|
### 1. One screen
|
|
8
8
|
- **Rule statement:** The full flow fits on one screen without requiring panning in the initial default view.
|
|
@@ -66,6 +66,20 @@ The 10 rules ensure flow-studio pages remain readable, informative, accessible,
|
|
|
66
66
|
|
|
67
67
|
---
|
|
68
68
|
|
|
69
|
+
### 11. The lane is the owner
|
|
70
|
+
- **Rule statement:** A lane names the app, team or system that owns its row. No column repeats the owner.
|
|
71
|
+
- **Rationale:** An "Apps" column between the real stages and the pages adds a block that says nothing new. In the estate lineage it sat between the reporting tables and the pages, and the owner asked why it was there (2026-10-09).
|
|
72
|
+
- **Pre-flight checklist question:** Does any column hold one node per lane that only repeats the lane title?
|
|
73
|
+
- **Common mistake & fix:** Adding an "App" column because the pattern list showed one. *Fix:* Remove the column. Link the stage that feeds a page straight to the page; a page that reads no table stands alone.
|
|
74
|
+
|
|
75
|
+
### 12. Draw a shared node once
|
|
76
|
+
- **Rule statement:** A database, job or table that several rows share is one node, centred on the rows it serves, with a link to each node downstream. A chip is only for a node that lives in another lane.
|
|
77
|
+
- **Rationale:** Copying a shared node into every row hides that it is shared and makes the reader compare copies. In the app lineage page, a database repeated in every source row looked like several databases (2026-10-09).
|
|
78
|
+
- **Pre-flight checklist question:** Does any node id appear as a chip inside the lane that already holds the real node?
|
|
79
|
+
- **Common mistake & fix:** One row per source, each with its own copy of the host, database and job. *Fix:* Draw each distinct node once, place it at the mean position of its rows, move it down if it would touch its neighbour, and de-duplicate links by their two ends.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
69
83
|
## Pre-flight checklist for agents
|
|
70
84
|
|
|
71
85
|
Before presenting a generated flow page, verify:
|
|
@@ -77,6 +91,8 @@ Before presenting a generated flow page, verify:
|
|
|
77
91
|
6. **Accessible names:** Does every button, input, tab, and control have an `aria-label` or visible text?
|
|
78
92
|
7. **Keyboard navigation:** Can a user Tab through controls and press Escape to close the inspector?
|
|
79
93
|
8. **Responsive breakdown:** Does the layout degrade cleanly down to 820x900?
|
|
94
|
+
9. **No owner column:** Does any column only repeat the lane's owner? Remove it (rule 11).
|
|
95
|
+
10. **Shared nodes once:** Is every shared database, job or table drawn once, not copied per row (rule 12)?
|
|
80
96
|
|
|
81
97
|
---
|
|
82
98
|
|
|
@@ -88,4 +104,6 @@ Before presenting a generated flow page, verify:
|
|
|
88
104
|
- **Text spill:** Node labels exceed card width. *Fix:* Use short titles (under 24 characters) and set `subtitle` for auxiliary metadata.
|
|
89
105
|
- **Permanent side panel:** The inspector remains open on load. *Fix:* The inspector must start closed and open only on selection.
|
|
90
106
|
- **Flat visual hierarchy:** Every card looks identical. *Fix:* Vary card kinds (`card`, `group`, `gateway`, `chip`) and state tones (`ok`, `warn`, `crit`, `accent`).
|
|
107
|
+
- **Owner column:** A column of one node per lane that repeats the lane title. *Fix:* Remove it; the lane is the owner.
|
|
108
|
+
- **Copies of a shared node:** The same database or job drawn in every row. *Fix:* Draw it once and link it to each node downstream.
|
|
91
109
|
- **Unlabelled controls:** Icon buttons without text lack screen reader descriptions. *Fix:* Provide descriptive `aria-label` attributes on all buttons.
|
|
@@ -649,3 +649,41 @@ The methodology in this document was extracted from a real multi-agent UI-polish
|
|
|
649
649
|
| Debug | 7 issues catalogued (A–G) | Fix-up plan created |
|
|
650
650
|
|
|
651
651
|
This end-to-end cycle took **~10 sessions** with full artefact trail and zero rework of fundamental design decisions.
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
---
|
|
655
|
+
|
|
656
|
+
## agy as an implementer: what was measured (2026-10-08)
|
|
657
|
+
|
|
658
|
+
A 17-hour run with 14 lanes used agy (Antigravity) as the main implementer. Details: `.caddis/kb/agy-headless-implementer-limits.md` in the caddis source repo. Read the rule first, then the evidence.
|
|
659
|
+
|
|
660
|
+
**Rule:** send agy only small, exact, mechanical cards, test fixes and CI watching. Send design-fidelity work, cross-file refactors and judgment to Codex or Claude. Give Opus only short contract-sized tasks.
|
|
661
|
+
|
|
662
|
+
**Why (measured):**
|
|
663
|
+
- On that machine only the Flash models worked. `gemini-3.1-pro-high` failed with "not supported in the selected location".
|
|
664
|
+
- Small exact cards: 112 of 114 passed. A card to port an app shell from screenshots ran 40 minutes and changed one icon file.
|
|
665
|
+
- In `--print` mode agy started the full test suite as a background task and waited for a notification that never came (35 to 40 minutes lost, twice).
|
|
666
|
+
- On narrow gates it invented statuses, counts and names. 11 of 14 lanes needed up to 3 review rounds.
|
|
667
|
+
|
|
668
|
+
### The agy card template
|
|
669
|
+
|
|
670
|
+
Every card that goes to agy has these sections. A card without them is not ready.
|
|
671
|
+
|
|
672
|
+
1. `## Goal`: one sentence, one component or one page.
|
|
673
|
+
2. `## Files`: a bullet list of every file the card may touch. The gate rejects any change outside it.
|
|
674
|
+
3. `## Tests first`: the failing tests to write before the code, and the one targeted command that runs them.
|
|
675
|
+
4. `## Rules for agy` (copy this block as it is):
|
|
676
|
+
- Edit first. Run foreground commands only.
|
|
677
|
+
- Never start a background task. Never wait for a notification.
|
|
678
|
+
- Never run the full test suite. The orchestrator gate runs it.
|
|
679
|
+
- No invented data: no made-up statuses, counts, names or sign-offs. If a fact is missing, say so in the card result.
|
|
680
|
+
- No fake controls. Do not remove a feature to make a test pass.
|
|
681
|
+
- Codes go through the shared formatter.
|
|
682
|
+
5. `## Done when`: the exact observable result, and "report what you changed and what you could not do".
|
|
683
|
+
|
|
684
|
+
### Orchestrator rules from the same run
|
|
685
|
+
|
|
686
|
+
- The gate runs the full suite, requires that a NEW commit exists, and stages only source folders.
|
|
687
|
+
- Restore known-noisy files (CRLF snapshot rewrites, screenshots) before checking for changes.
|
|
688
|
+
- Do not let Codex `-s workspace-write` run builds. Files it creates are owned by a sandbox user and can block worktree removal.
|
|
689
|
+
- Do not run `caddis-push` or `caddis update` while any `agy.exe` runs. The release now refuses or skips in that case.
|
|
@@ -70,7 +70,9 @@ In either route, derive node and link IDs from immutable business keys. Preserve
|
|
|
70
70
|
8. **Short titles**: titles fit one line at column width. Long descriptions belong in subtitles or member lists.
|
|
71
71
|
9. **Fixed node dimensions**: node kinds use fixed box sizes so font rendering does not shift layout.
|
|
72
72
|
10. **Reduced motion**: removes all animations while preserving static borders and status glows.
|
|
73
|
-
11. **
|
|
73
|
+
11. **The lane is the owner**: a lane is the app, team or system that owns its row. Never add a column that repeats the owner (an "Apps" or "Owner" column); it adds a block between the real stages and the pages and says nothing new.
|
|
74
|
+
12. **Draw a shared node once**: when several rows use the same database, job or table, draw one node, centre it on the rows it serves, and link it to each node downstream. A chip (`kind: "chip"` with `ref`) is only for a node that lives in another lane, never a copy inside the same lane.
|
|
75
|
+
13. **Thin links, dotted canvas**: links are hairlines (about 0.8 px, with a 6 px arrowhead) and the canvas background is faint dots, not a grid. Both come from tokens (`--edge-w`, `--dot`, ...), so an app can change them with one CSS line; see `references/design-language.md`.
|
|
74
76
|
|
|
75
77
|
## Model summary
|
|
76
78
|
|
|
@@ -21,7 +21,7 @@ You can start from these base states and adjust words to match your domain:
|
|
|
21
21
|
|
|
22
22
|
- **Example file:** `examples/lineage.json`
|
|
23
23
|
- **Scenario:** Trace data assets from ingestion sources through storage, pipelines, transformations, to consuming dashboards and applications.
|
|
24
|
-
- **Typical columns:** Source, Host, Database, Pipeline, Tables, Refresher, Views, App
|
|
24
|
+
- **Typical columns:** Source, Host, Database, Pipeline, Tables, Refresher, Views, Dashboard. Do not add an App or Owner column: the lane is the app (ux-rules 11).
|
|
25
25
|
- **Typical lanes:** Customer Portal, Order Service, Data Warehouse, Reporting Platform. A lane without documentation uses `gap: true`.
|
|
26
26
|
- **Node count guideline:** 15 to 30 nodes.
|
|
27
27
|
- **Recommended inspector relations:**
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# UX and design rules
|
|
2
2
|
|
|
3
|
-
The
|
|
3
|
+
The 12 rules ensure flow-studio pages remain readable, informative, accessible, and fast.
|
|
4
4
|
|
|
5
|
-
## The
|
|
5
|
+
## The 12 rules
|
|
6
6
|
|
|
7
7
|
### 1. One screen
|
|
8
8
|
- **Rule statement:** The full flow fits on one screen without requiring panning in the initial default view.
|
|
@@ -66,6 +66,20 @@ The 10 rules ensure flow-studio pages remain readable, informative, accessible,
|
|
|
66
66
|
|
|
67
67
|
---
|
|
68
68
|
|
|
69
|
+
### 11. The lane is the owner
|
|
70
|
+
- **Rule statement:** A lane names the app, team or system that owns its row. No column repeats the owner.
|
|
71
|
+
- **Rationale:** An "Apps" column between the real stages and the pages adds a block that says nothing new. In the estate lineage it sat between the reporting tables and the pages, and the owner asked why it was there (2026-10-09).
|
|
72
|
+
- **Pre-flight checklist question:** Does any column hold one node per lane that only repeats the lane title?
|
|
73
|
+
- **Common mistake & fix:** Adding an "App" column because the pattern list showed one. *Fix:* Remove the column. Link the stage that feeds a page straight to the page; a page that reads no table stands alone.
|
|
74
|
+
|
|
75
|
+
### 12. Draw a shared node once
|
|
76
|
+
- **Rule statement:** A database, job or table that several rows share is one node, centred on the rows it serves, with a link to each node downstream. A chip is only for a node that lives in another lane.
|
|
77
|
+
- **Rationale:** Copying a shared node into every row hides that it is shared and makes the reader compare copies. In the app lineage page, a database repeated in every source row looked like several databases (2026-10-09).
|
|
78
|
+
- **Pre-flight checklist question:** Does any node id appear as a chip inside the lane that already holds the real node?
|
|
79
|
+
- **Common mistake & fix:** One row per source, each with its own copy of the host, database and job. *Fix:* Draw each distinct node once, place it at the mean position of its rows, move it down if it would touch its neighbour, and de-duplicate links by their two ends.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
69
83
|
## Pre-flight checklist for agents
|
|
70
84
|
|
|
71
85
|
Before presenting a generated flow page, verify:
|
|
@@ -77,6 +91,8 @@ Before presenting a generated flow page, verify:
|
|
|
77
91
|
6. **Accessible names:** Does every button, input, tab, and control have an `aria-label` or visible text?
|
|
78
92
|
7. **Keyboard navigation:** Can a user Tab through controls and press Escape to close the inspector?
|
|
79
93
|
8. **Responsive breakdown:** Does the layout degrade cleanly down to 820x900?
|
|
94
|
+
9. **No owner column:** Does any column only repeat the lane's owner? Remove it (rule 11).
|
|
95
|
+
10. **Shared nodes once:** Is every shared database, job or table drawn once, not copied per row (rule 12)?
|
|
80
96
|
|
|
81
97
|
---
|
|
82
98
|
|
|
@@ -88,4 +104,6 @@ Before presenting a generated flow page, verify:
|
|
|
88
104
|
- **Text spill:** Node labels exceed card width. *Fix:* Use short titles (under 24 characters) and set `subtitle` for auxiliary metadata.
|
|
89
105
|
- **Permanent side panel:** The inspector remains open on load. *Fix:* The inspector must start closed and open only on selection.
|
|
90
106
|
- **Flat visual hierarchy:** Every card looks identical. *Fix:* Vary card kinds (`card`, `group`, `gateway`, `chip`) and state tones (`ok`, `warn`, `crit`, `accent`).
|
|
107
|
+
- **Owner column:** A column of one node per lane that repeats the lane title. *Fix:* Remove it; the lane is the owner.
|
|
108
|
+
- **Copies of a shared node:** The same database or job drawn in every row. *Fix:* Draw it once and link it to each node downstream.
|
|
91
109
|
- **Unlabelled controls:** Icon buttons without text lack screen reader descriptions. *Fix:* Provide descriptive `aria-label` attributes on all buttons.
|
package/bundles/manifest.json
CHANGED
package/dist/cli.js
CHANGED
|
@@ -5761,15 +5761,77 @@ var claudeAdapter = {
|
|
|
5761
5761
|
// src/agents/agy.ts
|
|
5762
5762
|
init_exec();
|
|
5763
5763
|
init_which();
|
|
5764
|
-
init_pkg();
|
|
5765
5764
|
import { readFileSync as readFileSync2, existsSync as existsSync2, mkdtempSync, cpSync, rmSync } from "fs";
|
|
5766
5765
|
import os from "os";
|
|
5767
5766
|
import path2 from "path";
|
|
5767
|
+
|
|
5768
|
+
// src/util/agyProcs.ts
|
|
5769
|
+
init_exec();
|
|
5770
|
+
function parseTasklistCsv(stdout) {
|
|
5771
|
+
const pids = [];
|
|
5772
|
+
for (const line2 of stdout.split(/\r?\n/)) {
|
|
5773
|
+
const match = /^"agy\.exe","(\d+)"/i.exec(line2.trim());
|
|
5774
|
+
if (match) pids.push(Number(match[1]));
|
|
5775
|
+
}
|
|
5776
|
+
return pids;
|
|
5777
|
+
}
|
|
5778
|
+
function parsePgrep(stdout) {
|
|
5779
|
+
return stdout.split(/\r?\n/).map((line2) => line2.trim()).filter((line2) => /^\d+$/.test(line2)).map(Number);
|
|
5780
|
+
}
|
|
5781
|
+
async function listAgyPids() {
|
|
5782
|
+
if (process.platform === "win32") {
|
|
5783
|
+
const result2 = await run("tasklist", ["/FI", "IMAGENAME eq agy.exe", "/FO", "CSV", "/NH"], { timeout: 2e4 });
|
|
5784
|
+
return result2.ok ? parseTasklistCsv(result2.stdout) : [];
|
|
5785
|
+
}
|
|
5786
|
+
const result = await run("pgrep", ["-x", "agy"], { timeout: 1e4 });
|
|
5787
|
+
return result.ok ? parsePgrep(result.stdout) : [];
|
|
5788
|
+
}
|
|
5789
|
+
|
|
5790
|
+
// src/agents/agy.ts
|
|
5791
|
+
init_pkg();
|
|
5768
5792
|
var BIN2 = "agy";
|
|
5769
5793
|
var BUNDLE = "antigravity-plugin";
|
|
5770
5794
|
var PLUGIN2 = "caddis";
|
|
5771
5795
|
var EXTRAS_BUNDLE = "antigravity-plugin-extras";
|
|
5772
5796
|
var EXTRAS_PLUGIN = "caddis-extras";
|
|
5797
|
+
var REQUIRED_PLUGIN_FILES = ["plugin.json", "hooks.json", "guard_agy.py"];
|
|
5798
|
+
var ALLOW_RUNNING_ENV = "CADDIS_AGY_ALLOW_RUNNING";
|
|
5799
|
+
function pluginDirFor(home = os.homedir(), plugin = PLUGIN2) {
|
|
5800
|
+
return path2.join(home, ".gemini", "config", "plugins", plugin);
|
|
5801
|
+
}
|
|
5802
|
+
function missingPluginFiles(home = os.homedir()) {
|
|
5803
|
+
const dir = pluginDirFor(home);
|
|
5804
|
+
return REQUIRED_PLUGIN_FILES.filter((file) => !existsSync2(path2.join(dir, file)));
|
|
5805
|
+
}
|
|
5806
|
+
function backupInstalledPlugin(home) {
|
|
5807
|
+
const dir = pluginDirFor(home);
|
|
5808
|
+
if (!existsSync2(dir)) return null;
|
|
5809
|
+
const holder = mkdtempSync(path2.join(os.tmpdir(), "caddis-agy-backup-"));
|
|
5810
|
+
const copy = path2.join(holder, PLUGIN2);
|
|
5811
|
+
cpSync(dir, copy, { recursive: true });
|
|
5812
|
+
let keep = false;
|
|
5813
|
+
return {
|
|
5814
|
+
location: copy,
|
|
5815
|
+
// Returns null when the old plugin is back, else the reason it could not be put back.
|
|
5816
|
+
restore: () => {
|
|
5817
|
+
try {
|
|
5818
|
+
rmSync(dir, { recursive: true, force: true });
|
|
5819
|
+
cpSync(copy, dir, { recursive: true });
|
|
5820
|
+
return null;
|
|
5821
|
+
} catch (error) {
|
|
5822
|
+
keep = true;
|
|
5823
|
+
return error instanceof Error ? error.message : String(error);
|
|
5824
|
+
}
|
|
5825
|
+
},
|
|
5826
|
+
cleanup: () => {
|
|
5827
|
+
if (keep) return;
|
|
5828
|
+
try {
|
|
5829
|
+
rmSync(holder, { recursive: true, force: true });
|
|
5830
|
+
} catch {
|
|
5831
|
+
}
|
|
5832
|
+
}
|
|
5833
|
+
};
|
|
5834
|
+
}
|
|
5773
5835
|
function pluginManifestPath(home = os.homedir(), plugin = PLUGIN2) {
|
|
5774
5836
|
return path2.join(home, ".gemini", "config", "plugins", plugin, "plugin.json");
|
|
5775
5837
|
}
|
|
@@ -5890,7 +5952,21 @@ async function drive2(_action, options) {
|
|
|
5890
5952
|
message: "dry run \u2014 nothing executed"
|
|
5891
5953
|
};
|
|
5892
5954
|
}
|
|
5955
|
+
if (process.env[ALLOW_RUNNING_ENV] !== "1") {
|
|
5956
|
+
const running = await listAgyPids();
|
|
5957
|
+
if (running.length > 0) {
|
|
5958
|
+
return {
|
|
5959
|
+
ok: false,
|
|
5960
|
+
skipped: false,
|
|
5961
|
+
steps: [],
|
|
5962
|
+
message: `agy is running (PID ${running.join(", ")}). Installing now can empty the caddis plugin under it, and agy then refuses every edit. Stop agy and run this again, or set ${ALLOW_RUNNING_ENV}=1 if you know no agy holds the plugin folder.`
|
|
5963
|
+
};
|
|
5964
|
+
}
|
|
5965
|
+
}
|
|
5966
|
+
const home = options.home ?? os.homedir();
|
|
5967
|
+
const backup = backupInstalledPlugin(home);
|
|
5893
5968
|
const cleanups = [];
|
|
5969
|
+
if (backup) cleanups.push(backup.cleanup);
|
|
5894
5970
|
try {
|
|
5895
5971
|
const steps3 = [];
|
|
5896
5972
|
for (const step of planned) {
|
|
@@ -5899,6 +5975,23 @@ async function drive2(_action, options) {
|
|
|
5899
5975
|
cleanups.push(staged.cleanup);
|
|
5900
5976
|
const execArgs = [...step.args.slice(0, -1), staged.installPath];
|
|
5901
5977
|
const result = await run(step.cmd, execArgs);
|
|
5978
|
+
if (step.bundleDir === bundle) {
|
|
5979
|
+
const missing = missingPluginFiles(home);
|
|
5980
|
+
if (missing.length > 0) {
|
|
5981
|
+
let outcome = "there was nothing to restore";
|
|
5982
|
+
if (backup) {
|
|
5983
|
+
const failure = backup.restore();
|
|
5984
|
+
outcome = failure === null ? "the previous plugin was restored" : `the restore FAILED (${failure}); the old plugin is kept at ${backup.location}`;
|
|
5985
|
+
}
|
|
5986
|
+
steps3.push({ command, ok: false, code: result.code, output: `missing after the install: ${missing.join(", ")}` });
|
|
5987
|
+
return {
|
|
5988
|
+
ok: false,
|
|
5989
|
+
skipped: false,
|
|
5990
|
+
steps: steps3,
|
|
5991
|
+
message: `the caddis plugin is incomplete after the install (missing ${missing.join(", ")}); ` + outcome
|
|
5992
|
+
};
|
|
5993
|
+
}
|
|
5994
|
+
}
|
|
5902
5995
|
steps3.push({
|
|
5903
5996
|
command,
|
|
5904
5997
|
ok: result.ok,
|
package/package.json
CHANGED