@phreshos/cli 0.1.41 → 0.1.43

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 CHANGED
@@ -1,448 +1,101 @@
1
- # @phreshos/cli
1
+ # `@phreshos/cli`
2
2
 
3
- The `phresh` command for creating and operating Programs, and for installing
4
- and managing the PhreshOS System on the current machine.
3
+ The `phresh` command for creating and operating Programs and managing the
4
+ PhreshOS System on the current machine.
5
5
 
6
- ## Package status
6
+ The CLI uses the same public System interface as the Node and Server SDKs. It
7
+ adds command parsing, terminal presentation, project workflows, packaging, and
8
+ native service management.
7
9
 
8
- This package is one component of a larger architecture that is still under
9
- active testing. The architecture's components will be released in stages as
10
- their contracts and integrations are verified.
10
+ ## Installation
11
11
 
12
- `@phreshos/cli` is not intended to be used independently of that architecture.
13
- Its Program commands use `@phreshos/node`, whose `Project` model owns local
14
- project behavior and whose `System.connect()` provides the same Core contract
15
- used inside Server Programs. Runtime operations require a compatible System.
12
+ | Package manager | Command |
13
+ | --- | --- |
14
+ | npm | `npm install --global @phreshos/cli` |
15
+ | pnpm | `pnpm add --global @phreshos/cli` |
16
+ | Bun | `bun add --global @phreshos/cli` |
17
+ | Yarn Classic | `yarn global add @phreshos/cli` |
16
18
 
17
- ```bash
18
- phresh create app # create a complete new Program
19
- phresh init # describe this program, once
20
- phresh pack # run the optional build, then package its result
21
- phresh install # lay this program out on this machine
22
- phresh uninstall # remove its installed form
23
- phresh start # run what your build left, and stay with it
24
- phresh dev # run from source, and stay with it
25
- phresh system status # inspect the local System and its background service
26
- phresh program list # inspect authoritative state in the running System
27
- phresh describe endpoint ask # read one capability's exact contract
28
- ```
19
+ Node.js 20.10 or newer is required.
20
+
21
+ ## Program projects
29
22
 
30
- `phresh --help` lists them, `phresh <command> --help` explains one, and
31
- `phresh --version` says which CLI you have. `phresh describe [path...]` exposes
32
- the same complete command tree as machine-readable contracts. Nothing is guessed: an
33
- unknown command, an unknown flag and a malformed option are each refused
34
- and named.
23
+ ```sh
24
+ phresh create
25
+ phresh init
26
+ phresh dev
27
+ phresh start
28
+ phresh install
29
+ phresh uninstall
30
+ phresh pack
31
+ ```
35
32
 
36
- Program authoring commands act on the current project when no Program name is
37
- provided. `install` and `uninstall` accept a direct Program name. Running-System
38
- capabilities live in the top-level `program`, `process`, `endpoint`, and `window`
39
- namespaces. System execution and native lifecycle remain isolated under
40
- `phresh system`.
33
+ `create` produces the official starter Program. The remaining commands operate
34
+ on the current Program project, derive its concrete definition, and delegate
35
+ runtime operations to the connected System.
41
36
 
42
- ## System lifecycle
37
+ ## System
43
38
 
44
- ```bash
39
+ ```sh
45
40
  phresh system install
46
- phresh system uninstall
47
41
  phresh system status
48
- phresh system version
49
42
  phresh system start
50
43
  phresh system stop
51
44
  phresh system enable
52
45
  phresh system disable
