@kaluchi/jdtbridge 1.3.0 → 1.5.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/README.md CHANGED
@@ -45,8 +45,10 @@ jdt source <FQMN> # (alias: src) source cod
45
45
 
46
46
  ```bash
47
47
  jdt build [--project <name>] [--clean] # (alias: b) build project
48
- jdt test <FQMN> [--timeout N] # run JUnit test class or method
49
- jdt test --project <name> [--package <pkg>] # run tests in project/package
48
+ jdt test run <FQN> [-f] [-q] # launch tests (non-blocking)
49
+ jdt test run --project <name> [-f] # run tests in project
50
+ jdt test status <session> [-f] [--all] [--ignored] # show test progress/results
51
+ jdt test sessions # list test sessions
50
52
  ```
51
53
 
52
54
  All commands auto-refresh from disk. `build` is the only command that triggers explicit builds.
@@ -71,10 +73,26 @@ jdt rename <FQN> <newName> --field <old> # rename field
71
73
  jdt move <FQN> <target.package> # move type to another package
72
74
  ```
73
75
 
76
+ ### Launches
77
+
78
+ ```bash
79
+ jdt launch list # list launches (running + terminated)
80
+ jdt launch configs # list saved launch configurations
81
+ jdt launch run <config> [-f] [-q] # launch a configuration
82
+ jdt launch debug <config> [-f] [-q] # launch in debug mode
83
+ jdt launch logs <name> [-f] [--tail N] # show console output
84
+ jdt launch stop <name> # stop a running launch
85
+ jdt launch clear [name] # remove terminated launches
86
+ ```
87
+
88
+ `-f` streams output in real-time until the process terminates.
89
+ Without `-f`, `launch run` prints an onboarding guide with available commands (`-q` to suppress).
90
+ Console output persists in Eclipse and is available via `launch logs` at any time.
91
+
74
92
  ### Editor
75
93
 
76
94
  ```bash
77
- jdt active-editor # (alias: ae) current file and cursor line
95
+ jdt editors # (alias: ed) list all open editors (absolute paths)
78
96
  jdt open <FQMN> # open in Eclipse editor
