@akshar5/cohall 0.3.0 → 0.3.2
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 +173 -44
- 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 +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.2](https://github.com/AksharP5/cohall/compare/v0.3.1...v0.3.2) (2026-08-06)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* make setup and provider diagnostics reliable ([#11](https://github.com/AksharP5/cohall/issues/11)) ([3c621c7](https://github.com/AksharP5/cohall/commit/3c621c7b2c13ba192ced7d93319cc41df3af256e))
|
|
9
|
+
|
|
10
|
+
## [0.3.1](https://github.com/AksharP5/cohall/compare/v0.3.0...v0.3.1) (2026-08-06)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* make Codex and OpenCode delegation reliable ([974e0d1](https://github.com/AksharP5/cohall/commit/974e0d127a56060ee9fec0210b138a0879dde9d8))
|
|
16
|
+
|
|
3
17
|
## [0.3.0](https://github.com/AksharP5/cohall/compare/v0.2.0...v0.3.0) (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.2", 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,9 +1833,36 @@ 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",
|
|
1865
|
+
"--skip-git-repo-check",
|
|
1819
1866
|
"-c",
|
|
1820
1867
|
'approval_policy="never"',
|
|
1821
1868
|
...options.model === undefined ? [] : ["--model", options.model],
|
|
@@ -1837,7 +1884,8 @@ var opencodeCommand = (options) => [
|
|
|
1837
1884
|
"--format",
|
|
1838
1885
|
"json",
|
|
1839
1886
|
...options.model === undefined ? [] : ["--model", options.model],
|
|
1840
|
-
...options.sessionId === undefined ? [] : ["--session", options.sessionId]
|
|
1887
|
+
...options.sessionId === undefined ? [] : ["--session", options.sessionId],
|
|
1888
|
+
options.prompt
|
|
1841
1889
|
];
|
|
1842
1890
|
var command = (options) => {
|
|
1843
1891
|
switch (options.provider) {
|
|
@@ -1893,6 +1941,18 @@ var textFromPart = (value) => {
|
|
|
1893
1941
|
}
|
|
1894
1942
|
return text(part.text) ?? text(part.content) ?? text(record(part.part)?.text);
|
|
1895
1943
|
};
|
|
1944
|
+
var openCodeError = (event) => {
|
|
1945
|
+
if (text(event.type) !== "error") {
|
|
1946
|
+
return;
|
|
1947
|
+
}
|
|
1948
|
+
const error = record(event.error);
|
|
1949
|
+
const name = text(error?.name);
|
|
1950
|
+
const message = text(record(error?.data)?.message) ?? text(error?.message) ?? text(event.message) ?? text(event.error);
|
|
1951
|
+
if (name !== undefined && message !== undefined) {
|
|
1952
|
+
return `OpenCode ${name}: ${message}`;
|
|
1953
|
+
}
|
|
1954
|
+
return message === undefined ? "OpenCode reported an error" : `OpenCode: ${message}`;
|
|
1955
|
+
};
|
|
1896
1956
|
var parseOpenCode = (stdout, existingSession) => {
|
|
1897
1957
|
let result = "";
|
|
1898
1958
|
let sessionId = existingSession;
|
|
@@ -1905,6 +1965,10 @@ var parseOpenCode = (stdout, existingSession) => {
|
|
|
1905
1965
|
if (event === undefined) {
|
|
1906
1966
|
continue;
|
|
1907
1967
|
}
|
|
1968
|
+
const failure = openCodeError(event);
|
|
1969
|
+
if (failure !== undefined) {
|
|
1970
|
+
throw new Error(failure);
|
|
1971
|
+
}
|
|
1908
1972
|
sessionId = text(event.sessionID) ?? text(event.sessionId) ?? text(event.session_id) ?? text(record(event.info)?.sessionID) ?? sessionId;
|
|
1909
1973
|
const content = textFromPart(event.part) ?? textFromPart(event) ?? text(event.result);
|
|
1910
1974
|
if (content !== undefined && content.length > 0) {
|
|
@@ -1912,7 +1976,7 @@ var parseOpenCode = (stdout, existingSession) => {
|
|
|
1912
1976
|
}
|
|
1913
1977
|
}
|
|
1914
1978
|
if (result.length === 0) {
|
|
1915
|
-
throw new Error("OpenCode
|
|
1979
|
+
throw new Error("OpenCode produced no result; verify its authentication and use a supported project workspace");
|
|
1916
1980
|
}
|
|
1917
1981
|
return { result, ...sessionId === undefined ? {} : { sessionId } };
|
|
1918
1982
|
};
|
|
@@ -1929,19 +1993,14 @@ var parse = (options, stdout) => {
|
|
|
1929
1993
|
var byteLength = (value) => new TextEncoder().encode(value).byteLength;
|
|
1930
1994
|
var run = (options) => Effect2.tryPromise({
|
|
1931
1995
|
try: async (signal) => {
|
|
1932
|
-
|
|
1996
|
+
const [commandName, ...arguments_] = command(options);
|
|
1997
|
+
const executable = commandName === undefined ? undefined : findExecutable(commandName);
|
|
1998
|
+
if (executable === undefined) {
|
|
1933
1999
|
throw new ProviderUnavailableError({
|
|
1934
2000
|
provider: options.provider,
|
|
1935
2001
|
message: `${executables[options.provider]} is not available on this device`
|
|
1936
2002
|
});
|
|
1937
2003
|
}
|
|
1938
|
-
const [executable, ...arguments_] = command(options);
|
|
1939
|
-
if (executable === undefined) {
|
|
1940
|
-
throw new ProviderRunError({
|
|
1941
|
-
provider: options.provider,
|
|
1942
|
-
message: `No command is configured for ${options.provider}`
|
|
1943
|
-
});
|
|
1944
|
-
}
|
|
1945
2004
|
const child = spawn(executable, arguments_, {
|
|
1946
2005
|
cwd: options.cwd,
|
|
1947
2006
|
env: {
|
|
@@ -1969,10 +2028,10 @@ var run = (options) => Effect2.tryPromise({
|
|
|
1969
2028
|
killTimer.unref();
|
|
1970
2029
|
};
|
|
1971
2030
|
signal.addEventListener("abort", terminate, { once: true });
|
|
1972
|
-
child.stdin.end(options.prompt);
|
|
2031
|
+
child.stdin.end(options.provider === "opencode" ? undefined : options.prompt);
|
|
1973
2032
|
const [stdout, stderr, exitCode] = await Promise.all([
|
|
1974
2033
|
boundedText(child.stdout, 1024 * 1024, terminate),
|
|
1975
|
-
|
|
2034
|
+
captureText(child.stderr, 64 * 1024),
|
|
1976
2035
|
exited
|
|
1977
2036
|
]);
|
|
1978
2037
|
signal.removeEventListener("abort", terminate);
|
|
@@ -1982,7 +2041,7 @@ var run = (options) => Effect2.tryPromise({
|
|
|
1982
2041
|
if (exitCode !== 0) {
|
|
1983
2042
|
throw new ProviderRunError({
|
|
1984
2043
|
provider: options.provider,
|
|
1985
|
-
message: (
|
|
2044
|
+
message: failureMessage(executables[options.provider], stderr, exitCode),
|
|
1986
2045
|
exitCode
|
|
1987
2046
|
});
|
|
1988
2047
|
}
|
|
@@ -2015,7 +2074,7 @@ var run = (options) => Effect2.tryPromise({
|
|
|
2015
2074
|
// apps/device/src/cli.ts
|
|
2016
2075
|
import { Effect as Effect5, Schema as Schema6 } from "effect";
|
|
2017
2076
|
import { access as access2, mkdir as mkdir2, readFile as readFile2, stat, writeFile as writeFile2 } from "node:fs/promises";
|
|
2018
|
-
import { homedir as
|
|
2077
|
+
import { homedir as homedir3 } from "node:os";
|
|
2019
2078
|
import { dirname as dirname2, join as join3 } from "node:path";
|
|
2020
2079
|
|
|
2021
2080
|
// skills/cohall/SKILL.md
|
|
@@ -2156,9 +2215,10 @@ Return the diagnostic failure to the user rather than claiming remote work ran.
|
|
|
2156
2215
|
init_src();
|
|
2157
2216
|
import { Effect as Effect3, Schema as Schema4 } from "effect";
|
|
2158
2217
|
import { access, chmod, mkdir, readFile, realpath, rename, writeFile } from "node:fs/promises";
|
|
2159
|
-
import { homedir, hostname, platform as platform2 } from "node:os";
|
|
2218
|
+
import { homedir as homedir2, hostname, platform as platform2 } from "node:os";
|
|
2160
2219
|
import { dirname, join as join2, resolve } from "node:path";
|
|
2161
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));
|
|
2162
2222
|
var Sandbox = Schema4.Literals(["read-only", "workspace-write", "danger-full-access"]);
|
|
2163
2223
|
var StoredConfiguration = Schema4.Struct({
|
|
2164
2224
|
version: Schema4.Literal(1),
|
|
@@ -2168,6 +2228,7 @@ var StoredConfiguration = Schema4.Struct({
|
|
|
2168
2228
|
workspaces: WorkspaceList,
|
|
2169
2229
|
clientToken: Schema4.optionalKey(Schema4.NonEmptyString),
|
|
2170
2230
|
deviceToken: Schema4.optionalKey(Schema4.NonEmptyString),
|
|
2231
|
+
providers: Schema4.optionalKey(ProviderList),
|
|
2171
2232
|
model: Schema4.optionalKey(Schema4.NonEmptyString),
|
|
2172
2233
|
sandbox: Schema4.optionalKey(Sandbox)
|
|
2173
2234
|
});
|
|
@@ -2182,17 +2243,18 @@ var DeviceConfiguration = Schema4.Struct({
|
|
|
2182
2243
|
id: DeviceId,
|
|
2183
2244
|
name: Schema4.NonEmptyString,
|
|
2184
2245
|
workspaces: WorkspaceList,
|
|
2246
|
+
providers: Schema4.optionalKey(ProviderList),
|
|
2185
2247
|
model: Schema4.optionalKey(Schema4.NonEmptyString),
|
|
2186
2248
|
sandbox: Schema4.optionalKey(Sandbox)
|
|
2187
2249
|
});
|
|
2188
2250
|
var defaultConfigDirectory = () => {
|
|
2189
2251
|
if (platform2() === "win32") {
|
|
2190
|
-
return join2(process.env.APPDATA ?? join2(
|
|
2252
|
+
return join2(process.env.APPDATA ?? join2(homedir2(), "AppData", "Roaming"), "Cohall");
|
|
2191
2253
|
}
|
|
2192
2254
|
if (platform2() === "darwin") {
|
|
2193
|
-
return join2(
|
|
2255
|
+
return join2(homedir2(), "Library", "Application Support", "Cohall");
|
|
2194
2256
|
}
|
|
2195
|
-
return join2(process.env.XDG_CONFIG_HOME ?? join2(
|
|
2257
|
+
return join2(process.env.XDG_CONFIG_HOME ?? join2(homedir2(), ".config"), "cohall");
|
|
2196
2258
|
};
|
|
2197
2259
|
var configurationPath = () => resolve(process.env.COHALL_CONFIG ?? join2(defaultConfigDirectory(), "config.json"));
|
|
2198
2260
|
var normalizeRelayUrl = (input) => {
|
|
@@ -2217,6 +2279,9 @@ var parseWorkspaces = (workspaceList, workspaceJson) => Effect3.tryPromise({
|
|
|
2217
2279
|
},
|
|
2218
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)}`)
|
|
2219
2281
|
});
|
|
2282
|
+
var parseProviders = (input) => Schema4.decodeUnknownSync(ProviderList)([
|
|
2283
|
+
...new Set(input.split(",").map((provider) => provider.trim()).filter(Boolean))
|
|
2284
|
+
]);
|
|
2220
2285
|
var readStoredConfiguration = async () => {
|
|
2221
2286
|
const path = configurationPath();
|
|
2222
2287
|
if (!await access(path).then(() => true).catch(() => false)) {
|
|
@@ -2251,6 +2316,7 @@ var makeStoredConfiguration = async (input) => {
|
|
|
2251
2316
|
const canonicalWorkspaces = input.workspaces === undefined ? existing?.workspaces ?? [] : await Effect3.runPromise(parseWorkspaces("", JSON.stringify(input.workspaces)));
|
|
2252
2317
|
const clientToken = input.clientToken ?? existing?.clientToken;
|
|
2253
2318
|
const deviceToken = input.deviceToken ?? existing?.deviceToken;
|
|
2319
|
+
const providers = input.providers === "auto" ? undefined : input.providers ?? existing?.providers;
|
|
2254
2320
|
return StoredConfiguration.make({
|
|
2255
2321
|
version: 1,
|
|
2256
2322
|
relayUrl: normalizeRelayUrl(input.relayUrl),
|
|
@@ -2259,6 +2325,7 @@ var makeStoredConfiguration = async (input) => {
|
|
|
2259
2325
|
workspaces: canonicalWorkspaces,
|
|
2260
2326
|
...clientToken === undefined ? {} : { clientToken },
|
|
2261
2327
|
...deviceToken === undefined ? {} : { deviceToken },
|
|
2328
|
+
...providers === undefined ? {} : { providers },
|
|
2262
2329
|
...existing?.model === undefined ? {} : { model: existing.model },
|
|
2263
2330
|
...existing?.sandbox === undefined ? {} : { sandbox: existing.sandbox }
|
|
2264
2331
|
});
|
|
@@ -2276,6 +2343,10 @@ var environmentSandbox = () => {
|
|
|
2276
2343
|
const value = process.env.COHALL_SANDBOX ?? process.env.COHALL_CODEX_SANDBOX;
|
|
2277
2344
|
return value === undefined ? undefined : Schema4.decodeUnknownSync(Sandbox)(value);
|
|
2278
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
|
+
};
|
|
2279
2350
|
var storedOrDefaults = async () => {
|
|
2280
2351
|
const stored = await readStoredConfiguration();
|
|
2281
2352
|
return stored ?? StoredConfiguration.make({
|
|
@@ -2328,12 +2399,14 @@ var loadDeviceConfiguration = Effect3.tryPromise({
|
|
|
2328
2399
|
}
|
|
2329
2400
|
const model = environmentProvider() ?? stored.model;
|
|
2330
2401
|
const sandbox = environmentSandbox() ?? stored.sandbox;
|
|
2402
|
+
const providers = environmentProviders(stored);
|
|
2331
2403
|
return DeviceConfiguration.make({
|
|
2332
2404
|
relayUrl: normalizeRelayUrl(process.env.COHALL_RELAY_URL ?? stored.relayUrl),
|
|
2333
2405
|
token,
|
|
2334
2406
|
id: process.env.COHALL_DEVICE_ID === undefined ? stored.deviceId : DeviceId.make(process.env.COHALL_DEVICE_ID),
|
|
2335
2407
|
name: process.env.COHALL_DEVICE_NAME ?? stored.deviceName,
|
|
2336
2408
|
workspaces,
|
|
2409
|
+
...providers === undefined ? {} : { providers },
|
|
2337
2410
|
...model === undefined ? {} : { model },
|
|
2338
2411
|
...sandbox === undefined ? {} : { sandbox }
|
|
2339
2412
|
});
|
|
@@ -2446,6 +2519,7 @@ var valueOptions = new Set([
|
|
|
2446
2519
|
"prompt",
|
|
2447
2520
|
"prompt-file",
|
|
2448
2521
|
"provider",
|
|
2522
|
+
"providers",
|
|
2449
2523
|
"relay",
|
|
2450
2524
|
"sandbox",
|
|
2451
2525
|
"target",
|
|
@@ -2465,8 +2539,9 @@ var aliases = new Map([
|
|
|
2465
2539
|
var help = `Cohall connects agents running on your own devices.
|
|
2466
2540
|
|
|
2467
2541
|
Usage:
|
|
2468
|
-
cohall join --relay <url> --workspace <path> [--token-file <path>]
|
|
2469
|
-
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]
|
|
2470
2545
|
cohall config
|
|
2471
2546
|
cohall devices
|
|
2472
2547
|
cohall delegate [prompt] [--target @device] [--provider provider]
|
|
@@ -2641,9 +2716,9 @@ var ownerClient = async () => {
|
|
|
2641
2716
|
};
|
|
2642
2717
|
var installSkill = async (target) => {
|
|
2643
2718
|
const roots = {
|
|
2644
|
-
agents: join3(
|
|
2645
|
-
claude: join3(
|
|
2646
|
-
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")
|
|
2647
2722
|
};
|
|
2648
2723
|
const selected = target === "all" ? Object.values(roots) : (target in roots) ? [roots[target]] : undefined;
|
|
2649
2724
|
if (selected === undefined) {
|
|
@@ -2660,7 +2735,14 @@ var printSkill = () => console.log(SKILL_default.trimEnd());
|
|
|
2660
2735
|
var runCli = async (command2, raw) => {
|
|
2661
2736
|
const arguments_ = parseArguments(raw);
|
|
2662
2737
|
if (command2 === "join") {
|
|
2663
|
-
allowOptions(arguments_, [
|
|
2738
|
+
allowOptions(arguments_, [
|
|
2739
|
+
"client-only",
|
|
2740
|
+
"name",
|
|
2741
|
+
"providers",
|
|
2742
|
+
"relay",
|
|
2743
|
+
"token-file",
|
|
2744
|
+
"workspace"
|
|
2745
|
+
]);
|
|
2664
2746
|
noPositionals(arguments_, command2);
|
|
2665
2747
|
const token = await pairingToken(arguments_);
|
|
2666
2748
|
const relayUrl = normalizeRelayUrl(option(arguments_, "relay") ?? "http://127.0.0.1:8787");
|
|
@@ -2668,13 +2750,16 @@ var runCli = async (command2, raw) => {
|
|
|
2668
2750
|
const suppliedWorkspaces = values(arguments_, "workspace");
|
|
2669
2751
|
const workspaces = suppliedWorkspaces.length === 0 ? existing?.workspaces : suppliedWorkspaces;
|
|
2670
2752
|
const deviceName = option(arguments_, "name");
|
|
2753
|
+
const providerInput = option(arguments_, "providers");
|
|
2754
|
+
const providers = providerInput === undefined ? undefined : providerInput === "auto" ? "auto" : parseProviders(providerInput);
|
|
2671
2755
|
if (!arguments_.options.has("client-only") && (workspaces?.length ?? 0) === 0) {
|
|
2672
2756
|
throw new Error("At least one --workspace is required when joining a device");
|
|
2673
2757
|
}
|
|
2674
2758
|
const draft = await makeStoredConfiguration({
|
|
2675
2759
|
relayUrl,
|
|
2676
2760
|
...deviceName === undefined ? {} : { deviceName },
|
|
2677
|
-
...workspaces === undefined ? {} : { workspaces }
|
|
2761
|
+
...workspaces === undefined ? {} : { workspaces },
|
|
2762
|
+
...providers === undefined ? {} : { providers }
|
|
2678
2763
|
});
|
|
2679
2764
|
const paired = await Effect5.runPromise(exchangePairing(relayUrl, { token }));
|
|
2680
2765
|
const clientToken = paired.credentials.find((credential) => credential.session.role === "client")?.token;
|
|
@@ -2714,6 +2799,7 @@ var runCli = async (command2, raw) => {
|
|
|
2714
2799
|
workspaces: configuration2.workspaces,
|
|
2715
2800
|
client_credential: configuration2.clientToken !== undefined,
|
|
2716
2801
|
device_credential: configuration2.deviceToken !== undefined,
|
|
2802
|
+
providers: configuration2.providers ?? "auto",
|
|
2717
2803
|
model: configuration2.model,
|
|
2718
2804
|
sandbox: configuration2.sandbox
|
|
2719
2805
|
}
|
|
@@ -2721,7 +2807,7 @@ var runCli = async (command2, raw) => {
|
|
|
2721
2807
|
return;
|
|
2722
2808
|
}
|
|
2723
2809
|
if (command2 === "configure") {
|
|
2724
|
-
allowOptions(arguments_, ["model", "name", "relay", "sandbox", "workspace"]);
|
|
2810
|
+
allowOptions(arguments_, ["model", "name", "providers", "relay", "sandbox", "workspace"]);
|
|
2725
2811
|
noPositionals(arguments_, command2);
|
|
2726
2812
|
const existing = await readStoredConfiguration();
|
|
2727
2813
|
const relayUrl = normalizeRelayUrl(option(arguments_, "relay") ?? existing?.relayUrl ?? "http://127.0.0.1:8787");
|
|
@@ -2729,6 +2815,8 @@ var runCli = async (command2, raw) => {
|
|
|
2729
2815
|
const workspaces = supplied.length === 0 ? existing?.workspaces ?? [] : await Effect5.runPromise(parseWorkspaces("", JSON.stringify(supplied)));
|
|
2730
2816
|
const sandbox = option(arguments_, "sandbox");
|
|
2731
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);
|
|
2732
2820
|
const configuration2 = StoredConfiguration.make({
|
|
2733
2821
|
version: 1,
|
|
2734
2822
|
relayUrl,
|
|
@@ -2737,6 +2825,7 @@ var runCli = async (command2, raw) => {
|
|
|
2737
2825
|
workspaces,
|
|
2738
2826
|
...existing?.clientToken === undefined ? {} : { clientToken: existing.clientToken },
|
|
2739
2827
|
...existing?.deviceToken === undefined ? {} : { deviceToken: existing.deviceToken },
|
|
2828
|
+
...providers === undefined ? {} : { providers },
|
|
2740
2829
|
...model === undefined ? {} : { model },
|
|
2741
2830
|
...(sandbox ?? existing?.sandbox) === undefined ? {} : {
|
|
2742
2831
|
sandbox: Schema6.decodeUnknownSync(Schema6.Literals(["read-only", "workspace-write", "danger-full-access"]))(sandbox ?? existing?.sandbox)
|
|
@@ -2764,8 +2853,16 @@ var runCli = async (command2, raw) => {
|
|
|
2764
2853
|
print({
|
|
2765
2854
|
cli_skill: "npx -y @akshar5/cohall skill install all",
|
|
2766
2855
|
codex_mcp: "codex mcp add cohall -- npx -y @akshar5/cohall mcp",
|
|
2767
|
-
claude_mcp: "claude mcp add --scope user cohall -- npx -y @akshar5/cohall mcp",
|
|
2768
|
-
|
|
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
|
+
}
|
|
2769
2866
|
});
|
|
2770
2867
|
return;
|
|
2771
2868
|
}
|
|
@@ -2779,17 +2876,39 @@ var runCli = async (command2, raw) => {
|
|
|
2779
2876
|
}).catch(() => {
|
|
2780
2877
|
return;
|
|
2781
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
|
+
];
|
|
2782
2898
|
print({
|
|
2899
|
+
version,
|
|
2783
2900
|
relay: response?.ok === true ? "reachable" : "unreachable",
|
|
2784
2901
|
relay_url: relayUrl,
|
|
2785
2902
|
config_path: configurationPath(),
|
|
2786
2903
|
client_credential: process.env.COHALL_CLIENT_TOKEN !== undefined || configuration2?.clientToken !== undefined,
|
|
2787
|
-
device_credential:
|
|
2904
|
+
device_credential: hasDeviceCredential,
|
|
2788
2905
|
workspaces: configuration2?.workspaces ?? [],
|
|
2789
|
-
providers:
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
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
|
|
2793
2912
|
});
|
|
2794
2913
|
return;
|
|
2795
2914
|
}
|
|
@@ -2933,7 +3052,7 @@ var capabilities = (providers) => {
|
|
|
2933
3052
|
const values2 = providers.map((provider) => ({
|
|
2934
3053
|
id: provider,
|
|
2935
3054
|
label: providerLabel(provider),
|
|
2936
|
-
detail:
|
|
3055
|
+
detail: `${providerLabel(provider)} executable detected; authentication is checked when work starts`
|
|
2937
3056
|
}));
|
|
2938
3057
|
if (findExecutable("google-chrome") !== undefined || findExecutable("chromium") !== undefined || platform3() === "darwin") {
|
|
2939
3058
|
values2.push({ id: "browser-session", label: "Signed-in browser" });
|
|
@@ -2946,10 +3065,12 @@ var capabilities = (providers) => {
|
|
|
2946
3065
|
}
|
|
2947
3066
|
return values2;
|
|
2948
3067
|
};
|
|
3068
|
+
var selectProviders = (installed, configured) => configured === undefined ? installed : configured.filter((provider) => installed.includes(provider));
|
|
2949
3069
|
var describeDevice = (configuration, status = "online") => {
|
|
2950
3070
|
const operatingSystem = platform3();
|
|
2951
3071
|
const platformName = operatingSystem === "darwin" || operatingSystem === "linux" ? operatingSystem : operatingSystem === "win32" ? "windows" : "unknown";
|
|
2952
|
-
const
|
|
3072
|
+
const installed = availableProviders();
|
|
3073
|
+
const providers = selectProviders(installed, configuration.providers);
|
|
2953
3074
|
return Device.make({
|
|
2954
3075
|
id: configuration.id,
|
|
2955
3076
|
name: configuration.name,
|
|
@@ -3284,6 +3405,14 @@ var main = async () => {
|
|
|
3284
3405
|
console.log(version);
|
|
3285
3406
|
return;
|
|
3286
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
|
+
}
|
|
3287
3416
|
if (command2 === "relay") {
|
|
3288
3417
|
const { runRelay: runRelay2 } = await init_main().then(() => exports_main);
|
|
3289
3418
|
await runRelay2();
|
|
@@ -3306,4 +3435,4 @@ await main().catch((cause) => {
|
|
|
3306
3435
|
process.exitCode = 1;
|
|
3307
3436
|
});
|
|
3308
3437
|
|
|
3309
|
-
//# debugId=
|
|
3438
|
+
//# debugId=480B573B626A966164756E2164756E21
|