Initialize repository with harness code and assets

Initial import of all source code, config, and README assets: the
packages workspace (cli, core, server, web, docs, landing, skills),
build scripts, tooling config, and CI workflows.

Includes the data-layout revision made on this branch: the local data
root defaults to ~/.penguin/data (PENGUIN_HOME still overrides; the
installer keeps its binaries in ~/.penguin), and every Agent lives
under <project>/agents/<agent>/ — path helpers, the three
agent-enumeration scans, the system prompt, built-in Skills, tests
and docs all follow the new layout.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ihk8iQuo3kv2aPjAYEPuR
This commit is contained in:
Yaowei Zheng
2026-07-19 14:06:53 +08:00
committed by GitHub
parent 056bed7aeb
commit 45bfae6e94
543 changed files with 92949 additions and 0 deletions
@@ -0,0 +1,96 @@
/**
* Admin user backend: user list / create / reset password / delete.
*
* - Create: username is the user_id (^[a-z][a-z0-9_-]{1,31}$); admin sets the initial
* password, flagged with password_is_initial; a default Project `proj-<username>` is
* auto-created, rolling back the user row on failure.
* - Reset password: also flags the password as initial and clears all of the user's
* login sessions (forcing re-login).
* - Delete: the built-in admin cannot be deleted; Projects owned by the user are
* deleted along with it (including data directories), with sessions/memberships/UI
* preferences cascade-deleted via foreign keys.
*/
import type { UserInfo } from "../api/types.js";
import { HttpError } from "../http/errors.js";
import { MIN_PASSWORD_LENGTH, toUserInfo } from "../auth/service.js";
import { hashPassword } from "../auth/password.js";
import type { AuthSessionsRepo } from "../db/repos/auth-sessions.js";
import type { ProjectsRepo } from "../db/repos/projects.js";
import type { UserRow, UsersRepo } from "../db/repos/users.js";
import { SEMANTIC_ID_RULE, USERNAME_PATTERN } from "./ids.js";
import type { ProjectService } from "./project-service.js";
export interface AdminServiceDeps {
users: UsersRepo;
authSessions: AuthSessionsRepo;
projects: ProjectsRepo;
projectService: ProjectService;
now?: () => Date;
}
export class AdminService {
private readonly now: () => Date;
constructor(private readonly deps: AdminServiceDeps) {
this.now = deps.now ?? (() => new Date());
}
listUsers(): UserInfo[] {
return this.deps.users.list().map(toUserInfo);
}
async createUser(userId: string, password: string): Promise<UserInfo> {
if (!USERNAME_PATTERN.test(userId)) {
throw new HttpError(400, "invalid_user_id", `用户名须为 2~32 位:${SEMANTIC_ID_RULE}。`);
}
if (password.length < MIN_PASSWORD_LENGTH) {
throw new HttpError(400, "invalid_password", "密码至少 8 个字符。");
}
if (this.deps.users.findById(userId)) {
throw new HttpError(409, "user_exists", `用户已存在:${userId}。`);
}
const user: UserRow = {
userId,
passwordHash: await hashPassword(password),
isAdmin: false,
passwordIsInitial: true,
createdAt: this.now().toISOString(),
};
this.deps.users.insert(user);
try {
await this.deps.projectService.provisionInitialProject(user, false);
} catch (err) {
// Compensation: roll back the user row if default Project creation fails (e.g. proj-<username> already taken).
this.deps.users.delete(user.userId);
throw err;
}
return toUserInfo(user);
}
/** Reset another user's password: flags it as initial and clears all their sessions (prompts a password change on next login). */
async resetPassword(userId: string, password: string): Promise<void> {
if (!this.deps.users.findById(userId)) {
throw new HttpError(404, "user_not_found", `用户不存在:${userId}。`);
}
if (password.length < MIN_PASSWORD_LENGTH) {
throw new HttpError(400, "invalid_password", "密码至少 8 个字符。");
}
this.deps.users.updatePassword(userId, await hashPassword(password), true);
this.deps.authSessions.deleteByUser(userId);
}
/** Delete user: the built-in admin cannot be deleted; owned Projects (including data directories) are deleted along with it. */
async deleteUser(userId: string): Promise<void> {
const target = this.deps.users.findById(userId);
if (!target) {
throw new HttpError(404, "user_not_found", `用户不存在:${userId}。`);
}
if (target.isAdmin) {
throw new HttpError(409, "cannot_delete_admin", "内置管理员不能删除。");
}
for (const project of this.deps.projects.listByOwner(userId)) {
await this.deps.projectService.destroyProject(project.projectId);
}
this.deps.users.delete(userId); // auth_sessions / project_members / ui_prefs cascade-deleted
}
}