79
97
  ```
80
98
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaluchi/jdtbridge",
3
- "version": "1.3.0",
3
+ "version": "1.5.0",
4
4
  "description": "CLI for Eclipse JDT Bridge — semantic Java analysis via Eclipse JDT SearchEngine",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,83 @@
1
+ // Configure Claude Code for JDT Bridge projects.
2
+
3
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { execSync } from "node:child_process";
6
+
7
+ const JDT_HOOK_COMMAND = `node -e "d=JSON.parse(require('fs').readFileSync(0,'utf8'));d.tool_input?.command?.startsWith('jdt ')&&process.stdout.write(JSON.stringify({hookSpecificOutput:{hookEventName:'PreToolUse',permissionDecision:'allow'}}))"`;
8
+
9
+ /**
10
+ * Find the project root (git root or cwd).
11
+ * @returns {string}
12
+ */
13
+ export function findProjectRoot() {
14
+ try {
15
+ return execSync("git rev-parse --show-toplevel", {
16
+ encoding: "utf8",
17
+ stdio: ["pipe", "pipe", "pipe"],
18
+ }).trim();
19
+ } catch {
20
+ return process.cwd();
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Merge JDT Bridge settings into a Claude Code settings object.
26
+ * Idempotent — safe to call multiple times.
27
+ * @param {object} settings existing settings (mutated in place)
28
+ * @returns {object} the mutated settings
29
+ */
30
+ export function mergeJdtSettings(settings) {
31
+ // Permissions
32
+ if (!settings.permissions) settings.permissions = {};
33
+ const allow = settings.permissions.allow || [];
34
+ if (!allow.includes("Bash(jdt *)")) allow.push("Bash(jdt *)");
35
+ settings.permissions.allow = allow;
36
+
37
+ // Hook
38
+ if (!settings.hooks) settings.hooks = {};
39
+ if (!settings.hooks.PreToolUse) settings.hooks.PreToolUse = [];
40
+ const existing = settings.hooks.PreToolUse.find(
41
+ (h) =>
42
+ h.matcher === "Bash" &&
43
+ h.hooks?.some((hk) => hk.command === JDT_HOOK_COMMAND),
44
+ );
45
+ if (!existing) {
46
+ settings.hooks.PreToolUse.push({
47
+ matcher: "Bash",
48
+ hooks: [{ type: "command", command: JDT_HOOK_COMMAND }],
49
+ });
50
+ }
51
+
52
+ return settings;
53
+ }
54
+
55
+ /**
56
+ * Write Claude Code settings for JDT Bridge to a project.
57
+ * Creates .claude/settings.json if it doesn't exist,
58
+ * merges into existing if it does.
59
+ * @param {string} [root] project root (default: git root or cwd)
60
+ * @returns {{ file: string, settings: object }}
61
+ */
62
+ export function installClaudeSettings(root) {
63
+ if (!root) root = findProjectRoot();
64
+
65
+ const dir = join(root, ".claude");
66
+ const file = join(dir, "settings.json");
67
+
68
+ let settings = {};
69
+ if (existsSync(file)) {
70
+ try {
71
+ settings = JSON.parse(readFileSync(file, "utf8"));
72
+ } catch {
73
+ // corrupt — overwrite
74
+ }
75
+ }
76
+
77
+ mergeJdtSettings(settings);
78
+
79
+ mkdirSync(dir, { recursive: true });
80
+ writeFileSync(file, JSON.stringify(settings, null, 2) + "\n");
81
+
82
+ return { file, settings };
83
+ }
package/src/cli.mjs CHANGED
@@ -11,7 +11,9 @@ import { implementors, help as implementorsHelp } from "./commands/implementors.
11
11
  import { typeInfo, help as typeInfoHelp } from "./commands/type-info.mjs";
12
12
  import { source, help as sourceHelp } from "./commands/source.mjs";
13
13
  import { build, help as buildHelp } from "./commands/build.mjs";
14
- import { test, help as testHelp } from "./commands/test.mjs";
14
+ import { testRun, help as testRunHelp } from "./commands/test-run.mjs";
15
+ import { testStatus, help as testStatusHelp } from "./commands/test-status.mjs";
16
+ import { testSessions, help as testSessionsHelp } from "./commands/test-sessions.mjs";
15
17
  import { errors, help as errorsHelp } from "./commands/errors.mjs";
16
18
  import {
17
19
  organizeImports,
@@ -24,11 +26,29 @@ import {
24
26
  moveHelp,
25
27
  } from "./commands/refactoring.mjs";
26
28
  import {
27
- activeEditor,
29
+ editors,
28
30
  open,
29
- activeEditorHelp,
31
+ editorsHelp,
30
32
  openHelp,
31
33
  } from "./commands/editor.mjs";
34
+ import {
35
+ launchList,
36
+ launchConfigs,
37
+ launchRun,
38
+ launchDebug,
39
+ launchStop,
40
+ launchClear,
41
+ launchLogs,
42
+ launchConsole,
43
+ launchListHelp,
44
+ launchConfigsHelp,
45
+ launchRunHelp,
46
+ launchDebugHelp,
47
+ launchStopHelp,
48
+ launchClearHelp,
49
+ launchLogsHelp,
50
+ launchConsoleHelp,
51
+ } from "./commands/launch.mjs";
32
52
  import { setup, help as setupHelp } from "./commands/setup.mjs";
33
53
  import { isConnectionError } from "./client.mjs";
34
54
  import { bold, red, dim } from "./color.mjs";
@@ -36,6 +56,75 @@ import { createRequire } from "node:module";
36
56
 
37
57
  const { version } = createRequire(import.meta.url)("../package.json");
38
58
 
59
+ const launchSubcommands = {
60
+ list: { fn: launchList, help: launchListHelp },
61
+ configs: { fn: launchConfigs, help: launchConfigsHelp },
62
+ run: { fn: launchRun, help: launchRunHelp },
63
+ debug: { fn: launchDebug, help: launchDebugHelp },
64
+ stop: { fn: launchStop, help: launchStopHelp },
65
+ clear: { fn: launchClear, help: launchClearHelp },
66
+ logs: { fn: launchLogs, help: launchLogsHelp },
67
+ console: { fn: launchConsole, help: launchConsoleHelp },
68
+ };
69
+
70
+ const launchHelp = `Manage launches (running and terminated processes).
71
+
72
+ Subcommands:
73
+ jdt launch list list all launches
74
+ jdt launch configs list saved launch configurations
75
+ jdt launch run <config> [-f] [-q] launch a configuration
76
+ jdt launch debug <config> [-f] [-q] launch in debug mode
77
+ jdt launch logs <name> [-f] [--tail N] show console output
78
+ jdt launch stop <name> stop a running launch
79
+ jdt launch clear [name] remove terminated launches
80
+
81
+ Use "jdt help launch <subcommand>" for details.`;
82
+
83
+ async function launchDispatch(args) {
84
+ const [sub, ...rest] = args;
85
+ if (!sub || sub === "--help") {
86
+ console.log(launchHelp);
87
+ return;
88
+ }
89
+ const cmd = launchSubcommands[sub];
90
+ if (!cmd) {
91
+ console.error(`Unknown launch subcommand: ${sub}`);
92
+ console.log(launchHelp);
93
+ process.exit(1);
94
+ }
95
+ await cmd.fn(rest);
96
+ }
97
+
98
+ const testSubcommands = {
99
+ run: { fn: testRun, help: testRunHelp },
100
+ status: { fn: testStatus, help: testStatusHelp },
101
+ sessions: { fn: testSessions, help: testSessionsHelp },
102
+ };
103
+
104
+ const testHelp = `Run and monitor JUnit tests via Eclipse's built-in runner.
105
+
106
+ Subcommands:
107
+ jdt test run <FQN> [-f] [-q] launch tests (non-blocking)
108
+ jdt test status <session> [-f] [--all] show test progress/results
109
+ jdt test sessions list test sessions
110
+
111
+ Use "jdt help test <subcommand>" for details.`;
112
+
113
+ async function testDispatch(args) {
114
+ const [sub, ...rest] = args;
115
+ if (!sub || sub === "--help") {
116
+ console.log(testHelp);
117
+ return;
118
+ }
119
+ const cmd = testSubcommands[sub];
120
+ if (!cmd) {
121
+ console.error(`Unknown test subcommand: ${sub}`);
122
+ console.log(testHelp);
123
+ process.exit(1);
124
+ }
125
+ await cmd.fn(rest);
126
+ }
127
+
39
128
  const commands = {
40
129
  projects: { fn: projects, help: projectsHelp },
41
130
  "project-info": { fn: projectInfo, help: projectInfoHelp },
@@ -47,14 +136,15 @@ const commands = {
47
136
  "type-info": { fn: typeInfo, help: typeInfoHelp },
48
137
  source: { fn: source, help: sourceHelp },
49
138
  build: { fn: build, help: buildHelp },
50
- test: { fn: test, help: testHelp },
139
+ test: { fn: testDispatch, help: testHelp },
51
140
  errors: { fn: errors, help: errorsHelp },
52
141
  "organize-imports": { fn: organizeImports, help: organizeImportsHelp },
53
142
  format: { fn: format, help: formatHelp },
54
143
  rename: { fn: rename, help: renameHelp },
55
144
  move: { fn: move, help: moveHelp },
56
- "active-editor": { fn: activeEditor, help: activeEditorHelp },
145
+ editors: { fn: editors, help: editorsHelp },
57
146
  open: { fn: open, help: openHelp },
147
+ launch: { fn: launchDispatch, help: launchHelp },
58
148
  setup: { fn: setup, help: setupHelp },
59
149
  };
60
150
 
@@ -67,7 +157,7 @@ const aliases = {
67
157
  pi: "project-info",
68
158
  ti: "type-info",
69
159
  oi: "organize-imports",
70
- ae: "active-editor",
160
+ ed: "editors",
71
161
  src: "source",
72
162
  b: "build",
73
163
  err: "errors",
@@ -104,12 +194,13 @@ Search & navigation:
104
194
  hierarchy${fmtAliases("hierarchy")} <FQN> full hierarchy (supers + interfaces + subtypes)
105
195
  implementors${fmtAliases("implementors")} <FQMN> implementations of interface method
106
196
  type-info${fmtAliases("type-info")} <FQN> class overview (fields, methods, line numbers)
107
- source${fmtAliases("source")} <FQMN> type or method source code (project and libraries)
197
+ source${fmtAliases("source")} <FQMN> source + resolved references (navigation)
108
198
 
109
199
  Testing & building:
110
200
  build${fmtAliases("build")} [--project <name>] [--clean] build project (incremental or clean)
111
- test <FQMN> run JUnit test class or method
112
- test --project <name> [--package <pkg>] run tests in project/package
201
+ test run <FQN> [-f] [-q] launch tests (non-blocking)
202
+ test status <session> [-f] [--all] show test progress/results
203
+ test sessions list test sessions
113
204
 
114
205
  Diagnostics:
115
206
  errors${fmtAliases("errors")} [--file <path>] [--project <name>] compilation errors
@@ -120,8 +211,17 @@ Refactoring:
120
211
  rename <FQMN> <newName> [--field <old>] rename type/method/field
121
212
  move <FQN> <target.package> move type to another package
122
213
 
214
+ Launches:
215
+ launch list list launches (running + terminated)
216
+ launch configs list saved launch configurations
217
+ launch run <config> [-f] [-q] launch a configuration
218
+ launch debug <config> [-f] [-q] launch in debug mode
219
+ launch logs <name> [-f] [--tail N] show console output
220
+ launch stop <name> stop a running launch
221
+ launch clear [name] remove terminated launches
222
+
123
223
  Editor:
124
- active-editor${fmtAliases("active-editor")} current file and cursor line
224
+ editors${fmtAliases("editors")} list open editors (absolute paths)
125
225
  open <FQMN> open in Eclipse editor
126
226
 
127
227
  Setup:
@@ -146,7 +246,11 @@ export async function run(argv) {
146
246
  if (command === "help") {
147
247
  const topic = rest[0];
148
248
  const resolved = topic ? resolve(topic) : null;
149
- if (resolved) {
249
+ if (resolved === "launch" && rest[1] && launchSubcommands[rest[1]]) {
250
+ console.log(launchSubcommands[rest[1]].help);
251
+ } else if (resolved === "test" && rest[1] && testSubcommands[rest[1]]) {
252
+ console.log(testSubcommands[rest[1]].help);
253
+ } else if (resolved) {
150
254
  console.log(commands[resolved].help);
151
255
  } else if (topic) {
152
256
  console.error(`Unknown command: ${topic}`);
package/src/client.mjs CHANGED
@@ -158,6 +158,93 @@ export function getRaw(path, timeoutMs = 10_000) {
158
158
  });
159
159
  }
160
160
 
161
+ /**
162
+ * HTTP GET with streaming response. Pipes text/plain chunks to
163
+ * the provided writable stream (typically process.stdout).
164
+ * Resolves when the server closes the connection.
165
+ * Rejects on non-200 status or connection error.
166
+ * @param {string} path
167
+ * @param {import('stream').Writable} dest
168
+ * @returns {Promise<void>}
169
+ */
170
+ export function getStream(path, dest) {
171
+ const inst = connect();
172
+ return new Promise((resolve, reject) => {
173
+ const req = request(
174
+ {
175
+ hostname: "127.0.0.1",
176
+ port: inst.port,
177
+ path,
178
+ method: "GET",
179
+ timeout: 0, // no timeout for streaming
180
+ headers: authHeaders(),
181
+ },
182
+ (res) => {
183
+ if (res.statusCode !== 200) {
184
+ let data = "";
185
+ res.on("data", (chunk) => (data += chunk));
186
+ res.on("end", () => reject(new Error(data.trim())));
187
+ return;
188
+ }
189
+ res.pipe(dest, { end: false });
190
+ res.on("end", resolve);
191
+ res.on("error", reject);
192
+ },
193
+ );
194
+ req.on("error", reject);
195
+ req.end();
196
+ });
197
+ }
198
+
199
+ /**
200
+ * HTTP GET streaming with line-by-line callback. Designed for
201
+ * JSONL (newline-delimited JSON) endpoints. Calls onLine for
202
+ * each complete line received.
203
+ * Resolves when the server closes the connection.
204
+ * @param {string} path
205
+ * @param {(line: string) => void} onLine
206
+ * @returns {Promise<void>}
207
+ */
208
+ export function getStreamLines(path, onLine) {
209
+ const inst = connect();
210
+ return new Promise((resolve, reject) => {
211
+ const req = request(
212
+ {
213
+ hostname: "127.0.0.1",
214
+ port: inst.port,
215
+ path,
216
+ method: "GET",
217
+ timeout: 0,
218
+ headers: authHeaders(),
219
+ },
220
+ (res) => {
221
+ if (res.statusCode !== 200) {
222
+ let data = "";
223
+ res.on("data", (chunk) => (data += chunk));
224
+ res.on("end", () => reject(new Error(data.trim())));
225
+ return;
226
+ }
227
+ let buffer = "";
228
+ res.on("data", (chunk) => {
229
+ buffer += chunk;
230
+ const lines = buffer.split("\n");
231
+ buffer = lines.pop();
232
+ for (const line of lines) {
233
+ if (line.trim()) onLine(line);
234
+ }
235
+ });
236
+ res.on("end", () => {
237
+ if (buffer.trim()) onLine(buffer);
238
+ resolve();
239
+ });
240
+ res.on("error", reject);
241
+ },
242
+ );
243
+ req.on("error", reject);
244
+ req.end();
245
+ });
246
+ }
247
+
161
248
  /**
162
249
  * Check if error is a connection refused error.
163
250
  * @param {Error} e
@@ -1,17 +1,18 @@
1
1
  import { get } from "../client.mjs";
2
2
  import { extractPositional, parseFlags, parseFqmn } from "../args.mjs";
3
- import { stripProject } from "../paths.mjs";
4
3
 
5
- export async function activeEditor() {
6
- const result = await get("/active-editor");
7
- if (result.error) {
8
- console.error(result.error);
4
+ export async function editors() {
5
+ const results = await get("/editors");
6
+ if (results.error) {
7
+ console.error(results.error);
9
8
  process.exit(1);
10
9
  }
11
- if (result.file === null) {
12
- console.log("(no file open)");
13
- } else {
14
- console.log(`${stripProject(result.file)}:${result.line}`);
10
+ if (results.length === 0) {
11
+ console.log("(no open editors)");
12
+ return;
13
+ }
14
+ for (const r of results) {
15
+ console.log(r.file);
15
16
  }
16
17
  }
17
18
 
@@ -38,15 +39,17 @@ export async function open(args) {
38
39
  console.log("Opened");
39
40
  }
40
41
 
41
- export const activeEditorHelp = `Show the file and cursor line of the active Eclipse editor.
42
+ export const editorsHelp = `List all open editors in Eclipse. Active editor first.
43
+
44
+ Usage: jdt editors
42
45
 
43
- Usage: jdt active-editor`;
46
+ Output: absolute file paths, one per line.`;
44
47
 
45
48
  export const openHelp = `Open a type or method in the Eclipse editor.
