@kaluchi/jdtbridge 1.2.0 → 1.4.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
@@ -32,20 +32,20 @@ Run `jdt help <command>` for detailed flags and examples. Most commands have sho
32
32
  ```bash
33
33
  jdt projects # list workspace projects
34
34
  jdt project-info <name> [--lines N] # (alias: pi) project overview
35
- jdt find <Name> [--source-only] # find type declarations (* wildcards)
36
- jdt references <FQN> [method] [--field <name>] # (alias: refs) references to type/method/field
35
+ jdt find <Name|package> [--source-only] # find types by name, wildcard, or package
36
+ jdt references <FQMN> [--field <name>] # (alias: refs) references to type/method/field
37
37
  jdt subtypes <FQN> # (alias: subt) all subtypes/implementors
38
38
  jdt hierarchy <FQN> # (alias: hier) supers + interfaces + subtypes
39
- jdt implementors <FQN> <method> [--arity N] # (alias: impl) implementations of interface method
39
+ jdt implementors <FQMN> # (alias: impl) implementations of interface method
40
40
  jdt type-info <FQN> # (alias: ti) class overview (fields, methods)
41
- jdt source <FQN> [method] [--arity N] # (alias: src) source code (project + libraries)
41
+ jdt source <FQMN> # (alias: src) source code (project + libraries)
42
42
  ```
43
43
 
44
44
  ### Testing & building
45
45
 
46
46
  ```bash
47
47
  jdt build [--project <name>] [--clean] # (alias: b) build project
48
- jdt test <FQN> [method] [--timeout N] # run JUnit test class or method
48
+ jdt test <FQMN> [--timeout N] # run JUnit test class or method
49
49
  jdt test --project <name> [--package <pkg>] # run tests in project/package
50
50
  ```
51
51
 
@@ -66,16 +66,32 @@ File paths are workspace-relative: `my-app/src/main/java/.../Foo.java`.
66
66
  jdt organize-imports <file> # (alias: oi) organize imports
67
67
  jdt format <file> # (alias: fmt) format code (Eclipse settings)
68
68
  jdt rename <FQN> <newName> # rename type
69
- jdt rename <FQN> <newName> --method <old> # rename method
69
+ jdt rename <FQMN> <newName> # rename method (FQMN includes method)
70
70
  jdt rename <FQN> <newName> --field <old> # rename field
71
71
  jdt move <FQN> <target.package> # move type to another package
72
72
  ```
73
73
 
74
+ ### Launches
75
+
76
+ ```bash
77
+ jdt launch list # list launches (running + terminated)
78
+ jdt launch configs # list saved launch configurations
79
+ jdt launch run <config> [-f] [-q] # launch a configuration
80
+ jdt launch debug <config> [-f] [-q] # launch in debug mode
81
+ jdt launch logs <name> [-f] [--tail N] # show console output
82
+ jdt launch stop <name> # stop a running launch
83
+ jdt launch clear [name] # remove terminated launches
84
+ ```
85
+
86
+ `-f` streams output in real-time until the process terminates.
87
+ Without `-f`, `launch run` prints an onboarding guide with available commands (`-q` to suppress).
88
+ Console output persists in Eclipse and is available via `launch logs` at any time.
89
+
74
90
  ### Editor
75
91
 
76
92
  ```bash
