logdig 0.1.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.
- package/LICENSE +21 -0
- package/QUICKSTART.md +83 -0
- package/README.md +208 -0
- package/bin/logdig.js +289 -0
- package/package.json +57 -0
- package/src/directories.js +20 -0
- package/src/index.js +190 -0
- package/src/journal.js +457 -0
- package/src/pi-client.js +127 -0
- package/src/pi-process.js +19 -0
- package/src/session-runner.js +185 -0
- package/src/settings.js +155 -0
- package/src/setup.js +122 -0
- package/src/transcript.js +276 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Soleone
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/QUICKSTART.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# LogDig quickstart
|
|
2
|
+
|
|
3
|
+
A small work journal in your existing Obsidian daily notes. Start manually, with one day, and keep automatic capture off until the results feel useful.
|
|
4
|
+
|
|
5
|
+
## 1. Install and configure
|
|
6
|
+
|
|
7
|
+
With Node.js 22.19+ and Pi installed (once the first npm release is published):
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install -g logdig
|
|
11
|
+
logdig init
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Before the first release, run `npm link` from this project folder instead. No build step is needed. To work directly from the checkout without installing, replace `logdig` below with `node ./bin/logdig.js`.
|
|
15
|
+
|
|
16
|
+
In the wizard:
|
|
17
|
+
|
|
18
|
+
- **Daily-notes folder:** choose the folder holding `YYYY-MM-DD.md` notes, for example `My Vault/Daily`.
|
|
19
|
+
- **Summary cache:** choose `My Vault/LogDig` to keep the full summaries visible in Obsidian.
|
|
20
|
+
- **Heading and length:** `# Projects` and `small` are a good first try. Your personal `# Log` stays separate.
|
|
21
|
+
- **Timezone:** check that this is the timezone you journal in.
|
|
22
|
+
- **Advanced settings:** leave them alone unless you use a custom Pi history folder, executable, or model.
|
|
23
|
+
- **Automatic capture:** choose **no** for now.
|
|
24
|
+
- **Pi extension:** optional. The CLI works without it.
|
|
25
|
+
|
|
26
|
+
Press Enter to accept defaults. A typo only re-asks that question. Review the choices before saving. Ctrl+C or declining confirmation leaves your settings unchanged. Setup writes settings only, not summaries or daily notes.
|
|
27
|
+
|
|
28
|
+
## 2. Check and preview
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
logdig doctor
|
|
32
|
+
logdig backfill 1 --dry-run
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The preview shows projects, dates, destination files, cache hits, and which sessions need summarizing. **No model requests, file changes, or new folders.** It does not display transcript excerpts.
|
|
36
|
+
|
|
37
|
+
`1` means today in your chosen timezone. If nothing happened today:
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
logdig backfill 7 --dry-run
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
If no history is found at all, start a saved Pi session or select the right history folder in the wizard's advanced settings.
|
|
44
|
+
|
|
45
|
+
## 3. Make the first entry
|
|
46
|
+
|
|
47
|
+
Once the preview looks right, run the same range without `--dry-run`:
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
logdig backfill 1
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
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.
|
|
54
|
+
|
|
55
|
+
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/<session-id>.md` for the latest session summary.
|
|
56
|
+
|
|
57
|
+
Repeating an unchanged session reuses its summary and does not insert that entry again. Updated sessions or changed summary settings can append a new version. Run only one save at a time against your journal, and let vault sync finish first.
|
|
58
|
+
|
|
59
|
+
## Optional: use `/journal` inside Pi
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
logdig pi-install
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Restart Pi or run `/reload`. Then, inside Pi:
|
|
66
|
+
|
|
67
|
+
```text
|
|
68
|
+
/journal backfill 1 --dry-run
|
|
69
|
+
/journal
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The first command previews today. The second journals the current session using your active Pi model, unless you configured an override. Automatic capture remains off unless you enable it.
|
|
73
|
+
|
|
74
|
+
## Updating
|
|
75
|
+
|
|
76
|
+
After a new npm release:
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
npm install -g logdig@latest
|
|
80
|
+
logdig --version
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
For more commands, privacy details, and troubleshooting, see [README.md](README.md).
|
package/README.md
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# LogDig
|
|
2
|
+
|
|
3
|
+
Remember what you worked on, without writing another status report.
|
|
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 session also gets a cached Markdown note with **Small**, **Medium**, and **Large** summaries, so the details are there when you want them.
|
|
6
|
+
|
|
7
|
+
No build step, runtime dependencies, or separate model credentials. Try a safe preview before sending any history to a model.
|
|
8
|
+
|
|
9
|
+
## Start small
|
|
10
|
+
|
|
11
|
+
You need **Node.js 22.19+**, Pi installed and signed in, and a daily-notes folder using `YYYY-MM-DD.md` filenames. Custom daily-note filename formats are not supported yet.
|
|
12
|
+
|
|
13
|
+
Install globally to make `logdig` available on your PATH (once the first npm release is published):
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
npm install -g logdig
|
|
17
|
+
logdig init
|
|
18
|
+
logdig doctor
|
|
19
|
+
logdig backfill 1 --dry-run
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Working from a checkout before the first release? Run `npm link` once, then use the same commands. No build step is needed. You can also run `node ./bin/logdig.js ...` without installing anything.
|
|
23
|
+
|
|
24
|
+
For a one-off run without a global install, use `npx logdig --help`.
|
|
25
|
+
|
|
26
|
+
Setup explains the choices, keeps defaults on Enter, and re-asks only the question you mistyped. It shows a review before saving. Declining that review or pressing Ctrl+C leaves your settings unchanged. Setup does not summarize sessions or write daily notes.
|
|
27
|
+
|
|
28
|
+
For a comfortable first try:
|
|
29
|
+
|
|
30
|
+
- Choose the folder **inside your vault** holding your daily notes, such as `My Vault/Daily`.
|
|
31
|
+
- Put the summary cache in `My Vault/LogDig` if you want to browse it in Obsidian.
|
|
32
|
+
- Keep the `# Projects` heading and **small** summary unless you prefer otherwise. Your personal `# Log` section stays separate.
|
|
33
|
+
- Check the timezone. It determines the journal date and time.
|
|
34
|
+
- Leave automatic capture **off** until you have tried a manual run. Pi integration is optional.
|
|
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.
|
|
37
|
+
|
|
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
|
+
|
|
40
|
+
When the preview looks right:
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
logdig backfill 1
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
This journals sessions whose last user message was **today in your configured timezone**, not a rolling 24-hour window. If today is quiet, preview seven days instead:
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
logdig backfill 7 --dry-run
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
See [QUICKSTART.md](QUICKSTART.md) for the short, copyable walkthrough.
|
|
53
|
+
|
|
54
|
+
## What you get
|
|
55
|
+
|
|
56
|
+
With `My Vault/Daily` and `My Vault/LogDig` selected:
|
|
57
|
+
|
|
58
|
+
```text
|
|
59
|
+
My Vault/
|
|
60
|
+
├── Daily/
|
|
61
|
+
│ └── YYYY-MM-DD.md your writing, plus project-grouped summaries under # Projects
|
|
62
|
+
└── LogDig/
|
|
63
|
+
├── Sessions/
|
|
64
|
+
│ └── <session-id>.md latest cached summary and provenance
|
|
65
|
+
└── Entries/
|
|
66
|
+
└── <entry-id>.md snapshot of all three layers for a journal entry
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Daily entries live under `# Projects`, grouped by project in first-seen order, with timestamps sorted within each project. For [try](https://github.com/tobi/try)-style directories, a leading `YYYY-MM-DD-` is omitted from the project label: `2026-01-17-learn` becomes `learn`. The source path is unchanged.
|
|
70
|
+
|
|
71
|
+
For example:
|
|
72
|
+
|
|
73
|
+
```markdown
|
|
74
|
+
# Projects
|
|
75
|
+
|
|
76
|
+
## my-project
|
|
77
|
+
|
|
78
|
+
**[[<entry-id>|20:54]]**
|
|
79
|
+
|
|
80
|
+
A short summary.
|
|
81
|
+
|
|
82
|
+
**[[<another-entry-id>|21:19]]**
|
|
83
|
+
|
|
84
|
+
More work on the same project.
|
|
85
|
+
|
|
86
|
+
## another-project
|
|
87
|
+
|
|
88
|
+
**[[<single-entry-id>|23:13]]**: One entry stays compact.
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
A project with one entry puts the timestamp inline at the start of its summary. When a second entry arrives, both timestamps move to separate lines. Summary wording is preserved, including any edits you made.
|
|
92
|
+
|
|
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-session usage 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"`. Session duration is wall-clock time, including idle periods; 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
|
+
The **last user message** supplies the date and time, even if the assistant finishes after midnight. Alternate session branches are included as explorations, not assumed to be the final result.
|
|
96
|
+
|
|
97
|
+
Unchanged sessions reuse their summaries. The identifier in each timestamp link prevents duplicate entries, without HTML comments. Older project-name links and comment-wrapped entries are still recognized. Changing the default does not override existing saved heading preferences; rerun setup to change them. Changing the session, timezone, explicit model, summary level, or heading can append a **new version** rather than replace an earlier journal entry. Linked entry notes keep their original summary even when the latest session cache changes. Handwritten content and summary wording are preserved; an inline timestamp can be expanded when its project gains another entry. Headings inside frontmatter or fenced code are not insertion targets.
|
|
98
|
+
|
|
99
|
+
Missing daily-note and cache folders are created only by a real save. Raw Pi history stays in Pi's storage.
|
|
100
|
+
|
|
101
|
+
## Commands
|
|
102
|
+
|
|
103
|
+
After a global install or `npm link`, use `logdig` from any directory. All commands also work as `node ./bin/logdig.js ...` from the checkout.
|
|
104
|
+
|
|
105
|
+
```text
|
|
106
|
+
logdig init configure paths, summaries, and optional Pi integration
|
|
107
|
+
logdig doctor check paths and Pi, with no model request
|
|
108
|
+
logdig config show effective settings and environment overrides
|
|
109
|
+
logdig --version show the installed version
|
|
110
|
+
logdig backfill journal the last 3 calendar days
|
|
111
|
+
logdig backfill 7 --dry-run preview seven days without changing anything
|
|
112
|
+
logdig backfill all --dry-run preview every discoverable saved session
|
|
113
|
+
logdig backfill 7 journal seven days
|
|
114
|
+
logdig backfill 7 --model provider/model
|
|
115
|
+
logdig backfill 7 --model default ignore a saved model override for this run
|
|
116
|
+
logdig pi-install install the /journal extension
|
|
117
|
+
logdig pi-uninstall remove it without deleting notes or summaries
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`--help` works before setup, including `logdig init --help` and `logdig backfill --help`.
|
|
121
|
+
|
|
122
|
+
New summaries may incur provider charges. Large sessions 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.
|
|
123
|
+
|
|
124
|
+
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.
|
|
125
|
+
|
|
126
|
+
## Inside Pi
|
|
127
|
+
|
|
128
|
+
Install the extension if you did not choose it during setup:
|
|
129
|
+
|
|
130
|
+
```sh
|
|
131
|
+
logdig pi-install
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Restart Pi or run `/reload`. Then, **inside Pi**:
|
|
135
|
+
|
|
136
|
+
```text
|
|
137
|
+
/journal journal the current session
|
|
138
|
+
/journal backfill 1 --dry-run preview today, including the active session
|
|
139
|
+
/journal backfill journal the last 3 calendar days
|
|
140
|
+
/journal backfill 7 journal seven days
|
|
141
|
+
/journal backfill all journal every discoverable session
|
|
142
|
+
/journal help show available commands
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The extension shows progress in Pi's status area, clears it when the operation ends, and tells you where the note and full summaries live. An empty session gets a next step rather than a false “saved” message.
|
|
146
|
+
|
|
147
|
+
Automatic capture is opt-in through `logdig init` or `PI_JOURNAL_AUTO=1`. It catches up recent sessions when Pi shuts down and can delay shutdown while summaries are generated. Capture failures are reported with recovery instructions.
|
|
148
|
+
|
|
149
|
+
## Models and privacy
|
|
150
|
+
|
|
151
|
+
CLI backfill uses Pi's normal startup model and existing authentication unless you configure a `provider/model` override. It runs headless requests with **tools, extensions, skills, prompt templates, project context files, and session saving disabled**. Providers supplied only by extensions are therefore not available to CLI backfill.
|
|
152
|
+
|
|
153
|
+
`/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.
|
|
154
|
+
|
|
155
|
+
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.
|
|
156
|
+
|
|
157
|
+
Model summaries are aids to memory, not proof of completed work. Keep Pi history as the source of truth.
|
|
158
|
+
|
|
159
|
+
## Settings and troubleshooting
|
|
160
|
+
|
|
161
|
+
Settings contain paths and preferences, never provider credentials:
|
|
162
|
+
|
|
163
|
+
- Linux: `$XDG_CONFIG_HOME/logdig/settings.json`, or `~/.config/logdig/settings.json`
|
|
164
|
+
- macOS: `~/Library/Application Support/LogDig/settings.json`
|
|
165
|
+
- Windows: `%APPDATA%\LogDig\settings.json`
|
|
166
|
+
|
|
167
|
+
Set `LOGDIG_CONFIG_PATH` to choose another settings file. Existing `PI_JOURNAL_*` environment variables remain supported and override saved values. Setup, `config`, and `doctor` name active overrides so you can see why a saved preference is not taking effect.
|
|
168
|
+
|
|
169
|
+
- **No saved history found:** create a saved Pi session, or use setup's advanced settings to select your history folder. This is especially useful with a custom Pi session directory.
|
|
170
|
+
- **Notes went to the wrong folder:** run `config`, check environment overrides, then run `init` to choose the daily-notes folder rather than the vault root.
|
|
171
|
+
- **Pi cannot start:** run `doctor`, then use setup's advanced settings to set the executable path.
|
|
172
|
+
- **A summary or note failed:** read the error, fix the path, permissions, authentication, or provider issue, and rerun. Successful cached work is kept.
|
|
173
|
+
- **Want to stop automatic capture:** rerun `init` and choose “no”, or set `PI_JOURNAL_AUTO=0`.
|
|
174
|
+
|
|
175
|
+
## Development
|
|
176
|
+
|
|
177
|
+
```sh
|
|
178
|
+
npm ci
|
|
179
|
+
npm test
|
|
180
|
+
npm link
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Tests use temporary directories and local fake model responses, including CLI subprocess tests and a packed, globally installed CLI smoke test. They do not use your Pi credentials, call a provider, or write to your actual vault. The package test installs only into a temporary prefix, not your real global npm directory.
|
|
184
|
+
|
|
185
|
+
`release-it` is a development dependency only. Version 20 supports the same Node.js minimum as LogDig. The `undici` override keeps its pinned HTTP dependency on a patched 7.x version until release-it updates that dependency. Published installations have no runtime dependencies.
|
|
186
|
+
|
|
187
|
+
## Releases
|
|
188
|
+
|
|
189
|
+
Releases are interactive and run from a clean, committed `main` checkout with its upstream configured. You need npm publishing access and permission to push to `origin`. No GitHub API token is needed; this workflow creates Git tags, not GitHub release pages.
|
|
190
|
+
|
|
191
|
+
The starting version is `0.0.0`, so the first minor release becomes `0.1.0`:
|
|
192
|
+
|
|
193
|
+
```sh
|
|
194
|
+
npm ci
|
|
195
|
+
npm login
|
|
196
|
+
npm run release:dry-run -- minor
|
|
197
|
+
npm run release -- minor
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
The dry run runs tests and checks npm/Git access, but does not bump versions, commit, tag, push, or publish. The real command runs tests, updates `package.json` and `package-lock.json`, publishes to npm, creates a release commit and `vX.Y.Z` tag, and pushes the commit/tag. Follow npm's authentication or two-factor prompts if requested. Future releases can use `npm run release -- patch` or choose the version interactively with `npm run release`.
|
|
201
|
+
|
|
202
|
+
`prepublishOnly` also runs the tests before a direct `npm publish`. Use `npm pack --dry-run` to inspect the published files: CLI, source, docs, manifest, and license only.
|
|
203
|
+
|
|
204
|
+
After a release, update a global installation with `npm install -g logdig@latest`. Uninstall with `npm uninstall -g logdig`; this does not remove settings or notes. If you installed the Pi extension, run `logdig pi-uninstall` before uninstalling the CLI.
|
|
205
|
+
|
|
206
|
+
## License
|
|
207
|
+
|
|
208
|
+
[MIT](LICENSE).
|
package/bin/logdig.js
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { collectSessions, parseBackfillArgument, writeSessions } from "../src/session-runner.js";
|
|
6
|
+
import { createPiModelClient } from "../src/pi-client.js";
|
|
7
|
+
import { spawnPiProcess } from "../src/pi-process.js";
|
|
8
|
+
import { environmentOverrides, loadSettings, validateSettings } from "../src/settings.js";
|
|
9
|
+
import { checkDirectory } from "../src/directories.js";
|
|
10
|
+
import { configureLogDig } from "../src/setup.js";
|
|
11
|
+
|
|
12
|
+
const packageRoot = fileURLToPath(new URL("../", import.meta.url));
|
|
13
|
+
const relativeEntry = path.relative(process.cwd(), fileURLToPath(import.meta.url)).split(path.sep).join("/");
|
|
14
|
+
const entryArgument = relativeEntry.startsWith(".") ? relativeEntry : `./${relativeEntry}`;
|
|
15
|
+
const installedPackage = path.basename(path.dirname(path.resolve(packageRoot))) === "node_modules";
|
|
16
|
+
const commandName = installedPackage || path.basename(process.argv[1] || "") === "logdig"
|
|
17
|
+
? "logdig"
|
|
18
|
+
: `node ${/\s/.test(entryArgument) ? JSON.stringify(entryArgument) : entryArgument}`;
|
|
19
|
+
|
|
20
|
+
function requireJournalPaths(settings) {
|
|
21
|
+
if (!settings.cacheDirectory || !settings.dailyDirectory) {
|
|
22
|
+
throw new Error(`Run '${commandName} init' first, or set PI_JOURNAL_DIR and PI_JOURNAL_DAILY_DIR. Settings file: ${settings.filePath}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function init() {
|
|
27
|
+
const configured = await configureLogDig(await loadSettings(), commandName);
|
|
28
|
+
if (!configured) return;
|
|
29
|
+
const { settings, installExtension } = configured;
|
|
30
|
+
if (installExtension) {
|
|
31
|
+
try {
|
|
32
|
+
await runPiCommand(settings.piCommand, ["install", packageRoot], { inherit: true, timeoutMs: 120_000 });
|
|
33
|
+
console.log("Pi extension installed. Restart Pi or run /reload to load /journal.");
|
|
34
|
+
} catch (error) {
|
|
35
|
+
throw new Error(`Your settings are saved, but the extension could not be installed: ${error.message}. Run '${commandName} pi-install' when Pi is available.`);
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
console.log(`Pi integration is optional. You can add it later with: ${commandName} pi-install`);
|
|
39
|
+
if (settings.autoCapture) console.warn(`Automatic capture needs the Pi extension installed. To enable it, run '${commandName} pi-install'.`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function runPiCommand(command, args, { cwd = process.cwd(), inherit = false, timeoutMs = 10_000 } = {}) {
|
|
44
|
+
return new Promise((resolve, reject) => {
|
|
45
|
+
const child = spawnPiProcess(command, args, {
|
|
46
|
+
cwd,
|
|
47
|
+
stdio: inherit ? "inherit" : ["ignore", "pipe", "pipe"],
|
|
48
|
+
});
|
|
49
|
+
let stdoutText = "";
|
|
50
|
+
let stderrText = "";
|
|
51
|
+
if (!inherit) {
|
|
52
|
+
child.stdout.on("data", (chunk) => { stdoutText += chunk.toString("utf8"); });
|
|
53
|
+
child.stderr.on("data", (chunk) => { stderrText += chunk.toString("utf8"); });
|
|
54
|
+
}
|
|
55
|
+
const timeout = setTimeout(() => {
|
|
56
|
+
child.kill("SIGTERM");
|
|
57
|
+
reject(new Error(`'${command}' did not respond within ${timeoutMs / 1000} seconds`));
|
|
58
|
+
}, timeoutMs);
|
|
59
|
+
child.once("error", (error) => {
|
|
60
|
+
clearTimeout(timeout);
|
|
61
|
+
reject(new Error(`Could not start '${command}': ${error.message}`));
|
|
62
|
+
});
|
|
63
|
+
child.once("close", (code, signal) => {
|
|
64
|
+
clearTimeout(timeout);
|
|
65
|
+
if (code === 0) resolve({ stdout: stdoutText.trim(), stderr: stderrText.trim() });
|
|
66
|
+
else reject(new Error(`${command} failed${signal ? ` (${signal})` : ` with exit code ${code}`}${stderrText ? `: ${stderrText.trim()}` : ""}`));
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function showConfig() {
|
|
72
|
+
const settings = await loadSettings();
|
|
73
|
+
const { filePath, configured, ...values } = settings;
|
|
74
|
+
console.log(`Settings: ${filePath}${configured ? "" : " (not created yet)"}`);
|
|
75
|
+
console.log(JSON.stringify(values, null, 2));
|
|
76
|
+
const overrides = environmentOverrides();
|
|
77
|
+
if (overrides.length) console.log(`Environment overrides: ${overrides.join(", ")}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function doctor() {
|
|
81
|
+
const settings = await loadSettings();
|
|
82
|
+
let issues = 0;
|
|
83
|
+
let warnings = 0;
|
|
84
|
+
console.log("LogDig checkup (no model requests or file changes)\n");
|
|
85
|
+
console.log(`Settings: ${settings.filePath}${settings.configured ? " (saved)" : " (not saved; using defaults/environment)"}`);
|
|
86
|
+
const overrides = environmentOverrides();
|
|
87
|
+
if (overrides.length) console.log(`Environment overrides: ${overrides.join(", ")}`);
|
|
88
|
+
|
|
89
|
+
for (const [label, directory, writable] of [
|
|
90
|
+
["Session history", settings.sessionDirectory, false],
|
|
91
|
+
["Summary cache", settings.cacheDirectory, true],
|
|
92
|
+
["Daily notes", settings.dailyDirectory, true],
|
|
93
|
+
]) {
|
|
94
|
+
if (!directory) {
|
|
95
|
+
console.error(`FIX ${label}: not configured. Run '${commandName} init' to choose a folder.`);
|
|
96
|
+
issues++;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
const result = await checkDirectory(directory, { writable, allowMissing: writable });
|
|
101
|
+
console.log(`OK ${label}: ${directory}${result.exists ? "" : " (will be created on a real backfill)"}`);
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (label === "Session history" && error.code === "ENOENT") {
|
|
104
|
+
console.warn(`WARN ${label}: ${directory} (not found yet). Start a saved Pi session, or choose its location with '${commandName} init'.`);
|
|
105
|
+
warnings++;
|
|
106
|
+
} else {
|
|
107
|
+
console.error(`FIX ${label}: ${directory} (${error.message}). Check folder permissions or choose another folder with '${commandName} init'.`);
|
|
108
|
+
issues++;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
const pi = await runPiCommand(settings.piCommand, ["--version", "--no-extensions", "--no-skills", "--no-prompt-templates", "--no-context-files", "--offline"]);
|
|
115
|
+
console.log(`OK Pi: ${pi.stdout || pi.stderr || "available"}`);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.error(`FIX Pi: ${error.message}. Install Pi or set its executable in '${commandName} init' (advanced settings).`);
|
|
118
|
+
issues++;
|
|
119
|
+
}
|
|
120
|
+
console.log(`\nSummary model: ${settings.model || "Pi startup default"}`);
|
|
121
|
+
console.log("Authentication is checked only when you request a summary. If it fails, open Pi and run /login.");
|
|
122
|
+
if (issues) {
|
|
123
|
+
console.error(`\n${issues} thing${issues === 1 ? "" : "s"} to fix. Address the FIX lines above, then run '${commandName} doctor' again.`);
|
|
124
|
+
process.exitCode = 1;
|
|
125
|
+
} else {
|
|
126
|
+
console.log(`\nChecks passed${warnings ? ` with ${warnings} warning${warnings === 1 ? "" : "s"}` : ""}. Next: ${commandName} backfill 1 --dry-run`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function backfill(args) {
|
|
131
|
+
const settings = await loadSettings();
|
|
132
|
+
let daysArgument;
|
|
133
|
+
let dryRun = false;
|
|
134
|
+
let selectedModel = settings.model;
|
|
135
|
+
for (let index = 0; index < args.length; index++) {
|
|
136
|
+
const arg = args[index];
|
|
137
|
+
if (arg === "--dry-run") {
|
|
138
|
+
dryRun = true;
|
|
139
|
+
} else if (arg === "--model" || arg === "-m") {
|
|
140
|
+
selectedModel = args[++index];
|
|
141
|
+
if (!selectedModel || selectedModel.startsWith("-")) throw new Error("--model requires provider/model, or 'default' to use Pi's startup model");
|
|
142
|
+
} else if (arg.startsWith("--model=")) {
|
|
143
|
+
selectedModel = arg.slice("--model=".length);
|
|
144
|
+
} else if (!arg.startsWith("-") && daysArgument === undefined) {
|
|
145
|
+
daysArgument = arg;
|
|
146
|
+
} else {
|
|
147
|
+
throw new Error(`Unknown backfill argument: ${arg}. Run '${commandName} backfill --help'.`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
selectedModel = selectedModel?.trim().toLowerCase() === "default" ? undefined : selectedModel;
|
|
151
|
+
selectedModel = validateSettings({ ...settings, model: selectedModel }).model;
|
|
152
|
+
const range = parseBackfillArgument(daysArgument);
|
|
153
|
+
requireJournalPaths(settings);
|
|
154
|
+
|
|
155
|
+
console.log(`${dryRun ? "Preview" : "Backfill"}: ${range.all ? "all saved sessions" : `last ${range.days} day${range.days === 1 ? "" : "s"}`} (${settings.timeZone})`);
|
|
156
|
+
console.log(`Daily notes: ${settings.dailyDirectory} (${settings.dailyHeader}, ${settings.dailySummary})`);
|
|
157
|
+
console.log(`Summary cache: ${path.join(settings.cacheDirectory, "Sessions")}`);
|
|
158
|
+
console.log(`Model: ${selectedModel || "Pi startup default"}`);
|
|
159
|
+
console.log(dryRun
|
|
160
|
+
? "Dry run: no model requests, no file changes, and no folders created."
|
|
161
|
+
: "Only new or changed sessions need model requests. Selected, redacted history is sent to Pi's model; provider charges may apply.");
|
|
162
|
+
const found = await collectSessions({ sessionDirectory: settings.sessionDirectory, timeZone: settings.timeZone, days: range.days });
|
|
163
|
+
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
|
+
if (found.sessions.length === 0) {
|
|
165
|
+
console.log(`No saved sessions found. History folder: ${settings.sessionDirectory}`);
|
|
166
|
+
console.log(`If you have saved history elsewhere, choose it in '${commandName} init' (advanced settings). Otherwise, start a Pi session first.`);
|
|
167
|
+
if (found.warnings.length) {
|
|
168
|
+
console.warn(found.warnings.join("\n"));
|
|
169
|
+
process.exitCode = 1;
|
|
170
|
+
}
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const modelClient = createPiModelClient({ ...settings, model: selectedModel });
|
|
175
|
+
const result = await writeSessions(modelClient, found.sessions, settings, {
|
|
176
|
+
...found,
|
|
177
|
+
dryRun,
|
|
178
|
+
onProgress: ({ index, total, sessionId, project, date, time, status, error, phase, sessionPath, dailyPath }) => {
|
|
179
|
+
if (["checking", "skipped"].includes(phase)) return;
|
|
180
|
+
console.log(`[${index}/${total}] ${project || sessionId}${date ? ` · ${date} ${time}` : ""}: ${error || status}`);
|
|
181
|
+
if (dryRun && dailyPath) {
|
|
182
|
+
console.log(` Daily note: ${dailyPath}`);
|
|
183
|
+
console.log(` Full summary: ${sessionPath}`);
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
const summaryCount = `${result.summariesCreated} ${result.summariesCreated === 1 ? "summary" : "summaries"}`;
|
|
188
|
+
const entryCount = `${result.entriesAppended} daily ${result.entriesAppended === 1 ? "entry" : "entries"}`;
|
|
189
|
+
console.log(dryRun
|
|
190
|
+
? `\nWould create ${summaryCount}, reuse ${result.summariesReused}, append ${entryCount}; ${result.entriesSkipped} already present.`
|
|
191
|
+
: `\nSaved: ${summaryCount} created, ${result.summariesReused} reused, ${entryCount} appended; ${result.entriesSkipped} already present.`);
|
|
192
|
+
if (result.sessionsSkipped) console.log(`${result.sessionsSkipped} session${result.sessionsSkipped === 1 ? "" : "s"} skipped (outside the date range or without journalable messages).`);
|
|
193
|
+
if (result.dates.length) {
|
|
194
|
+
console.log(`Daily-note dates: ${result.dates.join(", ")}`);
|
|
195
|
+
if (dryRun && result.summariesCreated) console.log(`${result.summariesCreated} session${result.summariesCreated === 1 ? "" : "s"} would need model requests. Large sessions may require multiple requests each.`);
|
|
196
|
+
if (!result.summariesCreated && !result.errors.length) console.log("These unchanged sessions need no model requests.");
|
|
197
|
+
} else if (!result.errors.length) {
|
|
198
|
+
console.log(`Nothing to journal in this range. Try '${commandName} backfill 7 --dry-run' or '${commandName} backfill all --dry-run'.`);
|
|
199
|
+
}
|
|
200
|
+
const warnings = [...found.warnings, ...result.errors];
|
|
201
|
+
if (warnings.length) {
|
|
202
|
+
console.warn(warnings.join("\n"));
|
|
203
|
+
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.");
|
|
204
|
+
process.exitCode = 1;
|
|
205
|
+
} else if (dryRun && result.dates.length) {
|
|
206
|
+
console.log(`\nWhen you're ready: ${commandName} backfill ${range.all ? "all" : range.days}${selectedModel ? ` --model ${selectedModel}` : settings.model ? " --model default" : ""}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function helpText() {
|
|
211
|
+
return [
|
|
212
|
+
"LogDig: a little work journal from your Pi sessions.",
|
|
213
|
+
"",
|
|
214
|
+
commandName === "logdig" ? "Start here:" : "Start here, from this checkout (no install needed):",
|
|
215
|
+
` ${commandName} init`,
|
|
216
|
+
` ${commandName} doctor`,
|
|
217
|
+
` ${commandName} backfill 1 --dry-run`,
|
|
218
|
+
"",
|
|
219
|
+
"Commands (install with 'npm install -g logdig', or use 'npm link' in a checkout):",
|
|
220
|
+
" logdig init guided setup; nothing is summarized",
|
|
221
|
+
" logdig doctor check paths and Pi without a model request",
|
|
222
|
+
" logdig config show settings and active environment overrides",
|
|
223
|
+
" logdig backfill [N|all] [--dry-run] [--model provider/model|default]",
|
|
224
|
+
" journal saved sessions (default: last 3 days)",
|
|
225
|
+
" logdig pi-install | pi-uninstall add or remove /journal integration",
|
|
226
|
+
" logdig --version show the installed version",
|
|
227
|
+
"",
|
|
228
|
+
"Preview first: --dry-run shows dates, files, and cache hits. It never calls Pi",
|
|
229
|
+
"or writes files. Remove --dry-run when you're ready to summarize.",
|
|
230
|
+
"",
|
|
231
|
+
"Real backfill uses Pi's startup model and existing authentication. Tools,",
|
|
232
|
+
"extensions, project context, and session saving are disabled for model requests.",
|
|
233
|
+
"Use --model default to ignore a saved model override for one run.",
|
|
234
|
+
"More help: README.md and QUICKSTART.md",
|
|
235
|
+
].join("\n");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async function main() {
|
|
239
|
+
const [command = "help", ...args] = process.argv.slice(2);
|
|
240
|
+
const commands = ["init", "config", "doctor", "backfill", "pi-install", "pi-uninstall", "help", "--help", "-h", "--version", "-v"];
|
|
241
|
+
if (!commands.includes(command)) throw new Error(`Unknown command: ${command}. Run '${commandName} help'.`);
|
|
242
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
243
|
+
console.log(helpText());
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
if (command !== "backfill" && args.length) throw new Error(`'${command}' does not accept arguments. Run '${commandName} help'.`);
|
|
247
|
+
switch (command) {
|
|
248
|
+
case "--version":
|
|
249
|
+
case "-v": {
|
|
250
|
+
const { version } = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8"));
|
|
251
|
+
console.log(version);
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
case "init":
|
|
255
|
+
await init();
|
|
256
|
+
break;
|
|
257
|
+
case "config":
|
|
258
|
+
await showConfig();
|
|
259
|
+
break;
|
|
260
|
+
case "doctor":
|
|
261
|
+
await doctor();
|
|
262
|
+
break;
|
|
263
|
+
case "backfill":
|
|
264
|
+
await backfill(args);
|
|
265
|
+
break;
|
|
266
|
+
case "pi-install": {
|
|
267
|
+
const settings = await loadSettings();
|
|
268
|
+
await runPiCommand(settings.piCommand, ["install", packageRoot], { inherit: true, timeoutMs: 120_000 });
|
|
269
|
+
console.log("Pi extension installed. Restart Pi or run /reload to load /journal.");
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
case "pi-uninstall": {
|
|
273
|
+
const settings = await loadSettings();
|
|
274
|
+
await runPiCommand(settings.piCommand, ["remove", packageRoot], { inherit: true, timeoutMs: 120_000 });
|
|
275
|
+
console.log("Pi extension removed.");
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
case "help":
|
|
279
|
+
case "--help":
|
|
280
|
+
case "-h":
|
|
281
|
+
console.log(helpText());
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
main().catch((error) => {
|
|
287
|
+
console.error(`logdig: ${error.message}`);
|
|
288
|
+
process.exitCode = error.code === "LOGDIG_SETUP_CANCELLED" ? 130 : 1;
|
|
289
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "logdig",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Turn saved Pi sessions into a work journal in your Obsidian daily notes.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Soleone",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/Soleone/logdig.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/Soleone/logdig#readme",
|
|
12
|
+
"bugs": "https://github.com/Soleone/logdig/issues",
|
|
13
|
+
"keywords": [
|
|
14
|
+
"pi-package",
|
|
15
|
+
"pi",
|
|
16
|
+
"obsidian",
|
|
17
|
+
"journal",
|
|
18
|
+
"cli"
|
|
19
|
+
],
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public",
|
|
22
|
+
"registry": "https://registry.npmjs.org"
|
|
23
|
+
},
|
|
24
|
+
"type": "module",
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=22.19.0"
|
|
27
|
+
},
|
|
28
|
+
"bin": {
|
|
29
|
+
"logdig": "./bin/logdig.js"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"bin/",
|
|
33
|
+
"src/",
|
|
34
|
+
"README.md",
|
|
35
|
+
"QUICKSTART.md",
|
|
36
|
+
"LICENSE"
|
|
37
|
+
],
|
|
38
|
+
"pi": {
|
|
39
|
+
"extensions": [
|
|
40
|
+
"./src/index.js"
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"test": "node --test",
|
|
45
|
+
"prepublishOnly": "npm test",
|
|
46
|
+
"release": "release-it",
|
|
47
|
+
"release:dry-run": "npm test && release-it --dry-run"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"release-it": "^20.2.1"
|
|
51
|
+
},
|
|
52
|
+
"overrides": {
|
|
53
|
+
"release-it": {
|
|
54
|
+
"undici": "^7.30.0"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|