@cordfuse/crosstalk 7.0.0-alpha.5 → 7.0.0-alpha.6

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 CHANGED
@@ -19,16 +19,14 @@ Requires: Node.js ≥ 20 + Docker on PATH + git on PATH. Works on Linux, macOS,
19
19
  ## Quickstart
20
20
 
21
21
  ```sh
22
- # 1. Scaffold a transport.
23
- crosstalk init mytransport
24
- cd mytransport
22
+ # 1. Scaffold a transport. Runtime-owned at <base>/crosstalk/transport/.
23
+ crosstalk init
25
24
 
26
- # 2. Bring up the engine container for this transport.
25
+ # 2. Bring up the engine container.
27
26
  crosstalk up
28
- # Generates docker-compose.yml in the transport root and runs
29
- # `docker compose up -d` for ghcr.io/cordfuse/crosstalk-server:<latest>.
30
- # Bind-mounts $PWD as the transport any `git remote add` from the
31
- # host is immediately visible to the engine.
27
+ # Generates <base>/crosstalk/docker-compose.yml and runs `docker
28
+ # compose up -d` for ghcr.io/cordfuse/crosstalk-server:<latest>.
29
+ # No cwd magic works from any directory.
32
30
 
33
31
  # 3. Install an agent CLI inside the container, then authenticate.
34
32
  crosstalk chat --shell
@@ -36,8 +34,8 @@ crosstalk chat --shell
36
34
  exit
37
35
  crosstalk chat --agent claude --login
38
36
  # Auth URL opens in your default browser via xdg-open / open / start.
39
- # Operator never has to copy-paste it. Token saved in the /crosstalk-root
40
- # volume so subsequent dispatched workers reuse it.
37
+ # Token saved in the /crosstalk-root volume so subsequent dispatched
38
+ # workers reuse it.
41
39
 
42
40
  # 4. Send your first message.
43
41
  crosstalk channel general
@@ -49,16 +47,19 @@ crosstalk replies <relPath printed by step above>
49
47
 
50
48
  ## Subcommand surface
51
49
 
50
+ Every name-resolved verb takes an optional `--containername <name>` (or `-c <name>`) flag. Bare verb = default container (literally named `crosstalk`).
51
+
52
52
  | Group | Subcommand | What it does |
53
53
  |---|---|---|
54
54
  | Meta | `crosstalk version` | Print client + engine versions (warns on skew) |
55
- | Protocol | `crosstalk init <dir>` | Scaffold a transport + `git init` |
55
+ | Protocol | `crosstalk init [-c <name>] [--remote <url>]` | Scaffold a transport at `<base>/<name>/transport/` |
56
56
  | Protocol | `crosstalk run --type primitive\|workflow ...` | Dispatch a primitive or workflow |
57
57
  | Protocol | `crosstalk replies <relPath>...` | Poll reply status |
58
- | Protocol | `crosstalk status` | Engine heartbeat, claimed models, channels |
58
+ | Protocol | `crosstalk status` | Container name, transport host path, engine state, channels |
59
59
  | Protocol | `crosstalk channel <name> [--rename\|--delete]` | Channel operations |
60
- | Lifecycle | `crosstalk up` | Generate docker-compose, start engine container |
61
- | Lifecycle | `crosstalk down [--volumes]` | Stop the engine container |
60
+ | Lifecycle | `crosstalk up` | Generate docker-compose, start the engine container |
61
+ | Lifecycle | `crosstalk down` | Stop the engine container (keep storage) |
62
+ | Lifecycle | `crosstalk rm [--force]` | Remove container AND wipe storage (cannot undo) |
62
63
  | Lifecycle | `crosstalk restart` | Down + up |
63
64
  | Lifecycle | `crosstalk pull` | Refresh the engine image |
64
65
  | Lifecycle | `crosstalk logs [-f]` | Stream the engine container's logs |
