@dmsdc-ai/aigentry-telepty 0.1.62 → 0.1.63

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/CLAUDE.md ADDED
@@ -0,0 +1,76 @@
1
+ # telepty — PTY Multiplexer & Session Orchestrator
2
+
3
+ aigentry 에코시스템의 **통신 인프라**. 멀티 AI 세션을 생성·연결·제어하는 PTY 멀티플렉서.
4
+
5
+ ## 에코시스템 내 역할
6
+
7
+ 9개 aigentry 세션(orchestrator, brain, amplify, dustcraw 등) 간 통신을 담당:
8
+ - 세션 생성/관리 (`allow`, `spawn`)
9
+ - 메시지 전달 (`inject`, `broadcast`, `multicast`, `reply`)
10
+ - 실시간 모니터링 (`tui`, `monitor`, `listen`)
11
+ - 세션 간 토론 조율 (`deliberate`)
12
+
13
+ ## 아키텍처
14
+
15
+ ```
16
+ CLI (cli.js) ──→ HTTP/WS ──→ Daemon (daemon.js:3848)
17
+ ├── Session WS (/api/sessions/:id)
18
+ ├── Event Bus WS (/api/bus)
19
+ └── REST API (/api/sessions/*)
20
+ ```
21
+
22
+ ### 핵심 모듈
23
+
24
+ | 파일 | 줄수 | 역할 |
25
+ |------|------|------|
26
+ | `cli.js` | ~1950 | CLI 명령 + allow-bridge (PTY 래핑) |
27
+ | `daemon.js` | ~1550 | HTTP/WS 서버, 세션 상태, inject 전달 |
28
+ | `tui.js` | ~500 | blessed 기반 TUI 대시보드 |
29
+ | `session-routing.js` | 81 | 세션 ID 해석, alias 매칭, 호스트 그룹핑 |
30
+ | `daemon-control.js` | 223 | 싱글톤 daemon PID 관리 |
31
+ | `auth.js` | 33 | UUID 토큰 기반 인증 |
32
+ | `interactive-terminal.js` | 71 | raw mode stdin/stdout 관리 |
33
+ | `skill-installer.js` | 269 | Claude/Codex/Gemini 스킬 설치 |
34
+
35
+ ### Inject 전달 경로 (wrapped session)
36
+
37
+ 1. **Primary**: `kitty @ send-text` (터미널 직접 전달, allow-bridge 우회)
38
+ 2. **Fallback**: WS → allow-bridge → `child.write()` (PTY)
39
+ 3. **Submit**: `osascript` Return 키 → kitty fallback → WS `\r`
40
+
41
+ busy 세션: CR은 큐에 대기 중인 텍스트와 함께 큐잉 후 올바른 순서로 flush.
42
+
43
+ ## 명령어
44
+
45
+ ```bash
46
+ # 테스트
47
+ npm test # 43 tests (node:test)
48
+
49
+ # 실행
50
+ telepty daemon # daemon 시작 (포트 3848)
51
+ telepty allow --id <name> claude # 세션 래핑
52
+ telepty tui # TUI 대시보드
53
+ telepty list # 세션 목록
54
+ telepty inject <id> "msg" # 메시지 주입
55
+ telepty broadcast "msg" # 전체 브로드캐스트
56
+ telepty session start --launch # kitty 탭으로 다중 세션 시작
57
+
58
+ # 릴리스
59
+ npm version patch --no-git-tag-version && npm publish --access public
60
+ ```
61
+
62
+ ## 주요 규칙
63
+
64
+ - inject 후 submit은 항상 `osascript`로 통일 (`--no-enter` + osascript keystroke)
65
+ - inject 시 발신자 session ID (`--from`)를 항상 포함
66
+ - PTY `\r` 직접 의존 금지
67
+
68
+ ## 최근 주요 변경 (v0.1.58–0.1.62)
69
+
70
+ | 버전 | 변경 |
71
+ |------|------|
72
+ | 0.1.62 | TUI 태스크 추적 — bus 이벤트에서 [태그] 자동 파싱, 세션별 상태 표시 |
73
+ | 0.1.61 | reconnect 시 resize/\x0c 제거 (멀티터미널 깜빡임 수정) |
74
+ | 0.1.60 | TUI P1 — s=start, k=kill, p=purge stale |
75
+ | 0.1.59 | wrapped inject 503 반환 + 테스트 43/43 정합성 |
76
+ | 0.1.58 | inject busy-session CR/text 순서 수정 + kittyWindowId 캐시 무효화 |
package/cli.js CHANGED
@@ -1039,6 +1039,16 @@ async function main() {
1039
1039
  process.exit(1);
1040
1040
  }
1041
1041
 
1042
+ // Entitlement: remote session check
1043
+ if (target.host && target.host !== '127.0.0.1' && target.host !== 'localhost') {
1044
+ const { checkEntitlement } = require('./entitlement');
1045
+ const ent = checkEntitlement({ feature: 'telepty.remote_sessions' });
1046
+ if (!ent.allowed) {
1047
+ console.error(`⚠️ ${ent.reason}\n Upgrade: ${ent.upgrade_url}`);
1048
+ process.exit(1);
1049
+ }
1050
+ }
1051
+
1042
1052
  const body = { prompt, no_enter: noEnter };
1043
1053
  if (fromId) body.from = fromId;
1044
1054
  if (replyTo) body.reply_to = replyTo;
package/daemon.js CHANGED
@@ -7,6 +7,7 @@ const { WebSocketServer } = require('ws');
7
7
  const { getConfig } = require('./auth');
8
8
  const pkg = require('./package.json');
9
9
  const { claimDaemonState, clearDaemonState } = require('./daemon-control');
