@secemp/elwood 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/LICENSE +21 -0
- package/README.md +132 -0
- package/examples/01-basic-query.js +38 -0
- package/examples/02-bug-fixer.js +57 -0
- package/examples/03-custom-system-prompt.js +41 -0
- package/examples/04-read-only-agent.js +38 -0
- package/examples/05-find-todos.js +37 -0
- package/examples/06-session-resume.js +70 -0
- package/examples/07-hooks-pretooluse.js +74 -0
- package/examples/08-hooks-posttooluse-audit.js +66 -0
- package/examples/09-hooks-block-etc.js +68 -0
- package/examples/10-hooks-redirect-sandbox.js +70 -0
- package/examples/11-subagents.js +54 -0
- package/examples/12-mcp-stdio.js +48 -0
- package/examples/13-mcp-http.js +54 -0
- package/examples/14-custom-tool.js +84 -0
- package/examples/15-custom-tool-unit-converter.js +132 -0
- package/examples/16-mcp-github.js +71 -0
- package/examples/17-session-store-postgres.js +78 -0
- package/examples/18-session-store-redis.js +65 -0
- package/examples/19-session-store-s3.js +67 -0
- package/examples/20-session-list.js +72 -0
- package/examples/21-hooks-notification-slack.js +78 -0
- package/examples/22-hooks-webhook-posttooluse.js +78 -0
- package/examples/23-hooks-subagent-tracker.js +59 -0
- package/examples/24-v2-session-api.js +62 -0
- package/examples/README.md +95 -0
- package/examples/basic.js +240 -0
- package/examples/smoke-test.js +296 -0
- package/package.json +52 -0
- package/src/ast-tools.js +182 -0
- package/src/index.js +70 -0
- package/src/instrumenter.js +2921 -0
- package/src/loader.js +306 -0
- package/src/locate.js +296 -0
- package/src/query.js +2168 -0
package/src/ast-tools.js
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ast-tools.js
|
|
3
|
+
*
|
|
4
|
+
* Low-level Babel AST helpers: parse, traverse, and generate.
|
|
5
|
+
*
|
|
6
|
+
* Parser options are tuned for the Claude Code CLI bundle, which is a
|
|
7
|
+
* minified ESM file (sourceType: 'module') and may contain JSX and
|
|
8
|
+
* TypeScript-style syntax emitted by the bundler. The same options are used
|
|
9
|
+
* in generic-dependency-splitter.js — reused verbatim here.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { readFileSync } from 'node:fs';
|
|
13
|
+
import { parse as babelParse } from '@babel/parser';
|
|
14
|
+
import _traverse from '@babel/traverse';
|
|
15
|
+
import _generate from '@babel/generator';
|
|
16
|
+
import * as t from '@babel/types';
|
|
17
|
+
|
|
18
|
+
// Handle both CommonJS-wrapped and native ESM default exports from Babel
|
|
19
|
+
const traverse = _traverse.default ?? _traverse;
|
|
20
|
+
const generate = _generate.default ?? _generate;
|
|
21
|
+
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Parser configuration
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Babel parser options that work for the Claude Code CLI bundle.
|
|
28
|
+
*
|
|
29
|
+
* Key choices:
|
|
30
|
+
* - sourceType: 'module' — cli.js starts with `import {…} from …`
|
|
31
|
+
* - errorRecovery: true — minified code sometimes hits minor spec
|
|
32
|
+
* edge-cases; we want a best-effort parse
|
|
33
|
+
* - plugins: jsx + typescript — bundlers may leave JSX/TS syntax behind;
|
|
34
|
+
* decorators-legacy for class decorators
|
|
35
|
+
*/
|
|
36
|
+
const PARSER_OPTIONS = {
|
|
37
|
+
sourceType: 'module',
|
|
38
|
+
plugins: ['jsx', 'typescript', 'decorators-legacy'],
|
|
39
|
+
errorRecovery: true,
|
|
40
|
+
// Increase token limit for 9 MB+ bundles
|
|
41
|
+
createParenthesizedExpressions: false,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Core API
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Read a file from disk and parse it into a Babel AST.
|
|
50
|
+
*
|
|
51
|
+
* @param {string} filePath - absolute path to the JS/MJS file
|
|
52
|
+
* @param {import('@babel/parser').ParserOptions} [extraOptions] - merged with defaults
|
|
53
|
+
* @returns {import('@babel/types').File} Babel AST File node
|
|
54
|
+
*/
|
|
55
|
+
function parseBundle(filePath, extraOptions = {}) {
|
|
56
|
+
const code = readFileSync(filePath, 'utf8');
|
|
57
|
+
return parseBundleCode(code, { ...PARSER_OPTIONS, ...extraOptions });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Parse a string of code into a Babel AST.
|
|
62
|
+
*
|
|
63
|
+
* @param {string} code
|
|
64
|
+
* @param {import('@babel/parser').ParserOptions} [extraOptions]
|
|
65
|
+
* @returns {import('@babel/types').File}
|
|
66
|
+
*/
|
|
67
|
+
function parseBundleCode(code, extraOptions = {}) {
|
|
68
|
+
const options = { ...PARSER_OPTIONS, ...extraOptions };
|
|
69
|
+
|
|
70
|
+
// Babel's parse() may throw even with errorRecovery for some edge cases.
|
|
71
|
+
// If it does, try again with a more permissive set.
|
|
72
|
+
try {
|
|
73
|
+
return babelParse(code, options);
|
|
74
|
+
} catch (firstErr) {
|
|
75
|
+
try {
|
|
76
|
+
return babelParse(code, {
|
|
77
|
+
...options,
|
|
78
|
+
// Drop TypeScript plugin as a last resort — it can trip on some
|
|
79
|
+
// patterns the ts plugin considers invalid even in JS files.
|
|
80
|
+
plugins: ['jsx', 'decorators-legacy'],
|
|
81
|
+
});
|
|
82
|
+
} catch {
|
|
83
|
+
// Re-throw the original, more informative error
|
|
84
|
+
throw firstErr;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Traverse a Babel AST with a visitor object.
|
|
91
|
+
*
|
|
92
|
+
* This is a thin wrapper around @babel/traverse that handles the CJS/ESM
|
|
93
|
+
* default-export discrepancy and provides consistent call semantics.
|
|
94
|
+
*
|
|
95
|
+
* @param {import('@babel/types').File} ast - root AST node (File or Program)
|
|
96
|
+
* @param {import('@babel/traverse').Visitor} visitors - visitor map
|
|
97
|
+
* @param {Record<string, unknown>} [scope] - optional scope bindings passed as state
|
|
98
|
+
* @returns {void}
|
|
99
|
+
*/
|
|
100
|
+
function traverseBundle(ast, visitors, scope = {}) {
|
|
101
|
+
traverse(ast, visitors, /* scope */ undefined, /* state */ scope);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Generate JavaScript source code from a Babel AST node.
|
|
106
|
+
*
|
|
107
|
+
* @param {import('@babel/types').Node} ast - any Babel node (File, Program, expression…)
|
|
108
|
+
* @param {import('@babel/generator').GeneratorOptions} [options]
|
|
109
|
+
* @param {string} [originalSource] - original source code; when provided, @babel/generator
|
|
110
|
+
* uses source-range-based generation to preserve original identifier names rather than
|
|
111
|
+
* regenerating them from AST node IDs. This is critical for CLI export fingerprinting:
|
|
112
|
+
* without it, minified names like `MC1` get replaced by Babel-internal names like `Hi8`.
|
|
113
|
+
* @returns {{ code: string, map: object | null }}
|
|
114
|
+
*/
|
|
115
|
+
function generateCode(ast, options = {}, originalSource) {
|
|
116
|
+
const result = generate(
|
|
117
|
+
ast,
|
|
118
|
+
{
|
|
119
|
+
// Compact output — we're not trying to produce readable code, just
|
|
120
|
+
// functional instrumented code. Set to false for debugging.
|
|
121
|
+
compact: false,
|
|
122
|
+
concise: false,
|
|
123
|
+
comments: true,
|
|
124
|
+
...options,
|
|
125
|
+
},
|
|
126
|
+
originalSource
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Convenience: parse → traverse → generate in one call.
|
|
134
|
+
*
|
|
135
|
+
* @param {string} filePath
|
|
136
|
+
* @param {import('@babel/traverse').Visitor} visitors
|
|
137
|
+
* @param {import('@babel/generator').GeneratorOptions} [genOptions]
|
|
138
|
+
* @returns {{ code: string, ast: import('@babel/types').File }}
|
|
139
|
+
*/
|
|
140
|
+
function transformBundle(filePath, visitors, genOptions = {}) {
|
|
141
|
+
const ast = parseBundle(filePath);
|
|
142
|
+
traverseBundle(ast, visitors);
|
|
143
|
+
const { code } = generateCode(ast, genOptions);
|
|
144
|
+
return { code, ast };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Convenience: parse a code string → traverse → generate.
|
|
149
|
+
*
|
|
150
|
+
* @param {string} code
|
|
151
|
+
* @param {import('@babel/traverse').Visitor} visitors
|
|
152
|
+
* @param {import('@babel/generator').GeneratorOptions} [genOptions]
|
|
153
|
+
* @returns {{ code: string, ast: import('@babel/types').File }}
|
|
154
|
+
*/
|
|
155
|
+
function transformCode(code, visitors, genOptions = {}) {
|
|
156
|
+
const ast = parseBundleCode(code);
|
|
157
|
+
traverseBundle(ast, visitors);
|
|
158
|
+
const { code: out } = generateCode(ast, genOptions);
|
|
159
|
+
return { code: out, ast };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
// Re-export babel utilities so callers don't need direct babel deps
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
165
|
+
|
|
166
|
+
export {
|
|
167
|
+
// Core API
|
|
168
|
+
parseBundle,
|
|
169
|
+
parseBundleCode,
|
|
170
|
+
traverseBundle,
|
|
171
|
+
generateCode,
|
|
172
|
+
transformBundle,
|
|
173
|
+
transformCode,
|
|
174
|
+
|
|
175
|
+
// Re-exports of the underlying Babel primitives
|
|
176
|
+
traverse,
|
|
177
|
+
generate,
|
|
178
|
+
t,
|
|
179
|
+
|
|
180
|
+
// Parser options — exported so other modules can extend them
|
|
181
|
+
PARSER_OPTIONS,
|
|
182
|
+
};
|
package/src/index.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* elwood — public API
|
|
3
|
+
*
|
|
4
|
+
* Programmatically import and instrument the locally installed Claude Code CLI
|
|
5
|
+
* via Babel AST.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
*
|
|
9
|
+
* import { locate, load, instrument } from 'elwood';
|
|
10
|
+
*
|
|
11
|
+
* // Install hooks before loading
|
|
12
|
+
* globalThis.__elwoodHooks = {
|
|
13
|
+
* onCall(fnName, args) { console.log('[call]', fnName); },
|
|
14
|
+
* onApiCall(label, url) { console.log('[api]', url); },
|
|
15
|
+
* onToolCall(name, firstArg) { console.log('[tool]', name, firstArg); },
|
|
16
|
+
* onStream(generatorName) { console.log('[stream]', generatorName); },
|
|
17
|
+
* };
|
|
18
|
+
*
|
|
19
|
+
* const { module, exports, teardown } = await load({ verbose: true });
|
|
20
|
+
* // … use exports …
|
|
21
|
+
* teardown(); // clean up the temp file
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
// ── Location ────────────────────────────────────────────────────────────────
|
|
25
|
+
export { locate, locateAsync } from './locate.js';
|
|
26
|
+
|
|
27
|
+
// ── Load + instrument pipeline ───────────────────────────────────────────────
|
|
28
|
+
export { load, instrument } from './loader.js';
|
|
29
|
+
|
|
30
|
+
// ── Raw AST tools ────────────────────────────────────────────────────────────
|
|
31
|
+
export {
|
|
32
|
+
parseBundle,
|
|
33
|
+
parseBundleCode,
|
|
34
|
+
traverseBundle,
|
|
35
|
+
generateCode,
|
|
36
|
+
transformBundle,
|
|
37
|
+
transformCode,
|
|
38
|
+
PARSER_OPTIONS,
|
|
39
|
+
} from './ast-tools.js';
|
|
40
|
+
|
|
41
|
+
// ── Instrumentation primitives ───────────────────────────────────────────────
|
|
42
|
+
export {
|
|
43
|
+
injectHooks,
|
|
44
|
+
detectBundlerPattern,
|
|
45
|
+
extractFunctionMap,
|
|
46
|
+
findCliExports,
|
|
47
|
+
injectCliExports,
|
|
48
|
+
DEFAULT_HOOK_VAR,
|
|
49
|
+
ANTHROPIC_API_HOST,
|
|
50
|
+
} from './instrumenter.js';
|
|
51
|
+
|
|
52
|
+
// ── Subprocess-free query() ──────────────────────────────────────────────────
|
|
53
|
+
export {
|
|
54
|
+
query,
|
|
55
|
+
AbortError,
|
|
56
|
+
tool,
|
|
57
|
+
createSdkMcpServer,
|
|
58
|
+
preload,
|
|
59
|
+
// Hook event and exit reason constants
|
|
60
|
+
HOOK_EVENTS,
|
|
61
|
+
EXIT_REASONS,
|
|
62
|
+
// Session utility functions (pure Node.js file I/O)
|
|
63
|
+
listSessions,
|
|
64
|
+
getSessionMessages,
|
|
65
|
+
getSessionInfo,
|
|
66
|
+
// V2 Session API (UNSTABLE)
|
|
67
|
+
unstable_v2_createSession,
|
|
68
|
+
unstable_v2_resumeSession,
|
|
69
|
+
unstable_v2_prompt,
|
|
70
|
+
} from './query.js';
|