@soederpop/luca 0.0.34 → 0.0.36

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/bun.lock CHANGED
@@ -5,7 +5,6 @@
5
5
  "name": "@soederpop/luca",
6
6
  "dependencies": {
7
7
  "@modelcontextprotocol/sdk": "^1.12.1",
8
- "@ngrok/ngrok": "^1.5.1",
9
8
  "@openai/codex": "^0.99.0",
10
9
  "@resvg/resvg-js": "^2.6.2",
11
10
  "@supabase/supabase-js": "^2.95.3",
package/docs/README.md CHANGED
@@ -25,7 +25,7 @@ Items: 169
25
25
  Sections: (none)
26
26
  Relationships: (none)
27
27
  Documents: 43
28
- IDs: examples/postgres, examples/yaml-tree, examples/grep, examples/ink, examples/git, examples/ink-renderer, examples/esbuild, examples/window-manager, examples/process-manager, examples/runpod, examples/google-auth, examples/port-exposer, examples/downloader, examples/secure-shell, examples/vault, examples/window-manager-layouts, examples/ipc-socket, examples/google-sheets, examples/fs, examples/networking, examples/ink-blocks, examples/ui, examples/opener, examples/nlp, examples/disk-cache, examples/assistant/CORE, examples/vm, examples/google-docs, examples/google-calendar, examples/content-db, examples/yaml, examples/package-finder, examples/os, examples/json-tree, examples/google-drive, examples/telegram, examples/file-manager, examples/repl, examples/python, examples/sqlite, examples/tts, examples/docker, examples/proc
28
+ IDs: examples/postgres, examples/yaml-tree, examples/grep, examples/ink, examples/git, examples/ink-renderer, examples/esbuild, examples/window-manager, examples/process-manager, examples/runpod, examples/google-auth, examples/downloader, examples/secure-shell, examples/vault, examples/window-manager-layouts, examples/ipc-socket, examples/google-sheets, examples/fs, examples/networking, examples/ink-blocks, examples/ui, examples/opener, examples/nlp, examples/disk-cache, examples/assistant/CORE, examples/vm, examples/google-docs, examples/google-calendar, examples/content-db, examples/yaml, examples/package-finder, examples/os, examples/json-tree, examples/google-drive, examples/telegram, examples/file-manager, examples/repl, examples/python, examples/sqlite, examples/tts, examples/docker, examples/proc
29
29
 
30
30
  Model: Idea
31
31
  Prefix: ideas
@@ -43,7 +43,6 @@
43
43
  - [Opener](./examples/opener.md)
44
44
  - [os](./examples/os.md)
45
45
  - [Package Finder](./examples/package-finder.md)
46
- - [Port Exposer](./examples/port-exposer.md)
47
46
  - [PostgreSQL](./examples/postgres.md)
48
47
  - [proc](./examples/proc.md)
49
48
  - [Process Manager](./examples/process-manager.md)
@@ -275,7 +275,7 @@ A table of contents for the container. **Run `luca describe <name>` for full doc
275
275
  | **AI Assistants** | `assistant`, `assistantsManager`, `conversation`, `conversationHistory`, `fileTools` | Build AI assistants, manage conversations, tool calling. `fileTools` composes lower-level features (`fs`, `grep`) into an assistant-ready tool surface — a good example of how features can define tools for assistants (see `references/examples/feature-as-tool-provider.md`). |
276
276
  | **AI Agent Wrappers** | `claudeCode`, `openaiCodex`, `lucaCoder` | Spawn and manage external AI agent CLIs as subprocesses |
277
277
  | **Data & Storage** | `sqlite`, `postgres`, `diskCache`, `contentDb`, `redis` | Databases, caching, document management |
278
- | **Networking** | `networking`, `dns`, `portExposer` | Network utilities, DNS, tunneling |
278
+ | **Networking** | `networking`, `dns` | Network utilities, DNS |
279
279
  | **Google Workspace** | `googleAuth`, `googleDrive`, `googleDocs`, `googleSheets`, `googleCalendar`, `googleMail` | OAuth and Google service wrappers |
280
280
  | **Dev Tools** | `git`, `docker`, `esbuild`, `vm`, `python`, `packageFinder` | Version control, containers, bundling, sandboxed execution |
281
281
  | **Content & NLP** | `docsReader`, `nlp`, `semanticSearch`, `skillsLibrary`, `jsonTree`, `yamlTree` | Document Q&A, text analysis, semantic search, skills, structured file ingestion |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soederpop/luca",
3
- "version": "0.0.34",
3
+ "version": "0.0.36",
4
4
  "website": "https://luca.soederpop.com",
5
5
  "description": "lightweight universal conversational architecture AKA Le Ultimate Component Architecture AKA Last Universal Common Ancestor, part AI part Human",
6
6
  "author": "jon soeder aka the people's champ <jon@soederpop.com>",
@@ -92,7 +92,6 @@
92
92
  },
