@jungjaehoon/mama-os 0.78.0 → 0.79.1
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/CHANGELOG.md +341 -0
- package/README.md +38 -24
- package/db/migrations/002-model-runs-parent-index.sql +11 -0
- package/db/migrations/003-upkeep-settled.sql +15 -0
- package/dist/api/google-read-actions.d.ts +30 -0
- package/dist/api/google-read-actions.d.ts.map +1 -0
- package/dist/api/google-read-actions.js +256 -0
- package/dist/api/google-read-actions.js.map +1 -0
- package/dist/api/record-actions.d.ts.map +1 -1
- package/dist/api/record-actions.js +23 -5
- package/dist/api/record-actions.js.map +1 -1
- package/dist/api/report-handler.d.ts +0 -6
- package/dist/api/report-handler.d.ts.map +1 -1
- package/dist/api/report-handler.js +0 -54
- package/dist/api/report-handler.js.map +1 -1
- package/dist/api/security-events.d.ts +1 -1
- package/dist/api/security-events.d.ts.map +1 -1
- package/dist/api/security-events.js.map +1 -1
- package/dist/api/source-actions.d.ts +9 -0
- package/dist/api/source-actions.d.ts.map +1 -1
- package/dist/api/source-actions.js +49 -0
- package/dist/api/source-actions.js.map +1 -1
- package/dist/api/viewer-request-security.d.ts +14 -1
- package/dist/api/viewer-request-security.d.ts.map +1 -1
- package/dist/api/viewer-request-security.js +26 -3
- package/dist/api/viewer-request-security.js.map +1 -1
- package/dist/api/viewer-server.d.ts +7 -0
- package/dist/api/viewer-server.d.ts.map +1 -1
- package/dist/api/viewer-server.js +32 -0
- package/dist/api/viewer-server.js.map +1 -1
- package/dist/api/work-actions.d.ts +19 -2
- package/dist/api/work-actions.d.ts.map +1 -1
- package/dist/api/work-actions.js +15 -0
- package/dist/api/work-actions.js.map +1 -1
- package/dist/cli/commands/backup.d.ts +7 -6
- package/dist/cli/commands/backup.d.ts.map +1 -1
- package/dist/cli/commands/backup.js +44 -7
- package/dist/cli/commands/backup.js.map +1 -1
- package/dist/cli/commands/control.d.ts +22 -4
- package/dist/cli/commands/control.d.ts.map +1 -1
- package/dist/cli/commands/control.js +172 -11
- package/dist/cli/commands/control.js.map +1 -1
- package/dist/cli/commands/daemon.d.ts +13 -2
- package/dist/cli/commands/daemon.d.ts.map +1 -1
- package/dist/cli/commands/daemon.js +211 -5
- package/dist/cli/commands/daemon.js.map +1 -1
- package/dist/cli/commands/init-questions.d.ts +110 -0
- package/dist/cli/commands/init-questions.d.ts.map +1 -0
- package/dist/cli/commands/init-questions.js +445 -0
- package/dist/cli/commands/init-questions.js.map +1 -0
- package/dist/cli/commands/init-section.d.ts +13 -0
- package/dist/cli/commands/init-section.d.ts.map +1 -0
- package/dist/cli/commands/init-section.js +439 -0
- package/dist/cli/commands/init-section.js.map +1 -0
- package/dist/cli/commands/init.d.ts +56 -2
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +562 -351
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/node-move.d.ts +20 -0
- package/dist/cli/commands/node-move.d.ts.map +1 -0
- package/dist/cli/commands/node-move.js +289 -0
- package/dist/cli/commands/node-move.js.map +1 -0
- package/dist/cli/commands/register-owner.d.ts +3 -0
- package/dist/cli/commands/register-owner.d.ts.map +1 -1
- package/dist/cli/commands/register-owner.js +2 -0
- package/dist/cli/commands/register-owner.js.map +1 -1
- package/dist/cli/commands/restore.d.ts +2 -0
- package/dist/cli/commands/restore.d.ts.map +1 -1
- package/dist/cli/commands/restore.js +7 -0
- package/dist/cli/commands/restore.js.map +1 -1
- package/dist/cli/commands/service.d.ts +16 -0
- package/dist/cli/commands/service.d.ts.map +1 -1
- package/dist/cli/commands/service.js +36 -8
- package/dist/cli/commands/service.js.map +1 -1
- package/dist/cli/commands/tools.d.ts +1 -1
- package/dist/cli/commands/tools.d.ts.map +1 -1
- package/dist/cli/commands/tools.js +8 -3
- package/dist/cli/commands/tools.js.map +1 -1
- package/dist/cli/commands/uninstall.d.ts.map +1 -1
- package/dist/cli/commands/uninstall.js +123 -15
- package/dist/cli/commands/uninstall.js.map +1 -1
- package/dist/cli/commands/update.d.ts +59 -0
- package/dist/cli/commands/update.d.ts.map +1 -0
- package/dist/cli/commands/update.js +948 -0
- package/dist/cli/commands/update.js.map +1 -0
- package/dist/cli/index.js +8 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/launch-files.d.ts +36 -0
- package/dist/cli/launch-files.d.ts.map +1 -1
- package/dist/cli/launch-files.js +94 -1
- package/dist/cli/launch-files.js.map +1 -1
- package/dist/cli/prompt.d.ts +2 -1
- package/dist/cli/prompt.d.ts.map +1 -1
- package/dist/cli/prompt.js +5 -2
- package/dist/cli/prompt.js.map +1 -1
- package/dist/cli/runtime/backup.d.ts +17 -1
- package/dist/cli/runtime/backup.d.ts.map +1 -1
- package/dist/cli/runtime/backup.js +32 -4
- package/dist/cli/runtime/backup.js.map +1 -1
- package/dist/cli/runtime/daemon-control.d.ts +92 -7
- package/dist/cli/runtime/daemon-control.d.ts.map +1 -1
- package/dist/cli/runtime/daemon-control.js +183 -8
- package/dist/cli/runtime/daemon-control.js.map +1 -1
- package/dist/cli/runtime/default-roots.d.ts +27 -0
- package/dist/cli/runtime/default-roots.d.ts.map +1 -0
- package/dist/cli/runtime/default-roots.js +61 -0
- package/dist/cli/runtime/default-roots.js.map +1 -0
- package/dist/cli/runtime/diagnostics.d.ts +25 -0
- package/dist/cli/runtime/diagnostics.d.ts.map +1 -1
- package/dist/cli/runtime/diagnostics.js +250 -63
- package/dist/cli/runtime/diagnostics.js.map +1 -1
- package/dist/cli/runtime/init-state.d.ts +20 -0
- package/dist/cli/runtime/init-state.d.ts.map +1 -0
- package/dist/cli/runtime/init-state.js +51 -0
- package/dist/cli/runtime/init-state.js.map +1 -0
- package/dist/cli/runtime/install-kind.d.ts +54 -0
- package/dist/cli/runtime/install-kind.d.ts.map +1 -0
- package/dist/cli/runtime/install-kind.js +164 -0
- package/dist/cli/runtime/install-kind.js.map +1 -0
- package/dist/cli/runtime/node-move-journal.d.ts +44 -0
- package/dist/cli/runtime/node-move-journal.d.ts.map +1 -0
- package/dist/cli/runtime/node-move-journal.js +48 -0
- package/dist/cli/runtime/node-move-journal.js.map +1 -0
- package/dist/cli/runtime/release-check.d.ts +94 -0
- package/dist/cli/runtime/release-check.d.ts.map +1 -0
- package/dist/cli/runtime/release-check.js +316 -0
- package/dist/cli/runtime/release-check.js.map +1 -0
- package/dist/cli/runtime/setup-page.d.ts +57 -0
- package/dist/cli/runtime/setup-page.d.ts.map +1 -0
- package/dist/cli/runtime/setup-page.js +266 -0
- package/dist/cli/runtime/setup-page.js.map +1 -0
- package/dist/cli/runtime/source-tools.d.ts +60 -0
- package/dist/cli/runtime/source-tools.d.ts.map +1 -0
- package/dist/cli/runtime/source-tools.js +170 -0
- package/dist/cli/runtime/source-tools.js.map +1 -0
- package/dist/cli/runtime/telegram-pairing.d.ts +73 -0
- package/dist/cli/runtime/telegram-pairing.d.ts.map +1 -0
- package/dist/cli/runtime/telegram-pairing.js +184 -0
- package/dist/cli/runtime/telegram-pairing.js.map +1 -0
- package/dist/cli/runtime/update-journal.d.ts +135 -0
- package/dist/cli/runtime/update-journal.d.ts.map +1 -0
- package/dist/cli/runtime/update-journal.js +144 -0
- package/dist/cli/runtime/update-journal.js.map +1 -0
- package/dist/cli/runtime/update-progress.d.ts +95 -0
- package/dist/cli/runtime/update-progress.d.ts.map +1 -0
- package/dist/cli/runtime/update-progress.js +154 -0
- package/dist/cli/runtime/update-progress.js.map +1 -0
- package/dist/cli/runtime/update-request.d.ts +58 -0
- package/dist/cli/runtime/update-request.d.ts.map +1 -0
- package/dist/cli/runtime/update-request.js +172 -0
- package/dist/cli/runtime/update-request.js.map +1 -0
- package/dist/connectors/calendar/index.d.ts +5 -1
- package/dist/connectors/calendar/index.d.ts.map +1 -1
- package/dist/connectors/calendar/index.js +19 -28
- package/dist/connectors/calendar/index.js.map +1 -1
- package/dist/connectors/chatwork/index.d.ts.map +1 -1
- package/dist/connectors/chatwork/index.js +7 -3
- package/dist/connectors/chatwork/index.js.map +1 -1
- package/dist/connectors/config-loader.d.ts +5 -0
- package/dist/connectors/config-loader.d.ts.map +1 -1
- package/dist/connectors/config-loader.js +8 -0
- package/dist/connectors/config-loader.js.map +1 -1
- package/dist/connectors/discord/index.d.ts.map +1 -1
- package/dist/connectors/discord/index.js +4 -3
- package/dist/connectors/discord/index.js.map +1 -1
- package/dist/connectors/drive/index.d.ts.map +1 -1
- package/dist/connectors/drive/index.js +15 -13
- package/dist/connectors/drive/index.js.map +1 -1
- package/dist/connectors/framework/gws-utils.d.ts +62 -0
- package/dist/connectors/framework/gws-utils.d.ts.map +1 -1
- package/dist/connectors/framework/gws-utils.js +133 -3
- package/dist/connectors/framework/gws-utils.js.map +1 -1
- package/dist/connectors/framework/types.d.ts +24 -0
- package/dist/connectors/framework/types.d.ts.map +1 -1
- package/dist/connectors/framework/types.js +9 -0
- package/dist/connectors/framework/types.js.map +1 -1
- package/dist/connectors/gmail/index.d.ts.map +1 -1
- package/dist/connectors/gmail/index.js +14 -13
- package/dist/connectors/gmail/index.js.map +1 -1
- package/dist/connectors/index.d.ts +4 -4
- package/dist/connectors/index.d.ts.map +1 -1
- package/dist/connectors/index.js +4 -10
- package/dist/connectors/index.js.map +1 -1
- package/dist/connectors/notion/index.d.ts.map +1 -1
- package/dist/connectors/notion/index.js +5 -3
- package/dist/connectors/notion/index.js.map +1 -1
- package/dist/connectors/sheets/index.d.ts.map +1 -1
- package/dist/connectors/sheets/index.js +17 -12
- package/dist/connectors/sheets/index.js.map +1 -1
- package/dist/connectors/slack/index.d.ts.map +1 -1
- package/dist/connectors/slack/index.js +9 -3
- package/dist/connectors/slack/index.js.map +1 -1
- package/dist/connectors/telegram/index.d.ts.map +1 -1
- package/dist/connectors/telegram/index.js +4 -3
- package/dist/connectors/telegram/index.js.map +1 -1
- package/dist/connectors/trello/index.d.ts +1 -1
- package/dist/connectors/trello/index.d.ts.map +1 -1
- package/dist/connectors/trello/index.js +10 -5
- package/dist/connectors/trello/index.js.map +1 -1
- package/dist/gateways/discord.d.ts +6 -0
- package/dist/gateways/discord.d.ts.map +1 -1
- package/dist/gateways/discord.js +38 -27
- package/dist/gateways/discord.js.map +1 -1
- package/dist/gateways/slack.d.ts +6 -0
- package/dist/gateways/slack.d.ts.map +1 -1
- package/dist/gateways/slack.js +38 -27
- package/dist/gateways/slack.js.map +1 -1
- package/dist/gateways/telegram-commands.d.ts +124 -0
- package/dist/gateways/telegram-commands.d.ts.map +1 -0
- package/dist/gateways/telegram-commands.js +131 -0
- package/dist/gateways/telegram-commands.js.map +1 -0
- package/dist/gateways/telegram.d.ts +20 -0
- package/dist/gateways/telegram.d.ts.map +1 -1
- package/dist/gateways/telegram.js +138 -34
- package/dist/gateways/telegram.js.map +1 -1
- package/dist/gateways/turn-contract.d.ts +11 -0
- package/dist/gateways/turn-contract.d.ts.map +1 -1
- package/dist/runtime/action-surface.d.ts +1 -1
- package/dist/runtime/action-surface.d.ts.map +1 -1
- package/dist/runtime/action-surface.js +10 -0
- package/dist/runtime/action-surface.js.map +1 -1
- package/dist/runtime/backend-security.d.ts +23 -2
- package/dist/runtime/backend-security.d.ts.map +1 -1
- package/dist/runtime/backend-security.js +50 -5
- package/dist/runtime/backend-security.js.map +1 -1
- package/dist/runtime/member-paths.d.ts +15 -7
- package/dist/runtime/member-paths.d.ts.map +1 -1
- package/dist/runtime/member-paths.js +64 -12
- package/dist/runtime/member-paths.js.map +1 -1
- package/dist/runtime/member-session.d.ts +4 -0
- package/dist/runtime/member-session.d.ts.map +1 -1
- package/dist/runtime/member-session.js +23 -2
- package/dist/runtime/member-session.js.map +1 -1
- package/dist/runtime/native-session.d.ts +2 -0
- package/dist/runtime/native-session.d.ts.map +1 -1
- package/dist/runtime/native-session.js +1 -0
- package/dist/runtime/native-session.js.map +1 -1
- package/dist/runtime/owner-runtime.d.ts +63 -1
- package/dist/runtime/owner-runtime.d.ts.map +1 -1
- package/dist/runtime/owner-runtime.js +180 -3
- package/dist/runtime/owner-runtime.js.map +1 -1
- package/dist/runtime/owner-system-prompt.d.ts.map +1 -1
- package/dist/runtime/owner-system-prompt.js +0 -1
- package/dist/runtime/owner-system-prompt.js.map +1 -1
- package/dist/runtime/principal-access.d.ts +2 -0
- package/dist/runtime/principal-access.d.ts.map +1 -1
- package/dist/runtime/principal-access.js +7 -0
- package/dist/runtime/principal-access.js.map +1 -1
- package/dist/runtime/principal-sessions.d.ts.map +1 -1
- package/dist/runtime/principal-sessions.js +8 -1
- package/dist/runtime/principal-sessions.js.map +1 -1
- package/dist/runtime/record-orders.d.ts.map +1 -1
- package/dist/runtime/record-orders.js +3 -29
- package/dist/runtime/record-orders.js.map +1 -1
- package/dist/runtime/report-scheduler.d.ts +9 -2
- package/dist/runtime/report-scheduler.d.ts.map +1 -1
- package/dist/runtime/report-scheduler.js +37 -10
- package/dist/runtime/report-scheduler.js.map +1 -1
- package/dist/runtime/stimulus-delivery.d.ts +3 -0
- package/dist/runtime/stimulus-delivery.d.ts.map +1 -1
- package/dist/runtime/stimulus-delivery.js +19 -3
- package/dist/runtime/stimulus-delivery.js.map +1 -1
- package/dist/runtime/tools-paths.d.ts +7 -0
- package/dist/runtime/tools-paths.d.ts.map +1 -1
- package/dist/runtime/tools-paths.js +11 -5
- package/dist/runtime/tools-paths.js.map +1 -1
- package/dist/runtime/upkeep-close.d.ts +17 -0
- package/dist/runtime/upkeep-close.d.ts.map +1 -0
- package/dist/runtime/upkeep-close.js +127 -0
- package/dist/runtime/upkeep-close.js.map +1 -0
- package/dist/runtime/upkeep-orders.d.ts +124 -0
- package/dist/runtime/upkeep-orders.d.ts.map +1 -0
- package/dist/runtime/upkeep-orders.js +310 -0
- package/dist/runtime/upkeep-orders.js.map +1 -0
- package/dist/runtime/upkeep-reconcile.d.ts +25 -0
- package/dist/runtime/upkeep-reconcile.d.ts.map +1 -0
- package/dist/runtime/upkeep-reconcile.js +231 -0
- package/dist/runtime/upkeep-reconcile.js.map +1 -0
- package/dist/runtime/upkeep-writeback.d.ts +22 -0
- package/dist/runtime/upkeep-writeback.d.ts.map +1 -0
- package/dist/runtime/upkeep-writeback.js +203 -0
- package/dist/runtime/upkeep-writeback.js.map +1 -0
- package/dist/utils/untrusted-content.d.ts.map +1 -1
- package/dist/utils/untrusted-content.js +8 -1
- package/dist/utils/untrusted-content.js.map +1 -1
- package/install/install.sh +457 -0
- package/install/node.json +9 -0
- package/package.json +3 -4
|
@@ -33,6 +33,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.waitForBoot = waitForBoot;
|
|
37
|
+
exports.viewerHealth = viewerHealth;
|
|
36
38
|
exports.runInit = runInit;
|
|
37
39
|
const node_crypto_1 = require("node:crypto");
|
|
38
40
|
const node_fs_1 = require("node:fs");
|
|
@@ -40,384 +42,593 @@ const node_os_1 = require("node:os");
|
|
|
40
42
|
const node_path_1 = require("node:path");
|
|
41
43
|
const yaml = __importStar(require("js-yaml"));
|
|
42
44
|
const config_js_1 = require("../../runtime/config.js");
|
|
45
|
+
const member_paths_js_1 = require("../../runtime/member-paths.js");
|
|
46
|
+
const tools_paths_js_1 = require("../../runtime/tools-paths.js");
|
|
47
|
+
const backend_security_js_1 = require("../../runtime/backend-security.js");
|
|
48
|
+
const viewer_server_js_1 = require("../../api/viewer-server.js");
|
|
43
49
|
const document_toolkit_js_1 = require("../../tools/document-toolkit.js");
|
|
44
|
-
const tools_js_1 = require("./tools.js");
|
|
45
50
|
const launch_files_js_1 = require("../launch-files.js");
|
|
46
51
|
const prompt_js_1 = require("../prompt.js");
|
|
47
52
|
const secrets_js_1 = require("../secrets.js");
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
];
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
trello: ['MAMA_TRELLO_KEY', 'MAMA_TRELLO_TOKEN'],
|
|
68
|
-
kagemusha: [],
|
|
69
|
-
calendar: [],
|
|
70
|
-
gmail: [],
|
|
71
|
-
drive: [],
|
|
72
|
-
sheets: [],
|
|
73
|
-
notion: ['MAMA_NOTION_TOKEN'],
|
|
74
|
-
obsidian: [],
|
|
75
|
-
discord: ['MAMA_DISCORD_TOKEN'],
|
|
76
|
-
telegram: ['MAMA_TELEGRAM_SOURCE_TOKEN'],
|
|
77
|
-
imessage: [],
|
|
78
|
-
'claude-code': [],
|
|
79
|
-
};
|
|
80
|
-
const gwsConnectorNames = new Set(['calendar', 'gmail', 'drive', 'sheets']);
|
|
81
|
-
const noSecretConnectorNames = new Set(['kagemusha', 'obsidian', 'imessage', 'claude-code']);
|
|
82
|
-
async function yes(prompt, label) {
|
|
83
|
-
const value = (await prompt.text(`${label} [y/N]`)).trim().toLowerCase();
|
|
84
|
-
if (['', 'n', 'no'].includes(value))
|
|
85
|
-
return false;
|
|
86
|
-
if (['y', 'yes'].includes(value))
|
|
87
|
-
return true;
|
|
88
|
-
throw new prompt_js_1.CliInputError('Answer yes or no.');
|
|
89
|
-
}
|
|
90
|
-
async function text(prompt, label) {
|
|
91
|
-
return (0, prompt_js_1.nonblankLine)(await prompt.text(label)).trim();
|
|
53
|
+
const diagnostics_js_1 = require("../runtime/diagnostics.js");
|
|
54
|
+
const setup_page_js_1 = require("../runtime/setup-page.js");
|
|
55
|
+
const default_roots_js_1 = require("../runtime/default-roots.js");
|
|
56
|
+
const telegram_pairing_js_1 = require("../runtime/telegram-pairing.js");
|
|
57
|
+
const init_state_js_1 = require("../runtime/init-state.js");
|
|
58
|
+
const daemon_control_js_1 = require("../runtime/daemon-control.js");
|
|
59
|
+
const systemd_service_js_1 = require("../runtime/systemd-service.js");
|
|
60
|
+
const daemon_js_1 = require("./daemon.js");
|
|
61
|
+
const init_questions_js_1 = require("./init-questions.js");
|
|
62
|
+
const init_section_js_1 = require("./init-section.js");
|
|
63
|
+
const register_owner_js_1 = require("./register-owner.js");
|
|
64
|
+
const service_js_1 = require("./service.js");
|
|
65
|
+
/** What init says to do about a source's tool the service cannot find. */
|
|
66
|
+
const SOURCE_TOOL_NEXT = 'Do that now; if setup stops waiting for MAMA to start, running the install command again continues it.';
|
|
67
|
+
function requiredVariable(env, name) {
|
|
68
|
+
const value = env[name]?.trim();
|
|
69
|
+
if (!value)
|
|
70
|
+
throw new prompt_js_1.CliInputError(`mama init --non-interactive needs ${name}`);
|
|
71
|
+
return (0, prompt_js_1.nonblankLine)(value).trim();
|
|
92
72
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
async function collectConnectors(prompt, secrets) {
|
|
98
|
-
const selected = (await prompt.text(`Connectors to enable (${connectorNames.join(', ')}; comma-separated, blank for none)`)).trim();
|
|
99
|
-
const names = selected === '' ? [] : [...new Set(selected.split(',').map((name) => name.trim()))];
|
|
100
|
-
if (names.some((name) => !connectorNames.includes(name))) {
|
|
101
|
-
throw new prompt_js_1.CliInputError(`Choose connectors from: ${connectorNames.join(', ')}`);
|
|
102
|
-
}
|
|
103
|
-
const config = {};
|
|
104
|
-
for (const name of names) {
|
|
105
|
-
for (const tokenName of connectorSecrets[name])
|
|
106
|
-
secrets[tokenName] = (0, prompt_js_1.nonblankLine)(await prompt.secret(tokenName));
|
|
107
|
-
if (name === 'slack')
|
|
108
|
-
secrets.MAMA_SLACK_APP_TOKEN = (0, prompt_js_1.nonblankLine)(await prompt.secret('MAMA_SLACK_APP_TOKEN'));
|
|
109
|
-
if (name === 'calendar')
|
|
110
|
-
prompt.write('Calendar currently reads the primary calendar only; its source channel id is calendar.');
|
|
111
|
-
if (name === 'gmail') {
|
|
112
|
-
config.gmail = {
|
|
113
|
-
enabled: true,
|
|
114
|
-
pollIntervalMinutes: 5,
|
|
115
|
-
channels: { inbox: { role: 'hub' } },
|
|
116
|
-
auth: { type: 'cli', cli: 'gws', cliAuthCommand: 'gws auth login' },
|
|
117
|
-
};
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
|
-
if (name === 'notion') {
|
|
121
|
-
config.notion = {
|
|
122
|
-
enabled: true,
|
|
123
|
-
pollIntervalMinutes: 5,
|
|
124
|
-
channels: { workspace: { role: 'hub', name: 'Notion workspace' } },
|
|
125
|
-
auth: { type: 'token', tokenName: 'MAMA_NOTION_TOKEN' },
|
|
126
|
-
};
|
|
127
|
-
continue;
|
|
128
|
-
}
|
|
129
|
-
if (name === 'obsidian') {
|
|
130
|
-
const vaultPath = await text(prompt, 'Obsidian vault path');
|
|
131
|
-
config.obsidian = {
|
|
132
|
-
enabled: true,
|
|
133
|
-
pollIntervalMinutes: 5,
|
|
134
|
-
channels: { vault: { role: 'hub', name: 'Vault', vaultPath } },
|
|
135
|
-
auth: { type: 'none' },
|
|
136
|
-
};
|
|
137
|
-
continue;
|
|
138
|
-
}
|
|
139
|
-
if (name === 'claude-code') {
|
|
140
|
-
const selectedProjects = (await text(prompt, 'Claude Code project directory names (comma-separated)'))
|
|
141
|
-
.split(',')
|
|
142
|
-
.map((project) => project.trim());
|
|
143
|
-
if (selectedProjects.some((project) => !project || /[/\\\s]/.test(project))) {
|
|
144
|
-
throw new prompt_js_1.CliInputError('Enter Claude Code project directory names without paths or spaces.');
|
|
145
|
-
}
|
|
146
|
-
const channels = {};
|
|
147
|
-
for (const project of selectedProjects) {
|
|
148
|
-
const alias = await text(prompt, `Display alias for ${project}`);
|
|
149
|
-
channels[project] = { role: 'hub', name: alias };
|
|
150
|
-
}
|
|
151
|
-
config['claude-code'] = {
|
|
152
|
-
enabled: true,
|
|
153
|
-
pollIntervalMinutes: 5,
|
|
154
|
-
channels,
|
|
155
|
-
auth: { type: 'none' },
|
|
156
|
-
};
|
|
157
|
-
continue;
|
|
158
|
-
}
|
|
159
|
-
if (name === 'drive') {
|
|
160
|
-
const folderIds = (await text(prompt, 'Drive folder ids (comma-separated)'))
|
|
161
|
-
.split(',')
|
|
162
|
-
.map((id) => id.trim());
|
|
163
|
-
if (folderIds.some((id) => !id || /\s/.test(id)))
|
|
164
|
-
throw new prompt_js_1.CliInputError('Enter Drive folder ids separated by commas without spaces.');
|
|
165
|
-
config.drive = {
|
|
166
|
-
enabled: true,
|
|
167
|
-
pollIntervalMinutes: 5,
|
|
168
|
-
channels: Object.fromEntries(folderIds.map((folderId) => [folderId, { role: 'hub', folderId }])),
|
|
169
|
-
auth: { type: 'cli', cli: 'gws', cliAuthCommand: 'gws auth login' },
|
|
170
|
-
};
|
|
171
|
-
continue;
|
|
172
|
-
}
|
|
173
|
-
if (name === 'sheets') {
|
|
174
|
-
const spreadsheetId = await text(prompt, 'Google Sheets spreadsheet id');
|
|
175
|
-
const sheetRange = await text(prompt, 'Google Sheets header-and-data range');
|
|
176
|
-
const dataRange = await optionalText(prompt, 'Separate data range (blank to use the full range)');
|
|
177
|
-
config.sheets = {
|
|
178
|
-
enabled: true,
|
|
179
|
-
pollIntervalMinutes: 5,
|
|
180
|
-
channels: {
|
|
181
|
-
spreadsheet: {
|
|
182
|
-
role: 'hub',
|
|
183
|
-
spreadsheetId,
|
|
184
|
-
sheetRange,
|
|
185
|
-
...(dataRange ? { dataRange } : {}),
|
|
186
|
-
},
|
|
187
|
-
},
|
|
188
|
-
auth: { type: 'cli', cli: 'gws', cliAuthCommand: 'gws auth login' },
|
|
189
|
-
};
|
|
190
|
-
continue;
|
|
191
|
-
}
|
|
192
|
-
const ids = (await text(prompt, `${name} ${name === 'trello' ? 'board' : 'channel'} ids (comma-separated ids only)`))
|
|
193
|
-
.split(',')
|
|
194
|
-
.map((id) => id.trim());
|
|
195
|
-
if (ids.some((id) => !id || /\s/.test(id)))
|
|
196
|
-
throw new prompt_js_1.CliInputError('Enter channel ids separated by commas, without display names.');
|
|
197
|
-
if (name === 'calendar' && (ids.length !== 1 || ids[0] !== 'calendar')) {
|
|
198
|
-
throw new prompt_js_1.CliInputError('Calendar supports only the source channel id calendar (primary calendar).');
|
|
199
|
-
}
|
|
200
|
-
config[name] = {
|
|
201
|
-
enabled: true,
|
|
202
|
-
pollIntervalMinutes: 5,
|
|
203
|
-
channels: Object.fromEntries(ids.map((id) => [id, { role: 'hub', ...(name === 'trello' ? { boardId: id } : {}) }])),
|
|
204
|
-
auth: gwsConnectorNames.has(name)
|
|
205
|
-
? { type: 'cli', cli: 'gws', cliAuthCommand: 'gws auth login' }
|
|
206
|
-
: noSecretConnectorNames.has(name)
|
|
207
|
-
? { type: 'none' }
|
|
208
|
-
: { type: 'token', tokenName: connectorSecrets[name].at(-1) },
|
|
209
|
-
};
|
|
73
|
+
function validChatId(chatId) {
|
|
74
|
+
if (!/^-[1-9]\d*$/.test(chatId)) {
|
|
75
|
+
throw new prompt_js_1.CliInputError('Enter a Telegram group or supergroup id (negative); private chats go to personal intake and cannot reach the common session.');
|
|
210
76
|
}
|
|
211
|
-
return
|
|
77
|
+
return chatId;
|
|
212
78
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
const
|
|
79
|
+
function validTelegramId(id) {
|
|
80
|
+
if (!/^[1-9]\d*$/.test(id))
|
|
81
|
+
throw new prompt_js_1.CliInputError('Enter your numeric Telegram user ID (only digits).');
|
|
82
|
+
return id;
|
|
83
|
+
}
|
|
84
|
+
/** The variables start.sh exports for a viewer behind a Cloudflare Access tunnel (service.ts). */
|
|
85
|
+
const TUNNEL_VARIABLES = [
|
|
86
|
+
'MAMA_CF_ACCESS_ISSUER',
|
|
87
|
+
'MAMA_CF_ACCESS_AUD',
|
|
88
|
+
'MAMA_VIEWER_HOSTNAMES',
|
|
89
|
+
'MAMA_VIEWER_OWNER_EMAILS',
|
|
90
|
+
];
|
|
91
|
+
/**
|
|
92
|
+
* The tunnel, from those variables: none set, no tunnel; any set, the issuer, audience and
|
|
93
|
+
* hostname are needed and the owner emails stay optional.
|
|
94
|
+
*/
|
|
95
|
+
function tunnelVariables(env) {
|
|
96
|
+
if (!TUNNEL_VARIABLES.some((name) => env[name]?.trim()))
|
|
97
|
+
return {};
|
|
98
|
+
const issuer = requiredVariable(env, 'MAMA_CF_ACCESS_ISSUER');
|
|
99
|
+
let url;
|
|
233
100
|
try {
|
|
234
|
-
new
|
|
101
|
+
url = new URL(issuer);
|
|
235
102
|
}
|
|
236
103
|
catch {
|
|
237
|
-
throw new prompt_js_1.CliInputError(
|
|
104
|
+
throw new prompt_js_1.CliInputError('MAMA_CF_ACCESS_ISSUER must be an HTTPS origin.');
|
|
238
105
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
106
|
+
if (url.protocol !== 'https:' ||
|
|
107
|
+
url.username ||
|
|
108
|
+
url.password ||
|
|
109
|
+
url.search ||
|
|
110
|
+
url.hash ||
|
|
111
|
+
url.pathname !== '/') {
|
|
112
|
+
throw new prompt_js_1.CliInputError('MAMA_CF_ACCESS_ISSUER must be an HTTPS origin.');
|
|
245
113
|
}
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
enabled: false,
|
|
250
|
-
allowed_channels: [],
|
|
251
|
-
owner_user_ids: [],
|
|
114
|
+
const viewer = {
|
|
115
|
+
MAMA_CF_ACCESS_ISSUER: url.origin,
|
|
116
|
+
MAMA_CF_ACCESS_AUD: requiredVariable(env, 'MAMA_CF_ACCESS_AUD'),
|
|
252
117
|
};
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
118
|
+
const hostname = requiredVariable(env, 'MAMA_VIEWER_HOSTNAMES');
|
|
119
|
+
if (!/^[a-z0-9]+(?:[.-][a-z0-9]+)*$/i.test(hostname))
|
|
120
|
+
throw new prompt_js_1.CliInputError('MAMA_VIEWER_HOSTNAMES must be one hostname, without scheme or path.');
|
|
121
|
+
viewer.MAMA_VIEWER_HOSTNAMES = hostname;
|
|
122
|
+
const emails = env.MAMA_VIEWER_OWNER_EMAILS?.trim();
|
|
123
|
+
if (emails)
|
|
124
|
+
viewer.MAMA_VIEWER_OWNER_EMAILS = (0, prompt_js_1.nonblankLine)(emails);
|
|
125
|
+
return viewer;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* The bot token and the pairing code, entered on the setup page (install.md, W2.4.5b). The token
|
|
129
|
+
* is checked as Telegram answers it; then pairOwner's `ask` is answered by the code's entry, whose
|
|
130
|
+
* reply waits for pairOwner: another `ask` means a wrong code, a return the right one. One process
|
|
131
|
+
* runs from the token to /bind, since the codes and the update offset live in pairOwner's memory.
|
|
132
|
+
*/
|
|
133
|
+
async function pairOnSetupPage(options) {
|
|
134
|
+
const { call, write } = options;
|
|
135
|
+
let taken;
|
|
136
|
+
const tokenTaken = new Promise((resolve) => {
|
|
137
|
+
taken = resolve;
|
|
138
|
+
});
|
|
139
|
+
// pairOwner's first ask: it has dropped what the bot held from before (telegram-pairing.ts).
|
|
140
|
+
let asked;
|
|
141
|
+
let notAsked;
|
|
142
|
+
const firstAsk = new Promise((resolve, reject) => {
|
|
143
|
+
asked = resolve;
|
|
144
|
+
notAsked = reject;
|
|
145
|
+
});
|
|
146
|
+
firstAsk.catch(() => { });
|
|
147
|
+
/** The ask waiting for an entry, and the entry waiting for pairOwner's verdict. */
|
|
148
|
+
let waiting;
|
|
149
|
+
let entry;
|
|
150
|
+
/** pairOwner's last line: after a wrong code, the one that says so. */
|
|
151
|
+
let lastLine = '';
|
|
152
|
+
const page = await (0, setup_page_js_1.openSetupPage)({
|
|
153
|
+
fields: [
|
|
154
|
+
{
|
|
155
|
+
name: 'token',
|
|
156
|
+
label: 'Telegram bot token (from BotFather)',
|
|
157
|
+
secret: true,
|
|
158
|
+
check: async (value) => {
|
|
159
|
+
let token;
|
|
160
|
+
let botUsername;
|
|
161
|
+
try {
|
|
162
|
+
token = (0, prompt_js_1.nonblankLine)(value).trim();
|
|
163
|
+
// The page asks again, so a network failure names the entry, not a rerun.
|
|
164
|
+
await (0, init_questions_js_1.checkTelegram)(token, undefined, call, write, {
|
|
165
|
+
next: 'enter it again on the setup page',
|
|
166
|
+
});
|
|
167
|
+
botUsername = await (0, telegram_pairing_js_1.botName)({ token, call, write });
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
if (error instanceof prompt_js_1.CliInputError)
|
|
171
|
+
write(error.message);
|
|
172
|
+
throw error;
|
|
173
|
+
}
|
|
174
|
+
write(`bot token accepted for @${botUsername}`);
|
|
175
|
+
taken({ token, botUsername });
|
|
176
|
+
// A message sent before pairOwner's first poll would be dropped with the held ones.
|
|
177
|
+
await firstAsk;
|
|
178
|
+
return `The bot token is accepted for @${botUsername}. In Telegram, send @${botUsername} a new private message; it answers with a pairing code.`;
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
name: 'code',
|
|
183
|
+
label: 'Pairing code (from the bot)',
|
|
184
|
+
secret: false,
|
|
185
|
+
check: (value) => new Promise((accept, refuse) => {
|
|
186
|
+
const answer = waiting;
|
|
187
|
+
// The page takes one entry at a time, and asks for the code only after pairOwner did.
|
|
188
|
+
if (answer === undefined)
|
|
189
|
+
throw new Error('a pairing code was entered with none asked for');
|
|
190
|
+
waiting = undefined;
|
|
191
|
+
entry = { accept, refuse };
|
|
192
|
+
answer(value);
|
|
193
|
+
}),
|
|
194
|
+
},
|
|
195
|
+
],
|
|
196
|
+
write,
|
|
197
|
+
platform: options.platform,
|
|
198
|
+
...(options.openRun ? { openRun: options.openRun } : {}),
|
|
199
|
+
...(options.lifetimeMs === undefined ? {} : { lifetimeMs: options.lifetimeMs }),
|
|
200
|
+
});
|
|
201
|
+
try {
|
|
202
|
+
const { token, botUsername } = await Promise.race([tokenTaken, page.stopped]);
|
|
203
|
+
const owner = await (0, telegram_pairing_js_1.pairOwner)({
|
|
204
|
+
token,
|
|
205
|
+
call,
|
|
206
|
+
botUsername,
|
|
207
|
+
write: (line) => {
|
|
208
|
+
lastLine = line;
|
|
209
|
+
write(line);
|
|
210
|
+
},
|
|
211
|
+
ask: (_label, signal) => {
|
|
212
|
+
// pairOwner asks again only after a wrong code, and wrote why just before.
|
|
213
|
+
entry?.refuse(new prompt_js_1.CliInputError(lastLine));
|
|
214
|
+
entry = undefined;
|
|
215
|
+
asked();
|
|
216
|
+
return new Promise((resolve, reject) => {
|
|
217
|
+
waiting = resolve;
|
|
218
|
+
signal.addEventListener('abort', () => reject(new Error('pairing stopped')), {
|
|
219
|
+
once: true,
|
|
220
|
+
});
|
|
221
|
+
page.stopped.catch(reject);
|
|
222
|
+
});
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
write('pairing code accepted');
|
|
226
|
+
entry?.accept(`The pairing code is accepted. Add @${botUsername} to the group for common work as an administrator, then send /bind in that group. This page has closed.`);
|
|
227
|
+
const group = await (0, telegram_pairing_js_1.bindCommonGroup)({
|
|
228
|
+
token,
|
|
229
|
+
call,
|
|
230
|
+
write,
|
|
231
|
+
botUsername,
|
|
232
|
+
ownerId: owner.ownerId,
|
|
233
|
+
offset: owner.offset,
|
|
234
|
+
earlier: owner.groupMessages,
|
|
235
|
+
});
|
|
236
|
+
await (0, telegram_pairing_js_1.confirmUpdates)({ token, call, write, offset: group.offset });
|
|
237
|
+
// The bot's post in the group proved the token; the group's privacy and membership are only
|
|
238
|
+
// reported, and doctor checks them again later.
|
|
239
|
+
(0, init_questions_js_1.report)(await (0, diagnostics_js_1.telegramCheck)(token, group.chatId, call), write);
|
|
240
|
+
return { token, ownerId: owner.ownerId, chatId: group.chatId };
|
|
263
241
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
};
|
|
270
|
-
if (slackEnabled) {
|
|
271
|
-
secrets.MAMA_SLACK_TOKEN ??= (0, prompt_js_1.nonblankLine)(await prompt.secret('Slack bot token'));
|
|
272
|
-
secrets.MAMA_SLACK_APP_TOKEN ??= (0, prompt_js_1.nonblankLine)(await prompt.secret('Slack Socket Mode app token'));
|
|
273
|
-
const channel = await text(prompt, 'Slack owner channel id');
|
|
274
|
-
const user = await text(prompt, 'Slack owner user id');
|
|
275
|
-
slackConfig = {
|
|
276
|
-
enabled: true,
|
|
277
|
-
owner_channel_id: channel,
|
|
278
|
-
allowed_channels: [channel],
|
|
279
|
-
owner_user_ids: [user],
|
|
280
|
-
};
|
|
242
|
+
catch (error) {
|
|
243
|
+
// An entry still waiting gets the sentence that ends the setup.
|
|
244
|
+
notAsked(error);
|
|
245
|
+
entry?.refuse(error);
|
|
246
|
+
throw error;
|
|
281
247
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
const jevKeyPath = (0, node_path_1.join)(root, 'jev-key');
|
|
285
|
-
const jevEnabled = await yes(prompt, 'Use Jev (TypeSafe) so the agent can judge many messages or items without reading them all? Owner text in those calls goes to the Jev service');
|
|
286
|
-
let jevKey;
|
|
287
|
-
if (jevEnabled && !(0, node_fs_1.existsSync)(jevKeyPath)) {
|
|
288
|
-
prompt.write('Get a Jev API key from TypeSafe: https://docs.typesafe.ai');
|
|
289
|
-
jevKey = (0, prompt_js_1.nonblankLine)(await prompt.secret('Jev API key'));
|
|
248
|
+
finally {
|
|
249
|
+
page.close();
|
|
290
250
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* The setup's answers. Those from the environment are all checked before the setup page opens,
|
|
254
|
+
* so a refused one does not make the owner pair again. The page takes the bot token and the
|
|
255
|
+
* pairing code: a secret never comes from a variable or a flag, which `ps` shows, and no Telegram
|
|
256
|
+
* ID is typed. Sources, Discord, Slack and Jev are set up afterwards with `mama init --section`.
|
|
257
|
+
*/
|
|
258
|
+
async function environmentAnswers(env, defaults, checks) {
|
|
259
|
+
const backend = requiredVariable(env, 'MAMA_INIT_BACKEND');
|
|
260
|
+
if (backend !== 'claude' && backend !== 'codex')
|
|
261
|
+
throw new prompt_js_1.CliInputError('MAMA_INIT_BACKEND must be claude or codex.');
|
|
262
|
+
checks.backend(backend);
|
|
263
|
+
const model = requiredVariable(env, 'MAMA_INIT_MODEL');
|
|
264
|
+
const timezone = (0, init_questions_js_1.validTimezone)(env.MAMA_INIT_TIMEZONE?.trim() || Intl.DateTimeFormat().resolvedOptions().timeZone);
|
|
265
|
+
const memberAnswer = env.MAMA_INIT_MEMBER_ROOT?.trim() || defaults?.memberRoot;
|
|
266
|
+
const toolsAnswer = env.MAMA_INIT_TOOLS_ROOT?.trim() || defaults?.toolsRoot;
|
|
267
|
+
if (!memberAnswer || !toolsAnswer)
|
|
268
|
+
throw new prompt_js_1.CliInputError('This platform has no default folders; set MAMA_INIT_MEMBER_ROOT and MAMA_INIT_TOOLS_ROOT.');
|
|
269
|
+
const { memberRoot, toolsRoot } = checks.roots(memberAnswer, toolsAnswer, {
|
|
270
|
+
member: !env.MAMA_INIT_MEMBER_ROOT?.trim(),
|
|
271
|
+
tools: !env.MAMA_INIT_TOOLS_ROOT?.trim(),
|
|
272
|
+
});
|
|
273
|
+
const viewer = tunnelVariables(env);
|
|
274
|
+
const paired = await checks.page();
|
|
275
|
+
return {
|
|
276
|
+
backend,
|
|
277
|
+
model,
|
|
278
|
+
timezone,
|
|
279
|
+
secrets: { MAMA_TELEGRAM_TOKEN: paired.token },
|
|
280
|
+
chatId: validChatId(paired.chatId),
|
|
281
|
+
ownerTelegramId: validTelegramId(paired.ownerId),
|
|
282
|
+
memberRoot,
|
|
283
|
+
toolsRoot,
|
|
284
|
+
viewer,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Wait until the started daemon reports its boot finished, then until the viewer answers. With a
|
|
289
|
+
* `version`, the daemon must be that version: `mama update` waits for the one it installed.
|
|
290
|
+
*/
|
|
291
|
+
async function waitForBoot(options) {
|
|
292
|
+
const started = Date.now();
|
|
293
|
+
let reported = started;
|
|
294
|
+
let stage = 'not started';
|
|
295
|
+
const next = options.next ?? 'running the install command again continues from here';
|
|
296
|
+
for (;;) {
|
|
297
|
+
// A daemon busy booting may miss one status deadline; that is no answer yet, not a failure.
|
|
298
|
+
const reply = await options.status().catch(() => null);
|
|
299
|
+
if (reply?.state === 'running' && reply.reply.ok === true) {
|
|
300
|
+
const status = (reply.reply.status ?? {});
|
|
301
|
+
stage = String(status.stage);
|
|
302
|
+
if (options.version !== undefined && status.version !== options.version) {
|
|
303
|
+
stage = `${stage} at version ${String(status.version)}, not ${options.version}`;
|
|
304
|
+
}
|
|
305
|
+
else if (stage === 'running')
|
|
306
|
+
break;
|
|
296
307
|
}
|
|
297
|
-
|
|
298
|
-
throw new prompt_js_1.CliInputError(
|
|
308
|
+
if (Date.now() - started >= options.timeoutMs) {
|
|
309
|
+
throw new prompt_js_1.CliInputError(`MAMA did not finish starting (last stage: ${stage}). mama logs shows why; ${next}.`);
|
|
299
310
|
}
|
|
300
|
-
if (
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
url.search ||
|
|
304
|
-
url.hash ||
|
|
305
|
-
url.pathname !== '/') {
|
|
306
|
-
throw new prompt_js_1.CliInputError('Access issuer must be an HTTPS origin.');
|
|
311
|
+
if (Date.now() - reported >= 30_000) {
|
|
312
|
+
options.write(`still starting (stage: ${stage})`);
|
|
313
|
+
reported = Date.now();
|
|
307
314
|
}
|
|
308
|
-
|
|
309
|
-
viewer.MAMA_CF_ACCESS_AUD = await text(prompt, 'Access audience');
|
|
310
|
-
const hostname = await text(prompt, 'Viewer hostname (no scheme or path)');
|
|
311
|
-
if (!/^[a-z0-9]+(?:[.-][a-z0-9]+)*$/i.test(hostname))
|
|
312
|
-
throw new prompt_js_1.CliInputError('Enter a hostname without scheme or path.');
|
|
313
|
-
viewer.MAMA_VIEWER_HOSTNAMES = hostname;
|
|
314
|
-
const emails = (await prompt.text('Viewer owner emails for access monitoring (comma-separated, optional)')).trim();
|
|
315
|
-
if (emails)
|
|
316
|
-
viewer.MAMA_VIEWER_OWNER_EMAILS = (0, prompt_js_1.nonblankLine)(emails);
|
|
315
|
+
await new Promise((resolve) => setTimeout(resolve, options.pollMs));
|
|
317
316
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
if (installLaunchAgent && (0, node_fs_1.existsSync)(plistPath))
|
|
321
|
-
throw new prompt_js_1.CliInputError('com.mama.server.plist already exists; init will not overwrite it.');
|
|
322
|
-
const config = (0, config_js_1.parseConfig)({
|
|
323
|
-
version: 1,
|
|
324
|
-
timezone,
|
|
325
|
-
tools_root: options.toolsRoot ?? (0, tools_js_1.defaultToolsRoot)(options.platform, home),
|
|
326
|
-
agent: {
|
|
327
|
-
backend,
|
|
328
|
-
model,
|
|
329
|
-
effort: 'medium',
|
|
330
|
-
max_turns: 100,
|
|
331
|
-
// A turn is stopped after 10 minutes without progress, or after an hour in all
|
|
332
|
-
// (owner, 2026-10-03); a full report on 2026-09-29 was cut off at 300 s while working.
|
|
333
|
-
timeout: 600_000,
|
|
334
|
-
max_turn_ms: 3_600_000,
|
|
335
|
-
run_token_budget: 0,
|
|
336
|
-
},
|
|
337
|
-
database: { path: (0, node_path_1.join)(root, 'memory.db') },
|
|
338
|
-
logging: { level: 'info', file: (0, node_path_1.join)(root, 'logs', 'daemon.log') },
|
|
339
|
-
telegram: {
|
|
340
|
-
enabled: true,
|
|
341
|
-
owner_chat_id: chatId,
|
|
342
|
-
allowed_chats: [chatId],
|
|
343
|
-
polling: true,
|
|
344
|
-
},
|
|
345
|
-
discord: discordConfig,
|
|
346
|
-
slack: slackConfig,
|
|
347
|
-
delivery: { reports: 'telegram', notifications: 'telegram', security_alerts: 'telegram' },
|
|
348
|
-
wiki: {
|
|
349
|
-
enabled: true,
|
|
350
|
-
vaultPath: (0, node_path_1.join)(root, 'workspace'),
|
|
351
|
-
wikiDir: (0, node_path_1.join)(root, 'workspace', 'wiki'),
|
|
352
|
-
},
|
|
353
|
-
...(jevEnabled ? { jev: { enabled: true } } : {}),
|
|
354
|
-
}, { home });
|
|
355
|
-
const locate = options.findExecutable ?? launch_files_js_1.findExecutable;
|
|
356
|
-
const backendPath = locate(backend);
|
|
357
|
-
const gwsPath = locate('gws');
|
|
358
|
-
const script = (0, launch_files_js_1.startScript)({
|
|
359
|
-
home,
|
|
360
|
-
viewer,
|
|
361
|
-
logLevel: config.logging.level,
|
|
362
|
-
nodePath: options.nodePath ?? (0, launch_files_js_1.stableNodePath)(process.execPath),
|
|
363
|
-
cliPath: options.cliPath ?? (0, node_path_1.join)(__dirname, '..', 'index.js'),
|
|
364
|
-
executablePaths: [backendPath, gwsPath].filter((path) => path !== undefined),
|
|
365
|
-
});
|
|
366
|
-
secrets.MAMA_AUTH_TOKEN = (0, node_crypto_1.randomBytes)(32).toString('hex');
|
|
367
|
-
(0, node_fs_1.mkdirSync)(root, { recursive: true, mode: 0o700 });
|
|
368
|
-
(0, node_fs_1.mkdirSync)((0, node_path_1.join)(root, 'logs'), { recursive: true, mode: 0o700 });
|
|
369
|
-
(0, node_fs_1.mkdirSync)((0, node_path_1.join)(root, 'workspace', 'wiki'), { recursive: true, mode: 0o700 });
|
|
370
|
-
// Finish all prompts before any write, and publish config last as the setup completion marker.
|
|
371
|
-
(0, secrets_js_1.updateSecrets)(home, secrets);
|
|
372
|
-
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(root, 'connectors.json'), `${JSON.stringify(connectors, null, 2)}\n`, {
|
|
373
|
-
flag: 'wx',
|
|
374
|
-
mode: 0o600,
|
|
375
|
-
});
|
|
376
|
-
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(root, 'start.sh'), script, { flag: 'wx', mode: 0o700 });
|
|
377
|
-
if (jevKey !== undefined)
|
|
378
|
-
(0, node_fs_1.writeFileSync)(jevKeyPath, `${jevKey}\n`, { flag: 'wx', mode: 0o600 });
|
|
379
|
-
if (installLaunchAgent) {
|
|
380
|
-
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(plistPath), { recursive: true });
|
|
381
|
-
(0, node_fs_1.writeFileSync)(plistPath, (0, launch_files_js_1.launchAgent)(home), { flag: 'wx', mode: 0o600 });
|
|
317
|
+
if (!(await options.health())) {
|
|
318
|
+
throw new prompt_js_1.CliInputError(`MAMA started but its viewer does not answer /health; mama doctor checks the install${options.next === undefined ? '' : `; ${options.next}`}.`);
|
|
382
319
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
if (!gwsPath)
|
|
391
|
-
prompt.write('Install gws and add its bin directory to PATH in ~/.mama/start.sh.');
|
|
392
|
-
prompt.write('Log in with the Google scopes your selected connectors need: gws auth login.');
|
|
320
|
+
}
|
|
321
|
+
const DEFAULT_VIEWER_PORT = (0, viewer_server_js_1.resolveApiPort)(undefined);
|
|
322
|
+
/** The first viewer port nothing listens on, from the default up. */
|
|
323
|
+
async function freeViewerPort(answers) {
|
|
324
|
+
for (let port = DEFAULT_VIEWER_PORT; port < DEFAULT_VIEWER_PORT + 50; port += 1) {
|
|
325
|
+
if (!(await answers(port, '127.0.0.1')))
|
|
326
|
+
return port;
|
|
393
327
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
328
|
+
throw new prompt_js_1.CliInputError(`Ports ${DEFAULT_VIEWER_PORT}-${DEFAULT_VIEWER_PORT + 49} are all in use; free one, then run the install command again.`);
|
|
329
|
+
}
|
|
330
|
+
async function viewerHealth(port) {
|
|
331
|
+
try {
|
|
332
|
+
const response = await fetch(`http://127.0.0.1:${port}/health`, {
|
|
333
|
+
signal: AbortSignal.timeout(5_000),
|
|
334
|
+
});
|
|
335
|
+
return response.ok;
|
|
336
|
+
}
|
|
337
|
+
catch {
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
/** How much longer than the daemon's own turn limit init waits for the backend check. */
|
|
342
|
+
const PROBE_MARGIN_MS = 60_000;
|
|
343
|
+
const initUsage = `Usage: mama init --non-interactive | mama init --section <${init_section_js_1.initSections.join('|')}> | mama init --section connectors --non-interactive`;
|
|
344
|
+
async function runInit(args = [], options = {}) {
|
|
345
|
+
if (args[0] === '--section') {
|
|
346
|
+
const nonInteractive = args.length === 3 && args[2] === '--non-interactive';
|
|
347
|
+
if (args.length !== 2 && !nonInteractive)
|
|
348
|
+
throw new prompt_js_1.CliInputError(initUsage);
|
|
349
|
+
return (0, init_section_js_1.runInitSection)(args[1], options, nonInteractive);
|
|
406
350
|
}
|
|
351
|
+
// The setup reads no terminal: the person's agent runs it (owner-setup.md).
|
|
352
|
+
if (args.length !== 1 || args[0] !== '--non-interactive')
|
|
353
|
+
throw new prompt_js_1.CliInputError(initUsage);
|
|
354
|
+
const write = (line) => (options.prompt ? options.prompt.write(line) : console.log(line));
|
|
355
|
+
const home = options.home ?? (0, node_os_1.homedir)();
|
|
356
|
+
const platform = options.platform ?? process.platform;
|
|
357
|
+
const root = (0, node_path_1.join)(home, '.mama');
|
|
358
|
+
const configPath = (0, node_path_1.join)(root, 'config.yaml');
|
|
359
|
+
const lockPaths = (0, daemon_control_js_1.daemonControlPaths)(root);
|
|
360
|
+
// While init writes the install, no daemon, restore or second init may run beside it, and the
|
|
361
|
+
// step record is read and repaired under the same lock, so a second init cannot act on one the
|
|
362
|
+
// first is still writing. A setup with only the service step left takes no lock: that step
|
|
363
|
+
// writes no install files, and the daemon a failed wait left starting holds the lock.
|
|
364
|
+
const peek = (0, init_state_js_1.readInitState)(root);
|
|
365
|
+
// An install set up before init kept its steps, or by hand: init will not take it over. Said
|
|
366
|
+
// before the lock, since that install's running daemon holds it.
|
|
367
|
+
if (peek === null && (0, node_fs_1.existsSync)(configPath))
|
|
368
|
+
throw new prompt_js_1.CliInputError('config.yaml already exists; init will not overwrite it. mama doctor checks this install.');
|
|
369
|
+
const serviceOnly = peek !== null &&
|
|
370
|
+
['config', 'owner', 'toolkit'].every((step) => peek.done.includes(step));
|
|
371
|
+
let lock = (0, node_fs_1.existsSync)(root) && !serviceOnly ? (0, daemon_control_js_1.acquireInstallLock)(lockPaths, 'init') : undefined;
|
|
372
|
+
let progress = { version: 1, done: [] };
|
|
373
|
+
const mark = (step) => {
|
|
374
|
+
progress.done.push(step);
|
|
375
|
+
(0, init_state_js_1.writeInitState)(root, progress);
|
|
376
|
+
};
|
|
377
|
+
const done = (step) => progress.done.includes(step);
|
|
407
378
|
try {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
379
|
+
let state = lock ? (0, init_state_js_1.readInitState)(root) : peek;
|
|
380
|
+
// The record is written before the setup's files, so one without the config step means an
|
|
381
|
+
// init stopped part-way through writing them. The files only init creates are removed and the
|
|
382
|
+
// step starts over; auth.env is merged, and a Codex login made before init stays.
|
|
383
|
+
if (state !== null && !state.done.includes('config')) {
|
|
384
|
+
for (const name of ['config.yaml', 'connectors.json', 'jev-key'])
|
|
385
|
+
(0, node_fs_1.rmSync)((0, node_path_1.join)(root, name), { force: true });
|
|
386
|
+
write('An earlier mama init stopped while writing its files; that step starts over.');
|
|
387
|
+
state = null;
|
|
388
|
+
}
|
|
389
|
+
if ((0, node_fs_1.existsSync)(configPath)) {
|
|
390
|
+
// An install set up before init kept its steps, or by hand: init will not take it over.
|
|
391
|
+
if (state === null)
|
|
392
|
+
throw new prompt_js_1.CliInputError('config.yaml already exists; init will not overwrite it.');
|
|
393
|
+
// A status, not a refusal: running the install command again on a set-up install ends here.
|
|
394
|
+
if ((0, init_state_js_1.initComplete)(state)) {
|
|
395
|
+
write('MAMA is already set up here; mama doctor checks it, mama service restart restarts it and mama update updates it.');
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
write(`continuing setup: done so far: ${state.done.join(', ')}`);
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
state = null;
|
|
402
|
+
// A partial manual setup must also be reviewed by the owner before replacing files.
|
|
403
|
+
for (const name of ['connectors.json', 'start.sh']) {
|
|
404
|
+
if ((0, node_fs_1.existsSync)((0, node_path_1.join)(root, name)))
|
|
405
|
+
throw new prompt_js_1.CliInputError(`${name} already exists; init will not overwrite it.`);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
progress = state ?? { version: 1, done: [] };
|
|
409
|
+
const locate = options.findExecutable ?? launch_files_js_1.findExecutable;
|
|
410
|
+
// Questions and checks come first, so a refused or cancelled setup leaves nothing in the MAMA
|
|
411
|
+
// home. The two folders are made as soon as they are named: a folder that cannot be this
|
|
412
|
+
// user's alone is refused before pairing, which would otherwise have to be done again.
|
|
413
|
+
let answers;
|
|
414
|
+
if (!done('config')) {
|
|
415
|
+
const defaults = (0, default_roots_js_1.defaultRoots)({
|
|
416
|
+
platform,
|
|
413
417
|
home,
|
|
414
|
-
|
|
415
|
-
write: prompt.write,
|
|
418
|
+
...(options.user === undefined ? {} : { user: options.user }),
|
|
416
419
|
});
|
|
420
|
+
const call = options.telegramCall ?? diagnostics_js_1.defaultTelegramCall;
|
|
421
|
+
const checks = {
|
|
422
|
+
// A signed-out backend's login is named, with the backend's full path; the agent runs it.
|
|
423
|
+
backend: (name) => (0, init_questions_js_1.checkBackend)(name, (0, node_path_1.join)(root, '.codex'), locate, options.probeRun, 'run the install command again'),
|
|
424
|
+
roots: (memberAnswer, toolsAnswer, isDefault) => {
|
|
425
|
+
const memberRoot = (0, node_path_1.resolve)(memberAnswer);
|
|
426
|
+
const toolsRoot = (0, node_path_1.resolve)(toolsAnswer);
|
|
427
|
+
try {
|
|
428
|
+
(0, member_paths_js_1.validateMemberRoot)(memberRoot, [home, root]);
|
|
429
|
+
(0, tools_paths_js_1.assertRootAncestors)((0, backend_security_js_1.physicalReadPath)(memberRoot), 'member_root');
|
|
430
|
+
(0, tools_paths_js_1.validateToolsRoot)(toolsRoot, [home, root, memberRoot]);
|
|
431
|
+
}
|
|
432
|
+
catch (error) {
|
|
433
|
+
throw new prompt_js_1.CliInputError(error.message);
|
|
434
|
+
}
|
|
435
|
+
for (const [folder, ownParent] of [
|
|
436
|
+
[memberRoot, isDefault.member],
|
|
437
|
+
[toolsRoot, isDefault.tools],
|
|
438
|
+
]) {
|
|
439
|
+
(0, default_roots_js_1.prepareRoot)(folder, {
|
|
440
|
+
ownParent,
|
|
441
|
+
...(options.uid === undefined ? {} : { uid: options.uid }),
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
return { memberRoot, toolsRoot };
|
|
445
|
+
},
|
|
446
|
+
page: () => pairOnSetupPage({
|
|
447
|
+
call,
|
|
448
|
+
write,
|
|
449
|
+
platform,
|
|
450
|
+
...(options.openRun ? { openRun: options.openRun } : {}),
|
|
451
|
+
...(options.setupPageMs === undefined ? {} : { lifetimeMs: options.setupPageMs }),
|
|
452
|
+
}),
|
|
453
|
+
};
|
|
454
|
+
answers = await environmentAnswers(options.env ?? process.env, defaults, checks);
|
|
455
|
+
}
|
|
456
|
+
if (lock === undefined && !serviceOnly) {
|
|
457
|
+
// A new install: the MAMA home is made only once the questions are answered, and another
|
|
458
|
+
// init may have started one meanwhile.
|
|
459
|
+
(0, node_fs_1.mkdirSync)(root, { recursive: true, mode: 0o700 });
|
|
460
|
+
lock = (0, daemon_control_js_1.acquireInstallLock)(lockPaths, 'init');
|
|
461
|
+
if ((0, init_state_js_1.readInitState)(root) !== null || (0, node_fs_1.existsSync)(configPath))
|
|
462
|
+
throw new prompt_js_1.CliInputError('Another mama init set up here meanwhile; run the install command again.');
|
|
463
|
+
}
|
|
464
|
+
// A restore sets the launch files aside too, so even the service step waits for it.
|
|
465
|
+
(0, daemon_control_js_1.refuseWhileRestoring)(root);
|
|
466
|
+
let config;
|
|
467
|
+
if (answers !== undefined) {
|
|
468
|
+
const { memberRoot, toolsRoot } = answers;
|
|
469
|
+
// Each account on a machine runs its own MAMA; another's viewer may already hold the port.
|
|
470
|
+
const port = await freeViewerPort(options.portAnswers ?? diagnostics_js_1.portAnswers);
|
|
471
|
+
if (port !== DEFAULT_VIEWER_PORT)
|
|
472
|
+
answers.viewer.MAMA_API_PORT = String(port);
|
|
473
|
+
config = (0, config_js_1.parseConfig)({
|
|
474
|
+
version: 1,
|
|
475
|
+
timezone: answers.timezone,
|
|
476
|
+
member_root: memberRoot,
|
|
477
|
+
tools_root: toolsRoot,
|
|
478
|
+
agent: {
|
|
479
|
+
backend: answers.backend,
|
|
480
|
+
model: answers.model,
|
|
481
|
+
effort: 'medium',
|
|
482
|
+
max_turns: 100,
|
|
483
|
+
// A turn is stopped after 10 minutes without progress, or after an hour in all
|
|
484
|
+
// (owner, 2026-10-03); a full report on 2026-09-29 was cut off at 300 s while working.
|
|
485
|
+
timeout: 600_000,
|
|
486
|
+
max_turn_ms: 3_600_000,
|
|
487
|
+
run_token_budget: 0,
|
|
488
|
+
},
|
|
489
|
+
database: { path: (0, node_path_1.join)(root, 'memory.db') },
|
|
490
|
+
logging: { level: 'info', file: (0, node_path_1.join)(root, 'logs', 'daemon.log') },
|
|
491
|
+
telegram: {
|
|
492
|
+
enabled: true,
|
|
493
|
+
owner_chat_id: answers.chatId,
|
|
494
|
+
allowed_chats: [answers.chatId],
|
|
495
|
+
polling: true,
|
|
496
|
+
},
|
|
497
|
+
discord: { enabled: false, allowed_channels: [], owner_user_ids: [] },
|
|
498
|
+
slack: { enabled: false, allowed_channels: [], owner_user_ids: [] },
|
|
499
|
+
delivery: { reports: 'telegram', notifications: 'telegram', security_alerts: 'telegram' },
|
|
500
|
+
wiki: {
|
|
501
|
+
enabled: true,
|
|
502
|
+
vaultPath: (0, node_path_1.join)(root, 'workspace'),
|
|
503
|
+
wikiDir: (0, node_path_1.join)(root, 'workspace', 'wiki'),
|
|
504
|
+
},
|
|
505
|
+
}, { home });
|
|
506
|
+
answers.secrets.MAMA_AUTH_TOKEN = (0, node_crypto_1.randomBytes)(32).toString('hex');
|
|
507
|
+
(0, init_state_js_1.writeInitState)(root, progress);
|
|
508
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.join)(root, 'logs'), { recursive: true, mode: 0o700 });
|
|
509
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.join)(root, 'workspace', 'wiki'), { recursive: true, mode: 0o700 });
|
|
510
|
+
// All answers are in before any write; the step is marked done once config.yaml exists.
|
|
511
|
+
(0, secrets_js_1.updateSecrets)(home, answers.secrets);
|
|
512
|
+
// No source yet: `mama init --section connectors` adds them.
|
|
513
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(root, 'connectors.json'), '{}\n', { flag: 'wx', mode: 0o600 });
|
|
514
|
+
(0, node_fs_1.writeFileSync)(configPath, yaml.dump(config), { flag: 'wx', mode: 0o600 });
|
|
515
|
+
progress.viewer = answers.viewer;
|
|
516
|
+
progress.pendingOwner = answers.ownerTelegramId;
|
|
517
|
+
mark('config');
|
|
518
|
+
write('Setup written. Credentials are stored only in auth.env (0600).');
|
|
519
|
+
}
|
|
520
|
+
else {
|
|
521
|
+
config = (0, config_js_1.loadConfig)({ home, path: configPath });
|
|
522
|
+
// A setup that stopped (often at the boot wait, when a source's tool is missing) names the
|
|
523
|
+
// tools again: the service does not start until they are installed.
|
|
524
|
+
await (0, init_questions_js_1.writeSourceTools)({ root, home, platform, configPath }, config, options, write, SOURCE_TOOL_NEXT);
|
|
525
|
+
}
|
|
526
|
+
if (!done('owner')) {
|
|
527
|
+
const owner = progress.pendingOwner;
|
|
528
|
+
if (!owner)
|
|
529
|
+
throw new prompt_js_1.CliInputError('No owner Telegram ID was recorded; run the install command again.');
|
|
530
|
+
await (0, register_owner_js_1.registerOwnerHolding)(root, config, config.member_root, owner);
|
|
531
|
+
delete progress.pendingOwner;
|
|
532
|
+
mark('owner');
|
|
533
|
+
write("You are registered as this install's first owner.");
|
|
534
|
+
}
|
|
535
|
+
if (!done('toolkit')) {
|
|
536
|
+
try {
|
|
537
|
+
if (options.installToolkit)
|
|
538
|
+
await options.installToolkit(config);
|
|
539
|
+
else
|
|
540
|
+
await (0, document_toolkit_js_1.installDocumentToolkit)({
|
|
541
|
+
toolsRoot: config.tools_root,
|
|
542
|
+
home,
|
|
543
|
+
memberRoot: config.member_root,
|
|
544
|
+
write,
|
|
545
|
+
});
|
|
546
|
+
write('Document toolkit installed for owner and member sessions.');
|
|
547
|
+
}
|
|
548
|
+
catch (error) {
|
|
549
|
+
// MAMA runs without the document tools; their host requirements must not keep it from
|
|
550
|
+
// starting. The daemon log names them as absent until mama tools install succeeds.
|
|
551
|
+
write(`The document tools could not be installed: ${error instanceof Error ? error.message : 'unknown error'}. MAMA runs without them; run mama tools install once that is fixed.`);
|
|
552
|
+
}
|
|
553
|
+
mark('toolkit');
|
|
554
|
+
}
|
|
417
555
|
}
|
|
418
|
-
|
|
419
|
-
|
|
556
|
+
finally {
|
|
557
|
+
// The daemon takes this lock at boot; it must be free before the service starts it.
|
|
558
|
+
lock?.release();
|
|
559
|
+
}
|
|
560
|
+
if (!done('service')) {
|
|
561
|
+
const startServiceFor = options.startService ??
|
|
562
|
+
(async (viewer) => {
|
|
563
|
+
const serviceOptions = {
|
|
564
|
+
home,
|
|
565
|
+
platform,
|
|
566
|
+
write,
|
|
567
|
+
viewer,
|
|
568
|
+
...(options.serviceRun ? { run: options.serviceRun } : {}),
|
|
569
|
+
...(options.findExecutable ? { findExecutable: options.findExecutable } : {}),
|
|
570
|
+
...(options.nodePath ? { nodePath: options.nodePath } : {}),
|
|
571
|
+
...(options.cliPath ? { cliPath: options.cliPath } : {}),
|
|
572
|
+
...(options.toolDirectoryExists
|
|
573
|
+
? { toolDirectoryExists: options.toolDirectoryExists }
|
|
574
|
+
: {}),
|
|
575
|
+
};
|
|
576
|
+
// Linux keeps the user's services running after logout only with linger (B4). Enabling it
|
|
577
|
+
// can need privileges this account lacks; that must not stop the service from starting.
|
|
578
|
+
if (platform === 'linux') {
|
|
579
|
+
const user = options.user ?? (0, node_os_1.userInfo)().username;
|
|
580
|
+
const result = (options.serviceRun ?? systemd_service_js_1.defaultServiceRun)('loginctl', [
|
|
581
|
+
'enable-linger',
|
|
582
|
+
user,
|
|
583
|
+
]);
|
|
584
|
+
if (result.error || result.status !== 0)
|
|
585
|
+
write(`Linger could not be enabled, so MAMA stops when you log out. Run once: sudo loginctl enable-linger ${user}`);
|
|
586
|
+
}
|
|
587
|
+
await (0, service_js_1.installService)([], serviceOptions);
|
|
588
|
+
await (0, service_js_1.startService)(serviceOptions);
|
|
589
|
+
});
|
|
590
|
+
// The port was free when the setup was written; a reboot or another account's MAMA started
|
|
591
|
+
// since may have taken it. This install's own daemon, if it runs, holds it legitimately.
|
|
592
|
+
const ownDaemon = await (options.daemonStatus ?? (() => (0, daemon_js_1.askDaemon)('status', { home })))().catch(() => null);
|
|
593
|
+
const answers = options.portAnswers ?? diagnostics_js_1.portAnswers;
|
|
594
|
+
if (ownDaemon?.state !== 'running') {
|
|
595
|
+
const stored = (0, viewer_server_js_1.resolveApiPort)(progress.viewer?.MAMA_API_PORT);
|
|
596
|
+
if (await answers(stored, '127.0.0.1')) {
|
|
597
|
+
const port = await freeViewerPort(answers);
|
|
598
|
+
// Named even when it is the default, so it replaces the port start.sh carries.
|
|
599
|
+
progress.viewer = { ...progress.viewer, MAMA_API_PORT: String(port) };
|
|
600
|
+
(0, init_state_js_1.writeInitState)(root, progress);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
await startServiceFor(progress.viewer ?? {});
|
|
604
|
+
const viewerPort = (0, viewer_server_js_1.resolveApiPort)(progress.viewer?.MAMA_API_PORT);
|
|
605
|
+
await waitForBoot({
|
|
606
|
+
status: options.daemonStatus ?? (() => (0, daemon_js_1.askDaemon)('status', { home })),
|
|
607
|
+
health: options.health ?? (() => viewerHealth(viewerPort)),
|
|
608
|
+
timeoutMs: options.bootTimeoutMs ?? 600_000,
|
|
609
|
+
pollMs: options.pollMs ?? 1_000,
|
|
610
|
+
write,
|
|
611
|
+
});
|
|
612
|
+
// The backend's version and login were read from this shell. One real turn through the
|
|
613
|
+
// daemon proves it answers as the service runs it: its PATH, environment, sandbox and model.
|
|
614
|
+
// The step is done only then, so running the install command again checks once more after a
|
|
615
|
+
// fix.
|
|
616
|
+
write('Checking the backend with one real turn through MAMA…');
|
|
617
|
+
// A turn that keeps progressing may run until agent.max_turn_ms, and the daemon answers with its
|
|
618
|
+
// own error before then; wait a little longer than that.
|
|
619
|
+
const timeoutMs = (0, config_js_1.loadConfig)({ home, path: configPath }).agent.max_turn_ms + PROBE_MARGIN_MS;
|
|
620
|
+
const probe = await (options.daemonProbe ?? (() => (0, daemon_js_1.askDaemon)('probe', { home, timeoutMs })))().catch((error) => {
|
|
621
|
+
throw new prompt_js_1.CliInputError(`MAMA did not answer the backend check (${error instanceof Error ? error.message : 'no answer'}). mama logs shows why; then run the install command again.`);
|
|
622
|
+
});
|
|
623
|
+
if (probe.state !== 'running')
|
|
624
|
+
throw new prompt_js_1.CliInputError('MAMA stopped before its backend could be checked. mama logs shows why; then run the install command again.');
|
|
625
|
+
if (probe.reply.ok !== true)
|
|
626
|
+
throw new prompt_js_1.CliInputError(`The backend did not answer a real turn: ${String(probe.reply.error)}. mama logs shows more, and mama doctor checks the backend's login; fix it, then run the install command again.`);
|
|
627
|
+
const probed = probe.reply.probe;
|
|
628
|
+
write(`${String(probed.backend)} (${String(probed.model)}) answered a real turn in ${String(probed.ms)} ms.`);
|
|
629
|
+
delete progress.viewer;
|
|
630
|
+
mark('service');
|
|
420
631
|
}
|
|
421
|
-
|
|
632
|
+
write('MAMA is running. Send your bot a message in Telegram.');
|
|
422
633
|
}
|
|
423
634
|
//# sourceMappingURL=init.js.map
|