saveinme 1.0.0 → 1.3.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/README.md CHANGED
@@ -4,19 +4,33 @@
4
4
 
5
5
  ## Install
6
6
 
7
+ To install globally from npm:
8
+ ```bash
9
+ npm install -g saveinme
10
+ ```
11
+
12
+ Or for local development:
7
13
  ```bash
8
14
  cd path/to/saveinme
9
15
  npm install
10
- npm link # makes `saveinme` available globally in any terminal
16
+ npm link
11
17
  ```
12
18
 
19
+ > **Note:** Once installed, both the **`saveinme`** and **`sim`** commands are registered globally and can be used interchangeably in any terminal window!
20
+
13
21
  ## Commands
14
22
 
23
+ > Both `saveinme` and `sim` can be used interchangeably!
24
+
15
25
  | Command | What it does |
16
26
  |---|---|
17
- | `saveinme` | List all your saved notes |
18
- | `saveinme -s` | Create a new note or edit an existing one |
19
- | `saveinme -s <title>` | Jump straight to editing a note by title |
27
+ | `saveinme` or `sim` | List all your saved notes |
28
+ | `saveinme -c` | Create a new note or edit an existing one |
29
+ | `saveinme -c <title>` | Jump straight to editing/creating a note by title |
30
+ | `saveinme -c <title> -n <notebook>` | Create/edit a note inside a specific notebook |
31
+ | `saveinme -cl` | Instantly save system clipboard content as a note |
32
+ | `saveinme -cl <title>` | Save clipboard content directly with a specific title |
33
+ | `saveinme -t <notebook>` | Set the default target notebook for new notes |
20
34
  | `saveinme -v <title>` | View a note in full |
21
35
  | `saveinme -d <title>` | Delete a note |
22
36
  | `saveinme -p <title>` | Toggle pin (pinned notes appear at the top) |
@@ -24,6 +38,34 @@ npm link # makes `saveinme` available globally in any terminal
24
38
  | `saveinme --path` | Show where your notes are stored |
25
39
  | `saveinme -h` | Help |
26
40
 
