@cordfuse/crosstalk 7.0.0-alpha.9 → 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.
- package/GUIDE-CLI.md +322 -0
- package/GUIDE-PROMPTS.md +118 -0
- package/LICENSE +21 -0
- package/README.md +402 -61
- package/bin/crosstalk.js +88 -54
- package/commands/agent.js +69 -0
- package/commands/auth.js +273 -0
- package/commands/channel.js +54 -44
- package/commands/chat.js +107 -71
- package/commands/daemon.js +120 -0
- package/commands/logs.js +108 -19
- package/commands/message.js +125 -0
- package/commands/server.js +153 -0
- package/commands/settings.js +49 -0
- package/commands/status.js +37 -13
- package/commands/token.js +136 -0
- package/commands/transport.js +270 -0
- package/commands/version.js +3 -3
- package/commands/workflow.js +234 -0
- package/deploy/crosstalk@.service +62 -0
- package/deploy/install.sh +82 -0
- package/lib/api-client.js +77 -22
- package/lib/credentials.js +207 -0
- package/lib/nativeServer.js +173 -0
- package/lib/resolve.js +101 -34
- package/package.json +27 -4
- package/src/activation.ts +104 -0
- package/src/api.ts +1716 -0
- package/src/auth/enforce.ts +68 -0
- package/src/auth/handlers.ts +266 -0
- package/src/auth/middleware.ts +132 -0
- package/src/auth/setup.ts +263 -0
- package/src/auth/tokens.ts +285 -0
- package/src/auth/users.ts +267 -0
- package/src/dispatch.ts +492 -0
- package/src/dispatchers.ts +91 -0
- package/src/filenames.ts +28 -0
- package/src/frontmatter.ts +26 -0
- package/src/init.ts +116 -0
- package/src/invoke.ts +201 -0
- package/src/log-buffer.ts +67 -0
- package/src/models.ts +283 -0
- package/src/resolve.ts +100 -0
- package/src/state.ts +190 -0
- package/src/stop.ts +37 -0
- package/src/transport.ts +243 -0
- package/src/web/auth-pages.ts +160 -0
- package/src/web/channels.ts +395 -0
- package/src/web/chat-page.ts +636 -0
- package/src/web/chat-pty.ts +254 -0
- package/src/web/dashboard.ts +129 -0
- package/src/web/layout.ts +237 -0
- package/src/web/stubs.ts +510 -0
- package/src/web/workflows.ts +490 -0
- package/src/workflow.ts +470 -0
- package/template/CLAUDE.md +10 -0
- package/template/CROSSTALK-VERSION +1 -0
- package/template/CROSSTALK.md +262 -0
- package/template/PROTOCOL.md +70 -0
- package/template/README.md +64 -0
- package/template/auth/.gitkeep +0 -0
- package/template/auth/README.md +224 -0
- package/template/data/crosstalk.yaml +196 -0
- package/template/gitignore +4 -0
- package/commands/down.js +0 -40
- package/commands/init.js +0 -243
- package/commands/pull.js +0 -22
- package/commands/replies.js +0 -40
- package/commands/restart.js +0 -29
- package/commands/rm.js +0 -109
- package/commands/run.js +0 -115
- package/commands/up.js +0 -135
package/commands/rm.js
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
// crosstalk rm — remove a transport completely.
|
|
2
|
-
//
|
|
3
|
-
// Stops the container if running, removes the container, deletes the
|
|
4
|
-
// entire <base>/<name>/ tree (transport git repo, installed CLIs, auth
|
|
5
|
-
// state, dispatcher state, compose file, port file). Confirmation
|
|
6
|
-
// prompt by default; --force / -f to skip for scripted use.
|
|
7
|
-
//
|
|
8
|
-
// Cannot undo. All message history is lost.
|
|
9
|
-
|
|
10
|
-
import { existsSync, rmSync } from 'fs';
|
|
11
|
-
import { spawnSync } from 'child_process';
|
|
12
|
-
import { createInterface } from 'readline';
|
|
13
|
-
import { has } from '../lib/argv.js';
|
|
14
|
-
import {
|
|
15
|
-
parseContainerName,
|
|
16
|
-
storagePaths,
|
|
17
|
-
isInitialized,
|
|
18
|
-
isRunning,
|
|
19
|
-
containerExists,
|
|
20
|
-
DEFAULT_CONTAINER_NAME,
|
|
21
|
-
} from '../lib/resolve.js';
|
|
22
|
-
|
|
23
|
-
function usage(exit = 0) {
|
|
24
|
-
const w = exit === 0 ? process.stdout : process.stderr;
|
|
25
|
-
w.write(
|
|
26
|
-
`Usage: crosstalk rm [--containername <name>] [--force]
|
|
27
|
-
|
|
28
|
-
Stops and removes the engine container, then deletes the entire
|
|
29
|
-
storage tree under <base>/<name>/. Includes the transport git repo
|
|
30
|
-
(all message history), installed agent CLIs + auth tokens, and
|
|
31
|
-
dispatcher state. CANNOT BE UNDONE.
|
|
32
|
-
|
|
33
|
-
Options:
|
|
34
|
-
--containername <name> Container to remove (default: 'crosstalk').
|
|
35
|
-
--force, -f Skip the confirmation prompt.
|
|
36
|
-
`,
|
|
37
|
-
);
|
|
38
|
-
process.exit(exit);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function confirm(promptText) {
|
|
42
|
-
return new Promise((resolve) => {
|
|
43
|
-
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
44
|
-
rl.question(promptText, (ans) => {
|
|
45
|
-
rl.close();
|
|
46
|
-
const yes = /^y(es)?$/i.test(ans.trim());
|
|
47
|
-
resolve(yes);
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export async function run(argv) {
|
|
53
|
-
if (has(argv, '--help') || has(argv, '-h')) usage(0);
|
|
54
|
-
|
|
55
|
-
let name;
|
|
56
|
-
try {
|
|
57
|
-
name = parseContainerName(argv);
|
|
58
|
-
} catch (err) {
|
|
59
|
-
process.stderr.write(`crosstalk rm: ${err.message}\n`);
|
|
60
|
-
return 1;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const paths = storagePaths(name);
|
|
64
|
-
const haveStorage = isInitialized(name) || existsSync(paths.storageRoot);
|
|
65
|
-
const haveContainer = containerExists(name);
|
|
66
|
-
|
|
67
|
-
if (!haveStorage && !haveContainer) {
|
|
68
|
-
process.stderr.write(`crosstalk rm: transport '${name}' not found.\n`);
|
|
69
|
-
return 1;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (!has(argv, '--force') && !has(argv, '-f')) {
|
|
73
|
-
process.stderr.write(
|
|
74
|
-
`Remove '${name}'?\n` +
|
|
75
|
-
` This deletes:\n` +
|
|
76
|
-
` - transport git repo at ${paths.transportDir}\n` +
|
|
77
|
-
` (all message history, channels, replies)\n` +
|
|
78
|
-
` - installed agent CLIs + auth tokens (claude, codex, ...)\n` +
|
|
79
|
-
` - dispatcher state (cursor, heartbeat, errors.log)\n` +
|
|
80
|
-
` Cannot undo.\n`,
|
|
81
|
-
);
|
|
82
|
-
const ok = await confirm('[y/N]: ');
|
|
83
|
-
if (!ok) {
|
|
84
|
-
process.stderr.write('crosstalk rm: cancelled.\n');
|
|
85
|
-
return 1;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Stop + remove the container (best-effort).
|
|
90
|
-
if (isRunning(name) && existsSync(paths.composeFile)) {
|
|
91
|
-
spawnSync('docker', ['compose', '-f', paths.composeFile, 'down'], { stdio: 'inherit' });
|
|
92
|
-
} else if (haveContainer) {
|
|
93
|
-
spawnSync('docker', ['rm', '-f', name], { stdio: 'ignore' });
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Wipe storage.
|
|
97
|
-
try {
|
|
98
|
-
rmSync(paths.storageRoot, { recursive: true, force: true });
|
|
99
|
-
} catch (err) {
|
|
100
|
-
process.stderr.write(
|
|
101
|
-
`crosstalk rm: failed to remove ${paths.storageRoot} — ${err.message}\n` +
|
|
102
|
-
` (container is removed, but storage cleanup needs intervention)\n`,
|
|
103
|
-
);
|
|
104
|
-
return 1;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
process.stdout.write(`Removed '${name}'.\n`);
|
|
108
|
-
return 0;
|
|
109
|
-
}
|
package/commands/run.js
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
// crosstalk run — dispatch a primitive or workflow via POST /messages.
|
|
2
|
-
//
|
|
3
|
-
// Mirrors crosstalkd's run surface:
|
|
4
|
-
// crosstalk run --type primitive --to <model>[@<machine>] [--as <persona>]
|
|
5
|
-
// [--fanout <n>] [--channel <name|uuid>] [--from <name>]
|
|
6
|
-
// [--new] <body|file|->
|
|
7
|
-
// crosstalk run --type workflow [--channel <name|uuid>] [--from <name>]
|
|
8
|
-
// [--new] <file|->
|
|
9
|
-
|
|
10
|
-
import { readFileSync, existsSync, statSync } from 'fs';
|
|
11
|
-
import { apiFor } from '../lib/api-client.js';
|
|
12
|
-
import { reportAndExit } from '../lib/errors.js';
|
|
13
|
-
import { flag, has, positionals } from '../lib/argv.js';
|
|
14
|
-
|
|
15
|
-
const FLAGS_WITH_VALUE = new Set(['--type', '--to', '--as', '--fanout', '--channel', '--from', '--containername', '-c']);
|
|
16
|
-
|
|
17
|
-
function usage(exit = 0) {
|
|
18
|
-
const w = exit === 0 ? process.stdout : process.stderr;
|
|
19
|
-
w.write(
|
|
20
|
-
`Usage:
|
|
21
|
-
crosstalk run --type primitive --to <model>[@<machine>] \\
|
|
22
|
-
[--as <persona>] [--fanout <n>] \\
|
|
23
|
-
[--channel <name|uuid>] [--from <name>] [--new] \\
|
|
24
|
-
<body|file|->
|
|
25
|
-
|
|
26
|
-
crosstalk run --type workflow [--channel <name|uuid>] \\
|
|
27
|
-
[--from <name>] [--new] <file|->
|
|
28
|
-
`,
|
|
29
|
-
);
|
|
30
|
-
process.exit(exit);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function readStdin() {
|
|
34
|
-
try {
|
|
35
|
-
return readFileSync(0, 'utf-8');
|
|
36
|
-
} catch {
|
|
37
|
-
return '';
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function resolveBody(arg) {
|
|
42
|
-
if (arg == null) return null;
|
|
43
|
-
if (arg === '-') return readStdin();
|
|
44
|
-
if (existsSync(arg)) {
|
|
45
|
-
try {
|
|
46
|
-
if (statSync(arg).isFile()) return readFileSync(arg, 'utf-8');
|
|
47
|
-
} catch { /* fall through to inline */ }
|
|
48
|
-
}
|
|
49
|
-
return arg;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export async function run(argv) {
|
|
53
|
-
if (has(argv, '--help') || has(argv, '-h')) usage(0);
|
|
54
|
-
|
|
55
|
-
const api = apiFor(argv);
|
|
56
|
-
const type = flag(argv, '--type');
|
|
57
|
-
if (type !== 'primitive' && type !== 'workflow') usage(1);
|
|
58
|
-
|
|
59
|
-
// Body is the last positional. positionals() skips flag values so the
|
|
60
|
-
// body resolves regardless of flag order — `--containername a8 hello`
|
|
61
|
-
// and `hello --containername a8` both work.
|
|
62
|
-
const pos = positionals(argv, [...FLAGS_WITH_VALUE]);
|
|
63
|
-
const bodyArg = pos[pos.length - 1];
|
|
64
|
-
|
|
65
|
-
const body = resolveBody(bodyArg);
|
|
66
|
-
if (body == null || body.length === 0) {
|
|
67
|
-
process.stderr.write('crosstalk run: missing body (file path, inline string, or `-` for stdin)\n');
|
|
68
|
-
return 1;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const channel = flag(argv, '--channel');
|
|
72
|
-
const from = flag(argv, '--from') ?? process.env.USER ?? 'operator';
|
|
73
|
-
|
|
74
|
-
let payload;
|
|
75
|
-
if (type === 'primitive') {
|
|
76
|
-
const to = flag(argv, '--to');
|
|
77
|
-
if (!to) {
|
|
78
|
-
process.stderr.write('crosstalk run --type primitive: --to <model> is required\n');
|
|
79
|
-
return 1;
|
|
80
|
-
}
|
|
81
|
-
const fanoutRaw = flag(argv, '--fanout');
|
|
82
|
-
const fanout = fanoutRaw ? Math.max(1, parseInt(fanoutRaw, 10)) : 1;
|
|
83
|
-
payload = {
|
|
84
|
-
type: 'primitive',
|
|
85
|
-
to,
|
|
86
|
-
as: flag(argv, '--as'),
|
|
87
|
-
channel,
|
|
88
|
-
from,
|
|
89
|
-
fanout,
|
|
90
|
-
body,
|
|
91
|
-
};
|
|
92
|
-
} else {
|
|
93
|
-
payload = {
|
|
94
|
-
type: 'workflow',
|
|
95
|
-
channel,
|
|
96
|
-
from,
|
|
97
|
-
body,
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
let resp;
|
|
102
|
-
try {
|
|
103
|
-
resp = await api.post('/messages', payload);
|
|
104
|
-
} catch (err) {
|
|
105
|
-
reportAndExit(err, 'crosstalk run');
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (resp.type === 'primitive') {
|
|
109
|
-
for (const p of resp.relPaths) process.stdout.write(`Sent: ${p}\n`);
|
|
110
|
-
} else {
|
|
111
|
-
process.stdout.write(`Workflow dispatched: ${resp.relPath}\n`);
|
|
112
|
-
process.stdout.write(`Child channel: ${resp.childChannel} (parent: ${resp.channel.slice(0, 8)})\n`);
|
|
113
|
-
}
|
|
114
|
-
return 0;
|
|
115
|
-
}
|
package/commands/up.js
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
// crosstalk up — bring up the engine container for a transport.
|
|
2
|
-
//
|
|
3
|
-
// alpha.6: no cwd magic. Resolves container by --containername flag
|
|
4
|
-
// (default 'crosstalk') and operates against <base>/<name>/.
|
|
5
|
-
//
|
|
6
|
-
// Three lifecycle cases:
|
|
7
|
-
// 1. Container already running → error (use chat/restart instead).
|
|
8
|
-
// 2. Storage exists, no container → resume; print one-line notice.
|
|
9
|
-
// 3. Storage doesn't exist → error pointing to `crosstalk init`.
|
|
10
|
-
//
|
|
11
|
-
// First-time up generates <base>/<name>/docker-compose.yml from a template.
|
|
12
|
-
// Subsequent ups respect operator edits — only regenerate if missing.
|
|
13
|
-
|
|
14
|
-
import { writeFileSync, existsSync, readFileSync } from 'fs';
|
|
15
|
-
import { spawnSync } from 'child_process';
|
|
16
|
-
import { has } from '../lib/argv.js';
|
|
17
|
-
import {
|
|
18
|
-
requireInitialized,
|
|
19
|
-
isRunning,
|
|
20
|
-
containerExists,
|
|
21
|
-
apiPortFor,
|
|
22
|
-
DEFAULT_CONTAINER_NAME,
|
|
23
|
-
CROSSTALK_LABEL,
|
|
24
|
-
} from '../lib/resolve.js';
|
|
25
|
-
|
|
26
|
-
const DEFAULT_IMAGE = process.env.CROSSTALK_IMAGE
|
|
27
|
-
?? 'ghcr.io/cordfuse/crosstalk-server:7.0.0-alpha.8';
|
|
28
|
-
|
|
29
|
-
function usage(exit = 0) {
|
|
30
|
-
const w = exit === 0 ? process.stdout : process.stderr;
|
|
31
|
-
w.write(
|
|
32
|
-
`Usage: crosstalk up [--containername <name>]
|
|
33
|
-
|
|
34
|
-
Starts the engine container for a transport that has already been
|
|
35
|
-
'crosstalk init'd. Default container is 'crosstalk'; pass --containername
|
|
36
|
-
to address a named one.
|
|
37
|
-
|
|
38
|
-
If the storage at <base>/<name>/ already exists (operator previously
|
|
39
|
-
brought it up then 'crosstalk down'd it), the container resumes against
|
|
40
|
-
that state — installed agent CLIs, OAuth tokens, dispatcher cursor all
|
|
41
|
-
carry over.
|
|
42
|
-
|
|
43
|
-
Environment:
|
|
44
|
-
CROSSTALK_IMAGE Override the engine image
|
|
45
|
-
CROSSTALK_ALIAS Override the engine's machine identity
|
|
46
|
-
(defaults to the container name)
|
|
47
|
-
`,
|
|
48
|
-
);
|
|
49
|
-
process.exit(exit);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function renderCompose({ name, image, apiPort, alias, uid, gid, paths }) {
|
|
53
|
-
return `# Generated by 'crosstalk up'. Machine-local — runtime-owned.
|
|
54
|
-
# Storage mode: ${paths.mode}
|
|
55
|
-
# transport (container /var/lib/crosstalk-transport) → ${paths.transportDir}
|
|
56
|
-
# crosstalk-root (container /crosstalk-root) → ${paths.crosstalkRoot}
|
|
57
|
-
# crosstalk-state (container /var/lib/crosstalk-state) → ${paths.crosstalkState}
|
|
58
|
-
|
|
59
|
-
services:
|
|
60
|
-
crosstalkd:
|
|
61
|
-
image: ${image}
|
|
62
|
-
container_name: ${name}
|
|
63
|
-
restart: unless-stopped
|
|
64
|
-
labels:
|
|
65
|
-
${CROSSTALK_LABEL.replace('=', ': "')}"
|
|
66
|
-
ports:
|
|
67
|
-
- "127.0.0.1:${apiPort}:7000"
|
|
68
|
-
volumes:
|
|
69
|
-
- ${paths.transportDir}:/var/lib/crosstalk-transport
|
|
70
|
-
- ${paths.crosstalkRoot}:/crosstalk-root
|
|
71
|
-
- ${paths.crosstalkState}:/var/lib/crosstalk-state
|
|
72
|
-
environment:
|
|
73
|
-
CROSSTALK_ALIAS: ${alias}
|
|
74
|
-
CROSSTALK_UID: "${uid}"
|
|
75
|
-
CROSSTALK_GID: "${gid}"
|
|
76
|
-
CROSSTALKD_API_PORT: "7000"
|
|
77
|
-
DISPATCH_JSON: "true"
|
|
78
|
-
DISPATCH_POLL_SECONDS: "30"
|
|
79
|
-
`;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export async function run(argv) {
|
|
83
|
-
if (has(argv, '--help') || has(argv, '-h')) usage(0);
|
|
84
|
-
|
|
85
|
-
const { name, paths } = requireInitialized(argv);
|
|
86
|
-
|
|
87
|
-
// Case 1: already running → error, don't surprise the operator with
|
|
88
|
-
// a silent no-op or implicit restart.
|
|
89
|
-
if (isRunning(name)) {
|
|
90
|
-
process.stderr.write(
|
|
91
|
-
`crosstalk up: '${name}' is already running.\n` +
|
|
92
|
-
` Use 'crosstalk chat${name === DEFAULT_CONTAINER_NAME ? '' : ` --containername ${name}`}' to attach,\n` +
|
|
93
|
-
` 'crosstalk restart${name === DEFAULT_CONTAINER_NAME ? '' : ` --containername ${name}`}' to recreate,\n` +
|
|
94
|
-
` or 'crosstalk down${name === DEFAULT_CONTAINER_NAME ? '' : ` --containername ${name}`}' first.\n`,
|
|
95
|
-
);
|
|
96
|
-
return 1;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Case "stopped but not removed" — `docker stop` without compose down.
|
|
100
|
-
// Silently remove the dead container so the recreate works.
|
|
101
|
-
if (containerExists(name)) {
|
|
102
|
-
spawnSync('docker', ['rm', '-f', name], { stdio: 'ignore' });
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Compose file generation. If operator edited theirs, respect it.
|
|
106
|
-
const generated = !existsSync(paths.composeFile);
|
|
107
|
-
if (generated) {
|
|
108
|
-
const alias = process.env.CROSSTALK_ALIAS ?? name;
|
|
109
|
-
const uid = typeof process.getuid === 'function' ? process.getuid() : 1000;
|
|
110
|
-
const gid = typeof process.getgid === 'function' ? process.getgid() : 1000;
|
|
111
|
-
const apiPort = apiPortFor(name);
|
|
112
|
-
const image = DEFAULT_IMAGE;
|
|
113
|
-
const content = renderCompose({ name, image, apiPort, alias, uid, gid, paths });
|
|
114
|
-
writeFileSync(paths.composeFile, content);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Resume notice when storage is non-fresh (operator brought it up
|
|
118
|
-
// before, has installed CLIs / auth state in crosstalk-root/). Heuristic:
|
|
119
|
-
// crosstalk-root has more than just a .gitkeep / nothing.
|
|
120
|
-
if (!generated) {
|
|
121
|
-
process.stdout.write(`Resuming '${name}' (existing state at ${paths.storageRoot})\n`);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const r = spawnSync('docker', ['compose', '-f', paths.composeFile, 'up', '-d'], {
|
|
125
|
-
stdio: 'inherit',
|
|
126
|
-
});
|
|
127
|
-
if (r.status !== 0) {
|
|
128
|
-
process.stderr.write(`crosstalk up: docker compose exited ${r.status}\n`);
|
|
129
|
-
return r.status ?? 1;
|
|
130
|
-
}
|
|
131
|
-
process.stdout.write(
|
|
132
|
-
`\n'${name}' starting. Check with 'crosstalk status${name === DEFAULT_CONTAINER_NAME ? '' : ` --containername ${name}`}' (give it ~5s).\n`,
|
|
133
|
-
);
|
|
134
|
-
return 0;
|
|
135
|
-
}
|