@mrclrchtr/supi-extras 3.2.0 → 4.0.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/README.md CHANGED
@@ -59,6 +59,10 @@ If the stash file cannot be read or written, the feature degrades to in-memory u
59
59
 
60
60
  ## Passive behavior
61
61
 
62
+ ### User path references
63
+
64
+ Treat `@<path>` in a user message as the path `<path>`: relative paths use PI's current working directory; absolute paths stay absolute. This fixes [pi#6487](https://github.com/earendil-works/pi/issues/6487).
65
+
62
66
  ### Tab-title spinner
63
67
 
64
68
  While the agent is working, the package animates a spinner in the terminal tab title. When the turn finishes, it shows a done marker. If `ask_user` is active, the spinner pauses so the waiting-for-input title is not overwritten.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-core",
3
- "version": "3.2.0",
3
+ "version": "4.0.0",
4
4
  "description": "SuPi core — shared infrastructure for SuPi extensions (XML context tags, config system)",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -50,7 +50,6 @@
50
50
  },
51
51
  "main": "src/api.ts",
52
52
  "exports": {
53
- "./abort-utils": "./src/abort-utils.ts",
54
53
  "./api": "./src/api.ts",
55
54
  "./config": "./src/config.ts",
56
55
  "./context": "./src/context.ts",
@@ -71,7 +70,6 @@
71
70
  "./status-spinner": "./src/status-spinner.ts",
72
71
  "./terminal": "./src/terminal.ts",
73
72
  "./tool-framework": "./src/tool-framework.ts",
74
- "./prompt-surface": "./src/prompt-surface.ts",
75
- "./types": "./src/types.ts"
73
+ "./prompt-surface": "./src/prompt-surface.ts"
76
74
  }
77
75
  }
@@ -6,8 +6,6 @@
6
6
  // For lighter imports, use one of the domain subpaths directly
7
7
  // (e.g. @mrclrchtr/supi-core/config, @mrclrchtr/supi-core/context).
8
8
 
9
- // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
10
- export * from "./abort-utils.ts";
11
9
  // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
12
10
  export * from "./config.ts";
13
11
  // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
@@ -42,5 +40,3 @@ export * from "./status-spinner.ts";
42
40
  export * from "./terminal.ts";
43
41
  // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
44
42
  export * from "./tool-framework.ts";
