dsh-code 0.2.0 → 0.3.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/README.md +7 -2
- package/README.zh.md +7 -2
- package/cordis.patch.yml +6 -0
- package/lib/index.mjs +1091 -105
- package/lib/types/app.d.ts +29 -0
- package/lib/types/mentions.d.ts +70 -0
- package/lib/types/questions.d.ts +48 -0
- package/lib/types/render/animations.d.ts +15 -0
- package/lib/types/render/markdown.d.ts +27 -0
- package/lib/types/render/projection.d.ts +13 -0
- package/lib/types/render/status.d.ts +4 -0
- package/lib/types/render/tool-preview.d.ts +15 -0
- package/lib/types/theme.d.ts +4 -0
- package/package.json +10 -2
- package/src/app.ts +582 -78
- package/src/index.ts +110 -46
- package/src/mentions.ts +193 -0
- package/src/questions.ts +143 -0
- package/src/render/animations.ts +22 -0
- package/src/render/markdown.ts +235 -0
- package/src/render/projection.ts +48 -7
- package/src/render/status.ts +14 -2
- package/src/render/tool-preview.ts +34 -0
- package/src/theme.ts +4 -0
package/lib/types/app.d.ts
CHANGED
|
@@ -18,13 +18,17 @@ import type { TranscriptStore } from './store.ts';
|
|
|
18
18
|
import type { ApprovalStore } from './approval.ts';
|
|
19
19
|
import type { CommandsView } from './commands.ts';
|
|
20
20
|
import type { ModelDirectory, ModelRow } from './models.ts';
|
|
21
|
+
import type { QuestionStore } from './questions.ts';
|
|
21
22
|
import type { SkillsView } from './skills.ts';
|
|
23
|
+
import type { MentionCandidate } from './mentions.ts';
|
|
22
24
|
/** Props the runner hands the app; callbacks stay owned by the runner. */
|
|
23
25
|
export interface AppProps {
|
|
24
26
|
/** Event-fed transcript store for the live session. */
|
|
25
27
|
store: TranscriptStore;
|
|
26
28
|
/** Approval-question store fed by the answerer listener. */
|
|
27
29
|
approval: ApprovalStore;
|
|
30
|
+
/** ask_user_question store fed by the single UI provider. */
|
|
31
|
+
questions: QuestionStore;
|
|
28
32
|
/** Live slash-command descriptor list (completion candidates). */
|
|
29
33
|
commands: CommandsView;
|
|
30
34
|
/** Live user-invocable skill catalog (completion candidates). */
|
|
@@ -49,12 +53,37 @@ export interface AppProps {
|
|
|
49
53
|
quit(): void;
|
|
50
54
|
/** Load the selectable model directory (called when /model opens). */
|
|
51
55
|
loadModels(): Promise<ModelDirectory>;
|
|
56
|
+
/** Load @mention candidates for the typed query (files + sessions). */
|
|
57
|
+
loadMentions(query: string, signal?: AbortSignal): Promise<readonly MentionCandidate[]>;
|
|
52
58
|
/** Apply one /model selection; returns the display label. */
|
|
53
59
|
selectModel(row: ModelRow): string;
|
|
60
|
+
/** Cycle to the next permission preset (Shift+Tab); returns the new label. */
|
|
61
|
+
cyclePermission(): string;
|
|
54
62
|
/** Registers the app's notice channel with the runner (called once on mount). */
|
|
55
63
|
onBridgeReady(bridge: {
|
|
56
64
|
notify(text: string): void;
|
|
57
65
|
}): void;
|
|
58
66
|
}
|
|
67
|
+
/** One completion candidate row. */
|
|
68
|
+
interface CompletionCandidate {
|
|
69
|
+
/** Insertion text for the command name (with leading slash). */
|
|
70
|
+
label: string;
|
|
71
|
+
/** Human-readable description shown beside the label. */
|
|
72
|
+
description: string;
|
|
73
|
+
/** Candidate origin; skills land the same literal text but route through the prompt. */
|
|
74
|
+
origin: 'command' | 'skill' | 'mention';
|
|
75
|
+
}
|
|
76
|
+
/** The completion menu snapshot the input editor publishes to the app. */
|
|
77
|
+
export interface MenuState {
|
|
78
|
+
/** Whether the menu is on screen (slash or @mention). */
|
|
79
|
+
active: boolean;
|
|
80
|
+
/** Whether the menu is driven by an @mention token. */
|
|
81
|
+
mention: boolean;
|
|
82
|
+
/** Highlighted candidate index (wraps by row count). */
|
|
83
|
+
index: number;
|
|
84
|
+
/** Rendered rows in display order. */
|
|
85
|
+
rows: readonly CompletionCandidate[];
|
|
86
|
+
}
|
|
59
87
|
/** The whole terminal app; state arrives via the store, output via Ink. */
|
|
60
88
|
export declare function App(props: AppProps): ReactElement;
|
|
89
|
+
export {};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace @mention support: file candidates from a bounded async scan of
|
|
3
|
+
* the session cwd, session candidates from the opt-in `sessionReferenceResolver`
|
|
4
|
+
* service, and submission preparation through its `prepare()` API. Picked
|
|
5
|
+
* session mentions land as canonical `@[label](dsh-session:…)` tokens; on
|
|
6
|
+
* submit the text is parsed back into readable `@label` text plus structured
|
|
7
|
+
* references, snapshots are injected via `agent.inject()` before the readable
|
|
8
|
+
* message wakes the driver (`followup` idle, `steer` running) — exactly the
|
|
9
|
+
* upstream README's wiring.
|
|
10
|
+
*
|
|
11
|
+
* @module @deepseek-ai/dsh-code/mentions
|
|
12
|
+
*/
|
|
13
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
14
|
+
import type { Agent } from '@deepseek-ai/dsh-agent';
|
|
15
|
+
import { parseSessionReferenceText, type SessionReferenceCandidate, type SessionReferenceInput } from '@deepseek-ai/dsh-session-reference';
|
|
16
|
+
/** Parsed submission text: readable text plus structured references. */
|
|
17
|
+
type ParsedSessionReferenceText = ReturnType<typeof parseSessionReferenceText>;
|
|
18
|
+
/** One filesystem entry the @ menu can complete. */
|
|
19
|
+
export interface FileCandidate {
|
|
20
|
+
/** Workspace-relative path with forward slashes. */
|
|
21
|
+
path: string;
|
|
22
|
+
/** Entry kind; directories insert with a trailing slash. */
|
|
23
|
+
kind: 'file' | 'directory';
|
|
24
|
+
}
|
|
25
|
+
/** One merged menu candidate (files and sessions, already ranked). */
|
|
26
|
+
export interface MentionCandidate {
|
|
27
|
+
/** Text inserted after the `@` (directories carry a trailing slash). */
|
|
28
|
+
label: string;
|
|
29
|
+
/** Human-readable origin shown beside the label. */
|
|
30
|
+
description: string;
|
|
31
|
+
/** Origin kind for icon/coloring decisions. */
|
|
32
|
+
kind: 'file' | 'directory' | 'session';
|
|
33
|
+
}
|
|
34
|
+
/** Prepared submission: readable content plus optional injected context. */
|
|
35
|
+
export interface PreparedMention {
|
|
36
|
+
/** Readable text with mention tokens normalized to `@label`. */
|
|
37
|
+
text: string;
|
|
38
|
+
/** Structured source sessions in appearance order (empty when none). */
|
|
39
|
+
references: SessionReferenceInput[];
|
|
40
|
+
/** Aggregated snapshot for `agent.inject()`, undefined without references. */
|
|
41
|
+
additionalContext?: import('@deepseek-ai/dsh-session').UserMessage;
|
|
42
|
+
}
|
|
43
|
+
/** Bounded async BFS scan of a workspace; unreadable entries are skipped. */
|
|
44
|
+
export declare function scanWorkspaceFiles(root: string, signal?: AbortSignal): Promise<readonly FileCandidate[]>;
|
|
45
|
+
/** The mention API the input editor and the runner share. */
|
|
46
|
+
export interface MentionsApi {
|
|
47
|
+
/** Scanned workspace files, cached across one session. */
|
|
48
|
+
files(): Promise<readonly FileCandidate[]>;
|
|
49
|
+
/** Ranked menu candidates for the typed `@` query. */
|
|
50
|
+
candidates(query: string, signal?: AbortSignal): Promise<readonly MentionCandidate[]>;
|
|
51
|
+
/** Parse submission text into readable text plus structured references. */
|
|
52
|
+
parse(text: string): ParsedSessionReferenceText;
|
|
53
|
+
/**
|
|
54
|
+
* Snapshot references and build the injected context. Throws the service's
|
|
55
|
+
* typed error on failure — the caller restores the draft and notifies.
|
|
56
|
+
*/
|
|
57
|
+
prepare(parsed: ParsedSessionReferenceText, signal?: AbortSignal): Promise<PreparedMention>;
|
|
58
|
+
/** Canonical mention token for a picked session candidate. */
|
|
59
|
+
sessionMention(candidate: SessionReferenceCandidate): string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Create the mention API for one agent's workspace. A missing
|
|
63
|
+
* session-reference service degrades to file mentions only (the scan still
|
|
64
|
+
* works); `prepare` then passes text through untouched.
|
|
65
|
+
* @param ctx - context carrying the optional `sessionReferenceResolver`.
|
|
66
|
+
* @param agent - the session owner; excluded from its own candidates.
|
|
67
|
+
* @param cwd - workspace root to scan.
|
|
68
|
+
*/
|
|
69
|
+
export declare function createMentions(ctx: Context, agent: Agent, cwd: string): MentionsApi;
|
|
70
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The terminal ask_user_question provider: registers the single UI provider
|
|
3
|
+
* on `ctx.userQuestions` and drives it with a FIFO queue — one question
|
|
4
|
+
* request on screen at a time, everything else waiting — then resolves the
|
|
5
|
+
* collected answers back into the tool's promise. The community TUI proved
|
|
6
|
+
* this exact pipeline shape; here the dialog is an Ink bar instead of a
|
|
7
|
+
* pi-tui inline modal.
|
|
8
|
+
*
|
|
9
|
+
* Plan reviews (`exit_plan_mode`) arrive through the same service with an
|
|
10
|
+
* `intent: { kind: 'plan-review' }` — the renderer highlights the approve
|
|
11
|
+
* option; the answer encoding is identical either way.
|
|
12
|
+
*
|
|
13
|
+
* @module @deepseek-ai/dsh-code/questions
|
|
14
|
+
*/
|
|
15
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
16
|
+
import { type AskUserQuestionAnswer, type AskUserQuestionRequest } from '@deepseek-ai/dsh-user-questions';
|
|
17
|
+
/** One question request waiting on the human, with its settle channels. */
|
|
18
|
+
export interface PendingQuestion {
|
|
19
|
+
/** The request the renderer walks through question by question. */
|
|
20
|
+
request: AskUserQuestionRequest;
|
|
21
|
+
/** Resolve the provider promise with the collected answers. */
|
|
22
|
+
resolve(answers: AskUserQuestionAnswer): void;
|
|
23
|
+
/** Reject the provider promise as aborted (also used for Esc cancel). */
|
|
24
|
+
reject(error: Error): void;
|
|
25
|
+
}
|
|
26
|
+
/** The pending-question snapshot the renderer subscribes to. */
|
|
27
|
+
export interface QuestionSnapshot {
|
|
28
|
+
/** The active request, or undefined when nothing is being asked. */
|
|
29
|
+
pending: PendingQuestion | undefined;
|
|
30
|
+
}
|
|
31
|
+
/** Store the pending question lands in; the renderer reads, the provider writes. */
|
|
32
|
+
export interface QuestionStore {
|
|
33
|
+
/** Subscribe to pending-state changes; returns the unsubscribe function. */
|
|
34
|
+
subscribe(listener: () => void): () => void;
|
|
35
|
+
/** Read the current snapshot (identity-stable between changes). */
|
|
36
|
+
getSnapshot(): QuestionSnapshot;
|
|
37
|
+
/** Submit the collected answers for the active request and advance the queue. */
|
|
38
|
+
submit(pending: PendingQuestion, answers: AskUserQuestionAnswer): void;
|
|
39
|
+
/** Cancel the active request (Esc) — rejects ASK_ABORTED and advances the queue. */
|
|
40
|
+
cancel(pending: PendingQuestion): void;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Mount the single `ctx.userQuestions` UI provider over a FIFO queue.
|
|
44
|
+
* @param ctx - context carrying the `userQuestions` service (dsh-base).
|
|
45
|
+
* @returns the store the renderer subscribes to; a context without the
|
|
46
|
+
* service yields a permanently empty store.
|
|
47
|
+
*/
|
|
48
|
+
export declare function mountQuestionProvider(ctx: Context): QuestionStore;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal animation frame tables derived from the web design language:
|
|
3
|
+
* the StateDot "ongoing" pixel chase (3×3 ring, 125ms flat-hold brightness
|
|
4
|
+
* steps, 1s cycle) becomes the single-cell stepped pulse below, and the
|
|
5
|
+
* streaming caret blink is the Claude-Code convention. Pure functions only —
|
|
6
|
+
* the Ink layer owns timers and colors.
|
|
7
|
+
*
|
|
8
|
+
* @module @deepseek-ai/dsh-code/render/animations
|
|
9
|
+
*/
|
|
10
|
+
/** Single-cell stepped pulse: flat holds mirroring the web's 125ms keyframes. */
|
|
11
|
+
export declare const PULSE_FRAMES: readonly ["█", "█", "▆", "▃", "▁", "▃", "▆", "█"];
|
|
12
|
+
/** Pulse frame for a monotonic tick. */
|
|
13
|
+
export declare function pulseFrame(tick: number): string;
|
|
14
|
+
/** Caret visibility: half the ticks on, half off (530ms blink). */
|
|
15
|
+
export declare function caretVisible(tick: number): boolean;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal markdown renderer for assistant replies: a pure GFM-subset
|
|
3
|
+
* block/inline parser producing styled line segments the Ink renderer maps
|
|
4
|
+
* to colored text. No ANSI here — the app owns color mapping, tests own the
|
|
5
|
+
* structure. The subset mirrors what agent replies actually emit: headings,
|
|
6
|
+
* emphasis, inline/fenced code, flat lists, blockquotes, links, rules, and
|
|
7
|
+
* wrapped paragraphs. Unknown syntax degrades to plain text (never throws).
|
|
8
|
+
*
|
|
9
|
+
* @module @deepseek-ai/dsh-code/render/markdown
|
|
10
|
+
*/
|
|
11
|
+
/** Style classes the renderer emits; the app maps them to colors/props. */
|
|
12
|
+
export type MdStyle = 'plain' | 'bold' | 'italic' | 'boldItalic' | 'code' | 'accent' | 'dim' | 'strike';
|
|
13
|
+
/** One styled run of text. */
|
|
14
|
+
export interface MdSegment {
|
|
15
|
+
/** Visible text (no ANSI). */
|
|
16
|
+
text: string;
|
|
17
|
+
/** Presentation class for the app's color map. */
|
|
18
|
+
style: MdStyle;
|
|
19
|
+
}
|
|
20
|
+
/** One rendered line: a sequence of styled runs. */
|
|
21
|
+
export interface MdLine {
|
|
22
|
+
segments: readonly MdSegment[];
|
|
23
|
+
}
|
|
24
|
+
/** Visible width of a run in columns (CJK counts double). */
|
|
25
|
+
export declare function visibleColumns(text: string): number;
|
|
26
|
+
/** Render markdown text into styled lines of at most `width` columns. */
|
|
27
|
+
export declare function renderMarkdown(text: string, width: number): readonly MdLine[];
|
|
@@ -12,12 +12,17 @@ export interface UserEntry {
|
|
|
12
12
|
kind: 'user';
|
|
13
13
|
/** Joined text blocks of the user message. */
|
|
14
14
|
text: string;
|
|
15
|
+
/** True for collapsed injected context (plugin/continuation notices), which
|
|
16
|
+
* the renderer marks with a dim ↳ instead of the user ❯ prompt. */
|
|
17
|
+
notice: boolean;
|
|
15
18
|
}
|
|
16
19
|
/** One assembled assistant reply. */
|
|
17
20
|
export interface AssistantEntry {
|
|
18
21
|
kind: 'assistant';
|
|
19
22
|
/** Joined text blocks of the assistant message. */
|
|
20
23
|
text: string;
|
|
24
|
+
/** Joined reasoning blocks of the same message, empty when the model thought out loud. */
|
|
25
|
+
reasoning: string;
|
|
21
26
|
}
|
|
22
27
|
/** One model-requested tool invocation and its settled state. */
|
|
23
28
|
export interface ToolEntry {
|
|
@@ -28,6 +33,8 @@ export interface ToolEntry {
|
|
|
28
33
|
name: string;
|
|
29
34
|
/** Raw arguments JSON string exactly as the model produced it. */
|
|
30
35
|
arguments: string;
|
|
36
|
+
/** Bounded human-meaningful arguments preview for the tool card. */
|
|
37
|
+
preview: string;
|
|
31
38
|
/** Execution state; `running` until the paired result lands. */
|
|
32
39
|
state: 'running' | 'done' | 'error';
|
|
33
40
|
/** Bounded first text block of the result, empty until it lands. */
|
|
@@ -83,6 +90,8 @@ export interface TranscriptView {
|
|
|
83
90
|
entries: readonly TranscriptEntry[];
|
|
84
91
|
/** Text accumulated from `assistant/chunk` deltas since the last flush. */
|
|
85
92
|
streaming: string;
|
|
93
|
+
/** Thinking accumulated from `assistant/chunk` reasoning deltas since the last flush. */
|
|
94
|
+
streamingReasoning: string;
|
|
86
95
|
/** Latest whole-list todo snapshot from `todo/write`, empty when none. */
|
|
87
96
|
todos: readonly TodoItem[];
|
|
88
97
|
/** True while a durable turn is open (`turn/start` … `turn/end`). */
|
|
@@ -96,6 +105,10 @@ export interface TranscriptView {
|
|
|
96
105
|
* Empty before the session's first request.
|
|
97
106
|
*/
|
|
98
107
|
model: string;
|
|
108
|
+
/** Plan mode state folded from the last `plan/mode` event. */
|
|
109
|
+
plan: boolean;
|
|
110
|
+
/** Active permission preset folded from the last `permission/preset` event, empty before one. */
|
|
111
|
+
permission: string;
|
|
99
112
|
/**
|
|
100
113
|
* Fold-internal timing anchors, never rendered: open step and tool-call
|
|
101
114
|
* start timestamps the next `assistant/message` / `tool/result` resolves
|
|
@@ -37,6 +37,10 @@ export interface StatusFacts {
|
|
|
37
37
|
branch: string;
|
|
38
38
|
/** Short session identifier (last dash-separated segment or tail). */
|
|
39
39
|
sessionId: string;
|
|
40
|
+
/** Whether plan mode is active (folded from `plan/mode`). */
|
|
41
|
+
plan: boolean;
|
|
42
|
+
/** Active permission preset (folded from `permission/preset`), empty when unknown. */
|
|
43
|
+
permission: string;
|
|
40
44
|
}
|
|
41
45
|
/**
|
|
42
46
|
* Build the footer's display groups; a group with no data drops out whole.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded preview line for a tool invocation's raw JSON arguments: the first
|
|
3
|
+
* human-meaningful string among the well-known keys (command, path, query, …)
|
|
4
|
+
* with a fallback to the bounded raw JSON. Shared by the tool card in the
|
|
5
|
+
* transcript and the approval bar's command preview.
|
|
6
|
+
*
|
|
7
|
+
* @module @deepseek-ai/dsh-code/render/tool-preview
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Resolve one bounded preview for raw tool arguments.
|
|
11
|
+
* @param args - raw JSON arguments string as the model produced it.
|
|
12
|
+
* @param toolName - the tool the arguments belong to (fallback label).
|
|
13
|
+
* @returns the preview line; empty when nothing useful resolves.
|
|
14
|
+
*/
|
|
15
|
+
export declare function toolArgumentsPreview(args: string, toolName: string): string;
|
package/lib/types/theme.d.ts
CHANGED
|
@@ -25,6 +25,10 @@ export declare const TUI_RGB: {
|
|
|
25
25
|
readonly error: readonly [239, 68, 68];
|
|
26
26
|
/** Warning amber — `--dsw-static-amber-500`. */
|
|
27
27
|
readonly warn: readonly [245, 158, 11];
|
|
28
|
+
/** Default foreground text — `--dsw-static-neutral-50`. */
|
|
29
|
+
readonly text: readonly [236, 240, 246];
|
|
30
|
+
/** Inline/fenced code — soft sky blue, distinct from brand accents. */
|
|
31
|
+
readonly code: readonly [125, 211, 252];
|
|
28
32
|
};
|
|
29
33
|
/** Paint with the primary brand blue: whale, wordmark, tool names, accents. */
|
|
30
34
|
export declare function brand(text: string): string;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-code",
|
|
3
3
|
"description": "Claude-Code-style interactive TUI bundle for DeepSeek Harness (dsh): DeepSeek-blue whale banner, live session transcript, and a blended status line",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.mjs",
|
|
7
7
|
"types": "lib/types/index.d.ts",
|
|
@@ -74,16 +74,24 @@
|
|
|
74
74
|
"@deepseek-ai/dsh-commands": "^0.1.0-rc.6",
|
|
75
75
|
"@deepseek-ai/dsh-invariants": "^0.1.0-rc.6",
|
|
76
76
|
"@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
|
|
77
|
+
"@deepseek-ai/dsh-permission-presets": "^0.1.0-rc.6",
|
|
78
|
+
"@deepseek-ai/dsh-plan-mode": "^0.1.0-rc.6",
|
|
77
79
|
"@deepseek-ai/dsh-session": "^0.1.0-rc.6",
|
|
78
80
|
"@deepseek-ai/dsh-session-persistence": "^0.1.0-rc.6",
|
|
81
|
+
"@deepseek-ai/dsh-session-reference": "^0.1.0-rc.6",
|
|
79
82
|
"@deepseek-ai/dsh-skill": "^0.1.0-rc.6",
|
|
80
|
-
"@deepseek-ai/dsh-user-approval": "^0.1.0-rc.6"
|
|
83
|
+
"@deepseek-ai/dsh-user-approval": "^0.1.0-rc.6",
|
|
84
|
+
"@deepseek-ai/dsh-user-questions": "^0.1.0-rc.6"
|
|
81
85
|
},
|
|
82
86
|
"devDependencies": {
|
|
83
87
|
"@deepseek-ai/dsh-commands": "^0.1.0-rc.6",
|
|
88
|
+
"@deepseek-ai/dsh-permission-presets": "^0.1.0-rc.6",
|
|
89
|
+
"@deepseek-ai/dsh-plan-mode": "^0.1.0-rc.6",
|
|
84
90
|
"@deepseek-ai/dsh-session-persistence": "^0.1.0-rc.6",
|
|
91
|
+
"@deepseek-ai/dsh-session-reference": "^0.1.0-rc.6",
|
|
85
92
|
"@deepseek-ai/dsh-skill": "^0.1.0-rc.6",
|
|
86
93
|
"@deepseek-ai/dsh-user-approval": "^0.1.0-rc.6",
|
|
94
|
+
"@deepseek-ai/dsh-user-questions": "^0.1.0-rc.6",
|
|
87
95
|
"@types/node": "^24.0.0",
|
|
88
96
|
"@types/react": "~18.3.1",
|
|
89
97
|
"tsdown": "^0.22.2",
|