@use-aistack/cli 0.6.2 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3,6 +3,9 @@
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
5
 
6
+ // src/version.ts
7
+ var CLI_VERSION = true ? "0.6.3" : "0.0.0-dev";
8
+
6
9
  // src/api.ts
7
10
  var BASE_URL = process.env.AISTACK_URL || "https://aistack.to";
8
11
  async function request(path3, options = {}) {
@@ -34,7 +37,12 @@ function failure(what, res) {
34
37
  async function authStart(machineName) {
35
38
  const res = await request("/api/cli/auth/start", {
36
39
  method: "POST",
37
- body: JSON.stringify(machineName ? { machineName } : {})
40
+ // `cliVersion` rides along so `cli_login_completed` can report which
41
+ // version linked the machine (#78). The server carries it on the pending
42
+ // session and reads it at the token exchange.
43
+ body: JSON.stringify(
44
+ machineName ? { machineName, cliVersion: CLI_VERSION } : { cliVersion: CLI_VERSION }
45
+ )
38
46
  });
39
47
  if (!res.ok) throw failure("Auth start failed", res);
40
48
  return res.json();
@@ -2750,7 +2758,11 @@ function emptyScanStats() {
2750
2758
  filesRead: 0,
2751
2759
  filesSkippedByMtime: 0,
2752
2760
  filesSkippedAsDuplicate: 0,
2753
- filesUnreadable: 0
2761
+ filesUnreadable: 0,
2762
+ filesForeign: 0,
2763
+ foreignOriginators: /* @__PURE__ */ new Map(),
2764
+ unreadableFiles: [],
2765
+ filesZstdUnsupported: 0
2754
2766
  };
2755
2767
  }
2756
2768
 
@@ -3042,11 +3054,12 @@ async function scan2(agg, opts = {}) {
3042
3054
  } catch {
3043
3055
  resolved = file;
3044
3056
  }
3045
- if (visited.has(resolved)) {
3057
+ const dedupKey = resolved.endsWith(".zst") ? resolved.slice(0, -".zst".length) : resolved;
3058
+ if (visited.has(dedupKey)) {
3046
3059
  stats.filesSkippedAsDuplicate++;
3047
3060
  continue;
3048
3061
  }
3049
- visited.add(resolved);
3062
+ visited.add(dedupKey);
3050
3063
  if (opts.sinceMs !== void 0) {
3051
3064
  try {
3052
3065
  const st = await stat3(file);
@@ -3060,11 +3073,20 @@ async function scan2(agg, opts = {}) {
3060
3073
  agg.files++;
3061
3074
  stats.filesRead++;
3062
3075
  if (opts.onProgress && agg.files % 200 === 0) opts.onProgress(agg.files);
3063
- try {
3064
- ingestFile2(agg, file, opts.sinceMs);
3065
- } catch {
3076
+ const outcome = ingestWithRetry(agg, file, opts);
3077
+ if (!outcome.ok) {
3066
3078
  stats.filesUnreadable++;
3067
3079
  stats.filesRead--;
3080
+ if (outcome.reason === "zstd-unsupported") stats.filesZstdUnsupported++;
3081
+ stats.unreadableFiles.push({
3082
+ path: path2.relative(root, file),
3083
+ reason: outcome.reason
3084
+ });
3085
+ } else if (!outcome.genuine) {
3086
+ stats.filesForeign++;
3087
+ stats.filesRead--;
3088
+ const seen = stats.foreignOriginators.get(outcome.originator) ?? 0;
3089
+ stats.foreignOriginators.set(outcome.originator, seen + 1);
3068
3090
  }
3069
3091
  }
3070
3092
  }
@@ -3079,29 +3101,81 @@ async function exists3(p8) {
3079
3101
  return false;
3080
3102
  }
3081
3103
  }
3082
- function ingestFile2(agg, file, sinceMs) {
3104
+ function errorClass(e) {
3105
+ const code = e?.code;
3106
+ if (typeof code === "string" && code.length > 0) return code;
3107
+ return e instanceof Error ? e.constructor.name : "unknown";
3108
+ }
3109
+ var readError = (reason) => Object.assign(new Error(reason), { code: reason });
3110
+ function ingestWithRetry(agg, file, opts) {
3111
+ try {
3112
+ return ingestFile2(agg, file, opts);
3113
+ } catch (e) {
3114
+ if (errorClass(e) === "ENOENT" && !file.endsWith(".zst")) {
3115
+ try {
3116
+ return ingestFile2(agg, `${file}.zst`, opts);
3117
+ } catch (e2) {
3118
+ return { ok: false, reason: errorClass(e2) };
3119
+ }
3120
+ }
3121
+ return { ok: false, reason: errorClass(e) };
3122
+ }
3123
+ }
3124
+ function ingestFile2(agg, file, opts) {
3125
+ const readFile = opts.readFileImpl ?? readFileSync9;
3083
3126
  let text;
3084
3127
  if (file.endsWith(".zst")) {
3085
- if (zstdDecompress === null) {
3086
- throw new Error("zstd not supported by this Node runtime");
3128
+ if (zstdDecompress === null) throw readError("zstd-unsupported");
3129
+ const raw = readFile(file);
3130
+ try {
3131
+ text = zstdDecompress(
3132
+ Buffer.isBuffer(raw) ? raw : Buffer.from(raw)
3133
+ ).toString("utf8");
3134
+ } catch {
3135
+ throw readError("zstd-corrupt");
3087
3136
  }
3088
- text = zstdDecompress(readFileSync9(file)).toString("utf8");
3089
3137
  } else {
3090
- text = readFileSync9(file, "utf8");
3138
+ text = readFile(file).toString("utf8");
3091
3139
  }
3092
- const state = createFileState();
3140
+ const records = [];
3141
+ let nonEmptyLines = 0;
3142
+ let parseErrors = 0;
3093
3143
  for (const line of text.split("\n")) {
3094
3144
  if (!line) continue;
3095
- agg.lines++;
3096
- let rec;
3145
+ nonEmptyLines++;
3097
3146
  try {
3098
- rec = JSON.parse(line);
3147
+ records.push(JSON.parse(line));
3099
3148
  } catch {
3100
- agg.parseErrors++;
3101
- continue;
3149
+ parseErrors++;
3102
3150
  }
3103
- ingestLine(agg, rec, state, sinceMs);
3104
3151
  }
3152
+ const verdict = classifyRollout(records);
3153
+ if (!verdict.genuine) return { ok: true, ...verdict };
3154
+ agg.lines += nonEmptyLines;
3155
+ agg.parseErrors += parseErrors;
3156
+ const state = createFileState();
3157
+ for (const rec of records) ingestLine(agg, rec, state, opts.sinceMs);
3158
+ return { ok: true, genuine: true };
3159
+ }
3160
+ function classifyRollout(records) {
3161
+ let originator = null;
3162
+ let sawTurnContext = false;
3163
+ let genuine = records.length > 0;
3164
+ for (const [i, raw] of records.entries()) {
3165
+ const rec = asObj(raw);
3166
+ const type = rec ? asStr(rec.type) : null;
3167
+ const payload = rec ? asObj(rec.payload) : null;
3168
+ if (i === 0 && type !== "session_meta") genuine = false;
3169
+ if (type === "session_meta" && payload && originator === null) {
3170
+ originator = asStr(payload.originator);
3171
+ } else if (type === "turn_context") {
3172
+ sawTurnContext = true;
3173
+ } else if (type === "event_msg" && payload && asStr(payload.type) === "token_count" && !sawTurnContext) {
3174
+ genuine = false;
3175
+ }
3176
+ }
3177
+ if (genuine) return { genuine: true };
3178
+ return { genuine: false, originator: originator ?? "(none)" };
3105
3179
  }
3106
3180
  function readConfiguredMcpServers(agg, configFile) {
3107
3181
  const file = configFile ?? path2.join(codexHome2(), "config.toml");
@@ -3370,11 +3444,12 @@ function mergeKeptPrivate(halves) {
3370
3444
  }
3371
3445
  return out;
3372
3446
  }
3373
- function buildSyncBody(built, syncConfig) {
3447
+ function buildSyncBody(built, syncConfig, autoSync) {
3374
3448
  const payloads = built.map((b) => b.payload);
3375
- if (!syncConfig.reviewKeptPrivate) return { payloads };
3449
+ const base = autoSync ? { payloads, autoSync } : { payloads };
3450
+ if (!syncConfig.reviewKeptPrivate) return base;
3376
3451
  return {
3377
- payloads,
3452
+ ...base,
3378
3453
  keptPrivate: mergeKeptPrivate(built.map((b) => b.keptPrivate))
3379
3454
  };
3380
3455
  }
@@ -3470,7 +3545,32 @@ function harnessLabel(name) {
3470
3545
  if (name === "codex") return "Codex";
3471
3546
  return name;
3472
3547
  }
3473
- function payloadBlock(payload, showHeader) {
3548
+ var UNREADABLE_FILES_SHOWN = 5;
3549
+ function scanNoteLines(stats, label) {
3550
+ const out = [];
3551
+ const shown = stats.unreadableFiles.slice(0, UNREADABLE_FILES_SHOWN);
3552
+ for (const f of shown) {
3553
+ out.push(` ${f.path} (${f.reason})`);
3554
+ }
3555
+ if (stats.unreadableFiles.length > shown.length) {
3556
+ out.push(
3557
+ ` ...${stats.unreadableFiles.length - shown.length} more`
3558
+ );
3559
+ }
3560
+ if (stats.filesZstdUnsupported > 0) {
3561
+ out.push(
3562
+ ` ${stats.filesZstdUnsupported} compressed rollout${stats.filesZstdUnsupported === 1 ? "" : "s"} need Node 22.15 or newer`
3563
+ );
3564
+ }
3565
+ if (stats.filesForeign > 0) {
3566
+ const origins = [...stats.foreignOriginators].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([name, n]) => n > 1 ? `${name} \xD7${n}` : name).join(", ");
3567
+ out.push(
3568
+ `skipped ${stats.filesForeign} file${stats.filesForeign === 1 ? "" : "s"} not written by ${label} \u2014 left out (originators: ${origins})`
3569
+ );
3570
+ }
3571
+ return out;
3572
+ }
3573
+ function payloadBlock(payload, showHeader, stats) {
3474
3574
  const out = [];
3475
3575
  if (showHeader) {
3476
3576
  out.push(
@@ -3493,6 +3593,9 @@ function payloadBlock(payload, showHeader) {
3493
3593
  `coverage ${cov.filesUnreadable} files unreadable \xB7 ${cov.linesFailed} lines failed \u2014 this reading is a floor`
3494
3594
  );
3495
3595
  }
3596
+ if (stats) {
3597
+ out.push(...scanNoteLines(stats, harnessLabel(payload.harness.name)));
3598
+ }
3496
3599
  out.push("");
3497
3600
  out.push("models");
3498
3601
  for (const m of payload.models) {
@@ -3524,7 +3627,8 @@ function buildGateSummary(ctx) {
3524
3627
  );
3525
3628
  }
3526
3629
  for (const payload of payloads) {
3527
- out.push(...payloadBlock(payload, payloads.length > 1));
3630
+ const stats = ctx.scanStats?.[payload.harness.name];
3631
+ out.push(...payloadBlock(payload, payloads.length > 1, stats));
3528
3632
  out.push("");
3529
3633
  }
3530
3634
  if (out[out.length - 1] === "") out.pop();
@@ -3550,6 +3654,12 @@ function buildGateSummary(ctx) {
3550
3654
  out.push(" they stay on this machine");
3551
3655
  }
3552
3656
  }
3657
+ if (body.autoSync !== void 0) {
3658
+ out.push("");
3659
+ out.push(
3660
+ `auto-sync ${body.autoSync.enabled ? `on, about every ${body.autoSync.frequencyHours}h` : "off"}`
3661
+ );
3662
+ }
3553
3663
  if (source === "bundled") {
3554
3664
  out.push("");
3555
3665
  out.push(
@@ -3577,9 +3687,11 @@ async function stageSync(deps) {
3577
3687
  ...token ? { token } : {}
3578
3688
  });
3579
3689
  const built = [];
3690
+ const scanStats = {};
3580
3691
  const sinceMs = windowStartMs(now, windowDays);
3581
3692
  for (const adapter of await adapters()) {
3582
3693
  const { aggregate, stats } = await adapter.scan({ sinceMs });
3694
+ scanStats[adapter.name] = stats;
3583
3695
  built.push(
3584
3696
  buildPayload({
3585
3697
  aggregate,
@@ -3593,7 +3705,8 @@ async function stageSync(deps) {
3593
3705
  })
3594
3706
  );
3595
3707
  }
3596
- const body = buildSyncBody(built, config);
3708
+ const settings = (deps.getSettingsImpl ?? getSettings)();
3709
+ const body = buildSyncBody(built, config, settings.autoSync);
3597
3710
  const bodyJson = JSON.stringify(body);
3598
3711
  const keptPrivate = mergeKeptPrivate(built.map((b) => b.keptPrivate));
3599
3712
  const ctx = {
@@ -3601,7 +3714,8 @@ async function stageSync(deps) {
3601
3714
  keptPrivate,
3602
3715
  config,
3603
3716
  source,
3604
- baseUrl: deps.baseUrl
3717
+ baseUrl: deps.baseUrl,
3718
+ scanStats
3605
3719
  };
3606
3720
  let blockedReason = null;
3607
3721
  if (built.length === 0) {
@@ -4072,7 +4186,7 @@ function runStdioSyncServer(deps) {
4072
4186
 
4073
4187
  // src/index.ts
4074
4188
  var program = new Command();
4075
- program.name("aistack").description("Measure and share your AI stack from your terminal").version("0.5.0");
4189
+ program.name("aistack").description("Measure and share your AI stack from your terminal").version(CLI_VERSION);
4076
4190
  program.command("login").description("Authenticate with AI Stack").action(loginCommand);
4077
4191
  program.command("collect").description("Scan and upload AI config files from your project").option("--no-global", "Exclude global config files (~/.claude, etc.)").action((options) => collectCommand({ global: options.global ?? true }));
4078
4192
  program.command("create").description("Download and write your stack's AI config files").action(createCommand);