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 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: Show connection status
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(` Try these commands:`);
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;
@@ -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 {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xray-code",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "XRay — code intelligence CLI. X-ray your codebase with instant cross-references.",
5
5
  "bin": {
6
6
  "xray": "dist/cli.js"