@atolljs/cli 0.1.6 → 0.1.8

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
@@ -42,6 +42,10 @@ atoll add mfe <name> publishable micro-frontend — framework-free
42
42
  contract (defineIslandContract) + worker entry
43
43
  attaching it + vite.mfe.config.ts publish build
44
44
  → dist-mfe/<name>.worker.js behind CORS
45
+ atoll add devtools [name] observability — src/atoll/devtools.ts init module
46
+ (import first in the app entry) + @atolljs/devtools
47
+ dev dep. Browser apps get the ?__atoll_devtools gate;
48
+ node hosts get the /node entry + ATOLL_DEVTOOLS=1
45
49
 
46
50
  atoll doctor [--fix] verify deps, lockfile, .npmrc policy,
47
51
  worker-entry detectability, COOP/COEP headers
package/dist/cli.js CHANGED
@@ -333,7 +333,7 @@ ${memImport}import type { ${P}Worker } from './${name}.worker';
333
333
  export const ${c} = connectWorker<${P}Worker>({
334
334
  worker: () =>
335
335
  new Worker(new URL('./${name}.worker.ts', import.meta.url), { type: 'module' }),
336
- ${memLine} poolSize: 'auto',
336
+ ${memLine} workers: 'auto',
337
337
  });
338
338
  export type ${P}Client = typeof ${c};
339
339
 
@@ -2194,6 +2194,57 @@ export async function register() {
2194
2194
  `
2195
2195
  };
2196
2196
  }
2197
+ function devtoolsSetup(name, host, wsUrl) {
2198
+ if (host === "node") {
2199
+ return `import { initDevtools } from '@atolljs/devtools/node';
2200
+
2201
+ /**
2202
+ * Devtools sink \u2014 imported first from the entry so it's installed before any
2203
+ * pool spawns (worker INIT carries the flag at spawn time; late installs
2204
+ * leave existing workers silent).
2205
+ *
2206
+ * No-op unless the process was started with ATOLL_DEVTOOLS=1 \u2014 then events
2207
+ * stream over WebSocket to the standalone dashboard (\`atoll devtools\`,
2208
+ * http://127.0.0.1:4780). Node has no BroadcastChannel page context, so the
2209
+ * aggregate server is the transport.
2210
+ */
2211
+ initDevtools({ session: { name: '${name}' } });
2212
+ `;
2213
+ }
2214
+ if (wsUrl) {
2215
+ return `import { initDevtools } from '@atolljs/devtools';
2216
+
2217
+ /**
2218
+ * Devtools sink \u2014 imported first from the app entry so it's installed before
2219
+ * any pool spawns (worker INIT carries the flag at spawn time; late installs
2220
+ * leave existing workers silent).
2221
+ *
2222
+ * No-op unless the page URL carries ?__atoll_devtools \u2014 then events stream
2223
+ * over WebSocket to the standalone aggregate dashboard (\`atoll devtools\`,
2224
+ * http://127.0.0.1:4780). This app's dev server doesn't mount /__atoll/, so
2225
+ * the overlay is off and the aggregate server is the viewer.
2226
+ */
2227
+ initDevtools({
2228
+ session: { name: '${name}' },
2229
+ overlay: false,
2230
+ url: '${wsUrl}',
2231
+ });
2232
+ `;
2233
+ }
2234
+ return `import { initDevtools } from '@atolljs/devtools';
2235
+
2236
+ /**
2237
+ * Devtools sink \u2014 imported first from the app entry so it's installed before
2238
+ * any pool spawns (worker INIT carries the flag at spawn time; late installs
2239
+ * leave existing workers silent).
2240
+ *
2241
+ * No-op unless the page URL carries ?__atoll_devtools \u2014 then events stream
2242
+ * over BroadcastChannel on the app's own origin and a flyout overlay mounts.
2243
+ * /__atoll/ itself is served by the @atolljs/vite plugin (plugins: [atoll()]).
2244
+ */
2245
+ initDevtools({ session: { name: '${name}' } });
2246
+ `;
2247
+ }
2197
2248
 
2198
2249
  // src/commands.ts
2199
2250
  var NODE_HOSTS = ["node", "nestjs"];
@@ -2292,7 +2343,8 @@ var ADD_KINDS = [
2292
2343
  "housed",
2293
2344
  "route",
2294
2345
  "component",
2295
- "instrumentation"
2346
+ "instrumentation",
2347
+ "devtools"
2296
2348
  ];
2297
2349
  var KIND_SCOPE = {
2298
2350
  worker: null,
@@ -2305,7 +2357,8 @@ var KIND_SCOPE = {
2305
2357
  housed: ["nestjs"],
2306
2358
  route: ["nextjs"],
2307
2359
  component: ["nextjs"],
2308
- instrumentation: ["nextjs"]
2360
+ instrumentation: ["nextjs"],
2361
+ devtools: null
2309
2362
  };
2310
2363
  var KIND_VARIANTS = {
2311
2364
  island: ["facade", "mono"],
@@ -2399,7 +2452,7 @@ async function runAdd(ctx) {
2399
2452
  } else {
2400
2453
  nameArg = rest[1];
2401
2454
  }
2402
- const name = await pickName(ctx, kind, nameArg);
2455
+ const name = kind === "devtools" ? nameArg ?? flag(ctx.args, "name") ?? project.pkg?.name?.split("/").pop() ?? "app" : await pickName(ctx, kind, nameArg);
2403
2456
  if (!name) return 1;
2404
2457
  const force = { force: hasFlag(ctx.args, "force") };
2405
2458
  const atollDir = flag(ctx.args, "dir") ?? rel(join3(project.srcDir, "atoll"));
@@ -2578,6 +2631,45 @@ pools warm at boot \u2014 extend register() as more routes appear.`
2578
2631
  );
