clairo 1.0.8 → 1.0.9

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 +120 -7
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -573,6 +573,26 @@ function extractTicketKey(text) {
573
573
  }
574
574
 
575
575
  // src/lib/jira/config.ts
576
+ function getExistingJiraConfigs(excludeRepoPath) {
577
+ const config = loadConfig();
578
+ const repos = config.repositories ?? {};
579
+ const configs = [];
580
+ const seen = /* @__PURE__ */ new Set();
581
+ for (const [repoPath, repoConfig] of Object.entries(repos)) {
582
+ if (repoPath === excludeRepoPath) continue;
583
+ if (!repoConfig.jiraSiteUrl || !repoConfig.jiraEmail || !repoConfig.jiraApiToken) continue;
584
+ const key = `${repoConfig.jiraSiteUrl}|${repoConfig.jiraEmail}`;
585
+ if (seen.has(key)) continue;
586
+ seen.add(key);
587
+ configs.push({
588
+ repoPath,
589
+ siteUrl: repoConfig.jiraSiteUrl,
590
+ email: repoConfig.jiraEmail,
591
+ apiToken: repoConfig.jiraApiToken
592
+ });
593
+ }
594
+ return configs;
595
+ }
576
596
  function isJiraConfigured(repoPath) {
577
597
  const config = getRepoConfig(repoPath);
578
598
  return !!(config.jiraSiteUrl && config.jiraEmail && config.jiraApiToken);
@@ -594,6 +614,13 @@ function getJiraCredentials(repoPath) {
594
614
  function setJiraCredentials(repoPath, email, apiToken) {
595
615
  updateRepoConfig(repoPath, { jiraEmail: email, jiraApiToken: apiToken });
596
616
  }
617
+ function clearJiraConfig(repoPath) {
618
+ updateRepoConfig(repoPath, {
619
+ jiraSiteUrl: void 0,
620
+ jiraEmail: void 0,
621
+ jiraApiToken: void 0
622
+ });
623
+ }
597
624
  function getLinkedTickets(repoPath, branch) {
598
625
  var _a;
599
626
  const config = getRepoConfig(repoPath);
@@ -2035,6 +2062,7 @@ function ChangeStatusModal({ repoPath, ticketKey, currentStatus, onComplete, onC
2035
2062
  // src/components/jira/ConfigureJiraSiteModal.tsx
2036
2063
  import { useState as useState15 } from "react";
2037
2064
  import { Box as Box9, Text as Text9, useInput as useInput8 } from "ink";
2065
+ import { ScrollView as ScrollView4 } from "ink-scroll-view";
2038
2066
 
2039
2067
  // src/lib/editor.ts
2040
2068
  import { spawnSync as spawnSync2 } from "child_process";
@@ -2066,27 +2094,57 @@ function openInEditor(content, filename) {
2066
2094
 
2067
2095
  // src/components/jira/ConfigureJiraSiteModal.tsx
2068
2096
  import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
2097
+ var MAX_VISIBLE_ITEMS = 4;
2069
2098
  function ConfigureJiraSiteModal({
2070
2099
  initialSiteUrl,
2071
2100
  initialEmail,
2101
+ existingConfigs = [],
2072
2102
  onSubmit,
2073
2103
  onCancel,
2074
2104
  loading,
2075
2105
  error
2076
2106
  }) {
2107
+ const hasExisting = existingConfigs.length > 0;
2108
+ const [mode, setMode] = useState15(hasExisting ? "choose" : "manual");
2109
+ const [selectedExisting, setSelectedExisting] = useState15(0);
2110
+ const scrollRef = useScrollToIndex(selectedExisting);
2077
2111
  const [siteUrl, setSiteUrl] = useState15(initialSiteUrl ?? "");
2078
2112
  const [email, setEmail] = useState15(initialEmail ?? "");
2079
2113
  const [apiToken, setApiToken] = useState15("");
2080
2114
  const [selectedItem, setSelectedItem] = useState15("siteUrl");
2081
2115
  const items = ["siteUrl", "email", "apiToken", "submit"];
2082
2116
  const canSubmit = siteUrl.trim() && email.trim() && apiToken.trim();
2117
+ const chooseItems = existingConfigs.length + 1;
2083
2118
  useInput8(
2084
2119
  (input, key) => {
2085
2120
  if (loading) return;
2086
2121
  if (key.escape) {
2122
+ if (mode === "manual" && hasExisting) {
2123
+ setMode("choose");
2124
+ return;
2125
+ }
2087
2126
  onCancel();
2088
2127
  return;
2089
2128
  }
2129
+ if (mode === "choose") {
2130
+ if (key.upArrow || input === "k") {
2131
+ setSelectedExisting((prev) => Math.max(0, prev - 1));
2132
+ return;
2133
+ }
2134
+ if (key.downArrow || input === "j") {
2135
+ setSelectedExisting((prev) => Math.min(chooseItems - 1, prev + 1));
2136
+ return;
2137
+ }
2138
+ if (key.return) {
2139
+ if (selectedExisting < existingConfigs.length) {
2140
+ const config = existingConfigs[selectedExisting];
2141
+ onSubmit(config.siteUrl, config.email, config.apiToken);
2142
+ } else {
2143
+ setMode("manual");
2144
+ }
2145
+ }
2146
+ return;
2147
+ }
2090
2148
  if (key.upArrow || input === "k") {
2091
2149
  setSelectedItem((prev) => {
2092
2150
  const idx = items.indexOf(prev);
@@ -2137,9 +2195,49 @@ function ConfigureJiraSiteModal({
2137
2195
  value !== void 0 && /* @__PURE__ */ jsx9(Box9, { marginLeft: 4, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: displayValue || "(empty - press Enter to edit)" }) })
2138
2196
  ] });
2139
2197
  };
2198
+ if (mode === "choose") {
2199
+ const totalItems = existingConfigs.length + 1;
2200
+ const listHeight = Math.min(totalItems * 2, MAX_VISIBLE_ITEMS * 2);
2201
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, paddingY: 1, children: [
2202
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: "cyan", children: "Configure Jira Site" }),
2203
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Select an existing configuration or enter new credentials" }),
2204
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1 }),
2205
+ error && /* @__PURE__ */ jsx9(Box9, { marginBottom: 1, children: /* @__PURE__ */ jsx9(Text9, { color: "red", children: error }) }),
2206
+ /* @__PURE__ */ jsx9(Box9, { height: listHeight, overflow: "hidden", children: /* @__PURE__ */ jsxs9(ScrollView4, { ref: scrollRef, children: [
2207
+ existingConfigs.map((config, idx) => {
2208
+ const isSelected = selectedExisting === idx;
2209
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
2210
+ /* @__PURE__ */ jsxs9(Text9, { color: isSelected ? "yellow" : void 0, bold: isSelected, children: [
2211
+ isSelected ? "> " : " ",
2212
+ config.siteUrl
2213
+ ] }),
2214
+ /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
2215
+ " ",
2216
+ config.email
2217
+ ] })
2218
+ ] }, config.siteUrl + config.email);
2219
+ }),
2220
+ /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsxs9(
2221
+ Text9,
2222
+ {
2223
+ color: selectedExisting === existingConfigs.length ? "yellow" : void 0,
2224
+ bold: selectedExisting === existingConfigs.length,
2225
+ children: [
2226
+ selectedExisting === existingConfigs.length ? "> " : " ",
2227
+ "Enter new credentials..."
2228
+ ]
2229
+ }
2230
+ ) })
2231
+ ] }) }),
2232
+ loading && /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text9, { color: "yellow", children: "Validating credentials..." }) })
2233
+ ] });
2234
+ }
2140
2235
  return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, paddingY: 1, children: [
2141
2236
  /* @__PURE__ */ jsx9(Text9, { bold: true, color: "cyan", children: "Configure Jira Site" }),
2142
- /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Up/Down to select, Enter to edit, Esc to cancel" }),
2237
+ /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
2238
+ "Up/Down to select, Enter to edit, Esc to ",
2239
+ hasExisting ? "go back" : "cancel"
2240
+ ] }),
2143
2241
  /* @__PURE__ */ jsx9(Box9, { marginTop: 1 }),
