@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.
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 -115
  72. 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
- }