@abide/abide 0.42.0 → 0.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/AGENTS.md +234 -300
  2. package/CHANGELOG.md +34 -0
  3. package/README.md +50 -93
  4. package/package.json +3 -2
  5. package/src/abideResolverPlugin.ts +104 -14
  6. package/src/buildCli.ts +1 -1
  7. package/src/discoveryEntry.ts +3 -3
  8. package/src/lib/bundle/BundleMenuItem.ts +1 -1
  9. package/src/lib/cli/createClient.ts +12 -12
  10. package/src/lib/cli/dispatchCommand.ts +1 -1
  11. package/src/lib/cli/printSessionStatus.ts +1 -1
  12. package/src/lib/cli/resolveCliTarget.ts +1 -1
  13. package/src/lib/cli/runCli.ts +1 -1
  14. package/src/lib/cli/types/CliManifest.ts +1 -1
  15. package/src/lib/cli/types/CliManifestEntry.ts +2 -2
  16. package/src/lib/mcp/annotationsForMethod.ts +5 -5
  17. package/src/lib/mcp/createMcpServer.ts +3 -3
  18. package/src/lib/mcp/mcpSurface.ts +14 -14
  19. package/src/lib/mcp/types/McpServerOptions.ts +1 -1
  20. package/src/lib/server/DELETE.ts +4 -4
  21. package/src/lib/server/GET.ts +4 -4
  22. package/src/lib/server/HEAD.ts +4 -4
  23. package/src/lib/server/PATCH.ts +4 -4
  24. package/src/lib/server/POST.ts +4 -4
  25. package/src/lib/server/PUT.ts +4 -4
  26. package/src/lib/server/agent.ts +4 -4
  27. package/src/lib/server/env.ts +1 -1
  28. package/src/lib/server/error.ts +49 -7
  29. package/src/lib/server/json.ts +1 -1
  30. package/src/lib/server/rpc/buildErrorConstructors.ts +19 -0
  31. package/src/lib/server/rpc/{defineVerb.ts → defineRpc.ts} +52 -34
  32. package/src/lib/server/rpc/{dispatchVerbInProcess.ts → dispatchRpcInProcess.ts} +3 -3
  33. package/src/lib/server/rpc/fieldErrorsFromIssues.ts +23 -0
  34. package/src/lib/server/rpc/{findVerbByCommandName.ts → findRpcByCommandName.ts} +5 -5
  35. package/src/lib/server/rpc/parseArgs.ts +6 -6
  36. package/src/lib/server/rpc/readBodyWithinLimit.ts +2 -2
  37. package/src/lib/server/rpc/registerRpc.ts +6 -0
  38. package/src/lib/server/rpc/{verbRegistry.ts → rpcRegistry.ts} +4 -4
  39. package/src/lib/server/rpc/{runWithVerbTimeout.ts → runWithRpcTimeout.ts} +4 -4
  40. package/src/lib/server/rpc/types/RemoteHandler.ts +9 -3
  41. package/src/lib/server/rpc/types/{VerbHelper.ts → RpcHelper.ts} +57 -23
  42. package/src/lib/server/rpc/types/{VerbRegistryEntry.ts → RpcRegistryEntry.ts} +3 -3
  43. package/src/lib/server/rpc/types/TypedResponse.ts +2 -2
  44. package/src/lib/server/rpc/unprocessed.ts +6 -7
  45. package/src/lib/server/rpc/validationError.ts +17 -0
  46. package/src/lib/server/runtime/buildInspectorSurface.ts +7 -7
  47. package/src/lib/server/runtime/buildOpenApiSpec.ts +6 -6
  48. package/src/lib/server/runtime/createRouteDispatcher.ts +7 -7
  49. package/src/lib/server/runtime/createServer.ts +4 -4
  50. package/src/lib/server/runtime/crossOriginForbidden.ts +1 -1
  51. package/src/lib/server/runtime/crossOriginGate.ts +4 -4
  52. package/src/lib/server/runtime/logExposedSurfaces.ts +4 -4
  53. package/src/lib/server/runtime/registryManifests.ts +2 -2
  54. package/src/lib/server/runtime/types/InspectorCacheEntry.ts +1 -1
  55. package/src/lib/server/runtime/types/InspectorRpc.ts +27 -0
  56. package/src/lib/server/runtime/types/InspectorSurface.ts +4 -4
  57. package/src/lib/server/runtime/types/RequestStore.ts +1 -1
  58. package/src/lib/server/runtime/warnUnguardedMcp.ts +2 -2
  59. package/src/lib/server/sockets/createSocketDispatcher.ts +15 -14
  60. package/src/lib/server/sockets/types/SocketOperation.ts +2 -2
  61. package/src/lib/shared/HttpError.ts +15 -1
  62. package/src/lib/shared/UNREACHABLE_STATUSES.ts +13 -0
  63. package/src/lib/shared/buildRpcRequest.ts +5 -5
  64. package/src/lib/shared/cache.ts +50 -168
  65. package/src/lib/shared/carriesBodyArgs.ts +4 -4
  66. package/src/lib/shared/createCacheStore.ts +12 -0
  67. package/src/lib/shared/createRemoteFunction.ts +11 -6
  68. package/src/lib/shared/decodeResponse.ts +2 -2
  69. package/src/lib/shared/detectRpcMethod.ts +17 -0
  70. package/src/lib/shared/emitLogRecord.ts +3 -3
  71. package/src/lib/shared/extraForwardHeaders.ts +1 -1
  72. package/src/lib/shared/findExportCallSite.ts +30 -273
  73. package/src/lib/shared/forwardHeaders.ts +2 -2
  74. package/src/lib/shared/httpErrorFor.ts +26 -0
  75. package/src/lib/shared/isAsciiWhitespace.ts +5 -0
  76. package/src/lib/shared/isIdentPart.ts +9 -0
  77. package/src/lib/shared/isIdentStart.ts +8 -0
  78. package/src/lib/shared/isReadOnlyMethod.ts +4 -4
  79. package/src/lib/shared/keyForRemoteCall.ts +4 -4
  80. package/src/lib/shared/log.ts +1 -1
  81. package/src/lib/shared/outboxProbeSlot.ts +20 -0
  82. package/src/lib/shared/pending.ts +4 -1
  83. package/src/lib/shared/prepareRpcModule.ts +96 -14
  84. package/src/lib/shared/probeRegistries.ts +17 -4
  85. package/src/lib/shared/queryStringFromArgs.ts +1 -1
  86. package/src/lib/shared/remoteMetaStore.ts +2 -2
  87. package/src/lib/shared/resolveClientFlags.ts +1 -1
  88. package/src/lib/shared/skipNonCode.ts +230 -0
  89. package/src/lib/shared/streamResponse.ts +9 -6
  90. package/src/lib/shared/stripImport.ts +3 -3
  91. package/src/lib/shared/types/CacheEntry.ts +2 -4
  92. package/src/lib/shared/types/CacheOnContext.ts +1 -11
  93. package/src/lib/shared/types/CacheStore.ts +13 -7
  94. package/src/lib/shared/types/ClientFlags.ts +1 -1
  95. package/src/lib/shared/types/ErrorConstructors.ts +17 -0
  96. package/src/lib/shared/types/ErrorDescriptor.ts +10 -0
  97. package/src/lib/shared/types/ErrorSpec.ts +11 -0
  98. package/src/lib/shared/types/HttpMethod.ts +1 -0
  99. package/src/lib/shared/types/Outbox.ts +9 -0
  100. package/src/lib/shared/types/OutboxEntry.ts +27 -0
  101. package/src/lib/shared/types/RawRemoteFunction.ts +2 -2
  102. package/src/lib/shared/types/RemoteCallable.ts +2 -2
  103. package/src/lib/shared/types/RemoteFunction.ts +21 -6
  104. package/src/lib/shared/types/RequestScopeInfo.ts +1 -1
  105. package/src/lib/shared/types/RpcErrorGuard.ts +40 -0
  106. package/src/lib/shared/types/RpcInvoker.ts +1 -1
  107. package/src/lib/shared/types/StandardSchemaV1.ts +1 -1
  108. package/src/lib/shared/types/ValidationErrorData.ts +20 -0
  109. package/src/lib/shared/url.ts +3 -3
  110. package/src/lib/shared/writeRpcDts.ts +7 -7
  111. package/src/lib/shared/writeTestRpcDts.ts +3 -3
  112. package/src/lib/test/createTestApp.ts +10 -10
  113. package/src/lib/ui/compile/awaitPlan.ts +48 -0
  114. package/src/lib/ui/compile/compileShadow.ts +75 -21
  115. package/src/lib/ui/compile/desugarSignals.ts +42 -6
  116. package/src/lib/ui/compile/generateBuild.ts +43 -60
  117. package/src/lib/ui/compile/generateSSR.ts +84 -54
  118. package/src/lib/ui/compile/ifPlan.ts +30 -0
  119. package/src/lib/ui/compile/parseTemplate.ts +41 -11
  120. package/src/lib/ui/compile/stripEffects.ts +17 -9
  121. package/src/lib/ui/compile/switchPlan.ts +22 -0
  122. package/src/lib/ui/compile/tryPlan.ts +29 -0
  123. package/src/lib/ui/compile/types/TemplateAttr.ts +8 -2
  124. package/src/lib/ui/compile/types/TemplateNode.ts +6 -2
  125. package/src/lib/ui/createScope.ts +20 -2
  126. package/src/lib/ui/dom/disposeRange.ts +6 -10
  127. package/src/lib/ui/dom/hydrate.ts +2 -5
  128. package/src/lib/ui/dom/mount.ts +1 -2
  129. package/src/lib/ui/dom/withScope.ts +6 -9
  130. package/src/lib/ui/effect.ts +4 -2
  131. package/src/lib/ui/navigate.ts +40 -13
  132. package/src/lib/ui/outbox.ts +30 -110
  133. package/src/lib/ui/remoteProxy.ts +160 -10
  134. package/src/lib/ui/router.ts +3 -3
  135. package/src/lib/ui/rpcOutbox/createOutboxQueue.ts +281 -0
  136. package/src/lib/ui/rpcOutbox/outboxRegistry.ts +69 -0
  137. package/src/lib/ui/socketChannel.ts +7 -1
  138. package/src/lib/ui/types/Scope.ts +20 -5
  139. package/template/CLAUDE.md +1 -1
  140. package/template/src/server/rpc/getHello.ts +3 -3
  141. package/template/test/app.test.ts +2 -2
  142. package/src/lib/server/rpc/registerVerb.ts +0 -6
  143. package/src/lib/server/runtime/types/InspectorVerb.ts +0 -27
  144. package/src/lib/shared/detectVerbMethod.ts +0 -17
  145. package/src/lib/shared/types/HttpVerb.ts +0 -1
  146. package/src/lib/ui/types/Outbox.ts +0 -14
