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/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: process.env.AGENT_API_URL ?? "http://localhost:3010",
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 store.get("apiUrl");
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
- async function cmdConnect() {
6068
- const init = await startDeviceCode();
6069
- const url = init.verification_url;
6070
- const opened = tryOpenBrowser(url);
6071
- const line = "\u2501".repeat(60);
6072
- console.log();
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
- console.log(` \x1B[1;36m${url}\x1B[0m`);
6079
- console.log();
6080
- console.log(" Pairing code (if you'd rather type it manually):");
6081
- console.log();
6082
- console.log(` \x1B[1;33m${init.user_code}\x1B[0m`);
6083
- console.log();
6084
- console.log(` Waiting for approval (expires in ${init.expires_in}s)`);
6085
- console.log(line);
6086
- console.log();
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
- console.log();
6097
- console.log("\u2713 approved \u2014 bearer stored");
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
- console.error();
6110
- console.error(`\u2717 device enrollment failed: ${e.message}`);
6111
- console.error(
6112
- " The approval went through but the server couldn't register this"
6113
- );
6114
- console.error(
6115
- " machine. Check that your modelstat API is reachable at"
6116
- );
6117
- console.error(` ${state.apiUrl} \u2014 then run \`modelstat connect\` again.`);
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
- console.error("\n\u2717 enrollment returned no device_id. Aborting.");
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
- console.log(`\u2713 device registered (${enroll.device_id.slice(0, 8)}\u2026)`);
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
- console.log(`\u2713 service installed (${svc.path})`);
6129
- console.log(` logs: ${svc.logs}`);
6130
- console.log();
6131
- const banner = "\u2501".repeat(60);
6132
- console.log(banner);
6133
- console.log(" You're connected. The agent is now running in the");
6134
- console.log(" background and will start on login.");
6135
- console.log();
6136
- console.log(" Manage it from the dashboard:");
6137
- console.log(" \x1B[1;36mhttps://modelstat.ai/dashboard\x1B[0m");
6138
- console.log();
6139
- console.log(" Stop it any time: \x1B[2mmodelstat stop\x1B[0m");
6140
- console.log(" Uninstall: \x1B[2mmodelstat uninstall\x1B[0m");
6141
- console.log(banner);
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: ${err.message}`);
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) console.warn(`
6444
+ if (dots % 15 === 0 && !opts.json) {
6445
+ console.warn(`
6156
6446
  [retry] ${e.message}`);
6447
+ }
6157
6448
  }
6158
6449
  dots += 1;
6159
- process.stdout.write(".");
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) return cmdConnect();
6560
+ if (!state.bearer || !state.deviceId)
6561
+ return cmdConnect(parseConnectOpts(rest));
6225
6562
  return cmdStart();
6226
- case "connect":
6227
- return cmdConnect();
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) \u2014 pair if needed, then run the daemon in the foreground");
6247
- console.log(" connect \u2014 pair + install as a background service, then exit");
6248
- console.log(" status \u2014 show pairing + service state");
6249
- console.log(" stop \u2014 stop and uninstall the background service");
6250
- console.log(" start \u2014 run the daemon (used by the installed service)");
6251
- console.log(" discover \u2014 one-shot report of installs/identities");
6252
- console.log(" scan \u2014 one-shot parse + upload of local JSONL");
6253
- console.log(" watch \u2014 continuous (chokidar) with periodic backstop");
6254
- console.log(" register \u2014 DEV shortcut (email-only, no OAuth)");
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
  }