10
+ const { checkEntitlement } = require('./entitlement');
10
11
 
11
12
  const config = getConfig();
12
13
  const EXPECTED_TOKEN = config.authToken;
@@ -209,6 +210,13 @@ app.post('/api/sessions/spawn', (req, res) => {
209
210
  const { session_id, command, args = [], cwd = process.cwd(), cols = 80, rows = 30, type = 'AGENT' } = req.body;
210
211
  if (!session_id) return res.status(400).json({ error: 'session_id is strictly required.' });
211
212
  if (sessions[session_id]) return res.status(409).json({ error: `Session ID '${session_id}' is already active.` });
213
+ // Entitlement: check session limit
214
+ const sessionCount = Object.keys(sessions).length;
215
+ const ent = checkEntitlement({ feature: 'telepty.multi_session', currentUsage: sessionCount });
216
+ if (!ent.allowed) {
217
+ console.log(`[ENTITLEMENT] Session limit reached (${sessionCount}/${ent.limit?.max || '?'}), tier: ${ent.tier}`);
218
+ return res.status(402).json({ error: ent.reason, upgrade_url: ent.upgrade_url, tier: ent.tier });
219
+ }
212
220
  if (!command) return res.status(400).json({ error: 'command is required' });
213
221
 
214
222
  const isWin = os.platform() === 'win32';
@@ -300,6 +308,15 @@ app.post('/api/sessions/spawn', (req, res) => {
300
308
  app.post('/api/sessions/register', (req, res) => {
301
309
  const { session_id, command, cwd = process.cwd() } = req.body;
302
310
  if (!session_id) return res.status(400).json({ error: 'session_id is required' });
311
+ // Entitlement: check session limit for new registrations
312
+ if (!sessions[session_id]) {
313
+ const sessionCount = Object.keys(sessions).length;
314
+ const ent = checkEntitlement({ feature: 'telepty.multi_session', currentUsage: sessionCount });
315
+ if (!ent.allowed) {
316
+ console.log(`[ENTITLEMENT] Session limit reached (${sessionCount}/${ent.limit?.max || '?'}), tier: ${ent.tier}`);
317
+ return res.status(402).json({ error: ent.reason, upgrade_url: ent.upgrade_url, tier: ent.tier });
318
+ }
319
+ }
303
320
  // Idempotent: allow re-registration (update command/cwd, keep clients)
304
321
  if (sessions[session_id]) {
305
322
  const existing = sessions[session_id];
package/entitlement.js ADDED
@@ -0,0 +1,70 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+
7
+ const LICENSE_PATH = path.join(os.homedir(), '.aigentry', 'license.json');
8
+ const UPGRADE_URL = 'https://aigentry.dev/upgrade';
9
+
10
+ const FEATURES = {
11
+ 'telepty.core': { tiers: ['free', 'pro', 'team'] },
12
+ 'telepty.multi_session': { tiers: ['pro', 'team'], freeLimit: 3 },
13
+ 'telepty.remote_sessions': { tiers: ['pro', 'team'] },
14
+ 'telepty.team_broadcast': { tiers: ['team'] }
15
+ };
16
+
17
+ function readLicense() {
18
+ try {
19
+ if (fs.existsSync(LICENSE_PATH)) {
20
+ const data = JSON.parse(fs.readFileSync(LICENSE_PATH, 'utf8'));
21
+ // Grace period: expired Pro/Team gets 30 days before downgrade
22
+ if (data.expires_at) {
23
+ const expiry = new Date(data.expires_at).getTime();
24
+ const grace = 30 * 24 * 60 * 60 * 1000;
25
+ if (Date.now() > expiry + grace) {
26
+ return { tier: 'free', expired: true };
27
+ }
28
+ }
29
+ return { tier: data.tier || 'free', expired: false };
30
+ }
31
+ } catch {}
32
+ return { tier: 'free', expired: false };
33
+ }
34
+
35
+ function checkEntitlement({ feature, currentUsage }) {
36
+ const def = FEATURES[feature];
37
+ if (!def) return { allowed: true, tier: 'free', reason: 'Unknown feature — allowing' };
38
+
39
+ const license = readLicense();
40
+ const tier = license.tier;
41
+
42
+ // Feature available on this tier
43
+ if (def.tiers.includes(tier)) {
44
+ return { allowed: true, tier, upgrade_url: UPGRADE_URL };
45
+ }
46
+
47
+ // Free tier with limit
48
+ if (tier === 'free' && def.freeLimit != null) {
49
+ const current = currentUsage || 0;
50
+ if (current < def.freeLimit) {
51
+ return { allowed: true, tier, limit: { current, max: def.freeLimit } };
52
+ }
53
+ return {
54
+ allowed: false, tier,
55
+ reason: `Free limit reached: ${current}/${def.freeLimit}. Upgrade to Pro for unlimited.`,
56
+ upgrade_url: UPGRADE_URL,
57
+ limit: { current, max: def.freeLimit }
58
+ };
59
+ }
60
+
61
+ // Not available on this tier
62
+ const requiredTier = def.tiers[0];
63
+ return {
64
+ allowed: false, tier,
65
+ reason: `Requires ${requiredTier.charAt(0).toUpperCase() + requiredTier.slice(1)} tier`,
66
+ upgrade_url: UPGRADE_URL
67
+ };
68
+ }
69
+
70
+ module.exports = { checkEntitlement, readLicense, LICENSE_PATH, FEATURES };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dmsdc-ai/aigentry-telepty",
3
- "version": "0.1.62",
3
+ "version": "0.1.63",
4
4
  "main": "daemon.js",
5
5
  "bin": {
6
6
  "aigentry-telepty": "install.js",