@akshar5/cohall 0.3.4 → 0.4.1

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/bin/cohall.js CHANGED
@@ -17,7 +17,7 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
17
17
 
18
18
  // packages/protocol/src/index.ts
19
19
  import { Schema } from "effect";
20
- var version = "0.3.4", bounded = (maxLength) => Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(maxLength)), optionalText = (maxLength) => Schema.String.check(Schema.isMaxLength(maxLength)), boundedArray = (schema, maxLength) => Schema.Array(schema).check(Schema.isMaxLength(maxLength)), uuid = (name) => Schema.String.check(Schema.isUUID(4)).pipe(Schema.brand(name)), isoTimestamp = (name) => Schema.String.check(Schema.isPattern(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?Z$/, {
20
+ var version = "0.4.1", bounded = (maxLength) => Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(maxLength)), optionalText = (maxLength) => Schema.String.check(Schema.isMaxLength(maxLength)), boundedArray = (schema, maxLength) => Schema.Array(schema).check(Schema.isMaxLength(maxLength)), uuid = (name) => Schema.String.check(Schema.isUUID(4)).pipe(Schema.brand(name)), isoTimestamp = (name) => Schema.String.check(Schema.isPattern(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?Z$/, {
21
21
  expected: "an ISO-8601 UTC timestamp"
22
22
  })).pipe(Schema.brand(name)), DeviceId, ThreadId, MessageId, TaskId, AuthSessionId, Timestamp, Platform, Provider, DeviceStatus, TaskStatus, TaskTraceEventKind, MessageRole, ConnectionRole, AuthSession, CreatePairingInput, PairingCredential, ExchangePairingInput, SessionCredential, PairingResult, Capability, Workspace, Device, Thread, Message, Task, TaskTraceEvent, TaskTrace, ThreadContext, CreateTaskInput, SocketEvent, ErrorResponse, terminalTaskStatuses, isTerminalTask = (task) => terminalTaskStatuses.has(task.status), now = () => Timestamp.make(new Date().toISOString()), makeDeviceId = () => DeviceId.make(crypto.randomUUID()), makeThreadId = () => ThreadId.make(crypto.randomUUID()), makeMessageId = () => MessageId.make(crypto.randomUUID()), makeTaskId = () => TaskId.make(crypto.randomUUID()), makeAuthSessionId = () => AuthSessionId.make(crypto.randomUUID()), decodeCreatePairingInput, decodeExchangePairingInput, decodeCreateTaskInput, decodeDevice, decodeSocketEvent;
23
23
  var init_src = __esm(() => {
@@ -201,27 +201,27 @@ var init_src = __esm(() => {
201
201
  });
202
202
 
203
203
  // apps/relay/src/config.ts
204
- import { Effect as Effect8, Schema as Schema8 } from "effect";
205
- import { access as access3, chmod as chmod2, mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
204
+ import { Effect as Effect8, Schema as Schema9 } from "effect";
205
+ import { access as access3, chmod as chmod4, mkdir as mkdir4, readFile as readFile4, writeFile as writeFile5 } from "node:fs/promises";
206
206
  import { homedir as homedir4, platform as platform4 } from "node:os";
207
- import { join as join4, resolve as resolve2 } from "node:path";
207
+ import { join as join5, resolve as resolve2 } from "node:path";
208
208
  var RelayConfiguration, defaultDataDirectory = () => {
209
209
  if (platform4() === "win32") {
210
- return join4(process.env.LOCALAPPDATA ?? join4(homedir4(), "AppData", "Local"), "Cohall");
210
+ return join5(process.env.LOCALAPPDATA ?? join5(homedir4(), "AppData", "Local"), "Cohall");
211
211
  }
212
212
  if (platform4() === "darwin") {
213
- return join4(homedir4(), "Library", "Application Support", "Cohall", "relay");
213
+ return join5(homedir4(), "Library", "Application Support", "Cohall", "relay");
214
214
  }
215
- return join4(process.env.XDG_DATA_HOME ?? join4(homedir4(), ".local", "share"), "cohall");
215
+ return join5(process.env.XDG_DATA_HOME ?? join5(homedir4(), ".local", "share"), "cohall");
216
216
  }, generatedToken = () => Array.from(crypto.getRandomValues(new Uint8Array(32)), (value) => value.toString(16).padStart(2, "0")).join(""), tokenFrom = async (path) => {
217
217
  if (await access3(path).then(() => true).catch(() => false)) {
218
- return (await readFile3(path, "utf8")).trim();
218
+ return (await readFile4(path, "utf8")).trim();
219
219
  }
220
220
  const token = generatedToken();
221
- await writeFile3(path, `${token}
221
+ await writeFile5(path, `${token}
222
222
  `, { mode: 384 });
223
223
  if (platform4() !== "win32") {
224
- await chmod2(path, 384);
224
+ await chmod4(path, 384);
225
225
  }
226
226
  return token;
227
227
  }, booleanEnvironment = (name) => {
@@ -235,13 +235,13 @@ var RelayConfiguration, defaultDataDirectory = () => {
235
235
  throw new Error(`${name} must be true or false`);
236
236
  }, isLoopback = (host) => host === "127.0.0.1" || host === "localhost" || host === "::1", loadEnvironmentConfiguration;
237
237
  var init_config = __esm(() => {
238
- RelayConfiguration = Schema8.Struct({
239
- host: Schema8.NonEmptyString,
240
- port: Schema8.Int.check(Schema8.isBetween({ minimum: 1, maximum: 65535 })),
241
- token: Schema8.NonEmptyString,
242
- dataDirectory: Schema8.NonEmptyString,
243
- databasePath: Schema8.NonEmptyString,
244
- allowRemote: Schema8.Boolean
238
+ RelayConfiguration = Schema9.Struct({
239
+ host: Schema9.NonEmptyString,
240
+ port: Schema9.Int.check(Schema9.isBetween({ minimum: 1, maximum: 65535 })),
241
+ token: Schema9.NonEmptyString,
242
+ dataDirectory: Schema9.NonEmptyString,
243
+ databasePath: Schema9.NonEmptyString,
244
+ allowRemote: Schema9.Boolean
245
245
  });
246
246
  loadEnvironmentConfiguration = Effect8.tryPromise({
247
247
  try: async () => {
@@ -252,17 +252,17 @@ var init_config = __esm(() => {
252
252
  throw new Error("Remote relay binding is disabled. Use a private network or TLS reverse proxy, then set COHALL_RELAY_ALLOW_REMOTE=true.");
253
253
  }
254
254
  const dataDirectory = resolve2(process.env.COHALL_DATA_DIR ?? defaultDataDirectory());
255
- await mkdir3(dataDirectory, { recursive: true, mode: 448 });
255
+ await mkdir4(dataDirectory, { recursive: true, mode: 448 });
256
256
  if (platform4() !== "win32") {
257
- await chmod2(dataDirectory, 448);
257
+ await chmod4(dataDirectory, 448);
258
258
  }
259
- const token = process.env.COHALL_TOKEN ?? await tokenFrom(join4(dataDirectory, "owner-token"));
260
- return Schema8.decodeUnknownSync(RelayConfiguration)({
259
+ const token = process.env.COHALL_TOKEN ?? await tokenFrom(join5(dataDirectory, "owner-token"));
260
+ return Schema9.decodeUnknownSync(RelayConfiguration)({
261
261
  host,
262
262
  port,
263
263
  token,
264
264
  dataDirectory,
265
- databasePath: join4(dataDirectory, "cohall.db"),
265
+ databasePath: join5(dataDirectory, "cohall.db"),
266
266
  allowRemote
267
267
  });
268
268
  },
@@ -408,12 +408,12 @@ __export(exports_store, {
408
408
  RelayStore: () => exports_store,
409
409
  PersistenceError: () => PersistenceError
410
410
  });
411
- import { Context as Context2, Effect as Effect9, Layer as Layer2, Schema as Schema9 } from "effect";
411
+ import { Context as Context2, Effect as Effect9, Layer as Layer2, Schema as Schema10 } from "effect";
412
412
  import { createHash, randomBytes } from "node:crypto";
413
413
  var PersistenceError, Service2, operationError = (operation) => (cause) => new PersistenceError({
414
414
  operation,
415
415
  message: cause instanceof Error ? cause.message : String(cause)
416
- }), decode = (operation, schema, value) => Schema9.decodeUnknownEffect(schema)(value).pipe(Effect9.mapError(operationError(operation))), threadFromRow = (row) => decode("RelayStore.decodeThread", Thread, {
416
+ }), decode = (operation, schema, value) => Schema10.decodeUnknownEffect(schema)(value).pipe(Effect9.mapError(operationError(operation))), threadFromRow = (row) => decode("RelayStore.decodeThread", Thread, {
417
417
  id: row.id,
418
418
  title: row.title,
419
419
  createdAt: row.created_at,
@@ -463,7 +463,7 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
463
463
  kind: row.kind,
464
464
  at: row.created_at,
465
465
  ...row.detail === null ? {} : { detail: row.detail }
466
- }), rolesFromRow = (row) => Schema9.decodeUnknownSync(Schema9.Array(ConnectionRole))(JSON.parse(row.roles_json)), authSessionFromRow = (row) => {
466
+ }), rolesFromRow = (row) => Schema10.decodeUnknownSync(Schema10.Array(ConnectionRole))(JSON.parse(row.roles_json)), authSessionFromRow = (row) => {
467
467
  const roles = rolesFromRow(row);
468
468
  const role = roles[0];
469
469
  return role === undefined ? Effect9.fail(new PersistenceError({
@@ -904,7 +904,7 @@ var PersistenceError, Service2, operationError = (operation) => (cause) => new P
904
904
  if (pairing === null) {
905
905
  throw new Error("Pairing credential is invalid, expired, or already used");
906
906
  }
907
- const roles = Schema9.decodeUnknownSync(Schema9.Array(ConnectionRole))(JSON.parse(pairing.roles_json));
907
+ const roles = Schema10.decodeUnknownSync(Schema10.Array(ConnectionRole))(JSON.parse(pairing.roles_json));
908
908
  const deviceId = roles.includes("device") ? makeDeviceId() : undefined;
909
909
  const consumed = db.query("UPDATE auth_pairings SET used_at = ? WHERE token_hash = ? AND used_at IS NULL").run(timestamp, pairing.token_hash);
910
910
  if (consumed.changes !== 1) {
@@ -1075,7 +1075,7 @@ var init_store = __esm(() => {
1075
1075
  init_src();
1076
1076
  init_database();
1077
1077
  init_store();
1078
- PersistenceError = class PersistenceError extends Schema9.TaggedErrorClass()("RelayStore.PersistenceError", { operation: Schema9.String, message: Schema9.String }) {
1078
+ PersistenceError = class PersistenceError extends Schema10.TaggedErrorClass()("RelayStore.PersistenceError", { operation: Schema10.String, message: Schema10.String }) {
1079
1079
  };
1080
1080
  Service2 = class Service2 extends Context2.Service()("@cohall/RelayStore") {
1081
1081
  };
@@ -1086,9 +1086,9 @@ var exports_main = {};
1086
1086
  __export(exports_main, {
1087
1087
  runRelay: () => runRelay
1088
1088
  });
1089
- import { Effect as Effect10, ManagedRuntime, Schema as Schema10 } from "effect";
1089
+ import { Effect as Effect10, ManagedRuntime, Schema as Schema11 } from "effect";
1090
1090
  import { createHash as createHash2, timingSafeEqual } from "node:crypto";
1091
- import { access as access4, chmod as chmod3 } from "node:fs/promises";
1091
+ import { access as access4, chmod as chmod5 } from "node:fs/promises";
1092
1092
  import { createServer } from "node:http";
1093
1093
  import { platform as platform5 } from "node:os";
1094
1094
  import { WebSocketServer } from "ws";
@@ -1173,7 +1173,7 @@ Content-Length: 0\r
1173
1173
  }, body = (request, decode2) => Effect10.tryPromise({
1174
1174
  try: () => request.json(),
1175
1175
  catch: () => new RequestError({ status: 400, message: "Expected a JSON request body" })
1176
- }).pipe(Effect10.flatMap(decode2), Effect10.mapError((cause) => cause instanceof RequestError ? cause : new RequestError({ status: 400, message: String(cause).slice(0, 2048) }))), pathId = (schema, value) => Schema10.decodeUnknownEffect(schema)(value).pipe(Effect10.mapError(() => new RequestError({ status: 400, message: `Invalid identifier: ${value}` }))), scoreDevice = (device, input, sourceDeviceId) => {
1176
+ }).pipe(Effect10.flatMap(decode2), Effect10.mapError((cause) => cause instanceof RequestError ? cause : new RequestError({ status: 400, message: String(cause).slice(0, 2048) }))), pathId = (schema, value) => Schema11.decodeUnknownEffect(schema)(value).pipe(Effect10.mapError(() => new RequestError({ status: 400, message: `Invalid identifier: ${value}` }))), scoreDevice = (device, input, sourceDeviceId) => {
1177
1177
  const prompt = `${input.prompt} ${input.context ?? ""}`.toLowerCase();
1178
1178
  const needsXcode = /\b(xcode|ios|macos|swiftui|app store)\b/.test(prompt);
1179
1179
  const needsBrowser = /\b(browser|chrome|youtube|twitter|logged[- ]?in|signed[- ]?in|x\.com)\b/.test(prompt);
@@ -1216,7 +1216,7 @@ Content-Length: 0\r
1216
1216
  yield* store.recover();
1217
1217
  }));
1218
1218
  if (platform5() !== "win32" && await access4(configuration.databasePath).then(() => true).catch(() => false)) {
1219
- await chmod3(configuration.databasePath, 384);
1219
+ await chmod5(configuration.databasePath, 384);
1220
1220
  }
1221
1221
  const hub = new Hub;
1222
1222
  let connections = 0;
@@ -1570,9 +1570,9 @@ var init_main = __esm(async () => {
1570
1570
  init_config();
1571
1571
  init_hub();
1572
1572
  init_store();
1573
- RequestError = class RequestError extends Schema10.TaggedErrorClass()("Relay.RequestError", {
1574
- status: Schema10.Int,
1575
- message: Schema10.String
1573
+ RequestError = class RequestError extends Schema11.TaggedErrorClass()("Relay.RequestError", {
1574
+ status: Schema11.Int,
1575
+ message: Schema11.String
1576
1576
  }) {
1577
1577
  };
1578
1578
  if (false) {}
@@ -1738,10 +1738,10 @@ init_src();
1738
1738
  init_src();
1739
1739
  import { Effect as Effect2, Schema as Schema3 } from "effect";
1740
1740
  import { accessSync, constants } from "node:fs";
1741
- import { homedir, platform } from "node:os";
1741
+ import { chmod, mkdtemp, rm, writeFile } from "node:fs/promises";
1742
+ import { homedir, platform, tmpdir } from "node:os";
1742
1743
  import { delimiter, extname, isAbsolute, join } from "node:path";
1743
1744
  import { spawn } from "node:child_process";
1744
- import { createInterface } from "node:readline";
1745
1745
 
1746
1746
  class ProviderUnavailableError extends Schema3.TaggedErrorClass()("CohallProvider.Unavailable", { provider: Provider, message: Schema3.String }) {
1747
1747
  }
@@ -1758,7 +1758,7 @@ var executables = {
1758
1758
  opencode: "opencode"
1759
1759
  };
1760
1760
  var providerEnvironment = () => ({
1761
- ...Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.startsWith("COHALL_"))),
1761
+ ...Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.startsWith("COHALL_") || name === "COHALL_CONFIG")),
1762
1762
  PATH: executableDirectories().join(delimiter)
1763
1763
  });
1764
1764
  var record = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? Object.fromEntries(Object.entries(value)) : undefined;
@@ -1879,42 +1879,90 @@ var claudeCommand = (options) => [
1879
1879
  ...options.model === undefined ? [] : ["--model", options.model],
1880
1880
  ...options.sessionId === undefined ? [] : ["--resume", options.sessionId]
1881
1881
  ];
1882
- var opencodeCommand = (options) => [
1882
+ var opencodeCommand = (options, promptPath) => [
1883
1883
  "opencode",
1884
1884
  "run",
1885
1885
  "--format",
1886
1886
  "json",
1887
1887
  ...options.model === undefined ? [] : ["--model", options.model],
1888
1888
  ...options.sessionId === undefined ? [] : ["--session", options.sessionId],
1889
- options.prompt
1889
+ "--file",
1890
+ promptPath,
1891
+ "Complete the task described in the attached Cohall prompt file."
1890
1892
  ];
1891
- var command = (options) => {
1893
+ var prepareCommand = async (options) => {
1892
1894
  switch (options.provider) {
1893
1895
  case "codex":
1894
- return codexCommand(options);
1896
+ return {
1897
+ command: codexCommand(options),
1898
+ input: options.prompt,
1899
+ cleanup: () => Promise.resolve()
1900
+ };
1895
1901
  case "claude-code":
1896
- return claudeCommand(options);
1897
- case "opencode":
1898
- return opencodeCommand(options);
1902
+ return {
1903
+ command: claudeCommand(options),
1904
+ input: options.prompt,
1905
+ cleanup: () => Promise.resolve()
1906
+ };
1907
+ case "opencode": {
1908
+ const directory = await mkdtemp(join(tmpdir(), "cohall-opencode-"));
1909
+ const promptPath = join(directory, "prompt.md");
1910
+ try {
1911
+ if (platform() !== "win32") {
1912
+ await chmod(directory, 448);
1913
+ }
1914
+ await writeFile(promptPath, options.prompt, { mode: 384 });
1915
+ return {
1916
+ command: opencodeCommand(options, promptPath),
1917
+ cleanup: () => rm(directory, { recursive: true, force: true })
1918
+ };
1919
+ } catch (cause) {
1920
+ await rm(directory, { recursive: true, force: true }).catch(() => {
1921
+ return;
1922
+ });
1923
+ throw cause;
1924
+ }
1925
+ }
1899
1926
  }
1900
1927
  };
1901
1928
  var forEachJsonEvent = async (stdout, onEvent) => {
1902
- const lines = createInterface({ input: stdout, crlfDelay: Infinity });
1903
- try {
1904
- for await (const line of lines) {
1905
- if (line.trim().length === 0) {
1906
- continue;
1907
- }
1908
- if (Buffer.byteLength(line) > 1024 * 1024) {
1909
- throw new Error("Provider event exceeded 1024 KiB");
1910
- }
1911
- const event = record(JSON.parse(line));
1912
- if (event !== undefined) {
1913
- onEvent(event);
1914
- }
1929
+ const limit = 1024 * 1024;
1930
+ let chunks = [];
1931
+ let size = 0;
1932
+ const append = (chunk) => {
1933
+ if (size + chunk.byteLength > limit) {
1934
+ throw new Error("Provider event exceeded 1024 KiB");
1915
1935
  }
1916
- } finally {
1917
- lines.close();
1936
+ if (chunk.byteLength > 0) {
1937
+ chunks.push(chunk);
1938
+ size += chunk.byteLength;
1939
+ }
1940
+ };
1941
+ const emit = () => {
1942
+ const line = Buffer.concat(chunks, size).toString("utf8");
1943
+ chunks = [];
1944
+ size = 0;
1945
+ if (line.trim().length === 0) {
1946
+ return;
1947
+ }
1948
+ const event = record(JSON.parse(line));
1949
+ if (event !== undefined) {
1950
+ onEvent(event);
1951
+ }
1952
+ };
1953
+ for await (const chunk of stdout) {
1954
+ const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
1955
+ let start = 0;
1956
+ for (let newline = bytes.indexOf(10, start);newline !== -1; ) {
1957
+ append(bytes.subarray(start, newline));
1958
+ emit();
1959
+ start = newline + 1;
1960
+ newline = bytes.indexOf(10, start);
1961
+ }
1962
+ append(bytes.subarray(start));
1963
+ }
1964
+ if (size > 0) {
1965
+ emit();
1918
1966
  }
1919
1967
  };
1920
1968
  var parseCodex = async (stdout, existingSession) => {
@@ -2002,14 +2050,15 @@ var parse = async (options, stdout, onFailure) => {
2002
2050
  var byteLength = (value) => new TextEncoder().encode(value).byteLength;
2003
2051
  var run = (options) => Effect2.tryPromise({
2004
2052
  try: async (signal) => {
2005
- const [commandName, ...arguments_] = command(options);
2006
- const executable = commandName === undefined ? undefined : findExecutable(commandName);
2053
+ const executable = findExecutable(executables[options.provider]);
2007
2054
  if (executable === undefined) {
2008
2055
  throw new ProviderUnavailableError({
2009
2056
  provider: options.provider,
2010
2057
  message: `${executables[options.provider]} is not available on this device`
2011
2058
  });
2012
2059
  }
2060
+ const prepared = await prepareCommand(options);
2061
+ const [, ...arguments_] = prepared.command;
2013
2062
  const child = spawn(executable, arguments_, {
2014
2063
  cwd: options.cwd,
2015
2064
  env: {
@@ -2037,36 +2086,46 @@ var run = (options) => Effect2.tryPromise({
2037
2086
  killTimer.unref();
2038
2087
  };
2039
2088
  signal.addEventListener("abort", terminate, { once: true });
2040
- child.stdin.end(options.provider === "opencode" ? undefined : options.prompt);
2041
- const [result, stderr, exitCode] = await Promise.all([
2042
- parse(options, child.stdout, terminate),
2043
- captureText(child.stderr, 64 * 1024),
2044
- exited
2045
- ]);
2046
- signal.removeEventListener("abort", terminate);
2047
- if (killTimer !== undefined && child.exitCode !== null) {
2048
- clearTimeout(killTimer);
2049
- }
2050
- if (exitCode !== 0) {
2051
- throw new ProviderRunError({
2052
- provider: options.provider,
2053
- message: failureMessage(executables[options.provider], stderr, exitCode),
2054
- exitCode
2055
- });
2056
- }
2057
- if (byteLength(result.result) > 131072) {
2058
- throw new ProviderRunError({
2059
- provider: options.provider,
2060
- message: "Provider result exceeded 128 KiB"
2061
- });
2062
- }
2063
- if (result.sessionId !== undefined && byteLength(result.sessionId) > 4096) {
2064
- throw new ProviderRunError({
2065
- provider: options.provider,
2066
- message: "Provider session ID exceeded 4 KiB"
2067
- });
2089
+ child.stdin.end(prepared.input);
2090
+ try {
2091
+ const [result, stderr, exitCode] = await Promise.all([
2092
+ parse(options, child.stdout, terminate),
2093
+ captureText(child.stderr, 64 * 1024),
2094
+ exited
2095
+ ]);
2096
+ if (exitCode !== 0) {
2097
+ throw new ProviderRunError({
2098
+ provider: options.provider,
2099
+ message: failureMessage(executables[options.provider], stderr, exitCode),
2100
+ exitCode
2101
+ });
2102
+ }
2103
+ if (byteLength(result.result) > 131072) {
2104
+ throw new ProviderRunError({
2105
+ provider: options.provider,
2106
+ message: "Provider result exceeded 128 KiB"
2107
+ });
2108
+ }
2109
+ if (result.sessionId !== undefined && byteLength(result.sessionId) > 4096) {
2110
+ throw new ProviderRunError({
2111
+ provider: options.provider,
2112
+ message: "Provider session ID exceeded 4 KiB"
2113
+ });
2114
+ }
2115
+ return result;
2116
+ } finally {
2117
+ signal.removeEventListener("abort", terminate);
2118
+ if (child.exitCode === null) {
2119
+ terminate();
2120
+ await exited.catch(() => {
2121
+ return;
2122
+ });
2123
+ }
2124
+ if (killTimer !== undefined) {
2125
+ clearTimeout(killTimer);
2126
+ }
2127
+ await prepared.cleanup();
2068
2128
  }
2069
- return result;
2070
2129
  },
2071
2130
  catch: (cause) => {
2072
2131
  if (cause instanceof ProviderUnavailableError || cause instanceof ProviderRunError) {
@@ -2080,10 +2139,10 @@ var run = (options) => Effect2.tryPromise({
2080
2139
  });
2081
2140
 
2082
2141
  // apps/device/src/cli.ts
2083
- import { Effect as Effect5, Schema as Schema6 } from "effect";
2084
- import { access as access2, mkdir as mkdir2, readFile as readFile2, stat, writeFile as writeFile2 } from "node:fs/promises";
2142
+ import { Effect as Effect5, Schema as Schema7 } from "effect";
2143
+ import { access as access2, mkdir as mkdir3, readFile as readFile3, stat, writeFile as writeFile4 } from "node:fs/promises";
2085
2144
  import { homedir as homedir3 } from "node:os";
2086
- import { dirname as dirname2, join as join3 } from "node:path";
2145
+ import { dirname as dirname3, join as join4 } from "node:path";
2087
2146
 
2088
2147
  // skills/cohall/SKILL.md
2089
2148
  var SKILL_default = `---
@@ -2222,7 +2281,7 @@ Return the diagnostic failure to the user rather than claiming remote work ran.
2222
2281
  // apps/device/src/config.ts
2223
2282
  init_src();
2224
2283
  import { Effect as Effect3, Schema as Schema4 } from "effect";
2225
- import { access, chmod, mkdir, readFile, realpath, rename, writeFile } from "node:fs/promises";
2284
+ import { access, chmod as chmod2, mkdir, readFile, realpath, rename, writeFile as writeFile2 } from "node:fs/promises";
2226
2285
  import { homedir as homedir2, hostname, platform as platform2 } from "node:os";
2227
2286
  import { dirname, join as join2, resolve } from "node:path";
2228
2287
  var WorkspaceList = Schema4.Array(Schema4.NonEmptyString).check(Schema4.isMaxLength(64));
@@ -2296,7 +2355,7 @@ var readStoredConfiguration = async () => {
2296
2355
  return;
2297
2356
  }
2298
2357
  if (platform2() !== "win32") {
2299
- await chmod(path, 384);
2358
+ await chmod2(path, 384);
2300
2359
  }
2301
2360
  try {
2302
2361
  return Schema4.decodeUnknownSync(StoredConfiguration)(JSON.parse(await readFile(path, "utf8")));
@@ -2310,24 +2369,35 @@ var writeStoredConfiguration = async (configuration) => {
2310
2369
  const temporary = `${path}.${process.pid}.tmp`;
2311
2370
  await mkdir(directory, { recursive: true, mode: 448 });
2312
2371
  if (platform2() !== "win32") {
2313
- await chmod(directory, 448);
2372
+ await chmod2(directory, 448);
2314
2373
  }
2315
- await writeFile(temporary, `${JSON.stringify(configuration, null, 2)}
2374
+ await writeFile2(temporary, `${JSON.stringify(configuration, null, 2)}
2316
2375
  `, { mode: 384 });
2317
2376
  await rename(temporary, path);
2318
2377
  if (platform2() !== "win32") {
2319
- await chmod(path, 384);
2378
+ await chmod2(path, 384);
2379
+ }
2380
+ };
2381
+ var credentialsForRelay = (configuration, relayUrl) => {
2382
+ if (configuration?.relayUrl !== normalizeRelayUrl(relayUrl)) {
2383
+ return {};
2320
2384
  }
2385
+ return {
2386
+ ...configuration.clientToken === undefined ? {} : { clientToken: configuration.clientToken },
2387
+ ...configuration.deviceToken === undefined ? {} : { deviceToken: configuration.deviceToken }
2388
+ };
2321
2389
  };
2322
2390
  var makeStoredConfiguration = async (input) => {
2323
2391
  const existing = await readStoredConfiguration();
2392
+ const relayUrl = normalizeRelayUrl(input.relayUrl);
2393
+ const retainedCredentials = credentialsForRelay(existing, relayUrl);
2324
2394
  const canonicalWorkspaces = input.workspaces === undefined ? existing?.workspaces ?? [] : await Effect3.runPromise(parseWorkspaces("", JSON.stringify(input.workspaces)));
2325
- const clientToken = input.clientToken ?? existing?.clientToken;
2326
- const deviceToken = input.deviceToken ?? existing?.deviceToken;
2395
+ const clientToken = input.clientToken ?? retainedCredentials.clientToken;
2396
+ const deviceToken = input.deviceToken ?? retainedCredentials.deviceToken;
2327
2397
  const providers = input.providers === "auto" ? undefined : input.providers ?? existing?.providers;
2328
2398
  return StoredConfiguration.make({
2329
2399
  version: 1,
2330
- relayUrl: normalizeRelayUrl(input.relayUrl),
2400
+ relayUrl,
2331
2401
  deviceId: existing?.deviceId ?? makeDeviceId(),
2332
2402
  deviceName: input.deviceName ?? existing?.deviceName ?? hostname(),
2333
2403
  workspaces: canonicalWorkspaces,
@@ -2368,12 +2438,13 @@ var storedOrDefaults = async () => {
2368
2438
  var loadClientConfiguration = Effect3.tryPromise({
2369
2439
  try: async () => {
2370
2440
  const stored = await storedOrDefaults();
2371
- const token = process.env.COHALL_CLIENT_TOKEN ?? stored.clientToken;
2441
+ const relayUrl = normalizeRelayUrl(process.env.COHALL_RELAY_URL ?? stored.relayUrl);
2442
+ const token = process.env.COHALL_CLIENT_TOKEN ?? credentialsForRelay(stored, relayUrl).clientToken;
2372
2443
  if (token === undefined) {
2373
2444
  throw new Error(`No client credential. Run cohall join with a pairing token on stdin or set COHALL_CLIENT_TOKEN.`);
2374
2445
  }
2375
2446
  return ClientConfiguration.make({
2376
- relayUrl: normalizeRelayUrl(process.env.COHALL_RELAY_URL ?? stored.relayUrl),
2447
+ relayUrl,
2377
2448
  token,
2378
2449
  ...process.env.COHALL_THREAD_ID === undefined ? {} : { mcpThreadId: process.env.COHALL_THREAD_ID }
2379
2450
  });
@@ -2397,7 +2468,8 @@ var loadOwnerConfiguration = Effect3.tryPromise({
2397
2468
  var loadDeviceConfiguration = Effect3.tryPromise({
2398
2469
  try: async () => {
2399
2470
  const stored = await storedOrDefaults();
2400
- const token = process.env.COHALL_DEVICE_TOKEN ?? stored.deviceToken;
2471
+ const relayUrl = normalizeRelayUrl(process.env.COHALL_RELAY_URL ?? stored.relayUrl);
2472
+ const token = process.env.COHALL_DEVICE_TOKEN ?? credentialsForRelay(stored, relayUrl).deviceToken;
2401
2473
  if (token === undefined) {
2402
2474
  throw new Error(`No device credential. Run cohall join with a pairing token on stdin or set COHALL_DEVICE_TOKEN.`);
2403
2475
  }
@@ -2409,7 +2481,7 @@ var loadDeviceConfiguration = Effect3.tryPromise({
2409
2481
  const sandbox = environmentSandbox() ?? stored.sandbox;
2410
2482
  const providers = environmentProviders(stored);
2411
2483
  return DeviceConfiguration.make({
2412
- relayUrl: normalizeRelayUrl(process.env.COHALL_RELAY_URL ?? stored.relayUrl),
2484
+ relayUrl,
2413
2485
  token,
2414
2486
  id: process.env.COHALL_DEVICE_ID === undefined ? stored.deviceId : DeviceId.make(process.env.COHALL_DEVICE_ID),
2415
2487
  name: process.env.COHALL_DEVICE_NAME ?? stored.deviceName,
@@ -2517,6 +2589,322 @@ var threadContext = Effect4.fn("Cohall.threadContext")(function* (client, thread
2517
2589
  return yield* client.threadContext(threadId);
2518
2590
  });
2519
2591
 
2592
+ // apps/device/src/upgrade.ts
2593
+ import { Schema as Schema6 } from "effect";
2594
+ import { execFile } from "node:child_process";
2595
+ import { chmod as chmod3, mkdir as mkdir2, readFile as readFile2, realpath as realpath2, rename as rename2, rm as rm2, writeFile as writeFile3 } from "node:fs/promises";
2596
+ import { dirname as dirname2, join as join3 } from "node:path";
2597
+ import { platform as operatingSystem } from "node:os";
2598
+ var packageName = "@akshar5/cohall";
2599
+ var semanticVersion = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
2600
+ var PackageManager = Schema6.Literals(["npm", "bun", "pnpm"]);
2601
+ var RestartReceipt = Schema6.Struct({
2602
+ version: Schema6.String,
2603
+ fromVersion: Schema6.String,
2604
+ packageManager: PackageManager,
2605
+ pendingServices: Schema6.Array(Schema6.String),
2606
+ restartedServices: Schema6.Array(Schema6.String)
2607
+ });
2608
+ var normalizePath = (path) => path.replaceAll("\\", "/");
2609
+ var normalizeUpgradeTarget = (target) => {
2610
+ if (target === undefined || target === "latest") {
2611
+ return "latest";
2612
+ }
2613
+ const normalized = target.startsWith("v") ? target.slice(1) : target;
2614
+ if (!semanticVersion.test(normalized)) {
2615
+ throw new Error("--to must be latest or an exact semantic version");
2616
+ }
2617
+ return normalized;
2618
+ };
2619
+ var packageInstallation = (canonicalEntrypoint, entrypoint = canonicalEntrypoint) => {
2620
+ const path = normalizePath(canonicalEntrypoint);
2621
+ const marker = `/node_modules/${packageName}/`;
2622
+ const packageIndex = path.lastIndexOf(marker);
2623
+ if (packageIndex === -1) {
2624
+ throw new Error("cohall upgrade requires a global npm, Bun, or pnpm installation; package-runner and source-checkout executions cannot upgrade themselves");
2625
+ }
2626
+ if (path.includes("/.npm/_npx/") || path.includes("/.bunx/") || path.includes("/pnpm/dlx/") || path.includes("/dlx/")) {
2627
+ throw new Error("This Cohall process is running from a temporary package-runner cache; install it globally before using cohall upgrade");
2628
+ }
2629
+ if (path.includes("/.bun/install/global/node_modules/")) {
2630
+ return { manager: "bun", entrypoint };
2631
+ }
2632
+ if (path.includes("/pnpm/global/") || path.includes("/.pnpm/")) {
2633
+ return { manager: "pnpm", entrypoint };
2634
+ }
2635
+ const nodeModulesParent = path.slice(0, packageIndex);
2636
+ const prefix = nodeModulesParent.endsWith("/lib") ? nodeModulesParent.slice(0, -"/lib".length) : nodeModulesParent;
2637
+ return { manager: "npm", prefix, entrypoint };
2638
+ };
2639
+ var packageInstallCommand = (installation, target) => {
2640
+ const specification = `${packageName}@${target}`;
2641
+ switch (installation.manager) {
2642
+ case "bun":
2643
+ return { command: "bun", arguments: ["add", "--global", specification] };
2644
+ case "pnpm":
2645
+ return { command: "pnpm", arguments: ["add", "--global", specification] };
2646
+ case "npm":
2647
+ return {
2648
+ command: "npm",
2649
+ arguments: [
2650
+ "install",
2651
+ "--global",
2652
+ ...installation.prefix === undefined ? [] : ["--prefix", installation.prefix],
2653
+ specification
2654
+ ]
2655
+ };
2656
+ }
2657
+ };
2658
+ var serviceCandidates = (runtimePlatform, uid) => {
2659
+ if (runtimePlatform === "linux") {
2660
+ const systemd = (manager, unit, device) => {
2661
+ const user = manager === "systemd-user" ? ["--user"] : [];
2662
+ return {
2663
+ id: `${manager}:${unit}`,
2664
+ label: unit,
2665
+ manager,
2666
+ device,
2667
+ check: { command: "systemctl", arguments: [...user, "is-active", "--quiet", unit] },
2668
+ restart: [{ command: "systemctl", arguments: [...user, "restart", unit] }]
2669
+ };
2670
+ };
2671
+ return [
2672
+ systemd("systemd-user", "cohall-relay.service", false),
2673
+ systemd("systemd-system", "cohall-relay.service", false),
2674
+ systemd("systemd-user", "cohall-device.service", true)
2675
+ ];
2676
+ }
2677
+ if (runtimePlatform === "darwin") {
2678
+ const domain = `gui/${uid ?? 0}`;
2679
+ const launchd = (label, device) => ({
2680
+ id: `launchd:${label}`,
2681
+ label,
2682
+ manager: "launchd",
2683
+ device,
2684
+ check: { command: "launchctl", arguments: ["print", `${domain}/${label}`] },
2685
+ activeOutput: /\bstate\s*=\s*running\b/,
2686
+ restart: [{ command: "launchctl", arguments: ["kickstart", "-k", `${domain}/${label}`] }]
2687
+ });
2688
+ return [launchd("com.cohall.relay", false), launchd("com.cohall.device", true)];
2689
+ }
2690
+ if (runtimePlatform === "win32") {
2691
+ return [
2692
+ {
2693
+ id: "scheduled-task:Cohall Device",
2694
+ label: "Cohall Device",
2695
+ manager: "scheduled-task",
2696
+ device: true,
2697
+ check: {
2698
+ command: "powershell.exe",
2699
+ arguments: [
2700
+ "-NoProfile",
2701
+ "-NonInteractive",
2702
+ "-Command",
2703
+ "if ((Get-ScheduledTask -TaskName 'Cohall Device' -ErrorAction SilentlyContinue).State -eq 'Running') { exit 0 } else { exit 1 }"
2704
+ ]
2705
+ },
2706
+ restart: [
2707
+ {
2708
+ command: "schtasks",
2709
+ arguments: ["/End", "/TN", "Cohall Device"],
2710
+ allowFailure: true
2711
+ },
2712
+ { command: "schtasks", arguments: ["/Run", "/TN", "Cohall Device"] }
2713
+ ]
2714
+ }
2715
+ ];
2716
+ }
2717
+ return [];
2718
+ };
2719
+ var defaultRunner = {
2720
+ run: (command, arguments_, timeoutMs = 300000) => new Promise((resolve2) => {
2721
+ execFile(command, [...arguments_], { encoding: "utf8", maxBuffer: 1024 * 1024, timeout: timeoutMs, windowsHide: true }, (error, stdout, stderr) => {
2722
+ resolve2({
2723
+ exitCode: typeof error?.code === "number" ? error.code : error === null ? 0 : 1,
2724
+ stdout,
2725
+ stderr,
2726
+ ...error === null ? {} : { error: error.message }
2727
+ });
2728
+ });
2729
+ })
2730
+ };
2731
+ var checked = async (runner, invocation, timeoutMs) => {
2732
+ const result = await runner.run(invocation.command, invocation.arguments, timeoutMs);
2733
+ if (result.exitCode === 0 || invocation.allowFailure === true) {
2734
+ return result;
2735
+ }
2736
+ const detail = (result.stderr.trim() || result.stdout.trim() || result.error || "unknown error").slice(0, 16384);
2737
+ throw new Error(`${invocation.command} ${invocation.arguments.join(" ")} failed with status ${result.exitCode}: ${detail}`);
2738
+ };
2739
+ var activeServices = async (runner, candidates) => {
2740
+ const active = [];
2741
+ for (const service of candidates) {
2742
+ const result = await runner.run(service.check.command, service.check.arguments, 1e4);
2743
+ if (result.exitCode === 0 && (service.activeOutput === undefined || service.activeOutput.test(`${result.stdout}
2744
+ ${result.stderr}`))) {
2745
+ active.push(service);
2746
+ }
2747
+ }
2748
+ return active;
2749
+ };
2750
+ var readReceipt = async (path) => readFile2(path, "utf8").then((content) => Schema6.decodeUnknownOption(RestartReceipt)(JSON.parse(content))).then((decoded) => decoded._tag === "Some" ? decoded.value : undefined).catch(() => {
2751
+ return;
2752
+ });
2753
+ var writeReceipt = async (path, value) => {
2754
+ const directory = dirname2(path);
2755
+ const temporary = `${path}.${process.pid}.tmp`;
2756
+ await mkdir2(directory, { recursive: true, mode: 448 });
2757
+ if (operatingSystem() !== "win32") {
2758
+ await chmod3(directory, 448);
2759
+ }
2760
+ await writeFile3(temporary, `${JSON.stringify(value, null, 2)}
2761
+ `, { mode: 384 });
2762
+ await rename2(temporary, path);
2763
+ };
2764
+ var installedVersion = async (entrypoint) => {
2765
+ const canonicalEntrypoint = await realpath2(entrypoint);
2766
+ const metadata = JSON.parse(await readFile2(join3(dirname2(dirname2(canonicalEntrypoint)), "package.json"), "utf8"));
2767
+ if (typeof metadata !== "object" || metadata === null || !("name" in metadata) || metadata.name !== packageName || !("version" in metadata) || typeof metadata.version !== "string") {
2768
+ throw new Error(`Could not verify the installed ${packageName} version`);
2769
+ }
2770
+ return metadata.version;
2771
+ };
2772
+ var restartReceiptPath = () => join3(dirname2(configurationPath()), "upgrade-restart.json");
2773
+ var restartServices = async (runner, services, statePath, state) => {
2774
+ let current = state;
2775
+ for (const service of services) {
2776
+ for (const invocation of service.restart) {
2777
+ await checked(runner, invocation, 60000);
2778
+ }
2779
+ current = {
2780
+ ...current,
2781
+ pendingServices: current.pendingServices.filter((id) => id !== service.id),
2782
+ restartedServices: [...current.restartedServices, service.id]
2783
+ };
2784
+ await writeReceipt(statePath, current);
2785
+ }
2786
+ return current;
2787
+ };
2788
+ var upgrade = async (options) => {
2789
+ const runner = options.runner ?? defaultRunner;
2790
+ const runtimePlatform = options.platform ?? process.platform;
2791
+ const uid = options.uid ?? process.getuid?.();
2792
+ const candidates = serviceCandidates(runtimePlatform, uid);
2793
+ const statePath = options.statePath ?? restartReceiptPath();
2794
+ const previous = await readReceipt(statePath);
2795
+ if (previous?.version === options.currentVersion) {
2796
+ const byId = new Map(candidates.map((service) => [service.id, service]));
2797
+ const pending = previous.pendingServices.flatMap((id) => {
2798
+ const service = byId.get(id);
2799
+ return service === undefined ? [] : [service];
2800
+ });
2801
+ const active = await activeServices(runner, pending);
2802
+ if (options.dryRun || !options.restart) {
2803
+ return {
2804
+ upgraded: previous.fromVersion !== previous.version,
2805
+ from_version: previous.fromVersion,
2806
+ installed_version: previous.version,
2807
+ requested_version: previous.version,
2808
+ package_manager: previous.packageManager,
2809
+ services_restarted: previous.restartedServices,
2810
+ services_pending_restart: active.map((service) => service.id),
2811
+ resumed_after_restart: false,
2812
+ dry_run: options.dryRun
2813
+ };
2814
+ }
2815
+ const activeIds = new Set(active.map((service) => service.id));
2816
+ const completedByRestart = options.delegated === true ? pending.filter((service) => service.device && activeIds.has(service.id)) : [];
2817
+ const completedIds = new Set(completedByRestart.map((service) => service.id));
2818
+ const remaining = active.filter((service) => !completedIds.has(service.id));
2819
+ let resumed = {
2820
+ ...previous,
2821
+ pendingServices: previous.pendingServices.filter((id) => !completedIds.has(id)),
2822
+ restartedServices: [
2823
+ ...previous.restartedServices,
2824
+ ...completedByRestart.map((service) => service.id)
2825
+ ]
2826
+ };
2827
+ if (remaining.length > 0) {
2828
+ await writeReceipt(statePath, resumed);
2829
+ resumed = await restartServices(runner, remaining, statePath, resumed);
2830
+ }
2831
+ await rm2(statePath, { force: true });
2832
+ return {
2833
+ upgraded: previous.fromVersion !== previous.version,
2834
+ from_version: previous.fromVersion,
2835
+ installed_version: previous.version,
2836
+ requested_version: previous.version,
2837
+ package_manager: previous.packageManager,
2838
+ services_restarted: resumed.restartedServices,
2839
+ services_pending_restart: [],
2840
+ resumed_after_restart: true,
2841
+ dry_run: false
2842
+ };
2843
+ }
2844
+ await rm2(statePath, { force: true });
2845
+ const target = normalizeUpgradeTarget(options.target);
2846
+ const entrypoint = options.entrypoint ?? process.argv[1];
2847
+ if (entrypoint === undefined) {
2848
+ throw new Error("Could not resolve the Cohall executable path");
2849
+ }
2850
+ const installation = packageInstallation(await realpath2(entrypoint), entrypoint);
2851
+ const install = packageInstallCommand(installation, target);
2852
+ const services = await activeServices(runner, candidates);
2853
+ if (options.dryRun) {
2854
+ return {
2855
+ upgraded: false,
2856
+ from_version: options.currentVersion,
2857
+ installed_version: options.currentVersion,
2858
+ requested_version: target,
2859
+ package_manager: installation.manager,
2860
+ services_restarted: [],
2861
+ services_pending_restart: options.restart ? services.map((service) => service.id) : [],
2862
+ resumed_after_restart: false,
2863
+ dry_run: true
2864
+ };
2865
+ }
2866
+ await checked(runner, install);
2867
+ const nextVersion = await installedVersion(entrypoint);
2868
+ if (target !== "latest" && nextVersion !== target) {
2869
+ throw new Error(`Installed Cohall ${nextVersion}, expected ${target}`);
2870
+ }
2871
+ const upgraded = nextVersion !== options.currentVersion;
2872
+ if (!options.restart || services.length === 0) {
2873
+ return {
2874
+ upgraded,
2875
+ from_version: options.currentVersion,
2876
+ installed_version: nextVersion,
2877
+ requested_version: target,
2878
+ package_manager: installation.manager,
2879
+ services_restarted: [],
2880
+ services_pending_restart: options.restart ? [] : services.map((service) => service.id),
2881
+ resumed_after_restart: false,
2882
+ dry_run: false
2883
+ };
2884
+ }
2885
+ const initial = {
2886
+ version: nextVersion,
2887
+ fromVersion: options.currentVersion,
2888
+ packageManager: installation.manager,
2889
+ pendingServices: services.map((service) => service.id),
2890
+ restartedServices: []
2891
+ };
2892
+ await writeReceipt(statePath, initial);
2893
+ const completed = await restartServices(runner, services, statePath, initial);
2894
+ await rm2(statePath, { force: true });
2895
+ return {
2896
+ upgraded,
2897
+ from_version: options.currentVersion,
2898
+ installed_version: nextVersion,
2899
+ requested_version: target,
2900
+ package_manager: installation.manager,
2901
+ services_restarted: completed.restartedServices,
2902
+ services_pending_restart: [],
2903
+ resumed_after_restart: false,
2904
+ dry_run: false
2905
+ };
2906
+ };
2907
+
2520
2908
  // apps/device/src/cli.ts
2521
2909
  var valueOptions = new Set([
2522
2910
  "context",
@@ -2534,9 +2922,10 @@ var valueOptions = new Set([
2534
2922
  "thread",
2535
2923
  "timeout",
2536
2924
  "token-file",
2925
+ "to",
2537
2926
  "workspace"
2538
2927
  ]);
2539
- var flagOptions = new Set(["client-only", "follow", "no-wait"]);
2928
+ var flagOptions = new Set(["client-only", "dry-run", "follow", "no-restart", "no-wait"]);
2540
2929
  var aliases = new Map([
2541
2930
  ["-c", "context"],
2542
2931
  ["-p", "prompt"],
@@ -2564,6 +2953,7 @@ Usage:
2564
2953
  cohall sessions
2565
2954
  cohall revoke <session-id>
2566
2955
  cohall doctor
2956
+ cohall upgrade [--to version|latest] [--no-restart] [--dry-run]
2567
2957
  cohall device
2568
2958
  cohall relay
2569
2959
  cohall mcp
@@ -2636,9 +3026,9 @@ var allowOptions = (arguments_, names) => {
2636
3026
  }
2637
3027
  }
2638
3028
  };
2639
- var noPositionals = (arguments_, command2) => {
3029
+ var noPositionals = (arguments_, command) => {
2640
3030
  if (arguments_.positionals.length > 0) {
2641
- throw new Error(`${command2} does not accept positional arguments`);
3031
+ throw new Error(`${command} does not accept positional arguments`);
2642
3032
  }
2643
3033
  };
2644
3034
  var identifier = (arguments_, label) => {
@@ -2666,7 +3056,7 @@ var readInput = async (arguments_, name) => {
2666
3056
  if ((await stat(path)).size > 131072) {
2667
3057
  throw new Error(`${name} file exceeds 128 KiB`);
2668
3058
  }
2669
- return readFile2(path, "utf8");
3059
+ return readFile3(path, "utf8");
2670
3060
  }
2671
3061
  const result = direct === "-" ? await readStdin(131072, name) : direct;
2672
3062
  if (result !== undefined && result.length > 131072) {
@@ -2702,7 +3092,7 @@ var pairingToken = async (arguments_) => {
2702
3092
  if (path !== undefined && (await stat(path)).size > 512) {
2703
3093
  throw new Error("Pairing token file exceeds 512 bytes");
2704
3094
  }
2705
- const token = (path === undefined ? await readStdin(512, "Pairing token") : await readFile2(path, "utf8")).trim();
3095
+ const token = (path === undefined ? await readStdin(512, "Pairing token") : await readFile3(path, "utf8")).trim();
2706
3096
  if (token.length === 0 || new TextEncoder().encode(token).byteLength > 256) {
2707
3097
  throw new Error("Provide a pairing token on stdin or with --token-file");
2708
3098
  }
@@ -2724,25 +3114,38 @@ var ownerClient = async () => {
2724
3114
  };
2725
3115
  var installSkill = async (target) => {
2726
3116
  const roots = {
2727
- agents: join3(homedir3(), ".agents", "skills", "cohall", "SKILL.md"),
2728
- claude: join3(homedir3(), ".claude", "skills", "cohall", "SKILL.md"),
2729
- opencode: join3(homedir3(), ".config", "opencode", "skills", "cohall", "SKILL.md")
3117
+ agents: join4(homedir3(), ".agents", "skills", "cohall", "SKILL.md"),
3118
+ claude: join4(homedir3(), ".claude", "skills", "cohall", "SKILL.md"),
3119
+ opencode: join4(homedir3(), ".config", "opencode", "skills", "cohall", "SKILL.md")
2730
3120
  };
2731
3121
  const selected = target === "all" ? Object.values(roots) : (target in roots) ? [roots[target]] : undefined;
2732
3122
  if (selected === undefined) {
2733
3123
  throw new Error("Skill target must be agents, claude, opencode, or all");
2734
3124
  }
2735
3125
  for (const path of selected) {
2736
- await mkdir2(dirname2(path), { recursive: true });
2737
- await writeFile2(path, SKILL_default);
3126
+ await mkdir3(dirname3(path), { recursive: true });
3127
+ await writeFile4(path, SKILL_default);
2738
3128
  }
2739
3129
  return selected;
2740
3130
  };
2741
3131
  var printHelp = () => console.log(help);
2742
3132
  var printSkill = () => console.log(SKILL_default.trimEnd());
2743
- var runCli = async (command2, raw) => {
3133
+ var runCli = async (command, raw) => {
2744
3134
  const arguments_ = parseArguments(raw);
2745
- if (command2 === "join") {
3135
+ if (command === "upgrade") {
3136
+ allowOptions(arguments_, ["dry-run", "no-restart", "to"]);
3137
+ noPositionals(arguments_, command);
3138
+ const target = option(arguments_, "to");
3139
+ print(await upgrade({
3140
+ currentVersion: version,
3141
+ ...target === undefined ? {} : { target },
3142
+ restart: !arguments_.options.has("no-restart"),
3143
+ dryRun: arguments_.options.has("dry-run"),
3144
+ delegated: process.env.COHALL_PROVIDER !== undefined
3145
+ }));
3146
+ return;
3147
+ }
3148
+ if (command === "join") {
2746
3149
  allowOptions(arguments_, [
2747
3150
  "client-only",
2748
3151
  "name",
@@ -2751,7 +3154,7 @@ var runCli = async (command2, raw) => {
2751
3154
  "token-file",
2752
3155
  "workspace"
2753
3156
  ]);
2754
- noPositionals(arguments_, command2);
3157
+ noPositionals(arguments_, command);
2755
3158
  const token = await pairingToken(arguments_);
2756
3159
  const relayUrl = normalizeRelayUrl(option(arguments_, "relay") ?? "http://127.0.0.1:8787");
2757
3160
  const existing = await readStoredConfiguration();
@@ -2793,9 +3196,9 @@ var runCli = async (command2, raw) => {
2793
3196
  });
2794
3197
  return;
2795
3198
  }
2796
- if (command2 === "config") {
3199
+ if (command === "config") {
2797
3200
  allowOptions(arguments_, []);
2798
- noPositionals(arguments_, command2);
3201
+ noPositionals(arguments_, command);
2799
3202
  const configuration2 = await readStoredConfiguration();
2800
3203
  print({
2801
3204
  config_path: configurationPath(),
@@ -2814,9 +3217,9 @@ var runCli = async (command2, raw) => {
2814
3217
  });
2815
3218
  return;
2816
3219
  }
2817
- if (command2 === "configure") {
3220
+ if (command === "configure") {
2818
3221
  allowOptions(arguments_, ["model", "name", "providers", "relay", "sandbox", "workspace"]);
2819
- noPositionals(arguments_, command2);
3222
+ noPositionals(arguments_, command);
2820
3223
  const existing = await readStoredConfiguration();
2821
3224
  const relayUrl = normalizeRelayUrl(option(arguments_, "relay") ?? existing?.relayUrl ?? "http://127.0.0.1:8787");
2822
3225
  const supplied = values(arguments_, "workspace");
@@ -2831,19 +3234,18 @@ var runCli = async (command2, raw) => {
2831
3234
  deviceId: existing?.deviceId ?? makeDeviceId(),
2832
3235
  deviceName: option(arguments_, "name") ?? existing?.deviceName ?? "cohall-device",
2833
3236
  workspaces,
2834
- ...existing?.clientToken === undefined ? {} : { clientToken: existing.clientToken },
2835
- ...existing?.deviceToken === undefined ? {} : { deviceToken: existing.deviceToken },
3237
+ ...credentialsForRelay(existing, relayUrl),
2836
3238
  ...providers === undefined ? {} : { providers },
2837
3239
  ...model === undefined ? {} : { model },
2838
3240
  ...(sandbox ?? existing?.sandbox) === undefined ? {} : {
2839
- sandbox: Schema6.decodeUnknownSync(Schema6.Literals(["read-only", "workspace-write", "danger-full-access"]))(sandbox ?? existing?.sandbox)
3241
+ sandbox: Schema7.decodeUnknownSync(Schema7.Literals(["read-only", "workspace-write", "danger-full-access"]))(sandbox ?? existing?.sandbox)
2840
3242
  }
2841
3243
  });
2842
3244
  await writeStoredConfiguration(configuration2);
2843
3245
  print({ configured: true, config_path: configurationPath() });
2844
3246
  return;
2845
3247
  }
2846
- if (command2 === "skill") {
3248
+ if (command === "skill") {
2847
3249
  allowOptions(arguments_, []);
2848
3250
  if (arguments_.positionals.length === 0) {
2849
3251
  printSkill();
@@ -2855,9 +3257,9 @@ var runCli = async (command2, raw) => {
2855
3257
  print({ installed: await installSkill(arguments_.positionals[1] ?? "all") });
2856
3258
  return;
2857
3259
  }
2858
- if (command2 === "integrations") {
3260
+ if (command === "integrations") {
2859
3261
  allowOptions(arguments_, []);
2860
- noPositionals(arguments_, command2);
3262
+ noPositionals(arguments_, command);
2861
3263
  print({
2862
3264
  cli_skill: "npx -y @akshar5/cohall skill install all",
2863
3265
  codex_mcp: "codex mcp add cohall -- npx -y @akshar5/cohall mcp",
@@ -2874,11 +3276,12 @@ var runCli = async (command2, raw) => {
2874
3276
  });
2875
3277
  return;
2876
3278
  }
2877
- if (command2 === "doctor") {
3279
+ if (command === "doctor") {
2878
3280
  allowOptions(arguments_, []);
2879
- noPositionals(arguments_, command2);
3281
+ noPositionals(arguments_, command);
2880
3282
  const configuration2 = await readStoredConfiguration();
2881
3283
  const relayUrl = normalizeRelayUrl(process.env.COHALL_RELAY_URL ?? configuration2?.relayUrl ?? "http://127.0.0.1:8787");
3284
+ const storedCredentials = credentialsForRelay(configuration2, relayUrl);
2882
3285
  const response = await fetch(`${relayUrl}/api/health`, {
2883
3286
  signal: AbortSignal.timeout(1e4)
2884
3287
  }).catch(() => {
@@ -2890,8 +3293,8 @@ var runCli = async (command2, raw) => {
2890
3293
  provider,
2891
3294
  findExecutable(provider === "claude-code" ? "claude" : provider) ?? "not found"
2892
3295
  ]));
2893
- const clientToken = process.env.COHALL_CLIENT_TOKEN ?? configuration2?.clientToken;
2894
- const hasDeviceCredential = process.env.COHALL_DEVICE_TOKEN !== undefined || configuration2?.deviceToken !== undefined;
3296
+ const clientToken = process.env.COHALL_CLIENT_TOKEN ?? storedCredentials.clientToken;
3297
+ const hasDeviceCredential = process.env.COHALL_DEVICE_TOKEN !== undefined || storedCredentials.deviceToken !== undefined;
2895
3298
  const deviceId = process.env.COHALL_DEVICE_ID ?? configuration2?.deviceId;
2896
3299
  const currentDevice = response?.ok !== true || clientToken === undefined || deviceId === undefined ? undefined : await Effect5.runPromise(exports_src.make({ baseUrl: relayUrl, token: clientToken }).devices()).then((devices) => devices.find((device) => device.id === deviceId)).catch(() => {
2897
3300
  return;
@@ -2908,7 +3311,7 @@ var runCli = async (command2, raw) => {
2908
3311
  relay: response?.ok === true ? "reachable" : "unreachable",
2909
3312
  relay_url: relayUrl,
2910
3313
  config_path: configurationPath(),
2911
- client_credential: process.env.COHALL_CLIENT_TOKEN !== undefined || configuration2?.clientToken !== undefined,
3314
+ client_credential: process.env.COHALL_CLIENT_TOKEN !== undefined || storedCredentials.clientToken !== undefined,
2912
3315
  device_credential: hasDeviceCredential,
2913
3316
  workspaces: configuration2?.workspaces ?? [],
2914
3317
  providers: providerExecutables,
@@ -2920,11 +3323,11 @@ var runCli = async (command2, raw) => {
2920
3323
  });
2921
3324
  return;
2922
3325
  }
2923
- if (command2 === "pair" || command2 === "sessions" || command2 === "revoke") {
3326
+ if (command === "pair" || command === "sessions" || command === "revoke") {
2924
3327
  const { configuration: configuration2, relay: relay2 } = await ownerClient();
2925
- if (command2 === "pair") {
3328
+ if (command === "pair") {
2926
3329
  allowOptions(arguments_, ["client-only", "label"]);
2927
- noPositionals(arguments_, command2);
3330
+ noPositionals(arguments_, command);
2928
3331
  const roles = arguments_.options.has("client-only") ? ["client"] : ["client", "device"];
2929
3332
  const credential = await Effect5.runPromise(relay2.createPairing({ label: option(arguments_, "label") ?? "Cohall device", roles }));
2930
3333
  print({
@@ -2935,24 +3338,24 @@ var runCli = async (command2, raw) => {
2935
3338
  });
2936
3339
  return;
2937
3340
  }
2938
- if (command2 === "sessions") {
3341
+ if (command === "sessions") {
2939
3342
  allowOptions(arguments_, []);
2940
- noPositionals(arguments_, command2);
3343
+ noPositionals(arguments_, command);
2941
3344
  print(await Effect5.runPromise(relay2.authSessions()));
2942
3345
  return;
2943
3346
  }
2944
3347
  allowOptions(arguments_, []);
2945
- print(await Effect5.runPromise(relay2.revokeAuthSession(Schema6.decodeUnknownSync(AuthSessionId)(identifier(arguments_, "session id")))));
3348
+ print(await Effect5.runPromise(relay2.revokeAuthSession(Schema7.decodeUnknownSync(AuthSessionId)(identifier(arguments_, "session id")))));
2946
3349
  return;
2947
3350
  }
2948
3351
  const { configuration, relay } = await client();
2949
- if (command2 === "devices") {
3352
+ if (command === "devices") {
2950
3353
  allowOptions(arguments_, []);
2951
- noPositionals(arguments_, command2);
3354
+ noPositionals(arguments_, command);
2952
3355
  print(await Effect5.runPromise(relay.devices()));
2953
3356
  return;
2954
3357
  }
2955
- if (command2 === "delegate") {
3358
+ if (command === "delegate") {
2956
3359
  allowOptions(arguments_, [
2957
3360
  "context",
2958
3361
  "context-file",
@@ -2985,22 +3388,22 @@ var runCli = async (command2, raw) => {
2985
3388
  prompt,
2986
3389
  ...target === undefined ? {} : { target },
2987
3390
  ...context === undefined ? {} : { context },
2988
- ...thread === undefined ? {} : { threadId: Schema6.decodeUnknownSync(ThreadId)(thread) },
3391
+ ...thread === undefined ? {} : { threadId: Schema7.decodeUnknownSync(ThreadId)(thread) },
2989
3392
  ...workspace === undefined ? {} : { workspace },
2990
- ...provider === undefined ? {} : { provider: Schema6.decodeUnknownSync(Provider)(provider) }
3393
+ ...provider === undefined ? {} : { provider: Schema7.decodeUnknownSync(Provider)(provider) }
2991
3394
  }));
2992
3395
  print(taskResult(arguments_.options.has("no-wait") ? task : await Effect5.runPromise(waitForTask(relay, task, timeout(arguments_)))));
2993
3396
  return;
2994
3397
  }
2995
- if (command2 === "status") {
3398
+ if (command === "status") {
2996
3399
  allowOptions(arguments_, []);
2997
- const id = Schema6.decodeUnknownSync(TaskId)(identifier(arguments_, "task id"));
3400
+ const id = Schema7.decodeUnknownSync(TaskId)(identifier(arguments_, "task id"));
2998
3401
  print(taskResult(await Effect5.runPromise(relay.getTask(id))));
2999
3402
  return;
3000
3403
  }
3001
- if (command2 === "trace") {
3404
+ if (command === "trace") {
3002
3405
  allowOptions(arguments_, ["follow"]);
3003
- const id = Schema6.decodeUnknownSync(TaskId)(identifier(arguments_, "task id"));
3406
+ const id = Schema7.decodeUnknownSync(TaskId)(identifier(arguments_, "task id"));
3004
3407
  if (arguments_.options.has("follow")) {
3005
3408
  await Effect5.runPromise(followTaskTrace(relay, id, printLine));
3006
3409
  return;
@@ -3008,37 +3411,39 @@ var runCli = async (command2, raw) => {
3008
3411
  print(await Effect5.runPromise(relay.traceTask(id)));
3009
3412
  return;
3010
3413
  }
3011
- if (command2 === "wait") {
3414
+ if (command === "wait") {
3012
3415
  allowOptions(arguments_, ["timeout"]);
3013
- const id = Schema6.decodeUnknownSync(TaskId)(identifier(arguments_, "task id"));
3416
+ const id = Schema7.decodeUnknownSync(TaskId)(identifier(arguments_, "task id"));
3014
3417
  const task = await Effect5.runPromise(relay.getTask(id));
3015
3418
  print(taskResult(await Effect5.runPromise(waitForTask(relay, task, timeout(arguments_)))));
3016
3419
  return;
3017
3420
  }
3018
- if (command2 === "cancel") {
3421
+ if (command === "cancel") {
3019
3422
  allowOptions(arguments_, []);
3020
- const id = Schema6.decodeUnknownSync(TaskId)(identifier(arguments_, "task id"));
3423
+ const id = Schema7.decodeUnknownSync(TaskId)(identifier(arguments_, "task id"));
3021
3424
  print(taskResult(await Effect5.runPromise(relay.cancelTask(id))));
3022
3425
  return;
3023
3426
  }
3024
- if (command2 === "thread") {
3427
+ if (command === "thread") {
3025
3428
  allowOptions(arguments_, []);
3026
- const id = Schema6.decodeUnknownSync(ThreadId)(identifier(arguments_, "thread id"));
3429
+ const id = Schema7.decodeUnknownSync(ThreadId)(identifier(arguments_, "thread id"));
3027
3430
  print(await Effect5.runPromise(threadContext(relay, id)));
3028
3431
  return;
3029
3432
  }
3030
- throw new Error(`Unknown Cohall command: ${command2}`);
3433
+ throw new Error(`Unknown Cohall command: ${command}`);
3031
3434
  };
3032
3435
 
3033
3436
  // apps/device/src/daemon.ts
3034
3437
  init_src();
3035
- import { Effect as Effect6, Schedule as Schedule2, Schema as Schema7 } from "effect";
3036
- import { realpath as realpath2 } from "node:fs/promises";
3438
+ import { Effect as Effect6, Schedule as Schedule2, Schema as Schema8 } from "effect";
3439
+ import { realpath as realpath3 } from "node:fs/promises";
3037
3440
  import { arch, hostname as hostname2, platform as platform3 } from "node:os";
3038
3441
  import { basename, isAbsolute as isAbsolute2, relative } from "node:path";
3039
3442
  import { WebSocket } from "ws";
3443
+ var maxRelayPayloadBytes = 256 * 1024;
3444
+ var maxQueuedRelayMessages = 8;
3040
3445
 
3041
- class DeviceConnectionError extends Schema7.TaggedErrorClass()("Device.ConnectionError", { message: Schema7.String }) {
3446
+ class DeviceConnectionError extends Schema8.TaggedErrorClass()("Device.ConnectionError", { message: Schema8.String }) {
3042
3447
  }
3043
3448
  var socketUrl = (configuration) => {
3044
3449
  const url = new URL(configuration.relayUrl);
@@ -3075,8 +3480,8 @@ var capabilities = (providers) => {
3075
3480
  };
3076
3481
  var selectProviders = (installed, configured) => configured === undefined ? installed : configured.filter((provider) => installed.includes(provider));
3077
3482
  var describeDevice = (configuration, status = "online") => {
3078
- const operatingSystem = platform3();
3079
- const platformName = operatingSystem === "darwin" || operatingSystem === "linux" ? operatingSystem : operatingSystem === "win32" ? "windows" : "unknown";
3483
+ const operatingSystem2 = platform3();
3484
+ const platformName = operatingSystem2 === "darwin" || operatingSystem2 === "linux" ? operatingSystem2 : operatingSystem2 === "win32" ? "windows" : "unknown";
3080
3485
  const installed = availableProviders();
3081
3486
  const providers = selectProviders(installed, configuration.providers);
3082
3487
  return Device.make({
@@ -3101,7 +3506,7 @@ var allowedWorkspace = async (configuration, requested) => {
3101
3506
  if (selected === undefined) {
3102
3507
  throw new Error("No workspace is configured on this device");
3103
3508
  }
3104
- const candidate = await realpath2(selected);
3509
+ const candidate = await realpath3(selected);
3105
3510
  const allowed = configuration.workspaces.some((root) => {
3106
3511
  const child = relative(root, candidate);
3107
3512
  return child === "" || !child.startsWith("..") && !isAbsolute2(child);
@@ -3235,7 +3640,10 @@ var cancel = (state, taskId) => {
3235
3640
  };
3236
3641
  var connect = (configuration, state) => Effect6.tryPromise({
3237
3642
  try: (signal) => new Promise((complete) => {
3238
- const socket = new WebSocket(socketUrl(configuration));
3643
+ const socket = new WebSocket(socketUrl(configuration), {
3644
+ maxPayload: maxRelayPayloadBytes,
3645
+ perMessageDeflate: false
3646
+ });
3239
3647
  const heartbeat = setInterval(() => {
3240
3648
  send(state, SocketEvent.make({
3241
3649
  _tag: "DeviceHeartbeat",
@@ -3244,6 +3652,7 @@ var connect = (configuration, state) => Effect6.tryPromise({
3244
3652
  }));
3245
3653
  }, 15000);
3246
3654
  let closed = false;
3655
+ let queuedMessages = 0;
3247
3656
  const close = () => {
3248
3657
  if (closed) {
3249
3658
  return;
@@ -3263,8 +3672,16 @@ var connect = (configuration, state) => Effect6.tryPromise({
3263
3672
  socket.send(JSON.stringify(SocketEvent.make({ _tag: "Authenticate", token: configuration.token })));
3264
3673
  });
3265
3674
  socket.on("message", (message) => {
3266
- const text2 = Buffer.isBuffer(message) ? message.toString("utf8") : message instanceof ArrayBuffer ? Buffer.from(message).toString("utf8") : Array.isArray(message) ? Buffer.concat(message).toString("utf8") : Buffer.from(message).toString("utf8");
3675
+ if (queuedMessages >= maxQueuedRelayMessages) {
3676
+ socket.close(4008, "Message queue limit reached");
3677
+ return;
3678
+ }
3679
+ queuedMessages += 1;
3267
3680
  state.processing = state.processing.then(async () => {
3681
+ if (closed) {
3682
+ return;
3683
+ }
3684
+ const text2 = Buffer.isBuffer(message) ? message.toString("utf8") : message instanceof ArrayBuffer ? Buffer.from(message).toString("utf8") : Array.isArray(message) ? Buffer.concat(message).toString("utf8") : Buffer.from(message).toString("utf8");
3268
3685
  const event = await Effect6.runPromise(Effect6.try({
3269
3686
  try: () => JSON.parse(text2),
3270
3687
  catch: () => new DeviceConnectionError({ message: "Relay sent invalid JSON" })
@@ -3297,6 +3714,8 @@ var connect = (configuration, state) => Effect6.tryPromise({
3297
3714
  }
3298
3715
  }).catch((cause) => {
3299
3716
  console.error(`Invalid relay event: ${cause instanceof Error ? cause.message : String(cause)}`);
3717
+ }).finally(() => {
3718
+ queuedMessages = Math.max(0, queuedMessages - 1);
3300
3719
  });
3301
3720
  });
3302
3721
  socket.once("close", close);
@@ -3404,43 +3823,43 @@ var runMcp = async (configuration) => {
3404
3823
 
3405
3824
  // apps/device/src/main.ts
3406
3825
  var main = async () => {
3407
- const command2 = process.argv[2];
3408
- if (command2 === undefined || command2 === "help" || command2 === "--help" || command2 === "-h") {
3826
+ const command = process.argv[2];
3827
+ if (command === undefined || command === "help" || command === "--help" || command === "-h") {
3409
3828
  printHelp();
3410
3829
  return;
3411
3830
  }
3412
- if (command2 === "--version" || command2 === "version") {
3831
+ if (command === "--version" || command === "version") {
3413
3832
  console.log(version);
3414
3833
  return;
3415
3834
  }
3416
- if (["device", "relay", "mcp"].includes(command2) && process.argv.length > 3) {
3835
+ if (["device", "relay", "mcp"].includes(command) && process.argv.length > 3) {
3417
3836
  const arguments_ = process.argv.slice(3);
3418
3837
  if (arguments_.length === 1 && ["--help", "-h", "help"].includes(arguments_[0] ?? "")) {
3419
3838
  printHelp();
3420
3839
  return;
3421
3840
  }
3422
- throw new Error(`${command2} does not accept arguments`);
3841
+ throw new Error(`${command} does not accept arguments`);
3423
3842
  }
3424
- if (command2 === "relay") {
3843
+ if (command === "relay") {
3425
3844
  const { runRelay: runRelay2 } = await init_main().then(() => exports_main);
3426
3845
  await runRelay2();
3427
3846
  return;
3428
3847
  }
3429
- if (command2 === "device") {
3848
+ if (command === "device") {
3430
3849
  const configuration = await Effect11.runPromise(loadDeviceConfiguration);
3431
3850
  await Effect11.runPromise(runDaemon(configuration));
3432
3851
  return;
3433
3852
  }
3434
- if (command2 === "mcp") {
3853
+ if (command === "mcp") {
3435
3854
  const configuration = await Effect11.runPromise(loadClientConfiguration);
3436
3855
  await runMcp(configuration);
3437
3856
  return;
3438
3857
  }
3439
- await runCli(command2, process.argv.slice(3));
3858
+ await runCli(command, process.argv.slice(3));
3440
3859
  };
3441
3860
  await main().catch((cause) => {
3442
3861
  console.error(JSON.stringify({ error: cause instanceof Error ? cause.message : String(cause) }, null, 2));
3443
3862
  process.exitCode = 1;
3444
3863
  });
3445
3864
 
3446
- //# debugId=492323E24FE9814364756E2164756E21
3865
+ //# debugId=ADB2C1D9ECE4F8B464756E2164756E21