@rol1n/pi-kit 0.1.0 → 0.1.1

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 (149) hide show
  1. package/README.md +33 -58
  2. package/ask-u-questions/README.md +26 -0
  3. package/{extensions/ask-u-questions → ask-u-questions}/dialog-ui.ts +23 -0
  4. package/{extensions/ask-u-questions → ask-u-questions}/index.ts +9 -5
  5. package/{extensions/ask-u-questions → ask-u-questions}/multi-choice.ts +1 -3
  6. package/{extensions/ask-u-questions → ask-u-questions}/single-choice.ts +1 -3
  7. package/{extensions/ask-u-questions → ask-u-questions}/tool-renderers.ts +1 -3
  8. package/ask-u-questions/types.ts +192 -0
  9. package/continue-signal/README.md +20 -0
  10. package/{extensions/continue-signal → continue-signal}/index.ts +21 -3
  11. package/custom-providers/README.md +20 -0
  12. package/{extensions/custom-providers → custom-providers}/index.ts +16 -2
  13. package/image-generate/README.md +25 -0
  14. package/{extensions/image-generate → image-generate}/index.ts +22 -2
  15. package/{extensions/image-generate → image-generate}/model-selector.ts +1 -1
  16. package/model-vision/README.md +22 -0
  17. package/{extensions/multi-harness → multi-harness}/README.md +3 -3
  18. package/oai-subquota/README.md +22 -0
  19. package/{extensions/oai-subquota → oai-subquota}/index.ts +16 -6
  20. package/{extensions/oai-subquota → oai-subquota}/snapshot.ts +127 -1
  21. package/{extensions/oai-subquota → oai-subquota}/ui.ts +1 -1
  22. package/package.json +45 -46
  23. package/prompt-snipaste/README.md +27 -0
  24. package/{extensions/prompt-snipaste → prompt-snipaste}/renderers.ts +0 -1
  25. package/{extensions/prompt-snipaste → prompt-snipaste}/snippets.ts +5 -2
  26. package/{extensions/prompt-snipaste → prompt-snipaste}/types.ts +0 -1
  27. package/screenshot/README.md +14 -0
  28. package/srp-theme/README.md +18 -0
  29. package/{extensions/srp-theme → srp-theme}/footer.ts +0 -1
  30. package/{extensions/srp-theme → srp-theme}/tps-meter.ts +0 -1
  31. package/{extensions/subagent → subagent}/README.md +1 -1
  32. package/tui-asr/README.md +21 -0
  33. package/{extensions/tui-asr → tui-asr}/dictation.ts +70 -3
  34. package/{extensions/tui-asr → tui-asr}/index.ts +2 -2
  35. package/{extensions/tui-asr → tui-asr}/input.ts +30 -1
  36. package/{extensions/tui-asr → tui-asr}/model-selector.ts +1 -1
  37. package/web-reach/README.md +19 -0
  38. package/{extensions/web-reach → web-reach}/page.ts +84 -2
  39. package/windows-wheel-fix/README.md +12 -0
  40. package/extensions/ask-u-questions/README.md +0 -32
  41. package/extensions/ask-u-questions/answers.ts +0 -53
  42. package/extensions/ask-u-questions/results.ts +0 -83
  43. package/extensions/ask-u-questions/schema.ts +0 -17
  44. package/extensions/ask-u-questions/types.ts +0 -45
  45. package/extensions/ask-u-questions/ui-lock.ts +0 -24
  46. package/extensions/continue-signal/README.md +0 -28
  47. package/extensions/continue-signal/renderer.ts +0 -21
  48. package/extensions/custom-providers/README.md +0 -28
  49. package/extensions/image-generate/README.md +0 -29
  50. package/extensions/image-generate/models.ts +0 -19
  51. package/extensions/model-vision/README.md +0 -28
  52. package/extensions/oai-subquota/README.md +0 -30
  53. package/extensions/oai-subquota/api.ts +0 -27
  54. package/extensions/oai-subquota/auth.ts +0 -34
  55. package/extensions/oai-subquota/cache.ts +0 -30
  56. package/extensions/oai-subquota/types.ts +0 -40
  57. package/extensions/prompt-snipaste/README.md +0 -33
  58. package/extensions/screenshot/README.md +0 -25
  59. package/extensions/srp-theme/README.md +0 -24
  60. package/extensions/subagent/skills/subagent/SKILL.md +0 -70
  61. package/extensions/tui-asr/README.md +0 -28
  62. package/extensions/tui-asr/audio.ts +0 -40
  63. package/extensions/tui-asr/clipboard.ts +0 -30
  64. package/extensions/tui-asr/providers.ts +0 -25
  65. package/extensions/web-reach/README.md +0 -37
  66. package/extensions/web-reach/pdf.ts +0 -42
  67. package/extensions/web-reach/reader.ts +0 -42
  68. package/extensions/windows-wheel-fix/README.md +0 -21
  69. /package/{extensions/custom-providers → custom-providers}/asr/handlers/dashscope.ts +0 -0
  70. /package/{extensions/custom-providers → custom-providers}/asr/index.ts +0 -0
  71. /package/{extensions/custom-providers → custom-providers}/asr/models.ts +0 -0
  72. /package/{extensions/custom-providers → custom-providers}/asr/types.ts +0 -0
  73. /package/{extensions/custom-providers → custom-providers}/chat/index.ts +0 -0
  74. /package/{extensions/custom-providers → custom-providers}/chat/magpie.ts +0 -0
  75. /package/{extensions/custom-providers → custom-providers}/chat/models.ts +0 -0
  76. /package/{extensions/custom-providers → custom-providers}/classifier/index.ts +0 -0
  77. /package/{extensions/custom-providers → custom-providers}/classifier/models.ts +0 -0
  78. /package/{extensions/custom-providers → custom-providers}/image/handlers/dashscope.ts +0 -0
  79. /package/{extensions/custom-providers → custom-providers}/image/handlers/openrouter.ts +0 -0
  80. /package/{extensions/custom-providers → custom-providers}/image/index.ts +0 -0
  81. /package/{extensions/custom-providers → custom-providers}/image/models.ts +0 -0
  82. /package/{extensions/custom-providers → custom-providers}/image/types.ts +0 -0
  83. /package/{extensions/custom-providers → custom-providers}/types.ts +0 -0
  84. /package/{extensions/image-generate → image-generate}/files.ts +0 -0
  85. /package/{extensions/memory-log → memory-log}/README.md +0 -0
  86. /package/{extensions/memory-log → memory-log}/agent/consolidator/prompt.ts +0 -0
  87. /package/{extensions/memory-log → memory-log}/agent/consolidator/tools.ts +0 -0
  88. /package/{extensions/memory-log → memory-log}/agent/cost.ts +0 -0
  89. /package/{extensions/memory-log → memory-log}/agent/index.ts +0 -0
  90. /package/{extensions/memory-log → memory-log}/agent/observer/prompt.ts +0 -0
  91. /package/{extensions/memory-log → memory-log}/agent/observer/tool.ts +0 -0
  92. /package/{extensions/memory-log → memory-log}/commands/compact.ts +0 -0
  93. /package/{extensions/memory-log → memory-log}/commands/consolidate.ts +0 -0
  94. /package/{extensions/memory-log → memory-log}/commands/status.ts +0 -0
  95. /package/{extensions/memory-log → memory-log}/config.ts +0 -0
  96. /package/{extensions/memory-log → memory-log}/debug-log.ts +0 -0
  97. /package/{extensions/memory-log → memory-log}/hooks/compaction-hook.ts +0 -0
  98. /package/{extensions/memory-log → memory-log}/hooks/compaction-trigger.ts +0 -0
  99. /package/{extensions/memory-log → memory-log}/hooks/consolidator-trigger.ts +0 -0
  100. /package/{extensions/memory-log → memory-log}/hooks/observer-trigger.ts +0 -0
  101. /package/{extensions/memory-log → memory-log}/ids.ts +0 -0
  102. /package/{extensions/memory-log → memory-log}/index.ts +0 -0
  103. /package/{extensions/memory-log → memory-log}/ledger/fold.ts +0 -0
  104. /package/{extensions/memory-log → memory-log}/ledger/index.ts +0 -0
  105. /package/{extensions/memory-log → memory-log}/ledger/pool.ts +0 -0
  106. /package/{extensions/memory-log → memory-log}/ledger/progress.ts +0 -0
  107. /package/{extensions/memory-log → memory-log}/ledger/projection.ts +0 -0
  108. /package/{extensions/memory-log → memory-log}/ledger/render.ts +0 -0
  109. /package/{extensions/memory-log → memory-log}/ledger/serialize.ts +0 -0
  110. /package/{extensions/memory-log → memory-log}/ledger/types.ts +0 -0
  111. /package/{extensions/memory-log → memory-log}/memory/index-render.ts +0 -0
  112. /package/{extensions/memory-log → memory-log}/memory/paths.ts +0 -0
  113. /package/{extensions/memory-log → memory-log}/memory/session.ts +0 -0
  114. /package/{extensions/memory-log → memory-log}/runtime.ts +0 -0
  115. /package/{extensions/memory-log → memory-log}/spawn/launch.ts +0 -0
  116. /package/{extensions/memory-log → memory-log}/spawn/runs.ts +0 -0
  117. /package/{extensions/memory-log → memory-log}/tokens.ts +0 -0
  118. /package/{extensions/memory-log → memory-log}/ui/status-controller.ts +0 -0
  119. /package/{extensions/memory-log → memory-log}/ui/timeline.ts +0 -0
  120. /package/{extensions/multi-harness → multi-harness}/adapters.mjs +0 -0
  121. /package/{extensions/multi-harness → multi-harness}/harness-done.mjs +0 -0
  122. /package/{extensions/multi-harness → multi-harness}/index.ts +0 -0
  123. /package/{extensions/multi-harness → multi-harness}/ipc.mjs +0 -0
  124. /package/{extensions/multi-harness → multi-harness}/opencode-plugin.mjs +0 -0
  125. /package/{extensions/multi-harness → multi-harness}/process-runner.mjs +0 -0
  126. /package/{extensions/multi-harness → multi-harness}/turn-hook.mjs +0 -0
  127. /package/{extensions/multi-harness → multi-harness}/worker-bridge.ts +0 -0
  128. /package/{extensions/prompt-snipaste → prompt-snipaste}/index.ts +0 -0
  129. /package/{extensions/prompt-snipaste → prompt-snipaste}/menu.ts +0 -0
  130. /package/{prompts → prompt-snipaste/prompts}/ask-questions.md +0 -0
  131. /package/{prompts → prompt-snipaste/prompts}/delegate-exploration.md +0 -0
  132. /package/{prompts → prompt-snipaste/prompts}/diagnose-report.md +0 -0
  133. /package/{prompts → prompt-snipaste/prompts}/frontend-tech-preset.md +0 -0
  134. /package/{prompts → prompt-snipaste/prompts}/orchestrator-mode.md +0 -0
  135. /package/{prompts → prompt-snipaste/prompts}/session-kickoff.md +0 -0
  136. /package/{prompts → prompt-snipaste/prompts}/verify-not-assume.md +0 -0
  137. /package/{extensions/srp-theme → srp-theme}/index.ts +0 -0
  138. /package/{extensions/subagent → subagent}/bridge.ts +0 -0
  139. /package/{extensions/subagent → subagent}/index.ts +0 -0
  140. /package/{extensions/subagent → subagent}/mux.ts +0 -0
  141. /package/{extensions/subagent → subagent}/session.ts +0 -0
  142. /package/{extensions/subagent → subagent}/ui.ts +0 -0
  143. /package/{extensions/tui-asr → tui-asr}/status.ts +0 -0
  144. /package/{extensions/web-reach → web-reach}/html.ts +0 -0
  145. /package/{extensions/web-reach → web-reach}/index.ts +0 -0
  146. /package/{extensions/web-reach → web-reach}/rsc.ts +0 -0
  147. /package/{extensions/web-reach → web-reach}/search.ts +0 -0
  148. /package/{extensions/web-reach → web-reach}/transport.ts +0 -0
  149. /package/{extensions/windows-wheel-fix → windows-wheel-fix}/index.ts +0 -0
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # pi-kit
2
2
 
