ideawave 0.0.103 → 0.0.105

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/README.md CHANGED
@@ -1,41 +1,293 @@
1
- # IdeaWave CLI
1
+ # IdeaWave TUI
2
2
 
3
- Run local coding agents from an IdeaWave board.
3
+ The local IdeaWave host, built with OpenTUI Solid and Effect. The application
4
+ provides runtime-validated compatibility contracts, scoped lifecycle, ACP client
5
+ discovery and runs, browser-hosted MCP tools, searchable local activity, and
6
+ privacy-safe traces, metrics, and approved operational logs inside the general
7
+ host-status dashboard.
8
+
9
+ ## Requirements
10
+
11
+ - Node.js 22 or newer for the published npx/bunx launcher and managed ACP
12
+ subprocesses.
13
+ - macOS and Linux glibc/musl on x64 or arm64, or Windows on x64.
14
+ - One supported ACP client for agent use: Claude Code, Codex, GitHub Copilot,
15
+ OpenCode, or a configured custom ACP command.
16
+
17
+ Users do not install Bun. Each platform npm package contains a Bun-compiled
18
+ IdeaWave host with OpenTUI and the Bun runtime embedded. Bun 1.4.0 remains a
19
+ pinned build and contributor dependency for this monorepo. x64 release hosts
20
+ use Bun's baseline target so older non-AVX2 CPUs do not fail at startup.
4
21
 
5
22
  ```bash
6
- npx --yes ideawave@latest
23
+ bun install
24
+ bun dev
7
25
  ```
8
26
 
