gentle-pi 3.5.0 → 3.5.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/README.md +43 -41
- package/bin/gentle-shell.mjs +183 -8
- package/docs/readme-reference.md +63 -12
- package/lib/gentle-shell-launcher.ts +421 -36
- package/package.json +1 -1
- package/runtime/gentle-shell-launcher.mjs +420 -35
- package/tests/agents-rpc-publisher.test.ts +66 -0
- package/tests/gentle-agents.test.ts +46 -0
- package/tests/gentle-shell-bin.test.ts +651 -3
- package/tests/gentle-shell-launcher.test.ts +659 -10
- package/tests/package-manifest.test.ts +2 -2
package/README.md
CHANGED
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
<a href="https://www.npmjs.com/package/gentle-pi"><img src="https://img.shields.io/npm/v/gentle-pi?style=for-the-badge&labelColor=1A1218&color=F095C8" alt="npm"></a>
|
|
13
13
|
<a href="https://pi.dev/packages/gentle-pi"><img src="https://img.shields.io/badge/Pi-native-F095C8?style=for-the-badge&labelColor=1A1218" alt="Pi-native package"></a>
|
|
14
14
|
<a href="LICENSE"><img src="https://img.shields.io/npm/l/gentle-pi?style=for-the-badge&labelColor=1A1218&color=F095C8" alt="MIT license"></a>
|
|
15
|
-
<a href="https://github.com/Gentleman-Programming/gentle-
|
|
16
|
-
<a href="https://github.com/Gentleman-Programming/gentle-
|
|
15
|
+
<a href="https://github.com/Gentleman-Programming/gentle-shell/stargazers"><img src="https://img.shields.io/github/stars/Gentleman-Programming/gentle-shell?style=for-the-badge&labelColor=1A1218&color=F095C8" alt="GitHub stars"></a>
|
|
16
|
+
<a href="https://github.com/Gentleman-Programming/gentle-shell"><img src="https://img.shields.io/github/last-commit/Gentleman-Programming/gentle-shell?style=for-the-badge&labelColor=1A1218&color=D7A0B8" alt="Last commit"></a>
|
|
17
17
|
</p>
|
|
18
18
|
|
|
19
19
|
<p align="center">
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
<p align="center"><strong>BUILT FOR PI</strong> · Coding-agent workspace · Focused agents · ODD</p>
|
|
38
38
|
|
|
39
39
|
<p align="center">
|
|
40
|
-
<a href="https://github.com/Gentleman-Programming/gentle-
|
|
40
|
+
<a href="https://github.com/Gentleman-Programming/gentle-shell/stargazers"><strong>★ Star gentle-shell on GitHub</strong></a>
|
|
41
41
|
</p>
|
|
42
42
|
|
|
43
43
|
<div align="center">
|
|
@@ -185,13 +185,13 @@ Extension commands are only useful if you can find them. `alt+k` opens a curated
|
|
|
185
185
|
|
|
186
186
|
---
|
|
187
187
|
|
|
188
|
-
### What's new in
|
|
188
|
+
### What's new in v3.5
|
|
189
189
|
|
|
190
|
-
The [
|
|
190
|
+
The [v3.5.1 release](https://github.com/Gentleman-Programming/gentle-shell/releases/tag/v3.5.1) makes Gentle Shell runnable on its own:
|
|
191
191
|
|
|
192
|
-
- **
|
|
193
|
-
- **
|
|
194
|
-
- **
|
|
192
|
+
- **Standalone launcher:** `npm i -g gentle-pi` installs `gentle-shell`, which opens Pi with the Gentle Shell package loaded from its own home (`~/.gentle-shell/agent`) or, with `--link`, from your existing `~/.pi/agent`; `gentle-shell install npm:<pkg>` and the other pi subcommands run against the selected home. A bundled or `PATH` pi is used, never a modified one.
|
|
193
|
+
- **Link mode take-over:** when `~/.pi/agent` already declares gentle-pi as a path package, the launcher takes over extension loading (`--no-extensions` plus explicit `-e` for every other declared package and loose extension) so tools never register twice.
|
|
194
|
+
- **Interactive RPC hosts:** with `GENTLE_SHELL_INTERACTIVE_HOST=1` and `--mode rpc`, ask-user tools use pi's RPC dialogs and gentle-agents publishes live subagent activity for the desktop app. See the [reference](docs/readme-reference.md#interactive-rpc-hosts).
|
|
195
195
|
|
|
196
196
|
---
|
|
197
197
|
|
|
@@ -203,13 +203,38 @@ The [v2.6.0 release](https://github.com/Gentleman-Programming/gentle-pi/releases
|
|
|
203
203
|
|
|
204
204
|
## Get started
|
|
205
205
|
|
|
206
|
-
Install the stable release, restart Pi, then synchronize the installed assets.
|
|
207
|
-
|
|
208
206
|
> **Naming transition:** The product is called `gentle-shell`; the current npm package and repository remain `gentle-pi` until migration.
|
|
209
207
|
|
|
208
|
+
### Path A: standalone `gentle-shell` (recommended, no pi changes)
|
|
209
|
+
|
|
210
|
+
`gentle-shell` opens Pi with the Gentle Shell package loaded, without installing it into your pi agent or editing its `settings.json`.
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
npm i -g gentle-pi
|
|
214
|
+
|
|
215
|
+
# Own home, never touches your pi install
|
|
216
|
+
gentle-shell
|
|
217
|
+
|
|
218
|
+
# Reuse your pi sign-ins, models and chats instead
|
|
219
|
+
gentle-shell --link
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
`gentle-shell` alone starts in its own home, `~/.gentle-shell/agent`. `gentle-shell --link` reuses `~/.pi/agent` as-is.
|
|
223
|
+
|
|
210
224
|
```bash
|
|
211
|
-
#
|
|
212
|
-
|
|
225
|
+
# Make --link the default
|
|
226
|
+
gentle-shell home link
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Every other argument is forwarded to pi unchanged, for example `gentle-shell --mode rpc` or `gentle-shell -p "..."`. Full flags, env vars, and modes: **[launcher reference](docs/readme-reference.md#gentle-shell-launcher)**.
|
|
230
|
+
|
|
231
|
+
### Path B: inside an existing pi
|
|
232
|
+
|
|
233
|
+
Install the stable release into an existing pi agent, restart Pi, then synchronize the installed assets.
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
# Published stable release: v3.5.1
|
|
237
|
+
pi install npm:gentle-pi@3.5.1
|
|
213
238
|
|
|
214
239
|
# Restart Pi, then run:
|
|
215
240
|
gentle-ai sync
|
|
@@ -218,7 +243,7 @@ gentle-ai sync
|
|
|
218
243
|
pi
|
|
219
244
|
```
|
|
220
245
|
|
|
221
|
-
See the [
|
|
246
|
+
See the [v3.5.1 release notes](https://github.com/Gentleman-Programming/gentle-shell/releases/tag/v3.5.1) for version-specific changes.
|
|
222
247
|
|
|
223
248
|
```text
|
|
224
249
|
/gentle:status
|
|
@@ -233,29 +258,6 @@ See the [v2.6.0 release notes](https://github.com/Gentleman-Programming/gentle-p
|
|
|
233
258
|
|
|
234
259
|
For prerequisites, source-checkout instructions, full install behavior, and release policy, use the **[installation reference](docs/readme-reference.md#install)**. For everyday work, describe the outcome and follow [ODD](#odd--the-everyday-workflow).
|
|
235
260
|
|
|
236
|
-
### Without touching your pi
|
|
237
|
-
|
|
238
|
-
`gentle-shell` opens Pi with the Gentle Shell package loaded, without installing it into your pi agent or editing its `settings.json`.
|
|
239
|
-
|
|
240
|
-
```bash
|
|
241
|
-
npm i -g gentle-pi
|
|
242
|
-
|
|
243
|
-
# Own home, never touches your pi install
|
|
244
|
-
gentle-shell
|
|
245
|
-
|
|
246
|
-
# Reuse your pi sign-ins, models and chats instead
|
|
247
|
-
gentle-shell --link
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
`gentle-shell` alone starts in its own home, `~/.gentle-shell/agent`. `gentle-shell --link` reuses `~/.pi/agent` as-is.
|
|
251
|
-
|
|
252
|
-
```bash
|
|
253
|
-
# Make --link the default
|
|
254
|
-
gentle-shell home link
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
Every other argument is forwarded to pi unchanged, for example `gentle-shell --mode rpc` or `gentle-shell -p "..."`. Full flags, env vars, and modes: **[launcher reference](docs/readme-reference.md#gentle-shell-launcher)**.
|
|
258
|
-
|
|
259
261
|
<p align="right"><a href="#top">Back to top ↑</a></p>
|
|
260
262
|
|
|
261
263
|
<p align="center">
|
|
@@ -287,17 +289,17 @@ Start with the product-facing destination, then move into the operational refere
|
|
|
287
289
|
This project is built in public. Bring a real workflow, a sharp question, a bug report, or a small improvement that makes the next person’s work clearer.
|
|
288
290
|
|
|
289
291
|
<p align="center">
|
|
290
|
-
<a href="https://github.com/Gentleman-Programming/gentle-
|
|
291
|
-
<a href="https://github.com/Gentleman-Programming/gentle-
|
|
292
|
+
<a href="https://github.com/Gentleman-Programming/gentle-shell/issues"><img src="https://img.shields.io/badge/Issues-join%20the%20conversation-F095C8?style=for-the-badge&labelColor=1A1218" alt="GitHub issues"></a>
|
|
293
|
+
<a href="https://github.com/Gentleman-Programming/gentle-shell/graphs/contributors"><img src="https://img.shields.io/badge/Contributors-thank%20you-D7A0B8?style=for-the-badge&labelColor=1A1218" alt="Contributors"></a>
|
|
292
294
|
<a href="https://discord.com/invite/gentleman-programming-769863833996754944"><img src="https://img.shields.io/badge/Discord-Gentleman%20Programming-F095C8?style=for-the-badge&labelColor=1A1218" alt="Gentleman Programming Discord"></a>
|
|
293
295
|
</p>
|
|
294
296
|
|
|
295
297
|
<p align="center">
|
|
296
|
-
<a href="https://github.com/Gentleman-Programming/gentle-
|
|
298
|
+
<a href="https://github.com/Gentleman-Programming/gentle-shell/graphs/contributors"><img src="https://contrib.rocks/image?repo=Gentleman-Programming/gentle-shell" alt="gentle-shell contributors"></a>
|
|
297
299
|
</p>
|
|
298
300
|
|
|
299
|
-
- Open an [issue](https://github.com/Gentleman-Programming/gentle-
|
|
300
|
-
- See the people shaping the project in the [contributors graph](https://github.com/Gentleman-Programming/gentle-
|
|
301
|
+
- Open an [issue](https://github.com/Gentleman-Programming/gentle-shell/issues) with the context needed to reproduce or understand the idea.
|
|
302
|
+
- See the people shaping the project in the [contributors graph](https://github.com/Gentleman-Programming/gentle-shell/graphs/contributors).
|
|
301
303
|
- Follow [Gentleman Programming](https://github.com/Gentleman-Programming) for the wider ecosystem.
|
|
302
304
|
|
|
303
305
|
<p align="right"><a href="#top">Back to top ↑</a></p>
|
package/bin/gentle-shell.mjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// resolution, pi resolution order, the version gate, and the pi invocation —
|
|
5
5
|
// lives in that pure, unit-tested module; this file only wires it to the real
|
|
6
6
|
// process, filesystem, and child process.
|
|
7
|
-
import { accessSync, constants as fsConstants, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { accessSync, constants as fsConstants, existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, statSync, writeFileSync } from "node:fs";
|
|
8
8
|
import { createRequire } from "node:module";
|
|
9
9
|
import { constants as osConstants, homedir } from "node:os";
|
|
10
10
|
import { delimiter, dirname, join, resolve as resolvePath } from "node:path";
|
|
@@ -13,16 +13,19 @@ import { fileURLToPath } from "node:url";
|
|
|
13
13
|
import {
|
|
14
14
|
buildPiInvocation,
|
|
15
15
|
checkPiVersion,
|
|
16
|
+
decideTakeOver,
|
|
16
17
|
describeVersion,
|
|
18
|
+
discoverLooseExtensionEntries,
|
|
19
|
+
findGentlePiDeclaration,
|
|
17
20
|
helpText,
|
|
18
21
|
launcherConfigPath,
|
|
19
22
|
missingPiMessage,
|
|
23
|
+
otherPackageInjections,
|
|
20
24
|
parseLauncherArgs,
|
|
21
25
|
parseLauncherConfig,
|
|
22
26
|
planSpawn,
|
|
23
27
|
resolveHome,
|
|
24
28
|
resolvePiRuntime,
|
|
25
|
-
settingsDeclareGentlePi,
|
|
26
29
|
} from "../runtime/gentle-shell-launcher.mjs";
|
|
27
30
|
import { installIsolatedTuiModeSetting } from "../scripts/install-tui-mode-setting.mjs";
|
|
28
31
|
|
|
@@ -83,7 +86,119 @@ function ownPackageVersion() {
|
|
|
83
86
|
}
|
|
84
87
|
|
|
85
88
|
function emptyArgs() {
|
|
86
|
-
return {
|
|
89
|
+
return {
|
|
90
|
+
link: false,
|
|
91
|
+
isolated: false,
|
|
92
|
+
home: undefined,
|
|
93
|
+
packageRoot: undefined,
|
|
94
|
+
help: false,
|
|
95
|
+
version: false,
|
|
96
|
+
command: undefined,
|
|
97
|
+
commandArgs: [],
|
|
98
|
+
passthrough: [],
|
|
99
|
+
piSubcommand: undefined,
|
|
100
|
+
error: undefined,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// package.json "name" reader injected into findGentlePiDeclaration: a
|
|
105
|
+
// missing or unreadable package.json, or a non-string "name", is never an
|
|
106
|
+
// error here — it just means that path package is not gentle-pi.
|
|
107
|
+
function readPackageName(dir) {
|
|
108
|
+
try {
|
|
109
|
+
const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
|
|
110
|
+
return typeof pkg.name === "string" ? pkg.name : undefined;
|
|
111
|
+
} catch {
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Best-effort realpath: a directory that does not exist (yet, or ever)
|
|
117
|
+
// cannot be realpath'd, so the take-over decision falls back to comparing
|
|
118
|
+
// the raw path instead of failing.
|
|
119
|
+
function safeRealpath(path) {
|
|
120
|
+
try {
|
|
121
|
+
return realpathSync(path);
|
|
122
|
+
} catch {
|
|
123
|
+
return path;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Used to filter the loose extension dirs a take-over re-injects: a missing
|
|
128
|
+
// path, or one that is not a directory (for example a stray file named
|
|
129
|
+
// "extensions"), is silently excluded rather than passed to pi as -e.
|
|
130
|
+
function isDirectory(path) {
|
|
131
|
+
try {
|
|
132
|
+
return statSync(path).isDirectory();
|
|
133
|
+
} catch {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Real-fs adapter for discoverLooseExtensionEntries (lib/gentle-shell-launcher.ts):
|
|
139
|
+
// statSync-based isFile/isDirectory (not readdirSync's Dirent, which uses
|
|
140
|
+
// lstat and so would treat a symlinked file or directory as neither) so a
|
|
141
|
+
// symlinked loose extension resolves the same way pi's own fs.existsSync-based
|
|
142
|
+
// checks would.
|
|
143
|
+
const looseExtensionFs = {
|
|
144
|
+
readdir(dir) {
|
|
145
|
+
let names;
|
|
146
|
+
try {
|
|
147
|
+
names = readdirSync(dir);
|
|
148
|
+
} catch (error) {
|
|
149
|
+
// resolveLooseExtensionEntries only calls this once isDirectory(dir)
|
|
150
|
+
// has already confirmed the directory exists, so a failure here (for
|
|
151
|
+
// example EACCES) is a real read failure, not a missing directory.
|
|
152
|
+
// Warn instead of silently dropping every loose extension it would
|
|
153
|
+
// have contributed (R4-loose-extension-enumeration-fails-silently).
|
|
154
|
+
process.stderr.write(`gentle-shell: could not read loose extension directory ${dir}: ${error.message} (skipping)\n`);
|
|
155
|
+
return [];
|
|
156
|
+
}
|
|
157
|
+
return names.map((name) => {
|
|
158
|
+
const entryPath = join(dir, name);
|
|
159
|
+
try {
|
|
160
|
+
const entryStat = statSync(entryPath);
|
|
161
|
+
return { name, isFile: entryStat.isFile(), isDirectory: entryStat.isDirectory() };
|
|
162
|
+
} catch {
|
|
163
|
+
return { name, isFile: false, isDirectory: false };
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
},
|
|
167
|
+
exists: existsSync,
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// A loose extensions directory that is itself a self-contained extension —
|
|
171
|
+
// a package.json declaring a non-empty "pi.extensions" manifest — is passed
|
|
172
|
+
// through as a single -e <dir> instead of being broken into per-file
|
|
173
|
+
// entries: pi's own module loader (jiti) resolves that case directly,
|
|
174
|
+
// exactly as it would for any other explicitly configured package path. A
|
|
175
|
+
// root-level index.ts/index.js is deliberately NOT treated as that same
|
|
176
|
+
// marker: pi's own discovery loads it as just another loose file, so
|
|
177
|
+
// collapsing the whole directory on its presence silently dropped sibling
|
|
178
|
+
// loose files like extra.ts (R4-loose-index-collapses-sibling-extensions).
|
|
179
|
+
function readPiManifestExtensions(dir) {
|
|
180
|
+
try {
|
|
181
|
+
const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
|
|
182
|
+
return Array.isArray(pkg?.pi?.extensions) ? pkg.pi.extensions : undefined;
|
|
183
|
+
} catch {
|
|
184
|
+
return undefined;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function looseDirHasOwnEntryPoint(dir) {
|
|
189
|
+
const manifestExtensions = readPiManifestExtensions(dir);
|
|
190
|
+
return manifestExtensions !== undefined && manifestExtensions.length > 0;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Resolves one candidate loose-extensions directory (<agentDir>/extensions or
|
|
194
|
+
// <cwd>/.pi/extensions) into the -e entries a take-over must re-inject: the
|
|
195
|
+
// directory itself when it is a self-contained extension, otherwise every
|
|
196
|
+
// loose file discoverLooseExtensionEntries finds inside it. A missing or
|
|
197
|
+
// non-directory candidate resolves to no entries.
|
|
198
|
+
function resolveLooseExtensionEntries(dir) {
|
|
199
|
+
if (!isDirectory(dir)) return [];
|
|
200
|
+
if (looseDirHasOwnEntryPoint(dir)) return [dir];
|
|
201
|
+
return discoverLooseExtensionEntries(dir, looseExtensionFs);
|
|
87
202
|
}
|
|
88
203
|
|
|
89
204
|
function loadConfig() {
|
|
@@ -165,17 +280,77 @@ async function main() {
|
|
|
165
280
|
process.stderr.write(`gentle-shell: using a separate home at ${home.dir}. Run 'gentle-shell --link' to reuse your pi sign-ins and chats.\n`);
|
|
166
281
|
}
|
|
167
282
|
|
|
168
|
-
|
|
169
|
-
|
|
283
|
+
const packageRootExplicit = args.packageRoot !== undefined;
|
|
284
|
+
const effectivePackageRoot = packageRootExplicit ? resolvePath(args.packageRoot) : packageRoot;
|
|
285
|
+
// R4-forced-package-root-unvalidated / R3-005: an unvalidated --package-root
|
|
286
|
+
// forces a take-over (dropping normal extension discovery via
|
|
287
|
+
// --no-extensions) and then hands pi -e/--theme/--skill/--prompt-template
|
|
288
|
+
// flags pointing at directories that do not exist, turning an operator typo
|
|
289
|
+
// into an obscure pi loader failure instead of a clear launcher error.
|
|
290
|
+
if (packageRootExplicit && !isDirectory(effectivePackageRoot)) {
|
|
291
|
+
fail(`--package-root ${args.packageRoot} does not exist or is not a directory.`, 2);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
let declaration;
|
|
295
|
+
let takeOver = false;
|
|
296
|
+
let otherPackagePaths = [];
|
|
297
|
+
let looseExtensionEntries = [];
|
|
298
|
+
|
|
299
|
+
// Only --link can read another gentle-pi declaration out of a real
|
|
300
|
+
// settings.json; isolated and --home homes never declare one, so they
|
|
301
|
+
// always get the plain injection (declaration stays undefined) unless
|
|
302
|
+
// --package-root itself forces a take-over below. A pi subcommand skips
|
|
303
|
+
// this whole block: buildPiInvocation ignores takeOver/declaration once
|
|
304
|
+
// piSubcommand is set, and running the take-over/loose-dir discovery
|
|
305
|
+
// anyway would still print a misleading "taking over gentle-pi..."
|
|
306
|
+
// message (and otherPackageInjections warnings) for a plain
|
|
307
|
+
// `gentle-shell install npm:x` that never actually takes anything over.
|
|
308
|
+
if (home.mode === "link" && args.piSubcommand === undefined) {
|
|
170
309
|
const settingsText = readJsonIfExists(join(home.dir, "settings.json"));
|
|
171
|
-
|
|
310
|
+
declaration = findGentlePiDeclaration(settingsText, { agentDir: home.dir, readPackageName });
|
|
311
|
+
const realEffectivePackageRoot = safeRealpath(effectivePackageRoot);
|
|
312
|
+
const realDeclaredDir = declaration?.kind === "path" ? safeRealpath(declaration.dir) : undefined;
|
|
313
|
+
takeOver = decideTakeOver({
|
|
314
|
+
declaration,
|
|
315
|
+
realPackageRoot: realEffectivePackageRoot,
|
|
316
|
+
realDeclaredDir,
|
|
317
|
+
packageRootExplicit,
|
|
318
|
+
});
|
|
319
|
+
if (takeOver) {
|
|
320
|
+
const skip = declaration ?? { kind: "path", dir: realEffectivePackageRoot };
|
|
321
|
+
const injections = otherPackageInjections({ settingsText, agentDir: home.dir, skip, isDirectory, realpath: safeRealpath });
|
|
322
|
+
otherPackagePaths = injections.paths;
|
|
323
|
+
for (const warning of injections.warnings) process.stderr.write(`${warning}\n`);
|
|
324
|
+
// --no-extensions drops pi's normal settings-driven extension
|
|
325
|
+
// discovery, which also covers loose (non-package) extensions
|
|
326
|
+
// under <agentDir>/extensions and the project-local
|
|
327
|
+
// <cwd>/.pi/extensions. Re-injecting either directory wholesale
|
|
328
|
+
// as `-e <dir>` does not work for a directory of loose files: pi's
|
|
329
|
+
// -e flag hands the path straight to its module loader with no
|
|
330
|
+
// directory-discovery pass, so a bare directory of loose files
|
|
331
|
+
// fails with "Cannot find module ...". Resolve each candidate
|
|
332
|
+
// into its actual loose file entries (or pass it through
|
|
333
|
+
// unchanged when it is itself a self-contained extension) so a
|
|
334
|
+
// take-over does not silently stop loading them.
|
|
335
|
+
looseExtensionEntries = [join(home.dir, "extensions"), join(process.cwd(), ".pi", "extensions")].flatMap(resolveLooseExtensionEntries);
|
|
336
|
+
const declaredFrom = declaration === undefined ? "the requested package root" : declaration.kind === "npm" ? "npm:gentle-pi" : declaration.dir;
|
|
337
|
+
process.stderr.write(
|
|
338
|
+
`gentle-shell: taking over gentle-pi from ${declaredFrom} for this run (settings unchanged; its skills, prompts, and themes still load alongside this launcher's).\n`,
|
|
339
|
+
);
|
|
340
|
+
}
|
|
172
341
|
}
|
|
342
|
+
// Isolated and --home homes have no declaration to take over: declaration
|
|
343
|
+
// stays undefined and buildPiInvocation injects effectivePackageRoot the
|
|
344
|
+
// same way it always has, --package-root included.
|
|
173
345
|
|
|
174
346
|
const invocation = buildPiInvocation({
|
|
175
347
|
runtime,
|
|
176
348
|
home,
|
|
177
|
-
packageRoot,
|
|
178
|
-
|
|
349
|
+
packageRoot: effectivePackageRoot,
|
|
350
|
+
declaration,
|
|
351
|
+
takeOver,
|
|
352
|
+
otherPackagePaths,
|
|
353
|
+
looseExtensionEntries,
|
|
179
354
|
passthrough: args.passthrough,
|
|
180
355
|
piSubcommand: args.piSubcommand,
|
|
181
356
|
baseEnv: process.env,
|
package/docs/readme-reference.md
CHANGED
|
@@ -135,15 +135,33 @@ Callers own keyboard policy, theme state, and business actions.
|
|
|
135
135
|
|
|
136
136
|
## Install
|
|
137
137
|
|
|
138
|
+
Two paths reach the same package. Path A stays standalone; Path B installs into an existing pi.
|
|
139
|
+
|
|
140
|
+
### Path A: standalone `gentle-shell` (recommended, no pi changes)
|
|
141
|
+
|
|
138
142
|
```bash
|
|
139
|
-
|
|
143
|
+
npm i -g gentle-pi
|
|
144
|
+
|
|
145
|
+
# Own home, never touches your pi install
|
|
146
|
+
gentle-shell
|
|
147
|
+
|
|
148
|
+
# Reuse your pi sign-ins, models and chats instead
|
|
149
|
+
gentle-shell --link
|
|
140
150
|
```
|
|
141
151
|
|
|
142
|
-
|
|
152
|
+
`gentle-shell` alone starts in its own home, `~/.gentle-shell/agent`. `gentle-shell --link` reuses `~/.pi/agent` as-is. Run `gentle-shell home link` to make `--link` the default. Full flags, env vars, and modes: [gentle-shell launcher](#gentle-shell-launcher).
|
|
153
|
+
|
|
154
|
+
### Path B: inside an existing pi
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
pi install npm:gentle-pi@3.5.1
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
The stable release is [`v3.5.1`](https://github.com/Gentleman-Programming/gentle-shell/releases/tag/v3.5.1). Restart Pi after installation, then run `gentle-ai sync`. That published release pairs with Gentle AI `v2.8.0` and provider contract `1.2.0`; capabilities `v2.5` are retained. The command above installs that exact published version.
|
|
143
161
|
|
|
144
162
|
### Source checkout
|
|
145
163
|
|
|
146
|
-
This checkout prepares `gentle-pi` `3.
|
|
164
|
+
This checkout prepares `gentle-pi` `3.5.1`; it is source state, not a published release. Its package-local native runtime pin is Gentle AI `v3.5.0`, distinct from the published `v3.5.1` pairing.
|
|
147
165
|
|
|
148
166
|
The native SDD status consumer accepts both the pinned producer's legacy
|
|
149
167
|
`apply`/`verify`/`remediate`/`archive` instruction record and the classical
|
|
@@ -161,13 +179,13 @@ Classical direct-archive behavior is compatibility-tested with an identified
|
|
|
161
179
|
upstream development build, not presented as a published fix or version bump.
|
|
162
180
|
The complete classical flow awaits a compatible published native version; this
|
|
163
181
|
change does not bump the pin. Ordinary attempt governance and research/planning simplification remain separate
|
|
164
|
-
work under [SDD parity #1051](https://github.com/Gentleman-Programming/gentle-
|
|
182
|
+
work under [SDD parity #1051](https://github.com/Gentleman-Programming/gentle-shell/issues/1051).
|
|
165
183
|
|
|
166
184
|
### Pi compatibility
|
|
167
185
|
|
|
168
186
|
The current package requires Pi 0.85.1 or newer (development tests pin 0.85.1). Use the latest Pi release; gentle-pi does not update your installed Pi automatically. Children, including any `GENTLE_PI_AGENTS_PI` override, must emit `agent_settled`: `agent_end` records a run's output but is not completion because retries or queued continuations may follow.
|
|
169
187
|
|
|
170
|
-
The [`v2.6.0` release](https://github.com/Gentleman-Programming/gentle-
|
|
188
|
+
The [`v2.6.0` release](https://github.com/Gentleman-Programming/gentle-shell/releases/tag/v2.6.0) added persistent registered worktrees and grouped `/gentle:changes` views; fuller workspace interaction details are in the [Gentle Shell reference](gentle-shell.md). It also adds named atomic `/gentle:profiles`, parent-confirmed native SDD preflight transport, native review intended-untracked selection and provider continuations, and opt-in custom ask responses. Pi recognizes its global Git-managed package path; subsystems install with explicit recovery guidance when npm lifecycle work was skipped. Windows keeps child consoles hidden and fixes ownership mode; Gentle Todo keeps the next pending task visible when collapsed.
|
|
171
189
|
|
|
172
190
|
### Install-time fullscreen
|
|
173
191
|
|
|
@@ -179,18 +197,30 @@ Malformed/nonobject JSON, symlink/nonregular settings, unsafe paths, or a busy s
|
|
|
179
197
|
|
|
180
198
|
### RDD history and opt-in
|
|
181
199
|
|
|
182
|
-
Native RDD was introduced in `gentle-pi` `v0.15.0` on 2026-07-10 with bounded review transactions. The current stable release, [`
|
|
200
|
+
Native RDD was introduced in `gentle-pi` `v0.15.0` on 2026-07-10 with bounded review transactions. The current stable release, [`v3.5.1`](https://github.com/Gentleman-Programming/gentle-shell/releases/tag/v3.5.1), includes native RDD:
|
|
183
201
|
|
|
184
202
|
```bash
|
|
185
203
|
# Stable release
|
|
186
|
-
pi install npm:gentle-pi@
|
|
204
|
+
pi install npm:gentle-pi@3.5.1
|
|
187
205
|
```
|
|
188
206
|
|
|
189
207
|
RDD remains opt-in. Enable it only through an explicit user decision with `/gentle:review-mode enable`; `status` lets you inspect the mode without changing it.
|
|
190
208
|
|
|
191
209
|
The source checkout's RDD integration installs Gentle AI only into its private `.gentle-ai/` directory. Darwin and Linux use pinned release assets with asset and executable SHA-256 verification (signed archives for source pin `v3.5.0`; raw prerelease binaries only under a prerelease pin). Windows x64 and arm64 build the exact `v3.5.0` source tag with a local Go 1.25.10+ toolchain, a sealed Go environment, `GOTOOLCHAIN=local`, and `GOSUMDB=sum.golang.org`; it does not download Go automatically. Windows provenance is Go-toolchain plus SumDB evidence and postinstall tamper detection, **not** Authenticode or protection against a malicious joint binary-and-manifest replacement. Package-private locks coordinate cooperative concurrent or crashed installers; their tombstones fail closed. A malicious same-user process with write access to package-private `node_modules` is outside that protocol because it can already replace package code, binary, or manifest, and portable Node has no pathname-delete CAS. It never uses `PATH` or a global `gentle-ai` installation. For development or offline installs only, set `GENTLE_PI_SKIP_GENTLE_AI_INSTALL=1`; native review operations then fail closed with an actionable `package-local-binary-missing` error. To recover explicitly, if `GENTLE_PI_SKIP_GENTLE_AI_INSTALL` is set, remove or unset it before changing to the installed `gentle-pi` package directory. Then run `node scripts/install-gentle-ai.mjs`. This invokes the package-owned installer without relying on a global binary or npm configuration change. A missing binary can result from skipped lifecycle scripts, but does not prove that lifecycle scripts were disabled.
|
|
192
210
|
|
|
193
|
-
Recommended companion packages:
|
|
211
|
+
Recommended companion packages, into the standalone `gentle-shell` home:
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
gentle-shell install npm:pi-intercom
|
|
215
|
+
gentle-shell install npm:gentle-engram
|
|
216
|
+
gentle-shell install npm:pi-web-access
|
|
217
|
+
gentle-shell install npm:pi-lens
|
|
218
|
+
gentle-shell install npm:@juicesharp/rpiv-ask-user-question
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
`--link` before the subcommand (for example `gentle-shell --link install npm:pi-intercom`) targets `~/.pi/agent` instead of the isolated home.
|
|
222
|
+
|
|
223
|
+
Or, when `gentle-pi` is installed inside an existing pi:
|
|
194
224
|
|
|
195
225
|
```bash
|
|
196
226
|
pi install npm:pi-intercom
|
|
@@ -241,6 +271,7 @@ gentle-shell home [link|isolated|<path>]
|
|
|
241
271
|
| `--link` | Home is `PI_CODING_AGENT_DIR` or `~/.pi/agent`. Reuses your existing pi sign-ins, models, and chats; never writes to its `settings.json`. |
|
|
242
272
|
| `--isolated` | Home is `GENTLE_SHELL_HOME` or `~/.gentle-shell/agent`. No credential seeding. Default when nothing else is configured. |
|
|
243
273
|
| `--home <path>` | Home is the given directory. |
|
|
274
|
+
| `--package-root <dir>` | Force this directory as the gentle-pi package to load, taking over from any conflicting package the target `settings.json` already declares (see "Loading the package" below). |
|
|
244
275
|
| `--help`, `-h` | Print usage (flags, commands, env vars) and exit 0. |
|
|
245
276
|
| `--version` | Print `gentle-shell <version>`, `pi <version>`, and `home <mode> <dir>`, then exit 0. |
|
|
246
277
|
| `--` | Everything after is forwarded to pi verbatim, even text that looks like a `gentle-shell` flag. |
|
|
@@ -255,6 +286,8 @@ gentle-shell home [link|isolated|<path>]
|
|
|
255
286
|
|
|
256
287
|
`gentle-shell install npm:<pkg>`, `gentle-shell remove ...`, `gentle-shell list`, `gentle-shell update ...`, `gentle-shell config`, and `gentle-shell auth ...` run pi's own commands against the resolved home — the `--isolated` home by default, or your own pi home with `--link`. A launcher flag before the subcommand (`--link`, `--isolated`, `--home <path>`) still selects which home the subcommand runs against. Running `gentle-shell install npm:gentle-pi` inside the isolated home is unnecessary: the launcher already loads the Gentle Shell package itself (see "Loading the package" below).
|
|
257
288
|
|
|
289
|
+
`gentle-shell update` and `gentle-shell list` follow that same home selection, so they inspect and update packages in whichever home the effective flag or persisted `home` config points to.
|
|
290
|
+
|
|
258
291
|
### pi runtime resolution
|
|
259
292
|
|
|
260
293
|
1. `GENTLE_SHELL_PI` — path to a pi executable, when set to a non-empty value.
|
|
@@ -274,7 +307,25 @@ If none resolve, `gentle-shell` exits 1 naming all three options. Once a runtime
|
|
|
274
307
|
|
|
275
308
|
### Loading the package
|
|
276
309
|
|
|
277
|
-
Unless the target home's `settings.json` already
|
|
310
|
+
Unless the target home's `settings.json` already declares gentle-pi (checked only for `--link`), every invocation injects `-e <package root> --theme <root>/themes --skill <root>/skills --prompt-template <root>/prompts` ahead of the forwarded arguments, so the Gentle Shell extensions, themes, skills, and prompt templates load without a separate `pi install`. Isolated and `--home <path>` homes never declare the package, so they always get this injection — except when the forwarded arguments start with one of pi's own subcommands (`install`, `remove`, `uninstall`, `update`, `list`, `config`, `auth`): pi dispatches those on `argv[0]` before it parses any flags, so the injection — and any take-over below — is skipped entirely and pi sees the bare subcommand, e.g. `gentle-shell install npm:x` runs exactly `pi install npm:x`. A subcommand never triggers a take-over, even against a home whose settings declare a conflicting gentle-pi; see "Managing packages" above.
|
|
311
|
+
|
|
312
|
+
A declaration is recognized either as `npm:gentle-pi[@version]` in the `packages` array, or as a local path package (string or `{"source": "..."}` entry, relative or absolute) whose own `package.json` names it `"gentle-pi"` — the shape produced when gentle-pi is developed from a checkout and referenced by path in `settings.json` instead of installed via `pi install npm:gentle-pi`.
|
|
313
|
+
|
|
314
|
+
- **A pi subcommand as the first forwarded argument**: no injection and no take-over at all, regardless of any declaration — pi must see the bare subcommand as `argv[0]`.
|
|
315
|
+
- **npm declaration matching this launcher's own install**: no injection — pi already loads gentle-pi from the declared package.
|
|
316
|
+
- **No declaration at all, or a path declaration that resolves (after `realpath`) to this launcher's own package root**: the same plain injection as above.
|
|
317
|
+
- **A declaration that resolves to a *different* gentle-pi** (a different checkout declared by path, for example) **— take-over**: `gentle-shell` prints `taking over gentle-pi from <declared source> for this run (settings unchanged; its skills, prompts, and themes still load alongside this launcher's)` to stderr, then runs pi with `--no-extensions` followed by an explicit `-e <dir>` for every *other* package already in settings (npm entries resolve to `<agent dir>/npm/node_modules/<name>`; path entries resolve relative to the settings file), then loose extension entries for `<agent dir>/extensions` and the project-local `<cwd>/.pi/extensions` (each candidate directory only consulted when it already exists), and finally its own `-e <package root> --theme ... --skill ... --prompt-template ...`. `settings.json` itself is never modified, and every `-e` path — including the launcher's own package root — is injected at most once even if it would otherwise repeat.
|
|
318
|
+
|
|
319
|
+
A declared *other* package whose resolved directory does not actually exist (a hand-edited `settings.json`, a failed or interrupted `pi install`, or an npm store laid out anywhere other than `<agent dir>/npm/node_modules`) is skipped with a stderr warning naming the source and the resolved path, instead of being handed to pi as an unresolvable `-e` that would fail the whole launch with "Cannot find module".
|
|
320
|
+
|
|
321
|
+
`--no-extensions` disables pi's normal directory-discovery pass, and pi's `-e` flag hands a path straight to its module loader with no discovery of its own — passing a loose extensions directory as-is via `-e <dir>` fails with "Cannot find module" unless that directory is itself a self-contained extension. So each loose candidate directory is resolved before injection: a directory that is itself a self-contained extension (a `package.json` declaring a `pi.extensions` manifest) is passed through as a single `-e <dir>`; otherwise its direct `*.ts`/`*.js`/`*.mjs` files — including a root-level `index.ts`/`index.js`, which is just another loose file — and any `<subdir>/index.ts`/`index.js` are discovered individually — mirroring pi's own directory scan — and each is injected as its own `-e <file>`. Hidden entries (dotfiles) and `*.d.ts` declaration files are skipped, since neither was ever a runnable extension.
|
|
322
|
+
|
|
323
|
+
A git-sourced other package is skipped with a stderr warning, since its install directory cannot be derived without pi's own package manager; an object entry with `extensions` or `autoload` filters is still included but warned about, because the take-over cannot honor those filters for extension discovery — that package's skills, prompts, and themes still load normally through settings discovery, which `--no-extensions` does not affect.
|
|
324
|
+
|
|
325
|
+
**Known limitation**: the take-over never removes the original declaration from `settings.json`, so its skills, prompt templates, and themes are still discovered alongside this launcher's own — only its extensions are replaced by `--no-extensions` plus the injected `-e` flags above.
|
|
326
|
+
- **`--package-root <dir>`**: forces a take-over using `<dir>` as the package root, even when settings already declare a matching `npm:gentle-pi`, or when there is no declaration at all. Use it to test a different gentle-pi checkout against a home whose settings already point at another one. Has no effect when the forwarded arguments start with a pi subcommand, since a subcommand skips the take-over entirely. The take-over path itself is only ever reached for `--link`: with `--isolated` or `--home <path>`, `--package-root` still changes which directory is injected, but always through the same plain injection as "no declaration at all" above — no `--no-extensions`, and no other-package or loose-extension re-injection — since those homes never carry a `settings.json` declaration to take over from. `--package-root` must also name an existing directory; a missing or non-directory path fails fast with a clear error instead of launching pi with unresolvable flags.
|
|
327
|
+
|
|
328
|
+
This take-over exists because two gentle-pi copies loaded at once — the declared one plus this launcher's own injection — register the same tools and extensions twice, which pi reports as tool conflicts (for example `Tool ask_user_choice conflicts with ...`).
|
|
278
329
|
|
|
279
330
|
### First run in an isolated or custom home
|
|
280
331
|
|
|
@@ -375,7 +426,7 @@ Reconciliation is intentionally narrow: native code may quarantine only the boun
|
|
|
375
426
|
|
|
376
427
|
Native lifecycle status remains informational. VALIDATE does not authorize delivery; commit, push, PR, and release commands follow ordinary repository policy. Recovery grants no new budget, and legacy graph bundle export/import is retired.
|
|
377
428
|
|
|
378
|
-
This is the post-U8 boundary, not the final architecture. [Issue #191](https://github.com/Gentleman-Programming/gentle-
|
|
429
|
+
This is the post-U8 boundary, not the final architecture. [Issue #191](https://github.com/Gentleman-Programming/gentle-shell/issues/191) is the immediate final unit in this same delivery: extract the remaining Pi command-projection and lifecycle-gate surface from `review-transaction.ts`, repoint runtime enforcement, then delete only dependencies proven unreachable without weakening graph-v1 Judgment Day. The branch-wide High-tier 4R runs after that extraction, before the single size-exception PR.
|
|
379
430
|
|
|
380
431
|
### Review Lens Selection (architecture reference)
|
|
381
432
|
|
|
@@ -1138,10 +1189,10 @@ tag="v${version}"
|
|
|
1138
1189
|
git fetch --no-tags origin "refs/tags/${tag}"
|
|
1139
1190
|
test "$(git rev-parse 'FETCH_HEAD^{commit}')" = "$(git rev-parse "${tag}^{commit}")"
|
|
1140
1191
|
gh workflow run publish.yml \
|
|
1141
|
-
--repo Gentleman-Programming/gentle-
|
|
1192
|
+
--repo Gentleman-Programming/gentle-shell \
|
|
1142
1193
|
--ref main \
|
|
1143
1194
|
-f tag="${tag}"
|
|
1144
|
-
gh run watch <run-id> --repo Gentleman-Programming/gentle-
|
|
1195
|
+
gh run watch <run-id> --repo Gentleman-Programming/gentle-shell --exit-status
|
|
1145
1196
|
npm view gentle-pi@<version> version --registry=https://registry.npmjs.org/
|
|
1146
1197
|
npm dist-tag ls gentle-pi --registry=https://registry.npmjs.org/
|
|
1147
1198
|
```
|