Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Agents → Skills is the workspace's skill library in the open SKILL.md format: write a skill in g1t, import a SKILL.md or zip, link a repository whose .g1t/skills folders publish on every push, or save a finished session as a skill once a person reviews it; attach a skill to an agent, a team or the whole workspace, each attachment pinned to a version. Agents see each skill's name and when to use it, and read the rest with use_skill when a request needs it; a skill never adds a permission, and scripts wait for an agent's own computer. The agent skills guide says how. | 1 | /** |
| 2 | * Files people upload for a skill (the editor's files, Import's upload), | |
| 3 | * as the agents service takes them: text as it is, anything else as | |
| 4 | * standard base64. The service checks the folder (@g1t/contracts | |
| 5 | * skill-format.ts); this only refuses what is plainly too large to send. | |
| 6 | */ | |
| 7 | import { SKILL_FOLDER_MAX_BYTES, type SkillFile } from "@g1t/contracts"; | |
| 8 | ||
| 9 | /** The most one upload sends: a zip of a 1 MB folder is smaller. */ | |
| 10 | export const SKILL_UPLOAD_MAX_BYTES = 2 * 1024 * 1024; | |
| 11 | ||
| 12 | export function base64Of(bytes: Uint8Array): string { | |
| 13 | let binary = ""; | |
| 14 | for (let i = 0; i < bytes.length; i += 0x8000) binary += String.fromCharCode(...bytes.subarray(i, i + 0x8000)); | |
| 15 | return btoa(binary); | |
| 16 | } | |
| 17 | ||
| 18 | /** The uploaded files in a form field that are real files, not empty inputs. */ | |
| 19 | export function uploadedFiles(form: FormData, name: string): File[] { | |
| 20 | return form.getAll(name).filter((value): value is File => typeof value === "object" && value !== null && "arrayBuffer" in value && (value as File).size > 0); | |
| 21 | } | |
| 22 | ||
| 23 | /** One uploaded file as a skill's file at `path`. */ | |
| 24 | export async function skillFileOf(path: string, file: File): Promise<SkillFile | { error: string }> { | |
| 25 | if (file.size > SKILL_FOLDER_MAX_BYTES) return { error: `${file.name} is over 1 MB, the most a skill holds.` }; | |
| 26 | const bytes = new Uint8Array(await file.arrayBuffer()); | |
| 27 | try { | |
| 28 | const text = new TextDecoder("utf-8", { fatal: true, ignoreBOM: false }).decode(bytes); | |
| 29 | if (!text.includes("\u0000")) return { path, content: text, encoding: "utf8" }; | |
| 30 | } catch { | |
| 31 | // Not text: sent as bytes. | |
| 32 | } | |
| 33 | return { path, content: base64Of(bytes), encoding: "base64" }; | |
| 34 | } |