saveinme 1.3.4 → 2.0.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/README.md CHANGED
@@ -25,16 +25,26 @@ npm link
25
25
  | Command | What it does |
26
26
  |---|---|
27
27
  | `saveinme` or `sim` | List all your saved notes |
28
+ | `saveinme <title>` | Directly view a note by its title |
29
+ | `saveinme -ui` | Launch the Split-Pane TUI Dashboard (keyboard-driven screen) |
28
30
  | `saveinme -c` | Create a new note or edit an existing one |
29
31
  | `saveinme -c <title>` | Jump straight to editing/creating a note by title |
30
32
  | `saveinme -c <title> -n <notebook>` | Create/edit a note inside a specific notebook |
31
33
  | `saveinme -cl` | Instantly save system clipboard content as a note |
32
34
  | `saveinme -cl <title>` | Save clipboard content directly with a specific title |
33
- | `saveinme -t <notebook>` | Set the default target notebook for new notes |
34
- | `saveinme -v <title>` | View a note in full |
35
- | `saveinme -d <title>` | Delete a note |
35
+ | `saveinme -d` | Set/select the default target notebook for new notes (opens interactive list) |
36
+ | `saveinme -d <notebook>` | Set the default target notebook directly |
37
+ | `saveinme -v` | View a note (opens interactive select menu) |
38
+ | `saveinme -v <title>` | View a specific note in full |
39
+ | `saveinme -rm` | Remove notes (opens interactive multi-select checkbox menu) |
40
+ | `saveinme -rm <title>` | Delete a specific note |
36
41
  | `saveinme -p <title>` | Toggle pin (pinned notes appear at the top) |
37
42
  | `saveinme -f <term>` | Search by title, content, tags, or category |
43
+ | `saveinme -ai` | Start an interactive AI chat session with Gemini |
44
+ | `saveinme -ai <query>` | Query notes database via Gemini AI |
45
+ | `saveinme -ai --setup` | Configure Gemini API key |
46
+ | `saveinme --sync` | Sync notes database push/pull to remote repository |
47
+ | `saveinme --sync --setup` | Configure Git remote sync URL |
38
48
  | `saveinme --path` | Show where your notes are stored |
39
49
  | `saveinme -h` | Help |
40
50
 
@@ -43,11 +53,11 @@ npm link
43
53
  ### Default Notebook Target
44
54
  You can lock the default notebook (category) where your notes go:
45
55
  ```bash
46
- sim -t "Work"
56
+ sim -d "Work"
47
57
  ```
48
58
  Once set, any new note created via `sim -c` or `sim -cl` will default to the `"Work"` notebook automatically.
49
- * View current target: `sim -t`
50
- * Clear default target: `sim -t --clear`
59
+ * View current target / Select from list: `sim -d`
60
+ * Clear default target: `sim -d --clear`
51
61
 
52
62
  ### Override Notebook
53
63
  Send a note to a specific notebook at any time using the `-n` or `--notebook` flag at the end of your command:
@@ -94,6 +104,54 @@ Type `:keep` (when editing) to keep the existing content unchanged.
94
104
 
95
105
  Works on **Windows** (PowerShell / CMD) and **Linux/macOS** — no external editor opens.
96
106
 
107
+ ## Split-Pane TUI Dashboard (`sim -ui`)
108
+
109
+ Open a visual workspace right inside your terminal:
110
+ ```bash
111
+ sim -ui
112
+ ```
113
+ - **Navigation**: Use Up/Down arrows (or `j`/`k` Vim keys) to browse notes.
114
+ - **Preview**: Note content and details update in real-time on the right.
115
+ - **Controls**:
116
+ - `Enter` — Edit the selected note.
117
+ - `c` — Create a new note.
118
+ - `p` — Pin/unpin the selected note.
119
+ - `d` — Delete the selected note (with confirmation).
120
+ - `/` — Enter search mode (type search terms, `Enter` to filter, `Esc` to clear).
121
+ - `q` or `Esc` — Quit dashboard.
122
+
123
+ ## AI Copilot (Gemini / Ollama) (`sim -ai`)
124
+
125
+ Query your notes using Google's Gemini AI (cloud-based) or Ollama (completely local and offline):
126
+
127
+ ```bash
128
+ # Initial Setup / Reset (prompts you to choose between Gemini or local Ollama)
129
+ sim -ai --setup
130
+ sim -ai --reset
131
+
132
+ # Ask questions directly based on your saved notes
133
+ sim -ai "which server port was mentioned in the api note?"
134
+ sim -ai "summarize the highlights of the book list note"
135
+
136
+ # Start an interactive chat session
137
+ sim -ai
138
+ ```
139
+ - **Gemini**: Setup prompts for a Google AI Studio API key (free tier available).
140
+ - **Ollama**: Setup prompts for your local server endpoint (default: `http://localhost:11434`) and local model name (default: `llama3`). Ensure you have downloaded the model first (e.g. run `ollama run llama3` in your terminal).
141
+
142
+ ## Git Auto-Sync (`sim --sync`)
143
+
144
+ Sync your notes across multiple devices using a private Git repository:
145
+ ```bash
146
+ # Setup remote URL (SSH preferred)
147
+ sim --sync --setup
148
+
149
+ # Manually trigger a full push/pull sync
150
+ sim --sync
151
+ ```
152
+ Once configured, `saveinme` will **automatically run background syncs** when you save or delete notes, keeping everything synced silently!
153
+ *Note: Config files (such as those storing secret API keys) are automatically ignored by Git to protect your security.*
154
+
97
155
  ## Storage
