@exreve/exk 1.0.17 → 1.0.19

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.
@@ -4,7 +4,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from '
4
4
  import { symlink as fsSymlink } from 'fs';
5
5
  import { getSkillContent } from './skills/index.js';
6
6
  import { isLocalModel, unwrapModelName, startOpenAIAdapter, getAdapterConfig } from './openaiAdapter.js';
7
- import { createModuleMcpServer } from './moduleMcpServer.js';
7
+ import { createModuleMcpServer, getModuleToolHint } from './moduleMcpServer.js';
8
8
  import path from 'path';
9
9
  import os from 'os';
10
10
  import { createRequire } from 'module';
@@ -134,10 +134,11 @@ function loadAiConfig() {
134
134
  const apiKey = typeof config.authToken === 'string' ? config.authToken.trim() : '';
135
135
  const baseUrl = typeof config.baseUrl === 'string' ? config.baseUrl.trim() : '';
136
136
  const model = typeof config.model === 'string' && config.model.trim() ? config.model.trim() : DEFAULT_AI_MODEL;
137
- return { apiKey, baseUrl, model };
137
+ const proxy = typeof config.proxy === 'string' ? config.proxy.trim() : '';
138
+ return { apiKey, baseUrl, model, proxy };
138
139
  }
139
140
  catch {
140
- return { apiKey: '', baseUrl: '', model: DEFAULT_AI_MODEL };
141
+ return { apiKey: '', baseUrl: '', model: DEFAULT_AI_MODEL, proxy: '' };
141
142
  }
142
143
  }
143
144
  /** Create (or reuse) an empty directory to use as CLAUDE_CONFIG_DIR.
@@ -151,6 +152,17 @@ function getEmptyConfigDir() {
151
152
  }
152
153
  return configDir;
153
154
  }
155
+ const PROXY_CONFIG_PATH = path.join(os.homedir(), '.talk-to-code', 'proxy.json');
156
+ /** Read proxy toggle state from disk (synchronous) */
157
+ function readProxyToggle() {
158
+ try {
159
+ const data = readFileSync(PROXY_CONFIG_PATH, 'utf-8');
160
+ return JSON.parse(data);
161
+ }
162
+ catch {
163
+ return { enabled: false };
164
+ }
165
+ }
154
166
  /** Env for the Claude Code child: copy of host env with host ANTHROPIC_* stripped, then inject from ai-config only.
155
167
  * If a local model is provided, override baseUrl to point to the anthropic-proxy adapter. */
