@rubytech/create-maxy-lite 0.1.12 → 0.1.14

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/lib/paths.mjs CHANGED
@@ -85,7 +85,13 @@ export function webchatInstallCommand({ webchatDir = WEBCHAT_DIR } = {}) {
85
85
  // node_modules first, then install: that recreates the spike's empty-dir build.
86
86
  // `npm install node-pty` also resolves package.json (ws). The env prefix forces
87
87
  // scripts on to match the spike's environment regardless of ambient ignore-scripts.
88
- return `${GUEST_BUILD_ENV}; cd ${webchatDir} && rm -rf node_modules package-lock.json && npm_config_ignore_scripts=false npm install node-pty`
88
+ // Ensure the build toolchain right here, in the same clean-env command, so the
89
+ // build can never run without make/g++ — the spike's working sequence was exactly
90
+ // `apt install -y python3 make g++` THEN `npm install node-pty`, together. The
91
+ // separate `toolchain` step's guard can be fooled into skipping (it found Termux's
92
+ // g++ via the leaked PATH while Ubuntu's make was never installed → node-gyp "not
93
+ // found: make"). apt is idempotent, so re-running it costs ~nothing.
94
+ return `${GUEST_BUILD_ENV}; apt-get install -y python3 make g++ && cd ${webchatDir} && rm -rf node_modules package-lock.json && npm_config_ignore_scripts=false npm install node-pty`
89
95
  }
90
96
 
91
97
  // The shipped skills, as bundled into the app dir, and the path the on-device
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-maxy-lite",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "description": "Install maxy-lite on an Android phone: orchestrates proot-distro Ubuntu, glibc Node, claude, the web-chat relay, the vault and its bind-mount — run via npx in bare Termux.",
5
5
  "type": "module",
6
6
  "engines": {
@@ -36,6 +36,7 @@ const STATE_DIR = process.env.LITE_STATE_DIR || path.join(AGENT_HOME, '.maxy-lit
36
36
  const SESSION_FILE = path.join(STATE_DIR, 'session-id')
37
37
  const TAIL_MS = 250 // JSONL poll interval
38
38
  const QUIET_MS = 1500 // silence that ends a turn for op=done
39
+ const NO_REPLY_MS = 8000 // a turn with NO transcript growth this long = swallowed; surface why
39
40
 
40
41
  const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
41
42
 
@@ -89,9 +90,26 @@ const child = pty.spawn(CLAUDE_BIN, args, {
89
90
  cwd: AGENT_HOME,
90
91
  env: process.env,
91
92
  })
92
- // The pty is input-only for our purposes; draining stdout prevents backpressure
93
- // from stalling claude. Render comes from the JSONL, never these bytes.
94
- child.onData(() => {})
93
+ // Render comes from the JSONL, not these bytes but capture a bounded, sanitised
94
+ // sample of claude's own pty output so a swallow is diagnosable. It reveals a
95
+ // first-run trust/onboarding prompt eating the input, an error screen, or silence
96
+ // (input never submitted). Draining also prevents backpressure stalling claude.
97
+ let ptyBytes = 0
98
+ let turnPtyBase = 0
99
+ let ptySample = ''
100
+ const sanitizePty = (s) =>
101
+ s
102
+ .replace(/\x1b\[[0-9;?]*[A-Za-z]/g, '')
103
+ .replace(/\x1b[()][AB0]/g, '')
104
+ .replace(/[\r\n]+/g, ' ')
105
+ .replace(/[^\x20-\x7e]/g, '')
106
+ child.onData((d) => {
107
+ ptyBytes += d.length
108
+ ptySample = (ptySample + sanitizePty(d)).slice(-300) // most recent 300 printable chars
109
+ })
110
+ // Log the fresh claude screen once it has settled — a trust/onboarding prompt here
111
+ // explains a swallowed first message before any turn is even sent.
112
+ setTimeout(() => log('output', { turn: 0, ptyBytes, head: ptySample.slice(0, 300) }), 3000)
95
113
  child.onExit(({ exitCode }) => log('child-exit', { exitCode }))
96
114
  log('spawn', { turn: 0, pty: true, pid: child.pid })
97
115
 
@@ -162,9 +180,26 @@ setInterval(tail, TAIL_MS)
162
180
  // Gated on turnSawGrowth so a slow first JSONL write (cold start, discovery
163
181
  // latency) cannot mark the turn done before any reply has rendered.
164
182
  setInterval(() => {
165
- if (turnActive && turnSawGrowth && Date.now() - lastActivity > QUIET_MS) {
183
+ if (!turnActive) return
184
+ if (turnSawGrowth && Date.now() - lastActivity > QUIET_MS) {
166
185
  turnActive = false
167
186
  log('done', { turn, ranMs: Date.now() - turnStart })
187
+ } else if (!turnSawGrowth && Date.now() - turnStart > NO_REPLY_MS) {
188
+ // No transcript growth after the input → the message was swallowed. Surface the
189
+ // two facts that locate it: what claude actually emitted to its pty, and whether
190
+ // its transcript was even found (and if not, what dirs exist vs the one wanted).
191
+ turnActive = false
192
+ log('output', { turn, ptyBytes: ptyBytes - turnPtyBase, head: ptySample.slice(0, 300) })
193
+ if (!transcriptPath) {
194
+ let dirs = []
195
+ try {
196
+ dirs = fs.readdirSync(PROJECTS_DIR)
197
+ } catch {
198
+ /* PROJECTS_DIR absent */
199
+ }
200
+ log('jsonl-missing', { want: `${sessionId}.jsonl`, projectsDir: PROJECTS_DIR, dirs: `[${dirs.join(',')}]` })
201
+ }
202
+ log('done', { turn, ok: false, reason: 'no-transcript-growth', ranMs: Date.now() - turnStart })
168
203
  }
169
204
  }, 500)
170
205
 
@@ -180,6 +215,8 @@ function submitToAgent(text) {
180
215
  turnSawGrowth = false
181
216
  turnStart = Date.now()
182
217
  lastActivity = Date.now()
218
+ turnPtyBase = ptyBytes // baseline so op=output reports bytes claude emitted THIS turn
219
+ ptySample = ''
183
220
  log('inbound', { turn, chars: text.length })
184
221
  child.write(ptyLine(text))
185
222
  log('spawn', { turn, pty: true, pid: child.pid })