@tpsdev-ai/flair 0.53.0 → 0.54.2
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 +4 -1
- package/dist/build-info.json +3 -3
- package/dist/cli.js +1791 -15648
- package/dist/commands/agent.js +453 -0
- package/dist/commands/attention.js +121 -0
- package/dist/commands/backup.js +115 -0
- package/dist/commands/bootstrap.js +91 -0
- package/dist/commands/bridge.js +608 -0
- package/dist/commands/deploy.js +180 -0
- package/dist/commands/doctor.js +1665 -0
- package/dist/commands/export.js +110 -0
- package/dist/commands/federation.js +1575 -0
- package/dist/commands/fleet.js +73 -0
- package/dist/commands/grant.js +109 -0
- package/dist/commands/hook.js +193 -0
- package/dist/commands/idp.js +193 -0
- package/dist/commands/import.js +134 -0
- package/dist/commands/init.js +1203 -0
- package/dist/commands/inspect.js +45 -0
- package/dist/commands/keys.js +187 -0
- package/dist/commands/mcp.js +707 -0
- package/dist/commands/memory.js +501 -0
- package/dist/commands/migrate-harness-memory.js +270 -0
- package/dist/commands/orgevent.js +138 -0
- package/dist/commands/presence.js +76 -0
- package/dist/commands/principal.js +338 -0
- package/dist/commands/quality.js +1164 -0
- package/dist/commands/reembed.js +296 -0
- package/dist/commands/relationship.js +76 -0
- package/dist/commands/rem.js +1048 -0
- package/dist/commands/restore.js +130 -0
- package/dist/commands/search.js +244 -0
- package/dist/commands/service.js +315 -0
- package/dist/commands/session.js +184 -0
- package/dist/commands/soul.js +155 -0
- package/dist/commands/status.js +931 -0
- package/dist/commands/test.js +93 -0
- package/dist/commands/uninstall.js +143 -0
- package/dist/commands/upgrade.js +1628 -0
- package/dist/commands/workspace.js +114 -0
- package/dist/deploy.js +24 -0
- package/dist/engine-version.js +12 -4
- package/dist/fabric-npm-install.js +87 -0
- package/dist/fabric-upgrade.js +30 -15
- package/dist/federation-verify.js +498 -0
- package/dist/fleet-verify.js +144 -21
- package/dist/install/clients.js +167 -0
- package/dist/lib/auth-resolve.js +76 -1
- package/dist/lib/daemon-liveness.js +131 -2
- package/dist/lib/doctor-config-path.js +61 -0
- package/dist/lib/doctor-federation-driver.js +189 -0
- package/dist/lib/doctor-run.js +40 -0
- package/dist/lib/entity-vocab-cli.js +3 -3
- package/dist/lib/federation-pair-identity.js +47 -0
- package/dist/lib/launchd-repair.js +5 -4
- package/dist/lib/npm-registry.js +578 -0
- package/dist/lib/ops-api-bind.js +115 -0
- package/dist/lib/owned-pins.js +219 -0
- package/dist/lib/uninstall-purge.js +218 -0
- package/dist/rem/restore.js +8 -10
- package/dist/resources/AgentReadPosition.js +74 -0
- package/dist/resources/Federation.js +8 -2
- package/dist/resources/Memory.js +4 -3
- package/dist/resources/MemoryBootstrap.js +41 -25
- package/dist/resources/MemoryCandidate.js +5 -6
- package/dist/resources/OrgEventCatchup.js +126 -47
- package/dist/resources/agent-read-position-lib.js +83 -0
- package/dist/resources/agent-read-position.js +120 -0
- package/dist/resources/embeddings-boot.js +32 -0
- package/dist/resources/federation-peer-liveness.js +73 -0
- package/dist/resources/health.js +68 -19
- package/dist/resources/mcp-tools.js +48 -279
- package/dist/resources/memory-visibility.js +3 -3
- package/dist/resources/migration-boot.js +59 -18
- package/dist/resources/migrations/embedding-stamp.js +20 -1
- package/dist/resources/migrations/recheck.js +43 -0
- package/dist/resources/migrations/runner.js +6 -1
- package/dist/resources/migrations/stamp-outstanding.js +171 -0
- package/dist/resources/migrations/visibility-backfill.js +2 -2
- package/dist/resources/org-event-catchup-lib.js +47 -0
- package/dist/resources/record-owner-guard.js +1 -0
- package/dist/resources/tool-descriptors/index.js +669 -0
- package/dist/stamp-migration-verify.js +163 -0
- package/dist/stamp-outstanding.js +144 -0
- package/dist/version-check.js +29 -8
- package/docs/api-reference.md +4 -2
- package/docs/deploying-on-fabric.md +11 -10
- package/docs/deployment.md +3 -1
- package/docs/federation.md +19 -0
- package/docs/hosted-on-fabric.md +3 -3
- package/docs/quickstart.md +2 -1
- package/docs/releasing.md +20 -6
- package/docs/spoke-bringup.md +10 -5
- package/docs/standalone-local.md +3 -1
- package/docs/upgrade.md +25 -6
- package/package.json +4 -4
- package/schemas/agent.graphql +15 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { resolveKeyPath, buildEd25519Auth } from "../lib/auth-resolve.js";
|
|
2
|
+
let cli;
|
|
3
|
+
/** Bind shared CLI helpers. cli.ts calls this immediately before register(program). */
|
|
4
|
+
export function bindCli(fns) {
|
|
5
|
+
cli = fns;
|
|
6
|
+
}
|
|
7
|
+
function resolveBaseUrl(opts) {
|
|
8
|
+
return cli.resolveBaseUrl(opts);
|
|
9
|
+
}
|
|
10
|
+
function resolveSigningAgentId(opts, command) {
|
|
11
|
+
return cli.resolveSigningAgentId(opts, command);
|
|
12
|
+
}
|
|
13
|
+
function parseEntitiesOptionOrExit(csv) {
|
|
14
|
+
return cli.parseEntitiesOptionOrExit(csv);
|
|
15
|
+
}
|
|
16
|
+
// ─── flair workspace ─────────────────────────────────────────────────────────
|
|
17
|
+
//
|
|
18
|
+
// Coordination write surface (Kris #510). `workspace set` writes the
|
|
19
|
+
// agent's OWN WorkspaceState via a signed PUT /WorkspaceState/{id}. Identity
|
|
20
|
+
// is asserted by including agentId in the body — the server never trusts it
|
|
21
|
+
// blindly, it 403s any mismatch against the Ed25519 signature's agentId
|
|
22
|
+
// (WorkspaceState.put(), resources/WorkspaceState.ts), so this is a
|
|
23
|
+
// self-declaration the server verifies 1:1, not attribution-from-body.
|
|
24
|
+
//
|
|
25
|
+
// (flair#679, measured against a real spawned Harper): table-backed resources
|
|
26
|
+
// only accept writes via PUT /<Table>/<id> — a bare POST /WorkspaceState 405s
|
|
27
|
+
// ("does not have a post method implemented to handle HTTP method POST"),
|
|
28
|
+
// same restriction documented in resources/Memory.ts and already fixed for
|
|
29
|
+
// `soul set` (#498). WorkspaceState.ts DOES define a post() method, but
|
|
30
|
+
// Harper's REST layer never routes a real HTTP POST to it — post() is only
|
|
31
|
+
// reachable via in-process resource instantiation, never the wire. put(),
|
|
32
|
+
// unlike post(), does NOT default createdAt/timestamp/agentId — the CLI
|
|
33
|
+
// supplies them all explicitly below.
|
|
34
|
+
export const MAX_WORKSPACE_FIELD_LENGTH = 2000;
|
|
35
|
+
/** Register the `flair workspace` command group (flair#1635). */
|
|
36
|
+
export function register(program) {
|
|
37
|
+
const ENTITIES_OPTION_DESCRIPTION = cli.ENTITIES_OPTION_DESCRIPTION;
|
|
38
|
+
const workspace = program.command("workspace").description("Manage agent workspace state (The Office Space)");
|
|
39
|
+
workspace
|
|
40
|
+
.command("set")
|
|
41
|
+
.description("Set your agent's current workspace state (PUT /WorkspaceState/{id})")
|
|
42
|
+
.requiredOption("--ref <ref>", "Workspace ref (branch, worktree, or task ref)")
|
|
43
|
+
.option("--label <text>", "Human-readable label for this workspace")
|
|
44
|
+
.option("--provider <name>", "Provider/runtime (e.g. claude-code, openclaw)", "cli")
|
|
45
|
+
.option("--task <id>", "Task/issue id this workspace is attached to")
|
|
46
|
+
.option("--phase <phase>", "Current phase (e.g. design, implement, review)")
|
|
47
|
+
.option("--summary <text>", "Short summary of current workspace state")
|
|
48
|
+
.option("--entities <csv>", ENTITIES_OPTION_DESCRIPTION)
|
|
49
|
+
.option("--agent <id>", "Agent ID (env: FLAIR_AGENT_ID)")
|
|
50
|
+
.option("--port <port>", "Harper HTTP port")
|
|
51
|
+
.option("--target <url>", "Remote Flair URL (env: FLAIR_TARGET)")
|
|
52
|
+
.action(async (opts) => {
|
|
53
|
+
const { agentId } = resolveSigningAgentId(opts, "workspace set");
|
|
54
|
+
if (!agentId) {
|
|
55
|
+
console.error("Error: agent ID required. Pass --agent <id> or set FLAIR_AGENT_ID environment variable.");
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
// Validate field lengths (free text → cap to bound the write).
|
|
59
|
+
for (const [name, val] of [["ref", opts.ref], ["label", opts.label], ["summary", opts.summary]]) {
|
|
60
|
+
if (val && String(val).length > MAX_WORKSPACE_FIELD_LENGTH) {
|
|
61
|
+
console.error(`Error: --${name} exceeds ${MAX_WORKSPACE_FIELD_LENGTH} character limit (got ${String(val).length}).`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// flair#1288: validate --entities before any key/network work; exits 1
|
|
66
|
+
// with the canonical format-and-type-set message on any malformed value.
|
|
67
|
+
const entities = opts.entities ? parseEntitiesOptionOrExit(String(opts.entities)) : undefined;
|
|
68
|
+
const keyPath = resolveKeyPath(agentId);
|
|
69
|
+
if (!keyPath) {
|
|
70
|
+
console.error(`Error: private key not found for agent '${agentId}'. Check ~/.flair/keys/ or set FLAIR_KEY_DIR.`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
const baseUrl = resolveBaseUrl(opts).replace(/\/$/, "");
|
|
74
|
+
// Deterministic id (agentId:ref) — re-running `workspace set` for the same
|
|
75
|
+
// ref overwrites the same record, which is intentional (one row per
|
|
76
|
+
// agent+ref, not an append log).
|
|
77
|
+
const id = `${agentId}:${opts.ref}`;
|
|
78
|
+
const auth = buildEd25519Auth(agentId, "PUT", `/WorkspaceState/${id}`, keyPath);
|
|
79
|
+
// agentId IS included in the body now — WorkspaceState.put() (unlike
|
|
80
|
+
// post()) does not auto-attribute from the signature, it 403s any
|
|
81
|
+
// mismatch. This is a self-declaration the server verifies against the
|
|
82
|
+
// signature, not a forgeable claim.
|
|
83
|
+
const now = new Date().toISOString();
|
|
84
|
+
const body = {
|
|
85
|
+
id,
|
|
86
|
+
agentId,
|
|
87
|
+
ref: opts.ref,
|
|
88
|
+
provider: opts.provider ?? "cli",
|
|
89
|
+
timestamp: now,
|
|
90
|
+
createdAt: now,
|
|
91
|
+
};
|
|
92
|
+
if (opts.label)
|
|
93
|
+
body.label = opts.label;
|
|
94
|
+
if (opts.task)
|
|
95
|
+
body.taskId = opts.task;
|
|
96
|
+
if (opts.phase)
|
|
97
|
+
body.phase = opts.phase;
|
|
98
|
+
if (opts.summary)
|
|
99
|
+
body.summary = opts.summary;
|
|
100
|
+
if (entities && entities.length > 0)
|
|
101
|
+
body.entities = entities;
|
|
102
|
+
const res = await fetch(`${baseUrl}/WorkspaceState/${id}`, {
|
|
103
|
+
method: "PUT",
|
|
104
|
+
headers: { "Content-Type": "application/json", Authorization: auth },
|
|
105
|
+
body: JSON.stringify(body),
|
|
106
|
+
});
|
|
107
|
+
if (!res.ok) {
|
|
108
|
+
const text = await res.text().catch(() => "");
|
|
109
|
+
console.error(`Error: PUT /WorkspaceState/${id} failed (${res.status}): ${text}`);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
console.log(`✓ Workspace state updated for '${agentId}': ref=${opts.ref}${opts.phase ? `, phase=${opts.phase}` : ""}`);
|
|
113
|
+
});
|
|
114
|
+
}
|
package/dist/deploy.js
CHANGED
|
@@ -6,6 +6,8 @@ import { fileURLToPath } from "node:url";
|
|
|
6
6
|
import { createRequire } from "node:module";
|
|
7
7
|
import { COMPONENT_ENV_FILENAME, PUBLIC_URL_KEY, isLoopbackUrl, planComponentEnv, publicUrlRemedy, } from "./component-env.js";
|
|
8
8
|
import { awaitOriginQuiescent, awaitReplicationConvergence, defaultConvergenceDeps, parseReplicationFailure, } from "./replication-convergence.js";
|
|
9
|
+
import { DEFAULT_STAMP_VERIFY_TIMEOUT_MS, verifyStampMigrationConverged } from "./stamp-migration-verify.js";
|
|
10
|
+
import { FABRIC_NPM_INSTALL_COMMAND } from "./fabric-npm-install.js";
|
|
9
11
|
// Files that must be PRESENT for a deploy root to be usable at all — a
|
|
10
12
|
// preflight sanity check, not the payload definition.
|
|
11
13
|
//
|
|
@@ -218,6 +220,12 @@ export function buildHarperDeployArgs(opts, url, project) {
|
|
|
218
220
|
`replicated=${opts.replicated !== false}`,
|
|
219
221
|
`deployment_timeout=${deploymentTimeoutMs}`,
|
|
220
222
|
`install_timeout=${installTimeoutMs}`,
|
|
223
|
+
// flair#886: Harper's default `npm install` writes every tarball into
|
|
224
|
+
// the node's `~/.npm/_cacache` and never evicts it. This command runs
|
|
225
|
+
// the same install against a disposable cache and deletes it after
|
|
226
|
+
// (see src/fabric-npm-install.ts). One argv element so Harper's
|
|
227
|
+
// `key=value` parser keeps the spaces inside the value.
|
|
228
|
+
`install_command=${FABRIC_NPM_INSTALL_COMMAND}`,
|
|
221
229
|
];
|
|
222
230
|
// --ignore-replication-errors escape hatch. Only appended when
|
|
223
231
|
// set — omitted entirely otherwise, so this is a no-op for every existing
|
|
@@ -929,6 +937,22 @@ export async function deploy(opts) {
|
|
|
929
937
|
timeoutMs: opts.verifyTimeoutMs ?? DEFAULT_VERIFY_TIMEOUT_MS,
|
|
930
938
|
onProgress: opts.onProgress,
|
|
931
939
|
});
|
|
940
|
+
// flair#1073: route verify proves the component is serving. It does not
|
|
941
|
+
// prove embedding-stamp re-embedded pre-flip rows. `upgrade --target`
|
|
942
|
+
// was reporting success while a Fabric corpus stayed split for days.
|
|
943
|
+
// Same --no-verify escape hatch; needs Basic admin (token-only skips).
|
|
944
|
+
if (opts.fabricUser && opts.fabricPassword) {
|
|
945
|
+
await verifyStampMigrationConverged({
|
|
946
|
+
baseUrl: url,
|
|
947
|
+
fabricUser: opts.fabricUser,
|
|
948
|
+
fabricPassword: opts.fabricPassword,
|
|
949
|
+
timeoutMs: opts.verifyTimeoutMs ?? DEFAULT_STAMP_VERIFY_TIMEOUT_MS,
|
|
950
|
+
onProgress: opts.onProgress,
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
else {
|
|
954
|
+
opts.onProgress?.("skipping embedding-stamp convergence check — needs --fabric-user/--fabric-password (token-only auth cannot read /HealthDetail)");
|
|
955
|
+
}
|
|
932
956
|
// Only meaningful for a target that is not loopback: a local Harper SHOULD
|
|
933
957
|
// advertise loopback, and asserting otherwise there would be wrong.
|
|
934
958
|
if (publicUrl) {
|
package/dist/engine-version.js
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
20
20
|
import { join, resolve } from "node:path";
|
|
21
21
|
import { homedir } from "node:os";
|
|
22
|
+
import { fetchDeclaredDependencies } from "./lib/npm-registry.js";
|
|
22
23
|
/** Filename of the engine-version stamp inside the data directory. */
|
|
23
24
|
export const ENGINE_VERSION_STAMP = "engine-version.txt";
|
|
24
25
|
/** Root directory for pre-upgrade snapshots (~/.flair/upgrade-snapshots). */
|
|
@@ -47,11 +48,18 @@ export function readInstalledHarperVersion(packageRoot) {
|
|
|
47
48
|
*/
|
|
48
49
|
export async function fetchDeclaredHarperVersion(flairVersion) {
|
|
49
50
|
try {
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
// flair#1688: resolve the configured registry rather than assuming npmjs —
|
|
52
|
+
// the engine-version decision must look at the same package the upgrade
|
|
53
|
+
// will actually install. flair#1692: the same scheme allowlist, redirect
|
|
54
|
+
// refusal, npm transport, and non-default-registry handling apply.
|
|
55
|
+
const result = await fetchDeclaredDependencies("@tpsdev-ai/flair", flairVersion, { timeoutMs: 5000 });
|
|
56
|
+
if (result.kind === "refused") {
|
|
57
|
+
console.error(result.message);
|
|
52
58
|
return null;
|
|
53
|
-
|
|
54
|
-
|
|
59
|
+
}
|
|
60
|
+
if (result.kind !== "ok")
|
|
61
|
+
return null;
|
|
62
|
+
return result.dependencies?.harper ?? result.dependencies?.["@harperfast/harper"] ?? null;
|
|
55
63
|
}
|
|
56
64
|
catch {
|
|
57
65
|
return null;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fabric-npm-install.ts — node-side `npm install` for Fabric deploys (flair#886).
|
|
3
|
+
*
|
|
4
|
+
* Harper's default component install writes every fetched tarball into the
|
|
5
|
+
* node's `~/.npm/_cacache` and never evicts it. A month of `flair deploy` /
|
|
6
|
+
* `flair upgrade --target` therefore leaves a growing cache on the hub —
|
|
7
|
+
* hundreds of MB per install once `node-llama-cpp` prebuilds are in the
|
|
8
|
+
* tree — while `get_components` and `system_information` never show it.
|
|
9
|
+
*
|
|
10
|
+
* This file is the `install_command` those two CLI paths pass to
|
|
11
|
+
* `harper deploy`. It runs `npm install` against a disposable cache
|
|
12
|
+
* directory and deletes that directory afterwards, so cache lifetime
|
|
13
|
+
* matches the deploy rather than the node.
|
|
14
|
+
*
|
|
15
|
+
* Harper splits `install_command` on spaces and spawns without a shell
|
|
16
|
+
* (`Application.installApplication`), so the command must be a single
|
|
17
|
+
* executable plus a path — no `sh -c`, no `&& rm`. The flags after
|
|
18
|
+
* `install` match Harper's own default when `install.command` is unset
|
|
19
|
+
* (`--force --ignore-scripts`); a custom command replaces that default
|
|
20
|
+
* entirely, and dropping either flag would change what the node runs.
|
|
21
|
+
*/
|
|
22
|
+
import { spawnSync } from "node:child_process";
|
|
23
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
24
|
+
import { tmpdir } from "node:os";
|
|
25
|
+
import { join, resolve } from "node:path";
|
|
26
|
+
import { pathToFileURL } from "node:url";
|
|
27
|
+
/** Path Harper runs, relative to the extracted component directory. */
|
|
28
|
+
export const FABRIC_NPM_INSTALL_ENTRY = "dist/fabric-npm-install.js";
|
|
29
|
+
/**
|
|
30
|
+
* Passed as `install_command=…` to `harper deploy`. Harper's
|
|
31
|
+
* `buildRequest` JSON-parses values that look like JSON; this string
|
|
32
|
+
* does not, so it stays a string. `split(" ")` must yield exactly
|
|
33
|
+
* `["node", FABRIC_NPM_INSTALL_ENTRY]`.
|
|
34
|
+
*/
|
|
35
|
+
export const FABRIC_NPM_INSTALL_COMMAND = `node ${FABRIC_NPM_INSTALL_ENTRY}`;
|
|
36
|
+
/** Harper's default argv when `install.command` is unset, plus `--cache`. */
|
|
37
|
+
export const FABRIC_NPM_INSTALL_ARGS_BEFORE_CACHE = [
|
|
38
|
+
"install",
|
|
39
|
+
"--force",
|
|
40
|
+
"--ignore-scripts",
|
|
41
|
+
];
|
|
42
|
+
/**
|
|
43
|
+
* Run `npm install` with `--cache` pointed at a fresh temp directory,
|
|
44
|
+
* then remove that directory on success or failure.
|
|
45
|
+
*
|
|
46
|
+
* Returns npm's exit status. Throws only when the spawn itself fails
|
|
47
|
+
* to start (ENOENT on `npm`, etc.) — the same split `fabric-upgrade`'s
|
|
48
|
+
* local staging install uses.
|
|
49
|
+
*/
|
|
50
|
+
export function installWithEphemeralNpmCache(deps = {}) {
|
|
51
|
+
const spawn = deps.spawn ??
|
|
52
|
+
((command, args, opts) => spawnSync(command, args, opts));
|
|
53
|
+
const mkdtemp = deps.mkdtemp ?? ((prefix) => mkdtempSync(prefix));
|
|
54
|
+
const rm = deps.rm ?? ((path) => rmSync(path, { recursive: true, force: true }));
|
|
55
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
56
|
+
const tmp = deps.tmpdir ?? tmpdir();
|
|
57
|
+
const cacheDir = mkdtemp(join(tmp, "flair-npm-"));
|
|
58
|
+
try {
|
|
59
|
+
const result = spawn("npm", [...FABRIC_NPM_INSTALL_ARGS_BEFORE_CACHE, "--cache", cacheDir], { cwd, stdio: "inherit" });
|
|
60
|
+
if (result.error)
|
|
61
|
+
throw result.error;
|
|
62
|
+
return result.status ?? 1;
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
rm(cacheDir);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function invokedAsMain() {
|
|
69
|
+
const entry = process.argv[1];
|
|
70
|
+
if (!entry)
|
|
71
|
+
return false;
|
|
72
|
+
try {
|
|
73
|
+
return import.meta.url === pathToFileURL(resolve(entry)).href;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (invokedAsMain()) {
|
|
80
|
+
try {
|
|
81
|
+
process.exit(installWithEphemeralNpmCache());
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
console.error(err instanceof Error ? err.message : err);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
}
|
package/dist/fabric-upgrade.js
CHANGED
|
@@ -30,6 +30,7 @@ import { mkdtempSync, rmSync, writeFileSync, readFileSync, existsSync } from "no
|
|
|
30
30
|
import { join } from "node:path";
|
|
31
31
|
import { tmpdir } from "node:os";
|
|
32
32
|
import { createRequire } from "node:module";
|
|
33
|
+
import { createRegistryNoticePrinter, fetchDeclaredDependencies, fetchLatestVersion } from "./lib/npm-registry.js";
|
|
33
34
|
/**
|
|
34
35
|
* Minimum harper version whose component packager works when the
|
|
35
36
|
* package root is under node_modules (flair#513 — the empty-tarball fix landed
|
|
@@ -154,30 +155,44 @@ export function buildDeployablePackageJson(flairVersion, pin) {
|
|
|
154
155
|
return pkg;
|
|
155
156
|
}
|
|
156
157
|
// ─── Default (real) dependency implementations ──────────────────────────────
|
|
157
|
-
|
|
158
|
+
// flair#1692: one printer per process, so a Fabric upgrade names the registry
|
|
159
|
+
// (and source) it stages from without repeating the line per lookup.
|
|
160
|
+
const noticeRegistry = createRegistryNoticePrinter();
|
|
158
161
|
async function defaultFetchLatestFlairVersion() {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
162
|
+
// flair#1688: the configured registry (scoped `@tpsdev-ai:registry`,
|
|
163
|
+
// project/user/global .npmrc, env `npm_config_registry`), not a hardcoded
|
|
164
|
+
// host — a Fabric upgrade must stage the package from the mirror the
|
|
165
|
+
// operator configured, or fail rather than silently use the public one.
|
|
166
|
+
// flair#1692: the value is validated as strict semver before it is used as
|
|
167
|
+
// `dependencies` in the staged package.json (npm accepts `pkg@<url>` as a
|
|
168
|
+
// remote-tarball spec, so an unvalidated value is an arbitrary-install
|
|
169
|
+
// primitive).
|
|
170
|
+
const result = await fetchLatestVersion(FLAIR_PKG, { timeoutMs: 10_000, onRegistry: noticeRegistry });
|
|
171
|
+
if (result.kind === "ok")
|
|
172
|
+
return result.version;
|
|
173
|
+
if (result.kind === "refused")
|
|
174
|
+
throw new Error(result.message);
|
|
175
|
+
if (result.kind === "invalid") {
|
|
176
|
+
throw new Error(`npm registry returned a non-semver latest version (${JSON.stringify(result.value)}) for ${FLAIR_PKG} ` +
|
|
177
|
+
`from ${result.registry.url} (source: ${result.registry.source}) — refusing to use it`);
|
|
164
178
|
}
|
|
165
|
-
|
|
166
|
-
if (!data.version)
|
|
167
|
-
throw new Error(`No version in registry response for ${FLAIR_PKG}`);
|
|
168
|
-
return data.version;
|
|
179
|
+
throw new Error(`Could not determine ${FLAIR_PKG}/latest: ${result.message}`);
|
|
169
180
|
}
|
|
170
181
|
async function defaultFetchDeclaredHarperVersion(flairVersion) {
|
|
171
|
-
const
|
|
172
|
-
|
|
182
|
+
const result = await fetchDeclaredDependencies(FLAIR_PKG, flairVersion, {
|
|
183
|
+
timeoutMs: 10_000,
|
|
184
|
+
onRegistry: noticeRegistry,
|
|
173
185
|
});
|
|
174
|
-
if (
|
|
186
|
+
if (result.kind === "refused") {
|
|
187
|
+
console.error(result.message);
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
if (result.kind !== "ok")
|
|
175
191
|
return null;
|
|
176
|
-
const data = (await res.json());
|
|
177
192
|
// Either package name (flair#870) — pre-rename flair versions declare the
|
|
178
193
|
// scoped one, and those are precisely the versions that may need an override.
|
|
179
194
|
for (const name of HARPER_PKG_NAMES) {
|
|
180
|
-
const declared =
|
|
195
|
+
const declared = result.dependencies?.[name];
|
|
181
196
|
if (declared)
|
|
182
197
|
return declared;
|
|
183
198
|
}
|