41
+ ## Notebook Target & Command Piping
42
+
43
+ ### Default Notebook Target
44
+ You can lock the default notebook (category) where your notes go:
45
+ ```bash
46
+ sim -t "Work"
47
+ ```
48
+ 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`
51
+
52
+ ### Override Notebook
53
+ Send a note to a specific notebook at any time using the `-n` or `--notebook` flag at the end of your command:
54
+ ```bash
55
+ sim -c "Todo List" -n "Personal"
56
+ ```
57
+
58
+ ### Stdin Piping
59
+ You can pipe the standard output of any command directly into a note!
60
+ ```bash
61
+ # Save search results directly to a note
62
+ grep -rn "TODO" src/ | sim -c "Code TODOs"
63
+
64
+ # Save wget/curl responses instantly
65
+ curl https://api.github.com/zen | sim -c "Zen Quote" -n "Quotes"
66
+ ```
67
+ The content will be saved instantly with the specified title and target notebook. If no title is specified, it will auto-generate one based on the first line.
68
+
27
69
  ## What gets saved per note
28
70
 
29
71
  - **Title** — a short name for the note
package/bin/saveinme.js CHANGED
@@ -6,6 +6,8 @@ import {
6
6
  togglePin,
7
7
  searchNotes,
8
8
  getDataPath,
9
+ getDefaultNotebook,
10
+ setDefaultNotebook,
9
11
  } from '../src/store.js';
10
12
 
11
13
  import {
@@ -17,14 +19,53 @@ import {
17
19
  showHelp,
18
20
  } from '../src/display.js';
19
21
 
20
- import { openEditor } from '../src/editor.js';
22
+ import { openEditor, saveFromClipboard, savePipedInput } from '../src/editor.js';
21
23
 
22
24
  // ─── Parse args ───────────────────────────────────────────────────────────────
23
25
 
24
26
  const argv = process.argv.slice(2);
27
+
28
+ // Extract --notebook or -n
29
+ let notebookOverride = null;
30
+ const nIdx = argv.findIndex(arg => arg === '-n' || arg === '--notebook');
31
+ if (nIdx !== -1 && nIdx + 1 < argv.length) {
32
+ notebookOverride = argv[nIdx + 1];
33
+ argv.splice(nIdx, 2); // remove from arguments
34
+ }
35
+
25
36
  const flag = argv[0];
26
37
  const rest = argv.slice(1).join(' ').trim();
27
38
 
39
+ // ─── Stdin Piping Support ──────────────────────────────────────────────────────
40
+
41
+ const creationFlags = [undefined, '-c', '--create', '-s', '--save'];
42
+ const isPiped = !process.stdin.isTTY && creationFlags.includes(flag);
43
+
44
+ if (isPiped) {
45
+ // Read piped input
46
+ let inputData = '';
47
+ process.stdin.setEncoding('utf8');
48
+ process.stdin.on('data', chunk => {
49
+ inputData += chunk;
50
+ });
51
+ process.stdin.on('end', async () => {
52
+ // Treat as note creation
53
+ const titleArg = (flag === '-c' || flag === '--create' || flag === '-s' || flag === '--save') ? rest : (flag || '');
54
+ await savePipedInput(inputData, titleArg, notebookOverride);
55
+ process.exit(0);
56
+ });
57
+ } else {
58
+ main().catch(err => {
59
+ // Suppress Ctrl+C noise from inquirer
60
+ if (err?.name === 'ExitPromptError' || err?.message === 'cancelled') {
61
+ process.stdout.write('\n');
62
+ process.exit(0);
63
+ }
64
+ console.error(err);
65
+ process.exit(1);
66
+ });
67
+ }
68
+
28
69
  // ─── Main ─────────────────────────────────────────────────────────────────────
29
70
 
30
71
  async function main() {
@@ -37,10 +78,42 @@ async function main() {
37
78
  break;
38
79
  }
39
80
 
40
- // ── Save / edit ────────────────────────────────────────────────────────
81
+ // ── Create / Edit ──────────────────────────────────────────────────────
82
+ case '-c':
83
+ case '--create':
41
84
  case '-s':
42
85
  case '--save': {
43
- await openEditor(rest || undefined);
86
+ await openEditor(rest || undefined, notebookOverride);
87
+ break;
88
+ }
89
+
90
+ // ── Paste from clipboard ───────────────────────────────────────────────
91
+ case '-cl':
92
+ case '--clip':
93
+ case '--paste': {
94
+ await saveFromClipboard(rest || undefined, notebookOverride);
95
+ break;
96
+ }
97
+
98
+ // ── Target default notebook ────────────────────────────────────────────
99
+ case '-t':
100
+ case '--target': {
101
+ if (rest) {
102
+ if (rest === '--clear' || rest === 'clear') {
103
+ setDefaultNotebook('');
104
+ showSuccess('Default notebook cleared.');
105
+ } else {
106
+ setDefaultNotebook(rest);
107
+ showSuccess(`Target notebook set to "${rest}".\n New notes will default to this notebook.`);
108
+ }
109
+ } 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
+ }
116
+ }
44
117
  break;
45
118
  }
46
119
 
@@ -115,13 +188,3 @@ async function main() {
115
188
  }
116
189
  }
117
190
  }
118
-
119
- main().catch(err => {
120
- // Suppress Ctrl+C / forced-exit noise from inquirer
121
- if (err?.name === 'ExitPromptError' || err?.message === 'cancelled') {
122
- process.stdout.write('\n');
123
- process.exit(0);
124
- }
125
- console.error(err);
126
- process.exit(1);
127
- });
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "saveinme",
3
- "version": "1.0.0",
3
+ "version": "1.3.2",
4
4
  "description": "Your personal terminal notebook — save anything, permanently.",
5
5
  "type": "module",
6
6
  "main": "./bin/saveinme.js",
7
7
  "bin": {
8
- "saveinme": "bin/saveinme.js"
8
+ "saveinme": "bin/saveinme.js",
9
+ "sim": "bin/saveinme.js"
9
10
  },
10
11
  "files": [
11
12
  "bin/",
@@ -0,0 +1,42 @@
1
+ import { execSync } from 'child_process';
2
+ import { platform } from 'os';
3
+
4
+ /**
5
+ * Reads text from the system clipboard cross-platform without external package dependencies.
6
+ * Supports Windows, macOS, and Linux (via xclip/xsel).
7
+ * @returns {string} The text from the clipboard, or empty string if failed/empty.
8
+ */
9
+ export function getClipboardText() {
10
+ const currentPlatform = platform();
11
+ try {
12
+ if (currentPlatform === 'win32') {
13
+ // Get-Clipboard returns string/array. We use raw output formatting.
14
+ const stdout = execSync('powershell.exe -NoProfile -Command "Get-Clipboard"', {
15
+ encoding: 'utf8',
16
+ stdio: ['ignore', 'pipe', 'ignore'],
17
+ });
18
+ return stdout || '';
19
+ } else if (currentPlatform === 'darwin') {
20
+ return execSync('pbpaste', {
21
+ encoding: 'utf8',
22
+ stdio: ['ignore', 'pipe', 'ignore'],
23
+ }) || '';
24
+ } else {
25
+ // Linux/BSD
26
+ try {
27
+ return execSync('xclip -selection clipboard -o', {
28
+ encoding: 'utf8',
29
+ stdio: ['ignore', 'pipe', 'ignore'],
30
+ }) || '';
31
+ } catch {
32
+ return execSync('xsel -ob', {
33
+ encoding: 'utf8',
34
+ stdio: ['ignore', 'pipe', 'ignore'],
35
+ }) || '';
36
+ }
37
+ }
38
+ } catch (e) {
39
+ // If clipboard tools are not installed or command fails
40
+ return '';
41
+ }
42
+ }
package/src/display.js CHANGED
@@ -135,7 +135,8 @@ export function showNotesList(notes, heading = null) {
135
135
  console.log(
136
136
  chalk.dim(
137
137
  `\n ${notes.length} note${notes.length !== 1 ? 's' : ''} saved` +
138
- ` · saveinme -s add/edit` +
138
+ ` · saveinme -c create` +
139
+ ` · saveinme -cl paste clip` +
139
140
  ` · saveinme -v <title> view` +
140
141
  ` · saveinme -f <term> search` +
141
142
  ` · saveinme -h help\n`
@@ -154,7 +155,7 @@ export function showNote(note) {
154
155
  ? note.tags.map(t => chalk.magentaBright(`#${t}`)).join(' ')