3
- Pi extensions and prompt templates, published together as **`@rol1n/pi-kit`** or as individual extension packages. Each extension's README describes its commands, settings, platform support, and dependencies.
3
+ Pi extensions and prompt templates distributed together as **`@rol1n/pi-kit`** and individually as **`@rol1n/<extension>`**. Each extension directory contains its own `index.ts` and `README.md`.
4
4
 
5
5
  ## Install
6
6
 
@@ -8,92 +8,67 @@ Pi extensions and prompt templates, published together as **`@rol1n/pi-kit`** or
8
8
  pi install npm:@rol1n/pi-kit
9
9
  ```
10
10
 
11
- Install from GitHub or a local checkout during development:
11
+ Install from GitHub or a local checkout:
12
12
 
13
13
  ```sh
14
- pi install git:git@github.com:RolinShmily/pi-kit.git
14
+ pi install https://github.com/RolinShmily/pi-kit
15
15
  pi install /absolute/path/to/pi-kit
16
16
  ```
17
17
 
18
- Update an installed package with `pi update npm:@rol1n/pi-kit`, or update all Pi packages with `pi update --extensions`.
18
+ Update with `pi update npm:@rol1n/pi-kit` or `pi update --extensions`.
19
19
 
20
20
  ## Extensions
21
21
 
22
- | Package and documentation | Purpose | Included in the kit |
23
- |---|---|---|
24
- | [ask-u-questions](extensions/ask-u-questions/README.md) | Interactive questions | Yes |
25
- | [continue-signal](extensions/continue-signal/README.md) | Resume an idle agent | Yes |
26
- | [custom-providers](extensions/custom-providers/README.md) | Additional model providers and gateway configuration | Yes |
27
- | [image-generate](extensions/image-generate/README.md) | Image generation and model selection | Yes |
28
- | [memory-log](extensions/memory-log/README.md) | Background observational memory | Yes |
29
- | [model-vision](extensions/model-vision/README.md) | Image understanding and OCR | No |
30
- | [multi-harness](extensions/multi-harness/README.md) | Delegate to Pi, Codex, Claude, and OpenCode | Yes |
31
- | [oai-subquota](extensions/oai-subquota/README.md) | Codex subscription quota | Yes |
32
- | [prompt-snipaste](extensions/prompt-snipaste/README.md) | Select snippets for the next user message | Yes |
33
- | [screenshot](extensions/screenshot/README.md) | Reference the latest Windows/WSL screenshot | No |
34
- | [srp-theme](extensions/srp-theme/README.md) | Footer, prompt preview, and throughput display | Yes |
35
- | [subagent](extensions/subagent/README.md) | Delegate to asynchronous Pi workers | Yes |
36
- | [tui-asr](extensions/tui-asr/README.md) | Streaming voice dictation | Yes |
37
- | [web-reach](extensions/web-reach/README.md) | Web search and page extraction | Yes |
38
- | [windows-wheel-fix](extensions/windows-wheel-fix/README.md) | Windows ConPTY/Zellij mouse reporting | Yes, active only on Windows |
39
-
40
- Independent package names use the `@rol1n/<name>` scope, such as `@rol1n/memory-log` or `@rol1n/multi-harness`. Each package has its own release version and README; the implementation is shared with the kit.
41
-
42
- Every extension uses the same directory layout:
43
-
44
- ```text
45
- extensions/<name>/
46
- ├── index.ts # Pi entrypoint and registration
47
- ├── README.md # Package documentation
48
- └── ... # Modules grouped by responsibility
49
- ```
50
-
51
- UI, transport, parsing, and storage helpers live beside the entrypoint as needed. Small extensions can remain a single cohesive `index.ts`.
22
+ | Extension | Purpose | Default in kit | Standalone npm |
23
+ |---|---|---|---|
24
+ | [ask-u-questions](ask-u-questions/README.md) | Interactive single/multi-choice and text dialogs | Yes | `@rol1n/ask-u-questions` |
25
+ | [continue-signal](continue-signal/README.md) | Resume an idle agent via `/continue` or Alt+C | Yes | `@rol1n/continue-signal` |
26
+ | [custom-providers](custom-providers/README.md) | Additional model providers and gateway registration | Yes | `@rol1n/custom-providers` |
27
+ | [image-generate](image-generate/README.md) | Image generation tool and `/image-generate` selector | Yes | `@rol1n/image-generate` |
28
+ | [memory-log](memory-log/README.md) | Background observational memory and compaction | Yes | `@rol1n/memory-log` |
29
+ | [model-vision](model-vision/README.md) | Image understanding and OCR tool | No | `@rol1n/model-vision` |
30
+ | [multi-harness](multi-harness/README.md) | Delegate to Pi, Codex, Claude, and OpenCode | Yes | `@rol1n/multi-harness` |
31
+ | [oai-subquota](oai-subquota/README.md) | OpenAI Codex subscription quota monitor | Yes | `@rol1n/oai-subquota` |
32
+ | [prompt-snipaste](prompt-snipaste/README.md) | Select prompt snippets for the next user message (bundles 7 built-in prompts) | Yes | `@rol1n/prompt-snipaste` |
33
+ | [screenshot](screenshot/README.md) | Insert the latest Windows/WSL screenshot path | No | `@rol1n/screenshot` |
34
+ | [srp-theme](srp-theme/README.md) | Prompt preview footer and streaming TPS meter | Yes | `@rol1n/srp-theme` |
35
+ | [subagent](subagent/README.md) | Delegate to asynchronous Pi workers | Yes | `@rol1n/subagent` |
36
+ | [tui-asr](tui-asr/README.md) | Streaming voice dictation via DashScope ASR | Yes | `@rol1n/tui-asr` |
37
+ | [web-reach](web-reach/README.md) | Keyless Exa web search and multi-tier page extraction | Yes | `@rol1n/web-reach` |
38
+ | [windows-wheel-fix](windows-wheel-fix/README.md) | Windows ConPTY/Zellij mouse-wheel reporting fix | Yes (Windows only) | `@rol1n/windows-wheel-fix` |
52
39
 
53
40
  ## Resource selection
54
41
 
55
- The kit currently exposes 13 extension entrypoints and the seven templates in [prompts](prompts/). Personal skills, provider credentials, MCP servers, and keybindings remain normal Pi configuration.
56
-
57
- Use `pi config` or narrow package resources in personal/project settings:
42
+ The kit exposes 13 extension entrypoints and the 7 prompt templates bundled in [prompt-snipaste/prompts](prompt-snipaste/prompts/). Narrow loaded resources with `pi config` or in `settings.json`:
58
43
 
59
44
  ```json
