tenbrains 2.1.0 → 2.3.0

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.
Files changed (89) hide show
  1. package/AGENTS.md +19 -2
  2. package/README.md +93 -9
  3. package/dist/ai/analyzer.d.ts +7 -2
  4. package/dist/ai/analyzer.d.ts.map +1 -1
  5. package/dist/ai/analyzer.js +19 -5
  6. package/dist/ai/analyzer.js.map +1 -1
  7. package/dist/ai/mock.d.ts +2 -1
  8. package/dist/ai/mock.d.ts.map +1 -1
  9. package/dist/ai/mock.js +12 -0
  10. package/dist/ai/mock.js.map +1 -1
  11. package/dist/ai/prompts.d.ts +3 -1
  12. package/dist/ai/prompts.d.ts.map +1 -1
  13. package/dist/ai/prompts.js +17 -2
  14. package/dist/ai/prompts.js.map +1 -1
  15. package/dist/bin/tenbrains.js +0 -0
  16. package/dist/cli.d.ts.map +1 -1
  17. package/dist/cli.js +115 -8
  18. package/dist/cli.js.map +1 -1
  19. package/dist/commands/analyze.d.ts.map +1 -1
  20. package/dist/commands/analyze.js +100 -11
  21. package/dist/commands/analyze.js.map +1 -1
  22. package/dist/commands/bookmark.d.ts.map +1 -1
  23. package/dist/commands/bookmark.js +10 -1
  24. package/dist/commands/bookmark.js.map +1 -1
  25. package/dist/commands/digest.d.ts +6 -1
  26. package/dist/commands/digest.d.ts.map +1 -1
  27. package/dist/commands/digest.js +15 -5
  28. package/dist/commands/digest.js.map +1 -1
  29. package/dist/commands/learn.d.ts.map +1 -1
  30. package/dist/commands/learn.js +14 -2
  31. package/dist/commands/learn.js.map +1 -1
  32. package/dist/commands/manifest.d.ts.map +1 -1
  33. package/dist/commands/manifest.js +6 -0
  34. package/dist/commands/manifest.js.map +1 -1
  35. package/dist/commands/objective-tags.d.ts +27 -0
  36. package/dist/commands/objective-tags.d.ts.map +1 -0
  37. package/dist/commands/objective-tags.js +105 -0
  38. package/dist/commands/objective-tags.js.map +1 -0
  39. package/dist/commands/objective.d.ts +11 -0
  40. package/dist/commands/objective.d.ts.map +1 -0
  41. package/dist/commands/objective.js +208 -0
  42. package/dist/commands/objective.js.map +1 -0
  43. package/dist/commands/record.d.ts +1 -1
  44. package/dist/commands/record.d.ts.map +1 -1
  45. package/dist/commands/record.js +10 -2
  46. package/dist/commands/record.js.map +1 -1
  47. package/dist/commands/search.d.ts.map +1 -1
  48. package/dist/commands/search.js +16 -2
  49. package/dist/commands/search.js.map +1 -1
  50. package/dist/commands/takeaway.d.ts.map +1 -1
  51. package/dist/commands/takeaway.js +4 -1
  52. package/dist/commands/takeaway.js.map +1 -1
  53. package/dist/core/text.d.ts +2 -0
  54. package/dist/core/text.d.ts.map +1 -1
  55. package/dist/core/text.js +10 -0
  56. package/dist/core/text.js.map +1 -1
  57. package/dist/db/database.d.ts +2 -1
  58. package/dist/db/database.d.ts.map +1 -1
  59. package/dist/db/database.js +3 -0
  60. package/dist/db/database.js.map +1 -1
  61. package/dist/db/migrations.d.ts.map +1 -1
  62. package/dist/db/migrations.js +25 -0
  63. package/dist/db/migrations.js.map +1 -1
  64. package/dist/db/repositories.d.ts +21 -1
  65. package/dist/db/repositories.d.ts.map +1 -1
  66. package/dist/db/repositories.js +209 -0
  67. package/dist/db/repositories.js.map +1 -1
  68. package/dist/domain/learn.d.ts +5 -4
  69. package/dist/domain/learn.d.ts.map +1 -1
  70. package/dist/domain/learn.js +39 -15
  71. package/dist/domain/learn.js.map +1 -1
  72. package/dist/domain/schemas.d.ts +14 -0
  73. package/dist/domain/schemas.d.ts.map +1 -1
  74. package/dist/domain/schemas.js +5 -0
  75. package/dist/domain/schemas.js.map +1 -1
  76. package/dist/domain/search.d.ts +2 -0
  77. package/dist/domain/search.d.ts.map +1 -1
  78. package/dist/domain/search.js +20 -1
  79. package/dist/domain/search.js.map +1 -1
  80. package/dist/domain/suggest.d.ts.map +1 -1
  81. package/dist/domain/suggest.js +34 -14
  82. package/dist/domain/suggest.js.map +1 -1
  83. package/dist/domain/types.d.ts +22 -0
  84. package/dist/domain/types.d.ts.map +1 -1
  85. package/dist/youtube/client.d.ts +31 -0
  86. package/dist/youtube/client.d.ts.map +1 -0
  87. package/dist/youtube/client.js +334 -0
  88. package/dist/youtube/client.js.map +1 -0
  89. package/package.json +5 -3
package/AGENTS.md CHANGED
@@ -46,10 +46,12 @@ Error envelope:
46
46
  inline strings, `@path` (read file), or `-` (read stdin). JSON inputs (`--posts`, `--ratings`) take
47
47
  the same forms. `takeaway refresh` fetches a timeline when `--posts` is omitted (needs an X token,
48
48
  usually a paid tier).
49
- - **Ids are prefixed and stable**: `post_`, `ana_`, `acc_`, `snap_`, `bm_`, `sug_`, `trk_`. Any id
49
+ - **Ids are prefixed and stable**: `post_`, `ana_`, `acc_`, `snap_`, `bm_`, `sug_`, `trk_`, `obj_`. Any id
50
50
  resolves via `tenbrains record get <id>`.
