chainlesschain 0.161.11 → 0.162.0

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 (184) hide show
  1. package/package.json +4 -1
  2. package/src/assets/web-panel/.build-hash +1 -1
  3. package/src/assets/web-panel/assets/{AIOps-Cv7DnNSv.js → AIOps-Dn8q-Cu4.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-B1m9njbz.js → ActionButton-BePEVs4O.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-CU5lrpIq.js → Analytics-CKp-5Gi0.js} +1 -1
  6. package/src/assets/web-panel/assets/AppLayout-BSudqld0.js +3 -0
  7. package/src/assets/web-panel/assets/{AppLayout-BpbfrB7k.css → AppLayout-DBgtkmP7.css} +1 -1
  8. package/src/assets/web-panel/assets/{Audit-DUkzhAbX.js → Audit-iefpSeRG.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-L1i5rG4e.js → Backup-Ce43ZIhD.js} +1 -1
  10. package/src/assets/web-panel/assets/BaseInput-BvGiwRav.js +1 -0
  11. package/src/assets/web-panel/assets/{Chat-ySuja9_N.js → Chat-BAaQg-Oc.js} +1 -1
  12. package/src/assets/web-panel/assets/{Checkbox-5jHkbotn.js → Checkbox-Dvn8uxKO.js} +1 -1
  13. package/src/assets/web-panel/assets/{Codegen-CpTSti2I.js → Codegen-CTcZCayj.js} +1 -1
  14. package/src/assets/web-panel/assets/{Col-C5SJSGcO.js → Col-CBFpGgb5.js} +1 -1
  15. package/src/assets/web-panel/assets/{Community-0PMz9hPX.js → Community-DmCYaV9M.js} +1 -1
  16. package/src/assets/web-panel/assets/Compact-DnOD5uMF.js +1 -0
  17. package/src/assets/web-panel/assets/{Compliance-BEVqh-i7.js → Compliance-u53e9lg-.js} +1 -1
  18. package/src/assets/web-panel/assets/{Cowork-C_UEYiR5.js → Cowork-CL5ohuXH.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cron-B4f0_E2W.js → Cron-DubcmRgt.js} +1 -1
  20. package/src/assets/web-panel/assets/{Crosschain-CZT6FKhl.js → Crosschain-BWXcUXc3.js} +1 -1
  21. package/src/assets/web-panel/assets/{DID-LDSJdg_E.js → DID-Cw0i8WjE.js} +1 -1
  22. package/src/assets/web-panel/assets/{Dashboard-CczHBzZy.js → Dashboard-BbofvIWH.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dropdown-KJXQPCdN.js → Dropdown-DN6ur5xV.js} +1 -1
  24. package/src/assets/web-panel/assets/Federation-CA-rIdkY.js +1 -0
  25. package/src/assets/web-panel/assets/{FormItemContext-CenKDKRv.js → FormItemContext-D_HGU4aA.js} +1 -1
  26. package/src/assets/web-panel/assets/{Git-Ck3yvIOj.js → Git-CBWFs6Uk.js} +1 -1
  27. package/src/assets/web-panel/assets/{Governance-C-Wqvp5q.js → Governance-Cx6PTC43.js} +1 -1
  28. package/src/assets/web-panel/assets/{Inference-D1Q4GQsE.js → Inference-Ubk1VstS.js} +1 -1
  29. package/src/assets/web-panel/assets/{KnowledgeGraph-6-id5kqA.js → KnowledgeGraph-DTB10MYO.js} +1 -1
  30. package/src/assets/web-panel/assets/{Logs-BIrBldyi.js → Logs-Tfk4FBVC.js} +1 -1
  31. package/src/assets/web-panel/assets/{Marketplace-DuEYH2k1.js → Marketplace-BAsztG4O.js} +1 -1
  32. package/src/assets/web-panel/assets/{McpTools-CJyrjpOa.js → McpTools-DliPfvuK.js} +1 -1
  33. package/src/assets/web-panel/assets/{Memory-25VBuGGA.js → Memory-hkw6Xwyv.js} +1 -1
  34. package/src/assets/web-panel/assets/{MobileBridge-DDepR_yK.js → MobileBridge-eeNMFGj6.js} +1 -1
  35. package/src/assets/web-panel/assets/{Mtc-B52jkX63.js → Mtc-BP3Riowm.js} +1 -1
  36. package/src/assets/web-panel/assets/{MtcAudit-BimrvH_0.js → MtcAudit-DvD7pE7V.js} +6 -6
  37. package/src/assets/web-panel/assets/Multisig-CPtc-fVL.js +7 -0
  38. package/src/assets/web-panel/assets/Multisig-kwPDnXnl.css +1 -0
  39. package/src/assets/web-panel/assets/{NLProgramming-D9sqtzgg.js → NLProgramming-1y__Epjg.js} +1 -1
  40. package/src/assets/web-panel/assets/{Notes-BtaoLR6l.js → Notes-CEvJqMP8.js} +1 -1
  41. package/src/assets/web-panel/assets/{NotificationSettings-DGc4Pnhc.js → NotificationSettings-wmCYm99r.js} +1 -1
  42. package/src/assets/web-panel/assets/{Organization-CH8YH1WC.js → Organization-CSoz8gGr.js} +1 -1
  43. package/src/assets/web-panel/assets/{Overflow-BEfCQNlN.js → Overflow-kf1mnFrM.js} +1 -1
  44. package/src/assets/web-panel/assets/{OverrideContext-DYxbd09I.js → OverrideContext-x9ZzjLwk.js} +1 -1
  45. package/src/assets/web-panel/assets/{P2P-Bp_HTJ00.js → P2P-C4GfXJ8Z.js} +1 -1
  46. package/src/assets/web-panel/assets/{Permissions-UHQF_4gL.js → Permissions-CW1Mn95X.js} +1 -1
  47. package/src/assets/web-panel/assets/{Pipeline-yCVADMbj.js → Pipeline-CM1ppddn.js} +1 -1
  48. package/src/assets/web-panel/assets/{Privacy-C1oNG_1P.js → Privacy-DuAY5fEm.js} +1 -1
  49. package/src/assets/web-panel/assets/{ProjectInit-DMW2d6Cb.js → ProjectInit-C72779b6.js} +2 -2
  50. package/src/assets/web-panel/assets/{ProjectSettings-5G8pJeC_.js → ProjectSettings-CAilXWam.js} +1 -1
  51. package/src/assets/web-panel/assets/Projects-0OrfK90x.css +1 -0
  52. package/src/assets/web-panel/assets/Projects-DTIlVTkF.js +1 -0
  53. package/src/assets/web-panel/assets/{Providers-CL_oE5tl.js → Providers-Cf6Yv8bu.js} +1 -1
  54. package/src/assets/web-panel/assets/{QuickAsk-BV38kJpn.js → QuickAsk-CK1_VVzw.js} +1 -1
  55. package/src/assets/web-panel/assets/{Recommend-SAj9Q6gO.js → Recommend-QBC7KgvR.js} +1 -1
  56. package/src/assets/web-panel/assets/{Reputation-1kCD0gaB.js → Reputation-ju6lWzMM.js} +1 -1
  57. package/src/assets/web-panel/assets/Row-DQwipGEm.js +1 -0
  58. package/src/assets/web-panel/assets/{RssFeed-CAJCjHjh.js → RssFeed-DkLGmeLW.js} +1 -1
  59. package/src/assets/web-panel/assets/{Search-cvcqK6k5.js → Search-YYhitzkt.js} +1 -1
  60. package/src/assets/web-panel/assets/{Security-CQg7wTKN.js → Security-DEllaBqe.js} +1 -1
  61. package/src/assets/web-panel/assets/{Services-CsUFvNqx.js → Services-BgPHBq9S.js} +2 -2
  62. package/src/assets/web-panel/assets/{Skeleton-xBd6cpeg.js → Skeleton-DHEyJI7c.js} +1 -1
  63. package/src/assets/web-panel/assets/{Skills-CaroVcXP.js → Skills-c212vgf3.js} +1 -1
  64. package/src/assets/web-panel/assets/{Sla-BtpCM7Ko.js → Sla-COqhw_no.js} +1 -1
  65. package/src/assets/web-panel/assets/{SpeechSettings-BqMJfygi.js → SpeechSettings-Bmds06su.js} +1 -1
  66. package/src/assets/web-panel/assets/{SyncSettings-CZouEnTT.js → SyncSettings-Bx1_A34Y.js} +1 -1
  67. package/src/assets/web-panel/assets/{Tasks-CXRSaKbt.js → Tasks-B0Lo_9aN.js} +1 -1
  68. package/src/assets/web-panel/assets/{Templates-DJY8TQ5i.js → Templates-D0RcpfuW.js} +1 -1
  69. package/src/assets/web-panel/assets/{Tenant-BvR-WVDe.js → Tenant-G0yVctLy.js} +1 -1
  70. package/src/assets/web-panel/assets/Terminal-DqgLtK3c.js +3 -0
  71. package/src/assets/web-panel/assets/Terminal-G1DrFtKr.css +1 -0
  72. package/src/assets/web-panel/assets/{Tokens-BvsTsita.js → Tokens-Cdt0aqCc.js} +1 -1
  73. package/src/assets/web-panel/assets/{Trigger-BpAXwpX5.js → Trigger-DFbfZFuJ.js} +1 -1
  74. package/src/assets/web-panel/assets/{Trust-CQn3-pr1.js → Trust-BQXsWPz6.js} +1 -1
  75. package/src/assets/web-panel/assets/{UkeySign-mdcVVgmW.js → UkeySign-DBem5Xkj.js} +1 -1
  76. package/src/assets/web-panel/assets/{VideoEditing-B64a6mxs.js → VideoEditing-B1yipKKv.js} +1 -1
  77. package/src/assets/web-panel/assets/{Wallet-CD6sh797.js → Wallet-iPJtU5QZ.js} +4 -4
  78. package/src/assets/web-panel/assets/{WebAuthn-CBMAhuJk.js → WebAuthn-HLb9srUL.js} +1 -1
  79. package/src/assets/web-panel/assets/{WorkflowEditor-BL2e4pEf.js → WorkflowEditor-BSqXL8GD.js} +1 -1
  80. package/src/assets/web-panel/assets/addon-fit-CK6X9sAG.js +1 -0
  81. package/src/assets/web-panel/assets/{chat-D0R75-Vf.js → chat-D_J-ZVBh.js} +1 -1
  82. package/src/assets/web-panel/assets/{collapseMotion-CZQhO80h.js → collapseMotion-CjFH_Jop.js} +1 -1
  83. package/src/assets/web-panel/assets/{colors-Z5R3KUYW.js → colors-B126JcB4.js} +1 -1
  84. package/src/assets/web-panel/assets/{compact-item-Buyen0aR.js → compact-item-Bw5EEKw4.js} +1 -1
  85. package/src/assets/web-panel/assets/{createContext-D9l1X-OX.js → createContext-xLJXI46O.js} +1 -1
  86. package/src/assets/web-panel/assets/{echarts-TwhxblyG.js → echarts-Bq-n0MtJ.js} +1 -1
  87. package/src/assets/web-panel/assets/{hasIn-DV4wtumO.js → hasIn-K8QCNlyy.js} +1 -1
  88. package/src/assets/web-panel/assets/{icons-Q99MnhG8.js → icons-NT6gy8Ee.js} +2 -2
  89. package/src/assets/web-panel/assets/{index--pPzkOu2.js → index-85lab9HJ.js} +1 -1
  90. package/src/assets/web-panel/assets/{index-BSXvEQFS.js → index-B9ztiTnX.js} +1 -1
  91. package/src/assets/web-panel/assets/index-BEYypR_d.js +1 -0
  92. package/src/assets/web-panel/assets/{index-C4m8PjHn.js → index-BQ_NIMQS.js} +1 -1
  93. package/src/assets/web-panel/assets/{index-BAf0-ayI.js → index-BSgvvXgq.js} +2 -2
  94. package/src/assets/web-panel/assets/{index-RpJYGfg0.js → index-BWUG9yVI.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-Cs7GKmFk.js → index-B_PqXUkT.js} +6 -6
  96. package/src/assets/web-panel/assets/index-BaXXyyT0.js +21 -0
  97. package/src/assets/web-panel/assets/index-Bptjq-wQ.js +1 -0
  98. package/src/assets/web-panel/assets/{index-CpemtT_B.js → index-BsbKjK1o.js} +2 -2
  99. package/src/assets/web-panel/assets/{index-Cs2QeQFi.js → index-Bv9odL87.js} +2 -2
  100. package/src/assets/web-panel/assets/{index-0P8ZPtKp.js → index-C2CgD7TN.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-CprQ5h5e.js → index-COCq6xuZ.js} +1 -1
  102. package/src/assets/web-panel/assets/index-CVeuzjxG.js +55 -0
  103. package/src/assets/web-panel/assets/index-CWBajOcE.js +3 -0
  104. package/src/assets/web-panel/assets/{index-CT46jhtC.js → index-CvAey6iD.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-Z-i_sfIN.js → index-DFPVNSby.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-CoV9UGnc.js → index-DFwF_JeQ.js} +2 -2
  107. package/src/assets/web-panel/assets/{index-DO9gd9wz.js → index-DLZZi1Km.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-BRWO_MUe.js → index-DY5Oc3z6.js} +4 -4
  109. package/src/assets/web-panel/assets/index-DZWfmuM2.js +1 -0
  110. package/src/assets/web-panel/assets/{index-CRvOaSuy.js → index-DZcij18m.js} +2 -2
  111. package/src/assets/web-panel/assets/{index-BLDanIEV.js → index-Da7bZewp.js} +1 -1
  112. package/src/assets/web-panel/assets/index-DdMzLeDn.js +1 -0
  113. package/src/assets/web-panel/assets/index-Dj8BNg-z.js +1 -0
  114. package/src/assets/web-panel/assets/index-DlX09aXr.js +1 -0
  115. package/src/assets/web-panel/assets/{index-CHs1Rtgi.js → index-DrfiELFa.js} +2 -2
  116. package/src/assets/web-panel/assets/{index-DMcK4Son.js → index-Dt0Tp8TS.js} +1 -1
  117. package/src/assets/web-panel/assets/index-DvNCyE3A.js +13 -0
  118. package/src/assets/web-panel/assets/{index-vSjbNv8F.js → index-LZYGhjVr.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-AXmFZTsT.js → index-fMwtau_-.js} +2 -2
  120. package/src/assets/web-panel/assets/{index-D_g5wyjB.js → index-mqHxeING.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-DBBX0xZN.js → index-mzNWO8Yy.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-UAkLBWLE.js → index-p3d5_s7V.js} +2 -2
  123. package/src/assets/web-panel/assets/{index-BBFrPlY4.js → index-qJbfToeQ.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-DtRv6Tqv.js → index-sAfwWh0R.js} +8 -8
  125. package/src/assets/web-panel/assets/index-xJkPbv0L.js +12 -0
  126. package/src/assets/web-panel/assets/index-yhA-qP4Y.js +1 -0
  127. package/src/assets/web-panel/assets/index-zEr6BW9X.js +1 -0
  128. package/src/assets/web-panel/assets/{initDefaultProps-C5tyEoyp.js → initDefaultProps-CacwmJft.js} +1 -1
  129. package/src/assets/web-panel/assets/{motion-By5VpSMZ.js → motion-BWrP7Lz6.js} +2 -2
  130. package/src/assets/web-panel/assets/{move-CNRFLi9k.js → move-imnbiy1n.js} +1 -1
  131. package/src/assets/web-panel/assets/{omit-DYc7DRhT.js → omit-BlaOv91I.js} +1 -1
  132. package/src/assets/web-panel/assets/{pickAttrs-D667btUF.js → pickAttrs-DUGKp0ro.js} +1 -1
  133. package/src/assets/web-panel/assets/{placementArrow-Ce-Y2-Qq.js → placementArrow-P0ZA4yAK.js} +1 -1
  134. package/src/assets/web-panel/assets/responsiveObserve-CZz-M9cI.js +1 -0
  135. package/src/assets/web-panel/assets/{slide-CyKGKfJ8.js → slide-BEnICwoX.js} +1 -1
  136. package/src/assets/web-panel/assets/{statusUtils-DffXAKmJ.js → statusUtils-DdYKRSiH.js} +1 -1
  137. package/src/assets/web-panel/assets/{styleChecker-CeeSLisW.js → styleChecker-DgUxvOQt.js} +1 -1
  138. package/src/assets/web-panel/assets/useFlexGapSupport-BUEmeWdR.js +1 -0
  139. package/src/assets/web-panel/assets/{useFs--Vkrwu51.js → useFs-BXsj6Z5e.js} +1 -1
  140. package/src/assets/web-panel/assets/{useMergedState-D3PX36GT.js → useMergedState-O7QXt4P5.js} +1 -1
  141. package/src/assets/web-panel/assets/{useRefs-opfcK7Pm.js → useRefs-0J6m8UWN.js} +1 -1
  142. package/src/assets/web-panel/assets/{useState-Cp5WlLTS.js → useState-CSzR8F8O.js} +1 -1
  143. package/src/assets/web-panel/assets/{vendor-BVLz_z7V.js → vendor-M5lGV-wr.js} +1 -1
  144. package/src/assets/web-panel/assets/{vnode-ChnNrJNw.js → vnode-B3Pp583L.js} +1 -1
  145. package/src/assets/web-panel/assets/xterm-BZcWGsqw.js +9 -0
  146. package/src/assets/web-panel/assets/xterm-DFuMZ0ql.css +1 -0
  147. package/src/assets/web-panel/assets/{zoom-DE1Teifl.js → zoom-D-LYfiQh.js} +1 -1
  148. package/src/assets/web-panel/index.html +3 -3
  149. package/src/commands/crosschain.js +403 -1
  150. package/src/commands/pair.js +291 -0
  151. package/src/gateways/terminal/PtyManager.js +248 -0
  152. package/src/gateways/terminal/RingBuffer.js +61 -0
  153. package/src/gateways/terminal/terminal-handlers.js +121 -0
  154. package/src/gateways/ws/topic-handler-attachment.js +211 -0
  155. package/src/index.js +2 -0
  156. package/src/lib/cross-chain-mtc.js +275 -6
  157. package/src/lib/cross-chain.js +48 -2
  158. package/src/lib/lan-pairing-preflight.js +425 -0
  159. package/src/lib/lan-pairing-tokens.js +264 -0
  160. package/src/runtime/agent-runtime.js +33 -0
  161. package/src/assets/web-panel/assets/AppLayout-CrQBcjC1.js +0 -3
  162. package/src/assets/web-panel/assets/BaseInput-CQSPRZVh.js +0 -1
  163. package/src/assets/web-panel/assets/Compact-CwrJK_94.js +0 -1
  164. package/src/assets/web-panel/assets/Federation-Bu085RVF.js +0 -1
  165. package/src/assets/web-panel/assets/Multisig-D-IuEDLa.css +0 -1
  166. package/src/assets/web-panel/assets/Multisig-DWsGuuyz.js +0 -1
  167. package/src/assets/web-panel/assets/Projects-BpLpgCga.js +0 -1
  168. package/src/assets/web-panel/assets/Projects-C34BWmmy.css +0 -1
  169. package/src/assets/web-panel/assets/Row-CUF6ofec.js +0 -1
  170. package/src/assets/web-panel/assets/index-BBvRNeKy.js +0 -1
  171. package/src/assets/web-panel/assets/index-BJzerWCW.js +0 -1
  172. package/src/assets/web-panel/assets/index-BbZ_pXN3.js +0 -1
  173. package/src/assets/web-panel/assets/index-BloaIh8i.js +0 -1
  174. package/src/assets/web-panel/assets/index-C1u_F_WR.js +0 -12
  175. package/src/assets/web-panel/assets/index-CHPsWSeL.js +0 -1
  176. package/src/assets/web-panel/assets/index-CLk9-Adx.js +0 -13
  177. package/src/assets/web-panel/assets/index-Cyal1Os0.js +0 -1
  178. package/src/assets/web-panel/assets/index-DYq0yh8a.js +0 -1
  179. package/src/assets/web-panel/assets/index-KvlSpGws.js +0 -21
  180. package/src/assets/web-panel/assets/index-WbY1RGjg.js +0 -3
  181. package/src/assets/web-panel/assets/index-cTf60nCj.js +0 -1
  182. package/src/assets/web-panel/assets/index-ry4j00pG.js +0 -55
  183. package/src/assets/web-panel/assets/responsiveObserve-BzzJzAds.js +0 -1
  184. package/src/assets/web-panel/assets/useFlexGapSupport-CCzODKgI.js +0 -1
