@use-aistack/cli 0.7.0 → 0.7.2

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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Measure and share your AI stack. The CLI scans your local Claude Code transcripts, shows you exactly what would go up, and publishes only after you approve in your terminal.
4
4
 
5
- Run on-demand with `npx` no install required:
5
+ Run on-demand with `npx` - no install required:
6
6
 
7
7
  ```sh
8
8
  npx @use-aistack/cli sync
@@ -12,17 +12,17 @@ npx @use-aistack/cli sync
12
12
 
13
13
  ### `npx @use-aistack/cli sync`
14
14
 
15
- The default command. Scan your local transcripts (rolling 30 days), review the full summary, and publish the measured layer to your stack. Nothing leaves your machine without your explicit approval at the prompt. Raw transcripts, prompts, and paths never leave at all only aggregates.
15
+ The default command. Scan your local transcripts (rolling 30 days), review the full summary, and publish the measured layer to your stack. Nothing leaves your machine without your explicit approval at the prompt. Raw transcripts, prompts, and paths never leave at all - only aggregates.
16
16
 
17
17
  ```sh
18
18
  npx @use-aistack/cli sync
19
19
  ```
20
20
 
21
- On an unlinked machine, `sync` starts the login flow inline your browser opens to approve the machine, then the sync continues. One command is the whole onboarding.
21
+ On an unlinked machine, `sync` starts the login flow inline - your browser opens to approve the machine, then the sync continues. One command is the whole onboarding.
22
22
 
23
23
  ### `npx @use-aistack/cli sync --auto on` / `off`
24
24
 
25
- Optional: keep your stack fresh without manual syncs. `on` asks your stack for the permission, then writes a `SessionStart` hook into the harnesses you actually use `~/.claude/settings.json` for Claude Code, `~/.codex/hooks.json` for Codex. The hook runs a silent sync at most once per day when a session starts. `off` removes the hooks and takes the permission back.
25
+ Optional: keep your stack fresh without manual syncs. `on` asks your stack for the permission, then writes a `SessionStart` hook into the harnesses you actually use - `~/.claude/settings.json` for Claude Code, `~/.codex/hooks.json` for Codex. The hook runs a silent sync at most once per day when a session starts. `off` removes the hooks and takes the permission back.
26
26
 
27
27
  ```sh
28
28
  npx @use-aistack/cli sync --auto on # enable, default every 24h
@@ -30,13 +30,13 @@ npx @use-aistack/cli sync --auto on --every 12 # custom frequency in hours
30
30
  npx @use-aistack/cli sync --auto off # revoke
31
31
  ```
32
32
 
33
- **Your stack owns the permission, not this machine.** The silent run asks aistack.to before it publishes anything, so the switch on your stack page is a complete revoke: it stops every machine, even one whose hooks are still installed. It works the other way too turn the switch on, run one `sync`, and that machine installs its own triggers. A harness you adopt months later gets its trigger the same way.
33
+ **Your stack owns the permission, not this machine.** The silent run asks aistack.to before it publishes anything, so the switch on your stack page is a complete revoke: it stops every machine, even one whose hooks are still installed. It works the other way too - turn the switch on, run one `sync`, and that machine installs its own triggers. A harness you adopt months later gets its trigger the same way.
34
34
 
35
35
  The silent run (`sync --auto`) never prompts and never installs a hook. Each run appends one line to `~/.config/aistack/sync.log` (capped at 200 lines). The next interactive `sync` reports the last result. After 3 failures in a row, one visible message appears in Claude Code and names the fix. No email, no dialogs.
36
36
 
37
37
  ### `npx @use-aistack/cli login`
38
38
 
39
- Link this machine to your AI Stack account via browser. Optional `sync` runs this inline when the machine is not linked yet.
39
+ Link this machine to your AI Stack account via browser. Optional - `sync` runs this inline when the machine is not linked yet.
40
40
 
41
41
  ```sh
42
42
  npx @use-aistack/cli login
@@ -44,7 +44,7 @@ npx @use-aistack/cli login
44
44
 
45
45
  ### `npx @use-aistack/cli connect claude`
46
46
 
