limbo-ai 1.9.2 → 1.9.4

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 (2) hide show
  1. package/cli.js +41 -9
  2. package/package.json +1 -1
package/cli.js CHANGED
@@ -20,6 +20,8 @@ const COMPOSE_FILE = path.join(LIMBO_DIR, 'docker-compose.yml');
20
20
  const GHCR_IMAGE = 'ghcr.io/tomasward1/limbo';
21
21
  const DEFAULT_TAG = require('./package.json').version;
22
22
  const PORT = 18789;
23
+ // OpenClaw's OAuth callback server port — must be exposed when running auth inside Docker
24
+ const OPENCLAW_AUTH_PORT = 1453;
23
25
 
24
26
  // OpenClaw compatibility snapshots from official docs:
25
27
  // - https://docs.openclaw.ai/providers/openai
@@ -811,13 +813,23 @@ function applyOpenClawConfig(cfg) {
811
813
  ok(t(cfg.language, 'configFlowDone'));
812
814
  }
813
815
 
814
- // Strip ANSI escape sequences so URL/text matching works on TTY output.
815
- const stripAnsi = (str) => str.replace(/\x1b\[[0-9;]*[A-Za-z]/g, '').replace(/\r/g, '');
816
+ // Strip all ANSI/VT100 escape sequences from a string.
817
+ // Uses standards-based ECMA-48 byte ranges:
818
+ // CSI: ESC [ <param bytes 0x30-0x3F>* <intermediate bytes 0x20-0x2F>* <final byte 0x40-0x7E>
819
+ // Two-char ESC: ESC <0x40-0x5F> (e.g. ESC M, ESC =, ESC 7/8 save/restore cursor)
820
+ // OSC: ESC ] <any> BEL|ST
821
+ // Covers private-mode sequences like \x1b[?25l (hide cursor) that the old [0-9;]* missed.
822
+ const stripAnsi = (str) => str
823
+ .replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, '') // CSI sequences (all parameter byte combos)
824
+ .replace(/\x1b[@-Z\\-_]/g, '') // two-char ESC sequences
825
+ .replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, '') // OSC sequences
826
+ .replace(/\r/g, '');
816
827
 
817
828
  // Spawn OpenClaw auth with filtered output: extract OAuth URLs, suppress branding.
818
829
  // --tty is required so openclaw sees a TTY inside the container and runs the auth wizard.
819
830
  // We pipe stdout/stderr to filter content while the container gets a proper PTY allocation.
820
- function streamFilteredAuth(dockerArgs) {
831
+ // onUrl: optional callback invoked with each unique URL as it appears (e.g. to auto-open browser).
832
+ function streamFilteredAuth(dockerArgs, onUrl = null) {
821
833
  return new Promise((resolve) => {
822
834
  const proc = spawn('docker', dockerArgs, {
823
835
  cwd: LIMBO_DIR,
@@ -839,13 +851,25 @@ function streamFilteredAuth(dockerArgs) {
839
851
  const emitLine = (rawLine) => {
840
852
  const line = stripAnsi(rawLine);
841
853
  const urls = line.match(urlRe) || [];
842
- // Whitelist-only: only emit URLs — everything else is branding or TUI chrome
843
- for (const url of urls) {
844
- if (!seenUrls.has(url)) {
845
- seenUrls.add(url);
846
- console.log(`\n ${c.cyan}${c.bold}→ ${url}${c.reset}\n`);
854
+ if (urls.length > 0) {
855
+ for (const url of urls) {
856
+ if (!seenUrls.has(url)) {
857
+ seenUrls.add(url);
858
+ console.log(`\n ${c.cyan}${c.bold}→ ${url}${c.reset}\n`);
859
+ if (onUrl) onUrl(url);
860
+ }
847
861
  }
862
+ return; // don't double-print the line containing the URL
848
863
  }
864
+ // Suppress OpenClaw branding
865
+ if (/openclaw/i.test(line)) return;
866
+ // Suppress TUI chrome: lines that are only spinner/decoration/box-drawing chars.
867
+ // OpenClaw's TUI writes animation frames separated by \r — after our \r-split, each
868
+ // frame becomes a short line (often a single char). We filter these out so they don't
869
+ // scatter across the terminal as individual console.log lines.
870
+ const tuiChrome = /^[\s\u2500-\u257f\u2580-\u259f\u25a0-\u25ff\u2600-\u26ff◇◆●○◈→←↑↓⠀-\u28ff]*$/u;
871
+ if (tuiChrome.test(line)) return;
872
+ if (line.trim()) console.log(` ${line}`);
849
873
  };
850
874
 
851
875
  proc.stdout.on('data', handleData);
@@ -874,8 +898,16 @@ async function runSubscriptionAuthFlow(cfg) {
874
898
  let exitCode;
875
899
  if (cfg.providerFamily === 'openai') {
876
900
  // --tty allocates a PTY inside the container so openclaw's auth wizard runs correctly.
901
+ // -p exposes the OAuth callback port so the browser redirect reaches the in-container server.
877
902
  // We still pipe stdout/stderr to filter out branding and highlight the OAuth URL.
878
- exitCode = await streamFilteredAuth(['compose', 'run', '--tty', '--rm', '--entrypoint', 'openclaw', 'limbo', ...authArgs]);
903
+ const opener = process.platform === 'darwin' ? 'open' : 'xdg-open';
904
+ exitCode = await streamFilteredAuth(
905
+ ['compose', 'run', '--tty', '--rm', '-p', `${OPENCLAW_AUTH_PORT}:${OPENCLAW_AUTH_PORT}`, '--entrypoint', 'openclaw', 'limbo', ...authArgs],
906
+ (url) => {
907
+ // Auto-open the browser so the user doesn't need to copy/paste the URL
908
+ try { spawnSync(opener, [url], { stdio: 'ignore', timeout: 3000 }); } catch {}
909
+ },
910
+ );
879
911
  } else {
880
912
  // Anthropic paste-token is interactive (user pastes a token); keep stdio inherited
881
913
  const authResult = runOpenClaw(authArgs);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "limbo-ai",
3
- "version": "1.9.2",
3
+ "version": "1.9.4",
4
4
  "description": "Your personal AI memory agent — install and manage Limbo via npx",
5
5
  "type": "commonjs",
6
6
  "bin": {