meta-sovereign 0.49.18 → 0.51.0
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/README.hi.md +8 -2
- package/README.md +8 -2
- package/README.ru.md +8 -2
- package/README.zh.md +8 -2
- package/js/src/cli/automation-commands.js +2 -1
- package/js/src/cli/browser-open.js +25 -0
- package/js/src/cli/contact-commands.js +43 -0
- package/js/src/cli/cv-commands.js +5 -4
- package/js/src/cli/greetings-commands.js +2 -1
- package/js/src/cli/index.js +62 -28
- package/js/src/cli/migrate-commands.js +2 -1
- package/js/src/cli/trust-commands.js +78 -2
- package/js/src/cv/browser.js +42 -2
- package/js/src/index.d.ts +6 -0
- package/js/src/server/aggregate.js +12 -1
- package/js/src/server/confirmations.js +2 -1
- package/js/src/server/cors.js +44 -13
- package/js/src/server/hosted-ui.js +17 -10
- package/js/src/server/index.js +50 -14
- package/js/src/server/local-key-enclave.swift +98 -0
- package/js/src/server/local-key.js +85 -0
- package/js/src/server/local-tls.js +78 -16
- package/js/src/server/log.js +2 -1
- package/js/src/server/native-helper.js +100 -0
- package/js/src/server/routes-contacts-import.js +110 -0
- package/js/src/server/routes-derived.js +5 -2
- package/js/src/server/routes-passcode.js +5 -1
- package/js/src/server/routes-telegram.js +2 -13
- package/js/src/server/telegram-route-error.js +29 -0
- package/js/src/sources/contacts-import.js +173 -0
- package/js/src/sources/system-contacts.js +66 -0
- package/js/src/sources/system-contacts.plist +7 -0
- package/js/src/sources/system-contacts.swift +74 -0
- package/js/src/sources/telegram-account.js +10 -1
- package/js/src/sources/telegram-backfill.js +49 -25
- package/js/src/sources/telegram-catchup.js +24 -10
- package/js/src/sources/telegram-channels.js +13 -4
- package/js/src/sources/telegram-history.js +12 -0
- package/js/src/sources/telegram-pages.js +37 -10
- package/js/src/sources/vcard.js +203 -0
- package/js/src/storage/app-directory.js +78 -0
- package/js/src/web/app.css +112 -1
- package/js/src/web/app.min.js +89 -89
- package/js/src/web/chat-account.js +38 -0
- package/js/src/web/chat-calls.js +23 -9
- package/js/src/web/chat-format-bar.js +2 -1
- package/js/src/web/chat-history-load.js +80 -0
- package/js/src/web/chat-history-status.js +94 -0
- package/js/src/web/chat-profile.js +8 -3
- package/js/src/web/chat-sidebar.js +53 -8
- package/js/src/web/chat-state.js +44 -32
- package/js/src/web/chat-view.js +48 -17
- package/js/src/web/chat-window.js +130 -72
- package/js/src/web/client-log.js +8 -2
- package/js/src/web/connection-guides.js +11 -0
- package/js/src/web/contacts-import.js +279 -0
- package/js/src/web/contacts-live.js +63 -0
- package/js/src/web/discover.js +32 -8
- package/js/src/web/dom.js +15 -0
- package/js/src/web/download/copy.js +4 -4
- package/js/src/web/icons.js +8 -0
- package/js/src/web/locales/en-chat.js +15 -0
- package/js/src/web/locales/en.js +30 -2
- package/js/src/web/locales/hi-chat.js +15 -0
- package/js/src/web/locales/hi.js +33 -2
- package/js/src/web/locales/ru-chat.js +14 -0
- package/js/src/web/locales/ru.js +34 -2
- package/js/src/web/locales/zh-chat.js +11 -0
- package/js/src/web/locales/zh.js +30 -2
- package/js/src/web/nav-items.js +1 -0
- package/js/src/web/panel-resize.js +95 -0
- package/js/src/web/peer-avatar.js +9 -2
- package/js/src/web/person-panel.js +11 -0
- package/js/src/web/shell/app-shell.js +12 -1
- package/js/src/web/views.js +102 -21
- package/js/src/web/web-mode-banner.js +3 -1
- package/js/src/web/web-mode.js +5 -9
- package/package.json +6 -2
package/README.hi.md
CHANGED
|
@@ -55,9 +55,13 @@ node js/bin/meta-sovereign.js serve
|
|
|
55
55
|
|
|
56
56
|
Package npm पर publish होने के बाद `npx -y meta-sovereign serve` (या `npm install -g meta-sovereign && meta-sovereign serve`) काम करेगा। तब तक toolchain के बिना server चलाने का तरीका desktop app (ऊपर) है।
|
|
57
57
|
|
|
58
|
-
दोनों backend default रूप से <http://127.0.0.1:8787> पर listen करते हैं। JS server hosted app के origin (`https://link-foundation.github.io`) को CORS headers के साथ जवाब देता है; दूसरे origins `meta-sovereign serve --allow-origin=<origins>` या `MS_ALLOW_ORIGINS` से allow करें। Safari `https` pages को `http://127.0.0.1` तक नहीं पहुँचने देता, लेकिन `https://127.0.0.1` तक पहुँचने देता है; server अब उसी port पर HTTPS का भी जवाब देता है और इसके लिए localhost का अपना certificate बनाता है। एक बार `node js/bin/meta-sovereign.js trust` चलाएँ (macOS login keychain में जोड़ता है; दूसरे systems पर certificate का path print होता है) या Safari में `https://127.0.0.1:8787/trust` खोलें और prompts follow करें — उसके बाद hosted app Safari में सीधे server से connect हो जाता है। Trust से पहले notice server का नाम
|
|
58
|
+
दोनों backend default रूप से <http://127.0.0.1:8787> पर listen करते हैं। JS server hosted app के origin (`https://link-foundation.github.io`) को CORS headers के साथ जवाब देता है; दूसरे origins `meta-sovereign serve --allow-origin=<origins>` या `MS_ALLOW_ORIGINS` से allow करें। Safari `https` pages को `http://127.0.0.1` तक नहीं पहुँचने देता, लेकिन `https://127.0.0.1` तक पहुँचने देता है; server अब उसी port पर HTTPS का भी जवाब देता है और इसके लिए localhost का अपना certificate बनाता है। एक बार `node js/bin/meta-sovereign.js trust` चलाएँ (macOS login keychain में जोड़ता है; दूसरे systems पर certificate का path print होता है) या Safari में `https://127.0.0.1:8787/trust` खोलें और prompts follow करें — उसके बाद hosted app Safari में सीधे server से connect हो जाता है। Trust से पहले notice server का नाम और **सर्वर पर भरोसा करें** (<https://127.0.0.1:8787/trust>) दिखाता है; localhost UI का link तभी मिलता है जब वह स्पष्ट रूप से enable हो। `--tls=false` / `MS_TLS=off` HTTPS बंद करता है। Safari trusted local HTTPS API से hosted app उपयोग करता है; localhost UI वैकल्पिक है। Hosted SPA `discoverServer()` से local server खोजता है (saved override → `127.0.0.1` ports)। अगर browser auto-connect नहीं करता, app में **Settings → Server** खोलें और binary द्वारा print किया गया URL paste करें।
|
|
59
59
|
|
|
60
|
-
Default UI hosted app है: `serve` उसका पता छापता है, <https://link-foundation.github.io/meta-sovereign/>, और app हमारे port 8787 पर server को खुद ढूँढ लेता है (वह 8788, 7001, 7002 और 3000 भी देखता है; किसी दूसरे port पर `serve` बताता है कि उसे **Settings → Server** में set करें), इसलिए server को web site host नहीं करनी पड़ती।
|
|
60
|
+
Default UI hosted app है: `serve` उसका पता छापता है, <https://link-foundation.github.io/meta-sovereign/>, और app हमारे port 8787 पर server को खुद ढूँढ लेता है (वह 8788, 7001, 7002 और 3000 भी देखता है; किसी दूसरे port पर `serve` बताता है कि उसे **Settings → Server** में set करें), इसलिए server को web site host नहीं करनी पड़ती। Default रूप से `/` GitHub Pages पर redirect करता है और server केवल API, sync transports और connection helpers देता है। बिना internet के लिए localhost web app वैकल्पिक है: `serve --local-ui=true` (या `MS_LOCAL_UI=on`) से enable करें; फिर वह <http://127.0.0.1:8787> पर मिलता है। Desktop shell अपना bundled UI स्पष्ट रूप से enable करता है। `--ui-url=<url>` (या `MS_UI_URL`) कोई दूसरी hosted copy बताता है, जैसे किसी fork का Pages, और उसके origin को अनुमति देता है।
|
|
61
|
+
|
|
62
|
+
macOS पर interactive `serve` पूरा setup करता है: इस installation की TLS key को Secure Enclave से protect करता है, certificate trust जाँचता है, जरूरत पर ही trust माँगता है और GitHub Pages app खोलता है। Headless launch: `node js/bin/meta-sovereign.js serve --trust=true --open=true`। `--trust=false` और `--open=false` से opt out करें। Swift helper को macOS Command Line Tools चाहिए; unsupported Mac पर `--hardware-key=false` स्पष्ट रूप से file key चुनता है। Hardware protection disk की key पर लागू है; Node key को memory में उपयोग करता है। नियम और सीमाएँ: [local server setup और security](docs/LOCAL-SERVER-SECURITY.md)।
|
|
63
|
+
|
|
64
|
+
खुली चैट पहले लोड होती है और लोडिंग या पुनःप्रयास की स्थिति दिखाती है। सभी / निजी / समूह / चैनल फ़िल्टर हर Telegram फ़ोल्डर में उपलब्ध हैं। कॉलम की चौड़ाई ऊर्ध्वाधर डिवाइडर खींचकर बदलें; आकार याद रखे जाते हैं। कॉल मुख्य मेनू में चैट के बाद है और Telegram खाता संपादन प्रोफ़ाइल में है। डार्क थीम में नियंत्रण सफ़ेद रेखा आइकन इस्तेमाल करते हैं। JS सर्वर OS उपयोगकर्ता की ऐप फ़ोल्डर में डेटा रखता है: macOS में `~/Library/Application Support/Meta Sovereign/store`, Windows में `%APPDATA%/Meta Sovereign/store` और Linux में `${XDG_CONFIG_HOME:-~/.config}/Meta Sovereign/store`। पुराने checkout से `serve` चलाने पर `.meta-sovereign` एक बार स्थानांतरित होता है; संदेश, संपर्क, सत्र, मीडिया और प्रमाणपत्र सुरक्षित रहते हैं तथा लिंक पुराने पथ बनाए रखता है। रीस्टार्ट और अलग कार्य निर्देशिका में वही स्टोर इस्तेमाल होता है। `--store` या `META_SOVEREIGN_STORE_DIR` से बदलें। [चैट और डेटा संग्रहण](docs/CHAT-READINESS.md) देखें।
|
|
61
65
|
|
|
62
66
|
## SPA को server से जोड़ें
|
|
63
67
|
|
|
@@ -78,6 +82,8 @@ SPA server को इस क्रम में चुनता है ([`js/src
|
|
|
78
82
|
- **एकीकृत इनबॉक्स**: VK, Telegram, X, WhatsApp, Facebook, Instagram, LinkedIn, ईमेल, GitHub, Upwork, PeoplePerHour और जॉब बोर्ड; जहाँ नेटवर्क अनुमति देता है वहाँ लाइव अपडेट (Telegram, VK Long Poll, incremental IMAP, GitHub), बाकी के लिए किसी भी आकार के आर्काइव का आयात।
|
|
79
83
|
- **Telegram के अपने ऐप जैसी चैट**: हर संदेश एक बार भेजा जाता है, डिलीवरी टिक, लाइव अपडेट और अंतराल के बाद catch-up, पढ़ा-चिह्न, cloud ड्राफ़्ट, reload के बाद भी बचे रहने वाले retry ([`docs/CHAT-REFERENCES.hi.md`](docs/CHAT-REFERENCES.hi.md))।
|
|
80
84
|
- **CRM**: किसी संपर्क के बारे में सब कुछ एक दृश्य में, अलग-अलग नेटवर्क पर वही व्यक्ति, communities और सदस्यताएँ, खोज, नियम से ऑडियंस (`friends:vk & chat:vk:… - list:blocked`)।
|
|
85
|
+
- **संपर्क आयात**: macOS Contacts (सिंक किए गए iCloud खातों सहित) या किसी भी प्लेटफ़ॉर्म के vCard निर्यात का पूर्वावलोकन करके आयात करें। दोबारा आयात करने पर रिकॉर्ड की पहचान बनी रहती है। निर्देशिका सिंक के दौरान अपडेट होती है और बिना संदेश वाले लोग भी दिखाती है। [संपर्क आयात](docs/CONTACTS-IMPORT.md) देखें।
|
|
86
|
+
|
|
81
87
|
- **Outreach, broadcast और ऑटोमेशन**: किसे, कब, किन शर्तों पर और किस पाठ से लिखना है; कौन क्या पाएगा इसका preview; डिफ़ॉल्ट रूप से सुरक्षित ड्राफ़्ट, या quiet hours, हर चैट का cooldown, दैनिक सीमा और opt-out सूची के साथ धीमी गति से स्वचालित भेजना। हर स्वचालित भेजना एक ही outbox से जाता है: अधिक से अधिक एक बार, और अस्पष्ट परिणाम अपने आप दोहराया नहीं जाता ([`docs/USER-GUIDE.hi.md`](docs/USER-GUIDE.hi.md), §11)।
|
|
82
88
|
- **Operator कतार और उत्तर**: अपठित संदेश चैट-दर-चैट, आपके पिछले उत्तरों से सुझाव (Tab एक चुनता है), पैटर्न और उत्तर के रूप, मिलते संदेश पर auto-reply।
|
|
83
89
|
- **व्यवसाय और community खाते**: VK community token Bots Long Poll चलाता है; Telegram Business के quick replies, स्वागत और अनुपस्थिति संदेश, कार्य-समय और chatbots।
|
package/README.md
CHANGED
|
@@ -55,9 +55,13 @@ node js/bin/meta-sovereign.js serve
|
|
|
55
55
|
|
|
56
56
|
Once the package is published to npm, `npx -y meta-sovereign serve` (or `npm install -g meta-sovereign && meta-sovereign serve`) will work. Until then, the desktop app (above) is the way to run the server without a toolchain.
|
|
57
57
|
|
|
58
|
-
Either backend listens on <http://127.0.0.1:8787> by default. The JS server answers the hosted app's origin (`https://link-foundation.github.io`) with CORS headers; allow other origins with `meta-sovereign serve --allow-origin=<origins>` or `MS_ALLOW_ORIGINS`. Safari does not let `https` pages reach `http://127.0.0.1`, but it allows `https://127.0.0.1`; the server also answers HTTPS on the same port with its own certificate for localhost — run `node js/bin/meta-sovereign.js trust` once (macOS adds it to the login keychain; other systems print the certificate path), or open `https://127.0.0.1:8787/trust` in Safari and follow the prompts, and the hosted app connects directly. Before trusting, the notice names the server
|
|
58
|
+
Either backend listens on <http://127.0.0.1:8787> by default. The JS server answers the hosted app's origin (`https://link-foundation.github.io`) with CORS headers; allow other origins with `meta-sovereign serve --allow-origin=<origins>` or `MS_ALLOW_ORIGINS`. Safari does not let `https` pages reach `http://127.0.0.1`, but it allows `https://127.0.0.1`; the server also answers HTTPS on the same port with its own certificate for localhost — run `node js/bin/meta-sovereign.js trust` once (macOS adds it to the login keychain; other systems print the certificate path), or open `https://127.0.0.1:8787/trust` in Safari and follow the prompts, and the hosted app connects directly. Before trusting, the notice names the server and offers **Trust the server** (<https://127.0.0.1:8787/trust>); it links a localhost UI only if that UI was explicitly enabled. `--tls=false` / `MS_TLS=off` turns HTTPS off. Safari uses the hosted app through the trusted local HTTPS API; the localhost UI is optional. The hosted SPA discovers the local server automatically through `discoverServer()` (saved override → `127.0.0.1` ports). If your browser does not auto-connect, open the in-app **Settings → Server** prompt and paste the URL the binary printed.
|
|
59
59
|
|
|
60
|
-
The hosted app is the default UI: `serve` prints its address, <https://link-foundation.github.io/meta-sovereign/>, and the app finds the server on our port 8787 by itself (it also looks at 8788, 7001, 7002 and 3000; on another port `serve` says to set it in **Settings → Server**), so the server does not have to host a web site.
|
|
60
|
+
The hosted app is the default UI: `serve` prints its address, <https://link-foundation.github.io/meta-sovereign/>, and the app finds the server on our port 8787 by itself (it also looks at 8788, 7001, 7002 and 3000; on another port `serve` says to set it in **Settings → Server**), so the server does not have to host a web site. By default `/` redirects to GitHub Pages and the server serves only its API, sync transports and connection helpers. To opt into a localhost web app for offline use, run `serve --local-ui=true` (or set `MS_LOCAL_UI=on`); that copy is then available at <http://127.0.0.1:8787>. Desktop shells explicitly enable their bundled UI. `--ui-url=<url>` (or `MS_UI_URL`) names another hosted copy, such as a fork's Pages, and allows its origin.
|
|
61
|
+
|
|
62
|
+
On macOS, interactive `serve` now handles the full setup: it protects this installation’s TLS key with Secure Enclave, detects existing certificate trust, requests trust only when needed, and opens the GitHub Pages app. For a headless launch use `node js/bin/meta-sovereign.js serve --trust=true --open=true`. `--trust=false` and `--open=false` opt out. The Swift helper needs the macOS Command Line Tools; `--hardware-key=false` explicitly opts into a file key on unsupported Macs. Hardware protection applies to the key on disk; Node uses it in memory. See [local server setup and security](docs/LOCAL-SERVER-SECURITY.md) for the exact access rules and limits.
|
|
63
|
+
|
|
64
|
+
Open conversations load first and show loading or retry status until their history is ready. All / Private / Groups / Channels filters remain available inside every Telegram folder. Drag the vertical dividers to resize columns; widths are remembered. Calls is in the main menu after Chat, and Telegram account editing is under Profile. The default dark controls use white line icons. The JS server keeps its store under your OS user: `~/Library/Application Support/Meta Sovereign/store` on macOS, `%APPDATA%/Meta Sovereign/store` on Windows, and `${XDG_CONFIG_HOME:-~/.config}/Meta Sovereign/store` on Linux. Starting `serve` from an older checkout migrates its `.meta-sovereign` store once, preserving messages, contacts, sessions, media and certificate; a compatibility link preserves old paths. Restarts and a different working directory reuse the same store. `--store` or `META_SOVEREIGN_STORE_DIR` override it. See [chat readiness and data storage](docs/CHAT-READINESS.md).
|
|
61
65
|
|
|
62
66
|
## Connect the SPA to your server
|
|
63
67
|
|
|
@@ -78,6 +82,8 @@ The full user-facing flow — install nothing, install Rust server, install JS s
|
|
|
78
82
|
- **Unified inbox** across VK, Telegram, X, WhatsApp, Facebook, Instagram, LinkedIn, email, GitHub, Upwork, PeoplePerHour and job boards, with live updates where the network allows (Telegram, the VK Long Poll, incremental IMAP, GitHub) and full-size archive imports for the rest.
|
|
79
83
|
- **Chat that behaves like Telegram's own apps**: each message sent once, delivery ticks, live updates and catch-up after a gap, read marking, cloud drafts, retries that survive a reload. How the open-source Telegram, Signal and Element clients do it, pinned to the commits we read, is in [`docs/CHAT-REFERENCES.md`](docs/CHAT-REFERENCES.md).
|
|
80
84
|
- **CRM**: everything known about a contact in one view, the same person across networks, communities and memberships, search, audiences by rule (`friends:vk & chat:vk:… - list:blocked`).
|
|
85
|
+
- **Contact imports**: preview and import macOS Contacts (including synced iCloud accounts) or vCard exports from any platform; repeated imports keep stable identities. The directory updates during sync and includes people without messages. See [contact imports](docs/CONTACTS-IMPORT.md).
|
|
86
|
+
|
|
81
87
|
- **Outreach, broadcasts and automations**: who to message, when, on what conditions and with which text; a preview of who gets what; safe drafts by default or paced auto-send with quiet hours, a per-chat cooldown, daily caps and an opt-out list. Every automated send goes through one outbox, so it happens at most once and an unclear result is never retried by itself ([`docs/USER-GUIDE.md` §11](docs/USER-GUIDE.md#11-automations-message-people-by-rule-safely)).
|
|
82
88
|
- **Operator queue and replies**: walk unread messages chat by chat, reply suggestions from your own past answers (Tab takes one), patterns and reply variations, auto-replies on a matching message.
|
|
83
89
|
- **Business and community accounts**: a VK community token runs the Bots Long Poll; Telegram Business quick replies, greeting and away messages, hours and chatbots.
|
package/README.ru.md
CHANGED
|
@@ -55,9 +55,13 @@ node js/bin/meta-sovereign.js serve
|
|
|
55
55
|
|
|
56
56
|
Когда пакет будет опубликован в npm, заработают `npx -y meta-sovereign serve` (или `npm install -g meta-sovereign && meta-sovereign serve`). До тех пор запустить server без toolchain можно через desktop app (выше).
|
|
57
57
|
|
|
58
|
-
Оба backend по умолчанию слушают <http://127.0.0.1:8787>. JS server отвечает origin hosted app (`https://link-foundation.github.io`) с CORS headers; другие origins разрешаются через `meta-sovereign serve --allow-origin=<origins>` или `MS_ALLOW_ORIGINS`. Safari не пускает `https` страницы на `http://127.0.0.1`, но пускает на `https://127.0.0.1`; сервер теперь отвечает по HTTPS на том же порту с собственным сертификатом для localhost. Выполните `node js/bin/meta-sovereign.js trust` один раз (macOS добавляет сертификат в login keychain; на других системах печатается путь к сертификату) или откройте `https://127.0.0.1:8787/trust` в Safari и следуйте подсказкам — после этого hosted app в Safari подключается к серверу напрямую. До того уведомление называет
|
|
58
|
+
Оба backend по умолчанию слушают <http://127.0.0.1:8787>. JS server отвечает origin hosted app (`https://link-foundation.github.io`) с CORS headers; другие origins разрешаются через `meta-sovereign serve --allow-origin=<origins>` или `MS_ALLOW_ORIGINS`. Safari не пускает `https` страницы на `http://127.0.0.1`, но пускает на `https://127.0.0.1`; сервер теперь отвечает по HTTPS на том же порту с собственным сертификатом для localhost. Выполните `node js/bin/meta-sovereign.js trust` один раз (macOS добавляет сертификат в login keychain; на других системах печатается путь к сертификату) или откройте `https://127.0.0.1:8787/trust` в Safari и следуйте подсказкам — после этого hosted app в Safari подключается к серверу напрямую. До того уведомление называет сервер и предлагает **Доверять серверу** (<https://127.0.0.1:8787/trust>); ссылка на localhost-копию появляется только при её явном включении. `--tls=false` / `MS_TLS=off` отключает HTTPS. Safari использует размещённое приложение через доверенный локальный HTTPS API; localhost-интерфейс необязателен. Hosted SPA автоматически находит локальный сервер через `discoverServer()` (сохраненный override → порты `127.0.0.1`). Если браузер не подключился сам, откройте **Settings → Server** в приложении и вставьте URL, который напечатал сервер.
|
|
59
59
|
|
|
60
|
-
По умолчанию интерфейс — размещённое приложение: `serve` печатает его адрес, <https://link-foundation.github.io/meta-sovereign/>, а приложение само находит сервер на нашем порту 8787 (а также смотрит 8788, 7001, 7002 и 3000; на другом порту `serve` подскажет указать его в **Settings → Server**), так что серверу не нужно держать сайт.
|
|
60
|
+
По умолчанию интерфейс — размещённое приложение: `serve` печатает его адрес, <https://link-foundation.github.io/meta-sovereign/>, а приложение само находит сервер на нашем порту 8787 (а также смотрит 8788, 7001, 7002 и 3000; на другом порту `serve` подскажет указать его в **Settings → Server**), так что серверу не нужно держать сайт. По умолчанию `/` перенаправляет на GitHub Pages, а сервер предоставляет API, синхронизацию и вспомогательные маршруты подключения. Копия на localhost для работы без интернета включается отдельно: `serve --local-ui=true` (или `MS_LOCAL_UI=on`), после чего она доступна на <http://127.0.0.1:8787>. Desktop-оболочка явно включает свой встроенный интерфейс. `--ui-url=<url>` (или `MS_UI_URL`) задаёт другую размещённую копию, например Pages форка, и разрешает её origin.
|
|
61
|
+
|
|
62
|
+
В macOS интерактивный `serve` выполняет настройку целиком: защищает TLS-ключ этой установки через Secure Enclave, проверяет доверие сертификату, запрашивает его только при необходимости и открывает приложение на GitHub Pages. Без интерактивного терминала: `node js/bin/meta-sovereign.js serve --trust=true --open=true`. `--trust=false` и `--open=false` отключают эти действия. Для Swift-помощника нужны macOS Command Line Tools; `--hardware-key=false` явно выбирает файловый ключ на неподдерживаемом Mac. Аппаратная защита действует на диске; Node использует ключ в памяти. Правила и ограничения: [настройка и безопасность локального сервера](docs/LOCAL-SERVER-SECURITY.md).
|
|
63
|
+
|
|
64
|
+
Открытый чат загружается первым и показывает загрузку или ожидание повторной попытки. Фильтры Все / Личные / Группы / Каналы доступны внутри каждой папки Telegram. Вертикальные разделители меняют ширину столбцов; размеры запоминаются. Звонки находятся в главном меню после Чата, редактор аккаунта Telegram — в Профиле. В тёмной теме элементы управления используют белые контурные значки. JS-сервер хранит данные в папке пользователя ОС: `~/Library/Application Support/Meta Sovereign/store` в macOS, `%APPDATA%/Meta Sovereign/store` в Windows и `${XDG_CONFIG_HOME:-~/.config}/Meta Sovereign/store` в Linux. Запуск `serve` из старого checkout однократно переносит `.meta-sovereign`, сохраняя сообщения, контакты, сессии, медиа и сертификат; ссылка сохраняет старые пути. Перезапуск и другой рабочий каталог используют то же хранилище. Переопределение: `--store` или `META_SOVEREIGN_STORE_DIR`. См. [чаты и хранение данных](docs/CHAT-READINESS.md).
|
|
61
65
|
|
|
62
66
|
## Подключение SPA к серверу
|
|
63
67
|
|
|
@@ -78,6 +82,8 @@ SPA выбирает сервер в таком порядке ([`js/src/web/dis
|
|
|
78
82
|
- **Единый inbox** для VK, Telegram, X, WhatsApp, Facebook, Instagram, LinkedIn, почты, GitHub, Upwork, PeoplePerHour и сайтов вакансий: живые обновления там, где сеть это позволяет (Telegram, VK Long Poll, инкрементальный IMAP, GitHub), и импорт архивов любого размера для остальных.
|
|
79
83
|
- **Чат, который ведёт себя как приложения Telegram**: каждое сообщение отправляется один раз, галочки доставки, живые обновления и догрузка после разрыва, отметки о прочтении, облачные черновики, повторы, переживающие перезагрузку ([`docs/CHAT-REFERENCES.ru.md`](docs/CHAT-REFERENCES.ru.md)).
|
|
80
84
|
- **CRM**: всё о контакте в одном окне, один и тот же человек в разных сетях, сообщества и членства, поиск, аудитории по правилам (`friends:vk & chat:vk:… - list:blocked`).
|
|
85
|
+
- **Импорт контактов**: предпросмотр и импорт из Контактов macOS (включая синхронизированные учётные записи iCloud) или файлов vCard на любой платформе; повторный импорт сохраняет идентичность записей. Список обновляется при синхронизации и включает людей без сообщений. См. [импорт контактов](docs/CONTACTS-IMPORT.md).
|
|
86
|
+
|
|
81
87
|
- **Рассылки, broadcast и автоматизации**: кому писать, когда, при каких условиях и каким текстом; предпросмотр, кто что получит; по умолчанию безопасные черновики или отправка с паузами, тихими часами, перерывом для каждого чата, дневными лимитами и списком отписавшихся. Каждая автоматическая отправка идёт через один outbox: не больше одного раза, а неясный результат сам не повторяется ([`docs/USER-GUIDE.ru.md`](docs/USER-GUIDE.ru.md), §11).
|
|
82
88
|
- **Очередь оператора и ответы**: непрочитанные сообщения чат за чатом, подсказки ответов из ваших прошлых ответов (Tab берёт подсказку), паттерны и варианты ответов, автоответы на подходящее сообщение.
|
|
83
89
|
- **Аккаунты бизнеса и сообществ**: токен сообщества VK запускает Bots Long Poll; Telegram Business: быстрые ответы, приветствие и сообщение об отсутствии, часы работы, чат-боты.
|
package/README.zh.md
CHANGED
|
@@ -55,9 +55,13 @@ node js/bin/meta-sovereign.js serve
|
|
|
55
55
|
|
|
56
56
|
包发布到 npm 之后,`npx -y meta-sovereign serve`(或 `npm install -g meta-sovereign && meta-sovereign serve`)才会可用。在此之前,不想装工具链的话可以使用桌面 app(见上文)。
|
|
57
57
|
|
|
58
|
-
两个后端默认监听 <http://127.0.0.1:8787>。JS 服务器会对托管 app 的 origin(`https://link-foundation.github.io`)返回 CORS 头;其他 origin 可以用 `meta-sovereign serve --allow-origin=<origins>` 或 `MS_ALLOW_ORIGINS` 允许。Safari 不允许 `https` 页面访问 `http://127.0.0.1`,但允许访问 `https://127.0.0.1`;服务器现在也在同一端口上提供 HTTPS 服务,并使用专为 localhost 颁发的自签名证书。运行一次 `node js/bin/meta-sovereign.js trust`(macOS:将证书添加到 login keychain;其他系统:打印证书路径),或在 Safari 中打开 `https://127.0.0.1:8787/trust` 并按提示操作,之后托管 app 在 Safari
|
|
58
|
+
两个后端默认监听 <http://127.0.0.1:8787>。JS 服务器会对托管 app 的 origin(`https://link-foundation.github.io`)返回 CORS 头;其他 origin 可以用 `meta-sovereign serve --allow-origin=<origins>` 或 `MS_ALLOW_ORIGINS` 允许。Safari 不允许 `https` 页面访问 `http://127.0.0.1`,但允许访问 `https://127.0.0.1`;服务器现在也在同一端口上提供 HTTPS 服务,并使用专为 localhost 颁发的自签名证书。运行一次 `node js/bin/meta-sovereign.js trust`(macOS:将证书添加到 login keychain;其他系统:打印证书路径),或在 Safari 中打开 `https://127.0.0.1:8787/trust` 并按提示操作,之后托管 app 在 Safari 中即可直接连接服务器。信任之前,提示会显示服务器地址及 **信任服务器**(<https://127.0.0.1:8787/trust>)链接;仅当显式启用 localhost 界面时才显示其链接。`--tls=false` / `MS_TLS=off` 可关闭 HTTPS。Safari 通过受信任的本地 HTTPS API 使用托管应用;localhost 界面是可选的。托管的 SPA 会通过 `discoverServer()` 自动发现本地服务器(保存的覆盖值 → `127.0.0.1` 端口)。如果浏览器没有自动连接,请打开应用内 **Settings → Server**,粘贴服务器打印的 URL。
|
|
59
59
|
|
|
60
|
-
默认界面是托管的应用:`serve` 会打印其地址 <https://link-foundation.github.io/meta-sovereign/>,应用会自行在我们的端口 8787 上找到服务器(也会查看 8788、7001、7002 和 3000;若使用其他端口,`serve` 会提示在 **Settings → Server**
|
|
60
|
+
默认界面是托管的应用:`serve` 会打印其地址 <https://link-foundation.github.io/meta-sovereign/>,应用会自行在我们的端口 8787 上找到服务器(也会查看 8788、7001、7002 和 3000;若使用其他端口,`serve` 会提示在 **Settings → Server** 中设置),因此服务器无需自己托管网站。默认情况下,`/` 重定向到 GitHub Pages,服务器只提供 API、同步传输和连接辅助路由。如需离线使用 localhost 网页,请显式运行 `serve --local-ui=true`(或设置 `MS_LOCAL_UI=on`),随后可在 <http://127.0.0.1:8787> 访问该副本。桌面壳会显式启用其内置界面。`--ui-url=<url>`(或 `MS_UI_URL`)指定另一个托管副本(例如某个 fork 的 Pages),并允许其 origin。
|
|
61
|
+
|
|
62
|
+
在 macOS 交互式终端中,`serve` 会完成整个设置:使用 Secure Enclave 保护本次安装的 TLS 私钥,检测证书是否已受信任,仅在需要时请求信任,并打开 GitHub Pages 应用。无交互终端时可运行 `node js/bin/meta-sovereign.js serve --trust=true --open=true`。`--trust=false` 和 `--open=false` 可关闭相应操作。Swift 辅助程序需要 macOS Command Line Tools;不支持的 Mac 可显式使用 `--hardware-key=false` 选择文件密钥。硬件保护针对磁盘上的密钥;Node 在内存中使用密钥。详见[本地服务器设置与安全](docs/LOCAL-SERVER-SECURITY.md)。
|
|
63
|
+
|
|
64
|
+
打开的聊天优先加载,并显示加载或重试状态。全部 / 私聊 / 群组 / 频道筛选在每个 Telegram 文件夹内都可用。拖动竖直分隔线调整列宽,尺寸会保存。通话位于主菜单聊天之后,Telegram 账户编辑移到个人资料。默认深色控件使用白色线条图标。 JS 服务器将数据保存在操作系统用户的应用文件夹:macOS 为 `~/Library/Application Support/Meta Sovereign/store`,Windows 为 `%APPDATA%/Meta Sovereign/store`,Linux 为 `${XDG_CONFIG_HOME:-~/.config}/Meta Sovereign/store`。从旧 checkout 启动 `serve` 会一次性迁移 `.meta-sovereign`,保留消息、联系人、会话、媒体和证书,并用兼容目录链接保留旧路径。重启或改变工作目录仍使用同一存储。可通过 `--store` 或 `META_SOVEREIGN_STORE_DIR` 覆盖。参见[聊天就绪与数据存储](docs/CHAT-READINESS.md)。
|
|
61
65
|
|
|
62
66
|
## 将 SPA 连接到服务器
|
|
63
67
|
|
|
@@ -78,6 +82,8 @@ SPA 按以下顺序选择服务器([`js/src/web/discover.js`](js/src/web/disco
|
|
|
78
82
|
- **统一收件箱**:覆盖 VK、Telegram、X、WhatsApp、Facebook、Instagram、LinkedIn、电子邮件、GitHub、Upwork、PeoplePerHour 和招聘网站;网络允许时实时更新(Telegram、VK Long Poll、增量 IMAP、GitHub),其余可导入任意大小的存档。
|
|
79
83
|
- **像 Telegram 官方应用一样的聊天**:每条消息只发送一次,送达对勾,实时更新与断线后补齐,已读标记,云端草稿,刷新后仍保留的重试([`docs/CHAT-REFERENCES.zh.md`](docs/CHAT-REFERENCES.zh.md))。
|
|
80
84
|
- **CRM**:一个视图看到联系人的全部信息,跨网络识别同一个人,社区与成员关系,搜索,按规则定义受众(`friends:vk & chat:vk:… - list:blocked`)。
|
|
85
|
+
- **导入联系人**:预览并导入 macOS 通讯录(包括已同步的 iCloud 账户)或任何平台导出的 vCard;重复导入保留记录身份。目录在同步期间自动更新,也包含没有消息的联系人。参见[联系人导入](docs/CONTACTS-IMPORT.md)。
|
|
86
|
+
|
|
81
87
|
- **外联、群发与自动化**:给谁发、何时发、在什么条件下发、发什么内容;预览谁会收到什么;默认生成安全草稿,或按节奏自动发送,并有安静时段、每个聊天的冷却期、每日上限和退订名单。每次自动发送都经过同一个发件箱:最多发送一次,结果不明时不会自行重试([`docs/USER-GUIDE.zh.md`](docs/USER-GUIDE.zh.md) 第 11 节)。
|
|
82
88
|
- **Operator 队列与回复**:逐个聊天处理未读消息,根据您以往的回答给出回复建议(按 Tab 采用),模式与回复变体,对匹配消息自动回复。
|
|
83
89
|
- **企业与社区账号**:VK 社区令牌运行 Bots Long Poll;Telegram Business 的快捷回复、问候与离开消息、营业时间和聊天机器人。
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* the chat menu does); with `--test` the conditions are ignored there.
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
+
import { defaultStoreDir } from '../storage/app-directory.js';
|
|
22
23
|
import { promises as fs } from 'node:fs';
|
|
23
24
|
import { createInterface } from 'node:readline/promises';
|
|
24
25
|
import { getSource } from '../sources/index.js';
|
|
@@ -383,7 +384,7 @@ const automationsCmd = async (args, log, deps) => {
|
|
|
383
384
|
log(`unknown subcommand "${sub}"\n${AUTOMATION_HELP}`);
|
|
384
385
|
return 1;
|
|
385
386
|
}
|
|
386
|
-
const store = await deps.openStore(args.store ??
|
|
387
|
+
const store = await deps.openStore(args.store ?? defaultStoreDir());
|
|
387
388
|
const { runner, close } = runnerFor(store, args, deps);
|
|
388
389
|
try {
|
|
389
390
|
return await handler({
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Open the hosted face of the app, including when the local UI is enabled.
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
|
|
4
|
+
export const maybeOpenHostedApp = (args, handle, log) => {
|
|
5
|
+
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
6
|
+
const enabled =
|
|
7
|
+
args.open === undefined ? interactive : String(args.open) !== 'false';
|
|
8
|
+
if (args.foreground === false || !enabled) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const [program, ...prefix] =
|
|
12
|
+
process.platform === 'darwin'
|
|
13
|
+
? ['open']
|
|
14
|
+
: process.platform === 'win32'
|
|
15
|
+
? ['rundll32.exe', 'url.dll,FileProtocolHandler']
|
|
16
|
+
: ['xdg-open'];
|
|
17
|
+
const browser = spawn(program, [...prefix, handle.uiUrl], {
|
|
18
|
+
detached: true,
|
|
19
|
+
stdio: 'ignore',
|
|
20
|
+
});
|
|
21
|
+
browser.on('error', () =>
|
|
22
|
+
log(`open the app in your browser → ${handle.uiUrl}`)
|
|
23
|
+
);
|
|
24
|
+
browser.unref();
|
|
25
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Local address-book imports, with the same parser and plan as the app.
|
|
2
|
+
import { defaultStoreDir } from '../storage/app-directory.js';
|
|
3
|
+
import { promises as fs } from 'node:fs';
|
|
4
|
+
import { decodeVcard, parseVcard } from '../sources/vcard.js';
|
|
5
|
+
import { readSystemContacts } from '../sources/system-contacts.js';
|
|
6
|
+
import {
|
|
7
|
+
planContactsImport,
|
|
8
|
+
applyContactsImport,
|
|
9
|
+
} from '../sources/contacts-import.js';
|
|
10
|
+
|
|
11
|
+
export const CONTACTS_HELP = ` contacts-import [--file=<contacts.vcf> | --system] [--store=<dir>] [--preview]
|
|
12
|
+
import a vCard export or macOS Contacts (including synced iCloud accounts); nothing is uploaded`;
|
|
13
|
+
|
|
14
|
+
export const createContactCommands = ({ openStore }) => ({
|
|
15
|
+
'contacts-import': async (args, log) => {
|
|
16
|
+
const system = args.system === true || args.system === 'true';
|
|
17
|
+
if (system === Boolean(args.file)) {
|
|
18
|
+
log('choose --file=<contacts.vcf> or --system');
|
|
19
|
+
return 1;
|
|
20
|
+
}
|
|
21
|
+
const storeDir = args.store ?? defaultStoreDir();
|
|
22
|
+
const store = await openStore(storeDir);
|
|
23
|
+
try {
|
|
24
|
+
const rows = system
|
|
25
|
+
? await readSystemContacts(storeDir)
|
|
26
|
+
: parseVcard(decodeVcard(await fs.readFile(args.file)));
|
|
27
|
+
const plan = await planContactsImport(store, rows, {
|
|
28
|
+
source: system ? 'system' : 'vcard',
|
|
29
|
+
});
|
|
30
|
+
const preview = args.preview === true || args.preview === 'true';
|
|
31
|
+
log(
|
|
32
|
+
JSON.stringify(
|
|
33
|
+
preview
|
|
34
|
+
? { preview: true, ...plan.stats }
|
|
35
|
+
: await applyContactsImport(store, plan)
|
|
36
|
+
)
|
|
37
|
+
);
|
|
38
|
+
return 0;
|
|
39
|
+
} finally {
|
|
40
|
+
await store.close?.();
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
});
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* this module importing the entry point back.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import { defaultStoreDir } from '../storage/app-directory.js';
|
|
14
15
|
import {
|
|
15
16
|
compareAllCvs,
|
|
16
17
|
cvPlatformCatalogue,
|
|
@@ -147,7 +148,7 @@ const planCmd = async (args, log) => {
|
|
|
147
148
|
};
|
|
148
149
|
|
|
149
150
|
const readCmd = async (args, log, openStore, injected) => {
|
|
150
|
-
const store = await openStore(args.store ??
|
|
151
|
+
const store = await openStore(args.store ?? defaultStoreDir());
|
|
151
152
|
const ids = parsePlatforms(args.platforms);
|
|
152
153
|
const { entries, failures } = await readAllCvs(
|
|
153
154
|
ids,
|
|
@@ -168,7 +169,7 @@ const readCmd = async (args, log, openStore, injected) => {
|
|
|
168
169
|
};
|
|
169
170
|
|
|
170
171
|
const diffCmd = async (args, log, openStore) => {
|
|
171
|
-
const store = await openStore(args.store ??
|
|
172
|
+
const store = await openStore(args.store ?? defaultStoreDir());
|
|
172
173
|
const entries = await loadStoredCvs(store, parsePlatforms(args.platforms));
|
|
173
174
|
const comparison = compareAllCvs(entries, { prefer: args.prefer ?? null });
|
|
174
175
|
const diffs = diffAllCvs(entries, null);
|
|
@@ -213,7 +214,7 @@ const applyConfirmed = async (preview, { args, log, ask, run }) => {
|
|
|
213
214
|
};
|
|
214
215
|
|
|
215
216
|
const syncCmd = async (args, log, openStore, injected) => {
|
|
216
|
-
const store = await openStore(args.store ??
|
|
217
|
+
const store = await openStore(args.store ?? defaultStoreDir());
|
|
217
218
|
const ids = parsePlatforms(args.platforms);
|
|
218
219
|
const run = (dryRun, confirmed = null) =>
|
|
219
220
|
syncCvAcross(ids, {
|
|
@@ -256,7 +257,7 @@ const syncCmd = async (args, log, openStore, injected) => {
|
|
|
256
257
|
};
|
|
257
258
|
|
|
258
259
|
const telemetryCmd = async (args, log, openStore) => {
|
|
259
|
-
const store = await openStore(args.store ??
|
|
260
|
+
const store = await openStore(args.store ?? defaultStoreDir());
|
|
260
261
|
const events = await loadCvTelemetry(store, {
|
|
261
262
|
runId: args.run ?? null,
|
|
262
263
|
platform: args.platform ?? null,
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* its own `openStore`, and the tests inject fake source adapters.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import { defaultStoreDir } from '../storage/app-directory.js';
|
|
14
15
|
import { parseList } from './cv-commands.js';
|
|
15
16
|
import { getSource } from '../sources/index.js';
|
|
16
17
|
import {
|
|
@@ -40,7 +41,7 @@ const optionalNumber = (value) =>
|
|
|
40
41
|
|
|
41
42
|
const asJson = (value) => JSON.stringify(value, null, 2);
|
|
42
43
|
|
|
43
|
-
const storeOf = (args, openStore) => openStore(args.store ??
|
|
44
|
+
const storeOf = (args, openStore) => openStore(args.store ?? defaultStoreDir());
|
|
44
45
|
|
|
45
46
|
/**
|
|
46
47
|
* `--stickers=vk:21,facebook:369239263222822` → selection entries. The
|
package/js/src/cli/index.js
CHANGED
|
@@ -13,7 +13,10 @@
|
|
|
13
13
|
* entry point unit-testable.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
import { defaultStoreDir, resolveUserStore } from '../storage/app-directory.js';
|
|
16
17
|
import path from 'node:path';
|
|
18
|
+
import { maybeOpenHostedApp } from './browser-open.js';
|
|
19
|
+
import { CONTACTS_HELP, createContactCommands } from './contact-commands.js';
|
|
17
20
|
import { promises as fs } from 'node:fs';
|
|
18
21
|
import { parseArgs } from './lino-args.js';
|
|
19
22
|
import { CV_HELP, createCvCommands } from './cv-commands.js';
|
|
@@ -25,7 +28,11 @@ import {
|
|
|
25
28
|
GREETINGS_HELP,
|
|
26
29
|
createGreetingCommands,
|
|
27
30
|
} from './greetings-commands.js';
|
|
28
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
TRUST_HELP,
|
|
33
|
+
createTrustCommands,
|
|
34
|
+
setupLocalCertificate,
|
|
35
|
+
} from './trust-commands.js';
|
|
29
36
|
import {
|
|
30
37
|
MIGRATE_HELP,
|
|
31
38
|
SCHEMA_ERROR_CODES,
|
|
@@ -83,10 +90,13 @@ Commands:
|
|
|
83
90
|
backup --store=<dir> --archive=<dir> [--keep=<n>]
|
|
84
91
|
restore --file=<path> --store=<dir> [--replace]
|
|
85
92
|
passcode --clear [--store=<dir>] (remove a forgotten local passcode)
|
|
86
|
-
serve [--port=<n>] [--store=<dir>] [--allow-origin=<origins|none>] [--ui-url=<url>] [--local-ui=
|
|
93
|
+
serve [--port=<n>] [--store=<dir>] [--allow-origin=<origins|none>] [--ui-url=<url>] [--local-ui=true] [--tls=false] [--trust=true|false] [--hardware-key=false] [--open=true|false] [--quiet-logs]
|
|
94
|
+
open https://link-foundation.github.io/meta-sovereign/; the localhost UI is opt-in
|
|
95
|
+
on macOS: protect the TLS key with Secure Enclave; detect trust, set it up once in an interactive terminal
|
|
87
96
|
verbose logs are written to <store>/logs/meta-sovereign.log by default
|
|
88
97
|
https is served on the same port (for Safari); --tls=false or MS_TLS=off turns it off
|
|
89
98
|
${TRUST_HELP}
|
|
99
|
+
${CONTACTS_HELP}
|
|
90
100
|
sources
|
|
91
101
|
source-pull --source=<name> [--protocol=<p>] [--host=<mail-host>] [--owner=<o>] [--repo=<r>] [--state=<s>] [--offset=<n>] [--limit=<n>] [--store=<dir>]
|
|
92
102
|
email-send --protocol=<gmail|microsoft-graph|jmap|smtp> --to=<email> --subject=<s> --text=<body> [--host=<mail-host>]
|
|
@@ -135,14 +145,14 @@ const importCmd = async (args, log) => {
|
|
|
135
145
|
const file = args.file;
|
|
136
146
|
const archive = await fs.readFile(file, 'utf8');
|
|
137
147
|
const parsed = file.endsWith('.json') ? JSON.parse(archive) : archive;
|
|
138
|
-
const store = await openStore(args.store ??
|
|
148
|
+
const store = await openStore(args.store ?? defaultStoreDir());
|
|
139
149
|
const n = await importInto(store, args.source, parsed);
|
|
140
150
|
log(`imported ${n} messages from ${args.source}`);
|
|
141
151
|
return 0;
|
|
142
152
|
};
|
|
143
153
|
|
|
144
154
|
const exportCmd = async (args, log) => {
|
|
145
|
-
const store = await openReadOnly(args.store ??
|
|
155
|
+
const store = await openReadOnly(args.store ?? defaultStoreDir());
|
|
146
156
|
const links = await store.query();
|
|
147
157
|
await fs.writeFile(args.file, JSON.stringify(links, null, 2));
|
|
148
158
|
log(`exported ${links.length} links to ${args.file}`);
|
|
@@ -150,7 +160,7 @@ const exportCmd = async (args, log) => {
|
|
|
150
160
|
};
|
|
151
161
|
|
|
152
162
|
const backupCmd = async (args, log) => {
|
|
153
|
-
const store = await openReadOnly(args.store ??
|
|
163
|
+
const store = await openReadOnly(args.store ?? defaultStoreDir());
|
|
154
164
|
const file = await createBackup(store, { archiveDir: args.archive });
|
|
155
165
|
if (args.keep) {
|
|
156
166
|
await pruneBackups({ archiveDir: args.archive, keep: Number(args.keep) });
|
|
@@ -166,7 +176,7 @@ const passcodeCmd = async (args, log) => {
|
|
|
166
176
|
log('usage: meta-sovereign passcode --clear [--store=<dir>]');
|
|
167
177
|
return 1;
|
|
168
178
|
}
|
|
169
|
-
const cleared = clearPasscode(args.store ??
|
|
179
|
+
const cleared = clearPasscode(args.store ?? defaultStoreDir());
|
|
170
180
|
log(cleared ? 'local passcode cleared' : 'no local passcode was set');
|
|
171
181
|
return 0;
|
|
172
182
|
};
|
|
@@ -176,7 +186,7 @@ const restoreCmd = async (args, log) => {
|
|
|
176
186
|
// Swap the whole log in one step; the store must not be in use (R-Z16).
|
|
177
187
|
const n = await restoreBackupToDir(
|
|
178
188
|
args.file,
|
|
179
|
-
args.store ??
|
|
189
|
+
args.store ?? defaultStoreDir(),
|
|
180
190
|
{
|
|
181
191
|
replace: true,
|
|
182
192
|
}
|
|
@@ -184,7 +194,7 @@ const restoreCmd = async (args, log) => {
|
|
|
184
194
|
log(`replaced the store with ${n} records from ${args.file}`);
|
|
185
195
|
return 0;
|
|
186
196
|
}
|
|
187
|
-
const store = await openStore(args.store ??
|
|
197
|
+
const store = await openStore(args.store ?? defaultStoreDir());
|
|
188
198
|
const n = await restoreBackup(store, args.file);
|
|
189
199
|
log(`restored ${n} links from ${args.file}`);
|
|
190
200
|
return 0;
|
|
@@ -196,7 +206,7 @@ const serveOptions = (args) => ({
|
|
|
196
206
|
// back to MS_ALLOW_ORIGINS and then the built-in defaults.
|
|
197
207
|
...(args['allow-origin'] ? { allowOrigins: args['allow-origin'] } : {}),
|
|
198
208
|
// The hosted app is the UI; the server's own copy is a fallback
|
|
199
|
-
//
|
|
209
|
+
// enabled with `--local-ui=true` (R-Y58, R-Y59).
|
|
200
210
|
...(args['ui-url'] ? { uiUrl: args['ui-url'] } : {}),
|
|
201
211
|
...(args['local-ui'] !== undefined ? { localUi: args['local-ui'] } : {}),
|
|
202
212
|
// https beside http on the same port, for Safari (R-Y64).
|
|
@@ -221,12 +231,12 @@ const logAddresses = (handle, log) => {
|
|
|
221
231
|
log(
|
|
222
232
|
handle.localUi
|
|
223
233
|
? `local copy (fallback, e.g. for Safari or offline) → ${origin}/`
|
|
224
|
-
: `local copy off: ${origin}/ opens the app above`
|
|
234
|
+
: `local copy off: ${origin}/ opens the app above (enable with --local-ui=true)`
|
|
225
235
|
);
|
|
226
236
|
};
|
|
227
237
|
|
|
228
238
|
const serveCmd = async (args, log) => {
|
|
229
|
-
const storeDir = args.store ??
|
|
239
|
+
const storeDir = args.store ?? defaultStoreDir();
|
|
230
240
|
// Verbose logging is on by default: every request, event and error is
|
|
231
241
|
// written to a file in the app folder (and echoed here), so the root
|
|
232
242
|
// cause of any issue is always on disk. `--quiet-logs` opts out.
|
|
@@ -242,6 +252,9 @@ const serveCmd = async (args, log) => {
|
|
|
242
252
|
}
|
|
243
253
|
let handle;
|
|
244
254
|
try {
|
|
255
|
+
if (args.foreground !== false) {
|
|
256
|
+
await setupLocalCertificate(args, log);
|
|
257
|
+
}
|
|
245
258
|
handle = await startServer({
|
|
246
259
|
port: Number(args.port ?? 8787),
|
|
247
260
|
storeDir,
|
|
@@ -256,6 +269,7 @@ const serveCmd = async (args, log) => {
|
|
|
256
269
|
return 1;
|
|
257
270
|
}
|
|
258
271
|
logAddresses(handle, log);
|
|
272
|
+
maybeOpenHostedApp(args, handle, log);
|
|
259
273
|
if (serverLog) {
|
|
260
274
|
log(`verbose logs → ${serverLog.file}`);
|
|
261
275
|
}
|
|
@@ -300,7 +314,7 @@ const rawEmailTransport = (args, protocol) =>
|
|
|
300
314
|
: null;
|
|
301
315
|
|
|
302
316
|
const sourcePullCmd = async (args, log) => {
|
|
303
|
-
const store = await openStore(args.store ??
|
|
317
|
+
const store = await openStore(args.store ?? defaultStoreDir());
|
|
304
318
|
const protocol = args.protocol;
|
|
305
319
|
const result = await pullLiveInto(store, args.source, {
|
|
306
320
|
protocol,
|
|
@@ -371,7 +385,7 @@ const githubLiveFromArgs = (args) =>
|
|
|
371
385
|
});
|
|
372
386
|
|
|
373
387
|
const githubCloneCmd = async (args, log) => {
|
|
374
|
-
const store = await openStore(args.store ??
|
|
388
|
+
const store = await openStore(args.store ?? defaultStoreDir());
|
|
375
389
|
const live = githubLiveFromArgs(args);
|
|
376
390
|
const result = await live.cloneRepo({
|
|
377
391
|
owner: args.owner,
|
|
@@ -477,7 +491,7 @@ const peoplePerHourMessageCmd = boardMessageCmd(
|
|
|
477
491
|
);
|
|
478
492
|
|
|
479
493
|
const audienceCmd = async (args, log) => {
|
|
480
|
-
const store = await openReadOnly(args.store ??
|
|
494
|
+
const store = await openReadOnly(args.store ?? defaultStoreDir());
|
|
481
495
|
const all = await store.query();
|
|
482
496
|
const matched = evalAudience(all, args.query ?? '');
|
|
483
497
|
log(JSON.stringify(matched, null, 2));
|
|
@@ -485,7 +499,7 @@ const audienceCmd = async (args, log) => {
|
|
|
485
499
|
};
|
|
486
500
|
|
|
487
501
|
const factsCmd = async (args, log) => {
|
|
488
|
-
const store = await openReadOnly(args.store ??
|
|
502
|
+
const store = await openReadOnly(args.store ?? defaultStoreDir());
|
|
489
503
|
const all = await store.query();
|
|
490
504
|
const messages = all.filter((l) => l.id?.startsWith('msg:'));
|
|
491
505
|
const patterns = all
|
|
@@ -502,7 +516,7 @@ const factsCmd = async (args, log) => {
|
|
|
502
516
|
};
|
|
503
517
|
|
|
504
518
|
const searchCmd = async (args, log) => {
|
|
505
|
-
const store = await openReadOnly(args.store ??
|
|
519
|
+
const store = await openReadOnly(args.store ?? defaultStoreDir());
|
|
506
520
|
const results = await localSearch(store, {
|
|
507
521
|
query: args.query ?? '',
|
|
508
522
|
min: Number(args.min ?? 0.2),
|
|
@@ -512,7 +526,7 @@ const searchCmd = async (args, log) => {
|
|
|
512
526
|
};
|
|
513
527
|
|
|
514
528
|
const broadcastCmd = async (args, log) => {
|
|
515
|
-
const store = await openStore(args.store ??
|
|
529
|
+
const store = await openStore(args.store ?? defaultStoreDir());
|
|
516
530
|
const networks = (args.networks?.split(',') ?? listSources()).filter((n) =>
|
|
517
531
|
listSources().includes(n)
|
|
518
532
|
);
|
|
@@ -530,7 +544,7 @@ const broadcastCmd = async (args, log) => {
|
|
|
530
544
|
};
|
|
531
545
|
|
|
532
546
|
const listByPrefixCmd = (prefix) => async (args, log) => {
|
|
533
|
-
const store = await openReadOnly(args.store ??
|
|
547
|
+
const store = await openReadOnly(args.store ?? defaultStoreDir());
|
|
534
548
|
const all = await store.query();
|
|
535
549
|
log(
|
|
536
550
|
JSON.stringify(
|
|
@@ -553,7 +567,7 @@ const patternsInferCmd = async (args, log) => {
|
|
|
553
567
|
};
|
|
554
568
|
|
|
555
569
|
const graphsRunCmd = async (args, log) => {
|
|
556
|
-
const store = await openStore(args.store ??
|
|
570
|
+
const store = await openStore(args.store ?? defaultStoreDir());
|
|
557
571
|
const persisted = await store.get(args.id);
|
|
558
572
|
if (!persisted) {
|
|
559
573
|
log(`graph ${args.id} not found`);
|
|
@@ -576,7 +590,7 @@ const graphsRunCmd = async (args, log) => {
|
|
|
576
590
|
};
|
|
577
591
|
|
|
578
592
|
const profileCmd = async (args, log) => {
|
|
579
|
-
const store = await openStore(args.store ??
|
|
593
|
+
const store = await openStore(args.store ?? defaultStoreDir());
|
|
580
594
|
if (args.name || args.bio) {
|
|
581
595
|
const profile = {
|
|
582
596
|
id: 'profile:me',
|
|
@@ -606,7 +620,7 @@ const profileCmd = async (args, log) => {
|
|
|
606
620
|
};
|
|
607
621
|
|
|
608
622
|
const resumeCmd = async (args, log) => {
|
|
609
|
-
const store = await openStore(args.store ??
|
|
623
|
+
const store = await openStore(args.store ?? defaultStoreDir());
|
|
610
624
|
if (args.title || args.body) {
|
|
611
625
|
const resume = {
|
|
612
626
|
id: 'resume:me',
|
|
@@ -635,7 +649,7 @@ const resumeCmd = async (args, log) => {
|
|
|
635
649
|
};
|
|
636
650
|
|
|
637
651
|
const syncListenCmd = async (args, log) => {
|
|
638
|
-
const store = await openStore(args.store ??
|
|
652
|
+
const store = await openStore(args.store ?? defaultStoreDir());
|
|
639
653
|
const peer = createPeer(store, {
|
|
640
654
|
node: 'cli-listener',
|
|
641
655
|
schemaVersion: CURRENT_SCHEMA,
|
|
@@ -649,7 +663,7 @@ const syncListenCmd = async (args, log) => {
|
|
|
649
663
|
};
|
|
650
664
|
|
|
651
665
|
const syncConnectCmd = async (args, log) => {
|
|
652
|
-
const store = await openStore(args.store ??
|
|
666
|
+
const store = await openStore(args.store ?? defaultStoreDir());
|
|
653
667
|
const peer = createPeer(store, {
|
|
654
668
|
node: 'cli-client',
|
|
655
669
|
schemaVersion: CURRENT_SCHEMA,
|
|
@@ -667,7 +681,7 @@ const syncConnectCmd = async (args, log) => {
|
|
|
667
681
|
};
|
|
668
682
|
|
|
669
683
|
const outreachCmd = async (args, log) => {
|
|
670
|
-
const store = await openStore(args.store ??
|
|
684
|
+
const store = await openStore(args.store ?? defaultStoreDir());
|
|
671
685
|
const all = await store.query();
|
|
672
686
|
const matched = evalAudience(all, args.query ?? '');
|
|
673
687
|
const replyGroup = args.reply
|
|
@@ -689,7 +703,7 @@ const exportEncryptedCmd = async (args, log) => {
|
|
|
689
703
|
log('export-encrypted: --passphrase is required (R-K13)');
|
|
690
704
|
return 1;
|
|
691
705
|
}
|
|
692
|
-
const store = await openReadOnly(args.store ??
|
|
706
|
+
const store = await openReadOnly(args.store ?? defaultStoreDir());
|
|
693
707
|
const file = await writeEncryptedExport(store, args.file, {
|
|
694
708
|
passphrase: args.passphrase,
|
|
695
709
|
});
|
|
@@ -702,7 +716,7 @@ const purgeTombstonesCmd = async (args, log) => {
|
|
|
702
716
|
log('purge-tombstones: refusing without --confirm=true (R-K4)');
|
|
703
717
|
return 1;
|
|
704
718
|
}
|
|
705
|
-
const store = await openStore(args.store ??
|
|
719
|
+
const store = await openStore(args.store ?? defaultStoreDir());
|
|
706
720
|
const idPrefix = args['id-prefix'] ?? null;
|
|
707
721
|
const olderThan = args['older-than']
|
|
708
722
|
? new Date(args['older-than']).toISOString()
|
|
@@ -728,7 +742,7 @@ const purgeTombstonesCmd = async (args, log) => {
|
|
|
728
742
|
};
|
|
729
743
|
|
|
730
744
|
const vaultFile = (args) =>
|
|
731
|
-
args.file ?? path.join(args.store ??
|
|
745
|
+
args.file ?? path.join(args.store ?? defaultStoreDir(), 'vault.json');
|
|
732
746
|
|
|
733
747
|
const vaultInitCmd = async (args, log) => {
|
|
734
748
|
const file = vaultFile(args);
|
|
@@ -816,6 +830,7 @@ const COMMANDS = {
|
|
|
816
830
|
...createGreetingCommands({ openStore }),
|
|
817
831
|
// Let Safari trust the server's own certificate (R-Y64).
|
|
818
832
|
...createTrustCommands(),
|
|
833
|
+
...createContactCommands({ openStore }),
|
|
819
834
|
// Bring a store to the current data schema, or look first (R-Z17).
|
|
820
835
|
...createMigrateCommands(),
|
|
821
836
|
help: async (_a, log) => {
|
|
@@ -846,7 +861,26 @@ export const runCli = async (argv, { log = console.log } = {}) => {
|
|
|
846
861
|
return 1;
|
|
847
862
|
}
|
|
848
863
|
try {
|
|
849
|
-
|
|
864
|
+
const args = parseArgs(rest);
|
|
865
|
+
if (
|
|
866
|
+
args.store === undefined &&
|
|
867
|
+
![
|
|
868
|
+
'help',
|
|
869
|
+
'--help',
|
|
870
|
+
'-h',
|
|
871
|
+
'version',
|
|
872
|
+
'--version',
|
|
873
|
+
'-v',
|
|
874
|
+
undefined,
|
|
875
|
+
].includes(cmd)
|
|
876
|
+
) {
|
|
877
|
+
args.store =
|
|
878
|
+
['serve', 'trust'].includes(cmd) &&
|
|
879
|
+
!process.env.META_SOVEREIGN_STORE_DIR
|
|
880
|
+
? await resolveUserStore({ log })
|
|
881
|
+
: defaultStoreDir();
|
|
882
|
+
}
|
|
883
|
+
return await handler(args, log);
|
|
850
884
|
} catch (error) {
|
|
851
885
|
if (error?.code === 'STORE_LOCKED' || SCHEMA_ERROR_CODES.has(error?.code)) {
|
|
852
886
|
log(error.message); // another process owns the store (R-Z2); the schema (R-Z17)
|