2579
2632
  return 0;
2580
2633
  }
2634
+ case "devtools": {
2635
+ const host = hostOf(framework);
2636
+ const wsUrl = host === "browser" && framework === "angular" ? "ws://127.0.0.1:4780/events" : void 0;
2637
+ const file = `${atollDir}/devtools.ts`;
2638
+ await writeTree(
2639
+ ctx.io,
2640
+ ctx.cwd,
2641
+ [{ path: file, content: devtoolsSetup(name, host, wsUrl) }],
2642
+ force
2643
+ );
2644
+ if (missingDeps(project.pkg, ["@atolljs/devtools"]).length) {
2645
+ await offerInstall(ctx, project, ["-D", "@atolljs/devtools"]);
2646
+ }
2647
+ const srcRel = rel(project.srcDir);
2648
+ const spec = file.startsWith(`${srcRel}/`) ? `./${file.slice(srcRel.length + 1).replace(/\.ts$/, "")}` : `./${file.replace(/\.ts$/, "")}`;
2649
+ ctx.io.print("");
2650
+ ctx.io.print(ctx.io.fmt.strong("wire it:"));
2651
+ ctx.io.print(` import the module first in your entry \u2014 before any pool spawns:`);
2652
+ ctx.io.print(ctx.io.fmt.accent(` import '${spec}';`));
2653
+ if (host === "node") {
2654
+ ctx.io.print(
2655
+ ` ${ctx.io.fmt.accent("ATOLL_DEVTOOLS=1")} starts streaming \xB7 ${ctx.io.fmt.accent("atoll devtools")} serves the dashboard`
2656
+ );
2657
+ } else if (wsUrl) {
2658
+ ctx.io.print(
2659
+ ` ${ctx.io.fmt.accent("atoll devtools")} serves the aggregate dashboard \xB7 open the app with ${ctx.io.fmt.accent("?__atoll_devtools")}`
2660
+ );
2661
+ } else {
2662
+ ctx.io.print(
2663
+ ` open the app with ${ctx.io.fmt.accent("?__atoll_devtools")} \u2014 the flyout mounts and /__atoll/ serves the dashboard`
2664
+ );
2665
+ ctx.io.print(
2666
+ ctx.io.fmt.dim(
2667
+ ` /__atoll/ needs the @atolljs/vite plugin (plugins: [atoll()]) \u2014 no plugin? run \`atoll devtools\` + pass transport: 'websocket'`
2668
+ )
2669
+ );
2670
+ }
2671
+ return 0;
2672
+ }
2581
2673
  default:
