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.
- package/bin/note.js +144 -28
- 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
|
-
|
|
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,
|
|
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,
|
|
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:
|
|
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
|
-
|
|
260
|
-
|
|
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
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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
|
|
447
|
+
info(`Fetching current content of ${noteLabel}...`);
|
|
332
448
|
|
|
333
449
|
try {
|
|
334
450
|
let notePassword = '';
|
|
335
|
-
let res = await apiFetch(config,
|
|
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,
|
|
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,
|
|
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
|
|
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('
|
|
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
|
|