93
93
  "dependencies": {
94
94
  "@modelcontextprotocol/sdk": "^1.12.1",
95
- "@ngrok/ngrok": "^1.5.1",
96
95
  "@openai/codex": "^0.99.0",
97
96
  "@resvg/resvg-js": "^2.6.2",
98
97
  "@supabase/supabase-js": "^2.95.3",
@@ -105,7 +104,7 @@
105
104
  "chokidar": "^3.5.3",
106
105
  "cli-markdown": "^3.5.0",
107
106
  "compromise": "^14.14.5",
108
- "contentbase": "^0.1.7",
107
+ "contentbase": "^0.2.0",
109
108
  "cors": "^2.8.5",
110
109
  "detect-port": "^1.5.1",
111
110
  "dotenv": "^17.2.4",
@@ -129,19 +129,16 @@ async function main() {
129
129
  }
130
130
  }
131
131
 
132
- // ── Phase 1b: Expose MCP Servers via ngrok ─────────────────────────────
132
+ // ── Phase 1b: Build local MCP Server URLs ──────────────────────────────
133
133
 
134
134
  const publicUrls: Record<string, string> = {}
135
- const exposers: Array<{ close(): Promise<void> }> = []
136
135
 
137
136
  for (const def of MCP_SERVERS) {
138
137
  const entry = serverMap[def.tag]!
139
138
  if (entry.status === 'running' || entry.status === 'external') {
140
- const exposer = container.feature('portExposer', { port: def.port, enable: true })
141
- const url = await exposer.expose()
139
+ const url = `http://localhost:${def.port}`
142
140
  publicUrls[def.tag] = `${url}/mcp`
143
141
  entry.publicUrl = url
144
- exposers.push(exposer)
145
142
  }
146
143
  }
147
144
 
