xray-code 0.1.0 → 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,10 +19,11 @@ 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")
25
- .version("0.1.0");
26
+ .version("0.1.1");
26
27
  // ─── Auth commands ──────────────────────────────────────────────
27
28
  program
28
29
  .command("login")
@@ -67,12 +68,28 @@ program
67
68
  if (!existsSync(dir))
68
69
  mkdirSync(dir, { recursive: true });
69
70
  writeFileSync(join(dir, "config.json"), JSON.stringify({ server: entry.server, apiKey: entry.apiKey }, null, 2), { mode: 0o600 });
70
- console.log(` XRay activated!`);
71
- console.log(` Config saved to ~/.xray/config.json\n`);
71
+ console.log(` Invite code accepted!\n`);
72
+ console.log(` Now let's connect your GitHub account...\n`);
73
+ try {
74
+ await (0, auth_js_1.login)();
75
+ }
76
+ catch {
77
+ console.log(`\n GitHub login skipped.\n`);
78
+ }
72
79
  const client = new client_js_1.XRayClient(entry.server, entry.apiKey);
73
- const health = await client.health();
74
- console.log(` Connected: ${health.symbols.toLocaleString()} symbols, ${health.uses.toLocaleString()} refs`);
75
- console.log(` You're good to go! Try: xray search <symbol>\n`);
80
+ try {
81
+ const health = await client.health();
82
+ console.log(`\n ══════════════════════════════════════════════════`);
83
+ console.log(` XRay is ready!`);
84
+ console.log(` ══════════════════════════════════════════════════\n`);
85
+ console.log(` Symbols: ${health.symbols.toLocaleString()}`);
86
+ console.log(` Refs: ${health.uses.toLocaleString()}`);
87
+ console.log(` Files: ${health.files.toLocaleString()}\n`);
88
+ console.log(` Try: npx xray-code search <symbol-name>\n`);
89
+ }
90
+ catch {
91
+ console.log(` Config saved. Try 'npx xray-code health' in a minute.\n`);
92
+ }
76
93
  return;
77
94
  }
78
95
  }
@@ -84,13 +101,39 @@ program
84
101
  if (!existsSync(dir))
85
102
  mkdirSync(dir, { recursive: true });
86
103
  writeFileSync(join(dir, "config.json"), JSON.stringify({ server: data.server, apiKey: data.api_key }, null, 2), { mode: 0o600 });
87
- console.log(` XRay activated!`);
88
- console.log(` Config saved to ~/.xray/config.json`);
89
- console.log(` Space: ${data.space}\n`);
104
+ console.log(` Invite code accepted!\n`);
105
+ // Step 2: GitHub login
106
+ console.log(` Now let's connect your GitHub account...\n`);
107
+ try {
108
+ await (0, auth_js_1.login)();
109
+ }
110
+ catch {
111
+ console.log(`\n GitHub login skipped — you can run 'npx xray-code login' later.\n`);
112
+ }
113
+ // Step 3: Install Claude Code integration in current project
90
114
  const client = new client_js_1.XRayClient(data.server, data.api_key);
91
- const health = await client.health();
92
- console.log(` Connected: ${health.symbols.toLocaleString()} symbols, ${health.uses.toLocaleString()} refs`);
93
- console.log(` You're good to go! Try: xray search <symbol>\n`);
115
+ try {
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);
119
+ console.log(`\n ══════════════════════════════════════════════════`);
120
+ console.log(` XRay is ready!`);
121
+ console.log(` ══════════════════════════════════════════════════\n`);
122
+ console.log(` Codebase: ${data.space}`);
123
+ console.log(` Symbols: ${health.symbols.toLocaleString()}`);
124
+ console.log(` Refs: ${health.uses.toLocaleString()}`);
125
+ console.log(` Files: ${health.files.toLocaleString()}\n`);
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:`);
129
+ console.log(` npx xray-code search <symbol-name>`);
130
+ console.log(` npx xray-code who-uses <symbol-name>`);
131
+ console.log(` npx xray-code impact <symbol-name>`);
132
+ console.log(` npx xray-code health\n`);
133
+ }
134
+ catch {
135
+ console.log(` Config saved. Server may still be indexing — try 'npx xray-code health' in a minute.\n`);
136
+ }
94
137
  }
95
138
  catch (e) {
96
139
  console.error(` Activation failed: ${e}\n`);
@@ -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.0",
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"