hillclimb 0.1.4 → 0.1.5

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