myaiforone 1.1.21 → 1.1.22

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": "myaiforone",
3
- "version": "1.1.21",
3
+ "version": "1.1.22",
4
4
  "type": "module",
5
5
  "description": "Routes messages from phone channels to project-specific Claude Code agents",
6
6
  "bin": {
package/public/admin.html CHANGED
@@ -257,6 +257,7 @@ html,body{width:100%;height:100%;max-width:100vw;background:var(--bg-deep);font-
257
257
  <button class="type-tab" data-tab="channels" onclick="switchTab('channels')">Channels</button>
258
258
  <button class="type-tab" data-tab="activity" onclick="switchTab('activity')">Activity</button>
259
259
  <button class="type-tab active" data-tab="settings" onclick="switchTab('settings')">Settings</button>
260
+ <button class="type-tab" data-tab="updates" onclick="switchTab('updates')">Updates</button>
260
261
  <button class="type-tab" data-tab="docs" onclick="switchTab('docs')">Docs</button>
261
262
  </div>
262
263
 
@@ -693,6 +694,52 @@ html,body{width:100%;height:100%;max-width:100vw;background:var(--bg-deep);font-
693
694
  </div>
694
695
 
695
696
  <!-- ═══════════════════════════ DOCS TAB ═══════════════════════════ -->
697
+ <div class="tab-panel" id="panel-updates">
698
+ <div class="docs-page">
699
+ <div class="page-title">Platform Updates</div>
700
+ <div class="page-subtitle">Check for new versions and update MyAIforOne with one click.</div>
701
+ <div class="section" style="margin-top:24px">
702
+ <div class="section-header" style="padding:16px 20px;display:flex;align-items:center;gap:10px">
703
+ <span style="font-size:18px">&#x2B06;</span>
704
+ <span style="font-weight:700;font-size:14px">Version Status</span>
705
+ </div>
706
+ <div class="section-body" style="padding:20px" id="updatesPanel">
707
+ <div id="updateStatus" style="display:flex;flex-direction:column;gap:16px">
708
+ <div style="display:flex;align-items:center;gap:12px">
709
+ <span style="font-size:13px;color:var(--text-secondary)">Installed version:</span>
710
+ <span id="currentVersion" style="font-family:var(--font-mono);font-size:14px;font-weight:600;color:var(--accent)">--</span>
711
+ </div>
712
+ <div style="display:flex;align-items:center;gap:12px">
713
+ <span style="font-size:13px;color:var(--text-secondary)">Latest version:</span>
714
+ <span id="latestVersion" style="font-family:var(--font-mono);font-size:14px;font-weight:600;color:var(--text-primary)">--</span>
715
+ </div>
716
+ <div id="updateBadge" style="display:none;padding:10px 16px;border-radius:8px;font-size:13px;font-weight:600"></div>
717
+ <div style="display:flex;gap:10px;margin-top:8px">
718
+ <button onclick="checkForUpdates()" id="checkBtn" style="font-family:var(--font-sans);font-size:13px;font-weight:600;padding:8px 20px;border-radius:8px;border:1px solid var(--border-glow);background:var(--bg-input);color:var(--text-primary);cursor:pointer;transition:all .2s">Check for Updates</button>
719
+ <button onclick="applyUpdate()" id="updateBtn" style="display:none;font-family:var(--font-sans);font-size:13px;font-weight:700;padding:8px 20px;border-radius:8px;border:none;background:var(--accent);color:#000;cursor:pointer;transition:all .2s">Update Now</button>
720
+ </div>
721
+ <div id="updateLog" style="display:none;margin-top:12px;padding:12px 16px;border-radius:8px;background:var(--bg-input);border:1px solid var(--border-dim);font-family:var(--font-mono);font-size:12px;color:var(--text-secondary);white-space:pre-wrap"></div>
722
+ </div>
723
+ </div>
724
+ </div>
725
+ <div class="section" style="margin-top:16px">
726
+ <div class="section-header" style="padding:16px 20px;display:flex;align-items:center;gap:10px">
727
+ <span style="font-size:18px">&#x2139;</span>
728
+ <span style="font-weight:700;font-size:14px">How Updates Work</span>
729
+ </div>
730
+ <div class="section-body" style="padding:20px;font-size:13px;color:var(--text-secondary);line-height:1.7">
731
+ <p>Clicking <strong style="color:var(--text-primary)">Update Now</strong> will:</p>
732
+ <ol style="margin:10px 0 0 20px">
733
+ <li>Clear the local npm/npx cache for MyAIforOne</li>
734
+ <li>Restart the service, pulling the latest version from npm</li>
735
+ <li>The page will reconnect automatically once the new version is running</li>
736
+ </ol>
737
+ <p style="margin-top:12px;color:var(--text-muted)">You can also update manually from the command line: <code style="font-family:var(--font-mono);color:var(--accent)">npx myaiforone@latest</code></p>
738
+ </div>
739
+ </div>
740
+ </div>
741
+ </div>
742
+
696
743
  <div class="tab-panel" id="panel-docs">
697
744
  <div class="docs-page">
698
745
  <div class="page-title">Docs</div>
@@ -766,10 +813,11 @@ function showToast(msg, type='ok', html=false) {
766
813
  }
767
814
 
768
815
  // ── Tab switching ──────────────────────────────────────────────────────
769
- const TABS = ['channels','activity','settings','docs'];
816
+ const TABS = ['channels','activity','settings','updates','docs'];
770
817
  let channelsLoaded = false;
771
818
  let activityLoaded = false;
772
819
  let settingsLoaded = false;
820
+ let updatesLoaded = false;
773
821
 
774
822
  function switchTab(name) {
775
823
  if (!TABS.includes(name)) name = 'settings';
@@ -781,6 +829,7 @@ function switchTab(name) {
781
829
  if (name === 'channels' && !channelsLoaded) loadChannels();
782
830
  if (name === 'activity' && !activityLoaded) initActivity();
783
831
  if (name === 'settings' && !settingsLoaded) { loadProfile(); loadAccounts(); loadService(); loadSaas(); settingsLoaded = true; }
832
+ if (name === 'updates' && !updatesLoaded) { checkForUpdates(); updatesLoaded = true; }
784
833
  // Update URL param
785
834
  const url = new URL(location.href);
786
835
  url.searchParams.set('tab', name);
@@ -794,6 +843,98 @@ function switchTab(name) {
794
843
  switchTab(TABS.includes(tab) ? tab : 'settings');
795
844
  })();
796
845
 
846
+ // ──────────────────────────────────────────────────────────────────────
847
+ // ── PLATFORM UPDATES ─────────────────────────────────────────────────
848
+ // ──────────────────────────────────────────────────────────────────────
849
+
850
+ async function checkForUpdates() {
851
+ const checkBtn = document.getElementById('checkBtn');
852
+ const badge = document.getElementById('updateBadge');
853
+ const updateBtn = document.getElementById('updateBtn');
854
+ const logEl = document.getElementById('updateLog');
855
+ checkBtn.textContent = 'Checking...';
856
+ checkBtn.disabled = true;
857
+ try {
858
+ const r = await fetch('/api/version');
859
+ const d = await r.json();
860
+ document.getElementById('currentVersion').textContent = 'v' + d.current;
861
+ document.getElementById('latestVersion').textContent = 'v' + d.latest;
862
+ if (d.updateAvailable) {
863
+ badge.style.display = 'block';
864
+ badge.style.background = 'var(--accent-bg)';
865
+ badge.style.color = 'var(--accent)';
866
+ badge.style.border = '1px solid var(--border-active)';
867
+ badge.textContent = 'Update available: v' + d.current + ' \u2192 v' + d.latest;
868
+ updateBtn.style.display = 'inline-block';
869
+ } else {
870
+ badge.style.display = 'block';
871
+ badge.style.background = 'var(--green-bg)';
872
+ badge.style.color = 'var(--green)';
873
+ badge.style.border = '1px solid rgba(74,222,128,0.25)';
874
+ badge.textContent = 'You are running the latest version.';
875
+ updateBtn.style.display = 'none';
876
+ }
877
+ logEl.style.display = 'none';
878
+ } catch (e) {
879
+ badge.style.display = 'block';
880
+ badge.style.background = 'var(--red-bg)';
881
+ badge.style.color = 'var(--red)';
882
+ badge.style.border = '1px solid rgba(248,113,113,0.25)';
883
+ badge.textContent = 'Failed to check for updates: ' + e.message;
884
+ }
885
+ checkBtn.textContent = 'Check for Updates';
886
+ checkBtn.disabled = false;
887
+ }
888
+
889
+ async function applyUpdate() {
890
+ const updateBtn = document.getElementById('updateBtn');
891
+ const logEl = document.getElementById('updateLog');
892
+ const badge = document.getElementById('updateBadge');
893
+ updateBtn.disabled = true;
894
+ updateBtn.textContent = 'Updating...';
895
+ logEl.style.display = 'block';
896
+ logEl.textContent = 'Clearing cache and restarting with latest version...\n';
897
+ try {
898
+ const r = await fetch('/api/update', { method: 'POST' });
899
+ const d = await r.json();
900
+ logEl.textContent += d.message + '\n\nWaiting for service to come back online...';
901
+ badge.textContent = 'Updating...';
902
+ badge.style.background = 'var(--amber-bg)';
903
+ badge.style.color = 'var(--amber)';
904
+ badge.style.border = '1px solid rgba(251,191,36,0.25)';
905
+ // Poll health until service is back
906
+ let attempts = 0;
907
+ const poll = setInterval(async () => {
908
+ attempts++;
909
+ try {
910
+ const h = await fetch('/health', { signal: AbortSignal.timeout(2000) });
911
+ if (h.ok) {
912
+ clearInterval(poll);
913
+ logEl.textContent += '\nService is back online!';
914
+ badge.textContent = 'Update complete! Reloading...';
915
+ badge.style.background = 'var(--green-bg)';
916
+ badge.style.color = 'var(--green)';
917
+ badge.style.border = '1px solid rgba(74,222,128,0.25)';
918
+ setTimeout(() => location.reload(), 1500);
919
+ }
920
+ } catch {
921
+ if (attempts > 30) {
922
+ clearInterval(poll);
923
+ logEl.textContent += '\nService did not come back after 60s. Check manually.';
924
+ updateBtn.textContent = 'Update Now';
925
+ updateBtn.disabled = false;
926
+ } else {
927
+ logEl.textContent += '.';
928
+ }
929
+ }
930
+ }, 2000);
931
+ } catch (e) {
932
+ logEl.textContent += 'Error: ' + e.message;
933
+ updateBtn.textContent = 'Update Now';
934
+ updateBtn.disabled = false;
935
+ }
936
+ }
937
+
797
938
  // ──────────────────────────────────────────────────────────────────────
798
939
  // ── CHANNELS ──────────────────────────────────────────────────────────
799
940
  // ──────────────────────────────────────────────────────────────────────
package/src/web-ui.ts CHANGED
@@ -5409,6 +5409,84 @@ Project context and credentials are at: ${projectDir}/context.md and ${projectDi
5409
5409
  }, 500);
5410
5410
  });
5411
5411
 
5412
+ // ─── API: Platform Updates ──────────────────────────────────────────
5413
+
5414
+ // GET /api/version — current + latest version
5415
+ app.get("/api/version", async (_req, res) => {
5416
+ try {
5417
+ const pkgPath = join(opts.baseDir, "package.json");
5418
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
5419
+ const current = pkg.version || "unknown";
5420
+ // Fetch latest from npm registry
5421
+ let latest = current;
5422
+ let updateAvailable = false;
5423
+ try {
5424
+ const resp = await fetch("https://registry.npmjs.org/myaiforone/latest");
5425
+ if (resp.ok) {
5426
+ const data = await resp.json() as any;
5427
+ latest = data.version || current;
5428
+ updateAvailable = latest !== current;
5429
+ }
5430
+ } catch { /* offline — just report current */ }
5431
+ res.json({ ok: true, current, latest, updateAvailable });
5432
+ } catch (e: any) {
5433
+ res.status(500).json({ error: e.message });
5434
+ }
5435
+ });
5436
+
5437
+ // POST /api/update — clear npx cache and restart with latest version
5438
+ app.post("/api/update", async (_req, res) => {
5439
+ try {
5440
+ const platform = process.platform;
5441
+ log.info("[Update] Platform update triggered via API");
5442
+
5443
+ // Clear npx cache for myaiforone
5444
+ if (platform === "win32") {
5445
+ const npxDir = join(process.env.LOCALAPPDATA || "", "npm-cache", "_npx");
5446
+ if (existsSync(npxDir)) {
5447
+ for (const d of readdirSync(npxDir)) {
5448
+ const candidate = join(npxDir, d, "node_modules", "myaiforone");
5449
+ if (existsSync(candidate)) {
5450
+ rmSync(join(npxDir, d), { recursive: true, force: true });
5451
+ log.info(`[Update] Cleared npx cache: ${d}`);
5452
+ }
5453
+ }
5454
+ }
5455
+ } else {
5456
+ // macOS/Linux: ~/.npm/_npx
5457
+ const home = process.env.HOME || "";
5458
+ const npxDir = join(home, ".npm", "_npx");
5459
+ if (existsSync(npxDir)) {
5460
+ for (const d of readdirSync(npxDir)) {
5461
+ const candidate = join(npxDir, d, "node_modules", "myaiforone");
5462
+ if (existsSync(candidate)) {
5463
+ rmSync(join(npxDir, d), { recursive: true, force: true });
5464
+ log.info(`[Update] Cleared npx cache: ${d}`);
5465
+ }
5466
+ }
5467
+ }
5468
+ }
5469
+
5470
+ res.json({ ok: true, message: "Cache cleared. Restarting with latest version..." });
5471
+
5472
+ // Respawn via npx to pull latest, then exit current process
5473
+ setTimeout(() => {
5474
+ log.info("[Update] Spawning npx myaiforone to pull latest...");
5475
+ const child = cpSpawn("npx", ["myaiforone"], {
5476
+ cwd: process.cwd(),
5477
+ env: { ...process.env },
5478
+ stdio: "ignore",
5479
+ detached: true,
5480
+ shell: true,
5481
+ });
5482
+ child.unref();
5483
+ process.exit(0);
5484
+ }, 1000);
5485
+ } catch (e: any) {
5486
+ res.status(500).json({ error: e.message });
5487
+ }
5488
+ });
5489
+
5412
5490
  // ─── API: Skills ────────────────────────────────────────────────────
5413
5491
 
5414
5492
  // GET /api/agents/:agentId/skills — list all skills available to an agent