pipe-kan 0.34.0 → 0.34.2

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/README.md CHANGED
@@ -63,6 +63,8 @@ bunx pipe-kan --plane --projects APH,PULSE,PKAN,PUI,DEC
63
63
 
64
64
  Same `--projects` style as Jira: comma-separated, trimmed, uppercased. CLI flags are startup defaults. The sidebar switcher (O then W) checks Sources, Workspaces, and Projects and refreshes the merged Board. The header project chip opens that same menu. Scope flags still round-trip the selection. Next launch uses argv, else `PLANE_WORKSPACE`, else `personal`.
65
65
 
66
+ Workspace discovery: Plane's public v1 API has no workspace-list endpoint, so the switcher lists every workspace the key can read by probing candidate slugs (startup flags, `PLANE_WORKSPACE`, `PLANE_WORKSPACES`, `PIPE_KAN_WORKSPACES`, plus `personal` and `work`) regardless of the startup scope. The startup flags bar is hidden by default; Display > Show startup flags bar brings it back.
67
+
66
68
  Plane mode adds Initiatives and Projects. Initiatives are modules (health, status, priority, target date) in a list, board, or timeline. Projects show completion, target date, and health. Display is Shift+V. The Filter menu (F) adds view filters, chips, and saved views. Right-click a row for status, assignee, priority, labels, or copy link. Cmd+K jumps to those views; `ow` lists workspaces.
67
69
 
68
70
  When more than one Workspace is selected, issue keys are `workspace/KEY` so `work/PULSE` and `personal/PULSE` stay distinct. Catalog reads are serialized, cached, and keep trailing slashes.
