@mrclrchtr/supi-ask-user 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.
Files changed (51) hide show
  1. package/README.md +163 -67
  2. package/node_modules/@mrclrchtr/supi-core/README.md +52 -41
  3. package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
  4. package/node_modules/@mrclrchtr/supi-core/src/api.ts +15 -13
  5. package/node_modules/@mrclrchtr/supi-core/src/{config-settings.ts → config/config-settings.ts} +2 -2
  6. package/node_modules/@mrclrchtr/supi-core/src/{context-provider-registry.ts → context/context-provider-registry.ts} +1 -1
  7. package/node_modules/@mrclrchtr/supi-core/src/extension.ts +1 -1
  8. package/node_modules/@mrclrchtr/supi-core/src/index.ts +15 -13
  9. package/node_modules/@mrclrchtr/supi-core/src/path-utils.ts +40 -0
  10. package/node_modules/@mrclrchtr/supi-core/src/registry-utils.ts +42 -10
  11. package/node_modules/@mrclrchtr/supi-core/src/{settings-registry.ts → settings/settings-registry.ts} +1 -1
  12. package/package.json +2 -2
  13. package/src/api.ts +19 -0
  14. package/src/ask-user.ts +71 -131
  15. package/src/index.ts +23 -1
  16. package/src/normalize.ts +153 -142
  17. package/src/render/result.ts +102 -0
  18. package/src/render/transcript.ts +65 -0
  19. package/src/render/tree-summary.ts +10 -0
  20. package/src/schema.ts +41 -38
  21. package/src/session/controller.ts +281 -0
  22. package/src/session/lock.ts +19 -0
  23. package/src/tool/guidance.ts +15 -0
  24. package/src/types.ts +56 -55
  25. package/src/ui/choose-renderer.ts +11 -0
  26. package/src/ui/overlay-actions.ts +42 -0
  27. package/src/ui/overlay-component.ts +400 -0
  28. package/src/ui/overlay-render.ts +219 -0
  29. package/src/ui/overlay-view.ts +313 -0
  30. package/src/ui/overlay.ts +28 -0
  31. package/src/ui/types.ts +38 -0
  32. package/src/flow.ts +0 -224
  33. package/src/format.ts +0 -66
  34. package/src/render/ui-rich-render-editor.ts +0 -51
  35. package/src/render/ui-rich-render-env.ts +0 -15
  36. package/src/render/ui-rich-render-footer.ts +0 -55
  37. package/src/render/ui-rich-render-markdown.ts +0 -33
  38. package/src/render/ui-rich-render-notes.ts +0 -80
  39. package/src/render/ui-rich-render-types.ts +0 -17
  40. package/src/render/ui-rich-render.ts +0 -323
  41. package/src/render.ts +0 -95
  42. package/src/result.ts +0 -90
  43. package/src/ui/ui-rich-handlers.ts +0 -369
  44. package/src/ui/ui-rich-inline.ts +0 -77
  45. package/src/ui/ui-rich-state.ts +0 -179
  46. package/src/ui/ui-rich.ts +0 -144
  47. /package/node_modules/@mrclrchtr/supi-core/src/{config.ts → config/config.ts} +0 -0
  48. /package/node_modules/@mrclrchtr/supi-core/src/{context-messages.ts → context/context-messages.ts} +0 -0
  49. /package/node_modules/@mrclrchtr/supi-core/src/{context-tag.ts → context/context-tag.ts} +0 -0
  50. /package/node_modules/@mrclrchtr/supi-core/src/{settings-command.ts → settings/settings-command.ts} +0 -0
  51. /package/node_modules/@mrclrchtr/supi-core/src/{settings-ui.ts → settings/settings-ui.ts} +0 -0
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @mrclrchtr/supi-ask-user
2
2
 
