@termfleet/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-launch.d.ts +78 -0
- package/dist/agent-launch.js +247 -0
- package/dist/agent-session-id.d.ts +10 -0
- package/dist/agent-session-id.js +36 -0
- package/dist/agent-session-index-client.d.ts +7 -0
- package/dist/agent-session-index-client.js +86 -0
- package/dist/agent-session-index-worker.d.ts +1 -0
- package/dist/agent-session-index-worker.js +20 -0
- package/dist/agent-session-index.d.ts +34 -0
- package/dist/agent-session-index.js +527 -0
- package/dist/agent-session-tail.d.ts +33 -0
- package/dist/agent-session-tail.js +184 -0
- package/dist/agent-session-watcher.d.ts +36 -0
- package/dist/agent-session-watcher.js +194 -0
- package/dist/agent-session.d.ts +380 -0
- package/dist/agent-session.js +1688 -0
- package/dist/background-runner.d.ts +3 -0
- package/dist/background-runner.js +55 -0
- package/dist/boot-queue.d.ts +35 -0
- package/dist/boot-queue.js +66 -0
- package/dist/build-info.d.ts +5 -0
- package/dist/build-info.js +38 -0
- package/dist/collab/canvas-doc.d.ts +47 -0
- package/dist/collab/canvas-doc.js +83 -0
- package/dist/contracts/auth.d.ts +77 -0
- package/dist/contracts/auth.js +1 -0
- package/dist/contracts/canvas.d.ts +34 -0
- package/dist/contracts/canvas.js +76 -0
- package/dist/contracts/console-layout.d.ts +39 -0
- package/dist/contracts/console-layout.js +135 -0
- package/dist/contracts/files.d.ts +38 -0
- package/dist/contracts/files.js +37 -0
- package/dist/contracts/provider-url.d.ts +3 -0
- package/dist/contracts/provider-url.js +49 -0
- package/dist/contracts/registry.d.ts +58 -0
- package/dist/contracts/registry.js +285 -0
- package/dist/launch-trace.d.ts +6 -0
- package/dist/launch-trace.js +33 -0
- package/dist/lib/errors.d.ts +1 -0
- package/dist/lib/errors.js +5 -0
- package/dist/lib/exec.d.ts +13 -0
- package/dist/lib/exec.js +134 -0
- package/dist/local-providers.d.ts +32 -0
- package/dist/local-providers.js +184 -0
- package/dist/local-tunnel.d.ts +6 -0
- package/dist/local-tunnel.js +258 -0
- package/dist/provider-access-token.d.ts +11 -0
- package/dist/provider-access-token.js +77 -0
- package/dist/provider-client.d.ts +152 -0
- package/dist/provider-client.js +666 -0
- package/dist/provider-url-resolver.d.ts +16 -0
- package/dist/provider-url-resolver.js +37 -0
- package/dist/registry-client.d.ts +93 -0
- package/dist/registry-client.js +170 -0
- package/dist/registry.d.ts +56 -0
- package/dist/registry.js +406 -0
- package/dist/session-attention.d.ts +24 -0
- package/dist/session-attention.js +54 -0
- package/dist/session-lifecycle.d.ts +83 -0
- package/dist/session-lifecycle.js +658 -0
- package/dist/session-window.d.ts +3 -0
- package/dist/session-window.js +20 -0
- package/dist/terminal-client.d.ts +49 -0
- package/dist/terminal-client.js +89 -0
- package/dist/types.d.ts +155 -0
- package/dist/types.js +21 -0
- package/package.json +26 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
const RUNNER_PATH = join(tmpdir(), "termfleet-background-runner.sh");
|
|
5
|
+
export function leaseDirForPaneTarget(target) {
|
|
6
|
+
if (!target) {
|
|
7
|
+
throw new Error("A pane target is required for lifecycle leases.");
|
|
8
|
+
}
|
|
9
|
+
return join(tmpdir(), "termfleet-background-leases", encodeURIComponent(target));
|
|
10
|
+
}
|
|
11
|
+
export function ensureBackgroundRunner() {
|
|
12
|
+
const script = `#!/bin/sh
|
|
13
|
+
set -eu
|
|
14
|
+
# Lease files record the full command line — keep them owner-only (0600) and the
|
|
15
|
+
# lease dir 0700 so other local users can't read them while a job runs.
|
|
16
|
+
umask 077
|
|
17
|
+
if [ "$#" -lt 1 ]; then
|
|
18
|
+
echo "usage: $0 command [args...]" >&2
|
|
19
|
+
exit 64
|
|
20
|
+
fi
|
|
21
|
+
if [ -z "\${TERMFLEET_BACKGROUND_LEASE_DIR:-}" ]; then
|
|
22
|
+
echo "TERMFLEET_BACKGROUND_LEASE_DIR is required" >&2
|
|
23
|
+
exit 64
|
|
24
|
+
fi
|
|
25
|
+
mkdir -p "$TERMFLEET_BACKGROUND_LEASE_DIR"
|
|
26
|
+
"$@" &
|
|
27
|
+
pid="$!"
|
|
28
|
+
start_time="$(ps -p "$pid" -o lstart= | sed 's/^ *//;s/ *$//')"
|
|
29
|
+
if [ -z "$start_time" ]; then
|
|
30
|
+
echo "Could not read process start time for $pid" >&2
|
|
31
|
+
kill "$pid" 2>/dev/null || true
|
|
32
|
+
exit 70
|
|
33
|
+
fi
|
|
34
|
+
lease="$TERMFLEET_BACKGROUND_LEASE_DIR/$pid.json"
|
|
35
|
+
tmp="$lease.$$"
|
|
36
|
+
printf '{"pid":%s,"processStartTime":%s,"command":%s}\\n' \
|
|
37
|
+
"$pid" \
|
|
38
|
+
"$(printf '%s' "$start_time" | node -pe 'JSON.stringify(require("fs").readFileSync(0, "utf8"))')" \
|
|
39
|
+
"$(printf '%s' "$*" | node -pe 'JSON.stringify(require("fs").readFileSync(0, "utf8"))')" > "$tmp"
|
|
40
|
+
mv "$tmp" "$lease"
|
|
41
|
+
wait "$pid"
|
|
42
|
+
status="$?"
|
|
43
|
+
rm -f "$lease"
|
|
44
|
+
exit "$status"
|
|
45
|
+
`;
|
|
46
|
+
if (!existsSync(RUNNER_PATH)) {
|
|
47
|
+
writeFileSync(RUNNER_PATH, script, { mode: 0o700 });
|
|
48
|
+
}
|
|
49
|
+
return RUNNER_PATH;
|
|
50
|
+
}
|
|
51
|
+
export function ensureLeaseDir(target) {
|
|
52
|
+
const dir = leaseDirForPaneTarget(target);
|
|
53
|
+
mkdirSync(dir, { mode: 0o700, recursive: true });
|
|
54
|
+
return dir;
|
|
55
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BootQueue — a small FIFO async concurrency gate for agent boots ("the door").
|
|
3
|
+
*
|
|
4
|
+
* Booting an agent (`agent:create`: open a window, wait for the shell, send the
|
|
5
|
+
* launch command, wait for the agent's first response) is the scarce, slow,
|
|
6
|
+
* roughly-serial operation on a provider. A burst of concurrent boots stampedes
|
|
7
|
+
* it and the later ones exceed their timeout.
|
|
8
|
+
*
|
|
9
|
+
* This gate holds a slot ONLY for the duration of one boot and releases it the
|
|
10
|
+
* instant the boot resolves or rejects (ready-or-timeout) — never for the
|
|
11
|
+
* agent's running life. So the queue moves at boot-speed: it only ever paces how
|
|
12
|
+
* many boots are in flight at once (`maxConcurrent`), not how many agents run.
|
|
13
|
+
*
|
|
14
|
+
* It is intentionally a queue (wait your turn) rather than a reject: getting
|
|
15
|
+
* through the door is transient, so a brief wait is correct; rejecting would
|
|
16
|
+
* just churn. (Steady-state run-concurrency is a separate concern handled by the
|
|
17
|
+
* caller via reject + reconcile.)
|
|
18
|
+
*/
|
|
19
|
+
export declare class BootQueue {
|
|
20
|
+
private active;
|
|
21
|
+
private readonly waiters;
|
|
22
|
+
private readonly maxConcurrent;
|
|
23
|
+
constructor(maxConcurrent: number);
|
|
24
|
+
/** Boots currently holding a slot (in flight). */
|
|
25
|
+
get inFlight(): number;
|
|
26
|
+
/** Boots waiting for a slot. */
|
|
27
|
+
get queued(): number;
|
|
28
|
+
/**
|
|
29
|
+
* Run a boot through the gate. Waits (FIFO) until a slot is free, runs the
|
|
30
|
+
* boot, and releases the slot when it settles — success or failure alike.
|
|
31
|
+
*/
|
|
32
|
+
run<T>(boot: () => Promise<T>): Promise<T>;
|
|
33
|
+
private acquire;
|
|
34
|
+
private release;
|
|
35
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BootQueue — a small FIFO async concurrency gate for agent boots ("the door").
|
|
3
|
+
*
|
|
4
|
+
* Booting an agent (`agent:create`: open a window, wait for the shell, send the
|
|
5
|
+
* launch command, wait for the agent's first response) is the scarce, slow,
|
|
6
|
+
* roughly-serial operation on a provider. A burst of concurrent boots stampedes
|
|
7
|
+
* it and the later ones exceed their timeout.
|
|
8
|
+
*
|
|
9
|
+
* This gate holds a slot ONLY for the duration of one boot and releases it the
|
|
10
|
+
* instant the boot resolves or rejects (ready-or-timeout) — never for the
|
|
11
|
+
* agent's running life. So the queue moves at boot-speed: it only ever paces how
|
|
12
|
+
* many boots are in flight at once (`maxConcurrent`), not how many agents run.
|
|
13
|
+
*
|
|
14
|
+
* It is intentionally a queue (wait your turn) rather than a reject: getting
|
|
15
|
+
* through the door is transient, so a brief wait is correct; rejecting would
|
|
16
|
+
* just churn. (Steady-state run-concurrency is a separate concern handled by the
|
|
17
|
+
* caller via reject + reconcile.)
|
|
18
|
+
*/
|
|
19
|
+
export class BootQueue {
|
|
20
|
+
active = 0;
|
|
21
|
+
waiters = [];
|
|
22
|
+
maxConcurrent;
|
|
23
|
+
constructor(maxConcurrent) {
|
|
24
|
+
this.maxConcurrent = Math.max(1, Math.floor(maxConcurrent));
|
|
25
|
+
}
|
|
26
|
+
/** Boots currently holding a slot (in flight). */
|
|
27
|
+
get inFlight() {
|
|
28
|
+
return this.active;
|
|
29
|
+
}
|
|
30
|
+
/** Boots waiting for a slot. */
|
|
31
|
+
get queued() {
|
|
32
|
+
return this.waiters.length;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Run a boot through the gate. Waits (FIFO) until a slot is free, runs the
|
|
36
|
+
* boot, and releases the slot when it settles — success or failure alike.
|
|
37
|
+
*/
|
|
38
|
+
async run(boot) {
|
|
39
|
+
await this.acquire();
|
|
40
|
+
try {
|
|
41
|
+
return await boot();
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
this.release();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
acquire() {
|
|
48
|
+
if (this.active < this.maxConcurrent) {
|
|
49
|
+
this.active += 1;
|
|
50
|
+
return Promise.resolve();
|
|
51
|
+
}
|
|
52
|
+
return new Promise((resolve) => {
|
|
53
|
+
this.waiters.push(resolve);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
release() {
|
|
57
|
+
const next = this.waiters.shift();
|
|
58
|
+
if (next) {
|
|
59
|
+
// Hand the held slot directly to the next waiter (active count unchanged).
|
|
60
|
+
next();
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
this.active -= 1;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
let cachedBuildInfo;
|
|
6
|
+
export function getTermfleetBuildInfo() {
|
|
7
|
+
if (cachedBuildInfo) {
|
|
8
|
+
return cachedBuildInfo;
|
|
9
|
+
}
|
|
10
|
+
const version = process.env.TERMFLEET_VERSION?.trim() || readPackageVersion();
|
|
11
|
+
const buildSha = process.env.TERMFLEET_BUILD_SHA?.trim() || readGitBuildSha();
|
|
12
|
+
cachedBuildInfo = {
|
|
13
|
+
...(buildSha ? { buildSha } : {}),
|
|
14
|
+
version
|
|
15
|
+
};
|
|
16
|
+
return cachedBuildInfo;
|
|
17
|
+
}
|
|
18
|
+
function readPackageVersion() {
|
|
19
|
+
try {
|
|
20
|
+
const packageFile = join(dirname(dirname(dirname(fileURLToPath(import.meta.url)))), "package.json");
|
|
21
|
+
const packageJson = JSON.parse(readFileSync(packageFile, "utf8"));
|
|
22
|
+
return typeof packageJson.version === "string" && packageJson.version.trim() ? packageJson.version : "0.0.0";
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return "0.0.0";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function readGitBuildSha() {
|
|
29
|
+
try {
|
|
30
|
+
return execFileSync("git", ["rev-parse", "--short=12", "HEAD"], {
|
|
31
|
+
encoding: "utf8",
|
|
32
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
33
|
+
}).trim() || undefined;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { AuthOrganizationId } from "../contracts/auth.js";
|
|
2
|
+
import type { CanvasComment, CanvasPoint } from "../contracts/canvas.js";
|
|
3
|
+
import type { ConsoleLayout, ProviderGroupAppearance } from "../contracts/console-layout.js";
|
|
4
|
+
import * as Y from "yjs";
|
|
5
|
+
export declare const LAYOUT_GROUPS_KEY = "providerGroups";
|
|
6
|
+
export declare const META_KEY = "meta";
|
|
7
|
+
export declare const COMMENTS_KEY = "comments";
|
|
8
|
+
export declare const VIEWPORT_FIELD = "viewport";
|
|
9
|
+
export type CanvasGroupLayout = {
|
|
10
|
+
appearance?: ProviderGroupAppearance;
|
|
11
|
+
position?: {
|
|
12
|
+
x: number;
|
|
13
|
+
y: number;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
export declare function layoutGroups(doc: Y.Doc): Y.Map<CanvasGroupLayout>;
|
|
17
|
+
export declare function commentsMap(doc: Y.Doc): Y.Map<CanvasComment>;
|
|
18
|
+
export declare function metaMap(doc: Y.Doc): Y.Map<unknown>;
|
|
19
|
+
export declare function readCanvasLayout(doc: Y.Doc): ConsoleLayout;
|
|
20
|
+
export declare function writeCanvasLayout(doc: Y.Doc, layout: ConsoleLayout): void;
|
|
21
|
+
export declare function writeCanvasGroup(doc: Y.Doc, groupId: string, group: CanvasGroupLayout): void;
|
|
22
|
+
export declare function readCanvasComments(doc: Y.Doc): CanvasComment[];
|
|
23
|
+
export declare function upsertCanvasComment(doc: Y.Doc, comment: CanvasComment): void;
|
|
24
|
+
export type CanvasAwarenessUser = {
|
|
25
|
+
color?: string;
|
|
26
|
+
email: string;
|
|
27
|
+
fullName: string;
|
|
28
|
+
id: string;
|
|
29
|
+
imageUrl?: string;
|
|
30
|
+
};
|
|
31
|
+
export type CanvasAwarenessState = {
|
|
32
|
+
canvasId: string;
|
|
33
|
+
cursor?: CanvasPoint;
|
|
34
|
+
organizationId: AuthOrganizationId;
|
|
35
|
+
selection?: string[];
|
|
36
|
+
user: CanvasAwarenessUser;
|
|
37
|
+
viewport?: {
|
|
38
|
+
x: number;
|
|
39
|
+
y: number;
|
|
40
|
+
zoom: number;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
export declare function canvasRoom(organizationId: AuthOrganizationId, canvasId: string): string;
|
|
44
|
+
export declare function parseCanvasRoom(room: string): {
|
|
45
|
+
canvasId: string;
|
|
46
|
+
organizationId: AuthOrganizationId;
|
|
47
|
+
} | undefined;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// The shared-canvas state, modeled as ONE Y.Doc (the Track C collab engine). CRDT
|
|
2
|
+
// merge replaces the socket "last write wins" broadcast, so two people dragging
|
|
3
|
+
// different machines — or commenting at once — converge instead of clobbering.
|
|
4
|
+
//
|
|
5
|
+
// providerGroups : Y.Map<groupId, { appearance?, position? }> (per-machine merge)
|
|
6
|
+
// meta : Y.Map (the shared viewport, doc-version marker)
|
|
7
|
+
// comments : Y.Map<commentId, CanvasComment> (keyed ⇒ idempotent)
|
|
8
|
+
//
|
|
9
|
+
// This IS the one-owner layout seam (machineLayoutKey / console-layout), not a
|
|
10
|
+
// rival store — readCanvasLayout/writeCanvasLayout translate the SAME ConsoleLayout
|
|
11
|
+
// shape the rest of the app uses. Ephemeral presence (cursor / viewport / selection
|
|
12
|
+
// per user) lives in awareness (canvas-awareness.ts), never in the doc.
|
|
13
|
+
export const LAYOUT_GROUPS_KEY = "providerGroups";
|
|
14
|
+
export const META_KEY = "meta";
|
|
15
|
+
export const COMMENTS_KEY = "comments";
|
|
16
|
+
export const VIEWPORT_FIELD = "viewport";
|
|
17
|
+
export function layoutGroups(doc) {
|
|
18
|
+
return doc.getMap(LAYOUT_GROUPS_KEY);
|
|
19
|
+
}
|
|
20
|
+
export function commentsMap(doc) {
|
|
21
|
+
return doc.getMap(COMMENTS_KEY);
|
|
22
|
+
}
|
|
23
|
+
export function metaMap(doc) {
|
|
24
|
+
return doc.getMap(META_KEY);
|
|
25
|
+
}
|
|
26
|
+
// --- Layout (the shared ConsoleLayout) ---
|
|
27
|
+
export function readCanvasLayout(doc) {
|
|
28
|
+
const providerGroups = {};
|
|
29
|
+
for (const [id, value] of layoutGroups(doc).entries()) {
|
|
30
|
+
providerGroups[id] = value;
|
|
31
|
+
}
|
|
32
|
+
const viewport = metaMap(doc).get(VIEWPORT_FIELD);
|
|
33
|
+
return { providerGroups, ...(viewport ? { viewport } : {}) };
|
|
34
|
+
}
|
|
35
|
+
// Reconcile the doc to a full ConsoleLayout: upsert every group, prune the ones
|
|
36
|
+
// that no longer exist, and set the shared viewport — all in ONE transaction so
|
|
37
|
+
// observers see a single coherent change, not a partial layout mid-write.
|
|
38
|
+
export function writeCanvasLayout(doc, layout) {
|
|
39
|
+
doc.transact(() => {
|
|
40
|
+
const groups = layoutGroups(doc);
|
|
41
|
+
const next = new Set(Object.keys(layout.providerGroups));
|
|
42
|
+
for (const id of [...groups.keys()]) {
|
|
43
|
+
if (!next.has(id)) {
|
|
44
|
+
groups.delete(id);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
for (const [id, value] of Object.entries(layout.providerGroups)) {
|
|
48
|
+
groups.set(id, value);
|
|
49
|
+
}
|
|
50
|
+
if (layout.viewport) {
|
|
51
|
+
metaMap(doc).set(VIEWPORT_FIELD, layout.viewport);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
// Merge ONE group's layout without touching the others — the granular write the
|
|
56
|
+
// board issues when a single machine is dragged or restyled (the CRDT win over
|
|
57
|
+
// broadcasting the whole layout).
|
|
58
|
+
export function writeCanvasGroup(doc, groupId, group) {
|
|
59
|
+
layoutGroups(doc).set(groupId, group);
|
|
60
|
+
}
|
|
61
|
+
// --- Comments ---
|
|
62
|
+
export function readCanvasComments(doc) {
|
|
63
|
+
return [...commentsMap(doc).values()].sort((left, right) => left.createdAt - right.createdAt);
|
|
64
|
+
}
|
|
65
|
+
export function upsertCanvasComment(doc, comment) {
|
|
66
|
+
commentsMap(doc).set(comment.id, comment);
|
|
67
|
+
}
|
|
68
|
+
// The room name a canvas syncs under — org-scoped so two orgs never share a doc.
|
|
69
|
+
export function canvasRoom(organizationId, canvasId) {
|
|
70
|
+
return `canvas:${organizationId}:${canvasId}`;
|
|
71
|
+
}
|
|
72
|
+
// Inverse of canvasRoom: recover the org + canvas from a room name, so the collab
|
|
73
|
+
// server can authorize a connection against the right organization. Returns
|
|
74
|
+
// undefined for anything that isn't a canvas room.
|
|
75
|
+
export function parseCanvasRoom(room) {
|
|
76
|
+
const match = /^canvas:([^:]+):(.+)$/.exec(room);
|
|
77
|
+
const organizationId = match?.[1];
|
|
78
|
+
const canvasId = match?.[2];
|
|
79
|
+
if (!organizationId || !canvasId) {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
return { canvasId, organizationId: organizationId };
|
|
83
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export type AuthUserId = `user_${string}`;
|
|
2
|
+
export type AuthOrganizationId = `org_${string}`;
|
|
3
|
+
export type AuthSessionId = `sess_${string}`;
|
|
4
|
+
export type AuthEmailAddress = {
|
|
5
|
+
id: string;
|
|
6
|
+
emailAddress: string;
|
|
7
|
+
verification: {
|
|
8
|
+
status: "verified" | "unverified";
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
export type AuthUser = {
|
|
12
|
+
id: AuthUserId;
|
|
13
|
+
object: "user";
|
|
14
|
+
username: string | null;
|
|
15
|
+
firstName: string | null;
|
|
16
|
+
lastName: string | null;
|
|
17
|
+
fullName: string;
|
|
18
|
+
primaryEmailAddressId: string;
|
|
19
|
+
primaryEmailAddress: AuthEmailAddress;
|
|
20
|
+
emailAddresses: AuthEmailAddress[];
|
|
21
|
+
imageUrl: string;
|
|
22
|
+
createdAt: number;
|
|
23
|
+
updatedAt: number;
|
|
24
|
+
};
|
|
25
|
+
export type AuthOrganization = {
|
|
26
|
+
id: AuthOrganizationId;
|
|
27
|
+
object: "organization";
|
|
28
|
+
name: string;
|
|
29
|
+
slug: string;
|
|
30
|
+
imageUrl: string;
|
|
31
|
+
createdBy: AuthUserId;
|
|
32
|
+
createdAt: number;
|
|
33
|
+
updatedAt: number;
|
|
34
|
+
};
|
|
35
|
+
export type AuthOrganizationMembershipRole = "org:admin" | "org:member";
|
|
36
|
+
export type AuthOrganizationMembership = {
|
|
37
|
+
id: string;
|
|
38
|
+
object: "organization_membership";
|
|
39
|
+
role: AuthOrganizationMembershipRole;
|
|
40
|
+
organization: AuthOrganization;
|
|
41
|
+
publicUserData: {
|
|
42
|
+
userId: AuthUserId;
|
|
43
|
+
firstName: string | null;
|
|
44
|
+
lastName: string | null;
|
|
45
|
+
imageUrl: string;
|
|
46
|
+
identifier: string;
|
|
47
|
+
};
|
|
48
|
+
createdAt: number;
|
|
49
|
+
updatedAt: number;
|
|
50
|
+
};
|
|
51
|
+
export type AuthSessionStatus = "active" | "ended";
|
|
52
|
+
export type AuthSession = {
|
|
53
|
+
id: AuthSessionId;
|
|
54
|
+
object: "session";
|
|
55
|
+
status: AuthSessionStatus;
|
|
56
|
+
userId: AuthUserId;
|
|
57
|
+
activeOrganizationId: AuthOrganizationId | null;
|
|
58
|
+
lastActiveAt: number;
|
|
59
|
+
expireAt: number;
|
|
60
|
+
abandonAt: number;
|
|
61
|
+
createdAt: number;
|
|
62
|
+
updatedAt: number;
|
|
63
|
+
};
|
|
64
|
+
export type AuthSessionResource = {
|
|
65
|
+
object: "session_resource";
|
|
66
|
+
session: AuthSession;
|
|
67
|
+
user: AuthUser;
|
|
68
|
+
organizationMemberships: AuthOrganizationMembership[];
|
|
69
|
+
activeOrganization: AuthOrganization | null;
|
|
70
|
+
};
|
|
71
|
+
export type AuthSignInResponse = {
|
|
72
|
+
object: "sign_in";
|
|
73
|
+
status: "complete";
|
|
74
|
+
createdSessionId: AuthSessionId;
|
|
75
|
+
sessionToken: string;
|
|
76
|
+
sessionResource: AuthSessionResource;
|
|
77
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { AuthOrganizationId } from "./auth.js";
|
|
2
|
+
export type CanvasPoint = {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
};
|
|
6
|
+
export type CanvasCommentsFilter = "open" | "resolved";
|
|
7
|
+
export type CanvasComment = {
|
|
8
|
+
author: {
|
|
9
|
+
fullName: string;
|
|
10
|
+
id: string;
|
|
11
|
+
imageUrl?: string;
|
|
12
|
+
};
|
|
13
|
+
body: string;
|
|
14
|
+
canvasId: string;
|
|
15
|
+
coordinateSpace: "canvas";
|
|
16
|
+
createdAt: number;
|
|
17
|
+
id: string;
|
|
18
|
+
organizationId: AuthOrganizationId;
|
|
19
|
+
position: CanvasPoint;
|
|
20
|
+
replies: CanvasCommentReply[];
|
|
21
|
+
resolvedAt?: number;
|
|
22
|
+
};
|
|
23
|
+
export type CanvasCommentReply = {
|
|
24
|
+
author: {
|
|
25
|
+
fullName: string;
|
|
26
|
+
id: string;
|
|
27
|
+
imageUrl?: string;
|
|
28
|
+
};
|
|
29
|
+
body: string;
|
|
30
|
+
createdAt: number;
|
|
31
|
+
id: string;
|
|
32
|
+
};
|
|
33
|
+
export declare function parseCanvasCommentsList(payload: unknown): CanvasComment[];
|
|
34
|
+
export declare function parseCanvasComment(payload: unknown): CanvasComment;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export function parseCanvasCommentsList(payload) {
|
|
2
|
+
const source = payload && typeof payload === "object" ? payload.comments : undefined;
|
|
3
|
+
return Array.isArray(source)
|
|
4
|
+
? source.map((comment) => {
|
|
5
|
+
try {
|
|
6
|
+
return parseCanvasComment(comment);
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
}).filter((comment) => Boolean(comment))
|
|
12
|
+
: [];
|
|
13
|
+
}
|
|
14
|
+
export function parseCanvasComment(payload) {
|
|
15
|
+
if (!payload || typeof payload !== "object") {
|
|
16
|
+
throw new Error("Canvas comment payload is required.");
|
|
17
|
+
}
|
|
18
|
+
const source = payload;
|
|
19
|
+
const createdAt = Number(source.createdAt);
|
|
20
|
+
const x = Number(source.position?.x);
|
|
21
|
+
const y = Number(source.position?.y);
|
|
22
|
+
if (typeof source.id !== "string" ||
|
|
23
|
+
typeof source.canvasId !== "string" ||
|
|
24
|
+
source.coordinateSpace !== "canvas" ||
|
|
25
|
+
typeof source.organizationId !== "string" ||
|
|
26
|
+
typeof source.body !== "string" ||
|
|
27
|
+
!source.author ||
|
|
28
|
+
typeof source.author.id !== "string" ||
|
|
29
|
+
typeof source.author.fullName !== "string" ||
|
|
30
|
+
!Number.isFinite(createdAt) ||
|
|
31
|
+
!Number.isFinite(x) ||
|
|
32
|
+
!Number.isFinite(y)) {
|
|
33
|
+
throw new Error("Canvas comment payload is malformed.");
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
author: {
|
|
37
|
+
fullName: source.author.fullName,
|
|
38
|
+
id: source.author.id,
|
|
39
|
+
imageUrl: source.author.imageUrl
|
|
40
|
+
},
|
|
41
|
+
body: source.body,
|
|
42
|
+
canvasId: source.canvasId,
|
|
43
|
+
coordinateSpace: "canvas",
|
|
44
|
+
createdAt,
|
|
45
|
+
id: source.id,
|
|
46
|
+
organizationId: source.organizationId,
|
|
47
|
+
position: { x, y },
|
|
48
|
+
replies: Array.isArray(source.replies) ? source.replies.map(parseCanvasCommentReply).filter((reply) => Boolean(reply)) : [],
|
|
49
|
+
resolvedAt: source.resolvedAt
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function parseCanvasCommentReply(payload) {
|
|
53
|
+
if (!payload || typeof payload !== "object") {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
const source = payload;
|
|
57
|
+
const createdAt = Number(source.createdAt);
|
|
58
|
+
if (typeof source.id !== "string" ||
|
|
59
|
+
typeof source.body !== "string" ||
|
|
60
|
+
!source.author ||
|
|
61
|
+
typeof source.author.id !== "string" ||
|
|
62
|
+
typeof source.author.fullName !== "string" ||
|
|
63
|
+
!Number.isFinite(createdAt)) {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
author: {
|
|
68
|
+
fullName: source.author.fullName,
|
|
69
|
+
id: source.author.id,
|
|
70
|
+
imageUrl: source.author.imageUrl
|
|
71
|
+
},
|
|
72
|
+
body: source.body,
|
|
73
|
+
createdAt,
|
|
74
|
+
id: source.id
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export type ConsoleLayout = {
|
|
2
|
+
providerGroups: Record<string, {
|
|
3
|
+
appearance?: ProviderGroupAppearance;
|
|
4
|
+
position?: {
|
|
5
|
+
x: number;
|
|
6
|
+
y: number;
|
|
7
|
+
};
|
|
8
|
+
}>;
|
|
9
|
+
viewport?: {
|
|
10
|
+
x: number;
|
|
11
|
+
y: number;
|
|
12
|
+
zoom: number;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export type ProviderGroupAppearance = {
|
|
16
|
+
accentColor?: string;
|
|
17
|
+
backgroundBlur?: number;
|
|
18
|
+
backgroundColor?: string;
|
|
19
|
+
backgroundOpacity?: number;
|
|
20
|
+
desktopGrid?: boolean;
|
|
21
|
+
icon?: string;
|
|
22
|
+
mode?: ProviderAppearanceMode;
|
|
23
|
+
terminalCursorBlink?: boolean;
|
|
24
|
+
terminalCursorStyle?: ProviderTerminalCursorStyle;
|
|
25
|
+
terminalFontFamily?: string;
|
|
26
|
+
wallpaper?: string;
|
|
27
|
+
wallpaperFit?: ProviderWallpaperFit;
|
|
28
|
+
wallpaperUrl?: string;
|
|
29
|
+
windowCornerRadius?: number;
|
|
30
|
+
windowFrost?: number;
|
|
31
|
+
windowOpacity?: number;
|
|
32
|
+
windowTitlebar?: ProviderWindowTitlebarStyle;
|
|
33
|
+
};
|
|
34
|
+
export type ProviderAppearanceMode = "dark" | "light";
|
|
35
|
+
export type ProviderTerminalCursorStyle = "bar" | "block" | "underline";
|
|
36
|
+
export type ProviderWallpaperFit = "center" | "fill" | "fit" | "tile";
|
|
37
|
+
export type ProviderWindowTitlebarStyle = "minimal" | "plain" | "traffic";
|
|
38
|
+
export declare function parseConsoleLayout(payload: unknown): ConsoleLayout;
|
|
39
|
+
export declare function parseConsoleLayoutPatch(payload: unknown): ConsoleLayout;
|