@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 +14 -0
- package/README.md +57 -4
- package/bin/cohall.js +152 -41
- package/bin/cohall.js.map +7 -7
- package/deploy/launchd/com.cohall.device.plist +31 -0
- package/deploy/systemd/cohall-device.service +17 -0
- package/deploy/systemd/cohall-relay.service +30 -0
- package/deploy/windows/install-device.ps1 +21 -0
- package/docs/install.md +87 -0
- package/docs/integrations.md +92 -0
- package/docs/releasing.md +33 -0
- package/docs/services.md +103 -0
- package/package.json +7 -1
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
|
-
|
|
134
|
-
|
|
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
|
|
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.
|
|
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
|
|
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(
|
|
210
|
+
return join4(process.env.LOCALAPPDATA ?? join4(homedir4(), "AppData", "Local"), "Cohall");
|
|
211
211
|
}
|
|
212
212
|
if (platform4() === "darwin") {
|
|
213
|
-
return join4(
|
|
213
|
+
return join4(homedir4(), "Library", "Application Support", "Cohall", "relay");
|
|
214
214
|
}
|
|
215
|
-
return join4(process.env.XDG_DATA_HOME ?? join4(
|
|
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 = () =>
|
|
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("\\") ? [""] : (
|
|
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
|
-
|
|
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
|
-
|
|
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: (
|
|
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
|
|
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(
|
|
2252
|
+
return join2(process.env.APPDATA ?? join2(homedir2(), "AppData", "Roaming"), "Cohall");
|
|
2209
2253
|
}
|
|
2210
2254
|
if (platform2() === "darwin") {
|
|
2211
|
-
return join2(
|
|
2255
|
+
return join2(homedir2(), "Library", "Application Support", "Cohall");
|
|
2212
2256
|
}
|
|
2213
|
-
return join2(process.env.XDG_CONFIG_HOME ?? join2(
|
|
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]
|
|
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(
|
|
2663
|
-
claude: join3(
|
|
2664
|
-
opencode: join3(
|
|
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_, [
|
|
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
|
-
|
|
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:
|
|
2904
|
+
device_credential: hasDeviceCredential,
|
|
2806
2905
|
workspaces: configuration2?.workspaces ?? [],
|
|
2807
|
-
providers:
|
|
2808
|
-
|
|
2809
|
-
|
|
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:
|
|
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
|
|
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=
|
|
3438
|
+
//# debugId=D325B2522A94114164756E2164756E21
|