@ariobarin/glossa 0.2.1 → 0.2.3-beta.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 +2 -5
- package/dist/app.js +592 -1311
- package/package.json +1 -1
package/dist/app.js
CHANGED
|
@@ -10436,10 +10436,10 @@ var require_react_reconciler_development = __commonJS({
|
|
|
10436
10436
|
fiber = fiber.next, id--;
|
|
10437
10437
|
return fiber;
|
|
10438
10438
|
}
|
|
10439
|
-
function copyWithSetImpl(obj,
|
|
10440
|
-
if (index >=
|
|
10441
|
-
var key =
|
|
10442
|
-
updated[key] = copyWithSetImpl(obj[key],
|
|
10439
|
+
function copyWithSetImpl(obj, path12, index, value) {
|
|
10440
|
+
if (index >= path12.length) return value;
|
|
10441
|
+
var key = path12[index], updated = isArrayImpl(obj) ? obj.slice() : assign({}, obj);
|
|
10442
|
+
updated[key] = copyWithSetImpl(obj[key], path12, index + 1, value);
|
|
10443
10443
|
return updated;
|
|
10444
10444
|
}
|
|
10445
10445
|
function copyWithRename(obj, oldPath, newPath) {
|
|
@@ -10466,11 +10466,11 @@ var require_react_reconciler_development = __commonJS({
|
|
|
10466
10466
|
);
|
|
10467
10467
|
return updated;
|
|
10468
10468
|
}
|
|
10469
|
-
function copyWithDeleteImpl(obj,
|
|
10470
|
-
var key =
|
|
10471
|
-
if (index + 1 ===
|
|
10469
|
+
function copyWithDeleteImpl(obj, path12, index) {
|
|
10470
|
+
var key = path12[index], updated = isArrayImpl(obj) ? obj.slice() : assign({}, obj);
|
|
10471
|
+
if (index + 1 === path12.length)
|
|
10472
10472
|
return isArrayImpl(updated) ? updated.splice(key, 1) : delete updated[key], updated;
|
|
10473
|
-
updated[key] = copyWithDeleteImpl(obj[key],
|
|
10473
|
+
updated[key] = copyWithDeleteImpl(obj[key], path12, index + 1);
|
|
10474
10474
|
return updated;
|
|
10475
10475
|
}
|
|
10476
10476
|
function shouldSuspendImpl() {
|
|
@@ -23747,29 +23747,29 @@ var require_react_reconciler_development = __commonJS({
|
|
|
23747
23747
|
var didWarnAboutNestedUpdates = false;
|
|
23748
23748
|
var didWarnAboutFindNodeInStrictMode = {};
|
|
23749
23749
|
var overrideHookState = null, overrideHookStateDeletePath = null, overrideHookStateRenamePath = null, overrideProps = null, overridePropsDeletePath = null, overridePropsRenamePath = null, scheduleUpdate = null, scheduleRetry = null, setErrorHandler = null, setSuspenseHandler = null;
|
|
23750
|
-
overrideHookState = function(fiber, id,
|
|
23750
|
+
overrideHookState = function(fiber, id, path12, value) {
|
|
23751
23751
|
id = findHook(fiber, id);
|
|
23752
|
-
null !== id && (
|
|
23752
|
+
null !== id && (path12 = copyWithSetImpl(id.memoizedState, path12, 0, value), id.memoizedState = path12, id.baseState = path12, fiber.memoizedProps = assign({}, fiber.memoizedProps), path12 = enqueueConcurrentRenderForLane(fiber, 2), null !== path12 && scheduleUpdateOnFiber(path12, fiber, 2));
|
|
23753
23753
|
};
|
|
23754
|
-
overrideHookStateDeletePath = function(fiber, id,
|
|
23754
|
+
overrideHookStateDeletePath = function(fiber, id, path12) {
|
|
23755
23755
|
id = findHook(fiber, id);
|
|
23756
|
-
null !== id && (
|
|
23756
|
+
null !== id && (path12 = copyWithDeleteImpl(id.memoizedState, path12, 0), id.memoizedState = path12, id.baseState = path12, fiber.memoizedProps = assign({}, fiber.memoizedProps), path12 = enqueueConcurrentRenderForLane(fiber, 2), null !== path12 && scheduleUpdateOnFiber(path12, fiber, 2));
|
|
23757
23757
|
};
|
|
23758
23758
|
overrideHookStateRenamePath = function(fiber, id, oldPath, newPath) {
|
|
23759
23759
|
id = findHook(fiber, id);
|
|
23760
23760
|
null !== id && (oldPath = copyWithRename(id.memoizedState, oldPath, newPath), id.memoizedState = oldPath, id.baseState = oldPath, fiber.memoizedProps = assign({}, fiber.memoizedProps), oldPath = enqueueConcurrentRenderForLane(fiber, 2), null !== oldPath && scheduleUpdateOnFiber(oldPath, fiber, 2));
|
|
23761
23761
|
};
|
|
23762
|
-
overrideProps = function(fiber,
|
|
23763
|
-
fiber.pendingProps = copyWithSetImpl(fiber.memoizedProps,
|
|
23762
|
+
overrideProps = function(fiber, path12, value) {
|
|
23763
|
+
fiber.pendingProps = copyWithSetImpl(fiber.memoizedProps, path12, 0, value);
|
|
23764
23764
|
fiber.alternate && (fiber.alternate.pendingProps = fiber.pendingProps);
|
|
23765
|
-
|
|
23766
|
-
null !==
|
|
23765
|
+
path12 = enqueueConcurrentRenderForLane(fiber, 2);
|
|
23766
|
+
null !== path12 && scheduleUpdateOnFiber(path12, fiber, 2);
|
|
23767
23767
|
};
|
|
23768
|
-
overridePropsDeletePath = function(fiber,
|
|
23769
|
-
fiber.pendingProps = copyWithDeleteImpl(fiber.memoizedProps,
|
|
23768
|
+
overridePropsDeletePath = function(fiber, path12) {
|
|
23769
|
+
fiber.pendingProps = copyWithDeleteImpl(fiber.memoizedProps, path12, 0);
|
|
23770
23770
|
fiber.alternate && (fiber.alternate.pendingProps = fiber.pendingProps);
|
|
23771
|
-
|
|
23772
|
-
null !==
|
|
23771
|
+
path12 = enqueueConcurrentRenderForLane(fiber, 2);
|
|
23772
|
+
null !== path12 && scheduleUpdateOnFiber(path12, fiber, 2);
|
|
23773
23773
|
};
|
|
23774
23774
|
overridePropsRenamePath = function(fiber, oldPath, newPath) {
|
|
23775
23775
|
fiber.pendingProps = copyWithRename(
|
|
@@ -26745,536 +26745,6 @@ var require_semver2 = __commonJS({
|
|
|
26745
26745
|
}
|
|
26746
26746
|
});
|
|
26747
26747
|
|
|
26748
|
-
// src/auth-config.ts
|
|
26749
|
-
var DEFAULT_AUTH_CONFIG = {
|
|
26750
|
-
issuer: "https://dev-fl2h5xhp6umeh74m.us.auth0.com/",
|
|
26751
|
-
clientId: "9mwnK9nTAd8q1kxnKIZxC1wodxzfWHg5",
|
|
26752
|
-
audience: "https://mcp.glossa.sh/",
|
|
26753
|
-
scope: "openid profile offline_access glossa:device"
|
|
26754
|
-
};
|
|
26755
|
-
function configuredValue(environment, name, fallback) {
|
|
26756
|
-
const value = environment[name]?.trim();
|
|
26757
|
-
return value || fallback;
|
|
26758
|
-
}
|
|
26759
|
-
function loadAuthConfig(environment = process.env) {
|
|
26760
|
-
return {
|
|
26761
|
-
issuer: configuredValue(environment, "GLOSSA_AUTH0_ISSUER", DEFAULT_AUTH_CONFIG.issuer),
|
|
26762
|
-
clientId: configuredValue(
|
|
26763
|
-
environment,
|
|
26764
|
-
"GLOSSA_AUTH0_CLI_CLIENT_ID",
|
|
26765
|
-
DEFAULT_AUTH_CONFIG.clientId
|
|
26766
|
-
),
|
|
26767
|
-
audience: configuredValue(
|
|
26768
|
-
environment,
|
|
26769
|
-
"GLOSSA_AUTH0_AUDIENCE",
|
|
26770
|
-
DEFAULT_AUTH_CONFIG.audience
|
|
26771
|
-
),
|
|
26772
|
-
scope: DEFAULT_AUTH_CONFIG.scope
|
|
26773
|
-
};
|
|
26774
|
-
}
|
|
26775
|
-
|
|
26776
|
-
// src/config-store.ts
|
|
26777
|
-
import path2 from "node:path";
|
|
26778
|
-
|
|
26779
|
-
// src/secure-store.ts
|
|
26780
|
-
import { chmod, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
26781
|
-
import os from "node:os";
|
|
26782
|
-
import path from "node:path";
|
|
26783
|
-
var KEYRING_SERVICE = "Glossa";
|
|
26784
|
-
function configDirectory() {
|
|
26785
|
-
if (process.platform === "win32") {
|
|
26786
|
-
return path.join(process.env.APPDATA ?? os.homedir(), "Glossa");
|
|
26787
|
-
}
|
|
26788
|
-
return path.join(
|
|
26789
|
-
process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), ".config"),
|
|
26790
|
-
"glossa"
|
|
26791
|
-
);
|
|
26792
|
-
}
|
|
26793
|
-
var SecureStore = class {
|
|
26794
|
-
#options;
|
|
26795
|
-
#warned = false;
|
|
26796
|
-
constructor(options) {
|
|
26797
|
-
this.#options = options;
|
|
26798
|
-
}
|
|
26799
|
-
async save(value) {
|
|
26800
|
-
const serialized = JSON.stringify(value);
|
|
26801
|
-
const entry = await this.#entry();
|
|
26802
|
-
if (entry) {
|
|
26803
|
-
try {
|
|
26804
|
-
await this.#writeEntry(entry, serialized);
|
|
26805
|
-
await rm(this.#options.file, { force: true });
|
|
26806
|
-
return "keyring";
|
|
26807
|
-
} catch {
|
|
26808
|
-
}
|
|
26809
|
-
}
|
|
26810
|
-
this.#warn();
|
|
26811
|
-
await mkdir(path.dirname(this.#options.file), {
|
|
26812
|
-
recursive: true,
|
|
26813
|
-
mode: 448
|
|
26814
|
-
});
|
|
26815
|
-
await writeFile(
|
|
26816
|
-
this.#options.file,
|
|
26817
|
-
`${JSON.stringify(value, null, 2)}
|
|
26818
|
-
`,
|
|
26819
|
-
{ encoding: "utf8", mode: 384 }
|
|
26820
|
-
);
|
|
26821
|
-
if (process.platform !== "win32") {
|
|
26822
|
-
await chmod(this.#options.file, 384);
|
|
26823
|
-
}
|
|
26824
|
-
return "file";
|
|
26825
|
-
}
|
|
26826
|
-
async load() {
|
|
26827
|
-
const entry = await this.#entry();
|
|
26828
|
-
if (entry) {
|
|
26829
|
-
const value2 = await this.#readEntry(entry, true);
|
|
26830
|
-
if (value2 != null) return { value: value2, backend: "keyring" };
|
|
26831
|
-
}
|
|
26832
|
-
let value;
|
|
26833
|
-
try {
|
|
26834
|
-
value = this.#options.parse(await readFile(this.#options.file, "utf8"));
|
|
26835
|
-
} catch (error46) {
|
|
26836
|
-
if (error46.code === "ENOENT") return null;
|
|
26837
|
-
throw error46;
|
|
26838
|
-
}
|
|
26839
|
-
if (entry) {
|
|
26840
|
-
try {
|
|
26841
|
-
await this.#writeEntry(entry, JSON.stringify(value));
|
|
26842
|
-
await rm(this.#options.file, { force: true });
|
|
26843
|
-
return { value, backend: "keyring" };
|
|
26844
|
-
} catch {
|
|
26845
|
-
}
|
|
26846
|
-
}
|
|
26847
|
-
this.#warn();
|
|
26848
|
-
return { value, backend: "file" };
|
|
26849
|
-
}
|
|
26850
|
-
async peek() {
|
|
26851
|
-
const entry = await this.#entry();
|
|
26852
|
-
if (entry) {
|
|
26853
|
-
const value = await this.#readEntry(entry, false);
|
|
26854
|
-
if (value != null) return { value, backend: "keyring" };
|
|
26855
|
-
}
|
|
26856
|
-
try {
|
|
26857
|
-
return {
|
|
26858
|
-
value: this.#options.parse(await readFile(this.#options.file, "utf8")),
|
|
26859
|
-
backend: "file"
|
|
26860
|
-
};
|
|
26861
|
-
} catch (error46) {
|
|
26862
|
-
if (error46.code === "ENOENT") return null;
|
|
26863
|
-
throw error46;
|
|
26864
|
-
}
|
|
26865
|
-
}
|
|
26866
|
-
async delete() {
|
|
26867
|
-
const entry = await this.#entry();
|
|
26868
|
-
let keyringDeleteFailed = false;
|
|
26869
|
-
if (entry) {
|
|
26870
|
-
try {
|
|
26871
|
-
const deleted = await entry.deleteCredential();
|
|
26872
|
-
if (!deleted && await entry.getSecret() != null) {
|
|
26873
|
-
keyringDeleteFailed = true;
|
|
26874
|
-
}
|
|
26875
|
-
} catch {
|
|
26876
|
-
keyringDeleteFailed = true;
|
|
26877
|
-
}
|
|
26878
|
-
}
|
|
26879
|
-
await rm(this.#options.file, { force: true });
|
|
26880
|
-
if (keyringDeleteFailed) {
|
|
26881
|
-
throw new Error(
|
|
26882
|
-
"The operating-system credential store could not remove the Glossa credential."
|
|
26883
|
-
);
|
|
26884
|
-
}
|
|
26885
|
-
}
|
|
26886
|
-
async #entry() {
|
|
26887
|
-
if (this.#options.entryProvider) {
|
|
26888
|
-
try {
|
|
26889
|
-
return await this.#options.entryProvider();
|
|
26890
|
-
} catch {
|
|
26891
|
-
return null;
|
|
26892
|
-
}
|
|
26893
|
-
}
|
|
26894
|
-
try {
|
|
26895
|
-
const { AsyncEntry } = await import("@napi-rs/keyring");
|
|
26896
|
-
return new AsyncEntry(KEYRING_SERVICE, this.#options.account);
|
|
26897
|
-
} catch {
|
|
26898
|
-
return null;
|
|
26899
|
-
}
|
|
26900
|
-
}
|
|
26901
|
-
async #readEntry(entry, migrateLegacy) {
|
|
26902
|
-
let found = false;
|
|
26903
|
-
try {
|
|
26904
|
-
const secret = await entry.getSecret();
|
|
26905
|
-
if (secret != null) {
|
|
26906
|
-
found = true;
|
|
26907
|
-
try {
|
|
26908
|
-
const bytes = secret instanceof Uint8Array ? secret : Uint8Array.from(secret);
|
|
26909
|
-
return this.#options.parse(
|
|
26910
|
-
new TextDecoder("utf-8", { fatal: true }).decode(bytes)
|
|
26911
|
-
);
|
|
26912
|
-
} catch {
|
|
26913
|
-
}
|
|
26914
|
-
}
|
|
26915
|
-
} catch {
|
|
26916
|
-
}
|
|
26917
|
-
try {
|
|
26918
|
-
const serialized = await entry.getPassword();
|
|
26919
|
-
if (serialized != null) {
|
|
26920
|
-
found = true;
|
|
26921
|
-
const value = this.#options.parse(serialized);
|
|
26922
|
-
if (migrateLegacy) {
|
|
26923
|
-
try {
|
|
26924
|
-
await this.#writeEntry(entry, serialized);
|
|
26925
|
-
} catch {
|
|
26926
|
-
}
|
|
26927
|
-
}
|
|
26928
|
-
return value;
|
|
26929
|
-
}
|
|
26930
|
-
} catch {
|
|
26931
|
-
}
|
|
26932
|
-
if (found && migrateLegacy) {
|
|
26933
|
-
try {
|
|
26934
|
-
await entry.deleteCredential();
|
|
26935
|
-
} catch {
|
|
26936
|
-
}
|
|
26937
|
-
}
|
|
26938
|
-
return null;
|
|
26939
|
-
}
|
|
26940
|
-
async #writeEntry(entry, serialized) {
|
|
26941
|
-
await entry.setSecret(new TextEncoder().encode(serialized));
|
|
26942
|
-
}
|
|
26943
|
-
#warn() {
|
|
26944
|
-
if (this.#warned) return;
|
|
26945
|
-
this.#warned = true;
|
|
26946
|
-
(this.#options.warn ?? console.warn)(this.#options.warning);
|
|
26947
|
-
}
|
|
26948
|
-
};
|
|
26949
|
-
|
|
26950
|
-
// src/config-store.ts
|
|
26951
|
-
var FILE_CREDENTIAL_WARNING = "Warning: the operating-system credential store is unavailable. Glossa is using a mode-0600 credential file.";
|
|
26952
|
-
function parseCredentials(value) {
|
|
26953
|
-
let parsed;
|
|
26954
|
-
try {
|
|
26955
|
-
parsed = JSON.parse(value);
|
|
26956
|
-
} catch {
|
|
26957
|
-
throw new Error("Stored Glossa credentials are invalid.");
|
|
26958
|
-
}
|
|
26959
|
-
if (typeof parsed.issuer !== "string" || typeof parsed.clientId !== "string" || typeof parsed.audience !== "string" || typeof parsed.accessToken !== "string" || typeof parsed.expiresAt !== "string" || !Number.isFinite(Date.parse(parsed.expiresAt)) || typeof parsed.tokenType !== "string" || parsed.refreshToken !== void 0 && typeof parsed.refreshToken !== "string" || parsed.scope !== void 0 && typeof parsed.scope !== "string" || parsed.requestedScope !== void 0 && typeof parsed.requestedScope !== "string") {
|
|
26960
|
-
throw new Error("Stored Glossa credentials are invalid.");
|
|
26961
|
-
}
|
|
26962
|
-
return parsed;
|
|
26963
|
-
}
|
|
26964
|
-
var store = new SecureStore({
|
|
26965
|
-
account: "oauth",
|
|
26966
|
-
file: path2.join(configDirectory(), "credentials.json"),
|
|
26967
|
-
warning: FILE_CREDENTIAL_WARNING,
|
|
26968
|
-
parse: parseCredentials
|
|
26969
|
-
});
|
|
26970
|
-
async function saveCredentials(credentials) {
|
|
26971
|
-
return await store.save(credentials);
|
|
26972
|
-
}
|
|
26973
|
-
async function loadCredentials() {
|
|
26974
|
-
const loaded = await store.load();
|
|
26975
|
-
return loaded ? { credentials: loaded.value, backend: loaded.backend } : null;
|
|
26976
|
-
}
|
|
26977
|
-
async function peekCredentials() {
|
|
26978
|
-
const loaded = await store.peek();
|
|
26979
|
-
return loaded ? { credentials: loaded.value, backend: loaded.backend } : null;
|
|
26980
|
-
}
|
|
26981
|
-
async function deleteCredentials() {
|
|
26982
|
-
await store.delete();
|
|
26983
|
-
}
|
|
26984
|
-
|
|
26985
|
-
// src/auth-scopes.ts
|
|
26986
|
-
function normalizedScopes(value) {
|
|
26987
|
-
return [...new Set(value?.trim().split(/\s+/).filter(Boolean) ?? [])].sort();
|
|
26988
|
-
}
|
|
26989
|
-
function scopesMatch(left, right) {
|
|
26990
|
-
return JSON.stringify(normalizedScopes(left)) === JSON.stringify(normalizedScopes(right));
|
|
26991
|
-
}
|
|
26992
|
-
function grantedScopesSatisfyRequest(granted, requested, hasRefreshToken) {
|
|
26993
|
-
const grantedSet = new Set(normalizedScopes(granted));
|
|
26994
|
-
return normalizedScopes(requested).every(
|
|
26995
|
-
(scope) => grantedSet.has(scope) || scope === "offline_access" && hasRefreshToken
|
|
26996
|
-
);
|
|
26997
|
-
}
|
|
26998
|
-
|
|
26999
|
-
// src/auth-session.ts
|
|
27000
|
-
var EXPIRY_BUFFER_MS = 6e4;
|
|
27001
|
-
function endpoint(issuer, pathname) {
|
|
27002
|
-
return new URL(pathname, issuer.endsWith("/") ? issuer : `${issuer}/`).toString();
|
|
27003
|
-
}
|
|
27004
|
-
var SessionExpiredError = class extends Error {
|
|
27005
|
-
constructor() {
|
|
27006
|
-
super("Session expired. Run Glossa again to sign in.");
|
|
27007
|
-
this.name = "SessionExpiredError";
|
|
27008
|
-
}
|
|
27009
|
-
};
|
|
27010
|
-
function sessionExpiredError() {
|
|
27011
|
-
return new SessionExpiredError();
|
|
27012
|
-
}
|
|
27013
|
-
function oauthMessage(data, status) {
|
|
27014
|
-
return data.error_description ?? data.error ?? `HTTP ${status}`;
|
|
27015
|
-
}
|
|
27016
|
-
function isTokenResponse(data) {
|
|
27017
|
-
return "access_token" in data;
|
|
27018
|
-
}
|
|
27019
|
-
function accessTokenNeedsRefresh(credentials, now = Date.now()) {
|
|
27020
|
-
return Date.parse(credentials.expiresAt) <= now + EXPIRY_BUFFER_MS;
|
|
27021
|
-
}
|
|
27022
|
-
async function refreshCredentials(credentials, dependencies = {}) {
|
|
27023
|
-
if (!credentials.refreshToken) throw sessionExpiredError();
|
|
27024
|
-
const fetchRequest = dependencies.fetch ?? fetch;
|
|
27025
|
-
const remove = dependencies.deleteCredentials ?? deleteCredentials;
|
|
27026
|
-
const save = dependencies.saveCredentials ?? saveCredentials;
|
|
27027
|
-
const now = dependencies.now ?? Date.now;
|
|
27028
|
-
const response = await fetchRequest(endpoint(credentials.issuer, "oauth/token"), {
|
|
27029
|
-
method: "POST",
|
|
27030
|
-
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
27031
|
-
body: new URLSearchParams({
|
|
27032
|
-
grant_type: "refresh_token",
|
|
27033
|
-
client_id: credentials.clientId,
|
|
27034
|
-
refresh_token: credentials.refreshToken
|
|
27035
|
-
}),
|
|
27036
|
-
...dependencies.signal ? { signal: dependencies.signal } : {}
|
|
27037
|
-
});
|
|
27038
|
-
const data = await response.json();
|
|
27039
|
-
if (!response.ok || !isTokenResponse(data)) {
|
|
27040
|
-
const oauth = data;
|
|
27041
|
-
if (oauth.error === "invalid_grant") {
|
|
27042
|
-
await remove();
|
|
27043
|
-
throw sessionExpiredError();
|
|
27044
|
-
}
|
|
27045
|
-
throw new Error(oauthMessage(oauth, response.status));
|
|
27046
|
-
}
|
|
27047
|
-
const grantedScope = data.scope ?? credentials.scope;
|
|
27048
|
-
if (credentials.requestedScope && !grantedScopesSatisfyRequest(
|
|
27049
|
-
grantedScope,
|
|
27050
|
-
credentials.requestedScope,
|
|
27051
|
-
Boolean(data.refresh_token ?? credentials.refreshToken)
|
|
27052
|
-
)) {
|
|
27053
|
-
await remove();
|
|
27054
|
-
throw sessionExpiredError();
|
|
27055
|
-
}
|
|
27056
|
-
const refreshed = {
|
|
27057
|
-
...credentials,
|
|
27058
|
-
accessToken: data.access_token,
|
|
27059
|
-
refreshToken: data.refresh_token ?? credentials.refreshToken,
|
|
27060
|
-
expiresAt: new Date(now() + data.expires_in * 1e3).toISOString(),
|
|
27061
|
-
tokenType: data.token_type,
|
|
27062
|
-
...grantedScope ? { scope: grantedScope } : {}
|
|
27063
|
-
};
|
|
27064
|
-
await save(refreshed);
|
|
27065
|
-
return refreshed;
|
|
27066
|
-
}
|
|
27067
|
-
async function validCredentials(credentials, dependencies = {}) {
|
|
27068
|
-
const now = dependencies.now ?? Date.now;
|
|
27069
|
-
if (!accessTokenNeedsRefresh(credentials, now())) return credentials;
|
|
27070
|
-
return await refreshCredentials(credentials, dependencies);
|
|
27071
|
-
}
|
|
27072
|
-
function parseProfile(value) {
|
|
27073
|
-
if (!value || typeof value !== "object") {
|
|
27074
|
-
throw new Error("Auth0 returned an invalid user profile.");
|
|
27075
|
-
}
|
|
27076
|
-
const profile = value;
|
|
27077
|
-
if (typeof profile.sub !== "string" || profile.name !== void 0 && typeof profile.name !== "string" || profile.email !== void 0 && typeof profile.email !== "string") {
|
|
27078
|
-
throw new Error("Auth0 returned an invalid user profile.");
|
|
27079
|
-
}
|
|
27080
|
-
return profile;
|
|
27081
|
-
}
|
|
27082
|
-
async function requestProfile(credentials, fetchRequest, signal) {
|
|
27083
|
-
return await fetchRequest(endpoint(credentials.issuer, "userinfo"), {
|
|
27084
|
-
headers: { authorization: `${credentials.tokenType} ${credentials.accessToken}` },
|
|
27085
|
-
...signal ? { signal } : {}
|
|
27086
|
-
});
|
|
27087
|
-
}
|
|
27088
|
-
async function loadUserProfile(credentials, dependencies = {}) {
|
|
27089
|
-
const fetchRequest = dependencies.fetch ?? fetch;
|
|
27090
|
-
let current = await validCredentials(credentials, dependencies);
|
|
27091
|
-
let response = await requestProfile(current, fetchRequest, dependencies.signal);
|
|
27092
|
-
if (response.status === 401 && current.refreshToken) {
|
|
27093
|
-
current = await refreshCredentials(current, dependencies);
|
|
27094
|
-
response = await requestProfile(current, fetchRequest, dependencies.signal);
|
|
27095
|
-
}
|
|
27096
|
-
if (!response.ok) {
|
|
27097
|
-
throw new Error(`Auth0 profile request failed with HTTP ${response.status}.`);
|
|
27098
|
-
}
|
|
27099
|
-
return { credentials: current, profile: parseProfile(await response.json()) };
|
|
27100
|
-
}
|
|
27101
|
-
|
|
27102
|
-
// src/device-flow.ts
|
|
27103
|
-
import { setTimeout as delay } from "node:timers/promises";
|
|
27104
|
-
|
|
27105
|
-
// src/open-browser.ts
|
|
27106
|
-
import { spawn } from "node:child_process";
|
|
27107
|
-
async function openBrowser(url2) {
|
|
27108
|
-
const command = process.platform === "darwin" ? { file: "open", args: [url2] } : process.platform === "win32" ? { file: "cmd", args: ["/c", "start", "", url2] } : { file: "xdg-open", args: [url2] };
|
|
27109
|
-
return await new Promise((resolve) => {
|
|
27110
|
-
const child = spawn(command.file, command.args, {
|
|
27111
|
-
detached: true,
|
|
27112
|
-
stdio: "ignore",
|
|
27113
|
-
windowsHide: true
|
|
27114
|
-
});
|
|
27115
|
-
child.once("error", () => resolve(false));
|
|
27116
|
-
child.once("spawn", () => {
|
|
27117
|
-
child.unref();
|
|
27118
|
-
resolve(true);
|
|
27119
|
-
});
|
|
27120
|
-
});
|
|
27121
|
-
}
|
|
27122
|
-
|
|
27123
|
-
// src/device-flow.ts
|
|
27124
|
-
function endpoint2(issuer, pathname) {
|
|
27125
|
-
return new URL(pathname, issuer.endsWith("/") ? issuer : `${issuer}/`).toString();
|
|
27126
|
-
}
|
|
27127
|
-
function canceledError() {
|
|
27128
|
-
return new Error("Login canceled.");
|
|
27129
|
-
}
|
|
27130
|
-
function assertActive(signal) {
|
|
27131
|
-
if (signal?.aborted) throw canceledError();
|
|
27132
|
-
}
|
|
27133
|
-
async function defaultDelay(milliseconds, signal) {
|
|
27134
|
-
await delay(milliseconds, void 0, signal ? { signal } : void 0);
|
|
27135
|
-
}
|
|
27136
|
-
async function postForm(fetchRequest, url2, values, signal) {
|
|
27137
|
-
const response = await fetchRequest(url2, {
|
|
27138
|
-
method: "POST",
|
|
27139
|
-
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
27140
|
-
body: new URLSearchParams(values),
|
|
27141
|
-
...signal ? { signal } : {}
|
|
27142
|
-
});
|
|
27143
|
-
const data = await response.json();
|
|
27144
|
-
if (!response.ok) {
|
|
27145
|
-
const oauth = data;
|
|
27146
|
-
throw new Error(oauth.error_description ?? oauth.error ?? `HTTP ${response.status}`);
|
|
27147
|
-
}
|
|
27148
|
-
return data;
|
|
27149
|
-
}
|
|
27150
|
-
async function authorizeWithDeviceFlow(options, dependencies = {}) {
|
|
27151
|
-
const fetchRequest = dependencies.fetch ?? fetch;
|
|
27152
|
-
const wait = dependencies.delay ?? defaultDelay;
|
|
27153
|
-
const browse = dependencies.openBrowser ?? openBrowser;
|
|
27154
|
-
const now = dependencies.now ?? Date.now;
|
|
27155
|
-
const log = dependencies.log ?? console.log;
|
|
27156
|
-
try {
|
|
27157
|
-
assertActive(options.signal);
|
|
27158
|
-
const code = await postForm(
|
|
27159
|
-
fetchRequest,
|
|
27160
|
-
endpoint2(options.issuer, "oauth/device/code"),
|
|
27161
|
-
{
|
|
27162
|
-
client_id: options.clientId,
|
|
27163
|
-
audience: options.audience,
|
|
27164
|
-
scope: options.scope
|
|
27165
|
-
},
|
|
27166
|
-
options.signal
|
|
27167
|
-
);
|
|
27168
|
-
const verificationUrl = code.verification_uri_complete ?? code.verification_uri;
|
|
27169
|
-
const opened = await browse(verificationUrl);
|
|
27170
|
-
log(opened ? "Opened your browser for Glossa login." : "Open this URL to sign in:");
|
|
27171
|
-
log(verificationUrl);
|
|
27172
|
-
if (!code.verification_uri_complete) log(`Code: ${code.user_code}`);
|
|
27173
|
-
const startedAt = now();
|
|
27174
|
-
let intervalSeconds = Math.max(code.interval ?? 5, 1);
|
|
27175
|
-
while (now() - startedAt < code.expires_in * 1e3) {
|
|
27176
|
-
assertActive(options.signal);
|
|
27177
|
-
await wait(intervalSeconds * 1e3, options.signal);
|
|
27178
|
-
assertActive(options.signal);
|
|
27179
|
-
const response = await fetchRequest(endpoint2(options.issuer, "oauth/token"), {
|
|
27180
|
-
method: "POST",
|
|
27181
|
-
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
27182
|
-
body: new URLSearchParams({
|
|
27183
|
-
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
27184
|
-
device_code: code.device_code,
|
|
27185
|
-
client_id: options.clientId
|
|
27186
|
-
}),
|
|
27187
|
-
...options.signal ? { signal: options.signal } : {}
|
|
27188
|
-
});
|
|
27189
|
-
const data = await response.json();
|
|
27190
|
-
if (response.ok && "access_token" in data) {
|
|
27191
|
-
const grantedScope = data.scope ?? options.scope;
|
|
27192
|
-
if (!grantedScopesSatisfyRequest(
|
|
27193
|
-
grantedScope,
|
|
27194
|
-
options.scope,
|
|
27195
|
-
Boolean(data.refresh_token)
|
|
27196
|
-
)) {
|
|
27197
|
-
throw new Error("Auth0 did not grant the permissions Glossa requires.");
|
|
27198
|
-
}
|
|
27199
|
-
return {
|
|
27200
|
-
issuer: options.issuer,
|
|
27201
|
-
clientId: options.clientId,
|
|
27202
|
-
audience: options.audience,
|
|
27203
|
-
accessToken: data.access_token,
|
|
27204
|
-
...data.refresh_token ? { refreshToken: data.refresh_token } : {},
|
|
27205
|
-
expiresAt: new Date(now() + data.expires_in * 1e3).toISOString(),
|
|
27206
|
-
tokenType: data.token_type,
|
|
27207
|
-
scope: grantedScope,
|
|
27208
|
-
requestedScope: options.scope
|
|
27209
|
-
};
|
|
27210
|
-
}
|
|
27211
|
-
const error46 = data;
|
|
27212
|
-
if (error46.error === "authorization_pending") continue;
|
|
27213
|
-
if (error46.error === "slow_down") {
|
|
27214
|
-
intervalSeconds += 5;
|
|
27215
|
-
continue;
|
|
27216
|
-
}
|
|
27217
|
-
if (error46.error === "access_denied") throw new Error("Login was denied.");
|
|
27218
|
-
if (error46.error === "expired_token") throw new Error("The login code expired.");
|
|
27219
|
-
throw new Error(error46.error_description ?? error46.error);
|
|
27220
|
-
}
|
|
27221
|
-
throw new Error("The login code expired.");
|
|
27222
|
-
} catch (error46) {
|
|
27223
|
-
if (options.signal?.aborted || error46 instanceof Error && error46.name === "AbortError") {
|
|
27224
|
-
throw canceledError();
|
|
27225
|
-
}
|
|
27226
|
-
throw error46;
|
|
27227
|
-
}
|
|
27228
|
-
}
|
|
27229
|
-
async function loginWithDeviceFlow(options, dependencies = {}) {
|
|
27230
|
-
const credentials = await authorizeWithDeviceFlow(options, dependencies);
|
|
27231
|
-
if (!credentials.refreshToken) {
|
|
27232
|
-
throw new Error("Auth0 did not issue a refresh token.");
|
|
27233
|
-
}
|
|
27234
|
-
const save = dependencies.saveCredentials ?? saveCredentials;
|
|
27235
|
-
await save(credentials);
|
|
27236
|
-
(dependencies.log ?? console.log)("Signed in to Glossa.");
|
|
27237
|
-
}
|
|
27238
|
-
|
|
27239
|
-
// src/auth-login.ts
|
|
27240
|
-
function normalizedIssuer(value) {
|
|
27241
|
-
return value.replace(/\/+$/, "");
|
|
27242
|
-
}
|
|
27243
|
-
function credentialsMatchLoginOptions(credentials, options) {
|
|
27244
|
-
return normalizedIssuer(credentials.issuer) === normalizedIssuer(options.issuer) && credentials.clientId === options.clientId && credentials.audience === options.audience && scopesMatch(credentials.requestedScope, options.scope) && grantedScopesSatisfyRequest(
|
|
27245
|
-
credentials.scope,
|
|
27246
|
-
options.scope,
|
|
27247
|
-
Boolean(credentials.refreshToken)
|
|
27248
|
-
);
|
|
27249
|
-
}
|
|
27250
|
-
async function signedInSession(options, dependencies = {}) {
|
|
27251
|
-
const load = dependencies.loadCredentials ?? loadCredentials;
|
|
27252
|
-
const validate2 = dependencies.validCredentials ?? validCredentials;
|
|
27253
|
-
const login = dependencies.loginWithDeviceFlow ?? loginWithDeviceFlow;
|
|
27254
|
-
const loaded = await load();
|
|
27255
|
-
if (loaded && credentialsMatchLoginOptions(loaded.credentials, options)) {
|
|
27256
|
-
try {
|
|
27257
|
-
const credentials = await validate2(
|
|
27258
|
-
loaded.credentials,
|
|
27259
|
-
options.signal ? { signal: options.signal } : {}
|
|
27260
|
-
);
|
|
27261
|
-
return { credentials, loginPerformed: false };
|
|
27262
|
-
} catch (error46) {
|
|
27263
|
-
if (!(error46 instanceof SessionExpiredError)) throw error46;
|
|
27264
|
-
}
|
|
27265
|
-
}
|
|
27266
|
-
await login(options);
|
|
27267
|
-
const completed = await load();
|
|
27268
|
-
if (!completed) throw new Error("Glossa could not load the completed login.");
|
|
27269
|
-
return {
|
|
27270
|
-
credentials: await validate2(
|
|
27271
|
-
completed.credentials,
|
|
27272
|
-
options.signal ? { signal: options.signal } : {}
|
|
27273
|
-
),
|
|
27274
|
-
loginPerformed: true
|
|
27275
|
-
};
|
|
27276
|
-
}
|
|
27277
|
-
|
|
27278
26748
|
// ../../node_modules/zod/v4/classic/external.js
|
|
27279
26749
|
var external_exports = {};
|
|
27280
26750
|
__export(external_exports, {
|
|
@@ -28006,10 +27476,10 @@ function mergeDefs(...defs) {
|
|
|
28006
27476
|
function cloneDef(schema) {
|
|
28007
27477
|
return mergeDefs(schema._zod.def);
|
|
28008
27478
|
}
|
|
28009
|
-
function getElementAtPath(obj,
|
|
28010
|
-
if (!
|
|
27479
|
+
function getElementAtPath(obj, path12) {
|
|
27480
|
+
if (!path12)
|
|
28011
27481
|
return obj;
|
|
28012
|
-
return
|
|
27482
|
+
return path12.reduce((acc, key) => acc?.[key], obj);
|
|
28013
27483
|
}
|
|
28014
27484
|
function promiseAllObject(promisesObj) {
|
|
28015
27485
|
const keys = Object.keys(promisesObj);
|
|
@@ -28370,11 +27840,11 @@ function aborted(x, startIndex = 0) {
|
|
|
28370
27840
|
}
|
|
28371
27841
|
return false;
|
|
28372
27842
|
}
|
|
28373
|
-
function prefixIssues(
|
|
27843
|
+
function prefixIssues(path12, issues) {
|
|
28374
27844
|
return issues.map((iss) => {
|
|
28375
27845
|
var _a;
|
|
28376
27846
|
(_a = iss).path ?? (_a.path = []);
|
|
28377
|
-
iss.path.unshift(
|
|
27847
|
+
iss.path.unshift(path12);
|
|
28378
27848
|
return iss;
|
|
28379
27849
|
});
|
|
28380
27850
|
}
|
|
@@ -28536,7 +28006,7 @@ function formatError(error46, mapper = (issue2) => issue2.message) {
|
|
|
28536
28006
|
}
|
|
28537
28007
|
function treeifyError(error46, mapper = (issue2) => issue2.message) {
|
|
28538
28008
|
const result = { errors: [] };
|
|
28539
|
-
const processError = (error47,
|
|
28009
|
+
const processError = (error47, path12 = []) => {
|
|
28540
28010
|
var _a, _b;
|
|
28541
28011
|
for (const issue2 of error47.issues) {
|
|
28542
28012
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
@@ -28546,7 +28016,7 @@ function treeifyError(error46, mapper = (issue2) => issue2.message) {
|
|
|
28546
28016
|
} else if (issue2.code === "invalid_element") {
|
|
28547
28017
|
processError({ issues: issue2.issues }, issue2.path);
|
|
28548
28018
|
} else {
|
|
28549
|
-
const fullpath = [...
|
|
28019
|
+
const fullpath = [...path12, ...issue2.path];
|
|
28550
28020
|
if (fullpath.length === 0) {
|
|
28551
28021
|
result.errors.push(mapper(issue2));
|
|
28552
28022
|
continue;
|
|
@@ -28578,8 +28048,8 @@ function treeifyError(error46, mapper = (issue2) => issue2.message) {
|
|
|
28578
28048
|
}
|
|
28579
28049
|
function toDotPath(_path) {
|
|
28580
28050
|
const segs = [];
|
|
28581
|
-
const
|
|
28582
|
-
for (const seg of
|
|
28051
|
+
const path12 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
28052
|
+
for (const seg of path12) {
|
|
28583
28053
|
if (typeof seg === "number")
|
|
28584
28054
|
segs.push(`[${seg}]`);
|
|
28585
28055
|
else if (typeof seg === "symbol")
|
|
@@ -39910,6 +39380,7 @@ function containsRestrictedAuthenticationData(value) {
|
|
|
39910
39380
|
|
|
39911
39381
|
// ../protocol/dist/index.js
|
|
39912
39382
|
var MAX_TEXT_BYTES = 1024 * 1024;
|
|
39383
|
+
var MAX_IMAGE_BYTES = 4 * 1024 * 1024;
|
|
39913
39384
|
var MAX_EDIT_DIFF_BYTES = 128 * 1024;
|
|
39914
39385
|
var MAX_EDIT_OPERATIONS = 100;
|
|
39915
39386
|
var MAX_COMMAND_OUTPUT_BYTES = 12 * 1024;
|
|
@@ -39958,6 +39429,13 @@ var readFileJobSchema = readFileRequestSchema.extend({
|
|
|
39958
39429
|
type: external_exports.literal("read_file"),
|
|
39959
39430
|
requestId: external_exports.string().uuid()
|
|
39960
39431
|
});
|
|
39432
|
+
var viewImageRequestSchema = external_exports.object({
|
|
39433
|
+
path: relativePathSchema
|
|
39434
|
+
}).strict();
|
|
39435
|
+
var viewImageJobSchema = viewImageRequestSchema.extend({
|
|
39436
|
+
type: external_exports.literal("view_image"),
|
|
39437
|
+
requestId: external_exports.string().uuid()
|
|
39438
|
+
});
|
|
39961
39439
|
var structuredReadTimeoutSchema = external_exports.number().int().min(1).max(MAX_STRUCTURED_READ_TIMEOUT_MS);
|
|
39962
39440
|
var listFilesCursorSchema = external_exports.string().max(4096).describe("Opaque cursor returned by an earlier list_files result.");
|
|
39963
39441
|
var listFilesRequestSchema = external_exports.object({
|
|
@@ -40100,6 +39578,7 @@ var cancelCommandJobSchema = cancelCommandRequestSchema.extend({
|
|
|
40100
39578
|
});
|
|
40101
39579
|
var workerJobSchema = external_exports.discriminatedUnion("type", [
|
|
40102
39580
|
readFileJobSchema,
|
|
39581
|
+
viewImageJobSchema,
|
|
40103
39582
|
listFilesJobSchema,
|
|
40104
39583
|
searchTextJobSchema,
|
|
40105
39584
|
readFileRangeJobSchema,
|
|
@@ -40129,9 +39608,12 @@ var UsageError = class extends Error {
|
|
|
40129
39608
|
};
|
|
40130
39609
|
var retiredCommands = /* @__PURE__ */ new Set([
|
|
40131
39610
|
"completions",
|
|
39611
|
+
"devices",
|
|
40132
39612
|
"doctor",
|
|
40133
39613
|
"login",
|
|
40134
|
-
"
|
|
39614
|
+
"logout",
|
|
39615
|
+
"start",
|
|
39616
|
+
"status"
|
|
40135
39617
|
]);
|
|
40136
39618
|
function parseWorkspace(args) {
|
|
40137
39619
|
let selectedPath;
|
|
@@ -40189,12 +39671,6 @@ function parseWorkspace(args) {
|
|
|
40189
39671
|
accessProfile
|
|
40190
39672
|
};
|
|
40191
39673
|
}
|
|
40192
|
-
function parseDevices(args) {
|
|
40193
|
-
if (args[0] === "revoke" && args.length === 2) {
|
|
40194
|
-
return { command: "devices", action: "revoke", deviceId: args[1] };
|
|
40195
|
-
}
|
|
40196
|
-
throw new UsageError("Use: glossa devices revoke <id>.");
|
|
40197
|
-
}
|
|
40198
39674
|
function parseUpdate(args) {
|
|
40199
39675
|
if (args.length === 0) return { command: "update", action: "install" };
|
|
40200
39676
|
let check3 = false;
|
|
@@ -40254,15 +39730,6 @@ function parseInvocation(args) {
|
|
|
40254
39730
|
return { command: "version" };
|
|
40255
39731
|
}
|
|
40256
39732
|
if (command === "--") return parseWorkspace(args);
|
|
40257
|
-
if (command === "status") {
|
|
40258
|
-
noOptions("Status", options);
|
|
40259
|
-
return { command };
|
|
40260
|
-
}
|
|
40261
|
-
if (command === "devices") return parseDevices(options);
|
|
40262
|
-
if (command === "logout") {
|
|
40263
|
-
noOptions("Logout", options);
|
|
40264
|
-
return { command };
|
|
40265
|
-
}
|
|
40266
39733
|
if (command === "unpair") {
|
|
40267
39734
|
noOptions("Unpair", options);
|
|
40268
39735
|
return { command };
|
|
@@ -40291,47 +39758,9 @@ function formatRelativeTime(iso, now = Date.now()) {
|
|
|
40291
39758
|
const days = Math.round(hours / 24);
|
|
40292
39759
|
return `${days}d ago`;
|
|
40293
39760
|
}
|
|
40294
|
-
function formatDeviceRow(device, now = Date.now()) {
|
|
40295
|
-
const platform2 = device.platform ?? "unknown platform";
|
|
40296
|
-
return `${device.id} ${device.name} ${platform2} last seen ${formatRelativeTime(device.lastSeenAt, now)} ${deviceStatus(device)}`;
|
|
40297
|
-
}
|
|
40298
|
-
|
|
40299
|
-
// src/logout.ts
|
|
40300
|
-
function browserLogoutUrl(issuer) {
|
|
40301
|
-
return new URL(
|
|
40302
|
-
"v2/logout",
|
|
40303
|
-
issuer.endsWith("/") ? issuer : `${issuer}/`
|
|
40304
|
-
).toString();
|
|
40305
|
-
}
|
|
40306
|
-
async function logoutFromGlossa(dependencies = {}) {
|
|
40307
|
-
const remove = dependencies.deleteCredentials ?? deleteCredentials;
|
|
40308
|
-
const peek = dependencies.peekCredentials ?? peekCredentials;
|
|
40309
|
-
const browse = dependencies.openBrowser ?? openBrowser;
|
|
40310
|
-
const log = dependencies.log ?? console.log;
|
|
40311
|
-
let stored = null;
|
|
40312
|
-
let present = true;
|
|
40313
|
-
try {
|
|
40314
|
-
stored = await peek();
|
|
40315
|
-
present = stored !== null;
|
|
40316
|
-
} catch {
|
|
40317
|
-
}
|
|
40318
|
-
const issuer = dependencies.issuer ?? stored?.credentials.issuer;
|
|
40319
|
-
await remove();
|
|
40320
|
-
log(
|
|
40321
|
-
present ? "Signed out of the Glossa account. Computer pairing stays active." : "Already signed out of the Glossa account. Computer pairing stays active."
|
|
40322
|
-
);
|
|
40323
|
-
const url2 = browserLogoutUrl(issuer ?? loadAuthConfig().issuer);
|
|
40324
|
-
const opened = await browse(url2);
|
|
40325
|
-
if (opened) {
|
|
40326
|
-
log("Opened Glossa browser sign-out.");
|
|
40327
|
-
} else {
|
|
40328
|
-
log("Open this URL to finish signing out in your browser:");
|
|
40329
|
-
log(url2);
|
|
40330
|
-
}
|
|
40331
|
-
}
|
|
40332
39761
|
|
|
40333
39762
|
// src/relay-client.ts
|
|
40334
|
-
import
|
|
39763
|
+
import os from "node:os";
|
|
40335
39764
|
var DEFAULT_RELAY_ORIGIN = "https://mcp.glossa.sh";
|
|
40336
39765
|
function isLoopback(hostname3) {
|
|
40337
39766
|
return hostname3 === "localhost" || hostname3 === "127.0.0.1" || hostname3 === "[::1]";
|
|
@@ -40375,10 +39804,61 @@ function loadRelayEndpoints(environment = process.env) {
|
|
|
40375
39804
|
return { relayOrigin, workerOrigin };
|
|
40376
39805
|
}
|
|
40377
39806
|
function defaultDeviceName() {
|
|
40378
|
-
return deviceNameSchema.parse(
|
|
39807
|
+
return deviceNameSchema.parse(os.hostname());
|
|
39808
|
+
}
|
|
39809
|
+
var PairingCodeExpiredError = class extends Error {
|
|
39810
|
+
};
|
|
39811
|
+
function pairingHttpError(status) {
|
|
39812
|
+
if (status === 429) {
|
|
39813
|
+
return new Error("Glossa pairing is rate limited. Try again later.");
|
|
39814
|
+
}
|
|
39815
|
+
return new Error(`The Glossa relay returned HTTP ${status}.`);
|
|
39816
|
+
}
|
|
39817
|
+
async function createPairing(endpoints, deviceName, platform2, fetchRequest = fetch) {
|
|
39818
|
+
const response = await fetchRequest(`${endpoints.relayOrigin}/v1/pairings`, {
|
|
39819
|
+
method: "POST",
|
|
39820
|
+
headers: { "content-type": "application/json" },
|
|
39821
|
+
body: JSON.stringify({ name: deviceNameSchema.parse(deviceName), platform: platform2 })
|
|
39822
|
+
});
|
|
39823
|
+
let data = {};
|
|
39824
|
+
try {
|
|
39825
|
+
data = await response.json();
|
|
39826
|
+
} catch {
|
|
39827
|
+
}
|
|
39828
|
+
if (!response.ok) throw pairingHttpError(response.status);
|
|
39829
|
+
if (typeof data.code !== "string" || typeof data.expiresAt !== "string" || Number.isNaN(Date.parse(data.expiresAt))) {
|
|
39830
|
+
throw new Error("The Glossa relay returned an invalid pairing response.");
|
|
39831
|
+
}
|
|
39832
|
+
return { code: data.code, expiresAt: data.expiresAt };
|
|
39833
|
+
}
|
|
39834
|
+
async function redeemPairing(endpoints, code, fetchRequest = fetch) {
|
|
39835
|
+
const response = await fetchRequest(`${endpoints.relayOrigin}/v1/pairings/redeem`, {
|
|
39836
|
+
method: "POST",
|
|
39837
|
+
headers: { "content-type": "application/json" },
|
|
39838
|
+
body: JSON.stringify({ code })
|
|
39839
|
+
});
|
|
39840
|
+
let data = {};
|
|
39841
|
+
try {
|
|
39842
|
+
data = await response.json();
|
|
39843
|
+
} catch {
|
|
39844
|
+
}
|
|
39845
|
+
if (response.status === 202) return "pending";
|
|
39846
|
+
if (response.status === 404) {
|
|
39847
|
+
throw new PairingCodeExpiredError(
|
|
39848
|
+
"The pairing code expired. Glossa will show a fresh one."
|
|
39849
|
+
);
|
|
39850
|
+
}
|
|
39851
|
+
if (!response.ok) throw pairingHttpError(response.status);
|
|
39852
|
+
if (typeof data.device?.id !== "string" || typeof data.device.name !== "string" || typeof data.device_token !== "string") {
|
|
39853
|
+
throw new Error("The Glossa relay returned an invalid pairing response.");
|
|
39854
|
+
}
|
|
39855
|
+
return {
|
|
39856
|
+
device: { id: data.device.id, name: data.device.name },
|
|
39857
|
+
token: data.device_token
|
|
39858
|
+
};
|
|
40379
39859
|
}
|
|
40380
|
-
function relayError(status, data) {
|
|
40381
|
-
if (status === 401) return new Error(
|
|
39860
|
+
function relayError(status, data, unauthorized) {
|
|
39861
|
+
if (status === 401) return new Error(unauthorized);
|
|
40382
39862
|
if (status === 403 && data.error === "account_disabled") {
|
|
40383
39863
|
return new Error("This Glossa account is disabled.");
|
|
40384
39864
|
}
|
|
@@ -40401,7 +39881,7 @@ function relayError(status, data) {
|
|
|
40401
39881
|
function validNullableString(value) {
|
|
40402
39882
|
return value === null || typeof value === "string";
|
|
40403
39883
|
}
|
|
40404
|
-
function
|
|
39884
|
+
function parseDevices(value) {
|
|
40405
39885
|
if (!Array.isArray(value)) {
|
|
40406
39886
|
throw new Error("The Glossa relay returned an invalid device list response.");
|
|
40407
39887
|
}
|
|
@@ -40416,63 +39896,18 @@ function parseDevices2(value) {
|
|
|
40416
39896
|
activeWorkers: device.activeWorkers ?? null
|
|
40417
39897
|
}));
|
|
40418
39898
|
}
|
|
40419
|
-
|
|
39899
|
+
var PAIRING_REJECTED = "This computer's pairing is no longer valid. Run Glossa again to pair it.";
|
|
39900
|
+
async function listDevices(endpoints, authorization, fetchRequest = fetch) {
|
|
40420
39901
|
const response = await fetchRequest(`${endpoints.relayOrigin}/v1/devices`, {
|
|
40421
|
-
headers: {
|
|
40422
|
-
authorization: `${credentials.tokenType} ${credentials.accessToken}`
|
|
40423
|
-
}
|
|
39902
|
+
headers: { authorization }
|
|
40424
39903
|
});
|
|
40425
39904
|
let data = {};
|
|
40426
39905
|
try {
|
|
40427
39906
|
data = await response.json();
|
|
40428
39907
|
} catch {
|
|
40429
39908
|
}
|
|
40430
|
-
if (!response.ok) throw relayError(response.status, data);
|
|
40431
|
-
return
|
|
40432
|
-
}
|
|
40433
|
-
async function enrollDevice(endpoints, credentials, deviceName, fetchRequest = fetch) {
|
|
40434
|
-
const baseName = deviceNameSchema.parse(deviceName);
|
|
40435
|
-
let name = baseName;
|
|
40436
|
-
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
40437
|
-
const response = await fetchRequest(`${endpoints.relayOrigin}/v1/devices/enroll`, {
|
|
40438
|
-
method: "POST",
|
|
40439
|
-
headers: {
|
|
40440
|
-
authorization: `${credentials.tokenType} ${credentials.accessToken}`,
|
|
40441
|
-
"content-type": "application/json"
|
|
40442
|
-
},
|
|
40443
|
-
body: JSON.stringify({ name, platform: `${process.platform}-${process.arch}` })
|
|
40444
|
-
});
|
|
40445
|
-
let data = {};
|
|
40446
|
-
try {
|
|
40447
|
-
data = await response.json();
|
|
40448
|
-
} catch {
|
|
40449
|
-
}
|
|
40450
|
-
if (response.status === 409 && data.error === "device_name_conflict" && attempt < 2) {
|
|
40451
|
-
const activeNames = new Set(
|
|
40452
|
-
(await listDevices(endpoints, credentials, fetchRequest)).filter((device) => device.revokedAt === null).map((device) => device.name)
|
|
40453
|
-
);
|
|
40454
|
-
for (let suffix = 2; ; suffix += 1) {
|
|
40455
|
-
const ending = `-${suffix}`;
|
|
40456
|
-
const candidate = `${baseName.slice(0, 80 - ending.length)}${ending}`;
|
|
40457
|
-
if (!activeNames.has(candidate)) {
|
|
40458
|
-
name = deviceNameSchema.parse(candidate);
|
|
40459
|
-
break;
|
|
40460
|
-
}
|
|
40461
|
-
}
|
|
40462
|
-
continue;
|
|
40463
|
-
}
|
|
40464
|
-
if (!response.ok) throw relayError(response.status, data);
|
|
40465
|
-
if (typeof data.device?.id !== "string" || typeof data.device.name !== "string" || typeof data.device_token !== "string") {
|
|
40466
|
-
throw new Error("The Glossa relay returned an invalid device enrollment response.");
|
|
40467
|
-
}
|
|
40468
|
-
return {
|
|
40469
|
-
relayOrigin: endpoints.relayOrigin,
|
|
40470
|
-
deviceId: data.device.id,
|
|
40471
|
-
deviceName: data.device.name,
|
|
40472
|
-
token: data.device_token
|
|
40473
|
-
};
|
|
40474
|
-
}
|
|
40475
|
-
throw new Error("Glossa could not enroll this computer.");
|
|
39909
|
+
if (!response.ok) throw relayError(response.status, data, PAIRING_REJECTED);
|
|
39910
|
+
return parseDevices(data.devices);
|
|
40476
39911
|
}
|
|
40477
39912
|
async function revokePairedDevice(endpoints, device, fetchRequest = fetch) {
|
|
40478
39913
|
const response = await fetchRequest(`${endpoints.relayOrigin}/device`, {
|
|
@@ -40485,103 +39920,17 @@ async function revokePairedDevice(endpoints, device, fetchRequest = fetch) {
|
|
|
40485
39920
|
}
|
|
40486
39921
|
throw new Error(`The Glossa relay returned HTTP ${response.status}.`);
|
|
40487
39922
|
}
|
|
40488
|
-
async function revokeDevice(endpoints,
|
|
39923
|
+
async function revokeDevice(endpoints, authorization, deviceId, fetchRequest = fetch) {
|
|
40489
39924
|
const response = await fetchRequest(`${endpoints.relayOrigin}/v1/devices/${encodeURIComponent(deviceId)}`, {
|
|
40490
39925
|
method: "DELETE",
|
|
40491
|
-
headers: {
|
|
40492
|
-
authorization: `${credentials.tokenType} ${credentials.accessToken}`
|
|
40493
|
-
}
|
|
39926
|
+
headers: { authorization }
|
|
40494
39927
|
});
|
|
40495
39928
|
if (!response.ok) {
|
|
40496
39929
|
const data = await response.json().catch(() => ({}));
|
|
40497
|
-
throw relayError(response.status, data);
|
|
39930
|
+
throw relayError(response.status, data, PAIRING_REJECTED);
|
|
40498
39931
|
}
|
|
40499
39932
|
}
|
|
40500
39933
|
|
|
40501
|
-
// src/status-display.ts
|
|
40502
|
-
function formatStatus(status) {
|
|
40503
|
-
const lines = [
|
|
40504
|
-
`Signed in as ${status.account}.`,
|
|
40505
|
-
`Relay connected: ${status.relay}`
|
|
40506
|
-
];
|
|
40507
|
-
if (status.activeWorkers === null) {
|
|
40508
|
-
lines.push("Active workspaces: unavailable");
|
|
40509
|
-
} else if (status.activeWorkers === 0) {
|
|
40510
|
-
lines.push("No active workspaces. Run glossa from the project folder you want to expose.");
|
|
40511
|
-
} else {
|
|
40512
|
-
lines.push(`Active workspaces: ${status.activeWorkers}`);
|
|
40513
|
-
}
|
|
40514
|
-
if (status.devices.length === 0) lines.push("No active devices.");
|
|
40515
|
-
else lines.push(...status.devices.map((device) => formatDeviceRow(device)));
|
|
40516
|
-
return lines;
|
|
40517
|
-
}
|
|
40518
|
-
|
|
40519
|
-
// src/status-service.ts
|
|
40520
|
-
function accountLabel(profile) {
|
|
40521
|
-
return profile.email ?? profile.name ?? profile.sub;
|
|
40522
|
-
}
|
|
40523
|
-
function activeWorkerCount(devices) {
|
|
40524
|
-
if (devices.some((device) => device.activeWorkers === null)) return null;
|
|
40525
|
-
return devices.reduce((sum, device) => sum + device.activeWorkers, 0);
|
|
40526
|
-
}
|
|
40527
|
-
var WorkspaceStatusService = class {
|
|
40528
|
-
constructor(credentials, endpoints, dependencies = {}) {
|
|
40529
|
-
this.endpoints = endpoints;
|
|
40530
|
-
this.dependencies = dependencies;
|
|
40531
|
-
this.#credentials = credentials;
|
|
40532
|
-
}
|
|
40533
|
-
endpoints;
|
|
40534
|
-
dependencies;
|
|
40535
|
-
#credentials;
|
|
40536
|
-
#inFlight;
|
|
40537
|
-
async refresh(signal) {
|
|
40538
|
-
if (this.#inFlight) return await this.#inFlight;
|
|
40539
|
-
const pending = this.#load(signal);
|
|
40540
|
-
this.#inFlight = pending;
|
|
40541
|
-
try {
|
|
40542
|
-
return await pending;
|
|
40543
|
-
} finally {
|
|
40544
|
-
if (this.#inFlight === pending) this.#inFlight = void 0;
|
|
40545
|
-
}
|
|
40546
|
-
}
|
|
40547
|
-
async #load(signal) {
|
|
40548
|
-
const validate2 = this.dependencies.validCredentials ?? validCredentials;
|
|
40549
|
-
const devicesForAccount = this.dependencies.listDevices ?? listDevices;
|
|
40550
|
-
const profileForAccount = this.dependencies.loadUserProfile ?? loadUserProfile;
|
|
40551
|
-
const baseFetch = this.dependencies.fetch ?? fetch;
|
|
40552
|
-
const fetchRequest = signal ? async (input, init) => await baseFetch(input, { ...init, signal }) : baseFetch;
|
|
40553
|
-
this.#credentials = await validate2(
|
|
40554
|
-
this.#credentials,
|
|
40555
|
-
signal ? { signal } : {}
|
|
40556
|
-
);
|
|
40557
|
-
const requestCredentials = this.#credentials;
|
|
40558
|
-
const profileRequest = profileForAccount(
|
|
40559
|
-
requestCredentials,
|
|
40560
|
-
signal ? { signal, fetch: fetchRequest } : { fetch: fetchRequest }
|
|
40561
|
-
).catch((error46) => {
|
|
40562
|
-
if (signal?.aborted) throw error46;
|
|
40563
|
-
return null;
|
|
40564
|
-
});
|
|
40565
|
-
const devicesRequest = devicesForAccount(
|
|
40566
|
-
this.endpoints,
|
|
40567
|
-
requestCredentials,
|
|
40568
|
-
fetchRequest
|
|
40569
|
-
);
|
|
40570
|
-
const [profile, devices] = await Promise.all([
|
|
40571
|
-
profileRequest,
|
|
40572
|
-
devicesRequest
|
|
40573
|
-
]);
|
|
40574
|
-
if (profile) this.#credentials = profile.credentials;
|
|
40575
|
-
const activeDevices = devices.filter((device) => device.revokedAt === null);
|
|
40576
|
-
return {
|
|
40577
|
-
account: profile ? accountLabel(profile.profile) : "Account unavailable",
|
|
40578
|
-
relay: this.endpoints.relayOrigin,
|
|
40579
|
-
activeWorkers: activeWorkerCount(activeDevices),
|
|
40580
|
-
devices: activeDevices
|
|
40581
|
-
};
|
|
40582
|
-
}
|
|
40583
|
-
};
|
|
40584
|
-
|
|
40585
39934
|
// src/ui-hud.tsx
|
|
40586
39935
|
var import_react34 = __toESM(require_react(), 1);
|
|
40587
39936
|
|
|
@@ -40735,7 +40084,7 @@ __export(base_exports, {
|
|
|
40735
40084
|
synchronizedOutput: () => synchronizedOutput
|
|
40736
40085
|
});
|
|
40737
40086
|
import process2 from "node:process";
|
|
40738
|
-
import
|
|
40087
|
+
import os2 from "node:os";
|
|
40739
40088
|
|
|
40740
40089
|
// ../../node_modules/environment/index.js
|
|
40741
40090
|
var isBrowser = globalThis.window?.document !== void 0;
|
|
@@ -40834,7 +40183,7 @@ var isOldWindows = () => {
|
|
|
40834
40183
|
if (isBrowser || !isWindows2) {
|
|
40835
40184
|
return false;
|
|
40836
40185
|
}
|
|
40837
|
-
const parts =
|
|
40186
|
+
const parts = os2.release().split(".");
|
|
40838
40187
|
const major = Number(parts[0]);
|
|
40839
40188
|
const build = Number(parts[2] ?? 0);
|
|
40840
40189
|
if (major < 10) {
|
|
@@ -45830,7 +45179,7 @@ var ansi_styles_default2 = ansiStyles2;
|
|
|
45830
45179
|
|
|
45831
45180
|
// ../../node_modules/chalk/source/vendor/supports-color/index.js
|
|
45832
45181
|
import process5 from "node:process";
|
|
45833
|
-
import
|
|
45182
|
+
import os3 from "node:os";
|
|
45834
45183
|
import tty2 from "node:tty";
|
|
45835
45184
|
function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process5.argv) {
|
|
45836
45185
|
const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
|
|
@@ -45895,7 +45244,7 @@ function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
|
|
|
45895
45244
|
return min;
|
|
45896
45245
|
}
|
|
45897
45246
|
if (process5.platform === "win32") {
|
|
45898
|
-
const osRelease =
|
|
45247
|
+
const osRelease = os3.release().split(".");
|
|
45899
45248
|
if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
|
|
45900
45249
|
return Number(osRelease[2]) >= 14931 ? 3 : 2;
|
|
45901
45250
|
}
|
|
@@ -47671,8 +47020,8 @@ function Text({ color, backgroundColor, dimColor = false, bold = false, italic =
|
|
|
47671
47020
|
}
|
|
47672
47021
|
|
|
47673
47022
|
// ../../node_modules/ink/build/components/ErrorOverview.js
|
|
47674
|
-
var cleanupPath = (
|
|
47675
|
-
return
|
|
47023
|
+
var cleanupPath = (path12) => {
|
|
47024
|
+
return path12?.replace(`file://${cwd()}/`, "");
|
|
47676
47025
|
};
|
|
47677
47026
|
var stackUtils = new import_stack_utils.default({
|
|
47678
47027
|
cwd: cwd(),
|
|
@@ -49774,14 +49123,136 @@ var import_react33 = __toESM(require_react(), 1);
|
|
|
49774
49123
|
|
|
49775
49124
|
// src/first-run.ts
|
|
49776
49125
|
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
|
|
49777
|
-
import
|
|
49126
|
+
import path2 from "node:path";
|
|
49127
|
+
|
|
49128
|
+
// src/secure-store.ts
|
|
49129
|
+
import { chmod, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
49130
|
+
import os4 from "node:os";
|
|
49131
|
+
import path from "node:path";
|
|
49132
|
+
var KEYRING_SERVICE = "Glossa";
|
|
49133
|
+
function configDirectory() {
|
|
49134
|
+
if (process.platform === "win32") {
|
|
49135
|
+
return path.join(process.env.APPDATA ?? os4.homedir(), "Glossa");
|
|
49136
|
+
}
|
|
49137
|
+
return path.join(
|
|
49138
|
+
process.env.XDG_CONFIG_HOME ?? path.join(os4.homedir(), ".config"),
|
|
49139
|
+
"glossa"
|
|
49140
|
+
);
|
|
49141
|
+
}
|
|
49142
|
+
var SecureStore = class {
|
|
49143
|
+
#options;
|
|
49144
|
+
#warned = false;
|
|
49145
|
+
constructor(options) {
|
|
49146
|
+
this.#options = options;
|
|
49147
|
+
}
|
|
49148
|
+
async save(value) {
|
|
49149
|
+
const serialized = JSON.stringify(value);
|
|
49150
|
+
const entry = await this.#entry();
|
|
49151
|
+
if (entry) {
|
|
49152
|
+
try {
|
|
49153
|
+
await this.#writeEntry(entry, serialized);
|
|
49154
|
+
await rm(this.#options.file, { force: true });
|
|
49155
|
+
return "keyring";
|
|
49156
|
+
} catch {
|
|
49157
|
+
}
|
|
49158
|
+
}
|
|
49159
|
+
this.#warn();
|
|
49160
|
+
await mkdir(path.dirname(this.#options.file), {
|
|
49161
|
+
recursive: true,
|
|
49162
|
+
mode: 448
|
|
49163
|
+
});
|
|
49164
|
+
await writeFile(
|
|
49165
|
+
this.#options.file,
|
|
49166
|
+
`${JSON.stringify(value, null, 2)}
|
|
49167
|
+
`,
|
|
49168
|
+
{ encoding: "utf8", mode: 384 }
|
|
49169
|
+
);
|
|
49170
|
+
if (process.platform !== "win32") {
|
|
49171
|
+
await chmod(this.#options.file, 384);
|
|
49172
|
+
}
|
|
49173
|
+
return "file";
|
|
49174
|
+
}
|
|
49175
|
+
async load() {
|
|
49176
|
+
const entry = await this.#entry();
|
|
49177
|
+
if (entry) {
|
|
49178
|
+
const value2 = await this.#readEntry(entry);
|
|
49179
|
+
if (value2 != null) return { value: value2, backend: "keyring" };
|
|
49180
|
+
}
|
|
49181
|
+
let value;
|
|
49182
|
+
try {
|
|
49183
|
+
value = this.#options.parse(await readFile(this.#options.file, "utf8"));
|
|
49184
|
+
} catch (error46) {
|
|
49185
|
+
if (error46.code === "ENOENT") return null;
|
|
49186
|
+
throw error46;
|
|
49187
|
+
}
|
|
49188
|
+
this.#warn();
|
|
49189
|
+
return { value, backend: "file" };
|
|
49190
|
+
}
|
|
49191
|
+
async delete() {
|
|
49192
|
+
const entry = await this.#entry();
|
|
49193
|
+
let keyringDeleteFailed = false;
|
|
49194
|
+
if (entry) {
|
|
49195
|
+
try {
|
|
49196
|
+
const deleted = await entry.deleteCredential();
|
|
49197
|
+
if (!deleted && await entry.getSecret() != null) {
|
|
49198
|
+
keyringDeleteFailed = true;
|
|
49199
|
+
}
|
|
49200
|
+
} catch {
|
|
49201
|
+
keyringDeleteFailed = true;
|
|
49202
|
+
}
|
|
49203
|
+
}
|
|
49204
|
+
await rm(this.#options.file, { force: true });
|
|
49205
|
+
if (keyringDeleteFailed) {
|
|
49206
|
+
throw new Error(
|
|
49207
|
+
"The operating-system credential store could not remove the Glossa credential."
|
|
49208
|
+
);
|
|
49209
|
+
}
|
|
49210
|
+
}
|
|
49211
|
+
async #entry() {
|
|
49212
|
+
if (this.#options.entryProvider) {
|
|
49213
|
+
try {
|
|
49214
|
+
return await this.#options.entryProvider();
|
|
49215
|
+
} catch {
|
|
49216
|
+
return null;
|
|
49217
|
+
}
|
|
49218
|
+
}
|
|
49219
|
+
try {
|
|
49220
|
+
const { AsyncEntry } = await import("@napi-rs/keyring");
|
|
49221
|
+
return new AsyncEntry(KEYRING_SERVICE, this.#options.account);
|
|
49222
|
+
} catch {
|
|
49223
|
+
return null;
|
|
49224
|
+
}
|
|
49225
|
+
}
|
|
49226
|
+
async #readEntry(entry) {
|
|
49227
|
+
try {
|
|
49228
|
+
const secret = await entry.getSecret();
|
|
49229
|
+
if (secret == null) return null;
|
|
49230
|
+
const bytes = secret instanceof Uint8Array ? secret : Uint8Array.from(secret);
|
|
49231
|
+
return this.#options.parse(
|
|
49232
|
+
new TextDecoder("utf-8", { fatal: true }).decode(bytes)
|
|
49233
|
+
);
|
|
49234
|
+
} catch {
|
|
49235
|
+
return null;
|
|
49236
|
+
}
|
|
49237
|
+
}
|
|
49238
|
+
async #writeEntry(entry, serialized) {
|
|
49239
|
+
await entry.setSecret(new TextEncoder().encode(serialized));
|
|
49240
|
+
}
|
|
49241
|
+
#warn() {
|
|
49242
|
+
if (this.#warned) return;
|
|
49243
|
+
this.#warned = true;
|
|
49244
|
+
(this.#options.warn ?? console.warn)(this.#options.warning);
|
|
49245
|
+
}
|
|
49246
|
+
};
|
|
49247
|
+
|
|
49248
|
+
// src/first-run.ts
|
|
49778
49249
|
var CONNECT_HINT_FILE = "connect-hint-shown";
|
|
49779
49250
|
var CONNECT_HINT_URL = "https://glossa.sh/docs/quickstart";
|
|
49780
49251
|
function shouldShowConnectHint(relayOrigin) {
|
|
49781
49252
|
return relayOrigin === DEFAULT_RELAY_ORIGIN;
|
|
49782
49253
|
}
|
|
49783
49254
|
function connectHintStore(directory = configDirectory()) {
|
|
49784
|
-
const file2 =
|
|
49255
|
+
const file2 = path2.join(directory, CONNECT_HINT_FILE);
|
|
49785
49256
|
return {
|
|
49786
49257
|
async exists() {
|
|
49787
49258
|
try {
|
|
@@ -49798,15 +49269,15 @@ function connectHintStore(directory = configDirectory()) {
|
|
|
49798
49269
|
}
|
|
49799
49270
|
};
|
|
49800
49271
|
}
|
|
49801
|
-
async function announceConnectHint(
|
|
49802
|
-
if (await
|
|
49272
|
+
async function announceConnectHint(store2, log) {
|
|
49273
|
+
if (await store2.exists()) return false;
|
|
49803
49274
|
log(`Next: return to ChatGPT and use Glossa. If it is not connected yet, follow ${CONNECT_HINT_URL}.`);
|
|
49804
|
-
await
|
|
49275
|
+
await store2.mark();
|
|
49805
49276
|
return true;
|
|
49806
49277
|
}
|
|
49807
49278
|
|
|
49808
49279
|
// src/device-store.ts
|
|
49809
|
-
import
|
|
49280
|
+
import path3 from "node:path";
|
|
49810
49281
|
var FILE_DEVICE_WARNING = "Warning: the operating-system credential store is unavailable. Glossa is using a mode-0600 device credential file.";
|
|
49811
49282
|
function parseDeviceCredential(value) {
|
|
49812
49283
|
let parsed;
|
|
@@ -49830,36 +49301,36 @@ function parseDeviceCredential(value) {
|
|
|
49830
49301
|
}
|
|
49831
49302
|
return parsed;
|
|
49832
49303
|
}
|
|
49833
|
-
var
|
|
49304
|
+
var store = new SecureStore({
|
|
49834
49305
|
account: "device",
|
|
49835
|
-
file:
|
|
49306
|
+
file: path3.join(configDirectory(), "device.json"),
|
|
49836
49307
|
warning: FILE_DEVICE_WARNING,
|
|
49837
49308
|
parse: parseDeviceCredential
|
|
49838
49309
|
});
|
|
49839
49310
|
async function loadDeviceCredential() {
|
|
49840
|
-
return (await
|
|
49311
|
+
return (await store.load())?.value ?? null;
|
|
49841
49312
|
}
|
|
49842
49313
|
async function saveDeviceCredential(credential) {
|
|
49843
|
-
await
|
|
49314
|
+
await store.save(credential);
|
|
49844
49315
|
}
|
|
49845
49316
|
async function deleteDeviceCredential() {
|
|
49846
|
-
await
|
|
49317
|
+
await store.delete();
|
|
49847
49318
|
}
|
|
49848
49319
|
|
|
49849
49320
|
// src/device-pairing-lock.ts
|
|
49850
49321
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
49851
49322
|
import { mkdir as mkdir4, readFile as readFile3, rm as rm3, stat, writeFile as writeFile4 } from "node:fs/promises";
|
|
49852
|
-
import
|
|
49853
|
-
import { setTimeout as
|
|
49323
|
+
import path5 from "node:path";
|
|
49324
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
49854
49325
|
|
|
49855
49326
|
// src/update-lock.ts
|
|
49856
49327
|
import { randomUUID } from "node:crypto";
|
|
49857
49328
|
import { mkdir as mkdir3, readdir, rm as rm2, writeFile as writeFile3 } from "node:fs/promises";
|
|
49858
|
-
import
|
|
49329
|
+
import path4 from "node:path";
|
|
49859
49330
|
var UPDATE_SUFFIX = ".update";
|
|
49860
49331
|
var SESSION_SUFFIX = ".session";
|
|
49861
49332
|
function updateRuntimeDirectory() {
|
|
49862
|
-
return
|
|
49333
|
+
return path4.join(configDirectory(), "runtime");
|
|
49863
49334
|
}
|
|
49864
49335
|
function processIsAlive(pid) {
|
|
49865
49336
|
if (!Number.isSafeInteger(pid) || pid <= 0) return false;
|
|
@@ -49889,7 +49360,7 @@ async function activeLeaseFiles(directory, suffix) {
|
|
|
49889
49360
|
for (const entry of entries) {
|
|
49890
49361
|
if (!entry.isFile() || !entry.name.endsWith(suffix)) continue;
|
|
49891
49362
|
const pid = leasePid(entry.name, suffix);
|
|
49892
|
-
const file2 =
|
|
49363
|
+
const file2 = path4.join(directory, entry.name);
|
|
49893
49364
|
if (pid !== null && processIsAlive(pid)) active.push(file2);
|
|
49894
49365
|
else await rm2(file2, { force: true });
|
|
49895
49366
|
}
|
|
@@ -49903,7 +49374,7 @@ async function activeSessionFiles(directory) {
|
|
|
49903
49374
|
}
|
|
49904
49375
|
async function createUpdateLock(directory) {
|
|
49905
49376
|
await mkdir3(directory, { recursive: true, mode: 448 });
|
|
49906
|
-
const lockFile =
|
|
49377
|
+
const lockFile = path4.join(
|
|
49907
49378
|
directory,
|
|
49908
49379
|
`${process.pid}-${randomUUID()}${UPDATE_SUFFIX}`
|
|
49909
49380
|
);
|
|
@@ -49941,7 +49412,7 @@ async function withWorkspaceLease(action, directory = updateRuntimeDirectory())
|
|
|
49941
49412
|
if (await updateLockIsActive(directory)) {
|
|
49942
49413
|
throw new Error("Glossa is updating. Run this workspace again after the update finishes.");
|
|
49943
49414
|
}
|
|
49944
|
-
const leaseFile =
|
|
49415
|
+
const leaseFile = path4.join(
|
|
49945
49416
|
directory,
|
|
49946
49417
|
`${process.pid}-${randomUUID()}${SESSION_SUFFIX}`
|
|
49947
49418
|
);
|
|
@@ -49968,10 +49439,10 @@ var DEVICE_PAIRING_LOCK_POLL_MS = 100;
|
|
|
49968
49439
|
var DEVICE_PAIRING_LOCK_MAX_AGE_MS = 6 * 6e4;
|
|
49969
49440
|
var DEVICE_PAIRING_GUARD_MAX_AGE_MS = 3e4;
|
|
49970
49441
|
function lockPath(directory) {
|
|
49971
|
-
return
|
|
49442
|
+
return path5.join(directory, DEVICE_PAIRING_LOCK);
|
|
49972
49443
|
}
|
|
49973
49444
|
function guardPath(directory) {
|
|
49974
|
-
return
|
|
49445
|
+
return path5.join(directory, DEVICE_PAIRING_LOCK_GUARD);
|
|
49975
49446
|
}
|
|
49976
49447
|
function newOwner() {
|
|
49977
49448
|
return {
|
|
@@ -50026,7 +49497,7 @@ async function acquireGuard(directory, signal) {
|
|
|
50026
49497
|
await rm3(file2, { force: true });
|
|
50027
49498
|
continue;
|
|
50028
49499
|
}
|
|
50029
|
-
await
|
|
49500
|
+
await delay(
|
|
50030
49501
|
DEVICE_PAIRING_LOCK_POLL_MS,
|
|
50031
49502
|
void 0,
|
|
50032
49503
|
signal ? { signal } : void 0
|
|
@@ -50074,7 +49545,7 @@ async function acquireDevicePairingLock(directory, signal) {
|
|
|
50074
49545
|
signal
|
|
50075
49546
|
);
|
|
50076
49547
|
if (reclaimed) continue;
|
|
50077
|
-
await
|
|
49548
|
+
await delay(
|
|
50078
49549
|
DEVICE_PAIRING_LOCK_POLL_MS,
|
|
50079
49550
|
void 0,
|
|
50080
49551
|
signal ? { signal } : void 0
|
|
@@ -50091,46 +49562,69 @@ async function withDevicePairingLease(action, signal, directory = updateRuntimeD
|
|
|
50091
49562
|
}
|
|
50092
49563
|
|
|
50093
49564
|
// src/device-pairing.ts
|
|
50094
|
-
|
|
49565
|
+
import { setTimeout as delay2 } from "node:timers/promises";
|
|
49566
|
+
var MAX_PAIRING_CODES = 3;
|
|
49567
|
+
var POLL_INTERVAL_MS = 2e3;
|
|
49568
|
+
function canceledError() {
|
|
50095
49569
|
return new Error("Pairing canceled.");
|
|
50096
49570
|
}
|
|
50097
|
-
function
|
|
50098
|
-
|
|
49571
|
+
async function defaultDelay(milliseconds, signal) {
|
|
49572
|
+
await delay2(milliseconds, void 0, signal ? { signal } : void 0);
|
|
50099
49573
|
}
|
|
50100
49574
|
async function pairDevice(endpoints, signal, dependencies = {}) {
|
|
50101
|
-
const
|
|
50102
|
-
const
|
|
50103
|
-
const authConfig = dependencies.loadAuthConfig ?? loadAuthConfig;
|
|
49575
|
+
const create3 = dependencies.createPairing ?? createPairing;
|
|
49576
|
+
const redeem = dependencies.redeemPairing ?? redeemPairing;
|
|
50104
49577
|
const name = dependencies.defaultDeviceName ?? defaultDeviceName;
|
|
49578
|
+
const wait = dependencies.delay ?? defaultDelay;
|
|
49579
|
+
const now = dependencies.now ?? Date.now;
|
|
50105
49580
|
const log = dependencies.log ?? console.log;
|
|
50106
49581
|
const baseFetch = dependencies.fetch ?? fetch;
|
|
50107
49582
|
const fetchRequest = signal ? async (input, init) => await baseFetch(input, { ...init, signal }) : baseFetch;
|
|
50108
49583
|
try {
|
|
50109
49584
|
signal?.throwIfAborted();
|
|
50110
49585
|
log("This computer needs to be paired with Glossa.");
|
|
50111
|
-
const
|
|
50112
|
-
const
|
|
50113
|
-
|
|
50114
|
-
|
|
50115
|
-
|
|
50116
|
-
|
|
50117
|
-
}
|
|
50118
|
-
|
|
50119
|
-
|
|
50120
|
-
|
|
50121
|
-
|
|
50122
|
-
|
|
50123
|
-
|
|
49586
|
+
const deviceName = name();
|
|
49587
|
+
const platform2 = `${process.platform}-${process.arch}`;
|
|
49588
|
+
for (let attempt = 0; attempt < MAX_PAIRING_CODES; attempt += 1) {
|
|
49589
|
+
signal?.throwIfAborted();
|
|
49590
|
+
const pairing = await create3(endpoints, deviceName, platform2, fetchRequest);
|
|
49591
|
+
log("");
|
|
49592
|
+
log(`Pairing code: ${pairing.code} (valid for 10 minutes)`);
|
|
49593
|
+
log("");
|
|
49594
|
+
log(`Enter it at ${endpoints.relayOrigin}/panel`);
|
|
49595
|
+
const expiresAt = Date.parse(pairing.expiresAt);
|
|
49596
|
+
while (now() < expiresAt) {
|
|
49597
|
+
signal?.throwIfAborted();
|
|
49598
|
+
await wait(POLL_INTERVAL_MS, signal);
|
|
49599
|
+
signal?.throwIfAborted();
|
|
49600
|
+
try {
|
|
49601
|
+
const redeemed = await redeem(endpoints, pairing.code, fetchRequest);
|
|
49602
|
+
if (redeemed === "pending") continue;
|
|
49603
|
+
log(`Paired with Glossa as ${redeemed.device.name}.`);
|
|
49604
|
+
return {
|
|
49605
|
+
relayOrigin: endpoints.relayOrigin,
|
|
49606
|
+
deviceId: redeemed.device.id,
|
|
49607
|
+
deviceName: redeemed.device.name,
|
|
49608
|
+
token: redeemed.token
|
|
49609
|
+
};
|
|
49610
|
+
} catch (error46) {
|
|
49611
|
+
if (!(error46 instanceof PairingCodeExpiredError)) throw error46;
|
|
49612
|
+
log(error46.message);
|
|
49613
|
+
break;
|
|
49614
|
+
}
|
|
49615
|
+
}
|
|
49616
|
+
}
|
|
49617
|
+
throw new Error("Pairing timed out. Run Glossa again to retry.");
|
|
50124
49618
|
} catch (error46) {
|
|
50125
49619
|
if (signal?.aborted || error46 instanceof Error && error46.name === "AbortError") {
|
|
50126
|
-
throw
|
|
49620
|
+
throw canceledError();
|
|
50127
49621
|
}
|
|
50128
49622
|
throw error46;
|
|
50129
49623
|
}
|
|
50130
49624
|
}
|
|
50131
49625
|
|
|
50132
49626
|
// src/worker/command-service.ts
|
|
50133
|
-
import { spawn
|
|
49627
|
+
import { spawn } from "node:child_process";
|
|
50134
49628
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
50135
49629
|
import { StringDecoder } from "node:string_decoder";
|
|
50136
49630
|
import { setTimeout as delay3 } from "node:timers/promises";
|
|
@@ -50405,7 +49899,7 @@ async function terminateProcessTree(child) {
|
|
|
50405
49899
|
if (!child.pid || child.exitCode !== null) return;
|
|
50406
49900
|
if (process.platform === "win32") {
|
|
50407
49901
|
await new Promise((resolve) => {
|
|
50408
|
-
const killer =
|
|
49902
|
+
const killer = spawn("taskkill", ["/pid", String(child.pid), "/t", "/f"], {
|
|
50409
49903
|
stdio: "ignore",
|
|
50410
49904
|
windowsHide: true
|
|
50411
49905
|
});
|
|
@@ -50495,7 +49989,7 @@ var CommandService = class {
|
|
|
50495
49989
|
const invocation = options.argv ? { file: options.argv[0], args: options.argv.slice(1) } : shellInvocation(options.shellCommand);
|
|
50496
49990
|
let child;
|
|
50497
49991
|
try {
|
|
50498
|
-
child =
|
|
49992
|
+
child = spawn(invocation.file, invocation.args, {
|
|
50499
49993
|
cwd: cwd2,
|
|
50500
49994
|
env: process.env,
|
|
50501
49995
|
detached: process.platform !== "win32",
|
|
@@ -50727,14 +50221,14 @@ import {
|
|
|
50727
50221
|
stat as stat3,
|
|
50728
50222
|
writeFile as writeFile5
|
|
50729
50223
|
} from "node:fs/promises";
|
|
50730
|
-
import
|
|
50224
|
+
import path7 from "node:path";
|
|
50731
50225
|
import { performance as performance2 } from "node:perf_hooks";
|
|
50732
50226
|
import { StringDecoder as StringDecoder2 } from "node:string_decoder";
|
|
50733
50227
|
|
|
50734
50228
|
// src/worker/path-policy.ts
|
|
50735
50229
|
import { lstat, realpath, stat as stat2 } from "node:fs/promises";
|
|
50736
50230
|
import os5 from "node:os";
|
|
50737
|
-
import
|
|
50231
|
+
import path6 from "node:path";
|
|
50738
50232
|
function samePath(left, right) {
|
|
50739
50233
|
return process.platform === "win32" ? left.toLowerCase() === right.toLowerCase() : left === right;
|
|
50740
50234
|
}
|
|
@@ -50746,15 +50240,15 @@ function accountHomeDirectory() {
|
|
|
50746
50240
|
}
|
|
50747
50241
|
}
|
|
50748
50242
|
function isWithin(root, candidate) {
|
|
50749
|
-
const relative =
|
|
50750
|
-
return relative === "" || !relative.startsWith(`..${
|
|
50243
|
+
const relative = path6.relative(root, candidate);
|
|
50244
|
+
return relative === "" || !relative.startsWith(`..${path6.sep}`) && relative !== ".." && !path6.isAbsolute(relative);
|
|
50751
50245
|
}
|
|
50752
50246
|
function validateRelativePath(value) {
|
|
50753
50247
|
if (value.includes("\0")) {
|
|
50754
50248
|
throw new WorkerError("invalid_path", "Paths cannot contain null bytes.");
|
|
50755
50249
|
}
|
|
50756
50250
|
const explicitNativePosixPath = process.platform !== "win32" && value.startsWith("./");
|
|
50757
|
-
if (
|
|
50251
|
+
if (path6.isAbsolute(value) || path6.posix.isAbsolute(value) || !explicitNativePosixPath && path6.win32.isAbsolute(value)) {
|
|
50758
50252
|
throw new WorkerError("absolute_path", "Absolute paths are not allowed.");
|
|
50759
50253
|
}
|
|
50760
50254
|
const segments = explicitNativePosixPath ? value.split(/\/+/).filter(Boolean) : value.split(/[\\/]+/);
|
|
@@ -50764,7 +50258,7 @@ function validateRelativePath(value) {
|
|
|
50764
50258
|
return value === "" ? "." : value;
|
|
50765
50259
|
}
|
|
50766
50260
|
async function canonicalizeRoot(candidate) {
|
|
50767
|
-
const root = await realpath(
|
|
50261
|
+
const root = await realpath(path6.resolve(candidate)).catch((error46) => {
|
|
50768
50262
|
if (error46.code === "ENOENT") {
|
|
50769
50263
|
throw new WorkerError("root_not_found", "The workspace directory does not exist.");
|
|
50770
50264
|
}
|
|
@@ -50774,10 +50268,10 @@ async function canonicalizeRoot(candidate) {
|
|
|
50774
50268
|
if (!rootStat.isDirectory()) {
|
|
50775
50269
|
throw new WorkerError("root_not_directory", "The exposed root must be a directory.");
|
|
50776
50270
|
}
|
|
50777
|
-
const filesystemRoot =
|
|
50271
|
+
const filesystemRoot = path6.parse(root).root;
|
|
50778
50272
|
const homes = await Promise.all(
|
|
50779
50273
|
[os5.homedir(), accountHomeDirectory()].map(
|
|
50780
|
-
async (home) => await realpath(home).catch(() =>
|
|
50274
|
+
async (home) => await realpath(home).catch(() => path6.resolve(home))
|
|
50781
50275
|
)
|
|
50782
50276
|
);
|
|
50783
50277
|
const isHomeDirectory = homes.some((home) => samePath(root, home));
|
|
@@ -50813,7 +50307,7 @@ var PathPolicy = class _PathPolicy {
|
|
|
50813
50307
|
return canonical;
|
|
50814
50308
|
}
|
|
50815
50309
|
async resolveDiscoveredExisting(candidate) {
|
|
50816
|
-
const lexical =
|
|
50310
|
+
const lexical = path6.resolve(candidate);
|
|
50817
50311
|
if (!isWithin(this.root, lexical)) {
|
|
50818
50312
|
throw new WorkerError("path_escape", "The requested path escapes the exposed root.");
|
|
50819
50313
|
}
|
|
@@ -50831,7 +50325,7 @@ var PathPolicy = class _PathPolicy {
|
|
|
50831
50325
|
}
|
|
50832
50326
|
async resolveWritableFile(relativePath) {
|
|
50833
50327
|
const lexical = this.resolveLexical(relativePath);
|
|
50834
|
-
const parent =
|
|
50328
|
+
const parent = path6.dirname(lexical);
|
|
50835
50329
|
await this.rejectLinkedComponents(parent);
|
|
50836
50330
|
const canonicalParent = await realpath(parent).catch((error46) => {
|
|
50837
50331
|
if (error46.code === "ENOENT") {
|
|
@@ -50862,7 +50356,7 @@ var PathPolicy = class _PathPolicy {
|
|
|
50862
50356
|
if (error46 instanceof WorkerError) throw error46;
|
|
50863
50357
|
if (error46.code !== "ENOENT") throw error46;
|
|
50864
50358
|
}
|
|
50865
|
-
return
|
|
50359
|
+
return path6.join(canonicalParent, path6.basename(lexical));
|
|
50866
50360
|
}
|
|
50867
50361
|
async resolveWritableDirectory(relativePath, recursive) {
|
|
50868
50362
|
const lexical = this.resolveLexical(relativePath);
|
|
@@ -50885,7 +50379,7 @@ var PathPolicy = class _PathPolicy {
|
|
|
50885
50379
|
if (error46.code !== "ENOENT") throw error46;
|
|
50886
50380
|
}
|
|
50887
50381
|
if (!recursive) {
|
|
50888
|
-
const parent =
|
|
50382
|
+
const parent = path6.dirname(lexical);
|
|
50889
50383
|
await this.rejectLinkedComponents(parent);
|
|
50890
50384
|
const canonicalParent = await realpath(parent).catch(
|
|
50891
50385
|
(error46) => {
|
|
@@ -50905,11 +50399,11 @@ var PathPolicy = class _PathPolicy {
|
|
|
50905
50399
|
throw new WorkerError("not_directory", "The destination parent is not a directory.");
|
|
50906
50400
|
}
|
|
50907
50401
|
return {
|
|
50908
|
-
target:
|
|
50402
|
+
target: path6.join(canonicalParent, path6.basename(lexical)),
|
|
50909
50403
|
exists: false
|
|
50910
50404
|
};
|
|
50911
50405
|
}
|
|
50912
|
-
let existingAncestor =
|
|
50406
|
+
let existingAncestor = path6.dirname(lexical);
|
|
50913
50407
|
while (!samePath(existingAncestor, this.root)) {
|
|
50914
50408
|
try {
|
|
50915
50409
|
await this.rejectLinkedComponents(existingAncestor);
|
|
@@ -50921,9 +50415,9 @@ var PathPolicy = class _PathPolicy {
|
|
|
50921
50415
|
throw new WorkerError("not_directory", "The destination parent is not a directory.");
|
|
50922
50416
|
}
|
|
50923
50417
|
return {
|
|
50924
|
-
target:
|
|
50418
|
+
target: path6.join(
|
|
50925
50419
|
canonicalAncestor,
|
|
50926
|
-
|
|
50420
|
+
path6.relative(existingAncestor, lexical)
|
|
50927
50421
|
),
|
|
50928
50422
|
exists: false
|
|
50929
50423
|
};
|
|
@@ -50931,13 +50425,13 @@ var PathPolicy = class _PathPolicy {
|
|
|
50931
50425
|
if (error46 instanceof WorkerError) throw error46;
|
|
50932
50426
|
if (error46.code !== "ENOENT") throw error46;
|
|
50933
50427
|
}
|
|
50934
|
-
existingAncestor =
|
|
50428
|
+
existingAncestor = path6.dirname(existingAncestor);
|
|
50935
50429
|
}
|
|
50936
50430
|
return { target: lexical, exists: false };
|
|
50937
50431
|
}
|
|
50938
50432
|
async resolveVacantPath(relativePath) {
|
|
50939
50433
|
const lexical = this.resolveLexical(relativePath);
|
|
50940
|
-
const parent =
|
|
50434
|
+
const parent = path6.dirname(lexical);
|
|
50941
50435
|
await this.rejectLinkedComponents(parent);
|
|
50942
50436
|
const canonicalParent = await realpath(parent).catch(
|
|
50943
50437
|
(error46) => {
|
|
@@ -50966,11 +50460,11 @@ var PathPolicy = class _PathPolicy {
|
|
|
50966
50460
|
if (error46 instanceof WorkerError) throw error46;
|
|
50967
50461
|
if (error46.code !== "ENOENT") throw error46;
|
|
50968
50462
|
}
|
|
50969
|
-
return
|
|
50463
|
+
return path6.join(canonicalParent, path6.basename(lexical));
|
|
50970
50464
|
}
|
|
50971
50465
|
resolveLexical(relativePath) {
|
|
50972
50466
|
const validated = validateRelativePath(relativePath);
|
|
50973
|
-
const candidate =
|
|
50467
|
+
const candidate = path6.resolve(this.root, validated);
|
|
50974
50468
|
if (!isWithin(this.root, candidate)) {
|
|
50975
50469
|
throw new WorkerError("path_escape", "The requested path escapes the exposed root.");
|
|
50976
50470
|
}
|
|
@@ -50980,11 +50474,11 @@ var PathPolicy = class _PathPolicy {
|
|
|
50980
50474
|
if (!isWithin(this.root, candidate)) {
|
|
50981
50475
|
throw new WorkerError("path_escape", "The requested path escapes the exposed root.");
|
|
50982
50476
|
}
|
|
50983
|
-
const relative =
|
|
50477
|
+
const relative = path6.relative(this.root, candidate);
|
|
50984
50478
|
if (!relative) return;
|
|
50985
50479
|
let current = this.root;
|
|
50986
|
-
for (const segment of relative.split(
|
|
50987
|
-
current =
|
|
50480
|
+
for (const segment of relative.split(path6.sep)) {
|
|
50481
|
+
current = path6.join(current, segment);
|
|
50988
50482
|
try {
|
|
50989
50483
|
const currentStat = await lstat(current);
|
|
50990
50484
|
if (currentStat.isSymbolicLink()) {
|
|
@@ -51006,13 +50500,25 @@ var PathPolicy = class _PathPolicy {
|
|
|
51006
50500
|
function sha256(content) {
|
|
51007
50501
|
return createHash("sha256").update(content).digest("hex");
|
|
51008
50502
|
}
|
|
50503
|
+
function imageMimeType(content) {
|
|
50504
|
+
if (content.byteLength >= 8 && content[0] === 137 && content[1] === 80 && content[2] === 78 && content[3] === 71 && content[4] === 13 && content[5] === 10 && content[6] === 26 && content[7] === 10) {
|
|
50505
|
+
return "image/png";
|
|
50506
|
+
}
|
|
50507
|
+
if (content.byteLength >= 3 && content[0] === 255 && content[1] === 216 && content[2] === 255) {
|
|
50508
|
+
return "image/jpeg";
|
|
50509
|
+
}
|
|
50510
|
+
if (content.byteLength >= 12 && content[0] === 82 && content[1] === 73 && content[2] === 70 && content[3] === 70 && content[8] === 87 && content[9] === 69 && content[10] === 66 && content[11] === 80) {
|
|
50511
|
+
return "image/webp";
|
|
50512
|
+
}
|
|
50513
|
+
return void 0;
|
|
50514
|
+
}
|
|
51009
50515
|
function isPathWithin(root, candidate) {
|
|
51010
|
-
const relative =
|
|
51011
|
-
return relative === "" || !relative.startsWith(`..${
|
|
50516
|
+
const relative = path7.relative(root, candidate);
|
|
50517
|
+
return relative === "" || !relative.startsWith(`..${path7.sep}`) && relative !== ".." && !path7.isAbsolute(relative);
|
|
51012
50518
|
}
|
|
51013
50519
|
var fileWriteTails = /* @__PURE__ */ new Map();
|
|
51014
50520
|
async function withFileWriteLock(target, operation) {
|
|
51015
|
-
const normalized =
|
|
50521
|
+
const normalized = path7.normalize(target);
|
|
51016
50522
|
const key = process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
51017
50523
|
const predecessor = fileWriteTails.get(key);
|
|
51018
50524
|
let release;
|
|
@@ -51029,7 +50535,7 @@ async function withFileWriteLock(target, operation) {
|
|
|
51029
50535
|
}
|
|
51030
50536
|
}
|
|
51031
50537
|
async function withFileWriteLocks(targets, operation) {
|
|
51032
|
-
const unique = [...new Set(targets.map((target) =>
|
|
50538
|
+
const unique = [...new Set(targets.map((target) => path7.normalize(target)))].sort(
|
|
51033
50539
|
(left, right) => left.localeCompare(right)
|
|
51034
50540
|
);
|
|
51035
50541
|
const run = async (index) => {
|
|
@@ -51065,7 +50571,7 @@ function compareNames(left, right) {
|
|
|
51065
50571
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
51066
50572
|
}
|
|
51067
50573
|
function relativeSortKey(root, target) {
|
|
51068
|
-
const relative =
|
|
50574
|
+
const relative = path7.relative(root, target);
|
|
51069
50575
|
if (!relative) return ".";
|
|
51070
50576
|
return process.platform === "win32" ? relative.replaceAll("\\", "/") : relative;
|
|
51071
50577
|
}
|
|
@@ -51413,6 +50919,43 @@ var FileService = class {
|
|
|
51413
50919
|
const target = await this.policy.resolveExisting(relativePath);
|
|
51414
50920
|
return await this.#readResolvedText(target);
|
|
51415
50921
|
}
|
|
50922
|
+
async readImage(relativePath) {
|
|
50923
|
+
const target = await this.policy.resolveExisting(relativePath);
|
|
50924
|
+
const targetStat = await stat3(target);
|
|
50925
|
+
if (!targetStat.isFile()) {
|
|
50926
|
+
throw new WorkerError("not_file", "The requested path is not a file.");
|
|
50927
|
+
}
|
|
50928
|
+
if (targetStat.size > MAX_IMAGE_BYTES) {
|
|
50929
|
+
throw new WorkerError(
|
|
50930
|
+
"image_too_large",
|
|
50931
|
+
"The image exceeds the 4 MiB image limit."
|
|
50932
|
+
);
|
|
50933
|
+
}
|
|
50934
|
+
const content = await this.#readFileBytes(
|
|
50935
|
+
target,
|
|
50936
|
+
MAX_IMAGE_BYTES,
|
|
50937
|
+
targetStat.size
|
|
50938
|
+
);
|
|
50939
|
+
if (content.byteLength > MAX_IMAGE_BYTES) {
|
|
50940
|
+
throw new WorkerError(
|
|
50941
|
+
"image_too_large",
|
|
50942
|
+
"The image exceeds the 4 MiB image limit."
|
|
50943
|
+
);
|
|
50944
|
+
}
|
|
50945
|
+
const mimeType = imageMimeType(content);
|
|
50946
|
+
if (!mimeType) {
|
|
50947
|
+
throw new WorkerError(
|
|
50948
|
+
"unsupported_image",
|
|
50949
|
+
"Only PNG, JPEG, and WebP images are supported."
|
|
50950
|
+
);
|
|
50951
|
+
}
|
|
50952
|
+
return {
|
|
50953
|
+
data: content.toString("base64"),
|
|
50954
|
+
mimeType,
|
|
50955
|
+
sha256: sha256(content),
|
|
50956
|
+
bytes: content.byteLength
|
|
50957
|
+
};
|
|
50958
|
+
}
|
|
51416
50959
|
async listFiles(options = {}) {
|
|
51417
50960
|
const deadlineAt = this.#scanDeadline(options.timeoutMs);
|
|
51418
50961
|
const startPath = options.path ?? ".";
|
|
@@ -51449,7 +50992,7 @@ var FileService = class {
|
|
|
51449
50992
|
if (batch.overflow) throw scanLimitError();
|
|
51450
50993
|
scannedEntries += batch.children.length;
|
|
51451
50994
|
for (const child of batch.children) {
|
|
51452
|
-
const target =
|
|
50995
|
+
const target = path7.join(directory, child.name);
|
|
51453
50996
|
heapPush(frontier, {
|
|
51454
50997
|
target,
|
|
51455
50998
|
sortKey: relativeSortKey(this.policy.root, target),
|
|
@@ -51473,7 +51016,7 @@ var FileService = class {
|
|
|
51473
51016
|
throw scanLimitError();
|
|
51474
51017
|
}
|
|
51475
51018
|
scannedEntries += 1;
|
|
51476
|
-
const target =
|
|
51019
|
+
const target = path7.join(directory, child.name);
|
|
51477
51020
|
let childStat;
|
|
51478
51021
|
try {
|
|
51479
51022
|
childStat = await this.#withinDeadline(
|
|
@@ -51625,7 +51168,7 @@ var FileService = class {
|
|
|
51625
51168
|
if (!patterns) return false;
|
|
51626
51169
|
const normalized = relativePath.replaceAll("\\", "/");
|
|
51627
51170
|
try {
|
|
51628
|
-
return patterns.some((pattern) =>
|
|
51171
|
+
return patterns.some((pattern) => path7.posix.matchesGlob(normalized, pattern));
|
|
51629
51172
|
} catch {
|
|
51630
51173
|
throw new WorkerError("invalid_search", "Search glob pattern is invalid.");
|
|
51631
51174
|
}
|
|
@@ -51654,7 +51197,7 @@ var FileService = class {
|
|
|
51654
51197
|
const searchFile = async (target) => {
|
|
51655
51198
|
const relative = displayPath(this.policy.root, target);
|
|
51656
51199
|
if (extensions && !extensions.some(
|
|
51657
|
-
(extension) =>
|
|
51200
|
+
(extension) => path7.basename(target).toLowerCase().endsWith(extension)
|
|
51658
51201
|
)) {
|
|
51659
51202
|
return false;
|
|
51660
51203
|
}
|
|
@@ -51749,7 +51292,7 @@ var FileService = class {
|
|
|
51749
51292
|
scannedEntries += batch.children.length;
|
|
51750
51293
|
for (const child of batch.children) {
|
|
51751
51294
|
this.#assertBeforeDeadline(deadlineAt);
|
|
51752
|
-
const target =
|
|
51295
|
+
const target = path7.join(directory, child.name);
|
|
51753
51296
|
let targetType;
|
|
51754
51297
|
if (this.#trustDirectoryEntryTypes) {
|
|
51755
51298
|
if (child.isSymbolicLink()) {
|
|
@@ -52059,7 +51602,7 @@ var FileService = class {
|
|
|
52059
51602
|
);
|
|
52060
51603
|
}
|
|
52061
51604
|
if (expectedSha256) await requireRevision(target, expectedSha256);
|
|
52062
|
-
const temporary =
|
|
51605
|
+
const temporary = path7.join(path7.dirname(target), `.glossa-${randomUUID4()}.tmp`);
|
|
52063
51606
|
try {
|
|
52064
51607
|
await writeFile5(temporary, bytes, { flag: "wx", mode: 384 });
|
|
52065
51608
|
target = await this.policy.resolveWritableFile(relativePath);
|
|
@@ -52104,6 +51647,7 @@ function restrictedDataError2() {
|
|
|
52104
51647
|
function jobInputContainsRestrictedData(job) {
|
|
52105
51648
|
switch (job.type) {
|
|
52106
51649
|
case "read_file":
|
|
51650
|
+
case "view_image":
|
|
52107
51651
|
return containsRestrictedAuthenticationData(job.path);
|
|
52108
51652
|
case "list_files":
|
|
52109
51653
|
return containsRestrictedAuthenticationData({
|
|
@@ -52194,6 +51738,9 @@ var LocalWorker = class _LocalWorker {
|
|
|
52194
51738
|
case "read_file":
|
|
52195
51739
|
value = await this.files.readText(job.path);
|
|
52196
51740
|
break;
|
|
51741
|
+
case "view_image":
|
|
51742
|
+
value = await this.files.readImage(job.path);
|
|
51743
|
+
break;
|
|
52197
51744
|
case "list_files":
|
|
52198
51745
|
value = await this.files.listFiles({
|
|
52199
51746
|
...job.path ? { path: job.path } : {},
|
|
@@ -52307,18 +51854,29 @@ var DEFAULT_HEARTBEAT_MS = 15e3;
|
|
|
52307
51854
|
var CAPACITY_REFRESH_POLL_MS = 1;
|
|
52308
51855
|
var MAX_CONCURRENT_JOBS = 5;
|
|
52309
51856
|
var WORKER_TOKEN_PATTERN = /^glw_[A-Za-z0-9_-]{43}$/;
|
|
51857
|
+
var LEGACY_WORKER_CAPABILITIES = {
|
|
51858
|
+
commandProgress: true,
|
|
51859
|
+
concurrentJobs: true,
|
|
51860
|
+
structuredReads: true,
|
|
51861
|
+
structuredMutations: true,
|
|
51862
|
+
commandOutputRanges: true
|
|
51863
|
+
};
|
|
51864
|
+
var WORKER_CAPABILITIES = {
|
|
51865
|
+
...LEGACY_WORKER_CAPABILITIES,
|
|
51866
|
+
imageReads: true
|
|
51867
|
+
};
|
|
52310
51868
|
var DeviceRejectedError = class extends Error {
|
|
52311
51869
|
constructor() {
|
|
52312
51870
|
super("The relay rejected the device credential.");
|
|
52313
51871
|
this.name = "DeviceRejectedError";
|
|
52314
51872
|
}
|
|
52315
51873
|
};
|
|
52316
|
-
var
|
|
52317
|
-
constructor(
|
|
51874
|
+
var RelayProtocolUnsupportedError = class extends Error {
|
|
51875
|
+
constructor() {
|
|
52318
51876
|
super(
|
|
52319
|
-
|
|
51877
|
+
"The relay does not support this Glossa worker protocol. Update the relay before reconnecting this workspace."
|
|
52320
51878
|
);
|
|
52321
|
-
this.name = "
|
|
51879
|
+
this.name = "RelayProtocolUnsupportedError";
|
|
52322
51880
|
}
|
|
52323
51881
|
};
|
|
52324
51882
|
var RelayResponseError = class extends Error {
|
|
@@ -52329,19 +51887,24 @@ var RelayResponseError = class extends Error {
|
|
|
52329
51887
|
}
|
|
52330
51888
|
status;
|
|
52331
51889
|
};
|
|
52332
|
-
function
|
|
52333
|
-
if (value === void 0) return void 0;
|
|
51890
|
+
function requiredWorkerToken(value) {
|
|
52334
51891
|
if (typeof value !== "string" || !WORKER_TOKEN_PATTERN.test(value)) {
|
|
52335
51892
|
throw new Error("The relay returned an invalid worker credential.");
|
|
52336
51893
|
}
|
|
52337
51894
|
return value;
|
|
52338
51895
|
}
|
|
52339
|
-
function
|
|
51896
|
+
function acceptsCapabilities(value, requireImageReads) {
|
|
52340
51897
|
if (typeof value !== "object" || value === null) return false;
|
|
52341
51898
|
if (!("capabilities" in value)) return false;
|
|
52342
51899
|
const capabilities = value.capabilities;
|
|
52343
51900
|
if (typeof capabilities !== "object" || capabilities === null) return false;
|
|
52344
|
-
|
|
51901
|
+
const required2 = Object.keys(LEGACY_WORKER_CAPABILITIES);
|
|
51902
|
+
if (!required2.every(
|
|
51903
|
+
(capability) => capabilities[capability] === true
|
|
51904
|
+
)) {
|
|
51905
|
+
return false;
|
|
51906
|
+
}
|
|
51907
|
+
return !requireImageReads || capabilities.imageReads === true;
|
|
52345
51908
|
}
|
|
52346
51909
|
function jobLane(job) {
|
|
52347
51910
|
switch (job.type) {
|
|
@@ -52351,6 +51914,7 @@ function jobLane(job) {
|
|
|
52351
51914
|
case "cancel_command":
|
|
52352
51915
|
return "cancel";
|
|
52353
51916
|
case "read_file":
|
|
51917
|
+
case "view_image":
|
|
52354
51918
|
case "list_files":
|
|
52355
51919
|
case "search_text":
|
|
52356
51920
|
case "read_file_range":
|
|
@@ -52364,25 +51928,27 @@ function jobLane(job) {
|
|
|
52364
51928
|
return "mutation";
|
|
52365
51929
|
}
|
|
52366
51930
|
}
|
|
52367
|
-
function acceptedJobTypes(counts, total,
|
|
51931
|
+
function acceptedJobTypes(counts, total, imageReads) {
|
|
52368
51932
|
if (total >= MAX_CONCURRENT_JOBS) return [];
|
|
52369
51933
|
const accepted = [];
|
|
52370
51934
|
if (counts.status < 1) {
|
|
52371
|
-
accepted.push("get_command");
|
|
52372
|
-
if (commandOutputRanges) accepted.push("read_command_output");
|
|
51935
|
+
accepted.push("get_command", "read_command_output");
|
|
52373
51936
|
}
|
|
52374
51937
|
if (counts.cancel < 1) accepted.push("cancel_command");
|
|
52375
51938
|
if (counts.read < 2) {
|
|
52376
51939
|
accepted.push("read_file");
|
|
52377
|
-
if (
|
|
52378
|
-
|
|
52379
|
-
}
|
|
51940
|
+
if (imageReads) accepted.push("view_image");
|
|
51941
|
+
accepted.push("list_files", "search_text", "read_file_range");
|
|
52380
51942
|
}
|
|
52381
51943
|
if (counts.mutation < 1) {
|
|
52382
|
-
accepted.push(
|
|
52383
|
-
|
|
52384
|
-
|
|
52385
|
-
|
|
51944
|
+
accepted.push(
|
|
51945
|
+
"write_file",
|
|
51946
|
+
"edit_file",
|
|
51947
|
+
"make_directory",
|
|
51948
|
+
"delete_path",
|
|
51949
|
+
"move_path",
|
|
51950
|
+
"run_command"
|
|
51951
|
+
);
|
|
52386
51952
|
}
|
|
52387
51953
|
return accepted;
|
|
52388
51954
|
}
|
|
@@ -52424,7 +51990,7 @@ var RemoteWorker = class {
|
|
|
52424
51990
|
constructor(options) {
|
|
52425
51991
|
this.#origin = new URL(options.origin);
|
|
52426
51992
|
this.#deviceToken = options.deviceToken;
|
|
52427
|
-
this.#accessProfile = options.accessProfile;
|
|
51993
|
+
this.#accessProfile = options.accessProfile ?? "workspace";
|
|
52428
51994
|
this.#workspaceLabel = options.workspaceLabel;
|
|
52429
51995
|
this.#workerVersion = options.workerVersion;
|
|
52430
51996
|
this.#worker = options.worker;
|
|
@@ -52450,17 +52016,14 @@ var RemoteWorker = class {
|
|
|
52450
52016
|
registeredSession = session;
|
|
52451
52017
|
this.#onStatus({
|
|
52452
52018
|
state: "connected",
|
|
52453
|
-
reconnected: connectedBefore
|
|
52454
|
-
legacyRelay: session.legacyRelay,
|
|
52455
|
-
accessProfileAccepted: session.accessProfileAccepted,
|
|
52456
|
-
workspaceLabelAccepted: session.workspaceLabelAccepted
|
|
52019
|
+
reconnected: connectedBefore
|
|
52457
52020
|
});
|
|
52458
52021
|
connectedBefore = true;
|
|
52459
52022
|
failures = 0;
|
|
52460
52023
|
await this.#pollGeneration(session);
|
|
52461
52024
|
} catch (error46) {
|
|
52462
52025
|
if (this.#signal.aborted) return;
|
|
52463
|
-
if (error46 instanceof DeviceRejectedError || error46 instanceof
|
|
52026
|
+
if (error46 instanceof DeviceRejectedError || error46 instanceof RelayProtocolUnsupportedError) {
|
|
52464
52027
|
throw error46;
|
|
52465
52028
|
}
|
|
52466
52029
|
const delay5 = reconnectDelayMs(
|
|
@@ -52489,153 +52052,50 @@ var RemoteWorker = class {
|
|
|
52489
52052
|
}
|
|
52490
52053
|
}
|
|
52491
52054
|
async #register() {
|
|
52492
|
-
const
|
|
52055
|
+
const registrationBody = (capabilities) => ({
|
|
52493
52056
|
workerId: this.#workerId,
|
|
52494
|
-
|
|
52495
|
-
|
|
52496
|
-
|
|
52497
|
-
|
|
52498
|
-
|
|
52499
|
-
|
|
52500
|
-
|
|
52501
|
-
|
|
52502
|
-
|
|
52503
|
-
|
|
52504
|
-
|
|
52505
|
-
|
|
52506
|
-
|
|
52507
|
-
|
|
52508
|
-
|
|
52509
|
-
}
|
|
52510
|
-
};
|
|
52511
|
-
const structuredBody = {
|
|
52512
|
-
workerId: this.#workerId,
|
|
52513
|
-
capabilities: {
|
|
52514
|
-
commandProgress: true,
|
|
52515
|
-
concurrentJobs: true,
|
|
52516
|
-
structuredReads: true
|
|
52517
|
-
}
|
|
52518
|
-
};
|
|
52519
|
-
const concurrentBody = {
|
|
52520
|
-
workerId: this.#workerId,
|
|
52521
|
-
capabilities: { commandProgress: true, concurrentJobs: true }
|
|
52522
|
-
};
|
|
52523
|
-
const capabilityBodies = [
|
|
52524
|
-
currentBody,
|
|
52525
|
-
mutationBody,
|
|
52526
|
-
structuredBody,
|
|
52527
|
-
concurrentBody,
|
|
52528
|
-
{
|
|
52529
|
-
workerId: this.#workerId,
|
|
52530
|
-
capabilities: { commandProgress: true }
|
|
52531
|
-
},
|
|
52532
|
-
{ workerId: this.#workerId }
|
|
52533
|
-
];
|
|
52534
|
-
const metadataPhases = [
|
|
52535
|
-
{
|
|
52536
|
-
includeVersion: Boolean(this.#workerVersion),
|
|
52537
|
-
includeLabel: Boolean(this.#workspaceLabel)
|
|
52538
|
-
},
|
|
52539
|
-
...this.#workspaceLabel ? [{
|
|
52540
|
-
includeVersion: Boolean(this.#workerVersion),
|
|
52541
|
-
includeLabel: false
|
|
52542
|
-
}] : [],
|
|
52543
|
-
...this.#workerVersion && this.#workspaceLabel ? [{ includeVersion: false, includeLabel: true }] : [],
|
|
52544
|
-
{ includeVersion: false, includeLabel: false }
|
|
52545
|
-
];
|
|
52546
|
-
const attempts = [];
|
|
52547
|
-
const seenAttempts = /* @__PURE__ */ new Set();
|
|
52548
|
-
const pushAttempt = (base, includeProfile, includeVersion, includeLabel, legacyRelay = false) => {
|
|
52549
|
-
const body = {
|
|
52550
|
-
...base,
|
|
52551
|
-
...includeProfile && this.#accessProfile ? { accessProfile: this.#accessProfile } : {},
|
|
52552
|
-
...includeVersion && this.#workerVersion ? { workerVersion: this.#workerVersion } : {},
|
|
52553
|
-
...includeLabel && this.#workspaceLabel ? { workspaceLabel: this.#workspaceLabel } : {}
|
|
52554
|
-
};
|
|
52555
|
-
const key = JSON.stringify(body);
|
|
52556
|
-
if (seenAttempts.has(key)) return;
|
|
52557
|
-
seenAttempts.add(key);
|
|
52558
|
-
attempts.push({ body, legacyRelay });
|
|
52559
|
-
};
|
|
52560
|
-
if (this.#accessProfile) {
|
|
52561
|
-
for (const metadata of metadataPhases) {
|
|
52562
|
-
for (const body of capabilityBodies) {
|
|
52563
|
-
pushAttempt(
|
|
52564
|
-
body,
|
|
52565
|
-
true,
|
|
52566
|
-
metadata.includeVersion,
|
|
52567
|
-
metadata.includeLabel
|
|
52568
|
-
);
|
|
52569
|
-
}
|
|
52570
|
-
}
|
|
52571
|
-
}
|
|
52572
|
-
if (this.#accessProfile === void 0 || this.#accessProfile === "system") {
|
|
52573
|
-
for (const metadata of metadataPhases) {
|
|
52574
|
-
for (const body of capabilityBodies) {
|
|
52575
|
-
pushAttempt(
|
|
52576
|
-
body,
|
|
52577
|
-
false,
|
|
52578
|
-
metadata.includeVersion,
|
|
52579
|
-
metadata.includeLabel
|
|
52580
|
-
);
|
|
52581
|
-
}
|
|
52057
|
+
accessProfile: this.#accessProfile,
|
|
52058
|
+
...this.#workerVersion ? { workerVersion: this.#workerVersion } : {},
|
|
52059
|
+
...this.#workspaceLabel ? { workspaceLabel: this.#workspaceLabel } : {},
|
|
52060
|
+
capabilities
|
|
52061
|
+
});
|
|
52062
|
+
let response;
|
|
52063
|
+
let imageReads = true;
|
|
52064
|
+
try {
|
|
52065
|
+
response = await this.#post(
|
|
52066
|
+
"/device/register",
|
|
52067
|
+
registrationBody(WORKER_CAPABILITIES)
|
|
52068
|
+
);
|
|
52069
|
+
} catch (error46) {
|
|
52070
|
+
if (!(error46 instanceof RelayResponseError) || error46.status !== 400) {
|
|
52071
|
+
throw error46;
|
|
52582
52072
|
}
|
|
52583
|
-
|
|
52584
|
-
}
|
|
52585
|
-
for (const attempt of attempts) {
|
|
52586
|
-
let response;
|
|
52073
|
+
imageReads = false;
|
|
52587
52074
|
try {
|
|
52588
|
-
response = await this.#post(
|
|
52589
|
-
|
|
52590
|
-
|
|
52591
|
-
|
|
52075
|
+
response = await this.#post(
|
|
52076
|
+
"/device/register",
|
|
52077
|
+
registrationBody(LEGACY_WORKER_CAPABILITIES)
|
|
52078
|
+
);
|
|
52079
|
+
} catch (legacyError) {
|
|
52080
|
+
if (legacyError instanceof RelayResponseError && legacyError.status === 400) {
|
|
52081
|
+
throw new RelayProtocolUnsupportedError();
|
|
52592
52082
|
}
|
|
52593
|
-
throw
|
|
52594
|
-
}
|
|
52595
|
-
const value = await response.json();
|
|
52596
|
-
if (typeof value !== "object" || value === null || !("generation" in value) || typeof value.generation !== "string") {
|
|
52597
|
-
throw new Error("The relay returned an invalid registration response.");
|
|
52598
|
-
}
|
|
52599
|
-
if (!attempt.legacyRelay && (!("workerId" in value) || value.workerId !== this.#workerId)) {
|
|
52600
|
-
throw new Error("The relay returned an invalid registration response.");
|
|
52601
|
-
}
|
|
52602
|
-
const workerToken = "workerToken" in value ? optionalWorkerToken(value.workerToken) : void 0;
|
|
52603
|
-
const accessProfileAccepted = this.#accessProfile === void 0 || this.#accessProfile === "system" || "accessProfile" in value && value.accessProfile === this.#accessProfile;
|
|
52604
|
-
const session = {
|
|
52605
|
-
generation: value.generation,
|
|
52606
|
-
legacyRelay: attempt.legacyRelay,
|
|
52607
|
-
concurrentJobs: !attempt.legacyRelay && supportsCapability(value, "concurrentJobs"),
|
|
52608
|
-
structuredReads: !attempt.legacyRelay && supportsCapability(value, "structuredReads"),
|
|
52609
|
-
structuredMutations: !attempt.legacyRelay && supportsCapability(value, "structuredMutations"),
|
|
52610
|
-
commandOutputRanges: !attempt.legacyRelay && supportsCapability(value, "commandOutputRanges"),
|
|
52611
|
-
accessProfileAccepted,
|
|
52612
|
-
workspaceLabelAccepted: this.#workspaceLabel === void 0 || "workspaceLabel" in value && value.workspaceLabel === this.#workspaceLabel,
|
|
52613
|
-
...workerToken ? { workerToken } : {}
|
|
52614
|
-
};
|
|
52615
|
-
if (!accessProfileAccepted && this.#accessProfile) {
|
|
52616
|
-
throw new RelayAccessProfileUnsupportedError(this.#accessProfile);
|
|
52083
|
+
throw legacyError;
|
|
52617
52084
|
}
|
|
52618
|
-
return session;
|
|
52619
52085
|
}
|
|
52620
|
-
|
|
52621
|
-
|
|
52086
|
+
const value = await response.json();
|
|
52087
|
+
if (typeof value !== "object" || value === null || !("generation" in value) || typeof value.generation !== "string" || !("workerId" in value) || value.workerId !== this.#workerId || !("workerToken" in value) || !("accessProfile" in value) || value.accessProfile !== this.#accessProfile || !acceptsCapabilities(value, imageReads) || this.#workspaceLabel !== void 0 && (!("workspaceLabel" in value) || value.workspaceLabel !== this.#workspaceLabel)) {
|
|
52088
|
+
throw new RelayProtocolUnsupportedError();
|
|
52622
52089
|
}
|
|
52623
|
-
|
|
52090
|
+
return {
|
|
52091
|
+
generation: value.generation,
|
|
52092
|
+
workerToken: requiredWorkerToken(value.workerToken),
|
|
52093
|
+
imageReads
|
|
52094
|
+
};
|
|
52624
52095
|
}
|
|
52625
52096
|
async #pollGeneration(session) {
|
|
52626
|
-
if (session.legacyRelay || !session.concurrentJobs) {
|
|
52627
|
-
await this.#pollSequentially(session);
|
|
52628
|
-
return;
|
|
52629
|
-
}
|
|
52630
52097
|
await this.#pollConcurrently(session);
|
|
52631
52098
|
}
|
|
52632
|
-
async #pollSequentially(session) {
|
|
52633
|
-
while (!this.#signal.aborted) {
|
|
52634
|
-
const job = await this.#pollForJob(session);
|
|
52635
|
-
if (!job) continue;
|
|
52636
|
-
await this.#handleAndPost(session, job, true);
|
|
52637
|
-
}
|
|
52638
|
-
}
|
|
52639
52099
|
async #pollConcurrently(session) {
|
|
52640
52100
|
const counts = {
|
|
52641
52101
|
status: 0,
|
|
@@ -52693,7 +52153,7 @@ var RemoteWorker = class {
|
|
|
52693
52153
|
counts[lane] += 1;
|
|
52694
52154
|
ensureHeartbeat();
|
|
52695
52155
|
let task;
|
|
52696
|
-
task = this.#handleAndPost(session, job
|
|
52156
|
+
task = this.#handleAndPost(session, job).catch((error46) => {
|
|
52697
52157
|
failure ??= error46;
|
|
52698
52158
|
}).finally(() => {
|
|
52699
52159
|
counts[lane] -= 1;
|
|
@@ -52709,9 +52169,7 @@ var RemoteWorker = class {
|
|
|
52709
52169
|
const acceptedTypes = acceptedJobTypes(
|
|
52710
52170
|
counts,
|
|
52711
52171
|
inFlight.size,
|
|
52712
|
-
session.
|
|
52713
|
-
session.structuredMutations,
|
|
52714
|
-
session.commandOutputRanges
|
|
52172
|
+
session.imageReads
|
|
52715
52173
|
);
|
|
52716
52174
|
if (acceptedTypes.length === 0) {
|
|
52717
52175
|
await Promise.race(inFlight);
|
|
@@ -52741,9 +52199,7 @@ var RemoteWorker = class {
|
|
|
52741
52199
|
const refreshedTypes = acceptedJobTypes(
|
|
52742
52200
|
counts,
|
|
52743
52201
|
inFlight.size,
|
|
52744
|
-
session.
|
|
52745
|
-
session.structuredMutations,
|
|
52746
|
-
session.commandOutputRanges
|
|
52202
|
+
session.imageReads
|
|
52747
52203
|
);
|
|
52748
52204
|
const newlyAcceptedTypes = refreshedTypes.filter(
|
|
52749
52205
|
(type) => !acceptedTypes.includes(type)
|
|
@@ -52783,7 +52239,7 @@ var RemoteWorker = class {
|
|
|
52783
52239
|
async #pollForJob(session, acceptedTypes, waitMs) {
|
|
52784
52240
|
const response = await this.#post(
|
|
52785
52241
|
"/device/poll",
|
|
52786
|
-
|
|
52242
|
+
{
|
|
52787
52243
|
workerId: this.#workerId,
|
|
52788
52244
|
generation: session.generation,
|
|
52789
52245
|
...acceptedTypes ? { acceptedTypes } : {},
|
|
@@ -52801,26 +52257,12 @@ var RemoteWorker = class {
|
|
|
52801
52257
|
}
|
|
52802
52258
|
return parsed.data;
|
|
52803
52259
|
}
|
|
52804
|
-
async #handleAndPost(session, job
|
|
52805
|
-
const
|
|
52806
|
-
void this.#post(
|
|
52807
|
-
"/device/heartbeat",
|
|
52808
|
-
{ workerId: this.#workerId, generation: session.generation },
|
|
52809
|
-
session.workerToken
|
|
52810
|
-
).catch(() => {
|
|
52811
|
-
});
|
|
52812
|
-
}, this.#heartbeatMs) : void 0;
|
|
52813
|
-
heartbeat?.unref();
|
|
52814
|
-
let result;
|
|
52815
|
-
try {
|
|
52816
|
-
result = await this.#worker.handle(job);
|
|
52817
|
-
} finally {
|
|
52818
|
-
if (heartbeat) clearInterval(heartbeat);
|
|
52819
|
-
}
|
|
52260
|
+
async #handleAndPost(session, job) {
|
|
52261
|
+
const result = await this.#worker.handle(job);
|
|
52820
52262
|
try {
|
|
52821
52263
|
await this.#post(
|
|
52822
52264
|
"/device/result",
|
|
52823
|
-
|
|
52265
|
+
{ workerId: this.#workerId, result },
|
|
52824
52266
|
session.workerToken
|
|
52825
52267
|
);
|
|
52826
52268
|
} catch (error46) {
|
|
@@ -52829,39 +52271,24 @@ var RemoteWorker = class {
|
|
|
52829
52271
|
}
|
|
52830
52272
|
}
|
|
52831
52273
|
async #unregister(session) {
|
|
52832
|
-
|
|
52833
|
-
|
|
52834
|
-
|
|
52835
|
-
|
|
52836
|
-
|
|
52837
|
-
|
|
52838
|
-
|
|
52839
|
-
|
|
52840
|
-
|
|
52841
|
-
|
|
52842
|
-
|
|
52843
|
-
|
|
52844
|
-
}
|
|
52845
|
-
);
|
|
52846
|
-
if (response.ok) return "accepted";
|
|
52847
|
-
return [400, 401, 404, 409].includes(response.status) ? "rejected" : "unreachable";
|
|
52848
|
-
} catch {
|
|
52849
|
-
return "unreachable";
|
|
52850
|
-
}
|
|
52851
|
-
};
|
|
52852
|
-
if (!session?.workerToken) {
|
|
52853
|
-
await unregister(`Device ${this.#deviceToken}`);
|
|
52854
|
-
return;
|
|
52855
|
-
}
|
|
52856
|
-
const result = await unregister(`Worker ${session.workerToken}`);
|
|
52857
|
-
if (result === "rejected") {
|
|
52858
|
-
await unregister(`Device ${this.#deviceToken}`);
|
|
52274
|
+
if (!session) return;
|
|
52275
|
+
try {
|
|
52276
|
+
await this.#fetcher(new URL("/device/unregister", this.#origin), {
|
|
52277
|
+
method: "POST",
|
|
52278
|
+
headers: {
|
|
52279
|
+
authorization: `Worker ${session.workerToken}`,
|
|
52280
|
+
"content-type": "application/json"
|
|
52281
|
+
},
|
|
52282
|
+
body: JSON.stringify({ workerId: this.#workerId }),
|
|
52283
|
+
signal: AbortSignal.timeout(3e3)
|
|
52284
|
+
});
|
|
52285
|
+
} catch {
|
|
52859
52286
|
}
|
|
52860
52287
|
}
|
|
52861
|
-
async #post(
|
|
52288
|
+
async #post(path12, body, workerToken) {
|
|
52862
52289
|
const timeout = AbortSignal.timeout(WORKER_REQUEST_TIMEOUT_MS);
|
|
52863
52290
|
const signal = AbortSignal.any([this.#signal, timeout]);
|
|
52864
|
-
const response = await this.#fetcher(new URL(
|
|
52291
|
+
const response = await this.#fetcher(new URL(path12, this.#origin), {
|
|
52865
52292
|
method: "POST",
|
|
52866
52293
|
headers: {
|
|
52867
52294
|
authorization: workerToken ? `Worker ${workerToken}` : `Device ${this.#deviceToken}`,
|
|
@@ -52892,6 +52319,7 @@ function activitySafeJob(job) {
|
|
|
52892
52319
|
if (!containsRestrictedAuthenticationData(job)) return job;
|
|
52893
52320
|
switch (job.type) {
|
|
52894
52321
|
case "read_file":
|
|
52322
|
+
case "view_image":
|
|
52895
52323
|
return { ...job, path: "[restricted input blocked]" };
|
|
52896
52324
|
case "list_files":
|
|
52897
52325
|
return {
|
|
@@ -53018,27 +52446,6 @@ function accessProfileSummary(accessProfile) {
|
|
|
53018
52446
|
return "System access: clients can modify files and run commands with this account's full environment, permissions, credentials, and network access.";
|
|
53019
52447
|
}
|
|
53020
52448
|
}
|
|
53021
|
-
function accessProfileNotice(status, requestedProfile) {
|
|
53022
|
-
if (status.state !== "connected" || !requestedProfile || status.accessProfileAccepted !== false) {
|
|
53023
|
-
return void 0;
|
|
53024
|
-
}
|
|
53025
|
-
return "The relay needs an update before this access profile can be shown to connected clients. Local profile enforcement remains active.";
|
|
53026
|
-
}
|
|
53027
|
-
function workspaceLabelNotice(status, requestedLabel) {
|
|
53028
|
-
if (status.state !== "connected" || !requestedLabel || status.workspaceLabelAccepted !== false) {
|
|
53029
|
-
return void 0;
|
|
53030
|
-
}
|
|
53031
|
-
return "The relay needs an update before workspace labels are available. This workspace is online without the requested label.";
|
|
53032
|
-
}
|
|
53033
|
-
var legacyRelayNotice = "The relay needs an update before this computer can expose several workspaces at once.";
|
|
53034
|
-
function combinedCompatibilityNotice(labelNotice, profileNotice, includeLegacyRelayNotice) {
|
|
53035
|
-
const messages = [
|
|
53036
|
-
labelNotice,
|
|
53037
|
-
profileNotice,
|
|
53038
|
-
includeLegacyRelayNotice ? legacyRelayNotice : void 0
|
|
53039
|
-
].filter((message) => Boolean(message));
|
|
53040
|
-
return messages.length > 0 ? messages.join(" ") : void 0;
|
|
53041
|
-
}
|
|
53042
52449
|
function statusMessage(status, connectedBefore) {
|
|
53043
52450
|
if (status.state === "connecting") return "Connecting to Glossa...";
|
|
53044
52451
|
if (status.state === "connected") {
|
|
@@ -53053,15 +52460,12 @@ function statusMessage(status, connectedBefore) {
|
|
|
53053
52460
|
async function connectRemoteWorker(endpoints, device, worker, options, signal, onConnected) {
|
|
53054
52461
|
let connectionState;
|
|
53055
52462
|
let connectedBefore = false;
|
|
53056
|
-
let labelNoticeShown = false;
|
|
53057
|
-
let profileNoticeShown = false;
|
|
53058
|
-
let legacyNoticeShown = false;
|
|
53059
52463
|
let connectHintTask;
|
|
53060
52464
|
const remoteWorker = new RemoteWorker({
|
|
53061
52465
|
origin: endpoints.workerOrigin,
|
|
53062
52466
|
deviceToken: device.token,
|
|
53063
52467
|
...options.workerVersion ? { workerVersion: options.workerVersion } : {},
|
|
53064
|
-
|
|
52468
|
+
accessProfile: options.accessProfile ?? DEFAULT_WORKER_ACCESS_PROFILE,
|
|
53065
52469
|
...options.workspaceLabel ? { workspaceLabel: options.workspaceLabel } : {},
|
|
53066
52470
|
worker: visibleWorker(worker, options),
|
|
53067
52471
|
signal,
|
|
@@ -53075,25 +52479,7 @@ async function connectRemoteWorker(endpoints, device, worker, options, signal, o
|
|
|
53075
52479
|
} else {
|
|
53076
52480
|
options.onEvent?.({ type: "status", status });
|
|
53077
52481
|
}
|
|
53078
|
-
|
|
53079
|
-
const profileNotice = profileNoticeShown ? void 0 : accessProfileNotice(status, options.accessProfile);
|
|
53080
|
-
const includeLegacyNotice = status.state === "connected" && status.legacyRelay && !legacyNoticeShown;
|
|
53081
|
-
const compatibilityNotice = combinedCompatibilityNotice(
|
|
53082
|
-
labelNotice,
|
|
53083
|
-
profileNotice,
|
|
53084
|
-
includeLegacyNotice
|
|
53085
|
-
);
|
|
53086
|
-
if (labelNotice) labelNoticeShown = true;
|
|
53087
|
-
if (profileNotice) profileNoticeShown = true;
|
|
53088
|
-
if (includeLegacyNotice) legacyNoticeShown = true;
|
|
53089
|
-
if (compatibilityNotice) {
|
|
53090
|
-
report(
|
|
53091
|
-
options,
|
|
53092
|
-
{ type: "notice", message: compatibilityNotice },
|
|
53093
|
-
compatibilityNotice
|
|
53094
|
-
);
|
|
53095
|
-
}
|
|
53096
|
-
if (status.state === "connected" && !status.reconnected && !compatibilityNotice && shouldShowConnectHint(endpoints.relayOrigin) && !connectHintTask) {
|
|
52482
|
+
if (status.state === "connected" && !status.reconnected && shouldShowConnectHint(endpoints.relayOrigin) && !connectHintTask) {
|
|
53097
52483
|
connectHintTask = announceConnectHint(
|
|
53098
52484
|
connectHintStore(),
|
|
53099
52485
|
(message) => {
|
|
@@ -53197,7 +52583,7 @@ function initialHudState(workspace) {
|
|
|
53197
52583
|
notice: void 0
|
|
53198
52584
|
};
|
|
53199
52585
|
}
|
|
53200
|
-
var MAX_STORED_ACTIVITIES =
|
|
52586
|
+
var MAX_STORED_ACTIVITIES = 9999;
|
|
53201
52587
|
var MAX_STORED_ACTIVITY_TARGET_CHARS = 512;
|
|
53202
52588
|
function truncate(value, width) {
|
|
53203
52589
|
if (width <= 0) return "";
|
|
@@ -53258,12 +52644,12 @@ function formatByteCount(value) {
|
|
|
53258
52644
|
}
|
|
53259
52645
|
return `${(kibibytes / 1024).toFixed(1)} MiB`;
|
|
53260
52646
|
}
|
|
53261
|
-
function workspacePath(
|
|
53262
|
-
return
|
|
52647
|
+
function workspacePath(path12) {
|
|
52648
|
+
return path12 || ".";
|
|
53263
52649
|
}
|
|
53264
|
-
function pathSummary(
|
|
52650
|
+
function pathSummary(path12, details = []) {
|
|
53265
52651
|
return {
|
|
53266
|
-
target: `path ${quoteActivityInput(
|
|
52652
|
+
target: `path ${quoteActivityInput(path12)}`,
|
|
53267
52653
|
details,
|
|
53268
52654
|
truncation: "middle"
|
|
53269
52655
|
};
|
|
@@ -53274,6 +52660,7 @@ function assertNever2(_value) {
|
|
|
53274
52660
|
function summarizeJob(job) {
|
|
53275
52661
|
switch (job.type) {
|
|
53276
52662
|
case "read_file":
|
|
52663
|
+
case "view_image":
|
|
53277
52664
|
return pathSummary(job.path);
|
|
53278
52665
|
case "list_files":
|
|
53279
52666
|
return pathSummary(workspacePath(job.path), [
|
|
@@ -53555,7 +52942,6 @@ function primaryFooterHints() {
|
|
|
53555
52942
|
{ key: "W", label: "Workspace" },
|
|
53556
52943
|
{ key: "D", label: "Devices" },
|
|
53557
52944
|
{ key: "?", label: "Help" },
|
|
53558
|
-
{ key: "L", label: "Account sign out", tone: COLORS.coral },
|
|
53559
52945
|
{ key: "Q", label: "Quit", tone: COLORS.coral }
|
|
53560
52946
|
];
|
|
53561
52947
|
}
|
|
@@ -53626,12 +53012,6 @@ function buildFooterRows(state, usable) {
|
|
|
53626
53012
|
function promptText(state) {
|
|
53627
53013
|
if (state.busy) return { message: "Working\u2026" };
|
|
53628
53014
|
if (!state.prompt) return void 0;
|
|
53629
|
-
if (state.prompt.type === "logout") {
|
|
53630
|
-
return {
|
|
53631
|
-
message: "Sign out the CLI account and disconnect? Computer pairing stays active.",
|
|
53632
|
-
choices: "Y confirm N cancel"
|
|
53633
|
-
};
|
|
53634
|
-
}
|
|
53635
53015
|
if (state.prompt.type === "access-confirm") {
|
|
53636
53016
|
const detail = state.prompt.accessProfile === "system" ? "Commands will inherit this OS account's permissions." : "This allows guarded file writes.";
|
|
53637
53017
|
return {
|
|
@@ -53918,7 +53298,7 @@ function DevicesView({ state, usable, bodyBudget, color }) {
|
|
|
53918
53298
|
const devices = state.status?.devices.slice(window2.start, window2.end) ?? [];
|
|
53919
53299
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { height: bodyBudget, flexDirection: "column", flexShrink: 0, overflow: "hidden", children: [
|
|
53920
53300
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Blank, {}),
|
|
53921
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionTitle, { color, children: "
|
|
53301
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionTitle, { color, children: "Paired" }),
|
|
53922
53302
|
state.statusLoading ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
53923
53303
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Blank, {}),
|
|
53924
53304
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, children: "Loading devices\u2026" })
|
|
@@ -53926,7 +53306,7 @@ function DevicesView({ state, usable, bodyBudget, color }) {
|
|
|
53926
53306
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Blank, {}),
|
|
53927
53307
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, children: "Devices are not loaded." })
|
|
53928
53308
|
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
53929
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.ink : void 0, wrap: "truncate", children: state.
|
|
53309
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.ink : void 0, wrap: "truncate", children: state.deviceName ?? "This computer" }),
|
|
53930
53310
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, wrap: "truncate", children: state.status.relay }),
|
|
53931
53311
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Blank, {}),
|
|
53932
53312
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionTitle, { color, children: "Overview" }),
|
|
@@ -53978,7 +53358,6 @@ function HelpView({ bodyBudget, color }) {
|
|
|
53978
53358
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionTitle, { color, tone: COLORS.coral, children: "Manage" }),
|
|
53979
53359
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(HelpRow, { keyLabel: "\u2190/\u2192", label: "Change workspace access", color, tone: COLORS.coral }),
|
|
53980
53360
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(HelpRow, { keyLabel: "Enter/R", label: "Revoke selected device", color, tone: COLORS.coral }),
|
|
53981
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(HelpRow, { keyLabel: "L", label: "Account sign out", color, tone: COLORS.coral }),
|
|
53982
53361
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Blank, {}),
|
|
53983
53362
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionTitle, { color, children: "App" }),
|
|
53984
53363
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(HelpRow, { keyLabel: "Q", label: "Disconnect and quit", color, tone: COLORS.coral }),
|
|
@@ -54067,9 +53446,9 @@ function HudScreen({ state, columns, rows, color = true, now = Date.now() }) {
|
|
|
54067
53446
|
] });
|
|
54068
53447
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { width: Math.max(8, columns), height: Math.max(6, rows), flexDirection: "column", children: margin > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { marginLeft: margin, children: inner }) : inner });
|
|
54069
53448
|
}
|
|
54070
|
-
async function loadDevices(
|
|
54071
|
-
const current =
|
|
54072
|
-
|
|
53449
|
+
async function loadDevices(store2, actions, signal) {
|
|
53450
|
+
const current = store2.getSnapshot();
|
|
53451
|
+
store2.update((state) => ({
|
|
54073
53452
|
...state,
|
|
54074
53453
|
view: "devices",
|
|
54075
53454
|
prompt: void 0,
|
|
@@ -54077,20 +53456,20 @@ async function loadDevices(store3, actions, signal) {
|
|
|
54077
53456
|
}));
|
|
54078
53457
|
if (current.statusLoading) return;
|
|
54079
53458
|
if (current.connection !== "connected" && current.connection !== "retrying") {
|
|
54080
|
-
|
|
53459
|
+
store2.update((state) => ({
|
|
54081
53460
|
...state,
|
|
54082
53461
|
notice: "Devices are available after Glossa connects."
|
|
54083
53462
|
}));
|
|
54084
53463
|
return;
|
|
54085
53464
|
}
|
|
54086
|
-
|
|
53465
|
+
store2.update((state) => ({
|
|
54087
53466
|
...state,
|
|
54088
53467
|
statusLoading: true
|
|
54089
53468
|
}));
|
|
54090
53469
|
try {
|
|
54091
53470
|
const status = await actions.loadStatus(signal);
|
|
54092
53471
|
if (signal.aborted) return;
|
|
54093
|
-
|
|
53472
|
+
store2.update((state) => ({
|
|
54094
53473
|
...state,
|
|
54095
53474
|
status,
|
|
54096
53475
|
deviceSelection: Math.min(
|
|
@@ -54101,24 +53480,24 @@ async function loadDevices(store3, actions, signal) {
|
|
|
54101
53480
|
}));
|
|
54102
53481
|
} catch (error46) {
|
|
54103
53482
|
if (signal.aborted) return;
|
|
54104
|
-
|
|
53483
|
+
store2.update((state) => ({
|
|
54105
53484
|
...state,
|
|
54106
53485
|
statusLoading: false,
|
|
54107
53486
|
notice: error46 instanceof Error ? error46.message : String(error46)
|
|
54108
53487
|
}));
|
|
54109
53488
|
}
|
|
54110
53489
|
}
|
|
54111
|
-
function beginAccessChange(
|
|
53490
|
+
function beginAccessChange(store2, actions, accessProfile) {
|
|
54112
53491
|
actions.changeAccessProfile(accessProfile);
|
|
54113
|
-
|
|
53492
|
+
store2.update((state) => ({
|
|
54114
53493
|
...state,
|
|
54115
53494
|
pendingAccessProfile: accessProfile,
|
|
54116
53495
|
prompt: void 0,
|
|
54117
53496
|
notice: void 0
|
|
54118
53497
|
}));
|
|
54119
53498
|
}
|
|
54120
|
-
function HudRuntime({ store:
|
|
54121
|
-
const state = (0, import_react34.useSyncExternalStore)(
|
|
53499
|
+
function HudRuntime({ store: store2, actions, signal, stop }) {
|
|
53500
|
+
const state = (0, import_react34.useSyncExternalStore)(store2.subscribe, store2.getSnapshot, store2.getSnapshot);
|
|
54122
53501
|
const { columns, rows } = use_window_size_default();
|
|
54123
53502
|
const [, setClock] = (0, import_react34.useState)(0);
|
|
54124
53503
|
(0, import_react34.useEffect)(() => {
|
|
@@ -54127,7 +53506,7 @@ function HudRuntime({ store: store3, actions, signal, stop }) {
|
|
|
54127
53506
|
return () => clearInterval(timer);
|
|
54128
53507
|
}, [state.view, state.activities.length]);
|
|
54129
53508
|
(0, import_react34.useEffect)(() => {
|
|
54130
|
-
|
|
53509
|
+
store2.update((current) => {
|
|
54131
53510
|
let next = current;
|
|
54132
53511
|
const metrics = screenMetrics(current, columns, rows);
|
|
54133
53512
|
if (current.view === "activity") {
|
|
@@ -54149,9 +53528,9 @@ function HudRuntime({ store: store3, actions, signal, stop }) {
|
|
|
54149
53528
|
}
|
|
54150
53529
|
return next;
|
|
54151
53530
|
});
|
|
54152
|
-
}, [columns, rows,
|
|
53531
|
+
}, [columns, rows, store2]);
|
|
54153
53532
|
use_input_default((input, key) => {
|
|
54154
|
-
const current =
|
|
53533
|
+
const current = store2.getSnapshot();
|
|
54155
53534
|
const value = input.toLowerCase();
|
|
54156
53535
|
if (key.ctrl && value === "c" || value === "q") {
|
|
54157
53536
|
stop();
|
|
@@ -54160,21 +53539,17 @@ function HudRuntime({ store: store3, actions, signal, stop }) {
|
|
|
54160
53539
|
if (current.busy) return;
|
|
54161
53540
|
if (current.prompt) {
|
|
54162
53541
|
if (key.escape || value === "n") {
|
|
54163
|
-
|
|
53542
|
+
store2.update((state2) => ({ ...state2, prompt: void 0, notice: void 0 }));
|
|
54164
53543
|
return;
|
|
54165
53544
|
}
|
|
54166
53545
|
if (value !== "y") return;
|
|
54167
|
-
if (current.prompt.type === "logout") {
|
|
54168
|
-
stop("logout");
|
|
54169
|
-
return;
|
|
54170
|
-
}
|
|
54171
53546
|
if (current.prompt.type === "access-confirm") {
|
|
54172
|
-
beginAccessChange(
|
|
53547
|
+
beginAccessChange(store2, actions, current.prompt.accessProfile);
|
|
54173
53548
|
return;
|
|
54174
53549
|
}
|
|
54175
53550
|
const device = current.status?.devices[current.prompt.deviceIndex];
|
|
54176
53551
|
if (!device) return;
|
|
54177
|
-
|
|
53552
|
+
store2.update((state2) => ({
|
|
54178
53553
|
...state2,
|
|
54179
53554
|
busy: true,
|
|
54180
53555
|
prompt: void 0,
|
|
@@ -54182,13 +53557,13 @@ function HudRuntime({ store: store3, actions, signal, stop }) {
|
|
|
54182
53557
|
}));
|
|
54183
53558
|
void actions.revokeDevice(device.id, signal).then(async () => {
|
|
54184
53559
|
if (signal.aborted) return;
|
|
54185
|
-
|
|
54186
|
-
await loadDevices(
|
|
53560
|
+
store2.update((state2) => ({ ...state2, busy: false }));
|
|
53561
|
+
await loadDevices(store2, actions, signal);
|
|
54187
53562
|
if (signal.aborted) return;
|
|
54188
|
-
|
|
53563
|
+
store2.update((state2) => ({ ...state2, notice: `Revoked ${device.name}.` }));
|
|
54189
53564
|
}).catch((error46) => {
|
|
54190
53565
|
if (signal.aborted) return;
|
|
54191
|
-
|
|
53566
|
+
store2.update((state2) => ({
|
|
54192
53567
|
...state2,
|
|
54193
53568
|
busy: false,
|
|
54194
53569
|
notice: error46 instanceof Error ? error46.message : String(error46)
|
|
@@ -54202,7 +53577,7 @@ function HudRuntime({ store: store3, actions, signal, stop }) {
|
|
|
54202
53577
|
const maxPage = activityMaxPage(current.activities.length, capacity);
|
|
54203
53578
|
const page = key.upArrow ? Math.min(maxPage, current.activityPage + 1) : Math.max(0, current.activityPage - 1);
|
|
54204
53579
|
if (page !== current.activityPage) {
|
|
54205
|
-
|
|
53580
|
+
store2.update((state2) => ({ ...state2, activityPage: page }));
|
|
54206
53581
|
}
|
|
54207
53582
|
return;
|
|
54208
53583
|
}
|
|
@@ -54214,7 +53589,7 @@ function HudRuntime({ store: store3, actions, signal, stop }) {
|
|
|
54214
53589
|
}
|
|
54215
53590
|
const selection = key.upArrow ? Math.max(0, current.deviceSelection - 1) : Math.min(total - 1, current.deviceSelection + 1);
|
|
54216
53591
|
if (selection !== current.deviceSelection) {
|
|
54217
|
-
|
|
53592
|
+
store2.update((state2) => ({ ...state2, deviceSelection: selection, notice: void 0 }));
|
|
54218
53593
|
}
|
|
54219
53594
|
return;
|
|
54220
53595
|
}
|
|
@@ -54223,9 +53598,9 @@ function HudRuntime({ store: store3, actions, signal, stop }) {
|
|
|
54223
53598
|
const accessProfile = adjacentAccessProfile(current.accessProfile, direction);
|
|
54224
53599
|
if (!accessProfile) return;
|
|
54225
53600
|
if (direction < 0) {
|
|
54226
|
-
beginAccessChange(
|
|
53601
|
+
beginAccessChange(store2, actions, accessProfile);
|
|
54227
53602
|
} else {
|
|
54228
|
-
|
|
53603
|
+
store2.update((state2) => ({
|
|
54229
53604
|
...state2,
|
|
54230
53605
|
prompt: { type: "access-confirm", accessProfile },
|
|
54231
53606
|
notice: void 0
|
|
@@ -54234,11 +53609,11 @@ function HudRuntime({ store: store3, actions, signal, stop }) {
|
|
|
54234
53609
|
return;
|
|
54235
53610
|
}
|
|
54236
53611
|
if (key.escape) {
|
|
54237
|
-
|
|
53612
|
+
store2.update((state2) => ({ ...state2, view: "workspace", notice: void 0 }));
|
|
54238
53613
|
return;
|
|
54239
53614
|
}
|
|
54240
53615
|
if (value === "a") {
|
|
54241
|
-
|
|
53616
|
+
store2.update((state2) => ({
|
|
54242
53617
|
...state2,
|
|
54243
53618
|
view: "activity",
|
|
54244
53619
|
activityPage: current.view === "activity" ? state2.activityPage : 0,
|
|
@@ -54247,7 +53622,7 @@ function HudRuntime({ store: store3, actions, signal, stop }) {
|
|
|
54247
53622
|
return;
|
|
54248
53623
|
}
|
|
54249
53624
|
if (value === "w") {
|
|
54250
|
-
|
|
53625
|
+
store2.update((state2) => ({
|
|
54251
53626
|
...state2,
|
|
54252
53627
|
view: "workspace",
|
|
54253
53628
|
prompt: void 0,
|
|
@@ -54257,27 +53632,27 @@ function HudRuntime({ store: store3, actions, signal, stop }) {
|
|
|
54257
53632
|
}
|
|
54258
53633
|
if (value === "d") {
|
|
54259
53634
|
if (current.status) {
|
|
54260
|
-
|
|
53635
|
+
store2.update((state2) => ({
|
|
54261
53636
|
...state2,
|
|
54262
53637
|
view: "devices",
|
|
54263
53638
|
prompt: void 0,
|
|
54264
53639
|
notice: void 0
|
|
54265
53640
|
}));
|
|
54266
53641
|
} else {
|
|
54267
|
-
void loadDevices(
|
|
53642
|
+
void loadDevices(store2, actions, signal);
|
|
54268
53643
|
}
|
|
54269
53644
|
return;
|
|
54270
53645
|
}
|
|
54271
53646
|
if ((key.return || input === "\r" || input === "\n" || value === "r") && current.view === "devices") {
|
|
54272
53647
|
const count = current.status?.devices.length ?? 0;
|
|
54273
53648
|
if (count === 0) {
|
|
54274
|
-
|
|
53649
|
+
store2.update((state2) => ({ ...state2, notice: "There are no devices to revoke." }));
|
|
54275
53650
|
return;
|
|
54276
53651
|
}
|
|
54277
53652
|
const metrics = screenMetrics(current, columns, rows);
|
|
54278
53653
|
const capacity = statusDeviceCapacity(current, metrics.bodyBudget, metrics.usable);
|
|
54279
53654
|
const selection = Math.min(current.deviceSelection, count - 1);
|
|
54280
|
-
|
|
53655
|
+
store2.update((state2) => capacity === 0 ? { ...state2, notice: "Increase the terminal height to choose a device." } : {
|
|
54281
53656
|
...state2,
|
|
54282
53657
|
deviceSelection: selection,
|
|
54283
53658
|
prompt: { type: "revoke-confirm", deviceIndex: selection },
|
|
@@ -54285,16 +53660,8 @@ function HudRuntime({ store: store3, actions, signal, stop }) {
|
|
|
54285
53660
|
});
|
|
54286
53661
|
return;
|
|
54287
53662
|
}
|
|
54288
|
-
if (value === "l") {
|
|
54289
|
-
store3.update((state2) => ({
|
|
54290
|
-
...state2,
|
|
54291
|
-
prompt: { type: "logout" },
|
|
54292
|
-
notice: void 0
|
|
54293
|
-
}));
|
|
54294
|
-
return;
|
|
54295
|
-
}
|
|
54296
53663
|
if (input === "?") {
|
|
54297
|
-
|
|
53664
|
+
store2.update((state2) => ({
|
|
54298
53665
|
...state2,
|
|
54299
53666
|
view: "help",
|
|
54300
53667
|
notice: void 0
|
|
@@ -54317,11 +53684,9 @@ async function runSessionHud(actions, input = process.stdin, output = process.st
|
|
|
54317
53684
|
throw new Error("Glossa requires an interactive terminal.");
|
|
54318
53685
|
}
|
|
54319
53686
|
const controller = new AbortController();
|
|
54320
|
-
const
|
|
54321
|
-
let exitAction = "quit";
|
|
53687
|
+
const store2 = new HudStore(actions.workspace);
|
|
54322
53688
|
let instance;
|
|
54323
|
-
const stop = (
|
|
54324
|
-
exitAction = action;
|
|
53689
|
+
const stop = () => {
|
|
54325
53690
|
controller.abort();
|
|
54326
53691
|
instance?.unmount();
|
|
54327
53692
|
};
|
|
@@ -54333,7 +53698,7 @@ async function runSessionHud(actions, input = process.stdin, output = process.st
|
|
|
54333
53698
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
54334
53699
|
HudRuntime,
|
|
54335
53700
|
{
|
|
54336
|
-
store:
|
|
53701
|
+
store: store2,
|
|
54337
53702
|
actions,
|
|
54338
53703
|
signal: controller.signal,
|
|
54339
53704
|
stop
|
|
@@ -54350,13 +53715,13 @@ async function runSessionHud(actions, input = process.stdin, output = process.st
|
|
|
54350
53715
|
maxFps: 30
|
|
54351
53716
|
}
|
|
54352
53717
|
);
|
|
54353
|
-
const session = actions.run(controller.signal, (event) =>
|
|
53718
|
+
const session = actions.run(controller.signal, (event) => store2.event(event)).then(() => {
|
|
54354
53719
|
if (!controller.signal.aborted) {
|
|
54355
|
-
|
|
53720
|
+
store2.update((state) => ({ ...state, connection: "disconnected" }));
|
|
54356
53721
|
}
|
|
54357
53722
|
}).catch((error46) => {
|
|
54358
53723
|
if (!controller.signal.aborted) {
|
|
54359
|
-
|
|
53724
|
+
store2.update((state) => ({
|
|
54360
53725
|
...state,
|
|
54361
53726
|
connection: "error",
|
|
54362
53727
|
message: error46 instanceof Error ? error46.message : String(error46)
|
|
@@ -54369,7 +53734,6 @@ async function runSessionHud(actions, input = process.stdin, output = process.st
|
|
|
54369
53734
|
try {
|
|
54370
53735
|
await instance.waitUntilExit();
|
|
54371
53736
|
await session;
|
|
54372
|
-
return exitAction;
|
|
54373
53737
|
} finally {
|
|
54374
53738
|
controller.abort();
|
|
54375
53739
|
process.removeListener("SIGINT", stopFromSignal);
|
|
@@ -54379,10 +53743,10 @@ async function runSessionHud(actions, input = process.stdin, output = process.st
|
|
|
54379
53743
|
|
|
54380
53744
|
// src/update-service.ts
|
|
54381
53745
|
var import_semver = __toESM(require_semver2(), 1);
|
|
54382
|
-
import { spawn as
|
|
53746
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
54383
53747
|
import { createHash as createHash2 } from "node:crypto";
|
|
54384
53748
|
import { chmod as chmod3, readdir as readdir2, rename as rename2, rm as rm5, writeFile as writeFile6 } from "node:fs/promises";
|
|
54385
|
-
import
|
|
53749
|
+
import path8 from "node:path";
|
|
54386
53750
|
var PACKAGE_NAME = "@ariobarin/glossa";
|
|
54387
53751
|
var DEFAULT_REGISTRY_URL = "https://registry.npmjs.org/@ariobarin%2Fglossa";
|
|
54388
53752
|
var DEFAULT_RELEASE_BASE_URL = "https://github.com/ariobarin/glossa/releases/download";
|
|
@@ -54459,8 +53823,8 @@ async function cleanupUpdateBackups(distribution, options = {}) {
|
|
|
54459
53823
|
const platform2 = options.platform ?? process.platform;
|
|
54460
53824
|
if (distribution !== "standalone" || platform2 !== "win32") return;
|
|
54461
53825
|
const executable = options.executablePath ?? process.execPath;
|
|
54462
|
-
const directory =
|
|
54463
|
-
const prefix = `${
|
|
53826
|
+
const directory = path8.dirname(executable);
|
|
53827
|
+
const prefix = `${path8.basename(executable)}.old-`;
|
|
54464
53828
|
let entries;
|
|
54465
53829
|
try {
|
|
54466
53830
|
entries = await readdir2(directory, { withFileTypes: true });
|
|
@@ -54473,7 +53837,7 @@ async function cleanupUpdateBackups(distribution, options = {}) {
|
|
|
54473
53837
|
const ownerPid = backupOwnerPid(entry.name, prefix);
|
|
54474
53838
|
return ownerPid !== null && !processIsAlive(ownerPid);
|
|
54475
53839
|
}).map(async (entry) => {
|
|
54476
|
-
await rm5(
|
|
53840
|
+
await rm5(path8.join(directory, entry.name), { force: true }).catch(() => void 0);
|
|
54477
53841
|
})
|
|
54478
53842
|
);
|
|
54479
53843
|
}
|
|
@@ -54514,7 +53878,7 @@ function npmInstallInvocation(version2, platform2 = process.platform, commandShe
|
|
|
54514
53878
|
async function runNpmInstall(version2) {
|
|
54515
53879
|
const { command, args } = npmInstallInvocation(version2);
|
|
54516
53880
|
await new Promise((resolve, reject) => {
|
|
54517
|
-
const child =
|
|
53881
|
+
const child = spawn2(command, args, { stdio: "inherit" });
|
|
54518
53882
|
child.once("error", reject);
|
|
54519
53883
|
child.once("close", (status) => {
|
|
54520
53884
|
if (status === 0) resolve();
|
|
@@ -54549,9 +53913,9 @@ async function installStandaloneUpdate(info, options) {
|
|
|
54549
53913
|
if (actual !== expected) {
|
|
54550
53914
|
throw new Error("Glossa refused to update because the SHA-256 checksum did not match.");
|
|
54551
53915
|
}
|
|
54552
|
-
const download =
|
|
54553
|
-
|
|
54554
|
-
`${
|
|
53916
|
+
const download = path8.join(
|
|
53917
|
+
path8.dirname(destination),
|
|
53918
|
+
`${path8.basename(destination)}.download-${process.pid}`
|
|
54555
53919
|
);
|
|
54556
53920
|
try {
|
|
54557
53921
|
await writeFile6(download, binary, { mode: 493 });
|
|
@@ -54578,13 +53942,13 @@ async function installUpdate(info, distribution, options = {}) {
|
|
|
54578
53942
|
|
|
54579
53943
|
// src/update-state.ts
|
|
54580
53944
|
import { chmod as chmod4, mkdir as mkdir6, readFile as readFile5, rename as rename3, rm as rm6, writeFile as writeFile7 } from "node:fs/promises";
|
|
54581
|
-
import
|
|
53945
|
+
import path9 from "node:path";
|
|
54582
53946
|
var UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
54583
53947
|
function defaultUpdateChannel(version2) {
|
|
54584
53948
|
return version2.includes("-") ? "beta" : "stable";
|
|
54585
53949
|
}
|
|
54586
53950
|
function updateStateFile() {
|
|
54587
|
-
return
|
|
53951
|
+
return path9.join(configDirectory(), "updates.json");
|
|
54588
53952
|
}
|
|
54589
53953
|
function isPolicy(value) {
|
|
54590
53954
|
return value === "notify" || value === "auto" || value === "off";
|
|
@@ -54603,12 +53967,10 @@ async function loadUpdateState(currentVersion, file2 = updateStateFile()) {
|
|
|
54603
53967
|
try {
|
|
54604
53968
|
const parsed = JSON.parse(await readFile5(file2, "utf8"));
|
|
54605
53969
|
const lastCheckedAt = optionalString(parsed.lastCheckedAt);
|
|
54606
|
-
const latestVersion = optionalString(parsed.latestVersion);
|
|
54607
53970
|
return {
|
|
54608
53971
|
policy: isPolicy(parsed.policy) ? parsed.policy : defaults.policy,
|
|
54609
53972
|
channel: isChannel(parsed.channel) ? parsed.channel : defaults.channel,
|
|
54610
|
-
...lastCheckedAt ? { lastCheckedAt } : {}
|
|
54611
|
-
...latestVersion ? { latestVersion } : {}
|
|
53973
|
+
...lastCheckedAt ? { lastCheckedAt } : {}
|
|
54612
53974
|
};
|
|
54613
53975
|
} catch (error46) {
|
|
54614
53976
|
if (error46.code === "ENOENT" || error46 instanceof SyntaxError) {
|
|
@@ -54618,7 +53980,7 @@ async function loadUpdateState(currentVersion, file2 = updateStateFile()) {
|
|
|
54618
53980
|
}
|
|
54619
53981
|
}
|
|
54620
53982
|
async function saveUpdateState(state, file2 = updateStateFile()) {
|
|
54621
|
-
const directory =
|
|
53983
|
+
const directory = path9.dirname(file2);
|
|
54622
53984
|
const temporary = `${file2}.${process.pid}.tmp`;
|
|
54623
53985
|
await mkdir6(directory, { recursive: true, mode: 448 });
|
|
54624
53986
|
try {
|
|
@@ -54642,11 +54004,10 @@ async function configureUpdates(currentVersion, changes, file2 = updateStateFile
|
|
|
54642
54004
|
await saveUpdateState(state, file2);
|
|
54643
54005
|
return state;
|
|
54644
54006
|
}
|
|
54645
|
-
async function recordUpdateCheck(currentVersion,
|
|
54007
|
+
async function recordUpdateCheck(currentVersion, checkedAt = /* @__PURE__ */ new Date(), file2 = updateStateFile()) {
|
|
54646
54008
|
const state = {
|
|
54647
54009
|
...await loadUpdateState(currentVersion, file2),
|
|
54648
|
-
lastCheckedAt: checkedAt.toISOString()
|
|
54649
|
-
latestVersion
|
|
54010
|
+
lastCheckedAt: checkedAt.toISOString()
|
|
54650
54011
|
};
|
|
54651
54012
|
await saveUpdateState(state, file2);
|
|
54652
54013
|
return state;
|
|
@@ -54673,41 +54034,18 @@ async function unpairComputer(dependencies = {}) {
|
|
|
54673
54034
|
log("Unpaired this computer from Glossa.");
|
|
54674
54035
|
}
|
|
54675
54036
|
|
|
54676
|
-
// src/usage-store.ts
|
|
54677
|
-
import { appendFile, chmod as chmod5, mkdir as mkdir7, readFile as readFile6 } from "node:fs/promises";
|
|
54678
|
-
import path11 from "node:path";
|
|
54679
|
-
function usageFile() {
|
|
54680
|
-
return path11.join(configDirectory(), "usage.jsonl");
|
|
54681
|
-
}
|
|
54682
|
-
async function recordUsageEvent(event, file2 = usageFile()) {
|
|
54683
|
-
const directory = path11.dirname(file2);
|
|
54684
|
-
await mkdir7(directory, { recursive: true, mode: 448 });
|
|
54685
|
-
await appendFile(file2, `${JSON.stringify(event)}
|
|
54686
|
-
`, {
|
|
54687
|
-
encoding: "utf8",
|
|
54688
|
-
mode: 384
|
|
54689
|
-
});
|
|
54690
|
-
if (process.platform !== "win32") await chmod5(file2, 384);
|
|
54691
|
-
}
|
|
54692
|
-
async function recordSessionUsage(at = /* @__PURE__ */ new Date(), file2 = usageFile()) {
|
|
54693
|
-
await recordUsageEvent({ v: 1, at: at.toISOString(), kind: "session" }, file2);
|
|
54694
|
-
}
|
|
54695
|
-
async function recordToolUsage(tool, ok, at = /* @__PURE__ */ new Date(), file2 = usageFile()) {
|
|
54696
|
-
await recordUsageEvent({ v: 1, at: at.toISOString(), kind: "tool", tool, ok }, file2);
|
|
54697
|
-
}
|
|
54698
|
-
|
|
54699
54037
|
// src/worker/root-selection.ts
|
|
54700
54038
|
import { realpath as realpath2 } from "node:fs/promises";
|
|
54701
54039
|
import os6 from "node:os";
|
|
54702
|
-
import
|
|
54040
|
+
import path10 from "node:path";
|
|
54703
54041
|
function containsPath(root, candidate) {
|
|
54704
|
-
const relative =
|
|
54705
|
-
return relative !== "" && relative !== ".." && !relative.startsWith(`..${
|
|
54042
|
+
const relative = path10.relative(root, candidate);
|
|
54043
|
+
return relative !== "" && relative !== ".." && !relative.startsWith(`..${path10.sep}`) && !path10.isAbsolute(relative);
|
|
54706
54044
|
}
|
|
54707
54045
|
async function rejectImplicitHomeAncestor(root) {
|
|
54708
54046
|
const homes = await Promise.all(
|
|
54709
54047
|
[os6.homedir(), accountHomeDirectory()].map(
|
|
54710
|
-
async (home) => await realpath2(home).catch(() =>
|
|
54048
|
+
async (home) => await realpath2(home).catch(() => path10.resolve(home))
|
|
54711
54049
|
)
|
|
54712
54050
|
);
|
|
54713
54051
|
if (homes.some((home) => containsPath(root, home))) {
|
|
@@ -54725,9 +54063,9 @@ async function selectExposureRoot(explicitPath, cwd2 = process.cwd()) {
|
|
|
54725
54063
|
|
|
54726
54064
|
// src/worker/workspace-lease.ts
|
|
54727
54065
|
import { createHash as createHash3 } from "node:crypto";
|
|
54728
|
-
import { lstat as lstat3, mkdir as
|
|
54066
|
+
import { lstat as lstat3, mkdir as mkdir7, rm as rm7, stat as stat4 } from "node:fs/promises";
|
|
54729
54067
|
import net from "node:net";
|
|
54730
|
-
import
|
|
54068
|
+
import path11 from "node:path";
|
|
54731
54069
|
import { setTimeout as delay4 } from "node:timers/promises";
|
|
54732
54070
|
var GUARD_STALE_MS = 5e3;
|
|
54733
54071
|
var GUARD_RETRY_MS = 10;
|
|
@@ -54751,12 +54089,12 @@ function workspaceIdentity(root) {
|
|
|
54751
54089
|
}
|
|
54752
54090
|
function defaultLeaseDirectory() {
|
|
54753
54091
|
if (typeof process.getuid === "function") {
|
|
54754
|
-
return
|
|
54092
|
+
return path11.join("/tmp", `glossa-workspaces-${process.getuid()}`);
|
|
54755
54093
|
}
|
|
54756
|
-
return
|
|
54094
|
+
return path11.join(accountHomeDirectory(), ".glossa-workspace-leases");
|
|
54757
54095
|
}
|
|
54758
54096
|
async function ensureLeaseDirectory(directory) {
|
|
54759
|
-
await
|
|
54097
|
+
await mkdir7(directory, { recursive: true, mode: 448 });
|
|
54760
54098
|
const directoryStat = await lstat3(directory);
|
|
54761
54099
|
if (directoryStat.isSymbolicLink() || !directoryStat.isDirectory()) {
|
|
54762
54100
|
throw new Error("The Glossa workspace lease location is not a private directory.");
|
|
@@ -54773,14 +54111,14 @@ function endpointFor(identity, directory) {
|
|
|
54773
54111
|
if (process.platform === "win32") {
|
|
54774
54112
|
return `\\\\.\\pipe\\glossa-workspace-${identity}`;
|
|
54775
54113
|
}
|
|
54776
|
-
return
|
|
54114
|
+
return path11.join(directory, `${identity}.sock`);
|
|
54777
54115
|
}
|
|
54778
54116
|
async function acquireGuard2(directory, identity) {
|
|
54779
|
-
const guard =
|
|
54117
|
+
const guard = path11.join(directory, `${identity}.guard`);
|
|
54780
54118
|
const deadline = Date.now() + GUARD_TIMEOUT_MS;
|
|
54781
54119
|
while (true) {
|
|
54782
54120
|
try {
|
|
54783
|
-
await
|
|
54121
|
+
await mkdir7(guard, { mode: 448 });
|
|
54784
54122
|
return async () => await rm7(guard, { recursive: true, force: true });
|
|
54785
54123
|
} catch (error46) {
|
|
54786
54124
|
if (error46.code !== "EEXIST") throw error46;
|
|
@@ -54807,7 +54145,7 @@ async function acquireGuard2(directory, identity) {
|
|
|
54807
54145
|
function createLeaseServer() {
|
|
54808
54146
|
return net.createServer((socket) => socket.destroy());
|
|
54809
54147
|
}
|
|
54810
|
-
async function listen(server,
|
|
54148
|
+
async function listen(server, endpoint) {
|
|
54811
54149
|
await new Promise((resolve, reject) => {
|
|
54812
54150
|
const onError = (error46) => {
|
|
54813
54151
|
server.removeListener("listening", onListening);
|
|
@@ -54819,12 +54157,12 @@ async function listen(server, endpoint3) {
|
|
|
54819
54157
|
};
|
|
54820
54158
|
server.once("error", onError);
|
|
54821
54159
|
server.once("listening", onListening);
|
|
54822
|
-
server.listen(
|
|
54160
|
+
server.listen(endpoint);
|
|
54823
54161
|
});
|
|
54824
54162
|
}
|
|
54825
|
-
async function endpointIsActive(
|
|
54163
|
+
async function endpointIsActive(endpoint) {
|
|
54826
54164
|
return await new Promise((resolve) => {
|
|
54827
|
-
const socket = net.createConnection(
|
|
54165
|
+
const socket = net.createConnection(endpoint);
|
|
54828
54166
|
let settled = false;
|
|
54829
54167
|
const finish = (active) => {
|
|
54830
54168
|
if (settled) return;
|
|
@@ -54847,14 +54185,14 @@ async function acquireWorkspaceLease(root, options = {}) {
|
|
|
54847
54185
|
const userDirectory = options.directory ?? defaultLeaseDirectory();
|
|
54848
54186
|
await ensureLeaseDirectory(userDirectory);
|
|
54849
54187
|
const identity = workspaceIdentity(root);
|
|
54850
|
-
const
|
|
54188
|
+
const endpoint = endpointFor(identity, userDirectory);
|
|
54851
54189
|
const releaseGuard = await acquireGuard2(userDirectory, identity);
|
|
54852
54190
|
let server;
|
|
54853
54191
|
try {
|
|
54854
54192
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
54855
54193
|
server = createLeaseServer();
|
|
54856
54194
|
try {
|
|
54857
|
-
await listen(server,
|
|
54195
|
+
await listen(server, endpoint);
|
|
54858
54196
|
server.unref();
|
|
54859
54197
|
let released = false;
|
|
54860
54198
|
return {
|
|
@@ -54866,7 +54204,7 @@ async function acquireWorkspaceLease(root, options = {}) {
|
|
|
54866
54204
|
try {
|
|
54867
54205
|
await closeServer(server);
|
|
54868
54206
|
if (process.platform !== "win32") {
|
|
54869
|
-
await rm7(
|
|
54207
|
+
await rm7(endpoint, { force: true });
|
|
54870
54208
|
}
|
|
54871
54209
|
} finally {
|
|
54872
54210
|
await releaseGuard2();
|
|
@@ -54875,11 +54213,11 @@ async function acquireWorkspaceLease(root, options = {}) {
|
|
|
54875
54213
|
};
|
|
54876
54214
|
} catch (error46) {
|
|
54877
54215
|
await closeServer(server);
|
|
54878
|
-
if (await endpointIsActive(
|
|
54216
|
+
if (await endpointIsActive(endpoint)) {
|
|
54879
54217
|
throw new WorkspaceAlreadyActiveError();
|
|
54880
54218
|
}
|
|
54881
54219
|
if (error46.code !== "EADDRINUSE") throw error46;
|
|
54882
|
-
if (process.platform !== "win32") await rm7(
|
|
54220
|
+
if (process.platform !== "win32") await rm7(endpoint, { force: true });
|
|
54883
54221
|
}
|
|
54884
54222
|
}
|
|
54885
54223
|
throw new Error("Could not reserve this workspace for the current Glossa process.");
|
|
@@ -54889,15 +54227,12 @@ async function acquireWorkspaceLease(root, options = {}) {
|
|
|
54889
54227
|
}
|
|
54890
54228
|
|
|
54891
54229
|
// src/main.ts
|
|
54892
|
-
var VERSION = "0.2.1";
|
|
54230
|
+
var VERSION = "0.2.3-beta.1";
|
|
54893
54231
|
var DISTRIBUTION = "npm";
|
|
54894
54232
|
var HELP = `Glossa ${VERSION}
|
|
54895
54233
|
|
|
54896
54234
|
Usage:
|
|
54897
54235
|
glossa [--access <read-only|workspace|system>] [--label <name>] [directory]
|
|
54898
|
-
glossa status
|
|
54899
|
-
glossa devices revoke <id>
|
|
54900
|
-
glossa logout
|
|
54901
54236
|
glossa unpair
|
|
54902
54237
|
glossa update [--check]
|
|
54903
54238
|
glossa update --policy <notify|auto|off>
|
|
@@ -54919,68 +54254,18 @@ Keys:
|
|
|
54919
54254
|
\u2190\u2192 change workspace access
|
|
54920
54255
|
Enter/r revoke selected device
|
|
54921
54256
|
Esc workspace
|
|
54922
|
-
l account sign out
|
|
54923
54257
|
? help
|
|
54924
54258
|
q disconnect and quit`;
|
|
54925
|
-
async function
|
|
54926
|
-
const
|
|
54927
|
-
const cancel = () => controller.abort();
|
|
54928
|
-
process.once("SIGINT", cancel);
|
|
54929
|
-
try {
|
|
54930
|
-
return await action(controller.signal);
|
|
54931
|
-
} finally {
|
|
54932
|
-
process.removeListener("SIGINT", cancel);
|
|
54933
|
-
}
|
|
54934
|
-
}
|
|
54935
|
-
async function authenticatedSession(signal) {
|
|
54936
|
-
if (signal) return await signedInSession({ ...loadAuthConfig(), signal });
|
|
54937
|
-
return await withLoginSignal(
|
|
54938
|
-
async (loginSignal) => await signedInSession({ ...loadAuthConfig(), signal: loginSignal })
|
|
54939
|
-
);
|
|
54940
|
-
}
|
|
54941
|
-
async function authenticatedCredentials(signal) {
|
|
54942
|
-
return (await authenticatedSession(signal)).credentials;
|
|
54943
|
-
}
|
|
54944
|
-
async function showStatus() {
|
|
54945
|
-
const credentials = await authenticatedCredentials();
|
|
54946
|
-
const endpoints = loadRelayEndpoints();
|
|
54947
|
-
const status = await new WorkspaceStatusService(
|
|
54948
|
-
credentials,
|
|
54949
|
-
endpoints
|
|
54950
|
-
).refresh();
|
|
54951
|
-
for (const line of formatStatus(status)) console.log(line);
|
|
54952
|
-
}
|
|
54953
|
-
function hudStatus(status) {
|
|
54954
|
-
return {
|
|
54955
|
-
...status,
|
|
54956
|
-
devices: status.devices.map((device) => ({
|
|
54957
|
-
id: device.id,
|
|
54958
|
-
name: device.name,
|
|
54959
|
-
platform: device.platform ?? "Unknown platform",
|
|
54960
|
-
lastSeen: formatRelativeTime(device.lastSeenAt),
|
|
54961
|
-
status: deviceStatus(device)
|
|
54962
|
-
}))
|
|
54963
|
-
};
|
|
54964
|
-
}
|
|
54965
|
-
async function revokeKnownDevice(deviceId) {
|
|
54966
|
-
const credentials = await authenticatedCredentials();
|
|
54967
|
-
await revokeDevice(loadRelayEndpoints(), credentials, deviceId);
|
|
54968
|
-
}
|
|
54969
|
-
async function runWorkspaceSession(path14, label, accessProfile) {
|
|
54970
|
-
const root = await selectExposureRoot(path14);
|
|
54259
|
+
async function runWorkspaceSession(path12, label, accessProfile) {
|
|
54260
|
+
const root = await selectExposureRoot(path12);
|
|
54971
54261
|
const lease = await acquireWorkspaceLease(root);
|
|
54972
|
-
let usageWrites = Promise.resolve();
|
|
54973
|
-
const queueUsage = (write) => {
|
|
54974
|
-
usageWrites = usageWrites.then(write).catch(() => void 0);
|
|
54975
|
-
};
|
|
54976
54262
|
try {
|
|
54977
54263
|
const endpoints = loadRelayEndpoints();
|
|
54978
54264
|
const device = await deviceForSession(endpoints);
|
|
54979
54265
|
let postExitNotice;
|
|
54980
54266
|
let requestedAccessProfile = accessProfile;
|
|
54981
54267
|
let activeSessionController;
|
|
54982
|
-
|
|
54983
|
-
const exitAction = await runSessionHud({
|
|
54268
|
+
await runSessionHud({
|
|
54984
54269
|
workspace: root,
|
|
54985
54270
|
...label ? { workspaceLabel: label } : {},
|
|
54986
54271
|
run: async (signal, onEvent) => {
|
|
@@ -55000,9 +54285,6 @@ async function runWorkspaceSession(path14, label, accessProfile) {
|
|
|
55000
54285
|
signal: sessionController.signal,
|
|
55001
54286
|
onEvent: (event) => {
|
|
55002
54287
|
postExitNotice = retainPostExitNotice(postExitNotice, event);
|
|
55003
|
-
if (event.type === "activity" && event.phase === "returned") {
|
|
55004
|
-
queueUsage(async () => await recordToolUsage(event.job.type, event.ok));
|
|
55005
|
-
}
|
|
55006
54288
|
onEvent(event);
|
|
55007
54289
|
},
|
|
55008
54290
|
quiet: true,
|
|
@@ -55024,16 +54306,24 @@ async function runWorkspaceSession(path14, label, accessProfile) {
|
|
|
55024
54306
|
}
|
|
55025
54307
|
},
|
|
55026
54308
|
loadStatus: async (signal) => {
|
|
55027
|
-
const
|
|
55028
|
-
|
|
55029
|
-
|
|
55030
|
-
|
|
54309
|
+
const withSignal = async (input, init) => await fetch(input, { ...init, signal });
|
|
54310
|
+
const devices = (await listDevices(endpoints, `Device ${device.token}`, withSignal)).filter((entry) => entry.revokedAt === null);
|
|
54311
|
+
return {
|
|
54312
|
+
relay: endpoints.relayOrigin,
|
|
54313
|
+
activeWorkers: devices.some((entry) => entry.activeWorkers === null) ? null : devices.reduce((total, entry) => total + entry.activeWorkers, 0),
|
|
54314
|
+
devices: devices.map((entry) => ({
|
|
54315
|
+
id: entry.id,
|
|
54316
|
+
name: entry.name,
|
|
54317
|
+
platform: entry.platform ?? "Unknown platform",
|
|
54318
|
+
lastSeen: formatRelativeTime(entry.lastSeenAt),
|
|
54319
|
+
status: deviceStatus(entry)
|
|
54320
|
+
}))
|
|
54321
|
+
};
|
|
55031
54322
|
},
|
|
55032
54323
|
revokeDevice: async (deviceId, signal) => {
|
|
55033
|
-
const credentials = await authenticatedCredentials(signal);
|
|
55034
54324
|
await revokeDevice(
|
|
55035
54325
|
endpoints,
|
|
55036
|
-
|
|
54326
|
+
`Device ${device.token}`,
|
|
55037
54327
|
deviceId,
|
|
55038
54328
|
async (input, init) => await fetch(input, { ...init, signal })
|
|
55039
54329
|
);
|
|
@@ -55045,20 +54335,18 @@ async function runWorkspaceSession(path14, label, accessProfile) {
|
|
|
55045
54335
|
}
|
|
55046
54336
|
});
|
|
55047
54337
|
if (postExitNotice) console.error(postExitNotice);
|
|
55048
|
-
if (exitAction === "logout") await logoutFromGlossa();
|
|
55049
54338
|
} finally {
|
|
55050
|
-
await usageWrites;
|
|
55051
54339
|
await lease.release();
|
|
55052
54340
|
}
|
|
55053
54341
|
}
|
|
55054
|
-
async function runWorkspace(
|
|
54342
|
+
async function runWorkspace(path12, label, accessProfile) {
|
|
55055
54343
|
await withWorkspaceLease(
|
|
55056
|
-
async () => await runWorkspaceSession(
|
|
54344
|
+
async () => await runWorkspaceSession(path12, label, accessProfile)
|
|
55057
54345
|
);
|
|
55058
54346
|
}
|
|
55059
54347
|
async function refreshUpdateInfo(timeoutMs) {
|
|
55060
54348
|
const info = await loadUpdateInfo(timeoutMs);
|
|
55061
|
-
await recordUpdateCheck(VERSION
|
|
54349
|
+
await recordUpdateCheck(VERSION);
|
|
55062
54350
|
return info;
|
|
55063
54351
|
}
|
|
55064
54352
|
async function loadUpdateInfo(timeoutMs) {
|
|
@@ -55113,11 +54401,11 @@ async function updateBeforeWorkspace() {
|
|
|
55113
54401
|
return false;
|
|
55114
54402
|
}
|
|
55115
54403
|
if (!info.updateAvailable) {
|
|
55116
|
-
await recordUpdateCheck(VERSION
|
|
54404
|
+
await recordUpdateCheck(VERSION);
|
|
55117
54405
|
return false;
|
|
55118
54406
|
}
|
|
55119
54407
|
if (state.policy === "notify") {
|
|
55120
|
-
await recordUpdateCheck(VERSION
|
|
54408
|
+
await recordUpdateCheck(VERSION);
|
|
55121
54409
|
console.error(
|
|
55122
54410
|
`Glossa ${info.availableVersion} is available. Run glossa update after disconnecting.`
|
|
55123
54411
|
);
|
|
@@ -55152,17 +54440,10 @@ async function main() {
|
|
|
55152
54440
|
invocation.label,
|
|
55153
54441
|
invocation.accessProfile
|
|
55154
54442
|
);
|
|
55155
|
-
} else if (invocation.command === "status") {
|
|
55156
|
-
await showStatus();
|
|
55157
|
-
} else if (invocation.command === "logout") {
|
|
55158
|
-
await logoutFromGlossa();
|
|
55159
54443
|
} else if (invocation.command === "unpair") {
|
|
55160
54444
|
await unpairComputer();
|
|
55161
54445
|
} else if (invocation.command === "update") {
|
|
55162
54446
|
await runUpdateCommand(invocation);
|
|
55163
|
-
} else {
|
|
55164
|
-
await revokeKnownDevice(invocation.deviceId);
|
|
55165
|
-
console.log(`Revoked device ${invocation.deviceId}. Running workspaces on it are disconnected.`);
|
|
55166
54447
|
}
|
|
55167
54448
|
}
|
|
55168
54449
|
main().catch((error46) => {
|