@markmdev/pebble 0.1.12 → 0.1.15

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.
package/dist/cli/index.js CHANGED
@@ -8,6 +8,9 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
8
8
 
9
9
  // src/cli/index.ts
10
10
  import { Command } from "commander";
11
+ import { readFileSync as readFileSync2 } from "fs";
12
+ import { fileURLToPath as fileURLToPath2 } from "url";
13
+ import { dirname as dirname2, join as join3 } from "path";
11
14
 
12
15
  // src/shared/types.ts
13
16
  var ISSUE_TYPES = ["task", "bug", "epic", "verification"];
@@ -37,7 +40,7 @@ var TYPE_LABELS = {
37
40
 
38
41
  // src/cli/lib/storage.ts
39
42
  import * as fs from "fs";
40
- import * as path from "path";
43
+ import * as path2 from "path";
41
44
 
42
45
  // src/cli/lib/id.ts
43
46
  import * as crypto from "crypto";
@@ -59,23 +62,90 @@ function derivePrefix(folderName) {
59
62
  return clean.slice(0, 4).toUpperCase().padEnd(4, "X");
60
63
  }
61
64
 
65
+ // src/cli/lib/git.ts
66
+ import { execSync } from "child_process";
67
+ import path from "path";
68
+ function getMainWorktreeRoot() {
69
+ try {
70
+ const gitCommonDir = execSync("git rev-parse --git-common-dir", {
71
+ encoding: "utf-8",
72
+ stdio: ["pipe", "pipe", "pipe"]
73
+ }).trim();
74
+ const gitDir = execSync("git rev-parse --git-dir", {
75
+ encoding: "utf-8",
76
+ stdio: ["pipe", "pipe", "pipe"]
77
+ }).trim();
78
+ const normalizedCommon = path.resolve(gitCommonDir);
79
+ const normalizedGit = path.resolve(gitDir);
80
+ if (normalizedCommon === normalizedGit) {
81
+ return null;
82
+ }
83
+ const mainRoot = path.dirname(normalizedCommon);
84
+ return mainRoot;
85
+ } catch {
86
+ return null;
87
+ }
88
+ }
89
+
62
90
  // src/cli/lib/storage.ts
63
91
  var PEBBLE_DIR = ".pebble";
64
92
  var ISSUES_FILE = "issues.jsonl";
65
93
  var CONFIG_FILE = "config.json";
94
+ function getConfigSafe(pebbleDir) {
95
+ try {
96
+ const configPath = path2.join(pebbleDir, CONFIG_FILE);
97
+ if (!fs.existsSync(configPath)) {
98
+ return null;
99
+ }
100
+ const content = fs.readFileSync(configPath, "utf-8");
101
+ return JSON.parse(content);
102
+ } catch {
103
+ return null;
104
+ }
105
+ }
106
+ function resolveWorktreePebbleDir(localPebbleDir) {
107
+ if (process.env.PEBBLE_LOCAL === "1") {
108
+ return localPebbleDir;
109
+ }
110
+ const config = getConfigSafe(localPebbleDir);
111
+ if (config?.useMainTreePebble === false) {
112
+ return localPebbleDir;
113
+ }
114
+ const mainRoot = getMainWorktreeRoot();
115
+ if (!mainRoot) {
116
+ return localPebbleDir;
117
+ }
118
+ const mainPebble = path2.join(mainRoot, PEBBLE_DIR);
119
+ if (fs.existsSync(mainPebble) && fs.statSync(mainPebble).isDirectory()) {
120
+ return mainPebble;
121
+ }
122
+ return localPebbleDir;
123
+ }
66
124
  function discoverPebbleDir(startDir = process.cwd()) {
67
- let currentDir = path.resolve(startDir);
68
- const root = path.parse(currentDir).root;
125
+ if (process.env.PEBBLE_LOCAL !== "1") {
126
+ const mainRoot = getMainWorktreeRoot();
127
+ if (mainRoot) {
128
+ const mainPebble = path2.join(mainRoot, PEBBLE_DIR);
129
+ if (fs.existsSync(mainPebble) && fs.statSync(mainPebble).isDirectory()) {
130
+ const config = getConfigSafe(mainPebble);
131
+ if (config?.useMainTreePebble !== false) {
132
+ return mainPebble;
133
+ }
134
+ }
135
+ }
136
+ }
137
+ let currentDir = path2.resolve(startDir);
138
+ const root = path2.parse(currentDir).root;
69
139
  while (currentDir !== root) {
70
- const pebbleDir = path.join(currentDir, PEBBLE_DIR);
140
+ const pebbleDir = path2.join(currentDir, PEBBLE_DIR);
71
141
  if (fs.existsSync(pebbleDir) && fs.statSync(pebbleDir).isDirectory()) {
72
- return pebbleDir;
142
+ return resolveWorktreePebbleDir(pebbleDir);
73
143
  }
74
- currentDir = path.dirname(currentDir);
144
+ currentDir = path2.dirname(currentDir);
75
145
  }
76
- const rootPebble = path.join(root, PEBBLE_DIR);
146
+ const rootPebble = path2.join(root, PEBBLE_DIR);
77
147
  if (fs.existsSync(rootPebble) && fs.statSync(rootPebble).isDirectory()) {
78
- return rootPebble;
148
+ return resolveWorktreePebbleDir(rootPebble);
79
149
  }
80
150
  return null;
81
151
  }
@@ -87,23 +157,35 @@ function getPebbleDir() {
87
157
  return dir;
88
158
  }
89
159
  function ensurePebbleDir(baseDir = process.cwd()) {
90
- const pebbleDir = path.join(baseDir, PEBBLE_DIR);
160
+ let targetDir = baseDir;
161
+ if (process.env.PEBBLE_LOCAL !== "1") {
162
+ const mainRoot = getMainWorktreeRoot();
163
+ if (mainRoot) {
164
+ const mainPebble = path2.join(mainRoot, PEBBLE_DIR);
165
+ if (fs.existsSync(mainPebble) && fs.statSync(mainPebble).isDirectory()) {
166
+ return mainPebble;
167
+ }
168
+ targetDir = mainRoot;
169
+ }
170
+ }
171
+ const pebbleDir = path2.join(targetDir, PEBBLE_DIR);
91
172
  if (!fs.existsSync(pebbleDir)) {
92
173
  fs.mkdirSync(pebbleDir, { recursive: true });
93
- const folderName = path.basename(baseDir);
174
+ const folderName = path2.basename(targetDir);
94
175
  const config = {
95
176
  prefix: derivePrefix(folderName),
96
- version: "0.1.0"
177
+ version: "0.1.0",
178
+ useMainTreePebble: true
97
179
  };
98
180
  setConfig(config, pebbleDir);
99
- const issuesPath = path.join(pebbleDir, ISSUES_FILE);
181
+ const issuesPath = path2.join(pebbleDir, ISSUES_FILE);
100
182
  fs.writeFileSync(issuesPath, "", "utf-8");
101
183
  }
102
184
  return pebbleDir;
103
185
  }
104
186
  function getIssuesPath(pebbleDir) {
105
187
  const dir = pebbleDir ?? getPebbleDir();
106
- return path.join(dir, ISSUES_FILE);
188
+ return path2.join(dir, ISSUES_FILE);
107
189
  }
108
190
  function appendEvent(event, pebbleDir) {
109
191
  const issuesPath = getIssuesPath(pebbleDir);
@@ -129,7 +211,7 @@ function readEvents(pebbleDir) {
129
211
  if (!dir) {
130
212
  return [];
131
213
  }
132
- const issuesPath = path.join(dir, ISSUES_FILE);
214
+ const issuesPath = path2.join(dir, ISSUES_FILE);
133
215
  if (!fs.existsSync(issuesPath)) {
134
216
  return [];
135
217
  }
@@ -145,7 +227,7 @@ function readEvents(pebbleDir) {
145
227
  }
146
228
  function getConfigPath(pebbleDir) {
147
229
  const dir = pebbleDir ?? getPebbleDir();
148
- return path.join(dir, CONFIG_FILE);
230
+ return path2.join(dir, CONFIG_FILE);
149
231
  }
150
232
  function getConfig(pebbleDir) {
151
233
  const configPath = getConfigPath(pebbleDir);
@@ -157,7 +239,7 @@ function getConfig(pebbleDir) {
157
239
  }
158
240
  function setConfig(config, pebbleDir) {
159
241
  const dir = pebbleDir ?? getPebbleDir();
160
- const configPath = path.join(dir, CONFIG_FILE);
242
+ const configPath = path2.join(dir, CONFIG_FILE);
161
243
  fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
162
244
  }
163
245
  function getOrCreatePebbleDir() {
@@ -491,6 +573,21 @@ function getOpenBlockers(issueId) {
491
573
  const state = computeState(events);
492
574
  return issue.blockedBy.map((id) => state.get(id)).filter((i) => i !== void 0 && i.status !== "closed");
493
575
  }
576
+ function getComputedState() {
577
+ const events = readEvents();
578
+ return computeState(events);
579
+ }
580
+ function getAncestryChain(issueId, state) {
581
+ const chain = [];
582
+ let current = state.get(issueId);
583
+ while (current?.parent) {
584
+ const parent = state.get(current.parent);
585
+ if (!parent) break;
586
+ chain.push({ id: parent.id, title: parent.title });
587
+ current = parent;
588
+ }
589
+ return chain;
590
+ }
494
591
 
495
592
  // src/shared/time.ts
496
593
  import { formatDistanceToNow, parseISO } from "date-fns";
@@ -536,7 +633,10 @@ function formatIssueDetailPretty(issue, ctx) {
536
633
  lines.push(`Type: ${formatType(issue.type)}`);
537
634
  lines.push(`Priority: ${formatPriority(issue.priority)}`);
538
635
  lines.push(`Status: ${formatStatus(issue.status)}`);
539
- if (issue.parent) {
636
+ if (ctx.ancestry && ctx.ancestry.length > 0) {
637
+ const chain = [...ctx.ancestry].reverse().map((a) => a.id).join(" > ");
638
+ lines.push(`Ancestry: ${chain}`);
639
+ } else if (issue.parent) {
540
640
  lines.push(`Parent: ${issue.parent}`);
541
641
  }
542
642
  if (issue.description) {
@@ -602,7 +702,8 @@ function outputIssueDetail(issue, ctx, pretty) {
602
702
  blocking: ctx.blocking.map((i) => i.id),
603
703
  children: ctx.children.map((i) => ({ id: i.id, title: i.title, status: i.status })),
604
704
  verifications: ctx.verifications.map((i) => ({ id: i.id, title: i.title, status: i.status })),
605
- related: ctx.related.map((i) => i.id)
705
+ related: ctx.related.map((i) => i.id),
706
+ ...ctx.ancestry && ctx.ancestry.length > 0 && { ancestry: ctx.ancestry }
606
707
  };
607
708
  console.log(formatJson(output));
608
709
  }
@@ -798,11 +899,12 @@ function formatIssueListVerbose(issues, sectionHeader) {
798
899
  lines.push("");
799
900
  }
800
901
  for (const info of issues) {
801
- const { issue, blocking, children, verifications, blockers, parent } = info;
902
+ const { issue, blocking, children, verifications, blockers, ancestry } = info;
802
903
  lines.push(`${issue.id}: ${issue.title}`);
803
904
  lines.push(` Type: ${formatType(issue.type)} | Priority: P${issue.priority} | Created: ${formatRelativeTime(issue.createdAt)}`);
804
- if (parent) {
805
- lines.push(` Epic: ${parent.id} (${parent.title})`);
905
+ if (ancestry.length > 0) {
906
+ const chain = [...ancestry].reverse().map((a) => a.id).join(" > ");
907
+ lines.push(` Ancestry: ${chain}`);
806
908
  }
807
909
  if (blocking.length > 0) {
808
910
  lines.push(` Blocking: ${blocking.join(", ")}`);
@@ -824,13 +926,13 @@ function outputIssueListVerbose(issues, pretty, sectionHeader, limitInfo) {
824
926
  console.log(formatLimitMessage(limitInfo));
825
927
  }
826
928
  } else {
827
- const output = issues.map(({ issue, blocking, children, verifications, blockers, parent }) => ({
929
+ const output = issues.map(({ issue, blocking, children, verifications, blockers, ancestry }) => ({
828
930
  ...issue,
829
931
  blocking,
830
932
  childrenCount: issue.type === "epic" ? children : void 0,
831
933
  verificationsCount: verifications,
832
934
  ...blockers && { openBlockers: blockers },
833
- ...parent && { parentInfo: parent }
935
+ ...ancestry.length > 0 && { ancestry }
834
936
  }));
835
937
  if (limitInfo?.limited) {
836
938
  console.log(formatJson({ issues: output, _meta: limitInfo }));
@@ -1403,20 +1505,15 @@ function listCommand(program2) {
1403
1505
  limited: limit > 0 && total > limit
1404
1506
  };
1405
1507
  if (options.verbose) {
1508
+ const state = getComputedState();
1406
1509
  const verboseIssues = issues.map((issue) => {
1407
- const info = {
1510
+ return {
1408
1511
  issue,
1409
1512
  blocking: getBlocking(issue.id).map((i) => i.id),
1410
1513
  children: getChildren(issue.id).length,
1411
- verifications: getVerifications(issue.id).length
1514
+ verifications: getVerifications(issue.id).length,
1515
+ ancestry: getAncestryChain(issue.id, state)
1412
1516
  };
1413
- if (issue.parent) {
1414
- const parentIssue = getIssue(issue.parent);
1415
- if (parentIssue) {
1416
- info.parent = { id: parentIssue.id, title: parentIssue.title };
1417
- }
1418
- }
1419
- return info;
1420
1517
  });
1421
1518
  let sectionHeader = "Issues";
1422
1519
  if (filters.status) {
@@ -1453,7 +1550,9 @@ function showCommand(program2) {
1453
1550
  const children = issue.type === "epic" ? getChildren(resolvedId) : [];
1454
1551
  const verifications = getVerifications(resolvedId);
1455
1552
  const related = getRelated(resolvedId);
1456
- outputIssueDetail(issue, { blocking, children, verifications, related }, pretty);
1553
+ const state = getComputedState();
1554
+ const ancestry = getAncestryChain(resolvedId, state);
1555
+ outputIssueDetail(issue, { blocking, children, verifications, related, ancestry }, pretty);
1457
1556
  } catch (error) {
1458
1557
  outputError(error, pretty);
1459
1558
  }
@@ -1486,20 +1585,15 @@ function readyCommand(program2) {
1486
1585
  limited: limit > 0 && total > limit
1487
1586
  };
1488
1587
  if (options.verbose) {
1588
+ const state = getComputedState();
1489
1589
  const verboseIssues = issues.map((issue) => {
1490
- const info = {
1590
+ return {
1491
1591
  issue,
1492
1592
  blocking: getBlocking(issue.id).map((i) => i.id),
1493
1593
  children: getChildren(issue.id).length,
1494
- verifications: getVerifications(issue.id).length
1594
+ verifications: getVerifications(issue.id).length,
1595
+ ancestry: getAncestryChain(issue.id, state)
1495
1596
  };
1496
- if (issue.parent) {
1497
- const parentIssue = getIssue(issue.parent);
1498
- if (parentIssue) {
1499
- info.parent = { id: parentIssue.id, title: parentIssue.title };
1500
- }
1501
- }
1502
- return info;
1503
1597
  });
1504
1598
  outputIssueListVerbose(verboseIssues, pretty, "Ready Issues", limitInfo);
1505
1599
  } else {
@@ -1530,23 +1624,18 @@ function blockedCommand(program2) {
1530
1624
  limited: limit > 0 && total > limit
1531
1625
  };
1532
1626
  if (options.verbose) {
1627
+ const state = getComputedState();
1533
1628
  const verboseIssues = issues.map((issue) => {
1534
1629
  const allBlockers = getBlockers(issue.id);
1535
1630
  const openBlockers = allBlockers.filter((b) => b.status !== "closed").map((b) => b.id);
1536
- const info = {
1631
+ return {
1537
1632
  issue,
1538
1633
  blocking: getBlocking(issue.id).map((i) => i.id),
1539
1634
  children: getChildren(issue.id).length,
1540
1635
  verifications: getVerifications(issue.id).length,
1541
- blockers: openBlockers
1636
+ blockers: openBlockers,
1637
+ ancestry: getAncestryChain(issue.id, state)
1542
1638
  };
1543
- if (issue.parent) {
1544
- const parentIssue = getIssue(issue.parent);
1545
- if (parentIssue) {
1546
- info.parent = { id: parentIssue.id, title: parentIssue.title };
1547
- }
1548
- }
1549
- return info;
1550
1639
  });
1551
1640
  outputIssueListVerbose(verboseIssues, pretty, "Blocked Issues", limitInfo);
1552
1641
  } else {
@@ -2036,7 +2125,7 @@ function formatGraphPretty(issues) {
2036
2125
  import express from "express";
2037
2126
  import cors from "cors";
2038
2127
  import { fileURLToPath } from "url";
2039
- import path2 from "path";
2128
+ import path3 from "path";
2040
2129
  import fs2 from "fs";
2041
2130
  import net from "net";
2042
2131
  import open from "open";
@@ -2131,14 +2220,14 @@ function uiCommand(program2) {
2131
2220
  console.error("Error: --files option requires at least one path");
2132
2221
  process.exit(1);
2133
2222
  }
2134
- issueFiles = issueFiles.map((p) => path2.resolve(process.cwd(), p));
2223
+ issueFiles = issueFiles.map((p) => path3.resolve(process.cwd(), p));
2135
2224
  console.log(`Multi-worktree mode: watching ${issueFiles.length} file(s)`);
2136
2225
  for (const f of issueFiles) {
2137
2226
  console.log(` - ${f}`);
2138
2227
  }
2139
2228
  } else {
2140
2229
  const pebbleDir = getOrCreatePebbleDir();
2141
- issueFiles = [path2.join(pebbleDir, "issues.jsonl")];
2230
+ issueFiles = [path3.join(pebbleDir, "issues.jsonl")];
2142
2231
  }
2143
2232
  if (!options.files) {
2144
2233
  getOrCreatePebbleDir();
@@ -2161,7 +2250,7 @@ function uiCommand(program2) {
2161
2250
  res.status(400).json({ error: "path is required" });
2162
2251
  return;
2163
2252
  }
2164
- const resolved = path2.resolve(process.cwd(), filePath);
2253
+ const resolved = path3.resolve(process.cwd(), filePath);
2165
2254
  if (!fs2.existsSync(resolved)) {
2166
2255
  res.status(400).json({ error: `File not found: ${filePath}` });
2167
2256
  return;
@@ -2199,10 +2288,10 @@ function uiCommand(program2) {
2199
2288
  });
2200
2289
  app.get("/api/worktrees", (_req, res) => {
2201
2290
  try {
2202
- const { execSync } = __require("child_process");
2291
+ const { execSync: execSync2 } = __require("child_process");
2203
2292
  let worktreeOutput;
2204
2293
  try {
2205
- worktreeOutput = execSync("git worktree list --porcelain", {
2294
+ worktreeOutput = execSync2("git worktree list --porcelain", {
2206
2295
  encoding: "utf-8",
2207
2296
  cwd: process.cwd()
2208
2297
  });
@@ -2224,7 +2313,7 @@ function uiCommand(program2) {
2224
2313
  }
2225
2314
  }
2226
2315
  if (worktreePath) {
2227
- const issuesFile = path2.join(worktreePath, ".pebble", "issues.jsonl");
2316
+ const issuesFile = path3.join(worktreePath, ".pebble", "issues.jsonl");
2228
2317
  const hasIssues = fs2.existsSync(issuesFile);
2229
2318
  const isActive = issueFiles.includes(issuesFile);
2230
2319
  let issueCount = 0;
@@ -2318,7 +2407,7 @@ data: ${message}
2318
2407
  }
2319
2408
  targetFile = issueFiles[targetIndex];
2320
2409
  }
2321
- const pebbleDir = targetFile ? path2.dirname(targetFile) : getOrCreatePebbleDir();
2410
+ const pebbleDir = targetFile ? path3.dirname(targetFile) : getOrCreatePebbleDir();
2322
2411
  const config = getConfig(pebbleDir);
2323
2412
  const { title, type, priority, description, parent } = req.body;
2324
2413
  if (!title || typeof title !== "string") {
@@ -2509,7 +2598,7 @@ data: ${message}
2509
2598
  return;
2510
2599
  }
2511
2600
  issue = localIssue;
2512
- targetFile = path2.join(pebbleDir, "issues.jsonl");
2601
+ targetFile = path3.join(pebbleDir, "issues.jsonl");
2513
2602
  }
2514
2603
  const { title, type, priority, status, description, parent, relatedTo } = req.body;
2515
2604
  const updates = {};
@@ -2637,7 +2726,7 @@ data: ${message}
2637
2726
  return;
2638
2727
  }
2639
2728
  issue = localIssue;
2640
- targetFile = path2.join(pebbleDir, "issues.jsonl");
2729
+ targetFile = path3.join(pebbleDir, "issues.jsonl");
2641
2730
  }
2642
2731
  if (issue.status === "closed") {
2643
2732
  res.status(400).json({ error: "Issue is already closed" });
@@ -2711,7 +2800,7 @@ data: ${message}
2711
2800
  }
2712
2801
  } else {
2713
2802
  const pebbleDir = getOrCreatePebbleDir();
2714
- appendEventToFile(autoCloseEvent, path2.join(pebbleDir, "issues.jsonl"));
2803
+ appendEventToFile(autoCloseEvent, path3.join(pebbleDir, "issues.jsonl"));
2715
2804
  }
2716
2805
  autoClosed = { id: targetIssue.id, title: targetIssue.title };
2717
2806
  }
@@ -2751,7 +2840,7 @@ data: ${message}
2751
2840
  return;
2752
2841
  }
2753
2842
  issue = localIssue;
2754
- targetFile = path2.join(pebbleDir, "issues.jsonl");
2843
+ targetFile = path3.join(pebbleDir, "issues.jsonl");
2755
2844
  }
2756
2845
  if (issue.status !== "closed") {
2757
2846
  res.status(400).json({ error: "Issue is not closed" });
@@ -2799,7 +2888,7 @@ data: ${message}
2799
2888
  return;
2800
2889
  }
2801
2890
  issue = localIssue;
2802
- targetFile = path2.join(pebbleDir, "issues.jsonl");
2891
+ targetFile = path3.join(pebbleDir, "issues.jsonl");
2803
2892
  }
2804
2893
  const { text, author } = req.body;
2805
2894
  if (!text || typeof text !== "string" || text.trim() === "") {
@@ -2851,7 +2940,7 @@ data: ${message}
2851
2940
  return;
2852
2941
  }
2853
2942
  issue = localIssue;
2854
- targetFile = path2.join(pebbleDir, "issues.jsonl");
2943
+ targetFile = path3.join(pebbleDir, "issues.jsonl");
2855
2944
  }
2856
2945
  const { blockerId } = req.body;
2857
2946
  if (!blockerId) {
@@ -2925,7 +3014,7 @@ data: ${message}
2925
3014
  return;
2926
3015
  }
2927
3016
  issue = localIssue;
2928
- targetFile = path2.join(pebbleDir, "issues.jsonl");
3017
+ targetFile = path3.join(pebbleDir, "issues.jsonl");
2929
3018
  }
2930
3019
  let resolvedBlockerId;
2931
3020
  if (isMultiWorktree()) {
@@ -2963,12 +3052,12 @@ data: ${message}
2963
3052
  res.status(500).json({ error: error.message });
2964
3053
  }
2965
3054
  });
2966
- const __filename2 = fileURLToPath(import.meta.url);
2967
- const __dirname2 = path2.dirname(__filename2);
2968
- const uiPath = path2.resolve(__dirname2, "../ui");
3055
+ const __filename3 = fileURLToPath(import.meta.url);
3056
+ const __dirname3 = path3.dirname(__filename3);
3057
+ const uiPath = path3.resolve(__dirname3, "../ui");
2969
3058
  app.use(express.static(uiPath));
2970
3059
  app.get("*", (_req, res) => {
2971
- res.sendFile(path2.join(uiPath, "index.html"));
3060
+ res.sendFile(path3.join(uiPath, "index.html"));
2972
3061
  });
2973
3062
  const requestedPort = parseInt(options.port, 10);
2974
3063
  const actualPort = await findAvailablePort(requestedPort);
@@ -2992,7 +3081,7 @@ data: ${message}
2992
3081
  // src/cli/commands/import.ts
2993
3082
  import * as fs3 from "fs";
2994
3083
  import * as readline from "readline";
2995
- import * as path3 from "path";
3084
+ import * as path4 from "path";
2996
3085
  function importCommand(program2) {
2997
3086
  program2.command("import <file>").description("Import issues from a Beads issues.jsonl file").option("--dry-run", "Show what would be imported without writing").option("--prefix <prefix>", "Override the ID prefix (default: derive from folder name)").action(async (file, options) => {
2998
3087
  const pretty = program2.opts().pretty ?? false;
@@ -3005,7 +3094,7 @@ function importCommand(program2) {
3005
3094
  console.log(pretty ? "No issues found in file." : formatJson({ imported: 0 }));
3006
3095
  return;
3007
3096
  }
3008
- const prefix = options.prefix ?? derivePrefix(path3.basename(process.cwd()));
3097
+ const prefix = options.prefix ?? derivePrefix(path4.basename(process.cwd()));
3009
3098
  const { events, idMap, stats } = convertToPebbleEvents(beadsIssues, prefix);
3010
3099
  if (options.dryRun) {
3011
3100
  if (pretty) {
@@ -3183,7 +3272,7 @@ function generateSuffix() {
3183
3272
 
3184
3273
  // src/cli/commands/merge.ts
3185
3274
  import * as fs4 from "fs";
3186
- import * as path4 from "path";
3275
+ import * as path5 from "path";
3187
3276
  function mergeEvents(filePaths) {
3188
3277
  const seen = /* @__PURE__ */ new Map();
3189
3278
  for (const filePath of filePaths) {
@@ -3226,7 +3315,7 @@ function mergeCommand(program2) {
3226
3315
  const pretty = program2.opts().pretty ?? false;
3227
3316
  const filePaths = [];
3228
3317
  for (const file of files) {
3229
- const resolved = path4.resolve(process.cwd(), file);
3318
+ const resolved = path5.resolve(process.cwd(), file);
3230
3319
  if (!fs4.existsSync(resolved)) {
3231
3320
  console.error(`Error: File not found: ${file}`);
3232
3321
  process.exit(1);
@@ -3628,7 +3717,7 @@ function verificationsCommand(program2) {
3628
3717
  }
3629
3718
 
3630
3719
  // src/cli/commands/init.ts
3631
- import * as path5 from "path";
3720
+ import * as path6 from "path";
3632
3721
  function initCommand(program2) {
3633
3722
  program2.command("init").description("Initialize a new .pebble directory in the current directory").option("--force", "Re-initialize even if .pebble already exists").action((options) => {
3634
3723
  const existing = discoverPebbleDir();
@@ -3644,17 +3733,26 @@ function initCommand(program2) {
3644
3733
  console.log(JSON.stringify({
3645
3734
  initialized: true,
3646
3735
  path: pebbleDir,
3647
- configPath: path5.join(pebbleDir, "config.json"),
3648
- issuesPath: path5.join(pebbleDir, "issues.jsonl")
3736
+ configPath: path6.join(pebbleDir, "config.json"),
3737
+ issuesPath: path6.join(pebbleDir, "issues.jsonl")
3649
3738
  }));
3650
3739
  });
3651
3740
  }
3652
3741
 
3653
3742
  // src/cli/index.ts
3743
+ var __filename2 = fileURLToPath2(import.meta.url);
3744
+ var __dirname2 = dirname2(__filename2);
3745
+ var packageJson = JSON.parse(readFileSync2(join3(__dirname2, "../../package.json"), "utf-8"));
3654
3746
  var program = new Command();
3655
- program.name("pebble").description("A lightweight JSONL-based issue tracker").version("0.1.0");
3747
+ program.name("pebble").description("A lightweight JSONL-based issue tracker").version(packageJson.version);
3656
3748
  program.option("-P, --pretty", "Human-readable output (default: JSON)");
3657
3749
  program.option("--json", "JSON output (this is the default, flag not needed)");
3750
+ program.option("--local", "Use local .pebble directory even in a git worktree");
3751
+ program.hook("preAction", () => {
3752
+ if (program.opts().local) {
3753
+ process.env.PEBBLE_LOCAL = "1";
3754
+ }
3755
+ });
3658
3756
  createCommand(program);
3659
3757
  updateCommand(program);
3660
3758
  closeCommand(program);