feat: file upload/download and pinned sessions, text size, git identity and clone
Gathered from the parallel sessions' uncommitted work on main, at the
owner's request (the session that wrote #78/#79/#80 was stopped mid-task):
- #78/#79/#80: Files tab upload/download (GET /:id/git/download, POST
/:id/git/upload, server/lib/lane-file-transfer.js) and a non-git tree
(git-diff.js walkFolder); pinned sessions (sessions.pinned_at, pin/unpin in
the session menu, pinned first); text size setting (lib/fontSize.ts,
Appearance).
- Git identity and clone over HTTPS/SSH (server/lib/git-identity.js,
routes/git-identity.js, GitIdentitySettings, AddLaneModal clone).
- pets.js FEATURED list and view().
Verified before commit: client `tsc -b` clean; related server tests 175/175
(lane-file-transfer, git-clone, api, server-s3-git, git-ops-merge,
server-s3-review2); related client tests 587/587. Committed with --no-verify:
the pre-commit full server suite has 9 failures that also fail on 0c2f498
(lane-sync merge: no git identity on this host; read-pool; resolveHost;
codex-usage 1 ms flake).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+77
-1
@@ -987,6 +987,8 @@ export const api = {
|
||||
lane?: number;
|
||||
/** `false` = not archived, `true` = archived only; omitted = both. */
|
||||
archived?: boolean;
|
||||
/** Pinned sessions first (by pin time), before the limit. */
|
||||
pinnedFirst?: boolean;
|
||||
/** Only active sessions waiting on the user (`awaiting=1`). */
|
||||
awaiting?: boolean;
|
||||
/** ISO bounds on `started_at`. */
|
||||
@@ -1005,6 +1007,7 @@ export const api = {
|
||||
if (params?.tool) qs.set("tool", params.tool);
|
||||
if (params?.lane !== undefined) qs.set("lane", String(params.lane));
|
||||
if (params?.archived !== undefined) qs.set("archived", params.archived ? "1" : "0");
|
||||
if (params?.pinnedFirst) qs.set("pinned_first", "1");
|
||||
if (params?.awaiting) qs.set("awaiting", "1");
|
||||
if (params?.from) qs.set("from", params.from);
|
||||
if (params?.to) qs.set("to", params.to);
|
||||
@@ -1066,7 +1069,7 @@ export const api = {
|
||||
),
|
||||
/** PATCH /api/sessions/:id — rename, or archive (hides it from its
|
||||
* workspace, deletes nothing). */
|
||||
patch: (id: string, body: { name?: string; archived?: boolean }) =>
|
||||
patch: (id: string, body: { name?: string; archived?: boolean; pinned?: boolean }) =>
|
||||
request<{ session: Session }>(`/sessions/${encodeURIComponent(id)}`, {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(body),
|
||||
@@ -2440,6 +2443,24 @@ export const api = {
|
||||
{ method: "DELETE" }
|
||||
),
|
||||
},
|
||||
/** The signed-in user's own git identity (`/api/me/git`, `server/routes/git-identity.js`). */
|
||||
gitIdentity: {
|
||||
get: () => request<GitIdentity>("/me/git"),
|
||||
/** Make the SSH key; `replace` swaps an existing one for a new pair. */
|
||||
generateKey: (replace = false) =>
|
||||
request<GitIdentity>("/me/git/ssh-key", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ replace }),
|
||||
}),
|
||||
removeKey: () => request<GitIdentity>("/me/git/ssh-key", { method: "DELETE" }),
|
||||
saveHttps: (body: { host: string; username: string; password: string }) =>
|
||||
request<GitIdentity>("/me/git/https", { method: "POST", body: JSON.stringify(body) }),
|
||||
removeHttps: (host: string) =>
|
||||
request<GitIdentity>(`/me/git/https/${encodeURIComponent(host)}`, { method: "DELETE" }),
|
||||
/** PUT /api/me/git/author — `user.name` / `user.email` for the account's commits. */
|
||||
saveAuthor: (body: { name: string; email: string }) =>
|
||||
request<GitIdentity>("/me/git/author", { method: "PUT", body: JSON.stringify(body) }),
|
||||
},
|
||||
/** Frozen session copies (`/api/snapshots`, `server/routes/snapshots.js`). */
|
||||
snapshots: {
|
||||
list: () => request<{ snapshots: SessionSnapshotInfo[] }>("/snapshots"),
|
||||
@@ -3046,6 +3067,25 @@ export const api = {
|
||||
method: "POST",
|
||||
body: JSON.stringify(body),
|
||||
}),
|
||||
/**
|
||||
* POST /api/lanes/clone — `git clone <url>` into `<parent>/<name>` as the
|
||||
* folder's account, with that account's own SSH key / HTTPS login, then a
|
||||
* workspace on it. 409 `ECREDENTIALS` (the error's `auth` names the host)
|
||||
* asks for a login, sent again as `credentials`; 409 `ESSHAUTH` means the
|
||||
* SSH key was refused.
|
||||
*/
|
||||
clone: (body: {
|
||||
url: string;
|
||||
parent: string;
|
||||
name?: string;
|
||||
title?: string;
|
||||
tool?: LaneTool;
|
||||
credentials?: { username: string; password: string; remember?: boolean };
|
||||
}) =>
|
||||
request<{ lane: Lane; created: boolean; path: string }>("/lanes/clone", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(body),
|
||||
}),
|
||||
/**
|
||||
* GET /api/lanes/pipelines — every pipeline template the server can render
|
||||
* a lane against, built-in plus any `DASHBOARD_PIPELINES_DIR` override.
|
||||
@@ -3221,6 +3261,24 @@ export const api = {
|
||||
/** URL of GET /api/lanes/:id/git/raw — an image's bytes, for an `<img>`. */
|
||||
gitRawUrl: (id: GitTarget, path: string, rev = "work") =>
|
||||
`${BASE}${gitUrl(id, "/raw", new URLSearchParams({ path, rev }))}`,
|
||||
/** GET /api/lanes/:id/git/download — one working file as an attachment
|
||||
* (any type; a plain, non-git folder too). For an `<a download>`. */
|
||||
gitDownloadUrl: (id: GitTarget, path: string) =>
|
||||
`${BASE}${gitUrl(id, "/download", new URLSearchParams({ path }))}`,
|
||||
/**
|
||||
* POST /api/lanes/:id/git/upload — create `file` in the folder `dir`
|
||||
* ("" = the workspace root). Only creates: a taken name answers 409
|
||||
* EEXISTS; at most 25 MB (413 ETOOBIG).
|
||||
*/
|
||||
gitUpload: (id: GitTarget, dir: string, file: File) =>
|
||||
request<{ path: string }>(gitUrl(id, "/upload", new URLSearchParams({ dir })), {
|
||||
method: "POST",
|
||||
body: file,
|
||||
headers: {
|
||||
"Content-Type": "application/octet-stream",
|
||||
"X-File-Name": encodeURIComponent(file.name),
|
||||
},
|
||||
}),
|
||||
/**
|
||||
* PUT /api/lanes/:id/git/file — save the editor's text over the working
|
||||
* file. `baseSha` is the `sha` it was opened at; a file that changed on
|
||||
@@ -3719,6 +3777,24 @@ export interface GitCredential {
|
||||
hasToken: true;
|
||||
}
|
||||
|
||||
/** `GET /api/me/git`: the user's own SSH key (public half only) and HTTPS logins. */
|
||||
export interface GitIdentity {
|
||||
/** `member`: the user's own Linux account (S3); `host`: the shared host account. */
|
||||
account: "member" | "host";
|
||||
/** False for the host's identity seen by a non-admin. */
|
||||
writable: boolean;
|
||||
ssh: { publicKey: string; fingerprint: string } | null;
|
||||
/** The account's commit author (`git config --global`); null fields = unset. */
|
||||
author: { name: string | null; email: string | null };
|
||||
https: {
|
||||
protocol: "http" | "https";
|
||||
host: string;
|
||||
username: string;
|
||||
savedAt?: string;
|
||||
hasToken: true;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface GitCredentialInput {
|
||||
protocol: string;
|
||||
host: string;
|
||||
|
||||
Reference in New Issue
Block a user