60
45
  {
61
46
  "packages": [
62
47
  {
63
48
  "source": "npm:@rol1n/pi-kit",
64
- "extensions": ["extensions/**", "!extensions/tui-asr/**"],
65
- "prompts": ["prompts/diagnose-report.md"]
49
+ "extensions": ["**/*.ts", "!tui-asr/**"],
50
+ "prompts": ["prompt-snipaste/prompts/diagnose-report.md"]
66
51
  }
67
52
  ]
68
53
  }
69
54
  ```
70
55
 
71
- Omit a resource field to load the package defaults; use `[]` to disable that resource type. Filters select resources already exposed by the package. When also installing an independent extension package, exclude its entry from the kit to avoid loading the same feature twice.
72
-
73
56
  ## Build and publish
74
57
 
75
- From a source checkout, edit [kit.config.json](https://github.com/RolinShmily/pi-kit/blob/main/kit.config.json) to choose each extension's `bundle` flag, package name, version, entrypoint, runtime files, and README. Then synchronize and inspect the aggregate release:
58
+ [kit.config.json](kit.config.json) controls which extensions are bundled into `@rol1n/pi-kit` (`bundle: true`) and inter-extension npm dependencies (`deps`).
76
59
 
77
60
  ```sh
61
+ # Sync package.json from kit.config.json and validate
78
62
  npm run manifest
