sealkeep 0.9.0 → 0.11.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/ARCHITECTURE.md +163 -14
- package/CHANGELOG.md +223 -1
- package/CONTROL_PLANE.md +2 -2
- package/LICENSE +1 -1
- package/README.md +108 -22
- package/THIRD_PARTY.md +2 -2
- package/THREAT_MODEL.md +23 -4
- package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/chunk-access.d.ts +26 -4
- package/dist/packages/sealkeep-crypto/src/chunk-access.js +219 -0
- package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/envelope.d.ts +11 -1
- package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/envelope.js +58 -21
- package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/format.d.ts +2 -2
- package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/index.d.ts +1 -0
- package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/index.js +1 -0
- package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/recipients.js +4 -4
- package/dist/packages/sealkeep-crypto/src/squeeze.d.ts +6 -0
- package/dist/packages/sealkeep-crypto/src/squeeze.js +39 -0
- package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/stream.d.ts +36 -3
- package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/stream.js +196 -37
- package/dist/site/index.html +1808 -1904
- package/dist/site/llms.txt +67 -0
- package/dist/site/trust/architecture-data-flow.html +53 -0
- package/dist/site/trust/audit-roadmap.html +37 -0
- package/dist/site/trust/deployment-responsibility.html +11 -0
- package/dist/site/trust/dpa-sample.html +30 -0
- package/dist/site/trust/release-provenance.html +21 -0
- package/dist/site/trust/subprocessors.html +15 -0
- package/dist/site/trust/threat-model.html +34 -0
- package/dist/site/trust/toms.html +41 -0
- package/dist/site/trust-document.css +32 -0
- package/dist/site/trust.html +73 -0
- package/dist/site/visual/assets/index-8Nxnkc7o.js +207 -0
- package/dist/site/visual/assets/index-oxLy1bpy.css +1 -0
- package/dist/site/visual/index.html +18 -0
- package/dist/site.zip +0 -0
- package/dist/src/activity.d.ts +9 -0
- package/dist/src/activity.js +90 -1
- package/dist/src/adapters.d.ts +175 -5
- package/dist/src/adapters.js +961 -73
- package/dist/src/agent-context.d.ts +135 -0
- package/dist/src/agent-context.js +1059 -0
- package/dist/src/archive-copies.d.ts +47 -0
- package/dist/src/archive-copies.js +179 -0
- package/dist/src/audit.d.ts +1 -1
- package/dist/src/audit.js +29 -4
- package/dist/src/autopilot.d.ts +52 -7
- package/dist/src/autopilot.js +143 -25
- package/dist/src/background-bandwidth.d.ts +46 -0
- package/dist/src/background-bandwidth.js +301 -0
- package/dist/src/background-cpu.d.ts +82 -0
- package/dist/src/background-cpu.js +212 -0
- package/dist/src/background-worker-error.d.ts +12 -0
- package/dist/src/background-worker-error.js +18 -0
- package/dist/src/branding.d.ts +24 -1
- package/dist/src/branding.js +31 -1
- package/dist/src/bridge.d.ts +233 -0
- package/dist/src/bridge.js +604 -0
- package/dist/src/byte-stream.d.ts +91 -0
- package/dist/src/byte-stream.js +385 -0
- package/dist/src/chunk-store.d.ts +41 -8
- package/dist/src/chunk-store.js +161 -65
- package/dist/src/cli.js +1698 -163
- package/dist/src/cloud.d.ts +841 -31
- package/dist/src/cloud.js +3196 -277
- package/dist/src/context-background.d.ts +37 -0
- package/dist/src/context-background.js +309 -0
- package/dist/src/context-drain-child.d.ts +1 -0
- package/dist/src/context-drain-child.js +98 -0
- package/dist/src/context-reader.d.ts +118 -0
- package/dist/src/context-reader.js +447 -0
- package/dist/src/control-plane/auth.d.ts +32 -4
- package/dist/src/control-plane/auth.js +85 -24
- package/dist/src/control-plane/server.js +19 -6
- package/dist/src/control-plane.d.ts +17 -1
- package/dist/src/control-plane.js +32 -6
- package/dist/src/crypto.d.ts +1 -1
- package/dist/src/crypto.js +5 -5
- package/dist/src/daemon-lease.d.ts +70 -0
- package/dist/src/daemon-lease.js +420 -0
- package/dist/src/daemon.d.ts +94 -1
- package/dist/src/daemon.js +1082 -105
- package/dist/src/darwin-service-policy.d.ts +41 -0
- package/dist/src/darwin-service-policy.js +60 -0
- package/dist/src/dashboard-cli.js +15 -15
- package/dist/src/device-authorization.d.ts +37 -0
- package/dist/src/device-authorization.js +199 -0
- package/dist/src/device-enrollment.d.ts +91 -0
- package/dist/src/device-enrollment.js +349 -0
- package/dist/src/disk.d.ts +17 -12
- package/dist/src/disk.js +43 -17
- package/dist/src/doctor.d.ts +35 -1
- package/dist/src/doctor.js +316 -41
- package/dist/src/durable-ticket-lock.d.ts +24 -0
- package/dist/src/durable-ticket-lock.js +232 -0
- package/dist/src/enroll.d.ts +1 -1
- package/dist/src/enroll.js +13 -7
- package/dist/src/env.d.ts +10 -1
- package/dist/src/env.js +11 -3
- package/dist/src/errors.d.ts +8 -8
- package/dist/src/errors.js +6 -6
- package/dist/src/flush.d.ts +12 -0
- package/dist/src/flush.js +37 -0
- package/dist/src/heartbeat.d.ts +86 -12
- package/dist/src/heartbeat.js +415 -29
- package/dist/src/index-background-watchdog.d.ts +1 -0
- package/dist/src/index-background-watchdog.js +94 -0
- package/dist/src/index-background-work.d.ts +21 -0
- package/dist/src/index-background-work.js +25 -0
- package/dist/src/index-background.d.ts +64 -0
- package/dist/src/index-background.js +394 -0
- package/dist/src/index-build-child.d.ts +1 -0
- package/dist/src/index-build-child.js +109 -0
- package/dist/src/index-manifest.d.ts +52 -0
- package/dist/src/index-manifest.js +444 -0
- package/dist/src/index-publication-proof.d.ts +84 -0
- package/dist/src/index-publication-proof.js +380 -0
- package/dist/src/index-publication-state.d.ts +149 -0
- package/dist/src/index-publication-state.js +696 -0
- package/dist/src/index-publication-verifier.d.ts +89 -0
- package/dist/src/index-publication-verifier.js +341 -0
- package/dist/src/index-publish.d.ts +62 -0
- package/dist/src/index-publish.js +540 -0
- package/dist/src/index-scratch-cleanup.d.ts +19 -0
- package/dist/src/index-scratch-cleanup.js +166 -0
- package/dist/src/index-segment-types.d.ts +132 -0
- package/dist/src/index-segment-types.js +21 -0
- package/dist/src/index-segments.d.ts +9 -0
- package/dist/src/index-segments.js +516 -0
- package/dist/src/index-store.d.ts +123 -0
- package/dist/src/index-store.js +495 -0
- package/dist/src/index-sync.d.ts +91 -15
- package/dist/src/index-sync.js +286 -53
- package/dist/src/index-upgrade-publication.d.ts +30 -0
- package/dist/src/index-upgrade-publication.js +179 -0
- package/dist/src/integration-manager.d.ts +32 -0
- package/dist/src/integration-manager.js +394 -0
- package/dist/src/leakscan.js +1 -1
- package/dist/src/local-api.d.ts +56 -15
- package/dist/src/local-api.js +4979 -541
- package/dist/src/machine-settings.d.ts +51 -0
- package/dist/src/machine-settings.js +166 -0
- package/dist/src/managed-chunks.d.ts +5 -2
- package/dist/src/managed-chunks.js +14 -14
- package/dist/src/mcp-install.d.ts +11 -9
- package/dist/src/mcp-install.js +73 -28
- package/dist/src/mcp-workspace.d.ts +18 -0
- package/dist/src/mcp-workspace.js +50 -0
- package/dist/src/mcp.js +294 -25
- package/dist/src/migrate.js +27 -21
- package/dist/src/notify.d.ts +1 -1
- package/dist/src/notify.js +5 -5
- package/dist/src/offload.d.ts +201 -14
- package/dist/src/offload.js +1848 -140
- package/dist/src/onboarding.d.ts +8 -1
- package/dist/src/onboarding.js +4 -4
- package/dist/src/packages.d.ts +2 -2
- package/dist/src/packages.js +10 -2
- package/dist/src/passkey.d.ts +0 -1
- package/dist/src/passkey.js +2 -7
- package/dist/src/password-lock.d.ts +2 -2
- package/dist/src/password-lock.js +6 -6
- package/dist/src/paths.d.ts +2 -0
- package/dist/src/paths.js +2 -0
- package/dist/src/presence.d.ts +86 -0
- package/dist/src/presence.js +240 -0
- package/dist/src/progress-deadline.d.ts +21 -0
- package/dist/src/progress-deadline.js +91 -0
- package/dist/src/project-repair.d.ts +55 -0
- package/dist/src/project-repair.js +131 -0
- package/dist/src/providers/gcs.d.ts +28 -7
- package/dist/src/providers/gcs.js +35 -24
- package/dist/src/providers/gdrive.d.ts +71 -8
- package/dist/src/providers/gdrive.js +223 -50
- package/dist/src/providers/index.d.ts +11 -3
- package/dist/src/providers/index.js +30 -10
- package/dist/src/providers/s3.d.ts +30 -8
- package/dist/src/providers/s3.js +41 -30
- package/dist/src/providers/safe-storage-fetch.d.ts +12 -0
- package/dist/src/providers/safe-storage-fetch.js +72 -0
- package/dist/src/queue.d.ts +136 -19
- package/dist/src/queue.js +862 -96
- package/dist/src/reclaim-transaction.d.ts +156 -0
- package/dist/src/reclaim-transaction.js +1027 -0
- package/dist/src/recovery-codes.d.ts +32 -0
- package/dist/src/recovery-codes.js +338 -0
- package/dist/src/recovery.js +12 -9
- package/dist/src/rehydrate.d.ts +25 -22
- package/dist/src/rehydrate.js +319 -23
- package/dist/src/restore.d.ts +57 -4
- package/dist/src/restore.js +272 -36
- package/dist/src/resume-stub.d.ts +92 -0
- package/dist/src/resume-stub.js +417 -0
- package/dist/src/retention.d.ts +98 -7
- package/dist/src/retention.js +1066 -62
- package/dist/src/rotate.js +3 -3
- package/dist/src/search.d.ts +566 -8
- package/dist/src/search.js +5897 -290
- package/dist/src/secrets.d.ts +51 -7
- package/dist/src/secrets.js +316 -24
- package/dist/src/service.d.ts +49 -11
- package/dist/src/service.js +776 -35
- package/dist/src/share.js +3 -3
- package/dist/src/shared-spaces.d.ts +98 -0
- package/dist/src/shared-spaces.js +214 -0
- package/dist/src/source-reader.d.ts +73 -0
- package/dist/src/source-reader.js +715 -0
- package/dist/src/spool.d.ts +1 -1
- package/dist/src/spool.js +1 -1
- package/dist/src/start-tui.js +2 -1
- package/dist/src/start.js +2 -2
- package/dist/src/storage-endpoint.d.ts +21 -0
- package/dist/src/storage-endpoint.js +122 -0
- package/dist/src/storage-setup.js +12 -12
- package/dist/src/storage-targets.d.ts +109 -6
- package/dist/src/storage-targets.js +975 -67
- package/dist/src/stream-to-cloud.d.ts +5 -1
- package/dist/src/stream-to-cloud.js +34 -14
- package/dist/src/sync-rules.d.ts +31 -6
- package/dist/src/sync-rules.js +153 -14
- package/dist/src/team-backfill-scheduling.d.ts +8 -0
- package/dist/src/team-backfill-scheduling.js +33 -0
- package/dist/src/team-backfill.d.ts +116 -0
- package/dist/src/team-backfill.js +1429 -0
- package/dist/src/team-index-cache.d.ts +16 -0
- package/dist/src/team-index-cache.js +152 -0
- package/dist/src/team-offboarding.d.ts +38 -0
- package/dist/src/team-offboarding.js +1043 -0
- package/dist/src/team-presence.d.ts +127 -0
- package/dist/src/team-presence.js +904 -0
- package/dist/src/team-publication-policy.d.ts +20 -0
- package/dist/src/team-publication-policy.js +140 -0
- package/dist/src/team-realtime.d.ts +68 -0
- package/dist/src/team-realtime.js +816 -0
- package/dist/src/team-source-facts-cache.d.ts +23 -0
- package/dist/src/team-source-facts-cache.js +255 -0
- package/dist/src/trash.d.ts +1 -1
- package/dist/src/trash.js +2 -2
- package/dist/src/tui.js +11 -12
- package/dist/src/types.d.ts +173 -7
- package/dist/src/types.js +20 -0
- package/dist/src/ui-server.d.ts +163 -35
- package/dist/src/ui-server.js +712 -72
- package/dist/src/ui.d.ts +1 -2
- package/dist/src/ui.js +1 -2
- package/dist/src/upload.d.ts +27 -0
- package/dist/src/upload.js +383 -43
- package/dist/src/vault.d.ts +226 -30
- package/dist/src/vault.js +1776 -192
- package/dist/src/watcher.d.ts +7 -1
- package/dist/src/watcher.js +198 -55
- package/dist/src/worker.d.ts +27 -3
- package/dist/src/worker.js +274 -55
- package/package.json +33 -12
- package/scripts/native-reboot-rehearsal.mjs +90 -0
- package/web/app.js +6032 -343
- package/web/bootstrap.js +17 -0
- package/web/index.html +255 -57
- package/web/rail.js +317 -40
- package/web/retention.html +2 -2
- package/web/rules-view.js +188 -16
- package/web/sessions-view.js +485 -62
- package/web/sessions.html +2 -2
- package/web/setup-api.js +152 -29
- package/web/setup-logic.js +68 -9
- package/web/setup.html +113 -44
- package/web/setup.js +604 -71
- package/web/style.css +513 -98
- package/dist/packages/vaultline-crypto/src/chunk-access.js +0 -93
- /package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/aead.d.ts +0 -0
- /package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/aead.js +0 -0
- /package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/format.js +0 -0
- /package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/recipients.d.ts +0 -0
- /package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/sha256-stream.d.ts +0 -0
- /package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/sha256-stream.js +0 -0
package/web/sessions.html
CHANGED
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
<body>
|
|
10
10
|
<!-- The Memory pool is a dashboard view now (index.html#sessions) — one page,
|
|
11
11
|
no reloads between views. This stub keeps old bookmarks and links working,
|
|
12
|
-
preserving a #
|
|
12
|
+
preserving a #ticket=… fragment when one is carried. -->
|
|
13
13
|
<script>
|
|
14
14
|
const h = location.hash.replace(/^#/, "");
|
|
15
|
-
location.replace(h.
|
|
15
|
+
location.replace(new URLSearchParams(h).has("ticket") ? `./index.html#${h}` : "./index.html#sessions");
|
|
16
16
|
</script>
|
|
17
17
|
</body>
|
|
18
18
|
</html>
|
package/web/setup-api.js
CHANGED
|
@@ -20,9 +20,10 @@
|
|
|
20
20
|
/**
|
|
21
21
|
* Every path this page may ever request. Nothing is concatenated onto them.
|
|
22
22
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
23
|
+
* Agent integration is committed only by Finish. The key screen discloses that
|
|
24
|
+
* this updates Sealkeep-owned Codex/Claude settings, and the service endpoint
|
|
25
|
+
* applies capture hooks plus the local MCP registration together so no later
|
|
26
|
+
* command is required.
|
|
26
27
|
*/
|
|
27
28
|
export const ROUTES = {
|
|
28
29
|
state: "/v1/setup/state",
|
|
@@ -31,52 +32,115 @@ export const ROUTES = {
|
|
|
31
32
|
storage: "/v1/setup/storage",
|
|
32
33
|
storageTest: "/v1/setup/storage/test",
|
|
33
34
|
enroll: "/v1/setup/enroll",
|
|
35
|
+
deviceAuthorization: "/v1/setup/account-authorization",
|
|
36
|
+
deviceAuthorizationComplete: "/v1/setup/account-authorization/complete",
|
|
37
|
+
deviceEnrollment: "/v1/setup/device-enrollment",
|
|
38
|
+
recoveryCode: "/v1/setup/recovery-code",
|
|
34
39
|
service: "/v1/setup/service"
|
|
35
40
|
};
|
|
36
41
|
|
|
37
|
-
const
|
|
42
|
+
const DEVICE_ENROLLMENT_COMPLETE = /^\/v1\/setup\/device-enrollment\/[1-9]\d*\/complete$/;
|
|
43
|
+
|
|
44
|
+
const TOKEN_KEY = "sealkeep.localApiToken";
|
|
45
|
+
const BROWSER_HANDOFF_KEY = "sealkeep.browserHandoff.v1";
|
|
46
|
+
const BROWSER_HANDOFF_CLIENT_KEY = "sealkeep.browserHandoffClient.v1";
|
|
38
47
|
|
|
39
48
|
export class SetupError extends Error {
|
|
40
|
-
constructor(message, { status = 0, route = "", missing = false } = {}) {
|
|
49
|
+
constructor(message, { status = 0, route = "", missing = false, code = "" } = {}) {
|
|
41
50
|
super(message);
|
|
42
51
|
this.name = "SetupError";
|
|
43
52
|
this.status = status;
|
|
44
53
|
this.route = route;
|
|
54
|
+
this.code = code;
|
|
45
55
|
/** True when this build of Sealkeep has no such endpoint yet. */
|
|
46
56
|
this.missing = missing;
|
|
47
57
|
}
|
|
48
58
|
}
|
|
49
59
|
|
|
50
60
|
let token = "";
|
|
61
|
+
// Older releases used persistent browser storage. A trusted local page deletes
|
|
62
|
+
// that legacy bearer during upgrade; current credentials remain tab-scoped.
|
|
63
|
+
try { localStorage.removeItem(TOKEN_KEY); } catch { /* storage can be disabled */ }
|
|
51
64
|
|
|
52
65
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
66
|
+
* A secure launch URL carries a short-lived ticket in its fragment. The page
|
|
67
|
+
* scrubs the fragment first, then redeems the ticket in a same-origin POST.
|
|
68
|
+
* Only the returned bearer enters sessionStorage — the same key and rules as
|
|
69
|
+
* the dashboard. Neither credential is persisted in localStorage, a cookie,
|
|
70
|
+
* or another URL; the short-lived fragment is scrubbed before redemption.
|
|
58
71
|
*/
|
|
59
|
-
export function readTokenFromLocation() {
|
|
72
|
+
export async function readTokenFromLocation() {
|
|
60
73
|
if (typeof location === "undefined") return token;
|
|
61
74
|
const hash = new URLSearchParams(String(location.hash ?? "").replace(/^#/, ""));
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
75
|
+
const hasTicket = hash.has("ticket");
|
|
76
|
+
const ticket = hash.get("ticket") ?? "";
|
|
77
|
+
if (hasTicket && typeof history !== "undefined") {
|
|
78
|
+
const search = new URLSearchParams(location.search ?? "");
|
|
79
|
+
search.delete("__sealkeep_bootstrap");
|
|
80
|
+
const cleanSearch = search.toString();
|
|
81
|
+
history.replaceState(null, "", location.pathname + (cleanSearch ? `?${cleanSearch}` : ""));
|
|
66
82
|
}
|
|
67
83
|
token = sessionStorage.getItem(TOKEN_KEY) ?? "";
|
|
84
|
+
if (!hasTicket) return token;
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const response = await fetch("/v1/auth/bootstrap", {
|
|
88
|
+
method: "POST",
|
|
89
|
+
headers: { "content-type": "application/json", accept: "application/json" },
|
|
90
|
+
body: JSON.stringify({ ticket })
|
|
91
|
+
});
|
|
92
|
+
const payload = await response.json().catch(() => ({}));
|
|
93
|
+
if (!response.ok || typeof payload.token !== "string" || payload.token.length === 0) throw new Error("bootstrap refused");
|
|
94
|
+
setToken(payload.token);
|
|
95
|
+
} catch {
|
|
96
|
+
setToken("");
|
|
97
|
+
}
|
|
68
98
|
return token;
|
|
69
99
|
}
|
|
70
100
|
|
|
101
|
+
/** Public, non-secret fallback for a browser without Fetch Metadata support. */
|
|
102
|
+
export async function beginBrowserHandoff() {
|
|
103
|
+
let requestId = sessionStorage.getItem(BROWSER_HANDOFF_KEY) ?? "";
|
|
104
|
+
let clientRequestId = sessionStorage.getItem(BROWSER_HANDOFF_CLIENT_KEY) ?? "";
|
|
105
|
+
if (!clientRequestId) {
|
|
106
|
+
const bytes = new Uint8Array(32);
|
|
107
|
+
crypto.getRandomValues(bytes);
|
|
108
|
+
clientRequestId = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("").slice(0, 43);
|
|
109
|
+
sessionStorage.setItem(BROWSER_HANDOFF_CLIENT_KEY, clientRequestId);
|
|
110
|
+
}
|
|
111
|
+
if (!requestId) {
|
|
112
|
+
const response = await fetch("/v1/auth/bootstrap-request", {
|
|
113
|
+
method: "POST",
|
|
114
|
+
headers: { "content-type": "application/json", accept: "application/json" },
|
|
115
|
+
body: JSON.stringify({ label: "setup browser", requestId: clientRequestId })
|
|
116
|
+
});
|
|
117
|
+
const payload = await response.json().catch(() => ({}));
|
|
118
|
+
if (!response.ok || typeof payload.requestId !== "string") throw new SetupError("Sealkeep could not start a browser sign-in request.", { status: response.status });
|
|
119
|
+
requestId = payload.requestId;
|
|
120
|
+
sessionStorage.setItem(BROWSER_HANDOFF_KEY, requestId);
|
|
121
|
+
}
|
|
122
|
+
return requestId;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export async function checkBrowserHandoff(requestId) {
|
|
126
|
+
const response = await fetch(`/v1/auth/bootstrap-request/${encodeURIComponent(requestId)}`, { headers: { accept: "application/json" } });
|
|
127
|
+
const payload = await response.json().catch(() => ({}));
|
|
128
|
+
if (response.status === 410 || payload.status === "expired") {
|
|
129
|
+
sessionStorage.removeItem(BROWSER_HANDOFF_KEY);
|
|
130
|
+
sessionStorage.removeItem(BROWSER_HANDOFF_CLIENT_KEY);
|
|
131
|
+
}
|
|
132
|
+
if (!response.ok && response.status !== 410) throw new SetupError("Sealkeep could not check the browser sign-in request.", { status: response.status });
|
|
133
|
+
return payload;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function clearBrowserHandoff() {
|
|
137
|
+
sessionStorage.removeItem(BROWSER_HANDOFF_KEY);
|
|
138
|
+
sessionStorage.removeItem(BROWSER_HANDOFF_CLIENT_KEY);
|
|
139
|
+
}
|
|
140
|
+
|
|
71
141
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
* Setting only the module variable meant a token pasted by a person was
|
|
76
|
-
* forgotten the moment anything called `readTokenFromLocation` again — which
|
|
77
|
-
* `boot` does first thing — so signing a second browser in appeared to do
|
|
78
|
-
* nothing at all. Session storage, not local: a token that outlives the window
|
|
79
|
-
* is a token nobody remembers to revoke.
|
|
142
|
+
* Keeps a redeemed bearer in this tab only. Exported for the setup transport's
|
|
143
|
+
* tests and sign-out path; the UI never accepts a bearer from a person.
|
|
80
144
|
*/
|
|
81
145
|
export function setToken(value) {
|
|
82
146
|
token = String(value ?? "");
|
|
@@ -86,8 +150,18 @@ export function setToken(value) {
|
|
|
86
150
|
}
|
|
87
151
|
export function hasToken() { return token.length > 0; }
|
|
88
152
|
|
|
153
|
+
function isLocalAuthChallenge(response) {
|
|
154
|
+
const challenge = response?.headers?.get?.("www-authenticate") ?? "";
|
|
155
|
+
return response?.status === 401 && /^Bearer(?:\s|$)/i.test(challenge);
|
|
156
|
+
}
|
|
157
|
+
|
|
89
158
|
async function request(route, { method = "GET", body } = {}) {
|
|
90
|
-
if (!Object.values(ROUTES).includes(route)
|
|
159
|
+
if (!Object.values(ROUTES).includes(route) && !DEVICE_ENROLLMENT_COMPLETE.test(route)) {
|
|
160
|
+
throw new SetupError(`Refusing to request ${route}: not a setup route.`, { route });
|
|
161
|
+
}
|
|
162
|
+
if (!token) {
|
|
163
|
+
throw new SetupError("This browser is not signed in yet. Reload this local page to sign in automatically.", { status: 401, route });
|
|
164
|
+
}
|
|
91
165
|
|
|
92
166
|
let response;
|
|
93
167
|
try {
|
|
@@ -100,15 +174,16 @@ async function request(route, { method = "GET", body } = {}) {
|
|
|
100
174
|
throw new SetupError("Sealkeep on this machine stopped answering. Is it still running in the terminal you started it from?", { route });
|
|
101
175
|
}
|
|
102
176
|
|
|
103
|
-
if (response
|
|
104
|
-
|
|
177
|
+
if (isLocalAuthChallenge(response) || response.status === 403) {
|
|
178
|
+
setToken("");
|
|
179
|
+
throw new SetupError("This browser's Sealkeep access expired. Reload this local page to sign in again.", { status: response.status, route });
|
|
105
180
|
}
|
|
106
181
|
if (response.status === 404) {
|
|
107
182
|
throw new SetupError(`This build of Sealkeep has no ${route} endpoint yet.`, { status: 404, route, missing: true });
|
|
108
183
|
}
|
|
109
184
|
|
|
110
185
|
const payload = await response.json().catch(() => ({}));
|
|
111
|
-
if (!response.ok) throw new SetupError(payload?.error?.message ?? `${response.status} ${response.statusText || "request failed"}`, { status: response.status, route });
|
|
186
|
+
if (!response.ok) throw new SetupError(payload?.error?.message ?? `${response.status} ${response.statusText || "request failed"}`, { status: response.status, route, code: payload?.error?.code ?? "" });
|
|
112
187
|
return payload;
|
|
113
188
|
}
|
|
114
189
|
|
|
@@ -167,8 +242,8 @@ export const saveStorage = (config) => request(ROUTES.storage, { method: "POST",
|
|
|
167
242
|
export const testStorage = (config) => request(ROUTES.storageTest, { method: "POST", body: config });
|
|
168
243
|
|
|
169
244
|
// `reclaim` is deliberately left at its server default of false: the service
|
|
170
|
-
// starts, but does not get standing permission to
|
|
171
|
-
//
|
|
245
|
+
// starts, but does not get standing permission to permanently reclaim a person's
|
|
246
|
+
// local originals from a single wizard click. That permission is granted knowingly, later,
|
|
172
247
|
// from Settings. The old body sent `{ enabled: true }`, a field the strict
|
|
173
248
|
// schema does not have, so the call was rejected and the service never started.
|
|
174
249
|
export const startService = () => request(ROUTES.service, { method: "POST", body: { confirm: true } });
|
|
@@ -179,3 +254,51 @@ export const startService = () => request(ROUTES.service, { method: "POST", body
|
|
|
179
254
|
* for why that distinction is load-bearing.
|
|
180
255
|
*/
|
|
181
256
|
export const enrollDevice = (email, code) => request(ROUTES.enroll, { method: "POST", body: { email, code } });
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Starts browser approval without putting the verifier, account token, machine
|
|
260
|
+
* credential or enrollment private key in this page. Only the machine label
|
|
261
|
+
* crosses loopback; OS family and detected agent enums are added locally.
|
|
262
|
+
*/
|
|
263
|
+
export const startDeviceAuthorization = (label) => request(ROUTES.deviceAuthorization, {
|
|
264
|
+
method: "POST",
|
|
265
|
+
body: { label }
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
/** The opaque request handle is a body field, so even it stays out of access logs. */
|
|
269
|
+
export const completeDeviceAuthorization = (requestId) => request(ROUTES.deviceAuthorizationComplete, {
|
|
270
|
+
method: "POST",
|
|
271
|
+
body: { requestId: String(requestId ?? "") }
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Starts a fresh-device request after ordinary account linking succeeds.
|
|
276
|
+
* The response contains public display state only. Its private key and claim
|
|
277
|
+
* capability remain in this machine's native secret backend.
|
|
278
|
+
*/
|
|
279
|
+
export const startDeviceEnrollment = (label) => request(ROUTES.deviceEnrollment, {
|
|
280
|
+
method: "POST",
|
|
281
|
+
body: { label }
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Poll and completion are deliberately one operation. While the source has
|
|
286
|
+
* not approved, the server answers 202 with `waiting_for_approval`; after
|
|
287
|
+
* approval the same call claims, decrypts and adopts the vault locally.
|
|
288
|
+
*
|
|
289
|
+
* Only a validated integer is interpolated. Callers cannot name a route, and
|
|
290
|
+
* no code, key, token or recovery material can enter the URL.
|
|
291
|
+
*/
|
|
292
|
+
export function completeDeviceEnrollment(requestId) {
|
|
293
|
+
const id = Number(requestId);
|
|
294
|
+
if (!Number.isSafeInteger(id) || id < 1) {
|
|
295
|
+
throw new SetupError("Refusing to poll an invalid device enrollment request.", { route: ROUTES.deviceEnrollment });
|
|
296
|
+
}
|
|
297
|
+
return request(`${ROUTES.deviceEnrollment}/${id}/complete`, { method: "POST", body: { confirm: true } });
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** Recover a lost-device vault locally after Google account authorization. */
|
|
301
|
+
export const recoverWithBackupCode = (code) => request(ROUTES.recoveryCode, {
|
|
302
|
+
method: "POST",
|
|
303
|
+
body: { code: String(code ?? "") }
|
|
304
|
+
});
|
package/web/setup-logic.js
CHANGED
|
@@ -209,7 +209,7 @@ export function validateStorage(providerId, values) {
|
|
|
209
209
|
*/
|
|
210
210
|
export function buildStoragePayload(providerId, values) {
|
|
211
211
|
const bucket = String(values.bucket ?? "").trim();
|
|
212
|
-
const prefix = normalisePrefix(values.prefix) || "
|
|
212
|
+
const prefix = normalisePrefix(values.prefix) || "sealkeep";
|
|
213
213
|
const base = { provider: providerId, bucket, prefix };
|
|
214
214
|
|
|
215
215
|
if (providerId === "gcs") {
|
|
@@ -332,8 +332,8 @@ export function summariseDone(result) {
|
|
|
332
332
|
label: "Vault",
|
|
333
333
|
badge: { text: "created", kind: "verified" },
|
|
334
334
|
detail: result.password
|
|
335
|
-
?
|
|
336
|
-
:
|
|
335
|
+
? "Sealkeep data folder on this machine"
|
|
336
|
+
: "Sealkeep data folder on this machine — opened by the recovery phrase you wrote down, and by nothing else."
|
|
337
337
|
});
|
|
338
338
|
|
|
339
339
|
// Named again even though it was chosen two screens ago, because this is the
|
|
@@ -373,14 +373,14 @@ export function summariseDone(result) {
|
|
|
373
373
|
const managed = Boolean(account && !account.error);
|
|
374
374
|
if (account) {
|
|
375
375
|
rows.push(managed
|
|
376
|
-
? { label: "Sealkeep account", badge: { text: "linked", kind: "verified" }, detail: `This machine is linked to your account${account.plan && account.plan !== "unknown" ? ` (${account.plan})` : ""}. Managed storage is on — it receives ${copies}.` }
|
|
377
|
-
: { label: "Sealkeep account", badge: { text: "not linked", kind: "alert" }, detail: `${account.error} This machine is not linked to your account yet, so archives are staying on this disk only.
|
|
376
|
+
? { label: "Sealkeep account", badge: { text: "linked", kind: "verified" }, detail: `This machine is linked to your account${account.plan && account.plan !== "unknown" ? ` (${account.plan})` : ""}. Managed storage is on — it receives ${copies}. Approved devices can bring this same vault identity onto a fresh machine without exposing its key to the browser or Sealkeep Cloud.` }
|
|
377
|
+
: { label: "Sealkeep account", badge: { text: "not linked", kind: "alert" }, detail: `${account.error} This machine is not linked to your account yet, so archives are staying on this disk only. Your vault and key are fine. Open setup again (sealkeep ui, then \u201cWhere they go\u201d) and paste a fresh pairing code to turn on managed storage.` });
|
|
378
378
|
}
|
|
379
379
|
|
|
380
380
|
if (result.storage) {
|
|
381
381
|
rows.push(result.storage.error
|
|
382
382
|
? { label: "Storage", badge: { text: "not connected", kind: "alert" }, detail: `${result.storage.error} Your vault is fine and is keeping archives on this disk. Try the storage settings again from the dashboard.` }
|
|
383
|
-
: { label: "Storage", badge: { text: result.storage.verified ? "verified" : "saved", kind: result.storage.verified ? "verified" : "held" }, detail: `${result.storage.provider
|
|
383
|
+
: { label: "Storage", badge: { text: result.storage.verified ? "verified" : "saved", kind: result.storage.verified ? "verified" : "held" }, detail: `${({ gdrive: "Google Drive", s3: "S3-compatible", r2: "Cloudflare R2", b2: "Backblaze B2", gcs: "Google Cloud Storage" }[result.storage.provider] ?? "Your storage")} — ${copies}. The credentials went to this machine's keychain and cannot be read back.` });
|
|
384
384
|
} else if (managed) {
|
|
385
385
|
rows.push({ label: "Storage", badge: { text: "Sealkeep managed", kind: "verified" }, detail: `Your account's storage receives ${copies}. Nothing else to set up.` });
|
|
386
386
|
} else if (!account) {
|
|
@@ -390,9 +390,68 @@ export function summariseDone(result) {
|
|
|
390
390
|
// storage row would only repeat it.
|
|
391
391
|
|
|
392
392
|
const service = result.service;
|
|
393
|
-
rows.push(service?.
|
|
394
|
-
? { label: "Background service", badge: { text: "
|
|
395
|
-
:
|
|
393
|
+
rows.push(service?.waitingForUnlock
|
|
394
|
+
? { label: "Background service", badge: { text: "waiting for unlock", kind: "held" }, detail: "Installed at login, but it cannot preserve or index sessions until this machine stores the recovery phrase. Open Settings › Unlocking on the dashboard." }
|
|
395
|
+
: service?.running
|
|
396
|
+
? { label: "Background service", badge: { text: "running", kind: "verified" }, detail: service.detail ?? "It checks for new sessions on its own. Nothing to keep open." }
|
|
397
|
+
: service?.installed
|
|
398
|
+
? { label: "Background service", badge: { text: "installed, not running", kind: "held" }, detail: `${service?.error ?? service?.detail ?? "The service manager did not confirm a running process."} Use Settings › Automatic archiving to repair it.` }
|
|
399
|
+
: { label: "Background service", badge: { text: "not running", kind: "held" }, detail: `${service?.error ?? service?.detail ?? "It did not start."} You can turn it on any time from Settings › Automatic archiving on the dashboard.` });
|
|
400
|
+
|
|
401
|
+
// Codex separates a hook definition from permission to execute that exact
|
|
402
|
+
// definition. A successful file write can therefore still be waiting for
|
|
403
|
+
// review, or can remain explicitly disabled by the user. Keep that distinct
|
|
404
|
+
// from the watcher: the watcher preserves session files independently.
|
|
405
|
+
if (Array.isArray(result.hooks) && result.hooks.length > 0) {
|
|
406
|
+
const label = (hook) => ({ codex: "Codex", claude: "Claude Code" }[hook.agent] ?? hook.agent ?? "Agent");
|
|
407
|
+
const active = result.hooks.filter((hook) => hook.installed && hook.active !== false);
|
|
408
|
+
const inactive = result.hooks.filter((hook) => hook.installed && hook.active === false);
|
|
409
|
+
const failed = result.hooks.filter((hook) => !hook.installed);
|
|
410
|
+
if (inactive.length > 0 || failed.length > 0) {
|
|
411
|
+
const details = [
|
|
412
|
+
...inactive.map((hook) => hook.attention || `${label(hook)} has the lifecycle configured, but it is not active.`),
|
|
413
|
+
...failed.map((hook) => `${label(hook)}: ${hook.reason || "the lifecycle could not be configured."}`),
|
|
414
|
+
"The running background watcher still preserves supported session files automatically."
|
|
415
|
+
];
|
|
416
|
+
rows.push({
|
|
417
|
+
label: "Agent automation",
|
|
418
|
+
badge: { text: active.length > 0 ? "partly active" : "review needed", kind: "held" },
|
|
419
|
+
detail: details.join(" ")
|
|
420
|
+
});
|
|
421
|
+
} else {
|
|
422
|
+
rows.push({
|
|
423
|
+
label: "Agent automation",
|
|
424
|
+
badge: { text: "active", kind: "verified" },
|
|
425
|
+
detail: `${active.map(label).join(" and ")} lifecycle hooks are configured, enabled and trusted.`
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const mcp = result.mcp;
|
|
431
|
+
if (mcp) {
|
|
432
|
+
const agents = Array.isArray(mcp.agents) ? mcp.agents : [];
|
|
433
|
+
const installed = agents.filter((agent) => agent.installed);
|
|
434
|
+
const failed = agents.filter((agent) => !agent.installed);
|
|
435
|
+
rows.push(installed.length > 0 && failed.length === 0
|
|
436
|
+
? {
|
|
437
|
+
label: "Agent memory",
|
|
438
|
+
badge: { text: "connected", kind: "verified" },
|
|
439
|
+
detail: `${installed.map((agent) => agent.label).join(" and ")} can search this vault through the local Sealkeep MCP. No command is left for you to run.`
|
|
440
|
+
}
|
|
441
|
+
: installed.length > 0
|
|
442
|
+
? {
|
|
443
|
+
label: "Agent memory",
|
|
444
|
+
badge: { text: "partly connected", kind: "held" },
|
|
445
|
+
detail: `${installed.map((agent) => agent.label).join(" and ")} can search this vault. ${failed.map((agent) => `${agent.label}: ${agent.detail}`).join(" ")}`
|
|
446
|
+
}
|
|
447
|
+
: {
|
|
448
|
+
label: "Agent memory",
|
|
449
|
+
badge: { text: "not connected", kind: "held" },
|
|
450
|
+
detail: failed.length > 0
|
|
451
|
+
? failed.map((agent) => `${agent.label}: ${agent.detail}`).join(" ")
|
|
452
|
+
: "No supported agent was found on this machine yet. Install Codex or Claude Code and reopen setup; Sealkeep will register its local memory connection automatically."
|
|
453
|
+
});
|
|
454
|
+
}
|
|
396
455
|
|
|
397
456
|
return rows;
|
|
398
457
|
}
|
package/web/setup.html
CHANGED
|
@@ -57,8 +57,8 @@
|
|
|
57
57
|
</div>
|
|
58
58
|
<div class="trace-row sealed">
|
|
59
59
|
<b>The copy is stored. The original stays yours.</b>
|
|
60
|
-
<p>Copies go wherever you choose next — keeping them only on this disk is a real answer.
|
|
61
|
-
<span class="state">
|
|
60
|
+
<p>Copies go wherever you choose next — keeping them only on this disk is a real answer. Nothing removes an original by default. If you later enable reclaiming, Sealkeep first proves the remote copy, search entry, exact bytes, and idleness; then it permanently frees that local original. Codex keeps a tiny same-session resume pointer.</p>
|
|
61
|
+
<span class="state">off by default</span>
|
|
62
62
|
</div>
|
|
63
63
|
</div>
|
|
64
64
|
|
|
@@ -158,36 +158,83 @@
|
|
|
158
158
|
<div class="panel pad" id="account-panel" hidden>
|
|
159
159
|
<h3>Your Sealkeep account</h3>
|
|
160
160
|
<p class="note">
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
161
|
+
Connect this machine in your browser. Create an account or sign in,
|
|
162
|
+
review the machine and detected agents, then approve once. Setup will
|
|
163
|
+
continue here automatically.
|
|
164
164
|
</p>
|
|
165
|
-
<div class="
|
|
166
|
-
<
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
</div>
|
|
170
|
-
<div class="field">
|
|
171
|
-
<label for="f-enroll-code">Pairing code</label>
|
|
172
|
-
<input id="f-enroll-code" name="enrollCode" type="text" autocomplete="off" spellcheck="false" placeholder="paste the code">
|
|
173
|
-
</div>
|
|
165
|
+
<div class="field">
|
|
166
|
+
<label for="f-device-label">Name this machine</label>
|
|
167
|
+
<input id="f-device-label" name="deviceLabel" type="text" autocomplete="off" spellcheck="false" maxlength="80" value="My new machine">
|
|
168
|
+
<p class="note">Only this label, the OS family, detected agent names and an encryption public key are shown for approval. No hostname or local path is sent.</p>
|
|
174
169
|
</div>
|
|
175
|
-
<
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
<div class="actions">
|
|
179
|
-
<a class="action primary" href="https://sealkeep.spala.ai/#signup" target="_blank" rel="noreferrer noopener">Create an account</a>
|
|
180
|
-
<a class="action" href="https://sealkeep.spala.ai/#signin" target="_blank" rel="noreferrer noopener">I already have one — sign in</a>
|
|
170
|
+
<div class="actions" id="account-start-actions">
|
|
171
|
+
<button type="button" class="action primary" id="account-authorize">Continue securely in your browser</button>
|
|
172
|
+
<a class="action" id="account-approval-link" target="_blank" rel="noreferrer noopener" hidden>Open approval page</a>
|
|
181
173
|
</div>
|
|
182
|
-
<p class="note"
|
|
183
|
-
<
|
|
184
|
-
|
|
174
|
+
<p class="note" id="account-authorization-status" aria-live="polite"></p>
|
|
175
|
+
<p class="note error" id="account-authorization-error" hidden></p>
|
|
176
|
+
|
|
177
|
+
<details class="panel pad callout" id="pairing-fallback">
|
|
178
|
+
<summary>Manual fallback — use an email and pairing code</summary>
|
|
179
|
+
<p class="note">Use this only when the browser approval page cannot return to this machine. The pairing code is single use, and it is not your recovery phrase. Sign in first, generate it, then paste it below.</p>
|
|
180
|
+
<div class="actions">
|
|
181
|
+
<a class="action" href="https://sealkeep.spala.ai/#signup" target="_blank" rel="noreferrer noopener">Create an account</a>
|
|
182
|
+
<a class="action" href="https://sealkeep.spala.ai/#signin" target="_blank" rel="noreferrer noopener">Sign in</a>
|
|
183
|
+
<button type="button" class="action" id="show-pairing-form">I have a pairing code</button>
|
|
184
|
+
</div>
|
|
185
|
+
<div id="account-pairing-form" hidden>
|
|
186
|
+
<div class="row">
|
|
187
|
+
<div class="field">
|
|
188
|
+
<label for="f-enroll-email">The email on your account</label>
|
|
189
|
+
<input id="f-enroll-email" name="enrollEmail" type="email" autocomplete="email" spellcheck="false" placeholder="you@example.com">
|
|
190
|
+
</div>
|
|
191
|
+
<div class="field">
|
|
192
|
+
<label for="f-enroll-code">Pairing code</label>
|
|
193
|
+
<input id="f-enroll-code" name="enrollCode" type="text" autocomplete="off" spellcheck="false" placeholder="paste the code">
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
<p class="note error" id="e-enroll-code" hidden></p>
|
|
197
|
+
<div class="actions">
|
|
198
|
+
<button type="button" class="action primary" id="enroll-go">Pair this machine</button>
|
|
199
|
+
</div>
|
|
200
|
+
<p class="note" id="enroll-result" aria-live="polite"></p>
|
|
201
|
+
<p class="note">Pairing links this machine to your plan and encrypted storage. The code cannot open an archive, and neither can Sealkeep.</p>
|
|
202
|
+
</div>
|
|
203
|
+
</details>
|
|
204
|
+
|
|
205
|
+
<div class="panel pad callout" id="device-enrollment" hidden>
|
|
206
|
+
<h3>Your account already has preserved memories</h3>
|
|
207
|
+
<p>Approve this machine from Sealkeep Settings on a device that already opens your vault. Compare the code on both screens before approving.</p>
|
|
208
|
+
<div id="device-enrollment-comparison" hidden>
|
|
209
|
+
<div class="boundary">
|
|
210
|
+
<span class="side">shown on both devices</span>
|
|
211
|
+
<b id="device-enrollment-code" aria-label="Six-digit comparison code"></b>
|
|
212
|
+
<span class="side sealed">expires shortly</span>
|
|
213
|
+
</div>
|
|
214
|
+
<p class="note" id="device-enrollment-expiry"></p>
|
|
215
|
+
</div>
|
|
216
|
+
<p class="note" id="device-enrollment-status" aria-live="polite"></p>
|
|
217
|
+
<p class="note error" id="device-enrollment-error" hidden></p>
|
|
218
|
+
<div class="actions">
|
|
219
|
+
<button type="button" class="action" id="device-enrollment-retry" hidden>Start a new request</button>
|
|
220
|
+
</div>
|
|
221
|
+
<details class="panel pad explain" id="backup-code-recovery">
|
|
222
|
+
<summary>I lost every other device — use a backup code</summary>
|
|
223
|
+
<p class="note">Enter one unused code from the Recovery Kit you printed or saved. It is opened on this machine; Gmail and SealKeep Cloud cannot read it.</p>
|
|
224
|
+
<div class="row">
|
|
225
|
+
<label for="f-recovery-code">Backup recovery code</label>
|
|
226
|
+
<input id="f-recovery-code" type="password" autocomplete="off" spellcheck="false" placeholder="SK-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XX">
|
|
227
|
+
<button type="button" class="action primary" id="recovery-code-go">Recover this machine</button>
|
|
228
|
+
</div>
|
|
229
|
+
<div class="field">
|
|
230
|
+
<label for="f-recovery-code-file">Or choose a saved Recovery Kit (.txt)</label>
|
|
231
|
+
<input id="f-recovery-code-file" type="file" accept=".txt,text/plain" autocomplete="off">
|
|
232
|
+
</div>
|
|
233
|
+
<p class="note error" id="recovery-code-error" hidden></p>
|
|
234
|
+
<p class="note" id="recovery-code-status" aria-live="polite"></p>
|
|
235
|
+
</details>
|
|
236
|
+
<p class="note">Only an encrypted package for this machine crosses the hosted service. The recovery phrase, enrollment private key, account token, and machine key never appear on this page.</p>
|
|
185
237
|
</div>
|
|
186
|
-
<p class="note" id="enroll-result"></p>
|
|
187
|
-
<p class="note">
|
|
188
|
-
This links the machine to your plan and storage. It is not your recovery phrase and it does not
|
|
189
|
-
let anyone open a sealed archive — including us.
|
|
190
|
-
</p>
|
|
191
238
|
</div>
|
|
192
239
|
|
|
193
240
|
<form class="panel pad" id="storage-form" hidden>
|
|
@@ -220,7 +267,7 @@
|
|
|
220
267
|
<div class="actions"><button type="button" class="action" data-go="2">Back</button></div>
|
|
221
268
|
<div class="actions">
|
|
222
269
|
<p class="note error" id="storage-error" hidden></p>
|
|
223
|
-
<button type="button" class="action primary" data-go="4">Continue to your key</button>
|
|
270
|
+
<button type="button" class="action primary" id="storage-continue" data-go="4">Continue to your key</button>
|
|
224
271
|
</div>
|
|
225
272
|
</div>
|
|
226
273
|
</section>
|
|
@@ -232,6 +279,10 @@
|
|
|
232
279
|
<p class="lede" id="phrase-lede">The part that decides who can ever open these archives.</p>
|
|
233
280
|
</header>
|
|
234
281
|
|
|
282
|
+
<div class="panel pad callout">
|
|
283
|
+
<p><b>Finish makes the product automatic.</b> Sealkeep starts at login, watches supported agent sessions, and adds its local memory connection to detected Codex and Claude Code settings. You will not need to run a command afterwards.</p>
|
|
284
|
+
</div>
|
|
285
|
+
|
|
235
286
|
<!-- The free paths: a password first, because it is the kind of key people
|
|
236
287
|
already know how to keep. The phrase still exists — offered after the
|
|
237
288
|
vault does, as the extra rather than the toll. -->
|
|
@@ -308,6 +359,17 @@
|
|
|
308
359
|
|
|
309
360
|
<ol class="phrase" id="phrase-words" aria-label="Your 24-word recovery phrase"></ol>
|
|
310
361
|
|
|
362
|
+
<div class="panel pad callout" id="recovery-codes" hidden>
|
|
363
|
+
<h3>Your one-time backup codes</h3>
|
|
364
|
+
<p>Each code can restore this same vault after you sign in with Google, even if every trusted machine is gone. SealKeep stores only ciphertext; save these away from this machine.</p>
|
|
365
|
+
<ol class="plain" id="recovery-code-list"></ol>
|
|
366
|
+
<div class="actions">
|
|
367
|
+
<button type="button" class="action" id="copy-recovery-codes">Copy all codes</button>
|
|
368
|
+
<button type="button" class="action" id="download-recovery-codes">Download Recovery Kit</button>
|
|
369
|
+
</div>
|
|
370
|
+
<p class="note" id="recovery-codes-note">Shown once. Generating a replacement set later invalidates this set.</p>
|
|
371
|
+
</div>
|
|
372
|
+
|
|
311
373
|
<div class="actions phrase-actions">
|
|
312
374
|
<button type="button" class="action" id="copy-phrase">Copy the phrase</button>
|
|
313
375
|
<button type="button" class="action" id="print-phrase">Print this page</button>
|
|
@@ -343,6 +405,22 @@
|
|
|
343
405
|
<div class="actions"><button type="button" class="action primary" id="finish-existing">Finish setup</button></div>
|
|
344
406
|
</div>
|
|
345
407
|
</div>
|
|
408
|
+
|
|
409
|
+
<!-- A returning account with preserved memories receives its existing
|
|
410
|
+
vault identity through device-to-device E2EE. There is no new phrase
|
|
411
|
+
ceremony here because creating a temporary vault would replace the
|
|
412
|
+
identity the other device just approved. -->
|
|
413
|
+
<div id="phrase-enrolled" hidden>
|
|
414
|
+
<div class="panel pad callout">
|
|
415
|
+
<h3>Your existing vault is ready on this machine</h3>
|
|
416
|
+
<p>An approved device encrypted the vault identity directly for this machine. Sealkeep opened that package locally and kept the key in this machine's keychain.</p>
|
|
417
|
+
<p>No recovery phrase, private enrollment key, account token, or machine key was shown to this page or stored by the browser.</p>
|
|
418
|
+
</div>
|
|
419
|
+
<div class="wizfoot">
|
|
420
|
+
<p class="note">The same vault now continues here; setup did not create a temporary replacement.</p>
|
|
421
|
+
<div class="actions"><button type="button" class="action primary" id="finish-enrolled">Finish setup</button></div>
|
|
422
|
+
</div>
|
|
423
|
+
</div>
|
|
346
424
|
</section>
|
|
347
425
|
|
|
348
426
|
<!-- 5 ─ Done ──────────────────────────────────────────────────── -->
|
|
@@ -358,7 +436,7 @@
|
|
|
358
436
|
<h3>What happens next</h3>
|
|
359
437
|
<p id="next-detail">Nothing else is needed. The next time one of your agents finishes a session, the transcript is queued here and archived. It shows up on the dashboard within a few minutes.</p>
|
|
360
438
|
<h3>Two things worth doing today</h3>
|
|
361
|
-
<p><span id="today-key">Check that your recovery phrase is somewhere you will still have it in a year — a drawer beats a laptop.</span> Then open <a href="./index.html#rules">Retention</a> and decide
|
|
439
|
+
<p><span id="today-key">Check that your recovery phrase is somewhere you will still have it in a year — a drawer beats a laptop.</span> Then open <a href="./index.html#rules">Retention</a> and decide whether Sealkeep may permanently free safely preserved originals. Until you say so, it never does.</p>
|
|
362
440
|
</div>
|
|
363
441
|
|
|
364
442
|
<div class="wizfoot">
|
|
@@ -371,27 +449,18 @@
|
|
|
371
449
|
</section>
|
|
372
450
|
|
|
373
451
|
<!-- Shown instead of a blank page when the machine cannot answer. -->
|
|
374
|
-
<!-- The
|
|
375
|
-
|
|
376
|
-
to "run sealkeep ui" is both a terminal instruction and the wrong one:
|
|
377
|
-
it would open their default browser, not the one they are looking at. So
|
|
378
|
-
the page offers the way in that works from here. -->
|
|
452
|
+
<!-- The redeemed bearer lives in sessionStorage, which belongs to one tab.
|
|
453
|
+
A direct local visit gives each new browser its own one-use ticket. -->
|
|
379
454
|
<section class="screen" id="screen-gate" hidden>
|
|
380
455
|
<header>
|
|
381
|
-
<h1>
|
|
382
|
-
<p class="lede">Sealkeep
|
|
456
|
+
<h1>Open Sealkeep securely</h1>
|
|
457
|
+
<p class="lede">Sealkeep normally signs in a direct local-browser visit automatically. If this browser does not provide that signal, request approval from any Sealkeep tab already open on this machine.</p>
|
|
383
458
|
</header>
|
|
384
459
|
<div class="panel pad">
|
|
385
|
-
<p class="note">Paste the token from <code id="gate-token-path">local-api-token</code> in your Sealkeep data folder (<code>~/.sealkeep</code>, or <code>~/.vaultline</code> for older vaults). It is a local file; nothing here is sent anywhere.</p>
|
|
386
460
|
<form id="gate-form">
|
|
387
|
-
<div class="
|
|
388
|
-
<label for="gate-token">Local API token</label>
|
|
389
|
-
<input id="gate-token" type="password" autocomplete="off" spellcheck="false" placeholder="paste the token">
|
|
390
|
-
</div>
|
|
391
|
-
<div class="actions"><button type="submit" class="action primary">Connect this browser</button></div>
|
|
461
|
+
<div class="actions"><button id="gate-retry" type="submit" class="action primary">Request access in this browser</button></div>
|
|
392
462
|
</form>
|
|
393
463
|
<p class="note error" id="gate-error" hidden></p>
|
|
394
|
-
<p class="note">On macOS or Linux: <code>cat ~/.sealkeep/local-api-token</code> (older vaults: <code>~/.vaultline</code>)</p>
|
|
395
464
|
</div>
|
|
396
465
|
</section>
|
|
397
466
|
|