codex-relay 1.2.2 → 1.2.4

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
@@ -95,7 +95,6 @@ The relay listens on `0.0.0.0:8787` by default. Configure it with environment va
95
95
  | -------------------------------------- | -------------------------------------------------------------------------------------------------- |
96
96
  | `PORT` | Server port. Defaults to `8787`. |
97
97
  | `HOST` | Listen host. Defaults to `0.0.0.0`. |
98
- | `CODEX_RELAY_PUBLIC_URL` | URL printed into the pairing QR, for example a Tailscale or tunnel URL. |
99
98
  | `CODEX_RELAY_WORKSPACE_PATH` | Workspace path Codex should use. Defaults to the directory where you run `npx codex-relay@latest`. |
100
99
  | `CODEX_RELAY_AUTH_DB_PATH` | Pairing and session database path. Defaults to `.codex-relay/auth.db`. |
101
100
  | `CODEX_RELAY_APPROVAL_SECRET` | Secret used by the local approve command. Usually generated automatically. |
@@ -109,10 +108,6 @@ Examples:
109
108
  PORT=8788 npx codex-relay@latest
110
109
  ```
111
110
 
112
- ```sh
113
- CODEX_RELAY_PUBLIC_URL=http://100.64.0.10:8787 npx codex-relay@latest
114
- ```
115
-
116
111
  ```sh
117
112
  CODEX_RELAY_WORKSPACE_PATH=/path/to/project npx codex-relay@latest