51
51
  - **Dedup is automatic.** Re-ingesting a post with the same `--id` (external X id) reuses the stored
52
52
  post (`meta.deduped: true`).
53
+ - **Objective tagging is explicit.** Repeat `--objective <slug>` on supported creation commands, or
54
+ use `objective link`; objectives must already exist. The current focus never adds tags.
53
55
  - **Isolate state with `--db <path>`** if you want a per-task workspace.
54
56
  - **No environment variables.** Configure once with `tenbrains setup --provider <p> --api-key <k>`
55
57
  (add `--x-bearer <token>` for X timeline fetches; `-` pipes a secret without echo). Or test offline
@@ -73,6 +75,21 @@ echo '[{"concept":"Agentic","familiarity":2,"interest":5}]' \
73
75
  | tenbrains analyze --provider mock --text "..." --learn --minutes 10 --ratings -
74
76
  ```
75
77
 
78
+ Create and apply learning objectives (repeat `--objective` for multiple goals):
79
+
80
+ ```bash
81
+ tenbrains objective add "Agentic systems" --focus
82
+ tenbrains analyze --provider mock --text "..." --objective agentic-systems
83
+ tenbrains objective link bm_... --objective agentic-systems
84
+ tenbrains objective show agentic-systems
85
+ ```
86
+
87
+ Objective descriptions lens learning-track concept order through deterministic token overlap. For
88
+ multiple described objectives, use the maximum overlap against any one description. Read
89
+ `objective show`'s raw progress counts; do not derive or present a made-up completion percentage.
90
+ The current focus also biases `suggest generate` when it has a description, but never tags.
91
+ Use `search --objective <slug>` or `digest --objective <slug>` for explicit scoped output.
92
+
76
93
  Account takeaways (supply recent posts, or fetch from X with a token):
77
94
 
78
95
  ```bash
@@ -85,7 +102,7 @@ tenbrains takeaway show levelsio --history
85
102
  Suggestion feedback loop:
86
103
 
87
104
  ```bash
88
- tenbrains suggest generate # ranks analyzed, un-bookmarked posts vs your saved signal
105
+ tenbrains suggest generate # saved-signal ranking + described current-focus bias
89
106
  tenbrains suggest save sug_... # -> creates a bookmark (meta.bookmarkId)
90
107
  tenbrains suggest dismiss sug_... # -> suppressed in future ranking
91
108
  ```
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # tenbrains
2
2
 
3
- An **agent-first** CLI for X research. It analyzes posts, tracks followed accounts, surfaces
3
+ An **agent-first** CLI for X and YouTube research. It analyzes posts and video transcripts, tracks followed accounts, surfaces
4
4
  suggestions, and **persists every outcome to a local SQLite database** — all from the command line,
5
5
  with no `.env` files and no hosted backend.
6
6
 
@@ -21,8 +21,8 @@ machine-readable code and a deterministic exit status, and the entire surface is
21
21
  error codes, and exit codes as JSON — an agent can discover the whole tool in one call.
22
22
  - **Non-interactive.** No blocking prompts. Content comes in via flags, `@file`, or `-` (stdin);
23
23
  credentials are collected through commands, never by hand-editing a dotfile.
24
- - **Everything persisted.** Posts, analyses, takeaways, bookmarks, suggestions, and learning tracks
25
- all land in one local SQLite file you can point anywhere with `--db`.
24
+ - **Everything persisted.** Posts, analyses, takeaways, bookmarks, suggestions, learning tracks,
25
+ and learning objectives all land in one local SQLite file you can point anywhere with `--db`.
26
26
  - **Offline-capable.** The built-in `mock` provider produces deterministic analysis with no network,
27
27
  so agents and CI can exercise the full pipeline without API keys.
28
28
 
@@ -32,6 +32,13 @@ machine-readable code and a deterministic exit status, and the entire surface is
32
32
 
33
33
  ## Install
34
34
 
35
+ ```bash
36
+ npm install -g tenbrains # from npm — puts `tenbrains` (and `tb`) on PATH
37
+ tenbrains --help
38
+ ```
39
+
40
+ Or from a clone:
41
+
35
42
  ```bash
36
43
  npm install
37
44
  npm run build
@@ -58,6 +65,9 @@ tenbrains analyze --author levelsio --id 1790000000000000000 \
58
65
  # 2b. Or analyze a tweet by URL — fetched free via X's oEmbed endpoint, no key needed.
59
66
  tenbrains analyze --url "https://x.com/jack/status/20"
60
67
 
68
+ # 2c. Or fetch and analyze public YouTube captions, then add a narrative digest and study plan.
69
+ tenbrains analyze --url "https://www.youtube.com/watch?v=M7lc1UVf-VE" --summarize --learn
70
+
61
71
  # 3. Read it back / explore.
62
72
  tenbrains analyze list --limit 5
63
73
  tenbrains search "agent cli"
@@ -101,15 +111,16 @@ Exit codes: `0` success · `2` usage · `3` not found · `4` missing credentials
101
111
 
102
112
  | Command | Purpose |
103
113
  | --- | --- |
104
- | `analyze` | Analyze a post (`--text`, or `--url`/`--id` to fetch) into topic, summary, intent, 5 novel concepts. `--thread` analyzes a whole thread as one document; `--learn` also builds a track. |
114
+ | `analyze` | Analyze a post or YouTube transcript (`--text`, `--transcript`, or `--url` to fetch) into topic, summary, intent, and concepts. `--summarize` adds a narrative digest; `--learn` builds a track. |
105
115
  | `analyze list` / `analyze get <id>` | Read stored analyses. |
116
+ | `objective add\|list\|show\|focus\|archive\|link\|unlink` | Manage first-class learning goals, one optional current focus, and explicit record tags. |
106
117
  | `takeaway follow\|unfollow\|list\|refresh\|show` | Track accounts; summarize recent posts (supplied via `--posts` or fetched from X) into snapshots. |
107
- | `suggest generate\|list\|save\|dismiss\|add` | Rank un-saved posts against your saved signal; save/dismiss feedback. |
118
+ | `suggest generate\|list\|save\|dismiss\|add` | Rank un-saved posts against your saved signal, biased toward a described current objective focus; save/dismiss feedback. |
108
119
  | `bookmark add\|list\|show\|tag\|remove` | Save posts with tags (auto-suggested from analysis) and notes. |
109
120
  | `learn generate\|today\|done\|show\|list` | Build 7-day Feynman learning tracks, get today's task, and check off progress. |
110
- | `search <query>` | Full-text search (SQLite FTS5, BM25-ranked, stemmed) across analyses, takeaways, and bookmarks. |
121
+ | `search <query>` | Full-text search (SQLite FTS5, BM25-ranked, stemmed) across analyses, takeaways, and bookmarks; optionally filter by objective. |
111
122
  | `import x-archive <path>` | Bulk-import your extracted official X archive: likes become bookmarked posts, your tweets become posts. Free, idempotent. |
112
- | `digest [--days N]` | Markdown recap of analyses, takeaways, and bookmarks saved in the window (default 7 days). |
123
+ | `digest [--days N]` | Markdown recap of analyses, takeaways, and bookmarks saved in the window (default 7 days), optionally filtered by objective. |
113
124
  | `setup` / `config set\|get\|list\|unset\|path` | Collect and manage provider credentials and defaults. |
114
125
  | `record get <id>` | Resolve any record by its prefixed id (`post_`, `ana_`, `acc_`, ...). |
115
126
  | `db stats\|migrate\|vacuum\|reindex\|reset` | Inspect and maintain the database. |
@@ -125,6 +136,78 @@ echo "long post text..." | tenbrains analyze --provider mock --text -
125
136
  tenbrains takeaway refresh levelsio --provider mock --posts @recent.json
126
137
  ```
