modelstat 0.0.7 → 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 +417 -66
- 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),
|
|
@@ -5149,7 +5201,7 @@ function machineId() {
|
|
|
5149
5201
|
function defaultUserEmail() {
|
|
5150
5202
|
return process.env.AGENT_USER_EMAIL ?? state.userEmail ?? "aram@dev.local";
|
|
5151
5203
|
}
|
|
5152
|
-
var here, store, state;
|
|
5204
|
+
var here, DEFAULT_API_URL, LEGACY_LOCALHOST_API, store, state;
|
|
5153
5205
|
var init_config = __esm({
|
|
5154
5206
|
"src/config.ts"() {
|
|
5155
5207
|
"use strict";
|
|
@@ -5162,20 +5214,35 @@ var init_config = __esm({
|
|
|
5162
5214
|
}
|
|
5163
5215
|
if (d === "/") break;
|
|
5164
5216
|
}
|
|
5217
|
+
DEFAULT_API_URL = "https://modelstat.ai";
|
|
5218
|
+
LEGACY_LOCALHOST_API = "http://localhost:3010";
|
|
5165
5219
|
store = new Conf({
|
|
5166
5220
|
projectName: "modelstat-agent-dev",
|
|
5167
5221
|
defaults: {
|
|
5168
|
-
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: "",
|
|
5169
5227
|
bearerToken: null,
|
|
5170
5228
|
deviceId: null,
|
|
5229
|
+
deviceUuid: null,
|
|
5230
|
+
claimCode: null,
|
|
5231
|
+
claimUrl: null,
|
|
5171
5232
|
userEmail: null,
|
|
5172
5233
|
defaultOrgId: null,
|
|
5173
5234
|
cursor: {}
|
|
5174
5235
|
}
|
|
5175
5236
|
});
|
|
5176
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. */
|
|
5177
5241
|
get apiUrl() {
|
|
5178
|
-
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;
|
|
5179
5246
|
},
|
|
5180
5247
|
setApiUrl(v) {
|
|
5181
5248
|
store.set("apiUrl", v);
|
|
@@ -5198,6 +5265,24 @@ var init_config = __esm({
|
|
|
5198
5265
|
setUserEmail(v) {
|
|
5199
5266
|
store.set("userEmail", v);
|
|
5200
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
|
+
},
|
|
5201
5286
|
getCursor(path) {
|
|
5202
5287
|
return store.get("cursor")[path];
|
|
5203
5288
|
},
|
|
@@ -5215,6 +5300,27 @@ var init_config = __esm({
|
|
|
5215
5300
|
|
|
5216
5301
|
// src/api.ts
|
|
5217
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
|
+
}
|
|
5218
5324
|
async function startDeviceCode() {
|
|
5219
5325
|
const res = await request(`${state.apiUrl}/v1/auth/device_code`, {
|
|
5220
5326
|
method: "POST",
|
|
@@ -5800,6 +5906,7 @@ init_api();
|
|
|
5800
5906
|
init_config();
|
|
5801
5907
|
init_scan();
|
|
5802
5908
|
import { spawn } from "child_process";
|
|
5909
|
+
import { randomBytes } from "crypto";
|
|
5803
5910
|
import { platform as platform4, release, arch as cpuArch, hostname as hostname2 } from "os";
|
|
5804
5911
|
import { setTimeout as delay } from "timers/promises";
|
|
5805
5912
|
|
|
@@ -6044,6 +6151,78 @@ function osArch() {
|
|
|
6044
6151
|
if (a === "arm64") return "arm64";
|
|
6045
6152
|
return "other";
|
|
6046
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
|
+
}
|
|
6047
6226
|
async function cmdRegister() {
|
|
6048
6227
|
const email = defaultUserEmail();
|
|
6049
6228
|
const res = await enrollDevice({
|
|
@@ -6055,6 +6234,9 @@ async function cmdRegister() {
|
|
|
6055
6234
|
agent_version: AGENT_VERSION3,
|
|
6056
6235
|
user_email: email
|
|
6057
6236
|
});
|
|
6237
|
+
if (!res.bearer_token || !res.device_id) {
|
|
6238
|
+
throw new Error("enroll response missing bearer or device_id");
|
|
6239
|
+
}
|
|
6058
6240
|
state.setBearer(res.bearer_token);
|
|
6059
6241
|
state.setDeviceId(res.device_id);
|
|
6060
6242
|
state.setUserEmail(email);
|
|
@@ -6064,28 +6246,103 @@ async function cmdRegister() {
|
|
|
6064
6246
|
console.log(` bearer: ${res.bearer_token.slice(0, 16)}\u2026`);
|
|
6065
6247
|
console.log(` state: ${state.storePath}`);
|
|
6066
6248
|
}
|
|
6067
|
-
|
|
6068
|
-
|
|
6069
|
-
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
console.log(line);
|
|
6074
|
-
console.log(
|
|
6075
|
-
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
|
+
`
|
|
6076
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.");
|
|
6077
6309
|
console.log();
|
|
6078
|
-
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
|
|
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
|
+
}
|
|
6087
6343
|
const deadline = Date.now() + init.expires_in * 1e3;
|
|
6088
6344
|
let dots = 0;
|
|
6345
|
+
let lastPollEmit = 0;
|
|
6089
6346
|
while (Date.now() < deadline) {
|
|
6090
6347
|
await delay(init.interval * 1e3);
|
|
6091
6348
|
try {
|
|
@@ -6093,8 +6350,11 @@ async function cmdConnect() {
|
|
|
6093
6350
|
if (res.status === "approved") {
|
|
6094
6351
|
state.setBearer(res.bearer_token);
|
|
6095
6352
|
state.setUserEmail(`user:${res.user_id}`);
|
|
6096
|
-
|
|
6097
|
-
|
|
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
|
+
}
|
|
6098
6358
|
let enroll = null;
|
|
6099
6359
|
try {
|
|
6100
6360
|
enroll = await enrollDevice({
|
|
@@ -6106,43 +6366,72 @@ async function cmdConnect() {
|
|
|
6106
6366
|
agent_version: AGENT_VERSION3
|
|
6107
6367
|
});
|
|
6108
6368
|
} catch (e) {
|
|
6109
|
-
|
|
6110
|
-
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
|
|
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
|
+
}
|
|
6118
6382
|
process.exit(1);
|
|
6119
6383
|
}
|
|
6120
6384
|
if (!enroll?.device_id) {
|
|
6121
|
-
|
|
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
|
+
}
|
|
6122
6392
|
process.exit(1);
|
|
6123
6393
|
}
|
|
6124
6394
|
state.setDeviceId(enroll.device_id);
|
|
6125
|
-
|
|
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
|
+
}
|
|
6126
6399
|
try {
|
|
6127
6400
|
const svc = installService();
|
|
6128
|
-
|
|
6129
|
-
|
|
6130
|
-
|
|
6131
|
-
|
|
6132
|
-
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
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 });
|
|
6142
6422
|
return;
|
|
6143
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
|
+
}
|
|
6144
6433
|
console.warn();
|
|
6145
|
-
console.warn(`\u26A0 couldn't install as a background service: ${
|
|
6434
|
+
console.warn(`\u26A0 couldn't install as a background service: ${message}`);
|
|
6146
6435
|
console.warn(" Running in the foreground instead \u2014 press Ctrl-C to stop.");
|
|
6147
6436
|
console.warn(" (Install globally with `npm i -g modelstat` then re-run `modelstat connect`.)");
|
|
6148
6437
|
console.warn();
|
|
@@ -6152,13 +6441,31 @@ async function cmdConnect() {
|
|
|
6152
6441
|
}
|
|
6153
6442
|
}
|
|
6154
6443
|
} catch (e) {
|
|
6155
|
-
if (dots % 15 === 0)
|
|
6444
|
+
if (dots % 15 === 0 && !opts.json) {
|
|
6445
|
+
console.warn(`
|
|
6156
6446
|
[retry] ${e.message}`);
|
|
6447
|
+
}
|
|
6157
6448
|
}
|
|
6158
6449
|
dots += 1;
|
|
6159
|
-
|
|
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");
|
|
6160
6468
|
}
|
|
6161
|
-
console.error("\ndevice_code expired before approval");
|
|
6162
6469
|
process.exit(1);
|
|
6163
6470
|
}
|
|
6164
6471
|
async function cmdDiscover() {
|
|
@@ -6211,22 +6518,58 @@ async function cmdStatus() {
|
|
|
6211
6518
|
if (paired) {
|
|
6212
6519
|
console.log(` user: ${state.userEmail ?? "(unknown)"}`);
|
|
6213
6520
|
console.log(` device: ${state.deviceId}`);
|
|
6521
|
+
console.log(` uuid: ${state.deviceUuid ?? "(not self-registered)"}`);
|
|
6214
6522
|
}
|
|
6215
6523
|
console.log(`service: ${s.running ? "running" : "stopped"} (${s.hint})`);
|
|
6216
6524
|
console.log(`logs: ${logsDir()}`);
|
|
6217
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
|
+
};
|
|
6218
6553
|
}
|
|
6219
6554
|
async function main() {
|
|
6220
6555
|
const cmd = process.argv[2];
|
|
6556
|
+
const rest = process.argv.slice(3);
|
|
6221
6557
|
switch (cmd) {
|
|
6222
6558
|
case void 0:
|
|
6223
6559
|
case "start":
|
|
6224
|
-
if (!state.bearer || !state.deviceId)
|
|
6560
|
+
if (!state.bearer || !state.deviceId)
|
|
6561
|
+
return cmdConnect(parseConnectOpts(rest));
|
|
6225
6562
|
return cmdStart();
|
|
6226
|
-
case "connect":
|
|
6227
|
-
|
|
6563
|
+
case "connect": {
|
|
6564
|
+
const opts = parseConnectOpts(rest);
|
|
6565
|
+
return opts.oauth ? cmdConnectOAuth(opts) : cmdConnect(opts);
|
|
6566
|
+
}
|
|
6228
6567
|
case "register":
|
|
6229
6568
|
return cmdRegister();
|
|
6569
|
+
case "self-register":
|
|
6570
|
+
return cmdSelfRegister();
|
|
6571
|
+
case "await-claim":
|
|
6572
|
+
return cmdAwaitClaim();
|
|
6230
6573
|
case "discover":
|
|
6231
6574
|
return cmdDiscover();
|
|
6232
6575
|
case "scan":
|
|
@@ -6238,20 +6581,28 @@ async function main() {
|
|
|
6238
6581
|
return cmdStop();
|
|
6239
6582
|
case "status":
|
|
6240
6583
|
return cmdStatus();
|
|
6584
|
+
case "paths":
|
|
6585
|
+
cmdPaths(rest);
|
|
6586
|
+
return;
|
|
6241
6587
|
default:
|
|
6242
6588
|
console.log(
|
|
6243
|
-
"usage: modelstat [connect|status|stop|start|discover|scan|watch|register]"
|
|
6589
|
+
"usage: modelstat [connect|self-register|await-claim|status|paths|stop|start|discover|scan|watch|register]"
|
|
6244
6590
|
);
|
|
6245
6591
|
console.log();
|
|
6246
|
-
console.log(" (no args)
|
|
6247
|
-
console.log(" connect
|
|
6248
|
-
console.log("
|
|
6249
|
-
console.log("
|
|
6250
|
-
console.log("
|
|
6251
|
-
console.log("
|
|
6252
|
-
console.log("
|
|
6253
|
-
console.log("
|
|
6254
|
-
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)");
|
|
6255
6606
|
process.exit(1);
|
|
6256
6607
|
}
|
|
6257
6608
|
}
|