open-notepad 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/bin/note.js +144 -28
  2. package/package.json +1 -1
package/bin/note.js CHANGED
@@ -147,6 +147,92 @@ async function handleLogin() {
147
147
  const current = await loadConfig();
148
148
 
149
149
  const apiUrl = await ask('Enter Server API URL', current.apiUrl);
150
+
151
+ // Generate a random session code
152
+ const sessionCode = 'cli_' + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
153
+
154
+ // Try browser-based login
155
+ info('Registering CLI session...');
156
+ try {
157
+ const regRes = await fetch(`${apiUrl.replace(/\/$/, '')}/api/cli/session`, {
158
+ method: 'POST',
159
+ headers: { 'Content-Type': 'application/json' },
160
+ body: JSON.stringify({ code: sessionCode })
161
+ });
162
+
163
+ if (!regRes.ok) {
164
+ throw new Error(`Server returned ${regRes.status}`);
165
+ }
166
+
167
+ // Open browser
168
+ const authPageUrl = `${apiUrl.replace(/\/$/, '')}/auth-cli-20260628dontuseforyounote?session=${sessionCode}`;
169
+ info(`Opening browser for authentication...`);
170
+ log(`\n${colors.dim}If browser does not open automatically, visit:${colors.reset}`);
171
+ log(`${colors.bold}${colors.cyan}${authPageUrl}${colors.reset}\n`);
172
+
173
+ // Platform-specific browser open
174
+ const openCmd = process.platform === 'win32' ? 'start'
175
+ : process.platform === 'darwin' ? 'open'
176
+ : 'xdg-open';
177
+ const child = spawn(openCmd, [authPageUrl], { shell: true, stdio: 'ignore', detached: true });
178
+ child.unref();
179
+
180
+ // Poll for completion
181
+ info('Waiting for browser authorization...');
182
+ const maxAttempts = 120; // 2 minutes (polling every 1s)
183
+ let attempts = 0;
184
+
185
+ while (attempts < maxAttempts) {
186
+ await new Promise(r => setTimeout(r, 1500));
187
+ attempts++;
188
+
189
+ try {
190
+ const pollRes = await fetch(`${apiUrl.replace(/\/$/, '')}/api/cli/session/${sessionCode}`);
191
+
192
+ if (pollRes.status === 404) {
193
+ // Session expired or not found
194
+ if (attempts > 10) {
195
+ error('Session expired. Please try again.');
196
+ return;
197
+ }
198
+ continue;
199
+ }
200
+
201
+ if (pollRes.ok) {
202
+ const data = await pollRes.json();
203
+
204
+ if (data.status === 'completed' && data.room_id && data.api_key) {
205
+ clearScreen();
206
+ log(`${colors.bgBlue}${colors.white}${colors.bold} CLI Connected Successfully! ${colors.reset}\n`);
207
+ await saveConfig({
208
+ apiUrl,
209
+ roomId: data.room_id,
210
+ apiKey: data.api_key
211
+ });
212
+ success(`Linked to room: ${colors.bold}${data.room_id}.notepad.web.id${colors.reset}`);
213
+ info('You can now use all CLI commands. Try: note list');
214
+ return;
215
+ }
216
+ }
217
+ } catch (pollErr) {
218
+ // Silently continue polling
219
+ }
220
+
221
+ // Progress indicator
222
+ if (attempts % 4 === 0) {
223
+ process.stdout.write(`\r${colors.dim} Polling... (${attempts}s elapsed, waiting for browser auth)${colors.reset} `);
224
+ }
225
+ }
226
+
227
+ process.stdout.write('\n');
228
+ warning('Browser authorization timed out.');
229
+ info('Falling back to manual configuration...\n');
230
+ } catch (e) {
231
+ warning(`Browser login unavailable: ${e.message}`);
232
+ info('Using manual configuration instead...\n');
233
+ }
234
+
235
+ // Fallback: manual configuration
150
236
  const roomId = await ask('Enter Subdomain/Room ID (e.g. "saya")', current.roomId);
151
237
  const apiKey = await ask('Enter Room API Token/Key', current.apiKey);
152
238
 
@@ -199,31 +285,36 @@ async function handleView(codeArg) {
199
285
  if (!code) return;
200
286
 
201
287
  const { targetRoom, targetCode } = resolveTarget(code, config);
202
- if (!targetRoom) {
203
- warning('No room specified. Please run `note login` or specify room:code.');
204
- return;
205
- }
206
288
 
207
- info(`Loading note ${colors.bold}/${targetRoom}/${targetCode}${colors.reset}...`);
289
+ // Determine if this is a room note or a guest note
290
+ const isGuest = !targetRoom;
291
+ const endpoint = isGuest
292
+ ? `/api/notes/guest/${targetCode}`
293
+ : `/api/notes/room/${targetRoom}/${targetCode}`;
294
+ const noteLabel = isGuest
295
+ ? `guest/${targetCode}`
296
+ : `/${targetRoom}/${targetCode}`;
297
+
298
+ info(`Loading note ${colors.bold}${noteLabel}${colors.reset}...`);
208
299
 
209
300
  try {
210
301
  // Attempt standard fetch with API key
211
- let res = await apiFetch(config, `/api/notes/room/${targetRoom}/${targetCode}`);
302
+ let res = await apiFetch(config, endpoint);
212
303
 
213
304
  if (res.status === 404) {
214
- error(`Note "${targetCode}" not found in room "${targetRoom}".`);
305
+ error(`Note "${targetCode}" not found${isGuest ? ' (guest)' : ` in room "${targetRoom}"`}.`);
215
306
  return;
216
307
  }
217
308
 
218
309
  let noteData = await res.json();
219
310
 
220
- // Check if the note needs a password
221
- if (noteData.has_password && (!noteData.content || res.status === 403)) {
311
+ // Check if the note needs a password (room notes only)
312
+ if (!isGuest && noteData.has_password && (!noteData.content || res.status === 403)) {
222
313
  const pwd = await askPassword('Enter note password');
223
314
 
224
315
  // Note: We bypass API Key header if providing note password for private notes
225
316
  // because backend rejects (is_api = true && private_note = true) outright.
226
- res = await apiFetch(config, `/api/notes/room/${targetRoom}/${targetCode}`, {
317
+ res = await apiFetch(config, endpoint, {
227
318
  headers: { 'x-note-password': pwd },
228
319
  skipApiKey: true
229
320
  });
@@ -241,7 +332,7 @@ async function handleView(codeArg) {
241
332
  }
242
333
 
243
334
  clearScreen();
244
- log(`${colors.bold}${colors.blue}Note: /${targetRoom}/${targetCode}${colors.reset}`);
335
+ log(`${colors.bold}${colors.blue}Note: ${noteLabel}${colors.reset}`);
245
336
  log(`${colors.dim}------------------------------------------------------------${colors.reset}`);
246
337
  log(noteData.content || `${colors.dim}(Empty note)${colors.reset}`);
247
338
  log(`${colors.dim}------------------------------------------------------------${colors.reset}`);
@@ -256,8 +347,30 @@ async function handleCreate(codeArg) {
256
347
  if (!code) return;
257
348
 
258
349
  const { targetRoom, targetCode } = resolveTarget(code, config);
259
- if (!targetRoom) {
260
- warning('No room specified. Please run `note login` or specify room:code.');
350
+ const isGuest = !targetRoom;
351
+
352
+ if (isGuest) {
353
+ info(`Creating guest note ${colors.bold}${targetCode}${colors.reset}...`);
354
+ try {
355
+ let saveRes = await apiFetch(config, `/api/notes/guest/${targetCode}`, {
356
+ method: 'POST',
357
+ body: JSON.stringify({ content: '' })
358
+ });
359
+
360
+ if (!saveRes.ok) {
361
+ error(`Failed to create guest note (status: ${saveRes.status})`);
362
+ return;
363
+ }
364
+
365
+ success(`Guest note "${targetCode}" created successfully!`);
366
+
367
+ const editNow = await ask('Open editor to write content now? (y/n)', 'y');
368
+ if (editNow.toLowerCase().startsWith('y')) {
369
+ await handleEdit(targetCode);
370
+ }
371
+ } catch (e) {
372
+ error(e.message);
373
+ }
261
374
  return;
262
375
  }
263
376
 
@@ -323,33 +436,36 @@ async function handleEdit(codeArg) {
323
436
  if (!code) return;
324
437
 
325
438
  const { targetRoom, targetCode } = resolveTarget(code, config);
326
- if (!targetRoom) {
327
- warning('No room specified. Please run `note login` or specify room:code.');
328
- return;
329
- }
439
+ const isGuest = !targetRoom;
440
+ const endpoint = isGuest
441
+ ? `/api/notes/guest/${targetCode}`
442
+ : `/api/notes/room/${targetRoom}/${targetCode}`;
443
+ const noteLabel = isGuest
444
+ ? `guest/${targetCode}`
445
+ : `/${targetRoom}/${targetCode}`;
330
446
 
331
- info(`Fetching current content of /${targetRoom}/${targetCode}...`);
447
+ info(`Fetching current content of ${noteLabel}...`);
332
448
 
333
449
  try {
334
450
  let notePassword = '';
335
- let res = await apiFetch(config, `/api/notes/room/${targetRoom}/${targetCode}`);
451
+ let res = await apiFetch(config, endpoint);
336
452
 
337
453
  // If it doesn't exist, offer to create it
338
454
  if (res.status === 404) {
339
455
  const create = await ask('Note does not exist. Create it? (y/n)', 'y');
340
456
  if (create.toLowerCase().startsWith('y')) {
341
- await handleCreate(`${targetRoom}:${targetCode}`);
457
+ await handleCreate(isGuest ? targetCode : `${targetRoom}:${targetCode}`);
342
458
  }
343
459
  return;
344
460
  }
345
461
 
346
462
  let noteData = await res.json();
347
463
 
348
- // If locked, request password
349
- if (noteData.has_password && (!noteData.content || res.status === 403)) {
464
+ // If locked, request password (room notes only)
465
+ if (!isGuest && noteData.has_password && (!noteData.content || res.status === 403)) {
350
466
  notePassword = await askPassword('Enter note password to edit');
351
467
 
352
- res = await apiFetch(config, `/api/notes/room/${targetRoom}/${targetCode}`, {
468
+ res = await apiFetch(config, endpoint, {
353
469
  headers: { 'x-note-password': notePassword },
354
470
  skipApiKey: true
355
471
  });
@@ -370,7 +486,7 @@ async function handleEdit(codeArg) {
370
486
 
371
487
  // Create temp file
372
488
  const tempDir = os.tmpdir();
373
- const tempFilePath = path.join(tempDir, `note_${targetRoom}_${targetCode}.txt`);
489
+ const tempFilePath = path.join(tempDir, `note_${isGuest ? 'guest' : targetRoom}_${targetCode}.txt`);
374
490
  await fs.writeFile(tempFilePath, initialContent, 'utf-8');
375
491
 
376
492
  // Open editor
@@ -394,15 +510,15 @@ async function handleEdit(codeArg) {
394
510
  info('Saving changes back to server...');
395
511
 
396
512
  // Save note
397
- const saveRes = await apiFetch(config, `/api/notes/room/${targetRoom}/${targetCode}`, {
513
+ const saveRes = await apiFetch(config, endpoint, {
398
514
  method: 'POST',
399
515
  headers: notePassword ? { 'x-note-password': notePassword } : {},
400
- skipApiKey: !!notePassword, // Skip API key header if note password is used
516
+ skipApiKey: isGuest || !!notePassword, // Skip API key header if note password is used or if guest
401
517
  body: JSON.stringify({ content: updatedContent })
402
518
  });
403
519
 
404
520
  if (saveRes.ok) {
405
- success(`Saved /${targetRoom}/${targetCode} successfully!`);
521
+ success(`Saved ${noteLabel} successfully!`);
406
522
  } else {
407
523
  error(`Failed to save changes. Server returned status: ${saveRes.status}`);
408
524
  }
@@ -422,7 +538,7 @@ async function handleDelete(codeArg) {
422
538
 
423
539
  const { targetRoom, targetCode } = resolveTarget(code, config);
424
540
  if (!targetRoom) {
425
- warning('No room specified. Please run `note login` or specify room:code.');
541
+ warning('Guest notes cannot be deleted via API. You can overwrite them with empty content to clear them.');
426
542
  return;
427
543
  }
428
544
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-notepad",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "CLI tool for notepad.web.id to access, edit, create, and list room notes interactively.",
5
5
  "type": "module",
6
6
  "bin": {