@phenx-inc/ctlsurf 0.16.0 → 0.17.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 (37) hide show
  1. package/out/headless/index.mjs +549 -116
  2. package/out/headless/index.mjs.map +4 -4
  3. package/out/main/index.js +416 -38
  4. package/out/preload/index.js +3 -0
  5. package/out/renderer/assets/{cssMode-Xx1-SozN.js → cssMode-BtQxI2Eb.js} +3 -3
  6. package/out/renderer/assets/{freemarker2-DM49yPw1.js → freemarker2-DRW5dmY5.js} +1 -1
  7. package/out/renderer/assets/{handlebars-DJJBblpV.js → handlebars-CFLF3cP7.js} +1 -1
  8. package/out/renderer/assets/{html-DnMnJOxw.js → html-DtnBOkwp.js} +1 -1
  9. package/out/renderer/assets/{htmlMode-BG5u2CSH.js → htmlMode-DMVysDCg.js} +3 -3
  10. package/out/renderer/assets/{index-DID1LlMU.css → index-CEjwKjtd.css} +45 -0
  11. package/out/renderer/assets/{index-D65QIRev.js → index-Dm70hk5s.js} +292 -38
  12. package/out/renderer/assets/{javascript-Dci_phmN.js → javascript-ChlyA1sf.js} +2 -2
  13. package/out/renderer/assets/{jsonMode-Dmp3JJSm.js → jsonMode-D1_oHzeN.js} +3 -3
  14. package/out/renderer/assets/{liquid-C9KVysk1.js → liquid-D4ftdy1G.js} +1 -1
  15. package/out/renderer/assets/{lspLanguageFeatures-IDD0-KWa.js → lspLanguageFeatures-Ckqu5ubG.js} +1 -1
  16. package/out/renderer/assets/{mdx-ascmBFXX.js → mdx-B9wsVYSC.js} +1 -1
  17. package/out/renderer/assets/{python-DcbM4Hy5.js → python-e23tD6IS.js} +1 -1
  18. package/out/renderer/assets/{razor-XJROxpYK.js → razor-CrDyXj3d.js} +1 -1
  19. package/out/renderer/assets/{tsMode-EXbzul3n.js → tsMode-BwGC5o4O.js} +1 -1
  20. package/out/renderer/assets/{typescript-DhTtFBLt.js → typescript-Do-98d_O.js} +1 -1
  21. package/out/renderer/assets/{xml-BLfEkVl1.js → xml-Dpdu5R3m.js} +1 -1
  22. package/out/renderer/assets/{yaml-DpzsqvWa.js → yaml-B0Zt7LCJ.js} +1 -1
  23. package/out/renderer/index.html +2 -2
  24. package/package.json +1 -1
  25. package/scripts/test-terminal-scroll.cjs +64 -0
  26. package/src/main/headless.ts +12 -0
  27. package/src/main/index.ts +8 -0
  28. package/src/main/monitor.ts +540 -0
  29. package/src/main/orchestrator.ts +10 -0
  30. package/src/main/pty.ts +5 -0
  31. package/src/main/tui.ts +37 -3
  32. package/src/preload/index.ts +4 -0
  33. package/src/renderer/App.tsx +9 -1
  34. package/src/renderer/components/Resources.tsx +217 -0
  35. package/src/renderer/components/StatusBar.tsx +6 -1
  36. package/src/renderer/components/TerminalPanel.tsx +54 -20
  37. package/src/renderer/styles.css +45 -0