46
49
 
47
50
  Usage: jdt open <FQN>[#method[(param types)]]
48
51
 
49
52
  Examples:
50
- jdt open app.m8.dao.StaffDaoImpl
51
- jdt open app.m8.dao.StaffDaoImpl#getStaff
52
- jdt open "app.m8.dao.StaffDaoImpl#save(Order)"`;
53
+ jdt open com.example.dao.UserDaoImpl
54
+ jdt open com.example.dao.UserDaoImpl#getStaff
55
+ jdt open "com.example.dao.UserDaoImpl#save(Order)"`;
@@ -45,4 +45,4 @@ export const help = `Show full type hierarchy: superclasses, interfaces, and sub
45
45
 
46
46
  Usage: jdt hierarchy <FQN>
47
47
 
48
- Example: jdt hierarchy app.m8.web.client.AGMEntryPoint`;
48
+ Example: jdt hierarchy com.example.client.AppEntryPoint`;
@@ -35,4 +35,4 @@ export const help = `Find implementations of an interface method across all impl
35
35
  Usage: jdt implementors <FQN>#<method>[(param types)]
36
36
 
37
37
  Examples:
38
- jdt implementors app.m8.web.shared.core.HasId#getId`;
38
+ jdt implementors com.example.core.HasId#getId`;