@abide/abide 0.118.0 → 0.120.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 (150) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/bin/abide.ts +74 -11
  3. package/docs/REGISTRY.md +5 -4
  4. package/docs/RULEBOOK.md +35 -0
  5. package/docs/abide.md +363 -127
  6. package/package.json +1 -1
  7. package/scaffold/src/getGreeting.test.ts +1 -0
  8. package/scaffold/src/index.abide +4 -1
  9. package/scaffold/src/index.test.ts +2 -0
  10. package/scaffold/src/layout.abide +6 -6
  11. package/src/compiler/Lowering.ts +20 -2
  12. package/src/compiler/PositionTable.ts +1 -0
  13. package/src/compiler/Refusals.ts +1 -0
  14. package/src/compiler/TypeSession.ts +11 -8
  15. package/src/compiler/aliasedSymbol.ts +1 -1
  16. package/src/compiler/ambientReadsIn.ts +1 -0
  17. package/src/compiler/appLayout.ts +4 -2
  18. package/src/compiler/argsDefaultsIn.ts +1 -0
  19. package/src/compiler/argsSchemaOf.ts +1 -0
  20. package/src/compiler/checkApp.ts +3 -1
  21. package/src/compiler/checkedShadow.ts +2 -1
  22. package/src/compiler/classifyRead.ts +1 -0
  23. package/src/compiler/classifyTemplate.ts +1 -0
  24. package/src/compiler/compileApp.ts +4 -2
  25. package/src/compiler/compiledEntry.ts +1 -0
  26. package/src/compiler/componentGraph.ts +1 -0
  27. package/src/compiler/declaredExports.ts +4 -2
  28. package/src/compiler/emitClient.ts +19 -14
  29. package/src/compiler/emitComponent.ts +1 -0
  30. package/src/compiler/emitSource.ts +2 -1
  31. package/src/compiler/formatAbide.ts +857 -0
  32. package/src/compiler/formatDiagnosis.ts +1 -0
  33. package/src/compiler/handlersUnder.ts +2 -1
  34. package/src/compiler/hookCalls.ts +1 -0
  35. package/src/compiler/lineOf.ts +17 -0
  36. package/src/compiler/matchersOf.ts +13 -3
  37. package/src/compiler/mountsOf.ts +2 -0
  38. package/src/compiler/paramFile.ts +1 -0
  39. package/src/compiler/parseAbide.ts +48 -28
  40. package/src/compiler/projectShadow.ts +10 -3
  41. package/src/compiler/reactiveOptions.ts +1 -0
  42. package/src/compiler/reactiveTypes.ts +2 -1
  43. package/src/compiler/readsIn.ts +3 -2
  44. package/src/compiler/renderMarkup.ts +3 -2
  45. package/src/compiler/resolverIn.ts +2 -0
  46. package/src/compiler/sameTemplate.ts +150 -0
  47. package/src/compiler/scaffold.ts +4 -11
  48. package/src/compiler/serveLanguage.ts +46 -19
  49. package/src/compiler/sharedModuleWrites.ts +2 -1
  50. package/src/compiler/sourceMapOf.ts +1 -0
  51. package/src/compiler/splitImports.ts +2 -0
  52. package/src/compiler/templateWarnings.ts +2 -2
  53. package/src/compiler/trackingWarnings.ts +1 -0
  54. package/src/compiler/writeDefaultProject.ts +5 -1
  55. package/src/server/BRIDGE_SCHEME.ts +3 -0
  56. package/src/server/BRIDGE_VERSION.ts +1 -1
  57. package/src/server/abideModules.ts +2 -0
  58. package/src/server/answerFailure.ts +1 -0
  59. package/src/server/answerRequest.ts +15 -14
  60. package/src/server/answerRpc.ts +3 -2
  61. package/src/server/answerSocket.ts +2 -1
  62. package/src/server/answerValue.ts +1 -0
  63. package/src/server/answerWorker.ts +1 -0
  64. package/src/server/appTree.ts +3 -1
  65. package/src/server/argsFor.ts +1 -0
  66. package/src/server/assistant.ts +2 -1
  67. package/src/server/banner.ts +2 -0
  68. package/src/server/bridgeRelay.ts +1 -0
  69. package/src/server/callerOf.ts +1 -0
  70. package/src/server/claudeApi.ts +1 -0
  71. package/src/server/claudeCode.ts +30 -14
  72. package/src/server/clientBundle.ts +1 -0
  73. package/src/server/coerceArgs.ts +1 -2
  74. package/src/server/commands.ts +9 -0
  75. package/src/server/compressAnswer.ts +2 -1
  76. package/src/server/config.ts +2 -0
  77. package/src/server/createApp.ts +4 -2
  78. package/src/server/crossingOrigin.ts +1 -0
  79. package/src/server/csp.ts +1 -0
  80. package/src/server/deferredBuilds.ts +1 -0
  81. package/src/server/derivedArgs.ts +1 -0
  82. package/src/server/devWorker.ts +2 -1
  83. package/src/server/faceOf.ts +2 -7
  84. package/src/server/generatedSurfaces.ts +5 -1
  85. package/src/server/hooks.ts +1 -0
  86. package/src/server/iconAccent.ts +1 -1
  87. package/src/server/installServer.ts +1 -0
  88. package/src/server/installSourceMaps.ts +1 -0
  89. package/src/server/launcherPage.ts +2 -2
  90. package/src/server/manifestOf.ts +1 -0
  91. package/src/server/muxUpgrade.ts +1 -0
  92. package/src/server/nativeMenu.ts +2 -0
  93. package/src/server/principal.ts +2 -1
  94. package/src/server/remote.ts +1 -0
  95. package/src/server/render.ts +3 -3
  96. package/src/server/renderComponent.ts +1 -0
  97. package/src/server/reportHealth.ts +1 -0
  98. package/src/server/requestScope.ts +3 -1
  99. package/src/server/runDesktop.ts +3 -2
  100. package/src/server/seedBuffer.ts +1 -0
  101. package/src/server/serveDev.ts +3 -2
  102. package/src/server/shell.ts +2 -0
  103. package/src/server/streamEvent.ts +1 -0
  104. package/src/server/surfaceOf.ts +1 -0
  105. package/src/server/testBuild.ts +1 -0
  106. package/src/server/testMount.ts +1 -0
  107. package/src/server/trace.ts +2 -0
  108. package/src/server/withAbideSources.ts +1 -0
  109. package/src/shared/Attributes.ts +12 -8
  110. package/src/shared/cookies.ts +1 -1
  111. package/src/shared/escapeText.ts +1 -1
  112. package/src/shared/index.ts +1 -5
  113. package/src/shared/jsonSchema.ts +1 -3
  114. package/src/shared/menuBridge.ts +1 -2
  115. package/src/shared/outbox.ts +1 -1
  116. package/src/shared/reactive/Keyed.ts +2 -2
  117. package/src/shared/reactive/Reactive.ts +12 -16
  118. package/src/shared/reactive/index.ts +3 -4
  119. package/src/shared/reactive/isReactive.ts +1 -1
  120. package/src/shared/reactive/scoped.ts +2 -2
  121. package/src/shared/reactive/share.ts +1 -1
  122. package/src/shared/reactive/watch.ts +4 -4
  123. package/src/shared/refuse.ts +1 -1
  124. package/src/shared/rpc.ts +8 -11
  125. package/src/shared/url.ts +1 -1
  126. package/src/shared/validateJson.ts +1 -1
  127. package/src/ui/ArmRegion.ts +1 -0
  128. package/src/ui/Level.ts +1 -0
  129. package/src/ui/List.ts +1 -0
  130. package/src/ui/RawRun.ts +1 -0
  131. package/src/ui/Router.ts +1 -0
  132. package/src/ui/TryRegion.ts +1 -0
  133. package/src/ui/adoptWalk.ts +2 -1
  134. package/src/ui/bindElement.ts +1 -0
  135. package/src/ui/bindFiles.ts +1 -0
  136. package/src/ui/bindGroup.ts +1 -0
  137. package/src/ui/bindProperty.ts +1 -0
  138. package/src/ui/bindSpread.ts +1 -0
  139. package/src/ui/index.ts +1 -6
  140. package/src/ui/outboxQueue.ts +1 -1
  141. package/src/ui/parseMarkup.ts +2 -1
  142. package/src/ui/reconcile.ts +2 -1
  143. package/src/ui/reconcileHead.ts +1 -0
  144. package/src/ui/regions.ts +2 -1
  145. package/src/ui/registerWorker.ts +1 -0
  146. package/src/ui/rows.ts +1 -0
  147. package/src/ui/setMarkup.ts +1 -0
  148. package/src/ui/setText.ts +1 -0
  149. package/src/ui/socketWire.ts +4 -3
  150. package/src/ui/wire.ts +2 -1