47
- Optional: install the in-session sync surface for Claude Code the `aistack` MCP server (user scope) plus the `aistack-sync` Skill. After that, say "sync my stack" in any Claude Code session. Both halves install together or not at all.
47
+ Optional: install the in-session sync surface for Claude Code - the `aistack` MCP server (user scope) plus the `aistack-sync` Skill. After that, say "sync my stack" in any Claude Code session. Both halves install together or not at all.
48
48
 
49
49
  ```sh
50
50
  npx @use-aistack/cli connect claude
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@
4
4
  import { Command } from "commander";
5
5
 
6
6
  // src/version.ts
7
- var CLI_VERSION = true ? "0.7.0" : "0.0.0-dev";
7
+ var CLI_VERSION = true ? "0.7.2" : "0.0.0-dev";
8
8
 
9
9
  // src/api.ts
10
10
  var BASE_URL = process.env.AISTACK_URL || "https://aistack.to";
@@ -76,11 +76,11 @@ async function formatHttpError(res, label) {
76
76
  try {
77
77
  const body = JSON.parse(text);
78
78
  const detail = body.error || body.message;
79
- if (detail) return `${prefix} \u2014 ${detail}`;
79
+ if (detail) return `${prefix} - ${detail}`;
80
80
  } catch {
81
81
  }
82
82
  const snippet = text.trim().slice(0, 500);
83
- return snippet ? `${prefix} \u2014 ${snippet}` : prefix;
83
+ return snippet ? `${prefix} - ${snippet}` : prefix;
84
84
  }
85
85
  async function syncPublish(token, bodyJson) {
86
86
  const res = await request("/api/cli/sync", {
@@ -768,7 +768,7 @@ var LOCAL_PATTERNS = [
768
768
  { path: ".clinerules", type: "rule", group: "cline" },
769
769
  { path: ".roorules", type: "rule", group: "roo" },
770
770
  { path: ".github/copilot-instructions.md", type: "rule", group: "copilot" },
771
- // MCP servers are detected separately as pkg-reference links (see mcp.ts)
771
+ // MCP servers are detected separately as pkg-reference links (see mcp.ts) -
772
772
  // their config files are intentionally NOT collected as content here (which
773
773
  // would also upload `env` secrets).
774
774
  // Config
@@ -1425,11 +1425,11 @@ var PRICES = {
1425
1425
  ]),
1426
1426
  "claude-sonnet-4-6": anthropic(flat(3, 15)),
1427
1427
  "claude-haiku-4-5": anthropic(flat(1, 5)),
1428
- // Fast mode (research preview) Claude API only, Opus 5 / Opus 4.8 only.
1428
+ // Fast mode (research preview) - Claude API only, Opus 5 / Opus 4.8 only.
1429
1429
  // Opus 4.7 fast mode was removed, so there is deliberately no 4-7 entry.
1430
1430
  "claude-opus-5#fast": anthropic(flat(10, 50)),
1431
1431
  "claude-opus-4-8#fast": anthropic(flat(10, 50)),
1432
- // OpenAI (Codex) standard-context tier (<272K; observed context window is
1432
+ // OpenAI (Codex) - standard-context tier (<272K; observed context window is
1433
1433
  // 258,400).
1434
1434
  "gpt-5.5": openai(flat(5, 30)),
1435
1435
  "gpt-5.4": openai(flat(2.5, 15)),
@@ -1437,21 +1437,21 @@ var PRICES = {
1437
1437
  "gpt-5.3-codex": openai(flat(1.75, 14)),
1438
1438
  // The gpt-5.6 family launched 2026-07-29; Terra and Luna were repriced on
1439
1439
  // 2026-07-30 (-20% / -80%). The one-day launch rates are not on the list
1440
- // page and are NOT encoded a July-29 Terra/Luna record underprices for
1440
+ // page and are NOT encoded - a July-29 Terra/Luna record underprices for
1441
1441
  // one day rather than carrying a rate we cannot cite (#72).
1442
1442
  "gpt-5.6-sol": openai(flat(5, 30)),
1443
1443
  "gpt-5.6-terra": openai(flat(2, 12)),
1444
1444
  "gpt-5.6-luna": openai(flat(0.2, 1.2)),
1445
- // NOT on OpenAI's list page an internal Codex routing label with no
1445
+ // NOT on OpenAI's list page - an internal Codex routing label with no
1446
1446
  // official price (openai/codex#20981). Rate is the aggregator consensus
1447
1447
  // ($2.50 / $15.00), scoped in explicitly by ticket #72 because it carries
1448
1448
  // real token volume in Codex rollouts.
1449
1449
  "codex-auto-review": openai(flat(2.5, 15)),
1450
- // Google (opencode, pi-mono) Standard tier. Where a model is
1450
+ // Google (opencode, pi-mono) - Standard tier. Where a model is
1451
1451
  // context-tiered, the <=200K rate is encoded, exactly as the OpenAI rows
1452
1452
  // encode the standard-context tier: the payload carries no per-response
1453
1453
  // context length, so the cheaper side keeps the figure a lower bound. Only
1454
- // the Pro and Flash families are here image, TTS, embedding, Live and
1454
+ // the Pro and Flash families are here - image, TTS, embedding, Live and
1455
1455
  // robotics models are not what a coding harness selects.
1456
1456
  "gemini-3.1-pro-preview": google(flat(2, 12)),
1457
1457
  "gemini-3.6-flash": google(flat(1.5, 7.5)),
@@ -1903,7 +1903,7 @@ var BUNDLED_SYNC_CONFIG = {
1903
1903
  // upload the names it is holding back. The default is ON server-side, so this
1904
1904
  // costs the owner one retry and never costs them a name.
1905
1905
  reviewKeptPrivate: false,
1906
- // No fetch, no destination and the gate refuses to publish without one.
1906
+ // No fetch, no destination - and the gate refuses to publish without one.
1907
1907
  stack: null,
1908
1908
  // No fetch, no permission either. This costs nothing on its own: `stack` is
1909
1909
  // null in the same breath, so the stage blocks before any publish.
@@ -1974,7 +1974,7 @@ function readSyncConfig(raw) {
1974
1974
  },
1975
1975
  // Anything other than an explicit `true` fails closed.
1976
1976
  publishCost: obj.publishCost === true,
1977
- // Absent means "no stack resolved" an anonymous fetch, or a token bound
1977
+ // Absent means "no stack resolved" - an anonymous fetch, or a token bound
1978
1978
  // to nothing. Both fail closed to publishing no user-chosen names.
1979
1979
  optIns: readOptIns(obj.optIns),
1980
1980
  // Anything other than an explicit `true` keeps the names on the machine.
@@ -3369,9 +3369,9 @@ function readCounts2(usageRaw) {
3369
3369
  const split = typeof u.cacheWrite1h === "number" && Number.isFinite(u.cacheWrite1h);
3370
3370
  const cacheWrite1h = split ? Math.min(Math.max(u.cacheWrite1h, 0), cacheWrite) : 0;
3371
3371
  return {
3372
- // Already exclusive of cache traffic no subtraction (research §2).
3372
+ // Already exclusive of cache traffic - no subtraction (research §2).
3373
3373
  input: asNum(u.input),
3374
- // `reasoning` is a subset of `output` never added.
3374
+ // `reasoning` is a subset of `output` - never added.
3375
3375
  output: asNum(u.output),
3376
3376
  cacheWrite5m: split ? cacheWrite - cacheWrite1h : 0,
3377
3377
  cacheWrite1h,
@@ -3784,7 +3784,7 @@ function harnessLabel(name) {
3784
3784
  if (name === CLAUDE_HARNESS_NAME) return "Claude Code";
3785
3785
  if (name === CODEX_HARNESS_NAME) return "Codex";
3786
3786
  if (name === OPENCODE_HARNESS_NAME) return "opencode";
3787
- if (name === PI_HARNESS_NAME) return "pi-mono";
3787
+ if (name === PI_HARNESS_NAME) return "Pi";
3788
3788
  return name;
3789
3789
  }
3790
3790
  function harnessListLabel(adapters) {
@@ -3845,14 +3845,14 @@ function installClaudeConnect(run = runClaude, copySkill = (src, dest) => cpSync
3845
3845
  if (source === null) {
3846
3846
  return {
3847
3847
  ok: false,
3848
- message: "this install is missing its bundled Skill (skills/aistack-sync) \u2014 nothing was installed"
3848
+ message: "this install is missing its bundled Skill (skills/aistack-sync) - nothing was installed"
3849
3849
  };
3850
3850
  }
3851
3851
  const add = run(MCP_ADD_ARGS);
3852
3852
  if (add.notFound) {
3853
3853
  return {
3854
3854
  ok: false,
3855
- message: `claude was not found on PATH \u2014 nothing was installed. Manual install:
3855
+ message: `claude was not found on PATH - nothing was installed. Manual install:
3856
3856
  ${MANUAL_MCP_ADD}`
