@akshar5/cohall 0.3.3 → 0.4.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.
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.3", 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.0", 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,9 +1738,11 @@ 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";
1745
+ import { createInterface } from "node:readline";
1744
1746
 
1745
1747
  class ProviderUnavailableError extends Schema3.TaggedErrorClass()("CohallProvider.Unavailable", { provider: Provider, message: Schema3.String }) {
1746
1748
  }
@@ -1878,48 +1880,86 @@ var claudeCommand = (options) => [
1878
1880
  ...options.model === undefined ? [] : ["--model", options.model],
1879
1881
  ...options.sessionId === undefined ? [] : ["--resume", options.sessionId]
1880
1882
  ];
1881
- var opencodeCommand = (options) => [
1883
+ var opencodeCommand = (options, promptPath) => [
1882
1884
  "opencode",
1883
1885
  "run",
1884
1886
  "--format",
1885
1887
  "json",
1886
1888
  ...options.model === undefined ? [] : ["--model", options.model],
1887
1889
  ...options.sessionId === undefined ? [] : ["--session", options.sessionId],
1888
- options.prompt
1890
+ "--file",
1891
+ promptPath,
1892
+ "Complete the task described in the attached Cohall prompt file."
1889
1893
  ];
1890
- var command = (options) => {
1894
+ var prepareCommand = async (options) => {
1891
1895
  switch (options.provider) {
1892
1896
  case "codex":
1893
- return codexCommand(options);
1897
+ return {
1898
+ command: codexCommand(options),
1899
+ input: options.prompt,
1900
+ cleanup: () => Promise.resolve()
1901
+ };
1894
1902
  case "claude-code":
1895
- return claudeCommand(options);
1896
- case "opencode":
1897
- return opencodeCommand(options);
1903
+ return {
1904
+ command: claudeCommand(options),
1905
+ input: options.prompt,
1906
+ cleanup: () => Promise.resolve()
1907
+ };
1908
+ case "opencode": {
1909
+ const directory = await mkdtemp(join(tmpdir(), "cohall-opencode-"));
1910
+ const promptPath = join(directory, "prompt.md");
1911
+ try {
1912
+ if (platform() !== "win32") {
1913
+ await chmod(directory, 448);
1914
+ }
1915
+ await writeFile(promptPath, options.prompt, { mode: 384 });
1916
+ return {
1917
+ command: opencodeCommand(options, promptPath),
1918
+ cleanup: () => rm(directory, { recursive: true, force: true })
1919
+ };
1920
+ } catch (cause) {
1921
+ await rm(directory, { recursive: true, force: true }).catch(() => {
1922
+ return;
1923
+ });
1924
+ throw cause;
1925
+ }
1926
+ }
1898
1927
  }
1899
1928
  };
1900
- var parseCodex = (stdout, existingSession) => {
1929
+ var forEachJsonEvent = async (stdout, onEvent) => {
1930
+ const lines = createInterface({ input: stdout, crlfDelay: Infinity });
1931
+ try {
1932
+ for await (const line of lines) {
1933
+ if (line.trim().length === 0) {
1934
+ continue;
1935
+ }
1936
+ if (Buffer.byteLength(line) > 1024 * 1024) {
1937
+ throw new Error("Provider event exceeded 1024 KiB");
1938
+ }
1939
+ const event = record(JSON.parse(line));
1940
+ if (event !== undefined) {
1941
+ onEvent(event);
1942
+ }
1943
+ }
1944
+ } finally {
1945
+ lines.close();
1946
+ }
1947
+ };
1948
+ var parseCodex = async (stdout, existingSession) => {
1901
1949
  let result = "";
1902
1950
  let sessionId = existingSession;
1903
- for (const line of stdout.split(`
1904
- `)) {
1905
- if (line.trim().length === 0) {
1906
- continue;
1907
- }
1908
- const event = record(JSON.parse(line));
1909
- if (event === undefined) {
1910
- continue;
1911
- }
1951
+ await forEachJsonEvent(stdout, (event) => {
1912
1952
  if (text(event.type) === "thread.started") {
1913
1953
  sessionId = text(event.thread_id) ?? sessionId;
1914
1954
  }
1915
1955
  if (text(event.type) !== "item.completed") {
1916
- continue;
1956
+ return;
1917
1957
  }
1918
1958
  const item = record(event.item);
1919
1959
  if (text(item?.type) === "agent_message") {
1920
1960
  result = text(item?.text) ?? text(item?.content) ?? result;
1921
1961
  }
1922
- }
1962
+ });
1923
1963
  return {
1924
1964
  result: result || "Codex completed the task without a text response.",
1925
1965
  ...sessionId === undefined ? {} : { sessionId }
@@ -1953,18 +1993,10 @@ var openCodeError = (event) => {
1953
1993
  }
1954
1994
  return message === undefined ? "OpenCode reported an error" : `OpenCode: ${message}`;
1955
1995
  };
1956
- var parseOpenCode = (stdout, existingSession) => {
1996
+ var parseOpenCode = async (stdout, existingSession) => {
1957
1997
  let result = "";
1958
1998
  let sessionId = existingSession;
1959
- for (const line of stdout.split(`
1960
- `)) {
1961
- if (line.trim().length === 0) {
1962
- continue;
1963
- }
1964
- const event = record(JSON.parse(line));
1965
- if (event === undefined) {
1966
- continue;
1967
- }
1999
+ await forEachJsonEvent(stdout, (event) => {
1968
2000
  const failure = openCodeError(event);
1969
2001
  if (failure !== undefined) {
1970
2002
  throw new Error(failure);
@@ -1974,33 +2006,39 @@ var parseOpenCode = (stdout, existingSession) => {
1974
2006
  if (content !== undefined && content.length > 0) {
1975
2007
  result = content;
1976
2008
  }
1977
- }
2009
+ });
1978
2010
  if (result.length === 0) {
1979
2011
  throw new Error("OpenCode produced no result; verify its authentication and use a supported project workspace");
1980
2012
  }
1981
2013
  return { result, ...sessionId === undefined ? {} : { sessionId } };
1982
2014
  };
1983
- var parse = (options, stdout) => {
1984
- switch (options.provider) {
1985
- case "codex":
1986
- return parseCodex(stdout, options.sessionId);
1987
- case "claude-code":
1988
- return parseClaude(stdout, options.sessionId);
1989
- case "opencode":
1990
- return parseOpenCode(stdout, options.sessionId);
2015
+ var parse = async (options, stdout, onFailure) => {
2016
+ try {
2017
+ switch (options.provider) {
2018
+ case "codex":
2019
+ return await parseCodex(stdout, options.sessionId);
2020
+ case "claude-code":
2021
+ return parseClaude(await boundedText(stdout, 1024 * 1024, onFailure), options.sessionId);
2022
+ case "opencode":
2023
+ return await parseOpenCode(stdout, options.sessionId);
2024
+ }
2025
+ } catch (cause) {
2026
+ onFailure();
2027
+ throw cause;
1991
2028
  }
1992
2029
  };
1993
2030
  var byteLength = (value) => new TextEncoder().encode(value).byteLength;
1994
2031
  var run = (options) => Effect2.tryPromise({
1995
2032
  try: async (signal) => {
1996
- const [commandName, ...arguments_] = command(options);
1997
- const executable = commandName === undefined ? undefined : findExecutable(commandName);
2033
+ const executable = findExecutable(executables[options.provider]);
1998
2034
  if (executable === undefined) {
1999
2035
  throw new ProviderUnavailableError({
2000
2036
  provider: options.provider,
2001
2037
  message: `${executables[options.provider]} is not available on this device`
2002
2038
  });
2003
2039
  }
2040
+ const prepared = await prepareCommand(options);
2041
+ const [, ...arguments_] = prepared.command;
2004
2042
  const child = spawn(executable, arguments_, {
2005
2043
  cwd: options.cwd,
2006
2044
  env: {
@@ -2028,37 +2066,46 @@ var run = (options) => Effect2.tryPromise({
2028
2066
  killTimer.unref();
2029
2067
  };
2030
2068
  signal.addEventListener("abort", terminate, { once: true });
2031
- child.stdin.end(options.provider === "opencode" ? undefined : options.prompt);
2032
- const [stdout, stderr, exitCode] = await Promise.all([
2033
- boundedText(child.stdout, 1024 * 1024, terminate),
2034
- captureText(child.stderr, 64 * 1024),
2035
- exited
2036
- ]);
2037
- signal.removeEventListener("abort", terminate);
2038
- if (killTimer !== undefined && child.exitCode !== null) {
2039
- clearTimeout(killTimer);
2040
- }
2041
- if (exitCode !== 0) {
2042
- throw new ProviderRunError({
2043
- provider: options.provider,
2044
- message: failureMessage(executables[options.provider], stderr, exitCode),
2045
- exitCode
2046
- });
2047
- }
2048
- const result = parse(options, stdout);
2049
- if (byteLength(result.result) > 131072) {
2050
- throw new ProviderRunError({
2051
- provider: options.provider,
2052
- message: "Provider result exceeded 128 KiB"
2053
- });
2054
- }
2055
- if (result.sessionId !== undefined && byteLength(result.sessionId) > 4096) {
2056
- throw new ProviderRunError({
2057
- provider: options.provider,
2058
- message: "Provider session ID exceeded 4 KiB"
2059
- });
2069
+ child.stdin.end(prepared.input);
2070
+ try {
2071
+ const [result, stderr, exitCode] = await Promise.all([
2072
+ parse(options, child.stdout, terminate),
2073
+ captureText(child.stderr, 64 * 1024),
2074
+ exited
2075
+ ]);
2076
+ if (exitCode !== 0) {
2077
+ throw new ProviderRunError({
2078
+ provider: options.provider,
2079
+ message: failureMessage(executables[options.provider], stderr, exitCode),
2080
+ exitCode
2081
+ });
2082
+ }
2083
+ if (byteLength(result.result) > 131072) {
2084
+ throw new ProviderRunError({
2085
+ provider: options.provider,
2086
+ message: "Provider result exceeded 128 KiB"
2087
+ });
2088
+ }
2089
+ if (result.sessionId !== undefined && byteLength(result.sessionId) > 4096) {
2090
+ throw new ProviderRunError({
2091
+ provider: options.provider,
2092
+ message: "Provider session ID exceeded 4 KiB"
2093
+ });
2094
+ }
2095
+ return result;
2096
+ } finally {
2097
+ signal.removeEventListener("abort", terminate);
2098
+ if (child.exitCode === null) {
2099
+ terminate();
2100
+ await exited.catch(() => {
2101
+ return;
2102
+ });
2103
+ }
2104
+ if (killTimer !== undefined) {
2105
+ clearTimeout(killTimer);
2106
+ }
2107
+ await prepared.cleanup();
2060
2108
  }
2061
- return result;
2062
2109
  },
2063
2110
  catch: (cause) => {
2064
2111
  if (cause instanceof ProviderUnavailableError || cause instanceof ProviderRunError) {
@@ -2072,10 +2119,10 @@ var run = (options) => Effect2.tryPromise({
2072
2119
  });
2073
2120
 
2074
2121
  // apps/device/src/cli.ts
2075
- import { Effect as Effect5, Schema as Schema6 } from "effect";
2076
- import { access as access2, mkdir as mkdir2, readFile as readFile2, stat, writeFile as writeFile2 } from "node:fs/promises";
2122
+ import { Effect as Effect5, Schema as Schema7 } from "effect";
2123
+ import { access as access2, mkdir as mkdir3, readFile as readFile3, stat, writeFile as writeFile4 } from "node:fs/promises";
2077
2124
  import { homedir as homedir3 } from "node:os";
2078
- import { dirname as dirname2, join as join3 } from "node:path";
2125
+ import { dirname as dirname3, join as join4 } from "node:path";
2079
2126
 
2080
2127
  // skills/cohall/SKILL.md
2081
2128
  var SKILL_default = `---
@@ -2214,7 +2261,7 @@ Return the diagnostic failure to the user rather than claiming remote work ran.
2214
2261
  // apps/device/src/config.ts
2215
2262
  init_src();
2216
2263
  import { Effect as Effect3, Schema as Schema4 } from "effect";
2217
- import { access, chmod, mkdir, readFile, realpath, rename, writeFile } from "node:fs/promises";
2264
+ import { access, chmod as chmod2, mkdir, readFile, realpath, rename, writeFile as writeFile2 } from "node:fs/promises";
2218
2265
  import { homedir as homedir2, hostname, platform as platform2 } from "node:os";
2219
2266
  import { dirname, join as join2, resolve } from "node:path";
2220
2267
  var WorkspaceList = Schema4.Array(Schema4.NonEmptyString).check(Schema4.isMaxLength(64));
@@ -2288,7 +2335,7 @@ var readStoredConfiguration = async () => {
2288
2335
  return;
2289
2336
  }
2290
2337
  if (platform2() !== "win32") {
2291
- await chmod(path, 384);
2338
+ await chmod2(path, 384);
2292
2339
  }
2293
2340
  try {
2294
2341
  return Schema4.decodeUnknownSync(StoredConfiguration)(JSON.parse(await readFile(path, "utf8")));
@@ -2302,24 +2349,35 @@ var writeStoredConfiguration = async (configuration) => {
2302
2349
  const temporary = `${path}.${process.pid}.tmp`;
2303
2350
  await mkdir(directory, { recursive: true, mode: 448 });
2304
2351
  if (platform2() !== "win32") {
2305
- await chmod(directory, 448);
2352
+ await chmod2(directory, 448);
2306
2353
  }
2307
- await writeFile(temporary, `${JSON.stringify(configuration, null, 2)}
2354
+ await writeFile2(temporary, `${JSON.stringify(configuration, null, 2)}
2308
2355
  `, { mode: 384 });
2309
2356
  await rename(temporary, path);
2310
2357
  if (platform2() !== "win32") {
2311
- await chmod(path, 384);
2358
+ await chmod2(path, 384);
2312
2359
  }
2313
2360
  };
2361
+ var credentialsForRelay = (configuration, relayUrl) => {
2362
+ if (configuration?.relayUrl !== normalizeRelayUrl(relayUrl)) {
2363
+ return {};
2364
+ }
2365
+ return {
2366
+ ...configuration.clientToken === undefined ? {} : { clientToken: configuration.clientToken },
2367
+ ...configuration.deviceToken === undefined ? {} : { deviceToken: configuration.deviceToken }
2368
+ };
2369
+ };
2314
2370
  var makeStoredConfiguration = async (input) => {
2315
2371
  const existing = await readStoredConfiguration();
2372
+ const relayUrl = normalizeRelayUrl(input.relayUrl);
2373
+ const retainedCredentials = credentialsForRelay(existing, relayUrl);
2316
2374
  const canonicalWorkspaces = input.workspaces === undefined ? existing?.workspaces ?? [] : await Effect3.runPromise(parseWorkspaces("", JSON.stringify(input.workspaces)));
2317
- const clientToken = input.clientToken ?? existing?.clientToken;
2318
- const deviceToken = input.deviceToken ?? existing?.deviceToken;
2375
+ const clientToken = input.clientToken ?? retainedCredentials.clientToken;
2376
+ const deviceToken = input.deviceToken ?? retainedCredentials.deviceToken;
2319
2377
  const providers = input.providers === "auto" ? undefined : input.providers ?? existing?.providers;
2320
2378
  return StoredConfiguration.make({
2321
2379
  version: 1,
2322
- relayUrl: normalizeRelayUrl(input.relayUrl),
2380
+ relayUrl,
2323
2381
  deviceId: existing?.deviceId ?? makeDeviceId(),
2324
2382
  deviceName: input.deviceName ?? existing?.deviceName ?? hostname(),
2325
2383
  workspaces: canonicalWorkspaces,
@@ -2360,12 +2418,13 @@ var storedOrDefaults = async () => {
2360
2418
  var loadClientConfiguration = Effect3.tryPromise({
2361
2419
  try: async () => {
2362
2420
  const stored = await storedOrDefaults();
2363
- const token = process.env.COHALL_CLIENT_TOKEN ?? stored.clientToken;
2421
+ const relayUrl = normalizeRelayUrl(process.env.COHALL_RELAY_URL ?? stored.relayUrl);
2422
+ const token = process.env.COHALL_CLIENT_TOKEN ?? credentialsForRelay(stored, relayUrl).clientToken;
2364
2423
  if (token === undefined) {
2365
2424
  throw new Error(`No client credential. Run cohall join with a pairing token on stdin or set COHALL_CLIENT_TOKEN.`);
2366
2425
  }
2367
2426
  return ClientConfiguration.make({
2368
- relayUrl: normalizeRelayUrl(process.env.COHALL_RELAY_URL ?? stored.relayUrl),
2427
+ relayUrl,
2369
2428
  token,
2370
2429
  ...process.env.COHALL_THREAD_ID === undefined ? {} : { mcpThreadId: process.env.COHALL_THREAD_ID }
2371
2430
  });
@@ -2389,7 +2448,8 @@ var loadOwnerConfiguration = Effect3.tryPromise({
2389
2448
  var loadDeviceConfiguration = Effect3.tryPromise({
2390
2449
  try: async () => {
2391
2450
  const stored = await storedOrDefaults();
2392
- const token = process.env.COHALL_DEVICE_TOKEN ?? stored.deviceToken;
2451
+ const relayUrl = normalizeRelayUrl(process.env.COHALL_RELAY_URL ?? stored.relayUrl);
2452
+ const token = process.env.COHALL_DEVICE_TOKEN ?? credentialsForRelay(stored, relayUrl).deviceToken;
2393
2453
  if (token === undefined) {
2394
2454
  throw new Error(`No device credential. Run cohall join with a pairing token on stdin or set COHALL_DEVICE_TOKEN.`);
2395
2455
  }
@@ -2401,7 +2461,7 @@ var loadDeviceConfiguration = Effect3.tryPromise({
2401
2461
  const sandbox = environmentSandbox() ?? stored.sandbox;
2402
2462
  const providers = environmentProviders(stored);
2403
2463
  return DeviceConfiguration.make({
2404
- relayUrl: normalizeRelayUrl(process.env.COHALL_RELAY_URL ?? stored.relayUrl),
2464
+ relayUrl,
2405
2465
  token,
2406
2466
  id: process.env.COHALL_DEVICE_ID === undefined ? stored.deviceId : DeviceId.make(process.env.COHALL_DEVICE_ID),
2407
2467
  name: process.env.COHALL_DEVICE_NAME ?? stored.deviceName,
@@ -2509,6 +2569,322 @@ var threadContext = Effect4.fn("Cohall.threadContext")(function* (client, thread
2509
2569
  return yield* client.threadContext(threadId);
2510
2570
  });
2511
2571
 
2572
+ // apps/device/src/upgrade.ts
2573
+ import { Schema as Schema6 } from "effect";
2574
+ import { execFile } from "node:child_process";
2575
+ 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";
2576
+ import { dirname as dirname2, join as join3 } from "node:path";
2577
+ import { platform as operatingSystem } from "node:os";
2578
+ var packageName = "@akshar5/cohall";
2579
+ var semanticVersion = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
2580
+ var PackageManager = Schema6.Literals(["npm", "bun", "pnpm"]);
2581
+ var RestartReceipt = Schema6.Struct({
2582
+ version: Schema6.String,
2583
+ fromVersion: Schema6.String,
2584
+ packageManager: PackageManager,
2585
+ pendingServices: Schema6.Array(Schema6.String),
2586
+ restartedServices: Schema6.Array(Schema6.String)
2587
+ });
2588
+ var normalizePath = (path) => path.replaceAll("\\", "/");
2589
+ var normalizeUpgradeTarget = (target) => {
2590
+ if (target === undefined || target === "latest") {
2591
+ return "latest";
2592
+ }
2593
+ const normalized = target.startsWith("v") ? target.slice(1) : target;
2594
+ if (!semanticVersion.test(normalized)) {
2595
+ throw new Error("--to must be latest or an exact semantic version");
2596
+ }
2597
+ return normalized;
2598
+ };
2599
+ var packageInstallation = (canonicalEntrypoint, entrypoint = canonicalEntrypoint) => {
2600
+ const path = normalizePath(canonicalEntrypoint);
2601
+ const marker = `/node_modules/${packageName}/`;
2602
+ const packageIndex = path.lastIndexOf(marker);
2603
+ if (packageIndex === -1) {
2604
+ throw new Error("cohall upgrade requires a global npm, Bun, or pnpm installation; package-runner and source-checkout executions cannot upgrade themselves");
2605
+ }
2606
+ if (path.includes("/.npm/_npx/") || path.includes("/.bunx/") || path.includes("/pnpm/dlx/") || path.includes("/dlx/")) {
2607
+ throw new Error("This Cohall process is running from a temporary package-runner cache; install it globally before using cohall upgrade");
2608
+ }
2609
+ if (path.includes("/.bun/install/global/node_modules/")) {
2610
+ return { manager: "bun", entrypoint };
2611
+ }
2612
+ if (path.includes("/pnpm/global/") || path.includes("/.pnpm/")) {
2613
+ return { manager: "pnpm", entrypoint };
2614
+ }
2615
+ const nodeModulesParent = path.slice(0, packageIndex);
2616
+ const prefix = nodeModulesParent.endsWith("/lib") ? nodeModulesParent.slice(0, -"/lib".length) : nodeModulesParent;
2617
+ return { manager: "npm", prefix, entrypoint };
2618
+ };
2619
+ var packageInstallCommand = (installation, target) => {
2620
+ const specification = `${packageName}@${target}`;
2621
+ switch (installation.manager) {
2622
+ case "bun":
2623
+ return { command: "bun", arguments: ["add", "--global", specification] };
2624
+ case "pnpm":
2625
+ return { command: "pnpm", arguments: ["add", "--global", specification] };
2626
+ case "npm":
2627
+ return {
2628
+ command: "npm",
2629
+ arguments: [
2630
+ "install",
2631
+ "--global",
2632
+ ...installation.prefix === undefined ? [] : ["--prefix", installation.prefix],
2633
+ specification
2634
+ ]
2635
+ };
2636
+ }
2637
+ };
2638
+ var serviceCandidates = (runtimePlatform, uid) => {
2639
+ if (runtimePlatform === "linux") {
2640
+ const systemd = (manager, unit, device) => {
2641
+ const user = manager === "systemd-user" ? ["--user"] : [];
2642
+ return {
2643
+ id: `${manager}:${unit}`,
2644
+ label: unit,
2645
+ manager,
2646
+ device,
2647
+ check: { command: "systemctl", arguments: [...user, "is-active", "--quiet", unit] },
2648
+ restart: [{ command: "systemctl", arguments: [...user, "restart", unit] }]
2649
+ };
2650
+ };
2651
+ return [
2652
+ systemd("systemd-user", "cohall-relay.service", false),
2653
+ systemd("systemd-system", "cohall-relay.service", false),
2654
+ systemd("systemd-user", "cohall-device.service", true)
2655
+ ];
2656
+ }
2657
+ if (runtimePlatform === "darwin") {
2658
+ const domain = `gui/${uid ?? 0}`;
2659
+ const launchd = (label, device) => ({
2660
+ id: `launchd:${label}`,
2661
+ label,
2662
+ manager: "launchd",
2663
+ device,
2664
+ check: { command: "launchctl", arguments: ["print", `${domain}/${label}`] },
2665
+ activeOutput: /\bstate\s*=\s*running\b/,
2666
+ restart: [{ command: "launchctl", arguments: ["kickstart", "-k", `${domain}/${label}`] }]
2667
+ });
2668
+ return [launchd("com.cohall.relay", false), launchd("com.cohall.device", true)];
2669
+ }
2670
+ if (runtimePlatform === "win32") {
2671
+ return [
2672
+ {
2673
+ id: "scheduled-task:Cohall Device",
2674
+ label: "Cohall Device",
2675
+ manager: "scheduled-task",
2676
+ device: true,
2677
+ check: {
2678
+ command: "powershell.exe",
2679
+ arguments: [
2680
+ "-NoProfile",
2681
+ "-NonInteractive",
2682
+ "-Command",
2683
+ "if ((Get-ScheduledTask -TaskName 'Cohall Device' -ErrorAction SilentlyContinue).State -eq 'Running') { exit 0 } else { exit 1 }"
2684
+ ]
2685
+ },
2686
+ restart: [
2687
+ {
2688
+ command: "schtasks",
2689
+ arguments: ["/End", "/TN", "Cohall Device"],
2690
+ allowFailure: true
2691
+ },
2692
+ { command: "schtasks", arguments: ["/Run", "/TN", "Cohall Device"] }
2693
+ ]
2694
+ }
2695
+ ];
2696
+ }
2697
+ return [];
2698
+ };
2699
+ var defaultRunner = {
2700
+ run: (command, arguments_, timeoutMs = 300000) => new Promise((resolve2) => {
2701
+ execFile(command, [...arguments_], { encoding: "utf8", maxBuffer: 1024 * 1024, timeout: timeoutMs, windowsHide: true }, (error, stdout, stderr) => {
2702
+ resolve2({
2703
+ exitCode: typeof error?.code === "number" ? error.code : error === null ? 0 : 1,
2704
+ stdout,
2705
+ stderr,
2706
+ ...error === null ? {} : { error: error.message }
2707
+ });
2708
+ });
2709
+ })
2710
+ };
2711
+ var checked = async (runner, invocation, timeoutMs) => {
2712
+ const result = await runner.run(invocation.command, invocation.arguments, timeoutMs);
2713
+ if (result.exitCode === 0 || invocation.allowFailure === true) {
2714
+ return result;
2715
+ }
2716
+ const detail = (result.stderr.trim() || result.stdout.trim() || result.error || "unknown error").slice(0, 16384);
2717
+ throw new Error(`${invocation.command} ${invocation.arguments.join(" ")} failed with status ${result.exitCode}: ${detail}`);
2718
+ };
2719
+ var activeServices = async (runner, candidates) => {
2720
+ const active = [];
2721
+ for (const service of candidates) {
2722
+ const result = await runner.run(service.check.command, service.check.arguments, 1e4);
2723
+ if (result.exitCode === 0 && (service.activeOutput === undefined || service.activeOutput.test(`${result.stdout}
2724
+ ${result.stderr}`))) {
2725
+ active.push(service);
2726
+ }
2727
+ }
2728
+ return active;
2729
+ };
2730
+ var readReceipt = async (path) => readFile2(path, "utf8").then((content) => Schema6.decodeUnknownOption(RestartReceipt)(JSON.parse(content))).then((decoded) => decoded._tag === "Some" ? decoded.value : undefined).catch(() => {
2731
+ return;
2732
+ });
2733
+ var writeReceipt = async (path, value) => {
2734
+ const directory = dirname2(path);
2735
+ const temporary = `${path}.${process.pid}.tmp`;
2736
+ await mkdir2(directory, { recursive: true, mode: 448 });
2737
+ if (operatingSystem() !== "win32") {
2738
+ await chmod3(directory, 448);
2739
+ }
2740
+ await writeFile3(temporary, `${JSON.stringify(value, null, 2)}
2741
+ `, { mode: 384 });
2742
+ await rename2(temporary, path);
2743
+ };
2744
+ var installedVersion = async (entrypoint) => {
2745
+ const canonicalEntrypoint = await realpath2(entrypoint);
2746
+ const metadata = JSON.parse(await readFile2(join3(dirname2(dirname2(canonicalEntrypoint)), "package.json"), "utf8"));
2747
+ if (typeof metadata !== "object" || metadata === null || !("name" in metadata) || metadata.name !== packageName || !("version" in metadata) || typeof metadata.version !== "string") {
2748
+ throw new Error(`Could not verify the installed ${packageName} version`);
2749
+ }
2750
+ return metadata.version;
2751
+ };
2752
+ var restartReceiptPath = () => join3(dirname2(configurationPath()), "upgrade-restart.json");
2753
+ var restartServices = async (runner, services, statePath, state) => {
2754
+ let current = state;
2755
+ for (const service of services) {
2756
+ for (const invocation of service.restart) {
2757
+ await checked(runner, invocation, 60000);
2758
+ }
2759
+ current = {
2760
+ ...current,
2761
+ pendingServices: current.pendingServices.filter((id) => id !== service.id),
2762
+ restartedServices: [...current.restartedServices, service.id]
2763
+ };
2764
+ await writeReceipt(statePath, current);
2765
+ }
2766
+ return current;
2767
+ };
2768
+ var upgrade = async (options) => {
2769
+ const runner = options.runner ?? defaultRunner;
2770
+ const runtimePlatform = options.platform ?? process.platform;
2771
+ const uid = options.uid ?? process.getuid?.();
2772
+ const candidates = serviceCandidates(runtimePlatform, uid);
2773
+ const statePath = options.statePath ?? restartReceiptPath();
2774
+ const previous = await readReceipt(statePath);
2775
+ if (previous?.version === options.currentVersion) {
2776
+ const byId = new Map(candidates.map((service) => [service.id, service]));
2777
+ const pending = previous.pendingServices.flatMap((id) => {
2778
+ const service = byId.get(id);
2779
+ return service === undefined ? [] : [service];
2780
+ });
2781
+ const active = await activeServices(runner, pending);
2782
+ if (options.dryRun || !options.restart) {
2783
+ return {
2784
+ upgraded: previous.fromVersion !== previous.version,
2785
+ from_version: previous.fromVersion,
2786
+ installed_version: previous.version,
2787
+ requested_version: previous.version,
2788
+ package_manager: previous.packageManager,
2789
+ services_restarted: previous.restartedServices,
2790
+ services_pending_restart: active.map((service) => service.id),
2791
+ resumed_after_restart: false,
2792
+ dry_run: options.dryRun
2793
+ };
2794
+ }
2795
+ const activeIds = new Set(active.map((service) => service.id));
2796
+ const completedByRestart = options.delegated === true ? pending.filter((service) => service.device && activeIds.has(service.id)) : [];
2797
+ const completedIds = new Set(completedByRestart.map((service) => service.id));
2798
+ const remaining = active.filter((service) => !completedIds.has(service.id));
2799
+ let resumed = {
2800
+ ...previous,
2801
+ pendingServices: previous.pendingServices.filter((id) => !completedIds.has(id)),
2802
+ restartedServices: [
2803
+ ...previous.restartedServices,
2804
+ ...completedByRestart.map((service) => service.id)
2805
+ ]
2806
+ };
2807
+ if (remaining.length > 0) {
2808
+ await writeReceipt(statePath, resumed);
2809
+ resumed = await restartServices(runner, remaining, statePath, resumed);
2810
+ }
2811
+ await rm2(statePath, { force: true });
2812
+ return {
2813
+ upgraded: previous.fromVersion !== previous.version,
2814
+ from_version: previous.fromVersion,
2815
+ installed_version: previous.version,
2816
+ requested_version: previous.version,
2817
+ package_manager: previous.packageManager,
2818
+ services_restarted: resumed.restartedServices,
2819
+ services_pending_restart: [],
2820
+ resumed_after_restart: true,
2821
+ dry_run: false
2822
+ };
2823
+ }
2824
+ await rm2(statePath, { force: true });
2825
+ const target = normalizeUpgradeTarget(options.target);
2826
+ const entrypoint = options.entrypoint ?? process.argv[1];
2827
+ if (entrypoint === undefined) {
2828
+ throw new Error("Could not resolve the Cohall executable path");
2829
+ }
2830
+ const installation = packageInstallation(await realpath2(entrypoint), entrypoint);
2831
+ const install = packageInstallCommand(installation, target);
2832
+ const services = await activeServices(runner, candidates);
2833
+ if (options.dryRun) {
2834
+ return {
2835
+ upgraded: false,
2836
+ from_version: options.currentVersion,
2837
+ installed_version: options.currentVersion,
2838
+ requested_version: target,
2839
+ package_manager: installation.manager,
2840
+ services_restarted: [],
2841
+ services_pending_restart: options.restart ? services.map((service) => service.id) : [],
2842
+ resumed_after_restart: false,
2843
+ dry_run: true
2844
+ };
2845
+ }
2846
+ await checked(runner, install);
2847
+ const nextVersion = await installedVersion(entrypoint);
2848
+ if (target !== "latest" && nextVersion !== target) {
2849
+ throw new Error(`Installed Cohall ${nextVersion}, expected ${target}`);
2850
+ }
2851
+ const upgraded = nextVersion !== options.currentVersion;
2852
+ if (!upgraded || !options.restart || services.length === 0) {
2853
+ return {
2854
+ upgraded,
2855
+ from_version: options.currentVersion,
2856
+ installed_version: nextVersion,
2857
+ requested_version: target,
2858
+ package_manager: installation.manager,
2859
+ services_restarted: [],
2860
+ services_pending_restart: upgraded && !options.restart ? services.map((service) => service.id) : [],
2861
+ resumed_after_restart: false,
2862
+ dry_run: false
2863
+ };
2864
+ }
2865
+ const initial = {
2866
+ version: nextVersion,
2867
+ fromVersion: options.currentVersion,
2868
+ packageManager: installation.manager,
2869
+ pendingServices: services.map((service) => service.id),
2870
+ restartedServices: []
2871
+ };
2872
+ await writeReceipt(statePath, initial);
2873
+ const completed = await restartServices(runner, services, statePath, initial);
2874
+ await rm2(statePath, { force: true });
2875
+ return {
2876
+ upgraded: true,
2877
+ from_version: options.currentVersion,
2878
+ installed_version: nextVersion,
2879
+ requested_version: target,
2880
+ package_manager: installation.manager,
2881
+ services_restarted: completed.restartedServices,
2882
+ services_pending_restart: [],
2883
+ resumed_after_restart: false,
2884
+ dry_run: false
2885
+ };
2886
+ };
2887
+
2512
2888
  // apps/device/src/cli.ts
2513
2889
  var valueOptions = new Set([
2514
2890
  "context",
@@ -2526,9 +2902,10 @@ var valueOptions = new Set([
2526
2902
  "thread",
2527
2903
  "timeout",
2528
2904
  "token-file",
2905
+ "to",
2529
2906
  "workspace"
2530
2907
  ]);
2531
- var flagOptions = new Set(["client-only", "follow", "no-wait"]);
2908
+ var flagOptions = new Set(["client-only", "dry-run", "follow", "no-restart", "no-wait"]);
2532
2909
  var aliases = new Map([
2533
2910
  ["-c", "context"],
2534
2911
  ["-p", "prompt"],
@@ -2556,6 +2933,7 @@ Usage:
2556
2933
  cohall sessions
2557
2934
  cohall revoke <session-id>
2558
2935
  cohall doctor
2936
+ cohall upgrade [--to version|latest] [--no-restart] [--dry-run]
2559
2937
  cohall device
2560
2938
  cohall relay
2561
2939
  cohall mcp
@@ -2628,9 +3006,9 @@ var allowOptions = (arguments_, names) => {
2628
3006
  }
2629
3007
  }
2630
3008
  };
2631
- var noPositionals = (arguments_, command2) => {
3009
+ var noPositionals = (arguments_, command) => {
2632
3010
  if (arguments_.positionals.length > 0) {
2633
- throw new Error(`${command2} does not accept positional arguments`);
3011
+ throw new Error(`${command} does not accept positional arguments`);
2634
3012
  }
2635
3013
  };
2636
3014
  var identifier = (arguments_, label) => {
@@ -2658,7 +3036,7 @@ var readInput = async (arguments_, name) => {
2658
3036
  if ((await stat(path)).size > 131072) {
2659
3037
  throw new Error(`${name} file exceeds 128 KiB`);
2660
3038
  }
2661
- return readFile2(path, "utf8");
3039
+ return readFile3(path, "utf8");
2662
3040
  }
2663
3041
  const result = direct === "-" ? await readStdin(131072, name) : direct;
2664
3042
  if (result !== undefined && result.length > 131072) {
@@ -2694,7 +3072,7 @@ var pairingToken = async (arguments_) => {
2694
3072
  if (path !== undefined && (await stat(path)).size > 512) {
2695
3073
  throw new Error("Pairing token file exceeds 512 bytes");
2696
3074
  }
2697
- const token = (path === undefined ? await readStdin(512, "Pairing token") : await readFile2(path, "utf8")).trim();
3075
+ const token = (path === undefined ? await readStdin(512, "Pairing token") : await readFile3(path, "utf8")).trim();
2698
3076
  if (token.length === 0 || new TextEncoder().encode(token).byteLength > 256) {
2699
3077
  throw new Error("Provide a pairing token on stdin or with --token-file");
2700
3078
  }
@@ -2716,25 +3094,38 @@ var ownerClient = async () => {
2716
3094
  };
2717
3095
  var installSkill = async (target) => {
2718
3096
  const roots = {
2719
- agents: join3(homedir3(), ".agents", "skills", "cohall", "SKILL.md"),
2720
- claude: join3(homedir3(), ".claude", "skills", "cohall", "SKILL.md"),
2721
- opencode: join3(homedir3(), ".config", "opencode", "skills", "cohall", "SKILL.md")
3097
+ agents: join4(homedir3(), ".agents", "skills", "cohall", "SKILL.md"),
3098
+ claude: join4(homedir3(), ".claude", "skills", "cohall", "SKILL.md"),
3099
+ opencode: join4(homedir3(), ".config", "opencode", "skills", "cohall", "SKILL.md")
2722
3100
  };
2723
3101
  const selected = target === "all" ? Object.values(roots) : (target in roots) ? [roots[target]] : undefined;
2724
3102
  if (selected === undefined) {
2725
3103
  throw new Error("Skill target must be agents, claude, opencode, or all");
2726
3104
  }
2727
3105
  for (const path of selected) {
2728
- await mkdir2(dirname2(path), { recursive: true });
2729
- await writeFile2(path, SKILL_default);
3106
+ await mkdir3(dirname3(path), { recursive: true });
3107
+ await writeFile4(path, SKILL_default);
2730
3108
  }
2731
3109
  return selected;
2732
3110
  };
2733
3111
  var printHelp = () => console.log(help);
2734
3112
  var printSkill = () => console.log(SKILL_default.trimEnd());
2735
- var runCli = async (command2, raw) => {
3113
+ var runCli = async (command, raw) => {
2736
3114
  const arguments_ = parseArguments(raw);
2737
- if (command2 === "join") {
3115
+ if (command === "upgrade") {
3116
+ allowOptions(arguments_, ["dry-run", "no-restart", "to"]);
3117
+ noPositionals(arguments_, command);
3118
+ const target = option(arguments_, "to");
3119
+ print(await upgrade({
3120
+ currentVersion: version,
3121
+ ...target === undefined ? {} : { target },
3122
+ restart: !arguments_.options.has("no-restart"),
3123
+ dryRun: arguments_.options.has("dry-run"),
3124
+ delegated: process.env.COHALL_PROVIDER !== undefined
3125
+ }));
3126
+ return;
3127
+ }
3128
+ if (command === "join") {
2738
3129
  allowOptions(arguments_, [
2739
3130
  "client-only",
2740
3131
  "name",
@@ -2743,7 +3134,7 @@ var runCli = async (command2, raw) => {
2743
3134
  "token-file",
2744
3135
  "workspace"
2745
3136
  ]);
2746
- noPositionals(arguments_, command2);
3137
+ noPositionals(arguments_, command);
2747
3138
  const token = await pairingToken(arguments_);
2748
3139
  const relayUrl = normalizeRelayUrl(option(arguments_, "relay") ?? "http://127.0.0.1:8787");
2749
3140
  const existing = await readStoredConfiguration();
@@ -2785,9 +3176,9 @@ var runCli = async (command2, raw) => {
2785
3176
  });
2786
3177
  return;
2787
3178
  }
2788
- if (command2 === "config") {
3179
+ if (command === "config") {
2789
3180
  allowOptions(arguments_, []);
2790
- noPositionals(arguments_, command2);
3181
+ noPositionals(arguments_, command);
2791
3182
  const configuration2 = await readStoredConfiguration();
2792
3183
  print({
2793
3184
  config_path: configurationPath(),
@@ -2806,9 +3197,9 @@ var runCli = async (command2, raw) => {
2806
3197
  });
2807
3198
  return;
2808
3199
  }
2809
- if (command2 === "configure") {
3200
+ if (command === "configure") {
2810
3201
  allowOptions(arguments_, ["model", "name", "providers", "relay", "sandbox", "workspace"]);
2811
- noPositionals(arguments_, command2);
3202
+ noPositionals(arguments_, command);
2812
3203
  const existing = await readStoredConfiguration();
2813
3204
  const relayUrl = normalizeRelayUrl(option(arguments_, "relay") ?? existing?.relayUrl ?? "http://127.0.0.1:8787");
2814
3205
  const supplied = values(arguments_, "workspace");
@@ -2823,19 +3214,18 @@ var runCli = async (command2, raw) => {
2823
3214
  deviceId: existing?.deviceId ?? makeDeviceId(),
2824
3215
  deviceName: option(arguments_, "name") ?? existing?.deviceName ?? "cohall-device",
2825
3216
  workspaces,
2826
- ...existing?.clientToken === undefined ? {} : { clientToken: existing.clientToken },
2827
- ...existing?.deviceToken === undefined ? {} : { deviceToken: existing.deviceToken },
3217
+ ...credentialsForRelay(existing, relayUrl),
2828
3218
  ...providers === undefined ? {} : { providers },
2829
3219
  ...model === undefined ? {} : { model },
2830
3220
  ...(sandbox ?? existing?.sandbox) === undefined ? {} : {
2831
- sandbox: Schema6.decodeUnknownSync(Schema6.Literals(["read-only", "workspace-write", "danger-full-access"]))(sandbox ?? existing?.sandbox)
3221
+ sandbox: Schema7.decodeUnknownSync(Schema7.Literals(["read-only", "workspace-write", "danger-full-access"]))(sandbox ?? existing?.sandbox)
2832
3222
  }
2833
3223
  });
2834
3224
  await writeStoredConfiguration(configuration2);
2835
3225
  print({ configured: true, config_path: configurationPath() });
2836
3226
  return;
2837
3227
  }
2838
- if (command2 === "skill") {
3228
+ if (command === "skill") {
2839
3229
  allowOptions(arguments_, []);
2840
3230
  if (arguments_.positionals.length === 0) {
2841
3231
  printSkill();
@@ -2847,9 +3237,9 @@ var runCli = async (command2, raw) => {
2847
3237
  print({ installed: await installSkill(arguments_.positionals[1] ?? "all") });
2848
3238
  return;
2849
3239
  }
2850
- if (command2 === "integrations") {
3240
+ if (command === "integrations") {
2851
3241
  allowOptions(arguments_, []);
2852
- noPositionals(arguments_, command2);
3242
+ noPositionals(arguments_, command);
2853
3243
  print({
2854
3244
  cli_skill: "npx -y @akshar5/cohall skill install all",
2855
3245
  codex_mcp: "codex mcp add cohall -- npx -y @akshar5/cohall mcp",
@@ -2866,11 +3256,12 @@ var runCli = async (command2, raw) => {
2866
3256
  });
2867
3257
  return;
2868
3258
  }
2869
- if (command2 === "doctor") {
3259
+ if (command === "doctor") {
2870
3260
  allowOptions(arguments_, []);
2871
- noPositionals(arguments_, command2);
3261
+ noPositionals(arguments_, command);
2872
3262
  const configuration2 = await readStoredConfiguration();
2873
3263
  const relayUrl = normalizeRelayUrl(process.env.COHALL_RELAY_URL ?? configuration2?.relayUrl ?? "http://127.0.0.1:8787");
3264
+ const storedCredentials = credentialsForRelay(configuration2, relayUrl);
2874
3265
  const response = await fetch(`${relayUrl}/api/health`, {
2875
3266
  signal: AbortSignal.timeout(1e4)
2876
3267
  }).catch(() => {
@@ -2882,8 +3273,8 @@ var runCli = async (command2, raw) => {
2882
3273
  provider,
2883
3274
  findExecutable(provider === "claude-code" ? "claude" : provider) ?? "not found"
2884
3275
  ]));
2885
- const clientToken = process.env.COHALL_CLIENT_TOKEN ?? configuration2?.clientToken;
2886
- const hasDeviceCredential = process.env.COHALL_DEVICE_TOKEN !== undefined || configuration2?.deviceToken !== undefined;
3276
+ const clientToken = process.env.COHALL_CLIENT_TOKEN ?? storedCredentials.clientToken;
3277
+ const hasDeviceCredential = process.env.COHALL_DEVICE_TOKEN !== undefined || storedCredentials.deviceToken !== undefined;
2887
3278
  const deviceId = process.env.COHALL_DEVICE_ID ?? configuration2?.deviceId;
2888
3279
  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(() => {
2889
3280
  return;
@@ -2900,7 +3291,7 @@ var runCli = async (command2, raw) => {
2900
3291
  relay: response?.ok === true ? "reachable" : "unreachable",
2901
3292
  relay_url: relayUrl,
2902
3293
  config_path: configurationPath(),
2903
- client_credential: process.env.COHALL_CLIENT_TOKEN !== undefined || configuration2?.clientToken !== undefined,
3294
+ client_credential: process.env.COHALL_CLIENT_TOKEN !== undefined || storedCredentials.clientToken !== undefined,
2904
3295
  device_credential: hasDeviceCredential,
2905
3296
  workspaces: configuration2?.workspaces ?? [],
2906
3297
  providers: providerExecutables,
@@ -2912,11 +3303,11 @@ var runCli = async (command2, raw) => {
2912
3303
  });
2913
3304
  return;
2914
3305
  }
2915
- if (command2 === "pair" || command2 === "sessions" || command2 === "revoke") {
3306
+ if (command === "pair" || command === "sessions" || command === "revoke") {
2916
3307
  const { configuration: configuration2, relay: relay2 } = await ownerClient();
2917
- if (command2 === "pair") {
3308
+ if (command === "pair") {
2918
3309
  allowOptions(arguments_, ["client-only", "label"]);
2919
- noPositionals(arguments_, command2);
3310
+ noPositionals(arguments_, command);
2920
3311
  const roles = arguments_.options.has("client-only") ? ["client"] : ["client", "device"];
2921
3312
  const credential = await Effect5.runPromise(relay2.createPairing({ label: option(arguments_, "label") ?? "Cohall device", roles }));
2922
3313
  print({
@@ -2927,24 +3318,24 @@ var runCli = async (command2, raw) => {
2927
3318
  });
2928
3319
  return;
2929
3320
  }
2930
- if (command2 === "sessions") {
3321
+ if (command === "sessions") {
2931
3322
  allowOptions(arguments_, []);
2932
- noPositionals(arguments_, command2);
3323
+ noPositionals(arguments_, command);
2933
3324
  print(await Effect5.runPromise(relay2.authSessions()));
2934
3325
  return;
2935
3326
  }
2936
3327
  allowOptions(arguments_, []);
2937
- print(await Effect5.runPromise(relay2.revokeAuthSession(Schema6.decodeUnknownSync(AuthSessionId)(identifier(arguments_, "session id")))));
3328
+ print(await Effect5.runPromise(relay2.revokeAuthSession(Schema7.decodeUnknownSync(AuthSessionId)(identifier(arguments_, "session id")))));
2938
3329
  return;
2939
3330
  }
2940
3331
  const { configuration, relay } = await client();
2941
- if (command2 === "devices") {
3332
+ if (command === "devices") {
2942
3333
  allowOptions(arguments_, []);
2943
- noPositionals(arguments_, command2);
3334
+ noPositionals(arguments_, command);
2944
3335
  print(await Effect5.runPromise(relay.devices()));
2945
3336
  return;
2946
3337
  }
2947
- if (command2 === "delegate") {
3338
+ if (command === "delegate") {
2948
3339
  allowOptions(arguments_, [
2949
3340
  "context",
2950
3341
  "context-file",
@@ -2977,22 +3368,22 @@ var runCli = async (command2, raw) => {
2977
3368
  prompt,
2978
3369
  ...target === undefined ? {} : { target },
2979
3370
  ...context === undefined ? {} : { context },
2980
- ...thread === undefined ? {} : { threadId: Schema6.decodeUnknownSync(ThreadId)(thread) },
3371
+ ...thread === undefined ? {} : { threadId: Schema7.decodeUnknownSync(ThreadId)(thread) },
2981
3372
  ...workspace === undefined ? {} : { workspace },
2982
- ...provider === undefined ? {} : { provider: Schema6.decodeUnknownSync(Provider)(provider) }
3373
+ ...provider === undefined ? {} : { provider: Schema7.decodeUnknownSync(Provider)(provider) }
2983
3374
  }));
2984
3375
  print(taskResult(arguments_.options.has("no-wait") ? task : await Effect5.runPromise(waitForTask(relay, task, timeout(arguments_)))));
2985
3376
  return;
2986
3377
  }
2987
- if (command2 === "status") {
3378
+ if (command === "status") {
2988
3379
  allowOptions(arguments_, []);
2989
- const id = Schema6.decodeUnknownSync(TaskId)(identifier(arguments_, "task id"));
3380
+ const id = Schema7.decodeUnknownSync(TaskId)(identifier(arguments_, "task id"));
2990
3381
  print(taskResult(await Effect5.runPromise(relay.getTask(id))));
2991
3382
  return;
2992
3383
  }
2993
- if (command2 === "trace") {
3384
+ if (command === "trace") {
2994
3385
  allowOptions(arguments_, ["follow"]);
2995
- const id = Schema6.decodeUnknownSync(TaskId)(identifier(arguments_, "task id"));
3386
+ const id = Schema7.decodeUnknownSync(TaskId)(identifier(arguments_, "task id"));
2996
3387
  if (arguments_.options.has("follow")) {
2997
3388
  await Effect5.runPromise(followTaskTrace(relay, id, printLine));
2998
3389
  return;
@@ -3000,37 +3391,37 @@ var runCli = async (command2, raw) => {
3000
3391
  print(await Effect5.runPromise(relay.traceTask(id)));
3001
3392
  return;
3002
3393
  }
3003
- if (command2 === "wait") {
3394
+ if (command === "wait") {
3004
3395
  allowOptions(arguments_, ["timeout"]);
3005
- const id = Schema6.decodeUnknownSync(TaskId)(identifier(arguments_, "task id"));
3396
+ const id = Schema7.decodeUnknownSync(TaskId)(identifier(arguments_, "task id"));
3006
3397
  const task = await Effect5.runPromise(relay.getTask(id));
3007
3398
  print(taskResult(await Effect5.runPromise(waitForTask(relay, task, timeout(arguments_)))));
3008
3399
  return;
3009
3400
  }
3010
- if (command2 === "cancel") {
3401
+ if (command === "cancel") {
3011
3402
  allowOptions(arguments_, []);
3012
- const id = Schema6.decodeUnknownSync(TaskId)(identifier(arguments_, "task id"));
3403
+ const id = Schema7.decodeUnknownSync(TaskId)(identifier(arguments_, "task id"));
3013
3404
  print(taskResult(await Effect5.runPromise(relay.cancelTask(id))));
3014
3405
  return;
3015
3406
  }
3016
- if (command2 === "thread") {
3407
+ if (command === "thread") {
3017
3408
  allowOptions(arguments_, []);
3018
- const id = Schema6.decodeUnknownSync(ThreadId)(identifier(arguments_, "thread id"));
3409
+ const id = Schema7.decodeUnknownSync(ThreadId)(identifier(arguments_, "thread id"));
3019
3410
  print(await Effect5.runPromise(threadContext(relay, id)));
3020
3411
  return;
3021
3412
  }
3022
- throw new Error(`Unknown Cohall command: ${command2}`);
3413
+ throw new Error(`Unknown Cohall command: ${command}`);
3023
3414
  };
3024
3415
 
3025
3416
  // apps/device/src/daemon.ts
3026
3417
  init_src();
3027
- import { Effect as Effect6, Schedule as Schedule2, Schema as Schema7 } from "effect";
3028
- import { realpath as realpath2 } from "node:fs/promises";
3418
+ import { Effect as Effect6, Schedule as Schedule2, Schema as Schema8 } from "effect";
3419
+ import { realpath as realpath3 } from "node:fs/promises";
3029
3420
  import { arch, hostname as hostname2, platform as platform3 } from "node:os";
3030
3421
  import { basename, isAbsolute as isAbsolute2, relative } from "node:path";
3031
3422
  import { WebSocket } from "ws";
3032
3423
 
3033
- class DeviceConnectionError extends Schema7.TaggedErrorClass()("Device.ConnectionError", { message: Schema7.String }) {
3424
+ class DeviceConnectionError extends Schema8.TaggedErrorClass()("Device.ConnectionError", { message: Schema8.String }) {
3034
3425
  }
3035
3426
  var socketUrl = (configuration) => {
3036
3427
  const url = new URL(configuration.relayUrl);
@@ -3067,8 +3458,8 @@ var capabilities = (providers) => {
3067
3458
  };
3068
3459
  var selectProviders = (installed, configured) => configured === undefined ? installed : configured.filter((provider) => installed.includes(provider));
3069
3460
  var describeDevice = (configuration, status = "online") => {
3070
- const operatingSystem = platform3();
3071
- const platformName = operatingSystem === "darwin" || operatingSystem === "linux" ? operatingSystem : operatingSystem === "win32" ? "windows" : "unknown";
3461
+ const operatingSystem2 = platform3();
3462
+ const platformName = operatingSystem2 === "darwin" || operatingSystem2 === "linux" ? operatingSystem2 : operatingSystem2 === "win32" ? "windows" : "unknown";
3072
3463
  const installed = availableProviders();
3073
3464
  const providers = selectProviders(installed, configuration.providers);
3074
3465
  return Device.make({
@@ -3093,7 +3484,7 @@ var allowedWorkspace = async (configuration, requested) => {
3093
3484
  if (selected === undefined) {
3094
3485
  throw new Error("No workspace is configured on this device");
3095
3486
  }
3096
- const candidate = await realpath2(selected);
3487
+ const candidate = await realpath3(selected);
3097
3488
  const allowed = configuration.workspaces.some((root) => {
3098
3489
  const child = relative(root, candidate);
3099
3490
  return child === "" || !child.startsWith("..") && !isAbsolute2(child);
@@ -3396,43 +3787,43 @@ var runMcp = async (configuration) => {
3396
3787
 
3397
3788
  // apps/device/src/main.ts
3398
3789
  var main = async () => {
3399
- const command2 = process.argv[2];
3400
- if (command2 === undefined || command2 === "help" || command2 === "--help" || command2 === "-h") {
3790
+ const command = process.argv[2];
3791
+ if (command === undefined || command === "help" || command === "--help" || command === "-h") {
3401
3792
  printHelp();
3402
3793
  return;
3403
3794
  }
3404
- if (command2 === "--version" || command2 === "version") {
3795
+ if (command === "--version" || command === "version") {
3405
3796
  console.log(version);
3406
3797
  return;
3407
3798
  }
3408
- if (["device", "relay", "mcp"].includes(command2) && process.argv.length > 3) {
3799
+ if (["device", "relay", "mcp"].includes(command) && process.argv.length > 3) {
3409
3800
  const arguments_ = process.argv.slice(3);
3410
3801
  if (arguments_.length === 1 && ["--help", "-h", "help"].includes(arguments_[0] ?? "")) {
3411
3802
  printHelp();
3412
3803
  return;
3413
3804
  }
3414
- throw new Error(`${command2} does not accept arguments`);
3805
+ throw new Error(`${command} does not accept arguments`);
3415
3806
  }
3416
- if (command2 === "relay") {
3807
+ if (command === "relay") {
3417
3808
  const { runRelay: runRelay2 } = await init_main().then(() => exports_main);
3418
3809
  await runRelay2();
3419
3810
  return;
3420
3811
  }
3421
- if (command2 === "device") {
3812
+ if (command === "device") {
3422
3813
  const configuration = await Effect11.runPromise(loadDeviceConfiguration);
3423
3814
  await Effect11.runPromise(runDaemon(configuration));
3424
3815
  return;
3425
3816
  }
3426
- if (command2 === "mcp") {
3817
+ if (command === "mcp") {
3427
3818
  const configuration = await Effect11.runPromise(loadClientConfiguration);
3428
3819
  await runMcp(configuration);
3429
3820
  return;
3430
3821
  }
3431
- await runCli(command2, process.argv.slice(3));
3822
+ await runCli(command, process.argv.slice(3));
3432
3823
  };
3433
3824
  await main().catch((cause) => {
3434
3825
  console.error(JSON.stringify({ error: cause instanceof Error ? cause.message : String(cause) }, null, 2));
3435
3826
  process.exitCode = 1;
3436
3827
  });
3437
3828
 
3438
- //# debugId=D325B2522A94114164756E2164756E21
3829
+ //# debugId=C3D1F939F21B764D64756E2164756E21