@@ -0,0 +1,291 @@
1
+ /**
2
+ * `cc pair` — LAN pairing utilities. v0.1 ships only the `preflight`
3
+ * subcommand for Linux LAN-pairing diagnostics (#21 A.1 PR1).
4
+ *
5
+ * Future:
6
+ * cc pair init — headless mode pairing initiator (PR2)
7
+ * cc pair accept --qr ... — accept a scanned QR from CLI (PR2)
8
+ */
9
+
10
+ import chalk from "chalk";
11
+ import { Command } from "commander";
12
+ import {
13
+ runPreflight,
14
+ firewallCommandTemplate,
15
+ detectFirewallTool,
16
+ STATUS,
17
+ } from "../lib/lan-pairing-preflight.js";
18
+ import {
19
+ addToken,
20
+ listTokens,
21
+ findToken,
22
+ revokeToken,
23
+ STATUS as TOKEN_STATUS,
24
+ } from "../lib/lan-pairing-tokens.js";
25
+
26
+ function statusBadge(s) {
27
+ switch (s) {
28
+ case STATUS.OK:
29
+ return chalk.green("✔ ok");
30
+ case STATUS.WARNING:
31
+ return chalk.yellow("⚠ warn");
32
+ case STATUS.BLOCKER:
33
+ return chalk.red("✖ blocker");
34
+ case STATUS.SKIP:
35
+ return chalk.gray("· skip");
36
+ default:
37
+ return s;
38
+ }
39
+ }
40
+
41
+ function printHumanReadable(report, showFirewall) {
42
+ console.log(chalk.bold("\n cc pair preflight\n"));
43
+ for (const c of report.checks) {
44
+ console.log(
45
+ ` ${statusBadge(c.status).padEnd(20)} ${chalk.bold(c.name.padEnd(22))} ${c.detail}`,
46
+ );
47
+ }
48
+ console.log();
49
+ console.log(
50
+ chalk.bold(
51
+ ` summary: ${chalk.green(report.summary.ok + " ok")}, ` +
52
+ `${chalk.yellow(report.summary.warnings + " warning")}, ` +
53
+ `${chalk.red(report.summary.blockers + " blocker")}`,
54
+ ),
55
+ );
56
+
57
+ // Show firewall commands when there's a blocker on multicast_bind, or
58
+ // when --show-firewall is set.
59
+ const multicastBlocked = report.checks.some(
60
+ (c) => c.name === "multicast_bind" && c.status === STATUS.BLOCKER,
61
+ );
62
+ if (showFirewall || multicastBlocked) {
63
+ const tool = detectFirewallTool();
64
+ const tpl = firewallCommandTemplate(tool);
65
+ if (tpl) {
66
+ console.log();
67
+ console.log(chalk.bold(" Firewall fix (detected tool: " + tool + "):"));
68
+ console.log();
69
+ console.log(
70
+ tpl
71
+ .split("\n")
72
+ .map((l) => " " + l)
73
+ .join("\n"),
74
+ );
75
+ }
76
+ }
77
+ console.log();
78
+ }
79
+
80
+ function tokenStatusBadge(s) {
81
+ switch (s) {
82
+ case TOKEN_STATUS.PENDING:
83
+ return chalk.cyan("pending");
84
+ case TOKEN_STATUS.CONSUMED:
85
+ return chalk.green("consumed");
86
+ case TOKEN_STATUS.REVOKED:
87
+ return chalk.gray("revoked");
88
+ case TOKEN_STATUS.EXPIRED:
89
+ return chalk.yellow("expired");
90
+ default:
91
+ return s;
92
+ }
93
+ }
94
+
95
+ function relTime(ms) {
96
+ const diff = ms - Date.now();
97
+ const absMin = Math.abs(diff) / 60_000;
98
+ if (absMin < 60) {
99
+ return diff > 0
100
+ ? `${Math.round(absMin)} min from now`
101
+ : `${Math.round(absMin)} min ago`;
102
+ }
103
+ const absH = absMin / 60;
104
+ if (absH < 24) {
105
+ return diff > 0
106
+ ? `${Math.round(absH)}h from now`
107
+ : `${Math.round(absH)}h ago`;
108
+ }
109
+ return diff > 0
110
+ ? `${Math.round(absH / 24)}d from now`
111
+ : `${Math.round(absH / 24)}d ago`;
112
+ }
113
+
114
+ export function registerPairCommand(program) {
115
+ const pair = program
116
+ .command("pair")
117
+ .description(
118
+ "LAN pairing utilities (preflight diagnostics + pairing token issuance)",
119
+ );
120
+
121
+ pair
122
+ .command("preflight")
123
+ .description(
124
+ "Diagnose Linux LAN pairing — checks multicast bind, port 5353 holders, firewall hints (#21 A.1)",
125
+ )
126
+ .option("--json", "Machine-readable JSON output")
127
+ .option(
128
+ "--show-firewall",
129
+ "Always print firewall fix commands (default: only on blocker)",
130
+ )
131
+ .option(
132
+ "--multicast-bind-timeout-ms <ms>",
133
+ "Timeout for multicast bind probe",
134
+ (v) => parseInt(v, 10),
135
+ )
136
+ .action(async (options) => {
137
+ const report = await runPreflight({
138
+ multicastBindTimeoutMs: options.multicastBindTimeoutMs,
139
+ });
140
+
141
+ if (options.json) {
142
+ console.log(JSON.stringify(report, null, 2));
143
+ process.exitCode = report.exitCode;
144
+ return;
145
+ }
146
+
147
+ printHumanReadable(report, options.showFirewall);
148
+ process.exitCode = report.exitCode;
149
+ });
150
+
151
+ // ─── pair token (PR2) ─────────────────────────────────────────
152
+
153
+ const token = pair
154
+ .command("token")
155
+ .description(
156
+ "Manage pairing tokens (issue/list/show/revoke). Tokens are compatible with desktop device-pairing-handler QR shape.",
157
+ );
158
+
159
+ token
160
+ .command("generate")
161
+ .description("Issue a new 6-digit pairing token for a given DID")
162
+ .requiredOption("--did <did>", "PC DID (the token will pair this DID)")
163
+ .option("--name <name>", "Friendly device name (default: hostname)")
164
+ .option(
165
+ "--device-id <id>",
166
+ "Override deviceId (default: cli-<hostname>-<ts>)",
167
+ )
168
+ .option("--json", "Machine-readable JSON output (full qrData + metadata)")
169
+ .action((options) => {
170
+ const deviceInfo = (() => {
171
+ const out = {};
172
+ if (options.name) out.name = options.name;
173
+ if (options.deviceId) out.deviceId = options.deviceId;
174
+ return Object.keys(out).length ? out : undefined;
175
+ })();
176
+ const t = addToken({ did: options.did, deviceInfo });
177
+ if (options.json) {
178
+ console.log(JSON.stringify(t, null, 2));
179
+ return;
180
+ }
181
+ console.log();
182
+ console.log(chalk.bold(" ✓ Pairing token issued\n"));
183
+ console.log(" code: " + chalk.bold.cyan(t.code));
184
+ console.log(" did: " + t.qrData.did);
185
+ console.log(" device: " + (t.qrData.deviceInfo?.name || "(none)"));
186
+ console.log(" expires: " + relTime(t.expiresAtMs));
187
+ console.log();
188
+ console.log(
189
+ chalk.gray(" QR data (paste into mobile scanner / Electron handler):"),
190
+ );
191
+ console.log();
192
+ console.log(" " + JSON.stringify(t.qrData));
193
+ console.log();
194
+ });
195
+
196
+ token
197
+ .command("list")
198
+ .description("List pairing tokens")
199
+ .option(
200
+ "--status <s>",
201
+ "Filter by status (pending/consumed/revoked/expired)",
202
+ )
203
+ .option("--did <did>", "Filter by DID")
204
+ .option("--json", "Machine-readable JSON output")
205
+ .action((options) => {
206
+ const tokens = listTokens({ status: options.status, did: options.did });
207
+ if (options.json) {
208
+ console.log(JSON.stringify({ tokens }, null, 2));
209
+ return;
210
+ }
211
+ if (tokens.length === 0) {
212
+ console.log(chalk.gray("\n No pairing tokens.\n"));
213
+ return;
214
+ }
215
+ console.log();
216
+ console.log(chalk.bold(" Pairing tokens:\n"));
217
+ console.log(
218
+ " " +
219
+ chalk.gray("code").padEnd(10) +
220
+ chalk.gray("status").padEnd(14) +
221
+ chalk.gray("did").padEnd(30) +
222
+ chalk.gray("expires"),
223
+ );
224
+ for (const t of tokens) {
225
+ console.log(
226
+ " " +
227
+ chalk.bold(t.code).padEnd(20) +
228
+ tokenStatusBadge(t.status).padEnd(20) +
229
+ String(t.qrData?.did || "?")
230
+ .slice(0, 28)
231
+ .padEnd(30) +
232
+ relTime(t.expiresAtMs),
233
+ );
234
+ }
235
+ console.log();
236
+ });
237
+
238
+ token
239
+ .command("show <code>")
240
+ .description("Show a single pairing token by code")
241
+ .option("--json", "Machine-readable JSON output")
242
+ .action((code, options) => {
243
+ const t = findToken(code);
244
+ if (!t) {
245
+ console.error(chalk.red(`✖ token not found: ${code}`));
246
+ process.exitCode = 2;
247
+ return;
248
+ }
249
+ if (options.json) {
250
+ console.log(JSON.stringify(t, null, 2));
251
+ return;
252
+ }
253
+ console.log();
254
+ console.log(chalk.bold(` Token ${chalk.cyan(t.code)}\n`));
255
+ console.log(" status: " + tokenStatusBadge(t.status));
256
+ console.log(" did: " + t.qrData.did);
257
+ console.log(
258
+ " device: " +
259
+ (t.qrData.deviceInfo?.name || "(none)") +
260
+ " (" +
261
+ (t.qrData.deviceInfo?.platform || "?") +
262
+ ")",
263
+ );
264
+ console.log(" created: " + new Date(t.createdAtMs).toISOString());
265
+ console.log(" expires: " + new Date(t.expiresAtMs).toISOString());
266
+ if (t.consumedAtMs)
267
+ console.log(" consumed: " + new Date(t.consumedAtMs).toISOString());
268
+ if (t.revokedAtMs)
269
+ console.log(" revoked: " + new Date(t.revokedAtMs).toISOString());
270
+ console.log();
271
+ });
272
+
273
+ token
274
+ .command("revoke <code>")
275
+ .description("Revoke a pending pairing token")
276
+ .option("--json", "Machine-readable JSON output")
277
+ .action((code, options) => {
278
+ const r = revokeToken(code);
279
+ if (options.json) {
280
+ console.log(JSON.stringify(r, null, 2));
281
+ if (!r.revoked) process.exitCode = 2;
282
+ return;
283
+ }
284
+ if (r.revoked) {
285
+ console.log(chalk.green(`\n ✓ Token ${code} revoked.\n`));
286
+ } else {
287
+ console.error(chalk.red(`\n ✖ Cannot revoke: ${r.reason}\n`));
288
+ process.exitCode = 2;
289
+ }
290
+ });
291
+ }
@@ -0,0 +1,248 @@
1
+ /**
2
+ * PtyManager — ESM mirror of `desktop-app-vue/src/main/terminal/PtyManager.js`.
3
+ *
4
+ * Keep both copies in sync. The desktop copy targets Electron's CJS main
5
+ * process; this one targets `cc ui` (Node ESM) so `cc ui` users also get
6
+ * remote-terminal support via the same WS protocol.
7
+ *
8
+ * Design notes — see docs/design/Android_Remote_Terminal_Plan_A.md §4:
9
+ * - node-pty loaded lazily so the module loads without the native
10
+ * binding installed (tests, headless CI, machines without it).
11
+ * - Per-session RingBuffer (256 KiB default, memory-only — no disk
12
+ * persistence: terminal stdout often contains secrets).
13
+ * - EventEmitter fans out `stdout` / `exit` so handlers translate to
14
+ * WS envelopes without coupling.
15
+ * - Idle kill default 24h, resets on stdin OR stdout activity.
16
+ * - Shell whitelist enforced at create() — unknown shell → throws
17
+ * `shell_not_allowed`.
18
+ */
19
+
20
+ import EventEmitter from "events";
21
+ import { randomUUID } from "crypto";
22
+ import { createRequire } from "module";
23
+ import { RingBuffer } from "./RingBuffer.js";
24
+
25
+ const require = createRequire(import.meta.url);
26
+
27
+ export const DEFAULT_CONFIG = Object.freeze({
28
+ shellWhitelist: ["pwsh", "cmd", "bash", "wsl"],
29
+ defaultShell: process.platform === "win32" ? "pwsh" : "bash",
30
+ defaultCwd: null,
31
+ maxConcurrentSessions: 8,
32
+ ringBufferBytes: 256 * 1024,
33
+ idleKillMs: 24 * 60 * 60 * 1000,
34
+ });
35
+
36
+ function resolveShellCmd(shell) {
37
+ if (process.platform === "win32") {
38
+ if (shell === "cmd") return process.env.ComSpec || "cmd.exe";
39
+ if (shell === "pwsh") return "pwsh.exe";
40
+ if (shell === "wsl") return "wsl.exe";
41
+ if (shell === "bash") return "bash.exe";
42
+ }
43
+ return shell;
44
+ }
45
+
46
+ class PtySession {
47
+ constructor({ id, shell, cwd, cols, rows, proc, createdAt, ringBuffer }) {
48
+ this.id = id;
49
+ this.shell = shell;
50
+ this.cwd = cwd;
51
+ this.cols = cols;
52
+ this.rows = rows;
53
+ this.proc = proc;
54
+ this.createdAt = createdAt;
55
+ this.ringBuffer = ringBuffer;
56
+ this.alive = true;
57
+ this.exitCode = null;
58
+ this.signal = null;
59
+ this.lastActivityAt = createdAt;
60
+ }
61
+ }
62
+
63
+ export class PtyManager extends EventEmitter {
64
+ constructor(opts = {}) {
65
+ super();
66
+ this.config = { ...DEFAULT_CONFIG, ...(opts.config || {}) };
67
+ this._deps = {
68
+ loadNodePty: opts._deps?.loadNodePty || (() => require("node-pty")),
69
+ now: opts._deps?.now || (() => Date.now()),
70
+ };
71
+ this._sessions = new Map();
72
+ this._idleTimer = null;
73
+ this._stopped = false;
74
+ }
75
+
76
+ create(req = {}) {
77
+ if (this._stopped) throw new Error("pty_manager_stopped");
78
+ if (this._sessions.size >= this.config.maxConcurrentSessions) {
79
+ throw new Error("max_concurrent_sessions_exceeded");
80
+ }
81
+ const shell = req.shell || this.config.defaultShell;
82
+ if (!this.config.shellWhitelist.includes(shell)) {
83
+ throw new Error("shell_not_allowed");
84
+ }
85
+
86
+ let pty;
87
+ try {
88
+ pty = this._deps.loadNodePty();
89
+ } catch {
90
+ const err = new Error("pty_native_unavailable");
91
+ err.cause = "node-pty failed to load (native binding missing)";
92
+ throw err;
93
+ }
94
+
95
+ const cwd = req.cwd || this.config.defaultCwd || process.cwd();
96
+ const cols = Number.isFinite(req.cols) ? req.cols : 80;
97
+ const rows = Number.isFinite(req.rows) ? req.rows : 24;
98
+ const cmd = resolveShellCmd(shell);
99
+
100
+ const proc = pty.spawn(cmd, [], {
101
+ name: "xterm-256color",
102
+ cols,
103
+ rows,
104
+ cwd,
105
+ env: { ...process.env, ...(req.env || {}) },
106
+ });
107
+
108
+ const sessionId = randomUUID();
109
+ const session = new PtySession({
110
+ id: sessionId,
111
+ shell,
112
+ cwd,
113
+ cols,
114
+ rows,
115
+ proc,
116
+ createdAt: this._deps.now(),
117
+ ringBuffer: new RingBuffer({ maxBytes: this.config.ringBufferBytes }),
118
+ });
119
+ this._sessions.set(sessionId, session);
120
+
121
+ proc.onData((data) => {
122
+ const buf = typeof data === "string" ? Buffer.from(data, "utf-8") : data;
123
+ const seq = session.ringBuffer.push(buf);
124
+ session.lastActivityAt = this._deps.now();
125
+ this.emit("stdout", { sessionId, data: buf, seq });
126
+ });
127
+
128
+ proc.onExit(({ exitCode, signal }) => {
129
+ session.alive = false;
130
+ session.exitCode = exitCode ?? null;
131
+ session.signal = signal ?? null;
132
+ this.emit("exit", {
133
+ sessionId,
134
+ exitCode: session.exitCode,
135
+ signal: session.signal,
136
+ });
137
+ setTimeout(() => {
138
+ if (this._sessions.get(sessionId) === session) {
139
+ this._sessions.delete(sessionId);
140
+ }
141
+ }, 60 * 1000).unref?.();
142
+ });
143
+
144
+ this._ensureIdleSweeper();
145
+
146
+ return {
147
+ sessionId,
148
+ pid: proc.pid,
149
+ shell,
150
+ createdAt: session.createdAt,
151
+ };
152
+ }
153
+
154
+ write(sessionId, data) {
155
+ const session = this._sessions.get(sessionId);
156
+ if (!session) throw new Error("session_not_found");
157
+ if (!session.alive) throw new Error("session_not_alive");
158
+ const str = Buffer.isBuffer(data) ? data.toString("utf-8") : data;
159
+ session.proc.write(str);
160
+ session.lastActivityAt = this._deps.now();
161
+ }
162
+
163
+ resize(sessionId, cols, rows) {
164
+ const session = this._sessions.get(sessionId);
165
+ if (!session) throw new Error("session_not_found");
166
+ if (!session.alive) throw new Error("session_not_alive");
167
+ if (!Number.isFinite(cols) || !Number.isFinite(rows)) {
168
+ throw new Error("invalid_dimensions");
169
+ }
170
+ session.proc.resize(cols, rows);
171
+ session.cols = cols;
172
+ session.rows = rows;
173
+ }
174
+
175
+ close(sessionId) {
176
+ const session = this._sessions.get(sessionId);
177
+ if (!session) throw new Error("session_not_found");
178
+ if (session.alive) {
179
+ try {
180
+ session.proc.kill();
181
+ } catch {
182
+ /* onExit will mark dead */
183
+ }
184
+ }
185
+ }
186
+
187
+ list() {
188
+ const out = [];
189
+ for (const s of this._sessions.values()) {
190
+ out.push({
191
+ id: s.id,
192
+ shell: s.shell,
193
+ cwd: s.cwd,
194
+ createdAt: s.createdAt,
195
+ alive: s.alive,
196
+ lastSeq: s.ringBuffer.lastSeq,
197
+ });
198
+ }
199
+ return out;
200
+ }
201
+
202
+ history(sessionId, fromSeq = 0) {
203
+ const session = this._sessions.get(sessionId);
204
+ if (!session) throw new Error("session_not_found");
205
+ return session.ringBuffer.since(fromSeq);
206
+ }
207
+
208
+ _ensureIdleSweeper() {
209
+ if (this._idleTimer) return;
210
+ this._idleTimer = setInterval(() => this._sweepIdle(), 60 * 60 * 1000);
211
+ this._idleTimer.unref?.();
212
+ }
213
+
214
+ _sweepIdle() {
215
+ const now = this._deps.now();
216
+ for (const session of this._sessions.values()) {
217
+ if (!session.alive) continue;
218
+ if (now - session.lastActivityAt > this.config.idleKillMs) {
219
+ try {
220
+ session.proc.kill();
221
+ } catch {
222
+ /* onExit will clean up */
223
+ }
224
+ }
225
+ }
226
+ }
227
+
228
+ shutdown() {
229
+ this._stopped = true;
230
+ if (this._idleTimer) {
231
+ clearInterval(this._idleTimer);
232
+ this._idleTimer = null;
233
+ }
234
+ for (const session of this._sessions.values()) {
235
+ if (session.alive) {
236
+ try {
237
+ session.proc.kill();
238
+ } catch {
239
+ /* best effort */
240
+ }
241
+ }
242
+ }
243
+ }
244
+
245
+ get _sessionCount() {
246
+ return this._sessions.size;
247
+ }
248
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * RingBuffer — bounded byte-aware FIFO for PTY stdout chunks.
3
+ *
4
+ * ESM mirror of `desktop-app-vue/src/main/terminal/RingBuffer.js`. Both
5
+ * shells (Electron web-shell + `cc ui`) ship their own copy because the
6
+ * runtime module systems are incompatible (CJS vs ESM). Keep them in
7
+ * sync — future consolidation goes into a workspace package once the
8
+ * surface stabilises.
9
+ */
10
+
11
+ export class RingBuffer {
12
+ constructor(opts = {}) {
13
+ const maxBytes = opts.maxBytes ?? 256 * 1024;
14
+ if (typeof maxBytes !== "number" || maxBytes <= 0) {
15
+ throw new TypeError("RingBuffer: maxBytes must be a positive number");
16
+ }
17
+ this.maxBytes = maxBytes;
18
+ this._entries = [];
19
+ this._totalBytes = 0;
20
+ this._lastSeq = 0;
21
+ this._firstRetainedSeq = 1;
22
+ }
23
+
24
+ push(data) {
25
+ const buf = Buffer.isBuffer(data)
26
+ ? data
27
+ : typeof data === "string"
28
+ ? Buffer.from(data, "utf-8")
29
+ : Buffer.from(data);
30
+ this._lastSeq += 1;
31
+ const seq = this._lastSeq;
32
+ this._entries.push({ seq, data: buf });
33
+ this._totalBytes += buf.byteLength;
34
+ this._evictUntilUnderCap();
35
+ return seq;
36
+ }
37
+
38
+ _evictUntilUnderCap() {
39
+ while (this._totalBytes > this.maxBytes && this._entries.length > 0) {
40
+ const evicted = this._entries.shift();
41
+ this._totalBytes -= evicted.data.byteLength;
42
+ this._firstRetainedSeq = evicted.seq + 1;
43
+ }
44
+ }
45
+
46
+ since(fromSeq = 0) {
47
+ const truncated = fromSeq > 0 && fromSeq < this._firstRetainedSeq;
48
+ const chunks = this._entries.filter((e) => e.seq >= fromSeq);
49
+ return { chunks, truncated };
50
+ }
51
+
52
+ get lastSeq() {
53
+ return this._lastSeq;
54
+ }
55
+ get totalBytes() {
56
+ return this._totalBytes;
57
+ }
58
+ get size() {
59
+ return this._entries.length;
60
+ }
61
+ }