Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>io.github.nbclaudecodegui</groupId>
<artifactId>netbeans-claude-code-gui</artifactId>
<version>0.21.28-SNAPSHOT</version>
<version>0.21.29-SNAPSHOT</version>
<packaging>nbm</packaging>
<name>Netbeans Claude Code GUI</name>
<description>NetBeans plugin that provides a GUI for Claude Code CLI — run claude code sessions directly inside the IDE.</description>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ public PtyProcess start(String workingDir, ClaudeProfile profile, String extraCl

appendSessionFlags(cmd, workingDir, env, extraCliArgs, mode, resumeSessionId);

lastCommand = String.join(" ", cmd);
lastCommand = toShellCommand(cmd);
LOG.info("Claude command: " + lastCommand);
PtyProcessBuilder builder = new PtyProcessBuilder(cmd.toArray(new String[0]))
.setEnvironment(env)
Expand Down Expand Up @@ -616,6 +616,37 @@ static Map<String, String> buildEnv(ClaudeProfile profile, java.nio.file.Path pr
* Builds a minimal {@code settings.local.json} from scratch for the given port.
* Used as a fallback when the existing file cannot be parsed.
*/
/**
* Converts a command list to a shell-pasteable string.
* Arguments that contain spaces, quotes, or other shell-special characters
* are quoted so the result can be pasted directly into a terminal.
* On Windows, double-quotes inside an argument are doubled ("").
* On other platforms, they are backslash-escaped (\").
*/
static String toShellCommand(List<String> cmd) {
boolean windows = System.getProperty("os.name", "").toLowerCase().contains("win");
return toShellCommand(cmd, windows);
}

static String toShellCommand(List<String> cmd, boolean windows) {
StringBuilder sb = new StringBuilder();
for (String arg : cmd) {
if (sb.length() > 0) sb.append(' ');
boolean needsQuoting = arg.isEmpty() || arg.chars().anyMatch(c ->
c == ' ' || c == '\t' || c == '"' || c == '\'' || c == '\\' ||
c == '{' || c == '}' || c == '(' || c == ')' ||
c == '&' || c == '|' || c == '<' || c == '>');
if (!needsQuoting) {
sb.append(arg);
} else if (windows) {
sb.append('"').append(arg.replace("\"", "\"\"")).append('"');
} else {
sb.append('"').append(arg.replace("\"", "\\\"")).append('"');
}
}
return sb.toString();
}

/**
* Builds the JSON string to pass as {@code --mcp-config} to the Claude CLI.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.pty4j.PtyProcess;
import io.github.nbclaudecodegui.settings.ClaudeProfile;
import java.io.File;
import java.util.List;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
Expand Down Expand Up @@ -170,6 +171,52 @@ void testCleanupRemovesApiKeyHelperButKeepsUserContent() {
assertTrue(cleaned.contains("myCustomKey"), "cleaned should preserve user content");
}

// -------------------------------------------------------------------------
// toShellCommand
// -------------------------------------------------------------------------

@Test
void testToShellCommandNoQuoting() {
List<String> cmd = List.of("claude", "--continue");
// plain args need no quoting on either platform
assertEquals("claude --continue", ClaudeProcess.toShellCommand(cmd, false));
assertEquals("claude --continue", ClaudeProcess.toShellCommand(cmd, true));
}

@Test
void testToShellCommandQuotesJsonArgUnix() {
String json = ClaudeProcess.buildMcpConfigJson(28991);
List<String> cmd = List.of("claude", "--mcp-config", json, "--continue");
String result = ClaudeProcess.toShellCommand(cmd, false);
// Expected: claude --mcp-config "{\"mcpServers\":...}" --continue
assertTrue(result.startsWith("claude --mcp-config \""), "should quote JSON arg");
assertTrue(result.endsWith("\" --continue"), "should close quote before next arg");
String inner = result.substring("claude --mcp-config \"".length(),
result.lastIndexOf("\" --continue"));
// On Unix all " inside must be escaped as \"
for (int i = 0; i < inner.length(); i++) {
if (inner.charAt(i) == '"') {
assertTrue(i > 0 && inner.charAt(i - 1) == '\\',
"unescaped double-quote at position " + i + " in: " + inner);
}
}
}

@Test
void testToShellCommandQuotesJsonArgWindows() {
String json = ClaudeProcess.buildMcpConfigJson(28991);
List<String> cmd = List.of("claude", "--mcp-config", json, "--continue");
String result = ClaudeProcess.toShellCommand(cmd, true);
// Expected: claude --mcp-config "{""mcpServers"":...}" --continue
assertTrue(result.startsWith("claude --mcp-config \""), "should quote JSON arg");
assertTrue(result.endsWith("\" --continue"), "should close quote before next arg");
String inner = result.substring("claude --mcp-config \"".length(),
result.lastIndexOf("\" --continue"));
// On Windows all " inside must be doubled as ""
assertFalse(inner.contains("\\\""), "Windows quoting must not use backslash-escape");
assertTrue(inner.contains("\"\""), "Windows quoting must double double-quotes");
}

// -------------------------------------------------------------------------
// buildMcpConfigJson
// -------------------------------------------------------------------------
Expand Down
Loading