@rigxyz/tapd 0.6.5 → 0.6.8
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 +44 -5
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-7RKYNS5V.js → chunk-S5AVBVLP.js} +0 -0
- package/dist/chunk-S5AVBVLP.js.map +1 -0
- package/dist/index.d.ts +134 -18
- package/dist/index.js +5 -1
- package/package.json +3 -2
- package/dist/chunk-7RKYNS5V.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -9,19 +9,28 @@ import { GetBindingResponse, PrepareUploadRequest, PrepareUploadResponse, Comple
|
|
|
9
9
|
* bulky generated artifacts (per design doc § "Large File Policy").
|
|
10
10
|
* Bulky media/archives are re-included under the top-level
|
|
11
11
|
* `attachments/` folder (chat attachments must sync).
|
|
12
|
-
* A user CAN override
|
|
12
|
+
* A user CAN override the non-secret ones (dependencies, media, …)
|
|
13
|
+
* via `!pattern` in their .tapignore.
|
|
13
14
|
* 2. Project .tapignore.
|
|
14
|
-
* 3.
|
|
15
|
-
*
|
|
16
|
-
*
|
|
15
|
+
* 3. Secrets + conflict sidecars (NON_OVERRIDABLE_IGNORE_LINES), added
|
|
16
|
+
* again after the project file. The .tapignore itself syncs from
|
|
17
|
+
* other members, so a `!.env` there would otherwise make every
|
|
18
|
+
* member's daemon upload their own .env.
|
|
19
|
+
* 4. Always-excluded paths — Tap's own local metadata, plus the paths
|
|
20
|
+
* that never sync between members (core isNeverSyncPath: .git,
|
|
21
|
+
* agent/MCP config). Matched case-insensitively, never re-enabled by
|
|
22
|
+
* `!pattern`; the relay refuses them anyway (validateRigPath rejects
|
|
23
|
+
* reserved paths, the change route refuses never-sync ones).
|
|
17
24
|
*/
|
|
18
25
|
|
|
19
26
|
declare const BUILTIN_IGNORE_LINES: ReadonlyArray<string>;
|
|
20
27
|
declare function loadIgnore(rootDir: string): Ignore;
|
|
21
28
|
/**
|
|
22
29
|
* Hard-exclude check that runs in addition to .tapignore. Tap's own
|
|
23
|
-
* local metadata
|
|
24
|
-
* their .tapignore —
|
|
30
|
+
* local metadata and the never-sync paths (.git, agent/MCP config) are
|
|
31
|
+
* invisible to sync no matter what the user puts in their .tapignore —
|
|
32
|
+
* including `!pattern` overrides. Case-insensitive: on APFS `.RIG/` is
|
|
33
|
+
* `.rig/`.
|
|
25
34
|
*/
|
|
26
35
|
declare function isAlwaysExcluded(posixPath: string): boolean;
|
|
27
36
|
|
|
@@ -287,6 +296,10 @@ declare function hashFile(absPath: string): Promise<string>;
|
|
|
287
296
|
* 7. Populate .rig/tap/state.local.db with last_applied_change_id
|
|
288
297
|
* per path, and the resulting cursor.
|
|
289
298
|
*
|
|
299
|
+
* `deferUpload` stops after step 3a (plus an empty state DB): the binding
|
|
300
|
+
* exists and the config is on disk, and the daemon's first scan uploads
|
|
301
|
+
* everything (`tapd start` pushes whatever the relay doesn't know yet).
|
|
302
|
+
*
|
|
290
303
|
* Returns a summary with the binding, the owner token (so the caller
|
|
291
304
|
* CLI can print it once), upload + change counts, and any walker
|
|
292
305
|
* warnings (oversized files / symlinks / invalid paths). Caller is
|
|
@@ -302,6 +315,15 @@ type InitOptions = {
|
|
|
302
315
|
deviceLabel?: string;
|
|
303
316
|
/** Org slug to scope the new binding to. Caller must already be a member. */
|
|
304
317
|
org?: string;
|
|
318
|
+
/** Create the binding as this kind (the relay defaults to `'rig'`). */
|
|
319
|
+
kind?: "rig" | "space";
|
|
320
|
+
/**
|
|
321
|
+
* Create the binding and write the local config, but leave the initial
|
|
322
|
+
* upload to the daemon's first scan (`tapd start` pushes every file the
|
|
323
|
+
* relay doesn't know yet). For a brand-new folder that should be usable
|
|
324
|
+
* the moment its binding exists — a new space in the desktop app.
|
|
325
|
+
*/
|
|
326
|
+
deferUpload?: boolean;
|
|
305
327
|
/** Test seam: override fetch (used to drive the in-process relay). */
|
|
306
328
|
fetch?: typeof fetch;
|
|
307
329
|
};
|
|
@@ -316,6 +338,8 @@ type InitResult = {
|
|
|
316
338
|
warnings: WalkResult["warnings"];
|
|
317
339
|
/** Cursor written to state.local.db after the initial submit. */
|
|
318
340
|
cursor: string;
|
|
341
|
+
/** True when the upload was left to the daemon's first scan (`deferUpload`). */
|
|
342
|
+
deferredUpload: boolean;
|
|
319
343
|
};
|
|
320
344
|
declare class AlreadyInitializedError extends Error {
|
|
321
345
|
readonly path: string;
|
|
@@ -419,15 +443,27 @@ declare function revoke(opts: {
|
|
|
419
443
|
/**
|
|
420
444
|
* Apply remote change events to the local filesystem.
|
|
421
445
|
*
|
|
422
|
-
*
|
|
423
|
-
*
|
|
424
|
-
*
|
|
425
|
-
*
|
|
426
|
-
*
|
|
446
|
+
* Echo suppression is handled via the state DB: an event at or before the
|
|
447
|
+
* path's last_applied_change_id (our own push coming back, or a version
|
|
448
|
+
* already superseded here), or whose hash matches our last_seen_hash, is
|
|
449
|
+
* skipped without a download.
|
|
450
|
+
*
|
|
451
|
+
* A change made on both sides is first three-way merged (merge.ts) against
|
|
452
|
+
* the version this device last synced; a clean merge becomes the file and
|
|
453
|
+
* is uploaded on top of the remote change. Otherwise the remote stays the
|
|
454
|
+
* file and the local edit moves to a `.conflict-from.<this device>.<chg>`
|
|
455
|
+
* copy — a local copy that hasn't seen a newer remote version must never
|
|
456
|
+
* replace it (the 2026-09-30 clear-harbor incident). Identical bytes on
|
|
457
|
+
* both sides are not a conflict.
|
|
427
458
|
*
|
|
428
459
|
* Materialization is atomic: download to `<file>.tap-tmp.<rand>` then
|
|
429
460
|
* `rename` into place. Atomic on POSIX; on Windows, `fs.rename` falls
|
|
430
461
|
* back to MoveFileEx-style replace.
|
|
462
|
+
*
|
|
463
|
+
* Files from other members are treated like downloads: never executable
|
|
464
|
+
* (the event's `executable` bit is ignored), quarantined on macOS so
|
|
465
|
+
* Gatekeeper checks them before they run, and never-sync paths (.git,
|
|
466
|
+
* agent/MCP config — core isNeverSyncPath) are not applied at all.
|
|
431
467
|
*/
|
|
432
468
|
|
|
433
469
|
/** State-DB meta key — running count of conflicts surfaced by `tapd status`. */
|
|
@@ -450,8 +486,8 @@ declare function conflictSidecarPath(path: string, ev: {
|
|
|
450
486
|
* Apply mode.
|
|
451
487
|
*
|
|
452
488
|
* - `primary`: the local copy has write authority. Local-divergence
|
|
453
|
-
* guards protect unpushed edits (
|
|
454
|
-
* clobber a dir)
|
|
489
|
+
* guards protect unpushed edits (conflict copies, keep-on-delete,
|
|
490
|
+
* refuse to clobber a dir).
|
|
455
491
|
* - `mirror`: a read-only import of another binding. The mount has no
|
|
456
492
|
* local authority, so **remote always wins** across every event type —
|
|
457
493
|
* every divergence guard flips to remote-authoritative. There is no
|
|
@@ -473,8 +509,8 @@ type ApplyResult = {
|
|
|
473
509
|
errors: ReadonlyArray<ApplyEventError>;
|
|
474
510
|
};
|
|
475
511
|
/**
|
|
476
|
-
* One iteration of the apply loop:
|
|
477
|
-
* materialize each event, update the cursor.
|
|
512
|
+
* One iteration of the apply loop: page through `GET /changes?after=<cursor>`
|
|
513
|
+
* until caught up, materialize each event, update the cursor.
|
|
478
514
|
*
|
|
479
515
|
* Per-event errors (hash mismatch, file/directory collision, disk full,
|
|
480
516
|
* etc.) are caught, logged, and recorded to the state DB — the cursor
|
|
@@ -492,6 +528,62 @@ declare function applyOnce(opts: {
|
|
|
492
528
|
mode?: ApplyMode;
|
|
493
529
|
}): Promise<ApplyResult>;
|
|
494
530
|
|
|
531
|
+
/**
|
|
532
|
+
* Line-based three-way merge for a file changed on both sides (layer 0:
|
|
533
|
+
* deterministic, no AI). The applier tries it before falling back to a
|
|
534
|
+
* conflict copy: base = the version this device last synced, ours = the
|
|
535
|
+
* local file, theirs = the remote version being applied.
|
|
536
|
+
*
|
|
537
|
+
* The merge itself is node-diff3's `diff3Merge` (overlapping or touching
|
|
538
|
+
* hunks are a conflict; the same change on both sides is not). Two things
|
|
539
|
+
* are layered around it:
|
|
540
|
+
*
|
|
541
|
+
* - Only text it can't corrupt: valid UTF-8, no NUL, ≤ 1 MB per side, and
|
|
542
|
+
* not a format where a clean line merge can still be wrong (TOML, YAML,
|
|
543
|
+
* lockfiles, CSV/TSV). A merged `.json` must still parse.
|
|
544
|
+
* - Bounded work. node-diff3's LCS slows down sharply on many repeated
|
|
545
|
+
* lines (a 5,000-line doc that is one-third blank lines takes seconds),
|
|
546
|
+
* and this runs on the daemon's event loop. So the lines all three
|
|
547
|
+
* versions share at the start and end are set aside, the rest is cut at
|
|
548
|
+
* lines that occur exactly once in every version and in the same order
|
|
549
|
+
* (a merge can't move them), and each piece is merged on its own. Text
|
|
550
|
+
* still too repetitive to merge quickly gives up — a conflict copy,
|
|
551
|
+
* never a hang.
|
|
552
|
+
*
|
|
553
|
+
* Line endings: if every version uses one style, lines are compared
|
|
554
|
+
* without it and the result takes the style a side switched to (ours if
|
|
555
|
+
* both did), else the base's. A file mixing styles is merged as raw lines.
|
|
556
|
+
* The trailing newline follows the same rule.
|
|
557
|
+
*/
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* One auto-merge: this device's edit to `path` and another member's change
|
|
561
|
+
* `theirsChangeId`, both made on top of `baseChangeId`. Successive merges
|
|
562
|
+
* into an edit not yet uploaded update one record (`theirsChangeId` is
|
|
563
|
+
* then the newest remote change merged in). `changeId` is the upload that
|
|
564
|
+
* carried the merged file, once it went up.
|
|
565
|
+
*/
|
|
566
|
+
type MergeRecord = {
|
|
567
|
+
path: string;
|
|
568
|
+
at: string;
|
|
569
|
+
baseChangeId: string | null;
|
|
570
|
+
theirsChangeId: string;
|
|
571
|
+
theirsDeviceId: string | null;
|
|
572
|
+
/** The local bytes before the merge (not stored on the relay). */
|
|
573
|
+
oursHash: string;
|
|
574
|
+
resultHash: string;
|
|
575
|
+
changeId?: string;
|
|
576
|
+
};
|
|
577
|
+
declare function readMerges(stateDb: StateDb): MergeRecord[];
|
|
578
|
+
type MergeResult = {
|
|
579
|
+
ok: true;
|
|
580
|
+
bytes: Buffer;
|
|
581
|
+
} | {
|
|
582
|
+
ok: false;
|
|
583
|
+
reason: string;
|
|
584
|
+
};
|
|
585
|
+
declare function mergeText(path: string, base: Buffer, ours: Buffer, theirs: Buffer): MergeResult;
|
|
586
|
+
|
|
495
587
|
/**
|
|
496
588
|
* Claude Code context provider (Context Layer § 4, docs/context-layer-design-doc.md).
|
|
497
589
|
*
|
|
@@ -557,8 +649,11 @@ type IntentAssignment = {
|
|
|
557
649
|
|
|
558
650
|
type NotSyncedFile = {
|
|
559
651
|
path: string;
|
|
560
|
-
reason: "quota_exceeded" | "file_too_large";
|
|
561
|
-
/**
|
|
652
|
+
reason: "quota_exceeded" | "file_too_large" | "owner_only" | "protected";
|
|
653
|
+
/**
|
|
654
|
+
* Hash of the refused bytes; absent for walker-level oversize skips and
|
|
655
|
+
* for refused deletes/mkdirs/rmdirs.
|
|
656
|
+
*/
|
|
562
657
|
hash?: string;
|
|
563
658
|
size?: number;
|
|
564
659
|
/** The space's byte limit, for quota_exceeded. */
|
|
@@ -595,6 +690,8 @@ declare function scanAndPush(opts: {
|
|
|
595
690
|
* map and the batch simply carries no intent.
|
|
596
691
|
*/
|
|
597
692
|
resolveIntents?: (changedPaths: ReadonlyArray<string>) => Promise<Map<string, IntentAssignment>>;
|
|
693
|
+
/** Internal: this is the re-scan that uploads merges a stale_base refusal produced. */
|
|
694
|
+
afterMerge?: boolean;
|
|
598
695
|
}): Promise<ScanAndPushResult>;
|
|
599
696
|
|
|
600
697
|
/**
|
|
@@ -742,6 +839,19 @@ type SubscriptionHandle = {
|
|
|
742
839
|
stop(): Promise<void>;
|
|
743
840
|
/** Force one apply cycle now (through the mutex). */
|
|
744
841
|
applyNow(): Promise<ApplyResult>;
|
|
842
|
+
/**
|
|
843
|
+
* Apply every remote change, then run `fn` before any other apply
|
|
844
|
+
* starts — so an upload only ever goes out from a tree that has seen the
|
|
845
|
+
* relay's latest. `{ ok: false }` (fn not run) when the changes couldn't
|
|
846
|
+
* be fetched.
|
|
847
|
+
*/
|
|
848
|
+
catchUpThen<T>(fn: () => Promise<T>): Promise<{
|
|
849
|
+
ok: true;
|
|
850
|
+
value: T;
|
|
851
|
+
} | {
|
|
852
|
+
ok: false;
|
|
853
|
+
error: unknown;
|
|
854
|
+
}>;
|
|
745
855
|
/** Underlying state DB. Caller MUST NOT close it — stop() does. */
|
|
746
856
|
stateDb: StateDb;
|
|
747
857
|
};
|
|
@@ -914,6 +1024,12 @@ declare function subscribe(opts: SseSubscriberOptions): SseSubscriber;
|
|
|
914
1024
|
* subscription.ts; this file wires the pieces together and owns the
|
|
915
1025
|
* primary writer.
|
|
916
1026
|
*
|
|
1027
|
+
* Every upload goes through the primary subscription's catchUpThen: remote
|
|
1028
|
+
* changes are applied first, under the same lock. When they can't be
|
|
1029
|
+
* fetched (relay down, offline) nothing is uploaded — a copy that hasn't
|
|
1030
|
+
* seen the latest remote changes must not be pushed over them — and the
|
|
1031
|
+
* push is retried.
|
|
1032
|
+
*
|
|
917
1033
|
* Critical invariant: import mount points are HARD-excluded from the
|
|
918
1034
|
* primary writer (watcher `ignored` + walker `excludePaths`), so a
|
|
919
1035
|
* mirrored subtree is never re-uploaded into the primary's own binding
|
|
@@ -982,4 +1098,4 @@ type DaemonHandle = {
|
|
|
982
1098
|
};
|
|
983
1099
|
declare function start(opts: StartOptions): Promise<DaemonHandle>;
|
|
984
1100
|
|
|
985
|
-
export { type AddImportOptions, type AddImportResult, AlreadyInitializedError, AlreadyJoinedError, type ApplyMode, type ApplyResult, BINDING_CONFIG_PATH, BUILTIN_IGNORE_LINES, type BindingConfig, type DaemonHandle, type ImportHandle, type ImportMountFile, type ImportSpec, ImportTokenScopeError, type ImportValidationError, ImportValidationFailed, type InitOptions, type InitResult, InvalidInviteUrlError, type JoinOptions, type JoinResult, type LocalPathState, type MintInviteOptions, NotInitializedError, type NotSyncedFile, RelayClient, type RelayClientOptions, RelayError, type RemoveImportResult, STATE_KEY_CONFLICT_COUNT, type ScanAndPushResult, type SseSubscriber, type SseSubscriberOptions, type StartOptions, type StartSubscriptionOptions, type StateDb, type SubscriptionConfig, type SubscriptionHandle, type WalkFile, type WalkResult, type WalkWarning, addImport, applyOnce, bindingConfigFile, conflictSidecarPath, createBinding, hashFile, importMountFile, importStateDbPath, importsDir, init, isAlwaysExcluded, join, list as listInvites, loadIgnore, loadImportMounts, mint as mintInvite, normalizeImportPath, openStateDb, parseInviteUrl, primaryStateDbPath, readBindingConfig, readImportMount, readNotSynced, removeImport, revoke as revokeInvite, scanAndPush, subscribe as sseSubscribe, start, startSubscription, validateImportName, validateImportPath, validateImports, walk, writeBindingConfig, writeImportMount };
|
|
1101
|
+
export { type AddImportOptions, type AddImportResult, AlreadyInitializedError, AlreadyJoinedError, type ApplyMode, type ApplyResult, BINDING_CONFIG_PATH, BUILTIN_IGNORE_LINES, type BindingConfig, type DaemonHandle, type ImportHandle, type ImportMountFile, type ImportSpec, ImportTokenScopeError, type ImportValidationError, ImportValidationFailed, type InitOptions, type InitResult, InvalidInviteUrlError, type JoinOptions, type JoinResult, type LocalPathState, type MergeRecord, type MintInviteOptions, NotInitializedError, type NotSyncedFile, RelayClient, type RelayClientOptions, RelayError, type RemoveImportResult, STATE_KEY_CONFLICT_COUNT, type ScanAndPushResult, type SseSubscriber, type SseSubscriberOptions, type StartOptions, type StartSubscriptionOptions, type StateDb, type SubscriptionConfig, type SubscriptionHandle, type WalkFile, type WalkResult, type WalkWarning, addImport, applyOnce, bindingConfigFile, conflictSidecarPath, createBinding, hashFile, importMountFile, importStateDbPath, importsDir, init, isAlwaysExcluded, join, list as listInvites, loadIgnore, loadImportMounts, mergeText, mint as mintInvite, normalizeImportPath, openStateDb, parseInviteUrl, primaryStateDbPath, readBindingConfig, readImportMount, readMerges, readNotSynced, removeImport, revoke as revokeInvite, scanAndPush, subscribe as sseSubscribe, start, startSubscription, validateImportName, validateImportPath, validateImports, walk, writeBindingConfig, writeImportMount };
|
package/dist/index.js
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
list,
|
|
27
27
|
loadIgnore,
|
|
28
28
|
loadImportMounts,
|
|
29
|
+
mergeText,
|
|
29
30
|
mint,
|
|
30
31
|
normalizeImportPath,
|
|
31
32
|
openStateDb,
|
|
@@ -33,6 +34,7 @@ import {
|
|
|
33
34
|
primaryStateDbPath,
|
|
34
35
|
readBindingConfig,
|
|
35
36
|
readImportMount,
|
|
37
|
+
readMerges,
|
|
36
38
|
readNotSynced,
|
|
37
39
|
removeImport,
|
|
38
40
|
revoke,
|
|
@@ -46,7 +48,7 @@ import {
|
|
|
46
48
|
walk,
|
|
47
49
|
writeBindingConfig,
|
|
48
50
|
writeImportMount
|
|
49
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-S5AVBVLP.js";
|
|
50
52
|
export {
|
|
51
53
|
AlreadyInitializedError,
|
|
52
54
|
AlreadyJoinedError,
|
|
@@ -74,6 +76,7 @@ export {
|
|
|
74
76
|
list as listInvites,
|
|
75
77
|
loadIgnore,
|
|
76
78
|
loadImportMounts,
|
|
79
|
+
mergeText,
|
|
77
80
|
mint as mintInvite,
|
|
78
81
|
normalizeImportPath,
|
|
79
82
|
openStateDb,
|
|
@@ -81,6 +84,7 @@ export {
|
|
|
81
84
|
primaryStateDbPath,
|
|
82
85
|
readBindingConfig,
|
|
83
86
|
readImportMount,
|
|
87
|
+
readMerges,
|
|
84
88
|
readNotSynced,
|
|
85
89
|
removeImport,
|
|
86
90
|
revoke as revokeInvite,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rigxyz/tapd",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.8",
|
|
4
4
|
"description": "Local daemon for the hosted Tap relay — watches files, hashes content, uploads/downloads objects, applies remote changes. Pairs with @rigxyz/cli for shared-rig sync.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Dylan Bourgeois <dtsbourg@gmail.com>",
|
|
@@ -40,7 +40,8 @@
|
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"chokidar": "^4.0.1",
|
|
42
42
|
"ignore": "^5.3.0",
|
|
43
|
-
"kleur": "^4.1.5"
|
|
43
|
+
"kleur": "^4.1.5",
|
|
44
|
+
"node-diff3": "3.2.1"
|
|
44
45
|
},
|
|
45
46
|
"devDependencies": {
|
|
46
47
|
"@tap/core": "workspace:*",
|