@vellumai/cli 0.7.0 → 0.7.1
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/README.md +49 -0
- package/package.json +1 -1
- package/src/__tests__/backup.test.ts +475 -0
- package/src/__tests__/config-utils.test.ts +35 -48
- package/src/__tests__/teleport.test.ts +86 -28
- package/src/commands/backup.ts +117 -71
- package/src/commands/client.ts +10 -9
- package/src/commands/exec.ts +21 -8
- package/src/commands/hatch.ts +2 -6
- package/src/commands/login.ts +15 -33
- package/src/commands/logs.ts +2 -7
- package/src/commands/ps.ts +41 -6
- package/src/commands/restore.ts +26 -47
- package/src/commands/ssh.ts +2 -5
- package/src/commands/teleport.ts +38 -24
- package/src/commands/tunnel.ts +2 -7
- package/src/commands/upgrade.ts +108 -7
- package/src/components/DefaultMainScreen.tsx +25 -3
- package/src/index.ts +2 -7
- package/src/lib/__tests__/local-runtime-client.test.ts +122 -25
- package/src/lib/__tests__/platform-client-signed-url.test.ts +2 -2
- package/src/lib/__tests__/runtime-url.test.ts +87 -0
- package/src/lib/__tests__/terminal-session.test.ts +202 -0
- package/src/lib/assistant-client.ts +5 -21
- package/src/lib/assistant-config.ts +34 -16
- package/src/lib/cli-error.ts +1 -0
- package/src/lib/client-identity.ts +1 -1
- package/src/lib/config-utils.ts +1 -97
- package/src/lib/docker.ts +2 -2
- package/src/lib/job-polling.ts +1 -1
- package/src/lib/local-runtime-client.ts +81 -28
- package/src/lib/local.ts +27 -58
- package/src/lib/platform-client.ts +1 -220
- package/src/lib/platform-releases.ts +23 -0
- package/src/lib/runtime-url.ts +30 -0
- package/src/lib/sync-cloud-assistants.ts +126 -0
- package/src/lib/terminal-client.ts +6 -1
- package/src/lib/terminal-session.ts +127 -48
- package/src/lib/tui-log.ts +60 -0
- package/src/lib/xdg-log.ts +10 -4
|
@@ -665,182 +665,10 @@ export async function rollbackPlatformAssistant(
|
|
|
665
665
|
throw new Error(`Rollback failed: ${response.status} ${response.statusText}`);
|
|
666
666
|
}
|
|
667
667
|
|
|
668
|
-
// ---------------------------------------------------------------------------
|
|
669
|
-
// Migration export
|
|
670
|
-
// ---------------------------------------------------------------------------
|
|
671
|
-
|
|
672
|
-
export async function platformInitiateExport(
|
|
673
|
-
token: string,
|
|
674
|
-
description?: string,
|
|
675
|
-
platformUrl?: string,
|
|
676
|
-
): Promise<{ jobId: string; status: string }> {
|
|
677
|
-
const resolvedUrl = platformUrl || getPlatformUrl();
|
|
678
|
-
const response = await fetch(`${resolvedUrl}/v1/migrations/export/`, {
|
|
679
|
-
method: "POST",
|
|
680
|
-
headers: await authHeaders(token, platformUrl),
|
|
681
|
-
body: JSON.stringify({ description: description ?? "CLI backup" }),
|
|
682
|
-
});
|
|
683
|
-
|
|
684
|
-
if (response.status !== 201) {
|
|
685
|
-
const body = (await response.json().catch(() => ({}))) as {
|
|
686
|
-
detail?: string;
|
|
687
|
-
};
|
|
688
|
-
throw new Error(
|
|
689
|
-
body.detail ??
|
|
690
|
-
`Export initiation failed: ${response.status} ${response.statusText}`,
|
|
691
|
-
);
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
const body = (await response.json()) as {
|
|
695
|
-
job_id: string;
|
|
696
|
-
status: string;
|
|
697
|
-
};
|
|
698
|
-
return { jobId: body.job_id, status: body.status };
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
export async function platformPollExportStatus(
|
|
702
|
-
jobId: string,
|
|
703
|
-
token: string,
|
|
704
|
-
platformUrl?: string,
|
|
705
|
-
): Promise<{ status: string; downloadUrl?: string; error?: string }> {
|
|
706
|
-
const resolvedUrl = platformUrl || getPlatformUrl();
|
|
707
|
-
const response = await fetch(
|
|
708
|
-
`${resolvedUrl}/v1/migrations/export/${jobId}/status/`,
|
|
709
|
-
{
|
|
710
|
-
headers: await authHeaders(token, platformUrl),
|
|
711
|
-
},
|
|
712
|
-
);
|
|
713
|
-
|
|
714
|
-
if (response.status === 404) {
|
|
715
|
-
throw new Error("Export job not found");
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
if (!response.ok) {
|
|
719
|
-
throw new Error(
|
|
720
|
-
`Export status check failed: ${response.status} ${response.statusText}`,
|
|
721
|
-
);
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
const body = (await response.json()) as {
|
|
725
|
-
status: string;
|
|
726
|
-
download_url?: string;
|
|
727
|
-
error?: string;
|
|
728
|
-
};
|
|
729
|
-
return {
|
|
730
|
-
status: body.status,
|
|
731
|
-
downloadUrl: body.download_url,
|
|
732
|
-
error: body.error,
|
|
733
|
-
};
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
export async function platformDownloadExport(
|
|
737
|
-
downloadUrl: string,
|
|
738
|
-
): Promise<Response> {
|
|
739
|
-
const response = await fetch(downloadUrl);
|
|
740
|
-
if (!response.ok) {
|
|
741
|
-
throw new Error(
|
|
742
|
-
`Download failed: ${response.status} ${response.statusText}`,
|
|
743
|
-
);
|
|
744
|
-
}
|
|
745
|
-
return response;
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
// ---------------------------------------------------------------------------
|
|
749
|
-
// Migration import
|
|
750
|
-
// ---------------------------------------------------------------------------
|
|
751
|
-
|
|
752
|
-
export async function platformImportPreflight(
|
|
753
|
-
bundleData: Uint8Array<ArrayBuffer>,
|
|
754
|
-
token: string,
|
|
755
|
-
platformUrl?: string,
|
|
756
|
-
): Promise<{ statusCode: number; body: Record<string, unknown> }> {
|
|
757
|
-
const resolvedUrl = platformUrl || getPlatformUrl();
|
|
758
|
-
const response = await fetch(
|
|
759
|
-
`${resolvedUrl}/v1/migrations/import-preflight/`,
|
|
760
|
-
{
|
|
761
|
-
method: "POST",
|
|
762
|
-
headers: {
|
|
763
|
-
...(await authHeaders(token, platformUrl)),
|
|
764
|
-
"Content-Type": "application/octet-stream",
|
|
765
|
-
},
|
|
766
|
-
body: new Blob([bundleData]),
|
|
767
|
-
signal: AbortSignal.timeout(120_000),
|
|
768
|
-
},
|
|
769
|
-
);
|
|
770
|
-
|
|
771
|
-
const body = (await response.json().catch(() => ({}))) as Record<
|
|
772
|
-
string,
|
|
773
|
-
unknown
|
|
774
|
-
>;
|
|
775
|
-
return { statusCode: response.status, body };
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
export async function platformImportBundle(
|
|
779
|
-
bundleData: Uint8Array<ArrayBuffer>,
|
|
780
|
-
token: string,
|
|
781
|
-
platformUrl?: string,
|
|
782
|
-
): Promise<{ statusCode: number; body: Record<string, unknown> }> {
|
|
783
|
-
const resolvedUrl = platformUrl || getPlatformUrl();
|
|
784
|
-
const response = await fetch(`${resolvedUrl}/v1/migrations/import/`, {
|
|
785
|
-
method: "POST",
|
|
786
|
-
headers: {
|
|
787
|
-
...(await authHeaders(token, platformUrl)),
|
|
788
|
-
"Content-Type": "application/octet-stream",
|
|
789
|
-
},
|
|
790
|
-
body: new Blob([bundleData]),
|
|
791
|
-
signal: AbortSignal.timeout(300_000),
|
|
792
|
-
});
|
|
793
|
-
|
|
794
|
-
const body = (await response.json().catch(() => ({}))) as Record<
|
|
795
|
-
string,
|
|
796
|
-
unknown
|
|
797
|
-
>;
|
|
798
|
-
return { statusCode: response.status, body };
|
|
799
|
-
}
|
|
800
|
-
|
|
801
668
|
// ---------------------------------------------------------------------------
|
|
802
669
|
// Signed-URL upload flow
|
|
803
670
|
// ---------------------------------------------------------------------------
|
|
804
671
|
|
|
805
|
-
export async function platformRequestUploadUrl(
|
|
806
|
-
token: string,
|
|
807
|
-
platformUrl?: string,
|
|
808
|
-
): Promise<{ uploadUrl: string; bundleKey: string; expiresAt: string }> {
|
|
809
|
-
const resolvedUrl = platformUrl || getPlatformUrl();
|
|
810
|
-
const response = await fetch(`${resolvedUrl}/v1/migrations/upload-url/`, {
|
|
811
|
-
method: "POST",
|
|
812
|
-
headers: await authHeaders(token, platformUrl),
|
|
813
|
-
body: JSON.stringify({ content_type: "application/octet-stream" }),
|
|
814
|
-
});
|
|
815
|
-
|
|
816
|
-
if (response.status === 201) {
|
|
817
|
-
const body = (await response.json()) as {
|
|
818
|
-
upload_url: string;
|
|
819
|
-
bundle_key: string;
|
|
820
|
-
expires_at: string;
|
|
821
|
-
};
|
|
822
|
-
return {
|
|
823
|
-
uploadUrl: body.upload_url,
|
|
824
|
-
bundleKey: body.bundle_key,
|
|
825
|
-
expiresAt: body.expires_at,
|
|
826
|
-
};
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
if (response.status === 404 || response.status === 503) {
|
|
830
|
-
throw new Error(
|
|
831
|
-
"Signed uploads are not available on this platform instance",
|
|
832
|
-
);
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
const errorBody = (await response.json().catch(() => ({}))) as {
|
|
836
|
-
detail?: string;
|
|
837
|
-
};
|
|
838
|
-
throw new Error(
|
|
839
|
-
errorBody.detail ??
|
|
840
|
-
`Failed to request upload URL: ${response.status} ${response.statusText}`,
|
|
841
|
-
);
|
|
842
|
-
}
|
|
843
|
-
|
|
844
672
|
export async function platformUploadToSignedUrl(
|
|
845
673
|
uploadUrl: string,
|
|
846
674
|
bundleData: Uint8Array<ArrayBuffer>,
|
|
@@ -911,46 +739,6 @@ export async function platformImportBundleFromGcs(
|
|
|
911
739
|
return { statusCode: response.status, body };
|
|
912
740
|
}
|
|
913
741
|
|
|
914
|
-
export async function platformPollImportStatus(
|
|
915
|
-
jobId: string,
|
|
916
|
-
token: string,
|
|
917
|
-
platformUrl?: string,
|
|
918
|
-
): Promise<{
|
|
919
|
-
status: string;
|
|
920
|
-
result?: Record<string, unknown>;
|
|
921
|
-
error?: string;
|
|
922
|
-
}> {
|
|
923
|
-
const resolvedUrl = platformUrl || getPlatformUrl();
|
|
924
|
-
const response = await fetch(
|
|
925
|
-
`${resolvedUrl}/v1/migrations/import/${jobId}/status/`,
|
|
926
|
-
{
|
|
927
|
-
headers: await authHeaders(token, platformUrl),
|
|
928
|
-
},
|
|
929
|
-
);
|
|
930
|
-
|
|
931
|
-
if (response.status === 404) {
|
|
932
|
-
throw new Error("Import job not found");
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
if (!response.ok) {
|
|
936
|
-
throw new Error(
|
|
937
|
-
`Import status check failed: ${response.status} ${response.statusText}`,
|
|
938
|
-
);
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
const body = (await response.json()) as {
|
|
942
|
-
status: string;
|
|
943
|
-
job_id?: string;
|
|
944
|
-
result?: Record<string, unknown>;
|
|
945
|
-
error?: string;
|
|
946
|
-
};
|
|
947
|
-
return {
|
|
948
|
-
status: body.status,
|
|
949
|
-
result: body.result,
|
|
950
|
-
error: body.error,
|
|
951
|
-
};
|
|
952
|
-
}
|
|
953
|
-
|
|
954
742
|
// ---------------------------------------------------------------------------
|
|
955
743
|
// Unified signed-url + job-status endpoints (teleport-gcs-unify)
|
|
956
744
|
// ---------------------------------------------------------------------------
|
|
@@ -1027,8 +815,7 @@ export function parseUnifiedJobStatus(
|
|
|
1027
815
|
* runtime can GET the bundle from during an import-from-GCS flow.
|
|
1028
816
|
*
|
|
1029
817
|
* Retries once with a fresh org-ID cache on 401 to match the retry pattern
|
|
1030
|
-
* used by other authenticated platform helpers.
|
|
1031
|
-
* callers can decide to fall back (e.g. legacy inline upload).
|
|
818
|
+
* used by other authenticated platform helpers.
|
|
1032
819
|
*/
|
|
1033
820
|
export async function platformRequestSignedUrl(
|
|
1034
821
|
params: {
|
|
@@ -1086,12 +873,6 @@ export async function platformRequestSignedUrl(
|
|
|
1086
873
|
};
|
|
1087
874
|
}
|
|
1088
875
|
|
|
1089
|
-
if (response.status === 503) {
|
|
1090
|
-
throw new Error(
|
|
1091
|
-
`Signed URL endpoint unavailable (503) — caller may fall back`,
|
|
1092
|
-
);
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
876
|
const errorBody = (await response.json().catch(() => ({}))) as {
|
|
1096
877
|
detail?: string;
|
|
1097
878
|
};
|
|
@@ -7,6 +7,29 @@ export interface ResolvedImageRefs {
|
|
|
7
7
|
source: "platform" | "dockerhub";
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Fetch the latest stable release version from the platform API.
|
|
12
|
+
* Returns the version string (e.g. "0.7.0") or null if unavailable.
|
|
13
|
+
* The releases endpoint returns entries ordered newest-first.
|
|
14
|
+
*/
|
|
15
|
+
export async function fetchLatestStableVersion(): Promise<string | null> {
|
|
16
|
+
try {
|
|
17
|
+
const platformUrl = getPlatformUrl();
|
|
18
|
+
const response = await fetch(`${platformUrl}/v1/releases/?stable=true`, {
|
|
19
|
+
signal: AbortSignal.timeout(10_000),
|
|
20
|
+
});
|
|
21
|
+
if (!response.ok) return null;
|
|
22
|
+
|
|
23
|
+
const releases = (await response.json()) as Array<{
|
|
24
|
+
version?: string;
|
|
25
|
+
}>;
|
|
26
|
+
const first = releases[0];
|
|
27
|
+
return first?.version ?? null;
|
|
28
|
+
} catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
10
33
|
/**
|
|
11
34
|
* Resolve image references for a given version.
|
|
12
35
|
*
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { AssistantEntry } from "./assistant-config.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Resolve the URL for a runtime migration endpoint, taking the assistant's
|
|
5
|
+
* topology into account.
|
|
6
|
+
*
|
|
7
|
+
* - For local/docker assistants, `runtimeUrl` is the loopback gateway and
|
|
8
|
+
* the runtime serves `/v1/migrations/<subpath>` directly. The CLI hits
|
|
9
|
+
* that path with guardian-token bearer auth.
|
|
10
|
+
* - For platform-managed (cloud="vellum") assistants, `runtimeUrl` is the
|
|
11
|
+
* platform host (e.g. `https://platform.vellum.ai`). The platform's
|
|
12
|
+
* `MigrationViewSet` does NOT expose `export-to-gcs` or arbitrary runtime
|
|
13
|
+
* migration paths under `/v1/migrations/...`. The wildcard runtime proxy
|
|
14
|
+
* at `/v1/assistants/<id>/<path:rest>` is what forwards arbitrary runtime
|
|
15
|
+
* paths to the managed runtime — vembda's unified proxy bootstraps the
|
|
16
|
+
* guardian token internally for the runtime call. From the CLI side it's
|
|
17
|
+
* user-session auth.
|
|
18
|
+
*
|
|
19
|
+
* The `subpath` is appended to the migrations namespace verbatim
|
|
20
|
+
* (e.g. `"export-to-gcs"`, `"import-from-gcs"`, `\`jobs/${jobId}\``).
|
|
21
|
+
*/
|
|
22
|
+
export function resolveRuntimeMigrationUrl(
|
|
23
|
+
entry: Pick<AssistantEntry, "cloud" | "runtimeUrl" | "assistantId">,
|
|
24
|
+
subpath: string,
|
|
25
|
+
): string {
|
|
26
|
+
if (entry.cloud === "vellum") {
|
|
27
|
+
return `${entry.runtimeUrl}/v1/assistants/${entry.assistantId}/migrations/${subpath}`;
|
|
28
|
+
}
|
|
29
|
+
return `${entry.runtimeUrl}/v1/migrations/${subpath}`;
|
|
30
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync cloud-managed (platform) assistants into the local lockfile.
|
|
3
|
+
*
|
|
4
|
+
* - Adds new platform assistants that aren't in the lockfile yet.
|
|
5
|
+
* - Removes lockfile entries whose IDs are no longer returned by the platform
|
|
6
|
+
* (e.g. retired assistants).
|
|
7
|
+
*
|
|
8
|
+
* Used by both `vellum login` and `vellum ps` to keep the lockfile fresh.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
loadAllAssistants,
|
|
13
|
+
removeAssistantEntry,
|
|
14
|
+
saveAssistantEntry,
|
|
15
|
+
} from "./assistant-config.js";
|
|
16
|
+
import {
|
|
17
|
+
fetchCurrentUser,
|
|
18
|
+
fetchPlatformAssistants,
|
|
19
|
+
getPlatformUrl,
|
|
20
|
+
readPlatformToken,
|
|
21
|
+
} from "./platform-client.js";
|
|
22
|
+
|
|
23
|
+
export type SyncLogger = (message: string) => void;
|
|
24
|
+
|
|
25
|
+
export interface SyncResult {
|
|
26
|
+
added: number;
|
|
27
|
+
removed: number;
|
|
28
|
+
email?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface SyncOptions {
|
|
32
|
+
log?: SyncLogger;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Fetch platform assistants and reconcile against the lockfile.
|
|
37
|
+
* Returns the number of entries added/removed, or `null` if the user
|
|
38
|
+
* is not logged in or the fetch fails.
|
|
39
|
+
*/
|
|
40
|
+
export async function syncCloudAssistants(
|
|
41
|
+
options?: SyncOptions,
|
|
42
|
+
): Promise<SyncResult | null> {
|
|
43
|
+
const log = options?.log;
|
|
44
|
+
const platformUrl = getPlatformUrl();
|
|
45
|
+
log?.(`Platform URL: ${platformUrl}`);
|
|
46
|
+
|
|
47
|
+
const token = readPlatformToken();
|
|
48
|
+
if (!token) {
|
|
49
|
+
log?.("No platform token found — skipping cloud sync");
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
log?.(
|
|
53
|
+
`Token found (${token.length} chars, prefix: ${token.slice(0, 6)}…)`,
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
// Fetch user info for the login status line
|
|
57
|
+
let email: string | undefined;
|
|
58
|
+
try {
|
|
59
|
+
log?.("Fetching current user…");
|
|
60
|
+
const user = await fetchCurrentUser(token);
|
|
61
|
+
email = user.email;
|
|
62
|
+
log?.(`Authenticated as ${user.email} (${user.id})`);
|
|
63
|
+
} catch (err) {
|
|
64
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
65
|
+
log?.(`Failed to fetch current user: ${msg}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let platformAssistants: { id: string; name: string; status: string }[];
|
|
69
|
+
try {
|
|
70
|
+
log?.("Fetching platform assistants…");
|
|
71
|
+
platformAssistants = await fetchPlatformAssistants(token);
|
|
72
|
+
log?.(
|
|
73
|
+
`Platform returned ${platformAssistants.length} assistant(s): ${platformAssistants.map((a) => a.name || a.id).join(", ") || "(none)"}`,
|
|
74
|
+
);
|
|
75
|
+
} catch (err) {
|
|
76
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
77
|
+
log?.(`fetchPlatformAssistants failed: ${msg}`);
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (platformAssistants.length === 0) {
|
|
82
|
+
log?.(
|
|
83
|
+
"Platform returned 0 assistants — this may mean the API returned a non-ok status (check token validity)",
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const platformIds = new Set(platformAssistants.map((a) => a.id));
|
|
88
|
+
|
|
89
|
+
// Add new platform assistants not yet in the lockfile
|
|
90
|
+
const existingCloudIds = new Set(
|
|
91
|
+
loadAllAssistants()
|
|
92
|
+
.filter((a) => a.cloud === "vellum")
|
|
93
|
+
.map((a) => a.assistantId),
|
|
94
|
+
);
|
|
95
|
+
log?.(
|
|
96
|
+
`Lockfile has ${existingCloudIds.size} cloud assistant(s): ${[...existingCloudIds].join(", ") || "(none)"}`,
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
let added = 0;
|
|
100
|
+
for (const pa of platformAssistants) {
|
|
101
|
+
if (!existingCloudIds.has(pa.id)) {
|
|
102
|
+
log?.(`Adding ${pa.name || pa.id} to lockfile`);
|
|
103
|
+
saveAssistantEntry({
|
|
104
|
+
assistantId: pa.id,
|
|
105
|
+
runtimeUrl: getPlatformUrl(),
|
|
106
|
+
cloud: "vellum",
|
|
107
|
+
species: "vellum",
|
|
108
|
+
hatchedAt: new Date().toISOString(),
|
|
109
|
+
});
|
|
110
|
+
added++;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Remove stale lockfile entries that the platform no longer knows about
|
|
115
|
+
let removed = 0;
|
|
116
|
+
for (const id of existingCloudIds) {
|
|
117
|
+
if (!platformIds.has(id)) {
|
|
118
|
+
log?.(`Removing stale entry ${id} from lockfile`);
|
|
119
|
+
removeAssistantEntry(id);
|
|
120
|
+
removed++;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
log?.(`Sync complete: ${added} added, ${removed} removed`);
|
|
125
|
+
return { added, removed, email };
|
|
126
|
+
}
|
|
@@ -18,14 +18,19 @@ export async function createTerminalSession(
|
|
|
18
18
|
cols: number,
|
|
19
19
|
rows: number,
|
|
20
20
|
platformUrl?: string,
|
|
21
|
+
service?: string,
|
|
21
22
|
): Promise<{ session_id: string }> {
|
|
22
23
|
const baseUrl = platformUrl || getPlatformUrl();
|
|
24
|
+
const body: Record<string, unknown> = { cols, rows };
|
|
25
|
+
if (service) {
|
|
26
|
+
body.service = service;
|
|
27
|
+
}
|
|
23
28
|
const response = await fetch(
|
|
24
29
|
`${baseUrl}/v1/assistants/${assistantId}/terminal/sessions/`,
|
|
25
30
|
{
|
|
26
31
|
method: "POST",
|
|
27
32
|
headers: await authHeaders(token, platformUrl),
|
|
28
|
-
body: JSON.stringify(
|
|
33
|
+
body: JSON.stringify(body),
|
|
29
34
|
},
|
|
30
35
|
);
|
|
31
36
|
if (!response.ok) {
|