@fadhelhaidar/pi-ask-user 0.0.0-stage → 1.0.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.
- package/LICENSE +21 -0
- package/NOTICE.md +43 -0
- package/README.md +111 -2
- package/dist/chunks/external-editor-5wd19nyg.js +51 -0
- package/dist/chunks/index-bx2tbz93.js +5 -0
- package/dist/chunks/index-x7fb685n.js +226 -0
- package/dist/chunks/questionnaire-session-gdq5zqtt.js +2190 -0
- package/dist/events.d.ts +53 -0
- package/dist/events.js +8 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +538 -0
- package/dist/locales/de.json +31 -0
- package/dist/locales/en.json +29 -0
- package/dist/locales/es.json +31 -0
- package/dist/locales/fr.json +31 -0
- package/dist/locales/pt-BR.json +31 -0
- package/dist/locales/pt.json +31 -0
- package/dist/locales/ru.json +31 -0
- package/dist/locales/uk.json +31 -0
- package/dist/locales/zh.json +33 -0
- package/docs/configuration.md +121 -0
- package/docs/hosts.md +97 -0
- package/docs/keyboard.md +115 -0
- package/docs/localization.md +87 -0
- package/docs/tool-schema.md +127 -0
- package/package.json +64 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 juicesharp
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/NOTICE.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Attribution and provenance
|
|
2
|
+
|
|
3
|
+
## Original project
|
|
4
|
+
|
|
5
|
+
This extension is derived from [juicesharp/rpiv-mono](https://github.com/juicesharp/rpiv-mono),
|
|
6
|
+
specifically [`packages/rpiv-ask-user-question`](https://github.com/juicesharp/rpiv-mono/tree/main/packages/rpiv-ask-user-question),
|
|
7
|
+
published as `@juicesharp/rpiv-ask-user-question`. The original code is MIT-licensed
|
|
8
|
+
and authored by juicesharp. This is an extraction of that implementation with
|
|
9
|
+
personal patches, not an independently written replacement.
|
|
10
|
+
|
|
11
|
+
## Source lineage
|
|
12
|
+
|
|
13
|
+
1. Local interaction patches began against the published 2.12.0 extension.
|
|
14
|
+
2. The patches were ported into the personal fork `FadhelHaidar/rpiv-mono`,
|
|
15
|
+
based on upstream commit
|
|
16
|
+
[`68d9a0014b70006d7b04b57933752338a2716db7`](https://github.com/juicesharp/rpiv-mono/commit/68d9a0014b70006d7b04b57933752338a2716db7).
|
|
17
|
+
3. The patched extension was extracted into `FadhelHaidar/pi-ask-user` from
|
|
18
|
+
fork snapshot `c814ebb34cbcf68143ea88089be8c7cd1a69159c`.
|
|
19
|
+
|
|
20
|
+
Patch identifiers in the former fork (recorded here for provenance, not as
|
|
21
|
+
required downloads or links to a repository that must remain online):
|
|
22
|
+
|
|
23
|
+
- `76abd683b1a300e4962ed1ec2288c6430c10fadb` — combined multi-select/custom
|
|
24
|
+
answers, Enter-only toggling, immediate custom-row editing, and draft retention.
|
|
25
|
+
- `023c6ac46e7eb99e7a3e3558ef13aba8bcff7090` — regression coverage for those behaviors.
|
|
26
|
+
|
|
27
|
+
## Customization and independent maintenance
|
|
28
|
+
|
|
29
|
+
The interaction changes let custom text supplement predefined selections while
|
|
30
|
+
keeping checkbox selection distinct from typing and preserving unchecked drafts.
|
|
31
|
+
The standalone extraction removes the need to maintain or install a full
|
|
32
|
+
monorepo fork for this single tool. This project owns its releases; upstream
|
|
33
|
+
synchronization and upstream PR preparation are not part of its maintenance workflow.
|
|
34
|
+
|
|
35
|
+
`lib/config-io.ts` and the fixtures in `test/utils/` were also extracted from
|
|
36
|
+
that MIT-licensed source tree. Original copyright is retained in LICENSE.
|
|
37
|
+
|
|
38
|
+
This repository is maintained independently. It does not require the monorepo,
|
|
39
|
+
an upstream checkout, or the rpiv-config npm package. The rpiv-i18n integration
|
|
40
|
+
remains optional at runtime; its published package is used for localization tests.
|
|
41
|
+
|
|
42
|
+
The tool name, rpiv event names, localization namespace, and configuration path
|
|
43
|
+
remain unchanged for compatibility with existing sessions and other extensions.
|
package/README.md
CHANGED
|
@@ -1,3 +1,112 @@
|
|
|
1
|
-
#
|
|
1
|
+
# pi-ask-user
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A standalone Pi extension providing the `ask_user_question` tool: tabbed questionnaires, single- and multi-select choices, custom answers, notes, and markdown previews.
|
|
4
|
+
|
|
5
|
+
Maintained independently by FadhelHaidar. No monorepo checkout or upstream-sync workflow is needed.
|
|
6
|
+
|
|
7
|
+
## Origin and why this version exists
|
|
8
|
+
|
|
9
|
+
This project is derived from **juicesharp's [`@juicesharp/rpiv-ask-user-question`](https://github.com/juicesharp/rpiv-mono/tree/main/packages/rpiv-ask-user-question)**, originally part of the MIT-licensed [`juicesharp/rpiv-mono`](https://github.com/juicesharp/rpiv-mono) monorepo. The original author built the questionnaire UI and tool; this repository preserves that work and its copyright notice.
|
|
10
|
+
|
|
11
|
+
The customization started as local patches to the published **2.12.0** extension, then moved into a personal monorepo fork, `FadhelHaidar/rpiv-mono`. This standalone repository extracts that patched extension rather than rewriting it. Its upstream baseline is commit [`68d9a0014b70006d7b04b57933752338a2716db7`](https://github.com/juicesharp/rpiv-mono/commit/68d9a0014b70006d7b04b57933752338a2716db7); the extraction snapshot and patch identifiers are recorded in [NOTICE.md](NOTICE.md), even if the former personal fork is deleted.
|
|
12
|
+
|
|
13
|
+
### Why customize the interaction?
|
|
14
|
+
|
|
15
|
+
The goal is to make multi-select questions work naturally when the available choices are useful **but not exhaustive**:
|
|
16
|
+
|
|
17
|
+
- **Choose options and add your own answer together.** Custom text should supplement checked choices, not replace them. For example, selecting “Unit tests” and “Integration tests” can also include “Add a smoke test on deployment.”
|
|
18
|
+
- **Keep typing separate from checkbox toggling.** Enter explicitly toggles a checkbox; Space is text in the custom editor and never toggles a checkbox.
|
|
19
|
+
- **Type as soon as the custom row is focused.** There is no extra step to open the editor, and Enter toggles whether the text is included without closing the editor.
|
|
20
|
+
- **Do not lose work when changing your mind.** Unchecking custom text excludes it from the submitted answer but preserves the draft. Navigation and tab changes keep the draft available, and rechecking restores it.
|
|
21
|
+
|
|
22
|
+
These are deliberate preferences for this version, not a claim that the original extension is unsuitable for everyone. Single-select behavior is preserved, and the multi-select changes have dedicated regression tests.
|
|
23
|
+
|
|
24
|
+
### Why a standalone repository?
|
|
25
|
+
|
|
26
|
+
Only the ask-user extension is needed here. Keeping an entire monorepo fork solely to install one customized tool adds unrelated packages, root-level packaging changes, and an upstream-sync workflow that this project does not need.
|
|
27
|
+
|
|
28
|
+
This repository therefore owns its releases independently. The config helper and test fixtures needed by the extension are included locally, while Pi supplies the host APIs. Optional rpiv-i18n compatibility remains available. There is **no dependency on the former fork**, no requirement to track upstream releases, and no upstream PR workflow to maintain.
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
pi install git:github.com/FadhelHaidar/pi-ask-user@v1.0.1
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Starting with v1.0.1, Git releases include prebuilt JavaScript and translations in `dist/`. Installation uses these files directly: no Bun, compiler, development dependencies, or local build is required. Pi and its runtime dependencies are still required.
|
|
37
|
+
|
|
38
|
+
Restart Pi after installing. Remove any other package registering `ask_user_question` to avoid duplicate registration.
|
|
39
|
+
|
|
40
|
+
### GitSync and new machines
|
|
41
|
+
|
|
42
|
+
Sync the Pi package declaration `git:github.com/FadhelHaidar/pi-ask-user@v1.0.1`, then install the declared package on the destination machine using Pi. If you sync a local checkout instead, include the tracked `dist/` directory and install it with `pi install /path/to/pi-ask-user`. Neither route needs `bun run build` on the destination.
|
|
43
|
+
|
|
44
|
+
Pinned tags do not automatically advance. A machine still configured for `@v1.0.0` keeps the old package until you change the declaration to a prebuilt release tag. A tag must be published before the corresponding install command works; source changes alone do not create a release.
|
|
45
|
+
|
|
46
|
+
Each new GitHub release also provides an npm-format `.tgz` containing the same prebuilt runtime. This does not publish the package to npm; the supported Pi installation route remains Git.
|
|
47
|
+
|
|
48
|
+
## Preserved custom behavior
|
|
49
|
+
|
|
50
|
+
- Multi-select answers can include predefined choices **and** custom text together.
|
|
51
|
+
- **Enter** toggles checkboxes; Space remains ordinary text while typing.
|
|
52
|
+
- The `Type something.` row is immediately editable when focused.
|
|
53
|
+
- Unchecking the custom answer keeps its draft; rechecking restores it.
|
|
54
|
+
- Drafts and checkbox state survive tab changes.
|
|
55
|
+
- Single-select behavior remains unchanged.
|
|
56
|
+
|
|
57
|
+
Use arrow keys to move, Tab to switch questions, Enter to choose/toggle, and Esc to cancel. The custom-answer row is appended automatically; models must not author it. Previews are available for single-select questions. Ctrl+] collapses the overlay, Shift+Enter adds a line, and Ctrl+G opens the external editor.
|
|
58
|
+
|
|
59
|
+
## Compatibility and configuration
|
|
60
|
+
|
|
61
|
+
The tool remains named `ask_user_question`. Event names and the localization namespace are preserved for compatibility. Existing configuration remains at `~/.config/rpiv-ask-user-question/config.json`, honoring `XDG_CONFIG_HOME` with legacy fallback.
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{ "collapseKey": "alt+o" }
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The optional `@juicesharp/rpiv-i18n` integration is retained. Without it, the UI uses English. Runtime config helpers are included locally; no rpiv-config dependency is required.
|
|
68
|
+
|
|
69
|
+
## Reference
|
|
70
|
+
|
|
71
|
+
- [Tool schema](docs/tool-schema.md)
|
|
72
|
+
- [Keyboard and layout](docs/keyboard.md)
|
|
73
|
+
- [Configuration](docs/configuration.md)
|
|
74
|
+
- [Hosts and runtime behavior](docs/hosts.md)
|
|
75
|
+
- [Localization](docs/localization.md)
|
|
76
|
+
|
|
77
|
+
## Development
|
|
78
|
+
|
|
79
|
+
Requires Node.js 22+ and Bun.
|
|
80
|
+
|
|
81
|
+
GitHub Actions is the routine builder. Branch pushes and pull requests build before checking freshness, types, and tests, so missing or stale committed `dist/` does not block source changes. After a successful run for `main`, a separate job commits only the tested `dist/` when it changed. It skips an outdated run if `main` has advanced and never force-pushes. Bot commits made with the repository's GitHub token do not trigger another workflow run.
|
|
82
|
+
|
|
83
|
+
Local validation is optional:
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
bun install
|
|
87
|
+
bun run build
|
|
88
|
+
bun run build:check
|
|
89
|
+
bun run typecheck
|
|
90
|
+
bun run test
|
|
91
|
+
pi -e ./index.ts
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`pi -e ./index.ts` loads source for development. Use `pi -e ./dist/index.js` to smoke-test the shipped extension after building. For local builds, use Bun 1.4.2, matching CI: compiler versions can change artifact bytes. You can push source changes without routinely building or committing `dist/` locally; Actions updates the tracked runtime on `main`. Branch checkouts may have stale artifacts until built, and installations should use a completed prebuilt release. There are no install-time build hooks. TypeScript declarations ship with both public entry points. Event declarations are generated from `events.ts`; keep `scripts/index.d.ts.template` synchronized if the root factory signature or exports change.
|
|
95
|
+
|
|
96
|
+
The workflow files must first be pushed to GitHub before Actions can run them. Repository or branch-protection rules may need to permit `github-actions[bot]` commits to `main` using the repository token. This project does not change those settings automatically. If publication is blocked or loses a concurrent push race, inspect the run and rerun the workflow for the latest `main` commit.
|
|
97
|
+
|
|
98
|
+
Tests and fixtures live in this repository, including the multi-select/custom-answer regressions. Published optional localization is installed only for development tests. Pi supplies its host libraries at runtime; the build leaves bare package imports external.
|
|
99
|
+
|
|
100
|
+
### Preparing a release
|
|
101
|
+
|
|
102
|
+
1. Set `package.json` to the new release version and commit the source and manifest changes. Local build/test and `npm pack --dry-run --ignore-scripts` checks are optional validation.
|
|
103
|
+
2. After review, push or merge the changes to `main` and wait for its Tests workflow, including `publish-dist`, to succeed. If `dist/` changed, wait for the automatic build commit to appear on `main`; if it did not change, no extra commit is needed.
|
|
104
|
+
3. Fetch the updated `main` and inspect the commit you will tag. Only then create and push a matching `v<version>` tag (for this change, `v1.0.1`) on that fresh, prebuilt commit. Do not tag the earlier source-only commit or move an existing release tag.
|
|
105
|
+
|
|
106
|
+
Tag pushes do not run the branch build/publish workflow. They validate the checked-in prebuilt runtime rather than silently rebuilding a release.
|
|
107
|
+
|
|
108
|
+
The release workflow rejects tags that do not match `package.json`, checks build freshness, runs tests, packs the prebuilt files, and attaches both a versioned tarball and the stable `pi-ask-user.tgz` asset to each GitHub release. The stable URL `/releases/latest/download/pi-ask-user.tgz` lets Pi update to the newest published release without changing the package declaration. It uses the repository's GitHub token, not npm credentials. A GitHub Actions release run is required to produce the downloadable assets.
|
|
109
|
+
|
|
110
|
+
## License
|
|
111
|
+
|
|
112
|
+
MIT. Derived from juicesharp's rpiv-ask-user-question; original copyright is retained. See [LICENSE](LICENSE) and [NOTICE.md](NOTICE.md).
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// state/external-editor.ts
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
function runEditor(command, file) {
|
|
7
|
+
const [editor, ...args] = command.split(" ");
|
|
8
|
+
if (!editor)
|
|
9
|
+
return Promise.reject(new Error("External editor command is empty"));
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
const child = spawn(editor, [...args, file], {
|
|
12
|
+
stdio: "inherit",
|
|
13
|
+
shell: process.platform === "win32"
|
|
14
|
+
});
|
|
15
|
+
child.once("error", reject);
|
|
16
|
+
child.once("close", (code, signal) => {
|
|
17
|
+
if (code === 0) {
|
|
18
|
+
resolve();
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const reason = signal ? `signal ${signal}` : `exit code ${code ?? "unknown"}`;
|
|
22
|
+
reject(new Error(`External editor exited with ${reason}`));
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
async function editWithExternalEditor(tui, command, value) {
|
|
27
|
+
const tempDir = mkdtempSync(join(tmpdir(), "rpiv-ask-user-question-"));
|
|
28
|
+
const tempFile = join(tempDir, "answer.md");
|
|
29
|
+
let tuiStopped = false;
|
|
30
|
+
try {
|
|
31
|
+
writeFileSync(tempFile, value, "utf8");
|
|
32
|
+
tui.stop();
|
|
33
|
+
tuiStopped = true;
|
|
34
|
+
process.stdout.write(`Launching external editor: ${command}
|
|
35
|
+
Pi will resume when the editor exits.
|
|
36
|
+
`);
|
|
37
|
+
await runEditor(command, tempFile);
|
|
38
|
+
return readFileSync(tempFile, "utf8").replace(/\r?\n$/, "");
|
|
39
|
+
} finally {
|
|
40
|
+
try {
|
|
41
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
42
|
+
} catch {}
|
|
43
|
+
if (tuiStopped) {
|
|
44
|
+
tui.start();
|
|
45
|
+
tui.requestRender(true);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export {
|
|
50
|
+
editWithExternalEditor
|
|
51
|
+
};
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
// lib/config-io.ts
|
|
2
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { dirname, isAbsolute, join } from "node:path";
|
|
5
|
+
import { Type } from "typebox";
|
|
6
|
+
import { Value } from "typebox/value";
|
|
7
|
+
function expandTilde(p) {
|
|
8
|
+
if (p === "~")
|
|
9
|
+
return homedir();
|
|
10
|
+
if (p.startsWith("~/"))
|
|
11
|
+
return join(homedir(), p.slice(2));
|
|
12
|
+
return p;
|
|
13
|
+
}
|
|
14
|
+
function defaultConfigDir() {
|
|
15
|
+
return join(homedir(), ".config");
|
|
16
|
+
}
|
|
17
|
+
function resolveConfigDir() {
|
|
18
|
+
const xdg = readEnvVar("XDG_CONFIG_HOME");
|
|
19
|
+
if (!xdg)
|
|
20
|
+
return defaultConfigDir();
|
|
21
|
+
const expanded = expandTilde(xdg);
|
|
22
|
+
return isAbsolute(expanded) ? expanded : defaultConfigDir();
|
|
23
|
+
}
|
|
24
|
+
function legacyConfigPath(name, file = "config.json") {
|
|
25
|
+
return join(defaultConfigDir(), name, file);
|
|
26
|
+
}
|
|
27
|
+
function configPath(name, file = "config.json") {
|
|
28
|
+
return join(resolveConfigDir(), name, file);
|
|
29
|
+
}
|
|
30
|
+
function loadJsonConfig(path) {
|
|
31
|
+
if (!existsSync(path))
|
|
32
|
+
return {};
|
|
33
|
+
try {
|
|
34
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
35
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
36
|
+
return {};
|
|
37
|
+
return parsed;
|
|
38
|
+
} catch (err) {
|
|
39
|
+
console.warn(`rpiv-config: invalid JSON at ${path}, using default ({}) — ${err.message}`);
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function loadJsonConfigWithLegacyFallback(name, file = "config.json") {
|
|
44
|
+
const xdgPath = configPath(name, file);
|
|
45
|
+
if (existsSync(xdgPath)) {
|
|
46
|
+
return loadJsonConfig(xdgPath);
|
|
47
|
+
}
|
|
48
|
+
return loadJsonConfig(legacyConfigPath(name, file));
|
|
49
|
+
}
|
|
50
|
+
var GuidanceFieldsSchema = Type.Object({
|
|
51
|
+
promptSnippet: Type.Optional(Type.String()),
|
|
52
|
+
promptGuidelines: Type.Optional(Type.Array(Type.String())),
|
|
53
|
+
description: Type.Optional(Type.String())
|
|
54
|
+
}, { additionalProperties: true });
|
|
55
|
+
function validateGuidanceFields(fields) {
|
|
56
|
+
if (!fields || typeof fields !== "object")
|
|
57
|
+
return {};
|
|
58
|
+
const g = fields;
|
|
59
|
+
const result = {};
|
|
60
|
+
if (typeof g.promptSnippet === "string" && g.promptSnippet.length > 0) {
|
|
61
|
+
result.promptSnippet = g.promptSnippet;
|
|
62
|
+
}
|
|
63
|
+
if (Array.isArray(g.promptGuidelines) && g.promptGuidelines.length > 0 && g.promptGuidelines.every((s) => typeof s === "string" && s.length > 0)) {
|
|
64
|
+
result.promptGuidelines = g.promptGuidelines;
|
|
65
|
+
}
|
|
66
|
+
if (typeof g.description === "string" && g.description.length > 0) {
|
|
67
|
+
result.description = g.description;
|
|
68
|
+
}
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
function readEnvVar(key, fallback) {
|
|
72
|
+
return process.env[key]?.trim() || fallback;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// config.ts
|
|
76
|
+
var DEFAULT_COLLAPSE_KEY = "ctrl+]";
|
|
77
|
+
var COLLAPSE_KEY_OFF = "off";
|
|
78
|
+
var SPECIAL_KEYS = new Set([
|
|
79
|
+
"escape",
|
|
80
|
+
"esc",
|
|
81
|
+
"enter",
|
|
82
|
+
"return",
|
|
83
|
+
"tab",
|
|
84
|
+
"space",
|
|
85
|
+
"backspace",
|
|
86
|
+
"delete",
|
|
87
|
+
"insert",
|
|
88
|
+
"clear",
|
|
89
|
+
"home",
|
|
90
|
+
"end",
|
|
91
|
+
"pageup",
|
|
92
|
+
"pagedown",
|
|
93
|
+
"up",
|
|
94
|
+
"down",
|
|
95
|
+
"left",
|
|
96
|
+
"right",
|
|
97
|
+
...Array.from({ length: 12 }, (_, i) => `f${i + 1}`)
|
|
98
|
+
]);
|
|
99
|
+
var MODIFIERS = new Set(["ctrl", "shift", "alt", "super"]);
|
|
100
|
+
function isValidCollapseKeySpec(spec) {
|
|
101
|
+
if (!spec)
|
|
102
|
+
return false;
|
|
103
|
+
if (spec.startsWith("+") || spec.endsWith("+") || spec.includes("++"))
|
|
104
|
+
return false;
|
|
105
|
+
const parts = spec.split("+");
|
|
106
|
+
const base = parts[parts.length - 1] ?? "";
|
|
107
|
+
const modifiers = parts.slice(0, -1);
|
|
108
|
+
if (modifiers.length !== new Set(modifiers).size)
|
|
109
|
+
return false;
|
|
110
|
+
if (!modifiers.every((m) => MODIFIERS.has(m)))
|
|
111
|
+
return false;
|
|
112
|
+
return base.length === 1 ? /[a-z0-9_\-!@#$%^&*()|~`'":;,./<>?[\]{}=\\]/.test(base) : SPECIAL_KEYS.has(base);
|
|
113
|
+
}
|
|
114
|
+
function resolveCollapseKey(config) {
|
|
115
|
+
const raw = config.collapseKey?.trim().toLowerCase();
|
|
116
|
+
if (raw === undefined || raw === "")
|
|
117
|
+
return DEFAULT_COLLAPSE_KEY;
|
|
118
|
+
if (raw === COLLAPSE_KEY_OFF)
|
|
119
|
+
return COLLAPSE_KEY_OFF;
|
|
120
|
+
return isValidCollapseKeySpec(raw) ? raw : DEFAULT_COLLAPSE_KEY;
|
|
121
|
+
}
|
|
122
|
+
var COMPOUND_KEY_DISPLAY = {
|
|
123
|
+
pageup: "PageUp",
|
|
124
|
+
pagedown: "PageDown"
|
|
125
|
+
};
|
|
126
|
+
function formatKeySpecForDisplay(spec) {
|
|
127
|
+
return spec.split("+").map((part) => COMPOUND_KEY_DISPLAY[part] ?? (part.length <= 1 ? part.toUpperCase() : part.charAt(0).toUpperCase() + part.slice(1))).join("+");
|
|
128
|
+
}
|
|
129
|
+
function loadConfig() {
|
|
130
|
+
return loadJsonConfigWithLegacyFallback("rpiv-ask-user-question");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// state/row-intent.ts
|
|
134
|
+
var SENTINEL_KINDS = ["other", "next"];
|
|
135
|
+
var ROW_INTENT_META = {
|
|
136
|
+
option: {
|
|
137
|
+
label: "",
|
|
138
|
+
reserved: false,
|
|
139
|
+
livesInMainList: true,
|
|
140
|
+
numbered: true,
|
|
141
|
+
activatesInputMode: false,
|
|
142
|
+
blocksMultiToggle: false,
|
|
143
|
+
autoSubmitsInMulti: false,
|
|
144
|
+
autoAppendOnSingleSelect: false,
|
|
145
|
+
autoAppendOnMultiSelect: false
|
|
146
|
+
},
|
|
147
|
+
other: {
|
|
148
|
+
label: "Type something.",
|
|
149
|
+
reserved: true,
|
|
150
|
+
livesInMainList: true,
|
|
151
|
+
numbered: true,
|
|
152
|
+
activatesInputMode: true,
|
|
153
|
+
blocksMultiToggle: false,
|
|
154
|
+
autoSubmitsInMulti: false,
|
|
155
|
+
autoAppendOnSingleSelect: true,
|
|
156
|
+
autoAppendOnMultiSelect: true
|
|
157
|
+
},
|
|
158
|
+
next: {
|
|
159
|
+
label: "Next",
|
|
160
|
+
reserved: true,
|
|
161
|
+
livesInMainList: true,
|
|
162
|
+
numbered: false,
|
|
163
|
+
activatesInputMode: false,
|
|
164
|
+
blocksMultiToggle: true,
|
|
165
|
+
autoSubmitsInMulti: true,
|
|
166
|
+
autoAppendOnSingleSelect: false,
|
|
167
|
+
autoAppendOnMultiSelect: true
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
var LABELS_BY_KIND = {
|
|
171
|
+
other: ROW_INTENT_META.other.label,
|
|
172
|
+
next: ROW_INTENT_META.next.label
|
|
173
|
+
};
|
|
174
|
+
var RESERVED_LABEL_SET = new Set([
|
|
175
|
+
"Other",
|
|
176
|
+
...SENTINEL_KINDS.filter((k) => ROW_INTENT_META[k].reserved).map((k) => ROW_INTENT_META[k].label)
|
|
177
|
+
]);
|
|
178
|
+
function sentinelsToAppend(question) {
|
|
179
|
+
const out = [];
|
|
180
|
+
for (const k of SENTINEL_KINDS) {
|
|
181
|
+
const meta = ROW_INTENT_META[k];
|
|
182
|
+
if (!meta.livesInMainList)
|
|
183
|
+
continue;
|
|
184
|
+
if (question.multiSelect === true) {
|
|
185
|
+
if (meta.autoAppendOnMultiSelect)
|
|
186
|
+
out.push(k);
|
|
187
|
+
} else {
|
|
188
|
+
if (meta.autoAppendOnSingleSelect)
|
|
189
|
+
out.push(k);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return out;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// state/i18n-bridge.ts
|
|
196
|
+
var I18N_NAMESPACE = "@juicesharp/rpiv-ask-user-question";
|
|
197
|
+
var scopeImpl;
|
|
198
|
+
try {
|
|
199
|
+
const sdk = await import("@juicesharp/rpiv-i18n");
|
|
200
|
+
scopeImpl = sdk.scope(I18N_NAMESPACE);
|
|
201
|
+
} catch {
|
|
202
|
+
scopeImpl = (_key, fallback) => fallback;
|
|
203
|
+
}
|
|
204
|
+
var t = scopeImpl;
|
|
205
|
+
function displayLabel(kind) {
|
|
206
|
+
return t(`sentinel.${kind}`, ROW_INTENT_META[kind].label);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// tool/format-answer.ts
|
|
210
|
+
var NO_INPUT_PLACEHOLDER = "(no input)";
|
|
211
|
+
function formatAnswerScalar(a, _variant) {
|
|
212
|
+
switch (a.kind) {
|
|
213
|
+
case "multi": {
|
|
214
|
+
const parts = [...a.selected ?? []];
|
|
215
|
+
if (a.answer && a.answer.length > 0)
|
|
216
|
+
parts.push(a.answer);
|
|
217
|
+
return parts.length > 0 ? parts.join(", ") : NO_INPUT_PLACEHOLDER;
|
|
218
|
+
}
|
|
219
|
+
case "custom":
|
|
220
|
+
return a.answer && a.answer.length > 0 ? a.answer : NO_INPUT_PLACEHOLDER;
|
|
221
|
+
case "option":
|
|
222
|
+
return a.answer ?? NO_INPUT_PLACEHOLDER;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export { validateGuidanceFields, DEFAULT_COLLAPSE_KEY, COLLAPSE_KEY_OFF, resolveCollapseKey, formatKeySpecForDisplay, loadConfig, ROW_INTENT_META, sentinelsToAppend, I18N_NAMESPACE, t, displayLabel, formatAnswerScalar };
|