tinymux/client/web/js/spawns.js
Stephen Dennis 03b2cea53d Bring Web client to feature parity with Android/iOS Titan
New JS modules:
  timers.js — setInterval-based repeating timers
  hooks.js — CONNECT/DISCONNECT/ACTIVITY event hooks
  spawns.js — pattern-based output routing to spawn views
  variables.js — $var.name namespace (world, event, regexp, datetime, temp)
  mcp.js — MCP 2.1 protocol with simpleedit support

Trigger system upgraded:
  hilite, substitution (find/replace), line classification, TTS,
  enabled flag, capture group substitution ($0, $1, $2)

New slash commands (23 total):
  /repeat, /killtimer, /timers — timer management
  /hook, /unhook, /hooks — event hooks
  /spawn add|remove|list|focus — output routing
  /log — toggle logging (downloads as file)
  /speak — text-to-speech via Web Speech API
  /set, /unset, /vars — variable management

Integration:
  MCP parser per connection, intercepts #$# lines
  MCP editor dialog (textarea) for remote editing
  Hooks fire on connect/disconnect with auto-login
  Timers auto-cancel on disconnect
  Spawn matching routes to per-tab line buffers
  Log downloads as timestamped .log file via Blob URL
  TTS via SpeechSynthesis API
  Reconnect path wired with all new features

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 11:32:02 -06:00

60 lines
1.9 KiB
JavaScript

// spawns.js -- Spawn (output routing) system for the web client.
'use strict';
class SpawnDB {
constructor() {
this.spawns = []; // [{name, path, patterns, exceptions, prefix, maxLines, weight}]
}
add(def) {
const s = {
name: def.name || '',
path: def.path || def.name.toLowerCase(),
patterns: def.patterns || [],
exceptions: def.exceptions || [],
prefix: def.prefix || '',
maxLines: def.maxLines || 20000,
weight: def.weight || 0,
};
s._compiled = s.patterns.map(p => { try { return new RegExp(p, 'i'); } catch(e) { return null; } }).filter(Boolean);
s._exceptions = s.exceptions.map(p => { try { return new RegExp(p, 'i'); } catch(e) { return null; } }).filter(Boolean);
const idx = this.spawns.findIndex(x => x.path === s.path);
if (idx >= 0) this.spawns[idx] = s;
else this.spawns.push(s);
this.spawns.sort((a, b) => a.weight - b.weight);
}
remove(path) {
this.spawns = this.spawns.filter(s => s.path !== path);
}
list() { return this.spawns; }
// Returns array of spawn paths that match this line
match(line) {
const matched = [];
for (const s of this.spawns) {
if (!s._compiled.length) continue;
const hits = s._compiled.some(r => r.test(line));
if (!hits) continue;
const excepted = s._exceptions.some(r => r.test(line));
if (excepted) continue;
matched.push(s.path);
}
return matched;
}
loadFrom(defs) {
this.spawns = [];
for (const d of defs) this.add(d);
}
toJSON() {
return this.spawns.map(s => ({
name: s.name, path: s.path, patterns: s.patterns,
exceptions: s.exceptions, prefix: s.prefix,
maxLines: s.maxLines, weight: s.weight,
}));
}
}