@cordfuse/crosstalk 7.0.0-alpha.8 → 7.0.0-beta.1

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.
Files changed (72) hide show
  1. package/GUIDE-CLI.md +322 -0
  2. package/GUIDE-PROMPTS.md +118 -0
  3. package/LICENSE +21 -0
  4. package/README.md +402 -61
  5. package/bin/crosstalk.js +88 -54
  6. package/commands/agent.js +69 -0
  7. package/commands/auth.js +273 -0
  8. package/commands/channel.js +54 -44
  9. package/commands/chat.js +107 -71
  10. package/commands/daemon.js +120 -0
  11. package/commands/logs.js +108 -19
  12. package/commands/message.js +125 -0
  13. package/commands/server.js +153 -0
  14. package/commands/settings.js +49 -0
  15. package/commands/status.js +37 -13
  16. package/commands/token.js +136 -0
  17. package/commands/transport.js +270 -0
  18. package/commands/version.js +3 -3
  19. package/commands/workflow.js +234 -0
  20. package/deploy/crosstalk@.service +62 -0
  21. package/deploy/install.sh +82 -0
  22. package/lib/api-client.js +77 -22
  23. package/lib/credentials.js +207 -0
  24. package/lib/nativeServer.js +173 -0
  25. package/lib/resolve.js +101 -34
  26. package/package.json +27 -4
  27. package/src/activation.ts +104 -0
  28. package/src/api.ts +1716 -0
  29. package/src/auth/enforce.ts +68 -0
  30. package/src/auth/handlers.ts +266 -0
  31. package/src/auth/middleware.ts +132 -0
  32. package/src/auth/setup.ts +263 -0
  33. package/src/auth/tokens.ts +285 -0
  34. package/src/auth/users.ts +267 -0
  35. package/src/dispatch.ts +492 -0
  36. package/src/dispatchers.ts +91 -0
  37. package/src/filenames.ts +28 -0
  38. package/src/frontmatter.ts +26 -0
  39. package/src/init.ts +116 -0
  40. package/src/invoke.ts +201 -0
  41. package/src/log-buffer.ts +67 -0
  42. package/src/models.ts +283 -0
  43. package/src/resolve.ts +100 -0
  44. package/src/state.ts +190 -0
  45. package/src/stop.ts +37 -0
  46. package/src/transport.ts +243 -0
  47. package/src/web/auth-pages.ts +160 -0
  48. package/src/web/channels.ts +395 -0
  49. package/src/web/chat-page.ts +636 -0
  50. package/src/web/chat-pty.ts +254 -0
  51. package/src/web/dashboard.ts +129 -0
  52. package/src/web/layout.ts +237 -0
  53. package/src/web/stubs.ts +510 -0
  54. package/src/web/workflows.ts +490 -0
  55. package/src/workflow.ts +470 -0
  56. package/template/CLAUDE.md +10 -0
  57. package/template/CROSSTALK-VERSION +1 -0
  58. package/template/CROSSTALK.md +262 -0
  59. package/template/PROTOCOL.md +70 -0
  60. package/template/README.md +64 -0
  61. package/template/auth/.gitkeep +0 -0
  62. package/template/auth/README.md +224 -0
  63. package/template/data/crosstalk.yaml +196 -0
  64. package/template/gitignore +4 -0
  65. package/commands/down.js +0 -40
  66. package/commands/init.js +0 -243
  67. package/commands/pull.js +0 -22
  68. package/commands/replies.js +0 -40
  69. package/commands/restart.js +0 -29
  70. package/commands/rm.js +0 -109
  71. package/commands/run.js +0 -122
  72. package/commands/up.js +0 -135