127
138
 
139
+ ## Learning objectives
140
+
141
+ Objectives are persistent learning goals, separate from loose bookmark tags. Each objective has a
142
+ name, derived slug, optional description, active/archived lifecycle, and tagged-record counts.
143
+ You can keep several active objectives while marking at most one as the current focus:
144
+
145
+ ```bash
146
+ tenbrains objective add "Stablecoins" \
147
+ --description "Understand reserve models, settlement, and failure modes." --focus
148
+ tenbrains objective add "AI agents"
149
+ tenbrains objective list
150
+ tenbrains objective show # defaults to the current focus
151
+ tenbrains objective focus ai-agents
152
+ tenbrains objective focus --clear
153
+ tenbrains objective link post_... --objective stablecoins
154
+ tenbrains objective unlink post_... --objective stablecoins
155
+ tenbrains objective archive stablecoins
156
+ ```
157
+
158
+ Tag at creation time with a repeatable `--objective` flag:
159
+
160
+ ```bash
161
+ tenbrains analyze --provider mock --text "..." \
162
+ --objective stablecoins --objective ai-agents
163
+ tenbrains takeaway follow levelsio --objective ai-agents
164
+ tenbrains bookmark add --post-id post_... --objective stablecoins
165
+ tenbrains learn generate --analysis ana_... --objective stablecoins
166
+ tenbrains search "reserve risk" --objective stablecoins
167
+ tenbrains digest --objective stablecoins
168
+ ```
169
+
170
+ `bookmark add` tags the bookmark's post. Without an explicit `--objective`, `learn generate` inherits
171
+ the objective tags on its source analysis' post; supplying the flag overrides that inheritance.
172
+ Every referenced objective must already exist or the command returns `NOT_FOUND` without silently
173
+ creating one. Focus never tags records automatically. `objective show` groups tagged posts,
174
+ accounts, bookmarks, and tracks; `record get` includes an `objectives` array for linkable records.
175
+
176
+ When a selected or inherited objective has a description, learning tracks rank concepts first by
177
+ deterministic token overlap with that description, then by the existing interest and familiarity
178
+ ratings. With multiple described objectives, a concept uses its highest overlap with any one
179
+ description; matches are not summed across objectives. `objective show` also returns descriptive
180
+ progress counts under `data.progress`:
181
+ accounts followed, posts and transcripts analyzed, bookmarks saved, tracks completed, and learning
182
+ days completed/total. It never fabricates a completion percentage.
183
+
184
+ `suggest generate` adds deterministic token-overlap relevance from the current focus description to
185
+ its existing saved-interest scores. With no focus or no usable focus description, its scores,
186
+ reasons, and ordering are unchanged. This bias never creates objective links. `search` and `digest`
187
+ accept one `--objective <slug>` filter and return only analyses whose posts, takeaways whose
188
+ accounts, and bookmarks (directly or through their posts) are tagged to that objective. Unknown
189
+ objectives return `NOT_FOUND`.
190
+
191
+ ## YouTube transcripts
192
+
193
+ Pass a public YouTube watch, `youtu.be`, Shorts, or embed URL to `analyze`. tenbrains selects a
194
+ caption track without an API key (manual before auto-generated; `--lang`, then English, then the
195
+ first available), stores the transcript and video metadata with the post, and runs the normal
196
+ analysis pipeline:
197
+
198
+ ```bash
199
+ tenbrains analyze --url "https://youtu.be/M7lc1UVf-VE" --lang en
200
+ tenbrains analyze --url "https://youtu.be/M7lc1UVf-VE" --summarize --learn
201
+ tenbrains analyze --url "https://youtu.be/M7lc1UVf-VE" --transcript @captions.txt
202
+ ```
203
+
204
+ `--summarize` returns `{ summary, keyPoints[] }` under `data.summary`, persists it in the post's
205
+ `raw` metadata, and uses that digest as the condensed input for concept extraction. It composes
206
+ with `--learn`. If a video is unavailable or has no captions, supply an existing transcript with
207
+ `--transcript <text|@file|->`. v1 is caption-only: it does not download audio or invoke Whisper.
208
+ YouTube's WEB caption URLs can return empty bodies, so the client retries through the embedded
209
+ Android player API; that undocumented client version is the primary maintenance surface.
210
+
128
211
  ## Configuration & credentials