77
- jdt active-editor # (alias: ae) current file and cursor line
78
- jdt open <FQN> [method] [--arity N] # open in Eclipse editor
93
+ jdt editors # (alias: ed) list all open editors (absolute paths)
94
+ jdt open <FQMN> # open in Eclipse editor
79
95
  ```
80
96
 
81
97
  ## Instance discovery
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaluchi/jdtbridge",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "CLI for Eclipse JDT Bridge — semantic Java analysis via Eclipse JDT SearchEngine",
5
5
  "type": "module",
6
6
  "bin": {
package/src/args.mjs CHANGED
@@ -35,3 +35,73 @@ export function extractPositional(args) {
35
35
  }
36
36
  return result;
37
37
  }
38
+
39
+ /**
40
+ * Parse a Fully Qualified Method Name (FQMN) string.
41
+ *
42
+ * Supported formats:
43
+ * "pkg.Class#method(Type, Type)" — javadoc / surefire style
44
+ * "pkg.Class#method" — method without signature
45
+ * "pkg.Class.method(Type, Type)" — Eclipse Copy Qualified Name style
46
+ * "pkg.Class" — plain FQN (no method)
47
+ *
48
+ * Returns { className, method, paramTypes } where paramTypes is:
49
+ * null — no signature specified (any overload)
50
+ * [] — empty parens, i.e. zero-arg method
51
+ * ["String", "int[]", ...] — explicit parameter types
52
+ */
53
+ export function parseFqmn(input) {
54
+ if (!input) return { className: null, method: null, paramTypes: null };
55
+
56
+ // Javadoc style: Class#method or Class#method(params)
57
+ const hashIdx = input.indexOf("#");
58
+ if (hashIdx >= 0) {
59
+ return parseMethodPart(input.substring(0, hashIdx), input.substring(hashIdx + 1));
60
+ }
61
+
62
+ // Eclipse Copy Qualified Name: only when parentheses are present
63
+ const parenIdx = input.indexOf("(");
64
+ if (parenIdx >= 0) {
65
+ const dotIdx = input.lastIndexOf(".", parenIdx);
66
+ if (dotIdx >= 0) {
67
+ return parseMethodPart(input.substring(0, dotIdx), input.substring(dotIdx + 1));
68
+ }
69
+ }
70
+
71
+ // Plain FQN — no method
72
+ return { className: input, method: null, paramTypes: null };
73
+ }
74
+
75
+ function parseMethodPart(className, rest) {
76
+ const parenIdx = rest.indexOf("(");
77
+ if (parenIdx < 0) {
78
+ return { className, method: rest || null, paramTypes: null };
79
+ }
80
+
81
+ const method = rest.substring(0, parenIdx);
82
+ const closeIdx = rest.lastIndexOf(")");
83
+ const paramsStr = rest.substring(parenIdx + 1, closeIdx >= 0 ? closeIdx : rest.length);
84
+
85
+ if (paramsStr.trim() === "") {
86
+ return { className, method, paramTypes: [] };
87
+ }
88
+
89
+ return { className, method, paramTypes: splitParams(paramsStr) };
90
+ }
91
+
92
+ function splitParams(str) {
93
+ const params = [];
94
+ let depth = 0;
95
+ let start = 0;
96
+ for (let i = 0; i < str.length; i++) {
97
+ if (str[i] === "<") depth++;
98
+ else if (str[i] === ">") depth--;
99
+ else if (str[i] === "," && depth === 0) {
100
+ params.push(str.substring(start, i).trim());
101
+ start = i + 1;
102
+ }
103
+ }
104
+ const last = str.substring(start).trim();
105
+ if (last) params.push(last);
106
+ return params;
107
+ }
package/src/cli.mjs CHANGED
@@ -24,11 +24,29 @@ import {
24
24
  moveHelp,
25
25
  } from "./commands/refactoring.mjs";
26
26
  import {
27
- activeEditor,
27
+ editors,
28
28
  open,
29
- activeEditorHelp,
29
+ editorsHelp,
30
30
  openHelp,
31
31
  } from "./commands/editor.mjs";
32
+ import {
33
+ launchList,
34
+ launchConfigs,
35
+ launchRun,
36
+ launchDebug,
37
+ launchStop,
38
+ launchClear,
39
+ launchLogs,
40
+ launchConsole,
41
+ launchListHelp,
42
+ launchConfigsHelp,
43
+ launchRunHelp,
44
+ launchDebugHelp,
45
+ launchStopHelp,
46
+ launchClearHelp,
47
+ launchLogsHelp,
48
+ launchConsoleHelp,
49
+ } from "./commands/launch.mjs";
32
50
  import { setup, help as setupHelp } from "./commands/setup.mjs";
33
51
  import { isConnectionError } from "./client.mjs";
34
52
  import { bold, red, dim } from "./color.mjs";
@@ -36,6 +54,45 @@ import { createRequire } from "node:module";
36
54
 
37
55
  const { version } = createRequire(import.meta.url)("../package.json");
38
56
 
57
+ const launchSubcommands = {
58
+ list: { fn: launchList, help: launchListHelp },
59
+ configs: { fn: launchConfigs, help: launchConfigsHelp },
60
+ run: { fn: launchRun, help: launchRunHelp },
61
+ debug: { fn: launchDebug, help: launchDebugHelp },
62
+ stop: { fn: launchStop, help: launchStopHelp },
63
+ clear: { fn: launchClear, help: launchClearHelp },
64
+ logs: { fn: launchLogs, help: launchLogsHelp },
65
+ console: { fn: launchConsole, help: launchConsoleHelp },
66
+ };
67
+
68
+ const launchHelp = `Manage launches (running and terminated processes).
69
+
70
+ Subcommands:
71
+ jdt launch list list all launches
72
+ jdt launch configs list saved launch configurations
73
+ jdt launch run <config> [-f] [-q] launch a configuration
74
+ jdt launch debug <config> [-f] [-q] launch in debug mode
75
+ jdt launch logs <name> [-f] [--tail N] show console output
76
+ jdt launch stop <name> stop a running launch
77
+ jdt launch clear [name] remove terminated launches
78
+
79
+ Use "jdt help launch <subcommand>" for details.`;
80
+
81
+ async function launchDispatch(args) {
82
+ const [sub, ...rest] = args;
83
+ if (!sub || sub === "--help") {
84
+ console.log(launchHelp);
85
+ return;
86
+ }
87
+ const cmd = launchSubcommands[sub];
88
+ if (!cmd) {
89
+ console.error(`Unknown launch subcommand: ${sub}`);
90
+ console.log(launchHelp);
91
+ process.exit(1);
92
+ }
93
+ await cmd.fn(rest);
94
+ }
95
+
39
96
  const commands = {
40
97
  projects: { fn: projects, help: projectsHelp },
41
98
  "project-info": { fn: projectInfo, help: projectInfoHelp },
@@ -53,8 +110,9 @@ const commands = {
53
110
  format: { fn: format, help: formatHelp },
54
111
  rename: { fn: rename, help: renameHelp },
55
112
  move: { fn: move, help: moveHelp },
56
- "active-editor": { fn: activeEditor, help: activeEditorHelp },
113
+ editors: { fn: editors, help: editorsHelp },
57
114
  open: { fn: open, help: openHelp },
115
+ launch: { fn: launchDispatch, help: launchHelp },
58
116
  setup: { fn: setup, help: setupHelp },
59
117
  };
60
118
 
@@ -67,7 +125,7 @@ const aliases = {
67
125
  pi: "project-info",
68
126
  ti: "type-info",
69
127
  oi: "organize-imports",
70
- ae: "active-editor",
128
+ ed: "editors",
71
129
  src: "source",
72
130
  b: "build",
73
131
  err: "errors",
@@ -98,17 +156,17 @@ Requires: Eclipse running with the jdtbridge plugin.
98
156
  Search & navigation:
99
157
  projects list workspace projects
100
158
  project-info${fmtAliases("project-info")} <name> [--lines N] project overview (adaptive detail)
101
- find <Name|*Pattern*> [--source-only] find type declarations
102
- references${fmtAliases("references")} <FQN> [method] [--field <name>] references to type/method/field
159
+ find <Name|*Pattern*|pkg> [--source-only] find types by name, wildcard, or package
160
+ references${fmtAliases("references")} <FQMN> [--field <name>] references to type/method/field
103
161
  subtypes${fmtAliases("subtypes")} <FQN> all subtypes/implementors
104
162
  hierarchy${fmtAliases("hierarchy")} <FQN> full hierarchy (supers + interfaces + subtypes)
105
- implementors${fmtAliases("implementors")} <FQN> <method> [--arity n] implementations of interface method
163
+ implementors${fmtAliases("implementors")} <FQMN> implementations of interface method
106
164
  type-info${fmtAliases("type-info")} <FQN> class overview (fields, methods, line numbers)
107
- source${fmtAliases("source")} <FQN> [method] [--arity n] type or method source code (project and libraries)
165
+ source${fmtAliases("source")} <FQMN> type or method source code (project and libraries)
108
166
 
109
167
  Testing & building:
110
168
  build${fmtAliases("build")} [--project <name>] [--clean] build project (incremental or clean)
111
- test <FQN> [method] run JUnit test class or method
169
+ test <FQMN> run JUnit test class or method
112
170
  test --project <name> [--package <pkg>] run tests in project/package
113
171
 
114
172
  Diagnostics:
@@ -117,12 +175,21 @@ Diagnostics:
117
175
  Refactoring:
118
176
  organize-imports${fmtAliases("organize-imports")} <file> organize imports
119
177
  format${fmtAliases("format")} <file> format with Eclipse project settings
120
- rename <FQN> <newName> [--method|--field] rename type/method/field
178
+ rename <FQMN> <newName> [--field <old>] rename type/method/field
121
179
  move <FQN> <target.package> move type to another package
122
180
 
181
+ Launches:
182
+ launch list list launches (running + terminated)
183
+ launch configs list saved launch configurations
184
+ launch run <config> [-f] [-q] launch a configuration
185
+ launch debug <config> [-f] [-q] launch in debug mode
186
+ launch logs <name> [-f] [--tail N] show console output
187
+ launch stop <name> stop a running launch
188
+ launch clear [name] remove terminated launches
189
+
123
190
  Editor:
124
- active-editor${fmtAliases("active-editor")} current file and cursor line
125
- open <FQN> [method] open in Eclipse editor
191
+ editors${fmtAliases("editors")} list open editors (absolute paths)
192
+ open <FQMN> open in Eclipse editor
126
193
 
127
194
  Setup:
128
195
  setup [--check|--remove] install/check/remove Eclipse plugin
@@ -146,7 +213,9 @@ export async function run(argv) {
146
213
  if (command === "help") {
147
214
  const topic = rest[0];
148
215
  const resolved = topic ? resolve(topic) : null;
149
- if (resolved) {
216
+ if (resolved === "launch" && rest[1] && launchSubcommands[rest[1]]) {
217
+ console.log(launchSubcommands[rest[1]].help);
218
+ } else if (resolved) {
150
219
  console.log(commands[resolved].help);
151
220
  } else if (topic) {
152
221
  console.error(`Unknown command: ${topic}`);
package/src/client.mjs CHANGED
@@ -158,6 +158,44 @@ 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
+
161
199
  /**
162
200
  * Check if error is a connection refused error.
163
201
  * @param {Error} e
@@ -1,33 +1,36 @@
1
1
  import { get } from "../client.mjs";
2
- import { extractPositional, parseFlags } from "../args.mjs";
3
- import { stripProject } from "../paths.mjs";
2
+ import { extractPositional, parseFlags, parseFqmn } from "../args.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
 
18
19
  export async function open(args) {
19
20
  const pos = extractPositional(args);
20
21
  const flags = parseFlags(args);
21
- const fqn = pos[0];
22
- const method = pos[1];
22
+ const parsed = parseFqmn(pos[0]);
23
+ const fqn = parsed.className;
24
+ const method = parsed.method || pos[1];
23
25
  if (!fqn) {
24
- console.error("Usage: open <FQN> [method] [--arity n]");
26
+ console.error("Usage: open <FQN>[#method[(param types)]]");
25
27
  process.exit(1);
26
28
  }
27
29
  let url = `/open?class=${encodeURIComponent(fqn)}`;
28
30
  if (method) url += `&method=${encodeURIComponent(method)}`;
29
- if (flags.arity !== undefined && flags.arity !== true)
30
- url += `&arity=${flags.arity}`;
31
+ if (parsed.paramTypes) {
32
+ url += `&paramTypes=${encodeURIComponent(parsed.paramTypes.join(","))}`;
33
+ }
31
34
  const result = await get(url);
32
35
  if (result.error) {
33
36
  console.error(result.error);
@@ -36,14 +39,17 @@ export async function open(args) {
36
39
  console.log("Opened");
37
40
  }
38
41
 
39
- 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
40
45
 
41
- Usage: jdt active-editor`;
46
+ Output: absolute file paths, one per line.`;
42
47
 
43
48
  export const openHelp = `Open a type or method in the Eclipse editor.