118
113
  ```
@@ -124,7 +119,6 @@ The phone must be able to reach one of the URLs printed by the relay.
124
119
  - On the same Wi-Fi network, the relay usually prints a local network address.
125
120
  - On Tailscale, the relay prefers your Tailscale address when it can detect one.
126
121
  - If several Wi-Fi, VPN, or virtual network addresses are available, the QR includes all detected candidates and the app tries them automatically.
127
- - If the printed URL is not reachable from the phone, set `CODEX_RELAY_PUBLIC_URL` to a reachable HTTP URL.
128
122
 
129
123
  ## Troubleshooting
130
124
 
@@ -154,4 +148,3 @@ Connection checklist:
154
148
  - If keeping the same network is difficult, are both devices connected through Tailscale or another reachable private network?
155
149
  - Can the phone open the exact `Mobile:` URL printed by the relay?
156
150
  - Does the computer firewall allow inbound traffic on the relay port, usually `8787`?
157
- - If the printed URL is not reachable, did you set `CODEX_RELAY_PUBLIC_URL` to a reachable LAN, Tailscale, or tunnel URL?
@@ -1,2 +1,2 @@
1
- import { $ as SandboxModeSchema, A as PairResponseSchema, At as WorkspaceSelectionRequestSchema, B as QueuedThreadInputSchema, Bt as isPromptSkillMarkdownMention, C as ListQueuedThreadInputsResponseSchema, Ct as WorkspaceDirectoryEntrySchema, D as ListWorkspaceFilesResponseSchema, Dt as WorkspaceMarkdownPreviewTargetSchema, E as ListWorkspaceDirectoriesResponseSchema, Et as WorkspaceGitActionResponseSchema, F as PromptAttachmentSummarySchema, Ft as WorkspaceTerminalSessionResponseSchema, G as ResolveApprovalRequestSchema, Gt as promptSkillMentionMarkdown, H as RateLimitWindowSchema, Ht as promptMarkdownWithSkills, I as PromptContextInputSchema, It as WorkspaceTerminalStartRequestSchema, J as RunThreadResponseSchema, K as ResolveApprovalResponseSchema, Kt as promptSkillMentionTextCandidates, L as PromptContextSchema, Lt as apiPaths, M as PendingInputRequestQuestionSchema, Mt as WorkspaceTerminalOutputChunkSchema, N as PendingInputRequestSchema, Nt as WorkspaceTerminalOutputResponseSchema, O as PairEncryptedPayloadSchema, Ot as WorkspacePreviewNavigationRequestSchema, P as PromptAttachmentSchema, Pt as WorkspaceTerminalResizeRequestSchema, Q as RuntimePreferencesSchema, R as PromptSkillSchema, Rt as chatMessageDetailsFromPromptContext, S as ListModelsResponseSchema, St as WorkspaceChangesResponseSchema, T as ListThreadsResponseSchema, Tt as WorkspaceFileMentionSchema, U as RateLimitsResponseSchema, Ut as promptSkillDisplayName, V as RateLimitBucketSchema, Vt as normalizePromptContext, W as ReasoningEffortSchema, Wt as promptSkillMentionLabel, X as RuntimePreferencesByWorkspacePathSchema, Y as RuntimeModeSchema, Z as RuntimePreferencesResponseSchema, _ as EncryptedPayloadSchema, _t as UpdateWorkspaceFileContentRequestSchema, a as ArchiveThreadResponseSchema, at as ThreadContextWindowResponseSchema, b as InterruptThreadRunResponseSchema, bt as WORKSPACE_PREVIEW_TAB_VALUES, c as ChatMessageRoleSchema, ct as ThreadGoalSchema, d as CheckoutWorkspaceBranchRequestSchema, dt as ThreadMessageDetailResponseSchema, et as StatusResponseSchema, f as CodexModelSchema, ft as ThreadRunOptionsSchema, g as CreateThreadResponseSchema, gt as UpdateThreadGoalRequestSchema, h as CreateThreadRequestSchema, ht as UpdateRuntimePreferencesRequestSchema, i as ApprovalModeSchema, it as ThreadCollaborationModeSchema, j as PendingInputRequestOptionSchema, jt as WorkspaceTerminalInputRequestSchema, k as PairRequestSchema, kt as WorkspacePreviewTabSchema, l as ChatMessageSchema, lt as ThreadGoalStatusSchema, m as ContextWindowUsageSchema, mt as ThreadSummarySchema, n as AgentSkillSourceSchema, nt as StreamThreadRunRequestSchema, o as ChatMessageKindSchema, ot as ThreadDetailResponseSchema, p as CommitPushWorkspaceRequestSchema, pt as ThreadStateSchema, q as RunThreadRequestSchema, qt as stripPromptSkillMentions, r as ApprovalDecisionSchema, rt as SubmitThreadInputResponseSchema, s as ChatMessagePromptDetailsSchema, st as ThreadGoalResponseSchema, t as AgentSkillSchema, tt as StreamThreadRunEventSchema, u as ChatMessageStateSchema, ut as ThreadMessageDetailFieldSchema, v as ErrorResponseSchema, vt as VersionResponseSchema, w as ListSkillsResponseSchema, wt as WorkspaceFileContentResponseSchema, x as IsoDateTimeSchema, xt as WebPreviewTargetSchema, y as ImageAttachmentUploadResponseSchema, yt as WORKSPACE_PREVIEW_OPEN_PROTOCOL, z as QueuedThreadInputActionResponseSchema, zt as createOpenApiDocument } from "./api-schema2.js";
2
- export { AgentSkillSchema, AgentSkillSourceSchema, ApprovalDecisionSchema, ApprovalModeSchema, ArchiveThreadResponseSchema, ChatMessageKindSchema, ChatMessagePromptDetailsSchema, ChatMessageRoleSchema, ChatMessageSchema, ChatMessageStateSchema, CheckoutWorkspaceBranchRequestSchema, CodexModelSchema, CommitPushWorkspaceRequestSchema, ContextWindowUsageSchema, CreateThreadRequestSchema, CreateThreadResponseSchema, EncryptedPayloadSchema, ErrorResponseSchema, ImageAttachmentUploadResponseSchema, InterruptThreadRunResponseSchema, IsoDateTimeSchema, ListModelsResponseSchema, ListQueuedThreadInputsResponseSchema, ListSkillsResponseSchema, ListThreadsResponseSchema, ListWorkspaceDirectoriesResponseSchema, ListWorkspaceFilesResponseSchema, PairEncryptedPayloadSchema, PairRequestSchema, PairResponseSchema, PendingInputRequestOptionSchema, PendingInputRequestQuestionSchema, PendingInputRequestSchema, PromptAttachmentSchema, PromptAttachmentSummarySchema, PromptContextInputSchema, PromptContextSchema, PromptSkillSchema, QueuedThreadInputActionResponseSchema, QueuedThreadInputSchema, RateLimitBucketSchema, RateLimitWindowSchema, RateLimitsResponseSchema, ReasoningEffortSchema, ResolveApprovalRequestSchema, ResolveApprovalResponseSchema, RunThreadRequestSchema, RunThreadResponseSchema, RuntimeModeSchema, RuntimePreferencesByWorkspacePathSchema, RuntimePreferencesResponseSchema, RuntimePreferencesSchema, SandboxModeSchema, StatusResponseSchema, StreamThreadRunEventSchema, StreamThreadRunRequestSchema, SubmitThreadInputResponseSchema, ThreadCollaborationModeSchema, ThreadContextWindowResponseSchema, ThreadDetailResponseSchema, ThreadGoalResponseSchema, ThreadGoalSchema, ThreadGoalStatusSchema, ThreadMessageDetailFieldSchema, ThreadMessageDetailResponseSchema, ThreadRunOptionsSchema, ThreadStateSchema, ThreadSummarySchema, UpdateRuntimePreferencesRequestSchema, UpdateThreadGoalRequestSchema, UpdateWorkspaceFileContentRequestSchema, VersionResponseSchema, WORKSPACE_PREVIEW_OPEN_PROTOCOL, WORKSPACE_PREVIEW_TAB_VALUES, WebPreviewTargetSchema, WorkspaceChangesResponseSchema, WorkspaceDirectoryEntrySchema, WorkspaceFileContentResponseSchema, WorkspaceFileMentionSchema, WorkspaceGitActionResponseSchema, WorkspaceMarkdownPreviewTargetSchema, WorkspacePreviewNavigationRequestSchema, WorkspacePreviewTabSchema, WorkspaceSelectionRequestSchema, WorkspaceTerminalInputRequestSchema, WorkspaceTerminalOutputChunkSchema, WorkspaceTerminalOutputResponseSchema, WorkspaceTerminalResizeRequestSchema, WorkspaceTerminalSessionResponseSchema, WorkspaceTerminalStartRequestSchema, apiPaths, chatMessageDetailsFromPromptContext, createOpenApiDocument, isPromptSkillMarkdownMention, normalizePromptContext, promptMarkdownWithSkills, promptSkillDisplayName, promptSkillMentionLabel, promptSkillMentionMarkdown, promptSkillMentionTextCandidates, stripPromptSkillMentions };
1
+ import { $ as SandboxModeSchema, A as PairResponseSchema, At as WorkspaceSelectionRequestSchema, B as QueuedThreadInputSchema, Bt as chatMessageDetailsFromPromptContext, C as ListQueuedThreadInputsResponseSchema, Ct as WorkspaceDirectoryEntrySchema, D as ListWorkspaceFilesResponseSchema, Dt as WorkspaceMarkdownPreviewTargetSchema, E as ListWorkspaceDirectoriesResponseSchema, Et as WorkspaceGitActionResponseSchema, F as PromptAttachmentSummarySchema, Ft as WorkspaceTerminalOutputResponseSchema, G as ResolveApprovalRequestSchema, Gt as promptSkillDisplayName, H as RateLimitWindowSchema, Ht as isPromptSkillMarkdownMention, I as PromptContextInputSchema, It as WorkspaceTerminalResizeRequestSchema, J as RunThreadResponseSchema, Jt as promptSkillMentionTextCandidates, K as ResolveApprovalResponseSchema, Kt as promptSkillMentionLabel, L as PromptContextSchema, Lt as WorkspaceTerminalSessionResponseSchema, M as PendingInputRequestQuestionSchema, Mt as WorkspaceTailscaleServeResponseSchema, N as PendingInputRequestSchema, Nt as WorkspaceTerminalInputRequestSchema, O as PairEncryptedPayloadSchema, Ot as WorkspacePreviewNavigationRequestSchema, P as PromptAttachmentSchema, Pt as WorkspaceTerminalOutputChunkSchema, Q as RuntimePreferencesSchema, R as PromptSkillSchema, Rt as WorkspaceTerminalStartRequestSchema, S as ListModelsResponseSchema, St as WorkspaceChangesResponseSchema, T as ListThreadsResponseSchema, Tt as WorkspaceFileMentionSchema, U as RateLimitsResponseSchema, Ut as normalizePromptContext, V as RateLimitBucketSchema, Vt as createOpenApiDocument, W as ReasoningEffortSchema, Wt as promptMarkdownWithSkills, X as RuntimePreferencesByWorkspacePathSchema, Y as RuntimeModeSchema, Yt as stripPromptSkillMentions, Z as RuntimePreferencesResponseSchema, _ as EncryptedPayloadSchema, _t as UpdateWorkspaceFileContentRequestSchema, a as ArchiveThreadResponseSchema, at as ThreadContextWindowResponseSchema, b as InterruptThreadRunResponseSchema, bt as WORKSPACE_PREVIEW_TAB_VALUES, c as ChatMessageRoleSchema, ct as ThreadGoalSchema, d as CheckoutWorkspaceBranchRequestSchema, dt as ThreadMessageDetailResponseSchema, et as StatusResponseSchema, f as CodexModelSchema, ft as ThreadRunOptionsSchema, g as CreateThreadResponseSchema, gt as UpdateThreadGoalRequestSchema, h as CreateThreadRequestSchema, ht as UpdateRuntimePreferencesRequestSchema, i as ApprovalModeSchema, it as ThreadCollaborationModeSchema, j as PendingInputRequestOptionSchema, jt as WorkspaceTailscaleServeRequestSchema, k as PairRequestSchema, kt as WorkspacePreviewTabSchema, l as ChatMessageSchema, lt as ThreadGoalStatusSchema, m as ContextWindowUsageSchema, mt as ThreadSummarySchema, n as AgentSkillSourceSchema, nt as StreamThreadRunRequestSchema, o as ChatMessageKindSchema, ot as ThreadDetailResponseSchema, p as CommitPushWorkspaceRequestSchema, pt as ThreadStateSchema, q as RunThreadRequestSchema, qt as promptSkillMentionMarkdown, r as ApprovalDecisionSchema, rt as SubmitThreadInputResponseSchema, s as ChatMessagePromptDetailsSchema, st as ThreadGoalResponseSchema, t as AgentSkillSchema, tt as StreamThreadRunEventSchema, u as ChatMessageStateSchema, ut as ThreadMessageDetailFieldSchema, v as ErrorResponseSchema, vt as VersionResponseSchema, w as ListSkillsResponseSchema, wt as WorkspaceFileContentResponseSchema, x as IsoDateTimeSchema, xt as WebPreviewTargetSchema, y as ImageAttachmentUploadResponseSchema, yt as WORKSPACE_PREVIEW_OPEN_PROTOCOL, z as QueuedThreadInputActionResponseSchema, zt as apiPaths } from "./api-schema2.js";
2
+ export { AgentSkillSchema, AgentSkillSourceSchema, ApprovalDecisionSchema, ApprovalModeSchema, ArchiveThreadResponseSchema, ChatMessageKindSchema, ChatMessagePromptDetailsSchema, ChatMessageRoleSchema, ChatMessageSchema, ChatMessageStateSchema, CheckoutWorkspaceBranchRequestSchema, CodexModelSchema, CommitPushWorkspaceRequestSchema, ContextWindowUsageSchema, CreateThreadRequestSchema, CreateThreadResponseSchema, EncryptedPayloadSchema, ErrorResponseSchema, ImageAttachmentUploadResponseSchema, InterruptThreadRunResponseSchema, IsoDateTimeSchema, ListModelsResponseSchema, ListQueuedThreadInputsResponseSchema, ListSkillsResponseSchema, ListThreadsResponseSchema, ListWorkspaceDirectoriesResponseSchema, ListWorkspaceFilesResponseSchema, PairEncryptedPayloadSchema, PairRequestSchema, PairResponseSchema, PendingInputRequestOptionSchema, PendingInputRequestQuestionSchema, PendingInputRequestSchema, PromptAttachmentSchema, PromptAttachmentSummarySchema, PromptContextInputSchema, PromptContextSchema, PromptSkillSchema, QueuedThreadInputActionResponseSchema, QueuedThreadInputSchema, RateLimitBucketSchema, RateLimitWindowSchema, RateLimitsResponseSchema, ReasoningEffortSchema, ResolveApprovalRequestSchema, ResolveApprovalResponseSchema, RunThreadRequestSchema, RunThreadResponseSchema, RuntimeModeSchema, RuntimePreferencesByWorkspacePathSchema, RuntimePreferencesResponseSchema, RuntimePreferencesSchema, SandboxModeSchema, StatusResponseSchema, StreamThreadRunEventSchema, StreamThreadRunRequestSchema, SubmitThreadInputResponseSchema, ThreadCollaborationModeSchema, ThreadContextWindowResponseSchema, ThreadDetailResponseSchema, ThreadGoalResponseSchema, ThreadGoalSchema, ThreadGoalStatusSchema, ThreadMessageDetailFieldSchema, ThreadMessageDetailResponseSchema, ThreadRunOptionsSchema, ThreadStateSchema, ThreadSummarySchema, UpdateRuntimePreferencesRequestSchema, UpdateThreadGoalRequestSchema, UpdateWorkspaceFileContentRequestSchema, VersionResponseSchema, WORKSPACE_PREVIEW_OPEN_PROTOCOL, WORKSPACE_PREVIEW_TAB_VALUES, WebPreviewTargetSchema, WorkspaceChangesResponseSchema, WorkspaceDirectoryEntrySchema, WorkspaceFileContentResponseSchema, WorkspaceFileMentionSchema, WorkspaceGitActionResponseSchema, WorkspaceMarkdownPreviewTargetSchema, WorkspacePreviewNavigationRequestSchema, WorkspacePreviewTabSchema, WorkspaceSelectionRequestSchema, WorkspaceTailscaleServeRequestSchema, WorkspaceTailscaleServeResponseSchema, WorkspaceTerminalInputRequestSchema, WorkspaceTerminalOutputChunkSchema, WorkspaceTerminalOutputResponseSchema, WorkspaceTerminalResizeRequestSchema, WorkspaceTerminalSessionResponseSchema, WorkspaceTerminalStartRequestSchema, apiPaths, chatMessageDetailsFromPromptContext, createOpenApiDocument, isPromptSkillMarkdownMention, normalizePromptContext, promptMarkdownWithSkills, promptSkillDisplayName, promptSkillMentionLabel, promptSkillMentionMarkdown, promptSkillMentionTextCandidates, stripPromptSkillMentions };
@@ -409,6 +409,11 @@ const WorkspaceTerminalResizeRequestSchema = z.object({
409
409
  cols: z.number().int().min(2).max(300),
410
410
  rows: z.number().int().min(2).max(120)
411
411
  });
412
+ const WorkspaceTailscaleServeRequestSchema = z.object({ url: z.string().url() });
413
+ const WorkspaceTailscaleServeResponseSchema = z.object({
414
+ port: z.number().int().positive(),
415
+ url: z.string().url()
416
+ });
412
417
  const PairRequestSchema = z.object({
413
418
  clientSessionId: z.string().trim().min(1).max(120).optional(),
414
419
  clientName: z.string().trim().min(1).max(80).optional(),
@@ -724,6 +729,7 @@ const apiPaths = {
724
729
  workspaceTerminalOutput: (sessionId) => `/v1/workspace/terminal/sessions/${encodeURIComponent(sessionId)}/output`,
725
730
  workspaceTerminalOutputStream: (sessionId) => `/v1/workspace/terminal/sessions/${encodeURIComponent(sessionId)}/output/stream`,
726
731
  workspaceTerminalResize: (sessionId) => `/v1/workspace/terminal/sessions/${encodeURIComponent(sessionId)}/resize`,
732
+ workspaceTailscaleServe: "/v1/workspace/tailscale/serve",
727
733
  imageAttachments: "/v1/attachments/images",
728
734
  imageAttachment: (attachmentId) => `/v1/attachments/images/${encodeURIComponent(attachmentId)}`,
729
735
  threads: "/v1/threads",
@@ -843,6 +849,15 @@ function createOpenApiDocument() {
843
849
  }
844
850
  }
845
851
  },
852
+ "/v1/workspace/tailscale/serve": { post: {
853
+ summary: "Start Tailscale Serve for a workspace preview URL",
854
+ requestBody: jsonRequest("WorkspaceTailscaleServeRequest"),
855
+ responses: {
856
+ "200": jsonResponse("WorkspaceTailscaleServeResponse"),
857
+ "400": jsonResponse("ErrorResponse"),
858
+ "502": jsonResponse("ErrorResponse")
859
+ }
860
+ } },
846
861
  "/v1/threads/{threadId}/runs": { post: {
847
862
  summary: "Run a prompt on a Codex thread",
848
863
  parameters: [{
@@ -1339,6 +1354,28 @@ function createOpenApiDocument() {
1339
1354
  workspacePath: { type: "string" }
1340
1355
  }
1341
1356
  },
1357
+ WorkspaceTailscaleServeRequest: {
1358
+ type: "object",
1359
+ required: ["url"],
1360
+ properties: { url: {
1361
+ type: "string",
1362
+ format: "uri"
1363
+ } }
1364
+ },
1365
+ WorkspaceTailscaleServeResponse: {
1366
+ type: "object",
1367
+ required: ["url", "port"],
1368
+ properties: {
1369
+ url: {
1370
+ type: "string",
1371
+ format: "uri"
1372
+ },
1373
+ port: {
1374
+ type: "integer",
1375
+ minimum: 1
1376
+ }
1377
+ }
1378
+ },
1342
1379
  CreateThreadRequest: {
1343
1380
  type: "object",
1344
1381
  properties: {
@@ -1689,4 +1726,4 @@ function jsonResponse(schemaName) {
1689
1726
  };
1690
1727
  }
1691
1728
  //#endregion
1692
- export { SandboxModeSchema as $, PairResponseSchema as A, WorkspaceSelectionRequestSchema as At, QueuedThreadInputSchema as B, isPromptSkillMarkdownMention as Bt, ListQueuedThreadInputsResponseSchema as C, WorkspaceDirectoryEntrySchema as Ct, ListWorkspaceFilesResponseSchema as D, WorkspaceMarkdownPreviewTargetSchema as Dt, ListWorkspaceDirectoriesResponseSchema as E, WorkspaceGitActionResponseSchema as Et, PromptAttachmentSummarySchema as F, WorkspaceTerminalSessionResponseSchema as Ft, ResolveApprovalRequestSchema as G, promptSkillMentionMarkdown as Gt, RateLimitWindowSchema as H, promptMarkdownWithSkills as Ht, PromptContextInputSchema as I, WorkspaceTerminalStartRequestSchema as It, RunThreadResponseSchema as J, ResolveApprovalResponseSchema as K, promptSkillMentionTextCandidates as Kt, PromptContextSchema as L, apiPaths as Lt, PendingInputRequestQuestionSchema as M, WorkspaceTerminalOutputChunkSchema as Mt, PendingInputRequestSchema as N, WorkspaceTerminalOutputResponseSchema as Nt, PairEncryptedPayloadSchema as O, WorkspacePreviewNavigationRequestSchema as Ot, PromptAttachmentSchema as P, WorkspaceTerminalResizeRequestSchema as Pt, RuntimePreferencesSchema as Q, PromptSkillSchema as R, chatMessageDetailsFromPromptContext as Rt, ListModelsResponseSchema as S, WorkspaceChangesResponseSchema as St, ListThreadsResponseSchema as T, WorkspaceFileMentionSchema as Tt, RateLimitsResponseSchema as U, promptSkillDisplayName as Ut, RateLimitBucketSchema as V, normalizePromptContext as Vt, ReasoningEffortSchema as W, promptSkillMentionLabel as Wt, RuntimePreferencesByWorkspacePathSchema as X, RuntimeModeSchema as Y, RuntimePreferencesResponseSchema as Z, EncryptedPayloadSchema as _, UpdateWorkspaceFileContentRequestSchema as _t, ArchiveThreadResponseSchema as a, ThreadContextWindowResponseSchema as at, InterruptThreadRunResponseSchema as b, WORKSPACE_PREVIEW_TAB_VALUES as bt, ChatMessageRoleSchema as c, ThreadGoalSchema as ct, CheckoutWorkspaceBranchRequestSchema as d, ThreadMessageDetailResponseSchema as dt, StatusResponseSchema as et, CodexModelSchema as f, ThreadRunOptionsSchema as ft, CreateThreadResponseSchema as g, UpdateThreadGoalRequestSchema as gt, CreateThreadRequestSchema as h, UpdateRuntimePreferencesRequestSchema as ht, ApprovalModeSchema as i, ThreadCollaborationModeSchema as it, PendingInputRequestOptionSchema as j, WorkspaceTerminalInputRequestSchema as jt, PairRequestSchema as k, WorkspacePreviewTabSchema as kt, ChatMessageSchema as l, ThreadGoalStatusSchema as lt, ContextWindowUsageSchema as m, ThreadSummarySchema as mt, AgentSkillSourceSchema as n, StreamThreadRunRequestSchema as nt, ChatMessageKindSchema as o, ThreadDetailResponseSchema as ot, CommitPushWorkspaceRequestSchema as p, ThreadStateSchema as pt, RunThreadRequestSchema as q, stripPromptSkillMentions as qt, ApprovalDecisionSchema as r, SubmitThreadInputResponseSchema as rt, ChatMessagePromptDetailsSchema as s, ThreadGoalResponseSchema as st, AgentSkillSchema as t, StreamThreadRunEventSchema as tt, ChatMessageStateSchema as u, ThreadMessageDetailFieldSchema as ut, ErrorResponseSchema as v, VersionResponseSchema as vt, ListSkillsResponseSchema as w, WorkspaceFileContentResponseSchema as wt, IsoDateTimeSchema as x, WebPreviewTargetSchema as xt, ImageAttachmentUploadResponseSchema as y, WORKSPACE_PREVIEW_OPEN_PROTOCOL as yt, QueuedThreadInputActionResponseSchema as z, createOpenApiDocument as zt };
1729
+ export { SandboxModeSchema as $, PairResponseSchema as A, WorkspaceSelectionRequestSchema as At, QueuedThreadInputSchema as B, chatMessageDetailsFromPromptContext as Bt, ListQueuedThreadInputsResponseSchema as C, WorkspaceDirectoryEntrySchema as Ct, ListWorkspaceFilesResponseSchema as D, WorkspaceMarkdownPreviewTargetSchema as Dt, ListWorkspaceDirectoriesResponseSchema as E, WorkspaceGitActionResponseSchema as Et, PromptAttachmentSummarySchema as F, WorkspaceTerminalOutputResponseSchema as Ft, ResolveApprovalRequestSchema as G, promptSkillDisplayName as Gt, RateLimitWindowSchema as H, isPromptSkillMarkdownMention as Ht, PromptContextInputSchema as I, WorkspaceTerminalResizeRequestSchema as It, RunThreadResponseSchema as J, promptSkillMentionTextCandidates as Jt, ResolveApprovalResponseSchema as K, promptSkillMentionLabel as Kt, PromptContextSchema as L, WorkspaceTerminalSessionResponseSchema as Lt, PendingInputRequestQuestionSchema as M, WorkspaceTailscaleServeResponseSchema as Mt, PendingInputRequestSchema as N, WorkspaceTerminalInputRequestSchema as Nt, PairEncryptedPayloadSchema as O, WorkspacePreviewNavigationRequestSchema as Ot, PromptAttachmentSchema as P, WorkspaceTerminalOutputChunkSchema as Pt, RuntimePreferencesSchema as Q, PromptSkillSchema as R, WorkspaceTerminalStartRequestSchema as Rt, ListModelsResponseSchema as S, WorkspaceChangesResponseSchema as St, ListThreadsResponseSchema as T, WorkspaceFileMentionSchema as Tt, RateLimitsResponseSchema as U, normalizePromptContext as Ut, RateLimitBucketSchema as V, createOpenApiDocument as Vt, ReasoningEffortSchema as W, promptMarkdownWithSkills as Wt, RuntimePreferencesByWorkspacePathSchema as X, RuntimeModeSchema as Y, stripPromptSkillMentions as Yt, RuntimePreferencesResponseSchema as Z, EncryptedPayloadSchema as _, UpdateWorkspaceFileContentRequestSchema as _t, ArchiveThreadResponseSchema as a, ThreadContextWindowResponseSchema as at, InterruptThreadRunResponseSchema as b, WORKSPACE_PREVIEW_TAB_VALUES as bt, ChatMessageRoleSchema as c, ThreadGoalSchema as ct, CheckoutWorkspaceBranchRequestSchema as d, ThreadMessageDetailResponseSchema as dt, StatusResponseSchema as et, CodexModelSchema as f, ThreadRunOptionsSchema as ft, CreateThreadResponseSchema as g, UpdateThreadGoalRequestSchema as gt, CreateThreadRequestSchema as h, UpdateRuntimePreferencesRequestSchema as ht, ApprovalModeSchema as i, ThreadCollaborationModeSchema as it, PendingInputRequestOptionSchema as j, WorkspaceTailscaleServeRequestSchema as jt, PairRequestSchema as k, WorkspacePreviewTabSchema as kt, ChatMessageSchema as l, ThreadGoalStatusSchema as lt, ContextWindowUsageSchema as m, ThreadSummarySchema as mt, AgentSkillSourceSchema as n, StreamThreadRunRequestSchema as nt, ChatMessageKindSchema as o, ThreadDetailResponseSchema as ot, CommitPushWorkspaceRequestSchema as p, ThreadStateSchema as pt, RunThreadRequestSchema as q, promptSkillMentionMarkdown as qt, ApprovalDecisionSchema as r, SubmitThreadInputResponseSchema as rt, ChatMessagePromptDetailsSchema as s, ThreadGoalResponseSchema as st, AgentSkillSchema as t, StreamThreadRunEventSchema as tt, ChatMessageStateSchema as u, ThreadMessageDetailFieldSchema as ut, ErrorResponseSchema as v, VersionResponseSchema as vt, ListSkillsResponseSchema as w, WorkspaceFileContentResponseSchema as wt, IsoDateTimeSchema as x, WebPreviewTargetSchema as xt, ImageAttachmentUploadResponseSchema as y, WORKSPACE_PREVIEW_OPEN_PROTOCOL as yt, QueuedThreadInputActionResponseSchema as z, apiPaths as zt };
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { Lt as apiPaths } from "./api-schema2.js";
2
+ import { zt as apiPaths } from "./api-schema2.js";
3
3
  import { n as codexRelayHome, o as getConnectUrlGuidance, r as legacyCodexRelayDataPath, s as createTursoPairingSessionStore, t as codexRelayDataPath } from "./paths.js";
4
4
  import { Command } from "@commander-js/extra-typings";
5
5
  import qrcode from "qrcode-terminal";
package/dist/paths.js CHANGED
@@ -252,7 +252,7 @@ function resultRows(result) {
252
252
  function getConnectUrlGuidance(url) {
253
253
  const host = parseUrlHost(url);
254
254
  if (!host) return;
255
- if (isLocalhost(host) || isUnspecifiedHost(host)) return "This address is only reachable from this computer. Use a same-Wi-Fi address, Tailscale, or CODEX_RELAY_PUBLIC_URL for mobile pairing.";
255
+ if (isLocalhost(host) || isUnspecifiedHost(host)) return "This address is only reachable from this computer. Use a same-Wi-Fi address or Tailscale for mobile pairing.";
256
256
  if (isTailscaleHost(host)) return "Using Tailscale. Keep Tailscale connected on both this computer and the phone.";
257
257
  if (isPrivateIPv4Host(host) || isLocalIPv6Host(host)) return "Using a local Wi-Fi/LAN address. Keep the phone and computer on the same network; if pairing is flaky, try Tailscale.";
258
258
  return "Using a configured or public address. Make sure the phone can reach it before pairing.";
@@ -269,7 +269,6 @@ function createPairingQrPayload(details) {
269
269
  }
270
270
  function getConnectUrlCandidates(details) {
271
271
  return dedupeCandidates([
272
- ...configuredConnectUrlCandidates(),
273
272
  ...tailscaleConnectUrlCandidates(details.port),
274
273
  ...localNetworkConnectUrlCandidates(details.port),
275
274
  {
@@ -289,13 +288,6 @@ function normalizeUrl(value) {
289
288
  return;
290
289
  }
291
290
  }
292
- function configuredConnectUrlCandidates() {
293
- const configuredUrl = normalizeUrl(process.env.CODEX_RELAY_PUBLIC_URL);
294
- return configuredUrl ? [{
295
- label: "Configured",
296
- url: configuredUrl
297
- }] : [];
298
- }
299
291
  function tailscaleConnectUrlCandidates(port) {
300
292
  const status = getTailscaleStatus();
301
293
  const candidates = [];
package/dist/src.js CHANGED
@@ -1,10 +1,10 @@
1
- import { A as PairResponseSchema, C as ListQueuedThreadInputsResponseSchema, D as ListWorkspaceFilesResponseSchema, E as ListWorkspaceDirectoriesResponseSchema, Et as WorkspaceGitActionResponseSchema, Ft as WorkspaceTerminalSessionResponseSchema, G as ResolveApprovalRequestSchema, Ht as promptMarkdownWithSkills, It as WorkspaceTerminalStartRequestSchema, K as ResolveApprovalResponseSchema, Lt as apiPaths, Nt as WorkspaceTerminalOutputResponseSchema, Pt as WorkspaceTerminalResizeRequestSchema, Q as RuntimePreferencesSchema, Rt as chatMessageDetailsFromPromptContext, S as ListModelsResponseSchema, St as WorkspaceChangesResponseSchema, T as ListThreadsResponseSchema, U as RateLimitsResponseSchema, Vt as normalizePromptContext, X as RuntimePreferencesByWorkspacePathSchema, Z as RuntimePreferencesResponseSchema, _ as EncryptedPayloadSchema, _t as UpdateWorkspaceFileContentRequestSchema, a as ArchiveThreadResponseSchema, at as ThreadContextWindowResponseSchema, b as InterruptThreadRunResponseSchema, ct as ThreadGoalSchema, d as CheckoutWorkspaceBranchRequestSchema, dt as ThreadMessageDetailResponseSchema, et as StatusResponseSchema, gt as UpdateThreadGoalRequestSchema, h as CreateThreadRequestSchema, ht as UpdateRuntimePreferencesRequestSchema, jt as WorkspaceTerminalInputRequestSchema, k as PairRequestSchema, l as ChatMessageSchema, lt as ThreadGoalStatusSchema, m as ContextWindowUsageSchema, mt as ThreadSummarySchema, nt as StreamThreadRunRequestSchema, ot as ThreadDetailResponseSchema, p as CommitPushWorkspaceRequestSchema, q as RunThreadRequestSchema, qt as stripPromptSkillMentions, rt as SubmitThreadInputResponseSchema, st as ThreadGoalResponseSchema, tt as StreamThreadRunEventSchema, ut as ThreadMessageDetailFieldSchema, vt as VersionResponseSchema, w as ListSkillsResponseSchema, wt as WorkspaceFileContentResponseSchema, y as ImageAttachmentUploadResponseSchema, z as QueuedThreadInputActionResponseSchema, zt as createOpenApiDocument } from "./api-schema2.js";
1
+ import { A as PairResponseSchema, Bt as chatMessageDetailsFromPromptContext, C as ListQueuedThreadInputsResponseSchema, D as ListWorkspaceFilesResponseSchema, E as ListWorkspaceDirectoriesResponseSchema, Et as WorkspaceGitActionResponseSchema, Ft as WorkspaceTerminalOutputResponseSchema, G as ResolveApprovalRequestSchema, It as WorkspaceTerminalResizeRequestSchema, K as ResolveApprovalResponseSchema, Lt as WorkspaceTerminalSessionResponseSchema, Mt as WorkspaceTailscaleServeResponseSchema, Nt as WorkspaceTerminalInputRequestSchema, Q as RuntimePreferencesSchema, Rt as WorkspaceTerminalStartRequestSchema, S as ListModelsResponseSchema, St as WorkspaceChangesResponseSchema, T as ListThreadsResponseSchema, U as RateLimitsResponseSchema, Ut as normalizePromptContext, Vt as createOpenApiDocument, Wt as promptMarkdownWithSkills, X as RuntimePreferencesByWorkspacePathSchema, Yt as stripPromptSkillMentions, Z as RuntimePreferencesResponseSchema, _ as EncryptedPayloadSchema, _t as UpdateWorkspaceFileContentRequestSchema, a as ArchiveThreadResponseSchema, at as ThreadContextWindowResponseSchema, b as InterruptThreadRunResponseSchema, ct as ThreadGoalSchema, d as CheckoutWorkspaceBranchRequestSchema, dt as ThreadMessageDetailResponseSchema, et as StatusResponseSchema, gt as UpdateThreadGoalRequestSchema, h as CreateThreadRequestSchema, ht as UpdateRuntimePreferencesRequestSchema, jt as WorkspaceTailscaleServeRequestSchema, k as PairRequestSchema, l as ChatMessageSchema, lt as ThreadGoalStatusSchema, m as ContextWindowUsageSchema, mt as ThreadSummarySchema, nt as StreamThreadRunRequestSchema, ot as ThreadDetailResponseSchema, p as CommitPushWorkspaceRequestSchema, q as RunThreadRequestSchema, rt as SubmitThreadInputResponseSchema, st as ThreadGoalResponseSchema, tt as StreamThreadRunEventSchema, ut as ThreadMessageDetailFieldSchema, vt as VersionResponseSchema, w as ListSkillsResponseSchema, wt as WorkspaceFileContentResponseSchema, y as ImageAttachmentUploadResponseSchema, z as QueuedThreadInputActionResponseSchema, zt as apiPaths } from "./api-schema2.js";
2
2
  import { a as getConnectUrlCandidates, i as createPairingQrPayload, o as getConnectUrlGuidance, r as legacyCodexRelayDataPath, s as createTursoPairingSessionStore, t as codexRelayDataPath } from "./paths.js";
3
3
  import { createRequire } from "node:module";
4
4
  import qrcode from "qrcode-terminal";
5
5
  import { execFile, spawn } from "node:child_process";
6
6
  import fs, { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
7
- import { access, appendFile, copyFile, mkdir, open, readFile, readdir, stat, writeFile } from "node:fs/promises";
7
+ import { access, appendFile, copyFile, mkdir, open, readFile, readdir, realpath, stat, writeFile } from "node:fs/promises";
8
8
  import path, { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
9
9
  import { fileURLToPath } from "node:url";
10
10
  import { z } from "zod";
@@ -728,8 +728,17 @@ async function listAvailableSkills(options) {
728
728
  }
729
729
  async function findSkillFiles(root) {
730
730
  const found = [];
731
+ const visitedDirectories = /* @__PURE__ */ new Set();
731
732
  async function walk(path, depth) {
732
733
  if (depth < 0) return;
734
+ let realDirectoryPath;
735
+ try {
736
+ realDirectoryPath = await realpath(path);
737
+ } catch {
738
+ return;
739
+ }
740
+ if (visitedDirectories.has(realDirectoryPath)) return;
741
+ visitedDirectories.add(realDirectoryPath);
733
742
  let entries;
734
743
  try {
735
744
  entries = await readdir(path, { withFileTypes: true });
@@ -738,20 +747,34 @@ async function findSkillFiles(root) {
738
747
  }
739
748
  for (const entry of entries) {
740
749
  const entryPath = join(path, entry.name);
741
- if (entry.isFile() && entry.name === "SKILL.md") {
750
+ const kind = await directoryEntryKind(entry, entryPath);
751
+ if (kind === "file" && entry.name === "SKILL.md") {
742
752
  found.push({
743
753
  ...root,
744
754
  path: entryPath
745
755
  });
746
756
  continue;
747
757
  }
748
- if (!entry.isDirectory() || entry.name === "node_modules") continue;
758
+ if (kind !== "directory" || entry.name === "node_modules") continue;
749
759
  await walk(entryPath, depth - 1);
750
760
  }
751
761
  }
752
762
  await walk(root.path, root.maxDepth);
753
763
  return found;
754
764
  }
765
+ async function directoryEntryKind(entry, entryPath) {
766
+ if (entry.isFile()) return "file";
767
+ if (entry.isDirectory()) return "directory";
768
+ if (!entry.isSymbolicLink()) return null;
769
+ try {
770
+ const target = await stat(entryPath);
771
+ if (target.isFile()) return "file";
772
+ if (target.isDirectory()) return "directory";
773
+ } catch {
774
+ return null;
775
+ }
776
+ return null;
777
+ }
755
778
  async function readSkill(entry) {
756
779
  let markdown;
757
780
  try {
@@ -777,7 +800,7 @@ async function readSkill(entry) {
777
800
  }
778
801
  function parseSkillMarkdown(markdown) {
779
802
  const frontmatter = parseFrontmatter(markdown);
780
- const heading = markdown.match(/^#\s+(.+)$/m)?.[1]?.trim();
803
+ const heading = firstMarkdownHeading(markdownBody(markdown));
781
804
  return {
782
805
  description: frontmatter.description,
783
806
  displayName: heading ? cleanHeading(heading) : void 0,
@@ -789,13 +812,59 @@ function parseFrontmatter(markdown) {
789
812
  const end = markdown.indexOf("\n---", 4);
790
813
  if (end < 0) return {};
791
814
  const fields = {};
792
- for (const line of markdown.slice(4, end).split("\n")) {
793
- const match = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
815
+ const lines = markdown.slice(4, end).split("\n");
816
+ for (let index = 0; index < lines.length; index += 1) {
817
+ const match = lines[index].match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
794
818
  if (!match) continue;
795
- fields[match[1]] = unquoteYamlScalar(match[2].trim());
819
+ const key = match[1];
820
+ const value = match[2].trim();
821
+ if (isBlockScalar(value)) {
822
+ const blockLines = [];
823
+ while (index + 1 < lines.length && (lines[index + 1].trim() === "" || /^\s/.test(lines[index + 1]))) {
824
+ index += 1;
825
+ blockLines.push(lines[index].replace(/^\s+/, ""));
826
+ }
827
+ fields[key] = formatBlockScalar(value, blockLines);
828
+ continue;
829
+ }
830
+ fields[key] = unquoteYamlScalar(value);
796
831
  }
797
832
  return fields;
798
833
  }
834
+ function isBlockScalar(value) {
835
+ return [
836
+ "|",
837
+ "|-",
838
+ "|+",
839
+ ">",
840
+ ">-",
841
+ ">+"
842
+ ].includes(value);
843
+ }
844
+ function formatBlockScalar(marker, lines) {
845
+ const trimmedLines = lines.map((line) => line.trimEnd());
846
+ if (marker.startsWith("|")) return trimmedLines.join("\n").trim();
847
+ return trimmedLines.map((line) => line.trim()).filter(Boolean).join(" ");
848
+ }
849
+ function markdownBody(markdown) {
850
+ if (!markdown.startsWith("---\n")) return markdown;
851
+ const end = markdown.indexOf("\n---", 4);
852
+ if (end < 0) return markdown;
853
+ return markdown.slice(end + 4).replace(/^\r?\n/, "");
854
+ }
855
+ function firstMarkdownHeading(markdown) {
856
+ let fence;
857
+ for (const line of markdown.split("\n")) {
858
+ const fenceMatch = line.match(/^\s*(```|~~~)/);
859
+ if (fenceMatch) {
860
+ fence = fence ? void 0 : fenceMatch[1];
861
+ continue;
862
+ }
863
+ if (fence) continue;
864
+ const heading = line.match(/^#\s+(.+)$/)?.[1]?.trim();
865
+ if (heading) return heading;
866
+ }
867
+ }
799
868
  function unquoteYamlScalar(value) {
800
869
  if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1);