2582
2674
  ctx.io.error(`unhandled add kind ${kind}`);
2583
2675
  return 1;
@@ -2639,6 +2731,41 @@ async function runCreate(ctx) {
2639
2731
  }
2640
2732
  return 0;
2641
2733
  }
2734
+ async function runDevtools(ctx) {
2735
+ const { io } = ctx;
2736
+ const spec = "@atolljs/devtools/server";
2737
+ const mod = await import(spec).catch(async () => {
2738
+ const local = new URL("../../devtools/dist/server.js", import.meta.url).href;
2739
+ return import(local).catch(() => null);
2740
+ });
2741
+ if (!mod?.createDevtoolsServer) {
2742
+ io.error(`@atolljs/devtools isn't installed \u2014 ${io.fmt.accent("npm i -D @atolljs/devtools")}`);
2743
+ return 1;
2744
+ }
2745
+ const port = Number(flag(ctx.args, "port") ?? 4780);
2746
+ const otlpEndpoint = flag(ctx.args, "otlp");
2747
+ const headers = Object.fromEntries(
2748
+ (flag(ctx.args, "otlp-headers") ?? "").split(",").filter((h) => h.includes("=")).map((h) => [h.slice(0, h.indexOf("=")).trim(), h.slice(h.indexOf("=") + 1).trim()])
2749
+ );
2750
+ const server = await mod.createDevtoolsServer({
2751
+ port: Number.isFinite(port) ? port : 4780,
2752
+ ...otlpEndpoint ? {
2753
+ otlp: {
2754
+ endpoint: otlpEndpoint,
2755
+ headers,
2756
+ serviceName: flag(ctx.args, "otlp-service"),
2757
+ onError: (e) => io.warn(`otlp: ${e.message}`)
2758
+ }
2759
+ } : {}
2760
+ });
2761
+ io.print(`atoll devtools \u2192 ${io.fmt.accent(server.url)}`);
2762
+ io.print(io.fmt.dim(" ingest endpoint: " + server.url.replace("http://", "ws://") + "/events"));
2763
+ if (otlpEndpoint) io.print(io.fmt.dim(" otlp export: " + otlpEndpoint));
2764
+ io.print(io.fmt.dim(" in the app: connectDevtools() from '@atolljs/devtools'"));
2765
+ io.warn("serving \u2014 Ctrl+C to stop");
2766
+ return new Promise(() => {
2767
+ });
2768
+ }
2642
2769
  function runDoctor(ctx) {
2643
2770
  const project = detectProject(ctx.cwd);
2644
2771
  const fix = hasFlag(ctx.args, "fix");
@@ -2750,6 +2877,8 @@ var HELP = (fmt) => `${fmt.strong("atoll")} \u2014 bootstrap multithreading ${fm
2750
2877
  route nextjs: api route pool \u2014 task | client variants
2751
2878
  component nextjs: 'use client' hooks component over a pool
2752
2879
  instrumentation nextjs: server bootstrap that warms route pools
2880
+ devtools observability \u2014 init module + @atolljs/devtools install
2881
+ (browser: ?__atoll_devtools + /__atoll/ \xB7 node: ATOLL_DEVTOOLS=1)
2753
2882
 
2754
2883
  Frameworks: react vue solid svelte angular nestjs nextjs node
2755
2884
  [--dir <path>] [--memory <name>|--no-memory] [--variant <v>] [--force]
@@ -2758,6 +2887,11 @@ var HELP = (fmt) => `${fmt.strong("atoll")} \u2014 bootstrap multithreading ${fm
2758
2887
  Check the setup \u2014 deps, lockfile, .npmrc policy, bundler-detectable
2759
2888
  worker entries, COOP/COEP headers. --fix writes a hardened .npmrc.
2760
2889
 
2890
+ ${fmt.accent("atoll devtools")} [--port 4780] [--otlp <url>] [--otlp-headers k=v,\u2026] [--otlp-service <name>]
2891
+ Serve the local devtools dashboard \u2014 pools, task timings, shared-memory
2892
+ writes, island traffic. Apps connect via connectDevtools().
2893
+ --otlp also forwards every session to an OpenTelemetry OTLP/HTTP endpoint.
2894
+
2761
2895
  ${fmt.dim(`flags: --yes/-y accepts defaults, --force overwrites existing files,
2762
2896
  --no-install skips dependency installation`)}
2763
2897
  `;
@@ -2778,6 +2912,8 @@ async function runCli(argv, io, cwd) {
2778
2912
  return await runAdd(ctx);
2779
2913
  case "doctor":
2780
2914
  return runDoctor(ctx);
2915
+ case "devtools":
2916
+ return await runDevtools(ctx);
2781
2917
  case "help":
2782
2918
  case void 0:
2783
2919
  io.print(HELP(io.fmt));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atolljs/cli",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "atoll — scaffold worker pools, shared-memory contracts, and framework islands (experimental)",
5
5
  "homepage": "https://jwhenry3.github.io/atolljs/consumer/quickstart/",
6
6
  "type": "module",
package/src/cli.ts CHANGED
@@ -12,7 +12,7 @@
12
12
  */
13
13
  import { consoleIo, type Fmt, type Io } from './io.ts';
14
14
  import { hasFlag, parseArgs } from './parse.ts';
15
- import { runAdd, runCreate, runDoctor, runInit, type Ctx } from './commands.ts';
15
+ import { runAdd, runCreate, runDevtools, runDoctor, runInit, type Ctx } from './commands.ts';
16
16
 
17
17
  const HELP = (fmt: Fmt) => `${fmt.strong('atoll')} — bootstrap multithreading ${fmt.dim('(experimental)')}
18
18
 
@@ -38,6 +38,8 @@ const HELP = (fmt: Fmt) => `${fmt.strong('atoll')} — bootstrap multithreading
38
38
  route nextjs: api route pool — task | client variants
39
39
  component nextjs: 'use client' hooks component over a pool
40
40
  instrumentation nextjs: server bootstrap that warms route pools
41
+ devtools observability — init module + @atolljs/devtools install
42
+ (browser: ?__atoll_devtools + /__atoll/ · node: ATOLL_DEVTOOLS=1)
41
43
 
42
44
  Frameworks: react vue solid svelte angular nestjs nextjs node
43
45
  [--dir <path>] [--memory <name>|--no-memory] [--variant <v>] [--force]
@@ -46,6 +48,11 @@ const HELP = (fmt: Fmt) => `${fmt.strong('atoll')} — bootstrap multithreading
46
48
  Check the setup — deps, lockfile, .npmrc policy, bundler-detectable
47
49
  worker entries, COOP/COEP headers. --fix writes a hardened .npmrc.
48
50
 
51
+ ${fmt.accent('atoll devtools')} [--port 4780] [--otlp <url>] [--otlp-headers k=v,…] [--otlp-service <name>]
52
+ Serve the local devtools dashboard — pools, task timings, shared-memory
53
+ writes, island traffic. Apps connect via connectDevtools().
54
+ --otlp also forwards every session to an OpenTelemetry OTLP/HTTP endpoint.
55
+
49
56
  ${fmt.dim(`flags: --yes/-y accepts defaults, --force overwrites existing files,
50
57
  --no-install skips dependency installation`)}
51
58
  `;
@@ -72,6 +79,8 @@ export async function runCli(argv: readonly string[], io: Io, cwd: string): Prom
72
79
  return await runAdd(ctx);
73
80
  case 'doctor':
74
81
  return runDoctor(ctx);
82
+ case 'devtools':
83
+ return await runDevtools(ctx);
75
84
  case 'help':
76
85
  case undefined:
77
86
  io.print(HELP(io.fmt));
package/src/commands.ts CHANGED
@@ -145,6 +145,7 @@ const ADD_KINDS = [
145
145
  'route',
146
146
  'component',
147
147
  'instrumentation',
148
+ 'devtools',
148
149
  ] as const;
149
150
  type AddKind = (typeof ADD_KINDS)[number];
150
151
 
@@ -161,6 +162,7 @@ const KIND_SCOPE: Record<AddKind, readonly Framework[] | null> = {
161
162
  route: ['nextjs'],
162
163
  component: ['nextjs'],
163
164
  instrumentation: ['nextjs'],
165
+ devtools: null,
164
166
  };
165
167
 
166
168
  /**
@@ -283,7 +285,12 @@ export async function runAdd(ctx: Ctx): Promise<number> {
283
285
  nameArg = rest[1];
284
286
  }
285
287
 
286
- const name = await pickName(ctx, kind, nameArg);
288
+ // devtools writes a fixed devtools.ts — a positional/--name argument
289
+ // becomes the dashboard session label, not a file name.
290
+ const name =
291
+ kind === 'devtools'
292
+ ? (nameArg ?? flag(ctx.args, 'name') ?? project.pkg?.name?.split('/').pop() ?? 'app')
293
+ : await pickName(ctx, kind, nameArg);
287
294
  if (!name) return 1;
288
295
  const force = { force: hasFlag(ctx.args, 'force') };
289
296
  const atollDir = flag(ctx.args, 'dir') ?? rel(join(project.srcDir, 'atoll'));
@@ -480,6 +487,54 @@ export async function runAdd(ctx: Ctx): Promise<number> {
480
487
  return 0;
481
488
  }
482
489
 
490
+ case 'devtools': {
491
+ const host = hostOf(framework);
492
+ // Angular's CLI dev server mounts no /__atoll/ — stream to the
493
+ // aggregate server instead of the BroadcastChannel + flyout path.
494
+ const wsUrl = host === 'browser' && framework === 'angular'
495
+ ? 'ws://127.0.0.1:4780/events'
496
+ : undefined;
497
+ const file = `${atollDir}/devtools.ts`;
498
+ await writeTree(
499
+ ctx.io,
500
+ ctx.cwd,
501
+ [{ path: file, content: T.devtoolsSetup(name, host, wsUrl) }],
502
+ force,
503
+ );
504
+ if (missingDeps(project.pkg, ['@atolljs/devtools']).length) {
505
+ await offerInstall(ctx, project, ['-D', '@atolljs/devtools']);
506
+ }
507
+ // Print the specifier relative to srcDir — the import lands in the
508
+ // app entry (src/main.ts style), not in cwd.
509
+ const srcRel = rel(project.srcDir);
510
+ const spec = file.startsWith(`${srcRel}/`)
511
+ ? `./${file.slice(srcRel.length + 1).replace(/\.ts$/, '')}`
512
+ : `./${file.replace(/\.ts$/, '')}`;
513
+ ctx.io.print('');
514
+ ctx.io.print(ctx.io.fmt.strong('wire it:'));
515
+ ctx.io.print(` import the module first in your entry — before any pool spawns:`);
516
+ ctx.io.print(ctx.io.fmt.accent(` import '${spec}';`));
517
+ if (host === 'node') {
518
+ ctx.io.print(
519
+ ` ${ctx.io.fmt.accent('ATOLL_DEVTOOLS=1')} starts streaming · ${ctx.io.fmt.accent('atoll devtools')} serves the dashboard`,
520
+ );
521
+ } else if (wsUrl) {
522
+ ctx.io.print(
523
+ ` ${ctx.io.fmt.accent('atoll devtools')} serves the aggregate dashboard · open the app with ${ctx.io.fmt.accent('?__atoll_devtools')}`,
524
+ );
525
+ } else {
526
+ ctx.io.print(
527
+ ` open the app with ${ctx.io.fmt.accent('?__atoll_devtools')} — the flyout mounts and /__atoll/ serves the dashboard`,
528
+ );
529
+ ctx.io.print(
530
+ ctx.io.fmt.dim(
531
+ ` /__atoll/ needs the @atolljs/vite plugin (plugins: [atoll()]) — no plugin? run \`atoll devtools\` + pass transport: 'websocket'`,
532
+ ),
533
+ );
534
+ }
535
+ return 0;
536
+ }
537
+
483
538
  default:
484
539
  ctx.io.error(`unhandled add kind ${kind}`);
485
540
  return 1;
@@ -557,6 +612,65 @@ export async function runCreate(ctx: Ctx): Promise<number> {
557
612
  return 0;
558
613
  }
559
614
 
615
+ /* ── atoll devtools ─────────────────────────────────────────────────────── */
616
+
617
+ /**
618
+ * `atoll devtools [--port N]` — boot the local devtools server + dashboard.
619
+ * The server lives in @atolljs/devtools: resolved as an installed package in
620
+ * consumer projects, with a relative fallback so `npm run atoll devtools`
621
+ * works inside the atoll monorepo where the package isn't installed.
622
+ *
623
+ * Never resolves — the process serves until SIGINT.
624
+ */
625
+ export async function runDevtools(ctx: Ctx): Promise<number> {
626
+ const { io } = ctx;
627
+ const spec = '@atolljs/devtools/server';
628
+ const mod: {
629
+ createDevtoolsServer?: (o: {
630
+ port: number;
631
+ otlp?: { endpoint: string; headers: Record<string, string>; serviceName?: string; onError?: (e: Error) => void };
632
+ }) => Promise<{ url: string }>;
633
+ } | null =
634
+ await import(spec).catch(async () => {
635
+ // In-repo fallback: the workspace package's built dist (extensionless
636
+ // sources can't run under type stripping — `npm run build` there first).
637
+ const local = new URL('../../devtools/dist/server.js', import.meta.url).href;
638
+ return import(local).catch(() => null);
639
+ });
640
+ if (!mod?.createDevtoolsServer) {
641
+ io.error(`@atolljs/devtools isn't installed — ${io.fmt.accent('npm i -D @atolljs/devtools')}`);
642
+ return 1;
643
+ }
644
+ const port = Number(flag(ctx.args, 'port') ?? 4780);
645
+ const otlpEndpoint = flag(ctx.args, 'otlp');
646
+ // `k=v,k2=v2`: the OTEL_EXPORTER_OTLP_HEADERS format.
647
+ const headers = Object.fromEntries(
648
+ (flag(ctx.args, 'otlp-headers') ?? '')
649
+ .split(',')
650
+ .filter((h) => h.includes('='))
651
+ .map((h) => [h.slice(0, h.indexOf('=')).trim(), h.slice(h.indexOf('=') + 1).trim()]),
652
+ );
653
+ const server = await mod.createDevtoolsServer({
654
+ port: Number.isFinite(port) ? port : 4780,
655
+ ...(otlpEndpoint
656
+ ? {
657
+ otlp: {
658
+ endpoint: otlpEndpoint,
659
+ headers,
660
+ serviceName: flag(ctx.args, 'otlp-service'),
661
+ onError: (e: Error) => io.warn(`otlp: ${e.message}`),
662
+ },
663
+ }
664
+ : {}),
665
+ });
666
+ io.print(`atoll devtools → ${io.fmt.accent(server.url)}`);
667
+ io.print(io.fmt.dim(' ingest endpoint: ' + server.url.replace('http://', 'ws://') + '/events'));
668
+ if (otlpEndpoint) io.print(io.fmt.dim(' otlp export: ' + otlpEndpoint));
669
+ io.print(io.fmt.dim(" in the app: connectDevtools() from '@atolljs/devtools'"));
670
+ io.warn('serving — Ctrl+C to stop');
671
+ return new Promise<number>(() => {});
672
+ }
673
+
560
674
  /* ── atoll doctor ───────────────────────────────────────────────────────── */
