@claude-code-kit/ink-renderer 0.1.0 → 0.2.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 +21 -0
- package/README.md +45 -0
- package/dist/index.js +125 -21
- package/dist/index.mjs +124 -20
- package/package.json +8 -5
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Minnzen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# @claude-code-kit/ink-renderer
|
|
2
|
+
|
|
3
|
+
Terminal rendering engine for claude-code-kit — React reconciler, Yoga Flexbox layout, keyboard/mouse events, ANSI output.
|
|
4
|
+
|
|
5
|
+
Part of [claude-code-kit](https://github.com/Minnzen/claude-code-kit).
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @claude-code-kit/ink-renderer react
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import React from 'react'
|
|
17
|
+
import { render, Box, Text } from '@claude-code-kit/ink-renderer'
|
|
18
|
+
|
|
19
|
+
function App() {
|
|
20
|
+
return (
|
|
21
|
+
<Box flexDirection="column" padding={1}>
|
|
22
|
+
<Text bold color="green">Hello from claude-code-kit</Text>
|
|
23
|
+
<Text>Build terminal UIs like React apps.</Text>
|
|
24
|
+
</Box>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
await render(<App />)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Included
|
|
32
|
+
|
|
33
|
+
- Rendering API: `render`, `renderSync`, `createRoot`
|
|
34
|
+
- Primitives: `Box`, `Text`, `Spacer`, `Newline`, `Link`, `Button`, `ScrollBox`
|
|
35
|
+
- Hooks: `useInput`, `useApp`, `useStdin`, `useInterval`, `useAnimationFrame`
|
|
36
|
+
- Terminal helpers: `AlternateScreen`, `RawAnsi`, `Ansi`, `ErrorOverview`
|
|
37
|
+
|
|
38
|
+
## Docs
|
|
39
|
+
|
|
40
|
+
- Full project docs: [github.com/Minnzen/claude-code-kit](https://github.com/Minnzen/claude-code-kit)
|
|
41
|
+
- Components overview: [docs/components.md](https://github.com/Minnzen/claude-code-kit/blob/main/docs/components.md)
|
|
42
|
+
|
|
43
|
+
## License
|
|
44
|
+
|
|
45
|
+
MIT
|
package/dist/index.js
CHANGED
|
@@ -80,9 +80,96 @@ var import_stream = require("stream");
|
|
|
80
80
|
// src/ink.tsx
|
|
81
81
|
var import_auto_bind = __toESM(require("auto-bind"));
|
|
82
82
|
var import_fs3 = require("fs");
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
|
|
84
|
+
// src/lodash-replacements.ts
|
|
85
|
+
function noop() {
|
|
86
|
+
}
|
|
87
|
+
function throttle(func, wait, options = {}) {
|
|
88
|
+
let timerId;
|
|
89
|
+
let lastCallTime;
|
|
90
|
+
let lastInvokeTime = 0;
|
|
91
|
+
let lastArgs;
|
|
92
|
+
let result;
|
|
93
|
+
const leading = options.leading !== false;
|
|
94
|
+
const trailing = options.trailing !== false;
|
|
95
|
+
function invokeFunc(time) {
|
|
96
|
+
lastInvokeTime = time;
|
|
97
|
+
const args = lastArgs;
|
|
98
|
+
lastArgs = void 0;
|
|
99
|
+
result = func(...args);
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
function startTimer(pendingFunc, remainingWait2) {
|
|
103
|
+
timerId = setTimeout(pendingFunc, remainingWait2);
|
|
104
|
+
}
|
|
105
|
+
function shouldInvoke(time) {
|
|
106
|
+
const timeSinceLastCall = lastCallTime === void 0 ? wait : time - lastCallTime;
|
|
107
|
+
const timeSinceLastInvoke = time - lastInvokeTime;
|
|
108
|
+
return lastCallTime === void 0 || timeSinceLastCall >= wait || timeSinceLastCall < 0 || timeSinceLastInvoke >= wait;
|
|
109
|
+
}
|
|
110
|
+
function remainingWait(time) {
|
|
111
|
+
const timeSinceLastCall = time - (lastCallTime ?? 0);
|
|
112
|
+
return Math.max(0, wait - timeSinceLastCall);
|
|
113
|
+
}
|
|
114
|
+
function timerExpired() {
|
|
115
|
+
const time = Date.now();
|
|
116
|
+
if (shouldInvoke(time)) {
|
|
117
|
+
trailingEdge(time);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
startTimer(timerExpired, remainingWait(time));
|
|
121
|
+
}
|
|
122
|
+
function trailingEdge(time) {
|
|
123
|
+
timerId = void 0;
|
|
124
|
+
if (trailing && lastArgs) {
|
|
125
|
+
invokeFunc(time);
|
|
126
|
+
}
|
|
127
|
+
lastArgs = void 0;
|
|
128
|
+
}
|
|
129
|
+
function leadingEdge(time) {
|
|
130
|
+
lastInvokeTime = time;
|
|
131
|
+
startTimer(timerExpired, wait);
|
|
132
|
+
if (leading) {
|
|
133
|
+
invokeFunc(time);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const throttled = function(...args) {
|
|
137
|
+
const time = Date.now();
|
|
138
|
+
const isInvoking = shouldInvoke(time);
|
|
139
|
+
lastArgs = args;
|
|
140
|
+
lastCallTime = time;
|
|
141
|
+
if (isInvoking) {
|
|
142
|
+
if (timerId === void 0) {
|
|
143
|
+
leadingEdge(time);
|
|
144
|
+
return result;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (timerId === void 0) {
|
|
148
|
+
startTimer(timerExpired, wait);
|
|
149
|
+
}
|
|
150
|
+
return result;
|
|
151
|
+
};
|
|
152
|
+
throttled.cancel = function() {
|
|
153
|
+
if (timerId !== void 0) {
|
|
154
|
+
clearTimeout(timerId);
|
|
155
|
+
}
|
|
156
|
+
lastInvokeTime = 0;
|
|
157
|
+
lastArgs = void 0;
|
|
158
|
+
lastCallTime = void 0;
|
|
159
|
+
timerId = void 0;
|
|
160
|
+
};
|
|
161
|
+
throttled.flush = function() {
|
|
162
|
+
if (timerId === void 0) {
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
trailingEdge(Date.now());
|
|
166
|
+
return result;
|
|
167
|
+
};
|
|
168
|
+
return throttled;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/ink.tsx
|
|
172
|
+
var import_constants4 = require("react-reconciler/constants.js");
|
|
86
173
|
var import_signal_exit = require("signal-exit");
|
|
87
174
|
var import_yoga_layout3 = require("@claude-code-kit/shared/yoga-layout");
|
|
88
175
|
var import_shared13 = require("@claude-code-kit/shared");
|
|
@@ -172,6 +259,22 @@ var colorize = (str, color, type) => {
|
|
|
172
259
|
const thirdValue = Number(matches[3]);
|
|
173
260
|
return type === "foreground" ? import_chalk.default.rgb(firstValue, secondValue, thirdValue)(str) : import_chalk.default.bgRgb(firstValue, secondValue, thirdValue)(str);
|
|
174
261
|
}
|
|
262
|
+
const PLAIN_ANSI = {
|
|
263
|
+
black: type === "foreground" ? import_chalk.default.black : import_chalk.default.bgBlack,
|
|
264
|
+
red: type === "foreground" ? import_chalk.default.red : import_chalk.default.bgRed,
|
|
265
|
+
green: type === "foreground" ? import_chalk.default.green : import_chalk.default.bgGreen,
|
|
266
|
+
yellow: type === "foreground" ? import_chalk.default.yellow : import_chalk.default.bgYellow,
|
|
267
|
+
blue: type === "foreground" ? import_chalk.default.blue : import_chalk.default.bgBlue,
|
|
268
|
+
magenta: type === "foreground" ? import_chalk.default.magenta : import_chalk.default.bgMagenta,
|
|
269
|
+
cyan: type === "foreground" ? import_chalk.default.cyan : import_chalk.default.bgCyan,
|
|
270
|
+
white: type === "foreground" ? import_chalk.default.white : import_chalk.default.bgWhite,
|
|
271
|
+
gray: type === "foreground" ? import_chalk.default.gray : import_chalk.default.bgBlackBright,
|
|
272
|
+
grey: type === "foreground" ? import_chalk.default.gray : import_chalk.default.bgBlackBright
|
|
273
|
+
};
|
|
274
|
+
const fn = PLAIN_ANSI[color];
|
|
275
|
+
if (fn) {
|
|
276
|
+
return fn(str);
|
|
277
|
+
}
|
|
175
278
|
return str;
|
|
176
279
|
};
|
|
177
280
|
function applyTextStyles(text, styles2) {
|
|
@@ -2153,7 +2256,7 @@ function findOwnerChainAtRow(root, y) {
|
|
|
2153
2256
|
}
|
|
2154
2257
|
|
|
2155
2258
|
// src/events/dispatcher.ts
|
|
2156
|
-
var import_constants = require("react-reconciler/constants");
|
|
2259
|
+
var import_constants = require("react-reconciler/constants.js");
|
|
2157
2260
|
var import_shared3 = require("@claude-code-kit/shared");
|
|
2158
2261
|
|
|
2159
2262
|
// src/events/event-handlers.ts
|
|
@@ -2813,7 +2916,7 @@ var diff = (before, after) => {
|
|
|
2813
2916
|
const changed = {};
|
|
2814
2917
|
let isChanged = false;
|
|
2815
2918
|
for (const key of Object.keys(before)) {
|
|
2816
|
-
const isDeleted = after ? !Object.
|
|
2919
|
+
const isDeleted = after ? !Object.prototype.hasOwnProperty.call(after, key) : true;
|
|
2817
2920
|
if (isDeleted) {
|
|
2818
2921
|
changed[key] = void 0;
|
|
2819
2922
|
isChanged = true;
|
|
@@ -4142,7 +4245,7 @@ function findPlainTextUrlAt(screen, col, row) {
|
|
|
4142
4245
|
let url = token.slice(urlStart, urlEnd);
|
|
4143
4246
|
const OPENER = { ")": "(", "]": "[", "}": "{" };
|
|
4144
4247
|
while (url.length > 0) {
|
|
4145
|
-
const last = url.
|
|
4248
|
+
const last = url.charAt(url.length - 1);
|
|
4146
4249
|
if (".,;:!?".includes(last)) {
|
|
4147
4250
|
url = url.slice(0, -1);
|
|
4148
4251
|
continue;
|
|
@@ -4506,7 +4609,7 @@ function osc(...parts) {
|
|
|
4506
4609
|
}
|
|
4507
4610
|
function wrapForMultiplexer(sequence) {
|
|
4508
4611
|
if (process.env["TMUX"]) {
|
|
4509
|
-
const escaped = sequence.
|
|
4612
|
+
const escaped = sequence.split("\x1B").join("\x1B\x1B");
|
|
4510
4613
|
return `\x1BPtmux;${escaped}\x1B\\`;
|
|
4511
4614
|
}
|
|
4512
4615
|
if (process.env["STY"]) {
|
|
@@ -4515,7 +4618,7 @@ function wrapForMultiplexer(sequence) {
|
|
|
4515
4618
|
return sequence;
|
|
4516
4619
|
}
|
|
4517
4620
|
function tmuxPassthrough(payload) {
|
|
4518
|
-
return `${ESC}Ptmux;${payload.
|
|
4621
|
+
return `${ESC}Ptmux;${payload.split(ESC).join(ESC + ESC)}${ST}`;
|
|
4519
4622
|
}
|
|
4520
4623
|
async function tmuxLoadBuffer(text) {
|
|
4521
4624
|
if (!process.env["TMUX"]) return false;
|
|
@@ -4755,11 +4858,12 @@ function supportsTabStatus() {
|
|
|
4755
4858
|
function tabStatus(fields) {
|
|
4756
4859
|
const parts = [];
|
|
4757
4860
|
const rgb2 = (c) => c.type === "rgb" ? `#${[c.r, c.g, c.b].map((n) => n.toString(16).padStart(2, "0")).join("")}` : "";
|
|
4861
|
+
const escapeStatusValue = (value) => value.split("\\").join("\\\\").split(";").join("\\;");
|
|
4758
4862
|
if ("indicator" in fields)
|
|
4759
4863
|
parts.push(`indicator=${fields.indicator ? rgb2(fields.indicator) : ""}`);
|
|
4760
4864
|
if ("status" in fields)
|
|
4761
4865
|
parts.push(
|
|
4762
|
-
`status=${fields.status
|
|
4866
|
+
`status=${fields.status ? escapeStatusValue(fields.status) : ""}`
|
|
4763
4867
|
);
|
|
4764
4868
|
if ("statusColor" in fields)
|
|
4765
4869
|
parts.push(
|
|
@@ -6797,12 +6901,13 @@ var Output = class {
|
|
|
6797
6901
|
if (operation.fromAbsolute) absoluteClears.push(rect);
|
|
6798
6902
|
}
|
|
6799
6903
|
const clips = [];
|
|
6904
|
+
const getCurrentClip = () => clips[clips.length - 1];
|
|
6800
6905
|
for (const operation of this.operations) {
|
|
6801
6906
|
switch (operation.type) {
|
|
6802
6907
|
case "clear":
|
|
6803
6908
|
continue;
|
|
6804
6909
|
case "clip":
|
|
6805
|
-
clips.push(intersectClip(
|
|
6910
|
+
clips.push(intersectClip(getCurrentClip(), operation.clip));
|
|
6806
6911
|
continue;
|
|
6807
6912
|
case "unclip":
|
|
6808
6913
|
clips.pop();
|
|
@@ -6815,7 +6920,7 @@ var Output = class {
|
|
|
6815
6920
|
width: regionWidth,
|
|
6816
6921
|
height: regionHeight
|
|
6817
6922
|
} = operation;
|
|
6818
|
-
const clip =
|
|
6923
|
+
const clip = getCurrentClip();
|
|
6819
6924
|
const startX = Math.max(regionX, clip?.x1 ?? 0);
|
|
6820
6925
|
const startY = Math.max(regionY, clip?.y1 ?? 0);
|
|
6821
6926
|
const maxY = Math.min(
|
|
@@ -6861,7 +6966,7 @@ var Output = class {
|
|
|
6861
6966
|
let lines = text.split("\n");
|
|
6862
6967
|
let swFrom = 0;
|
|
6863
6968
|
let prevContentEnd = 0;
|
|
6864
|
-
const clip =
|
|
6969
|
+
const clip = getCurrentClip();
|
|
6865
6970
|
if (clip) {
|
|
6866
6971
|
const clipHorizontally = typeof clip?.x1 === "number" && typeof clip?.x2 === "number";
|
|
6867
6972
|
const clipVertically = typeof clip?.y1 === "number" && typeof clip?.y2 === "number";
|
|
@@ -7984,8 +8089,7 @@ function dropSubtreeCache(node) {
|
|
|
7984
8089
|
var render_node_to_output_default = renderNodeToOutput;
|
|
7985
8090
|
|
|
7986
8091
|
// src/render-to-screen.ts
|
|
7987
|
-
var
|
|
7988
|
-
var import_constants3 = require("react-reconciler/constants");
|
|
8092
|
+
var import_constants3 = require("react-reconciler/constants.js");
|
|
7989
8093
|
var import_shared11 = require("@claude-code-kit/shared");
|
|
7990
8094
|
var timing = { reconcile: 0, yoga: 0, paint: 0, scan: 0, calls: 0 };
|
|
7991
8095
|
function scanPositions(screen, query) {
|
|
@@ -8382,7 +8486,7 @@ var Ink = class {
|
|
|
8382
8486
|
stylePool: this.stylePool
|
|
8383
8487
|
});
|
|
8384
8488
|
const deferredRender = () => queueMicrotask(this.onRender);
|
|
8385
|
-
this.scheduleRender = (
|
|
8489
|
+
this.scheduleRender = throttle(deferredRender, FRAME_INTERVAL_MS, {
|
|
8386
8490
|
leading: true,
|
|
8387
8491
|
trailing: true
|
|
8388
8492
|
});
|
|
@@ -8428,13 +8532,13 @@ var Ink = class {
|
|
|
8428
8532
|
false,
|
|
8429
8533
|
null,
|
|
8430
8534
|
"id",
|
|
8431
|
-
|
|
8535
|
+
noop,
|
|
8432
8536
|
// onUncaughtError
|
|
8433
|
-
|
|
8537
|
+
noop,
|
|
8434
8538
|
// onCaughtError
|
|
8435
|
-
|
|
8539
|
+
noop,
|
|
8436
8540
|
// onRecoverableError
|
|
8437
|
-
|
|
8541
|
+
noop
|
|
8438
8542
|
// onDefaultTransitionIndicator
|
|
8439
8543
|
);
|
|
8440
8544
|
if (false) {
|
|
@@ -9225,7 +9329,7 @@ var Ink = class {
|
|
|
9225
9329
|
render(node) {
|
|
9226
9330
|
this.currentNode = node;
|
|
9227
9331
|
const tree = /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(App, { stdin: this.options.stdin, stdout: this.options.stdout, stderr: this.options.stderr, exitOnCtrlC: this.options.exitOnCtrlC, onExit: this.unmount, terminalColumns: this.terminalColumns, terminalRows: this.terminalRows, selection: this.selection, onSelectionChange: this.notifySelectionChange, onClickAt: this.dispatchClick, onHoverAt: this.dispatchHover, getHyperlinkAt: this.getHyperlinkAt, onOpenHyperlink: this.openHyperlink, onMultiClick: this.handleMultiClick, onSelectionDrag: this.handleSelectionDrag, onStdinResume: this.reassertTerminalModes, onCursorDeclaration: this.setCursorDeclaration, dispatchKeyboardEvent: this.dispatchKeyboardEvent, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TerminalWriteProvider, { value: this.writeRaw, children: node }) });
|
|
9228
|
-
reconciler_default.updateContainerSync(tree, this.container, null,
|
|
9332
|
+
reconciler_default.updateContainerSync(tree, this.container, null, noop);
|
|
9229
9333
|
reconciler_default.flushSyncWork();
|
|
9230
9334
|
}
|
|
9231
9335
|
unmount(error) {
|
|
@@ -9261,7 +9365,7 @@ var Ink = class {
|
|
|
9261
9365
|
clearTimeout(this.drainTimer);
|
|
9262
9366
|
this.drainTimer = null;
|
|
9263
9367
|
}
|
|
9264
|
-
reconciler_default.updateContainerSync(null, this.container, null,
|
|
9368
|
+
reconciler_default.updateContainerSync(null, this.container, null, noop);
|
|
9265
9369
|
reconciler_default.flushSyncWork();
|
|
9266
9370
|
instances_default.delete(this.options.stdout);
|
|
9267
9371
|
this.rootNode.yogaNode?.free();
|
package/dist/index.mjs
CHANGED
|
@@ -5,9 +5,96 @@ import { Stream } from "stream";
|
|
|
5
5
|
// src/ink.tsx
|
|
6
6
|
import autoBind from "auto-bind";
|
|
7
7
|
import { closeSync, constants as fsConstants, openSync, readSync, writeSync } from "fs";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
|
|
9
|
+
// src/lodash-replacements.ts
|
|
10
|
+
function noop() {
|
|
11
|
+
}
|
|
12
|
+
function throttle(func, wait, options = {}) {
|
|
13
|
+
let timerId;
|
|
14
|
+
let lastCallTime;
|
|
15
|
+
let lastInvokeTime = 0;
|
|
16
|
+
let lastArgs;
|
|
17
|
+
let result;
|
|
18
|
+
const leading = options.leading !== false;
|
|
19
|
+
const trailing = options.trailing !== false;
|
|
20
|
+
function invokeFunc(time) {
|
|
21
|
+
lastInvokeTime = time;
|
|
22
|
+
const args = lastArgs;
|
|
23
|
+
lastArgs = void 0;
|
|
24
|
+
result = func(...args);
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
function startTimer(pendingFunc, remainingWait2) {
|
|
28
|
+
timerId = setTimeout(pendingFunc, remainingWait2);
|
|
29
|
+
}
|
|
30
|
+
function shouldInvoke(time) {
|
|
31
|
+
const timeSinceLastCall = lastCallTime === void 0 ? wait : time - lastCallTime;
|
|
32
|
+
const timeSinceLastInvoke = time - lastInvokeTime;
|
|
33
|
+
return lastCallTime === void 0 || timeSinceLastCall >= wait || timeSinceLastCall < 0 || timeSinceLastInvoke >= wait;
|
|
34
|
+
}
|
|
35
|
+
function remainingWait(time) {
|
|
36
|
+
const timeSinceLastCall = time - (lastCallTime ?? 0);
|
|
37
|
+
return Math.max(0, wait - timeSinceLastCall);
|
|
38
|
+
}
|
|
39
|
+
function timerExpired() {
|
|
40
|
+
const time = Date.now();
|
|
41
|
+
if (shouldInvoke(time)) {
|
|
42
|
+
trailingEdge(time);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
startTimer(timerExpired, remainingWait(time));
|
|
46
|
+
}
|
|
47
|
+
function trailingEdge(time) {
|
|
48
|
+
timerId = void 0;
|
|
49
|
+
if (trailing && lastArgs) {
|
|
50
|
+
invokeFunc(time);
|
|
51
|
+
}
|
|
52
|
+
lastArgs = void 0;
|
|
53
|
+
}
|
|
54
|
+
function leadingEdge(time) {
|
|
55
|
+
lastInvokeTime = time;
|
|
56
|
+
startTimer(timerExpired, wait);
|
|
57
|
+
if (leading) {
|
|
58
|
+
invokeFunc(time);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const throttled = function(...args) {
|
|
62
|
+
const time = Date.now();
|
|
63
|
+
const isInvoking = shouldInvoke(time);
|
|
64
|
+
lastArgs = args;
|
|
65
|
+
lastCallTime = time;
|
|
66
|
+
if (isInvoking) {
|
|
67
|
+
if (timerId === void 0) {
|
|
68
|
+
leadingEdge(time);
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (timerId === void 0) {
|
|
73
|
+
startTimer(timerExpired, wait);
|
|
74
|
+
}
|
|
75
|
+
return result;
|
|
76
|
+
};
|
|
77
|
+
throttled.cancel = function() {
|
|
78
|
+
if (timerId !== void 0) {
|
|
79
|
+
clearTimeout(timerId);
|
|
80
|
+
}
|
|
81
|
+
lastInvokeTime = 0;
|
|
82
|
+
lastArgs = void 0;
|
|
83
|
+
lastCallTime = void 0;
|
|
84
|
+
timerId = void 0;
|
|
85
|
+
};
|
|
86
|
+
throttled.flush = function() {
|
|
87
|
+
if (timerId === void 0) {
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
trailingEdge(Date.now());
|
|
91
|
+
return result;
|
|
92
|
+
};
|
|
93
|
+
return throttled;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/ink.tsx
|
|
97
|
+
import { ConcurrentRoot } from "react-reconciler/constants.js";
|
|
11
98
|
import { onExit } from "signal-exit";
|
|
12
99
|
import { getYogaCounters as getYogaCounters2 } from "@claude-code-kit/shared/yoga-layout";
|
|
13
100
|
import { logForDebugging as logForDebugging7 } from "@claude-code-kit/shared";
|
|
@@ -97,6 +184,22 @@ var colorize = (str, color, type) => {
|
|
|
97
184
|
const thirdValue = Number(matches[3]);
|
|
98
185
|
return type === "foreground" ? chalk.rgb(firstValue, secondValue, thirdValue)(str) : chalk.bgRgb(firstValue, secondValue, thirdValue)(str);
|
|
99
186
|
}
|
|
187
|
+
const PLAIN_ANSI = {
|
|
188
|
+
black: type === "foreground" ? chalk.black : chalk.bgBlack,
|
|
189
|
+
red: type === "foreground" ? chalk.red : chalk.bgRed,
|
|
190
|
+
green: type === "foreground" ? chalk.green : chalk.bgGreen,
|
|
191
|
+
yellow: type === "foreground" ? chalk.yellow : chalk.bgYellow,
|
|
192
|
+
blue: type === "foreground" ? chalk.blue : chalk.bgBlue,
|
|
193
|
+
magenta: type === "foreground" ? chalk.magenta : chalk.bgMagenta,
|
|
194
|
+
cyan: type === "foreground" ? chalk.cyan : chalk.bgCyan,
|
|
195
|
+
white: type === "foreground" ? chalk.white : chalk.bgWhite,
|
|
196
|
+
gray: type === "foreground" ? chalk.gray : chalk.bgBlackBright,
|
|
197
|
+
grey: type === "foreground" ? chalk.gray : chalk.bgBlackBright
|
|
198
|
+
};
|
|
199
|
+
const fn = PLAIN_ANSI[color];
|
|
200
|
+
if (fn) {
|
|
201
|
+
return fn(str);
|
|
202
|
+
}
|
|
100
203
|
return str;
|
|
101
204
|
};
|
|
102
205
|
function applyTextStyles(text, styles2) {
|
|
@@ -2095,7 +2198,7 @@ import {
|
|
|
2095
2198
|
DefaultEventPriority,
|
|
2096
2199
|
DiscreteEventPriority,
|
|
2097
2200
|
NoEventPriority
|
|
2098
|
-
} from "react-reconciler/constants";
|
|
2201
|
+
} from "react-reconciler/constants.js";
|
|
2099
2202
|
import { logError } from "@claude-code-kit/shared";
|
|
2100
2203
|
|
|
2101
2204
|
// src/events/event-handlers.ts
|
|
@@ -2755,7 +2858,7 @@ var diff = (before, after) => {
|
|
|
2755
2858
|
const changed = {};
|
|
2756
2859
|
let isChanged = false;
|
|
2757
2860
|
for (const key of Object.keys(before)) {
|
|
2758
|
-
const isDeleted = after ? !Object.
|
|
2861
|
+
const isDeleted = after ? !Object.prototype.hasOwnProperty.call(after, key) : true;
|
|
2759
2862
|
if (isDeleted) {
|
|
2760
2863
|
changed[key] = void 0;
|
|
2761
2864
|
isChanged = true;
|
|
@@ -4087,7 +4190,7 @@ function findPlainTextUrlAt(screen, col, row) {
|
|
|
4087
4190
|
let url = token.slice(urlStart, urlEnd);
|
|
4088
4191
|
const OPENER = { ")": "(", "]": "[", "}": "{" };
|
|
4089
4192
|
while (url.length > 0) {
|
|
4090
|
-
const last = url.
|
|
4193
|
+
const last = url.charAt(url.length - 1);
|
|
4091
4194
|
if (".,;:!?".includes(last)) {
|
|
4092
4195
|
url = url.slice(0, -1);
|
|
4093
4196
|
continue;
|
|
@@ -4451,7 +4554,7 @@ function osc(...parts) {
|
|
|
4451
4554
|
}
|
|
4452
4555
|
function wrapForMultiplexer(sequence) {
|
|
4453
4556
|
if (process.env["TMUX"]) {
|
|
4454
|
-
const escaped = sequence.
|
|
4557
|
+
const escaped = sequence.split("\x1B").join("\x1B\x1B");
|
|
4455
4558
|
return `\x1BPtmux;${escaped}\x1B\\`;
|
|
4456
4559
|
}
|
|
4457
4560
|
if (process.env["STY"]) {
|
|
@@ -4460,7 +4563,7 @@ function wrapForMultiplexer(sequence) {
|
|
|
4460
4563
|
return sequence;
|
|
4461
4564
|
}
|
|
4462
4565
|
function tmuxPassthrough(payload) {
|
|
4463
|
-
return `${ESC}Ptmux;${payload.
|
|
4566
|
+
return `${ESC}Ptmux;${payload.split(ESC).join(ESC + ESC)}${ST}`;
|
|
4464
4567
|
}
|
|
4465
4568
|
async function tmuxLoadBuffer(text) {
|
|
4466
4569
|
if (!process.env["TMUX"]) return false;
|
|
@@ -4700,11 +4803,12 @@ function supportsTabStatus() {
|
|
|
4700
4803
|
function tabStatus(fields) {
|
|
4701
4804
|
const parts = [];
|
|
4702
4805
|
const rgb2 = (c) => c.type === "rgb" ? `#${[c.r, c.g, c.b].map((n) => n.toString(16).padStart(2, "0")).join("")}` : "";
|
|
4806
|
+
const escapeStatusValue = (value) => value.split("\\").join("\\\\").split(";").join("\\;");
|
|
4703
4807
|
if ("indicator" in fields)
|
|
4704
4808
|
parts.push(`indicator=${fields.indicator ? rgb2(fields.indicator) : ""}`);
|
|
4705
4809
|
if ("status" in fields)
|
|
4706
4810
|
parts.push(
|
|
4707
|
-
`status=${fields.status
|
|
4811
|
+
`status=${fields.status ? escapeStatusValue(fields.status) : ""}`
|
|
4708
4812
|
);
|
|
4709
4813
|
if ("statusColor" in fields)
|
|
4710
4814
|
parts.push(
|
|
@@ -6748,12 +6852,13 @@ var Output = class {
|
|
|
6748
6852
|
if (operation.fromAbsolute) absoluteClears.push(rect);
|
|
6749
6853
|
}
|
|
6750
6854
|
const clips = [];
|
|
6855
|
+
const getCurrentClip = () => clips[clips.length - 1];
|
|
6751
6856
|
for (const operation of this.operations) {
|
|
6752
6857
|
switch (operation.type) {
|
|
6753
6858
|
case "clear":
|
|
6754
6859
|
continue;
|
|
6755
6860
|
case "clip":
|
|
6756
|
-
clips.push(intersectClip(
|
|
6861
|
+
clips.push(intersectClip(getCurrentClip(), operation.clip));
|
|
6757
6862
|
continue;
|
|
6758
6863
|
case "unclip":
|
|
6759
6864
|
clips.pop();
|
|
@@ -6766,7 +6871,7 @@ var Output = class {
|
|
|
6766
6871
|
width: regionWidth,
|
|
6767
6872
|
height: regionHeight
|
|
6768
6873
|
} = operation;
|
|
6769
|
-
const clip =
|
|
6874
|
+
const clip = getCurrentClip();
|
|
6770
6875
|
const startX = Math.max(regionX, clip?.x1 ?? 0);
|
|
6771
6876
|
const startY = Math.max(regionY, clip?.y1 ?? 0);
|
|
6772
6877
|
const maxY = Math.min(
|
|
@@ -6812,7 +6917,7 @@ var Output = class {
|
|
|
6812
6917
|
let lines = text.split("\n");
|
|
6813
6918
|
let swFrom = 0;
|
|
6814
6919
|
let prevContentEnd = 0;
|
|
6815
|
-
const clip =
|
|
6920
|
+
const clip = getCurrentClip();
|
|
6816
6921
|
if (clip) {
|
|
6817
6922
|
const clipHorizontally = typeof clip?.x1 === "number" && typeof clip?.x2 === "number";
|
|
6818
6923
|
const clipVertically = typeof clip?.y1 === "number" && typeof clip?.y2 === "number";
|
|
@@ -7935,8 +8040,7 @@ function dropSubtreeCache(node) {
|
|
|
7935
8040
|
var render_node_to_output_default = renderNodeToOutput;
|
|
7936
8041
|
|
|
7937
8042
|
// src/render-to-screen.ts
|
|
7938
|
-
import
|
|
7939
|
-
import { LegacyRoot } from "react-reconciler/constants";
|
|
8043
|
+
import { LegacyRoot } from "react-reconciler/constants.js";
|
|
7940
8044
|
import { logForDebugging as logForDebugging5 } from "@claude-code-kit/shared";
|
|
7941
8045
|
var timing = { reconcile: 0, yoga: 0, paint: 0, scan: 0, calls: 0 };
|
|
7942
8046
|
function scanPositions(screen, query) {
|
|
@@ -8379,13 +8483,13 @@ var Ink = class {
|
|
|
8379
8483
|
false,
|
|
8380
8484
|
null,
|
|
8381
8485
|
"id",
|
|
8382
|
-
|
|
8486
|
+
noop,
|
|
8383
8487
|
// onUncaughtError
|
|
8384
|
-
|
|
8488
|
+
noop,
|
|
8385
8489
|
// onCaughtError
|
|
8386
|
-
|
|
8490
|
+
noop,
|
|
8387
8491
|
// onRecoverableError
|
|
8388
|
-
|
|
8492
|
+
noop
|
|
8389
8493
|
// onDefaultTransitionIndicator
|
|
8390
8494
|
);
|
|
8391
8495
|
if (false) {
|
|
@@ -9176,7 +9280,7 @@ var Ink = class {
|
|
|
9176
9280
|
render(node) {
|
|
9177
9281
|
this.currentNode = node;
|
|
9178
9282
|
const tree = /* @__PURE__ */ jsx7(App, { stdin: this.options.stdin, stdout: this.options.stdout, stderr: this.options.stderr, exitOnCtrlC: this.options.exitOnCtrlC, onExit: this.unmount, terminalColumns: this.terminalColumns, terminalRows: this.terminalRows, selection: this.selection, onSelectionChange: this.notifySelectionChange, onClickAt: this.dispatchClick, onHoverAt: this.dispatchHover, getHyperlinkAt: this.getHyperlinkAt, onOpenHyperlink: this.openHyperlink, onMultiClick: this.handleMultiClick, onSelectionDrag: this.handleSelectionDrag, onStdinResume: this.reassertTerminalModes, onCursorDeclaration: this.setCursorDeclaration, dispatchKeyboardEvent: this.dispatchKeyboardEvent, children: /* @__PURE__ */ jsx7(TerminalWriteProvider, { value: this.writeRaw, children: node }) });
|
|
9179
|
-
reconciler_default.updateContainerSync(tree, this.container, null,
|
|
9283
|
+
reconciler_default.updateContainerSync(tree, this.container, null, noop);
|
|
9180
9284
|
reconciler_default.flushSyncWork();
|
|
9181
9285
|
}
|
|
9182
9286
|
unmount(error) {
|
|
@@ -9212,7 +9316,7 @@ var Ink = class {
|
|
|
9212
9316
|
clearTimeout(this.drainTimer);
|
|
9213
9317
|
this.drainTimer = null;
|
|
9214
9318
|
}
|
|
9215
|
-
reconciler_default.updateContainerSync(null, this.container, null,
|
|
9319
|
+
reconciler_default.updateContainerSync(null, this.container, null, noop);
|
|
9216
9320
|
reconciler_default.flushSyncWork();
|
|
9217
9321
|
instances_default.delete(this.options.stdout);
|
|
9218
9322
|
this.rootNode.yogaNode?.free();
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-code-kit/ink-renderer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Terminal rendering engine extracted from Claude Code — React reconciler + Yoga layout + TTY output",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"module": "./dist/index.mjs",
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
9
|
"files": [
|
|
10
|
-
"dist"
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md"
|
|
11
12
|
],
|
|
12
13
|
"exports": {
|
|
13
14
|
".": {
|
|
@@ -26,7 +27,6 @@
|
|
|
26
27
|
"emoji-regex": "*",
|
|
27
28
|
"get-east-asian-width": "*",
|
|
28
29
|
"indent-string": "*",
|
|
29
|
-
"lodash-es": "*",
|
|
30
30
|
"semver": "*",
|
|
31
31
|
"signal-exit": "*",
|
|
32
32
|
"stack-utils": "*",
|
|
@@ -35,13 +35,14 @@
|
|
|
35
35
|
"type-fest": "*",
|
|
36
36
|
"usehooks-ts": "*",
|
|
37
37
|
"wrap-ansi": "*",
|
|
38
|
-
"@claude-code-kit/shared": "0.
|
|
38
|
+
"@claude-code-kit/shared": "0.2.0"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"react": ">=18.0.0",
|
|
42
42
|
"react-reconciler": ">=0.29.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
+
"@types/react-reconciler": "0.33.0",
|
|
45
46
|
"react": "*",
|
|
46
47
|
"react-reconciler": "*",
|
|
47
48
|
"tsup": "*",
|
|
@@ -70,6 +71,8 @@
|
|
|
70
71
|
"access": "public"
|
|
71
72
|
},
|
|
72
73
|
"scripts": {
|
|
73
|
-
"build": "tsup"
|
|
74
|
+
"build": "tsup",
|
|
75
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
76
|
+
"lint": "biome check src/"
|
|
74
77
|
}
|
|
75
78
|
}
|