@jals1212/pi-spaces 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/README.md +101 -14
- package/dist/command.js +10 -5
- package/dist/config.js +248 -0
- package/dist/context.js +1 -1
- package/dist/relaunch.js +39 -5
- package/dist/spaces.js +9 -5
- package/extensions/spaces.ts +19 -5
- package/package.json +2 -1
- package/skills/pi-spaces/SKILL.md +21 -14
- package/src/command.ts +10 -5
- package/src/config.ts +248 -0
- package/src/context.ts +1 -1
- package/src/relaunch.ts +39 -5
- package/src/spaces.ts +9 -5
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,56 @@ commands and options and a patch version fixes.
|
|
|
6
6
|
|
|
7
7
|
## Unreleased
|
|
8
8
|
|
|
9
|
+
## 0.7.0
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- The setup hint of `/space new` names the space with its profile, as
|
|
14
|
+
`pi-space` now requires: `pi-space setup <profile>/<name>`, where the
|
|
15
|
+
profile is the one of the running Pi (`pi` when no profile matches its base
|
|
16
|
+
home). `/space` itself still takes the bare name.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- The built-in `pi` profile no longer takes another profile's home: inside
|
|
21
|
+
the Pi of a program whose profile names its `home`, `PI_CODING_AGENT_DIR`
|
|
22
|
+
holds that home, and `pi` now keeps `~/.pi/agent` (its spaces in
|
|
23
|
+
`~/.pi/spaces`) instead of pointing at that program's home and spaces.
|
|
24
|
+
|
|
25
|
+
## 0.6.0
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- Profiles in a config file (`~/.pi-spaces/config.json`, or `PI_SPACES_CONFIG`):
|
|
30
|
+
named descriptions of a program that runs Pi, with its base `home`,
|
|
31
|
+
`command`, spaces `dir`, `setup` command and `spaceEnv`. The format carries a
|
|
32
|
+
version (`"schema": "pi-spaces.config/v1"`); a newer version is refused with
|
|
33
|
+
a message that it needs a newer pi-spaces, and unknown keys are refused. A
|
|
34
|
+
built-in `pi` profile always exists and can be replaced. See the README.
|
|
35
|
+
- `spaceEnv`: variables that exist only in spaces. Entering a space sets them
|
|
36
|
+
(`{home}` and `{name}` filled in) and moving between spaces recomputes
|
|
37
|
+
them. Going back to `base` puts back the values the base home had (kept in `PI_SPACES_BASE_ENV` meanwhile). Without
|
|
38
|
+
`spaceEnv`, whatever a program keeps outside its agent home stays shared with
|
|
39
|
+
the base home, as before. The variables pi-spaces sets itself are refused.
|
|
40
|
+
- The extension applies the profile whose `home` is the base home: its
|
|
41
|
+
`spaceEnv` on `/space switch` and `pi --space`, its `dir` as the spaces
|
|
42
|
+
folder, and `/space new` names `pi-space setup <name>` when it has a `setup`.
|
|
43
|
+
- `@jals1212/pi-spaces/config` exports the loader (`loadConfig`, `configPath`,
|
|
44
|
+
`profileByName`, `profileForHome`, `spaceEnvFor`, `activeProfile`,
|
|
45
|
+
`ConfigError`) for the `pi-space` command.
|
|
46
|
+
|
|
47
|
+
### Changed
|
|
48
|
+
|
|
49
|
+
- `completion` is a reserved name, for `pi-space completion`. A space already
|
|
50
|
+
named `completion` is no longer listed; rename its folder by hand.
|
|
51
|
+
- The spaces folder is `PI_SPACES_DIR`, else the matching profile's `dir`, else
|
|
52
|
+
`spaces` next to the base home. `spacesDir` takes the profile's dir as an
|
|
53
|
+
optional third argument.
|
|
54
|
+
- `relaunchEnv` and `planStartup` take an optional `spaceEnv`; without it they
|
|
55
|
+
behave exactly as before.
|
|
56
|
+
- A config file that cannot be used never stops Pi: the extension warns once
|
|
57
|
+
when the session starts and behaves as with no file.
|
|
58
|
+
|
|
9
59
|
## 0.5.0
|
|
10
60
|
|
|
11
61
|
### Added
|
package/README.md
CHANGED
|
@@ -45,10 +45,10 @@ From your shell:
|
|
|
45
45
|
```sh
|
|
46
46
|
pi --space work # start Pi in "work"
|
|
47
47
|
pi --space work "review this branch" # with a prompt
|
|
48
|
-
pi-space new work
|
|
49
|
-
pi-space remove work
|
|
50
|
-
pi-space update --every # update the packages of base and of every space (optional CLI)
|
|
51
|
-
pi-space # pick
|
|
48
|
+
pi-space new pi/work # create a space of the profile "pi" (optional CLI)
|
|
49
|
+
pi-space remove pi/work # delete it, after asking; keeps a backup unless told not to (optional CLI)
|
|
50
|
+
pi-space update --every # update the packages of base and of every space of every profile (optional CLI)
|
|
51
|
+
pi-space # pick a space of any profile: arrows, Enter, type to filter (optional CLI)
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
A new space starts empty except for a `settings.json` that declares this package, so `/space` works inside it. Sign in and install packages there as you would in a fresh Pi.
|
|
@@ -59,7 +59,7 @@ A new space starts empty except for a `settings.json` that declares this package
|
|
|
59
59
|
| --- | --- |
|
|
60
60
|
| `/space` | Opens a list of the other spaces and restarts Pi in the one you pick. Without the interactive terminal it lists them instead. |
|
|
61
61
|
| `/space list` | Lists `base` and every space, with `*` on the active one. |
|
|
62
|
-
| `/space new <name>` | Creates an empty space. When a setup command is set (`PI_SPACES_SETUP`), it names `pi-space setup <name>` to run from a shell. |
|
|
62
|
+
| `/space new <name>` | Creates an empty space. When a setup command is set (the profile's `setup`, or `PI_SPACES_SETUP`), it names `pi-space setup <profile>/<name>` to run from a shell, with this Pi's profile (`pi` when none matches). |
|
|
63
63
|
| `/space switch <name>` | Restarts Pi in that space. `base` is the home Pi started with. |
|
|
64
64
|
| `/space switch` | Same as `/space`. |
|
|
65
65
|
| `/space rename <old> <new>` | Renames a space. The active space cannot be renamed. |
|
|
@@ -67,21 +67,105 @@ A new space starts empty except for a `settings.json` that declares this package
|
|
|
67
67
|
| `pi --space <name>` | Starts Pi directly in a space, keeping the prompt and every other option. |
|
|
68
68
|
| `pi --space <name> <command> [args...]` | Runs one of Pi's package commands (`install`, `remove`, `uninstall`, `update`, `list`) in a space, or in the base home with `base`: `pi --space work update --extensions`, `pi --space work install npm:<package>`. |
|
|
69
69
|
|
|
70
|
-
The status line shows `◆ <name>` while a space is active. In the base home it shows `◆ base` when pi-spaces opened it (`pi --space base`, `pi-space base`, or `/space switch base`), and nothing when Pi was started plainly.
|
|
70
|
+
The status line shows `◆ <name>` while a space is active. In the base home it shows `◆ base` when pi-spaces opened it (`pi --space base`, `pi-space pi/base`, or `/space switch base`), and nothing when Pi was started plainly.
|
|
71
71
|
|
|
72
72
|
Choosing an argument such as `switch` from the `/space ` suggestions only fills the line; press Enter again to run it. `/space` and Enter opens the picker in one step.
|
|
73
73
|
|
|
74
|
-
**Names**: lowercase letters, digits, `-` and `_`, starting with a letter or digit, at most 64 characters. `base` is reserved, and so are the command words `list`, `new`, `remove`, `rename`, `switch`, `update`, `install`, `uninstall` and `
|
|
74
|
+
**Names**: lowercase letters, digits, `-` and `_`, starting with a letter or digit, at most 64 characters. `base` is reserved, and so are the command words `list`, `new`, `remove`, `rename`, `switch`, `update`, `install`, `uninstall`, `setup` and `completion`.
|
|
75
75
|
|
|
76
76
|
## The `pi-space` command
|
|
77
77
|
|
|
78
|
-
The optional `@jals1212/pi-spaces-cli` package adds a `pi-space` command to your shell: `pi-space`
|
|
78
|
+
The optional `@jals1212/pi-spaces-cli` package adds a `pi-space` command to your shell. It names every space as `<profile>/<name>` (`pi/work`, `my/base`): the profile is an entry of the [config file](#profiles), and `pi` (plain Pi) is always there. `pi-space` opens a picker of the spaces of every profile and starts Pi in the one you choose, `pi-space <profile>/<name> [pi args...]` starts Pi in a named space, `pi-space new <profile>/<name>` creates one, `pi-space rename <profile>/<old> <new>` renames one, `pi-space remove <profile>/<name>` deletes one, and `pi-space list` lists every profile's spaces. `pi-space list|update|install|uninstall <profile>/<name|--every> ...` runs Pi's package commands in a space, in a profile's spaces, or (`--every` alone) in every profile's. The profile says which program to run, where its spaces live and how to prepare a new space; `--command`, `--dir` and `--setup` override those for one call, for one named profile. `pi-space setup <profile>/<name|--every>` runs the setup command again. The command does not read `PI_SPACES_COMMAND`, `PI_SPACES_DIR` or `PI_SPACES_SETUP` as settings: they are what it hands to the Pi it starts. It only runs `pi --space <name> [command]`, so this extension must be installed in the home it opens. See [`cli/README.md`](cli/README.md) for the commands, the options and the exit status.
|
|
79
|
+
|
|
80
|
+
## Profiles
|
|
81
|
+
|
|
82
|
+
A **profile** describes one program that runs Pi (plain Pi is one too): where its agent home is, the command that starts it, where its spaces live, how to prepare a new space, and the variables it needs per space. Profiles live in an optional JSON file, so you do not need shell variables or functions to tell pi-spaces about a program. Without the file, nothing changes: this is exactly how pi-spaces behaves without any configuration.
|
|
83
|
+
|
|
84
|
+
The extension uses the profile whose `home` is the base home Pi started with (the one `base` goes back to); inside a space that is the home the space came from. The optional `pi-space` command ([`cli/README.md`](cli/README.md)) reads the same file, and the profile is the first word of every space it takes: `pi-space ml/work`. It does not use `default`: the profile is always named.
|
|
85
|
+
|
|
86
|
+
### The file
|
|
87
|
+
|
|
88
|
+
| | |
|
|
89
|
+
| --- | --- |
|
|
90
|
+
| Location | `~/.pi-spaces/config.json` |
|
|
91
|
+
| Change it | `PI_SPACES_CONFIG=<path>` (a leading `~` is your home folder; a relative path is taken from it). The `pi-space` command also takes `--config <path>` (see [`cli/README.md`](cli/README.md)). |
|
|
92
|
+
| Missing | No profiles: Pi, spaces and `pi-space` behave as without this feature. |
|
|
93
|
+
| Invalid | See [When the file is wrong](#when-the-file-is-wrong). |
|
|
94
|
+
|
|
95
|
+
A complete example for a program called `my-launcher`, which keeps its own configuration outside its agent home:
|
|
96
|
+
|
|
97
|
+
```json
|
|
98
|
+
{
|
|
99
|
+
"schema": "pi-spaces.config/v1",
|
|
100
|
+
"profiles": {
|
|
101
|
+
"ml": {
|
|
102
|
+
"home": "~/.my-launcher/agent",
|
|
103
|
+
"command": "my-launcher --",
|
|
104
|
+
"setup": "my-launcher --home {home} setup",
|
|
105
|
+
"spaceEnv": { "MY_LAUNCHER_CONFIG_HOME": "{home}/config" }
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
With that file, a space of `my-launcher` lives in `~/.my-launcher/spaces/<name>`, `/space new <name>` points you at `pi-space setup ml/<name>`, and entering the space `work` starts Pi with `MY_LAUNCHER_CONFIG_HOME=~/.my-launcher/spaces/work/config`, while the base home gets no such variable from pi-spaces. From a shell, `pi-space ml/work` starts that space, and an alias such as `alias mlw='pi-space ml/work'` is optional sugar on top.
|
|
112
|
+
|
|
113
|
+
### Schema `pi-spaces.config/v1`
|
|
114
|
+
|
|
115
|
+
Top level (every other key is refused):
|
|
116
|
+
|
|
117
|
+
| Key | Type | Required | Meaning |
|
|
118
|
+
| --- | --- | --- | --- |
|
|
119
|
+
| `schema` | string | yes | Exactly `"pi-spaces.config/v1"`. |
|
|
120
|
+
| `default` | string | no | Accepted and validated (it must name a profile of the file, or `pi`), but not used: neither the extension nor `pi-space` falls back to it. `pi-space` warns when it reads a file that has it; remove the key. |
|
|
121
|
+
| `profiles` | object | no | Profiles by name. Names: lowercase letters, digits, `-` and `_`, starting with a letter or digit, at most 64 characters. |
|
|
122
|
+
|
|
123
|
+
A profile (every other key is refused; every key is optional):
|
|
124
|
+
|
|
125
|
+
| Key | Type | Default | Meaning |
|
|
126
|
+
| --- | --- | --- | --- |
|
|
127
|
+
| `home` | string, not empty | `PI_CODING_AGENT_DIR`, else `~/.pi/agent` | The base agent home of the program: the home it starts Pi with, which `base` goes back to. `~` is your home folder; a relative path is taken from it. |
|
|
128
|
+
| `command` | string, not empty | `pi` for the profile `pi`; none for the others | The command that starts the program (`my-launcher --`), in front of `--space <name>`. |
|
|
129
|
+
| `dir` | string, not empty | `spaces` next to `home` (`~/.my-launcher/agent` gives `~/.my-launcher/spaces`) | The folder that holds the profile's spaces. `~` and relative paths as in `home`. |
|
|
130
|
+
| `setup` | string, not empty | none | The command that prepares a new space; `{home}` and `{name}` become the space's folder and name. With it, `/space new` names `pi-space setup <profile>/<name>`. |
|
|
131
|
+
| `spaceEnv` | object of strings | none | Variables that exist only in spaces; see below. Names: letters, digits and `_`, not starting with a digit; not `PI_CODING_AGENT_DIR`, `PI_SPACE`, `PI_SPACE_HOME` or any `PI_SPACES_*`, which pi-spaces sets itself. |
|
|
132
|
+
|
|
133
|
+
All strings are trimmed. `{home}` and `{name}` are replaced everywhere they appear in a `spaceEnv` value (and `setup`), and `$` in a value stays literal.
|
|
134
|
+
|
|
135
|
+
### `spaceEnv`: what is separate and what is shared
|
|
136
|
+
|
|
137
|
+
Pi keeps everything in its agent home, so a space separates it completely with no configuration. A program that runs Pi may also keep something **outside** that home (a configuration folder, say) and hand its location to Pi in an environment variable. When you switch, pi-spaces moves only the variables that point inside the old home (see [How switching works](#how-switching-works)); a variable that points elsewhere is not recognised as part of the home and stays as it was. So without `spaceEnv`, whatever the program keeps outside the agent home is **shared** between the base home and every space. That is the default on purpose: nothing is separated that you did not ask to separate.
|
|
138
|
+
|
|
139
|
+
To separate it, name the variable in `spaceEnv`, with a value built from the space:
|
|
140
|
+
|
|
141
|
+
- Entering a space sets the variable, from the template, with `{home}` the space's folder and `{name}` its name.
|
|
142
|
+
- Going back to `base` puts back the value the base home had when you left it, or removes the variable when it had none, so the base home runs exactly as it was started. The base's values travel in `PI_SPACES_BASE_ENV` while you are in spaces.
|
|
143
|
+
- Going from one space to another sets it again for the target; a value that pointed into the old space is replaced, not moved.
|
|
144
|
+
- `pi --space <name>` does the same at startup.
|
|
145
|
+
|
|
146
|
+
pi-spaces does not create what a variable points to; `setup` (or the program itself) does.
|
|
147
|
+
|
|
148
|
+
### The built-in `pi` profile
|
|
149
|
+
|
|
150
|
+
Plain Pi is always a profile named `pi`: command `pi`, no setup, no `spaceEnv`. Its home is `PI_SPACES_BASE_HOME` (set inside a space), else `PI_CODING_AGENT_DIR`, else `~/.pi/agent`.
|
|
151
|
+
|
|
152
|
+
Inside another program's Pi those variables hold that program's home. When a profile of the file names that folder as its `home`, `pi` does not take it and uses `~/.pi/agent`, so `pi/<name>` is still plain Pi there.
|
|
153
|
+
|
|
154
|
+
Naming `pi` in `profiles` replaces it as a whole; what that entry leaves out still takes the defaults above for `home` and `command`, never anything from the old built-in's other fields.
|
|
155
|
+
|
|
156
|
+
### Schema versions
|
|
157
|
+
|
|
158
|
+
The file says which format it uses in `schema`, so the format can grow without breaking files. This pi-spaces reads `pi-spaces.config/v1` only. A file with a newer `pi-spaces.config/vN` is refused with a message that it needs a newer pi-spaces (update the package); anything else in `schema`, or no `schema`, is refused as invalid. A newer format is never half-read.
|
|
159
|
+
|
|
160
|
+
### When the file is wrong
|
|
161
|
+
|
|
162
|
+
Unknown keys, wrong types, bad names, a `default` that names no profile, an unreadable file and invalid JSON are all refused with a message that names the file and the problem, for example `~/.pi-spaces/config.json: unknown key "hmoe" in profile "ml"; allowed keys are "home", "command", "dir", "setup" and "spaceEnv".` Pi never stops for it: the extension shows the message once as a warning when the session starts (on stderr without a terminal UI) and then behaves as if there were no file. A missing file is not an error.
|
|
79
163
|
|
|
80
164
|
## Configuration reference
|
|
81
165
|
|
|
82
166
|
### Where spaces live
|
|
83
167
|
|
|
84
|
-
In a `spaces` folder next to the home Pi starts with: `~/.pi/agent` keeps its spaces in `~/.pi/spaces/<name>/`. A program that runs Pi with its own home gets its own folder too, for example `~/.tool/agent` → `~/.tool/spaces`. The folder stays the same while you switch between spaces. `PI_SPACES_DIR` replaces it.
|
|
168
|
+
In a `spaces` folder next to the home Pi starts with: `~/.pi/agent` keeps its spaces in `~/.pi/spaces/<name>/`. A program that runs Pi with its own home gets its own folder too, for example `~/.tool/agent` → `~/.tool/spaces`. The folder stays the same while you switch between spaces. A [profile](#profiles)'s `dir` replaces it for that profile's home, and `PI_SPACES_DIR` replaces both. The `pi-space` command does not read `PI_SPACES_DIR` (or `PI_SPACES_COMMAND` and `PI_SPACES_SETUP`) as a setting: it takes the folder from the profile or `--dir`, and sets the variable for the Pi it starts.
|
|
85
169
|
|
|
86
170
|
### Environment variables
|
|
87
171
|
|
|
@@ -89,9 +173,10 @@ Read:
|
|
|
89
173
|
|
|
90
174
|
| Variable | Meaning |
|
|
91
175
|
| --- | --- |
|
|
92
|
-
| `PI_SPACES_DIR` | The spaces folder. A relative value is taken from your home folder. Default: `spaces` next to the base home. |
|
|
93
|
-
| `
|
|
94
|
-
| `
|
|
176
|
+
| `PI_SPACES_DIR` | The spaces folder. A relative value is taken from your home folder. Default: the matching profile's `dir`, else `spaces` next to the base home. |
|
|
177
|
+
| `PI_SPACES_CONFIG` | The [config file](#profiles) with the profiles. A leading `~` is your home folder and a relative value is taken from it. Default: `~/.pi-spaces/config.json`. |
|
|
178
|
+
| `PI_SPACES_COMMAND` | The program that starts Pi (default `pi`): told to the model and exported (see below). A switch starts Pi again with the command line it already has, so this is information, not a setting. |
|
|
179
|
+
| `PI_SPACES_SETUP` | The command that prepares a new space. `/space new` names `pi-space setup <profile>/<name>` when it is set. The `pi-space` command sets it for the Pi it starts, from the profile or `--setup`; it does not read it from your shell. |
|
|
95
180
|
| `PI_CODING_AGENT_DIR` | Pi's agent home; its parent holds the default spaces folder. Default: `~/.pi/agent`. |
|
|
96
181
|
|
|
97
182
|
Set for the Pi a switch starts:
|
|
@@ -101,6 +186,7 @@ Set for the Pi a switch starts:
|
|
|
101
186
|
| `PI_CODING_AGENT_DIR` | The space's folder, or the base home for `base`. |
|
|
102
187
|
| `PI_SPACE` | The space's name. Unset in the base home. |
|
|
103
188
|
| `PI_SPACES_BASE_HOME` | The home Pi started with, so `base` and the spaces folder survive switches. |
|
|
189
|
+
| `PI_SPACES_BASE_ENV` | Only with a profile's `spaceEnv`, and only in a space: the base home's own values of those variables (JSON), put back when you return to `base`. |
|
|
104
190
|
|
|
105
191
|
Set in every Pi this extension runs in, even one started without a switch, for its tools and the programs they start:
|
|
106
192
|
|
|
@@ -108,7 +194,7 @@ Set in every Pi this extension runs in, even one started without a switch, for i
|
|
|
108
194
|
| --- | --- |
|
|
109
195
|
| `PI_SPACE` | The active space's name, unset in the base home: the same variable a switch sets. |
|
|
110
196
|
| `PI_SPACE_HOME` | The agent home this Pi runs with: the space's folder, or the base home. |
|
|
111
|
-
| `PI_SPACES_COMMAND` | Kept when it has a value; set to `pi` when it is unset or blank. |
|
|
197
|
+
| `PI_SPACES_COMMAND` | Kept when it has a value; set to `pi` when it is unset or blank. The `pi-space` command sets it for the Pi it starts (the profile's `command`, or `--command`), and the extension reads it to tell the model and the tools how Pi was started (see below); `pi-space` itself does not read it from your shell. |
|
|
112
198
|
|
|
113
199
|
### What the model is told
|
|
114
200
|
|
|
@@ -133,6 +219,7 @@ Pi reads its agent home once, when it starts, so a running session cannot move t
|
|
|
133
219
|
|
|
134
220
|
- `PI_CODING_AGENT_DIR`, `PI_SPACE` and `PI_SPACES_BASE_HOME` are set as in [Environment variables](#environment-variables).
|
|
135
221
|
- Any other variable whose value is the old home, or a path inside it, moves to the same path in the new home; in a list such as `PATH`, each entry moves on its own. This keeps variables that a launcher derived from the home consistent. `PWD` and `OLDPWD` are left alone.
|
|
222
|
+
- With a [profile](#profiles) that has `spaceEnv`, those variables are then set, with `{home}` and `{name}` filled in, when the target is a space; when it is `base`, they get back the values the base home had (`PI_SPACES_BASE_ENV`).
|
|
136
223
|
- The same options are kept, such as `--model`, `-e`, `--tools` and other extensions' options. The initial prompt, `@file` arguments, everything after `--`, and the session options (`-c`, `-r`, `--session`, `--session-id`, `--fork`, `--session-dir`, `--no-session`, `-n`) are dropped, because they belong to the home being left.
|
|
137
224
|
- Switching works only in the interactive terminal.
|
|
138
225
|
|
|
@@ -164,7 +251,7 @@ npm test # links cli/ to this checkout, builds both, run
|
|
|
164
251
|
pi -e ./extensions/spaces.ts # try the extension without installing it
|
|
165
252
|
```
|
|
166
253
|
|
|
167
|
-
`src/` is TypeScript. Pi loads the extension's TypeScript itself. The command in `cli/` imports the core as `@jals1212/pi-spaces/spaces
|
|
254
|
+
`src/` is TypeScript. Pi loads the extension's TypeScript itself. The command in `cli/` imports the core as `@jals1212/pi-spaces/spaces` (and the profiles code as `@jals1212/pi-spaces/config`): the plain JavaScript in `dist/` that `npm run build` makes (Node does not strip types from files under `node_modules`). `npm pack` and `npm publish` build it first. `scripts/link-self.mjs` makes `cli/node_modules/@jals1212/pi-spaces` a link to this checkout, so the command's tests use the core from the working tree instead of from npm.
|
|
168
255
|
|
|
169
256
|
Branches: work goes to `develop` through pull requests; `main` receives `develop` when a release closes.
|
|
170
257
|
|
package/dist/command.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// The /space command, kept apart from Pi's extension API so it can be tested directly.
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
|
|
3
4
|
import { parseSpaceFlag, relaunchEnv, resolveSwitch } from "./relaunch.js";
|
|
4
5
|
import {
|
|
5
6
|
activeSpace,
|
|
@@ -40,6 +41,8 @@ const SUBCOMMANDS = ["list", "new", "remove", "rename", "switch"];
|
|
|
40
41
|
|
|
41
42
|
|
|
42
43
|
|
|
44
|
+
|
|
45
|
+
|
|
43
46
|
|
|
44
47
|
|
|
45
48
|
|
|
@@ -55,11 +58,13 @@ export async function runSpaceCommand(args , deps )
|
|
|
55
58
|
try {
|
|
56
59
|
if (sub === "list" && rest.length === 0) return list(deps);
|
|
57
60
|
if (sub === "new" && rest.length === 1) {
|
|
58
|
-
const
|
|
59
|
-
const
|
|
61
|
+
const [name] = rest;
|
|
62
|
+
const path = createSpace(deps.dir, name, deps.declaration());
|
|
63
|
+
// pi-space names a space with its profile; without a matching profile this Pi is plain pi.
|
|
64
|
+
const qualified = `${deps.profile?.name ?? "pi"}/${name}`;
|
|
60
65
|
ctx.ui.notify(
|
|
61
|
-
hasSetupCommand(deps.env)
|
|
62
|
-
? `Created space "${name}" at ${path}. Prepare it from a shell with: pi-space setup ${
|
|
66
|
+
hasSetupCommand(deps.env) || Boolean(deps.profile?.setup)
|
|
67
|
+
? `Created space "${name}" at ${path}. Prepare it from a shell with: pi-space setup ${qualified}. Then enter it with /space switch ${name}.`
|
|
63
68
|
: `Created space "${name}" at ${path}. Enter it with /space switch ${name}.`,
|
|
64
69
|
"info",
|
|
65
70
|
);
|
|
@@ -153,7 +158,7 @@ function switchTo(name , deps ) {
|
|
|
153
158
|
if (ctx.mode !== "tui") throw new SpaceError("/space switch needs the interactive terminal.");
|
|
154
159
|
const currentHome = deps.currentHome();
|
|
155
160
|
const target = resolveSwitch({ dir: deps.dir, name, currentHome, env: deps.env });
|
|
156
|
-
deps.relaunch(deps.command(), relaunchEnv(deps.env, { ...target, currentHome }));
|
|
161
|
+
deps.relaunch(deps.command(), relaunchEnv(deps.env, { ...target, currentHome, spaceEnv: deps.profile?.spaceEnv }));
|
|
157
162
|
ctx.ui.notify(
|
|
158
163
|
target.space === undefined ? "Switching to the base home; Pi restarts in it." : `Switching to space "${target.space}"; Pi restarts in it.`,
|
|
159
164
|
"info",
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
// The config file: named profiles that describe a program that runs Pi (its agent home, the command
|
|
2
|
+
// that starts it, where its spaces live, how to set a space up, and the variables it needs per space).
|
|
3
|
+
// Pure and host-agnostic: the file system, the environment and the user's home are all inputs.
|
|
4
|
+
import { readFileSync } from "node:fs";
|
|
5
|
+
import { dirname, join, resolve } from "node:path";
|
|
6
|
+
import { expandTilde, samePath } from "./spaces.js";
|
|
7
|
+
|
|
8
|
+
/** The schema this version of pi-spaces reads. */
|
|
9
|
+
export const SCHEMA = "pi-spaces.config/v1";
|
|
10
|
+
const NEWER_SCHEMA = /^pi-spaces\.config\/v([1-9][0-9]*)$/;
|
|
11
|
+
const PROFILE_NAME = /^[a-z0-9][a-z0-9_-]{0,63}$/;
|
|
12
|
+
const VARIABLE_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
13
|
+
// Set by pi-spaces itself on every switch; a profile that set them would break switching.
|
|
14
|
+
const RESERVED_VARIABLES = new Set(["PI_CODING_AGENT_DIR", "PI_SPACE", "PI_SPACE_HOME"]);
|
|
15
|
+
const isReservedVariable = (name ) => RESERVED_VARIABLES.has(name) || name.startsWith("PI_SPACES_");
|
|
16
|
+
const TOP_KEYS = ["schema", "default", "profiles"];
|
|
17
|
+
const PROFILE_KEYS = ["home", "command", "dir", "setup", "spaceEnv"];
|
|
18
|
+
const BUILT_IN = "pi";
|
|
19
|
+
|
|
20
|
+
export class ConfigError extends Error {
|
|
21
|
+
constructor(message ) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.name = "ConfigError";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** A profile as written in the file: validated and trimmed, paths not yet expanded. */
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
/** A profile with its paths resolved. `command`, `setup` and `spaceEnv` are set only when the profile has them. */
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
/** The config file: PI_SPACES_CONFIG (a relative value is taken from the user's home), else ~/.pi-spaces/config.json. */
|
|
60
|
+
export function configPath(env , home ) {
|
|
61
|
+
const fromEnv = env.PI_SPACES_CONFIG?.trim();
|
|
62
|
+
if (fromEnv) return resolve(home, expandTilde(fromEnv, home));
|
|
63
|
+
return join(home, ".pi-spaces", "config.json");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function isObject(value ) {
|
|
67
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function reason(error ) {
|
|
71
|
+
return error instanceof Error ? error.message : String(error);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Reads and validates the config file. A missing file is no config (undefined). Anything else wrong
|
|
78
|
+
* throws a ConfigError that names the file and the problem; unknown keys are refused so typos fail loudly.
|
|
79
|
+
*/
|
|
80
|
+
export function loadConfig(path , read = (file) => readFileSync(file, "utf8")) {
|
|
81
|
+
let text ;
|
|
82
|
+
try {
|
|
83
|
+
text = read(path);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
if ((error )?.code === "ENOENT") return undefined;
|
|
86
|
+
throw new ConfigError(`${path}: cannot be read: ${reason(error)}`);
|
|
87
|
+
}
|
|
88
|
+
const fail = (problem) => {
|
|
89
|
+
throw new ConfigError(`${path}: ${problem}`);
|
|
90
|
+
};
|
|
91
|
+
let json ;
|
|
92
|
+
try {
|
|
93
|
+
json = JSON.parse(text);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
return fail(`not valid JSON: ${reason(error)}`);
|
|
96
|
+
}
|
|
97
|
+
if (!isObject(json)) return fail("the top level must be a JSON object.");
|
|
98
|
+
// The version first: a newer file may use keys this one does not know.
|
|
99
|
+
checkSchema(json.schema, "schema" in json, fail);
|
|
100
|
+
for (const key of Object.keys(json)) {
|
|
101
|
+
if (!TOP_KEYS.includes(key)) fail(`unknown key "${key}"; allowed keys are "schema", "default" and "profiles".`);
|
|
102
|
+
}
|
|
103
|
+
const config = { path, profiles: parseProfiles(json.profiles, "profiles" in json, fail) };
|
|
104
|
+
if ("default" in json) {
|
|
105
|
+
const name = json.default;
|
|
106
|
+
if (typeof name !== "string" || name === "") return fail('"default" must be a string naming a profile.');
|
|
107
|
+
const known = profileNames(config);
|
|
108
|
+
if (!known.includes(name)) fail(`"default" names unknown profile "${name}"; known profiles: ${known.join(", ")}.`);
|
|
109
|
+
config.default = name;
|
|
110
|
+
}
|
|
111
|
+
return config;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function checkSchema(schema , present , fail ) {
|
|
115
|
+
if (!present) fail(`"schema" is required; use "${SCHEMA}".`);
|
|
116
|
+
if (schema === SCHEMA) return;
|
|
117
|
+
const version = typeof schema === "string" ? NEWER_SCHEMA.exec(schema)?.[1] : undefined;
|
|
118
|
+
// Only a canonical vN with N above 1 is a newer version; v0 and v01 are just invalid.
|
|
119
|
+
if (version !== undefined && Number(version) > 1) {
|
|
120
|
+
fail(`"schema" is "${schema}", which needs a newer pi-spaces; this one reads "${SCHEMA}".`);
|
|
121
|
+
}
|
|
122
|
+
fail(`invalid "schema" ${JSON.stringify(schema)}; use "${SCHEMA}".`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function parseProfiles(value , present , fail ) {
|
|
126
|
+
if (!present) return [];
|
|
127
|
+
if (!isObject(value)) return fail('"profiles" must be an object of profiles.');
|
|
128
|
+
return Object.entries(value).map(([name, profile]) => parseProfile(name, profile, fail));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function parseProfile(name , value , fail ) {
|
|
132
|
+
if (!PROFILE_NAME.test(name)) {
|
|
133
|
+
fail(`invalid profile name "${name}": use lowercase letters, digits, "-" and "_", starting with a letter or digit (at most 64 characters).`);
|
|
134
|
+
}
|
|
135
|
+
if (!isObject(value)) return fail(`profile "${name}" must be an object.`);
|
|
136
|
+
for (const key of Object.keys(value)) {
|
|
137
|
+
if (!PROFILE_KEYS.includes(key)) {
|
|
138
|
+
fail(`unknown key "${key}" in profile "${name}"; allowed keys are "home", "command", "dir", "setup" and "spaceEnv".`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const spec = { name };
|
|
142
|
+
for (const key of ["home", "command", "dir", "setup"] ) {
|
|
143
|
+
if (!(key in value)) continue;
|
|
144
|
+
const text = value[key];
|
|
145
|
+
if (typeof text !== "string" || text.trim() === "") return fail(`"${key}" in profile "${name}" must be a non-empty string.`);
|
|
146
|
+
spec[key] = text.trim();
|
|
147
|
+
}
|
|
148
|
+
if ("spaceEnv" in value) spec.spaceEnv = parseSpaceEnv(name, value.spaceEnv, fail);
|
|
149
|
+
return spec;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function parseSpaceEnv(profile , value , fail ) {
|
|
153
|
+
if (!isObject(value)) return fail(`"spaceEnv" in profile "${profile}" must be an object of strings.`);
|
|
154
|
+
const entries = [];
|
|
155
|
+
for (const [key, text] of Object.entries(value)) {
|
|
156
|
+
if (!VARIABLE_NAME.test(key)) {
|
|
157
|
+
fail(`invalid variable name "${key}" in "spaceEnv" of profile "${profile}"; use letters, digits and "_", not starting with a digit.`);
|
|
158
|
+
}
|
|
159
|
+
if (isReservedVariable(key)) fail(`"${key}" in "spaceEnv" of profile "${profile}" is set by pi-spaces itself; choose another variable.`);
|
|
160
|
+
if (typeof text !== "string") return fail(`"spaceEnv.${key}" in profile "${profile}" must be a string.`);
|
|
161
|
+
entries.push([key, text]);
|
|
162
|
+
}
|
|
163
|
+
// fromEntries defines own properties, so a name such as __proto__ stays plain data.
|
|
164
|
+
return Object.fromEntries(entries);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** The profile names, in file order, with `pi` last unless the file defines it. */
|
|
168
|
+
export function profileNames(config ) {
|
|
169
|
+
const names = (config?.profiles ?? []).map((profile) => profile.name);
|
|
170
|
+
return names.includes(BUILT_IN) ? names : [...names, BUILT_IN];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* The built-in pi home: the base home inside a space (PI_SPACES_BASE_HOME), else PI_CODING_AGENT_DIR, else ~/.pi/agent.
|
|
175
|
+
* A home that a profile of the file names is that profile's, not pi's: inside the Pi of another program those
|
|
176
|
+
* variables hold that program's home, so pi keeps ~/.pi/agent.
|
|
177
|
+
*/
|
|
178
|
+
function builtInHome(config , env , home ) {
|
|
179
|
+
const fromEnv = env.PI_SPACES_BASE_HOME || env.PI_CODING_AGENT_DIR;
|
|
180
|
+
const found = fromEnv ? resolve(home, expandTilde(fromEnv, home)) : undefined;
|
|
181
|
+
const claimed = (path ) => config?.profiles.some((spec) => spec.home !== undefined && samePath(resolve(home, expandTilde(spec.home, home)), path));
|
|
182
|
+
return found !== undefined && !claimed(found) ? found : join(home, ".pi", "agent");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Resolves a profile; what a profile omits for `home` falls back to the built-in pi's, and `pi` also gets its `command`. */
|
|
186
|
+
function resolveProfile(config , spec , env , home ) {
|
|
187
|
+
const base = spec.home === undefined ? builtInHome(config, env, home) : resolve(home, expandTilde(spec.home, home));
|
|
188
|
+
const profile = {
|
|
189
|
+
name: spec.name,
|
|
190
|
+
home: base,
|
|
191
|
+
dir: spec.dir === undefined ? join(dirname(base), "spaces") : resolve(home, expandTilde(spec.dir, home)),
|
|
192
|
+
};
|
|
193
|
+
const command = spec.command ?? (spec.name === BUILT_IN ? "pi" : undefined);
|
|
194
|
+
if (command !== undefined) profile.command = command;
|
|
195
|
+
if (spec.setup !== undefined) profile.setup = spec.setup;
|
|
196
|
+
if (spec.spaceEnv !== undefined) profile.spaceEnv = spec.spaceEnv;
|
|
197
|
+
return profile;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** The profile with this name, or a ConfigError that lists the known ones. `pi` always exists. */
|
|
201
|
+
export function profileByName(config , name , env , home ) {
|
|
202
|
+
const spec = config?.profiles.find((profile) => profile.name === name) ?? (name === BUILT_IN ? { name } : undefined);
|
|
203
|
+
if (spec === undefined) throw new ConfigError(`Unknown profile "${name}"; known profiles: ${profileNames(config).join(", ")}.`);
|
|
204
|
+
return resolveProfile(config, spec, env, home);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** The profile whose home is the base home (the same place, real paths compared): the first in file order, then built-in `pi`. */
|
|
208
|
+
export function profileForHome(config , baseHome , env , home ) {
|
|
209
|
+
for (const name of profileNames(config)) {
|
|
210
|
+
const profile = profileByName(config, name, env, home);
|
|
211
|
+
if (samePath(profile.home, baseHome)) return profile;
|
|
212
|
+
}
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** The templates with every `{home}` and `{name}` replaced. Replacer functions keep `$` literal. */
|
|
217
|
+
export function expandSpaceEnv(templates , spaceHome , spaceName ) {
|
|
218
|
+
const fill = (template ) => template.replace(/\{home\}/g, () => spaceHome).replace(/\{name\}/g, () => spaceName);
|
|
219
|
+
return Object.fromEntries(Object.entries(templates).map(([key, template]) => [key, fill(template)]));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** The variables a space gets: the profile's spaceEnv for this space's folder and name (none without spaceEnv). */
|
|
223
|
+
export function spaceEnvFor(profile , spaceHome , spaceName ) {
|
|
224
|
+
return expandSpaceEnv(profile.spaceEnv ?? {}, spaceHome, spaceName);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* The profile this Pi runs under, from the config file: the one whose home is the base home
|
|
229
|
+
* (PI_SPACES_BASE_HOME, else the agent home). An unusable file never stops Pi: it comes back as
|
|
230
|
+
* a warning and the result is the same as with no file.
|
|
231
|
+
*/
|
|
232
|
+
export function activeProfile(
|
|
233
|
+
env ,
|
|
234
|
+
home ,
|
|
235
|
+
agentHome ,
|
|
236
|
+
read ,
|
|
237
|
+
) {
|
|
238
|
+
const baseHome = env.PI_SPACES_BASE_HOME || agentHome;
|
|
239
|
+
let config ;
|
|
240
|
+
let warning ;
|
|
241
|
+
try {
|
|
242
|
+
config = loadConfig(configPath(env, home), read);
|
|
243
|
+
} catch (error) {
|
|
244
|
+
if (!(error instanceof ConfigError)) throw error;
|
|
245
|
+
warning = error.message;
|
|
246
|
+
}
|
|
247
|
+
return { profile: profileForHome(config, baseHome, env, home), warning };
|
|
248
|
+
}
|
package/dist/context.js
CHANGED
|
@@ -20,7 +20,7 @@ export function spaceInfo(dir , agentHome , env ) {
|
|
|
20
20
|
return {
|
|
21
21
|
space: activeSpace(dir, agentHome),
|
|
22
22
|
home: resolve(agentHome),
|
|
23
|
-
// A blank value counts as unset, so the default is pi
|
|
23
|
+
// A blank value counts as unset, so the default is pi (pi-space leaves it unset for a profile without a command).
|
|
24
24
|
command: env.PI_SPACES_COMMAND?.trim() || "pi",
|
|
25
25
|
};
|
|
26
26
|
}
|
package/dist/relaunch.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// running the same Pi command again with PI_CODING_AGENT_DIR pointing at the space.
|
|
3
3
|
import { spawnSync } from "node:child_process";
|
|
4
4
|
import { delimiter, join, resolve, sep } from "node:path";
|
|
5
|
+
import { expandSpaceEnv } from "./config.js";
|
|
5
6
|
import { activeSpace, BASE, listSpaces, SpaceError, validateName } from "./spaces.js";
|
|
6
7
|
|
|
7
8
|
// Pi options that take a separate value, so the value is kept (or dropped) with them.
|
|
@@ -79,14 +80,18 @@ export function resolveSwitch(input
|
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
// PWD and OLDPWD describe the working directory, which the relaunch keeps.
|
|
82
|
-
|
|
83
|
+
// PI_SPACES_BASE_ENV holds the base's own values, which must come back exactly as they were.
|
|
84
|
+
const NOT_REWRITTEN = new Set(["PI_SPACES_DIR", "PI_SPACES_BASE_HOME", "PI_SPACES_BASE_ENV", "PWD", "OLDPWD"]);
|
|
83
85
|
|
|
84
86
|
/**
|
|
85
87
|
* The environment for the relaunched Pi. Every variable whose value is the current home,
|
|
86
88
|
* or a path inside it, moves to the same path in the target home, so variables a host
|
|
87
89
|
* set from the home stay consistent without this package knowing the host.
|
|
90
|
+
* With the profile's spaceEnv templates, those variables are then set for a space with `{home}` and `{name}`
|
|
91
|
+
* filled in, recomputed for each space. The base's own values of them are kept in PI_SPACES_BASE_ENV on the way
|
|
92
|
+
* out and put back on the way back, so the base home runs exactly as it was started.
|
|
88
93
|
*/
|
|
89
|
-
export function relaunchEnv(env , target
|
|
94
|
+
export function relaunchEnv(env , target ) {
|
|
90
95
|
const from = resolve(target.currentHome);
|
|
91
96
|
const next = {};
|
|
92
97
|
for (const [key, value] of Object.entries(env)) {
|
|
@@ -99,9 +104,37 @@ export function relaunchEnv(env , target
|
|
|
99
104
|
next.PI_SPACES_BASE_HOME = target.baseHome;
|
|
100
105
|
if (target.space === undefined) delete next.PI_SPACE;
|
|
101
106
|
else next.PI_SPACE = target.space;
|
|
107
|
+
if (target.spaceEnv && Object.keys(target.spaceEnv).length > 0) {
|
|
108
|
+
const saved = baseValues(env, target);
|
|
109
|
+
for (const key of Object.keys(target.spaceEnv)) delete next[key];
|
|
110
|
+
if (target.space !== undefined) {
|
|
111
|
+
Object.assign(next, expandSpaceEnv(target.spaceEnv, target.targetHome, target.space));
|
|
112
|
+
next.PI_SPACES_BASE_ENV = JSON.stringify(saved);
|
|
113
|
+
} else {
|
|
114
|
+
Object.assign(next, saved);
|
|
115
|
+
delete next.PI_SPACES_BASE_ENV;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
102
118
|
return next;
|
|
103
119
|
}
|
|
104
120
|
|
|
121
|
+
/**
|
|
122
|
+
* The base home's own values of the spaceEnv variables: read from the environment when leaving the base home,
|
|
123
|
+
* else the ones kept in PI_SPACES_BASE_ENV (none when it is missing or unreadable).
|
|
124
|
+
*/
|
|
125
|
+
function baseValues(env , target ) {
|
|
126
|
+
const keys = Object.keys(target.spaceEnv ?? {});
|
|
127
|
+
if (resolve(target.currentHome) === resolve(target.baseHome)) {
|
|
128
|
+
return Object.fromEntries(keys.flatMap((key) => (env[key] === undefined ? [] : [[key, env[key] ]])));
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
const kept = JSON.parse(env.PI_SPACES_BASE_ENV ?? "{}");
|
|
132
|
+
return Object.fromEntries(keys.flatMap((key) => (typeof kept?.[key] === "string" ? [[key, kept[key]]] : [])));
|
|
133
|
+
} catch {
|
|
134
|
+
return {};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
105
138
|
|
|
106
139
|
|
|
107
140
|
|
|
@@ -164,9 +197,10 @@ const PACKAGE_COMMANDS = new Set(["install", "remove", "uninstall", "update", "l
|
|
|
164
197
|
* space; otherwise run Pi again in the space with every other argument, since nothing has run yet.
|
|
165
198
|
* `pi --space <name> <package command> ...` always runs Pi again, even in the current home, because
|
|
166
199
|
* Pi runs a package command (install, remove, uninstall, update, list) only as its first argument.
|
|
200
|
+
* `spaceEnv` is the matched profile's variable templates, applied by relaunchEnv.
|
|
167
201
|
*/
|
|
168
|
-
export function planStartup(input
|
|
169
|
-
const { proc, dir, currentHome, env } = input;
|
|
202
|
+
export function planStartup(input ) {
|
|
203
|
+
const { proc, dir, currentHome, env, spaceEnv } = input;
|
|
170
204
|
const parsed = parseSpaceFlag(proc.argv.slice(2));
|
|
171
205
|
if ("error" in parsed) return { kind: "error", message: parsed.error };
|
|
172
206
|
if (parsed.name === undefined) return { kind: "none" };
|
|
@@ -183,7 +217,7 @@ export function planStartup(input
|
|
|
183
217
|
}
|
|
184
218
|
return { kind: "error", message: error.message };
|
|
185
219
|
}
|
|
186
|
-
return { kind: "relaunch", command: relaunchCommand(proc, args), env: relaunchEnv(env, { ...target, currentHome }) };
|
|
220
|
+
return { kind: "relaunch", command: relaunchCommand(proc, args), env: relaunchEnv(env, { ...target, currentHome, spaceEnv }) };
|
|
187
221
|
}
|
|
188
222
|
|
|
189
223
|
/** Runs the command now with inherited stdio and returns its exit code (1 when it cannot start). */
|