46
+ phresh system uninstall
53
47
  ```
54
48
 
55
- `install` resolves the newest compatible stable release from the official
56
- [`PhreshOS/system`](https://github.com/PhreshOS/system) GitHub Releases. It
57
- downloads the production archive and adjacent checksum, verifies every byte,
58
- installs production dependencies into a staged version directory, atomically
59
- points the stable `current` path at it, then registers, enables, and starts the
60
- native per-user service. The selected release and the service entry therefore
61
- cannot become two competing sources of truth if installation is interrupted.
62
- It never reads a source checkout and never requires Bun or TypeScript.
63
-
64
- The System runs under `launchd` on macOS, a real `systemd --user` manager on
65
- Linux, and a least-privilege per-user scheduled task on Windows. In Linux
66
- containers with no init manager, it runs as a detached user-owned background
67
- process that survives the terminal but ends with the container. Automatic
68
- startup is unavailable there rather than being reported as enabled. `start`
69
- and `stop` change current execution only; where a native manager exists,
70
- `enable` and `disable` change automatic startup only. Successful installation
71
- and startup show the desktop address. `status` reports that same address with
72
- the installed version, service readiness, and automatic startup without
73
- changing them; `version` reports only the installed System release.
74
-
75
- Installation files and persistent System state have separate homes. Removing
76
- the System unregisters its service and removes its release files while keeping
77
- `~/.phreshos`, including Programs and owner data. The owner-local gateway uses an
78
- owner-only socket file on POSIX and an owner-created duplex named pipe on
79
- Windows; neither becomes a network endpoint or introduces a bearer secret.
80
-
81
- ## create
82
-
83
- `phresh create <directory>` creates a complete Server and Client Program from
84
- the maintained `PhreshOS/phresh-program` repository. The CLI build downloads
85
- the newest complete stable release, validates its source identity and version,
86
- removes repository-only material, and bundles that exact authoring project.
87
- The resolved source digest is recorded with the bundle. The installed CLI
88
- therefore creates projects offline without reading a live branch or maintaining
89
- a release pin or second template by hand.
90
-
91
- The directory name becomes the stable kebab-case Program identity. In a
92
- terminal, `create` asks for the directory when it is omitted, the readable
93
- Program name, and the package manager. With no terminal, the directory is the
94
- first argument and every optional choice is named:
95
-
96
- ```bash
97
- phresh create status-board \
98
- --name "Status Board" \
99
- --package-manager npm
100
- ```
101
-
102
- Dependencies are installed by default. `--no-install` creates the same valid
103
- project and leaves installation as the first reported next step. Generated and
104
- initialized Programs always use the published package ranges embedded in the
105
- CLI; repository layout never changes dependency meaning.
106
-
107
- ## Saying something to a program you start
108
-
109
- ```bash
110
- phresh dev --run-option-path=/notes.md --run-option-line=42
111
- ```
112
-
113
- Read back by name, on either half:
114
-
115
- ```ts
116
- const path = await context.option("path")
117
- ```
118
-
119
- **Options are text, all of them.** An option must mean the same thing
120
- however the process was started, and a command line can only hand over
121
- text — a number made here would be a guess about your program's meaning
122
- by the one party with no way to know it. Is `--run-option-id=007` seven,
123
- or a string with two noughts in front? Only your program knows, so your
124
- program decides: `Number(...)`, once, where the meaning is.
125
-
126
- Which is what argv and the environment have always been, for the same
127
- reason. The prefix is long because these share a line with the tool's
128
- own flags, and a program wanting an option called `client` should not
129
- have to fight the CLI for the word.
130
-
131
- ## phresh.config is not a program's configuration
132
-
133
- A program's configuration is **derived** from it — three times, and the
134
- derivations differ only in where each half is said to be. That is the
135
- rule everything else here follows from.
136
-
137
- It follows that every field which lands in a `program.json` is spelled
138
- the way the contract spells it and crosses untouched: `size`, not a
139
- width and a height; `startCommand` or `entryFile`, not a generic command.
140
-
141
- An optional top-level `buildCommand` is authoring metadata. `phresh start`,
142
- `phresh install`, and `phresh pack` run it from this project before consuming
143
- the production files. It never crosses into `program.json` or the system.
144
- `phresh dev` uses the development declarations and does not build.
145
-
146
- Everything else is yours: where each half is left.
147
-
148
- ```ts
149
- import { defineConfig } from "@phreshos/core"
150
-
151
- export default defineConfig({
152
-
153
- identity: "file-manager", // kebab-case: the program's stable address
154
-
155
- name: "File Manager", // what a person reads
156
-
157
- version: "0.1.0",
158
-
159
- description: "A file manager",
160
-
161
- icon: "icon.png",
162
-
163
- categories: ["Utilities"],
164
-
165
- keywords: ["files", "storage"],
166
-
167
- website: "https://example.com/file-manager",
168
-
169
- buildCommand: "bun run build",
170
-
171
- server: {
172
-
173
- location: "build/server",
174
-
175
- installCommand: "npm ci",
176
-
177
- uninstallCommand: "npm run clean:external",
178
-
179
- startCommand: "node main.js",
180
-
181
- development: {
182
-
183
- startCommand: "tsx server/main.ts"
184
- }
185
- },
186
-
187
- client: {
188
-
189
- location: "dist",
190
-
191
- size: { width: "1/2", height: 440 },
192
-
193
- position: { x: 60, y: 40 },
194
-
195
- development: {
196
-
197
- url: "http://localhost:5173",
198
-
199
- startCommand: "bun run dev"
200
- }
201
- }
202
- })
203
- ```
204
-
205
- **`identity` identifies; `name` is read.** The identity is kebab-case
206
- because it is also the directory the system lays your program out in, so
207
- it is a path component before anything else. The name is free-form,
208
- identifies nothing, and absent means the identity serves for both.
209
-
210
- `identity`, `version` and `description` begin from your `package.json`
211
- during `init`; the readable `name` is asked for. They are written into
212
- the config rather than read from the manifest later. `pack` says so if
213
- the two versions have drifted apart.
214
-
215
- A window's `size` and `position` are finite pixel numbers or linear
216
- expressions. Fractions and percentages are equivalent relative terms, so
217
- `"1/2"` and `"50%"` mean the same thing; pixel offsets may be combined with
218
- them, as in `"50% + 10"`. Every value survives derivation unchanged.
219
-
220
- ## development — what `phresh dev` needs
221
-
222
- `phresh init` offers to configure development for each declared half. It uses
223
- the project's `dev` script as a suggested command when one exists, but records
224
- nothing unless the author chooses it. If development is left unconfigured,
225
- `phresh dev` refuses and names the declarations it needs:
226
-
227
- ```
228
- Nothing here says how this program is developed.
229
-
230
- Say how the server runs or where the client is served:
231
-
232
- server: { …, development: { startCommand: "tsx source/server/main.ts" } }
233
- client: { …, development: { url: "http://localhost:5173", startCommand: "bun run dev" } }
234
- ```
235
-
236
- Each half may carry a `development` block, but the two shapes are deliberately
237
- different. A Server and its development block each select exactly one of
238
- `startCommand` or `entryFile`. A command starts an isolated operating-system
239
- process tree; an entry module runs as a Worker owned by the System. In
240
- development, the directory containing `phresh.config.ts` becomes the derived
241
- Server location. A client block requires an HTTP(S) `url`; development clients
242
- are never resolved from filesystem paths.
243
-
244
- An `entryFile` is a path inside its Server location. Packaging copies it with
245
- the rest of that directory, and the System rejects a path that escapes those
246
- files. A Worker has no independent process working directory and is not a
247
- security boundary; resolve module-owned resources with `import.meta.url` and
248
- use the Server SDK for Program storage.
249
-
250
- The client development shape may also declare `startCommand`. `phresh dev`
251
- runs it from the project directory as a foreground development tool; the
252
- command is never derived into the Program sent to the system. The tool and
253
- the attached Program share one lifetime, so ending either ends the other.
254
-
255
- Before launching the Program, `phresh dev` waits up to 15 seconds for the client
256
- development URL to respond. While it remains unavailable, the URL is printed
257
- every two seconds. A command that exits first is reported immediately. This
258
- means the window is never deliberately opened onto a client that the authoring
259
- tool already knows is unavailable.
260
-
261
- ## init
262
-
263
- `phresh init` turns an existing package into a Program project. It reads the
264
- identity, version, and description from `package.json`, ensures the project has
265
- the matching `@phreshos/core` development dependency, and writes the typed
266
- `phresh.config.ts` authoring description.
267
-
268
- In a terminal, `init` asks for the production locations and Server execution
269
- modes needed by `start` and `install`, including whether a package build should
270
- prepare those locations. It then offers the development Server mode, Client
271
- command, and URL needed by `dev`.
272
- Existing `build` and `dev` package scripts become editable suggestions, never
273
- silent assumptions. A single Endpoint defaults to `dist`; when both Endpoints
274
- exist, their defaults are `dist/server` and `dist/client`. API documentation is
275
- opt-in and defaults to disabled even when a likely document already exists.
49
+ System installation acquires and verifies the official release archive and
50
+ configures the native per-user service. Starting and stopping control current
51
+ execution; enabling and disabling control automatic startup.
276
52
 
277
- Outside a terminal it never waits for input; the same values are supplied as
278
- named options:
53
+ ## Runtime
279
54
 
280
- ```bash
281
- phresh init --client \
282
- --client-location dist \
283
- --build-command "bun run build" \
284
- --client-development-url http://localhost:5173 \
285
- --client-development-start-command "bun run dev"
286
-
287
- phresh init --server \
288
- --server-location dist \
289
- --server-start-command "node main.js"
55
+ ```sh
56
+ phresh program list
57
+ phresh process list --program my-program
58
+ phresh endpoint inspect \
59
+ --program my-program \
60
+ --process main \
61
+ --endpoint server
62
+ phresh window inspect --program my-program --process main
290
63
  ```
