modelstat 0.0.22 → 0.0.24
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/LICENSE +87 -0
- package/dist/cli.mjs +433 -151
- package/dist/cli.mjs.map +1 -1
- package/package.json +17 -18
- package/vendor/tray-mac/build-app.sh +0 -0
package/dist/cli.mjs
CHANGED
|
@@ -4339,6 +4339,18 @@ var init_schemas = __esm({
|
|
|
4339
4339
|
tool_calls: external_exports.record(external_exports.string(), external_exports.number().int().nonnegative()).default({}),
|
|
4340
4340
|
// Files touched, relative to git root. Never absolute — scrubbed by agent.
|
|
4341
4341
|
files_touched: external_exports.array(external_exports.string().max(512)).max(256).default([]),
|
|
4342
|
+
// Redacted excerpt of the conversation turn (user prompt or
|
|
4343
|
+
// assistant response). The PARSER is responsible for:
|
|
4344
|
+
// 1. Pulling a representative snippet from the turn (≤320 chars).
|
|
4345
|
+
// 2. Running it through @modelstat/core/redact PLUS, when
|
|
4346
|
+
// available, the on-device Privacy Filter adapter.
|
|
4347
|
+
// 3. Stripping code blocks and file-path noise.
|
|
4348
|
+
// Optional — events without it fall back to metadata-only abstracts
|
|
4349
|
+
// (the historical behaviour). The companion-core pipeline runs
|
|
4350
|
+
// redact() over it again as defence-in-depth before building the
|
|
4351
|
+
// summarize prompt; it never gets stored long-term server-side, only
|
|
4352
|
+
// used to construct the summarize input.
|
|
4353
|
+
content_excerpt: external_exports.string().max(320).optional(),
|
|
4342
4354
|
// Reference to originating file for reparsing
|
|
4343
4355
|
source_file: external_exports.string().max(1024).nullable(),
|
|
4344
4356
|
source_byte_offset: external_exports.number().int().nonnegative().nullable(),
|
|
@@ -4354,7 +4366,7 @@ var init_schemas = __esm({
|
|
|
4354
4366
|
secrets_found: external_exports.number().int().nonnegative().default(0),
|
|
4355
4367
|
emails_redacted: external_exports.number().int().nonnegative().default(0),
|
|
4356
4368
|
paths_redacted_absolute: external_exports.number().int().nonnegative().default(0)
|
|
4357
|
-
});
|
|
4369
|
+
}).catchall(external_exports.number().int().nonnegative());
|
|
4358
4370
|
TaxonomyHintRooted = external_exports.object({
|
|
4359
4371
|
root_key: external_exports.string().max(60),
|
|
4360
4372
|
name: external_exports.string().max(120),
|
|
@@ -4651,6 +4663,28 @@ import { createHash } from "crypto";
|
|
|
4651
4663
|
import { createReadStream } from "fs";
|
|
4652
4664
|
import { stat } from "fs/promises";
|
|
4653
4665
|
import { createInterface } from "readline";
|
|
4666
|
+
function extractExcerpt(content) {
|
|
4667
|
+
if (!content) return void 0;
|
|
4668
|
+
let text = "";
|
|
4669
|
+
if (typeof content === "string") {
|
|
4670
|
+
text = content;
|
|
4671
|
+
} else if (Array.isArray(content)) {
|
|
4672
|
+
const parts = [];
|
|
4673
|
+
for (const block of content) {
|
|
4674
|
+
if (block && block.type === "text" && typeof block.text === "string") {
|
|
4675
|
+
parts.push(block.text);
|
|
4676
|
+
}
|
|
4677
|
+
}
|
|
4678
|
+
text = parts.join(" ");
|
|
4679
|
+
}
|
|
4680
|
+
if (!text) return void 0;
|
|
4681
|
+
text = text.replace(/```[\s\S]*?```/g, " ").replace(/`[^`]*`/g, " ");
|
|
4682
|
+
text = text.replace(/\s+/g, " ").trim();
|
|
4683
|
+
if (!text) return void 0;
|
|
4684
|
+
const cleaned = redact(text).text;
|
|
4685
|
+
const truncated = cleaned.slice(0, 320);
|
|
4686
|
+
return truncated.length > 0 ? truncated : void 0;
|
|
4687
|
+
}
|
|
4654
4688
|
async function parseClaudeCodeJsonl(ctx) {
|
|
4655
4689
|
const events = [];
|
|
4656
4690
|
let rawLines = 0;
|
|
@@ -4701,6 +4735,7 @@ async function parseClaudeCodeJsonl(ctx) {
|
|
|
4701
4735
|
continue;
|
|
4702
4736
|
}
|
|
4703
4737
|
const slug = guessRepoSlugFromPath(cwd);
|
|
4738
|
+
const excerpt = extractExcerpt(a.message?.content);
|
|
4704
4739
|
events.push({
|
|
4705
4740
|
source_event_id: sourceEventId(ctx.deviceId, ctx.sourceFile, offsetAtLineStart),
|
|
4706
4741
|
ts: a.timestamp,
|
|
@@ -4729,6 +4764,7 @@ async function parseClaudeCodeJsonl(ctx) {
|
|
|
4729
4764
|
duration_ms: null,
|
|
4730
4765
|
tool_calls: {},
|
|
4731
4766
|
files_touched: [],
|
|
4767
|
+
...excerpt ? { content_excerpt: excerpt } : {},
|
|
4732
4768
|
source_file: ctx.sourceFile,
|
|
4733
4769
|
source_byte_offset: offsetAtLineStart,
|
|
4734
4770
|
// Files in ~/.claude/projects/ come from the Claude Code app
|
|
@@ -4743,6 +4779,7 @@ async function parseClaudeCodeJsonl(ctx) {
|
|
|
4743
4779
|
skipped += 1;
|
|
4744
4780
|
continue;
|
|
4745
4781
|
}
|
|
4782
|
+
const excerpt = extractExcerpt(u.message?.content);
|
|
4746
4783
|
events.push({
|
|
4747
4784
|
source_event_id: sourceEventId(ctx.deviceId, ctx.sourceFile, offsetAtLineStart),
|
|
4748
4785
|
ts: u.timestamp,
|
|
@@ -4759,6 +4796,7 @@ async function parseClaudeCodeJsonl(ctx) {
|
|
|
4759
4796
|
duration_ms: null,
|
|
4760
4797
|
tool_calls: {},
|
|
4761
4798
|
files_touched: [],
|
|
4799
|
+
...excerpt ? { content_excerpt: excerpt } : {},
|
|
4762
4800
|
source_file: ctx.sourceFile,
|
|
4763
4801
|
source_byte_offset: offsetAtLineStart,
|
|
4764
4802
|
billing: "subscription"
|
|
@@ -7487,6 +7525,107 @@ var init_src2 = __esm({
|
|
|
7487
7525
|
}
|
|
7488
7526
|
});
|
|
7489
7527
|
|
|
7528
|
+
// src/identity.ts
|
|
7529
|
+
import {
|
|
7530
|
+
chmodSync,
|
|
7531
|
+
mkdirSync,
|
|
7532
|
+
readFileSync as readFileSync2,
|
|
7533
|
+
renameSync,
|
|
7534
|
+
writeFileSync,
|
|
7535
|
+
existsSync as existsSync3
|
|
7536
|
+
} from "fs";
|
|
7537
|
+
import { homedir as homedir2, hostname as osHostname } from "os";
|
|
7538
|
+
import { join as join2 } from "path";
|
|
7539
|
+
function ensureRoot() {
|
|
7540
|
+
mkdirSync(ROOT, { recursive: true, mode: 448 });
|
|
7541
|
+
}
|
|
7542
|
+
function writeAtomic(meta) {
|
|
7543
|
+
ensureRoot();
|
|
7544
|
+
const tmp = `${IDENTITY_FILE}.${process.pid}.tmp`;
|
|
7545
|
+
writeFileSync(tmp, JSON.stringify(meta, null, 2), { mode: 384 });
|
|
7546
|
+
renameSync(tmp, IDENTITY_FILE);
|
|
7547
|
+
try {
|
|
7548
|
+
chmodSync(IDENTITY_FILE, 384);
|
|
7549
|
+
} catch {
|
|
7550
|
+
}
|
|
7551
|
+
}
|
|
7552
|
+
function identityPath() {
|
|
7553
|
+
return IDENTITY_FILE;
|
|
7554
|
+
}
|
|
7555
|
+
function hasIdentityFile() {
|
|
7556
|
+
return existsSync3(IDENTITY_FILE);
|
|
7557
|
+
}
|
|
7558
|
+
function parseFile() {
|
|
7559
|
+
try {
|
|
7560
|
+
const raw = readFileSync2(IDENTITY_FILE, "utf8");
|
|
7561
|
+
const obj = JSON.parse(raw);
|
|
7562
|
+
if (!obj.deviceUuid || !obj.deviceId || !obj.bearerToken) {
|
|
7563
|
+
return null;
|
|
7564
|
+
}
|
|
7565
|
+
return {
|
|
7566
|
+
deviceUuid: obj.deviceUuid,
|
|
7567
|
+
deviceId: obj.deviceId,
|
|
7568
|
+
bearerToken: obj.bearerToken,
|
|
7569
|
+
claimCode: obj.claimCode ?? null,
|
|
7570
|
+
claimUrl: obj.claimUrl ?? null,
|
|
7571
|
+
hostname: obj.hostname ?? osHostname(),
|
|
7572
|
+
createdAt: obj.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
7573
|
+
userEmail: obj.userEmail ?? null,
|
|
7574
|
+
defaultOrgId: obj.defaultOrgId ?? null
|
|
7575
|
+
};
|
|
7576
|
+
} catch {
|
|
7577
|
+
return null;
|
|
7578
|
+
}
|
|
7579
|
+
}
|
|
7580
|
+
function loadIdentity(migrateFromConf2) {
|
|
7581
|
+
const fromFile = parseFile();
|
|
7582
|
+
if (fromFile) return fromFile;
|
|
7583
|
+
if (!migrateFromConf2) return null;
|
|
7584
|
+
const legacy = migrateFromConf2();
|
|
7585
|
+
if (!legacy) return null;
|
|
7586
|
+
if (!legacy.deviceUuid || !legacy.deviceId || !legacy.bearerToken) {
|
|
7587
|
+
return null;
|
|
7588
|
+
}
|
|
7589
|
+
const migrated = {
|
|
7590
|
+
deviceUuid: legacy.deviceUuid,
|
|
7591
|
+
deviceId: legacy.deviceId,
|
|
7592
|
+
bearerToken: legacy.bearerToken,
|
|
7593
|
+
claimCode: legacy.claimCode ?? null,
|
|
7594
|
+
claimUrl: legacy.claimUrl ?? null,
|
|
7595
|
+
hostname: osHostname(),
|
|
7596
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7597
|
+
userEmail: legacy.userEmail ?? null,
|
|
7598
|
+
defaultOrgId: legacy.defaultOrgId ?? null
|
|
7599
|
+
};
|
|
7600
|
+
writeAtomic(migrated);
|
|
7601
|
+
return migrated;
|
|
7602
|
+
}
|
|
7603
|
+
function saveIdentity(meta) {
|
|
7604
|
+
writeAtomic(meta);
|
|
7605
|
+
}
|
|
7606
|
+
function backupIdentity() {
|
|
7607
|
+
if (!existsSync3(IDENTITY_FILE)) return null;
|
|
7608
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
7609
|
+
const dest = `${IDENTITY_FILE}.bak-${stamp}`;
|
|
7610
|
+
renameSync(IDENTITY_FILE, dest);
|
|
7611
|
+
return dest;
|
|
7612
|
+
}
|
|
7613
|
+
function updateIdentity(patch) {
|
|
7614
|
+
const current = parseFile();
|
|
7615
|
+
if (!current) return null;
|
|
7616
|
+
const merged = { ...current, ...patch };
|
|
7617
|
+
writeAtomic(merged);
|
|
7618
|
+
return merged;
|
|
7619
|
+
}
|
|
7620
|
+
var ROOT, IDENTITY_FILE;
|
|
7621
|
+
var init_identity = __esm({
|
|
7622
|
+
"src/identity.ts"() {
|
|
7623
|
+
"use strict";
|
|
7624
|
+
ROOT = join2(homedir2(), ".modelstat");
|
|
7625
|
+
IDENTITY_FILE = join2(ROOT, "identity.json");
|
|
7626
|
+
}
|
|
7627
|
+
});
|
|
7628
|
+
|
|
7490
7629
|
// ../../node_modules/.pnpm/undici@7.25.0/node_modules/undici/lib/core/symbols.js
|
|
7491
7630
|
var require_symbols = __commonJS({
|
|
7492
7631
|
"../../node_modules/.pnpm/undici@7.25.0/node_modules/undici/lib/core/symbols.js"(exports, module) {
|
|
@@ -33061,15 +33200,15 @@ function envPaths(name, { suffix = "nodejs" } = {}) {
|
|
|
33061
33200
|
}
|
|
33062
33201
|
return linux(name);
|
|
33063
33202
|
}
|
|
33064
|
-
var
|
|
33203
|
+
var homedir3, tmpdir, env, macos, windows, linux;
|
|
33065
33204
|
var init_env_paths = __esm({
|
|
33066
33205
|
"../../node_modules/.pnpm/env-paths@3.0.0/node_modules/env-paths/index.js"() {
|
|
33067
33206
|
"use strict";
|
|
33068
|
-
|
|
33207
|
+
homedir3 = os.homedir();
|
|
33069
33208
|
tmpdir = os.tmpdir();
|
|
33070
33209
|
({ env } = process2);
|
|
33071
33210
|
macos = (name) => {
|
|
33072
|
-
const library = path.join(
|
|
33211
|
+
const library = path.join(homedir3, "Library");
|
|
33073
33212
|
return {
|
|
33074
33213
|
data: path.join(library, "Application Support", name),
|
|
33075
33214
|
config: path.join(library, "Preferences", name),
|
|
@@ -33079,8 +33218,8 @@ var init_env_paths = __esm({
|
|
|
33079
33218
|
};
|
|
33080
33219
|
};
|
|
33081
33220
|
windows = (name) => {
|
|
33082
|
-
const appData = env.APPDATA || path.join(
|
|
33083
|
-
const localAppData = env.LOCALAPPDATA || path.join(
|
|
33221
|
+
const appData = env.APPDATA || path.join(homedir3, "AppData", "Roaming");
|
|
33222
|
+
const localAppData = env.LOCALAPPDATA || path.join(homedir3, "AppData", "Local");
|
|
33084
33223
|
return {
|
|
33085
33224
|
// Data/config/cache/log are invented by me as Windows isn't opinionated about this
|
|
33086
33225
|
data: path.join(localAppData, name, "Data"),
|
|
@@ -33091,13 +33230,13 @@ var init_env_paths = __esm({
|
|
|
33091
33230
|
};
|
|
33092
33231
|
};
|
|
33093
33232
|
linux = (name) => {
|
|
33094
|
-
const username = path.basename(
|
|
33233
|
+
const username = path.basename(homedir3);
|
|
33095
33234
|
return {
|
|
33096
|
-
data: path.join(env.XDG_DATA_HOME || path.join(
|
|
33097
|
-
config: path.join(env.XDG_CONFIG_HOME || path.join(
|
|
33098
|
-
cache: path.join(env.XDG_CACHE_HOME || path.join(
|
|
33235
|
+
data: path.join(env.XDG_DATA_HOME || path.join(homedir3, ".local", "share"), name),
|
|
33236
|
+
config: path.join(env.XDG_CONFIG_HOME || path.join(homedir3, ".config"), name),
|
|
33237
|
+
cache: path.join(env.XDG_CACHE_HOME || path.join(homedir3, ".cache"), name),
|
|
33099
33238
|
// https://wiki.debian.org/XDGBaseDirectorySpecification#state
|
|
33100
|
-
log: path.join(env.XDG_STATE_HOME || path.join(
|
|
33239
|
+
log: path.join(env.XDG_STATE_HOME || path.join(homedir3, ".local", "state"), name),
|
|
33101
33240
|
temp: path.join(tmpdir, username, name)
|
|
33102
33241
|
};
|
|
33103
33242
|
};
|
|
@@ -33538,9 +33677,9 @@ import { once } from "events";
|
|
|
33538
33677
|
import { createWriteStream } from "fs";
|
|
33539
33678
|
import path3 from "path";
|
|
33540
33679
|
import { Readable } from "stream";
|
|
33541
|
-
function
|
|
33680
|
+
function writeFileSync2(filePath, data, options = DEFAULT_WRITE_OPTIONS) {
|
|
33542
33681
|
if (isString(options))
|
|
33543
|
-
return
|
|
33682
|
+
return writeFileSync2(filePath, data, { encoding: options });
|
|
33544
33683
|
const timeout = options.timeout ?? DEFAULT_TIMEOUT_SYNC;
|
|
33545
33684
|
const retryOptions = { timeout };
|
|
33546
33685
|
let tempDisposer = null;
|
|
@@ -43663,7 +43802,7 @@ var init_source = __esm({
|
|
|
43663
43802
|
fs2.writeFileSync(this.path, data, { mode: this.#options.configFileMode });
|
|
43664
43803
|
} else {
|
|
43665
43804
|
try {
|
|
43666
|
-
|
|
43805
|
+
writeFileSync2(this.path, data, { mode: this.#options.configFileMode });
|
|
43667
43806
|
} catch (error) {
|
|
43668
43807
|
if (error?.code === "EXDEV") {
|
|
43669
43808
|
fs2.writeFileSync(this.path, data, { mode: this.#options.configFileMode });
|
|
@@ -43765,20 +43904,52 @@ var init_source = __esm({
|
|
|
43765
43904
|
});
|
|
43766
43905
|
|
|
43767
43906
|
// src/config.ts
|
|
43768
|
-
import { existsSync as
|
|
43907
|
+
import { existsSync as existsSync4 } from "fs";
|
|
43769
43908
|
import { hostname } from "os";
|
|
43770
43909
|
import { dirname as dirname2, resolve as resolve3 } from "path";
|
|
43771
43910
|
import { fileURLToPath } from "url";
|
|
43772
|
-
|
|
43911
|
+
function migrateFromConf() {
|
|
43912
|
+
const bearer = store.get("bearerToken");
|
|
43913
|
+
const deviceUuid = store.get("deviceUuid");
|
|
43914
|
+
const deviceId = store.get("deviceId");
|
|
43915
|
+
if (!bearer || !deviceUuid || !deviceId) return null;
|
|
43916
|
+
return {
|
|
43917
|
+
bearerToken: bearer,
|
|
43918
|
+
deviceId,
|
|
43919
|
+
deviceUuid,
|
|
43920
|
+
claimCode: store.get("claimCode"),
|
|
43921
|
+
claimUrl: store.get("claimUrl"),
|
|
43922
|
+
userEmail: store.get("userEmail"),
|
|
43923
|
+
defaultOrgId: store.get("defaultOrgId")
|
|
43924
|
+
};
|
|
43925
|
+
}
|
|
43926
|
+
function clearConfIdentity() {
|
|
43927
|
+
store.set("bearerToken", null);
|
|
43928
|
+
store.set("deviceId", null);
|
|
43929
|
+
store.set("deviceUuid", null);
|
|
43930
|
+
store.set("claimCode", null);
|
|
43931
|
+
store.set("claimUrl", null);
|
|
43932
|
+
}
|
|
43933
|
+
function writeThrough(patch) {
|
|
43934
|
+
if (!cachedIdentity) {
|
|
43935
|
+
throw new Error(
|
|
43936
|
+
"config: no identity yet \u2014 call state.saveFreshIdentity() first"
|
|
43937
|
+
);
|
|
43938
|
+
}
|
|
43939
|
+
cachedIdentity = { ...cachedIdentity, ...patch };
|
|
43940
|
+
updateIdentity(patch);
|
|
43941
|
+
}
|
|
43942
|
+
var import_dotenv, here, DEFAULT_API_URL, LEGACY_LOCALHOST_API, store, cachedIdentity, state;
|
|
43773
43943
|
var init_config2 = __esm({
|
|
43774
43944
|
"src/config.ts"() {
|
|
43775
43945
|
"use strict";
|
|
43776
43946
|
import_dotenv = __toESM(require_main(), 1);
|
|
43777
43947
|
init_source();
|
|
43948
|
+
init_identity();
|
|
43778
43949
|
here = dirname2(fileURLToPath(import.meta.url));
|
|
43779
43950
|
for (let d = here, i = 0; i < 8; i++, d = resolve3(d, "..")) {
|
|
43780
43951
|
const candidate = resolve3(d, ".env");
|
|
43781
|
-
if (
|
|
43952
|
+
if (existsSync4(candidate)) {
|
|
43782
43953
|
(0, import_dotenv.config)({ path: candidate });
|
|
43783
43954
|
break;
|
|
43784
43955
|
}
|
|
@@ -43804,6 +43975,14 @@ var init_config2 = __esm({
|
|
|
43804
43975
|
cursor: {}
|
|
43805
43976
|
}
|
|
43806
43977
|
});
|
|
43978
|
+
cachedIdentity = (() => {
|
|
43979
|
+
const had = store.get("bearerToken") !== null;
|
|
43980
|
+
const id = loadIdentity(migrateFromConf);
|
|
43981
|
+
if (id && had && !store.path.includes("fallback")) {
|
|
43982
|
+
clearConfIdentity();
|
|
43983
|
+
}
|
|
43984
|
+
return id;
|
|
43985
|
+
})();
|
|
43807
43986
|
state = {
|
|
43808
43987
|
/** Resolution order: env var → stored value (if user ran `setApiUrl`
|
|
43809
43988
|
* or paired pre-0.0.8) → production default. The legacy localhost
|
|
@@ -43817,42 +43996,66 @@ var init_config2 = __esm({
|
|
|
43817
43996
|
setApiUrl(v) {
|
|
43818
43997
|
store.set("apiUrl", v);
|
|
43819
43998
|
},
|
|
43999
|
+
// ── Identity: backed by ~/.modelstat/identity.json ─────────────
|
|
44000
|
+
/** Seed a fresh identity after a successful self-register. Writes
|
|
44001
|
+
* the file atomically; use `state.backupAndReset()` first if
|
|
44002
|
+
* overwriting an existing identity. */
|
|
44003
|
+
saveFreshIdentity(meta) {
|
|
44004
|
+
const id = {
|
|
44005
|
+
deviceUuid: meta.deviceUuid,
|
|
44006
|
+
deviceId: meta.deviceId,
|
|
44007
|
+
bearerToken: meta.bearerToken,
|
|
44008
|
+
claimCode: meta.claimCode,
|
|
44009
|
+
claimUrl: meta.claimUrl,
|
|
44010
|
+
hostname: hostname(),
|
|
44011
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
44012
|
+
userEmail: null,
|
|
44013
|
+
defaultOrgId: null
|
|
44014
|
+
};
|
|
44015
|
+
saveIdentity(id);
|
|
44016
|
+
cachedIdentity = id;
|
|
44017
|
+
},
|
|
44018
|
+
get identity() {
|
|
44019
|
+
return cachedIdentity;
|
|
44020
|
+
},
|
|
43820
44021
|
get bearer() {
|
|
43821
|
-
return
|
|
44022
|
+
return cachedIdentity?.bearerToken ?? null;
|
|
43822
44023
|
},
|
|
43823
44024
|
setBearer(v) {
|
|
43824
|
-
|
|
44025
|
+
if (v === null) {
|
|
44026
|
+
cachedIdentity = null;
|
|
44027
|
+
return;
|
|
44028
|
+
}
|
|
44029
|
+
writeThrough({ bearerToken: v });
|
|
43825
44030
|
},
|
|
43826
44031
|
get deviceId() {
|
|
43827
|
-
return
|
|
43828
|
-
},
|
|
43829
|
-
setDeviceId(v) {
|
|
43830
|
-
store.set("deviceId", v);
|
|
43831
|
-
},
|
|
43832
|
-
get userEmail() {
|
|
43833
|
-
return store.get("userEmail");
|
|
43834
|
-
},
|
|
43835
|
-
setUserEmail(v) {
|
|
43836
|
-
store.set("userEmail", v);
|
|
44032
|
+
return cachedIdentity?.deviceId ?? null;
|
|
43837
44033
|
},
|
|
43838
44034
|
get deviceUuid() {
|
|
43839
|
-
return
|
|
43840
|
-
},
|
|
43841
|
-
setDeviceUuid(v) {
|
|
43842
|
-
store.set("deviceUuid", v);
|
|
44035
|
+
return cachedIdentity?.deviceUuid ?? null;
|
|
43843
44036
|
},
|
|
43844
44037
|
get claimCode() {
|
|
43845
|
-
return
|
|
44038
|
+
return cachedIdentity?.claimCode ?? null;
|
|
43846
44039
|
},
|
|
43847
44040
|
setClaimCode(v) {
|
|
43848
|
-
|
|
44041
|
+
if (!cachedIdentity) return;
|
|
44042
|
+
writeThrough({ claimCode: v });
|
|
43849
44043
|
},
|
|
43850
44044
|
get claimUrl() {
|
|
43851
|
-
return
|
|
44045
|
+
return cachedIdentity?.claimUrl ?? null;
|
|
43852
44046
|
},
|
|
43853
44047
|
setClaimUrl(v) {
|
|
43854
|
-
|
|
44048
|
+
if (!cachedIdentity) return;
|
|
44049
|
+
writeThrough({ claimUrl: v });
|
|
43855
44050
|
},
|
|
44051
|
+
get userEmail() {
|
|
44052
|
+
return cachedIdentity?.userEmail ?? null;
|
|
44053
|
+
},
|
|
44054
|
+
setUserEmail(v) {
|
|
44055
|
+
if (!cachedIdentity) return;
|
|
44056
|
+
writeThrough({ userEmail: v });
|
|
44057
|
+
},
|
|
44058
|
+
// ── Runtime state: stays in conf ───────────────────────────────
|
|
43856
44059
|
getCursor(path5) {
|
|
43857
44060
|
return store.get("cursor")[path5];
|
|
43858
44061
|
},
|
|
@@ -44032,9 +44235,9 @@ var OLLAMA_CHAT_MODEL, OLLAMA_EMBED_MODEL, SUMMARISER_SYSTEM_PROMPT, SUMMARISER_
|
|
|
44032
44235
|
var init_prompts = __esm({
|
|
44033
44236
|
"../../packages/companion-core/src/pipeline/prompts.ts"() {
|
|
44034
44237
|
"use strict";
|
|
44035
|
-
OLLAMA_CHAT_MODEL = "qwen3:0.
|
|
44238
|
+
OLLAMA_CHAT_MODEL = "qwen3.5:0.8b";
|
|
44036
44239
|
OLLAMA_EMBED_MODEL = "bge-small-en-v1.5";
|
|
44037
|
-
SUMMARISER_SYSTEM_PROMPT = "You summarise an AI coding session in ONE sentence, \u2264 240 characters.
|
|
44240
|
+
SUMMARISER_SYSTEM_PROMPT = "You summarise an AI coding session in ONE sentence, \u2264 240 characters. If the user message includes sampled conversation excerpts, base your summary on what the developer was actually working on (the substance \u2014 what was being built, debugged, refactored, or designed). If only metadata is given, paraphrase the metadata. Never quote the excerpts verbatim. No PII, no code literals, no file paths, no API keys. Reply with only the sentence.";
|
|
44038
44241
|
SUMMARISER_MAX_TOKENS = 120;
|
|
44039
44242
|
SUMMARISER_TEMPERATURE = 0.2;
|
|
44040
44243
|
QWEN_CHARS_PER_TOKEN = 3.3;
|
|
@@ -44145,7 +44348,14 @@ async function summariseSlice(sessionId, slice, adapters2) {
|
|
|
44145
44348
|
first.files_touched?.length ? `files touched: ${first.files_touched.slice(0, 5).join(", ")}` : null,
|
|
44146
44349
|
Object.keys(first.tool_calls ?? {}).length ? `tool calls: ${Object.keys(first.tool_calls).slice(0, 5).join(", ")}` : null
|
|
44147
44350
|
].filter(Boolean).join("; ");
|
|
44148
|
-
const
|
|
44351
|
+
const excerpts = sampleAndRedactExcerpts(slice);
|
|
44352
|
+
const excerptBlock = excerpts.length ? excerpts.map((e, i) => ` [turn ${i + 1}] "${e.replace(/\s+/g, " ").trim()}"`).join("\n") : "";
|
|
44353
|
+
const prompt = excerptBlock ? `Session context: ${promptFacts || "generic coding session"}.
|
|
44354
|
+
|
|
44355
|
+
Sampled excerpts from the conversation (already redacted of PII and secrets):
|
|
44356
|
+
${excerptBlock}
|
|
44357
|
+
|
|
44358
|
+
Write ONE sentence (\u2264240 chars) describing what the human was working on. Focus on the substance \u2014 what was being built, debugged, or designed. No quotes, no PII, no code literals, no file paths.` : `Session context: ${promptFacts || "generic coding session"}.
|
|
44149
44359
|
Write one sentence describing what the human was doing.`;
|
|
44150
44360
|
let rawAbstract;
|
|
44151
44361
|
try {
|
|
@@ -44153,7 +44363,20 @@ Write one sentence describing what the human was doing.`;
|
|
|
44153
44363
|
} catch {
|
|
44154
44364
|
rawAbstract = promptFacts || `${first.tool} session with ${slice.length} turns`;
|
|
44155
44365
|
}
|
|
44156
|
-
const
|
|
44366
|
+
const regexPass = redact(rawAbstract);
|
|
44367
|
+
let abstractText = regexPass.text;
|
|
44368
|
+
const counts = { ...regexPass.counts };
|
|
44369
|
+
if (adapters2.redact) {
|
|
44370
|
+
try {
|
|
44371
|
+
const modelPass = await adapters2.redact(regexPass.text);
|
|
44372
|
+
abstractText = modelPass.text;
|
|
44373
|
+
for (const [k, v] of Object.entries(modelPass.counts)) {
|
|
44374
|
+
if (k.startsWith("pf_")) counts[k] = v;
|
|
44375
|
+
}
|
|
44376
|
+
} catch {
|
|
44377
|
+
}
|
|
44378
|
+
}
|
|
44379
|
+
const redacted = { text: abstractText, counts };
|
|
44157
44380
|
const tags = [
|
|
44158
44381
|
{ root_key: "tools", name: first.tool, confidence: 1 },
|
|
44159
44382
|
{ root_key: "providers", name: first.provider, confidence: 1 }
|
|
@@ -44194,11 +44417,39 @@ Write one sentence describing what the human was doing.`;
|
|
|
44194
44417
|
abstract: redacted.text.slice(0, ABSTRACT_MAX_CHARS),
|
|
44195
44418
|
tokens,
|
|
44196
44419
|
tags,
|
|
44420
|
+
// counts is `Record<string, number>` after the optional model
|
|
44421
|
+
// merge; the schema's RedactionReport requires the three regex
|
|
44422
|
+
// counters (always populated from regexPass.counts) plus a
|
|
44423
|
+
// number-valued catchall for pf_*.
|
|
44197
44424
|
redaction: redacted.counts,
|
|
44198
44425
|
source_event_ids: sourceEventIds,
|
|
44199
44426
|
abstract_embedding: segmentEmbedding && segmentEmbedding.length === 384 ? segmentEmbedding : void 0
|
|
44200
44427
|
};
|
|
44201
44428
|
}
|
|
44429
|
+
function sampleAndRedactExcerpts(slice) {
|
|
44430
|
+
const withContent = [];
|
|
44431
|
+
for (let i = 0; i < slice.length; i++) {
|
|
44432
|
+
const c = slice[i]?.content_excerpt;
|
|
44433
|
+
if (c && c.trim().length > 0) withContent.push({ idx: i, text: c });
|
|
44434
|
+
}
|
|
44435
|
+
if (withContent.length === 0) return [];
|
|
44436
|
+
const picks = [0];
|
|
44437
|
+
if (withContent.length > 1) picks.push(withContent.length - 1);
|
|
44438
|
+
for (const frac of [0.25, 0.5, 0.75]) {
|
|
44439
|
+
const idx = Math.floor(withContent.length * frac);
|
|
44440
|
+
if (!picks.includes(idx)) picks.push(idx);
|
|
44441
|
+
if (picks.length >= 5) break;
|
|
44442
|
+
}
|
|
44443
|
+
picks.sort((a, b) => a - b);
|
|
44444
|
+
const out = [];
|
|
44445
|
+
for (const i of picks) {
|
|
44446
|
+
const raw = withContent[i]?.text;
|
|
44447
|
+
if (!raw) continue;
|
|
44448
|
+
const redacted = redact(raw).text;
|
|
44449
|
+
out.push(redacted.slice(0, 200));
|
|
44450
|
+
}
|
|
44451
|
+
return out;
|
|
44452
|
+
}
|
|
44202
44453
|
function turnSurface(e) {
|
|
44203
44454
|
const parts = [e.kind, e.tool];
|
|
44204
44455
|
if (e.model) parts.push(e.model);
|
|
@@ -44402,23 +44653,23 @@ var init_pipeline2 = __esm({
|
|
|
44402
44653
|
|
|
44403
44654
|
// src/scan.ts
|
|
44404
44655
|
import { readdir, stat as stat2 } from "fs/promises";
|
|
44405
|
-
import { homedir as
|
|
44406
|
-
import { join as
|
|
44656
|
+
import { homedir as homedir4 } from "os";
|
|
44657
|
+
import { join as join3 } from "path";
|
|
44407
44658
|
async function scanAll(cb = {}) {
|
|
44408
44659
|
const deviceId = state.deviceId;
|
|
44409
44660
|
if (!deviceId) throw new Error("agent not enrolled \u2014 run `register` first");
|
|
44410
44661
|
const jobs = [];
|
|
44411
44662
|
try {
|
|
44412
|
-
const base =
|
|
44663
|
+
const base = join3(homedir4(), ".claude/projects");
|
|
44413
44664
|
const projects = await readdir(base).catch(() => []);
|
|
44414
44665
|
for (const p of projects) {
|
|
44415
|
-
const dir =
|
|
44666
|
+
const dir = join3(base, p);
|
|
44416
44667
|
const ds = await stat2(dir).catch(() => null);
|
|
44417
44668
|
if (!ds?.isDirectory()) continue;
|
|
44418
44669
|
const files = await readdir(dir);
|
|
44419
44670
|
for (const f of files) {
|
|
44420
44671
|
if (!f.endsWith(".jsonl")) continue;
|
|
44421
|
-
const full =
|
|
44672
|
+
const full = join3(dir, f);
|
|
44422
44673
|
jobs.push({
|
|
44423
44674
|
path: full,
|
|
44424
44675
|
parse: async () => {
|
|
@@ -44432,17 +44683,17 @@ async function scanAll(cb = {}) {
|
|
|
44432
44683
|
console.warn("claude scan skipped:", e.message);
|
|
44433
44684
|
}
|
|
44434
44685
|
try {
|
|
44435
|
-
const base =
|
|
44686
|
+
const base = join3(homedir4(), ".codex/sessions");
|
|
44436
44687
|
const years = await readdir(base).catch(() => []);
|
|
44437
44688
|
for (const y of years) {
|
|
44438
|
-
const months = await readdir(
|
|
44689
|
+
const months = await readdir(join3(base, y)).catch(() => []);
|
|
44439
44690
|
for (const m of months) {
|
|
44440
|
-
const days = await readdir(
|
|
44691
|
+
const days = await readdir(join3(base, y, m)).catch(() => []);
|
|
44441
44692
|
for (const d of days) {
|
|
44442
|
-
const files = await readdir(
|
|
44693
|
+
const files = await readdir(join3(base, y, m, d)).catch(() => []);
|
|
44443
44694
|
for (const f of files) {
|
|
44444
44695
|
if (!f.startsWith("rollout-") || !f.endsWith(".jsonl")) continue;
|
|
44445
|
-
const full =
|
|
44696
|
+
const full = join3(base, y, m, d, f);
|
|
44446
44697
|
jobs.push({
|
|
44447
44698
|
path: full,
|
|
44448
44699
|
parse: async () => {
|
|
@@ -44516,7 +44767,7 @@ var init_scan = __esm({
|
|
|
44516
44767
|
init_pipeline2();
|
|
44517
44768
|
init_config2();
|
|
44518
44769
|
init_api();
|
|
44519
|
-
AGENT_VERSION = "agent-dev-0.0.
|
|
44770
|
+
AGENT_VERSION = "agent-dev-0.0.23";
|
|
44520
44771
|
BATCH_MAX_EVENTS = 2e3;
|
|
44521
44772
|
}
|
|
44522
44773
|
});
|
|
@@ -44524,17 +44775,17 @@ var init_scan = __esm({
|
|
|
44524
44775
|
// src/lock.ts
|
|
44525
44776
|
import {
|
|
44526
44777
|
closeSync,
|
|
44527
|
-
existsSync as
|
|
44528
|
-
mkdirSync as
|
|
44778
|
+
existsSync as existsSync6,
|
|
44779
|
+
mkdirSync as mkdirSync3,
|
|
44529
44780
|
openSync,
|
|
44530
|
-
readFileSync as
|
|
44531
|
-
renameSync,
|
|
44781
|
+
readFileSync as readFileSync3,
|
|
44782
|
+
renameSync as renameSync2,
|
|
44532
44783
|
unlinkSync as unlinkSync2,
|
|
44533
|
-
writeFileSync as
|
|
44784
|
+
writeFileSync as writeFileSync4,
|
|
44534
44785
|
writeSync
|
|
44535
44786
|
} from "fs";
|
|
44536
|
-
import { homedir as
|
|
44537
|
-
import { join as
|
|
44787
|
+
import { homedir as homedir6 } from "os";
|
|
44788
|
+
import { join as join5 } from "path";
|
|
44538
44789
|
function isProcessAlive(pid) {
|
|
44539
44790
|
if (!pid || pid <= 0) return false;
|
|
44540
44791
|
try {
|
|
@@ -44548,7 +44799,7 @@ function isProcessAlive(pid) {
|
|
|
44548
44799
|
}
|
|
44549
44800
|
function readLock() {
|
|
44550
44801
|
try {
|
|
44551
|
-
const raw =
|
|
44802
|
+
const raw = readFileSync3(LOCK_FILE, "utf8");
|
|
44552
44803
|
const obj = JSON.parse(raw);
|
|
44553
44804
|
if (typeof obj.pid !== "number") return null;
|
|
44554
44805
|
return {
|
|
@@ -44562,7 +44813,7 @@ function readLock() {
|
|
|
44562
44813
|
}
|
|
44563
44814
|
}
|
|
44564
44815
|
function writeLockAtomic(meta) {
|
|
44565
|
-
|
|
44816
|
+
mkdirSync3(LOCK_DIR, { recursive: true });
|
|
44566
44817
|
const tmp = `${LOCK_FILE}.${meta.pid}.${Date.now()}.tmp`;
|
|
44567
44818
|
const fd = openSync(tmp, "wx");
|
|
44568
44819
|
try {
|
|
@@ -44570,7 +44821,7 @@ function writeLockAtomic(meta) {
|
|
|
44570
44821
|
} finally {
|
|
44571
44822
|
closeSync(fd);
|
|
44572
44823
|
}
|
|
44573
|
-
|
|
44824
|
+
renameSync2(tmp, LOCK_FILE);
|
|
44574
44825
|
}
|
|
44575
44826
|
function removeLockIfOwned(ownerPid) {
|
|
44576
44827
|
const lock = readLock();
|
|
@@ -44635,8 +44886,8 @@ var LOCK_DIR, LOCK_FILE;
|
|
|
44635
44886
|
var init_lock = __esm({
|
|
44636
44887
|
"src/lock.ts"() {
|
|
44637
44888
|
"use strict";
|
|
44638
|
-
LOCK_DIR =
|
|
44639
|
-
LOCK_FILE =
|
|
44889
|
+
LOCK_DIR = join5(homedir6(), ".modelstat");
|
|
44890
|
+
LOCK_FILE = join5(LOCK_DIR, "daemon.lock");
|
|
44640
44891
|
}
|
|
44641
44892
|
});
|
|
44642
44893
|
|
|
@@ -46366,7 +46617,7 @@ __export(daemon_exports, {
|
|
|
46366
46617
|
setProgress: () => setProgress,
|
|
46367
46618
|
setQueue: () => setQueue
|
|
46368
46619
|
});
|
|
46369
|
-
import { existsSync as
|
|
46620
|
+
import { existsSync as existsSync7, statSync as statSync2 } from "fs";
|
|
46370
46621
|
function setPhase(phase, message) {
|
|
46371
46622
|
status.phase = phase;
|
|
46372
46623
|
status.message = message ?? null;
|
|
@@ -46488,21 +46739,21 @@ async function runDaemon(opts = {}) {
|
|
|
46488
46739
|
await runDiscovery();
|
|
46489
46740
|
await runScanCycle("startup");
|
|
46490
46741
|
const chokidar = (await Promise.resolve().then(() => (init_esm2(), esm_exports))).default;
|
|
46491
|
-
const { homedir:
|
|
46492
|
-
const { join:
|
|
46493
|
-
const home2 =
|
|
46742
|
+
const { homedir: homedir8, platform: platform5 } = await import("os");
|
|
46743
|
+
const { join: join9 } = await import("path");
|
|
46744
|
+
const home2 = homedir8();
|
|
46494
46745
|
const dirs = [
|
|
46495
|
-
|
|
46496
|
-
|
|
46497
|
-
|
|
46498
|
-
|
|
46746
|
+
join9(home2, ".claude/projects"),
|
|
46747
|
+
join9(home2, ".codex/sessions"),
|
|
46748
|
+
join9(home2, ".cursor/ai-tracking"),
|
|
46749
|
+
join9(home2, ".gemini"),
|
|
46499
46750
|
...platform5() === "darwin" ? [
|
|
46500
|
-
|
|
46501
|
-
|
|
46751
|
+
join9(home2, "Library/Application Support/Cursor/User/workspaceStorage"),
|
|
46752
|
+
join9(home2, "Library/Application Support/Claude")
|
|
46502
46753
|
] : [
|
|
46503
|
-
|
|
46754
|
+
join9(home2, ".config/Cursor/User/workspaceStorage")
|
|
46504
46755
|
]
|
|
46505
|
-
].filter((p) =>
|
|
46756
|
+
].filter((p) => existsSync7(p) && statSync2(p).isDirectory());
|
|
46506
46757
|
setPhase("watching", `Watching ${dirs.length} directories`);
|
|
46507
46758
|
const watcher = chokidar.watch(dirs, {
|
|
46508
46759
|
persistent: true,
|
|
@@ -46549,7 +46800,7 @@ var init_daemon = __esm({
|
|
|
46549
46800
|
init_config2();
|
|
46550
46801
|
init_lock();
|
|
46551
46802
|
init_scan();
|
|
46552
|
-
AGENT_VERSION2 = "agent-dev-0.0.
|
|
46803
|
+
AGENT_VERSION2 = "agent-dev-0.0.23";
|
|
46553
46804
|
HEARTBEAT_INTERVAL_MS = 1e4;
|
|
46554
46805
|
SCAN_INTERVAL_MS = 5 * 60 * 1e3;
|
|
46555
46806
|
status = {
|
|
@@ -46569,37 +46820,37 @@ var watch_exports = {};
|
|
|
46569
46820
|
__export(watch_exports, {
|
|
46570
46821
|
watchForever: () => watchForever
|
|
46571
46822
|
});
|
|
46572
|
-
import { existsSync as
|
|
46573
|
-
import { homedir as
|
|
46574
|
-
import { join as
|
|
46823
|
+
import { existsSync as existsSync8 } from "fs";
|
|
46824
|
+
import { homedir as homedir7, platform as platform3 } from "os";
|
|
46825
|
+
import { join as join8 } from "path";
|
|
46575
46826
|
function resolveWatchDirs() {
|
|
46576
|
-
const home2 =
|
|
46577
|
-
const xdgConfig = process.env.XDG_CONFIG_HOME ??
|
|
46578
|
-
const xdgData = process.env.XDG_DATA_HOME ??
|
|
46827
|
+
const home2 = homedir7();
|
|
46828
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME ?? join8(home2, ".config");
|
|
46829
|
+
const xdgData = process.env.XDG_DATA_HOME ?? join8(home2, ".local/share");
|
|
46579
46830
|
const candidates = [
|
|
46580
46831
|
// universal (default HOME-rooted CLI data dirs)
|
|
46581
|
-
|
|
46582
|
-
|
|
46583
|
-
|
|
46584
|
-
|
|
46585
|
-
|
|
46832
|
+
join8(home2, ".claude/projects"),
|
|
46833
|
+
join8(home2, ".codex/sessions"),
|
|
46834
|
+
join8(home2, ".cursor/ai-tracking"),
|
|
46835
|
+
join8(home2, ".gemini"),
|
|
46836
|
+
join8(home2, ".aider"),
|
|
46586
46837
|
// XDG / Linux
|
|
46587
|
-
|
|
46588
|
-
|
|
46589
|
-
|
|
46590
|
-
|
|
46591
|
-
|
|
46592
|
-
|
|
46838
|
+
join8(xdgConfig, "claude/projects"),
|
|
46839
|
+
join8(xdgConfig, "codex/sessions"),
|
|
46840
|
+
join8(xdgConfig, "Cursor/User/workspaceStorage"),
|
|
46841
|
+
join8(xdgConfig, "Code/User/workspaceStorage"),
|
|
46842
|
+
join8(xdgConfig, "Code - Insiders/User/workspaceStorage"),
|
|
46843
|
+
join8(xdgData, "claude/projects"),
|
|
46593
46844
|
// macOS
|
|
46594
46845
|
...platform3() === "darwin" ? [
|
|
46595
|
-
|
|
46596
|
-
|
|
46597
|
-
|
|
46598
|
-
|
|
46599
|
-
|
|
46846
|
+
join8(home2, "Library/Application Support/Cursor/User/workspaceStorage"),
|
|
46847
|
+
join8(home2, "Library/Application Support/Claude"),
|
|
46848
|
+
join8(home2, "Library/Application Support/Code/User/workspaceStorage"),
|
|
46849
|
+
join8(home2, "Library/Application Support/Windsurf/User/workspaceStorage"),
|
|
46850
|
+
join8(home2, "Library/Application Support/Zed")
|
|
46600
46851
|
] : []
|
|
46601
46852
|
];
|
|
46602
|
-
return Array.from(new Set(candidates)).filter((p) =>
|
|
46853
|
+
return Array.from(new Set(candidates)).filter((p) => existsSync8(p));
|
|
46603
46854
|
}
|
|
46604
46855
|
async function safeScan(reason) {
|
|
46605
46856
|
if (scanning) {
|
|
@@ -46659,51 +46910,53 @@ var init_watch = __esm({
|
|
|
46659
46910
|
|
|
46660
46911
|
// src/cli.ts
|
|
46661
46912
|
init_src2();
|
|
46913
|
+
init_identity();
|
|
46662
46914
|
init_api();
|
|
46663
46915
|
init_config2();
|
|
46664
46916
|
init_scan();
|
|
46665
46917
|
import { spawn } from "child_process";
|
|
46666
46918
|
import { randomBytes } from "crypto";
|
|
46667
46919
|
import { platform as platform4, release, arch as cpuArch, hostname as hostname2 } from "os";
|
|
46920
|
+
import { createInterface as createInterface3 } from "readline";
|
|
46668
46921
|
|
|
46669
46922
|
// src/service.ts
|
|
46670
46923
|
import { spawnSync } from "child_process";
|
|
46671
46924
|
import {
|
|
46672
46925
|
copyFileSync,
|
|
46673
|
-
existsSync as
|
|
46674
|
-
mkdirSync,
|
|
46926
|
+
existsSync as existsSync5,
|
|
46927
|
+
mkdirSync as mkdirSync2,
|
|
46675
46928
|
unlinkSync,
|
|
46676
|
-
writeFileSync as
|
|
46929
|
+
writeFileSync as writeFileSync3
|
|
46677
46930
|
} from "fs";
|
|
46678
|
-
import { homedir as
|
|
46679
|
-
import { dirname as dirname4, join as
|
|
46931
|
+
import { homedir as homedir5, platform as platform2, userInfo } from "os";
|
|
46932
|
+
import { dirname as dirname4, join as join4 } from "path";
|
|
46680
46933
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
46681
46934
|
var SERVICE_LABEL = "ai.modelstat.agent";
|
|
46682
46935
|
var SYSTEMD_UNIT = "modelstat";
|
|
46683
46936
|
function home() {
|
|
46684
|
-
return
|
|
46937
|
+
return homedir5();
|
|
46685
46938
|
}
|
|
46686
46939
|
function stateDir() {
|
|
46687
|
-
return
|
|
46940
|
+
return join4(home(), ".modelstat");
|
|
46688
46941
|
}
|
|
46689
46942
|
function binDir() {
|
|
46690
|
-
return
|
|
46943
|
+
return join4(stateDir(), "bin");
|
|
46691
46944
|
}
|
|
46692
46945
|
function logDir() {
|
|
46693
|
-
return
|
|
46946
|
+
return join4(stateDir(), "logs");
|
|
46694
46947
|
}
|
|
46695
46948
|
function installedCliPath() {
|
|
46696
|
-
return
|
|
46949
|
+
return join4(binDir(), "modelstat.mjs");
|
|
46697
46950
|
}
|
|
46698
46951
|
function runningCliPath() {
|
|
46699
46952
|
return fileURLToPath2(import.meta.url).replace(/service\.(mjs|js|ts)$/, "cli.mjs");
|
|
46700
46953
|
}
|
|
46701
46954
|
function installBundle() {
|
|
46702
|
-
|
|
46703
|
-
|
|
46955
|
+
mkdirSync2(binDir(), { recursive: true });
|
|
46956
|
+
mkdirSync2(logDir(), { recursive: true });
|
|
46704
46957
|
const src = runningCliPath();
|
|
46705
46958
|
const dest = installedCliPath();
|
|
46706
|
-
if (!
|
|
46959
|
+
if (!existsSync5(src)) {
|
|
46707
46960
|
throw new Error(
|
|
46708
46961
|
`Can't find the CLI bundle to install from (${src}). Are you running a local dev build?`
|
|
46709
46962
|
);
|
|
@@ -46715,21 +46968,21 @@ function nodeBinary() {
|
|
|
46715
46968
|
return process.execPath;
|
|
46716
46969
|
}
|
|
46717
46970
|
function plistPath() {
|
|
46718
|
-
return
|
|
46971
|
+
return join4(home(), "Library", "LaunchAgents", `${SERVICE_LABEL}.plist`);
|
|
46719
46972
|
}
|
|
46720
46973
|
function locateTrayExecutable() {
|
|
46721
46974
|
const candidates = [
|
|
46722
|
-
|
|
46975
|
+
join4(home(), "Applications", "ModelstatTray.app", "Contents", "MacOS", "modelstat-tray"),
|
|
46723
46976
|
"/Applications/ModelstatTray.app/Contents/MacOS/modelstat-tray"
|
|
46724
46977
|
];
|
|
46725
46978
|
for (const p of candidates) {
|
|
46726
|
-
if (
|
|
46979
|
+
if (existsSync5(p)) return p;
|
|
46727
46980
|
}
|
|
46728
46981
|
return null;
|
|
46729
46982
|
}
|
|
46730
46983
|
function writePlist(cliPath) {
|
|
46731
46984
|
const p = plistPath();
|
|
46732
|
-
|
|
46985
|
+
mkdirSync2(dirname4(p), { recursive: true });
|
|
46733
46986
|
const tray = locateTrayExecutable();
|
|
46734
46987
|
const programArgs = tray ? ` <string>${tray}</string>` : [
|
|
46735
46988
|
` <string>${nodeBinary()}</string>`,
|
|
@@ -46749,8 +47002,8 @@ ${programArgs}
|
|
|
46749
47002
|
<key>KeepAlive</key>
|
|
46750
47003
|
<dict><key>SuccessfulExit</key><false/></dict>
|
|
46751
47004
|
<key>ThrottleInterval</key><integer>30</integer>
|
|
46752
|
-
<key>StandardOutPath</key><string>${
|
|
46753
|
-
<key>StandardErrorPath</key><string>${
|
|
47005
|
+
<key>StandardOutPath</key><string>${join4(logDir(), "out.log")}</string>
|
|
47006
|
+
<key>StandardErrorPath</key><string>${join4(logDir(), "err.log")}</string>
|
|
46754
47007
|
<key>EnvironmentVariables</key>
|
|
46755
47008
|
<dict>
|
|
46756
47009
|
<key>PATH</key><string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin</string>
|
|
@@ -46759,7 +47012,7 @@ ${programArgs}
|
|
|
46759
47012
|
</dict>
|
|
46760
47013
|
</plist>
|
|
46761
47014
|
`;
|
|
46762
|
-
|
|
47015
|
+
writeFileSync3(p, plist, { mode: 420 });
|
|
46763
47016
|
return p;
|
|
46764
47017
|
}
|
|
46765
47018
|
function launchctl(args) {
|
|
@@ -46790,7 +47043,7 @@ function macUninstall() {
|
|
|
46790
47043
|
const target = `gui/${uid}/${SERVICE_LABEL}`;
|
|
46791
47044
|
launchctl(["bootout", target]);
|
|
46792
47045
|
const plist = plistPath();
|
|
46793
|
-
if (
|
|
47046
|
+
if (existsSync5(plist)) {
|
|
46794
47047
|
try {
|
|
46795
47048
|
unlinkSync(plist);
|
|
46796
47049
|
} catch {
|
|
@@ -46803,12 +47056,12 @@ function macStatus() {
|
|
|
46803
47056
|
return { running: r.ok, hint: r.ok ? "launchd managed" : "not installed" };
|
|
46804
47057
|
}
|
|
46805
47058
|
function systemdUnitPath() {
|
|
46806
|
-
const xdg = process.env.XDG_CONFIG_HOME ??
|
|
46807
|
-
return
|
|
47059
|
+
const xdg = process.env.XDG_CONFIG_HOME ?? join4(home(), ".config");
|
|
47060
|
+
return join4(xdg, "systemd", "user", `${SYSTEMD_UNIT}.service`);
|
|
46808
47061
|
}
|
|
46809
47062
|
function writeSystemdUnit(cliPath) {
|
|
46810
47063
|
const unitPath = systemdUnitPath();
|
|
46811
|
-
|
|
47064
|
+
mkdirSync2(dirname4(unitPath), { recursive: true });
|
|
46812
47065
|
const unit = `[Unit]
|
|
46813
47066
|
Description=modelstat agent
|
|
46814
47067
|
Documentation=https://modelstat.ai
|
|
@@ -46823,13 +47076,13 @@ RestartSec=10
|
|
|
46823
47076
|
# Don't restart-storm if the service is persistently unreachable.
|
|
46824
47077
|
StartLimitIntervalSec=300
|
|
46825
47078
|
StartLimitBurst=10
|
|
46826
|
-
StandardOutput=append:${
|
|
46827
|
-
StandardError=append:${
|
|
47079
|
+
StandardOutput=append:${join4(logDir(), "out.log")}
|
|
47080
|
+
StandardError=append:${join4(logDir(), "err.log")}
|
|
46828
47081
|
|
|
46829
47082
|
[Install]
|
|
46830
47083
|
WantedBy=default.target
|
|
46831
47084
|
`;
|
|
46832
|
-
|
|
47085
|
+
writeFileSync3(unitPath, unit, { mode: 420 });
|
|
46833
47086
|
return unitPath;
|
|
46834
47087
|
}
|
|
46835
47088
|
function systemctl(args) {
|
|
@@ -46849,7 +47102,7 @@ function linuxInstall() {
|
|
|
46849
47102
|
function linuxUninstall() {
|
|
46850
47103
|
systemctl(["disable", "--now", `${SYSTEMD_UNIT}.service`]);
|
|
46851
47104
|
const unit = systemdUnitPath();
|
|
46852
|
-
if (
|
|
47105
|
+
if (existsSync5(unit)) {
|
|
46853
47106
|
try {
|
|
46854
47107
|
unlinkSync(unit);
|
|
46855
47108
|
} catch {
|
|
@@ -46893,9 +47146,9 @@ function logsDir() {
|
|
|
46893
47146
|
}
|
|
46894
47147
|
function installTrayApp(sourceAppPath) {
|
|
46895
47148
|
if (platform2() !== "darwin") return null;
|
|
46896
|
-
if (!
|
|
46897
|
-
const dest =
|
|
46898
|
-
|
|
47149
|
+
if (!existsSync5(sourceAppPath)) return null;
|
|
47150
|
+
const dest = join4(home(), "Applications", "ModelstatTray.app");
|
|
47151
|
+
mkdirSync2(dirname4(dest), { recursive: true });
|
|
46899
47152
|
spawnSync("rm", ["-rf", dest]);
|
|
46900
47153
|
const r = spawnSync("cp", ["-R", sourceAppPath, dest], { encoding: "utf8" });
|
|
46901
47154
|
if (r.status !== 0) {
|
|
@@ -46908,25 +47161,25 @@ function bundledTrayAppPath() {
|
|
|
46908
47161
|
const here2 = dirname4(fileURLToPath2(import.meta.url));
|
|
46909
47162
|
const candidates = [
|
|
46910
47163
|
// Pre-built .app — CI with codesigning drops one here.
|
|
46911
|
-
|
|
47164
|
+
join4(here2, "..", "vendor", "ModelstatTray.app"),
|
|
46912
47165
|
// Local dev layout: apps/agent-dev/src/service.ts → ../../tray-mac/build/ModelstatTray.app
|
|
46913
|
-
|
|
47166
|
+
join4(here2, "..", "..", "tray-mac", "build", "ModelstatTray.app")
|
|
46914
47167
|
];
|
|
46915
47168
|
for (const c of candidates) {
|
|
46916
|
-
if (
|
|
47169
|
+
if (existsSync5(c)) return c;
|
|
46917
47170
|
}
|
|
46918
47171
|
const sourceDirs = [
|
|
46919
|
-
|
|
46920
|
-
|
|
47172
|
+
join4(here2, "..", "vendor", "tray-mac"),
|
|
47173
|
+
join4(here2, "..", "..", "tray-mac")
|
|
46921
47174
|
];
|
|
46922
47175
|
for (const src of sourceDirs) {
|
|
46923
|
-
const build =
|
|
46924
|
-
if (!
|
|
47176
|
+
const build = join4(src, "build-app.sh");
|
|
47177
|
+
if (!existsSync5(build)) continue;
|
|
46925
47178
|
if (!hasSwift()) return null;
|
|
46926
47179
|
const r = spawnSync("bash", [build], { cwd: src, encoding: "utf8" });
|
|
46927
47180
|
if (r.status === 0) {
|
|
46928
|
-
const app =
|
|
46929
|
-
if (
|
|
47181
|
+
const app = join4(src, "build", "ModelstatTray.app");
|
|
47182
|
+
if (existsSync5(app)) return app;
|
|
46930
47183
|
}
|
|
46931
47184
|
}
|
|
46932
47185
|
return null;
|
|
@@ -46942,6 +47195,20 @@ function trayStatus() {
|
|
|
46942
47195
|
}
|
|
46943
47196
|
|
|
46944
47197
|
// src/cli.ts
|
|
47198
|
+
async function confirmPrompt(question, defaultYes) {
|
|
47199
|
+
if (process.stdin.isTTY !== true) return defaultYes;
|
|
47200
|
+
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
47201
|
+
try {
|
|
47202
|
+
const raw = await new Promise((resolve6) => rl.question(question, resolve6));
|
|
47203
|
+
const ans = raw.trim().toLowerCase();
|
|
47204
|
+
if (ans === "") return defaultYes;
|
|
47205
|
+
if (ans === "y" || ans === "yes") return true;
|
|
47206
|
+
if (ans === "n" || ans === "no") return false;
|
|
47207
|
+
return defaultYes;
|
|
47208
|
+
} finally {
|
|
47209
|
+
rl.close();
|
|
47210
|
+
}
|
|
47211
|
+
}
|
|
46945
47212
|
function tryOpenBrowser(url) {
|
|
46946
47213
|
const p = platform4();
|
|
46947
47214
|
const cmd = p === "darwin" ? "open" : p === "win32" ? "cmd" : "xdg-open";
|
|
@@ -47008,11 +47275,13 @@ async function cmdSelfRegister() {
|
|
|
47008
47275
|
device_uuid: deviceUuid,
|
|
47009
47276
|
fingerprint
|
|
47010
47277
|
});
|
|
47011
|
-
state.
|
|
47012
|
-
|
|
47013
|
-
|
|
47014
|
-
|
|
47015
|
-
|
|
47278
|
+
state.saveFreshIdentity({
|
|
47279
|
+
deviceUuid: res.device_uuid,
|
|
47280
|
+
deviceId: res.device_id,
|
|
47281
|
+
bearerToken: res.device_secret,
|
|
47282
|
+
claimCode: res.claim_code,
|
|
47283
|
+
claimUrl: res.claim_url
|
|
47284
|
+
});
|
|
47016
47285
|
process.stdout.write(` \x1B[32m\u2713\x1B[0m registered device_id=${res.device_id}
|
|
47017
47286
|
`);
|
|
47018
47287
|
process.stdout.write(` \x1B[32m\u2713\x1B[0m secret ${res.secret_prefix}\u2026 (hashed on server, never re-sent)
|
|
@@ -47072,19 +47341,21 @@ async function cmdConnect(opts) {
|
|
|
47072
47341
|
};
|
|
47073
47342
|
const wipeAndSelfRegister = async (reason) => {
|
|
47074
47343
|
warn(`${reason} \u2014 re-registering this device`);
|
|
47344
|
+
const bak = backupIdentity();
|
|
47345
|
+
if (bak) warn(`old identity moved to ${bak}`);
|
|
47075
47346
|
state.setBearer(null);
|
|
47076
|
-
state.setDeviceId(null);
|
|
47077
|
-
state.setDeviceUuid(null);
|
|
47078
|
-
state.setClaimCode(null);
|
|
47079
|
-
state.setClaimUrl(null);
|
|
47080
47347
|
await cmdSelfRegister();
|
|
47081
47348
|
};
|
|
47082
|
-
if (
|
|
47349
|
+
if (opts.fresh && hasIdentityFile()) {
|
|
47350
|
+
step("`--fresh` passed \u2014 minting a new device identity");
|
|
47351
|
+
await wipeAndSelfRegister("forced fresh start");
|
|
47352
|
+
} else if (!state.deviceUuid || !state.bearer || !state.deviceId) {
|
|
47083
47353
|
step("Registering this device with modelstat.ai");
|
|
47084
47354
|
await cmdSelfRegister();
|
|
47085
47355
|
} else {
|
|
47086
47356
|
step("Re-using existing device identity");
|
|
47087
47357
|
ok(`device ${state.deviceId}`);
|
|
47358
|
+
ok(`identity file ${identityPath()}`);
|
|
47088
47359
|
try {
|
|
47089
47360
|
const me = await fetchDeviceMe(state.bearer);
|
|
47090
47361
|
if (me.claim_code && me.claim_code !== state.claimCode) {
|
|
@@ -47096,6 +47367,15 @@ async function cmdConnect(opts) {
|
|
|
47096
47367
|
}
|
|
47097
47368
|
} catch (e) {
|
|
47098
47369
|
if (e instanceof DeviceMeUnauthorized) {
|
|
47370
|
+
const interactive = !opts.yes && process.stdin.isTTY === true;
|
|
47371
|
+
if (interactive) {
|
|
47372
|
+
const prompt = "cached credentials no longer accepted by the server. Re-register this device? [Y/n] ";
|
|
47373
|
+
const answer = await confirmPrompt(prompt, true);
|
|
47374
|
+
if (!answer) {
|
|
47375
|
+
warn("keeping existing identity; connect aborted");
|
|
47376
|
+
return;
|
|
47377
|
+
}
|
|
47378
|
+
}
|
|
47099
47379
|
await wipeAndSelfRegister("cached credentials no longer valid");
|
|
47100
47380
|
} else {
|
|
47101
47381
|
warn(`couldn't refresh device state: ${e.message}`);
|
|
@@ -47375,7 +47655,9 @@ function cmdPaths(args) {
|
|
|
47375
47655
|
function parseConnectOpts(argv) {
|
|
47376
47656
|
return {
|
|
47377
47657
|
json: argv.includes("--json"),
|
|
47378
|
-
noBrowser: argv.includes("--no-browser")
|
|
47658
|
+
noBrowser: argv.includes("--no-browser"),
|
|
47659
|
+
fresh: argv.includes("--fresh"),
|
|
47660
|
+
yes: argv.includes("--yes") || argv.includes("-y")
|
|
47379
47661
|
};
|
|
47380
47662
|
}
|
|
47381
47663
|
async function main() {
|