@seedvault/cli 0.4.0 → 0.4.2
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 +141 -51
- package/package.json +6 -1
package/dist/sv.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// @bun
|
|
3
3
|
|
|
4
4
|
// src/index.ts
|
|
5
|
-
import { readFileSync as
|
|
6
|
-
import { resolve as
|
|
5
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
6
|
+
import { resolve as resolve7 } from "path";
|
|
7
7
|
|
|
8
8
|
// src/commands/init.ts
|
|
9
9
|
import * as readline from "readline/promises";
|
|
@@ -200,22 +200,48 @@ function createClient(serverUrl, token) {
|
|
|
200
200
|
return res.json();
|
|
201
201
|
},
|
|
202
202
|
async putFile(username, path, content) {
|
|
203
|
-
const res = await request("PUT", `/v1/
|
|
203
|
+
const res = await request("PUT", `/v1/files/${username}/${encodePath(path)}`, {
|
|
204
204
|
body: content,
|
|
205
205
|
contentType: "text/markdown"
|
|
206
206
|
});
|
|
207
207
|
return res.json();
|
|
208
208
|
},
|
|
209
209
|
async deleteFile(username, path) {
|
|
210
|
-
await request("DELETE", `/v1/
|
|
210
|
+
await request("DELETE", `/v1/files/${username}/${encodePath(path)}`);
|
|
211
211
|
},
|
|
212
212
|
async listFiles(username, prefix) {
|
|
213
|
-
const
|
|
214
|
-
const
|
|
215
|
-
|
|
213
|
+
const fullPrefix = prefix ? `${username}/${prefix}` : `${username}/`;
|
|
214
|
+
const qs = `?prefix=${encodeURIComponent(fullPrefix)}`;
|
|
215
|
+
const res = await request("GET", `/v1/files${qs}`);
|
|
216
|
+
const data = await res.json();
|
|
217
|
+
return {
|
|
218
|
+
files: data.files.map((f) => ({
|
|
219
|
+
...f,
|
|
220
|
+
path: f.path.startsWith(`${username}/`) ? f.path.slice(username.length + 1) : f.path
|
|
221
|
+
}))
|
|
222
|
+
};
|
|
216
223
|
},
|
|
217
224
|
async getFile(username, path) {
|
|
218
|
-
const
|
|
225
|
+
const fullPath = `${username}/${path}`;
|
|
226
|
+
const res = await request("POST", "/v1/sh", {
|
|
227
|
+
body: JSON.stringify({ cmd: `cat "${fullPath}"` }),
|
|
228
|
+
contentType: "application/json"
|
|
229
|
+
});
|
|
230
|
+
const exitCode = parseInt(res.headers.get("X-Exit-Code") || "0", 10);
|
|
231
|
+
if (exitCode !== 0) {
|
|
232
|
+
const stderr = decodeURIComponent(res.headers.get("X-Stderr") || "");
|
|
233
|
+
if (stderr.includes("No such file or directory")) {
|
|
234
|
+
throw new ApiError(404, "File not found");
|
|
235
|
+
}
|
|
236
|
+
throw new ApiError(500, stderr || `cat exited with code ${exitCode}`);
|
|
237
|
+
}
|
|
238
|
+
return res.text();
|
|
239
|
+
},
|
|
240
|
+
async sh(cmd) {
|
|
241
|
+
const res = await request("POST", "/v1/sh", {
|
|
242
|
+
body: JSON.stringify({ cmd }),
|
|
243
|
+
contentType: "application/json"
|
|
244
|
+
});
|
|
219
245
|
return res.text();
|
|
220
246
|
},
|
|
221
247
|
async health() {
|
|
@@ -2012,12 +2038,26 @@ function watch(paths, options = {}) {
|
|
|
2012
2038
|
import { relative as relative4 } from "path";
|
|
2013
2039
|
function createWatcher(collections2, onEvent) {
|
|
2014
2040
|
const paths = collections2.map((f) => f.path);
|
|
2041
|
+
const shouldIgnore = (filePath) => {
|
|
2042
|
+
for (const col of collections2) {
|
|
2043
|
+
if (filePath.startsWith(col.path + "/") || filePath === col.path) {
|
|
2044
|
+
const relPath = filePath.slice(col.path.length + 1);
|
|
2045
|
+
const segments = relPath.split("/");
|
|
2046
|
+
for (const seg of segments) {
|
|
2047
|
+
if (seg.startsWith("."))
|
|
2048
|
+
return true;
|
|
2049
|
+
if (seg === "node_modules")
|
|
2050
|
+
return true;
|
|
2051
|
+
if (seg.includes(".tmp."))
|
|
2052
|
+
return true;
|
|
2053
|
+
}
|
|
2054
|
+
return false;
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
return false;
|
|
2058
|
+
};
|
|
2015
2059
|
const watcher = watch(paths, {
|
|
2016
|
-
ignored:
|
|
2017
|
-
/(^|[/\\])\./,
|
|
2018
|
-
"**/node_modules/**",
|
|
2019
|
-
"**/*.tmp.*"
|
|
2020
|
-
],
|
|
2060
|
+
ignored: shouldIgnore,
|
|
2021
2061
|
persistent: true,
|
|
2022
2062
|
ignoreInitial: true,
|
|
2023
2063
|
awaitWriteFinish: {
|
|
@@ -2164,8 +2204,37 @@ class Syncer {
|
|
|
2164
2204
|
skipped += result.skipped;
|
|
2165
2205
|
deleted += result.deleted;
|
|
2166
2206
|
}
|
|
2207
|
+
deleted += await this.purgeOrphans();
|
|
2167
2208
|
return { uploaded, skipped, deleted };
|
|
2168
2209
|
}
|
|
2210
|
+
async purgeOrphans() {
|
|
2211
|
+
let deleted = 0;
|
|
2212
|
+
const { files: allServerFiles } = await this.client.listFiles(this.username);
|
|
2213
|
+
const collectionNames = new Set(this.collections.map((c) => c.name));
|
|
2214
|
+
const orphans = allServerFiles.filter((f) => {
|
|
2215
|
+
const prefix = f.path.split("/")[0];
|
|
2216
|
+
return !collectionNames.has(prefix);
|
|
2217
|
+
});
|
|
2218
|
+
if (orphans.length === 0)
|
|
2219
|
+
return 0;
|
|
2220
|
+
this.log(`Purging ${orphans.length} orphaned file(s) from removed collections...`);
|
|
2221
|
+
await pooled(orphans, SYNC_CONCURRENCY, async (f) => {
|
|
2222
|
+
try {
|
|
2223
|
+
await this.client.deleteFile(this.username, f.path);
|
|
2224
|
+
deleted++;
|
|
2225
|
+
} catch {
|
|
2226
|
+
this.queue.enqueue({
|
|
2227
|
+
type: "delete",
|
|
2228
|
+
username: this.username,
|
|
2229
|
+
serverPath: f.path,
|
|
2230
|
+
content: null,
|
|
2231
|
+
queuedAt: new Date().toISOString()
|
|
2232
|
+
});
|
|
2233
|
+
}
|
|
2234
|
+
});
|
|
2235
|
+
this.log(` Purged ${deleted} orphaned file(s)`);
|
|
2236
|
+
return deleted;
|
|
2237
|
+
}
|
|
2169
2238
|
async syncCollection(collection) {
|
|
2170
2239
|
let uploaded = 0;
|
|
2171
2240
|
let skipped = 0;
|
|
@@ -2908,27 +2977,8 @@ async function status() {
|
|
|
2908
2977
|
async function ls(args) {
|
|
2909
2978
|
const config = loadConfig();
|
|
2910
2979
|
const client = createClient(config.server, config.token);
|
|
2911
|
-
const
|
|
2912
|
-
|
|
2913
|
-
if (files.length === 0) {
|
|
2914
|
-
console.log(prefix ? `No files matching '${prefix}'.` : "No files in your contributor.");
|
|
2915
|
-
return;
|
|
2916
|
-
}
|
|
2917
|
-
const maxPath = Math.max(...files.map((f) => f.path.length));
|
|
2918
|
-
for (const f of files) {
|
|
2919
|
-
const size = formatSize(f.size);
|
|
2920
|
-
const date = new Date(f.modifiedAt).toLocaleString();
|
|
2921
|
-
console.log(` ${f.path.padEnd(maxPath + 2)} ${size.padStart(8)} ${date}`);
|
|
2922
|
-
}
|
|
2923
|
-
console.log(`
|
|
2924
|
-
${files.length} file(s)`);
|
|
2925
|
-
}
|
|
2926
|
-
function formatSize(bytes) {
|
|
2927
|
-
if (bytes < 1024)
|
|
2928
|
-
return `${bytes} B`;
|
|
2929
|
-
if (bytes < 1024 * 1024)
|
|
2930
|
-
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
2931
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
2980
|
+
const output = await client.sh(`ls ${args.join(" ")}`);
|
|
2981
|
+
process.stdout.write(output);
|
|
2932
2982
|
}
|
|
2933
2983
|
|
|
2934
2984
|
// src/commands/cat.ts
|
|
@@ -2937,19 +2987,25 @@ async function cat(args) {
|
|
|
2937
2987
|
console.error("Usage: sv cat <path>");
|
|
2938
2988
|
process.exit(1);
|
|
2939
2989
|
}
|
|
2940
|
-
const filePath = args[0];
|
|
2941
2990
|
const config = loadConfig();
|
|
2942
2991
|
const client = createClient(config.server, config.token);
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2992
|
+
const output = await client.sh(`cat ${args.join(" ")}`);
|
|
2993
|
+
process.stdout.write(output);
|
|
2994
|
+
}
|
|
2995
|
+
|
|
2996
|
+
// src/commands/sh.ts
|
|
2997
|
+
async function sh(args) {
|
|
2998
|
+
if (args.length === 0) {
|
|
2999
|
+
console.error("Usage: sv sh <command>");
|
|
3000
|
+
console.error('Example: sv sh "ls -la yiliu/"');
|
|
3001
|
+
console.error('Example: sv sh "grep -r pattern ."');
|
|
3002
|
+
process.exit(1);
|
|
2952
3003
|
}
|
|
3004
|
+
const cmd = args.join(" ");
|
|
3005
|
+
const config = loadConfig();
|
|
3006
|
+
const client = createClient(config.server, config.token);
|
|
3007
|
+
const output = await client.sh(cmd);
|
|
3008
|
+
process.stdout.write(output);
|
|
2953
3009
|
}
|
|
2954
3010
|
|
|
2955
3011
|
// src/commands/contributors.ts
|
|
@@ -2991,6 +3047,31 @@ Share this with the person you want to invite.`);
|
|
|
2991
3047
|
}
|
|
2992
3048
|
}
|
|
2993
3049
|
|
|
3050
|
+
// src/commands/upgrade.ts
|
|
3051
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
3052
|
+
import { resolve as resolve6 } from "path";
|
|
3053
|
+
var {$ } = globalThis.Bun;
|
|
3054
|
+
var INSTALL_SCRIPT_URL = "https://raw.githubusercontent.com/collaborator-ai/seedvault/main/install-cli.sh";
|
|
3055
|
+
async function upgrade() {
|
|
3056
|
+
const pkgPath = resolve6(import.meta.dirname, "..", "..", "package.json");
|
|
3057
|
+
let currentVersion = "unknown";
|
|
3058
|
+
try {
|
|
3059
|
+
const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
3060
|
+
currentVersion = pkg.version;
|
|
3061
|
+
} catch {}
|
|
3062
|
+
console.log(`Current version: ${currentVersion}`);
|
|
3063
|
+
console.log(`Fetching latest from: ${INSTALL_SCRIPT_URL}
|
|
3064
|
+
`);
|
|
3065
|
+
const result = await $`curl -fsSL ${INSTALL_SCRIPT_URL} | bash -s -- --no-onboard`.quiet().nothrow();
|
|
3066
|
+
if (result.exitCode !== 0) {
|
|
3067
|
+
console.error("Upgrade failed:");
|
|
3068
|
+
console.error(result.stderr.toString());
|
|
3069
|
+
process.exit(1);
|
|
3070
|
+
}
|
|
3071
|
+
console.log(result.stdout.toString());
|
|
3072
|
+
console.log("Upgrade complete. Run 'sv --version' to verify.");
|
|
3073
|
+
}
|
|
3074
|
+
|
|
2994
3075
|
// src/index.ts
|
|
2995
3076
|
var USAGE = `
|
|
2996
3077
|
Seedvault CLI
|
|
@@ -2998,9 +3079,10 @@ Seedvault CLI
|
|
|
2998
3079
|
Usage: sv <command> [options]
|
|
2999
3080
|
|
|
3000
3081
|
Setup:
|
|
3001
|
-
init
|
|
3002
|
-
init --server URL --token T
|
|
3003
|
-
init --server URL --name N
|
|
3082
|
+
init Interactive first-time setup
|
|
3083
|
+
init --server URL --token T Non-interactive (existing token)
|
|
3084
|
+
init --server URL --name N [--invite CODE] Non-interactive (signup)
|
|
3085
|
+
init --force Overwrite existing config
|
|
3004
3086
|
|
|
3005
3087
|
Collections:
|
|
3006
3088
|
add <path> [--name N] Add a collection path
|
|
@@ -3014,12 +3096,16 @@ Daemon:
|
|
|
3014
3096
|
status Show sync status
|
|
3015
3097
|
|
|
3016
3098
|
Files:
|
|
3017
|
-
|
|
3018
|
-
|
|
3099
|
+
sh <command> Run a shell command on the vault (ls, cat, grep, etc.)
|
|
3100
|
+
ls [args...] Shorthand for: sv sh "ls [args...]"
|
|
3101
|
+
cat <path> Shorthand for: sv sh "cat <path>"
|
|
3019
3102
|
|
|
3020
3103
|
Vault:
|
|
3021
3104
|
contributors List all contributors
|
|
3022
3105
|
invite Generate an invite code (operator only)
|
|
3106
|
+
|
|
3107
|
+
Maintenance:
|
|
3108
|
+
upgrade Upgrade CLI to latest version
|
|
3023
3109
|
`.trim();
|
|
3024
3110
|
async function main() {
|
|
3025
3111
|
const [cmd, ...args] = process.argv.slice(2);
|
|
@@ -3028,8 +3114,8 @@ async function main() {
|
|
|
3028
3114
|
return;
|
|
3029
3115
|
}
|
|
3030
3116
|
if (cmd === "--version" || cmd === "-v") {
|
|
3031
|
-
const pkgPath =
|
|
3032
|
-
const pkg = JSON.parse(
|
|
3117
|
+
const pkgPath = resolve7(import.meta.dirname, "..", "package.json");
|
|
3118
|
+
const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
|
|
3033
3119
|
console.log(pkg.version);
|
|
3034
3120
|
return;
|
|
3035
3121
|
}
|
|
@@ -3049,6 +3135,8 @@ async function main() {
|
|
|
3049
3135
|
return await stop();
|
|
3050
3136
|
case "status":
|
|
3051
3137
|
return await status();
|
|
3138
|
+
case "sh":
|
|
3139
|
+
return await sh(args);
|
|
3052
3140
|
case "ls":
|
|
3053
3141
|
return await ls(args);
|
|
3054
3142
|
case "cat":
|
|
@@ -3057,6 +3145,8 @@ async function main() {
|
|
|
3057
3145
|
return await contributors();
|
|
3058
3146
|
case "invite":
|
|
3059
3147
|
return await invite();
|
|
3148
|
+
case "upgrade":
|
|
3149
|
+
return await upgrade();
|
|
3060
3150
|
default:
|
|
3061
3151
|
console.error(`Unknown command: ${cmd}
|
|
3062
3152
|
`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seedvault/cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"sv": "bin/sv.mjs"
|
|
@@ -15,6 +15,11 @@
|
|
|
15
15
|
"dist",
|
|
16
16
|
"bin"
|
|
17
17
|
],
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/collaborator-ai/seedvault.git",
|
|
21
|
+
"directory": "cli"
|
|
22
|
+
},
|
|
18
23
|
"dependencies": {
|
|
19
24
|
"chokidar": "^4"
|
|
20
25
|
},
|