SDKs · 5. SDKCrux packages
SDKCrux is a second SDK monorepo, unrelated to the Crux sdks/ directory, that publishes into the same @cuecrux/ npm scope. Exactly one of its packages is installable from public npm today: @cuecrux/engine-client 1.1.1. The other twenty are either restricted to GitHub Packages, declared public but never published, or private.
This chapter is reference for people who already depend on SDKCrux or need to evaluate it. If you are choosing a client for the Crux Daemon, chapter 0 already answered that: use the in-repo Crux SDKs. Read this chapter for the Engine client, for @cuecrux/memory's API, which is the best ergonomics anywhere in the portfolio, and unpublished, or to work out what you are actually running.
Every defect referenced here is documented with its evidence in chapter 6.
5.1 What you can actually install
Registry state queried 2026-07-27.
| Package | Manifest version | publishConfig | On public npm | Release workflow |
|---|---|---|---|---|
@cuecrux/engine-client | 1.1.1 | registry.npmjs.org, access: public | Yes, 1.1.1 | Two, both workflow_dispatch |
@cuecrux/factory | 0.1.0 | registry.npmjs.org, access: public | No, 404 | One, workflow_dispatch |
@cuecrux/memory | 0.1.0 | registry.npmjs.org, access: public | No, 404 | None |
cuecrux-receipt | 0.1.1 | access: public, no registry override | No, 404 | None |
@cuecrux/policy-cli | 0.14.1 | none at all | No, 404 | None |
Four of those five are not on npm at all, and three of the four have no release workflow that could ever put them there. A public publishConfig with no workflow behind it is a declaration of intent, not a release. If you want @cuecrux/memory or cuecrux-receipt, vendoring the source is the only route.
The repository root is private, pnpm@9.12.1, and requires Node ≥ 22 (package.json:2). Registry routing for the internal scope is set in .npmrc: @cuecrux-internal:registry=https://npm.pkg.github.com (.npmrc:2).
5.2 The complete inventory
Twenty-one packages, in three regimes.
Public scope, npm-targeted
| Package | Version | Path | State |
|---|---|---|---|
@cuecrux/engine-client | 1.1.1 | packages/engine-client | Published. Typings do not resolve, 6.1. Two dead methods, 6.2 |
@cuecrux/factory | 0.1.0 | packages/factory | Not published. A one-line re-export of packages/internal/factory |
@cuecrux/memory | 0.1.0 | packages/memory | Not published, no workflow, not in CODEOWNERS, not in the test workspace. One response-shape bug, 6.4 |
cuecrux-receipt | 0.1.1 | packages/cli | Not published. Would install broken, 6.5 |
@cuecrux/policy-cli | 0.14.1 | apps/policy-cli | Not published. Public scope by accident, and invisible to the publish guard, 6.6 |
Restricted, GitHub Packages, with an explicit publishConfig
Each of these declares registry: https://npm.pkg.github.com and access: restricted. You need a GitHub Packages token to install any of them.
| Package | Version | Path | What it is |
|---|---|---|---|
@cuecrux-internal/core | 1.0.0 | packages/core | The shared runtime: 48 export lines covering budgets, canonical JSON, crypto, an encrypted cache, telemetry, retry, tracing, policy, provenance, replay and assurance receipts (index.ts:1). Ships two binaries, cuecrux-gen-sdk-receipt and cuecrux-verify-sdk-receipt |
@cuecrux-internal/engine | 1.0.0 | packages/engine | Engine request/response DTOs as Zod schemas, an EngineClient and EngineHttpError, plus a ./mocks subpath (index.ts:1) |
@cuecrux-internal/engine-sdk | 0.1.1 | packages/engine-sdk | Receipt fetch and verify helpers; re-exports ./verify only (index.ts:1) |
@cuecrux-internal/mocks | 1.0.0 | packages/mocks | MSW handlers and fixtures for /healthz, /search and /v1/answers (index.ts:1) |
@cuecrux-internal/watch | 1.2.0 | packages/watch | Operator utilities: types, env, Postgres helpers, SQL generation, intake, thresholds, telemetry, Prometheus (index.ts:1) |
@cuecrux-internal/factory | 0.1.0 | packages/internal/factory | Factory job contracts and FactoryClient, the source of truth behind the public @cuecrux/factory (index.ts:1) |
No publishConfig at all
These are private: false with no publishing configuration. The @cuecrux-internal/* names are saved from a public accident by the .npmrc scope mapping; @cuecrux/policy-cli is not.
| Package | Version | Path | What it is |
|---|---|---|---|
@cuecrux-internal/auth | 0.14.1 | packages/auth | OIDC, SAML and session helpers (index.ts:1) |
@cuecrux-internal/bff | 0.15.1 | packages/bff | Backend-for-frontend: policyGuard, engineClient, whyTrust, an operator summary API (index.ts:1) |
@cuecrux-internal/cache | 1.0.0 | packages/cache | getOrCompute, topicKey, MemorySemanticCache (index.ts:5) |
@cuecrux-internal/connectors | 0.0.1 | packages/connectors | Google Drive and Confluence export, permissions, aggregation (index.ts:1) |
@cuecrux-internal/eval-lab | 0.0.1 | packages/eval-lab | recallAtK, dcgAtK, ndcgAtK, hallucinationFlag, aggregate; binary cuecrux-eval (index.ts:14) |
@cuecrux-internal/trust-report | 0.13.3 | packages/trust-report | Trust-report DTOs, plugin hooks, HTML and PDF renderers (index.ts:1) |
@cuecrux-internal/web | 0.1.0 | packages/web | Plan-aware UI hints and a ModeSwitch.vue (index.ts:1) |
@cuecrux-internal/webcrux | 0.14.0 | packages/webcrux | resolveFreshnessState, nextStricterMode, daysBetween and a StaleBanner.vue (index.ts:1) |
@cuecrux-internal/sdk-core | 0.0.1 | packages/internal/core | Name and path disagree: it lives at internal/core but is named sdk-core. Only connectors depends on it |
@cuecrux-internal/observability | 0.0.0 | packages/internal/observability | private: true. Plain index.js, no TypeScript, no build |
@cuecrux/policy-cli | 0.14.1 | apps/policy-cli | A binary named policy-cli, depending on the restricted @cuecrux-internal/core (package.json:2) |
5.3 CruxMemory: the two-verb facade
This is the best-designed client API in either estate. It is also not published, has no release workflow, no CODEOWNERS entry, no changelog and no compatibility manifest. Treat it as a design to copy or a source tree to vendor, not a dependency to add.
@cuecrux/memory is generated from the daemon's own /v1/openapi.json, has zero runtime dependencies, and needs a global fetch or an injected one (package.json:11).
class CruxMemory {
/** Full typed client (facts, sessions, query, receipts), the escape hatch. */
readonly client: DaemonClient;
constructor(client: DaemonClient);
static connect(options?: ConnectOptions): Promise<CruxMemory>;
remember(content: string, options?: RememberOptions): Promise<Fact>;
recall(query: string, options?: RecallOptions): Promise<Fact[]>;
forget(factId: string): Promise<void>;
}
Source: memory.ts:52. ConnectOptions is an alias for DiscoveryOptions (memory.ts:36).
const memory = await CruxMemory.connect();
await memory.remember("Deploys go through cargo-deploy, never bare cargo.");
const hits = await memory.recall("how do we deploy?");
RememberOptions
| Field | Type | Default | Maps to |
|---|---|---|---|
entity | string | "memory" | entity |
key | string | mem-<Date.now()>-<6 random base36 chars> (memory.ts:38) | key |
confidence | number | omitted | confidence |
private | boolean | omitted | private, the daemon rejects true with a 400 (facts.rs:229); the SDK's own JSDoc says so (memory.ts:12) |
horizonClass | HorizonClass: one of volatile, medium, stable, none | omitted | horizon_class |
actor | string | omitted | actor: durable authorship, a passport id or agent name |
sourceReceipt | string | omitted | source_receipt |
Source: memory.ts:6.
horizonClass and actor are the reason this facade is more capable than @cuecrux/client on writes: neither Crux SDK can set them.
RecallOptions
| Field | Type | Maps to |
|---|---|---|
entity | string | entity, exact match |
entityPrefix | string | entity_prefix, e.g. execplan: |
topK | number | top_k, daemon default 10 |
tokenBudget | number | token_budget, fills by descending score until exhausted |
Source: memory.ts:25.
recall() returns result.facts and discards total_tokens (memory.ts:99). If you set a tokenBudget and want to know how much of it was spent, call memory.client.queryFacts(...) directly instead.
forget(factId) delegates to deleteFact and resolves to void; the daemon's {"deleted": true} body is discarded (memory.ts:103).
Complete export list
Values (index.ts:1): CruxMemory, createDaemonClient, CruxDaemonError, discoverDaemon, DEFAULT_LOCAL_URL, ENV_AGENT_TOKEN, ENV_DAEMON_URL, ENV_REMOTE_URL.
Types: ConnectOptions, RecallOptions, RememberOptions, DaemonClient, DaemonClientOptions, DiscoveredDaemon, DiscoveryOptions, DiscoverySource, Fact, StoreFact, FactQueryResult, FactExportResult, FactQueryParams, ExportFactsParams, SessionState, HorizonClass, TextSearchBody, TextSearchExpandBody, GraphExpandBody, TimeRangeBody, ExpandResultId, and the raw generated components, operations and paths.
ensureFetch is not re-exported here, unlike in @cuecrux/engine-client. It throws Fetch implementation is required. Provide one via the fetchImpl option or run on Node >= 18. (fetch-guard.ts:3).
5.4 The typed daemon client behind the facade
interface DaemonClientOptions {
baseUrl: string; // required
token?: string; // CRUX_AGENT_TOKEN; held in a closure, never logged
fetchImpl?: typeof fetch; // tests, polyfills
}
function createDaemonClient(options: DaemonClientOptions): DaemonClient;
Source: client.ts:27. This is the widest daemon coverage of any client: nineteen methods, including the three receipt routes neither Crux SDK touches.
| Method | HTTP | Returns |
|---|---|---|
healthz() | GET /healthz | unknown |
readyz() | GET /readyz | unknown |
version() | GET /v1/version | unknown |
storeFact(body) | PUT /v1/facts | Fact |
storeFactsBulk(facts) | PUT /v1/facts/bulk | unknown |
getFact(factId) | GET /v1/facts/{id} | Fact |
deleteFact(factId) | DELETE /v1/facts/{id} | void |
getFactsByEntity(entity) | GET /v1/facts/entity/{entity} | Fact[]: wrong, see 6.4 |
queryFacts(params?) | GET /v1/facts | FactQueryResult |
exportFacts(params?) | GET /v1/facts/export | FactExportResult |
getSessionState(sessionId) | GET /v1/sessions/{id}/state | SessionState |
putSessionState(sessionId, state) | PUT /v1/sessions/{id}/state | SessionState |
textSearch(body) | POST /v1/query/text-search | unknown |
textSearchExpand(body) | POST /v1/query/text-search/expand | unknown |
graphExpand(body) | POST /v1/query/graph-expand | unknown |
timeRange(body) | POST /v1/query/time-range | unknown |
getReceipt(receiptId, tenantId) | GET /v1/receipts/{id}?tenant_id= | unknown |
getReceiptSignature(receiptId, tenantId) | GET /v1/receipts/{id}/signature?tenant_id= | unknown |
getReceiptVerification(receiptId, tenantId) | GET /v1/receipts/{id}/verification?tenant_id= | unknown |
Source: client.ts:41.
The unknown return types are deliberate and, in a documentation set that values honesty, worth praising: where the recorded OpenAPI leaves a 200 body untyped, the author refused to hand-guess a shape and typed it unknown, with an inline comment saying so (client.ts:147). @cuecrux/client types those same responses because it was written against the daemon rather than its spec. Both choices are legitimate; the second is more useful, the first is more honest about what the spec guarantees.
A note on the receipt routes, since this is the only client that reaches them. A retrieval receipt is a verifiable record of what was stored and retrieved, content-addressed tamper-evidence. It is not an attestation that an agent did anything. Execution receipts are Ed25519-signed; retrieval receipts are BLAKE3 content-addressed and are not signed. Do not collapse the two.
Data shapes, generated from the recorded spec (types.gen.ts:306):
// Fact, required
{ confidence: number; deleted: boolean; entity: string; fact_id: string;
key: string; stored_at: string; tokens: number; value: string }
// Fact, optional
{ actor?; horizon_class?; private?; reverified_at?; source_receipt?;
superseded_by?; supersedes?; version? } // version is int32, starts at 1
// StoreFact, required: entity, key, value
// optional: actor, confidence, horizon_class, private, source_receipt
FactQueryResult = { facts: Fact[]; total_tokens: number }
FactExportResult = { facts: Fact[]; has_more: boolean; next_cursor?: string | null }
SessionState = { session_id; state: unknown; total_tokens; updated_at; expires_at? }
TextSearchBody = { tenant_id; query; limit?; min_score?; mode?; token_budget?; include_receipt? }
GraphExpandBody = { tenant_id; seed_artifact_ids: number[]; edge_types?; max_hops?;
budget?; min_confidence?; include_state? }
TimeRangeBody = { tenant_id; start_micros; end_micros; artifact_ids?;
include_relations?; limit? }
ExpandResultId = { doc_id: number; segment_index: number }
Note TextSearchBody.include_receipt, a field neither Crux SDK exposes.
Error model
class CruxDaemonError extends Error {
readonly status: number; // HTTP status
readonly path: string; // request pathname
readonly body?: string; // raw response text, undefined if unreadable
// name = 'CruxDaemonError'
// message = `Crux daemon request failed: ${path} -> ${status}`
}
Source: errors.ts:2. Thrown for every non-2xx (client.ts:102).
There is no error taxonomy here: no 404-to-null convenience, no Problem Details parsing, no retry. Both Crux SDKs are better on this axis. The bearer token is never placed in the message or the body, which is correct.
5.5 Daemon discovery
The one capability no other client has.
function discoverDaemon(options?: DiscoveryOptions): Promise<DiscoveredDaemon>;
interface DiscoveryOptions {
baseUrl?: string; // explicit — skips probing
token?: string; // else CRUX_AGENT_TOKEN
env?: Record<string, string | undefined>; // defaults to process.env
fetchImpl?: typeof fetch;
probeTimeoutMs?: number; // default 750
}
interface DiscoveredDaemon {
baseUrl: string;
token?: string;
source: 'explicit' | 'env' | 'local' | 'remote';
}
Resolution order (discovery.ts:56):
- explicit
options.baseUrl, with no probe,source: 'explicit' CRUX_DAEMON_URL,source: 'env'- probe
http://127.0.0.1:14800/readyzunderAbortSignal.timeout(probeTimeoutMs ?? 750),source: 'local' CRUX_REMOTE_URL,source: 'remote'- otherwise throw a plain
Errornaming both environment variables
Exported constants: DEFAULT_LOCAL_URL = http://127.0.0.1:14800, ENV_DAEMON_URL = CRUX_DAEMON_URL, ENV_REMOTE_URL = CRUX_REMOTE_URL, ENV_AGENT_TOKEN = CRUX_AGENT_TOKEN (discovery.ts:4).
Two things to know. The probe checks /readyz, not /healthz, so a running but not ready daemon is treated as absent and discovery falls through to the remote. And step 5 throws a plain Error, not a CruxDaemonError, so instanceof CruxDaemonError will not catch a discovery failure.
5.6 @cuecrux/engine-client, in full
The only installable SDKCrux package. createEngineClient returns an object of nine async methods. Every one throws a bare Error with the message "<operation> failed: <status>" on any non-OK response, no status property, no body.
function createEngineClient(
baseUrl: string,
fetchImpl?: typeof fetch,
): { healthz; search; answers; qualityEstimate; qualityBoost; qualityJob; receipt; provenance; trustReport };
function ensureFetch(scope?: { fetch?: typeof fetch }): typeof fetch;
Source: index.ts:96. Also exported as types: paths, components, operations, AnswerReceipt, ProvenanceRecord, TrustReportFormat, QualityBoostType, QualityEstimateRequest, QualityEstimateResponse, QualityBoostRequest, QualityBoostResponse, QualityJobResponse (index.ts:209).
| Method | HTTP | Works today |
|---|---|---|
healthz() | GET /healthz | Yes |
search(params) | GET /v1/search | No. The route is not served, 6.2 |
answers(body) | POST /v1/answers | Yes |
qualityEstimate(body) | POST /v1/quality/estimate | Yes |
qualityBoost(body) | POST /v1/quality/boost | Yes |
qualityJob(jobId) | GET /v1/quality/jobs/{id} | Yes |
receipt(answerId) | GET /v1/answers/{id}/receipt | Yes |
provenance(artifactId) | GET /v1/provenance/{id} | Yes |
trustReport(answerId, options?) | GET /v1/answers/{id}/trust-report | No. The route is not served, 6.2 |
Request and response shapes:
// answers(body), request
{ q: string; // required
topK?: number; rerankK?: number; mode?: string; freshness?: unknown;
audience?: unknown; corpusIds?: string[]; filters?: unknown }
// answers(body), response
{ outcome; collapse_report; answerId; answer; citations; usedK;
retrievedIds; timings; crown; counterfactual; upgrade_hint }
// qualityEstimate(body)
{ boost_type: QualityBoostType; corpus_ids?: string[]; credit_budget?: number }
-> { estimated_artifacts; estimated_cost_crux; estimated_duration_minutes;
techniques_applied: string[]; exceeds_budget; capped_artifacts; upgrade_url? }
// qualityBoost(body), credit_budget is REQUIRED here, unlike estimate
{ boost_type: QualityBoostType; corpus_ids?: string[]; credit_budget: number }
-> { job_id; status: 'queued'; estimated_artifacts; estimated_cost;
exceeds_budget; capped_artifacts }
// qualityJob(jobId)
-> { job_id; status; boost_type; estimated_cost; credit_budget;
counts: Record<string, number>; created_at; updated_at }
// receipt(answerId)
-> { receiptId; snapshotId; answerId; mode; queryHash; fusion; retrieval;
selection; timings; receiptHash; knowledgeStateCursor?; signature?; evidence }
// provenance(artifactId)
-> { artifactId; artifactHash; signer; sigScheme; recordedAt }
type QualityBoostType = 'context_notation' | 'hierarchical' | 'propositions' | 'full_tier3';
type TrustReportFormat = 'html' | 'pdf' | 'json';
Source: index.ts:45. trustReport returns a string for html, a Uint8Array for pdf and unknown for json, and defaults to html, but the route does not exist, so all three paths throw.
Three further facts you need before using it:
It declares a peer dependency it never imports. peerDependencies: { "cross-fetch": "^4.0.0" } (package.json:21) appears nowhere in src/. Installing it is unnecessary; npm will warn if you do not.
It cannot authenticate. See 3.5 for the fetch-wrapping workaround.
Its typings do not resolve. npm install succeeds, import works at runtime, and TypeScript cannot see the module at all. The workaround is 6.1.
5.7 @cuecrux/factory and FactoryClient
@cuecrux/factory is one line (index.ts:2):
export * from '../../internal/factory/src/index';
That is a relative source-tree import across a package boundary, not a workspace dependency. It only works because tsup bundles with dts: { resolve: true }. The package is not published.
The surface it re-exports (index.ts:1): everything in dto.ts, plus generateFactoryOpenApi, FactoryClient, FactoryClientError, FactoryCallOptions and FactoryClientOptions.
interface FactoryClientOptions {
baseUrl: string;
apiKey?: string; // -> Authorization: Bearer <apiKey>
timeoutMs?: number; // default 10_000, floor 1_000
maxRetries?: number; // default 2, floor 0
retryBaseDelayMs?: number; // default 150, floor 50
retryMaxDelayMs?: number; // default 2_000
defaultHeaders?: Record<string, string>;
fetchImpl?: typeof fetch;
}
interface FactoryCallOptions {
requestId?: string; // else a fresh randomUUID()
signal?: AbortSignal;
}
class FactoryClient {
constructor(opts: FactoryClientOptions);
createJob(args: CreateJobRequest, opts?: FactoryCallOptions): Promise<CreateJobResponse>;
getJob(id: string, query?: GetJobQuery, opts?: FactoryCallOptions): Promise<GetJobResponse>;
listJobs(query?: ListJobsQuery, opts?: FactoryCallOptions): Promise<ListJobsResponse>;
cancelJob(id: string, body?: CancelJobRequest, opts?: FactoryCallOptions): Promise<CancelJobResponse>;
}
class FactoryClientError extends Error {
readonly statusCode: number;
readonly requestId: string;
readonly responseBody?: string;
}
Source: client.ts:22 and client.ts:82.
| Method | HTTP | Retried |
|---|---|---|
createJob | POST /api/ingest.create | No: deliberately, to avoid duplicate jobs |
getJob | GET /api/jobs/{id} | Yes |
listJobs | GET /api/jobs | Yes |
cancelJob | POST /api/jobs/{id}/cancel | Yes |
This is the only client in either estate with a retry policy, request-id correlation and schema-validated responses. Its behaviour is documented in 4.8.
DTOs are Zod schemas with OpenAPI annotations. FactoryMode is light, verified or audit. JobStatus is a thirteen-member enum: queued, fetching, scanning, parsing, chunking, embedding, waiting_for_engine, blocked_engine_down, committing, completed, completed_with_errors, failed, dead_lettered (dto.ts:23). Also exported: ArtifactRef, JobTimeline, JobSource, JobCommit, JobIngest, JobFetchSummary, JobEvent, Job, JobResult, JobsSummary, and the four request/response envelope pairs.
Version drift. The changelog documents a 0.1.1 release, but the manifest still reads 0.1.0, as does the public wrapper. The changelog and the manifest disagree.
5.8 cuecrux-receipt and the receipt verifiers
A CLI with one command (cli.ts:52):
cuecrux-receipt verify <answerId> [-b|--base <url>] [-f|--file <path>]
| Exit code | Meaning |
|---|---|
0 | The receipt hash matches and the signature verifies |
1 | Verification failed |
2 | An exception was thrown, network, parse or schema |
Default base URL http://localhost:3333 (cli.ts:34). Programmatic exports: runVerifyCommand(answerId, options, deps?), createProgram(run?), and the types VerifyOptions and VerifyDeps (cli.ts:7). The deps parameter is a clean injection point for testing, fetchReceipt, verifyReceipt, readFile, log, error.
The verification itself lives in @cuecrux-internal/engine-sdk (verify.ts:19):
type Receipt = { snapshot: {...}; receipt_hash: string;
signature_ed25519: string; signer_pubkey_ed25519: string };
type ReceiptVerification = { hashMatches: boolean; signatureValid: boolean };
verifyReceipt(receipt: Receipt): Promise<ReceiptVerification>; // throws ZodError on malformed input
fetchReceipt(baseUrl: string, answerId: string, fetchImpl?): Promise<Receipt>;
What a passing verification proves, and what it does not. hashMatches says the recorded snapshot canonicalises to the recorded receipt_hash; signatureValid says the recorded Ed25519 signature is valid over that hash under the supplied public key. Together that is tamper-evidence over a record; it does not attest that any agent behaved in any particular way, and it does not establish that the key belongs to who you think it does unless you pinned the key yourself.
Contract mismatch. The receipt shape this verifier accepts (snapshot, receipt_hash, signature_ed25519) is not the shape @cuecrux/engine-client.receipt() returns from the same route (receiptId, snapshotId, receiptHash, signature). Two packages in one repository model one endpoint incompatibly. Piping one into the other throws a ZodError.
5.9 Release and publish machinery
Nineteen workflows. The parts that determine what reaches a registry:
| Lane | Workflow | Trigger | What it publishes |
|---|---|---|---|
| Private | release-private.yml | Push to main, or dispatch | @cuecrux-internal/* to GitHub Packages, only if the repository variable SDK_GH_PACKAGES_ENABLED is 'true'. Nothing documents that variable |
| Public, general | release-public.yml | Dispatch only | @cuecrux/engine-client, after verify-publish-targets --mode public |
| Public, engine-client | release-public-engine-client.yml | Dispatch only | @cuecrux/engine-client. Skips verify-publish-targets entirely |
| Public, factory | release-public-factory.yml | Dispatch only | @cuecrux/factory, with the guard |
Secrets come from Vault over GitHub OIDC; public publishing uses npm Trusted Publishing with --provenance and no NPM_TOKEN. That much matches the Crux estate.
What does not match: the public lane publishes from a live working tree with no reproducibility check. There is no second build, no sha256 diff, and no packed-artifact handoff between the build and publish jobs. Compare 2.10, where the Python SDK builds twice and fails the run if the sums differ.
The publish guard, scripts/verify-publish-targets.ts with scripts/verify-lib.ts, enforces:
| Mode | Rule |
|---|---|
--mode private | Rejects any release name not starting with @cuecrux-internal/ unless explicitly allowlisted, and requires every internal package's publishConfig.registry to contain npm.pkg.github.com |
--mode public | Requires --package <name>, rejects any other package in the release set, and requires registry.npmjs.org plus access: 'public' |
Its blind spot: workspace packages are loaded from packages/ and packages/internal/ only (verify-lib.ts:22), apps/ is never scanned. See 6.6.
@cuecrux/memory satisfies every public-mode check and there is no workflow that would ever invoke them. No workflow builds, tests or publishes it by name; it is reached only incidentally by pnpm -r build and pnpm -r test. It is also absent from .github/CODEOWNERS.
Useful commands, if you are working in the repository:
pnpm install --frozen-lockfile
pnpm build # pnpm -r --parallel build
pnpm -r test # the only invocation that reaches @cuecrux/memory's tests
pnpm test:api-parity # byte-compares the recorded Engine spec against ../Engine/openapi.json
pnpm generate:openapi # re-records the Engine spec from a sibling checkout
pnpm typecheck typechecks exactly one package. pnpm lint is prettier --check ., the ESLint config in the repository is never executed by any script or workflow. pnpm test:api-parity fails today; see 6.8.
5.10 If you already depend on SDKCrux
| You depend on | Recommendation |
|---|---|
@cuecrux/engine-client for answers, receipt, provenance or the quality routes | Keep it. Add the auth wrapper from 3.5 and the typings shim from 6.1 |
@cuecrux/engine-client.search() or .trustReport() | These 404. Move to answers() or call the Engine route you actually want |
@cuecrux/memory for daemon access | You are running vendored code. Fix getFactsByEntity per 6.4, or migrate to @cuecrux/client, which covers the same surface minus receipts |
@cuecrux/memory specifically for horizonClass, actor or the receipt routes | No published alternative exists. Keep the vendored copy, or call those routes directly |
Any @cuecrux-internal/* package | You hold a GitHub Packages token. Nothing changes; note that six of them declare a types path their build does not emit |
cuecrux-receipt | You built it from source. Note the shape mismatch in 5.8 before piping engine-client output into it |
For anything new against the Crux Daemon, use @cuecrux/client or corecrux-client.
Sources
- packages/memory/src/memory.ts:52,
CruxMemory - packages/memory/src/client.ts:41,
DaemonClient, all nineteen methods - packages/memory/src/discovery.ts:56, the discovery order
- packages/memory/src/errors.ts:2,
CruxDaemonError - packages/engine-client/src/index.ts:96,
createEngineClient - packages/internal/factory/src/client.ts:22,
FactoryClientOptions - packages/internal/factory/src/dto.ts:23, the thirteen-member
JobStatus - packages/cli/src/cli.ts:52, the
verifycommand - packages/core/src/index.ts:1, the shared runtime's 48 export lines
- scripts/verify-lib.ts:22, the publish guard's scan roots
- .npmrc:2, internal-scope registry routing
- Registry status for all five public-scoped packages queried against npm on 2026-07-27

