pipe-kan 0.26.0 → 0.27.0

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/pipe-kan.js CHANGED
@@ -1,10 +1,143 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/server.ts
4
- import { readFileSync as readFileSync4, writeSync } from "node:fs";
4
+ import { readFileSync as readFileSync6, writeSync } from "node:fs";
5
5
  import { createServer } from "node:http";
6
- import { tmpdir as tmpdir2 } from "node:os";
7
- import { join as join8 } from "node:path";
6
+ import { tmpdir as tmpdir3 } from "node:os";
7
+ import { join as join10 } from "node:path";
8
+
9
+ // src/app.ts
10
+ import { readFileSync as readFileSync2 } from "node:fs";
11
+ import { availableParallelism as availableParallelism2, homedir as homedir2, tmpdir } from "node:os";
12
+ import { join as join3 } from "node:path";
13
+
14
+ // src/target-end.ts
15
+ var EXACT_NAME = "Target End Date";
16
+ var ALIAS_NAMES = new Set(["Target End", "Target end"]);
17
+ var NAMED_FIELD_KEYS = [
18
+ "Target End Date",
19
+ "Target End",
20
+ "Target end",
21
+ "targetEndDate",
22
+ "targetenddate",
23
+ "targetEnd",
24
+ "targetend"
25
+ ];
26
+ function isTargetStart(name) {
27
+ const folded = name.trim().toLowerCase();
28
+ return folded === "target start" || folded === "target start date";
29
+ }
30
+ function asNames(raw) {
31
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
32
+ return;
33
+ const names = {};
34
+ for (const [id, value] of Object.entries(raw)) {
35
+ if (typeof value === "string" && value.trim())
36
+ names[id] = value.trim();
37
+ }
38
+ return Object.keys(names).length ? names : undefined;
39
+ }
40
+ function resolveTargetEndFieldId(names) {
41
+ if (!names)
42
+ return;
43
+ let alias;
44
+ for (const [id, name] of Object.entries(names)) {
45
+ if (!id.startsWith("customfield_"))
46
+ continue;
47
+ if (isTargetStart(name))
48
+ continue;
49
+ if (name === EXACT_NAME)
50
+ return id;
51
+ if (ALIAS_NAMES.has(name) && !alias)
52
+ alias = id;
53
+ }
54
+ return alias;
55
+ }
56
+ function isoDay(value) {
57
+ if (typeof value === "string" && value.trim()) {
58
+ const text = value.trim();
59
+ const day = /^(\d{4})-(\d{2})-(\d{2})/.exec(text);
60
+ if (day)
61
+ return `${day[1]}-${day[2]}-${day[3]}`;
62
+ const parsed = Date.parse(text);
63
+ if (Number.isNaN(parsed))
64
+ return;
65
+ const date = new Date(parsed);
66
+ const y = date.getFullYear();
67
+ const m = String(date.getMonth() + 1).padStart(2, "0");
68
+ const d = String(date.getDate()).padStart(2, "0");
69
+ return `${y}-${m}-${d}`;
70
+ }
71
+ if (value && typeof value === "object") {
72
+ const item = value;
73
+ return isoDay(item.value) ?? isoDay(item.date);
74
+ }
75
+ return;
76
+ }
77
+ function issueTargetEnd(fields, opts = {}) {
78
+ if (!fields)
79
+ return;
80
+ for (const key of NAMED_FIELD_KEYS) {
81
+ const day = isoDay(fields[key]);
82
+ if (day)
83
+ return day;
84
+ }
85
+ const mapped = resolveTargetEndFieldId(opts.names) ?? opts.fieldId;
86
+ if (mapped && mapped.startsWith("customfield_")) {
87
+ const day = isoDay(fields[mapped]);
88
+ if (day)
89
+ return day;
90
+ }
91
+ return;
92
+ }
93
+ function mergeViewIntoIssue(listIssue, viewIssue) {
94
+ if (!viewIssue || typeof viewIssue !== "object")
95
+ return listIssue;
96
+ if (!listIssue || typeof listIssue !== "object")
97
+ return viewIssue;
98
+ const list = listIssue;
99
+ const view = viewIssue;
100
+ const names = { ...asNames(list.names), ...asNames(view.names) };
101
+ return {
102
+ ...list,
103
+ ...view,
104
+ ...Object.keys(names).length ? { names } : {},
105
+ fields: { ...list.fields, ...view.fields }
106
+ };
107
+ }
108
+ function unquoteYaml(value) {
109
+ const trimmed = value.trim();
110
+ if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
111
+ return trimmed.slice(1, -1).trim();
112
+ }
113
+ return trimmed;
114
+ }
115
+ function targetEndFieldIdFromJiraConfig(yaml) {
116
+ if (!yaml.trim())
117
+ return;
118
+ const names = {};
119
+ let name;
120
+ let key;
121
+ const flush = () => {
122
+ if (name && key)
123
+ names[key] = name;
124
+ name = undefined;
125
+ key = undefined;
126
+ };
127
+ for (const rawLine of yaml.split(/\r?\n/)) {
128
+ const line = rawLine.replace(/\t/g, " ");
129
+ if (/^\s*-\s*$/.test(line) || /^\s*-\s+\S/.test(line))
130
+ flush();
131
+ const nameMatch = line.match(/^\s*-?\s*name:\s*(.+?)\s*$/);
132
+ if (nameMatch)
133
+ name = unquoteYaml(nameMatch[1] ?? "");
134
+ const keyMatch = line.match(/^\s*-?\s*key:\s*['"]?(customfield_\d+)['"]?/);
135
+ if (keyMatch)
136
+ key = keyMatch[1];
137
+ }
138
+ flush();
139
+ return resolveTargetEndFieldId(names);
140
+ }
8
141
 
9
142
  // src/board.ts
10
143
  function formatDueDate(value) {
@@ -20,19 +153,6 @@ function formatDueDate(value) {
20
153
  year: "numeric"
21
154
  });
22
155
  }
23
- function formatTargetEnd(value) {
24
- if (typeof value !== "string" || !value)
25
- return;
26
- const day = /^(\d{4})-(\d{2})-(\d{2})/.exec(value);
27
- const date = day ? new Date(Number(day[1]), Number(day[2]) - 1, Number(day[3])) : new Date(value);
28
- if (Number.isNaN(date.getTime()))
29
- return;
30
- return date.toLocaleDateString("en-US", {
31
- month: "short",
32
- day: "numeric",
33
- year: "numeric"
34
- });
35
- }
36
156
  function createdAt(fields) {
37
157
  return typeof fields?.created === "string" && fields.created ? fields.created : undefined;
38
158
  }
@@ -101,31 +221,6 @@ function issueAssignee(fields) {
101
221
  }
102
222
  return;
103
223
  }