45
- // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
46
- export * from "./types.ts";
@@ -22,14 +22,15 @@ export interface EvidenceBadgeInput {
22
22
  * |------------------------------------------------------|---------------------------------------|
23
23
  * | shown=12, total=12, omitted=0, label="references" | `12 references` |
24
24
  * | shown=8, total=20, omitted=12, label="symbols" | `8 of 20 symbols (12 omitted)` |
25
- * | shown=5, total=null, reason="timeout", label="matches" | `5 matches — timeout` |
25
+ * | shown=5, total=null, omitted=2, reason="timeout" | `5 matches (2 collected omitted; more may exist — timeout)` |
26
26
  */
27
27
  export function formatEvidenceBadge(input: EvidenceBadgeInput): string {
28
28
  const { shownCount, totalCount, omittedCount, partialReason, label } = input;
29
29
 
30
30
  if (totalCount === null) {
31
31
  const reasonSuffix = partialReason ? ` — ${partialReason}` : "";
32
- return `${shownCount} ${label}${reasonSuffix}`;
32
+ const omittedPrefix = (omittedCount ?? 0) > 0 ? `${omittedCount} collected omitted; ` : "";
33
+ return `${shownCount} ${label} (${omittedPrefix}more may exist${reasonSuffix})`;
33
34
  }
34
35
 
35
36
  if (omittedCount !== null && omittedCount > 0) {
@@ -6,8 +6,6 @@
6
6
  // For lighter imports, use one of the domain subpaths directly
7
7
  // (e.g. @mrclrchtr/supi-core/config, @mrclrchtr/supi-core/context).
8
8
 
9
- // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
10
- export * from "./abort-utils.ts";
11
9
  // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
12
10
  export * from "./config.ts";
13
11
  // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
@@ -36,5 +34,3 @@ export * from "./status-spinner.ts";
36
34
  export * from "./terminal.ts";
37
35
  // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
38
36
  export * from "./tool-framework.ts";
39
- // biome-ignore lint/performance/noReExportAll: intentional convenience barrel
40
- export * from "./types.ts";
@@ -24,16 +24,6 @@ export interface WithRetryOptions {
24
24
  onRetry?: (attempt: number, delayMs: number) => void;
25
25
  }
26
26
 
27
- /**
28
- * Attempt an async operation with retries and exponential backoff.
29
- *
30
- * If the signal is already aborted on entry, the operation is skipped entirely.
31
- * If the signal aborts during a delay, the delay is cancelled immediately.
32
- *
33
- * @param fn - The async operation to retry.
34
- * @param options - Optional configuration for retries, backoff, signal, and callbacks.
35
- * @returns The result on success, or `null` if all attempts fail or the signal aborts.
36
- */
37
27
  /**
38
28
  * Create a promise that resolves after `ms` milliseconds, or rejects if
39
29
  * the signal fires before the timeout elapses.
@@ -1,4 +1,5 @@
1
1
  import * as path from "node:path";
2
+ import { fileURLToPath, pathToFileURL } from "node:url";
2
3
 
3
4
  /** Strip pi's optional leading `@` file-path prefix from a tool input. */
4
5
  export function stripToolPathPrefix(target: string): string {
@@ -16,25 +17,28 @@ export function resolveToolPath(cwd: string, target: string): string {
16
17
  return path.resolve(cwd, stripToolPathPrefix(target));
17
18
  }
18
19
 
19
- /** Convert a file path to a file:// URI. */
20
+ /**
21
+ * Convert a file path to a file:// URI.
22
+ *
23
+ * Uses Node's `pathToFileURL` to produce a standards-compliant URI with
24
+ * proper percent-encoding of spaces, hashes, and other special characters.
25
+ */
20
26
  export function fileToUri(filePath: string): string {
21
- const resolved = path.resolve(filePath);
22
- if (process.platform === "win32") {
23
- return `file:///${resolved.replace(/\\/g, "/")}`;
24
- }
25
- return `file://${resolved}`;
27
+ return pathToFileURL(filePath).href;
26
28
  }
27
29
 
28
- /** Convert a file:// URI to a file path. */
30
+ /**
31
+ * Convert a file:// URI to a file path.
32
+ *
33
+ * Uses Node's `fileURLToPath` for standards-compliant decoding. Non-file
34
+ * URIs are passed through unchanged so consumers (such as LSP diagnostic
35
+ * handling) remain compatible with non-file URI schemes.
36
+ */
29
37
  export function uriToFile(uri: string): string {
30
38
  if (!uri.startsWith("file://")) return uri;
31
- let filePath = decodeURIComponent(uri.slice(7));
32
- if (
33
- process.platform === "win32" &&
34
- filePath.startsWith("/") &&
35
- /^[A-Za-z]:/.test(filePath.slice(1))
36
- ) {
37
- filePath = filePath.slice(1);
39
+ try {
40
+ return fileURLToPath(uri);
41
+ } catch {
42
+ return uri;
38
43
  }
39
- return filePath;
40
44
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-extras",
3
- "version": "3.2.0",
3
+ "version": "4.0.0",
4
4
  "description": "SuPi extras — command aliases, skill shorthand, tab spinner, /supi-stash prompt stash with TUI overlay, and other small utilities",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -33,7 +33,7 @@
33
33
  ],
34
34
  "dependencies": {
35
35
  "clipboardy": "^5.3.1",
36
- "@mrclrchtr/supi-core": "3.2.0"
36
+ "@mrclrchtr/supi-core": "4.0.0"
37
37
  },
38
38
  "bundledDependencies": [
39
39
  "@mrclrchtr/supi-core"
package/src/index.ts CHANGED
@@ -6,7 +6,16 @@ import skillShortcut from "./skill-shortcut.ts";
6
6
  import supiFooter from "./supi-footer.ts";
7
7
  import tabSpinner from "./tab-spinner.ts";
8
8
 
9
+ const PATH_RESOLUTION_GUIDANCE =
10
+ "Treat `@<path>` in a user message as the path `<path>`:" +
11
+ " resolve relative paths from the current working directory;" +
12
+ " absolute paths stay absolute.";
13
+
9
14
  export default function (pi: Parameters<typeof tabSpinner>[0]) {
15
+ pi.on("before_agent_start", (event) => ({
16
+ systemPrompt: `${event.systemPrompt}\n\n${PATH_RESOLUTION_GUIDANCE}`,
17
+ }));
18
+
10
19
  tabSpinner(pi);
11
20
  promptStash(pi);
12
21
  copyPrompt(pi);
@@ -1,31 +0,0 @@
1
- /**
2
- * Abort controller/signal utility helpers.
3
- *
4
- * @module
5
- */
6
-
7
- /**
8
- * Combine a caller-provided abort controller with a generation timeout signal.
9
- *
10
- * Returns the combined signal and a cleanup function that removes both
11
- * event listeners. Callers must invoke cleanup in a `finally` block
12
- * to avoid listener leaks.
13
- */
14
- export function combineAbortSignals(
15
- abort: AbortController,
16
- timeoutMs: number,
17
- ): { signal: AbortSignal; cleanup: () => void } {
18
- const timeoutSignal = AbortSignal.timeout(timeoutMs);
19
- const combinedController = new AbortController();
20
- const onAbort = () => combinedController.abort();
21
- abort.signal.addEventListener("abort", onAbort, { once: true });
22
- timeoutSignal.addEventListener("abort", onAbort, { once: true });
23
-
24
- return {
25
- signal: combinedController.signal,
26
- cleanup: () => {
27
- abort.signal.removeEventListener("abort", onAbort);
28
- timeoutSignal.removeEventListener("abort", onAbort);
29
- },
30
- };
31
- }
@@ -1,11 +0,0 @@
1
- /** 0-based position used by LSP and code-intelligence internally. */
2
- export interface CodePosition {
3
- line: number;
4
- character: number;
5
- }
6
-
7
- /** Normalized location — flat replacement for LSP's nested Location/range shape. */
8
- export interface CodeLocation {
9
- uri: string;
10
- range: { start: CodePosition; end: CodePosition };
11
- }
@@ -1,2 +0,0 @@
1
- // supi-core types domain — shared substrate types (zero dependencies, pure types).
2
- export type { CodeLocation, CodePosition } from "./substrate-types.ts";