@dmsdc-ai/aigentry-telepty 0.1.83 → 0.1.85

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/AGENTS.md ADDED
@@ -0,0 +1,74 @@
1
+ # AGENTS.md — aigentry-telepty
2
+
3
+ ## Overview
4
+
5
+ PTY Multiplexer & Session Orchestrator — aigentry 에코시스템의 **통신 인프라**.
6
+ npm: `@dmsdc-ai/aigentry-telepty` | 멀티 AI 세션을 생성·연결·제어하는 PTY 멀티플렉서.
7
+
8
+ ## Architecture
9
+
10
+ ```
11
+ CLI (cli.js) ──→ HTTP/WS ──→ Daemon (daemon.js:3848)
12
+ ├── Session WS (/api/sessions/:id)
13
+ ├── Event Bus WS (/api/bus)
14
+ └── REST API (/api/sessions/*)
15
+ ```
16
+
17
+ | 파일 | 역할 |
18
+ |------|------|
19
+ | `cli.js` | CLI 명령 + allow-bridge (PTY 래핑) |
20
+ | `daemon.js` | HTTP/WS 서버, 세션 상태, inject 전달 |
21
+ | `tui.js` | blessed 기반 TUI 대시보드 |
22
+ | `session-routing.js` | 세션 ID 해석, alias 매칭, 호스트 그룹핑 |
23
+ | `daemon-control.js` | 싱글톤 daemon PID 관리 |
24
+ | `auth.js` | UUID 토큰 기반 인증 |
25
+ | `interactive-terminal.js` | raw mode stdin/stdout 관리 |
26
+ | `skill-installer.js` | CLI별 스킬 설치 (Claude/Codex/Gemini) |
27
+
28
+ ## Inject 전달 경로 (wrapped session)
29
+
30
+ 1. **Primary**: `kitty @ send-text` (터미널 직접 전달, allow-bridge 우회)
31
+ 2. **Fallback**: WS → allow-bridge → `child.write()` (PTY)
32
+ 3. **Submit**: `osascript` Return 키 → kitty fallback → WS `\r`
33
+
34
+ busy 세션: CR은 큐에 대기 중인 텍스트와 함께 큐잉 후 올바른 순서로 flush.
35
+
36
+ ## Commands
37
+
38
+ ```bash
39
+ npm test # 43 tests (node:test)
40
+ telepty daemon # daemon 시작 (포트 3848)
41
+ telepty allow --id <name> claude # 세션 래핑
42
+ telepty tui # TUI 대시보드
43
+ telepty list # 세션 목록
44
+ telepty inject <id> "msg" # 메시지 주입
45
+ telepty broadcast "msg" # 전체 브로드캐스트
46
+ telepty session start --launch # kitty 탭으로 다중 세션 시작
47
+ ```
48
+
49
+ ## Key Rules
50
+
51
+ - inject 후 submit은 항상 `osascript`로 통일 (`--no-enter` + osascript keystroke)
52
+ - inject 시 발신자 session ID (`--from`)를 항상 포함
53
+ - PTY `\r` 직접 의존 금지
54
+
55
+ ## Session Communication
56
+
57
+ ```bash
58
+ # List active sessions
59
+ telepty list
60
+
61
+ # Send message to another session
62
+ telepty inject --from aigentry-telepty-{cli} <target-session> "message"
63
+
64
+ # Report to orchestrator
65
+ telepty inject --ref --from aigentry-telepty-{cli} aigentry-orchestrator-claude "report"
66
+ ```
67
+
68
+ ## Work Principles
69
+
70
+ - **Best-First**: 항상 최선의 해결책 선택. 차선책/우회 금지.
71
+ - **Configurable**: 설정으로 제어 가능한 구조. 하드코딩 금지.
72
+ - **Evidence-Based**: 추측 금지. 데이터/로그/테스트 결과 기반 판단.
73
+ - **Fail Fast**: 에러 즉시 보고. 숨기지 않음.
74
+ - **Constitution**: ~/projects/aigentry/docs/CONSTITUTION.md 준수.
package/GEMINI.md ADDED
@@ -0,0 +1,10 @@
1
+ @AGENTS.md
2
+
3
+ # Gemini CLI — aigentry-telepty
4
+
5
+ ## Gemini 전용 설정
6
+
7
+ - 세션 ID: `aigentry-telepty-gemini`
8
+ - 보고: `telepty inject --ref --from aigentry-telepty-gemini aigentry-orchestrator-claude "보고 내용"`
9
+ - Gemini 강점 활용: upstream 이슈 검색, API 리서치, 문서화 우선
10
+ - 헌법: `~/projects/aigentry/docs/CONSTITUTION.md`
package/cli.js CHANGED
@@ -871,6 +871,7 @@ async function main() {
871
871
  }
