@rigxyz/tapd 0.3.0 → 0.4.1

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/bin.js CHANGED
@@ -2,23 +2,29 @@
2
2
  import {
3
3
  AlreadyInitializedError,
4
4
  AlreadyJoinedError,
5
+ ImportTokenScopeError,
6
+ ImportValidationFailed,
5
7
  InvalidInviteUrlError,
6
8
  NotInitializedError,
7
9
  RelayClient,
8
10
  STATE_KEY_CONFLICT_COUNT,
11
+ addImport,
9
12
  bindingConfigFile,
10
13
  computeLocalDiff,
11
14
  findConflictSidecars,
15
+ importStateDbPath,
12
16
  init,
13
17
  join,
14
18
  list,
15
19
  loadIgnore,
20
+ loadImportMounts,
16
21
  mint,
17
22
  openStateDb,
18
23
  readBindingConfig,
24
+ removeImport,
19
25
  revoke,
20
26
  start
21
- } from "./chunk-N3KRCOBG.js";
27
+ } from "./chunk-JBX2ZRMD.js";
22
28
 
23
29
  // src/bin.ts
24
30
  import { join as pathJoin, resolve as resolve2 } from "path";
@@ -272,7 +278,7 @@ import { existsSync as existsSync4 } from "fs";
272
278
  import { join as join4 } from "path";
273
279
 
274
280
  // src/version.ts
275
- var TAPD_VERSION = "0.3.0";
281
+ var TAPD_VERSION = "0.4.1";
276
282
 
277
283
  // src/status.ts
278
284
  var STATE_KEY_LAST_APPLY_ERROR = "last_apply_error";