98
156
 
99
157
  Notes are stored permanently at:
package/bin/saveinme.js CHANGED
@@ -19,7 +19,10 @@ import {
19
19
  showHelp,
20
20
  } from '../src/display.js';
21
21
 
22
- import { openEditor, saveFromClipboard, savePipedInput } from '../src/editor.js';
22
+ import { openEditor, saveFromClipboard, savePipedInput, interactiveGroupDelete, interactiveSetDefaultNotebook, interactiveViewNote } from '../src/editor.js';
23
+ import { syncNotes, triggerAutoSync } from '../src/sync.js';
24
+ import { queryAI, chatAI, setupApiKey } from '../src/ai.js';
25
+ import { launchTUI } from '../src/tui.js';
23
26
 
24
27
  // ─── Parse args ───────────────────────────────────────────────────────────────
25
28
 
@@ -95,7 +98,9 @@ async function main() {
95
98
  break;
96
99
  }
97
100
 
98
- // ── Target default notebook ────────────────────────────────────────────
101
+ // ── Default target notebook ────────────────────────────────────────────
102
+ case '-d':
103
+ case '--default':
99
104
  case '-t':
100
105
  case '--target': {
101
106
  if (rest) {
@@ -107,12 +112,7 @@ async function main() {
107
112
  showSuccess(`Target notebook set to "${rest}".\n New notes will default to this notebook.`);
108
113
  }
109
114
  } else {
110
- const current = getDefaultNotebook();
111
- if (current) {
112
- showInfo(`Current default notebook is "${current}".\n Change it with: sim -t <notebook>\n Clear it with: sim -t --clear`);
113
- } else {
114
- showInfo('No default notebook set. New notes will be uncategorized.\n Set one with: sim -t <notebook>');
115
- }
115
+ await interactiveSetDefaultNotebook();
116
116
  }
117
117
  break;
118
118
  }
