termify-agent 1.0.61 → 1.0.62

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": "termify-agent",
3
- "version": "1.0.61",
3
+ "version": "1.0.62",
4
4
  "description": "Termify Agent CLI - Connect your local terminal to Termify",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -14,6 +14,7 @@
14
14
  * `termify-agent start` for faster npm install times. See src/setup.ts.
15
15
  */
16
16
 
17
+ import Conf from 'conf';
17
18
  import { execSync } from 'child_process';
18
19
  import { createWriteStream, mkdirSync, chmodSync, existsSync, unlinkSync, copyFileSync, rmSync, readFileSync, readlinkSync, writeFileSync, symlinkSync, lstatSync, readdirSync } from 'fs';
19
20
  import { isAbsolute, join, dirname, resolve } from 'path';
@@ -33,6 +34,7 @@ const TERMIFY_DIR = join(homedir(), '.termify');
33
34
  const DAEMON_PATH = join(TERMIFY_DIR, `termify-daemon${EXE}`);
34
35
  const STATS_AGENT_PATH = join(TERMIFY_DIR, `stats-agent${EXE}`);
35
36
  const WG_GO_PATH = join(TERMIFY_DIR, 'wireguard-go');
37
+ const DISTILL_MARKER = '## Distill';
36
38
 
37
39
  // Marker: skip node-pty test on repeated installs if it already works for this ABI
38
40
  const NODE_PTY_OK_MARKER = join(TERMIFY_DIR, `.node-pty-ok-${process.versions.modules}`);
@@ -520,6 +522,101 @@ async function setupDistill() {
520
522
  return { installed: true, degraded: !reachable, shimPath: distillShimPath };
521
523
  }
522
524
 
525
+ function readConfiguredServerUrl() {
526
+ try {
527
+ const config = new Conf({
528
+ projectName: 'termify-agent',
529
+ defaults: {
530
+ serverUrl: 'https://termify.justdiego.com',
531
+ servers: [],
532
+ },
533
+ });
534
+
535
+ const servers = config.get('servers') || [];
536
+ if (Array.isArray(servers) && servers.length > 0) {
537
+ const primary = servers.find((server) => server && server.isPrimary) || servers[0];
538
+ if (primary?.serverUrl) return primary.serverUrl;
539
+ }
540
+
541
+ const serverUrl = config.get('serverUrl');
542
+ return typeof serverUrl === 'string' && serverUrl ? serverUrl : null;
543
+ } catch {
544
+ return null;
545
+ }
546
+ }
547
+
548
+ function escapeRegExp(value) {
549
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
550
+ }
551
+
552
+ function insertOrReplaceBlock(content, marker, newBlock) {
553
+ const markerEscaped = escapeRegExp(marker);
554
+ const regex = new RegExp(`${markerEscaped}[\\s\\S]*?(?=\\n## |\\n---\\n|$)`, '');
555
+ if (content.includes(marker)) {
556
+ return content.replace(regex, newBlock);
557
+ }
558
+ return content.trimEnd() + '\n\n' + newBlock + '\n';
559
+ }
560
+
561
+ function updateInstructionFile(filePath, block, { createIfMissing = true } = {}) {
562
+ if (!createIfMissing && !existsSync(filePath)) return false;
563
+ let content = '';
564
+ if (existsSync(filePath)) {
565
+ content = readFileSync(filePath, 'utf8');
566
+ }
567
+ const next = insertOrReplaceBlock(content, DISTILL_MARKER, block);
568
+ writeFileSync(filePath, next);
569
+ return true;
570
+ }
571
+
572
+ export async function syncBundleMetadata(options = {}) {
573
+ const homeDir = options.homeDir || homedir();
574
+ const serverUrl = options.serverUrl || readConfiguredServerUrl();
575
+ const fetchImpl = options.fetchImpl || fetch;
576
+ const log = options.log || (() => {});
577
+
578
+ if (!serverUrl) {
579
+ return { synced: false, reason: 'no-server-url' };
580
+ }
581
+
582
+ let manifest;
583
+ try {
584
+ const response = await fetchImpl(`${serverUrl}/api/downloads/bundles/manifest`);
585
+ if (!response?.ok) {
586
+ return { synced: false, reason: `http-${response?.status || 'unknown'}` };
587
+ }
588
+ manifest = await response.json();
589
+ } catch {
590
+ return { synced: false, reason: 'fetch-failed' };
591
+ }
592
+
593
+ if (!manifest || manifest.version !== 1) {
594
+ return { synced: false, reason: 'invalid-manifest' };
595
+ }
596
+
597
+ mkdirSync(join(homeDir, '.termify'), { recursive: true });
598
+ writeFileSync(join(homeDir, '.termify', 'bundles-manifest.json'), JSON.stringify(manifest, null, 2) + '\n');
599
+
600
+ if (manifest.distillDefaults) {
601
+ writeFileSync(join(homeDir, '.termify', 'distill.json'), JSON.stringify(manifest.distillDefaults, null, 2) + '\n');
602
+ }
603
+
604
+ const instructions = manifest.instructions?.distill;
605
+ if (instructions) {
606
+ updateInstructionFile(join(homeDir, '.codex', 'instructions.md'), instructions);
607
+
608
+ const geminiDir = join(homeDir, '.gemini');
609
+ if (existsSync(geminiDir)) {
610
+ updateInstructionFile(join(geminiDir, 'GEMINI.md'), instructions);
611
+ }
612
+
613
+ updateInstructionFile(join(homeDir, '.claude', 'CLAUDE.md'), instructions, { createIfMissing: false });
614
+ }
615
+
616
+ log('[termify-agent] Bundle metadata refreshed');
617
+ return { synced: true, manifest };
618
+ }
619
+
523
620
  // ---------------------------------------------------------------------------
