Add Rain V0 for MeInput full-range chat analysis and delivery tooling.
Memind CI / Test, build, and release guards (push) Has been cancelled

Introduce rain-service orchestration, browser-safe chat skill filtering, MeInput
adapter helpers, and verify/deploy scripts so Rain mode can summarize recent input
without Memory V2 pollution.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-09-04 13:49:40 +08:00
parent b2a5caf67d
commit be464a5b8d
18 changed files with 1475 additions and 50 deletions
+176
View File
@@ -0,0 +1,176 @@
#!/usr/bin/env node
/**
* Deploy MeInput tutorial + case pages to 103 production under 唐 user.
* Usage: node scripts/deploy-tang-meinput-pages-103.mjs [--send-wechat]
*/
import { execSync } from 'node:child_process';
import crypto from 'node:crypto';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
const HOST = 'john@58.38.22.103';
const REMOTE_ROOT = '/Users/john/Project/Memind';
const TANG = 'a70ff537-8908-486e-9b6c-042e07cc25db';
const JOHN_LOCAL = '1c99b83b-0454-474f-a5d2-129d34506a32';
const FILES = [
'meinput-tkmind-portrait-tutorial.html',
'meinput-tkmind-portrait-tutorial-wechat.html',
'behavior-pattern-analysis.html',
];
const PUBLIC_BASE = `https://m.tkmind.cn/MindSpace/${TANG}/public`;
const NODE103 = '/opt/homebrew/opt/node@24/bin/node';
const sendWechat = process.argv.includes('--send-wechat');
function sh(cmd) {
execSync(cmd, { stdio: 'inherit' });
}
const remoteScript = `
import crypto from 'node:crypto';
import fs from 'node:fs';
import path from 'node:path';
import mysql from 'mysql2/promise';
const TANG = '${TANG}';
const REMOTE_ROOT = '${REMOTE_ROOT}';
const FILES = ${JSON.stringify(FILES)};
const REQUEST_ID = 'deploy-meinput-tutorial-20260904';
const PUBLIC_BASE = '${PUBLIC_BASE}';
process.loadEnvFile(path.join(REMOTE_ROOT, '.env'));
const pool = mysql.createPool({ uri: process.env.DATABASE_URL, connectionLimit: 2 });
async function ensureReady(relativePath) {
const now = Date.now();
const id = crypto.randomUUID();
await pool.query(
\`INSERT INTO h5_page_delivery_contracts
(id, user_id, request_id, workspace_relative_path, data_mode, status, ready_at, created_at, updated_at)
VALUES (?, ?, ?, ?, 'static', 'ready', ?, ?, ?)
ON DUPLICATE KEY UPDATE status = 'ready', ready_at = VALUES(ready_at), failure_reason = NULL, updated_at = VALUES(updated_at)\`,
[id, TANG, REQUEST_ID, relativePath, now, now, now],
);
}
async function main() {
const results = [];
for (const name of FILES) {
const relativePath = 'public/' + name;
const abs = path.join(REMOTE_ROOT, 'MindSpace', TANG, relativePath);
const exists = fs.existsSync(abs);
const size = exists ? fs.statSync(abs).size : 0;
if (exists) await ensureReady(relativePath);
results.push({ file: name, exists, size, url: PUBLIC_BASE + '/' + name });
}
console.log(JSON.stringify({ ok: true, pages: results }, null, 2));
await pool.end();
}
main().catch((e) => { console.error(e); process.exit(1); });
`.trim();
async function sendWechatLinks() {
const remoteWechat = `
import path from 'node:path';
import mysql from 'mysql2/promise';
const TANG = '${TANG}';
const PUBLIC_BASE = '${PUBLIC_BASE}';
const FILES = ${JSON.stringify(FILES)};
process.loadEnvFile('${REMOTE_ROOT}/.env');
const pool = mysql.createPool({ uri: process.env.DATABASE_URL, connectionLimit: 2 });
const appId = process.env.H5_WECHAT_MP_APP_ID ?? process.env.WECHAT_MP_APP_ID;
const appSecret = process.env.H5_WECHAT_MP_APP_SECRET ?? process.env.WECHAT_MP_APP_SECRET;
if (!appId || !appSecret) throw new Error('missing wechat credentials');
const [ident] = await pool.query(
'SELECT openid FROM h5_user_wechat_identities WHERE user_id = ? AND app_id = ? LIMIT 1',
[TANG, appId],
);
const openid = ident?.[0]?.openid;
if (!openid) throw new Error('tang not bound to wechat');
const tokenRes = await fetch('https://api.weixin.qq.com/cgi-bin/stable_token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ grant_type: 'client_credential', appid: appId, secret: appSecret }),
});
const tokenPayload = await tokenRes.json();
if (!tokenPayload.access_token) throw new Error(JSON.stringify(tokenPayload));
const lines = [
'MeInput × TKMind 教程与案例已发布:',
'',
'📱 教程(公众号发布版)',
PUBLIC_BASE + '/meinput-tkmind-portrait-tutorial-wechat.html',
'',
'📖 教程(网页阅读版)',
PUBLIC_BASE + '/meinput-tkmind-portrait-tutorial.html',
'',
'🧭 案例:用户A 全景画像',
PUBLIC_BASE + '/behavior-pattern-analysis.html',
];
const text = lines.join('\\n').slice(0, 2048);
const sendRes = await fetch('https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=' + encodeURIComponent(tokenPayload.access_token), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ touser: openid, msgtype: 'text', text: { content: text } }),
});
const sendPayload = await sendRes.json();
if (Number(sendPayload.errcode ?? 0) !== 0) throw new Error(JSON.stringify(sendPayload));
console.log(JSON.stringify({ ok: true, sent: true, openid: openid.slice(0, 8) + '...' }));
await pool.end();
`.trim();
const tmp = `${REMOTE_ROOT}/.tmp-tang-meinput-wechat.mjs`;
fs.writeFileSync(path.join(root, '.tmp-tang-wechat.mjs'), remoteWechat);
sh(`scp -q ${path.join(root, '.tmp-tang-wechat.mjs')} ${HOST}:${tmp}`);
sh(`ssh -o BatchMode=yes ${HOST} 'cd ${REMOTE_ROOT} && ${NODE103} ${tmp} && rm -f ${tmp}'`);
fs.unlinkSync(path.join(root, '.tmp-tang-wechat.mjs'));
}
async function main() {
const localDir = path.join(root, 'MindSpace', JOHN_LOCAL, 'public');
for (const f of FILES) {
const src = path.join(localDir, f);
if (!fs.existsSync(src)) throw new Error(`missing local file: ${src}`);
}
const remotePublic = `${REMOTE_ROOT}/MindSpace/${TANG}/public`;
sh(`ssh -o BatchMode=yes ${HOST} 'mkdir -p ${remotePublic}'`);
for (const f of FILES) {
sh(`scp -q ${path.join(localDir, f)} ${HOST}:${remotePublic}/${f}`);
}
const tmpLocal = path.join(root, '.tmp-tang-deploy-103.mjs');
fs.writeFileSync(tmpLocal, remoteScript);
const tmpRemote = `${REMOTE_ROOT}/.tmp-tang-meinput-deploy.mjs`;
sh(`scp -q ${tmpLocal} ${HOST}:${tmpRemote}`);
sh(`ssh -o BatchMode=yes ${HOST} 'cd ${REMOTE_ROOT} && ${NODE103} ${tmpRemote} && rm -f ${tmpRemote}'`);
fs.unlinkSync(tmpLocal);
console.log('\n=== Production URLs ===');
for (const f of FILES) console.log(`${PUBLIC_BASE}/${f}`);
for (const f of FILES) {
const code = execSync(
`curl -sS -o /dev/null -w '%{http_code}' 'https://m.tkmind.cn/MindSpace/${TANG}/public/${f}'`,
{ encoding: 'utf8' },
).trim();
console.log(`${f}: HTTP ${code}`);
}
if (sendWechat) {
await sendWechatLinks();
}
}
main().catch((e) => {
console.error(e);
process.exit(1);
});