@hasna/hook-knowledge-context 0.1.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 +53 -0
- package/dist/hook.d.ts +56 -0
- package/dist/hook.js +273 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# hook-knowledge-context
|
|
2
|
+
|
|
3
|
+
Codewith lifecycle hook that injects deterministic Knowledge context packs.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
hooks install knowledge-context --target codewith
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
By default this prints a Codewith TOML fragment and does not mutate managed
|
|
12
|
+
Codewith config. Apply that fragment through `open-configs` or the managed
|
|
13
|
+
config renderer. For explicit local testing only:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
hooks install knowledge-context --target codewith --apply-codewith --codewith-config /tmp/codewith-config.toml
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## How it works
|
|
20
|
+
|
|
21
|
+
On `SessionStart`, `UserPromptSubmit`, and `SubagentStart`, the hook builds a
|
|
22
|
+
small redacted query from the hook input and runs:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
knowledge context pack <query> --from search --max-items <n> --max-tokens <n> --json
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The hook intentionally does not pass `--semantic`, does not call web search,
|
|
29
|
+
does not call ask/build/generate flows, and does not crawl raw stores directly.
|
|
30
|
+
When Knowledge returns a nonempty context pack, the hook redacts credential-like
|
|
31
|
+
text from that pack and emits Codewith-native
|
|
32
|
+
`hookSpecificOutput.additionalContext` with the same event name.
|
|
33
|
+
|
|
34
|
+
All failures are fail-open: missing CLI, timeout, nonzero exit, malformed JSON,
|
|
35
|
+
bad stdin, or empty packs return `{"continue":true}` without context.
|
|
36
|
+
|
|
37
|
+
## Configuration
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
export HOOKS_KNOWLEDGE_CONTEXT_DISABLE=1 # Kill switch
|
|
41
|
+
export HOOKS_KNOWLEDGE_COMMAND=knowledge # CLI command/path
|
|
42
|
+
export HOOKS_KNOWLEDGE_TIMEOUT_MS=1500 # Per-call timeout
|
|
43
|
+
export HOOKS_KNOWLEDGE_MAX_ITEMS=6 # Context pack item budget
|
|
44
|
+
export HOOKS_KNOWLEDGE_MAX_TOKENS=1200 # Context pack token budget
|
|
45
|
+
export HOOKS_KNOWLEDGE_MAX_QUERY_CHARS=1200 # Redacted query bound
|
|
46
|
+
export HOOKS_KNOWLEDGE_MAX_OUTPUT_CHARS=8000
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Events
|
|
50
|
+
|
|
51
|
+
- `SessionStart`
|
|
52
|
+
- `UserPromptSubmit`
|
|
53
|
+
- `SubagentStart`
|
package/dist/hook.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Codewith Hook: knowledge-context
|
|
4
|
+
*
|
|
5
|
+
* Deterministically injects bounded Knowledge context into Codewith lifecycle
|
|
6
|
+
* events. This hook never calls LLM providers, semantic search, web search, or
|
|
7
|
+
* raw stores directly; it delegates to `knowledge context pack --from search`.
|
|
8
|
+
*/
|
|
9
|
+
export type KnowledgeContextEvent = "SessionStart" | "UserPromptSubmit" | "SubagentStart";
|
|
10
|
+
interface HookInput {
|
|
11
|
+
session_id?: string;
|
|
12
|
+
cwd?: string;
|
|
13
|
+
hook_event_name?: string;
|
|
14
|
+
model?: string;
|
|
15
|
+
permission_mode?: string;
|
|
16
|
+
source?: string;
|
|
17
|
+
prompt?: string;
|
|
18
|
+
user_prompt?: string;
|
|
19
|
+
agent_id?: string;
|
|
20
|
+
agent_type?: string;
|
|
21
|
+
turn_id?: string;
|
|
22
|
+
}
|
|
23
|
+
interface HookOutput {
|
|
24
|
+
continue: boolean;
|
|
25
|
+
hookSpecificOutput?: {
|
|
26
|
+
hookEventName: KnowledgeContextEvent;
|
|
27
|
+
additionalContext: string;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export interface KnowledgeContextConfig {
|
|
31
|
+
command: string;
|
|
32
|
+
timeoutMs: number;
|
|
33
|
+
maxItems: number;
|
|
34
|
+
maxTokens: number;
|
|
35
|
+
maxQueryChars: number;
|
|
36
|
+
maxOutputChars: number;
|
|
37
|
+
}
|
|
38
|
+
export interface KnowledgeExecResult {
|
|
39
|
+
ok: boolean;
|
|
40
|
+
stdout?: string;
|
|
41
|
+
timedOut?: boolean;
|
|
42
|
+
}
|
|
43
|
+
export type KnowledgeExecutor = (query: string, config: KnowledgeContextConfig) => Promise<KnowledgeExecResult>;
|
|
44
|
+
export declare function getConfig(env?: NodeJS.ProcessEnv): KnowledgeContextConfig;
|
|
45
|
+
export declare function isKnowledgeContextEvent(value: string | undefined): value is KnowledgeContextEvent;
|
|
46
|
+
export declare function redactSecrets(text: string): string;
|
|
47
|
+
export declare function sanitizeQuery(text: string, maxChars: number): string;
|
|
48
|
+
export declare function buildQuery(input: HookInput, config: KnowledgeContextConfig): string | null;
|
|
49
|
+
export declare function buildKnowledgeArgs(query: string, config: KnowledgeContextConfig): string[];
|
|
50
|
+
export declare function spawnKnowledgeContextPack(query: string, config: KnowledgeContextConfig): Promise<KnowledgeExecResult>;
|
|
51
|
+
export declare function truncate(text: string, max: number): string;
|
|
52
|
+
export declare function extractContextText(pack: unknown): string | null;
|
|
53
|
+
export declare function formatAdditionalContext(event: KnowledgeContextEvent, context: string, config: KnowledgeContextConfig): string;
|
|
54
|
+
export declare function buildHookOutput(input: HookInput | null, executor?: KnowledgeExecutor, env?: NodeJS.ProcessEnv): Promise<HookOutput>;
|
|
55
|
+
export declare function run(): Promise<void>;
|
|
56
|
+
export {};
|
package/dist/hook.js
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
|
|
4
|
+
// src/hook.ts
|
|
5
|
+
import { readFileSync } from "fs";
|
|
6
|
+
import { spawn } from "child_process";
|
|
7
|
+
var DEFAULT_TIMEOUT_MS = 1500;
|
|
8
|
+
var DEFAULT_MAX_ITEMS = 6;
|
|
9
|
+
var DEFAULT_MAX_TOKENS = 1200;
|
|
10
|
+
var DEFAULT_MAX_QUERY_CHARS = 1200;
|
|
11
|
+
var DEFAULT_MAX_OUTPUT_CHARS = 8000;
|
|
12
|
+
var SUPPORTED_EVENTS = new Set([
|
|
13
|
+
"SessionStart",
|
|
14
|
+
"UserPromptSubmit",
|
|
15
|
+
"SubagentStart"
|
|
16
|
+
]);
|
|
17
|
+
function readStdinJson() {
|
|
18
|
+
try {
|
|
19
|
+
const input = readFileSync(0, "utf-8").trim();
|
|
20
|
+
if (!input)
|
|
21
|
+
return null;
|
|
22
|
+
return JSON.parse(input);
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function respond(output) {
|
|
28
|
+
console.log(JSON.stringify(output));
|
|
29
|
+
}
|
|
30
|
+
function boundedNumber(raw, fallback, min, max) {
|
|
31
|
+
const value = Number(raw);
|
|
32
|
+
if (!Number.isFinite(value))
|
|
33
|
+
return fallback;
|
|
34
|
+
return Math.min(max, Math.max(min, Math.floor(value)));
|
|
35
|
+
}
|
|
36
|
+
function commandFromEnv(raw) {
|
|
37
|
+
if (raw && /^[^\s\0]+$/.test(raw))
|
|
38
|
+
return raw;
|
|
39
|
+
return "knowledge";
|
|
40
|
+
}
|
|
41
|
+
function getConfig(env = process.env) {
|
|
42
|
+
return {
|
|
43
|
+
command: commandFromEnv(env.HOOKS_KNOWLEDGE_COMMAND),
|
|
44
|
+
timeoutMs: boundedNumber(env.HOOKS_KNOWLEDGE_TIMEOUT_MS, DEFAULT_TIMEOUT_MS, 100, 1e4),
|
|
45
|
+
maxItems: boundedNumber(env.HOOKS_KNOWLEDGE_MAX_ITEMS, DEFAULT_MAX_ITEMS, 1, 20),
|
|
46
|
+
maxTokens: boundedNumber(env.HOOKS_KNOWLEDGE_MAX_TOKENS, DEFAULT_MAX_TOKENS, 100, 8000),
|
|
47
|
+
maxQueryChars: boundedNumber(env.HOOKS_KNOWLEDGE_MAX_QUERY_CHARS, DEFAULT_MAX_QUERY_CHARS, 80, 4000),
|
|
48
|
+
maxOutputChars: boundedNumber(env.HOOKS_KNOWLEDGE_MAX_OUTPUT_CHARS, DEFAULT_MAX_OUTPUT_CHARS, 500, 20000)
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function isKnowledgeContextEvent(value) {
|
|
52
|
+
return SUPPORTED_EVENTS.has(value);
|
|
53
|
+
}
|
|
54
|
+
function stringField(input, key) {
|
|
55
|
+
const value = input[key];
|
|
56
|
+
return typeof value === "string" ? value : "";
|
|
57
|
+
}
|
|
58
|
+
function redactSecrets(text) {
|
|
59
|
+
let redacted = text;
|
|
60
|
+
redacted = redacted.replace(/\b(api[_-]?key|token|secret|password|passwd|pwd)\b\s*[:=]\s*[^,\s]+/gi, (_match, key) => `${key}=<redacted>`);
|
|
61
|
+
redacted = redacted.replace(/\bsk-[A-Za-z0-9_-]{16,}\b/g, "<redacted>");
|
|
62
|
+
redacted = redacted.replace(/\bgh[pousr]_[A-Za-z0-9_]{20,}\b/g, "<redacted>");
|
|
63
|
+
redacted = redacted.replace(/\bxox[baprs]-[A-Za-z0-9-]{20,}\b/g, "<redacted>");
|
|
64
|
+
redacted = redacted.replace(/\bAKIA[0-9A-Z]{16}\b/g, "<redacted>");
|
|
65
|
+
redacted = redacted.replace(/\bAIza[0-9A-Za-z_-]{20,}\b/g, "<redacted>");
|
|
66
|
+
redacted = redacted.replace(/\b[A-Za-z0-9+/]{48,}={0,2}\b/g, "<redacted>");
|
|
67
|
+
return redacted;
|
|
68
|
+
}
|
|
69
|
+
function sanitizeQuery(text, maxChars) {
|
|
70
|
+
const normalized = text.replace(/[\u0000-\u001f\u007f]/g, " ").replace(/\s+/g, " ").trim();
|
|
71
|
+
const redacted = redactSecrets(normalized);
|
|
72
|
+
return redacted.length > maxChars ? redacted.slice(0, maxChars).trim() : redacted;
|
|
73
|
+
}
|
|
74
|
+
function buildQuery(input, config) {
|
|
75
|
+
const cwd = stringField(input, "cwd");
|
|
76
|
+
const event = stringField(input, "hook_event_name");
|
|
77
|
+
if (event === "UserPromptSubmit") {
|
|
78
|
+
const prompt = stringField(input, "prompt") || stringField(input, "user_prompt");
|
|
79
|
+
return sanitizeQuery(`Codewith user prompt context. cwd: ${cwd}. prompt: ${prompt}`, config.maxQueryChars);
|
|
80
|
+
}
|
|
81
|
+
if (event === "SubagentStart") {
|
|
82
|
+
const agentType = stringField(input, "agent_type") || "subagent";
|
|
83
|
+
const turnId = stringField(input, "turn_id");
|
|
84
|
+
return sanitizeQuery(`Codewith subagent start context. cwd: ${cwd}. agent type: ${agentType}. turn: ${turnId}`, config.maxQueryChars);
|
|
85
|
+
}
|
|
86
|
+
if (event === "SessionStart") {
|
|
87
|
+
const source = stringField(input, "source") || "startup";
|
|
88
|
+
const model = stringField(input, "model");
|
|
89
|
+
return sanitizeQuery(`Codewith session start context. cwd: ${cwd}. source: ${source}. model: ${model}`, config.maxQueryChars);
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
function buildKnowledgeArgs(query, config) {
|
|
94
|
+
return [
|
|
95
|
+
"context",
|
|
96
|
+
"pack",
|
|
97
|
+
query,
|
|
98
|
+
"--from",
|
|
99
|
+
"search",
|
|
100
|
+
"--max-items",
|
|
101
|
+
String(config.maxItems),
|
|
102
|
+
"--max-tokens",
|
|
103
|
+
String(config.maxTokens),
|
|
104
|
+
"--json"
|
|
105
|
+
];
|
|
106
|
+
}
|
|
107
|
+
function spawnKnowledgeContextPack(query, config) {
|
|
108
|
+
const args = buildKnowledgeArgs(query, config);
|
|
109
|
+
return new Promise((resolve) => {
|
|
110
|
+
let stdout = "";
|
|
111
|
+
let settled = false;
|
|
112
|
+
let timedOut = false;
|
|
113
|
+
let timer;
|
|
114
|
+
const settle = (result) => {
|
|
115
|
+
if (settled)
|
|
116
|
+
return;
|
|
117
|
+
settled = true;
|
|
118
|
+
if (timer)
|
|
119
|
+
clearTimeout(timer);
|
|
120
|
+
resolve(result);
|
|
121
|
+
};
|
|
122
|
+
const child = spawn(config.command, args, {
|
|
123
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
124
|
+
env: process.env
|
|
125
|
+
});
|
|
126
|
+
timer = setTimeout(() => {
|
|
127
|
+
timedOut = true;
|
|
128
|
+
try {
|
|
129
|
+
child.kill("SIGTERM");
|
|
130
|
+
} catch {}
|
|
131
|
+
}, config.timeoutMs);
|
|
132
|
+
child.stdout.setEncoding("utf-8");
|
|
133
|
+
child.stdout.on("data", (chunk) => {
|
|
134
|
+
stdout += chunk;
|
|
135
|
+
if (stdout.length > config.maxOutputChars * 2) {
|
|
136
|
+
stdout = stdout.slice(0, config.maxOutputChars * 2);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
child.on("error", () => settle({ ok: false, timedOut }));
|
|
140
|
+
child.on("close", (code) => settle({ ok: code === 0 && !timedOut, stdout, timedOut }));
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
function firstString(obj, keys) {
|
|
144
|
+
for (const key of keys) {
|
|
145
|
+
const value = obj[key];
|
|
146
|
+
if (typeof value === "string" && value.trim())
|
|
147
|
+
return value.trim();
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
function firstArray(obj, keys) {
|
|
152
|
+
for (const key of keys) {
|
|
153
|
+
const value = obj[key];
|
|
154
|
+
if (Array.isArray(value))
|
|
155
|
+
return value;
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
function truncate(text, max) {
|
|
160
|
+
if (text.length <= max)
|
|
161
|
+
return text;
|
|
162
|
+
if (max <= 3)
|
|
163
|
+
return text.slice(0, max);
|
|
164
|
+
return `${text.slice(0, max - 3)}...`;
|
|
165
|
+
}
|
|
166
|
+
function formatPackItems(items) {
|
|
167
|
+
const lines = [];
|
|
168
|
+
for (const [index, item] of items.entries()) {
|
|
169
|
+
if (typeof item === "string" && item.trim()) {
|
|
170
|
+
lines.push(`- ${truncate(item.trim(), 900)}`);
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (!item || typeof item !== "object")
|
|
174
|
+
continue;
|
|
175
|
+
const obj = item;
|
|
176
|
+
const title = firstString(obj, ["title", "name", "id", "ref", "uri"]) || `item ${index + 1}`;
|
|
177
|
+
const body = firstString(obj, ["summary", "text", "content", "snippet", "preview", "body"]);
|
|
178
|
+
const source = firstString(obj, ["source", "uri", "ref", "url", "citation"]);
|
|
179
|
+
if (!body && !source)
|
|
180
|
+
continue;
|
|
181
|
+
const suffix = source ? ` (source: ${truncate(source, 180)})` : "";
|
|
182
|
+
lines.push(`- ${truncate(title, 160)}${suffix}${body ? `: ${truncate(body, 900)}` : ""}`);
|
|
183
|
+
}
|
|
184
|
+
return lines.length > 0 ? lines.join(`
|
|
185
|
+
`) : null;
|
|
186
|
+
}
|
|
187
|
+
function extractContextText(pack) {
|
|
188
|
+
if (typeof pack === "string") {
|
|
189
|
+
return pack.trim() || null;
|
|
190
|
+
}
|
|
191
|
+
if (Array.isArray(pack)) {
|
|
192
|
+
return formatPackItems(pack);
|
|
193
|
+
}
|
|
194
|
+
if (!pack || typeof pack !== "object") {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
const obj = pack;
|
|
198
|
+
const direct = firstString(obj, ["additionalContext", "context", "markdown", "content", "text", "summary"]);
|
|
199
|
+
if (direct)
|
|
200
|
+
return direct;
|
|
201
|
+
for (const key of ["pack", "contextPack", "context_pack", "data", "result"]) {
|
|
202
|
+
const nested = obj[key];
|
|
203
|
+
const text = extractContextText(nested);
|
|
204
|
+
if (text)
|
|
205
|
+
return text;
|
|
206
|
+
}
|
|
207
|
+
const items = firstArray(obj, ["items", "results", "sources", "citations"]);
|
|
208
|
+
return items ? formatPackItems(items) : null;
|
|
209
|
+
}
|
|
210
|
+
function formatAdditionalContext(event, context, config) {
|
|
211
|
+
const header = `[hook-knowledge-context] Deterministic Knowledge context (${event}; knowledge context pack --from search):`;
|
|
212
|
+
const safeContext = redactSecrets(context.trim());
|
|
213
|
+
return `${header}
|
|
214
|
+
|
|
215
|
+
${truncate(safeContext, Math.max(0, config.maxOutputChars - header.length - 2))}`;
|
|
216
|
+
}
|
|
217
|
+
async function buildHookOutput(input, executor = spawnKnowledgeContextPack, env = process.env) {
|
|
218
|
+
if (env.HOOKS_KNOWLEDGE_CONTEXT_DISABLE === "1") {
|
|
219
|
+
return { continue: true };
|
|
220
|
+
}
|
|
221
|
+
if (!input) {
|
|
222
|
+
return { continue: true };
|
|
223
|
+
}
|
|
224
|
+
const event = input.hook_event_name;
|
|
225
|
+
if (!isKnowledgeContextEvent(event)) {
|
|
226
|
+
return { continue: true };
|
|
227
|
+
}
|
|
228
|
+
const config = getConfig(env);
|
|
229
|
+
const query = buildQuery(input, config);
|
|
230
|
+
if (!query) {
|
|
231
|
+
return { continue: true };
|
|
232
|
+
}
|
|
233
|
+
try {
|
|
234
|
+
const result = await executor(query, config);
|
|
235
|
+
if (!result.ok || !result.stdout) {
|
|
236
|
+
return { continue: true };
|
|
237
|
+
}
|
|
238
|
+
const parsed = JSON.parse(result.stdout);
|
|
239
|
+
const context = extractContextText(parsed);
|
|
240
|
+
if (!context) {
|
|
241
|
+
return { continue: true };
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
continue: true,
|
|
245
|
+
hookSpecificOutput: {
|
|
246
|
+
hookEventName: event,
|
|
247
|
+
additionalContext: formatAdditionalContext(event, context, config)
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
} catch {
|
|
251
|
+
return { continue: true };
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
async function run() {
|
|
255
|
+
respond(await buildHookOutput(readStdinJson()));
|
|
256
|
+
}
|
|
257
|
+
if (__require.main == __require.module) {
|
|
258
|
+
run();
|
|
259
|
+
}
|
|
260
|
+
export {
|
|
261
|
+
truncate,
|
|
262
|
+
spawnKnowledgeContextPack,
|
|
263
|
+
sanitizeQuery,
|
|
264
|
+
run,
|
|
265
|
+
redactSecrets,
|
|
266
|
+
isKnowledgeContextEvent,
|
|
267
|
+
getConfig,
|
|
268
|
+
formatAdditionalContext,
|
|
269
|
+
extractContextText,
|
|
270
|
+
buildQuery,
|
|
271
|
+
buildKnowledgeArgs,
|
|
272
|
+
buildHookOutput
|
|
273
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hasna/hook-knowledge-context",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Codewith hook that injects deterministic Knowledge context packs into lifecycle events",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/hook.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/hook.js",
|
|
10
|
+
"types": "./dist/hook.d.ts"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist/hook.js",
|
|
15
|
+
"dist/hook.d.ts",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "rm -rf dist && bun build ./src/hook.ts --outdir ./dist --target node && tsc -p tsconfig.json --emitDeclarationOnly --declaration --declarationMap false --noEmit false",
|
|
20
|
+
"prepublishOnly": "bun run build",
|
|
21
|
+
"typecheck": "tsc --noEmit"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"codewith",
|
|
25
|
+
"hook",
|
|
26
|
+
"knowledge",
|
|
27
|
+
"context",
|
|
28
|
+
"deterministic",
|
|
29
|
+
"session-start",
|
|
30
|
+
"user-prompt-submit",
|
|
31
|
+
"subagent-start"
|
|
32
|
+
],
|
|
33
|
+
"author": "Hasna",
|
|
34
|
+
"license": "Apache-2.0",
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "https://github.com/hasna/hooks.git"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public",
|
|
41
|
+
"registry": "https://registry.npmjs.org/"
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=18",
|
|
45
|
+
"bun": ">=1.0"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/bun": "^1.3.8",
|
|
49
|
+
"@types/node": "^20",
|
|
50
|
+
"typescript": "^5.0.0"
|
|
51
|
+
}
|
|
52
|
+
}
|