@@ -72,10 +73,14 @@ crosstalk replies <relPath printed by step above>
72
73
 
73
74
  ## What lives where
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 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
- - **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
- - **Transport bind-mount** `$PWD:/var/lib/crosstalk-transport`. Operator git commands on the host are seen by the engine immediately.
76
+ - **Host** — `crosstalk` binary on PATH. Per-transport storage at `<base>/<name>/` (one tree per container).
77
+ - **Container** (literally named whatever the operator picked at `init`, default `crosstalk`; image `ghcr.io/cordfuse/crosstalk-server`) — `crosstalkd` daemon, bind-mounted from:
78
+ - `<base>/<name>/transport/` `/var/lib/crosstalk-transport` (runtime-owned git repo)
79
+ - `<base>/<name>/crosstalk-root/` `/crosstalk-root` (operator CLIs + auth)
80
+ - `<base>/<name>/crosstalk-state/` → `/var/lib/crosstalk-state` (cursor + heartbeat)
81
+ - **`<base>`** is per-OS user-mode: `$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.
82
+ - **Engine HTTP API** — `127.0.0.1:<port>`. Default container = port 7000; named containers allocate at init from 7001 upward, recorded in `<base>/<name>/api-port`.
83
+ - **Container identification** — every crosstalkd container gets the Docker label `crosstalk.transport=true`. The client uses the label for inventory queries, not name-pattern matching.
79
84
 
80
85
  ---
81
86
 
package/bin/crosstalk.js CHANGED
@@ -30,6 +30,7 @@ const SUBCOMMANDS = {
30
30
  init: 'init.js',
31
31
  up: 'up.js',
32
32
  down: 'down.js',
33
+ rm: 'rm.js',
33
34
  restart: 'restart.js',
34
35
  pull: 'pull.js',
35
36
  logs: 'logs.js',
@@ -4,7 +4,7 @@
4
4
  // endpoints that haven't been added to the engine yet; they land
5
5
  // when the engine gains them.
6
6
 
7
- import { api } from '../lib/api-client.js';
7
+ import { apiFor } from '../lib/api-client.js';
8
8
  import { reportAndExit } from '../lib/errors.js';
9
9
  import { positionals, has, flag } from '../lib/argv.js';
10
10
 
@@ -25,7 +25,8 @@ function usage(exit = 0) {
25
25
  export async function run(argv) {
26
26
  if (has(argv, '--help') || has(argv, '-h')) usage(0);
27
27
 
28
- const pos = positionals(argv, ['--rename']);
28
+ const api = apiFor(argv);
29
+ const pos = positionals(argv, ['--rename', '--containername', '-c']);
29
30
  if (pos.length === 0) usage(1);
30
31
 
31
32
  // `crosstalk channel list` → list channels
package/commands/chat.js CHANGED
@@ -23,8 +23,8 @@
23
23
  // fallback, not the primary mechanism.
24
24
 
25
25
  import { spawn, spawnSync } from 'child_process';
26
- import { requireTransportRoot, containerName } from '../lib/transport.js';
27
- import { api, ConnectError } from '../lib/api-client.js';
26
+ import { requireInitialized } from '../lib/resolve.js';
27
+ import { apiFor, ConnectError } from '../lib/api-client.js';
28
28
  import { flag, has } from '../lib/argv.js';
29
29
 
30
30
  const KNOWN_AGENTS = ['claude', 'codex', 'gemini', 'qwen', 'opencode', 'agy'];
@@ -195,8 +195,8 @@ function runLogin(container, agent) {
195
195
  export async function run(argv) {
196
196
  if (has(argv, '--help') || has(argv, '-h')) usage(0);
197
197
 
198
- const root = requireTransportRoot();
199
- const name = containerName(root);
198
+ const { name } = requireInitialized(argv);
199
+ const api = apiFor(argv);
200
200
 
201
201
  if (has(argv, '--shell')) {
202
202
  return runInteractive(name, 'bash');
package/commands/down.js CHANGED
@@ -1,18 +1,22 @@
1
- // crosstalk down — stop the engine container for this transport.
1
+ // crosstalk down — stop a transport's container, keep storage.
2
+ //
3
+ // alpha.6: name-resolved. --volumes flag retires (no named volumes in
4
+ // the bind-mount model). Reversible — next `up` resumes against the
5
+ // preserved <base>/<name>/ storage.
2
6
 
3
7
  import { existsSync } from 'fs';
4
8
  import { spawnSync } from 'child_process';
5
- import { requireTransportRoot, composeFile } from '../lib/transport.js';
6
9
  import { has } from '../lib/argv.js';
10
+ import { requireInitialized, DEFAULT_CONTAINER_NAME } from '../lib/resolve.js';
7
11
 
8
12
  function usage(exit = 0) {
9
13
  const w = exit === 0 ? process.stdout : process.stderr;
10
14
  w.write(
11
- `Usage: crosstalk down [--volumes]
15
+ `Usage: crosstalk down [--containername <name>]
12
16
 
13
- Stops the engine container for the transport in the current directory.
14
- With --volumes, also removes the persistent volumes (clears operator-
15
- installed CLIs + auth state; you'll have to re-install + re-auth).
17
+ Stops the engine container for a transport. Storage at <base>/<name>/
18
+ is preserved next 'crosstalk up' resumes against it. Use
19
+ 'crosstalk rm' if you want to wipe storage as well.
16
20
  `,
