modelstat 0.0.6 → 0.0.10
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 +67 -30
- package/dist/cli.mjs +725 -96
- package/dist/cli.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -67,6 +67,12 @@ var init_enums = __esm({
|
|
|
67
67
|
"raw_sdk_anthropic",
|
|
68
68
|
"raw_sdk_openai",
|
|
69
69
|
"raw_sdk_google",
|
|
70
|
+
// Web chat UIs (Chrome-extension companion). Categorically distinct
|
|
71
|
+
// from *_cli / *_desktop tools — same provider, different surface.
|
|
72
|
+
"chatgpt_web",
|
|
73
|
+
"claude_web",
|
|
74
|
+
"gemini_web",
|
|
75
|
+
"grok_web",
|
|
70
76
|
"unknown"
|
|
71
77
|
];
|
|
72
78
|
PROVIDERS = [
|
|
@@ -117,6 +123,7 @@ var init_enums = __esm({
|
|
|
117
123
|
"snap",
|
|
118
124
|
"manual",
|
|
119
125
|
"docker",
|
|
126
|
+
"chrome_extension",
|
|
120
127
|
"unknown"
|
|
121
128
|
];
|
|
122
129
|
OS_FAMILIES = ["macos", "linux", "windows", "other"];
|
|
@@ -4258,7 +4265,7 @@ var init_zod = __esm({
|
|
|
4258
4265
|
});
|
|
4259
4266
|
|
|
4260
4267
|
// ../../packages/core/src/schemas.ts
|
|
4261
|
-
var TokenUsage, TaxonomyHint, SessionSegmentSummary, SessionSummary, GitContext, RawEvent, IngestBatch, DeviceEnrollment, DetectedInstallation, DetectedIdentity, DiscoveryReport, ClassificationConfidenceEnum;
|
|
4268
|
+
var TokenUsage, TaxonomyHint, SessionSegmentSummary, SessionSummary, GitContext, RawEvent, IngestBatch, DeviceEnrollment, DeviceSelfRegister, DeviceClaimRequest, ProcessingMetadata, RedactionPolicy, DetectedInstallation, DetectedIdentity, DiscoveryReport, ClassificationConfidenceEnum;
|
|
4262
4269
|
var init_schemas = __esm({
|
|
4263
4270
|
"../../packages/core/src/schemas.ts"() {
|
|
4264
4271
|
"use strict";
|
|
@@ -4375,6 +4382,51 @@ var init_schemas = __esm({
|
|
|
4375
4382
|
arch: external_exports.enum(["x86_64", "arm64", "other"]),
|
|
4376
4383
|
agent_version: external_exports.string().max(40)
|
|
4377
4384
|
});
|
|
4385
|
+
DeviceSelfRegister = external_exports.object({
|
|
4386
|
+
/** Agent-generated UUIDv7 — must pass shape + recent-timestamp checks. */
|
|
4387
|
+
device_uuid: external_exports.string(),
|
|
4388
|
+
/** Base64-encoded ed25519 public key, exactly 32 raw bytes. Optional
|
|
4389
|
+
* but recommended (used for sender-constrained tokens / DPoP later). */
|
|
4390
|
+
public_key: external_exports.string().optional(),
|
|
4391
|
+
/** Free-form snapshot shown to the human on the claim page so they
|
|
4392
|
+
* can sanity-check what they're claiming. */
|
|
4393
|
+
fingerprint: external_exports.object({
|
|
4394
|
+
hostname: external_exports.string().max(120).optional(),
|
|
4395
|
+
os: external_exports.string().max(60).optional(),
|
|
4396
|
+
os_family: external_exports.enum(OS_FAMILIES).optional(),
|
|
4397
|
+
os_version: external_exports.string().max(60).optional(),
|
|
4398
|
+
arch: external_exports.enum(["x86_64", "arm64", "other"]).optional(),
|
|
4399
|
+
agent: external_exports.string().max(80).optional(),
|
|
4400
|
+
agent_version: external_exports.string().max(40).optional()
|
|
4401
|
+
// Allow extra fields for forward-compat without breaking old agents.
|
|
4402
|
+
}).catchall(external_exports.union([external_exports.string(), external_exports.number(), external_exports.boolean()])).default({})
|
|
4403
|
+
});
|
|
4404
|
+
DeviceClaimRequest = external_exports.object({
|
|
4405
|
+
claim_code: external_exports.string().max(40),
|
|
4406
|
+
/** Optional org_id if the user belongs to multiple orgs and wants to
|
|
4407
|
+
* claim into a specific one. Defaults to personal org. */
|
|
4408
|
+
org_id: external_exports.string().uuid().optional()
|
|
4409
|
+
});
|
|
4410
|
+
ProcessingMetadata = external_exports.object({
|
|
4411
|
+
redacted_by: external_exports.string().max(120).optional(),
|
|
4412
|
+
redaction_policy: external_exports.string().max(80).optional(),
|
|
4413
|
+
redaction_policy_version: external_exports.string().max(20).optional(),
|
|
4414
|
+
redactions_applied: external_exports.number().int().min(0).optional(),
|
|
4415
|
+
compacted: external_exports.boolean().optional(),
|
|
4416
|
+
summarized: external_exports.boolean().optional(),
|
|
4417
|
+
bytes_saved: external_exports.number().int().min(0).optional(),
|
|
4418
|
+
changes_applied: external_exports.number().int().min(0).optional(),
|
|
4419
|
+
original_size_bytes: external_exports.number().int().min(0).optional(),
|
|
4420
|
+
uploaded_size_bytes: external_exports.number().int().min(0).optional()
|
|
4421
|
+
});
|
|
4422
|
+
RedactionPolicy = external_exports.object({
|
|
4423
|
+
name: external_exports.string().max(60),
|
|
4424
|
+
version: external_exports.string().max(20),
|
|
4425
|
+
description: external_exports.string().max(400),
|
|
4426
|
+
redacts: external_exports.array(external_exports.string()).max(40),
|
|
4427
|
+
is_default: external_exports.boolean().default(false),
|
|
4428
|
+
recommended_for: external_exports.string().max(160).optional()
|
|
4429
|
+
});
|
|
4378
4430
|
DetectedInstallation = external_exports.object({
|
|
4379
4431
|
tool: external_exports.enum(TOOLS),
|
|
4380
4432
|
install_method: external_exports.enum(INSTALL_METHODS),
|
|
@@ -4424,6 +4476,17 @@ var init_redact = __esm({
|
|
|
4424
4476
|
}
|
|
4425
4477
|
});
|
|
4426
4478
|
|
|
4479
|
+
// ../../packages/core/src/billing.ts
|
|
4480
|
+
var MILLION, FREE_INCLUDED_TOKENS, TEAM_INCLUDED_PER_SEAT;
|
|
4481
|
+
var init_billing = __esm({
|
|
4482
|
+
"../../packages/core/src/billing.ts"() {
|
|
4483
|
+
"use strict";
|
|
4484
|
+
MILLION = 1000000n;
|
|
4485
|
+
FREE_INCLUDED_TOKENS = 100n * MILLION;
|
|
4486
|
+
TEAM_INCLUDED_PER_SEAT = 250n * MILLION;
|
|
4487
|
+
}
|
|
4488
|
+
});
|
|
4489
|
+
|
|
4427
4490
|
// ../../packages/core/src/index.ts
|
|
4428
4491
|
var init_src = __esm({
|
|
4429
4492
|
"../../packages/core/src/index.ts"() {
|
|
@@ -4432,6 +4495,7 @@ var init_src = __esm({
|
|
|
4432
4495
|
init_schemas();
|
|
4433
4496
|
init_ids();
|
|
4434
4497
|
init_redact();
|
|
4498
|
+
init_billing();
|
|
4435
4499
|
}
|
|
4436
4500
|
});
|
|
4437
4501
|
|
|
@@ -5137,7 +5201,7 @@ function machineId() {
|
|
|
5137
5201
|
function defaultUserEmail() {
|
|
5138
5202
|
return process.env.AGENT_USER_EMAIL ?? state.userEmail ?? "aram@dev.local";
|
|
5139
5203
|
}
|
|
5140
|
-
var here, store, state;
|
|
5204
|
+
var here, DEFAULT_API_URL, LEGACY_LOCALHOST_API, store, state;
|
|
5141
5205
|
var init_config = __esm({
|
|
5142
5206
|
"src/config.ts"() {
|
|
5143
5207
|
"use strict";
|
|
@@ -5150,20 +5214,35 @@ var init_config = __esm({
|
|
|
5150
5214
|
}
|
|
5151
5215
|
if (d === "/") break;
|
|
5152
5216
|
}
|
|
5217
|
+
DEFAULT_API_URL = "https://modelstat.ai";
|
|
5218
|
+
LEGACY_LOCALHOST_API = "http://localhost:3010";
|
|
5153
5219
|
store = new Conf({
|
|
5154
5220
|
projectName: "modelstat-agent-dev",
|
|
5155
5221
|
defaults: {
|
|
5156
|
-
apiUrl
|
|
5222
|
+
// Intentionally empty — the apiUrl getter below computes this
|
|
5223
|
+
// from env + stored value + DEFAULT_API_URL. Keeping the stored
|
|
5224
|
+
// default empty avoids freezing a stale value into the config
|
|
5225
|
+
// file the way 0.0.7 did.
|
|
5226
|
+
apiUrl: "",
|
|
5157
5227
|
bearerToken: null,
|
|
5158
5228
|
deviceId: null,
|
|
5229
|
+
deviceUuid: null,
|
|
5230
|
+
claimCode: null,
|
|
5231
|
+
claimUrl: null,
|
|
5159
5232
|
userEmail: null,
|
|
5160
5233
|
defaultOrgId: null,
|
|
5161
5234
|
cursor: {}
|
|
5162
5235
|
}
|
|
5163
5236
|
});
|
|
5164
5237
|
state = {
|
|
5238
|
+
/** Resolution order: env var → stored value (if user ran `setApiUrl`
|
|
5239
|
+
* or paired pre-0.0.8) → production default. The legacy localhost
|
|
5240
|
+
* value is ignored so upgrades from 0.0.7 self-heal. */
|
|
5165
5241
|
get apiUrl() {
|
|
5166
|
-
return
|
|
5242
|
+
if (process.env.AGENT_API_URL) return process.env.AGENT_API_URL;
|
|
5243
|
+
const stored = store.get("apiUrl");
|
|
5244
|
+
if (stored && stored !== LEGACY_LOCALHOST_API) return stored;
|
|
5245
|
+
return DEFAULT_API_URL;
|
|
5167
5246
|
},
|
|
5168
5247
|
setApiUrl(v) {
|
|
5169
5248
|
store.set("apiUrl", v);
|
|
@@ -5186,6 +5265,24 @@ var init_config = __esm({
|
|
|
5186
5265
|
setUserEmail(v) {
|
|
5187
5266
|
store.set("userEmail", v);
|
|
5188
5267
|
},
|
|
5268
|
+
get deviceUuid() {
|
|
5269
|
+
return store.get("deviceUuid");
|
|
5270
|
+
},
|
|
5271
|
+
setDeviceUuid(v) {
|
|
5272
|
+
store.set("deviceUuid", v);
|
|
5273
|
+
},
|
|
5274
|
+
get claimCode() {
|
|
5275
|
+
return store.get("claimCode");
|
|
5276
|
+
},
|
|
5277
|
+
setClaimCode(v) {
|
|
5278
|
+
store.set("claimCode", v);
|
|
5279
|
+
},
|
|
5280
|
+
get claimUrl() {
|
|
5281
|
+
return store.get("claimUrl");
|
|
5282
|
+
},
|
|
5283
|
+
setClaimUrl(v) {
|
|
5284
|
+
store.set("claimUrl", v);
|
|
5285
|
+
},
|
|
5189
5286
|
getCursor(path) {
|
|
5190
5287
|
return store.get("cursor")[path];
|
|
5191
5288
|
},
|
|
@@ -5203,6 +5300,27 @@ var init_config = __esm({
|
|
|
5203
5300
|
|
|
5204
5301
|
// src/api.ts
|
|
5205
5302
|
import { request } from "undici";
|
|
5303
|
+
async function selfRegister(input) {
|
|
5304
|
+
const res = await request(`${state.apiUrl}/v1/devices/self-register`, {
|
|
5305
|
+
method: "POST",
|
|
5306
|
+
headers: { "content-type": "application/json" },
|
|
5307
|
+
body: JSON.stringify(input)
|
|
5308
|
+
});
|
|
5309
|
+
if (res.statusCode >= 300) {
|
|
5310
|
+
throw new Error(`self-register failed: ${res.statusCode} ${await res.body.text()}`);
|
|
5311
|
+
}
|
|
5312
|
+
return await res.body.json();
|
|
5313
|
+
}
|
|
5314
|
+
async function fetchDeviceMe(secret) {
|
|
5315
|
+
const res = await request(`${state.apiUrl}/v1/devices/me`, {
|
|
5316
|
+
method: "GET",
|
|
5317
|
+
headers: { authorization: `Bearer ${secret}` }
|
|
5318
|
+
});
|
|
5319
|
+
if (res.statusCode >= 300) {
|
|
5320
|
+
throw new Error(`devices/me failed: ${res.statusCode} ${await res.body.text()}`);
|
|
5321
|
+
}
|
|
5322
|
+
return await res.body.json();
|
|
5323
|
+
}
|
|
5206
5324
|
async function startDeviceCode() {
|
|
5207
5325
|
const res = await request(`${state.apiUrl}/v1/auth/device_code`, {
|
|
5208
5326
|
method: "POST",
|
|
@@ -5512,7 +5630,7 @@ __export(daemon_exports, {
|
|
|
5512
5630
|
setProgress: () => setProgress,
|
|
5513
5631
|
setQueue: () => setQueue
|
|
5514
5632
|
});
|
|
5515
|
-
import { existsSync as
|
|
5633
|
+
import { existsSync as existsSync5, statSync as statSync2 } from "fs";
|
|
5516
5634
|
import { request as request2 } from "undici";
|
|
5517
5635
|
function setPhase(phase, message) {
|
|
5518
5636
|
status.phase = phase;
|
|
@@ -5616,21 +5734,21 @@ async function runDaemon() {
|
|
|
5616
5734
|
await runDiscovery();
|
|
5617
5735
|
await runScanCycle("startup");
|
|
5618
5736
|
const chokidar2 = (await import("chokidar")).default;
|
|
5619
|
-
const { homedir:
|
|
5620
|
-
const { join:
|
|
5621
|
-
const
|
|
5737
|
+
const { homedir: homedir5, platform: platform5 } = await import("os");
|
|
5738
|
+
const { join: join5 } = await import("path");
|
|
5739
|
+
const home2 = homedir5();
|
|
5622
5740
|
const dirs = [
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
...
|
|
5628
|
-
|
|
5629
|
-
|
|
5741
|
+
join5(home2, ".claude/projects"),
|
|
5742
|
+
join5(home2, ".codex/sessions"),
|
|
5743
|
+
join5(home2, ".cursor/ai-tracking"),
|
|
5744
|
+
join5(home2, ".gemini"),
|
|
5745
|
+
...platform5() === "darwin" ? [
|
|
5746
|
+
join5(home2, "Library/Application Support/Cursor/User/workspaceStorage"),
|
|
5747
|
+
join5(home2, "Library/Application Support/Claude")
|
|
5630
5748
|
] : [
|
|
5631
|
-
|
|
5749
|
+
join5(home2, ".config/Cursor/User/workspaceStorage")
|
|
5632
5750
|
]
|
|
5633
|
-
].filter((p) =>
|
|
5751
|
+
].filter((p) => existsSync5(p) && statSync2(p).isDirectory());
|
|
5634
5752
|
setPhase("watching", `Watching ${dirs.length} directories`);
|
|
5635
5753
|
const watcher = chokidar2.watch(dirs, {
|
|
5636
5754
|
persistent: true,
|
|
@@ -5695,37 +5813,37 @@ __export(watch_exports, {
|
|
|
5695
5813
|
watchForever: () => watchForever
|
|
5696
5814
|
});
|
|
5697
5815
|
import chokidar from "chokidar";
|
|
5698
|
-
import { existsSync as
|
|
5699
|
-
import { homedir as
|
|
5700
|
-
import { join as
|
|
5816
|
+
import { existsSync as existsSync6 } from "fs";
|
|
5817
|
+
import { homedir as homedir4, platform as platform3 } from "os";
|
|
5818
|
+
import { join as join4 } from "path";
|
|
5701
5819
|
function resolveWatchDirs() {
|
|
5702
|
-
const
|
|
5703
|
-
const xdgConfig = process.env.XDG_CONFIG_HOME ??
|
|
5704
|
-
const xdgData = process.env.XDG_DATA_HOME ??
|
|
5820
|
+
const home2 = homedir4();
|
|
5821
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME ?? join4(home2, ".config");
|
|
5822
|
+
const xdgData = process.env.XDG_DATA_HOME ?? join4(home2, ".local/share");
|
|
5705
5823
|
const candidates = [
|
|
5706
5824
|
// universal (default HOME-rooted CLI data dirs)
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5825
|
+
join4(home2, ".claude/projects"),
|
|
5826
|
+
join4(home2, ".codex/sessions"),
|
|
5827
|
+
join4(home2, ".cursor/ai-tracking"),
|
|
5828
|
+
join4(home2, ".gemini"),
|
|
5829
|
+
join4(home2, ".aider"),
|
|
5712
5830
|
// XDG / Linux
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5831
|
+
join4(xdgConfig, "claude/projects"),
|
|
5832
|
+
join4(xdgConfig, "codex/sessions"),
|
|
5833
|
+
join4(xdgConfig, "Cursor/User/workspaceStorage"),
|
|
5834
|
+
join4(xdgConfig, "Code/User/workspaceStorage"),
|
|
5835
|
+
join4(xdgConfig, "Code - Insiders/User/workspaceStorage"),
|
|
5836
|
+
join4(xdgData, "claude/projects"),
|
|
5719
5837
|
// macOS
|
|
5720
|
-
...
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5838
|
+
...platform3() === "darwin" ? [
|
|
5839
|
+
join4(home2, "Library/Application Support/Cursor/User/workspaceStorage"),
|
|
5840
|
+
join4(home2, "Library/Application Support/Claude"),
|
|
5841
|
+
join4(home2, "Library/Application Support/Code/User/workspaceStorage"),
|
|
5842
|
+
join4(home2, "Library/Application Support/Windsurf/User/workspaceStorage"),
|
|
5843
|
+
join4(home2, "Library/Application Support/Zed")
|
|
5726
5844
|
] : []
|
|
5727
5845
|
];
|
|
5728
|
-
return Array.from(new Set(candidates)).filter((p) =>
|
|
5846
|
+
return Array.from(new Set(candidates)).filter((p) => existsSync6(p));
|
|
5729
5847
|
}
|
|
5730
5848
|
async function safeScan(reason) {
|
|
5731
5849
|
if (scanning) {
|
|
@@ -5788,10 +5906,225 @@ init_api();
|
|
|
5788
5906
|
init_config();
|
|
5789
5907
|
init_scan();
|
|
5790
5908
|
import { spawn } from "child_process";
|
|
5791
|
-
import {
|
|
5909
|
+
import { randomBytes } from "crypto";
|
|
5910
|
+
import { platform as platform4, release, arch as cpuArch, hostname as hostname2 } from "os";
|
|
5792
5911
|
import { setTimeout as delay } from "timers/promises";
|
|
5912
|
+
|
|
5913
|
+
// src/service.ts
|
|
5914
|
+
import { spawnSync } from "child_process";
|
|
5915
|
+
import {
|
|
5916
|
+
copyFileSync,
|
|
5917
|
+
existsSync as existsSync4,
|
|
5918
|
+
mkdirSync,
|
|
5919
|
+
unlinkSync,
|
|
5920
|
+
writeFileSync
|
|
5921
|
+
} from "fs";
|
|
5922
|
+
import { homedir as homedir3, platform as platform2, userInfo } from "os";
|
|
5923
|
+
import { dirname as dirname3, join as join3 } from "path";
|
|
5924
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5925
|
+
var SERVICE_LABEL = "ai.modelstat.agent";
|
|
5926
|
+
var SYSTEMD_UNIT = "modelstat";
|
|
5927
|
+
function home() {
|
|
5928
|
+
return homedir3();
|
|
5929
|
+
}
|
|
5930
|
+
function stateDir() {
|
|
5931
|
+
return join3(home(), ".modelstat");
|
|
5932
|
+
}
|
|
5933
|
+
function binDir() {
|
|
5934
|
+
return join3(stateDir(), "bin");
|
|
5935
|
+
}
|
|
5936
|
+
function logDir() {
|
|
5937
|
+
return join3(stateDir(), "logs");
|
|
5938
|
+
}
|
|
5939
|
+
function installedCliPath() {
|
|
5940
|
+
return join3(binDir(), "modelstat.mjs");
|
|
5941
|
+
}
|
|
5942
|
+
function runningCliPath() {
|
|
5943
|
+
return fileURLToPath2(import.meta.url).replace(/service\.(mjs|js|ts)$/, "cli.mjs");
|
|
5944
|
+
}
|
|
5945
|
+
function installBundle() {
|
|
5946
|
+
mkdirSync(binDir(), { recursive: true });
|
|
5947
|
+
mkdirSync(logDir(), { recursive: true });
|
|
5948
|
+
const src = runningCliPath();
|
|
5949
|
+
const dest = installedCliPath();
|
|
5950
|
+
if (!existsSync4(src)) {
|
|
5951
|
+
throw new Error(
|
|
5952
|
+
`Can't find the CLI bundle to install from (${src}). Are you running a local dev build?`
|
|
5953
|
+
);
|
|
5954
|
+
}
|
|
5955
|
+
copyFileSync(src, dest);
|
|
5956
|
+
return dest;
|
|
5957
|
+
}
|
|
5958
|
+
function nodeBinary() {
|
|
5959
|
+
return process.execPath;
|
|
5960
|
+
}
|
|
5961
|
+
function plistPath() {
|
|
5962
|
+
return join3(home(), "Library", "LaunchAgents", `${SERVICE_LABEL}.plist`);
|
|
5963
|
+
}
|
|
5964
|
+
function writePlist(cliPath) {
|
|
5965
|
+
const p = plistPath();
|
|
5966
|
+
mkdirSync(dirname3(p), { recursive: true });
|
|
5967
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
5968
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
5969
|
+
<plist version="1.0">
|
|
5970
|
+
<dict>
|
|
5971
|
+
<key>Label</key><string>${SERVICE_LABEL}</string>
|
|
5972
|
+
<key>ProgramArguments</key>
|
|
5973
|
+
<array>
|
|
5974
|
+
<string>${nodeBinary()}</string>
|
|
5975
|
+
<string>${cliPath}</string>
|
|
5976
|
+
<string>start</string>
|
|
5977
|
+
</array>
|
|
5978
|
+
<key>RunAtLoad</key><true/>
|
|
5979
|
+
<key>KeepAlive</key>
|
|
5980
|
+
<dict><key>SuccessfulExit</key><false/></dict>
|
|
5981
|
+
<key>ThrottleInterval</key><integer>30</integer>
|
|
5982
|
+
<key>StandardOutPath</key><string>${join3(logDir(), "out.log")}</string>
|
|
5983
|
+
<key>StandardErrorPath</key><string>${join3(logDir(), "err.log")}</string>
|
|
5984
|
+
<key>EnvironmentVariables</key>
|
|
5985
|
+
<dict>
|
|
5986
|
+
<key>PATH</key><string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin</string>
|
|
5987
|
+
</dict>
|
|
5988
|
+
<key>WorkingDirectory</key><string>${home()}</string>
|
|
5989
|
+
</dict>
|
|
5990
|
+
</plist>
|
|
5991
|
+
`;
|
|
5992
|
+
writeFileSync(p, plist, { mode: 420 });
|
|
5993
|
+
return p;
|
|
5994
|
+
}
|
|
5995
|
+
function launchctl(args) {
|
|
5996
|
+
const r = spawnSync("launchctl", args, { encoding: "utf8" });
|
|
5997
|
+
return { ok: r.status === 0, out: r.stdout ?? "", err: r.stderr ?? "" };
|
|
5998
|
+
}
|
|
5999
|
+
function macInstall() {
|
|
6000
|
+
const cliPath = installBundle();
|
|
6001
|
+
const plist = writePlist(cliPath);
|
|
6002
|
+
const uid = userInfo().uid;
|
|
6003
|
+
const target = `gui/${uid}/${SERVICE_LABEL}`;
|
|
6004
|
+
launchctl(["bootout", target]);
|
|
6005
|
+
const boot = launchctl(["bootstrap", `gui/${uid}`, plist]);
|
|
6006
|
+
if (!boot.ok && !/already loaded|service already bootstrapped/i.test(boot.err)) {
|
|
6007
|
+
const load = launchctl(["load", "-w", plist]);
|
|
6008
|
+
if (!load.ok) {
|
|
6009
|
+
throw new Error(
|
|
6010
|
+
`launchctl load failed:
|
|
6011
|
+
bootstrap: ${boot.err.trim()}
|
|
6012
|
+
load: ${load.err.trim()}`
|
|
6013
|
+
);
|
|
6014
|
+
}
|
|
6015
|
+
}
|
|
6016
|
+
launchctl(["kickstart", "-k", target]);
|
|
6017
|
+
}
|
|
6018
|
+
function macUninstall() {
|
|
6019
|
+
const uid = userInfo().uid;
|
|
6020
|
+
const target = `gui/${uid}/${SERVICE_LABEL}`;
|
|
6021
|
+
launchctl(["bootout", target]);
|
|
6022
|
+
const plist = plistPath();
|
|
6023
|
+
if (existsSync4(plist)) {
|
|
6024
|
+
try {
|
|
6025
|
+
unlinkSync(plist);
|
|
6026
|
+
} catch {
|
|
6027
|
+
}
|
|
6028
|
+
}
|
|
6029
|
+
}
|
|
6030
|
+
function macStatus() {
|
|
6031
|
+
const uid = userInfo().uid;
|
|
6032
|
+
const r = launchctl(["print", `gui/${uid}/${SERVICE_LABEL}`]);
|
|
6033
|
+
return { running: r.ok, hint: r.ok ? "launchd managed" : "not installed" };
|
|
6034
|
+
}
|
|
6035
|
+
function systemdUnitPath() {
|
|
6036
|
+
const xdg = process.env.XDG_CONFIG_HOME ?? join3(home(), ".config");
|
|
6037
|
+
return join3(xdg, "systemd", "user", `${SYSTEMD_UNIT}.service`);
|
|
6038
|
+
}
|
|
6039
|
+
function writeSystemdUnit(cliPath) {
|
|
6040
|
+
const unitPath = systemdUnitPath();
|
|
6041
|
+
mkdirSync(dirname3(unitPath), { recursive: true });
|
|
6042
|
+
const unit = `[Unit]
|
|
6043
|
+
Description=modelstat agent
|
|
6044
|
+
Documentation=https://modelstat.ai
|
|
6045
|
+
After=network-online.target
|
|
6046
|
+
Wants=network-online.target
|
|
6047
|
+
|
|
6048
|
+
[Service]
|
|
6049
|
+
Type=simple
|
|
6050
|
+
ExecStart=${nodeBinary()} ${cliPath} start
|
|
6051
|
+
Restart=always
|
|
6052
|
+
RestartSec=10
|
|
6053
|
+
# Don't restart-storm if the backend is persistently unreachable.
|
|
6054
|
+
StartLimitIntervalSec=300
|
|
6055
|
+
StartLimitBurst=10
|
|
6056
|
+
StandardOutput=append:${join3(logDir(), "out.log")}
|
|
6057
|
+
StandardError=append:${join3(logDir(), "err.log")}
|
|
6058
|
+
|
|
6059
|
+
[Install]
|
|
6060
|
+
WantedBy=default.target
|
|
6061
|
+
`;
|
|
6062
|
+
writeFileSync(unitPath, unit, { mode: 420 });
|
|
6063
|
+
return unitPath;
|
|
6064
|
+
}
|
|
6065
|
+
function systemctl(args) {
|
|
6066
|
+
const r = spawnSync("systemctl", ["--user", ...args], { encoding: "utf8" });
|
|
6067
|
+
return { ok: r.status === 0, out: r.stdout ?? "", err: r.stderr ?? "" };
|
|
6068
|
+
}
|
|
6069
|
+
function linuxInstall() {
|
|
6070
|
+
const cliPath = installBundle();
|
|
6071
|
+
writeSystemdUnit(cliPath);
|
|
6072
|
+
systemctl(["daemon-reload"]);
|
|
6073
|
+
const en = systemctl(["enable", "--now", `${SYSTEMD_UNIT}.service`]);
|
|
6074
|
+
if (!en.ok) {
|
|
6075
|
+
throw new Error(`systemctl enable failed: ${en.err.trim()}`);
|
|
6076
|
+
}
|
|
6077
|
+
systemctl(["restart", `${SYSTEMD_UNIT}.service`]);
|
|
6078
|
+
}
|
|
6079
|
+
function linuxUninstall() {
|
|
6080
|
+
systemctl(["disable", "--now", `${SYSTEMD_UNIT}.service`]);
|
|
6081
|
+
const unit = systemdUnitPath();
|
|
6082
|
+
if (existsSync4(unit)) {
|
|
6083
|
+
try {
|
|
6084
|
+
unlinkSync(unit);
|
|
6085
|
+
} catch {
|
|
6086
|
+
}
|
|
6087
|
+
}
|
|
6088
|
+
systemctl(["daemon-reload"]);
|
|
6089
|
+
}
|
|
6090
|
+
function linuxStatus() {
|
|
6091
|
+
const r = systemctl(["is-active", `${SYSTEMD_UNIT}.service`]);
|
|
6092
|
+
const active = r.out.trim() === "active";
|
|
6093
|
+
return { running: active, hint: active ? "systemd managed" : "not running" };
|
|
6094
|
+
}
|
|
6095
|
+
function installService() {
|
|
6096
|
+
const p = platform2();
|
|
6097
|
+
if (p === "darwin") {
|
|
6098
|
+
macInstall();
|
|
6099
|
+
return { path: plistPath(), logs: logDir() };
|
|
6100
|
+
}
|
|
6101
|
+
if (p === "linux") {
|
|
6102
|
+
linuxInstall();
|
|
6103
|
+
return { path: systemdUnitPath(), logs: logDir() };
|
|
6104
|
+
}
|
|
6105
|
+
throw new Error(
|
|
6106
|
+
`Service installation isn't supported on ${p}. Run 'modelstat start' manually to keep the agent running.`
|
|
6107
|
+
);
|
|
6108
|
+
}
|
|
6109
|
+
function uninstallService() {
|
|
6110
|
+
const p = platform2();
|
|
6111
|
+
if (p === "darwin") return macUninstall();
|
|
6112
|
+
if (p === "linux") return linuxUninstall();
|
|
6113
|
+
throw new Error(`Service uninstall isn't supported on ${p}.`);
|
|
6114
|
+
}
|
|
6115
|
+
function serviceStatus() {
|
|
6116
|
+
const p = platform2();
|
|
6117
|
+
if (p === "darwin") return macStatus();
|
|
6118
|
+
if (p === "linux") return linuxStatus();
|
|
6119
|
+
return { running: false, hint: `unsupported platform (${p})` };
|
|
6120
|
+
}
|
|
6121
|
+
function logsDir() {
|
|
6122
|
+
return logDir();
|
|
6123
|
+
}
|
|
6124
|
+
|
|
6125
|
+
// src/cli.ts
|
|
5793
6126
|
function tryOpenBrowser(url) {
|
|
5794
|
-
const p =
|
|
6127
|
+
const p = platform4();
|
|
5795
6128
|
const cmd = p === "darwin" ? "open" : p === "win32" ? "cmd" : "xdg-open";
|
|
5796
6129
|
const args = p === "win32" ? ["/c", "start", "", url] : [url];
|
|
5797
6130
|
try {
|
|
@@ -5807,7 +6140,7 @@ function tryOpenBrowser(url) {
|
|
|
5807
6140
|
}
|
|
5808
6141
|
var AGENT_VERSION3 = "agent-dev-0.0.1";
|
|
5809
6142
|
function osFamily() {
|
|
5810
|
-
const p =
|
|
6143
|
+
const p = platform4();
|
|
5811
6144
|
if (p === "darwin") return "macos";
|
|
5812
6145
|
if (p === "linux") return "linux";
|
|
5813
6146
|
return "other";
|
|
@@ -5818,6 +6151,78 @@ function osArch() {
|
|
|
5818
6151
|
if (a === "arm64") return "arm64";
|
|
5819
6152
|
return "other";
|
|
5820
6153
|
}
|
|
6154
|
+
function generateUuidV7() {
|
|
6155
|
+
const ms = Date.now();
|
|
6156
|
+
const tsHex = ms.toString(16).padStart(12, "0");
|
|
6157
|
+
const rand = randomBytes(10);
|
|
6158
|
+
rand[0] = rand[0] & 15 | 112;
|
|
6159
|
+
rand[2] = rand[2] & 63 | 128;
|
|
6160
|
+
const hex = tsHex + rand.toString("hex");
|
|
6161
|
+
return [
|
|
6162
|
+
hex.slice(0, 8),
|
|
6163
|
+
hex.slice(8, 12),
|
|
6164
|
+
hex.slice(12, 16),
|
|
6165
|
+
hex.slice(16, 20),
|
|
6166
|
+
hex.slice(20, 32)
|
|
6167
|
+
].join("-");
|
|
6168
|
+
}
|
|
6169
|
+
async function cmdSelfRegister() {
|
|
6170
|
+
const deviceUuid = state.deviceUuid ?? generateUuidV7();
|
|
6171
|
+
const fingerprint = {
|
|
6172
|
+
hostname: hostname2(),
|
|
6173
|
+
os_family: osFamily(),
|
|
6174
|
+
os_version: release(),
|
|
6175
|
+
arch: osArch(),
|
|
6176
|
+
agent: "modelstat-agent-dev",
|
|
6177
|
+
agent_version: AGENT_VERSION3
|
|
6178
|
+
};
|
|
6179
|
+
const res = await selfRegister({
|
|
6180
|
+
device_uuid: deviceUuid,
|
|
6181
|
+
fingerprint
|
|
6182
|
+
});
|
|
6183
|
+
state.setDeviceUuid(res.device_uuid);
|
|
6184
|
+
state.setDeviceId(res.device_id);
|
|
6185
|
+
state.setBearer(res.device_secret);
|
|
6186
|
+
state.setClaimCode(res.claim_code);
|
|
6187
|
+
state.setClaimUrl(res.claim_url);
|
|
6188
|
+
console.log(`\u2713 self-registered`);
|
|
6189
|
+
console.log(` device_uuid: ${res.device_uuid}`);
|
|
6190
|
+
console.log(` device_id: ${res.device_id}`);
|
|
6191
|
+
console.log(` secret_prefix: ${res.secret_prefix}\u2026`);
|
|
6192
|
+
console.log();
|
|
6193
|
+
console.log(` Claim this device to attach it to your account:`);
|
|
6194
|
+
console.log(` ${res.claim_url}`);
|
|
6195
|
+
console.log(` code: ${res.claim_code}`);
|
|
6196
|
+
console.log();
|
|
6197
|
+
console.log(` state: ${state.storePath}`);
|
|
6198
|
+
}
|
|
6199
|
+
async function cmdAwaitClaim() {
|
|
6200
|
+
const secret = state.bearer;
|
|
6201
|
+
if (!secret) {
|
|
6202
|
+
console.error("not registered \u2014 run `modelstat self-register` first");
|
|
6203
|
+
process.exit(1);
|
|
6204
|
+
}
|
|
6205
|
+
const url = state.claimUrl ?? "(visit your dashboard)";
|
|
6206
|
+
console.log(`waiting for human to claim this device:
|
|
6207
|
+
${url}
|
|
6208
|
+
`);
|
|
6209
|
+
while (true) {
|
|
6210
|
+
let me;
|
|
6211
|
+
try {
|
|
6212
|
+
me = await fetchDeviceMe(secret);
|
|
6213
|
+
} catch (e) {
|
|
6214
|
+
console.error(`poll failed: ${e.message}`);
|
|
6215
|
+
await delay(5e3);
|
|
6216
|
+
continue;
|
|
6217
|
+
}
|
|
6218
|
+
if (me.status === "claimed") {
|
|
6219
|
+
console.log(`\u2713 claimed by user_id=${me.user_id}`);
|
|
6220
|
+
return;
|
|
6221
|
+
}
|
|
6222
|
+
process.stdout.write(".");
|
|
6223
|
+
await delay(2e3);
|
|
6224
|
+
}
|
|
6225
|
+
}
|
|
5821
6226
|
async function cmdRegister() {
|
|
5822
6227
|
const email = defaultUserEmail();
|
|
5823
6228
|
const res = await enrollDevice({
|
|
@@ -5829,6 +6234,9 @@ async function cmdRegister() {
|
|
|
5829
6234
|
agent_version: AGENT_VERSION3,
|
|
5830
6235
|
user_email: email
|
|
5831
6236
|
});
|
|
6237
|
+
if (!res.bearer_token || !res.device_id) {
|
|
6238
|
+
throw new Error("enroll response missing bearer or device_id");
|
|
6239
|
+
}
|
|
5832
6240
|
state.setBearer(res.bearer_token);
|
|
5833
6241
|
state.setDeviceId(res.device_id);
|
|
5834
6242
|
state.setUserEmail(email);
|
|
@@ -5838,28 +6246,103 @@ async function cmdRegister() {
|
|
|
5838
6246
|
console.log(` bearer: ${res.bearer_token.slice(0, 16)}\u2026`);
|
|
5839
6247
|
console.log(` state: ${state.storePath}`);
|
|
5840
6248
|
}
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
console.log(line);
|
|
5848
|
-
console.log(
|
|
5849
|
-
opened ? " Opening your browser to approve this device\u2026" : " Open this URL in any browser to approve this device:"
|
|
6249
|
+
var DASHBOARD_URL = "https://modelstat.ai/dashboard";
|
|
6250
|
+
function emitEvent(opts, event, fields = {}) {
|
|
6251
|
+
if (!opts.json) return;
|
|
6252
|
+
process.stdout.write(
|
|
6253
|
+
`${JSON.stringify({ v: 1, ts: Date.now(), event, ...fields })}
|
|
6254
|
+
`
|
|
5850
6255
|
);
|
|
6256
|
+
}
|
|
6257
|
+
async function cmdConnect(opts) {
|
|
6258
|
+
if (!state.deviceUuid || !state.bearer || !state.deviceId) {
|
|
6259
|
+
await cmdSelfRegister();
|
|
6260
|
+
}
|
|
6261
|
+
const claimCode = state.claimCode ?? "(unknown)";
|
|
6262
|
+
const claimUrl = state.claimUrl ?? `https://modelstat.ai/d/${claimCode}`;
|
|
6263
|
+
const agentUrl = `https://modelstat.ai/da/${claimCode}`;
|
|
6264
|
+
emitEvent(opts, "registered", {
|
|
6265
|
+
device_uuid: state.deviceUuid,
|
|
6266
|
+
device_id: state.deviceId,
|
|
6267
|
+
claim_code: claimCode,
|
|
6268
|
+
claim_url: claimUrl,
|
|
6269
|
+
agent_url: agentUrl
|
|
6270
|
+
});
|
|
6271
|
+
let serviceOk = false;
|
|
6272
|
+
try {
|
|
6273
|
+
const svc = installService();
|
|
6274
|
+
serviceOk = true;
|
|
6275
|
+
emitEvent(opts, "service_installed", { path: svc.path, logs: svc.logs });
|
|
6276
|
+
} catch (e) {
|
|
6277
|
+
emitEvent(opts, "service_install_failed", { error: e.message });
|
|
6278
|
+
}
|
|
6279
|
+
if (!opts.json) {
|
|
6280
|
+
const line = "\u2501".repeat(60);
|
|
6281
|
+
console.log();
|
|
6282
|
+
console.log(line);
|
|
6283
|
+
console.log(` \u2713 Device registered \u2014 streaming your AI usage to modelstat.`);
|
|
6284
|
+
console.log();
|
|
6285
|
+
console.log(` Open your dashboard (no sign-up needed):`);
|
|
6286
|
+
console.log(` \x1B[1;36m${claimUrl}\x1B[0m`);
|
|
6287
|
+
console.log();
|
|
6288
|
+
console.log(` Agent-friendly (for LLMs / MCPs):`);
|
|
6289
|
+
console.log(` \x1B[2m${agentUrl}\x1B[0m`);
|
|
6290
|
+
console.log();
|
|
6291
|
+
console.log(` Claim this device so it keeps analyzing past 100M tokens/mo:`);
|
|
6292
|
+
console.log(` \x1B[2m${claimUrl}/claim\x1B[0m`);
|
|
6293
|
+
console.log(line);
|
|
6294
|
+
console.log();
|
|
6295
|
+
}
|
|
6296
|
+
if (!opts.noBrowser) {
|
|
6297
|
+
const opened = tryOpenBrowser(claimUrl);
|
|
6298
|
+
emitEvent(opts, "browser_open_attempted", { opened });
|
|
6299
|
+
}
|
|
6300
|
+
emitEvent(opts, "done", { claim_url: claimUrl, agent_url: agentUrl });
|
|
6301
|
+
if (serviceOk) {
|
|
6302
|
+
return;
|
|
6303
|
+
}
|
|
6304
|
+
if (opts.json) {
|
|
6305
|
+
return;
|
|
6306
|
+
}
|
|
6307
|
+
console.log(" Service install not supported on this platform \u2014 running in foreground.");
|
|
6308
|
+
console.log(" Press Ctrl-C to stop.");
|
|
5851
6309
|
console.log();
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
6310
|
+
const { runDaemon: runDaemon2 } = await Promise.resolve().then(() => (init_daemon(), daemon_exports));
|
|
6311
|
+
await runDaemon2();
|
|
6312
|
+
}
|
|
6313
|
+
async function cmdConnectOAuth(opts) {
|
|
6314
|
+
const init = await startDeviceCode();
|
|
6315
|
+
const url = init.verification_url;
|
|
6316
|
+
emitEvent(opts, "device_code_issued", {
|
|
6317
|
+
verification_url: url,
|
|
6318
|
+
user_code: init.user_code,
|
|
6319
|
+
expires_in: init.expires_in,
|
|
6320
|
+
interval: init.interval
|
|
6321
|
+
});
|
|
6322
|
+
const opened = opts.noBrowser ? false : tryOpenBrowser(url);
|
|
6323
|
+
if (!opts.noBrowser) {
|
|
6324
|
+
emitEvent(opts, "browser_open_attempted", { opened });
|
|
6325
|
+
}
|
|
6326
|
+
if (!opts.json) {
|
|
6327
|
+
const line = "\u2501".repeat(60);
|
|
6328
|
+
console.log();
|
|
6329
|
+
console.log(line);
|
|
6330
|
+
console.log(
|
|
6331
|
+
opened ? " Opening your browser to approve this device\u2026" : " Open this URL in any browser to approve this device:"
|
|
6332
|
+
);
|
|
6333
|
+
console.log();
|
|
6334
|
+
console.log(` \x1B[1;36m${url}\x1B[0m`);
|
|
6335
|
+
console.log();
|
|
6336
|
+
console.log(" Pairing code:");
|
|
6337
|
+
console.log(` \x1B[1;33m${init.user_code}\x1B[0m`);
|
|
6338
|
+
console.log();
|
|
6339
|
+
console.log(` Waiting for approval (expires in ${init.expires_in}s)`);
|
|
6340
|
+
console.log(line);
|
|
6341
|
+
console.log();
|
|
6342
|
+
}
|
|
5861
6343
|
const deadline = Date.now() + init.expires_in * 1e3;
|
|
5862
6344
|
let dots = 0;
|
|
6345
|
+
let lastPollEmit = 0;
|
|
5863
6346
|
while (Date.now() < deadline) {
|
|
5864
6347
|
await delay(init.interval * 1e3);
|
|
5865
6348
|
try {
|
|
@@ -5867,8 +6350,11 @@ async function cmdConnect() {
|
|
|
5867
6350
|
if (res.status === "approved") {
|
|
5868
6351
|
state.setBearer(res.bearer_token);
|
|
5869
6352
|
state.setUserEmail(`user:${res.user_id}`);
|
|
5870
|
-
|
|
5871
|
-
|
|
6353
|
+
emitEvent(opts, "approved", { user_id: res.user_id });
|
|
6354
|
+
if (!opts.json) {
|
|
6355
|
+
console.log();
|
|
6356
|
+
console.log("\u2713 approved \u2014 bearer stored");
|
|
6357
|
+
}
|
|
5872
6358
|
let enroll = null;
|
|
5873
6359
|
try {
|
|
5874
6360
|
enroll = await enrollDevice({
|
|
@@ -5880,39 +6366,106 @@ async function cmdConnect() {
|
|
|
5880
6366
|
agent_version: AGENT_VERSION3
|
|
5881
6367
|
});
|
|
5882
6368
|
} catch (e) {
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
6369
|
+
const message = e.message;
|
|
6370
|
+
emitEvent(opts, "error", { code: "enroll_failed", message });
|
|
6371
|
+
if (!opts.json) {
|
|
6372
|
+
console.error();
|
|
6373
|
+
console.error(`\u2717 device enrollment failed: ${message}`);
|
|
6374
|
+
console.error(
|
|
6375
|
+
" The approval went through but the server couldn't register this"
|
|
6376
|
+
);
|
|
6377
|
+
console.error(
|
|
6378
|
+
" machine. Check that your modelstat API is reachable at"
|
|
6379
|
+
);
|
|
6380
|
+
console.error(` ${state.apiUrl} \u2014 then run \`modelstat connect\` again.`);
|
|
6381
|
+
}
|
|
5892
6382
|
process.exit(1);
|
|
5893
6383
|
}
|
|
5894
6384
|
if (!enroll?.device_id) {
|
|
5895
|
-
|
|
6385
|
+
emitEvent(opts, "error", {
|
|
6386
|
+
code: "enroll_failed",
|
|
6387
|
+
message: "enrollment returned no device_id"
|
|
6388
|
+
});
|
|
6389
|
+
if (!opts.json) {
|
|
6390
|
+
console.error("\n\u2717 enrollment returned no device_id. Aborting.");
|
|
6391
|
+
}
|
|
5896
6392
|
process.exit(1);
|
|
5897
6393
|
}
|
|
5898
6394
|
state.setDeviceId(enroll.device_id);
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
6395
|
+
emitEvent(opts, "device_enrolled", { device_id: enroll.device_id });
|
|
6396
|
+
if (!opts.json) {
|
|
6397
|
+
console.log(`\u2713 device registered (${enroll.device_id.slice(0, 8)}\u2026)`);
|
|
6398
|
+
}
|
|
6399
|
+
try {
|
|
6400
|
+
const svc = installService();
|
|
6401
|
+
emitEvent(opts, "service_installed", {
|
|
6402
|
+
path: svc.path,
|
|
6403
|
+
logs: svc.logs
|
|
6404
|
+
});
|
|
6405
|
+
if (!opts.json) {
|
|
6406
|
+
console.log(`\u2713 service installed (${svc.path})`);
|
|
6407
|
+
console.log(` logs: ${svc.logs}`);
|
|
6408
|
+
console.log();
|
|
6409
|
+
const banner = "\u2501".repeat(60);
|
|
6410
|
+
console.log(banner);
|
|
6411
|
+
console.log(" You're connected. The agent is now running in the");
|
|
6412
|
+
console.log(" background and will start on login.");
|
|
6413
|
+
console.log();
|
|
6414
|
+
console.log(" Manage it from the dashboard:");
|
|
6415
|
+
console.log(` \x1B[1;36m${DASHBOARD_URL}\x1B[0m`);
|
|
6416
|
+
console.log();
|
|
6417
|
+
console.log(" Stop it any time: \x1B[2mmodelstat stop\x1B[0m");
|
|
6418
|
+
console.log(" Uninstall: \x1B[2mmodelstat uninstall\x1B[0m");
|
|
6419
|
+
console.log(banner);
|
|
6420
|
+
}
|
|
6421
|
+
emitEvent(opts, "done", { dashboard_url: DASHBOARD_URL });
|
|
6422
|
+
return;
|
|
6423
|
+
} catch (err) {
|
|
6424
|
+
const message = err.message;
|
|
6425
|
+
emitEvent(opts, "service_install_failed", {
|
|
6426
|
+
error: message,
|
|
6427
|
+
fallback: opts.json ? "manual" : "foreground"
|
|
6428
|
+
});
|
|
6429
|
+
if (opts.json) {
|
|
6430
|
+
emitEvent(opts, "done", { dashboard_url: DASHBOARD_URL });
|
|
6431
|
+
return;
|
|
6432
|
+
}
|
|
6433
|
+
console.warn();
|
|
6434
|
+
console.warn(`\u26A0 couldn't install as a background service: ${message}`);
|
|
6435
|
+
console.warn(" Running in the foreground instead \u2014 press Ctrl-C to stop.");
|
|
6436
|
+
console.warn(" (Install globally with `npm i -g modelstat` then re-run `modelstat connect`.)");
|
|
6437
|
+
console.warn();
|
|
6438
|
+
const { runDaemon: runDaemon2 } = await Promise.resolve().then(() => (init_daemon(), daemon_exports));
|
|
6439
|
+
await runDaemon2();
|
|
6440
|
+
return;
|
|
6441
|
+
}
|
|
5907
6442
|
}
|
|
5908
6443
|
} catch (e) {
|
|
5909
|
-
if (dots % 15 === 0)
|
|
6444
|
+
if (dots % 15 === 0 && !opts.json) {
|
|
6445
|
+
console.warn(`
|
|
5910
6446
|
[retry] ${e.message}`);
|
|
6447
|
+
}
|
|
5911
6448
|
}
|
|
5912
6449
|
dots += 1;
|
|
5913
|
-
|
|
6450
|
+
if (opts.json) {
|
|
6451
|
+
const now = Date.now();
|
|
6452
|
+
if (now - lastPollEmit >= 1e4) {
|
|
6453
|
+
emitEvent(opts, "polling", {
|
|
6454
|
+
seconds_remaining: Math.max(0, Math.round((deadline - now) / 1e3))
|
|
6455
|
+
});
|
|
6456
|
+
lastPollEmit = now;
|
|
6457
|
+
}
|
|
6458
|
+
} else {
|
|
6459
|
+
process.stdout.write(".");
|
|
6460
|
+
}
|
|
6461
|
+
}
|
|
6462
|
+
emitEvent(opts, "error", {
|
|
6463
|
+
code: "device_code_expired",
|
|
6464
|
+
message: "device_code expired before approval"
|
|
6465
|
+
});
|
|
6466
|
+
if (!opts.json) {
|
|
6467
|
+
console.error("\ndevice_code expired before approval");
|
|
5914
6468
|
}
|
|
5915
|
-
console.error("\ndevice_code expired before approval");
|
|
5916
6469
|
process.exit(1);
|
|
5917
6470
|
}
|
|
5918
6471
|
async function cmdDiscover() {
|
|
@@ -5947,33 +6500,109 @@ async function cmdStart() {
|
|
|
5947
6500
|
const { runDaemon: runDaemon2 } = await Promise.resolve().then(() => (init_daemon(), daemon_exports));
|
|
5948
6501
|
await runDaemon2();
|
|
5949
6502
|
}
|
|
6503
|
+
async function cmdStop() {
|
|
6504
|
+
try {
|
|
6505
|
+
uninstallService();
|
|
6506
|
+
console.log("\u2713 service stopped and uninstalled");
|
|
6507
|
+
console.log(` Your device pairing is still in ${state.storePath}`);
|
|
6508
|
+
console.log(" Run `modelstat connect` again to re-enable.");
|
|
6509
|
+
} catch (err) {
|
|
6510
|
+
console.error(`\u2717 ${err.message}`);
|
|
6511
|
+
process.exit(1);
|
|
6512
|
+
}
|
|
6513
|
+
}
|
|
6514
|
+
async function cmdStatus() {
|
|
6515
|
+
const s = serviceStatus();
|
|
6516
|
+
const paired = !!state.bearer && !!state.deviceId;
|
|
6517
|
+
console.log(`paired: ${paired ? "yes" : "no"}`);
|
|
6518
|
+
if (paired) {
|
|
6519
|
+
console.log(` user: ${state.userEmail ?? "(unknown)"}`);
|
|
6520
|
+
console.log(` device: ${state.deviceId}`);
|
|
6521
|
+
console.log(` uuid: ${state.deviceUuid ?? "(not self-registered)"}`);
|
|
6522
|
+
}
|
|
6523
|
+
console.log(`service: ${s.running ? "running" : "stopped"} (${s.hint})`);
|
|
6524
|
+
console.log(`logs: ${logsDir()}`);
|
|
6525
|
+
console.log(`state: ${state.storePath}`);
|
|
6526
|
+
console.log(`api: ${state.apiUrl}`);
|
|
6527
|
+
}
|
|
6528
|
+
function cmdPaths(args) {
|
|
6529
|
+
const data = {
|
|
6530
|
+
state: state.storePath,
|
|
6531
|
+
logs: logsDir(),
|
|
6532
|
+
api: state.apiUrl,
|
|
6533
|
+
paired: !!state.bearer && !!state.deviceId
|
|
6534
|
+
};
|
|
6535
|
+
if (args.includes("--json")) {
|
|
6536
|
+
process.stdout.write(`${JSON.stringify(data)}
|
|
6537
|
+
`);
|
|
6538
|
+
return;
|
|
6539
|
+
}
|
|
6540
|
+
for (const [k, v] of Object.entries(data)) {
|
|
6541
|
+
console.log(`${k.padEnd(8)} ${String(v)}`);
|
|
6542
|
+
}
|
|
6543
|
+
}
|
|
6544
|
+
function parseConnectOpts(argv) {
|
|
6545
|
+
return {
|
|
6546
|
+
json: argv.includes("--json"),
|
|
6547
|
+
noBrowser: argv.includes("--no-browser"),
|
|
6548
|
+
// --oauth selects the legacy device-code flow (for users who
|
|
6549
|
+
// already have an account and want to stream straight into their
|
|
6550
|
+
// personal org). Default is the new self-register flow.
|
|
6551
|
+
oauth: argv.includes("--oauth")
|
|
6552
|
+
};
|
|
6553
|
+
}
|
|
5950
6554
|
async function main() {
|
|
5951
6555
|
const cmd = process.argv[2];
|
|
6556
|
+
const rest = process.argv.slice(3);
|
|
5952
6557
|
switch (cmd) {
|
|
5953
6558
|
case void 0:
|
|
5954
6559
|
case "start":
|
|
5955
|
-
if (!state.bearer || !state.deviceId)
|
|
6560
|
+
if (!state.bearer || !state.deviceId)
|
|
6561
|
+
return cmdConnect(parseConnectOpts(rest));
|
|
5956
6562
|
return cmdStart();
|
|
5957
|
-
case "connect":
|
|
5958
|
-
|
|
6563
|
+
case "connect": {
|
|
6564
|
+
const opts = parseConnectOpts(rest);
|
|
6565
|
+
return opts.oauth ? cmdConnectOAuth(opts) : cmdConnect(opts);
|
|
6566
|
+
}
|
|
5959
6567
|
case "register":
|
|
5960
6568
|
return cmdRegister();
|
|
6569
|
+
case "self-register":
|
|
6570
|
+
return cmdSelfRegister();
|
|
6571
|
+
case "await-claim":
|
|
6572
|
+
return cmdAwaitClaim();
|
|
5961
6573
|
case "discover":
|
|
5962
6574
|
return cmdDiscover();
|
|
5963
6575
|
case "scan":
|
|
5964
6576
|
return cmdScan();
|
|
5965
6577
|
case "watch":
|
|
5966
6578
|
return cmdWatch();
|
|
6579
|
+
case "stop":
|
|
6580
|
+
case "uninstall":
|
|
6581
|
+
return cmdStop();
|
|
6582
|
+
case "status":
|
|
6583
|
+
return cmdStatus();
|
|
6584
|
+
case "paths":
|
|
6585
|
+
cmdPaths(rest);
|
|
6586
|
+
return;
|
|
5967
6587
|
default:
|
|
5968
|
-
console.log(
|
|
6588
|
+
console.log(
|
|
6589
|
+
"usage: modelstat [connect|self-register|await-claim|status|paths|stop|start|discover|scan|watch|register]"
|
|
6590
|
+
);
|
|
5969
6591
|
console.log();
|
|
5970
|
-
console.log(" (no args)
|
|
5971
|
-
console.log(" connect
|
|
5972
|
-
console.log("
|
|
5973
|
-
console.log("
|
|
5974
|
-
console.log("
|
|
5975
|
-
console.log("
|
|
5976
|
-
console.log("
|
|
6592
|
+
console.log(" (no args) \u2014 pair if needed, then run the daemon in the foreground");
|
|
6593
|
+
console.log(" connect \u2014 OAuth device-code pair + install service, then exit");
|
|
6594
|
+
console.log(" flags: --json (NDJSON events on stdout), --no-browser");
|
|
6595
|
+
console.log(" self-register \u2014 agent-first: generate UUIDv7, get device_secret + claim code");
|
|
6596
|
+
console.log(" (no human required for the first call)");
|
|
6597
|
+
console.log(" await-claim \u2014 block until a human claims this self-registered device");
|
|
6598
|
+
console.log(" status \u2014 show pairing + service state");
|
|
6599
|
+
console.log(" paths \u2014 print state file + log dir + api URL (use --json for machine-readable)");
|
|
6600
|
+
console.log(" stop \u2014 stop and uninstall the background service");
|
|
6601
|
+
console.log(" start \u2014 run the daemon (used by the installed service)");
|
|
6602
|
+
console.log(" discover \u2014 one-shot report of installs/identities");
|
|
6603
|
+
console.log(" scan \u2014 one-shot parse + upload of local JSONL");
|
|
6604
|
+
console.log(" watch \u2014 continuous (chokidar) with periodic backstop");
|
|
6605
|
+
console.log(" register \u2014 DEV shortcut (email-only, no OAuth)");
|
|
5977
6606
|
process.exit(1);
|
|
5978
6607
|
}
|
|
5979
6608
|
}
|