@procrastivity/clast 0.0.1 → 0.0.3

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.
@@ -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 `clast snapshot` (idempotent; silent on no-op). This guarantees we're not missing anything that ran since the last hook fire.
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
- clast snapshot
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 from yesterday
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
- clast --json sessions --day yesterday
54
+ $CLAST_BIN --json sessions --since -30d
30
55
  ```
31
56
 
32
- Filter to sessions with `curated: false`. If everything from yesterday is already curated, print "Nothing to curate from yesterday already done." and stop.
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
- clast --json show <session-id> --full --turns 8
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
- clast breadcrumb --read --project <slug> --day yesterday
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
- When generating each draft, use this prompt internally (with the placeholders filled in):
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
- Be concise. Prefer bullets over paragraphs. Use the user's terminology (project-specific names, file paths). Don't invent details. If you're uncertain about something, omit it rather than guess.
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
- Suggest tags after the entry, separated by a blank line, prefixed with "Suggested tags:". The user will confirm.
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
- clast entries write \
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 from yesterday**: print "No sessions from yesterday." and stop.
191
- - **All sessions already curated**: print "All sessions from yesterday already curated." and stop.
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
- clast registry resolve "$(pwd)"
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
- clast --json entries --project <slug> --limit 5
54
+ $CLAST_BIN --json entries --project <slug> --limit 5
32
55
 
33
56
  # Today's breadcrumbs for this project
34
- clast breadcrumb --read --project <slug> --day today
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
- clast --json sessions --day today --project <slug>
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
- clast entries read <entry-path>
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-contract.md#clast-snapshot`](./docs/cli-contract.md#clast-snapshot)
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-contract.md#clast-projects`](./docs/cli-contract.md#clast-projects),
35
- [`docs/cli-contract.md#clast-sessions`](./docs/cli-contract.md#clast-sessions),
36
- and [`docs/cli-contract.md#clast-show`](./docs/cli-contract.md#clast-show)
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-contract.md#entry-frontmatter`](./docs/cli-contract.md#entry-frontmatter)
51
+ [`docs/reference/cli.md#entry-frontmatter`](./docs/reference/cli.md#entry-frontmatter)
52
52
  for the full frontmatter schema and
53
- [`docs/cli-contract.md#clast-entries`](./docs/cli-contract.md#clast-entries)
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-contract.md#clast-breadcrumb`](./docs/cli-contract.md#clast-breadcrumb)
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-contract.md#clast-stats`](./docs/cli-contract.md#clast-stats)
81
- and [`docs/cli-contract.md#clast-doctor`](./docs/cli-contract.md#clast-doctor)
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
 
@@ -90,9 +90,11 @@ for the current set of flags.
90
90
  ./install.sh ~/.local
91
91
  ```
92
92
 
93
- `make install` wraps the same script. Use `./uninstall.sh ~/.local` (or
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)
93
+ `make install` wraps the same script; `make install-local` installs the working
94
+ tree to `~/.local` with no sudo (and `make uninstall-local` removes it). Use
95
+ `./uninstall.sh ~/.local` (or `make uninstall` for the default prefix) to remove
96
+ the installed files. See
97
+ [`docs/reference/repo-bootstrap.md#installsh--uninstallsh`](./docs/reference/repo-bootstrap.md#installsh--uninstallsh)
96
98
  for the rationale.
97
99
 
98
100
  ## Install with Nix
@@ -106,7 +108,7 @@ nix build .#default && ./result/bin/clast --version
106
108
  ```
107
109
 
108
110
  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)
111
+ See [`docs/reference/repo-bootstrap.md#nix-flake`](./docs/reference/repo-bootstrap.md#nix-flake)
110
112
  for the full overlay wiring.
111
113
 
112
114
  ## Install via npm
@@ -136,7 +138,7 @@ Today the plugin ships a single `SessionStart` hook: every time a Claude Code
136
138
  session starts it backgrounds `clast snapshot`, so your journal stays current
137
139
  with zero manual effort. The hook is best-effort and silent: if the `clast` CLI
138
140
  isn't on your `PATH` yet, sessions still start cleanly. See
139
- [`docs/skill-prompts.md#hook-sessionstart`](./docs/skill-prompts.md#hook-sessionstart)
141
+ [`docs/reference/plugin.md#hook-sessionstart`](./docs/reference/plugin.md#hook-sessionstart)
140
142
  for the hook's design rationale.
141
143
 
142
144
  ### `/day-wakeup`
@@ -145,7 +147,7 @@ At the start of each day, run `/day-wakeup` inside any Claude Code session after
145
147
  the plugin is installed. It performs once-per-day cross-project curation of
146
148
  yesterday's sessions into durable journal entries, walking each uncurated session
147
149
  through a draft you can accept, edit, skip, or mark for in-entry promotion. See
148
- [`docs/skill-prompts.md#skill-1-day-wakeup`](./docs/skill-prompts.md#skill-1-day-wakeup).
150
+ [`docs/reference/plugin.md#skill-1-day-wakeup`](./docs/reference/plugin.md#skill-1-day-wakeup).
149
151
 
150
152
  ### `/wakeup`
151
153
 
@@ -153,7 +155,7 @@ When starting work on a specific project, run `/wakeup` (or `/wakeup <slug>` fro
153
155
  anywhere) to get a per-project read-only briefing synthesized from recent curated entries,
154
156
  today's breadcrumbs, and any sessions already started today. `/wakeup` never writes — it
155
157
  only reads. See
156
- [`docs/skill-prompts.md#skill-2-wakeup`](./docs/skill-prompts.md#skill-2-wakeup).
158
+ [`docs/reference/plugin.md#skill-2-wakeup`](./docs/reference/plugin.md#skill-2-wakeup).
157
159
 
158
160
  ## Development
159
161
 
@@ -166,15 +168,16 @@ only reads. See
166
168
  Pull requests run lint, tests, version sync, npm pack shape, Nix smoke, flake check, and Nix build automatically.
167
169
  Releases trigger on `v*` tags, and the tag version must match `package.json` exactly.
168
170
  The release workflow publishes to npm with provenance and creates a GitHub Release with the npm tarball attached.
169
- Configure the `NPM_TOKEN` repo secret before the first release tag.
171
+ 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
172
 
171
173
  ## Documentation
172
174
 
173
- - [`docs/overview.md`](./docs/overview.md) project overview and design.
174
- - [`docs/cli-contract.md`](./docs/cli-contract.md) — CLI reference.
175
- - [`docs/skill-prompts.md`](./docs/skill-prompts.md) plugin reference.
176
- - [`docs/repo-bootstrap.md`](./docs/repo-bootstrap.md) repo layout and packaging.
177
- - [`docs/releasing.md`](./docs/releasing.md) release runbook.
175
+ Start at [`docs/README.md`](./docs/README.md) for the full index. Highlights:
176
+
177
+ - **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)
178
+ - **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)
179
+ - **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)
180
+ - **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
181
  - [`examples/`](./examples/) — cron, systemd-timer, and workflow samples.
179
182
 
180
183
  ## License
@@ -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 "$(realpath "$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 "$@"