@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 +23 -18
- package/bin/crosstalk.js +1 -0
- package/commands/channel.js +3 -2
- package/commands/chat.js +4 -4
- package/commands/down.js +18 -15
- package/commands/init.js +146 -56
- package/commands/logs.js +8 -10
- package/commands/pull.js +7 -9
- package/commands/replies.js +4 -3
- package/commands/restart.js +7 -9
- package/commands/rm.js +109 -0
- package/commands/run.js +4 -3
- package/commands/status.js +15 -10
- package/commands/up.js +79 -169
- package/commands/version.js +3 -2
- package/lib/api-client.js +36 -17
- package/lib/argv.js +4 -2
- package/lib/resolve.js +181 -0
- package/package.json +1 -1
- package/lib/transport.js +0 -51
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
|
|
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
|
|
25
|
+
# 2. Bring up the engine container.
|
|
27
26
|
crosstalk up
|
|
28
|
-
# Generates docker-compose.yml
|
|
29
|
-
#
|
|
30
|
-
#
|
|
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
|
-
#
|
|
40
|
-
#
|
|
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 <
|
|
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` |
|
|
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
|
|
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
|
|
76
|
-
- **Container** (`
|
|
77
|
-
-
|
|
78
|
-
-
|
|
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
package/commands/channel.js
CHANGED
|
@@ -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 {
|
|
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
|
|
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 {
|
|
27
|
-
import {
|
|
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
|
|
199
|
-
const
|
|
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
|
|
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 [--
|
|
15
|
+
`Usage: crosstalk down [--containername <name>]
|
|
12
16
|
|
|
13
|
-
Stops the engine container for
|
|
14
|
-
|
|
15
|
-
|
|
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
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
33
|
-
|
|
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
|
|
1
|
+
// crosstalk init — scaffold a transport into <base>/<name>/.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
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
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
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
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
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,
|
|
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.
|
|
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 <
|
|
43
|
+
crosstalk init [--containername <name>] [--remote <url>]
|
|
31
44
|
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
51
|
-
|
|
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
|
-
//
|
|
54
|
-
//
|
|
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
|
-
|
|
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', `${
|
|
126
|
+
'-v', `${paths.transportDir}:/init-target`,
|
|
65
127
|
...userArgs,
|
|
66
128
|
'--entrypoint', 'crosstalkd',
|
|
67
|
-
|
|
129
|
+
image,
|
|
68
130
|
'init', '/init-target',
|
|
69
131
|
];
|
|
70
132
|
|
|
71
|
-
const
|
|
72
|
-
if (
|
|
73
|
-
process.stderr.write(`crosstalk init: docker run exited ${
|
|
74
|
-
process.stderr.write(` command was: docker ${
|
|
75
|
-
return
|
|
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
|
|
79
|
-
//
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
|
23
|
-
|
|
24
|
-
|
|
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',
|
|
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, {
|
|
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
|
|
14
|
-
|
|
15
|
-
|
|
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',
|
|
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;
|
package/commands/replies.js
CHANGED
|
@@ -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 {
|
|
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
|
|
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;
|
package/commands/restart.js
CHANGED
|
@@ -1,23 +1,21 @@
|
|
|
1
|
-
// crosstalk restart — restart
|
|
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
|
|
14
|
-
|
|
15
|
-
|
|
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',
|
|
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;
|