open-notepad 1.0.6 → 1.0.8
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 -81
- package/package.json +1 -1
package/bin/note.js
CHANGED
|
@@ -141,101 +141,155 @@ async function apiFetch(config, endpoint, options = {}) {
|
|
|
141
141
|
|
|
142
142
|
// Commands
|
|
143
143
|
async function handleLogin(apiUrlArg) {
|
|
144
|
-
clearScreen();
|
|
145
|
-
log(`${colors.bgBlue}${colors.white}${colors.bold} Configure Notepad CLI ${colors.reset}\n`);
|
|
146
|
-
|
|
147
144
|
const current = await loadConfig();
|
|
148
145
|
const apiUrl = apiUrlArg || current.apiUrl || 'https://notepad.web.id';
|
|
149
146
|
|
|
150
|
-
//
|
|
151
|
-
const
|
|
147
|
+
// Present choices
|
|
148
|
+
const loginOptions = [
|
|
149
|
+
'🌐 Sign in with Google (Browser Redirect)',
|
|
150
|
+
'🔑 Enter API Key (Manual)'
|
|
151
|
+
];
|
|
152
152
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
method: 'POST',
|
|
158
|
-
headers: { 'Content-Type': 'application/json' },
|
|
159
|
-
body: JSON.stringify({ code: sessionCode })
|
|
160
|
-
});
|
|
153
|
+
const titleCallback = () => {
|
|
154
|
+
log(`${colors.bgBlue}${colors.white}${colors.bold} Configure Notepad CLI ${colors.reset}`);
|
|
155
|
+
log(`${colors.dim}Select how you want to authenticate with ${colors.reset}${colors.cyan}${apiUrl}${colors.reset}\n`);
|
|
156
|
+
};
|
|
161
157
|
|
|
162
|
-
|
|
163
|
-
throw new Error(`Server returned ${regRes.status}`);
|
|
164
|
-
}
|
|
158
|
+
const choice = await selectMenuOption(loginOptions, titleCallback);
|
|
165
159
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
: process.platform === 'darwin' ? 'open'
|
|
175
|
-
: 'xdg-open';
|
|
176
|
-
const child = spawn(openCmd, [authPageUrl], { shell: true, stdio: 'ignore', detached: true });
|
|
177
|
-
child.unref();
|
|
178
|
-
|
|
179
|
-
// Poll for completion
|
|
180
|
-
info('Waiting for browser authorization...');
|
|
181
|
-
const maxAttempts = 120; // 2 minutes (polling every 1.5s)
|
|
182
|
-
let attempts = 0;
|
|
160
|
+
if (choice === -1) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (choice === 0) {
|
|
165
|
+
// Google Login (Browser flow)
|
|
166
|
+
clearScreen();
|
|
167
|
+
log(`${colors.bgBlue}${colors.white}${colors.bold} Google Authentication ${colors.reset}\n`);
|
|
183
168
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
attempts++;
|
|
169
|
+
// Generate a random session code
|
|
170
|
+
const sessionCode = 'cli_' + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
|
187
171
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
172
|
+
info('Registering CLI session...');
|
|
173
|
+
try {
|
|
174
|
+
const regRes = await fetch(`${apiUrl.replace(/\/$/, '')}/api/cli/session`, {
|
|
175
|
+
method: 'POST',
|
|
176
|
+
headers: { 'Content-Type': 'application/json' },
|
|
177
|
+
body: JSON.stringify({ code: sessionCode })
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
if (!regRes.ok) {
|
|
181
|
+
throw new Error(`Server returned ${regRes.status}`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Open browser
|
|
185
|
+
const authPageUrl = `${apiUrl.replace(/\/$/, '')}/auth-cli-20260628dontuseforyounote?session=${sessionCode}`;
|
|
186
|
+
info(`Opening browser for authentication...`);
|
|
187
|
+
log(`\n${colors.dim}If browser does not open automatically, visit:${colors.reset}`);
|
|
188
|
+
log(`${colors.bold}${colors.cyan}${authPageUrl}${colors.reset}\n`);
|
|
189
|
+
|
|
190
|
+
// Platform-specific browser open
|
|
191
|
+
const openCmd = process.platform === 'win32' ? 'start'
|
|
192
|
+
: process.platform === 'darwin' ? 'open'
|
|
193
|
+
: 'xdg-open';
|
|
194
|
+
const child = spawn(openCmd, [authPageUrl], { shell: true, stdio: 'ignore', detached: true });
|
|
195
|
+
child.unref();
|
|
196
|
+
|
|
197
|
+
// Poll for completion
|
|
198
|
+
info('Waiting for browser authorization...');
|
|
199
|
+
log(`${colors.dim}Press Esc at any time to cancel and return to menu.${colors.reset}\n`);
|
|
200
|
+
const maxAttempts = 120; // 2 minutes (polling every 1.5s)
|
|
201
|
+
let attempts = 0;
|
|
202
|
+
let cancelled = false;
|
|
203
|
+
|
|
204
|
+
// Listen to Esc keypress during polling
|
|
205
|
+
const hasTTY = process.stdin.isTTY && typeof process.stdin.setRawMode === 'function';
|
|
206
|
+
const onPollKeypress = (str, key) => {
|
|
207
|
+
if (key && (key.name === 'escape' || (key.ctrl && key.name === 'c'))) {
|
|
208
|
+
cancelled = true;
|
|
198
209
|
}
|
|
210
|
+
};
|
|
199
211
|
|
|
200
|
-
|
|
201
|
-
|
|
212
|
+
if (hasTTY) {
|
|
213
|
+
readline.emitKeypressEvents(process.stdin);
|
|
214
|
+
try {
|
|
215
|
+
process.stdin.setRawMode(true);
|
|
216
|
+
} catch (e) {}
|
|
217
|
+
process.stdin.resume();
|
|
218
|
+
process.stdin.on('keypress', onPollKeypress);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
while (attempts < maxAttempts && !cancelled) {
|
|
222
|
+
await new Promise(r => setTimeout(r, 1500));
|
|
223
|
+
if (cancelled) break;
|
|
224
|
+
attempts++;
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
const pollRes = await fetch(`${apiUrl.replace(/\/$/, '')}/api/cli/session/${sessionCode}`);
|
|
202
228
|
|
|
203
|
-
if (
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
229
|
+
if (pollRes.status === 404) {
|
|
230
|
+
if (attempts > 10) {
|
|
231
|
+
error('Session expired. Please try again.');
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (pollRes.ok) {
|
|
238
|
+
const data = await pollRes.json();
|
|
239
|
+
|
|
240
|
+
if (data.status === 'completed' && data.room_id && data.api_key) {
|
|
241
|
+
clearScreen();
|
|
242
|
+
log(`${colors.bgBlue}${colors.white}${colors.bold} CLI Connected Successfully! ${colors.reset}\n`);
|
|
243
|
+
await saveConfig({
|
|
244
|
+
apiUrl,
|
|
245
|
+
roomId: data.room_id,
|
|
246
|
+
apiKey: data.api_key
|
|
247
|
+
});
|
|
248
|
+
success(`Linked to room: ${colors.bold}${data.room_id}.notepad.web.id${colors.reset}`);
|
|
249
|
+
info('You can now use all CLI commands. Try: note list');
|
|
250
|
+
cancelled = false; // Reset to indicate success
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
214
253
|
}
|
|
254
|
+
} catch (pollErr) {
|
|
255
|
+
// Silently continue polling
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (attempts % 4 === 0) {
|
|
259
|
+
process.stdout.write(`\r${colors.dim} Polling... (${Math.round(attempts * 1.5)}s elapsed, waiting for browser auth)${colors.reset} `);
|
|
215
260
|
}
|
|
216
|
-
} catch (pollErr) {
|
|
217
|
-
// Silently continue polling
|
|
218
261
|
}
|
|
219
262
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
263
|
+
if (hasTTY) {
|
|
264
|
+
try {
|
|
265
|
+
process.stdin.off('keypress', onPollKeypress);
|
|
266
|
+
process.stdin.setRawMode(false);
|
|
267
|
+
} catch (e) {}
|
|
223
268
|
}
|
|
224
|
-
}
|
|
225
269
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
info('Using manual configuration instead...\n');
|
|
232
|
-
}
|
|
270
|
+
if (cancelled) {
|
|
271
|
+
process.stdout.write('\n');
|
|
272
|
+
warning('Browser authorization cancelled by user.');
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
233
275
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
276
|
+
if (attempts >= maxAttempts) {
|
|
277
|
+
process.stdout.write('\n');
|
|
278
|
+
warning('Browser authorization timed out.');
|
|
279
|
+
}
|
|
280
|
+
} catch (e) {
|
|
281
|
+
error(`Browser login unavailable: ${e.message}`);
|
|
282
|
+
}
|
|
283
|
+
} else {
|
|
284
|
+
// Manual API Key configuration
|
|
285
|
+
clearScreen();
|
|
286
|
+
log(`${colors.bgBlue}${colors.white}${colors.bold} Manual API Key Config ${colors.reset}\n`);
|
|
287
|
+
|
|
288
|
+
const roomId = await ask('Enter Subdomain/Room ID (e.g. "saya")', current.roomId);
|
|
289
|
+
const apiKey = await ask('Enter Room API Token/Key', current.apiKey);
|
|
237
290
|
|
|
238
|
-
|
|
291
|
+
await saveConfig({ apiUrl, roomId, apiKey });
|
|
292
|
+
}
|
|
239
293
|
}
|
|
240
294
|
|
|
241
295
|
async function handleList() {
|
|
@@ -633,7 +687,7 @@ function selectMenuOption(options, titleCallback) {
|
|
|
633
687
|
log(` ${colors.dim}${opt}${colors.reset}`);
|
|
634
688
|
}
|
|
635
689
|
});
|
|
636
|
-
log(`\n${colors.dim}Use Up/Down arrows to navigate, Enter to select.${colors.reset}`);
|
|
690
|
+
log(`\n${colors.dim}Use Up/Down arrows to navigate, Enter to select, Esc to go back.${colors.reset}`);
|
|
637
691
|
};
|
|
638
692
|
|
|
639
693
|
readline.emitKeypressEvents(process.stdin);
|
|
@@ -665,6 +719,12 @@ function selectMenuOption(options, titleCallback) {
|
|
|
665
719
|
} catch (e) {}
|
|
666
720
|
process.stdin.off('keypress', onKeypress);
|
|
667
721
|
resolve(activeIndex);
|
|
722
|
+
} else if (key && key.name === 'escape') {
|
|
723
|
+
try {
|
|
724
|
+
process.stdin.setRawMode(false);
|
|
725
|
+
} catch (e) {}
|
|
726
|
+
process.stdin.off('keypress', onKeypress);
|
|
727
|
+
resolve(-1);
|
|
668
728
|
}
|
|
669
729
|
};
|
|
670
730
|
|
|
@@ -694,6 +754,12 @@ async function startMainMenu() {
|
|
|
694
754
|
|
|
695
755
|
const choice = await selectMenuOption(options, titleCallback);
|
|
696
756
|
|
|
757
|
+
if (choice === -1 || choice === 6) {
|
|
758
|
+
clearScreen();
|
|
759
|
+
log('Goodbye!');
|
|
760
|
+
process.exit(0);
|
|
761
|
+
}
|
|
762
|
+
|
|
697
763
|
switch (choice) {
|
|
698
764
|
case 0:
|
|
699
765
|
await handleList();
|
|
@@ -719,10 +785,6 @@ async function startMainMenu() {
|
|
|
719
785
|
await handleLogin();
|
|
720
786
|
await ask('Press Enter to return to menu');
|
|
721
787
|
break;
|
|
722
|
-
case 6:
|
|
723
|
-
clearScreen();
|
|
724
|
-
log('Goodbye!');
|
|
725
|
-
process.exit(0);
|
|
726
788
|
}
|
|
727
789
|
}
|
|
728
790
|
}
|