@procrastivity/clast 0.0.1 → 0.0.2
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 +67 -58
- package/.claude-plugin/skills/wakeup/SKILL.md +28 -5
- package/README.md +21 -20
- package/bin/clast-brief +305 -0
- package/bin/clast-wake +579 -0
- package/hooks/snapshot.sh +18 -10
- package/lib/clast/clast-decode-lib.bash +2 -2
- package/lib/clast/clast-dismissed-lib.bash +73 -0
- package/lib/clast/clast-lib.bash +1 -1
- package/lib/clast/clast-manifest-lib.bash +2 -2
- package/lib/clast/clast-registry-lib.bash +26 -6
- package/lib/clast/clast-subcommands/doctor.bash +1 -1
- package/lib/clast/clast-subcommands/entries.bash +3 -2
- package/lib/clast/clast-subcommands/projects.bash +3 -3
- package/lib/clast/clast-subcommands/sessions.bash +120 -11
- package/lib/clast/clast-subcommands/show.bash +1 -1
- package/lib/clast/clast-subcommands/snapshot.bash +2 -2
- package/lib/clast/clast-subcommands/stats.bash +2 -2
- package/lib/clast/prompts/brief-system.md +31 -0
- package/lib/clast/prompts/brief-user.md +10 -0
- package/lib/clast/prompts/day-wakeup-draft-system.md +28 -0
- package/lib/clast/prompts/day-wakeup-draft-user.md +15 -0
- package/package.json +1 -1
|
@@ -13,23 +13,76 @@ Curation at end-of-session has high friction (the user wants to stop, not summar
|
|
|
13
13
|
|
|
14
14
|
The transcripts themselves are captured automatically by the SessionStart hook + cron — the user never has to remember to log anything. What `/day-wakeup` does is **curate the captured transcripts into durable entries the user controls**, and prompt for promotion of decisions, common-issues, and workflows along the way.
|
|
15
15
|
|
|
16
|
+
## Step 0: Resolve the clast binary
|
|
17
|
+
|
|
18
|
+
Before running any `clast` command, determine which binary to use. Run this
|
|
19
|
+
once at the start and reuse the result for all commands in this skill:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
if [[ -n "${CLAUDE_PLUGIN_ROOT:-}" && -x "$CLAUDE_PLUGIN_ROOT/bin/clast" ]]; then
|
|
23
|
+
CLAST_BIN="$CLAUDE_PLUGIN_ROOT/bin/clast"
|
|
24
|
+
elif command -v clast >/dev/null 2>&1; then
|
|
25
|
+
CLAST_BIN="clast"
|
|
26
|
+
else
|
|
27
|
+
_pdir="$(find ~/.claude -maxdepth 5 -name plugin.json -path '*/clast/.claude-plugin/*' -print -quit 2>/dev/null)"
|
|
28
|
+
if [[ -n "$_pdir" ]]; then
|
|
29
|
+
CLAST_BIN="$(cd "$(dirname "$_pdir")/../.." && pwd)/bin/clast"
|
|
30
|
+
fi
|
|
31
|
+
fi
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
If `CLAST_BIN` is still empty, tell the user: "clast CLI not found. Install it
|
|
35
|
+
with `npm i -g @procrastivity/clast` or see the README for other options."
|
|
36
|
+
|
|
37
|
+
Use `$CLAST_BIN` in place of bare `clast` for all commands in this skill.
|
|
38
|
+
|
|
16
39
|
## Step 1: Ensure fresh data
|
|
17
40
|
|
|
18
|
-
Run
|
|
41
|
+
Run `$CLAST_BIN snapshot` (idempotent; silent on no-op). This guarantees we're not missing anything that ran since the last hook fire.
|
|
19
42
|
|
|
20
43
|
```bash
|
|
21
|
-
|
|
44
|
+
$CLAST_BIN snapshot
|
|
22
45
|
```
|
|
23
46
|
|
|
24
47
|
Errors here are non-fatal — proceed even if it fails, just warn the user.
|
|
25
48
|
|
|
26
|
-
## Step 2: Enumerate uncurated sessions
|
|
49
|
+
## Step 2: Enumerate uncurated sessions
|
|
50
|
+
|
|
51
|
+
Query all recent sessions (last 30 days) and filter to uncurated:
|
|
27
52
|
|
|
28
53
|
```bash
|
|
29
|
-
|
|
54
|
+
$CLAST_BIN --json sessions --since -30d
|
|
30
55
|
```
|
|
31
56
|
|
|
32
|
-
Filter to sessions with `curated: false
|
|
57
|
+
Filter to sessions with `curated: false` or `stale: true` (stale sessions were curated but their transcript was updated since). If none remain, print "Nothing to curate — all sessions are curated or dismissed." and stop.
|
|
58
|
+
|
|
59
|
+
### Triage when multiple days have uncurated sessions
|
|
60
|
+
|
|
61
|
+
If uncurated sessions span more than one day (e.g., after a weekend or break), present a triage step before processing. Show a per-day breakdown:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
Found 45 uncurated sessions across 5 days (2026-05-26 to 2026-05-30).
|
|
65
|
+
|
|
66
|
+
2026-05-26 8 session(s)
|
|
67
|
+
2026-05-27 12 session(s)
|
|
68
|
+
2026-05-28 15 session(s)
|
|
69
|
+
2026-05-29 7 session(s)
|
|
70
|
+
2026-05-30 3 session(s)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Then present via AskUserQuestion:
|
|
74
|
+
|
|
75
|
+
- **question**: "How would you like to handle these?"
|
|
76
|
+
- **header**: "Backlog"
|
|
77
|
+
- **options**:
|
|
78
|
+
- `Process all` — curate everything
|
|
79
|
+
- `Yesterday only` — only process yesterday's sessions
|
|
80
|
+
- `Choose days back` — prompt for a number, process only that many days back
|
|
81
|
+
- `Dismiss older, process recent` — prompt for how many days to keep, dismiss the rest via `$CLAST_BIN sessions dismiss`, then process what remains
|
|
82
|
+
|
|
83
|
+
If only one day has uncurated sessions, skip triage and process directly.
|
|
84
|
+
|
|
85
|
+
### Ordering
|
|
33
86
|
|
|
34
87
|
Group sessions by project for presentation. Order: most recent project first, sessions chronological within each project.
|
|
35
88
|
|
|
@@ -39,13 +92,13 @@ For each session in the list:
|
|
|
39
92
|
|
|
40
93
|
1. Read session details:
|
|
41
94
|
```bash
|
|
42
|
-
|
|
95
|
+
$CLAST_BIN --json show <session-id> --full --turns 8
|
|
43
96
|
```
|
|
44
97
|
This returns metadata + first 8 and last 8 turns of the transcript (text only, no tool calls — kept compact).
|
|
45
98
|
|
|
46
99
|
2. Read breadcrumbs for this project from yesterday:
|
|
47
100
|
```bash
|
|
48
|
-
|
|
101
|
+
$CLAST_BIN breadcrumb --read --project <slug> --day yesterday
|
|
49
102
|
```
|
|
50
103
|
|
|
51
104
|
3. Generate a draft entry using the **draft generation prompt** (see below).
|
|
@@ -80,56 +133,12 @@ Run `/wakeup <project>` to start working on a specific project today.
|
|
|
80
133
|
|
|
81
134
|
## Draft generation prompt
|
|
82
135
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
````markdown
|
|
86
|
-
You are drafting a journal entry for a Claude Code session that the user just reviewed. The entry will be written to the user's journal and may be read days or weeks later to refresh context on what was happening.
|
|
87
|
-
|
|
88
|
-
Session metadata:
|
|
89
|
-
- Project: {project}
|
|
90
|
-
- Branch: {branch}
|
|
91
|
-
- Start: {start}
|
|
92
|
-
- End: {end}
|
|
93
|
-
- Approximate messages: {msg_count}
|
|
94
|
-
|
|
95
|
-
First 8 turns of the session:
|
|
96
|
-
{first_turns}
|
|
97
|
-
|
|
98
|
-
Last 8 turns of the session:
|
|
99
|
-
{last_turns}
|
|
100
|
-
|
|
101
|
-
Breadcrumbs the user left during this session's day:
|
|
102
|
-
{breadcrumbs}
|
|
103
|
-
|
|
104
|
-
Draft a journal entry in this exact markdown structure. Omit any section that has no content (don't write "N/A"):
|
|
105
|
-
|
|
106
|
-
```
|
|
107
|
-
# Session: <short human-readable title>
|
|
108
|
-
|
|
109
|
-
## Goal
|
|
110
|
-
One sentence describing what this session was trying to accomplish.
|
|
111
|
-
|
|
112
|
-
## What shipped
|
|
113
|
-
- Bullet list of what actually got done (files written, features built, fixes landed). Extract from the transcript.
|
|
114
|
-
|
|
115
|
-
## Issues + fixes
|
|
116
|
-
- **Issue:** what broke. **Fix:** what resolved it.
|
|
117
|
-
|
|
118
|
-
## Dead ends touched
|
|
119
|
-
- **Tried:** approach. (You can usually see this in the transcript as "tried X then switched to Y".)
|
|
120
|
-
- Note: if you can't tell *why* an approach was abandoned from the transcript, leave that for the user to fill in. Don't speculate.
|
|
121
|
-
|
|
122
|
-
## Open threads
|
|
123
|
-
- Anything still unfinished or deferred. Use the breadcrumbs and the last turns of the session as signal.
|
|
124
|
-
|
|
125
|
-
## Notes
|
|
126
|
-
- Anything else useful for the next session in this project.
|
|
127
|
-
```
|
|
136
|
+
The prompt templates live in `lib/clast/prompts/` so they are shared with the standalone `clast-wake` script:
|
|
128
137
|
|
|
129
|
-
|
|
138
|
+
- **System prompt:** `lib/clast/prompts/day-wakeup-draft-system.md`
|
|
139
|
+
- **User prompt template:** `lib/clast/prompts/day-wakeup-draft-user.md` (uses `{{placeholder}}` syntax)
|
|
130
140
|
|
|
131
|
-
|
|
132
|
-
````
|
|
141
|
+
When generating each draft, read those files and substitute the placeholders with session data. The user prompt template uses these placeholders: `{{project}}`, `{{branch}}`, `{{start}}`, `{{end}}`, `{{msg_count}}`, `{{first_turns}}`, `{{last_turns}}`, `{{breadcrumbs}}`.
|
|
133
142
|
|
|
134
143
|
## AskUserQuestion: promotion options per session
|
|
135
144
|
|
|
@@ -169,7 +178,7 @@ When the user accepts:
|
|
|
169
178
|
2. Pipe the entry body (without the suggested-tags trailer) to `clast entries write`:
|
|
170
179
|
|
|
171
180
|
```bash
|
|
172
|
-
|
|
181
|
+
$CLAST_BIN entries write \
|
|
173
182
|
--session <session-id> \
|
|
174
183
|
--slug <session-slug> \
|
|
175
184
|
--tags <tag1>,<tag2>,<tag3> \
|
|
@@ -187,8 +196,8 @@ For promoted items (decisions, common-issues, workflows): currently these are tr
|
|
|
187
196
|
|
|
188
197
|
## Edge cases
|
|
189
198
|
|
|
190
|
-
- **No sessions
|
|
191
|
-
- **
|
|
199
|
+
- **No uncurated sessions**: print "Nothing to curate — all sessions are curated or dismissed." and stop.
|
|
200
|
+
- **Multi-day backlog**: present the triage step (Step 2) so the user can choose scope before processing.
|
|
192
201
|
- **`clast snapshot` fails**: warn the user, then attempt to proceed with whatever's already in the manifest.
|
|
193
202
|
- **`clast show` fails for a specific session**: skip that session, note it in the final summary, continue with the rest.
|
|
194
203
|
- **User says "do them all without prompting"**: not a v1 feature. Each session gets its own AskUserQuestion. The friction is intentional — it's where curation happens.
|
|
@@ -12,12 +12,35 @@ Synthesize a briefing for the current (or named) project so the user can resume
|
|
|
12
12
|
|
|
13
13
|
`/day-wakeup` curates yesterday's work into entries. `/wakeup` reads those entries back when starting work in a specific repo. The two are complementary: one writes, one reads.
|
|
14
14
|
|
|
15
|
+
## Step 0: Resolve the clast binary
|
|
16
|
+
|
|
17
|
+
Before running any `clast` command, determine which binary to use. Run this
|
|
18
|
+
once at the start and reuse the result for all commands in this skill:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
if [[ -n "${CLAUDE_PLUGIN_ROOT:-}" && -x "$CLAUDE_PLUGIN_ROOT/bin/clast" ]]; then
|
|
22
|
+
CLAST_BIN="$CLAUDE_PLUGIN_ROOT/bin/clast"
|
|
23
|
+
elif command -v clast >/dev/null 2>&1; then
|
|
24
|
+
CLAST_BIN="clast"
|
|
25
|
+
else
|
|
26
|
+
_pdir="$(find ~/.claude -maxdepth 5 -name plugin.json -path '*/clast/.claude-plugin/*' -print -quit 2>/dev/null)"
|
|
27
|
+
if [[ -n "$_pdir" ]]; then
|
|
28
|
+
CLAST_BIN="$(cd "$(dirname "$_pdir")/../.." && pwd)/bin/clast"
|
|
29
|
+
fi
|
|
30
|
+
fi
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
If `CLAST_BIN` is still empty, tell the user: "clast CLI not found. Install it
|
|
34
|
+
with `npm i -g @procrastivity/clast` or see the README for other options."
|
|
35
|
+
|
|
36
|
+
Use `$CLAST_BIN` in place of bare `clast` for all commands in this skill.
|
|
37
|
+
|
|
15
38
|
## Step 1: Resolve the project
|
|
16
39
|
|
|
17
40
|
If the user passed a slug as an argument (`/wakeup xesapps`), use it directly. Otherwise resolve from current working directory:
|
|
18
41
|
|
|
19
42
|
```bash
|
|
20
|
-
|
|
43
|
+
$CLAST_BIN registry resolve "$(pwd)"
|
|
21
44
|
```
|
|
22
45
|
|
|
23
46
|
If `pwd` doesn't resolve and no slug was given: print "Not in a registered project. Run `clast registry add .` first, or invoke as `/wakeup <slug>`." and stop.
|
|
@@ -28,19 +51,19 @@ In parallel:
|
|
|
28
51
|
|
|
29
52
|
```bash
|
|
30
53
|
# Recent curated entries for this project (newest first)
|
|
31
|
-
|
|
54
|
+
$CLAST_BIN --json entries --project <slug> --limit 5
|
|
32
55
|
|
|
33
56
|
# Today's breadcrumbs for this project
|
|
34
|
-
|
|
57
|
+
$CLAST_BIN breadcrumb --read --project <slug> --day today
|
|
35
58
|
|
|
36
59
|
# Today's session activity (if any — user might have started already)
|
|
37
|
-
|
|
60
|
+
$CLAST_BIN --json sessions --day today --project <slug>
|
|
38
61
|
```
|
|
39
62
|
|
|
40
63
|
For each entry returned, also read the body if it'll fit (file sizes are typically 1–5KB each):
|
|
41
64
|
|
|
42
65
|
```bash
|
|
43
|
-
|
|
66
|
+
$CLAST_BIN entries read <entry-path>
|
|
44
67
|
```
|
|
45
68
|
|
|
46
69
|
## Step 3: Synthesize the briefing
|
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ clast snapshot --dry-run --json | jq # preview what would be captured
|
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
`clast snapshot` is idempotent and silent on no-op, safe to run from cron or
|
|
21
|
-
a SessionStart hook. See [`docs/cli
|
|
21
|
+
a SessionStart hook. See [`docs/reference/cli.md#clast-snapshot`](./docs/reference/cli.md#clast-snapshot)
|
|
22
22
|
for the full flag reference.
|
|
23
23
|
|
|
24
24
|
## Read your sessions
|
|
@@ -31,9 +31,9 @@ clast show <session-uuid> --full # metadata + first/last turns
|
|
|
31
31
|
|
|
32
32
|
Window flags (`--day`, `--since`, `--until`) accept ISO dates and
|
|
33
33
|
relative keywords. See
|
|
34
|
-
[`docs/cli
|
|
35
|
-
[`docs/cli
|
|
36
|
-
and [`docs/cli
|
|
34
|
+
[`docs/reference/cli.md#clast-projects`](./docs/reference/cli.md#clast-projects),
|
|
35
|
+
[`docs/reference/cli.md#clast-sessions`](./docs/reference/cli.md#clast-sessions),
|
|
36
|
+
and [`docs/reference/cli.md#clast-show`](./docs/reference/cli.md#clast-show)
|
|
37
37
|
for the full flag and output schemas.
|
|
38
38
|
|
|
39
39
|
## Curate an entry
|
|
@@ -48,9 +48,9 @@ printf 'Notes...\n' | clast entries write \
|
|
|
48
48
|
`clast entries write` looks up the session in the manifest, composes the
|
|
49
49
|
documented frontmatter from the captured snapshot + registry, and writes
|
|
50
50
|
`entries/YYYY-MM-DD-HHMM-<project-slug>-<session-slug>.md` atomically. See
|
|
51
|
-
[`docs/cli
|
|
51
|
+
[`docs/reference/cli.md#entry-frontmatter`](./docs/reference/cli.md#entry-frontmatter)
|
|
52
52
|
for the full frontmatter schema and
|
|
53
|
-
[`docs/cli
|
|
53
|
+
[`docs/reference/cli.md#clast-entries`](./docs/reference/cli.md#clast-entries)
|
|
54
54
|
for the flag reference.
|
|
55
55
|
|
|
56
56
|
## Leave a breadcrumb
|
|
@@ -64,7 +64,7 @@ clast breadcrumb --read --global
|
|
|
64
64
|
```
|
|
65
65
|
|
|
66
66
|
Breadcrumbs are append-only one-line notes for `/wakeup` and `/day-wakeup`.
|
|
67
|
-
See [`docs/cli
|
|
67
|
+
See [`docs/reference/cli.md#clast-breadcrumb`](./docs/reference/cli.md#clast-breadcrumb)
|
|
68
68
|
for the full command contract.
|
|
69
69
|
|
|
70
70
|
## Inspect and audit the journal
|
|
@@ -77,8 +77,8 @@ clast doctor # check manifest, registry, snapshots
|
|
|
77
77
|
clast doctor --fix # rebuild a broken manifest, prune orphans
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
-
See [`docs/cli
|
|
81
|
-
and [`docs/cli
|
|
80
|
+
See [`docs/reference/cli.md#clast-stats`](./docs/reference/cli.md#clast-stats)
|
|
81
|
+
and [`docs/reference/cli.md#clast-doctor`](./docs/reference/cli.md#clast-doctor)
|
|
82
82
|
for the contract reference, and `clast stats --help` / `clast doctor --help`
|
|
83
83
|
for the current set of flags.
|
|
84
84
|
|
|
@@ -92,7 +92,7 @@ for the current set of flags.
|
|
|
92
92
|
|
|
93
93
|
`make install` wraps the same script. Use `./uninstall.sh ~/.local` (or
|
|
94
94
|
`make uninstall` for the default prefix) to remove the installed files. See
|
|
95
|
-
[`docs/repo-bootstrap.md#installsh--uninstallsh`](./docs/repo-bootstrap.md#installsh--uninstallsh)
|
|
95
|
+
[`docs/reference/repo-bootstrap.md#installsh--uninstallsh`](./docs/reference/repo-bootstrap.md#installsh--uninstallsh)
|
|
96
96
|
for the rationale.
|
|
97
97
|
|
|
98
98
|
## Install with Nix
|
|
@@ -106,7 +106,7 @@ nix build .#default && ./result/bin/clast --version
|
|
|
106
106
|
```
|
|
107
107
|
|
|
108
108
|
For Home Manager or nix-darwin users, `overlays.default` exposes `pkgs.clast`.
|
|
109
|
-
See [`docs/repo-bootstrap.md#nix-flake`](./docs/repo-bootstrap.md#nix-flake)
|
|
109
|
+
See [`docs/reference/repo-bootstrap.md#nix-flake`](./docs/reference/repo-bootstrap.md#nix-flake)
|
|
110
110
|
for the full overlay wiring.
|
|
111
111
|
|
|
112
112
|
## Install via npm
|
|
@@ -136,7 +136,7 @@ Today the plugin ships a single `SessionStart` hook: every time a Claude Code
|
|
|
136
136
|
session starts it backgrounds `clast snapshot`, so your journal stays current
|
|
137
137
|
with zero manual effort. The hook is best-effort and silent: if the `clast` CLI
|
|
138
138
|
isn't on your `PATH` yet, sessions still start cleanly. See
|
|
139
|
-
[`docs/
|
|
139
|
+
[`docs/reference/plugin.md#hook-sessionstart`](./docs/reference/plugin.md#hook-sessionstart)
|
|
140
140
|
for the hook's design rationale.
|
|
141
141
|
|
|
142
142
|
### `/day-wakeup`
|
|
@@ -145,7 +145,7 @@ At the start of each day, run `/day-wakeup` inside any Claude Code session after
|
|
|
145
145
|
the plugin is installed. It performs once-per-day cross-project curation of
|
|
146
146
|
yesterday's sessions into durable journal entries, walking each uncurated session
|
|
147
147
|
through a draft you can accept, edit, skip, or mark for in-entry promotion. See
|
|
148
|
-
[`docs/
|
|
148
|
+
[`docs/reference/plugin.md#skill-1-day-wakeup`](./docs/reference/plugin.md#skill-1-day-wakeup).
|
|
149
149
|
|
|
150
150
|
### `/wakeup`
|
|
151
151
|
|
|
@@ -153,7 +153,7 @@ When starting work on a specific project, run `/wakeup` (or `/wakeup <slug>` fro
|
|
|
153
153
|
anywhere) to get a per-project read-only briefing synthesized from recent curated entries,
|
|
154
154
|
today's breadcrumbs, and any sessions already started today. `/wakeup` never writes — it
|
|
155
155
|
only reads. See
|
|
156
|
-
[`docs/
|
|
156
|
+
[`docs/reference/plugin.md#skill-2-wakeup`](./docs/reference/plugin.md#skill-2-wakeup).
|
|
157
157
|
|
|
158
158
|
## Development
|
|
159
159
|
|
|
@@ -166,15 +166,16 @@ only reads. See
|
|
|
166
166
|
Pull requests run lint, tests, version sync, npm pack shape, Nix smoke, flake check, and Nix build automatically.
|
|
167
167
|
Releases trigger on `v*` tags, and the tag version must match `package.json` exactly.
|
|
168
168
|
The release workflow publishes to npm with provenance and creates a GitHub Release with the npm tarball attached.
|
|
169
|
-
|
|
169
|
+
Publishing to npm uses Trusted Publishing (OIDC) — no `NPM_TOKEN` secret. Configure the trusted publisher for `@procrastivity/clast` on npmjs.com before the first release tag; see [`docs/reference/releasing.md`](./docs/reference/releasing.md#trusted-publishing-setup).
|
|
170
170
|
|
|
171
171
|
## Documentation
|
|
172
172
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
- [
|
|
176
|
-
- [
|
|
177
|
-
- [
|
|
173
|
+
Start at [`docs/README.md`](./docs/README.md) for the full index. Highlights:
|
|
174
|
+
|
|
175
|
+
- **Explanation** — [What is clast?](./docs/explanation/what-is-clast.md) · [Architecture](./docs/explanation/architecture.md) · [Data model](./docs/explanation/data-model.md) · [Conventions](./docs/explanation/conventions.md)
|
|
176
|
+
- **Getting started** — [Install](./docs/getting-started/install.md) · [First snapshot](./docs/getting-started/first-snapshot.md) · [Install the plugin](./docs/getting-started/install-the-plugin.md)
|
|
177
|
+
- **Guides** — [curate an entry](./docs/guides/curate-an-entry.md), [use breadcrumbs](./docs/guides/use-breadcrumbs.md), [automate with cron](./docs/guides/automate-with-cron.md) or [systemd](./docs/guides/automate-with-systemd.md), [repair the journal](./docs/guides/repair-the-journal.md), [query recipes](./docs/guides/query-recipes.md), [run without Claude Code](./docs/guides/run-without-claude-code.md), [morning briefing](./examples/workflows/morning-briefing.md)
|
|
178
|
+
- **Reference** — [CLI](./docs/reference/cli.md) · [Plugin](./docs/reference/plugin.md) · [Entry frontmatter](./docs/reference/entry-frontmatter.md) · [Config](./docs/reference/config.md) · [Repo bootstrap](./docs/reference/repo-bootstrap.md) · [Releasing](./docs/reference/releasing.md)
|
|
178
179
|
- [`examples/`](./examples/) — cron, systemd-timer, and workflow samples.
|
|
179
180
|
|
|
180
181
|
## License
|
package/bin/clast-brief
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# clast-brief — LLM-powered project briefing without Claude Code.
|
|
3
|
+
#
|
|
4
|
+
# Replicates the /wakeup plugin skill using an OpenAI-compatible
|
|
5
|
+
# chat completions endpoint. Reads curated entries, breadcrumbs,
|
|
6
|
+
# and today's sessions for a project, then synthesizes a briefing.
|
|
7
|
+
#
|
|
8
|
+
# Usage: clast-brief [<project-slug>]
|
|
9
|
+
#
|
|
10
|
+
# If no slug is given, resolves from the current working directory.
|
|
11
|
+
#
|
|
12
|
+
# Required env vars:
|
|
13
|
+
# CLAST_LLM_BASE_URL — e.g. https://api.openai.com/v1
|
|
14
|
+
# CLAST_LLM_API_KEY — bearer token
|
|
15
|
+
# CLAST_LLM_MODEL — e.g. gpt-4o, llama3
|
|
16
|
+
set -euo pipefail
|
|
17
|
+
|
|
18
|
+
CLAST_BRIEF_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
19
|
+
|
|
20
|
+
# ── Helpers ──────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
die() { printf 'clast-brief: %s\n' "$1" >&2; exit "${2:-1}"; }
|
|
23
|
+
warn() { printf 'clast-brief: warning: %s\n' "$1" >&2; }
|
|
24
|
+
info() { printf '%s\n' "$1"; }
|
|
25
|
+
|
|
26
|
+
# ── Preflight ────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
preflight() {
|
|
29
|
+
for tool in clast curl jq; do
|
|
30
|
+
if ! command -v "$tool" >/dev/null 2>&1; then
|
|
31
|
+
die "required tool not found: $tool"
|
|
32
|
+
fi
|
|
33
|
+
done
|
|
34
|
+
|
|
35
|
+
local missing=0
|
|
36
|
+
if [[ -z "${CLAST_LLM_BASE_URL:-}" ]]; then
|
|
37
|
+
warn "CLAST_LLM_BASE_URL not set"; missing=1
|
|
38
|
+
fi
|
|
39
|
+
if [[ -z "${CLAST_LLM_API_KEY:-}" ]]; then
|
|
40
|
+
warn "CLAST_LLM_API_KEY not set"; missing=1
|
|
41
|
+
fi
|
|
42
|
+
if [[ -z "${CLAST_LLM_MODEL:-}" ]]; then
|
|
43
|
+
warn "CLAST_LLM_MODEL not set"; missing=1
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
if (( missing )); then
|
|
47
|
+
cat >&2 <<'EOF'
|
|
48
|
+
|
|
49
|
+
Set these env vars before running clast-brief:
|
|
50
|
+
|
|
51
|
+
export CLAST_LLM_BASE_URL="https://api.openai.com/v1"
|
|
52
|
+
export CLAST_LLM_API_KEY="sk-..."
|
|
53
|
+
export CLAST_LLM_MODEL="gpt-4o"
|
|
54
|
+
|
|
55
|
+
Or for a local model (ollama, vllm, etc.):
|
|
56
|
+
|
|
57
|
+
export CLAST_LLM_BASE_URL="http://localhost:11434/v1"
|
|
58
|
+
export CLAST_LLM_API_KEY="unused"
|
|
59
|
+
export CLAST_LLM_MODEL="llama3"
|
|
60
|
+
EOF
|
|
61
|
+
exit 1
|
|
62
|
+
fi
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
# ── LLM call ─────────────────────────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
llm_chat() {
|
|
68
|
+
local system_msg="$1"
|
|
69
|
+
local user_msg="$2"
|
|
70
|
+
|
|
71
|
+
local payload
|
|
72
|
+
payload="$(jq -cn \
|
|
73
|
+
--arg model "$CLAST_LLM_MODEL" \
|
|
74
|
+
--arg system "$system_msg" \
|
|
75
|
+
--arg user "$user_msg" \
|
|
76
|
+
'{
|
|
77
|
+
model: $model,
|
|
78
|
+
messages: [
|
|
79
|
+
{role: "system", content: $system},
|
|
80
|
+
{role: "user", content: $user}
|
|
81
|
+
],
|
|
82
|
+
temperature: 0.3
|
|
83
|
+
}')"
|
|
84
|
+
|
|
85
|
+
local response http_code body
|
|
86
|
+
response="$(curl -s -w '\n%{http_code}' \
|
|
87
|
+
"${CLAST_LLM_BASE_URL}/chat/completions" \
|
|
88
|
+
-H "Authorization: Bearer $CLAST_LLM_API_KEY" \
|
|
89
|
+
-H "Content-Type: application/json" \
|
|
90
|
+
-d "$payload" 2>&1)" || true
|
|
91
|
+
|
|
92
|
+
http_code="$(tail -n1 <<<"$response")"
|
|
93
|
+
body="$(sed '$d' <<<"$response")"
|
|
94
|
+
|
|
95
|
+
if [[ "$http_code" != "200" ]]; then
|
|
96
|
+
warn "LLM API returned HTTP $http_code"
|
|
97
|
+
if [[ -n "$body" ]]; then
|
|
98
|
+
local err_msg
|
|
99
|
+
err_msg="$(jq -r '.error.message // .error // .' <<<"$body" 2>/dev/null || echo "$body")"
|
|
100
|
+
warn "$err_msg"
|
|
101
|
+
fi
|
|
102
|
+
return 1
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
local content
|
|
106
|
+
content="$(jq -r '.choices[0].message.content // empty' <<<"$body" 2>/dev/null)" || {
|
|
107
|
+
warn "failed to parse LLM response"
|
|
108
|
+
return 1
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if [[ -z "$content" ]]; then
|
|
112
|
+
warn "LLM returned empty content"
|
|
113
|
+
return 1
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
printf '%s' "$content"
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
# ── Prompt template ──────────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
resolve_prompt_dir() {
|
|
122
|
+
local dir="$CLAST_BRIEF_DIR/lib/clast/prompts"
|
|
123
|
+
if [[ -d "$dir" ]]; then
|
|
124
|
+
printf '%s' "$dir"
|
|
125
|
+
return
|
|
126
|
+
fi
|
|
127
|
+
local installed
|
|
128
|
+
for installed in /usr/local/lib/clast/prompts "$HOME/.local/lib/clast/prompts"; do
|
|
129
|
+
if [[ -d "$installed" ]]; then
|
|
130
|
+
printf '%s' "$installed"
|
|
131
|
+
return
|
|
132
|
+
fi
|
|
133
|
+
done
|
|
134
|
+
die "cannot find prompts directory (checked $dir and install paths)"
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
load_system_prompt() {
|
|
138
|
+
local prompt_dir
|
|
139
|
+
prompt_dir="$(resolve_prompt_dir)"
|
|
140
|
+
local file="$prompt_dir/brief-system.md"
|
|
141
|
+
if [[ ! -r "$file" ]]; then
|
|
142
|
+
die "system prompt not found: $file"
|
|
143
|
+
fi
|
|
144
|
+
cat "$file"
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
build_user_prompt() {
|
|
148
|
+
local project="$1" entries="$2" breadcrumbs="$3" sessions="$4"
|
|
149
|
+
|
|
150
|
+
local prompt_dir
|
|
151
|
+
prompt_dir="$(resolve_prompt_dir)"
|
|
152
|
+
local template_file="$prompt_dir/brief-user.md"
|
|
153
|
+
|
|
154
|
+
if [[ -r "$template_file" ]]; then
|
|
155
|
+
local template
|
|
156
|
+
template="$(cat "$template_file")"
|
|
157
|
+
template="${template//\{\{project\}\}/${project}}"
|
|
158
|
+
template="${template//\{\{entries\}\}/${entries:-None.}}"
|
|
159
|
+
template="${template//\{\{breadcrumbs\}\}/${breadcrumbs:-None.}}"
|
|
160
|
+
template="${template//\{\{sessions\}\}/${sessions:-None.}}"
|
|
161
|
+
printf '%s' "$template"
|
|
162
|
+
else
|
|
163
|
+
warn "user prompt template not found: $template_file — using inline fallback"
|
|
164
|
+
cat <<EOF
|
|
165
|
+
Project: ${project}
|
|
166
|
+
|
|
167
|
+
Recent curated entries (newest first):
|
|
168
|
+
${entries:-None.}
|
|
169
|
+
|
|
170
|
+
Today's breadcrumbs for this project:
|
|
171
|
+
${breadcrumbs:-None.}
|
|
172
|
+
|
|
173
|
+
Today's session activity for this project:
|
|
174
|
+
${sessions:-None.}
|
|
175
|
+
EOF
|
|
176
|
+
fi
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
# ── Resolve project ──────────────────────────────────────────────────
|
|
180
|
+
|
|
181
|
+
resolve_project() {
|
|
182
|
+
local slug="${1:-}"
|
|
183
|
+
|
|
184
|
+
if [[ -n "$slug" ]]; then
|
|
185
|
+
printf '%s' "$slug"
|
|
186
|
+
return
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
local resolved
|
|
190
|
+
resolved="$(clast registry resolve "$(pwd)" 2>/dev/null)" || true
|
|
191
|
+
if [[ -n "$resolved" ]]; then
|
|
192
|
+
printf '%s' "$resolved"
|
|
193
|
+
return
|
|
194
|
+
fi
|
|
195
|
+
|
|
196
|
+
die "Not in a registered project. Run \`clast registry add .\` first, or invoke as \`clast-brief <slug>\`."
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
# ── Gather data ──────────────────────────────────────────────────────
|
|
200
|
+
|
|
201
|
+
gather_entries() {
|
|
202
|
+
local project="$1"
|
|
203
|
+
local entries_json
|
|
204
|
+
entries_json="$(clast --json entries --project "$project" --limit 5 2>/dev/null)" || true
|
|
205
|
+
|
|
206
|
+
if [[ -z "$entries_json" ]] || [[ "$(jq 'length' <<<"$entries_json" 2>/dev/null)" == "0" ]]; then
|
|
207
|
+
return
|
|
208
|
+
fi
|
|
209
|
+
|
|
210
|
+
local n i entry_meta entry_path entry_body result=""
|
|
211
|
+
n="$(jq 'length' <<<"$entries_json")"
|
|
212
|
+
|
|
213
|
+
for (( i = 0; i < n; i++ )); do
|
|
214
|
+
entry_meta="$(jq -c ".[$i]" <<<"$entries_json")"
|
|
215
|
+
entry_path="$(jq -r '.path' <<<"$entry_meta")"
|
|
216
|
+
|
|
217
|
+
entry_body="$(clast entries read "$entry_path" 2>/dev/null)" || true
|
|
218
|
+
if [[ -z "$entry_body" ]]; then
|
|
219
|
+
local title date
|
|
220
|
+
title="$(jq -r '.title // "untitled"' <<<"$entry_meta")"
|
|
221
|
+
date="$(jq -r '.date' <<<"$entry_meta")"
|
|
222
|
+
entry_body="# $date — $title (body not available)"
|
|
223
|
+
fi
|
|
224
|
+
|
|
225
|
+
if [[ -n "$result" ]]; then
|
|
226
|
+
result="${result}
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
"
|
|
231
|
+
fi
|
|
232
|
+
result="${result}${entry_body}"
|
|
233
|
+
done
|
|
234
|
+
|
|
235
|
+
printf '%s' "$result"
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
gather_breadcrumbs() {
|
|
239
|
+
local project="$1"
|
|
240
|
+
clast breadcrumb --read --project "$project" --day today 2>/dev/null || true
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
gather_sessions() {
|
|
244
|
+
local project="$1"
|
|
245
|
+
local sessions_json
|
|
246
|
+
sessions_json="$(clast --json sessions --day today --project "$project" 2>/dev/null)" || true
|
|
247
|
+
|
|
248
|
+
if [[ -z "$sessions_json" ]] || [[ "$(jq 'length' <<<"$sessions_json" 2>/dev/null)" == "0" ]]; then
|
|
249
|
+
return
|
|
250
|
+
fi
|
|
251
|
+
|
|
252
|
+
local n
|
|
253
|
+
n="$(jq 'length' <<<"$sessions_json")"
|
|
254
|
+
|
|
255
|
+
if (( n > 5 )); then
|
|
256
|
+
local latest_start
|
|
257
|
+
latest_start="$(jq -r 'sort_by(.start) | last | .start // ""' <<<"$sessions_json")"
|
|
258
|
+
printf 'Worked %d sessions today, most recent at %s.' "$n" "${latest_start:11:5}"
|
|
259
|
+
return
|
|
260
|
+
fi
|
|
261
|
+
|
|
262
|
+
jq -r '
|
|
263
|
+
sort_by(.start) | .[] |
|
|
264
|
+
"\(.start[11:16]) start: \(if .branch and .branch != "null" then .branch else "no branch" end), \(.msg_count_approx) messages"
|
|
265
|
+
' <<<"$sessions_json" 2>/dev/null || true
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
# ── Main ─────────────────────────────────────────────────────────────
|
|
269
|
+
|
|
270
|
+
main() {
|
|
271
|
+
preflight
|
|
272
|
+
|
|
273
|
+
local project
|
|
274
|
+
project="$(resolve_project "${1:-}")"
|
|
275
|
+
info "Briefing for project: $project"
|
|
276
|
+
|
|
277
|
+
info "Gathering context..."
|
|
278
|
+
|
|
279
|
+
local entries breadcrumbs sessions
|
|
280
|
+
entries="$(gather_entries "$project")"
|
|
281
|
+
breadcrumbs="$(gather_breadcrumbs "$project")"
|
|
282
|
+
sessions="$(gather_sessions "$project")"
|
|
283
|
+
|
|
284
|
+
if [[ -z "$entries" && -z "$breadcrumbs" && -z "$sessions" ]]; then
|
|
285
|
+
info "No curated entries, breadcrumbs, or sessions for \`$project\`."
|
|
286
|
+
info "Run \`clast-wake\` to curate recent sessions first, or run \`clast sessions --project $project\` to see what's available."
|
|
287
|
+
exit 0
|
|
288
|
+
fi
|
|
289
|
+
|
|
290
|
+
local system_prompt user_prompt
|
|
291
|
+
system_prompt="$(load_system_prompt)"
|
|
292
|
+
user_prompt="$(build_user_prompt "$project" "$entries" "$breadcrumbs" "$sessions")"
|
|
293
|
+
|
|
294
|
+
info "Synthesizing briefing..."
|
|
295
|
+
printf '\n'
|
|
296
|
+
|
|
297
|
+
local briefing
|
|
298
|
+
if ! briefing="$(llm_chat "$system_prompt" "$user_prompt")"; then
|
|
299
|
+
die "LLM call failed"
|
|
300
|
+
fi
|
|
301
|
+
|
|
302
|
+
printf '%s\n' "$briefing"
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
main "$@"
|