@sheepbun/yips 0.1.1
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/dist/agent/commands/command-catalog.js +243 -0
- package/dist/agent/commands/commands.js +418 -0
- package/dist/agent/conductor.js +118 -0
- package/dist/agent/context/code-context.js +68 -0
- package/dist/agent/context/memory-store.js +159 -0
- package/dist/agent/context/session-store.js +211 -0
- package/dist/agent/protocol/tool-protocol.js +160 -0
- package/dist/agent/skills/skills.js +327 -0
- package/dist/agent/tools/tool-executor.js +415 -0
- package/dist/agent/tools/tool-safety.js +52 -0
- package/dist/app/index.js +35 -0
- package/dist/app/repl.js +105 -0
- package/dist/app/update-check.js +132 -0
- package/dist/app/version.js +51 -0
- package/dist/code-context.js +68 -0
- package/dist/colors.js +204 -0
- package/dist/command-catalog.js +242 -0
- package/dist/commands.js +350 -0
- package/dist/conductor.js +94 -0
- package/dist/config/config.js +335 -0
- package/dist/config/hooks.js +187 -0
- package/dist/config.js +335 -0
- package/dist/downloader-state.js +302 -0
- package/dist/downloader-ui.js +289 -0
- package/dist/gateway/adapters/discord.js +108 -0
- package/dist/gateway/adapters/formatting.js +96 -0
- package/dist/gateway/adapters/telegram.js +106 -0
- package/dist/gateway/adapters/types.js +2 -0
- package/dist/gateway/adapters/whatsapp.js +124 -0
- package/dist/gateway/auth-policy.js +66 -0
- package/dist/gateway/core.js +87 -0
- package/dist/gateway/headless-conductor.js +328 -0
- package/dist/gateway/message-router.js +23 -0
- package/dist/gateway/rate-limiter.js +48 -0
- package/dist/gateway/runtime/backend-policy.js +18 -0
- package/dist/gateway/runtime/discord-bot.js +104 -0
- package/dist/gateway/runtime/discord-main.js +69 -0
- package/dist/gateway/session-manager.js +77 -0
- package/dist/gateway/types.js +2 -0
- package/dist/hardware.js +92 -0
- package/dist/hooks.js +187 -0
- package/dist/index.js +34 -0
- package/dist/input-engine.js +250 -0
- package/dist/llama-client.js +227 -0
- package/dist/llama-server.js +620 -0
- package/dist/llm/llama-client.js +227 -0
- package/dist/llm/llama-server.js +620 -0
- package/dist/llm/token-counter.js +47 -0
- package/dist/memory-store.js +159 -0
- package/dist/messages.js +59 -0
- package/dist/model-downloader.js +382 -0
- package/dist/model-manager-state.js +118 -0
- package/dist/model-manager-ui.js +194 -0
- package/dist/model-manager.js +190 -0
- package/dist/models/hardware.js +92 -0
- package/dist/models/model-downloader.js +382 -0
- package/dist/models/model-manager.js +190 -0
- package/dist/prompt-box.js +78 -0
- package/dist/prompt-composer.js +498 -0
- package/dist/repl.js +105 -0
- package/dist/session-store.js +211 -0
- package/dist/spinner.js +76 -0
- package/dist/title-box.js +388 -0
- package/dist/token-counter.js +47 -0
- package/dist/tool-executor.js +415 -0
- package/dist/tool-protocol.js +121 -0
- package/dist/tool-safety.js +52 -0
- package/dist/tui/app.js +2553 -0
- package/dist/tui/startup.js +56 -0
- package/dist/tui-input-routing.js +53 -0
- package/dist/tui.js +51 -0
- package/dist/types/app-types.js +2 -0
- package/dist/types.js +2 -0
- package/dist/ui/colors.js +204 -0
- package/dist/ui/downloader/downloader-state.js +302 -0
- package/dist/ui/downloader/downloader-ui.js +289 -0
- package/dist/ui/input/input-engine.js +250 -0
- package/dist/ui/input/tui-input-routing.js +53 -0
- package/dist/ui/input/vt-session.js +168 -0
- package/dist/ui/messages.js +59 -0
- package/dist/ui/model-manager/model-manager-state.js +118 -0
- package/dist/ui/model-manager/model-manager-ui.js +194 -0
- package/dist/ui/prompt/prompt-box.js +78 -0
- package/dist/ui/prompt/prompt-composer.js +498 -0
- package/dist/ui/spinner.js +76 -0
- package/dist/ui/title-box.js +388 -0
- package/dist/ui/tui/app.js +6 -0
- package/dist/ui/tui/autocomplete.js +85 -0
- package/dist/ui/tui/constants.js +18 -0
- package/dist/ui/tui/history.js +29 -0
- package/dist/ui/tui/layout.js +341 -0
- package/dist/ui/tui/runtime-core.js +2584 -0
- package/dist/ui/tui/runtime-utils.js +53 -0
- package/dist/ui/tui/start-tui.js +54 -0
- package/dist/ui/tui/startup.js +56 -0
- package/dist/version.js +51 -0
- package/dist/vt-session.js +168 -0
- package/install.sh +457 -0
- package/package.json +128 -0
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CONFIG_PATH_ENV_VAR = exports.DEFAULT_CONFIG_PATH = void 0;
|
|
4
|
+
exports.getDefaultConfig = getDefaultConfig;
|
|
5
|
+
exports.resolveConfigPath = resolveConfigPath;
|
|
6
|
+
exports.mergeConfig = mergeConfig;
|
|
7
|
+
exports.loadConfig = loadConfig;
|
|
8
|
+
exports.saveConfig = saveConfig;
|
|
9
|
+
exports.updateConfig = updateConfig;
|
|
10
|
+
const node_fs_1 = require("node:fs");
|
|
11
|
+
const promises_1 = require("node:fs/promises");
|
|
12
|
+
const node_os_1 = require("node:os");
|
|
13
|
+
const node_path_1 = require("node:path");
|
|
14
|
+
const SUPPORTED_BACKENDS = new Set(["llamacpp", "claude"]);
|
|
15
|
+
const PORT_CONFLICT_POLICIES = new Set([
|
|
16
|
+
"fail",
|
|
17
|
+
"kill-llama",
|
|
18
|
+
"kill-user"
|
|
19
|
+
]);
|
|
20
|
+
const SUPPORTED_HOOKS = [
|
|
21
|
+
"on-session-start",
|
|
22
|
+
"on-session-end",
|
|
23
|
+
"on-file-write",
|
|
24
|
+
"on-file-read",
|
|
25
|
+
"pre-commit"
|
|
26
|
+
];
|
|
27
|
+
const DEFAULT_HOOK_TIMEOUT_MS = 10_000;
|
|
28
|
+
const MAX_HOOK_TIMEOUT_MS = 120_000;
|
|
29
|
+
exports.DEFAULT_CONFIG_PATH = ".yips_config.json";
|
|
30
|
+
exports.CONFIG_PATH_ENV_VAR = "YIPS_CONFIG_PATH";
|
|
31
|
+
function getDefaultConfig() {
|
|
32
|
+
const llamaHost = "127.0.0.1";
|
|
33
|
+
const llamaPort = 8080;
|
|
34
|
+
return {
|
|
35
|
+
streaming: true,
|
|
36
|
+
verbose: false,
|
|
37
|
+
backend: "llamacpp",
|
|
38
|
+
llamaBaseUrl: buildBaseUrl(llamaHost, llamaPort),
|
|
39
|
+
llamaServerPath: "",
|
|
40
|
+
llamaModelsDir: (0, node_path_1.resolve)((0, node_os_1.homedir)(), ".yips", "models"),
|
|
41
|
+
llamaHost,
|
|
42
|
+
llamaPort,
|
|
43
|
+
llamaContextSize: 8192,
|
|
44
|
+
llamaGpuLayers: 999,
|
|
45
|
+
llamaAutoStart: true,
|
|
46
|
+
llamaPortConflictPolicy: "kill-user",
|
|
47
|
+
model: "default",
|
|
48
|
+
tokensMode: "auto",
|
|
49
|
+
tokensManualMax: 8192,
|
|
50
|
+
nicknames: {},
|
|
51
|
+
hooks: {}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function resolveConfigPath(configPath = exports.DEFAULT_CONFIG_PATH) {
|
|
55
|
+
if (configPath === exports.DEFAULT_CONFIG_PATH) {
|
|
56
|
+
const envPath = process.env[exports.CONFIG_PATH_ENV_VAR]?.trim();
|
|
57
|
+
if (envPath && envPath.length > 0) {
|
|
58
|
+
return (0, node_path_1.resolve)(envPath);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return (0, node_path_1.resolve)(process.cwd(), configPath);
|
|
62
|
+
}
|
|
63
|
+
function isRecord(value) {
|
|
64
|
+
return typeof value === "object" && value !== null;
|
|
65
|
+
}
|
|
66
|
+
function normalizeBoolean(value, fallback) {
|
|
67
|
+
return typeof value === "boolean" ? value : fallback;
|
|
68
|
+
}
|
|
69
|
+
function normalizeBackend(value, fallback) {
|
|
70
|
+
if (typeof value === "string" && SUPPORTED_BACKENDS.has(value)) {
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
return fallback;
|
|
74
|
+
}
|
|
75
|
+
function normalizeModel(value, fallback) {
|
|
76
|
+
if (typeof value !== "string") {
|
|
77
|
+
return fallback;
|
|
78
|
+
}
|
|
79
|
+
const trimmed = value.trim();
|
|
80
|
+
return trimmed.length > 0 ? trimmed : fallback;
|
|
81
|
+
}
|
|
82
|
+
function normalizeBaseUrl(value, fallback) {
|
|
83
|
+
if (typeof value !== "string") {
|
|
84
|
+
return fallback;
|
|
85
|
+
}
|
|
86
|
+
const trimmed = value.trim();
|
|
87
|
+
if (trimmed.length === 0) {
|
|
88
|
+
return fallback;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
const parsed = new URL(trimmed);
|
|
92
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
93
|
+
return fallback;
|
|
94
|
+
}
|
|
95
|
+
return parsed.toString().replace(/\/$/, "");
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return fallback;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function parseBoolean(value, fallback) {
|
|
102
|
+
if (value === undefined) {
|
|
103
|
+
return fallback;
|
|
104
|
+
}
|
|
105
|
+
const normalized = value.trim().toLowerCase();
|
|
106
|
+
if (normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on") {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
if (normalized === "0" || normalized === "false" || normalized === "no" || normalized === "off") {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
return fallback;
|
|
113
|
+
}
|
|
114
|
+
function normalizeString(value, fallback) {
|
|
115
|
+
if (typeof value !== "string") {
|
|
116
|
+
return fallback;
|
|
117
|
+
}
|
|
118
|
+
const trimmed = value.trim();
|
|
119
|
+
return trimmed.length > 0 ? trimmed : fallback;
|
|
120
|
+
}
|
|
121
|
+
function normalizeHost(value, fallback) {
|
|
122
|
+
return normalizeString(value, fallback);
|
|
123
|
+
}
|
|
124
|
+
function normalizePort(value, fallback) {
|
|
125
|
+
if (typeof value === "number" && Number.isInteger(value) && value > 0 && value <= 65535) {
|
|
126
|
+
return value;
|
|
127
|
+
}
|
|
128
|
+
if (typeof value === "string") {
|
|
129
|
+
const parsed = Number(value.trim());
|
|
130
|
+
if (Number.isInteger(parsed) && parsed > 0 && parsed <= 65535) {
|
|
131
|
+
return parsed;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return fallback;
|
|
135
|
+
}
|
|
136
|
+
function normalizePositiveInt(value, fallback) {
|
|
137
|
+
if (typeof value === "number" && Number.isInteger(value) && value > 0) {
|
|
138
|
+
return value;
|
|
139
|
+
}
|
|
140
|
+
if (typeof value === "string") {
|
|
141
|
+
const parsed = Number(value.trim());
|
|
142
|
+
if (Number.isInteger(parsed) && parsed > 0) {
|
|
143
|
+
return parsed;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return fallback;
|
|
147
|
+
}
|
|
148
|
+
function normalizePositiveIntWithMax(value, fallback, max) {
|
|
149
|
+
const normalized = normalizePositiveInt(value, fallback);
|
|
150
|
+
return Math.min(normalized, max);
|
|
151
|
+
}
|
|
152
|
+
function normalizePortConflictPolicy(value, fallback) {
|
|
153
|
+
if (typeof value === "string" && PORT_CONFLICT_POLICIES.has(value)) {
|
|
154
|
+
return value;
|
|
155
|
+
}
|
|
156
|
+
return fallback;
|
|
157
|
+
}
|
|
158
|
+
function normalizeTokensMode(value, fallback) {
|
|
159
|
+
if (value === "auto" || value === "manual") {
|
|
160
|
+
return value;
|
|
161
|
+
}
|
|
162
|
+
return fallback;
|
|
163
|
+
}
|
|
164
|
+
function buildBaseUrl(host, port) {
|
|
165
|
+
return `http://${host}:${port}`;
|
|
166
|
+
}
|
|
167
|
+
function parseBaseUrlHostPort(baseUrl) {
|
|
168
|
+
try {
|
|
169
|
+
const parsed = new URL(baseUrl);
|
|
170
|
+
if (!parsed.hostname || !parsed.port) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
const port = Number(parsed.port);
|
|
174
|
+
if (!Number.isInteger(port) || port <= 0 || port > 65535) {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
host: parsed.hostname,
|
|
179
|
+
port
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function normalizeNicknames(value, fallback) {
|
|
187
|
+
if (!isRecord(value)) {
|
|
188
|
+
return { ...fallback };
|
|
189
|
+
}
|
|
190
|
+
const next = {};
|
|
191
|
+
for (const [key, nickname] of Object.entries(value)) {
|
|
192
|
+
if (typeof key !== "string" || typeof nickname !== "string") {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
const trimmedKey = key.trim();
|
|
196
|
+
const trimmedNickname = nickname.trim();
|
|
197
|
+
if (trimmedKey.length === 0 || trimmedNickname.length === 0) {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
next[trimmedKey] = trimmedNickname;
|
|
201
|
+
}
|
|
202
|
+
return next;
|
|
203
|
+
}
|
|
204
|
+
function normalizeHookConfig(value) {
|
|
205
|
+
if (!isRecord(value)) {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
const command = normalizeString(value.command, "");
|
|
209
|
+
if (command.length === 0) {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
const timeoutMs = normalizePositiveIntWithMax(value.timeoutMs, DEFAULT_HOOK_TIMEOUT_MS, MAX_HOOK_TIMEOUT_MS);
|
|
213
|
+
return { command, timeoutMs };
|
|
214
|
+
}
|
|
215
|
+
function normalizeHooks(value, fallback) {
|
|
216
|
+
const next = {};
|
|
217
|
+
for (const hookName of SUPPORTED_HOOKS) {
|
|
218
|
+
const existing = fallback[hookName];
|
|
219
|
+
if (existing) {
|
|
220
|
+
next[hookName] = { ...existing };
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (!isRecord(value)) {
|
|
224
|
+
return next;
|
|
225
|
+
}
|
|
226
|
+
for (const hookName of SUPPORTED_HOOKS) {
|
|
227
|
+
const normalized = normalizeHookConfig(value[hookName]);
|
|
228
|
+
if (normalized) {
|
|
229
|
+
next[hookName] = normalized;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return next;
|
|
233
|
+
}
|
|
234
|
+
function applyEnvOverrides(config) {
|
|
235
|
+
const envHost = normalizeHost(process.env["YIPS_LLAMA_HOST"], config.llamaHost);
|
|
236
|
+
const envPort = normalizePort(process.env["YIPS_LLAMA_PORT"], config.llamaPort);
|
|
237
|
+
const envBaseUrl = normalizeBaseUrl(process.env["YIPS_LLAMA_BASE_URL"], buildBaseUrl(envHost, envPort));
|
|
238
|
+
const envBaseUrlHostPort = parseBaseUrlHostPort(envBaseUrl);
|
|
239
|
+
return {
|
|
240
|
+
...config,
|
|
241
|
+
llamaHost: envBaseUrlHostPort?.host ?? envHost,
|
|
242
|
+
llamaPort: envBaseUrlHostPort?.port ?? envPort,
|
|
243
|
+
llamaBaseUrl: envBaseUrl,
|
|
244
|
+
llamaServerPath: normalizeString(process.env["YIPS_LLAMA_SERVER_PATH"], config.llamaServerPath),
|
|
245
|
+
llamaModelsDir: normalizeString(process.env["YIPS_LLAMA_MODELS_DIR"], config.llamaModelsDir),
|
|
246
|
+
llamaContextSize: normalizePositiveInt(process.env["YIPS_LLAMA_CONTEXT_SIZE"], config.llamaContextSize),
|
|
247
|
+
llamaGpuLayers: normalizePositiveInt(process.env["YIPS_LLAMA_GPU_LAYERS"], config.llamaGpuLayers),
|
|
248
|
+
llamaAutoStart: parseBoolean(process.env["YIPS_LLAMA_AUTO_START"], config.llamaAutoStart),
|
|
249
|
+
llamaPortConflictPolicy: normalizePortConflictPolicy(process.env["YIPS_LLAMA_PORT_CONFLICT_POLICY"], config.llamaPortConflictPolicy),
|
|
250
|
+
model: normalizeModel(process.env["YIPS_MODEL"], config.model),
|
|
251
|
+
tokensMode: normalizeTokensMode(process.env["YIPS_TOKENS_MODE"], config.tokensMode),
|
|
252
|
+
tokensManualMax: normalizePositiveInt(process.env["YIPS_TOKENS_MANUAL_MAX"], config.tokensManualMax),
|
|
253
|
+
hooks: normalizeHooks(config.hooks, config.hooks)
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function mergeConfig(defaults, candidate) {
|
|
257
|
+
if (!isRecord(candidate)) {
|
|
258
|
+
return defaults;
|
|
259
|
+
}
|
|
260
|
+
const llamaHost = normalizeHost(candidate.llamaHost, defaults.llamaHost);
|
|
261
|
+
const llamaPort = normalizePort(candidate.llamaPort, defaults.llamaPort);
|
|
262
|
+
const llamaBaseUrl = normalizeBaseUrl(candidate.llamaBaseUrl, buildBaseUrl(llamaHost, llamaPort));
|
|
263
|
+
const baseUrlHostPort = parseBaseUrlHostPort(llamaBaseUrl);
|
|
264
|
+
return {
|
|
265
|
+
streaming: normalizeBoolean(candidate.streaming, defaults.streaming),
|
|
266
|
+
verbose: normalizeBoolean(candidate.verbose, defaults.verbose),
|
|
267
|
+
backend: normalizeBackend(candidate.backend, defaults.backend),
|
|
268
|
+
llamaBaseUrl,
|
|
269
|
+
llamaServerPath: normalizeString(candidate.llamaServerPath, defaults.llamaServerPath),
|
|
270
|
+
llamaModelsDir: normalizeString(candidate.llamaModelsDir, defaults.llamaModelsDir),
|
|
271
|
+
llamaHost: baseUrlHostPort?.host ?? llamaHost,
|
|
272
|
+
llamaPort: baseUrlHostPort?.port ?? llamaPort,
|
|
273
|
+
llamaContextSize: normalizePositiveInt(candidate.llamaContextSize, defaults.llamaContextSize),
|
|
274
|
+
llamaGpuLayers: normalizePositiveInt(candidate.llamaGpuLayers, defaults.llamaGpuLayers),
|
|
275
|
+
llamaAutoStart: normalizeBoolean(candidate.llamaAutoStart, defaults.llamaAutoStart),
|
|
276
|
+
llamaPortConflictPolicy: normalizePortConflictPolicy(candidate.llamaPortConflictPolicy, defaults.llamaPortConflictPolicy),
|
|
277
|
+
model: normalizeModel(candidate.model, defaults.model),
|
|
278
|
+
tokensMode: normalizeTokensMode(candidate.tokensMode, defaults.tokensMode),
|
|
279
|
+
tokensManualMax: normalizePositiveInt(candidate.tokensManualMax, defaults.tokensManualMax),
|
|
280
|
+
nicknames: normalizeNicknames(candidate.nicknames, defaults.nicknames),
|
|
281
|
+
hooks: normalizeHooks(candidate.hooks, defaults.hooks)
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
async function loadConfig(configPath = exports.DEFAULT_CONFIG_PATH) {
|
|
285
|
+
const defaults = getDefaultConfig();
|
|
286
|
+
const path = resolveConfigPath(configPath);
|
|
287
|
+
const legacyDefaultPath = (0, node_path_1.resolve)(process.cwd(), exports.DEFAULT_CONFIG_PATH);
|
|
288
|
+
const candidatePaths = [path];
|
|
289
|
+
if (configPath === exports.DEFAULT_CONFIG_PATH && path !== legacyDefaultPath) {
|
|
290
|
+
candidatePaths.push(legacyDefaultPath);
|
|
291
|
+
}
|
|
292
|
+
let readablePath = null;
|
|
293
|
+
for (const candidatePath of candidatePaths) {
|
|
294
|
+
try {
|
|
295
|
+
await (0, promises_1.access)(candidatePath, node_fs_1.constants.R_OK);
|
|
296
|
+
readablePath = candidatePath;
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
// keep trying
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (!readablePath) {
|
|
304
|
+
return { config: applyEnvOverrides(defaults), path, source: "default" };
|
|
305
|
+
}
|
|
306
|
+
try {
|
|
307
|
+
const rawConfig = await (0, promises_1.readFile)(readablePath, "utf8");
|
|
308
|
+
const parsedConfig = JSON.parse(rawConfig);
|
|
309
|
+
return {
|
|
310
|
+
config: applyEnvOverrides(mergeConfig(defaults, parsedConfig)),
|
|
311
|
+
path: readablePath,
|
|
312
|
+
source: "file"
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
catch (error) {
|
|
316
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
317
|
+
return {
|
|
318
|
+
config: applyEnvOverrides(defaults),
|
|
319
|
+
path: readablePath,
|
|
320
|
+
source: "default",
|
|
321
|
+
warning: `Failed to load config at ${readablePath}: ${message}`
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
async function saveConfig(config, configPath = exports.DEFAULT_CONFIG_PATH) {
|
|
326
|
+
const path = resolveConfigPath(configPath);
|
|
327
|
+
const normalized = mergeConfig(getDefaultConfig(), config);
|
|
328
|
+
await (0, promises_1.writeFile)(path, `${JSON.stringify(normalized, null, 2)}\n`, "utf8");
|
|
329
|
+
}
|
|
330
|
+
async function updateConfig(patch, configPath = exports.DEFAULT_CONFIG_PATH) {
|
|
331
|
+
const loaded = await loadConfig(configPath);
|
|
332
|
+
const merged = mergeConfig(getDefaultConfig(), { ...loaded.config, ...patch });
|
|
333
|
+
await saveConfig(merged, configPath);
|
|
334
|
+
return merged;
|
|
335
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatHookFailure = formatHookFailure;
|
|
4
|
+
exports.runHook = runHook;
|
|
5
|
+
const node_child_process_1 = require("node:child_process");
|
|
6
|
+
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
7
|
+
const MAX_TIMEOUT_MS = 120_000;
|
|
8
|
+
const SHUTDOWN_GRACE_MS = 500;
|
|
9
|
+
function resolveHookCommand(config, hook) {
|
|
10
|
+
const configured = config.hooks[hook]?.command;
|
|
11
|
+
if (typeof configured !== "string") {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
const trimmed = configured.trim();
|
|
15
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
16
|
+
}
|
|
17
|
+
function resolveHookTimeoutMs(config, hook) {
|
|
18
|
+
const configured = config.hooks[hook]?.timeoutMs;
|
|
19
|
+
if (typeof configured !== "number" || !Number.isInteger(configured) || configured <= 0) {
|
|
20
|
+
return DEFAULT_TIMEOUT_MS;
|
|
21
|
+
}
|
|
22
|
+
return Math.min(configured, MAX_TIMEOUT_MS);
|
|
23
|
+
}
|
|
24
|
+
function createEventId() {
|
|
25
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
26
|
+
}
|
|
27
|
+
function toErrorMessage(error) {
|
|
28
|
+
return error instanceof Error ? error.message : String(error);
|
|
29
|
+
}
|
|
30
|
+
function extractFilePath(payload) {
|
|
31
|
+
const path = payload["path"];
|
|
32
|
+
if (typeof path === "string" && path.trim().length > 0) {
|
|
33
|
+
return path.trim();
|
|
34
|
+
}
|
|
35
|
+
const filePath = payload["filePath"];
|
|
36
|
+
if (typeof filePath === "string" && filePath.trim().length > 0) {
|
|
37
|
+
return filePath.trim();
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
function formatHookFailure(result) {
|
|
42
|
+
const base = `[hook:${result.hook}] ${result.message}`;
|
|
43
|
+
if (result.status === "timeout") {
|
|
44
|
+
return base;
|
|
45
|
+
}
|
|
46
|
+
if (result.exitCode !== null) {
|
|
47
|
+
return `${base} (exit ${result.exitCode})`;
|
|
48
|
+
}
|
|
49
|
+
return base;
|
|
50
|
+
}
|
|
51
|
+
async function runHook(config, hook, payload, context = {}) {
|
|
52
|
+
const cwd = context.cwd ?? process.cwd();
|
|
53
|
+
const eventId = createEventId();
|
|
54
|
+
const timestamp = new Date().toISOString();
|
|
55
|
+
const command = resolveHookCommand(config, hook);
|
|
56
|
+
if (!command) {
|
|
57
|
+
return {
|
|
58
|
+
hook,
|
|
59
|
+
status: "skipped",
|
|
60
|
+
command: null,
|
|
61
|
+
cwd,
|
|
62
|
+
message: `No hook configured for ${hook}.`,
|
|
63
|
+
durationMs: 0,
|
|
64
|
+
eventId,
|
|
65
|
+
timestamp,
|
|
66
|
+
stdout: "",
|
|
67
|
+
stderr: "",
|
|
68
|
+
exitCode: null,
|
|
69
|
+
signal: null,
|
|
70
|
+
timedOut: false
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const timeoutMs = resolveHookTimeoutMs(config, hook);
|
|
74
|
+
const filePath = extractFilePath(payload);
|
|
75
|
+
const startedAt = Date.now();
|
|
76
|
+
const envelope = {
|
|
77
|
+
hook,
|
|
78
|
+
eventId,
|
|
79
|
+
timestamp,
|
|
80
|
+
cwd,
|
|
81
|
+
sessionName: context.sessionName ?? null,
|
|
82
|
+
data: payload
|
|
83
|
+
};
|
|
84
|
+
return await new Promise((resolveResult) => {
|
|
85
|
+
let stdout = "";
|
|
86
|
+
let stderr = "";
|
|
87
|
+
let settled = false;
|
|
88
|
+
let timedOut = false;
|
|
89
|
+
let killTimer = null;
|
|
90
|
+
const child = (0, node_child_process_1.spawn)("sh", ["-lc", command], {
|
|
91
|
+
cwd,
|
|
92
|
+
env: {
|
|
93
|
+
...process.env,
|
|
94
|
+
...context.env,
|
|
95
|
+
YIPS_HOOK_NAME: hook,
|
|
96
|
+
YIPS_HOOK_EVENT_ID: eventId,
|
|
97
|
+
YIPS_HOOK_TIMESTAMP: timestamp,
|
|
98
|
+
YIPS_HOOK_CWD: cwd,
|
|
99
|
+
YIPS_HOOK_SESSION_NAME: context.sessionName ?? "",
|
|
100
|
+
YIPS_HOOK_FILE_PATH: filePath ?? ""
|
|
101
|
+
},
|
|
102
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
103
|
+
});
|
|
104
|
+
const settle = (result) => {
|
|
105
|
+
if (settled) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
settled = true;
|
|
109
|
+
if (killTimer) {
|
|
110
|
+
clearTimeout(killTimer);
|
|
111
|
+
}
|
|
112
|
+
resolveResult({
|
|
113
|
+
hook,
|
|
114
|
+
eventId,
|
|
115
|
+
timestamp,
|
|
116
|
+
cwd,
|
|
117
|
+
...result
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
child.stdout.on("data", (chunk) => {
|
|
121
|
+
stdout += chunk.toString("utf8");
|
|
122
|
+
});
|
|
123
|
+
child.stderr.on("data", (chunk) => {
|
|
124
|
+
stderr += chunk.toString("utf8");
|
|
125
|
+
});
|
|
126
|
+
child.on("error", (error) => {
|
|
127
|
+
settle({
|
|
128
|
+
status: "error",
|
|
129
|
+
command,
|
|
130
|
+
message: `Hook execution failed: ${toErrorMessage(error)}`,
|
|
131
|
+
durationMs: Math.max(0, Date.now() - startedAt),
|
|
132
|
+
stdout,
|
|
133
|
+
stderr,
|
|
134
|
+
exitCode: null,
|
|
135
|
+
signal: null,
|
|
136
|
+
timedOut
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
child.on("close", (code, signal) => {
|
|
140
|
+
if (timedOut) {
|
|
141
|
+
settle({
|
|
142
|
+
status: "timeout",
|
|
143
|
+
command,
|
|
144
|
+
message: `Hook timed out after ${timeoutMs}ms.`,
|
|
145
|
+
durationMs: Math.max(0, Date.now() - startedAt),
|
|
146
|
+
stdout,
|
|
147
|
+
stderr,
|
|
148
|
+
exitCode: code,
|
|
149
|
+
signal,
|
|
150
|
+
timedOut: true
|
|
151
|
+
});
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const success = code === 0;
|
|
155
|
+
settle({
|
|
156
|
+
status: success ? "ok" : "error",
|
|
157
|
+
command,
|
|
158
|
+
message: success ? "Hook completed successfully." : "Hook exited with a non-zero status.",
|
|
159
|
+
durationMs: Math.max(0, Date.now() - startedAt),
|
|
160
|
+
stdout,
|
|
161
|
+
stderr,
|
|
162
|
+
exitCode: code,
|
|
163
|
+
signal,
|
|
164
|
+
timedOut: false
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
killTimer = setTimeout(() => {
|
|
168
|
+
if (settled) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
timedOut = true;
|
|
172
|
+
child.kill("SIGTERM");
|
|
173
|
+
setTimeout(() => {
|
|
174
|
+
if (!settled) {
|
|
175
|
+
child.kill("SIGKILL");
|
|
176
|
+
}
|
|
177
|
+
}, SHUTDOWN_GRACE_MS);
|
|
178
|
+
}, timeoutMs);
|
|
179
|
+
const stdinPayload = `${JSON.stringify(envelope)}\n`;
|
|
180
|
+
child.stdin.write(stdinPayload, "utf8", (error) => {
|
|
181
|
+
if (error) {
|
|
182
|
+
stderr += `stdin write failed: ${toErrorMessage(error)}\n`;
|
|
183
|
+
}
|
|
184
|
+
child.stdin.end();
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
}
|