155
156
  : null,
156
157
  note.priority
157
- ? `${PRIORITY_COLOR[note.priority] ?? chalk.white}(●) ${note.priority}`
158
+ ? (PRIORITY_COLOR[note.priority] ?? chalk.white)(`(●) ${note.priority}`)
158
159
  : null,
159
160
  note.pinned ? chalk.yellow('📌 pinned') : null,
160
161
  ].filter(Boolean).join(' ');
@@ -230,28 +231,88 @@ export function showInfo(msg) {
230
231
  export function showHelp() {
231
232
  showHeader();
232
233
  console.log(`
233
- ${chalk.bold.cyanBright('Usage:')}
234
-
235
- ${chalk.bold.white('saveinme')} List all saved notes
236
- ${chalk.bold.white('saveinme -s')} Create or edit a note (interactive)
237
- ${chalk.bold.white('saveinme -s')} ${chalk.italic('<title>')} Edit a specific note by title
238
- ${chalk.bold.white('saveinme -v')} ${chalk.italic('<title>')} View a note in full
239
- ${chalk.bold.white('saveinme -d')} ${chalk.italic('<title>')} Delete a note
240
- ${chalk.bold.white('saveinme -p')} ${chalk.italic('<title>')} Toggle pin on a note
241
- ${chalk.bold.white('saveinme -f')} ${chalk.italic('<term>')} Search by title, content, tags, category
242
- ${chalk.bold.white('saveinme -h')} Show this help
243
-
244
- ${chalk.bold.cyanBright('Note fields saved:')}
245
-
246
- ${chalk.cyan('·')} Title ${chalk.cyan('·')} Content (multi-line)
247
- ${chalk.cyan('·')} Category ${chalk.cyan('·')} Tags (multiple)
248
- ${chalk.cyan('·')} Priority ${chalk.cyan('·')} Pinned
249
- ${chalk.cyan('·')} Created date ${chalk.cyan('·')} Updated date
250
- ${chalk.cyan('·')} Word count ${chalk.cyan('·')} Char count
251
-
252
- ${chalk.bold.cyanBright('Storage:')}
253
-
254
- ${chalk.dim('~/.saveinme/notes.json')} ${chalk.dim('(permanent, survives reboots)')}
234
+ ================================================================================
235
+ 🚀 ${chalk.bold.yellow('COMMANDS CHEAT SHEET (QUICK OVERVIEW)')}
236
+ ================================================================================
237
+ ${chalk.dim(' * Note: You can use "saveinme" or the shortcut "sim" interchangeably *')}
238
+
239
+ ${chalk.bold.white('sim')} List all saved notes (pinned first)
240
+ ${chalk.bold.white('sim -c')} [${chalk.italic('title')}] Create/edit a note interactively
241
+ ${chalk.bold.white('sim -cl')} [${chalk.italic('title')}] Instantly save text from your clipboard
242
+ ${chalk.bold.white('sim -t')} [${chalk.italic('notebook')}] Set default target notebook (folder)
243
+ ${chalk.bold.white('sim -v')} ${chalk.italic('<title>')} View a note's full content
244
+ ${chalk.bold.white('sim -d')} ${chalk.italic('<title>')} Delete a note permanently
245
+ ${chalk.bold.white('sim -p')} ${chalk.italic('<title>')} Toggle pin status (stays at top)
246
+ ${chalk.bold.white('sim -f')} ${chalk.italic('<term>')} Search title, content, category, or tags
247
+ ${chalk.bold.white('sim --path')} Show database folder path on disk
248
+ ${chalk.bold.white('sim -h')} Show this detailed help manual
249
+
250
+ ================================================================================
251
+ 📖 ${chalk.bold.yellow('DETAILED EXPLANATIONS & EXAMPLES')}
252
+ ================================================================================
253
+
254
+ ${chalk.bold.cyan('1. Creating and Editing Notes')}
255
+ ↳ Running ${chalk.white('sim -c')} starts the interactive editor. You'll be asked for:
256
+ - Title
257
+ - Content (write line-by-line. Type ${chalk.green(':save')} on a new line to finish)
258
+ - Details (optional: tags, priority, pin status)
259
+
260
+ ${chalk.yellow('Example (Create):')}
261
+ $ sim -c
262
+ ${chalk.yellow('Example (Directly create/edit by title):')}
263
+ $ sim -c "My Todo List"
264
+
265
+ ${chalk.bold.cyan('2. Clipboard Notes')}
266
+ ↳ Automatically copies your clipboard text and saves it ${chalk.bold.green('instantly')} with ${chalk.bold.red('zero prompts')}.
267
+ - Target: Default target notebook, unless overridden with ${chalk.white('-n')}.
268
+ - Title: Uses custom title if provided, otherwise defaults to:
269
+ ${chalk.cyan('Clipboard Note - YYYY-MM-DD HH_MM_SS')}
270
+
271
+ ${chalk.yellow('Example (Save clipboard instantly):')}
272
+ $ sim -cl
273
+ ${chalk.yellow('Example (Save clipboard with custom title):')}
274
+ $ sim -cl "important log snippet"
275
+
276
+ ${chalk.bold.cyan('3. Default Target Notebooks')}
277
+ ↳ Targets allow you to automatically group notes by category (like folder routing).
278
+ - Set active notebook target: ${chalk.white('sim -t "<name>"')}
279
+ - View current active target: ${chalk.white('sim -t')}
280
+ - Clear target default: ${chalk.white('sim -t --clear')}
281
+
282
+ ${chalk.yellow('Example (Set target to "Work"):')}
283
+ $ sim -t "Work"
284
+ $ sim -c "meeting summary" ${chalk.dim('↳ Automatically saved inside notebook "Work"')}
285
+ ${chalk.yellow('Example (Override target for single command using -n):')}
286
+ $ sim -c "groceries" -n "Personal" ${chalk.dim('↳ Bypasses default target, saves to "Personal"')}
287
+
288
+ ${chalk.bold.cyan('4. Piping Command Outputs (Advanced)')}
289
+ ↳ Pipe stdout of any command directly into a note. Saves ${chalk.bold.green('instantly')} with ${chalk.bold.red('zero prompts')}!
290
+ - Target: Default target notebook, unless overridden with ${chalk.white('-n')}.
291
+ - Title: Uses custom title if provided, otherwise defaults to:
292
+ ${chalk.cyan('Piped Note - YYYY-MM-DD HH_MM_SS')}
293
+
294
+ ${chalk.yellow('Example (Save curl/wget responses instantly):')}
295
+ $ curl https://api.github.com/zen | sim -c "Zen Quote" -n "Inspiration"
296
+ ${chalk.yellow('Example (Save tail/history list instantly):')}
297
+ $ history | tail -n 15 | sim -c
298
+ ${chalk.yellow('Example (Log grep search queries instantly):')}
299
+ $ grep -rn "TODO" src/ | sim -c "Project TODOs"
300
+
301
+ ================================================================================
302
+ ✏️ ${chalk.bold.yellow('IN-TERMINAL EDITOR COMMANDS')}
303
+ ================================================================================
304
+ While typing multi-line text inside the terminal editor, type these on a new line:
305
+ ${chalk.green(':save')} Save changes and exit
306
+ ${chalk.red(':cancel')} Discard all changes
307
+ ${chalk.cyan(':keep')} Keep existing note text unchanged (only when editing)
308
+
309
+ ================================================================================
310
+ 💾 ${chalk.bold.yellow('BACKUP & LOCAL DATABASE')}
311
+ ================================================================================
312
+ Your database JSON files are stored permanently under:
313
+ Notes database: ${chalk.cyan('~/.saveinme/notes.json')}
314
+ Config settings: ${chalk.cyan('~/.saveinme/config.json')}
315
+ ================================================================================
255
316
  `);
256
317
  }
257
318
 
package/src/editor.js CHANGED
@@ -1,8 +1,121 @@
1
1
  import { input, select, confirm } from '@inquirer/prompts';
2
2
  import chalk from 'chalk';
3
- import { getAllNotes, saveNote, getNoteByTitle } from './store.js';
4
- import { showHeader, showSuccess, showError } from './display.js';
3
+ import { getAllNotes, saveNote, getNoteByTitle, getDefaultNotebook } from './store.js';
4
+ import { showHeader, showSuccess, showError, showInfo } from './display.js';
5
5
  import { multilineEditor } from './multiline.js';
6
+ import { getClipboardText } from './clipboard.js';
7
+
8
+ // Helper for generating unique, readable default titles
9
+ function getTimestamp() {
10
+ const now = new Date();
11
+ const yyyy = now.getFullYear();
12
+ const mm = String(now.getMonth() + 1).padStart(2, '0');
13
+ const dd = String(now.getDate()).padStart(2, '0');
14
+ const hh = String(now.getHours()).padStart(2, '0');
15
+ const min = String(now.getMinutes()).padStart(2, '0');
16
+ const ss = String(now.getSeconds()).padStart(2, '0');
17
+ return `${yyyy}-${mm}-${dd} ${hh}_${min}_${ss}`;
18
+ }
19
+
20
+ // ─── Save from clipboard directly ─────────────────────────────────────────────
21
+
22
+ /**
23
+ * Reads content directly from system clipboard and saves it as a note.
24
+ * Saves instantly with zero prompts.
25
+ * @param {string|undefined} titleArg Optional title passed via CLI
26
+ * @param {string|null} notebookOverride Target notebook overriding the default setting
27
+ */
28
+ export async function saveFromClipboard(titleArg, notebookOverride) {
29
+ showHeader();
30
+ console.log('');
31
+
32
+ const clipboardContent = getClipboardText().trim();
33
+
34
+ if (!clipboardContent) {
35
+ showError('Clipboard is empty or could not be read.');
36
+ return;
37
+ }
38
+
39
+ // Show a preview of the clipboard text
40
+ console.log(chalk.bold.cyanBright('📋 Clipboard Content Detected:'));
41
+ const lines = clipboardContent.split('\n');
42
+ const previewLines = lines.slice(0, 5);
43
+ console.log(chalk.dim(' ┌──────────────────────────────────────────────────'));
44
+ previewLines.forEach(line => {
45
+ console.log(chalk.dim(` │ ${line.slice(0, 70)}${line.length > 70 ? '...' : ''}`));
46
+ });
47
+ if (lines.length > 5) {
48
+ console.log(chalk.dim(` │ ... and ${lines.length - 5} more lines`));
49
+ }
50
+ console.log(chalk.dim(' └──────────────────────────────────────────────────\n'));
51
+
52
+ // Resolve title
53
+ const title = (titleArg && titleArg.trim()) || `Clipboard Note - ${getTimestamp()}`;
54
+ const existingNote = getNoteByTitle(title);
55
+
56
+ // Resolve notebook target
57
+ const defaultNotebook = getDefaultNotebook();
58
+ const category = (notebookOverride !== null) ? notebookOverride.trim() : (existingNote?.category ?? defaultNotebook);
59
+
60
+ saveNote({
61
+ id: existingNote?.id, // updates if title matches exactly
62
+ title: title.trim(),
63
+ content: clipboardContent,
64
+ category: category.trim(),
65
+ tags: existingNote?.tags ?? [],
66
+ priority: existingNote?.priority ?? 'medium',
67
+ pinned: existingNote?.pinned ?? false,
68
+ });
69
+
70
+ const wc = clipboardContent.split(/\s+/).filter(Boolean).length;
71
+ showSuccess(
72
+ `Clipboard note "${title.trim()}" saved!` +
73
+ (category ? ` [Notebook: ${category.trim()}]` : '') +
74
+ chalk.dim(` (${wc} words)`)
75
+ );
76
+ }
77
+
78
+ // ─── Save piped stdin directly ────────────────────────────────────────────────
79
+
80
+ /**
81
+ * Saves piped/redirected input directly into notes.
82
+ * Saves instantly with zero prompts.
83
+ * @param {string} inputContent The text read from stdin
84
+ * @param {string|undefined} titleArg The title passed as command line argument
85
+ * @param {string|null} notebookOverride Optional target notebook passed via CLI
86
+ */
87
+ export async function savePipedInput(inputContent, titleArg, notebookOverride) {
88
+ const content = inputContent.trim();
89
+ if (!content) {
90
+ showError('Standard input is empty.');
91
+ return;
92
+ }
93
+
94
+ // Resolve notebook target
95
+ const defaultNotebook = getDefaultNotebook();
96
+ const category = (notebookOverride !== null) ? notebookOverride.trim() : defaultNotebook;
97
+
98
+ // Resolve title
99
+ const title = (titleArg && titleArg.trim()) || `Piped Note - ${getTimestamp()}`;
100
+ const existingNote = getNoteByTitle(title);
101
+
102
+ saveNote({
103
+ id: existingNote?.id,
104
+ title: title.trim(),
105
+ content: content,
106
+ category: category,
107
+ tags: existingNote?.tags ?? [],
108
+ priority: existingNote?.priority ?? 'medium',
109
+ pinned: existingNote?.pinned ?? false,
110
+ });
111
+
112
+ const wc = content.split(/\s+/).filter(Boolean).length;
113
+ showSuccess(
114
+ `Saved piped note "${title.trim()}"` +
115
+ (category ? ` to notebook "${category}"` : '') +
116
+ chalk.dim(` (${wc} words)`)
117
+ );
118
+ }
6
119
 
7
120
  // ─── Main editor entry point ──────────────────────────────────────────────────
8
121
 
@@ -10,8 +123,9 @@ import { multilineEditor } from './multiline.js';
10
123
  * Opens the interactive note editor.
11
124
  * Flow: Title → Content → (optional details) → Save
12
125
  * @param {string|undefined} titleArg If provided, jump straight to editing that note.
126
+ * @param {string|null} notebookOverride Optional target notebook passed via CLI
13
127
  */
14
- export async function openEditor(titleArg) {
128
+ export async function openEditor(titleArg, notebookOverride) {
15
129
  showHeader();
16
130
  console.log('');
17
131
 
@@ -59,16 +173,41 @@ export async function openEditor(titleArg) {
59
173
 
60
174
  console.log('');
61
175
 
176
+ // Check if they want to paste from clipboard if clipboard is not empty
177
+ const clipText = getClipboardText().trim();
178
+ let content = null;
179
+ let useClipboard = false;
180
+
181
+ if (clipText && !existingNote) {
182
+ const preview = clipText.slice(0, 45) + (clipText.length > 45 ? '...' : '');
183
+ const choice = await select({
184
+ message: 'How would you like to write the content?',
185
+ choices: [
186
+ { name: `✍️ Write in terminal editor`, value: 'manual' },
187
+ { name: `📋 Paste from Clipboard: "${preview}"`, value: 'clip' },
188
+ ],
189
+ });
190
+ if (choice === 'clip') {
191
+ content = clipText;
192
+ useClipboard = true;
193
+ }
194
+ }
195
+
62
196
  // ── 1. Title ──────────────────────────────────────────────────────────────
197
+ const firstLine = clipText.split('\n')[0].trim().slice(0, 40);
198
+ const defaultTitle = existingNote?.title ?? (titleArg || (useClipboard && firstLine.length > 3 ? firstLine : ''));
199
+
63
200
  const title = await input({
64
201
  message: 'Title:',
65
- default: existingNote?.title ?? (titleArg || ''),
202
+ default: defaultTitle,
66
203
  validate: v => v.trim().length > 0 || 'Title cannot be empty.',
67
204
  });
68
205
 
69
- // ── 2. Content (straight to the editor) ───────────────────────────────────
70
- console.log('');
71
- const content = await multilineEditor(existingNote?.content ?? '');
206
+ // ── 2. Content (straight to the editor if not clipboard) ───────────────────
207
+ if (!useClipboard) {
208
+ console.log('');
209
+ content = await multilineEditor(existingNote?.content ?? '');
210
+ }
72
211
 
73
212
  if (content === null) {
74
213
  showError('Note discarded.');
@@ -92,8 +231,10 @@ export async function openEditor(titleArg) {
92
231
  default: false,
93
232
  });
94
233
 
234
+ // Target notebook/category default
235
+ const defaultNotebook = getDefaultNotebook();
236
+ let category = notebookOverride !== null ? notebookOverride : (existingNote?.category ?? defaultNotebook);
95
237
  let tags = existingNote?.tags ?? [];
96
- let category = existingNote?.category ?? '';
97
238
  let priority = existingNote?.priority ?? 'medium';
98
239
  let pinned = existingNote?.pinned ?? false;
99
240
 
@@ -104,13 +245,13 @@ export async function openEditor(titleArg) {
104
245
  const existingCats = [...new Set(notes.map(n => n.category).filter(Boolean))];
105
246
  if (existingCats.length > 0) {
106
247
  const catChoice = await select({
107
- message: 'Category:',
248
+ message: 'Category (Notebook):',
108
249
  choices: [
109
250
  ...existingCats.map(c => ({ name: c, value: c })),
110
251
  { name: chalk.cyan('+ New category…'), value: '__new__' },
111
252
  { name: chalk.dim('— None'), value: '' },
112
253
  ],
113
- default: existingNote?.category || '',
254
+ default: category || undefined,
114
255
  });
115
256
  category =
116
257
  catChoice === '__new__'
@@ -118,15 +259,15 @@ export async function openEditor(titleArg) {
118
259
  : catChoice;
119
260
  } else {
120
261
  category = await input({
121
- message: 'Category (optional):',
122
- default: existingNote?.category ?? '',
262
+ message: 'Category (Notebook) (optional):',
263
+ default: category,
123
264
  });
124
265
  }
125
266
 
126
267
  // Tags
127
268
  const tagsRaw = await input({
128
269
  message: 'Tags (comma-separated):',
129
- default: (existingNote?.tags ?? []).join(', '),
270
+ default: tags.join(', '),
130
271
  });
131
272
  tags = tagsRaw
132
273
  .split(',')
@@ -141,13 +282,13 @@ export async function openEditor(titleArg) {
141
282
  { name: chalk.yellow('🟡 Medium'), value: 'medium' },
142
283
  { name: chalk.red('🔴 High'), value: 'high' },
143
284
  ],
144
- default: existingNote?.priority ?? 'medium',
285
+ default: priority,
145
286
  });
146
287
 
147
288
  // Pin
148
289
  pinned = await confirm({
149
290
  message: 'Pin this note?',
150
- default: existingNote?.pinned ?? false,
291
+ default: pinned,
151
292
  });
152
293
  }
153
294
 
@@ -165,6 +306,7 @@ export async function openEditor(titleArg) {
165
306
  const wc = finalContent.trim().split(/\s+/).filter(Boolean).length;
166
307
  showSuccess(
167
308
  `Note "${title.trim()}" saved!` +
309
+ (category ? ` [Notebook: ${category.trim()}]` : '') +
168
310
  chalk.dim(` (${wc} words)`)
169
311
  );
170
312
  }
package/src/store.js CHANGED
@@ -105,6 +105,25 @@ export function searchNotes(term) {
105
105
  );
106
106
  }
107
107
 
108
+ const CONFIG_FILE = join(DATA_DIR, 'config.json');
109
+
108
110
  export function getDataPath() {
109
111
  return DATA_FILE;
110
112
  }
113
+
114
+ export function getDefaultNotebook() {
115
+ ensureDir();
116
+ if (!existsSync(CONFIG_FILE)) return '';
117
+ try {
118
+ const config = JSON.parse(readFileSync(CONFIG_FILE, 'utf-8'));
119
+ return config.defaultNotebook ?? '';
120
+ } catch {
121
+ return '';
122
+ }
123
+ }
124
+
125
+ export function setDefaultNotebook(name) {
126
+ ensureDir();
127
+ const config = { defaultNotebook: name.trim() };
128
+ writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8');
129
+ }