@@ -496,7 +493,6 @@ async function main() {
496
493
  useInput(
497
494
  (input, key) => {
498
495
  if (key.ctrl && input === 'c') {
499
- for (const e of exposers) e.close().catch(() => {})
500
496
  pm.killAll()
501
497
  exit()
502
498
  }
@@ -553,7 +549,6 @@ async function main() {
553
549
  await ink.render(h(App))
554
550
  await ink.waitUntilExit()
555
551
 
556
- for (const e of exposers) await e.close().catch(() => {})
557
552
  pm.killAll()
558
553
  }
559
554
 
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # CI-style test: cross-compile luca for Linux and verify it runs in a Docker container
5
+ # Usage: bash scripts/test-linux-binary.sh
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
8
+ PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
9
+ DIST_DIR="$PROJECT_DIR/dist"
10
+ LINUX_BINARY="$DIST_DIR/luca-linux"
11
+
12
+ echo "=== Cross-compiling luca for linux-arm64 ==="
13
+ cd "$PROJECT_DIR"
14
+
15
+ # Run the pre-compile build steps (introspection, scaffolds, etc.)
16
+ bun run build:introspection
17
+ bun run build:scaffolds
18
+ bun run build:bootstrap
19
+ bun run build:python-bridge
20
+ bash scripts/stamp-build.sh
21
+
22
+ # Cross-compile for linux arm64 (matches Docker on Apple Silicon)
23
+ bun build ./src/cli/cli.ts --compile --target=bun-linux-arm64 --outfile "$LINUX_BINARY" --external node-llama-cpp
24
+
25
+ echo ""
26
+ echo "=== Built linux binary: $(file "$LINUX_BINARY") ==="
27
+ echo ""
28
+
29
+ # Create a minimal Dockerfile inline
30
+ DOCKER_TAG="luca-linux-test"
31
+
32
+ docker build -t "$DOCKER_TAG" -f - "$DIST_DIR" <<'DOCKERFILE'
33
+ FROM debian:bookworm-slim
34
+ RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && rm -rf /var/lib/apt/lists/*
35
+ WORKDIR /app
36
+ COPY luca-linux /usr/local/bin/luca
37
+ RUN chmod +x /usr/local/bin/luca
38
+ DOCKERFILE
39
+
40
+ echo ""
41
+ echo "=== Running smoke tests in Docker container ==="
42
+ echo ""
43
+
44
+ PASS=0
45
+ FAIL=0
46
+
47
+ run_test() {
48
+ local description="$1"
49
+ shift
50
+ echo -n " TEST: $description ... "
51
+ if output=$(docker run --rm "$DOCKER_TAG" "$@" 2>&1); then
52
+ echo "PASS"
53
+ PASS=$((PASS + 1))
54
+ else
55
+ echo "FAIL"
56
+ echo " Output: $output"
57
+ FAIL=$((FAIL + 1))
58
+ fi
59
+ }
60
+
61
+ # Basic smoke tests
62
+ run_test "binary executes" luca --version
63
+ run_test "help flag works" luca --help
64
+ run_test "eval runs JS expression" luca eval "1 + 1"
65
+ run_test "describe features" luca describe features
66
+ run_test "container basics via eval" luca eval "container.uuid"
67
+
68
+ echo ""
69
+ echo "=== Results: $PASS passed, $FAIL failed ==="
70
+
71
+ # Cleanup
72
+ docker rmi "$DOCKER_TAG" > /dev/null 2>&1 || true
73
+ rm -f "$LINUX_BINARY"
74
+
75
+ if [ "$FAIL" -gt 0 ]; then
76
+ echo "FAILED"
77
+ exit 1
78
+ fi
79
+
80
+ echo "ALL TESTS PASSED"
@@ -1,5 +1,5 @@
1
1
  // Auto-generated bootstrap content
2
- // Generated at: 2026-03-26T03:30:22.249Z
2
+ // Generated at: 2026-03-28T00:36:31.608Z
3
3
  // Source: docs/bootstrap/*.md, docs/bootstrap/templates/*, docs/examples/*.md, docs/tutorials/*.md
4
4
  //
5
5
  // Do not edit manually. Run: luca build-bootstrap
@@ -282,7 +282,7 @@ A table of contents for the container. **Run \`luca describe <name>\` for full d
282
282
  | **AI Assistants** | \`assistant\`, \`assistantsManager\`, \`conversation\`, \`conversationHistory\`, \`fileTools\` | Build AI assistants, manage conversations, tool calling. \`fileTools\` composes lower-level features (\`fs\`, \`grep\`) into an assistant-ready tool surface — a good example of how features can define tools for assistants (see \`references/examples/feature-as-tool-provider.md\`). |
283
283
  | **AI Agent Wrappers** | \`claudeCode\`, \`openaiCodex\`, \`lucaCoder\` | Spawn and manage external AI agent CLIs as subprocesses |
284
284
  | **Data & Storage** | \`sqlite\`, \`postgres\`, \`diskCache\`, \`contentDb\`, \`redis\` | Databases, caching, document management |
285
- | **Networking** | \`networking\`, \`dns\`, \`portExposer\` | Network utilities, DNS, tunneling |
285
+ | **Networking** | \`networking\`, \`dns\` | Network utilities, DNS |
286
286
  | **Google Workspace** | \`googleAuth\`, \`googleDrive\`, \`googleDocs\`, \`googleSheets\`, \`googleCalendar\`, \`googleMail\` | OAuth and Google service wrappers |
287
287
  | **Dev Tools** | \`git\`, \`docker\`, \`esbuild\`, \`vm\`, \`python\`, \`packageFinder\` | Version control, containers, bundling, sandboxed execution |
288
288
  | **Content & NLP** | \`docsReader\`, \`nlp\`, \`semanticSearch\`, \`skillsLibrary\`, \`jsonTree\`, \`yamlTree\` | Document Q&A, text analysis, semantic search, skills, structured file ingestion |
@@ -2684,96 +2684,6 @@ console.log('Done')
2684
2684
  ## Summary
2685
2685
 
2686
2686
  The ask/reply protocol gives you awaitable request/response over WebSocket without leaving the Luca helper API. The client calls \`ask(type, data)\` and gets back a promise. The server's message handler gets \`reply()\` and \`replyError()\` injected on any message that carries a \`requestId\`. The server can also \`ask()\` a specific client. Timeouts, error propagation, and cleanup of pending requests on disconnect are all handled automatically.
2687
- `,
2688
- "port-exposer.md": `---
2689
- title: "Port Exposer"
2690
- tags: [portExposer, ngrok, networking, tunnel]
2691
- lastTested: null
2692
- lastTestPassed: null
2693
- ---
2694
-
2695
- # portExposer
2696
-
2697
- Exposes local HTTP services via ngrok with SSL-enabled public URLs. Useful for development, testing webhooks, and sharing local services with external consumers.
2698
-
2699
- ## Overview
2700
-
2701
- The \`portExposer\` feature creates an ngrok tunnel from a local port to a public HTTPS URL. It supports custom subdomains, regional endpoints, basic auth, and OAuth (features that require a paid ngrok plan). Requires ngrok to be installed or available as a dependency, and optionally an auth token for premium features.
2702
-
2703
- ## Enabling the Feature
2704
-
2705
- \`\`\`ts
2706
- const exposer = container.feature('portExposer', {
2707
- port: 3000,
2708
- enable: true
2709
- })
2710
- console.log('Port Exposer enabled:', exposer.state.get('enabled'))
2711
- \`\`\`
2712
-
2713
- ## Exploring the API
2714
-
2715
- \`\`\`ts
2716
- const docs = container.features.describe('portExposer')
2717
- console.log(docs)
2718
- \`\`\`
2719
-
2720
- ## Checking Connection State
2721
-
2722
- \`\`\`ts
2723
- const exposer = container.feature('portExposer', { port: 3000 })
2724
- console.log('Connected:', exposer.isConnected())
2725
- \`\`\`
2726
-
2727
- ## Exposing a Port
2728
-
2729
- Create a tunnel and get the public URL.
2730
-
2731
- \`\`\`ts skip
2732
- const url = await exposer.expose()
2733
- console.log('Public URL:', url)
2734
- console.log('Connected:', exposer.isConnected())
2735
- \`\`\`
2736
-
2737
- The returned URL is an HTTPS endpoint that forwards traffic to \`localhost:3000\`. The tunnel remains active until \`close()\` is called or the process exits.
2738
-
2739
- ## Getting Connection Info
2740
-
2741
- Retrieve a snapshot of the current tunnel state.
2742
-
2743
- \`\`\`ts skip
2744
- await exposer.expose()
2745
- const info = exposer.getConnectionInfo()
2746
- console.log('Public URL:', info.publicUrl)
2747
- console.log('Local port:', info.localPort)
2748
- console.log('Connected at:', info.connectedAt)
2749
- \`\`\`
2750
-
2751
- ## Reconnecting with New Options
2752
-
2753
- Close the existing tunnel and re-expose with different settings.
2754
-
2755
- \`\`\`ts skip
2756
- const url1 = await exposer.expose()
2757
- console.log('First URL:', url1)
2758
-
2759
- const url2 = await exposer.reconnect({ port: 8080 })
2760
- console.log('New URL (port 8080):', url2)
2761
- \`\`\`
2762
-
2763
- The \`reconnect\` method calls \`close()\` internally, merges the new options, then calls \`expose()\` again.
2764
-
2765
- ## Closing the Tunnel
2766
-
2767
- \`\`\`ts skip
2768
- await exposer.close()
2769
- console.log('Tunnel closed:', !exposer.isConnected())
2770
- \`\`\`
2771
-
2772
- Calling \`close()\` when no tunnel is active is a safe no-op. The \`disable()\` method also closes the tunnel before disabling the feature.
2773
-
2774
- ## Summary
2775
-
2776
- The \`portExposer\` feature wraps ngrok to expose local ports as public HTTPS endpoints. It supports connection lifecycle management, reconnection with new options, and event-driven notifications for tunnel state changes. Requires ngrok to be installed.
2777
2687
  `,
2778
2688
  "secure-shell.md": `---
2779
2689
  title: "Secure Shell"
@@ -1,4 +1,4 @@
1
1
  // Generated at compile time — do not edit manually
2
- export const BUILD_SHA = 'd3e8b52'
2
+ export const BUILD_SHA = 'acd8dd0'
3
3
  export const BUILD_BRANCH = 'main'
4
- export const BUILD_DATE = '2026-03-26T03:30:23Z'
4
+ export const BUILD_DATE = '2026-03-28T00:36:32Z'
@@ -1,7 +1,7 @@
1
1
  import { setBuildTimeData, setContainerBuildTimeData } from './index.js';
2
2
 
3
3
  // Auto-generated introspection registry data
4
- // Generated at: 2026-03-26T03:30:20.574Z
4
+ // Generated at: 2026-03-28T00:36:29.946Z
5
5
 
6
6
  setBuildTimeData('features.googleDocs', {
7
7
  "id": "features.googleDocs",
@@ -8861,136 +8861,6 @@ setBuildTimeData('features.processManager', {
8861
8861
  }
8862
8862
  });
8863
8863
 
8864
- setBuildTimeData('portExposer', {
8865
- "id": "portExposer",
8866
- "description": "Port Exposer Feature Exposes local HTTP services via ngrok with SSL-enabled public URLs. Perfect for development, testing, and sharing local services securely. Features: - SSL-enabled public URLs for local services - Custom subdomains and domains (with paid plans) - Authentication options (basic auth, OAuth) - Regional endpoint selection - Connection state management",
8867
- "shortcut": "portExposer",
8868
- "className": "PortExposer",
8869
- "methods": {
8870
- "expose": {
8871
- "description": "Expose the local port via ngrok. Creates an ngrok tunnel to the specified local port and returns the SSL-enabled public URL. Emits `exposed` on success or `error` on failure.",
8872
- "parameters": {
8873
- "port": {
8874
- "type": "number",
8875
- "description": "Optional port override; falls back to `options.port`"
8876
- }
8877
- },
8878
- "required": [],
8879
- "returns": "Promise<string>",
8880
- "examples": [
8881
- {
8882
- "language": "ts",
8883
- "code": "const exposer = container.feature('portExposer', { port: 3000 })\nconst url = await exposer.expose()\nconsole.log(`Public URL: ${url}`)\n\n// Override port at call time\nconst url2 = await exposer.expose(8080)"
8884
- }
8885
- ]
8886
- },
8887
- "close": {
8888
- "description": "Stop exposing the port and close the ngrok tunnel. Tears down the ngrok listener, resets connection state, and emits `closed`. Safe to call when no tunnel is active (no-op).",
8889
- "parameters": {},
8890
- "required": [],
8891
- "returns": "Promise<void>",
8892
- "examples": [
8893
- {
8894
- "language": "ts",
8895
- "code": "const exposer = container.feature('portExposer', { port: 3000 })\nawait exposer.expose()\n// ... later\nawait exposer.close()\nconsole.log(exposer.isConnected()) // false"
8896
- }
8897
- ]
8898
- },
8899
- "getPublicUrl": {
8900
- "description": "Get the current public URL if connected. Returns the live URL from the ngrok listener, or `undefined` if no tunnel is active.",
8901
- "parameters": {},
8902
- "required": [],
8903
- "returns": "string | undefined",
8904
- "examples": [
8905
- {
8906
- "language": "ts",
8907
- "code": "const exposer = container.feature('portExposer', { port: 3000 })\nawait exposer.expose()\nconsole.log(exposer.getPublicUrl()) // 'https://abc123.ngrok.io'"
8908
- }
8909
- ]
8910
- },
8911
- "isConnected": {
8912
- "description": "Check if the ngrok tunnel is currently connected.",
8913
- "parameters": {},
8914
- "required": [],
8915
- "returns": "boolean",
8916
- "examples": [
8917
- {
8918
- "language": "ts",
8919
- "code": "const exposer = container.feature('portExposer', { port: 3000 })\nconsole.log(exposer.isConnected()) // false\nawait exposer.expose()\nconsole.log(exposer.isConnected()) // true"
8920
- }
8921
- ]
8922
- },
8923
- "getConnectionInfo": {
8924
- "description": "Get a snapshot of the current connection information. Returns an object with the tunnel's connected status, public URL, local port, connection timestamp, and session metadata.",
8925
- "parameters": {},
8926
- "required": [],
8927
- "returns": "void",
8928
- "examples": [
8929
- {
8930
- "language": "ts",
8931
- "code": "const exposer = container.feature('portExposer', { port: 3000 })\nawait exposer.expose()\nconst info = exposer.getConnectionInfo()\nconsole.log(info.publicUrl, info.localPort, info.connectedAt)"
8932
- }
8933
- ]
8934
- },
8935
- "reconnect": {
8936
- "description": "Close the existing tunnel and re-expose with optionally updated options. Calls `close()` first, merges any new options, then calls `expose()`.",
8937
- "parameters": {
8938
- "newOptions": {
8939
- "type": "Partial<PortExposerOptions>",
8940
- "description": "Optional partial options to merge before reconnecting"
8941
- }
8942
- },
8943
- "required": [],
8944
- "returns": "Promise<string>",
8945
- "examples": [
8946
- {
8947
- "language": "ts",
8948
- "code": "const exposer = container.feature('portExposer', { port: 3000 })\nawait exposer.expose()\n// Switch to a different port\nconst newUrl = await exposer.reconnect({ port: 8080 })"
8949
- }
8950
- ]
8951
- },
8952
- "disable": {
8953
- "description": "Disable the feature, ensuring the ngrok tunnel is closed first. Overrides the base `disable()` to guarantee that the tunnel is torn down before the feature is marked as disabled.",
8954
- "parameters": {},
8955
- "required": [],
8956
- "returns": "Promise<this>",
8957
- "examples": [
8958
- {
8959
- "language": "ts",
8960
- "code": "const exposer = container.feature('portExposer', { port: 3000 })\nawait exposer.expose()\nawait exposer.disable()"
8961
- }
8962
- ]
8963
- }
8964
- },
8965
- "getters": {},
8966
- "events": {
8967
- "exposed": {
8968
- "name": "exposed",
8969
- "description": "Event emitted by PortExposer",
8970
- "arguments": {}
8971
- },
8972
- "error": {
8973
- "name": "error",
8974
- "description": "Event emitted by PortExposer",
8975
- "arguments": {}
8976
- },
8977
- "closed": {
8978
- "name": "closed",
8979
- "description": "Event emitted by PortExposer",
8980
- "arguments": {}
8981
- }
8982
- },
8983
- "state": {},
8984
- "options": {},
8985
- "envVars": [],
8986
- "examples": [
8987
- {
8988
- "language": "ts",
8989
- "code": "// Basic usage\nconst exposer = container.feature('portExposer', { port: 3000 })\nconst url = await exposer.expose()\nconsole.log(`Service available at: ${url}`)\n\n// With custom subdomain\nconst exposer = container.feature('portExposer', {\n port: 8080,\n subdomain: 'my-app',\n authToken: 'your-ngrok-token'\n})"
8990
- }
8991
- ]
8992
- });
8993
-
8994
8864
  setBuildTimeData('features.googleSheets', {
8995
8865
  "id": "features.googleSheets",
8996
8866
  "description": "Google Sheets feature for reading spreadsheet data as JSON, CSV, or raw arrays. Depends on the googleAuth feature for authentication. Creates a Sheets v4 API client lazily and provides convenient methods for reading tabular data.",
@@ -27573,135 +27443,6 @@ export const introspectionData = [
27573
27443
  }
27574
27444
  }
27575
27445
  },
27576
- {
27577
- "id": "portExposer",
27578
- "description": "Port Exposer Feature Exposes local HTTP services via ngrok with SSL-enabled public URLs. Perfect for development, testing, and sharing local services securely. Features: - SSL-enabled public URLs for local services - Custom subdomains and domains (with paid plans) - Authentication options (basic auth, OAuth) - Regional endpoint selection - Connection state management",
27579
- "shortcut": "portExposer",
27580
- "className": "PortExposer",
27581
- "methods": {
27582
- "expose": {
27583
- "description": "Expose the local port via ngrok. Creates an ngrok tunnel to the specified local port and returns the SSL-enabled public URL. Emits `exposed` on success or `error` on failure.",
27584
- "parameters": {
27585
- "port": {
27586
- "type": "number",
27587
- "description": "Optional port override; falls back to `options.port`"
27588
- }
27589
- },
27590
- "required": [],
27591
- "returns": "Promise<string>",
27592
- "examples": [
27593
- {
27594
- "language": "ts",
27595
- "code": "const exposer = container.feature('portExposer', { port: 3000 })\nconst url = await exposer.expose()\nconsole.log(`Public URL: ${url}`)\n\n// Override port at call time\nconst url2 = await exposer.expose(8080)"
27596
- }
27597
- ]
27598
- },
27599
- "close": {
27600
- "description": "Stop exposing the port and close the ngrok tunnel. Tears down the ngrok listener, resets connection state, and emits `closed`. Safe to call when no tunnel is active (no-op).",
27601
- "parameters": {},
27602
- "required": [],
27603
- "returns": "Promise<void>",
27604
- "examples": [
27605
- {
27606
- "language": "ts",
27607
- "code": "const exposer = container.feature('portExposer', { port: 3000 })\nawait exposer.expose()\n// ... later\nawait exposer.close()\nconsole.log(exposer.isConnected()) // false"
27608
- }
27609
- ]
27610
- },
27611
- "getPublicUrl": {
27612
- "description": "Get the current public URL if connected. Returns the live URL from the ngrok listener, or `undefined` if no tunnel is active.",
27613
- "parameters": {},
27614
- "required": [],
27615
- "returns": "string | undefined",
27616
- "examples": [
27617
- {
27618
- "language": "ts",
27619
- "code": "const exposer = container.feature('portExposer', { port: 3000 })\nawait exposer.expose()\nconsole.log(exposer.getPublicUrl()) // 'https://abc123.ngrok.io'"
27620
- }
27621
- ]
27622
- },
27623
- "isConnected": {
27624
- "description": "Check if the ngrok tunnel is currently connected.",
27625
- "parameters": {},
27626
- "required": [],
27627
- "returns": "boolean",
27628
- "examples": [
27629
- {
27630
- "language": "ts",
27631
- "code": "const exposer = container.feature('portExposer', { port: 3000 })\nconsole.log(exposer.isConnected()) // false\nawait exposer.expose()\nconsole.log(exposer.isConnected()) // true"
27632
- }
27633
- ]
27634
- },
27635
- "getConnectionInfo": {
27636
- "description": "Get a snapshot of the current connection information. Returns an object with the tunnel's connected status, public URL, local port, connection timestamp, and session metadata.",
27637
- "parameters": {},
27638
- "required": [],
27639
- "returns": "void",
27640
- "examples": [
27641
- {
27642
- "language": "ts",
27643
- "code": "const exposer = container.feature('portExposer', { port: 3000 })\nawait exposer.expose()\nconst info = exposer.getConnectionInfo()\nconsole.log(info.publicUrl, info.localPort, info.connectedAt)"
27644
- }
27645
- ]
27646
- },
27647
- "reconnect": {
27648
- "description": "Close the existing tunnel and re-expose with optionally updated options. Calls `close()` first, merges any new options, then calls `expose()`.",
27649
- "parameters": {
27650
- "newOptions": {
27651
- "type": "Partial<PortExposerOptions>",
27652
- "description": "Optional partial options to merge before reconnecting"
27653
- }
27654
- },
27655
- "required": [],
27656
- "returns": "Promise<string>",
27657
- "examples": [
27658
- {
27659
- "language": "ts",
27660
- "code": "const exposer = container.feature('portExposer', { port: 3000 })\nawait exposer.expose()\n// Switch to a different port\nconst newUrl = await exposer.reconnect({ port: 8080 })"
27661
- }
27662
- ]
27663
- },
27664
- "disable": {
27665
- "description": "Disable the feature, ensuring the ngrok tunnel is closed first. Overrides the base `disable()` to guarantee that the tunnel is torn down before the feature is marked as disabled.",
27666
- "parameters": {},
27667
- "required": [],
27668
- "returns": "Promise<this>",
27669
- "examples": [
27670
- {
27671
- "language": "ts",
27672
- "code": "const exposer = container.feature('portExposer', { port: 3000 })\nawait exposer.expose()\nawait exposer.disable()"
27673
- }
27674
- ]
27675
- }
27676
- },
27677
- "getters": {},
27678
- "events": {
27679
- "exposed": {
27680
- "name": "exposed",
27681
- "description": "Event emitted by PortExposer",
27682
- "arguments": {}
27683
- },
27684
- "error": {
27685
- "name": "error",
27686
- "description": "Event emitted by PortExposer",
27687
- "arguments": {}
27688
- },
27689
- "closed": {
27690
- "name": "closed",
27691
- "description": "Event emitted by PortExposer",
27692
- "arguments": {}
27693
- }
27694
- },
27695
- "state": {},
27696
- "options": {},
27697
- "envVars": [],
27698
- "examples": [
27699
- {
27700
- "language": "ts",
27701
- "code": "// Basic usage\nconst exposer = container.feature('portExposer', { port: 3000 })\nconst url = await exposer.expose()\nconsole.log(`Service available at: ${url}`)\n\n// With custom subdomain\nconst exposer = container.feature('portExposer', {\n port: 8080,\n subdomain: 'my-app',\n authToken: 'your-ngrok-token'\n})"
27702
- }
27703
- ]
27704
- },
27705
27446
  {
27706
27447
  "id": "features.googleSheets",
27707
27448
  "description": "Google Sheets feature for reading spreadsheet data as JSON, CSV, or raw arrays. Depends on the googleAuth feature for authentication. Creates a Sheets v4 API client lazily and provides convenient methods for reading tabular data.",