@@ -120,24 +120,57 @@ async function main() {
120
120
  // ── View ───────────────────────────────────────────────────────────────
121
121
  case '-v':
122
122
  case '--view': {
123
- if (!rest) { showError('Provide a title: saveinme -v <title>'); break; }
124
- const note = getNoteByTitle(rest);
125
- if (!note) showError(`No note found with title "${rest}".`);
126
- else showNote(note);
123
+ if (!rest) {
124
+ await interactiveViewNote();
125
+ } else {
126
+ const note = getNoteByTitle(rest);
127
+ if (!note) showError(`No note found with title "${rest}".`);
128
+ else showNote(note);
129
+ }
127
130
  break;
128
131
  }
129
132
 
130
- // ── Delete ─────────────────────────────────────────────────────────────
131
- case '-d':
133
+ // ── Remove / Delete ────────────────────────────────────────────────────
134
+ case '-rm':
135
+ case '--remove':
132
136
  case '--delete': {
133
- if (!rest) { showError('Provide a title: saveinme -d <title>'); break; }
134
- const note = getNoteByTitle(rest);
135
- if (!note) {
136
- showError(`No note found with title "${rest}".`);
137
+ if (!rest) {
138
+ await interactiveGroupDelete();
139
+ triggerAutoSync();
137
140
  } else {
138
- const ok = deleteNoteById(note.id);
139
- if (ok) showSuccess(`Note "${rest}" deleted.`);
140
- else showError('Delete failed.');
141
+ const titles = argv.slice(1);
142
+ const singleTitle = titles.join(' ').trim();
143
+ const note = getNoteByTitle(singleTitle);
144
+ if (note) {
145
+ const ok = deleteNoteById(note.id);
146
+ if (ok) {
147
+ showSuccess(`Note "${note.title}" deleted.`);
148
+ triggerAutoSync();
149
+ } else {
150
+ showError(`Delete failed for "${note.title}".`);
151
+ }
152
+ } else if (titles.length > 1) {
153
+ let deletedAny = false;
154
+ for (const t of titles) {
155
+ const n = getNoteByTitle(t);
156
+ if (n) {
157
+ const ok = deleteNoteById(n.id);
158
+ if (ok) {
159
+ showSuccess(`Note "${n.title}" deleted.`);
160
+ deletedAny = true;
161
+ } else {
162
+ showError(`Delete failed for "${n.title}".`);
163
+ }
164
+ } else {
165
+ showError(`No note found with title "${t}".`);
166
+ }
167
+ }
168
+ if (deletedAny) {
169
+ triggerAutoSync();
170
+ }
171
+ } else {
172
+ showError(`No note found with title "${singleTitle}".`);
173
+ }
141
174
  }
142
175
  break;
143
176
  }
@@ -152,6 +185,7 @@ async function main() {
152
185
  } else {
153
186
  const nowPinned = togglePin(note.id);
154
187
  showSuccess(`Note "${rest}" ${nowPinned ? '📌 pinned' : 'unpinned'}.`);
188
+ triggerAutoSync();
155
189
  }
156
190
  break;
157
191
  }
@@ -166,6 +200,36 @@ async function main() {
166
200
  else showNotesList(results, `Search results for "${rest}"`);
167
201
  break;
168
202
  }
203
+ // ── TUI Dashboard ──────────────────────────────────────────────────────
204
+ case '-ui':
205
+ case '--ui':
206
+ case '--tui': {
207
+ launchTUI();
208
+ break;
209
+ }
210
+
211
+ // ── Git Sync ───────────────────────────────────────────────────────────
212
+ case '--sync': {
213
+ if (rest === '--setup' || rest === 'setup') {
214
+ await syncNotes(true);
215
+ } else {
216
+ await syncNotes();
217
+ }
218
+ break;
219
+ }
220
+
221
+ // ── AI Copilot ─────────────────────────────────────────────────────────
222
+ case '-ai':
223
+ case '--ai': {
224
+ if (rest === '--setup' || rest === 'setup' || rest === '-reset' || rest === '--reset' || rest === 'reset') {
225
+ await setupApiKey();
226
+ } else if (!rest) {
227
+ await chatAI();
228
+ } else {
229
+ await queryAI(rest);
230
+ }
231
+ break;
232
+ }
169
233
 
170
234
  // ── Storage path ───────────────────────────────────────────────────────
171
235
  case '--path': {
@@ -181,10 +245,21 @@ async function main() {
181
245
  break;
182
246
  }
183
247
 
184
- // ── Unknown ────────────────────────────────────────────────────────────
248
+ // ── Unknown / Direct View ──────────────────────────────────────────────
185
249
  default: {
186
- showError(`Unknown flag "${flag}". Run saveinme -h for help.`);
187
- process.exitCode = 1;
250
+ if (flag && flag.startsWith('-')) {
251
+ showError(`Unknown flag "${flag}". Run saveinme -h for help.`);
252
+ process.exitCode = 1;
253
+ } else {
254
+ const fullTitle = argv.join(' ').trim();
255
+ const note = getNoteByTitle(fullTitle);
256
+ if (note) {
257
+ showNote(note);
258
+ } else {
259
+ showError(`No note found with title "${fullTitle}".`);
260
+ process.exitCode = 1;
261
+ }
262
+ }
188
263
  }
189
264
  }
190
265
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "saveinme",
3
- "version": "1.3.4",
3
+ "version": "2.0.0",
4
4
  "description": "Your personal terminal notebook — save anything, permanently.",
5
5
  "type": "module",
6
6
  "main": "./bin/saveinme.js",
@@ -17,6 +17,7 @@
17
17
  "start": "node bin/saveinme.js"
18
18
  },
19
19
  "dependencies": {
20
+ "@google/generative-ai": "^0.24.1",
20
21
  "@inquirer/prompts": "^5.0.0",
21
22
  "boxen": "^7.1.1",
22
23
  "chalk": "^5.3.0",