@@ -284,9 +290,9 @@ function statusToJson(report) {
284
290
  return {
285
291
  protocolVersion: STATUS_PROTOCOL_VERSION,
286
292
  version: TAPD_VERSION,
287
- bindingId: report.config.bindingId,
288
- relayUrl: report.config.relayUrl,
289
- deviceId: report.config.deviceId,
293
+ bindingId: report.config?.bindingId ?? null,
294
+ relayUrl: report.config?.relayUrl ?? null,
295
+ deviceId: report.config?.deviceId ?? null,
290
296
  daemon: report.daemon,
291
297
  cursor: report.cursor,
292
298
  remoteCursor: report.remoteCursor,
@@ -295,13 +301,65 @@ function statusToJson(report) {
295
301
  pendingUploads: report.pendingUploads,
296
302
  conflicts: report.conflicts,
297
303
  lastApplyError: report.lastApplyError,
298
- offline: report.offline
304
+ offline: report.offline,
305
+ mounts: report.mounts
299
306
  };
300
307
  }
308
+ function readMounts(rootDir) {
309
+ const loaded = loadImportMounts(rootDir);
310
+ const mounts = [];
311
+ const readyPaths = [];
312
+ for (const spec of loaded.ready) {
313
+ let cursor;
314
+ const dbPath = importStateDbPath(rootDir, spec.name);
315
+ if (existsSync4(dbPath)) {
316
+ const db = openStateDb(dbPath);
317
+ try {
318
+ cursor = db.getCursor();
319
+ } finally {
320
+ db.close();
321
+ }
322
+ }
323
+ mounts.push({
324
+ name: spec.name,
325
+ path: spec.path,
326
+ state: "ok",
327
+ bindingId: spec.bindingId,
328
+ ...cursor ? { cursor } : {}
329
+ });
330
+ readyPaths.push(spec.path);
331
+ }
332
+ for (const b of loaded.broken) {
333
+ mounts.push({ name: b.name, path: b.path, state: "misconfigured", reason: b.reason });
334
+ }
335
+ mounts.sort((a, b) => a.name.localeCompare(b.name));
336
+ return { mounts, readyPaths };
337
+ }
301
338
  async function status(opts) {
302
339
  const config = readBindingConfig(opts.rootDir);
340
+ const { mounts, readyPaths } = readMounts(opts.rootDir);
341
+ const pidfilePath = join4(opts.rootDir, DAEMON_PIDFILE_REL);
342
+ const livePid = readLivePid(pidfilePath);
343
+ const daemon = livePid !== null ? { running: true, pid: livePid } : { running: false };
303
344
  if (!config) {
304
- throw new NotInitializedError(opts.rootDir);
345
+ if (mounts.length === 0) {
346
+ throw new NotInitializedError(opts.rootDir);
347
+ }
348
+ return {
349
+ config: null,
350
+ cursor: "chg_0",
351
+ remoteCursor: "chg_0",
352
+ trackedPaths: 0,
353
+ pendingPeek: [],
354
+ pendingApplies: 0,
355
+ pendingUploads: 0,
356
+ conflicts: [],
357
+ conflictCount: 0,
358
+ lastApplyError: null,
359
+ daemon,
360
+ offline: false,
361
+ mounts
362
+ };
305
363
  }
306
364
  const db = openStateDb(join4(opts.rootDir, ".rig", "tap", "state.local.db"));
307
365
  try {
@@ -314,9 +372,6 @@ async function status(opts) {
314
372
  const lastErrJson = db.getMeta(STATE_KEY_LAST_APPLY_ERROR);
315
373
  const lastErrAt = db.getMeta(STATE_KEY_LAST_APPLY_ERROR_AT);
316
374
  const lastApplyError = lastErrJson ? safeParseLastError(lastErrJson, lastErrAt) : null;
317
- const pidfilePath = join4(opts.rootDir, DAEMON_PIDFILE_REL);
318
- const livePid = readLivePid(pidfilePath);
319
- const daemon = livePid !== null ? { running: true, pid: livePid } : { running: false };
320
375
  const client = new RelayClient({
321
376
  baseUrl: config.relayUrl,
322
377
  bindingId: config.bindingId,
@@ -341,7 +396,12 @@ async function status(opts) {
341
396
  let pendingUploads = 0;
342
397
  if (existsSync4(opts.rootDir)) {
343
398
  const ignore = loadIgnore(opts.rootDir);
344
- const diff = await computeLocalDiff({ rootDir: opts.rootDir, stateDb: db, ignore });
399
+ const diff = await computeLocalDiff({
400
+ rootDir: opts.rootDir,
401
+ stateDb: db,
402
+ ignore,
403
+ excludePaths: readyPaths
404
+ });
345
405
  pendingUploads = diff.writes.length + diff.deletes.length + diff.mkdirs.length + diff.rmdirs.length;
346
406
  }
347
407
  const conflicts = findConflictSidecars(opts.rootDir);
@@ -357,7 +417,8 @@ async function status(opts) {
357
417
  conflictCount,
358
418
  lastApplyError,
359
419
  daemon,
360
- offline
420
+ offline,
421
+ mounts
361
422
  };
362
423
  } finally {
363
424
  db.close();
@@ -506,6 +567,7 @@ ${C.bold().cyan("COMMANDS")}
506
567
  ${C.green("tapd invite")} mint / list / revoke invite URLs (owner only)
507
568
  ${C.green("tapd members")} list / remove / set-role for binding members (owner only for write ops)
508
569
  ${C.green("tapd join")} accept an invite URL into the current directory
570
+ ${C.green("tapd import")} mount another binding read-only into a subdir (meta-rig)
509
571
  ${C.green("tapd start")} run the watch + apply daemon (foreground)
510
572
  ${C.green("tapd status")} show binding + cursor + pending remote events
511
573
  ${C.green("tapd history")} list ChangeEvents for a single path (recovery)
@@ -892,6 +954,129 @@ async function runJoin(positional2, flags2) {
892
954
  console.log("");
893
955
  console.log(C.dim(" Next: `tapd start` (initial apply pulls the remote manifest)."));
894
956
  }
957
+ async function runImport(positional2, flags2) {
958
+ const json = flags2.get("json") === true;
959
+ const rootDir = resolve2(flags2.get("dir") ?? process.cwd());
960
+ const sub = positional2[0];
961
+ if (sub === "remove") {
962
+ const name = positional2[1];
963
+ if (!name) {
964
+ if (json) printJsonError("missing_argument", "usage: tapd import remove <name>");
965
+ else console.error(kleur.red("error:"), "usage: tapd import remove <name>");
966
+ process.exit(2);
967
+ }
968
+ let accessToken2 = null;
969
+ try {
970
+ accessToken2 = readHubToken();
971
+ } catch {
972
+ accessToken2 = null;
973
+ }
974
+ const result2 = await removeImport({
975
+ rootDir,
976
+ name,
977
+ purge: flags2.get("purge") === true,
978
+ accessToken: accessToken2
979
+ });
980
+ if (json) {
981
+ printJson({ protocolVersion: PROTOCOL_VERSION, ...result2, name });
982
+ return;
983
+ }
984
+ const C2 = kleur;
985
+ if (!result2.removed) {
986
+ console.log(C2.dim(`no import named "${name}"`));
987
+ return;
988
+ }
989
+ console.log(`${C2.green("\u2713")} removed import ${C2.bold(name)}${result2.purged ? C2.dim(" (purged files)") : ""}`);
990
+ switch (result2.revoke) {
991
+ case "revoked":
992
+ console.log(C2.dim(" grant relinquished on the relay."));
993
+ break;
994
+ case "not-creator":
995
+ console.log(
996
+ C2.yellow(" note:") + " the grant belongs to another user and remains active \u2014 only its owner can revoke it."
997
+ );
998
+ break;
999
+ case "no-token":
1000
+ console.log(C2.yellow(" note:") + " not signed in \u2014 the grant was not revoked. Run `rig login`, then re-remove, to relinquish it.");
1001
+ break;
1002
+ case "offline":
1003
+ console.log(C2.yellow(" note:") + " relay unreachable \u2014 local mount removed, grant not yet revoked.");
1004
+ break;
1005
+ }
1006
+ console.log(C2.dim(" restart the daemon to apply: `tapd start`"));
1007
+ return;
1008
+ }
1009
+ const inviteUrl = sub;
1010
+ const mountPath = flags2.get("path");
1011
+ if (!inviteUrl || !mountPath) {
1012
+ const msg = "usage: tapd import <invite-url> --path <subdir> [--name <name>]";
1013
+ if (json) printJsonError("missing_argument", msg);
1014
+ else console.error(kleur.red("error:"), msg);
1015
+ process.exit(2);
1016
+ }
1017
+ const anonymous = flags2.get("anonymous") === true;
1018
+ let accessToken = null;
1019
+ if (!anonymous) {
1020
+ try {
1021
+ accessToken = readHubToken();
1022
+ } catch (err) {
1023
+ if (err instanceof HubTokenMissingError) {
1024
+ if (json) printJsonError("not_logged_in", err.message);
1025
+ else {
1026
+ console.error(kleur.red("error:"), err.message);
1027
+ console.error(kleur.dim(" pass --anonymous for a pure-capability invite, or run `rig login`."));
1028
+ }
1029
+ process.exit(1);
1030
+ }
1031
+ throw err;
1032
+ }
1033
+ }
1034
+ let result;
1035
+ try {
1036
+ result = await addImport({
1037
+ rootDir,
1038
+ inviteUrl,
1039
+ path: mountPath,
1040
+ name: flags2.get("name"),
1041
+ accessToken,
1042
+ deviceLabel: flags2.get("device-label")
1043
+ });
1044
+ } catch (err) {
1045
+ if (err instanceof ImportValidationFailed) {
1046
+ if (json) printJsonError("import_invalid", err.message);
1047
+ else console.error(kleur.red("error:"), err.message);
1048
+ process.exit(2);
1049
+ }
1050
+ if (err instanceof ImportTokenScopeError) {
1051
+ if (json) printJsonError("insufficient_ops", err.message);
1052
+ else console.error(kleur.red("error:"), err.message);
1053
+ process.exit(1);
1054
+ }
1055
+ if (err instanceof InvalidInviteUrlError) {
1056
+ if (json) printJsonError("invalid_invite_url", err.message);
1057
+ else console.error(kleur.red("error:"), err.message);
1058
+ process.exit(2);
1059
+ }
1060
+ throw err;
1061
+ }
1062
+ if (json) {
1063
+ printJson({ protocolVersion: PROTOCOL_VERSION, ...result });
1064
+ return;
1065
+ }
1066
+ const C = kleur;
1067
+ console.log(C.bold().cyan("tapd import"));
1068
+ console.log(` name: ${C.bold(result.name)}`);
1069
+ console.log(` mount: ${result.path} ${C.dim("(read-only mirror)")}`);
1070
+ console.log(` binding: ${result.bindingId}`);
1071
+ console.log(` ops: ${result.ops.join(", ")}`);
1072
+ console.log(C.dim(` Token stored in ${rootDir}/.rig/tap/imports/${result.name}.local.json (mode 0600).`));
1073
+ if (result.importerBindingVerified === false) {
1074
+ console.log(
1075
+ C.yellow(" note:") + " recorded as a personal import (you \u2192 source); the rig-level edge needs membership in this meta-rig."
1076
+ );
1077
+ }
1078
+ console.log(C.dim(" Next: (re)start the daemon to begin mirroring \u2014 `tapd start`."));
1079
+ }
895
1080
  async function runStatus(flags2) {
896
1081
  const json = flags2.get("json") === true;
897
1082
  const rootDir = resolve2(flags2.get("dir") ?? process.cwd());
@@ -914,31 +1099,45 @@ async function runStatus(flags2) {
914
1099
  }
915
1100
  const C = kleur;
916
1101
  console.log(C.bold().cyan("tapd status"));
917
- console.log(` binding: ${C.bold(report.config.bindingId)}`);
918
- console.log(` relay: ${report.config.relayUrl}`);
919
- console.log(` device: ${C.bold(report.config.deviceId)}`);
920
- console.log(` cursor: ${report.cursor}`);
921
- console.log(` tracked: ${report.trackedPaths} paths`);
922
- if (report.pendingPeek.length === 0) {
923
- console.log(` pending: ${C.green("up to date")}`);
924
- } else {
925
- console.log(` pending: ${C.yellow(`${report.pendingPeek.length}+ events behind`)}`);
926
- for (const ev of report.pendingPeek) {
927
- console.log(C.dim(` ${ev.op.padEnd(6)} ${ev.path} (${ev.id})`));
1102
+ if (report.config) {
1103
+ console.log(` binding: ${C.bold(report.config.bindingId)}`);
1104
+ console.log(` relay: ${report.config.relayUrl}`);
1105
+ console.log(` device: ${C.bold(report.config.deviceId)}`);
1106
+ console.log(` cursor: ${report.cursor}`);
1107
+ console.log(` tracked: ${report.trackedPaths} paths`);
1108
+ if (report.pendingPeek.length === 0) {
1109
+ console.log(` pending: ${C.green("up to date")}`);
1110
+ } else {
1111
+ console.log(` pending: ${C.yellow(`${report.pendingPeek.length}+ events behind`)}`);
1112
+ for (const ev of report.pendingPeek) {
1113
+ console.log(C.dim(` ${ev.op.padEnd(6)} ${ev.path} (${ev.id})`));
1114
+ }
1115
+ }
1116
+ if (report.conflictCount > 0) {
1117
+ console.log(
1118
+ ` conflicts: ${C.yellow(String(report.conflictCount))} ${C.dim("(sidecars at *.conflict-from.*)")}`
1119
+ );
1120
+ } else {
1121
+ console.log(` conflicts: ${C.dim("none")}`);
1122
+ }
1123
+ if (report.lastApplyError) {
1124
+ const e = report.lastApplyError;
1125
+ console.log(
1126
+ ` last err: ${C.red(e.reason)} ${C.dim(`(${e.op} ${e.path} @ ${e.eventId}${e.at ? `, ${e.at}` : ""})`)}`
1127
+ );
928
1128
  }
929
- }
930
- if (report.conflictCount > 0) {
931
- console.log(
932
- ` conflicts: ${C.yellow(String(report.conflictCount))} ${C.dim("(sidecars at *.conflict-from.*)")}`
933
- );
934
1129
  } else {
935
- console.log(` conflicts: ${C.dim("none")}`);
1130
+ console.log(C.dim(" (import-only meta-rig \u2014 no primary binding)"));
936
1131
  }
937
- if (report.lastApplyError) {
938
- const e = report.lastApplyError;
939
- console.log(
940
- ` last err: ${C.red(e.reason)} ${C.dim(`(${e.op} ${e.path} @ ${e.eventId}${e.at ? `, ${e.at}` : ""})`)}`
941
- );
1132
+ if (report.mounts.length > 0) {
1133
+ console.log(` imports:`);
1134
+ for (const m of report.mounts) {
1135
+ if (m.state === "ok") {
1136
+ console.log(` ${C.green("\u25CF")} ${C.bold(m.name)} \u2192 ${m.path}${m.cursor ? C.dim(` (${m.cursor})`) : ""}`);
1137
+ } else {
1138
+ console.log(` ${C.yellow("\u25CB")} ${C.bold(m.name)} ${C.yellow("misconfigured")} ${C.dim(`(${m.reason ?? "unknown"})`)}`);
1139
+ }
1140
+ }
942
1141
  }
943
1142
  }
944
1143
  async function runUninit(flags2) {
@@ -1269,6 +1468,9 @@ if (flags.get("version") === true || command === "-v") {
1269
1468
  case "join":
1270
1469
  await runJoin(positional, flags);
1271
1470
  return;
1471
+ case "import":
1472
+ await runImport(positional, flags);
1473
+ return;
1272
1474
  case "start":
1273
1475
  await runStart(flags);
1274
1476
  return;