@shiori-sh/cli 0.3.0 → 0.4.0
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/dist/index.js +117 -31
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/commands/auth.ts
|
|
4
|
+
import { exec } from "child_process";
|
|
5
|
+
import { hostname } from "os";
|
|
4
6
|
import { createInterface } from "readline";
|
|
5
7
|
|
|
6
8
|
// src/config.ts
|
|
@@ -87,16 +89,120 @@ function promptSecret(question) {
|
|
|
87
89
|
stdin.on("data", onData);
|
|
88
90
|
});
|
|
89
91
|
}
|
|
92
|
+
function openUrl(url) {
|
|
93
|
+
const platform = process.platform;
|
|
94
|
+
const cmd = platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
|
|
95
|
+
exec(`${cmd} ${JSON.stringify(url)}`);
|
|
96
|
+
}
|
|
97
|
+
function sleep(ms) {
|
|
98
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
99
|
+
}
|
|
100
|
+
async function browserAuth() {
|
|
101
|
+
const baseUrl = getBaseUrl();
|
|
102
|
+
process.stdout.write("Starting browser authentication... ");
|
|
103
|
+
const deviceName = hostname();
|
|
104
|
+
const startRes = await fetch(`${baseUrl}/api/auth/cli/start`, {
|
|
105
|
+
method: "POST",
|
|
106
|
+
headers: { "Content-Type": "application/json" },
|
|
107
|
+
body: JSON.stringify({ device_name: deviceName })
|
|
108
|
+
});
|
|
109
|
+
if (!startRes.ok) {
|
|
110
|
+
console.error("failed.");
|
|
111
|
+
console.error("Could not start browser auth. Try `shiori auth --api-key` instead.");
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
const { code, auth_url } = await startRes.json();
|
|
115
|
+
console.log("done.\n");
|
|
116
|
+
console.log(` Your code: ${code}
|
|
117
|
+
`);
|
|
118
|
+
console.log(` ${auth_url}
|
|
119
|
+
`);
|
|
120
|
+
await prompt("Press Enter to open in browser (or open the URL above manually)...");
|
|
121
|
+
openUrl(auth_url);
|
|
122
|
+
console.log("\nWaiting for authorization...");
|
|
123
|
+
const POLL_INTERVAL = 2e3;
|
|
124
|
+
const MAX_POLLS = 150;
|
|
125
|
+
let polls = 0;
|
|
126
|
+
while (polls < MAX_POLLS) {
|
|
127
|
+
await sleep(POLL_INTERVAL);
|
|
128
|
+
polls++;
|
|
129
|
+
try {
|
|
130
|
+
const pollRes = await fetch(`${baseUrl}/api/auth/cli/poll?code=${code}`);
|
|
131
|
+
if (!pollRes.ok) {
|
|
132
|
+
console.error("\nAuthorization failed. The code may have expired.");
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
const data = await pollRes.json();
|
|
136
|
+
if (data.status === "approved" && data.api_key) {
|
|
137
|
+
const meRes = await fetch(`${baseUrl}/api/user/me`, {
|
|
138
|
+
headers: { Authorization: `Bearer ${data.api_key}` }
|
|
139
|
+
});
|
|
140
|
+
if (!meRes.ok) {
|
|
141
|
+
console.error("\nReceived API key but verification failed.");
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
const meData = await meRes.json();
|
|
145
|
+
writeConfig({ ...readConfig(), api_key: data.api_key });
|
|
146
|
+
console.log(`
|
|
147
|
+
Authenticated as ${meData.user?.full_name || meData.user?.id}`);
|
|
148
|
+
console.log("API key saved to ~/.shiori/config.json");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (data.status === "expired") {
|
|
152
|
+
console.error("\nAuthorization code expired. Please try again.");
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
if (data.status === "consumed") {
|
|
156
|
+
console.error("\nAuthorization code already used. Please try again.");
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
} catch {
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
console.error("\nTimed out waiting for authorization. Please try again.");
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
async function apiKeyAuth() {
|
|
166
|
+
console.log("\nTo authenticate, you need an API key from Shiori.\n");
|
|
167
|
+
console.log(" 1. Open https://www.shiori.sh/home -> Settings");
|
|
168
|
+
console.log(" 2. Create and copy your API key");
|
|
169
|
+
console.log(" 3. Paste it below\n");
|
|
170
|
+
const key = await promptSecret("API key: ");
|
|
171
|
+
const trimmed = key.trim();
|
|
172
|
+
if (!trimmed.startsWith("shk_")) {
|
|
173
|
+
console.error("\nInvalid API key format. Keys start with 'shk_'.");
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
process.stdout.write("\nVerifying... ");
|
|
177
|
+
const baseUrl = getBaseUrl();
|
|
178
|
+
const res = await fetch(`${baseUrl}/api/user/me`, {
|
|
179
|
+
headers: { Authorization: `Bearer ${trimmed}` }
|
|
180
|
+
});
|
|
181
|
+
if (!res.ok) {
|
|
182
|
+
console.error("failed.\nAuthentication failed. Check your API key and try again.");
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
const data = await res.json();
|
|
186
|
+
writeConfig({ ...readConfig(), api_key: trimmed });
|
|
187
|
+
console.log(`done.
|
|
188
|
+
|
|
189
|
+
Authenticated as ${data.user?.full_name || data.user?.id}`);
|
|
190
|
+
console.log("API key saved to ~/.shiori/config.json");
|
|
191
|
+
}
|
|
90
192
|
async function run(args) {
|
|
91
193
|
if (args.includes("--help") || args.includes("-h")) {
|
|
92
|
-
console.log(`shiori auth - Authenticate with
|
|
194
|
+
console.log(`shiori auth - Authenticate with Shiori
|
|
93
195
|
|
|
94
196
|
Usage: shiori auth [options]
|
|
95
197
|
|
|
96
198
|
Options:
|
|
199
|
+
--api-key Authenticate by pasting an API key manually
|
|
97
200
|
--status Show current authentication status
|
|
98
201
|
--logout Remove stored credentials
|
|
99
|
-
--help, -h Show this help
|
|
202
|
+
--help, -h Show this help
|
|
203
|
+
|
|
204
|
+
By default, opens your browser for a quick sign-in.
|
|
205
|
+
Use --api-key if you prefer to paste a key manually or are on a headless machine.`);
|
|
100
206
|
return;
|
|
101
207
|
}
|
|
102
208
|
if (args.includes("--status")) {
|
|
@@ -122,31 +228,11 @@ Options:
|
|
|
122
228
|
const answer = await prompt("You are already authenticated. Replace existing key? (y/N) ");
|
|
123
229
|
if (answer.trim().toLowerCase() !== "y") return;
|
|
124
230
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const key = await promptSecret("API key: ");
|
|
130
|
-
const trimmed = key.trim();
|
|
131
|
-
if (!trimmed.startsWith("shk_")) {
|
|
132
|
-
console.error("\nInvalid API key format. Keys start with 'shk_'.");
|
|
133
|
-
process.exit(1);
|
|
134
|
-
}
|
|
135
|
-
process.stdout.write("\nVerifying... ");
|
|
136
|
-
const baseUrl = getBaseUrl();
|
|
137
|
-
const res = await fetch(`${baseUrl}/api/user/me`, {
|
|
138
|
-
headers: { Authorization: `Bearer ${trimmed}` }
|
|
139
|
-
});
|
|
140
|
-
if (!res.ok) {
|
|
141
|
-
console.error("failed.\nAuthentication failed. Check your API key and try again.");
|
|
142
|
-
process.exit(1);
|
|
231
|
+
if (args.includes("--api-key")) {
|
|
232
|
+
await apiKeyAuth();
|
|
233
|
+
} else {
|
|
234
|
+
await browserAuth();
|
|
143
235
|
}
|
|
144
|
-
const data = await res.json();
|
|
145
|
-
writeConfig({ ...readConfig(), api_key: trimmed });
|
|
146
|
-
console.log(`done.
|
|
147
|
-
|
|
148
|
-
Authenticated as ${data.user?.full_name || data.user?.id}`);
|
|
149
|
-
console.log("API key saved to ~/.shiori/config.json");
|
|
150
236
|
}
|
|
151
237
|
|
|
152
238
|
// src/api.ts
|
|
@@ -768,7 +854,7 @@ function reportError(command, error) {
|
|
|
768
854
|
method: "POST",
|
|
769
855
|
headers: { "Content-Type": "application/json" },
|
|
770
856
|
body: JSON.stringify({
|
|
771
|
-
version: "0.
|
|
857
|
+
version: "0.4.0",
|
|
772
858
|
command,
|
|
773
859
|
error: message,
|
|
774
860
|
platform: process.platform
|
|
@@ -808,7 +894,7 @@ async function checkForUpdate(currentVersion, isJson) {
|
|
|
808
894
|
|
|
809
895
|
// src/index.ts
|
|
810
896
|
var COMMANDS = {
|
|
811
|
-
auth: { run, desc: "Authenticate with
|
|
897
|
+
auth: { run, desc: "Authenticate with Shiori (browser or API key)" },
|
|
812
898
|
list: { run: run5, desc: "List saved links" },
|
|
813
899
|
search: { run: run8, desc: "Search saved links" },
|
|
814
900
|
get: { run: run4, desc: "Get a link by ID (includes content)" },
|
|
@@ -833,7 +919,7 @@ Options:
|
|
|
833
919
|
--version, -v Show version
|
|
834
920
|
|
|
835
921
|
Get started:
|
|
836
|
-
shiori auth
|
|
922
|
+
shiori auth Sign in via your browser
|
|
837
923
|
shiori list List your recent links
|
|
838
924
|
shiori save <url> Save a new link
|
|
839
925
|
shiori search <query> Search your links
|
|
@@ -847,7 +933,7 @@ async function main() {
|
|
|
847
933
|
return;
|
|
848
934
|
}
|
|
849
935
|
if (command === "--version" || command === "-v") {
|
|
850
|
-
console.log("0.
|
|
936
|
+
console.log("0.4.0");
|
|
851
937
|
return;
|
|
852
938
|
}
|
|
853
939
|
const cmd = COMMANDS[command];
|
|
@@ -860,7 +946,7 @@ async function main() {
|
|
|
860
946
|
const cmdArgs = args.slice(1);
|
|
861
947
|
const isJson = cmdArgs.includes("--json");
|
|
862
948
|
await cmd.run(cmdArgs);
|
|
863
|
-
checkForUpdate("0.
|
|
949
|
+
checkForUpdate("0.4.0", isJson).catch(() => {
|
|
864
950
|
});
|
|
865
951
|
}
|
|
866
952
|
main().catch((err) => {
|