@fink-andreas/pi-linear-tools 0.7.1 → 0.7.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/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.7.2 (2026-07-21)
4
+
5
+ Patch release for Pi package rename compatibility and terminal-safe fallback rendering.
6
+
7
+ ### Bug Fixes
8
+ - **Support renamed Pi packages**: The extension now supports both legacy `@mariozechner/*` imports and the current `@earendil-works/*` package scope, including globally installed Pi paths.
9
+ - **Prevent CJK fallback-rendering crashes**: Plain-text fallback output now truncates by terminal columns, preventing over-width lines from CJK/non-ASCII text and tabs from triggering Pi TUI's width guard.
10
+
11
+ ### Tests
12
+ - Added fallback-renderer regression coverage for CJK text, tabs, unavailable Markdown dependencies, and Markdown rendering failures.
13
+
3
14
  ## v0.7.1 (2026-06-09)
4
15
 
5
16
  Patch release for issue creation milestone assignment and configuration text cleanup.
@@ -12,7 +12,11 @@ async function importPiCodingAgent() {
12
12
  try {
13
13
  return await import('@mariozechner/pi-coding-agent');
14
14
  } catch {
15
- return importFromPiRoot('dist/index.js');
15
+ try {
16
+ return await import('@earendil-works/pi-coding-agent');
17
+ } catch {
18
+ return importFromPiRoot('dist/index.js');
19
+ }
16
20
  }
17
21
  }
18
22
 
@@ -20,8 +24,16 @@ async function importPiTui() {
20
24
  try {
21
25
  return await import('@mariozechner/pi-tui');
22
26
  } catch {
23
- // pi-tui is a dependency of pi-coding-agent and may be nested under it
24
- return importFromPiRoot('node_modules/@mariozechner/pi-tui/dist/index.js');
27
+ try {
28
+ return await import('@earendil-works/pi-tui');
29
+ } catch {
30
+ // pi-tui is a dependency of pi-coding-agent and may be nested under it
31
+ try {
32
+ return await importFromPiRoot('node_modules/@mariozechner/pi-tui/dist/index.js');
33
+ } catch {
34
+ return importFromPiRoot('node_modules/@earendil-works/pi-tui/dist/index.js');
35
+ }
36
+ }
25
37
  }
26
38
  }
27
39
 
@@ -602,33 +614,56 @@ async function executeToolSafely(operationLabel, operation, options = {}) {
602
614
  }
603
615
  }
604
616
 
