mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 21:01:08 +02:00
Schließt die platform/product-split-Lücke: HEAD's apps/api/src/index.ts
referenziert seit dem Forms-M10d-Commit personasInternalRoutes /
personasAdminRoutes — die Implementierung lag aber noch nicht im Repo.
Build war strukturell broken bis hierhin.
Was wandert von mana-auth nach apps/api:
apps/api/src/modules/personas/
├── schema.ts — pgSchema('personas') mit personas /
│ persona_actions / persona_feedback;
│ userId ist plain text (Cross-DB-FK auf
│ mana-auth's auth.users geht nach Split nicht).
├── internal-routes.ts — service-key gated GET /due, POST /:id/actions
│ und POST /:id/feedback. Append-only +
│ idempotent über deterministische row-ids
│ (tickId-i-tool / tickId-module).
└── admin-routes.ts — admin-JWT gated CRUD; ruft mana-auth via
/api/v1/admin/users + /api/v1/auth/register
+ /api/v1/internal/users/:id/persona-stamp
für den User-Lifecycle.
Persona-runner-Client zeigt jetzt auf apps/api:
- config.ts: neues apiUrl-Feld (default http://localhost:3060,
Env MANA_API_URL); authUrl bleibt für /api/v1/auth/login + spaces.
- clients/mana-auth-internal.ts: drei Calls treffen jetzt
/api/v1/personas/internal/* statt mana-auth's
/api/v1/internal/personas/* — Datei-Name bleibt um Call-Site-Diff
klein zu halten.
- index.ts: ManaAuthInternalClient bekommt config.apiUrl statt authUrl.
Seed/Cleanup-Skripte:
- --api= als bevorzugter Flag, --auth= als Legacy-Alias (cached
Shell-History würde sonst hart brechen).
- default http://localhost:3060, Env MANA_API_URL.
- Endpoint-Pfade umgeschrieben:
POST /api/v1/admin/personas → /api/v1/personas/admin
DELETE /api/v1/admin/personas/:id → /api/v1/personas/admin/:id
drizzle.config.ts: schema-Array + schemaFilter um 'personas' erweitert.
DB-push ist Pflicht-Schritt vor erstem Boot, sonst 42P01 auf /due.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
120 lines
3.6 KiB
TypeScript
120 lines
3.6 KiB
TypeScript
#!/usr/bin/env bun
|
|
/**
|
|
* Seed the persona catalog via apps/api.
|
|
*
|
|
* Idempotent: re-running upserts metadata, never duplicates users. New
|
|
* personas in catalog.json get registered; existing ones get their
|
|
* descriptor refreshed.
|
|
*
|
|
* After the platform/product split, the personas admin endpoint lives
|
|
* in apps/api (which talks to mana-auth via service-key for user
|
|
* lifecycle). The script targets MANA_API_URL, defaulting to
|
|
* http://localhost:3060.
|
|
*
|
|
* Requires:
|
|
* - apps/api running (default http://localhost:3060)
|
|
* - mana-auth running (apps/api calls it for register + persona-stamp)
|
|
* - An admin-tier user JWT (export MANA_ADMIN_JWT or pass --jwt=…)
|
|
* - PERSONA_SEED_SECRET in env (or accept the dev fallback locally)
|
|
*
|
|
* Usage:
|
|
* pnpm seed:personas
|
|
* pnpm seed:personas --api=https://api.mana.how --jwt=eyJ…
|
|
*/
|
|
|
|
import { loadCatalog, type PersonaSpec } from './catalog';
|
|
import { personaPassword } from './password';
|
|
|
|
interface CliOptions {
|
|
apiUrl: string;
|
|
adminJwt: string;
|
|
dryRun: boolean;
|
|
}
|
|
|
|
function parseArgs(): CliOptions {
|
|
const args = process.argv.slice(2);
|
|
const get = (key: string): string | undefined => {
|
|
const found = args.find((a) => a.startsWith(`--${key}=`));
|
|
return found?.slice(`--${key}=`.length);
|
|
};
|
|
|
|
// Accept --api= going forward; --auth= still works as a legacy alias
|
|
// since some shells/scripts already cache the old flag.
|
|
const apiUrl = get('api') ?? get('auth') ?? process.env.MANA_API_URL ?? 'http://localhost:3060';
|
|
const adminJwt = get('jwt') ?? process.env.MANA_ADMIN_JWT ?? '';
|
|
const dryRun = args.includes('--dry-run');
|
|
|
|
if (!adminJwt) {
|
|
console.error(
|
|
'❌ Missing admin JWT. Set MANA_ADMIN_JWT or pass --jwt=… (must be a token for a user with role=admin).'
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
return { apiUrl, adminJwt, dryRun };
|
|
}
|
|
|
|
async function upsertPersona(opts: CliOptions, p: PersonaSpec): Promise<void> {
|
|
const password = personaPassword(p.email);
|
|
const body = {
|
|
email: p.email,
|
|
name: p.name,
|
|
password,
|
|
archetype: p.archetype,
|
|
systemPrompt: p.systemPrompt,
|
|
moduleMix: p.moduleMix,
|
|
tickCadence: p.tickCadence,
|
|
};
|
|
|
|
if (opts.dryRun) {
|
|
console.log(` · would upsert ${p.email} (${p.archetype})`);
|
|
return;
|
|
}
|
|
|
|
const res = await fetch(`${opts.apiUrl}/api/v1/personas/admin`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'content-type': 'application/json',
|
|
authorization: `Bearer ${opts.adminJwt}`,
|
|
},
|
|
body: JSON.stringify(body),
|
|
});
|
|
|
|
if (!res.ok) {
|
|
const text = await res.text().catch(() => '<unreadable body>');
|
|
throw new Error(`POST /personas/admin → ${res.status}: ${text.slice(0, 300)}`);
|
|
}
|
|
|
|
const result = (await res.json()) as { ok: true; userId: string; email: string };
|
|
console.log(` ✓ ${result.email} (${p.archetype}) user=${result.userId.slice(0, 8)}…`);
|
|
}
|
|
|
|
async function main(): Promise<void> {
|
|
const opts = parseArgs();
|
|
const { personas } = loadCatalog();
|
|
|
|
console.log(`▸ Persona catalog: ${personas.length} entries`);
|
|
console.log(`▸ API URL: ${opts.apiUrl}`);
|
|
if (opts.dryRun) console.log('▸ DRY-RUN — no requests will be sent');
|
|
console.log('');
|
|
|
|
const failures: Array<{ email: string; error: string }> = [];
|
|
for (const persona of personas) {
|
|
try {
|
|
await upsertPersona(opts, persona);
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : String(err);
|
|
console.error(` ✗ ${persona.email} — ${msg}`);
|
|
failures.push({ email: persona.email, error: msg });
|
|
}
|
|
}
|
|
|
|
console.log('');
|
|
if (failures.length > 0) {
|
|
console.error(`✗ Done with ${failures.length} failure(s).`);
|
|
process.exit(1);
|
|
}
|
|
console.log(`✓ Done. ${personas.length} personas upserted.`);
|
|
}
|
|
|
|
void main();
|