3857
3857
  };
3858
3858
  }
@@ -3860,7 +3860,7 @@ ${MANUAL_MCP_ADD}`
3860
3860
  if (add.status !== 0 && !alreadyRegistered) {
3861
3861
  return {
3862
3862
  ok: false,
3863
- message: `claude mcp add failed \u2014 nothing was installed.
3863
+ message: `claude mcp add failed - nothing was installed.
3864
3864
  ${add.output.trim()}`
3865
3865
  };
3866
3866
  }
@@ -3870,7 +3870,7 @@ ${add.output.trim()}`
3870
3870
  if (!alreadyRegistered) run(MCP_REMOVE_ARGS);
3871
3871
  return {
3872
3872
  ok: false,
3873
- message: `copying the Skill to ${SKILL_DEST} failed \u2014 the MCP registration was ${alreadyRegistered ? "left as it was" : "rolled back"}.
3873
+ message: `copying the Skill to ${SKILL_DEST} failed - the MCP registration was ${alreadyRegistered ? "left as it was" : "rolled back"}.
3874
3874
  ${e instanceof Error ? e.message : String(e)}`
3875
3875
  };
3876
3876
  }
@@ -3882,7 +3882,7 @@ ${e instanceof Error ? e.message : String(e)}`
3882
3882
  async function connectCommand(harness) {
3883
3883
  intro2("connect");
3884
3884
  if (harness !== "claude") {
3885
- outroError(`unknown harness "${harness}" \u2014 supported: claude`);
3885
+ outroError(`unknown harness "${harness}" - supported: claude`);
3886
3886
  process.exitCode = 1;
3887
3887
  return;
3888
3888
  }
@@ -4147,10 +4147,10 @@ function readHooksJson(file) {
4147
4147
  }
4148
4148
  return { error: `${file} does not hold a JSON object` };
4149
4149
  } catch {
4150
- return { error: `${file} is not valid JSON \u2014 fix it, then retry` };
4150
+ return { error: `${file} is not valid JSON - fix it, then retry` };
4151
4151
  }
4152
4152
  }
4153
- var CODEX_TRUST_INSTRUCTION = "Codex hook written \u2014 open Codex and run /hooks once to trust it, or it will not run.";
4153
+ var CODEX_TRUST_INSTRUCTION = "Codex hook written - open Codex and run /hooks once to trust it, or it will not run.";
4154
4154
  function installCodexAutoSyncHook(file = codexHooksFile()) {
4155
4155
  const read = readHooksJson(file);
4156
4156
  if ("error" in read) return { ok: false, message: read.error };
@@ -4239,7 +4239,7 @@ function readClaudeSettings(file) {
4239
4239
  }
4240
4240
  return { error: `${file} does not hold a JSON object` };
4241
4241
  } catch {
4242
- return { error: `${file} is not valid JSON \u2014 fix it, then retry` };
4242
+ return { error: `${file} is not valid JSON - fix it, then retry` };
4243
4243
  }
4244
4244
  }
4245
4245
  function installAutoSyncHook(file = CLAUDE_SETTINGS_FILE) {
@@ -4339,7 +4339,7 @@ async function enableAutoSync(frequencyHours = DEFAULT_FREQUENCY_HOURS, deps = {
4339
4339
  return {
4340
4340
  ok: true,
4341
4341
  message: [
4342
- `Auto-sync is on \u2014 about every ${frequencyHours}h when a ${harnessListLabel(detected)} session starts. Turn it off any time: npx @use-aistack/cli sync --auto off`,
4342
+ `Auto-sync is on. It runs about every ${frequencyHours}h when a ${harnessListLabel(detected)} session starts. Turn it off any time: npx @use-aistack/cli sync --auto off`,
4343
4343
  ...trustLine ? [trustLine] : []
4344
4344
  ].join("\n")
4345
4345
  };
@@ -4365,7 +4365,7 @@ async function disableAutoSync(deps = {}) {
4365
4365
  await (deps.setAutoSyncImpl ?? setAutoSync)(token, { enabled: false });
4366
4366
  } catch (e) {
4367
4367
  failures.push(
4368
- `aistack.to was not told (${e instanceof Error ? e.message : String(e)}) \u2014 your other machines keep the permission`
4368
+ `aistack.to was not told (${e instanceof Error ? e.message : String(e)}); your other machines keep the permission`
4369
4369
  );
4370
4370
  }
4371
4371
  }
@@ -4421,7 +4421,7 @@ async function reconcileAutoSync(permission, deps = {}) {
4421
4421
  if (installed.length === 0 && mirrored) return null;
4422
4422
  return {
4423
4423
  ok: true,
4424
- message: installed.length > 0 ? `Auto-sync is on for this stack \u2014 installed the ${installed.join(" and ")} trigger on this machine. About every ${frequencyHours}h when a session starts.` : `Auto-sync is on for this stack \u2014 about every ${frequencyHours}h when a ${harnessListLabel(detected)} session starts.`
4424
+ message: installed.length > 0 ? `Auto-sync is on for this stack. Installed the ${installed.join(" and ")} trigger on this machine; it runs about every ${frequencyHours}h when a session starts.` : `Auto-sync is on for this stack. It runs about every ${frequencyHours}h when a ${harnessListLabel(detected)} session starts.`
4425
4425
  };
4426
4426
  }
4427
4427
  async function settleAutoSync(permission, deps = {}) {
@@ -4504,6 +4504,9 @@ function fmtUSD(n) {
4504
4504
  return `\u2248$${Math.round(n).toLocaleString("en-US")}`;
4505
4505
  }
4506
4506
  var fmtPct = (share) => `${(share * 100).toFixed(1)}%`;
4507
+ function fmtReceivedAt(ms) {
4508
+ return `${new Date(ms).toISOString().slice(0, 16).replace("T", " ")} UTC`;
4509
+ }
4507
4510
  function totalUSD(payload) {
4508
4511
  let sum = 0;
4509
4512
  let any = false;
@@ -4581,18 +4584,16 @@ function scanNoteLines(stats, label) {
4581
4584
  if (stats.filesForeign > 0) {
4582
4585
  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(", ");
4583
4586
  out.push(
4584
- `skipped ${stats.filesForeign} file${stats.filesForeign === 1 ? "" : "s"} not written by ${label} \u2014 left out (originators: ${origins})`
4587
+ `skipped ${stats.filesForeign} file${stats.filesForeign === 1 ? "" : "s"} not written by ${label} (originators: ${origins})`
4585
4588
  );
4586
4589
  }
4587
4590
  return out;
4588
4591
  }
4589
- function payloadBlock(payload, showHeader, stats) {
4592
+ function payloadBlock(payload, stats) {
4590
4593
  const out = [];
4591
- if (showHeader) {
4592
- out.push(
4593
- `\u2014 ${harnessLabel(payload.harness.name)}${payload.harness.version ? ` ${payload.harness.version}` : ""}`
4594
- );
4595
- }
4594
+ out.push(
4595
+ `- ${harnessLabel(payload.harness.name)}${payload.harness.version ? ` ${payload.harness.version}` : ""}`
4596
+ );
4596
4597
  out.push(
4597
4598
  `window ${payload.window.days} days \xB7 ${payload.window.from} \u2192 ${payload.window.to}`
4598
4599
  );
@@ -4606,7 +4607,7 @@ function payloadBlock(payload, showHeader, stats) {
4606
4607
  const cov = payload.coverage;
4607
4608
  if (cov.filesUnreadable > 0 || cov.linesFailed > 0) {
4608
4609
  out.push(
4609
- `coverage ${cov.filesUnreadable} files unreadable \xB7 ${cov.linesFailed} lines failed \u2014 this reading is a floor`
4610
+ `coverage ${cov.filesUnreadable} files unreadable \xB7 ${cov.linesFailed} lines failed \xB7 this reading is a floor`
4610
4611
  );
4611
4612
  }
4612
4613
  if (stats) {
@@ -4633,19 +4634,22 @@ function buildGateSummary(ctx) {
4633
4634
  const { payloads } = body;
4634
4635
  const host = baseUrl.replace(/^https?:\/\//, "");
4635
4636
  const out = [];
4636
- out.push("from your machine \u2014 sync preview");
4637
+ out.push("from your machine \xB7 sync preview");
4637
4638
  out.push("");
4638
4639
  if (config.stack === null) {
4639
- out.push("to (no linked stack \u2014 publish is unavailable)");
4640
+ out.push("to (no linked stack; publish is unavailable)");
4640
4641
  } else {
4641
4642
  out.push(
4642
4643
  `to ${config.stack.name} \xB7 ${host}/stacks/${config.stack.slug}`
4643
4644
  );
4644
4645
  }
4646
+ out.push(
4647
+ `searched ${HARNESS_ADAPTERS.map((a) => harnessLabel(a.name).toLowerCase()).join(", ")}`
4648
+ );
4645
4649
  for (const payload of payloads) {
4646
4650
  const stats = ctx.scanStats?.[payload.harness.name];
4647
- out.push(...payloadBlock(payload, payloads.length > 1, stats));
4648
4651
  out.push("");
4652
+ out.push(...payloadBlock(payload, stats));
4649
4653
  }
4650
4654
  if (out[out.length - 1] === "") out.pop();
4651
4655
  const n = payloads.reduce((a, p8) => a + withheldCount(p8), 0);
@@ -4664,7 +4668,7 @@ function buildGateSummary(ctx) {
4664
4668
  if (body.keptPrivate !== void 0 && config.stack !== null) {
4665
4669
  out.push(` publish them at ${host}/stacks/${config.stack.slug}/changes`);
4666
4670
  out.push(
4667
- " (they go up for you to review \u2014 turn off: Review kept-private names, on your stack)"
4671
+ " (they go up for you to review - turn off: Review kept-private names, on your stack)"
4668
4672
  );
4669
4673
  } else {
4670
4674
  out.push(" they stay on this machine");
@@ -4679,7 +4683,7 @@ function buildGateSummary(ctx) {
4679
4683
  if (source === "bundled") {
4680
4684
  out.push("");
4681
4685
  out.push(
4682
- "! could not fetch your settings from aistack \u2014 using the bundled list."
4686
+ "! could not fetch your settings from aistack - using the bundled list."
4683
4687
  );
4684
4688
  out.push(
4685
4689
  " This publishes less: no cost, no ticked names, nothing staged for review."
@@ -4734,7 +4738,7 @@ async function stageSync(deps) {
4734
4738
  };
4735
4739
  let blockedReason = null;
4736
4740
  if (built.length === 0) {
4737
- blockedReason = `No active harness on this machine \u2014 no Claude Code and no Codex transcript from the last ${windowDays} days to read.`;
4741
+ blockedReason = `No active harness on this machine - no Claude Code and no Codex transcript from the last ${windowDays} days to read.`;
4738
4742
  } else if (token === null) {
4739
4743
  blockedReason = "This machine is not linked. Run `npx @use-aistack/cli login` first.";
4740
4744
  } else if (config.stack === null) {
@@ -4758,7 +4762,7 @@ async function stageSync(deps) {
4758
4762
  var SYNC_LOG_FILE = join10(homedir13(), ".config", "aistack", "sync.log");
4759
4763
  var SYNC_LOG_MAX_LINES = 200;
4760
4764
  var FIX_COMMAND = "npx @use-aistack/cli sync";
4761
- var REVOKED_RESULT = "off \u2014 auto-sync is switched off for this stack";
4765
+ var REVOKED_RESULT = "off - auto-sync is switched off for this stack";
4762
4766
  function appendLogLine(file, line) {
4763
4767
  mkdirSync5(dirname7(file), { recursive: true });
4764
4768
  appendFileSync(file, `${line}
