@procrastivity/clast 0.0.2 → 0.0.4
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/skills/day-wakeup/SKILL.md +21 -19
- package/.claude-plugin/skills/wakeup/SKILL.md +15 -13
- package/README.md +36 -31
- package/bin/clast +22 -124
- package/bin/clast-plumbing +173 -0
- package/examples/cron/clast-snapshot.service +2 -2
- package/examples/cron/clast-snapshot.timer +1 -1
- package/examples/cron/crontab.sample +4 -4
- package/examples/workflows/morning-briefing.md +11 -11
- package/hooks/snapshot.sh +5 -5
- package/lib/clast/clast-dismissed-lib.bash +6 -5
- package/lib/clast/clast-lib.bash +5 -2
- package/lib/clast/clast-manifest-lib.bash +28 -5
- package/lib/clast/clast-porcelain-lib.bash +217 -0
- package/lib/clast/clast-porcelain-subcommands/brief.bash +167 -0
- package/{bin/clast-wake → lib/clast/clast-porcelain-subcommands/wake.bash} +87 -217
- package/lib/clast/clast-registry-lib.bash +32 -14
- package/lib/clast/clast-subcommands/entries.bash +9 -15
- package/lib/clast/clast-subcommands/projects.bash +7 -1
- package/lib/clast/clast-subcommands/sessions.bash +92 -47
- package/lib/clast/clast-subcommands/show.bash +18 -7
- package/lib/clast/clast-subcommands/snapshot.bash +12 -2
- package/lib/clast/clast-subcommands/stats.bash +7 -2
- package/lib/clast/prompts/day-wakeup-draft-system.md +1 -0
- package/package.json +3 -2
- package/bin/clast-brief +0 -305
|
@@ -1,32 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
# clast-wake — LLM-powered day curation without Claude Code.
|
|
1
|
+
# clast wake — LLM-powered interactive day curation.
|
|
3
2
|
#
|
|
4
|
-
# Replicates the /day-wakeup plugin skill using an OpenAI-compatible
|
|
5
|
-
#
|
|
6
|
-
#
|
|
3
|
+
# Replicates the /day-wakeup plugin skill using an OpenAI-compatible chat
|
|
4
|
+
# completions endpoint. Calls clast-plumbing for data, assembles prompts,
|
|
5
|
+
# calls the LLM via curl, presents drafts interactively.
|
|
7
6
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# CLAST_LLM_API_KEY — bearer token
|
|
11
|
-
# CLAST_LLM_MODEL — e.g. gpt-4o, llama3
|
|
12
|
-
set -euo pipefail
|
|
7
|
+
# Usage: clast wake
|
|
8
|
+
# shellcheck shell=bash
|
|
13
9
|
|
|
14
|
-
|
|
10
|
+
# --- Small helpers -----------------------------------------------------------
|
|
15
11
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
die() { printf 'clast-wake: %s\n' "$1" >&2; exit "${2:-1}"; }
|
|
19
|
-
warn() { printf 'clast-wake: warning: %s\n' "$1" >&2; }
|
|
20
|
-
info() { printf '%s\n' "$1"; }
|
|
21
|
-
|
|
22
|
-
slugify() {
|
|
12
|
+
_clast_wake_slugify() {
|
|
23
13
|
local s="$1"
|
|
24
14
|
s="${s,,}"
|
|
25
15
|
s="$(printf '%s' "$s" | sed 's/[^a-z0-9]/-/g; s/--*/-/g; s/^-//; s/-$//')"
|
|
26
16
|
printf '%s' "${s:0:60}"
|
|
27
17
|
}
|
|
28
18
|
|
|
29
|
-
|
|
19
|
+
_clast_wake_separator() {
|
|
30
20
|
local label="$1"
|
|
31
21
|
local width=72
|
|
32
22
|
local pad=$(( width - ${#label} - 4 ))
|
|
@@ -37,141 +27,25 @@ separator() {
|
|
|
37
27
|
printf '── %s %s\n' "$label" "$dashes"
|
|
38
28
|
}
|
|
39
29
|
|
|
40
|
-
#
|
|
30
|
+
# --- Preflight ---------------------------------------------------------------
|
|
41
31
|
|
|
42
|
-
|
|
32
|
+
_clast_wake_preflight() {
|
|
43
33
|
if [[ ! -t 0 ]]; then
|
|
44
|
-
|
|
45
|
-
fi
|
|
46
|
-
|
|
47
|
-
for tool in clast curl jq; do
|
|
48
|
-
if ! command -v "$tool" >/dev/null 2>&1; then
|
|
49
|
-
die "required tool not found: $tool"
|
|
50
|
-
fi
|
|
51
|
-
done
|
|
52
|
-
|
|
53
|
-
local missing=0
|
|
54
|
-
if [[ -z "${CLAST_LLM_BASE_URL:-}" ]]; then
|
|
55
|
-
warn "CLAST_LLM_BASE_URL not set"; missing=1
|
|
56
|
-
fi
|
|
57
|
-
if [[ -z "${CLAST_LLM_API_KEY:-}" ]]; then
|
|
58
|
-
warn "CLAST_LLM_API_KEY not set"; missing=1
|
|
59
|
-
fi
|
|
60
|
-
if [[ -z "${CLAST_LLM_MODEL:-}" ]]; then
|
|
61
|
-
warn "CLAST_LLM_MODEL not set"; missing=1
|
|
62
|
-
fi
|
|
63
|
-
|
|
64
|
-
if (( missing )); then
|
|
65
|
-
cat >&2 <<'EOF'
|
|
66
|
-
|
|
67
|
-
Set these env vars before running clast-wake:
|
|
68
|
-
|
|
69
|
-
export CLAST_LLM_BASE_URL="https://api.openai.com/v1"
|
|
70
|
-
export CLAST_LLM_API_KEY="sk-..."
|
|
71
|
-
export CLAST_LLM_MODEL="gpt-4o"
|
|
72
|
-
|
|
73
|
-
Or for a local model (ollama, vllm, etc.):
|
|
74
|
-
|
|
75
|
-
export CLAST_LLM_BASE_URL="http://localhost:11434/v1"
|
|
76
|
-
export CLAST_LLM_API_KEY="unused"
|
|
77
|
-
export CLAST_LLM_MODEL="llama3"
|
|
78
|
-
EOF
|
|
79
|
-
exit 1
|
|
80
|
-
fi
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
# ── LLM call ─────────────────────────────────────────────────────────
|
|
84
|
-
|
|
85
|
-
llm_chat() {
|
|
86
|
-
local system_msg="$1"
|
|
87
|
-
local user_msg="$2"
|
|
88
|
-
|
|
89
|
-
local payload
|
|
90
|
-
payload="$(jq -cn \
|
|
91
|
-
--arg model "$CLAST_LLM_MODEL" \
|
|
92
|
-
--arg system "$system_msg" \
|
|
93
|
-
--arg user "$user_msg" \
|
|
94
|
-
'{
|
|
95
|
-
model: $model,
|
|
96
|
-
messages: [
|
|
97
|
-
{role: "system", content: $system},
|
|
98
|
-
{role: "user", content: $user}
|
|
99
|
-
],
|
|
100
|
-
temperature: 0.3
|
|
101
|
-
}')"
|
|
102
|
-
|
|
103
|
-
local response http_code body
|
|
104
|
-
response="$(curl -s -w '\n%{http_code}' \
|
|
105
|
-
"${CLAST_LLM_BASE_URL}/chat/completions" \
|
|
106
|
-
-H "Authorization: Bearer $CLAST_LLM_API_KEY" \
|
|
107
|
-
-H "Content-Type: application/json" \
|
|
108
|
-
-d "$payload" 2>&1)" || true
|
|
109
|
-
|
|
110
|
-
http_code="$(tail -n1 <<<"$response")"
|
|
111
|
-
body="$(sed '$d' <<<"$response")"
|
|
112
|
-
|
|
113
|
-
if [[ "$http_code" != "200" ]]; then
|
|
114
|
-
warn "LLM API returned HTTP $http_code"
|
|
115
|
-
if [[ -n "$body" ]]; then
|
|
116
|
-
local err_msg
|
|
117
|
-
err_msg="$(jq -r '.error.message // .error // .' <<<"$body" 2>/dev/null || echo "$body")"
|
|
118
|
-
warn "$err_msg"
|
|
119
|
-
fi
|
|
120
|
-
return 1
|
|
121
|
-
fi
|
|
122
|
-
|
|
123
|
-
local content
|
|
124
|
-
content="$(jq -r '.choices[0].message.content // empty' <<<"$body" 2>/dev/null)" || {
|
|
125
|
-
warn "failed to parse LLM response"
|
|
126
|
-
return 1
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if [[ -z "$content" ]]; then
|
|
130
|
-
warn "LLM returned empty content"
|
|
131
|
-
return 1
|
|
132
|
-
fi
|
|
133
|
-
|
|
134
|
-
printf '%s' "$content"
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
# ── Prompt template ──────────────────────────────────────────────────
|
|
138
|
-
|
|
139
|
-
resolve_prompt_dir() {
|
|
140
|
-
local dir="$CLAST_WAKE_DIR/lib/clast/prompts"
|
|
141
|
-
if [[ -d "$dir" ]]; then
|
|
142
|
-
printf '%s' "$dir"
|
|
143
|
-
return
|
|
34
|
+
clast_porcelain_die "clast wake requires an interactive terminal (stdin is not a tty)."
|
|
144
35
|
fi
|
|
145
|
-
|
|
146
|
-
for installed in /usr/local/lib/clast/prompts "$HOME/.local/lib/clast/prompts"; do
|
|
147
|
-
if [[ -d "$installed" ]]; then
|
|
148
|
-
printf '%s' "$installed"
|
|
149
|
-
return
|
|
150
|
-
fi
|
|
151
|
-
done
|
|
152
|
-
die "cannot find prompts directory (checked $dir and install paths)"
|
|
36
|
+
clast_porcelain_preflight_llm
|
|
153
37
|
}
|
|
154
38
|
|
|
155
|
-
|
|
156
|
-
local prompt_dir
|
|
157
|
-
prompt_dir="$(resolve_prompt_dir)"
|
|
158
|
-
local file="$prompt_dir/day-wakeup-draft-system.md"
|
|
159
|
-
if [[ ! -r "$file" ]]; then
|
|
160
|
-
die "system prompt not found: $file"
|
|
161
|
-
fi
|
|
162
|
-
cat "$file"
|
|
163
|
-
}
|
|
39
|
+
# --- User prompt -------------------------------------------------------------
|
|
164
40
|
|
|
165
|
-
|
|
41
|
+
_clast_wake_build_user_prompt() {
|
|
166
42
|
local project="$1" branch="$2" start="$3" end="$4" msg_count="$5"
|
|
167
43
|
local first_turns="$6" last_turns="$7" breadcrumbs="$8"
|
|
168
44
|
|
|
169
|
-
local
|
|
170
|
-
|
|
171
|
-
local template_file="$prompt_dir/day-wakeup-draft-user.md"
|
|
45
|
+
local template_file template
|
|
46
|
+
template_file="$(clast_porcelain_user_prompt_file day-wakeup-draft-user)"
|
|
172
47
|
|
|
173
|
-
if [[ -
|
|
174
|
-
local template
|
|
48
|
+
if [[ -n "$template_file" ]]; then
|
|
175
49
|
template="$(cat "$template_file")"
|
|
176
50
|
template="${template//\{\{project\}\}/${project}}"
|
|
177
51
|
template="${template//\{\{branch\}\}/${branch:-unknown}}"
|
|
@@ -183,7 +57,7 @@ build_user_prompt() {
|
|
|
183
57
|
template="${template//\{\{breadcrumbs\}\}/${breadcrumbs:-None.}}"
|
|
184
58
|
printf '%s' "$template"
|
|
185
59
|
else
|
|
186
|
-
|
|
60
|
+
clast_porcelain_warn "user prompt template not found: day-wakeup-draft-user.md — using inline fallback"
|
|
187
61
|
cat <<EOF
|
|
188
62
|
Session metadata:
|
|
189
63
|
- Project: ${project}
|
|
@@ -204,34 +78,31 @@ EOF
|
|
|
204
78
|
fi
|
|
205
79
|
}
|
|
206
80
|
|
|
207
|
-
#
|
|
81
|
+
# --- Draft parsing -----------------------------------------------------------
|
|
208
82
|
|
|
209
|
-
|
|
83
|
+
_clast_wake_extract_title() {
|
|
210
84
|
local draft="$1"
|
|
211
|
-
|
|
212
|
-
title="$(grep -m1 '^# Session:' <<<"$draft" | sed 's/^# Session:[[:space:]]*//')"
|
|
213
|
-
printf '%s' "$title"
|
|
85
|
+
grep -m1 '^# Session:' <<<"$draft" | sed 's/^# Session:[[:space:]]*//'
|
|
214
86
|
}
|
|
215
87
|
|
|
216
|
-
|
|
88
|
+
_clast_wake_extract_tags() {
|
|
217
89
|
local draft="$1"
|
|
218
90
|
local tags
|
|
219
91
|
tags="$(grep -i '^Suggested tags:' <<<"$draft" | sed 's/^[Ss]uggested tags:[[:space:]]*//')"
|
|
220
|
-
|
|
221
|
-
printf '%s' "$tags"
|
|
92
|
+
printf '%s' "$tags" | sed 's/[[:space:]]*,[[:space:]]*/,/g'
|
|
222
93
|
}
|
|
223
94
|
|
|
224
|
-
|
|
95
|
+
_clast_wake_strip_tags_trailer() {
|
|
225
96
|
local draft="$1"
|
|
226
97
|
printf '%s' "$draft" | sed '/^$/{ N; /\n[Ss]uggested tags:/d; }' | sed '/^[Ss]uggested tags:/d'
|
|
227
98
|
}
|
|
228
99
|
|
|
229
|
-
#
|
|
100
|
+
# --- Session slug ------------------------------------------------------------
|
|
230
101
|
|
|
231
|
-
|
|
102
|
+
_clast_wake_get_session_slug() {
|
|
232
103
|
local snapshot_path="$1" draft_title="$2"
|
|
233
104
|
local journal_dir
|
|
234
|
-
journal_dir="$(clast whereami 2>/dev/null | grep '^journal_dir:' | awk '{print $2}')" || true
|
|
105
|
+
journal_dir="$(clast-plumbing whereami 2>/dev/null | grep '^journal_dir:' | awk '{print $2}')" || true
|
|
235
106
|
if [[ -z "$journal_dir" ]]; then
|
|
236
107
|
journal_dir="${HOME}/.claude/journal"
|
|
237
108
|
fi
|
|
@@ -242,22 +113,22 @@ get_session_slug() {
|
|
|
242
113
|
local ai_title
|
|
243
114
|
ai_title="$(jq -r 'select(.type == "ai-title") | .aiTitle' "$abs_path" 2>/dev/null | tail -1)" || true
|
|
244
115
|
if [[ -n "$ai_title" && "$ai_title" != "null" ]]; then
|
|
245
|
-
|
|
116
|
+
_clast_wake_slugify "$ai_title"
|
|
246
117
|
return
|
|
247
118
|
fi
|
|
248
119
|
fi
|
|
249
120
|
|
|
250
121
|
if [[ -n "$draft_title" ]]; then
|
|
251
|
-
|
|
122
|
+
_clast_wake_slugify "$draft_title"
|
|
252
123
|
return
|
|
253
124
|
fi
|
|
254
125
|
|
|
255
126
|
printf 'session'
|
|
256
127
|
}
|
|
257
128
|
|
|
258
|
-
#
|
|
129
|
+
# --- Interactive menu --------------------------------------------------------
|
|
259
130
|
|
|
260
|
-
|
|
131
|
+
_clast_wake_prompt_choice() {
|
|
261
132
|
local choice
|
|
262
133
|
printf '\n [a] Accept [e] Edit [d] Dismiss [s] Skip [q] Stop here\n' >/dev/tty
|
|
263
134
|
printf ' Choice: ' >/dev/tty
|
|
@@ -266,23 +137,22 @@ prompt_choice() {
|
|
|
266
137
|
printf '%s' "$choice"
|
|
267
138
|
}
|
|
268
139
|
|
|
269
|
-
|
|
140
|
+
_clast_wake_prompt_edit_feedback() {
|
|
270
141
|
local feedback
|
|
271
142
|
printf '\n What should change? ' >/dev/tty
|
|
272
143
|
read -r feedback </dev/tty
|
|
273
144
|
printf '%s' "$feedback"
|
|
274
145
|
}
|
|
275
146
|
|
|
276
|
-
#
|
|
147
|
+
# --- Triage ------------------------------------------------------------------
|
|
277
148
|
|
|
278
149
|
_clast_wake_triage() {
|
|
279
150
|
local uncurated="$1" total="$2" day_count="$3"
|
|
280
151
|
local first_day="$4" last_day="$5" project_count="$6"
|
|
281
152
|
|
|
282
|
-
|
|
153
|
+
clast_porcelain_info "Found $total uncurated session(s) across $day_count day(s) ($first_day to $last_day)." >/dev/tty
|
|
283
154
|
printf '\n' >/dev/tty
|
|
284
155
|
|
|
285
|
-
# Show per-day breakdown
|
|
286
156
|
local breakdown
|
|
287
157
|
breakdown="$(jq -r '
|
|
288
158
|
group_by(.day_bucket) | sort_by(.[0].day_bucket) | .[] |
|
|
@@ -315,13 +185,13 @@ _clast_wake_triage() {
|
|
|
315
185
|
printf ' How many days back? ' >/dev/tty
|
|
316
186
|
read -r days </dev/tty
|
|
317
187
|
if ! [[ "$days" =~ ^[1-9][0-9]*$ ]]; then
|
|
318
|
-
|
|
188
|
+
clast_porcelain_warn "invalid number — processing all sessions"
|
|
319
189
|
printf '%s' "$uncurated"
|
|
320
190
|
return
|
|
321
191
|
fi
|
|
322
192
|
local cutoff
|
|
323
193
|
cutoff="$(date -d "-${days} days" +%Y-%m-%d 2>/dev/null)" || {
|
|
324
|
-
|
|
194
|
+
clast_porcelain_warn "failed to compute date — processing all sessions"
|
|
325
195
|
printf '%s' "$uncurated"
|
|
326
196
|
return
|
|
327
197
|
}
|
|
@@ -332,28 +202,27 @@ _clast_wake_triage() {
|
|
|
332
202
|
printf ' Keep how many days? (dismiss everything older) ' >/dev/tty
|
|
333
203
|
read -r days_keep </dev/tty
|
|
334
204
|
if ! [[ "$days_keep" =~ ^[1-9][0-9]*$ ]]; then
|
|
335
|
-
|
|
205
|
+
clast_porcelain_warn "invalid number — processing all sessions"
|
|
336
206
|
printf '%s' "$uncurated"
|
|
337
207
|
return
|
|
338
208
|
fi
|
|
339
209
|
local cutoff_keep
|
|
340
210
|
cutoff_keep="$(date -d "-${days_keep} days" +%Y-%m-%d 2>/dev/null)" || {
|
|
341
|
-
|
|
211
|
+
clast_porcelain_warn "failed to compute date — processing all sessions"
|
|
342
212
|
printf '%s' "$uncurated"
|
|
343
213
|
return
|
|
344
214
|
}
|
|
345
|
-
# Dismiss older sessions
|
|
346
215
|
local old_ids
|
|
347
216
|
old_ids="$(jq -r "[.[] | select(.day_bucket < \"$cutoff_keep\")] | .[].session_id" <<<"$uncurated")"
|
|
348
217
|
local dismiss_count=0
|
|
349
218
|
local old_id
|
|
350
219
|
while IFS= read -r old_id; do
|
|
351
220
|
[[ -z "$old_id" ]] && continue
|
|
352
|
-
clast sessions dismiss "$old_id" --reason "bulk dismiss via clast
|
|
221
|
+
clast-plumbing sessions dismiss "$old_id" --reason "bulk dismiss via clast wake triage" 2>/dev/null
|
|
353
222
|
dismiss_count=$(( dismiss_count + 1 ))
|
|
354
223
|
done <<<"$old_ids"
|
|
355
224
|
if (( dismiss_count > 0 )); then
|
|
356
|
-
|
|
225
|
+
clast_porcelain_info " Dismissed $dismiss_count older session(s)." >/dev/tty
|
|
357
226
|
fi
|
|
358
227
|
jq -c "[.[] | select(.day_bucket >= \"$cutoff_keep\")]" <<<"$uncurated"
|
|
359
228
|
;;
|
|
@@ -361,26 +230,29 @@ _clast_wake_triage() {
|
|
|
361
230
|
printf '[]'
|
|
362
231
|
;;
|
|
363
232
|
*)
|
|
364
|
-
|
|
233
|
+
clast_porcelain_warn "unknown choice — processing all sessions"
|
|
365
234
|
printf '%s' "$uncurated"
|
|
366
235
|
;;
|
|
367
236
|
esac
|
|
368
237
|
}
|
|
369
238
|
|
|
370
|
-
#
|
|
239
|
+
# --- Main --------------------------------------------------------------------
|
|
371
240
|
|
|
372
|
-
|
|
373
|
-
|
|
241
|
+
clast_cmd_wake() {
|
|
242
|
+
_clast_wake_preflight
|
|
374
243
|
|
|
375
|
-
|
|
376
|
-
if ! clast snapshot 2>/dev/null; then
|
|
377
|
-
|
|
244
|
+
clast_porcelain_info "Snapshotting fresh transcripts..."
|
|
245
|
+
if ! clast-plumbing snapshot 2>/dev/null; then
|
|
246
|
+
clast_porcelain_warn "clast-plumbing snapshot failed — proceeding with existing data"
|
|
378
247
|
fi
|
|
379
248
|
|
|
380
|
-
|
|
249
|
+
clast_porcelain_info "Checking for uncurated or stale sessions..."
|
|
250
|
+
# Window is configurable; a shorter default keeps the scan fast on large
|
|
251
|
+
# journals (clast sessions cost scales with sessions in the window).
|
|
252
|
+
local since="${CLAST_WAKE_SINCE:--14d}"
|
|
381
253
|
local sessions_json
|
|
382
|
-
sessions_json="$(clast --json sessions --since
|
|
383
|
-
|
|
254
|
+
sessions_json="$(clast-plumbing --json sessions --since "$since" 2>/dev/null)" || {
|
|
255
|
+
clast_porcelain_die "failed to list sessions"
|
|
384
256
|
}
|
|
385
257
|
|
|
386
258
|
local uncurated
|
|
@@ -389,8 +261,8 @@ main() {
|
|
|
389
261
|
total="$(jq 'length' <<<"$uncurated")"
|
|
390
262
|
|
|
391
263
|
if (( total == 0 )); then
|
|
392
|
-
|
|
393
|
-
|
|
264
|
+
clast_porcelain_info "Nothing to curate — all sessions are curated or dismissed."
|
|
265
|
+
return 0
|
|
394
266
|
fi
|
|
395
267
|
|
|
396
268
|
local day_count first_day last_day project_count
|
|
@@ -403,13 +275,13 @@ main() {
|
|
|
403
275
|
uncurated="$(_clast_wake_triage "$uncurated" "$total" "$day_count" "$first_day" "$last_day" "$project_count")"
|
|
404
276
|
total="$(jq 'length' <<<"$uncurated")"
|
|
405
277
|
if (( total == 0 )); then
|
|
406
|
-
|
|
407
|
-
|
|
278
|
+
clast_porcelain_info "Nothing left to curate."
|
|
279
|
+
return 0
|
|
408
280
|
fi
|
|
409
281
|
project_count="$(jq '[.[].project] | unique | length' <<<"$uncurated")"
|
|
410
282
|
fi
|
|
411
283
|
|
|
412
|
-
|
|
284
|
+
clast_porcelain_info "Processing $total session(s) across $project_count project(s)."
|
|
413
285
|
printf '\n'
|
|
414
286
|
|
|
415
287
|
local curated_count=0 skipped_count=0 dismissed_count=0
|
|
@@ -440,12 +312,12 @@ main() {
|
|
|
440
312
|
[[ -n "$branch" && "$branch" != "null" ]] && label="$label, $branch"
|
|
441
313
|
label="$label)"
|
|
442
314
|
|
|
443
|
-
|
|
444
|
-
|
|
315
|
+
_clast_wake_separator "$label"
|
|
316
|
+
clast_porcelain_info "Gathering context..."
|
|
445
317
|
|
|
446
318
|
local show_json
|
|
447
|
-
show_json="$(clast --json show "$sid" --full --turns 8 2>/dev/null)" || {
|
|
448
|
-
|
|
319
|
+
show_json="$(clast-plumbing --json show "$sid" --full --turns 8 2>/dev/null)" || {
|
|
320
|
+
clast_porcelain_warn "failed to read session $sid — skipping"
|
|
449
321
|
skipped_count=$(( skipped_count + 1 ))
|
|
450
322
|
i=$(( i + 1 ))
|
|
451
323
|
continue
|
|
@@ -463,14 +335,14 @@ main() {
|
|
|
463
335
|
' <<<"$show_json" 2>/dev/null)" || true
|
|
464
336
|
|
|
465
337
|
local breadcrumbs=""
|
|
466
|
-
breadcrumbs="$(clast breadcrumb --read --project "$project" --day yesterday 2>/dev/null)" || true
|
|
338
|
+
breadcrumbs="$(clast-plumbing breadcrumb --read --project "$project" --day yesterday 2>/dev/null)" || true
|
|
467
339
|
|
|
468
340
|
local user_prompt
|
|
469
|
-
user_prompt="$(
|
|
341
|
+
user_prompt="$(_clast_wake_build_user_prompt "$project" "$branch" "$start_ts" "$end_ts" "$msg_count" \
|
|
470
342
|
"$first_turns" "$last_turns" "$breadcrumbs")"
|
|
471
343
|
|
|
472
344
|
local system_prompt
|
|
473
|
-
system_prompt="$(
|
|
345
|
+
system_prompt="$(clast_porcelain_load_system_prompt day-wakeup-draft-system)"
|
|
474
346
|
|
|
475
347
|
local draft="" edit_extra=""
|
|
476
348
|
local drafting=1
|
|
@@ -484,10 +356,10 @@ main() {
|
|
|
484
356
|
Revisions requested by user: ${edit_extra}"
|
|
485
357
|
fi
|
|
486
358
|
|
|
487
|
-
|
|
488
|
-
if ! draft="$(
|
|
359
|
+
clast_porcelain_info "Generating draft..."
|
|
360
|
+
if ! draft="$(clast_porcelain_llm_chat "$full_system" "$full_user")"; then
|
|
489
361
|
printf '\n'
|
|
490
|
-
|
|
362
|
+
clast_porcelain_warn "LLM call failed for session $sid"
|
|
491
363
|
local retry
|
|
492
364
|
printf ' [r] Retry [s] Skip [q] Stop\n Choice: '
|
|
493
365
|
read -r -n1 retry </dev/tty
|
|
@@ -503,39 +375,39 @@ Revisions requested by user: ${edit_extra}"
|
|
|
503
375
|
printf '\n'
|
|
504
376
|
|
|
505
377
|
local choice
|
|
506
|
-
choice="$(
|
|
378
|
+
choice="$(_clast_wake_prompt_choice)"
|
|
507
379
|
|
|
508
380
|
case "$choice" in
|
|
509
381
|
a|A)
|
|
510
382
|
local title tags body slug
|
|
511
|
-
title="$(
|
|
512
|
-
tags="$(
|
|
513
|
-
body="$(
|
|
514
|
-
slug="$(
|
|
383
|
+
title="$(_clast_wake_extract_title "$draft")"
|
|
384
|
+
tags="$(_clast_wake_extract_tags "$draft")"
|
|
385
|
+
body="$(_clast_wake_strip_tags_trailer "$draft")"
|
|
386
|
+
slug="$(_clast_wake_get_session_slug "$snapshot_path" "$title")"
|
|
515
387
|
|
|
516
388
|
local write_args=(entries write --session "$sid" --slug "$slug" --body-stdin)
|
|
517
389
|
[[ -n "$tags" ]] && write_args+=(--tags "$tags")
|
|
518
390
|
[[ -n "$title" ]] && write_args+=(--title "$title")
|
|
519
391
|
|
|
520
392
|
local write_result
|
|
521
|
-
if write_result="$(printf '%s\n' "$body" | clast "${write_args[@]}" 2>&1)"; then
|
|
522
|
-
|
|
393
|
+
if write_result="$(printf '%s\n' "$body" | clast-plumbing "${write_args[@]}" 2>&1)"; then
|
|
394
|
+
clast_porcelain_info " $write_result"
|
|
523
395
|
curated_count=$(( curated_count + 1 ))
|
|
524
396
|
curated_projects+=("$project")
|
|
525
397
|
else
|
|
526
|
-
|
|
398
|
+
clast_porcelain_warn "failed to write entry: $write_result"
|
|
527
399
|
fi
|
|
528
400
|
drafting=0
|
|
529
401
|
;;
|
|
530
402
|
e|E)
|
|
531
|
-
edit_extra="$(
|
|
403
|
+
edit_extra="$(_clast_wake_prompt_edit_feedback)"
|
|
532
404
|
;;
|
|
533
405
|
d|D)
|
|
534
|
-
if clast sessions dismiss "$sid" --reason "dismissed via clast
|
|
535
|
-
|
|
406
|
+
if clast-plumbing sessions dismiss "$sid" --reason "dismissed via clast wake" 2>/dev/null; then
|
|
407
|
+
clast_porcelain_info " Dismissed."
|
|
536
408
|
dismissed_count=$(( dismissed_count + 1 ))
|
|
537
409
|
else
|
|
538
|
-
|
|
410
|
+
clast_porcelain_warn "failed to dismiss session $sid"
|
|
539
411
|
fi
|
|
540
412
|
drafting=0
|
|
541
413
|
;;
|
|
@@ -548,7 +420,7 @@ Revisions requested by user: ${edit_extra}"
|
|
|
548
420
|
drafting=0
|
|
549
421
|
;;
|
|
550
422
|
*)
|
|
551
|
-
|
|
423
|
+
clast_porcelain_info " Unknown choice '$choice' — skipping."
|
|
552
424
|
skipped_count=$(( skipped_count + 1 ))
|
|
553
425
|
drafting=0
|
|
554
426
|
;;
|
|
@@ -565,15 +437,13 @@ Revisions requested by user: ${edit_extra}"
|
|
|
565
437
|
[[ -z "$unique_projects" || "$unique_projects" == "0" ]] && unique_projects=0
|
|
566
438
|
|
|
567
439
|
printf '\n'
|
|
568
|
-
|
|
569
|
-
|
|
440
|
+
_clast_wake_separator "Summary"
|
|
441
|
+
clast_porcelain_info " Curated: $curated_count session(s) across $unique_projects project(s)"
|
|
570
442
|
if (( dismissed_count > 0 )); then
|
|
571
|
-
|
|
443
|
+
clast_porcelain_info " Dismissed: $dismissed_count session(s)"
|
|
572
444
|
fi
|
|
573
|
-
|
|
445
|
+
clast_porcelain_info " Skipped: $skipped_count session(s)"
|
|
574
446
|
if (( remaining > 0 )); then
|
|
575
|
-
|
|
447
|
+
clast_porcelain_info " Remaining: $remaining session(s) (stopped early)"
|
|
576
448
|
fi
|
|
577
449
|
}
|
|
578
|
-
|
|
579
|
-
main "$@"
|
|
@@ -61,25 +61,24 @@ clast_registry_resolve() {
|
|
|
61
61
|
local arr
|
|
62
62
|
arr="$(clast_registry_list_json)"
|
|
63
63
|
|
|
64
|
-
# Segment input: starts with `-`.
|
|
65
|
-
# segments registered as-is)
|
|
64
|
+
# Segment input: starts with `-`. The candidate set is the raw segment
|
|
65
|
+
# (handles segments registered as-is) followed by every dash-decoded
|
|
66
|
+
# filesystem path. Test them all in ONE jq pass — first candidate in
|
|
67
|
+
# order to match a registry .path/.aliases wins — instead of forking one
|
|
68
|
+
# jq per candidate, which for deep paths meant ~hundreds/thousands of
|
|
69
|
+
# forks per resolve (the dominant cost of `clast wake` startup).
|
|
66
70
|
if [[ "$input" == -* ]]; then
|
|
67
|
-
local
|
|
68
|
-
|
|
71
|
+
local -a candidates=("$input")
|
|
72
|
+
local -a decoded=()
|
|
73
|
+
mapfile -t decoded < <(clast_decode_candidates "$input")
|
|
74
|
+
candidates+=("${decoded[@]}")
|
|
75
|
+
local cands_json slug
|
|
76
|
+
cands_json="$(printf '%s\n' "${candidates[@]}" | jq -Rn '[inputs]')"
|
|
77
|
+
slug="$(_clast_registry_lookup_paths "$cands_json" "$arr" 2>/dev/null)" || true
|
|
69
78
|
if [[ -n "$slug" ]]; then
|
|
70
79
|
printf '%s\n' "$slug"
|
|
71
80
|
return 0
|
|
72
81
|
fi
|
|
73
|
-
local -a candidates=()
|
|
74
|
-
mapfile -t candidates < <(clast_decode_candidates "$input")
|
|
75
|
-
local c
|
|
76
|
-
for c in "${candidates[@]}"; do
|
|
77
|
-
slug="$(_clast_registry_lookup_path "$c" "$arr")" || continue
|
|
78
|
-
if [[ -n "$slug" ]]; then
|
|
79
|
-
printf '%s\n' "$slug"
|
|
80
|
-
return 0
|
|
81
|
-
fi
|
|
82
|
-
done
|
|
83
82
|
return 1
|
|
84
83
|
fi
|
|
85
84
|
|
|
@@ -107,6 +106,25 @@ _clast_registry_lookup_path() {
|
|
|
107
106
|
' <<<"$arr"
|
|
108
107
|
}
|
|
109
108
|
|
|
109
|
+
# _clast_registry_lookup_paths <candidates-json-array> <registry-json-array>
|
|
110
|
+
# Batched form of _clast_registry_lookup_path: given an ordered JSON array
|
|
111
|
+
# of candidate paths, return the slug for the FIRST candidate (in order)
|
|
112
|
+
# that matches a registry entry's .path or .aliases — path before alias,
|
|
113
|
+
# matching the single-path helper. Print slug or empty. One jq pass for
|
|
114
|
+
# the whole candidate set.
|
|
115
|
+
_clast_registry_lookup_paths() {
|
|
116
|
+
local cands_json="$1" arr="$2"
|
|
117
|
+
jq -r --argjson cands "$cands_json" '
|
|
118
|
+
. as $arr
|
|
119
|
+
| first(
|
|
120
|
+
$cands[] as $c
|
|
121
|
+
| ( ($arr | map(select(.path == $c)) | .[0].slug)
|
|
122
|
+
// ($arr | map(select((.aliases? // []) | index($c) != null)) | .[0].slug) )
|
|
123
|
+
| select(. != null)
|
|
124
|
+
) // empty
|
|
125
|
+
' <<<"$arr"
|
|
126
|
+
}
|
|
127
|
+
|
|
110
128
|
# clast_registry_add <path> [--slug NAME] [--remote URL]
|
|
111
129
|
# Append a single JSONL line. Default slug = basename(path). Default
|
|
112
130
|
# remote = `git -C <path> remote get-url origin` (or absent). If the
|
|
@@ -262,24 +262,20 @@ _clast_entries_list() {
|
|
|
262
262
|
return 0
|
|
263
263
|
fi
|
|
264
264
|
|
|
265
|
-
printf '%-
|
|
266
|
-
"date" "time" "project" "slug" "tags"
|
|
265
|
+
printf '%-50s %s\n' "entry" "tags"
|
|
267
266
|
|
|
268
|
-
local n i row
|
|
267
|
+
local n i row r_path r_basename r_tags tags_disp
|
|
269
268
|
n="$(jq 'length' <<<"$rows_json")"
|
|
270
269
|
for (( i = 0; i < n; i++ )); do
|
|
271
270
|
row="$(jq -c ".[$i]" <<<"$rows_json")"
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
r_project="$(jq -r '.project // ""' <<<"$row")"
|
|
275
|
-
r_slug="$(jq -r '.session_slug // ""' <<<"$row")"
|
|
271
|
+
r_path="$(jq -r '.path // ""' <<<"$row")"
|
|
272
|
+
r_basename="$(basename "$r_path")"
|
|
276
273
|
r_tags="$(jq -r '.tags // [] | join(",")' <<<"$row")"
|
|
277
274
|
tags_disp="$r_tags"
|
|
278
275
|
if (( ${#tags_disp} > 30 )); then
|
|
279
276
|
tags_disp="${tags_disp:0:29}…"
|
|
280
277
|
fi
|
|
281
|
-
printf '%-
|
|
282
|
-
"$r_date" "$r_time" "$r_project" "$r_slug" "$tags_disp"
|
|
278
|
+
printf '%-50s %s\n' "$r_basename" "$tags_disp"
|
|
283
279
|
done
|
|
284
280
|
}
|
|
285
281
|
|
|
@@ -515,6 +511,7 @@ _clast_entries_write() {
|
|
|
515
511
|
trimmed="${rt#"${rt%%[![:space:]]*}"}"
|
|
516
512
|
trimmed="${trimmed%"${trimmed##*[![:space:]]}"}"
|
|
517
513
|
[[ -z "$trimmed" ]] && continue
|
|
514
|
+
trimmed="${trimmed,,}"
|
|
518
515
|
if ! [[ "$trimmed" =~ ^[a-z0-9][a-z0-9-]{0,31}$ ]]; then
|
|
519
516
|
_clast_entries_err "write: invalid tag '$trimmed'"; return 2
|
|
520
517
|
fi
|
|
@@ -566,16 +563,13 @@ _clast_entries_write() {
|
|
|
566
563
|
fi
|
|
567
564
|
|
|
568
565
|
# Best-effort branch from snapshot.
|
|
566
|
+
# Claude Code stores gitBranch (camelCase) on type:"user" lines, not on line 1.
|
|
569
567
|
local journal_dir snapshot_abs branch=""
|
|
570
568
|
journal_dir="$(clast_journal_dir)"
|
|
571
569
|
snapshot_abs="$journal_dir/$snapshot_rel"
|
|
572
570
|
if [[ -r "$snapshot_abs" ]]; then
|
|
573
|
-
branch="$(
|
|
574
|
-
|
|
575
|
-
# If the value looks like a path, drop it.
|
|
576
|
-
case "$branch" in
|
|
577
|
-
/*|"") branch="" ;;
|
|
578
|
-
esac
|
|
571
|
+
branch="$(grep -m1 '"gitBranch"' "$snapshot_abs" 2>/dev/null \
|
|
572
|
+
| jq -r '.gitBranch // empty' 2>/dev/null || true)"
|
|
579
573
|
fi
|
|
580
574
|
|
|
581
575
|
local author machine
|