contextswitch 0.1.5 → 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.
@@ -0,0 +1,10 @@
1
+ import {
2
+ archiveCommand,
3
+ listArchives
4
+ } from "./chunk-GDWJEQJQ.js";
5
+ import "./chunk-72MK25T3.js";
6
+ import "./chunk-F36TGFK2.js";
7
+ export {
8
+ archiveCommand,
9
+ listArchives
10
+ };
@@ -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,37 @@ 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
+ var LAUNCH_MODES = ["window", "tab", "inline"];
16
+ function validateDomainName(name) {
17
+ if (!name || name.length === 0) {
18
+ return "Domain name cannot be empty";
19
+ }
20
+ if (name.length > 64) {
21
+ return "Domain name must be 64 characters or fewer";
22
+ }
23
+ if (!DOMAIN_NAME_REGEX.test(name)) {
24
+ return "Domain name must start with a letter or number and contain only letters, numbers, hyphens, underscores, and dots";
25
+ }
26
+ if (name === "." || name === "..") {
27
+ return 'Domain name cannot be "." or ".."';
28
+ }
29
+ return null;
30
+ }
16
31
  var DomainConfigSchema = z.object({
17
- name: z.string().min(1).describe("Domain identifier"),
32
+ 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
33
  workingDirectory: z.string().describe("Working directory for this domain"),
19
34
  claudeConfig: z.object({
20
35
  instructions: z.string().optional().describe("Path to CLAUDE.md file"),
21
36
  memory: z.array(z.string()).optional().describe("Memory file paths")
22
37
  }).optional(),
23
38
  mcpServers: z.record(MCPServerSchema).optional().describe("MCP server configurations"),
24
- env: z.record(z.string()).optional().describe("Environment variables for this domain"),
39
+ env: z.record(
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"),
41
+ z.string()
42
+ ).optional().describe("Environment variables for this domain"),
43
+ launchMode: z.enum(LAUNCH_MODES).optional().describe("How to open Claude: window, tab, or inline"),
25
44
  extends: z.string().optional().describe("Parent domain to inherit from"),
26
45
  metadata: z.object({
27
46
  description: z.string().optional(),
@@ -49,6 +68,7 @@ var GlobalConfigSchema = z.object({
49
68
  autoArchive: z.boolean().default(true),
50
69
  archiveDirectory: z.string().optional(),
51
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"),
52
72
  logLevel: z.enum(["debug", "info", "warn", "error"]).default("info"),
53
73
  platform: z.object({
54
74
  forceType: z.enum(["darwin", "linux", "win32"]).optional(),
@@ -56,6 +76,10 @@ var GlobalConfigSchema = z.object({
56
76
  killTimeout: z.number().default(5e3).describe("Milliseconds to wait before force kill")
57
77
  }).optional()
58
78
  });
79
+ var SessionFallbackMarkerSchema = z.object({
80
+ domain: z.string().min(1).regex(DOMAIN_NAME_REGEX),
81
+ sessionId: z.string().min(1)
82
+ });
59
83
  var DEFAULT_DOMAIN_TEMPLATE = {
60
84
  name: "default",
61
85
  workingDirectory: process.cwd(),
@@ -70,6 +94,15 @@ var DEFAULT_DOMAIN_TEMPLATE = {
70
94
  tags: []
71
95
  }
72
96
  };
97
+ function validateEnvVarName(name) {
98
+ if (!name || name.length === 0) {
99
+ return "Environment variable name cannot be empty";
100
+ }
101
+ if (!ENV_VAR_NAME_REGEX.test(name)) {
102
+ return `Invalid environment variable name '${name}': must start with a letter or underscore and contain only letters, digits, and underscores`;
103
+ }
104
+ return null;
105
+ }
73
106
  function validateDomain(data) {
74
107
  return DomainConfigSchema.parse(data);
75
108
  }
@@ -86,30 +119,28 @@ function createInitialState() {
86
119
  }
87
120
 
88
121
  // src/core/config.ts
122
+ import { readFileSync, existsSync, writeFileSync, readdirSync, renameSync, unlinkSync } from "fs";
123
+ import { parse, stringify } from "yaml";
89
124
  var ConfigManager = class {
90
- globalConfig = null;
91
- state = null;
92
- domainCache = /* @__PURE__ */ new Map();
93
125
  /**
94
126
  * Load global configuration
95
127
  */
96
128
  loadGlobalConfig() {
97
129
  const configPath = paths.globalConfigFile;
98
130
  if (!existsSync(configPath)) {
99
- this.globalConfig = {
131
+ const defaultConfig = {
100
132
  version: "1.0.0",
101
133
  defaultDomain: "default",
102
134
  autoArchive: true,
103
135
  logLevel: "info"
104
136
  };
105
- this.saveGlobalConfig(this.globalConfig);
106
- return this.globalConfig;
137
+ this.saveGlobalConfig(defaultConfig);
138
+ return defaultConfig;
107
139
  }
108
140
  try {
109
141
  const content = readFileSync(configPath, "utf-8");
110
142
  const data = parse(content);
111
- this.globalConfig = GlobalConfigSchema.parse(data);
112
- return this.globalConfig;
143
+ return GlobalConfigSchema.parse(data);
113
144
  } catch (error) {
114
145
  throw new Error(`Failed to load global config: ${error}`);
115
146
  }
@@ -123,23 +154,25 @@ var ConfigManager = class {
123
154
  const content = stringify(config, { indent: 2 });
124
155
  writeFileSync(tempPath, content, "utf-8");
125
156
  renameSync(tempPath, configPath);
126
- this.globalConfig = config;
127
157
  }
128
158
  /**
129
159
  * Load state file
130
160
  */
131
161
  loadState() {
132
162
  const statePath = paths.stateFile;
163
+ debug("config", `Loading state from ${statePath}`);
133
164
  if (!existsSync(statePath)) {
134
- this.state = createInitialState();
135
- this.saveState(this.state);
136
- return this.state;
165
+ debug("config", "No state file found, creating initial state");
166
+ const initial = createInitialState();
167
+ this.saveState(initial);
168
+ return initial;
137
169
  }
138
170
  try {
139
171
  const content = readFileSync(statePath, "utf-8");
140
172
  const data = JSON.parse(content);
141
- this.state = validateState(data);
142
- return this.state;
173
+ const state = validateState(data);
174
+ debug("config", `State loaded: activeDomain=${state.activeDomain}, sessions=${Object.keys(state.sessions || {}).join(",") || "none"}`);
175
+ return state;
143
176
  } catch (error) {
144
177
  throw new Error(`Failed to load state: ${error}`);
145
178
  }
@@ -150,19 +183,23 @@ var ConfigManager = class {
150
183
  saveState(state) {
151
184
  const statePath = paths.stateFile;
152
185
  const tempPath = `${statePath}.tmp`;
186
+ debug("config", `Saving state to ${statePath} (activeDomain=${state.activeDomain})`);
153
187
  const content = JSON.stringify(state, null, 2);
154
188
  writeFileSync(tempPath, content, "utf-8");
155
189
  renameSync(tempPath, statePath);
156
- this.state = state;
157
190
  }
158
191
  /**
159
192
  * Load a domain configuration
160
193
  */
161
194
  loadDomain(domainName) {
162
- if (this.domainCache.has(domainName)) {
163
- return this.domainCache.get(domainName);
164
- }
195
+ return this.loadDomainInternal(domainName, []);
196
+ }
197
+ /**
198
+ * Internal domain loader with cycle detection for inheritance chains.
199
+ */
200
+ loadDomainInternal(domainName, ancestors) {
165
201
  const domainPath = paths.domainConfigFile(domainName);
202
+ debug("config", `Loading domain '${domainName}' from ${domainPath}`);
166
203
  if (!existsSync(domainPath)) {
167
204
  throw new Error(`Domain '${domainName}' not found`);
168
205
  }
@@ -171,7 +208,11 @@ var ConfigManager = class {
171
208
  const data = parse(content);
172
209
  let domain = validateDomain(data);
173
210
  if (domain.extends) {
174
- const parent = this.loadDomain(domain.extends);
211
+ if (ancestors.includes(domain.extends)) {
212
+ throw new Error(`Circular inheritance detected: ${[...ancestors, domainName, domain.extends].join(" -> ")}`);
213
+ }
214
+ debug("config", `Domain '${domainName}' extends '${domain.extends}', merging`);
215
+ const parent = this.loadDomainInternal(domain.extends, [...ancestors, domainName]);
175
216
  domain = this.mergeDomains(parent, domain);
176
217
  }
177
218
  domain.workingDirectory = paths.expandPath(domain.workingDirectory);
@@ -181,7 +222,6 @@ var ConfigManager = class {
181
222
  if (domain.claudeConfig?.memory) {
182
223
  domain.claudeConfig.memory = domain.claudeConfig.memory.map((p) => paths.expandPath(p));
183
224
  }
184
- this.domainCache.set(domainName, domain);
185
225
  return domain;
186
226
  } catch (error) {
187
227
  throw new Error(`Failed to load domain '${domainName}': ${error}`);
@@ -196,7 +236,6 @@ var ConfigManager = class {
196
236
  const content = stringify(domain, { indent: 2 });
197
237
  writeFileSync(tempPath, content, "utf-8");
198
238
  renameSync(tempPath, domainPath);
199
- this.domainCache.set(domain.name, domain);
200
239
  }
201
240
  /**
202
241
  * Create a new domain from template
@@ -241,7 +280,6 @@ var ConfigManager = class {
241
280
  throw new Error(`Domain '${domainName}' not found`);
242
281
  }
243
282
  unlinkSync(domainPath);
244
- this.domainCache.delete(domainName);
245
283
  }
246
284
  /**
247
285
  * Merge two domain configurations (child overrides parent)
@@ -289,13 +327,14 @@ var ConfigManager = class {
289
327
  * Clear cache
290
328
  */
291
329
  clearCache() {
292
- this.globalConfig = null;
293
- this.state = null;
294
- this.domainCache.clear();
295
330
  }
296
331
  };
297
332
  var configManager = new ConfigManager();
298
333
 
299
334
  export {
335
+ LAUNCH_MODES,
336
+ validateDomainName,
337
+ SessionFallbackMarkerSchema,
338
+ validateEnvVarName,
300
339
  configManager
301
340
  };
@@ -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
  };