@@ -4779,7 +4783,7 @@ async function runAutoSync(deps) {
4779
4783
  const settings = getSettings(settingsFile);
4780
4784
  const config = settings.autoSync;
4781
4785
  if (config?.enabled !== true) {
4782
- appendLogLine(logFile, `${stamp} skipped \u2014 auto-sync is not enabled`);
4786
+ appendLogLine(logFile, `${stamp} skipped - auto-sync is not enabled`);
4783
4787
  return;
4784
4788
  }
4785
4789
  const frequencyHours = config.frequencyHours || DEFAULT_FREQUENCY_HOURS;
@@ -4827,7 +4831,7 @@ async function runAutoSync(deps) {
4827
4831
  },
4828
4832
  settingsFile
4829
4833
  );
4830
- appendLogLine(logFile, `${stamp} skipped \u2014 ${REVOKED_RESULT}`);
4834
+ appendLogLine(logFile, `${stamp} skipped - ${REVOKED_RESULT}`);
4831
4835
  return;
4832
4836
  }
4833
4837
  if (failure2 === null) {
@@ -4836,14 +4840,14 @@ async function runAutoSync(deps) {
4836
4840
  autoSyncState: {
4837
4841
  lastRunAt: now,
4838
4842
  lastSuccessAt: now,
4839
- lastResult: `ok \u2014 published at ${stamp}`,
4843
+ lastResult: `ok - published at ${stamp}`,
4840
4844
  consecutiveFailures: 0,
4841
4845
  failureWarned: false
4842
4846
  }
4843
4847
  },
4844
4848
  settingsFile
4845
4849
  );