9
- Then open [ideawave.app](https://ideawave.app), choose a board, and start an agent run.
27
+ To create and smoke-test the installable release artifacts for the current
28
+ platform:
10
29
 
11
- ## Requirements
30
+ ```bash
31
+ bun run release:pack --target current
32
+ bun test tests/release-artifact.test.ts
33
+ ```
34
+
35
+ The builder writes a launcher tarball plus the current platform's exact host
36
+ tarball to `dist/standalone/tarballs/`. The release workflow builds all seven
37
+ unsigned targets on one Blacksmith Linux producer. Automated release acceptance
38
+ executes Linux glibc/musl packages (with ARM headless checks through QEMU); the
39
+ separate manual, credential-free compatibility workflow executes macOS arm64,
40
+ macOS x64 under Rosetta, and Windows x64. It does not sign, publish, or gate the
41
+ Linux release. The artifact test installs the two current-platform tarballs
42
+ into a clean consumer project; it does not depend on a global install or this
43
+ checkout.
44
+
45
+ The package publishes to npm as `ideawave`, so both `npx ideawave` and ordinary
46
+ `bunx ideawave` open the same TUI. Its small Node launcher validates Node,
47
+ detects OS/CPU/libc, resolves the exact-version native host package, and starts
48
+ that executable. There is no postinstall, binary copy, fallback download, or
49
+ IdeaWave-owned executable cache. The universal package intentionally retains
50
+ only `playwright-core` as a JavaScript sidecar so browser installation can run
51
+ under the validated launcher Node. `bunx --bun ideawave` is rejected because it
52
+ overrides the Node shebang and would make managed ACP and Playwright subprocess
53
+ selection ambiguous.
12
54
 
13
- - Node.js 18 or newer
14
- - Google Chrome for screenshot capture. Browser cards stay inside the app
15
- iframe and use the CLI's local browser-kit proxy.
16
- - At least one supported agent runtime installed and signed in:
17
- - Claude Code
18
- - Codex CLI
19
- - GitHub Copilot CLI
20
- - OpenCode
55
+ On a clean profile, the first interactive start asks which ACP clients to use.
56
+ The selection is checked before it is saved, and the dashboard opens only when
57
+ at least one selected client is ready. Existing configured profiles start
58
+ directly. Use `--onboard` to choose again or `--skip-onboarding` for automation.
21
59
 
22
60
  ## Commands
23
61
 
62
+ | Command | Behavior |
63
+ | --- | --- |
64
+ | `ideawave` | Run first-use setup when needed, then open the status workspace. |
65
+ | `ideawave --onboard` | Re-run ACP client selection and verification. |
66
+ | `ideawave --skip-onboarding` | Open without reading or changing onboarding state. |
67
+ | `ideawave --dev` | Allow Railway PR preview origins on the browser bridge (development/preview mode). |
68
+ | `ideawave doctor` | Run passive checks and print a report without initializing OpenTUI. |
69
+ | `ideawave doctor --deep` | Also launch ready ACP clients briefly; processes may use network/authentication state. |
70
+ | `ideawave doctor --json` | Print the versioned privacy-safe automation report. May be combined with `--deep`. |
71
+ | `ideawave traces` | Validate trace-export configuration without starting the host, accessing the network, or displaying collector/header values. |
72
+ | `ideawave traces --json` | Print the versioned privacy-safe trace configuration report. |
73
+ | `ideawave --version` | Print the installed package version. `-v` and `ideawave version` are aliases. |
74
+ | `ideawave --headless` | Run the same host services as the TUI without initializing OpenTUI. |
75
+ | `ideawave capture install` | Install the managed Chromium headless shell used by iframe capture. On Linux musl, install system Chromium instead (`apk add chromium` on Alpine). |
76
+
77
+ `doctor` exits with status `0` when all checks pass, `1` when warnings are
78
+ present, and `2` when a check fails or the command cannot complete. Onboarding
79
+ returns `3` when no selected client is usable and `64` for invalid arguments.
80
+ Redirected output is plain text without ANSI control sequences.
81
+ The JSON doctor schema is versioned with `schemaVersion: 1`; it omits persisted
82
+ commands, arguments, environment values, paths, overrides, and runtime IDs.
83
+
84
+ `traces` exits with status `0` when trace export is intentionally disabled or
85
+ valid, `1` when export was requested but its configuration is incomplete or
86
+ invalid, and `64` for invalid options. It reports the standard OpenTelemetry
87
+ environment-variable name that won precedence, but never its endpoint or header
88
+ value. The command validates configuration for a future host process; it does
89
+ not contact a collector or inspect another running host.
90
+
91
+ `--headless` initializes the shared `Application` layer, prints one JSON readiness
92
+ line containing only loopback ports, and stays active until `SIGINT` or `SIGTERM`.
93
+ Ordinary interactive startup requires terminal stdin and stdout; automation must
94
+ choose `--headless`, `doctor`, or `traces` explicitly.
95
+
96
+ ## Status Controls
97
+
98
+ | Key | Action |
99
+ | --- | --- |
100
+ | `1`, `a` | Select the Activity workspace tab. |
101
+ | `2`, `v` | Select the Doctor workspace tab. |
102
+ | `Tab` | Switch workspace tabs. |
103
+ | `/`, `b` | Search activity or edit severity, category, time, service, session, and run filters. |
104
+ | `p`, `f` | Pause/resume the view or follow the newest matching activity. |
105
+ | `j`, `k`, `Up`, `Down` | Select an activity record. |
106
+ | `Enter`, `y` | Inspect a selected record or copy its safe projection with OSC 52. |
107
+ | `r` | Run fast passive diagnostics. |
108
+ | `d` | Review side effects, then run a cancellable ACP launch check. |
109
+ | `i` | Review side effects, then install missing managed Claude/Codex bridges. |
110
+ | `e` | Review side effects, then install the managed capture engine. |
111
+ | `s`, `g` | Show browser session or live agent detail. |
112
+ | `o` | Show runtime configuration and per-signal telemetry detail. |
113
+ | `x`, `c` | Cancel active deep checks or installation. |
114
+ | `l` | Clear only the current local view; retained history and service state are unchanged. |
115
+ | `?` | Show controls and safety details. |
116
+ | `q` | Close an overlay, or quit from the dashboard. |
117
+ | `Esc` | Close an overlay. |
118
+ | `Ctrl+C` | Cancel active work, or quit when idle. |
119
+
120
+ Passive doctor checks do not launch ACP processes, install packages, or access
121
+ the network. Deep checks briefly launch ready ACP commands. Managed bridge
122
+ installation uses npm to place each exact pin in IdeaWave's versioned cache,
123
+ validates and warms the bundled runtime, and can access the network and package
124
+ cache. Warm launches run the verified adapter directly with Node. Both
125
+ operations are Effect-scoped and clean up staging work on cancellation.
126
+
127
+ Playwright does not publish supported managed browser builds for Linux musl.
128
+ On Alpine and other musl systems, IdeaWave uses a distribution-provided
129
+ `chromium` executable (or `IDEAWAVE_CAPTURE_EXECUTABLE_PATH`) and does not
130
+ download a glibc browser into the managed cache.
131
+
132
+ The left status panel remains visible on both workspace tabs. Activity entries
133
+ always show their producing service label. When readiness transitions to a state
134
+ where no ACP client can be activated, Doctor is selected once so remediation is
135
+ immediately visible; a later explicit tab choice is respected.
136
+
137
+ ## Telemetry
138
+
139
+ Remote export stays disabled on an ordinary install and automatically uses
140
+ continuous mode when an OTLP endpoint is configured. It uses vendor-neutral OTLP/HTTP JSON and
141
+ never sends prompts, responses, tool arguments or results, credentials,
142
+ environment values, paths, URLs, user/card/run identifiers, or raw errors.
143
+ Browser session IDs are the only approved high-cardinality correlation field and
144
+ are never used as metric dimensions. Remote logs use a finite event and message
145
+ catalog; local Activity summaries and arbitrary producer messages cannot cross
146
+ the export boundary.
147
+
148
+ Trace and log queues are deliberately bounded and in-process. IdeaWave does not
149
+ create a local trace file, persist telemetry between launches, or backfill the
150
+ Activity ring. Use `ideawave traces` for a non-networking configuration check;
151
+ use Activity or Doctor inside the running host for live queue/export state, and
152
+ the configured collector for retained trace search. Signal-specific endpoint
153
+ and header variables take precedence over their generic counterparts, matching
154
+ the [OpenTelemetry OTLP exporter contract](https://opentelemetry.io/docs/specs/otel/protocol/exporter/).
155
+
156
+ | Variable | Behavior |
157
+ | --- | --- |
158
+ | `IDEAWAVE_TELEMETRY_MODE=disabled` | Explicitly opt out of all remote telemetry. |
159
+ | `IDEAWAVE_TELEMETRY_MODE=manual` | Keep a bounded local queue without exporting automatically. |
160
+ | `IDEAWAVE_TELEMETRY_MODE=continuous` | Export the bounded queue automatically with retry. This is inferred when an OTLP endpoint is configured. |
161
+ | `IDEAWAVE_TELEMETRY_LOGS=disabled` | Explicitly opt approved operational logs out while retaining traces and metrics. Logs are enabled by default. |
162
+ | `OTEL_EXPORTER_OTLP_ENDPOINT` | Operator-owned collector or ingest-proxy base URL. `/v1/logs`, `/v1/traces`, and `/v1/metrics` are resolved automatically. |
163
+ | `OTEL_EXPORTER_OTLP_HEADERS` | Optional comma-separated, percent-encoded `name=value` request headers. Values are never displayed or exported. |
164
+ | `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` | Optional final OTLP/HTTP logs URL; overrides the generic endpoint for logs. |
165
+ | `OTEL_EXPORTER_OTLP_LOGS_HEADERS` | Optional logs-only headers; overrides generic headers for logs. |
166
+ | `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | Optional final OTLP/HTTP traces URL; overrides the generic endpoint for traces. |
167
+ | `OTEL_EXPORTER_OTLP_TRACES_HEADERS` | Optional traces-only headers; overrides generic headers for traces. |
168
+ | `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT` | Optional final OTLP/HTTP metrics URL; overrides the generic endpoint for metrics. |
169
+ | `OTEL_EXPORTER_OTLP_METRICS_HEADERS` | Optional metrics-only headers; overrides generic headers for metrics. |
170
+
171
+ The Activity header and Doctor show per-signal configuration, queue depth,
172
+ drops, retry state, last success, and safe last-failure codes. Each signal has
173
+ reserved bounded capacity, so a log burst cannot consume trace or metric space.
174
+ A failure on one signal does not erase another signal's success or break browser,
175
+ ACP, MCP, Doctor, iframe proxy, capture, or local log behavior. Shutdown
176
+ interrupts all in-flight HTTP requests within one total final-flush bound so an
177
+ unavailable collector cannot hang terminal restoration.
178
+
179
+ ### Collector And Axiom Routing
180
+
181
+ Production deployments should send all three signal paths to an operator-owned
182
+ OpenTelemetry Collector or product-owned ingest proxy. The collector holds
183
+ credentials and routes logs and traces to separate Axiom Events datasets and
184
+ metrics to an Axiom Metrics dataset. A single generic product configuration is
185
+ then sufficient:
186
+
24
187
  ```bash
25
- npx --yes ideawave@latest --dev
26
- npx ideawave --help
27
- npx ideawave --version
188
+ IDEAWAVE_TELEMETRY_MODE=continuous
189
+ IDEAWAVE_TELEMETRY_LOGS=enabled
190
+ OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318
28
191
  ```
29
192
 
30
- ## Troubleshooting
193
+ A direct Axiom setup is supported only for an explicit developer-owned local
194
+ validation. It requires three dataset routes because Axiom signal datasets are
195
+ not interchangeable. Header values use OTLP's comma-separated percent-encoded
196
+ format and are never retained in application state:
197
+
198
+ ```bash
199
+ IDEAWAVE_TELEMETRY_MODE=continuous
200
+ IDEAWAVE_TELEMETRY_LOGS=enabled
201
+ OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=https://api.axiom.co/v1/logs
202
+ OTEL_EXPORTER_OTLP_LOGS_HEADERS='Authorization=Bearer%20LOCAL_TOKEN,X-Axiom-Dataset=ideawave-local-logs'
203
+ OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://api.axiom.co/v1/traces
204
+ OTEL_EXPORTER_OTLP_TRACES_HEADERS='Authorization=Bearer%20LOCAL_TOKEN,X-Axiom-Dataset=ideawave-local-traces'
205
+ OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=https://api.axiom.co/v1/metrics
206
+ OTEL_EXPORTER_OTLP_METRICS_HEADERS='Authorization=Bearer%20LOCAL_TOKEN,X-Axiom-Dataset=ideawave-local-metrics'
207
+ ```
208
+
209
+ Do not commit credentials or ship a shared token. Export applies only to future
210
+ approved records; retained local Activity history is never backfilled.
211
+
212
+ ## Compatibility
213
+
214
+ Runtime configuration remains compatible with the existing CLI:
215
+
216
+ - `IDEAWAVE_RUNTIME_CONFIG_PATH` overrides `runtimes.json`.
217
+ - Linux defaults to `${XDG_CONFIG_HOME:-~/.config}/ideawave/runtimes.json`.
218
+ - macOS defaults to `~/Library/Application Support/ideawave/runtimes.json`.
219
+ - Windows defaults to `%APPDATA%\ideawave\runtimes.json`.
220
+ - `IDEAWAVE_ACP_BRIDGE_CACHE_DIR` overrides managed bridge state.
221
+ - Browser bridge candidates remain `127.0.0.1:43275-43277`.
222
+ - The preferred MCP port remains `127.0.0.1:43274`; set
223
+ `IDEAWAVE_MCP_PORT` to another loopback port, or `0` for an OS-assigned port.
31
224
 
32
- If a runtime appears unavailable, confirm its command is on your `PATH` and that you are signed in to that tool.
225
+ The application validates version-1 runtime files, relay messages, runtime RPC
226
+ requests, agent run payloads, and MCP context with Effect Schema. Missing files
227
+ decode to the legacy empty default. Invalid external data produces typed
228
+ boundary/configuration failures.
33
229
 
34
- For supported runtimes, the CLI fetches small ACP bridge packages on first use. npm caches them, so later runs are fast. If npm's cache was cleared, keep the CLI running and retry after the bridge downloads again.
230
+ ## Rollout
231
+
232
+ Release qualification, fallback, persistence, platform checks, and live browser
233
+ plus telemetry correlation steps are defined in [the rollout runbook](docs/rollout.md).
234
+ The legacy CLI must remain available until every gate in that runbook has recorded
235
+ evidence for the release candidate.
236
+
237
+ ## Phase Decisions
238
+
239
+ - The browser remains the authenticated board tool host. Browser session IDs
240
+ are correlation data, not authentication.
241
+ - Effect is pinned to `4.0.0-beta.102`; v3 APIs are not used.
242
+ - `@effect/platform-node-shared` is an exact direct dependency and a workspace
243
+ resolution. The platform-bun prerelease range otherwise lets package managers
244
+ install a newer Effect prerelease beside the pinned beta, which breaks
245
+ service-context identity at runtime.
246
+ - Product telemetry uses only an explicit operational metadata allowlist.
247
+ Prompts, responses, tool data, credentials, environment values, paths, and
248
+ URLs are prohibited.
249
+ - Direct Axiom credentials may be used only in explicit developer-owned local
250
+ experiments. The product path requires an ingest proxy or operator-owned
251
+ collector and warns when a direct credential is present.
252
+ - Browser automation remains isolated behind the Phase 5 iframe proxy and
253
+ capture service boundaries.
254
+ - First-run setup verifies an explicit user selection and writes it through the
255
+ version-1 runtime configuration contract. Running the passive `Doctor` service
256
+ itself is non-mutating.
257
+ - The original branded sidebar and activity dashboard remains the primary TUI.
258
+ Doctor, sessions, runs, logs, and configuration extend that shell rather than
259
+ replace it with single-purpose primary screens.
260
+
261
+ ## Architecture
262
+
263
+ - `src/domain/` contains compatibility schemas, typed errors, shared snapshots,
264
+ activity events, and telemetry policy.
265
+ - `src/services/` contains Effect service contracts and Bun production layers.
266
+ - `src/ui/` adapts the managed Effect runtime to Solid subscriptions and
267
+ commands.
268
+ - Solid renders authoritative snapshots and a separate bounded activity stream;
269
+ it does not own discovery timers or infrastructure state.
270
+ - `src/services/mock.ts` provides a deterministic mock `Application` layer for
271
+ presentation work.
272
+
273
+ Interactive startup uses one `ManagedRuntime`, one OpenTUI renderer scope, and
274
+ `BunRuntime.runMain` inside the compiled host. The public package launcher and
275
+ managed ACP/Playwright children use Node. The top-level doctor command never
276
+ imports the TUI module.
277
+ OpenTUI signal handlers are disabled so Effect owns
278
+ `SIGINT`/`SIGTERM`; renderer destruction restores the terminal and closes the
279
+ runtime scope.
280
+
281
+ ## Verification
282
+
283
+ ```bash
284
+ bun run check
285
+ bun test
286
+ bun run release:pack --target current
287
+ bun run validate:pilotty # when pilotty is installed
288
+ ```
35
289
 
36
- The CLI advertises `browser-proxy-v6` and `browser-dom-snapshot-v1` so browser
37
- cards can share one local browser profile, route pages and subresources through
38
- the local browser-kit proxy, and publish a sanitized read-only fallback for
39
- collaborators. The normal command
40
- keeps screenshot capture available. `--dev` skips screenshot-engine setup and
41
- allows Railway PR previews for browser-card development.
290
+ The suite covers compatibility fixtures, typed invalid boundaries, resolver
291
+ precedence, passive/deep doctor behavior, interruption cleanup, cross-process
292
+ bridge locks, first-run reselection, privacy redaction, scoped shutdown, and
293
+ wide/narrow/tiny OpenTUI rendering.
@@ -0,0 +1,154 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from "node:child_process";
3
+ import { accessSync, constants, readFileSync } from "node:fs";
4
+ import { createRequire } from "node:module";
5
+ import path from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import { isPathInside, launcherRuntimeError } from "./launcher-core.mjs";
8
+ import { detectLinuxLibc, resolveHostTarget } from "./platform.mjs";
9
+
10
+ const require = createRequire(import.meta.url);
11
+ const packageRoot = fileURLToPath(new URL("..", import.meta.url));
12
+
13
+ function fail(message) {
14
+ process.stderr.write(`${message}\n`);
15
+ process.exitCode = 1;
16
+ }
17
+
18
+ function readManifest(filename, label) {
19
+ try {
20
+ const value = JSON.parse(readFileSync(filename, "utf8"));
21
+ if (!value || typeof value !== "object") throw new Error("not an object");
22
+ return value;
23
+ } catch (error) {
24
+ throw new Error(`${label} manifest is unreadable at ${filename}: ${String(error)}`);
25
+ }
26
+ }
27
+
28
+ function resolveDistribution() {
29
+ const manifest = readManifest(path.join(packageRoot, "package.json"), "IdeaWave launcher");
30
+ if (manifest.name !== "ideawave" || typeof manifest.version !== "string" || !manifest.version) {
31
+ throw new Error("IdeaWave launcher manifest has an invalid package name or version. Reinstall IdeaWave.");
32
+ }
33
+
34
+ const libc = process.platform === "linux" ? detectLinuxLibc() : null;
35
+ const target = resolveHostTarget({
36
+ platform: process.platform,
37
+ architecture: process.arch,
38
+ libc,
39
+ });
40
+ if (!target) {
41
+ const tuple =
42
+ process.platform === "linux"
43
+ ? `${process.platform}/${process.arch}/${libc ?? "unknown-libc"}`
44
+ : `${process.platform}/${process.arch}`;
45
+ throw new Error(
46
+ `IdeaWave does not have a native host for ${tuple}. Supported targets are macOS and Linux glibc/musl on x64 or arm64, and Windows on x64.`,
47
+ );
48
+ }
49
+
50
+ let hostManifestPath;
51
+ try {
52
+ hostManifestPath = require.resolve(`${target.packageName}/package.json`);
53
+ } catch {
54
+ throw new Error(
55
+ `IdeaWave's ${target.key} host package is missing (expected ${target.packageName}@${manifest.version}). ` +
56
+ `Reinstall with optional dependencies enabled, for example \`npm install ideawave@${manifest.version} --include=optional\`.`,
57
+ );
58
+ }
59
+ const hostManifest = readManifest(hostManifestPath, `IdeaWave ${target.key} host`);
60
+ if (hostManifest.name !== target.packageName) {
61
+ throw new Error(
62
+ `IdeaWave host package mismatch: expected ${target.packageName}, found ${hostManifest.name ?? "an unnamed package"}. Reinstall IdeaWave.`,
63
+ );
64
+ }
65
+ if (hostManifest.version !== manifest.version) {
66
+ throw new Error(
67
+ `IdeaWave host version mismatch: launcher ${manifest.version}, host ${hostManifest.version ?? "unknown"}. ` +
68
+ "Clear the package-runner cache and reinstall IdeaWave.",
69
+ );
70
+ }
71
+
72
+ const hostRoot = path.dirname(hostManifestPath);
73
+ const executable = path.resolve(hostRoot, ...target.executable.split("/"));
74
+ if (!isPathInside(hostRoot, executable)) {
75
+ throw new Error(`IdeaWave host package ${target.packageName} declared an unsafe executable path.`);
76
+ }
77
+ try {
78
+ accessSync(executable, process.platform === "win32" ? constants.F_OK : constants.X_OK);
79
+ } catch {
80
+ throw new Error(`IdeaWave's native host is missing or not executable at ${executable}. Reinstall IdeaWave.`);
81
+ }
82
+
83
+ let playwrightManifestPath;
84
+ try {
85
+ playwrightManifestPath = require.resolve("playwright-core/package.json");
86
+ } catch {
87
+ throw new Error("IdeaWave's Playwright runtime sidecar is missing. Reinstall IdeaWave.");
88
+ }
89
+ const playwrightManifest = readManifest(playwrightManifestPath, "IdeaWave Playwright sidecar");
90
+ const expectedPlaywright = manifest.dependencies?.["playwright-core"];
91
+ if (typeof expectedPlaywright !== "string" || playwrightManifest.version !== expectedPlaywright) {
92
+ throw new Error(
93
+ `IdeaWave Playwright sidecar mismatch: expected ${expectedPlaywright ?? "an exact pinned version"}, ` +
94
+ `found ${playwrightManifest.version ?? "unknown"}. Reinstall IdeaWave.`,
95
+ );
96
+ }
97
+
98
+ return {
99
+ executable,
100
+ manifest,
101
+ playwrightRoot: path.dirname(playwrightManifestPath),
102
+ target,
103
+ };
104
+ }
105
+
106
+ const runtimeError = launcherRuntimeError();
107
+ if (runtimeError) {
108
+ fail(runtimeError);
109
+ } else {
110
+ try {
111
+ const distribution = resolveDistribution();
112
+ const child = spawn(distribution.executable, process.argv.slice(2), {
113
+ cwd: process.cwd(),
114
+ stdio: "inherit",
115
+ env: {
116
+ ...process.env,
117
+ IDEAWAVE_DISTRIBUTION_MODE: "standalone",
118
+ IDEAWAVE_LAUNCHER_NODE_PATH: process.execPath,
119
+ IDEAWAVE_LAUNCHER_NODE_VERSION: process.version,
120
+ IDEAWAVE_PLAYWRIGHT_CORE_PATH: distribution.playwrightRoot,
121
+ IDEAWAVE_PRODUCT_VERSION: distribution.manifest.version,
122
+ },
123
+ });
124
+
125
+ const signalHandlers = new Map();
126
+ for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
127
+ if (process.platform === "win32" && signal === "SIGHUP") continue;
128
+ const handler = () => {
129
+ if (child.exitCode === null && child.signalCode === null) child.kill(signal);
130
+ };
131
+ signalHandlers.set(signal, handler);
132
+ process.on(signal, handler);
133
+ }
134
+ const removeSignalHandlers = () => {
135
+ for (const [signal, handler] of signalHandlers) process.removeListener(signal, handler);
136
+ signalHandlers.clear();
137
+ };
138
+
139
+ child.once("error", (error) => {
140
+ removeSignalHandlers();
141
+ fail(`IdeaWave failed to start its ${distribution.target.key} host: ${String(error)}`);
142
+ });
143
+ child.once("exit", (code, signal) => {
144
+ removeSignalHandlers();
145
+ if (signal && process.platform !== "win32") {
146
+ process.kill(process.pid, signal);
147
+ return;
148
+ }
149
+ process.exitCode = code ?? (signal ? 1 : 0);
150
+ });
151
+ } catch (error) {
152
+ fail(error instanceof Error ? error.message : String(error));
153
+ }
154
+ }
@@ -0,0 +1,35 @@
1
+ import path from "node:path";
2
+
3
+ export const MINIMUM_NODE_MAJOR = 22;
4
+
5
+ export function parseNodeMajor(version) {
6
+ const match = /^v?(\d+)(?:\.|$)/.exec(String(version));
7
+ return match ? Number(match[1]) : null;
8
+ }
9
+
10
+ /**
11
+ * @param {{ nodeVersion?: string, bunVersion?: string | null, runtimeName?: string } | undefined} options
12
+ */
13
+ export function launcherRuntimeError(options = {}) {
14
+ const {
15
+ nodeVersion = process.version,
16
+ bunVersion = process.versions?.bun,
17
+ runtimeName = process.release?.name,
18
+ } = options;
19
+ if (bunVersion) {
20
+ return "IdeaWave's launcher must run on Node.js, not Bun. Run `bunx ideawave` without `--bun`, or use `npx ideawave`.";
21
+ }
22
+ if (runtimeName !== "node") {
23
+ return `IdeaWave's launcher requires Node.js 22 or newer; detected ${runtimeName || "an unknown runtime"}.`;
24
+ }
25
+ const major = parseNodeMajor(nodeVersion);
26
+ if (major === null || major < MINIMUM_NODE_MAJOR) {
27
+ return `IdeaWave requires Node.js 22 or newer; this launcher is using ${nodeVersion || "an unknown version"}.`;
28
+ }
29
+ return null;
30
+ }
31
+
32
+ export function isPathInside(root, candidate) {
33
+ const relative = path.relative(path.resolve(root), path.resolve(candidate));
34
+ return relative !== "" && relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
35
+ }
@@ -0,0 +1,100 @@
1
+ const host = (key, options) => Object.freeze({ key, ...options });
2
+
3
+ const HOSTS = Object.freeze({
4
+ "darwin-arm64": host("darwin-arm64", {
5
+ packageName: "ideawave-host-darwin-arm64",
6
+ bunTarget: "bun-darwin-arm64",
7
+ os: "darwin",
8
+ cpu: "arm64",
9
+ executable: "bin/ideawave-host",
10
+ }),
11
+ "darwin-x64": host("darwin-x64", {
12
+ packageName: "ideawave-host-darwin-x64",
13
+ bunTarget: "bun-darwin-x64-baseline",
14
+ os: "darwin",
15
+ cpu: "x64",
16
+ executable: "bin/ideawave-host",
17
+ }),
18
+ "linux-arm64-glibc": host("linux-arm64-glibc", {
19
+ packageName: "ideawave-host-linux-arm64",
20
+ bunTarget: "bun-linux-arm64",
21
+ os: "linux",
22
+ cpu: "arm64",
23
+ libc: "glibc",
24
+ executable: "bin/ideawave-host",
25
+ }),
26
+ "linux-arm64-musl": host("linux-arm64-musl", {
27
+ packageName: "ideawave-host-linux-arm64-musl",
28
+ bunTarget: "bun-linux-arm64-musl",
29
+ os: "linux",
30
+ cpu: "arm64",
31
+ libc: "musl",
32
+ executable: "bin/ideawave-host",
33
+ }),
34
+ "linux-x64-glibc": host("linux-x64-glibc", {
35
+ packageName: "ideawave-host-linux-x64",
36
+ bunTarget: "bun-linux-x64-baseline",
37
+ os: "linux",
38
+ cpu: "x64",
39
+ libc: "glibc",
40
+ executable: "bin/ideawave-host",
41
+ }),
42
+ "linux-x64-musl": host("linux-x64-musl", {
43
+ packageName: "ideawave-host-linux-x64-musl",
44
+ bunTarget: "bun-linux-x64-baseline-musl",
45
+ os: "linux",
46
+ cpu: "x64",
47
+ libc: "musl",
48
+ executable: "bin/ideawave-host",
49
+ }),
50
+ "win32-x64": host("win32-x64", {
51
+ packageName: "ideawave-host-windows",
52
+ bunTarget: "bun-windows-x64-baseline",
53
+ os: "win32",
54
+ cpu: "x64",
55
+ executable: "bin/ideawave-host.exe",
56
+ }),
57
+ });
58
+
59
+ export const SUPPORTED_HOSTS = HOSTS;
60
+
61
+ /**
62
+ * @param {{
63
+ * platform?: string,
64
+ * report?: { header?: Record<string, unknown> } | null,
65
+ * } | undefined} options
66
+ */
67
+ export function detectLinuxLibc(options = {}) {
68
+ const { platform = process.platform, report = undefined } = options;
69
+ if (platform !== "linux") return null;
70
+
71
+ let runtimeReport = report;
72
+ if (runtimeReport === undefined) {
73
+ try {
74
+ runtimeReport = process.report?.getReport();
75
+ } catch {
76
+ return null;
77
+ }
78
+ }
79
+ if (!runtimeReport || typeof runtimeReport !== "object") return null;
80
+
81
+ const header = runtimeReport.header;
82
+ if (!header || typeof header !== "object") return null;
83
+ if (typeof header.glibcVersionRuntime === "string" && header.glibcVersionRuntime) {
84
+ return "glibc";
85
+ }
86
+
87
+ // Node's Linux diagnostic report exposes glibcVersionRuntime only on glibc.
88
+ // A real Linux report with no such field is therefore a musl runtime. An
89
+ // absent/malformed report remains unknown instead of silently choosing musl.
90
+ return typeof header.nodejsVersion === "string" || typeof header.nodeVersion === "string" ? "musl" : null;
91
+ }
92
+
93
+ export function resolveHostTarget({
94
+ platform = process.platform,
95
+ architecture = process.arch,
96
+ libc = platform === "linux" ? detectLinuxLibc({ platform }) : null,
97
+ } = {}) {
98
+ const key = platform === "linux" ? `${platform}-${architecture}-${libc ?? "unknown"}` : `${platform}-${architecture}`;
99
+ return HOSTS[key] ?? null;
100
+ }
package/package.json CHANGED
@@ -1,44 +1,49 @@
1
1
  {
2
2
  "name": "ideawave",
3
- "version": "0.0.103",
4
- "description": "IdeaWave CLI",
3
+ "version": "0.0.105",
4
+ "description": "IdeaWave local agent host and terminal UI",
5
5
  "type": "module",
6
+ "license": "UNLICENSED",
6
7
  "bin": {
7
- "ideawave": "dist/index.mjs"
8
+ "ideawave": "bin/ideawave.mjs"
8
9
  },
9
10
  "files": [
10
- "dist/index.mjs",
11
+ "bin",
11
12
  "README.md"
12
13
  ],
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
13
17
  "engines": {
14
- "node": ">=18"
18
+ "node": ">=22"
15
19
  },
16
20
  "homepage": "https://ideawave.app",
17
21
  "repository": {
18
22
  "type": "git",
19
23
  "url": "git+https://github.com/LoopedIn-ai/ideawave-app.git",
20
- "directory": "apps/web/npm/ideawave"
24
+ "directory": "apps/tui"
21
25
  },
22
26
  "bugs": {
23
27
  "url": "https://github.com/LoopedIn-ai/ideawave-app/issues"
24
28
  },
25
29
  "keywords": [
26
30
  "ideawave",
27
- "cli",
31
+ "tui",
28
32
  "agent",
29
33
  "whiteboard",
30
- "mcp"
34
+ "mcp",
35
+ "acp"
31
36
  ],
32
- "license": "UNLICENSED",
33
37
  "dependencies": {
34
- "@agentclientprotocol/sdk": "^1.3.0",
35
- "@modelcontextprotocol/sdk": "^1.29.0",
36
- "css-tree": "^3.2.1",
37
- "playwright-core": "1.60.0",
38
- "ws": "^8.21.0",
39
- "zod": "^4.3.6"
38
+ "playwright-core": "1.62.1"
40
39
  },
41
- "publishConfig": {
42
- "access": "public"
40
+ "optionalDependencies": {
41
+ "ideawave-host-darwin-arm64": "0.0.105",
42
+ "ideawave-host-darwin-x64": "0.0.105",
43
+ "ideawave-host-linux-arm64": "0.0.105",
44
+ "ideawave-host-linux-arm64-musl": "0.0.105",
45
+ "ideawave-host-linux-x64": "0.0.105",
46
+ "ideawave-host-linux-x64-musl": "0.0.105",
47
+ "ideawave-host-windows": "0.0.105"
43
48
  }
44
49
  }