@akshar5/cohall 0.3.4 → 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.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.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,7 +1738,8 @@ 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
1745
  import { createInterface } from "node:readline";
@@ -1879,23 +1880,50 @@ var claudeCommand = (options) => [
1879
1880
  ...options.model === undefined ? [] : ["--model", options.model],
1880
1881
  ...options.sessionId === undefined ? [] : ["--resume", options.sessionId]
1881
1882
  ];
1882
- var opencodeCommand = (options) => [
1883
+ var opencodeCommand = (options, promptPath) => [
1883
1884
  "opencode",
1884
1885
  "run",
1885
1886
  "--format",
1886
1887
  "json",
1887
1888
  ...options.model === undefined ? [] : ["--model", options.model],
1888
1889
  ...options.sessionId === undefined ? [] : ["--session", options.sessionId],
1889
- options.prompt
1890
+ "--file",
1891
+ promptPath,
1892
+ "Complete the task described in the attached Cohall prompt file."
1890
1893
  ];
1891
- var command = (options) => {
1894
+ var prepareCommand = async (options) => {
1892
1895
  switch (options.provider) {
1893
1896
  case "codex":
1894
- return codexCommand(options);
1897
+ return {
1898
+ command: codexCommand(options),
1899
+ input: options.prompt,
1900
+ cleanup: () => Promise.resolve()
1901
+ };
1895
1902
  case "claude-code":
1896
- return claudeCommand(options);
1897
- case "opencode":
1898
- 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
+ }
1899
1927
  }
1900
1928
  };
1901
1929
  var forEachJsonEvent = async (stdout, onEvent) => {
@@ -2002,14 +2030,15 @@ var parse = async (options, stdout, onFailure) => {
2002
2030
  var byteLength = (value) => new TextEncoder().encode(value).byteLength;
2003
2031
  var run = (options) => Effect2.tryPromise({
2004
2032
  try: async (signal) => {
2005
- const [commandName, ...arguments_] = command(options);
2006
- const executable = commandName === undefined ? undefined : findExecutable(commandName);
2033
+ const executable = findExecutable(executables[options.provider]);
2007
2034
  if (executable === undefined) {
2008
2035
  throw new ProviderUnavailableError({
2009
2036
  provider: options.provider,
2010
2037
  message: `${executables[options.provider]} is not available on this device`
2011
2038
  });
2012
2039
  }
2040
+ const prepared = await prepareCommand(options);
2041
+ const [, ...arguments_] = prepared.command;
2013
2042
  const child = spawn(executable, arguments_, {
2014
2043
  cwd: options.cwd,
2015
2044
  env: {
@@ -2037,36 +2066,46 @@ var run = (options) => Effect2.tryPromise({
2037
2066
  killTimer.unref();
2038
2067
  };
2039
2068
  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
- });
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();
2068
2108
  }
2069
- return result;
2070
2109
  },
2071
2110
  catch: (cause) => {
2072
2111
  if (cause instanceof ProviderUnavailableError || cause instanceof ProviderRunError) {
@@ -2080,10 +2119,10 @@ var run = (options) => Effect2.tryPromise({
2080
2119
  });
2081
2120
 
2082
2121
  // 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";
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";
2085
2124
  import { homedir as homedir3 } from "node:os";
2086
- import { dirname as dirname2, join as join3 } from "node:path";
2125
+ import { dirname as dirname3, join as join4 } from "node:path";
2087
2126
 
2088
2127
  // skills/cohall/SKILL.md
2089
2128
  var SKILL_default = `---
@@ -2222,7 +2261,7 @@ Return the diagnostic failure to the user rather than claiming remote work ran.
2222
2261
  // apps/device/src/config.ts
2223
2262
  init_src();
2224
2263
  import { Effect as Effect3, Schema as Schema4 } from "effect";
2225
- 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";
2226
2265
  import { homedir as homedir2, hostname, platform as platform2 } from "node:os";
2227
2266
  import { dirname, join as join2, resolve } from "node:path";
2228
2267
  var WorkspaceList = Schema4.Array(Schema4.NonEmptyString).check(Schema4.isMaxLength(64));
@@ -2296,7 +2335,7 @@ var readStoredConfiguration = async () => {
2296
2335
  return;
2297
2336
  }
2298
2337
  if (platform2() !== "win32") {
2299
- await chmod(path, 384);
2338
+ await chmod2(path, 384);
2300
2339
  }
2301
2340
  try {
2302
2341
  return Schema4.decodeUnknownSync(StoredConfiguration)(JSON.parse(await readFile(path, "utf8")));
@@ -2310,24 +2349,35 @@ var writeStoredConfiguration = async (configuration) => {
2310
2349
  const temporary = `${path}.${process.pid}.tmp`;
2311
2350
  await mkdir(directory, { recursive: true, mode: 448 });
2312
2351
  if (platform2() !== "win32") {
2313
- await chmod(directory, 448);
2352
+ await chmod2(directory, 448);
2314
2353
  }
2315
- await writeFile(temporary, `${JSON.stringify(configuration, null, 2)}
2354
+ await writeFile2(temporary, `${JSON.stringify(configuration, null, 2)}
2316
2355
  `, { mode: 384 });
2317
2356
  await rename(temporary, path);
2318
2357
  if (platform2() !== "win32") {
2319
- await chmod(path, 384);
2358
+ await chmod2(path, 384);
2359
+ }
2360
+ };
2361
+ var credentialsForRelay = (configuration, relayUrl) => {
2362
+ if (configuration?.relayUrl !== normalizeRelayUrl(relayUrl)) {
2363
+ return {};
2320
2364
  }
2365
+ return {
2366
+ ...configuration.clientToken === undefined ? {} : { clientToken: configuration.clientToken },
2367
+ ...configuration.deviceToken === undefined ? {} : { deviceToken: configuration.deviceToken }
2368
+ };
2321
2369
  };
2322
2370
  var makeStoredConfiguration = async (input) => {
2323
2371
  const existing = await readStoredConfiguration();
2372
+ const relayUrl = normalizeRelayUrl(input.relayUrl);
2373
+ const retainedCredentials = credentialsForRelay(existing, relayUrl);
2324
2374
  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;
2375
+ const clientToken = input.clientToken ?? retainedCredentials.clientToken;
2376
+ const deviceToken = input.deviceToken ?? retainedCredentials.deviceToken;
2327
2377
  const providers = input.providers === "auto" ? undefined : input.providers ?? existing?.providers;
2328
2378
  return StoredConfiguration.make({
2329
2379
  version: 1,
2330
- relayUrl: normalizeRelayUrl(input.relayUrl),
2380
+ relayUrl,
2331
2381
  deviceId: existing?.deviceId ?? makeDeviceId(),
2332
2382
  deviceName: input.deviceName ?? existing?.deviceName ?? hostname(),
2333
2383
  workspaces: canonicalWorkspaces,
@@ -2368,12 +2418,13 @@ var storedOrDefaults = async () => {
2368
2418
  var loadClientConfiguration = Effect3.tryPromise({
2369
2419
  try: async () => {
2370
2420
  const stored = await storedOrDefaults();
2371
- 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;
2372
2423
  if (token === undefined) {
2373
2424
  throw new Error(`No client credential. Run cohall join with a pairing token on stdin or set COHALL_CLIENT_TOKEN.`);
2374
2425
  }
2375
2426
  return ClientConfiguration.make({
2376
- relayUrl: normalizeRelayUrl(process.env.COHALL_RELAY_URL ?? stored.relayUrl),
2427
+ relayUrl,
2377
2428
  token,
2378
2429
  ...process.env.COHALL_THREAD_ID === undefined ? {} : { mcpThreadId: process.env.COHALL_THREAD_ID }
2379
2430
  });
@@ -2397,7 +2448,8 @@ var loadOwnerConfiguration = Effect3.tryPromise({
2397
2448
  var loadDeviceConfiguration = Effect3.tryPromise({
2398
2449
  try: async () => {
2399
2450
  const stored = await storedOrDefaults();
2400
- 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;
2401
2453
  if (token === undefined) {
2402
2454
  throw new Error(`No device credential. Run cohall join with a pairing token on stdin or set COHALL_DEVICE_TOKEN.`);
2403
2455
  }
@@ -2409,7 +2461,7 @@ var loadDeviceConfiguration = Effect3.tryPromise({
2409
2461
  const sandbox = environmentSandbox() ?? stored.sandbox;
2410
2462
  const providers = environmentProviders(stored);
2411
2463
  return DeviceConfiguration.make({
2412
- relayUrl: normalizeRelayUrl(process.env.COHALL_RELAY_URL ?? stored.relayUrl),
2464
+ relayUrl,
2413
2465
  token,
2414
2466
  id: process.env.COHALL_DEVICE_ID === undefined ? stored.deviceId : DeviceId.make(process.env.COHALL_DEVICE_ID),
2415
2467
  name: process.env.COHALL_DEVICE_NAME ?? stored.deviceName,
@@ -2517,6 +2569,322 @@ var threadContext = Effect4.fn("Cohall.threadContext")(function* (client, thread
2517
2569
  return yield* client.threadContext(threadId);
2518
2570
  });
2519
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
+
2520
2888
  // apps/device/src/cli.ts
2521
2889
  var valueOptions = new Set([
2522
2890
  "context",
@@ -2534,9 +2902,10 @@ var valueOptions = new Set([
2534
2902
  "thread",
2535
2903
  "timeout",
2536
2904
  "token-file",
2905
+ "to",
2537
2906
  "workspace"
2538
2907
  ]);
2539
- var flagOptions = new Set(["client-only", "follow", "no-wait"]);
2908
+ var flagOptions = new Set(["client-only", "dry-run", "follow", "no-restart", "no-wait"]);
2540
2909
  var aliases = new Map([
2541
2910
  ["-c", "context"],
2542
2911
  ["-p", "prompt"],
@@ -2564,6 +2933,7 @@ Usage:
2564
2933
  cohall sessions
2565
2934
  cohall revoke <session-id>
2566
2935
  cohall doctor
2936
+ cohall upgrade [--to version|latest] [--no-restart] [--dry-run]
2567
2937
  cohall device
2568
2938
  cohall relay
2569
2939
  cohall mcp
@@ -2636,9 +3006,9 @@ var allowOptions = (arguments_, names) => {
2636
3006
  }
2637
3007
  }
2638
3008
  };
2639
- var noPositionals = (arguments_, command2) => {
3009
+ var noPositionals = (arguments_, command) => {
2640
3010
  if (arguments_.positionals.length > 0) {
2641
- throw new Error(`${command2} does not accept positional arguments`);
3011
+ throw new Error(`${command} does not accept positional arguments`);
2642
3012
  }
2643
3013
  };
2644
3014
  var identifier = (arguments_, label) => {
@@ -2666,7 +3036,7 @@ var readInput = async (arguments_, name) => {
2666
3036
  if ((await stat(path)).size > 131072) {
2667
3037
  throw new Error(`${name} file exceeds 128 KiB`);
2668
3038
  }
2669
- return readFile2(path, "utf8");
3039
+ return readFile3(path, "utf8");
2670
3040
  }
2671
3041
  const result = direct === "-" ? await readStdin(131072, name) : direct;
2672
3042
  if (result !== undefined && result.length > 131072) {
@@ -2702,7 +3072,7 @@ var pairingToken = async (arguments_) => {
2702
3072
  if (path !== undefined && (await stat(path)).size > 512) {
2703
3073
  throw new Error("Pairing token file exceeds 512 bytes");
2704
3074
  }
2705
- 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();
2706
3076
  if (token.length === 0 || new TextEncoder().encode(token).byteLength > 256) {
2707
3077
  throw new Error("Provide a pairing token on stdin or with --token-file");
2708
3078
  }
@@ -2724,25 +3094,38 @@ var ownerClient = async () => {
2724
3094
  };
2725
3095
  var installSkill = async (target) => {
2726
3096
  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")
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")
2730
3100
  };
2731
3101
  const selected = target === "all" ? Object.values(roots) : (target in roots) ? [roots[target]] : undefined;
2732
3102
  if (selected === undefined) {
2733
3103
  throw new Error("Skill target must be agents, claude, opencode, or all");
2734
3104
  }
2735
3105
  for (const path of selected) {
2736
- await mkdir2(dirname2(path), { recursive: true });
2737
- await writeFile2(path, SKILL_default);
3106
+ await mkdir3(dirname3(path), { recursive: true });
3107
+ await writeFile4(path, SKILL_default);
2738
3108
  }
2739
3109
  return selected;
2740
3110
  };
2741
3111
  var printHelp = () => console.log(help);
2742
3112
  var printSkill = () => console.log(SKILL_default.trimEnd());
2743
- var runCli = async (command2, raw) => {
3113
+ var runCli = async (command, raw) => {
2744
3114
  const arguments_ = parseArguments(raw);
2745
- 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") {
2746
3129
  allowOptions(arguments_, [
2747
3130
  "client-only",
2748
3131
  "name",
@@ -2751,7 +3134,7 @@ var runCli = async (command2, raw) => {
2751
3134
  "token-file",
2752
3135
  "workspace"
2753
3136
  ]);
2754
- noPositionals(arguments_, command2);
3137
+ noPositionals(arguments_, command);
2755
3138
  const token = await pairingToken(arguments_);
2756
3139
  const relayUrl = normalizeRelayUrl(option(arguments_, "relay") ?? "http://127.0.0.1:8787");
2757
3140
  const existing = await readStoredConfiguration();
@@ -2793,9 +3176,9 @@ var runCli = async (command2, raw) => {
2793
3176
  });
2794
3177
  return;
2795
3178
  }
2796
- if (command2 === "config") {
3179
+ if (command === "config") {
2797
3180
  allowOptions(arguments_, []);
2798
- noPositionals(arguments_, command2);
3181
+ noPositionals(arguments_, command);
2799
3182
  const configuration2 = await readStoredConfiguration();
2800
3183
  print({
2801
3184
  config_path: configurationPath(),
@@ -2814,9 +3197,9 @@ var runCli = async (command2, raw) => {
2814
3197
  });
2815
3198
  return;
2816
3199
  }
2817
- if (command2 === "configure") {
3200
+ if (command === "configure") {
2818
3201
  allowOptions(arguments_, ["model", "name", "providers", "relay", "sandbox", "workspace"]);
2819
- noPositionals(arguments_, command2);
3202
+ noPositionals(arguments_, command);
2820
3203
  const existing = await readStoredConfiguration();
2821
3204
  const relayUrl = normalizeRelayUrl(option(arguments_, "relay") ?? existing?.relayUrl ?? "http://127.0.0.1:8787");
2822
3205
  const supplied = values(arguments_, "workspace");
@@ -2831,19 +3214,18 @@ var runCli = async (command2, raw) => {
2831
3214
  deviceId: existing?.deviceId ?? makeDeviceId(),
2832
3215
  deviceName: option(arguments_, "name") ?? existing?.deviceName ?? "cohall-device",
2833
3216
  workspaces,
2834
- ...existing?.clientToken === undefined ? {} : { clientToken: existing.clientToken },
2835
- ...existing?.deviceToken === undefined ? {} : { deviceToken: existing.deviceToken },
3217
+ ...credentialsForRelay(existing, relayUrl),
2836
3218
  ...providers === undefined ? {} : { providers },
2837
3219
  ...model === undefined ? {} : { model },
2838
3220
  ...(sandbox ?? existing?.sandbox) === undefined ? {} : {
2839
- 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)
2840
3222
  }
2841
3223
  });
2842
3224
  await writeStoredConfiguration(configuration2);
2843
3225
  print({ configured: true, config_path: configurationPath() });
2844
3226
  return;
2845
3227
  }
2846
- if (command2 === "skill") {
3228
+ if (command === "skill") {
2847
3229
  allowOptions(arguments_, []);
2848
3230
  if (arguments_.positionals.length === 0) {
2849
3231
  printSkill();
@@ -2855,9 +3237,9 @@ var runCli = async (command2, raw) => {
2855
3237
  print({ installed: await installSkill(arguments_.positionals[1] ?? "all") });
2856
3238
  return;
2857
3239
  }
2858
- if (command2 === "integrations") {
3240
+ if (command === "integrations") {
2859
3241
  allowOptions(arguments_, []);
2860
- noPositionals(arguments_, command2);
3242
+ noPositionals(arguments_, command);
2861
3243
  print({
2862
3244
  cli_skill: "npx -y @akshar5/cohall skill install all",
2863
3245
  codex_mcp: "codex mcp add cohall -- npx -y @akshar5/cohall mcp",
@@ -2874,11 +3256,12 @@ var runCli = async (command2, raw) => {
2874
3256
  });
2875
3257
  return;
2876
3258
  }
2877
- if (command2 === "doctor") {
3259
+ if (command === "doctor") {
2878
3260
  allowOptions(arguments_, []);
2879
- noPositionals(arguments_, command2);
3261
+ noPositionals(arguments_, command);
2880
3262
  const configuration2 = await readStoredConfiguration();
2881
3263
  const relayUrl = normalizeRelayUrl(process.env.COHALL_RELAY_URL ?? configuration2?.relayUrl ?? "http://127.0.0.1:8787");
3264
+ const storedCredentials = credentialsForRelay(configuration2, relayUrl);
2882
3265
  const response = await fetch(`${relayUrl}/api/health`, {
2883
3266
  signal: AbortSignal.timeout(1e4)
2884
3267
  }).catch(() => {
@@ -2890,8 +3273,8 @@ var runCli = async (command2, raw) => {
2890
3273
  provider,
2891
3274
  findExecutable(provider === "claude-code" ? "claude" : provider) ?? "not found"
2892
3275
  ]));
2893
- const clientToken = process.env.COHALL_CLIENT_TOKEN ?? configuration2?.clientToken;
2894
- 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;
2895
3278
  const deviceId = process.env.COHALL_DEVICE_ID ?? configuration2?.deviceId;
2896
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(() => {
2897
3280
  return;
@@ -2908,7 +3291,7 @@ var runCli = async (command2, raw) => {
2908
3291
  relay: response?.ok === true ? "reachable" : "unreachable",
2909
3292
  relay_url: relayUrl,
2910
3293
  config_path: configurationPath(),
2911
- client_credential: process.env.COHALL_CLIENT_TOKEN !== undefined || configuration2?.clientToken !== undefined,
3294
+ client_credential: process.env.COHALL_CLIENT_TOKEN !== undefined || storedCredentials.clientToken !== undefined,
2912
3295
  device_credential: hasDeviceCredential,
2913
3296
  workspaces: configuration2?.workspaces ?? [],
2914
3297
  providers: providerExecutables,
@@ -2920,11 +3303,11 @@ var runCli = async (command2, raw) => {
2920
3303
  });
2921
3304
  return;
2922
3305
  }
2923
- if (command2 === "pair" || command2 === "sessions" || command2 === "revoke") {
3306
+ if (command === "pair" || command === "sessions" || command === "revoke") {
2924
3307
  const { configuration: configuration2, relay: relay2 } = await ownerClient();
2925
- if (command2 === "pair") {
3308
+ if (command === "pair") {
2926
3309
  allowOptions(arguments_, ["client-only", "label"]);
2927
- noPositionals(arguments_, command2);
3310
+ noPositionals(arguments_, command);
2928
3311
  const roles = arguments_.options.has("client-only") ? ["client"] : ["client", "device"];
2929
3312
  const credential = await Effect5.runPromise(relay2.createPairing({ label: option(arguments_, "label") ?? "Cohall device", roles }));
2930
3313
  print({
@@ -2935,24 +3318,24 @@ var runCli = async (command2, raw) => {
2935
3318
  });
2936
3319
  return;
2937
3320
  }
2938
- if (command2 === "sessions") {
3321
+ if (command === "sessions") {
2939
3322
  allowOptions(arguments_, []);
2940
- noPositionals(arguments_, command2);
3323
+ noPositionals(arguments_, command);
2941
3324
  print(await Effect5.runPromise(relay2.authSessions()));
2942
3325
  return;
2943
3326
  }
2944
3327
  allowOptions(arguments_, []);
2945
- 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")))));
2946
3329
  return;
2947
3330
  }
2948
3331
  const { configuration, relay } = await client();
2949
- if (command2 === "devices") {
3332
+ if (command === "devices") {
2950
3333
  allowOptions(arguments_, []);
2951
- noPositionals(arguments_, command2);
3334
+ noPositionals(arguments_, command);
2952
3335
  print(await Effect5.runPromise(relay.devices()));
2953
3336
  return;
2954
3337
  }
2955
- if (command2 === "delegate") {
3338
+ if (command === "delegate") {
2956
3339
  allowOptions(arguments_, [
2957
3340
  "context",
2958
3341
  "context-file",
@@ -2985,22 +3368,22 @@ var runCli = async (command2, raw) => {
2985
3368
  prompt,
2986
3369
  ...target === undefined ? {} : { target },
2987
3370
  ...context === undefined ? {} : { context },
2988
- ...thread === undefined ? {} : { threadId: Schema6.decodeUnknownSync(ThreadId)(thread) },
3371
+ ...thread === undefined ? {} : { threadId: Schema7.decodeUnknownSync(ThreadId)(thread) },
2989
3372
  ...workspace === undefined ? {} : { workspace },
2990
- ...provider === undefined ? {} : { provider: Schema6.decodeUnknownSync(Provider)(provider) }
3373
+ ...provider === undefined ? {} : { provider: Schema7.decodeUnknownSync(Provider)(provider) }
2991
3374
  }));
2992
3375
  print(taskResult(arguments_.options.has("no-wait") ? task : await Effect5.runPromise(waitForTask(relay, task, timeout(arguments_)))));
2993
3376
  return;
2994
3377
  }
2995
- if (command2 === "status") {
3378
+ if (command === "status") {
2996
3379
  allowOptions(arguments_, []);
2997
- const id = Schema6.decodeUnknownSync(TaskId)(identifier(arguments_, "task id"));
3380
+ const id = Schema7.decodeUnknownSync(TaskId)(identifier(arguments_, "task id"));
2998
3381
  print(taskResult(await Effect5.runPromise(relay.getTask(id))));
2999
3382
  return;
3000
3383
  }
3001
- if (command2 === "trace") {
3384
+ if (command === "trace") {
3002
3385
  allowOptions(arguments_, ["follow"]);
3003
- const id = Schema6.decodeUnknownSync(TaskId)(identifier(arguments_, "task id"));
3386
+ const id = Schema7.decodeUnknownSync(TaskId)(identifier(arguments_, "task id"));
3004
3387
  if (arguments_.options.has("follow")) {
3005
3388
  await Effect5.runPromise(followTaskTrace(relay, id, printLine));
3006
3389
  return;
@@ -3008,37 +3391,37 @@ var runCli = async (command2, raw) => {
3008
3391
  print(await Effect5.runPromise(relay.traceTask(id)));
3009
3392
  return;
3010
3393
  }
3011
- if (command2 === "wait") {
3394
+ if (command === "wait") {
3012
3395
  allowOptions(arguments_, ["timeout"]);
3013
- const id = Schema6.decodeUnknownSync(TaskId)(identifier(arguments_, "task id"));
3396
+ const id = Schema7.decodeUnknownSync(TaskId)(identifier(arguments_, "task id"));
3014
3397
  const task = await Effect5.runPromise(relay.getTask(id));
3015
3398
  print(taskResult(await Effect5.runPromise(waitForTask(relay, task, timeout(arguments_)))));
3016
3399
  return;
3017
3400
  }
3018
- if (command2 === "cancel") {
3401
+ if (command === "cancel") {
3019
3402
  allowOptions(arguments_, []);
3020
- const id = Schema6.decodeUnknownSync(TaskId)(identifier(arguments_, "task id"));
3403
+ const id = Schema7.decodeUnknownSync(TaskId)(identifier(arguments_, "task id"));
3021
3404
  print(taskResult(await Effect5.runPromise(relay.cancelTask(id))));
3022
3405
  return;
3023
3406
  }
3024
- if (command2 === "thread") {
3407
+ if (command === "thread") {
3025
3408
  allowOptions(arguments_, []);
3026
- const id = Schema6.decodeUnknownSync(ThreadId)(identifier(arguments_, "thread id"));
3409
+ const id = Schema7.decodeUnknownSync(ThreadId)(identifier(arguments_, "thread id"));
3027
3410
  print(await Effect5.runPromise(threadContext(relay, id)));
3028
3411
  return;
3029
3412
  }
3030
- throw new Error(`Unknown Cohall command: ${command2}`);
3413
+ throw new Error(`Unknown Cohall command: ${command}`);
3031
3414
  };
3032
3415
 
3033
3416
  // apps/device/src/daemon.ts
3034
3417
  init_src();
3035
- import { Effect as Effect6, Schedule as Schedule2, Schema as Schema7 } from "effect";
3036
- 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";
3037
3420
  import { arch, hostname as hostname2, platform as platform3 } from "node:os";
3038
3421
  import { basename, isAbsolute as isAbsolute2, relative } from "node:path";
3039
3422
  import { WebSocket } from "ws";
3040
3423
 
3041
- class DeviceConnectionError extends Schema7.TaggedErrorClass()("Device.ConnectionError", { message: Schema7.String }) {
3424
+ class DeviceConnectionError extends Schema8.TaggedErrorClass()("Device.ConnectionError", { message: Schema8.String }) {
3042
3425
  }
3043
3426
  var socketUrl = (configuration) => {
3044
3427
  const url = new URL(configuration.relayUrl);
@@ -3075,8 +3458,8 @@ var capabilities = (providers) => {
3075
3458
  };
3076
3459
  var selectProviders = (installed, configured) => configured === undefined ? installed : configured.filter((provider) => installed.includes(provider));
3077
3460
  var describeDevice = (configuration, status = "online") => {
3078
- const operatingSystem = platform3();
3079
- 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";
3080
3463
  const installed = availableProviders();
3081
3464
  const providers = selectProviders(installed, configuration.providers);
3082
3465
  return Device.make({
@@ -3101,7 +3484,7 @@ var allowedWorkspace = async (configuration, requested) => {
3101
3484
  if (selected === undefined) {
3102
3485
  throw new Error("No workspace is configured on this device");
3103
3486
  }
3104
- const candidate = await realpath2(selected);
3487
+ const candidate = await realpath3(selected);
3105
3488
  const allowed = configuration.workspaces.some((root) => {
3106
3489
  const child = relative(root, candidate);
3107
3490
  return child === "" || !child.startsWith("..") && !isAbsolute2(child);
@@ -3404,43 +3787,43 @@ var runMcp = async (configuration) => {
3404
3787
 
3405
3788
  // apps/device/src/main.ts
3406
3789
  var main = async () => {
3407
- const command2 = process.argv[2];
3408
- 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") {
3409
3792
  printHelp();
3410
3793
  return;
3411
3794
  }
3412
- if (command2 === "--version" || command2 === "version") {
3795
+ if (command === "--version" || command === "version") {
3413
3796
  console.log(version);
3414
3797
  return;
3415
3798
  }
3416
- if (["device", "relay", "mcp"].includes(command2) && process.argv.length > 3) {
3799
+ if (["device", "relay", "mcp"].includes(command) && process.argv.length > 3) {
3417
3800
  const arguments_ = process.argv.slice(3);
3418
3801
  if (arguments_.length === 1 && ["--help", "-h", "help"].includes(arguments_[0] ?? "")) {
3419
3802
  printHelp();
3420
3803
  return;
3421
3804
  }
3422
- throw new Error(`${command2} does not accept arguments`);
3805
+ throw new Error(`${command} does not accept arguments`);
3423
3806
  }
3424
- if (command2 === "relay") {
3807
+ if (command === "relay") {
3425
3808
  const { runRelay: runRelay2 } = await init_main().then(() => exports_main);
3426
3809
  await runRelay2();
3427
3810
  return;
3428
3811
  }
3429
- if (command2 === "device") {
3812
+ if (command === "device") {
3430
3813
  const configuration = await Effect11.runPromise(loadDeviceConfiguration);
3431
3814
  await Effect11.runPromise(runDaemon(configuration));
3432
3815
  return;
3433
3816
  }
3434
- if (command2 === "mcp") {
3817
+ if (command === "mcp") {
3435
3818
  const configuration = await Effect11.runPromise(loadClientConfiguration);
3436
3819
  await runMcp(configuration);
3437
3820
  return;
3438
3821
  }
3439
- await runCli(command2, process.argv.slice(3));
3822
+ await runCli(command, process.argv.slice(3));
3440
3823
  };
3441
3824
  await main().catch((cause) => {
3442
3825
  console.error(JSON.stringify({ error: cause instanceof Error ? cause.message : String(cause) }, null, 2));
3443
3826
  process.exitCode = 1;
3444
3827
  });
3445
3828
 
3446
- //# debugId=492323E24FE9814364756E2164756E21
3829
+ //# debugId=C3D1F939F21B764D64756E2164756E21