79
63
  npm run check
80
- npm pack --dry-run --ignore-scripts
81
- npm publish --access public
82
- ```
83
64
 
84
- Prepare an independent release from the same source:
65
+ # Stage any extension into dist/<id>
66
+ npm run prepare:extension -- subagent
85
67
 
86
- ```sh
87
- npm run prepare:extension -- memory-log
88
- cd dist/extensions/memory-log
89
- npm pack --dry-run --ignore-scripts
90
- npm publish --access public
68
+ # Publish all standalone extensions, or the kit, or one extension
69
+ npm run publish:ci -- extensions
70
+ npm run publish:ci -- kit
71
+ npm run publish:ci -- tui-asr
91
72
  ```
92
73
 
93
- The preparation command copies that extension's README to the package root and includes shared implementation files where needed. Add new runtime assets to its `files` list. Names and versions are configured independently; use an available npm name and a new version for each publication. Preparation does not install dependencies or publish anything.
94
-
95
- All packages link to [RolinShmily/pi-kit](https://github.com/RolinShmily/pi-kit). The `pi-package` keyword makes each published package eligible for the Pi gallery; indexing can take time. Pi supplies the host libraries declared as peer dependencies.
96
-
97
- ## Attribution
98
-
99
- Existing author and copyright notices remain with their source files. Upstream issue references and integration details are linked from the individual extension READMEs, including the [Windows terminal workaround](extensions/windows-wheel-fix/README.md).
74
+ GitHub Actions OIDC Trusted Publishing is configured in [`.github/workflows/publish.yml`](.github/workflows/publish.yml).
@@ -0,0 +1,26 @@
1
+ # ask-u-questions
2
+
3
+ Interactive single-choice, multiple-choice, and free-text questions for Pi. Registers the `ask_user_question` tool and serializes dialogs through a shared UI lock.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ pi install npm:@rol1n/ask-u-questions
9
+ ```
10
+
11
+ Also included in `@rol1n/pi-kit`.
12
+
13
+ ## Use
14
+
15
+ ```json
16
+ {
17
+ "question": "Which output format should I use?",
18
+ "options": [
19
+ { "label": "Markdown (Recommended)", "value": "markdown" },
20
+ { "label": "JSON", "value": "json" }
21
+ ],
22
+ "multiSelect": false
23
+ }
24
+ ```
25
+
26
+ Omit `options` or pass `[]` for free-text input. Set `multiSelect: true` only when multiple choices are expected. `details` adds supporting context, and an `Other` option is provided automatically for choice questions.
@@ -55,3 +55,26 @@ export function addWrapped(
55
55
  }