package/CHANGELOG.md CHANGED
@@ -1,5 +1,39 @@
1
1
  # abide
2
2
 
3
+ ## 0.43.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 6453cf9: repurpose the bare outbox() export to the global reactive aggregate ([`003713d`](https://github.com/briancray/abide/commit/003713dab125b3df72d6cf832ec18ea546922c79))
8
+ - 6453cf9: typed navigate params, scope-destructured primitives, precise prop diagnostics ([`0769896`](https://github.com/briancray/abide/commit/07698966147e96970c27e0278e2152a23e48b219))
9
+ - 6453cf9: OutboxEntry type + status ([`08d9b03`](https://github.com/briancray/abide/commit/08d9b036501110ab24c18258cc79364f9ac71ea8))
10
+ - 6453cf9: migrate kitchen-sink to rpc-native outbox; AGENTS.md surface ([`1b8aa3f`](https://github.com/briancray/abide/commit/1b8aa3fe631181e7798d0703eb09a0f22dba0f88))
11
+ - 6453cf9: persist request body + content-type, replay on reload ([`26c309f`](https://github.com/briancray/abide/commit/26c309f20bf573e512b74477ba2319c22218f7d9))
12
+ - 6453cf9: client registry of durable rpc queues ([`2b89c20`](https://github.com/briancray/abide/commit/2b89c20b1f1f1794f90152f46958ee760333bab5))
13
+ - 6453cf9: reject server-only imports in the client bundle with an evidence chain ([`2de1730`](https://github.com/briancray/abide/commit/2de1730872898d320c240a89b570dfaa9acace7a))
14
+ - 6453cf9: name-keyed typed errors via a handler errors ctx ([`62f75a8`](https://github.com/briancray/abide/commit/62f75a8341d61ac9cfd1e7cbeccc66ddec991643))
15
+ - 6453cf9: createOutboxQueue enqueue + reactive/persisted entries ([`88560c9`](https://github.com/briancray/abide/commit/88560c942d0f28623a68ed1315bb2341ad66a9c3))
16
+ - 6453cf9: bundler threads outbox:true into the client proxy emission ([`b279960`](https://github.com/briancray/abide/commit/b27996089f4a3c8786fcd1ade530057a34f086fc))
17
+ - 6453cf9: pending() reflects the durable queue (in-flight or queued) ([`bfa505d`](https://github.com/briancray/abide/commit/bfa505d8d5326f0464be35568ac8ae6fc4bd1901))
18
+ - 6453cf9: class: and style: directives for surgical reactive class/style writes ([`c5ee6d1`](https://github.com/briancray/abide/commit/c5ee6d13faad82ac45c6c1627c6e9435c9c59c5f))
19
+ - 6453cf9: durable return type — outbox:true overload on mutating rpc helpers ([`d916c6b`](https://github.com/briancray/abide/commit/d916c6b5b563ddc4dee07a015cde08d32e6dd750))
20
+ - 6453cf9: outbox:true rpc option, gated to mutating methods ([`e37f2a3`](https://github.com/briancray/abide/commit/e37f2a333e201ef40303dd25bafb9f0ca82dd220))
21
+ - 6453cf9: drain loop — deliver/retry/error/cancel under the entry signal ([`ec32868`](https://github.com/briancray/abide/commit/ec32868eb860b6185f21c196b18f03c7b3605ba8))
22
+ - 6453cf9: durable client invoke path + rpc.outbox face ([`f96a0c0`](https://github.com/briancray/abide/commit/f96a0c025cfda5f6daf713f4d69857854b8f3459))
23
+
24
+ ### Patch Changes
25
+
26
+ - 6453cf9: notify() forEach callback must not return a value ([`1c7f20f`](https://github.com/briancray/abide/commit/1c7f20fa5b6a24f3a5e90ab20521f7d24ba55d57))
27
+ - 6453cf9: Breaking: rename defineVerb export to defineRpc ([`29fe778`](https://github.com/briancray/abide/commit/29fe7782844e0de15c53fad711d6616cc90a27e0))
28
+ - 6453cf9: rename VERB*\* constants to RPC*\* ([`391687a`](https://github.com/briancray/abide/commit/391687a252a33ba355ba929944cd1a2cef83c1ef))
29
+ - 6453cf9: rename verb-named rpc helpers to rpc ([`4ebdd8f`](https://github.com/briancray/abide/commit/4ebdd8ff862121a48a39fbdb7b2893a14fd52022))
30
+ - 6453cf9: rename verbRegistry to rpcRegistry ([`507adf5`](https://github.com/briancray/abide/commit/507adf5d5ae3a1b66ef85fc495ba1a974a530abf))
31
+ - 6453cf9: rename Verb-named types/helpers/files to Rpc ([`8342e96`](https://github.com/briancray/abide/commit/8342e963babbddcb9a87dcc99fc2e4958dbc5750))
32
+ - 6453cf9: refresh AGENTS surface map ({#…} blocks, scaffold flags, migration error) ([`88cc4d9`](https://github.com/briancray/abide/commit/88cc4d97205bd3de74a08a6dbae3f2221f0275fe))
33
+ - 6453cf9: commit working tree — outbox, rpc errors, compile plans, cache refactor ([`c673dc7`](https://github.com/briancray/abide/commit/c673dc71522e33ad4f9b48f41ac73fa6c4f03a02))
34
+ - 6453cf9: rename HttpVerb type to HttpMethod ([`d64a59a`](https://github.com/briancray/abide/commit/d64a59a045f77c627b98dbb1b3a12b545f3d3c60))
35
+ - 6453cf9: scrub verb from comments, AGENTS.md, and inspector surface (rpc/method) ([`d9356ce`](https://github.com/briancray/abide/commit/d9356ce9eeba6b03d43ef67fc500cf697e34d39c))
36
+
3
37
  ## 0.42.0
4
38
 
5
39
  ### Minor Changes
package/README.md CHANGED
@@ -1,170 +1,127 @@
1
1
  # abide
2
2
 
3
- **One typed declaration fans out to HTTP, a CLI, an MCP tool, and an
4
- OpenAPI spec — the bundler swaps the runtime per side.**
3
+ **One typed declaration fans out to an HTTP endpoint, a CLI subcommand, an MCP tool, and an OpenAPI operation — and the same callable runs on the server and in the browser.**
5
4
 
6
- abide is an isomorphic framework on Bun where you write a function once and
7
- it serves every consumer: a browser fetch, an in-process SSR call, a CLI
8
- subcommand, an MCP tool, an OpenAPI operation. The same callable keeps its
9
- name and behaviour on both sides — the bundler decides whether it runs the
10
- real handler or a network proxy. Built for humans _and_ machines.
5
+ abide is a type-safe isomorphic framework on Bun and web standards. You declare an RPC, a socket, or a `.abide` component once; the bundler swaps the runtime per side (in-process on the server, a typed `fetch` proxy in the browser) so the name, the call, and the behaviour are identical wherever you import it. Built for humans *and* the machines that now read and drive software.
11
6
 
12
- - One direct dependency (`typescript`); `tailwindcss` + `bun-plugin-tailwind`
13
- are optional peers. Single runtime: Bun ≥ 1.3.0.
7
+ - One direct dependency (TypeScript). Bun is the only runtime — no Node, no separate bundler, no dev server to wire up.
14
8
 
15
9
  ## Quick start
16
10
 
17
11
  ```sh
18
- bunx abide scaffold my-app # scaffolds, installs deps, and starts dev
12
+ bunx abide scaffold my-app # scaffolds the project, installs it, and starts dev
19
13
  ```
20
14
 
21
- Or read the full feature tour in the kitchen-sink example:
15
+ Or read the runnable tour every primitive below, live:
22
16
 
23
17
  ```sh
24
- git clone https://github.com/briancray/abide
18
+ git clone git@github.com:briancray/abide.git
25
19
  cd abide/examples/kitchen-sink
26
20
  bun install
27
- bun run dev
21
+ bun dev
28
22
  ```
29
23
 
30
24
  ## RPCs
31
25
 
32
- An RPC is one export per file under `src/server/rpc/`. The file path is the
33
- URL; the export name is the verb. A Standard Schema (zod / valibot / arktype,
34
- unadapted) validates the args and projects the same shape into the MCP tool,
35
- the CLI flags, and the OpenAPI operation.
26
+ An RPC is one export per file under `src/server/rpc/`. The file path is the URL (`getMessages.ts` → `/rpc/getMessages`), and a Standard Schema (zod / valibot / arktype, unadapted) validates the args once and projects the same declaration into every surface.
36
27
 
37
28
  ```ts
38
29
  // src/server/rpc/getMessages.ts
39
30
  import { GET } from '@abide/abide/server/GET'
40
31
  import { json } from '@abide/abide/server/json'
41
32
  import { z } from 'zod'
42
- import { recent } from '../../chatState.ts'
43
33
 
44
- const inputSchema = z.object({ room: z.string(), limit: z.coerce.number().default(20) })
34
+ const inputSchema = z.object({ room: z.string() })
45
35
 
46
- export const getMessages = GET(({ room, limit }) => json(recent(room).slice(-limit)), {
47
- inputSchema,
48
- })
36
+ export const getMessages = GET(({ room }) => json(db.messages(room)), { inputSchema })
49
37
  ```
50
38
 
51
- One declaration, every surface:
39
+ That single declaration fans out:
52
40
 
53
41
  ```text
54
- getMessages = GET(fn, { inputSchema })
55
-
56
- ┌─────────────┬─────────┼──────────┬──────────────┐
57
-
58
- SSR call browser MCP tool CLI sub- OpenAPI
59
- cache(fn)() fetch (read) command operation
60
- proxy
42
+ export const getMessages = GET(fn, { inputSchema })
43
+
44
+ ┌────────────┬────────────┬────────────┬────────────┐
45
+
46
+ SSR call browser MCP tool CLI cmd OpenAPI
47
+ cache(fn)() fetch proxy (read-only) abide cli /openapi.json
61
48
  ```
62
49
 
63
- A schema unlocks the CLI for every verb and MCP for read-only verbs (`GET` /
64
- `HEAD`); a mutating verb never auto-exposes to MCP — it needs an explicit
65
- `clients: { mcp: true }`. Consume the verb four ways: `cache(getMessages)(args)`
66
- in-process (warm SSR hydration), the swapped `fetch` proxy in the browser,
67
- `getMessages.raw(args)` for the untouched `Response`, and
68
- `getMessages.stream(args)` to iterate a `jsonl`/`sse` body.
50
+ The schema is the gate: it unlocks the CLI and for read-only methods (GET/HEAD) — the MCP tool automatically. A mutating method (POST/PUT/PATCH/DELETE) never auto-exposes to MCP; it opts in with `clients: { mcp: true }`. Consume a call four ways: `cache(getMessages)()` in-process (warm SSR + hydration), the swapped `fetch` proxy in the browser, `getMessages.raw(args)` for the untouched `Response`, and `getMessages.stream(args)` for a frame stream.
69
51
 
70
- > Query args arrive as strings — wrap numeric/boolean fields in `z.coerce.*`.
71
- > The per-verb `timeout` (504 on every surface) is distinct from the
72
- > client-side `ABIDE_CLIENT_TIMEOUT`.
52
+ > Query args travel as strings — use `z.coerce.*` for numbers/booleans on GET/DELETE/HEAD. A per-RPC `timeout` (a 504 on every surface) is distinct from the client-wide `ABIDE_CLIENT_TIMEOUT`.
73
53
 
74
54
  ## Sockets
75
55
 
76
- A socket is one broadcast topic per file under `src/server/sockets/`. A
77
- `Socket<T>` is an isomorphic `AsyncIterable<T>`; every socket multiplexes onto
78
- one WebSocket at `/__abide/sockets`.
56
+ A socket is one broadcast topic per file under `src/server/sockets/`. A `Socket<T>` is an isomorphic `AsyncIterable<T>` — `for await (const m of chat)` is the live stream — and every socket multiplexes onto one WebSocket at `/__abide/sockets`.
79
57
 
80
58
  ```ts
81
59
  // src/server/sockets/chat.ts
82
60
  import { socket } from '@abide/abide/server/socket'
83
61
  import { z } from 'zod'
84
62
 
85
- const schema = z.object({ id: z.string(), from: z.string(), text: z.string(), at: z.number() })
63
+ const schema = z.object({ from: z.string(), text: z.string(), at: z.number() })
86
64
 
87
- // retain the last 100 frames; evict any older than an hour
65
+ // tail: retain the last 100 frames for late joiners; ttl: evict frames older than an hour.
88
66
  export const chat = socket({ schema, tail: 100, ttl: 3_600_000 })
89
67
  export type ChatMessage = z.infer<typeof schema>
90
68
  ```
91
69
 
92
- It also has an HTTP face for clients that can't speak the multiplex (the CLI
93
- and MCP): `GET /__abide/sockets/chat` returns the retained tail, and
94
- `POST /__abide/sockets/chat` publishes — gated by `clientPublish` (default off,
95
- so browsers publish through a validating verb instead).
70
+ Each socket also has an HTTP face at `/__abide/sockets/<name>`: `GET` returns the retained tail, `POST` publishes (only when the socket declares `clientPublish`). `chat.publish(m)` is isomorphic — server-side it fans out in-process and to remote subscribers; client-side it sends a validated `pub` frame.
96
71
 
97
- ## Components — the full template
72
+ ## Components
98
73
 
99
- A `.abide` component pulls the verb and the socket above into one page and
100
- exercises the template grammar. `scope`, `props`, `effect`, `html`, and
101
- `snippet` are ambient — no import needed.
74
+ A component is a `.abide` file: valid HTML with a `<script>`, `{#…}` control-flow blocks, `<template name>` snippets, `{expr}` bindings, and a component-scoped `<style>`. This page imports the RPC and socket above `cache(getMessages)` seeds the list warm at SSR, `tail(chat)` layers live frames on top after hydration.
102
75
 
103
- <!-- prettier-ignore -->
104
76
  ```html
105
77
  <script>
106
78
  import { cache } from '@abide/abide/shared/cache'
107
79
  import { tail } from '@abide/abide/ui/tail'
108
80
  import { getMessages } from '$server/rpc/getMessages.ts'
109
- import { publishChat } from '$server/rpc/publishChat.ts'
81
+ import { createMessage } from '$server/rpc/createMessage.ts'
110
82
  import { chat } from '$server/sockets/chat.ts'
111
83
  import Avatar from '$ui/Avatar.abide'
112
84
 
113
- const { room } = props()
85
+ const { room = 'lobby' } = props()
86
+ const { state, computed } = scope()
114
87
 
115
- // warm on the server, live on the client
116
- const history = scope().computed(() => cache(getMessages)({ room }))
117
- const latest = scope().computed(() => tail(chat))
88
+ let draft = state('')
89
+ let notify = state(true)
90
+ const live = computed(() => tail(chat)) // read-only derived
91
+ const ready = computed(() => draft.trim().length > 0)
118
92
 
119
- let from = scope().state('alice')
120
- let text = scope().state('')
121
- let pinned = scope().state(false)
122
- let view = scope().state('all')
123
-
124
- async function send() {
125
- await publishChat({ from, text })
126
- text = ''
93
+ async function send(event: SubmitEvent) {
94
+ event.preventDefault()
95
+ await createMessage({ room, text: draft, notify })
96
+ draft = ''
127
97
  }
128
98
  </script>
129
99
 
130
- <template name="line" args={message}>
131
- <li><Avatar name={message.from} /> <b>{message.from}</b>: {message.text}</li>
100
+ <template name="row" args={{ msg }}>
101
+ <li><Avatar name={msg.from} /> {msg.text}</li>
132
102
  </template>
133
103
 
134
104
  <form onsubmit={send}>
135
- <input bind:value={from} placeholder="name" />
136
- <input bind:value={text} placeholder="message" />
137
- <label><input type="checkbox" bind:checked={pinned} /> pin</label>
138
- <label><input type="radio" bind:group={view} value="all" /> all</label>
139
- <label><input type="radio" bind:group={view} value="mine" /> mine</label>
140
- <button disabled={!text}>send</button>
105
+ <input bind:value={draft} placeholder="message" />
106
+ <label><input type="checkbox" bind:checked={notify} /> notify</label>
107
+ <button disabled={!ready()}>Send</button>
141
108
  </form>
142
109
 
143
- {#if latest}
144
- <p>latest from {latest.from}</p>
145
- {:else}
146
- <p>no messages yet</p>
147
- {/if}
148
-
149
- {#switch view}
150
- {:case 'mine'}<p>showing your messages</p>
151
- {:default}<p>showing every message</p>
152
- {/switch}
110
+ {#if live()}<p class="muted">latest: {live().text}</p>{/if}
153
111
 
154
- {#await history}
155
- <p>loading…</p>
156
- {:then data}
112
+ {#await cache(getMessages)({ room })}
113
+ <p>Loading…</p>
114
+ {:then messages}
157
115
  <ul>
158
- {#for message, i of data by message.id}
159
- {i}. {line(message)}
160
- {/for}
116
+ {#for msg of messages by msg.at}{row({ msg })}{/for}
161
117
  </ul>
162
- {:catch reason}
163
- <p>failed: {reason.message}</p>
118
+ {:catch error}
119
+ <p class="error">{error.message}</p>
164
120
  {/await}
165
121
 
166
122
  <style>
167
- form { display: flex; gap: 0.5rem; }
123
+ .muted { color: #64748b; }
124
+ .error { color: #b91c1c; }
168
125
  </style>
169
126
  ```
170
127
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abide/abide",
3
- "version": "0.42.0",
3
+ "version": "0.43.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "Isomorphic multimodal HTTP framework built for humans and machines in a single Bun runtime",
@@ -54,7 +54,7 @@
54
54
  "./server/AppModule": "./src/lib/server/AppModule.ts",
55
55
  "./server/InspectorContext": "./src/lib/server/runtime/types/InspectorContext.ts",
56
56
  "./server/agent": "./src/lib/server/agent.ts",
57
- "./server/rpc/defineVerb": "./src/lib/server/rpc/defineVerb.ts",
57
+ "./server/rpc/defineRpc": "./src/lib/server/rpc/defineRpc.ts",
58
58
  "./server/sockets/defineSocket": "./src/lib/server/sockets/defineSocket.ts",
59
59
  "./server/prompts/definePrompt": "./src/lib/server/prompts/definePrompt.ts",
60
60
  "./ui/tail": "./src/lib/ui/tail.ts",
@@ -62,6 +62,7 @@
62
62
  "./shared/pending": "./src/lib/shared/pending.ts",
63
63
  "./shared/refreshing": "./src/lib/shared/refreshing.ts",
64
64
  "./shared/HttpError": "./src/lib/shared/HttpError.ts",
65
+ "./shared/ValidationErrorData": "./src/lib/shared/types/ValidationErrorData.ts",
65
66
  "./shared/html": "./src/lib/shared/html.ts",
66
67
  "./shared/snippet": "./src/lib/shared/snippet.ts",
67
68
  "./shared/withJsonSchema": "./src/lib/shared/withJsonSchema.ts",
@@ -1,5 +1,6 @@
1
1
  // node:fs existsSync — Bun plugin onResolve is sync-only; Bun.file().exists() is async
2
2
  import { existsSync, statSync } from 'node:fs'
3
+ import { dirname, join } from 'node:path'
3
4
  import type { BunPlugin } from 'bun'
4
5
  import { Glob } from 'bun'
5
6
  import { abideImportName } from './lib/shared/abideImportName.ts'
@@ -80,7 +81,7 @@ function once<T>(produce: () => Promise<T>): () => Promise<T> {
80
81
 
81
82
  /*
82
83
  Bun plugin that wires every virtual import abide produces at build time:
83
- - `abide:rpc` — { rpcUrl: () => import(rpc-module) } HTTP-verb manifest
84
+ - `abide:rpc` — { rpcUrl: () => import(rpc-module) } HTTP-method manifest
84
85
  - `abide:sockets` — { socketName: () => import(socket-module) } socket manifest
85
86
  - `abide:pages` — { pageUrl: () => import(page.abide) } manifest
86
87
  - `abide:prompts` — { promptName: () => import(prompt-module) } manifest
@@ -91,8 +92,8 @@ Bun plugin that wires every virtual import abide produces at build time:
91
92
  - `abide:shell` — app.html content (custom or default)
92
93
 
93
94
  Also rewrites modules under src/server/rpc and src/server/sockets:
94
- - src/server/rpc/<file>.ts: each HTTP-verb export is bound to a runtime
95
- implementation — defineVerb on the server, remoteProxy on the client.
95
+ - src/server/rpc/<file>.ts: each HTTP-method export is bound to a runtime
96
+ implementation — defineRpc on the server, remoteProxy on the client.
96
97
  - src/server/sockets/<file>.ts: each `socket(opts)` export is bound to
97
98
  defineSocket on the server (with the socket name + opts) or
98
99
  socketProxy on the client (name only — opts are server-side).
@@ -147,7 +148,7 @@ export function abideResolverPlugin({
147
148
  scanDir(rpcDir, '**/*.ts').then(async (rpcFiles) => {
148
149
  const importName = await abideImportNameOnce()
149
150
  await writeRpcDts({ cwd, rpcDir, rpcFiles, importName })
150
- /* Typed createTestApp `app.rpc.<verb>` surface. */
151
+ /* Typed createTestApp `app.rpc.<rpc>` surface. */
151
152
  await writeTestRpcDts({ cwd, rpcFiles, importName })
152
153
  return rpcFiles
153
154
  }),
@@ -194,9 +195,91 @@ export function abideResolverPlugin({
194
195
  const socketsFilter = new RegExp(`^${escapeRegex(socketsDir)}/.*\\.ts$`)
195
196
  const promptsFilter = new RegExp(`^${escapeRegex(promptsDir)}/.*\\.md$`)
196
197
 
198
+ /*
199
+ Side-crossing guard (client target only). The client bundle must never pull a
200
+ server-only name into the browser. The exception is a registered rpc/socket —
201
+ `$server/rpc/*` and `$server/sockets/*` are replaced with remoteProxy/socketProxy
202
+ stubs by the onLoad hooks below — so only OTHER paths under src/server/ (helpers,
203
+ runtime/) and the public `abide/server/*` names are violations. `importerOf` records
204
+ each resolved edge so a violation can show the import chain back to its client-side
205
+ origin as evidence; it is reset per build in onStart.
206
+ */
207
+ const importerOf = new Map<string, string>()
208
+ const isProxiedServerModule = (path: string): boolean =>
209
+ path.startsWith(`${rpcDir}/`) || path.startsWith(`${socketsDir}/`)
210
+ const isServerOnlyModule = (path: string): boolean =>
211
+ path.startsWith(`${serverDir}/`) && !isProxiedServerModule(path)
212
+ const showPath = (path: string): string =>
213
+ path.startsWith(`${cwd}/`) ? path.slice(cwd.length + 1) : path
214
+ /* Walks the recorded edges from the offending importer back to a graph root (cycle-safe),
215
+ formatting each module relative to cwd — the evidence a side-crossing throws. */
216
+ function sideCrossingChain(leaf: string, importer: string): string {
217
+ const chain = [leaf, importer]
218
+ const seen = new Set([importer])
219
+ let cursor = importer
220
+ while (importerOf.has(cursor)) {
221
+ cursor = importerOf.get(cursor) as string
222
+ if (seen.has(cursor)) {
223
+ break
224
+ }
225
+ seen.add(cursor)
226
+ chain.push(cursor)
227
+ }
228
+ return chain.reverse().map(showPath).join('\n → ')
229
+ }
230
+ function throwSideCrossing(leaf: string, label: string, importer: string): never {
231
+ throw new Error(
232
+ `[abide] a client module imports the server-only name \`${label}\` — server code must not reach the browser bundle. Move it to src/shared/ or behind an RPC. Import chain:\n ${sideCrossingChain(leaf, importer)}`,
233
+ )
234
+ }
235
+ /* Records the edge and, on the client target, rejects a non-server module reaching a
236
+ server-only one. Shared by the relative-import and `$server` alias resolvers. */
237
+ function recordAndGuard(resolved: string, label: string, importer: string | undefined): void {
238
+ if (importer === undefined) {
239
+ return
240
+ }
241
+ importerOf.set(resolved, importer)
242
+ if (target === 'client' && isServerOnlyModule(resolved) && !isServerOnlyModule(importer)) {
243
+ throwSideCrossing(resolved, label, importer)
244
+ }
245
+ }
246
+
197
247
  return {
198
248
  name: 'abide-resolver',
199
249
  setup(build) {
250
+ /* Fresh edge graph each build (dev watch reuses the plugin instance).
251
+ onStart is build-time only — absent in the runtime/preload plugin context. */
252
+ build.onStart?.(() => {
253
+ importerOf.clear()
254
+ })
255
+
256
+ /*
257
+ Relative-import edge recorder + side-crossing guard. Matches `./` and `../`
258
+ specifiers, records the edge for the evidence chain, and (client target) throws
259
+ when a non-server module reaches a server-only file. Returns undefined so normal
260
+ resolution proceeds — this only observes and, on violation, aborts.
261
+ */
262
+ build.onResolve({ filter: /^\.\.?\// }, (args) => {
263
+ if (args.importer) {
264
+ const resolved = resolveExtension(join(dirname(args.importer), args.path))
265
+ recordAndGuard(resolved, args.path, args.importer)
266
+ }
267
+ return undefined
268
+ })
269
+
270
+ /*
271
+ The public `abide/server/*` names are server-only. Importing one into the client
272
+ bundle is a side-crossing (the canonical `abide` and `@abide/abide` specifiers; a
273
+ custom package alias falls through to the relative/`$server` guards). The server
274
+ target resolves these normally.
275
+ */
276
+ build.onResolve({ filter: /(^|\/)abide\/server\// }, (args) => {
277
+ if (target === 'client' && args.importer && !isServerOnlyModule(args.importer)) {
278
+ throwSideCrossing(args.path, args.path, args.importer)
279
+ }
280
+ return undefined
281
+ })
282
+
200
283
  build.onResolve(
201
284
  {
202
285
  filter: /\/_virtual\/(rpc|sockets|prompts|pages|layouts|app|config|mcp-resources|mcp|assets|public-assets|shell|app-info|cli-manifest|cli-name|cli-chrome|bundle-window|bundle-disconnected-component|bundle-disconnected)\.ts$/,
@@ -226,7 +309,11 @@ export function abideResolverPlugin({
226
309
  for (const [alias, baseDir] of Object.entries(dirAliases)) {
227
310
  build.onResolve({ filter: new RegExp(`^\\${alias}(\\/.*)?$`) }, (args) => {
228
311
  const subpath = args.path.slice(alias.length)
229
- return { path: resolveExtension(subpath ? `${baseDir}${subpath}` : baseDir) }
312
+ const resolved = resolveExtension(subpath ? `${baseDir}${subpath}` : baseDir)
313
+ /* `$server/rpc/*` + `$server/sockets/*` are proxied (allowed); other
314
+ `$server/*` imports into the client bundle are side-crossings. */
315
+ recordAndGuard(resolved, args.path, args.importer)
316
+ return { path: resolved }
230
317
  })
231
318
  }
232
319
 
@@ -262,7 +349,7 @@ export function abideResolverPlugin({
262
349
  const prepared = prepareRpcModule(source, importName)
263
350
  if (!prepared) {
264
351
  throw new Error(
265
- `[abide] src/server/rpc/${relativePath} has no \`export const <name> = <VERB>(...)\` — every $rpc module must declare exactly one remote function`,
352
+ `[abide] src/server/rpc/${relativePath} has no \`export const <name> = <METHOD>(...)\` — every $rpc module must declare exactly one remote function`,
266
353
  )
267
354
  }
268
355
  const expectedName = fileStem(relativePath)
@@ -279,22 +366,25 @@ export function abideResolverPlugin({
279
366
  so page imports resolve identically on both sides.
280
367
  */
281
368
  if (target === 'client') {
369
+ /* A durable rpc (`outbox: true`) gets the third arg so its client proxy
370
+ parks unreachable calls onto the outbox. */
371
+ const durableArg = prepared.durable ? ', { outbox: true }' : ''
282
372
  const contents = `import { remoteProxy as __abideRemoteProxy__ } from '${importName}/ui/remoteProxy';
283
- export const ${prepared.exportName} = __abideRemoteProxy__(${JSON.stringify(prepared.verb)}, ${JSON.stringify(url)});
373
+ export const ${prepared.exportName} = __abideRemoteProxy__(${JSON.stringify(prepared.method)}, ${JSON.stringify(url)}${durableArg});
284
374
  `
285
375
  return { contents, loader: 'ts' }
286
376
  }
287
377
  /*
288
- Server target: strip the user's verb import, then rewrite
289
- the `<VERB>(` call so the verb (from the identifier) and
378
+ Server target: strip the user's rpc import, then rewrite
379
+ the `<METHOD>(` call so the method (from the identifier) and
290
380
  the URL (from the file path) are threaded into the
291
- runtime constructor — defineVerb. The user's handler body
381
+ runtime constructor — defineRpc. The user's handler body
292
382
  stays intact between the parens; any generics on the call
293
383
  are dropped (they carry no runtime info). Rewriting is
294
384
  tokenizer-driven so `GET` mentions inside strings and
295
385
  comments are left alone.
296
386
  */
297
- const banner = `import { defineVerb as __abideDefineVerb__ } from '${importName}/server/rpc/defineVerb';
387
+ const banner = `import { defineRpc as __abideDefineRpc__ } from '${importName}/server/rpc/defineRpc';
298
388
  `
299
389
  return { contents: `${banner}${prepared.rewriteForServer(url)}`, loader: 'ts' }
300
390
  })
@@ -471,7 +561,7 @@ ${optionLines}
471
561
  The CLI binary's bake-time manifest. Discovery (a
472
562
  one-shot script the bundler runs separately) writes
473
563
  `${cwd}/dist/cli-manifest.json` from the populated
474
- verbRegistry; this virtual splices that JSON in as a
564
+ rpcRegistry; this virtual splices that JSON in as a
475
565
  default-exported object. Empty manifest when the
476
566
  discovery file is missing — the binary still works
477
567
  but exposes no subcommands until the user runs the
@@ -606,7 +696,7 @@ export const footer = ${JSON.stringify(footer)}
606
696
  if (args.path === 'abide:mcp') {
607
697
  /*
608
698
  The MCP server is fully framework-generated — tools from
609
- the verb registry, prompts from src/mcp/prompts, resources
699
+ the rpc registry, prompts from src/mcp/prompts, resources
610
700
  from src/mcp/resources. createMcpServer is internal; there
611
701
  is no user-authored server module. Server identity comes
612
702
  from package.json so the `mcp__<name>__*` permission prefix
@@ -798,7 +888,7 @@ async function scanPages(pagesDir: string): Promise<PagesScan> {
798
888
 
799
889
  /*
800
890
  Walks one registry directory once: src/server/rpc (every `.ts` file is an
801
- HTTP-verb rpc handler), src/server/sockets (each `.ts` file declares one
891
+ HTTP-method rpc handler), src/server/sockets (each `.ts` file declares one
802
892
  socket, loaded lazily on first sub/pub frame), or src/mcp/prompts (each `.md`
803
893
  file declares one MCP prompt — frontmatter for metadata, body for the
804
894
  template). Returns an empty list when the directory doesn't exist so an app
package/src/buildCli.ts CHANGED
@@ -21,7 +21,7 @@ the compiled server beside it, so `/start` can spawn a local instance.
21
21
  1. Client build (once): `build` produces the platform-independent `dist/_app`
22
22
  the server binaries embed. It clears dist first, so it runs before everything.
23
23
  2. Discovery: build the discovery entry into a temporary JS bundle and run it. It
24
- imports every rpc/socket module so defineVerb / defineSocket populate the
24
+ imports every rpc/socket module so defineRpc / defineSocket populate the
25
25
  registries, then prints the CLI manifest to stdout → `dist/cli-manifest.json`.
26
26
  3. Per target: a server binary (`compile`, reusing the shared `dist/_app` via
27
27
  `buildClient:false`) plus the CLI binary, written side by side. The resolver's
@@ -3,14 +3,14 @@ import { rpc } from './_virtual/rpc.ts'
3
3
  // @ts-expect-error virtual module resolved by abideResolverPlugin
4
4
  import { sockets } from './_virtual/sockets.ts'
5
5
  import type { CliManifestEntry } from './lib/cli/types/CliManifestEntry.ts'
6
- import { verbRegistry } from './lib/server/rpc/verbRegistry.ts'
6
+ import { rpcRegistry } from './lib/server/rpc/rpcRegistry.ts'
7
7
  import { socketOperations } from './lib/server/sockets/socketOperations.ts'
8
8
  import { socketRegistry } from './lib/server/sockets/socketRegistry.ts'
9
9
  import { commandNameForUrl } from './lib/shared/commandNameForUrl.ts'
10
10
  import { jsonSchemaForSchema } from './lib/shared/jsonSchemaForSchema.ts'
11
11
 
12
12
  /*
13
- One-shot script that imports every rpc + socket module so defineVerb /
13
+ One-shot script that imports every rpc + socket module so defineRpc /
14
14
  defineSocket populate the process-wide registries, then prints the CLI
15
15
  manifest to stdout as JSON. Used by buildCli to bake the manifest into
16
16
  the standalone binary at build time without resorting to static source
@@ -23,7 +23,7 @@ await Promise.all([
23
23
 
24
24
  const manifest: Record<string, CliManifestEntry> = {}
25
25
 
26
- for (const entry of verbRegistry.values()) {
26
+ for (const entry of rpcRegistry.values()) {
27
27
  if (!entry.clients.cli) {
28
28
  continue
29
29
  }
@@ -8,7 +8,7 @@ handles it:
8
8
  if (event.detail.name === 'sync') syncNow()
9
9
  })
10
10
 
11
- Emitting an event (rather than calling a verb directly) is what lets a menu
11
+ Emitting an event (rather than calling a rpc directly) is what lets a menu
12
12
  drive parameterised work: a click carries no arguments, so the app computes
13
13
  them and makes the call itself. `shortcut` is the key for the Cmd-based
14
14
  equivalent (e.g. `'r'` → Cmd-R).
@@ -1,8 +1,8 @@
1
- import { dispatchVerbInProcess } from '../server/rpc/dispatchVerbInProcess.ts'
2
- import { findVerbByCommandName } from '../server/rpc/findVerbByCommandName.ts'
1
+ import { dispatchRpcInProcess } from '../server/rpc/dispatchRpcInProcess.ts'
2
+ import { findRpcByCommandName } from '../server/rpc/findRpcByCommandName.ts'
3
3
  import { buildRpcProxy } from '../shared/buildRpcProxy.ts'
4
4
  import { buildRpcRequest } from '../shared/buildRpcRequest.ts'
5
- import type { HttpVerb } from '../shared/types/HttpVerb.ts'
5
+ import type { HttpMethod } from '../shared/types/HttpMethod.ts'
6
6
  import type { RpcInvoker } from '../shared/types/RpcInvoker.ts'
7
7
  import type { CliManifest } from './types/CliManifest.ts'
8
8
 
@@ -24,8 +24,8 @@ decided at construction:
24
24
  against `<url>/<manifest[name].url>` using the manifest's method.
25
25
  Auth header is set from `token` when provided.
26
26
  - Without `url`: in-process mode. Each property access looks up the
27
- verb in the registry (populated by importing the project's rpc
28
- modules) and calls `verb.fetch(synthesizedRequest)` — same code
27
+ rpc in the registry (populated by importing the project's rpc
28
+ modules) and calls `rpc.fetch(synthesizedRequest)` — same code
29
29
  path the HTTP router uses, no network hop.
30
30
 
31
31
  The `manifest` is the bundler-emitted CLI manifest baked into the thin
@@ -62,9 +62,9 @@ export function createClient<Api extends AnyApi = AnyApi>(opts?: {
62
62
  undefined when the name is unknown in the active mode. Remote mode (url set)
63
63
  resolves method + url from the baked manifest — registry fallback for
64
64
  same-project callers — and sends the synthesized Request over the network.
65
- In-process mode resolves the verb from the registry and routes through
66
- dispatchVerbInProcess, the same synthesize-and-fetch the MCP dispatcher
67
- uses, so the two consumer surfaces can't drift on how a verb is invoked.
65
+ In-process mode resolves the rpc from the registry and routes through
66
+ dispatchRpcInProcess, the same synthesize-and-fetch the MCP dispatcher
67
+ uses, so the two consumer surfaces can't drift on how a rpc is invoked.
68
68
  */
69
69
  function resolveSend(name: string): ((args?: unknown) => Promise<Response>) | undefined {
70
70
  if (url) {
@@ -83,12 +83,12 @@ export function createClient<Api extends AnyApi = AnyApi>(opts?: {
83
83
  }),
84
84
  )
85
85
  }
86
- const entry = findVerbByCommandName(name)
86
+ const entry = findRpcByCommandName(name)
87
87
  if (!entry) {
88
88
  return undefined
89
89
  }
90
90
  return (args) =>
91
- dispatchVerbInProcess({
91
+ dispatchRpcInProcess({
92
92
  remote: entry.remote,
93
93
  args,
94
94
  baseUrl: 'http://localhost/',
@@ -99,8 +99,8 @@ export function createClient<Api extends AnyApi = AnyApi>(opts?: {
99
99
  // Remote-mode registry fallback for callers passing a url but no manifest.
100
100
  function registryCommand(
101
101
  name: string,
102
- ): { method: HttpVerb; url: string; accept?: string } | undefined {
103
- const found = findVerbByCommandName(name)
102
+ ): { method: HttpMethod; url: string; accept?: string } | undefined {
103
+ const found = findRpcByCommandName(name)
104
104
  return found ? { method: found.remote.method, url: found.remote.url } : undefined
105
105
  }
106
106
 
@@ -12,7 +12,7 @@ import type { CliManifest } from './types/CliManifest.ts'
12
12
  Runs one RPC command against a target server and prints the result, returning a
13
13
  process exit code. Shared by the one-shot path (runCli) and the interactive
14
14
  session (runSession) so a command behaves identically typed at the shell or at
15
- the session prompt. Streaming responses (sse/jsonl — a streaming verb or a socket
15
+ the session prompt. Streaming responses (sse/jsonl — a streaming rpc or a socket
16
16
  `tail`) print frame-by-frame as NDJSON; everything else decodes and prints once.
17
17
  */
18
18
  export async function dispatchCommand({
@@ -5,7 +5,7 @@ import type { CliTarget } from './types/CliTarget.ts'
5
5
  Prints the session's connection line. A local instance (spawned child) reads as
6
6
  "running a local instance"; a remote one reads as "connected to <name>", using the
7
7
  name the target already carries from its resolve-time probe and only re-probing
8
- when it doesn't. No target → the not-connected hint listing the verbs.
8
+ when it doesn't. No target → the not-connected hint listing the rpcs.
9
9
  */
10
10
  export async function printSessionStatus(target: CliTarget | undefined): Promise<void> {
11
11
  if (!target) {
@@ -11,7 +11,7 @@ const AUTO_START_CEILING_MS = 3000
11
11
 
12
12
  /*
13
13
  Resolves the connection to resume when the CLI runs without an explicit
14
- connection verb — the terminal analogue of the bundle's resolveLaunchTarget.
14
+ connection rpc — the terminal analogue of the bundle's resolveLaunchTarget.
15
15
  Reads the saved intent:
16
16
  - embedded → boot a fresh local instance (bounded; undefined on failure)
17
17
  - url, still alive → connect to it