@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.
Files changed (2) hide show
  1. package/dist/index.js +117 -31
  2. 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 your Shiori API key
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
- console.log("\nTo authenticate, you need an API key from Shiori.\n");
126
- console.log(" 1. Open https://www.shiori.sh/home -> Settings");
127
- console.log(" 2. Create and copy your API key");
128
- console.log(" 3. Paste it below\n");
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.3.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 your Shiori API key" },
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 Authenticate with your API key
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.3.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.3.0", isJson).catch(() => {
949
+ checkForUpdate("0.4.0", isJson).catch(() => {
864
950
  });
865
951
  }
866
952
  main().catch((err) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shiori-sh/cli",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "CLI for managing your Shiori link library",
5
5
  "author": "Brian Lovin",
6
6
  "license": "MIT",