Add image-designer skill, align billing to DeepSeek ×1, and enrich Plaza demo.

Introduce AI image generation with chat shortcut and agent API, improve MindSpace chat-to-page save resolution, and seed Plaza covers with production deploy scripts.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
John
2026-06-16 16:29:19 -07:00
parent 03690ee354
commit 6d99d762da
155 changed files with 2748 additions and 190 deletions
+34 -20
View File
@@ -1,6 +1,7 @@
import crypto from 'node:crypto';
import fs from 'node:fs';
import path from 'node:path';
import argon2 from 'argon2';
import { computeDeltaCostCents, loadBillingConfig, normalizeTokenState } from './billing.mjs';
import { buildInsufficientBalancePayload, loadRechargeConfig } from './billing-recharge.mjs';
import {
@@ -64,25 +65,38 @@ function hashPasswordPbkdf2(password, salt) {
return crypto.pbkdf2Sync(password, salt, 100000, 64, 'sha512').toString('hex');
}
function hashPasswordArgon2id(password, salt) {
return crypto
.argon2Sync(PASSWORD_ALGORITHM_ARGON2ID, {
message: password,
nonce: Buffer.from(salt, 'hex'),
async function hashPasswordArgon2id(password, salt) {
if (typeof crypto.argon2Sync === 'function') {
return crypto
.argon2Sync(PASSWORD_ALGORITHM_ARGON2ID, {
message: password,
nonce: Buffer.from(salt, 'hex'),
parallelism: ARGON2_PARALLELISM,
tagLength: ARGON2_TAG_LENGTH,
memory: ARGON2_MEMORY,
passes: ARGON2_PASSES,
})
.toString('hex');
}
return (
await argon2.hash(password, {
type: argon2.argon2id,
salt: Buffer.from(salt, 'hex'),
memoryCost: ARGON2_MEMORY,
timeCost: ARGON2_PASSES,
parallelism: ARGON2_PARALLELISM,
tagLength: ARGON2_TAG_LENGTH,
memory: ARGON2_MEMORY,
passes: ARGON2_PASSES,
hashLength: ARGON2_TAG_LENGTH,
raw: true,
})
.toString('hex');
).toString('hex');
}
function createPasswordRecord(password, algorithm = PASSWORD_ALGORITHM_ARGON2ID) {
async function createPasswordRecord(password, algorithm = PASSWORD_ALGORITHM_ARGON2ID) {
const salt = crypto.randomBytes(16).toString('hex');
if (algorithm === PASSWORD_ALGORITHM_ARGON2ID) {
return {
salt,
passwordHash: hashPasswordArgon2id(password, salt),
passwordHash: await hashPasswordArgon2id(password, salt),
passwordAlgorithm: PASSWORD_ALGORITHM_ARGON2ID,
};
}
@@ -93,10 +107,10 @@ function createPasswordRecord(password, algorithm = PASSWORD_ALGORITHM_ARGON2ID)
};
}
function verifyPassword(password, row) {
async function verifyPassword(password, row) {
const algorithm = row.password_algorithm || PASSWORD_ALGORITHM_PBKDF2;
if (algorithm === PASSWORD_ALGORITHM_ARGON2ID) {
return safeEqual(hashPasswordArgon2id(password, row.salt), row.password_hash);
return safeEqual(await hashPasswordArgon2id(password, row.salt), row.password_hash);
}
return safeEqual(hashPasswordPbkdf2(password, row.salt), row.password_hash);
}
@@ -337,7 +351,7 @@ export function createUserAuth(pool, options = {}) {
return { ok: false, message: '请输入有效邮箱' };
}
const { salt, passwordHash, passwordAlgorithm } = createPasswordRecord(password);
const { salt, passwordHash, passwordAlgorithm } = await createPasswordRecord(password);
const userId = crypto.randomUUID();
const layout = await publishLayoutFor({ id: userId, username: normalized });
const workspaceRoot = layout.publishDir;
@@ -433,7 +447,7 @@ export function createUserAuth(pool, options = {}) {
return { ok: false, message: '用户名或密码错误' };
}
if (!verifyPassword(password, row)) {
if (!(await verifyPassword(password, row))) {
const current =
failure && failure.resetAt > now
? failure
@@ -447,7 +461,7 @@ export function createUserAuth(pool, options = {}) {
}
if ((row.password_algorithm || PASSWORD_ALGORITHM_PBKDF2) !== PASSWORD_ALGORITHM_ARGON2ID) {
const nextPassword = createPasswordRecord(password);
const nextPassword = await createPasswordRecord(password);
await pool.query(
`UPDATE h5_users
SET salt = ?, password_hash = ?, password_algorithm = ?, updated_at = ?
@@ -490,7 +504,7 @@ export function createUserAuth(pool, options = {}) {
return { ok: false, message: '账户已禁用,请联系管理员' };
}
const { salt, passwordHash, passwordAlgorithm } = createPasswordRecord(password);
const { salt, passwordHash, passwordAlgorithm } = await createPasswordRecord(password);
const now = Date.now();
await pool.query(
`UPDATE h5_users SET salt = ?, password_hash = ?, password_algorithm = ?, updated_at = ? WHERE id = ?`,
@@ -720,7 +734,7 @@ export function createUserAuth(pool, options = {}) {
const root = isAdmin
? path.resolve(workspaceRoot || path.join(usersRoot, normalized))
: (await publishLayoutFor({ id: userId, username: normalized })).publishDir;
const { salt, passwordHash, passwordAlgorithm } = createPasswordRecord(password);
const { salt, passwordHash, passwordAlgorithm } = await createPasswordRecord(password);
const now = Date.now();
const conn = await pool.getConnection();
@@ -1263,7 +1277,7 @@ export function createUserAuth(pool, options = {}) {
);
if (rows.length === 0) return;
const { salt, passwordHash, passwordAlgorithm } = createPasswordRecord(adminPassword);
const { salt, passwordHash, passwordAlgorithm } = await createPasswordRecord(adminPassword);
const now = Date.now();
await pool.query(
`UPDATE h5_users SET salt = ?, password_hash = ?, password_algorithm = ?, updated_at = ? WHERE id = ?`,
@@ -1923,7 +1937,7 @@ export function createUserAuth(pool, options = {}) {
}) => {
const normalized = await generateWechatUsername(openid);
const randomPassword = crypto.randomBytes(24).toString('base64url');
const { salt, passwordHash, passwordAlgorithm } = createPasswordRecord(randomPassword);
const { salt, passwordHash, passwordAlgorithm } = await createPasswordRecord(randomPassword);
const userId = crypto.randomUUID();
const layout = await publishLayoutFor({ id: userId, username: normalized });
const workspaceRoot = layout.publishDir;