561
675
 
562
676
  interface Check {
package/src/templates.ts CHANGED
@@ -158,7 +158,7 @@ ${memImport}import type { ${P}Worker } from './${name}.worker';
158
158
  export const ${c} = connectWorker<${P}Worker>({
159
159
  worker: () =>
160
160
  new Worker(new URL('./${name}.worker.ts', import.meta.url), { type: 'module' }),
161
- ${memLine} poolSize: 'auto',
161
+ ${memLine} workers: 'auto',
162
162
  });
163
163
  export type ${P}Client = typeof ${c};
164
164
 
@@ -2163,3 +2163,66 @@ export async function register() {
2163
2163
  };
2164
2164
  }
2165
2165
 
2166
+
2167
+ /**
2168
+ * `add devtools` — the init module, imported first from the app entry so the
2169
+ * sink is installed before any pool spawns (worker INIT carries the flag at
2170
+ * spawn time; late installs leave existing workers silent).
2171
+ *
2172
+ * Three shapes:
2173
+ * - node host → '@atolljs/devtools/node' entry, ATOLL_DEVTOOLS env gate
2174
+ * - browser → '@atolljs/devtools', ?__atoll_devtools URL gate + overlay
2175
+ * - browser + wsUrl → same browser gate but streams to the aggregate server
2176
+ * (angular: nothing mounts /__atoll/)
2177
+ */
2178
+ export function devtoolsSetup(name: string, host: 'browser' | 'node', wsUrl?: string): string {
2179
+ if (host === 'node') {
2180
+ return `import { initDevtools } from '@atolljs/devtools/node';
2181
+
2182
+ /**
2183
+ * Devtools sink — imported first from the entry so it's installed before any
2184
+ * pool spawns (worker INIT carries the flag at spawn time; late installs
2185
+ * leave existing workers silent).
2186
+ *
2187
+ * No-op unless the process was started with ATOLL_DEVTOOLS=1 — then events
2188
+ * stream over WebSocket to the standalone dashboard (\`atoll devtools\`,
2189
+ * http://127.0.0.1:4780). Node has no BroadcastChannel page context, so the
2190
+ * aggregate server is the transport.
2191
+ */
2192
+ initDevtools({ session: { name: '${name}' } });
2193
+ `;
2194
+ }
2195
+ if (wsUrl) {
2196
+ return `import { initDevtools } from '@atolljs/devtools';
2197
+
2198
+ /**
2199
+ * Devtools sink — imported first from the app entry so it's installed before
2200
+ * any pool spawns (worker INIT carries the flag at spawn time; late installs
2201
+ * leave existing workers silent).
2202
+ *
2203
+ * No-op unless the page URL carries ?__atoll_devtools — then events stream
2204
+ * over WebSocket to the standalone aggregate dashboard (\`atoll devtools\`,
2205
+ * http://127.0.0.1:4780). This app's dev server doesn't mount /__atoll/, so
2206
+ * the overlay is off and the aggregate server is the viewer.
2207
+ */
2208
+ initDevtools({
2209
+ session: { name: '${name}' },
2210
+ overlay: false,
2211
+ url: '${wsUrl}',
2212
+ });
2213
+ `;
2214
+ }
2215
+ return `import { initDevtools } from '@atolljs/devtools';
2216
+
2217
+ /**
2218
+ * Devtools sink — imported first from the app entry so it's installed before
2219
+ * any pool spawns (worker INIT carries the flag at spawn time; late installs
2220
+ * leave existing workers silent).
2221
+ *
2222
+ * No-op unless the page URL carries ?__atoll_devtools — then events stream
2223
+ * over BroadcastChannel on the app's own origin and a flyout overlay mounts.
2224
+ * /__atoll/ itself is served by the @atolljs/vite plugin (plugins: [atoll()]).
2225
+ */
2226
+ initDevtools({ session: { name: '${name}' } });
2227
+ `;
2228
+ }