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 +277 -25
- package/bin/ideawave.mjs +154 -0
- package/bin/launcher-core.mjs +35 -0
- package/bin/platform.mjs +100 -0
- package/package.json +22 -17
- package/dist/index.mjs +0 -1174
package/README.md
CHANGED
|
@@ -1,41 +1,293 @@
|
|
|
1
|
-
# IdeaWave
|
|
1
|
+
# IdeaWave TUI
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
23
|
+
bun install
|
|
24
|
+
bun dev
|
|
7
25
|
```
|
|
8
26
|
|
|
9
|
-
|
|
27
|
+
To create and smoke-test the installable release artifacts for the current
|
|
28
|
+
platform:
|
|
10
29
|
|
|
11
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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.
|
package/bin/ideawave.mjs
ADDED
|
@@ -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
|
+
}
|
package/bin/platform.mjs
ADDED
|
@@ -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.
|
|
4
|
-
"description": "IdeaWave
|
|
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": "
|
|
8
|
+
"ideawave": "bin/ideawave.mjs"
|
|
8
9
|
},
|
|
9
10
|
"files": [
|
|
10
|
-
"
|
|
11
|
+
"bin",
|
|
11
12
|
"README.md"
|
|
12
13
|
],
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public"
|
|
16
|
+
},
|
|
13
17
|
"engines": {
|
|
14
|
-
"node": ">=
|
|
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/
|
|
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
|
-
"
|
|
31
|
+
"tui",
|
|
28
32
|
"agent",
|
|
29
33
|
"whiteboard",
|
|
30
|
-
"mcp"
|
|
34
|
+
"mcp",
|
|
35
|
+
"acp"
|
|
31
36
|
],
|
|
32
|
-
"license": "UNLICENSED",
|
|
33
37
|
"dependencies": {
|
|
34
|
-
"
|
|
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
|
-
"
|
|
42
|
-
"
|
|
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
|
}
|