@ariobarin/glossa 0.2.0-beta.2 → 0.2.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 +4 -1
- package/dist/app.js +676 -446
- 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, path14, index, value) {
|
|
10440
|
+
if (index >= path14.length) return value;
|
|
10441
|
+
var key = path14[index], updated = isArrayImpl(obj) ? obj.slice() : assign({}, obj);
|
|
10442
|
+
updated[key] = copyWithSetImpl(obj[key], path14, 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, path14, index) {
|
|
10470
|
+
var key = path14[index], updated = isArrayImpl(obj) ? obj.slice() : assign({}, obj);
|
|
10471
|
+
if (index + 1 === path14.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], path14, 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, path14, value) {
|
|
23751
23751
|
id = findHook(fiber, id);
|
|
23752
|
-
null !== id && (
|
|
23752
|
+
null !== id && (path14 = copyWithSetImpl(id.memoizedState, path14, 0, value), id.memoizedState = path14, id.baseState = path14, fiber.memoizedProps = assign({}, fiber.memoizedProps), path14 = enqueueConcurrentRenderForLane(fiber, 2), null !== path14 && scheduleUpdateOnFiber(path14, fiber, 2));
|
|
23753
23753
|
};
|
|
23754
|
-
overrideHookStateDeletePath = function(fiber, id,
|
|
23754
|
+
overrideHookStateDeletePath = function(fiber, id, path14) {
|
|
23755
23755
|
id = findHook(fiber, id);
|
|
23756
|
-
null !== id && (
|
|
23756
|
+
null !== id && (path14 = copyWithDeleteImpl(id.memoizedState, path14, 0), id.memoizedState = path14, id.baseState = path14, fiber.memoizedProps = assign({}, fiber.memoizedProps), path14 = enqueueConcurrentRenderForLane(fiber, 2), null !== path14 && scheduleUpdateOnFiber(path14, 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, path14, value) {
|
|
23763
|
+
fiber.pendingProps = copyWithSetImpl(fiber.memoizedProps, path14, 0, value);
|
|
23764
23764
|
fiber.alternate && (fiber.alternate.pendingProps = fiber.pendingProps);
|
|
23765
|
-
|
|
23766
|
-
null !==
|
|
23765
|
+
path14 = enqueueConcurrentRenderForLane(fiber, 2);
|
|
23766
|
+
null !== path14 && scheduleUpdateOnFiber(path14, fiber, 2);
|
|
23767
23767
|
};
|
|
23768
|
-
overridePropsDeletePath = function(fiber,
|
|
23769
|
-
fiber.pendingProps = copyWithDeleteImpl(fiber.memoizedProps,
|
|
23768
|
+
overridePropsDeletePath = function(fiber, path14) {
|
|
23769
|
+
fiber.pendingProps = copyWithDeleteImpl(fiber.memoizedProps, path14, 0);
|
|
23770
23770
|
fiber.alternate && (fiber.alternate.pendingProps = fiber.pendingProps);
|
|
23771
|
-
|
|
23772
|
-
null !==
|
|
23771
|
+
path14 = enqueueConcurrentRenderForLane(fiber, 2);
|
|
23772
|
+
null !== path14 && scheduleUpdateOnFiber(path14, fiber, 2);
|
|
23773
23773
|
};
|
|
23774
23774
|
overridePropsRenamePath = function(fiber, oldPath, newPath) {
|
|
23775
23775
|
fiber.pendingProps = copyWithRename(
|
|
@@ -27007,21 +27007,6 @@ var SessionExpiredError = class extends Error {
|
|
|
27007
27007
|
this.name = "SessionExpiredError";
|
|
27008
27008
|
}
|
|
27009
27009
|
};
|
|
27010
|
-
function accessTokenSubject(credentials) {
|
|
27011
|
-
try {
|
|
27012
|
-
const parts = credentials.accessToken.split(".");
|
|
27013
|
-
if (parts.length !== 3) throw new Error();
|
|
27014
|
-
const payload = JSON.parse(
|
|
27015
|
-
Buffer.from(parts[1], "base64url").toString("utf8")
|
|
27016
|
-
);
|
|
27017
|
-
if (typeof payload.sub !== "string" || payload.sub.length === 0) {
|
|
27018
|
-
throw new Error();
|
|
27019
|
-
}
|
|
27020
|
-
return payload.sub;
|
|
27021
|
-
} catch {
|
|
27022
|
-
throw new Error("Glossa could not identify the signed-in account.");
|
|
27023
|
-
}
|
|
27024
|
-
}
|
|
27025
27010
|
function sessionExpiredError() {
|
|
27026
27011
|
return new SessionExpiredError();
|
|
27027
27012
|
}
|
|
@@ -27162,11 +27147,10 @@ async function postForm(fetchRequest, url2, values, signal) {
|
|
|
27162
27147
|
}
|
|
27163
27148
|
return data;
|
|
27164
27149
|
}
|
|
27165
|
-
async function
|
|
27150
|
+
async function authorizeWithDeviceFlow(options, dependencies = {}) {
|
|
27166
27151
|
const fetchRequest = dependencies.fetch ?? fetch;
|
|
27167
27152
|
const wait = dependencies.delay ?? defaultDelay;
|
|
27168
27153
|
const browse = dependencies.openBrowser ?? openBrowser;
|
|
27169
|
-
const save = dependencies.saveCredentials ?? saveCredentials;
|
|
27170
27154
|
const now = dependencies.now ?? Date.now;
|
|
27171
27155
|
const log = dependencies.log ?? console.log;
|
|
27172
27156
|
try {
|
|
@@ -27204,26 +27188,25 @@ async function loginWithDeviceFlow(options, dependencies = {}) {
|
|
|
27204
27188
|
});
|
|
27205
27189
|
const data = await response.json();
|
|
27206
27190
|
if (response.ok && "access_token" in data) {
|
|
27207
|
-
if (!data.refresh_token) {
|
|
27208
|
-
throw new Error("Auth0 did not issue a refresh token.");
|
|
27209
|
-
}
|
|
27210
27191
|
const grantedScope = data.scope ?? options.scope;
|
|
27211
|
-
if (!grantedScopesSatisfyRequest(
|
|
27192
|
+
if (!grantedScopesSatisfyRequest(
|
|
27193
|
+
grantedScope,
|
|
27194
|
+
options.scope,
|
|
27195
|
+
Boolean(data.refresh_token)
|
|
27196
|
+
)) {
|
|
27212
27197
|
throw new Error("Auth0 did not grant the permissions Glossa requires.");
|
|
27213
27198
|
}
|
|
27214
|
-
|
|
27199
|
+
return {
|
|
27215
27200
|
issuer: options.issuer,
|
|
27216
27201
|
clientId: options.clientId,
|
|
27217
27202
|
audience: options.audience,
|
|
27218
27203
|
accessToken: data.access_token,
|
|
27219
|
-
refreshToken: data.refresh_token,
|
|
27204
|
+
...data.refresh_token ? { refreshToken: data.refresh_token } : {},
|
|
27220
27205
|
expiresAt: new Date(now() + data.expires_in * 1e3).toISOString(),
|
|
27221
27206
|
tokenType: data.token_type,
|
|
27222
27207
|
scope: grantedScope,
|
|
27223
27208
|
requestedScope: options.scope
|
|
27224
|
-
}
|
|
27225
|
-
log("Signed in to Glossa.");
|
|
27226
|
-
return;
|
|
27209
|
+
};
|
|
27227
27210
|
}
|
|
27228
27211
|
const error46 = data;
|
|
27229
27212
|
if (error46.error === "authorization_pending") continue;
|
|
@@ -27243,6 +27226,15 @@ async function loginWithDeviceFlow(options, dependencies = {}) {
|
|
|
27243
27226
|
throw error46;
|
|
27244
27227
|
}
|
|
27245
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
|
+
}
|
|
27246
27238
|
|
|
27247
27239
|
// src/auth-login.ts
|
|
27248
27240
|
function normalizedIssuer(value) {
|
|
@@ -28014,10 +28006,10 @@ function mergeDefs(...defs) {
|
|
|
28014
28006
|
function cloneDef(schema) {
|
|
28015
28007
|
return mergeDefs(schema._zod.def);
|
|
28016
28008
|
}
|
|
28017
|
-
function getElementAtPath(obj,
|
|
28018
|
-
if (!
|
|
28009
|
+
function getElementAtPath(obj, path14) {
|
|
28010
|
+
if (!path14)
|
|
28019
28011
|
return obj;
|
|
28020
|
-
return
|
|
28012
|
+
return path14.reduce((acc, key) => acc?.[key], obj);
|
|
28021
28013
|
}
|
|
28022
28014
|
function promiseAllObject(promisesObj) {
|
|
28023
28015
|
const keys = Object.keys(promisesObj);
|
|
@@ -28378,11 +28370,11 @@ function aborted(x, startIndex = 0) {
|
|
|
28378
28370
|
}
|
|
28379
28371
|
return false;
|
|
28380
28372
|
}
|
|
28381
|
-
function prefixIssues(
|
|
28373
|
+
function prefixIssues(path14, issues) {
|
|
28382
28374
|
return issues.map((iss) => {
|
|
28383
28375
|
var _a;
|
|
28384
28376
|
(_a = iss).path ?? (_a.path = []);
|
|
28385
|
-
iss.path.unshift(
|
|
28377
|
+
iss.path.unshift(path14);
|
|
28386
28378
|
return iss;
|
|
28387
28379
|
});
|
|
28388
28380
|
}
|
|
@@ -28544,7 +28536,7 @@ function formatError(error46, mapper = (issue2) => issue2.message) {
|
|
|
28544
28536
|
}
|
|
28545
28537
|
function treeifyError(error46, mapper = (issue2) => issue2.message) {
|
|
28546
28538
|
const result = { errors: [] };
|
|
28547
|
-
const processError = (error47,
|
|
28539
|
+
const processError = (error47, path14 = []) => {
|
|
28548
28540
|
var _a, _b;
|
|
28549
28541
|
for (const issue2 of error47.issues) {
|
|
28550
28542
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
@@ -28554,7 +28546,7 @@ function treeifyError(error46, mapper = (issue2) => issue2.message) {
|
|
|
28554
28546
|
} else if (issue2.code === "invalid_element") {
|
|
28555
28547
|
processError({ issues: issue2.issues }, issue2.path);
|
|
28556
28548
|
} else {
|
|
28557
|
-
const fullpath = [...
|
|
28549
|
+
const fullpath = [...path14, ...issue2.path];
|
|
28558
28550
|
if (fullpath.length === 0) {
|
|
28559
28551
|
result.errors.push(mapper(issue2));
|
|
28560
28552
|
continue;
|
|
@@ -28586,8 +28578,8 @@ function treeifyError(error46, mapper = (issue2) => issue2.message) {
|
|
|
28586
28578
|
}
|
|
28587
28579
|
function toDotPath(_path) {
|
|
28588
28580
|
const segs = [];
|
|
28589
|
-
const
|
|
28590
|
-
for (const seg of
|
|
28581
|
+
const path14 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
28582
|
+
for (const seg of path14) {
|
|
28591
28583
|
if (typeof seg === "number")
|
|
28592
28584
|
segs.push(`[${seg}]`);
|
|
28593
28585
|
else if (typeof seg === "symbol")
|
|
@@ -40271,6 +40263,10 @@ function parseInvocation(args) {
|
|
|
40271
40263
|
noOptions("Logout", options);
|
|
40272
40264
|
return { command };
|
|
40273
40265
|
}
|
|
40266
|
+
if (command === "unpair") {
|
|
40267
|
+
noOptions("Unpair", options);
|
|
40268
|
+
return { command };
|
|
40269
|
+
}
|
|
40274
40270
|
if (command === "update") return parseUpdate(options);
|
|
40275
40271
|
return parseWorkspace(args);
|
|
40276
40272
|
}
|
|
@@ -40322,7 +40318,7 @@ async function logoutFromGlossa(dependencies = {}) {
|
|
|
40322
40318
|
const issuer = dependencies.issuer ?? stored?.credentials.issuer;
|
|
40323
40319
|
await remove();
|
|
40324
40320
|
log(
|
|
40325
|
-
present ? "Signed out of Glossa." : "Already signed out of Glossa."
|
|
40321
|
+
present ? "Signed out of the Glossa account. Computer pairing stays active." : "Already signed out of the Glossa account. Computer pairing stays active."
|
|
40326
40322
|
);
|
|
40327
40323
|
const url2 = browserLogoutUrl(issuer ?? loadAuthConfig().issuer);
|
|
40328
40324
|
const opened = await browse(url2);
|
|
@@ -40434,12 +40430,6 @@ async function listDevices(endpoints, credentials, fetchRequest = fetch) {
|
|
|
40434
40430
|
if (!response.ok) throw relayError(response.status, data);
|
|
40435
40431
|
return parseDevices2(data.devices);
|
|
40436
40432
|
}
|
|
40437
|
-
async function accountOwnsDevice(endpoints, credentials, deviceId, fetchRequest = fetch) {
|
|
40438
|
-
const devices = await listDevices(endpoints, credentials, fetchRequest);
|
|
40439
|
-
return devices.some(
|
|
40440
|
-
(device) => device.id === deviceId && device.revokedAt === null
|
|
40441
|
-
);
|
|
40442
|
-
}
|
|
40443
40433
|
async function enrollDevice(endpoints, credentials, deviceName, fetchRequest = fetch) {
|
|
40444
40434
|
const baseName = deviceNameSchema.parse(deviceName);
|
|
40445
40435
|
let name = baseName;
|
|
@@ -40484,6 +40474,17 @@ async function enrollDevice(endpoints, credentials, deviceName, fetchRequest = f
|
|
|
40484
40474
|
}
|
|
40485
40475
|
throw new Error("Glossa could not enroll this computer.");
|
|
40486
40476
|
}
|
|
40477
|
+
async function revokePairedDevice(endpoints, device, fetchRequest = fetch) {
|
|
40478
|
+
const response = await fetchRequest(`${endpoints.relayOrigin}/device`, {
|
|
40479
|
+
method: "DELETE",
|
|
40480
|
+
headers: { authorization: `Device ${device.token}` }
|
|
40481
|
+
});
|
|
40482
|
+
if (response.ok || response.status === 401 || response.status === 404) return;
|
|
40483
|
+
if (response.status === 429) {
|
|
40484
|
+
throw new Error("Glossa device authentication is rate limited. Try again later.");
|
|
40485
|
+
}
|
|
40486
|
+
throw new Error(`The Glossa relay returned HTTP ${response.status}.`);
|
|
40487
|
+
}
|
|
40487
40488
|
async function revokeDevice(endpoints, credentials, deviceId, fetchRequest = fetch) {
|
|
40488
40489
|
const response = await fetchRequest(`${endpoints.relayOrigin}/v1/devices/${encodeURIComponent(deviceId)}`, {
|
|
40489
40490
|
method: "DELETE",
|
|
@@ -47670,8 +47671,8 @@ function Text({ color, backgroundColor, dimColor = false, bold = false, italic =
|
|
|
47670
47671
|
}
|
|
47671
47672
|
|
|
47672
47673
|
// ../../node_modules/ink/build/components/ErrorOverview.js
|
|
47673
|
-
var cleanupPath = (
|
|
47674
|
-
return
|
|
47674
|
+
var cleanupPath = (path14) => {
|
|
47675
|
+
return path14?.replace(`file://${cwd()}/`, "");
|
|
47675
47676
|
};
|
|
47676
47677
|
var stackUtils = new import_stack_utils.default({
|
|
47677
47678
|
cwd: cwd(),
|
|
@@ -47845,7 +47846,7 @@ function App({ children, stdin, stdout, stderr, writeToStdout, writeToStderr, ex
|
|
|
47845
47846
|
for (const subscriber of animationSubscribersRef.current.values()) {
|
|
47846
47847
|
nextDueTime = Math.min(nextDueTime, subscriber.nextDueTime);
|
|
47847
47848
|
}
|
|
47848
|
-
const
|
|
47849
|
+
const delay5 = Math.max(0, nextDueTime - performance.now());
|
|
47849
47850
|
animationTimerRef.current = setTimeout(() => {
|
|
47850
47851
|
animationTimerRef.current = void 0;
|
|
47851
47852
|
const currentTime = performance.now();
|
|
@@ -47859,7 +47860,7 @@ function App({ children, stdin, stdout, stderr, writeToStdout, writeToStderr, ex
|
|
|
47859
47860
|
subscriber.nextDueTime = subscriber.startTime + elapsedFrames * subscriber.interval;
|
|
47860
47861
|
}
|
|
47861
47862
|
scheduleAnimationTick();
|
|
47862
|
-
},
|
|
47863
|
+
}, delay5);
|
|
47863
47864
|
}, [clearAnimationTimer]);
|
|
47864
47865
|
const animationSubscribe = (0, import_react15.useCallback)((callback, interval) => {
|
|
47865
47866
|
const startTime = performance.now();
|
|
@@ -49799,7 +49800,7 @@ function connectHintStore(directory = configDirectory()) {
|
|
|
49799
49800
|
}
|
|
49800
49801
|
async function announceConnectHint(store3, log) {
|
|
49801
49802
|
if (await store3.exists()) return false;
|
|
49802
|
-
log(`Next:
|
|
49803
|
+
log(`Next: return to ChatGPT and use Glossa. If it is not connected yet, follow ${CONNECT_HINT_URL}.`);
|
|
49803
49804
|
await store3.mark();
|
|
49804
49805
|
return true;
|
|
49805
49806
|
}
|
|
@@ -49845,11 +49846,294 @@ async function deleteDeviceCredential() {
|
|
|
49845
49846
|
await store2.delete();
|
|
49846
49847
|
}
|
|
49847
49848
|
|
|
49849
|
+
// src/device-pairing-lock.ts
|
|
49850
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
49851
|
+
import { mkdir as mkdir4, readFile as readFile3, rm as rm3, stat, writeFile as writeFile4 } from "node:fs/promises";
|
|
49852
|
+
import path6 from "node:path";
|
|
49853
|
+
import { setTimeout as delay2 } from "node:timers/promises";
|
|
49854
|
+
|
|
49855
|
+
// src/update-lock.ts
|
|
49856
|
+
import { randomUUID } from "node:crypto";
|
|
49857
|
+
import { mkdir as mkdir3, readdir, rm as rm2, writeFile as writeFile3 } from "node:fs/promises";
|
|
49858
|
+
import path5 from "node:path";
|
|
49859
|
+
var UPDATE_SUFFIX = ".update";
|
|
49860
|
+
var SESSION_SUFFIX = ".session";
|
|
49861
|
+
function updateRuntimeDirectory() {
|
|
49862
|
+
return path5.join(configDirectory(), "runtime");
|
|
49863
|
+
}
|
|
49864
|
+
function processIsAlive(pid) {
|
|
49865
|
+
if (!Number.isSafeInteger(pid) || pid <= 0) return false;
|
|
49866
|
+
try {
|
|
49867
|
+
process.kill(pid, 0);
|
|
49868
|
+
return true;
|
|
49869
|
+
} catch (error46) {
|
|
49870
|
+
return error46.code === "EPERM";
|
|
49871
|
+
}
|
|
49872
|
+
}
|
|
49873
|
+
function leasePid(name, suffix) {
|
|
49874
|
+
if (!name.endsWith(suffix)) return null;
|
|
49875
|
+
const separator = name.indexOf("-");
|
|
49876
|
+
if (separator <= 0) return null;
|
|
49877
|
+
const pid = Number(name.slice(0, separator));
|
|
49878
|
+
return Number.isSafeInteger(pid) && pid > 0 ? pid : null;
|
|
49879
|
+
}
|
|
49880
|
+
async function activeLeaseFiles(directory, suffix) {
|
|
49881
|
+
let entries;
|
|
49882
|
+
try {
|
|
49883
|
+
entries = await readdir(directory, { withFileTypes: true });
|
|
49884
|
+
} catch (error46) {
|
|
49885
|
+
if (error46.code === "ENOENT") return [];
|
|
49886
|
+
throw error46;
|
|
49887
|
+
}
|
|
49888
|
+
const active = [];
|
|
49889
|
+
for (const entry of entries) {
|
|
49890
|
+
if (!entry.isFile() || !entry.name.endsWith(suffix)) continue;
|
|
49891
|
+
const pid = leasePid(entry.name, suffix);
|
|
49892
|
+
const file2 = path5.join(directory, entry.name);
|
|
49893
|
+
if (pid !== null && processIsAlive(pid)) active.push(file2);
|
|
49894
|
+
else await rm2(file2, { force: true });
|
|
49895
|
+
}
|
|
49896
|
+
return active;
|
|
49897
|
+
}
|
|
49898
|
+
async function updateLockIsActive(directory) {
|
|
49899
|
+
return (await activeLeaseFiles(directory, UPDATE_SUFFIX)).length > 0;
|
|
49900
|
+
}
|
|
49901
|
+
async function activeSessionFiles(directory) {
|
|
49902
|
+
return await activeLeaseFiles(directory, SESSION_SUFFIX);
|
|
49903
|
+
}
|
|
49904
|
+
async function createUpdateLock(directory) {
|
|
49905
|
+
await mkdir3(directory, { recursive: true, mode: 448 });
|
|
49906
|
+
const lockFile = path5.join(
|
|
49907
|
+
directory,
|
|
49908
|
+
`${process.pid}-${randomUUID()}${UPDATE_SUFFIX}`
|
|
49909
|
+
);
|
|
49910
|
+
const content = `${JSON.stringify({ pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() })}
|
|
49911
|
+
`;
|
|
49912
|
+
await writeFile3(lockFile, content, {
|
|
49913
|
+
encoding: "utf8",
|
|
49914
|
+
flag: "wx",
|
|
49915
|
+
mode: 384
|
|
49916
|
+
});
|
|
49917
|
+
try {
|
|
49918
|
+
const contenders = await activeLeaseFiles(directory, UPDATE_SUFFIX);
|
|
49919
|
+
if (contenders.some((file2) => file2 !== lockFile)) {
|
|
49920
|
+
throw new Error("Another Glossa update is already running.");
|
|
49921
|
+
}
|
|
49922
|
+
return lockFile;
|
|
49923
|
+
} catch (error46) {
|
|
49924
|
+
await rm2(lockFile, { force: true });
|
|
49925
|
+
throw error46;
|
|
49926
|
+
}
|
|
49927
|
+
}
|
|
49928
|
+
async function withUpdateLease(action, directory = updateRuntimeDirectory()) {
|
|
49929
|
+
const lockFile = await createUpdateLock(directory);
|
|
49930
|
+
try {
|
|
49931
|
+
if ((await activeSessionFiles(directory)).length > 0) {
|
|
49932
|
+
throw new Error("Disconnect every running Glossa workspace before updating.");
|
|
49933
|
+
}
|
|
49934
|
+
return await action();
|
|
49935
|
+
} finally {
|
|
49936
|
+
await rm2(lockFile, { force: true });
|
|
49937
|
+
}
|
|
49938
|
+
}
|
|
49939
|
+
async function withWorkspaceLease(action, directory = updateRuntimeDirectory()) {
|
|
49940
|
+
await mkdir3(directory, { recursive: true, mode: 448 });
|
|
49941
|
+
if (await updateLockIsActive(directory)) {
|
|
49942
|
+
throw new Error("Glossa is updating. Run this workspace again after the update finishes.");
|
|
49943
|
+
}
|
|
49944
|
+
const leaseFile = path5.join(
|
|
49945
|
+
directory,
|
|
49946
|
+
`${process.pid}-${randomUUID()}${SESSION_SUFFIX}`
|
|
49947
|
+
);
|
|
49948
|
+
await writeFile3(
|
|
49949
|
+
leaseFile,
|
|
49950
|
+
`${JSON.stringify({ pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() })}
|
|
49951
|
+
`,
|
|
49952
|
+
{ encoding: "utf8", flag: "wx", mode: 384 }
|
|
49953
|
+
);
|
|
49954
|
+
try {
|
|
49955
|
+
if (await updateLockIsActive(directory)) {
|
|
49956
|
+
throw new Error("Glossa is updating. Run this workspace again after the update finishes.");
|
|
49957
|
+
}
|
|
49958
|
+
return await action();
|
|
49959
|
+
} finally {
|
|
49960
|
+
await rm2(leaseFile, { force: true });
|
|
49961
|
+
}
|
|
49962
|
+
}
|
|
49963
|
+
|
|
49964
|
+
// src/device-pairing-lock.ts
|
|
49965
|
+
var DEVICE_PAIRING_LOCK = "device-pairing.lock";
|
|
49966
|
+
var DEVICE_PAIRING_LOCK_GUARD = "device-pairing.lock.guard";
|
|
49967
|
+
var DEVICE_PAIRING_LOCK_POLL_MS = 100;
|
|
49968
|
+
var DEVICE_PAIRING_LOCK_MAX_AGE_MS = 6 * 6e4;
|
|
49969
|
+
var DEVICE_PAIRING_GUARD_MAX_AGE_MS = 3e4;
|
|
49970
|
+
function lockPath(directory) {
|
|
49971
|
+
return path6.join(directory, DEVICE_PAIRING_LOCK);
|
|
49972
|
+
}
|
|
49973
|
+
function guardPath(directory) {
|
|
49974
|
+
return path6.join(directory, DEVICE_PAIRING_LOCK_GUARD);
|
|
49975
|
+
}
|
|
49976
|
+
function newOwner() {
|
|
49977
|
+
return {
|
|
49978
|
+
pid: process.pid,
|
|
49979
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
49980
|
+
token: randomUUID2()
|
|
49981
|
+
};
|
|
49982
|
+
}
|
|
49983
|
+
async function readOwner(file2) {
|
|
49984
|
+
try {
|
|
49985
|
+
const value = JSON.parse(await readFile3(file2, "utf8"));
|
|
49986
|
+
if (!Number.isSafeInteger(value.pid) || typeof value.startedAt !== "string" || !Number.isFinite(Date.parse(value.startedAt)) || typeof value.token !== "string" || value.token.length === 0) {
|
|
49987
|
+
return void 0;
|
|
49988
|
+
}
|
|
49989
|
+
return value;
|
|
49990
|
+
} catch (error46) {
|
|
49991
|
+
if (error46.code === "ENOENT") return void 0;
|
|
49992
|
+
return void 0;
|
|
49993
|
+
}
|
|
49994
|
+
}
|
|
49995
|
+
async function lockIsStale(file2, maxAgeMs) {
|
|
49996
|
+
const owner = await readOwner(file2);
|
|
49997
|
+
if (!owner) {
|
|
49998
|
+
try {
|
|
49999
|
+
const metadata = await stat(file2);
|
|
50000
|
+
return Date.now() - metadata.mtimeMs > maxAgeMs;
|
|
50001
|
+
} catch (error46) {
|
|
50002
|
+
if (error46.code === "ENOENT") return false;
|
|
50003
|
+
return true;
|
|
50004
|
+
}
|
|
50005
|
+
}
|
|
50006
|
+
const ageMs = Date.now() - Date.parse(owner.startedAt);
|
|
50007
|
+
return ageMs > maxAgeMs || !processIsAlive(owner.pid);
|
|
50008
|
+
}
|
|
50009
|
+
async function acquireGuard(directory, signal) {
|
|
50010
|
+
const file2 = guardPath(directory);
|
|
50011
|
+
for (; ; ) {
|
|
50012
|
+
signal?.throwIfAborted();
|
|
50013
|
+
const owner = newOwner();
|
|
50014
|
+
try {
|
|
50015
|
+
await writeFile4(file2, `${JSON.stringify(owner)}
|
|
50016
|
+
`, {
|
|
50017
|
+
encoding: "utf8",
|
|
50018
|
+
flag: "wx",
|
|
50019
|
+
mode: 384
|
|
50020
|
+
});
|
|
50021
|
+
return { file: file2, token: owner.token };
|
|
50022
|
+
} catch (error46) {
|
|
50023
|
+
if (error46.code !== "EEXIST") throw error46;
|
|
50024
|
+
}
|
|
50025
|
+
if (await lockIsStale(file2, DEVICE_PAIRING_GUARD_MAX_AGE_MS)) {
|
|
50026
|
+
await rm3(file2, { force: true });
|
|
50027
|
+
continue;
|
|
50028
|
+
}
|
|
50029
|
+
await delay2(
|
|
50030
|
+
DEVICE_PAIRING_LOCK_POLL_MS,
|
|
50031
|
+
void 0,
|
|
50032
|
+
signal ? { signal } : void 0
|
|
50033
|
+
);
|
|
50034
|
+
}
|
|
50035
|
+
}
|
|
50036
|
+
async function removeOwnedLock(file2, token) {
|
|
50037
|
+
const owner = await readOwner(file2);
|
|
50038
|
+
if (owner?.token === token) await rm3(file2, { force: true });
|
|
50039
|
+
}
|
|
50040
|
+
async function withGuard(directory, action, signal) {
|
|
50041
|
+
const guard = await acquireGuard(directory, signal);
|
|
50042
|
+
try {
|
|
50043
|
+
return await action();
|
|
50044
|
+
} finally {
|
|
50045
|
+
await removeOwnedLock(guard.file, guard.token);
|
|
50046
|
+
}
|
|
50047
|
+
}
|
|
50048
|
+
async function acquireDevicePairingLock(directory, signal) {
|
|
50049
|
+
await mkdir4(directory, { recursive: true, mode: 448 });
|
|
50050
|
+
const file2 = lockPath(directory);
|
|
50051
|
+
for (; ; ) {
|
|
50052
|
+
signal?.throwIfAborted();
|
|
50053
|
+
const owner = newOwner();
|
|
50054
|
+
try {
|
|
50055
|
+
await writeFile4(file2, `${JSON.stringify(owner)}
|
|
50056
|
+
`, {
|
|
50057
|
+
encoding: "utf8",
|
|
50058
|
+
flag: "wx",
|
|
50059
|
+
mode: 384
|
|
50060
|
+
});
|
|
50061
|
+
return { file: file2, token: owner.token };
|
|
50062
|
+
} catch (error46) {
|
|
50063
|
+
if (error46.code !== "EEXIST") throw error46;
|
|
50064
|
+
}
|
|
50065
|
+
const reclaimed = await withGuard(
|
|
50066
|
+
directory,
|
|
50067
|
+
async () => {
|
|
50068
|
+
if (!await lockIsStale(file2, DEVICE_PAIRING_LOCK_MAX_AGE_MS)) {
|
|
50069
|
+
return false;
|
|
50070
|
+
}
|
|
50071
|
+
await rm3(file2, { force: true });
|
|
50072
|
+
return true;
|
|
50073
|
+
},
|
|
50074
|
+
signal
|
|
50075
|
+
);
|
|
50076
|
+
if (reclaimed) continue;
|
|
50077
|
+
await delay2(
|
|
50078
|
+
DEVICE_PAIRING_LOCK_POLL_MS,
|
|
50079
|
+
void 0,
|
|
50080
|
+
signal ? { signal } : void 0
|
|
50081
|
+
);
|
|
50082
|
+
}
|
|
50083
|
+
}
|
|
50084
|
+
async function withDevicePairingLease(action, signal, directory = updateRuntimeDirectory()) {
|
|
50085
|
+
const lock = await acquireDevicePairingLock(directory, signal);
|
|
50086
|
+
try {
|
|
50087
|
+
return await action();
|
|
50088
|
+
} finally {
|
|
50089
|
+
await withGuard(directory, async () => await removeOwnedLock(lock.file, lock.token));
|
|
50090
|
+
}
|
|
50091
|
+
}
|
|
50092
|
+
|
|
50093
|
+
// src/device-pairing.ts
|
|
50094
|
+
function canceledError2() {
|
|
50095
|
+
return new Error("Pairing canceled.");
|
|
50096
|
+
}
|
|
50097
|
+
function pairingScope(scope) {
|
|
50098
|
+
return scope.split(/\s+/).filter((value) => value && value !== "offline_access").join(" ");
|
|
50099
|
+
}
|
|
50100
|
+
async function pairDevice(endpoints, signal, dependencies = {}) {
|
|
50101
|
+
const authorize = dependencies.authorizeWithDeviceFlow ?? authorizeWithDeviceFlow;
|
|
50102
|
+
const enroll = dependencies.enrollDevice ?? enrollDevice;
|
|
50103
|
+
const authConfig = dependencies.loadAuthConfig ?? loadAuthConfig;
|
|
50104
|
+
const name = dependencies.defaultDeviceName ?? defaultDeviceName;
|
|
50105
|
+
const log = dependencies.log ?? console.log;
|
|
50106
|
+
const baseFetch = dependencies.fetch ?? fetch;
|
|
50107
|
+
const fetchRequest = signal ? async (input, init) => await baseFetch(input, { ...init, signal }) : baseFetch;
|
|
50108
|
+
try {
|
|
50109
|
+
signal?.throwIfAborted();
|
|
50110
|
+
log("This computer needs to be paired with Glossa.");
|
|
50111
|
+
const auth = authConfig();
|
|
50112
|
+
const credentials = await authorize(
|
|
50113
|
+
{
|
|
50114
|
+
...auth,
|
|
50115
|
+
scope: pairingScope(auth.scope),
|
|
50116
|
+
...signal ? { signal } : {}
|
|
50117
|
+
},
|
|
50118
|
+
{ fetch: fetchRequest, log }
|
|
50119
|
+
);
|
|
50120
|
+
signal?.throwIfAborted();
|
|
50121
|
+
const paired = await enroll(endpoints, credentials, name(), fetchRequest);
|
|
50122
|
+
log(`Paired with Glossa as ${paired.deviceName}.`);
|
|
50123
|
+
return paired;
|
|
50124
|
+
} catch (error46) {
|
|
50125
|
+
if (signal?.aborted || error46 instanceof Error && error46.name === "AbortError") {
|
|
50126
|
+
throw canceledError2();
|
|
50127
|
+
}
|
|
50128
|
+
throw error46;
|
|
50129
|
+
}
|
|
50130
|
+
}
|
|
50131
|
+
|
|
49848
50132
|
// src/worker/command-service.ts
|
|
49849
50133
|
import { spawn as spawn2 } from "node:child_process";
|
|
49850
|
-
import { randomUUID } from "node:crypto";
|
|
50134
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
49851
50135
|
import { StringDecoder } from "node:string_decoder";
|
|
49852
|
-
import { setTimeout as
|
|
50136
|
+
import { setTimeout as delay3 } from "node:timers/promises";
|
|
49853
50137
|
|
|
49854
50138
|
// src/worker/errors.ts
|
|
49855
50139
|
var WorkerError = class extends Error {
|
|
@@ -49964,7 +50248,7 @@ async function waitForChange(record2, afterSequence, waitMs) {
|
|
|
49964
50248
|
try {
|
|
49965
50249
|
await Promise.race([
|
|
49966
50250
|
change,
|
|
49967
|
-
|
|
50251
|
+
delay3(waitMs, void 0, { signal: waitController.signal })
|
|
49968
50252
|
]);
|
|
49969
50253
|
} finally {
|
|
49970
50254
|
record2.changeWaiters.delete(changed);
|
|
@@ -50135,7 +50419,7 @@ async function terminateProcessTree(child) {
|
|
|
50135
50419
|
} catch {
|
|
50136
50420
|
child.kill("SIGTERM");
|
|
50137
50421
|
}
|
|
50138
|
-
await
|
|
50422
|
+
await delay3(2e3);
|
|
50139
50423
|
if (child.exitCode === null) {
|
|
50140
50424
|
try {
|
|
50141
50425
|
process.kill(-child.pid, "SIGKILL");
|
|
@@ -50228,7 +50512,7 @@ var CommandService = class {
|
|
|
50228
50512
|
const completion = new Promise((resolve) => {
|
|
50229
50513
|
complete = resolve;
|
|
50230
50514
|
});
|
|
50231
|
-
const id =
|
|
50515
|
+
const id = randomUUID3();
|
|
50232
50516
|
const record2 = {
|
|
50233
50517
|
id,
|
|
50234
50518
|
child,
|
|
@@ -50300,7 +50584,7 @@ var CommandService = class {
|
|
|
50300
50584
|
try {
|
|
50301
50585
|
await Promise.race([
|
|
50302
50586
|
record2.completion,
|
|
50303
|
-
|
|
50587
|
+
delay3(waitMs, void 0, { signal: waitController.signal })
|
|
50304
50588
|
]);
|
|
50305
50589
|
} finally {
|
|
50306
50590
|
waitController.abort();
|
|
@@ -50326,7 +50610,7 @@ var CommandService = class {
|
|
|
50326
50610
|
try {
|
|
50327
50611
|
await Promise.race([
|
|
50328
50612
|
record2.completion,
|
|
50329
|
-
|
|
50613
|
+
delay3(waitMs, void 0, { signal: waitController.signal })
|
|
50330
50614
|
]);
|
|
50331
50615
|
} finally {
|
|
50332
50616
|
waitController.abort();
|
|
@@ -50428,29 +50712,29 @@ var CommandService = class {
|
|
|
50428
50712
|
};
|
|
50429
50713
|
|
|
50430
50714
|
// src/worker/file-service.ts
|
|
50431
|
-
import { createHash, randomUUID as
|
|
50715
|
+
import { createHash, randomUUID as randomUUID4 } from "node:crypto";
|
|
50432
50716
|
import {
|
|
50433
50717
|
chmod as chmod2,
|
|
50434
50718
|
link as link2,
|
|
50435
50719
|
lstat as lstat2,
|
|
50436
|
-
mkdir as
|
|
50720
|
+
mkdir as mkdir5,
|
|
50437
50721
|
opendir,
|
|
50438
50722
|
open,
|
|
50439
|
-
readFile as
|
|
50723
|
+
readFile as readFile4,
|
|
50440
50724
|
rename,
|
|
50441
|
-
rm as
|
|
50725
|
+
rm as rm4,
|
|
50442
50726
|
rmdir,
|
|
50443
|
-
stat as
|
|
50444
|
-
writeFile as
|
|
50727
|
+
stat as stat3,
|
|
50728
|
+
writeFile as writeFile5
|
|
50445
50729
|
} from "node:fs/promises";
|
|
50446
|
-
import
|
|
50730
|
+
import path8 from "node:path";
|
|
50447
50731
|
import { performance as performance2 } from "node:perf_hooks";
|
|
50448
50732
|
import { StringDecoder as StringDecoder2 } from "node:string_decoder";
|
|
50449
50733
|
|
|
50450
50734
|
// src/worker/path-policy.ts
|
|
50451
|
-
import { lstat, realpath, stat } from "node:fs/promises";
|
|
50735
|
+
import { lstat, realpath, stat as stat2 } from "node:fs/promises";
|
|
50452
50736
|
import os5 from "node:os";
|
|
50453
|
-
import
|
|
50737
|
+
import path7 from "node:path";
|
|
50454
50738
|
function samePath(left, right) {
|
|
50455
50739
|
return process.platform === "win32" ? left.toLowerCase() === right.toLowerCase() : left === right;
|
|
50456
50740
|
}
|
|
@@ -50462,15 +50746,15 @@ function accountHomeDirectory() {
|
|
|
50462
50746
|
}
|
|
50463
50747
|
}
|
|
50464
50748
|
function isWithin(root, candidate) {
|
|
50465
|
-
const relative =
|
|
50466
|
-
return relative === "" || !relative.startsWith(`..${
|
|
50749
|
+
const relative = path7.relative(root, candidate);
|
|
50750
|
+
return relative === "" || !relative.startsWith(`..${path7.sep}`) && relative !== ".." && !path7.isAbsolute(relative);
|
|
50467
50751
|
}
|
|
50468
50752
|
function validateRelativePath(value) {
|
|
50469
50753
|
if (value.includes("\0")) {
|
|
50470
50754
|
throw new WorkerError("invalid_path", "Paths cannot contain null bytes.");
|
|
50471
50755
|
}
|
|
50472
50756
|
const explicitNativePosixPath = process.platform !== "win32" && value.startsWith("./");
|
|
50473
|
-
if (
|
|
50757
|
+
if (path7.isAbsolute(value) || path7.posix.isAbsolute(value) || !explicitNativePosixPath && path7.win32.isAbsolute(value)) {
|
|
50474
50758
|
throw new WorkerError("absolute_path", "Absolute paths are not allowed.");
|
|
50475
50759
|
}
|
|
50476
50760
|
const segments = explicitNativePosixPath ? value.split(/\/+/).filter(Boolean) : value.split(/[\\/]+/);
|
|
@@ -50480,20 +50764,20 @@ function validateRelativePath(value) {
|
|
|
50480
50764
|
return value === "" ? "." : value;
|
|
50481
50765
|
}
|
|
50482
50766
|
async function canonicalizeRoot(candidate) {
|
|
50483
|
-
const root = await realpath(
|
|
50767
|
+
const root = await realpath(path7.resolve(candidate)).catch((error46) => {
|
|
50484
50768
|
if (error46.code === "ENOENT") {
|
|
50485
50769
|
throw new WorkerError("root_not_found", "The workspace directory does not exist.");
|
|
50486
50770
|
}
|
|
50487
50771
|
throw error46;
|
|
50488
50772
|
});
|
|
50489
|
-
const rootStat = await
|
|
50773
|
+
const rootStat = await stat2(root);
|
|
50490
50774
|
if (!rootStat.isDirectory()) {
|
|
50491
50775
|
throw new WorkerError("root_not_directory", "The exposed root must be a directory.");
|
|
50492
50776
|
}
|
|
50493
|
-
const filesystemRoot =
|
|
50777
|
+
const filesystemRoot = path7.parse(root).root;
|
|
50494
50778
|
const homes = await Promise.all(
|
|
50495
50779
|
[os5.homedir(), accountHomeDirectory()].map(
|
|
50496
|
-
async (home) => await realpath(home).catch(() =>
|
|
50780
|
+
async (home) => await realpath(home).catch(() => path7.resolve(home))
|
|
50497
50781
|
)
|
|
50498
50782
|
);
|
|
50499
50783
|
const isHomeDirectory = homes.some((home) => samePath(root, home));
|
|
@@ -50529,7 +50813,7 @@ var PathPolicy = class _PathPolicy {
|
|
|
50529
50813
|
return canonical;
|
|
50530
50814
|
}
|
|
50531
50815
|
async resolveDiscoveredExisting(candidate) {
|
|
50532
|
-
const lexical =
|
|
50816
|
+
const lexical = path7.resolve(candidate);
|
|
50533
50817
|
if (!isWithin(this.root, lexical)) {
|
|
50534
50818
|
throw new WorkerError("path_escape", "The requested path escapes the exposed root.");
|
|
50535
50819
|
}
|
|
@@ -50547,7 +50831,7 @@ var PathPolicy = class _PathPolicy {
|
|
|
50547
50831
|
}
|
|
50548
50832
|
async resolveWritableFile(relativePath) {
|
|
50549
50833
|
const lexical = this.resolveLexical(relativePath);
|
|
50550
|
-
const parent =
|
|
50834
|
+
const parent = path7.dirname(lexical);
|
|
50551
50835
|
await this.rejectLinkedComponents(parent);
|
|
50552
50836
|
const canonicalParent = await realpath(parent).catch((error46) => {
|
|
50553
50837
|
if (error46.code === "ENOENT") {
|
|
@@ -50558,7 +50842,7 @@ var PathPolicy = class _PathPolicy {
|
|
|
50558
50842
|
if (!isWithin(this.root, canonicalParent)) {
|
|
50559
50843
|
throw new WorkerError("path_escape", "The destination escapes the exposed root.");
|
|
50560
50844
|
}
|
|
50561
|
-
if (!(await
|
|
50845
|
+
if (!(await stat2(canonicalParent)).isDirectory()) {
|
|
50562
50846
|
throw new WorkerError("not_directory", "The destination parent is not a directory.");
|
|
50563
50847
|
}
|
|
50564
50848
|
try {
|
|
@@ -50578,7 +50862,7 @@ var PathPolicy = class _PathPolicy {
|
|
|
50578
50862
|
if (error46 instanceof WorkerError) throw error46;
|
|
50579
50863
|
if (error46.code !== "ENOENT") throw error46;
|
|
50580
50864
|
}
|
|
50581
|
-
return
|
|
50865
|
+
return path7.join(canonicalParent, path7.basename(lexical));
|
|
50582
50866
|
}
|
|
50583
50867
|
async resolveWritableDirectory(relativePath, recursive) {
|
|
50584
50868
|
const lexical = this.resolveLexical(relativePath);
|
|
@@ -50601,7 +50885,7 @@ var PathPolicy = class _PathPolicy {
|
|
|
50601
50885
|
if (error46.code !== "ENOENT") throw error46;
|
|
50602
50886
|
}
|
|
50603
50887
|
if (!recursive) {
|
|
50604
|
-
const parent =
|
|
50888
|
+
const parent = path7.dirname(lexical);
|
|
50605
50889
|
await this.rejectLinkedComponents(parent);
|
|
50606
50890
|
const canonicalParent = await realpath(parent).catch(
|
|
50607
50891
|
(error46) => {
|
|
@@ -50617,15 +50901,15 @@ var PathPolicy = class _PathPolicy {
|
|
|
50617
50901
|
if (!isWithin(this.root, canonicalParent)) {
|
|
50618
50902
|
throw new WorkerError("path_escape", "The destination escapes the exposed root.");
|
|
50619
50903
|
}
|
|
50620
|
-
if (!(await
|
|
50904
|
+
if (!(await stat2(canonicalParent)).isDirectory()) {
|
|
50621
50905
|
throw new WorkerError("not_directory", "The destination parent is not a directory.");
|
|
50622
50906
|
}
|
|
50623
50907
|
return {
|
|
50624
|
-
target:
|
|
50908
|
+
target: path7.join(canonicalParent, path7.basename(lexical)),
|
|
50625
50909
|
exists: false
|
|
50626
50910
|
};
|
|
50627
50911
|
}
|
|
50628
|
-
let existingAncestor =
|
|
50912
|
+
let existingAncestor = path7.dirname(lexical);
|
|
50629
50913
|
while (!samePath(existingAncestor, this.root)) {
|
|
50630
50914
|
try {
|
|
50631
50915
|
await this.rejectLinkedComponents(existingAncestor);
|
|
@@ -50633,13 +50917,13 @@ var PathPolicy = class _PathPolicy {
|
|
|
50633
50917
|
if (!isWithin(this.root, canonicalAncestor)) {
|
|
50634
50918
|
throw new WorkerError("path_escape", "The destination escapes the exposed root.");
|
|
50635
50919
|
}
|
|
50636
|
-
if (!(await
|
|
50920
|
+
if (!(await stat2(canonicalAncestor)).isDirectory()) {
|
|
50637
50921
|
throw new WorkerError("not_directory", "The destination parent is not a directory.");
|
|
50638
50922
|
}
|
|
50639
50923
|
return {
|
|
50640
|
-
target:
|
|
50924
|
+
target: path7.join(
|
|
50641
50925
|
canonicalAncestor,
|
|
50642
|
-
|
|
50926
|
+
path7.relative(existingAncestor, lexical)
|
|
50643
50927
|
),
|
|
50644
50928
|
exists: false
|
|
50645
50929
|
};
|
|
@@ -50647,13 +50931,13 @@ var PathPolicy = class _PathPolicy {
|
|
|
50647
50931
|
if (error46 instanceof WorkerError) throw error46;
|
|
50648
50932
|
if (error46.code !== "ENOENT") throw error46;
|
|
50649
50933
|
}
|
|
50650
|
-
existingAncestor =
|
|
50934
|
+
existingAncestor = path7.dirname(existingAncestor);
|
|
50651
50935
|
}
|
|
50652
50936
|
return { target: lexical, exists: false };
|
|
50653
50937
|
}
|
|
50654
50938
|
async resolveVacantPath(relativePath) {
|
|
50655
50939
|
const lexical = this.resolveLexical(relativePath);
|
|
50656
|
-
const parent =
|
|
50940
|
+
const parent = path7.dirname(lexical);
|
|
50657
50941
|
await this.rejectLinkedComponents(parent);
|
|
50658
50942
|
const canonicalParent = await realpath(parent).catch(
|
|
50659
50943
|
(error46) => {
|
|
@@ -50669,7 +50953,7 @@ var PathPolicy = class _PathPolicy {
|
|
|
50669
50953
|
if (!isWithin(this.root, canonicalParent)) {
|
|
50670
50954
|
throw new WorkerError("path_escape", "The destination escapes the exposed root.");
|
|
50671
50955
|
}
|
|
50672
|
-
if (!(await
|
|
50956
|
+
if (!(await stat2(canonicalParent)).isDirectory()) {
|
|
50673
50957
|
throw new WorkerError("not_directory", "The destination parent is not a directory.");
|
|
50674
50958
|
}
|
|
50675
50959
|
try {
|
|
@@ -50682,11 +50966,11 @@ var PathPolicy = class _PathPolicy {
|
|
|
50682
50966
|
if (error46 instanceof WorkerError) throw error46;
|
|
50683
50967
|
if (error46.code !== "ENOENT") throw error46;
|
|
50684
50968
|
}
|
|
50685
|
-
return
|
|
50969
|
+
return path7.join(canonicalParent, path7.basename(lexical));
|
|
50686
50970
|
}
|
|
50687
50971
|
resolveLexical(relativePath) {
|
|
50688
50972
|
const validated = validateRelativePath(relativePath);
|
|
50689
|
-
const candidate =
|
|
50973
|
+
const candidate = path7.resolve(this.root, validated);
|
|
50690
50974
|
if (!isWithin(this.root, candidate)) {
|
|
50691
50975
|
throw new WorkerError("path_escape", "The requested path escapes the exposed root.");
|
|
50692
50976
|
}
|
|
@@ -50696,11 +50980,11 @@ var PathPolicy = class _PathPolicy {
|
|
|
50696
50980
|
if (!isWithin(this.root, candidate)) {
|
|
50697
50981
|
throw new WorkerError("path_escape", "The requested path escapes the exposed root.");
|
|
50698
50982
|
}
|
|
50699
|
-
const relative =
|
|
50983
|
+
const relative = path7.relative(this.root, candidate);
|
|
50700
50984
|
if (!relative) return;
|
|
50701
50985
|
let current = this.root;
|
|
50702
|
-
for (const segment of relative.split(
|
|
50703
|
-
current =
|
|
50986
|
+
for (const segment of relative.split(path7.sep)) {
|
|
50987
|
+
current = path7.join(current, segment);
|
|
50704
50988
|
try {
|
|
50705
50989
|
const currentStat = await lstat(current);
|
|
50706
50990
|
if (currentStat.isSymbolicLink()) {
|
|
@@ -50723,12 +51007,12 @@ function sha256(content) {
|
|
|
50723
51007
|
return createHash("sha256").update(content).digest("hex");
|
|
50724
51008
|
}
|
|
50725
51009
|
function isPathWithin(root, candidate) {
|
|
50726
|
-
const relative =
|
|
50727
|
-
return relative === "" || !relative.startsWith(`..${
|
|
51010
|
+
const relative = path8.relative(root, candidate);
|
|
51011
|
+
return relative === "" || !relative.startsWith(`..${path8.sep}`) && relative !== ".." && !path8.isAbsolute(relative);
|
|
50728
51012
|
}
|
|
50729
51013
|
var fileWriteTails = /* @__PURE__ */ new Map();
|
|
50730
51014
|
async function withFileWriteLock(target, operation) {
|
|
50731
|
-
const normalized =
|
|
51015
|
+
const normalized = path8.normalize(target);
|
|
50732
51016
|
const key = process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
50733
51017
|
const predecessor = fileWriteTails.get(key);
|
|
50734
51018
|
let release;
|
|
@@ -50745,7 +51029,7 @@ async function withFileWriteLock(target, operation) {
|
|
|
50745
51029
|
}
|
|
50746
51030
|
}
|
|
50747
51031
|
async function withFileWriteLocks(targets, operation) {
|
|
50748
|
-
const unique = [...new Set(targets.map((target) =>
|
|
51032
|
+
const unique = [...new Set(targets.map((target) => path8.normalize(target)))].sort(
|
|
50749
51033
|
(left, right) => left.localeCompare(right)
|
|
50750
51034
|
);
|
|
50751
51035
|
const run = async (index) => {
|
|
@@ -50757,7 +51041,7 @@ async function withFileWriteLocks(targets, operation) {
|
|
|
50757
51041
|
async function requireRevision(target, expectedSha256) {
|
|
50758
51042
|
let actual = null;
|
|
50759
51043
|
try {
|
|
50760
|
-
actual = sha256(await
|
|
51044
|
+
actual = sha256(await readFile4(target));
|
|
50761
51045
|
} catch (error46) {
|
|
50762
51046
|
if (error46.code !== "ENOENT") throw error46;
|
|
50763
51047
|
}
|
|
@@ -50781,7 +51065,7 @@ function compareNames(left, right) {
|
|
|
50781
51065
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
50782
51066
|
}
|
|
50783
51067
|
function relativeSortKey(root, target) {
|
|
50784
|
-
const relative =
|
|
51068
|
+
const relative = path8.relative(root, target);
|
|
50785
51069
|
if (!relative) return ".";
|
|
50786
51070
|
return process.platform === "win32" ? relative.replaceAll("\\", "/") : relative;
|
|
50787
51071
|
}
|
|
@@ -51093,7 +51377,7 @@ var FileService = class {
|
|
|
51093
51377
|
}
|
|
51094
51378
|
async #readResolvedText(target, maximumBytes = MAX_TEXT_BYTES) {
|
|
51095
51379
|
const boundedMaximum = Math.min(maximumBytes, MAX_TEXT_BYTES);
|
|
51096
|
-
const targetStat = await
|
|
51380
|
+
const targetStat = await stat3(target);
|
|
51097
51381
|
if (!targetStat.isFile()) {
|
|
51098
51382
|
throw new WorkerError("not_file", "The requested path is not a file.");
|
|
51099
51383
|
}
|
|
@@ -51136,7 +51420,7 @@ var FileService = class {
|
|
|
51136
51420
|
this.policy.resolveExisting(startPath),
|
|
51137
51421
|
deadlineAt
|
|
51138
51422
|
);
|
|
51139
|
-
const startStat = await this.#withinDeadline(
|
|
51423
|
+
const startStat = await this.#withinDeadline(stat3(start), deadlineAt);
|
|
51140
51424
|
if (!startStat.isDirectory()) {
|
|
51141
51425
|
throw new WorkerError("not_directory", "The requested path is not a directory.");
|
|
51142
51426
|
}
|
|
@@ -51165,7 +51449,7 @@ var FileService = class {
|
|
|
51165
51449
|
if (batch.overflow) throw scanLimitError();
|
|
51166
51450
|
scannedEntries += batch.children.length;
|
|
51167
51451
|
for (const child of batch.children) {
|
|
51168
|
-
const target =
|
|
51452
|
+
const target = path8.join(directory, child.name);
|
|
51169
51453
|
heapPush(frontier, {
|
|
51170
51454
|
target,
|
|
51171
51455
|
sortKey: relativeSortKey(this.policy.root, target),
|
|
@@ -51189,7 +51473,7 @@ var FileService = class {
|
|
|
51189
51473
|
throw scanLimitError();
|
|
51190
51474
|
}
|
|
51191
51475
|
scannedEntries += 1;
|
|
51192
|
-
const target =
|
|
51476
|
+
const target = path8.join(directory, child.name);
|
|
51193
51477
|
let childStat;
|
|
51194
51478
|
try {
|
|
51195
51479
|
childStat = await this.#withinDeadline(
|
|
@@ -51341,7 +51625,7 @@ var FileService = class {
|
|
|
51341
51625
|
if (!patterns) return false;
|
|
51342
51626
|
const normalized = relativePath.replaceAll("\\", "/");
|
|
51343
51627
|
try {
|
|
51344
|
-
return patterns.some((pattern) =>
|
|
51628
|
+
return patterns.some((pattern) => path8.posix.matchesGlob(normalized, pattern));
|
|
51345
51629
|
} catch {
|
|
51346
51630
|
throw new WorkerError("invalid_search", "Search glob pattern is invalid.");
|
|
51347
51631
|
}
|
|
@@ -51359,7 +51643,7 @@ var FileService = class {
|
|
|
51359
51643
|
this.policy.resolveExisting(options.path ?? "."),
|
|
51360
51644
|
deadlineAt
|
|
51361
51645
|
);
|
|
51362
|
-
const startStat = await this.#withinDeadline(
|
|
51646
|
+
const startStat = await this.#withinDeadline(stat3(start), deadlineAt);
|
|
51363
51647
|
const matches = [];
|
|
51364
51648
|
let scannedEntries = 0;
|
|
51365
51649
|
let scannedFiles = 0;
|
|
@@ -51370,7 +51654,7 @@ var FileService = class {
|
|
|
51370
51654
|
const searchFile = async (target) => {
|
|
51371
51655
|
const relative = displayPath(this.policy.root, target);
|
|
51372
51656
|
if (extensions && !extensions.some(
|
|
51373
|
-
(extension) =>
|
|
51657
|
+
(extension) => path8.basename(target).toLowerCase().endsWith(extension)
|
|
51374
51658
|
)) {
|
|
51375
51659
|
return false;
|
|
51376
51660
|
}
|
|
@@ -51465,7 +51749,7 @@ var FileService = class {
|
|
|
51465
51749
|
scannedEntries += batch.children.length;
|
|
51466
51750
|
for (const child of batch.children) {
|
|
51467
51751
|
this.#assertBeforeDeadline(deadlineAt);
|
|
51468
|
-
const target =
|
|
51752
|
+
const target = path8.join(directory, child.name);
|
|
51469
51753
|
let targetType;
|
|
51470
51754
|
if (this.#trustDirectoryEntryTypes) {
|
|
51471
51755
|
if (child.isSymbolicLink()) {
|
|
@@ -51613,12 +51897,12 @@ var FileService = class {
|
|
|
51613
51897
|
);
|
|
51614
51898
|
if (current.exists) return { created: false };
|
|
51615
51899
|
try {
|
|
51616
|
-
await
|
|
51900
|
+
await mkdir5(current.target, { recursive });
|
|
51617
51901
|
} catch (error46) {
|
|
51618
51902
|
if (error46.code !== "EEXIST") throw error46;
|
|
51619
51903
|
}
|
|
51620
51904
|
const resolved = await this.policy.resolveExisting(relativePath);
|
|
51621
|
-
if (!(await
|
|
51905
|
+
if (!(await stat3(resolved)).isDirectory()) {
|
|
51622
51906
|
throw new WorkerError("not_directory", "The destination is not a directory.");
|
|
51623
51907
|
}
|
|
51624
51908
|
return { created: true };
|
|
@@ -51650,12 +51934,12 @@ var FileService = class {
|
|
|
51650
51934
|
try {
|
|
51651
51935
|
if (targetStat.isDirectory()) {
|
|
51652
51936
|
if (recursive) {
|
|
51653
|
-
await
|
|
51937
|
+
await rm4(target, { recursive: true, force: false });
|
|
51654
51938
|
} else {
|
|
51655
51939
|
await rmdir(target);
|
|
51656
51940
|
}
|
|
51657
51941
|
} else {
|
|
51658
|
-
await
|
|
51942
|
+
await rm4(target, { force: false });
|
|
51659
51943
|
}
|
|
51660
51944
|
} catch (error46) {
|
|
51661
51945
|
const code = error46.code;
|
|
@@ -51763,7 +52047,7 @@ var FileService = class {
|
|
|
51763
52047
|
return await withFileWriteLock(target, async () => {
|
|
51764
52048
|
let existingMode;
|
|
51765
52049
|
try {
|
|
51766
|
-
const existingStat = await
|
|
52050
|
+
const existingStat = await stat3(target);
|
|
51767
52051
|
if (existingStat.isFile()) existingMode = existingStat.mode & 4095;
|
|
51768
52052
|
} catch (error46) {
|
|
51769
52053
|
if (error46.code !== "ENOENT") throw error46;
|
|
@@ -51775,9 +52059,9 @@ var FileService = class {
|
|
|
51775
52059
|
);
|
|
51776
52060
|
}
|
|
51777
52061
|
if (expectedSha256) await requireRevision(target, expectedSha256);
|
|
51778
|
-
const temporary =
|
|
52062
|
+
const temporary = path8.join(path8.dirname(target), `.glossa-${randomUUID4()}.tmp`);
|
|
51779
52063
|
try {
|
|
51780
|
-
await
|
|
52064
|
+
await writeFile5(temporary, bytes, { flag: "wx", mode: 384 });
|
|
51781
52065
|
target = await this.policy.resolveWritableFile(relativePath);
|
|
51782
52066
|
const tempStat = await lstat2(temporary);
|
|
51783
52067
|
if (!tempStat.isFile() || tempStat.isSymbolicLink()) {
|
|
@@ -51803,7 +52087,7 @@ var FileService = class {
|
|
|
51803
52087
|
}
|
|
51804
52088
|
}
|
|
51805
52089
|
} finally {
|
|
51806
|
-
await
|
|
52090
|
+
await rm4(temporary, { force: true });
|
|
51807
52091
|
}
|
|
51808
52092
|
return { sha256: sha256(bytes), bytes: bytes.byteLength };
|
|
51809
52093
|
});
|
|
@@ -51830,7 +52114,9 @@ function jobInputContainsRestrictedData(job) {
|
|
|
51830
52114
|
return containsRestrictedAuthenticationData({
|
|
51831
52115
|
query: job.query,
|
|
51832
52116
|
path: job.path,
|
|
51833
|
-
extensions: job.extensions
|
|
52117
|
+
extensions: job.extensions,
|
|
52118
|
+
includeGlobs: job.includeGlobs,
|
|
52119
|
+
excludeGlobs: job.excludeGlobs
|
|
51834
52120
|
});
|
|
51835
52121
|
case "read_file_range":
|
|
51836
52122
|
return containsRestrictedAuthenticationData(job.path);
|
|
@@ -52013,7 +52299,7 @@ var LocalWorker = class _LocalWorker {
|
|
|
52013
52299
|
};
|
|
52014
52300
|
|
|
52015
52301
|
// src/worker/remote-worker.ts
|
|
52016
|
-
import { randomUUID as
|
|
52302
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
52017
52303
|
var WORKER_REQUEST_TIMEOUT_MS = 19e3;
|
|
52018
52304
|
var DEFAULT_RECONNECT_BASE_MS = 500;
|
|
52019
52305
|
var DEFAULT_RECONNECT_MAX_MS = 1e4;
|
|
@@ -52133,7 +52419,7 @@ var RemoteWorker = class {
|
|
|
52133
52419
|
#reconnectBaseMs;
|
|
52134
52420
|
#reconnectMaxMs;
|
|
52135
52421
|
#heartbeatMs;
|
|
52136
|
-
#workerId =
|
|
52422
|
+
#workerId = randomUUID5();
|
|
52137
52423
|
#onStatus;
|
|
52138
52424
|
constructor(options) {
|
|
52139
52425
|
this.#origin = new URL(options.origin);
|
|
@@ -52177,7 +52463,7 @@ var RemoteWorker = class {
|
|
|
52177
52463
|
if (error46 instanceof DeviceRejectedError || error46 instanceof RelayAccessProfileUnsupportedError) {
|
|
52178
52464
|
throw error46;
|
|
52179
52465
|
}
|
|
52180
|
-
const
|
|
52466
|
+
const delay5 = reconnectDelayMs(
|
|
52181
52467
|
failures,
|
|
52182
52468
|
this.#random,
|
|
52183
52469
|
this.#reconnectBaseMs,
|
|
@@ -52187,10 +52473,10 @@ var RemoteWorker = class {
|
|
|
52187
52473
|
this.#onStatus({
|
|
52188
52474
|
state: "retrying",
|
|
52189
52475
|
error: error46 instanceof Error ? error46 : new Error(String(error46)),
|
|
52190
|
-
retryInMs:
|
|
52476
|
+
retryInMs: delay5
|
|
52191
52477
|
});
|
|
52192
52478
|
try {
|
|
52193
|
-
await this.#sleep(
|
|
52479
|
+
await this.#sleep(delay5, this.#signal);
|
|
52194
52480
|
} catch (sleepError) {
|
|
52195
52481
|
if (this.#signal.aborted) return;
|
|
52196
52482
|
throw sleepError;
|
|
@@ -52269,7 +52555,7 @@ var RemoteWorker = class {
|
|
|
52269
52555
|
const key = JSON.stringify(body);
|
|
52270
52556
|
if (seenAttempts.has(key)) return;
|
|
52271
52557
|
seenAttempts.add(key);
|
|
52272
|
-
attempts.push({ body, legacyRelay
|
|
52558
|
+
attempts.push({ body, legacyRelay });
|
|
52273
52559
|
};
|
|
52274
52560
|
if (this.#accessProfile) {
|
|
52275
52561
|
for (const metadata of metadataPhases) {
|
|
@@ -52294,7 +52580,7 @@ var RemoteWorker = class {
|
|
|
52294
52580
|
);
|
|
52295
52581
|
}
|
|
52296
52582
|
}
|
|
52297
|
-
attempts.push({ body: {}, legacyRelay: true
|
|
52583
|
+
attempts.push({ body: {}, legacyRelay: true });
|
|
52298
52584
|
}
|
|
52299
52585
|
for (const attempt of attempts) {
|
|
52300
52586
|
let response;
|
|
@@ -52314,17 +52600,22 @@ var RemoteWorker = class {
|
|
|
52314
52600
|
throw new Error("The relay returned an invalid registration response.");
|
|
52315
52601
|
}
|
|
52316
52602
|
const workerToken = "workerToken" in value ? optionalWorkerToken(value.workerToken) : void 0;
|
|
52317
|
-
|
|
52603
|
+
const accessProfileAccepted = this.#accessProfile === void 0 || this.#accessProfile === "system" || "accessProfile" in value && value.accessProfile === this.#accessProfile;
|
|
52604
|
+
const session = {
|
|
52318
52605
|
generation: value.generation,
|
|
52319
52606
|
legacyRelay: attempt.legacyRelay,
|
|
52320
52607
|
concurrentJobs: !attempt.legacyRelay && supportsCapability(value, "concurrentJobs"),
|
|
52321
52608
|
structuredReads: !attempt.legacyRelay && supportsCapability(value, "structuredReads"),
|
|
52322
52609
|
structuredMutations: !attempt.legacyRelay && supportsCapability(value, "structuredMutations"),
|
|
52323
52610
|
commandOutputRanges: !attempt.legacyRelay && supportsCapability(value, "commandOutputRanges"),
|
|
52324
|
-
accessProfileAccepted
|
|
52611
|
+
accessProfileAccepted,
|
|
52325
52612
|
workspaceLabelAccepted: this.#workspaceLabel === void 0 || "workspaceLabel" in value && value.workspaceLabel === this.#workspaceLabel,
|
|
52326
52613
|
...workerToken ? { workerToken } : {}
|
|
52327
52614
|
};
|
|
52615
|
+
if (!accessProfileAccepted && this.#accessProfile) {
|
|
52616
|
+
throw new RelayAccessProfileUnsupportedError(this.#accessProfile);
|
|
52617
|
+
}
|
|
52618
|
+
return session;
|
|
52328
52619
|
}
|
|
52329
52620
|
if (this.#accessProfile && this.#accessProfile !== "system") {
|
|
52330
52621
|
throw new RelayAccessProfileUnsupportedError(this.#accessProfile);
|
|
@@ -52567,10 +52858,10 @@ var RemoteWorker = class {
|
|
|
52567
52858
|
await unregister(`Device ${this.#deviceToken}`);
|
|
52568
52859
|
}
|
|
52569
52860
|
}
|
|
52570
|
-
async #post(
|
|
52861
|
+
async #post(path14, body, workerToken) {
|
|
52571
52862
|
const timeout = AbortSignal.timeout(WORKER_REQUEST_TIMEOUT_MS);
|
|
52572
52863
|
const signal = AbortSignal.any([this.#signal, timeout]);
|
|
52573
|
-
const response = await this.#fetcher(new URL(
|
|
52864
|
+
const response = await this.#fetcher(new URL(path14, this.#origin), {
|
|
52574
52865
|
method: "POST",
|
|
52575
52866
|
headers: {
|
|
52576
52867
|
authorization: workerToken ? `Worker ${workerToken}` : `Device ${this.#deviceToken}`,
|
|
@@ -52691,56 +52982,27 @@ function visibleWorker(worker, options) {
|
|
|
52691
52982
|
}
|
|
52692
52983
|
async function deviceForSession(endpoints, dependencies = {}, signal) {
|
|
52693
52984
|
const loadDevice = dependencies.loadDeviceCredential ?? loadDeviceCredential;
|
|
52694
|
-
const loadLogin = dependencies.loadCredentials ?? loadCredentials;
|
|
52695
|
-
const validate2 = dependencies.validCredentials ?? validCredentials;
|
|
52696
|
-
const subjectFor = dependencies.accessTokenSubject ?? accessTokenSubject;
|
|
52697
52985
|
const removeDevice = dependencies.deleteDeviceCredential ?? deleteDeviceCredential;
|
|
52698
|
-
const enroll = dependencies.enrollDevice ?? enrollDevice;
|
|
52699
52986
|
const saveDevice = dependencies.saveDeviceCredential ?? saveDeviceCredential;
|
|
52700
|
-
const
|
|
52701
|
-
const
|
|
52702
|
-
const
|
|
52703
|
-
const fetchRequest = signal ? async (input, init) => await baseFetch(input, { ...init, signal }) : baseFetch;
|
|
52987
|
+
const pair = dependencies.pairDevice ?? pairDevice;
|
|
52988
|
+
const revoke = dependencies.revokePairedDevice ?? revokePairedDevice;
|
|
52989
|
+
const withPairingLease = dependencies.withDevicePairingLease ?? withDevicePairingLease;
|
|
52704
52990
|
signal?.throwIfAborted();
|
|
52705
52991
|
const stored = await loadDevice();
|
|
52706
|
-
|
|
52707
|
-
|
|
52708
|
-
|
|
52709
|
-
const
|
|
52710
|
-
if (
|
|
52711
|
-
|
|
52712
|
-
|
|
52713
|
-
|
|
52714
|
-
|
|
52715
|
-
|
|
52716
|
-
const
|
|
52717
|
-
|
|
52718
|
-
|
|
52719
|
-
|
|
52720
|
-
await saveDevice(migrated);
|
|
52721
|
-
return migrated;
|
|
52722
|
-
}
|
|
52723
|
-
await removeDevice();
|
|
52724
|
-
}
|
|
52725
|
-
signal?.throwIfAborted();
|
|
52726
|
-
const current = await currentCredentials();
|
|
52727
|
-
const enrolled = await enroll(
|
|
52728
|
-
endpoints,
|
|
52729
|
-
current,
|
|
52730
|
-
name(),
|
|
52731
|
-
fetchRequest
|
|
52732
|
-
);
|
|
52733
|
-
const bound = {
|
|
52734
|
-
...enrolled,
|
|
52735
|
-
accountSubject: subjectFor(current)
|
|
52736
|
-
};
|
|
52737
|
-
await saveDevice(bound);
|
|
52738
|
-
return bound;
|
|
52739
|
-
}
|
|
52740
|
-
async function reenrollRejectedDevice(endpoints, dependencies = {}, signal) {
|
|
52741
|
-
const remove = dependencies.deleteDeviceCredential ?? deleteDeviceCredential;
|
|
52742
|
-
await remove();
|
|
52743
|
-
return await deviceForSession(endpoints, dependencies, signal);
|
|
52992
|
+
if (stored?.relayOrigin === endpoints.relayOrigin) return stored;
|
|
52993
|
+
return await withPairingLease(async () => {
|
|
52994
|
+
signal?.throwIfAborted();
|
|
52995
|
+
const current = await loadDevice();
|
|
52996
|
+
if (current?.relayOrigin === endpoints.relayOrigin) return current;
|
|
52997
|
+
if (current) {
|
|
52998
|
+
await revoke({ relayOrigin: current.relayOrigin }, current);
|
|
52999
|
+
await removeDevice();
|
|
53000
|
+
}
|
|
53001
|
+
signal?.throwIfAborted();
|
|
53002
|
+
const paired = await pair(endpoints, signal);
|
|
53003
|
+
await saveDevice(paired);
|
|
53004
|
+
return paired;
|
|
53005
|
+
}, signal);
|
|
52744
53006
|
}
|
|
52745
53007
|
function retryMessage(retryInMs) {
|
|
52746
53008
|
const seconds = Math.max(1, Math.ceil(retryInMs / 1e3));
|
|
@@ -52852,9 +53114,6 @@ async function connectRemoteWorker(endpoints, device, worker, options, signal, o
|
|
|
52852
53114
|
await connectHintTask;
|
|
52853
53115
|
}
|
|
52854
53116
|
}
|
|
52855
|
-
function shouldRecoverRejectedDevice(error46, recoveredRejectedDevice, connected) {
|
|
52856
|
-
return error46 instanceof DeviceRejectedError && !recoveredRejectedDevice && !connected;
|
|
52857
|
-
}
|
|
52858
53117
|
async function runManagedSession(root, endpoints, options = {}) {
|
|
52859
53118
|
const controller = new AbortController();
|
|
52860
53119
|
const stop = () => controller.abort();
|
|
@@ -52869,9 +53128,9 @@ async function runManagedSession(root, endpoints, options = {}) {
|
|
|
52869
53128
|
try {
|
|
52870
53129
|
const accessProfile = options.accessProfile ?? DEFAULT_WORKER_ACCESS_PROFILE;
|
|
52871
53130
|
const sessionOptions = { ...options, accessProfile };
|
|
52872
|
-
|
|
53131
|
+
const device = options.device ?? await deviceForSession(
|
|
52873
53132
|
endpoints,
|
|
52874
|
-
|
|
53133
|
+
{},
|
|
52875
53134
|
controller.signal
|
|
52876
53135
|
);
|
|
52877
53136
|
controller.signal.throwIfAborted();
|
|
@@ -52892,41 +53151,18 @@ async function runManagedSession(root, endpoints, options = {}) {
|
|
|
52892
53151
|
console.error(accessProfileSummary(accessProfile));
|
|
52893
53152
|
console.error("Press Ctrl+C to disconnect.");
|
|
52894
53153
|
}
|
|
52895
|
-
|
|
52896
|
-
|
|
52897
|
-
|
|
52898
|
-
|
|
52899
|
-
|
|
52900
|
-
|
|
52901
|
-
|
|
52902
|
-
|
|
52903
|
-
sessionOptions,
|
|
52904
|
-
controller.signal,
|
|
52905
|
-
() => {
|
|
52906
|
-
connected = true;
|
|
52907
|
-
}
|
|
52908
|
-
);
|
|
52909
|
-
break;
|
|
52910
|
-
} catch (error46) {
|
|
52911
|
-
if (!shouldRecoverRejectedDevice(
|
|
52912
|
-
error46,
|
|
52913
|
-
recoveredRejectedDevice,
|
|
52914
|
-
connected
|
|
52915
|
-
)) {
|
|
52916
|
-
throw error46;
|
|
52917
|
-
}
|
|
52918
|
-
recoveredRejectedDevice = true;
|
|
52919
|
-
device = await reenrollRejectedDevice(
|
|
52920
|
-
endpoints,
|
|
52921
|
-
options.credentials ? { credentials: options.credentials } : {},
|
|
52922
|
-
controller.signal
|
|
52923
|
-
);
|
|
52924
|
-
}
|
|
52925
|
-
}
|
|
53154
|
+
await connectRemoteWorker(
|
|
53155
|
+
endpoints,
|
|
53156
|
+
device,
|
|
53157
|
+
worker,
|
|
53158
|
+
sessionOptions,
|
|
53159
|
+
controller.signal,
|
|
53160
|
+
() => void 0
|
|
53161
|
+
);
|
|
52926
53162
|
} catch (error46) {
|
|
52927
53163
|
if (error46 instanceof DeviceRejectedError) {
|
|
52928
53164
|
await deleteDeviceCredential();
|
|
52929
|
-
throw new Error("The relay rejected this
|
|
53165
|
+
throw new Error("The relay rejected this paired computer. Run Glossa again to pair it with your account.");
|
|
52930
53166
|
}
|
|
52931
53167
|
throw error46;
|
|
52932
53168
|
} finally {
|
|
@@ -52951,7 +53187,7 @@ function initialHudState(workspace) {
|
|
|
52951
53187
|
message: void 0,
|
|
52952
53188
|
activities: [],
|
|
52953
53189
|
activityPage: 0,
|
|
52954
|
-
view: "
|
|
53190
|
+
view: "workspace",
|
|
52955
53191
|
status: void 0,
|
|
52956
53192
|
deviceSelection: 0,
|
|
52957
53193
|
pendingAccessProfile: void 0,
|
|
@@ -53022,12 +53258,12 @@ function formatByteCount(value) {
|
|
|
53022
53258
|
}
|
|
53023
53259
|
return `${(kibibytes / 1024).toFixed(1)} MiB`;
|
|
53024
53260
|
}
|
|
53025
|
-
function workspacePath(
|
|
53026
|
-
return
|
|
53261
|
+
function workspacePath(path14) {
|
|
53262
|
+
return path14 || ".";
|
|
53027
53263
|
}
|
|
53028
|
-
function pathSummary(
|
|
53264
|
+
function pathSummary(path14, details = []) {
|
|
53029
53265
|
return {
|
|
53030
|
-
target: `path ${quoteActivityInput(
|
|
53266
|
+
target: `path ${quoteActivityInput(path14)}`,
|
|
53031
53267
|
details,
|
|
53032
53268
|
truncation: "middle"
|
|
53033
53269
|
};
|
|
@@ -53226,6 +53462,7 @@ var COLORS = {
|
|
|
53226
53462
|
muted: "#aaa4b5",
|
|
53227
53463
|
purple: "#8054ff",
|
|
53228
53464
|
purpleReadable: "#ad98ff",
|
|
53465
|
+
success: "#65d6a6",
|
|
53229
53466
|
coral: "#ff665f",
|
|
53230
53467
|
line: "#5c556e"
|
|
53231
53468
|
};
|
|
@@ -53233,7 +53470,9 @@ function Text2({ color, ...props }) {
|
|
|
53233
53470
|
return color === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { ...props }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { ...props, color });
|
|
53234
53471
|
}
|
|
53235
53472
|
var ACTIVITY_REFRESH_INTERVAL_MS = 1e4;
|
|
53473
|
+
var ACTIVITY_STATUS_COLUMN_WIDTH = 2;
|
|
53236
53474
|
var ACTIVITY_TOOL_COLUMN_WIDTH = 15;
|
|
53475
|
+
var ACTIVITY_AGE_COLUMN_WIDTH = 10;
|
|
53237
53476
|
var ACTIVITY_PREAMBLE_LINES = 1;
|
|
53238
53477
|
var HudStore = class {
|
|
53239
53478
|
#state;
|
|
@@ -53279,10 +53518,10 @@ function activityAge(updatedAt, working, now) {
|
|
|
53279
53518
|
if (elapsedMs < 864e5) return `${Math.floor(elapsedMs / 36e5)}h ago`;
|
|
53280
53519
|
return `${Math.floor(elapsedMs / 864e5)}d ago`;
|
|
53281
53520
|
}
|
|
53282
|
-
function
|
|
53283
|
-
if (activity.state === "
|
|
53284
|
-
if (activity.state === "
|
|
53285
|
-
return COLORS.
|
|
53521
|
+
function activityStatus(activity) {
|
|
53522
|
+
if (activity.state === "failed") return { symbol: "\xD7", tone: COLORS.coral };
|
|
53523
|
+
if (activity.state === "working") return { symbol: "\u25CB", tone: COLORS.purpleReadable };
|
|
53524
|
+
return { symbol: "\u2713", tone: COLORS.success };
|
|
53286
53525
|
}
|
|
53287
53526
|
function activitySummary(activity) {
|
|
53288
53527
|
return [activity.summary.target, ...activity.summary.details].join(" \xB7 ");
|
|
@@ -53298,31 +53537,28 @@ function adjacentAccessProfile(accessProfile, direction) {
|
|
|
53298
53537
|
const index = ACCESS_PROFILES.indexOf(accessProfile);
|
|
53299
53538
|
return ACCESS_PROFILES[index + direction];
|
|
53300
53539
|
}
|
|
53301
|
-
function
|
|
53302
|
-
|
|
53303
|
-
|
|
53304
|
-
|
|
53305
|
-
|
|
53306
|
-
|
|
53307
|
-
|
|
53308
|
-
|
|
53309
|
-
|
|
53310
|
-
|
|
53311
|
-
|
|
53312
|
-
}
|
|
53313
|
-
function
|
|
53314
|
-
|
|
53315
|
-
|
|
53316
|
-
|
|
53317
|
-
}
|
|
53318
|
-
|
|
53319
|
-
|
|
53320
|
-
|
|
53321
|
-
|
|
53322
|
-
|
|
53323
|
-
{ key: "L", label: "Sign out", tone: COLORS.coral },
|
|
53324
|
-
{ key: "Q", label: "Quit", tone: COLORS.coral }
|
|
53325
|
-
];
|
|
53540
|
+
function accessProfileCapabilities(accessProfile) {
|
|
53541
|
+
if (accessProfile === "read-only") {
|
|
53542
|
+
return { summary: "Read files only", detail: "No changes \xB7 no commands" };
|
|
53543
|
+
}
|
|
53544
|
+
if (accessProfile === "workspace") {
|
|
53545
|
+
return { summary: "Read + write files", detail: "Commands disabled" };
|
|
53546
|
+
}
|
|
53547
|
+
return {
|
|
53548
|
+
summary: "Read + write files + commands",
|
|
53549
|
+
detail: "OS account permissions apply"
|
|
53550
|
+
};
|
|
53551
|
+
}
|
|
53552
|
+
function primaryFooterHints() {
|
|
53553
|
+
return [
|
|
53554
|
+
{ key: "A", label: "Activity" },
|
|
53555
|
+
{ key: "W", label: "Workspace" },
|
|
53556
|
+
{ key: "D", label: "Devices" },
|
|
53557
|
+
{ key: "?", label: "Help" },
|
|
53558
|
+
{ key: "L", label: "Account sign out", tone: COLORS.coral },
|
|
53559
|
+
{ key: "Q", label: "Quit", tone: COLORS.coral }
|
|
53560
|
+
];
|
|
53561
|
+
}
|
|
53326
53562
|
function contextualFooterHints(state) {
|
|
53327
53563
|
if (state.view === "activity") {
|
|
53328
53564
|
return [
|
|
@@ -53370,7 +53606,7 @@ function splitFooterHints(hints, usable) {
|
|
|
53370
53606
|
return rows;
|
|
53371
53607
|
}
|
|
53372
53608
|
function buildFooterRows(state, usable) {
|
|
53373
|
-
const rows = splitFooterHints(
|
|
53609
|
+
const rows = splitFooterHints(primaryFooterHints(), usable).map((left) => ({
|
|
53374
53610
|
left,
|
|
53375
53611
|
right: []
|
|
53376
53612
|
}));
|
|
@@ -53391,7 +53627,10 @@ function promptText(state) {
|
|
|
53391
53627
|
if (state.busy) return { message: "Working\u2026" };
|
|
53392
53628
|
if (!state.prompt) return void 0;
|
|
53393
53629
|
if (state.prompt.type === "logout") {
|
|
53394
|
-
return {
|
|
53630
|
+
return {
|
|
53631
|
+
message: "Sign out the CLI account and disconnect? Computer pairing stays active.",
|
|
53632
|
+
choices: "Y confirm N cancel"
|
|
53633
|
+
};
|
|
53395
53634
|
}
|
|
53396
53635
|
if (state.prompt.type === "access-confirm") {
|
|
53397
53636
|
const detail = state.prompt.accessProfile === "system" ? "Commands will inherit this OS account's permissions." : "This allows guarded file writes.";
|
|
@@ -53462,7 +53701,7 @@ function Line({ usable, color }) {
|
|
|
53462
53701
|
}
|
|
53463
53702
|
function Header({ state, usable, bodyBudget, color }) {
|
|
53464
53703
|
const statusColor = state.connection === "error" ? COLORS.coral : COLORS.purpleReadable;
|
|
53465
|
-
const pageTitle = state.view === "activity" ? "
|
|
53704
|
+
const pageTitle = state.view === "activity" ? "Activity" : state.view === "devices" ? "Devices" : state.view === "workspace" ? "Workspace" : state.view === "help" ? "Help" : void 0;
|
|
53466
53705
|
const activityPage = state.view === "activity" ? activityPageInfo(state, bodyBudget) : void 0;
|
|
53467
53706
|
const activityRange = activityPage && activityPage.maxPage > 0 ? ` (${activityPage.rangeStart}-${activityPage.rangeEnd}/${state.activities.length})` : "";
|
|
53468
53707
|
const deviceWindow = state.view === "devices" ? deviceListWindow(state, bodyBudget, usable) : void 0;
|
|
@@ -53488,46 +53727,118 @@ function SectionTitle({ children, color, tone = COLORS.purpleReadable }) {
|
|
|
53488
53727
|
function Blank() {
|
|
53489
53728
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { height: 1 });
|
|
53490
53729
|
}
|
|
53491
|
-
function
|
|
53730
|
+
function ActivityPreviewHeader({ usable, color }) {
|
|
53731
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { width: usable, children: [
|
|
53732
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { flexGrow: 1, flexShrink: 1, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionTitle, { color, children: "Activity" }) }),
|
|
53733
|
+
usable >= 24 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { marginLeft: 1, flexShrink: 0, children: [
|
|
53734
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { bold: true, color: color ? COLORS.purpleReadable : void 0, children: "A" }),
|
|
53735
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, children: " View all" })
|
|
53736
|
+
] }) : null
|
|
53737
|
+
] });
|
|
53738
|
+
}
|
|
53739
|
+
function AccessSectionTitle({ accessProfile, usable, color }) {
|
|
53740
|
+
const lower = adjacentAccessProfile(accessProfile, -1);
|
|
53741
|
+
const higher = adjacentAccessProfile(accessProfile, 1);
|
|
53742
|
+
const key = lower && higher ? "\u2190/\u2192" : lower ? "\u2190" : "\u2192";
|
|
53743
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { width: usable, children: [
|
|
53744
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionTitle, { color, children: "Access" }),
|
|
53745
|
+
usable >= 24 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { marginLeft: 3, flexShrink: 0, children: [
|
|
53746
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { bold: true, color: color ? COLORS.purpleReadable : void 0, children: key }),
|
|
53747
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, children: " Switch" })
|
|
53748
|
+
] }) : null
|
|
53749
|
+
] });
|
|
53750
|
+
}
|
|
53751
|
+
function activityLayout(usable) {
|
|
53752
|
+
const toolWidth = Math.min(
|
|
53753
|
+
ACTIVITY_TOOL_COLUMN_WIDTH,
|
|
53754
|
+
Math.max(0, usable - ACTIVITY_STATUS_COLUMN_WIDTH)
|
|
53755
|
+
);
|
|
53756
|
+
return {
|
|
53757
|
+
toolWidth,
|
|
53758
|
+
showSummary: usable >= ACTIVITY_STATUS_COLUMN_WIDTH + toolWidth + 5,
|
|
53759
|
+
showAge: usable >= ACTIVITY_STATUS_COLUMN_WIDTH + toolWidth + ACTIVITY_AGE_COLUMN_WIDTH + 10
|
|
53760
|
+
};
|
|
53761
|
+
}
|
|
53762
|
+
function WorkspaceView({ state, usable, bodyBudget, color, now }) {
|
|
53492
53763
|
const displayedAccessProfile = state.pendingAccessProfile ?? state.accessProfile;
|
|
53764
|
+
const showConnectionMessage = Boolean(
|
|
53765
|
+
state.message && (state.connection === "retrying" || state.connection === "error")
|
|
53766
|
+
);
|
|
53767
|
+
let fixedLines = 3;
|
|
53768
|
+
if (displayedAccessProfile) fixedLines += 5;
|
|
53769
|
+
if (state.deviceName) fixedLines += 3;
|
|
53770
|
+
if (showConnectionMessage) fixedLines += 2;
|
|
53771
|
+
const activityCapacity = Math.min(3, Math.max(0, bodyBudget - fixedLines - 2));
|
|
53772
|
+
const activityPreview = activityCapacity > 0 ? state.activities.slice(-activityCapacity) : [];
|
|
53493
53773
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { height: bodyBudget, flexDirection: "column", flexShrink: 0, overflow: "hidden", children: [
|
|
53494
53774
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Blank, {}),
|
|
53495
53775
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionTitle, { color, children: "Workspace" }),
|
|
53496
53776
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.ink : void 0, wrap: "truncate-middle", children: state.workspace }),
|
|
53497
53777
|
displayedAccessProfile ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
53498
53778
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Blank, {}),
|
|
53499
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
53500
|
-
|
|
53501
|
-
|
|
53779
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
53780
|
+
AccessSectionTitle,
|
|
53781
|
+
{
|
|
53782
|
+
accessProfile: displayedAccessProfile,
|
|
53783
|
+
usable,
|
|
53784
|
+
color
|
|
53785
|
+
}
|
|
53786
|
+
),
|
|
53787
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { bold: true, color: color ? COLORS.ink : void 0, wrap: "truncate", children: accessProfileLabel(displayedAccessProfile) }),
|
|
53788
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, wrap: "truncate", children: accessProfileCapabilities(displayedAccessProfile).summary }),
|
|
53789
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, wrap: "truncate", children: accessProfileCapabilities(displayedAccessProfile).detail })
|
|
53502
53790
|
] }) : null,
|
|
53503
53791
|
state.deviceName ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
53504
53792
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Blank, {}),
|
|
53505
53793
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionTitle, { color, children: "Device" }),
|
|
53506
53794
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.ink : void 0, wrap: "truncate", children: state.deviceName })
|
|
53507
53795
|
] }) : null,
|
|
53508
|
-
|
|
53796
|
+
activityCapacity > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
53797
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Blank, {}),
|
|
53798
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ActivityPreviewHeader, { usable, color }),
|
|
53799
|
+
state.activities.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, children: "No activity yet." }) : activityPreview.map((activity) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
53800
|
+
ActivityRow,
|
|
53801
|
+
{
|
|
53802
|
+
activity,
|
|
53803
|
+
usable,
|
|
53804
|
+
color,
|
|
53805
|
+
now
|
|
53806
|
+
},
|
|
53807
|
+
activity.requestId
|
|
53808
|
+
))
|
|
53809
|
+
] }) : null,
|
|
53810
|
+
showConnectionMessage ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
53509
53811
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Blank, {}),
|
|
53510
53812
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.coral : void 0, wrap: "truncate", children: state.message })
|
|
53511
53813
|
] }) : null
|
|
53512
53814
|
] });
|
|
53513
53815
|
}
|
|
53514
53816
|
function ActivityRow({ activity, usable, color, now }) {
|
|
53515
|
-
const toolWidth =
|
|
53817
|
+
const { toolWidth, showSummary, showAge } = activityLayout(usable);
|
|
53516
53818
|
const age = activityAge(activity.updatedAt, activity.state === "working", now);
|
|
53517
|
-
const
|
|
53518
|
-
const showSummary = usable >= toolWidth + 5;
|
|
53819
|
+
const status = activityStatus(activity);
|
|
53519
53820
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { width: usable, height: 1, flexShrink: 0, children: [
|
|
53821
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { width: ACTIVITY_STATUS_COLUMN_WIDTH, flexShrink: 0, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { bold: true, color: color ? status.tone : void 0, children: status.symbol }) }),
|
|
53520
53822
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { width: toolWidth, flexShrink: 0, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
53521
53823
|
Text2,
|
|
53522
53824
|
{
|
|
53523
53825
|
bold: true,
|
|
53524
|
-
color: color ?
|
|
53826
|
+
color: color ? COLORS.ink : void 0,
|
|
53525
53827
|
wrap: "truncate",
|
|
53526
53828
|
children: activity.tool
|
|
53527
53829
|
}
|
|
53528
53830
|
) }),
|
|
53529
53831
|
showSummary ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box_default, { marginLeft: 2, flexGrow: 1, flexShrink: 1, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, wrap: "truncate-middle", children: activitySummary(activity) }) }) : null,
|
|
53530
|
-
showAge ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
53832
|
+
showAge ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
53833
|
+
Box_default,
|
|
53834
|
+
{
|
|
53835
|
+
width: ACTIVITY_AGE_COLUMN_WIDTH,
|
|
53836
|
+
marginLeft: 2,
|
|
53837
|
+
flexShrink: 0,
|
|
53838
|
+
justifyContent: "flex-end",
|
|
53839
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text2, { color: color ? COLORS.muted : void 0, children: age })
|
|
53840
|
+
}
|
|
53841
|
+
) : null
|
|
53531
53842
|
] });
|
|
53532
53843
|
}
|
|
53533
53844
|
function ActivityView({ state, usable, bodyBudget, color, now }) {
|
|
@@ -53662,12 +53973,12 @@ function HelpView({ bodyBudget, color }) {
|
|
|
53662
53973
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(HelpRow, { keyLabel: "W", label: "Workspace", color }),
|
|
53663
53974
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(HelpRow, { keyLabel: "D", label: "Devices", color }),
|
|
53664
53975
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(HelpRow, { keyLabel: "?", label: "Help", color }),
|
|
53665
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(HelpRow, { keyLabel: "Esc", label: "
|
|
53976
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(HelpRow, { keyLabel: "Esc", label: "Workspace", color }),
|
|
53666
53977
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Blank, {}),
|
|
53667
53978
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionTitle, { color, tone: COLORS.coral, children: "Manage" }),
|
|
53668
53979
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(HelpRow, { keyLabel: "\u2190/\u2192", label: "Change workspace access", color, tone: COLORS.coral }),
|
|
53669
53980
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(HelpRow, { keyLabel: "Enter/R", label: "Revoke selected device", color, tone: COLORS.coral }),
|
|
53670
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(HelpRow, { keyLabel: "L", label: "
|
|
53981
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(HelpRow, { keyLabel: "L", label: "Account sign out", color, tone: COLORS.coral }),
|
|
53671
53982
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Blank, {}),
|
|
53672
53983
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionTitle, { color, children: "App" }),
|
|
53673
53984
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(HelpRow, { keyLabel: "Q", label: "Disconnect and quit", color, tone: COLORS.coral }),
|
|
@@ -53741,7 +54052,16 @@ function HudScreen({ state, columns, rows, color = true, now = Date.now() }) {
|
|
|
53741
54052
|
bodyBudget: metrics.bodyBudget,
|
|
53742
54053
|
color
|
|
53743
54054
|
}
|
|
53744
|
-
) : state.view === "workspace" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
54055
|
+
) : state.view === "workspace" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
54056
|
+
WorkspaceView,
|
|
54057
|
+
{
|
|
54058
|
+
state,
|
|
54059
|
+
usable: metrics.usable,
|
|
54060
|
+
bodyBudget: metrics.bodyBudget,
|
|
54061
|
+
color,
|
|
54062
|
+
now
|
|
54063
|
+
}
|
|
54064
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HelpView, { bodyBudget: metrics.bodyBudget, color }),
|
|
53745
54065
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Overlay, { state, usable: metrics.usable, color }),
|
|
53746
54066
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Footer, { rows: metrics.footerRows, usable: metrics.usable, color })
|
|
53747
54067
|
] });
|
|
@@ -53802,7 +54122,7 @@ function HudRuntime({ store: store3, actions, signal, stop }) {
|
|
|
53802
54122
|
const { columns, rows } = use_window_size_default();
|
|
53803
54123
|
const [, setClock] = (0, import_react34.useState)(0);
|
|
53804
54124
|
(0, import_react34.useEffect)(() => {
|
|
53805
|
-
if (state.view !== "activity" || state.activities.length === 0) return;
|
|
54125
|
+
if (state.view !== "activity" && state.view !== "workspace" || state.activities.length === 0) return;
|
|
53806
54126
|
const timer = setInterval(() => setClock((value) => value + 1), ACTIVITY_REFRESH_INTERVAL_MS);
|
|
53807
54127
|
return () => clearInterval(timer);
|
|
53808
54128
|
}, [state.view, state.activities.length]);
|
|
@@ -53914,7 +54234,7 @@ function HudRuntime({ store: store3, actions, signal, stop }) {
|
|
|
53914
54234
|
return;
|
|
53915
54235
|
}
|
|
53916
54236
|
if (key.escape) {
|
|
53917
|
-
store3.update((state2) => ({ ...state2, view: "
|
|
54237
|
+
store3.update((state2) => ({ ...state2, view: "workspace", notice: void 0 }));
|
|
53918
54238
|
return;
|
|
53919
54239
|
}
|
|
53920
54240
|
if (value === "a") {
|
|
@@ -54061,119 +54381,8 @@ async function runSessionHud(actions, input = process.stdin, output = process.st
|
|
|
54061
54381
|
var import_semver = __toESM(require_semver2(), 1);
|
|
54062
54382
|
import { spawn as spawn3 } from "node:child_process";
|
|
54063
54383
|
import { createHash as createHash2 } from "node:crypto";
|
|
54064
|
-
import { chmod as chmod3, readdir as readdir2, rename as rename2, rm as
|
|
54065
|
-
import
|
|
54066
|
-
|
|
54067
|
-
// src/update-lock.ts
|
|
54068
|
-
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
54069
|
-
import { mkdir as mkdir4, readdir, rm as rm3, writeFile as writeFile4 } from "node:fs/promises";
|
|
54070
|
-
import path7 from "node:path";
|
|
54071
|
-
var UPDATE_SUFFIX = ".update";
|
|
54072
|
-
var SESSION_SUFFIX = ".session";
|
|
54073
|
-
function updateRuntimeDirectory() {
|
|
54074
|
-
return path7.join(configDirectory(), "runtime");
|
|
54075
|
-
}
|
|
54076
|
-
function processIsAlive(pid) {
|
|
54077
|
-
if (!Number.isSafeInteger(pid) || pid <= 0) return false;
|
|
54078
|
-
try {
|
|
54079
|
-
process.kill(pid, 0);
|
|
54080
|
-
return true;
|
|
54081
|
-
} catch (error46) {
|
|
54082
|
-
return error46.code === "EPERM";
|
|
54083
|
-
}
|
|
54084
|
-
}
|
|
54085
|
-
function leasePid(name, suffix) {
|
|
54086
|
-
if (!name.endsWith(suffix)) return null;
|
|
54087
|
-
const separator = name.indexOf("-");
|
|
54088
|
-
if (separator <= 0) return null;
|
|
54089
|
-
const pid = Number(name.slice(0, separator));
|
|
54090
|
-
return Number.isSafeInteger(pid) && pid > 0 ? pid : null;
|
|
54091
|
-
}
|
|
54092
|
-
async function activeLeaseFiles(directory, suffix) {
|
|
54093
|
-
let entries;
|
|
54094
|
-
try {
|
|
54095
|
-
entries = await readdir(directory, { withFileTypes: true });
|
|
54096
|
-
} catch (error46) {
|
|
54097
|
-
if (error46.code === "ENOENT") return [];
|
|
54098
|
-
throw error46;
|
|
54099
|
-
}
|
|
54100
|
-
const active = [];
|
|
54101
|
-
for (const entry of entries) {
|
|
54102
|
-
if (!entry.isFile() || !entry.name.endsWith(suffix)) continue;
|
|
54103
|
-
const pid = leasePid(entry.name, suffix);
|
|
54104
|
-
const file2 = path7.join(directory, entry.name);
|
|
54105
|
-
if (pid !== null && processIsAlive(pid)) active.push(file2);
|
|
54106
|
-
else await rm3(file2, { force: true });
|
|
54107
|
-
}
|
|
54108
|
-
return active;
|
|
54109
|
-
}
|
|
54110
|
-
async function updateLockIsActive(directory) {
|
|
54111
|
-
return (await activeLeaseFiles(directory, UPDATE_SUFFIX)).length > 0;
|
|
54112
|
-
}
|
|
54113
|
-
async function activeSessionFiles(directory) {
|
|
54114
|
-
return await activeLeaseFiles(directory, SESSION_SUFFIX);
|
|
54115
|
-
}
|
|
54116
|
-
async function createUpdateLock(directory) {
|
|
54117
|
-
await mkdir4(directory, { recursive: true, mode: 448 });
|
|
54118
|
-
const lockFile = path7.join(
|
|
54119
|
-
directory,
|
|
54120
|
-
`${process.pid}-${randomUUID4()}${UPDATE_SUFFIX}`
|
|
54121
|
-
);
|
|
54122
|
-
const content = `${JSON.stringify({ pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() })}
|
|
54123
|
-
`;
|
|
54124
|
-
await writeFile4(lockFile, content, {
|
|
54125
|
-
encoding: "utf8",
|
|
54126
|
-
flag: "wx",
|
|
54127
|
-
mode: 384
|
|
54128
|
-
});
|
|
54129
|
-
try {
|
|
54130
|
-
const contenders = await activeLeaseFiles(directory, UPDATE_SUFFIX);
|
|
54131
|
-
if (contenders.some((file2) => file2 !== lockFile)) {
|
|
54132
|
-
throw new Error("Another Glossa update is already running.");
|
|
54133
|
-
}
|
|
54134
|
-
return lockFile;
|
|
54135
|
-
} catch (error46) {
|
|
54136
|
-
await rm3(lockFile, { force: true });
|
|
54137
|
-
throw error46;
|
|
54138
|
-
}
|
|
54139
|
-
}
|
|
54140
|
-
async function withUpdateLease(action, directory = updateRuntimeDirectory()) {
|
|
54141
|
-
const lockFile = await createUpdateLock(directory);
|
|
54142
|
-
try {
|
|
54143
|
-
if ((await activeSessionFiles(directory)).length > 0) {
|
|
54144
|
-
throw new Error("Disconnect every running Glossa workspace before updating.");
|
|
54145
|
-
}
|
|
54146
|
-
return await action();
|
|
54147
|
-
} finally {
|
|
54148
|
-
await rm3(lockFile, { force: true });
|
|
54149
|
-
}
|
|
54150
|
-
}
|
|
54151
|
-
async function withWorkspaceLease(action, directory = updateRuntimeDirectory()) {
|
|
54152
|
-
await mkdir4(directory, { recursive: true, mode: 448 });
|
|
54153
|
-
if (await updateLockIsActive(directory)) {
|
|
54154
|
-
throw new Error("Glossa is updating. Run this workspace again after the update finishes.");
|
|
54155
|
-
}
|
|
54156
|
-
const leaseFile = path7.join(
|
|
54157
|
-
directory,
|
|
54158
|
-
`${process.pid}-${randomUUID4()}${SESSION_SUFFIX}`
|
|
54159
|
-
);
|
|
54160
|
-
await writeFile4(
|
|
54161
|
-
leaseFile,
|
|
54162
|
-
`${JSON.stringify({ pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() })}
|
|
54163
|
-
`,
|
|
54164
|
-
{ encoding: "utf8", flag: "wx", mode: 384 }
|
|
54165
|
-
);
|
|
54166
|
-
try {
|
|
54167
|
-
if (await updateLockIsActive(directory)) {
|
|
54168
|
-
throw new Error("Glossa is updating. Run this workspace again after the update finishes.");
|
|
54169
|
-
}
|
|
54170
|
-
return await action();
|
|
54171
|
-
} finally {
|
|
54172
|
-
await rm3(leaseFile, { force: true });
|
|
54173
|
-
}
|
|
54174
|
-
}
|
|
54175
|
-
|
|
54176
|
-
// src/update-service.ts
|
|
54384
|
+
import { chmod as chmod3, readdir as readdir2, rename as rename2, rm as rm5, writeFile as writeFile6 } from "node:fs/promises";
|
|
54385
|
+
import path9 from "node:path";
|
|
54177
54386
|
var PACKAGE_NAME = "@ariobarin/glossa";
|
|
54178
54387
|
var DEFAULT_REGISTRY_URL = "https://registry.npmjs.org/@ariobarin%2Fglossa";
|
|
54179
54388
|
var DEFAULT_RELEASE_BASE_URL = "https://github.com/ariobarin/glossa/releases/download";
|
|
@@ -54250,8 +54459,8 @@ async function cleanupUpdateBackups(distribution, options = {}) {
|
|
|
54250
54459
|
const platform2 = options.platform ?? process.platform;
|
|
54251
54460
|
if (distribution !== "standalone" || platform2 !== "win32") return;
|
|
54252
54461
|
const executable = options.executablePath ?? process.execPath;
|
|
54253
|
-
const directory =
|
|
54254
|
-
const prefix = `${
|
|
54462
|
+
const directory = path9.dirname(executable);
|
|
54463
|
+
const prefix = `${path9.basename(executable)}.old-`;
|
|
54255
54464
|
let entries;
|
|
54256
54465
|
try {
|
|
54257
54466
|
entries = await readdir2(directory, { withFileTypes: true });
|
|
@@ -54264,7 +54473,7 @@ async function cleanupUpdateBackups(distribution, options = {}) {
|
|
|
54264
54473
|
const ownerPid = backupOwnerPid(entry.name, prefix);
|
|
54265
54474
|
return ownerPid !== null && !processIsAlive(ownerPid);
|
|
54266
54475
|
}).map(async (entry) => {
|
|
54267
|
-
await
|
|
54476
|
+
await rm5(path9.join(directory, entry.name), { force: true }).catch(() => void 0);
|
|
54268
54477
|
})
|
|
54269
54478
|
);
|
|
54270
54479
|
}
|
|
@@ -54340,12 +54549,12 @@ async function installStandaloneUpdate(info, options) {
|
|
|
54340
54549
|
if (actual !== expected) {
|
|
54341
54550
|
throw new Error("Glossa refused to update because the SHA-256 checksum did not match.");
|
|
54342
54551
|
}
|
|
54343
|
-
const download =
|
|
54344
|
-
|
|
54345
|
-
`${
|
|
54552
|
+
const download = path9.join(
|
|
54553
|
+
path9.dirname(destination),
|
|
54554
|
+
`${path9.basename(destination)}.download-${process.pid}`
|
|
54346
54555
|
);
|
|
54347
54556
|
try {
|
|
54348
|
-
await
|
|
54557
|
+
await writeFile6(download, binary, { mode: 493 });
|
|
54349
54558
|
if (platform2 === "win32") {
|
|
54350
54559
|
await replaceWindowsExecutable(download, destination);
|
|
54351
54560
|
} else {
|
|
@@ -54354,7 +54563,7 @@ async function installStandaloneUpdate(info, options) {
|
|
|
54354
54563
|
}
|
|
54355
54564
|
return "installed";
|
|
54356
54565
|
} catch (error46) {
|
|
54357
|
-
await
|
|
54566
|
+
await rm5(download, { force: true });
|
|
54358
54567
|
throw error46;
|
|
54359
54568
|
}
|
|
54360
54569
|
}
|
|
@@ -54368,14 +54577,14 @@ async function installUpdate(info, distribution, options = {}) {
|
|
|
54368
54577
|
}
|
|
54369
54578
|
|
|
54370
54579
|
// src/update-state.ts
|
|
54371
|
-
import { chmod as chmod4, mkdir as
|
|
54372
|
-
import
|
|
54580
|
+
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 path10 from "node:path";
|
|
54373
54582
|
var UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
54374
54583
|
function defaultUpdateChannel(version2) {
|
|
54375
54584
|
return version2.includes("-") ? "beta" : "stable";
|
|
54376
54585
|
}
|
|
54377
54586
|
function updateStateFile() {
|
|
54378
|
-
return
|
|
54587
|
+
return path10.join(configDirectory(), "updates.json");
|
|
54379
54588
|
}
|
|
54380
54589
|
function isPolicy(value) {
|
|
54381
54590
|
return value === "notify" || value === "auto" || value === "off";
|
|
@@ -54392,7 +54601,7 @@ async function loadUpdateState(currentVersion, file2 = updateStateFile()) {
|
|
|
54392
54601
|
channel: defaultUpdateChannel(currentVersion)
|
|
54393
54602
|
};
|
|
54394
54603
|
try {
|
|
54395
|
-
const parsed = JSON.parse(await
|
|
54604
|
+
const parsed = JSON.parse(await readFile5(file2, "utf8"));
|
|
54396
54605
|
const lastCheckedAt = optionalString(parsed.lastCheckedAt);
|
|
54397
54606
|
const latestVersion = optionalString(parsed.latestVersion);
|
|
54398
54607
|
return {
|
|
@@ -54409,11 +54618,11 @@ async function loadUpdateState(currentVersion, file2 = updateStateFile()) {
|
|
|
54409
54618
|
}
|
|
54410
54619
|
}
|
|
54411
54620
|
async function saveUpdateState(state, file2 = updateStateFile()) {
|
|
54412
|
-
const directory =
|
|
54621
|
+
const directory = path10.dirname(file2);
|
|
54413
54622
|
const temporary = `${file2}.${process.pid}.tmp`;
|
|
54414
|
-
await
|
|
54623
|
+
await mkdir6(directory, { recursive: true, mode: 448 });
|
|
54415
54624
|
try {
|
|
54416
|
-
await
|
|
54625
|
+
await writeFile7(temporary, `${JSON.stringify(state, null, 2)}
|
|
54417
54626
|
`, {
|
|
54418
54627
|
encoding: "utf8",
|
|
54419
54628
|
mode: 384
|
|
@@ -54421,7 +54630,7 @@ async function saveUpdateState(state, file2 = updateStateFile()) {
|
|
|
54421
54630
|
if (process.platform !== "win32") await chmod4(temporary, 384);
|
|
54422
54631
|
await rename3(temporary, file2);
|
|
54423
54632
|
} finally {
|
|
54424
|
-
await
|
|
54633
|
+
await rm6(temporary, { force: true });
|
|
54425
54634
|
}
|
|
54426
54635
|
}
|
|
54427
54636
|
async function configureUpdates(currentVersion, changes, file2 = updateStateFile()) {
|
|
@@ -54448,15 +54657,31 @@ function isUpdateCheckDue(lastCheckedAt, now = Date.now()) {
|
|
|
54448
54657
|
return !Number.isFinite(checkedAt) || now - checkedAt >= UPDATE_CHECK_INTERVAL_MS;
|
|
54449
54658
|
}
|
|
54450
54659
|
|
|
54660
|
+
// src/unpair.ts
|
|
54661
|
+
async function unpairComputer(dependencies = {}) {
|
|
54662
|
+
const load = dependencies.loadDeviceCredential ?? loadDeviceCredential;
|
|
54663
|
+
const remove = dependencies.deleteDeviceCredential ?? deleteDeviceCredential;
|
|
54664
|
+
const revoke = dependencies.revokePairedDevice ?? revokePairedDevice;
|
|
54665
|
+
const log = dependencies.log ?? console.log;
|
|
54666
|
+
const device = await load();
|
|
54667
|
+
if (!device) {
|
|
54668
|
+
log("This computer is not paired with Glossa.");
|
|
54669
|
+
return;
|
|
54670
|
+
}
|
|
54671
|
+
await revoke({ relayOrigin: device.relayOrigin }, device);
|
|
54672
|
+
await remove();
|
|
54673
|
+
log("Unpaired this computer from Glossa.");
|
|
54674
|
+
}
|
|
54675
|
+
|
|
54451
54676
|
// src/usage-store.ts
|
|
54452
|
-
import { appendFile, chmod as chmod5, mkdir as
|
|
54453
|
-
import
|
|
54677
|
+
import { appendFile, chmod as chmod5, mkdir as mkdir7, readFile as readFile6 } from "node:fs/promises";
|
|
54678
|
+
import path11 from "node:path";
|
|
54454
54679
|
function usageFile() {
|
|
54455
|
-
return
|
|
54680
|
+
return path11.join(configDirectory(), "usage.jsonl");
|
|
54456
54681
|
}
|
|
54457
54682
|
async function recordUsageEvent(event, file2 = usageFile()) {
|
|
54458
|
-
const directory =
|
|
54459
|
-
await
|
|
54683
|
+
const directory = path11.dirname(file2);
|
|
54684
|
+
await mkdir7(directory, { recursive: true, mode: 448 });
|
|
54460
54685
|
await appendFile(file2, `${JSON.stringify(event)}
|
|
54461
54686
|
`, {
|
|
54462
54687
|
encoding: "utf8",
|
|
@@ -54474,15 +54699,15 @@ async function recordToolUsage(tool, ok, at = /* @__PURE__ */ new Date(), file2
|
|
|
54474
54699
|
// src/worker/root-selection.ts
|
|
54475
54700
|
import { realpath as realpath2 } from "node:fs/promises";
|
|
54476
54701
|
import os6 from "node:os";
|
|
54477
|
-
import
|
|
54702
|
+
import path12 from "node:path";
|
|
54478
54703
|
function containsPath(root, candidate) {
|
|
54479
|
-
const relative =
|
|
54480
|
-
return relative !== "" && relative !== ".." && !relative.startsWith(`..${
|
|
54704
|
+
const relative = path12.relative(root, candidate);
|
|
54705
|
+
return relative !== "" && relative !== ".." && !relative.startsWith(`..${path12.sep}`) && !path12.isAbsolute(relative);
|
|
54481
54706
|
}
|
|
54482
54707
|
async function rejectImplicitHomeAncestor(root) {
|
|
54483
54708
|
const homes = await Promise.all(
|
|
54484
54709
|
[os6.homedir(), accountHomeDirectory()].map(
|
|
54485
|
-
async (home) => await realpath2(home).catch(() =>
|
|
54710
|
+
async (home) => await realpath2(home).catch(() => path12.resolve(home))
|
|
54486
54711
|
)
|
|
54487
54712
|
);
|
|
54488
54713
|
if (homes.some((home) => containsPath(root, home))) {
|
|
@@ -54500,10 +54725,10 @@ async function selectExposureRoot(explicitPath, cwd2 = process.cwd()) {
|
|
|
54500
54725
|
|
|
54501
54726
|
// src/worker/workspace-lease.ts
|
|
54502
54727
|
import { createHash as createHash3 } from "node:crypto";
|
|
54503
|
-
import { lstat as lstat3, mkdir as
|
|
54728
|
+
import { lstat as lstat3, mkdir as mkdir8, rm as rm7, stat as stat4 } from "node:fs/promises";
|
|
54504
54729
|
import net from "node:net";
|
|
54505
|
-
import
|
|
54506
|
-
import { setTimeout as
|
|
54730
|
+
import path13 from "node:path";
|
|
54731
|
+
import { setTimeout as delay4 } from "node:timers/promises";
|
|
54507
54732
|
var GUARD_STALE_MS = 5e3;
|
|
54508
54733
|
var GUARD_RETRY_MS = 10;
|
|
54509
54734
|
var GUARD_TIMEOUT_MS = 1e4;
|
|
@@ -54526,12 +54751,12 @@ function workspaceIdentity(root) {
|
|
|
54526
54751
|
}
|
|
54527
54752
|
function defaultLeaseDirectory() {
|
|
54528
54753
|
if (typeof process.getuid === "function") {
|
|
54529
|
-
return
|
|
54754
|
+
return path13.join("/tmp", `glossa-workspaces-${process.getuid()}`);
|
|
54530
54755
|
}
|
|
54531
|
-
return
|
|
54756
|
+
return path13.join(accountHomeDirectory(), ".glossa-workspace-leases");
|
|
54532
54757
|
}
|
|
54533
54758
|
async function ensureLeaseDirectory(directory) {
|
|
54534
|
-
await
|
|
54759
|
+
await mkdir8(directory, { recursive: true, mode: 448 });
|
|
54535
54760
|
const directoryStat = await lstat3(directory);
|
|
54536
54761
|
if (directoryStat.isSymbolicLink() || !directoryStat.isDirectory()) {
|
|
54537
54762
|
throw new Error("The Glossa workspace lease location is not a private directory.");
|
|
@@ -54548,26 +54773,26 @@ function endpointFor(identity, directory) {
|
|
|
54548
54773
|
if (process.platform === "win32") {
|
|
54549
54774
|
return `\\\\.\\pipe\\glossa-workspace-${identity}`;
|
|
54550
54775
|
}
|
|
54551
|
-
return
|
|
54776
|
+
return path13.join(directory, `${identity}.sock`);
|
|
54552
54777
|
}
|
|
54553
|
-
async function
|
|
54554
|
-
const guard =
|
|
54778
|
+
async function acquireGuard2(directory, identity) {
|
|
54779
|
+
const guard = path13.join(directory, `${identity}.guard`);
|
|
54555
54780
|
const deadline = Date.now() + GUARD_TIMEOUT_MS;
|
|
54556
54781
|
while (true) {
|
|
54557
54782
|
try {
|
|
54558
|
-
await
|
|
54559
|
-
return async () => await
|
|
54783
|
+
await mkdir8(guard, { mode: 448 });
|
|
54784
|
+
return async () => await rm7(guard, { recursive: true, force: true });
|
|
54560
54785
|
} catch (error46) {
|
|
54561
54786
|
if (error46.code !== "EEXIST") throw error46;
|
|
54562
54787
|
let stale = false;
|
|
54563
54788
|
try {
|
|
54564
|
-
stale = Date.now() - (await
|
|
54789
|
+
stale = Date.now() - (await stat4(guard)).mtimeMs >= GUARD_STALE_MS;
|
|
54565
54790
|
} catch (statError) {
|
|
54566
54791
|
if (statError.code === "ENOENT") continue;
|
|
54567
54792
|
throw statError;
|
|
54568
54793
|
}
|
|
54569
54794
|
if (stale) {
|
|
54570
|
-
await
|
|
54795
|
+
await rm7(guard, { recursive: true, force: true });
|
|
54571
54796
|
continue;
|
|
54572
54797
|
}
|
|
54573
54798
|
if (Date.now() >= deadline) {
|
|
@@ -54575,7 +54800,7 @@ async function acquireGuard(directory, identity) {
|
|
|
54575
54800
|
"Timed out while checking whether this workspace is already active."
|
|
54576
54801
|
);
|
|
54577
54802
|
}
|
|
54578
|
-
await
|
|
54803
|
+
await delay4(GUARD_RETRY_MS);
|
|
54579
54804
|
}
|
|
54580
54805
|
}
|
|
54581
54806
|
}
|
|
@@ -54623,7 +54848,7 @@ async function acquireWorkspaceLease(root, options = {}) {
|
|
|
54623
54848
|
await ensureLeaseDirectory(userDirectory);
|
|
54624
54849
|
const identity = workspaceIdentity(root);
|
|
54625
54850
|
const endpoint3 = endpointFor(identity, userDirectory);
|
|
54626
|
-
const releaseGuard = await
|
|
54851
|
+
const releaseGuard = await acquireGuard2(userDirectory, identity);
|
|
54627
54852
|
let server;
|
|
54628
54853
|
try {
|
|
54629
54854
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
@@ -54637,11 +54862,11 @@ async function acquireWorkspaceLease(root, options = {}) {
|
|
|
54637
54862
|
if (released) return;
|
|
54638
54863
|
released = true;
|
|
54639
54864
|
await ensureLeaseDirectory(userDirectory);
|
|
54640
|
-
const releaseGuard2 = await
|
|
54865
|
+
const releaseGuard2 = await acquireGuard2(userDirectory, identity);
|
|
54641
54866
|
try {
|
|
54642
54867
|
await closeServer(server);
|
|
54643
54868
|
if (process.platform !== "win32") {
|
|
54644
|
-
await
|
|
54869
|
+
await rm7(endpoint3, { force: true });
|
|
54645
54870
|
}
|
|
54646
54871
|
} finally {
|
|
54647
54872
|
await releaseGuard2();
|
|
@@ -54654,7 +54879,7 @@ async function acquireWorkspaceLease(root, options = {}) {
|
|
|
54654
54879
|
throw new WorkspaceAlreadyActiveError();
|
|
54655
54880
|
}
|
|
54656
54881
|
if (error46.code !== "EADDRINUSE") throw error46;
|
|
54657
|
-
if (process.platform !== "win32") await
|
|
54882
|
+
if (process.platform !== "win32") await rm7(endpoint3, { force: true });
|
|
54658
54883
|
}
|
|
54659
54884
|
}
|
|
54660
54885
|
throw new Error("Could not reserve this workspace for the current Glossa process.");
|
|
@@ -54664,7 +54889,7 @@ async function acquireWorkspaceLease(root, options = {}) {
|
|
|
54664
54889
|
}
|
|
54665
54890
|
|
|
54666
54891
|
// src/main.ts
|
|
54667
|
-
var VERSION = "0.2.
|
|
54892
|
+
var VERSION = "0.2.1";
|
|
54668
54893
|
var DISTRIBUTION = "npm";
|
|
54669
54894
|
var HELP = `Glossa ${VERSION}
|
|
54670
54895
|
|
|
@@ -54673,6 +54898,7 @@ Usage:
|
|
|
54673
54898
|
glossa status
|
|
54674
54899
|
glossa devices revoke <id>
|
|
54675
54900
|
glossa logout
|
|
54901
|
+
glossa unpair
|
|
54676
54902
|
glossa update [--check]
|
|
54677
54903
|
glossa update --policy <notify|auto|off>
|
|
54678
54904
|
glossa update --channel <beta|stable>
|
|
@@ -54692,8 +54918,8 @@ Keys:
|
|
|
54692
54918
|
\u2191\u2193 select or browse
|
|
54693
54919
|
\u2190\u2192 change workspace access
|
|
54694
54920
|
Enter/r revoke selected device
|
|
54695
|
-
Esc
|
|
54696
|
-
l sign out
|
|
54921
|
+
Esc workspace
|
|
54922
|
+
l account sign out
|
|
54697
54923
|
? help
|
|
54698
54924
|
q disconnect and quit`;
|
|
54699
54925
|
async function withLoginSignal(action) {
|
|
@@ -54740,8 +54966,8 @@ async function revokeKnownDevice(deviceId) {
|
|
|
54740
54966
|
const credentials = await authenticatedCredentials();
|
|
54741
54967
|
await revokeDevice(loadRelayEndpoints(), credentials, deviceId);
|
|
54742
54968
|
}
|
|
54743
|
-
async function runWorkspaceSession(
|
|
54744
|
-
const root = await selectExposureRoot(
|
|
54969
|
+
async function runWorkspaceSession(path14, label, accessProfile) {
|
|
54970
|
+
const root = await selectExposureRoot(path14);
|
|
54745
54971
|
const lease = await acquireWorkspaceLease(root);
|
|
54746
54972
|
let usageWrites = Promise.resolve();
|
|
54747
54973
|
const queueUsage = (write) => {
|
|
@@ -54749,8 +54975,7 @@ async function runWorkspaceSession(path13, label, accessProfile) {
|
|
|
54749
54975
|
};
|
|
54750
54976
|
try {
|
|
54751
54977
|
const endpoints = loadRelayEndpoints();
|
|
54752
|
-
|
|
54753
|
-
const statusService = new WorkspaceStatusService(credentials, endpoints);
|
|
54978
|
+
const device = await deviceForSession(endpoints);
|
|
54754
54979
|
let postExitNotice;
|
|
54755
54980
|
let requestedAccessProfile = accessProfile;
|
|
54756
54981
|
let activeSessionController;
|
|
@@ -54768,7 +54993,7 @@ async function runWorkspaceSession(path13, label, accessProfile) {
|
|
|
54768
54993
|
else signal.addEventListener("abort", stopSession, { once: true });
|
|
54769
54994
|
try {
|
|
54770
54995
|
await runManagedSession(root, endpoints, {
|
|
54771
|
-
|
|
54996
|
+
device,
|
|
54772
54997
|
workerVersion: VERSION,
|
|
54773
54998
|
accessProfile: sessionAccessProfile,
|
|
54774
54999
|
...label ? { workspaceLabel: label } : {},
|
|
@@ -54799,10 +55024,13 @@ async function runWorkspaceSession(path13, label, accessProfile) {
|
|
|
54799
55024
|
}
|
|
54800
55025
|
},
|
|
54801
55026
|
loadStatus: async (signal) => {
|
|
54802
|
-
|
|
55027
|
+
const credentials = await authenticatedCredentials(signal);
|
|
55028
|
+
return hudStatus(
|
|
55029
|
+
await new WorkspaceStatusService(credentials, endpoints).refresh(signal)
|
|
55030
|
+
);
|
|
54803
55031
|
},
|
|
54804
55032
|
revokeDevice: async (deviceId, signal) => {
|
|
54805
|
-
credentials = await
|
|
55033
|
+
const credentials = await authenticatedCredentials(signal);
|
|
54806
55034
|
await revokeDevice(
|
|
54807
55035
|
endpoints,
|
|
54808
55036
|
credentials,
|
|
@@ -54823,9 +55051,9 @@ async function runWorkspaceSession(path13, label, accessProfile) {
|
|
|
54823
55051
|
await lease.release();
|
|
54824
55052
|
}
|
|
54825
55053
|
}
|
|
54826
|
-
async function runWorkspace(
|
|
55054
|
+
async function runWorkspace(path14, label, accessProfile) {
|
|
54827
55055
|
await withWorkspaceLease(
|
|
54828
|
-
async () => await runWorkspaceSession(
|
|
55056
|
+
async () => await runWorkspaceSession(path14, label, accessProfile)
|
|
54829
55057
|
);
|
|
54830
55058
|
}
|
|
54831
55059
|
async function refreshUpdateInfo(timeoutMs) {
|
|
@@ -54928,6 +55156,8 @@ async function main() {
|
|
|
54928
55156
|
await showStatus();
|
|
54929
55157
|
} else if (invocation.command === "logout") {
|
|
54930
55158
|
await logoutFromGlossa();
|
|
55159
|
+
} else if (invocation.command === "unpair") {
|
|
55160
|
+
await unpairComputer();
|
|
54931
55161
|
} else if (invocation.command === "update") {
|
|
54932
55162
|
await runUpdateCommand(invocation);
|
|
54933
55163
|
} else {
|