@@ -1,11 +1,16 @@
1
1
  import { existsSync, type FSWatcher, watch } from 'node:fs' // bun exposes no file watcher or sync existence check of its own
2
2
  import { dirname } from 'node:path' // bun exposes no path helpers of its own
3
+
3
4
  import { realOrAsGiven } from '#server/realOrAsGiven.ts'
5
+
4
6
  import { checkSource, lowerApp } from './checkApp.ts'
5
7
  import { citedClauses } from './citedClauses.ts'
6
8
  import { type AppChecker, openChecker } from './compileApp.ts'
7
9
  import { DOCS_SITE } from './DOCS_SITE.ts'
10
+ import { formatAbide } from './formatAbide.ts'
11
+ import { formatDiagnosis } from './formatDiagnosis.ts'
8
12
  import { isNamedAlone } from './isNamedAlone.ts'
13
+ import { lineOf, offsetOf } from './lineOf.ts'
9
14
  import { nearest } from './nearest.ts'
10
15
  import type { Diagnosis, Inspection, Place } from './TypeSession.ts'
11
16
 
@@ -290,6 +295,7 @@ export async function serveLanguage(
290
295
  definitionProvider: true,
291
296
  referencesProvider: true,
292
297
  renameProvider: { prepareProvider: true },
298
+ documentFormattingProvider: true,
293
299
  completionProvider: {
294
300
  triggerCharacters: ['.', '<', '#', ':', '/'],
295
301
  },
@@ -392,8 +398,7 @@ export async function serveLanguage(
392
398
  const found = await references(params)
393
399
  const declarations = (
394
400
  params?.context as
395
- | { includeDeclaration?: boolean }
396
- | undefined
401
+ { includeDeclaration?: boolean } | undefined
397
402
  )?.includeDeclaration
398
403
  const locations: object[] = []
399
404
  for (const place of found?.occurrences ?? [])
@@ -457,6 +462,45 @@ export async function serveLanguage(
457
462
  })
458
463
  return true
459
464
  }
