@relayfile/sdk 0.7.23 → 0.7.25

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 CHANGED
@@ -15,7 +15,7 @@ The recommended way to use `@relayfile/sdk` in an agent workflow is through
15
15
  environment variable generation for `relayfile-mount`, and agent invites.
16
16
 
17
17
  ```ts
18
- import { RelayfileSetup } from '@relayfile/sdk'
18
+ import { RelayfileSetup } from '@relayfile/sdk/cli'
19
19
 
20
20
  const setup = await RelayfileSetup.login({
21
21
  onLoginUrl: (url) => console.log(`Sign in to Relayfile Cloud: ${url}`),
@@ -72,6 +72,12 @@ advanced hosts that already provide a valid Cloud bearer token. After Cloud auth
72
72
  complete the Notion OAuth flow as described in
73
73
  [`docs/agent-workspace-golden-path.md`](../../../docs/agent-workspace-golden-path.md).
74
74
 
75
+ The default `@relayfile/sdk` entry point is safe for Worker-style bundles and
76
+ does not statically import the CLI-only mount launcher or local browser login
77
+ helpers. Import interactive login and local mount launcher utilities from
78
+ `@relayfile/sdk/cli`, `@relayfile/sdk/cloud-login`, or
79
+ `@relayfile/sdk/mount-launcher`.
80
+
75
81
  ---
76
82
 
77
83
  ## Low-Level Client Example
@@ -0,0 +1,6 @@
1
+ export * from "../index.js";
2
+ export { RelayfileSetup } from "./setup.js";
3
+ export { createDefaultMountLauncher, defaultMountLauncher, readMountedWorkspaceStatus } from "../mount-launcher.js";
4
+ export { runRelayfileCloudLogin, type RelayfileCloudLoginOptions, type RelayfileCloudTokenSet, type RelayfileCloudTokenSetupOptions } from "../cloud-login.js";
5
+ export { createRelayfileCloudAccessTokenProvider } from "../cloud-token-provider.js";
6
+ export type { MountLauncher, MountLauncherEvent, MountLauncherInstance, MountLauncherStart, MountedWorkspaceStatus, ReadMountedWorkspaceStatusInput } from "../setup-types.js";
@@ -0,0 +1,5 @@
1
+ export * from "../index.js";
2
+ export { RelayfileSetup } from "./setup.js";
3
+ export { createDefaultMountLauncher, defaultMountLauncher, readMountedWorkspaceStatus } from "../mount-launcher.js";
4
+ export { runRelayfileCloudLogin } from "../cloud-login.js";
5
+ export { createRelayfileCloudAccessTokenProvider } from "../cloud-token-provider.js";
@@ -0,0 +1,9 @@
1
+ import { type RelayfileCloudLoginOptions, type RelayfileCloudTokenSet, type RelayfileCloudTokenSetupOptions } from "../cloud-login.js";
2
+ import { RelayfileSetup as BaseRelayfileSetup } from "../setup.js";
3
+ import type { MountLauncher, MountedWorkspaceStatus, ReadMountedWorkspaceStatusInput } from "../setup-types.js";
4
+ export declare class RelayfileSetup extends BaseRelayfileSetup {
5
+ static login(options?: RelayfileCloudLoginOptions): Promise<RelayfileSetup>;
6
+ static fromCloudTokens(tokens: RelayfileCloudTokenSet, options?: RelayfileCloudTokenSetupOptions): RelayfileSetup;
7
+ protected getDefaultMountLauncher(): MountLauncher;
8
+ protected readMountedWorkspaceStatus(input: ReadMountedWorkspaceStatusInput): Promise<MountedWorkspaceStatus>;
9
+ }
@@ -0,0 +1,39 @@
1
+ import { createRelayfileCloudAccessTokenProvider } from "../cloud-token-provider.js";
2
+ import { runRelayfileCloudLogin } from "../cloud-login.js";
3
+ import { defaultMountLauncher, readMountedWorkspaceStatus } from "../mount-launcher.js";
4
+ import { RelayfileSetup as BaseRelayfileSetup } from "../setup.js";
5
+ const DEFAULT_CLOUD_API_URL = "https://agentrelay.com/cloud";
6
+ export class RelayfileSetup extends BaseRelayfileSetup {
7
+ static async login(options = {}) {
8
+ const cloudApiUrl = options.cloudApiUrl ?? DEFAULT_CLOUD_API_URL;
9
+ const tokens = await runRelayfileCloudLogin({
10
+ ...options,
11
+ cloudApiUrl
12
+ });
13
+ await options.onTokens?.({ ...tokens });
14
+ return RelayfileSetup.fromCloudTokens(tokens, {
15
+ ...options,
16
+ cloudApiUrl: tokens.apiUrl ?? cloudApiUrl
17
+ });
18
+ }
19
+ static fromCloudTokens(tokens, options = {}) {
20
+ const cloudApiUrl = options.cloudApiUrl ?? tokens.apiUrl ?? DEFAULT_CLOUD_API_URL;
21
+ return new RelayfileSetup({
22
+ ...options,
23
+ cloudApiUrl,
24
+ accessToken: createRelayfileCloudAccessTokenProvider({
25
+ ...tokens,
26
+ apiUrl: tokens.apiUrl ?? cloudApiUrl
27
+ }, {
28
+ ...options,
29
+ cloudApiUrl
30
+ })
31
+ });
32
+ }
33
+ getDefaultMountLauncher() {
34
+ return defaultMountLauncher;
35
+ }
36
+ readMountedWorkspaceStatus(input) {
37
+ return readMountedWorkspaceStatus(input);
38
+ }
39
+ }
@@ -1,17 +1,6 @@
1
- import type { AccessTokenProvider } from "./client.js";
2
- import type { RelayfileSetupOptions } from "./setup-types.js";
3
- export interface RelayfileCloudTokenSet {
4
- apiUrl?: string;
5
- accessToken: string;
6
- refreshToken: string;
7
- accessTokenExpiresAt: string;
8
- refreshTokenExpiresAt?: string;
9
- }
10
- export interface RelayfileCloudTokenSetupOptions extends Omit<RelayfileSetupOptions, "accessToken" | "cloudApiUrl"> {
11
- cloudApiUrl?: string;
12
- refreshWindowMs?: number;
13
- onTokens?: (tokens: RelayfileCloudTokenSet) => void | Promise<void>;
14
- }
1
+ import type { RelayfileCloudTokenSetupOptions, RelayfileCloudTokenSet } from "./cloud-token-provider.js";
2
+ export type { RelayfileCloudTokenSetupOptions, RelayfileCloudTokenSet } from "./cloud-token-provider.js";
3
+ export { createRelayfileCloudAccessTokenProvider } from "./cloud-token-provider.js";
15
4
  export interface RelayfileCloudLoginOptions extends RelayfileCloudTokenSetupOptions {
16
5
  redirectHost?: string;
17
6
  redirectPort?: number;
@@ -24,4 +13,3 @@ export interface RelayfileCloudLoginOptions extends RelayfileCloudTokenSetupOpti
24
13
  successMessage?: string;
25
14
  }
26
15
  export declare function runRelayfileCloudLogin(options?: RelayfileCloudLoginOptions): Promise<RelayfileCloudTokenSet>;
27
- export declare function createRelayfileCloudAccessTokenProvider(initialTokens: RelayfileCloudTokenSet, options?: RelayfileCloudTokenSetupOptions): AccessTokenProvider;
@@ -1,11 +1,9 @@
1
- import { CloudAbortError, CloudApiError, CloudTimeoutError, MalformedCloudResponseError } from "./setup-errors.js";
2
- import { RELAYFILE_SDK_VERSION } from "./version.js";
1
+ import { CloudAbortError, CloudTimeoutError, MalformedCloudResponseError } from "./setup-errors.js";
3
2
  const DEFAULT_CLOUD_API_URL = "https://agentrelay.com/cloud";
4
3
  const DEFAULT_LOGIN_TIMEOUT_MS = 5 * 60 * 1000;
5
4
  const DEFAULT_REDIRECT_HOST = "127.0.0.1";
6
5
  const DEFAULT_REDIRECT_PATH = "/callback";
7
- const DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
8
- const DEFAULT_REFRESH_WINDOW_MS = 60_000;
6
+ export { createRelayfileCloudAccessTokenProvider } from "./cloud-token-provider.js";
9
7
  export async function runRelayfileCloudLogin(options = {}) {
10
8
  const cloudApiUrl = normalizeNonEmptyString(options.cloudApiUrl) ?? DEFAULT_CLOUD_API_URL;
11
9
  const redirectHost = normalizeNonEmptyString(options.redirectHost) ?? DEFAULT_REDIRECT_HOST;
@@ -108,71 +106,6 @@ export async function runRelayfileCloudLogin(options = {}) {
108
106
  });
109
107
  });
110
108
  }
111
- export function createRelayfileCloudAccessTokenProvider(initialTokens, options = {}) {
112
- const cloudApiUrl = normalizeNonEmptyString(options.cloudApiUrl) ??
113
- normalizeNonEmptyString(initialTokens.apiUrl) ??
114
- DEFAULT_CLOUD_API_URL;
115
- const requestTimeoutMs = Math.max(1, Math.floor(options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS));
116
- const refreshWindowMs = Math.max(0, Math.floor(options.refreshWindowMs ?? DEFAULT_REFRESH_WINDOW_MS));
117
- let tokens = {
118
- ...initialTokens,
119
- apiUrl: normalizeNonEmptyString(initialTokens.apiUrl) ?? cloudApiUrl
120
- };
121
- let refreshPromise;
122
- return async () => {
123
- if (shouldRefresh(tokens, refreshWindowMs)) {
124
- if (!refreshPromise) {
125
- refreshPromise = refresh();
126
- }
127
- try {
128
- await refreshPromise;
129
- }
130
- finally {
131
- refreshPromise = undefined;
132
- }
133
- }
134
- return tokens.accessToken;
135
- };
136
- async function refresh() {
137
- const response = await fetchWithTimeout(buildCloudUrl(cloudApiUrl, "api/v1/auth/token/refresh").toString(), {
138
- method: "POST",
139
- headers: {
140
- "Content-Type": "application/json",
141
- "X-Relayfile-SDK-Version": RELAYFILE_SDK_VERSION
142
- },
143
- body: JSON.stringify({ refreshToken: tokens.refreshToken })
144
- }, requestTimeoutMs);
145
- const payload = await readResponseBody(response);
146
- if (!response.ok) {
147
- throw new CloudApiError(response.status, payload);
148
- }
149
- tokens = readTokenSetFromPayload(payload, cloudApiUrl);
150
- await options.onTokens?.({ ...tokens });
151
- }
152
- }
153
- function shouldRefresh(tokens, refreshWindowMs) {
154
- const expiresAt = Date.parse(tokens.accessTokenExpiresAt);
155
- if (Number.isNaN(expiresAt)) {
156
- return true;
157
- }
158
- return expiresAt - Date.now() <= refreshWindowMs;
159
- }
160
- async function fetchWithTimeout(url, init, timeoutMs) {
161
- const controller = new AbortController();
162
- const timer = setTimeout(() => controller.abort(), timeoutMs);
163
- try {
164
- return await fetch(url, { ...init, signal: controller.signal });
165
- }
166
- catch (error) {
167
- if (controller.signal.aborted) {
168
- throw new CloudTimeoutError("refreshCloudAccessToken", timeoutMs);
169
- }
170
- throw error;
171
- }
172
- finally {
173
- clearTimeout(timer);
174
- }
175
- }
176
109
  function readTokenSetFromSearchParams(params, fallbackApiUrl) {
177
110
  return {
178
111
  apiUrl: normalizeNonEmptyString(params.get("api_url") ?? undefined) ?? fallbackApiUrl,
@@ -182,15 +115,6 @@ function readTokenSetFromSearchParams(params, fallbackApiUrl) {
182
115
  refreshTokenExpiresAt: normalizeNonEmptyString(params.get("refresh_token_expires_at") ?? undefined)
183
116
  };
184
117
  }
185
- function readTokenSetFromPayload(payload, fallbackApiUrl) {
186
- return {
187
- apiUrl: readOptionalStringField(payload, "apiUrl") ?? fallbackApiUrl,
188
- accessToken: requireStringField(payload, "accessToken"),
189
- refreshToken: requireStringField(payload, "refreshToken"),
190
- accessTokenExpiresAt: requireStringField(payload, "accessTokenExpiresAt"),
191
- refreshTokenExpiresAt: readOptionalStringField(payload, "refreshTokenExpiresAt")
192
- };
193
- }
194
118
  function requireSearchParam(params, field) {
195
119
  const value = normalizeNonEmptyString(params.get(field) ?? undefined);
196
120
  if (!value) {
@@ -198,22 +122,6 @@ function requireSearchParam(params, field) {
198
122
  }
199
123
  return value;
200
124
  }
201
- async function readResponseBody(response) {
202
- const text = await response.text();
203
- if (text === "") {
204
- return null;
205
- }
206
- const contentType = response.headers.get("content-type") ?? "";
207
- if (contentType.includes("application/json")) {
208
- try {
209
- return JSON.parse(text);
210
- }
211
- catch {
212
- return text;
213
- }
214
- }
215
- return text;
216
- }
217
125
  function requireStringField(payload, field) {
218
126
  const value = readField(payload, field);
219
127
  if (typeof value !== "string" || value.trim() === "") {
@@ -0,0 +1,15 @@
1
+ import type { AccessTokenProvider } from "./client.js";
2
+ import type { RelayfileSetupOptions } from "./setup-types.js";
3
+ export interface RelayfileCloudTokenSet {
4
+ apiUrl?: string;
5
+ accessToken: string;
6
+ refreshToken: string;
7
+ accessTokenExpiresAt: string;
8
+ refreshTokenExpiresAt?: string;
9
+ }
10
+ export interface RelayfileCloudTokenSetupOptions extends Omit<RelayfileSetupOptions, "accessToken" | "cloudApiUrl"> {
11
+ cloudApiUrl?: string;
12
+ refreshWindowMs?: number;
13
+ onTokens?: (tokens: RelayfileCloudTokenSet) => void | Promise<void>;
14
+ }
15
+ export declare function createRelayfileCloudAccessTokenProvider(initialTokens: RelayfileCloudTokenSet, options?: RelayfileCloudTokenSetupOptions): AccessTokenProvider;
@@ -0,0 +1,129 @@
1
+ import { CloudApiError, CloudTimeoutError, MalformedCloudResponseError } from "./setup-errors.js";
2
+ import { RELAYFILE_SDK_VERSION } from "./version.js";
3
+ const DEFAULT_CLOUD_API_URL = "https://agentrelay.com/cloud";
4
+ const DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
5
+ const DEFAULT_REFRESH_WINDOW_MS = 60_000;
6
+ export function createRelayfileCloudAccessTokenProvider(initialTokens, options = {}) {
7
+ const cloudApiUrl = normalizeNonEmptyString(options.cloudApiUrl) ??
8
+ normalizeNonEmptyString(initialTokens.apiUrl) ??
9
+ DEFAULT_CLOUD_API_URL;
10
+ const requestTimeoutMs = Math.max(1, Math.floor(options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS));
11
+ const refreshWindowMs = Math.max(0, Math.floor(options.refreshWindowMs ?? DEFAULT_REFRESH_WINDOW_MS));
12
+ let tokens = {
13
+ ...initialTokens,
14
+ apiUrl: normalizeNonEmptyString(initialTokens.apiUrl) ?? cloudApiUrl
15
+ };
16
+ let refreshPromise;
17
+ return async () => {
18
+ if (shouldRefresh(tokens, refreshWindowMs)) {
19
+ if (!refreshPromise) {
20
+ refreshPromise = refresh();
21
+ }
22
+ try {
23
+ await refreshPromise;
24
+ }
25
+ finally {
26
+ refreshPromise = undefined;
27
+ }
28
+ }
29
+ return tokens.accessToken;
30
+ };
31
+ async function refresh() {
32
+ const response = await fetchWithTimeout(buildCloudUrl(cloudApiUrl, "api/v1/auth/token/refresh").toString(), {
33
+ method: "POST",
34
+ headers: {
35
+ "Content-Type": "application/json",
36
+ "X-Relayfile-SDK-Version": RELAYFILE_SDK_VERSION
37
+ },
38
+ body: JSON.stringify({ refreshToken: tokens.refreshToken })
39
+ }, requestTimeoutMs);
40
+ const payload = await readResponseBody(response);
41
+ if (!response.ok) {
42
+ throw new CloudApiError(response.status, payload);
43
+ }
44
+ tokens = readTokenSetFromPayload(payload, cloudApiUrl);
45
+ await options.onTokens?.({ ...tokens });
46
+ }
47
+ }
48
+ function shouldRefresh(tokens, refreshWindowMs) {
49
+ const expiresAt = Date.parse(tokens.accessTokenExpiresAt);
50
+ if (Number.isNaN(expiresAt)) {
51
+ return true;
52
+ }
53
+ return expiresAt - Date.now() <= refreshWindowMs;
54
+ }
55
+ async function fetchWithTimeout(url, init, timeoutMs) {
56
+ const controller = new AbortController();
57
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
58
+ try {
59
+ return await fetch(url, { ...init, signal: controller.signal });
60
+ }
61
+ catch (error) {
62
+ if (controller.signal.aborted) {
63
+ throw new CloudTimeoutError("refreshCloudAccessToken", timeoutMs);
64
+ }
65
+ throw error;
66
+ }
67
+ finally {
68
+ clearTimeout(timer);
69
+ }
70
+ }
71
+ function readTokenSetFromPayload(payload, fallbackApiUrl) {
72
+ return {
73
+ apiUrl: readOptionalStringField(payload, "apiUrl") ?? fallbackApiUrl,
74
+ accessToken: requireStringField(payload, "accessToken"),
75
+ refreshToken: requireStringField(payload, "refreshToken"),
76
+ accessTokenExpiresAt: requireStringField(payload, "accessTokenExpiresAt"),
77
+ refreshTokenExpiresAt: readOptionalStringField(payload, "refreshTokenExpiresAt")
78
+ };
79
+ }
80
+ async function readResponseBody(response) {
81
+ const text = await response.text();
82
+ if (text === "") {
83
+ return null;
84
+ }
85
+ const contentType = response.headers.get("content-type") ?? "";
86
+ if (contentType.includes("application/json")) {
87
+ try {
88
+ return JSON.parse(text);
89
+ }
90
+ catch {
91
+ return text;
92
+ }
93
+ }
94
+ return text;
95
+ }
96
+ function requireStringField(payload, field) {
97
+ const value = readField(payload, field);
98
+ if (typeof value !== "string" || value.trim() === "") {
99
+ throw new MalformedCloudResponseError(field, payload);
100
+ }
101
+ return value;
102
+ }
103
+ function readOptionalStringField(payload, field) {
104
+ const value = readField(payload, field);
105
+ if (value === undefined) {
106
+ return undefined;
107
+ }
108
+ if (typeof value !== "string" || value.trim() === "") {
109
+ throw new MalformedCloudResponseError(field, payload);
110
+ }
111
+ return value;
112
+ }
113
+ function readField(payload, field) {
114
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
115
+ return undefined;
116
+ }
117
+ return payload[field];
118
+ }
119
+ function buildCloudUrl(baseUrl, path) {
120
+ const base = new URL(baseUrl);
121
+ if (!base.pathname.endsWith("/")) {
122
+ base.pathname = `${base.pathname}/`;
123
+ }
124
+ return new URL(path.replace(/^\/+/, ""), base);
125
+ }
126
+ function normalizeNonEmptyString(value) {
127
+ const normalized = value?.trim();
128
+ return normalized ? normalized : undefined;
129
+ }
package/dist/index.d.ts CHANGED
@@ -2,8 +2,7 @@ export { RelayFileClient, DEFAULT_RELAYFILE_BASE_URL, type AccessTokenProvider,
2
2
  export { RelayfileSetup, RELAYFILE_SDK_VERSION, WorkspaceHandle } from "./setup.js";
3
3
  export { type RelayfileCloudLoginOptions, type RelayfileCloudTokenSet, type RelayfileCloudTokenSetupOptions } from "./cloud-login.js";
4
4
  export { CloudAbortError, CloudApiError, CloudTimeoutError, InvalidLocalDirError, InvalidMountModeError, InvalidRemotePathError, IntegrationConnectionTimeoutError, MalformedCloudResponseError, MissingConnectionIdError, MountModeUnavailableError, MountReadyTimeoutError, MountSessionInputError, ProviderNotConnectedError, ProviderNotReadyError, RelayfileSetupError, UnknownProviderError } from "./setup-errors.js";
5
- export { type EnsureMountedWorkspaceInput, WORKSPACE_INTEGRATION_PROVIDERS, type AgentWorkspaceInvite, type AgentWorkspaceInviteOptions, type AgentWorkspaceScopedInviteOptions, type ConnectIntegrationOptions, type ConnectIntegrationResult, type CreateWorkspaceOptions, type JoinWorkspaceOptions, type MountLauncher, type MountLauncherEvent, type MountLauncherInstance, type MountLauncherStart, type MountMode, type MountSessionRequest, type MountSessionResponse, type MountSessionResult, type MountedWorkspaceHandle, type MountedWorkspaceStatus, type MountWorkspaceInput, type RelayfileSetupOptions, type RelayfileSetupRetryOptions, type WaitForConnectionOptions, type WorkspaceInfo, type WorkspaceIntegrationProvider, type WorkspaceMountEnv, type WorkspaceMountEnvOptions, type WorkspacePermissions } from "./setup-types.js";
6
- export { createDefaultMountLauncher, defaultMountLauncher, readMountedWorkspaceStatus } from "./mount-launcher.js";
5
+ export { type EnsureMountedWorkspaceInput, WORKSPACE_INTEGRATION_PROVIDERS, type AgentWorkspaceInvite, type AgentWorkspaceInviteOptions, type AgentWorkspaceScopedInviteOptions, type ConnectIntegrationOptions, type ConnectIntegrationResult, type CreateWorkspaceOptions, type JoinWorkspaceOptions, type MountLauncher, type MountLauncherEvent, type MountLauncherInstance, type MountLauncherStart, type MountMode, type MountSessionRequest, type MountSessionResponse, type MountSessionResult, type MountedWorkspaceHandle, type MountedWorkspaceStatus, type MountWorkspaceInput, type ReadMountedWorkspaceStatusInput, type RelayfileSetupOptions, type RelayfileSetupRetryOptions, type WaitForConnectionOptions, type WorkspaceInfo, type WorkspaceIntegrationProvider, type WorkspaceMountEnv, type WorkspaceMountEnvOptions, type WorkspacePermissions } from "./setup-types.js";
7
6
  export { RelayFileSync, type RelayFileSyncOptions, type RelayFileSyncPong, type RelayFileSyncReconnectOptions, type RelayFileSyncSocket, type RelayFileSyncState, type RelayFileSyncTokenProvider } from "./sync.js";
8
7
  export { onWrite, pathMatches, type OnWriteClient, type OnWriteHandler, type OnWriteHandlerError, type OnWriteOptions } from "./onWrite.js";
9
8
  export { InvalidStateError, PayloadTooLargeError, QueueFullError, RelayFileApiError, RevisionConflictError } from "./errors.js";
package/dist/index.js CHANGED
@@ -2,7 +2,6 @@ export { RelayFileClient, DEFAULT_RELAYFILE_BASE_URL } from "./client.js";
2
2
  export { RelayfileSetup, RELAYFILE_SDK_VERSION, WorkspaceHandle } from "./setup.js";
3
3
  export { CloudAbortError, CloudApiError, CloudTimeoutError, InvalidLocalDirError, InvalidMountModeError, InvalidRemotePathError, IntegrationConnectionTimeoutError, MalformedCloudResponseError, MissingConnectionIdError, MountModeUnavailableError, MountReadyTimeoutError, MountSessionInputError, ProviderNotConnectedError, ProviderNotReadyError, RelayfileSetupError, UnknownProviderError } from "./setup-errors.js";
4
4
  export { WORKSPACE_INTEGRATION_PROVIDERS } from "./setup-types.js";
5
- export { createDefaultMountLauncher, defaultMountLauncher, readMountedWorkspaceStatus } from "./mount-launcher.js";
6
5
  export { RelayFileSync } from "./sync.js";
7
6
  export { onWrite, pathMatches } from "./onWrite.js";
8
7
  export { InvalidStateError, PayloadTooLargeError, QueueFullError, RelayFileApiError, RevisionConflictError } from "./errors.js";
@@ -1,21 +1,10 @@
1
1
  import { spawn } from "node:child_process";
2
- import type { MountLauncher, MountMode, MountedWorkspaceStatus } from "./setup-types.js";
2
+ import type { MountLauncher, MountedWorkspaceStatus, ReadMountedWorkspaceStatusInput } from "./setup-types.js";
3
3
  interface DefaultMountLauncherOptions {
4
4
  spawnImpl?: typeof spawn;
5
5
  now?: () => number;
6
6
  readyPollIntervalMs?: number;
7
7
  }
8
- interface ReadMountedWorkspaceStatusInput {
9
- localDir: string;
10
- workspaceId: string;
11
- remotePath: string;
12
- mode: MountMode;
13
- relayfileBaseUrl: string;
14
- relayfileToken: string;
15
- expiresAt: string | null;
16
- suggestedRefreshAt: string | null;
17
- pid?: number;
18
- }
19
8
  export declare const defaultMountLauncher: MountLauncher;
20
9
  export declare function createDefaultMountLauncher(options?: DefaultMountLauncherOptions): MountLauncher;
21
10
  export declare function readMountedWorkspaceStatus(input: ReadMountedWorkspaceStatusInput): Promise<MountedWorkspaceStatus>;
@@ -117,6 +117,17 @@ export interface MountedWorkspaceStatus {
117
117
  pendingWriteback?: number;
118
118
  pendingConflicts?: number;
119
119
  }
120
+ export interface ReadMountedWorkspaceStatusInput {
121
+ localDir: string;
122
+ workspaceId: string;
123
+ remotePath: string;
124
+ mode: MountMode;
125
+ relayfileBaseUrl: string;
126
+ relayfileToken: string;
127
+ expiresAt: string | null;
128
+ suggestedRefreshAt: string | null;
129
+ pid?: number;
130
+ }
120
131
  export interface MountedWorkspaceHandle {
121
132
  readonly workspaceId: string;
122
133
  readonly localDir: string;
package/dist/setup.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { RelayFileClient, type AccessTokenProvider } from "./client.js";
2
- import { type RelayfileCloudLoginOptions, type RelayfileCloudTokenSet, type RelayfileCloudTokenSetupOptions } from "./cloud-login.js";
3
- import { type EnsureMountedWorkspaceInput, type AgentWorkspaceInvite, type AgentWorkspaceInviteOptions, type AgentWorkspaceScopedInviteOptions, type ConnectIntegrationOptions, type ConnectIntegrationResult, type CreateWorkspaceOptions, type JoinWorkspaceOptions, type MountedWorkspaceHandle, type MountWorkspaceInput, type RelayfileSetupOptions, type WaitForConnectionOptions, type WorkspaceMountEnv, type WorkspaceMountEnvOptions, type WorkspaceInfo, type WorkspaceIntegrationProvider, type WorkspacePermissions } from "./setup-types.js";
2
+ import { type RelayfileCloudTokenSet, type RelayfileCloudTokenSetupOptions } from "./cloud-token-provider.js";
3
+ import type { RelayfileCloudLoginOptions } from "./cloud-login.js";
4
+ import { type EnsureMountedWorkspaceInput, type AgentWorkspaceInvite, type AgentWorkspaceInviteOptions, type AgentWorkspaceScopedInviteOptions, type ConnectIntegrationOptions, type ConnectIntegrationResult, type CreateWorkspaceOptions, type JoinWorkspaceOptions, type MountLauncher, type MountedWorkspaceHandle, type MountedWorkspaceStatus, type MountWorkspaceInput, type ReadMountedWorkspaceStatusInput, type RelayfileSetupOptions, type WaitForConnectionOptions, type WorkspaceMountEnv, type WorkspaceMountEnvOptions, type WorkspaceInfo, type WorkspaceIntegrationProvider, type WorkspacePermissions } from "./setup-types.js";
4
5
  export { RELAYFILE_SDK_VERSION } from "./version.js";
5
6
  interface JoinWorkspaceResponse {
6
7
  workspaceId?: string;
@@ -53,6 +54,8 @@ export declare class RelayfileSetup {
53
54
  }): Promise<ValidatedJoinWorkspaceResponse>;
54
55
  requestJson(options: CloudRequestOptions): Promise<unknown>;
55
56
  getCloudApiUrl(): string;
57
+ protected getDefaultMountLauncher(): MountLauncher;
58
+ protected readMountedWorkspaceStatus(input: ReadMountedWorkspaceStatusInput): Promise<MountedWorkspaceStatus>;
56
59
  private resolveWorkspaceForMount;
57
60
  private createMountSession;
58
61
  }
package/dist/setup.js CHANGED
@@ -1,10 +1,8 @@
1
- import path from "node:path";
2
1
  import { RelayFileClient } from "./client.js";
3
- import { createRelayfileCloudAccessTokenProvider, runRelayfileCloudLogin } from "./cloud-login.js";
4
- import { CloudAbortError, CloudApiError, CloudTimeoutError, InvalidLocalDirError, InvalidMountModeError, InvalidRemotePathError, IntegrationConnectionTimeoutError, MalformedCloudResponseError, MissingConnectionIdError, MountSessionInputError, ProviderNotConnectedError, ProviderNotReadyError, UnknownProviderError } from "./setup-errors.js";
2
+ import { createRelayfileCloudAccessTokenProvider } from "./cloud-token-provider.js";
3
+ import { CloudAbortError, CloudApiError, CloudTimeoutError, InvalidLocalDirError, InvalidMountModeError, InvalidRemotePathError, IntegrationConnectionTimeoutError, MalformedCloudResponseError, MissingConnectionIdError, MountSessionInputError, ProviderNotConnectedError, ProviderNotReadyError, RelayfileSetupError, UnknownProviderError } from "./setup-errors.js";
5
4
  import { WORKSPACE_INTEGRATION_PROVIDERS } from "./setup-types.js";
6
5
  import { RELAYFILE_SDK_VERSION } from "./version.js";
7
- import { defaultMountLauncher, readMountedWorkspaceStatus } from "./mount-launcher.js";
8
6
  export { RELAYFILE_SDK_VERSION } from "./version.js";
9
7
  const DEFAULT_CLOUD_API_URL = "https://agentrelay.com/cloud";
10
8
  const DEFAULT_RELAYCAST_BASE_URL = "https://api.relaycast.dev";
@@ -19,22 +17,19 @@ const DEFAULT_WAIT_TIMEOUT_MS = 300_000;
19
17
  const DEFAULT_MOUNT_READY_TIMEOUT_MS = 60_000;
20
18
  const DEFAULT_MOUNT_AGENT_NAME = "relayfile-mount";
21
19
  const TOKEN_REFRESH_AGE_MS = 55 * 60 * 1000;
20
+ const nodeOnlyMountLauncher = {
21
+ async start() {
22
+ throw new RelayfileSetupError("The default relayfile-mount launcher is only available from @relayfile/sdk/cli. Import RelayfileSetup from @relayfile/sdk/cli or pass a custom launcher to mountWorkspace().", "node_only_sdk_feature");
23
+ }
24
+ };
22
25
  export class RelayfileSetup {
23
26
  cloudApiUrl;
24
27
  accessToken;
25
28
  requestTimeoutMs;
26
29
  retryOptions;
27
30
  static async login(options = {}) {
28
- const cloudApiUrl = options.cloudApiUrl ?? DEFAULT_CLOUD_API_URL;
29
- const tokens = await runRelayfileCloudLogin({
30
- ...options,
31
- cloudApiUrl
32
- });
33
- await options.onTokens?.({ ...tokens });
34
- return RelayfileSetup.fromCloudTokens(tokens, {
35
- ...options,
36
- cloudApiUrl: tokens.apiUrl ?? cloudApiUrl
37
- });
31
+ void options;
32
+ throw new RelayfileSetupError("RelayfileSetup.login() starts a local HTTP callback server and is only available from @relayfile/sdk/cli. Import RelayfileSetup from @relayfile/sdk/cli for interactive Cloud login.", "node_only_sdk_feature");
38
33
  }
39
34
  static fromCloudTokens(tokens, options = {}) {
40
35
  const cloudApiUrl = options.cloudApiUrl ?? tokens.apiUrl ?? DEFAULT_CLOUD_API_URL;
@@ -107,7 +102,7 @@ export class RelayfileSetup {
107
102
  const normalized = normalizeMountWorkspaceInput(input);
108
103
  const workspace = await this.resolveWorkspaceForMount(normalized);
109
104
  const mountSession = await this.createMountSession(workspace, normalized);
110
- const launcher = normalized.launcher;
105
+ const launcher = normalized.launcher ?? this.getDefaultMountLauncher();
111
106
  const launcherInstance = await launcher.start({
112
107
  env: buildMountLauncherEnv(mountSession),
113
108
  signal: normalized.signal,
@@ -129,7 +124,8 @@ export class RelayfileSetup {
129
124
  return new MountedWorkspaceHandleImpl({
130
125
  mountSession,
131
126
  launcherInstance: normalized.background ? launcherInstance : undefined,
132
- probeOnly: !normalized.background
127
+ probeOnly: !normalized.background,
128
+ readStatus: (statusInput) => this.readMountedWorkspaceStatus(statusInput)
133
129
  });
134
130
  }
135
131
  async ensureMountedWorkspace(input) {
@@ -218,6 +214,18 @@ export class RelayfileSetup {
218
214
  getCloudApiUrl() {
219
215
  return this.cloudApiUrl;
220
216
  }
217
+ getDefaultMountLauncher() {
218
+ return nodeOnlyMountLauncher;
219
+ }
220
+ async readMountedWorkspaceStatus(input) {
221
+ return {
222
+ ready: true,
223
+ mode: input.mode,
224
+ pid: input.pid,
225
+ expiresAt: input.expiresAt,
226
+ suggestedRefreshAt: input.suggestedRefreshAt
227
+ };
228
+ }
221
229
  async resolveWorkspaceForMount(input) {
222
230
  if (input.workspace) {
223
231
  return input.workspace;
@@ -686,6 +694,7 @@ class MountedWorkspaceHandleImpl {
686
694
  mountSession;
687
695
  launcherInstance;
688
696
  probeOnly;
697
+ readStatus;
689
698
  readySnapshot = true;
690
699
  stopPromise;
691
700
  constructor(input) {
@@ -698,6 +707,7 @@ class MountedWorkspaceHandleImpl {
698
707
  this.suggestedRefreshAt = input.mountSession.suggestedRefreshAt;
699
708
  this.launcherInstance = input.launcherInstance;
700
709
  this.probeOnly = input.probeOnly;
710
+ this.readStatus = input.readStatus;
701
711
  }
702
712
  get ready() {
703
713
  return this.readySnapshot;
@@ -706,7 +716,17 @@ class MountedWorkspaceHandleImpl {
706
716
  return buildMountedWorkspaceEnv(this.mountSession);
707
717
  }
708
718
  async status() {
709
- const status = await readMountedWorkspaceStatus({
719
+ if (this.launcherInstance) {
720
+ const status = await this.launcherInstance.status();
721
+ const mergedStatus = {
722
+ ...status,
723
+ expiresAt: this.expiresAt,
724
+ suggestedRefreshAt: this.suggestedRefreshAt
725
+ };
726
+ this.readySnapshot = mergedStatus.ready;
727
+ return mergedStatus;
728
+ }
729
+ const status = await this.readStatus({
710
730
  localDir: this.localDir,
711
731
  workspaceId: this.workspaceId,
712
732
  remotePath: this.remotePath,
@@ -907,14 +927,14 @@ function normalizeMountWorkspaceInput(input) {
907
927
  return {
908
928
  workspace: input.workspace,
909
929
  workspaceId: normalizeNonEmptyString(input.workspaceId),
910
- localDir: path.resolve(localDir),
930
+ localDir: resolveLocalDir(localDir),
911
931
  remotePath: normalizeMountRemotePath(input.remotePath),
912
932
  mode: normalizeMountModeInput(input.mode),
913
933
  background: input.background !== false,
914
934
  agentName: normalizeNonEmptyString(input.agentName),
915
935
  scopes: input.scopes && input.scopes.length > 0 ? [...input.scopes] : undefined,
916
936
  signal: input.signal,
917
- launcher: input.launcher ?? defaultMountLauncher,
937
+ launcher: input.launcher,
918
938
  readyTimeoutMs: Math.max(1, Math.floor(input.readyTimeoutMs ?? DEFAULT_MOUNT_READY_TIMEOUT_MS))
919
939
  };
920
940
  }
@@ -943,12 +963,63 @@ function normalizeMountRemotePath(remotePath) {
943
963
  if (segments.some((segment) => segment === "..")) {
944
964
  throw new InvalidRemotePathError(normalized);
945
965
  }
946
- const resolved = path.posix.normalize(normalized.startsWith("/") ? normalized : `/${normalized}`);
966
+ const resolved = normalizePosixPath(normalized.startsWith("/") ? normalized : `/${normalized}`);
947
967
  if (!resolved.startsWith("/")) {
948
968
  throw new InvalidRemotePathError(normalized);
949
969
  }
950
970
  return resolved === "/" ? "/" : resolved.replace(/\/+$/, "");
951
971
  }
972
+ function resolveLocalDir(localDir) {
973
+ const normalized = localDir.replace(/\\/g, "/");
974
+ if (isAbsolutePathLike(normalized)) {
975
+ return normalizePosixPath(normalized);
976
+ }
977
+ const cwd = readProcessCwd();
978
+ if (!cwd) {
979
+ return normalized;
980
+ }
981
+ return normalizePosixPath(`${cwd.replace(/\\/g, "/")}/${normalized}`);
982
+ }
983
+ function isAbsolutePathLike(value) {
984
+ return value.startsWith("/") || /^[A-Za-z]:\//.test(value);
985
+ }
986
+ function readProcessCwd() {
987
+ const maybeProcess = globalThis.process;
988
+ try {
989
+ return maybeProcess?.cwd?.();
990
+ }
991
+ catch {
992
+ return undefined;
993
+ }
994
+ }
995
+ function normalizePosixPath(value) {
996
+ const isDrivePath = /^[A-Za-z]:\//.test(value);
997
+ const prefix = isDrivePath
998
+ ? value.slice(0, 3)
999
+ : value.startsWith("/")
1000
+ ? "/"
1001
+ : "";
1002
+ const rest = isDrivePath ? value.slice(3) : value;
1003
+ const segments = [];
1004
+ for (const segment of rest.split("/")) {
1005
+ if (!segment || segment === ".") {
1006
+ continue;
1007
+ }
1008
+ if (segment === "..") {
1009
+ segments.pop();
1010
+ continue;
1011
+ }
1012
+ segments.push(segment);
1013
+ }
1014
+ const joined = segments.join("/");
1015
+ if (prefix === "/") {
1016
+ return joined ? `/${joined}` : "/";
1017
+ }
1018
+ if (prefix) {
1019
+ return joined ? `${prefix}${joined}` : prefix;
1020
+ }
1021
+ return joined || ".";
1022
+ }
952
1023
  function normalizeNonEmptyString(value) {
953
1024
  const normalized = value?.trim();
954
1025
  return normalized ? normalized : undefined;
package/package.json CHANGED
@@ -1,10 +1,35 @@
1
1
  {
2
2
  "name": "@relayfile/sdk",
3
- "version": "0.7.23",
3
+ "version": "0.7.25",
4
4
  "description": "TypeScript SDK for relayfile — real-time filesystem for humans and agents",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "type": "module",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ },
13
+ "./cli": {
14
+ "types": "./dist/cli/index.d.ts",
15
+ "default": "./dist/cli/index.js"
16
+ },
17
+ "./cloud-login": {
18
+ "types": "./dist/cloud-login.d.ts",
19
+ "default": "./dist/cloud-login.js"
20
+ },
21
+ "./mount-launcher": {
22
+ "types": "./dist/mount-launcher.d.ts",
23
+ "default": "./dist/mount-launcher.js"
24
+ },
25
+ "./mount-harness": {
26
+ "types": "./dist/mount-harness.d.ts",
27
+ "default": "./dist/mount-harness.js"
28
+ },
29
+ "./dist/*": "./dist/*",
30
+ "./package.json": "./package.json"
31
+ },
32
+ "sideEffects": false,
8
33
  "files": [
9
34
  "dist"
10
35
  ],
@@ -22,7 +47,7 @@
22
47
  "prepublishOnly": "npm run build"
23
48
  },
24
49
  "dependencies": {
25
- "@relayfile/core": "0.7.23"
50
+ "@relayfile/core": "0.7.25"
26
51
  },
27
52
  "devDependencies": {
28
53
  "typescript": "^5.7.3",