156
168
  function envForClaudeCodeChild(localModel) {
@@ -160,11 +172,24 @@ function envForClaudeCodeChild(localModel) {
160
172
  delete env.ANTHROPIC_BASE_URL;
161
173
  delete env.ANTHROPIC_AUTH_TOKEN;
162
174
  // Inject from our ai-config.json only
163
- const { apiKey, baseUrl } = loadAiConfig();
175
+ const { apiKey, baseUrl, proxy } = loadAiConfig();
164
176
  if (apiKey)
165
177
  env.ANTHROPIC_API_KEY = apiKey;
166
178
  if (baseUrl)
167
179
  env.ANTHROPIC_BASE_URL = baseUrl;
180
+ // Apply proxy if enabled
181
+ const proxyToggle = readProxyToggle();
182
+ if (proxyToggle.enabled && proxy) {
183
+ env.HTTPS_PROXY = proxy;
184
+ env.HTTP_PROXY = proxy;
185
+ }
186
+ else {
187
+ // Clear any inherited proxy env
188
+ delete env.HTTPS_PROXY;
189
+ delete env.HTTP_PROXY;
190
+ delete env.https_proxy;
191
+ delete env.http_proxy;
192
+ }
168
193
  // Prevent ~/.claude/settings.json env section from overriding our base URL.
169
194
  // This redirects the Claude config dir to an empty dir so that
170
195
  // ~/.claude/settings.json (which may have ANTHROPIC_BASE_URL set to z.ai)
@@ -418,7 +443,14 @@ export class AgentSessionManager {
418
443
  // Create a fresh MCP server for each query call (SDK connects transport internally, cannot reuse)
419
444
  ...(() => {
420
445
  const mcpServer = this.buildMcpServer(sessionId);
421
- return mcpServer ? { mcpServers: [mcpServer] } : {};
446
+ if (mcpServer) {
447
+ const toolHint = getModuleToolHint(session.enabledModules || []);
448
+ return {
449
+ mcpServers: [mcpServer],
450
+ ...(toolHint ? { systemPrompt: toolHint } : {})
451
+ };
452
+ }
453
+ return {};
422
454
  })(),
423
455
  ...(pathToClaudeCodeExecutable ? { pathToClaudeCodeExecutable } : {}),
424
456
  spawnClaudeCodeProcess: (spawnOptions) => {
package/dist/index.js CHANGED
@@ -66,6 +66,33 @@ async function fetchAiConfig(authToken) {
66
66
  }
67
67
  }
68
68
  const AI_CONFIG_FILE = path.join(CONFIG_DIR, 'ai-config.json');
69
+ const PROXY_CONFIG_FILE = path.join(CONFIG_DIR, 'proxy.json');
70
+ /** Read proxy toggle state from disk */
71
+ async function readProxyConfig() {
72
+ try {
73
+ const raw = await fs.readFile(PROXY_CONFIG_FILE, 'utf-8');
74
+ return JSON.parse(raw);
75
+ }
76
+ catch {
77
+ return { enabled: false };
78
+ }
79
+ }
80
+ /** Write proxy toggle state to disk */
81
+ async function writeProxyConfig(cfg) {
82
+ await fs.mkdir(CONFIG_DIR, { recursive: true });
83
+ await fs.writeFile(PROXY_CONFIG_FILE, JSON.stringify(cfg, null, 2));
84
+ }
85
+ /** Get the proxy URL from ai-config.json (saved from backend) */
86
+ function getProxyUrl() {
87
+ try {
88
+ const raw = fsSync.readFileSync(AI_CONFIG_FILE, 'utf-8');
89
+ const j = JSON.parse(raw);
90
+ return typeof j.proxy === 'string' ? j.proxy.trim() : '';
91
+ }
92
+ catch {
93
+ return '';
94
+ }
95
+ }
69
96
  /** True if ai-config.json has a model API key (not read from host ANTHROPIC_* env). */
70
97
  function hasAiCredentials() {
71
98
  try {
@@ -1796,6 +1823,39 @@ async function runDaemon(foreground = false, email) {
1796
1823
  });
1797
1824
  });
1798
1825
  // Cloudflared handlers
1826
+ // Proxy toggle handler
1827
+ socket.on('proxy:toggle:request', async (data, callback) => {
1828
+ try {
1829
+ const { enable } = data;
1830
+ const proxyUrl = getProxyUrl();
1831
+ if (enable && !proxyUrl) {
1832
+ callback?.({ success: false, enabled: false });
1833
+ return;
1834
+ }
1835
+ await writeProxyConfig({ enabled: enable });
1836
+ if (foreground) {
1837
+ console.log(`[CLI] Proxy ${enable ? 'enabled' : 'disabled'}${proxyUrl ? `: ${proxyUrl}` : ''}`);
1838
+ }
1839
+ else {
1840
+ console.log(`Proxy ${enable ? 'enabled' : 'disabled'}`);
1841
+ }
1842
+ callback?.({ success: true, enabled: enable, proxyUrl: enable ? proxyUrl : undefined });
1843
+ }
1844
+ catch (error) {
1845
+ callback?.({ success: false, enabled: false });
1846
+ }
1847
+ });
1848
+ // Proxy status handler
1849
+ socket.on('proxy:status:request', async (_data, callback) => {
1850
+ try {
1851
+ const proxyConfig = await readProxyConfig();
1852
+ const proxyUrl = getProxyUrl();
1853
+ callback?.({ enabled: proxyConfig.enabled, proxyUrl: proxyConfig.enabled ? proxyUrl : undefined });
1854
+ }
1855
+ catch {
1856
+ callback?.({ enabled: false });
1857
+ }
1858
+ });
1799
1859
  socket.on('cloudflared:check:request', async () => {
1800
1860
  try {
1801
1861
  let installed = false;
@@ -2779,4 +2839,37 @@ program
2779
2839
  process.exit(1);
2780
2840
  });
2781
2841
  });
2842
+ // Enable proxy command
2843
+ program
2844
+ .command('enable')
2845
+ .description('Enable a feature (e.g. proxy)')
2846
+ .argument('<feature>', 'Feature to enable (proxy)')
2847
+ .action(async (feature) => {
2848
+ if (feature !== 'proxy') {
2849
+ console.error(`Unknown feature: ${feature}. Available: proxy`);
2850
+ process.exit(1);
2851
+ }
2852
+ const proxyUrl = getProxyUrl();
2853
+ if (!proxyUrl) {
2854
+ console.log('No proxy URL configured. Run "exk daemon" first to sync config from server.');
2855
+ process.exit(1);
2856
+ }
2857
+ await writeProxyConfig({ enabled: true });
2858
+ console.log(`Proxy enabled: ${proxyUrl}`);
2859
+ process.exit(0);
2860
+ });
2861
+ // Disable proxy command
2862
+ program
2863
+ .command('disable')
2864
+ .description('Disable a feature (e.g. proxy)')
2865
+ .argument('<feature>', 'Feature to disable (proxy)')
2866
+ .action(async (feature) => {
2867
+ if (feature !== 'proxy') {
2868
+ console.error(`Unknown feature: ${feature}. Available: proxy`);
2869
+ process.exit(1);
2870
+ }
2871
+ await writeProxyConfig({ enabled: false });
2872
+ console.log('Proxy disabled');
2873
+ process.exit(0);
2874
+ });
2782
2875
  program.parse();
@@ -91,3 +91,20 @@ export function createModuleMcpServer(config) {
91
91
  });
92
92
  return server;
93
93
  }
94
+ /**
95
+ * Get a system prompt hint describing available module tools.
96
+ * This ensures the model knows about custom MCP tools upfront without
97
+ * having to discover them through the MCP tool-listing mechanism.
98
+ */
99
+ export function getModuleToolHint(enabledModules) {
100
+ if (enabledModules.length === 0)
101
+ return null;
102
+ const toolDescriptions = [];
103
+ if (enabledModules.includes('user-choice')) {
104
+ toolDescriptions.push('- user_choice_request: Request user input when making decisions. Presents a modal to the user with options and waits for their response. Use this when you need the user to choose between options or confirm an action.');
105
+ }
106
+ // Add more module tool descriptions here as they are implemented
107
+ if (toolDescriptions.length === 0)
108
+ return null;
109
+ return `You have access to the following custom MCP tools:\n${toolDescriptions.join('\n')}\n\nUse these tools proactively when they are relevant to the task.`;
110
+ }
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exreve/exk",
3
- "version": "1.0.17",
3
+ "version": "1.0.19",
4
4
  "description": "exk - Control Claude CLI with voice and programmable interfaces",
5
5
  "type": "module",
6
6
  "bin": {