@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.
Files changed (36) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +132 -0
  3. package/examples/01-basic-query.js +38 -0
  4. package/examples/02-bug-fixer.js +57 -0
  5. package/examples/03-custom-system-prompt.js +41 -0
  6. package/examples/04-read-only-agent.js +38 -0
  7. package/examples/05-find-todos.js +37 -0
  8. package/examples/06-session-resume.js +70 -0
  9. package/examples/07-hooks-pretooluse.js +74 -0
  10. package/examples/08-hooks-posttooluse-audit.js +66 -0
  11. package/examples/09-hooks-block-etc.js +68 -0
  12. package/examples/10-hooks-redirect-sandbox.js +70 -0
  13. package/examples/11-subagents.js +54 -0
  14. package/examples/12-mcp-stdio.js +48 -0
  15. package/examples/13-mcp-http.js +54 -0
  16. package/examples/14-custom-tool.js +84 -0
  17. package/examples/15-custom-tool-unit-converter.js +132 -0
  18. package/examples/16-mcp-github.js +71 -0
  19. package/examples/17-session-store-postgres.js +78 -0
  20. package/examples/18-session-store-redis.js +65 -0
  21. package/examples/19-session-store-s3.js +67 -0
  22. package/examples/20-session-list.js +72 -0
  23. package/examples/21-hooks-notification-slack.js +78 -0
  24. package/examples/22-hooks-webhook-posttooluse.js +78 -0
  25. package/examples/23-hooks-subagent-tracker.js +59 -0
  26. package/examples/24-v2-session-api.js +62 -0
  27. package/examples/README.md +95 -0
  28. package/examples/basic.js +240 -0
  29. package/examples/smoke-test.js +296 -0
  30. package/package.json +52 -0
  31. package/src/ast-tools.js +182 -0
  32. package/src/index.js +70 -0
  33. package/src/instrumenter.js +2921 -0
  34. package/src/loader.js +306 -0
  35. package/src/locate.js +296 -0
  36. package/src/query.js +2168 -0
@@ -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';