capsule-ai-cli
Version:
The AI Model Orchestrator - Intelligent multi-model workflows with device-locked licensing
157 lines • 5.77 kB
JavaScript
import { createHash, randomBytes } from 'crypto';
import { networkInterfaces, hostname, platform, cpus } from 'os';
import keytar from 'keytar';
export class LocalLicenseService {
SERVICE_NAME = 'capsule-cli';
ACCOUNT_NAME = 'license';
MACHINE_ID_ACCOUNT = 'machine_id';
async activate(licenseKey) {
try {
if (!this.isValidLicenseFormat(licenseKey)) {
return {
success: false,
error: 'Invalid license key format'
};
}
const machineId = await this.getMachineId();
const licenseInfo = this.decodeLicense(licenseKey, machineId);
if (!licenseInfo) {
return {
success: false,
error: 'Invalid license key for this machine'
};
}
await keytar.setPassword(this.SERVICE_NAME, this.ACCOUNT_NAME, licenseKey);
await keytar.setPassword(this.SERVICE_NAME, this.MACHINE_ID_ACCOUNT, machineId);
await keytar.setPassword(this.SERVICE_NAME, 'activation_date', new Date().toISOString());
return { success: true };
}
catch (error) {
return {
success: false,
error: `Failed to activate license: ${error.message}`
};
}
}
async validate() {
try {
const licenseKey = await keytar.getPassword(this.SERVICE_NAME, this.ACCOUNT_NAME);
if (!licenseKey) {
return { valid: false };
}
const storedMachineId = await keytar.getPassword(this.SERVICE_NAME, this.MACHINE_ID_ACCOUNT);
const currentMachineId = await this.getMachineId();
if (storedMachineId !== currentMachineId) {
return { valid: false };
}
const licenseInfo = this.decodeLicense(licenseKey, currentMachineId);
if (!licenseInfo) {
return { valid: false };
}
const activationDate = await keytar.getPassword(this.SERVICE_NAME, 'activation_date');
if (activationDate) {
licenseInfo.activatedAt = new Date(activationDate);
}
return {
valid: true,
license: licenseInfo
};
}
catch {
return { valid: false };
}
}
async deactivate() {
await keytar.deletePassword(this.SERVICE_NAME, this.ACCOUNT_NAME);
await keytar.deletePassword(this.SERVICE_NAME, 'activation_date');
}
isValidLicenseFormat(key) {
const pattern = /^[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$/;
return pattern.test(key);
}
decodeLicense(licenseKey, machineId) {
try {
const parts = licenseKey.split('-');
if (parts.length !== 4)
return null;
const tierCode = parts[0];
const payload = parts.slice(0, 3).join('');
const checksum = parts[3];
const hash = createHash('sha256')
.update(payload + machineId + 'capsule-secret-v2')
.digest('hex')
.substring(0, 4)
.toUpperCase();
if (hash !== checksum) {
const demoHash = createHash('sha256')
.update(payload + 'capsule-demo-v2')
.digest('hex')
.substring(0, 4)
.toUpperCase();
if (demoHash !== checksum) {
return null;
}
}
const tier = tierCode === 'PRO' ? 'pro' :
tierCode === 'CLOUD' ? 'cloud' :
'free';
const features = tier === 'pro' || tier === 'cloud' ? [
'model_fusion',
'smart_router',
'advanced_tools',
'local_analytics',
'priority_support'
] : [
'basic_chat',
'single_model'
];
if (tier === 'cloud') {
features.push('cloud_analytics', 'hosted_models', 'team_collaboration', 'api_access');
}
return {
key: licenseKey,
tier,
features,
activatedAt: new Date()
};
}
catch {
return null;
}
}
async getMachineId() {
const interfaces = networkInterfaces();
const macs = [];
for (const [name, nets] of Object.entries(interfaces)) {
if (nets) {
for (const net of nets) {
if (!net.internal && net.mac && net.mac !== '00:00:00:00:00:00') {
macs.push(net.mac);
}
}
}
}
const hardwareInfo = [
hostname(),
platform(),
cpus()[0]?.model || 'unknown',
macs.sort().join(',') || 'no-mac'
].join('|');
return createHash('sha256')
.update(hardwareInfo)
.digest('hex')
.substring(0, 16);
}
generateDemoLicenseKey(tier = 'pro') {
const tierCode = tier === 'cloud' ? 'CLOUD' : 'PRO';
const randomPart = randomBytes(4).toString('hex').toUpperCase();
const payload = `${tierCode}-${randomPart.substring(0, 4)}-${randomPart.substring(4, 8)}`;
const hash = createHash('sha256')
.update(payload + 'capsule-demo-v2')
.digest('hex')
.substring(0, 4)
.toUpperCase();
return `${payload}-${hash}`;
}
}
//# sourceMappingURL=license-service.js.map