4846
- appendLogLine(logFile, `${stamp} ok \u2014 published${url ? ` ${url}` : ""}`);
4850
+ appendLogLine(logFile, `${stamp} ok - published${url ? ` ${url}` : ""}`);
4847
4851
  return;
4848
4852
  }
4849
4853
  const consecutiveFailures = (state.consecutiveFailures ?? 0) + 1;
@@ -4853,7 +4857,7 @@ async function runAutoSync(deps) {
4853
4857
  autoSyncState: {
4854
4858
  ...state,
4855
4859
  lastRunAt: now,
4856
- lastResult: `failed at ${stamp} \u2014 ${failure2}`,
4860
+ lastResult: `failed at ${stamp} - ${failure2}`,
4857
4861
  consecutiveFailures,
4858
4862
  failureWarned: state.failureWarned === true || shouldWarn
4859
4863
  }
@@ -4862,7 +4866,7 @@ async function runAutoSync(deps) {
4862
4866
  );
4863
4867
  appendLogLine(
4864
4868
  logFile,
4865
- `${stamp} fail (${consecutiveFailures} in a row) \u2014 ${failure2}`
4869
+ `${stamp} fail (${consecutiveFailures} in a row) - ${failure2}`
4866
4870
  );
4867
4871
  if (shouldWarn) {
4868
4872
  emit(
@@ -4895,7 +4899,7 @@ async function syncCommand(options = {}) {
4895
4899
  }
4896
4900
  if (options.auto !== void 0) {
4897
4901
  intro2("sync");
4898
- outroError(`unknown --auto value "${options.auto}" \u2014 use on or off`);
4902
+ outroError(`unknown --auto value "${options.auto}" (use on or off)`);
4899
4903
  process.exitCode = 1;
4900
4904
  return;
4901
4905
  }
@@ -4908,16 +4912,16 @@ async function syncCommand(options = {}) {
4908
4912
  p7.log.warn(CODEX_TRUST_INSTRUCTION);
4909
4913
  }
4910
4914
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
4911
- outroError("sync needs an interactive terminal \u2014 nothing was sent");
4915
+ outroError("sync needs an interactive terminal. Nothing was sent.");
4912
4916
  process.exitCode = 1;
4913
4917
  return;
4914
4918
  }
4915
4919
  if (getToken() === null) {
4916
4920
  p7.log.message(
4917
- "This machine is not linked to an aistack account yet \u2014 linking it first."
4921
+ "This machine is not linked to an aistack account yet. Linking it now."
4918
4922
  );
4919
4923
  if (!await performLogin()) {
4920
- outroError("login failed \u2014 nothing was sent");
4924
+ outroError("login failed. Nothing was sent.");
4921
4925
  process.exitCode = 1;
4922
4926
  return;
4923
4927
  }
@@ -4957,12 +4961,14 @@ async function syncCommand(options = {}) {
4957
4961
  const res = await syncPublish(staged.token, staged.bodyJson);
4958
4962
  s.stop("Published");
4959
4963
  const lines2 = [
4960
- `Snapshot received at ${new Date(res.receivedAt).toISOString()}`,
4964
+ `Snapshot received ${fmtReceivedAt(res.receivedAt)}`,
4965
+ "",
4966
+ "Your stack now shows what actually ran:",
4961
4967
  lime(res.url)
4962
4968
  ];
4963
4969
  if (res.keptPrivate.refused && staged.body.keptPrivate !== void 0) {
4964
4970
  lines2.push(
4965
- "Note: the kept-private names were refused by the server \u2014 the review switch is off there now. They stayed on this machine."
4971
+ "Note: the server refused the kept-private names because its review switch is off. They stayed on this machine."
4966
4972
  );
4967
4973
  } else if (res.keptPrivate.stored > 0) {
4968
4974
  lines2.push(
@@ -4987,10 +4993,10 @@ var STAGE_TTL_MS = 10 * 60 * 1e3;
4987
4993
  var ELICIT_TIMEOUT_MS = 10 * 60 * 1e3;
4988
4994
  var PREVIEW_TOOL = {
4989
4995
  name: "sync_preview",
4990
- description: "Scan local agent transcripts (Claude Code, Codex) and stage a measured-usage snapshot for aistack. Returns the full preview of exactly what would publish. Show the returned text to the user VERBATIM \u2014 it is the review surface. Nothing is sent.",
4996
+ description: "Scan local agent transcripts (Claude Code, Codex) and stage a measured-usage snapshot for aistack. Returns the full preview of exactly what would publish. Show the returned text to the user VERBATIM - it is the review surface. Nothing is sent.",
4991
4997
  inputSchema: { type: "object", properties: {} },
4992
4998
  annotations: {
4993
- title: "aistack \u2014 preview sync (sends nothing)",
4999
+ title: "aistack - preview sync (sends nothing)",
4994
5000
  readOnlyHint: true,
4995
5001
  openWorldHint: true
4996
5002
  }
@@ -5009,7 +5015,7 @@ var PUBLISH_TOOL = {
5009
5015
  required: ["preview_id"]
5010
5016
  },
5011
5017
  annotations: {
5012
- title: "aistack \u2014 publish measured usage (asks the user first)",
5018
+ title: "aistack - publish measured usage (asks the user first)",
5013
5019
  destructiveHint: false,
5014
5020
  openWorldHint: true
5015
5021
  }
@@ -5064,7 +5070,7 @@ function createSyncServer(deps, send) {
5064
5070
  return ok(
5065
5071
  id,
5066
5072
  textResult(
5067
- "Not published: this Claude Code version did not declare the elicitation capability, so the approve dialog cannot be shown. The gate never degrades silently \u2014 update Claude Code and try again.",
5073
+ "Not published: this Claude Code version did not declare the elicitation capability, so the approve dialog cannot be shown. The gate never degrades silently - update Claude Code and try again.",
5068
5074
  true
5069
5075
  )
5070
5076
  );
@@ -5138,13 +5144,15 @@ function createSyncServer(deps, send) {
5138
5144
  (res) => {
5139
5145
  if (staged?.id === approvedStage.id) staged = null;
5140
5146
  const lines2 = [
5141
- `Published. Snapshot received at ${new Date(res.receivedAt).toISOString()}.`,
5147
+ `Published. Snapshot received ${fmtReceivedAt(res.receivedAt)}.`,
5148
+ "",
5149
+ "Your stack now shows what actually ran:",
5142
5150
  res.url
5143
5151
  ];
5144
5152
  const kp = approvedStage.body.keptPrivate;
5145
5153
  if (res.keptPrivate.refused && kp !== void 0) {
5146
5154
  lines2.push(
5147
- "Note: the kept-private names were refused by the server \u2014 the review switch is off there now. They stayed on this machine."
5155
+ "Note: the server refused the kept-private names because its review switch is off. They stayed on this machine."
5148
5156
  );
5149
5157
  } else if (res.keptPrivate.stored > 0) {
5150
5158
  lines2.push(