package/dist/pipe-kan.js CHANGED
@@ -1841,6 +1841,7 @@ function sourceOptions(input) {
1841
1841
  // src/plane.ts
1842
1842
  var DEFAULT_PLANE_HOST = "https://plane.tail48fe8.ts.net";
1843
1843
  var DEFAULT_PLANE_WORKSPACE = "personal";
1844
+ var COMMON_WORKSPACE_SLUGS = ["personal", "work"];
1844
1845
  var STATE_GROUPS = ["backlog", "unstarted", "started", "completed", "cancelled"];
1845
1846
  function planeHost(env = process.env) {
1846
1847
  return (env.PLANE_HOST ?? DEFAULT_PLANE_HOST).replace(/\/$/, "");
@@ -2217,6 +2218,44 @@ function createPlaneCli(opts) {
2217
2218
  return;
2218
2219
  return { id: asString(row?.id) ?? slug, name: asString(row?.name) ?? slug, slug };
2219
2220
  }
2221
+ const known = new Set;
2222
+ let probeCache;
2223
+ async function probeWorkspaces() {
2224
+ const ttl = opts.cacheTtlMs ?? 300000;
2225
+ if (ttl > 0 && probeCache && nowFn() - probeCache.at < ttl)
2226
+ return probeCache.rows;
2227
+ const rows = await probeWorkspacesUncached();
2228
+ probeCache = { at: nowFn(), rows };
2229
+ return rows;
2230
+ }
2231
+ async function probeWorkspacesUncached() {
2232
+ const env = opts.env ?? process.env;
2233
+ const fromFlags = parseFlags(`${defaultFlags} ${lastFlags}`);
2234
+ const candidates = new Set([
2235
+ ...known,
2236
+ ...fromFlags.workspaces,
2237
+ ...fromFlags.workspace ? [fromFlags.workspace] : [],
2238
+ ...fromFlags.scopes.map((scope) => scope.workspace),
2239
+ ...env.PLANE_WORKSPACE ? [env.PLANE_WORKSPACE] : [],
2240
+ ...(env.PLANE_WORKSPACES ?? "").split(","),
2241
+ ...(env.PIPE_KAN_WORKSPACES ?? "").split(","),
2242
+ ...COMMON_WORKSPACE_SLUGS
2243
+ ]);
2244
+ const results = await Promise.all([...candidates].map(async (raw) => {
2245
+ const slug = raw.trim();
2246
+ if (!slug || !/^[A-Za-z0-9_-]+$/.test(slug))
2247
+ return;
2248
+ try {
2249
+ await requestJson(queryPath(`/workspaces/${slug}/projects/`, { per_page: "1" }));
2250
+ known.add(slug);
2251
+ return { id: slug, name: slug, slug };
2252
+ } catch {
2253
+ known.delete(slug);
2254
+ return;
2255
+ }
2256
+ }));
2257
+ return results.filter((row) => Boolean(row));
2258
+ }
2220
2259
  function parseWorkspaceList(raw) {
2221
2260
  const rows = Array.isArray(raw) ? raw : asRecord(raw)?.results;
2222
2261
  if (!Array.isArray(rows))
@@ -2290,7 +2329,7 @@ function createPlaneCli(opts) {
2290
2329
  };
2291
2330
  }
2292
2331
  const projectCache = new Map;
2293
- const cacheTtlMs = opts.cacheTtlMs ?? 30000;
2332
+ const cacheTtlMs = opts.cacheTtlMs ?? 300000;
2294
2333
  async function projectsFor(slug) {
2295
2334
  const hit = projectCache.get(slug);
2296
2335
  const now = nowFn();
@@ -2560,17 +2599,17 @@ function createPlaneCli(opts) {
2560
2599
  return loaded.columns;
2561
2600
  },
2562
2601
  async listWorkspaces() {
2602
+ const ttl = opts.cacheTtlMs ?? 300000;
2603
+ if (ttl > 0 && probeCache && nowFn() - probeCache.at < ttl)
2604
+ return probeCache.rows;
2563
2605
  for (const path of ["/users/me/workspaces/", "/workspaces/"]) {
2564
2606
  try {
2565
- return parseWorkspaceList(await requestJson(path)) ?? [];
2566
- } catch (err) {
2567
- const status = err.status;
2568
- if (status === 404 && path === "/users/me/workspaces/")
2569
- continue;
2570
- return [];
2571
- }
2607
+ const listed = parseWorkspaceList(await requestJson(path));
2608
+ if (listed?.length)
2609
+ return listed;
2610
+ } catch {}
2572
2611
  }
2573
- return [];
2612
+ return probeWorkspaces();
2574
2613
  },
2575
2614
  async listProjectIdentifiers(workspace) {
2576
2615
  const slug = workspace.trim();
@@ -2740,6 +2779,7 @@ async function createBoardApp(opts) {
2740
2779
  apiKey: env.PLANE_API_KEY ?? "",
2741
2780
  workspace: planeWorkspace(flags, env),
2742
2781
  flags,
2782
+ env,
2743
2783
  fetch: opts.fetch ?? resolvePlaneFetch(env)
2744
2784
  }) : useJira ? createJiraCli({
2745
2785
  bin,
@@ -14172,9 +14212,15 @@ function handleRequest(req, res, ctx) {
14172
14212
  if (handleAppApi(req, res, ctx.app, { kind: ctx.kind, env: ctx.env, onSource: ctx.switchSource }) || handleAgentApi(req, res, ctx.app)) {
14173
14213
  return true;
14174
14214
  }
14175
- if (ctx.kind === "plane")
14176
- return false;
14177
- return handleFakeJira(req, res, ctx.store);
14215
+ if (ctx.kind !== "plane" && handleFakeJira(req, res, ctx.store))
14216
+ return true;
14217
+ if ((req.url ?? "").split("?")[0].startsWith("/api/")) {
14218
+ res.statusCode = 404;
14219
+ res.setHeader("content-type", "application/json");
14220
+ res.end(JSON.stringify({ error: "unknown API route" }));
14221
+ return true;
14222
+ }
14223
+ return false;
14178
14224
  }
14179
14225
 
14180
14226
  // src/jira-config.ts
@@ -14315,12 +14361,12 @@ async function runServer(opts) {
14315
14361
  live.store = next.store;
14316
14362
  live.kind = next.kind;
14317
14363
  if (next.kind === "plane" || next.kind === "jira") {
14318
- try {
14319
- await refreshFromJira(live.app, next.kind);
14320
- } catch (err) {
14321
- const message = err instanceof Error ? err.message : String(err);
14322
- return { ok: true, kind: live.kind, source, error: message };
14323
- }
14364
+ const app = live.app;
14365
+ const kind = next.kind;
14366
+ refreshFromJira(app, kind).catch((err) => {
14367
+ console.error(`${kind} Refresh after source switch failed`, err);
14368
+ });
14369
+ return { ok: true, kind: live.kind, source, refreshing: true };
14324
14370
  }
14325
14371
  return { ok: true, kind: live.kind, source };
14326
14372
  } catch (err) {
@@ -14340,20 +14386,17 @@ async function runServer(opts) {
14340
14386
  res.end("pipe-kan UI is missing. Run bun run build.");
14341
14387
  }
14342
14388
  });
14343
- if (live.kind === "plane") {
14344
- try {
14345
- await refreshFromJira(live.app, live.kind, { piped: Boolean(piped) });
14346
- } catch (err) {
14347
- console.error(`${live.kind} Refresh failed`, err);
14348
- }
14349
- const error = live.app.board().error;
14350
- if (error)
14351
- console.error(`${live.kind} Refresh failed; keeping last live Board`, error);
14352
- }
14353
14389
  const { host, port } = resolveListen();
14354
14390
  await bindListen(server, host, port);
14355
14391
  const origin = `http://127.0.0.1:${port}`;
14356
14392
  announce(`pipe-kan http://${host}:${port}`);
14393
+ if (live.kind === "plane") {
14394
+ refreshFromJira(live.app, live.kind, { piped: Boolean(piped) }).catch((err) => console.error(`${live.kind} Refresh failed`, err)).then(() => {
14395
+ const error = live.app.board().error;
14396
+ if (error)
14397
+ console.error(`${live.kind} Refresh failed; keeping last live Board`, error);
14398
+ });
14399
+ }
14357
14400
  if (live.kind === "plane") {
14358
14401
  announce(`cli plane ${planeHost()} ${planeWorkspace(flags)}`);
14359
14402
  announce(`Plane host default ${DEFAULT_PLANE_HOST}`);