ctxloom-pro 1.1.1 → 1.1.3
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/apps/dashboard/dist/dashboard/client/assets/{index-Bfa2_GyA.js → index-DNFP0Mer.js} +51 -51
- package/apps/dashboard/dist/dashboard/client/index.html +1 -1
- package/apps/dashboard/dist/server/index.js +275 -57
- package/dist/{chunk-CXKKREER.js → chunk-POGILISN.js} +119 -31
- package/dist/index.js +26 -27
- package/dist/{src-JI5LH2V7.js → src-2I4ZNYVG.js} +6 -2
- package/package.json +1 -1
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>ctxloom dashboard</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-DNFP0Mer.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="/assets/index-MBoNDzdn.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
@@ -169,12 +169,12 @@ var init_VectorStore = __esm({
|
|
|
169
169
|
// server/index.ts
|
|
170
170
|
import express from "express";
|
|
171
171
|
import cors from "cors";
|
|
172
|
-
import
|
|
172
|
+
import path42 from "path";
|
|
173
173
|
import fs32 from "fs";
|
|
174
174
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
175
175
|
|
|
176
176
|
// server/loader.ts
|
|
177
|
-
import
|
|
177
|
+
import path36 from "path";
|
|
178
178
|
|
|
179
179
|
// ../../packages/core/src/graph/DependencyGraph.ts
|
|
180
180
|
import fs7 from "fs";
|
|
@@ -3133,8 +3133,8 @@ var CoChangeIndex = class _CoChangeIndex {
|
|
|
3133
3133
|
if (event.isBulk || event.isMerge) return;
|
|
3134
3134
|
const paths = event.files.map((f) => f.path);
|
|
3135
3135
|
if (paths.length === 0) return;
|
|
3136
|
-
for (const
|
|
3137
|
-
this.nodeCounts.set(
|
|
3136
|
+
for (const path43 of paths) {
|
|
3137
|
+
this.nodeCounts.set(path43, (this.nodeCounts.get(path43) ?? 0) + 1);
|
|
3138
3138
|
}
|
|
3139
3139
|
for (let i = 0; i < paths.length; i++) {
|
|
3140
3140
|
for (let j = i + 1; j < paths.length; j++) {
|
|
@@ -3281,8 +3281,8 @@ var ChurnIndex = class _ChurnIndex {
|
|
|
3281
3281
|
*/
|
|
3282
3282
|
snapshot() {
|
|
3283
3283
|
const nodes = {};
|
|
3284
|
-
for (const [
|
|
3285
|
-
nodes[
|
|
3284
|
+
for (const [path43, raw] of this.nodes) {
|
|
3285
|
+
nodes[path43] = {
|
|
3286
3286
|
commits: raw.commits,
|
|
3287
3287
|
churnLines: raw.churnLines,
|
|
3288
3288
|
bugCommits: raw.bugCommits,
|
|
@@ -3297,8 +3297,8 @@ var ChurnIndex = class _ChurnIndex {
|
|
|
3297
3297
|
*/
|
|
3298
3298
|
static load(s) {
|
|
3299
3299
|
const idx = new _ChurnIndex();
|
|
3300
|
-
for (const [
|
|
3301
|
-
idx.nodes.set(
|
|
3300
|
+
for (const [path43, raw] of Object.entries(s.nodes)) {
|
|
3301
|
+
idx.nodes.set(path43, {
|
|
3302
3302
|
commits: raw.commits,
|
|
3303
3303
|
churnLines: raw.churnLines,
|
|
3304
3304
|
bugCommits: raw.bugCommits,
|
|
@@ -3311,8 +3311,8 @@ var ChurnIndex = class _ChurnIndex {
|
|
|
3311
3311
|
// -------------------------------------------------------------------------
|
|
3312
3312
|
// Private helpers
|
|
3313
3313
|
// -------------------------------------------------------------------------
|
|
3314
|
-
getOrCreate(
|
|
3315
|
-
const existing = this.nodes.get(
|
|
3314
|
+
getOrCreate(path43) {
|
|
3315
|
+
const existing = this.nodes.get(path43);
|
|
3316
3316
|
if (existing !== void 0) return existing;
|
|
3317
3317
|
const fresh = {
|
|
3318
3318
|
commits: 0,
|
|
@@ -3321,7 +3321,7 @@ var ChurnIndex = class _ChurnIndex {
|
|
|
3321
3321
|
authorCounts: {},
|
|
3322
3322
|
lastTouch: 0
|
|
3323
3323
|
};
|
|
3324
|
-
this.nodes.set(
|
|
3324
|
+
this.nodes.set(path43, fresh);
|
|
3325
3325
|
return fresh;
|
|
3326
3326
|
}
|
|
3327
3327
|
};
|
|
@@ -3402,12 +3402,12 @@ var OwnershipIndex = class _OwnershipIndex {
|
|
|
3402
3402
|
*/
|
|
3403
3403
|
snapshot() {
|
|
3404
3404
|
const nodes = {};
|
|
3405
|
-
for (const [
|
|
3405
|
+
for (const [path43, raw] of this.nodes) {
|
|
3406
3406
|
const authorWeights = {};
|
|
3407
3407
|
for (const [email, entry] of Object.entries(raw.authorWeights)) {
|
|
3408
3408
|
authorWeights[email] = { ...entry };
|
|
3409
3409
|
}
|
|
3410
|
-
nodes[
|
|
3410
|
+
nodes[path43] = { authorWeights, lastTouch: raw.lastTouch };
|
|
3411
3411
|
}
|
|
3412
3412
|
return { version: 1, nodes };
|
|
3413
3413
|
}
|
|
@@ -3416,23 +3416,23 @@ var OwnershipIndex = class _OwnershipIndex {
|
|
|
3416
3416
|
*/
|
|
3417
3417
|
static load(s) {
|
|
3418
3418
|
const idx = new _OwnershipIndex();
|
|
3419
|
-
for (const [
|
|
3419
|
+
for (const [path43, raw] of Object.entries(s.nodes)) {
|
|
3420
3420
|
const authorWeights = {};
|
|
3421
3421
|
for (const [email, entry] of Object.entries(raw.authorWeights)) {
|
|
3422
3422
|
authorWeights[email] = { ...entry };
|
|
3423
3423
|
}
|
|
3424
|
-
idx.nodes.set(
|
|
3424
|
+
idx.nodes.set(path43, { authorWeights, lastTouch: raw.lastTouch });
|
|
3425
3425
|
}
|
|
3426
3426
|
return idx;
|
|
3427
3427
|
}
|
|
3428
3428
|
// -------------------------------------------------------------------------
|
|
3429
3429
|
// Private helpers
|
|
3430
3430
|
// -------------------------------------------------------------------------
|
|
3431
|
-
getOrCreate(
|
|
3432
|
-
const existing = this.nodes.get(
|
|
3431
|
+
getOrCreate(path43) {
|
|
3432
|
+
const existing = this.nodes.get(path43);
|
|
3433
3433
|
if (existing !== void 0) return existing;
|
|
3434
3434
|
const fresh = { authorWeights: {}, lastTouch: 0 };
|
|
3435
|
-
this.nodes.set(
|
|
3435
|
+
this.nodes.set(path43, fresh);
|
|
3436
3436
|
return fresh;
|
|
3437
3437
|
}
|
|
3438
3438
|
};
|
|
@@ -4458,8 +4458,8 @@ var ZodIssueCode = util.arrayToEnum([
|
|
|
4458
4458
|
"not_finite"
|
|
4459
4459
|
]);
|
|
4460
4460
|
var quotelessJson = (obj) => {
|
|
4461
|
-
const
|
|
4462
|
-
return
|
|
4461
|
+
const json3 = JSON.stringify(obj, null, 2);
|
|
4462
|
+
return json3.replace(/"([^"]+)":/g, "$1:");
|
|
4463
4463
|
};
|
|
4464
4464
|
var ZodError = class _ZodError extends Error {
|
|
4465
4465
|
get errors() {
|
|
@@ -4670,8 +4670,8 @@ function getErrorMap() {
|
|
|
4670
4670
|
|
|
4671
4671
|
// ../../node_modules/zod/v3/helpers/parseUtil.js
|
|
4672
4672
|
var makeIssue = (params) => {
|
|
4673
|
-
const { data, path:
|
|
4674
|
-
const fullPath = [...
|
|
4673
|
+
const { data, path: path43, errorMaps, issueData } = params;
|
|
4674
|
+
const fullPath = [...path43, ...issueData.path || []];
|
|
4675
4675
|
const fullIssue = {
|
|
4676
4676
|
...issueData,
|
|
4677
4677
|
path: fullPath
|
|
@@ -4787,11 +4787,11 @@ var errorUtil;
|
|
|
4787
4787
|
|
|
4788
4788
|
// ../../node_modules/zod/v3/types.js
|
|
4789
4789
|
var ParseInputLazyPath = class {
|
|
4790
|
-
constructor(parent, value,
|
|
4790
|
+
constructor(parent, value, path43, key) {
|
|
4791
4791
|
this._cachedPath = [];
|
|
4792
4792
|
this.parent = parent;
|
|
4793
4793
|
this.data = value;
|
|
4794
|
-
this._path =
|
|
4794
|
+
this._path = path43;
|
|
4795
4795
|
this._key = key;
|
|
4796
4796
|
}
|
|
4797
4797
|
get path() {
|
|
@@ -11268,30 +11268,198 @@ import { readFileSync as readFileSync2 } from "fs";
|
|
|
11268
11268
|
// ../../packages/core/src/license/index.ts
|
|
11269
11269
|
import os3 from "os";
|
|
11270
11270
|
|
|
11271
|
+
// ../../packages/core/src/license/DistinctIdStore.ts
|
|
11272
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2 } from "fs";
|
|
11273
|
+
import path32 from "path";
|
|
11274
|
+
import os4 from "os";
|
|
11275
|
+
var UUID_V4_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
11276
|
+
function distinctIdPath(home) {
|
|
11277
|
+
return path32.join(home ?? os4.homedir(), ".ctxloom", "distinct_id");
|
|
11278
|
+
}
|
|
11279
|
+
function isValidV4(id) {
|
|
11280
|
+
return typeof id === "string" && UUID_V4_REGEX.test(id);
|
|
11281
|
+
}
|
|
11282
|
+
function getOrCreateDistinctId(home) {
|
|
11283
|
+
const filePath = distinctIdPath(home);
|
|
11284
|
+
if (existsSync2(filePath)) {
|
|
11285
|
+
try {
|
|
11286
|
+
const raw = readFileSync3(filePath, "utf8");
|
|
11287
|
+
const parsed = JSON.parse(raw);
|
|
11288
|
+
if (parsed !== null && typeof parsed === "object" && isValidV4(parsed["id"])) {
|
|
11289
|
+
return parsed;
|
|
11290
|
+
}
|
|
11291
|
+
} catch {
|
|
11292
|
+
}
|
|
11293
|
+
}
|
|
11294
|
+
const record = {
|
|
11295
|
+
id: crypto.randomUUID(),
|
|
11296
|
+
alias_pending: os4.hostname()
|
|
11297
|
+
};
|
|
11298
|
+
mkdirSync2(path32.dirname(filePath), { recursive: true });
|
|
11299
|
+
writeFileSync2(filePath, JSON.stringify(record), { mode: 384 });
|
|
11300
|
+
return record;
|
|
11301
|
+
}
|
|
11302
|
+
function markAliasSent(home) {
|
|
11303
|
+
const filePath = distinctIdPath(home);
|
|
11304
|
+
try {
|
|
11305
|
+
const raw = readFileSync3(filePath, "utf8");
|
|
11306
|
+
const parsed = JSON.parse(raw);
|
|
11307
|
+
const { alias_pending: _dropped, ...rest } = parsed;
|
|
11308
|
+
writeFileSync2(filePath, JSON.stringify(rest), { mode: 384 });
|
|
11309
|
+
} catch {
|
|
11310
|
+
}
|
|
11311
|
+
}
|
|
11312
|
+
|
|
11271
11313
|
// ../../packages/core/src/license/telemetry.ts
|
|
11272
11314
|
var TELEMETRY_DISABLED = process.env["CTXLOOM_NO_TELEMETRY"] === "1" || process.env["DO_NOT_TRACK"] === "1";
|
|
11273
11315
|
var CTXLOOM_VERSION = typeof __CTXLOOM_VERSION__ === "string" && __CTXLOOM_VERSION__.length > 0 ? __CTXLOOM_VERSION__ : "dev";
|
|
11316
|
+
var POSTHOG_HOST = "https://eu.i.posthog.com";
|
|
11274
11317
|
var POSTHOG_KEY = process.env["POSTHOG_API_KEY"] ?? (typeof __TELEMETRY_POSTHOG_KEY__ === "string" ? __TELEMETRY_POSTHOG_KEY__ : "");
|
|
11275
11318
|
var SENTRY_DSN = process.env["SENTRY_DSN"] ?? (typeof __TELEMETRY_SENTRY_DSN__ === "string" ? __TELEMETRY_SENTRY_DSN__ : "");
|
|
11319
|
+
var cachedDistinctId = null;
|
|
11320
|
+
function resolveDistinctId() {
|
|
11321
|
+
if (cachedDistinctId) return cachedDistinctId;
|
|
11322
|
+
try {
|
|
11323
|
+
cachedDistinctId = getOrCreateDistinctId();
|
|
11324
|
+
return cachedDistinctId;
|
|
11325
|
+
} catch {
|
|
11326
|
+
return null;
|
|
11327
|
+
}
|
|
11328
|
+
}
|
|
11329
|
+
function track(event, props = {}) {
|
|
11330
|
+
if (TELEMETRY_DISABLED || !POSTHOG_KEY) return;
|
|
11331
|
+
const record = resolveDistinctId();
|
|
11332
|
+
if (!record) return;
|
|
11333
|
+
void sendPostHog(event, record.id, props);
|
|
11334
|
+
if (record.alias_pending) {
|
|
11335
|
+
const oldAlias = record.alias_pending;
|
|
11336
|
+
void sendAlias(record.id, oldAlias);
|
|
11337
|
+
}
|
|
11338
|
+
}
|
|
11339
|
+
function captureError(err, context = {}) {
|
|
11340
|
+
if (TELEMETRY_DISABLED || !SENTRY_DSN) return;
|
|
11341
|
+
const record = resolveDistinctId();
|
|
11342
|
+
const augmented = record ? { ...context, distinct_id: record.id } : context;
|
|
11343
|
+
void sendSentry(err, augmented);
|
|
11344
|
+
}
|
|
11345
|
+
async function sendPostHog(event, distinctId, props) {
|
|
11346
|
+
try {
|
|
11347
|
+
await fetch(`${POSTHOG_HOST}/capture/`, {
|
|
11348
|
+
method: "POST",
|
|
11349
|
+
headers: { "Content-Type": "application/json" },
|
|
11350
|
+
body: JSON.stringify({
|
|
11351
|
+
api_key: POSTHOG_KEY,
|
|
11352
|
+
distinct_id: distinctId,
|
|
11353
|
+
event,
|
|
11354
|
+
properties: {
|
|
11355
|
+
$lib: "ctxloom-cli",
|
|
11356
|
+
release: CTXLOOM_VERSION,
|
|
11357
|
+
...props
|
|
11358
|
+
}
|
|
11359
|
+
}),
|
|
11360
|
+
signal: AbortSignal.timeout(4e3)
|
|
11361
|
+
});
|
|
11362
|
+
} catch {
|
|
11363
|
+
}
|
|
11364
|
+
}
|
|
11365
|
+
async function sendAlias(newId, oldAlias) {
|
|
11366
|
+
try {
|
|
11367
|
+
const res = await fetch(`${POSTHOG_HOST}/capture/`, {
|
|
11368
|
+
method: "POST",
|
|
11369
|
+
headers: { "Content-Type": "application/json" },
|
|
11370
|
+
body: JSON.stringify({
|
|
11371
|
+
api_key: POSTHOG_KEY,
|
|
11372
|
+
distinct_id: newId,
|
|
11373
|
+
event: "$create_alias",
|
|
11374
|
+
properties: {
|
|
11375
|
+
$lib: "ctxloom-cli",
|
|
11376
|
+
release: CTXLOOM_VERSION,
|
|
11377
|
+
alias: oldAlias
|
|
11378
|
+
}
|
|
11379
|
+
}),
|
|
11380
|
+
signal: AbortSignal.timeout(4e3)
|
|
11381
|
+
});
|
|
11382
|
+
if (res.ok) {
|
|
11383
|
+
try {
|
|
11384
|
+
markAliasSent();
|
|
11385
|
+
} catch {
|
|
11386
|
+
}
|
|
11387
|
+
if (cachedDistinctId) cachedDistinctId = { id: cachedDistinctId.id };
|
|
11388
|
+
}
|
|
11389
|
+
} catch {
|
|
11390
|
+
}
|
|
11391
|
+
}
|
|
11392
|
+
async function sendSentry(err, context) {
|
|
11393
|
+
try {
|
|
11394
|
+
const dsn = parseDsn(SENTRY_DSN);
|
|
11395
|
+
if (!dsn) return;
|
|
11396
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
11397
|
+
const stack = err instanceof Error ? err.stack : void 0;
|
|
11398
|
+
await fetch(`https://${dsn.host}/api/${dsn.projectId}/store/`, {
|
|
11399
|
+
method: "POST",
|
|
11400
|
+
headers: {
|
|
11401
|
+
"Content-Type": "application/json",
|
|
11402
|
+
"X-Sentry-Auth": `Sentry sentry_version=7, sentry_key=${dsn.key}`
|
|
11403
|
+
},
|
|
11404
|
+
body: JSON.stringify({
|
|
11405
|
+
event_id: crypto.randomUUID().replace(/-/g, ""),
|
|
11406
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11407
|
+
platform: "node",
|
|
11408
|
+
level: "error",
|
|
11409
|
+
exception: {
|
|
11410
|
+
values: [
|
|
11411
|
+
{
|
|
11412
|
+
type: err instanceof Error ? err.constructor.name : "Error",
|
|
11413
|
+
value: message,
|
|
11414
|
+
stacktrace: stack ? { frames: parseStack(stack) } : void 0
|
|
11415
|
+
}
|
|
11416
|
+
]
|
|
11417
|
+
},
|
|
11418
|
+
extra: context,
|
|
11419
|
+
tags: { runtime: "node", component: "cli-license", release: CTXLOOM_VERSION }
|
|
11420
|
+
}),
|
|
11421
|
+
signal: AbortSignal.timeout(4e3)
|
|
11422
|
+
});
|
|
11423
|
+
} catch {
|
|
11424
|
+
}
|
|
11425
|
+
}
|
|
11426
|
+
function parseDsn(dsn) {
|
|
11427
|
+
try {
|
|
11428
|
+
const url = new URL(dsn);
|
|
11429
|
+
const projectId = url.pathname.replace(/^\//, "");
|
|
11430
|
+
return { host: url.hostname, key: url.username, projectId };
|
|
11431
|
+
} catch {
|
|
11432
|
+
return null;
|
|
11433
|
+
}
|
|
11434
|
+
}
|
|
11435
|
+
function scrubPath(filename) {
|
|
11436
|
+
return filename.replace(/^\/Users\/[^/]+\//, "/Users/~/").replace(/^\/home\/[^/]+\//, "/home/~/").replace(/^([A-Z]:\\\\Users\\\\)[^\\]+\\\\/, "$1~\\\\").replace(/^([A-Z]:\\Users\\)[^\\]+\\/, "$1~\\");
|
|
11437
|
+
}
|
|
11438
|
+
function parseStack(stack) {
|
|
11439
|
+
return stack.split("\n").slice(1).map((line) => {
|
|
11440
|
+
const m = line.trim().match(/at (.+?) \((.+?):(\d+):\d+\)/);
|
|
11441
|
+
if (!m) return null;
|
|
11442
|
+
return { function: m[1] ?? "", filename: scrubPath(m[2] ?? ""), lineno: Number(m[3]) };
|
|
11443
|
+
}).filter((f) => f !== null).slice(0, 20);
|
|
11444
|
+
}
|
|
11276
11445
|
|
|
11277
11446
|
// ../../packages/core/src/server/ProjectState.ts
|
|
11278
|
-
import
|
|
11447
|
+
import path34 from "path";
|
|
11279
11448
|
|
|
11280
11449
|
// ../../packages/core/src/server/projectId.ts
|
|
11281
11450
|
import crypto5 from "crypto";
|
|
11282
|
-
import
|
|
11451
|
+
import path33 from "path";
|
|
11283
11452
|
|
|
11284
11453
|
// ../../packages/core/src/server/ProjectStateManager.ts
|
|
11285
11454
|
init_logger();
|
|
11286
|
-
import os4 from "os";
|
|
11287
11455
|
|
|
11288
11456
|
// ../../packages/core/src/server/resolveProjectRoot.ts
|
|
11289
11457
|
import fs29 from "fs";
|
|
11290
|
-
import
|
|
11458
|
+
import path35 from "path";
|
|
11291
11459
|
|
|
11292
11460
|
// server/loader.ts
|
|
11293
11461
|
async function loadContext(root) {
|
|
11294
|
-
const absRoot =
|
|
11462
|
+
const absRoot = path36.resolve(root);
|
|
11295
11463
|
const overlay = new GitOverlayStore(absRoot);
|
|
11296
11464
|
const gitEnabled = await overlay.loadSnapshot();
|
|
11297
11465
|
const graph = new DependencyGraph();
|
|
@@ -11545,20 +11713,20 @@ function buildOwnershipRouter(ctx) {
|
|
|
11545
11713
|
// server/routes/file.ts
|
|
11546
11714
|
import { Router as Router7 } from "express";
|
|
11547
11715
|
import fs30 from "fs/promises";
|
|
11548
|
-
import
|
|
11716
|
+
import path37 from "path";
|
|
11549
11717
|
function buildFileRouter(ctx) {
|
|
11550
11718
|
const router = Router7();
|
|
11551
11719
|
router.get("/", async (req, res) => {
|
|
11552
11720
|
const rel = req.query.path;
|
|
11553
11721
|
if (!rel) return res.status(400).json({ error: "missing path" });
|
|
11554
|
-
const abs =
|
|
11555
|
-
const rootBoundary = ctx.root.endsWith(
|
|
11722
|
+
const abs = path37.resolve(ctx.root, rel);
|
|
11723
|
+
const rootBoundary = ctx.root.endsWith(path37.sep) ? ctx.root : ctx.root + path37.sep;
|
|
11556
11724
|
if (abs !== ctx.root && !abs.startsWith(rootBoundary)) {
|
|
11557
11725
|
return res.status(403).json({ error: "forbidden" });
|
|
11558
11726
|
}
|
|
11559
11727
|
try {
|
|
11560
11728
|
const content = await fs30.readFile(abs, "utf-8");
|
|
11561
|
-
const ext =
|
|
11729
|
+
const ext = path37.extname(abs).slice(1);
|
|
11562
11730
|
res.json({ content, lines: content.split("\n").length, ext });
|
|
11563
11731
|
} catch {
|
|
11564
11732
|
res.status(404).json({ error: "not found" });
|
|
@@ -11570,7 +11738,7 @@ function buildFileRouter(ctx) {
|
|
|
11570
11738
|
// server/routes/open.ts
|
|
11571
11739
|
import { Router as Router8 } from "express";
|
|
11572
11740
|
import { execFile as execFile2 } from "child_process";
|
|
11573
|
-
import
|
|
11741
|
+
import path38 from "path";
|
|
11574
11742
|
function tryOpen(bin, abs) {
|
|
11575
11743
|
return new Promise((resolve) => {
|
|
11576
11744
|
execFile2(bin, [abs], { timeout: 5e3 }, (err) => resolve(!err));
|
|
@@ -11581,8 +11749,8 @@ function buildOpenRouter(ctx) {
|
|
|
11581
11749
|
router.post("/", async (req, res) => {
|
|
11582
11750
|
const rel = req.body?.path;
|
|
11583
11751
|
if (!rel || typeof rel !== "string") return res.status(400).json({ error: "missing path" });
|
|
11584
|
-
const abs =
|
|
11585
|
-
const rootBoundary = ctx.root.endsWith(
|
|
11752
|
+
const abs = path38.resolve(ctx.root, rel);
|
|
11753
|
+
const rootBoundary = ctx.root.endsWith(path38.sep) ? ctx.root : ctx.root + path38.sep;
|
|
11586
11754
|
if (abs !== ctx.root && !abs.startsWith(rootBoundary)) {
|
|
11587
11755
|
return res.status(403).json({ error: "forbidden" });
|
|
11588
11756
|
}
|
|
@@ -11594,7 +11762,7 @@ function buildOpenRouter(ctx) {
|
|
|
11594
11762
|
|
|
11595
11763
|
// server/routes/tokens.ts
|
|
11596
11764
|
import { Router as Router9 } from "express";
|
|
11597
|
-
import
|
|
11765
|
+
import path39 from "path";
|
|
11598
11766
|
import fs31 from "fs";
|
|
11599
11767
|
var CHARS_PER_TOKEN = 4;
|
|
11600
11768
|
var cache = null;
|
|
@@ -11610,7 +11778,7 @@ function buildTokensRouter(ctx) {
|
|
|
11610
11778
|
let fullChars = 0;
|
|
11611
11779
|
let skeletonChars = 0;
|
|
11612
11780
|
for (const file of files) {
|
|
11613
|
-
const absPath =
|
|
11781
|
+
const absPath = path39.join(ctx.root, file);
|
|
11614
11782
|
try {
|
|
11615
11783
|
const content = fs31.readFileSync(absPath, "utf-8");
|
|
11616
11784
|
fullChars += content.length;
|
|
@@ -11713,23 +11881,23 @@ function buildFileTrendsRouter(ctx) {
|
|
|
11713
11881
|
|
|
11714
11882
|
// server/routes/projects.ts
|
|
11715
11883
|
import { Router as Router12 } from "express";
|
|
11716
|
-
import
|
|
11884
|
+
import path41 from "path";
|
|
11717
11885
|
|
|
11718
11886
|
// server/projects.ts
|
|
11719
|
-
import { existsSync as
|
|
11887
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
|
|
11720
11888
|
import os5 from "os";
|
|
11721
|
-
import
|
|
11889
|
+
import path40 from "path";
|
|
11722
11890
|
import crypto6 from "crypto";
|
|
11723
11891
|
var HOME = os5.homedir();
|
|
11724
|
-
var REGISTRY_PATH =
|
|
11892
|
+
var REGISTRY_PATH = path40.join(HOME, ".ctxloom", "repos.json");
|
|
11725
11893
|
function slugFor(root) {
|
|
11726
|
-
const abs =
|
|
11894
|
+
const abs = path40.resolve(root);
|
|
11727
11895
|
return crypto6.createHash("sha1").update(abs).digest("hex").slice(0, 12);
|
|
11728
11896
|
}
|
|
11729
11897
|
function readRegistry() {
|
|
11730
|
-
if (!
|
|
11898
|
+
if (!existsSync3(REGISTRY_PATH)) return [];
|
|
11731
11899
|
try {
|
|
11732
|
-
const raw =
|
|
11900
|
+
const raw = readFileSync4(REGISTRY_PATH, "utf-8");
|
|
11733
11901
|
const parsed = JSON.parse(raw);
|
|
11734
11902
|
if (!Array.isArray(parsed)) return [];
|
|
11735
11903
|
return parsed.filter((r) => typeof r?.root === "string");
|
|
@@ -11738,27 +11906,27 @@ function readRegistry() {
|
|
|
11738
11906
|
}
|
|
11739
11907
|
}
|
|
11740
11908
|
function listProjects(defaultRoot) {
|
|
11741
|
-
const absDefault =
|
|
11909
|
+
const absDefault = path40.resolve(defaultRoot);
|
|
11742
11910
|
const out = [
|
|
11743
11911
|
{
|
|
11744
11912
|
slug: slugFor(absDefault),
|
|
11745
|
-
name:
|
|
11913
|
+
name: path40.basename(absDefault) || absDefault,
|
|
11746
11914
|
root: absDefault,
|
|
11747
11915
|
isDefault: true,
|
|
11748
|
-
hasSnapshot:
|
|
11916
|
+
hasSnapshot: existsSync3(path40.join(absDefault, ".ctxloom"))
|
|
11749
11917
|
}
|
|
11750
11918
|
];
|
|
11751
11919
|
const seen = /* @__PURE__ */ new Set([absDefault]);
|
|
11752
11920
|
for (const entry of readRegistry()) {
|
|
11753
|
-
const abs =
|
|
11921
|
+
const abs = path40.resolve(entry.root);
|
|
11754
11922
|
if (seen.has(abs)) continue;
|
|
11755
11923
|
seen.add(abs);
|
|
11756
11924
|
const item = {
|
|
11757
11925
|
slug: slugFor(abs),
|
|
11758
|
-
name: entry.name ?? (
|
|
11926
|
+
name: entry.name ?? (path40.basename(abs) || abs),
|
|
11759
11927
|
root: abs,
|
|
11760
11928
|
isDefault: false,
|
|
11761
|
-
hasSnapshot:
|
|
11929
|
+
hasSnapshot: existsSync3(path40.join(abs, ".ctxloom"))
|
|
11762
11930
|
};
|
|
11763
11931
|
if (entry.alias !== void 0) item.alias = entry.alias;
|
|
11764
11932
|
out.push(item);
|
|
@@ -11811,15 +11979,64 @@ function buildProjectsRouter(deps) {
|
|
|
11811
11979
|
} catch (err) {
|
|
11812
11980
|
const detail = err instanceof Error ? err.message : String(err);
|
|
11813
11981
|
res.status(500).json({
|
|
11814
|
-
error: `failed to switch to ${
|
|
11982
|
+
error: `failed to switch to ${path41.basename(target.root)}: ${detail}`
|
|
11815
11983
|
});
|
|
11816
11984
|
}
|
|
11817
11985
|
});
|
|
11818
11986
|
return router;
|
|
11819
11987
|
}
|
|
11820
11988
|
|
|
11989
|
+
// server/routes/telemetry.ts
|
|
11990
|
+
import { Router as Router13, json as json2 } from "express";
|
|
11991
|
+
var DASHBOARD_EVENT_ALLOWLIST = /* @__PURE__ */ new Set([
|
|
11992
|
+
"dashboard_loaded",
|
|
11993
|
+
"dashboard_page_viewed"
|
|
11994
|
+
]);
|
|
11995
|
+
var MAX_MESSAGE_LENGTH = 2e3;
|
|
11996
|
+
var MAX_STACK_LENGTH = 1e4;
|
|
11997
|
+
function buildTelemetryRouter() {
|
|
11998
|
+
const router = Router13();
|
|
11999
|
+
router.get("/identity", (_req, res) => {
|
|
12000
|
+
const disabled = process.env.CTXLOOM_NO_TELEMETRY === "1" || process.env.DO_NOT_TRACK === "1";
|
|
12001
|
+
res.json({ enabled: !disabled });
|
|
12002
|
+
});
|
|
12003
|
+
router.post("/event", json2(), (req, res) => {
|
|
12004
|
+
const body = req.body;
|
|
12005
|
+
const event = body?.event;
|
|
12006
|
+
if (typeof event !== "string" || !DASHBOARD_EVENT_ALLOWLIST.has(event)) {
|
|
12007
|
+
res.status(400).json({ error: "invalid event" });
|
|
12008
|
+
return;
|
|
12009
|
+
}
|
|
12010
|
+
const rawProps = body?.props;
|
|
12011
|
+
const sanitizedProps = rawProps && typeof rawProps === "object" && !Array.isArray(rawProps) ? rawProps : {};
|
|
12012
|
+
track(event, {
|
|
12013
|
+
...sanitizedProps,
|
|
12014
|
+
surface: "dashboard"
|
|
12015
|
+
});
|
|
12016
|
+
res.status(204).end();
|
|
12017
|
+
});
|
|
12018
|
+
router.post("/error", json2(), (req, res) => {
|
|
12019
|
+
const body = req.body;
|
|
12020
|
+
const message = body?.message;
|
|
12021
|
+
if (typeof message !== "string" || message.length === 0 || message.length > MAX_MESSAGE_LENGTH) {
|
|
12022
|
+
res.status(400).json({ error: "invalid message" });
|
|
12023
|
+
return;
|
|
12024
|
+
}
|
|
12025
|
+
const stack = body?.stack;
|
|
12026
|
+
const err = new Error(message);
|
|
12027
|
+
if (typeof stack === "string" && stack.length > 0 && stack.length <= MAX_STACK_LENGTH) {
|
|
12028
|
+
err.stack = stack;
|
|
12029
|
+
}
|
|
12030
|
+
const rawContext = body?.context;
|
|
12031
|
+
const sanitizedContext = rawContext && typeof rawContext === "object" && !Array.isArray(rawContext) ? rawContext : {};
|
|
12032
|
+
captureError(err, { ...sanitizedContext, surface: "dashboard" });
|
|
12033
|
+
res.status(204).end();
|
|
12034
|
+
});
|
|
12035
|
+
return router;
|
|
12036
|
+
}
|
|
12037
|
+
|
|
11821
12038
|
// server/index.ts
|
|
11822
|
-
var __dirname2 =
|
|
12039
|
+
var __dirname2 = path42.dirname(fileURLToPath2(import.meta.url));
|
|
11823
12040
|
async function startDashboard(options) {
|
|
11824
12041
|
const { root, port, open } = options;
|
|
11825
12042
|
console.log(`ctxloom dashboard \u2014 loading context from ${root}...`);
|
|
@@ -11878,7 +12095,7 @@ async function startDashboard(options) {
|
|
|
11878
12095
|
}
|
|
11879
12096
|
activeWatcher = null;
|
|
11880
12097
|
}
|
|
11881
|
-
const snapshotDir =
|
|
12098
|
+
const snapshotDir = path42.join(targetRoot, ".ctxloom");
|
|
11882
12099
|
try {
|
|
11883
12100
|
activeWatcher = fs32.watch(snapshotDir, (_event, filename) => {
|
|
11884
12101
|
if (!filename || !filename.includes("snapshot")) return;
|
|
@@ -11899,6 +12116,7 @@ async function startDashboard(options) {
|
|
|
11899
12116
|
}
|
|
11900
12117
|
}
|
|
11901
12118
|
attachSnapshotWatcher(ctx.root);
|
|
12119
|
+
app.use("/api/telemetry", buildTelemetryRouter());
|
|
11902
12120
|
app.use("/api/projects", buildProjectsRouter({
|
|
11903
12121
|
ctx,
|
|
11904
12122
|
defaultRoot: root,
|
|
@@ -11908,12 +12126,12 @@ async function startDashboard(options) {
|
|
|
11908
12126
|
attachSnapshotWatcher(newRoot);
|
|
11909
12127
|
}
|
|
11910
12128
|
}));
|
|
11911
|
-
const clientDist =
|
|
11912
|
-
const clientDistExists = fs32.existsSync(
|
|
12129
|
+
const clientDist = path42.join(__dirname2, "../dashboard/client");
|
|
12130
|
+
const clientDistExists = fs32.existsSync(path42.join(clientDist, "index.html"));
|
|
11913
12131
|
if (clientDistExists) {
|
|
11914
12132
|
app.use(express.static(clientDist, { dotfiles: "allow" }));
|
|
11915
12133
|
app.get(/.*/, (_req, res) => {
|
|
11916
|
-
res.sendFile(
|
|
12134
|
+
res.sendFile(path42.join(clientDist, "index.html"), { dotfiles: "allow" });
|
|
11917
12135
|
});
|
|
11918
12136
|
} else {
|
|
11919
12137
|
app.get(/^\/(?!api\/).*/, (_req, res) => {
|