@procrastivity/clast 0.0.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 +11 -0
- package/.claude-plugin/skills/day-wakeup/SKILL.md +194 -0
- package/.claude-plugin/skills/wakeup/SKILL.md +115 -0
- package/LICENSE +21 -0
- package/README.md +182 -0
- package/bin/.gitkeep +0 -0
- package/bin/clast +172 -0
- package/examples/config/config.toml.sample +32 -0
- package/examples/cron/clast-snapshot.service +10 -0
- package/examples/cron/clast-snapshot.timer +15 -0
- package/examples/cron/crontab.sample +16 -0
- package/examples/workflows/morning-briefing.md +137 -0
- package/hooks/hooks.json +8 -0
- package/hooks/snapshot.sh +15 -0
- package/lib/clast/.gitkeep +0 -0
- package/lib/clast/clast-decode-lib.bash +184 -0
- package/lib/clast/clast-lib.bash +233 -0
- package/lib/clast/clast-manifest-lib.bash +232 -0
- package/lib/clast/clast-registry-lib.bash +252 -0
- package/lib/clast/clast-subcommands/.gitkeep +0 -0
- package/lib/clast/clast-subcommands/breadcrumb.bash +454 -0
- package/lib/clast/clast-subcommands/doctor.bash +607 -0
- package/lib/clast/clast-subcommands/entries.bash +697 -0
- package/lib/clast/clast-subcommands/projects.bash +309 -0
- package/lib/clast/clast-subcommands/registry.bash +207 -0
- package/lib/clast/clast-subcommands/sessions.bash +275 -0
- package/lib/clast/clast-subcommands/show.bash +283 -0
- package/lib/clast/clast-subcommands/snapshot.bash +294 -0
- package/lib/clast/clast-subcommands/stats.bash +354 -0
- package/lib/clast/clast-subcommands/whereami.bash +108 -0
- package/package.json +39 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
# clast-subcommands/projects.bash — `clast projects`.
|
|
2
|
+
#
|
|
3
|
+
# Read-only view over .manifest.jsonl: list projects (segments) with
|
|
4
|
+
# session activity in a date window. See docs/cli-contract.md#clast-projects.
|
|
5
|
+
# shellcheck shell=bash
|
|
6
|
+
# shellcheck source=lib/clast/clast-lib.bash
|
|
7
|
+
# shellcheck source=lib/clast/clast-manifest-lib.bash
|
|
8
|
+
# shellcheck source=lib/clast/clast-registry-lib.bash
|
|
9
|
+
# shellcheck source=lib/clast/clast-decode-lib.bash
|
|
10
|
+
|
|
11
|
+
_clast_projects_usage() {
|
|
12
|
+
cat <<'EOF'
|
|
13
|
+
Usage: clast projects [--day DATE] [--since DATE] [--until DATE] [--unregistered]
|
|
14
|
+
|
|
15
|
+
List projects with activity in a date window.
|
|
16
|
+
|
|
17
|
+
Flags:
|
|
18
|
+
--day DATE Single-day window (default: today). Mutually exclusive
|
|
19
|
+
with --since/--until.
|
|
20
|
+
--since DATE Start of range (inclusive).
|
|
21
|
+
--until DATE End of range (inclusive).
|
|
22
|
+
--unregistered Show only projects whose segment does not resolve via
|
|
23
|
+
the registry.
|
|
24
|
+
-h, --help Print this usage and exit.
|
|
25
|
+
|
|
26
|
+
DATE accepts ISO (YYYY-MM-DD), `today`, `yesterday`, `last-week`,
|
|
27
|
+
`-Nd`, or `-Nw`. See docs/cli-contract.md#date-parsing.
|
|
28
|
+
EOF
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
_clast_projects_err() {
|
|
32
|
+
local msg="$1"
|
|
33
|
+
if [[ -n "${CLAST_JSON:-}" ]]; then
|
|
34
|
+
jq -cn --arg m "$msg" '{error:$m, code:2}'
|
|
35
|
+
else
|
|
36
|
+
clast_log_error "projects: $msg"
|
|
37
|
+
fi
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
clast_cmd_projects() {
|
|
41
|
+
local day_filter="" since_date="" until_date=""
|
|
42
|
+
local unregistered_only=0
|
|
43
|
+
|
|
44
|
+
while [[ $# -gt 0 ]]; do
|
|
45
|
+
case "$1" in
|
|
46
|
+
--day)
|
|
47
|
+
if [[ $# -lt 2 ]]; then _clast_projects_err "--day requires a value"; return 2; fi
|
|
48
|
+
if ! day_filter="$(clast_parse_date "$2" 2>/dev/null)"; then
|
|
49
|
+
_clast_projects_err "invalid date '$2'"; return 2
|
|
50
|
+
fi
|
|
51
|
+
shift 2 ;;
|
|
52
|
+
--day=*)
|
|
53
|
+
if ! day_filter="$(clast_parse_date "${1#*=}" 2>/dev/null)"; then
|
|
54
|
+
_clast_projects_err "invalid date '${1#*=}'"; return 2
|
|
55
|
+
fi
|
|
56
|
+
shift ;;
|
|
57
|
+
--since)
|
|
58
|
+
if [[ $# -lt 2 ]]; then _clast_projects_err "--since requires a value"; return 2; fi
|
|
59
|
+
if ! since_date="$(clast_parse_date "$2" 2>/dev/null)"; then
|
|
60
|
+
_clast_projects_err "invalid date '$2'"; return 2
|
|
61
|
+
fi
|
|
62
|
+
shift 2 ;;
|
|
63
|
+
--since=*)
|
|
64
|
+
if ! since_date="$(clast_parse_date "${1#*=}" 2>/dev/null)"; then
|
|
65
|
+
_clast_projects_err "invalid date '${1#*=}'"; return 2
|
|
66
|
+
fi
|
|
67
|
+
shift ;;
|
|
68
|
+
--until)
|
|
69
|
+
if [[ $# -lt 2 ]]; then _clast_projects_err "--until requires a value"; return 2; fi
|
|
70
|
+
if ! until_date="$(clast_parse_date "$2" 2>/dev/null)"; then
|
|
71
|
+
_clast_projects_err "invalid date '$2'"; return 2
|
|
72
|
+
fi
|
|
73
|
+
shift 2 ;;
|
|
74
|
+
--until=*)
|
|
75
|
+
if ! until_date="$(clast_parse_date "${1#*=}" 2>/dev/null)"; then
|
|
76
|
+
_clast_projects_err "invalid date '${1#*=}'"; return 2
|
|
77
|
+
fi
|
|
78
|
+
shift ;;
|
|
79
|
+
--unregistered)
|
|
80
|
+
unregistered_only=1; shift ;;
|
|
81
|
+
-h|--help)
|
|
82
|
+
_clast_projects_usage; return 0 ;;
|
|
83
|
+
--)
|
|
84
|
+
shift; break ;;
|
|
85
|
+
-*)
|
|
86
|
+
_clast_projects_err "unknown flag '$1'"; return 2 ;;
|
|
87
|
+
*)
|
|
88
|
+
_clast_projects_err "unexpected positional '$1'"; return 2 ;;
|
|
89
|
+
esac
|
|
90
|
+
done
|
|
91
|
+
|
|
92
|
+
if [[ -n "$day_filter" && ( -n "$since_date" || -n "$until_date" ) ]]; then
|
|
93
|
+
_clast_projects_err "--day is mutually exclusive with --since/--until"
|
|
94
|
+
return 2
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
if [[ -z "$day_filter" && -z "$since_date" && -z "$until_date" ]]; then
|
|
98
|
+
day_filter="$(clast_today)"
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
# `--until` defaults to `today` per docs/cli-contract.md#clast-projects when
|
|
102
|
+
# `--since` is supplied without an explicit upper bound.
|
|
103
|
+
if [[ -n "$since_date" && -z "$until_date" ]]; then
|
|
104
|
+
until_date="$(clast_today)"
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
# Single-day window when --day is set, or --since == --until.
|
|
108
|
+
local single_day=""
|
|
109
|
+
if [[ -n "$day_filter" ]]; then
|
|
110
|
+
single_day="$day_filter"
|
|
111
|
+
elif [[ -n "$since_date" && "$since_date" == "$until_date" ]]; then
|
|
112
|
+
single_day="$since_date"
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
local filter
|
|
116
|
+
filter="$(_clast_projects_window_filter "$day_filter" "$since_date" "$until_date")"
|
|
117
|
+
|
|
118
|
+
local journal_dir
|
|
119
|
+
journal_dir="$(clast_journal_dir)"
|
|
120
|
+
|
|
121
|
+
# Two-pass over the manifest stream: first pass finds the most-recent
|
|
122
|
+
# manifest line per session_id within the window (manifest is append-
|
|
123
|
+
# ordered, so the last occurrence wins). Second pass aggregates per
|
|
124
|
+
# segment.
|
|
125
|
+
declare -A latest_line=()
|
|
126
|
+
local line sid
|
|
127
|
+
while IFS= read -r line; do
|
|
128
|
+
[[ -z "$line" ]] && continue
|
|
129
|
+
sid="$(jq -r '.session_id' <<<"$line")"
|
|
130
|
+
[[ -z "$sid" || "$sid" == "null" ]] && continue
|
|
131
|
+
latest_line["$sid"]="$line"
|
|
132
|
+
done < <(clast_manifest_iterate "$filter")
|
|
133
|
+
|
|
134
|
+
declare -A seg_session_count=()
|
|
135
|
+
declare -A seg_msg_count=()
|
|
136
|
+
declare -A seg_last_active=()
|
|
137
|
+
local snapshot mtime seg msgs abs_path
|
|
138
|
+
|
|
139
|
+
for sid in "${!latest_line[@]}"; do
|
|
140
|
+
line="${latest_line[$sid]}"
|
|
141
|
+
snapshot="$(jq -r '.snapshot' <<<"$line")"
|
|
142
|
+
mtime="$(jq -r '.source_mtime' <<<"$line")"
|
|
143
|
+
seg="$(awk -F/ 'NR==1{print $3}' <<<"$snapshot")"
|
|
144
|
+
[[ -z "$seg" ]] && continue
|
|
145
|
+
|
|
146
|
+
seg_session_count["$seg"]=$(( ${seg_session_count["$seg"]:-0} + 1 ))
|
|
147
|
+
|
|
148
|
+
abs_path="$journal_dir/$snapshot"
|
|
149
|
+
if [[ -r "$abs_path" ]]; then
|
|
150
|
+
msgs="$(wc -l <"$abs_path" 2>/dev/null | tr -d ' ')"
|
|
151
|
+
[[ -z "$msgs" ]] && msgs=0
|
|
152
|
+
else
|
|
153
|
+
if [[ -n "${CLAST_VERBOSE:-}" ]]; then
|
|
154
|
+
clast_log_warn "projects: snapshot missing or unreadable: $abs_path"
|
|
155
|
+
fi
|
|
156
|
+
msgs=0
|
|
157
|
+
fi
|
|
158
|
+
seg_msg_count["$seg"]=$(( ${seg_msg_count["$seg"]:-0} + msgs ))
|
|
159
|
+
|
|
160
|
+
if [[ -z "${seg_last_active["$seg"]:-}" || "$mtime" > "${seg_last_active["$seg"]}" ]]; then
|
|
161
|
+
seg_last_active["$seg"]="$mtime"
|
|
162
|
+
fi
|
|
163
|
+
done
|
|
164
|
+
|
|
165
|
+
# Build per-segment rows as JSON, applying registry + --unregistered.
|
|
166
|
+
local -a rows=()
|
|
167
|
+
local slug path remote registered row decode_rc decoded
|
|
168
|
+
for seg in "${!seg_session_count[@]}"; do
|
|
169
|
+
if slug="$(clast_registry_resolve "$seg" 2>/dev/null)" && [[ -n "$slug" ]]; then
|
|
170
|
+
registered=true
|
|
171
|
+
path="$(_clast_projects_path_for_slug "$slug")"
|
|
172
|
+
remote="$(_clast_projects_remote_for_slug "$slug")"
|
|
173
|
+
else
|
|
174
|
+
registered=false
|
|
175
|
+
slug=""
|
|
176
|
+
remote=""
|
|
177
|
+
decode_rc=0
|
|
178
|
+
decoded="$(clast_decode_segment "$seg" 2>/dev/null)" || decode_rc=$?
|
|
179
|
+
if (( decode_rc == 0 )); then
|
|
180
|
+
path="$decoded"
|
|
181
|
+
else
|
|
182
|
+
path=""
|
|
183
|
+
fi
|
|
184
|
+
fi
|
|
185
|
+
|
|
186
|
+
if (( unregistered_only == 1 )) && [[ "$registered" == "true" ]]; then
|
|
187
|
+
continue
|
|
188
|
+
fi
|
|
189
|
+
|
|
190
|
+
row="$(jq -cn \
|
|
191
|
+
--arg slug "$slug" \
|
|
192
|
+
--arg path "$path" \
|
|
193
|
+
--arg segment "$seg" \
|
|
194
|
+
--arg remote "$remote" \
|
|
195
|
+
--argjson session_count "${seg_session_count[$seg]}" \
|
|
196
|
+
--argjson msg_count_approx "${seg_msg_count[$seg]}" \
|
|
197
|
+
--arg last_active "${seg_last_active[$seg]}" \
|
|
198
|
+
--argjson registered "$registered" \
|
|
199
|
+
'{
|
|
200
|
+
slug: (if $slug == "" then null else $slug end),
|
|
201
|
+
path: (if $path == "" then null else $path end),
|
|
202
|
+
segment: $segment,
|
|
203
|
+
remote: (if $remote == "" then null else $remote end),
|
|
204
|
+
session_count: $session_count,
|
|
205
|
+
msg_count_approx: $msg_count_approx,
|
|
206
|
+
last_active: $last_active,
|
|
207
|
+
registered: $registered
|
|
208
|
+
}')"
|
|
209
|
+
rows+=("$row")
|
|
210
|
+
done
|
|
211
|
+
|
|
212
|
+
# Sort by (-session_count, slug-or-segment).
|
|
213
|
+
local rows_json='[]'
|
|
214
|
+
if (( ${#rows[@]} > 0 )); then
|
|
215
|
+
rows_json="$(printf '%s\n' "${rows[@]}" \
|
|
216
|
+
| jq -cs 'sort_by([-.session_count, (.slug // .segment)])')"
|
|
217
|
+
fi
|
|
218
|
+
|
|
219
|
+
if [[ -n "${CLAST_JSON:-}" ]]; then
|
|
220
|
+
printf '%s\n' "$rows_json"
|
|
221
|
+
return 0
|
|
222
|
+
fi
|
|
223
|
+
|
|
224
|
+
if [[ -n "${CLAST_QUIET:-}" ]]; then
|
|
225
|
+
return 0
|
|
226
|
+
fi
|
|
227
|
+
|
|
228
|
+
# Default human output: header + one row per project.
|
|
229
|
+
printf '%-17s %-33s %9s %5s %s\n' \
|
|
230
|
+
"slug" "path" "sessions" "msgs" "last_active"
|
|
231
|
+
|
|
232
|
+
local n
|
|
233
|
+
n="$(jq 'length' <<<"$rows_json")"
|
|
234
|
+
local i sj
|
|
235
|
+
for (( i = 0; i < n; i++ )); do
|
|
236
|
+
sj="$(jq -c ".[$i]" <<<"$rows_json")"
|
|
237
|
+
local r_slug r_path r_seg r_sessions r_msgs r_last_active disp_slug disp_path
|
|
238
|
+
r_slug="$(jq -r '.slug // ""' <<<"$sj")"
|
|
239
|
+
r_path="$(jq -r '.path // ""' <<<"$sj")"
|
|
240
|
+
r_seg="$(jq -r '.segment' <<<"$sj")"
|
|
241
|
+
r_sessions="$(jq -r '.session_count' <<<"$sj")"
|
|
242
|
+
r_msgs="$(jq -r '.msg_count_approx' <<<"$sj")"
|
|
243
|
+
r_last_active="$(jq -r '.last_active' <<<"$sj")"
|
|
244
|
+
|
|
245
|
+
if [[ -n "$r_slug" ]]; then
|
|
246
|
+
disp_slug="$r_slug"
|
|
247
|
+
else
|
|
248
|
+
disp_slug="(unregistered)"
|
|
249
|
+
fi
|
|
250
|
+
if [[ -n "$r_path" ]]; then
|
|
251
|
+
disp_path="$r_path"
|
|
252
|
+
else
|
|
253
|
+
disp_path="$r_seg"
|
|
254
|
+
fi
|
|
255
|
+
|
|
256
|
+
local disp_last
|
|
257
|
+
if [[ -n "$single_day" ]]; then
|
|
258
|
+
disp_last="${r_last_active:11:5}"
|
|
259
|
+
else
|
|
260
|
+
disp_last="${r_last_active:0:10} ${r_last_active:11:5}"
|
|
261
|
+
fi
|
|
262
|
+
|
|
263
|
+
printf '%-17s %-33s %9s %5s %s\n' \
|
|
264
|
+
"$disp_slug" "$disp_path" "$r_sessions" "$r_msgs" "$disp_last"
|
|
265
|
+
done
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
# _clast_projects_window_filter <day_filter> <since> <until>
|
|
269
|
+
# Build a jq select-body string matching .day_bucket against the window.
|
|
270
|
+
_clast_projects_window_filter() {
|
|
271
|
+
local day="$1" since="$2" until="$3"
|
|
272
|
+
if [[ -n "$day" ]]; then
|
|
273
|
+
printf '.day_bucket == "%s"' "$day"
|
|
274
|
+
return 0
|
|
275
|
+
fi
|
|
276
|
+
local parts=()
|
|
277
|
+
if [[ -n "$since" ]]; then
|
|
278
|
+
parts+=(".day_bucket >= \"$since\"")
|
|
279
|
+
fi
|
|
280
|
+
if [[ -n "$until" ]]; then
|
|
281
|
+
parts+=(".day_bucket <= \"$until\"")
|
|
282
|
+
fi
|
|
283
|
+
if (( ${#parts[@]} == 0 )); then
|
|
284
|
+
printf 'true'
|
|
285
|
+
return 0
|
|
286
|
+
fi
|
|
287
|
+
local joined="${parts[0]}"
|
|
288
|
+
local i
|
|
289
|
+
for (( i = 1; i < ${#parts[@]}; i++ )); do
|
|
290
|
+
joined+=" and ${parts[i]}"
|
|
291
|
+
done
|
|
292
|
+
printf '%s' "$joined"
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
# _clast_projects_path_for_slug <slug>
|
|
296
|
+
# First registry path for <slug>, or empty.
|
|
297
|
+
_clast_projects_path_for_slug() {
|
|
298
|
+
local slug="$1"
|
|
299
|
+
clast_registry_list_json \
|
|
300
|
+
| jq -r --arg s "$slug" 'map(select(.slug == $s)) | .[0].path // empty'
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
# _clast_projects_remote_for_slug <slug>
|
|
304
|
+
# First non-empty registry remote for <slug>, or empty.
|
|
305
|
+
_clast_projects_remote_for_slug() {
|
|
306
|
+
local slug="$1"
|
|
307
|
+
clast_registry_list_json \
|
|
308
|
+
| jq -r --arg s "$slug" 'map(select(.slug == $s and (.remote // "") != "")) | .[0].remote // empty'
|
|
309
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# clast-subcommands/registry.bash — `clast registry list|add|resolve|remove`.
|
|
2
|
+
# shellcheck shell=bash
|
|
3
|
+
# shellcheck source=lib/clast/clast-lib.bash
|
|
4
|
+
# shellcheck source=lib/clast/clast-registry-lib.bash
|
|
5
|
+
|
|
6
|
+
_clast_registry_usage() {
|
|
7
|
+
cat <<'EOF'
|
|
8
|
+
Usage:
|
|
9
|
+
clast registry list [--json]
|
|
10
|
+
clast registry add <path> [--slug NAME] [--remote URL] [--json]
|
|
11
|
+
clast registry resolve <path-or-segment> [--json]
|
|
12
|
+
clast registry remove <slug> [--json]
|
|
13
|
+
EOF
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
clast_cmd_registry() {
|
|
17
|
+
if [[ $# -eq 0 ]]; then
|
|
18
|
+
_clast_registry_usage >&2
|
|
19
|
+
return 2
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
local op="$1"; shift
|
|
23
|
+
case "$op" in
|
|
24
|
+
list) _clast_registry_op_list "$@" ;;
|
|
25
|
+
add) _clast_registry_op_add "$@" ;;
|
|
26
|
+
resolve) _clast_registry_op_resolve "$@" ;;
|
|
27
|
+
remove) _clast_registry_op_remove "$@" ;;
|
|
28
|
+
-h|--help)
|
|
29
|
+
_clast_registry_usage
|
|
30
|
+
return 0
|
|
31
|
+
;;
|
|
32
|
+
*)
|
|
33
|
+
clast_log_error "registry: unknown op '$op'"
|
|
34
|
+
_clast_registry_usage >&2
|
|
35
|
+
return 2
|
|
36
|
+
;;
|
|
37
|
+
esac
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
_clast_registry_op_list() {
|
|
41
|
+
local json="${CLAST_JSON:-}"
|
|
42
|
+
while [[ $# -gt 0 ]]; do
|
|
43
|
+
case "$1" in
|
|
44
|
+
--json) json=1; shift ;;
|
|
45
|
+
-h|--help)
|
|
46
|
+
cat <<'EOF'
|
|
47
|
+
Usage: clast registry list [--json]
|
|
48
|
+
EOF
|
|
49
|
+
return 0
|
|
50
|
+
;;
|
|
51
|
+
*)
|
|
52
|
+
clast_log_error "registry list: unexpected arg '$1'"
|
|
53
|
+
return 2
|
|
54
|
+
;;
|
|
55
|
+
esac
|
|
56
|
+
done
|
|
57
|
+
|
|
58
|
+
local arr
|
|
59
|
+
arr="$(clast_registry_list_json)"
|
|
60
|
+
|
|
61
|
+
if [[ -n "$json" ]]; then
|
|
62
|
+
printf '%s\n' "$arr"
|
|
63
|
+
return 0
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
printf '%-17s %-33s %-43s %s\n' slug path remote aliases
|
|
67
|
+
local n i slug path remote aliases
|
|
68
|
+
n="$(jq 'length' <<<"$arr")"
|
|
69
|
+
for (( i = 0; i < n; i++ )); do
|
|
70
|
+
slug="$(jq -r ".[$i].slug // \"\"" <<<"$arr")"
|
|
71
|
+
path="$(jq -r ".[$i].path // \"\"" <<<"$arr")"
|
|
72
|
+
remote="$(jq -r ".[$i].remote // \"\"" <<<"$arr")"
|
|
73
|
+
aliases="$(jq -r ".[$i].aliases // [] | if length == 0 then \"(none)\" else join(\",\") end" <<<"$arr")"
|
|
74
|
+
printf '%-17s %-33s %-43s %s\n' "$slug" "$path" "$remote" "$aliases"
|
|
75
|
+
done
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
_clast_registry_op_add() {
|
|
79
|
+
local json="${CLAST_JSON:-}"
|
|
80
|
+
local -a passthrough=()
|
|
81
|
+
while [[ $# -gt 0 ]]; do
|
|
82
|
+
case "$1" in
|
|
83
|
+
--json) json=1; shift ;;
|
|
84
|
+
-h|--help)
|
|
85
|
+
cat <<'EOF'
|
|
86
|
+
Usage: clast registry add <path> [--slug NAME] [--remote URL] [--json]
|
|
87
|
+
EOF
|
|
88
|
+
return 0
|
|
89
|
+
;;
|
|
90
|
+
# TODO(v1.1): interactive --slug prompt when stdin is a TTY.
|
|
91
|
+
--slug|--remote)
|
|
92
|
+
if [[ $# -lt 2 ]]; then
|
|
93
|
+
clast_log_error "registry add: $1 requires a value"
|
|
94
|
+
return 2
|
|
95
|
+
fi
|
|
96
|
+
passthrough+=("$1" "$2"); shift 2 ;;
|
|
97
|
+
--slug=*|--remote=*)
|
|
98
|
+
passthrough+=("$1"); shift ;;
|
|
99
|
+
*)
|
|
100
|
+
passthrough+=("$1"); shift ;;
|
|
101
|
+
esac
|
|
102
|
+
done
|
|
103
|
+
|
|
104
|
+
local line rc=0
|
|
105
|
+
line="$(clast_registry_add "${passthrough[@]}")" || rc=$?
|
|
106
|
+
if (( rc != 0 )); then
|
|
107
|
+
return "$rc"
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
if [[ -n "$json" ]]; then
|
|
111
|
+
printf '%s\n' "$line"
|
|
112
|
+
else
|
|
113
|
+
local slug path
|
|
114
|
+
slug="$(jq -r '.slug' <<<"$line")"
|
|
115
|
+
path="$(jq -r '.path' <<<"$line")"
|
|
116
|
+
printf 'registered %s → %s\n' "$slug" "$path"
|
|
117
|
+
fi
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
_clast_registry_op_resolve() {
|
|
121
|
+
local json="${CLAST_JSON:-}"
|
|
122
|
+
local input=""
|
|
123
|
+
while [[ $# -gt 0 ]]; do
|
|
124
|
+
case "$1" in
|
|
125
|
+
--json) json=1; shift ;;
|
|
126
|
+
-h|--help)
|
|
127
|
+
cat <<'EOF'
|
|
128
|
+
Usage: clast registry resolve <path-or-segment> [--json]
|
|
129
|
+
EOF
|
|
130
|
+
return 0
|
|
131
|
+
;;
|
|
132
|
+
*)
|
|
133
|
+
if [[ -n "$input" ]]; then
|
|
134
|
+
clast_log_error "registry resolve: unexpected arg '$1'"
|
|
135
|
+
return 2
|
|
136
|
+
fi
|
|
137
|
+
input="$1"; shift
|
|
138
|
+
;;
|
|
139
|
+
esac
|
|
140
|
+
done
|
|
141
|
+
|
|
142
|
+
if [[ -z "$input" ]]; then
|
|
143
|
+
clast_log_error "registry resolve: <path-or-segment> is required"
|
|
144
|
+
return 2
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
local slug
|
|
148
|
+
if slug="$(clast_registry_resolve "$input")" && [[ -n "$slug" ]]; then
|
|
149
|
+
if [[ -n "$json" ]]; then
|
|
150
|
+
jq -cn --arg slug "$slug" '{slug: $slug}'
|
|
151
|
+
else
|
|
152
|
+
printf '%s\n' "$slug"
|
|
153
|
+
fi
|
|
154
|
+
return 0
|
|
155
|
+
fi
|
|
156
|
+
|
|
157
|
+
if [[ -n "$json" ]]; then
|
|
158
|
+
printf '%s\n' '{"error":"not registered"}'
|
|
159
|
+
else
|
|
160
|
+
clast_log_error "not registered"
|
|
161
|
+
fi
|
|
162
|
+
return 1
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
_clast_registry_op_remove() {
|
|
166
|
+
local json="${CLAST_JSON:-}"
|
|
167
|
+
local slug=""
|
|
168
|
+
while [[ $# -gt 0 ]]; do
|
|
169
|
+
case "$1" in
|
|
170
|
+
--json) json=1; shift ;;
|
|
171
|
+
-h|--help)
|
|
172
|
+
cat <<'EOF'
|
|
173
|
+
Usage: clast registry remove <slug> [--json]
|
|
174
|
+
EOF
|
|
175
|
+
return 0
|
|
176
|
+
;;
|
|
177
|
+
*)
|
|
178
|
+
if [[ -n "$slug" ]]; then
|
|
179
|
+
clast_log_error "registry remove: unexpected arg '$1'"
|
|
180
|
+
return 2
|
|
181
|
+
fi
|
|
182
|
+
slug="$1"; shift
|
|
183
|
+
;;
|
|
184
|
+
esac
|
|
185
|
+
done
|
|
186
|
+
|
|
187
|
+
if [[ -z "$slug" ]]; then
|
|
188
|
+
clast_log_error "registry remove: <slug> is required"
|
|
189
|
+
return 2
|
|
190
|
+
fi
|
|
191
|
+
|
|
192
|
+
if clast_registry_remove "$slug"; then
|
|
193
|
+
if [[ -n "$json" ]]; then
|
|
194
|
+
jq -cn --arg slug "$slug" '{removed: $slug}'
|
|
195
|
+
else
|
|
196
|
+
printf 'unregistered %s\n' "$slug"
|
|
197
|
+
fi
|
|
198
|
+
return 0
|
|
199
|
+
fi
|
|
200
|
+
|
|
201
|
+
if [[ -n "$json" ]]; then
|
|
202
|
+
printf '%s\n' '{"error":"not registered"}'
|
|
203
|
+
else
|
|
204
|
+
clast_log_error "not registered"
|
|
205
|
+
fi
|
|
206
|
+
return 1
|
|
207
|
+
}
|