56
56
  }
57
57
 
58
+ const SHARED_UI_LOCK_KEY = "__piSharedUiLock";
59
+ function getSharedUiLock(): { withLock<T>(fn: () => T | Promise<T>): Promise<T> } {
60
+ const g = globalThis as any;
61
+ if (!g[SHARED_UI_LOCK_KEY]) {
62
+ let chain: Promise<void> = Promise.resolve();
63
+ g[SHARED_UI_LOCK_KEY] = {
64
+ withLock<T>(fn: () => T | Promise<T>): Promise<T> {
65
+ const prev = chain;
66
+ let release: () => void;
67
+ chain = new Promise<void>((r) => {
68
+ release = r;
69
+ });
70
+ return prev.then(fn).finally(() => release!());
71
+ },
72
+ };
73
+ }
74
+ return g[SHARED_UI_LOCK_KEY];
75
+ }
76
+ const sharedUiLock = getSharedUiLock();
77
+
78
+ export function withUILock<T>(fn: () => Promise<T>): Promise<T> {
79
+ return sharedUiLock.withLock(fn);
80
+ }
@@ -1,12 +1,16 @@
1
1
  /** Interactive questions, serialized through the shared dialog lock. */
2
2
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
- import type { AskUserQuestionMode } from "./types.ts";
4
- import { AskUserQuestionParams } from "./schema.ts";
5
- import { normalizeOptions } from "./answers.ts";
6
- import { buildResult, cancelledResult, unavailableResult } from "./results.ts";
3
+ import {
4
+ AskUserQuestionParams,
5
+ buildResult,
6
+ cancelledResult,
7
+ normalizeOptions,
8
+ unavailableResult,
9
+ type AskUserQuestionMode,
10
+ } from "./types.ts";
11
+ import { withUILock } from "./dialog-ui.ts";
7
12
  import { askSingleChoice } from "./single-choice.ts";
8
13
  import { askMultiChoice } from "./multi-choice.ts";
9
- import { withUILock } from "./ui-lock.ts";
10
14
  import { questionRenderers } from "./tool-renderers.ts";
11
15
 
12
16
  export type { AskOption, TextAnswer, OptionAnswer, OtherAnswer, AskAnswer, AskUserQuestionStatus, AskUserQuestionMode, AskUserQuestionResultDetails } from "./types.ts";
@@ -1,7 +1,6 @@
1
1
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
  import { Editor, Key, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
3
- import type { AskAnswer, AskOption, DisplayOption } from "./types.ts";
4
- import { getOtherLabel, isChineseContext, sortAnswers } from "./answers.ts";
3
+ import { getOtherLabel, isChineseContext, sortAnswers, type AskAnswer, type AskOption, type DisplayOption } from "./types.ts";
5
4
  import { addWrapped, createEditorTheme } from "./dialog-ui.ts";
6
5
 
7
6
  export async function askMultiChoice(
@@ -257,4 +256,3 @@ export async function askMultiChoice(
257
256
  },
258
257
  );
259
258
  }
260
-
@@ -1,7 +1,6 @@
1
1
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
  import { Editor, Key, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
3
- import type { AskAnswer, AskOption, DisplayOption } from "./types.ts";
4
- import { getOtherLabel, isChineseContext } from "./answers.ts";
3
+ import { getOtherLabel, isChineseContext, type AskAnswer, type AskOption, type DisplayOption } from "./types.ts";
5
4
  import { addWrapped, createEditorTheme } from "./dialog-ui.ts";
6
5
 
7
6
  export async function askSingleChoice(
@@ -179,4 +178,3 @@ export async function askSingleChoice(
179
178
  },
180
179
  );
181
180
  }
182
-
@@ -1,8 +1,6 @@
1
1
  import type { ToolDefinition } from "@earendil-works/pi-coding-agent";
2
2
  import { Text } from "@earendil-works/pi-tui";
3
- import type { AskUserQuestionParams } from "./schema.ts";
4
- import type { AskUserQuestionResultDetails } from "./types.ts";
5
- import { getOtherLabel, isChineseContext, normalizeOptions } from "./answers.ts";
3
+ import { AskUserQuestionParams, getOtherLabel, isChineseContext, normalizeOptions, type AskUserQuestionResultDetails } from "./types.ts";
6
4
 