@@ -0,0 +1,196 @@
1
+ # Crosstalk transport config — the operator-edited file.
2
+ #
3
+ # Schema:
4
+ #
5
+ # providers:
6
+ # <provider-name>:
7
+ # env_file: <relpath> # optional — dotenv file under transport
8
+ # env: # optional — inline ${VAR} interpolation
9
+ # <KEY>: ${HOST_VAR}
10
+ # models:
11
+ # <model-name>: <cli command>
12
+ #
13
+ # Models are addressed in `<provider>/<model>` form everywhere (--to flag,
14
+ # message `to:` field, workflow plan targets). A bare `<model>` is also
15
+ # accepted when exactly one claimed model has that name; ambiguous bare
16
+ # names fail with a pick-list.
17
+ #
18
+ # Dispatchers (machines) read this file, take the first token of each
19
+ # model's command, and check PATH. Only models whose CLI is installed
20
+ # locally are claimed by that dispatcher.
21
+ #
22
+ # COMMAND CONTRACT — what `<cli command>` means
23
+ # ---------------------------------------------
24
+ # The command is a whitespace-tokenized argv list, NOT a shell pipeline.
25
+ #
26
+ # - First token = the binary the dispatcher checks PATH for.
27
+ # - Remaining tokens = static argv passed to that binary.
28
+ # - The runtime APPENDS the message body as the FINAL positional arg
29
+ # (stdin fallback when the prompt exceeds 64 KB).
30
+ # - Quoting (`'…'`, `"…"`), command substitution (`$(…)`), pipes (`|`),
31
+ # and redirection (`>`, `<`) are NOT interpreted — they pass through
32
+ # as literal argv characters. `bash -c 'echo X'` will NOT work as
33
+ # written; the quotes become part of argv.
34
+ #
35
+ # If you need shell features (pipelines, substitution, env expansion in
36
+ # the command itself), write a wrapper script that reads the prompt
37
+ # from $1, drop it inside the container, then reference it:
38
+ #
39
+ # crosstalk chat --shell
40
+ # cat > /crosstalk-root/.local/bin/myagent-wrapper <<'WRAP'
41
+ # #!/usr/bin/env bash
42
+ # /path/to/real-agent --some-flag "$1"
43
+ # WRAP
44
+ # chmod +x /crosstalk-root/.local/bin/myagent-wrapper
45
+ # exit
46
+ # crosstalk restart
47
+ #
48
+ # Then in this file:
49
+ # providers:
50
+ # custom:
51
+ # models:
52
+ # myagent: myagent-wrapper
53
+ #
54
+ # Why `/crosstalk-root/.local/bin/`: it's already on $PATH and lives
55
+ # under the persistent /crosstalk-root volume, so the wrapper survives
56
+ # `crosstalk restart`. /usr/local/bin/ does NOT work — it's in the
57
+ # container's writable layer and gets wiped on every restart.
58
+ # `crosstalk restart` is required after dropping the wrapper so the
59
+ # dispatcher rescans PATH and claims the new model.
60
+ #
61
+ # Absolute paths also work (alpha.12+): an operator can place the
62
+ # wrapper anywhere under /crosstalk-root and reference it as
63
+ # myagent: /crosstalk-root/wrappers/myagent-wrapper
64
+ # The dispatcher resolves absolute first tokens directly (skipping
65
+ # the PATH walk).
66
+ #
67
+ # Skip-bad-provider (alpha.11+) / skip-bad-model (alpha.12+): typo'd
68
+ # providers and unreachable model binaries are both logged to stderr
69
+ # and skipped — the rest of the registry loads. Check `crosstalk logs`
70
+ # (or `docker logs <container>`) to see which providers / models were
71
+ # rejected and why.
72
+ #
73
+ # Auth: two layouts, pick whichever fits — they coexist per provider.
74
+ #
75
+ # env_file: <relpath>
76
+ # Points at a dotenv file under the transport. Engine reads it at
77
+ # agent spawn and merges KEY=VALUE pairs into the subprocess env.
78
+ # Operator-edited file holds the raw secrets.
79
+ #
80
+ # env: { KEY: ${HOST_VAR}, ... }
81
+ # Inline map. Values MUST be `${HOST_VAR}` references — raw secrets
82
+ # are refused so the yaml stays committable. Engine resolves each
83
+ # ${HOST_VAR} from process.env at spawn time. Missing host vars are
84
+ # dropped silently (the agent CLI surfaces its own auth error). Use
85
+ # this when you want the provider→credential mapping visible in one
86
+ # file.
87
+ #
88
+ # Precedence at spawn (last-wins): process.env → env_file → env →
89
+ # dispatchEnv. So `env` overrides `env_file` per-key if both set, and
90
+ # dispatch metadata wins over everything.
91
+ #
92
+ # Tip: agent CLIs accept aliases (sonnet, opus, haiku, etc.) that auto-
93
+ # track the latest model version. Prefer aliases over pinned full names
94
+ # unless you need a specific version.
95
+
96
+ providers:
97
+
98
+ # ── Anthropic (Claude) ─────────────────────────────────────────────
99
+ # Headless auth env (pick one): CLAUDE_CODE_OAUTH_TOKEN (subscription
100
+ # token from `claude setup-token`), or ANTHROPIC_API_KEY (Console key).
101
+ # OAuth token takes precedence when both are set.
102
+ #
103
+ # anthropic-personal:
104
+ # env_file: auth/anthropic-personal.env
105
+ # models:
106
+ # sonnet: claude --print --dangerously-skip-permissions --model sonnet
107
+ # haiku: claude --print --dangerously-skip-permissions --model haiku
108
+ # opus: claude --print --dangerously-skip-permissions --model opus
109
+
110
+ # ── Google (Gemini CLI) ────────────────────────────────────────────
111
+ # Headless auth env: GEMINI_API_KEY (from aistudio.google.com/apikey).
112
+ # Vertex AI / GCA are alternatives — see the gemini-cli docs.
113
+ #
114
+ # google-personal:
115
+ # env_file: auth/google-personal.env
116
+ # models:
117
+ # gemini-pro: gemini --skip-trust --yolo -p --model gemini-1.5-pro
118
+ # gemini-flash: gemini --skip-trust --yolo -p --model gemini-1.5-flash
119
+
120
+ # ── Inline `env:` block alternative (alpha.10) ─────────────────────
121
+ # Same per-provider scoping as `env_file:`, but the provider→credential
122
+ # mapping is visible in this one file. Values are `${VAR}` references
123
+ # only — raw secrets are rejected at parse time. Resolve from the host
124
+ # environment (export the var before `crosstalk up`, or set it in your
125
+ # shell rc / a sourced .env). Useful when running multiple Gemini
126
+ # accounts so it's obvious which key feeds which provider.
127
+ #
128
+ # google-work:
129
+ # env:
130
+ # GEMINI_API_KEY: ${GOOGLE_WORK_KEY}
131
+ # models:
132
+ # gemini-work: gemini --skip-trust --yolo -p
133
+
134
+ # ── Google (Antigravity CLI — agy) ─────────────────────────────────
135
+ # Reads GEMINI_API_KEY too. Can share its env_file with the google-*
136
+ # provider above, or live under a separate provider if you want
137
+ # different keys for agy vs gemini-cli.
138
+ #
139
+ # google-agy:
140
+ # env_file: auth/google-personal.env
141
+ # models:
142
+ # agy: agy --print
143
+
144
+ # ── OpenAI (Codex CLI) ─────────────────────────────────────────────
145
+ # Codex auths via its own credential store at ~/.codex/auth.json, NOT
146
+ # via env var. Run once via `crosstalk chat --shell`:
147
+ # echo "$OPENAI_API_KEY" | codex login --with-api-key
148
+ # The store persists in /crosstalk-root across container restarts.
149
+ # No env_file is needed (declare one if you want isolated codex
150
+ # accounts via HOME swap — express in the cli string itself).
151
+ #
152
+ # openai-codex:
153
+ # models:
154
+ # codex: codex --skip-git-repo-check
155
+
156
+ # ── OpenRouter (gateway → many models) ─────────────────────────────
157
+ # OpenAI-compatible gateway. Use with codex, qwen, opencode (native),
158
+ # or any other OpenAI-SDK-shaped client.
159
+ # OPENAI_API_KEY=sk-or-v1-...
160
+ # OPENAI_BASE_URL=https://openrouter.ai/api/v1
161
+ #
162
+ # openrouter:
163
+ # env_file: auth/openrouter.env
164
+ # models:
165
+ # qwen3-coder-or: qwen --auth-type openai --yolo --model qwen/qwen3-coder
166
+ # opencode-or: opencode --model qwen/qwen3-coder
167
+
168
+ # ── opencode (multi-provider via own config) ───────────────────────
169
+ # opencode reads native OPENROUTER_API_KEY, or per-provider env
170
+ # (ANTHROPIC_API_KEY, OPENAI_API_KEY, ...), or its own config at
171
+ # ~/.config/opencode/. The provider you put it under here decides
172
+ # which env file it sees at spawn.
173
+ #
174
+ # opencode-native:
175
+ # env_file: auth/openrouter.env
176
+ # models:
177
+ # opencode: opencode -p
178
+
179
+ # ── Local Ollama (no auth) ─────────────────────────────────────────
180
+ # OpenAI-compatible endpoint for whatever models are pulled on the
181
+ # ollama host. Auth is `OPENAI_API_KEY=ollama` (any non-empty string)
182
+ # + OPENAI_BASE_URL pointing at the ollama instance.
183
+ #
184
+ # ollama-local:
185
+ # env_file: auth/ollama-local.env
186
+ # models:
187
+ # qwen-coder-local: qwen --auth-type openai --yolo --model qwen2.5-coder:14b
188
+
189
+ # ── Local Ollama via Tailscale (one GPU box serves the swarm) ──────
190
+ # Same as ollama-local but pointed at a remote ollama via Tailscale.
191
+ # Operators on multiple machines all share one GPU host.
192
+ #
193
+ # ollama-tailnet:
194
+ # env_file: auth/ollama-tailnet.env
195
+ # models:
196
+ # qwen-coder-tailnet: qwen --auth-type openai --yolo --model qwen2.5-coder:14b
@@ -0,0 +1,4 @@
1
+ .DS_Store
2
+ Thumbs.db
3
+ node_modules/
4
+ .turnq/
package/commands/down.js DELETED
@@ -1,40 +0,0 @@
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.
6
-
7
- import { existsSync } from 'fs';
8
- import { spawnSync } from 'child_process';
9
- import { has } from '../lib/argv.js';
10
- import { requireInitialized, DEFAULT_CONTAINER_NAME } from '../lib/resolve.js';
11
-
12
- function usage(exit = 0) {
13
- const w = exit === 0 ? process.stdout : process.stderr;
14
- w.write(
15
- `Usage: crosstalk down [--containername <name>]
16
-
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.
20
- `,
21
- );
22
- process.exit(exit);
23
- }
24
-
25
- export async function run(argv) {
26
- if (has(argv, '--help') || has(argv, '-h')) usage(0);
27
-
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
- );
33
- return 0;
34
- }
35
-
36
- const r = spawnSync('docker', ['compose', '-f', paths.composeFile, 'down'], {
37
- stdio: 'inherit',
38
- });
39
- return r.status ?? 1;
40
- }
package/commands/init.js DELETED
@@ -1,243 +0,0 @@
1
- // crosstalk init — scaffold a transport into <base>/<name>/.
2
- //
3
- // alpha.7 rewrite: phased + idempotent. Each phase is independent and
4
- // safe to re-run; re-init detects missing markers and repairs them.
5
- // Mac caught bug A in alpha.6 where the api-port file was written at
6
- // the end of the pipeline, so any disruption (image pull, git failure)
7
- // left storage existing but unstartable — and re-init silently
8
- // no-op-ed instead of fixing it.
9
- //
10
- // Phases:
11
- // 1. Storage layout — mkdir subdirs + write api-port. Pure FS, fast,
12
- // runs first so the resolver-visible marker exists before anything
13
- // else can go wrong.
14
- // 2. Transport scaffold — `crosstalkd init` in a one-shot container.
15
- // Skipped if transport/.git already exists. stdout/stderr captured
16
- // (mac's bug B — in-container scaffold output was leaking).
17
- // 3. Git init + initial commit. Skipped if .git exists.
18
- // 4. Remote setup if --remote given (errors if a different origin
19
- // is already set; idempotent on same-value).
20
- // 5. Summary.
21
- //
22
- // Each phase reports created/skipped/repaired so operators can tell
23
- // what just happened.
24
-
25
- import { mkdirSync, existsSync, writeFileSync, readFileSync } from 'fs';
26
- import { spawnSync } from 'child_process';
27
- import { join } from 'path';
28
- import { has, flag } from '../lib/argv.js';
29
- import {
30
- parseContainerName,
31
- storagePaths,
32
- pickFreePort,
33
- DEFAULT_CONTAINER_NAME,
34
- } from '../lib/resolve.js';
35
-
36
- const DEFAULT_IMAGE = process.env.CROSSTALK_IMAGE
37
- ?? 'ghcr.io/cordfuse/crosstalk-server:7.0.0-alpha.8';
38
-
39
- function usage(exit = 0) {
40
- const w = exit === 0 ? process.stdout : process.stderr;
41
- w.write(
42
- `Usage:
43
- crosstalk init [--containername <name>] [--remote <url>]
44
-
45
- Creates a new transport on this machine, or repairs an existing
46
- incomplete one. Idempotent — re-running fills in any missing markers
47
- without disturbing existing state.
48
-
49
- The transport git repo lives at <base>/<name>/transport/ where <base> is:
50
- Linux: $XDG_DATA_HOME/crosstalk/ (or ~/.local/share/crosstalk/)
51
- macOS: ~/Library/Application Support/crosstalk/
52
- Windows: %LOCALAPPDATA%\\crosstalk\\
53
-
54
- Set CROSSTALK_STORAGE_MODE=system to use the system-wide path (sudo/UAC
55
- required; intended for headless multi-operator hosts).
56
-
57
- Options:
58
- --containername <name> Container name (default: 'crosstalk'). Must be
59
- lowercase alphanumeric + ._- (no leading . or -).
60
- One default + N named containers per machine.
61
- --remote <url> Set git upstream. If a different remote is
62
- already set, errors instead of overwriting.
63
- --image <tag> Override the engine image (default: bundled
64
- GHCR tag; or CROSSTALK_IMAGE env).
65
- -c <name> Shorthand for --containername.
66
-
67
- Examples:
68
- crosstalk init # default container
69
- crosstalk init --containername uat # second container
70
- crosstalk init --remote git\\@github.com:me/t.git # with upstream
71
- `,
72
- );
73
- process.exit(exit);
74
- }
75
-
76
- export async function run(argv) {
77
- if (has(argv, '--help') || has(argv, '-h')) usage(0);
78
-
79
- let name;
80
- try {
81
- name = parseContainerName(argv);
82
- } catch (err) {
83
- process.stderr.write(`crosstalk init: ${err.message}\n`);
84
- return 1;
85
- }
86
-
87
- const remote = flag(argv, '--remote');
88
- const image = flag(argv, '--image') ?? DEFAULT_IMAGE;
89
- const paths = storagePaths(name);
90
- const report = [];
91
-
92
- // ── Phase 1: Storage layout ────────────────────────────────────────
93
- // mkdir is idempotent (recursive: true). Write api-port if absent.
94
- // This is the load-bearing fix for bug A — port file lands FIRST,
95
- // before anything else can go wrong (image pull, git init, etc.).
96
- try {
97
- mkdirSync(paths.transportDir, { recursive: true });
98
- mkdirSync(paths.crosstalkRoot, { recursive: true });
99
- mkdirSync(paths.crosstalkState, { recursive: true });
100
- } catch (err) {
101
- process.stderr.write(
102
- `crosstalk init: failed to create storage at ${paths.storageRoot} — ${err.message}\n`,
103
- );
104
- if (paths.mode === 'system') {
105
- process.stderr.write(
106
- ` System-mode storage requires write access to ${paths.base}.\n` +
107
- ` Try: sudo mkdir -p ${paths.storageRoot} && sudo chown -R $USER ${paths.storageRoot}\n`,
108
- );
109
- }
110
- return 1;
111
- }
112
-
113
- let port;
114
- if (existsSync(paths.portFile)) {
115
- const v = Number(readFileSync(paths.portFile, 'utf-8').trim());
116
- if (Number.isInteger(v) && v > 0 && v < 65536) {
117
- port = v;
118
- report.push(` api port: ${port} (existing)`);
119
- }
120
- }
121
- if (!port) {
122
- try {
123
- port = pickFreePort(name);
124
- writeFileSync(paths.portFile, `${port}\n`);
125
- report.push(` api port: ${port} (allocated)`);
126
- } catch (err) {
127
- process.stderr.write(`crosstalk init: port allocation failed — ${err.message}\n`);
128
- return 1;
129
- }
130
- }
131
-
132
- // ── Phase 2: Transport scaffold ────────────────────────────────────
133
- // The template-scaffold check is "does CROSSTALK-VERSION exist?" NOT
134
- // ".git exists" — git init is a separate phase, and the engine refuses
135
- // to re-scaffold over an existing CROSSTALK-VERSION (without --force).
136
- // Decoupling means a disrupted init (template written, git init never
137
- // ran) re-enters Phase 3 cleanly on re-run.
138
- const transportGitDir = join(paths.transportDir, '.git');
139
- const crosstalkVersionFile = join(paths.transportDir, 'CROSSTALK-VERSION');
140
- if (!existsSync(crosstalkVersionFile)) {
141
- const uid = typeof process.getuid === 'function' ? process.getuid() : null;
142
- const gid = typeof process.getgid === 'function' ? process.getgid() : null;
143
- const userArgs = (uid != null && gid != null) ? ['--user', `${uid}:${gid}`] : [];
144
-
145
- const dockerArgs = [
146
- 'run', '--rm',
147
- '-v', `${paths.transportDir}:/init-target`,
148
- ...userArgs,
149
- '--entrypoint', 'crosstalkd',
150
- image,
151
- 'init', '/init-target',
152
- ];
153
-
154
- // Bug B fix: don't inherit stdio — the engine prints next-steps that
155
- // confuse operators ("Transport initialized at /init-target" with
156
- // container-internal paths). Capture, show only on failure.
157
- const initRun = spawnSync('docker', dockerArgs, { stdio: 'pipe' });
158
- if (initRun.status !== 0) {
159
- const stderr = initRun.stderr?.toString() ?? '';
160
- const stdout = initRun.stdout?.toString() ?? '';
161
- process.stderr.write(`crosstalk init: docker run exited ${initRun.status}\n`);
162
- if (stderr.trim()) process.stderr.write(` stderr: ${stderr.trim()}\n`);
163
- if (stdout.trim()) process.stderr.write(` stdout: ${stdout.trim()}\n`);
164
- process.stderr.write(` command was: docker ${dockerArgs.join(' ')}\n`);
165
- return initRun.status ?? 1;
166
- }
167
- report.push(` template: scaffolded via ${image}`);
168
- } else {
169
- report.push(` template: present (skipped scaffold)`);
170
- }
171
-
172
- // ── Phase 3: Git init + initial commit ─────────────────────────────
173
- if (!existsSync(crosstalkVersionFile)) {
174
- // Engine init succeeded but didn't leave files. Bail clearly — we
175
- // won't proceed to git init on an empty dir.
176
- process.stderr.write(
177
- `crosstalk init: transport template missing at ${paths.transportDir}\n` +
178
- ` Engine init didn't write template files. Try 'crosstalk rm' then re-init.\n`,
179
- );
180
- return 1;
181
- }
182
-
183
- const gitSteps = [
184
- ['init', '--quiet', '--initial-branch=main'],
185
- ['add', '-A'],
186
- ['commit', '--quiet', '-m', 'initial transport'],
187
- ];
188
- let gitOk = true;
189
- for (const args of gitSteps) {
190
- const gr = spawnSync('git', args, { cwd: paths.transportDir, stdio: 'pipe' });
191
- if (gr.status !== 0) {
192
- // 'git init' on existing repo is fine (it re-runs idempotently);
193
- // 'add -A' is also idempotent. 'commit' fails when there's
194
- // nothing to commit — that means a previous run already committed.
195
- // Only the first call (git init) is load-bearing; the rest are
196
- // best-effort to populate the initial state.
197
- gitOk = false;
198
- break;
199
- }
200
- }
201
- report.push(gitOk ? ` git: initialized + committed` : ` git: already present (skipped)`);
202
-
203
- // ── Phase 4: Remote setup ──────────────────────────────────────────
204
- if (remote) {
205
- const cur = spawnSync('git', ['remote', 'get-url', 'origin'], {
206
- cwd: paths.transportDir,
207
- stdio: 'pipe',
208
- });
209
- const currentUrl = cur.status === 0 ? cur.stdout.toString().trim() : null;
210
- if (!currentUrl) {
211
- const rr = spawnSync('git', ['remote', 'add', 'origin', remote], {
212
- cwd: paths.transportDir,
213
- stdio: 'pipe',
214
- });
215
- if (rr.status !== 0) {
216
- process.stderr.write(
217
- `crosstalk init: 'git remote add origin ${remote}' failed — ${rr.stderr?.toString().trim()}\n`,
218
- );
219
- } else {
220
- report.push(` remote: ${remote} (added)`);
221
- }
222
- } else if (currentUrl === remote) {
223
- report.push(` remote: ${remote} (already set)`);
224
- } else {
225
- process.stderr.write(
226
- `crosstalk init: remote already set to '${currentUrl}'; refusing to overwrite with '${remote}'.\n` +
227
- ` Use 'crosstalk status' to see the transport path; cd there and adjust git config manually if intended.\n`,
228
- );
229
- return 1;
230
- }
231
- }
232
-
233
- // ── Phase 5: Summary ───────────────────────────────────────────────
234
- process.stdout.write(
235
- `\nTransport '${name}' ready:\n` +
236
- ` storage: ${paths.storageRoot}\n` +
237
- ` transport: ${paths.transportDir}\n` +
238
- ` image: ${image}\n` +
239
- report.join('\n') + '\n' +
240
- `\nNext: crosstalk up${name === DEFAULT_CONTAINER_NAME ? '' : ` --containername ${name}`}\n`,
241
- );
242
- return 0;
243
- }
package/commands/pull.js DELETED
@@ -1,22 +0,0 @@
1
- // crosstalk pull — refresh the engine image for a transport.
2
-
3
- import { existsSync } from 'fs';
4
- import { spawnSync } from 'child_process';
5
- import { has } from '../lib/argv.js';
6
- import { requireInitialized } from '../lib/resolve.js';
7
-
8
- export async function run(argv) {
9
- if (has(argv, '--help') || has(argv, '-h')) {
10
- process.stdout.write('Usage: crosstalk pull [--containername <name>]\n');
11
- return 0;
12
- }
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`);
16
- return 1;
17
- }
18
- const r = spawnSync('docker', ['compose', '-f', paths.composeFile, 'pull'], {
19
- stdio: 'inherit',
20
- });
21
- return r.status ?? 1;
22
- }
@@ -1,40 +0,0 @@
1
- // crosstalk replies <relPath> [<relPath>...] — poll reply status.
2
- //
3
- // Exit 0 if all targets have REPLIED or FAILED, exit 2 while any PENDING
4
- // — agents can poll cheaply in a loop. Mirrors crosstalkd replies.
5
-
6
- import { apiFor } from '../lib/api-client.js';
7
- import { reportAndExit } from '../lib/errors.js';
8
- import { positionals, has } from '../lib/argv.js';
9
-
10
- function usage(exit = 0) {
11
- const w = exit === 0 ? process.stdout : process.stderr;
12
- w.write('Usage: crosstalk replies [--containername <name>] <relPath> [<relPath>...]\n');
13
- process.exit(exit);
14
- }
15
-
16
- export async function run(argv) {
17
- if (has(argv, '--help') || has(argv, '-h')) usage(0);
18
- const api = apiFor(argv);
19
- const targets = positionals(argv, ['--containername', '-c']);
20
- if (targets.length === 0) usage(1);
21
-
22
- let resp;
23
- try {
24
- resp = await api.get(`/replies?relPaths=${encodeURIComponent(targets.join(','))}`);
25
- } catch (err) {
26
- reportAndExit(err, 'crosstalk replies');
27
- }
28
-
29
- let pending = 0;
30
- for (const r of resp.replies) {
31
- if (r.status === 'PENDING') {
32
- process.stdout.write(`PENDING ${r.target}\n`);
33
- pending++;
34
- } else {
35
- const tag = r.status === 'FAILED' ? 'FAILED ' : 'REPLIED ';
36
- process.stdout.write(`${tag} ${r.target} <- ${r.from} (${r.replyRelPath})\n`);
37
- }
38
- }
39
- return pending > 0 ? 2 : 0;
40
- }
@@ -1,29 +0,0 @@
1
- // crosstalk restart — restart a transport's engine container.
2
- //
3
- // Uses `compose up -d --force-recreate` (not `compose restart`) so
4
- // changes to env_file (e.g. tokens added via `crosstalk auth paste`)
5
- // are picked up. Plain `compose restart` just stops + starts the same
6
- // container without re-reading config.
7
-
8
- import { existsSync } from 'fs';
9
- import { spawnSync } from 'child_process';
10
- import { has } from '../lib/argv.js';
11
- import { requireInitialized } from '../lib/resolve.js';
12
-
13
- export async function run(argv) {
14
- if (has(argv, '--help') || has(argv, '-h')) {
15
- process.stdout.write('Usage: crosstalk restart [--containername <name>]\n');
16
- return 0;
17
- }
18
- const { name, paths } = requireInitialized(argv);
19
- if (!existsSync(paths.composeFile)) {
20
- process.stderr.write(`crosstalk restart: '${name}' has no compose file — run 'crosstalk up' first.\n`);
21
- return 1;
22
- }
23
- const r = spawnSync(
24
- 'docker',
25
- ['compose', '-f', paths.composeFile, 'up', '-d', '--force-recreate'],
26
- { stdio: 'inherit' },
27
- );
28
- return r.status ?? 1;
29
- }