@cordfuse/crosstalk 7.0.0-alpha.4 → 7.0.0-alpha.5
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/README.md +2 -2
- package/commands/init.js +3 -3
- package/commands/up.js +119 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ crosstalk chat --shell
|
|
|
36
36
|
exit
|
|
37
37
|
crosstalk chat --agent claude --login
|
|
38
38
|
# Auth URL opens in your default browser via xdg-open / open / start.
|
|
39
|
-
# Operator never has to copy-paste it. Token saved in the /root
|
|
39
|
+
# Operator never has to copy-paste it. Token saved in the /crosstalk-root
|
|
40
40
|
# volume so subsequent dispatched workers reuse it.
|
|
41
41
|
|
|
42
42
|
# 4. Send your first message.
|
|
@@ -73,7 +73,7 @@ crosstalk replies <relPath printed by step above>
|
|
|
73
73
|
## What lives where
|
|
74
74
|
|
|
75
75
|
- **Host** — `crosstalk` binary on PATH, `~/path/to/mytransport/` (the operator's transport directory, bind-mounted).
|
|
76
|
-
- **Container** (`crosstalk-server`, image `ghcr.io/cordfuse/crosstalk-server`) — `crosstalkd` daemon, dispatch state
|
|
76
|
+
- **Container** (`crosstalk-server`, image `ghcr.io/cordfuse/crosstalk-server`) — `crosstalkd` daemon, dispatch state bind-mounted at `<base>/<transport>/crosstalk-state:/var/lib/crosstalk-state`, operator-installed agent CLIs + auth tokens bind-mounted at `<base>/<transport>/crosstalk-root:/crosstalk-root`. `<base>` is per-OS (`$XDG_DATA_HOME/crosstalk` on Linux, `~/Library/Application Support/crosstalk` on macOS, `%LOCALAPPDATA%\crosstalk` on Windows); `CROSSTALK_STORAGE_MODE=system` switches to `/var/lib`-style paths instead.
|
|
77
77
|
- **Engine HTTP API** — `127.0.0.1:7000` (compose `127.0.0.1:HOST:CONTAINER` port mapping is the actual security boundary; engine binds 0.0.0.0 inside the container).
|
|
78
78
|
- **Transport bind-mount** — `$PWD:/var/lib/crosstalk-transport`. Operator git commands on the host are seen by the engine immediately.
|
|
79
79
|
|
package/commands/init.js
CHANGED
|
@@ -21,7 +21,7 @@ import { spawnSync } from 'child_process';
|
|
|
21
21
|
import { has, positionals } from '../lib/argv.js';
|
|
22
22
|
|
|
23
23
|
const DEFAULT_IMAGE = process.env.CROSSTALK_IMAGE
|
|
24
|
-
?? 'ghcr.io/cordfuse/crosstalk-server:7.0.0-alpha.
|
|
24
|
+
?? 'ghcr.io/cordfuse/crosstalk-server:7.0.0-alpha.5';
|
|
25
25
|
|
|
26
26
|
function usage(exit = 0) {
|
|
27
27
|
const w = exit === 0 ? process.stdout : process.stderr;
|
|
@@ -34,9 +34,9 @@ function usage(exit = 0) {
|
|
|
34
34
|
|
|
35
35
|
The image must be pullable (or already present locally). For local dev
|
|
36
36
|
before the GHCR publish pipeline ships, build the image first:
|
|
37
|
-
docker build -t crosstalk-server:7.0.0-alpha.
|
|
37
|
+
docker build -t crosstalk-server:7.0.0-alpha.5 -f server/Dockerfile .
|
|
38
38
|
Then run:
|
|
39
|
-
CROSSTALK_IMAGE=crosstalk-server:7.0.0-alpha.
|
|
39
|
+
CROSSTALK_IMAGE=crosstalk-server:7.0.0-alpha.5 crosstalk init mytransport
|
|
40
40
|
`,
|
|
41
41
|
);
|
|
42
42
|
process.exit(exit);
|
package/commands/up.js
CHANGED
|
@@ -9,15 +9,17 @@
|
|
|
9
9
|
//
|
|
10
10
|
// Subsequent runs: just shell out to `docker compose up -d`.
|
|
11
11
|
|
|
12
|
-
import { writeFileSync, existsSync, appendFileSync, readFileSync } from 'fs';
|
|
12
|
+
import { writeFileSync, existsSync, mkdirSync, appendFileSync, readFileSync } from 'fs';
|
|
13
13
|
import { spawnSync } from 'child_process';
|
|
14
14
|
import { join } from 'path';
|
|
15
|
+
import { homedir } from 'os';
|
|
15
16
|
import { requireTransportRoot, transportName, composeFile } from '../lib/transport.js';
|
|
16
17
|
import { has } from '../lib/argv.js';
|
|
17
18
|
|
|
18
19
|
const DEFAULT_IMAGE = process.env.CROSSTALK_IMAGE
|
|
19
|
-
?? 'ghcr.io/cordfuse/crosstalk-server:7.0.0-alpha.
|
|
20
|
+
?? 'ghcr.io/cordfuse/crosstalk-server:7.0.0-alpha.5';
|
|
20
21
|
const DEFAULT_API_PORT = Number(process.env.CROSSTALK_API_PORT) || 7000;
|
|
22
|
+
const STORAGE_MODE = (process.env.CROSSTALK_STORAGE_MODE ?? 'user').toLowerCase();
|
|
21
23
|
|
|
22
24
|
function usage(exit = 0) {
|
|
23
25
|
const w = exit === 0 ? process.stdout : process.stderr;
|
|
@@ -26,25 +28,94 @@ function usage(exit = 0) {
|
|
|
26
28
|
|
|
27
29
|
Brings up the engine container for the transport in the current directory
|
|
28
30
|
(or any ancestor containing CROSSTALK-VERSION). First-time use generates
|
|
29
|
-
docker-compose.yml in the transport root.
|
|
30
|
-
|
|
31
|
+
docker-compose.yml in the transport root.
|
|
32
|
+
|
|
33
|
+
Environment:
|
|
34
|
+
CROSSTALK_IMAGE Image to pin (default: bundled GHCR tag)
|
|
35
|
+
CROSSTALK_API_PORT Host port to map (default: 7000)
|
|
36
|
+
CROSSTALK_ALIAS Dispatcher's machine identity in the bus
|
|
37
|
+
CROSSTALK_STORAGE_MODE user (default) | system
|
|
38
|
+
Where bind-mounted crosstalk-root +
|
|
39
|
+
crosstalk-state live on the host. 'system'
|
|
40
|
+
uses paths under /var/lib (Linux) / /Library
|
|
41
|
+
(macOS) / C:\\ProgramData (Windows) — requires
|
|
42
|
+
sudo/UAC and (on macOS) Docker Desktop file-
|
|
43
|
+
sharing allowlist. 'user' uses paths under
|
|
44
|
+
the operator's home directory with no
|
|
45
|
+
escalation.
|
|
31
46
|
`,
|
|
32
47
|
);
|
|
33
48
|
process.exit(exit);
|
|
34
49
|
}
|
|
35
50
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
51
|
+
// Resolve the host directories that back the container's /crosstalk-root
|
|
52
|
+
// (operator-installed CLIs + auth) and /var/lib/crosstalk-state
|
|
53
|
+
// (dispatcher state) bind mounts.
|
|
54
|
+
//
|
|
55
|
+
// User-mode (default) keeps everything under the operator's home so no
|
|
56
|
+
// sudo / UAC / Docker Desktop file-sharing allowlist gymnastics are
|
|
57
|
+
// needed. System-mode is for headless production-style deployments
|
|
58
|
+
// where multiple operators share a machine and a service user manages
|
|
59
|
+
// the daemon.
|
|
60
|
+
//
|
|
61
|
+
// Host-side dir names use the same `crosstalk-` prefix as the container
|
|
62
|
+
// paths so things stay identifiable wherever they surface (backup tools,
|
|
63
|
+
// file managers, support-email screenshots).
|
|
64
|
+
function resolveStoragePaths(transportName, mode) {
|
|
65
|
+
const platform = process.platform;
|
|
66
|
+
// Per-OS base directories.
|
|
67
|
+
const bases = (platform === 'darwin')
|
|
68
|
+
? {
|
|
69
|
+
user: join(homedir(), 'Library', 'Application Support', 'crosstalk'),
|
|
70
|
+
system: '/Library/Application Support/crosstalk',
|
|
71
|
+
}
|
|
72
|
+
: (platform === 'win32')
|
|
73
|
+
? {
|
|
74
|
+
// %LOCALAPPDATA% should always be set on supported Win versions;
|
|
75
|
+
// fall back to ~/AppData/Local if it isn't.
|
|
76
|
+
user: process.env['LOCALAPPDATA']
|
|
77
|
+
? join(process.env['LOCALAPPDATA'], 'crosstalk')
|
|
78
|
+
: join(homedir(), 'AppData', 'Local', 'crosstalk'),
|
|
79
|
+
system: 'C:\\ProgramData\\crosstalk',
|
|
80
|
+
}
|
|
81
|
+
: /* linux + other unix */ {
|
|
82
|
+
// Honor XDG_DATA_HOME silently (no docs surface; the 99% of
|
|
83
|
+
// operators who haven't set it get ~/.local/share/crosstalk
|
|
84
|
+
// either way).
|
|
85
|
+
user: join(process.env['XDG_DATA_HOME'] ?? join(homedir(), '.local', 'share'), 'crosstalk'),
|
|
86
|
+
system: '/var/lib/crosstalk',
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
if (mode !== 'user' && mode !== 'system') {
|
|
90
|
+
throw new Error(
|
|
91
|
+
`crosstalk up: CROSSTALK_STORAGE_MODE='${mode}' invalid — must be 'user' or 'system'.`,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
const base = bases[mode];
|
|
95
|
+
const transportRoot = join(base, transportName);
|
|
96
|
+
return {
|
|
97
|
+
crosstalkRoot: join(transportRoot, 'crosstalk-root'),
|
|
98
|
+
crosstalkState: join(transportRoot, 'crosstalk-state'),
|
|
99
|
+
mode,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function renderCompose({ name, image, apiPort, alias, uid, gid, crosstalkRootHost, crosstalkStateHost, storageMode }) {
|
|
104
|
+
// Entrypoint runs as root for setup (npm prefix, SSH key import). It
|
|
105
|
+
// then adjusts a 'crosstalkd' user to match CROSSTALK_UID/GID
|
|
106
|
+
// (operator's host UID), chowns the bind-mounted transport, state
|
|
107
|
+
// dir, AND /crosstalk-root to that user, then drops via setpriv before
|
|
108
|
+
// exec'ing the dispatcher. Net effect: bind-mount files written by
|
|
109
|
+
// the engine are operator-owned on the host, so \`git remote add\`
|
|
110
|
+
// and \`rm -rf\` from the host both work without sudo.
|
|
45
111
|
return `# Generated by \`crosstalk up\`. Safe to edit; subsequent \`up\`s
|
|
46
112
|
# only regenerate when this file is missing. Compose config is machine-
|
|
47
113
|
# local — kept out of git via .gitignore by default.
|
|
114
|
+
#
|
|
115
|
+
# Storage mode: ${storageMode}
|
|
116
|
+
# crosstalk-root (container /crosstalk-root) → ${crosstalkRootHost}
|
|
117
|
+
# crosstalk-state (container /var/lib/crosstalk-state) → ${crosstalkStateHost}
|
|
118
|
+
# transport (container /var/lib/crosstalk-transport) → \${PWD} (bind to this directory)
|
|
48
119
|
|
|
49
120
|
services:
|
|
50
121
|
crosstalkd:
|
|
@@ -58,11 +129,12 @@ services:
|
|
|
58
129
|
# to the engine on the next tick. No clone-on-startup needed.
|
|
59
130
|
- .:/var/lib/crosstalk-transport
|
|
60
131
|
# Operator-installed agent CLIs (claude, codex, agy, ...) + auth
|
|
61
|
-
# state.
|
|
62
|
-
-
|
|
63
|
-
|
|
64
|
-
#
|
|
65
|
-
|
|
132
|
+
# state. Chowned to operator UID at entrypoint so host-side
|
|
133
|
+
# \`rm -rf\` works without sudo.
|
|
134
|
+
- ${crosstalkRootHost}:/crosstalk-root
|
|
135
|
+
# Dispatcher's machine-local state: cursor, heartbeat, errors.log,
|
|
136
|
+
# pidfile, wake.signal. Operator-UID-owned (chowned by entrypoint).
|
|
137
|
+
- ${crosstalkStateHost}:/var/lib/crosstalk-state
|
|
66
138
|
environment:
|
|
67
139
|
CROSSTALK_ALIAS: ${alias}
|
|
68
140
|
CROSSTALK_UID: "${uid}"
|
|
@@ -70,10 +142,6 @@ services:
|
|
|
70
142
|
CROSSTALKD_API_PORT: "7000"
|
|
71
143
|
DISPATCH_JSON: "true"
|
|
72
144
|
DISPATCH_POLL_SECONDS: "30"
|
|
73
|
-
|
|
74
|
-
volumes:
|
|
75
|
-
crosstalk-root:
|
|
76
|
-
crosstalk-state:
|
|
77
145
|
`;
|
|
78
146
|
}
|
|
79
147
|
|
|
@@ -101,6 +169,31 @@ export async function run(argv) {
|
|
|
101
169
|
const alias = process.env.CROSSTALK_ALIAS ?? process.env.HOSTNAME ?? transportName(root);
|
|
102
170
|
const uid = typeof process.getuid === 'function' ? process.getuid() : 1000;
|
|
103
171
|
const gid = typeof process.getgid === 'function' ? process.getgid() : 1000;
|
|
172
|
+
let storage;
|
|
173
|
+
try {
|
|
174
|
+
storage = resolveStoragePaths(transportName(root), STORAGE_MODE);
|
|
175
|
+
} catch (err) {
|
|
176
|
+
process.stderr.write(`${err.message}\n`);
|
|
177
|
+
return 1;
|
|
178
|
+
}
|
|
179
|
+
// Pre-create the host bind-mount directories. Docker would auto-
|
|
180
|
+
// create them as root-owned otherwise, which would break the
|
|
181
|
+
// operator-UID chown step inside the container.
|
|
182
|
+
try {
|
|
183
|
+
mkdirSync(storage.crosstalkRoot, { recursive: true });
|
|
184
|
+
mkdirSync(storage.crosstalkState, { recursive: true });
|
|
185
|
+
} catch (err) {
|
|
186
|
+
process.stderr.write(
|
|
187
|
+
`crosstalk up: failed to create storage directories — ${err.message}\n`,
|
|
188
|
+
);
|
|
189
|
+
if (storage.mode === 'system') {
|
|
190
|
+
process.stderr.write(
|
|
191
|
+
` System-mode storage requires write access to ${storage.crosstalkRoot}'s parent.\n` +
|
|
192
|
+
` Try: sudo mkdir -p ${storage.crosstalkRoot} ${storage.crosstalkState} && sudo chown -R \$USER ${storage.crosstalkRoot} ${storage.crosstalkState}\n`,
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
return 1;
|
|
196
|
+
}
|
|
104
197
|
const content = renderCompose({
|
|
105
198
|
name: transportName(root),
|
|
106
199
|
image: DEFAULT_IMAGE,
|
|
@@ -108,10 +201,13 @@ export async function run(argv) {
|
|
|
108
201
|
alias,
|
|
109
202
|
uid,
|
|
110
203
|
gid,
|
|
204
|
+
crosstalkRootHost: storage.crosstalkRoot,
|
|
205
|
+
crosstalkStateHost: storage.crosstalkState,
|
|
206
|
+
storageMode: storage.mode,
|
|
111
207
|
});
|
|
112
208
|
writeFileSync(composeYml, content);
|
|
113
209
|
ensureGitignored(root);
|
|
114
|
-
process.stdout.write(`Generated ${composeYml}\n`);
|
|
210
|
+
process.stdout.write(`Generated ${composeYml} (storage: ${storage.mode})\n`);
|
|
115
211
|
}
|
|
116
212
|
|
|
117
213
|
const r = spawnSync('docker', ['compose', '-f', composeYml, 'up', '-d'], {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cordfuse/crosstalk",
|
|
3
|
-
"version": "7.0.0-alpha.
|
|
3
|
+
"version": "7.0.0-alpha.5",
|
|
4
4
|
"description": "Crosstalk client — host-side CLI that talks to the crosstalkd daemon running inside the crosstalk-server container.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|