3
- Structured `ask_user` tool and rich questionnaire overlay for the [pi coding agent](https://github.com/earendil-works/pi). Lets the agent pause and ask you focused, typed decisions picking from lists, multi-selecting, or entering freeform text.
3
+ Adds a redesigned `ask_user` tool to the [pi coding agent](https://github.com/earendil-works/pi). It lets the model pause and request a small decision form when explicit human input is required.
4
4
 
5
5
  ## Install
6
6
 
@@ -14,34 +14,153 @@ For local development:
14
14
  pi install ./packages/supi-ask-user
15
15
  ```
16
16
 
17
- After editing the source, run `/reload` to pick up changes.
17
+ After editing the source, run `/reload`.
18
18
 
19
- ## What it adds
19
+ ## What you get
20
20
 
21
- Registers the `ask_user` tool — callable by the model during an agent run. When the agent needs explicit user input to proceed, it invokes `ask_user` with a questionnaire, and the extension opens an interactive overlay.
21
+ After install, pi gets one new tool:
22
22
 
23
- **Question types:**
23
+ - **`ask_user`** — open a blocking decision form during a run
24
24
 
25
- | Type | Use |
26
- |------|-----|
27
- | `choice` | Pick one option (default) or multiple options (`multi: true`) from a list |
28
- | `text` | Freeform text input |
25
+ The tool presents a structured questionnaire in the TUI overlay and blocks the agent turn until the user responds. It is designed for focused decisions, **not** long surveys or open-ended discovery.
29
26
 
30
- For yes/no questions, use `choice` with options `{value: "yes", label: "Yes"}` and `{value: "no", label: "No"}` — there is no separate `yesno` type.
27
+ Typical use cases:
31
28
 
32
- **Key behaviors:**
29
+ - Clarify a narrow implementation choice
30
+ - Confirm a risky or destructive action
31
+ - Ask for a preference the repo cannot answer
32
+ - Gather one short cluster of related decisions before proceeding
33
33
 
34
- - Returns an error in non-interactive or print-mode sessions (no fallback dialog).
35
- - Only one questionnaire runs at a time — concurrent `ask_user` calls return an error.
36
- - Cancelling or closing the overlay aborts the current agent turn.
37
- - Completed answers appear as a readable summary entry in the `/tree` view.
34
+ ## Package surfaces
38
35
 
39
- ## Usage
36
+ - `@mrclrchtr/supi-ask-user/extension` — pi extension entrypoint, registers the `ask_user` tool
37
+ - `@mrclrchtr/supi-ask-user/api` — reusable types and utilities
40
38
 
41
- The agent decides when to call `ask_user`. You control how it's used through the system prompt guidelines the extension injects. A minimal example the agent might construct:
39
+ Example:
40
+
41
+ ```ts
42
+ import { normalizeQuestionnaire, AskUserController } from "@mrclrchtr/supi-ask-user/api";
43
+
44
+ const questionnaire = normalizeQuestionnaire(params);
45
+ const controller = new AskUserController(questionnaire);
46
+ ```
47
+
48
+ ## Request shape
49
+
50
+ `ask_user` accepts a small form with optional framing text:
51
+
52
+ | Field | Type | Description |
53
+ |-------|------|-------------|
54
+ | `title` | string (optional) | Short overall title for the form |
55
+ | `intro` | string (optional) | Why the agent is asking |
56
+ | `questions` | array (1–4) | Choice or text questions |
57
+ | `allowPartialSubmit` | boolean (optional) | Let the user submit partial progress |
58
+ | `allowDiscuss` | boolean (optional) | Let the user switch back into discussion instead of giving a final decision |
59
+
60
+ ## Questions
61
+
62
+ Each question has a `type`, `id`, `header`, and `prompt`. Two question types are supported:
63
+
64
+ ### `choice` — fixed options
65
+
66
+ | Field | Type | Description |
67
+ |-------|------|-------------|
68
+ | `options` | array (2–12) | Allowed answers with `value`, `label`, and optional `description`/`preview` |
69
+ | `required` | boolean (default: `true`) | Whether this question must be answered |
70
+ | `multi` | boolean (default: `false`) | Allow selecting multiple options |
71
+ | `allowOther` | boolean | Allow a freeform answer instead of listed options. Single-select only. |
72
+ | `recommendation` | string \| string[] | Recommended option value(s) |
73
+ | `initial` | string \| string[] | Initially selected option value(s) |
74
+
75
+ Model yes/no questions as a `choice` with `{ value: "yes", label: "Yes" }` and `{ value: "no", label: "No" }`.
76
+
77
+ ### `text` — freeform input
78
+
79
+ | Field | Type | Description |
80
+ |-------|------|-------------|
81
+ | `required` | boolean (default: `true`) | Whether this question must be answered |
82
+ | `initial` | string | Initial value shown in the editor |
83
+ | `placeholder` | string | Placeholder shown before the user types |
84
+
85
+ ## Result
86
+
87
+ A completed form returns a result with `details.status` set to one of:
88
+
89
+ | Status | Meaning |
90
+ |--------|---------|
91
+ | `submitted` | Full submit, all required questions answered |
92
+ | `partial` | Partial submit with some required questions unanswered |
93
+ | `discuss` | User wants to continue the conversation instead of deciding |
94
+ | `cancelled` | User explicitly cancelled (aborts the current agent turn) |
95
+ | `aborted` | The interaction was aborted externally (aborts the current agent turn) |
96
+
97
+ `details.answersById` maps question IDs to their answers. Each answer has a `kind` and type-specific data:
98
+
99
+ - `{ kind: "choice", selections: [{ value, label, note? }] }` — single or multi-select choice, with optional per-option user notes
100
+ - `{ kind: "custom", value: "..." }` — freeform `allowOther` answer
101
+ - `{ kind: "text", value: "..." }` — freeform text answer
102
+
103
+ `details.missingQuestionIds` lists any required questions that were left unanswered on a partial submit.
104
+
105
+ ## Behavior
106
+
107
+ - Requires pi in interactive (TUI) mode with custom overlay support — no degraded fallback
108
+ - Only one `ask_user` form may be active at a time; calling `ask_user` while another form is in flight returns an error
109
+ - Cancellation or abort stops the current agent turn
110
+ - Completed forms are summarized in the session tree
111
+ - Do not use `ask_user` for open-ended interviews or repo facts the agent can discover on its own
112
+
113
+ ## Tool guidance
114
+
115
+ The tool registers the following prompt guidance that the model sees:
116
+
117
+ - Use ask_user only when explicit user input is required to proceed safely; do not use ask_user for open-ended interviews or repo facts.
118
+ - Use ask_user with 1-4 related questions; prefer one when possible.
119
+ - Use ask_user `choice` for fixed options and ask_user `text` for freeform input; model yes/no as `choice` with `{ value: "yes", label: "Yes" }` and `{ value: "no", label: "No" }`.
120
+ - Use ask_user `allowOther` only on single-select `choice` questions.
121
+ - Use ask_user `allowDiscuss` or `allowPartialSubmit` only when that outcome is actionable.
122
+ - Do not call ask_user while another ask_user form is already in flight.
123
+
124
+ ## UI controls
125
+
126
+ ### Choice questions
127
+
128
+ - `↑↓` — move between options
129
+ - `Space` — select the focused option (single-select) or toggle (multi-select)
130
+ - `Enter` — submit the current answer
131
+ - `n` — edit a note for the focused choice option
132
+ - `←` — go back to the previous question
133
+ - `Esc` — cancel the whole form (or close the note editor if one is open)
134
+
135
+ On wide terminals, option previews render side-by-side with the option list. On narrow terminals, previews stack below.
136
+
137
+ Notes are available only for real `choice` options. They do not apply to `text` questions, `Other…` freeform answers, or other exceptional action rows. Saving a non-empty note selects the option if needed; clearing a note leaves the current selection alone; deselecting a multi-select option removes its note with the selection.
138
+
139
+ Only exceptional action rows are visible:
140
+
141
+ - `Other…` — when `allowOther` is enabled
142
+ - `Discuss instead…` — when `allowDiscuss` is enabled
143
+ - `Submit partial answers` — when `allowPartialSubmit` is enabled
144
+ - `Skip question` — for optional questions
145
+
146
+ Back and cancel are keyboard-only (`←`, `Esc`) — no visible rows.
147
+
148
+ ### Text questions
149
+
150
+ - The editor is visible immediately (no separate entry row)
151
+ - `Enter` — submit the current text
152
+ - `↓` — move from the editor into visible exceptional action rows
153
+ - `↑` — from the first action row, return focus to the editor
154
+ - `Esc` — cancel the whole form
155
+
156
+ Exceptional action rows (`Discuss instead…`, `Submit partial answers`) may appear below the editor when those paths are enabled.
157
+
158
+ ## Example
42
159
 
43
160
  ```json
44
161
  {
162
+ "title": "Formatter decision",
163
+ "intro": "I need one explicit choice before I update the repo config.",
45
164
  "questions": [
46
165
  {
47
166
  "type": "choice",
@@ -53,63 +172,40 @@ The agent decides when to call `ask_user`. You control how it's used through the
53
172
  { "value": "prettier", "label": "Prettier" }
54
173
  ],
55
174
  "recommendation": "biome",
56
- "default": "biome"
175
+ "initial": "biome"
57
176
  },
58
177
  {
59
178
  "type": "text",
60
179
  "id": "reason",
61
180
  "header": "Reason",
62
- "prompt": "Why this formatter?",
63
- "default": "Faster linting"
181
+ "prompt": "Anything I should optimize for?",
182
+ "required": false,
183
+ "placeholder": "optional"
64
184
  }
65
- ]
66
- }
67
- ```
68
-
69
- **Multi-select example:**
70
-
71
- ```json
72
- {
73
- "type": "choice",
74
- "multi": true,
75
- "id": "features",
76
- "header": "Features",
77
- "prompt": "Which features to include?",
78
- "options": [
79
- { "value": "auth", "label": "Authentication" },
80
- { "value": "caching", "label": "Caching" },
81
- { "value": "logging", "label": "Logging" }
82
185
  ],
83
- "recommendation": ["auth", "caching"]
186
+ "allowDiscuss": true
84
187
  }
85
188
  ```
86
189
 
87
- **Per-question features:**
88
-
89
- - `multi` — set to `true` to enable multi-select (replaces the former `multichoice` type). Default `false`.
90
- - `recommendation` — highlights the preferred option with a visual badge. String for single-select, array for multi-select.
91
- - `default` — pre-selects a starting value the user can accept with a single keystroke. String for single-select, array for multi-select.
92
- - `allowOther` — lets the user type a custom answer instead of picking from options.
93
- - `allowDiscuss` — lets the user opt into a discussion instead of deciding immediately.
94
- - `preview` — rich content (markdown, code, or ASCII mockups) shown alongside the option.
95
-
96
- **Questionnaire-level controls:**
97
-
98
- - `allowSkip` — exposes a Skip action so the user can submit partial results without answering all required questions.
99
-
100
- ## Limits
101
-
102
- - **1–4 questions** per questionnaire. Use one decision per call; chain multiple calls when you need more questions.
103
- - **2–12 options** per `choice` question (single or multi-select).
104
- - **60 characters** max per question header.
105
- - **4000 characters** max per question prompt.
106
-
107
- ## Requirements
108
-
109
- - `@earendil-works/pi-coding-agent`
110
- - `@earendil-works/pi-tui`
111
- - `typebox`
112
-
113
- ## Source
114
-
115
- Entrypoint: `src/ask-user.ts` — registers the `ask_user` tool, drives the questionnaire overlay, and manages the concurrency lock.
190
+ ## Source layout
191
+
192
+ - `src/extension.ts` — pi extension entrypoint
193
+ - `src/api.ts` — reusable public surface
194
+ - `src/index.ts` — package barrel
195
+ - `src/ask-user.ts` — tool registration and execution boundary
196
+ - `src/schema.ts` — tool-call parameter schema (TypeBox)
197
+ - `src/types.ts` — internal normalized types and answer shapes
198
+ - `src/normalize.ts` — validation and lowering into internal types
199
+ - `src/tool/guidance.ts` — prompt guidance and tool description
200
+ - `src/session/controller.ts` — headless decision-form state machine
201
+ - `src/session/lock.ts` — session-scoped concurrency lock
202
+ - `src/ui/choose-renderer.ts` — custom-overlay capability gate
203
+ - `src/ui/overlay.ts` — overlay runner that creates the custom interaction session
204
+ - `src/ui/overlay-component.ts` — rich custom interaction state and input orchestration
205
+ - `src/ui/overlay-view.ts` choice/action row modeling and split-layout helpers
206
+ - `src/ui/overlay-render.ts` rich overlay rendering built on `Markdown`, `Editor`, and `SelectList`
207
+ - `src/ui/overlay-actions.ts` exceptional-action list wiring for text questions
208
+ - `src/ui/types.ts` shared UI runner types
209
+ - `src/render/result.ts` — tool result shaping
210
+ - `src/render/transcript.ts` — transcript rendering
211
+ - `src/render/tree-summary.ts` — session-tree summary labels
@@ -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
+ }