termify-agent 1.0.48 → 1.0.49

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.
@@ -21474,6 +21474,29 @@ function loadConfigFile() {
21474
21474
  }
21475
21475
  }
21476
21476
  var _fileConfig = loadConfigFile();
21477
+ function resolveConfig(config2) {
21478
+ if (config2.token) {
21479
+ return {
21480
+ token: config2.token,
21481
+ apiUrl: config2.apiUrl || "http://localhost:3001"
21482
+ };
21483
+ }
21484
+ if (!Array.isArray(config2.endpoints) || config2.endpoints.length === 0) {
21485
+ return null;
21486
+ }
21487
+ const validEndpoints = config2.endpoints.filter((endpoint2) => {
21488
+ return Boolean(endpoint2?.apiUrl && endpoint2?.token);
21489
+ });
21490
+ if (validEndpoints.length === 0) {
21491
+ return null;
21492
+ }
21493
+ const activeEndpoint = config2.syncEndpoint ? validEndpoints.find((endpoint2) => endpoint2.name === config2.syncEndpoint) : void 0;
21494
+ const endpoint = activeEndpoint || validEndpoints[0];
21495
+ return {
21496
+ token: endpoint.token,
21497
+ apiUrl: endpoint.apiUrl
21498
+ };
21499
+ }
21477
21500
  var TERMIFY_STATUS_URL = process.env.TERMIFY_STATUS_URL || "";
21478
21501
  var TERMINAL_ID = process.env.TERMIFY_TERMINAL_ID || "";
21479
21502
  var SESSION_ID = process.env.TERMIFY_SESSION_ID || "";
