@shipers-dev/dayline 0.95.1 → 0.97.0

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.
Files changed (2) hide show
  1. package/dist/index.js +160 -6
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -45260,7 +45260,7 @@ import { parseArgs } from "util";
45260
45260
  // package.json
45261
45261
  var package_default = {
45262
45262
  name: "@shipers-dev/dayline",
45263
- version: "0.95.1",
45263
+ version: "0.97.0",
45264
45264
  type: "module",
45265
45265
  bin: {
45266
45266
  dayline: "./dist/index.js"
@@ -46573,6 +46573,7 @@ function buildProjectTree(args2) {
46573
46573
  }
46574
46574
 
46575
46575
  // src/_impl/daemon-main.ts
46576
+ init_paths();
46576
46577
  init_errors();
46577
46578
 
46578
46579
  // src/_impl/funnel.ts
@@ -46673,12 +46674,116 @@ function parseChatMessageBody(raw) {
46673
46674
  return { ok: true, value: { chat_id: chatId, text } };
46674
46675
  }
46675
46676
 
46677
+ // src/_impl/api-proxy.ts
46678
+ //! The daemon's forwarder to the worker API.
46679
+ //!
46680
+ //! Native clients (the desktop app) never reach the worker themselves: every
46681
+ //! call they make rides `/api/**` on the local daemon, so the only host the
46682
+ //! app connects to is 127.0.0.1 and exactly one place — here — decides what
46683
+ //! leaves the machine. Split out of daemon-main so the allowlist is readable
46684
+ //! on its own and testable without standing up a server.
46685
+ var API_PROXY_ROUTES = [
46686
+ { method: "GET", path: "/api/auth/me" },
46687
+ { method: "GET", path: "/api/workspaces/:ws/agents" },
46688
+ { method: "GET", path: "/api/workspaces/:ws/devices" },
46689
+ { method: "GET", path: "/api/workspaces/:ws/skills" },
46690
+ { method: "GET", path: "/api/workspaces/:ws/projects" },
46691
+ { method: "GET", path: "/api/workspaces/:ws/projects/:project/chats" },
46692
+ { method: "POST", path: "/api/workspaces/:ws/projects/:project/chats" },
46693
+ { method: "GET", path: "/api/workspaces/:ws/chats/:chat" },
46694
+ { method: "PATCH", path: "/api/workspaces/:ws/chats/:chat" },
46695
+ { method: "DELETE", path: "/api/workspaces/:ws/chats/:chat" },
46696
+ { method: "GET", path: "/api/workspaces/:ws/chats/:chat/snapshot" },
46697
+ { method: "POST", path: "/api/workspaces/:ws/agent/chats/query" },
46698
+ { method: "GET", path: "/api/workspaces/:ws/projects/:project/issues" },
46699
+ { method: "POST", path: "/api/workspaces/:ws/projects/:project/issues" },
46700
+ { method: "GET", path: "/api/workspaces/:ws/projects/:project/issues/:issue" },
46701
+ { method: "PATCH", path: "/api/workspaces/:ws/projects/:project/issues/:issue" },
46702
+ { method: "DELETE", path: "/api/workspaces/:ws/projects/:project/issues/:issue" },
46703
+ { method: "POST", path: "/api/workspaces/:ws/projects/:project/issues/:issue/complete" },
46704
+ { method: "POST", path: "/api/workspaces/:ws/projects/:project/issues/:issue/fail" },
46705
+ { method: "POST", path: "/api/workspaces/:ws/projects/:project/issues/:issue/stop" },
46706
+ { method: "GET", path: "/api/workspaces/:ws/projects/:project/issues/:issue/dispatches" },
46707
+ { method: "GET", path: "/api/workspaces/:ws/projects/:project/issues/:issue/activity" },
46708
+ { method: "GET", path: "/api/workspaces/:ws/projects/:project/devices" },
46709
+ { method: "PATCH", path: "/api/workspaces/:ws/projects/:project/devices/:device" },
46710
+ { method: "POST", path: "/api/workspaces/:ws/projects/:project/devices/:device/prepare-working-dir" },
46711
+ { method: "POST", path: "/api/workspaces/:ws/projects/:project/devices/:device/worktree" },
46712
+ { method: "GET", path: "/api/devices/:device/fs/*" }
46713
+ ];
46714
+ var API_PROXY_MATCHERS = API_PROXY_ROUTES.map(({ method, path }) => ({
46715
+ method,
46716
+ re: new RegExp(`^${path.split("/").map((seg) => seg.startsWith(":") ? "[^/]+" : seg === "*" ? ".+" : seg.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("/")}$`)
46717
+ }));
46718
+ function apiProxyAllows(method, pathname) {
46719
+ return API_PROXY_MATCHERS.some((r) => r.method === method && r.re.test(pathname));
46720
+ }
46721
+ var HOP_BY_HOP = new Set([
46722
+ "host",
46723
+ "connection",
46724
+ "keep-alive",
46725
+ "proxy-authenticate",
46726
+ "proxy-authorization",
46727
+ "te",
46728
+ "trailer",
46729
+ "transfer-encoding",
46730
+ "upgrade",
46731
+ "content-length",
46732
+ "authorization",
46733
+ "origin",
46734
+ "referer",
46735
+ "accept-encoding"
46736
+ ]);
46737
+ function makeApiProxy(apiUrl, session) {
46738
+ return async function proxyToApi(req, url2) {
46739
+ const userSession = session();
46740
+ if (!userSession) {
46741
+ return Response.json({
46742
+ ok: false,
46743
+ code: "no_device_session",
46744
+ message: "this device has no user session — re-pair it with `dayline connect`"
46745
+ }, { status: 401 });
46746
+ }
46747
+ const target = new URL(`${apiUrl.replace(/\/+$/, "")}${url2.pathname}`);
46748
+ target.search = url2.search;
46749
+ target.searchParams.delete("token");
46750
+ const headers = new Headers;
46751
+ for (const [k, v] of req.headers) {
46752
+ if (!HOP_BY_HOP.has(k.toLowerCase()))
46753
+ headers.set(k, v);
46754
+ }
46755
+ headers.set("authorization", `Bearer ${userSession}`);
46756
+ const method = req.method.toUpperCase();
46757
+ const body = method === "GET" || method === "HEAD" ? undefined : await req.arrayBuffer();
46758
+ let upstream;
46759
+ try {
46760
+ upstream = await fetch(target.toString(), {
46761
+ method,
46762
+ headers,
46763
+ body,
46764
+ redirect: "manual",
46765
+ signal: AbortSignal.timeout(60000)
46766
+ });
46767
+ } catch (e) {
46768
+ return Response.json({ ok: false, code: "upstream_unreachable", message: String(e?.message || e) }, { status: 502 });
46769
+ }
46770
+ const bytes = await upstream.arrayBuffer();
46771
+ const out = new Headers;
46772
+ for (const name of ["content-type", "cache-control", "etag", "location"]) {
46773
+ const v = upstream.headers.get(name);
46774
+ if (v)
46775
+ out.set(name, v);
46776
+ }
46777
+ return new Response(bytes, { status: upstream.status, headers: out });
46778
+ };
46779
+ }
46780
+
46676
46781
  // src/_impl/daemon-main.ts
46677
46782
  init_lib();
46678
46783
  // package.json
46679
46784
  var package_default2 = {
46680
46785
  name: "@shipers-dev/dayline",
46681
- version: "0.95.1",
46786
+ version: "0.97.0",
46682
46787
  type: "module",
46683
46788
  bin: {
46684
46789
  dayline: "./dist/index.js"
@@ -46943,7 +47048,7 @@ var daemonProgram = ({ cfg, apiUrl }) => exports_Effect.gen(function* () {
46943
47048
  let projectTreeCache = null;
46944
47049
  const corsHeaders = (origin) => ({
46945
47050
  "Access-Control-Allow-Origin": origin || "*",
46946
- "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
47051
+ "Access-Control-Allow-Methods": "GET, POST, PATCH, DELETE, OPTIONS",
46947
47052
  "Access-Control-Allow-Headers": "Authorization, Content-Type",
46948
47053
  "Access-Control-Max-Age": "600",
46949
47054
  Vary: "Origin"
@@ -46959,6 +47064,35 @@ var daemonProgram = ({ cfg, apiUrl }) => exports_Effect.gen(function* () {
46959
47064
  }
46960
47065
  return null;
46961
47066
  };
47067
+ let sessionToken = cfg.sessionToken ?? null;
47068
+ const proxyToApi = makeApiProxy(apiUrl, () => sessionToken);
47069
+ const sessionRoute = async (req) => {
47070
+ if (req.method !== "GET" && req.method !== "DELETE") {
47071
+ return Response.json({ error: "method not allowed" }, { status: 405 });
47072
+ }
47073
+ if (req.method === "DELETE") {
47074
+ sessionToken = null;
47075
+ try {
47076
+ const current = JSON.parse(await Bun.file(CONFIG_PATH).text());
47077
+ delete current.sessionToken;
47078
+ await Bun.write(CONFIG_PATH, JSON.stringify(current, null, 2));
47079
+ } catch (e) {
47080
+ log3(`session: could not persist sign-out: ${e}`);
47081
+ }
47082
+ return Response.json({ signed_in: false });
47083
+ }
47084
+ if (!sessionToken) {
47085
+ return Response.json({ signed_in: false, reason: "no_device_session" });
47086
+ }
47087
+ const me = await proxyToApi(new Request(`http://127.0.0.1/api/auth/me`, { headers: req.headers }), new URL("http://127.0.0.1/api/auth/me"));
47088
+ if (me.status === 401) {
47089
+ return Response.json({ signed_in: false, reason: "session_expired" });
47090
+ }
47091
+ if (!me.ok) {
47092
+ return Response.json({ signed_in: false, reason: "api_unreachable" }, { status: 502 });
47093
+ }
47094
+ return Response.json({ signed_in: true, me: await me.json() });
47095
+ };
46962
47096
  const withCors = (res, origin) => {
46963
47097
  for (const [k, v] of Object.entries(corsHeaders(origin))) {
46964
47098
  res.headers.set(k, v);
@@ -47005,6 +47139,21 @@ var daemonProgram = ({ cfg, apiUrl }) => exports_Effect.gen(function* () {
47005
47139
  const route = () => {
47006
47140
  if (url2.pathname === "/health")
47007
47141
  return Response.json({ ok: true, device_id: cfg.deviceId, cli_version: CLI_VERSION });
47142
+ if (url2.pathname === "/session") {
47143
+ const denied = requireLocalAuth(req, url2, { allowQueryToken: true });
47144
+ if (denied)
47145
+ return denied;
47146
+ return sessionRoute(req);
47147
+ }
47148
+ if (url2.pathname === "/api" || url2.pathname.startsWith("/api/")) {
47149
+ const denied = requireLocalAuth(req, url2, { allowQueryToken: true });
47150
+ if (denied)
47151
+ return denied;
47152
+ if (!apiProxyAllows(req.method.toUpperCase(), url2.pathname)) {
47153
+ return Response.json({ ok: false, code: "route_not_proxied", message: `${req.method} ${url2.pathname} is not a proxied API route` }, { status: 404 });
47154
+ }
47155
+ return proxyToApi(req, url2);
47156
+ }
47008
47157
  if (url2.pathname === "/files" && req.method === "GET") {
47009
47158
  if (req.headers.get("authorization") !== expectedAuth)
47010
47159
  return new Response("unauthorized", { status: 401 });
@@ -48135,7 +48284,8 @@ async function awaitPairing(apiUrl, deviceName, opts = {}) {
48135
48284
  device_id: j.device_id,
48136
48285
  token: j.token,
48137
48286
  dispatch_secret: j.dispatch_secret ?? null,
48138
- workspace_id: j.workspace_id ?? null
48287
+ workspace_id: j.workspace_id ?? null,
48288
+ session: j.session ?? null
48139
48289
  };
48140
48290
  }
48141
48291
  }
@@ -48154,7 +48304,7 @@ var connectCmd = exports_Effect.fn("connectCmd")(function* () {
48154
48304
  const api2 = yield* Api2;
48155
48305
  yield* config2.ensureDirs;
48156
48306
  let cfg = yield* config2.load;
48157
- if (!cfg.deviceId || !cfg.authToken) {
48307
+ if (!cfg.deviceId || !cfg.authToken || !cfg.sessionToken) {
48158
48308
  const apiUrl = cfg.apiUrl || process.env.MULTI_API || process.env.MULTI_API_URL || "https://api.dayline.ai";
48159
48309
  const name = deviceNameFromEnv();
48160
48310
  const detected = yield* exports_Effect.promise(() => detectAgents());
@@ -48171,10 +48321,14 @@ var connectCmd = exports_Effect.fn("connectCmd")(function* () {
48171
48321
  deviceId: bundle.device_id,
48172
48322
  authToken: bundle.token,
48173
48323
  dispatchSecret: bundle.dispatch_secret ?? undefined,
48174
- workspaceId: bundle.workspace_id ?? undefined
48324
+ workspaceId: bundle.workspace_id ?? undefined,
48325
+ sessionToken: bundle.session ?? undefined
48175
48326
  });
48176
48327
  cfg = yield* config2.load;
48177
48328
  yield* logger.log(`connect: paired device=${cfg.deviceId} workspace=${cfg.workspaceId}`);
48329
+ if (!cfg.sessionToken) {
48330
+ yield* logger.log("connect: this API did not issue a device session — desktop clients will stay signed out");
48331
+ }
48178
48332
  }
48179
48333
  if (!cfg.dispatchSecret) {
48180
48334
  return yield* exports_Effect.fail(new UsageError({ message: "Missing dispatch secret. Re-pair via 'dayline setup'." }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipers-dev/dayline",
3
- "version": "0.95.1",
3
+ "version": "0.97.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "dayline": "./dist/index.js"