@macintacos/caret 1.1.0 → 1.1.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/.claude-plugin/plugin.json +1 -1
- package/README.md +3 -0
- package/bin/caret-launcher +48 -23
- package/commands/doctor.md +2 -1
- package/dist/cli.js +1148 -722
- package/opencode/commands/doctor.md +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -60,6 +60,9 @@ bunx --no-cache @macintacos/caret@latest install --refresh # update
|
|
|
60
60
|
bunx --no-cache @macintacos/caret@latest install --uninstall # remove
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
+
`--refresh` restarts the caret service on the newest caret installed, never older than the
|
|
64
|
+
one you ran; caret keeps a copy of it under `~/.local/state/caret/roots/`.
|
|
65
|
+
|
|
63
66
|
`caret`'s UI is designed to check for updates, at most once a day. When a newer `caret` is
|
|
64
67
|
out, the review UI says so once: a toast on load that opens **What's new** (the skipped
|
|
65
68
|
release notes or trunk commits), a mark on the settings button, and a
|
package/bin/caret-launcher
CHANGED
|
@@ -19,6 +19,9 @@
|
|
|
19
19
|
# service the launchd label WITHOUT `.plist` on macOS; the full unit name
|
|
20
20
|
# WITH `.service` on Linux. Only macOS appends a suffix below.
|
|
21
21
|
#
|
|
22
|
+
# $XDG_STATE_HOME/caret/roots/<version>/ holds caret install's own copies, scanned
|
|
23
|
+
# beside the agents' caches.
|
|
24
|
+
#
|
|
22
25
|
# Each record must end with a newline: `read -r` reports EOF on a file without
|
|
23
26
|
# one, and the read is what decides the record exists.
|
|
24
27
|
|
|
@@ -28,6 +31,8 @@ state="${XDG_STATE_HOME:-$HOME/.local/state}/caret"
|
|
|
28
31
|
records="$state/launcher"
|
|
29
32
|
claude_roots="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/plugins/cache/caret/caret"
|
|
30
33
|
opencode_roots="${XDG_CACHE_HOME:-$HOME/.cache}/opencode/packages/@macintacos"
|
|
34
|
+
# Keep in sync with ownedRootsDir() (src/config/paths.ts).
|
|
35
|
+
owned_roots="$state/roots"
|
|
31
36
|
|
|
32
37
|
# The units name no log destination, so nothing opens this before caret does:
|
|
33
38
|
# openDaemonStderr's 0700/0600 guarantee (src/daemon/lifecycle.ts) happens here for a
|
|
@@ -60,50 +65,70 @@ read_record() {
|
|
|
60
65
|
[ -f "$1" ] && read -r value <"$1" && [ -n "$value" ] && printf '%s' "$value"
|
|
61
66
|
}
|
|
62
67
|
|
|
63
|
-
# Highest of the `<version><TAB><
|
|
64
|
-
# rather than `sort -V`, which BSD sort does not carry everywhere.
|
|
65
|
-
# equal numeric prefix the whole
|
|
66
|
-
#
|
|
68
|
+
# Highest of the `<version><TAB><rest>` lines on stdin, printing <rest>. A numeric
|
|
69
|
+
# field sort rather than `sort -V`, which BSD sort does not carry everywhere. On an
|
|
70
|
+
# equal numeric prefix sort compares the whole line, and two things ride on that:
|
|
71
|
+
# candidates() puts its rank right after the version, so an agent's root (1) beats an
|
|
72
|
+
# owned copy (0) — keep the sort unstable (no -s); and a prerelease sorts ABOVE its
|
|
73
|
+
# release (0.15.0-rc.1 beats 0.15.0), a ceiling since caret publishes no prereleases.
|
|
74
|
+
# Keep in sync with pickLauncherRoot (src/commands/install/launcher.ts).
|
|
67
75
|
highest() { sort -t. -k1,1n -k2,2n -k3,3n | tail -1 | cut -f2-; }
|
|
68
76
|
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
#
|
|
77
|
+
# bin/caret-native embeds its UI at compile time; the bundle serves ui/dist, so a
|
|
78
|
+
# half-installed one would answer every UI request with ENOENT. A non-executable
|
|
79
|
+
# bin/caret would exec-fail at 126 and the supervisor would respawn into it forever.
|
|
80
|
+
# Keep in sync with isRunnableRoot (src/daemon/lifecycle.ts).
|
|
81
|
+
root_runnable() {
|
|
82
|
+
[ -x "$1/bin/caret" ] || return 1
|
|
83
|
+
[ -x "$1/bin/caret-native" ] || [ -f "$1/ui/dist/index.html" ]
|
|
84
|
+
}
|
|
85
|
+
|
|
72
86
|
# Version comes from package.json, never the directory name: OpenCode names its
|
|
73
87
|
# dir after the verbatim `plugin` specifier. The key is anchored so a minified
|
|
74
88
|
# manifest yields nothing rather than a later key's value — a miss falls into the
|
|
75
89
|
# branch that names the directory, a wrong version picks the wrong caret.
|
|
76
90
|
candidate_version() {
|
|
77
|
-
|
|
91
|
+
root_runnable "$1" || return 1
|
|
78
92
|
sed -n 's/^[[:space:]]*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$1/package.json" 2>/dev/null | head -1
|
|
79
93
|
}
|
|
80
94
|
|
|
81
|
-
# Every dir either cache
|
|
82
|
-
# a broken install (terminal) and an empty cache
|
|
83
|
-
# is a two-line npm project, not a caret root; the
|
|
95
|
+
# Every dir either agent's cache or caret's own copies offer, runnable caret or
|
|
96
|
+
# not — the difference between a broken install (terminal) and an empty cache
|
|
97
|
+
# (eviction). The OpenCode entry is a two-line npm project, not a caret root; the
|
|
98
|
+
# root is one level down. `*` skips dot-prefixed dirs, so a half-copied owned root
|
|
99
|
+
# is never a candidate. Keep in sync with launcherCandidateDirs
|
|
100
|
+
# (src/commands/install/launcher.ts).
|
|
84
101
|
candidate_dirs() {
|
|
85
102
|
local dir
|
|
86
|
-
for dir in "$claude_roots"/* "$opencode_roots"/caret*/node_modules/@macintacos/caret
|
|
103
|
+
for dir in "$claude_roots"/* "$opencode_roots"/caret*/node_modules/@macintacos/caret \
|
|
104
|
+
"$owned_roots"/*; do
|
|
87
105
|
if [ -d "$dir" ]; then printf '%s\n' "$dir"; fi
|
|
88
106
|
done
|
|
89
107
|
}
|
|
90
108
|
|
|
109
|
+
# `<version><TAB><rank><TAB><dir>`: rank 0 for caret's own copy, 1 for an agent's.
|
|
91
110
|
candidates() {
|
|
92
|
-
local dir version
|
|
111
|
+
local dir version rank
|
|
93
112
|
candidate_dirs | while read -r dir; do
|
|
94
113
|
version="$(candidate_version "$dir" || true)"
|
|
95
|
-
|
|
114
|
+
rank=1
|
|
115
|
+
case "$dir" in "$owned_roots"/*) rank=0 ;; esac
|
|
116
|
+
if [ -n "$version" ]; then printf '%s\t%s\t%s\n' "$version" "$rank" "$dir"; fi
|
|
96
117
|
done
|
|
97
118
|
}
|
|
98
119
|
|
|
99
120
|
resolve_root() {
|
|
100
121
|
local pin
|
|
101
122
|
pin="$(read_record "$records/pinned-root" || true)"
|
|
102
|
-
if [ -n "$pin" ] &&
|
|
123
|
+
if [ -n "$pin" ] && root_runnable "$pin"; then
|
|
103
124
|
printf '%s' "$pin"
|
|
104
125
|
return 0
|
|
105
126
|
fi
|
|
106
|
-
|
|
127
|
+
# The rank sorts an agent's root above an owned copy of the same version: a hook
|
|
128
|
+
# reuses a supervised daemon only when its build matches. highest() leaves
|
|
129
|
+
# candidates()' rank in front of the dir. Keep in sync with pickLauncherRoot
|
|
130
|
+
# (src/commands/install/launcher.ts).
|
|
131
|
+
candidates | highest | cut -f2-
|
|
107
132
|
}
|
|
108
133
|
|
|
109
134
|
# The newest mise-managed bun, for a machine whose mise shims are not on the
|
|
@@ -153,8 +178,8 @@ stop_agent() {
|
|
|
153
178
|
# tears its service down and deletes itself. Returns non-zero without touching
|
|
154
179
|
# anything when there is no `service` record — nothing here installed a
|
|
155
180
|
# supervisor, so this launcher cannot know what still names it, and deleting
|
|
156
|
-
# itself would leave whatever does respawning on ENOENT. Deleting
|
|
157
|
-
#
|
|
181
|
+
# itself would leave whatever does respawning on ENOENT. Deleting bin/, the
|
|
182
|
+
# records and caret's own roots leaves review state for a reinstall.
|
|
158
183
|
evict() {
|
|
159
184
|
local service unit
|
|
160
185
|
service="$(read_record "$records/service" || true)"
|
|
@@ -166,17 +191,17 @@ evict() {
|
|
|
166
191
|
systemctl --user disable --now "$service" || true
|
|
167
192
|
unit="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user/$service"
|
|
168
193
|
fi
|
|
169
|
-
printf 'caret-launcher: no caret installed anywhere; removing %s and %s.\n' \
|
|
170
|
-
"$unit" "$state
|
|
194
|
+
printf 'caret-launcher: no caret installed anywhere; removing %s and %s/{bin,launcher,roots}.\n' \
|
|
195
|
+
"$unit" "$state" >&2
|
|
171
196
|
rm -f "$unit"
|
|
172
|
-
rm -rf "${state:?}/bin" "${state:?}/launcher"
|
|
197
|
+
rm -rf "${state:?}/bin" "${state:?}/launcher" "${state:?}/roots"
|
|
173
198
|
exit 0
|
|
174
199
|
}
|
|
175
200
|
|
|
176
201
|
# Only under a supervisor: a hand-run launcher keeps its diagnostics on the terminal.
|
|
177
202
|
if [ "${CARET_SUPERVISED:-}" = 1 ]; then open_daemon_log; fi
|
|
178
203
|
|
|
179
|
-
# Only a total miss re-probes. A populated version dir with no runnable
|
|
204
|
+
# Only a total miss re-probes. A populated version dir with no runnable caret
|
|
180
205
|
# is far more likely a bad install than an extraction caught mid-flight, and that
|
|
181
206
|
# path is recoverable with `caret install`; an empty cache is the one whose branch
|
|
182
207
|
# deletes things, so it is the one worth waiting on.
|
|
@@ -190,7 +215,7 @@ done
|
|
|
190
215
|
if [ -z "$root" ]; then
|
|
191
216
|
found="$(candidate_dirs)"
|
|
192
217
|
if [ -z "$found" ]; then evict || true; fi
|
|
193
|
-
printf 'caret-launcher: no runnable bin/caret under any of:\n%s\n' \
|
|
218
|
+
printf 'caret-launcher: no runnable caret (an executable bin/caret and a UI build) under any of:\n%s\n' \
|
|
194
219
|
"${found:-(no install directories at all)}" >&2
|
|
195
220
|
printf 'caret-launcher: run: caret install — to restore the service.\n' >&2
|
|
196
221
|
stop_agent
|
package/commands/doctor.md
CHANGED
|
@@ -39,7 +39,8 @@ one. What each id means:
|
|
|
39
39
|
|
|
40
40
|
| id | what failed | relay |
|
|
41
41
|
| --- | --- | --- |
|
|
42
|
-
| `daemon-reachable` | a caret service is recorded but the daemon did not answer
|
|
42
|
+
| `daemon-reachable` | a caret service is recorded but its supervisor is not keeping it alive (not loaded, turned off, or parked) or the daemon did not answer; or the effective port answers as something other than caret | the check's remedy verbatim, then offer to read any log it names |
|
|
43
|
+
| `daemon-version` | the running daemon is older than npm's latest | the check's remedy verbatim |
|
|
43
44
|
| `daemon-lock` | the lock names a dead pid, or a port caret is not configured to bind | the check's remedy verbatim |
|
|
44
45
|
| `agent-install` | the agent the check names does not have caret enabled | `caret install` |
|
|
45
46
|
| `log-errors` | a live log wrote an NDJSON error record in the last 24h; the detail names which log and when it last erred | step 4 |
|