ft-scout 1.0.9 → 1.1.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/.firebase/hosting.d2Vi.cache +9 -0
- package/.firebaserc +14 -0
- package/AUTONOMOUS_FEATURES.md +192 -0
- package/README.md +482 -29
- package/bin/ft.js +2 -0
- package/bin/src/commands/agent.d.ts.map +1 -0
- package/bin/src/commands/agent.js +1230 -0
- package/bin/src/commands/agent.js.map +1 -0
- package/bin/src/commands/audit.d.ts.map +1 -0
- package/bin/src/commands/audit.js +104 -0
- package/bin/src/commands/audit.js.map +1 -0
- package/bin/src/commands/authCommand.d.ts.map +1 -0
- package/bin/src/commands/authCommand.js +38 -0
- package/bin/src/commands/authCommand.js.map +1 -0
- package/bin/src/commands/benchmark.d.ts.map +1 -0
- package/bin/src/commands/benchmark.js +135 -0
- package/bin/src/commands/benchmark.js.map +1 -0
- package/bin/src/commands/brief.d.ts.map +1 -0
- package/bin/src/commands/brief.js +37 -0
- package/bin/src/commands/brief.js.map +1 -0
- package/bin/src/commands/configCommand.d.ts.map +1 -0
- package/bin/src/commands/configCommand.js +53 -0
- package/bin/src/commands/configCommand.js.map +1 -0
- package/bin/src/commands/dashboard.d.ts.map +1 -0
- package/bin/src/commands/dashboard.js +41 -0
- package/bin/src/commands/dashboard.js.map +1 -0
- package/bin/src/commands/history.d.ts.map +1 -0
- package/bin/src/commands/history.js +20 -0
- package/bin/src/commands/history.js.map +1 -0
- package/bin/src/commands/init.d.ts.map +1 -1
- package/bin/src/commands/init.js +408 -79
- package/bin/src/commands/init.js.map +1 -1
- package/bin/src/commands/login.d.ts.map +1 -0
- package/bin/src/commands/login.js +190 -0
- package/bin/src/commands/login.js.map +1 -0
- package/bin/src/commands/logout.d.ts.map +1 -0
- package/bin/src/commands/logout.js +15 -0
- package/bin/src/commands/logout.js.map +1 -0
- package/bin/src/commands/mcp.d.ts.map +1 -0
- package/bin/src/commands/mcp.js +393 -0
- package/bin/src/commands/mcp.js.map +1 -0
- package/bin/src/commands/owners.js +6 -6
- package/bin/src/commands/owners.js.map +1 -1
- package/bin/src/commands/plan.d.ts.map +1 -0
- package/bin/src/commands/plan.js +152 -0
- package/bin/src/commands/plan.js.map +1 -0
- package/bin/src/commands/quota.d.ts.map +1 -0
- package/bin/src/commands/quota.js +32 -0
- package/bin/src/commands/quota.js.map +1 -0
- package/bin/src/commands/recommend.d.ts.map +1 -0
- package/bin/src/commands/recommend.js +46 -0
- package/bin/src/commands/recommend.js.map +1 -0
- package/bin/src/commands/risky.d.ts.map +1 -1
- package/bin/src/commands/risky.js +235 -39
- package/bin/src/commands/risky.js.map +1 -1
- package/bin/src/commands/setup.d.ts.map +1 -1
- package/bin/src/commands/setup.js +411 -16
- package/bin/src/commands/setup.js.map +1 -1
- package/bin/src/commands/signup.d.ts.map +1 -0
- package/bin/src/commands/signup.js +118 -0
- package/bin/src/commands/signup.js.map +1 -0
- package/bin/src/commands/undo.d.ts.map +1 -0
- package/bin/src/commands/undo.js +59 -0
- package/bin/src/commands/undo.js.map +1 -0
- package/bin/src/commands/web.d.ts.map +1 -0
- package/bin/src/commands/web.js +90 -0
- package/bin/src/commands/web.js.map +1 -0
- package/bin/src/discovery/config.d.ts.map +1 -0
- package/bin/src/discovery/config.js +41 -0
- package/bin/src/discovery/config.js.map +1 -0
- package/bin/src/discovery/display.d.ts.map +1 -0
- package/bin/src/discovery/display.js +57 -0
- package/bin/src/discovery/display.js.map +1 -0
- package/bin/src/discovery/messages.d.ts.map +1 -0
- package/bin/src/discovery/messages.js +7 -0
- package/bin/src/discovery/messages.js.map +1 -0
- package/bin/src/discovery/signals.d.ts.map +1 -0
- package/bin/src/discovery/signals.js +33 -0
- package/bin/src/discovery/signals.js.map +1 -0
- package/bin/src/engine/actionVerifier.d.ts.map +1 -0
- package/bin/src/engine/actionVerifier.js +283 -0
- package/bin/src/engine/actionVerifier.js.map +1 -0
- package/bin/src/engine/agentEngine.d.ts.map +1 -0
- package/bin/src/engine/agentEngine.js +2188 -0
- package/bin/src/engine/agentEngine.js.map +1 -0
- package/bin/src/engine/agentState.d.ts.map +1 -0
- package/bin/src/engine/agentState.js +381 -0
- package/bin/src/engine/agentState.js.map +1 -0
- package/bin/src/engine/browser/browserController.d.ts.map +1 -0
- package/bin/src/engine/browser/browserController.js +737 -0
- package/bin/src/engine/browser/browserController.js.map +1 -0
- package/bin/src/engine/browser/index.d.ts.map +1 -0
- package/bin/src/engine/browser/index.js +2 -0
- package/bin/src/engine/browser/index.js.map +1 -0
- package/bin/src/engine/computer/computerController.d.ts.map +1 -0
- package/bin/src/engine/computer/computerController.js +773 -0
- package/bin/src/engine/computer/computerController.js.map +1 -0
- package/bin/src/engine/computer/computerDiagnostic.d.ts.map +1 -0
- package/bin/src/engine/computer/computerDiagnostic.js +71 -0
- package/bin/src/engine/computer/computerDiagnostic.js.map +1 -0
- package/bin/src/engine/computer/index.d.ts.map +1 -0
- package/bin/src/engine/computer/index.js +4 -0
- package/bin/src/engine/computer/index.js.map +1 -0
- package/bin/src/engine/computer/mockComputer.d.ts.map +1 -0
- package/bin/src/engine/computer/mockComputer.js +260 -0
- package/bin/src/engine/computer/mockComputer.js.map +1 -0
- package/bin/src/engine/computer/scoutDriver.cs +1161 -0
- package/bin/src/engine/computer/scout_driver.exe +0 -0
- package/bin/src/engine/git.d.ts.map +1 -1
- package/bin/src/engine/git.js +71 -0
- package/bin/src/engine/git.js.map +1 -1
- package/bin/src/engine/llm.d.ts.map +1 -1
- package/bin/src/engine/llm.js +1264 -35
- package/bin/src/engine/llm.js.map +1 -1
- package/bin/src/engine/planner.d.ts.map +1 -0
- package/bin/src/engine/planner.js +321 -0
- package/bin/src/engine/planner.js.map +1 -0
- package/bin/src/engine/runtime/agentRuntime.d.ts.map +1 -0
- package/bin/src/engine/runtime/agentRuntime.js +1258 -0
- package/bin/src/engine/runtime/agentRuntime.js.map +1 -0
- package/bin/src/engine/runtime/index.d.ts.map +1 -0
- package/bin/src/engine/runtime/index.js +2 -0
- package/bin/src/engine/runtime/index.js.map +1 -0
- package/bin/src/engine/safety.d.ts.map +1 -0
- package/bin/src/engine/safety.js +316 -0
- package/bin/src/engine/safety.js.map +1 -0
- package/bin/src/engine/scoutAudioRecorder.cs +215 -0
- package/bin/src/engine/scout_audio_recorder.exe +0 -0
- package/bin/src/engine/toolRegistry.d.ts.map +1 -0
- package/bin/src/engine/toolRegistry.js +521 -0
- package/bin/src/engine/toolRegistry.js.map +1 -0
- package/bin/src/engine/verifier.d.ts.map +1 -0
- package/bin/src/engine/verifier.js +414 -0
- package/bin/src/engine/verifier.js.map +1 -0
- package/bin/src/engine/voiceEngine.d.ts.map +1 -0
- package/bin/src/engine/voiceEngine.js +474 -0
- package/bin/src/engine/voiceEngine.js.map +1 -0
- package/bin/src/engine/web.d.ts.map +1 -0
- package/bin/src/engine/web.js +161 -0
- package/bin/src/engine/web.js.map +1 -0
- package/bin/src/engine/workspace/agentWorkspace.d.ts.map +1 -0
- package/bin/src/engine/workspace/agentWorkspace.js +505 -0
- package/bin/src/engine/workspace/agentWorkspace.js.map +1 -0
- package/bin/src/engine/workspace/index.d.ts.map +1 -0
- package/bin/src/engine/workspace/index.js +2 -0
- package/bin/src/engine/workspace/index.js.map +1 -0
- package/bin/src/index.js +369 -19
- package/bin/src/index.js.map +1 -1
- package/bin/src/mcp/client.d.ts.map +1 -0
- package/bin/src/mcp/client.js +127 -0
- package/bin/src/mcp/client.js.map +1 -0
- package/bin/src/mcp/discovery.d.ts.map +1 -0
- package/bin/src/mcp/discovery.js +91 -0
- package/bin/src/mcp/discovery.js.map +1 -0
- package/bin/src/mcp/health.d.ts.map +1 -0
- package/bin/src/mcp/health.js +44 -0
- package/bin/src/mcp/health.js.map +1 -0
- package/bin/src/mcp/index.d.ts.map +1 -0
- package/bin/src/mcp/index.js +15 -0
- package/bin/src/mcp/index.js.map +1 -0
- package/bin/src/mcp/manager.d.ts.map +1 -0
- package/bin/src/mcp/manager.js +648 -0
- package/bin/src/mcp/manager.js.map +1 -0
- package/bin/src/mcp/permissions.d.ts.map +1 -0
- package/bin/src/mcp/permissions.js +50 -0
- package/bin/src/mcp/permissions.js.map +1 -0
- package/bin/src/mcp/persistence.d.ts.map +1 -0
- package/bin/src/mcp/persistence.js +171 -0
- package/bin/src/mcp/persistence.js.map +1 -0
- package/bin/src/mcp/recovery.d.ts.map +1 -0
- package/bin/src/mcp/recovery.js +75 -0
- package/bin/src/mcp/recovery.js.map +1 -0
- package/bin/src/mcp/telemetry.d.ts.map +1 -0
- package/bin/src/mcp/telemetry.js +82 -0
- package/bin/src/mcp/telemetry.js.map +1 -0
- package/bin/src/mcp/toolRouter.d.ts.map +1 -0
- package/bin/src/mcp/toolRouter.js +127 -0
- package/bin/src/mcp/toolRouter.js.map +1 -0
- package/bin/src/mcp/transport.d.ts.map +1 -0
- package/bin/src/mcp/transport.js +328 -0
- package/bin/src/mcp/transport.js.map +1 -0
- package/bin/src/mcp/types.d.ts.map +1 -0
- package/bin/src/mcp/types.js +6 -0
- package/bin/src/mcp/types.js.map +1 -0
- package/bin/src/server/server.d.ts.map +1 -0
- package/bin/src/server/server.js +989 -0
- package/bin/src/server/server.js.map +1 -0
- package/bin/src/utils/admin.d.ts.map +1 -0
- package/bin/src/utils/admin.js +231 -0
- package/bin/src/utils/admin.js.map +1 -0
- package/bin/src/utils/auditLogger.d.ts.map +1 -0
- package/bin/src/utils/auditLogger.js +70 -0
- package/bin/src/utils/auditLogger.js.map +1 -0
- package/bin/src/utils/auth.d.ts.map +1 -0
- package/bin/src/utils/auth.js +679 -0
- package/bin/src/utils/auth.js.map +1 -0
- package/bin/src/utils/branding.d.ts.map +1 -0
- package/bin/src/utils/branding.js +14 -0
- package/bin/src/utils/branding.js.map +1 -0
- package/bin/src/utils/config.d.ts.map +1 -1
- package/bin/src/utils/config.js +124 -3
- package/bin/src/utils/config.js.map +1 -1
- package/bin/src/utils/email.d.ts.map +1 -0
- package/bin/src/utils/email.js +267 -0
- package/bin/src/utils/email.js.map +1 -0
- package/bin/src/utils/firebaseAuth.d.ts.map +1 -0
- package/bin/src/utils/firebaseAuth.js +174 -0
- package/bin/src/utils/firebaseAuth.js.map +1 -0
- package/bin/src/utils/holoseneAuth.d.ts.map +1 -0
- package/bin/src/utils/holoseneAuth.js +307 -0
- package/bin/src/utils/holoseneAuth.js.map +1 -0
- package/bin/src/utils/language.d.ts.map +1 -0
- package/bin/src/utils/language.js +181 -0
- package/bin/src/utils/language.js.map +1 -0
- package/bin/src/utils/markdown.d.ts.map +1 -0
- package/bin/src/utils/markdown.js +215 -0
- package/bin/src/utils/markdown.js.map +1 -0
- package/bin/src/utils/payment.d.ts.map +1 -0
- package/bin/src/utils/payment.js +290 -0
- package/bin/src/utils/payment.js.map +1 -0
- package/bin/src/utils/pricing.d.ts.map +1 -0
- package/bin/src/utils/pricing.js +165 -0
- package/bin/src/utils/pricing.js.map +1 -0
- package/bin/src/utils/razorpay.d.ts.map +1 -0
- package/bin/src/utils/razorpay.js +144 -0
- package/bin/src/utils/razorpay.js.map +1 -0
- package/bin/src/utils/session.d.ts.map +1 -0
- package/bin/src/utils/session.js +82 -0
- package/bin/src/utils/session.js.map +1 -0
- package/bin/src/utils/updateCheck.d.ts.map +1 -0
- package/bin/src/utils/updateCheck.js +108 -0
- package/bin/src/utils/updateCheck.js.map +1 -0
- package/downloads/invoice-2026-03.pdf +1 -0
- package/firebase.json +74 -0
- package/package.json +49 -46
- package/web/ScoutFavicon.png +0 -0
- package/web/app.js +3944 -0
- package/web/styles.css +3081 -0
- package/.ft/context.json +0 -17
- package/email-template.txt +0 -44
|
@@ -0,0 +1,679 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import crypto from 'crypto';
|
|
5
|
+
const AUTH_DIR = path.join(os.homedir(), '.ft');
|
|
6
|
+
const AUTH_FILE = path.join(AUTH_DIR, 'auth.json');
|
|
7
|
+
const DEFAULT_AUTH_CONFIG = {
|
|
8
|
+
token: null,
|
|
9
|
+
refreshToken: null,
|
|
10
|
+
tenantId: null,
|
|
11
|
+
provider: null,
|
|
12
|
+
user: null,
|
|
13
|
+
loggedInAt: null,
|
|
14
|
+
creditsUsed: 0,
|
|
15
|
+
creditsTotal: 100,
|
|
16
|
+
currentTier: 'free',
|
|
17
|
+
renewAt: null,
|
|
18
|
+
cancelAtPeriodEnd: false,
|
|
19
|
+
pendingDowngrade: null,
|
|
20
|
+
paidTier: null,
|
|
21
|
+
byokEnabled: false,
|
|
22
|
+
customLlmProvider: 'nvidia',
|
|
23
|
+
customApiKey: undefined,
|
|
24
|
+
customBaseUrl: undefined,
|
|
25
|
+
};
|
|
26
|
+
const ACCOUNTS_FILE = path.join(AUTH_DIR, 'accounts.json');
|
|
27
|
+
const SYSTEM_PEPPER = 'ft_scout_ledger_integrity_pepper_v1_9a8b7c';
|
|
28
|
+
export const TIER_RANK = {
|
|
29
|
+
free: 0,
|
|
30
|
+
pro: 1,
|
|
31
|
+
enterprise: 2,
|
|
32
|
+
};
|
|
33
|
+
export const TIER_CREDIT_LIMITS = {
|
|
34
|
+
free: 100,
|
|
35
|
+
pro: 1000,
|
|
36
|
+
enterprise: 10000,
|
|
37
|
+
};
|
|
38
|
+
export const TIER_SESSION_LIMITS = {
|
|
39
|
+
free: 1,
|
|
40
|
+
pro: 5,
|
|
41
|
+
enterprise: Infinity,
|
|
42
|
+
};
|
|
43
|
+
function ensureAuthDir() {
|
|
44
|
+
if (!fs.existsSync(AUTH_DIR)) {
|
|
45
|
+
fs.mkdirSync(AUTH_DIR, { recursive: true });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function getDeviceIntegrityKey() {
|
|
49
|
+
const machineFingerprint = `${os.hostname()}:${os.platform()}:${os.arch()}:${os.homedir()}:${SYSTEM_PEPPER}`;
|
|
50
|
+
return crypto.createHash('sha256').update(machineFingerprint).digest('hex');
|
|
51
|
+
}
|
|
52
|
+
export function generateRecordSignature(record) {
|
|
53
|
+
const key = getDeviceIntegrityKey();
|
|
54
|
+
const payload = [
|
|
55
|
+
record.userId || '',
|
|
56
|
+
(record.email || '').toLowerCase().trim(),
|
|
57
|
+
record.currentTier || 'free',
|
|
58
|
+
record.creditsUsed,
|
|
59
|
+
record.creditsTotal,
|
|
60
|
+
record.renewAt,
|
|
61
|
+
record.transactionId || '',
|
|
62
|
+
].join('|');
|
|
63
|
+
return crypto.createHmac('sha256', key).update(payload).digest('hex');
|
|
64
|
+
}
|
|
65
|
+
export function verifyRecordIntegrity(record) {
|
|
66
|
+
if (!record._sig || typeof record._sig !== 'string')
|
|
67
|
+
return false;
|
|
68
|
+
const cleanRecord = { ...record };
|
|
69
|
+
delete cleanRecord._sig;
|
|
70
|
+
const expected = generateRecordSignature(cleanRecord);
|
|
71
|
+
try {
|
|
72
|
+
const a = Buffer.from(record._sig, 'hex');
|
|
73
|
+
const b = Buffer.from(expected, 'hex');
|
|
74
|
+
return a.length === b.length && crypto.timingSafeEqual(a, b);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
export function generateAuthSignature(config) {
|
|
81
|
+
const key = getDeviceIntegrityKey();
|
|
82
|
+
const userId = config.user?.id || '';
|
|
83
|
+
const email = (config.user?.email || '').toLowerCase().trim();
|
|
84
|
+
const payload = [
|
|
85
|
+
userId,
|
|
86
|
+
email,
|
|
87
|
+
config.currentTier || 'free',
|
|
88
|
+
config.creditsUsed ?? 0,
|
|
89
|
+
config.creditsTotal ?? 100,
|
|
90
|
+
config.renewAt || '',
|
|
91
|
+
config.transactionId || '',
|
|
92
|
+
].join('|');
|
|
93
|
+
return crypto.createHmac('sha256', key).update(payload).digest('hex');
|
|
94
|
+
}
|
|
95
|
+
export function verifyAuthIntegrity(config) {
|
|
96
|
+
if (!config.user)
|
|
97
|
+
return true; // Unauthenticated default state is safe
|
|
98
|
+
if (!config._sig || typeof config._sig !== 'string')
|
|
99
|
+
return false;
|
|
100
|
+
const cleanConfig = { ...config };
|
|
101
|
+
delete cleanConfig._sig;
|
|
102
|
+
const expected = generateAuthSignature(cleanConfig);
|
|
103
|
+
try {
|
|
104
|
+
const a = Buffer.from(config._sig, 'hex');
|
|
105
|
+
const b = Buffer.from(expected, 'hex');
|
|
106
|
+
return a.length === b.length && crypto.timingSafeEqual(a, b);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
export function loadAccountsLedger() {
|
|
113
|
+
if (!fs.existsSync(ACCOUNTS_FILE)) {
|
|
114
|
+
return {};
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
const raw = fs.readFileSync(ACCOUNTS_FILE, 'utf-8');
|
|
118
|
+
const ledger = JSON.parse(raw);
|
|
119
|
+
let hasTampered = false;
|
|
120
|
+
for (const key of Object.keys(ledger)) {
|
|
121
|
+
const record = ledger[key];
|
|
122
|
+
if (!verifyRecordIntegrity(record)) {
|
|
123
|
+
hasTampered = true;
|
|
124
|
+
console.warn(`\x1b[33m[Scout Security Guard] Tampering detected in account ledger for ${record.email || key}! Reverting to secure defaults.\x1b[0m`);
|
|
125
|
+
record.currentTier = 'free';
|
|
126
|
+
record.creditsTotal = 100;
|
|
127
|
+
// Clamping usage so they cannot reset usage by tampering
|
|
128
|
+
record.creditsUsed = 100;
|
|
129
|
+
delete record.transactionId;
|
|
130
|
+
delete record._sig;
|
|
131
|
+
record._sig = generateRecordSignature(record);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
const allowedMax = TIER_CREDIT_LIMITS[record.currentTier] || 100;
|
|
135
|
+
if (record.creditsTotal > allowedMax) {
|
|
136
|
+
record.creditsTotal = allowedMax;
|
|
137
|
+
delete record._sig;
|
|
138
|
+
record._sig = generateRecordSignature(record);
|
|
139
|
+
hasTampered = true;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (hasTampered) {
|
|
144
|
+
fs.writeFileSync(ACCOUNTS_FILE, JSON.stringify(ledger, null, 2), 'utf-8');
|
|
145
|
+
}
|
|
146
|
+
return ledger;
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return {};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
export function saveAccountsLedger(ledger) {
|
|
153
|
+
ensureAuthDir();
|
|
154
|
+
for (const key of Object.keys(ledger)) {
|
|
155
|
+
const record = ledger[key];
|
|
156
|
+
const allowedMax = TIER_CREDIT_LIMITS[record.currentTier] || 100;
|
|
157
|
+
record.creditsTotal = Math.min(record.creditsTotal, allowedMax);
|
|
158
|
+
delete record._sig;
|
|
159
|
+
record._sig = generateRecordSignature(record);
|
|
160
|
+
}
|
|
161
|
+
fs.writeFileSync(ACCOUNTS_FILE, JSON.stringify(ledger, null, 2), 'utf-8');
|
|
162
|
+
}
|
|
163
|
+
function getUserAccountKey(user) {
|
|
164
|
+
if (!user)
|
|
165
|
+
return null;
|
|
166
|
+
if (user.email && user.email.trim()) {
|
|
167
|
+
return user.email.trim().toLowerCase();
|
|
168
|
+
}
|
|
169
|
+
if (user.id && user.id.trim()) {
|
|
170
|
+
return user.id.trim().toLowerCase();
|
|
171
|
+
}
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
function computeNextRenewalDate(fromDate) {
|
|
175
|
+
const d = new Date(fromDate || new Date());
|
|
176
|
+
d.setMonth(d.getMonth() + 1);
|
|
177
|
+
return d;
|
|
178
|
+
}
|
|
179
|
+
export function loadAuthConfig() {
|
|
180
|
+
if (!fs.existsSync(AUTH_FILE)) {
|
|
181
|
+
return { ...DEFAULT_AUTH_CONFIG };
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
const raw = fs.readFileSync(AUTH_FILE, 'utf-8');
|
|
185
|
+
const parsed = JSON.parse(raw);
|
|
186
|
+
const user = parsed.user && typeof parsed.user === 'object' ? parsed.user : null;
|
|
187
|
+
const userKey = getUserAccountKey(user);
|
|
188
|
+
const isAuthTampered = user && !verifyAuthIntegrity(parsed);
|
|
189
|
+
let creditsUsed = typeof parsed.creditsUsed === 'number' ? parsed.creditsUsed : 0;
|
|
190
|
+
let creditsTotal = typeof parsed.creditsTotal === 'number' ? parsed.creditsTotal : 100;
|
|
191
|
+
let currentTier = typeof parsed.currentTier === 'string' ? parsed.currentTier : 'free';
|
|
192
|
+
let renewAt = typeof parsed.renewAt === 'string' ? parsed.renewAt : null;
|
|
193
|
+
let transactionId = typeof parsed.transactionId === 'string' ? parsed.transactionId : undefined;
|
|
194
|
+
if (userKey && user) {
|
|
195
|
+
const ledger = loadAccountsLedger();
|
|
196
|
+
const record = ledger[userKey];
|
|
197
|
+
if (isAuthTampered) {
|
|
198
|
+
console.warn(`\x1b[33m[Scout Security Guard] Tampering detected in auth session! Restoring verified ledger state.\x1b[0m`);
|
|
199
|
+
}
|
|
200
|
+
if (record) {
|
|
201
|
+
const now = new Date();
|
|
202
|
+
const renewDate = new Date(record.renewAt);
|
|
203
|
+
const isRenewalDue = !Number.isNaN(renewDate.getTime()) && now >= renewDate;
|
|
204
|
+
// Auto-promote tier if a paidTier is active and not expired
|
|
205
|
+
if (record.paidTier && record.paidTier !== 'free' && record.currentTier === 'free') {
|
|
206
|
+
if (isRenewalDue) {
|
|
207
|
+
record.paidTier = null;
|
|
208
|
+
ledger[userKey] = record;
|
|
209
|
+
saveAccountsLedger(ledger);
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
record.currentTier = record.paidTier;
|
|
213
|
+
record.creditsTotal = TIER_CREDIT_LIMITS[record.paidTier] || 1000;
|
|
214
|
+
ledger[userKey] = record;
|
|
215
|
+
saveAccountsLedger(ledger);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (isRenewalDue) {
|
|
219
|
+
if (record.currentTier !== 'free') {
|
|
220
|
+
// Paid plan has reached its expiration time: automatically shift to Free tier
|
|
221
|
+
record.currentTier = 'free';
|
|
222
|
+
record.creditsTotal = TIER_CREDIT_LIMITS['free'] || 100;
|
|
223
|
+
record.creditsUsed = 0;
|
|
224
|
+
record.cancelAtPeriodEnd = false;
|
|
225
|
+
record.pendingDowngrade = null;
|
|
226
|
+
record.paidTier = null;
|
|
227
|
+
delete record.transactionId;
|
|
228
|
+
const nextRenewal = new Date(now.getFullYear(), now.getMonth() + 1, 1);
|
|
229
|
+
record.renewAt = nextRenewal.toISOString();
|
|
230
|
+
record.lastResetAt = now.toISOString();
|
|
231
|
+
ledger[userKey] = record;
|
|
232
|
+
saveAccountsLedger(ledger);
|
|
233
|
+
creditsUsed = 0;
|
|
234
|
+
creditsTotal = record.creditsTotal;
|
|
235
|
+
currentTier = record.currentTier;
|
|
236
|
+
renewAt = record.renewAt;
|
|
237
|
+
transactionId = undefined;
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
// Renewal date has arrived for free tier: reset credits for the new month cycle
|
|
241
|
+
const nextRenewal = new Date(renewDate);
|
|
242
|
+
while (nextRenewal <= now) {
|
|
243
|
+
nextRenewal.setMonth(nextRenewal.getMonth() + 1);
|
|
244
|
+
}
|
|
245
|
+
record.creditsUsed = 0;
|
|
246
|
+
record.renewAt = nextRenewal.toISOString();
|
|
247
|
+
record.lastResetAt = now.toISOString();
|
|
248
|
+
ledger[userKey] = record;
|
|
249
|
+
saveAccountsLedger(ledger);
|
|
250
|
+
creditsUsed = 0;
|
|
251
|
+
renewAt = record.renewAt;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
// Before renewal date: strictly enforce accumulated creditsUsed
|
|
256
|
+
creditsUsed = record.creditsUsed;
|
|
257
|
+
creditsTotal = record.creditsTotal || creditsTotal;
|
|
258
|
+
currentTier = record.currentTier || currentTier;
|
|
259
|
+
renewAt = record.renewAt || renewAt;
|
|
260
|
+
transactionId = record.transactionId;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
else if (isAuthTampered) {
|
|
264
|
+
// No ledger record and tampered auth session: revert to free tier with 0 credits
|
|
265
|
+
currentTier = 'free';
|
|
266
|
+
creditsTotal = 100;
|
|
267
|
+
creditsUsed = 100;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
const allowedMax = TIER_CREDIT_LIMITS[currentTier] || 100;
|
|
271
|
+
creditsTotal = Math.min(creditsTotal, allowedMax);
|
|
272
|
+
const record = (userKey && user) ? loadAccountsLedger()[userKey] : undefined;
|
|
273
|
+
const config = {
|
|
274
|
+
token: typeof parsed.token === 'string' ? parsed.token : null,
|
|
275
|
+
refreshToken: typeof parsed.refreshToken === 'string' ? parsed.refreshToken : null,
|
|
276
|
+
tenantId: typeof parsed.tenantId === 'string' ? parsed.tenantId : null,
|
|
277
|
+
provider: typeof parsed.provider === 'string' ? parsed.provider : 'firebase',
|
|
278
|
+
user,
|
|
279
|
+
loggedInAt: typeof parsed.loggedInAt === 'string' ? parsed.loggedInAt : null,
|
|
280
|
+
creditsUsed,
|
|
281
|
+
creditsTotal,
|
|
282
|
+
currentTier,
|
|
283
|
+
renewAt,
|
|
284
|
+
cancelAtPeriodEnd: record ? Boolean(record.cancelAtPeriodEnd) : (typeof parsed.cancelAtPeriodEnd === 'boolean' ? parsed.cancelAtPeriodEnd : false),
|
|
285
|
+
pendingDowngrade: record ? (record.pendingDowngrade || undefined) : (typeof parsed.pendingDowngrade === 'string' ? parsed.pendingDowngrade : undefined),
|
|
286
|
+
paidTier: record ? (record.paidTier || undefined) : (typeof parsed.paidTier === 'string' ? parsed.paidTier : undefined),
|
|
287
|
+
transactionId,
|
|
288
|
+
byokEnabled: typeof parsed.byokEnabled === 'boolean' ? parsed.byokEnabled : false,
|
|
289
|
+
customLlmProvider: typeof parsed.customLlmProvider === 'string' ? parsed.customLlmProvider : 'nvidia',
|
|
290
|
+
customApiKey: typeof parsed.customApiKey === 'string' ? parsed.customApiKey : undefined,
|
|
291
|
+
customBaseUrl: typeof parsed.customBaseUrl === 'string' ? parsed.customBaseUrl : undefined,
|
|
292
|
+
};
|
|
293
|
+
if (isAuthTampered && user) {
|
|
294
|
+
saveAuthConfig(config);
|
|
295
|
+
}
|
|
296
|
+
return config;
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
return { ...DEFAULT_AUTH_CONFIG };
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
export function saveAuthConfig(config) {
|
|
303
|
+
ensureAuthDir();
|
|
304
|
+
const ledger = loadAccountsLedger();
|
|
305
|
+
const userKey = getUserAccountKey(config.user);
|
|
306
|
+
let finalConfig = { ...config };
|
|
307
|
+
if (userKey && config.user) {
|
|
308
|
+
const existing = ledger[userKey];
|
|
309
|
+
const now = new Date();
|
|
310
|
+
if (existing) {
|
|
311
|
+
const effectiveRenewAt = config.renewAt || existing.renewAt;
|
|
312
|
+
const renewDate = new Date(effectiveRenewAt);
|
|
313
|
+
const isRenewalDue = !Number.isNaN(renewDate.getTime()) && now >= renewDate;
|
|
314
|
+
if (isRenewalDue) {
|
|
315
|
+
if (existing.currentTier !== 'free') {
|
|
316
|
+
// Paid plan has expired / is revoked on its expiration time: automatically shift to free tier
|
|
317
|
+
const nextRenewal = new Date(now.getFullYear(), now.getMonth() + 1, 1);
|
|
318
|
+
const updatedRecord = {
|
|
319
|
+
...existing,
|
|
320
|
+
creditsUsed: 0,
|
|
321
|
+
renewAt: nextRenewal.toISOString(),
|
|
322
|
+
lastResetAt: now.toISOString(),
|
|
323
|
+
currentTier: 'free',
|
|
324
|
+
creditsTotal: TIER_CREDIT_LIMITS['free'] || 100,
|
|
325
|
+
cancelAtPeriodEnd: false,
|
|
326
|
+
pendingDowngrade: null,
|
|
327
|
+
paidTier: null,
|
|
328
|
+
transactionId: undefined,
|
|
329
|
+
};
|
|
330
|
+
ledger[userKey] = updatedRecord;
|
|
331
|
+
finalConfig.creditsUsed = 0;
|
|
332
|
+
finalConfig.renewAt = updatedRecord.renewAt;
|
|
333
|
+
finalConfig.currentTier = 'free';
|
|
334
|
+
finalConfig.creditsTotal = 100;
|
|
335
|
+
finalConfig.cancelAtPeriodEnd = false;
|
|
336
|
+
finalConfig.pendingDowngrade = null;
|
|
337
|
+
finalConfig.paidTier = null;
|
|
338
|
+
delete finalConfig.transactionId;
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
// Monthly cycle reached for free tier: Reset credits to 0 and calculate next renewal
|
|
342
|
+
const nextRenewal = new Date(renewDate);
|
|
343
|
+
while (nextRenewal <= now) {
|
|
344
|
+
nextRenewal.setMonth(nextRenewal.getMonth() + 1);
|
|
345
|
+
}
|
|
346
|
+
const updatedRecord = {
|
|
347
|
+
...existing,
|
|
348
|
+
creditsUsed: 0,
|
|
349
|
+
renewAt: nextRenewal.toISOString(),
|
|
350
|
+
lastResetAt: now.toISOString(),
|
|
351
|
+
currentTier: 'free',
|
|
352
|
+
creditsTotal: 100,
|
|
353
|
+
cancelAtPeriodEnd: false,
|
|
354
|
+
pendingDowngrade: null,
|
|
355
|
+
};
|
|
356
|
+
ledger[userKey] = updatedRecord;
|
|
357
|
+
finalConfig.creditsUsed = 0;
|
|
358
|
+
finalConfig.renewAt = updatedRecord.renewAt;
|
|
359
|
+
finalConfig.currentTier = 'free';
|
|
360
|
+
finalConfig.creditsTotal = 100;
|
|
361
|
+
finalConfig.cancelAtPeriodEnd = false;
|
|
362
|
+
finalConfig.pendingDowngrade = null;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
else if (config.resetCredits === true) {
|
|
366
|
+
// Explicit verified plan upgrade / subscription purchase
|
|
367
|
+
// Ensure users cannot downgrade paid plans even with resetCredits
|
|
368
|
+
const incomingTier = config.currentTier || existing.currentTier || 'free';
|
|
369
|
+
const existingTierRank = TIER_RANK[existing.currentTier] ?? 0;
|
|
370
|
+
const incomingTierRank = TIER_RANK[incomingTier] ?? 0;
|
|
371
|
+
const resolvedTier = incomingTierRank < existingTierRank ? existing.currentTier : incomingTier;
|
|
372
|
+
const resolvedTotal = TIER_CREDIT_LIMITS[resolvedTier] || 100;
|
|
373
|
+
const resolvedRenewAt = config.renewAt || existing.renewAt || computeNextRenewalDate().toISOString();
|
|
374
|
+
const updatedRecord = {
|
|
375
|
+
...existing,
|
|
376
|
+
creditsUsed: 0,
|
|
377
|
+
currentTier: resolvedTier,
|
|
378
|
+
creditsTotal: resolvedTotal,
|
|
379
|
+
renewAt: resolvedRenewAt,
|
|
380
|
+
lastResetAt: now.toISOString(),
|
|
381
|
+
transactionId: config.transactionId || existing.transactionId,
|
|
382
|
+
cancelAtPeriodEnd: false,
|
|
383
|
+
pendingDowngrade: null,
|
|
384
|
+
paidTier: resolvedTier,
|
|
385
|
+
};
|
|
386
|
+
ledger[userKey] = updatedRecord;
|
|
387
|
+
finalConfig.creditsUsed = 0;
|
|
388
|
+
finalConfig.currentTier = resolvedTier;
|
|
389
|
+
finalConfig.creditsTotal = resolvedTotal;
|
|
390
|
+
finalConfig.renewAt = resolvedRenewAt;
|
|
391
|
+
finalConfig.transactionId = updatedRecord.transactionId;
|
|
392
|
+
finalConfig.cancelAtPeriodEnd = false;
|
|
393
|
+
finalConfig.pendingDowngrade = null;
|
|
394
|
+
finalConfig.paidTier = resolvedTier;
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
// Before renewal date: strictly maintain consumed credits
|
|
398
|
+
const candidateUsed = typeof config.creditsUsed === 'number' ? config.creditsUsed : 0;
|
|
399
|
+
const resolvedUsed = Math.max(existing.creditsUsed, candidateUsed);
|
|
400
|
+
// DO NOT ALLOW DOWNGRADING PAID PLANS:
|
|
401
|
+
// If existing is on a paid plan, prevent manual downgrade to Free or a lower paid plan
|
|
402
|
+
const existingTierRank = TIER_RANK[existing.currentTier] ?? 0;
|
|
403
|
+
const candidateTier = config.currentTier || existing.currentTier || 'free';
|
|
404
|
+
const candidateTierRank = TIER_RANK[candidateTier] ?? 0;
|
|
405
|
+
// If candidate tier is lower than existing paid tier, ignore downgrade and keep existing
|
|
406
|
+
let resolvedTier = existing.currentTier;
|
|
407
|
+
if (candidateTierRank >= existingTierRank) {
|
|
408
|
+
resolvedTier = candidateTier;
|
|
409
|
+
}
|
|
410
|
+
const resolvedTotal = TIER_CREDIT_LIMITS[resolvedTier] || existing.creditsTotal || 100;
|
|
411
|
+
const resolvedRenewAt = config.renewAt || existing.renewAt || computeNextRenewalDate().toISOString();
|
|
412
|
+
const resolvedPaidTier = resolvedTier !== 'free' ? resolvedTier : null;
|
|
413
|
+
const updatedRecord = {
|
|
414
|
+
...existing,
|
|
415
|
+
userId: config.user.id || existing.userId,
|
|
416
|
+
email: config.user.email || existing.email,
|
|
417
|
+
creditsUsed: resolvedUsed,
|
|
418
|
+
creditsTotal: resolvedTotal,
|
|
419
|
+
currentTier: resolvedTier,
|
|
420
|
+
renewAt: resolvedRenewAt,
|
|
421
|
+
cancelAtPeriodEnd: false,
|
|
422
|
+
pendingDowngrade: null,
|
|
423
|
+
paidTier: resolvedPaidTier,
|
|
424
|
+
transactionId: config.transactionId || existing.transactionId,
|
|
425
|
+
byokEnabled: config.byokEnabled !== undefined ? config.byokEnabled : existing.byokEnabled,
|
|
426
|
+
customLlmProvider: config.customLlmProvider || existing.customLlmProvider,
|
|
427
|
+
customApiKey: config.customApiKey !== undefined ? config.customApiKey : existing.customApiKey,
|
|
428
|
+
customBaseUrl: config.customBaseUrl !== undefined ? config.customBaseUrl : existing.customBaseUrl,
|
|
429
|
+
};
|
|
430
|
+
ledger[userKey] = updatedRecord;
|
|
431
|
+
finalConfig.creditsUsed = resolvedUsed;
|
|
432
|
+
finalConfig.currentTier = resolvedTier;
|
|
433
|
+
finalConfig.creditsTotal = resolvedTotal;
|
|
434
|
+
finalConfig.renewAt = resolvedRenewAt;
|
|
435
|
+
finalConfig.cancelAtPeriodEnd = false;
|
|
436
|
+
finalConfig.pendingDowngrade = null;
|
|
437
|
+
finalConfig.paidTier = resolvedPaidTier;
|
|
438
|
+
finalConfig.transactionId = updatedRecord.transactionId;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
// First time user registration / first login
|
|
443
|
+
const renewAt = config.renewAt || computeNextRenewalDate().toISOString();
|
|
444
|
+
const initialRecord = {
|
|
445
|
+
userId: config.user.id,
|
|
446
|
+
email: config.user.email,
|
|
447
|
+
currentTier: config.currentTier || 'free',
|
|
448
|
+
creditsUsed: config.creditsUsed ?? 0,
|
|
449
|
+
creditsTotal: config.creditsTotal ?? 100,
|
|
450
|
+
renewAt,
|
|
451
|
+
lastResetAt: now.toISOString(),
|
|
452
|
+
transactionId: config.transactionId,
|
|
453
|
+
cancelAtPeriodEnd: config.cancelAtPeriodEnd ?? false,
|
|
454
|
+
pendingDowngrade: config.pendingDowngrade ?? null,
|
|
455
|
+
paidTier: config.paidTier ?? (config.currentTier !== 'free' ? config.currentTier : null),
|
|
456
|
+
byokEnabled: config.byokEnabled ?? false,
|
|
457
|
+
customLlmProvider: config.customLlmProvider,
|
|
458
|
+
customApiKey: config.customApiKey,
|
|
459
|
+
customBaseUrl: config.customBaseUrl,
|
|
460
|
+
};
|
|
461
|
+
ledger[userKey] = initialRecord;
|
|
462
|
+
finalConfig.creditsUsed = initialRecord.creditsUsed;
|
|
463
|
+
finalConfig.creditsTotal = initialRecord.creditsTotal;
|
|
464
|
+
finalConfig.currentTier = initialRecord.currentTier;
|
|
465
|
+
finalConfig.renewAt = initialRecord.renewAt;
|
|
466
|
+
finalConfig.cancelAtPeriodEnd = initialRecord.cancelAtPeriodEnd;
|
|
467
|
+
finalConfig.pendingDowngrade = initialRecord.pendingDowngrade;
|
|
468
|
+
finalConfig.paidTier = initialRecord.paidTier;
|
|
469
|
+
finalConfig.transactionId = initialRecord.transactionId;
|
|
470
|
+
}
|
|
471
|
+
saveAccountsLedger(ledger);
|
|
472
|
+
}
|
|
473
|
+
const cleanConfig = { ...finalConfig };
|
|
474
|
+
delete cleanConfig._sig;
|
|
475
|
+
finalConfig._sig = generateAuthSignature(cleanConfig);
|
|
476
|
+
fs.writeFileSync(AUTH_FILE, JSON.stringify(finalConfig, null, 2), 'utf-8');
|
|
477
|
+
}
|
|
478
|
+
export function clearAuthConfig() {
|
|
479
|
+
ensureAuthDir();
|
|
480
|
+
try {
|
|
481
|
+
if (fs.existsSync(AUTH_FILE)) {
|
|
482
|
+
const current = loadAuthConfig();
|
|
483
|
+
const userKey = getUserAccountKey(current.user);
|
|
484
|
+
if (userKey && current.user) {
|
|
485
|
+
const ledger = loadAccountsLedger();
|
|
486
|
+
const existing = ledger[userKey];
|
|
487
|
+
ledger[userKey] = {
|
|
488
|
+
userId: current.user.id,
|
|
489
|
+
email: current.user.email,
|
|
490
|
+
currentTier: current.currentTier || existing?.currentTier || 'free',
|
|
491
|
+
creditsUsed: current.creditsUsed ?? existing?.creditsUsed ?? 0,
|
|
492
|
+
creditsTotal: current.creditsTotal ?? existing?.creditsTotal ?? 100,
|
|
493
|
+
renewAt: current.renewAt || existing?.renewAt || computeNextRenewalDate().toISOString(),
|
|
494
|
+
lastResetAt: existing?.lastResetAt || new Date().toISOString(),
|
|
495
|
+
byokEnabled: current.byokEnabled,
|
|
496
|
+
customLlmProvider: current.customLlmProvider,
|
|
497
|
+
customApiKey: current.customApiKey,
|
|
498
|
+
customBaseUrl: current.customBaseUrl,
|
|
499
|
+
};
|
|
500
|
+
saveAccountsLedger(ledger);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
catch { }
|
|
505
|
+
fs.writeFileSync(AUTH_FILE, JSON.stringify(DEFAULT_AUTH_CONFIG, null, 2), 'utf-8');
|
|
506
|
+
}
|
|
507
|
+
export function isLoggedIn() {
|
|
508
|
+
const config = loadAuthConfig();
|
|
509
|
+
return Boolean(config.token && config.user);
|
|
510
|
+
}
|
|
511
|
+
export function getAuthToken() {
|
|
512
|
+
const config = loadAuthConfig();
|
|
513
|
+
return config.token;
|
|
514
|
+
}
|
|
515
|
+
export function getCurrentUser() {
|
|
516
|
+
const config = loadAuthConfig();
|
|
517
|
+
return config.user;
|
|
518
|
+
}
|
|
519
|
+
export function getNextRenewalDate(config) {
|
|
520
|
+
const cfg = config || loadAuthConfig();
|
|
521
|
+
const baseDateStr = cfg.renewAt || cfg.user?.createdAt || cfg.loggedInAt;
|
|
522
|
+
const now = new Date();
|
|
523
|
+
if (baseDateStr) {
|
|
524
|
+
const baseDate = new Date(baseDateStr);
|
|
525
|
+
if (!Number.isNaN(baseDate.getTime())) {
|
|
526
|
+
const renewal = new Date(baseDate);
|
|
527
|
+
const renewalDay = renewal.getDate();
|
|
528
|
+
while (renewal <= now) {
|
|
529
|
+
renewal.setDate(1);
|
|
530
|
+
renewal.setMonth(renewal.getMonth() + 1);
|
|
531
|
+
const lastDayOfMonth = new Date(renewal.getFullYear(), renewal.getMonth() + 1, 0).getDate();
|
|
532
|
+
renewal.setDate(Math.min(renewalDay, lastDayOfMonth));
|
|
533
|
+
}
|
|
534
|
+
return renewal;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
// Default fallback: 1st of next month
|
|
538
|
+
return new Date(now.getFullYear(), now.getMonth() + 1, 1);
|
|
539
|
+
}
|
|
540
|
+
export function formatRenewalDate(date) {
|
|
541
|
+
const formatted = date.toLocaleDateString('en-US', {
|
|
542
|
+
month: 'short',
|
|
543
|
+
day: 'numeric',
|
|
544
|
+
year: 'numeric',
|
|
545
|
+
});
|
|
546
|
+
const now = new Date();
|
|
547
|
+
const diffMs = date.getTime() - now.getTime();
|
|
548
|
+
const diffDays = Math.ceil(diffMs / (1000 * 60 * 60 * 24));
|
|
549
|
+
if (diffDays <= 0) {
|
|
550
|
+
return `${formatted} (resets today)`;
|
|
551
|
+
}
|
|
552
|
+
else if (diffDays === 1) {
|
|
553
|
+
return `${formatted} (resets tomorrow)`;
|
|
554
|
+
}
|
|
555
|
+
else {
|
|
556
|
+
return `${formatted} (in ${diffDays} days)`;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
export function consumeCredit(amount = 1) {
|
|
560
|
+
const config = loadAuthConfig();
|
|
561
|
+
if (config.byokEnabled) {
|
|
562
|
+
return {
|
|
563
|
+
success: true,
|
|
564
|
+
isByok: true,
|
|
565
|
+
remaining: Infinity,
|
|
566
|
+
isLowCredit: false,
|
|
567
|
+
reason: 'BYOK Mode Active (Custom API / Proxy Endpoint used)',
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
const used = config.creditsUsed ?? 0;
|
|
571
|
+
const total = config.creditsTotal ?? 100;
|
|
572
|
+
const remaining = total - used;
|
|
573
|
+
if (remaining < amount) {
|
|
574
|
+
const renewalDate = getNextRenewalDate(config);
|
|
575
|
+
const renewalFormatted = formatRenewalDate(renewalDate);
|
|
576
|
+
return {
|
|
577
|
+
success: false,
|
|
578
|
+
isByok: false,
|
|
579
|
+
remaining: Math.max(0, remaining),
|
|
580
|
+
isLowCredit: true,
|
|
581
|
+
reason: `Credit limit reached for ${config.currentTier || 'Free'} plan (${used}/${total} credits consumed).`,
|
|
582
|
+
renewalFormatted,
|
|
583
|
+
renewalDate,
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
const remainingAfter = total - (used + amount);
|
|
587
|
+
const isLowCredit = remainingAfter <= Math.ceil(total * 0.15);
|
|
588
|
+
const updatedConfig = {
|
|
589
|
+
...config,
|
|
590
|
+
creditsUsed: used + amount,
|
|
591
|
+
};
|
|
592
|
+
saveAuthConfig(updatedConfig);
|
|
593
|
+
return {
|
|
594
|
+
success: true,
|
|
595
|
+
isByok: false,
|
|
596
|
+
remaining: remainingAfter,
|
|
597
|
+
isLowCredit,
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
export function checkTierAccess(feature) {
|
|
601
|
+
const config = loadAuthConfig();
|
|
602
|
+
if (config.byokEnabled) {
|
|
603
|
+
return { allowed: true };
|
|
604
|
+
}
|
|
605
|
+
const used = config.creditsUsed ?? 0;
|
|
606
|
+
const total = config.creditsTotal ?? 100;
|
|
607
|
+
if (used >= total) {
|
|
608
|
+
const renewalDate = getNextRenewalDate(config);
|
|
609
|
+
const renewalFormatted = formatRenewalDate(renewalDate);
|
|
610
|
+
return {
|
|
611
|
+
allowed: false,
|
|
612
|
+
reason: `Credit quota exhausted (${used}/${total} credits used). Upgrade your plan or enable BYOK mode in dashboard.`,
|
|
613
|
+
renewalFormatted,
|
|
614
|
+
renewalDate,
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
return { allowed: true };
|
|
618
|
+
}
|
|
619
|
+
export function getTierSessionLimit(tier) {
|
|
620
|
+
const current = (tier || loadAuthConfig().currentTier || 'free').toLowerCase();
|
|
621
|
+
return TIER_SESSION_LIMITS[current] ?? 1;
|
|
622
|
+
}
|
|
623
|
+
export function canStartNewSession(currentActiveCount, tier) {
|
|
624
|
+
const limit = getTierSessionLimit(tier);
|
|
625
|
+
if (currentActiveCount >= limit) {
|
|
626
|
+
return {
|
|
627
|
+
allowed: false,
|
|
628
|
+
limit,
|
|
629
|
+
current: currentActiveCount,
|
|
630
|
+
reason: `Concurrent session limit reached (${currentActiveCount}/${limit === Infinity ? 'Unlimited' : limit} active slots). Upgrade your plan to unlock more concurrent slots.`
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
return {
|
|
634
|
+
allowed: true,
|
|
635
|
+
limit,
|
|
636
|
+
current: currentActiveCount
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
export function checkTierFeatureCapability(capability, tierOverride) {
|
|
640
|
+
const config = loadAuthConfig();
|
|
641
|
+
const currentTier = (tierOverride || config.currentTier || 'free').toLowerCase();
|
|
642
|
+
const capabilities = {
|
|
643
|
+
autonomous_features: { minTier: 'pro', name: 'Autonomous Multi-Step Computer & Browser Automation' },
|
|
644
|
+
scratchpad: { minTier: 'pro', name: 'AI Scratchpad Reasoning & Working Memory' },
|
|
645
|
+
continuous_screen: { minTier: 'pro', name: 'Continuous Visual Aura HUD' },
|
|
646
|
+
audit_logging: { minTier: 'pro', name: 'Compliance & Activity Audit Logging' },
|
|
647
|
+
nvidia_inception_models: { minTier: 'pro', name: 'NVIDIA Inception NIM High-Throughput Reasoning' },
|
|
648
|
+
window_isolation: { minTier: 'enterprise', name: 'Per-Window Isolation & Privacy Masking' },
|
|
649
|
+
unlimited_sessions: { minTier: 'enterprise', name: 'Unlimited Concurrent Session Slots' },
|
|
650
|
+
};
|
|
651
|
+
const cap = capabilities[capability];
|
|
652
|
+
if (!cap) {
|
|
653
|
+
return { allowed: true, requiredTier: 'free', currentTier };
|
|
654
|
+
}
|
|
655
|
+
const tierRank = { free: 0, pro: 1, enterprise: 2 };
|
|
656
|
+
const userRank = tierRank[currentTier] ?? 0;
|
|
657
|
+
const requiredRank = tierRank[cap.minTier] ?? 0;
|
|
658
|
+
if (userRank >= requiredRank) {
|
|
659
|
+
return { allowed: true, requiredTier: cap.minTier, currentTier };
|
|
660
|
+
}
|
|
661
|
+
return {
|
|
662
|
+
allowed: false,
|
|
663
|
+
requiredTier: cap.minTier,
|
|
664
|
+
currentTier,
|
|
665
|
+
reason: `${cap.name} is exclusive to ${cap.minTier.toUpperCase()} and higher plans. Your current plan is ${currentTier.toUpperCase()}. Free developers can perform basic code editing, creating files and folders, and running commands.`
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
export function isProOrHigher(tierOverride) {
|
|
669
|
+
const config = loadAuthConfig();
|
|
670
|
+
const current = (tierOverride || config.currentTier || 'free').toLowerCase();
|
|
671
|
+
return current === 'pro' || current === 'enterprise';
|
|
672
|
+
}
|
|
673
|
+
export function canUseAutonomousFeatures(tierOverride) {
|
|
674
|
+
return isProOrHigher(tierOverride);
|
|
675
|
+
}
|
|
676
|
+
export function canUseScratchpad(tierOverride) {
|
|
677
|
+
return isProOrHigher(tierOverride);
|
|
678
|
+
}
|
|
679
|
+
//# sourceMappingURL=auth.js.map
|