open-notepad 1.0.0 → 1.0.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/bin/note.js +143 -42
- package/package.json +1 -1
package/bin/note.js
CHANGED
|
@@ -87,7 +87,7 @@ async function loadConfig() {
|
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
return {
|
|
90
|
-
apiUrl: 'https://
|
|
90
|
+
apiUrl: 'https://notepad.web.id',
|
|
91
91
|
roomId: '',
|
|
92
92
|
apiKey: ''
|
|
93
93
|
};
|
|
@@ -102,6 +102,17 @@ async function saveConfig(config) {
|
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
function resolveTarget(code, config) {
|
|
106
|
+
let targetRoom = config.roomId;
|
|
107
|
+
let targetCode = code;
|
|
108
|
+
if (code && code.includes(':')) {
|
|
109
|
+
const parts = code.split(':');
|
|
110
|
+
targetRoom = parts[0];
|
|
111
|
+
targetCode = parts[1];
|
|
112
|
+
}
|
|
113
|
+
return { targetRoom, targetCode };
|
|
114
|
+
}
|
|
115
|
+
|
|
105
116
|
// HTTP request wrappers
|
|
106
117
|
async function apiFetch(config, endpoint, options = {}) {
|
|
107
118
|
const url = `${config.apiUrl.replace(/\/$/, '')}${endpoint}`;
|
|
@@ -136,6 +147,92 @@ async function handleLogin() {
|
|
|
136
147
|
const current = await loadConfig();
|
|
137
148
|
|
|
138
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
|
|
139
236
|
const roomId = await ask('Enter Subdomain/Room ID (e.g. "saya")', current.roomId);
|
|
140
237
|
const apiKey = await ask('Enter Room API Token/Key', current.apiKey);
|
|
141
238
|
|
|
@@ -184,22 +281,23 @@ async function handleList() {
|
|
|
184
281
|
|
|
185
282
|
async function handleView(codeArg) {
|
|
186
283
|
const config = await loadConfig();
|
|
187
|
-
if (!config.roomId) {
|
|
188
|
-
warning('Not configured. Please run `note login` first.');
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
284
|
const code = codeArg || await ask('Enter note code to view');
|
|
193
285
|
if (!code) return;
|
|
194
286
|
|
|
195
|
-
|
|
287
|
+
const { targetRoom, targetCode } = resolveTarget(code, config);
|
|
288
|
+
if (!targetRoom) {
|
|
289
|
+
warning('No room specified. Please run `note login` or specify room:code.');
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
info(`Loading note ${colors.bold}/${targetRoom}/${targetCode}${colors.reset}...`);
|
|
196
294
|
|
|
197
295
|
try {
|
|
198
296
|
// Attempt standard fetch with API key
|
|
199
|
-
let res = await apiFetch(config, `/api/notes/room/${
|
|
297
|
+
let res = await apiFetch(config, `/api/notes/room/${targetRoom}/${targetCode}`);
|
|
200
298
|
|
|
201
299
|
if (res.status === 404) {
|
|
202
|
-
error(`Note "${
|
|
300
|
+
error(`Note "${targetCode}" not found in room "${targetRoom}".`);
|
|
203
301
|
return;
|
|
204
302
|
}
|
|
205
303
|
|
|
@@ -211,7 +309,7 @@ async function handleView(codeArg) {
|
|
|
211
309
|
|
|
212
310
|
// Note: We bypass API Key header if providing note password for private notes
|
|
213
311
|
// because backend rejects (is_api = true && private_note = true) outright.
|
|
214
|
-
res = await apiFetch(config, `/api/notes/room/${
|
|
312
|
+
res = await apiFetch(config, `/api/notes/room/${targetRoom}/${targetCode}`, {
|
|
215
313
|
headers: { 'x-note-password': pwd },
|
|
216
314
|
skipApiKey: true
|
|
217
315
|
});
|
|
@@ -229,7 +327,7 @@ async function handleView(codeArg) {
|
|
|
229
327
|
}
|
|
230
328
|
|
|
231
329
|
clearScreen();
|
|
232
|
-
log(`${colors.bold}${colors.blue}Note: /${
|
|
330
|
+
log(`${colors.bold}${colors.blue}Note: /${targetRoom}/${targetCode}${colors.reset}`);
|
|
233
331
|
log(`${colors.dim}------------------------------------------------------------${colors.reset}`);
|
|
234
332
|
log(noteData.content || `${colors.dim}(Empty note)${colors.reset}`);
|
|
235
333
|
log(`${colors.dim}------------------------------------------------------------${colors.reset}`);
|
|
@@ -240,14 +338,15 @@ async function handleView(codeArg) {
|
|
|
240
338
|
|
|
241
339
|
async function handleCreate(codeArg) {
|
|
242
340
|
const config = await loadConfig();
|
|
243
|
-
if (!config.roomId) {
|
|
244
|
-
warning('Not configured. Please run `note login` first.');
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
341
|
const code = codeArg || await ask('Enter new note code');
|
|
249
342
|
if (!code) return;
|
|
250
343
|
|
|
344
|
+
const { targetRoom, targetCode } = resolveTarget(code, config);
|
|
345
|
+
if (!targetRoom) {
|
|
346
|
+
warning('No room specified. Please run `note login` or specify room:code.');
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
|
|
251
350
|
const isPublicStr = await ask('Make note public? (y/n)', 'y');
|
|
252
351
|
const isPublic = isPublicStr.toLowerCase().startsWith('y');
|
|
253
352
|
|
|
@@ -265,11 +364,11 @@ async function handleCreate(codeArg) {
|
|
|
265
364
|
}
|
|
266
365
|
}
|
|
267
366
|
|
|
268
|
-
info(`Creating note ${colors.bold}/${
|
|
367
|
+
info(`Creating note ${colors.bold}/${targetRoom}/${targetCode}${colors.reset}...`);
|
|
269
368
|
|
|
270
369
|
try {
|
|
271
370
|
// 1. Create/Save note with empty content (must send API key)
|
|
272
|
-
let saveRes = await apiFetch(config, `/api/notes/room/${
|
|
371
|
+
let saveRes = await apiFetch(config, `/api/notes/room/${targetRoom}/${targetCode}`, {
|
|
273
372
|
method: 'POST',
|
|
274
373
|
body: JSON.stringify({ content: '' })
|
|
275
374
|
});
|
|
@@ -280,7 +379,7 @@ async function handleCreate(codeArg) {
|
|
|
280
379
|
}
|
|
281
380
|
|
|
282
381
|
// 2. Set visibility and password (calls settings endpoint)
|
|
283
|
-
const settingsRes = await apiFetch(config, `/api/notes/room/${
|
|
382
|
+
const settingsRes = await apiFetch(config, `/api/notes/room/${targetRoom}/${targetCode}/settings`, {
|
|
284
383
|
method: 'POST',
|
|
285
384
|
body: JSON.stringify({
|
|
286
385
|
is_public: isPublic,
|
|
@@ -293,11 +392,11 @@ async function handleCreate(codeArg) {
|
|
|
293
392
|
return;
|
|
294
393
|
}
|
|
295
394
|
|
|
296
|
-
success(`Note "${
|
|
395
|
+
success(`Note "${targetCode}" created successfully in room "${targetRoom}"!`);
|
|
297
396
|
|
|
298
397
|
const editNow = await ask('Open editor to write content now? (y/n)', 'y');
|
|
299
398
|
if (editNow.toLowerCase().startsWith('y')) {
|
|
300
|
-
await handleEdit(
|
|
399
|
+
await handleEdit(`${targetRoom}:${targetCode}`);
|
|
301
400
|
}
|
|
302
401
|
} catch (e) {
|
|
303
402
|
error(e.message);
|
|
@@ -306,25 +405,26 @@ async function handleCreate(codeArg) {
|
|
|
306
405
|
|
|
307
406
|
async function handleEdit(codeArg) {
|
|
308
407
|
const config = await loadConfig();
|
|
309
|
-
if (!config.roomId) {
|
|
310
|
-
warning('Not configured. Please run `note login` first.');
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
408
|
const code = codeArg || await ask('Enter note code to edit');
|
|
315
409
|
if (!code) return;
|
|
316
410
|
|
|
317
|
-
|
|
411
|
+
const { targetRoom, targetCode } = resolveTarget(code, config);
|
|
412
|
+
if (!targetRoom) {
|
|
413
|
+
warning('No room specified. Please run `note login` or specify room:code.');
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
info(`Fetching current content of /${targetRoom}/${targetCode}...`);
|
|
318
418
|
|
|
319
419
|
try {
|
|
320
420
|
let notePassword = '';
|
|
321
|
-
let res = await apiFetch(config, `/api/notes/room/${
|
|
421
|
+
let res = await apiFetch(config, `/api/notes/room/${targetRoom}/${targetCode}`);
|
|
322
422
|
|
|
323
423
|
// If it doesn't exist, offer to create it
|
|
324
424
|
if (res.status === 404) {
|
|
325
425
|
const create = await ask('Note does not exist. Create it? (y/n)', 'y');
|
|
326
426
|
if (create.toLowerCase().startsWith('y')) {
|
|
327
|
-
await handleCreate(
|
|
427
|
+
await handleCreate(`${targetRoom}:${targetCode}`);
|
|
328
428
|
}
|
|
329
429
|
return;
|
|
330
430
|
}
|
|
@@ -335,7 +435,7 @@ async function handleEdit(codeArg) {
|
|
|
335
435
|
if (noteData.has_password && (!noteData.content || res.status === 403)) {
|
|
336
436
|
notePassword = await askPassword('Enter note password to edit');
|
|
337
437
|
|
|
338
|
-
res = await apiFetch(config, `/api/notes/room/${
|
|
438
|
+
res = await apiFetch(config, `/api/notes/room/${targetRoom}/${targetCode}`, {
|
|
339
439
|
headers: { 'x-note-password': notePassword },
|
|
340
440
|
skipApiKey: true
|
|
341
441
|
});
|
|
@@ -356,7 +456,7 @@ async function handleEdit(codeArg) {
|
|
|
356
456
|
|
|
357
457
|
// Create temp file
|
|
358
458
|
const tempDir = os.tmpdir();
|
|
359
|
-
const tempFilePath = path.join(tempDir, `note_${
|
|
459
|
+
const tempFilePath = path.join(tempDir, `note_${targetRoom}_${targetCode}.txt`);
|
|
360
460
|
await fs.writeFile(tempFilePath, initialContent, 'utf-8');
|
|
361
461
|
|
|
362
462
|
// Open editor
|
|
@@ -380,7 +480,7 @@ async function handleEdit(codeArg) {
|
|
|
380
480
|
info('Saving changes back to server...');
|
|
381
481
|
|
|
382
482
|
// Save note
|
|
383
|
-
const saveRes = await apiFetch(config, `/api/notes/room/${
|
|
483
|
+
const saveRes = await apiFetch(config, `/api/notes/room/${targetRoom}/${targetCode}`, {
|
|
384
484
|
method: 'POST',
|
|
385
485
|
headers: notePassword ? { 'x-note-password': notePassword } : {},
|
|
386
486
|
skipApiKey: !!notePassword, // Skip API key header if note password is used
|
|
@@ -388,7 +488,7 @@ async function handleEdit(codeArg) {
|
|
|
388
488
|
});
|
|
389
489
|
|
|
390
490
|
if (saveRes.ok) {
|
|
391
|
-
success(`Saved /${
|
|
491
|
+
success(`Saved /${targetRoom}/${targetCode} successfully!`);
|
|
392
492
|
} else {
|
|
393
493
|
error(`Failed to save changes. Server returned status: ${saveRes.status}`);
|
|
394
494
|
}
|
|
@@ -403,28 +503,29 @@ async function handleEdit(codeArg) {
|
|
|
403
503
|
|
|
404
504
|
async function handleDelete(codeArg) {
|
|
405
505
|
const config = await loadConfig();
|
|
406
|
-
if (!config.roomId) {
|
|
407
|
-
warning('Not configured. Please run `note login` first.');
|
|
408
|
-
return;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
506
|
const code = codeArg || await ask('Enter note code to delete');
|
|
412
507
|
if (!code) return;
|
|
413
508
|
|
|
414
|
-
const
|
|
509
|
+
const { targetRoom, targetCode } = resolveTarget(code, config);
|
|
510
|
+
if (!targetRoom) {
|
|
511
|
+
warning('No room specified. Please run `note login` or specify room:code.');
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const confirm = await ask(`Are you sure you want to delete /${targetRoom}/${targetCode}? (y/n)`, 'n');
|
|
415
516
|
if (!confirm.toLowerCase().startsWith('y')) {
|
|
416
517
|
info('Delete canceled.');
|
|
417
518
|
return;
|
|
418
519
|
}
|
|
419
520
|
|
|
420
|
-
info(`Deleting note /${
|
|
521
|
+
info(`Deleting note /${targetRoom}/${targetCode}...`);
|
|
421
522
|
try {
|
|
422
|
-
const res = await apiFetch(config, `/api/rooms/${
|
|
523
|
+
const res = await apiFetch(config, `/api/rooms/${targetRoom}/notes/${targetCode}`, {
|
|
423
524
|
method: 'DELETE'
|
|
424
525
|
});
|
|
425
526
|
|
|
426
527
|
if (res.ok) {
|
|
427
|
-
success(`Note "${
|
|
528
|
+
success(`Note "${targetCode}" deleted successfully from room "${targetRoom}".`);
|
|
428
529
|
} else {
|
|
429
530
|
error(`Failed to delete note. Status: ${res.status}`);
|
|
430
531
|
}
|