@seedvault/cli 0.2.0 → 0.3.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/sv.js +60 -48
- package/package.json +1 -1
package/dist/sv.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
3
|
|
|
4
|
+
// src/index.ts
|
|
5
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
6
|
+
import { resolve as resolve6 } from "path";
|
|
7
|
+
|
|
4
8
|
// src/commands/init.ts
|
|
5
9
|
import * as readline from "readline/promises";
|
|
6
|
-
import {
|
|
10
|
+
import { stdout } from "process";
|
|
11
|
+
import * as fs from "fs";
|
|
7
12
|
|
|
8
13
|
// src/config.ts
|
|
9
14
|
import { join, resolve, relative, isAbsolute } from "path";
|
|
@@ -190,23 +195,23 @@ function createClient(serverUrl, token) {
|
|
|
190
195
|
const res = await request("GET", "/v1/contributors");
|
|
191
196
|
return res.json();
|
|
192
197
|
},
|
|
193
|
-
async putFile(
|
|
194
|
-
const res = await request("PUT", `/v1/contributors/${
|
|
198
|
+
async putFile(username, path, content) {
|
|
199
|
+
const res = await request("PUT", `/v1/contributors/${username}/files/${encodePath(path)}`, {
|
|
195
200
|
body: content,
|
|
196
201
|
contentType: "text/markdown"
|
|
197
202
|
});
|
|
198
203
|
return res.json();
|
|
199
204
|
},
|
|
200
|
-
async deleteFile(
|
|
201
|
-
await request("DELETE", `/v1/contributors/${
|
|
205
|
+
async deleteFile(username, path) {
|
|
206
|
+
await request("DELETE", `/v1/contributors/${username}/files/${encodePath(path)}`);
|
|
202
207
|
},
|
|
203
|
-
async listFiles(
|
|
208
|
+
async listFiles(username, prefix) {
|
|
204
209
|
const qs = prefix ? `?prefix=${encodeURIComponent(prefix)}` : "";
|
|
205
|
-
const res = await request("GET", `/v1/contributors/${
|
|
210
|
+
const res = await request("GET", `/v1/contributors/${username}/files${qs}`);
|
|
206
211
|
return res.json();
|
|
207
212
|
},
|
|
208
|
-
async getFile(
|
|
209
|
-
const res = await request("GET", `/v1/contributors/${
|
|
213
|
+
async getFile(username, path) {
|
|
214
|
+
const res = await request("GET", `/v1/contributors/${username}/files/${encodePath(path)}`);
|
|
210
215
|
return res.text();
|
|
211
216
|
},
|
|
212
217
|
async health() {
|
|
@@ -232,21 +237,21 @@ async function init(args) {
|
|
|
232
237
|
console.error(`Could not reach server at ${flags.server}`);
|
|
233
238
|
process.exit(1);
|
|
234
239
|
}
|
|
235
|
-
const
|
|
236
|
-
if (!
|
|
237
|
-
console.error("When using --token, also pass --
|
|
240
|
+
const username = flags["username"] || "";
|
|
241
|
+
if (!username) {
|
|
242
|
+
console.error("When using --token, also pass --username");
|
|
238
243
|
process.exit(1);
|
|
239
244
|
}
|
|
240
245
|
const config = {
|
|
241
246
|
server: flags.server,
|
|
242
247
|
token: flags.token,
|
|
243
|
-
|
|
248
|
+
username,
|
|
244
249
|
collections: []
|
|
245
250
|
};
|
|
246
251
|
saveConfig(config);
|
|
247
252
|
console.log("Seedvault configured.");
|
|
248
253
|
console.log(` Server: ${config.server}`);
|
|
249
|
-
console.log(`
|
|
254
|
+
console.log(` Username: ${config.username}`);
|
|
250
255
|
return;
|
|
251
256
|
}
|
|
252
257
|
if (flags.server && flags.name) {
|
|
@@ -261,17 +266,24 @@ async function init(args) {
|
|
|
261
266
|
const config = {
|
|
262
267
|
server: flags.server,
|
|
263
268
|
token: result.token,
|
|
264
|
-
|
|
269
|
+
username: result.contributor.username,
|
|
265
270
|
collections: []
|
|
266
271
|
};
|
|
267
272
|
saveConfig(config);
|
|
268
273
|
console.log("Signed up and configured.");
|
|
269
274
|
console.log(` Server: ${config.server}`);
|
|
270
|
-
console.log(`
|
|
275
|
+
console.log(` Username: ${result.contributor.username}`);
|
|
271
276
|
console.log(` Token: ${result.token}`);
|
|
272
277
|
return;
|
|
273
278
|
}
|
|
274
|
-
|
|
279
|
+
let input;
|
|
280
|
+
try {
|
|
281
|
+
const fd = fs.openSync("/dev/tty", "r");
|
|
282
|
+
input = fs.createReadStream("", { fd });
|
|
283
|
+
} catch {
|
|
284
|
+
input = process.stdin;
|
|
285
|
+
}
|
|
286
|
+
const rl = readline.createInterface({ input, output: stdout });
|
|
275
287
|
try {
|
|
276
288
|
console.log(`Seedvault Setup
|
|
277
289
|
`);
|
|
@@ -292,25 +304,24 @@ async function init(args) {
|
|
|
292
304
|
const hasToken = await rl.question("Do you already have a token? (y/N): ");
|
|
293
305
|
if (hasToken.toLowerCase() === "y") {
|
|
294
306
|
const token = await rl.question("Token: ");
|
|
295
|
-
const
|
|
296
|
-
const config = { server, token: token.trim(),
|
|
307
|
+
const username = await rl.question("Username: ");
|
|
308
|
+
const config = { server, token: token.trim(), username: username.trim(), collections: [] };
|
|
297
309
|
saveConfig(config);
|
|
298
310
|
console.log(`
|
|
299
311
|
Seedvault configured.`);
|
|
300
312
|
} else {
|
|
301
|
-
const name = await rl.question("
|
|
313
|
+
const name = await rl.question("Username (e.g. your-name-notes): ");
|
|
302
314
|
const invite = await rl.question("Invite code (leave blank if first user): ");
|
|
303
315
|
const result = await client.signup(name.trim(), invite.trim() || undefined);
|
|
304
316
|
const config = {
|
|
305
317
|
server,
|
|
306
318
|
token: result.token,
|
|
307
|
-
|
|
319
|
+
username: result.contributor.username,
|
|
308
320
|
collections: []
|
|
309
321
|
};
|
|
310
322
|
saveConfig(config);
|
|
311
323
|
console.log(`
|
|
312
|
-
Signed up as '${result.contributor.
|
|
313
|
-
console.log(` Contributor ID: ${result.contributor.id}`);
|
|
324
|
+
Signed up as '${result.contributor.username}'.`);
|
|
314
325
|
console.log(` Token: ${result.token}`);
|
|
315
326
|
console.log(`
|
|
316
327
|
Save your token \u2014 it won't be shown again.`);
|
|
@@ -2087,9 +2098,9 @@ class RetryQueue {
|
|
|
2087
2098
|
const op = this.items[0];
|
|
2088
2099
|
try {
|
|
2089
2100
|
if (op.type === "put" && op.content !== null) {
|
|
2090
|
-
await this.client.putFile(op.
|
|
2101
|
+
await this.client.putFile(op.username, op.serverPath, op.content);
|
|
2091
2102
|
} else if (op.type === "delete") {
|
|
2092
|
-
await this.client.deleteFile(op.
|
|
2103
|
+
await this.client.deleteFile(op.username, op.serverPath);
|
|
2093
2104
|
}
|
|
2094
2105
|
this.items.shift();
|
|
2095
2106
|
this.backoff = MIN_BACKOFF;
|
|
@@ -2117,13 +2128,13 @@ class RetryQueue {
|
|
|
2117
2128
|
// src/daemon/syncer.ts
|
|
2118
2129
|
class Syncer {
|
|
2119
2130
|
client;
|
|
2120
|
-
|
|
2131
|
+
username;
|
|
2121
2132
|
collections;
|
|
2122
2133
|
queue;
|
|
2123
2134
|
log;
|
|
2124
2135
|
constructor(opts) {
|
|
2125
2136
|
this.client = opts.client;
|
|
2126
|
-
this.
|
|
2137
|
+
this.username = opts.username;
|
|
2127
2138
|
this.collections = opts.collections;
|
|
2128
2139
|
this.log = opts.onLog;
|
|
2129
2140
|
this.queue = new RetryQueue(opts.client, opts.onLog);
|
|
@@ -2149,7 +2160,7 @@ class Syncer {
|
|
|
2149
2160
|
let deleted = 0;
|
|
2150
2161
|
this.log(`Syncing '${collection.name}' (${collection.path})...`);
|
|
2151
2162
|
try {
|
|
2152
|
-
const { files: serverFiles } = await this.client.listFiles(this.
|
|
2163
|
+
const { files: serverFiles } = await this.client.listFiles(this.username, collection.name + "/");
|
|
2153
2164
|
const serverMap = new Map;
|
|
2154
2165
|
for (const f of serverFiles) {
|
|
2155
2166
|
serverMap.set(f.path, f.modifiedAt);
|
|
@@ -2171,12 +2182,12 @@ class Syncer {
|
|
|
2171
2182
|
}
|
|
2172
2183
|
const content = await readFile(localFile.path, "utf-8");
|
|
2173
2184
|
try {
|
|
2174
|
-
await this.client.putFile(this.
|
|
2185
|
+
await this.client.putFile(this.username, serverPath, content);
|
|
2175
2186
|
uploaded++;
|
|
2176
2187
|
} catch {
|
|
2177
2188
|
this.queue.enqueue({
|
|
2178
2189
|
type: "put",
|
|
2179
|
-
|
|
2190
|
+
username: this.username,
|
|
2180
2191
|
serverPath,
|
|
2181
2192
|
content,
|
|
2182
2193
|
queuedAt: new Date().toISOString()
|
|
@@ -2187,12 +2198,12 @@ class Syncer {
|
|
|
2187
2198
|
if (localServerPaths.has(f.path))
|
|
2188
2199
|
continue;
|
|
2189
2200
|
try {
|
|
2190
|
-
await this.client.deleteFile(this.
|
|
2201
|
+
await this.client.deleteFile(this.username, f.path);
|
|
2191
2202
|
deleted++;
|
|
2192
2203
|
} catch {
|
|
2193
2204
|
this.queue.enqueue({
|
|
2194
2205
|
type: "delete",
|
|
2195
|
-
|
|
2206
|
+
username: this.username,
|
|
2196
2207
|
serverPath: f.path,
|
|
2197
2208
|
content: null,
|
|
2198
2209
|
queuedAt: new Date().toISOString()
|
|
@@ -2210,15 +2221,15 @@ class Syncer {
|
|
|
2210
2221
|
let queued = 0;
|
|
2211
2222
|
this.log(`Removing '${collection.name}' files from server...`);
|
|
2212
2223
|
try {
|
|
2213
|
-
const { files: serverFiles } = await this.client.listFiles(this.
|
|
2224
|
+
const { files: serverFiles } = await this.client.listFiles(this.username, collection.name + "/");
|
|
2214
2225
|
for (const f of serverFiles) {
|
|
2215
2226
|
try {
|
|
2216
|
-
await this.client.deleteFile(this.
|
|
2227
|
+
await this.client.deleteFile(this.username, f.path);
|
|
2217
2228
|
deleted++;
|
|
2218
2229
|
} catch {
|
|
2219
2230
|
this.queue.enqueue({
|
|
2220
2231
|
type: "delete",
|
|
2221
|
-
|
|
2232
|
+
username: this.username,
|
|
2222
2233
|
serverPath: f.path,
|
|
2223
2234
|
content: null,
|
|
2224
2235
|
queuedAt: new Date().toISOString()
|
|
@@ -2238,7 +2249,7 @@ class Syncer {
|
|
|
2238
2249
|
this.log(`PUT ${event.serverPath} (${content.length} bytes)`);
|
|
2239
2250
|
this.queue.enqueue({
|
|
2240
2251
|
type: "put",
|
|
2241
|
-
|
|
2252
|
+
username: this.username,
|
|
2242
2253
|
serverPath: event.serverPath,
|
|
2243
2254
|
content,
|
|
2244
2255
|
queuedAt: new Date().toISOString()
|
|
@@ -2247,7 +2258,7 @@ class Syncer {
|
|
|
2247
2258
|
this.log(`DELETE ${event.serverPath}`);
|
|
2248
2259
|
this.queue.enqueue({
|
|
2249
2260
|
type: "delete",
|
|
2250
|
-
|
|
2261
|
+
username: this.username,
|
|
2251
2262
|
serverPath: event.serverPath,
|
|
2252
2263
|
content: null,
|
|
2253
2264
|
queuedAt: new Date().toISOString()
|
|
@@ -2661,7 +2672,7 @@ async function startForeground() {
|
|
|
2661
2672
|
maybeLogOverlapWarning(removedOverlappingCollections);
|
|
2662
2673
|
log("Seedvault daemon starting...");
|
|
2663
2674
|
log(` Server: ${config.server}`);
|
|
2664
|
-
log(` Contributor: ${config.
|
|
2675
|
+
log(` Contributor: ${config.username}`);
|
|
2665
2676
|
if (config.collections.length === 0) {
|
|
2666
2677
|
log(" Collections: none");
|
|
2667
2678
|
log(" Waiting for collections to be added...");
|
|
@@ -2671,7 +2682,7 @@ async function startForeground() {
|
|
|
2671
2682
|
writeFileSync2(getPidPath(), String(process.pid));
|
|
2672
2683
|
const syncer = new Syncer({
|
|
2673
2684
|
client,
|
|
2674
|
-
|
|
2685
|
+
username: config.username,
|
|
2675
2686
|
collections: config.collections,
|
|
2676
2687
|
onLog: log
|
|
2677
2688
|
});
|
|
@@ -2801,7 +2812,7 @@ async function status() {
|
|
|
2801
2812
|
console.log(`Seedvault Status
|
|
2802
2813
|
`);
|
|
2803
2814
|
console.log(` Server: ${config.server}`);
|
|
2804
|
-
console.log(` Contributor: ${config.
|
|
2815
|
+
console.log(` Contributor: ${config.username}`);
|
|
2805
2816
|
try {
|
|
2806
2817
|
const platform = detectPlatform();
|
|
2807
2818
|
const serviceName = platform === "macos" ? "launchd" : platform === "linux" ? "systemd" : "Task Scheduler";
|
|
@@ -2839,7 +2850,7 @@ async function ls(args) {
|
|
|
2839
2850
|
const config = loadConfig();
|
|
2840
2851
|
const client = createClient(config.server, config.token);
|
|
2841
2852
|
const prefix = args[0] || undefined;
|
|
2842
|
-
const { files } = await client.listFiles(config.
|
|
2853
|
+
const { files } = await client.listFiles(config.username, prefix);
|
|
2843
2854
|
if (files.length === 0) {
|
|
2844
2855
|
console.log(prefix ? `No files matching '${prefix}'.` : "No files in your contributor.");
|
|
2845
2856
|
return;
|
|
@@ -2871,7 +2882,7 @@ async function cat(args) {
|
|
|
2871
2882
|
const config = loadConfig();
|
|
2872
2883
|
const client = createClient(config.server, config.token);
|
|
2873
2884
|
try {
|
|
2874
|
-
const content = await client.getFile(config.
|
|
2885
|
+
const content = await client.getFile(config.username, filePath);
|
|
2875
2886
|
process.stdout.write(content);
|
|
2876
2887
|
} catch (e) {
|
|
2877
2888
|
if (e instanceof ApiError && e.status === 404) {
|
|
@@ -2894,9 +2905,8 @@ async function contributors() {
|
|
|
2894
2905
|
console.log(`Contributors:
|
|
2895
2906
|
`);
|
|
2896
2907
|
for (const contributor of contributors2) {
|
|
2897
|
-
const you = contributor.
|
|
2898
|
-
console.log(` ${contributor.
|
|
2899
|
-
console.log(` ID: ${contributor.id}`);
|
|
2908
|
+
const you = contributor.username === config.username ? " (you)" : "";
|
|
2909
|
+
console.log(` ${contributor.username}${you}`);
|
|
2900
2910
|
console.log(` Created: ${new Date(contributor.createdAt).toLocaleString()}`);
|
|
2901
2911
|
console.log();
|
|
2902
2912
|
}
|
|
@@ -2930,8 +2940,8 @@ Usage: sv <command> [options]
|
|
|
2930
2940
|
|
|
2931
2941
|
Setup:
|
|
2932
2942
|
init Interactive first-time setup
|
|
2933
|
-
init --server URL --token T
|
|
2934
|
-
init --server URL --name N
|
|
2943
|
+
init --server URL --token T --username U Non-interactive (existing token)
|
|
2944
|
+
init --server URL --name N Non-interactive (signup)
|
|
2935
2945
|
|
|
2936
2946
|
Collections:
|
|
2937
2947
|
add <path> [--name N] Add a collection path
|
|
@@ -2959,7 +2969,9 @@ async function main() {
|
|
|
2959
2969
|
return;
|
|
2960
2970
|
}
|
|
2961
2971
|
if (cmd === "--version" || cmd === "-v") {
|
|
2962
|
-
|
|
2972
|
+
const pkgPath = resolve6(import.meta.dirname, "..", "package.json");
|
|
2973
|
+
const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
2974
|
+
console.log(pkg.version);
|
|
2963
2975
|
return;
|
|
2964
2976
|
}
|
|
2965
2977
|
try {
|