129
212
 
130
213
  There is **no `.env`**. The CLI owns credential collection:
@@ -172,8 +255,9 @@ If your tier can't read a tweet/timeline, the CLI returns a structured `PROVIDER
172
255
 
173
256
  A single SQLite file (default `~/.local/share/tenbrains/tenbrains.db`, override with `--db`). Schema
174
257
  is versioned and migrated automatically on open. Tables: `posts`, `analyses`, `accounts`,
175
- `takeaway_snapshots`, `bookmarks`, `suggestions`, `learning_tracks`, plus a trigger-maintained
176
- FTS5 index (`search_fts`) behind `search` — rebuild it anytime with `tenbrains db reindex`.
258
+ `takeaway_snapshots`, `bookmarks`, `suggestions`, `learning_tracks`, `track_progress`, `objectives`,
259
+ and `objective_links`, plus a trigger-maintained FTS5 index (`search_fts`) behind `search` — rebuild
260
+ it anytime with `tenbrains db reindex`.
177
261
 
178
262
  ```bash
179
263
  tenbrains --db ./research.db analyze --provider mock --text "..." # isolate a workspace
@@ -1,4 +1,4 @@
1
- import { type AnalysisResult, type TakeawayResult } from "../domain/schemas.js";
1
+ import { type AnalysisResult, type SummaryResult, type TakeawayResult } from "../domain/schemas.js";
2
2
  import type { ResolvedProvider } from "./resolve.js";
3
3
  export interface AnalysisOutcome {
4
4
  result: AnalysisResult;
@@ -10,7 +10,12 @@ export interface PostForAnalysis {
10
10
  authorName?: string | undefined;
11
11
  url?: string | undefined;
12
12
  }
13
- export declare function analyzePost(resolved: ResolvedProvider, post: PostForAnalysis): Promise<AnalysisOutcome>;
13
+ export declare function analyzePost(resolved: ResolvedProvider, post: PostForAnalysis, kind?: "tweet" | "transcript"): Promise<AnalysisOutcome>;
14
+ export interface SummaryOutcome {
15
+ result: SummaryResult;
16
+ mock: boolean;
17
+ }
18
+ export declare function summarizeContent(resolved: ResolvedProvider, content: string): Promise<SummaryOutcome>;
14
19
  export interface TakeawayOutcome {
15
20
  result: TakeawayResult;
16
21
  mock: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../../src/ai/analyzer.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,cAAc,EAEnB,KAAK,cAAc,EAEpB,MAAM,sBAAsB,CAAC;AAU9B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,cAAc,CAAC;IACvB,IAAI,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1B;AAED,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,gBAAgB,EAC1B,IAAI,EAAE,eAAe,GACpB,OAAO,CAAC,eAAe,CAAC,CAkB1B;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,cAAc,CAAC;IACvB,IAAI,EAAE,OAAO,CAAC;CACf;AAED,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,gBAAgB,EAC1B,KAAK,EAAE;IACL,OAAO,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC;IACzD,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC,CAAC;CAC/D,GACA,OAAO,CAAC,eAAe,CAAC,CAkB1B"}
1
+ {"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../../src/ai/analyzer.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,cAAc,EAEnB,KAAK,aAAa,EAElB,KAAK,cAAc,EAEpB,MAAM,sBAAsB,CAAC;AAY9B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,cAAc,CAAC;IACvB,IAAI,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1B;AAED,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,gBAAgB,EAC1B,IAAI,EAAE,eAAe,EACrB,IAAI,GAAE,OAAO,GAAG,YAAsB,GACrC,OAAO,CAAC,eAAe,CAAC,CAkB1B;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,aAAa,CAAC;IACtB,IAAI,EAAE,OAAO,CAAC;CACf;AAED,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,cAAc,CAAC,CAkBzB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,cAAc,CAAC;IACvB,IAAI,EAAE,OAAO,CAAC;CACf;AAED,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,gBAAgB,EAC1B,KAAK,EAAE;IACL,OAAO,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC;IACzD,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC,CAAC;CAC/D,GACA,OAAO,CAAC,eAAe,CAAC,CAkB1B"}
@@ -1,10 +1,10 @@
1
1
  import { parseOrThrow } from "../core/validate.js";
2
- import { AnalysisResultSchema, TakeawayResultSchema, } from "../domain/schemas.js";
2
+ import { AnalysisResultSchema, SummaryResultSchema, TakeawayResultSchema, } from "../domain/schemas.js";
3
3
  import { complete } from "./client.js";
4
4
  import { extractJsonObject } from "./json.js";
5
- import { mockAnalysis, mockTakeaway } from "./mock.js";
6
- import { accountTakeawaySystemPrompt, accountTakeawayUserPrompt, tweetAnalysisSystemPrompt, tweetAnalysisUserPrompt, } from "./prompts.js";
7
- export async function analyzePost(resolved, post) {
5
+ import { mockAnalysis, mockSummary, mockTakeaway } from "./mock.js";
6
+ import { accountTakeawaySystemPrompt, accountTakeawayUserPrompt, contentSummarySystemPrompt, contentSummaryUserPrompt, tweetAnalysisSystemPrompt, tweetAnalysisUserPrompt, } from "./prompts.js";
7
+ export async function analyzePost(resolved, post, kind = "tweet") {
8
8
  if (resolved.provider === "mock") {
9
9
  return { result: mockAnalysis(post.text), mock: true };
10
10
  }
@@ -12,12 +12,26 @@ export async function analyzePost(resolved, post) {
12
12
  provider: resolved.provider,
13
13
  model: resolved.model,
14
14
  apiKey: resolved.apiKey,
15
- system: tweetAnalysisSystemPrompt(),
15
+ system: tweetAnalysisSystemPrompt(kind),
16
16
  user: tweetAnalysisUserPrompt(post),
17
17
  });
18
18
  const result = parseOrThrow(AnalysisResultSchema, extractJsonObject(text), "Provider response did not match the expected analysis shape.", "PROVIDER_BAD_OUTPUT");
19
19
  return { result, mock: false };
20
20
  }
21
+ export async function summarizeContent(resolved, content) {
22
+ if (resolved.provider === "mock") {
23
+ return { result: mockSummary(content), mock: true };
24
+ }
25
+ const text = await complete({
26
+ provider: resolved.provider,
27
+ model: resolved.model,
28
+ apiKey: resolved.apiKey,
29
+ system: contentSummarySystemPrompt(),
30
+ user: contentSummaryUserPrompt(content),
31
+ });
32
+ const result = parseOrThrow(SummaryResultSchema, extractJsonObject(text), "Provider response did not match the expected summary shape.", "PROVIDER_BAD_OUTPUT");
33
+ return { result, mock: false };
34
+ }
21
35
  export async function summarizeAccount(resolved, input) {
22
36
  if (resolved.provider === "mock") {
23
37
  return { result: mockTakeaway(input.posts), mock: true };
@@ -1 +1 @@
1
- {"version":3,"file":"analyzer.js","sourceRoot":"","sources":["../../src/ai/analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAEL,oBAAoB,EAEpB,oBAAoB,GACrB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,EACL,2BAA2B,EAC3B,yBAAyB,EACzB,yBAAyB,EACzB,uBAAuB,GACxB,MAAM,cAAc,CAAC;AAetB,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,QAA0B,EAC1B,IAAqB;IAErB,IAAI,QAAQ,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;QACjC,OAAO,EAAE,MAAM,EAAE,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACzD,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC;QAC1B,QAAQ,EAAE,QAAQ,CAAC,QAAQ;QAC3B,KAAK,EAAE,QAAQ,CAAC,KAAK;QACrB,MAAM,EAAE,QAAQ,CAAC,MAAgB;QACjC,MAAM,EAAE,yBAAyB,EAAE;QACnC,IAAI,EAAE,uBAAuB,CAAC,IAAI,CAAC;KACpC,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,YAAY,CACzB,oBAAoB,EACpB,iBAAiB,CAAC,IAAI,CAAC,EACvB,8DAA8D,EAC9D,qBAAqB,CACtB,CAAC;IACF,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AACjC,CAAC;AAOD,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,QAA0B,EAC1B,KAGC;IAED,IAAI,QAAQ,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;QACjC,OAAO,EAAE,MAAM,EAAE,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC3D,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC;QAC1B,QAAQ,EAAE,QAAQ,CAAC,QAAQ;QAC3B,KAAK,EAAE,QAAQ,CAAC,KAAK;QACrB,MAAM,EAAE,QAAQ,CAAC,MAAgB;QACjC,MAAM,EAAE,2BAA2B,EAAE;QACrC,IAAI,EAAE,yBAAyB,CAAC,KAAK,CAAC;KACvC,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,YAAY,CACzB,oBAAoB,EACpB,iBAAiB,CAAC,IAAI,CAAC,EACvB,8DAA8D,EAC9D,qBAAqB,CACtB,CAAC;IACF,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AACjC,CAAC"}
1
+ {"version":3,"file":"analyzer.js","sourceRoot":"","sources":["../../src/ai/analyzer.ts"],"names":[],"mappings":"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"}
package/dist/ai/mock.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- import type { AnalysisResult, TakeawayResult } from "../domain/schemas.js";
1
+ import type { AnalysisResult, SummaryResult, TakeawayResult } from "../domain/schemas.js";
2
2
  export declare function mockAnalysis(text: string): AnalysisResult;
3
+ export declare function mockSummary(text: string): SummaryResult;
3
4
  export declare function mockTakeaway(posts: Array<{
4
5
  text: string;
5
6
  }>): TakeawayResult;
@@ -1 +1 @@
1
- {"version":3,"file":"mock.d.ts","sourceRoot":"","sources":["../../src/ai/mock.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAsC3E,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,CAsBzD;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,cAAc,CAgB3E"}
1
+ {"version":3,"file":"mock.d.ts","sourceRoot":"","sources":["../../src/ai/mock.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAsC1F,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,CAsBzD;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAWvD;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,cAAc,CAgB3E"}
package/dist/ai/mock.js CHANGED
@@ -49,6 +49,18 @@ export function mockAnalysis(text) {
49
49
  });
50
50
  return { topic, summary, intent, novelConcepts };
51
51
  }
52
+ export function mockSummary(text) {
53
+ const sentences = text
54
+ .trim()
55
+ .split(/(?<=[.!?])\s+/)
56
+ .filter(Boolean);
57
+ const summary = (sentences.slice(0, 4).join(" ") || firstSentence(text)).slice(0, 1200);
58
+ const terms = significantTerms(text, 5);
59
+ const keyPoints = terms.length
60
+ ? terms.map((term) => `${titleCase(term)} is a recurring theme in the content.`)
61
+ : ["The supplied content does not contain enough detail for additional key points."];
62
+ return { summary, keyPoints };
63
+ }
52
64
  export function mockTakeaway(posts) {
53
65
  const combined = posts.map((p) => p.text).join("\n");
54
66
  const terms = significantTerms(combined, 6);
@@ -1 +1 @@
1
- {"version":3,"file":"mock.js","sourceRoot":"","sources":["../../src/ai/mock.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mock.js","sourceRoot":"","sources":["../../src/ai/mock.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,6 @@
1
- export declare function tweetAnalysisSystemPrompt(): string;
1
+ export declare function tweetAnalysisSystemPrompt(kind?: "tweet" | "transcript"): string;
2
+ export declare function contentSummarySystemPrompt(): string;
3
+ export declare function contentSummaryUserPrompt(content: string): string;
2
4
  export declare function tweetAnalysisUserPrompt(post: {
3
5
  text: string;
4
6
  authorUsername?: string | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../src/ai/prompts.ts"],"names":[],"mappings":"AAWA,wBAAgB,yBAAyB,IAAI,MAAM,CAUlD;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1B,GAAG,MAAM,CAET;AAED,wBAAgB,2BAA2B,IAAI,MAAM,CAUpD;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE;IAC/C,OAAO,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC;IACzD,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC,CAAC;CAC/D,GAAG,MAAM,CAST"}
1
+ {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../src/ai/prompts.ts"],"names":[],"mappings":"AAWA,wBAAgB,yBAAyB,CAAC,IAAI,GAAE,OAAO,GAAG,YAAsB,GAAG,MAAM,CAYxF;AAED,wBAAgB,0BAA0B,IAAI,MAAM,CASnD;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEhE;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1B,GAAG,MAAM,CAET;AAED,wBAAgB,2BAA2B,IAAI,MAAM,CAUpD;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE;IAC/C,OAAO,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC;IACzD,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC,CAAC;CAC/D,GAAG,MAAM,CAST"}
@@ -6,9 +6,11 @@ function compactPost(post) {
6
6
  url: post.url ?? null,
7
7
  };
8
8
  }
9
- export function tweetAnalysisSystemPrompt() {
9
+ export function tweetAnalysisSystemPrompt(kind = "tweet") {
10
10
  return [
11
- "You are analyzing a post on X.",
11
+ kind === "transcript"
12
+ ? "You are analyzing a video transcript."
13
+ : "You are analyzing a post on X.",
12
14
  "Return only a valid JSON object with exactly these keys: topic, summary, intent, novelConcepts.",
13
15
  "novelConcepts must be an array with exactly 5 objects.",
14
16
  "Each concept object must have keys: name and whyItMattersInTweet.",
@@ -17,6 +19,19 @@ export function tweetAnalysisSystemPrompt() {
17
19
  "Keep it factual and avoid speculation when evidence is missing.",
18
20
  ].join(" ");
19
21
  }
22
+ export function contentSummarySystemPrompt() {
23
+ return [
24
+ "You are summarizing long-form content.",
25
+ "Return only a valid JSON object with exactly these keys: summary, keyPoints.",
26
+ "summary must be a substantive, readable multi-paragraph string.",
27
+ "keyPoints must be an array of non-empty strings.",
28
+ "Stay factual and grounded in the supplied content.",
29
+ "Do not include markdown fences, comments, or extra keys.",
30
+ ].join(" ");
31
+ }
32
+ export function contentSummaryUserPrompt(content) {
33
+ return JSON.stringify({ content }, null, 2);
34
+ }
20
35
  export function tweetAnalysisUserPrompt(post) {
21
36
  return JSON.stringify({ post: compactPost(post) }, null, 2);
22
37
  }
@@ -1 +1 @@
1
- {"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../src/ai/prompts.ts"],"names":[],"mappings":"AAEA,SAAS,WAAW,CAAC,IAAqE;IACxF,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,cAAc,EAAE,IAAI,CAAC,cAAc,IAAI,IAAI;QAC3C,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,IAAI;QACnC,GAAG,EAAE,IAAI,CAAC,GAAG,IAAI,IAAI;KACtB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,yBAAyB;IACvC,OAAO;QACL,gCAAgC;QAChC,iGAAiG;QACjG,wDAAwD;QACxD,mEAAmE;QACnE,kFAAkF;QAClF,0DAA0D;QAC1D,iEAAiE;KAClE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACd,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,IAKvC;IACC,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,WAAW,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AAC9D,CAAC;AAED,MAAM,UAAU,2BAA2B;IACzC,OAAO;QACL,mEAAmE;QACnE,8EAA8E;QAC9E,qCAAqC;QACrC,2DAA2D;QAC3D,yEAAyE;QACzE,oDAAoD;QACpD,0DAA0D;KAC3D,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACd,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,KAGzC;IACC,OAAO,IAAI,CAAC,SAAS,CACnB;QACE,OAAO,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,IAAI,IAAI,EAAE;QAC/E,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,IAAI,EAAE,CAAC,CAAC;KAChF,EACD,IAAI,EACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../src/ai/prompts.ts"],"names":[],"mappings":"AAEA,SAAS,WAAW,CAAC,IAAqE;IACxF,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,cAAc,EAAE,IAAI,CAAC,cAAc,IAAI,IAAI;QAC3C,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,IAAI;QACnC,GAAG,EAAE,IAAI,CAAC,GAAG,IAAI,IAAI;KACtB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,OAA+B,OAAO;IAC9E,OAAO;QACL,IAAI,KAAK,YAAY;YACnB,CAAC,CAAC,uCAAuC;YACzC,CAAC,CAAC,gCAAgC;QACpC,iGAAiG;QACjG,wDAAwD;QACxD,mEAAmE;QACnE,kFAAkF;QAClF,0DAA0D;QAC1D,iEAAiE;KAClE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACd,CAAC;AAED,MAAM,UAAU,0BAA0B;IACxC,OAAO;QACL,wCAAwC;QACxC,8EAA8E;QAC9E,iEAAiE;QACjE,kDAAkD;QAClD,oDAAoD;QACpD,0DAA0D;KAC3D,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACd,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,OAAe;IACtD,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AAC9C,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,IAKvC;IACC,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,WAAW,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AAC9D,CAAC;AAED,MAAM,UAAU,2BAA2B;IACzC,OAAO;QACL,mEAAmE;QACnE,8EAA8E;QAC9E,qCAAqC;QACrC,2DAA2D;QAC3D,yEAAyE;QACzE,oDAAoD;QACpD,0DAA0D;KAC3D,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACd,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,KAGzC;IACC,OAAO,IAAI,CAAC,SAAS,CACnB;QACE,OAAO,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,IAAI,IAAI,EAAE;QAC/E,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,IAAI,EAAE,CAAC,CAAC;KAChF,EACD,IAAI,EACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
File without changes
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAkB,MAAM,WAAW,CAAC;AA2KpD,wBAAgB,YAAY,IAAI,OAAO,CA4ftC;AAED,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAwBvD"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAkB,MAAM,WAAW,CAAC;AA6LpD,wBAAgB,YAAY,IAAI,OAAO,CAinBtC;AAED,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAwBvD"}
package/dist/cli.js CHANGED
@@ -8,6 +8,7 @@ import { digestCommand } from "./commands/digest.js";
8
8
  import { importXArchiveCommand } from "./commands/import.js";
9
9
  import { learnDoneCommand, learnGenerateCommand, learnListCommand, learnShowCommand, learnTodayCommand, } from "./commands/learn.js";
10
10
  import { manifestCommand } from "./commands/manifest.js";
11
+ import { objectiveAddCommand, objectiveArchiveCommand, objectiveFocusCommand, objectiveLinkCommand, objectiveListCommand, objectiveShowCommand, objectiveUnlinkCommand, } from "./commands/objective.js";
11
12
  import { recordGetCommand } from "./commands/record.js";
12
13
  import { searchCommand } from "./commands/search.js";
13
14
  import { suggestAddCommand, suggestDismissCommand, suggestGenerateCommand, suggestListCommand, suggestSaveCommand, } from "./commands/suggest.js";
@@ -92,7 +93,12 @@ function makeCommand(reg) {
92
93
  cmd.argument(arg.spec, arg.description ?? "");
93
94
  }
94
95
  for (const option of reg.options ?? []) {
95
- cmd.option(option.flags, option.description);
96
+ if (option.repeatable) {
97
+ cmd.option(option.flags, option.description, (value, previous) => [...previous, value], []);
98
+ }
99
+ else {
100
+ cmd.option(option.flags, option.description);
101
+ }
96
102
  }
97
103
  for (const negation of reg.negations ?? []) {
98
104
  cmd.option(negation.flags, negation.description);
@@ -117,7 +123,7 @@ export function buildProgram() {
117
123
  const program = new Command();
118
124
  program
119
125
  .name("tenbrains")
120
- .description("Agent-first CLI for X research: analyze posts, track accounts, surface suggestions, and persist every outcome to a local SQLite database.")
126
+ .description("Agent-first CLI for X and YouTube research: analyze content and persist every outcome to a local SQLite database.")
121
127
  .version(readVersion(), "-v, --version")
122
128
  .exitOverride()
123
129
  .configureOutput({ writeErr: () => { } });
@@ -125,13 +131,25 @@ export function buildProgram() {
125
131
  // --- analyze --------------------------------------------------------------
126
132
  const analyze = makeCommand({
127
133
  name: "analyze",
128
- description: "Analyze a post into topic, summary, intent, and 5 novel concepts.",
134
+ description: "Analyze a post or YouTube transcript into topic, summary, intent, and concepts.",
129
135
  options: [
130
136
  ...POST_INPUT_OPTIONS,
131
137
  {
132
138
  flags: "--thread [json]",
133
139
  description: "Analyze a whole thread as one document: pass parts as JSON (@file/- ok), or bare with --url/--id to fetch the author's self-thread via the X API",
134
140
  },
141
+ {
142
+ flags: "--transcript <text>",
143
+ description: "Video transcript; @file reads a file, - reads stdin",
144
+ },
145
+ {
146
+ flags: "--lang <code>",
147
+ description: "Preferred YouTube caption language (then English, then first available)",
148
+ },
149
+ {
150
+ flags: "--summarize",
151
+ description: "Also generate and persist a fuller narrative summary with key points",
152
+ },
135
153
  {
136
154
  flags: "--fetch <mode>",
137
155
  description: "With --url/--id and no --text: auto|oembed|api (default auto, free-first)",
@@ -141,6 +159,11 @@ export function buildProgram() {
141
159
  description: "X API Bearer token for --fetch api/fallback; - reads stdin",
142
160
  },
143
161
  ...PROVIDER_OPTIONS,
162
+ {
163
+ flags: "--objective <slug>",
164
+ description: "Tag the resulting post; repeat for multiple existing objectives",
165
+ repeatable: true,
166
+ },
144
167
  { flags: "--learn", description: "Also generate a 7-day Feynman learning track" },
145
168
  { flags: "--ratings <json>", description: "Concept ratings JSON for --learn (@file/- ok)" },
146
169
  { flags: "--minutes <n>", description: "Minutes per day for --learn (default 10)" },
@@ -176,7 +199,14 @@ export function buildProgram() {
176
199
  name: "follow",
177
200
  description: "Follow an account for takeaway tracking.",
178
201
  args: [{ spec: "<username>", description: "X username (with or without @)" }],
179
- options: [{ flags: "--name <name>", description: "Display name" }],
202
+ options: [
203
+ { flags: "--name <name>", description: "Display name" },
204
+ {
205
+ flags: "--objective <slug>",
206
+ description: "Tag the followed account; repeat for multiple existing objectives",
207
+ repeatable: true,
208
+ },
209
+ ],
180
210
  handler: takeawayFollowCommand,
181
211
  }));
182
212
  takeaway.addCommand(makeCommand({
@@ -232,7 +262,7 @@ export function buildProgram() {
232
262
  });
233
263
  suggest.addCommand(makeCommand({
234
264
  name: "generate",
235
- description: "Regenerate pending suggestions from the local corpus.",
265
+ description: "Regenerate suggestions, biased by the described current objective focus.",
236
266
  options: [{ flags: "--limit <n>", description: "Max suggestions returned (default 10)" }],
237
267
  handler: suggestGenerateCommand,
238
268
  }));
@@ -289,6 +319,11 @@ export function buildProgram() {
289
319
  { flags: "--tags <list>", description: "Comma-separated tags" },
290
320
  { flags: "--note <note>", description: "Freeform note" },
291
321
  { flags: "--source <source>", description: "Source label (default cli)" },
322
+ {
323
+ flags: "--objective <slug>",
324
+ description: "Tag the bookmarked post; repeat for multiple existing objectives",
325
+ repeatable: true,
326
+ },
292
327
  ],
293
328
  negations: [{ flags: "--no-auto-tags", description: "Disable automatic tag suggestion" }],
294
329
  handler: bookmarkAddCommand,
@@ -337,11 +372,16 @@ export function buildProgram() {
337
372
  });
338
373
  learn.addCommand(makeCommand({
339
374
  name: "generate",
340
- description: "Build a learning track from an analysis' concepts.",
375
+ description: "Build a learning track, lensed by any selected objective descriptions.",
341
376
  options: [
342
377
  { flags: "--analysis <id>", description: "Analysis id to build from" },
343
378
  { flags: "--ratings <json>", description: "Concept ratings JSON (@file/- ok)" },
344
379
  { flags: "--minutes <n>", description: "Minutes per day (default 10)" },
380
+ {
381
+ flags: "--objective <slug>",
382
+ description: "Tag the track; repeat to override inherited objectives",
383
+ repeatable: true,
384
+ },
345
385
  ],
346
386
  handler: learnGenerateCommand,
347
387
  }));
@@ -377,6 +417,69 @@ export function buildProgram() {
377
417
  handler: learnListCommand,
378
418
  }));
379
419
  program.addCommand(learn);
420
+ // --- objective ------------------------------------------------------------
421
+ const objective = makeCommand({
422
+ name: "objective",
423
+ description: "Manage first-class learning objectives and the current focus.",
424
+ handler: () => {
425
+ throw new CliError("USAGE", "Specify a subcommand: add|list|show|focus|archive|link|unlink.");
426
+ },
427
+ });
428
+ objective.addCommand(makeCommand({
429
+ name: "add",
430
+ description: "Create a learning objective; its kebab-case slug is derived from the name.",
431
+ args: [{ spec: "<name>", description: "Objective name" }],
432
+ options: [
433
+ { flags: "--description <text>", description: "Fuller statement of the learning goal" },
434
+ { flags: "--focus", description: "Make this objective the current focus" },
435
+ ],
436
+ handler: objectiveAddCommand,
437
+ }));
438
+ objective.addCommand(makeCommand({
439
+ name: "list",
440
+ description: "List objectives with tagged counts and the current-focus marker.",
441
+ options: [
442
+ {
443
+ flags: "--status <status>",
444
+ description: "Filter active|archived|all (default active)",
445
+ },
446
+ ],
447
+ handler: objectiveListCommand,
448
+ }));
449
+ objective.addCommand(makeCommand({
450
+ name: "show",
451
+ description: "Show an objective, tagged records, and descriptive progress (defaults to the current focus).",
452
+ args: [{ spec: "[slug]", description: "Objective slug or obj_ id" }],
453
+ handler: objectiveShowCommand,
454
+ }));
455
+ objective.addCommand(makeCommand({
456
+ name: "focus",
457
+ description: "Set or clear the one current objective focus.",
458
+ args: [{ spec: "[slug]", description: "Active objective slug or obj_ id" }],
459
+ options: [{ flags: "--clear", description: "Clear the current focus" }],
460
+ handler: objectiveFocusCommand,
461
+ }));
462
+ objective.addCommand(makeCommand({
463
+ name: "archive",
464
+ description: "Archive an objective while preserving its record links.",
465
+ args: [{ spec: "<slug>", description: "Objective slug or obj_ id" }],
466
+ handler: objectiveArchiveCommand,
467
+ }));
468
+ objective.addCommand(makeCommand({
469
+ name: "link",
470
+ description: "Tag an existing post, account, bookmark, or track with an objective.",
471
+ args: [{ spec: "<recordId>", description: "Existing post_, acc_, bm_, or trk_ id" }],
472
+ options: [{ flags: "--objective <slug>", description: "Existing objective slug or obj_ id" }],
473
+ handler: objectiveLinkCommand,
474
+ }));
475
+ objective.addCommand(makeCommand({
476
+ name: "unlink",
477
+ description: "Remove an objective tag from an existing record.",
478
+ args: [{ spec: "<recordId>", description: "Existing post_, acc_, bm_, or trk_ id" }],
479
+ options: [{ flags: "--objective <slug>", description: "Existing objective slug or obj_ id" }],
480
+ handler: objectiveUnlinkCommand,
481
+ }));
482
+ program.addCommand(objective);
380
483
  // --- search ---------------------------------------------------------------
381
484
  program.addCommand(makeCommand({
382
485
  name: "search",
@@ -385,6 +488,7 @@ export function buildProgram() {
385
488
  options: [
386
489
  { flags: "--type <list>", description: "Limit types: analysis,takeaway,bookmark (or all)" },
387
490
  { flags: "--limit <n>", description: "Max hits per type (default 10)" },
491
+ { flags: "--objective <slug>", description: "Only records tagged with this objective" },
388
492
  ],
389
493
  handler: searchCommand,
390
494
  }));
@@ -392,7 +496,10 @@ export function buildProgram() {
392
496
  program.addCommand(makeCommand({
393
497
  name: "digest",
394
498
  description: "Markdown recap of analyses, takeaways, and bookmarks saved recently.",
395
- options: [{ flags: "--days <n>", description: "Window in days (default 7)" }],
499
+ options: [
500
+ { flags: "--days <n>", description: "Window in days (default 7)" },
501
+ { flags: "--objective <slug>", description: "Only records tagged with this objective" },
502
+ ],
396
503
  handler: digestCommand,
397
504
  }));
398
505
  // --- import ---------------------------------------------------------------
@@ -479,7 +586,7 @@ export function buildProgram() {
479
586
  });
480
587
  record.addCommand(makeCommand({
481
588
  name: "get",
482
- description: "Fetch any record by id (post_/ana_/acc_/snap_/bm_/sug_/trk_).",
589
+ description: "Fetch any record by id, including obj_, with its objective tags.",
483
590
  args: [{ spec: "<id>" }],
484
591
  handler: recordGetCommand,
485
592
  }));