@mrclrchtr/supi-bash-timeout 1.3.1 → 1.5.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
@@ -1,6 +1,6 @@
1
1
  # @mrclrchtr/supi-bash-timeout
2
2
 
3
- Injects a default timeout on `bash` tool calls when the LLM omits one, preventing hung commands from blocking the [pi coding agent](https://github.com/earendil-works/pi) indefinitely.
3
+ Adds one small safety feature to the [pi coding agent](https://github.com/earendil-works/pi): if the model calls `bash` without a timeout, this package fills one in.
4
4
 
5
5
  ## Install
6
6
 
@@ -8,32 +8,40 @@ Injects a default timeout on `bash` tool calls when the LLM omits one, preventin
8
8
  pi install npm:@mrclrchtr/supi-bash-timeout
9
9
  ```
10
10
 
11
- Also included in the [SuPi meta-package](https://www.npmjs.com/package/@mrclrchtr/supi).
12
-
13
11
  For local development:
14
12
 
15
13
  ```bash
16
14
  pi install ./packages/supi-bash-timeout
17
15
  ```
18
16
 
19
- Edit the source and `/reload` to pick up changes.
17
+ After editing the source, run `/reload`.
18
+
19
+ ## What you get
20
+
21
+ After install, every `bash` tool call is checked before execution:
22
+
23
+ - if the model already set `timeout`, that value is kept
24
+ - if `timeout` is missing, this package injects a default value
25
+ - other tools are untouched
26
+
27
+ Default timeout: **120 seconds**
20
28
 
21
- ## What it adds
29
+ This is useful when you want a guardrail against hung commands in long or unattended sessions.
22
30
 
23
- Intercepts every `bash` tool call and injects a configurable default timeout when the model didn't specify one.
31
+ ## Settings
24
32
 
25
- - Only affects the `bash` tool other tools are untouched
26
- - Leaves explicit timeouts unchanged
27
- - Default timeout: **120 seconds**
33
+ This package registers a **Bash Timeout** section in `/supi-settings`.
28
34
 
29
- ## Configuration
35
+ Available setting:
30
36
 
31
- Config files (project overrides global):
37
+ - `defaultTimeout` default timeout for `bash` tool calls, in seconds
32
38
 
33
- | Scope | Path |
34
- |-------|------|
35
- | Global | `~/.pi/agent/supi/config.json` |
36
- | Project | `.pi/supi/config.json` |
39
+ Config is stored in the standard SuPi config files:
40
+
41
+ - global: `~/.pi/agent/supi/config.json`
42
+ - project: `.pi/supi/config.json`
43
+
44
+ Example:
37
45
 
38
46
  ```json
39
47
  {
@@ -43,15 +51,10 @@ Config files (project overrides global):
43
51
  }
44
52
  ```
45
53
 
46
- `defaultTimeout` must be a positive integer. Non-numeric, zero, and negative values fall back to the 120-second default.
47
-
48
- If `/supi-settings` is available (registered by the `@mrclrchtr/supi` meta-package), the **Bash Timeout** section also appears there with an editable field.
49
-
50
- ## Requirements
51
-
52
- - `@earendil-works/pi-coding-agent` (peer)
53
- - `@mrclrchtr/supi-core`
54
+ Invalid values are ignored and fall back to the built-in default of `120`.
54
55
 
55
56
  ## Source
56
57
 
57
- Extension entrypoint: `src/bash-timeout.ts`
58
+ - `src/bash-timeout.ts` — intercepts `bash` tool calls and injects missing timeouts
59
+ - `src/config.ts` — config loading and default values
60
+ - `src/settings-registration.ts` — `/supi-settings` registration
@@ -1,65 +1,78 @@
1
1
  # @mrclrchtr/supi-core
2
2
 
3
- Shared infrastructure for SuPi packages.
3
+ Shared infrastructure for SuPi extensions.
4
+
5
+ This package is mainly for extension authors. It gives you a common config system, settings plumbing, context helpers, registries, and a small extension surface that registers `/supi-settings`.
4
6
 
5
7
  ## Install
6
8
 
7
- Use it as a dependency in another extension package:
9
+ ### As a dependency for another extension
8
10
 
9
11
  ```bash
10
12
  pnpm add @mrclrchtr/supi-core
11
13
  ```
12
14
 
13
- ## Package role
14
-
15
- `@mrclrchtr/supi-core` now has two explicit surfaces:
15
+ ### As a pi package
16
16
 
17
- - `@mrclrchtr/supi-core/api` — shared library helpers for other SuPi packages
18
- - `@mrclrchtr/supi-core/extension` — a minimal pi extension that registers `/supi-settings`
19
-
20
- `pi.extensions` still points at the real file path `./src/extension.ts` inside the package. The `/api` and `/extension` paths are consumer-facing package exports, not manifest aliases.
17
+ ```bash
18
+ pi install npm:@mrclrchtr/supi-core
19
+ ```
21
20
 
22
- ## What it provides
21
+ Installing it as a pi package adds the minimal `/supi-settings` extension surface.
23
22
 
24
- Current exports cover:
23
+ ## Package surfaces
25
24
 
26
- - shared config loading, scoped reads, writes, and key removal
27
- - config-backed settings registration helpers for `/supi-settings`
28
- - the shared settings registry, overlay UI, and `registerSettingsCommand()` helper
29
- - XML `<extension-context>` wrapping plus context-message utilities
30
- - context-provider and debug-event registries reused across SuPi packages
31
- - project root and path helpers reused by packages such as `supi-lsp`
25
+ - `@mrclrchtr/supi-core/api` reusable helpers for other packages and extensions
26
+ - `@mrclrchtr/supi-core/extension` minimal pi extension that registers `/supi-settings`
32
27
 
33
- ## Config system
28
+ ## What you get from the API
34
29
 
35
- Config resolution order:
30
+ ### Config helpers
36
31
 
37
- ```text
38
- defaults <- global <- project
39
- ```
32
+ - `loadSupiConfig()` — merged config with resolution order `defaults <- global <- project`
33
+ - `loadSupiConfigForScope()` load one scope at a time for settings UIs
34
+ - `writeSupiConfig()` — persist values
35
+ - `removeSupiConfigKey()` — remove a key or override
40
36
 
41
37
  Config file locations:
42
38
 
43
39
  - global: `~/.pi/agent/supi/config.json`
44
40
  - project: `.pi/supi/config.json`
45
41
 
46
- Main helpers:
42
+ ### Settings helpers
43
+
44
+ - `registerSettings()` — register an arbitrary settings section
45
+ - `registerConfigSettings()` — register a config-backed settings section with scoped persistence helpers
46
+ - `registerSettingsCommand()` — register `/supi-settings`
47
+ - `openSettingsOverlay()` — open the shared settings UI directly
48
+ - `createInputSubmenu()` — helper for simple text-entry submenus
49
+
50
+ The built-in settings UI supports:
47
51
 
48
- - `loadSupiConfig()` — effective merged config (`defaults <- global <- project`)
49
- - `loadSupiConfigForScope()` raw single-scope config for settings UIs (`defaults <- selected scope`)
50
- - `writeSupiConfig()`
51
- - `removeSupiConfigKey()`
52
- - `registerConfigSettings()`
52
+ - project/global scope toggle
53
+ - grouped extension sections
54
+ - searchable setting lists
53
55
 
54
- ## Context and settings helpers
56
+ ### Context helpers
55
57
 
56
- - `wrapExtensionContext()`
58
+ - `wrapExtensionContext()` — wrap injected text in SuPi's `<extension-context>` tag
57
59
  - `findLastUserMessageIndex()`
58
60
  - `getContextToken()`
61
+ - `getPromptContent()`
59
62
  - `pruneAndReorderContextMessages()`
60
- - `registerSettings()`
61
- - `registerSettingsCommand()`
62
- - `openSettingsOverlay()`
63
+ - `restorePromptContent()`
64
+
65
+ ### Shared registries
66
+
67
+ - context-provider registry for `/supi-context`
68
+ - debug-event registry for producers that want shared debug capture
69
+ - settings registry used by `/supi-settings`
70
+
71
+ ### Project and session helpers
72
+
73
+ - project-root detection and directory walking helpers such as `findProjectRoot()` and `walkProject()`
74
+ - active-branch session helper: `getActiveBranchEntries()`
75
+ - terminal helpers such as `formatTitle()`, `signalWaiting()`, and `signalDone()`
63
76
 
64
77
  ## Example
65
78
 
@@ -80,17 +93,15 @@ registerConfigSettings({
80
93
  });
81
94
 
82
95
  const message = wrapExtensionContext("my-extension", "hello", {
83
- turn: 1,
84
96
  file: "CLAUDE.md",
97
+ turn: 1,
85
98
  });
86
99
  ```
87
100
 
88
- ## Requirements
89
-
90
- - `@earendil-works/pi-coding-agent`
91
- - `@earendil-works/pi-tui`
92
-
93
101
  ## Source
94
102
 
95
- - Library surface: `src/api.ts`
96
- - Extension surface: `src/extension.ts`
103
+ - `src/api.ts` — exported library surface
104
+ - `src/extension.ts` — minimal `/supi-settings` entrypoint
105
+ - `src/config.ts` — shared config loading and writing
106
+ - `src/config-settings.ts` — config-backed settings registration helper
107
+ - `src/settings-ui.ts` — shared settings overlay
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-core",
3
- "version": "1.3.1",
3
+ "version": "1.5.0",
4
4
  "description": "SuPi core — shared infrastructure for SuPi extensions (XML context tags, config system)",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -2,30 +2,30 @@
2
2
  // Provides XML context tag wrapping, unified config system, context-message utilities,
3
3
  // and settings registry for supi-wide TUI settings.
4
4
 
5
- export type { SupiConfigLocation, SupiConfigOptions } from "./config.ts";
5
+ export type { SupiConfigLocation, SupiConfigOptions } from "./config/config.ts";
6
6
  export {
7
7
  loadSupiConfig,
8
8
  loadSupiConfigForScope,
9
9
  removeSupiConfigKey,
10
10
  writeSupiConfig,
11
- } from "./config.ts";
12
- export type { ConfigSettingsHelpers, ConfigSettingsOptions } from "./config-settings.ts";
13
- export { registerConfigSettings } from "./config-settings.ts";
14
- export type { ContextMessageLike } from "./context-messages.ts";
11
+ } from "./config/config.ts";
12
+ export type { ConfigSettingsHelpers, ConfigSettingsOptions } from "./config/config-settings.ts";
13
+ export { registerConfigSettings } from "./config/config-settings.ts";
14
+ export type { ContextMessageLike } from "./context/context-messages.ts";
15
15
  export {
16
16
  findLastUserMessageIndex,
17
17
  getContextToken,
18
18
  getPromptContent,
19
19
  pruneAndReorderContextMessages,
20
20
  restorePromptContent,
21
- } from "./context-messages.ts";
22
- export type { ContextProvider } from "./context-provider-registry.ts";
21
+ } from "./context/context-messages.ts";
22
+ export type { ContextProvider } from "./context/context-provider-registry.ts";
23
23
  export {
24
24
  clearRegisteredContextProviders,
25
25
  getRegisteredContextProviders,
26
26
  registerContextProvider,
27
- } from "./context-provider-registry.ts";
28
- export { wrapExtensionContext } from "./context-tag.ts";
27
+ } from "./context/context-provider-registry.ts";
28
+ export { wrapExtensionContext } from "./context/context-tag.ts";
29
29
  export type {
30
30
  DebugAgentAccess,
31
31
  DebugEvent,
@@ -49,6 +49,7 @@ export {
49
49
  redactDebugData,
50
50
  resetDebugRegistry,
51
51
  } from "./debug-registry.ts";
52
+ export { fileToUri, resolveToolPath, stripToolPathPrefix, uriToFile } from "./path-utils.ts";
52
53
  export type { KnownRootEntry } from "./project-roots.ts";
53
54
  export {
54
55
  buildKnownRootsMap,
@@ -63,15 +64,16 @@ export {
63
64
  sortRootsBySpecificity,
64
65
  walkProject,
65
66
  } from "./project-roots.ts";
67
+ export { createRegistry, createSessionStateRegistry } from "./registry-utils.ts";
66
68
  export { getActiveBranchEntries } from "./session-utils.ts";
67
- export { registerSettingsCommand } from "./settings-command.ts";
68
- export type { SettingsScope, SettingsSection } from "./settings-registry.ts";
69
+ export { registerSettingsCommand } from "./settings/settings-command.ts";
70
+ export type { SettingsScope, SettingsSection } from "./settings/settings-registry.ts";
69
71
  export {
70
72
  clearRegisteredSettings,
71
73
  getRegisteredSettings,
72
74
  registerSettings,
73
- } from "./settings-registry.ts";
74
- export { createInputSubmenu, openSettingsOverlay } from "./settings-ui.ts";
75
+ } from "./settings/settings-registry.ts";
76
+ export { createInputSubmenu, openSettingsOverlay } from "./settings/settings-ui.ts";
75
77
  export type { TitleTarget } from "./terminal.ts";
76
78
  export {
77
79
  DONE_SYMBOL,
@@ -2,9 +2,9 @@
2
2
  // Wraps registerSettings() and centralizes selected-scope loading + scoped persistence.
3
3
 
4
4
  import type { SettingItem } from "@earendil-works/pi-tui";
5
+ import type { SettingsScope } from "../settings/settings-registry.ts";
6
+ import { registerSettings } from "../settings/settings-registry.ts";
5
7
  import { loadSupiConfigForScope, removeSupiConfigKey, writeSupiConfig } from "./config.ts";
6
- import type { SettingsScope } from "./settings-registry.ts";
7
- import { registerSettings } from "./settings-registry.ts";
8
8
 
9
9
  export interface ConfigSettingsHelpers {
10
10
  /** Write a key to the selected scope's config section. */
@@ -3,7 +3,7 @@
3
3
  // Extensions declare context data providers via `registerContextProvider()` during their
4
4
  // factory function. The `/supi-context` command reads them via `getRegisteredContextProviders()`.
5
5
 
6
- import { createRegistry } from "./registry-utils.ts";
6
+ import { createRegistry } from "../registry-utils.ts";
7
7
 
8
8
  export interface ContextProvider {
9
9
  /** Unique identifier — e.g. "rtk" */
@@ -1 +1 @@
1
- export { registerSettingsCommand as default } from "./settings-command.ts";
1
+ export { registerSettingsCommand as default } from "./settings/settings-command.ts";
@@ -2,30 +2,30 @@
2
2
  // Provides XML context tag wrapping, unified config system, context-message utilities,
3
3
  // and settings registry for supi-wide TUI settings.
4
4
 
5
- export type { SupiConfigLocation, SupiConfigOptions } from "./config.ts";
5
+ export type { SupiConfigLocation, SupiConfigOptions } from "./config/config.ts";
6
6
  export {
7
7
  loadSupiConfig,
8
8
  loadSupiConfigForScope,
9
9
  removeSupiConfigKey,
10
10
  writeSupiConfig,
11
- } from "./config.ts";
12
- export type { ConfigSettingsHelpers, ConfigSettingsOptions } from "./config-settings.ts";
13
- export { registerConfigSettings } from "./config-settings.ts";
14
- export type { ContextMessageLike } from "./context-messages.ts";
11
+ } from "./config/config.ts";
12
+ export type { ConfigSettingsHelpers, ConfigSettingsOptions } from "./config/config-settings.ts";
13
+ export { registerConfigSettings } from "./config/config-settings.ts";
14
+ export type { ContextMessageLike } from "./context/context-messages.ts";
15
15
  export {
16
16
  findLastUserMessageIndex,
17
17
  getContextToken,
18
18
  getPromptContent,
19
19
  pruneAndReorderContextMessages,
20
20
  restorePromptContent,
21
- } from "./context-messages.ts";
22
- export type { ContextProvider } from "./context-provider-registry.ts";
21
+ } from "./context/context-messages.ts";
22
+ export type { ContextProvider } from "./context/context-provider-registry.ts";
23
23
  export {
24
24
  clearRegisteredContextProviders,
25
25
  getRegisteredContextProviders,
26
26
  registerContextProvider,
27
- } from "./context-provider-registry.ts";
28
- export { wrapExtensionContext } from "./context-tag.ts";
27
+ } from "./context/context-provider-registry.ts";
28
+ export { wrapExtensionContext } from "./context/context-tag.ts";
29
29
  export type {
30
30
  DebugAgentAccess,
31
31
  DebugEvent,
@@ -49,6 +49,7 @@ export {
49
49
  redactDebugData,
50
50
  resetDebugRegistry,
51
51
  } from "./debug-registry.ts";
52
+ export { fileToUri, resolveToolPath, stripToolPathPrefix, uriToFile } from "./path-utils.ts";
52
53
  export type { KnownRootEntry } from "./project-roots.ts";
53
54
  export {
54
55
  buildKnownRootsMap,
@@ -63,15 +64,16 @@ export {
63
64
  sortRootsBySpecificity,
64
65
  walkProject,
65
66
  } from "./project-roots.ts";
67
+ export { createRegistry, createSessionStateRegistry } from "./registry-utils.ts";
66
68
  export { getActiveBranchEntries } from "./session-utils.ts";
67
- export { registerSettingsCommand } from "./settings-command.ts";
68
- export type { SettingsScope, SettingsSection } from "./settings-registry.ts";
69
+ export { registerSettingsCommand } from "./settings/settings-command.ts";
70
+ export type { SettingsScope, SettingsSection } from "./settings/settings-registry.ts";
69
71
  export {
70
72
  clearRegisteredSettings,
71
73
  getRegisteredSettings,
72
74
  registerSettings,
73
- } from "./settings-registry.ts";
74
- export { createInputSubmenu, openSettingsOverlay } from "./settings-ui.ts";
75
+ } from "./settings/settings-registry.ts";
76
+ export { createInputSubmenu, openSettingsOverlay } from "./settings/settings-ui.ts";
75
77
  export type { TitleTarget } from "./terminal.ts";
76
78
  export {
77
79
  DONE_SYMBOL,
@@ -0,0 +1,40 @@
1
+ import * as path from "node:path";
2
+
3
+ /** Strip pi's optional leading `@` file-path prefix from a tool input. */
4
+ export function stripToolPathPrefix(target: string): string {
5
+ return target.startsWith("@") ? target.slice(1) : target;
6
+ }
7
+
8
+ /**
9
+ * Resolve a tool-style file path from a session cwd.
10
+ *
11
+ * Built-in pi file tools accept a leading `@` prefix in path arguments, so
12
+ * shared SuPi path helpers normalize that prefix before resolving relative
13
+ * paths.
14
+ */
15
+ export function resolveToolPath(cwd: string, target: string): string {
16
+ return path.resolve(cwd, stripToolPathPrefix(target));
17
+ }
18
+
19
+ /** Convert a file path to a file:// URI. */
20
+ 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}`;
26
+ }
27
+
28
+ /** Convert a file:// URI to a file path. */
29
+ export function uriToFile(uri: string): string {
30
+ 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);
38
+ }
39
+ return filePath;
40
+ }
@@ -5,8 +5,20 @@
5
5
  // Without this, each symlink path gets its own module copy and its own Map,
6
6
  // so registrations from one instance are invisible to consumers in another.
7
7
 
8
+ import * as path from "node:path";
9
+
8
10
  const SYMBOL_PREFIX = "@mrclrchtr/supi-core/";
9
11
 
12
+ function getGlobalRegistryMap<T>(name: string): Map<string, T> {
13
+ const key = Symbol.for(SYMBOL_PREFIX + name);
14
+ let map = (globalThis as Record<symbol, unknown>)[key] as Map<string, T> | undefined;
15
+ if (!map) {
16
+ map = new Map<string, T>();
17
+ (globalThis as Record<symbol, unknown>)[key] = map;
18
+ }
19
+ return map;
20
+ }
21
+
10
22
  /**
11
23
  * Create a named registry backed by `globalThis` + `Symbol.for`.
12
24
  *
@@ -18,16 +30,7 @@ const SYMBOL_PREFIX = "@mrclrchtr/supi-core/";
18
30
  * @returns An object with `register`, `getAll`, and `clear` functions.
19
31
  */
20
32
  export function createRegistry<T>(name: string) {
21
- const key = Symbol.for(SYMBOL_PREFIX + name);
22
-
23
- const getMap = (): Map<string, T> => {
24
- let map = (globalThis as Record<symbol, unknown>)[key] as Map<string, T> | undefined;
25
- if (!map) {
26
- map = new Map<string, T>();
27
- (globalThis as Record<symbol, unknown>)[key] = map;
28
- }
29
- return map;
30
- };
33
+ const getMap = (): Map<string, T> => getGlobalRegistryMap<T>(name);
31
34
 
32
35
  return {
33
36
  /**
@@ -52,3 +55,32 @@ export function createRegistry<T>(name: string) {
52
55
  },
53
56
  };
54
57
  }
58
+
59
+ /**
60
+ * Create a named session-state registry keyed by normalized cwd.
61
+ *
62
+ * This helper is intended for session-scoped runtime services that should be
63
+ * shared across duplicate jiti module instances while keeping package-specific
64
+ * state unions and convenience wrappers local to the calling package.
65
+ */
66
+ export function createSessionStateRegistry<TState>(name: string) {
67
+ const getMap = (): Map<string, TState> => getGlobalRegistryMap<TState>(name);
68
+ const normalizeCwd = (cwd: string): string => path.resolve(cwd);
69
+
70
+ return {
71
+ /** Get the current state for one session cwd. */
72
+ get: (cwd: string): TState | undefined => {
73
+ return getMap().get(normalizeCwd(cwd));
74
+ },
75
+
76
+ /** Store the current state for one session cwd. */
77
+ set: (cwd: string, state: TState): void => {
78
+ getMap().set(normalizeCwd(cwd), state);
79
+ },
80
+
81
+ /** Clear the current state for one session cwd. */
82
+ clear: (cwd: string): void => {
83
+ getMap().delete(normalizeCwd(cwd));
84
+ },
85
+ };
86
+ }
@@ -4,7 +4,7 @@
4
4
  // factory function. The generic settings UI reads them via `getRegisteredSettings()`.
5
5
 
6
6
  import type { SettingItem } from "@earendil-works/pi-tui";
7
- import { createRegistry } from "./registry-utils.ts";
7
+ import { createRegistry } from "../registry-utils.ts";
8
8
 
9
9
  export type SettingsScope = "project" | "global";
10
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-bash-timeout",
3
- "version": "1.3.1",
3
+ "version": "1.5.0",
4
4
  "description": "SuPi bash-timeout extension — injects default timeout on bash tool calls",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -20,7 +20,7 @@
20
20
  "!__tests__"
21
21
  ],
22
22
  "dependencies": {
23
- "@mrclrchtr/supi-core": "1.3.1"
23
+ "@mrclrchtr/supi-core": "1.5.0"
24
24
  },
25
25
  "bundledDependencies": [
26
26
  "@mrclrchtr/supi-core"