harborloop 0.3.0 → 0.4.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/bin/checksums.json +12 -0
- package/bin/harbor.mjs +188 -0
- package/package.json +14 -30
- package/dist/api.d.ts +0 -109
- package/dist/api.js +0 -409
- package/dist/auth/login.d.ts +0 -14
- package/dist/auth/login.js +0 -158
- package/dist/auth/loopback.d.ts +0 -11
- package/dist/auth/loopback.js +0 -87
- package/dist/auth/pkce.d.ts +0 -7
- package/dist/auth/pkce.js +0 -14
- package/dist/auth/store.d.ts +0 -26
- package/dist/auth/store.js +0 -107
- package/dist/cli-release.d.ts +0 -31
- package/dist/cli-release.js +0 -102
- package/dist/commands/bind.d.ts +0 -23
- package/dist/commands/bind.js +0 -86
- package/dist/commands/context.d.ts +0 -12
- package/dist/commands/context.js +0 -80
- package/dist/commands/daemon.d.ts +0 -39
- package/dist/commands/daemon.js +0 -140
- package/dist/commands/doctor.d.ts +0 -16
- package/dist/commands/doctor.js +0 -937
- package/dist/commands/init.d.ts +0 -36
- package/dist/commands/init.js +0 -192
- package/dist/commands/status.d.ts +0 -5
- package/dist/commands/status.js +0 -164
- package/dist/commands/sync.d.ts +0 -40
- package/dist/commands/sync.js +0 -242
- package/dist/commands/toggle.d.ts +0 -16
- package/dist/commands/toggle.js +0 -69
- package/dist/commands/unbind.d.ts +0 -14
- package/dist/commands/unbind.js +0 -60
- package/dist/commands/workspaces.d.ts +0 -18
- package/dist/commands/workspaces.js +0 -41
- package/dist/context-budget.d.ts +0 -40
- package/dist/context-budget.js +0 -259
- package/dist/daemon/install.d.ts +0 -50
- package/dist/daemon/install.js +0 -192
- package/dist/daemon/loop.d.ts +0 -15
- package/dist/daemon/loop.js +0 -84
- package/dist/daemon/state.d.ts +0 -79
- package/dist/daemon/state.js +0 -213
- package/dist/daemon/tick.d.ts +0 -37
- package/dist/daemon/tick.js +0 -231
- package/dist/documents/apply.d.ts +0 -21
- package/dist/documents/apply.js +0 -58
- package/dist/documents/bundle.d.ts +0 -41
- package/dist/documents/bundle.js +0 -43
- package/dist/documents/inspect.d.ts +0 -12
- package/dist/documents/inspect.js +0 -33
- package/dist/documents/manifest.d.ts +0 -14
- package/dist/documents/manifest.js +0 -52
- package/dist/documents/pointer.d.ts +0 -32
- package/dist/documents/pointer.js +0 -135
- package/dist/documents/write.d.ts +0 -57
- package/dist/documents/write.js +0 -211
- package/dist/endpoints.d.ts +0 -7
- package/dist/endpoints.js +0 -15
- package/dist/env-bundle.d.ts +0 -25
- package/dist/env-bundle.js +0 -33
- package/dist/env-scan.d.ts +0 -27
- package/dist/env-scan.js +0 -41
- package/dist/env-sync.d.ts +0 -31
- package/dist/env-sync.js +0 -37
- package/dist/index.d.ts +0 -11
- package/dist/index.js +0 -282
- package/dist/install-hooks.d.ts +0 -11
- package/dist/install-hooks.js +0 -64
- package/dist/lock.d.ts +0 -31
- package/dist/lock.js +0 -192
- package/dist/machine.d.ts +0 -13
- package/dist/machine.js +0 -53
- package/dist/memories.d.ts +0 -31
- package/dist/memories.js +0 -74
- package/dist/origin.d.ts +0 -4
- package/dist/origin.js +0 -59
- package/dist/paths.d.ts +0 -12
- package/dist/paths.js +0 -45
- package/dist/providers/claude.d.ts +0 -65
- package/dist/providers/claude.js +0 -412
- package/dist/providers/codex-trust.d.ts +0 -36
- package/dist/providers/codex-trust.js +0 -141
- package/dist/providers/codex.d.ts +0 -46
- package/dist/providers/codex.js +0 -520
- package/dist/providers/cursor.d.ts +0 -45
- package/dist/providers/cursor.js +0 -344
- package/dist/providers/detect.d.ts +0 -16
- package/dist/providers/detect.js +0 -70
- package/dist/providers/hook-coverage.d.ts +0 -5
- package/dist/providers/hook-coverage.js +0 -47
- package/dist/providers/hook-entry.d.ts +0 -1
- package/dist/providers/hook-entry.js +0 -8
- package/dist/providers/surfaces.d.ts +0 -19
- package/dist/providers/surfaces.js +0 -91
- package/dist/reclaim.d.ts +0 -17
- package/dist/reclaim.js +0 -33
- package/dist/render.d.ts +0 -17
- package/dist/render.js +0 -61
- package/dist/repo-sync.d.ts +0 -27
- package/dist/repo-sync.js +0 -34
- package/dist/repos.d.ts +0 -11
- package/dist/repos.js +0 -82
- package/dist/skills.d.ts +0 -84
- package/dist/skills.js +0 -383
- package/dist/slugify.d.ts +0 -1
- package/dist/slugify.js +0 -8
- package/dist/state.d.ts +0 -23
- package/dist/state.js +0 -161
- package/dist/sync-report.d.ts +0 -59
- package/dist/sync-report.js +0 -87
- package/hooks/_recall.mjs +0 -288
- package/hooks/_receipt.mjs +0 -47
- package/hooks/_shared.mjs +0 -1610
- package/hooks/_upgrade.mjs +0 -51
- package/hooks/session-capture.mjs +0 -60
- package/hooks/session-compact.mjs +0 -19
- package/hooks/session-context.mjs +0 -258
- package/hooks/session-report.mjs +0 -42
- package/hooks/session-toolcontext.mjs +0 -183
package/dist/providers/codex.js
DELETED
|
@@ -1,520 +0,0 @@
|
|
|
1
|
-
import { homedir } from 'node:os';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { ensureConfigDir, readIfExists, updateFileLocked } from '../lock.js';
|
|
4
|
-
import { isUnrunnableCommandEntry } from './hook-entry.js';
|
|
5
|
-
import { HOOK_CAPTURE_BASENAME, HOOK_CONTEXT_BASENAME, HOOK_REPORT_BASENAME, HOOK_COMPACT_BASENAME, HOOK_TOOLCONTEXT_BASENAME, LEGACY_MCP_KEYS, MCP_KEY, } from './claude.js';
|
|
6
|
-
const MANAGED_MARKER = '# --- harbor shared context (managed by the harbor CLI) ---';
|
|
7
|
-
const LEGACY_MANAGED_MARKERS = [
|
|
8
|
-
'# --- Milestone shared context (managed by Milestone Desktop) ---',
|
|
9
|
-
'# --- Milestone shared context (managed by the harbor CLI) ---',
|
|
10
|
-
];
|
|
11
|
-
const isManagedMarker = (line) => {
|
|
12
|
-
const trimmed = line.trim();
|
|
13
|
-
return trimmed === MANAGED_MARKER || LEGACY_MANAGED_MARKERS.includes(trimmed);
|
|
14
|
-
};
|
|
15
|
-
const HOOK_BASENAMES = [
|
|
16
|
-
HOOK_CONTEXT_BASENAME,
|
|
17
|
-
HOOK_REPORT_BASENAME,
|
|
18
|
-
HOOK_CAPTURE_BASENAME,
|
|
19
|
-
HOOK_TOOLCONTEXT_BASENAME,
|
|
20
|
-
HOOK_COMPACT_BASENAME,
|
|
21
|
-
];
|
|
22
|
-
const HOOK_CONTEXT_TIMEOUT_S = 8;
|
|
23
|
-
const HOOK_REPORT_TIMEOUT_S = 5;
|
|
24
|
-
const HOOK_CAPTURE_TIMEOUT_S = 10;
|
|
25
|
-
const HOOK_TOOLCONTEXT_TIMEOUT_S = 5;
|
|
26
|
-
const HOOK_COMPACT_TIMEOUT_S = 2;
|
|
27
|
-
const EVENT_USER_PROMPT_SUBMIT = 'UserPromptSubmit';
|
|
28
|
-
const EVENT_STOP = 'Stop';
|
|
29
|
-
const EVENT_POST_TOOL_USE = 'PostToolUse';
|
|
30
|
-
const EVENT_PRE_TOOL_USE = 'PreToolUse';
|
|
31
|
-
const EVENT_POST_COMPACT = 'PostCompact';
|
|
32
|
-
export const MANAGED_HOOK_EVENTS = [
|
|
33
|
-
EVENT_USER_PROMPT_SUBMIT,
|
|
34
|
-
EVENT_STOP,
|
|
35
|
-
EVENT_POST_TOOL_USE,
|
|
36
|
-
EVENT_PRE_TOOL_USE,
|
|
37
|
-
EVENT_POST_COMPACT,
|
|
38
|
-
];
|
|
39
|
-
const CODEX_HOOK_EVENTS = MANAGED_HOOK_EVENTS;
|
|
40
|
-
export function resolveCodexPaths(home = homedir()) {
|
|
41
|
-
const fromEnv = process.env.CODEX_HOME;
|
|
42
|
-
const codexDir = fromEnv !== undefined && fromEnv.trim() !== ''
|
|
43
|
-
? fromEnv
|
|
44
|
-
: join(home, '.codex');
|
|
45
|
-
return {
|
|
46
|
-
codexDir,
|
|
47
|
-
configTomlPath: join(codexDir, 'config.toml'),
|
|
48
|
-
hooksJsonPath: join(codexDir, 'hooks.json'),
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
function parseTomlHeaderPath(raw) {
|
|
52
|
-
const segments = [];
|
|
53
|
-
let current = '';
|
|
54
|
-
let quote = null;
|
|
55
|
-
for (let i = 0; i < raw.length; i += 1) {
|
|
56
|
-
const ch = raw[i] ?? '';
|
|
57
|
-
if (quote !== null) {
|
|
58
|
-
if (ch === '\\' && quote === '"' && i + 1 < raw.length) {
|
|
59
|
-
current += raw[i + 1] ?? '';
|
|
60
|
-
i += 1;
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
if (ch === quote) {
|
|
64
|
-
quote = null;
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
current += ch;
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
70
|
-
if (ch === '"' || ch === "'") {
|
|
71
|
-
quote = ch;
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
if (ch === '.') {
|
|
75
|
-
segments.push(current.trim());
|
|
76
|
-
current = '';
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
current += ch;
|
|
80
|
-
}
|
|
81
|
-
segments.push(current.trim());
|
|
82
|
-
return segments.filter((s) => s.length > 0);
|
|
83
|
-
}
|
|
84
|
-
function scanMultilineState(line, state) {
|
|
85
|
-
let open = state;
|
|
86
|
-
let i = 0;
|
|
87
|
-
while (i < line.length) {
|
|
88
|
-
if (open !== null) {
|
|
89
|
-
if (line.startsWith(open, i)) {
|
|
90
|
-
open = null;
|
|
91
|
-
i += 3;
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
i += 1;
|
|
95
|
-
continue;
|
|
96
|
-
}
|
|
97
|
-
if (line.startsWith('\"\"\"', i)) {
|
|
98
|
-
open = '\"\"\"';
|
|
99
|
-
i += 3;
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
if (line.startsWith("'''", i)) {
|
|
103
|
-
open = "'''";
|
|
104
|
-
i += 3;
|
|
105
|
-
continue;
|
|
106
|
-
}
|
|
107
|
-
const ch = line[i];
|
|
108
|
-
if (ch === '#')
|
|
109
|
-
break;
|
|
110
|
-
if (ch === '\"' || ch === "'") {
|
|
111
|
-
const quote = ch;
|
|
112
|
-
i += 1;
|
|
113
|
-
while (i < line.length) {
|
|
114
|
-
if (quote === '\"' && line[i] === '\\') {
|
|
115
|
-
i += 2;
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
|
-
if (line[i] === quote) {
|
|
119
|
-
i += 1;
|
|
120
|
-
break;
|
|
121
|
-
}
|
|
122
|
-
i += 1;
|
|
123
|
-
}
|
|
124
|
-
continue;
|
|
125
|
-
}
|
|
126
|
-
i += 1;
|
|
127
|
-
}
|
|
128
|
-
return open;
|
|
129
|
-
}
|
|
130
|
-
function parseTomlTables(content) {
|
|
131
|
-
const lines = content.split('\n');
|
|
132
|
-
const headers = [];
|
|
133
|
-
let multiline = null;
|
|
134
|
-
for (let i = 0; i < lines.length; i += 1) {
|
|
135
|
-
const raw = lines[i] ?? '';
|
|
136
|
-
const startedInsideString = multiline !== null;
|
|
137
|
-
multiline = scanMultilineState(raw, multiline);
|
|
138
|
-
if (startedInsideString)
|
|
139
|
-
continue;
|
|
140
|
-
const trimmed = raw.trim();
|
|
141
|
-
const match = /^\[\[?([^\]]*)\]\]?\s*(#.*)?$/.exec(trimmed);
|
|
142
|
-
if (!match)
|
|
143
|
-
continue;
|
|
144
|
-
headers.push({ path: parseTomlHeaderPath(match[1] ?? ''), line: i });
|
|
145
|
-
}
|
|
146
|
-
return headers.map((h, idx) => ({
|
|
147
|
-
path: h.path,
|
|
148
|
-
startLine: h.line,
|
|
149
|
-
endLine: idx + 1 < headers.length ? (headers[idx + 1]?.line ?? lines.length) : lines.length,
|
|
150
|
-
}));
|
|
151
|
-
}
|
|
152
|
-
function removeTomlTables(content, match) {
|
|
153
|
-
if (content.trim().length === 0)
|
|
154
|
-
return content;
|
|
155
|
-
const lines = content.split('\n');
|
|
156
|
-
const doomed = new Set();
|
|
157
|
-
for (const table of parseTomlTables(content)) {
|
|
158
|
-
if (!match(table.path))
|
|
159
|
-
continue;
|
|
160
|
-
for (let i = table.startLine; i < table.endLine; i += 1)
|
|
161
|
-
doomed.add(i);
|
|
162
|
-
let above = table.startLine - 1;
|
|
163
|
-
while (above >= 0 && isManagedMarker(lines[above] ?? '')) {
|
|
164
|
-
doomed.add(above);
|
|
165
|
-
above -= 1;
|
|
166
|
-
}
|
|
167
|
-
while (above >= 0 && (lines[above] ?? 'x').trim() === '' && doomed.has(above + 1)) {
|
|
168
|
-
doomed.add(above);
|
|
169
|
-
above -= 1;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
if (doomed.size === 0)
|
|
173
|
-
return content;
|
|
174
|
-
const kept = lines.filter((_, i) => !doomed.has(i));
|
|
175
|
-
const collapsed = [];
|
|
176
|
-
for (const line of kept) {
|
|
177
|
-
const prev = collapsed.length > 0 ? (collapsed[collapsed.length - 1] ?? '') : null;
|
|
178
|
-
if (line.trim() === '' && prev !== null && prev.trim() === '')
|
|
179
|
-
continue;
|
|
180
|
-
collapsed.push(line);
|
|
181
|
-
}
|
|
182
|
-
return `${collapsed.join('\n').replace(/\n+$/, '')}\n`;
|
|
183
|
-
}
|
|
184
|
-
function appendTomlBlock(content, block) {
|
|
185
|
-
const base = content.trim().length === 0 ? '' : `${content.replace(/\n+$/, '')}\n`;
|
|
186
|
-
return `${base}\n${MANAGED_MARKER}\n${block.replace(/\n+$/, '')}\n`;
|
|
187
|
-
}
|
|
188
|
-
export function tomlString(value) {
|
|
189
|
-
return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
190
|
-
}
|
|
191
|
-
function unquoteTomlString(raw) {
|
|
192
|
-
const match = /^"((?:[^"\\]|\\.)*)"/.exec(raw.trim());
|
|
193
|
-
if (!match)
|
|
194
|
-
return null;
|
|
195
|
-
return (match[1] ?? '').replace(/\\"/g, '"').replace(/\\\\/g, '\\');
|
|
196
|
-
}
|
|
197
|
-
const MCP_ROOT_PATH = ['mcp_servers', MCP_KEY];
|
|
198
|
-
const MCP_HEADERS_PATH = ['mcp_servers', MCP_KEY, 'http_headers'];
|
|
199
|
-
const samePath = (a, b) => a.length === b.length && a.every((seg, i) => seg === b[i]);
|
|
200
|
-
const isOurAuthoredMcpTable = (path) => samePath(path, MCP_ROOT_PATH) || samePath(path, MCP_HEADERS_PATH);
|
|
201
|
-
const isMcpSubtreeOf = (path, key) => path.length >= 2 && path[0] === 'mcp_servers' && path[1] === key;
|
|
202
|
-
const isLegacyMcpSubtree = (path) => LEGACY_MCP_KEYS.some((key) => isMcpSubtreeOf(path, key));
|
|
203
|
-
const isOurMcpSubtree = (path) => isMcpSubtreeOf(path, MCP_KEY) || isLegacyMcpSubtree(path);
|
|
204
|
-
const tomlHeader = (path) => `[${path.map((s) => (/^[A-Za-z0-9_-]+$/.test(s) ? s : tomlString(s))).join('.')}]`;
|
|
205
|
-
function setTomlKey(content, tablePath, key, value) {
|
|
206
|
-
const target = parseTomlTables(content).find((t) => samePath(t.path, tablePath));
|
|
207
|
-
const line = `${key} = ${value}`;
|
|
208
|
-
if (target === undefined) {
|
|
209
|
-
return appendTomlBlock(content, `${tomlHeader(tablePath)}\n${line}`);
|
|
210
|
-
}
|
|
211
|
-
const lines = content.split('\n');
|
|
212
|
-
const keyPattern = new RegExp(`^\\s*${key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*=`);
|
|
213
|
-
for (let i = target.startLine + 1; i < target.endLine; i += 1) {
|
|
214
|
-
if (keyPattern.test(lines[i] ?? '')) {
|
|
215
|
-
lines[i] = line;
|
|
216
|
-
return lines.join('\n');
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
lines.splice(target.startLine + 1, 0, line);
|
|
220
|
-
return lines.join('\n');
|
|
221
|
-
}
|
|
222
|
-
export function mergeMcpServer(existing, mcpUrl, token) {
|
|
223
|
-
existing = removeTomlTables(existing, isLegacyMcpSubtree);
|
|
224
|
-
const authHeader = token.startsWith('Bearer ') ? token : `Bearer ${token}`;
|
|
225
|
-
const tables = parseTomlTables(existing);
|
|
226
|
-
const hasAny = tables.some((t) => isOurAuthoredMcpTable(t.path));
|
|
227
|
-
if (!hasAny) {
|
|
228
|
-
const block = [
|
|
229
|
-
tomlHeader(MCP_ROOT_PATH),
|
|
230
|
-
`url = ${tomlString(mcpUrl)}`,
|
|
231
|
-
'',
|
|
232
|
-
tomlHeader(MCP_HEADERS_PATH),
|
|
233
|
-
`Authorization = ${tomlString(authHeader)}`,
|
|
234
|
-
].join('\n');
|
|
235
|
-
return appendTomlBlock(existing, block);
|
|
236
|
-
}
|
|
237
|
-
let out = setTomlKey(existing, MCP_ROOT_PATH, 'url', tomlString(mcpUrl));
|
|
238
|
-
out = setTomlKey(out, MCP_HEADERS_PATH, 'Authorization', tomlString(authHeader));
|
|
239
|
-
return out;
|
|
240
|
-
}
|
|
241
|
-
export function removeMcpServer(existing) {
|
|
242
|
-
return removeTomlTables(existing, isOurMcpSubtree);
|
|
243
|
-
}
|
|
244
|
-
export function hasMcpServer(existing) {
|
|
245
|
-
return parseTomlTables(existing).some((t) => samePath(t.path, MCP_ROOT_PATH));
|
|
246
|
-
}
|
|
247
|
-
function readTableKey(existing, tableHeaders, key) {
|
|
248
|
-
if (existing.trim() === '')
|
|
249
|
-
return null;
|
|
250
|
-
let inTable = false;
|
|
251
|
-
let multiline = null;
|
|
252
|
-
for (const line of existing.split('\n')) {
|
|
253
|
-
const startedInsideString = multiline !== null;
|
|
254
|
-
multiline = scanMultilineState(line, multiline);
|
|
255
|
-
if (startedInsideString)
|
|
256
|
-
continue;
|
|
257
|
-
const trimmed = line.trim();
|
|
258
|
-
if (trimmed.startsWith('[')) {
|
|
259
|
-
inTable = tableHeaders.includes(trimmed);
|
|
260
|
-
continue;
|
|
261
|
-
}
|
|
262
|
-
if (!inTable || trimmed === '' || trimmed.startsWith('#'))
|
|
263
|
-
continue;
|
|
264
|
-
const eq = trimmed.indexOf('=');
|
|
265
|
-
if (eq === -1)
|
|
266
|
-
continue;
|
|
267
|
-
if (trimmed.slice(0, eq).trim() !== key)
|
|
268
|
-
continue;
|
|
269
|
-
return unquoteTomlString(trimmed.slice(eq + 1));
|
|
270
|
-
}
|
|
271
|
-
return null;
|
|
272
|
-
}
|
|
273
|
-
export function readInstalledToken(existing) {
|
|
274
|
-
const auth = readTableKey(existing, [
|
|
275
|
-
`[mcp_servers.${MCP_KEY}.http_headers]`,
|
|
276
|
-
`[mcp_servers."${MCP_KEY}".http_headers]`,
|
|
277
|
-
], 'Authorization');
|
|
278
|
-
if (auth === null || auth.trim() === '')
|
|
279
|
-
return null;
|
|
280
|
-
return auth.startsWith('Bearer ') ? auth.slice('Bearer '.length) : auth;
|
|
281
|
-
}
|
|
282
|
-
export function readInstalledMcpUrl(existing) {
|
|
283
|
-
return readTableKey(existing, [`[mcp_servers.${MCP_KEY}]`, `[mcp_servers."${MCP_KEY}"]`], 'url');
|
|
284
|
-
}
|
|
285
|
-
const isTrustTable = (hooksJsonPath) => (path) => path.length === 3 &&
|
|
286
|
-
path[0] === 'hooks' &&
|
|
287
|
-
path[1] === 'state' &&
|
|
288
|
-
(path[2] ?? '').startsWith(`${hooksJsonPath}:`);
|
|
289
|
-
export function mergeHookTrust(existing, hooksJsonPath, identities) {
|
|
290
|
-
const stripped = removeTomlTables(existing, isTrustTable(hooksJsonPath));
|
|
291
|
-
if (identities.length === 0)
|
|
292
|
-
return stripped;
|
|
293
|
-
const block = identities
|
|
294
|
-
.map((id) => `[hooks.state.${tomlString(id.key)}]\ntrusted_hash = ${tomlString(id.currentHash)}`)
|
|
295
|
-
.join('\n\n');
|
|
296
|
-
return appendTomlBlock(stripped, block);
|
|
297
|
-
}
|
|
298
|
-
export function removeHookTrust(existing, hooksJsonPath) {
|
|
299
|
-
return removeTomlTables(existing, isTrustTable(hooksJsonPath));
|
|
300
|
-
}
|
|
301
|
-
export function countHookTrustStamps(existing, hooksJsonPath) {
|
|
302
|
-
const lines = existing.split('\n');
|
|
303
|
-
return parseTomlTables(existing)
|
|
304
|
-
.filter((t) => isTrustTable(hooksJsonPath)(t.path))
|
|
305
|
-
.filter((t) => lines
|
|
306
|
-
.slice(t.startLine + 1, t.endLine)
|
|
307
|
-
.some((line) => /^\s*trusted_hash\s*=/.test(line))).length;
|
|
308
|
-
}
|
|
309
|
-
export function isOurHookCommand(command) {
|
|
310
|
-
if (typeof command !== 'string')
|
|
311
|
-
return false;
|
|
312
|
-
return HOOK_BASENAMES.some((base) => command.endsWith(base));
|
|
313
|
-
}
|
|
314
|
-
function stripOurHooks(event) {
|
|
315
|
-
if (!Array.isArray(event))
|
|
316
|
-
return [];
|
|
317
|
-
const kept = [];
|
|
318
|
-
for (const group of event) {
|
|
319
|
-
if (group === null || typeof group !== 'object' || Array.isArray(group)) {
|
|
320
|
-
kept.push(group);
|
|
321
|
-
continue;
|
|
322
|
-
}
|
|
323
|
-
const inner = group.hooks;
|
|
324
|
-
if (!Array.isArray(inner)) {
|
|
325
|
-
kept.push(group);
|
|
326
|
-
continue;
|
|
327
|
-
}
|
|
328
|
-
const keptInner = inner.filter((h) => {
|
|
329
|
-
if (h === null || typeof h !== 'object' || Array.isArray(h))
|
|
330
|
-
return true;
|
|
331
|
-
if (isUnrunnableCommandEntry(h))
|
|
332
|
-
return false;
|
|
333
|
-
return !isOurHookCommand(h.command);
|
|
334
|
-
});
|
|
335
|
-
if (keptInner.length === 0)
|
|
336
|
-
continue;
|
|
337
|
-
kept.push({ ...group, hooks: keptInner });
|
|
338
|
-
}
|
|
339
|
-
return kept;
|
|
340
|
-
}
|
|
341
|
-
function commandHookGroup(command, timeoutSeconds, statusMessage) {
|
|
342
|
-
const hook = {
|
|
343
|
-
type: 'command',
|
|
344
|
-
command,
|
|
345
|
-
timeout: timeoutSeconds,
|
|
346
|
-
};
|
|
347
|
-
if (statusMessage !== undefined)
|
|
348
|
-
hook.statusMessage = statusMessage;
|
|
349
|
-
return { hooks: [hook] };
|
|
350
|
-
}
|
|
351
|
-
export function mergeHooks(existing, paths) {
|
|
352
|
-
let root;
|
|
353
|
-
if (existing.trim().length === 0) {
|
|
354
|
-
root = {};
|
|
355
|
-
}
|
|
356
|
-
else {
|
|
357
|
-
try {
|
|
358
|
-
root = JSON.parse(existing);
|
|
359
|
-
}
|
|
360
|
-
catch (err) {
|
|
361
|
-
throw new Error(`Failed to parse hooks.json: ${err.message}`);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
if (root === null || typeof root !== 'object' || Array.isArray(root)) {
|
|
365
|
-
throw new Error('hooks.json root is not an object');
|
|
366
|
-
}
|
|
367
|
-
const rootObj = root;
|
|
368
|
-
rootObj.description = 'harbor shared-context hooks (team knowledge for bare Codex)';
|
|
369
|
-
let hooks = rootObj.hooks;
|
|
370
|
-
if (hooks === undefined) {
|
|
371
|
-
hooks = {};
|
|
372
|
-
rootObj.hooks = hooks;
|
|
373
|
-
}
|
|
374
|
-
if (hooks === null || typeof hooks !== 'object' || Array.isArray(hooks)) {
|
|
375
|
-
throw new Error('hooks.json `hooks` is not an object');
|
|
376
|
-
}
|
|
377
|
-
const hooksObj = hooks;
|
|
378
|
-
hooksObj[EVENT_USER_PROMPT_SUBMIT] = [
|
|
379
|
-
...stripOurHooks(hooksObj[EVENT_USER_PROMPT_SUBMIT]),
|
|
380
|
-
commandHookGroup(paths.contextHookPath, HOOK_CONTEXT_TIMEOUT_S, 'Loading harbor team context'),
|
|
381
|
-
];
|
|
382
|
-
hooksObj[EVENT_STOP] = [
|
|
383
|
-
...stripOurHooks(hooksObj[EVENT_STOP]),
|
|
384
|
-
commandHookGroup(paths.reportHookPath, HOOK_REPORT_TIMEOUT_S),
|
|
385
|
-
commandHookGroup(paths.captureHookPath, HOOK_CAPTURE_TIMEOUT_S),
|
|
386
|
-
];
|
|
387
|
-
hooksObj[EVENT_POST_TOOL_USE] = [
|
|
388
|
-
...stripOurHooks(hooksObj[EVENT_POST_TOOL_USE]),
|
|
389
|
-
commandHookGroup(paths.toolContextHookPath, HOOK_TOOLCONTEXT_TIMEOUT_S),
|
|
390
|
-
];
|
|
391
|
-
hooksObj[EVENT_PRE_TOOL_USE] = [
|
|
392
|
-
...stripOurHooks(hooksObj[EVENT_PRE_TOOL_USE]),
|
|
393
|
-
commandHookGroup(paths.toolContextHookPath, HOOK_TOOLCONTEXT_TIMEOUT_S),
|
|
394
|
-
];
|
|
395
|
-
hooksObj[EVENT_POST_COMPACT] = [
|
|
396
|
-
...stripOurHooks(hooksObj[EVENT_POST_COMPACT]),
|
|
397
|
-
commandHookGroup(paths.compactHookPath, HOOK_COMPACT_TIMEOUT_S),
|
|
398
|
-
];
|
|
399
|
-
return `${JSON.stringify(rootObj, null, 2)}\n`;
|
|
400
|
-
}
|
|
401
|
-
export function removeHooks(existing) {
|
|
402
|
-
if (existing.trim() === '')
|
|
403
|
-
return existing;
|
|
404
|
-
let root;
|
|
405
|
-
try {
|
|
406
|
-
root = JSON.parse(existing);
|
|
407
|
-
}
|
|
408
|
-
catch (err) {
|
|
409
|
-
throw new Error(`Failed to parse hooks.json: ${err.message}`);
|
|
410
|
-
}
|
|
411
|
-
if (root === null || typeof root !== 'object' || Array.isArray(root)) {
|
|
412
|
-
throw new Error('hooks.json root is not an object');
|
|
413
|
-
}
|
|
414
|
-
const rootObj = root;
|
|
415
|
-
const hooks = rootObj.hooks;
|
|
416
|
-
if (hooks !== null && typeof hooks === 'object' && !Array.isArray(hooks)) {
|
|
417
|
-
const hooksObj = hooks;
|
|
418
|
-
for (const event of CODEX_HOOK_EVENTS) {
|
|
419
|
-
if (!(event in hooksObj))
|
|
420
|
-
continue;
|
|
421
|
-
const stripped = stripOurHooks(hooksObj[event]);
|
|
422
|
-
if (stripped.length === 0)
|
|
423
|
-
delete hooksObj[event];
|
|
424
|
-
else
|
|
425
|
-
hooksObj[event] = stripped;
|
|
426
|
-
}
|
|
427
|
-
if (Object.keys(hooksObj).length === 0) {
|
|
428
|
-
delete rootObj.hooks;
|
|
429
|
-
delete rootObj.description;
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
return `${JSON.stringify(rootObj, null, 2)}\n`;
|
|
433
|
-
}
|
|
434
|
-
export function readInstalledHooks(existing) {
|
|
435
|
-
const commands = [];
|
|
436
|
-
const events = [];
|
|
437
|
-
const empty = { installed: false, commands, events };
|
|
438
|
-
if (existing.trim() === '')
|
|
439
|
-
return empty;
|
|
440
|
-
let root;
|
|
441
|
-
try {
|
|
442
|
-
root = JSON.parse(existing);
|
|
443
|
-
}
|
|
444
|
-
catch {
|
|
445
|
-
return empty;
|
|
446
|
-
}
|
|
447
|
-
const hooks = root?.hooks;
|
|
448
|
-
if (hooks === null || typeof hooks !== 'object' || Array.isArray(hooks)) {
|
|
449
|
-
return empty;
|
|
450
|
-
}
|
|
451
|
-
for (const event of CODEX_HOOK_EVENTS) {
|
|
452
|
-
const groups = hooks[event];
|
|
453
|
-
if (!Array.isArray(groups))
|
|
454
|
-
continue;
|
|
455
|
-
const before = commands.length;
|
|
456
|
-
for (const group of groups) {
|
|
457
|
-
if (group === null || typeof group !== 'object')
|
|
458
|
-
continue;
|
|
459
|
-
const inner = group.hooks;
|
|
460
|
-
if (!Array.isArray(inner))
|
|
461
|
-
continue;
|
|
462
|
-
for (const h of inner) {
|
|
463
|
-
if (h === null || typeof h !== 'object')
|
|
464
|
-
continue;
|
|
465
|
-
const command = h.command;
|
|
466
|
-
if (isOurHookCommand(command))
|
|
467
|
-
commands.push(String(command));
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
if (commands.length > before)
|
|
471
|
-
events.push(event);
|
|
472
|
-
}
|
|
473
|
-
return { installed: commands.length > 0, commands, events };
|
|
474
|
-
}
|
|
475
|
-
export function hasHooks(existing) {
|
|
476
|
-
return readInstalledHooks(existing).installed;
|
|
477
|
-
}
|
|
478
|
-
const ensureCodexDir = ensureConfigDir;
|
|
479
|
-
export async function readCodexConfig(home) {
|
|
480
|
-
return readIfExists(resolveCodexPaths(home).configTomlPath);
|
|
481
|
-
}
|
|
482
|
-
export async function readCodexHooksJson(home) {
|
|
483
|
-
return readIfExists(resolveCodexPaths(home).hooksJsonPath);
|
|
484
|
-
}
|
|
485
|
-
export async function installMcpServer(opts) {
|
|
486
|
-
const paths = resolveCodexPaths(opts.home);
|
|
487
|
-
await ensureCodexDir(paths.codexDir);
|
|
488
|
-
await updateFileLocked(paths.configTomlPath, (existing) => mergeMcpServer(existing, opts.mcpUrl, opts.token));
|
|
489
|
-
return paths.configTomlPath;
|
|
490
|
-
}
|
|
491
|
-
export async function uninstallMcpServer(home) {
|
|
492
|
-
const paths = resolveCodexPaths(home);
|
|
493
|
-
await updateFileLocked(paths.configTomlPath, (existing) => removeMcpServer(existing));
|
|
494
|
-
return paths.configTomlPath;
|
|
495
|
-
}
|
|
496
|
-
export async function installHooks(paths, home) {
|
|
497
|
-
const codex = resolveCodexPaths(home);
|
|
498
|
-
await ensureCodexDir(codex.codexDir);
|
|
499
|
-
await updateFileLocked(codex.hooksJsonPath, (existing) => mergeHooks(existing, paths));
|
|
500
|
-
return codex.hooksJsonPath;
|
|
501
|
-
}
|
|
502
|
-
export async function uninstallHooks(home) {
|
|
503
|
-
const paths = resolveCodexPaths(home);
|
|
504
|
-
await updateFileLocked(paths.hooksJsonPath, (existing) => removeHooks(existing));
|
|
505
|
-
const { realpath } = await import('node:fs/promises');
|
|
506
|
-
const realHooksPath = await realpath(paths.hooksJsonPath).catch(() => paths.hooksJsonPath);
|
|
507
|
-
await updateFileLocked(paths.configTomlPath, (existing) => {
|
|
508
|
-
let cleaned = removeHookTrust(existing, paths.hooksJsonPath);
|
|
509
|
-
if (realHooksPath !== paths.hooksJsonPath) {
|
|
510
|
-
cleaned = removeHookTrust(cleaned, realHooksPath);
|
|
511
|
-
}
|
|
512
|
-
return cleaned;
|
|
513
|
-
});
|
|
514
|
-
return paths.hooksJsonPath;
|
|
515
|
-
}
|
|
516
|
-
export async function stampHookTrust(opts) {
|
|
517
|
-
const paths = resolveCodexPaths(opts.home);
|
|
518
|
-
const keyPrefix = opts.hooksJsonPath ?? paths.hooksJsonPath;
|
|
519
|
-
await updateFileLocked(paths.configTomlPath, (existing) => mergeHookTrust(existing, keyPrefix, opts.identities));
|
|
520
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { type HookPaths } from './claude.js';
|
|
2
|
-
export declare const MANAGED_HOOK_EVENTS: readonly string[];
|
|
3
|
-
export interface CursorPaths {
|
|
4
|
-
readonly cursorDir: string;
|
|
5
|
-
readonly mcpJsonPath: string;
|
|
6
|
-
readonly hooksJsonPath: string;
|
|
7
|
-
}
|
|
8
|
-
export declare function resolveCursorPaths(home?: string): CursorPaths;
|
|
9
|
-
export declare function stripJsonComments(raw: string): {
|
|
10
|
-
readonly text: string;
|
|
11
|
-
readonly hadComments: boolean;
|
|
12
|
-
};
|
|
13
|
-
export interface CursorMcpServerEntry {
|
|
14
|
-
readonly url: string;
|
|
15
|
-
readonly headers: {
|
|
16
|
-
readonly Authorization: string;
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
export declare function buildMcpServerEntry(mcpUrl: string, token: string): CursorMcpServerEntry;
|
|
20
|
-
export declare function mergeMcpServer(existing: string, mcpUrl: string, token: string): string;
|
|
21
|
-
export declare function removeMcpServer(existing: string): string;
|
|
22
|
-
export declare function hasMcpServer(existing: string): boolean;
|
|
23
|
-
export declare function readInstalledToken(existing: string): string | null;
|
|
24
|
-
export declare function readInstalledMcpUrl(existing: string): string | null;
|
|
25
|
-
export declare function shellQuote(command: string): string;
|
|
26
|
-
export declare function isOurHookCommand(command: unknown): boolean;
|
|
27
|
-
export declare function mergeHooks(existing: string, paths: HookPaths): string;
|
|
28
|
-
export declare function removeHooks(existing: string): string;
|
|
29
|
-
export interface InstalledHooks {
|
|
30
|
-
readonly installed: boolean;
|
|
31
|
-
readonly commands: readonly string[];
|
|
32
|
-
readonly events: readonly string[];
|
|
33
|
-
}
|
|
34
|
-
export declare function readInstalledHooks(existing: string): InstalledHooks;
|
|
35
|
-
export declare function hasHooks(existing: string): boolean;
|
|
36
|
-
export declare function readCursorMcpJson(home?: string): Promise<string>;
|
|
37
|
-
export declare function readCursorHooksJson(home?: string): Promise<string>;
|
|
38
|
-
export declare function installMcpServer(opts: {
|
|
39
|
-
readonly mcpUrl: string;
|
|
40
|
-
readonly token: string;
|
|
41
|
-
readonly home?: string;
|
|
42
|
-
}): Promise<string>;
|
|
43
|
-
export declare function uninstallMcpServer(home?: string): Promise<string>;
|
|
44
|
-
export declare function installHooks(paths: HookPaths, home?: string): Promise<string>;
|
|
45
|
-
export declare function uninstallHooks(home?: string): Promise<string>;
|