465
+ case 'textDocument/formatting': {
466
+ // 38.130 — the text the editor holds, formatted as `abide format` formats a file:
467
+ // one edit replacing the whole of it, none where nothing moves.
468
+ const uri = (params?.textDocument as { uri: string }).uri
469
+ const document = documents.get(uri)
470
+ if (document === undefined) {
471
+ send({ id: message.id, result: null })
472
+ return true
473
+ }
474
+ const path = Bun.fileURLToPath(uri)
475
+ const text = document.text
476
+ const formatted = await formatAbide(path, text)
477
+ // Shown rather than published: a publish would replace what the check reported.
478
+ for (const finding of formatted.findings)
479
+ send({
480
+ method: 'window/showMessage',
481
+ params: {
482
+ type: 2,
483
+ message: formatDiagnosis(finding, dirname(path)),
484
+ },
485
+ })
486
+ send({
487
+ id: message.id,
488
+ result:
489
+ formatted.text === text
490
+ ? []
491
+ : [
492
+ {
493
+ range: rangeOf({
494
+ path,
495
+ start: { line: 1, column: 1 },
496
+ end: lineOf(text, text.length),
497
+ }),
498
+ newText: formatted.text,
499
+ },
500
+ ],
501
+ })
502
+ return true
503
+ }
460
504
  case 'textDocument/didClose': {
461
505
  const closed = params?.textDocument as { uri: string }
462
506
  if (!documents.delete(closed.uri)) return true
@@ -542,23 +586,6 @@ function wordLength(line: string, character: number): number {
542
586
  return word === undefined ? 1 : word.length
543
587
  }
544
588
 
545
- /**
546
- * The protocol's `{ line, character }`, from 0, as an offset into `text`. A character is a UTF-16
547
- * code unit in the protocol and in a JavaScript string alike.
548
- */
549
- function offsetOf(
550
- text: string,
551
- position: { line: number; character: number },
552
- ): number {
553
- let at = 0
554
- for (let line = 0; line < position.line; line += 1) {
555
- const end = text.indexOf('\n', at)
556
- if (end === -1) return text.length
557
- at = end + 1
558
- }
559
- return at + position.character
560
- }
561
-
562
589
  /** A `Place` at `path`, lines and columns from 1, as the protocol's `Location`, from 0. */
563
590
  function locationOf(path: string, place: Place): object {
564
591
  return { uri: Bun.pathToFileURL(path).href, range: rangeOf(place) }
@@ -4,9 +4,10 @@ import {
4
4
  isVariableDeclarationList,
5
5
  } from 'typescript/unstable/ast/is'
6
6
  import type {
7
- Symbol as CheckedSymbol,
8
7
  Checker,
8
+ Symbol as CheckedSymbol,
9
9
  } from 'typescript/unstable/async'
10
+
10
11
  import { classifyRead, FUNCTIONS } from './classifyRead.ts'
11
12
  import type { Finding } from './Finding.ts'
12
13
  import type { Span } from './projectShadow.ts'
@@ -11,6 +11,7 @@
11
11
  // What no chunk covers is the framework's own scaffolding, and maps to nothing.
12
12
 
13
13
  import { encodeMappings, type Segment } from '#server/sourceMappings.ts'
14
+
14
15
  import type { LoweredChunk } from './Lowering.ts'
15
16
  import type { Origin } from './projectShadow.ts'
16
17
  import { mapBack } from './ShadowMap.ts'
@@ -1,5 +1,7 @@
1
1
  import { SyntaxKind } from 'typescript/unstable/ast'
2
+
2
3
  import { COMPONENT_QUERY, TEXT_QUERY } from '#server/MODULE_QUERIES.ts'
4
+
3
5
  import { isContextualKeyword, type Token, tokenize } from './tokenize.ts'
4
6
 
5
7
  /**
@@ -2,12 +2,12 @@ import type { CheckerAnswer } from './CheckerAnswer.ts'
2
2
  import type { Read } from './classifyRead.ts'
3
3
  import { isComponentTag } from './classifyTemplate.ts'
4
4
  import { isNamedAlone } from './isNamedAlone.ts'
5
- import { Lowering } from './Lowering.ts'
6
5
  import { diagnosisAt } from './lineOf.ts'
6
+ import { Lowering } from './Lowering.ts'
7
7
  import type { AbideFile } from './parseAbide.ts'
8
8
  import {
9
- type Body,
10
9
  bodiesOf,
10
+ type Body,
11
11
  type ForBlock,
12
12
  type TemplateNode,
13
13
  visitBodies,
@@ -14,6 +14,7 @@ import {
14
14
  isPropertyAccessExpression,
15
15
  isWhileStatement,
16
16
  } from 'typescript/unstable/ast/is'
17
+
17
18
  import { FUNCTIONS, TRANSPARENT } from './classifyRead.ts'
18
19
  import type { Finding } from './Finding.ts'
19
20
  import type { PositionKind, PositionTable } from './PositionTable.ts'
@@ -11,6 +11,7 @@
11
11
  import { mkdtemp, rm } from 'node:fs/promises' // bun exposes no temporary-directory creation or recursive removal of its own
12
12
  import { tmpdir } from 'node:os' // bun exposes no temporary-directory path of its own
13
13
  import { dirname, resolve } from 'node:path' // bun exposes no path helpers of its own
14
+
14
15
  import { nearest } from './nearest.ts'
15
16
 
16
17
  /** What every abide app in the docs and the scaffold is written against. */
@@ -33,7 +34,10 @@ export async function writeDefaultProject(
33
34
  await Bun.write(
34
35
  tsconfig,
35
36
  JSON.stringify({
36
- compilerOptions: { ...COMPILER_OPTIONS, paths: await aliasPaths(root) },
37
+ compilerOptions: {
38
+ ...COMPILER_OPTIONS,
39
+ paths: await aliasPaths(root),
40
+ },
37
41
  include: [`${root}/**/*`],
38
42
  }),
39
43
  )
@@ -0,0 +1,3 @@
1
+ // 44.40 — the scheme a pairing link is written in, which the handler `abide-bridge install` registers
2
+ // opens. The bridge spells it out as `PAIRING_SCHEME`, and a test holds the two equal.
3
+ export const BRIDGE_SCHEME = 'abide-bridge'
@@ -1,3 +1,3 @@
1
1
  // The `abide-bridge` release a pairing command names, so a visitor's `bunx` runs the relay this
2
2
  // server speaks. `packages/abide/tests/claudeCode.test.ts` holds it to the bridge package's own.
3
- export const BRIDGE_VERSION = '0.3.4'
3
+ export const BRIDGE_VERSION = '0.4.0'
@@ -18,7 +18,9 @@
18
18
  // to this one rather than registering a plugin nobody asks.
19
19
 
20
20
  import { basename, dirname, resolve } from 'node:path' // bun exposes no path helpers of its own
21
+
21
22
  import { SERVER_SPECIFIER } from '#shared/SPECIFIERS.ts'
23
+
22
24
  import type { ServerTable } from './answerRequest.ts'
23
25
  import type { MountRow } from './answerRpc.ts'
24
26
  import {
@@ -7,6 +7,7 @@ import { type Failed, problemOf } from '#shared/Failed.ts'
7
7
  import { RENDER_LOG } from '#shared/log.ts'
8
8
  import { coversPrefix, partsOf } from '#shared/matchRoute.ts'
9
9
  import { coversRoute, parseRoutePattern } from '#shared/routePattern.ts'
10
+
10
11
  import type { AppSources } from './answerRequest.ts'
11
12
  import { DOCUMENT_HEADERS } from './DOCUMENT_HEADERS.ts'
12
13
  import { boundPage, renderDocument } from './render.ts'
@@ -19,23 +19,23 @@ import {
19
19
  } from '#shared/ADDRESSES.ts'
20
20
  import { APP_META } from '#shared/APP_META.ts'
21
21
  import { DEBUG_META } from '#shared/DEBUG_META.ts'
22
- import { ICON_ADDRESSES } from '#shared/LAUNCHER_FILES.ts'
23
- import { OFFLINE_META } from '#shared/OFFLINE_META.ts'
24
22
  import { decodedOrKept } from '#shared/decodedOrKept.ts'
25
23
  import { ERROR_META } from '#shared/ERROR_META.ts'
26
24
  import { escapeAttribute } from '#shared/escapeText.ts'
27
25
  import { type Failed, isFailed } from '#shared/Failed.ts'
28
26
  import { HandlerRoute } from '#shared/HandlerRoute.ts'
27
+ import { health } from '#shared/health.ts'
29
28
  import { KEEP_HEADER } from '#shared/KEEP_HEADER.ts'
30
29
  import { KEEP_OPENED } from '#shared/KEEP_OPENED.ts'
31
- import { health } from '#shared/health.ts'
30
+ import { ICON_ADDRESSES } from '#shared/LAUNCHER_FILES.ts'
32
31
  import { appName } from '#shared/log.ts'
33
- import { compose } from '#shared/Middleware.ts'
34
- import { MOUNT_META } from '#shared/MOUNT_META.ts'
35
32
  import { matchOf, type Params, partsOf } from '#shared/matchRoute.ts'
33
+ import { compose } from '#shared/Middleware.ts'
36
34
  import { MOUNT, mounted } from '#shared/mount.ts'
35
+ import { MOUNT_META } from '#shared/MOUNT_META.ts'
37
36
  import { NAVIGATION_HEADER } from '#shared/NAVIGATION_HEADER.ts'
38
- import { refuse, notFound as refusedNotFound } from '#shared/refuse.ts'
37
+ import { OFFLINE_META } from '#shared/OFFLINE_META.ts'
38
+ import { notFound as refusedNotFound, refuse } from '#shared/refuse.ts'
39
39
  import { arrive } from '#shared/route.ts'
40
40
  import {
41
41
  parseRoutePattern,
@@ -44,29 +44,30 @@ import {
44
44
  } from '#shared/routePattern.ts'
45
45
  import type { Method, Rpc } from '#shared/rpc.ts'
46
46
  import { rpcRequest } from '#shared/rpcRequest.ts'
47
+
47
48
  import { answered, answerFailure } from './answerFailure.ts'
48
49
  import {
49
50
  answerRpc,
50
51
  type Mounted,
51
- type MountRow,
52
52
  mountedOwn,
53
53
  mountHandlers,
54
+ type MountRow,
54
55
  } from './answerRpc.ts'
55
56
  import { answerBlobTagged, answerValue, contentEtag } from './answerValue.ts'
56
- import { answerBridge } from './bridgeRelay.ts'
57
- import { CLIENT_ENTRY } from './CLIENT_BUILD.ts'
58
- import { appManifest } from './appManifest.ts'
59
57
  import { answerWorker } from './answerWorker.ts'
58
+ import { appManifest } from './appManifest.ts'
59
+ import { answerBridge } from './bridgeRelay.ts'
60
60
  import { callerOrMint } from './callerOf.ts'
61
- import { settingOn } from './config.ts'
61
+ import { CLIENT_ENTRY } from './CLIENT_BUILD.ts'
62
62
  import { compressAnswer } from './compressAnswer.ts'
63
- import { DOCUMENT_HEADERS } from './DOCUMENT_HEADERS.ts'
64
- import { OFFLINE_STALE } from './OFFLINE_STALE.ts'
63
+ import { settingOn } from './config.ts'
65
64
  import { noteCallInRender } from './deferredBuilds.ts'
65
+ import { DOCUMENT_HEADERS } from './DOCUMENT_HEADERS.ts'
66
66
  import { answerMcp, openApi } from './generatedSurfaces.ts'
67
67
  import { type AppRung, failureOf, rungsOver } from './hooks.ts'
68
68
  import { chunksOf, type Manifest } from './manifestOf.ts'
69
69
  import { muxAdmission } from './muxUpgrade.ts'
70
+ import { OFFLINE_STALE } from './OFFLINE_STALE.ts'
70
71
  import { plainAnswer } from './plainAnswer.ts'
71
72
  import { prefers } from './prefers.ts'
72
73
  import { PRINCIPAL_MOUNT } from './principal.ts'
@@ -78,8 +79,8 @@ import {
78
79
  renderDocument,
79
80
  } from './render.ts'
80
81
  import {
81
- type AnsweringApp,
82
82
  adoptRequest,
83
+ type AnsweringApp,
83
84
  beginRender,
84
85
  currentRequestScope,
85
86
  type PageRender,
@@ -11,15 +11,16 @@ import { GLOBAL_GETS } from '#shared/GLOBAL_GETS.ts'
11
11
  import { isRecord } from '#shared/isRecord.ts'
12
12
  import type { JsonValue } from '#shared/JsonValue.ts'
13
13
  import { mounted as underMount } from '#shared/mount.ts'
14
- import { RPC_SERVER } from '#shared/RPC_SERVER.ts'
15
14
  import type { Gated } from '#shared/reactive/argsContract.ts'
16
15
  import { deadlineOf, heldGenerationOf } from '#shared/reactive/index.ts'
17
16
  import type { Defaults } from '#shared/reactive/parameterDefaults.ts'
18
17
  import { notFound, refuse } from '#shared/refuse.ts'
19
18
  import type { HeldToAnnotation, Method, Rpc } from '#shared/rpc.ts'
19
+ import { RPC_SERVER } from '#shared/RPC_SERVER.ts'
20
20
  import { argsInURL } from '#shared/rpcRequest.ts'
21
21
  import { VERSION_HEADER } from '#shared/VERSION_HEADER.ts'
22
22
  import { versionInEtag } from '#shared/versionInEtag.ts'
23
+
23
24
  import {
24
25
  answered,
25
26
  answerFailure,
@@ -29,7 +30,6 @@ import {
29
30
  import { followAsEvents } from './answerSocket.ts'
30
31
  import { answerValue, namesEtag, sseHeaders } from './answerValue.ts'
31
32
  import { argsFor } from './argsFor.ts'
32
- import { OFFLINE_STALE } from './OFFLINE_STALE.ts'
33
33
  import { callerOf } from './callerOf.ts'
34
34
  import {
35
35
  argsDocumentOf,
@@ -45,6 +45,7 @@ import {
45
45
  crossingRefusal,
46
46
  } from './crossingOrigin.ts'
47
47
  import { type AppRung, failureOf, rungsOver } from './hooks.ts'
48
+ import { OFFLINE_STALE } from './OFFLINE_STALE.ts'
48
49
  import { prefers } from './prefers.ts'
49
50
  import {
50
51
  currentRequestScope,
@@ -23,6 +23,7 @@ import { PATCH_SINKS } from '#shared/reactive/PATCH_SINKS.ts'
23
23
  import { unowned } from '#shared/reactive/watch.ts'
24
24
  import { refuse } from '#shared/refuse.ts'
25
25
  import { rpcRequest } from '#shared/rpcRequest.ts'
26
+
26
27
  import { handlerKey, type Mounted } from './answerRpc.ts'
27
28
  import { argsFor } from './argsFor.ts'
28
29
  import { positiveSetting } from './config.ts'
@@ -32,8 +33,8 @@ import { sendOrClose } from './muxUpgrade.ts'
32
33
  import {
33
34
  heldByApp,
34
35
  inOpenScope,
35
- type RequestScope,
36
36
  releaseScope,
37
+ type RequestScope,
37
38
  scopeFor,
38
39
  } from './requestScope.ts'
39
40
  import { EVENT_STREAM_OPENER, streamEvent } from './streamEvent.ts'
@@ -4,6 +4,7 @@
4
4
  // `Values` a handler returned — framed one chunk per pull (16.32, 16.44–16.47).
5
5
 
6
6
  import { isFailed, problemOf } from '#shared/Failed.ts'
7
+
7
8
  import { compressAnswer } from './compressAnswer.ts'
8
9
  import { prefers } from './prefers.ts'
9
10
  import {
@@ -19,6 +19,7 @@ import {
19
19
  } from '#shared/OFFLINE_CACHES.ts'
20
20
  import { PRINCIPAL_HEADER } from '#shared/PRINCIPAL_HEADER.ts'
21
21
  import { REACHED_MESSAGE } from '#shared/REACHED_MESSAGE.ts'
22
+
22
23
  import type { Manifest } from './manifestOf.ts'
23
24
 
24
25
  // What `framingOf` reads as a framed stream (16.32), and a binary's bytes (16.34), which may be one —
@@ -13,19 +13,21 @@
13
13
  // can drop them.
14
14
 
15
15
  import { type Dirent, existsSync, readdirSync } from 'node:fs' // bun has no directory listing or sync existence check of its own
16
+
16
17
  import type { Refusals } from '#compiler/Refusals.ts'
17
18
  import { ABIDE_ADDRESS } from '#shared/ADDRESSES.ts'
18
19
  import {
19
20
  PUBLIC_DIRECTORY,
20
21
  RESERVED_DIRECTORIES,
21
22
  } from '#shared/RESERVED_DIRECTORIES.ts'
23
+
22
24
  import {
23
25
  DIRECTORY_FILES,
26
+ isBenchFile,
24
27
  isDeclarationFile,
25
28
  isHandlerFile,
26
29
  isParamFile,
27
30
  isServerFile,
28
- isBenchFile,
29
31
  isTestFile,
30
32
  placeOf,
31
33
  routeOf,
@@ -5,6 +5,7 @@
5
5
  import { DERIVED_ARGS } from '#shared/DERIVED_ARGS.ts'
6
6
  import { normalized } from '#shared/reactive/argsContract.ts'
7
7
  import { validationError } from '#shared/refuse.ts'
8
+
8
9
  import type { Mounted } from './answerRpc.ts'
9
10
  import { coerceArgs } from './coerceArgs.ts'
10
11
 
@@ -12,8 +12,9 @@
12
12
 
13
13
  import { namedLogger } from '#shared/log.ts'
14
14
  import { refuse } from '#shared/refuse.ts'
15
- import { IMAGE_TYPES } from './IMAGE_TYPES.ts'
15
+
16
16
  import { type McpReply, McpSurface } from './generatedSurfaces.ts'
17
+ import { IMAGE_TYPES } from './IMAGE_TYPES.ts'
17
18
  import { request, within } from './requestScope.ts'
18
19
 
19
20
  /** A piece of a turn: words, or an image as base64 with its type. */
@@ -1,6 +1,8 @@
1
1
  import { networkInterfaces } from 'node:os' // bun exposes no network interface list of its own
2
+
2
3
  import { useColor } from '#shared/log.ts'
3
4
  import { paint as painted } from '#shared/paint.ts'
5
+
4
6
  import type { AppPackage } from './config.ts'
5
7
 
6
8
  /**
@@ -15,6 +15,7 @@
15
15
 
16
16
  import { isFailed } from '#shared/Failed.ts'
17
17
  import { refuse } from '#shared/refuse.ts'
18
+
18
19
  import { answered } from './answerFailure.ts'
19
20
  import { bodyWithin } from './answerRpc.ts'
20
21
  import { framed } from './answerValue.ts'
@@ -1,4 +1,5 @@
1
1
  import { cookies } from '#shared/cookies.ts'
2
+
2
3
  import { randomToken } from './randomToken.ts'
3
4
  import {
4
5
  currentRequestScope,
@@ -8,6 +8,7 @@
8
8
 
9
9
  import { framedStream } from '#shared/framing.ts'
10
10
  import { refuse } from '#shared/refuse.ts'
11
+
11
12
  import { blocksOf, type Provider, textOf } from './assistant.ts'
12
13
 
13
14
  /** A Messages API request body, less what abide writes, with the client's key and origin. */
@@ -10,6 +10,7 @@
10
10
 
11
11
  import { mounted } from '#shared/mount.ts'
12
12
  import { refuse } from '#shared/refuse.ts'
13
+
13
14
  import {
14
15
  type AssistantContent,
15
16
  type AssistantEvent,
@@ -18,6 +19,7 @@ import {
18
19
  forwardOf,
19
20
  type Provider,
20
21
  } from './assistant.ts'
22
+ import { BRIDGE_SCHEME } from './BRIDGE_SCHEME.ts'
21
23
  import { BRIDGE_VERSION } from './BRIDGE_VERSION.ts'
22
24
  import {
23
25
  followSignIn,
@@ -49,8 +51,15 @@ export type ClaudeCodeConfig = {
49
51
  /** The built-in tools a pairing command grants, as the visitor would after its `--`. 44.32. */
50
52
  type PairingOptions = Pick<ClaudeCodeConfig, 'tools'>
51
53
 
52
- /** Whether this browser's bridge is connected, and the command that pairs one where it is not. */
53
- export type BridgeStatus = { paired: boolean; command: string | undefined }
54
+ /**
55
+ * Whether this browser's bridge is connected, and the command and the link that pair one where it is
56
+ * not.
57
+ */
58
+ export type BridgeStatus = {
59
+ paired: boolean
60
+ command: string | undefined
61
+ link: string | undefined
62
+ }
54
63
 
55
64
  /** The visitor's own Claude Code, reached through the bridge paired to their browser. */
56
65
  function claudeCodeProvider(config: ClaudeCodeConfig = {}): Provider {
@@ -148,35 +157,40 @@ function askingCaller(): string {
148
157
  return caller
149
158
  }
150
159
 
160
+ /** One pairing code, as the command a visitor runs and the link they open, and when it stops standing. */
161
+ type Pairing = { command: string; link: string; expires: number }
162
+
151
163
  /**
152
- * The command that pairs a bridge with this browser, and when its code stops standing. 44.14. The
153
- * `tools` it is given are written after its `--`, so the visitor running it grants them (44.29, 44.32).
164
+ * The command and the link that pair a bridge with this browser, and when their code stops standing.
165
+ * 44.14. The `tools` it is given are written after the command's `--` and in the link, so the visitor
166
+ * running either grants them (44.29, 44.32, 44.40).
154
167
  */
155
- function bridgeCommand(options: PairingOptions = {}): {
156
- command: string
157
- expires: number
158
- } {
168
+ function bridgeCommand(options: PairingOptions = {}): Pairing {
159
169
  return commandFor(askingCaller(), signedInAs(), options)
160
170
  }
161
171
 
162
- /** The command pairing a bridge with `caller` signed in as `signedIn` (44.33). */
172
+ /** The command and the link pairing a bridge with `caller` signed in as `signedIn` (44.33). */
163
173
  function commandFor(
164
174
  caller: string,
165
175
  signedIn: string,
166
176
  { tools }: PairingOptions,
167
- ): { command: string; expires: number } {
177
+ ): Pairing {
168
178
  const { code, expires } = mintCode(caller, signedIn)
169
179
  const site = `${appOrigin() ?? new URL(request().url).origin}${mounted('')}`
170
- const grant = tools?.length ? ` -- --tools ${tools.join(',')}` : ''
180
+ const granted = tools?.length ? tools.join(',') : undefined
181
+ const query = new URLSearchParams({ site, code, version: BRIDGE_VERSION })
182
+ if (granted !== undefined) query.set('tools', granted)
183
+ const grant = granted === undefined ? '' : ` -- --tools ${granted}`
171
184
  return {
172
185
  command: `bunx @abide/bridge@${BRIDGE_VERSION} ${site} ${code}${grant}`,
186
+ link: `${BRIDGE_SCHEME}://pair?${query}`,
173
187
  expires,
174
188
  }
175
189
  }
176
190
 
177
191
  /**
178
- * This browser's pairing as it changes: unpaired with the command to run, then paired the moment a
179
- * bridge connects, and unpaired again if it goes. 44.20.
192
+ * This browser's pairing as it changes: unpaired with the command to run and the link to open, then
193
+ * paired the moment a bridge connects, and unpaired again if it goes. 44.20.
180
194
  *
181
195
  * 44.34 — IT FOLLOWS WHOEVER SIGNS IN AT THE BROWSER, though its own request carries the cookies it
182
196
  * opened with: a sign-in or a sign-out made since is the pairing it watches from then on, and the
@@ -202,7 +216,7 @@ async function* bridge(
202
216
  rouse()
203
217
  }
204
218
  signal.addEventListener('abort', rouse)
205
- let pairing: { command: string; expires: number } | undefined
219
+ let pairing: Pairing | undefined
206
220
  let said = ''
207
221
  try {
208
222
  while (!signal.aborted) {
@@ -224,7 +238,9 @@ async function* bridge(
224
238
  const status: BridgeStatus = {
225
239
  paired,
226
240
  command: paired ? undefined : pairing?.command,
241
+ link: paired ? undefined : pairing?.link,
227
242
  }
243
+ // The link is minted with the command, so the command alone tells a new pairing.
228
244
  const saying = `${status.paired} ${status.command}`
229
245
  if (saying !== said) {
230
246
  said = saying
@@ -7,6 +7,7 @@
7
7
  // long a browser may keep it.
8
8
 
9
9
  import { CLIENT_ADDRESS } from '#shared/ADDRESSES.ts'
10
+
10
11
  import { abideModules, type Compilation, tablePath } from './abideModules.ts'
11
12
  import { appPlugins } from './appPlugins.ts'
12
13
  import { CLIENT_ENTRY } from './CLIENT_BUILD.ts'
@@ -19,8 +19,7 @@ export type Document = {
19
19
  /** The document a handler's `args` schema declares, or `undefined`. */
20
20
  export function argsDocumentOf(handler: Gated<unknown>): Document | undefined {
21
21
  const gate = gateOf(handler) as
22
- | { '~standard'?: { jsonSchema?: { input?: () => unknown } } }
23
- | undefined
22
+ { '~standard'?: { jsonSchema?: { input?: () => unknown } } } | undefined
24
23
  const document = gate?.['~standard']?.jsonSchema?.input?.()
25
24
  return document !== null && typeof document === 'object'
26
25
  ? (document as Document)
@@ -5,8 +5,10 @@
5
5
 
6
6
  import { resolve } from 'node:path' // bun exposes no path helpers of its own
7
7
  import { parseArgs } from 'node:util' // bun exposes no argv parser of its own yet
8
+
8
9
  import { MCP_ADDRESS, OPENAPI_ADDRESS } from '#shared/ADDRESSES.ts'
9
10
  import { IN_PROCESS_ORIGIN } from '#shared/IN_PROCESS_ORIGIN.ts'
11
+
10
12
  import { banner } from './banner.ts'
11
13
  import { type App, type AppHandle, enterApp } from './createApp.ts'
12
14
  import { stopOnSignalOrFault } from './faultPolicy.ts'
@@ -32,6 +34,7 @@ type SourceCommands = Record<
32
34
  | 'dev'
33
35
  | 'build'
34
36
  | 'check'
37
+ | 'format'
35
38
  | 'routes'
36
39
  | 'compile'
37
40
  | 'bundle'
@@ -194,6 +197,11 @@ function commandsOf(host: Host): Row[] {
194
197
  'type-check the templates and the TypeScript, on the line you wrote',
195
198
  'check',
196
199
  ),
200
+ fromSource(
201
+ 'format [--check] [dir…]',
202
+ 'format every .abide — its code by bun fmt, its markup by its nesting',
203
+ 'format',
204
+ ),
197
205
  fromSource(
198
206
  'routes [--json]',
199
207
  'every address the app answers, its file, and where each hook is filed',
@@ -288,6 +296,7 @@ function parseFlags(argv: string[]) {
288
296
  target: { type: 'string' },
289
297
  platforms: { type: 'string' },
290
298
  json: { type: 'boolean' },
299
+ check: { type: 'boolean' },
291
300
  help: { type: 'boolean', short: 'h' },
292
301
  version: { type: 'boolean', short: 'v' },
293
302
  },
@@ -10,9 +10,10 @@
10
10
  // BUN'S GZIP parts from zlib's past ~130 KB, which only the off-loop lane reaches — so that lane takes
11
11
  // zlib's, the loop keeps bun's, and the two make the same bytes.
12
12
  import { brotliCompress, brotliCompressSync, constants, gzip } from 'node:zlib' // bun has no brotli, and no compression off the loop
13
+
13
14
  import {
14
- ENCODINGS,
15
15
  type Encoding,
16
+ ENCODINGS,
16
17
  negotiateEncoding,
17
18
  } from './negotiateEncoding.ts'
18
19
 
@@ -5,11 +5,13 @@
5
5
  // and a throw out of `onConfig` is the value's standing error, which a read throws (27.1).
6
6
 
7
7
  import { homedir } from 'node:os' // bun exposes no home directory of its own
8
+
8
9
  import { isRecord } from '#shared/isRecord.ts'
9
10
  import { numberFromText } from '#shared/numberFromText.ts'
10
11
  import { reactive } from '#shared/reactive/index.ts'
11
12
  import { validationError } from '#shared/refuse.ts'
12
13
  import { runSchema } from '#shared/Schema.ts'
14
+
13
15
  import { appDirectories } from './appDirectories.ts'
14
16
  import { type Env, HOOKS, inOrder } from './hooks.ts'
15
17
  import { withoutTrailingSlash } from './withoutTrailingSlash.ts'