291
64
 
292
- Use `phresh init --help` for the complete option list. An existing config is
293
- never replaced silently: a terminal asks, while automation must say `--force`.
294
-
295
- A Program must declare a Server endpoint, a Client endpoint, or both. Neither is
296
- refused during the interview rather than at the border, which is the
297
- earliest place it can be refused.
298
-
299
- The final `Next` line is derived from the resulting config. It always shows
300
- `phresh start` and `phresh install`; it shows `phresh dev` only when at least
301
- one Endpoint received a development declaration.
302
-
303
- Visual and advanced runtime defaults remain for the author to add deliberately:
304
- `icon`, `size`, `position`, `installCommand`, `start`, layers, and minimization
305
- are not guessed. An omitted `start` is `true`; only a default-off Endpoint needs to
306
- say `start: false`.
307
-
308
- ## start and dev
309
-
310
- Both **run your program without installing it, and stay attached.** They
311
- print the `program.json` it will be declared as, hand that to the system
312
- through the socket below the selected system home. With no override, that is
313
- `~/.phreshos/gateway.sock`. Set `PHRESHOS_HOME` to an absolute system home to
314
- address another system instance; the CLI derives
315
- `<PHRESHOS_HOME>/gateway.sock` from it. The socket is not selected
316
- separately from its instance. Only your account can open it, so nothing is
317
- sent to prove anything, and then the command holds.
318
-
319
- **The connection is the tether, in both directions.** Ctrl-C and your
320
- program stops. Close the window it opened and the command returns, with
321
- your program's own exit status as its own. Its `stdout` and `stderr`
322
- arrive in your terminal as well as its system log. The system always
323
- drains a server process; attachment adds the terminal as an audience
324
- rather than changing how the process starts.
325
-
326
- Nothing has to promise to clean up, which is the point — a promise would
327
- not survive `kill -9`, a closed terminal, or a dropped ssh session. All
328
- three end the command without running a line of it, and all three still
329
- close the socket, which is what the system is watching.
330
-
331
- **Attached means not installed; installed means persistent.** A program
332
- meant to outlive your terminal is installed rather than run.
333
-
334
- The run is registered as an ordinary uninstalled Program under the identity
335
- declared by this project. Before registration, the system ends and forgets any
336
- runtime Program already using that identity, whether it was installed or
337
- uninstalled. Forgetting never uninstalls: installed files and storage remain
338
- untouched while the attached Program becomes the sole runtime occupant. Its
339
- root process tethers the whole Program to this command; when it exits, remaining
340
- processes end and the runtime record disappears. A later `phresh install` can
341
- replace the preserved installed files and immediately register the identity as
342
- installed again. If no system is listening, the gateway says so plainly rather
343
- than exposing `ENOENT`.
344
-
345
- An attached Program still owns persistent project storage. The authoring tool
346
- declares `<project>/storage` explicitly, so `start` and `dev` keep the same
347
- database, store, data, cache, and logs as any other runtime form without
348
- inventing a path from the system's working directory. Installation changes
349
- where Program files are laid out; it does not change the logging contract.
350
-
351
- They are one derivation over one config, differing only in where each
352
- half is said to be:
65
+ The command hierarchy follows the runtime ownership hierarchy. Unknown
66
+ commands, flags, and malformed values reject rather than being guessed.
353
67
 
354
- | | locations from | derived form |
355
- |---|---|---|
356
- | `pack` | `location` | none — the system lays an installed program out |
357
- | `start` | `location` | absolute |
358
- | `dev` | project root for a declared server development block; `development.url` for a declared client block | server absolute, client URL |
359
-
360
- Every derived filesystem path is absolute because relative paths resolve
361
- against the `program.json` they were read from, and a derived one does not live
362
- beside your source. A client development URL remains the URL the author wrote.
363
-
364
- ## install
365
-
366
- **A program has two ways of being used: run it, or install it.**
367
-
368
- ```bash
369
- phresh install # this project, laid out on this machine
370
- phresh install flambo # the official Flambo Program
68
+ ```sh
69
+ phresh describe
70
+ phresh describe program
71
+ phresh describe process list
371
72
  ```
372
73
 
373
- Without a name, what is sent is
374
- the description this directory derives, and the system copies what it
375
- names into place — your program's parts are already on this disk at the
376
- locations it names, so there is nothing an archive would carry that the
377
- description does not already point at. `phresh pack` is for when you have
378
- somewhere to send a program; installing here is a different act.
379
-
380
- With a name, the CLI resolves and verifies that official Program's production
381
- release directly; the current directory is irrelevant.
382
-
383
- If `buildCommand` is declared, it completes successfully before anything is
384
- sent to the system. Without it, install uses the production files exactly as
385
- they stand.
74
+ `describe` exposes the command tree and exact options as machine-readable
75
+ contracts.
386
76
 
387
- Installing is the persistent one — laid out under `~/.phreshos/programs/<identity>`,
388
- marked installed, and reconstructed after a restart. Running is the other one:
389
- `phresh start` / `phresh dev` register it under its declared identity and
390
- attach its whole lifetime to your terminal.
77
+ ## Development
391
78
 
392
- The command installs through the machine's local gateway. A running
393
- Program may also install itself through the server SDK; installation is not a
394
- client capability.
395
-
396
- When a Server declares `installCommand`, `phresh install` writes that command's
397
- `stdout` and `stderr` chunks as the System emits them. The final installed
398
- confirmation appears only after the output stream completes successfully.
399
-
400
- That is also why it names **paths** rather than sending bytes: install
401
- used to want an upload because the installer was a browser, which has
402
- bytes and no path. You have the paths.
403
-
404
- ## uninstall
405
-
406
- ```bash
407
- phresh uninstall
408
- phresh uninstall flambo
409
- phresh uninstall --everything
79
+ ```sh
80
+ bun install --frozen-lockfile
81
+ bun run verify
410
82
  ```
