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