@nextclaw/nextclaw-engine-codex-sdk 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 +35 -0
- package/codex-sdk-loader.cjs +17 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +308 -0
- package/openclaw.plugin.json +11 -0
- package/package.json +42 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 NextClaw contributors
|
|
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,35 @@
|
|
|
1
|
+
# @nextclaw/nextclaw-engine-codex-sdk
|
|
2
|
+
|
|
3
|
+
Independent NextClaw engine plugin that registers `codex-sdk` using OpenAI official `@openai/codex-sdk`.
|
|
4
|
+
|
|
5
|
+
## Build
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm -C packages/extensions/nextclaw-engine-plugin-codex-sdk build
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage (local path)
|
|
12
|
+
|
|
13
|
+
Add plugin load path to config:
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"plugins": {
|
|
18
|
+
"load": {
|
|
19
|
+
"paths": [
|
|
20
|
+
"/absolute/path/to/packages/extensions/nextclaw-engine-plugin-codex-sdk"
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"agents": {
|
|
25
|
+
"defaults": {
|
|
26
|
+
"engine": "codex-sdk",
|
|
27
|
+
"model": "openai/gpt-5-codex",
|
|
28
|
+
"engineConfig": {
|
|
29
|
+
"apiBase": "https://your-relay.example.com/v1",
|
|
30
|
+
"apiKey": "sk-xxx"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
let cachedCtor = null;
|
|
2
|
+
|
|
3
|
+
async function loadCodexConstructor() {
|
|
4
|
+
if (cachedCtor) {
|
|
5
|
+
return cachedCtor;
|
|
6
|
+
}
|
|
7
|
+
const mod = await import("@openai/codex-sdk");
|
|
8
|
+
if (!mod || typeof mod.Codex !== "function") {
|
|
9
|
+
throw new Error("[codex-sdk] failed to load Codex constructor from @openai/codex-sdk");
|
|
10
|
+
}
|
|
11
|
+
cachedCtor = mod.Codex;
|
|
12
|
+
return cachedCtor;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = {
|
|
16
|
+
loadCodexConstructor
|
|
17
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { AgentEngineFactoryContext, AgentEngine } from '@nextclaw/core';
|
|
2
|
+
|
|
3
|
+
type PluginApi = {
|
|
4
|
+
registerEngine: (factory: (context: AgentEngineFactoryContext) => AgentEngine, opts?: {
|
|
5
|
+
kind?: string;
|
|
6
|
+
}) => void;
|
|
7
|
+
};
|
|
8
|
+
type PluginDefinition = {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
description: string;
|
|
12
|
+
configSchema: Record<string, unknown>;
|
|
13
|
+
register: (api: PluginApi) => void;
|
|
14
|
+
};
|
|
15
|
+
declare const plugin: PluginDefinition;
|
|
16
|
+
|
|
17
|
+
export { plugin as default };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import {
|
|
3
|
+
getApiBase,
|
|
4
|
+
getProvider,
|
|
5
|
+
SkillsLoader
|
|
6
|
+
} from "@nextclaw/core";
|
|
7
|
+
function readString(input, key) {
|
|
8
|
+
const value = input[key];
|
|
9
|
+
if (typeof value !== "string") {
|
|
10
|
+
return void 0;
|
|
11
|
+
}
|
|
12
|
+
const trimmed = value.trim();
|
|
13
|
+
return trimmed || void 0;
|
|
14
|
+
}
|
|
15
|
+
function readBoolean(input, key) {
|
|
16
|
+
const value = input[key];
|
|
17
|
+
if (typeof value === "boolean") {
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
return void 0;
|
|
21
|
+
}
|
|
22
|
+
function readStringRecord(input, key) {
|
|
23
|
+
const value = input[key];
|
|
24
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
25
|
+
return void 0;
|
|
26
|
+
}
|
|
27
|
+
const out = {};
|
|
28
|
+
for (const [entryKey, entryValue] of Object.entries(value)) {
|
|
29
|
+
if (typeof entryValue !== "string") {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const normalized = entryValue.trim();
|
|
33
|
+
if (!normalized) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
out[entryKey] = normalized;
|
|
37
|
+
}
|
|
38
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
39
|
+
}
|
|
40
|
+
function readRecord(input, key) {
|
|
41
|
+
const value = input[key];
|
|
42
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
43
|
+
return void 0;
|
|
44
|
+
}
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
function readStringArray(input, key) {
|
|
48
|
+
const value = input[key];
|
|
49
|
+
if (!Array.isArray(value)) {
|
|
50
|
+
return void 0;
|
|
51
|
+
}
|
|
52
|
+
const normalized = value.map((entry) => typeof entry === "string" ? entry.trim() : "").filter(Boolean);
|
|
53
|
+
return normalized.length > 0 ? normalized : void 0;
|
|
54
|
+
}
|
|
55
|
+
function readRequestedSkills(metadata) {
|
|
56
|
+
if (!metadata) {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
const raw = metadata.requested_skills ?? metadata.requestedSkills;
|
|
60
|
+
const values = [];
|
|
61
|
+
if (Array.isArray(raw)) {
|
|
62
|
+
for (const entry of raw) {
|
|
63
|
+
if (typeof entry !== "string") {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const trimmed = entry.trim();
|
|
67
|
+
if (trimmed) {
|
|
68
|
+
values.push(trimmed);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
} else if (typeof raw === "string") {
|
|
72
|
+
values.push(
|
|
73
|
+
...raw.split(/[,\s]+/g).map((entry) => entry.trim()).filter(Boolean)
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
return Array.from(new Set(values)).slice(0, 8);
|
|
77
|
+
}
|
|
78
|
+
function readReasoningEffort(input, key) {
|
|
79
|
+
const value = readString(input, key);
|
|
80
|
+
if (value === "minimal" || value === "low" || value === "medium" || value === "high" || value === "xhigh") {
|
|
81
|
+
return value;
|
|
82
|
+
}
|
|
83
|
+
return void 0;
|
|
84
|
+
}
|
|
85
|
+
function readSandboxMode(input, key) {
|
|
86
|
+
const value = readString(input, key);
|
|
87
|
+
if (value === "read-only" || value === "workspace-write" || value === "danger-full-access") {
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
return void 0;
|
|
91
|
+
}
|
|
92
|
+
function readApprovalPolicy(input, key) {
|
|
93
|
+
const value = readString(input, key);
|
|
94
|
+
if (value === "never" || value === "on-request" || value === "on-failure" || value === "untrusted") {
|
|
95
|
+
return value;
|
|
96
|
+
}
|
|
97
|
+
return void 0;
|
|
98
|
+
}
|
|
99
|
+
function readWebSearchMode(input, key) {
|
|
100
|
+
const value = readString(input, key);
|
|
101
|
+
if (value === "disabled" || value === "cached" || value === "live") {
|
|
102
|
+
return value;
|
|
103
|
+
}
|
|
104
|
+
return void 0;
|
|
105
|
+
}
|
|
106
|
+
function resolveEngineConfig(config, model, engineConfig) {
|
|
107
|
+
const provider = getProvider(config, model);
|
|
108
|
+
const apiKey = readString(engineConfig, "apiKey") ?? provider?.apiKey ?? void 0;
|
|
109
|
+
const apiBase = readString(engineConfig, "apiBase") ?? getApiBase(config, model) ?? void 0;
|
|
110
|
+
return { apiKey, apiBase };
|
|
111
|
+
}
|
|
112
|
+
const require2 = createRequire(import.meta.url);
|
|
113
|
+
const codexLoader = require2("../codex-sdk-loader.cjs");
|
|
114
|
+
class PluginCodexSdkEngine {
|
|
115
|
+
constructor(options) {
|
|
116
|
+
this.options = options;
|
|
117
|
+
this.defaultModel = options.model;
|
|
118
|
+
this.threadOptions = options.threadOptions;
|
|
119
|
+
this.skillsLoader = new SkillsLoader(options.workspace);
|
|
120
|
+
}
|
|
121
|
+
kind = "codex-sdk";
|
|
122
|
+
codexPromise = null;
|
|
123
|
+
threads = /* @__PURE__ */ new Map();
|
|
124
|
+
defaultModel;
|
|
125
|
+
threadOptions;
|
|
126
|
+
skillsLoader;
|
|
127
|
+
async handleInbound(params) {
|
|
128
|
+
const reply = await this.processDirect({
|
|
129
|
+
content: params.message.content,
|
|
130
|
+
sessionKey: params.sessionKey,
|
|
131
|
+
channel: params.message.channel,
|
|
132
|
+
chatId: params.message.chatId,
|
|
133
|
+
metadata: params.message.metadata
|
|
134
|
+
});
|
|
135
|
+
if (!reply.trim()) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
const outbound = {
|
|
139
|
+
channel: params.message.channel,
|
|
140
|
+
chatId: params.message.chatId,
|
|
141
|
+
content: reply,
|
|
142
|
+
media: [],
|
|
143
|
+
metadata: {}
|
|
144
|
+
};
|
|
145
|
+
if (params.publishResponse ?? true) {
|
|
146
|
+
await this.options.bus.publishOutbound(outbound);
|
|
147
|
+
}
|
|
148
|
+
return outbound;
|
|
149
|
+
}
|
|
150
|
+
async processDirect(params) {
|
|
151
|
+
const sessionKey = typeof params.sessionKey === "string" && params.sessionKey.trim() ? params.sessionKey : "cli:direct";
|
|
152
|
+
const channel = typeof params.channel === "string" && params.channel.trim() ? params.channel : "cli";
|
|
153
|
+
const chatId = typeof params.chatId === "string" && params.chatId.trim() ? params.chatId : "direct";
|
|
154
|
+
const model = readString(params.metadata ?? {}, "model") ?? this.defaultModel;
|
|
155
|
+
const requestedSkills = readRequestedSkills(params.metadata ?? {});
|
|
156
|
+
const session = this.options.sessionManager.getOrCreate(sessionKey);
|
|
157
|
+
const userExtra = { channel, chatId };
|
|
158
|
+
if (requestedSkills.length > 0) {
|
|
159
|
+
userExtra.requested_skills = requestedSkills;
|
|
160
|
+
}
|
|
161
|
+
const userEvent = this.options.sessionManager.addMessage(session, "user", params.content, userExtra);
|
|
162
|
+
params.onSessionEvent?.(userEvent);
|
|
163
|
+
const requestedSkillsContent = this.skillsLoader.loadSkillsForContext(requestedSkills);
|
|
164
|
+
const prompt = requestedSkillsContent.trim().length > 0 ? [
|
|
165
|
+
"## Requested Skills",
|
|
166
|
+
"Use the following selected skills for this turn:",
|
|
167
|
+
requestedSkillsContent,
|
|
168
|
+
"## User Message",
|
|
169
|
+
params.content
|
|
170
|
+
].join("\n\n") : params.content;
|
|
171
|
+
const thread = await this.resolveThread(sessionKey, model);
|
|
172
|
+
const streamed = await thread.runStreamed(prompt);
|
|
173
|
+
const itemTextById = /* @__PURE__ */ new Map();
|
|
174
|
+
const completedAgentMessages = [];
|
|
175
|
+
for await (const event of streamed.events) {
|
|
176
|
+
const streamEvent = this.options.sessionManager.appendEvent(session, {
|
|
177
|
+
type: `engine.codex.${event.type}`,
|
|
178
|
+
data: { event },
|
|
179
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
180
|
+
});
|
|
181
|
+
params.onSessionEvent?.(streamEvent);
|
|
182
|
+
this.emitAssistantDelta(event, itemTextById, params.onAssistantDelta);
|
|
183
|
+
if (event.type === "item.completed" && event.item.type === "agent_message") {
|
|
184
|
+
const text = event.item.text.trim();
|
|
185
|
+
if (text) {
|
|
186
|
+
completedAgentMessages.push(text);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (event.type === "turn.failed") {
|
|
190
|
+
throw new Error(event.error.message);
|
|
191
|
+
}
|
|
192
|
+
if (event.type === "error") {
|
|
193
|
+
throw new Error(event.message);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const reply = completedAgentMessages.join("\n").trim();
|
|
197
|
+
const assistantEvent = this.options.sessionManager.addMessage(session, "assistant", reply, {
|
|
198
|
+
channel,
|
|
199
|
+
chatId
|
|
200
|
+
});
|
|
201
|
+
params.onSessionEvent?.(assistantEvent);
|
|
202
|
+
this.options.sessionManager.save(session);
|
|
203
|
+
return reply;
|
|
204
|
+
}
|
|
205
|
+
applyRuntimeConfig(_config) {
|
|
206
|
+
}
|
|
207
|
+
async getCodex() {
|
|
208
|
+
if (!this.codexPromise) {
|
|
209
|
+
this.codexPromise = codexLoader.loadCodexConstructor().then(
|
|
210
|
+
(Ctor) => new Ctor({
|
|
211
|
+
apiKey: this.options.apiKey,
|
|
212
|
+
baseUrl: this.options.apiBase,
|
|
213
|
+
...this.options.codexPathOverride ? { codexPathOverride: this.options.codexPathOverride } : {},
|
|
214
|
+
...this.options.env ? { env: this.options.env } : {},
|
|
215
|
+
...this.options.cliConfig ? { config: this.options.cliConfig } : {}
|
|
216
|
+
})
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
return this.codexPromise;
|
|
220
|
+
}
|
|
221
|
+
async resolveThread(sessionKey, model) {
|
|
222
|
+
const cached = this.threads.get(sessionKey);
|
|
223
|
+
if (cached) {
|
|
224
|
+
return cached;
|
|
225
|
+
}
|
|
226
|
+
const codex = await this.getCodex();
|
|
227
|
+
const thread = codex.startThread({
|
|
228
|
+
...this.threadOptions,
|
|
229
|
+
model
|
|
230
|
+
});
|
|
231
|
+
this.threads.set(sessionKey, thread);
|
|
232
|
+
return thread;
|
|
233
|
+
}
|
|
234
|
+
emitAssistantDelta(event, itemTextById, onAssistantDelta) {
|
|
235
|
+
if (!onAssistantDelta) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (event.type !== "item.updated" && event.type !== "item.completed") {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (event.item.type !== "agent_message") {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const current = event.item.text ?? "";
|
|
245
|
+
const previous = itemTextById.get(event.item.id) ?? "";
|
|
246
|
+
if (current.length <= previous.length) {
|
|
247
|
+
itemTextById.set(event.item.id, current);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
const delta = current.slice(previous.length);
|
|
251
|
+
if (delta) {
|
|
252
|
+
onAssistantDelta(delta);
|
|
253
|
+
}
|
|
254
|
+
itemTextById.set(event.item.id, current);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
const plugin = {
|
|
258
|
+
id: "nextclaw-engine-codex-sdk",
|
|
259
|
+
name: "NextClaw Codex SDK Engine",
|
|
260
|
+
description: "Registers engine kind `codex-sdk` backed by OpenAI Codex SDK.",
|
|
261
|
+
configSchema: {
|
|
262
|
+
type: "object",
|
|
263
|
+
additionalProperties: true,
|
|
264
|
+
properties: {}
|
|
265
|
+
},
|
|
266
|
+
register(api) {
|
|
267
|
+
api.registerEngine(
|
|
268
|
+
(context) => {
|
|
269
|
+
const engineConfig = context.engineConfig ?? {};
|
|
270
|
+
const model = readString(engineConfig, "model") ?? context.model;
|
|
271
|
+
const resolved = resolveEngineConfig(context.config, model, engineConfig);
|
|
272
|
+
if (!resolved.apiKey) {
|
|
273
|
+
throw new Error(
|
|
274
|
+
`[codex-sdk] missing apiKey. Set agents.defaults.engineConfig.apiKey or providers.*.apiKey for model "${model}".`
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
return new PluginCodexSdkEngine({
|
|
278
|
+
bus: context.bus,
|
|
279
|
+
sessionManager: context.sessionManager,
|
|
280
|
+
model,
|
|
281
|
+
workspace: context.workspace,
|
|
282
|
+
apiKey: resolved.apiKey,
|
|
283
|
+
apiBase: resolved.apiBase,
|
|
284
|
+
codexPathOverride: readString(engineConfig, "codexPathOverride"),
|
|
285
|
+
env: readStringRecord(engineConfig, "env"),
|
|
286
|
+
cliConfig: readRecord(engineConfig, "config"),
|
|
287
|
+
threadOptions: {
|
|
288
|
+
model,
|
|
289
|
+
sandboxMode: readSandboxMode(engineConfig, "sandboxMode"),
|
|
290
|
+
workingDirectory: readString(engineConfig, "workingDirectory") ?? context.workspace,
|
|
291
|
+
skipGitRepoCheck: readBoolean(engineConfig, "skipGitRepoCheck"),
|
|
292
|
+
modelReasoningEffort: readReasoningEffort(engineConfig, "modelReasoningEffort"),
|
|
293
|
+
networkAccessEnabled: readBoolean(engineConfig, "networkAccessEnabled"),
|
|
294
|
+
webSearchMode: readWebSearchMode(engineConfig, "webSearchMode"),
|
|
295
|
+
webSearchEnabled: readBoolean(engineConfig, "webSearchEnabled"),
|
|
296
|
+
approvalPolicy: readApprovalPolicy(engineConfig, "approvalPolicy"),
|
|
297
|
+
additionalDirectories: readStringArray(engineConfig, "additionalDirectories")
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
},
|
|
301
|
+
{ kind: "codex-sdk" }
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
var index_default = plugin;
|
|
306
|
+
export {
|
|
307
|
+
index_default as default
|
|
308
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "nextclaw-engine-codex-sdk",
|
|
3
|
+
"name": "NextClaw Codex SDK Engine",
|
|
4
|
+
"description": "Registers engine kind `codex-sdk` backed by OpenAI Codex SDK.",
|
|
5
|
+
"version": "0.1.0",
|
|
6
|
+
"configSchema": {
|
|
7
|
+
"type": "object",
|
|
8
|
+
"additionalProperties": true,
|
|
9
|
+
"properties": {}
|
|
10
|
+
}
|
|
11
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nextclaw/nextclaw-engine-codex-sdk",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "NextClaw Codex engine plugin backed by OpenAI Codex SDK.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"codex-sdk-loader.cjs",
|
|
16
|
+
"openclaw.plugin.json",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"openclaw": {
|
|
20
|
+
"extensions": [
|
|
21
|
+
"dist/index.js"
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@nextclaw/core": "^0.7.0",
|
|
26
|
+
"@openai/codex-sdk": "^0.107.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^20.17.6",
|
|
30
|
+
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
|
31
|
+
"@typescript-eslint/parser": "^7.18.0",
|
|
32
|
+
"eslint": "^8.57.1",
|
|
33
|
+
"eslint-config-prettier": "^9.1.0",
|
|
34
|
+
"tsup": "^8.3.5",
|
|
35
|
+
"typescript": "^5.6.3"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsup --config tsup.config.ts",
|
|
39
|
+
"lint": "eslint .",
|
|
40
|
+
"tsc": "tsc -p tsconfig.json"
|
|
41
|
+
}
|
|
42
|
+
}
|