fraim-hub 2.0.326 → 2.0.328
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/dist/src/ai-hub/conversation-store.js +1 -1
- package/dist/src/ai-hub/desktop-installer-setup.js +79 -0
- package/dist/src/ai-hub/desktop-launcher.js +4 -0
- package/dist/src/ai-hub/desktop-main.js +18 -9
- package/dist/src/ai-hub/hub-app-materializer.js +10 -20
- package/dist/src/ai-hub/server.js +2 -1
- package/dist/src/api/payment/webhook.js +7 -6
- package/dist/src/cli/mcp/fraim-mcp-latest-launcher.js +200 -130
- package/dist/src/cli/mcp/mcp-server-registry.js +1 -6
- package/dist/src/cli/utils/managed-agent-install.js +5 -1
- package/dist/src/cli/utils/managed-agent-paths.js +14 -3
- package/dist/src/cli/utils/managed-node-runtime.js +22 -19
- package/dist/src/cli/utils/persist-shell-path.js +121 -0
- package/dist/src/core/utils/redact-secrets.js +89 -0
- package/dist/src/first-run/session-service.js +25 -145
- package/package.json +6 -3
- package/public/ai-hub/index.html +1 -1
- package/public/ai-hub/script.js +87 -13
- package/public/ai-hub/styles.css +17 -8
|
@@ -337,7 +337,7 @@ function normalizeConversation(projectPath, raw) {
|
|
|
337
337
|
status: value.status,
|
|
338
338
|
// Issue #904: carry pauseReason explicitly so it survives the ...value spread round-trip.
|
|
339
339
|
...(value.pauseReason !== undefined && { pauseReason: value.pauseReason }),
|
|
340
|
-
createdAt: value.createdAt ?? new Date().toISOString(),
|
|
340
|
+
createdAt: value.createdAt ?? value.lastUpdatedAt ?? new Date().toISOString(),
|
|
341
341
|
lastUpdatedAt: value.lastUpdatedAt ?? value.createdAt ?? new Date().toISOString(),
|
|
342
342
|
};
|
|
343
343
|
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.readInstallerSetupApiKey = readInstallerSetupApiKey;
|
|
7
|
+
exports.repairInstallerIdeMcpEntries = repairInstallerIdeMcpEntries;
|
|
8
|
+
exports.runDesktopInstallerSetup = runDesktopInstallerSetup;
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const child_process_1 = require("child_process");
|
|
12
|
+
const project_fraim_paths_1 = require("../core/utils/project-fraim-paths");
|
|
13
|
+
const ide_detector_1 = require("../cli/setup/ide-detector");
|
|
14
|
+
const auto_mcp_setup_1 = require("../cli/setup/auto-mcp-setup");
|
|
15
|
+
const fraim_mcp_latest_launcher_1 = require("../cli/mcp/fraim-mcp-latest-launcher");
|
|
16
|
+
const managed_node_runtime_1 = require("../cli/utils/managed-node-runtime");
|
|
17
|
+
const win32_cmd_spawn_1 = require("../cli/utils/win32-cmd-spawn");
|
|
18
|
+
function readInstallerSetupApiKey(configPath = path_1.default.join((0, project_fraim_paths_1.getUserFraimDirPath)(), 'config.json')) {
|
|
19
|
+
if (!fs_1.default.existsSync(configPath))
|
|
20
|
+
return null;
|
|
21
|
+
try {
|
|
22
|
+
const parsed = JSON.parse(fs_1.default.readFileSync(configPath, 'utf8'));
|
|
23
|
+
return typeof parsed.apiKey === 'string' && parsed.apiKey.trim() ? parsed.apiKey.trim() : null;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
async function repairInstallerIdeMcpEntries(options = {}) {
|
|
30
|
+
const logger = options.logger ?? console;
|
|
31
|
+
const fraimKey = readInstallerSetupApiKey(options.configPath);
|
|
32
|
+
if (!fraimKey) {
|
|
33
|
+
logger.log('[fraim-installer-setup] no existing config.json apiKey; IDE MCP repair will run from first-run');
|
|
34
|
+
return { status: 'skipped', ideCount: 0, reason: 'missing-config-api-key' };
|
|
35
|
+
}
|
|
36
|
+
const detectedIDEs = (options.detectInstalledIDEs ?? ide_detector_1.detectInstalledIDEs)();
|
|
37
|
+
const ideNames = detectedIDEs.map((ide) => ide.name).filter(Boolean);
|
|
38
|
+
if (ideNames.length === 0) {
|
|
39
|
+
logger.log('[fraim-installer-setup] no supported IDEs detected for MCP repair');
|
|
40
|
+
return { status: 'skipped', ideCount: 0, reason: 'no-detected-ides' };
|
|
41
|
+
}
|
|
42
|
+
const previousNonInteractive = process.env.FRAIM_NON_INTERACTIVE;
|
|
43
|
+
process.env.FRAIM_NON_INTERACTIVE = '1';
|
|
44
|
+
try {
|
|
45
|
+
await (options.autoConfigureMCP ?? auto_mcp_setup_1.autoConfigureMCP)(fraimKey, ideNames);
|
|
46
|
+
logger.log(`[fraim-installer-setup] repaired MCP config for ${ideNames.length} detected IDE(s)`);
|
|
47
|
+
return { status: 'repaired', ideCount: ideNames.length };
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
51
|
+
logger.warn(`[fraim-installer-setup] IDE MCP repair skipped after error: ${message}`);
|
|
52
|
+
return { status: 'failed', ideCount: ideNames.length, reason: message };
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
if (previousNonInteractive === undefined)
|
|
56
|
+
delete process.env.FRAIM_NON_INTERACTIVE;
|
|
57
|
+
else
|
|
58
|
+
process.env.FRAIM_NON_INTERACTIVE = previousNonInteractive;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function warmInstallerCliCache() {
|
|
62
|
+
const cliShim = (0, fraim_mcp_latest_launcher_1.getFraimCliShimPath)();
|
|
63
|
+
const options = { env: process.env, encoding: 'utf8', stdio: 'inherit' };
|
|
64
|
+
const result = process.platform === 'win32'
|
|
65
|
+
? (0, win32_cmd_spawn_1.spawnViaCmdExe)(cliShim, ['--version'], options)
|
|
66
|
+
: (0, child_process_1.spawnSync)(cliShim, ['--version'], options);
|
|
67
|
+
if (result.status !== 0) {
|
|
68
|
+
throw new Error(`FRAIM CLI bootstrap exited with code ${result.status ?? 'unknown'}.`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async function runDesktopInstallerSetup(logger = console) {
|
|
72
|
+
await (0, managed_node_runtime_1.ensureManagedNpm)({
|
|
73
|
+
onProgress: (message) => logger.log(`[fraim-installer-setup] ${message}`),
|
|
74
|
+
});
|
|
75
|
+
(0, fraim_mcp_latest_launcher_1.ensureFraimMcpLatestLauncher)();
|
|
76
|
+
warmInstallerCliCache();
|
|
77
|
+
await repairInstallerIdeMcpEntries({ logger });
|
|
78
|
+
logger.log('[fraim-installer-setup] stable CLI and MCP launchers are ready');
|
|
79
|
+
}
|
|
@@ -20,6 +20,10 @@ async function bootstrapFrom(entryPath) {
|
|
|
20
20
|
await entry.bootstrap();
|
|
21
21
|
}
|
|
22
22
|
async function launch() {
|
|
23
|
+
if (process.argv.includes('--fraim-installer-setup')) {
|
|
24
|
+
await bootstrapFrom(BUNDLED_ENTRY_PATH);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
23
27
|
// Issue #1415: deterministic escape hatch for integration testing — skips the real npm
|
|
24
28
|
// resolution (and its dependence on whatever happens to be published at test time) so a test can
|
|
25
29
|
// prove the Electron main-process boot chain itself (resolveHubAppEntry -> require -> bootstrap
|
|
@@ -20,7 +20,7 @@ const window_open_decision_1 = require("./window-open-decision");
|
|
|
20
20
|
const bundled_asset_resolver_1 = require("./bundled-asset-resolver");
|
|
21
21
|
const server_2 = require("../first-run/server");
|
|
22
22
|
const session_service_1 = require("../first-run/session-service");
|
|
23
|
-
const
|
|
23
|
+
const desktop_installer_setup_1 = require("./desktop-installer-setup");
|
|
24
24
|
const desktop_auto_updater_1 = require("./desktop-auto-updater");
|
|
25
25
|
const hub_app_materializer_1 = require("./hub-app-materializer");
|
|
26
26
|
const hub_instance_reconciliation_1 = require("./hub-instance-reconciliation");
|
|
@@ -89,13 +89,6 @@ function applyUserDataOverride() {
|
|
|
89
89
|
fs_1.default.mkdirSync(userDataDir, { recursive: true });
|
|
90
90
|
electron_1.app.setPath('userData', userDataDir);
|
|
91
91
|
}
|
|
92
|
-
function configurePackagedFraimRuntime() {
|
|
93
|
-
if (!electron_1.app.isPackaged && process.env.FRAIM_DESKTOP_FIRST_RUN !== '1')
|
|
94
|
-
return;
|
|
95
|
-
process.env.FRAIM_PACKAGED_CLI_EXECUTABLE = process.execPath;
|
|
96
|
-
process.env.FRAIM_PACKAGED_CLI_SCRIPT = (0, hub_app_materializer_1.resolvePackagedCliScriptPath)(__dirname);
|
|
97
|
-
(0, fraim_mcp_latest_launcher_1.ensureFraimMcpLatestLauncher)();
|
|
98
|
-
}
|
|
99
92
|
function shouldShowEmbeddedFirstRun() {
|
|
100
93
|
if (!electron_1.app.isPackaged && process.env.FRAIM_DESKTOP_FIRST_RUN !== '1')
|
|
101
94
|
return false;
|
|
@@ -153,6 +146,16 @@ async function configureAutoUpdater() {
|
|
|
153
146
|
});
|
|
154
147
|
return result.action === 'installing';
|
|
155
148
|
}
|
|
149
|
+
async function runPackagedInstallerRepair() {
|
|
150
|
+
if (!electron_1.app.isPackaged || process.env.FRAIM_INSTALLER_LIFECYCLE_TEST === '1')
|
|
151
|
+
return;
|
|
152
|
+
try {
|
|
153
|
+
await (0, desktop_installer_setup_1.runDesktopInstallerSetup)(console);
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
console.warn('[fraim] packaged installer repair failed:', error);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
156
159
|
// Issue #1415: shared by every path that needs to know "is a newer FRAIM Hub published than the
|
|
157
160
|
// one currently running" — the automatic second-instance/activate recheck below, and the
|
|
158
161
|
// manually-triggered restart from the web UI's update badge (see restartToLatestNow). Compares
|
|
@@ -655,6 +658,12 @@ async function bootstrap() {
|
|
|
655
658
|
preferredPort: options.preferredPort,
|
|
656
659
|
hasProjectPath: Boolean(options.projectPath),
|
|
657
660
|
});
|
|
661
|
+
if (process.argv.includes('--fraim-installer-setup')) {
|
|
662
|
+
await electron_1.app.whenReady();
|
|
663
|
+
await (0, desktop_installer_setup_1.runDesktopInstallerSetup)(console);
|
|
664
|
+
electron_1.app.exit(0);
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
658
667
|
electron_1.app.on('child-process-gone', (_event, details) => {
|
|
659
668
|
(0, hub_main_diagnostics_1.recordHubMainDiagnostic)('app.child_process_gone', details);
|
|
660
669
|
});
|
|
@@ -679,7 +688,6 @@ async function bootstrap() {
|
|
|
679
688
|
});
|
|
680
689
|
await electron_1.app.whenReady();
|
|
681
690
|
electron_1.app.setName(displayName(options.runtimeId));
|
|
682
|
-
configurePackagedFraimRuntime();
|
|
683
691
|
// Issue #1415 (follow-up): app.requestSingleInstanceLock() above only sees another instance of
|
|
684
692
|
// *this same identity* — it cannot see a Hub already running via a different launch path (e.g.
|
|
685
693
|
// `npx fraim-hub`'s raw electron.exe, confirmed live to end up under a different userData scope
|
|
@@ -710,6 +718,7 @@ async function bootstrap() {
|
|
|
710
718
|
// First-launch housekeeping (idempotent, fast on subsequent runs)
|
|
711
719
|
if (process.env.FRAIM_INSTALLER_LIFECYCLE_TEST !== '1') {
|
|
712
720
|
ensureLoginItem();
|
|
721
|
+
await runPackagedInstallerRepair();
|
|
713
722
|
const installingUpdate = await configureAutoUpdater();
|
|
714
723
|
if (installingUpdate)
|
|
715
724
|
return;
|
|
@@ -5,11 +5,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.HUB_APP_ENTRY_RELATIVE = exports.HUB_APP_PACKAGE_NAME = void 0;
|
|
7
7
|
exports.redactCredentialLikeSubstrings = redactCredentialLikeSubstrings;
|
|
8
|
-
exports.resolvePackagedCliScriptPath = resolvePackagedCliScriptPath;
|
|
9
8
|
exports.planHubAppMaterialization = planHubAppMaterialization;
|
|
10
9
|
exports.resolveHubAppCacheDir = resolveHubAppCacheDir;
|
|
11
10
|
exports.isHubAppCacheComplete = isHubAppCacheComplete;
|
|
12
11
|
exports.listCachedHubAppVersions = listCachedHubAppVersions;
|
|
12
|
+
exports.runNpmInstall = runNpmInstall;
|
|
13
13
|
exports.ensureMaterializedHubApp = ensureMaterializedHubApp;
|
|
14
14
|
exports.resolveHubAppEntry = resolveHubAppEntry;
|
|
15
15
|
const fs_1 = __importDefault(require("fs"));
|
|
@@ -55,19 +55,6 @@ function redactCredentialLikeSubstrings(text) {
|
|
|
55
55
|
exports.HUB_APP_PACKAGE_NAME = 'fraim-hub';
|
|
56
56
|
/** Relative path, from a materialized (or bundled) package root, to the Electron main entry. */
|
|
57
57
|
exports.HUB_APP_ENTRY_RELATIVE = path_1.default.join('dist', 'src', 'ai-hub', 'desktop-main.js');
|
|
58
|
-
/**
|
|
59
|
-
* Where the packaged CLI/MCP shim (`ensureFraimMcpLatestLauncher()`) should find `fraim`'s CLI
|
|
60
|
-
* script, derived from `desktop-main.ts`'s own directory (`<packageRoot>/dist/src/ai-hub/`) rather
|
|
61
|
-
* than Electron's `app.getAppPath()`. `desktop-launcher.ts` may have loaded `desktop-main.js` from a
|
|
62
|
-
* materialized `fraim-hub@<latest>` copy under `~/.fraim/bin/hub-app/<version>/` rather than from
|
|
63
|
-
* the original app.asar, and `app.getAppPath()` always names the latter regardless of which copy is
|
|
64
|
-
* actually running. `moduleDir` tracks whichever copy is really executing. Kept in this
|
|
65
|
-
* Electron-free module (rather than inlined in `desktop-main.ts`) so it is unit-testable without an
|
|
66
|
-
* Electron `app` object.
|
|
67
|
-
*/
|
|
68
|
-
function resolvePackagedCliScriptPath(moduleDir) {
|
|
69
|
-
return path_1.default.join(moduleDir, '..', '..', '..', 'node_modules', 'fraim', 'bin', 'fraim.js');
|
|
70
|
-
}
|
|
71
58
|
function pickNewestComplete(cachedVersions) {
|
|
72
59
|
const versions = cachedVersions.filter((entry) => entry.complete).map((entry) => entry.version);
|
|
73
60
|
if (versions.length === 0)
|
|
@@ -178,16 +165,19 @@ function listCachedHubAppVersions(fraimDir = (0, project_fraim_paths_1.getUserFr
|
|
|
178
165
|
* every dependency to that top level; nothing here relies on nested-dependency resolution.
|
|
179
166
|
*
|
|
180
167
|
* The `npm` executable itself is resolved via `ensureManagedNpm()` (system PATH, an already-managed
|
|
181
|
-
* runtime, or a freshly downloaded one) rather than assumed present on PATH
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
* purposes and never downloads a standalone npm (see managed-node-runtime.ts's own header comment).
|
|
168
|
+
* runtime, or a freshly downloaded one) rather than assumed present on PATH. Installer setup
|
|
169
|
+
* prepares that shared runtime, while this fallback also supports older installs and recovery
|
|
170
|
+
* when installer setup did not finish.
|
|
185
171
|
*/
|
|
186
172
|
function runNpmInstall(npmPath, args) {
|
|
173
|
+
// POSIX npm uses /usr/bin/env node. A GUI launch may have no system Node
|
|
174
|
+
// on PATH even after ensureManagedNpm has downloaded a complete runtime.
|
|
175
|
+
const npmPathValue = [path_1.default.dirname(npmPath), process.env.PATH].filter(Boolean).join(path_1.default.delimiter);
|
|
176
|
+
const env = { ...process.env, PATH: npmPathValue, ...(process.platform === 'win32' ? { Path: npmPathValue } : {}) };
|
|
187
177
|
if (process.platform !== 'win32') {
|
|
188
|
-
return (0, child_process_1.spawnSync)(npmPath, args, { encoding: 'utf8' });
|
|
178
|
+
return (0, child_process_1.spawnSync)(npmPath, args, { encoding: 'utf8', env });
|
|
189
179
|
}
|
|
190
|
-
return (0, win32_cmd_spawn_1.spawnViaCmdExe)(npmPath, args, { encoding: 'utf8' });
|
|
180
|
+
return (0, win32_cmd_spawn_1.spawnViaCmdExe)(npmPath, args, { encoding: 'utf8', env });
|
|
191
181
|
}
|
|
192
182
|
const defaultInstallPackage = async (version, destDir) => {
|
|
193
183
|
fs_1.default.mkdirSync(destDir, { recursive: true });
|
|
@@ -3826,8 +3826,9 @@ class AiHubServer {
|
|
|
3826
3826
|
try {
|
|
3827
3827
|
const ref = this.rawEventLogStore.append((0, conversation_store_1.conversationScopeKey)(run.scope, run.projectPath), run.conversationId || run.id, run.id, { channel: 'system', text, hubKind: kind, hostId: run.baseHostId || run.hostId, createdAt });
|
|
3828
3828
|
run.eventLogRefs = upsertEventLogRef(run.eventLogRefs, ref);
|
|
3829
|
-
if (ref.eventCount === 1)
|
|
3829
|
+
if (ref.eventCount === 1 && this.runRegistry.get(run.id)) {
|
|
3830
3830
|
this.persistRunConversationNow(run, run.managedByRunId ? undefined : (run.conversationId || run.id));
|
|
3831
|
+
}
|
|
3831
3832
|
}
|
|
3832
3833
|
catch (error) {
|
|
3833
3834
|
run.events.push((0, hosts_1.createHubEvent)('system', 'Event archive write failed; some Micro-manage entries are unavailable.'));
|
|
@@ -11,6 +11,7 @@ exports.resolveSubscriptionNextBillingDateWithInvoiceFallback = resolveSubscript
|
|
|
11
11
|
exports.buildCurrentPeriodEndUpdate = buildCurrentPeriodEndUpdate;
|
|
12
12
|
exports.resolveInvoicePaidNextBillingDate = resolveInvoicePaidNextBillingDate;
|
|
13
13
|
const stripe_1 = require("../../config/stripe");
|
|
14
|
+
const redact_secrets_1 = require("../../core/utils/redact-secrets");
|
|
14
15
|
const feature_flags_1 = require("../../config/feature-flags");
|
|
15
16
|
const email_service_1 = require("../../services/email-service");
|
|
16
17
|
const dashboard_access_1 = require("../../services/dashboard-access");
|
|
@@ -273,7 +274,7 @@ async function handleSubscriptionCreatedOrUpdated(subscription, dbService) {
|
|
|
273
274
|
expiresAt: null, // Subscriptions don't have fixed expiration
|
|
274
275
|
cancelAt: subscription.cancel_at ? new Date(subscription.cancel_at * 1000) : null
|
|
275
276
|
});
|
|
276
|
-
console.log(`✅ Upgraded trial key to paid subscription: ${apiKey.key}`);
|
|
277
|
+
console.log(`✅ Upgraded trial key to paid subscription: ${(0, redact_secrets_1.redactApiKey)(apiKey.key)}`);
|
|
277
278
|
}
|
|
278
279
|
else {
|
|
279
280
|
// Create new API key for subscription (shouldn't happen in normal flow)
|
|
@@ -295,7 +296,7 @@ async function handleSubscriptionCreatedOrUpdated(subscription, dbService) {
|
|
|
295
296
|
apiCallCount: 0,
|
|
296
297
|
personaSystemActive: (0, feature_flags_1.isPersonaEntitlementsEnabled)() || undefined
|
|
297
298
|
});
|
|
298
|
-
console.log(`✅ Created new API key for subscription: ${newKey}`);
|
|
299
|
+
console.log(`✅ Created new API key for subscription: ${(0, redact_secrets_1.redactApiKey)(newKey)}`);
|
|
299
300
|
apiKey = { key: newKey, userId: email };
|
|
300
301
|
}
|
|
301
302
|
}
|
|
@@ -358,7 +359,7 @@ async function handleSubscriptionDeleted(subscription, dbService) {
|
|
|
358
359
|
// Send cancellation email
|
|
359
360
|
const emailService = new email_service_1.EmailService();
|
|
360
361
|
await emailService.sendSubscriptionCancelled(apiKey.userId, new Date());
|
|
361
|
-
console.log(`✅ API key expired due to subscription cancellation: ${apiKey.key}`);
|
|
362
|
+
console.log(`✅ API key expired due to subscription cancellation: ${(0, redact_secrets_1.redactApiKey)(apiKey.key)}`);
|
|
362
363
|
}
|
|
363
364
|
async function handleInvoicePaid(invoice, dbService) {
|
|
364
365
|
console.log(`✅ Invoice paid: ${invoice.id}`);
|
|
@@ -438,7 +439,7 @@ async function handleInvoicePaid(invoice, dbService) {
|
|
|
438
439
|
suspendedAt: null,
|
|
439
440
|
suspensionReason: null
|
|
440
441
|
});
|
|
441
|
-
console.log(`✅ API key restored after payment: ${apiKey.key}`);
|
|
442
|
+
console.log(`✅ API key restored after payment: ${(0, redact_secrets_1.redactApiKey)(apiKey.key)}`);
|
|
442
443
|
// Send restoration email
|
|
443
444
|
const emailService = new email_service_1.EmailService();
|
|
444
445
|
const restorationBillingDate = nextBillingDate || new Date();
|
|
@@ -467,7 +468,7 @@ async function handleInvoicePaid(invoice, dbService) {
|
|
|
467
468
|
}
|
|
468
469
|
// Regular renewal
|
|
469
470
|
{
|
|
470
|
-
console.log(`✅ Subscription renewed: ${apiKey.key}`);
|
|
471
|
+
console.log(`✅ Subscription renewed: ${(0, redact_secrets_1.redactApiKey)(apiKey.key)}`);
|
|
471
472
|
if (nextBillingDate) {
|
|
472
473
|
await dbService.updateApiKey(apiKey.key, buildCurrentPeriodEndUpdate(nextBillingDate));
|
|
473
474
|
}
|
|
@@ -532,7 +533,7 @@ async function handleInvoicePaymentFailed(invoice, dbService) {
|
|
|
532
533
|
suspendedAt: new Date(),
|
|
533
534
|
suspensionReason: 'payment_failed'
|
|
534
535
|
});
|
|
535
|
-
console.log(`✅ API key suspended due to payment failure: ${apiKey.key}`);
|
|
536
|
+
console.log(`✅ API key suspended due to payment failure: ${(0, redact_secrets_1.redactApiKey)(apiKey.key)}`);
|
|
536
537
|
// Send payment failure email with 7-day grace period
|
|
537
538
|
const emailService = new email_service_1.EmailService();
|
|
538
539
|
const gracePeriodEndsAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days
|