Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
84 linesCodeBlameRaw
1/**
2 * People: a workspace's directory of the people in it, and each member's
3 * place in it: a title and what they own. Kept by identity beside
4 * membership; mirrors `crates/contracts/src/people.rs`.
5 */
6import type { User } from "./identity";
7import type { Result } from "./result";
8import type { TeamChannel, TeamLead, TeamRef, TeamRole, TeamVisibility } from "./teams";
9
10/** The longest title. */
11export const MAX_TITLE_LENGTH = 80;
12/** The most things one person owns, as their profile lists them, and the longest of them. */
13export const MAX_OWNS = 8;
14export const MAX_OWNS_LENGTH = 60;
15
16/** One member, as the directory and their profile show them. */
17export type DirectoryPerson = {
18 user_id: string;
19 username: string;
20 display_username?: string;
21 name: string | null;
22 avatar: string | null;
23 bio: string | null;
24 location: string | null;
25 pronouns: string | null;
26 /** An IANA time zone, for their local time. */
27 timezone: string | null;
28 role: "owner" | "member";
29 /** Their title in this workspace. */
30 title: string | null;
31 /** What they own: a few short phrases. */
32 owns: string[];
33 joined_at: string;
34};
35
36export type TeamPersonRef = { username: string; role: TeamRole };
37
38/** A team as the directory needs it. */
39export type DirectoryTeam = {
40 slug: string;
41 name: string;
42 description: string | null;
43 visibility: TeamVisibility;
44 parent: TeamRef | null;
45 lead: TeamLead | null;
46 channel: TeamChannel | null;
47 /** Its own people. */
48 people: TeamPersonRef[];
49 repos_count: number;
50};
51
52export type PeopleDirectory = {
53 people: DirectoryPerson[];
54 teams: DirectoryTeam[];
55 /** What members get on every repository. */
56 base_permission: "none" | "read" | "write" | "admin";
57 /** Whether the viewer owns the workspace: they set anyone's title and what they own. */
58 can_manage: boolean;
59};
60
61/** What changes on a member's profile; the rest stays. */
62export type MemberProfileChanges = {
63 title?: string;
64 owns?: string[];
65};
66
67export interface PeopleClient {
68 /** Everyone in a workspace and its teams, as the viewer may see them. Members only. */
69 peopleDirectory(viewer: User | null, workspace: string): Promise<Result<PeopleDirectory>>;
70 /** A member's title and what they own: themselves, or an owner. */
71 setMemberProfile(actor: User, workspace: string, username: string, changes: MemberProfileChanges): Promise<Result<DirectoryPerson>>;
72}
73
74/** What someone owns, as identity keeps it: trimmed, each once (ignoring case), at most eight. */
75export function cleanOwns(owns: readonly string[]): string[] {
76 const out: string[] = [];
77 for (const raw of owns) {
78 const phrase = raw.split(/\s+/).filter(Boolean).join(" ").slice(0, MAX_OWNS_LENGTH).replace(/[.,;]+$/, "").trim();
79 if (!phrase || out.some((kept) => kept.toLowerCase() === phrase.toLowerCase())) continue;
80 out.push(phrase);
81 if (out.length === MAX_OWNS) break;
82 }
83 return out;
84}