7
5
  export const questionRenderers: Pick<ToolDefinition<typeof AskUserQuestionParams, AskUserQuestionResultDetails>, "renderCall" | "renderResult"> = {
8
6
  renderCall(args, theme, context) {
@@ -0,0 +1,192 @@
1
+ import { Type } from "typebox";
2
+
3
+ const OptionSchema = Type.Object({
4
+ label: Type.String({ description: 'Option label. Put a recommended option first and append "(Recommended)".' }),
5
+ value: Type.Optional(Type.String({ description: "Machine-readable value; defaults to the label." })),
6
+ description: Type.Optional(Type.String({ description: "Additional context shown below the option." })),
7
+ });
8
+
9
+ export const AskUserQuestionParams = Type.Object({
10
+ question: Type.String({ description: "The question to ask. Ask only one question per call." }),
11
+ details: Type.Optional(Type.String({ description: "Background or instructions shown below the question." })),
12
+ options: Type.Optional(Type.Array(OptionSchema, {
13
+ description: "Choices for selection. Omit or pass an empty array for free-text input. An Other choice is always available.",
14
+ })),
15
+ multiSelect: Type.Optional(Type.Boolean({ description: "Allow selecting multiple options when true." })),
16
+ });
17
+
18
+ export interface AskOption {
19
+ label: string;
20
+ value: string;
21
+ description?: string;
22
+ }
23
+
24
+ export interface DisplayOption extends AskOption {
25
+ id: string;
26
+ index?: number;
27
+ isOther?: boolean;
28
+ isSubmit?: boolean;
29
+ }
30
+
31
+ export interface TextAnswer {
32
+ type: "text";
33
+ label: string;
34
+ value: string;
35
+ }
36
+
37
+ export interface OptionAnswer {
38
+ type: "option";
39
+ label: string;
40
+ value: string;
41
+ index: number;
42
+ }
43
+
44
+ export interface OtherAnswer {
45
+ type: "other";
46
+ label: string;
47
+ value: string;
48
+ }
49
+
50
+ export type AskAnswer = TextAnswer | OptionAnswer | OtherAnswer;
51
+ export type AskUserQuestionStatus = "answered" | "cancelled" | "unavailable";
52
+ export type AskUserQuestionMode = "text" | "single-select" | "multi-select";
53
+
54
+ export interface AskUserQuestionResultDetails {
55
+ status: AskUserQuestionStatus;
56
+ question: string;
57
+ context?: string;
58
+ mode: AskUserQuestionMode;
59
+ answers: AskAnswer[];
60
+ message?: string;
61
+ }
62
+
63
+ export function normalizeOptions(
64
+ options: Array<{ label: string; value?: string; description?: string }> | undefined,
65
+ ): AskOption[] {
66
+ return (options || [])
67
+ .map((option) => ({
68
+ label: option.label.trim(),
69
+ value: option.value?.trim() || option.label.trim(),
70
+ description: option.description?.trim() || undefined,
71
+ }))
72
+ .filter((option) => option.label.length > 0);
73
+ }
74
+
75
+ export function isChineseContext(
76
+ question: string,
77
+ options?: Array<{ label?: string; description?: string }>,
78
+ context?: string,
79
+ ): boolean {
80
+ if (/[\u4e00-\u9fa5]/.test(question)) return true;
81
+ if (context && /[\u4e00-\u9fa5]/.test(context)) return true;
82
+ if (options && options.some((o) => (o.label && /[\u4e00-\u9fa5]/.test(o.label)) || (o.description && /[\u4e00-\u9fa5]/.test(o.description)))) {
83
+ return true;
84
+ }
85
+ return false;
86
+ }
87
+
88
+ export function getOtherLabel(options: AskOption[], isZh = false): string {
89
+ if (isZh) {
90
+ return options.some((option) => option.label === "其他(自定义输入)")
91
+ ? "自定义输入"
92
+ : "其他(自定义输入)";
93
+ }
94
+ return options.some((option) => option.label === "Other (custom input)")
95
+ ? "Custom input"
96
+ : "Other (custom input)";
97
+ }
98
+
99
+ function answerSortRank(answer: AskAnswer): number {
100
+ switch (answer.type) {
101
+ case "option":
102
+ return answer.index;
103
+ case "other":
104
+ return Number.MAX_SAFE_INTEGER - 1;
105
+ case "text":
106
+ return Number.MAX_SAFE_INTEGER;
107
+ }
108
+ }
109
+
110
+ export function sortAnswers(answers: AskAnswer[]): AskAnswer[] {
111
+ return [...answers].sort((a, b) => answerSortRank(a) - answerSortRank(b));
112
+ }
113
+
114
+ function formatAnswerForModel(answer: AskAnswer): string {
115
+ switch (answer.type) {
116
+ case "text":
117
+ return answer.label;
118
+ case "other":
119
+ return `Other: ${answer.label}`;
120
+ case "option":
121
+ return `${answer.index}. ${answer.label}`;
122
+ }
123
+ }
124
+
125
+ function buildStructuredResult(
126
+ status: AskUserQuestionStatus,
127
+ question: string,
128
+ mode: AskUserQuestionMode,
129
+ answers: AskAnswer[],
130
+ context?: string,
131
+ message?: string,
132
+ ): AskUserQuestionResultDetails {
133
+ return {
134
+ status,
135
+ question,
136
+ context,
137
+ mode,
138
+ answers,
139
+ message,
140
+ };
141
+ }
142
+
143
+ export function cancelledResult(question: string, mode: AskUserQuestionMode, context?: string) {
144
+ const isZh = isChineseContext(question, undefined, context);
145
+ const message = isZh ? "用户取消了本次提问" : "User cancelled the question.";
146
+ return {
147
+ content: [{ type: "text" as const, text: message }],
148
+ details: buildStructuredResult("cancelled", question, mode, [], context, message),
149
+ };
150
+ }
151
+
152
+ export function unavailableResult(question: string, mode: AskUserQuestionMode, message: string, context?: string) {
153
+ return {
154
+ content: [{ type: "text" as const, text: message }],
155
+ details: buildStructuredResult("unavailable", question, mode, [], context, message),
156
+ };
157
+ }
158
+
159
+ export function buildResult(
160
+ question: string,
161
+ context: string | undefined,
162
+ mode: AskUserQuestionMode,
163
+ answers: AskAnswer[],
164
+ ) {
165
+ const isZh = isChineseContext(question, answers, context);
166
+ let text: string;
167
+ if (mode === "text") {
168
+ const answer = answers[0];
169
+ if (isZh) {
170
+ text = answer.label.trim().length > 0
171
+ ? `用户输入回答: ${answer.label}`
172
+ : "用户提交了空回答";
173
+ } else {
174
+ text = answer.label.trim().length > 0
175
+ ? `User answered: ${answer.label}`
176
+ : "User submitted an empty answer";
177
+ }
178
+ } else if (mode === "single-select") {
179
+ text = isZh
180
+ ? `用户选择项: ${formatAnswerForModel(answers[0])}`
181
+ : `User selected: ${formatAnswerForModel(answers[0])}`;
182
+ } else {
183
+ text = isZh
184
+ ? `用户选择多项:\n${answers.map((answer) => `- ${formatAnswerForModel(answer)}`).join("\n")}`
185
+ : `User selected multiple:\n${answers.map((answer) => `- ${formatAnswerForModel(answer)}`).join("\n")}`;
186
+ }
187
+
188
+ return {
189
+ content: [{ type: "text" as const, text }],
190
+ details: buildStructuredResult("answered", question, mode, answers, context),
191
+ };
192
+ }
@@ -0,0 +1,20 @@
1
+ # continue-signal
2
+
3
+ Resume an idle Pi agent with a visible continuation signal.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ pi install npm:@rol1n/continue-signal
9
+ ```
10
+
11
+ Also included in `@rol1n/pi-kit`.
12
+
13
+ ## Commands and shortcuts
14
+
15
+ | Action | Trigger |
16
+ |---|---|
17
+ | Resume unfinished work | `/continue` or `Alt+C` |
18
+ | Resume with custom instruction | `/continue Review the remaining test failures` |
19
+
20
+ An explicit command argument takes priority; otherwise the current editor draft is consumed, falling back to a default resume prompt.
@@ -3,13 +3,31 @@
3
3
  * Uses the editor draft when no prompt is supplied.
4
4
  */
5
5
 
6
- import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
7
- import { Key } from "@earendil-works/pi-tui";
8
- import { renderContinueSignal } from "./renderer.ts";
6
+ import type { ExtensionAPI, ExtensionContext, MessageRenderer } from "@earendil-works/pi-coding-agent";
7
+ import { Box, Key, Text } from "@earendil-works/pi-tui";
9
8
 
10
9
  const DEFAULT_PROMPT = "Review the conversation context and resume any unfinished work from where it stopped.";
11
10
  const CUSTOM_TYPE = "continue-signal";
12
11
 
12
+ const renderContinueSignal: MessageRenderer<{ timestamp?: number }> = (
13
+ message,
14
+ { expanded, outputPad },
15
+ theme,
16
+ ) => {
17
+ const time = new Date(message.details?.timestamp ?? Date.now()).toLocaleTimeString();
18
+ const title = `${theme.bold(theme.fg("accent", "⚡ LOOP RESUME SIGNAL"))} ${theme.fg("muted", `[${time}]` )}`;
19
+
20
+ const box = new Box(outputPad, 1, (text) => theme.bg("customMessageBg", text));
21
+ box.addChild(new Text(title, 0, 0));
22
+ box.addChild(new Text(theme.fg("text", "Re-linking conversation context & resuming agent loop..."), 0, 0));
23
+
24
+ if (expanded && message.content) {
25
+ box.addChild(new Text(theme.fg("dim", `Prompt: ${message.content}`), 0, 0));
26
+ }
27
+
28
+ return box;
29
+ };
30
+
13
31
  export default function (pi: ExtensionAPI) {
14
32
  pi.registerMessageRenderer(CUSTOM_TYPE, renderContinueSignal);
15
33
 
@@ -0,0 +1,20 @@
1
+ # custom-providers
2
+
3
+ Registers additional model providers through Pi's native model registry and `/login` interface.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ pi install npm:@rol1n/custom-providers
9
+ ```
10
+
11
+ Also included in `@rol1n/pi-kit`.
12
+
13
+ ## Providers
14
+
15
+ | Provider | Configuration |
16
+ |---|---|
17
+ | Magpie | `MAGPIE_BASE_URL` (default `http://127.0.0.1:3425/v1`); authenticate via `MAGPIE_GATEWAY_KEY` or `/login magpie` |
18
+ | SHUAI API | Authenticate via `/login shuaiapi` |
19
+ | BigModel | Authenticate via `/login bigmodel` |
20
+ | DashScope | Authenticate via `/login dashscope`; `DASHSCOPE_IMAGE_URL` overrides the image API base URL |
@@ -10,9 +10,19 @@ import { imageProviders } from "./image/index.ts";
10
10
  import { classifierProviders } from "./classifier/index.ts";
11
11
  import { asrProviders } from "./asr/index.ts";
12
12
 
13
- export { asrProviders, ALL_ASR_MODELS } from "./asr/index.ts";
13
+ export { asrProviders, ALL_ASR_MODELS, createAsrProvider, type AsrModelDefinition, type StreamingAsrProvider } from "./asr/index.ts";
14
+ export { imageProviders, fetchAllAvailableImageModels, generateImage, resolveImageModel, type ImageModelDefinition } from "./image/index.ts";
15
+
16
+ const BATCH_LOCK_KEY = "__piCustomProvidersBatchLock";
17
+
18
+ export function ensureCustomProviders(pi: ExtensionAPI): void {
19
+ const g = globalThis as Record<string, unknown>;
20
+ if (g[BATCH_LOCK_KEY]) return;
21
+ g[BATCH_LOCK_KEY] = true;
22
+ setTimeout(() => {
23
+ g[BATCH_LOCK_KEY] = false;
24
+ }, 0).unref?.();
14
25
 
15
- export default function customProviders(pi: ExtensionAPI): void {
16
26
  const providerIds = new Set<string>([
17
27
  ...Object.keys(chatProviders),
18
28
  ...Object.keys(imageProviders),
@@ -48,3 +58,7 @@ export default function customProviders(pi: ExtensionAPI): void {
48
58
  pi.registerProvider(id, config);
49
59
  }
50
60
  }
61
+
62
+ export default function customProviders(pi: ExtensionAPI): void {
63
+ ensureCustomProviders(pi);
64
+ }
@@ -0,0 +1,25 @@
1
+ # image-generate
2
+
3
+ Generate images through OpenRouter or DashScope and save them to `.pi/generated-images/` in the current project. Registers the `image_generate` tool and `/image-generate` model selector.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ pi install npm:@rol1n/image-generate
9
+ ```
10
+
11
+ Also included in `@rol1n/pi-kit`. Installing this package automatically installs `@rol1n/custom-providers`.
12
+
13
+ ## Use
14
+
15
+ Run `/image-generate model` to open the interactive model picker, or `/image-generate model qwen-image-3.0-pro` to switch directly.
16
+
17
+ ```json
18
+ {
19
+ "prompt": "A clean technical diagram of a terminal workflow",
20
+ "aspectRatio": "16:9",
21
+ "model": "google/gemini-3.1-flash-image"
22
+ }
23
+ ```
24
+
25
+ Authenticate via `/login openrouter` or `/login dashscope` (or `OPENROUTER_API_KEY` / `DASHSCOPE_API_KEY`).
@@ -12,13 +12,32 @@ import { Text, type AutocompleteItem } from "@earendil-works/pi-tui";
12
12
  import { Type } from "typebox";
13
13
 
14
14
  import {
15
+ ensureCustomProviders,
15
16
  fetchAllAvailableImageModels,
16
17
  generateImage,
17
18
  resolveImageModel,
18
19
  type ImageModelDefinition,
19
- } from "../custom-providers/image/index.ts";
20
+ } from "../custom-providers/index.ts";
20
21
  import { saveImageAtomically } from "./files.ts";
21
- import { DEFAULT_MODEL, registryImageModels, resolveProviderId } from "./models.ts";
22
+ /** Image catalog lookup and provider selection. */
23
+
24
+ export const DEFAULT_MODEL = "google/gemini-3.1-flash-image";
25
+
26
+ /** Image models known to Pi: OpenRouter's built-in catalog plus our DashScope registration. */
27
+ export function registryImageModels(ctx: { modelRegistry: { getModelsOfType(type: string): readonly any[] } }) {
28
+ return ctx.modelRegistry.getModelsOfType("image") as readonly { id: string; name?: string; provider: string }[];
29
+ }
30
+
31
+ /** Resolve which provider owns a model id, preferring the Pi registry over heuristics. */
32
+ export function resolveProviderId(
33
+ ctx: { modelRegistry: { getModelsOfType(type: string): readonly any[] } },
34
+ modelId: string,
35
+ ): string {
36
+ const lower = modelId.toLowerCase();
37
+ const known = registryImageModels(ctx).find((m) => m.id.toLowerCase() === lower);
38
+ if (known) return known.provider;
39
+ return lower.startsWith("qwen-image") ? "dashscope" : "openrouter";
40
+ }
22
41
  import { SearchableImageModelSelectorComponent } from "./model-selector.ts";
23
42
 
24
43
  const DEFAULT_TIMEOUT_MS = 120_000;
@@ -28,6 +47,7 @@ let currentModel = DEFAULT_MODEL;
28
47
  // ============================ Extension Entrypoint ============================
29
48
 
30
49
  export default function imageGenerate(pi: ExtensionAPI): void {
50
+ ensureCustomProviders(pi);
31
51
  // 1. Slash command: /image-generate model [name]
32
52
  pi.registerCommand("image-generate", {
33
53
  description: "Switch default image generation model (/image-generate model [name])",
@@ -1,5 +1,5 @@
1
1
  import { Container, Input, Spacer, Text, fuzzyFilter, getKeybindings } from "@earendil-works/pi-tui";
2
- import type { ImageModelDefinition } from "../custom-providers/image/index.ts";
2
+ import type { ImageModelDefinition } from "../custom-providers/index.ts";
3
3
 
4
4
  export class SearchableImageModelSelectorComponent extends Container {
5
5
  private searchInput: Input;
@@ -0,0 +1,22 @@
1
+ # model-vision
2
+
3
+ Analyze a local image or public HTTP(S) image URL with a vision model. Registers the `model_vision` tool for OCR, UI inspection, and visual Q&A.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ pi install npm:@rol1n/model-vision
9
+ ```
10
+
11
+ Available as a standalone package (not enabled in `@rol1n/pi-kit` by default).
12
+
13
+ ## Use
14
+
15
+ ```json
16
+ {
17
+ "image": "./screenshots/error.png",
18
+ "prompt": "Read the error message and describe the relevant UI."
19
+ }
20
+ ```
21
+
22
+ Supports PNG, JPEG, WebP, GIF, and BMP up to 10 MiB. Private/local network URLs are blocked. Default model fallback chain is configured in `DEFAULT_VISION_MODELS` in `index.ts`.