wtt-connect 0.2.51 → 0.2.52

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wtt-connect",
3
- "version": "0.2.51",
3
+ "version": "0.2.52",
4
4
  "private": false,
5
5
  "description": "WTT-native connector daemon for Codex, Claude Code, Cursor, Gemini, ACP, and other coding agent surfaces.",
6
6
  "type": "module",
package/src/main.js CHANGED
@@ -20,6 +20,7 @@ export async function main(args) {
20
20
  const argv = parseArgs(args.slice(1));
21
21
  if (!argv.envFile && argv.profile) argv.envFile = resolveProfileEnvFile(argv.profile);
22
22
  loadDefaultEnvFiles({ envFile: argv.envFile });
23
+ if (cmd === 'version' || cmd === '--version' || cmd === '-v') return printVersion();
23
24
  if (cmd === 'help' || cmd === '--help' || cmd === '-h') return printHelp();
24
25
  if (cmd === 'up' || cmd === 'link') return up(argv);
25
26
  if (cmd === 'list') return listProfiles(argv);
@@ -109,6 +110,15 @@ function parseArgs(args) {
109
110
  return out;
110
111
  }
111
112
 
113
+ function printVersion() {
114
+ try {
115
+ const pkg = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
116
+ console.log(pkg.version || 'unknown');
117
+ } catch {
118
+ console.log('unknown');
119
+ }
120
+ }
121
+
112
122
  function printHelp() {
113
123
  console.log(`wtt-connect
114
124
 
@@ -87,12 +87,16 @@ export async function up(argv) {
87
87
 
88
88
  export function listProfiles() {
89
89
  const dir = profilesDir();
90
+ const sandboxEntries = discoverCloudSandboxEntries();
90
91
  if (!fs.existsSync(dir)) {
91
- console.log('no profiles');
92
+ if (!sandboxEntries.length) console.log('no profiles');
93
+ for (const entry of sandboxEntries) {
94
+ console.log(`${entry.profile}\t${entry.adapter || 'cloud-sandbox'}\t${entry.agentId}\tcloud-sandbox`);
95
+ }
92
96
  return;
93
97
  }
94
98
  const files = fs.readdirSync(dir).filter((f) => f.endsWith('.env')).sort();
95
- if (!files.length) {
99
+ if (!files.length && !sandboxEntries.length) {
96
100
  console.log('no profiles');
97
101
  return;
98
102
  }
@@ -101,23 +105,36 @@ export function listProfiles() {
101
105
  const env = readEnv(path.join(dir, file));
102
106
  console.log(`${profile}\t${env.WTT_CONNECT_ADAPTER || ''}\t${env.WTT_AGENT_ID || ''}`);
103
107
  }
108
+ for (const entry of sandboxEntries) {
109
+ console.log(`${entry.profile}\t${entry.adapter || 'cloud-sandbox'}\t${entry.agentId}\tcloud-sandbox`);
110
+ }
104
111
  }
105
112
 
106
113
  export function status(argv) {
107
- const profiles = targetProfiles(argv);
108
- if (!profiles.length) {
114
+ const profiles = targetProfiles(argv, { allowEmpty: true });
115
+ const sandboxEntries = targetCloudSandboxEntries(argv);
116
+ if (!profiles.length && !sandboxEntries.length) {
109
117
  console.log('no profiles');
110
118
  return;
111
119
  }
112
120
  for (const profile of profiles) {
113
121
  console.log(`${profile}: ${serviceStatus(profile)}`);
114
122
  }
123
+ for (const entry of sandboxEntries) {
124
+ console.log(`${entry.profile}: ${cloudSandboxStatus(entry)}`);
125
+ }
115
126
  }
116
127
 
117
128
  export function restart(argv) {
118
- const profiles = targetProfiles(argv);
129
+ const profiles = targetProfiles(argv, { allowEmpty: true });
130
+ const sandboxEntries = targetCloudSandboxEntries(argv);
131
+ if (!profiles.length && !sandboxEntries.length) {
132
+ console.log('no profiles');
133
+ return;
134
+ }
119
135
  for (const profile of profiles) restartService(profile);
120
- status({ _: profiles });
136
+ for (const entry of sandboxEntries) restartCloudSandboxEntry(entry);
137
+ status({ _: [...profiles, ...sandboxEntries.map((entry) => entry.profile)] });
121
138
  }
122
139
 
123
140
  export function logs(argv) {
@@ -182,11 +199,17 @@ export function down(argv) {
182
199
  console.log(`kept state dir: ${defaultStateDir(safe)}`);
183
200
  }
184
201
 
185
- function targetProfiles(argv) {
202
+ function targetProfiles(argv, options = {}) {
186
203
  const requested = argv._ || [];
187
- if (requested.length && requested[0] !== 'all') return requested.map(sanitizeProfile);
204
+ if (requested.length && requested[0] !== 'all') {
205
+ const available = new Set(discoverProfiles());
206
+ const profiles = requested.map(sanitizeProfile).filter((profile) => available.has(profile));
207
+ if (profiles.length || options.allowEmpty) return profiles;
208
+ return requested.map(sanitizeProfile);
209
+ }
188
210
  const profiles = discoverProfiles();
189
211
  if (!profiles.length && requested[0] === 'all') return [];
212
+ if (!profiles.length && options.allowEmpty) return [];
190
213
  if (!profiles.length) throw new Error('no wtt-connect profiles found');
191
214
  return profiles;
192
215
  }
@@ -197,6 +220,132 @@ function discoverProfiles() {
197
220
  return fs.readdirSync(dir).filter((f) => f.endsWith('.env')).map((f) => sanitizeProfile(f.slice(0, -4))).sort();
198
221
  }
199
222
 
223
+ function targetCloudSandboxEntries(argv) {
224
+ const requested = (argv._ || []).map((value) => String(value || '').trim()).filter(Boolean);
225
+ const entries = discoverCloudSandboxEntries();
226
+ if (!requested.length || requested[0] === 'all') return entries;
227
+ const wanted = new Set(requested.map(sanitizeProfile));
228
+ return entries.filter((entry) => wanted.has(entry.profile) || wanted.has(entry.agentId));
229
+ }
230
+
231
+ function discoverCloudSandboxEntries() {
232
+ const root = path.join(os.homedir(), '.wtt-connect', 'agent');
233
+ if (!fs.existsSync(root)) return [];
234
+ const entries = [];
235
+ for (const name of fs.readdirSync(root).sort()) {
236
+ const agentRoot = path.join(root, name);
237
+ const envFile = path.join(agentRoot, 'run', 'wtt-connect.env');
238
+ if (!fs.existsSync(envFile)) continue;
239
+ const env = readEnv(envFile);
240
+ const agentId = sanitizeProfile(env.WTT_AGENT_ID || name);
241
+ if (!agentId) continue;
242
+ entries.push({
243
+ profile: agentId,
244
+ agentId,
245
+ adapter: env.WTT_CONNECT_ADAPTER || env.WTT_CONNECT_ADAPTERS || '',
246
+ envFile,
247
+ agentRoot,
248
+ pidFile: path.join(agentRoot, 'run', 'wtt-connect.pid'),
249
+ logFile: path.join(agentRoot, 'logs', 'wtt-connect.log'),
250
+ workDir: env.WTT_CONNECT_WORKDIR || path.join(agentRoot, 'workspace'),
251
+ });
252
+ }
253
+ return entries;
254
+ }
255
+
256
+ function cloudSandboxStatus(entry) {
257
+ const pid = readPid(entry.pidFile);
258
+ if (pid && processRunningWithAgent(pid, entry.agentId)) return `active pid=${pid}`;
259
+ const found = findPidByAgentEnv(entry.agentId);
260
+ if (found) return `active pid=${found}`;
261
+ return 'inactive';
262
+ }
263
+
264
+ function restartCloudSandboxEntry(entry) {
265
+ stopCloudSandboxEntry(entry);
266
+ ensureDir(path.dirname(entry.pidFile));
267
+ ensureDir(path.dirname(entry.logFile));
268
+ ensureDir(entry.workDir);
269
+ const bin = path.join(packageRoot(), 'bin', 'wtt-connect.js');
270
+ const script = [
271
+ `set -a`,
272
+ `. ${shellQuote(entry.envFile)}`,
273
+ `set +a`,
274
+ `export PATH=${shellQuote(runtimePath())}`,
275
+ `mkdir -p ${shellQuote(entry.workDir)} ${shellQuote(path.dirname(entry.pidFile))} ${shellQuote(path.dirname(entry.logFile))}`,
276
+ `cd ${shellQuote(entry.workDir)}`,
277
+ `nohup ${shellQuote(process.execPath)} ${shellQuote(bin)} start --env-file ${shellQuote(entry.envFile)} >> ${shellQuote(entry.logFile)} 2>&1 < /dev/null &`,
278
+ `echo $! > ${shellQuote(entry.pidFile)}`,
279
+ ].join('; ');
280
+ const result = spawnSync('bash', ['-lc', script], { encoding: 'utf8' });
281
+ if (result.status !== 0) {
282
+ throw new Error(`cloud sandbox restart failed for ${entry.agentId}: ${result.stderr || result.stdout}`);
283
+ }
284
+ }
285
+
286
+ function stopCloudSandboxEntry(entry) {
287
+ const pids = new Set();
288
+ const pid = readPid(entry.pidFile);
289
+ if (pid) pids.add(pid);
290
+ const envPid = findPidByAgentEnv(entry.agentId);
291
+ if (envPid) pids.add(envPid);
292
+ for (const target of pids) {
293
+ try { process.kill(target, 'SIGTERM'); } catch {}
294
+ }
295
+ const deadline = Date.now() + 2000;
296
+ while (Date.now() < deadline && Array.from(pids).some((target) => processAlive(target))) {
297
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 100);
298
+ }
299
+ for (const target of pids) {
300
+ if (!processAlive(target)) continue;
301
+ try { process.kill(target, 'SIGKILL'); } catch {}
302
+ }
303
+ try { fs.rmSync(entry.pidFile); } catch {}
304
+ }
305
+
306
+ function readPid(file) {
307
+ try {
308
+ const pid = Number.parseInt(fs.readFileSync(file, 'utf8').trim(), 10);
309
+ return Number.isFinite(pid) && pid > 0 ? pid : 0;
310
+ } catch {
311
+ return 0;
312
+ }
313
+ }
314
+
315
+ function processAlive(pid) {
316
+ try {
317
+ process.kill(pid, 0);
318
+ return true;
319
+ } catch {
320
+ return false;
321
+ }
322
+ }
323
+
324
+ function processRunningWithAgent(pid, agentId) {
325
+ if (!processAlive(pid)) return false;
326
+ try {
327
+ const env = fs.readFileSync(`/proc/${pid}/environ`, 'utf8').split('\0');
328
+ return env.includes(`WTT_AGENT_ID=${agentId}`);
329
+ } catch {
330
+ return false;
331
+ }
332
+ }
333
+
334
+ function findPidByAgentEnv(agentId) {
335
+ if (process.platform !== 'linux') return 0;
336
+ for (const name of fs.readdirSync('/proc')) {
337
+ if (!/^\d+$/.test(name)) continue;
338
+ const pid = Number.parseInt(name, 10);
339
+ if (pid === process.pid) continue;
340
+ if (processRunningWithAgent(pid, agentId)) return pid;
341
+ }
342
+ return 0;
343
+ }
344
+
345
+ function shellQuote(value) {
346
+ return `'${String(value).replace(/'/g, `'\\''`)}'`;
347
+ }
348
+
200
349
  function writeProfileEnv(file, values) {
201
350
  const lines = [
202
351
  '# wtt-connect profile',