@@ -1,4 +1,4 @@
1
- import { l as languages } from "./index-D65QIRev.js";
1
+ import { l as languages } from "./index-Dm70hk5s.js";
2
2
  const conf = {
3
3
  comments: {
4
4
  lineComment: "#",
@@ -1,4 +1,4 @@
1
- import { l as languages } from "./index-D65QIRev.js";
1
+ import { l as languages } from "./index-Dm70hk5s.js";
2
2
  const EMPTY_ELEMENTS = [
3
3
  "area",
4
4
  "base",
@@ -1,4 +1,4 @@
1
- import { c as createWebWorker, e as editor, U as Uri, a as MarkerTag, M as MarkerSeverity, l as languages, t as typescriptDefaults, R as Range } from "./index-D65QIRev.js";
1
+ import { c as createWebWorker, e as editor, U as Uri, a as MarkerTag, M as MarkerSeverity, l as languages, t as typescriptDefaults, R as Range } from "./index-Dm70hk5s.js";
2
2
  class WorkerManager {
3
3
  constructor(_modeId, _defaults) {
4
4
  this._modeId = _modeId;
@@ -1,4 +1,4 @@
1
- import { l as languages } from "./index-D65QIRev.js";
1
+ import { l as languages } from "./index-Dm70hk5s.js";
2
2
  const conf = {
3
3
  wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g,
4
4
  comments: {
@@ -1,4 +1,4 @@
1
- import { l as languages } from "./index-D65QIRev.js";
1
+ import { l as languages } from "./index-Dm70hk5s.js";
2
2
  const conf = {
3
3
  comments: {
4
4
  blockComment: ["<!--", "-->"]
@@ -1,4 +1,4 @@
1
- import { l as languages } from "./index-D65QIRev.js";
1
+ import { l as languages } from "./index-Dm70hk5s.js";
2
2
  const conf = {
3
3
  comments: {
4
4
  lineComment: "#"
@@ -106,8 +106,8 @@
106
106
  :root[data-theme='catppuccin-latte'] .boot-splash-title { color: #2f3145; }
107
107
  :root[data-theme='catppuccin-latte'] .boot-splash-label { color: #7c7f93; }
108
108
  </style>
109
- <script type="module" crossorigin src="./assets/index-D65QIRev.js"></script>
110
- <link rel="stylesheet" crossorigin href="./assets/index-DID1LlMU.css">
109
+ <script type="module" crossorigin src="./assets/index-Dm70hk5s.js"></script>
110
+ <link rel="stylesheet" crossorigin href="./assets/index-CEjwKjtd.css">
111
111
  </head>
112
112
  <body>
113
113
  <div id="boot-splash">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phenx-inc/ctlsurf",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "Agent-agnostic terminal and desktop app for ctlsurf — run Claude Code, Codex, or any coding agent with live session logging and remote control",
5
5
  "main": "out/main/index.js",
6
6
  "bin": {
@@ -0,0 +1,64 @@
1
+ // Exercise the actual React terminal panel and xterm parser in Electron.
2
+ // Run: node_modules/.bin/electron scripts/test-terminal-scroll.cjs
3
+ const { app, BrowserWindow } = require('electron')
4
+ const { build } = require('esbuild')
5
+ const fs = require('node:fs/promises')
6
+ const os = require('node:os')
7
+ const path = require('node:path')
8
+ app.on('window-all-closed', () => {})
9
+ app.on('child-process-gone', (_event, details) => console.error('Child process exited:', details))
10
+
11
+ async function main() {
12
+ console.log('Starting terminal scroll regression checks...')
13
+ const temp = await fs.mkdtemp(path.join(os.tmpdir(), 'ctlsurf-scroll-'))
14
+ app.setPath('userData', path.join(temp, 'profile'))
15
+ let win
16
+ try {
17
+ await build({
18
+ entryPoints: [path.join(__dirname, '../repro-terminal-scroll.tsx')],
19
+ outfile: path.join(temp, 'test.js'),
20
+ bundle: true,
21
+ platform: 'browser',
22
+ jsx: 'automatic',
23
+ external: ['@huggingface/transformers'],
24
+ })
25
+ await fs.writeFile(path.join(temp, 'index.html'), `<!doctype html><meta charset="UTF-8">
26
+ <meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'">
27
+ <link rel="stylesheet" href="test.css">
28
+ <style>
29
+ html, body, #root { margin: 0; width: 100%; height: 100%; }
30
+ .terminal-wrap, .terminal-container, .xterm { width: 100%; height: 100%; }
31
+ .terminal-wrap { position: relative; }
32
+ .term-jump-latest { position: absolute; bottom: 0; right: 0; }
33
+ </style>
34
+ <div id="root"></div><script src="test.js"></script>`)
35
+ await app.whenReady()
36
+ win = new BrowserWindow({
37
+ show: false, width: 900, height: 500,
38
+ webPreferences: { backgroundThrottling: false },
39
+ })
40
+ win.webContents.on('console-message', (event) => {
41
+ if (event.level === 'error') console.error(event.message)
42
+ })
43
+ win.webContents.on('render-process-gone', (_event, details) => console.error('Renderer exited:', details))
44
+ await win.loadFile(path.join(temp, 'index.html'))
45
+ let timeout
46
+ const result = await Promise.race([
47
+ win.webContents.executeJavaScript('window.scrollTest'),
48
+ new Promise((_, reject) => {
49
+ timeout = setTimeout(() => reject(new Error('Scroll test timed out')), 15_000)
50
+ }),
51
+ ]).finally(() => clearTimeout(timeout))
52
+ for (const line of result.checks) console.log(line)
53
+ if (result.error) throw new Error(result.error)
54
+ console.log('Terminal scroll regression checks passed.')
55
+ } finally {
56
+ win?.destroy()
57
+ await fs.rm(temp, { recursive: true, force: true, maxRetries: 5 })
58
+ }
59
+ }
60
+
61
+ main().then(() => app.exit(0), (err) => {
62
+ console.error(err)
63
+ app.exit(1)
64
+ })
@@ -31,6 +31,7 @@ import { Orchestrator } from './orchestrator'
31
31
  import { getSettingsDir } from './settingsDir'
32
32
  import { getBuiltinAgents, getAvailableAgents, isCodingAgent, type AgentConfig } from './agents'
33
33
  import { Tui } from './tui'
34
+ import { ResourceMonitor, summarizeSnapshot } from './monitor'
34
35
  import { fetchLatestNpmVersion, compareSemver, NPM_PACKAGE } from './updateCheck'
35
36
 
36
37
  function getCurrentVersion(): string {
@@ -177,6 +178,16 @@ async function main() {
177
178
  await orchestrator.spawnAgent(HEADLESS_TAB, agent, args.cwd, { trackTime: trackTimeOverride })
178
179
  orchestrator.resizePty(HEADLESS_TAB, ptySize.cols, ptySize.rows)
179
180
 
181
+ // CPU / RAM and the agent's plan quota, appended to the tab title.
182
+ const monitor = new ResourceMonitor(() => orchestrator.getTabProcesses())
183
+ const refreshResources = () => {
184
+ monitor.snapshot()
185
+ .then(snap => tui.update({ resources: summarizeSnapshot(snap) }))
186
+ .catch(() => { /* keep the last title */ })
187
+ }
188
+ refreshResources()
189
+ setInterval(refreshResources, 10_000).unref()
190
+
180
191
  // For coding agents, send an initial prompt to kick-start them
181
192
  if (isCodingAgent(agent)) {
182
193
  setTimeout(() => {
@@ -221,6 +232,7 @@ async function main() {
221
232
  if (process.stdin.isTTY) {
222
233
  process.stdin.setRawMode(false)
223
234
  }
235
+ monitor.stop()
224
236
  tui.destroy()
225
237
  await orchestrator.shutdown()
226
238
  process.exit(0)
package/src/main/index.ts CHANGED
@@ -27,6 +27,7 @@ import { getSettingsDir } from './settingsDir'
27
27
  import { Orchestrator } from './orchestrator'
28
28
  import { AttentionController } from './agentState'
29
29
  import { listSshHosts, getRecentHosts, addRecentHost, probeRemote, buildRemoteAgent, type RemoteAction } from './ssh'
30
+ import { ResourceMonitor } from './monitor'
30
31
 
31
32
  const SETTINGS_DIR = getSettingsDir(true)
32
33
 
@@ -498,6 +499,12 @@ async function checkForUpdate(): Promise<void> {
498
499
  ipcMain.handle('app:getVersion', () => app.getVersion())
499
500
  ipcMain.handle('app:getUpdateInfo', () => updateInfo)
500
501
 
502
+ // ─── Resources + plan quota (status bar) ──────────
503
+
504
+ // Samples only while the renderer keeps asking; `fast` = the panel is open.
505
+ const resourceMonitor = new ResourceMonitor(() => orchestrator.getTabProcesses())
506
+ ipcMain.handle('monitor:snapshot', (_event, fast: boolean) => resourceMonitor.snapshot(!!fast))
507
+
501
508
  // ─── Filesystem IPC ───────────────────────────────
502
509
 
503
510
  ipcMain.handle('fs:readDir', async (_event, dirPath: string) => {
@@ -831,6 +838,7 @@ let shutdownStarted = false
831
838
  function shutdownThenQuit(): void {
832
839
  if (shutdownStarted) return
833
840
  shutdownStarted = true
841
+ resourceMonitor.stop()
834
842
  void Promise.race([
835
843
  orchestrator.shutdown(),
836
844
  new Promise((resolve) => setTimeout(resolve, 10_000)),