44
49
 
45
- Usage: jdt open <FQN> [method] [--arity n]
50
+ Usage: jdt open <FQN>[#method[(param types)]]
46
51
 
47
52
  Examples:
48
53
  jdt open app.m8.dao.StaffDaoImpl
49
- jdt open app.m8.dao.StaffDaoImpl getStaff`;
54
+ jdt open app.m8.dao.StaffDaoImpl#getStaff
55
+ jdt open "app.m8.dao.StaffDaoImpl#save(Order)"`;
@@ -25,17 +25,20 @@ export async function find(args) {
25
25
  }
26
26
  }
27
27
 
28
- export const help = `Find type declarations by name or wildcard pattern.
28
+ export const help = `Find type declarations by name, wildcard, or package.
29
29
 
30
- Usage: jdt find <Name|*Pattern*> [--source-only]
30
+ Usage: jdt find <Name|*Pattern*|package.name> [--source-only]
31
31
 
32
32
  Arguments:
33
- Name exact type name (e.g. DataSourceUtils)
34
- *Pattern* wildcard pattern (e.g. *Controller*, Find*)
33
+ Name exact type name (e.g. DataSourceUtils)
34
+ *Pattern* wildcard pattern (e.g. *Controller*, Find*)
35
+ package.name dotted package name — lists all types in the package
35
36
 
