/** * `cleo project link`: register the current project with Cleo Nexus and bind * the remote project to the local one. * * ## What is sent * * `POST /v1/projects` with `{ projectId, label }`, plus `initialKey` for a * new project: * * - `projectId` is the tracked, immutable `.cleo/project-id` * (`readDeclaredProjectIdentity`), the key the server stores projects under, * so the remote id IS the local id. * - `label` is the project's display name in plaintext * ({@link getProjectDisplayName}), or `--label`. It must not look like a path. * - `initialKey` (onboarding B, T13101) is the project's first data key, * wrapped by the account master key at version 1, which the server stores * in the same transaction as the project. It is sent only when E3 * (`GET /v1/status?projectId=`) lists {@link NEXUS_FEATURE_PROJECT_INITIAL_KEY}, * names a full-profile device credential, and does not see the project yet. * A re-link never sends a key. A project registered without one (by an * older CLI, a server without the feature, or a device that could not * unlock the account key) gets it from its first `cleo cloud push` * (cleo-nexus #33). * * No filesystem path, git remote or directory layout is ever sent. * * ## Idempotency * * Registering the same id again answers 200 (201 on first registration) and * sets the server label to the one sent, so re-linking after a rename updates * the label. Linking twice is safe. * * ## Where the binding lives * * `/.cleo/nexus-link.json`, keyed by API origin. It is machine-local: * `.cleo/.gitignore` denies everything not allow-listed, so the binding (which * names an account's organization) is never committed. It is not * `project-context.json`, which is tracked in git and regenerated by * `cleo upgrade`, and not the project `cleo.db`, so linking never opens or * migrates the task store. Each clone links itself, the same way each machine * signs itself in. * * @task T12712 * @epic T12322 */ import type { NexusProjectLink, NexusProjectLinkResult } from '@cleocode/contracts'; import { type ProjectReplicaBinder } from './nexus-attach.js'; import { type NexusFlowOptions } from './nexus-auth.js'; import { type NexusDeviceStore } from './nexus-device.js'; import type { NexusVaultState } from './nexus-vault-state.js'; /** File name of the binding, inside the project's `.cleo/` directory. */ export declare const NEXUS_LINK_FILE = "nexus-link.json"; /** Longest label the server accepts (`RegisterProjectRequest.label`). */ export declare const NEXUS_LABEL_MAX = 120; /** Timeout of the E3 probe that decides whether a registration carries the project key. */ export declare const NEXUS_LINK_PROBE_TIMEOUT_MS = 15000; /** Options for {@link linkProjectToNexus}. */ export interface NexusLinkOptions extends NexusFlowOptions { /** Project root; defaults to the resolved current project. */ projectRoot?: string; /** Label to register (`--label`); defaults to {@link getProjectDisplayName}. */ label?: string; /** Device store (with device credentials); defaults to `/nexus-device.json`. */ deviceStore?: NexusDeviceStore; /** Replica binding (tests); defaults to the canonical project store. */ replicaBinder?: ProjectReplicaBinder; /** CLEO version reported in presence; defaults to the installed one. */ cliVersion?: string; /** `--rebind`: give this store a new replica id before attaching (see {@link attachProjectReplica}). */ rebind?: boolean; /** Vault state (trust pins read while unlocking the account key); defaults to `/nexus-vault.json`. */ vaultState?: NexusVaultState; /** Timeout of the E3 probe; default {@link NEXUS_LINK_PROBE_TIMEOUT_MS}. */ probeTimeoutMs?: number; } /** * Validate a project label: 1-120 characters; no path separator or look-alike * slash, no drive letter (`C:`), no leading `~`, no control characters; and * not the OS username, which would leak the account name. A label is a name, * never a path. * * @param raw - Candidate label. * @returns The trimmed label. * @throws {NexusAccountError} `E_NEXUS_INVALID_LABEL`. */ export declare function validateNexusProjectLabel(raw: string): string; /** * Path of the binding file for a project. * * @param projectRoot - Project root. * @returns `/.cleo/nexus-link.json`. */ export declare function nexusLinkPath(projectRoot: string): string; /** * The stored binding for a project and origin, if any. * * @param projectRoot - Project root. * @param apiUrl - API URL (any URL on the origin). * @returns The binding, or `null`. */ export declare function readNexusProjectLink(projectRoot: string, apiUrl: string): NexusProjectLink | null; /** * Record the replica this device attached for a linked project (a rebind's * successor, T13278), in the binding file under its lock: only the replica * fields of that origin's entry change. * * @param projectRoot - Project root. * @param apiUrl - API URL (any URL on the origin). * @param replica - The attached replica, this Nexus device, and when. * @throws {NexusAccountError} `E_NEXUS_VAULT_NOT_LINKED` when the origin has no readable entry. */ export declare function recordNexusProjectReplica(projectRoot: string, apiUrl: string, replica: { readonly replicaId: string; readonly nexusDeviceId: string; readonly attachedAt: string; }): Promise; /** * Every stored binding of a project, one per origin (T13231). An entry this * version cannot read is listed in `unreadable` by origin, never dropped * silently. * * @param projectRoot - Project root. * @returns The readable bindings and the origins of unreadable ones. */ export declare function readNexusProjectLinks(projectRoot: string): { links: NexusProjectLink[]; unreadable: string[]; }; /** * Register the project with Nexus (label only, never a path; a new project * with its first data key when the server takes it) and persist the binding * in `.cleo/nexus-link.json`. Idempotent. * * @param opts - Project root, label, API URL, store and test overrides. * @returns The binding, whether it already existed, and the key version the * registration stored. * @throws {NexusAccountError} Not signed in, expired session, invalid label, * not a CLEO project, or an API refusal (e.g. the id belongs to another account). */ export declare function linkProjectToNexus(opts?: NexusLinkOptions): Promise; //# sourceMappingURL=nexus-link.d.ts.map