@@ -21481,10 +21504,11 @@ var _api = null;
21481
21504
  function getApi() {
21482
21505
  if (_api)
21483
21506
  return _api;
21484
- const token = process.env.TERMIFY_TOKEN || process.env.TERMIFY_API_KEY || _fileConfig.token || "";
21507
+ const resolvedConfig = resolveConfig(_fileConfig);
21508
+ const token = process.env.TERMIFY_TOKEN || process.env.TERMIFY_API_KEY || resolvedConfig?.token || "";
21485
21509
  if (!token)
21486
21510
  return null;
21487
- const url = process.env.TERMIFY_API_URL || _fileConfig.apiUrl || "http://localhost:3001";
21511
+ const url = process.env.TERMIFY_API_URL || resolvedConfig?.apiUrl || "http://localhost:3001";
21488
21512
  _api = new TermifyApiService(url, token);
21489
21513
  return _api;
21490
21514
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "termify-agent",
3
- "version": "1.0.48",
3
+ "version": "1.0.49",
4
4
  "description": "Termify Agent CLI - Connect your local terminal to Termify",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -14,9 +14,9 @@
14
14
  * `termify-agent start` for faster npm install times. See src/setup.ts.
15
15
  */
16
16
 
17
- import { execSync, execFileSync } from 'child_process';
18
- import { createWriteStream, mkdirSync, chmodSync, existsSync, unlinkSync, copyFileSync, rmSync, readFileSync, writeFileSync, symlinkSync, lstatSync } from 'fs';
19
- import { join, dirname } from 'path';
17
+ import { execSync } from 'child_process';
18
+ import { createWriteStream, mkdirSync, chmodSync, existsSync, unlinkSync, copyFileSync, rmSync, readFileSync, readlinkSync, writeFileSync, symlinkSync, lstatSync } from 'fs';
19
+ import { isAbsolute, join, dirname, resolve } from 'path';
20
20
  import { homedir, platform, arch } from 'os';
21
21
  import { get } from 'https';
22
22
  import { fileURLToPath } from 'url';
@@ -422,37 +422,100 @@ function downloadFile(url, dest, redirects = 0) {
422
422
  /**
423
423
  * Create /usr/local/bin/termify-agent so `sudo termify-agent` works.
424
424
  */
425
- function ensureGlobalSymlink() {
426
- if (IS_WIN) return;
425
+ function resolveCurrentPackageBinPath() {
426
+ const packagedBinPath = join(__dirname, '..', 'bin', 'termify-agent.js');
427
+ return existsSync(packagedBinPath) ? packagedBinPath : null;
428
+ }
427
429
 
428
- const target = '/usr/local/bin/termify-agent';
430
+ function normalizeLinkTarget(target, linkTarget) {
431
+ return isAbsolute(linkTarget) ? linkTarget : resolve(dirname(target), linkTarget);
432
+ }
433
+
434
+ function isLegacyWrapperLink(linkTarget) {
435
+ return linkTarget.includes('/.termify/agent/');
436
+ }
429
437
 
438
+ function isLegacyWrapperFile(target) {
430
439
  try {
431
- lstatSync(target);
432
- console.log('[termify-agent] /usr/local/bin/termify-agent already exists, skipping.');
433
- return;
440
+ const contents = readFileSync(target, 'utf8');
441
+ return contents.includes('TERMIFY_DIR=') && contents.includes('/agent/bin/termify-agent.js');
434
442
  } catch {
435
- // Doesn't exist — good, we'll create it
443
+ return false;
436
444
  }
445
+ }
437
446
 
438
- let npmBinPath = null;
447
+ export function detectGlobalWrapperState(target, desiredPath) {
439
448
  try {
440
- npmBinPath = execFileSync('which', ['termify-agent'], { stdio: 'pipe', encoding: 'utf8' }).trim();
441
- } catch {
442
- npmBinPath = join(__dirname, '..', 'bin', 'termify-agent.js');
443
- if (!existsSync(npmBinPath)) {
444
- console.log('[termify-agent] Could not locate termify-agent binary for symlink.');
445
- return;
449
+ const stat = lstatSync(target);
450
+
451
+ if (stat.isSymbolicLink()) {
452
+ const linkTarget = readlinkSync(target);
453
+ const normalizedTarget = normalizeLinkTarget(target, linkTarget);
454
+
455
+ if (desiredPath && normalizedTarget === desiredPath) {
456
+ return 'ready';
457
+ }
458
+
459
+ if (isLegacyWrapperLink(linkTarget) || isLegacyWrapperLink(normalizedTarget)) {
460
+ return 'legacy';
461
+ }
462
+
463
+ return 'conflict';
464
+ }
465
+
466
+ if (stat.isFile() && isLegacyWrapperFile(target)) {
467
+ return 'legacy';
468
+ }
469
+
470
+ return 'conflict';
471
+ } catch (error) {
472
+ if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
473
+ return 'missing';
446
474
  }
475
+
476
+ throw error;
447
477
  }
478
+ }
448
479
 
449
- if (!npmBinPath || npmBinPath === target) return;
480
+ export function ensureGlobalSymlink(options = {}) {
481
+ if (IS_WIN) {
482
+ return { status: 'skipped', action: 'windows' };
483
+ }
484
+
485
+ const target = options.target || '/usr/local/bin/termify-agent';
486
+ const log = options.log || console.log;
487
+ const desiredPath = options.desiredPath || resolveCurrentPackageBinPath();
488
+
489
+ if (!desiredPath || !existsSync(desiredPath)) {
490
+ log('[termify-agent] Could not locate termify-agent binary for symlink.');
491
+ return { status: 'skipped', action: 'missing_bin' };
492
+ }
493
+
494
+ const wrapperState = detectGlobalWrapperState(target, desiredPath);
495
+ if (wrapperState === 'ready') {
496
+ log('[termify-agent] /usr/local/bin/termify-agent already points to the current npm install.');
497
+ return { status: 'ready', action: 'already_ready' };
498
+ }
499
+
500
+ if (wrapperState === 'conflict') {
501
+ log('[termify-agent] /usr/local/bin/termify-agent already exists, skipping.');
502
+ return { status: 'skipped', action: 'conflict' };
503
+ }
450
504
 
451
505
  try {
452
- symlinkSync(npmBinPath, target);
453
- console.log(`[termify-agent] Symlink created: ${target} \u2192 ${npmBinPath}`);
506
+ if (wrapperState === 'legacy') {
507
+ unlinkSync(target);
508
+ }
509
+
510
+ symlinkSync(desiredPath, target);
511
+ log(`[termify-agent] Symlink ${wrapperState === 'legacy' ? 'updated' : 'created'}: ${target} \u2192 ${desiredPath}`);
512
+ return {
513
+ status: 'ready',
514
+ action: wrapperState === 'legacy' ? 'replaced_legacy' : 'created',
515
+ };
454
516
  } catch {
455
- console.log(`[termify-agent] Tip: run 'sudo ln -sf "${npmBinPath}" ${target}' to enable sudo access`);
517
+ log(`[termify-agent] Tip: run 'sudo ln -sf "${desiredPath}" ${target}' to enable sudo access`);
518
+ return { status: 'skipped', action: 'permission_denied' };
456
519
  }
457
520
  }
458
521
 
@@ -522,8 +585,8 @@ async function main() {
522
585
  // Step 5: Global symlink
523
586
  const s3 = createSpinner('Creating global symlink...');
524
587
  try {
525
- ensureGlobalSymlink();
526
- if (existsSync('/usr/local/bin/termify-agent')) {
588
+ const symlinkResult = ensureGlobalSymlink();
589
+ if (symlinkResult?.status === 'ready') {
527
590
  s3.succeed('Global symlink ready');
528
591
  } else {
529
592
  s3.warn(`Symlink skipped ${DIM}(run: sudo ln -sf $(which termify-agent) /usr/local/bin/)${RESET}`);
@@ -539,7 +602,9 @@ async function main() {
539
602
  console.log('');
540
603
  }
541
604
 
542
- main().catch((err) => {
543
- console.warn(`\n ${WARN} Postinstall warning: ${err.message}\n`);
544
- process.exit(0);
545
- });
605
+ if (process.argv[1] && resolve(process.argv[1]) === __filename) {
606
+ main().catch((err) => {
607
+ console.warn(`\n ${WARN} Postinstall warning: ${err.message}\n`);
608
+ process.exit(0);
609
+ });
610
+ }
@@ -0,0 +1,96 @@
1
+ import assert from 'node:assert/strict';
2
+ import { mkdirSync, lstatSync, mkdtempSync, readFileSync, readlinkSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { dirname, join, resolve } from 'node:path';
5
+ import test from 'node:test';
6
+
7
+ import { detectGlobalWrapperState, ensureGlobalSymlink } from './postinstall.js';
8
+
9
+ function makeTempDir() {
10
+ return mkdtempSync(join(tmpdir(), 'termify-agent-postinstall-'));
11
+ }
12
+
13
+ test('ensureGlobalSymlink replaces the legacy curl wrapper with the npm package bin', (t) => {
14
+ const tempDir = makeTempDir();
15
+ const target = join(tempDir, 'usr-local-bin-termify-agent');
16
+ const desiredDir = join(tempDir, 'package', 'bin');
17
+ const desiredPath = join(desiredDir, 'termify-agent.js');
18
+
19
+ t.after(() => {
20
+ rmSync(tempDir, { recursive: true, force: true });
21
+ });
22
+
23
+ mkdirSync(desiredDir, { recursive: true });
24
+ writeFileSync(desiredPath, '#!/usr/bin/env node\n');
25
+ writeFileSync(
26
+ target,
27
+ [
28
+ '#!/bin/sh',
29
+ 'TERMIFY_DIR="$HOME/.termify"',
30
+ 'NODE_BIN="$TERMIFY_DIR/node/bin/node"',
31
+ 'exec "$NODE_BIN" "$TERMIFY_DIR/agent/bin/termify-agent.js" "$@"',
32
+ '',
33
+ ].join('\n')
34
+ );
35
+
36
+ assert.equal(detectGlobalWrapperState(target, desiredPath), 'legacy');
37
+
38
+ const result = ensureGlobalSymlink({
39
+ target,
40
+ desiredPath,
41
+ log: () => {},
42
+ });
43
+
44
+ assert.equal(result.status, 'ready');
45
+ assert.equal(result.action, 'replaced_legacy');
46
+ assert.equal(lstatSync(target).isSymbolicLink(), true);
47
+ assert.equal(resolve(dirname(target), readlinkSync(target)), desiredPath);
48
+ });
49
+
50
+ test('ensureGlobalSymlink does not overwrite a non-legacy existing target', (t) => {
51
+ const tempDir = makeTempDir();
52
+ const target = join(tempDir, 'usr-local-bin-termify-agent');
53
+ const desiredDir = join(tempDir, 'package', 'bin');
54
+ const desiredPath = join(desiredDir, 'termify-agent.js');
55
+ const customPath = join(tempDir, 'custom', 'termify-agent.js');
56
+
57
+ t.after(() => {
58
+ rmSync(tempDir, { recursive: true, force: true });
59
+ });
60
+
61
+ mkdirSync(desiredDir, { recursive: true });
62
+ mkdirSync(dirname(customPath), { recursive: true });
63
+ writeFileSync(desiredPath, '#!/usr/bin/env node\n');
64
+ writeFileSync(customPath, '#!/usr/bin/env node\n');
65
+ symlinkSync(customPath, target);
66
+
67
+ assert.equal(detectGlobalWrapperState(target, desiredPath), 'conflict');
68
+
69
+ const result = ensureGlobalSymlink({
70
+ target,
71
+ desiredPath,
72
+ log: () => {},
73
+ });
74
+
75
+ assert.equal(result.status, 'skipped');
76
+ assert.equal(result.action, 'conflict');
77
+ assert.equal(resolve(dirname(target), readlinkSync(target)), customPath);
78
+ });
79
+
80
+ test('detectGlobalWrapperState treats an up-to-date symlink as ready', (t) => {
81
+ const tempDir = makeTempDir();
82
+ const target = join(tempDir, 'usr-local-bin-termify-agent');
83
+ const desiredDir = join(tempDir, 'package', 'bin');
84
+ const desiredPath = join(desiredDir, 'termify-agent.js');
85
+
86
+ t.after(() => {
87
+ rmSync(tempDir, { recursive: true, force: true });
88
+ });
89
+
90
+ mkdirSync(desiredDir, { recursive: true });
91
+ writeFileSync(desiredPath, '#!/usr/bin/env node\n');
92
+ symlinkSync(desiredPath, target);
93
+
94
+ assert.equal(detectGlobalWrapperState(target, desiredPath), 'ready');
95
+ assert.equal(readFileSync(desiredPath, 'utf8'), '#!/usr/bin/env node\n');
96
+ });