hillclimb 0.1.4 → 0.1.6

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 +103 -162
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -174,11 +174,10 @@ function pacificParts(date) {
174
174
  hour: "2-digit",
175
175
  minute: "2-digit",
176
176
  second: "2-digit",
177
- hour12: false
177
+ hourCycle: "h23"
178
178
  }).formatToParts(date);
179
179
  const lookup = {};
180
180
  for (const p7 of parts) if (p7.type !== "literal") lookup[p7.type] = p7.value;
181
- if (lookup.hour === "24") lookup.hour = "00";
182
181
  return {
183
182
  year: lookup.year ?? "0000",
184
183
  month: lookup.month ?? "00",
@@ -382,7 +381,6 @@ var PlatformClient = class {
382
381
  if (!data?.session?.user || !data.session.session) return null;
383
382
  return {
384
383
  session: {
385
- activeOrganizationId: data.session.session.activeOrganizationId ?? null,
386
384
  userId: data.session.user.id ?? "",
387
385
  email: data.session.user.email ?? "",
388
386
  role: data.session.user.role ?? null
@@ -391,24 +389,18 @@ var PlatformClient = class {
391
389
  id: o.id,
392
390
  name: o.name,
393
391
  slug: o.slug
392
+ })),
393
+ projects: (data.projects ?? []).map((p7) => ({
394
+ id: p7.id,
395
+ name: p7.name,
396
+ slug: p7.slug
394
397
  }))
395
398
  };
396
399
  }
