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.
- package/dist/archive-HW56NYMR.js +10 -0
- package/dist/{chunk-YMFZWGZO.js → chunk-72MK25T3.js} +71 -32
- package/dist/{chunk-A7YXSI66.js → chunk-F36TGFK2.js} +21 -2
- package/dist/{chunk-756VUR5T.js → chunk-GBKMEML7.js} +205 -81
- package/dist/{chunk-GHF4FLJV.js → chunk-GDWJEQJQ.js} +6 -7
- package/dist/{chunk-KBKALWDX.js → chunk-MGIXKKM6.js} +3 -0
- package/dist/cli.js +117 -34
- package/dist/{process-E35QFSO6.js → process-7O6IL3T4.js} +2 -2
- package/dist/{reset-GZKUYPZR.js → reset-O67AMVTV.js} +8 -4
- package/dist/{session-IWXAKW6Z.js → session-H5HPE5OT.js} +1 -1
- package/dist/{switch-RZCXBTPC.js → switch-RRK6CHOB.js} +91 -81
- package/package.json +3 -3
- package/dist/archive-64CFJ3P5.js +0 -10
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
+
debug,
|
|
2
3
|
paths
|
|
3
|
-
} from "./chunk-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
106
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
142
|
-
|
|
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
|
-
|
|
163
|
-
|
|
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
|
-
|
|
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
|
|
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
|
};
|