contextswitch 0.1.5 → 0.1.6

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.
@@ -0,0 +1,10 @@
1
+ import {
2
+ archiveCommand,
3
+ listArchives
4
+ } from "./chunk-BGARUR7R.js";
5
+ import "./chunk-D3DHIVER.js";
6
+ import "./chunk-F36TGFK2.js";
7
+ export {
8
+ archiveCommand,
9
+ listArchives
10
+ };
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  configManager
3
- } from "./chunk-YMFZWGZO.js";
3
+ } from "./chunk-D3DHIVER.js";
4
4
  import {
5
5
  paths
6
- } from "./chunk-A7YXSI66.js";
6
+ } from "./chunk-F36TGFK2.js";
7
7
 
8
8
  // src/commands/archive.ts
9
9
  import picocolors from "picocolors";
@@ -26,18 +26,18 @@ async function archiveCommand(domainName) {
26
26
  if (!existsSync(archiveDir)) {
27
27
  mkdirSync(archiveDir, { recursive: true });
28
28
  }
29
+ const domain = configManager.loadDomain(domainName);
29
30
  const timestamp = /* @__PURE__ */ new Date();
30
31
  const archiveMetadata = {
31
32
  domain: domainName,
32
33
  session,
33
34
  timestamp: timestamp.toISOString(),
34
- config: configManager.loadDomain(domainName)
35
+ config: domain
35
36
  };
36
37
  const dateStr = timestamp.toISOString().replace(/[:.]/g, "-").replace("T", "_").slice(0, -5);
37
38
  const archiveName = `${dateStr}.json`;
38
39
  const archivePath = join(archiveDir, archiveName);
39
40
  writeFileSync(archivePath, JSON.stringify(archiveMetadata, null, 2), "utf-8");
40
- const domain = configManager.loadDomain(domainName);
41
41
  if (domain.claudeConfig?.memory) {
42
42
  const memoryArchiveDir = join(archiveDir, dateStr, "memory");
43
43
  mkdirSync(memoryArchiveDir, { recursive: true });
@@ -56,11 +56,10 @@ async function archiveCommand(domainName) {
56
56
  console.log(pc.green(`\u2705 Session archived successfully`));
57
57
  console.log(pc.gray(`Archive: ${archivePath}`));
58
58
  console.log(pc.gray(`Size: ${sizeKB} KB`));
59
- const { SessionManager } = await import("./session-IWXAKW6Z.js");
59
+ const { SessionManager } = await import("./session-H5HPE5OT.js");
60
60
  console.log(pc.gray(`Session age: ${SessionManager.getSessionAge(session.started)}`));
61
61
  } catch (error) {
62
- console.error(pc.red(`\u274C Failed to archive session: ${error}`));
63
- process.exit(1);
62
+ throw new Error(`Failed to archive session: ${error}`);
64
63
  }
65
64
  }
66
65
  function listArchives(domainName) {
@@ -1,10 +1,7 @@
1
1
  import {
2
+ debug,
2
3
  paths
3
- } from "./chunk-A7YXSI66.js";
4
-
5
- // src/core/config.ts
6
- import { readFileSync, existsSync, writeFileSync, readdirSync, renameSync, unlinkSync } from "fs";
7
- import { parse, stringify } from "yaml";
4
+ } from "./chunk-F36TGFK2.js";
8
5
 
9
6
  // src/core/domain.ts
10
7
  import { z } from "zod";
@@ -13,15 +10,35 @@ var MCPServerSchema = z.object({
13
10
  args: z.array(z.string()).optional().describe("Arguments for the command"),
14
11
  env: z.record(z.string()).optional().describe("Environment variables")
15
12
  });
13
+ var DOMAIN_NAME_REGEX = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
14
+ var ENV_VAR_NAME_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
15
+ function validateDomainName(name) {
16
+ if (!name || name.length === 0) {
17
+ return "Domain name cannot be empty";
18
+ }
19
+ if (name.length > 64) {
20
+ return "Domain name must be 64 characters or fewer";
21
+ }
22
+ if (!DOMAIN_NAME_REGEX.test(name)) {
23
+ return "Domain name must start with a letter or number and contain only letters, numbers, hyphens, underscores, and dots";
24
+ }
25
+ if (name === "." || name === "..") {
26
+ return 'Domain name cannot be "." or ".."';
27
+ }
28
+ return null;
29
+ }
16
30
  var DomainConfigSchema = z.object({
17
- name: z.string().min(1).describe("Domain identifier"),
31
+ name: z.string().min(1).regex(DOMAIN_NAME_REGEX, "Domain name must start with a letter or number and contain only letters, numbers, hyphens, underscores, and dots").describe("Domain identifier"),
18
32
  workingDirectory: z.string().describe("Working directory for this domain"),
19
33
  claudeConfig: z.object({
20
34
  instructions: z.string().optional().describe("Path to CLAUDE.md file"),
21
35
  memory: z.array(z.string()).optional().describe("Memory file paths")
22
36
  }).optional(),
23
37
  mcpServers: z.record(MCPServerSchema).optional().describe("MCP server configurations"),
24
- env: z.record(z.string()).optional().describe("Environment variables for this domain"),
38
+ env: z.record(
39
+ 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
+ z.string()
41
+ ).optional().describe("Environment variables for this domain"),
25
42
  extends: z.string().optional().describe("Parent domain to inherit from"),
26
43
  metadata: z.object({
27
44
  description: z.string().optional(),
@@ -56,6 +73,10 @@ var GlobalConfigSchema = z.object({
56
73
  killTimeout: z.number().default(5e3).describe("Milliseconds to wait before force kill")
57
74
  }).optional()
58
75
  });
76
+ var SessionFallbackMarkerSchema = z.object({
77
+ domain: z.string().min(1).regex(DOMAIN_NAME_REGEX),
78
+ sessionId: z.string().min(1)
79
+ });
59
80
  var DEFAULT_DOMAIN_TEMPLATE = {
60
81
  name: "default",
61
82
  workingDirectory: process.cwd(),
@@ -70,6 +91,15 @@ var DEFAULT_DOMAIN_TEMPLATE = {
70
91
  tags: []
71
92
  }
72
93
  };
94
+ function validateEnvVarName(name) {
95
+ if (!name || name.length === 0) {
96
+ return "Environment variable name cannot be empty";
97
+ }
98
+ if (!ENV_VAR_NAME_REGEX.test(name)) {
99
+ return `Invalid environment variable name '${name}': must start with a letter or underscore and contain only letters, digits, and underscores`;
100
+ }
101
+ return null;
102
+ }
73
103
  function validateDomain(data) {
74
104
  return DomainConfigSchema.parse(data);
75
105
  }
@@ -86,30 +116,28 @@ function createInitialState() {
86
116
  }
87
117
 
88
118
  // src/core/config.ts
119
+ import { readFileSync, existsSync, writeFileSync, readdirSync, renameSync, unlinkSync } from "fs";
120
+ import { parse, stringify } from "yaml";
89
121
  var ConfigManager = class {
90
- globalConfig = null;
91
- state = null;
92
- domainCache = /* @__PURE__ */ new Map();
93
122
  /**
94
123
  * Load global configuration
95
124
  */
96
125
  loadGlobalConfig() {
97
126
  const configPath = paths.globalConfigFile;
98
127
  if (!existsSync(configPath)) {
99
- this.globalConfig = {
128
+ const defaultConfig = {
100
129
  version: "1.0.0",
101
130
  defaultDomain: "default",
102
131
  autoArchive: true,
103
132
  logLevel: "info"
104
133
  };
105
- this.saveGlobalConfig(this.globalConfig);
106
- return this.globalConfig;
134
+ this.saveGlobalConfig(defaultConfig);
135
+ return defaultConfig;
107
136
  }
108
137
  try {
109
138
  const content = readFileSync(configPath, "utf-8");
110
139
  const data = parse(content);
111
- this.globalConfig = GlobalConfigSchema.parse(data);
112
- return this.globalConfig;
140
+ return GlobalConfigSchema.parse(data);
113
141
  } catch (error) {
114
142
  throw new Error(`Failed to load global config: ${error}`);
115
143
  }
@@ -123,23 +151,25 @@ var ConfigManager = class {
123
151
  const content = stringify(config, { indent: 2 });
124
152
  writeFileSync(tempPath, content, "utf-8");
125
153
  renameSync(tempPath, configPath);
126
- this.globalConfig = config;
127
154
  }
128
155
  /**
129
156
  * Load state file
130
157
  */
131
158
  loadState() {
132
159
  const statePath = paths.stateFile;
160
+ debug("config", `Loading state from ${statePath}`);
133
161
  if (!existsSync(statePath)) {
134
- this.state = createInitialState();
135
- this.saveState(this.state);
136
- return this.state;
162
+ debug("config", "No state file found, creating initial state");
163
+ const initial = createInitialState();
164
+ this.saveState(initial);
165
+ return initial;
137
166
  }
138
167
  try {
139
168
  const content = readFileSync(statePath, "utf-8");
140
169
  const data = JSON.parse(content);
141
- this.state = validateState(data);
142
- return this.state;
170
+ const state = validateState(data);
171
+ debug("config", `State loaded: activeDomain=${state.activeDomain}, sessions=${Object.keys(state.sessions || {}).join(",") || "none"}`);
172
+ return state;
143
173
  } catch (error) {
144
174
  throw new Error(`Failed to load state: ${error}`);
145
175
  }
@@ -150,19 +180,23 @@ var ConfigManager = class {
150
180
  saveState(state) {
151
181
  const statePath = paths.stateFile;
152
182
  const tempPath = `${statePath}.tmp`;
183
+ debug("config", `Saving state to ${statePath} (activeDomain=${state.activeDomain})`);
153
184
  const content = JSON.stringify(state, null, 2);
154
185
  writeFileSync(tempPath, content, "utf-8");
155
186
  renameSync(tempPath, statePath);
156
- this.state = state;
157
187
  }
158
188
  /**
159
189
  * Load a domain configuration
160
190
  */
161
191
  loadDomain(domainName) {
162
- if (this.domainCache.has(domainName)) {
163
- return this.domainCache.get(domainName);
164
- }
192
+ return this.loadDomainInternal(domainName, []);
193
+ }
194
+ /**
195
+ * Internal domain loader with cycle detection for inheritance chains.
196
+ */
197
+ loadDomainInternal(domainName, ancestors) {
165
198
  const domainPath = paths.domainConfigFile(domainName);
199
+ debug("config", `Loading domain '${domainName}' from ${domainPath}`);
166
200
  if (!existsSync(domainPath)) {
167
201
  throw new Error(`Domain '${domainName}' not found`);
168
202
  }
@@ -171,7 +205,11 @@ var ConfigManager = class {
171
205
  const data = parse(content);
172
206
  let domain = validateDomain(data);
173
207
  if (domain.extends) {
174
- const parent = this.loadDomain(domain.extends);
208
+ if (ancestors.includes(domain.extends)) {
209
+ throw new Error(`Circular inheritance detected: ${[...ancestors, domainName, domain.extends].join(" -> ")}`);
210
+ }
211
+ debug("config", `Domain '${domainName}' extends '${domain.extends}', merging`);
212
+ const parent = this.loadDomainInternal(domain.extends, [...ancestors, domainName]);
175
213
  domain = this.mergeDomains(parent, domain);
176
214
  }
177
215
  domain.workingDirectory = paths.expandPath(domain.workingDirectory);
@@ -181,7 +219,6 @@ var ConfigManager = class {
181
219
  if (domain.claudeConfig?.memory) {
182
220
  domain.claudeConfig.memory = domain.claudeConfig.memory.map((p) => paths.expandPath(p));
183
221
  }
184
- this.domainCache.set(domainName, domain);
185
222
  return domain;
186
223
  } catch (error) {
187
224
  throw new Error(`Failed to load domain '${domainName}': ${error}`);
@@ -196,7 +233,6 @@ var ConfigManager = class {
196
233
  const content = stringify(domain, { indent: 2 });
197
234
  writeFileSync(tempPath, content, "utf-8");
198
235
  renameSync(tempPath, domainPath);
199
- this.domainCache.set(domain.name, domain);
200
236
  }
201
237
  /**
202
238
  * Create a new domain from template
@@ -241,7 +277,6 @@ var ConfigManager = class {
241
277
  throw new Error(`Domain '${domainName}' not found`);
242
278
  }
243
279
  unlinkSync(domainPath);
244
- this.domainCache.delete(domainName);
245
280
  }
246
281
  /**
247
282
  * Merge two domain configurations (child overrides parent)
@@ -289,13 +324,13 @@ var ConfigManager = class {
289
324
  * Clear cache
290
325
  */
291
326
  clearCache() {
292
- this.globalConfig = null;
293
- this.state = null;
294
- this.domainCache.clear();
295
327
  }
296
328
  };
297
329
  var configManager = new ConfigManager();
298
330
 
299
331
  export {
332
+ validateDomainName,
333
+ SessionFallbackMarkerSchema,
334
+ validateEnvVarName,
300
335
  configManager
301
336
  };
@@ -72,6 +72,9 @@ var Paths = class _Paths {
72
72
  * Get domain config file path
73
73
  */
74
74
  domainConfigFile(domainName) {
75
+ if (domainName.includes("/") || domainName.includes("\\") || domainName === ".." || domainName === ".") {
76
+ throw new Error(`Unsafe domain name: ${domainName}`);
77
+ }
75
78
  return join(this.domainsDir, `${domainName}.yml`);
76
79
  }
77
80
  /**
@@ -108,7 +111,9 @@ var Paths = class _Paths {
108
111
  * Expand environment variables and ~ in paths
109
112
  */
110
113
  expandPath(inputPath) {
111
- if (inputPath.startsWith("~/")) {
114
+ if (inputPath === "~") {
115
+ inputPath = homedir();
116
+ } else if (inputPath.startsWith("~/")) {
112
117
  inputPath = join(homedir(), inputPath.slice(2));
113
118
  }
114
119
  inputPath = inputPath.replace(/\$([A-Z_][A-Z0-9_]*)/gi, (match, varName) => {
@@ -158,6 +163,20 @@ var Paths = class _Paths {
158
163
  };
159
164
  var paths = Paths.instance;
160
165
 
166
+ // src/core/debug.ts
167
+ import picocolors from "picocolors";
168
+ var pc = picocolors;
169
+ var enabled = false;
170
+ function enableDebug() {
171
+ enabled = true;
172
+ }
173
+ function debug(context, message) {
174
+ if (!enabled) return;
175
+ console.log(pc.dim(`[debug:${context}] ${message}`));
176
+ }
177
+
161
178
  export {
162
- paths
179
+ paths,
180
+ enableDebug,
181
+ debug
163
182
  };
@@ -1,6 +1,7 @@
1
1
  import {
2
+ debug,
2
3
  paths
3
- } from "./chunk-A7YXSI66.js";
4
+ } from "./chunk-F36TGFK2.js";
4
5
 
5
6
  // src/core/process.ts
6
7
  import { execSync, spawn } from "child_process";
@@ -47,9 +48,11 @@ var ProcessManager = class {
47
48
  const basename = command.split("/").pop() || "";
48
49
  const isClaudeBinary = basename === "claude" || basename === "claude.exe";
49
50
  if (isClaudeBinary) {
51
+ debug("process", `Found Claude process: PID=${pid} cmd=${command} args=${args.join(" ")}`);
50
52
  processes.push({ pid, command, args });
51
53
  }
52
54
  }
55
+ debug("process", `Unix process scan found ${processes.length} Claude process(es)`);
53
56
  return processes;
54
57
  } catch (error) {
55
58
  console.error(`Unix process detection failed: ${error}`);
@@ -73,11 +76,17 @@ var ProcessManager = class {
73
76
  const match = line.match(/^"?(\d+)"?,"?(.*?)"?$/);
74
77
  if (match) {
75
78
  const pid = parseInt(match[1], 10);
79
+ if (!Number.isInteger(pid) || pid <= 0) continue;
76
80
  const commandLine = match[2] || "";
77
- if (commandLine.includes("claude") && !commandLine.includes("cs switch")) {
81
+ if (commandLine.includes("cs switch")) continue;
82
+ const exe = commandLine.split(/[\s"]+/)[0] || "";
83
+ const bin = exe.split(/[/\\]/).pop() || "";
84
+ const isClaudeBinary = bin === "claude" || bin === "claude.exe";
85
+ if (isClaudeBinary) {
86
+ debug("process", `Found Claude process: PID=${pid} cmd=${commandLine}`);
78
87
  processes.push({
79
88
  pid,
80
- command: "claude",
89
+ command: exe,
81
90
  args: commandLine.split(" ").slice(1)
82
91
  });
83
92
  }
@@ -93,6 +102,10 @@ var ProcessManager = class {
93
102
  * Kill a process by PID
94
103
  */
95
104
  killProcess(pid, force = false) {
105
+ if (!Number.isInteger(pid) || pid <= 0) {
106
+ debug("process", `Invalid PID: ${pid}`);
107
+ return false;
108
+ }
96
109
  try {
97
110
  if (this.platform === "win32") {
98
111
  const flag = force ? "/F" : "";
@@ -114,88 +127,28 @@ var ProcessManager = class {
114
127
  if (processes.length === 0) {
115
128
  return;
116
129
  }
130
+ debug("process", `Found ${processes.length} Claude process(es) to terminate`);
117
131
  console.log(`Found ${processes.length} Claude process(es) to terminate`);
118
132
  for (const proc of processes) {
133
+ debug("process", `Sending SIGTERM to PID ${proc.pid}`);
119
134
  this.killProcess(proc.pid, false);
120
135
  }
121
136
  await new Promise((resolve) => setTimeout(resolve, Math.min(timeout, 1e3)));
122
137
  const remaining = this.findClaudeProcesses();
123
138
  for (const proc of remaining) {
139
+ debug("process", `Force killing PID ${proc.pid} (still alive after grace period)`);
124
140
  console.log(`Force killing process ${proc.pid}`);
125
141
  this.killProcess(proc.pid, true);
126
142
  }
127
143
  }
128
- /**
129
- * Spawn Claude with specific arguments
130
- */
131
- spawnClaude(args, options) {
132
- const claudeCmd = this.findClaudeExecutable();
133
- if (!claudeCmd) {
134
- throw new Error("Claude CLI not found. Please ensure it is installed and in your PATH.");
135
- }
136
- if (this.platform === "darwin") {
137
- const cwd = options?.cwd || process.cwd();
138
- const claudeCommand = `cd "${cwd}" && ${claudeCmd} ${args.join(" ")}`;
139
- const script = `tell app "Terminal" to do script "${claudeCommand.replace(/"/g, '\\"')}"`;
140
- const terminalProcess = spawn("osascript", ["-e", script], {
141
- detached: true,
142
- stdio: "ignore"
143
- });
144
- terminalProcess.unref();
145
- return terminalProcess.pid || 0;
146
- }
147
- if (this.platform === "linux") {
148
- const cwd = options?.cwd || process.cwd();
149
- const claudeCommand = `${claudeCmd} ${args.join(" ")}`;
150
- const terminals = ["gnome-terminal", "xterm", "konsole", "xfce4-terminal"];
151
- for (const term of terminals) {
152
- try {
153
- const termProcess = spawn(term, ["--", "bash", "-c", `cd "${cwd}" && ${claudeCommand}`], {
154
- detached: true,
155
- stdio: "ignore"
156
- });
157
- termProcess.unref();
158
- return termProcess.pid || 0;
159
- } catch {
160
- }
161
- }
162
- }
163
- if (this.platform === "win32") {
164
- const cwd = options?.cwd || process.cwd();
165
- const gitBash = this.findGitBash();
166
- if (gitBash) {
167
- const claudeCommand = `cd "${String(cwd).replace(/\\/g, "/")}" && ${claudeCmd} ${args.join(" ")}`;
168
- const cmdProcess = spawn("cmd.exe", ["/c", "start", "", gitBash, "-c", claudeCommand], {
169
- detached: true,
170
- stdio: "ignore",
171
- windowsHide: false
172
- });
173
- cmdProcess.unref();
174
- return cmdProcess.pid || 0;
175
- } else {
176
- const claudeCommand = `cd /d "${cwd}" && ${claudeCmd} ${args.join(" ")}`;
177
- const cmdProcess = spawn("cmd.exe", ["/c", "start", "cmd.exe", "/k", claudeCommand], {
178
- detached: true,
179
- stdio: "ignore",
180
- windowsHide: false
181
- });
182
- cmdProcess.unref();
183
- return cmdProcess.pid || 0;
184
- }
185
- }
186
- const claudeProcess = spawn(claudeCmd, args, {
187
- detached: true,
188
- stdio: "ignore",
189
- ...options
190
- });
191
- claudeProcess.unref();
192
- return claudeProcess.pid || 0;
193
- }
194
144
  /**
195
145
  * Spawn a shell script in a new terminal window
196
146
  */
197
147
  spawnTerminalScript(script, _cwd) {
198
- const scriptFile = join(paths.baseDir, "launch.sh");
148
+ 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", `Script contents:
151
+ ${script}`);
199
152
  writeFileSync(scriptFile, script, { mode: 493 });
200
153
  if (this.platform === "darwin") {
201
154
  const escapedPath = scriptFile.replace(/"/g, '\\"');
@@ -280,12 +233,15 @@ var ProcessManager = class {
280
233
  if (this.platform === "win32") {
281
234
  const output = execSync("where claude", { encoding: "utf-8" }).trim();
282
235
  const firstLine = output.split("\n")[0];
236
+ debug("process", `Found Claude via 'where': ${firstLine}`);
283
237
  return firstLine || null;
284
238
  } else {
285
239
  const output = execSync("which claude", { encoding: "utf-8" }).trim();
240
+ debug("process", `Found Claude via 'which': ${output}`);
286
241
  return output || null;
287
242
  }
288
243
  } catch {
244
+ debug("process", "Claude not found via PATH, checking common install locations");
289
245
  const commonPaths = this.platform === "win32" ? [
290
246
  join(process.env.LOCALAPPDATA || "", "Programs", "Claude Code", "claude.exe"),
291
247
  join(process.env.PROGRAMFILES || "C:\\Program Files", "Claude Code", "claude.exe"),
@@ -299,9 +255,11 @@ var ProcessManager = class {
299
255
  ];
300
256
  for (const candidatePath of commonPaths) {
301
257
  if (candidatePath && existsSync(candidatePath)) {
258
+ debug("process", `Found Claude at fallback path: ${candidatePath}`);
302
259
  return candidatePath;
303
260
  }
304
261
  }
262
+ debug("process", "Claude executable not found anywhere");
305
263
  return null;
306
264
  }
307
265
  }
@@ -340,6 +298,9 @@ var ProcessManager = class {
340
298
  * Check if a process is still running
341
299
  */
342
300
  isProcessRunning(pid) {
301
+ if (!Number.isInteger(pid) || pid <= 0) {
302
+ return false;
303
+ }
343
304
  try {
344
305
  if (this.platform === "win32") {
345
306
  const output = execSync(`tasklist /FI "PID eq ${pid}"`, { encoding: "utf-8" });
@@ -59,6 +59,9 @@ var SessionManager = class {
59
59
  return `${hours} hour${hours === 1 ? "" : "s"}`;
60
60
  }
61
61
  const minutes = Math.floor(diffMs / (1e3 * 60));
62
+ if (minutes === 0) {
63
+ return "just now";
64
+ }
62
65
  return `${minutes} minute${minutes === 1 ? "" : "s"}`;
63
66
  }
64
67
  /**
package/dist/cli.js CHANGED
@@ -1,10 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- configManager
4
- } from "./chunk-YMFZWGZO.js";
3
+ SessionFallbackMarkerSchema,
4
+ configManager,
5
+ validateDomainName
6
+ } from "./chunk-D3DHIVER.js";
5
7
  import {
8
+ debug,
9
+ enableDebug,
6
10
  paths
7
- } from "./chunk-A7YXSI66.js";
11
+ } from "./chunk-F36TGFK2.js";
8
12
 
9
13
  // src/cli.ts
10
14
  import { Command } from "commander";
@@ -20,7 +24,7 @@ function getVersion() {
20
24
  const packageJson = JSON.parse(readFileSync(packagePath, "utf-8"));
21
25
  return packageJson.version;
22
26
  } catch {
23
- return "0.1.5";
27
+ return "0.1.6";
24
28
  }
25
29
  }
26
30
  function processSessionFallbackMarker() {
@@ -30,20 +34,19 @@ function processSessionFallbackMarker() {
30
34
  }
31
35
  try {
32
36
  const content = readFileSync(markerPath, "utf-8");
33
- const marker = JSON.parse(content);
34
- if (marker.domain && marker.sessionId) {
35
- const state = configManager.loadState();
36
- const session = state.sessions?.[marker.domain];
37
- if (session) {
38
- session.sessionId = marker.sessionId;
39
- session.started = (/* @__PURE__ */ new Date()).toISOString();
40
- session.lastActive = (/* @__PURE__ */ new Date()).toISOString();
41
- state.activeSession = marker.sessionId;
42
- configManager.saveState(state);
43
- console.log(pc.yellow(`Note: Previous session for '${marker.domain}' could not be resumed.`));
44
- console.log(pc.yellow(`A new session (${marker.sessionId.substring(0, 8)}...) was started automatically.
37
+ const raw = JSON.parse(content);
38
+ const marker = SessionFallbackMarkerSchema.parse(raw);
39
+ const state = configManager.loadState();
40
+ const session = state.sessions?.[marker.domain];
41
+ if (session) {
42
+ session.sessionId = marker.sessionId;
43
+ session.started = (/* @__PURE__ */ new Date()).toISOString();
44
+ session.lastActive = (/* @__PURE__ */ new Date()).toISOString();
45
+ state.activeSession = marker.sessionId;
46
+ configManager.saveState(state);
47
+ console.log(pc.yellow(`Note: Previous session for '${marker.domain}' could not be resumed.`));
48
+ console.log(pc.yellow(`A new session (${marker.sessionId.substring(0, 8)}...) was started automatically.
45
49
  `));
46
- }
47
50
  }
48
51
  unlinkSync(markerPath);
49
52
  } catch {
@@ -53,8 +56,15 @@ function processSessionFallbackMarker() {
53
56
  }
54
57
  }
55
58
  }
59
+ function requireValidDomainName(name) {
60
+ const err = validateDomainName(name);
61
+ if (err) {
62
+ console.error(pc.red(`\u274C Invalid domain name: ${err}`));
63
+ process.exit(1);
64
+ }
65
+ }
56
66
  var program = new Command();
57
- 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()).addHelpText("after", `
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", `
58
68
  Quick Start:
59
69
  cs init Initialize ContextSwitch
60
70
  cs domain add myproject -w /path/to/project Create a domain with a working directory
@@ -62,7 +72,18 @@ Quick Start:
62
72
  cs list See all your domains
63
73
 
64
74
  The --working-dir (-w) flag sets where Claude will run. If omitted, the current directory is used.
65
- Run "cs domain add --help" for more details.`).hook("preAction", () => {
75
+ Run "cs domain add --help" for more details.
76
+
77
+ Debug:
78
+ cs --verbose <command> Show detailed debug output
79
+ CS_DEBUG=1 cs <command> Same via environment variable`).hook("preAction", () => {
80
+ if (program.opts().verbose || process.env.CS_DEBUG === "1") {
81
+ enableDebug();
82
+ debug("cli", `ContextSwitch v${getVersion()}`);
83
+ debug("cli", `Platform: ${process.platform}`);
84
+ debug("cli", `Node: ${process.version}`);
85
+ debug("cli", `Config dir: ${paths.baseDir}`);
86
+ }
66
87
  paths.ensureDirectories();
67
88
  processSessionFallbackMarker();
68
89
  });
@@ -127,6 +148,7 @@ program.command("status").description("Show current domain and session status").
127
148
  console.log(pc.yellow('No active domain. Use "cs switch <domain>" to activate one.'));
128
149
  return;
129
150
  }
151
+ requireValidDomainName(domain);
130
152
  const domainConfig = configManager.loadDomain(domain);
131
153
  const session = state.sessions?.[domain];
132
154
  console.log(pc.cyan(`\u{1F4CA} Domain Status: ${domain}
@@ -138,7 +160,7 @@ program.command("status").description("Show current domain and session status").
138
160
  if (session) {
139
161
  console.log(`
140
162
  ${pc.cyan("Session Information:")}`);
141
- const { SessionManager } = await import("./session-IWXAKW6Z.js");
163
+ const { SessionManager } = await import("./session-H5HPE5OT.js");
142
164
  console.log(SessionManager.formatSessionInfo(session));
143
165
  } else {
144
166
  console.log(pc.gray("\nNo active session for this domain."));
@@ -149,20 +171,47 @@ ${pc.cyan("Session Information:")}`);
149
171
  }
150
172
  });
151
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) => {
152
- const { switchCommand } = await import("./switch-RZCXBTPC.js");
174
+ requireValidDomainName(domain);
175
+ const { switchCommand } = await import("./switch-CM6GRYEA.js");
153
176
  await switchCommand(domain, options);
154
177
  });
155
- program.command("reset <domain>").description("Reset a domain session (archives current session)").option("--skip-archive", "Skip archiving the current session").action(async (domain, options) => {
156
- const { resetCommand } = await import("./reset-GZKUYPZR.js");
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) => {
179
+ requireValidDomainName(domain);
180
+ if (!options.force) {
181
+ const enquirer = await import("enquirer");
182
+ const response = await enquirer.default.prompt({
183
+ type: "confirm",
184
+ name: "confirmed",
185
+ message: `Reset session for '${domain}'? This will clear the current session${options.skipArchive ? "" : " (session will be archived first)"}.`
186
+ });
187
+ const confirmed = response.confirmed;
188
+ if (!confirmed) {
189
+ console.log(pc.gray("Cancelled."));
190
+ return;
191
+ }
192
+ }
193
+ const { resetCommand } = await import("./reset-TSWWNV2Y.js");
157
194
  await resetCommand(domain, options);
158
195
  });
159
196
  program.command("archive <domain>").description("Archive the current session for a domain").action(async (domain) => {
160
- const { archiveCommand } = await import("./archive-64CFJ3P5.js");
161
- await archiveCommand(domain);
197
+ requireValidDomainName(domain);
198
+ try {
199
+ const { archiveCommand } = await import("./archive-MN425GUR.js");
200
+ await archiveCommand(domain);
201
+ } catch (error) {
202
+ console.error(pc.red(`\u274C ${error}`));
203
+ process.exit(1);
204
+ }
162
205
  });
163
206
  var domainCmd = program.command("domain").description("Manage domains");
164
207
  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) => {
165
208
  try {
209
+ const nameError = validateDomainName(name);
210
+ if (nameError) {
211
+ console.error(pc.red(`\u274C Invalid domain name: ${nameError}`));
212
+ console.log(pc.gray("Valid examples: myproject, backend-api, app_v2, my.project"));
213
+ process.exit(1);
214
+ }
166
215
  if (configManager.domainExists(name)) {
167
216
  console.error(pc.red(`\u274C Domain '${name}' already exists`));
168
217
  process.exit(1);
@@ -190,7 +239,7 @@ Next steps:`));
190
239
  process.exit(1);
191
240
  }
192
241
  });
193
- domainCmd.command("remove <name>").alias("rm").description("Remove a domain from ContextSwitch (does not affect working directory)").action(async (name) => {
242
+ domainCmd.command("remove <name>").alias("rm").description("Remove a domain from ContextSwitch (does not affect working directory)").option("-f, --force", "Skip confirmation prompt").action(async (name, options) => {
194
243
  try {
195
244
  if (!configManager.domainExists(name)) {
196
245
  console.error(pc.red(`\u274C Domain '${name}' not found`));
@@ -202,6 +251,19 @@ domainCmd.command("remove <name>").alias("rm").description("Remove a domain from
202
251
  console.log(pc.gray("Switch to a different domain first."));
203
252
  process.exit(1);
204
253
  }
254
+ if (!options.force) {
255
+ const enquirer = await import("enquirer");
256
+ const response = await enquirer.default.prompt({
257
+ type: "confirm",
258
+ name: "confirmed",
259
+ message: `Remove domain '${name}'? This will delete the domain configuration and session data.`
260
+ });
261
+ const confirmed = response.confirmed;
262
+ if (!confirmed) {
263
+ console.log(pc.gray("Cancelled."));
264
+ return;
265
+ }
266
+ }
205
267
  if (state.sessions?.[name]) {
206
268
  delete state.sessions[name];
207
269
  configManager.saveState(state);
@@ -216,7 +278,7 @@ domainCmd.command("remove <name>").alias("rm").description("Remove a domain from
216
278
  }
217
279
  });
218
280
  program.command("doctor").description("Check system configuration and diagnose issues").action(async () => {
219
- const { processManager } = await import("./process-E35QFSO6.js");
281
+ const { processManager } = await import("./process-X2SYCF5V.js");
220
282
  console.log(pc.cyan("\u{1FA7A} Running diagnostics...\n"));
221
283
  const failures = [];
222
284
  const claudeInstalled = processManager.verifyClaudeInstalled();
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  ProcessManager,
3
3
  processManager
4
- } from "./chunk-756VUR5T.js";
5
- import "./chunk-A7YXSI66.js";
4
+ } from "./chunk-K7ISIY3Q.js";
5
+ import "./chunk-F36TGFK2.js";
6
6
  export {
7
7
  ProcessManager,
8
8
  processManager
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  archiveCommand
3
- } from "./chunk-GHF4FLJV.js";
3
+ } from "./chunk-BGARUR7R.js";
4
4
  import {
5
5
  configManager
6
- } from "./chunk-YMFZWGZO.js";
7
- import "./chunk-A7YXSI66.js";
6
+ } from "./chunk-D3DHIVER.js";
7
+ import "./chunk-F36TGFK2.js";
8
8
 
9
9
  // src/commands/reset.ts
10
10
  import picocolors from "picocolors";
@@ -21,11 +21,15 @@ async function resetCommand(domainName, options = {}) {
21
21
  return;
22
22
  }
23
23
  console.log(pc.cyan(`\u{1F504} Resetting domain '${domainName}'...`));
24
- if (!options.skipArchive) {
24
+ if (options.skipArchive) {
25
+ console.log(pc.gray("Skipping archive (--skip-archive)."));
26
+ } else {
25
27
  const globalConfig = configManager.loadGlobalConfig();
26
28
  if (globalConfig.autoArchive) {
27
29
  console.log(pc.gray("Archiving current session..."));
28
30
  await archiveCommand(domainName);
31
+ } else {
32
+ console.log(pc.gray("Skipping archive (autoArchive is disabled in global config)."));
29
33
  }
30
34
  }
31
35
  const newState = {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  SessionManager
3
- } from "./chunk-KBKALWDX.js";
3
+ } from "./chunk-MGIXKKM6.js";
4
4
  export {
5
5
  SessionManager
6
6
  };
@@ -1,27 +1,36 @@
1
1
  import {
2
2
  SessionManager
3
- } from "./chunk-KBKALWDX.js";
3
+ } from "./chunk-MGIXKKM6.js";
4
4
  import {
5
5
  processManager
6
- } from "./chunk-756VUR5T.js";
6
+ } from "./chunk-K7ISIY3Q.js";
7
7
  import {
8
- configManager
9
- } from "./chunk-YMFZWGZO.js";
8
+ configManager,
9
+ validateEnvVarName
10
+ } from "./chunk-D3DHIVER.js";
10
11
  import {
12
+ debug,
11
13
  paths
12
- } from "./chunk-A7YXSI66.js";
14
+ } from "./chunk-F36TGFK2.js";
13
15
 
14
16
  // src/commands/switch.ts
15
17
  import picocolors from "picocolors";
16
- import { existsSync, writeFileSync, readFileSync, unlinkSync, copyFileSync, mkdirSync, readdirSync, statSync } from "fs";
18
+ import { existsSync, writeFileSync, readFileSync, unlinkSync, copyFileSync, mkdirSync, symlinkSync, lstatSync, readlinkSync } from "fs";
17
19
  import { join, dirname, basename } from "path";
18
20
  var pc = picocolors;
19
21
  function shellEscapeArg(arg) {
20
22
  return "'" + arg.replace(/'/g, "'\\''") + "'";
21
23
  }
24
+ function isSymlinkBroken(path) {
25
+ try {
26
+ const lst = lstatSync(path);
27
+ return lst.isSymbolicLink() && !existsSync(path);
28
+ } catch {
29
+ return false;
30
+ }
31
+ }
22
32
  async function switchCommand(domainName, options = {}) {
23
33
  try {
24
- processSessionFallback();
25
34
  console.log(pc.cyan(`\u{1F504} Switching to domain '${domainName}'...`));
26
35
  if (!processManager.verifyClaudeInstalled()) {
27
36
  printClaudeInstallHelp();
@@ -58,21 +67,18 @@ async function switchCommand(domainName, options = {}) {
58
67
  isNewSession = true;
59
68
  console.log(pc.gray(`Creating new session ${sessionId.substring(0, 8)}...`));
60
69
  }
70
+ debug("switch", `Session ID: ${sessionId} (${isNewSession ? "new" : "resuming"})`);
71
+ debug("switch", `Working directory: ${domain.workingDirectory}`);
61
72
  await updateMCPConfig(domain);
62
73
  await handleMemoryFiles(domain);
63
74
  console.log(pc.gray("Starting Claude with new configuration..."));
64
- let pid;
65
75
  if (!isNewSession) {
66
76
  const fallbackSessionId = SessionManager.generateSessionId(domainName, true);
67
- pid = spawnClaudeWithFallback(domain, sessionId, fallbackSessionId);
77
+ debug("switch", `Resume with fallback: resume=${sessionId.substring(0, 8)}, fallback=${fallbackSessionId.substring(0, 8)}`);
78
+ spawnClaudeWithFallback(domain, sessionId, fallbackSessionId);
68
79
  } else {
69
- pid = processManager.spawnClaude(["--session-id", sessionId], {
70
- cwd: domain.workingDirectory,
71
- env: {
72
- ...process.env,
73
- ...domain.env
74
- }
75
- });
80
+ debug("switch", `New session: ${sessionId.substring(0, 8)}`);
81
+ spawnClaudeNewSession(domain, sessionId);
76
82
  }
77
83
  const newState = {
78
84
  ...state,
@@ -81,13 +87,10 @@ async function switchCommand(domainName, options = {}) {
81
87
  lastSwitch: (/* @__PURE__ */ new Date()).toISOString(),
82
88
  sessions: {
83
89
  ...state.sessions,
84
- [domainName]: SessionManager.createSessionRecord(domainName, sessionId, pid)
90
+ [domainName]: SessionManager.createSessionRecord(domainName, sessionId)
85
91
  }
86
92
  };
87
93
  configManager.saveState(newState);
88
- if (pid) {
89
- processManager.writePidFile(domainName, pid);
90
- }
91
94
  console.log(pc.green(`\u2705 Switched to domain '${domainName}'`));
92
95
  console.log(pc.gray(`Working directory: ${domain.workingDirectory}`));
93
96
  if (domain.mcpServers && Object.keys(domain.mcpServers).length > 0) {
@@ -120,6 +123,7 @@ async function updateMCPConfig(domain) {
120
123
  }
121
124
  };
122
125
  writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2), "utf-8");
126
+ debug("switch", `MCP config written to ${mcpConfigPath} with servers: ${Object.keys(mcpConfig.servers || {}).join(", ") || "none"}`);
123
127
  console.log(pc.gray("Updated MCP configuration"));
124
128
  }
125
129
  async function handleMemoryFiles(domain) {
@@ -131,38 +135,79 @@ async function handleMemoryFiles(domain) {
131
135
  if (!existsSync(memoryDir)) {
132
136
  mkdirSync(memoryDir, { recursive: true });
133
137
  }
134
- const incomingFileNames = /* @__PURE__ */ new Set();
135
138
  for (const memoryPath of domain.claudeConfig.memory) {
136
139
  const expandedPath = paths.expandPath(memoryPath);
137
- incomingFileNames.add(basename(expandedPath));
138
- }
139
- if (existsSync(memoryDir)) {
140
- const entries = readdirSync(memoryDir);
141
- for (const entry of entries) {
142
- const entryPath = join(memoryDir, entry);
140
+ if (!existsSync(expandedPath)) {
141
+ console.log(pc.yellow(`Warning: Memory file not found: ${memoryPath}`));
142
+ continue;
143
+ }
144
+ const fileName = basename(expandedPath);
145
+ const destPath = join(memoryDir, fileName);
146
+ if (existsSync(destPath) || isSymlinkBroken(destPath)) {
143
147
  try {
144
- const stat = statSync(entryPath);
145
- if (stat.isDirectory()) {
146
- continue;
147
- }
148
- if (incomingFileNames.has(entry)) {
149
- unlinkSync(entryPath);
148
+ const lst = lstatSync(destPath);
149
+ if (lst.isSymbolicLink()) {
150
+ const target = readlinkSync(destPath);
151
+ if (target === expandedPath) {
152
+ console.log(pc.gray(`Memory file already linked: ${fileName}`));
153
+ continue;
154
+ }
150
155
  }
156
+ unlinkSync(destPath);
151
157
  } catch {
158
+ try {
159
+ unlinkSync(destPath);
160
+ } catch {
161
+ }
152
162
  }
153
163
  }
164
+ try {
165
+ symlinkSync(expandedPath, destPath);
166
+ debug("switch", `Symlinked memory file: ${expandedPath} -> ${destPath}`);
167
+ console.log(pc.gray(`Linked memory file: ${fileName}`));
168
+ } catch (symlinkErr) {
169
+ debug("switch", `Symlink failed (${symlinkErr}), falling back to copy`);
170
+ copyFileSync(expandedPath, destPath);
171
+ console.log(pc.gray(`Copied memory file: ${fileName} (symlink not supported)`));
172
+ }
154
173
  }
155
- for (const memoryPath of domain.claudeConfig.memory) {
156
- const expandedPath = paths.expandPath(memoryPath);
157
- if (!existsSync(expandedPath)) {
158
- console.log(pc.yellow(`Warning: Memory file not found: ${memoryPath}`));
159
- continue;
174
+ }
175
+ function escapeShellValue(val) {
176
+ return String(val).replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/`/g, "\\`").replace(/\$/g, "\\$");
177
+ }
178
+ function buildEnvLines(env) {
179
+ if (!env) return "";
180
+ const lines = [];
181
+ for (const [k, v] of Object.entries(env)) {
182
+ const err = validateEnvVarName(k);
183
+ if (err) {
184
+ throw new Error(err);
160
185
  }
161
- const fileName = basename(expandedPath);
162
- const destPath = join(memoryDir, fileName);
163
- copyFileSync(expandedPath, destPath);
164
- console.log(pc.gray(`Loaded memory file: ${fileName}`));
186
+ lines.push(`export ${k}="${escapeShellValue(v)}"`);
165
187
  }
188
+ return lines.join("\n");
189
+ }
190
+ function spawnClaudeNewSession(domain, sessionId) {
191
+ const claudeCmd = processManager.getClaudeExecutable();
192
+ if (!claudeCmd) {
193
+ throw new Error("Claude CLI not found");
194
+ }
195
+ const cwd = domain.workingDirectory;
196
+ const domainName = domain.name;
197
+ const pidFile = shellEscapeArg(join(paths.baseDir, `${domainName}.pid`));
198
+ const envLines = buildEnvLines(domain.env);
199
+ const script = [
200
+ "#!/bin/bash",
201
+ `cd ${shellEscapeArg(cwd)}`,
202
+ envLines,
203
+ "",
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"
209
+ ].join("\n");
210
+ return processManager.spawnTerminalScript(script, cwd);
166
211
  }
167
212
  function spawnClaudeWithFallback(domain, resumeSessionId, fallbackSessionId) {
168
213
  const claudeCmd = processManager.getClaudeExecutable();
@@ -172,10 +217,7 @@ function spawnClaudeWithFallback(domain, resumeSessionId, fallbackSessionId) {
172
217
  const cwd = domain.workingDirectory;
173
218
  const markerFile = getSessionFallbackPath();
174
219
  const domainName = domain.name;
175
- const escapeShellValue = (val) => {
176
- return String(val).replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/`/g, "\\`").replace(/\$/g, "\\$");
177
- };
178
- const envLines = domain.env ? Object.entries(domain.env).map(([k, v]) => `export ${k}="${escapeShellValue(v)}"`).join("\n") : "";
220
+ const envLines = buildEnvLines(domain.env);
179
221
  const markerJson = JSON.stringify({ domain: domainName, sessionId: fallbackSessionId });
180
222
  const markerJsonEscaped = markerJson.replace(/'/g, "'\\''");
181
223
  const escapedCwd = shellEscapeArg(cwd);
@@ -183,13 +225,17 @@ function spawnClaudeWithFallback(domain, resumeSessionId, fallbackSessionId) {
183
225
  const escapedResumeId = shellEscapeArg(resumeSessionId);
184
226
  const escapedFallbackId = shellEscapeArg(fallbackSessionId);
185
227
  const escapedMarkerFile = shellEscapeArg(markerFile);
228
+ const pidFile = shellEscapeArg(join(paths.baseDir, `${domainName}.pid`));
186
229
  const script = [
187
230
  "#!/bin/bash",
188
231
  `cd ${escapedCwd}`,
189
232
  envLines,
190
233
  "",
191
- "# Try to resume the existing session",
192
- `${escapedClaudeCmd} --resume ${escapedResumeId}`,
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",
193
239
  "RESUME_EXIT=$?",
194
240
  "",
195
241
  "# If resume failed (non-zero exit), fall back to a new session",
@@ -201,7 +247,10 @@ function spawnClaudeWithFallback(domain, resumeSessionId, fallbackSessionId) {
201
247
  " # Write marker so cs can update state with the new session ID",
202
248
  ` echo '${markerJsonEscaped}' > ${escapedMarkerFile}`,
203
249
  "",
204
- ` ${escapedClaudeCmd} --session-id ${escapedFallbackId}`,
250
+ ` ${escapedClaudeCmd} --session-id ${escapedFallbackId} &`,
251
+ " CLAUDE_PID=$!",
252
+ ` echo $CLAUDE_PID > ${pidFile}`,
253
+ " wait $CLAUDE_PID",
205
254
  "fi"
206
255
  ].join("\n");
207
256
  return processManager.spawnTerminalScript(script, cwd);
@@ -209,36 +258,6 @@ function spawnClaudeWithFallback(domain, resumeSessionId, fallbackSessionId) {
209
258
  function getSessionFallbackPath() {
210
259
  return join(paths.baseDir, "session-fallback.json");
211
260
  }
212
- function processSessionFallback() {
213
- const markerPath = getSessionFallbackPath();
214
- if (!existsSync(markerPath)) {
215
- return;
216
- }
217
- try {
218
- const content = readFileSync(markerPath, "utf-8");
219
- const marker = JSON.parse(content);
220
- if (marker.domain && marker.sessionId) {
221
- const state = configManager.loadState();
222
- const session = state.sessions?.[marker.domain];
223
- if (session) {
224
- session.sessionId = marker.sessionId;
225
- session.started = (/* @__PURE__ */ new Date()).toISOString();
226
- session.lastActive = (/* @__PURE__ */ new Date()).toISOString();
227
- state.activeSession = marker.sessionId;
228
- configManager.saveState(state);
229
- console.log(pc.yellow(`Note: Previous session for '${marker.domain}' could not be resumed.`));
230
- console.log(pc.yellow(`A new session (${marker.sessionId.substring(0, 8)}...) was started automatically.
231
- `));
232
- }
233
- }
234
- unlinkSync(markerPath);
235
- } catch {
236
- try {
237
- unlinkSync(markerPath);
238
- } catch {
239
- }
240
- }
241
- }
242
261
  function printClaudeInstallHelp() {
243
262
  console.error(pc.red("\u274C Claude Code CLI not found\n"));
244
263
  console.log(pc.white("ContextSwitch requires the Claude Code CLI to be installed.\n"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "contextswitch",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Domain-based context management for Claude Code CLI",
5
5
  "main": "dist/cli.js",
6
6
  "type": "module",
@@ -1,10 +0,0 @@
1
- import {
2
- archiveCommand,
3
- listArchives
4
- } from "./chunk-GHF4FLJV.js";
5
- import "./chunk-YMFZWGZO.js";
6
- import "./chunk-A7YXSI66.js";
7
- export {
8
- archiveCommand,
9
- listArchives
10
- };