xray-code 0.1.1 → 0.1.2
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/cli.js +7 -2
- package/dist/install.d.ts +10 -0
- package/dist/install.js +265 -0
- package/dist/onboard.js +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -19,6 +19,7 @@ const commander_1 = require("commander");
|
|
|
19
19
|
const auth_js_1 = require("./auth.js");
|
|
20
20
|
const onboard_js_1 = require("./onboard.js");
|
|
21
21
|
const client_js_1 = require("./client.js");
|
|
22
|
+
const install_js_1 = require("./install.js");
|
|
22
23
|
const program = new commander_1.Command()
|
|
23
24
|
.name("xray")
|
|
24
25
|
.description("X-ray your codebase — instant cross-references and impact analysis")
|
|
@@ -109,10 +110,12 @@ program
|
|
|
109
110
|
catch {
|
|
110
111
|
console.log(`\n GitHub login skipped — you can run 'npx xray-code login' later.\n`);
|
|
111
112
|
}
|
|
112
|
-
// Step 3:
|
|
113
|
+
// Step 3: Install Claude Code integration in current project
|
|
113
114
|
const client = new client_js_1.XRayClient(data.server, data.api_key);
|
|
114
115
|
try {
|
|
115
116
|
const health = await client.health();
|
|
117
|
+
console.log(`\n Setting up Claude Code integration...\n`);
|
|
118
|
+
(0, install_js_1.installClaude)(process.cwd(), data.server, data.api_key, data.space, health.symbols, health.uses);
|
|
116
119
|
console.log(`\n ══════════════════════════════════════════════════`);
|
|
117
120
|
console.log(` XRay is ready!`);
|
|
118
121
|
console.log(` ══════════════════════════════════════════════════\n`);
|
|
@@ -120,7 +123,9 @@ program
|
|
|
120
123
|
console.log(` Symbols: ${health.symbols.toLocaleString()}`);
|
|
121
124
|
console.log(` Refs: ${health.uses.toLocaleString()}`);
|
|
122
125
|
console.log(` Files: ${health.files.toLocaleString()}\n`);
|
|
123
|
-
console.log(`
|
|
126
|
+
console.log(` Your Claude Code agent now has ambient code intelligence.`);
|
|
127
|
+
console.log(` Every Grep/Glob search automatically gets cross-reference context.\n`);
|
|
128
|
+
console.log(` Manual commands:`);
|
|
124
129
|
console.log(` npx xray-code search <symbol-name>`);
|
|
125
130
|
console.log(` npx xray-code who-uses <symbol-name>`);
|
|
126
131
|
console.log(` npx xray-code impact <symbol-name>`);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XRay Claude Code integration installer.
|
|
3
|
+
*
|
|
4
|
+
* After activation, installs:
|
|
5
|
+
* .claude/hooks/xray-inject.sh — ambient code intelligence on Grep/Glob
|
|
6
|
+
* .claude/settings.json — registers the hook
|
|
7
|
+
* CLAUDE.md — adds XRay section
|
|
8
|
+
* .claude/memory/xray.md — memory file for AI context
|
|
9
|
+
*/
|
|
10
|
+
export declare function installClaude(projectDir: string, server: string, apiKey: string, space: string, symbols: number, refs: number): void;
|
package/dist/install.js
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* XRay Claude Code integration installer.
|
|
4
|
+
*
|
|
5
|
+
* After activation, installs:
|
|
6
|
+
* .claude/hooks/xray-inject.sh — ambient code intelligence on Grep/Glob
|
|
7
|
+
* .claude/settings.json — registers the hook
|
|
8
|
+
* CLAUDE.md — adds XRay section
|
|
9
|
+
* .claude/memory/xray.md — memory file for AI context
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.installClaude = installClaude;
|
|
13
|
+
const node_fs_1 = require("node:fs");
|
|
14
|
+
const node_path_1 = require("node:path");
|
|
15
|
+
const HOOK_SCRIPT = `#!/usr/bin/env bash
|
|
16
|
+
# XRay Code Intelligence — ambient cross-reference injection
|
|
17
|
+
#
|
|
18
|
+
# PreToolUse:Grep|Glob
|
|
19
|
+
#
|
|
20
|
+
# When Claude searches for something, this hook queries the XRay API
|
|
21
|
+
# and injects cross-reference context automatically.
|
|
22
|
+
|
|
23
|
+
set -uo pipefail
|
|
24
|
+
|
|
25
|
+
XRAY_SERVER="\${XRAY_SERVER:-__XRAY_SERVER__}"
|
|
26
|
+
XRAY_KEY="\${XRAY_API_KEY:-__XRAY_KEY__}"
|
|
27
|
+
MAX_SYMBOLS=3
|
|
28
|
+
RATE_LIMIT_SECONDS=5
|
|
29
|
+
RATE_FILE="/tmp/.xray-inject-last"
|
|
30
|
+
|
|
31
|
+
RAW_INPUT=""
|
|
32
|
+
if [[ -t 0 ]]; then
|
|
33
|
+
RAW_INPUT="\${1:-}"
|
|
34
|
+
else
|
|
35
|
+
RAW_INPUT=$(cat)
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
[[ -z "$RAW_INPUT" ]] && exit 0
|
|
39
|
+
command -v jq &>/dev/null || exit 0
|
|
40
|
+
command -v curl &>/dev/null || exit 0
|
|
41
|
+
|
|
42
|
+
TOOL_NAME=$(echo "$RAW_INPUT" | jq -r '.tool_name // empty' 2>/dev/null) || exit 0
|
|
43
|
+
if [[ "$TOOL_NAME" != "Grep" ]] && [[ "$TOOL_NAME" != "Glob" ]]; then
|
|
44
|
+
exit 0
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
PATTERN=$(echo "$RAW_INPUT" | jq -r '.tool_input.pattern // empty' 2>/dev/null) || exit 0
|
|
48
|
+
[[ -z "$PATTERN" ]] && exit 0
|
|
49
|
+
|
|
50
|
+
SEARCH_TERM="$PATTERN"
|
|
51
|
+
SEARCH_TERM="\${SEARCH_TERM//\\*/}"
|
|
52
|
+
SEARCH_TERM="\${SEARCH_TERM//\\?/}"
|
|
53
|
+
SEARCH_TERM="\${SEARCH_TERM//\\^/}"
|
|
54
|
+
SEARCH_TERM="\${SEARCH_TERM//$/}"
|
|
55
|
+
SEARCH_TERM="\${SEARCH_TERM//\\\\b/}"
|
|
56
|
+
SEARCH_TERM="\${SEARCH_TERM//\\\\s+/ }"
|
|
57
|
+
SEARCH_TERM="\${SEARCH_TERM//\\\\w+/}"
|
|
58
|
+
SEARCH_TERM="\${SEARCH_TERM//\\.\\*/}"
|
|
59
|
+
SEARCH_TERM="\${SEARCH_TERM//\\(/}"
|
|
60
|
+
SEARCH_TERM="\${SEARCH_TERM//\\)/}"
|
|
61
|
+
SEARCH_TERM="\${SEARCH_TERM//\\[/}"
|
|
62
|
+
SEARCH_TERM="\${SEARCH_TERM//\\]/}"
|
|
63
|
+
SEARCH_TERM="\${SEARCH_TERM//\\{/}"
|
|
64
|
+
SEARCH_TERM="\${SEARCH_TERM//\\}/}"
|
|
65
|
+
SEARCH_TERM="\${SEARCH_TERM//|/}"
|
|
66
|
+
SEARCH_TERM=$(echo "$SEARCH_TERM" | sed 's|.*/||')
|
|
67
|
+
SEARCH_TERM="\${SEARCH_TERM//\\./}"
|
|
68
|
+
SEARCH_TERM=$(echo "$SEARCH_TERM" | xargs)
|
|
69
|
+
|
|
70
|
+
if [[ "$SEARCH_TERM" == *" "* ]]; then
|
|
71
|
+
LONGEST=""
|
|
72
|
+
for word in $SEARCH_TERM; do
|
|
73
|
+
if [[ \${#word} -gt \${#LONGEST} ]]; then
|
|
74
|
+
LONGEST="$word"
|
|
75
|
+
fi
|
|
76
|
+
done
|
|
77
|
+
SEARCH_TERM="$LONGEST"
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
[[ \${#SEARCH_TERM} -lt 4 ]] && exit 0
|
|
81
|
+
|
|
82
|
+
case "\${SEARCH_TERM,,}" in
|
|
83
|
+
import|require|function|class|struct|enum|trait|interface|const|type|return|async|await|pub|fn|let|mut|self|impl|use|mod|crate)
|
|
84
|
+
exit 0 ;;
|
|
85
|
+
esac
|
|
86
|
+
|
|
87
|
+
NOW=$(date +%s)
|
|
88
|
+
if [[ -f "$RATE_FILE" ]]; then
|
|
89
|
+
LAST=$(cat "$RATE_FILE" 2>/dev/null || echo "0")
|
|
90
|
+
if (( NOW - LAST < RATE_LIMIT_SECONDS )); then
|
|
91
|
+
exit 0
|
|
92
|
+
fi
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
CURL_ARGS=(-s --max-time 3)
|
|
96
|
+
if [[ -n "$XRAY_KEY" ]]; then
|
|
97
|
+
CURL_ARGS+=(-H "X-FoxRef-Key: $XRAY_KEY")
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
SEARCH_OUTPUT=$(curl "\${CURL_ARGS[@]}" "\${XRAY_SERVER}/api/v1/search?q=\${SEARCH_TERM}&limit=\${MAX_SYMBOLS}" 2>/dev/null) || exit 0
|
|
101
|
+
[[ -z "$SEARCH_OUTPUT" ]] && exit 0
|
|
102
|
+
|
|
103
|
+
TOTAL=$(echo "$SEARCH_OUTPUT" | jq -r '.count // 0' 2>/dev/null)
|
|
104
|
+
[[ "$TOTAL" == "0" ]] && exit 0
|
|
105
|
+
|
|
106
|
+
echo "$NOW" > "$RATE_FILE" 2>/dev/null || true
|
|
107
|
+
|
|
108
|
+
CONTEXT="XRAY: Pattern '\${SEARCH_TERM}' matches \${TOTAL} indexed symbol(s).\\n"
|
|
109
|
+
|
|
110
|
+
echo "$SEARCH_OUTPUT" | jq -r '.results[] | " \\(.name) (\\(.file):\\(.line))"' 2>/dev/null | while IFS= read -r line; do
|
|
111
|
+
CONTEXT="\${CONTEXT}\${line}\\n"
|
|
112
|
+
done
|
|
113
|
+
|
|
114
|
+
TOP_SYMBOL=$(echo "$SEARCH_OUTPUT" | jq -r '.results[0].name // empty' 2>/dev/null)
|
|
115
|
+
if [[ -n "$TOP_SYMBOL" ]]; then
|
|
116
|
+
USES_OUTPUT=$(curl "\${CURL_ARGS[@]}" "\${XRAY_SERVER}/api/v1/who-uses?symbol=\${TOP_SYMBOL}&limit=3" 2>/dev/null) || true
|
|
117
|
+
if [[ -n "$USES_OUTPUT" ]]; then
|
|
118
|
+
USES_COUNT=$(echo "$USES_OUTPUT" | jq -r '.count // 0' 2>/dev/null)
|
|
119
|
+
if [[ "$USES_COUNT" != "0" ]]; then
|
|
120
|
+
CONTEXT="\${CONTEXT}Top symbol '\${TOP_SYMBOL}' has \${USES_COUNT} cross-references.\\n"
|
|
121
|
+
echo "$USES_OUTPUT" | jq -r '.hits[:3][] | " [\\(if .is_heuristic then "heuristic" else "exact" end)] \\(.source_file):\\(.source_line)"' 2>/dev/null | while IFS= read -r line; do
|
|
122
|
+
CONTEXT="\${CONTEXT}\${line}\\n"
|
|
123
|
+
done
|
|
124
|
+
CONTEXT="\${CONTEXT}Run: npx xray-code who-uses \${TOP_SYMBOL} | npx xray-code impact \${TOP_SYMBOL}"
|
|
125
|
+
fi
|
|
126
|
+
fi
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
ESCAPED_CONTEXT=$(printf '%s' "$CONTEXT" | jq -Rs '.')
|
|
130
|
+
|
|
131
|
+
cat << EOF
|
|
132
|
+
{
|
|
133
|
+
"hookSpecificOutput": {
|
|
134
|
+
"hookEventName": "PreToolUse",
|
|
135
|
+
"additionalContext": $ESCAPED_CONTEXT
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
EOF
|
|
139
|
+
`;
|
|
140
|
+
function claudeMdSection(space, symbols, refs) {
|
|
141
|
+
return `## Code Intelligence (XRay)
|
|
142
|
+
|
|
143
|
+
This project has a code intelligence index at \`xray.proven.dev\`.
|
|
144
|
+
${symbols.toLocaleString()} symbols and ${refs.toLocaleString()} cross-references indexed, updated on every push.
|
|
145
|
+
|
|
146
|
+
### Commands
|
|
147
|
+
\`\`\`bash
|
|
148
|
+
npx xray-code search <query> # Fuzzy search for symbols by name
|
|
149
|
+
npx xray-code who-uses <symbol> # Find all usages of a symbol
|
|
150
|
+
npx xray-code impact <symbol> # What breaks if I change this?
|
|
151
|
+
npx xray-code symbols-in <file> # List symbols defined in a file
|
|
152
|
+
npx xray-code health # Check index status
|
|
153
|
+
\`\`\`
|
|
154
|
+
|
|
155
|
+
All commands support \`--json\` for machine-readable output.
|
|
156
|
+
|
|
157
|
+
### Ambient Intelligence (Automatic)
|
|
158
|
+
|
|
159
|
+
A hook automatically queries XRay when you search with Grep or Glob.
|
|
160
|
+
You don't need to call xray-code explicitly — it enriches your searches automatically.
|
|
161
|
+
|
|
162
|
+
### Rules
|
|
163
|
+
- XRay data is **exact** — never approximate or infer on top of it
|
|
164
|
+
- Always check \`impact\` before refactoring shared symbols
|
|
165
|
+
- The index auto-rebuilds on every push to main (webhook)
|
|
166
|
+
`;
|
|
167
|
+
}
|
|
168
|
+
function memoryContent(server) {
|
|
169
|
+
return `---
|
|
170
|
+
name: xray-code-intelligence
|
|
171
|
+
description: XRay code intelligence at ${server} — symbol search, cross-references, impact analysis
|
|
172
|
+
type: reference
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## XRay Code Intelligence
|
|
176
|
+
|
|
177
|
+
- **URL:** ${server}
|
|
178
|
+
- **What:** Code intelligence index — instant cross-references across the entire codebase
|
|
179
|
+
- **Commands:** npx xray-code search, who-uses, impact, symbols-in, health
|
|
180
|
+
- **Hook:** .claude/hooks/xray-inject.sh auto-injects context on Grep/Glob
|
|
181
|
+
- **Updated:** Index refreshes automatically when code is pushed to GitHub
|
|
182
|
+
- **Note:** All commands support --json flag for structured output
|
|
183
|
+
`;
|
|
184
|
+
}
|
|
185
|
+
function installClaude(projectDir, server, apiKey, space, symbols, refs) {
|
|
186
|
+
const claudeDir = (0, node_path_1.join)(projectDir, ".claude");
|
|
187
|
+
const hooksDir = (0, node_path_1.join)(claudeDir, "hooks");
|
|
188
|
+
const memoryDir = (0, node_path_1.join)(claudeDir, "memory");
|
|
189
|
+
const settingsPath = (0, node_path_1.join)(claudeDir, "settings.json");
|
|
190
|
+
const claudeMdPath = (0, node_path_1.join)(projectDir, "CLAUDE.md");
|
|
191
|
+
const hookPath = (0, node_path_1.join)(hooksDir, "xray-inject.sh");
|
|
192
|
+
const memoryPath = (0, node_path_1.join)(memoryDir, "xray.md");
|
|
193
|
+
(0, node_fs_1.mkdirSync)(hooksDir, { recursive: true });
|
|
194
|
+
(0, node_fs_1.mkdirSync)(memoryDir, { recursive: true });
|
|
195
|
+
// 1. Write hook script
|
|
196
|
+
const hookContent = HOOK_SCRIPT
|
|
197
|
+
.replace("__XRAY_SERVER__", server)
|
|
198
|
+
.replace("__XRAY_KEY__", apiKey);
|
|
199
|
+
(0, node_fs_1.writeFileSync)(hookPath, hookContent, { mode: 0o755 });
|
|
200
|
+
console.log(` Created: ${hookPath}`);
|
|
201
|
+
// 2. Update .claude/settings.json
|
|
202
|
+
let settings = {};
|
|
203
|
+
if ((0, node_fs_1.existsSync)(settingsPath)) {
|
|
204
|
+
try {
|
|
205
|
+
settings = JSON.parse((0, node_fs_1.readFileSync)(settingsPath, "utf-8"));
|
|
206
|
+
}
|
|
207
|
+
catch { /* start fresh */ }
|
|
208
|
+
}
|
|
209
|
+
if (!settings.hooks || typeof settings.hooks !== "object" || Array.isArray(settings.hooks)) {
|
|
210
|
+
settings.hooks = {};
|
|
211
|
+
}
|
|
212
|
+
const hooks = settings.hooks;
|
|
213
|
+
const preToolUse = hooks.PreToolUse || [];
|
|
214
|
+
const hookExists = preToolUse.some((group) => {
|
|
215
|
+
const groupHooks = group.hooks;
|
|
216
|
+
return groupHooks?.some((h) => h.command && String(h.command).includes("xray-inject"));
|
|
217
|
+
});
|
|
218
|
+
if (!hookExists) {
|
|
219
|
+
preToolUse.push({
|
|
220
|
+
matcher: "Grep|Glob",
|
|
221
|
+
hooks: [
|
|
222
|
+
{
|
|
223
|
+
type: "command",
|
|
224
|
+
command: hookPath,
|
|
225
|
+
timeout: 5000,
|
|
226
|
+
statusMessage: "XRay: injecting code intelligence...",
|
|
227
|
+
},
|
|
228
|
+
],
|
|
229
|
+
});
|
|
230
|
+
hooks.PreToolUse = preToolUse;
|
|
231
|
+
(0, node_fs_1.writeFileSync)(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
232
|
+
console.log(` Updated: ${settingsPath}`);
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
console.log(` Skipped: ${settingsPath} (hook already installed)`);
|
|
236
|
+
}
|
|
237
|
+
// 3. Update CLAUDE.md
|
|
238
|
+
const SECTION_START = "<!-- XRAY_START -->";
|
|
239
|
+
const SECTION_END = "<!-- XRAY_END -->";
|
|
240
|
+
const section = claudeMdSection(space, symbols, refs);
|
|
241
|
+
const wrappedSection = `${SECTION_START}\n${section}\n${SECTION_END}`;
|
|
242
|
+
if ((0, node_fs_1.existsSync)(claudeMdPath)) {
|
|
243
|
+
let existing = (0, node_fs_1.readFileSync)(claudeMdPath, "utf-8");
|
|
244
|
+
if (existing.includes(SECTION_START)) {
|
|
245
|
+
const startIdx = existing.indexOf(SECTION_START);
|
|
246
|
+
const endIdx = existing.indexOf(SECTION_END);
|
|
247
|
+
if (endIdx > startIdx) {
|
|
248
|
+
existing = existing.slice(0, startIdx) + wrappedSection + existing.slice(endIdx + SECTION_END.length);
|
|
249
|
+
(0, node_fs_1.writeFileSync)(claudeMdPath, existing);
|
|
250
|
+
console.log(` Updated: ${claudeMdPath} (replaced XRay section)`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
(0, node_fs_1.writeFileSync)(claudeMdPath, existing + "\n" + wrappedSection);
|
|
255
|
+
console.log(` Updated: ${claudeMdPath} (appended XRay section)`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
(0, node_fs_1.writeFileSync)(claudeMdPath, `# Project Instructions\n\n${wrappedSection}`);
|
|
260
|
+
console.log(` Created: ${claudeMdPath}`);
|
|
261
|
+
}
|
|
262
|
+
// 4. Write memory file
|
|
263
|
+
(0, node_fs_1.writeFileSync)(memoryPath, memoryContent(server));
|
|
264
|
+
console.log(` Created: ${memoryPath}`);
|
|
265
|
+
}
|
package/dist/onboard.js
CHANGED
|
@@ -106,7 +106,7 @@ async function onboard(repoFilter) {
|
|
|
106
106
|
console.log(`\n Selected: ${repo.full_name}\n`);
|
|
107
107
|
// ─── Step 2: Clone ────────────────────────────────────────────
|
|
108
108
|
const repoName = (0, node_path_1.basename)(repo.full_name);
|
|
109
|
-
const cloneDir = (0, node_path_1.join)((0, node_os_1.homedir)(), "Code", repoName);
|
|
109
|
+
const cloneDir = (0, node_path_1.join)((0, node_os_1.homedir)(), "Code", "xray-tenants", repoName);
|
|
110
110
|
if ((0, node_fs_1.existsSync)(cloneDir)) {
|
|
111
111
|
console.log(` Repo already exists at ${cloneDir} — pulling latest...`);
|
|
112
112
|
try {
|