hillclimb 0.5.5 → 0.6.0

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.
Files changed (2) hide show
  1. package/dist/cli.js +79 -207
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -9,22 +9,6 @@ import * as p6 from "@clack/prompts";
9
9
  import path7 from "path";
10
10
  import * as p3 from "@clack/prompts";
11
11
 
12
- // src/color.ts
13
- var IS_TTY = Boolean(process.stdout.isTTY);
14
- var NO_COLOR = process.env.FORCE_COLOR !== void 0 ? false : process.env.NO_COLOR !== void 0 || process.env.TERM === "dumb" || !IS_TTY;
15
- function wrap(open, close) {
16
- if (NO_COLOR) return (s) => s;
17
- const o = `\x1B[${open}m`;
18
- const c = `\x1B[${close}m`;
19
- return (s) => `${o}${s}${c}`;
20
- }
21
- var bold = wrap(1, 22);
22
- var dim = wrap(2, 22);
23
- var red = wrap(31, 39);
24
- var green = wrap(32, 39);
25
- var yellow = wrap(33, 39);
26
- var cyan = wrap(36, 39);
27
-
28
12
  // src/config.ts
29
13
  import fs from "fs";
30
14
  import os from "os";
@@ -369,18 +353,18 @@ var PlatformClient = class {
369
353
  let code;
370
354
  let message = `${method} ${relative} failed: HTTP ${res.status}`;
371
355
  try {
372
- const text3 = await res.text();
373
- if (text3) {
356
+ const text2 = await res.text();
357
+ if (text2) {
374
358
  try {
375
- const parsed = JSON.parse(text3);
359
+ const parsed = JSON.parse(text2);
376
360
  if (parsed.error?.code) code = parsed.error.code;
377
361
  if (parsed.error?.message) {
378
362
  message = `${method} ${relative} failed: ${parsed.error.message}`;
379
363
  } else {
380
- message = `${method} ${relative} failed: ${text3}`;
364
+ message = `${method} ${relative} failed: ${text2}`;
381
365
  }
382
366
  } catch {
383
- message = `${method} ${relative} failed: ${text3}`;
367
+ message = `${method} ${relative} failed: ${text2}`;
384
368
  }
385
369
  }
386
370
  } catch {
@@ -397,17 +381,6 @@ var PlatformClient = class {
397
381
  }
398
382
  return void 0;
399
383
  }
400
- async signInWithEmail(email, password2) {
401
- await this.request("POST", "/api/auth/sign-in/email", {
402
- email,
403
- password: password2
404
- });
405
- if (!this.sessionCookie) {
406
- throw new PlatformError(
407
- "Sign-in succeeded but no session cookie was returned"
408
- );
409
- }
410
- }
411
384
  async getBootstrap() {
412
385
  const data = await this.request("GET", "/api/v1/app/bootstrap");
413
386
  if (!data?.session?.user || !data.session.session) return null;
@@ -1483,22 +1456,6 @@ var CLAUDE_DEF = TOOLS[0];
1483
1456
 
1484
1457
  // src/commands/_shared.ts
1485
1458
  import * as p from "@clack/prompts";
1486
- async function requireText(label, opts = {}) {
1487
- const result = opts.password ? await p.password({
1488
- message: label,
1489
- validate: (v) => v && v.length > 0 ? void 0 : "Required"
1490
- }) : await p.text({
1491
- message: label,
1492
- placeholder: opts.placeholder,
1493
- initialValue: opts.initialValue,
1494
- validate: (v) => v && v.trim().length > 0 ? void 0 : "Required"
1495
- });
1496
- if (p.isCancel(result)) {
1497
- p.cancel("Cancelled.");
1498
- process.exit(0);
1499
- }
1500
- return typeof result === "string" ? result.trim() : "";
1501
- }
1502
1459
  async function withSpinner(startLabel, onSuccess, onFailureLabel, fn) {
1503
1460
  const spinner5 = p.spinner();
1504
1461
  spinner5.start(startLabel);
@@ -1517,6 +1474,24 @@ async function withSpinner(startLabel, onSuccess, onFailureLabel, fn) {
1517
1474
  // src/commands/web-auth.ts
1518
1475
  import { spawn } from "child_process";
1519
1476
  import * as p2 from "@clack/prompts";
1477
+
1478
+ // src/color.ts
1479
+ var IS_TTY = Boolean(process.stdout.isTTY);
1480
+ var NO_COLOR = process.env.FORCE_COLOR !== void 0 ? false : process.env.NO_COLOR !== void 0 || process.env.TERM === "dumb" || !IS_TTY;
1481
+ function wrap(open, close) {
1482
+ if (NO_COLOR) return (s) => s;
1483
+ const o = `\x1B[${open}m`;
1484
+ const c = `\x1B[${close}m`;
1485
+ return (s) => `${o}${s}${c}`;
1486
+ }
1487
+ var bold = wrap(1, 22);
1488
+ var dim = wrap(2, 22);
1489
+ var red = wrap(31, 39);
1490
+ var green = wrap(32, 39);
1491
+ var yellow = wrap(33, 39);
1492
+ var cyan = wrap(36, 39);
1493
+
1494
+ // src/commands/web-auth.ts
1520
1495
  function sleep(ms) {
1521
1496
  return new Promise((resolve) => setTimeout(resolve, ms));
1522
1497
  }
@@ -1531,10 +1506,6 @@ function openBrowser(url) {
1531
1506
  }
1532
1507
  }
1533
1508
  async function webAuth(apiBaseUrl) {
1534
- if (!IS_TTY) {
1535
- appendLog("info", "web-auth: skipped (not a TTY)");
1536
- return null;
1537
- }
1538
1509
  appendLog("info", `web-auth: started (apiBaseUrl=${apiBaseUrl})`);
1539
1510
  const client = new PlatformClient(apiBaseUrl);
1540
1511
  const codeResponse = await withSpinner(
@@ -1548,9 +1519,16 @@ async function webAuth(apiBaseUrl) {
1548
1519
  `web-auth: code prepared (expiresIn=${codeResponse.expiresIn}s, pollInterval=${codeResponse.interval}s)`
1549
1520
  );
1550
1521
  const url = codeResponse.verificationUrl;
1551
- openBrowser(url);
1552
- p2.log.info(`Opening browser to: ${cyan(url)}`);
1553
- p2.log.info(dim("If the browser did not open, copy the URL above."));
1522
+ if (IS_TTY) {
1523
+ openBrowser(url);
1524
+ p2.log.info(`Opening browser to: ${cyan(url)}`);
1525
+ p2.log.info(dim("If the browser did not open, copy the URL above."));
1526
+ } else {
1527
+ p2.log.info(
1528
+ "To sign in, open this URL in a browser and approve the request:"
1529
+ );
1530
+ p2.log.info(cyan(url));
1531
+ }
1554
1532
  const deadline = Date.now() + codeResponse.expiresIn * 1e3;
1555
1533
  const interval = codeResponse.interval * 1e3;
1556
1534
  const spinner5 = p2.spinner();
@@ -1674,66 +1652,24 @@ async function runInit(args = []) {
1674
1652
  bootstrap = await tryReuseIdentity(apiBaseUrl, client, saved);
1675
1653
  }
1676
1654
  if (!bootstrap) {
1677
- let method = projectSlugFlag ? "web" : "email";
1678
- if (IS_TTY && !projectSlugFlag) {
1679
- const selected = await p3.select({
1680
- message: "How would you like to sign in?",
1681
- options: [
1682
- {
1683
- value: "web",
1684
- label: "Web authentication (recommended)",
1685
- hint: "opens browser"
1686
- },
1687
- { value: "email", label: "Email and password" }
1688
- ],
1689
- initialValue: "web"
1690
- });
1691
- if (p3.isCancel(selected)) {
1692
- p3.cancel("Cancelled.");
1693
- process.exit(0);
1694
- }
1695
- method = selected;
1655
+ const result = await webAuth(apiBaseUrl);
1656
+ if (!result) {
1657
+ appendLog("error", "init: web authentication failed");
1658
+ p3.log.error("Web authentication failed.");
1659
+ p3.outro("Aborted.");
1660
+ process.exit(1);
1696
1661
  }
1697
- appendLog("info", `init: auth method selected (${method})`);
1698
- if (method === "web") {
1699
- const result = await webAuth(apiBaseUrl);
1700
- if (!result) {
1701
- appendLog("error", "init: web authentication failed");
1702
- p3.log.error("Web authentication failed.");
1703
- p3.outro("Aborted.");
1704
- process.exit(1);
1662
+ client = result.client;
1663
+ bootstrap = await withSpinner(
1664
+ "Loading account...",
1665
+ (b) => `Signed in as ${b.session.email}${b.session.role === "admin" ? " (admin)" : ""}.`,
1666
+ "Could not load account.",
1667
+ async () => {
1668
+ const data = await client.getBootstrap();
1669
+ if (!data) throw new Error("Bootstrap returned no session.");
1670
+ return data;
1705
1671
  }
1706
- client = result.client;
1707
- bootstrap = await withSpinner(
1708
- "Loading account...",
1709
- (b) => `Signed in as ${b.session.email}${b.session.role === "admin" ? " (admin)" : ""}.`,
1710
- "Could not load account.",
1711
- async () => {
1712
- const data = await client.getBootstrap();
1713
- if (!data) throw new Error("Bootstrap returned no session.");
1714
- return data;
1715
- }
1716
- );
1717
- } else {
1718
- const email = await requireText("Email");
1719
- const password2 = await requireText("Password", { password: true });
1720
- await withSpinner(
1721
- "Signing in...",
1722
- () => "Signed in.",
1723
- "Sign-in failed.",
1724
- () => client.signInWithEmail(email, password2)
1725
- );
1726
- bootstrap = await withSpinner(
1727
- "Loading account...",
1728
- (b) => `Signed in as ${b.session.email}${b.session.role === "admin" ? " (admin)" : ""}.`,
1729
- "Could not load account.",
1730
- async () => {
1731
- const data = await client.getBootstrap();
1732
- if (!data) throw new Error("Bootstrap returned no session.");
1733
- return data;
1734
- }
1735
- );
1736
- }
1672
+ );
1737
1673
  }
1738
1674
  let project;
1739
1675
  if (projectSlugFlag) {
@@ -2070,87 +2006,23 @@ async function runLogin(_args = []) {
2070
2006
  println();
2071
2007
  }
2072
2008
  }
2073
- let method = "email";
2074
- if (IS_TTY) {
2075
- const selected = await p4.select({
2076
- message: "How would you like to sign in?",
2077
- options: [
2078
- {
2079
- value: "web",
2080
- label: "Web authentication (recommended)",
2081
- hint: "opens browser"
2082
- },
2083
- { value: "email", label: "Email and password" }
2084
- ],
2085
- initialValue: "web"
2086
- });
2087
- if (p4.isCancel(selected)) {
2088
- p4.cancel("Cancelled.");
2089
- process.exit(0);
2090
- }
2091
- method = selected;
2092
- }
2093
- appendLog("info", `login: auth method selected (${method})`);
2094
- if (method === "web") {
2095
- const result = await webAuth(apiBaseUrl);
2096
- if (!result) {
2097
- appendLog("error", "login: web authentication failed");
2098
- p4.log.error("Web authentication failed.");
2099
- p4.outro("Aborted.");
2100
- process.exit(1);
2101
- }
2102
- await saveIdentity({
2103
- apiBaseUrl,
2104
- sessionCookie: result.sessionCookie,
2105
- email: result.email,
2106
- userId: result.userId,
2107
- savedAt: (/* @__PURE__ */ new Date()).toISOString()
2108
- });
2109
- appendLog("info", `login: completed via web auth (email=${result.email})`);
2110
- println();
2111
- row(CHECK, "Signed in", cyan(result.email));
2112
- footer("Run `npx hillclimb` in a repo to wire up auto-upload.");
2113
- return;
2114
- }
2115
- const email = await requireText("Email");
2116
- const password2 = await requireText("Password", { password: true });
2117
- const client = new PlatformClient(apiBaseUrl);
2118
- await withSpinner(
2119
- "Signing in...",
2120
- () => "Signed in.",
2121
- "Sign-in failed.",
2122
- () => client.signInWithEmail(email, password2)
2123
- );
2124
- const bootstrap = await withSpinner(
2125
- "Loading account...",
2126
- (b) => `Signed in as ${b.session.email}.`,
2127
- "Could not load account.",
2128
- async () => {
2129
- const data = await client.getBootstrap();
2130
- if (!data) throw new Error("Bootstrap returned no session.");
2131
- return data;
2132
- }
2133
- );
2134
- const cookie = client.getSessionCookie();
2135
- if (!cookie) {
2136
- appendLog("error", "login: session cookie missing after sign-in");
2137
- p4.log.error("Session cookie was lost during sign-in.");
2009
+ const result = await webAuth(apiBaseUrl);
2010
+ if (!result) {
2011
+ appendLog("error", "login: web authentication failed");
2012
+ p4.log.error("Web authentication failed.");
2138
2013
  p4.outro("Aborted.");
2139
2014
  process.exit(1);
2140
2015
  }
2141
2016
  await saveIdentity({
2142
2017
  apiBaseUrl,
2143
- sessionCookie: cookie,
2144
- email: bootstrap.session.email,
2145
- userId: bootstrap.session.userId,
2018
+ sessionCookie: result.sessionCookie,
2019
+ email: result.email,
2020
+ userId: result.userId,
2146
2021
  savedAt: (/* @__PURE__ */ new Date()).toISOString()
2147
2022
  });
2148
- appendLog(
2149
- "info",
2150
- `login: completed via email/password (email=${bootstrap.session.email})`
2151
- );
2023
+ appendLog("info", `login: completed via web auth (email=${result.email})`);
2152
2024
  println();
2153
- row(CHECK, "Signed in", cyan(bootstrap.session.email));
2025
+ row(CHECK, "Signed in", cyan(result.email));
2154
2026
  footer("Run `npx hillclimb` in a repo to wire up auto-upload.");
2155
2027
  }
2156
2028
 
@@ -11845,9 +11717,9 @@ function extractTextReasoningToolUses(content) {
11845
11717
  } else if (content !== void 0 && content !== null) {
11846
11718
  textParts.push(stringify(content));
11847
11719
  }
11848
- const text3 = textParts.filter((p7) => p7 && p7.trim()).map((p7) => p7.trim()).join("\n\n");
11720
+ const text2 = textParts.filter((p7) => p7 && p7.trim()).map((p7) => p7.trim()).join("\n\n");
11849
11721
  const reasoning = reasoningParts.filter((p7) => p7 && p7.trim()).map((p7) => p7.trim()).join("\n\n");
11850
- return [text3, reasoning || void 0, toolBlocks];
11722
+ return [text2, reasoning || void 0, toolBlocks];
11851
11723
  }
11852
11724
  function buildMetrics(usage) {
11853
11725
  if (typeof usage !== "object" || usage === null) return void 0;
@@ -11876,8 +11748,8 @@ function formatToolResult(block, toolUseResult) {
11876
11748
  if (content.trim()) parts.push(content.trim());
11877
11749
  } else if (Array.isArray(content)) {
11878
11750
  for (const item of content) {
11879
- const text3 = stringify(item);
11880
- if (text3.trim()) parts.push(text3.trim());
11751
+ const text2 = stringify(item);
11752
+ if (text2.trim()) parts.push(text2.trim());
11881
11753
  }
11882
11754
  } else if (content !== void 0 && content !== null && content !== "") {
11883
11755
  parts.push(stringify(content));
@@ -12005,7 +11877,7 @@ function convertClaudeToTrajectory(jsonlContent, sessionId) {
12005
11877
  const eventType = event.type;
12006
11878
  const timestamp = event.timestamp;
12007
11879
  if (eventType === "assistant") {
12008
- const [text3, reasoning, toolBlocks] = extractTextReasoningToolUses(
11880
+ const [text2, reasoning, toolBlocks] = extractTextReasoningToolUses(
12009
11881
  msg.content
12010
11882
  );
12011
11883
  const msgId = msg.id;
@@ -12029,12 +11901,12 @@ function convertClaudeToTrajectory(jsonlContent, sessionId) {
12029
11901
  extra.user_type = event.userType;
12030
11902
  extra.is_sidechain = event.isSidechain ?? false;
12031
11903
  const modelName = msg.model || defaultModelName;
12032
- if (text3 || reasoning || toolBlocks.length === 0) {
11904
+ if (text2 || reasoning || toolBlocks.length === 0) {
12033
11905
  normalizedEvents.push({
12034
11906
  kind: "message",
12035
11907
  timestamp,
12036
11908
  role: msg.role ?? "assistant",
12037
- text: text3 || "",
11909
+ text: text2 || "",
12038
11910
  reasoning: msg.role === "assistant" ? reasoning : void 0,
12039
11911
  metrics,
12040
11912
  extra: Object.keys(extra).length > 0 ? extra : void 0,
@@ -12074,13 +11946,13 @@ function convertClaudeToTrajectory(jsonlContent, sessionId) {
12074
11946
  if (eventType === "user") {
12075
11947
  const content = msg.content;
12076
11948
  if (typeof content === "string") {
12077
- const text3 = content.trim();
12078
- if (text3) {
11949
+ const text2 = content.trim();
11950
+ if (text2) {
12079
11951
  normalizedEvents.push({
12080
11952
  kind: "message",
12081
11953
  timestamp,
12082
11954
  role: "user",
12083
- text: text3,
11955
+ text: text2,
12084
11956
  extra: { is_sidechain: event.isSidechain ?? false }
12085
11957
  });
12086
11958
  }
@@ -12142,13 +12014,13 @@ function convertClaudeToTrajectory(jsonlContent, sessionId) {
12142
12014
  continue;
12143
12015
  }
12144
12016
  if (content !== void 0 && content !== null && content !== "") {
12145
- const text3 = stringify(content).trim();
12146
- if (text3) {
12017
+ const text2 = stringify(content).trim();
12018
+ if (text2) {
12147
12019
  normalizedEvents.push({
12148
12020
  kind: "message",
12149
12021
  timestamp,
12150
12022
  role: "user",
12151
- text: text3
12023
+ text: text2
12152
12024
  });
12153
12025
  }
12154
12026
  }
@@ -12284,8 +12156,8 @@ function extractMessageText(content) {
12284
12156
  const parts = [];
12285
12157
  for (const block of content) {
12286
12158
  if (typeof block === "object" && block !== null) {
12287
- const text3 = block.text;
12288
- if (typeof text3 === "string") parts.push(text3);
12159
+ const text2 = block.text;
12160
+ if (typeof text2 === "string") parts.push(text2);
12289
12161
  }
12290
12162
  }
12291
12163
  return parts.join("");
@@ -12417,12 +12289,12 @@ function convertCodexToTrajectory(jsonlContent, sessionId) {
12417
12289
  }
12418
12290
  if (payloadType === "message") {
12419
12291
  const content = payload.content;
12420
- const text3 = Array.isArray(content) ? extractMessageText(content) : "";
12292
+ const text2 = Array.isArray(content) ? extractMessageText(content) : "";
12421
12293
  normalizedEvents.push({
12422
12294
  kind: "message",
12423
12295
  timestamp,
12424
12296
  role: payload.role ?? "user",
12425
- text: text3,
12297
+ text: text2,
12426
12298
  reasoning: payload.role === "assistant" ? pendingReasoning : void 0
12427
12299
  });
12428
12300
  pendingReasoning = void 0;
@@ -12928,7 +12800,7 @@ function convertCursorToTrajectory(jsonlContent, sessionId) {
12928
12800
  else if (role === "user") source = "user";
12929
12801
  else source = "system";
12930
12802
  const contentParts = entry.message?.content;
12931
- let text3 = "";
12803
+ let text2 = "";
12932
12804
  if (Array.isArray(contentParts)) {
12933
12805
  const textParts = [];
12934
12806
  for (const part of contentParts) {
@@ -12936,13 +12808,13 @@ function convertCursorToTrajectory(jsonlContent, sessionId) {
12936
12808
  textParts.push(part.text);
12937
12809
  }
12938
12810
  }
12939
- text3 = textParts.join("\n\n").trim();
12811
+ text2 = textParts.join("\n\n").trim();
12940
12812
  }
12941
- if (!text3) continue;
12813
+ if (!text2) continue;
12942
12814
  const step = {
12943
12815
  step_id: stepId,
12944
12816
  source,
12945
- message: text3
12817
+ message: text2
12946
12818
  };
12947
12819
  steps.push(step);
12948
12820
  stepId++;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hillclimb",
3
- "version": "0.5.5",
3
+ "version": "0.6.0",
4
4
  "description": "Extract and export AI coding tool logs grouped by repo",
5
5
  "license": "MIT",
6
6
  "type": "module",