@openparachute/hub 0.7.6 → 0.7.7-rc.12
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 +7 -7
- package/package.json +8 -13
- package/src/__tests__/account-api.test.ts +798 -0
- package/src/__tests__/account-session.test.ts +252 -0
- package/src/__tests__/account-token.test.ts +316 -0
- package/src/__tests__/admin-connections-credentials.test.ts +41 -0
- package/src/__tests__/admin-handlers.test.ts +25 -0
- package/src/__tests__/admin-lock.test.ts +3 -14
- package/src/__tests__/admin-module-token.test.ts +10 -30
- package/src/__tests__/admin-surfaces.test.ts +21 -0
- package/src/__tests__/api-hub-upgrade.test.ts +11 -0
- package/src/__tests__/api-mint-token.test.ts +25 -0
- package/src/__tests__/api-modules-ops.test.ts +34 -29
- package/src/__tests__/api-modules.test.ts +50 -58
- package/src/__tests__/api-revoke-token.test.ts +23 -0
- package/src/__tests__/api-settings-hub-origin.test.ts +12 -0
- package/src/__tests__/api-settings-root-redirect.test.ts +12 -0
- package/src/__tests__/api-tokens.test.ts +44 -0
- package/src/__tests__/audience-gate.test.ts +24 -0
- package/src/__tests__/bearer-scheme-casing.test.ts +110 -0
- package/src/__tests__/chrome-strip.test.ts +18 -1
- package/src/__tests__/doctor.test.ts +10 -17
- package/src/__tests__/door-contract-parity.test.ts +46 -0
- package/src/__tests__/hub-server.test.ts +37 -0
- package/src/__tests__/hub.test.ts +29 -0
- package/src/__tests__/install.test.ts +279 -5
- package/src/__tests__/migrate.test.ts +3 -1
- package/src/__tests__/notes-serve.test.ts +216 -0
- package/src/__tests__/oauth-handlers.test.ts +19 -8
- package/src/__tests__/operator-token.test.ts +1 -2
- package/src/__tests__/port-assign.test.ts +37 -17
- package/src/__tests__/scope-explanations.test.ts +22 -2
- package/src/__tests__/serve-boot.test.ts +25 -36
- package/src/__tests__/service-spec-discovery.test.ts +30 -35
- package/src/__tests__/services-manifest.test.ts +372 -132
- package/src/__tests__/sessions.test.ts +75 -28
- package/src/__tests__/setup-wizard.test.ts +7 -10
- package/src/__tests__/setup.test.ts +13 -14
- package/src/__tests__/status.test.ts +0 -5
- package/src/__tests__/surface-notes-alias.test.ts +296 -0
- package/src/account-api.ts +677 -0
- package/src/account-session.ts +132 -0
- package/src/account-token.ts +200 -0
- package/src/admin-connections.ts +4 -2
- package/src/admin-lock.ts +1 -2
- package/src/admin-module-token.ts +13 -8
- package/src/admin-surfaces.ts +2 -1
- package/src/api-hub-upgrade.ts +2 -1
- package/src/api-invites.ts +19 -0
- package/src/api-mint-token.ts +2 -1
- package/src/api-modules-ops.ts +2 -1
- package/src/api-modules.ts +10 -8
- package/src/api-revoke-token.ts +2 -1
- package/src/api-settings-hub-origin.ts +2 -1
- package/src/api-settings-root-redirect.ts +2 -1
- package/src/api-tokens.ts +2 -1
- package/src/audience-gate.ts +2 -1
- package/src/chrome-strip.ts +16 -4
- package/src/commands/install.ts +86 -8
- package/src/commands/migrate.ts +5 -1
- package/src/commands/setup.ts +9 -6
- package/src/help.ts +6 -6
- package/src/hub-server.ts +247 -52
- package/src/hub-settings.ts +25 -1
- package/src/hub.ts +64 -31
- package/src/invites.ts +42 -0
- package/src/module-ops-client.ts +2 -1
- package/src/notes-serve.ts +73 -31
- package/src/oauth-handlers.ts +1 -11
- package/src/operator-token.ts +0 -1
- package/src/origin-check.ts +2 -2
- package/src/scope-explanations.ts +35 -5
- package/src/service-spec.ts +128 -74
- package/src/services-manifest.ts +112 -52
- package/src/sessions.ts +66 -30
- package/src/surface-notes-alias.ts +126 -0
- package/src/__tests__/admin-agent-token.test.ts +0 -173
- package/src/admin-agent-token.ts +0 -147
package/src/commands/install.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Database } from "bun:sqlite";
|
|
1
2
|
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
2
3
|
import { createConnection } from "node:net";
|
|
3
4
|
import { dirname, join } from "node:path";
|
|
@@ -15,6 +16,8 @@ import {
|
|
|
15
16
|
defaultSleep,
|
|
16
17
|
readHubPort,
|
|
17
18
|
} from "../hub-control.ts";
|
|
19
|
+
import { hubDbPath, openHubDb } from "../hub-db.ts";
|
|
20
|
+
import { resolveRootRedirectDetailed, setRootRedirect } from "../hub-settings.ts";
|
|
18
21
|
import { type HubUnitDeps, defaultHubUnitDeps, isHubUnitInstalled } from "../hub-unit.ts";
|
|
19
22
|
import {
|
|
20
23
|
type ModuleManifest,
|
|
@@ -33,6 +36,7 @@ import {
|
|
|
33
36
|
type FirstPartyFallback,
|
|
34
37
|
KNOWN_MODULES,
|
|
35
38
|
type KnownModule,
|
|
39
|
+
RETIRED_MODULES,
|
|
36
40
|
type ServiceSpec,
|
|
37
41
|
composeServiceSpec,
|
|
38
42
|
isCanonicalPort,
|
|
@@ -126,15 +130,9 @@ export function resolveInstallChannel(opts: {
|
|
|
126
130
|
* launch sinks in and `parachute install lens` has stopped appearing
|
|
127
131
|
* in support threads.
|
|
128
132
|
*
|
|
129
|
-
* `channel → agent` (2026-06-17): parachute-channel was renamed to
|
|
130
|
-
* parachute-agent. `parachute install channel` keeps resolving to the
|
|
131
|
-
* agent module for one release cycle so operators mid-upgrade aren't
|
|
132
|
-
* stranded. Remove alongside the `parachute-channel → agent` legacy
|
|
133
|
-
* manifest alias in service-spec.ts.
|
|
134
133
|
*/
|
|
135
134
|
const SERVICE_ALIASES: Record<string, string> = {
|
|
136
135
|
lens: "notes",
|
|
137
|
-
channel: "agent",
|
|
138
136
|
};
|
|
139
137
|
|
|
140
138
|
/**
|
|
@@ -145,14 +143,41 @@ const SERVICE_ALIASES: Record<string, string> = {
|
|
|
145
143
|
* real, non-Parachute package on npm). Install refuses with the message
|
|
146
144
|
* instead.
|
|
147
145
|
*/
|
|
146
|
+
const AGENT_RETIRED_MESSAGE =
|
|
147
|
+
"parachute-agent was retired from the hub on 2026-07-15. " +
|
|
148
|
+
"Parachute product development is focused on Vault and Surface.";
|
|
149
|
+
|
|
148
150
|
const RETIRED_INSTALL_SHORTS: Record<string, string> = {
|
|
151
|
+
agent: AGENT_RETIRED_MESSAGE,
|
|
152
|
+
channel:
|
|
153
|
+
"parachute-channel (later parachute-agent) was retired from the hub on 2026-07-15. " +
|
|
154
|
+
"Parachute product development is focused on Vault and Surface.",
|
|
149
155
|
runner:
|
|
150
156
|
"parachute-runner was retired from the hub's module registry on 2026-07-01 " +
|
|
151
|
-
"(the module set of record is vault, hub,
|
|
157
|
+
"(the module set of record is vault, hub, scribe, surface, app). " +
|
|
152
158
|
"An existing install keeps running under `parachute serve`; to install it anyway, " +
|
|
153
159
|
"pass the explicit npm package name (@openparachute/runner) or a local checkout path.",
|
|
154
160
|
};
|
|
155
161
|
|
|
162
|
+
const RETIRED_AGENT_PACKAGES = [
|
|
163
|
+
"@openparachute/agent",
|
|
164
|
+
"@openparachute/parachute-agent",
|
|
165
|
+
"@openparachute/channel",
|
|
166
|
+
"@openparachute/parachute-channel",
|
|
167
|
+
] as const;
|
|
168
|
+
|
|
169
|
+
function isRetiredAgentPackage(packageName: string): boolean {
|
|
170
|
+
return RETIRED_AGENT_PACKAGES.some(
|
|
171
|
+
(retired) => packageName === retired || packageName.startsWith(`${retired}@`),
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function isRetiredAgentManifest(manifest: ModuleManifest): boolean {
|
|
176
|
+
if (RETIRED_MODULES[manifest.name] !== undefined) return true;
|
|
177
|
+
if (manifest.manifestName && RETIRED_MODULES[manifest.manifestName] !== undefined) return true;
|
|
178
|
+
return manifest.name === "claw" && manifest.paths[0] === "/claw";
|
|
179
|
+
}
|
|
180
|
+
|
|
156
181
|
export interface InstallOpts {
|
|
157
182
|
runner?: Runner;
|
|
158
183
|
manifestPath?: string;
|
|
@@ -408,6 +433,16 @@ export interface InstallOpts {
|
|
|
408
433
|
* origin for stable assertions.
|
|
409
434
|
*/
|
|
410
435
|
wellKnownOrigin?: string;
|
|
436
|
+
/**
|
|
437
|
+
* Test seam for the `app`-only set-if-unset root-redirect write (hub-parity
|
|
438
|
+
* P5). Production opens `openHubDb(hubDbPath(configDir))` fresh and closes
|
|
439
|
+
* it after the write; passing this injects an already-open DB (e.g. a
|
|
440
|
+
* tempdir `hub.db`) so a test never touches the real `~/.parachute/hub.db`.
|
|
441
|
+
* Passing this ALSO opts a tempdir-manifestPath test INTO the write path —
|
|
442
|
+
* mirrors `guidanceCtx`'s discriminant: without it, the production gate
|
|
443
|
+
* (`manifestPath === SERVICES_MANIFEST_PATH`) skips the write entirely.
|
|
444
|
+
*/
|
|
445
|
+
rootRedirectDb?: Database;
|
|
411
446
|
}
|
|
412
447
|
|
|
413
448
|
async function defaultRunner(cmd: readonly string[]): Promise<number> {
|
|
@@ -734,12 +769,20 @@ function resolveInstallTarget(
|
|
|
734
769
|
log(`✗ ${input} has no readable package.json — can't install as a Parachute module.`);
|
|
735
770
|
return null;
|
|
736
771
|
}
|
|
772
|
+
if (isRetiredAgentPackage(packageName)) {
|
|
773
|
+
log(`✗ ${AGENT_RETIRED_MESSAGE}`);
|
|
774
|
+
return null;
|
|
775
|
+
}
|
|
737
776
|
return { kind: "local-path", absPath: input, packageName };
|
|
738
777
|
}
|
|
739
778
|
|
|
740
779
|
// Anything else is treated as an npm package (bare or @scope/name). The
|
|
741
780
|
// module.json contract gates this — third-party packages without a
|
|
742
781
|
// manifest fail post-install with a clear error, not silently.
|
|
782
|
+
if (isRetiredAgentPackage(input)) {
|
|
783
|
+
log(`✗ ${AGENT_RETIRED_MESSAGE}`);
|
|
784
|
+
return null;
|
|
785
|
+
}
|
|
743
786
|
return { kind: "npm", packageName: input };
|
|
744
787
|
}
|
|
745
788
|
|
|
@@ -962,6 +1005,10 @@ export async function install(input: string, opts: InstallOpts = {}): Promise<nu
|
|
|
962
1005
|
log,
|
|
963
1006
|
});
|
|
964
1007
|
if (installedManifest === "error") return 1;
|
|
1008
|
+
if (installedManifest && isRetiredAgentManifest(installedManifest)) {
|
|
1009
|
+
log(`✗ ${target.packageName}: ${AGENT_RETIRED_MESSAGE}`);
|
|
1010
|
+
return 1;
|
|
1011
|
+
}
|
|
965
1012
|
|
|
966
1013
|
let manifest: ModuleManifest;
|
|
967
1014
|
let extras: FirstPartyExtras | undefined;
|
|
@@ -969,7 +1016,7 @@ export async function install(input: string, opts: InstallOpts = {}): Promise<nu
|
|
|
969
1016
|
manifest = installedManifest ?? target.fallback.manifest;
|
|
970
1017
|
extras = target.fallback.extras;
|
|
971
1018
|
} else if (target.kind === "known-module") {
|
|
972
|
-
// KNOWN_MODULES shorts (vault / scribe /
|
|
1019
|
+
// KNOWN_MODULES shorts (vault / scribe / surface) carry no vendored
|
|
973
1020
|
// manifest (hub#310). The module's own `.parachute/module.json` is the
|
|
974
1021
|
// canonical source. When it's unreadable (legacy installs from before
|
|
975
1022
|
// module.json shipped, or test fixtures that mock the disk path without
|
|
@@ -1351,6 +1398,37 @@ export async function install(input: string, opts: InstallOpts = {}): Promise<nu
|
|
|
1351
1398
|
}
|
|
1352
1399
|
}
|
|
1353
1400
|
|
|
1401
|
+
// App-only: default the hub's bare `/` redirect to the app's front door on
|
|
1402
|
+
// first install (hub-parity P5, 2026-07-11) — SET-IF-UNSET ONLY, never
|
|
1403
|
+
// clobbering an operator's existing choice (`parachute hub
|
|
1404
|
+
// set-root-redirect` or the admin SPA PUT both still win on any later run,
|
|
1405
|
+
// and either can change it back). "Unset" means the resolved redirect is
|
|
1406
|
+
// still the built-in `/admin` DEFAULT — i.e. NEITHER the DB row NOR the
|
|
1407
|
+
// `PARACHUTE_HUB_ROOT_REDIRECT` env var (container deploys pin their landing
|
|
1408
|
+
// page there) has set it. Gating on `getRootRedirect(db) === null` would
|
|
1409
|
+
// miss the env tier and silently override an env-configured operator, since
|
|
1410
|
+
// the DB row we'd write wins over env on read (`resolveRootRedirectDetailed`
|
|
1411
|
+
// is DB-first). Gated on the same production-vs-test discriminant the
|
|
1412
|
+
// guidance probe below uses: a test driving install against a tempdir
|
|
1413
|
+
// manifestPath never opens the real `~/.parachute/hub.db` unless it opts in
|
|
1414
|
+
// via `opts.rootRedirectDb`.
|
|
1415
|
+
if (short === "app") {
|
|
1416
|
+
const dbProbeAllowed =
|
|
1417
|
+
opts.rootRedirectDb !== undefined || manifestPath === SERVICES_MANIFEST_PATH;
|
|
1418
|
+
if (dbProbeAllowed) {
|
|
1419
|
+
const db = opts.rootRedirectDb ?? openHubDb(hubDbPath(configDir));
|
|
1420
|
+
try {
|
|
1421
|
+
if (resolveRootRedirectDetailed(db).source === "default") {
|
|
1422
|
+
setRootRedirect(db, "/app/");
|
|
1423
|
+
log("✓ The hub's front page (`/`) now opens the app at /app/.");
|
|
1424
|
+
log(" Change it any time: `parachute hub set-root-redirect <path>` or the admin SPA.");
|
|
1425
|
+
}
|
|
1426
|
+
} finally {
|
|
1427
|
+
if (!opts.rootRedirectDb) db.close();
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1354
1432
|
// Per-service install footer — canonical next-step URLs and configuration
|
|
1355
1433
|
// hints. Vault prints its own (richer) footer from `parachute-vault init`
|
|
1356
1434
|
// (PR #166), so the spec leaves vault out and we don't double up here.
|
package/src/commands/migrate.ts
CHANGED
|
@@ -106,7 +106,11 @@ export function safelistEntries(): Set<string> {
|
|
|
106
106
|
* list once enough operator cycles have passed to be confident no
|
|
107
107
|
* install is still on the Lens-era code path.
|
|
108
108
|
*/
|
|
109
|
-
export const KNOWN_ARCHIVABLE_DIRS: ReadonlySet<string> = new Set<string>([
|
|
109
|
+
export const KNOWN_ARCHIVABLE_DIRS: ReadonlySet<string> = new Set<string>([
|
|
110
|
+
"lens",
|
|
111
|
+
"agent",
|
|
112
|
+
"channel",
|
|
113
|
+
]);
|
|
110
114
|
|
|
111
115
|
/**
|
|
112
116
|
* Known-cruft rules. Each rule names a label (the friendly description in
|
package/src/commands/setup.ts
CHANGED
|
@@ -110,7 +110,7 @@ function defaultAvailability(): InteractiveAvailability {
|
|
|
110
110
|
|
|
111
111
|
/**
|
|
112
112
|
* Survey ALL known first-party shortnames (vault / notes / scribe / agent /
|
|
113
|
-
* surface) regardless of tier — `installed` is true when the service
|
|
113
|
+
* surface / app) regardless of tier — `installed` is true when the service
|
|
114
114
|
* has a row in services.json. The fresh-install OFFER is narrowed downstream
|
|
115
115
|
* by `isOfferable` (drops already-installed + `deprecated`-tier shorts —
|
|
116
116
|
* notes); agent (`experimental`) is flagged exploratory in its blurb
|
|
@@ -121,7 +121,7 @@ function defaultAvailability(): InteractiveAvailability {
|
|
|
121
121
|
* like any unknown row it neither blocks setup nor appears in the offer.
|
|
122
122
|
*
|
|
123
123
|
* The full ServiceSpec is only available pre-install for FIRST_PARTY_FALLBACKS
|
|
124
|
-
* shorts (notes —
|
|
124
|
+
* shorts (notes / app — both carry a vendored manifest). KNOWN_MODULES shorts
|
|
125
125
|
* (vault / scribe / agent / surface) ship `.parachute/module.json`
|
|
126
126
|
* and self-register; pre-install we know manifestName + the urlForEntry quirk
|
|
127
127
|
* from `KNOWN_MODULES[short].extras`, which is all the survey/summary needs.
|
|
@@ -175,14 +175,17 @@ export function isOfferable(choice: { short: string; installed: boolean }): bool
|
|
|
175
175
|
const BLURBS: Record<string, string> = {
|
|
176
176
|
vault: "knowledge graph (MCP) — your owner-authenticated note + tag store",
|
|
177
177
|
surface: "Parachute UI host — auto-installs Notes on first boot (the recommended UI path)",
|
|
178
|
-
//
|
|
179
|
-
|
|
178
|
+
// hub-parity P5 (2026-07-11): `app` is now the NEW super-surface front
|
|
179
|
+
// door (@openparachute/parachute-app) — a real FIRST_PARTY_FALLBACKS
|
|
180
|
+
// entry, not a legacy survey row. It is UNRELATED to the pre-2026-05-27
|
|
181
|
+
// `app` package that renamed to `surface` (that blurb lives under the
|
|
182
|
+
// `surface` key above); the two just happen to share the short name
|
|
183
|
+
// across a rename.
|
|
184
|
+
app: "the super-surface front door — one app to sign in, browse your vault, and manage your hub/cloud",
|
|
180
185
|
// notes is `deprecated` (not offered on a fresh setup) — this blurb only
|
|
181
186
|
// renders if a legacy install surfaces it in the survey.
|
|
182
187
|
notes: "Notes PWA — web/mobile UI on top of vault (notes-daemon; superseded by `surface`)",
|
|
183
188
|
scribe: "audio transcription for dictation + recordings",
|
|
184
|
-
agent:
|
|
185
|
-
"(exploratory) chat with your Claude Code sessions — a channel per session (renamed from channel)",
|
|
186
189
|
};
|
|
187
190
|
|
|
188
191
|
function blurbFor(choice: ServiceChoice): string {
|
package/src/help.ts
CHANGED
|
@@ -362,11 +362,11 @@ Exit codes:
|
|
|
362
362
|
|
|
363
363
|
Example:
|
|
364
364
|
$ parachute status
|
|
365
|
-
SERVICE
|
|
366
|
-
parachute-vault
|
|
365
|
+
SERVICE PORT VERSION STATE PID UPTIME LATENCY SOURCE
|
|
366
|
+
parachute-vault 1940 0.2.4 active 12345 2h 13m 2ms bun-linked → parachute-vault @ 8aa167b
|
|
367
367
|
→ http://127.0.0.1:1940/vault/default/mcp
|
|
368
|
-
parachute-
|
|
369
|
-
→ http://127.0.0.1:1946/surface
|
|
368
|
+
parachute-surface 1946 0.2.0 active 12346 2h 12m 3ms npm (0.2.0-rc.4)
|
|
369
|
+
→ http://127.0.0.1:1946/surface
|
|
370
370
|
`;
|
|
371
371
|
}
|
|
372
372
|
|
|
@@ -551,8 +551,8 @@ Examples:
|
|
|
551
551
|
Module start commands (run by the supervisor under \`serve\`):
|
|
552
552
|
vault parachute-vault serve
|
|
553
553
|
scribe parachute-scribe serve
|
|
554
|
-
|
|
555
|
-
|
|
554
|
+
surface parachute-surface serve
|
|
555
|
+
app bun <cli>/notes-serve.ts --port <configured> --mount <paths[0]> --package @openparachute/parachute-app
|
|
556
556
|
notes bun <cli>/notes-serve.ts --port <configured> --mount <paths[0]> # back-compat: legacy notes-daemon
|
|
557
557
|
`;
|
|
558
558
|
}
|