rudel 0.1.8 → 0.1.10

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 +523 -325
  2. package/package.json +3 -2
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
- #!/usr/bin/env bun
2
- // @bun
1
+ #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
3
3
  var __create = Object.create;
4
4
  var __getProtoOf = Object.getPrototypeOf;
5
5
  var __defProp = Object.defineProperty;
@@ -26,7 +26,7 @@ var __export = (target, all) => {
26
26
  set: (newValue) => all[name] = () => newValue
27
27
  });
28
28
  };
29
- var __require = import.meta.require;
29
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
30
30
 
31
31
  // ../../node_modules/.bun/picocolors@1.1.1/node_modules/picocolors/picocolors.js
32
32
  var require_picocolors = __commonJS((exports, module) => {
@@ -1959,7 +1959,7 @@ async function run(app, inputs, context) {
1959
1959
  // package.json
1960
1960
  var package_default = {
1961
1961
  name: "rudel",
1962
- version: "0.1.8",
1962
+ version: "0.1.10",
1963
1963
  type: "module",
1964
1964
  description: "CLI for the Coding Agent Analytics Platform rudel.ai",
1965
1965
  license: "MIT",
@@ -1991,7 +1991,7 @@ var package_default = {
1991
1991
  scripts: {
1992
1992
  dev: "bun run src/bin/cli.ts",
1993
1993
  "check-types": "tsc --noEmit",
1994
- build: "bun build src/bin/cli.ts --outdir dist --target bun",
1994
+ build: "bun build src/bin/cli.ts --outdir dist --target node",
1995
1995
  test: "bun test"
1996
1996
  },
1997
1997
  dependencies: {
@@ -2007,15 +2007,16 @@ var package_default = {
2007
2007
  "@rudel/agent-adapters": "workspace:*",
2008
2008
  "@rudel/api-routes": "workspace:*",
2009
2009
  "@rudel/typescript-config": "workspace:*",
2010
+ "@types/node": "^22",
2010
2011
  "bun-types": "latest",
2011
2012
  typescript: "5.9.2"
2012
2013
  }
2013
2014
  };
2014
2015
 
2015
2016
  // ../../packages/agent-adapters/src/adapters/claude-code/index.ts
2016
- import { readdir as readdir2, readFile as readFile2, stat } from "fs/promises";
2017
- import { homedir as homedir2 } from "os";
2018
- import { dirname as dirname2, join as join3 } from "path";
2017
+ import { readdir as readdir2, readFile as readFile2, stat } from "node:fs/promises";
2018
+ import { homedir as homedir2 } from "node:os";
2019
+ import { dirname as dirname2, join as join3 } from "node:path";
2019
2020
 
2020
2021
  // ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/external.js
2021
2022
  var exports_external = {};
@@ -6076,9 +6077,9 @@ async function ingestRudelCodexSessions(ingestor, rows, options) {
6076
6077
  await ingestor.insert({ table: "rudel.codex_sessions", values: data });
6077
6078
  }
6078
6079
  // ../../packages/agent-adapters/src/utils.ts
6079
- import { readdir, readFile } from "fs/promises";
6080
- import { homedir } from "os";
6081
- import { join } from "path";
6080
+ import { readdir, readFile } from "node:fs/promises";
6081
+ import { homedir } from "node:os";
6082
+ import { join } from "node:path";
6082
6083
  function toClickHouseDateTime(isoString) {
6083
6084
  return isoString.replace("T", " ").replace("Z", "").replace(/\+.*$/, "");
6084
6085
  }
@@ -6132,21 +6133,11 @@ function toDisplayPath(absolutePath) {
6132
6133
  const home = homedir();
6133
6134
  return absolutePath.startsWith(home) ? `~${absolutePath.slice(home.length)}` : absolutePath;
6134
6135
  }
6135
- function groupProjectsForCwd(projects, cwd) {
6136
- const current = projects.filter((p) => p.projectPath === cwd);
6137
- const currentSet = new Set(current);
6138
- const subfolders = projects.filter((p) => !currentSet.has(p) && p.projectPath.startsWith(`${cwd}/`));
6139
- const subfoldersSet = new Set(subfolders);
6140
- const others = projects.filter((p) => !currentSet.has(p) && !subfoldersSet.has(p));
6141
- subfolders.sort((a, b) => a.projectPath.localeCompare(b.projectPath));
6142
- others.sort((a, b) => a.displayPath.localeCompare(b.displayPath));
6143
- return { current, subfolders, others };
6144
- }
6145
6136
 
6146
6137
  // ../../packages/agent-adapters/src/adapters/claude-code/settings.ts
6147
- import { execSync } from "child_process";
6148
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
6149
- import { dirname, join as join2, resolve } from "path";
6138
+ import { execSync } from "node:child_process";
6139
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
6140
+ import { dirname, join as join2, resolve } from "node:path";
6150
6141
  var HOOK_COMMAND = "rudel hooks claude session-end";
6151
6142
  function findClaudeDir() {
6152
6143
  let dir = resolve(process.cwd());
@@ -6465,14 +6456,14 @@ class ClaudeCodeAdapter {
6465
6456
  }
6466
6457
  var claudeCodeAdapter = new ClaudeCodeAdapter;
6467
6458
  // ../../packages/agent-adapters/src/adapters/codex/index.ts
6468
- import { readFile as readFile3 } from "fs/promises";
6469
- import { homedir as homedir4 } from "os";
6470
- import { join as join5 } from "path";
6459
+ import { readFile as readFile3 } from "node:fs/promises";
6460
+ import { homedir as homedir4 } from "node:os";
6461
+ import { join as join5 } from "node:path";
6471
6462
 
6472
6463
  // ../../packages/agent-adapters/src/adapters/codex/config.ts
6473
- import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
6474
- import { homedir as homedir3 } from "os";
6475
- import { join as join4 } from "path";
6464
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
6465
+ import { homedir as homedir3 } from "node:os";
6466
+ import { join as join4 } from "node:path";
6476
6467
 
6477
6468
  // ../../node_modules/.bun/smol-toml@1.6.0/node_modules/smol-toml/dist/error.js
6478
6469
  /*!
@@ -6594,7 +6585,7 @@ function skipComment(str, ptr) {
6594
6585
  if (c === "\r" && str[i + 1] === `
6595
6586
  `)
6596
6587
  return i + 1;
6597
- if (c < " " && c !== "\t" || c === "\x7F") {
6588
+ if (c < " " && c !== "\t" || c === "") {
6598
6589
  throw new TomlError("control characters are not allowed in comments", {
6599
6590
  toml: str,
6600
6591
  ptr
@@ -6841,7 +6832,7 @@ function parseString(str, ptr = 0, endPtr = str.length) {
6841
6832
  ptr: ptr - 1
6842
6833
  });
6843
6834
  }
6844
- } else if (c < " " && c !== "\t" || c === "\x7F") {
6835
+ } else if (c < " " && c !== "\t" || c === "") {
6845
6836
  throw new TomlError("control characters are not allowed in strings", {
6846
6837
  toml: str,
6847
6838
  ptr: ptr - 1
@@ -7756,7 +7747,7 @@ class CodexAdapter {
7756
7747
  }
7757
7748
  var codexAdapter = new CodexAdapter;
7758
7749
  // ../../packages/agent-adapters/src/registry.ts
7759
- import { existsSync as existsSync3 } from "fs";
7750
+ import { existsSync as existsSync3 } from "node:fs";
7760
7751
  var adapters = new Map;
7761
7752
  function registerAdapter(adapter) {
7762
7753
  adapters.set(adapter.source, adapter);
@@ -7778,33 +7769,346 @@ function getAvailableAdapters() {
7778
7769
  registerAdapter(claudeCodeAdapter);
7779
7770
  registerAdapter(codexAdapter);
7780
7771
 
7781
- // src/commands/dev/list-sessions.ts
7782
- async function runListSessions() {
7772
+ // src/lib/project-grouping.ts
7773
+ import { homedir as homedir6 } from "node:os";
7774
+
7775
+ // src/lib/git-info.ts
7776
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
7777
+ import { join as join6 } from "node:path";
7778
+
7779
+ // src/lib/exec.ts
7780
+ import { execFile } from "node:child_process";
7781
+ function exec(cmd, args) {
7782
+ return new Promise((resolve2) => {
7783
+ execFile(cmd, args, { encoding: "utf8" }, (error, stdout, stderr) => {
7784
+ resolve2({
7785
+ exitCode: error ? error.code ?? 1 : 0,
7786
+ stdout,
7787
+ stderr
7788
+ });
7789
+ });
7790
+ });
7791
+ }
7792
+
7793
+ // src/lib/git-info.ts
7794
+ function normalizeRemoteUrl(url) {
7795
+ return url.replace(/^(https?:\/\/|git@|ssh:\/\/)/, "").replace(/:/, "/").replace(/\.git$/, "");
7796
+ }
7797
+ async function getGitInfo(cwd) {
7798
+ const [remoteUrl, branch, sha, packageInfo] = await Promise.all([
7799
+ getGitRemoteUrl(cwd),
7800
+ getGitBranch(cwd),
7801
+ getGitSha(cwd),
7802
+ getPackageInfo(cwd)
7803
+ ]);
7804
+ const gitRemote = remoteUrl ? normalizeRemoteUrl(remoteUrl) : undefined;
7805
+ return {
7806
+ gitRemote,
7807
+ packageName: packageInfo?.name,
7808
+ packageType: packageInfo?.type,
7809
+ branch: branch ?? undefined,
7810
+ sha: sha ?? undefined
7811
+ };
7812
+ }
7813
+ async function getPackageInfo(cwd) {
7814
+ try {
7815
+ const result = await exec("git", [
7816
+ "-C",
7817
+ cwd,
7818
+ "rev-parse",
7819
+ "--show-toplevel"
7820
+ ]);
7821
+ const root = result.exitCode === 0 ? result.stdout.trim() : cwd;
7822
+ return getNodePackage(root) ?? getPythonPackage(root) ?? getRustPackage(root) ?? getGoModule(root);
7823
+ } catch {
7824
+ return null;
7825
+ }
7826
+ }
7827
+ function getNodePackage(root) {
7828
+ try {
7829
+ const filePath = join6(root, "package.json");
7830
+ if (!existsSync4(filePath))
7831
+ return null;
7832
+ const pkg = JSON.parse(readFileSync3(filePath, "utf-8"));
7833
+ return pkg.name ? { name: pkg.name, type: "package.json" } : null;
7834
+ } catch {
7835
+ return null;
7836
+ }
7837
+ }
7838
+ function getPythonPackage(root) {
7839
+ try {
7840
+ const filePath = join6(root, "pyproject.toml");
7841
+ if (!existsSync4(filePath))
7842
+ return null;
7843
+ const content = readFileSync3(filePath, "utf-8");
7844
+ const name = content.match(/^\s*name\s*=\s*"([^"]+)"/m)?.[1];
7845
+ return name ? { name, type: "pyproject.toml" } : null;
7846
+ } catch {
7847
+ return null;
7848
+ }
7849
+ }
7850
+ function getRustPackage(root) {
7851
+ try {
7852
+ const filePath = join6(root, "Cargo.toml");
7853
+ if (!existsSync4(filePath))
7854
+ return null;
7855
+ const content = readFileSync3(filePath, "utf-8");
7856
+ const name = content.match(/^\s*name\s*=\s*"([^"]+)"/m)?.[1];
7857
+ return name ? { name, type: "Cargo.toml" } : null;
7858
+ } catch {
7859
+ return null;
7860
+ }
7861
+ }
7862
+ function getGoModule(root) {
7863
+ try {
7864
+ const filePath = join6(root, "go.mod");
7865
+ if (!existsSync4(filePath))
7866
+ return null;
7867
+ const content = readFileSync3(filePath, "utf-8");
7868
+ const name = content.match(/^module\s+(\S+)/m)?.[1];
7869
+ return name ? { name, type: "go.mod" } : null;
7870
+ } catch {
7871
+ return null;
7872
+ }
7873
+ }
7874
+ async function getGitRemoteUrl(cwd) {
7875
+ try {
7876
+ const result = await exec("git", [
7877
+ "-C",
7878
+ cwd,
7879
+ "remote",
7880
+ "get-url",
7881
+ "origin"
7882
+ ]);
7883
+ if (result.exitCode !== 0)
7884
+ return null;
7885
+ return result.stdout.trim() || null;
7886
+ } catch {
7887
+ return null;
7888
+ }
7889
+ }
7890
+ async function getGitBranch(cwd) {
7891
+ try {
7892
+ const result = await exec("git", [
7893
+ "-C",
7894
+ cwd,
7895
+ "rev-parse",
7896
+ "--abbrev-ref",
7897
+ "HEAD"
7898
+ ]);
7899
+ if (result.exitCode !== 0)
7900
+ return null;
7901
+ return result.stdout.trim();
7902
+ } catch {
7903
+ return null;
7904
+ }
7905
+ }
7906
+ async function getGitSha(cwd) {
7907
+ try {
7908
+ const result = await exec("git", ["-C", cwd, "rev-parse", "HEAD"]);
7909
+ if (result.exitCode !== 0)
7910
+ return null;
7911
+ return result.stdout.trim();
7912
+ } catch {
7913
+ return null;
7914
+ }
7915
+ }
7916
+
7917
+ // src/lib/remote-cache.ts
7918
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
7919
+ import { mkdir, writeFile } from "node:fs/promises";
7920
+ import { homedir as homedir5 } from "node:os";
7921
+ import { dirname as dirname3, join as join7 } from "node:path";
7922
+ var CACHE_PATH = join7(homedir5(), ".rudel", "remote-cache.json");
7923
+ async function getRemoteCache() {
7924
+ try {
7925
+ if (!existsSync5(CACHE_PATH))
7926
+ return {};
7927
+ return JSON.parse(readFileSync4(CACHE_PATH, "utf-8"));
7928
+ } catch {
7929
+ return {};
7930
+ }
7931
+ }
7932
+ function getCachedRemote(cache, encodedDir) {
7933
+ return cache[encodedDir] ?? null;
7934
+ }
7935
+ function cacheRemote(cache, encodedDir, normalizedRemote) {
7936
+ cache[encodedDir] = normalizedRemote;
7937
+ }
7938
+ async function cacheRemotes(cache) {
7939
+ try {
7940
+ await mkdir(dirname3(CACHE_PATH), { recursive: true });
7941
+ await writeFile(CACHE_PATH, JSON.stringify(cache));
7942
+ } catch {}
7943
+ }
7944
+
7945
+ // src/lib/project-grouping.ts
7946
+ async function scanAndGroupProjects(cwd = process.cwd()) {
7783
7947
  const adapters2 = getAvailableAdapters();
7784
- const allProjects = [];
7948
+ const projects = [];
7785
7949
  for (const adapter of adapters2) {
7786
- const projects = await adapter.scanAllSessions();
7787
- allProjects.push(...projects);
7950
+ const scanned = await adapter.scanAllSessions();
7951
+ projects.push(...scanned);
7952
+ }
7953
+ const groups = await groupProjectsByRemote(projects, cwd);
7954
+ return { projects, groups };
7955
+ }
7956
+ function extractDisplayName(normalized) {
7957
+ const parts = normalized.split("/");
7958
+ if (parts.length >= 3) {
7959
+ return parts.slice(1).join("/");
7960
+ }
7961
+ return normalized;
7962
+ }
7963
+ function encodeProjectPath2(projectPath) {
7964
+ return projectPath.replace(/\//g, "-");
7965
+ }
7966
+ async function groupProjectsByRemote(projects, cwd) {
7967
+ const cache = await getRemoteCache();
7968
+ let cacheUpdated = false;
7969
+ const remotes = await Promise.all(projects.map((p) => getGitRemoteUrl(p.projectPath)));
7970
+ const grouped = new Map;
7971
+ const ungrouped = [];
7972
+ for (let i = 0;i < projects.length; i++) {
7973
+ const project = projects[i];
7974
+ const remote = remotes[i];
7975
+ if (remote) {
7976
+ const normalized = normalizeRemoteUrl(remote);
7977
+ const encodedDir = encodeProjectPath2(project.projectPath);
7978
+ if (getCachedRemote(cache, encodedDir) !== normalized) {
7979
+ cacheRemote(cache, encodedDir, normalized);
7980
+ cacheUpdated = true;
7981
+ }
7982
+ const existing = grouped.get(normalized);
7983
+ if (existing) {
7984
+ existing.projects.push(project);
7985
+ } else {
7986
+ grouped.set(normalized, { remote: normalized, projects: [project] });
7987
+ }
7988
+ } else {
7989
+ const encodedDir = encodeProjectPath2(project.projectPath);
7990
+ const cached = getCachedRemote(cache, encodedDir);
7991
+ if (cached) {
7992
+ const existing = grouped.get(cached);
7993
+ if (existing) {
7994
+ existing.projects.push(project);
7995
+ } else {
7996
+ grouped.set(cached, { remote: cached, projects: [project] });
7997
+ }
7998
+ } else {
7999
+ ungrouped.push(project);
8000
+ }
8001
+ }
8002
+ }
8003
+ const groups = [];
8004
+ for (const [, entry] of grouped) {
8005
+ const containsCwd = entry.projects.some((p) => cwd === p.projectPath || cwd.startsWith(`${p.projectPath}/`));
8006
+ groups.push({
8007
+ displayName: extractDisplayName(entry.remote),
8008
+ gitRemote: entry.remote,
8009
+ projects: entry.projects,
8010
+ totalSessions: entry.projects.reduce((s, p) => s + p.sessionCount, 0),
8011
+ containsCwd
8012
+ });
8013
+ }
8014
+ const homeSegments = homedir6().split("/").length;
8015
+ const remainingUngrouped = [];
8016
+ for (const project of ungrouped) {
8017
+ const match = findBestGroupByPath(project, groups, homeSegments);
8018
+ if (match) {
8019
+ match.projects.push(project);
8020
+ match.totalSessions += project.sessionCount;
8021
+ if (cwd === project.projectPath || cwd.startsWith(`${project.projectPath}/`)) {
8022
+ match.containsCwd = true;
8023
+ }
8024
+ } else {
8025
+ remainingUngrouped.push(project);
8026
+ }
8027
+ }
8028
+ for (const project of remainingUngrouped) {
8029
+ const containsCwd = cwd === project.projectPath || cwd.startsWith(`${project.projectPath}/`);
8030
+ groups.push({
8031
+ displayName: project.displayPath,
8032
+ gitRemote: null,
8033
+ projects: [project],
8034
+ totalSessions: project.sessionCount,
8035
+ containsCwd
8036
+ });
8037
+ }
8038
+ groups.sort((a, b) => {
8039
+ if (a.containsCwd !== b.containsCwd)
8040
+ return a.containsCwd ? -1 : 1;
8041
+ const aHasRemote = a.gitRemote !== null;
8042
+ const bHasRemote = b.gitRemote !== null;
8043
+ if (aHasRemote !== bHasRemote)
8044
+ return aHasRemote ? -1 : 1;
8045
+ return a.displayName.localeCompare(b.displayName);
8046
+ });
8047
+ if (cacheUpdated) {
8048
+ cacheRemotes(cache);
8049
+ }
8050
+ return groups;
8051
+ }
8052
+ function commonPrefixLength(a, b) {
8053
+ const partsA = a.split("/");
8054
+ const partsB = b.split("/");
8055
+ let count = 0;
8056
+ for (let i = 0;i < Math.min(partsA.length, partsB.length); i++) {
8057
+ if (partsA[i] === partsB[i])
8058
+ count++;
8059
+ else
8060
+ break;
8061
+ }
8062
+ return count;
8063
+ }
8064
+ function findBestGroupByPath(project, groups, homeSegments) {
8065
+ let bestGroup = null;
8066
+ let bestLen = 0;
8067
+ let secondBestLen = 0;
8068
+ for (const group2 of groups) {
8069
+ if (!group2.gitRemote)
8070
+ continue;
8071
+ let groupBest = 0;
8072
+ for (const p of group2.projects) {
8073
+ groupBest = Math.max(groupBest, commonPrefixLength(project.projectPath, p.projectPath));
8074
+ }
8075
+ if (groupBest > bestLen) {
8076
+ secondBestLen = bestLen;
8077
+ bestLen = groupBest;
8078
+ bestGroup = group2;
8079
+ } else if (groupBest > secondBestLen) {
8080
+ secondBestLen = groupBest;
8081
+ }
7788
8082
  }
8083
+ if (bestGroup && bestLen > homeSegments && bestLen > secondBestLen) {
8084
+ return bestGroup;
8085
+ }
8086
+ return null;
8087
+ }
8088
+
8089
+ // src/commands/dev/list-sessions.ts
8090
+ async function runListSessions() {
8091
+ const { projects: allProjects, groups } = await scanAndGroupProjects();
7789
8092
  if (allProjects.length === 0) {
7790
8093
  console.log("No projects with sessions found.");
7791
8094
  return;
7792
8095
  }
7793
- const cwd = process.cwd();
7794
- const grouped = groupProjectsForCwd(allProjects, cwd);
7795
8096
  const lines = [];
7796
- for (const proj of grouped.current) {
7797
- const name = getAdapter(proj.source).name;
7798
- lines.push(`[${name}] ${proj.displayPath} (${proj.sessionCount} sessions) [current]`);
7799
- }
7800
- for (const sub of grouped.subfolders) {
7801
- const name = getAdapter(sub.source).name;
7802
- const relative = sub.projectPath.slice(cwd.length + 1);
7803
- lines.push(` [${name}] ${relative} (${sub.sessionCount} sessions)`);
7804
- }
7805
- for (const other of grouped.others) {
7806
- const name = getAdapter(other.source).name;
7807
- lines.push(`[${name}] ${other.displayPath} (${other.sessionCount} sessions)`);
8097
+ for (const group2 of groups) {
8098
+ const isCurrent = group2.containsCwd ? " [current]" : "";
8099
+ if (group2.projects.length === 1) {
8100
+ const proj = group2.projects[0];
8101
+ const name = getAdapter(proj.source).name;
8102
+ lines.push(`[${name}] ${proj.displayPath} (${proj.sessionCount} sessions)${isCurrent}`);
8103
+ continue;
8104
+ }
8105
+ const totalSessions2 = group2.projects.reduce((s, p) => s + p.sessionCount, 0);
8106
+ lines.push(`${group2.displayName} (${group2.projects.length} projects, ${totalSessions2} sessions)${isCurrent}`);
8107
+ for (const proj of group2.projects) {
8108
+ const name = getAdapter(proj.source).name;
8109
+ const cwdMarker = proj.projectPath === process.cwd() ? " [cwd]" : "";
8110
+ lines.push(` [${name}] ${proj.displayPath} (${proj.sessionCount} sessions)${cwdMarker}`);
8111
+ }
7808
8112
  }
7809
8113
  const totalSessions = allProjects.reduce((s, p) => s + p.sessionCount, 0);
7810
8114
  console.log(`${allProjects.length} projects, ${totalSessions} sessions
@@ -7834,10 +8138,10 @@ var devRouteMap = buildRouteMap({
7834
8138
  // ../../node_modules/.bun/@clack+core@1.0.1/node_modules/@clack/core/dist/index.mjs
7835
8139
  var import_picocolors = __toESM(require_picocolors(), 1);
7836
8140
  var import_sisteransi = __toESM(require_src(), 1);
7837
- import { stdout as R, stdin as q } from "process";
7838
- import * as k from "readline";
7839
- import ot from "readline";
7840
- import { ReadStream as J } from "tty";
8141
+ import { stdout as R, stdin as q } from "node:process";
8142
+ import * as k from "node:readline";
8143
+ import ot from "node:readline";
8144
+ import { ReadStream as J } from "node:tty";
7841
8145
  function B(t, e, s) {
7842
8146
  if (!s.some((u) => !u.disabled))
7843
8147
  return t;
@@ -7921,7 +8225,7 @@ var X = (t, e = {}, s = {}) => {
7921
8225
  var pt = { limit: 1 / 0, ellipsis: "", ellipsisWidth: 0 };
7922
8226
  var S = (t, e = {}) => X(t, pt, e).width;
7923
8227
  var W = "\x1B";
7924
- var Z = "\x9B";
8228
+ var Z = "›";
7925
8229
  var Ft = 39;
7926
8230
  var j = "\x07";
7927
8231
  var Q = "[";
@@ -8242,7 +8546,7 @@ class Vt extends x {
8242
8546
  if (!this.userInput)
8243
8547
  return import_picocolors.default.inverse(import_picocolors.default.hidden("_"));
8244
8548
  if (this._cursor >= this.userInput.length)
8245
- return `${this.userInput}\u2588`;
8549
+ return `${this.userInput}█`;
8246
8550
  const e = this.userInput.slice(0, this._cursor), [s, ...i] = this.userInput.slice(this._cursor);
8247
8551
  return `${e}${import_picocolors.default.inverse(s)}${i.join("")}`;
8248
8552
  }
@@ -8430,7 +8734,7 @@ class Wt extends x {
8430
8734
 
8431
8735
  // ../../node_modules/.bun/@clack+prompts@1.0.1/node_modules/@clack/prompts/dist/index.mjs
8432
8736
  var import_picocolors2 = __toESM(require_picocolors(), 1);
8433
- import N2 from "process";
8737
+ import N2 from "node:process";
8434
8738
  var import_sisteransi2 = __toESM(require_src(), 1);
8435
8739
  function me() {
8436
8740
  return N2.platform !== "win32" ? N2.env.TERM !== "linux" : !!N2.env.CI || !!N2.env.WT_SESSION || !!N2.env.TERMINUS_SUBLIME || N2.env.ConEmuTask === "{cmd::Cmder}" || N2.env.TERM_PROGRAM === "Terminus-Sublime" || N2.env.TERM_PROGRAM === "vscode" || N2.env.TERM === "xterm-256color" || N2.env.TERM === "alacritty" || N2.env.TERMINAL_EMULATOR === "JetBrains-JediTerm";
@@ -8438,31 +8742,31 @@ function me() {
8438
8742
  var et2 = me();
8439
8743
  var ct2 = () => process.env.CI === "true";
8440
8744
  var C = (t, r) => et2 ? t : r;
8441
- var Rt = C("\u25C6", "*");
8442
- var dt2 = C("\u25A0", "x");
8443
- var $t2 = C("\u25B2", "x");
8444
- var V = C("\u25C7", "o");
8445
- var ht2 = C("\u250C", "T");
8446
- var d = C("\u2502", "|");
8447
- var x2 = C("\u2514", "\u2014");
8448
- var Ot = C("\u2510", "T");
8449
- var Pt = C("\u2518", "\u2014");
8450
- var Q2 = C("\u25CF", ">");
8451
- var H2 = C("\u25CB", " ");
8452
- var st2 = C("\u25FB", "[\u2022]");
8453
- var U2 = C("\u25FC", "[+]");
8454
- var q2 = C("\u25FB", "[ ]");
8455
- var Nt = C("\u25AA", "\u2022");
8456
- var rt2 = C("\u2500", "-");
8457
- var mt2 = C("\u256E", "+");
8458
- var Wt2 = C("\u251C", "+");
8459
- var pt2 = C("\u256F", "+");
8460
- var gt2 = C("\u2570", "+");
8461
- var Lt2 = C("\u256D", "+");
8462
- var ft2 = C("\u25CF", "\u2022");
8463
- var Ft2 = C("\u25C6", "*");
8464
- var yt2 = C("\u25B2", "!");
8465
- var Et2 = C("\u25A0", "x");
8745
+ var Rt = C("", "*");
8746
+ var dt2 = C("", "x");
8747
+ var $t2 = C("", "x");
8748
+ var V = C("", "o");
8749
+ var ht2 = C("", "T");
8750
+ var d = C("", "|");
8751
+ var x2 = C("", "");
8752
+ var Ot = C("", "T");
8753
+ var Pt = C("", "");
8754
+ var Q2 = C("", ">");
8755
+ var H2 = C("", " ");
8756
+ var st2 = C("", "[]");
8757
+ var U2 = C("", "[+]");
8758
+ var q2 = C("", "[ ]");
8759
+ var Nt = C("", "");
8760
+ var rt2 = C("", "-");
8761
+ var mt2 = C("", "+");
8762
+ var Wt2 = C("", "+");
8763
+ var pt2 = C("", "+");
8764
+ var gt2 = C("", "+");
8765
+ var Lt2 = C("", "+");
8766
+ var ft2 = C("", "");
8767
+ var Ft2 = C("", "*");
8768
+ var yt2 = C("", "!");
8769
+ var Et2 = C("", "x");
8466
8770
  var W2 = (t) => {
8467
8771
  switch (t) {
8468
8772
  case "initial":
@@ -8566,7 +8870,7 @@ var jt = (t, r = {}, s = {}) => {
8566
8870
  var Ee = { limit: 1 / 0, ellipsis: "", ellipsisWidth: 0 };
8567
8871
  var M2 = (t, r = {}) => jt(t, Ee, r).width;
8568
8872
  var ot2 = "\x1B";
8569
- var Gt = "\x9B";
8873
+ var Gt = "›";
8570
8874
  var ve = 39;
8571
8875
  var Ct2 = "\x07";
8572
8876
  var kt2 = "[";
@@ -8866,7 +9170,7 @@ ${import_picocolors2.default.cyan(x2)}
8866
9170
  } }).prompt();
8867
9171
  };
8868
9172
  var Ke = import_picocolors2.default.magenta;
8869
- var bt2 = ({ indicator: t = "dots", onCancel: r, output: s = process.stdout, cancelMessage: i, errorMessage: a, frames: o = et2 ? ["\u25D2", "\u25D0", "\u25D3", "\u25D1"] : ["\u2022", "o", "O", "0"], delay: u = et2 ? 80 : 120, signal: l, ...n } = {}) => {
9173
+ var bt2 = ({ indicator: t = "dots", onCancel: r, output: s = process.stdout, cancelMessage: i, errorMessage: a, frames: o = et2 ? ["", "", "", ""] : ["", "o", "O", "0"], delay: u = et2 ? 80 : 120, signal: l, ...n } = {}) => {
8870
9174
  const c = ct2();
8871
9175
  let g, F, p = false, E = false, $ = "", m, h = performance.now();
8872
9176
  const y2 = rt(s), f = n?.styleFrame ?? Ke, v = (b) => {
@@ -8923,7 +9227,7 @@ var bt2 = ({ indicator: t = "dots", onCancel: r, output: s = process.stdout, can
8923
9227
  return E;
8924
9228
  } };
8925
9229
  };
8926
- var zt = { light: C("\u2500", "-"), heavy: C("\u2501", "="), block: C("\u2588", "#") };
9230
+ var zt = { light: C("", "-"), heavy: C("", "="), block: C("", "#") };
8927
9231
  function qe({ style: t = "heavy", max: r = 100, size: s = 40, ...i } = {}) {
8928
9232
  const a = bt2(i);
8929
9233
  let o = 0, u = "";
@@ -10499,23 +10803,23 @@ function createApiClient(config) {
10499
10803
 
10500
10804
  // src/lib/credentials.ts
10501
10805
  import {
10502
- existsSync as existsSync4,
10806
+ existsSync as existsSync6,
10503
10807
  mkdirSync as mkdirSync2,
10504
- readFileSync as readFileSync3,
10808
+ readFileSync as readFileSync5,
10505
10809
  rmSync,
10506
10810
  writeFileSync as writeFileSync3
10507
- } from "fs";
10508
- import { homedir as homedir5 } from "os";
10509
- import { join as join6 } from "path";
10811
+ } from "node:fs";
10812
+ import { homedir as homedir7 } from "node:os";
10813
+ import { join as join8 } from "node:path";
10510
10814
  function getConfigDir() {
10511
- return process.env.RUDEL_CONFIG_DIR ?? join6(homedir5(), ".rudel");
10815
+ return process.env.RUDEL_CONFIG_DIR ?? join8(homedir7(), ".rudel");
10512
10816
  }
10513
10817
  function getCredentialsPath() {
10514
- return join6(getConfigDir(), "credentials.json");
10818
+ return join8(getConfigDir(), "credentials.json");
10515
10819
  }
10516
10820
  function saveCredentials(token, apiBaseUrl) {
10517
10821
  const dir = getConfigDir();
10518
- if (!existsSync4(dir)) {
10822
+ if (!existsSync6(dir)) {
10519
10823
  mkdirSync2(dir, { recursive: true, mode: 448 });
10520
10824
  }
10521
10825
  const data = { token, apiBaseUrl };
@@ -10525,14 +10829,14 @@ function saveCredentials(token, apiBaseUrl) {
10525
10829
  }
10526
10830
  function loadCredentials() {
10527
10831
  const path = getCredentialsPath();
10528
- if (!existsSync4(path))
10832
+ if (!existsSync6(path))
10529
10833
  return null;
10530
- const content = readFileSync3(path, "utf-8");
10834
+ const content = readFileSync5(path, "utf-8");
10531
10835
  return JSON.parse(content);
10532
10836
  }
10533
10837
  function clearCredentials() {
10534
10838
  const path = getCredentialsPath();
10535
- if (existsSync4(path)) {
10839
+ if (existsSync6(path)) {
10536
10840
  rmSync(path);
10537
10841
  }
10538
10842
  }
@@ -10694,8 +10998,10 @@ async function pMap(iterable, mapper, {
10694
10998
  var pMapSkip = Symbol("skip");
10695
10999
 
10696
11000
  // src/lib/failed-uploads.ts
10697
- import { homedir as homedir6 } from "os";
10698
- import { join as join7 } from "path";
11001
+ import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
11002
+ import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
11003
+ import { homedir as homedir8 } from "node:os";
11004
+ import { dirname as dirname4, join as join9 } from "node:path";
10699
11005
 
10700
11006
  // ../../node_modules/.bun/@orpc+contract@1.13.6/node_modules/@orpc/contract/dist/shared/contract.D_dZrO__.mjs
10701
11007
  function mergeErrorMap(errorMap1, errorMap2) {
@@ -11393,7 +11699,7 @@ var contract = {
11393
11699
  };
11394
11700
 
11395
11701
  // src/lib/failed-uploads.ts
11396
- var FAILED_UPLOADS_PATH = join7(homedir6(), ".rudel", "failed-uploads.json");
11702
+ var FAILED_UPLOADS_PATH = join9(homedir8(), ".rudel", "failed-uploads.json");
11397
11703
  function normalizeSource(raw) {
11398
11704
  if (typeof raw !== "string")
11399
11705
  return;
@@ -11403,10 +11709,9 @@ function normalizeSource(raw) {
11403
11709
  }
11404
11710
  async function loadFailedUploads() {
11405
11711
  try {
11406
- const file = Bun.file(FAILED_UPLOADS_PATH);
11407
- if (!await file.exists())
11712
+ if (!existsSync7(FAILED_UPLOADS_PATH))
11408
11713
  return [];
11409
- const data = await file.json();
11714
+ const data = JSON.parse(readFileSync6(FAILED_UPLOADS_PATH, "utf-8"));
11410
11715
  return data.failures.map((f) => ({
11411
11716
  ...f,
11412
11717
  source: normalizeSource(f.source)
@@ -11417,11 +11722,9 @@ async function loadFailedUploads() {
11417
11722
  }
11418
11723
  async function saveFailedUploads(failures) {
11419
11724
  try {
11420
- const { mkdir } = await import("fs/promises");
11421
- const { dirname: dirname3 } = await import("path");
11422
- await mkdir(dirname3(FAILED_UPLOADS_PATH), { recursive: true });
11725
+ await mkdir2(dirname4(FAILED_UPLOADS_PATH), { recursive: true });
11423
11726
  const data = { failures };
11424
- await Bun.write(FAILED_UPLOADS_PATH, JSON.stringify(data, null, 2));
11727
+ await writeFile2(FAILED_UPLOADS_PATH, JSON.stringify(data, null, 2));
11425
11728
  } catch {}
11426
11729
  }
11427
11730
  async function recordFailedUpload(failure) {
@@ -11541,136 +11844,26 @@ function renderBatchSummary(summary, options) {
11541
11844
  }
11542
11845
  }
11543
11846
 
11544
- // src/lib/git-info.ts
11545
- import { join as join8 } from "path";
11546
- var {$ } = globalThis.Bun;
11547
- function normalizeRemoteUrl(url) {
11548
- return url.replace(/^(https?:\/\/|git@|ssh:\/\/)/, "").replace(/:/, "/").replace(/\.git$/, "");
11549
- }
11550
- async function getGitInfo(cwd) {
11551
- const [remoteUrl, branch, sha, packageInfo] = await Promise.all([
11552
- getGitRemoteUrl(cwd),
11553
- getGitBranch(cwd),
11554
- getGitSha(cwd),
11555
- getPackageInfo(cwd)
11556
- ]);
11557
- const gitRemote = remoteUrl ? normalizeRemoteUrl(remoteUrl) : undefined;
11558
- return {
11559
- gitRemote,
11560
- packageName: packageInfo?.name,
11561
- packageType: packageInfo?.type,
11562
- branch: branch ?? undefined,
11563
- sha: sha ?? undefined
11564
- };
11565
- }
11566
- async function getPackageInfo(cwd) {
11567
- try {
11568
- const gitRootResult = await $`git -C ${cwd} rev-parse --show-toplevel`.quiet();
11569
- const root = gitRootResult.exitCode === 0 ? gitRootResult.text().trim() : cwd;
11570
- return await getNodePackage(root) ?? await getPythonPackage(root) ?? await getRustPackage(root) ?? await getGoModule(root);
11571
- } catch {
11572
- return null;
11573
- }
11574
- }
11575
- async function getNodePackage(root) {
11576
- try {
11577
- const file = Bun.file(join8(root, "package.json"));
11578
- if (!await file.exists())
11579
- return null;
11580
- const pkg = await file.json();
11581
- return pkg.name ? { name: pkg.name, type: "package.json" } : null;
11582
- } catch {
11583
- return null;
11584
- }
11585
- }
11586
- async function getPythonPackage(root) {
11587
- try {
11588
- const file = Bun.file(join8(root, "pyproject.toml"));
11589
- if (!await file.exists())
11590
- return null;
11591
- const content = await file.text();
11592
- const name = content.match(/^\s*name\s*=\s*"([^"]+)"/m)?.[1];
11593
- return name ? { name, type: "pyproject.toml" } : null;
11594
- } catch {
11595
- return null;
11596
- }
11597
- }
11598
- async function getRustPackage(root) {
11599
- try {
11600
- const file = Bun.file(join8(root, "Cargo.toml"));
11601
- if (!await file.exists())
11602
- return null;
11603
- const content = await file.text();
11604
- const name = content.match(/^\s*name\s*=\s*"([^"]+)"/m)?.[1];
11605
- return name ? { name, type: "Cargo.toml" } : null;
11606
- } catch {
11607
- return null;
11608
- }
11609
- }
11610
- async function getGoModule(root) {
11611
- try {
11612
- const file = Bun.file(join8(root, "go.mod"));
11613
- if (!await file.exists())
11614
- return null;
11615
- const content = await file.text();
11616
- const name = content.match(/^module\s+(\S+)/m)?.[1];
11617
- return name ? { name, type: "go.mod" } : null;
11618
- } catch {
11619
- return null;
11620
- }
11621
- }
11622
- async function getGitRemoteUrl(cwd) {
11623
- try {
11624
- const result = await $`git -C ${cwd} remote get-url origin`.quiet();
11625
- if (result.exitCode !== 0)
11626
- return null;
11627
- return result.text().trim() || null;
11628
- } catch {
11629
- return null;
11630
- }
11631
- }
11632
- async function getGitBranch(cwd) {
11633
- try {
11634
- const result = await $`git -C ${cwd} rev-parse --abbrev-ref HEAD`.quiet();
11635
- if (result.exitCode !== 0)
11636
- return null;
11637
- return result.text().trim();
11638
- } catch {
11639
- return null;
11640
- }
11641
- }
11642
- async function getGitSha(cwd) {
11643
- try {
11644
- const result = await $`git -C ${cwd} rev-parse HEAD`.quiet();
11645
- if (result.exitCode !== 0)
11646
- return null;
11647
- return result.text().trim();
11648
- } catch {
11649
- return null;
11650
- }
11651
- }
11652
-
11653
11847
  // src/lib/project-config.ts
11654
- import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
11655
- import { homedir as homedir7 } from "os";
11656
- import { join as join9 } from "path";
11657
- var {$: $2 } = globalThis.Bun;
11848
+ import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
11849
+ import { homedir as homedir9 } from "node:os";
11850
+ import { join as join10 } from "node:path";
11658
11851
  function getConfigDir2() {
11659
- return process.env.RUDEL_CONFIG_DIR ?? join9(homedir7(), ".rudel");
11852
+ return process.env.RUDEL_CONFIG_DIR ?? join10(homedir9(), ".rudel");
11660
11853
  }
11661
11854
  function getProjectsConfigPath() {
11662
- return join9(getConfigDir2(), "projects.json");
11855
+ return join10(getConfigDir2(), "projects.json");
11663
11856
  }
11664
11857
  function loadProjectsConfig() {
11665
11858
  const path = getProjectsConfigPath();
11666
- if (!existsSync5(path))
11859
+ if (!existsSync8(path))
11667
11860
  return { projects: {} };
11668
- const content = readFileSync4(path, "utf-8");
11861
+ const content = readFileSync7(path, "utf-8");
11669
11862
  return JSON.parse(content);
11670
11863
  }
11671
11864
  function saveProjectsConfig(config) {
11672
11865
  const dir = getConfigDir2();
11673
- if (!existsSync5(dir)) {
11866
+ if (!existsSync8(dir)) {
11674
11867
  mkdirSync3(dir, { recursive: true, mode: 448 });
11675
11868
  }
11676
11869
  writeFileSync4(getProjectsConfigPath(), JSON.stringify(config, null, 2), {
@@ -11679,16 +11872,27 @@ function saveProjectsConfig(config) {
11679
11872
  }
11680
11873
  async function getProjectKey(cwd) {
11681
11874
  try {
11682
- const result = await $2`git -C ${cwd} remote get-url origin`.quiet();
11875
+ const result = await exec("git", [
11876
+ "-C",
11877
+ cwd,
11878
+ "remote",
11879
+ "get-url",
11880
+ "origin"
11881
+ ]);
11683
11882
  if (result.exitCode === 0) {
11684
- const url = result.text().trim();
11883
+ const url = result.stdout.trim();
11685
11884
  return url.replace(/^(https?:\/\/|git@|ssh:\/\/)/, "").replace(/:/, "/").replace(/\.git$/, "");
11686
11885
  }
11687
11886
  } catch {}
11688
11887
  try {
11689
- const result = await $2`git -C ${cwd} rev-parse --show-toplevel`.quiet();
11888
+ const result = await exec("git", [
11889
+ "-C",
11890
+ cwd,
11891
+ "rev-parse",
11892
+ "--show-toplevel"
11893
+ ]);
11690
11894
  if (result.exitCode === 0) {
11691
- return result.text().trim();
11895
+ return result.stdout.trim();
11692
11896
  }
11693
11897
  } catch {}
11694
11898
  return cwd;
@@ -12896,7 +13100,7 @@ var exports_util_node = {};
12896
13100
  __export(exports_util_node, {
12897
13101
  inspect: () => inspect
12898
13102
  });
12899
- import util3 from "util";
13103
+ import util3 from "node:util";
12900
13104
  function inspect(obj, options) {
12901
13105
  return util3.inspect(obj, options);
12902
13106
  }
@@ -13072,7 +13276,7 @@ function getTextFormatter(options = {}) {
13072
13276
  else
13073
13277
  return tsOption;
13074
13278
  })();
13075
- const categorySeparator = options.category ?? "\xB7";
13279
+ const categorySeparator = options.category ?? "·";
13076
13280
  const valueRenderer = options.value ? (v) => options.value(v, inspect2) : inspect2;
13077
13281
  const levelRenderer = (() => {
13078
13282
  const levelOption = options.level;
@@ -13292,7 +13496,7 @@ function defaultConsoleFormatter(record) {
13292
13496
  const date = new Date(record.timestamp);
13293
13497
  const time = `${date.getUTCHours().toString().padStart(2, "0")}:${date.getUTCMinutes().toString().padStart(2, "0")}:${date.getUTCSeconds().toString().padStart(2, "0")}.${date.getUTCMilliseconds().toString().padStart(3, "0")}`;
13294
13498
  return [
13295
- `%c${time} %c${levelAbbreviations[record.level]}%c %c${record.category.join("\xB7")} %c${msg}`,
13499
+ `%c${time} %c${levelAbbreviations[record.level]}%c %c${record.category.join("·")} %c${msg}`,
13296
13500
  "color: gray;",
13297
13501
  logLevelStyles[record.level],
13298
13502
  "background-color: default;",
@@ -13503,8 +13707,8 @@ var ConfigError = class extends Error {
13503
13707
  };
13504
13708
 
13505
13709
  // src/logging.ts
13506
- import { homedir as homedir8 } from "os";
13507
- import { join as join11 } from "path";
13710
+ import { homedir as homedir10 } from "node:os";
13711
+ import { join as join12 } from "node:path";
13508
13712
 
13509
13713
  // ../../node_modules/.bun/@logtape+file@2.0.4+2d5de51540e256d2/node_modules/@logtape/file/dist/filesink.base.js
13510
13714
  var AdaptiveFlushStrategy = class {
@@ -13769,9 +13973,9 @@ function getBaseFileSink(path, options) {
13769
13973
  }
13770
13974
 
13771
13975
  // ../../node_modules/.bun/@logtape+file@2.0.4+2d5de51540e256d2/node_modules/@logtape/file/dist/filesink.node.js
13772
- import fs from "fs";
13773
- import { join as join10 } from "path";
13774
- import { promisify } from "util";
13976
+ import fs from "node:fs";
13977
+ import { join as join11 } from "node:path";
13978
+ import { promisify } from "node:util";
13775
13979
  var nodeDriver = {
13776
13980
  openSync(path) {
13777
13981
  return fs.openSync(path, "a");
@@ -13810,14 +14014,14 @@ var nodeTimeDriver = {
13810
14014
  readdirSync: fs.readdirSync,
13811
14015
  unlinkSync: fs.unlinkSync,
13812
14016
  mkdirSync: fs.mkdirSync,
13813
- joinPath: join10
14017
+ joinPath: join11
13814
14018
  };
13815
14019
  var nodeAsyncTimeDriver = {
13816
14020
  ...nodeAsyncDriver,
13817
14021
  readdirSync: fs.readdirSync,
13818
14022
  unlinkSync: fs.unlinkSync,
13819
14023
  mkdirSync: fs.mkdirSync,
13820
- joinPath: join10
14024
+ joinPath: join11
13821
14025
  };
13822
14026
  function getFileSink(path, options = {}) {
13823
14027
  if (options.nonBlocking)
@@ -13832,11 +14036,11 @@ function getFileSink(path, options = {}) {
13832
14036
  }
13833
14037
 
13834
14038
  // src/logging.ts
13835
- var LOG_DIR = join11(homedir8(), ".rudel", "logs");
13836
- var LOG_FILE = join11(LOG_DIR, "hook-upload.log");
14039
+ var LOG_DIR = join12(homedir10(), ".rudel", "logs");
14040
+ var LOG_FILE = join12(LOG_DIR, "hook-upload.log");
13837
14041
  async function setupHookLogging() {
13838
- const { mkdir } = await import("fs/promises");
13839
- await mkdir(LOG_DIR, { recursive: true }).catch(() => {});
14042
+ const { mkdir: mkdir3 } = await import("node:fs/promises");
14043
+ await mkdir3(LOG_DIR, { recursive: true }).catch(() => {});
13840
14044
  await configure({
13841
14045
  sinks: {
13842
14046
  file: getFileSink(LOG_FILE)
@@ -14019,7 +14223,11 @@ var hooksRouteMap = buildRouteMap({
14019
14223
  });
14020
14224
 
14021
14225
  // src/commands/login.ts
14022
- import { randomBytes } from "crypto";
14226
+ import { spawn } from "node:child_process";
14227
+ import { randomBytes } from "node:crypto";
14228
+ import {
14229
+ createServer
14230
+ } from "node:http";
14023
14231
  var DEFAULT_API_BASE = "https://app.rudel.ai";
14024
14232
  var DEFAULT_WEB_URL = "https://app.rudel.ai";
14025
14233
  var CALLBACK_TIMEOUT_MS = 120000;
@@ -14038,41 +14246,53 @@ async function runLogin(flags) {
14038
14246
  resolveCallback = resolve2;
14039
14247
  rejectCallback = reject;
14040
14248
  });
14041
- const server = Bun.serve({
14042
- port: 0,
14043
- hostname: "127.0.0.1",
14044
- fetch(request) {
14045
- const url = new URL(request.url);
14046
- if (url.pathname !== "/callback") {
14047
- return new Response("Not found", { status: 404 });
14048
- }
14049
- const receivedToken = url.searchParams.get("token");
14050
- const receivedState = url.searchParams.get("state");
14051
- if (receivedState !== state) {
14052
- rejectCallback(new Error("State mismatch \u2014 possible CSRF attack"));
14053
- return new Response("<html><body><h1>Login failed</h1><p>State mismatch. Please try again.</p></body></html>", { headers: { "Content-Type": "text/html" } });
14054
- }
14055
- if (!receivedToken) {
14056
- rejectCallback(new Error("No token received"));
14057
- return new Response("<html><body><h1>Login failed</h1><p>No token received.</p></body></html>", { headers: { "Content-Type": "text/html" } });
14058
- }
14059
- resolveCallback(receivedToken);
14060
- return new Response("<html><body><h1>Login successful!</h1><p>You can close this tab and return to the terminal.</p></body></html>", { headers: { "Content-Type": "text/html" } });
14249
+ const server = createServer((req, res) => {
14250
+ const url = new URL(req.url ?? "/", `http://${req.headers.host}`);
14251
+ if (url.pathname !== "/callback") {
14252
+ res.writeHead(404, { "Content-Type": "text/plain" });
14253
+ res.end("Not found");
14254
+ return;
14255
+ }
14256
+ const receivedToken = url.searchParams.get("token");
14257
+ const receivedState = url.searchParams.get("state");
14258
+ if (receivedState !== state) {
14259
+ rejectCallback(new Error("State mismatch — possible CSRF attack"));
14260
+ res.writeHead(200, { "Content-Type": "text/html" });
14261
+ res.end("<html><body><h1>Login failed</h1><p>State mismatch. Please try again.</p></body></html>");
14262
+ return;
14263
+ }
14264
+ if (!receivedToken) {
14265
+ rejectCallback(new Error("No token received"));
14266
+ res.writeHead(200, { "Content-Type": "text/html" });
14267
+ res.end("<html><body><h1>Login failed</h1><p>No token received.</p></body></html>");
14268
+ return;
14061
14269
  }
14270
+ resolveCallback(receivedToken);
14271
+ res.writeHead(200, { "Content-Type": "text/html" });
14272
+ res.end("<html><body><h1>Login successful!</h1><p>You can close this tab and return to the terminal.</p></body></html>");
14273
+ });
14274
+ await new Promise((resolve2) => {
14275
+ server.listen(0, "127.0.0.1", resolve2);
14062
14276
  });
14063
- const callbackUrl = `http://127.0.0.1:${server.port}/callback`;
14277
+ const port = server.address().port;
14278
+ const callbackUrl = `http://127.0.0.1:${port}/callback`;
14064
14279
  const loginUrl = `${flags.webUrl}?cli_callback=${encodeURIComponent(callbackUrl)}&state=${state}`;
14065
14280
  R2.info(`If the browser doesn't open, visit:
14066
14281
  ${loginUrl}`);
14067
14282
  if (!flags.noBrowser) {
14068
14283
  if (process.platform === "win32") {
14069
- Bun.spawn(["cmd", "/c", "start", "", loginUrl], {
14070
- stdout: "ignore",
14071
- stderr: "ignore"
14284
+ const child = spawn("cmd", ["/c", "start", "", loginUrl], {
14285
+ detached: true,
14286
+ stdio: "ignore"
14072
14287
  });
14288
+ child.unref();
14073
14289
  } else {
14074
14290
  const opener = process.platform === "darwin" ? "open" : "xdg-open";
14075
- Bun.spawn([opener, loginUrl], { stdout: "ignore", stderr: "ignore" });
14291
+ const child = spawn(opener, [loginUrl], {
14292
+ detached: true,
14293
+ stdio: "ignore"
14294
+ });
14295
+ child.unref();
14076
14296
  }
14077
14297
  }
14078
14298
  const timeout = setTimeout(() => {
@@ -14085,14 +14305,14 @@ ${loginUrl}`);
14085
14305
  token = await tokenPromise;
14086
14306
  } catch (error) {
14087
14307
  clearTimeout(timeout);
14088
- server.stop();
14308
+ server.close();
14089
14309
  spin.stop("Authentication failed");
14090
14310
  R2.error(error instanceof Error ? error.message : String(error));
14091
14311
  process.exitCode = 1;
14092
14312
  return;
14093
14313
  }
14094
14314
  clearTimeout(timeout);
14095
- server.stop();
14315
+ server.close();
14096
14316
  spin.message("Validating token...");
14097
14317
  const meResponse = await fetch(`${flags.apiBase}/rpc/me`, {
14098
14318
  method: "POST",
@@ -14214,9 +14434,9 @@ var setOrgCommand = buildCommand({
14214
14434
  });
14215
14435
 
14216
14436
  // src/lib/classifier.ts
14217
- import { mkdir, unlink } from "fs/promises";
14218
- import { homedir as homedir9 } from "os";
14219
- import { join as join12 } from "path";
14437
+ import { mkdir as mkdir3, unlink, writeFile as writeFile3 } from "node:fs/promises";
14438
+ import { homedir as homedir11 } from "node:os";
14439
+ import { join as join13 } from "node:path";
14220
14440
 
14221
14441
  // src/lib/types.ts
14222
14442
  var SESSION_TAGS = [
@@ -14243,27 +14463,24 @@ var SYSTEM_PROMPT = `You are a session classifier. Analyze the Claude Code sessi
14243
14463
  CRITICAL: Respond with ONLY the tag name. Nothing else. No explanation, no punctuation, no formatting. Just ONE of: research, new_feature, bug_fix, refactoring, documentation, tests`;
14244
14464
  async function classifySession(content) {
14245
14465
  const truncatedContent = content.slice(0, 50000);
14246
- const tempDir = join12(homedir9(), ".claude", "temp");
14247
- const tempFile = join12(tempDir, `classify-${Date.now()}.txt`);
14466
+ const tempDir = join13(homedir11(), ".claude", "temp");
14467
+ const tempFile = join13(tempDir, `classify-${Date.now()}.txt`);
14248
14468
  try {
14249
- await mkdir(tempDir, { recursive: true });
14250
- await Bun.write(tempFile, `Classify this session transcript:
14469
+ await mkdir3(tempDir, { recursive: true });
14470
+ await writeFile3(tempFile, `Classify this session transcript:
14251
14471
 
14252
14472
  ${truncatedContent}`);
14253
14473
  const prompt = `Read and classify the session transcript in this file: ${tempFile}`;
14254
14474
  const escapedPrompt = prompt.replace(/'/g, "'\\''");
14255
14475
  const escapedSystemPrompt = SYSTEM_PROMPT.replace(/'/g, "'\\''");
14256
- const proc = Bun.spawn([
14257
- "sh",
14476
+ const result = await exec("sh", [
14258
14477
  "-c",
14259
14478
  `echo '${escapedPrompt}' | claude --output-format text --print --model haiku --no-session-persistence --dangerously-skip-permissions --system-prompt '${escapedSystemPrompt}'`
14260
- ], { stdout: "pipe", stderr: "pipe" });
14261
- const exitCode = await proc.exited;
14262
- const stdout = await new Response(proc.stdout).text();
14263
- if (exitCode !== 0) {
14479
+ ]);
14480
+ if (result.exitCode !== 0) {
14264
14481
  return "other";
14265
14482
  }
14266
- const output = stdout.trim().toLowerCase();
14483
+ const output = result.stdout.trim().toLowerCase();
14267
14484
  if (SESSION_TAGS.includes(output)) {
14268
14485
  return output;
14269
14486
  }
@@ -14283,10 +14500,10 @@ ${truncatedContent}`);
14283
14500
  }
14284
14501
 
14285
14502
  // src/lib/session-resolver.ts
14286
- import { access, readdir as readdir3 } from "fs/promises";
14287
- import { homedir as homedir10 } from "os";
14288
- import { basename, dirname as dirname3, join as join13 } from "path";
14289
- var SESSIONS_BASE_DIR3 = join13(homedir10(), ".claude", "projects");
14503
+ import { access, readdir as readdir3 } from "node:fs/promises";
14504
+ import { homedir as homedir12 } from "node:os";
14505
+ import { basename, dirname as dirname5, join as join14 } from "node:path";
14506
+ var SESSIONS_BASE_DIR3 = join14(homedir12(), ".claude", "projects");
14290
14507
  async function resolveSession(input) {
14291
14508
  const isPath = input.includes("/") || input.endsWith(".jsonl");
14292
14509
  if (isPath) {
@@ -14303,7 +14520,7 @@ async function resolveFromPath(filePath) {
14303
14520
  throw new Error(`Session file not found: ${filePath}`);
14304
14521
  }
14305
14522
  const sessionId = filename.replace(/\.jsonl$/, "");
14306
- const sessionDir = dirname3(filePath);
14523
+ const sessionDir = dirname5(filePath);
14307
14524
  const parentDir = basename(sessionDir);
14308
14525
  const projectPath = await decodeProjectPath(parentDir);
14309
14526
  return { transcriptPath: filePath, projectPath, sessionDir, sessionId };
@@ -14318,11 +14535,11 @@ async function resolveFromId(sessionId) {
14318
14535
  throw new Error(`Session not found: ${sessionId}`);
14319
14536
  }
14320
14537
  for (const projectDir of projectDirs) {
14321
- const sessionDir = join13(SESSIONS_BASE_DIR3, projectDir);
14538
+ const sessionDir = join14(SESSIONS_BASE_DIR3, projectDir);
14322
14539
  try {
14323
14540
  const files = await readdir3(sessionDir);
14324
14541
  if (files.includes(sessionFileName)) {
14325
- const transcriptPath = join13(sessionDir, sessionFileName);
14542
+ const transcriptPath = join14(sessionDir, sessionFileName);
14326
14543
  const projectPath = await decodeProjectPath(projectDir);
14327
14544
  return {
14328
14545
  transcriptPath,
@@ -14352,45 +14569,26 @@ async function runInteractiveUpload(flags) {
14352
14569
  We("rudel upload");
14353
14570
  const spin = bt2();
14354
14571
  spin.start("Scanning projects...");
14355
- const adapters2 = getAvailableAdapters();
14356
- const allProjects = [];
14357
- for (const adapter of adapters2) {
14358
- const projects = await adapter.scanAllSessions();
14359
- allProjects.push(...projects);
14360
- }
14572
+ const { projects: allProjects, groups } = await scanAndGroupProjects();
14361
14573
  spin.stop(`Found ${allProjects.length} project(s)`);
14362
14574
  if (allProjects.length === 0) {
14363
14575
  R2.warn("No projects with sessions found.");
14364
14576
  Le("Nothing to upload.");
14365
14577
  return;
14366
14578
  }
14367
- const cwd = process.cwd();
14368
- const grouped = groupProjectsForCwd(allProjects, cwd);
14369
14579
  const options = [];
14370
14580
  const preSelected = [];
14371
- for (const proj of grouped.current) {
14372
- options.push({
14373
- value: proj,
14374
- label: `[${getAdapterName(proj.source)}] ${proj.displayPath}`,
14375
- hint: sessionCountHint(proj.sessionCount)
14376
- });
14377
- preSelected.push(proj);
14378
- }
14379
- for (const sub of grouped.subfolders) {
14380
- const relative = sub.projectPath.slice(cwd.length + 1);
14381
- options.push({
14382
- value: sub,
14383
- label: ` [${getAdapterName(sub.source)}] ${relative}`,
14384
- hint: sessionCountHint(sub.sessionCount)
14385
- });
14386
- preSelected.push(sub);
14387
- }
14388
- for (const other of grouped.others) {
14389
- options.push({
14390
- value: other,
14391
- label: `[${getAdapterName(other.source)}] ${other.displayPath}`,
14392
- hint: sessionCountHint(other.sessionCount)
14393
- });
14581
+ for (const group2 of groups) {
14582
+ for (const proj of group2.projects) {
14583
+ options.push({
14584
+ value: proj,
14585
+ label: `[${getAdapterName(proj.source)}] ${proj.displayPath}`,
14586
+ hint: sessionCountHint(proj.sessionCount)
14587
+ });
14588
+ if (group2.containsCwd) {
14589
+ preSelected.push(proj);
14590
+ }
14591
+ }
14394
14592
  }
14395
14593
  const selected = await je({
14396
14594
  message: "Select projects to upload",
@@ -14452,7 +14650,7 @@ async function runInteractiveUpload(flags) {
14452
14650
  });
14453
14651
  renderBatchSummary(summary, { showRetryHint: summary.failed > 0 });
14454
14652
  if (flags.dryRun) {
14455
- Le("Dry run complete \u2014 no sessions were uploaded.");
14653
+ Le("Dry run complete no sessions were uploaded.");
14456
14654
  } else {
14457
14655
  Le("Done!");
14458
14656
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rudel",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "type": "module",
5
5
  "description": "CLI for the Coding Agent Analytics Platform rudel.ai",
6
6
  "license": "MIT",
@@ -32,7 +32,7 @@
32
32
  "scripts": {
33
33
  "dev": "bun run src/bin/cli.ts",
34
34
  "check-types": "tsc --noEmit",
35
- "build": "bun build src/bin/cli.ts --outdir dist --target bun",
35
+ "build": "bun build src/bin/cli.ts --outdir dist --target node",
36
36
  "test": "bun test"
37
37
  },
38
38
  "dependencies": {
@@ -48,6 +48,7 @@
48
48
  "@rudel/agent-adapters": "0.0.0",
49
49
  "@rudel/api-routes": "0.0.0",
50
50
  "@rudel/typescript-config": "0.0.0",
51
+ "@types/node": "^22",
51
52
  "bun-types": "latest",
52
53
  "typescript": "5.9.2"
53
54
  }