104
- function issueTargetEnd(fields) {
105
- if (!fields)
106
- return;
107
- const candidates = [
108
- fields.targetEnd,
109
- fields.targetend,
110
- fields.targetEndDate,
111
- fields.targetenddate,
112
- fields["Target End"],
113
- fields["Target End Date"]
114
- ];
115
- const direct = candidates.find((value) => typeof value === "string" && value.trim());
116
- if (direct)
117
- return formatTargetEnd(direct);
118
- for (const [key, value] of Object.entries(fields)) {
119
- if (!key.startsWith("customfield_"))
120
- continue;
121
- if (typeof value === "string" && value.trim()) {
122
- const formatted = formatTargetEnd(value.trim());
123
- if (formatted)
124
- return formatted;
125
- }
126
- }
127
- return;
128
- }
129
224
  function toEpic(face) {
130
225
  return {
131
226
  key: face.key,
@@ -135,15 +230,19 @@ function toEpic(face) {
135
230
  ...face.assignee ? { assignee: face.assignee } : {},
136
231
  ...face.dueDate ? { dueDate: face.dueDate } : {},
137
232
  ...face.targetEnd ? { targetEnd: face.targetEnd } : {},
138
- ...face.labels ? { labels: face.labels } : {}
233
+ ...face.labels ? { labels: face.labels } : {},
234
+ ...face.created ? { created: face.created } : {}
139
235
  };
140
236
  }
141
- function toCard(issue, key) {
237
+ function toCard(issue, key, fieldId) {
142
238
  const summary = typeof issue.fields?.summary === "string" ? issue.fields.summary : "";
143
239
  const priority = typeof issue.fields?.priority?.name === "string" ? issue.fields.priority.name : undefined;
144
240
  const assignee = issueAssignee(issue.fields);
145
241
  const dueDate = formatDueDate(issue.fields?.duedate);
146
- const targetEnd = issueTargetEnd(issue.fields);
242
+ const targetEnd = issueTargetEnd(issue.fields, {
243
+ names: asNames(issue.names),
244
+ fieldId
245
+ });
147
246
  const type = issueType(issue.fields);
148
247
  const epic = epicKey(issue.fields);
149
248
  const labels = issueLabels(issue.fields);
@@ -161,7 +260,7 @@ function toCard(issue, key) {
161
260
  ...created ? { created } : {}
162
261
  };
163
262
  }
164
- function issuesToBoard(raw) {
263
+ function issuesToBoard(raw, opts = {}) {
165
264
  if (!Array.isArray(raw)) {
166
265
  throw new Error("jira-cli --raw payload must be a JSON array");
167
266
  }
@@ -185,7 +284,7 @@ function issuesToBoard(raw) {
185
284
  const status = typeof issue?.fields?.status?.name === "string" ? issue.fields.status.name : "";
186
285
  if (!key || !status)
187
286
  continue;
188
- const card = toCard(issue, key);
287
+ const card = toCard(issue, key, opts.targetEndFieldId);
189
288
  if ((card.type ?? "").toLowerCase() === "epic") {
190
289
  rememberEpic(toEpic({ ...card, status: issueStatus(issue.fields) }));
191
290
  continue;
@@ -217,6 +316,8 @@ function mergeEpics(listed, fromBoard) {
217
316
  Object.assign(existing, epic);
218
317
  if (!existing.targetEnd && epic.targetEnd)
219
318
  existing.targetEnd = epic.targetEnd;
319
+ if (!existing.created && epic.created)
320
+ existing.created = epic.created;
220
321
  }
221
322
  return [...byKey.values()];
222
323
  }
@@ -1001,9 +1102,40 @@ function flattenIssue(raw, url) {
1001
1102
  return [...rows, ...rest];
1002
1103
  }
1003
1104
 
1105
+ // src/field-map.ts
1106
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
1107
+ import { homedir } from "node:os";
1108
+ import { dirname, join as join2 } from "node:path";
1109
+ function jiraCliConfigPath(env = process.env) {
1110
+ const explicit = env.JIRA_CONFIG_FILE?.trim();
1111
+ if (explicit)
1112
+ return explicit;
1113
+ const xdg = env.XDG_CONFIG_HOME?.trim();
1114
+ if (xdg)
1115
+ return join2(xdg, ".jira", ".config.yml");
1116
+ return join2(homedir(), ".config", ".jira", ".config.yml");
1117
+ }
1118
+ function readTargetEndFieldMap(path) {
1119
+ try {
1120
+ const raw = JSON.parse(readFileSync(path, "utf8"));
1121
+ const targetEnd = typeof raw.targetEnd === "string" && raw.targetEnd.startsWith("customfield_") ? raw.targetEnd : undefined;
1122
+ return targetEnd ? { targetEnd } : {};
1123
+ } catch {
1124
+ return {};
1125
+ }
1126
+ }
1127
+ function writeTargetEndFieldMap(path, map) {
1128
+ const targetEnd = typeof map.targetEnd === "string" && map.targetEnd.startsWith("customfield_") ? map.targetEnd : undefined;
1129
+ if (!targetEnd)
1130
+ return;
1131
+ mkdirSync(dirname(path), { recursive: true });
1132
+ writeFileSync(path, `${JSON.stringify({ targetEnd }, null, 2)}
1133
+ `);
1134
+ }
1135
+
1004
1136
  // src/app.ts
1005
- function columnsOf(raw) {
1006
- return Object.fromEntries(issuesToBoard(raw).columns.map((column) => [column.title, column.cards]));
1137
+ function columnsOf(raw, fieldId) {
1138
+ return Object.fromEntries(issuesToBoard(raw, { targetEndFieldId: fieldId }).columns.map((column) => [column.title, column.cards]));
1007
1139
  }
1008
1140
  function stampMissingEpic(board, epic) {
1009
1141
  for (const column of board.columns) {
@@ -1014,21 +1146,76 @@ function stampMissingEpic(board, epic) {
1014
1146
  }
1015
1147
  return board;
1016
1148
  }
1017
- function cardsOf(raw) {
1149
+ function cardsOf(raw, fieldId) {
1018
1150
  if (!Array.isArray(raw))
1019
1151
  return [];
1020
- return issuesToBoard(raw).columns.flatMap((column) => column.cards);
1152
+ return issuesToBoard(raw, { targetEndFieldId: fieldId }).columns.flatMap((column) => column.cards);
1153
+ }
1154
+ function issueKeyOf(issue) {
1155
+ if (!issue || typeof issue !== "object" || !("key" in issue))
1156
+ return;
1157
+ return typeof issue.key === "string" ? issue.key : undefined;
1158
+ }
1159
+ function viewConcurrency() {
1160
+ const env = Number(process.env.PIPE_KAN_CHILDREN_CONCURRENCY);
1161
+ if (!Number.isNaN(env) && env > 0)
1162
+ return env;
1163
+ return Math.max(3, Math.min(10, availableParallelism2()));
1164
+ }
1165
+ function defaultFieldMapPath() {
1166
+ if (process.env.PIPE_KAN_FIELD_MAP)
1167
+ return process.env.PIPE_KAN_FIELD_MAP;
1168
+ if (process.env.VITEST) {
1169
+ return join3(tmpdir(), `pipe-kan-field-map-${process.pid}-${Math.random().toString(16).slice(2)}.json`);
1170
+ }
1171
+ return join3(homedir2(), ".pipe-kan", "field-map.json");
1172
+ }
1173
+ function defaultJiraConfigPath() {
1174
+ if (process.env.JIRA_CONFIG_FILE)
1175
+ return process.env.JIRA_CONFIG_FILE;
1176
+ return join3(process.env.XDG_CONFIG_HOME || join3(homedir2(), ".config"), ".jira", ".config.yml");
1177
+ }
1178
+ function safeRead(path) {
1179
+ if (!path)
1180
+ return;
1181
+ try {
1182
+ return readFileSync2(path, "utf8");
1183
+ } catch {
1184
+ return;
1185
+ }
1186
+ }
1187
+ async function poolMap(items, concurrency, worker) {
1188
+ const results = new Array(items.length);
1189
+ let next = 0;
1190
+ async function run() {
1191
+ while (next < items.length) {
1192
+ const index = next++;
1193
+ results[index] = await worker(items[index]);
1194
+ }
1195
+ }
1196
+ const workers = Math.min(Math.max(1, concurrency), Math.max(1, items.length));
1197
+ await Promise.all(Array.from({ length: workers }, run));
1198
+ return results;
1021
1199
  }
1022
1200
  function createApp(opts) {
1023
1201
  const cli = opts.cli ?? createStoreCli(opts.store);
1202
+ const fieldMapPath = opts.fieldMapPath ?? defaultFieldMapPath();
1203
+ const jiraConfigPath = opts.jiraConfigPath ?? defaultJiraConfigPath();
1024
1204
  let flags = opts.flags ?? DEFAULT_FLAGS;
1025
1205
  let payload = [];
1026
1206
  let epicsPayload = [];
1027
1207
  let childrenRaw = [];
1028
1208
  let hasChildrenCache = false;
1029
1209
  let childrenError;
1210
+ let targetEndFieldId = readTargetEndFieldMap(fieldMapPath).targetEnd ?? targetEndFieldIdFromJiraConfig(safeRead(jiraConfigPath) ?? "");
1211
+ function boardOpts() {
1212
+ return { targetEndFieldId };
1213
+ }
1214
+ function toBoard(raw) {
1215
+ return issuesToBoard(raw, boardOpts());
1216
+ }
1030
1217
  function listedEpicKeys() {
1031
- return issuesToBoard(epicsPayload).epics.map((epic) => epic.key);
1218
+ return toBoard(epicsPayload).epics.map((epic) => epic.key);
1032
1219
  }
1033
1220
  function cacheFromStore() {
1034
1221
  epicsPayload = opts.store.list(flagsToJql("-tEpic"));
@@ -1038,7 +1225,56 @@ function createApp(opts) {
1038
1225
  }
1039
1226
  function currentStatus(key) {
1040
1227
  const fromPayload = payload.find((issue) => issue.key === key)?.fields?.status?.name;
1041
- return fromPayload ?? issuesToBoard(epicsPayload).epics.find((epic) => epic.key === key)?.status ?? app.board().epics.find((epic) => epic.key === key)?.status;
1228
+ return fromPayload ?? toBoard(epicsPayload).epics.find((epic) => epic.key === key)?.status ?? app.board().epics.find((epic) => epic.key === key)?.status;
1229
+ }
1230
+ function rememberTargetEndId(issues) {
1231
+ for (const issue of issues) {
1232
+ if (!issue || typeof issue !== "object")
1233
+ continue;
1234
+ const row = issue;
1235
+ const id = resolveTargetEndFieldId(asNames(row.names));
1236
+ if (id) {
1237
+ targetEndFieldId = id;
1238
+ writeTargetEndFieldMap(fieldMapPath, { targetEnd: id });
1239
+ return;
1240
+ }
1241
+ }
1242
+ const fromConfig = targetEndFieldIdFromJiraConfig(safeRead(jiraConfigPath) ?? "");
1243
+ if (!fromConfig)
1244
+ return;
1245
+ targetEndFieldId = fromConfig;
1246
+ writeTargetEndFieldMap(fieldMapPath, { targetEnd: fromConfig });
1247
+ }
1248
+ async function hydrateRaw(raw) {
1249
+ if (!Array.isArray(raw) || raw.length === 0)
1250
+ return Array.isArray(raw) ? raw : [];
1251
+ const keys = [...new Set(raw.map(issueKeyOf).filter((key) => Boolean(key)))];
1252
+ if (!keys.length)
1253
+ return raw;
1254
+ const concurrency = viewConcurrency();
1255
+ console.log(`Refresh view-raw ${keys.length} issues; concurrency ${concurrency}`);
1256
+ const views = await poolMap(keys, concurrency, async (key) => {
1257
+ try {
1258
+ return JSON.parse(await cli.view(key));
1259
+ } catch (err) {
1260
+ const message = err instanceof Error ? err.message : String(err);
1261
+ console.log(`Refresh view ${key} failed; ${message}`);
1262
+ return null;
1263
+ }
1264
+ });
1265
+ const byKey = new Map;
1266
+ for (const view of views) {
1267
+ const key = issueKeyOf(view);
1268
+ if (key)
1269
+ byKey.set(key, view);
1270
+ }
1271
+ const merged = raw.map((issue) => {
1272
+ const key = issueKeyOf(issue);
1273
+ const view = key ? byKey.get(key) : undefined;
1274
+ return view ? mergeViewIntoIssue(issue, view) : issue;
1275
+ });
1276
+ rememberTargetEndId(merged);
1277
+ return merged;
1042
1278
  }
1043
1279
  async function tryMove(key, status) {
1044
1280
  const current = currentStatus(key);
@@ -1053,11 +1289,11 @@ function createApp(opts) {
1053
1289
  return flags;
1054
1290
  },
1055
1291
  board() {
1056
- const board = issuesToBoard(payload);
1292
+ const board = toBoard(payload);
1057
1293
  return {
1058
1294
  columns: board.columns,
1059
- epics: mergeEpics(issuesToBoard(epicsPayload).epics, board.epics),
1060
- ...hasChildrenCache ? { children: columnsOf(childrenRaw) } : {},
1295
+ epics: mergeEpics(toBoard(epicsPayload).epics, board.epics),
1296
+ ...hasChildrenCache ? { children: columnsOf(childrenRaw, targetEndFieldId) } : {},
1061
1297
  ...childrenError ? { error: childrenError } : {}
1062
1298
  };
1063
1299
  },
@@ -1069,6 +1305,7 @@ function createApp(opts) {
1069
1305
  childrenError = undefined;
1070
1306
  if (hydrateOpts?.fromStore)
1071
1307
  cacheFromStore();
1308
+ rememberTargetEndId([...payload, ...epicsPayload, ...childrenRaw]);
1072
1309
  return app.board();
1073
1310
  },
1074
1311
  async refresh(next) {
@@ -1078,17 +1315,17 @@ function createApp(opts) {
1078
1315
  try {
1079
1316
  const issues = await cli.list(flags);
1080
1317
  const epics = await cli.listEpics(flags);
1081
- const nextPayload = JSON.parse(issues);
1082
- const nextEpics = JSON.parse(epics);
1083
- const keys = issuesToBoard(nextEpics).epics.map((epic) => epic.key);
1084
- const issueCount = Array.isArray(nextPayload) ? nextPayload.length : 0;
1318
+ const listedPayload = JSON.parse(issues);
1319
+ const listedEpics = JSON.parse(epics);
1320
+ const keys = toBoard(listedEpics).epics.map((epic) => epic.key);
1321
+ const issueCount = Array.isArray(listedPayload) ? listedPayload.length : 0;
1085
1322
  console.log(`Refresh listed ${issueCount} issues, ${keys.length} epics`);
1086
1323
  let nextChildren = [];
1087
1324
  let nextHasCache = false;
1088
1325
  let nextError;
1089
1326
  try {
1090
1327
  nextChildren = JSON.parse(await cli.listChildren(keys));
1091
- const cards = cardsOf(nextChildren);
1328
+ const cards = cardsOf(nextChildren, targetEndFieldId);
1092
1329
  if (cards.length > 0 && !cards.some((card) => card.epic)) {
1093
1330
  console.log(`Refresh children missing Epic keys; skip ${keys.length} per-Epic lists`);
1094
1331
  nextChildren = [];
@@ -1103,9 +1340,9 @@ function createApp(opts) {
1103
1340
  nextError = err instanceof Error ? err.message : "Epic children list failed";
1104
1341
  console.log("Refresh children failed; keeping existing children", nextError);
1105
1342
  }
1106
- payload = nextPayload;
1107
- epicsPayload = nextEpics;
1108
- childrenRaw = nextChildren;
1343
+ payload = await hydrateRaw(listedPayload);
1344
+ epicsPayload = await hydrateRaw(listedEpics);
1345
+ childrenRaw = nextHasCache ? await hydrateRaw(nextChildren) : nextChildren;
1109
1346
  hasChildrenCache = nextHasCache;
1110
1347
  childrenError = nextError;
1111
1348
  return app.board();
@@ -1116,12 +1353,12 @@ function createApp(opts) {
1116
1353
  }
1117
1354
  },
1118
1355
  async children(epic) {
1119
- const listed = issuesToBoard(epicsPayload).epics;
1356
+ const listed = toBoard(epicsPayload).epics;
1120
1357
  if (!listed.some((e) => e.key === epic)) {
1121
1358
  return { columns: [], epics: [] };
1122
1359
  }
1123
1360
  if (hasChildrenCache) {
1124
- const cards = Object.fromEntries(Object.entries(columnsOf(childrenRaw)).map(([title, list]) => [
1361
+ const cards = Object.fromEntries(Object.entries(columnsOf(childrenRaw, targetEndFieldId)).map(([title, list]) => [
1125
1362
  title,
1126
1363
  list.filter((card) => card.epic === epic).map((card) => ({
1127
1364
  ...card,
@@ -1132,7 +1369,7 @@ function createApp(opts) {
1132
1369
  if (columns.length)
1133
1370
  return { columns, epics: [] };
1134
1371
  }
1135
- return stampMissingEpic(issuesToBoard(JSON.parse(await cli.listEpic(epic, flags))), epic);
1372
+ return stampMissingEpic(toBoard(JSON.parse(await cli.listEpic(epic, flags))), epic);
1136
1373
  },
1137
1374
  async move(key, status) {
1138
1375
  const result = await tryMove(key, status);
@@ -1194,7 +1431,12 @@ async function createBoardApp(opts) {
1194
1431
  token: env.JIRA_API_TOKEN,
1195
1432
  flags
1196
1433
  }) : createStoreCli(store, flags);
1197
- const app = createApp({ store, cli, flags });
1434
+ const app = createApp({
1435
+ store,
1436
+ cli,
1437
+ flags,
1438
+ jiraConfigPath: jiraCliConfigPath(env)
1439
+ });
1198
1440
  if (opts.piped) {
1199
1441
  app.hydrate(opts.raw);
1200
1442
  } else {
@@ -1398,10 +1640,10 @@ function formatCard(card) {
1398
1640
  }
1399
1641
 
1400
1642
  // src/server/agent/config.ts
1401
- import { existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync } from "node:fs";
1402
- import { homedir } from "node:os";
1403
- import { delimiter as delimiter2, dirname, join as join2 } from "node:path";
1404
- var DEFAULT_CONFIG_PATH = join2(homedir(), ".config", "pipe-kan", "agent.json");
1643
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
1644
+ import { homedir as homedir3 } from "node:os";
1645
+ import { delimiter as delimiter2, dirname as dirname2, join as join4 } from "node:path";
1646
+ var DEFAULT_CONFIG_PATH = join4(homedir3(), ".config", "pipe-kan", "agent.json");
1405
1647
  var FALLBACK_MODELS = {
1406
1648
  cursor: [
1407
1649
  { id: "composer-2", name: "Composer 2" },
@@ -1448,7 +1690,7 @@ function commandOnPath(command, env = process.env) {
1448
1690
  for (const dir of pathVar.split(delimiter2)) {
1449
1691
  if (!dir)
1450
1692
  continue;
1451
- if (existsSync2(join2(dir, command)))
1693
+ if (existsSync2(join4(dir, command)))
1452
1694
  return true;
1453
1695
  }
1454
1696
  return false;
@@ -1457,7 +1699,7 @@ function loadAgentConfig(path = agentConfigPath()) {
1457
1699
  if (!existsSync2(path))
1458
1700
  return structuredClone(DEFAULT_CONFIG);
1459
1701
  try {
1460
- const raw = JSON.parse(readFileSync(path, "utf8"));
1702
+ const raw = JSON.parse(readFileSync3(path, "utf8"));
1461
1703
  return mergeConfig(raw);
1462
1704
  } catch {
1463
1705
  return structuredClone(DEFAULT_CONFIG);
@@ -1476,7 +1718,7 @@ function saveAgentConfig(patch, path = agentConfigPath()) {
1476
1718
  }
1477
1719
  }
1478
1720
  ensureAgentConfigDir(path);
1479
- writeFileSync(path, `${JSON.stringify(next, null, 2)}
1721
+ writeFileSync2(path, `${JSON.stringify(next, null, 2)}
1480
1722
  `);
1481
1723
  return next;
1482
1724
  }
@@ -1492,16 +1734,16 @@ function mergeConfig(raw) {
1492
1734
  };
1493
1735
  }
1494
1736
  function ensureAgentConfigDir(path = agentConfigPath()) {
1495
- const dir = dirname(path);
1737
+ const dir = dirname2(path);
1496
1738
  if (!existsSync2(dir))
1497
- mkdirSync(dir, { recursive: true });
1739
+ mkdirSync2(dir, { recursive: true });
1498
1740
  }
1499
1741
 
1500
1742
  // src/server/agent/session.ts
1501
1743
  import { spawn as spawn2 } from "node:child_process";
1502
1744
  import { mkdtempSync, rmSync } from "node:fs";
1503
- import { tmpdir } from "node:os";
1504
- import { join as join3 } from "node:path";
1745
+ import { tmpdir as tmpdir2 } from "node:os";
1746
+ import { join as join5 } from "node:path";
1505
1747
  import { Readable } from "node:stream";
1506
1748
 
1507
1749
  // node_modules/@agentclientprotocol/sdk/dist/schema/index.js
@@ -11227,7 +11469,7 @@ class AcpSession {
11227
11469
  executeTool = null;
11228
11470
  stderr = "";
11229
11471
  selectedModelId = null;
11230
- workspace = mkdtempSync(join3(tmpdir(), "pipe-kan-agent-"));
11472
+ workspace = mkdtempSync(join5(tmpdir2(), "pipe-kan-agent-"));
11231
11473
  constructor(config) {
11232
11474
  this.config = config;
11233
11475
  this.id = crypto.randomUUID();
@@ -11539,11 +11781,11 @@ function permissionOptionForDecision(options, decision) {
11539
11781
  }
11540
11782
 
11541
11783
  // src/server/agent/skills.ts
11542
- import { existsSync as existsSync3, readFileSync as readFileSync2, readdirSync } from "node:fs";
11543
- import { homedir as homedir2 } from "node:os";
11544
- import { join as join4 } from "node:path";
11545
- function createSkillRegistry(bundledDir = join4(import.meta.dirname, "..", "..", "..", ".agents", "skills")) {
11546
- const userDir = join4(homedir2(), ".pi", "agent", "skills");
11784
+ import { existsSync as existsSync3, readFileSync as readFileSync4, readdirSync } from "node:fs";
11785
+ import { homedir as homedir4 } from "node:os";
11786
+ import { join as join6 } from "node:path";
11787
+ function createSkillRegistry(bundledDir = join6(import.meta.dirname, "..", "..", "..", ".agents", "skills")) {
11788
+ const userDir = join6(homedir4(), ".pi", "agent", "skills");
11547
11789
  return {
11548
11790
  list() {
11549
11791
  const bundled = listSkills(bundledDir);
@@ -11582,11 +11824,11 @@ function listSkills(dir) {
11582
11824
  return readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => readSkill(skillPath(dir, entry.name), entry.name)).filter((skill) => skill !== undefined);
11583
11825
  }
11584
11826
  function skillPath(dir, id) {
11585
- return join4(dir, id, "SKILL.md");
11827
+ return join6(dir, id, "SKILL.md");
11586
11828
  }
11587
11829
  function readSkill(path, id) {
11588
11830
  try {
11589
- const text = readFileSync2(path, "utf8");
11831
+ const text = readFileSync4(path, "utf8");
11590
11832
  const front = parseFrontMatter(text);
11591
11833
  return {
11592
11834
  id,
@@ -11613,8 +11855,8 @@ function parseFrontMatter(text) {
11613
11855
  }
11614
11856
 
11615
11857
  // src/server/agent/tools.ts
11616
- import { readFileSync as readFileSync3 } from "node:fs";
11617
- import { join as join5 } from "node:path";
11858
+ import { readFileSync as readFileSync5 } from "node:fs";
11859
+ import { join as join7 } from "node:path";
11618
11860
  var TOOLS = [
11619
11861
  {
11620
11862
  name: "board_state",
@@ -11731,7 +11973,7 @@ var EXECUTORS = {
11731
11973
  return { ok: false, error: "Path traversal not allowed" };
11732
11974
  const repoRoot = process.cwd();
11733
11975
  try {
11734
- const text = readFileSync3(join5(repoRoot, relPath), "utf8");
11976
+ const text = readFileSync5(join7(repoRoot, relPath), "utf8");
11735
11977
  return { ok: true, value: { path: relPath, text } };
11736
11978
  } catch (err) {
11737
11979
  return { ok: false, error: String(err) };
@@ -12264,12 +12506,12 @@ function handleRequest(req, res, ctx) {
12264
12506
  }
12265
12507
 
12266
12508
  // src/jira-config.ts
12267
- import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "node:fs";
12268
- import { join as join6 } from "node:path";
12509
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "node:fs";
12510
+ import { join as join8 } from "node:path";
12269
12511
  function writeJiraConfig(dir, server) {
12270
- mkdirSync2(dir, { recursive: true });
12271
- const path = join6(dir, "jira.config.yml");
12272
- writeFileSync2(path, [
12512
+ mkdirSync3(dir, { recursive: true });
12513
+ const path = join8(dir, "jira.config.yml");
12514
+ writeFileSync3(path, [
12273
12515
  "installation: Cloud",
12274
12516
  `server: ${server}`,
12275
12517
  `login: ${ME.emailAddress}`,
@@ -12324,7 +12566,7 @@ function stdinStat() {
12324
12566
 
12325
12567
  // src/ui.ts
12326
12568
  import { createReadStream, existsSync as existsSync4, statSync } from "node:fs";
12327
- import { dirname as dirname2, extname, join as join7, resolve as resolve2, sep } from "node:path";
12569
+ import { dirname as dirname3, extname, join as join9, resolve as resolve2, sep } from "node:path";
12328
12570
  import { fileURLToPath } from "node:url";
12329
12571
  var types = {
12330
12572
  ".css": "text/css; charset=utf-8",
@@ -12338,10 +12580,10 @@ var types = {
12338
12580
  ".woff2": "font/woff2"
12339
12581
  };
12340
12582
  function packageRoot(from = import.meta.url) {
12341
- return resolve2(dirname2(fileURLToPath(from)), "..");
12583
+ return resolve2(dirname3(fileURLToPath(from)), "..");
12342
12584
  }
12343
12585
  function uiDir(root) {
12344
- return join7(root, "dist", "ui");
12586
+ return join9(root, "dist", "ui");
12345
12587
  }
12346
12588
  function inside(root, file) {
12347
12589
  const base = resolve2(root);
@@ -12350,7 +12592,7 @@ function inside(root, file) {
12350
12592
  }
12351
12593
  function sendUi(root, req, res) {
12352
12594
  const ui = uiDir(root);
12353
- const index = join7(ui, "index.html");
12595
+ const index = join9(ui, "index.html");
12354
12596
  if (!existsSync4(index))
12355
12597
  return false;
12356
12598
  const path = new URL(req.url ?? "/", "http://127.0.0.1").pathname;
@@ -12371,7 +12613,7 @@ function announce(line) {
12371
12613
  }
12372
12614
  async function runServer(opts) {
12373
12615
  const piped = await readPipe();
12374
- const raw = piped ?? JSON.parse(readFileSync4(join8(opts.root, "fixtures/issues.json"), "utf8"));
12616
+ const raw = piped ?? JSON.parse(readFileSync6(join10(opts.root, "fixtures/issues.json"), "utf8"));
12375
12617
  const flags = argvToFlags(process.argv);
12376
12618
  const { app, store, kind } = await createBoardApp({
12377
12619
  raw,
@@ -12393,7 +12635,7 @@ async function runServer(opts) {
12393
12635
  const { host, port } = resolveListen();
12394
12636
  await bindListen(server, host, port);
12395
12637
  const origin = `http://127.0.0.1:${port}`;
12396
- const fakeConfig = writeJiraConfig(join8(tmpdir2(), "pipe-kan"), origin);
12638
+ const fakeConfig = writeJiraConfig(join10(tmpdir3(), "pipe-kan"), origin);
12397
12639
  announce(`pipe-kan http://${host}:${port}`);
12398
12640
  announce(`cli ${kind === "jira" ? resolveJiraBin() : "store"}`);
12399
12641
  announce(`Fake Jira ${origin}/rest/api/2/search`);