contextswitch 0.1.6 → 0.1.7

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.
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  archiveCommand,
3
3
  listArchives
4
- } from "./chunk-BGARUR7R.js";
5
- import "./chunk-D3DHIVER.js";
4
+ } from "./chunk-GDWJEQJQ.js";
5
+ import "./chunk-72MK25T3.js";
6
6
  import "./chunk-F36TGFK2.js";
7
7
  export {
8
8
  archiveCommand,
@@ -12,6 +12,7 @@ var MCPServerSchema = z.object({
12
12
  });
13
13
  var DOMAIN_NAME_REGEX = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
14
14
  var ENV_VAR_NAME_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
15
+ var LAUNCH_MODES = ["window", "tab", "inline"];
15
16
  function validateDomainName(name) {
16
17
  if (!name || name.length === 0) {
17
18
  return "Domain name cannot be empty";
@@ -39,6 +40,7 @@ var DomainConfigSchema = z.object({
39
40
  z.string().regex(ENV_VAR_NAME_REGEX, "Environment variable names must start with a letter or underscore and contain only letters, digits, and underscores"),
40
41
  z.string()
41
42
  ).optional().describe("Environment variables for this domain"),
43
+ launchMode: z.enum(LAUNCH_MODES).optional().describe("How to open Claude: window, tab, or inline"),
42
44
  extends: z.string().optional().describe("Parent domain to inherit from"),
43
45
  metadata: z.object({
44
46
  description: z.string().optional(),
@@ -66,6 +68,7 @@ var GlobalConfigSchema = z.object({
66
68
  autoArchive: z.boolean().default(true),
67
69
  archiveDirectory: z.string().optional(),
68
70
  claudePath: z.string().optional().describe("Path to Claude CLI executable"),
71
+ defaultLaunchMode: z.enum(LAUNCH_MODES).optional().describe("Default launch mode: window, tab, or inline"),
69
72
  logLevel: z.enum(["debug", "info", "warn", "error"]).default("info"),
70
73
  platform: z.object({
71
74
  forceType: z.enum(["darwin", "linux", "win32"]).optional(),
@@ -329,6 +332,7 @@ var ConfigManager = class {
329
332
  var configManager = new ConfigManager();
330
333
 
331
334
  export {
335
+ LAUNCH_MODES,
332
336
  validateDomainName,
333
337
  SessionFallbackMarkerSchema,
334
338
  validateEnvVarName,
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-F36TGFK2.js";
5
5
 
6
6
  // src/core/process.ts
7
- import { execSync, spawn } from "child_process";
7
+ import { execFileSync, execSync, spawn } from "child_process";
8
8
  import { platform } from "os";
9
9
  import { existsSync, readFileSync, writeFileSync, unlinkSync } from "fs";
10
10
  import { join } from "path";
@@ -142,17 +142,37 @@ var ProcessManager = class {
142
142
  }
143
143
  }
144
144
  /**
145
- * Spawn a shell script in a new terminal window
145
+ * Spawn a shell script in a terminal.
146
+ * @param mode - 'window' (new window), 'tab' (new tab), or 'inline' (current terminal)
146
147
  */
147
- spawnTerminalScript(script, _cwd) {
148
+ spawnTerminalScript(script, _cwd, mode = "window") {
148
149
  const scriptFile = join(paths.baseDir, `launch-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.sh`);
149
- debug("process", `Writing launch script to ${scriptFile}`);
150
+ debug("process", `Writing launch script to ${scriptFile} (mode: ${mode})`);
150
151
  debug("process", `Script contents:
151
152
  ${script}`);
152
153
  writeFileSync(scriptFile, script, { mode: 493 });
154
+ if (mode === "inline") {
155
+ debug("process", "Running inline \u2014 taking over current terminal");
156
+ try {
157
+ execFileSync("bash", [scriptFile], { stdio: "inherit" });
158
+ } catch {
159
+ }
160
+ return 0;
161
+ }
153
162
  if (this.platform === "darwin") {
154
163
  const escapedPath = scriptFile.replace(/"/g, '\\"');
155
- const appleScript = `tell app "Terminal" to do script "bash \\"${escapedPath}\\""`;
164
+ let appleScript;
165
+ if (mode === "tab") {
166
+ appleScript = [
167
+ 'tell application "Terminal"',
168
+ " activate",
169
+ ` tell application "System Events" to keystroke "t" using command down`,
170
+ ` do script "bash \\"${escapedPath}\\"" in front window`,
171
+ "end tell"
172
+ ].join("\n");
173
+ } else {
174
+ appleScript = `tell app "Terminal" to do script "bash \\"${escapedPath}\\""`;
175
+ }
156
176
  const proc2 = spawn("osascript", ["-e", appleScript], {
157
177
  detached: true,
158
178
  stdio: "ignore"
@@ -161,10 +181,12 @@ ${script}`);
161
181
  return proc2.pid || 0;
162
182
  }
163
183
  if (this.platform === "linux") {
184
+ const tabFlag = mode === "tab";
164
185
  const terminals = ["gnome-terminal", "xterm", "konsole", "xfce4-terminal"];
165
186
  for (const term of terminals) {
166
187
  try {
167
- const proc2 = spawn(term, ["--", "bash", scriptFile], {
188
+ const args = term === "gnome-terminal" && tabFlag ? ["--tab", "--", "bash", scriptFile] : ["--", "bash", scriptFile];
189
+ const proc2 = spawn(term, args, {
168
190
  detached: true,
169
191
  stdio: "ignore"
170
192
  });
@@ -175,16 +197,49 @@ ${script}`);
175
197
  }
176
198
  }
177
199
  if (this.platform === "win32") {
200
+ const psScriptFile = scriptFile.replace(/\.sh$/, ".ps1");
201
+ const psScript = this.convertToPowerShell(script);
202
+ writeFileSync(psScriptFile, psScript, { mode: 493 });
203
+ debug("process", `PowerShell script:
204
+ ${psScript}`);
205
+ const useTab = mode === "tab";
206
+ try {
207
+ const wtArgs = useTab ? ["-w", "0", "nt", "powershell", "-ExecutionPolicy", "Bypass", "-File", psScriptFile] : ["powershell", "-ExecutionPolicy", "Bypass", "-File", psScriptFile];
208
+ const proc2 = spawn("wt.exe", wtArgs, {
209
+ detached: true,
210
+ stdio: "ignore",
211
+ windowsHide: false
212
+ });
213
+ proc2.unref();
214
+ debug("process", "Launched via Windows Terminal (wt.exe)");
215
+ return proc2.pid || 0;
216
+ } catch {
217
+ debug("process", "Windows Terminal not available, falling back");
218
+ }
219
+ try {
220
+ const proc2 = spawn("cmd.exe", ["/c", "start", "powershell", "-ExecutionPolicy", "Bypass", "-File", psScriptFile], {
221
+ detached: true,
222
+ stdio: "ignore",
223
+ windowsHide: false
224
+ });
225
+ proc2.unref();
226
+ debug("process", "Launched via PowerShell (cmd start)");
227
+ return proc2.pid || 0;
228
+ } catch {
229
+ debug("process", "PowerShell launch failed, trying Git Bash");
230
+ }
178
231
  const gitBash = this.findGitBash();
179
- const bashCmd = gitBash || "bash";
180
- const scriptPath = scriptFile.replace(/\\/g, "/");
181
- const proc2 = spawn("cmd.exe", ["/c", "start", "", bashCmd, scriptPath], {
182
- detached: true,
183
- stdio: "ignore",
184
- windowsHide: false
185
- });
186
- proc2.unref();
187
- return proc2.pid || 0;
232
+ if (gitBash) {
233
+ const scriptPath = scriptFile.replace(/\\/g, "/");
234
+ const proc2 = spawn("cmd.exe", ["/c", "start", "", gitBash, scriptPath], {
235
+ detached: true,
236
+ stdio: "ignore",
237
+ windowsHide: false
238
+ });
239
+ proc2.unref();
240
+ debug("process", "Launched via Git Bash");
241
+ return proc2.pid || 0;
242
+ }
188
243
  }
189
244
  const proc = spawn("bash", [scriptFile], {
190
245
  detached: true,
@@ -202,6 +257,114 @@ ${script}`);
202
257
  /**
203
258
  * Find Git Bash executable on Windows
204
259
  */
260
+ /**
261
+ * Convert a simple bash launch script to PowerShell.
262
+ * Handles: cd, export, exec, echo, if/fi, and comments.
263
+ */
264
+ convertToPowerShell(bashScript) {
265
+ const lines = bashScript.split("\n");
266
+ const psLines = [];
267
+ for (const line of lines) {
268
+ const trimmed = line.trim();
269
+ if (trimmed.startsWith("#!/")) continue;
270
+ if (trimmed.startsWith("#")) {
271
+ psLines.push(trimmed);
272
+ continue;
273
+ }
274
+ if (trimmed === "") {
275
+ psLines.push("");
276
+ continue;
277
+ }
278
+ const cdMatch = trimmed.match(/^cd\s+(.+)$/);
279
+ if (cdMatch) {
280
+ const dir = cdMatch[1].replace(/^'|'$/g, "").replace(/'\\''/g, "'");
281
+ psLines.push(`Set-Location "${dir}"`);
282
+ continue;
283
+ }
284
+ const exportMatch = trimmed.match(/^export\s+([A-Za-z_][A-Za-z0-9_]*)="(.*)"$/);
285
+ if (exportMatch) {
286
+ psLines.push(`$env:${exportMatch[1]} = "${exportMatch[2]}"`);
287
+ continue;
288
+ }
289
+ if (trimmed.startsWith("exec ")) {
290
+ const cmd = trimmed.slice(5);
291
+ const parts = this.parseBashCommand(cmd);
292
+ psLines.push(`& ${parts.map((p) => `"${p}"`).join(" ")}`);
293
+ continue;
294
+ }
295
+ if (trimmed.startsWith("echo ")) {
296
+ const msg = trimmed.slice(5).replace(/^"|"$/g, "").replace(/^'|'$/g, "");
297
+ psLines.push(`Write-Host "${msg}"`);
298
+ continue;
299
+ }
300
+ if (trimmed.match(/^if\s+\[.*\];\s*then$/)) {
301
+ psLines.push("if ($LASTEXITCODE -ne 0) {");
302
+ continue;
303
+ }
304
+ if (trimmed === "fi") {
305
+ psLines.push("}");
306
+ continue;
307
+ }
308
+ if (trimmed === "RESUME_EXIT=$?") {
309
+ psLines.push("# Exit code captured automatically as $LASTEXITCODE");
310
+ continue;
311
+ }
312
+ const cmdLine = trimmed;
313
+ if (cmdLine.match(/^'?[A-Za-z\/\\:]/)) {
314
+ const parts = this.parseBashCommand(cmdLine);
315
+ psLines.push(`& ${parts.map((p) => `"${p}"`).join(" ")}`);
316
+ continue;
317
+ }
318
+ psLines.push(`# ${trimmed}`);
319
+ }
320
+ return psLines.join("\r\n");
321
+ }
322
+ /**
323
+ * Parse a bash command with shell-escaped single-quoted arguments into parts.
324
+ */
325
+ parseBashCommand(cmd) {
326
+ const parts = [];
327
+ let current = "";
328
+ let inSingleQuote = false;
329
+ let i = 0;
330
+ while (i < cmd.length) {
331
+ const ch = cmd[i];
332
+ if (inSingleQuote) {
333
+ if (ch === "'") {
334
+ if (cmd.slice(i, i + 4) === "'\\''") {
335
+ current += "'";
336
+ i += 4;
337
+ continue;
338
+ }
339
+ inSingleQuote = false;
340
+ i++;
341
+ continue;
342
+ }
343
+ current += ch;
344
+ i++;
345
+ } else {
346
+ if (ch === "'") {
347
+ inSingleQuote = true;
348
+ i++;
349
+ continue;
350
+ }
351
+ if (ch === " " || ch === " ") {
352
+ if (current.length > 0) {
353
+ parts.push(current);
354
+ current = "";
355
+ }
356
+ i++;
357
+ continue;
358
+ }
359
+ current += ch;
360
+ i++;
361
+ }
362
+ }
363
+ if (current.length > 0) {
364
+ parts.push(current);
365
+ }
366
+ return parts;
367
+ }
205
368
  findGitBash() {
206
369
  if (this.platform !== "win32") return null;
207
370
  const candidates = [
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  configManager
3
- } from "./chunk-D3DHIVER.js";
3
+ } from "./chunk-72MK25T3.js";
4
4
  import {
5
5
  paths
6
6
  } from "./chunk-F36TGFK2.js";
package/dist/cli.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  SessionFallbackMarkerSchema,
4
4
  configManager,
5
5
  validateDomainName
6
- } from "./chunk-D3DHIVER.js";
6
+ } from "./chunk-72MK25T3.js";
7
7
  import {
8
8
  debug,
9
9
  enableDebug,
@@ -24,7 +24,7 @@ function getVersion() {
24
24
  const packageJson = JSON.parse(readFileSync(packagePath, "utf-8"));
25
25
  return packageJson.version;
26
26
  } catch {
27
- return "0.1.6";
27
+ return "0.1.7";
28
28
  }
29
29
  }
30
30
  function processSessionFallbackMarker() {
@@ -66,17 +66,34 @@ function requireValidDomainName(name) {
66
66
  var program = new Command();
67
67
  program.name("cs").description("Domain-based context management for Claude Code CLI - Each domain represents a project with its own working directory and configuration").version(getVersion()).option("--verbose", "Enable debug output for troubleshooting").addHelpText("after", `
68
68
  Quick Start:
69
- cs init Initialize ContextSwitch
70
- cs domain add myproject -w /path/to/project Create a domain with a working directory
71
- cs switch myproject Switch to the domain (launches Claude)
72
- cs list See all your domains
69
+ cs init Initialize ContextSwitch
70
+ cs domain add myproject -w ~/projects/app Create a domain
71
+ cs switch myproject Switch to domain (launches Claude)
72
+ cs list See all domains
73
73
 
74
- The --working-dir (-w) flag sets where Claude will run. If omitted, the current directory is used.
75
- Run "cs domain add --help" for more details.
74
+ Launch Modes:
75
+ cs switch myproject Opens Claude in a new terminal window (default)
76
+ cs switch myproject --mode tab Opens Claude in a new terminal tab
77
+ cs switch myproject --mode inline Runs Claude in the current terminal
76
78
 
77
- Debug:
78
- cs --verbose <command> Show detailed debug output
79
- CS_DEBUG=1 cs <command> Same via environment variable`).hook("preAction", () => {
79
+ Set a default in domain YAML (launchMode: tab) or global config (defaultLaunchMode: inline).
80
+ Priority: --mode flag > domain config > global config > window.
81
+
82
+ Session Management:
83
+ cs status [domain] Show session info and age
84
+ cs reset <domain> Reset session (archives first)
85
+ cs reset <domain> --skip-archive Reset without archiving
86
+ cs archive <domain> Snapshot the current session
87
+
88
+ Domain Management:
89
+ cs domain add <name> -w <path> Create a domain (uses cwd if -w omitted)
90
+ cs domain add <name> --extends <parent> Inherit from a parent domain
91
+ cs domain remove <name> Remove a domain (prompts for confirmation)
92
+
93
+ Diagnostics:
94
+ cs doctor Check Claude install and system config
95
+ cs --verbose <command> Show debug output
96
+ CS_DEBUG=1 cs <command> Same via environment variable`).hook("preAction", () => {
80
97
  if (program.opts().verbose || process.env.CS_DEBUG === "1") {
81
98
  enableDebug();
82
99
  debug("cli", `ContextSwitch v${getVersion()}`);
@@ -170,12 +187,16 @@ ${pc.cyan("Session Information:")}`);
170
187
  process.exit(1);
171
188
  }
172
189
  });
173
- program.command("switch <domain>").description("Switch to a different domain").option("-f, --force", "Force new session even if one exists").action(async (domain, options) => {
190
+ program.command("switch <domain>").description("Switch to a domain and launch Claude\n\nResumes the existing session if one exists, or creates a new one.\nUse --force to discard the current session and start fresh.").option("-f, --force", "Force new session even if one exists").option("-m, --mode <mode>", "Launch mode: window (default), tab, or inline").action(async (domain, options) => {
174
191
  requireValidDomainName(domain);
175
- const { switchCommand } = await import("./switch-CM6GRYEA.js");
192
+ if (options.mode && !["window", "tab", "inline"].includes(options.mode)) {
193
+ console.error(pc.red(`\u274C Invalid launch mode '${options.mode}'. Must be: window, tab, or inline`));
194
+ process.exit(1);
195
+ }
196
+ const { switchCommand } = await import("./switch-RRK6CHOB.js");
176
197
  await switchCommand(domain, options);
177
198
  });
178
- program.command("reset <domain>").description("Reset a domain session (archives current session)").option("--skip-archive", "Skip archiving the current session").option("-f, --force", "Skip confirmation prompt").action(async (domain, options) => {
199
+ program.command("reset <domain>").description("Reset a domain session (archives current session by default)").option("--skip-archive", "Skip archiving the current session").option("-f, --force", "Skip confirmation prompt").action(async (domain, options) => {
179
200
  requireValidDomainName(domain);
180
201
  if (!options.force) {
181
202
  const enquirer = await import("enquirer");
@@ -190,20 +211,20 @@ program.command("reset <domain>").description("Reset a domain session (archives
190
211
  return;
191
212
  }
192
213
  }
193
- const { resetCommand } = await import("./reset-TSWWNV2Y.js");
214
+ const { resetCommand } = await import("./reset-O67AMVTV.js");
194
215
  await resetCommand(domain, options);
195
216
  });
196
217
  program.command("archive <domain>").description("Archive the current session for a domain").action(async (domain) => {
197
218
  requireValidDomainName(domain);
198
219
  try {
199
- const { archiveCommand } = await import("./archive-MN425GUR.js");
220
+ const { archiveCommand } = await import("./archive-HW56NYMR.js");
200
221
  await archiveCommand(domain);
201
222
  } catch (error) {
202
223
  console.error(pc.red(`\u274C ${error}`));
203
224
  process.exit(1);
204
225
  }
205
226
  });
206
- var domainCmd = program.command("domain").description("Manage domains");
227
+ var domainCmd = program.command("domain").alias("domains").description("Manage domains");
207
228
  domainCmd.command("add <name>").description('Create a new domain\n\nExamples:\n cs domain add myapi --working-dir /path/to/project\n cs domain add frontend -w C:\\Users\\you\\app -d "React frontend"\n\nIf --working-dir is not specified, the current directory is used.').option("-w, --working-dir <path>", "Working directory for this domain (defaults to current directory)").option("-d, --description <text>", "Domain description").option("--extends <parent>", "Inherit from parent domain").action(async (name, options) => {
208
229
  try {
209
230
  const nameError = validateDomainName(name);
@@ -278,7 +299,7 @@ domainCmd.command("remove <name>").alias("rm").description("Remove a domain from
278
299
  }
279
300
  });
280
301
  program.command("doctor").description("Check system configuration and diagnose issues").action(async () => {
281
- const { processManager } = await import("./process-X2SYCF5V.js");
302
+ const { processManager } = await import("./process-7O6IL3T4.js");
282
303
  console.log(pc.cyan("\u{1FA7A} Running diagnostics...\n"));
283
304
  const failures = [];
284
305
  const claudeInstalled = processManager.verifyClaudeInstalled();
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ProcessManager,
3
3
  processManager
4
- } from "./chunk-K7ISIY3Q.js";
4
+ } from "./chunk-GBKMEML7.js";
5
5
  import "./chunk-F36TGFK2.js";
6
6
  export {
7
7
  ProcessManager,
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  archiveCommand
3
- } from "./chunk-BGARUR7R.js";
3
+ } from "./chunk-GDWJEQJQ.js";
4
4
  import {
5
5
  configManager
6
- } from "./chunk-D3DHIVER.js";
6
+ } from "./chunk-72MK25T3.js";
7
7
  import "./chunk-F36TGFK2.js";
8
8
 
9
9
  // src/commands/reset.ts
@@ -3,11 +3,12 @@ import {
3
3
  } from "./chunk-MGIXKKM6.js";
4
4
  import {
5
5
  processManager
6
- } from "./chunk-K7ISIY3Q.js";
6
+ } from "./chunk-GBKMEML7.js";
7
7
  import {
8
+ LAUNCH_MODES,
8
9
  configManager,
9
10
  validateEnvVarName
10
- } from "./chunk-D3DHIVER.js";
11
+ } from "./chunk-72MK25T3.js";
11
12
  import {
12
13
  debug,
13
14
  paths
@@ -67,18 +68,21 @@ async function switchCommand(domainName, options = {}) {
67
68
  isNewSession = true;
68
69
  console.log(pc.gray(`Creating new session ${sessionId.substring(0, 8)}...`));
69
70
  }
71
+ const globalConfig = configManager.loadGlobalConfig();
72
+ const launchMode = (options.mode && LAUNCH_MODES.includes(options.mode) ? options.mode : void 0) || domain.launchMode || globalConfig.defaultLaunchMode || "window";
70
73
  debug("switch", `Session ID: ${sessionId} (${isNewSession ? "new" : "resuming"})`);
71
74
  debug("switch", `Working directory: ${domain.workingDirectory}`);
75
+ debug("switch", `Launch mode: ${launchMode}`);
72
76
  await updateMCPConfig(domain);
73
77
  await handleMemoryFiles(domain);
74
78
  console.log(pc.gray("Starting Claude with new configuration..."));
75
79
  if (!isNewSession) {
76
80
  const fallbackSessionId = SessionManager.generateSessionId(domainName, true);
77
81
  debug("switch", `Resume with fallback: resume=${sessionId.substring(0, 8)}, fallback=${fallbackSessionId.substring(0, 8)}`);
78
- spawnClaudeWithFallback(domain, sessionId, fallbackSessionId);
82
+ spawnClaudeWithFallback(domain, sessionId, fallbackSessionId, launchMode);
79
83
  } else {
80
84
  debug("switch", `New session: ${sessionId.substring(0, 8)}`);
81
- spawnClaudeNewSession(domain, sessionId);
85
+ spawnClaudeNewSession(domain, sessionId, launchMode);
82
86
  }
83
87
  const newState = {
84
88
  ...state,
@@ -187,29 +191,23 @@ function buildEnvLines(env) {
187
191
  }
188
192
  return lines.join("\n");
189
193
  }
190
- function spawnClaudeNewSession(domain, sessionId) {
194
+ function spawnClaudeNewSession(domain, sessionId, mode = "window") {
191
195
  const claudeCmd = processManager.getClaudeExecutable();
192
196
  if (!claudeCmd) {
193
197
  throw new Error("Claude CLI not found");
194
198
  }
195
199
  const cwd = domain.workingDirectory;
196
- const domainName = domain.name;
197
- const pidFile = shellEscapeArg(join(paths.baseDir, `${domainName}.pid`));
198
200
  const envLines = buildEnvLines(domain.env);
199
201
  const script = [
200
202
  "#!/bin/bash",
201
203
  `cd ${shellEscapeArg(cwd)}`,
202
204
  envLines,
203
205
  "",
204
- "# Launch Claude and capture its real PID",
205
- `${shellEscapeArg(claudeCmd)} --session-id ${shellEscapeArg(sessionId)} &`,
206
- "CLAUDE_PID=$!",
207
- `echo $CLAUDE_PID > ${pidFile}`,
208
- "wait $CLAUDE_PID"
206
+ `exec ${shellEscapeArg(claudeCmd)} --session-id ${shellEscapeArg(sessionId)}`
209
207
  ].join("\n");
210
- return processManager.spawnTerminalScript(script, cwd);
208
+ return processManager.spawnTerminalScript(script, cwd, mode);
211
209
  }
212
- function spawnClaudeWithFallback(domain, resumeSessionId, fallbackSessionId) {
210
+ function spawnClaudeWithFallback(domain, resumeSessionId, fallbackSessionId, mode = "window") {
213
211
  const claudeCmd = processManager.getClaudeExecutable();
214
212
  if (!claudeCmd) {
215
213
  throw new Error("Claude CLI not found");
@@ -225,20 +223,16 @@ function spawnClaudeWithFallback(domain, resumeSessionId, fallbackSessionId) {
225
223
  const escapedResumeId = shellEscapeArg(resumeSessionId);
226
224
  const escapedFallbackId = shellEscapeArg(fallbackSessionId);
227
225
  const escapedMarkerFile = shellEscapeArg(markerFile);
228
- const pidFile = shellEscapeArg(join(paths.baseDir, `${domainName}.pid`));
229
226
  const script = [
230
227
  "#!/bin/bash",
231
228
  `cd ${escapedCwd}`,
232
229
  envLines,
233
230
  "",
234
- "# Try to resume the existing session (background + wait to capture PID)",
235
- `${escapedClaudeCmd} --resume ${escapedResumeId} &`,
236
- "CLAUDE_PID=$!",
237
- `echo $CLAUDE_PID > ${pidFile}`,
238
- "wait $CLAUDE_PID",
231
+ "# Try to resume existing session (foreground to preserve TTY)",
232
+ `${escapedClaudeCmd} --resume ${escapedResumeId}`,
239
233
  "RESUME_EXIT=$?",
240
234
  "",
241
- "# If resume failed (non-zero exit), fall back to a new session",
235
+ "# If resume failed, fall back to a new session",
242
236
  "if [ $RESUME_EXIT -ne 0 ]; then",
243
237
  ' echo ""',
244
238
  ' echo "Previous session could not be resumed. Starting a fresh session..."',
@@ -247,13 +241,10 @@ function spawnClaudeWithFallback(domain, resumeSessionId, fallbackSessionId) {
247
241
  " # Write marker so cs can update state with the new session ID",
248
242
  ` echo '${markerJsonEscaped}' > ${escapedMarkerFile}`,
249
243
  "",
250
- ` ${escapedClaudeCmd} --session-id ${escapedFallbackId} &`,
251
- " CLAUDE_PID=$!",
252
- ` echo $CLAUDE_PID > ${pidFile}`,
253
- " wait $CLAUDE_PID",
244
+ ` exec ${escapedClaudeCmd} --session-id ${escapedFallbackId}`,
254
245
  "fi"
255
246
  ].join("\n");
256
- return processManager.spawnTerminalScript(script, cwd);
247
+ return processManager.spawnTerminalScript(script, cwd, mode);
257
248
  }
258
249
  function getSessionFallbackPath() {
259
250
  return join(paths.baseDir, "session-fallback.json");
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "contextswitch",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Domain-based context management for Claude Code CLI",
5
5
  "main": "dist/cli.js",
6
6
  "type": "module",
7
7
  "bin": {
8
- "cs": "./dist/cli.js",
9
- "contextswitch": "./dist/cli.js"
8
+ "cs": "dist/cli.js",
9
+ "contextswitch": "dist/cli.js"
10
10
  },
11
11
  "files": [
12
12
  "dist",