northbase 0.1.2 → 0.1.4
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/README.md +26 -0
- package/package.json +1 -1
- package/src/northbase.mjs +69 -3
package/README.md
CHANGED
|
@@ -103,6 +103,32 @@ On success, prints a single confirmation line to stdout:
|
|
|
103
103
|
PUT ok test/cli.md bytes=12 updated_at=2024-01-15T10:30:00.000Z
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
+
### List remote files
|
|
107
|
+
|
|
108
|
+
Prints every file path stored remotely, one per line. Useful for discovery or scripting.
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
northbase list # all files
|
|
112
|
+
northbase list memory/ # only paths starting with "memory/"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Pull (bulk sync)
|
|
116
|
+
|
|
117
|
+
Downloads all remote files whose `updated_at` differs from the local cache. Useful for "discover and mirror all your notes so an agent can read them locally."
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
northbase pull # sync everything
|
|
121
|
+
northbase pull memory/ # sync only files under memory/ prefix
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Prints a summary line to stdout:
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
PULL ok files=12 downloaded=3 skipped=9
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Per-file download progress goes to stderr. Pull never deletes local files.
|
|
131
|
+
|
|
106
132
|
## Local mirror
|
|
107
133
|
|
|
108
134
|
All files are mirrored at:
|
package/package.json
CHANGED
package/src/northbase.mjs
CHANGED
|
@@ -205,6 +205,68 @@ async function putFile(rel, content) {
|
|
|
205
205
|
return { bytes, updated_at };
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
// ── concurrency helper ────────────────────────────────────────────────────────
|
|
209
|
+
|
|
210
|
+
async function concurrentMap(items, limit, fn) {
|
|
211
|
+
const results = [];
|
|
212
|
+
let i = 0;
|
|
213
|
+
async function worker() {
|
|
214
|
+
while (i < items.length) {
|
|
215
|
+
const idx = i++;
|
|
216
|
+
results[idx] = await fn(items[idx]);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
|
|
220
|
+
return results;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ── list / pull commands ──────────────────────────────────────────────────────
|
|
224
|
+
|
|
225
|
+
async function cmdList(prefix) {
|
|
226
|
+
const supabase = await getAuthenticatedClient();
|
|
227
|
+
let query = supabase.from("files").select("path").order("path", { ascending: true });
|
|
228
|
+
if (prefix) query = query.like("path", `${prefix}%`);
|
|
229
|
+
const { data, error } = await query;
|
|
230
|
+
if (error) throw error;
|
|
231
|
+
for (const row of data) process.stdout.write(row.path + "\n");
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async function cmdPull(prefix) {
|
|
235
|
+
ensureDir(ROOT);
|
|
236
|
+
const idx = loadIndex();
|
|
237
|
+
const supabase = await getAuthenticatedClient();
|
|
238
|
+
|
|
239
|
+
let query = supabase.from("files").select("path, updated_at").order("path", { ascending: true });
|
|
240
|
+
if (prefix) query = query.like("path", `${prefix}%`);
|
|
241
|
+
const { data: remote, error } = await query;
|
|
242
|
+
if (error) throw error;
|
|
243
|
+
|
|
244
|
+
let downloaded = 0, skipped = 0;
|
|
245
|
+
|
|
246
|
+
await concurrentMap(remote, 5, async (row) => {
|
|
247
|
+
const rel = row.path;
|
|
248
|
+
const cached = idx.files?.[rel];
|
|
249
|
+
if (cached?.updated_at === row.updated_at) {
|
|
250
|
+
skipped++;
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
console.error("NORTHBASE PULL download", rel);
|
|
254
|
+
const { data: rows, error: err } = await supabase
|
|
255
|
+
.from("files").select("content, updated_at").eq("path", rel).limit(1);
|
|
256
|
+
if (err) throw err;
|
|
257
|
+
const content = rows?.[0]?.content ?? "";
|
|
258
|
+
const updated_at = rows?.[0]?.updated_at ?? row.updated_at;
|
|
259
|
+
const bytes = Buffer.byteLength(content, "utf8");
|
|
260
|
+
if (bytes > MAX_BYTES) throw new Error(`File too large (${bytes} bytes): ${rel}`);
|
|
261
|
+
writeLocal(rel, content);
|
|
262
|
+
idx.files[rel] = { updated_at, bytes };
|
|
263
|
+
downloaded++;
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
saveIndex(idx);
|
|
267
|
+
console.log(`PULL ok files=${remote.length} downloaded=${downloaded} skipped=${skipped}`);
|
|
268
|
+
}
|
|
269
|
+
|
|
208
270
|
// ── interactive prompts ───────────────────────────────────────────────────────
|
|
209
271
|
|
|
210
272
|
async function promptLine(question) {
|
|
@@ -299,9 +361,11 @@ async function cmdWhoami() {
|
|
|
299
361
|
async function main() {
|
|
300
362
|
const [cmd, ...args] = process.argv.slice(2);
|
|
301
363
|
|
|
302
|
-
if (cmd === "login") { await cmdLogin();
|
|
303
|
-
if (cmd === "logout") { await cmdLogout();
|
|
304
|
-
if (cmd === "whoami") { await cmdWhoami();
|
|
364
|
+
if (cmd === "login") { await cmdLogin(); return; }
|
|
365
|
+
if (cmd === "logout") { await cmdLogout(); return; }
|
|
366
|
+
if (cmd === "whoami") { await cmdWhoami(); return; }
|
|
367
|
+
if (cmd === "list") { await cmdList(args[0]); return; }
|
|
368
|
+
if (cmd === "pull") { await cmdPull(args[0]); return; }
|
|
305
369
|
|
|
306
370
|
if (cmd === "get") {
|
|
307
371
|
const rel = args[0];
|
|
@@ -326,6 +390,8 @@ async function main() {
|
|
|
326
390
|
console.log(" northbase login");
|
|
327
391
|
console.log(" northbase logout");
|
|
328
392
|
console.log(" northbase whoami");
|
|
393
|
+
console.log(" northbase list [prefix]");
|
|
394
|
+
console.log(" northbase pull [prefix]");
|
|
329
395
|
console.log(" northbase get <path>");
|
|
330
396
|
console.log(" northbase put <path>");
|
|
331
397
|
process.exit(1);
|