605
- function renderMarkdownResult(result, _options, _theme) {
617
+ // Conservative column width used only by the plain-text fallback renderer:
618
+ // printable ASCII counts as 1 column, tabs as 3 (matching pi-tui), and every
619
+ // other code point as 2. This overestimates narrow non-ASCII characters but
620
+ // never underestimates terminal display width, so rendered lines stay within
621
+ // the terminal width and pi-tui's over-width guard never fires.
622
+ function fallbackColumnWidth(codePoint) {
623
+ if (codePoint === 0x09) return 3;
624
+ return codePoint >= 0x20 && codePoint <= 0x7e ? 1 : 2;
625
+ }
626
+
627
+ export function truncateLineToColumns(line, maxColumns) {
628
+ let columns = 0;
629
+ let end = 0;
630
+ for (const char of line) {
631
+ const width = fallbackColumnWidth(char.codePointAt(0));
632
+ if (columns + width > maxColumns) break;
633
+ columns += width;
634
+ end += char.length;
635
+ }
636
+ return line.slice(0, end);
637
+ }
638
+
639
+ export function createPlainTextFallbackRenderer(text) {
640
+ const lines = text.split('\n');
641
+ return {
642
+ render: (width) => lines.map((line) => (width ? truncateLineToColumns(line, width) : line)),
643
+ invalidate: () => {},
644
+ };
645
+ }
646
+
647
+ export function renderMarkdownResult(result, _options, _theme, renderer = { Markdown, Text, getMarkdownTheme }) {
606
648
  const text = result.content?.[0]?.text || '';
649
+ const { Markdown: markdown, Text: textComponent, getMarkdownTheme: getTheme } = renderer;
607
650
 
608
651
  // Fall back to plain text if markdown packages not available
609
- if (!Markdown || !getMarkdownTheme) {
610
- const lines = text.split('\n');
611
- return {
612
- render: (width) => lines.map((line) => (width && line.length > width ? line.slice(0, width) : line)),
613
- invalidate: () => {},
614
- };
652
+ if (!markdown || !getTheme) {
653
+ return createPlainTextFallbackRenderer(text);
615
654
  }
616
655
 
617
656
  // Return Markdown component directly - the TUI will call its render() method
618
657
  try {
619
- const mdTheme = getMarkdownTheme();
620
- return new Markdown(text, 0, 0, mdTheme, _theme ? { color: (t) => _theme.fg('toolOutput', t) } : undefined);
658
+ const mdTheme = getTheme();
659
+ return new markdown(text, 0, 0, mdTheme, _theme ? { color: (t) => _theme.fg('toolOutput', t) } : undefined);
621
660
  } catch (error) {
622
661
  // If markdown rendering fails for any reason, show a visible error so we can diagnose.
623
662
  const msg = `[pi-linear-tools] Markdown render failed: ${String(error?.message || error)}`;
624
- if (Text) {
625
- return new Text((_theme ? _theme.fg('error', msg) : msg) + `\n\n` + text, 0, 0);
663
+ if (textComponent) {
664
+ return new textComponent((_theme ? _theme.fg('error', msg) : msg) + `\n\n` + text, 0, 0);
626
665
  }
627
- const lines = (msg + '\n\n' + text).split('\n');
628
- return {
629
- render: (width) => lines.map((line) => (width && line.length > width ? line.slice(0, width) : line)),
630
- invalidate: () => {},
631
- };
666
+ return createPlainTextFallbackRenderer(msg + '\n\n' + text);
632
667
  }
633
668
  }
634
669
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fink-andreas/pi-linear-tools",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "description": "Pi extension with Linear SDK tools and configuration commands",
5
5
  "type": "module",
6
6
  "engines": {
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "scripts": {
29
29
  "start": "node index.js",
30
- "test": "node tests/test-package-manifest.js && node tests/test-extension-registration.js && node tests/test-issue-comment-result.js && node tests/test-issue-priority.js && node tests/test-settings.js && node tests/test-issue-download.js && node tests/test-api-usage-caching.js && node tests/test-issue-create-milestone.js && node tests/test-assignee-update.js && node tests/test-rate-limit-fallback-update.js && node tests/test-full-assignee-flow.js && node tests/test-branch-param.js && node tests/test-team-filter.js && node tests/test-project-crud.js && node tests/test-project-lifecycle.js && node tests/test-sync-doc.js && node tests/test-issue-activity.js",
30
+ "test": "node tests/test-package-manifest.js && node tests/test-extension-registration.js && node tests/test-render-fallback.js && node tests/test-issue-comment-result.js && node tests/test-issue-priority.js && node tests/test-settings.js && node tests/test-issue-download.js && node tests/test-api-usage-caching.js && node tests/test-issue-create-milestone.js && node tests/test-assignee-update.js && node tests/test-rate-limit-fallback-update.js && node tests/test-full-assignee-flow.js && node tests/test-branch-param.js && node tests/test-team-filter.js && node tests/test-project-crud.js && node tests/test-project-lifecycle.js && node tests/test-sync-doc.js && node tests/test-issue-activity.js",
31
31
  "dev:sync-local-extension": "node scripts/dev-sync-local-extension.mjs",
32
32
  "release:check": "npm test && npm pack --dry-run"
33
33
  },
package/src/shared.js CHANGED
@@ -18,7 +18,7 @@ export function isPiCodingAgentRoot(dir) {
18
18
  if (!fs.existsSync(pkgPath)) return false;
19
19
  try {
20
20
  const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
21
- return pkg?.name === '@mariozechner/pi-coding-agent';
21
+ return pkg?.name === '@mariozechner/pi-coding-agent' || pkg?.name === '@earendil-works/pi-coding-agent';
22
22
  } catch {
23
23
  return false;
24
24
  }
@@ -50,9 +50,11 @@ export function findPiCodingAgentRoot() {
50
50
  {
51
51
  const binDir = path.dirname(entry);
52
52
  const prefix = path.resolve(binDir, '..');
53
- const candidate = path.join(prefix, 'lib', 'node_modules', '@mariozechner', 'pi-coding-agent');
54
- if (isPiCodingAgentRoot(candidate)) {
55
- return candidate;
53
+ for (const scope of ['@mariozechner', '@earendil-works']) {
54
+ const candidate = path.join(prefix, 'lib', 'node_modules', scope, 'pi-coding-agent');
55
+ if (isPiCodingAgentRoot(candidate)) {
56
+ return candidate;
57
+ }
56
58
  }
57
59
  }
58
60
 
@@ -60,6 +62,9 @@ export function findPiCodingAgentRoot() {
60
62
  for (const candidate of [
61
63
  '/usr/local/lib/node_modules/@mariozechner/pi-coding-agent',
62
64
  '/usr/lib/node_modules/@mariozechner/pi-coding-agent',
65
+ '/usr/local/lib/node_modules/@earendil-works/pi-coding-agent',
66
+ '/usr/lib/node_modules/@earendil-works/pi-coding-agent',
67
+ '/opt/homebrew/lib/node_modules/@earendil-works/pi-coding-agent',
63
68
  ]) {
64
69
  if (isPiCodingAgentRoot(candidate)) {
65
70
  return candidate;