524
621
  // Symlink in /usr/local/bin for sudo access
525
622
  // ---------------------------------------------------------------------------
@@ -717,6 +814,18 @@ async function main() {
717
814
  s4.warn(`distill skipped ${DIM}(${err.message || 'setup failed'})${RESET}`);
718
815
  }
719
816
 
817
+ const s5 = createSpinner('Refreshing bundle metadata...');
818
+ try {
819
+ const bundleSync = await syncBundleMetadata({ log: () => {} });
820
+ if (bundleSync.synced) {
821
+ s5.succeed('Bundle metadata refreshed');
822
+ } else {
823
+ s5.warn(`Bundle metadata skipped ${DIM}(${bundleSync.reason})${RESET}`);
824
+ }
825
+ } catch (err) {
826
+ s5.warn(`Bundle metadata skipped ${DIM}(${err.message || 'sync failed'})${RESET}`);
827
+ }
828
+
720
829
  unmuteConsole();
721
830
 
722
831
  console.log('');
@@ -1,10 +1,10 @@
1
1
  import assert from 'node:assert/strict';
2
- import { mkdirSync, lstatSync, mkdtempSync, readFileSync, readlinkSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
2
+ import { existsSync, mkdirSync, lstatSync, mkdtempSync, readFileSync, readlinkSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
3
3
  import { tmpdir } from 'node:os';
4
4
  import { dirname, join, resolve } from 'node:path';
5
5
  import test from 'node:test';
6
6
 
7
- import { detectGlobalWrapperState, ensureGlobalSymlink } from './postinstall.js';
7
+ import { detectGlobalWrapperState, ensureGlobalSymlink, syncBundleMetadata } from './postinstall.js';
8
8
 
9
9
  function makeTempDir() {
10
10
  return mkdtempSync(join(tmpdir(), 'termify-agent-postinstall-'));
@@ -94,3 +94,64 @@ test('detectGlobalWrapperState treats an up-to-date symlink as ready', (t) => {
94
94
  assert.equal(detectGlobalWrapperState(target, desiredPath), 'ready');
95
95
  assert.equal(readFileSync(desiredPath, 'utf8'), '#!/usr/bin/env node\n');
96
96
  });
97
+
98
+ test('syncBundleMetadata refreshes the local bundle manifest and distill instructions on reinstall', async (t) => {
99
+ const tempDir = makeTempDir();
100
+ const homeDir = join(tempDir, 'home');
101
+ const codexDir = join(homeDir, '.codex');
102
+ const geminiDir = join(homeDir, '.gemini');
103
+ const claudeDir = join(homeDir, '.claude');
104
+
105
+ t.after(() => {
106
+ rmSync(tempDir, { recursive: true, force: true });
107
+ });
108
+
109
+ mkdirSync(codexDir, { recursive: true });
110
+ mkdirSync(geminiDir, { recursive: true });
111
+ mkdirSync(claudeDir, { recursive: true });
112
+
113
+ writeFileSync(join(codexDir, 'instructions.md'), '# Codex Instructions\n');
114
+ writeFileSync(join(geminiDir, 'GEMINI.md'), '# Gemini Instructions\n');
115
+ writeFileSync(join(claudeDir, 'CLAUDE.md'), '# Claude Instructions\n');
116
+
117
+ const manifest = {
118
+ version: 1,
119
+ generatedAt: '2026-03-08T00:11:59Z',
120
+ mcps: [],
121
+ hooks: [],
122
+ hookDefinitions: {},
123
+ mcpRegistrations: {},
124
+ instructions: {
125
+ distill: '## Distill\n\nUse `distill -- <command>` for token reduction.\n',
126
+ },
127
+ distillDefaults: {
128
+ primaryHost: 'https://termify.justdiego.com/api/ollama',
129
+ primaryModel: 'huihui_ai/qwen3.5-abliterated:2b',
130
+ timeoutMs: 10000,
131
+ thinking: false,
132
+ },
133
+ };
134
+
135
+ const result = await syncBundleMetadata({
136
+ homeDir,
137
+ serverUrl: 'https://termify.justdiego.com',
138
+ fetchImpl: async () => ({
139
+ ok: true,
140
+ json: async () => manifest,
141
+ }),
142
+ log: () => {},
143
+ });
144
+
145
+ assert.equal(result.synced, true);
146
+
147
+ const localManifestPath = join(homeDir, '.termify', 'bundles-manifest.json');
148
+ const distillConfigPath = join(homeDir, '.termify', 'distill.json');
149
+
150
+ assert.equal(existsSync(localManifestPath), true);
151
+ assert.equal(existsSync(distillConfigPath), true);
152
+ assert.match(readFileSync(join(codexDir, 'instructions.md'), 'utf8'), /^## Distill/m);
153
+ assert.match(readFileSync(join(geminiDir, 'GEMINI.md'), 'utf8'), /^## Distill/m);
154
+ assert.match(readFileSync(join(claudeDir, 'CLAUDE.md'), 'utf8'), /^## Distill/m);
155
+ assert.equal(JSON.parse(readFileSync(localManifestPath, 'utf8')).generatedAt, '2026-03-08T00:11:59Z');
156
+ assert.equal(JSON.parse(readFileSync(distillConfigPath, 'utf8')).primaryModel, 'huihui_ai/qwen3.5-abliterated:2b');
157
+ });