@dmsdc-ai/aigentry-telepty 0.1.54 → 0.1.56

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.
@@ -53,6 +53,13 @@ Every session is uniquely identified by a locator triple:
53
53
  - Override: `TELEPTY_MACHINE_ID` env var
54
54
  - Exposed in: `GET /api/meta` (`machine_id` field), session `locator` object, bus event `source_host`
55
55
 
56
+ ### Global Session ID Uniqueness (P4)
57
+ - Convention: `{project}-{NNN}` (e.g. `aigentry-devkit-001`)
58
+ - Cross-machine uniqueness: guaranteed by `locator.machine_id` prefix in bus events
59
+ - Collision resolution: `resolveSessionAlias` returns local session only; remote sessions discovered via `source_host` field
60
+ - If two machines have `aigentry-devkit-001`, inject uses `target@host` to disambiguate
61
+ - Short form `aigentry-devkit-001` resolves to LOCAL session; remote requires explicit `@host`
62
+
56
63
  ### Peer Auth
57
64
  - Localhost: always trusted
58
65
  - Tailscale (100.x.y.z): trusted by default
package/daemon.js CHANGED
@@ -69,6 +69,24 @@ function relayToPeers(msg) {
69
69
  }
70
70
  }
71
71
 
72
+ // JWT auth: set TELEPTY_JWT_SECRET to enable. Tokens in Authorization: Bearer <token>
73
+ const JWT_SECRET = process.env.TELEPTY_JWT_SECRET || null;
74
+
75
+ function verifyJwt(token) {
76
+ if (!JWT_SECRET || !token) return false;
77
+ try {
78
+ // Simple HS256 JWT verification (no external deps)
79
+ const [headerB64, payloadB64, sigB64] = token.split('.');
80
+ if (!headerB64 || !payloadB64 || !sigB64) return false;
81
+ const expected = crypto.createHmac('sha256', JWT_SECRET)
82
+ .update(`${headerB64}.${payloadB64}`).digest('base64url');
83
+ if (sigB64 !== expected) return false;
84
+ const payload = JSON.parse(Buffer.from(payloadB64, 'base64url').toString());
85
+ if (payload.exp && Date.now() / 1000 > payload.exp) return false;
86
+ return payload;
87
+ } catch { return false; }
88
+ }
89
+
72
90
  function isAllowedPeer(ip) {
73
91
  if (!ip) return false;
74
92
  const cleanIp = ip.replace('::ffff:', '');
@@ -95,6 +113,12 @@ app.use((req, res, next) => {
95
113
  return next();
96
114
  }
97
115
 
116
+ // JWT Bearer token
117
+ const authHeader = req.headers['authorization'] || '';
118
+ if (authHeader.startsWith('Bearer ') && verifyJwt(authHeader.slice(7))) {
119
+ return next();
120
+ }
121
+
98
122
  console.warn(`[AUTH] Rejected unauthorized request from ${clientIp}`);
99
123
  res.status(401).json({ error: 'Unauthorized: Invalid or missing token.' });
100
124
  });
@@ -565,7 +589,7 @@ function sendViaKitty(sessionId, text) {
565
589
  }
566
590
 
567
591
  try {
568
- // Split text and CR — send-text for content, send-key for Enter
592
+ // Split text and CR — send-text for both (send-key corrupts keyboard protocol)
569
593
  const hasCr = text.endsWith('\r') || text.endsWith('\n');
570
594
  const textOnly = hasCr ? text.slice(0, -1) : text;
571
595
  if (textOnly.length > 0) {
@@ -577,7 +601,7 @@ function sendViaKitty(sessionId, text) {
577
601
  if (hasCr) {
578
602
  // Delay before sending Return — CLI needs time to process text input
579
603
  execSync('sleep 0.5', { timeout: 2000 });
580
- execSync(`kitty @ --to unix:${socket} send-key --match id:${windowId} Return`, {
604
+ execSync(`kitty @ --to unix:${socket} send-text --match id:${windowId} $'\\r'`, {
581
605
  timeout: 3000, stdio: ['pipe', 'pipe', 'pipe']
582
606
  });
583
607
  }
@@ -773,7 +797,7 @@ app.post('/api/sessions/:id/inject', (req, res) => {
773
797
  setTimeout(() => {
774
798
  if (wid && sock) {
775
799
  try {
776
- require('child_process').execSync(`kitty @ --to unix:${sock} send-key --match id:${wid} Return`, {
800
+ require('child_process').execSync(`kitty @ --to unix:${sock} send-text --match id:${wid} $'\\r'`, {
777
801
  timeout: 3000, stdio: ['pipe', 'pipe', 'pipe']
778
802
  });
779
803
  require('child_process').execSync(`kitty @ --to unix:${sock} set-tab-title --match id:${wid} '⚡ telepty :: ${id}'`, {
@@ -946,7 +970,7 @@ function busAutoRoute(msg) {
946
970
  });
947
971
  setTimeout(() => {
948
972
  try {
949
- require('child_process').execSync(`kitty @ --to unix:${sock} send-key --match id:${wid} Return`, {
973
+ require('child_process').execSync(`kitty @ --to unix:${sock} send-text --match id:${wid} $'\\r'`, {
950
974
  timeout: 3000, stdio: ['pipe', 'pipe', 'pipe']
951
975
  });
952
976
  } catch {}
@@ -1435,7 +1459,9 @@ server.on('upgrade', (req, socket, head) => {
1435
1459
  const url = new URL(req.url, 'http://' + req.headers.host);
1436
1460
  const token = url.searchParams.get('token');
1437
1461
 
1438
- if (!isAllowedPeer(req.socket.remoteAddress) && token !== EXPECTED_TOKEN) {
1462
+ const wsAuthHeader = req.headers['authorization'] || '';
1463
+ const wsJwtValid = wsAuthHeader.startsWith('Bearer ') && verifyJwt(wsAuthHeader.slice(7));
1464
+ if (!isAllowedPeer(req.socket.remoteAddress) && token !== EXPECTED_TOKEN && !wsJwtValid) {
1439
1465
  socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
1440
1466
  socket.destroy();
1441
1467
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dmsdc-ai/aigentry-telepty",
3
- "version": "0.1.54",
3
+ "version": "0.1.56",
4
4
  "main": "daemon.js",
5
5
  "bin": {
6
6
  "aigentry-telepty": "install.js",