@defrex/autobuild 0.8.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.
- package/LICENSE +201 -0
- package/README.md +192 -0
- package/SPEC.md +2269 -0
- package/bin/ab-build-runner.ts +47 -0
- package/bin/ab-dev.ts +27 -0
- package/bin/ab-dispatch-kernel.ts +92 -0
- package/bin/ab-harvest-runner.ts +29 -0
- package/bin/ab-init-probe.ts +19 -0
- package/bin/ab-process-group-reaper.ts +32 -0
- package/bin/ab.ts +6 -0
- package/bin/agent/ab +3 -0
- package/docs/answering-blocked-builds.md +89 -0
- package/docs/architecture.md +726 -0
- package/docs/assets/headline-wide.png +0 -0
- package/docs/configuration.md +1623 -0
- package/docs/hosted-dispatcher-cutover-validation.md +178 -0
- package/docs/hosted-dispatcher.md +204 -0
- package/docs/mcp.md +119 -0
- package/docs/operator-api.md +186 -0
- package/docs/remote-store-protocol.md +972 -0
- package/docs/remote-ticket-protocol.md +79 -0
- package/docs/setup.md +289 -0
- package/docs/spec-standard.md +63 -0
- package/docs/ticket-dependencies.md +59 -0
- package/docs/vercel-sandbox-rollout-probe.md +7 -0
- package/package.json +135 -0
- package/packages/core/src/cli/args.ts +74 -0
- package/packages/core/src/cli/artifact.ts +182 -0
- package/packages/core/src/cli/binary.ts +198 -0
- package/packages/core/src/cli/build-control.ts +789 -0
- package/packages/core/src/cli/build-progress.ts +55 -0
- package/packages/core/src/cli/bulk-control.ts +385 -0
- package/packages/core/src/cli/context.ts +470 -0
- package/packages/core/src/cli/dashboard/actions.ts +59 -0
- package/packages/core/src/cli/dashboard/cells.ts +92 -0
- package/packages/core/src/cli/dashboard/composer.ts +230 -0
- package/packages/core/src/cli/dashboard/detail.ts +100 -0
- package/packages/core/src/cli/dashboard/frame-image.ts +373 -0
- package/packages/core/src/cli/dashboard/live.ts +152 -0
- package/packages/core/src/cli/dashboard/model.ts +1173 -0
- package/packages/core/src/cli/dashboard/poll.ts +196 -0
- package/packages/core/src/cli/dashboard/render.ts +1568 -0
- package/packages/core/src/cli/dashboard/selection.ts +66 -0
- package/packages/core/src/cli/dashboard/session-feed.ts +244 -0
- package/packages/core/src/cli/dashboard/session-view.ts +506 -0
- package/packages/core/src/cli/dashboard/transcript.ts +108 -0
- package/packages/core/src/cli/dev.ts +46 -0
- package/packages/core/src/cli/dispatch-frontend.ts +1045 -0
- package/packages/core/src/cli/dispatch-process.ts +259 -0
- package/packages/core/src/cli/dispatch.ts +3692 -0
- package/packages/core/src/cli/dotenv.ts +48 -0
- package/packages/core/src/cli/env.ts +206 -0
- package/packages/core/src/cli/harvest.ts +698 -0
- package/packages/core/src/cli/help.ts +713 -0
- package/packages/core/src/cli/init-agent.ts +106 -0
- package/packages/core/src/cli/init-validation.ts +583 -0
- package/packages/core/src/cli/init.ts +756 -0
- package/packages/core/src/cli/installation.ts +333 -0
- package/packages/core/src/cli/keyboard.ts +261 -0
- package/packages/core/src/cli/main.ts +1399 -0
- package/packages/core/src/cli/mcp.ts +279 -0
- package/packages/core/src/cli/models.ts +65 -0
- package/packages/core/src/cli/observe.ts +55 -0
- package/packages/core/src/cli/plugin.ts +227 -0
- package/packages/core/src/cli/pr-attachments.ts +138 -0
- package/packages/core/src/cli/pr-summary.ts +124 -0
- package/packages/core/src/cli/publication-settlement.ts +188 -0
- package/packages/core/src/cli/repo-state.ts +213 -0
- package/packages/core/src/cli/repository-status.ts +80 -0
- package/packages/core/src/cli/self-update.ts +443 -0
- package/packages/core/src/cli/status.ts +895 -0
- package/packages/core/src/cli/store-opening.contract.ts +289 -0
- package/packages/core/src/cli/store-opening.ts +167 -0
- package/packages/core/src/cli/store-ref.ts +33 -0
- package/packages/core/src/cli/terminal-restore.ts +174 -0
- package/packages/core/src/cli/terminal.ts +361 -0
- package/packages/core/src/cli/terminals.ts +995 -0
- package/packages/core/src/cli/testkit.ts +194 -0
- package/packages/core/src/cli/ticket.ts +484 -0
- package/packages/core/src/cli/upgrade-agent.ts +180 -0
- package/packages/core/src/cli/upgrade-commit.ts +468 -0
- package/packages/core/src/cli/upgrade-notice.ts +91 -0
- package/packages/core/src/cli/upgrade-progress.ts +110 -0
- package/packages/core/src/cli/upgrade.ts +888 -0
- package/packages/core/src/cli/wait.ts +587 -0
- package/packages/core/src/cli/watch.ts +932 -0
- package/packages/core/src/config/live.ts +255 -0
- package/packages/core/src/config/load.ts +98 -0
- package/packages/core/src/config/roles.ts +438 -0
- package/packages/core/src/config/schema.ts +774 -0
- package/packages/core/src/distribution.ts +24 -0
- package/packages/core/src/events/catalog.ts +201 -0
- package/packages/core/src/events/envelope.ts +59 -0
- package/packages/core/src/events/payloads.ts +618 -0
- package/packages/core/src/events/repository.ts +559 -0
- package/packages/core/src/events/sessions.ts +180 -0
- package/packages/core/src/harvest/schema.ts +147 -0
- package/packages/core/src/ids.ts +45 -0
- package/packages/core/src/integration/harness.ts +681 -0
- package/packages/core/src/kernel/auto-merge.ts +259 -0
- package/packages/core/src/kernel/converge.ts +117 -0
- package/packages/core/src/kernel/dispatch-settings.ts +60 -0
- package/packages/core/src/kernel/dispatch-status.ts +141 -0
- package/packages/core/src/kernel/engine.ts +1047 -0
- package/packages/core/src/kernel/harvest.ts +823 -0
- package/packages/core/src/kernel/origin.ts +69 -0
- package/packages/core/src/kernel/phases.ts +196 -0
- package/packages/core/src/kernel/plan-verify-selection.ts +105 -0
- package/packages/core/src/kernel/pr-attachments.ts +82 -0
- package/packages/core/src/kernel/reducer.ts +752 -0
- package/packages/core/src/kernel/stall.ts +155 -0
- package/packages/core/src/kernel/verify-applicability.ts +52 -0
- package/packages/core/src/markdown.ts +67 -0
- package/packages/core/src/ontology.ts +288 -0
- package/packages/core/src/open-map.ts +203 -0
- package/packages/core/src/operator/annotations.ts +187 -0
- package/packages/core/src/operator/client.ts +311 -0
- package/packages/core/src/operator/control.ts +117 -0
- package/packages/core/src/operator/index.ts +62 -0
- package/packages/core/src/operator/presentation.ts +22 -0
- package/packages/core/src/operator/protocol.ts +98 -0
- package/packages/core/src/operator/query.ts +201 -0
- package/packages/core/src/operator/registry.ts +1043 -0
- package/packages/core/src/operator/requests.ts +138 -0
- package/packages/core/src/operator/sandbox.ts +458 -0
- package/packages/core/src/operator/server.ts +616 -0
- package/packages/core/src/operator/sessions.ts +223 -0
- package/packages/core/src/operator/tickets.ts +177 -0
- package/packages/core/src/plugin-sdk/index.ts +174 -0
- package/packages/core/src/plugins/contract-entry.ts +74 -0
- package/packages/core/src/plugins/contract-env.ts +3 -0
- package/packages/core/src/plugins/load.ts +259 -0
- package/packages/core/src/plugins/manifest.ts +236 -0
- package/packages/core/src/plugins/registry.ts +274 -0
- package/packages/core/src/plugins/runtimes.ts +75 -0
- package/packages/core/src/ports/forge/contract.ts +258 -0
- package/packages/core/src/ports/forge/create.ts +74 -0
- package/packages/core/src/ports/forge/fake.ts +488 -0
- package/packages/core/src/ports/forge/github-pr-attachments.ts +351 -0
- package/packages/core/src/ports/forge/github-transport.ts +352 -0
- package/packages/core/src/ports/forge/github.ts +1162 -0
- package/packages/core/src/ports/forge/local-git.ts +568 -0
- package/packages/core/src/ports/runner/claude.ts +873 -0
- package/packages/core/src/ports/runner/cli-stream.ts +90 -0
- package/packages/core/src/ports/runner/codex.ts +873 -0
- package/packages/core/src/ports/runner/contract.ts +492 -0
- package/packages/core/src/ports/runner/fake.ts +213 -0
- package/packages/core/src/ports/runner/json-record.ts +2 -0
- package/packages/core/src/ports/runner/live-contract-fixture.ts +176 -0
- package/packages/core/src/ports/runner/one-shot.ts +29 -0
- package/packages/core/src/ports/runner/pi-bridge-core.ts +136 -0
- package/packages/core/src/ports/runner/pi-bridge.js +6 -0
- package/packages/core/src/ports/runner/pi-rpc.ts +454 -0
- package/packages/core/src/ports/runner/pi.ts +617 -0
- package/packages/core/src/ports/runner/production.ts +74 -0
- package/packages/core/src/ports/runner/provider-error.ts +121 -0
- package/packages/core/src/ports/runner/routing.ts +234 -0
- package/packages/core/src/ports/runner/runtime.ts +214 -0
- package/packages/core/src/ports/runner/session-env.ts +34 -0
- package/packages/core/src/ports/runner/stream-parts.ts +103 -0
- package/packages/core/src/ports/tickets/contract.ts +570 -0
- package/packages/core/src/ports/tickets/create.ts +135 -0
- package/packages/core/src/ports/tickets/fake.ts +215 -0
- package/packages/core/src/ports/tickets/file.ts +559 -0
- package/packages/core/src/ports/tickets/linear.ts +811 -0
- package/packages/core/src/ports/tickets/operations.ts +161 -0
- package/packages/core/src/ports/tickets/remote-index.ts +13 -0
- package/packages/core/src/ports/tickets/remote-protocol.ts +80 -0
- package/packages/core/src/ports/tickets/remote-server.ts +185 -0
- package/packages/core/src/ports/tickets/remote.ts +130 -0
- package/packages/core/src/ports/tickets/update.ts +51 -0
- package/packages/core/src/ports/types.ts +528 -0
- package/packages/core/src/ports/workspace/build-execution.ts +123 -0
- package/packages/core/src/ports/workspace/contract.ts +136 -0
- package/packages/core/src/ports/workspace/create.ts +132 -0
- package/packages/core/src/ports/workspace/distribution-archive.ts +317 -0
- package/packages/core/src/ports/workspace/fake.ts +432 -0
- package/packages/core/src/ports/workspace/git-worktree.ts +848 -0
- package/packages/core/src/ports/workspace/harvest-execution.ts +111 -0
- package/packages/core/src/ports/workspace/in-process-build-execution.ts +30 -0
- package/packages/core/src/ports/workspace/local-build-execution.ts +88 -0
- package/packages/core/src/ports/workspace/operator-sandbox.ts +142 -0
- package/packages/core/src/ports/workspace/phase-scratch.ts +156 -0
- package/packages/core/src/ports/workspace/vercel-sandbox.ts +2541 -0
- package/packages/core/src/processes/build-child-exit.ts +78 -0
- package/packages/core/src/processes/build-child.ts +119 -0
- package/packages/core/src/processes/build-execution-state.ts +95 -0
- package/packages/core/src/processes/build-parent-watch.ts +35 -0
- package/packages/core/src/processes/build-runner.ts +2614 -0
- package/packages/core/src/processes/dispatcher.ts +2825 -0
- package/packages/core/src/processes/execution-settlement.ts +171 -0
- package/packages/core/src/processes/harvest-child.ts +115 -0
- package/packages/core/src/processes/harvest-execution-state.ts +121 -0
- package/packages/core/src/processes/harvest-runner.ts +1656 -0
- package/packages/core/src/processes/harvest.ts +595 -0
- package/packages/core/src/processes/infrastructure-failure-budget.ts +146 -0
- package/packages/core/src/processes/phase-failure-budget.ts +52 -0
- package/packages/core/src/processes/process-group-reaper.ts +35 -0
- package/packages/core/src/processes/process-group.ts +65 -0
- package/packages/core/src/processes/publication-loss.ts +62 -0
- package/packages/core/src/processes/publication-state.ts +93 -0
- package/packages/core/src/processes/sandbox-state.ts +101 -0
- package/packages/core/src/registry.ts +24 -0
- package/packages/core/src/sessions/reducer.ts +185 -0
- package/packages/core/src/skills.ts +7 -0
- package/packages/core/src/spec-standard.ts +47 -0
- package/packages/core/src/store/adapter.ts +80 -0
- package/packages/core/src/store/build-scope.ts +272 -0
- package/packages/core/src/store/contract.ts +2581 -0
- package/packages/core/src/store/local/blobs.ts +38 -0
- package/packages/core/src/store/local/schema.ts +180 -0
- package/packages/core/src/store/local/store.ts +1713 -0
- package/packages/core/src/store/memory.ts +1138 -0
- package/packages/core/src/store/phase-session.ts +425 -0
- package/packages/core/src/store/remote/client.ts +780 -0
- package/packages/core/src/store/remote/index.ts +12 -0
- package/packages/core/src/store/remote/protocol.ts +348 -0
- package/packages/core/src/store/remote/server.ts +923 -0
- package/packages/core/src/store/remote/token.ts +137 -0
- package/packages/core/src/store/remote/version.ts +10 -0
- package/packages/core/src/store/retention.ts +91 -0
- package/packages/core/src/store/session-handle.ts +265 -0
- package/packages/core/src/store/streams/assemble.ts +79 -0
- package/packages/core/src/store/streams/lock.ts +42 -0
- package/packages/core/src/store/streams/session-writer.ts +306 -0
- package/packages/core/src/store/streams/types.ts +171 -0
- package/packages/core/src/store/streams/wait.ts +127 -0
- package/packages/core/src/store/subscribe.ts +105 -0
- package/packages/core/src/store/types.ts +466 -0
- package/packages/core/src/testing/fixed.ts +44 -0
- package/packages/core/src/testing/store-failures.ts +32 -0
- package/packages/core/src/zod-issues.ts +91 -0
- package/skills/code-review/SKILL.md +100 -0
- package/skills/finalize/SKILL.md +44 -0
- package/skills/guide/SKILL.md +1781 -0
- package/skills/guide/references/operator-api.md +164 -0
- package/skills/guide/references/plugin-authoring.md +354 -0
- package/skills/guide/references/remote-store-protocol.md +972 -0
- package/skills/guide/references/remote-ticket-protocol.md +79 -0
- package/skills/guide/references/setup.md +289 -0
- package/skills/guide/references/spec-standard.md +63 -0
- package/skills/guide/references/ticket-dependencies.md +59 -0
- package/skills/harvest/SKILL.md +66 -0
- package/skills/harvest-review/SKILL.md +63 -0
- package/skills/implement/SKILL.md +82 -0
- package/skills/plan/SKILL.md +123 -0
- package/skills/plan-review/SKILL.md +121 -0
- package/skills/reconcile/SKILL.md +65 -0
- package/skills/spec/SKILL.md +132 -0
- package/skills/tickets/SKILL.md +139 -0
- package/templates/autobuild.toml +36 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import { stat, readFile, realpath } from 'node:fs/promises'
|
|
2
|
+
import { join, resolve, sep } from 'node:path'
|
|
3
|
+
import semver from 'semver'
|
|
4
|
+
import { PLUGIN_API_VERSION } from '../plugins/manifest'
|
|
5
|
+
import { defaultDistRoot } from './init'
|
|
6
|
+
|
|
7
|
+
export interface DistributionIdentity {
|
|
8
|
+
root: string
|
|
9
|
+
packageName: string
|
|
10
|
+
version: string
|
|
11
|
+
/** Bun records the resolved forge commit in a distribution-local `.bun-tag`. */
|
|
12
|
+
commit?: string
|
|
13
|
+
bunTag?: string
|
|
14
|
+
sourceCheckout: boolean
|
|
15
|
+
binaryPath: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type InstallScope = 'local' | 'global'
|
|
19
|
+
|
|
20
|
+
/** Where the owning package manager obtained the distribution: a `github:`
|
|
21
|
+
* forge dependency, or a version range satisfied from the npm registry. */
|
|
22
|
+
export type InstallChannel = 'github' | 'npm'
|
|
23
|
+
|
|
24
|
+
interface ManagedInstallationBase extends DistributionIdentity {
|
|
25
|
+
sourceCheckout: false
|
|
26
|
+
ownerRoot: string
|
|
27
|
+
ownerManifest: string
|
|
28
|
+
ownerLock: string
|
|
29
|
+
/** The owning manifest's direct dependency value, byte for byte. */
|
|
30
|
+
dependency: string
|
|
31
|
+
scope: InstallScope
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface BunForgeInstallation extends ManagedInstallationBase {
|
|
35
|
+
channel: 'github'
|
|
36
|
+
owner: string
|
|
37
|
+
repository: string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface NpmRegistryInstallation extends ManagedInstallationBase {
|
|
41
|
+
channel: 'npm'
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type ManagedInstallation = BunForgeInstallation | NpmRegistryInstallation
|
|
45
|
+
|
|
46
|
+
export type InstallationInspection =
|
|
47
|
+
| { kind: 'source'; identity: DistributionIdentity; reason: string }
|
|
48
|
+
| { kind: 'bun-forge'; installation: BunForgeInstallation }
|
|
49
|
+
| { kind: 'npm-registry'; installation: NpmRegistryInstallation }
|
|
50
|
+
| { kind: 'unknown'; identity: DistributionIdentity; reason: string }
|
|
51
|
+
|
|
52
|
+
async function exists(path: string): Promise<boolean> {
|
|
53
|
+
try {
|
|
54
|
+
await stat(path)
|
|
55
|
+
return true
|
|
56
|
+
} catch (error) {
|
|
57
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return false
|
|
58
|
+
throw error
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function object(value: unknown): Record<string, unknown> | undefined {
|
|
63
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
64
|
+
? (value as Record<string, unknown>)
|
|
65
|
+
: undefined
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function exactVersion(value: unknown, path: string): string {
|
|
69
|
+
if (typeof value !== 'string' || semver.valid(value) !== value) {
|
|
70
|
+
throw new Error(`${path} has an invalid package version`)
|
|
71
|
+
}
|
|
72
|
+
return value
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function binaryRelativePath(manifest: Record<string, unknown>, path: string): string {
|
|
76
|
+
const bin = manifest.bin
|
|
77
|
+
if (typeof bin === 'string' && bin !== '') return bin
|
|
78
|
+
const entries = object(bin)
|
|
79
|
+
const ab = entries?.ab
|
|
80
|
+
if (typeof ab !== 'string' || ab === '') {
|
|
81
|
+
throw new Error(`${path} does not declare the ab binary`)
|
|
82
|
+
}
|
|
83
|
+
return ab
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Read only distribution-local metadata. This is the complete `ab --version`
|
|
87
|
+
* path: no cwd, project configuration, package-manager command, or network. */
|
|
88
|
+
export async function readDistributionIdentity(
|
|
89
|
+
root = defaultDistRoot(),
|
|
90
|
+
): Promise<DistributionIdentity> {
|
|
91
|
+
const distributionRoot = resolve(root)
|
|
92
|
+
const packagePath = join(distributionRoot, 'package.json')
|
|
93
|
+
let manifest: Record<string, unknown>
|
|
94
|
+
try {
|
|
95
|
+
manifest = object(JSON.parse(await readFile(packagePath, 'utf8'))) ?? {}
|
|
96
|
+
} catch (error) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
`cannot read installed Autobuild metadata at ${packagePath}: ${
|
|
99
|
+
error instanceof Error ? error.message : String(error)
|
|
100
|
+
}`,
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
if (typeof manifest.name !== 'string' || manifest.name === '') {
|
|
104
|
+
throw new Error(`${packagePath} has no package name`)
|
|
105
|
+
}
|
|
106
|
+
const binary = binaryRelativePath(manifest, packagePath)
|
|
107
|
+
const sourceCheckout = await exists(join(distributionRoot, '.git'))
|
|
108
|
+
let bunTag: string | undefined
|
|
109
|
+
let commit: string | undefined
|
|
110
|
+
try {
|
|
111
|
+
bunTag = (await readFile(join(distributionRoot, '.bun-tag'), 'utf8')).trim()
|
|
112
|
+
const match = /-([0-9a-f]{7,40})$/i.exec(bunTag)
|
|
113
|
+
if (match?.[1] !== undefined) commit = match[1]
|
|
114
|
+
} catch (error) {
|
|
115
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
root: distributionRoot,
|
|
119
|
+
packageName: manifest.name,
|
|
120
|
+
version: exactVersion(manifest.version, packagePath),
|
|
121
|
+
...(commit === undefined ? {} : { commit }),
|
|
122
|
+
...(bunTag === undefined ? {} : { bunTag }),
|
|
123
|
+
sourceCheckout,
|
|
124
|
+
binaryPath: resolve(distributionRoot, binary),
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function formatInstalledVersion(identity: DistributionIdentity): string {
|
|
129
|
+
return [
|
|
130
|
+
`autobuild ${identity.version}${identity.commit === undefined ? '' : ` (commit ${identity.commit})`}`,
|
|
131
|
+
`plugin API ${PLUGIN_API_VERSION}`,
|
|
132
|
+
].join('\n')
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function ownerRootForPackage(identity: DistributionIdentity): string | undefined {
|
|
136
|
+
const packageSegments = identity.packageName.split('/')
|
|
137
|
+
if (packageSegments.some((segment) => segment === '' || segment === '.' || segment === '..')) {
|
|
138
|
+
return undefined
|
|
139
|
+
}
|
|
140
|
+
const suffix = [sep, 'node_modules', sep, packageSegments.join(sep)].join('')
|
|
141
|
+
if (!identity.root.endsWith(suffix)) return undefined
|
|
142
|
+
const owner = identity.root.slice(0, -suffix.length)
|
|
143
|
+
return owner === '' ? sep : owner
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function githubDependency(value: unknown): { owner: string; repository: string } | undefined {
|
|
147
|
+
if (typeof value !== 'string') return undefined
|
|
148
|
+
const match = /^github:([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)(?:#([^\s]+))?$/.exec(value)
|
|
149
|
+
if (match?.[1] === undefined || match[2] === undefined) return undefined
|
|
150
|
+
return { owner: match[1], repository: match[2] }
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** A registry dependency is a semver range (what `bun add <pkg>@<version>`
|
|
154
|
+
* writes) that the installed version satisfies. Aliases, tags, URLs, and
|
|
155
|
+
* `file:`/`link:` specifiers are not registry installs. */
|
|
156
|
+
function registryDependency(value: unknown, installedVersion: string): boolean {
|
|
157
|
+
if (typeof value !== 'string' || value.trim() === '') return false
|
|
158
|
+
if (semver.validRange(value) === null) return false
|
|
159
|
+
return semver.satisfies(installedVersion, value)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function parseJson(path: string): Promise<Record<string, unknown>> {
|
|
163
|
+
try {
|
|
164
|
+
return object(JSON.parse(await readFile(path, 'utf8'))) ?? {}
|
|
165
|
+
} catch (error) {
|
|
166
|
+
throw new Error(
|
|
167
|
+
`${path} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function parseJsonc(path: string): Promise<Record<string, unknown>> {
|
|
173
|
+
try {
|
|
174
|
+
const value = Bun.JSONC.parse(await readFile(path, 'utf8'))
|
|
175
|
+
return object(value) ?? {}
|
|
176
|
+
} catch (error) {
|
|
177
|
+
throw new Error(
|
|
178
|
+
`${path} is not valid JSONC: ${error instanceof Error ? error.message : String(error)}`,
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function lockDependency(lock: Record<string, unknown>, packageName: string): unknown {
|
|
184
|
+
const workspace = object(object(lock.workspaces)?.[''])
|
|
185
|
+
return object(workspace?.dependencies)?.[packageName]
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function lockPackageRecord(
|
|
189
|
+
lock: Record<string, unknown>,
|
|
190
|
+
packageName: string,
|
|
191
|
+
): unknown[] | undefined {
|
|
192
|
+
const value = object(lock.packages)?.[packageName]
|
|
193
|
+
return Array.isArray(value) ? value : undefined
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function samePath(left: string, right: string): Promise<boolean> {
|
|
197
|
+
try {
|
|
198
|
+
return (await realpath(left)) === (await realpath(right))
|
|
199
|
+
} catch (error) {
|
|
200
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return false
|
|
201
|
+
throw error
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Validate Bun's direct install records before permitting mutation. A
|
|
206
|
+
* `github:` dependency is a forge install whose repository comes from the
|
|
207
|
+
* owning manifest/lock, never by splitting the ambiguous hyphen-delimited
|
|
208
|
+
* `.bun-tag`. A semver-range dependency satisfied by the installed version,
|
|
209
|
+
* whose lock record resolves to that exact registry version, is an npm
|
|
210
|
+
* registry install. Anything else is a named refusal. */
|
|
211
|
+
export async function inspectInstallation(options: {
|
|
212
|
+
distRoot?: string
|
|
213
|
+
globalBin: string
|
|
214
|
+
}): Promise<InstallationInspection> {
|
|
215
|
+
const identity = await readDistributionIdentity(options.distRoot)
|
|
216
|
+
if (identity.sourceCheckout) {
|
|
217
|
+
return {
|
|
218
|
+
kind: 'source',
|
|
219
|
+
identity,
|
|
220
|
+
reason: 'running from a source checkout (.git is present)',
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const ownerRoot = ownerRootForPackage(identity)
|
|
225
|
+
if (ownerRoot === undefined) {
|
|
226
|
+
return {
|
|
227
|
+
kind: 'unknown',
|
|
228
|
+
identity,
|
|
229
|
+
reason: 'the distribution is not a direct Bun node_modules package',
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
const ownerManifest = join(ownerRoot, 'package.json')
|
|
233
|
+
const ownerLock = join(ownerRoot, 'bun.lock')
|
|
234
|
+
|
|
235
|
+
try {
|
|
236
|
+
const manifest = await parseJson(ownerManifest)
|
|
237
|
+
const lock = await parseJsonc(ownerLock)
|
|
238
|
+
const dependency = object(manifest.dependencies)?.[identity.packageName]
|
|
239
|
+
const repository = githubDependency(dependency)
|
|
240
|
+
const registry = repository === undefined && registryDependency(dependency, identity.version)
|
|
241
|
+
if ((repository === undefined && !registry) || typeof dependency !== 'string') {
|
|
242
|
+
return {
|
|
243
|
+
kind: 'unknown',
|
|
244
|
+
identity,
|
|
245
|
+
reason: `${ownerManifest} does not declare ${identity.packageName} as a direct github: dependency or an npm registry version range satisfied by ${identity.version}`,
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (lockDependency(lock, identity.packageName) !== dependency) {
|
|
249
|
+
return {
|
|
250
|
+
kind: 'unknown',
|
|
251
|
+
identity,
|
|
252
|
+
reason: `${ownerLock} does not agree with the direct dependency in ${ownerManifest}`,
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
const scope: InstallScope = (await samePath(join(options.globalBin, 'ab'), identity.binaryPath))
|
|
256
|
+
? 'global'
|
|
257
|
+
: 'local'
|
|
258
|
+
if (repository === undefined) {
|
|
259
|
+
// Registry provenance: Bun records `<name>@<version>` with an empty
|
|
260
|
+
// registry marker; a forge, tarball, or workspace resolution differs.
|
|
261
|
+
const record = lockPackageRecord(lock, identity.packageName)
|
|
262
|
+
if (
|
|
263
|
+
record === undefined ||
|
|
264
|
+
record[0] !== `${identity.packageName}@${identity.version}` ||
|
|
265
|
+
record[1] !== ''
|
|
266
|
+
) {
|
|
267
|
+
return {
|
|
268
|
+
kind: 'unknown',
|
|
269
|
+
identity,
|
|
270
|
+
reason: `${ownerLock} does not resolve ${identity.packageName} to registry version ${identity.version}`,
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
kind: 'npm-registry',
|
|
275
|
+
installation: {
|
|
276
|
+
...identity,
|
|
277
|
+
sourceCheckout: false,
|
|
278
|
+
channel: 'npm',
|
|
279
|
+
ownerRoot,
|
|
280
|
+
ownerManifest,
|
|
281
|
+
ownerLock,
|
|
282
|
+
dependency,
|
|
283
|
+
scope,
|
|
284
|
+
},
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
const expectedTagPrefix = `${repository.owner}-${repository.repository}-`
|
|
288
|
+
if (
|
|
289
|
+
identity.bunTag === undefined ||
|
|
290
|
+
!identity.bunTag.startsWith(expectedTagPrefix) ||
|
|
291
|
+
identity.commit === undefined
|
|
292
|
+
) {
|
|
293
|
+
return {
|
|
294
|
+
kind: 'unknown',
|
|
295
|
+
identity,
|
|
296
|
+
reason: 'the installed .bun-tag is malformed or contradicts the owning dependency',
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
const record = lockPackageRecord(lock, identity.packageName)
|
|
300
|
+
const resolvedPackage =
|
|
301
|
+
`${identity.packageName}@github:${repository.owner}/${repository.repository}` +
|
|
302
|
+
`#${identity.commit}`
|
|
303
|
+
if (record === undefined || record[0] !== resolvedPackage || record[2] !== identity.bunTag) {
|
|
304
|
+
return {
|
|
305
|
+
kind: 'unknown',
|
|
306
|
+
identity,
|
|
307
|
+
reason: `${ownerLock} does not contain matching Bun forge provenance`,
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
kind: 'bun-forge',
|
|
313
|
+
installation: {
|
|
314
|
+
...identity,
|
|
315
|
+
sourceCheckout: false,
|
|
316
|
+
channel: 'github',
|
|
317
|
+
ownerRoot,
|
|
318
|
+
ownerManifest,
|
|
319
|
+
ownerLock,
|
|
320
|
+
owner: repository.owner,
|
|
321
|
+
repository: repository.repository,
|
|
322
|
+
dependency,
|
|
323
|
+
scope,
|
|
324
|
+
},
|
|
325
|
+
}
|
|
326
|
+
} catch (error) {
|
|
327
|
+
return {
|
|
328
|
+
kind: 'unknown',
|
|
329
|
+
identity,
|
|
330
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import type { TerminalInputEvent } from './terminal'
|
|
2
|
+
import {
|
|
3
|
+
KITTY_KEYBOARD_FLAGS,
|
|
4
|
+
KITTY_KEYBOARD_MODE,
|
|
5
|
+
type TerminalModeController,
|
|
6
|
+
} from './terminal-restore'
|
|
7
|
+
|
|
8
|
+
/** Progressive enhancement flags requested from Kitty-compatible terminals. */
|
|
9
|
+
export { KITTY_KEYBOARD_FLAGS } from './terminal-restore'
|
|
10
|
+
/** Ask for the current progressive keyboard enhancement flags. */
|
|
11
|
+
export const QUERY_KEYBOARD_FLAGS = '\x1b[?u'
|
|
12
|
+
/**
|
|
13
|
+
* Push disambiguated escape codes (1), alternate keys (4), all keys as escape
|
|
14
|
+
* codes (8), and associated text (16). Alternate keys are essential: without
|
|
15
|
+
* them Cyrillic Ctrl-C reports key 1089 and the decoder cannot recognize it.
|
|
16
|
+
*/
|
|
17
|
+
export const PUSH_KEYBOARD_FLAGS = KITTY_KEYBOARD_MODE.enter
|
|
18
|
+
/** Pop exactly the keyboard mode frame pushed by this process. */
|
|
19
|
+
export const POP_KEYBOARD_FLAGS = KITTY_KEYBOARD_MODE.restore
|
|
20
|
+
/**
|
|
21
|
+
* Primary Device Attributes is not a keyboard command. It follows the
|
|
22
|
+
* verification query because its guaranteed, ordered reply lets verification
|
|
23
|
+
* fail deterministically when no keyboard-flags reply arrives.
|
|
24
|
+
*/
|
|
25
|
+
export const DEVICE_ATTRIBUTES_QUERY = '\x1b[c'
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Kitty modifiers are `1 + bitfield`; caps and num lock are reported on every
|
|
29
|
+
* key in report-all-keys mode and must not turn an ordinary key into a chord.
|
|
30
|
+
*/
|
|
31
|
+
export const MODIFIER_LOCK_MASK = 0b1100_0000
|
|
32
|
+
/** Shift and lock bits may still produce text; ctrl/alt/super/hyper/meta may not. */
|
|
33
|
+
const MODIFIER_TEXT_MASK = 0b1100_0001
|
|
34
|
+
/** No decoded numeric parameter can exceed Unicode's seven-digit maximum scalar. */
|
|
35
|
+
export const MAX_CSI_PARAM_DIGITS = 7
|
|
36
|
+
/**
|
|
37
|
+
* Three 7-digit key fields, two colons, a semicolon, 3-digit modifier, `:1`,
|
|
38
|
+
* `ESC[` and `u` fit in 32 bytes. Forty therefore covers every Ctrl-C report
|
|
39
|
+
* while keeping the parallel, non-consuming interrupt watch deliberately tight.
|
|
40
|
+
*/
|
|
41
|
+
export const INTERRUPT_WATCH_MAX = 40
|
|
42
|
+
|
|
43
|
+
/** ECMA-48 parameter/intermediate bytes that may extend a CSI run. */
|
|
44
|
+
export function isCsiParameterByte(ch: string): boolean {
|
|
45
|
+
if (ch.length !== 1) return false
|
|
46
|
+
const code = ch.charCodeAt(0)
|
|
47
|
+
return code >= 0x20 && code <= 0x3f
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* CSI sequences end at a byte in 0x40-0x7e. This includes every ASCII letter,
|
|
52
|
+
* so one ordinary letter always terminates an accumulating or draining run.
|
|
53
|
+
*/
|
|
54
|
+
export function endsCsi(sequence: string): boolean {
|
|
55
|
+
if (sequence.length <= 2) return false
|
|
56
|
+
const code = sequence.charCodeAt(sequence.length - 1)
|
|
57
|
+
return code >= 0x40 && code <= 0x7e
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Decode a `1 + bitfield` modifier parameter into submit-versus-newline. */
|
|
61
|
+
function modifiedReturn(mod: number | undefined): TerminalInputEvent {
|
|
62
|
+
if (mod === undefined) return { type: 'enter' }
|
|
63
|
+
const bits = Math.max(0, mod - 1)
|
|
64
|
+
return (bits & ~MODIFIER_LOCK_MASK) === 0 ? { type: 'enter' } : { type: 'newline' }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function parseParam(value: string | undefined): number | undefined {
|
|
68
|
+
if (value === undefined || value === '') return undefined
|
|
69
|
+
const parsed = Number.parseInt(value, 10)
|
|
70
|
+
return Number.isFinite(parsed) ? parsed : undefined
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function isTextCodePoint(code: number | undefined): code is number {
|
|
74
|
+
return (
|
|
75
|
+
code !== undefined &&
|
|
76
|
+
code >= 0x20 &&
|
|
77
|
+
code <= 0x10ffff &&
|
|
78
|
+
!(code >= 0x7f && code <= 0x9f) &&
|
|
79
|
+
!(code >= 0xd800 && code <= 0xdfff)
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Kitty reserves U+E000-U+F8FF in the key-code field for functional keys. */
|
|
84
|
+
function isFunctionalKeyCode(code: number): boolean {
|
|
85
|
+
return code >= 57344 && code <= 63743
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export type CsiReport =
|
|
89
|
+
| { kind: 'input'; event: TerminalInputEvent }
|
|
90
|
+
| { kind: 'keyboard-flags'; flags: number }
|
|
91
|
+
| { kind: 'device-attributes' }
|
|
92
|
+
| { kind: 'ignored' }
|
|
93
|
+
|
|
94
|
+
const ignored = (): CsiReport => ({ kind: 'ignored' })
|
|
95
|
+
const input = (event: TerminalInputEvent): CsiReport => ({ kind: 'input', event })
|
|
96
|
+
|
|
97
|
+
/** Decode one complete CSI sequence, whether legacy, Kitty CSI-u, or a reply. */
|
|
98
|
+
export function decodeCsi(sequence: string): CsiReport {
|
|
99
|
+
if (!sequence.startsWith('\x1b[') || !endsCsi(sequence)) return ignored()
|
|
100
|
+
const final = sequence.at(-1)
|
|
101
|
+
const body = sequence.slice(2, -1)
|
|
102
|
+
|
|
103
|
+
if (final === 'u' && body.startsWith('?')) {
|
|
104
|
+
return { kind: 'keyboard-flags', flags: parseParam(body.slice(1)) ?? 0 }
|
|
105
|
+
}
|
|
106
|
+
if (final === 'c' && body.startsWith('?')) return { kind: 'device-attributes' }
|
|
107
|
+
|
|
108
|
+
const params = body.split(';')
|
|
109
|
+
if (final === 'u') {
|
|
110
|
+
const [primaryRaw, shiftedRaw, baseRaw] = (params[0] ?? '').split(':')
|
|
111
|
+
const primary = parseParam(primaryRaw)
|
|
112
|
+
const shifted = parseParam(shiftedRaw)
|
|
113
|
+
const base = parseParam(baseRaw)
|
|
114
|
+
const [modRaw, eventRaw] = (params[1] ?? '').split(':')
|
|
115
|
+
const mod = parseParam(modRaw)
|
|
116
|
+
const event = parseParam(eventRaw) ?? 1
|
|
117
|
+
if (event !== 1 && event !== 2) return ignored()
|
|
118
|
+
|
|
119
|
+
if (primary === 13) return input(modifiedReturn(mod))
|
|
120
|
+
if (primary === 27) return input({ type: 'escape' })
|
|
121
|
+
if (primary === 127 || primary === 8) return input({ type: 'backspace' })
|
|
122
|
+
if (primary === 9) return ignored()
|
|
123
|
+
|
|
124
|
+
const bits = Math.max(0, (mod ?? 1) - 1)
|
|
125
|
+
if ((bits & 0b100) !== 0) {
|
|
126
|
+
const chordKeys = base === undefined ? [primary] : [primary, base]
|
|
127
|
+
// The Dvorak physical-QWERTY-C collision contains both 106 and 99. The
|
|
128
|
+
// produced `j` wins: a newline is recoverable while an interrupt is not.
|
|
129
|
+
if (chordKeys.includes(106)) return input({ type: 'newline' })
|
|
130
|
+
if (chordKeys.includes(99)) return input({ type: 'interrupt' })
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if ((bits & ~MODIFIER_TEXT_MASK) !== 0) return ignored()
|
|
134
|
+
|
|
135
|
+
if (params[2] !== undefined) {
|
|
136
|
+
const codes = params[2].split(':').map(parseParam)
|
|
137
|
+
if (codes.length === 0 || !codes.every(isTextCodePoint)) return ignored()
|
|
138
|
+
// Do not spread: valid associated text is unbounded and a large argument
|
|
139
|
+
// list eventually throws. Folding remains safe for long IME commits.
|
|
140
|
+
let text = ''
|
|
141
|
+
for (const code of codes) text += String.fromCodePoint(code)
|
|
142
|
+
return input({ type: 'text', text })
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const shiftedOnly = (bits & ~MODIFIER_LOCK_MASK) === 0b1
|
|
146
|
+
// The base-layout subfield names a physical position, not text in the
|
|
147
|
+
// operator's layout, and is intentionally never a printable fallback.
|
|
148
|
+
const code = shiftedOnly && shifted !== undefined ? shifted : primary
|
|
149
|
+
if (!isTextCodePoint(code) || isFunctionalKeyCode(code)) return ignored()
|
|
150
|
+
const text = String.fromCodePoint(code)
|
|
151
|
+
// Best effort for the bounded pre-verification window. Associated text is
|
|
152
|
+
// required after verification because this is wrong for shifted layouts.
|
|
153
|
+
return input({
|
|
154
|
+
type: 'text',
|
|
155
|
+
text: shiftedOnly && shifted === undefined ? text.toUpperCase() : text,
|
|
156
|
+
})
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (final === '~') {
|
|
160
|
+
if (params[0] === '27' && parseParam(params[2]) === 13) {
|
|
161
|
+
return input(modifiedReturn(parseParam(params[1])))
|
|
162
|
+
}
|
|
163
|
+
if (params[0] === '1' || params[0] === '7') return input({ type: 'home' })
|
|
164
|
+
if (params[0] === '4' || params[0] === '8') return input({ type: 'end' })
|
|
165
|
+
return ignored()
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
type Motion = Exclude<TerminalInputEvent['type'], 'text' | 'paste'>
|
|
169
|
+
const motions: Record<string, Motion | undefined> = {
|
|
170
|
+
A: 'up',
|
|
171
|
+
B: 'down',
|
|
172
|
+
C: 'right',
|
|
173
|
+
D: 'left',
|
|
174
|
+
H: 'home',
|
|
175
|
+
F: 'end',
|
|
176
|
+
}
|
|
177
|
+
const motion = final === undefined ? undefined : motions[final]
|
|
178
|
+
return motion === undefined ? ignored() : input({ type: motion })
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function isInterruptSequence(sequence: string): boolean {
|
|
182
|
+
const report = decodeCsi(sequence)
|
|
183
|
+
return report.kind === 'input' && report.event.type === 'interrupt'
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
type KeyboardPhase =
|
|
187
|
+
| 'idle'
|
|
188
|
+
| 'probing'
|
|
189
|
+
| 'supported'
|
|
190
|
+
| 'verifying'
|
|
191
|
+
| 'active'
|
|
192
|
+
| 'declined'
|
|
193
|
+
| 'finished'
|
|
194
|
+
|
|
195
|
+
export interface KeyboardProtocol {
|
|
196
|
+
query(): void
|
|
197
|
+
reported(flags: number): void
|
|
198
|
+
deviceAttributes(): void
|
|
199
|
+
screenEntered(): void
|
|
200
|
+
screenLeaving(): void
|
|
201
|
+
readonly pushed: boolean
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Negotiate and balance the per-screen Kitty keyboard mode stack. */
|
|
205
|
+
export function createKeyboardProtocol(
|
|
206
|
+
write: (chunk: string) => void,
|
|
207
|
+
modes: TerminalModeController,
|
|
208
|
+
): KeyboardProtocol {
|
|
209
|
+
let phase: KeyboardPhase = 'idle'
|
|
210
|
+
let onScreen = false
|
|
211
|
+
|
|
212
|
+
const push = (): void => {
|
|
213
|
+
if (phase !== 'supported' || !onScreen) return
|
|
214
|
+
phase = 'verifying'
|
|
215
|
+
// The ledger arms before the push write. Queries do not alter terminal
|
|
216
|
+
// state and therefore remain ordinary output after the declared enter.
|
|
217
|
+
modes.enter(KITTY_KEYBOARD_MODE)
|
|
218
|
+
write(QUERY_KEYBOARD_FLAGS + DEVICE_ATTRIBUTES_QUERY)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
query(): void {
|
|
223
|
+
if (phase !== 'idle') return
|
|
224
|
+
write(QUERY_KEYBOARD_FLAGS)
|
|
225
|
+
phase = 'probing'
|
|
226
|
+
},
|
|
227
|
+
reported(flags: number): void {
|
|
228
|
+
if (phase === 'probing') {
|
|
229
|
+
phase = 'supported'
|
|
230
|
+
push()
|
|
231
|
+
return
|
|
232
|
+
}
|
|
233
|
+
if (phase !== 'verifying') return
|
|
234
|
+
if ((flags & KITTY_KEYBOARD_FLAGS) === KITTY_KEYBOARD_FLAGS) {
|
|
235
|
+
phase = 'active'
|
|
236
|
+
} else {
|
|
237
|
+
modes.leave(KITTY_KEYBOARD_MODE)
|
|
238
|
+
phase = 'declined'
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
deviceAttributes(): void {
|
|
242
|
+
if (phase !== 'verifying') return
|
|
243
|
+
modes.leave(KITTY_KEYBOARD_MODE)
|
|
244
|
+
phase = 'declined'
|
|
245
|
+
},
|
|
246
|
+
screenEntered(): void {
|
|
247
|
+
if (phase === 'finished') return
|
|
248
|
+
onScreen = true
|
|
249
|
+
push()
|
|
250
|
+
},
|
|
251
|
+
screenLeaving(): void {
|
|
252
|
+
if (phase === 'finished') return
|
|
253
|
+
if (phase === 'verifying' || phase === 'active') modes.leave(KITTY_KEYBOARD_MODE)
|
|
254
|
+
phase = 'finished'
|
|
255
|
+
onScreen = false
|
|
256
|
+
},
|
|
257
|
+
get pushed(): boolean {
|
|
258
|
+
return phase === 'verifying' || phase === 'active'
|
|
259
|
+
},
|
|
260
|
+
}
|
|
261
|
+
}
|