logdig 0.1.1 → 0.1.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/QUICKSTART.md +9 -5
- package/README.md +34 -11
- package/bin/logdig.js +70 -43
- package/package.json +1 -1
- package/src/cli-status.js +35 -0
- package/src/index.js +20 -19
- package/src/journal.js +74 -31
- package/src/pi-client.js +7 -4
- package/src/session-runner.js +126 -74
- package/src/settings.js +10 -0
- package/src/setup.js +8 -3
- package/src/transcript.js +13 -16
- package/src/work-blocks.js +99 -0
package/QUICKSTART.md
CHANGED
|
@@ -19,7 +19,7 @@ In the wizard:
|
|
|
19
19
|
- **Summary cache:** choose `My Vault/LogDig` to keep the full summaries visible in Obsidian.
|
|
20
20
|
- **Heading and length:** `# Projects` and `small` are a good first try. Your personal `# Log` stays separate.
|
|
21
21
|
- **Timezone:** check that this is the timezone you journal in.
|
|
22
|
-
- **Advanced settings:**
|
|
22
|
+
- **Advanced settings:** choose a custom Pi history folder, executable, model, or thinking level here. Thinking defaults to no LogDig override; choose an explicit level such as `max` for a cost-first reasoning model if you are willing to wait longer.
|
|
23
23
|
- **Automatic capture:** choose **no** for now.
|
|
24
24
|
- **Pi extension:** optional. The CLI works without it.
|
|
25
25
|
|
|
@@ -33,9 +33,9 @@ logdig status 7
|
|
|
33
33
|
logdig backfill 1 --dry-run
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
`status`
|
|
36
|
+
`status` counts **logged**, **stale**, and **new** work blocks, with reusable summaries counted separately from blocks needing summarization. It is read-only. The backfill preview shows projects, dates, destination files, cache hits, and which blocks need summarizing. **Neither command makes model requests or changes files.** The preview does not display transcript excerpts.
|
|
37
37
|
|
|
38
|
-
`1` means today in your chosen timezone. If nothing happened today:
|
|
38
|
+
`1` means conversation activity today in your chosen timezone. Continuous overnight work stays on its starting day, so today's backfill may update yesterday's note. Resuming on a later date after at least four hours without conversation activity starts a linked continuation entry. If its preceding snapshot is missing, preview explicitly includes the earlier block needed for that link. If nothing happened today:
|
|
39
39
|
|
|
40
40
|
```sh
|
|
41
41
|
logdig backfill 7 --dry-run
|
|
@@ -51,11 +51,15 @@ Once the preview looks right, run the same range without `--dry-run`:
|
|
|
51
51
|
logdig backfill 1
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
+
To override the model and thinking level for one run, use `logdig backfill 1 --model provider/model --thinking max`. Use `--thinking default` to ignore a saved thinking override. More thinking may improve factual reconstruction, but can increase latency and token cost; available effort depends on the model. Saved thinking preferences also apply to `/journal` and automatic capture.
|
|
55
|
+
|
|
54
56
|
This may send selected, redacted history to your Pi model and incur provider charges. Pi uses its existing authentication. If authentication fails, open Pi, run `/login`, then retry. Common secrets are redacted, but redaction is not a complete secret scanner.
|
|
55
57
|
|
|
56
|
-
Open the daily-note date printed by the command. You will find project subheadings under `# Projects`, with timestamped summaries grouped beneath each one. Your existing text stays in place. Click a timestamp to open that entry's saved snapshot of all three summary lengths, or browse `<cache folder>/Sessions
|
|
58
|
+
Open the daily-note date printed by the command. You will find project subheadings under `# Projects`, with timestamped summaries grouped beneath each one. Your existing text stays in place. Click a timestamp to open that entry's saved snapshot of all three summary lengths, or browse `<cache folder>/Sessions/` for the latest work-block summaries.
|
|
59
|
+
|
|
60
|
+
Repeating unchanged work reuses its summary and does not insert that entry again. Session renames and other bookkeeping do not trigger regeneration. New work in the same block updates that block's row; continuation blocks get separate linked entries. Manually edited blurbs and older snapshots in `Entries/` are preserved. Run only one save at a time against your journal, and let vault sync finish first.
|
|
57
61
|
|
|
58
|
-
|
|
62
|
+
If you used the older whole-session journal format, the first real save regenerates summaries for selected work blocks and migrates the old rows while keeping their linked snapshots. Preview before saving to check the dates and model work.
|
|
59
63
|
|
|
60
64
|
## Optional: use `/journal` inside Pi
|
|
61
65
|
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Remember what you worked on, without writing another status report.
|
|
4
4
|
|
|
5
|
-
LogDig turns saved [Pi](https://pi.dev) sessions into short entries in your Obsidian daily notes. Your handwritten content stays in place. Each
|
|
5
|
+
LogDig turns saved [Pi](https://pi.dev) sessions into short entries in your Obsidian daily notes. Your handwritten content stays in place. Each work block also gets a cached Markdown note with **Small**, **Medium**, and **Large** summaries, so the details are there when you want them.
|
|
6
6
|
|
|
7
7
|
No build step, runtime dependencies, or separate model credentials. Try a safe preview before sending any history to a model.
|
|
8
8
|
|
|
@@ -33,7 +33,7 @@ For a comfortable first try:
|
|
|
33
33
|
- Check the timezone. It determines the journal date and time.
|
|
34
34
|
- Leave automatic capture **off** until you have tried a manual run. Pi integration is optional.
|
|
35
35
|
|
|
36
|
-
`doctor` checks folder access and Pi availability without requesting a summary. It reports broken paths and a missing Pi executable together, with a next step for each.
|
|
36
|
+
`doctor` checks folder access and Pi availability without requesting a summary. It reports broken paths and a missing Pi executable together, with a next step for each. Interactive terminals use colored Nerd Font status icons; set `LOGDIG_ICONS=0` for text markers, and `NO_COLOR` to disable color.
|
|
37
37
|
|
|
38
38
|
**`--dry-run` never calls Pi, writes files, or creates folders.** It shows the projects, dates, destination files, matching cached summaries, and sessions that would need model requests. The preview does not print transcript excerpts.
|
|
39
39
|
|
|
@@ -43,7 +43,7 @@ When the preview looks right:
|
|
|
43
43
|
logdig backfill 1
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
This journals
|
|
46
|
+
This journals work blocks with conversation activity **today in your configured timezone**, not a rolling 24-hour window. Overnight work can update yesterday's note without moving it to today. If today is quiet, preview seven days instead:
|
|
47
47
|
|
|
48
48
|
```sh
|
|
49
49
|
logdig backfill 7 --dry-run
|
|
@@ -61,7 +61,8 @@ My Vault/
|
|
|
61
61
|
│ └── YYYY-MM-DD.md your writing, plus project-grouped summaries under # Projects
|
|
62
62
|
└── LogDig/
|
|
63
63
|
├── Sessions/
|
|
64
|
-
│
|
|
64
|
+
│ ├── <session-id>.md latest summary of the first work block
|
|
65
|
+
│ └── <session-id>-<block-id>.md latest summary of each continuation
|
|
65
66
|
└── Entries/
|
|
66
67
|
└── <entry-id>.md snapshot of all three layers for a journal entry
|
|
67
68
|
```
|
|
@@ -88,13 +89,30 @@ More work on the same project.
|
|
|
88
89
|
**[[<single-entry-id>|23:13]]**: One entry stays compact.
|
|
89
90
|
```
|
|
90
91
|
|
|
91
|
-
|
|
92
|
+
Each entry keeps its timestamp inline at the start of its summary, even when a project has multiple entries. Summary wording is preserved, including any edits you made.
|
|
92
93
|
|
|
93
|
-
Obsidian displays each link as just the timestamp; clicking it opens that entry's detailed summary. The detailed note has two compact frontmatter properties when available: `sessionUsage` for recorded Pi
|
|
94
|
+
Obsidian displays each link as just the timestamp; clicking it opens that entry's detailed summary. The detailed note has two compact frontmatter properties when available: `sessionUsage` for recorded Pi usage within that work block and `logUsage` for LogDig's summary-generation requests, including intermediate chunks. Each shows cost, cached input, uncached input, output, and elapsed time, for example `"$3.73 ⚡12.2M ↑747k ↓62k · 1h 55m"`. Work-block duration is wall-clock time, including idle periods within the block; LogDig duration is the time spent generating that summary. The original session and LogDig calls are counted separately. Historical summaries made before usage tracking have no `logUsage`; their cost cannot be recovered without making new requests. Unknown cost or token counts are omitted, not shown as zero. Keep the summary cache inside your vault so Obsidian can resolve these links. Daily summaries do not create headings or code fences; structured detail stays in the linked note. Custom section headings are supported, with project subheadings one level deeper (or bold project labels beneath a level-six heading).
|
|
94
95
|
|
|
95
|
-
|
|
96
|
+
### Overnight work and continuations
|
|
96
97
|
|
|
97
|
-
|
|
98
|
+
A saved Pi session can contain several **work blocks**. A new block starts only at a new user message when both conditions hold:
|
|
99
|
+
|
|
100
|
+
- Its local date is later than the current block's starting date.
|
|
101
|
+
- At least **four hours** have passed since the preceding conversation activity. Assistant messages and tool results count as activity; session names, model switches, labels, usage records, and extension bookkeeping do not.
|
|
102
|
+
|
|
103
|
+
The block's **starting date and time** supply its journal timestamp. Continuous work from 10pm to 2am stays one entry on the starting day. Returning at 11am after a long break creates a separate entry on the new day, with a **Continues** link to the preceding block's saved snapshot. Repeated saves are checkpoints, not boundaries. Alternate session branches are included as explorations, not assumed to be the final result.
|
|
104
|
+
|
|
105
|
+
Date ranges select actual conversation activity, not just assigned journal dates. Today's backfill therefore catches assistant completion or continued work after midnight and updates yesterday's entry. If a selected continuation has no preceding snapshot, LogDig includes the missing earlier block(s) as prerequisites. Status and preview explicitly show these additions, including their summary-generation cost implications.
|
|
106
|
+
|
|
107
|
+
### Summary reuse and existing journals
|
|
108
|
+
|
|
109
|
+
Summary freshness is based on the selected, redacted evidence and bounded earlier context, plus summary-processing version, timezone, and configured generation policy. Earlier context is drawn from the preceding block's evidence, not its generated summary, and is marked as background rather than work to repeat. Metadata-only changes do not trigger model requests; usage totals can refresh separately. Explicit LogDig model or thinking-level changes invalidate summaries, while changes to Pi's defaults do not force regeneration under the default policy.
|
|
110
|
+
|
|
111
|
+
The identifier in each timestamp link prevents duplicate entries, without HTML comments. Older project-name links and comment-wrapped entries are still recognized. When a block evolves, LogDig replaces only that block's daily-note row with a link to the latest summary. Earlier blocks stay in place. Previous linked summary snapshots remain in `Entries/`, and manually edited daily summaries are preserved; usage metadata may be refreshed without regenerating the prose.
|
|
112
|
+
|
|
113
|
+
**Upgrading from whole-session journaling:** old summaries use an incompatible cache key and need one regeneration per selected work block. A real save assigns legacy entries to their work blocks using their recorded journal timestamp, updates or relocates their rows as needed, and keeps the original linked snapshots. Run `backfill N --dry-run` first to see the scope and model work. Status and preview never migrate files.
|
|
114
|
+
|
|
115
|
+
Saved heading preferences are not overridden by new defaults; rerun setup to change them. New entries keep their timestamps inline even when their project already has entries. Headings inside frontmatter or fenced code are not insertion targets.
|
|
98
116
|
|
|
99
117
|
Missing daily-note and cache folders are created only by a real save. Raw Pi history stays in Pi's storage.
|
|
100
118
|
|
|
@@ -111,8 +129,9 @@ logdig backfill journal the last 3 calendar days
|
|
|
111
129
|
logdig backfill 7 --dry-run preview seven days without changing anything
|
|
112
130
|
logdig backfill all --dry-run preview every discoverable saved session
|
|
113
131
|
logdig backfill 7 journal seven days
|
|
114
|
-
logdig backfill 7 --model provider/model
|
|
132
|
+
logdig backfill 7 --model provider/model --thinking max
|
|
115
133
|
logdig backfill 7 --model default ignore a saved model override for this run
|
|
134
|
+
logdig backfill 7 --thinking default ignore a saved thinking override for this run
|
|
116
135
|
logdig status show coverage for the last 3 calendar days
|
|
117
136
|
logdig status 7 show coverage for seven days
|
|
118
137
|
logdig status all --json output coverage for every saved session as JSON
|
|
@@ -122,9 +141,9 @@ logdig pi-uninstall remove it without deleting notes or summarie
|
|
|
122
141
|
|
|
123
142
|
`--help` works before setup, including `logdig init --help`, `logdig backfill --help`, and `logdig status --help`.
|
|
124
143
|
|
|
125
|
-
`status` is a read-only coverage check. It
|
|
144
|
+
`status` is a read-only coverage check. It counts work blocks: **logged** means the expected entry is present, **stale** means a previous snapshot exists but the entry needs updating, and **new** means no previous block snapshot was found. Summaries are reported separately as reusable or needing summarization. These are block counts, not exact request counts. It selects blocks by conversation activity in your configured timezone, including overnight updates and any missing continuation prerequisites. It never calls a model or writes files; scan warnings make the command exit nonzero so incomplete coverage is clear. JSON retains `sessions` as the result array, with one row per work block and both `sessionId` and `blockId`; totals use `logged`, `stale`, `new`, and `needsSummarizing`.
|
|
126
145
|
|
|
127
|
-
New summaries may incur provider charges. Large
|
|
146
|
+
New summaries may incur provider charges. Large work blocks are summarized in chunks and may need several model requests each. Backfill shows which project it is working on before the model completes. Failed sessions are reported, other sessions continue, and the command exits nonzero if anything needs attention. Fix the issue and rerun the same command; completed summaries are reused, even if a previous attempt failed to write a daily note.
|
|
128
147
|
|
|
129
148
|
Run one backfill at a time against a given journal. The extension prevents overlapping saves within one Pi process, but separate CLI/Pi processes and external note editors are not coordinated. Let an existing save or vault sync finish first.
|
|
130
149
|
|
|
@@ -157,6 +176,10 @@ CLI backfill uses Pi's normal startup model and existing authentication unless y
|
|
|
157
176
|
|
|
158
177
|
`/journal` uses the current Pi model, including registered providers, unless a LogDig override is set. The preview needs no available model or authentication. `doctor` checks the executable but does not validate model authentication; open Pi and run `/login` if a real save reports an authentication problem.
|
|
159
178
|
|
|
179
|
+
Choose a **thinking level** alongside the model in setup's advanced settings, or use `--thinking` for one CLI backfill. Supported values are `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, and `max`; `default` removes the LogDig override. Saved settings use `thinkingLevel`, and `PI_JOURNAL_THINKING` overrides it. This applies to intermediate timeline extraction and final summaries, including `/journal` and automatic capture. Support and effort mapping depend on the model and Pi provider, so `max` is not necessarily a distinct supported tier on every model.
|
|
180
|
+
|
|
181
|
+
More thinking can help separate proposals, failed attempts, and verified outcomes, but can increase latency and token cost. Explicitly enabled thinking allows CLI requests up to 30 minutes each, rather than the usual five; providers may impose their own timeouts. With no LogDig override, CLI backfill keeps Pi's startup thinking policy, while `/journal` keeps its existing provider-default behavior and does **not** inherit the active session's thinking level. Select an explicit level for consistent control in both paths. Changing it causes selected cached summaries to need regeneration; preview first to see the scope.
|
|
182
|
+
|
|
160
183
|
Selected user prompts, assistant conclusions, tool actions, test results, and error excerpts are sent to the chosen model after common credential redaction. System prompts, hidden reasoning, and image payloads are excluded. **Redaction is not a comprehensive secret scanner.** Review your provider's data handling before processing sensitive sessions or enabling automatic capture. Summaries can also contain private project details, so treat your cache and vault accordingly.
|
|
161
184
|
|
|
162
185
|
Model summaries are aids to memory, not proof of completed work. Keep Pi history as the source of truth.
|
package/bin/logdig.js
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { readFile } from "node:fs/promises";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { listJournaledSessions } from "../src/journal.js";
|
|
5
|
+
import { listJournaledSessions, summaryCachePolicy } from "../src/journal.js";
|
|
6
6
|
import { collectSessions, parseBackfillArgument, writeSessions } from "../src/session-runner.js";
|
|
7
7
|
import { createPiModelClient } from "../src/pi-client.js";
|
|
8
8
|
import { spawnPiProcess } from "../src/pi-process.js";
|
|
9
9
|
import { environmentOverrides, loadSettings, validateSettings } from "../src/settings.js";
|
|
10
10
|
import { checkDirectory } from "../src/directories.js";
|
|
11
11
|
import { configureLogDig } from "../src/setup.js";
|
|
12
|
+
import { statusPrefix, statusPrefixWidth } from "../src/cli-status.js";
|
|
12
13
|
|
|
13
14
|
const packageRoot = fileURLToPath(new URL("../", import.meta.url));
|
|
14
15
|
const relativeEntry = path.relative(process.cwd(), fileURLToPath(import.meta.url)).split(path.sep).join("/");
|
|
@@ -82,29 +83,35 @@ async function doctor() {
|
|
|
82
83
|
const settings = await loadSettings();
|
|
83
84
|
let issues = 0;
|
|
84
85
|
let warnings = 0;
|
|
85
|
-
|
|
86
|
-
const overrides = environmentOverrides();
|
|
87
|
-
if (overrides.length) console.log(`Environment overrides: ${overrides.join(", ")}`);
|
|
88
|
-
|
|
89
|
-
for (const [label, directory, writable] of [
|
|
86
|
+
const checks = [
|
|
90
87
|
["Session history", settings.sessionDirectory, false],
|
|
91
88
|
["Summary cache", settings.cacheDirectory, true],
|
|
92
89
|
["Daily notes", settings.dailyDirectory, true],
|
|
93
|
-
]
|
|
90
|
+
];
|
|
91
|
+
const statusWidth = statusPrefixWidth();
|
|
92
|
+
const labelWidth = Math.max(...checks.map(([label]) => label.length), "Pi".length);
|
|
93
|
+
const valueColumn = statusWidth + labelWidth + 2;
|
|
94
|
+
const formatLabel = (label, prefixWidth = statusWidth) => `${label}:${" ".repeat(valueColumn - prefixWidth - label.length - 1)}`;
|
|
95
|
+
|
|
96
|
+
console.log(`${statusPrefix("ok")}${formatLabel("Settings")}${settings.filePath}${settings.configured ? " (saved)" : " (not saved; using defaults/environment)"}`);
|
|
97
|
+
const overrides = environmentOverrides();
|
|
98
|
+
if (overrides.length) console.log(`Environment overrides: ${overrides.join(", ")}`);
|
|
99
|
+
|
|
100
|
+
for (const [label, directory, writable] of checks) {
|
|
94
101
|
if (!directory) {
|
|
95
|
-
console.error(
|
|
102
|
+
console.error(`${statusPrefix("error")}${formatLabel(label)}not configured. Run '${commandName} init' to choose a folder.`);
|
|
96
103
|
issues++;
|
|
97
104
|
continue;
|
|
98
105
|
}
|
|
99
106
|
try {
|
|
100
107
|
const result = await checkDirectory(directory, { writable, allowMissing: writable });
|
|
101
|
-
console.log(
|
|
108
|
+
console.log(`${statusPrefix("ok")}${formatLabel(label)}${directory}${result.exists ? "" : " (will be created on a real backfill)"}`);
|
|
102
109
|
} catch (error) {
|
|
103
110
|
if (label === "Session history" && error.code === "ENOENT") {
|
|
104
|
-
console.warn(
|
|
111
|
+
console.warn(`${statusPrefix("warning")}${formatLabel(label)}${directory} (not found yet). Start a saved Pi session, or choose its location with '${commandName} init'.`);
|
|
105
112
|
warnings++;
|
|
106
113
|
} else {
|
|
107
|
-
console.error(
|
|
114
|
+
console.error(`${statusPrefix("error")}${formatLabel(label)}${directory} (${error.message}). Check folder permissions or choose another folder with '${commandName} init'.`);
|
|
108
115
|
issues++;
|
|
109
116
|
}
|
|
110
117
|
}
|
|
@@ -112,12 +119,13 @@ async function doctor() {
|
|
|
112
119
|
|
|
113
120
|
try {
|
|
114
121
|
const pi = await runPiCommand(settings.piCommand, ["--version", "--no-extensions", "--no-skills", "--no-prompt-templates", "--no-context-files", "--offline"]);
|
|
115
|
-
console.log(
|
|
122
|
+
console.log(`${statusPrefix("ok")}${formatLabel("Pi")}${pi.stdout || pi.stderr || "available"}`);
|
|
116
123
|
} catch (error) {
|
|
117
|
-
console.error(
|
|
124
|
+
console.error(`${statusPrefix("error")}${formatLabel("Pi")}${error.message}. Install Pi or set its executable in '${commandName} init' (advanced settings).`);
|
|
118
125
|
issues++;
|
|
119
126
|
}
|
|
120
127
|
console.log(`\nSummary model: ${settings.model || "Pi startup default"}`);
|
|
128
|
+
console.log(`Thinking: ${settings.thinkingLevel || "Pi startup default"}`);
|
|
121
129
|
console.log("Authentication is checked only when you request a summary. If it fails, open Pi and run /login.");
|
|
122
130
|
if (issues) {
|
|
123
131
|
console.error(`\n${issues} thing${issues === 1 ? "" : "s"} to fix. Address the FIX lines above, then run '${commandName} doctor' again.`);
|
|
@@ -132,6 +140,7 @@ async function backfill(args) {
|
|
|
132
140
|
let daysArgument;
|
|
133
141
|
let dryRun = false;
|
|
134
142
|
let selectedModel = settings.model;
|
|
143
|
+
let selectedThinking = settings.thinkingLevel;
|
|
135
144
|
for (let index = 0; index < args.length; index++) {
|
|
136
145
|
const arg = args[index];
|
|
137
146
|
if (arg === "--dry-run") {
|
|
@@ -141,6 +150,11 @@ async function backfill(args) {
|
|
|
141
150
|
if (!selectedModel || selectedModel.startsWith("-")) throw new Error("--model requires provider/model, or 'default' to use Pi's startup model");
|
|
142
151
|
} else if (arg.startsWith("--model=")) {
|
|
143
152
|
selectedModel = arg.slice("--model=".length);
|
|
153
|
+
} else if (arg === "--thinking") {
|
|
154
|
+
selectedThinking = args[++index];
|
|
155
|
+
if (!selectedThinking || selectedThinking.startsWith("-")) throw new Error("--thinking requires default, off, minimal, low, medium, high, xhigh, or max");
|
|
156
|
+
} else if (arg.startsWith("--thinking=")) {
|
|
157
|
+
selectedThinking = arg.slice("--thinking=".length);
|
|
144
158
|
} else if (!arg.startsWith("-") && daysArgument === undefined) {
|
|
145
159
|
daysArgument = arg;
|
|
146
160
|
} else {
|
|
@@ -148,7 +162,9 @@ async function backfill(args) {
|
|
|
148
162
|
}
|
|
149
163
|
}
|
|
150
164
|
selectedModel = selectedModel?.trim().toLowerCase() === "default" ? undefined : selectedModel;
|
|
151
|
-
|
|
165
|
+
const generationSettings = validateSettings({ ...settings, model: selectedModel, thinkingLevel: selectedThinking });
|
|
166
|
+
selectedModel = generationSettings.model;
|
|
167
|
+
selectedThinking = generationSettings.thinkingLevel;
|
|
152
168
|
const range = parseBackfillArgument(daysArgument);
|
|
153
169
|
requireJournalPaths(settings);
|
|
154
170
|
|
|
@@ -156,9 +172,10 @@ async function backfill(args) {
|
|
|
156
172
|
console.log(`Daily notes: ${settings.dailyDirectory} (${settings.dailyHeader}, ${settings.dailySummary})`);
|
|
157
173
|
console.log(`Summary cache: ${path.join(settings.cacheDirectory, "Sessions")}`);
|
|
158
174
|
console.log(`Model: ${selectedModel || "Pi startup default"}`);
|
|
175
|
+
console.log(`Thinking: ${selectedThinking || "Pi startup default"}`);
|
|
159
176
|
console.log(dryRun
|
|
160
177
|
? "Dry run: no model requests, no file changes, and no folders created."
|
|
161
|
-
: "Only new or changed
|
|
178
|
+
: "Only new or changed work blocks need summarizing. Selected, redacted history is sent to Pi's model; provider charges may apply.");
|
|
162
179
|
const found = await collectSessions({ sessionDirectory: settings.sessionDirectory, timeZone: settings.timeZone, days: range.days });
|
|
163
180
|
console.log(`\nFound ${found.sessions.length} saved Pi session${found.sessions.length === 1 ? "" : "s"}${found.firstDate ? ` to check for ${found.firstDate} through ${found.lastDate}` : ""}.`);
|
|
164
181
|
if (found.sessions.length === 0) {
|
|
@@ -171,16 +188,17 @@ async function backfill(args) {
|
|
|
171
188
|
return;
|
|
172
189
|
}
|
|
173
190
|
|
|
174
|
-
const modelClient = createPiModelClient(
|
|
191
|
+
const modelClient = createPiModelClient(generationSettings);
|
|
175
192
|
const result = await writeSessions(modelClient, found.sessions, settings, {
|
|
176
193
|
...found,
|
|
177
194
|
dryRun,
|
|
178
|
-
onProgress: ({ index, total, sessionId, project, date, time, status, error, phase, sessionPath, dailyPath }) => {
|
|
195
|
+
onProgress: ({ index, total, sessionId, project, date, time, status, error, phase, sessionPath, dailyPath, prerequisite }) => {
|
|
179
196
|
if (["checking", "skipped"].includes(phase)) return;
|
|
180
197
|
console.log(`[${index}/${total}] ${project || sessionId}${date ? ` · ${date} ${time}` : ""}: ${error || status}`);
|
|
181
198
|
if (dryRun && dailyPath) {
|
|
182
199
|
console.log(` Daily note: ${dailyPath}`);
|
|
183
200
|
console.log(` Full summary: ${sessionPath}`);
|
|
201
|
+
if (prerequisite) console.log(" Includes this earlier block to establish the continuation link.");
|
|
184
202
|
}
|
|
185
203
|
},
|
|
186
204
|
});
|
|
@@ -193,8 +211,8 @@ async function backfill(args) {
|
|
|
193
211
|
if (result.sessionsSkipped) console.log(`${result.sessionsSkipped} session${result.sessionsSkipped === 1 ? "" : "s"} skipped (outside the date range or without journalable messages).`);
|
|
194
212
|
if (result.dates.length) {
|
|
195
213
|
console.log(`Daily-note dates: ${result.dates.join(", ")}`);
|
|
196
|
-
if (dryRun && result.summariesCreated) console.log(`${result.summariesCreated}
|
|
197
|
-
if (!result.summariesCreated && !result.errors.length) console.log("These unchanged
|
|
214
|
+
if (dryRun && result.summariesCreated) console.log(`${result.summariesCreated} work block${result.summariesCreated === 1 ? "" : "s"} would need summarizing. Large blocks may require multiple model requests each.`);
|
|
215
|
+
if (!result.summariesCreated && !result.errors.length) console.log("These unchanged work blocks need no model requests.");
|
|
198
216
|
} else if (!result.errors.length) {
|
|
199
217
|
console.log(`Nothing to journal in this range. Try '${commandName} backfill 7 --dry-run' or '${commandName} backfill all --dry-run'.`);
|
|
200
218
|
}
|
|
@@ -204,7 +222,7 @@ async function backfill(args) {
|
|
|
204
222
|
console.warn(dryRun ? "Preview incomplete. Fix the issues above, then preview again." : "Some sessions could not be saved. Successful summaries are cached. Fix the issues above and rerun the same command to retry.");
|
|
205
223
|
process.exitCode = 1;
|
|
206
224
|
} else if (dryRun && result.dates.length) {
|
|
207
|
-
console.log(`\nWhen you're ready: ${commandName} backfill ${range.all ? "all" : range.days}${selectedModel ? ` --model ${selectedModel}` : settings.model ? " --model default" : ""}`);
|
|
225
|
+
console.log(`\nWhen you're ready: ${commandName} backfill ${range.all ? "all" : range.days}${selectedModel ? ` --model ${selectedModel}` : settings.model ? " --model default" : ""}${selectedThinking ? ` --thinking ${selectedThinking}` : settings.thinkingLevel ? " --thinking default" : ""}`);
|
|
208
226
|
}
|
|
209
227
|
}
|
|
210
228
|
|
|
@@ -236,7 +254,7 @@ async function status(args) {
|
|
|
236
254
|
});
|
|
237
255
|
const previousEntries = await listJournaledSessions(settings.cacheDirectory);
|
|
238
256
|
const inspection = await writeSessions(
|
|
239
|
-
{
|
|
257
|
+
{ cachePolicy: summaryCachePolicy(settings) },
|
|
240
258
|
found.sessions,
|
|
241
259
|
settings,
|
|
242
260
|
{ ...found, dryRun: true, journaledEntries: previousEntries },
|
|
@@ -244,26 +262,31 @@ async function status(args) {
|
|
|
244
262
|
const sessions = inspection.sessionResults
|
|
245
263
|
.filter((session) => !["skipped", "error"].includes(session.status))
|
|
246
264
|
.map((session) => {
|
|
247
|
-
const previous = previousEntries.get(session.sessionId) || [];
|
|
248
|
-
const status = session.entryPresent ? "logged" : previous.length ? "
|
|
249
|
-
const
|
|
250
|
-
const sourceChanged = !hasCurrentSource && previous.some((entry) => entry.sourceFingerprint);
|
|
265
|
+
const previous = (previousEntries.get(session.sessionId) || []).filter((entry) => entry.blockId === session.blockId);
|
|
266
|
+
const status = session.entryPresent ? "logged" : previous.length ? "stale" : "new";
|
|
267
|
+
const summaryChanged = previous.length && !previous.some((entry) => entry.cacheFingerprint === session.cacheFingerprint);
|
|
251
268
|
return {
|
|
252
269
|
sessionId: session.sessionId,
|
|
270
|
+
blockId: session.blockId,
|
|
253
271
|
project: session.project,
|
|
254
272
|
date: session.date,
|
|
255
273
|
time: session.time,
|
|
274
|
+
activityDates: session.activityDates,
|
|
275
|
+
prerequisite: session.prerequisite,
|
|
276
|
+
...(session.continuationOf ? { continuationOf: session.continuationOf } : {}),
|
|
256
277
|
status,
|
|
257
|
-
...(status === "
|
|
258
|
-
reason:
|
|
278
|
+
...(status === "stale" ? {
|
|
279
|
+
reason: session.legacyEntry
|
|
280
|
+
? "previous entry needs work-block migration"
|
|
281
|
+
: summaryChanged ? "work-block evidence or generation policy changed" : "previous journal entry is not current",
|
|
259
282
|
} : {}),
|
|
260
|
-
summaryStatus: session.summaryReused ? "
|
|
283
|
+
summaryStatus: session.summaryReused ? "reusable" : "needs-summarizing",
|
|
261
284
|
dailyPath: session.dailyPath,
|
|
262
285
|
};
|
|
263
286
|
});
|
|
264
|
-
const
|
|
265
|
-
const
|
|
266
|
-
const
|
|
287
|
+
const stale = sessions.filter((session) => session.status === "stale").length;
|
|
288
|
+
const newCount = sessions.filter((session) => session.status === "new").length;
|
|
289
|
+
const needsSummarizing = sessions.filter((session) => session.summaryStatus === "needs-summarizing").length;
|
|
267
290
|
const warnings = [...found.warnings, ...inspection.errors];
|
|
268
291
|
const report = {
|
|
269
292
|
timeframe: {
|
|
@@ -277,9 +300,9 @@ async function status(args) {
|
|
|
277
300
|
scanned: found.sessions.length,
|
|
278
301
|
eligible: sessions.length,
|
|
279
302
|
logged: sessions.filter((session) => session.status === "logged").length,
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
303
|
+
stale,
|
|
304
|
+
new: newCount,
|
|
305
|
+
needsSummarizing,
|
|
283
306
|
skipped: inspection.sessionsSkipped,
|
|
284
307
|
errors: inspection.errors.length,
|
|
285
308
|
},
|
|
@@ -291,24 +314,26 @@ async function status(args) {
|
|
|
291
314
|
console.log(JSON.stringify(report, null, 2));
|
|
292
315
|
} else {
|
|
293
316
|
const label = range.all ? "all saved sessions" : `last ${range.days} calendar day${range.days === 1 ? "" : "s"}`;
|
|
294
|
-
console.log(`LogDig status: ${label} (${settings.timeZone})${found.firstDate ? ` · ${found.firstDate} through ${found.lastDate}` : ""}`);
|
|
295
|
-
console.log(`${report.totals.eligible}
|
|
296
|
-
console.log(`Summaries: ${report.totals.eligible -
|
|
297
|
-
const
|
|
317
|
+
console.log(`LogDig status: activity in ${label} (${settings.timeZone})${found.firstDate ? ` · ${found.firstDate} through ${found.lastDate}` : ""}`);
|
|
318
|
+
console.log(`${report.totals.eligible} work block${report.totals.eligible === 1 ? "" : "s"}: ${report.totals.logged} logged, ${stale} stale, ${newCount} new.`);
|
|
319
|
+
console.log(`Summaries: ${report.totals.eligible - needsSummarizing} reusable, ${needsSummarizing} work block${needsSummarizing === 1 ? " needs" : "s need"} summarizing.`);
|
|
320
|
+
const prerequisites = sessions.filter((session) => session.prerequisite).length;
|
|
321
|
+
if (prerequisites) console.log(`Includes ${prerequisites} earlier block${prerequisites === 1 ? "" : "s"} needed for continuation links.`);
|
|
322
|
+
const attention = sessions.filter((session) => session.status !== "logged" || session.summaryStatus !== "reusable");
|
|
298
323
|
if (attention.length) {
|
|
299
324
|
console.log("\nNeeds attention:");
|
|
300
325
|
for (const session of attention) {
|
|
301
|
-
const state = session.status
|
|
302
|
-
const summary = session.summaryStatus === "
|
|
326
|
+
const state = session.status + (session.reason ? `: ${session.reason}` : "");
|
|
327
|
+
const summary = session.summaryStatus === "reusable" ? "summary reusable" : "needs summarizing";
|
|
303
328
|
console.log(`- ${session.date} ${session.time} · ${session.project} · ${state}; ${summary} (${session.sessionId})`);
|
|
304
329
|
console.log(` Daily note: ${session.dailyPath}`);
|
|
305
330
|
}
|
|
306
331
|
} else if (!sessions.length) {
|
|
307
|
-
console.log("No journalable
|
|
332
|
+
console.log("No journalable work blocks found in this timeframe.");
|
|
308
333
|
} else {
|
|
309
334
|
console.log("Everything in this timeframe is current.");
|
|
310
335
|
}
|
|
311
|
-
if (
|
|
336
|
+
if (newCount || stale || needsSummarizing) {
|
|
312
337
|
console.log(`\nTo update the journal: ${commandName} backfill ${range.all ? "all" : range.days}`);
|
|
313
338
|
}
|
|
314
339
|
}
|
|
@@ -333,6 +358,7 @@ function helpText() {
|
|
|
333
358
|
" logdig doctor check paths and Pi without a model request",
|
|
334
359
|
" logdig config show settings and active environment overrides",
|
|
335
360
|
" logdig backfill [N|all] [--dry-run] [--model provider/model|default]",
|
|
361
|
+
" [--thinking default|off|minimal|low|medium|high|xhigh|max]",
|
|
336
362
|
" journal saved sessions (default: last 3 days)",
|
|
337
363
|
" logdig status [N|all] [--json] show journal coverage (default: last 3 days; read-only)",
|
|
338
364
|
" logdig pi-install | pi-uninstall add or remove /journal integration",
|
|
@@ -343,7 +369,8 @@ function helpText() {
|
|
|
343
369
|
"",
|
|
344
370
|
"Real backfill uses Pi's startup model and existing authentication. Tools,",
|
|
345
371
|
"extensions, project context, and session saving are disabled for model requests.",
|
|
346
|
-
"Use --model default to ignore
|
|
372
|
+
"Use --model default or --thinking default to ignore that saved override for one run.",
|
|
373
|
+
"More thinking may improve accuracy, but can increase latency and token cost.",
|
|
347
374
|
"More help: README.md and QUICKSTART.md",
|
|
348
375
|
].join("\n");
|
|
349
376
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const STATUS_WIDTH = 6;
|
|
2
|
+
const ICON_STATUS_WIDTH = 2;
|
|
3
|
+
|
|
4
|
+
const statuses = {
|
|
5
|
+
ok: { text: "OK", icon: "\uf00c", color: "32" },
|
|
6
|
+
warning: { text: "WARN", icon: "\uf071", color: "33" },
|
|
7
|
+
error: { text: "FIX", icon: "\uf00d", color: "31" },
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export { STATUS_WIDTH };
|
|
11
|
+
|
|
12
|
+
function useIcons({
|
|
13
|
+
isTTY = process.stdout.isTTY,
|
|
14
|
+
term = process.env.TERM,
|
|
15
|
+
icons = process.env.LOGDIG_ICONS !== "0",
|
|
16
|
+
} = {}) {
|
|
17
|
+
return isTTY && term !== "dumb" && icons;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function statusPrefixWidth(options) {
|
|
21
|
+
return useIcons(options) ? ICON_STATUS_WIDTH : STATUS_WIDTH;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function statusPrefix(status, options = {}) {
|
|
25
|
+
const marker = statuses[status];
|
|
26
|
+
if (!marker) throw new TypeError(`Unknown CLI status: ${status}`);
|
|
27
|
+
|
|
28
|
+
if (!useIcons(options)) {
|
|
29
|
+
return `${marker.text}${" ".repeat(STATUS_WIDTH - marker.text.length)}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const noColor = options.noColor ?? process.env.NO_COLOR !== undefined;
|
|
33
|
+
const icon = noColor ? marker.icon : `\u001b[${marker.color}m${marker.icon}\u001b[0m`;
|
|
34
|
+
return `${icon} `;
|
|
35
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { JOURNAL_SYSTEM_PROMPT } from "./journal.js";
|
|
3
|
+
import { JOURNAL_SYSTEM_PROMPT, summaryCachePolicy } from "./journal.js";
|
|
4
4
|
import { collectSessions, parseBackfillArgument, writeSessions } from "./session-runner.js";
|
|
5
5
|
import { loadSettings } from "./settings.js";
|
|
6
|
-
import { fingerprintSession } from "./transcript.js";
|
|
7
6
|
|
|
8
7
|
function requireJournalSettings(settings) {
|
|
9
8
|
if (!settings.cacheDirectory) throw new Error("Run 'logdig init' or set PI_JOURNAL_DIR to a LogDig cache folder");
|
|
@@ -23,7 +22,6 @@ function currentSession(ctx) {
|
|
|
23
22
|
entries: ctx.sessionManager.getEntries(),
|
|
24
23
|
sourcePath: ctx.sessionManager.getSessionFile() || "active Pi session",
|
|
25
24
|
};
|
|
26
|
-
session.fingerprint = fingerprintSession(session);
|
|
27
25
|
return session;
|
|
28
26
|
}
|
|
29
27
|
|
|
@@ -37,26 +35,29 @@ function extensionModelClient(ctx, settings) {
|
|
|
37
35
|
|
|
38
36
|
return {
|
|
39
37
|
modelLabel: model ? `${model.provider}/${model.id}` : "Pi current model",
|
|
40
|
-
|
|
38
|
+
cachePolicy: summaryCachePolicy(settings),
|
|
41
39
|
complete: async (prompt) => {
|
|
42
40
|
if (!model) throw new Error("Pi has no active model for journal generation");
|
|
43
41
|
if (!ctx.modelRegistry.hasConfiguredAuth(model)) {
|
|
44
42
|
throw new Error(`No configured authentication for ${model.provider}/${model.id}. Run /login in Pi, then try /journal again`);
|
|
45
43
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
{
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
44
|
+
const context = {
|
|
45
|
+
systemPrompt: JOURNAL_SYSTEM_PROMPT,
|
|
46
|
+
messages: [
|
|
47
|
+
{
|
|
48
|
+
role: "user",
|
|
49
|
+
content: [{ type: "text", text: prompt }],
|
|
50
|
+
timestamp: Date.now(),
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
};
|
|
54
|
+
const options = { sessionId: randomUUID(), cacheRetention: "none", signal: ctx.signal };
|
|
55
|
+
// Preserve existing provider defaults unless LogDig explicitly selects thinking.
|
|
56
|
+
if (settings.thinkingLevel === undefined) return ctx.modelRegistry.complete(model, context, options);
|
|
57
|
+
return ctx.modelRegistry.streamSimple(model, context, {
|
|
58
|
+
...options,
|
|
59
|
+
reasoning: settings.thinkingLevel === "off" ? undefined : settings.thinkingLevel,
|
|
60
|
+
}).result();
|
|
60
61
|
},
|
|
61
62
|
};
|
|
62
63
|
}
|
|
@@ -88,7 +89,7 @@ async function runBackfill(ctx, argument) {
|
|
|
88
89
|
days: range.days,
|
|
89
90
|
currentSession: currentSession(ctx),
|
|
90
91
|
});
|
|
91
|
-
const client = dryRun ? {
|
|
92
|
+
const client = dryRun ? { cachePolicy: summaryCachePolicy(settings) } : extensionModelClient(ctx, settings);
|
|
92
93
|
const result = await writeSessions(client, found.sessions, settings, { ...found, dryRun, onProgress: journalProgress(ctx) });
|
|
93
94
|
const warnings = [...found.warnings, ...result.errors];
|
|
94
95
|
const summaryCount = `${result.summariesCreated} ${result.summariesCreated === 1 ? "summary" : "summaries"}`;
|