801
870
  return value;
@@ -828,6 +897,102 @@ function dedupe(items, keyFor) {
828
897
  });
829
898
  }
830
899
  //#endregion
900
+ //#region src/tailscale-serve.ts
901
+ var TailscaleServeInvalidUrlError = class extends Error {
902
+ constructor(message) {
903
+ super(message);
904
+ this.name = "TailscaleServeInvalidUrlError";
905
+ }
906
+ };
907
+ var TailscaleServeCommandError = class extends Error {
908
+ constructor(message) {
909
+ super(message);
910
+ this.name = "TailscaleServeCommandError";
911
+ }
912
+ };
913
+ const execFileAsync$1 = promisify(execFile);
914
+ const tailscaleServeOptions = {
915
+ maxBuffer: 1024 * 1024,
916
+ timeout: 15e3
917
+ };
918
+ const tailscaleServeUrlPattern = /https:\/\/[^\s]+\.ts\.net\b[^\s]*/;
919
+ function parseTailscaleServePreviewUrl(url) {
920
+ let parsedUrl;
921
+ try {
922
+ parsedUrl = new URL(url);
923
+ } catch {
924
+ throw new TailscaleServeInvalidUrlError("Preview URL must be a valid URL.");
925
+ }
926
+ if (parsedUrl.protocol !== "http:") throw new TailscaleServeInvalidUrlError("Preview URL must use HTTP.");
927
+ if (!parsedUrl.port) throw new TailscaleServeInvalidUrlError("Preview URL must include an explicit port.");
928
+ const port = Number(parsedUrl.port);
929
+ if (!Number.isInteger(port) || port < 1 || port > 65535) throw new TailscaleServeInvalidUrlError("Preview URL port must be between 1 and 65535.");
930
+ if (!isAllowedTailscaleHost(parsedUrl.hostname)) throw new TailscaleServeInvalidUrlError("Preview URL must use a Tailscale 100.64.0.0/10 or .ts.net host.");
931
+ return {
932
+ port,
933
+ sourceUrl: parsedUrl.href
934
+ };
935
+ }
936
+ async function startTailscaleServeForPreviewUrl(input) {
937
+ const preview = parseTailscaleServePreviewUrl(input.url);
938
+ const execFile = input.execFile ?? defaultExecFile;
939
+ let result;
940
+ try {
941
+ result = await execFile("tailscale", [
942
+ "serve",
943
+ "--bg",
944
+ String(preview.port)
945
+ ], tailscaleServeOptions);
946
+ } catch (error) {
947
+ throw new TailscaleServeCommandError(errorMessage$1(error));
948
+ }
949
+ const serveUrl = `${result.stdout}\n${result.stderr}`.match(tailscaleServeUrlPattern)?.[0];
950
+ if (!serveUrl) throw new TailscaleServeCommandError("Tailscale Serve did not report a .ts.net HTTPS URL.");
951
+ return {
952
+ port: preview.port,
953
+ url: serveUrl
954
+ };
955
+ }
956
+ async function defaultExecFile(file, args, options) {
957
+ const result = await execFileAsync$1(file, [...args], options);
958
+ return {
959
+ stderr: result.stderr,
960
+ stdout: result.stdout
961
+ };
962
+ }
963
+ function isAllowedTailscaleHost(hostname) {
964
+ const lowerHostname = hostname.toLowerCase();
965
+ return lowerHostname.endsWith(".ts.net") || isTailscaleIpv4Host(lowerHostname);
966
+ }
967
+ function isTailscaleIpv4Host(hostname) {
968
+ const parts = hostname.split(".");
969
+ if (parts.length !== 4) return false;
970
+ const octets = parts.map((part) => Number(part));
971
+ if (!octets.every((octet) => Number.isInteger(octet) && octet >= 0 && octet <= 255)) return false;
972
+ const first = octets[0];
973
+ const second = octets[1];
974
+ return first === 100 && second !== void 0 && second >= 64 && second <= 127;
975
+ }
976
+ function errorMessage$1(error) {
977
+ return error instanceof Error ? error.message : "Tailscale Serve command failed.";
978
+ }
979
+ //#endregion
980
+ //#region src/workspace-terminal-shell.ts
981
+ function resolveWorkspaceTerminalShell(input) {
982
+ const env = input?.env ?? process.env;
983
+ if ((input?.platform ?? process.platform) === "win32") return {
984
+ args: [],
985
+ command: shellEnvValue(env.COMSPEC) || "powershell.exe"
986
+ };
987
+ return {
988
+ args: ["-l"],
989
+ command: shellEnvValue(env.SHELL) || "/bin/sh"
990
+ };
991
+ }
992
+ function shellEnvValue(value) {
993
+ return value?.trim() ?? "";
994
+ }
995
+ //#endregion
831
996
  //#region src/app.ts
