standout 0.5.27 → 0.5.29

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.
@@ -99,7 +99,7 @@ export type RawStats = {
99
99
  sessions: Map<string, {
100
100
  firstTs: number;
101
101
  lastTs: number;
102
- eventTimestamps: number[];
102
+ eventTimestamps: Set<number>;
103
103
  cwd: string | null;
104
104
  models: Set<string>;
105
105
  modelTurnCounts: Map<string, number>;
package/dist/ai-usage.js CHANGED
@@ -305,13 +305,14 @@ function registerFile(raw, filePath) {
305
305
  raw.filePaths.add(filePath);
306
306
  }
307
307
  function recordSessionMeta(raw, sessionId, ts, cwd, model, version) {
308
+ const minute = Math.floor(ts / 60_000);
308
309
  const existing = raw.sessions.get(sessionId);
309
310
  if (existing) {
310
311
  if (ts < existing.firstTs)
311
312
  existing.firstTs = ts;
312
313
  if (ts > existing.lastTs)
313
314
  existing.lastTs = ts;
314
- existing.eventTimestamps.push(ts);
315
+ existing.eventTimestamps.add(minute);
315
316
  if (!existing.cwd && cwd)
316
317
  existing.cwd = cwd;
317
318
  if (model)
@@ -323,7 +324,7 @@ function recordSessionMeta(raw, sessionId, ts, cwd, model, version) {
323
324
  raw.sessions.set(sessionId, {
324
325
  firstTs: ts,
325
326
  lastTs: ts,
326
- eventTimestamps: [ts],
327
+ eventTimestamps: new Set([minute]),
327
328
  cwd,
328
329
  models: model ? new Set([model]) : new Set(),
329
330
  modelTurnCounts: new Map(),
@@ -804,10 +805,11 @@ export function computeStreaks(activeDays) {
804
805
  current = 0;
805
806
  return { longest, current };
806
807
  }
807
- function estimateActiveDurationMs(timestamps) {
808
- const sorted = [...new Set(timestamps)]
809
- .filter((ts) => Number.isFinite(ts))
810
- .sort((a, b) => a - b);
808
+ function estimateActiveDurationMs(minuteBuckets) {
809
+ const sorted = [...new Set(minuteBuckets)]
810
+ .filter((m) => Number.isFinite(m))
811
+ .sort((a, b) => a - b)
812
+ .map((m) => m * 60_000);
811
813
  if (sorted.length === 0)
812
814
  return 0;
813
815
  if (sorted.length === 1)
@@ -840,10 +842,11 @@ function compactCwd(cwd) {
840
842
  }
841
843
  // Split a session's events into active segments (consecutive events <= 15min
842
844
  // apart), so concurrency reflects real work, not a tab idle for days.
843
- function activeSegments(timestamps) {
844
- const sorted = [...new Set(timestamps)]
845
- .filter((t) => Number.isFinite(t))
846
- .sort((a, b) => a - b);
845
+ function activeSegments(minuteBuckets) {
846
+ const sorted = [...new Set(minuteBuckets)]
847
+ .filter((m) => Number.isFinite(m))
848
+ .sort((a, b) => a - b)
849
+ .map((m) => m * 60_000);
847
850
  if (sorted.length === 0)
848
851
  return [];
849
852
  const maxGapMs = MAX_ACTIVE_GAP_MINUTES * 60 * 1000;
package/dist/cli.js CHANGED
@@ -4,7 +4,9 @@ import chalk from "chalk";
4
4
  import { createInterface } from "readline";
5
5
  import { markWrappedShared, STANDOUT_API_URL } from "./api.js";
6
6
  import { gatherFullEnrichment, gatherProfileBasics, gatherUsageStats, } from "./gather.js";
7
+ import { ensureHeapHeadroom } from "./heap.js";
7
8
  import { startMcpServer } from "./mcp.js";
9
+ import { configureProxyFromEnv } from "./proxy.js";
8
10
  import { SYSTEM_PROMPT } from "./prompt.js";
9
11
  import { TOOLS, executeTool } from "./tools.js";
10
12
  import { renderWrappedAll } from "./wrapped/render.js";
@@ -325,7 +327,13 @@ async function runAgent(jobId) {
325
327
  console.log(` Tokens: ${totalInputTokens.toLocaleString()} in / ${totalOutputTokens.toLocaleString()} out${cacheNote} in ${elapsed}s\n`);
326
328
  }
327
329
  async function main() {
330
+ ensureHeapHeadroom();
328
331
  const args = process.argv.slice(2);
332
+ // Route fetch through a configured proxy before any network call — Node's fetch
333
+ // ignores HTTP(S)_PROXY otherwise, which breaks users behind a corporate proxy.
334
+ const proxy = configureProxyFromEnv();
335
+ if (proxy)
336
+ process.stderr.write(` (using proxy ${proxy})\n`);
329
337
  if (args.includes("--gather")) {
330
338
  await confirmPrivacy();
331
339
  const data = await gatherFullEnrichment();
package/dist/heap.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function ensureHeapHeadroom(): void;
package/dist/heap.js ADDED
@@ -0,0 +1,25 @@
1
+ import { spawnSync } from "child_process";
2
+ import { totalmem } from "os";
3
+ // Node caps old-space at ~4GB by default regardless of physical RAM, so a big
4
+ // machine OOMs at the same point as a small one. On machines with headroom,
5
+ // re-exec once with a higher --max-old-space-size (half of RAM, capped at 8GB)
6
+ // so heavy log archives have room. The real bound is algorithmic (see
7
+ // ai-usage.ts); this is just a safety net.
8
+ export function ensureHeapHeadroom() {
9
+ if (process.env.STANDOUT_HEAP_BOOSTED === "1")
10
+ return;
11
+ if (process.execArgv.some((a) => a.startsWith("--max-old-space-size")))
12
+ return;
13
+ const totalMb = Math.floor(totalmem() / (1024 * 1024));
14
+ const desiredMb = Math.min(8192, Math.floor(totalMb / 2));
15
+ if (desiredMb <= 4096)
16
+ return;
17
+ const entry = process.argv[1];
18
+ if (!entry)
19
+ return;
20
+ const res = spawnSync(process.execPath, [`--max-old-space-size=${desiredMb}`, entry, ...process.argv.slice(2)], {
21
+ stdio: "inherit",
22
+ env: { ...process.env, STANDOUT_HEAP_BOOSTED: "1" },
23
+ });
24
+ process.exit(res.status ?? 0);
25
+ }
@@ -0,0 +1 @@
1
+ export declare function configureProxyFromEnv(): string | null;
package/dist/proxy.js ADDED
@@ -0,0 +1,27 @@
1
+ import { ProxyAgent, setGlobalDispatcher } from "undici";
2
+ // Node's built-in fetch (undici) ignores HTTP(S)_PROXY env vars by default. On a
3
+ // corporate network that forces traffic through a proxy, undici then connects
4
+ // directly, the proxy/firewall transparently intercepts the TLS handshake, and the
5
+ // client sees a mangled stream (ERR_SSL_PACKET_LENGTH_TOO_LONG / "packet length too
6
+ // long"). Routing fetch through the configured proxy fixes that.
7
+ //
8
+ // Honors the standard env vars (upper + lower case). NO_PROXY is intentionally not
9
+ // handled — we only ever talk to one host (the Standout API), so a global proxy is
10
+ // correct when one is set.
11
+ export function configureProxyFromEnv() {
12
+ const proxy = process.env.HTTPS_PROXY ||
13
+ process.env.https_proxy ||
14
+ process.env.HTTP_PROXY ||
15
+ process.env.http_proxy ||
16
+ process.env.ALL_PROXY ||
17
+ process.env.all_proxy;
18
+ if (!proxy)
19
+ return null;
20
+ try {
21
+ setGlobalDispatcher(new ProxyAgent(proxy));
22
+ return proxy;
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ }
@@ -124,9 +124,21 @@ export async function createWrapped(args) {
124
124
  await sleep(RETRY_BASE_DELAY_MS * attempt);
125
125
  continue;
126
126
  }
127
- process.stderr.write(` Couldn't reach Standout to build your wrapped. Your network (or a VPN/proxy) dropped the connection.\n` +
128
- ` Check your connection and re-run: npx standout\n` +
129
- ` (details: ${describeError(err)}; body ${bodyKb}KB)\n`);
127
+ const detail = describeError(err);
128
+ // A TLS-layer failure means something is intercepting HTTPS (VPN, proxy, or
129
+ // antivirus), not a transient drop — give targeted guidance.
130
+ const tlsIntercept = /ERR_SSL|ERR_TLS|packet length too long|SSL routines|wrong version number|DECRYPTION|sslv3|unknown ca|self.signed|unable to (verify|get local)/i.test(detail);
131
+ if (tlsIntercept) {
132
+ process.stderr.write(` Couldn't reach Standout: a VPN, proxy, or antivirus is intercepting the HTTPS connection.\n` +
133
+ ` Try one of: disconnect the VPN/proxy, switch networks, or set HTTPS_PROXY to your\n` +
134
+ ` corporate proxy (and NODE_EXTRA_CA_CERTS if it uses a custom certificate), then re-run: npx standout\n` +
135
+ ` (details: ${detail}; body ${bodyKb}KB)\n`);
136
+ }
137
+ else {
138
+ process.stderr.write(` Couldn't reach Standout to build your wrapped. Your network (or a VPN/proxy) dropped the connection.\n` +
139
+ ` Check your connection and re-run: npx standout\n` +
140
+ ` (details: ${detail}; body ${bodyKb}KB)\n`);
141
+ }
130
142
  return null;
131
143
  }
132
144
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "standout",
3
- "version": "0.5.27",
3
+ "version": "0.5.29",
4
4
  "description": "Build your developer profile with AI. One command, zero friction.",
5
5
  "type": "module",
6
6
  "main": "./dist/cli.js",
@@ -32,6 +32,7 @@
32
32
  "gradient-string": "^3.0.0",
33
33
  "open": "^11.0.0",
34
34
  "playwright-core": "^1.59.1",
35
+ "undici": "^6.26.0",
35
36
  "zod": "^3.24.0"
36
37
  },
37
38
  "devDependencies": {