36
37
  Flags:
37
38
  --source-only exclude binary/library types, show only workspace sources
38
39
 
39
40
  Examples:
40
41
  jdt find DataSourceUtils
41
- jdt find *Controller* --source-only`;
42
+ jdt find *Controller* --source-only
43
+ jdt find com.example.service
44
+ jdt find com.example.service.`;
@@ -1,18 +1,21 @@
1
1
  import { get } from "../client.mjs";
2
- import { extractPositional, parseFlags } from "../args.mjs";
2
+ import { extractPositional, parseFlags, parseFqmn } from "../args.mjs";
3
3
  import { stripProject } from "../paths.mjs";
4
4
 
5
5
  export async function implementors(args) {
6
6
  const pos = extractPositional(args);
7
7
  const flags = parseFlags(args);
8
- const [fqn, method] = pos;
8
+ const parsed = parseFqmn(pos[0]);
9
+ const fqn = parsed.className;
10
+ const method = parsed.method || pos[1];
9
11
  if (!fqn || !method) {
10
- console.error("Usage: implementors <FQN> <method> [--arity n]");
12
+ console.error("Usage: implementors <FQN>#<method>[(param types)]");
11
13
  process.exit(1);
12
14
  }
13
15
  let url = `/implementors?class=${encodeURIComponent(fqn)}&method=${encodeURIComponent(method)}`;
14
- if (flags.arity !== undefined && flags.arity !== true)
15
- url += `&arity=${flags.arity}`;
16
+ if (parsed.paramTypes) {
17
+ url += `&paramTypes=${encodeURIComponent(parsed.paramTypes.join(","))}`;
18
+ }
16
19
  const results = await get(url, 30_000);
17
20
  if (results.error) {
18
21
  console.error(results.error);
@@ -29,6 +32,7 @@ export async function implementors(args) {
29
32
 
30
33
  export const help = `Find implementations of an interface method across all implementing classes.
31
34
 
32
- Usage: jdt implementors <FQN> <method> [--arity <n>]
35
+ Usage: jdt implementors <FQN>#<method>[(param types)]
33
36
 
34
- Example: jdt implementors app.m8.web.shared.core.HasId getId`;
37
+ Examples:
38
+ jdt implementors app.m8.web.shared.core.HasId#getId`;
@@ -0,0 +1,308 @@
1
+ import { get, getStream } from "../client.mjs";
2
+ import { extractPositional, parseFlags } from "../args.mjs";
3
+
4
+ export async function launchList() {
5
+ const results = await get("/launch/list");
6
+ if (results.error) {
7
+ console.error(results.error);
8
+ process.exit(1);
9
+ }
10
+ if (results.length === 0) {
11
+ console.log("(no launches)");
12
+ return;
13
+ }
14
+ for (const r of results) {
15
+ const status = r.terminated
16
+ ? `terminated${r.exitCode !== undefined ? ` (${r.exitCode})` : ""}`
17
+ : "running";
18
+ const parts = [r.name, r.type, r.mode, status];
19
+ if (r.pid) parts.push(`pid:${r.pid}`);
20
+ console.log(parts.join(" "));
21
+ }
22
+ }
23
+
24
+ export async function launchConfigs() {
25
+ const results = await get("/launch/configs");
26
+ if (results.error) {
27
+ console.error(results.error);
28
+ process.exit(1);
29
+ }
30
+ if (results.length === 0) {
31
+ console.log("(no launch configurations)");
32
+ return;
33
+ }
34
+ for (const r of results) {
35
+ console.log(`${r.name} ${r.type}`);
36
+ }
37
+ }
38
+
39
+ export async function launchClear(args) {
40
+ const pos = extractPositional(args);
41
+ let url = "/launch/clear";
42
+ if (pos[0]) url += `?name=${encodeURIComponent(pos[0])}`;
43
+ const result = await get(url);
44
+ if (result.error) {
45
+ console.error(result.error);
46
+ process.exit(1);
47
+ }
48
+ console.log(`Removed ${result.removed} terminated launch${result.removed !== 1 ? "es" : ""}`);
49
+ }
50
+
51
+ /**
52
+ * Launch a configuration. Without -f, prints onboarding guide.
53
+ * With -f, streams console output until termination.
54
+ */
55
+ export async function launchRun(args) {
56
+ return launchWithMode(args, "run");
57
+ }
58
+
59
+ /** Launch in debug mode. Same interface as run. */
60
+ export async function launchDebug(args) {
61
+ return launchWithMode(args, "debug");
62
+ }
63
+
64
+ async function launchWithMode(args, mode) {
65
+ const pos = extractPositional(args);
66
+ const name = pos[0];
67
+ if (!name) {
68
+ console.error(`Usage: launch ${mode} <config-name> [-f] [-q]`);
69
+ process.exit(1);
70
+ }
71
+
72
+ let url = `/launch/run?name=${encodeURIComponent(name)}`;
73
+ if (mode === "debug") url += "&debug";
74
+ const result = await get(url, 30_000);
75
+ if (result.error) {
76
+ console.error(result.error);
77
+ process.exit(1);
78
+ }
79
+
80
+ const follow = args.includes("-f") || args.includes("--follow");
81
+ if (follow) {
82
+ console.error(formatLaunched(result));
83
+ const exitCode = await followLogs(result.name, args);
84
+ process.exit(exitCode);
85
+ }
86
+
87
+ const quiet = args.includes("-q") || args.includes("--quiet");
88
+ const n = result.name;
89
+ console.log(formatLaunched(result));
90
+
91
+ if (!quiet) {
92
+ console.log(launchGuide(n));
93
+ }
94
+ }
95
+
96
+ function formatLaunched(result) {
97
+ const parts = [`Launched ${result.name} (${result.mode})`];
98
+ if (result.type) parts[0] += ` [${result.type}]`;
99
+ if (result.pid) parts.push(` PID: ${result.pid}`);
100
+ if (result.workingDir) parts.push(` Working dir: ${result.workingDir}`);
101
+ if (result.cmdline) {
102
+ parts.push(` Command: ${result.cmdline}`);
103
+ }
104
+ return parts.join("\n");
105
+ }
106
+
107
+ function launchGuide(name) {
108
+ return `
109
+ Console output is captured by Eclipse and remains available
110
+ after the process terminates. You can read it at any time,
111
+ filter with grep, or pipe through tail/head.
112
+
113
+ View logs:
114
+ jdt launch logs ${name}
115
+ jdt launch logs ${name} --tail 30
116
+
117
+ Wait for completion (blocks until process exits):
118
+ jdt launch logs ${name} -f | tail -20
119
+ jdt launch logs ${name} -f
120
+
121
+ Manage:
122
+ jdt launch list
123
+ jdt launch stop ${name}
124
+ jdt launch clear
125
+
126
+ Run modes:
127
+ jdt launch run <config> launch (this output)
128
+ jdt launch run <config> -f launch + stream output
129
+ jdt launch run <config> -f | tail launch + wait + bounded
130
+ jdt launch debug <config> launch with debugger
131
+
132
+ Add -q to suppress this guide.`;
133
+ }
134
+
135
+ export async function launchStop(args) {
136
+ const pos = extractPositional(args);
137
+ const name = pos[0];
138
+ if (!name) {
139
+ console.error("Usage: launch stop <name>");
140
+ process.exit(1);
141
+ }
142
+ const url = `/launch/stop?name=${encodeURIComponent(name)}`;
143
+ const result = await get(url);
144
+ if (result.error) {
145
+ console.error(result.error);
146
+ process.exit(1);
147
+ }
148
+ console.log(`Stopped ${result.name}`);
149
+ }
150
+
151
+ /**
152
+ * Show console output. "logs" is the primary name,
153
+ * "console" is kept as an alias.
154
+ */
155
+ export async function launchLogs(args) {
156
+ const pos = extractPositional(args);
157
+ const flags = parseFlags(args);
158
+ const name = pos[0];
159
+ if (!name) {
160
+ console.error(
161
+ "Usage: launch logs <name> [-f|--follow] [--tail N]",
162
+ );
163
+ process.exit(1);
164
+ }
165
+
166
+ const follow = args.includes("-f") || args.includes("--follow");
167
+ if (follow) {
168
+ const exitCode = await followLogs(name, args);
169
+ process.exit(exitCode);
170
+ }
171
+
172
+ // Snapshot mode
173
+ let url = `/launch/console?name=${encodeURIComponent(name)}`;
174
+ if (flags.tail !== undefined && flags.tail !== true)
175
+ url += `&tail=${flags.tail}`;
176
+ if (args.includes("--stderr")) url += "&stream=stderr";
177
+ else if (args.includes("--stdout")) url += "&stream=stdout";
178
+ const result = await get(url, 30_000);
179
+ if (result.error) {
180
+ console.error(result.error);
181
+ process.exit(1);
182
+ }
183
+ if (result.output) {
184
+ const text = result.output.endsWith("\n")
185
+ ? result.output.slice(0, -1)
186
+ : result.output;
187
+ console.log(text);
188
+ }
189
+ }
190
+
191
+ /** Alias: console → logs */
192
+ export const launchConsole = launchLogs;
193
+
194
+ /**
195
+ * Stream console output until process terminates or Ctrl+C.
196
+ * Returns the process exit code (0 on detach).
197
+ */
198
+ async function followLogs(name, args) {
199
+ const flags = parseFlags(args);
200
+ let url = `/launch/console/stream?name=${encodeURIComponent(name)}`;
201
+ if (flags.tail !== undefined && flags.tail !== true)
202
+ url += `&tail=${flags.tail}`;
203
+ if (args.includes("--stderr")) url += "&stream=stderr";
204
+ else if (args.includes("--stdout")) url += "&stream=stdout";
205
+
206
+ // Ctrl+C = detach, not kill
207
+ let detached = false;
208
+ const onSigint = () => {
209
+ detached = true;
210
+ process.stdout.write("\n");
211
+ process.exit(0);
212
+ };
213
+ process.on("SIGINT", onSigint);
214
+
215
+ try {
216
+ await getStream(url, process.stdout);
217
+ } catch (e) {
218
+ if (!detached) {
219
+ console.error(e.message);
220
+ return 1;
221
+ }
222
+ return 0;
223
+ } finally {
224
+ process.removeListener("SIGINT", onSigint);
225
+ }
226
+
227
+ // Stream ended — fetch exit code
228
+ try {
229
+ const list = await get("/launch/list");
230
+ const entry = Array.isArray(list)
231
+ ? list.find((l) => l.name === name && l.terminated)
232
+ : null;
233
+ return entry?.exitCode ?? 0;
234
+ } catch {
235
+ return 0;
236
+ }
237
+ }
238
+
239
+ export const launchRunHelp = `Launch a saved configuration.
240
+
241
+ Usage: jdt launch run <config-name> [-f] [-q]
242
+
243
+ Without -f, launches and prints a guide with available commands.
244
+ With -f, launches and streams console output until the process terminates.
245
+
246
+ Flags:
247
+ -f, --follow stream output (Ctrl+C to detach, process keeps running)
248
+ -q, --quiet suppress onboarding guide
249
+
250
+ Examples:
251
+ jdt launch run m8-server run + show guide
252
+ jdt launch run m8-server -q run silently
253
+ jdt launch run jdtbridge-verify -f run + stream all output
254
+ jdt launch run m8-server -f | tail -20 run + wait + bounded output`;
255
+
256
+ export const launchDebugHelp = `Launch a configuration in debug mode.
257
+
258
+ Usage: jdt launch debug <config-name> [-f] [-q]
259
+
260
+ Same as "launch run" but attaches the Eclipse debugger.
261
+
262
+ Examples:
263
+ jdt launch debug m8-server
264
+ jdt launch debug m8-server -f`;
265
+
266
+ export const launchStopHelp = `Stop a running launch.
267
+
268
+ Usage: jdt launch stop <name>
269
+
270
+ Example: jdt launch stop m8-server`;
271
+
272
+ export const launchConfigsHelp = `List saved launch configurations (Run → Run Configurations).
273
+
274
+ Usage: jdt launch configs
275
+
276
+ Output: configuration name and type, one per line.`;
277
+
278
+ export const launchClearHelp = `Remove terminated launches and their console output.
279
+
280
+ Usage: jdt launch clear [name]
281
+
282
+ Without name, removes all terminated launches. With name, removes only that one.`;
283
+
284
+ export const launchListHelp = `List all launches (running and terminated).
285
+
286
+ Usage: jdt launch list
287
+
288
+ Output: name, type, mode, status — one launch per line.`;
289
+
290
+ export const launchLogsHelp = `Show console output of a launch.
291
+
292
+ Console output is captured by Eclipse and persists after the process
293
+ terminates. Without -f, returns a snapshot. With -f, streams in real-time.
294
+
295
+ Usage: jdt launch logs <name> [-f|--follow] [--tail N]
296
+
297
+ Flags:
298
+ -f, --follow stream until process terminates (Ctrl+C to detach)
299
+ --tail <N> last N lines only (snapshot), or start N lines back (follow)
300
+
301
+ Examples:
302
+ jdt launch logs m8-server full output snapshot
303
+ jdt launch logs m8-server --tail 30 last 30 lines
304
+ jdt launch logs m8-server -f stream live
305
+ jdt launch logs m8-server -f | tail -20 wait + bounded output`;
306
+
307
+ // Keep old name for backward compatibility
308
+ export const launchConsoleHelp = launchLogsHelp;
@@ -1,5 +1,5 @@
1
1
  import { get } from "../client.mjs";
2
- import { extractPositional, parseFlags } from "../args.mjs";
2
+ import { extractPositional, parseFlags, parseFqmn } from "../args.mjs";
3
3
  import { toWsPath } from "../paths.mjs";
4
4
  import { green, yellow } from "../color.mjs";
5
5
 
@@ -46,19 +46,21 @@ export async function format(args) {
46
46
  export async function rename(args) {
47
47
  const pos = extractPositional(args);
48
48
  const flags = parseFlags(args);
49
- const fqn = pos[0];
49
+ const parsed = parseFqmn(pos[0]);
50
+ const fqn = parsed.className;
50
51
  const newName = pos[1];
51
52
  if (!fqn || !newName) {
52
53
  console.error(
53
- "Usage: rename <FQN> <newName> [--field name] [--method name] [--arity n]",
54
+ "Usage: rename <FQN>[#method[(param types)]] <newName> [--field name]",
54
55
  );
55
56
  process.exit(1);
56
57
  }
57
58
  let url = `/rename?class=${encodeURIComponent(fqn)}&newName=${encodeURIComponent(newName)}`;
58
59
  if (flags.field) url += `&field=${encodeURIComponent(flags.field)}`;
59
- if (flags.method) url += `&method=${encodeURIComponent(flags.method)}`;
60
- if (flags.arity !== undefined && flags.arity !== true)
61
- url += `&arity=${flags.arity}`;
60
+ if (parsed.method) url += `&method=${encodeURIComponent(parsed.method)}`;
61
+ if (parsed.paramTypes) {
62
+ url += `&paramTypes=${encodeURIComponent(parsed.paramTypes.join(","))}`;
63
+ }
62
64
  const result = await get(url, 30_000);
63
65
  if (result.error) {
64
66
  console.error(result.error);
@@ -103,11 +105,12 @@ Example: jdt format m8-server/src/main/java/.../Foo.java`;
103
105
 
104
106
  export const renameHelp = `Rename a type, method, or field (updates all references).
105
107
 
106
- Usage: jdt rename <FQN> <newName> [--method <old>] [--field <old>] [--arity <n>]
108
+ Usage: jdt rename <FQN>[#method[(param types)]] <newName>
109
+ jdt rename <FQN> <newName> [--field <old>]
107
110
 
108
111
  Examples:
109
112
  jdt rename app.m8.dto.Foo Bar
110
- jdt rename app.m8.dto.Foo getBar --method getFoo`;
113
+ jdt rename app.m8.dto.Foo#getFoo getBar`;
111
114
 
112
115
  export const moveHelp = `Move a type to another package (updates all references).
113
116
 
@@ -1,24 +1,26 @@
1
1
  import { get } from "../client.mjs";
2
- import { extractPositional, parseFlags } from "../args.mjs";
2
+ import { extractPositional, parseFlags, parseFqmn } from "../args.mjs";
3
3
  import { formatReferences } from "../format/references.mjs";
4
4
 
5
5
  export async function references(args) {
6
6
  const pos = extractPositional(args);
7
7
  const flags = parseFlags(args);
8
- const fqn = pos[0];
8
+ const parsed = parseFqmn(pos[0]);
9
+ const fqn = parsed.className;
9
10
  if (!fqn) {
10
- console.error("Usage: references <FQN> [method] [--field name] [--arity n]");
11
+ console.error("Usage: references <FQN>[#method[(param types)]] [--field name]");
11
12
  process.exit(1);
12
13
  }
13
14
  let url = `/references?class=${encodeURIComponent(fqn)}`;
14
15
  if (flags.field) {
15
16
  url += `&field=${encodeURIComponent(flags.field)}`;
16
17
  } else {
17
- const method = pos[1];
18
+ const method = parsed.method || pos[1];
18
19
  if (method) url += `&method=${encodeURIComponent(method)}`;
20
+ if (parsed.paramTypes) {
21
+ url += `&paramTypes=${encodeURIComponent(parsed.paramTypes.join(","))}`;
22
+ }
19
23
  }
20
- if (flags.arity !== undefined && flags.arity !== true)
21
- url += `&arity=${flags.arity}`;
22
24
  const results = await get(url, 30_000);
23
25
  if (results.error) {
24
26
  console.error(results.error);
@@ -33,19 +35,20 @@ export async function references(args) {
33
35
 
34
36
  export const help = `Find all references to a type, method, or field across the workspace.
35
37
 
36
- Usage: jdt references <FQN> [method]
38
+ Usage: jdt references <FQN>[#method[(param types)]]
37
39
  jdt references <FQN> --field <name>
38
- jdt references <FQN> [method] --arity <n>
39
40
 
40
- Arguments:
41
- FQN fully qualified class name
42
- method method name (optional)
41
+ FQMN formats (Fully Qualified Method Name):
42
+ pkg.Class#method any overload
43
+ pkg.Class#method() zero-arg overload
44
+ pkg.Class#method(String) specific signature
45
+ pkg.Class.method(String) Eclipse Copy Qualified Name style
43
46
 
44
47
  Flags:
45
48
  --field <name> find references to a field
46
- --arity <n> disambiguate overloaded methods by parameter count
47
49
 
48
50
  Examples:
49
51
  jdt references app.m8.dto.web.core.IdOrgRoot
50
- jdt references app.m8.dao.StaffDaoImpl getStaff
52
+ jdt references app.m8.dao.StaffDaoImpl#getStaff
53
+ jdt references "app.m8.dao.StaffDaoImpl#save(Order)"
51
54
  jdt references app.m8.dao.StaffDaoImpl --field staffCache`;
@@ -1,19 +1,22 @@
1
1
  import { getRaw } from "../client.mjs";
2
- import { extractPositional, parseFlags } from "../args.mjs";
2
+ import { extractPositional, parseFlags, parseFqmn } from "../args.mjs";
3
3
  import { stripProject } from "../paths.mjs";
4
4
 
5
5
  export async function source(args) {
6
6
  const pos = extractPositional(args);
7
7
  const flags = parseFlags(args);
8
- const [fqn, method] = pos;
8
+ const parsed = parseFqmn(pos[0]);
9
+ const fqn = parsed.className;
9
10
  if (!fqn) {
10
- console.error("Usage: source <FQN> [method] [--arity n]");
11
+ console.error("Usage: source <FQN>[#method[(param types)]]");
11
12
  process.exit(1);
12
13
  }
14
+ const method = parsed.method || pos[1];
13
15
  let url = `/source?class=${encodeURIComponent(fqn)}`;
14
16
  if (method) url += `&method=${encodeURIComponent(method)}`;
15
- if (flags.arity !== undefined && flags.arity !== true)
16
- url += `&arity=${flags.arity}`;
17
+ if (parsed.paramTypes) {
18
+ url += `&paramTypes=${encodeURIComponent(parsed.paramTypes.join(","))}`;
19
+ }
17
20
  const result = await getRaw(url, 30_000);
18
21
  const file = result.headers["x-file"] || "?";
19
22
  const startLine = result.headers["x-start-line"] || "?";
@@ -35,9 +38,9 @@ export async function source(args) {
35
38
 
36
39
  export const help = `Print source code of a type or method.
37
40
 
38
- Usage: jdt source <FQN> [method] [--arity <n>]
41
+ Usage: jdt source <FQN>[#method[(param types)]]
39
42
 
40
43
  Examples:
41
44
  jdt source app.m8.dao.StaffDaoImpl
42
- jdt source app.m8.dao.StaffDaoImpl getStaff
43
- jdt source app.m8.dao.StaffDaoImpl save --arity 2`;
45
+ jdt source app.m8.dao.StaffDaoImpl#getStaff
46
+ jdt source "app.m8.dao.StaffDaoImpl#save(Order)"`;
@@ -1,15 +1,16 @@
1
1
  import { get } from "../client.mjs";
2
- import { extractPositional, parseFlags } from "../args.mjs";
2
+ import { extractPositional, parseFlags, parseFqmn } from "../args.mjs";
3
3
  import { formatTestResults } from "../format/test-results.mjs";
4
4
 
5
5
  export async function test(args) {
6
6
  const pos = extractPositional(args);
7
7
  const flags = parseFlags(args);
8
8
  let url = "/test?";
9
- const fqn = pos[0];
9
+ const parsed = parseFqmn(pos[0]);
10
+ const fqn = parsed.className;
10
11
  if (fqn) {
11
12
  url += `class=${encodeURIComponent(fqn)}`;
12
- const method = pos[1];
13
+ const method = parsed.method || pos[1];
13
14
  if (method) url += `&method=${encodeURIComponent(method)}`;
14
15
  } else if (flags.project) {
15
16
  url += `project=${encodeURIComponent(flags.project)}`;
@@ -33,7 +34,7 @@ export async function test(args) {
33
34
 
34
35
  export const help = `Run JUnit tests via Eclipse's built-in test runner.
35
36
 
36
- Usage: jdt test <FQN> [method]
37
+ Usage: jdt test <FQN>[#method]
37
38
  jdt test --project <name> [--package <pkg>]
38
39
 
39
40
  Flags:
@@ -43,5 +44,5 @@ Flags:
43
44
 
44
45
  Examples:
45
46
  jdt test app.m8ws.utils.ObjectMapperTest
46
- jdt test app.m8ws.utils.ObjectMapperTest testSerialize
47
+ jdt test app.m8ws.utils.ObjectMapperTest#testSerialize
47
48
  jdt test --project m8-server`;