@seedvault/cli 0.2.1 → 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/sv.js +149 -86
- package/package.json +1 -1
package/dist/sv.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
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
10
|
import { stdout } from "process";
|
|
@@ -172,6 +176,10 @@ function createClient(serverUrl, token) {
|
|
|
172
176
|
return res;
|
|
173
177
|
}
|
|
174
178
|
return {
|
|
179
|
+
async me() {
|
|
180
|
+
const res = await request("GET", "/v1/me");
|
|
181
|
+
return res.json();
|
|
182
|
+
},
|
|
175
183
|
async signup(name, invite) {
|
|
176
184
|
const body = { name };
|
|
177
185
|
if (invite)
|
|
@@ -191,23 +199,23 @@ function createClient(serverUrl, token) {
|
|
|
191
199
|
const res = await request("GET", "/v1/contributors");
|
|
192
200
|
return res.json();
|
|
193
201
|
},
|
|
194
|
-
async putFile(
|
|
195
|
-
const res = await request("PUT", `/v1/contributors/${
|
|
202
|
+
async putFile(username, path, content) {
|
|
203
|
+
const res = await request("PUT", `/v1/contributors/${username}/files/${encodePath(path)}`, {
|
|
196
204
|
body: content,
|
|
197
205
|
contentType: "text/markdown"
|
|
198
206
|
});
|
|
199
207
|
return res.json();
|
|
200
208
|
},
|
|
201
|
-
async deleteFile(
|
|
202
|
-
await request("DELETE", `/v1/contributors/${
|
|
209
|
+
async deleteFile(username, path) {
|
|
210
|
+
await request("DELETE", `/v1/contributors/${username}/files/${encodePath(path)}`);
|
|
203
211
|
},
|
|
204
|
-
async listFiles(
|
|
212
|
+
async listFiles(username, prefix) {
|
|
205
213
|
const qs = prefix ? `?prefix=${encodeURIComponent(prefix)}` : "";
|
|
206
|
-
const res = await request("GET", `/v1/contributors/${
|
|
214
|
+
const res = await request("GET", `/v1/contributors/${username}/files${qs}`);
|
|
207
215
|
return res.json();
|
|
208
216
|
},
|
|
209
|
-
async getFile(
|
|
210
|
-
const res = await request("GET", `/v1/contributors/${
|
|
217
|
+
async getFile(username, path) {
|
|
218
|
+
const res = await request("GET", `/v1/contributors/${username}/files/${encodePath(path)}`);
|
|
211
219
|
return res.text();
|
|
212
220
|
},
|
|
213
221
|
async health() {
|
|
@@ -227,27 +235,24 @@ async function init(args) {
|
|
|
227
235
|
}
|
|
228
236
|
if (flags.server && flags.token) {
|
|
229
237
|
const client = createClient(flags.server, flags.token);
|
|
238
|
+
let username;
|
|
230
239
|
try {
|
|
231
|
-
await client.
|
|
240
|
+
const me = await client.me();
|
|
241
|
+
username = me.username;
|
|
232
242
|
} catch {
|
|
233
|
-
console.error(`Could not
|
|
234
|
-
process.exit(1);
|
|
235
|
-
}
|
|
236
|
-
const contributorId = flags["contributor-id"] || "";
|
|
237
|
-
if (!contributorId) {
|
|
238
|
-
console.error("When using --token, also pass --contributor-id");
|
|
243
|
+
console.error(`Could not authenticate with server at ${flags.server}`);
|
|
239
244
|
process.exit(1);
|
|
240
245
|
}
|
|
241
246
|
const config = {
|
|
242
247
|
server: flags.server,
|
|
243
248
|
token: flags.token,
|
|
244
|
-
|
|
249
|
+
username,
|
|
245
250
|
collections: []
|
|
246
251
|
};
|
|
247
252
|
saveConfig(config);
|
|
248
253
|
console.log("Seedvault configured.");
|
|
249
254
|
console.log(` Server: ${config.server}`);
|
|
250
|
-
console.log(`
|
|
255
|
+
console.log(` Username: ${config.username}`);
|
|
251
256
|
return;
|
|
252
257
|
}
|
|
253
258
|
if (flags.server && flags.name) {
|
|
@@ -262,13 +267,13 @@ async function init(args) {
|
|
|
262
267
|
const config = {
|
|
263
268
|
server: flags.server,
|
|
264
269
|
token: result.token,
|
|
265
|
-
|
|
270
|
+
username: result.contributor.username,
|
|
266
271
|
collections: []
|
|
267
272
|
};
|
|
268
273
|
saveConfig(config);
|
|
269
274
|
console.log("Signed up and configured.");
|
|
270
275
|
console.log(` Server: ${config.server}`);
|
|
271
|
-
console.log(`
|
|
276
|
+
console.log(` Username: ${result.contributor.username}`);
|
|
272
277
|
console.log(` Token: ${result.token}`);
|
|
273
278
|
return;
|
|
274
279
|
}
|
|
@@ -299,26 +304,33 @@ async function init(args) {
|
|
|
299
304
|
}
|
|
300
305
|
const hasToken = await rl.question("Do you already have a token? (y/N): ");
|
|
301
306
|
if (hasToken.toLowerCase() === "y") {
|
|
302
|
-
const token = await rl.question("Token: ");
|
|
303
|
-
const
|
|
304
|
-
|
|
307
|
+
const token = (await rl.question("Token: ")).trim();
|
|
308
|
+
const authedClient = createClient(server, token);
|
|
309
|
+
let username;
|
|
310
|
+
try {
|
|
311
|
+
const me = await authedClient.me();
|
|
312
|
+
username = me.username;
|
|
313
|
+
} catch {
|
|
314
|
+
console.error(" Token is invalid or server rejected it.");
|
|
315
|
+
process.exit(1);
|
|
316
|
+
}
|
|
317
|
+
const config = { server, token, username, collections: [] };
|
|
305
318
|
saveConfig(config);
|
|
306
319
|
console.log(`
|
|
307
|
-
Seedvault configured
|
|
320
|
+
Seedvault configured as '${username}'`);
|
|
308
321
|
} else {
|
|
309
|
-
const name = await rl.question("
|
|
322
|
+
const name = await rl.question("Username (e.g. your-name-notes): ");
|
|
310
323
|
const invite = await rl.question("Invite code (leave blank if first user): ");
|
|
311
324
|
const result = await client.signup(name.trim(), invite.trim() || undefined);
|
|
312
325
|
const config = {
|
|
313
326
|
server,
|
|
314
327
|
token: result.token,
|
|
315
|
-
|
|
328
|
+
username: result.contributor.username,
|
|
316
329
|
collections: []
|
|
317
330
|
};
|
|
318
331
|
saveConfig(config);
|
|
319
332
|
console.log(`
|
|
320
|
-
Signed up as '${result.contributor.
|
|
321
|
-
console.log(` Contributor ID: ${result.contributor.id}`);
|
|
333
|
+
Signed up as '${result.contributor.username}'.`);
|
|
322
334
|
console.log(` Token: ${result.token}`);
|
|
323
335
|
console.log(`
|
|
324
336
|
Save your token \u2014 it won't be shown again.`);
|
|
@@ -330,6 +342,7 @@ Next steps:`);
|
|
|
330
342
|
} finally {
|
|
331
343
|
rl.close();
|
|
332
344
|
}
|
|
345
|
+
process.exit(0);
|
|
333
346
|
}
|
|
334
347
|
function parseFlags(args) {
|
|
335
348
|
const flags = {};
|
|
@@ -2095,9 +2108,9 @@ class RetryQueue {
|
|
|
2095
2108
|
const op = this.items[0];
|
|
2096
2109
|
try {
|
|
2097
2110
|
if (op.type === "put" && op.content !== null) {
|
|
2098
|
-
await this.client.putFile(op.
|
|
2111
|
+
await this.client.putFile(op.username, op.serverPath, op.content);
|
|
2099
2112
|
} else if (op.type === "delete") {
|
|
2100
|
-
await this.client.deleteFile(op.
|
|
2113
|
+
await this.client.deleteFile(op.username, op.serverPath);
|
|
2101
2114
|
}
|
|
2102
2115
|
this.items.shift();
|
|
2103
2116
|
this.backoff = MIN_BACKOFF;
|
|
@@ -2123,15 +2136,17 @@ class RetryQueue {
|
|
|
2123
2136
|
}
|
|
2124
2137
|
|
|
2125
2138
|
// src/daemon/syncer.ts
|
|
2139
|
+
var SYNC_CONCURRENCY = 10;
|
|
2140
|
+
|
|
2126
2141
|
class Syncer {
|
|
2127
2142
|
client;
|
|
2128
|
-
|
|
2143
|
+
username;
|
|
2129
2144
|
collections;
|
|
2130
2145
|
queue;
|
|
2131
2146
|
log;
|
|
2132
2147
|
constructor(opts) {
|
|
2133
2148
|
this.client = opts.client;
|
|
2134
|
-
this.
|
|
2149
|
+
this.username = opts.username;
|
|
2135
2150
|
this.collections = opts.collections;
|
|
2136
2151
|
this.log = opts.onLog;
|
|
2137
2152
|
this.queue = new RetryQueue(opts.client, opts.onLog);
|
|
@@ -2157,13 +2172,14 @@ class Syncer {
|
|
|
2157
2172
|
let deleted = 0;
|
|
2158
2173
|
this.log(`Syncing '${collection.name}' (${collection.path})...`);
|
|
2159
2174
|
try {
|
|
2160
|
-
const { files: serverFiles } = await this.client.listFiles(this.
|
|
2175
|
+
const { files: serverFiles } = await this.client.listFiles(this.username, collection.name + "/");
|
|
2161
2176
|
const serverMap = new Map;
|
|
2162
2177
|
for (const f of serverFiles) {
|
|
2163
2178
|
serverMap.set(f.path, f.modifiedAt);
|
|
2164
2179
|
}
|
|
2165
2180
|
const localFiles = await walkMd(collection.path);
|
|
2166
2181
|
const localServerPaths = new Set;
|
|
2182
|
+
const toUpload = [];
|
|
2167
2183
|
for (const localFile of localFiles) {
|
|
2168
2184
|
const relPath = toPosixPath(relative5(collection.path, localFile.path));
|
|
2169
2185
|
const serverPath = `${collection.name}/${relPath}`;
|
|
@@ -2178,35 +2194,37 @@ class Syncer {
|
|
|
2178
2194
|
}
|
|
2179
2195
|
}
|
|
2180
2196
|
const content = await readFile(localFile.path, "utf-8");
|
|
2197
|
+
toUpload.push({ serverPath, content });
|
|
2198
|
+
}
|
|
2199
|
+
await pooled(toUpload, SYNC_CONCURRENCY, async (item) => {
|
|
2181
2200
|
try {
|
|
2182
|
-
await this.client.putFile(this.
|
|
2201
|
+
await this.client.putFile(this.username, item.serverPath, item.content);
|
|
2183
2202
|
uploaded++;
|
|
2184
2203
|
} catch {
|
|
2185
2204
|
this.queue.enqueue({
|
|
2186
2205
|
type: "put",
|
|
2187
|
-
|
|
2188
|
-
serverPath,
|
|
2189
|
-
content,
|
|
2206
|
+
username: this.username,
|
|
2207
|
+
serverPath: item.serverPath,
|
|
2208
|
+
content: item.content,
|
|
2190
2209
|
queuedAt: new Date().toISOString()
|
|
2191
2210
|
});
|
|
2192
2211
|
}
|
|
2193
|
-
}
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
continue;
|
|
2212
|
+
});
|
|
2213
|
+
const toDelete = serverFiles.filter((f) => !localServerPaths.has(f.path));
|
|
2214
|
+
await pooled(toDelete, SYNC_CONCURRENCY, async (f) => {
|
|
2197
2215
|
try {
|
|
2198
|
-
await this.client.deleteFile(this.
|
|
2216
|
+
await this.client.deleteFile(this.username, f.path);
|
|
2199
2217
|
deleted++;
|
|
2200
2218
|
} catch {
|
|
2201
2219
|
this.queue.enqueue({
|
|
2202
2220
|
type: "delete",
|
|
2203
|
-
|
|
2221
|
+
username: this.username,
|
|
2204
2222
|
serverPath: f.path,
|
|
2205
2223
|
content: null,
|
|
2206
2224
|
queuedAt: new Date().toISOString()
|
|
2207
2225
|
});
|
|
2208
2226
|
}
|
|
2209
|
-
}
|
|
2227
|
+
});
|
|
2210
2228
|
this.log(` '${collection.name}': ${uploaded} uploaded, ${skipped} up-to-date, ${deleted} deleted`);
|
|
2211
2229
|
} catch (e) {
|
|
2212
2230
|
this.log(` '${collection.name}': sync failed (${e.message})`);
|
|
@@ -2218,22 +2236,22 @@ class Syncer {
|
|
|
2218
2236
|
let queued = 0;
|
|
2219
2237
|
this.log(`Removing '${collection.name}' files from server...`);
|
|
2220
2238
|
try {
|
|
2221
|
-
const { files: serverFiles } = await this.client.listFiles(this.
|
|
2222
|
-
|
|
2239
|
+
const { files: serverFiles } = await this.client.listFiles(this.username, collection.name + "/");
|
|
2240
|
+
await pooled(serverFiles, SYNC_CONCURRENCY, async (f) => {
|
|
2223
2241
|
try {
|
|
2224
|
-
await this.client.deleteFile(this.
|
|
2242
|
+
await this.client.deleteFile(this.username, f.path);
|
|
2225
2243
|
deleted++;
|
|
2226
2244
|
} catch {
|
|
2227
2245
|
this.queue.enqueue({
|
|
2228
2246
|
type: "delete",
|
|
2229
|
-
|
|
2247
|
+
username: this.username,
|
|
2230
2248
|
serverPath: f.path,
|
|
2231
2249
|
content: null,
|
|
2232
2250
|
queuedAt: new Date().toISOString()
|
|
2233
2251
|
});
|
|
2234
2252
|
queued++;
|
|
2235
2253
|
}
|
|
2236
|
-
}
|
|
2254
|
+
});
|
|
2237
2255
|
this.log(` '${collection.name}': ${deleted} deleted, ${queued} queued`);
|
|
2238
2256
|
} catch (e) {
|
|
2239
2257
|
this.log(` '${collection.name}': remove failed (${e.message})`);
|
|
@@ -2246,7 +2264,7 @@ class Syncer {
|
|
|
2246
2264
|
this.log(`PUT ${event.serverPath} (${content.length} bytes)`);
|
|
2247
2265
|
this.queue.enqueue({
|
|
2248
2266
|
type: "put",
|
|
2249
|
-
|
|
2267
|
+
username: this.username,
|
|
2250
2268
|
serverPath: event.serverPath,
|
|
2251
2269
|
content,
|
|
2252
2270
|
queuedAt: new Date().toISOString()
|
|
@@ -2255,7 +2273,7 @@ class Syncer {
|
|
|
2255
2273
|
this.log(`DELETE ${event.serverPath}`);
|
|
2256
2274
|
this.queue.enqueue({
|
|
2257
2275
|
type: "delete",
|
|
2258
|
-
|
|
2276
|
+
username: this.username,
|
|
2259
2277
|
serverPath: event.serverPath,
|
|
2260
2278
|
content: null,
|
|
2261
2279
|
queuedAt: new Date().toISOString()
|
|
@@ -2277,6 +2295,16 @@ async function walkMd(dir) {
|
|
|
2277
2295
|
await walkDirRecursive(dir, results);
|
|
2278
2296
|
return results;
|
|
2279
2297
|
}
|
|
2298
|
+
async function pooled(items, concurrency, fn) {
|
|
2299
|
+
let i = 0;
|
|
2300
|
+
async function worker() {
|
|
2301
|
+
while (i < items.length) {
|
|
2302
|
+
const idx = i++;
|
|
2303
|
+
await fn(items[idx]);
|
|
2304
|
+
}
|
|
2305
|
+
}
|
|
2306
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => worker()));
|
|
2307
|
+
}
|
|
2280
2308
|
async function walkDirRecursive(dir, results) {
|
|
2281
2309
|
const entries = await readdir3(dir, { withFileTypes: true });
|
|
2282
2310
|
for (const entry of entries) {
|
|
@@ -2643,7 +2671,7 @@ async function startForeground() {
|
|
|
2643
2671
|
if (removedOverlappingCollections.length > 0) {
|
|
2644
2672
|
config = normalizedConfig;
|
|
2645
2673
|
}
|
|
2646
|
-
|
|
2674
|
+
let client = createClient(config.server, config.token);
|
|
2647
2675
|
try {
|
|
2648
2676
|
await client.health();
|
|
2649
2677
|
} catch {
|
|
@@ -2669,7 +2697,7 @@ async function startForeground() {
|
|
|
2669
2697
|
maybeLogOverlapWarning(removedOverlappingCollections);
|
|
2670
2698
|
log("Seedvault daemon starting...");
|
|
2671
2699
|
log(` Server: ${config.server}`);
|
|
2672
|
-
log(` Contributor: ${config.
|
|
2700
|
+
log(` Contributor: ${config.username}`);
|
|
2673
2701
|
if (config.collections.length === 0) {
|
|
2674
2702
|
log(" Collections: none");
|
|
2675
2703
|
log(" Waiting for collections to be added...");
|
|
@@ -2677,9 +2705,9 @@ async function startForeground() {
|
|
|
2677
2705
|
log(` Collections: ${config.collections.map((f) => f.name).join(", ")}`);
|
|
2678
2706
|
}
|
|
2679
2707
|
writeFileSync2(getPidPath(), String(process.pid));
|
|
2680
|
-
|
|
2708
|
+
let syncer = new Syncer({
|
|
2681
2709
|
client,
|
|
2682
|
-
|
|
2710
|
+
username: config.username,
|
|
2683
2711
|
collections: config.collections,
|
|
2684
2712
|
onLog: log
|
|
2685
2713
|
});
|
|
@@ -2693,11 +2721,6 @@ async function startForeground() {
|
|
|
2693
2721
|
log("Will continue watching for changes...");
|
|
2694
2722
|
}
|
|
2695
2723
|
}
|
|
2696
|
-
const onWatcherEvent = (event) => {
|
|
2697
|
-
syncer.handleEvent(event).catch((e) => {
|
|
2698
|
-
log(`Error handling ${event.type} for ${event.serverPath}: ${e.message}`);
|
|
2699
|
-
});
|
|
2700
|
-
};
|
|
2701
2724
|
let watcher = null;
|
|
2702
2725
|
const rebuildWatcher = async (collections2) => {
|
|
2703
2726
|
if (watcher) {
|
|
@@ -2708,13 +2731,17 @@ async function startForeground() {
|
|
|
2708
2731
|
log("No collections configured. Daemon idle.");
|
|
2709
2732
|
return;
|
|
2710
2733
|
}
|
|
2711
|
-
watcher = createWatcher(collections2,
|
|
2734
|
+
watcher = createWatcher(collections2, (event) => {
|
|
2735
|
+
syncer.handleEvent(event).catch((e) => {
|
|
2736
|
+
log(`Error handling ${event.type} for ${event.serverPath}: ${e.message}`);
|
|
2737
|
+
});
|
|
2738
|
+
});
|
|
2712
2739
|
log(`Watching ${collections2.length} collection(s): ${collections2.map((f) => f.name).join(", ")}`);
|
|
2713
2740
|
};
|
|
2714
2741
|
await rebuildWatcher(config.collections);
|
|
2715
|
-
let
|
|
2742
|
+
let reloading = false;
|
|
2716
2743
|
const pollTimer = setInterval(() => {
|
|
2717
|
-
if (
|
|
2744
|
+
if (reloading)
|
|
2718
2745
|
return;
|
|
2719
2746
|
let nextConfig;
|
|
2720
2747
|
try {
|
|
@@ -2725,26 +2752,61 @@ async function startForeground() {
|
|
|
2725
2752
|
}
|
|
2726
2753
|
({ config: normalizedConfig, removedOverlappingCollections } = normalizeConfigCollections(nextConfig));
|
|
2727
2754
|
maybeLogOverlapWarning(removedOverlappingCollections);
|
|
2728
|
-
|
|
2755
|
+
const coreChanged = normalizedConfig.server !== config.server || normalizedConfig.token !== config.token || normalizedConfig.username !== config.username;
|
|
2756
|
+
if (!coreChanged) {
|
|
2757
|
+
const { nextConfig: reconciledConfig, added, removed } = reconcileCollections(config, normalizedConfig);
|
|
2758
|
+
if (added.length === 0 && removed.length === 0)
|
|
2759
|
+
return;
|
|
2760
|
+
reloading = true;
|
|
2761
|
+
(async () => {
|
|
2762
|
+
try {
|
|
2763
|
+
log(`Collections changed: +${added.map((c) => c.name).join(", ") || "none"}, -${removed.map((c) => c.name).join(", ") || "none"}`);
|
|
2764
|
+
config = reconciledConfig;
|
|
2765
|
+
syncer.setCollections(reconciledConfig.collections);
|
|
2766
|
+
await rebuildWatcher(reconciledConfig.collections);
|
|
2767
|
+
for (const collection of removed) {
|
|
2768
|
+
await syncer.purgeCollection(collection);
|
|
2769
|
+
}
|
|
2770
|
+
for (const collection of added) {
|
|
2771
|
+
await syncer.syncCollection(collection);
|
|
2772
|
+
}
|
|
2773
|
+
} catch (e) {
|
|
2774
|
+
log(`Failed to reload collections: ${e.message}`);
|
|
2775
|
+
} finally {
|
|
2776
|
+
reloading = false;
|
|
2777
|
+
}
|
|
2778
|
+
})();
|
|
2779
|
+
return;
|
|
2780
|
+
}
|
|
2781
|
+
reloading = true;
|
|
2729
2782
|
(async () => {
|
|
2730
2783
|
try {
|
|
2731
|
-
|
|
2732
|
-
if (
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2784
|
+
log("Config changed, reinitializing...");
|
|
2785
|
+
if (config.server !== normalizedConfig.server)
|
|
2786
|
+
log(` Server: ${config.server} -> ${normalizedConfig.server}`);
|
|
2787
|
+
if (config.username !== normalizedConfig.username)
|
|
2788
|
+
log(` Username: ${config.username} -> ${normalizedConfig.username}`);
|
|
2789
|
+
if (config.token !== normalizedConfig.token)
|
|
2790
|
+
log(` Token: updated`);
|
|
2791
|
+
syncer.stop();
|
|
2792
|
+
client = createClient(normalizedConfig.server, normalizedConfig.token);
|
|
2793
|
+
config = normalizedConfig;
|
|
2794
|
+
syncer = new Syncer({
|
|
2795
|
+
client,
|
|
2796
|
+
username: config.username,
|
|
2797
|
+
collections: config.collections,
|
|
2798
|
+
onLog: log
|
|
2799
|
+
});
|
|
2800
|
+
await rebuildWatcher(config.collections);
|
|
2801
|
+
if (config.collections.length > 0) {
|
|
2802
|
+
log("Running sync after reinitialize...");
|
|
2803
|
+
const { uploaded, skipped, deleted } = await syncer.initialSync();
|
|
2804
|
+
log(`Sync complete: ${uploaded} uploaded, ${skipped} skipped, ${deleted} deleted`);
|
|
2743
2805
|
}
|
|
2744
2806
|
} catch (e) {
|
|
2745
|
-
log(`Failed to
|
|
2807
|
+
log(`Failed to reinitialize: ${e.message}`);
|
|
2746
2808
|
} finally {
|
|
2747
|
-
|
|
2809
|
+
reloading = false;
|
|
2748
2810
|
}
|
|
2749
2811
|
})();
|
|
2750
2812
|
}, 1500);
|
|
@@ -2809,7 +2871,7 @@ async function status() {
|
|
|
2809
2871
|
console.log(`Seedvault Status
|
|
2810
2872
|
`);
|
|
2811
2873
|
console.log(` Server: ${config.server}`);
|
|
2812
|
-
console.log(` Contributor: ${config.
|
|
2874
|
+
console.log(` Contributor: ${config.username}`);
|
|
2813
2875
|
try {
|
|
2814
2876
|
const platform = detectPlatform();
|
|
2815
2877
|
const serviceName = platform === "macos" ? "launchd" : platform === "linux" ? "systemd" : "Task Scheduler";
|
|
@@ -2847,7 +2909,7 @@ async function ls(args) {
|
|
|
2847
2909
|
const config = loadConfig();
|
|
2848
2910
|
const client = createClient(config.server, config.token);
|
|
2849
2911
|
const prefix = args[0] || undefined;
|
|
2850
|
-
const { files } = await client.listFiles(config.
|
|
2912
|
+
const { files } = await client.listFiles(config.username, prefix);
|
|
2851
2913
|
if (files.length === 0) {
|
|
2852
2914
|
console.log(prefix ? `No files matching '${prefix}'.` : "No files in your contributor.");
|
|
2853
2915
|
return;
|
|
@@ -2879,7 +2941,7 @@ async function cat(args) {
|
|
|
2879
2941
|
const config = loadConfig();
|
|
2880
2942
|
const client = createClient(config.server, config.token);
|
|
2881
2943
|
try {
|
|
2882
|
-
const content = await client.getFile(config.
|
|
2944
|
+
const content = await client.getFile(config.username, filePath);
|
|
2883
2945
|
process.stdout.write(content);
|
|
2884
2946
|
} catch (e) {
|
|
2885
2947
|
if (e instanceof ApiError && e.status === 404) {
|
|
@@ -2902,9 +2964,8 @@ async function contributors() {
|
|
|
2902
2964
|
console.log(`Contributors:
|
|
2903
2965
|
`);
|
|
2904
2966
|
for (const contributor of contributors2) {
|
|
2905
|
-
const you = contributor.
|
|
2906
|
-
console.log(` ${contributor.
|
|
2907
|
-
console.log(` ID: ${contributor.id}`);
|
|
2967
|
+
const you = contributor.username === config.username ? " (you)" : "";
|
|
2968
|
+
console.log(` ${contributor.username}${you}`);
|
|
2908
2969
|
console.log(` Created: ${new Date(contributor.createdAt).toLocaleString()}`);
|
|
2909
2970
|
console.log();
|
|
2910
2971
|
}
|
|
@@ -2938,8 +2999,8 @@ Usage: sv <command> [options]
|
|
|
2938
2999
|
|
|
2939
3000
|
Setup:
|
|
2940
3001
|
init Interactive first-time setup
|
|
2941
|
-
init --server URL --token T
|
|
2942
|
-
init --server URL --name N
|
|
3002
|
+
init --server URL --token T --username U Non-interactive (existing token)
|
|
3003
|
+
init --server URL --name N Non-interactive (signup)
|
|
2943
3004
|
|
|
2944
3005
|
Collections:
|
|
2945
3006
|
add <path> [--name N] Add a collection path
|
|
@@ -2967,7 +3028,9 @@ async function main() {
|
|
|
2967
3028
|
return;
|
|
2968
3029
|
}
|
|
2969
3030
|
if (cmd === "--version" || cmd === "-v") {
|
|
2970
|
-
|
|
3031
|
+
const pkgPath = resolve6(import.meta.dirname, "..", "package.json");
|
|
3032
|
+
const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
3033
|
+
console.log(pkg.version);
|
|
2971
3034
|
return;
|
|
2972
3035
|
}
|
|
2973
3036
|
try {
|