872
872
  delete process.env.TELEPTY_SESSION_ID;
873
873
  process.env.TELEPTY_SESSION_ID = sessionId;
874
+ process.env.TELEPTY_AVAILABLE = 'true';
874
875
 
875
876
  await ensureDaemonRunning({ requiredCapabilities: ['wrapped-sessions'] });
876
877
 
@@ -914,7 +915,7 @@ async function main() {
914
915
  // Spawn local PTY (preserves isTTY, env, shell config)
915
916
  const pty = require('node-pty');
916
917
  const sessionCwd = process.cwd();
917
- const sessionEnv = { ...process.env, TELEPTY_SESSION_ID: sessionId };
918
+ const sessionEnv = { ...process.env, TELEPTY_SESSION_ID: sessionId, TELEPTY_AVAILABLE: 'true' };
918
919
  let child = null;
919
920
  let sessionStartTime = Date.now();
920
921
  let crashCount = 0;
package/daemon.js CHANGED
@@ -445,7 +445,7 @@ async function writeDataToSession(id, session, data) {
445
445
  // UDS delivery via net.connect()
446
446
  if (session.delivery && session.delivery.transport === 'unix_socket' && session.delivery.address) {
447
447
  return new Promise((resolve) => {
448
- const payload = JSON.stringify({ text: data, session_id: id }) + '\n';
448
+ const payload = JSON.stringify({ action: "Inject", workspace: id, text: data }) + '\n';
449
449
  const timeout = setTimeout(() => {
450
450
  sock.destroy();
451
451
  resolve(buildErrorBody('TIMEOUT', 'UDS delivery timed out.', { httpStatus: 504 }));
@@ -528,7 +528,7 @@ async function deliverInjectionToSession(id, session, prompt, options = {}) {
528
528
  return textResult;
529
529
  }
530
530
 
531
- if (!options.noEnter) {
531
+ if (!options.noEnter && session.type !== 'aterm') {
532
532
  const submitDelay = session.type === 'wrapped' ? 500 : 300;
533
533
  setTimeout(async () => {
534
534
  const submitResult = await writeDataToSession(id, session, '\r');
@@ -0,0 +1,163 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * MCP Telepty Server — Session management tools for AI CLIs
5
+ *
6
+ * Tools:
7
+ * telepty_list_sessions List all active telepty sessions
8
+ * telepty_inject_session Inject text into a session
9
+ * telepty_session_status Get detailed status of a session
10
+ */
11
+
12
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
13
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
14
+ import { z } from "zod";
15
+ import fs from "fs";
16
+ import path from "path";
17
+ import os from "os";
18
+
19
+ const PKG_VERSION = (() => {
20
+ try {
21
+ const p = JSON.parse(fs.readFileSync(new URL("../package.json", import.meta.url), "utf8"));
22
+ return p.version || "0.0.0";
23
+ } catch { return "0.0.0"; }
24
+ })();
25
+
26
+ // ── Config ──
27
+
28
+ function getAuthToken() {
29
+ try {
30
+ const configPath = path.join(os.homedir(), ".telepty", "config.json");
31
+ const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
32
+ return config.authToken || "";
33
+ } catch { return ""; }
34
+ }
35
+
36
+ function getDaemonUrl() {
37
+ const port = process.env.TELEPTY_PORT || "3848";
38
+ const host = process.env.TELEPTY_HOST || "127.0.0.1";
39
+ return `http://${host}:${port}`;
40
+ }
41
+
42
+ async function daemonFetch(endpoint, options = {}) {
43
+ const url = `${getDaemonUrl()}${endpoint}`;
44
+ const token = getAuthToken();
45
+ const headers = {
46
+ "Content-Type": "application/json",
47
+ ...(token ? { "x-telepty-token": token } : {}),
48
+ ...options.headers,
49
+ };
50
+ const res = await fetch(url, { ...options, headers });
51
+ if (!res.ok) {
52
+ const body = await res.text().catch(() => "");
53
+ throw new Error(`telepty daemon ${res.status}: ${body || res.statusText}`);
54
+ }
55
+ return res.json();
56
+ }
57
+
58
+ // ── MCP Server ──
59
+
60
+ const server = new McpServer({
61
+ name: "telepty",
62
+ version: PKG_VERSION,
63
+ });
64
+
65
+ // Tool: telepty_list_sessions
66
+ server.tool(
67
+ "telepty_list_sessions",
68
+ "List all active telepty sessions with their status, type, and last activity.",
69
+ {},
70
+ async () => {
71
+ try {
72
+ const sessions = await daemonFetch("/api/sessions");
73
+ if (!Array.isArray(sessions) || sessions.length === 0) {
74
+ return { content: [{ type: "text", text: "No active sessions." }] };
75
+ }
76
+ let text = `## Active Sessions (${sessions.length})\n\n`;
77
+ text += "| ID | Type | Status | Last Activity |\n";
78
+ text += "|----|------|--------|---------------|\n";
79
+ for (const s of sessions) {
80
+ const status = s.connected ? "connected" : (s.stale ? "stale" : "disconnected");
81
+ const lastAct = s.lastActivityAt ? new Date(s.lastActivityAt).toLocaleString() : "—";
82
+ text += `| ${s.id} | ${s.type || "—"} | ${status} | ${lastAct} |\n`;
83
+ }
84
+ return { content: [{ type: "text", text }] };
85
+ } catch (err) {
86
+ return { content: [{ type: "text", text: `Error listing sessions: ${err.message}` }], isError: true };
87
+ }
88
+ }
89
+ );
90
+
91
+ // Tool: telepty_session_status
92
+ server.tool(
93
+ "telepty_session_status",
94
+ "Get detailed status of a specific telepty session.",
95
+ {
96
+ session_id: z.string().describe("Session ID to query"),
97
+ },
98
+ async ({ session_id }) => {
99
+ try {
100
+ const sessions = await daemonFetch("/api/sessions");
101
+ const session = Array.isArray(sessions)
102
+ ? sessions.find(s => s.id === session_id || s.id.startsWith(session_id))
103
+ : null;
104
+ if (!session) {
105
+ return { content: [{ type: "text", text: `Session "${session_id}" not found.` }] };
106
+ }
107
+ const status = session.connected ? "connected" : (session.stale ? "stale" : "disconnected");
108
+ let text = `## Session: ${session.id}\n\n`;
109
+ text += `- **Type:** ${session.type || "unknown"}\n`;
110
+ text += `- **Status:** ${status}\n`;
111
+ text += `- **Command:** ${session.command || "—"}\n`;
112
+ text += `- **CWD:** ${session.cwd || "—"}\n`;
113
+ text += `- **Backend:** ${session.backend || "—"}\n`;
114
+ text += `- **Created:** ${session.createdAt ? new Date(session.createdAt).toLocaleString() : "—"}\n`;
115
+ text += `- **Last Activity:** ${session.lastActivityAt ? new Date(session.lastActivityAt).toLocaleString() : "—"}\n`;
116
+ if (session.stateReport) {
117
+ text += `- **State Report:** ${session.stateReport}\n`;
118
+ }
119
+ return { content: [{ type: "text", text }] };
120
+ } catch (err) {
121
+ return { content: [{ type: "text", text: `Error querying session: ${err.message}` }], isError: true };
122
+ }
123
+ }
124
+ );
125
+
126
+ // Tool: telepty_inject_session
127
+ server.tool(
128
+ "telepty_inject_session",
129
+ "Inject text into a telepty session. Use this to send messages or prompts to other AI sessions.",
130
+ {
131
+ session_id: z.string().describe("Target session ID"),
132
+ text: z.string().describe("Text to inject"),
133
+ from: z.string().optional().describe("Sender session ID (return address)"),
134
+ no_enter: z.boolean().default(false).describe("If true, do not send Enter after text"),
135
+ },
136
+ async ({ session_id, text, from, no_enter }) => {
137
+ try {
138
+ const body = { prompt: text };
139
+ if (from) body.from = from;
140
+ if (no_enter) body.no_enter = true;
141
+ const result = await daemonFetch(`/api/sessions/${encodeURIComponent(session_id)}/inject`, {
142
+ method: "POST",
143
+ body: JSON.stringify(body),
144
+ });
145
+ const deliveryInfo = result.deliveryPath ? ` (via ${result.deliveryPath})` : "";
146
+ return { content: [{ type: "text", text: `Injected to ${session_id}${deliveryInfo}.` }] };
147
+ } catch (err) {
148
+ return { content: [{ type: "text", text: `Error injecting to ${session_id}: ${err.message}` }], isError: true };
149
+ }
150
+ }
151
+ );
152
+
153
+ // ── Start ──
154
+
155
+ async function main() {
156
+ const transport = new StdioServerTransport();
157
+ await server.connect(transport);
158
+ }
159
+
160
+ main().catch((err) => {
161
+ console.error("telepty MCP server fatal:", err);
162
+ process.exit(1);
163
+ });
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@dmsdc-ai/aigentry-telepty",
3
- "version": "0.1.83",
3
+ "version": "0.1.85",
4
4
  "main": "daemon.js",
5
5
  "bin": {
6
6
  "aigentry-telepty": "install.js",
7
7
  "telepty": "cli.js",
8
- "telepty-install": "install.js"
8
+ "telepty-install": "install.js",
9
+ "telepty-mcp": "mcp-server/index.mjs"
9
10
  },
10
11
  "scripts": {
11
12
  "test": "node --test test/auth.test.js test/daemon.test.js test/daemon-singleton.test.js test/cli.test.js test/skill-installer.test.js test/interactive-terminal.test.js test/runtime-info.test.js test/session-routing.test.js",
@@ -20,6 +21,8 @@
20
21
  "remote",
21
22
  "inject",
22
23
  "ai-cli",
24
+ "mcp",
25
+ "model-context-protocol",
23
26
  "claude",
24
27
  "codex",
25
28
  "gemini",
@@ -38,6 +41,7 @@
38
41
  "url": "https://github.com/dmsdc-ai/aigentry-telepty/issues"
39
42
  },
40
43
  "dependencies": {
44
+ "@modelcontextprotocol/sdk": "^1.26.0",
41
45
  "blessed": "^0.1.81",
42
46
  "cors": "^2.8.6",
43
47
  "express": "^5.2.1",
@@ -45,6 +49,7 @@
45
49
  "prompts": "^2.4.2",
46
50
  "update-notifier": "^5.1.0",
47
51
  "uuid": "^13.0.0",
48
- "ws": "^8.19.0"
52
+ "ws": "^8.19.0",
53
+ "zod": "^3.24.0"
49
54
  }
50
55
  }
@@ -0,0 +1,71 @@
1
+ # telepty Session Communication / 세션 통신
2
+
3
+ AI CLI sessions use `telepty` to communicate with other sessions.
4
+
5
+ ## Environment Detection / 환경 감지
6
+
7
+ ```bash
8
+ # Check if telepty is available
9
+ echo "$TELEPTY_AVAILABLE" # "true" if telepty is installed and daemon running
10
+
11
+ # Get current session ID
12
+ echo "$TELEPTY_SESSION_ID" # e.g., "aigentry-brain-claude"
13
+ ```
14
+
15
+ If `$TELEPTY_AVAILABLE` is not set, telepty commands are unavailable.
16
+
17
+ ## Commands / 명령어
18
+
19
+ | English action | 한국어 동작 | Command |
20
+ |----------------|------------|---------|
21
+ | List sessions | 세션 목록 보기 | `telepty list` |
22
+ | Send to session | 세션에 메시지 보내기 | `telepty inject <session> 'message'` |
23
+ | Send with return address | 발신자 포함 전송 | `telepty inject --from $TELEPTY_SESSION_ID <session> 'message'` |
24
+ | Send without enter | 엔터 없이 전송 | `telepty inject --no-enter <session> 'message'` |
25
+ | Broadcast to all | 전체 브로드캐스트 | `telepty broadcast 'message'` |
26
+ | Multicast to group | 그룹 멀티캐스트 | `telepty multicast <id1,id2> 'message'` |
27
+ | Reply to last sender | 마지막 발신자에게 답장 | `telepty reply 'message'` |
28
+ | Check session status | 세션 상태 확인 | `telepty list` (check connected/stale) |
29
+ | Read session screen | 세션 화면 읽기 | `curl -s http://127.0.0.1:3848/api/sessions/<id>/screen -H "x-telepty-token: $TOKEN"` |
30
+ | Kill session | 세션 종료 | `telepty delete <session>` |
31
+ | Clean stale sessions | 비활성 세션 정리 | `telepty clean` |
32
+ | Rename session | 세션 이름 변경 | `telepty rename <old> <new>` |
33
+ | Listen to event bus | 이벤트 버스 수신 | `telepty listen` |
34
+ | Attach to session | 세션에 접속 | `telepty attach <session>` |
35
+
36
+ ## Natural Language → Command / 자연어 → 명령어
37
+
38
+ | English request | 한국어 요청 | Command |
39
+ |----------------|------------|---------|
40
+ | `list sessions` | `세션 목록 보여줘` | `telepty list` |
41
+ | `send to brain` | `brain에 메시지 보내줘` | `telepty inject aigentry-brain-claude 'msg'` |
42
+ | `broadcast message` | `전체에 메시지 보내줘` | `telepty broadcast 'msg'` |
43
+ | `reply to sender` | `답장해줘` | `telepty reply 'msg'` |
44
+ | `kill session` | `세션 종료해줘` | `telepty delete <session>` |
45
+ | `clean stale` | `비활성 정리해줘` | `telepty clean` |
46
+ | `check status` | `상태 확인해줘` | `telepty list` |
47
+ | `rename session` | `세션 이름 바꿔줘` | `telepty rename <old> <new>` |
48
+ | `read screen` | `화면 읽어줘` | REST API `/api/sessions/<id>/screen` |
49
+
50
+ ## Cross-Machine Sessions / 크로스 머신 세션
51
+
52
+ When the same session ID exists on multiple hosts, use `session_id@host`:
53
+
54
+ ```bash
55
+ telepty inject brain-claude@macbook 'message'
56
+ ```
57
+
58
+ ## Rules / 규칙
59
+
60
+ 1. **Return address**: Always include `--from $TELEPTY_SESSION_ID` when expecting a reply.
61
+ 2. **Submit method**: Never rely on PTY `\r` directly. Use `telepty inject` which handles submit automatically.
62
+ 3. **Env detection**: Check `$TELEPTY_AVAILABLE` before using telepty commands.
63
+ 4. **Session ID format**: Use `<project>-<cli>` pattern (e.g., `aigentry-brain-claude`).
64
+
65
+ ## Reporting / 보고
66
+
67
+ On task completion, report to orchestrator:
68
+
69
+ ```bash
70
+ telepty inject --from $TELEPTY_SESSION_ID aigentry-orchestrator-claude 'REPORT: <summary>'
71
+ ```