2144
2242
  error && /* @__PURE__ */ jsx9(Box9, { marginBottom: 1, children: /* @__PURE__ */ jsx9(Text9, { color: "red", children: error }) }),
2145
2243
  renderItem("siteUrl", "Site URL (e.g., https://company.atlassian.net)", siteUrl),
@@ -2251,6 +2349,11 @@ function JiraView({ isFocused, onModalChange, onJiraStateChange, onLogUpdated })
2251
2349
  modal.close();
2252
2350
  jira.refreshTickets(repo.repoPath, repo.currentBranch);
2253
2351
  };
2352
+ const handleRemoveConfig = () => {
2353
+ if (!repo.repoPath || !repo.currentBranch) return;
2354
+ clearJiraConfig(repo.repoPath);
2355
+ jira.initializeJiraState(repo.repoPath, repo.currentBranch, repo.currentRepoSlug);
2356
+ };
2254
2357
  useInput9(
2255
2358
  (input, key) => {
2256
2359
  if (input === "c" && jira.jiraState === "not_configured") {
@@ -2261,6 +2364,10 @@ function JiraView({ isFocused, onModalChange, onJiraStateChange, onLogUpdated })
2261
2364
  modal.open("link");
2262
2365
  return;
2263
2366
  }
2367
+ if (input === "r" && jira.jiraState !== "not_configured") {
2368
+ handleRemoveConfig();
2369
+ return;
2370
+ }
2264
2371
  if (jira.jiraState === "has_tickets") {
2265
2372
  if (key.upArrow || input === "k") nav.prev();
2266
2373
  if (key.downArrow || input === "j") nav.next();
@@ -2278,11 +2385,13 @@ function JiraView({ isFocused, onModalChange, onJiraStateChange, onLogUpdated })
2278
2385
  if (modal.type === "configure") {
2279
2386
  const siteUrl = repo.repoPath ? getJiraSiteUrl(repo.repoPath) : void 0;
2280
2387
  const creds = repo.repoPath ? getJiraCredentials(repo.repoPath) : { email: null, apiToken: null };
2388
+ const existingConfigs = getExistingJiraConfigs(repo.repoPath ?? void 0);
2281
2389
  return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", flexShrink: 0, children: /* @__PURE__ */ jsx11(
2282
2390
  ConfigureJiraSiteModal,
2283
2391
  {
2284
2392
  initialSiteUrl: siteUrl ?? void 0,
2285
2393
  initialEmail: creds.email ?? void 0,
2394
+ existingConfigs,
2286
2395
  onSubmit: handleConfigureSubmit,
2287
2396
  onCancel: () => {
2288
2397
  modal.close();
@@ -2346,7 +2455,7 @@ import { Box as Box14, useInput as useInput12 } from "ink";
2346
2455
  import { useRef as useRef7, useState as useState16 } from "react";
2347
2456
  import { TitledBox as TitledBox5 } from "@mishieck/ink-titled-box";
2348
2457
  import { Box as Box12, Text as Text12, useInput as useInput10 } from "ink";
2349
- import { ScrollView as ScrollView4 } from "ink-scroll-view";
2458
+ import { ScrollView as ScrollView5 } from "ink-scroll-view";
2350
2459
  import TextInput2 from "ink-text-input";
2351
2460
 
2352
2461
  // src/lib/claude/api.ts
@@ -2524,7 +2633,7 @@ ${value.trim()}
2524
2633
  onRefresh();
2525
2634
  };
2526
2635
  return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", flexGrow: 1, children: [
2527
- /* @__PURE__ */ jsx12(TitledBox5, { borderStyle: "round", titles: [displayTitle], borderColor, flexGrow: 1, children: /* @__PURE__ */ jsx12(Box12, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx12(ScrollView4, { ref: scrollRef, children: /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", paddingX: 1, children: [
2636
+ /* @__PURE__ */ jsx12(TitledBox5, { borderStyle: "round", titles: [displayTitle], borderColor, flexGrow: 1, children: /* @__PURE__ */ jsx12(Box12, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx12(ScrollView5, { ref: scrollRef, children: /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", paddingX: 1, children: [
2528
2637
  !date && /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: "Select a log file to view" }),
2529
2638
  date && content === null && /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: "Log file not found" }),
2530
2639
  date && content !== null && content.trim() === "" && /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: "Empty log file" }),
@@ -2560,7 +2669,7 @@ ${value.trim()}
2560
2669
  // src/components/logs/LogsHistoryBox.tsx
2561
2670
  import { TitledBox as TitledBox6 } from "@mishieck/ink-titled-box";
2562
2671
  import { Box as Box13, Text as Text13, useInput as useInput11 } from "ink";
2563
- import { ScrollView as ScrollView5 } from "ink-scroll-view";
2672
+ import { ScrollView as ScrollView6 } from "ink-scroll-view";
2564
2673
  import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
2565
2674
  function LogsHistoryBox({
2566
2675
  logFiles,
@@ -2593,7 +2702,7 @@ function LogsHistoryBox({
2593
2702
  );
2594
2703
  return /* @__PURE__ */ jsx13(TitledBox6, { borderStyle: "round", titles: [title], borderColor, height: 5, children: /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", paddingX: 1, flexGrow: 1, overflow: "hidden", children: [
2595
2704
  logFiles.length === 0 && /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: "No logs yet" }),
2596
- logFiles.length > 0 && /* @__PURE__ */ jsx13(ScrollView5, { ref: scrollRef, children: logFiles.map((file, idx) => {
2705
+ logFiles.length > 0 && /* @__PURE__ */ jsx13(ScrollView6, { ref: scrollRef, children: logFiles.map((file, idx) => {
2597
2706
  const isHighlighted = idx === highlightedIndex;
2598
2707
  const isSelected = file.date === selectedDate;
2599
2708
  const cursor = isHighlighted ? ">" : " ";
@@ -2695,13 +2804,17 @@ var GITHUB_KEYBINDINGS = {
2695
2804
  // src/constants/jira.ts
2696
2805
  var JIRA_KEYBINDINGS = {
2697
2806
  not_configured: [{ key: "c", label: "Configure Jira" }],
2698
- no_tickets: [{ key: "l", label: "Link Ticket" }],
2807
+ no_tickets: [
2808
+ { key: "l", label: "Link Ticket" },
2809
+ { key: "r", label: "Remove Config", color: "red" }
2810
+ ],
2699
2811
  has_tickets: [
2700
2812
  { key: "l", label: "Link" },
2701
2813
  { key: "s", label: "Status" },
2702
2814
  { key: "d", label: "Unlink", color: "red" },
2703
2815
  { key: "o", label: "Open", color: "green" },
2704
- { key: "y", label: "Copy Link" }
2816
+ { key: "y", label: "Copy Link" },
2817
+ { key: "r", label: "Remove Config", color: "red" }
2705
2818
  ]
2706
2819
  };
2707
2820
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clairo",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",