contextswitch 0.1.6 → 0.1.8
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/{archive-MN425GUR.js → archive-HW56NYMR.js} +2 -2
- package/dist/{chunk-D3DHIVER.js → chunk-72MK25T3.js} +4 -0
- package/dist/{chunk-K7ISIY3Q.js → chunk-7GF2GND2.js} +210 -24
- package/dist/{chunk-BGARUR7R.js → chunk-GDWJEQJQ.js} +1 -1
- package/dist/cli.js +39 -18
- package/dist/{process-X2SYCF5V.js → process-AKHWPKQE.js} +1 -1
- package/dist/{reset-TSWWNV2Y.js → reset-O67AMVTV.js} +2 -2
- package/dist/{switch-CM6GRYEA.js → switch-HUW2NAZD.js} +26 -31
- package/package.json +3 -3
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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,
|
|
@@ -196,12 +251,124 @@ ${script}`);
|
|
|
196
251
|
/**
|
|
197
252
|
* Get the Claude executable path (public accessor)
|
|
198
253
|
*/
|
|
199
|
-
getClaudeExecutable() {
|
|
254
|
+
getClaudeExecutable(configClaudePath) {
|
|
255
|
+
if (configClaudePath && existsSync(configClaudePath)) {
|
|
256
|
+
debug("process", `Using claudePath from config: ${configClaudePath}`);
|
|
257
|
+
return configClaudePath;
|
|
258
|
+
}
|
|
200
259
|
return this.findClaudeExecutable();
|
|
201
260
|
}
|
|
202
261
|
/**
|
|
203
262
|
* Find Git Bash executable on Windows
|
|
204
263
|
*/
|
|
264
|
+
/**
|
|
265
|
+
* Convert a simple bash launch script to PowerShell.
|
|
266
|
+
* Handles: cd, export, exec, echo, if/fi, and comments.
|
|
267
|
+
*/
|
|
268
|
+
convertToPowerShell(bashScript) {
|
|
269
|
+
const lines = bashScript.split("\n");
|
|
270
|
+
const psLines = [];
|
|
271
|
+
for (const line of lines) {
|
|
272
|
+
const trimmed = line.trim();
|
|
273
|
+
if (trimmed.startsWith("#!/")) continue;
|
|
274
|
+
if (trimmed.startsWith("#")) {
|
|
275
|
+
psLines.push(trimmed);
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
if (trimmed === "") {
|
|
279
|
+
psLines.push("");
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
const cdMatch = trimmed.match(/^cd\s+(.+)$/);
|
|
283
|
+
if (cdMatch) {
|
|
284
|
+
const dir = cdMatch[1].replace(/^'|'$/g, "").replace(/'\\''/g, "'");
|
|
285
|
+
psLines.push(`Set-Location "${dir}"`);
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
const exportMatch = trimmed.match(/^export\s+([A-Za-z_][A-Za-z0-9_]*)="(.*)"$/);
|
|
289
|
+
if (exportMatch) {
|
|
290
|
+
psLines.push(`$env:${exportMatch[1]} = "${exportMatch[2]}"`);
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
if (trimmed.startsWith("exec ")) {
|
|
294
|
+
const cmd = trimmed.slice(5);
|
|
295
|
+
const parts = this.parseBashCommand(cmd);
|
|
296
|
+
psLines.push(`& ${parts.map((p) => `"${p}"`).join(" ")}`);
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if (trimmed.startsWith("echo ")) {
|
|
300
|
+
const msg = trimmed.slice(5).replace(/^"|"$/g, "").replace(/^'|'$/g, "");
|
|
301
|
+
psLines.push(`Write-Host "${msg}"`);
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
if (trimmed.match(/^if\s+\[.*\];\s*then$/)) {
|
|
305
|
+
psLines.push("if ($LASTEXITCODE -ne 0) {");
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
if (trimmed === "fi") {
|
|
309
|
+
psLines.push("}");
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
if (trimmed === "RESUME_EXIT=$?") {
|
|
313
|
+
psLines.push("# Exit code captured automatically as $LASTEXITCODE");
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
const cmdLine = trimmed;
|
|
317
|
+
if (cmdLine.match(/^'?[A-Za-z\/\\:]/)) {
|
|
318
|
+
const parts = this.parseBashCommand(cmdLine);
|
|
319
|
+
psLines.push(`& ${parts.map((p) => `"${p}"`).join(" ")}`);
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
psLines.push(`# ${trimmed}`);
|
|
323
|
+
}
|
|
324
|
+
return psLines.join("\r\n");
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Parse a bash command with shell-escaped single-quoted arguments into parts.
|
|
328
|
+
*/
|
|
329
|
+
parseBashCommand(cmd) {
|
|
330
|
+
const parts = [];
|
|
331
|
+
let current = "";
|
|
332
|
+
let inSingleQuote = false;
|
|
333
|
+
let i = 0;
|
|
334
|
+
while (i < cmd.length) {
|
|
335
|
+
const ch = cmd[i];
|
|
336
|
+
if (inSingleQuote) {
|
|
337
|
+
if (ch === "'") {
|
|
338
|
+
if (cmd.slice(i, i + 4) === "'\\''") {
|
|
339
|
+
current += "'";
|
|
340
|
+
i += 4;
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
inSingleQuote = false;
|
|
344
|
+
i++;
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
current += ch;
|
|
348
|
+
i++;
|
|
349
|
+
} else {
|
|
350
|
+
if (ch === "'") {
|
|
351
|
+
inSingleQuote = true;
|
|
352
|
+
i++;
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
if (ch === " " || ch === " ") {
|
|
356
|
+
if (current.length > 0) {
|
|
357
|
+
parts.push(current);
|
|
358
|
+
current = "";
|
|
359
|
+
}
|
|
360
|
+
i++;
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
current += ch;
|
|
364
|
+
i++;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
if (current.length > 0) {
|
|
368
|
+
parts.push(current);
|
|
369
|
+
}
|
|
370
|
+
return parts;
|
|
371
|
+
}
|
|
205
372
|
findGitBash() {
|
|
206
373
|
if (this.platform !== "win32") return null;
|
|
207
374
|
const candidates = [
|
|
@@ -232,9 +399,26 @@ ${script}`);
|
|
|
232
399
|
try {
|
|
233
400
|
if (this.platform === "win32") {
|
|
234
401
|
const output = execSync("where claude", { encoding: "utf-8" }).trim();
|
|
235
|
-
const
|
|
236
|
-
debug("process", `
|
|
237
|
-
|
|
402
|
+
const candidates = output.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
|
403
|
+
debug("process", `'where claude' returned ${candidates.length} result(s): ${candidates.join(", ")}`);
|
|
404
|
+
const cliIndicators = ["npm", "node_modules", "bun", "nvm", ".volta", "Roaming"];
|
|
405
|
+
const guiIndicators = ["Program Files", "AppData\\Local\\claude", "AppData\\Local\\Programs\\claude"];
|
|
406
|
+
for (const candidate of candidates) {
|
|
407
|
+
const isLikelyCLI = cliIndicators.some((ind) => candidate.toLowerCase().includes(ind.toLowerCase()));
|
|
408
|
+
if (isLikelyCLI) {
|
|
409
|
+
debug("process", `Selected CLI candidate: ${candidate}`);
|
|
410
|
+
return candidate;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
for (const candidate of candidates) {
|
|
414
|
+
const isLikelyGUI = guiIndicators.some((ind) => candidate.toLowerCase().includes(ind.toLowerCase()));
|
|
415
|
+
if (!isLikelyGUI) {
|
|
416
|
+
debug("process", `Selected non-GUI candidate: ${candidate}`);
|
|
417
|
+
return candidate;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
debug("process", `Falling back to first 'where' result: ${candidates[0]}`);
|
|
421
|
+
return candidates[0] || null;
|
|
238
422
|
} else {
|
|
239
423
|
const output = execSync("which claude", { encoding: "utf-8" }).trim();
|
|
240
424
|
debug("process", `Found Claude via 'which': ${output}`);
|
|
@@ -243,11 +427,13 @@ ${script}`);
|
|
|
243
427
|
} catch {
|
|
244
428
|
debug("process", "Claude not found via PATH, checking common install locations");
|
|
245
429
|
const commonPaths = this.platform === "win32" ? [
|
|
246
|
-
|
|
247
|
-
join(process.env.
|
|
248
|
-
join(process.env.
|
|
249
|
-
|
|
250
|
-
"
|
|
430
|
+
// npm global installs
|
|
431
|
+
join(process.env.APPDATA || "", "npm", "claude.cmd"),
|
|
432
|
+
join(process.env.APPDATA || "", "npm", "claude"),
|
|
433
|
+
// bun installs
|
|
434
|
+
join(process.env.USERPROFILE || "", ".bun", "bin", "claude.exe"),
|
|
435
|
+
// Volta
|
|
436
|
+
join(process.env.USERPROFILE || "", ".volta", "bin", "claude.exe")
|
|
251
437
|
] : [
|
|
252
438
|
"/usr/local/bin/claude",
|
|
253
439
|
"/usr/bin/claude",
|
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
SessionFallbackMarkerSchema,
|
|
4
4
|
configManager,
|
|
5
5
|
validateDomainName
|
|
6
|
-
} from "./chunk-
|
|
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.
|
|
27
|
+
return "0.1.8";
|
|
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
|
|
70
|
-
cs domain add myproject -w /
|
|
71
|
-
cs switch myproject
|
|
72
|
-
cs list
|
|
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
|
-
|
|
75
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
|
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
|
-
|
|
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-HUW2NAZD.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-
|
|
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-
|
|
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-
|
|
302
|
+
const { processManager } = await import("./process-AKHWPKQE.js");
|
|
282
303
|
console.log(pc.cyan("\u{1FA7A} Running diagnostics...\n"));
|
|
283
304
|
const failures = [];
|
|
284
305
|
const claudeInstalled = processManager.verifyClaudeInstalled();
|
|
@@ -3,11 +3,12 @@ import {
|
|
|
3
3
|
} from "./chunk-MGIXKKM6.js";
|
|
4
4
|
import {
|
|
5
5
|
processManager
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-7GF2GND2.js";
|
|
7
7
|
import {
|
|
8
|
+
LAUNCH_MODES,
|
|
8
9
|
configManager,
|
|
9
10
|
validateEnvVarName
|
|
10
|
-
} from "./chunk-
|
|
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, globalConfig.claudePath);
|
|
79
83
|
} else {
|
|
80
84
|
debug("switch", `New session: ${sessionId.substring(0, 8)}`);
|
|
81
|
-
spawnClaudeNewSession(domain, sessionId);
|
|
85
|
+
spawnClaudeNewSession(domain, sessionId, launchMode, globalConfig.claudePath);
|
|
82
86
|
}
|
|
83
87
|
const newState = {
|
|
84
88
|
...state,
|
|
@@ -187,30 +191,24 @@ function buildEnvLines(env) {
|
|
|
187
191
|
}
|
|
188
192
|
return lines.join("\n");
|
|
189
193
|
}
|
|
190
|
-
function spawnClaudeNewSession(domain, sessionId) {
|
|
191
|
-
const claudeCmd = processManager.getClaudeExecutable();
|
|
194
|
+
function spawnClaudeNewSession(domain, sessionId, mode = "window", configClaudePath) {
|
|
195
|
+
const claudeCmd = processManager.getClaudeExecutable(configClaudePath);
|
|
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
|
-
|
|
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) {
|
|
213
|
-
const claudeCmd = processManager.getClaudeExecutable();
|
|
210
|
+
function spawnClaudeWithFallback(domain, resumeSessionId, fallbackSessionId, mode = "window", configClaudePath) {
|
|
211
|
+
const claudeCmd = processManager.getClaudeExecutable(configClaudePath);
|
|
214
212
|
if (!claudeCmd) {
|
|
215
213
|
throw new Error("Claude CLI not found");
|
|
216
214
|
}
|
|
@@ -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
|
|
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
|
|
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,26 +241,27 @@ 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");
|
|
260
251
|
}
|
|
261
252
|
function printClaudeInstallHelp() {
|
|
262
253
|
console.error(pc.red("\u274C Claude Code CLI not found\n"));
|
|
263
|
-
console.log(pc.white("ContextSwitch requires the Claude Code CLI
|
|
264
|
-
console.log(pc.cyan("Install via npm
|
|
254
|
+
console.log(pc.white("ContextSwitch requires the Claude Code CLI (not the desktop app).\n"));
|
|
255
|
+
console.log(pc.cyan("Install via npm:"));
|
|
265
256
|
console.log(pc.gray(" npm install -g @anthropic-ai/claude-code\n"));
|
|
266
257
|
console.log(pc.cyan("Or see the official docs:"));
|
|
267
258
|
console.log(pc.gray(" https://docs.anthropic.com/en/docs/claude-code\n"));
|
|
268
259
|
console.log(pc.white("After installing, verify it works:"));
|
|
269
|
-
console.log(pc.gray(" claude --version
|
|
260
|
+
console.log(pc.gray(" claude --version"));
|
|
261
|
+
console.log(pc.gray(' (should show "Claude Code" not the desktop app)\n'));
|
|
262
|
+
console.log(pc.white('If "claude" opens the desktop GUI instead, set the CLI path explicitly:'));
|
|
263
|
+
console.log(pc.gray(" Edit your config.yml and add:"));
|
|
264
|
+
console.log(pc.gray(" claudePath: /path/to/claude-code/claude\n"));
|
|
270
265
|
console.log(pc.gray("Then try your command again."));
|
|
271
266
|
}
|
|
272
267
|
export {
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "contextswitch",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
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": "
|
|
9
|
-
"contextswitch": "
|
|
8
|
+
"cs": "dist/cli.js",
|
|
9
|
+
"contextswitch": "dist/cli.js"
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
12
|
"dist",
|