411
83
 
412
- Ordinary uninstall removes the installed Program files while preserving its
413
- running Processes, stored data, and runtime Program. `--everything` explicitly
414
- ends those Processes, removes everything the system owns for the Program, and
415
- forgets its runtime record.
84
+ `verify` checks the scripts, builds the CLI and bundled starter, runs the
85
+ command tests, and validates the package artifact.
416
86
 
417
- When the installed Server declares `uninstallCommand`, the System runs it from
418
- that Server directory before removing files. `phresh uninstall` writes its
419
- ordered `stdout` and `stderr` chunks as they arrive. A failed cleanup command
420
- aborts removal and reports the failure.
87
+ See the [CLI documentation](https://github.com/PhreshOS/docs/blob/main/content/docs/sdks/cli.mdx)
88
+ for the command model.
421
89
 
422
- Without a name, the identity comes from this project's `phresh.config.ts`.
423
- With a name, the installed Program is addressed directly and the current
424
- directory is irrelevant.
90
+ ## Repository boundary
425
91
 
426
- ## What pack produces
92
+ This repository owns terminal interaction, project commands, packaging, System
93
+ acquisition, and host service integration. Node owns the external JavaScript
94
+ interface, Core owns shared contracts, and the System owns authoritative state.
427
95
 
428
- `pack` takes what is at each half's `location` and writes both `program.json`
429
- and `<identity>@<version>.zip`. The exact generated declaration is also stored
430
- inside the archive. Your program may leave its halves anywhere; the
431
- package always keeps them in the same places, so the artifact's shape
432
- belongs to the contract rather than to your project. That is why the
433
- `program.json` it writes names `server` and `client` explicitly — those
434
- are the canonical locations the package just created. An explicit
435
- `start: false` crosses with its half; an omitted value remains omitted
436
- and means `true`. At least one declared half must resolve to true.
96
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for the repository workflow and
97
+ [SECURITY.md](SECURITY.md) for private vulnerability reporting.
437
98
 
438
- There is **no wrapping directory**: `program.json`, `server/`, `client/`,
439
- optional `icon.png`, and optional `agent.md` sit at the package's root. The
440
- system names the directory it installs into from your program's `identity`.
441
- An authored agent document may use any project-relative path, while packaging
442
- normalizes it to `agent.md`.
99
+ ## License
443
100
 
444
- Nothing about the system moves because this exists. `program.json` is
445
- still the only declaration the system and release catalog read. Root
446
- `categories`, `keywords`, and `website` values are optional and cross into it
447
- without affecting execution. The file is generated output; `phresh.config.ts`
448
- remains the authored source.
101
+ Licensed under the [MIT License](LICENSE). Copyright © 2026 Zohayr SLILEH.
package/dist/cli.js CHANGED
@@ -12,6 +12,7 @@ import systemCommands from "./system/command.js";
12
12
  import controlCommands from "./control/command.js";
13
13
  import describeCommands from "./describe-command.js";
14
14
  import { commandContract } from "./command-contract.js";
15
+ import { blank, failure } from "./style.js";
15
16
  const metadata = createRequire(import.meta.url)("../package.json");
16
17
  const { version } = metadata;
17
18
  const coreRange = metadata.dependencies["@phreshos/core"];
@@ -96,13 +97,12 @@ describe(program.command("install")
96
97
  .description("install a local or official Program")
97
98
  .argument("[name]", "name of an official Program")
98
99
  .option("--run", "run the installed Program now")
99
- .option("--startup", "run the Program when the System starts")
100
100
  .action(async function (name, options) {
101
- await install({ name, run: options.run === true, startup: options.startup === true });
101
+ await install({ name, run: options.run === true });
102
102
  }), [
103
103
  "Without a name, builds and installs the Program declared by this project.",
104
104
  "A name installs its verified official production release. --run launches",
105
- "it now; --startup persists the same default launch for future starts."
105
+ "the installed Program now."
106
106
  ]);
107
107
  describe(program.command("uninstall")
108
108
  .description("uninstall a local or installed Program")
@@ -129,7 +129,7 @@ controlCommands(program);
129
129
  describeCommands(program);
130
130
  // Every command begins with the same breathing room. Keep this at the entry
131
131
  // point so individual commands never need to manufacture their own opening.
132
- console.log("");
132
+ blank();
133
133
  if (process.argv.length === 2)
134
134
  program.help();
135
135
  try {
@@ -141,7 +141,7 @@ catch (error) {
141
141
  else if (error instanceof ReportedFailure)
142
142
  process.exitCode = 1;
143
143
  else {
144
- console.error(`\n phresh: ${error instanceof Error ? error.message : String(error)}\n`);
144
+ failure(error instanceof Error ? error.message : String(error));
145
145
  process.exitCode = 1;
146
146
  }
147
147
  }
@@ -1,7 +1,7 @@
1
1
  import { Option } from "commander";
2
2
  import { commandContract } from "../command-contract.js";
3
- import { clientOverrideOptions, endpointOptions, outputOptions } from "./options.js";
4
- import { bounded, clientLaunch, connected, endpoint, endpointView, integer, output, payload, requireProcess, wait } from "./shared.js";
3
+ import { clientOverrideOptions, endpointOptions, outputOptions, serverOverrideOptions } from "./options.js";
4
+ import { bounded, clientLaunch, connected, endpoint, endpointView, integer, output, payload, requireProcess, serverLaunch, wait } from "./shared.js";
5
5
  export default function endpointCommands(root, connect) {
6
6
  const endpoints = commandContract(root.command("endpoint")
7
7
  .description("inspect, control, and communicate with Process Endpoints"));
@@ -10,16 +10,19 @@ export default function endpointCommands(root, connect) {
10
10
  .action(async (options) => withEndpoint(connect, options, async (process, name) => {
11
11
  output(await endpointView(process, name), options.compact);
12
12
  }));
13
- outputOptions(clientOverrideOptions(endpointOptions(endpoints.command("start")
14
- .description("start a fresh Endpoint incarnation"))))
13
+ outputOptions(serverOverrideOptions(clientOverrideOptions(endpointOptions(endpoints.command("start")
14
+ .description("start a fresh Endpoint incarnation")))))
15
15
  .action(async (options) => withEndpoint(connect, options, async (process, name) => {
16
- const overrides = clientLaunch(options);
17
- if (name === "server" && overrides !== undefined)
16
+ const client = clientLaunch(options);
17
+ const server = serverLaunch(options);
18
+ if (name === "server" && client !== undefined)
18
19
  throw new Error("Client overrides require --endpoint client");
20
+ if (name === "client" && server !== undefined)
21
+ throw new Error("Server overrides require --endpoint server");
19
22
  if (name === "client")
20
- await process.client.start(typeof overrides === "object" ? overrides : undefined);
23
+ await process.client.start(typeof client === "object" ? client : undefined);
21
24
  else
22
- await process.server.start();
25
+ await process.server.start(typeof server === "object" ? server : undefined);
23
26
  output(await endpointView(process, name), options.compact);
24
27
  }));
25
28
  outputOptions(endpointOptions(endpoints.command("stop")
@@ -19,6 +19,8 @@ export function clientOptions(command) {
19
19
  }
20
20
  export function clientOverrideOptions(command) {
21
21
  return command
22
+ .option("--client-service", "address this Client incarnation through system.service()")
23
+ .option("--no-client-service", "do not address this Client incarnation through system.service()")
22
24
  .option("--client-title <title>", "initial Window title")
23
25
  .option("--client-width <value>", "initial Window width")
24
26
  .option("--client-height <value>", "initial Window height")
@@ -28,8 +30,13 @@ export function clientOverrideOptions(command) {
28
30
  .option("--client-location <location>", "initial page beneath the Client location")
29
31
  .option("--client-minimized", "open the Window minimized");
30
32
  }
33
+ export function serverOverrideOptions(command) {
34
+ return command
35
+ .option("--server-service", "address this Server incarnation through system.service()")
36
+ .option("--no-server-service", "do not address this Server incarnation through system.service()");
37
+ }
31
38
  export function launchOptions(command) {
32
- return clientOptions(command)
39
+ return serverOverrideOptions(clientOptions(command))
33
40
  .option("--name <name>", "stable Program-local Process name")
34
41
  .option("--server", "start the Server Endpoint")
35
42
  .option("--no-server", "do not start the Server Endpoint")
@@ -1,4 +1,5 @@
1
1
  import { System } from "@phreshos/node";
2
+ import { blank } from "../style.js";
2
3
  export const connectSystem = () => System.connect();
3
4
  export async function connected(connect, action) {
4
5
  const system = await connect();
@@ -39,14 +40,19 @@ export async function programView(program) {
39
40
  };
40
41
  }
41
42
  export async function processView(process) {
42
- const [server, client] = await Promise.all([process.server.exists(), process.client.exists()]);
43
+ const [server, client, serverService, clientService] = await Promise.all([
44
+ process.server.exists(),
45
+ process.client.exists(),
46
+ process.server.isService(),
47
+ process.client.isService()
48
+ ]);
43
49
  return {
44
50
  identity: process.identity,
45
51
  name: process.name,
46
52
  program: process.program().identity,
47
53
  startedAt: process.startedAt.toISOString(),
48
- server: { declared: process.program().server !== null, running: server },
49
- client: { declared: process.program().client !== null, running: client }
54
+ server: { declared: process.program().server !== null, running: server, service: serverService },
55
+ client: { declared: process.program().client !== null, running: client, service: clientService }
50
56
  };
51
57
  }
52
58
  export async function endpointView(process, name) {
@@ -56,7 +62,8 @@ export async function endpointView(process, name) {
56
62
  program: program.identity,
57
63
  endpoint: name,
58
64
  declared: name === "server" ? program.server !== null : program.client !== null,
59
- running: await endpoint(process, name).exists()
65
+ running: await endpoint(process, name).exists(),
66
+ service: await endpoint(process, name).isService()
60
67
  };
61
68
  }
62
69
  export async function windowView(process) {
@@ -74,6 +81,7 @@ export async function windowView(process) {
74
81
  }
75
82
  export function output(value, compact) {
76
83
  console.log(JSON.stringify(value ?? null, null, compact ? undefined : 2));
84
+ blank();
77
85
  }
78
86
  export function payload(value) {
79
87
  if (value === undefined)
@@ -118,12 +126,13 @@ export function page(values, search, offset = 0, limit = 30, text) {
118
126
  }
119
127
  export function launch(options, named = false) {
120
128
  const client = clientLaunch(options);
129
+ const server = serverLaunch(options);
121
130
  const values = entries(options.option);
122
131
  const result = {};
123
132
  if (options.name !== undefined)
124
133
  result.name = options.name;
125
- if (options.server !== undefined)
126
- result.server = options.server;
134
+ if (server !== undefined)
135
+ result.server = server;
127
136
  if (client !== undefined)
128
137
  result.client = client;
129
138
  if (Object.keys(values).length)
@@ -133,6 +142,7 @@ export function launch(options, named = false) {
133
142
  return result;
134
143
  }
135
144
  export function clientLaunch(options) {
145
+ const service = options.clientService;
136
146
  const configured = options.clientTitle !== undefined
137
147
  || options.clientWidth !== undefined
138
148
  || options.clientHeight !== undefined
@@ -140,7 +150,8 @@ export function clientLaunch(options) {
140
150
  || options.clientY !== undefined
141
151
  || options.clientLayer !== undefined
142
152
  || options.clientLocation !== undefined
143
- || options.clientMinimized === true;
153
+ || options.clientMinimized === true
154
+ || service !== undefined;
144
155
  if (!configured)
145
156
  return options.client;
146
157
  if (options.client === false)
@@ -152,6 +163,7 @@ export function clientLaunch(options) {
152
163
  throw new Error("--client-x and --client-y must be supplied together");
153
164
  }
154
165
  return {
166
+ ...(service === undefined ? {} : { service }),
155
167
  ...(options.clientTitle === undefined ? {} : { title: options.clientTitle }),
156
168
  ...(options.clientWidth === undefined ? {} : { size: size(options.clientWidth, options.clientHeight) }),
157
169
  ...(options.clientX === undefined ? {} : { position: position(options.clientX, options.clientY) }),
@@ -160,6 +172,14 @@ export function clientLaunch(options) {
160
172
  ...(options.clientMinimized === true ? { minimize: true } : {})
161
173
  };
162
174
  }
175
+ export function serverLaunch(options) {
176
+ const service = options.serverService;
177
+ if (service === undefined)
178
+ return options.server;
179
+ if (options.server === false)
180
+ throw new Error("Server overrides cannot be combined with --no-server");
181
+ return { service };
182
+ }
163
183
  export function collect(value, previous = []) {
164
184
  return [...previous, value];
165
185
  }
package/dist/create.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { installProjectDependencies, projectPackageManager, projectScript } from "./project-dependency.js";
2
2
  import prompts from "./prompts.js";
3
- import { accent, bold } from "./style.js";
3
+ import { accent, blank, bold } from "./style.js";
4
4
  import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
5
5
  import { basename, dirname, extname, relative, resolve } from "node:path";
6
6
  /** Creates a complete Program from the bundled Phresh Program snapshot. */
@@ -60,7 +60,7 @@ export default async function create(options = {}, directory = process.cwd()) {
60
60
  console.log(bold(`\n${bundled.development ? "Run Development Program" : "Run Program"}`));
61
61
  console.log(accent(script));
62
62
  console.log(bold("\nYou can now open the project and start building your Program"));
63
- console.log("");
63
+ blank();
64
64
  }
65
65
  function template() {
66
66
  const candidates = [
@@ -1,4 +1,5 @@
1
1
  import { commandContract, readCommandContract } from "./command-contract.js";
2
+ import { blank } from "./style.js";
2
3
  /** Add one machine-readable description entry point for the complete CLI tree. */
3
4
  export default function describeCommands(program) {
4
5
  commandContract(program.command("describe")
@@ -9,6 +10,7 @@ export default function describeCommands(program) {
9
10
  const command = resolveCommand(program, path);
10
11
  const description = describe(command, path);
11
12
  console.log(JSON.stringify(description, null, options.compact ? undefined : 2));
13
+ blank();
12
14
  }), { guidance: ["Omit the path to discover all top-level commands, then describe progressively deeper paths."] });
13
15
  }
14
16
  function resolveCommand(root, path) {
package/dist/install.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Project } from "@phreshos/node";
2
- import { dim, heading, line } from "./style.js";
2
+ import { blank, dim, heading, line } from "./style.js";
3
3
  import installProgram, {} from "./program-installation.js";
4
4
  import { prepareOfficialProgram } from "./program-release.js";
5
5
  /**
@@ -15,10 +15,10 @@ import { prepareOfficialProgram } from "./program-release.js";
15
15
  * production Program is derived and sent. The command remains authoring
16
16
  * metadata and never becomes part of the installed Program.
17
17
  *
18
- * Installation remains distinct from execution unless `run` or `startup`
19
- * is explicitly requested. A run created here belongs to the installed
20
- * Program and therefore outlives this command; `phresh start` and `phresh
21
- * dev` remain attached authoring runs whose lifetime is the terminal's.
18
+ * Installation remains distinct from execution unless `run` is explicitly
19
+ * requested. A run created here belongs to the installed Program and
20
+ * therefore outlives this command; `phresh start` and `phresh dev` remain
21
+ * attached authoring runs whose lifetime is the terminal's.
22
22
  */
23
23
  export default async function install(options = {}) {
24
24
  const directory = options.directory ?? process.cwd();
@@ -32,11 +32,11 @@ export default async function install(options = {}) {
32
32
  const { name, identity, version } = result.program;
33
33
  heading(`${name || identity}${version ? ` ${version}` : ""}`, result.replaced ? "reinstalled" : "installed");
34
34
  if (result.replaced)
35
- console.log(` ${dim("its storage was kept, and its previous processes were ended")}\n`);
36
- if (result.startupEnabled)
37
- line("startup", "enabled");
35
+ console.log(` ${dim("its storage was kept, and its previous processes were ended")}`);
38
36
  if (result.process)
39
37
  line("process", result.process);
38
+ if (result.replaced || result.process)
39
+ blank();
40
40
  }
41
41
  return result;
42
42
  }
package/dist/launch.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Project } from "@phreshos/node";
2
2
  import { relative } from "node:path";
3
3
  import { assertAvailable, commandFailure, DevelopmentClient, waitForDevelopmentClient } from "./development-client.js";
4
- import { dim, heading, line } from "./style.js";
4
+ import { blank, dim, heading, line } from "./style.js";
5
5
  /** Run the current project through one connected System. */
6
6
  export default async function launch(mode, directory = process.cwd(), options = {}) {
7
7
  const project = await Project.open(directory);
@@ -16,7 +16,7 @@ export default async function launch(mode, directory = process.cwd(), options =
16
16
  line("storage", place(project.directory, String(definition.storage)));
17
17
  if (Object.keys(options).length)
18
18
  line("options", Object.entries(options).map(([name, value]) => `${name}=${value}`).join(" "));
19
- console.log("");
19
+ blank();
20
20
  const system = await (await import("@phreshos/node")).System.connect();
21
21
  const controller = new AbortController();
22
22
  const development = mode === "development" && definition.client && (definition.client.start ?? true)
@@ -70,7 +70,9 @@ export default async function launch(mode, directory = process.cwd(), options =
70
70
  process.exit(130);
71
71
  if (!ended)
72
72
  throw new Error("The System closed before the Program ended");
73
- console.log(`\n ${dim(ended.signal ? `ended on ${ended.signal}` : `ended with ${ended.code ?? 0}`)}\n`);
73
+ blank();
74
+ console.log(` ${dim(ended.signal ? `ended on ${ended.signal}` : `ended with ${ended.code ?? 0}`)}`);
75
+ blank();
74
76
  process.exit(ended.signal ? 128 : ended.code ?? 0);
75
77
  }
76
78
  async function consume(lifecycle, client) {
package/dist/pack.js CHANGED
@@ -1,11 +1,14 @@
1
1
  import { Project } from "@phreshos/node";
2
- import { line } from "./style.js";
2
+ import { blank, line } from "./style.js";
3
3
  /** Package the current Project and present only its command-facing progress. */
4
4
  export default async function pack(directory = process.cwd()) {
5
5
  const project = await Project.open(directory);
6
6
  if (project.config.buildCommand)
7
7
  line("build", project.config.buildCommand);
8
8
  const packed = await project.pack();
9
- console.log(`\nPacked ${packed.archive}`);
9
+ if (project.config.buildCommand)
10
+ blank();
11
+ console.log(`Packed ${packed.archive}`);
12
+ blank();
10
13
  return packed.archive;
11
14
  }
@@ -7,7 +7,6 @@ export default async function installProgram(program, options = {}) {
7
7
  const current = await system.program.find(identity);
8
8
  const replaced = await current?.installed() ?? false;
9
9
  let installed = null;
10
- let startupEnabled = false;
11
10
  let process = null;
12
11
  let installationFinished = false;
13
12
  try {
@@ -20,10 +19,6 @@ export default async function installProgram(program, options = {}) {
20
19
  for await (const chunk of installed.install())
21
20
  writeProgramCommandOutput(chunk);
22
21
  installationFinished = true;
23
- if (options.startup) {
24
- await installed.startup.enable();
25
- startupEnabled = true;
26
- }
27
22
  if (options.run)
28
23
  process = (await installed.process.create()).identity;
29
24
  }
@@ -41,14 +36,11 @@ export default async function installProgram(program, options = {}) {
41
36
  }
42
37
  if (!installed)
43
38
  throw new Error("The System ended Program installation without confirming it");
44
- if (options.startup && !startupEnabled)
45
- throw new Error("The System installed the Program without confirming startup");
46
39
  if (options.run && !process)
47
40
  throw new Error("The System installed the Program without confirming that it is running");
48
41
  return {
49
42
  program: { identity: installed.identity, name: installed.name, version: installed.version },
50
43
  replaced,
51
- startupEnabled,
52
44
  process
53
45
  };
54
46
  }
@@ -1,4 +1,4 @@
1
- import { line } from "./style.js";
1
+ import { blank, line } from "./style.js";
2
2
  import { spawn } from "node:child_process";
3
3
  import { existsSync, readFileSync } from "node:fs";
4
4
  import { resolve } from "node:path";
@@ -33,7 +33,7 @@ export default async function ensureProjectDependency(name, range, directory = p
33
33
  const manager = projectPackageManager(directory, manifest.packageManager);
34
34
  line("dependency", name, `${manager.name}, ${range}`);
35
35
  await run(manager.name, [...manager.addArgs(section), `${name}@${range}`], directory);
36
- console.log("");
36
+ blank();
37
37
  }
38
38
  function run(command, args, directory, output = "inherit") {
39
39
  return new Promise(function (settle, refuse) {
package/dist/prompts.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { cancel, confirm, intro, isCancel, log, outro, select, spinner, text } from "@clack/prompts";
2
2
  import colors from "picocolors";
3
- import { caution, column, ending, line, section } from "./style.js";
3
+ import { blank, caution, column, ending, line, section } from "./style.js";
4
4
  /** Signals an ordinary interactive cancellation rather than an operation failure. */
5
5
  export class PromptCancelled extends Error {
6
6
  }
@@ -36,8 +36,10 @@ export default function prompts() {
36
36
  function message(value = "") {
37
37
  if (interactive)
38
38
  log.message(value, { spacing: 0 });
39
+ else if (value)
40
+ console.log(` ${value}`);
39
41
  else
40
- console.log(value ? ` ${value}` : "");
42
+ blank();
41
43
  }
42
44
  function warning(value) {
43
45
  if (interactive)
package/dist/style.js CHANGED
@@ -6,6 +6,10 @@ export const accent = colors.cyan;
6
6
  export const positive = colors.green;
7
7
  export const caution = colors.yellow;
8
8
  export const negative = colors.red;
9
+ /** Writes one intentional blank line between terminal report blocks. */
10
+ export function blank() {
11
+ console.log("");
12
+ }
9
13
  // A label, what it says, and where that came from. The label is quiet
10
14
  // and the value is not, because the value is the thing being reported.
11
15
  //
@@ -20,12 +24,17 @@ export function column(label) {
20
24
  }
21
25
  export function heading(title, note) {
22
26
  section(title, note);
23
- console.log("");
27
+ blank();
24
28
  }
25
29
  export function section(title, note) {
26
30
  console.log(` ${bold(title)}${note ? ` ${dim("·")} ${dim(note)}` : ""}`);
27
31
  }
28
32
  export function ending(message) {
29
33
  console.log(` ${bold(message)}`);
30
- console.log("");
34
+ blank();
35
+ }
36
+ /** Writes one consistently indented command failure followed by closing space. */
37
+ export function failure(message) {
38
+ console.error(` phresh: ${message}`);
39
+ console.error("");
31
40
  }
@@ -201,7 +201,7 @@ export default class SystemLifecycle {
201
201
  }
202
202
  }
203
203
  async function provisionSetup() {
204
- await installProgram({ name: "setup", run: true, startup: true, announce: false });
204
+ await installProgram({ name: "setup", run: true, announce: false });
205
205
  }
206
206
  function definition(installation, executable) {
207
207
  return {
@@ -1,16 +1,23 @@
1
1
  # Phresh Program
2
2
 
3
- The minimal PhreshOS starter Program. Its Client talks directly to its Server;
4
- there is no MVC structure and no registered service.
3
+ The official minimal starter Program generated by `phresh create`.
5
4
 
6
- ```bash
7
- bun install
8
- bun run dev
5
+ It demonstrates one Client and one Server communicating directly through the
6
+ standard PhreshOS Endpoint contracts.
7
+
8
+ ## Create a Program
9
+
10
+ ```sh
11
+ phresh create
9
12
  ```
10
13
 
11
- The complete Program is intentionally small:
14
+ The CLI bundles a verified release of this repository and creates the new
15
+ project without requiring a live template checkout.
16
+
17
+ ## Structure
12
18
 
13
19
  ```text
20
+ phresh.config.ts
14
21
  client/
15
22
  ├── app.tsx
16
23
  ├── index.html
@@ -18,8 +25,42 @@ client/
18
25
  └── style.css
19
26
  server/
20
27
  └── main.ts
28
+ scripts/
29
+ └── build.ts
30
+ ```
31
+
32
+ The Server owns the counter. The Client renders it and communicates with its
33
+ paired Server. The example deliberately has no additional MVC hierarchy and no
34
+ registered Service.
35
+
36
+ ## Development
37
+
38
+ ```sh
39
+ bun install --frozen-lockfile
40
+ bun run verify
41
+ bun run dev
21
42
  ```
22
43
 
23
- `app.tsx` owns the small React component. The two `main` files are direct
24
- endpoint entries: the Client renders the app and the Server owns the counter.
25
- Nothing registers or exposes a named service.
44
+ Build, attach the production definition, or package the Program with:
45
+
46
+ ```sh
47
+ bun run build
48
+ bun run start
49
+ bun run pack
50
+ ```
51
+
52
+ `verify` checks the source, builds both Endpoints, and validates the packaged
53
+ Program shape.
54
+
55
+ ## Repository boundary
56
+
57
+ This repository owns the starter source distributed by the CLI. It remains a
58
+ complete ordinary Program rather than a second template model or generator-only
59
+ fixture.
60
+
61
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for the repository workflow and
62
+ [SECURITY.md](SECURITY.md) for private vulnerability reporting.
63
+
64
+ ## License
65
+
66
+ Licensed under the [MIT License](LICENSE). Copyright © 2026 Zohayr SLILEH.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "phresh",
3
3
  "private": true,
4
- "version": "0.1.23",
4
+ "version": "0.1.25",
5
5
  "description": "The official PhreshOS starter Program.",
6
6
  "type": "module",
7
7
  "scripts": {
@@ -14,15 +14,15 @@
14
14
  "starter"
15
15
  ],
16
16
  "dependencies": {
17
- "@phreshos/client": "^0.1.26",
18
- "@phreshos/core": "^0.1.26",
19
- "@phreshos/react": "^0.1.14",
20
- "@phreshos/server": "^0.1.27",
17
+ "@phreshos/client": "^0.1.29",
18
+ "@phreshos/core": "^0.1.31",
19
+ "@phreshos/react": "^0.1.17",
20
+ "@phreshos/server": "^0.1.32",
21
21
  "react": "^19.2.8",
22
22
  "react-dom": "^19.2.8"
23
23
  },
24
24
  "devDependencies": {
25
- "@phreshos/cli": "^0.1.41",
25
+ "@phreshos/cli": "^0.1.43",
26
26
  "@types/node": "^26.2.0",
27
27
  "@types/react": "^19.2.18",
28
28
  "@types/react-dom": "^19.2.4",
@@ -4,7 +4,7 @@ export default defineConfig({
4
4
  identity: "phresh",
5
5
  name: "Phresh Program",
6
6
  description: "A minimal counter with direct Client and Server endpoints.",
7
- version: "0.1.23",
7
+ version: "0.1.25",
8
8
  icon: "icon.png",
9
9
  categories: ["Development"],
10
10
  keywords: ["example", "counter", "client", "server"],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "repository": "PhreshOS/phresh-program",
3
- "version": "0.1.23",
4
- "sha256": "055a84726198072f481b14bd2aabd0f155982238914aded7808008c4197c123f",
3
+ "version": "0.1.25",
4
+ "sha256": "fa1d6e82cda7b129f08f0ca8d86d1bef1ff543b73e5926342ba5054054211d3e",
5
5
  "development": true
6
6
  }
package/dist/uninstall.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { readConfig } from "./project.js";
2
- import { dim, heading } from "./style.js";
2
+ import { blank, dim, heading } from "./style.js";
3
3
  import writeProgramCommandOutput from "./program-command-output.js";
4
4
  /** Uninstall an installed Program by name or by the current project's identity. */
5
5
  export default async function uninstall(options = {}) {
@@ -15,8 +15,9 @@ export default async function uninstall(options = {}) {
15
15
  writeProgramCommandOutput(chunk);
16
16
  heading(identity, "uninstalled");
17
17
  console.log(options.everything
18
- ? ` ${dim("Its processes, installed files, stored data, and runtime record were removed.")}\n`
19
- : ` ${dim("Its installed files were removed. Processes, stored data, and runtime state were kept.")}\n`);
18
+ ? ` ${dim("Its processes, installed files, stored data, and runtime record were removed.")}`
19
+ : ` ${dim("Its installed files were removed. Processes, stored data, and runtime state were kept.")}`);
20
+ blank();
20
21
  }
21
22
  finally {
22
23
  await system.disconnect();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@phreshos/cli",
3
3
  "type": "module",
4
- "version": "0.1.41",
4
+ "version": "0.1.43",
5
5
  "description": "The Phresh command-line interface for Program projects and system management.",
6
6
  "engines": {
7
7
  "node": ">=20.10"
@@ -49,8 +49,8 @@
49
49
  "packageManager": "bun@1.3.14",
50
50
  "dependencies": {
51
51
  "@clack/prompts": "^1.7.0",
52
- "@phreshos/core": "^0.1.27",
53
- "@phreshos/node": "^0.1.6",
52
+ "@phreshos/core": "^0.1.32",
53
+ "@phreshos/node": "^0.1.10",
54
54
  "adm-zip": "^0.6.0",
55
55
  "commander": "^15.0.0",
56
56
  "picocolors": "^1.1.1"