832
997
  const defaultWorkspacePath = process.cwd();
833
998
  const defaultCodexModel = "gpt-5.5";
@@ -1222,6 +1387,19 @@ function createApp(options = {}) {
1222
1387
  return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError("workspace_terminal_start_failed", errorMessage(error)), 400);
1223
1388
  }
1224
1389
  });
1390
+ app.post(apiPaths.workspaceTailscaleServe, async (c) => {
1391
+ const parsed = await parseRequestJson(c, options.pairing, secureSessionsByTokenHash, WorkspaceTailscaleServeRequestSchema);
1392
+ if (!parsed.success) return secureJson(c, options.pairing, secureSessionsByTokenHash, validationError(parsed.error), 400);
1393
+ try {
1394
+ const serve = await (options.tailscaleServeForPreviewUrl ?? startTailscaleServeForPreviewUrl)({ url: parsed.data.url });
1395
+ const response = WorkspaceTailscaleServeResponseSchema.parse(serve);
1396
+ return secureJson(c, options.pairing, secureSessionsByTokenHash, response);
1397
+ } catch (error) {
1398
+ const status = error instanceof TailscaleServeInvalidUrlError ? 400 : 502;
1399
+ const code = error instanceof TailscaleServeInvalidUrlError ? "invalid_tailscale_preview_url" : "tailscale_serve_failed";
1400
+ return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError(code, errorMessage(error)), status);
1401
+ }
1402
+ });
1225
1403
  app.get("/v1/workspace/terminal/sessions/:sessionId/output", async (c) => {
1226
1404
  const session = workspaceTerminalSessions.get(c.req.param("sessionId"));
1227
1405
  if (!session) return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError("workspace_terminal_not_found", "Terminal session was not found."), 404);
@@ -5532,8 +5710,8 @@ async function git(cwd, args) {
5532
5710
  function createWorkspaceTerminalSession(input) {
5533
5711
  const sessionId = randomUUID();
5534
5712
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
5535
- const shell = process.env.SHELL || "/bin/sh";
5536
- const child = pty.spawn(shell, ["-l"], {
5713
+ const shell = resolveWorkspaceTerminalShell();
5714
+ const child = pty.spawn(shell.command, [...shell.args], {
5537
5715
  cols: input.cols,
5538
5716
  cwd: input.cwd,
5539
5717
  env: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-relay",
3
- "version": "1.2.2",
3
+ "version": "1.2.4",
4
4
  "description": "Local Codex Relay CLI bridge for the Codex Relay mobile app.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
package/src/api-schema.ts CHANGED
@@ -464,6 +464,15 @@ export const WorkspaceTerminalResizeRequestSchema = z.object({
464
464
  rows: z.number().int().min(2).max(120),
465
465
  });
466
466
 
467
+ export const WorkspaceTailscaleServeRequestSchema = z.object({
468
+ url: z.string().url(),
469
+ });
470
+
471
+ export const WorkspaceTailscaleServeResponseSchema = z.object({
472
+ port: z.number().int().positive(),
473
+ url: z.string().url(),
474
+ });
475
+
467
476
  export const PairRequestSchema = z.object({
468
477
  clientSessionId: z.string().trim().min(1).max(120).optional(),
469
478
  clientName: z.string().trim().min(1).max(80).optional(),
@@ -762,6 +771,8 @@ export type WorkspaceTerminalSessionResponse = z.infer<
762
771
  export type WorkspaceTerminalOutputResponse = z.infer<typeof WorkspaceTerminalOutputResponseSchema>;
763
772
  export type WorkspaceTerminalInputRequest = z.infer<typeof WorkspaceTerminalInputRequestSchema>;
764
773
  export type WorkspaceTerminalResizeRequest = z.infer<typeof WorkspaceTerminalResizeRequestSchema>;
774
+ export type WorkspaceTailscaleServeRequest = z.infer<typeof WorkspaceTailscaleServeRequestSchema>;
775
+ export type WorkspaceTailscaleServeResponse = z.infer<typeof WorkspaceTailscaleServeResponseSchema>;
765
776
  export type WebPreviewTarget = z.infer<typeof WebPreviewTargetSchema>;
766
777
  export type PairRequest = z.infer<typeof PairRequestSchema>;
767
778
  export type PairResponse = z.infer<typeof PairResponseSchema>;
@@ -1024,6 +1035,7 @@ export const apiPaths = {
1024
1035
  `/v1/workspace/terminal/sessions/${encodeURIComponent(sessionId)}/output/stream`,
1025
1036
  workspaceTerminalResize: (sessionId: string) =>
1026
1037
  `/v1/workspace/terminal/sessions/${encodeURIComponent(sessionId)}/resize`,
1038
+ workspaceTailscaleServe: "/v1/workspace/tailscale/serve",
1027
1039
  imageAttachments: "/v1/attachments/images",
1028
1040
  imageAttachment: (attachmentId: string) =>
1029
1041
  `/v1/attachments/images/${encodeURIComponent(attachmentId)}`,
@@ -1170,6 +1182,17 @@ export function createOpenApiDocument() {
1170
1182
  },
1171
1183
  },
1172
1184
  },
1185
+ "/v1/workspace/tailscale/serve": {
1186
+ post: {
1187
+ summary: "Start Tailscale Serve for a workspace preview URL",
1188
+ requestBody: jsonRequest("WorkspaceTailscaleServeRequest"),
1189
+ responses: {
1190
+ "200": jsonResponse("WorkspaceTailscaleServeResponse"),
1191
+ "400": jsonResponse("ErrorResponse"),
1192
+ "502": jsonResponse("ErrorResponse"),
1193
+ },
1194
+ },
1195
+ },
1173
1196
  "/v1/threads/{threadId}/runs": {
1174
1197
  post: {
1175
1198
  summary: "Run a prompt on a Codex thread",
@@ -1528,6 +1551,21 @@ export function createOpenApiDocument() {
1528
1551
  workspacePath: { type: "string" },
1529
1552
  },
1530
1553
  },
1554
+ WorkspaceTailscaleServeRequest: {
1555
+ type: "object",
1556
+ required: ["url"],
1557
+ properties: {
1558
+ url: { type: "string", format: "uri" },
1559
+ },
1560
+ },
1561
+ WorkspaceTailscaleServeResponse: {
1562
+ type: "object",
1563
+ required: ["url", "port"],
1564
+ properties: {
1565
+ url: { type: "string", format: "uri" },
1566
+ port: { type: "integer", minimum: 1 },
1567
+ },
1568
+ },
1531
1569
  CreateThreadRequest: {
1532
1570
  type: "object",
1533
1571
  properties: {