17
21
  );
18
22
  process.exit(exit);
@@ -21,17 +25,16 @@ installed CLIs + auth state; you'll have to re-install + re-auth).
21
25
  export async function run(argv) {
22
26
  if (has(argv, '--help') || has(argv, '-h')) usage(0);
23
27
 
24
- const root = requireTransportRoot();
25
- const composeYml = composeFile(root);
26
- if (!existsSync(composeYml)) {
27
- process.stderr.write(`crosstalk down: no docker-compose.yml at ${composeYml}\n`);
28
- process.stderr.write(` (this transport was probably never brought up — nothing to do)\n`);
28
+ const { name, paths } = requireInitialized(argv);
29
+ if (!existsSync(paths.composeFile)) {
30
+ process.stderr.write(
31
+ `crosstalk down: '${name}' has no compose file (never brought up). Nothing to do.\n`,
32
+ );
29
33
  return 0;
30
34
  }
31
35
 
32
- const args = ['compose', '-f', composeYml, 'down'];
33
- if (has(argv, '--volumes')) args.push('--volumes');
34
-
35
- const r = spawnSync('docker', args, { cwd: root, stdio: 'inherit' });
36
+ const r = spawnSync('docker', ['compose', '-f', paths.composeFile, 'down'], {
37
+ stdio: 'inherit',
38
+ });
36
39
  return r.status ?? 1;
37
40
  }
package/commands/init.js CHANGED
@@ -1,42 +1,70 @@
1
- // crosstalk init <dir> — scaffold a new transport via docker.
1
+ // crosstalk init — scaffold a transport into <base>/<name>/.
2
2
  //
3
- // Doesn't go through the engine HTTP API there's no running engine
4
- // at init time. Instead, runs the engine in a one-shot container with
5
- // a bind-mount of the target directory, and lets crosstalkd's init
6
- // subcommand write the template files directly.
3
+ // alpha.6: runtime owns the transport. Operator no longer creates a
4
+ // `transport/` dir on their host filesystem; init creates it at
5
+ // <base>/<name>/transport/ where <base> is per-OS user-mode (or
6
+ // system-mode via CROSSTALK_STORAGE_MODE=system).
7
7
  //
8
- // docker run --rm \
9
- // -v "$(realpath <dir>):/init-target" \
10
- // --user "$(id -u):$(id -g)" \
11
- // <image> \
12
- // crosstalkd init /init-target
8
+ // Operator's interaction surface:
9
+ // crosstalk init # default container 'crosstalk'
10
+ // crosstalk init --containername uat # named container 'uat'
11
+ // crosstalk init --remote git@example:t.git # set upstream at init time
13
12
  //
14
- // The --user flag is critical: without it, the template files would
15
- // land owned by root (the container's user). Setting it to the
16
- // operator's UID/GID keeps file ownership sane.
13
+ // Mechanics:
14
+ // 1. Resolve <base>/<name>/ and pre-create the bind-mount subdirs
15
+ // (transport/, crosstalk-root/, crosstalk-state/).
16
+ // 2. Run `crosstalkd init /init-target` in a one-shot container,
17
+ // bind-mounting transport/ as /init-target. The engine writes its
18
+ // template (CROSSTALK-VERSION, PROTOCOL.md, CROSSTALK.md, data/,
19
+ // local/) into the bind-mount.
20
+ // 3. `git init` inside transport/ + initial commit.
21
+ // 4. If --remote: `git remote add origin <url>`.
22
+ // 5. Pick a free API port (default container always 7000; named
23
+ // containers search from 7001) and write to <base>/<name>/api-port.
17
24
 
18
- import { mkdirSync, existsSync } from 'fs';
19
- import { resolve } from 'path';
25
+ import { mkdirSync, existsSync, writeFileSync } from 'fs';
20
26
  import { spawnSync } from 'child_process';
21
- import { has, positionals } from '../lib/argv.js';
27
+ import { has, flag } from '../lib/argv.js';
28
+ import {
29
+ parseContainerName,
30
+ storagePaths,
31
+ isInitialized,
32
+ pickFreePort,
33
+ DEFAULT_CONTAINER_NAME,
34
+ } from '../lib/resolve.js';
22
35
 
23
36
  const DEFAULT_IMAGE = process.env.CROSSTALK_IMAGE
24
- ?? 'ghcr.io/cordfuse/crosstalk-server:7.0.0-alpha.5';
37
+ ?? 'ghcr.io/cordfuse/crosstalk-server:7.0.0-alpha.6';
25
38
 
26
39
  function usage(exit = 0) {
27
40
  const w = exit === 0 ? process.stdout : process.stderr;
28
41
  w.write(
29
42
  `Usage:
30
- crosstalk init <dir> # scaffold a new transport at <dir>
43
+ crosstalk init [--containername <name>] [--remote <url>]
31
44
 
32
- Image: ${DEFAULT_IMAGE}
33
- Override: CROSSTALK_IMAGE=<image-tag> crosstalk init <dir>
45
+ Creates a new transport on this machine. The transport git repo lives at
46
+ <base>/<name>/transport/ where <base> is:
47
+ Linux: $XDG_DATA_HOME/crosstalk/ (or ~/.local/share/crosstalk/)
48
+ macOS: ~/Library/Application Support/crosstalk/
49
+ Windows: %LOCALAPPDATA%\\crosstalk\\
34
50
 
35
- The image must be pullable (or already present locally). For local dev
36
- before the GHCR publish pipeline ships, build the image first:
37
- docker build -t crosstalk-server:7.0.0-alpha.5 -f server/Dockerfile .
38
- Then run:
39
- CROSSTALK_IMAGE=crosstalk-server:7.0.0-alpha.5 crosstalk init mytransport
51
+ Set CROSSTALK_STORAGE_MODE=system to use the system-wide path (sudo/UAC
52
+ required; intended for headless multi-operator hosts).
53
+
54
+ Options:
55
+ --containername <name> Container name (default: 'crosstalk'). Must be
56
+ lowercase alphanumeric + ._- (no leading . or -).
57
+ One default + N named containers per machine.
58
+ --remote <url> Set git upstream at init time. Engine's first
59
+ tick will fetch + rebase against this remote.
60
+ --image <tag> Override the engine image (default: bundled
61
+ GHCR tag; or CROSSTALK_IMAGE env).
62
+ -c <name> Shorthand for --containername.
63
+
64
+ Examples:
65
+ crosstalk init # default container
66
+ crosstalk init --containername uat # second container
67
+ crosstalk init --remote git\\@github.com:me/t.git # with upstream
40
68
  `,
41
69
  );
42
70
  process.exit(exit);
@@ -44,62 +72,124 @@ function usage(exit = 0) {
44
72
 
45
73
  export async function run(argv) {
46
74
  if (has(argv, '--help') || has(argv, '-h')) usage(0);
47
- const pos = positionals(argv, []);
48
- if (pos.length === 0) usage(1);
49
75
 
50
- const target = resolve(pos[0]);
51
- if (!existsSync(target)) mkdirSync(target, { recursive: true });
76
+ let name;
77
+ try {
78
+ name = parseContainerName(argv);
79
+ } catch (err) {
80
+ process.stderr.write(`crosstalk init: ${err.message}\n`);
81
+ return 1;
82
+ }
83
+
84
+ if (isInitialized(name)) {
85
+ const paths = storagePaths(name);
86
+ process.stderr.write(
87
+ `crosstalk init: transport '${name}' already initialized at ${paths.transportDir}.\n` +
88
+ ` Run 'crosstalk up${name === DEFAULT_CONTAINER_NAME ? '' : ` --containername ${name}`}' to start it,\n` +
89
+ ` or 'crosstalk rm${name === DEFAULT_CONTAINER_NAME ? '' : ` --containername ${name}`}' to wipe it first.\n`,
90
+ );
91
+ return 1;
92
+ }
93
+
94
+ const remote = flag(argv, '--remote');
95
+ const image = flag(argv, '--image') ?? DEFAULT_IMAGE;
96
+ const paths = storagePaths(name);
97
+
98
+ // Pre-create the storage tree. Docker would auto-create the bind-mount
99
+ // dirs root-owned otherwise, breaking the entrypoint's chown step.
100
+ try {
101
+ mkdirSync(paths.transportDir, { recursive: true });
102
+ mkdirSync(paths.crosstalkRoot, { recursive: true });
103
+ mkdirSync(paths.crosstalkState, { recursive: true });
104
+ } catch (err) {
105
+ process.stderr.write(
106
+ `crosstalk init: failed to create storage at ${paths.storageRoot} — ${err.message}\n`,
107
+ );
108
+ if (paths.mode === 'system') {
109
+ process.stderr.write(
110
+ ` System-mode storage requires write access to ${paths.base}.\n` +
111
+ ` Try: sudo mkdir -p ${paths.storageRoot} && sudo chown -R $USER ${paths.storageRoot}\n`,
112
+ );
113
+ }
114
+ return 1;
115
+ }
52
116
 
53
- // Resolve uid/gid via process.getuid() POSIX only. On Windows the
54
- // --user flag is omitted; Docker Desktop handles ownership differently.
117
+ // Engine-side template scaffolding: run crosstalkd init in a one-shot
118
+ // container, bind-mounting our transport/ as /init-target. --user maps
119
+ // ownership to operator UID so the written files are operator-owned.
55
120
  const uid = typeof process.getuid === 'function' ? process.getuid() : null;
56
121
  const gid = typeof process.getgid === 'function' ? process.getgid() : null;
57
122
  const userArgs = (uid != null && gid != null) ? ['--user', `${uid}:${gid}`] : [];
58
123
 
59
- // --entrypoint crosstalkd overrides the image's default ENTRYPOINT
60
- // (which is the dispatcher entrypoint.sh requiring TRANSPORT_GIT_URL).
61
- // For init we want to call crosstalkd directly with no env requirements.
62
- const args = [
124
+ const dockerArgs = [
63
125
  'run', '--rm',
64
- '-v', `${target}:/init-target`,
126
+ '-v', `${paths.transportDir}:/init-target`,
65
127
  ...userArgs,
66
128
  '--entrypoint', 'crosstalkd',
67
- DEFAULT_IMAGE,
129
+ image,
68
130
  'init', '/init-target',
69
131
  ];
70
132
 
71
- const r = spawnSync('docker', args, { stdio: 'inherit' });
72
- if (r.status !== 0) {
73
- process.stderr.write(`crosstalk init: docker run exited ${r.status}\n`);
74
- process.stderr.write(` command was: docker ${args.join(' ')}\n`);
75
- return r.status ?? 1;
133
+ const initRun = spawnSync('docker', dockerArgs, { stdio: 'inherit' });
134
+ if (initRun.status !== 0) {
135
+ process.stderr.write(`crosstalk init: docker run exited ${initRun.status}\n`);
136
+ process.stderr.write(` command was: docker ${dockerArgs.join(' ')}\n`);
137
+ return initRun.status ?? 1;
76
138
  }
77
139
 
78
- // Auto git init + initial commit so the transport is ready for
79
- // `crosstalk up` immediately. Engine expects a git repo (cursor +
80
- // commit operations on every tick). Doing this on the host so the
81
- // commit author uses the operator's local git config — not the
82
- // container's default.
83
- //
84
- // Best-effort: if any git step fails, print a warning but don't fail
85
- // the init (operator can complete the steps manually).
140
+ // Auto git init + initial commit. Doing it on the host so the commit
141
+ // author uses the operator's local git config not the container's.
86
142
  const gitSteps = [
87
143
  ['init', '--quiet', '--initial-branch=main'],
88
144
  ['add', '-A'],
89
145
  ['commit', '--quiet', '-m', 'initial transport'],
90
146
  ];
91
147
  for (const args of gitSteps) {
92
- const gr = spawnSync('git', args, { cwd: target, stdio: 'pipe' });
148
+ const gr = spawnSync('git', args, { cwd: paths.transportDir, stdio: 'pipe' });
93
149
  if (gr.status !== 0) {
94
150
  const stderr = gr.stderr?.toString().trim() ?? '';
95
151
  process.stderr.write(
96
- `crosstalk init: \`git ${args.join(' ')}\` failed (transport scaffolded but not committed)\n`,
152
+ `crosstalk init: 'git ${args.join(' ')}' failed ${stderr}\n` +
153
+ ` Storage was scaffolded but not committed. Finish manually at:\n` +
154
+ ` ${paths.transportDir}\n`,
155
+ );
156
+ // Not fatal — storage exists and engine will tolerate uncommitted state
157
+ break;
158
+ }
159
+ }
160
+
161
+ // Set up the upstream if requested. Failures here aren't fatal —
162
+ // operator can retry with `cd <transport-dir> && git remote add`.
163
+ if (remote) {
164
+ const rr = spawnSync('git', ['remote', 'add', 'origin', remote], {
165
+ cwd: paths.transportDir,
166
+ stdio: 'pipe',
167
+ });
168
+ if (rr.status !== 0) {
169
+ process.stderr.write(
170
+ `crosstalk init: 'git remote add origin ${remote}' failed — ${rr.stderr?.toString().trim()}\n`,
97
171
  );
98
- if (stderr) process.stderr.write(` ${stderr.split('\n').join('\n ')}\n`);
99
- process.stderr.write(` Finish manually: cd ${target} && git init && git add -A && git commit -m "initial transport"\n`);
100
- return 0; // not fatal — scaffold succeeded
101
172
  }
102
173
  }
103
- process.stdout.write(`\nGit initialized; transport is ready for \`crosstalk up\`.\n`);
174
+
175
+ // Allocate an API port and persist it for the resolver.
176
+ let port;
177
+ try {
178
+ port = pickFreePort(name);
179
+ writeFileSync(paths.portFile, `${port}\n`);
180
+ } catch (err) {
181
+ process.stderr.write(`crosstalk init: port allocation failed — ${err.message}\n`);
182
+ return 1;
183
+ }
184
+
185
+ process.stdout.write(
186
+ `\nTransport '${name}' initialized:\n` +
187
+ ` storage: ${paths.storageRoot}\n` +
188
+ ` transport: ${paths.transportDir}\n` +
189
+ ` image: ${image}\n` +
190
+ ` api port: ${port}\n` +
191
+ (remote ? ` remote: ${remote}\n` : '') +
192
+ `\nNext: crosstalk up${name === DEFAULT_CONTAINER_NAME ? '' : ` --containername ${name}`}\n`,
193
+ );
104
194
  return 0;
105
195
  }
package/commands/logs.js CHANGED
@@ -1,14 +1,14 @@
1
- // crosstalk logs — tail the engine container's logs.
1
+ // crosstalk logs — tail a transport's engine logs.
2
2
 
3
3
  import { existsSync } from 'fs';
4
4
  import { spawnSync } from 'child_process';
5
- import { requireTransportRoot, composeFile } from '../lib/transport.js';
6
5
  import { has } from '../lib/argv.js';
6
+ import { requireInitialized } from '../lib/resolve.js';
7
7
 
8
8
  function usage(exit = 0) {
9
9
  const w = exit === 0 ? process.stdout : process.stderr;
10
10
  w.write(
11
- `Usage: crosstalk logs [-f|--follow] [-n <lines>]
11
+ `Usage: crosstalk logs [--containername <name>] [-f|--follow] [-n <lines>]
12
12
 
13
13
  Streams the engine container's stdout/stderr. -f to follow (Ctrl-C to stop).
14
14
  `,
@@ -19,21 +19,19 @@ Streams the engine container's stdout/stderr. -f to follow (Ctrl-C to stop).
19
19
  export async function run(argv) {
20
20
  if (has(argv, '--help') || has(argv, '-h')) usage(0);
21
21
 
22
- const root = requireTransportRoot();
23
- const composeYml = composeFile(root);
24
- if (!existsSync(composeYml)) {
25
- process.stderr.write(`crosstalk logs: no docker-compose.yml — run 'crosstalk up' first.\n`);
22
+ const { name, paths } = requireInitialized(argv);
23
+ if (!existsSync(paths.composeFile)) {
24
+ process.stderr.write(`crosstalk logs: '${name}' has no compose file — run 'crosstalk up' first.\n`);
26
25
  return 1;
27
26
  }
28
- const args = ['compose', '-f', composeYml, 'logs'];
27
+ const args = ['compose', '-f', paths.composeFile, 'logs'];
29
28
  if (has(argv, '-f') || has(argv, '--follow')) args.push('--follow');
30
- // Pass through tail count if provided.
31
29
  for (let i = 0; i < argv.length; i++) {
32
30
  if (argv[i] === '-n' || argv[i] === '--tail') {
33
31
  args.push('--tail', argv[i + 1] ?? '100');
34
32
  i++;
35
33
  }
36
34
  }
37
- const r = spawnSync('docker', args, { cwd: root, stdio: 'inherit' });
35
+ const r = spawnSync('docker', args, { stdio: 'inherit' });
38
36
  return r.status ?? 1;
39
37
  }
package/commands/pull.js CHANGED
@@ -1,23 +1,21 @@
1
- // crosstalk pull — refresh the engine image.
1
+ // crosstalk pull — refresh the engine image for a transport.
2
2
 
3
3
  import { existsSync } from 'fs';
4
4
  import { spawnSync } from 'child_process';
5
- import { requireTransportRoot, composeFile } from '../lib/transport.js';
6
5
  import { has } from '../lib/argv.js';
6
+ import { requireInitialized } from '../lib/resolve.js';
7
7
 
8
8
  export async function run(argv) {
9
9
  if (has(argv, '--help') || has(argv, '-h')) {
10
- process.stdout.write('Usage: crosstalk pull\n');
10
+ process.stdout.write('Usage: crosstalk pull [--containername <name>]\n');
11
11
  return 0;
12
12
  }
13
- const root = requireTransportRoot();
14
- const composeYml = composeFile(root);
15
- if (!existsSync(composeYml)) {
16
- process.stderr.write(`crosstalk pull: no docker-compose.yml — run 'crosstalk up' first.\n`);
13
+ const { name, paths } = requireInitialized(argv);
14
+ if (!existsSync(paths.composeFile)) {
15
+ process.stderr.write(`crosstalk pull: '${name}' has no compose file — run 'crosstalk up' first.\n`);
17
16
  return 1;
18
17
  }
19
- const r = spawnSync('docker', ['compose', '-f', composeYml, 'pull'], {
20
- cwd: root,
18
+ const r = spawnSync('docker', ['compose', '-f', paths.composeFile, 'pull'], {
21
19
  stdio: 'inherit',
22
20
  });
23
21
  return r.status ?? 1;
@@ -3,19 +3,20 @@
3
3
  // Exit 0 if all targets have REPLIED or FAILED, exit 2 while any PENDING
4
4
  // — agents can poll cheaply in a loop. Mirrors crosstalkd replies.
5
5
 
6
- import { api } from '../lib/api-client.js';
6
+ import { apiFor } from '../lib/api-client.js';
7
7
  import { reportAndExit } from '../lib/errors.js';
8
8
  import { positionals, has } from '../lib/argv.js';
9
9
 
10
10
  function usage(exit = 0) {
11
11
  const w = exit === 0 ? process.stdout : process.stderr;
12
- w.write('Usage: crosstalk replies <relPath> [<relPath>...]\n');
12
+ w.write('Usage: crosstalk replies [--containername <name>] <relPath> [<relPath>...]\n');
13
13
  process.exit(exit);
14
14
  }
15
15
 
16
16
  export async function run(argv) {
17
17
  if (has(argv, '--help') || has(argv, '-h')) usage(0);
18
- const targets = positionals(argv, []);
18
+ const api = apiFor(argv);
19
+ const targets = positionals(argv, ['--containername', '-c']);
19
20
  if (targets.length === 0) usage(1);
20
21
 
21
22
  let resp;
@@ -1,23 +1,21 @@
1
- // crosstalk restart — restart the engine container.
1
+ // crosstalk restart — restart a transport's engine container.
2
2
 
3
3
  import { existsSync } from 'fs';
4
4
  import { spawnSync } from 'child_process';
5
- import { requireTransportRoot, composeFile } from '../lib/transport.js';
6
5
  import { has } from '../lib/argv.js';
6
+ import { requireInitialized } from '../lib/resolve.js';
7
7
 
8
8
  export async function run(argv) {
9
9
  if (has(argv, '--help') || has(argv, '-h')) {
10
- process.stdout.write('Usage: crosstalk restart\n');
10
+ process.stdout.write('Usage: crosstalk restart [--containername <name>]\n');
11
11
  return 0;
12
12
  }
13
- const root = requireTransportRoot();
14
- const composeYml = composeFile(root);
15
- if (!existsSync(composeYml)) {
16
- process.stderr.write(`crosstalk restart: no docker-compose.yml — run 'crosstalk up' first.\n`);
13
+ const { name, paths } = requireInitialized(argv);
14
+ if (!existsSync(paths.composeFile)) {
15
+ process.stderr.write(`crosstalk restart: '${name}' has no compose file — run 'crosstalk up' first.\n`);
17
16
  return 1;
18
17
  }
19
- const r = spawnSync('docker', ['compose', '-f', composeYml, 'restart'], {
20
- cwd: root,
18
+ const r = spawnSync('docker', ['compose', '-f', paths.composeFile, 'restart'], {
21
19
  stdio: 'inherit',
22
20
  });
23
21
  return r.status ?? 1;