@pellux/goodvibes-tui 2.0.7 → 2.0.9
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/CHANGELOG.md +36 -0
- package/README.md +1 -1
- package/docs/foundation-artifacts/operator-contract.json +468 -286
- package/package.json +3 -3
- package/src/input/commands/profile-types.ts +3 -1
- package/src/input/handler-interactions.ts +39 -0
- package/src/input/handler-types.ts +7 -0
- package/src/input/handler.ts +10 -21
- package/src/input/settings-modal-types.ts +14 -1
- package/src/renderer/settings-modal-helpers.ts +6 -0
- package/src/runtime/runtime-services-types.ts +2 -0
- package/src/runtime/services.ts +1 -0
- package/src/verification/verification-ledger.ts +44 -0
- package/src/version.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pellux/goodvibes-tui",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.9",
|
|
4
4
|
"description": "Terminal-native GoodVibes client for coding, operations, knowledge and channel workflows — a pure client of the GoodVibes daemon.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/main.ts",
|
|
@@ -95,8 +95,8 @@
|
|
|
95
95
|
"@anthropic-ai/bedrock-sdk": "^0.29.1",
|
|
96
96
|
"@anthropic-ai/sdk": "^0.92.0",
|
|
97
97
|
"@ast-grep/napi": "^0.42.0",
|
|
98
|
-
"@pellux/goodvibes-sdk": "2.0.
|
|
99
|
-
"@pellux/goodvibes-terminal-shell": "2.0.
|
|
98
|
+
"@pellux/goodvibes-sdk": "2.0.9",
|
|
99
|
+
"@pellux/goodvibes-terminal-shell": "2.0.9",
|
|
100
100
|
"bash-language-server": "^5.6.0",
|
|
101
101
|
"fuse.js": "^7.1.0",
|
|
102
102
|
"@pellux/goodvibes-daemon": "1.28.7",
|
|
@@ -354,7 +354,9 @@ export function toProfileProvenanceReport(value: unknown): Checked<ProfileProven
|
|
|
354
354
|
if (provenance === MALFORMED) return MALFORMED;
|
|
355
355
|
const superseded = checkedArray(record.superseded, toSuperseded);
|
|
356
356
|
if (superseded === MALFORMED) return MALFORMED;
|
|
357
|
-
|
|
357
|
+
// Platform runtime 2.0.8: the contract declares provenance nullable and
|
|
358
|
+
// always present — null IS the answer for a hand-edited field.
|
|
359
|
+
return { fieldId, present, handEdited, provenance: provenance ?? null, superseded };
|
|
358
360
|
}
|
|
359
361
|
|
|
360
362
|
function toChange(value: unknown): Checked<ProfileChangeView> {
|
|
@@ -58,6 +58,45 @@ export async function hydrateOnboardingWizardFromRuntimeForHandler(handler: Inpu
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* handlePasteForHandler - Shared paste path for Ctrl+V and middle-click.
|
|
63
|
+
*
|
|
64
|
+
* Tries the clipboard's image first, then its text. The clipboard itself comes
|
|
65
|
+
* from `handler.clipboardSource`, so tests can hand in a clipboard without
|
|
66
|
+
* touching the real one.
|
|
67
|
+
*
|
|
68
|
+
* The prompt is edited on the HANDLER here, not on the shortcut route state
|
|
69
|
+
* that dispatched the key. feedInputTokens snapshots the prompt before
|
|
70
|
+
* dispatching and would write that snapshot back over this edit, erasing the
|
|
71
|
+
* paste — it does not, because it only restores a field the action left
|
|
72
|
+
* untouched (see the promptBefore guard in handler-feed.ts). That guard is the
|
|
73
|
+
* only reason Ctrl+V works, so it is pinned by test.
|
|
74
|
+
*/
|
|
75
|
+
export function handlePasteForHandler(handler: InputHandler): ReturnType<typeof handleClipboardPaste> {
|
|
76
|
+
const result = handleClipboardPaste({
|
|
77
|
+
prompt: handler.prompt,
|
|
78
|
+
cursorPos: handler.cursorPos,
|
|
79
|
+
pasteRegistry: handler.pasteRegistry,
|
|
80
|
+
nextPasteId: handler.nextPasteId,
|
|
81
|
+
imageRegistry: handler.imageRegistry,
|
|
82
|
+
nextImageId: handler.nextImageId,
|
|
83
|
+
saveUndoState: () => handler.saveUndoState(),
|
|
84
|
+
ensureInputCursorVisible: () => handler.ensureInputCursorVisible(),
|
|
85
|
+
requestRender: handler.requestRender,
|
|
86
|
+
}, handler.uiServices.environment.shellPaths.workingDirectory, handler.clipboardSource);
|
|
87
|
+
|
|
88
|
+
handler.prompt = result.prompt;
|
|
89
|
+
handler.cursorPos = result.cursorPos;
|
|
90
|
+
handler.nextImageId = result.nextImageId;
|
|
91
|
+
handler.nextPasteId = result.nextPasteId;
|
|
92
|
+
|
|
93
|
+
if (!result.pasted) {
|
|
94
|
+
handler.conversationManager?.log('[Paste: clipboard does not contain supported text or image data]', { fg: '240' });
|
|
95
|
+
handler.requestRender();
|
|
96
|
+
}
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
|
|
61
100
|
export function registerPasteForHandler(handler: InputHandler, content: string): string {
|
|
62
101
|
const result = registerPaste({
|
|
63
102
|
pasteRegistry: handler.pasteRegistry,
|
|
@@ -19,6 +19,7 @@ import type { UiRuntimeServices } from '../runtime/ui-services.ts';
|
|
|
19
19
|
import type { CommandContext } from './command-registry.ts';
|
|
20
20
|
import type { ConversationManager } from '../core/conversation';
|
|
21
21
|
import type { ModelPickerModal, ModelPickerTarget } from './model-picker.ts';
|
|
22
|
+
import type { ClipboardPasteSource } from './handler-content-actions.ts';
|
|
22
23
|
import type { SelectionManager } from '@pellux/goodvibes-terminal-shell';
|
|
23
24
|
import type { InfiniteBuffer } from '@pellux/goodvibes-terminal-shell';
|
|
24
25
|
import type { AutocompleteEngine } from './autocomplete.ts';
|
|
@@ -81,6 +82,11 @@ export interface InputHandlerLike {
|
|
|
81
82
|
nextPasteId: number;
|
|
82
83
|
imageRegistry: Map<string, { data: string; mediaType: string }>;
|
|
83
84
|
nextImageId: number;
|
|
85
|
+
/**
|
|
86
|
+
* The clipboard this composer pastes from. Swappable so tests can supply a
|
|
87
|
+
* clipboard instead of reaching for the machine's real one.
|
|
88
|
+
*/
|
|
89
|
+
clipboardSource: ClipboardPasteSource;
|
|
84
90
|
|
|
85
91
|
// ── Timing ───────────────────────────────────────────────────────────────
|
|
86
92
|
lastCopyTime: number;
|
|
@@ -124,6 +130,7 @@ export interface InputHandlerLike {
|
|
|
124
130
|
// ── Methods: modal lifecycle ──────────────────────────────────────────────
|
|
125
131
|
modalOpened(name: string): void;
|
|
126
132
|
saveUndoState(): void;
|
|
133
|
+
ensureInputCursorVisible(contentWidth?: number): void;
|
|
127
134
|
|
|
128
135
|
// ── Methods: block actions (dispatched in executeBlockAction) ────────────
|
|
129
136
|
handleBlockCopy(): void;
|
package/src/input/handler.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node
|
|
|
2
2
|
import { dirname } from 'node:path';
|
|
3
3
|
import { InputTokenizer } from '@pellux/goodvibes-sdk/platform/core';
|
|
4
4
|
import { createOAuthLocalListener } from '@pellux/goodvibes-sdk/platform/config';
|
|
5
|
-
import { clearModalStackForHandler, cleanupMarkerRegistryForHandler, executeBlockActionForHandler, expandPromptForHandler, findMarkerAtPosForHandler, getImageAttachmentsForHandler, handleBlockCopyForHandler, handleBlockSaveForHandler, handleBlockToggleForHandler, handleBookmarkForHandler, handleCopyForHandler, handleCtrlCForHandler, handleDiffApplyForHandler, handleEscapeForHandler, hydrateOnboardingWizardFromRuntimeForHandler, modalOpenedForHandler, openOnboardingWizardForHandler, registerPasteForHandler } from './handler-interactions.ts';
|
|
5
|
+
import { clearModalStackForHandler, cleanupMarkerRegistryForHandler, executeBlockActionForHandler, expandPromptForHandler, findMarkerAtPosForHandler, getImageAttachmentsForHandler, handleBlockCopyForHandler, handleBlockSaveForHandler, handleBlockToggleForHandler, handleBookmarkForHandler, handleCopyForHandler, handleCtrlCForHandler, handleDiffApplyForHandler, handleEscapeForHandler, handlePasteForHandler, hydrateOnboardingWizardFromRuntimeForHandler, modalOpenedForHandler, openOnboardingWizardForHandler, registerPasteForHandler } from './handler-interactions.ts';
|
|
6
6
|
import { getViewportBottomLine } from '../renderer/conversation-layout.ts';
|
|
7
7
|
import { clearOnboardingModelPickerCancelStateForHandler, clearOnboardingPendingModelPickerTargetForHandler, completeOpenAiSubscriptionFromListenerForHandler, getOnboardingConfigValueForHandler, getOnboardingRuntimePostureForHandler, handleModelPickerCommitForHandler, handleOnboardingActionForHandler, handleOpenAiSubscriptionFinishForHandler, handleOpenAiSubscriptionStartForHandler, openModelPickerWithTargetForHandler, openProviderModelPickerWithTargetForHandler, refreshOnboardingHydrationForHandler, restartOnboardingExternalServicesIfNeededForHandler, restoreOnboardingModelPickerCancelStateForHandler, saveWizardProgressForHandler, syncRuntimeFromOnboardingRequestForHandler, verifyOnboardingRuntimePostureForHandler, } from './handler-onboarding.ts';
|
|
8
8
|
import type { OnboardingRuntimePosture } from './handler-types.ts';
|
|
@@ -53,12 +53,14 @@ import {
|
|
|
53
53
|
handleBlockToggle,
|
|
54
54
|
handleBookmark,
|
|
55
55
|
handleClipboardPaste,
|
|
56
|
+
type ClipboardPasteSource,
|
|
56
57
|
handleCopy,
|
|
57
58
|
handleCtrlC,
|
|
58
59
|
handleDiffApply,
|
|
59
60
|
mediaTypeFromExt,
|
|
60
61
|
registerPaste,
|
|
61
62
|
} from './handler-content-actions.ts';
|
|
63
|
+
import { pasteFromClipboard, pasteImageFromClipboard } from '../utils/clipboard.ts';
|
|
62
64
|
import {
|
|
63
65
|
handleIndicatorFocusToken,
|
|
64
66
|
handleMouseToken,
|
|
@@ -574,28 +576,15 @@ export class InputHandler implements InputHandlerLike {
|
|
|
574
576
|
* Tries image clipboard first, falls back to text paste.
|
|
575
577
|
*/
|
|
576
578
|
public handlePaste(): ReturnType<typeof handleClipboardPaste> {
|
|
577
|
-
|
|
578
|
-
prompt: this.prompt,
|
|
579
|
-
cursorPos: this.cursorPos,
|
|
580
|
-
pasteRegistry: this.pasteRegistry,
|
|
581
|
-
nextPasteId: this.nextPasteId,
|
|
582
|
-
imageRegistry: this.imageRegistry,
|
|
583
|
-
nextImageId: this.nextImageId,
|
|
584
|
-
saveUndoState: () => this.saveUndoState(),
|
|
585
|
-
ensureInputCursorVisible: () => this.ensureInputCursorVisible(),
|
|
586
|
-
requestRender: this.requestRender,
|
|
587
|
-
}, this.uiServices.environment.shellPaths.workingDirectory);
|
|
588
|
-
this.prompt = result.prompt;
|
|
589
|
-
this.cursorPos = result.cursorPos;
|
|
590
|
-
this.nextImageId = result.nextImageId;
|
|
591
|
-
this.nextPasteId = result.nextPasteId;
|
|
592
|
-
if (!result.pasted) {
|
|
593
|
-
this.conversationManager?.log('[Paste: clipboard does not contain supported text or image data]', { fg: '240' });
|
|
594
|
-
this.requestRender();
|
|
595
|
-
}
|
|
596
|
-
return result;
|
|
579
|
+
return handlePasteForHandler(this);
|
|
597
580
|
}
|
|
598
581
|
|
|
582
|
+
/**
|
|
583
|
+
* The clipboard this composer pastes from. Swappable so tests can supply a
|
|
584
|
+
* clipboard instead of reaching for the machine's real one.
|
|
585
|
+
*/
|
|
586
|
+
public clipboardSource: ClipboardPasteSource = { pasteImageFromClipboard, pasteFromClipboard };
|
|
587
|
+
|
|
599
588
|
/** Content width for wrapping — set by main.ts via setContentWidth(). */
|
|
600
589
|
public contentWidth = 76;
|
|
601
590
|
public panelMouseLayout: PanelMouseLayout | null = null;
|
|
@@ -81,6 +81,16 @@ export type SettingsCategory =
|
|
|
81
81
|
// hostedSessions.* key from the workspace, which is exactly what happened to
|
|
82
82
|
// push.* and cluster.* before them.
|
|
83
83
|
| 'hostedSessions'
|
|
84
|
+
// email.* / calendar.* / google.* — the connector keys the daemon really
|
|
85
|
+
// reads for its Google-backed mail and calendar services (platform runtime
|
|
86
|
+
// 2.0.8 registered them as described schema rows; before that they were
|
|
87
|
+
// cast onto the live config invisibly and answered "Unknown setting").
|
|
88
|
+
// Same mandatory-dual-membership rule as profile and hostedSessions above —
|
|
89
|
+
// without BOTH these entries and the SETTINGS_CATEGORY_GROUPS membership
|
|
90
|
+
// below, every one of the 22 connector keys is dropped from the workspace.
|
|
91
|
+
| 'email'
|
|
92
|
+
| 'calendar'
|
|
93
|
+
| 'google'
|
|
84
94
|
| 'danger';
|
|
85
95
|
|
|
86
96
|
export type SettingsFocusPane = 'categories' | 'settings';
|
|
@@ -96,7 +106,10 @@ export const SETTINGS_CATEGORY_GROUPS: ReadonlyArray<{
|
|
|
96
106
|
// answer "where does this conversation actually run, and who can reach it" —
|
|
97
107
|
// the detach policy is a fact about the daemon holding a session open, not
|
|
98
108
|
// about this terminal's presentation of it.
|
|
99
|
-
|
|
109
|
+
// 'email', 'calendar' and 'google' sit beside 'surfaces': they configure
|
|
110
|
+
// the daemon's connections to the owner's mail and calendar — where those
|
|
111
|
+
// services reach the world, not how this terminal presents anything.
|
|
112
|
+
{ label: 'Surfaces & Cloud', categories: ['surfaces', 'hostedSessions', 'device', 'conversationGate', 'integrations', 'connections', 'email', 'calendar', 'google', 'mcp', 'cloudflare'] },
|
|
100
113
|
// 'occasions' sits beside 'checkin': both are proactive background loops
|
|
101
114
|
// (a cadence, a judgment pass, a conditional channel delivery) rather than
|
|
102
115
|
// facts held about the owner — the data occasions/plans declare lives in
|
|
@@ -115,6 +115,9 @@ export const CATEGORY_INFO: Record<SettingsCategory, string> = {
|
|
|
115
115
|
relay: 'Outbound zero-knowledge relay reachability, for reaching this daemon from outside the LAN without opening an inbound port. Off by default — relay.enabled is the switch. The relay operator sees only ciphertext and connection metadata — self-host your own relay for full control.',
|
|
116
116
|
learning: 'Idle-time memory consolidation: merging duplicate standing memory records and decaying/archiving stale ones. Off by default — nothing runs until enabled.',
|
|
117
117
|
payments: 'Payment card on file for daemon-initiated purchases: card number, expiry, CVV, cardholder name, billing and shipping address, and whether the CVV is stored or requested at purchase time. Card fields are entered through /payments card (concealed input) and are secret-tier — never rendered in plaintext.',
|
|
118
|
+
email: 'The daemon\'s own mailbox connection — IMAP and SMTP hosts, ports and security, the mailbox and drafts folders, and the username. Password fields hold secret-store references, never values; /google connects a Gmail-backed setup in one action.',
|
|
119
|
+
calendar: 'Calendar connections the daemon reads and writes: the Google or Microsoft OAuth client for CalDAV-free access, or a read-only ICS URL. Secret fields hold secret-store references, never values.',
|
|
120
|
+
google: 'The Google OAuth connection record: project id, publishing status, and the refresh-token reference the mail and calendar services share. Established by /google in one action; a dead grant is diagnosed in plain words there.',
|
|
118
121
|
profile: 'What the platform knows about you — your name, how to reach you, where you are, how you like answers written, the people and places you mention — kept as one Markdown file at daemon scope that you can open and edit by hand at any time; your edits win and are never rewritten. These rows decide whether it is loaded at all, whether the assistant records facts it learns from things you say directly to it, whether it tells you in one line each time it does, whether the harmless part (how you are addressed, your city, your timezone, your formatting preferences, your style) is put in the model\'s context so it stops guessing, whether using a private detail such as your address is announced in the reply, whether settings left unset read their value from the profile, and where the file lives. Everything private — your name, contact details, home address, billing and shipping details, quiet hours, and the People, Places, Work and Notes sections — is never put in context in bulk and is reachable only by a named lookup that is disclosed when it happens. Run /profile to read the whole thing, /profile where <field> to see the surface, date and your exact words behind any recorded line, and /profile forget <field> to delete one.',
|
|
119
122
|
};
|
|
120
123
|
|
|
@@ -159,6 +162,9 @@ export const CATEGORY_LABELS: Record<(typeof SETTINGS_CATEGORIES)[number], strin
|
|
|
159
162
|
release: 'Release',
|
|
160
163
|
power: 'Power',
|
|
161
164
|
memory: 'Memory',
|
|
165
|
+
email: 'Email Connection',
|
|
166
|
+
calendar: 'Calendar Connections',
|
|
167
|
+
google: 'Google Connection',
|
|
162
168
|
danger: 'Danger',
|
|
163
169
|
tools: 'Tools',
|
|
164
170
|
network: 'Network',
|
|
@@ -108,6 +108,8 @@ export interface RuntimeServicesOptions {
|
|
|
108
108
|
export interface RuntimeServices {
|
|
109
109
|
readonly workingDirectory: string;
|
|
110
110
|
readonly homeDirectory: string;
|
|
111
|
+
/** The surface segment this runtime's stores live under (platform runtime 2.0.8 requires it — a blank segment resolved to the unscoped control-plane orphan). */
|
|
112
|
+
readonly surfaceRoot: string;
|
|
111
113
|
/** The declare-once session-storage handle every session reader and writer threads through — see session-storage-services.ts. */
|
|
112
114
|
readonly surface: SessionSurface;
|
|
113
115
|
readonly shellPaths: ShellPathService;
|
package/src/runtime/services.ts
CHANGED
|
@@ -559,6 +559,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
559
559
|
const services: RuntimeServices = {
|
|
560
560
|
workingDirectory,
|
|
561
561
|
homeDirectory,
|
|
562
|
+
surfaceRoot: GOODVIBES_TUI_SURFACE_ROOT,
|
|
562
563
|
surface,
|
|
563
564
|
shellPaths,
|
|
564
565
|
workspaceTrustManager,
|
|
@@ -438,12 +438,56 @@ export const PAYMENTS_LOCAL_SETTINGS = [
|
|
|
438
438
|
'payments.notifyChannels',
|
|
439
439
|
] as const;
|
|
440
440
|
|
|
441
|
+
/**
|
|
442
|
+
* Connector settings — the daemon's Google-backed mail/calendar keys plus the
|
|
443
|
+
* connected-host dial switch and the hosted-turn routing switch, declared as
|
|
444
|
+
* described schema rows by platform runtime 2.0.8 (before that the connector
|
|
445
|
+
* keys were cast onto the live config invisibly and the settings surface
|
|
446
|
+
* answered "Unknown setting" for keys the daemon really reads). Counted for
|
|
447
|
+
* exactly the reason DAEMON_MAILBOX_LOCAL_SETTINGS is: the same four-part
|
|
448
|
+
* persistence contract runs over every one of them in
|
|
449
|
+
* src/test/verification/connector-settings-persistence.test.ts, and that test
|
|
450
|
+
* asserts this list is the complete schema set for the email, calendar and
|
|
451
|
+
* google prefixes
|
|
452
|
+
* plus the two switches. Arriving uncounted is what dropped
|
|
453
|
+
* localBehaviorPercent through its floor when the schema grew. The three
|
|
454
|
+
* secret-valued keys hold goodvibes://secrets/ references, never values —
|
|
455
|
+
* same note as the mailbox passwords above.
|
|
456
|
+
*/
|
|
457
|
+
export const CONNECTOR_LOCAL_SETTINGS = [
|
|
458
|
+
'email.enabled',
|
|
459
|
+
'email.imapHost',
|
|
460
|
+
'email.imapPort',
|
|
461
|
+
'email.imapSecurity',
|
|
462
|
+
'email.smtpHost',
|
|
463
|
+
'email.smtpPort',
|
|
464
|
+
'email.smtpSecurity',
|
|
465
|
+
'email.username',
|
|
466
|
+
'email.passwordRef',
|
|
467
|
+
'email.smtpPasswordRef',
|
|
468
|
+
'email.fromAddress',
|
|
469
|
+
'email.mailbox',
|
|
470
|
+
'email.draftsMailbox',
|
|
471
|
+
'calendar.google.clientId',
|
|
472
|
+
'calendar.google.clientSecretRef',
|
|
473
|
+
'calendar.google.icsUrl',
|
|
474
|
+
'calendar.microsoft.clientId',
|
|
475
|
+
'calendar.microsoft.clientSecretRef',
|
|
476
|
+
'google.oauth.projectId',
|
|
477
|
+
'google.oauth.publishingStatus',
|
|
478
|
+
'google.oauth.refreshToken',
|
|
479
|
+
'google.credentials.migratedFrom',
|
|
480
|
+
'daemon.connectedHost.enabled',
|
|
481
|
+
'hostedSessions.routeConversationTurns',
|
|
482
|
+
] as const;
|
|
483
|
+
|
|
441
484
|
const SETTINGS_BEHAVIOR_VERIFIED = SETTINGS_BEHAVIOR_BASELINE
|
|
442
485
|
+ FEATURE_KNOB_LOCAL_SETTINGS.length
|
|
443
486
|
+ DEVICE_AND_TRIGGER_LOCAL_SETTINGS.length
|
|
444
487
|
+ CLUSTER_GROUP_LOCAL_SETTINGS.length
|
|
445
488
|
+ DAEMON_MAILBOX_LOCAL_SETTINGS.length
|
|
446
489
|
+ PAYMENTS_LOCAL_SETTINGS.length
|
|
490
|
+
+ CONNECTOR_LOCAL_SETTINGS.length
|
|
447
491
|
+ ENABLEMENT_KEYS_BEHAVIOR_VERIFIED;
|
|
448
492
|
|
|
449
493
|
const EXTERNAL_SLASH_COMMANDS = new Set([
|
package/src/version.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { join } from 'node:path';
|
|
|
6
6
|
// The prebuild script updates the fallback value before compilation.
|
|
7
7
|
// Uses import.meta.dir (Bun) to locate package.json relative to this file,
|
|
8
8
|
// which is correct regardless of the process working directory.
|
|
9
|
-
let _version = '2.0.
|
|
9
|
+
let _version = '2.0.9';
|
|
10
10
|
try {
|
|
11
11
|
const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8'));
|
|
12
12
|
// Only trust a version read from OUR OWN package.json. A Bun single-file
|