@akshar5/cohall 0.3.1 → 0.3.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.3](https://github.com/AksharP5/cohall/compare/v0.3.2...v0.3.3) (2026-08-06)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * patch MCP dependencies and release runtime ([#13](https://github.com/AksharP5/cohall/issues/13)) ([4155c00](https://github.com/AksharP5/cohall/commit/4155c00583cbe71e0afd85cc0438085dbdfa546c))
9
+
10
+ ## [0.3.2](https://github.com/AksharP5/cohall/compare/v0.3.1...v0.3.2) (2026-08-06)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * make setup and provider diagnostics reliable ([#11](https://github.com/AksharP5/cohall/issues/11)) ([3c621c7](https://github.com/AksharP5/cohall/commit/3c621c7b2c13ba192ced7d93319cc41df3af256e))
16
+
3
17
  ## [0.3.1](https://github.com/AksharP5/cohall/compare/v0.3.0...v0.3.1) (2026-08-06)
4
18
 
5
19
 
package/README.md CHANGED
@@ -78,6 +78,7 @@ read -rsp 'Pairing token: ' pairing_token; printf '\n'
78
78
  printf '%s' "$pairing_token" | npx -y @akshar5/cohall join \
79
79
  --relay https://cohall.example.com \
80
80
  --name macbook \
81
+ --providers codex \
81
82
  --workspace "$HOME/dev" \
82
83
  --workspace "$HOME/.skillsync/repo"
83
84
  unset pairing_token
@@ -130,8 +131,50 @@ terminal.
130
131
 
131
132
  ## Optional MCP
132
133
 
133
- Run `npx -y @akshar5/cohall integrations` for current setup commands. The MCP
134
- server exposes:
134
+ The MCP subprocess uses the current user's stored Cohall client configuration.
135
+ For clients that accept the common `mcpServers` JSON shape, paste:
136
+
137
+ ```json
138
+ {
139
+ "mcpServers": {
140
+ "cohall": {
141
+ "command": "npx",
142
+ "args": ["-y", "@akshar5/cohall", "mcp"]
143
+ }
144
+ }
145
+ }
146
+ ```
147
+
148
+ Codex:
149
+
150
+ ```bash
151
+ codex mcp add cohall -- npx -y @akshar5/cohall mcp
152
+ ```
153
+
154
+ Claude Code, available in every project for the current user:
155
+
156
+ ```bash
157
+ claude mcp add --transport stdio --scope user cohall -- \
158
+ npx -y @akshar5/cohall mcp
159
+ ```
160
+
161
+ OpenCode `opencode.json`:
162
+
163
+ ```json
164
+ {
165
+ "$schema": "https://opencode.ai/config.json",
166
+ "mcp": {
167
+ "cohall": {
168
+ "type": "local",
169
+ "command": ["npx", "-y", "@akshar5/cohall", "mcp"],
170
+ "enabled": true
171
+ }
172
+ }
173
+ }
174
+ ```
175
+
176
+ Run `npx -y @akshar5/cohall integrations` to print these command components.
177
+ The MCP server exposes:
135
178
 
136
179
  - `list_devices`
137
180
  - `delegate`
@@ -149,7 +192,14 @@ and [service setup](docs/services.md).
149
192
 
150
193
  ## Provider behavior
151
194
 
152
- Target devices advertise only provider executables they actually have:
195
+ Target devices advertise provider executables they actually have. Authentication
196
+ is checked when delegated work starts. Restrict a device to providers you have
197
+ configured locally:
198
+
199
+ ```bash
200
+ cohall configure --providers codex,claude-code
201
+ cohall configure --providers auto
202
+ ```
153
203
 
154
204
  | Provider | Required command | Session continuation |
155
205
  | ----------- | ---------------- | ------------------------ |
@@ -167,7 +217,9 @@ enforced after resolving symlinks.
167
217
 
168
218
  `npx -y @akshar5/cohall config` shows the active stored configuration without
169
219
  printing tokens. `npx -y @akshar5/cohall configure` changes relay, name,
170
- workspaces, model, or Codex sandbox.
220
+ workspaces, enabled providers, model, or Codex sandbox.
221
+ `npx -y @akshar5/cohall doctor` checks relay reachability, this device's relay
222
+ status, provider selection, executable paths, and version information.
171
223
  Environment variables override stored values:
172
224
 
173
225
  | Variable | Purpose |
@@ -179,6 +231,7 @@ Environment variables override stored values:
179
231
  | `COHALL_TOKEN` | Relay owner credential |
180
232
  | `COHALL_DEVICE_ID` | Stable device ID override |
181
233
  | `COHALL_DEVICE_NAME` | Advertised device name |
234
+ | `COHALL_DEVICE_PROVIDERS` | Provider allowlist or `auto` |
182
235
  | `COHALL_DEVICE_WORKSPACES` | Comma-separated workspace roots |
183
236
  | `COHALL_DEVICE_WORKSPACES_JSON` | JSON workspace roots; supports commas in paths |
184
237
  | `COHALL_MODEL` | Target provider model override |
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.1", bounded = (maxLength) => Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(maxLength)), optionalText = (maxLength) => Schema.String.check(Schema.isMaxLength(maxLength)), boundedArray = (schema, maxLength) => Schema.Array(schema).check(Schema.isMaxLength(maxLength)), uuid = (name) => Schema.String.check(Schema.isUUID(4)).pipe(Schema.brand(name)), isoTimestamp = (name) => Schema.String.check(Schema.isPattern(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?Z$/, {
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$/, {
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(() => {
@@ -203,16 +203,16 @@ var init_src = __esm(() => {
203
203
  // apps/relay/src/config.ts
204
204
  import { Effect as Effect8, Schema as Schema8 } from "effect";
205
205
  import { access as access3, chmod as chmod2, mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
206
- import { homedir as homedir3, platform as platform4 } from "node:os";
206
+ import { homedir as homedir4, platform as platform4 } from "node:os";
207
207
  import { join as join4, resolve as resolve2 } from "node:path";
208
208
  var RelayConfiguration, defaultDataDirectory = () => {
209
209
  if (platform4() === "win32") {
210
- return join4(process.env.LOCALAPPDATA ?? join4(homedir3(), "AppData", "Local"), "Cohall");
210
+ return join4(process.env.LOCALAPPDATA ?? join4(homedir4(), "AppData", "Local"), "Cohall");
211
211
  }
212
212
  if (platform4() === "darwin") {
213
- return join4(homedir3(), "Library", "Application Support", "Cohall", "relay");
213
+ return join4(homedir4(), "Library", "Application Support", "Cohall", "relay");
214
214
  }
215
- return join4(process.env.XDG_DATA_HOME ?? join4(homedir3(), ".local", "share"), "cohall");
215
+ return join4(process.env.XDG_DATA_HOME ?? join4(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
218
  return (await readFile3(path, "utf8")).trim();
@@ -1738,7 +1738,7 @@ 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 { platform } from "node:os";
1741
+ import { homedir, platform } from "node:os";
1742
1742
  import { delimiter, extname, isAbsolute, join } from "node:path";
1743
1743
  import { spawn } from "node:child_process";
1744
1744
 
@@ -1756,7 +1756,10 @@ var executables = {
1756
1756
  "claude-code": "claude",
1757
1757
  opencode: "opencode"
1758
1758
  };
1759
- var providerEnvironment = () => Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.startsWith("COHALL_")));
1759
+ var providerEnvironment = () => ({
1760
+ ...Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.startsWith("COHALL_"))),
1761
+ PATH: executableDirectories().join(delimiter)
1762
+ });
1760
1763
  var record = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? Object.fromEntries(Object.entries(value)) : undefined;
1761
1764
  var text = (value) => typeof value === "string" ? value : undefined;
1762
1765
  var executableCandidates = (command) => {
@@ -1765,8 +1768,25 @@ var executableCandidates = (command) => {
1765
1768
  }
1766
1769
  return (process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter((extension) => extension.length > 0).map((extension) => `${command}${extension.toLowerCase()}`);
1767
1770
  };
1771
+ var executableDirectories = () => {
1772
+ const home = homedir();
1773
+ const configured = (process.env.PATH ?? "").split(delimiter).filter(Boolean);
1774
+ const common = platform() === "win32" ? [
1775
+ process.env.APPDATA === undefined ? undefined : join(process.env.APPDATA, "npm"),
1776
+ process.env.LOCALAPPDATA === undefined ? undefined : join(process.env.LOCALAPPDATA, "pnpm"),
1777
+ join(home, ".bun", "bin")
1778
+ ] : [
1779
+ join(home, ".local", "bin"),
1780
+ join(home, ".npm-global", "bin"),
1781
+ join(home, ".bun", "bin"),
1782
+ join(home, ".local", "share", "pnpm"),
1783
+ "/opt/homebrew/bin",
1784
+ "/usr/local/bin"
1785
+ ];
1786
+ return [...new Set([...configured, ...common.filter((path) => path !== undefined)])];
1787
+ };
1768
1788
  var findExecutable = (command) => {
1769
- const paths = isAbsolute(command) || command.includes("/") || command.includes("\\") ? [""] : (process.env.PATH ?? "").split(delimiter);
1789
+ const paths = isAbsolute(command) || command.includes("/") || command.includes("\\") ? [""] : executableDirectories();
1770
1790
  for (const directory of paths) {
1771
1791
  for (const candidate of executableCandidates(command)) {
1772
1792
  const path = directory.length === 0 ? candidate : join(directory, candidate);
@@ -1813,6 +1833,32 @@ var boundedText = (stream, limit, onOverflow) => new Promise((resolve, reject) =
1813
1833
  stream.once("end", onEnd);
1814
1834
  stream.once("error", onError);
1815
1835
  });
1836
+ var captureText = (stream, limit) => new Promise((resolve, reject) => {
1837
+ const chunks = [];
1838
+ let captured = 0;
1839
+ let truncated = false;
1840
+ const onData = (chunk) => {
1841
+ const bytes = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
1842
+ const remaining = limit - captured;
1843
+ if (remaining > 0) {
1844
+ const selected = bytes.byteLength <= remaining ? bytes : bytes.subarray(0, remaining);
1845
+ chunks.push(selected);
1846
+ captured += selected.byteLength;
1847
+ }
1848
+ truncated ||= bytes.byteLength > remaining;
1849
+ };
1850
+ const onEnd = () => resolve({ text: Buffer.concat(chunks, captured).toString("utf8"), truncated });
1851
+ stream.on("data", onData);
1852
+ stream.once("end", onEnd);
1853
+ stream.once("error", reject);
1854
+ });
1855
+ var failureMessage = (command, stderr, exitCode) => {
1856
+ const fallback = `${command} exited with status ${exitCode}`;
1857
+ const suffix = stderr.truncated ? `
1858
+ [stderr truncated after 64 KiB]` : "";
1859
+ const available2 = 16384 - suffix.length;
1860
+ return `${(stderr.text.trim() || fallback).slice(0, available2)}${suffix}`;
1861
+ };
1816
1862
  var codexCommand = (options) => {
1817
1863
  const common = [
1818
1864
  "--json",
@@ -1947,19 +1993,14 @@ var parse = (options, stdout) => {
1947
1993
  var byteLength = (value) => new TextEncoder().encode(value).byteLength;
1948
1994
  var run = (options) => Effect2.tryPromise({
1949
1995
  try: async (signal) => {
1950
- if (!available(options.provider)) {
1996
+ const [commandName, ...arguments_] = command(options);
1997
+ const executable = commandName === undefined ? undefined : findExecutable(commandName);
1998
+ if (executable === undefined) {
1951
1999
  throw new ProviderUnavailableError({
1952
2000
  provider: options.provider,
1953
2001
  message: `${executables[options.provider]} is not available on this device`
1954
2002
  });
1955
2003
  }
1956
- const [executable, ...arguments_] = command(options);
1957
- if (executable === undefined) {
1958
- throw new ProviderRunError({
1959
- provider: options.provider,
1960
- message: `No command is configured for ${options.provider}`
1961
- });
1962
- }
1963
2004
  const child = spawn(executable, arguments_, {
1964
2005
  cwd: options.cwd,
1965
2006
  env: {
@@ -1990,7 +2031,7 @@ var run = (options) => Effect2.tryPromise({
1990
2031
  child.stdin.end(options.provider === "opencode" ? undefined : options.prompt);
1991
2032
  const [stdout, stderr, exitCode] = await Promise.all([
1992
2033
  boundedText(child.stdout, 1024 * 1024, terminate),
1993
- boundedText(child.stderr, 64 * 1024, terminate),
2034
+ captureText(child.stderr, 64 * 1024),
1994
2035
  exited
1995
2036
  ]);
1996
2037
  signal.removeEventListener("abort", terminate);
@@ -2000,7 +2041,7 @@ var run = (options) => Effect2.tryPromise({
2000
2041
  if (exitCode !== 0) {
2001
2042
  throw new ProviderRunError({
2002
2043
  provider: options.provider,
2003
- message: (stderr.trim() || `${executables[options.provider]} exited with status ${exitCode}`).slice(0, 16384),
2044
+ message: failureMessage(executables[options.provider], stderr, exitCode),
2004
2045
  exitCode
2005
2046
  });
2006
2047
  }
@@ -2033,7 +2074,7 @@ var run = (options) => Effect2.tryPromise({
2033
2074
  // apps/device/src/cli.ts
2034
2075
  import { Effect as Effect5, Schema as Schema6 } from "effect";
2035
2076
  import { access as access2, mkdir as mkdir2, readFile as readFile2, stat, writeFile as writeFile2 } from "node:fs/promises";
2036
- import { homedir as homedir2 } from "node:os";
2077
+ import { homedir as homedir3 } from "node:os";
2037
2078
  import { dirname as dirname2, join as join3 } from "node:path";
2038
2079
 
2039
2080
  // skills/cohall/SKILL.md
@@ -2174,9 +2215,10 @@ Return the diagnostic failure to the user rather than claiming remote work ran.
2174
2215
  init_src();
2175
2216
  import { Effect as Effect3, Schema as Schema4 } from "effect";
2176
2217
  import { access, chmod, mkdir, readFile, realpath, rename, writeFile } from "node:fs/promises";
2177
- import { homedir, hostname, platform as platform2 } from "node:os";
2218
+ import { homedir as homedir2, hostname, platform as platform2 } from "node:os";
2178
2219
  import { dirname, join as join2, resolve } from "node:path";
2179
2220
  var WorkspaceList = Schema4.Array(Schema4.NonEmptyString).check(Schema4.isMaxLength(64));
2221
+ var ProviderList = Schema4.Array(Provider).check(Schema4.isMinLength(1)).check(Schema4.isMaxLength(3));
2180
2222
  var Sandbox = Schema4.Literals(["read-only", "workspace-write", "danger-full-access"]);
2181
2223
  var StoredConfiguration = Schema4.Struct({
2182
2224
  version: Schema4.Literal(1),
@@ -2186,6 +2228,7 @@ var StoredConfiguration = Schema4.Struct({
2186
2228
  workspaces: WorkspaceList,
2187
2229
  clientToken: Schema4.optionalKey(Schema4.NonEmptyString),
2188
2230
  deviceToken: Schema4.optionalKey(Schema4.NonEmptyString),
2231
+ providers: Schema4.optionalKey(ProviderList),
2189
2232
  model: Schema4.optionalKey(Schema4.NonEmptyString),
2190
2233
  sandbox: Schema4.optionalKey(Sandbox)
2191
2234
  });
@@ -2200,17 +2243,18 @@ var DeviceConfiguration = Schema4.Struct({
2200
2243
  id: DeviceId,
2201
2244
  name: Schema4.NonEmptyString,
2202
2245
  workspaces: WorkspaceList,
2246
+ providers: Schema4.optionalKey(ProviderList),
2203
2247
  model: Schema4.optionalKey(Schema4.NonEmptyString),
2204
2248
  sandbox: Schema4.optionalKey(Sandbox)
2205
2249
  });
2206
2250
  var defaultConfigDirectory = () => {
2207
2251
  if (platform2() === "win32") {
2208
- return join2(process.env.APPDATA ?? join2(homedir(), "AppData", "Roaming"), "Cohall");
2252
+ return join2(process.env.APPDATA ?? join2(homedir2(), "AppData", "Roaming"), "Cohall");
2209
2253
  }
2210
2254
  if (platform2() === "darwin") {
2211
- return join2(homedir(), "Library", "Application Support", "Cohall");
2255
+ return join2(homedir2(), "Library", "Application Support", "Cohall");
2212
2256
  }
2213
- return join2(process.env.XDG_CONFIG_HOME ?? join2(homedir(), ".config"), "cohall");
2257
+ return join2(process.env.XDG_CONFIG_HOME ?? join2(homedir2(), ".config"), "cohall");
2214
2258
  };
2215
2259
  var configurationPath = () => resolve(process.env.COHALL_CONFIG ?? join2(defaultConfigDirectory(), "config.json"));
2216
2260
  var normalizeRelayUrl = (input) => {
@@ -2235,6 +2279,9 @@ var parseWorkspaces = (workspaceList, workspaceJson) => Effect3.tryPromise({
2235
2279
  },
2236
2280
  catch: (cause) => new Error(`Workspace roots must be existing paths; JSON input must be an array of paths: ${cause instanceof Error ? cause.message : String(cause)}`)
2237
2281
  });
2282
+ var parseProviders = (input) => Schema4.decodeUnknownSync(ProviderList)([
2283
+ ...new Set(input.split(",").map((provider) => provider.trim()).filter(Boolean))
2284
+ ]);
2238
2285
  var readStoredConfiguration = async () => {
2239
2286
  const path = configurationPath();
2240
2287
  if (!await access(path).then(() => true).catch(() => false)) {
@@ -2269,6 +2316,7 @@ var makeStoredConfiguration = async (input) => {
2269
2316
  const canonicalWorkspaces = input.workspaces === undefined ? existing?.workspaces ?? [] : await Effect3.runPromise(parseWorkspaces("", JSON.stringify(input.workspaces)));
2270
2317
  const clientToken = input.clientToken ?? existing?.clientToken;
2271
2318
  const deviceToken = input.deviceToken ?? existing?.deviceToken;
2319
+ const providers = input.providers === "auto" ? undefined : input.providers ?? existing?.providers;
2272
2320
  return StoredConfiguration.make({
2273
2321
  version: 1,
2274
2322
  relayUrl: normalizeRelayUrl(input.relayUrl),
@@ -2277,6 +2325,7 @@ var makeStoredConfiguration = async (input) => {
2277
2325
  workspaces: canonicalWorkspaces,
2278
2326
  ...clientToken === undefined ? {} : { clientToken },
2279
2327
  ...deviceToken === undefined ? {} : { deviceToken },
2328
+ ...providers === undefined ? {} : { providers },
2280
2329
  ...existing?.model === undefined ? {} : { model: existing.model },
2281
2330
  ...existing?.sandbox === undefined ? {} : { sandbox: existing.sandbox }
2282
2331
  });
@@ -2294,6 +2343,10 @@ var environmentSandbox = () => {
2294
2343
  const value = process.env.COHALL_SANDBOX ?? process.env.COHALL_CODEX_SANDBOX;
2295
2344
  return value === undefined ? undefined : Schema4.decodeUnknownSync(Sandbox)(value);
2296
2345
  };
2346
+ var environmentProviders = (stored) => {
2347
+ const value = process.env.COHALL_DEVICE_PROVIDERS;
2348
+ return value === undefined ? stored?.providers : value === "auto" ? undefined : parseProviders(value);
2349
+ };
2297
2350
  var storedOrDefaults = async () => {
2298
2351
  const stored = await readStoredConfiguration();
2299
2352
  return stored ?? StoredConfiguration.make({
@@ -2346,12 +2399,14 @@ var loadDeviceConfiguration = Effect3.tryPromise({
2346
2399
  }
2347
2400
  const model = environmentProvider() ?? stored.model;
2348
2401
  const sandbox = environmentSandbox() ?? stored.sandbox;
2402
+ const providers = environmentProviders(stored);
2349
2403
  return DeviceConfiguration.make({
2350
2404
  relayUrl: normalizeRelayUrl(process.env.COHALL_RELAY_URL ?? stored.relayUrl),
2351
2405
  token,
2352
2406
  id: process.env.COHALL_DEVICE_ID === undefined ? stored.deviceId : DeviceId.make(process.env.COHALL_DEVICE_ID),
2353
2407
  name: process.env.COHALL_DEVICE_NAME ?? stored.deviceName,
2354
2408
  workspaces,
2409
+ ...providers === undefined ? {} : { providers },
2355
2410
  ...model === undefined ? {} : { model },
2356
2411
  ...sandbox === undefined ? {} : { sandbox }
2357
2412
  });
@@ -2464,6 +2519,7 @@ var valueOptions = new Set([
2464
2519
  "prompt",
2465
2520
  "prompt-file",
2466
2521
  "provider",
2522
+ "providers",
2467
2523
  "relay",
2468
2524
  "sandbox",
2469
2525
  "target",
@@ -2483,8 +2539,9 @@ var aliases = new Map([
2483
2539
  var help = `Cohall connects agents running on your own devices.
2484
2540
 
2485
2541
  Usage:
2486
- cohall join --relay <url> --workspace <path> [--token-file <path>]
2487
- cohall configure [--relay url] [--name name] [--workspace path] [--model id]
2542
+ cohall join --relay <url> --workspace <path> [--providers list] [--token-file <path>]
2543
+ cohall configure [--relay url] [--name name] [--workspace path]
2544
+ [--providers codex,opencode|auto] [--model id]
2488
2545
  cohall config
2489
2546
  cohall devices
2490
2547
  cohall delegate [prompt] [--target @device] [--provider provider]
@@ -2659,9 +2716,9 @@ var ownerClient = async () => {
2659
2716
  };
2660
2717
  var installSkill = async (target) => {
2661
2718
  const roots = {
2662
- agents: join3(homedir2(), ".agents", "skills", "cohall", "SKILL.md"),
2663
- claude: join3(homedir2(), ".claude", "skills", "cohall", "SKILL.md"),
2664
- opencode: join3(homedir2(), ".config", "opencode", "skills", "cohall", "SKILL.md")
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")
2665
2722
  };
2666
2723
  const selected = target === "all" ? Object.values(roots) : (target in roots) ? [roots[target]] : undefined;
2667
2724
  if (selected === undefined) {
@@ -2678,7 +2735,14 @@ var printSkill = () => console.log(SKILL_default.trimEnd());
2678
2735
  var runCli = async (command2, raw) => {
2679
2736
  const arguments_ = parseArguments(raw);
2680
2737
  if (command2 === "join") {
2681
- allowOptions(arguments_, ["client-only", "name", "relay", "token-file", "workspace"]);
2738
+ allowOptions(arguments_, [
2739
+ "client-only",
2740
+ "name",
2741
+ "providers",
2742
+ "relay",
2743
+ "token-file",
2744
+ "workspace"
2745
+ ]);
2682
2746
  noPositionals(arguments_, command2);
2683
2747
  const token = await pairingToken(arguments_);
2684
2748
  const relayUrl = normalizeRelayUrl(option(arguments_, "relay") ?? "http://127.0.0.1:8787");
@@ -2686,13 +2750,16 @@ var runCli = async (command2, raw) => {
2686
2750
  const suppliedWorkspaces = values(arguments_, "workspace");
2687
2751
  const workspaces = suppliedWorkspaces.length === 0 ? existing?.workspaces : suppliedWorkspaces;
2688
2752
  const deviceName = option(arguments_, "name");
2753
+ const providerInput = option(arguments_, "providers");
2754
+ const providers = providerInput === undefined ? undefined : providerInput === "auto" ? "auto" : parseProviders(providerInput);
2689
2755
  if (!arguments_.options.has("client-only") && (workspaces?.length ?? 0) === 0) {
2690
2756
  throw new Error("At least one --workspace is required when joining a device");
2691
2757
  }
2692
2758
  const draft = await makeStoredConfiguration({
2693
2759
  relayUrl,
2694
2760
  ...deviceName === undefined ? {} : { deviceName },
2695
- ...workspaces === undefined ? {} : { workspaces }
2761
+ ...workspaces === undefined ? {} : { workspaces },
2762
+ ...providers === undefined ? {} : { providers }
2696
2763
  });
2697
2764
  const paired = await Effect5.runPromise(exchangePairing(relayUrl, { token }));
2698
2765
  const clientToken = paired.credentials.find((credential) => credential.session.role === "client")?.token;
@@ -2732,6 +2799,7 @@ var runCli = async (command2, raw) => {
2732
2799
  workspaces: configuration2.workspaces,
2733
2800
  client_credential: configuration2.clientToken !== undefined,
2734
2801
  device_credential: configuration2.deviceToken !== undefined,
2802
+ providers: configuration2.providers ?? "auto",
2735
2803
  model: configuration2.model,
2736
2804
  sandbox: configuration2.sandbox
2737
2805
  }
@@ -2739,7 +2807,7 @@ var runCli = async (command2, raw) => {
2739
2807
  return;
2740
2808
  }
2741
2809
  if (command2 === "configure") {
2742
- allowOptions(arguments_, ["model", "name", "relay", "sandbox", "workspace"]);
2810
+ allowOptions(arguments_, ["model", "name", "providers", "relay", "sandbox", "workspace"]);
2743
2811
  noPositionals(arguments_, command2);
2744
2812
  const existing = await readStoredConfiguration();
2745
2813
  const relayUrl = normalizeRelayUrl(option(arguments_, "relay") ?? existing?.relayUrl ?? "http://127.0.0.1:8787");
@@ -2747,6 +2815,8 @@ var runCli = async (command2, raw) => {
2747
2815
  const workspaces = supplied.length === 0 ? existing?.workspaces ?? [] : await Effect5.runPromise(parseWorkspaces("", JSON.stringify(supplied)));
2748
2816
  const sandbox = option(arguments_, "sandbox");
2749
2817
  const model = option(arguments_, "model") ?? existing?.model;
2818
+ const providerInput = option(arguments_, "providers");
2819
+ const providers = providerInput === undefined ? existing?.providers : providerInput === "auto" ? undefined : parseProviders(providerInput);
2750
2820
  const configuration2 = StoredConfiguration.make({
2751
2821
  version: 1,
2752
2822
  relayUrl,
@@ -2755,6 +2825,7 @@ var runCli = async (command2, raw) => {
2755
2825
  workspaces,
2756
2826
  ...existing?.clientToken === undefined ? {} : { clientToken: existing.clientToken },
2757
2827
  ...existing?.deviceToken === undefined ? {} : { deviceToken: existing.deviceToken },
2828
+ ...providers === undefined ? {} : { providers },
2758
2829
  ...model === undefined ? {} : { model },
2759
2830
  ...(sandbox ?? existing?.sandbox) === undefined ? {} : {
2760
2831
  sandbox: Schema6.decodeUnknownSync(Schema6.Literals(["read-only", "workspace-write", "danger-full-access"]))(sandbox ?? existing?.sandbox)
@@ -2782,8 +2853,16 @@ var runCli = async (command2, raw) => {
2782
2853
  print({
2783
2854
  cli_skill: "npx -y @akshar5/cohall skill install all",
2784
2855
  codex_mcp: "codex mcp add cohall -- npx -y @akshar5/cohall mcp",
2785
- claude_mcp: "claude mcp add --scope user cohall -- npx -y @akshar5/cohall mcp",
2786
- opencode_mcp: "Run opencode mcp add and use: npx -y @akshar5/cohall mcp"
2856
+ claude_mcp: "claude mcp add --transport stdio --scope user cohall -- npx -y @akshar5/cohall mcp",
2857
+ stdio_config: {
2858
+ command: "npx",
2859
+ args: ["-y", "@akshar5/cohall", "mcp"]
2860
+ },
2861
+ opencode_mcp: {
2862
+ type: "local",
2863
+ command: ["npx", "-y", "@akshar5/cohall", "mcp"],
2864
+ enabled: true
2865
+ }
2787
2866
  });
2788
2867
  return;
2789
2868
  }
@@ -2797,17 +2876,39 @@ var runCli = async (command2, raw) => {
2797
2876
  }).catch(() => {
2798
2877
  return;
2799
2878
  });
2879
+ const providerInput = process.env.COHALL_DEVICE_PROVIDERS;
2880
+ const selectedProviders = providerInput === undefined ? configuration2?.providers : providerInput === "auto" ? undefined : parseProviders(providerInput);
2881
+ const providerExecutables = Object.fromEntries(Provider.literals.map((provider) => [
2882
+ provider,
2883
+ findExecutable(provider === "claude-code" ? "claude" : provider) ?? "not found"
2884
+ ]));
2885
+ const clientToken = process.env.COHALL_CLIENT_TOKEN ?? configuration2?.clientToken;
2886
+ const hasDeviceCredential = process.env.COHALL_DEVICE_TOKEN !== undefined || configuration2?.deviceToken !== undefined;
2887
+ const deviceId = process.env.COHALL_DEVICE_ID ?? configuration2?.deviceId;
2888
+ 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
+ return;
2890
+ });
2891
+ const warnings = [
2892
+ ...response?.ok === true ? [] : ["Relay is unreachable"],
2893
+ ...hasDeviceCredential && response?.ok === true && currentDevice?.status !== "online" && currentDevice?.status !== "busy" ? [
2894
+ clientToken === undefined ? "Device status cannot be checked without a client credential" : "Device daemon is not connected or is not registered with the relay"
2895
+ ] : [],
2896
+ ...selectedProviders?.flatMap((provider) => providerExecutables[provider] === "not found" ? [`Configured provider ${provider} is not installed or not on PATH`] : []) ?? []
2897
+ ];
2800
2898
  print({
2899
+ version,
2801
2900
  relay: response?.ok === true ? "reachable" : "unreachable",
2802
2901
  relay_url: relayUrl,
2803
2902
  config_path: configurationPath(),
2804
2903
  client_credential: process.env.COHALL_CLIENT_TOKEN !== undefined || configuration2?.clientToken !== undefined,
2805
- device_credential: process.env.COHALL_DEVICE_TOKEN !== undefined || configuration2?.deviceToken !== undefined,
2904
+ device_credential: hasDeviceCredential,
2806
2905
  workspaces: configuration2?.workspaces ?? [],
2807
- providers: Object.fromEntries(Provider.literals.map((provider) => [
2808
- provider,
2809
- findExecutable(provider === "claude-code" ? "claude" : provider) ?? "not found"
2810
- ]))
2906
+ providers: providerExecutables,
2907
+ provider_selection: selectedProviders ?? "auto",
2908
+ provider_authentication: "checked when delegated work starts",
2909
+ device_status: hasDeviceCredential === false ? "not configured" : currentDevice?.status ?? "unknown",
2910
+ device_version: currentDevice?.version,
2911
+ warnings
2811
2912
  });
2812
2913
  return;
2813
2914
  }
@@ -2951,7 +3052,7 @@ var capabilities = (providers) => {
2951
3052
  const values2 = providers.map((provider) => ({
2952
3053
  id: provider,
2953
3054
  label: providerLabel(provider),
2954
- detail: `Uses this device's local ${providerLabel(provider)} login, configuration, and tools`
3055
+ detail: `${providerLabel(provider)} executable detected; authentication is checked when work starts`
2955
3056
  }));
2956
3057
  if (findExecutable("google-chrome") !== undefined || findExecutable("chromium") !== undefined || platform3() === "darwin") {
2957
3058
  values2.push({ id: "browser-session", label: "Signed-in browser" });
@@ -2964,10 +3065,12 @@ var capabilities = (providers) => {
2964
3065
  }
2965
3066
  return values2;
2966
3067
  };
3068
+ var selectProviders = (installed, configured) => configured === undefined ? installed : configured.filter((provider) => installed.includes(provider));
2967
3069
  var describeDevice = (configuration, status = "online") => {
2968
3070
  const operatingSystem = platform3();
2969
3071
  const platformName = operatingSystem === "darwin" || operatingSystem === "linux" ? operatingSystem : operatingSystem === "win32" ? "windows" : "unknown";
2970
- const providers = availableProviders();
3072
+ const installed = availableProviders();
3073
+ const providers = selectProviders(installed, configuration.providers);
2971
3074
  return Device.make({
2972
3075
  id: configuration.id,
2973
3076
  name: configuration.name,
@@ -3302,6 +3405,14 @@ var main = async () => {
3302
3405
  console.log(version);
3303
3406
  return;
3304
3407
  }
3408
+ if (["device", "relay", "mcp"].includes(command2) && process.argv.length > 3) {
3409
+ const arguments_ = process.argv.slice(3);
3410
+ if (arguments_.length === 1 && ["--help", "-h", "help"].includes(arguments_[0] ?? "")) {
3411
+ printHelp();
3412
+ return;
3413
+ }
3414
+ throw new Error(`${command2} does not accept arguments`);
3415
+ }
3305
3416
  if (command2 === "relay") {
3306
3417
  const { runRelay: runRelay2 } = await init_main().then(() => exports_main);
3307
3418
  await runRelay2();
@@ -3324,4 +3435,4 @@ await main().catch((cause) => {
3324
3435
  process.exitCode = 1;
3325
3436
  });
3326
3437
 
3327
- //# debugId=266CBA4746A41C1964756E2164756E21
3438
+ //# debugId=D325B2522A94114164756E2164756E21