397
- async setActiveOrganization(organizationId) {
398
- await this.request("POST", "/api/auth/organization/set-active", {
399
- organizationId
400
- });
401
- }
402
- async setActiveOrganizationAsAdmin(organizationId) {
403
- await this.request(
404
- "POST",
405
- `/api/v1/admin/organizations/${organizationId}/set-active`
406
- );
407
- }
408
- async listWorkspaces(organizationId) {
400
+ async listProjects() {
409
401
  const data = await this.request(
410
402
  "GET",
411
- `/api/v1/organizations/${organizationId}/workspaces`
403
+ "/api/v1/projects"
412
404
  );
413
405
  return data.workspaces ?? [];
414
406
  }
@@ -507,8 +499,8 @@ var PlatformClient = class {
507
499
  import fs4 from "fs";
508
500
  import os2 from "os";
509
501
  import path5 from "path";
510
- var HOOK_CMD = (sub) => `npx hillclimb ${sub}`;
511
- var GIT_TRACES_CMD = (tool) => `npx hillclimb git-traces --tool=${tool}`;
502
+ var HOOK_CMD = (sub) => `npx hillclimb@latest ${sub}`;
503
+ var GIT_TRACES_CMD = (tool) => `npx hillclimb@latest git-traces --tool=${tool}`;
512
504
  var TOOLS = [
513
505
  {
514
506
  tool: "claude",
@@ -542,7 +534,7 @@ var TOOLS = [
542
534
  // a plugin file at .opencode/plugins/hillclimb.js — auto-discovered by
543
535
  // opencode with no registration. The plugin subscribes to session.created
544
536
  // (SessionStart), session.idle (Stop, per-turn), and
545
- // server.instance.disposed (SessionEnd) and shells out to `npx hillclimb`.
537
+ // server.instance.disposed (SessionEnd) and shells out to `npx hillclimb@latest`.
546
538
  tool: "opencode",
547
539
  label: "opencode",
548
540
  settingsFile: ".opencode/plugins/hillclimb.js",
@@ -737,7 +729,7 @@ function copilotUninstall(settings, eventName, command) {
737
729
  }
738
730
  return true;
739
731
  }
740
- var OPENCODE_PLUGIN_VERSION = 2;
732
+ var OPENCODE_PLUGIN_VERSION = 3;
741
733
  var OPENCODE_PLUGIN_MARKER = `// HILLCLIMB_OPENCODE_PLUGIN_VERSION=${OPENCODE_PLUGIN_VERSION}`;
742
734
  var OPENCODE_PLUGIN_CONTENT = `${OPENCODE_PLUGIN_MARKER}
743
735
  // Auto-installed by \`npx hillclimb\`. Do not edit manually \u2014 re-running
@@ -785,7 +777,7 @@ function writeTranscript(sessionID) {
785
777
  function spawnHillclimb(subcommand, payload) {
786
778
  try {
787
779
  const args = subcommand.split(" ").filter(Boolean);
788
- const child = spawn("npx", ["hillclimb"].concat(args), {
780
+ const child = spawn("npx", ["hillclimb@latest"].concat(args), {
789
781
  detached: true,
790
782
  stdio: ["pipe", "ignore", "ignore"],
791
783
  });
@@ -936,10 +928,11 @@ function uninstall(settings, format, eventName, command) {
936
928
  }
937
929
  }
938
930
  function legacyCommandsFor(command) {
939
- const prefix = "npx hillclimb ";
931
+ const prefix = "npx hillclimb@latest ";
940
932
  if (!command.startsWith(prefix)) return [];
941
933
  const sub = command.slice(prefix.length);
942
934
  const legacy = [
935
+ `npx hillclimb ${sub}`,
943
936
  `hillclimb-extract ${sub}`,
944
937
  `npx hillclimb-extract ${sub}`,
945
938
  `npx @hillclimb/extract ${sub}`
@@ -947,6 +940,7 @@ function legacyCommandsFor(command) {
947
940
  const toolMatch = sub.match(/^(\S+)\s+--tool=\S+$/);
948
941
  if (toolMatch) {
949
942
  const bareSub = toolMatch[1];
943
+ legacy.push(`npx hillclimb@latest ${bareSub}`);
950
944
  legacy.push(`npx hillclimb ${bareSub}`);
951
945
  }
952
946
  return legacy;
@@ -1039,20 +1033,45 @@ async function ensureCodexHooksEnabled() {
1039
1033
  });
1040
1034
  await fs4.promises.writeFile(
1041
1035
  CODEX_CONFIG_PATH,
1042
- "[features]\ncodex_hooks = true\n"
1036
+ "[features]\nhooks = true\n"
1043
1037
  );
1044
1038
  return true;
1045
1039
  }
1046
1040
  throw err;
1047
1041
  }
1048
- if (/codex_hooks\s*=\s*true/i.test(content)) return false;
1042
+ const original = content;
1043
+ content = content.replace(
1044
+ /^\s*codex_hooks\s*=\s*(true|false)\s*(?:#.*)?\r?\n?/gim,
1045
+ ""
1046
+ );
1049
1047
  const featuresMatch = content.match(/^\[features\]\s*$/m);
1050
1048
  if (featuresMatch) {
1051
- const idx = featuresMatch.index + featuresMatch[0].length;
1052
- content = content.slice(0, idx) + "\ncodex_hooks = true" + content.slice(idx);
1049
+ const matchIndex = featuresMatch.index;
1050
+ if (matchIndex === void 0) {
1051
+ throw new Error("Could not locate [features] section.");
1052
+ }
1053
+ const bodyStart = matchIndex + featuresMatch[0].length;
1054
+ const nextSectionOffset = content.slice(bodyStart).search(/\n\[[^\n]+\]\s*(?:\r?\n|$)/);
1055
+ const bodyEnd = nextSectionOffset === -1 ? content.length : bodyStart + nextSectionOffset;
1056
+ const body = content.slice(bodyStart, bodyEnd);
1057
+ const hooksMatch = body.match(/^(\s*)hooks\s*=\s*(true|false)(.*)$/im);
1058
+ if (hooksMatch?.[2]?.toLowerCase() === "true") {
1059
+ } else if (hooksMatch?.index !== void 0) {
1060
+ const lineStart = bodyStart + hooksMatch.index;
1061
+ const lineEnd = lineStart + hooksMatch[0].length;
1062
+ content = content.slice(0, lineStart) + `${hooksMatch[1]}hooks = true${hooksMatch[3]}` + content.slice(lineEnd);
1063
+ } else {
1064
+ content = `${content.slice(0, bodyStart)}
1065
+ hooks = true${content.slice(bodyStart)}`;
1066
+ }
1053
1067
  } else {
1054
- content = content.trimEnd() + "\n\n[features]\ncodex_hooks = true\n";
1068
+ content = `${content.trimEnd()}
1069
+
1070
+ [features]
1071
+ hooks = true
1072
+ `;
1055
1073
  }
1074
+ if (content === original) return false;
1056
1075
  await fs4.promises.writeFile(CODEX_CONFIG_PATH, content);
1057
1076
  return true;
1058
1077
  }
@@ -1312,130 +1331,61 @@ async function runInit(args = []) {
1312
1331
  );
1313
1332
  }
1314
1333
  }
1315
- let activeOrgId = bootstrap.session.activeOrganizationId;
1316
- const orgs = bootstrap.organizations;
1317
- const isAdmin = bootstrap.session.role === "admin";
1318
- if (orgs.length === 0 && !isAdmin) {
1319
- p3.log.error(
1320
- "You do not belong to any organization. Ask a platform admin to add you."
1321
- );
1322
- p3.outro("Aborted.");
1323
- process.exit(1);
1324
- }
1325
- let pickedId;
1326
1334
  let workspace;
1327
1335
  if (workspaceSlugFlag) {
1328
1336
  const resolved = await withSpinner(
1329
- `Looking up workspace "${workspaceSlugFlag}"...`,
1330
- (w) => `Found workspace: ${w.name}`,
1331
- `Could not find workspace "${workspaceSlugFlag}".`,
1337
+ `Looking up project "${workspaceSlugFlag}"...`,
1338
+ (w) => `Found project: ${w.name}`,
1339
+ `Could not find project "${workspaceSlugFlag}".`,
1332
1340
  () => client.getWorkspaceBySlug(workspaceSlugFlag)
1333
1341
  );
1334
1342
  if (resolved.status !== "active") {
1335
1343
  p3.log.error(
1336
- `Workspace "${resolved.name}" is ${resolved.status}. Only active workspaces can be initialized.`
1344
+ `Project "${resolved.name}" is ${resolved.status}. Only active projects can be initialized.`
1337
1345
  );
1338
1346
  p3.outro("Aborted.");
1339
1347
  process.exit(1);
1340
1348
  }
1341
- pickedId = resolved.organizationId;
1342
1349
  workspace = resolved;
1343
1350
  appendLog(
1344
1351
  "info",
1345
- `init: workspace pre-scoped (slug=${workspaceSlugFlag}, id=${resolved.id}, orgId=${pickedId})`
1352
+ `init: project pre-scoped (slug=${workspaceSlugFlag}, id=${resolved.id})`
1346
1353
  );
1347
- const belongs = orgs.some((o) => o.id === pickedId);
1348
- if (!belongs && !isAdmin) {
1349
- p3.log.error(
1350
- "You do not have access to this workspace's organization."
1351
- );
1352
- p3.outro("Aborted.");
1353
- process.exit(1);
1354
- }
1355
- if (pickedId !== activeOrgId) {
1356
- try {
1357
- if (isAdmin) {
1358
- await client.setActiveOrganizationAsAdmin(pickedId);
1359
- } else {
1360
- await client.setActiveOrganization(pickedId);
1361
- }
1362
- } catch (err) {
1363
- appendLog("error", `init: setActiveOrganization failed: ${formatError(err)}`);
1364
- p3.log.error(err instanceof Error ? err.message : String(err));
1365
- p3.outro("Aborted.");
1366
- process.exit(1);
1367
- }
1368
- }
1369
- activeOrgId = pickedId;
1370
1354
  } else {
1371
- if (orgs.length === 1) {
1372
- const only = orgs[0];
1373
- if (!only) {
1374
- p3.log.error("Unexpected empty organization list.");
1375
- p3.outro("Aborted.");
1376
- process.exit(1);
1377
- }
1378
- pickedId = only.id;
1379
- p3.log.info(`Using organization: ${only.name}`);
1380
- } else {
1381
- const selected = await p3.select({
1382
- message: "Select an organization",
1383
- options: orgs.map((o) => ({
1384
- value: o.id,
1385
- label: o.name,
1386
- hint: o.id === activeOrgId ? `${o.slug ?? ""} (active)` : o.slug ?? ""
1387
- })),
1388
- initialValue: activeOrgId ?? orgs[0]?.id
1389
- });
1390
- if (p3.isCancel(selected)) {
1391
- p3.cancel("Cancelled.");
1392
- process.exit(0);
1393
- }
1394
- pickedId = selected;
1395
- }
1396
- if (pickedId !== activeOrgId) {
1397
- try {
1398
- if (isAdmin) {
1399
- await client.setActiveOrganizationAsAdmin(pickedId);
1400
- } else {
1401
- await client.setActiveOrganization(pickedId);
1402
- }
1403
- } catch (err) {
1404
- appendLog("error", `init: setActiveOrganization failed: ${formatError(err)}`);
1405
- p3.log.error(err instanceof Error ? err.message : String(err));
1406
- p3.outro("Aborted.");
1407
- process.exit(1);
1408
- }
1409
- }
1410
- activeOrgId = pickedId;
1411
1355
  const workspaces = await withSpinner(
1412
- "Loading workspaces...",
1413
- (ws) => `Found ${ws.length} workspace(s).`,
1414
- "Could not list workspaces.",
1415
- () => client.listWorkspaces(pickedId)
1356
+ "Loading projects...",
1357
+ (ws) => `Found ${ws.length} project(s).`,
1358
+ "Could not list projects.",
1359
+ () => client.listProjects()
1416
1360
  );
1417
1361
  const activeWorkspaces = workspaces.filter((w) => w.status === "active");
1418
1362
  const workspaceChoices = activeWorkspaces.length > 0 ? activeWorkspaces : workspaces;
1419
1363
  if (workspaceChoices.length === 0) {
1420
1364
  p3.log.error(
1421
- "This organization has no workspaces. Create one in the web UI first."
1365
+ "You do not have access to any projects. Create one in the web UI first."
1422
1366
  );
1423
1367
  p3.outro("Aborted.");
1424
1368
  process.exit(1);
1425
1369
  }
1426
- const selectedWorkspaceId = await p3.select({
1427
- message: "Select a workspace",
1428
- options: workspaceChoices.map((w) => ({
1429
- value: w.id,
1430
- label: w.name,
1431
- hint: `${w.slug} (${w.status})`
1432
- }))
1433
- });
1434
- if (p3.isCancel(selectedWorkspaceId)) {
1435
- p3.cancel("Cancelled.");
1436
- process.exit(0);
1370
+ let picked;
1371
+ if (workspaceChoices.length === 1) {
1372
+ picked = workspaceChoices[0];
1373
+ p3.log.info(`Using project: ${picked.name}`);
1374
+ } else {
1375
+ const selectedWorkspaceId = await p3.select({
1376
+ message: "Select a project",
1377
+ options: workspaceChoices.map((w) => ({
1378
+ value: w.id,
1379
+ label: w.name,
1380
+ hint: `${w.slug} (${w.status})`
1381
+ }))
1382
+ });
1383
+ if (p3.isCancel(selectedWorkspaceId)) {
1384
+ p3.cancel("Cancelled.");
1385
+ process.exit(0);
1386
+ }
1387
+ picked = workspaceChoices.find((w) => w.id === selectedWorkspaceId);
1437
1388
  }
1438
- const picked = workspaceChoices.find((w) => w.id === selectedWorkspaceId);
1439
1389
  if (!picked) {
1440
1390
  p3.log.error("Invalid workspace selection.");
1441
1391
  p3.outro("Aborted.");
@@ -1444,7 +1394,7 @@ async function runInit(args = []) {
1444
1394
  workspace = picked;
1445
1395
  appendLog(
1446
1396
  "info",
1447
- `init: workspace selected (slug=${workspace.slug}, id=${workspace.id}, orgId=${pickedId})`
1397
+ `init: project selected (slug=${workspace.slug}, id=${workspace.id})`
1448
1398
  );
1449
1399
  }
1450
1400
  const types = await withSpinner(
@@ -1455,7 +1405,7 @@ async function runInit(args = []) {
1455
1405
  );
1456
1406
  if (types.length === 0) {
1457
1407
  p3.log.error(
1458
- `Workspace "${workspace.name}" has no contribution types. Create one in the web UI first.`
1408
+ `Project "${workspace.name}" has no contribution types. Create one in the web UI first.`
1459
1409
  );
1460
1410
  p3.outro("Aborted.");
1461
1411
  process.exit(1);
@@ -1468,7 +1418,7 @@ async function runInit(args = []) {
1468
1418
  p3.log.info(`Using contribution type: ${type.name}`);
1469
1419
  } else {
1470
1420
  p3.log.warn(
1471
- 'Could not find the default "Agent Traces" contribution type in this workspace. Please contact the workspace maintainer.'
1421
+ 'Could not find the default "Agent Traces" contribution type in this project. Please contact the project maintainer.'
1472
1422
  );
1473
1423
  const selectedTypeId = await p3.select({
1474
1424
  message: "Select a contribution type",
@@ -1500,11 +1450,9 @@ async function runInit(args = []) {
1500
1450
  "info",
1501
1451
  `init: contribution type selected (slug=${type.slug}, name=${type.name})`
1502
1452
  );
1503
- const pickedOrg = orgs.find((o) => o.id === activeOrgId);
1504
1453
  await upsertProject(repoRoot, {
1505
1454
  apiBaseUrl,
1506
- organizationId: activeOrgId,
1507
- organizationName: pickedOrg?.name,
1455
+ organizationId: workspace.organizationId,
1508
1456
  workspaceId: workspace.id,
1509
1457
  workspaceSlug: workspace.slug,
1510
1458
  workspaceName: workspace.name,
@@ -1548,19 +1496,16 @@ async function runInit(args = []) {
1548
1496
  try {
1549
1497
  const changed = await ensureCodexHooksEnabled();
1550
1498
  if (changed) {
1551
- appendLog(
1552
- "info",
1553
- "init: enabled codex_hooks in ~/.codex/config.toml"
1554
- );
1555
- p3.log.success("Enabled codex_hooks in ~/.codex/config.toml");
1499
+ appendLog("info", "init: enabled hooks in ~/.codex/config.toml");
1500
+ p3.log.success("Enabled hooks in ~/.codex/config.toml");
1556
1501
  }
1557
1502
  } catch (err) {
1558
1503
  appendLog(
1559
1504
  "warn",
1560
- `init: could not enable codex_hooks: ${formatError(err)}`
1505
+ `init: could not enable Codex hooks: ${formatError(err)}`
1561
1506
  );
1562
1507
  p3.log.warn(
1563
- `Could not enable codex_hooks in config.toml: ${err instanceof Error ? err.message : String(err)}`
1508
+ `Could not enable hooks in config.toml: ${err instanceof Error ? err.message : String(err)}`
1564
1509
  );
1565
1510
  }
1566
1511
  }
@@ -1575,7 +1520,7 @@ async function runInit(args = []) {
1575
1520
  }
1576
1521
  appendLog(
1577
1522
  "info",
1578
- `init: completed (workspace=${workspace.slug}, contributionType=${type.slug})`
1523
+ `init: completed (project=${workspace.slug}, contributionType=${type.slug})`
1579
1524
  );
1580
1525
  p3.outro(
1581
1526
  `Done. Your next coding session in this repo will upload automatically to ${workspace.name}.`
@@ -1827,18 +1772,19 @@ async function runStatus(args = []) {
1827
1772
  const identity = await loadIdentity(config.apiBaseUrl);
1828
1773
  let signedInEmail = null;
1829
1774
  let loginError = null;
1830
- let orgNameFromBootstrap = null;
1831
1775
  if (!identity) {
1832
1776
  loginError = "expired";
1833
1777
  } else {
1834
- const client = new PlatformClient(config.apiBaseUrl, identity.sessionCookie);
1778
+ const client = new PlatformClient(
1779
+ config.apiBaseUrl,
1780
+ identity.sessionCookie
1781
+ );
1835
1782
  try {
1836
1783
  const bootstrap = await client.getBootstrap();
1837
1784
  if (!bootstrap) {
1838
1785
  loginError = "expired";
1839
1786
  } else {
1840
1787
  signedInEmail = bootstrap.session.email;
1841
- orgNameFromBootstrap = bootstrap.organizations.find((o) => o.id === config.organizationId)?.name ?? null;
1842
1788
  }
1843
1789
  } catch (err) {
1844
1790
  if (err instanceof PlatformError && err.status === 401) {
@@ -1858,9 +1804,7 @@ async function runStatus(args = []) {
1858
1804
  } else {
1859
1805
  row(CROSS, "Login", dim(`couldn't reach ${config.apiBaseUrl}`));
1860
1806
  }
1861
- const orgDisplay = config.organizationName ?? orgNameFromBootstrap ?? config.organizationId;
1862
- row(CHECK, "Organization", bold(orgDisplay));
1863
- row(CHECK, "Workspace", bold(config.workspaceName));
1807
+ row(CHECK, "Project", bold(config.workspaceName));
1864
1808
  row(CHECK, "Contribution", bold(config.contributionTypeName));
1865
1809
  row(CHECK, "Repo", bold(repoName));
1866
1810
  let anyHookInstalled = false;
@@ -1898,9 +1842,11 @@ async function runStatus(args = []) {
1898
1842
  debugRow("Configured repos:", String(Object.keys(projects.projects).length));
1899
1843
  debugRow("Repo root:", repoRoot);
1900
1844
  debugRow("API URL:", config.apiBaseUrl);
1901
- debugRow("Organization ID:", config.organizationId);
1845
+ if (config.organizationId) {
1846
+ debugRow("Legacy org ID:", config.organizationId);
1847
+ }
1902
1848
  const wsIdent = config.workspaceSlug === config.workspaceId ? config.workspaceId : `${config.workspaceSlug} / ${config.workspaceId}`;
1903
- debugRow("Workspace:", `${config.workspaceName} (${wsIdent})`);
1849
+ debugRow("Project:", `${config.workspaceName} (${wsIdent})`);
1904
1850
  debugRow(
1905
1851
  "Contribution type:",
1906
1852
  `${config.contributionTypeName} (${config.contributionTypeSlug})`
@@ -11717,13 +11663,8 @@ async function readStdin() {
11717
11663
  function sanitize(value) {
11718
11664
  return value.replace(/[^a-zA-Z0-9._-]/g, "_");
11719
11665
  }
11720
- function formatTimestamp(date) {
11721
- const pad = (n) => String(n).padStart(2, "0");
11722
- return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}-${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}`;
11723
- }
11724
- function formatTitleTimestamp(date) {
11725
- const pad = (n) => String(n).padStart(2, "0");
11726
- return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
11666
+ function formatEpochSeconds(date) {
11667
+ return String(Math.floor(date.getTime() / 1e3));
11727
11668
  }
11728
11669
  function lineHasAssistant(line) {
11729
11670
  return line.includes('"type":"assistant"') || line.includes('"role":"assistant"') || line.includes('"type":"assistant.');
@@ -11871,12 +11812,13 @@ async function uploadSession(args) {
11871
11812
  opencode: "opencode"
11872
11813
  };
11873
11814
  const toolLabel = toolLabels[sourceTool] ?? "Claude";
11874
- const title = `${toolLabel} session ${shortId} \u2014 ${formatTitleTimestamp(now)}`;
11815
+ const epochSeconds = formatEpochSeconds(now);
11816
+ const title = `${toolLabel} session ${shortId} \u2014 ${epochSeconds}`;
11875
11817
  const body = `Session ID: ${sessionId}
11876
11818
  Tool: ${toolLabel}
11877
11819
  Repo: ${repoRoot}
11878
11820
  Uploaded: ${now.toISOString()}`;
11879
- const zipFilename = `${sourceTool}-${sanitize(shortId)}-${formatTimestamp(now)}.zip`;
11821
+ const zipFilename = `${sourceTool}-${sanitize(shortId)}-${epochSeconds}.zip`;
11880
11822
  const output = new PlatformUploadOutput({
11881
11823
  client,
11882
11824
  workspaceId: config.workspaceId,
@@ -12519,9 +12461,8 @@ async function releaseLock(repoRoot, tool) {
12519
12461
  var CLI_VERSION = "0.1.0";
12520
12462
  var GIT_TRACES_SLUG = "git-traces";
12521
12463
  var MAX_UPLOAD_BYTES = 500 * 1024 * 1024;
12522
- function formatTitleTimestamp2(date) {
12523
- const pad = (n) => String(n).padStart(2, "0");
12524
- return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
12464
+ function formatEpochSeconds2(date) {
12465
+ return String(Math.floor(date.getTime() / 1e3));
12525
12466
  }
12526
12467
  var TOOL_LABELS = {
12527
12468
  cursor: "Cursor",
@@ -12768,12 +12709,13 @@ async function handleStop(payload, tool) {
12768
12709
  if (!artifacts) return;
12769
12710
  const toolLabel = TOOL_LABELS[tool] ?? "Claude";
12770
12711
  const now = /* @__PURE__ */ new Date();
12712
+ const epochSeconds = formatEpochSeconds2(now);
12771
12713
  const shortId = state.sessionId.slice(0, 12);
12772
12714
  const contribution = await client.createContribution(
12773
12715
  project.config.workspaceId,
12774
12716
  {
12775
12717
  contributionTypeSlug: GIT_TRACES_SLUG,
12776
- title: `${toolLabel} session ${shortId} \u2014 ${formatTitleTimestamp2(now)}`,
12718
+ title: `${toolLabel} session ${shortId} \u2014 ${epochSeconds}`,
12777
12719
  body: `Session ID: ${state.sessionId}
12778
12720
  Tool: ${toolLabel}
12779
12721
  Repo: ${cwd}
@@ -13130,9 +13072,8 @@ var ZipOutput = class {
13130
13072
  const repoName = sanitizeFilename(path15.basename(group.repoPath));
13131
13073
  const timeRange = options.timeRange;
13132
13074
  const rangePart = timeRange?.label ?? "all";
13133
- const now = /* @__PURE__ */ new Date();
13134
- const timestamp = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, "0")}${String(now.getDate()).padStart(2, "0")}-${String(now.getHours()).padStart(2, "0")}${String(now.getMinutes()).padStart(2, "0")}${String(now.getSeconds()).padStart(2, "0")}`;
13135
- const baseName = `logs-${repoName}-${rangePart}-${timestamp}`;
13075
+ const epochSeconds = Math.floor(Date.now() / 1e3);
13076
+ const baseName = `logs-${repoName}-${rangePart}-${epochSeconds}`;
13136
13077
  const outputPath = getUniqueFilename(downloadsDir, baseName, ".zip");
13137
13078
  const output = fs12.createWriteStream(outputPath);
13138
13079
  const archive = archiver2("zip", { zlib: { level: 6 } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hillclimb",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Extract and export AI coding tool logs grouped by repo",
5
5
  "license": "MIT",
6
6
  "type": "module",