@phreshos/cli 0.1.56 → 0.1.57

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.
@@ -1,3 +1,4 @@
1
+ import { parseLaunch } from "@phreshos/core";
1
2
  export function payload(value) {
2
3
  if (value === undefined)
3
4
  return undefined;
@@ -46,6 +47,8 @@ export function launch(options, named = false) {
46
47
  const result = {};
47
48
  if (options.name !== undefined)
48
49
  result.name = options.name;
50
+ if (options.replace !== undefined)
51
+ result.replace = options.replace;
49
52
  if (server !== undefined)
50
53
  result.server = server;
51
54
  if (client !== undefined)
@@ -54,7 +57,7 @@ export function launch(options, named = false) {
54
57
  result.options = values;
55
58
  if (named && !result.name)
56
59
  throw new Error("--name is required");
57
- return result;
60
+ return parseLaunch(result);
58
61
  }
59
62
  export function clientLaunch(options) {
60
63
  const service = options.clientService;
@@ -1,3 +1,4 @@
1
+ import { layers } from "@phreshos/core";
1
2
  import { collect, integer } from "./input.js";
2
3
  export const jsonOption = option("--json", "write the result as machine-readable JSON on one line");
3
4
  export const processOptions = Object.freeze([
@@ -16,7 +17,7 @@ export const clientOverrideOptions = Object.freeze([
16
17
  option("--client-height <value>", "initial Window height"),
17
18
  option("--client-x <value>", "initial Window horizontal position"),
18
19
  option("--client-y <value>", "initial Window vertical position"),
19
- option("--client-layer <layer>", "initial Window layer", { choices: ["window", "under", "over"] }),
20
+ option("--client-layer <layer>", "initial Window layer", { choices: layers }),
20
21
  option("--client-minimized", "open the Window minimized"),
21
22
  option("--client-maximized", "open the Window maximized")
22
23
  ]);
@@ -30,6 +31,7 @@ export const launchOptions = Object.freeze([
30
31
  option("--no-client", "do not start the Client Endpoint"),
31
32
  ...clientOverrideOptions,
32
33
  option("--name <name>", "stable Program-local Process name"),
34
+ option("--replace", "terminate and replace the Process with --name"),
33
35
  option("--server", "start the Server Endpoint"),
34
36
  option("--no-server", "do not start the Server Endpoint"),
35
37
  option("--option <name=value>", "immutable Process option; repeat for more values", {
@@ -80,9 +80,9 @@ export default function programCommands(root, connect) {
80
80
  async function eventView(event, message, scoped) {
81
81
  if (event === "uninstall") {
82
82
  if (scoped)
83
- return { program: await programView(scoped), everything: message === true };
83
+ return { program: await programView(scoped), purge: message === true };
84
84
  const current = message;
85
- return { program: await programView(current.program), everything: current.everything };
85
+ return { program: await programView(current.program), purge: current.purge };
86
86
  }
87
87
  if (scoped)
88
88
  return programView(scoped);
@@ -85,7 +85,7 @@ export default function projectCommands(program, coreRange) {
85
85
  name: "install",
86
86
  description: "install a local or official Program",
87
87
  arguments: [{ syntax: "[name]", description: "name of an official Program" }],
88
- options: [option("--run", "run the installed Program now")],
88
+ options: [option("--run", "launch the installed Program before startup"), option("--purge", "delete existing installed Program storage")],
89
89
  guidance: [
90
90
  "Without a name, builds and installs the Program declared by this project.",
91
91
  "A name installs its verified official production release. --run launches the installed Program now."
@@ -94,22 +94,22 @@ export default function projectCommands(program, coreRange) {
94
94
  examples: ["phresh install", "phresh install terminal --run"],
95
95
  output: textOutput("Installed Program and optional Process identity")
96
96
  }, async ({ arguments: [name], options }) => {
97
- await install({ name, run: options.run === true });
97
+ await install({ name, run: options.run === true, purge: options.purge === true });
98
98
  });
99
99
  defineCommand(program, {
100
100
  name: "uninstall",
101
101
  description: "uninstall a local or installed Program",
102
102
  arguments: [{ syntax: "[name]", description: "name of an installed Program" }],
103
- options: [option("--everything", "also remove Processes, data, and runtime state")],
103
+ options: [option("--purge", "also remove Processes, data, and runtime state")],
104
104
  guidance: [
105
105
  "Without a name, uses the Program declared by this project.",
106
- "Removes installed files while preserving Processes, data, and runtime Program state unless --everything is supplied."
106
+ "Removes installed files while preserving Processes, data, and runtime Program state unless --purge is supplied."
107
107
  ],
108
108
  requiresSystem: true,
109
- examples: ["phresh uninstall", "phresh uninstall terminal --everything"],
109
+ examples: ["phresh uninstall", "phresh uninstall terminal --purge"],
110
110
  output: textOutput("Uninstalled Program state")
111
111
  }, async ({ arguments: [name], options }) => {
112
- await uninstall({ name, everything: options.everything === true });
112
+ await uninstall({ name, purge: options.purge === true });
113
113
  });
114
114
  attached(program, "start", "run the production Program without installing", [
115
115
  "Runs this Program attached to the terminal. Output arrives here, and ending this command stops the Program.",
@@ -1,3 +1,4 @@
1
+ import { layers } from "@phreshos/core";
1
2
  import { value } from "../contract/schema.js";
2
3
  const metric = {
3
4
  anyOf: [value.number("pixels"), value.string("workspace-relative expression")]
@@ -12,7 +13,7 @@ const clientDeclaration = value.nullable(value.object({
12
13
  title: value.nullable(value.string("default Window title")),
13
14
  size: value.nullable(value.object({ width: metric, height: metric }, ["width", "height"], "default Window size")),
14
15
  position: value.nullable(value.object({ x: metric, y: metric }, ["x", "y"], "default Window position")),
15
- layer: value.nullable(value.enumeration(["window", "under", "over"], "default Window layer")),
16
+ layer: value.nullable(value.enumeration(layers, "default Window layer")),
16
17
  minimize: value.nullable(value.boolean("default minimized state")),
17
18
  maximize: value.nullable(value.boolean("default maximized state"))
18
19
  }, ["start", "service", "title", "size", "position", "layer", "minimize", "maximize"], "resolved Client Endpoint declaration"));
@@ -51,7 +52,7 @@ export const windowOutput = value.object({
51
52
  minimized: value.boolean("whether the Window is minimized"),
52
53
  maximized: value.boolean("whether the Window is maximized"),
53
54
  front: value.boolean("whether the Window is at the front of its layer"),
54
- layer: value.enumeration(["window", "under", "over"], "Window layer")
55
+ layer: value.enumeration(layers, "Window layer")
55
56
  }, ["process", "title", "position", "size", "minimized", "maximized", "front", "layer"], "Window state");
56
57
  export const programPresentation = fields(["Identity", "identity"], ["Asset", "assetId"], ["Name", "name"], ["Version", "version"], ["Description", "description"], ["Installed", "installed"], ["Agent", "hasAgent"], ["Server", "server"], ["Client", "client"]);
57
58
  export const programListPresentation = table("data", "Program", "Programs", "No matching Programs", [
package/dist/install.js CHANGED
@@ -15,8 +15,8 @@ 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
- * The System applies the Program's startup declaration during installation.
19
- * `run` explicitly requests an additional Process. A run created here belongs
18
+ * The System executes the Program's stored startup after installation.
19
+ * `run` requests an additional Process before startup, created by System. It belongs
20
20
  * to the installed Program and
21
21
  * therefore outlives this command; `phresh start` and `phresh dev` remain
22
22
  * attached authoring runs whose lifetime is the terminal's.
@@ -33,7 +33,7 @@ export default async function install(options = {}) {
33
33
  const { name, identity, version } = result.program;
34
34
  heading(`${name || identity}${version ? ` ${version}` : ""}`, result.replaced ? "reinstalled" : "installed");
35
35
  if (result.replaced)
36
- console.log(` ${dim("its storage was kept, and its previous processes were ended")}`);
36
+ console.log(` ${dim(options.purge ? "its storage was deleted, and its previous processes were ended" : "its storage was kept, and its previous processes were ended")}`);
37
37
  if (result.process)
38
38
  line("process", result.process);
39
39
  if (result.replaced || result.process)
@@ -9,6 +9,7 @@ export default async function installProgram(program, options = {}) {
9
9
  let installed = null;
10
10
  let process = null;
11
11
  let installationFinished = false;
12
+ let stopObserving;
12
13
  try {
13
14
  if (program instanceof Project) {
14
15
  await program.build();
@@ -16,22 +17,24 @@ export default async function installProgram(program, options = {}) {
16
17
  }
17
18
  else
18
19
  installed = await system.program.forceCreate(program);
19
- for await (const chunk of installed.install())
20
+ if (options.run)
21
+ stopObserving = installed.process.subscribe("create", created => { process ??= created.identity; });
22
+ for await (const chunk of installed.install({ launch: options.run ? true : undefined, purge: options.purge }))
20
23
  writeProgramCommandOutput(chunk);
21
24
  installationFinished = true;
22
- if (options.run)
23
- process = (await installed.process.create()).identity;
24
25
  }
25
26
  catch (error) {
26
27
  if (installed && !installationFinished) {
27
28
  try {
28
- await installed.forget();
29
+ if (!await installed.installed())
30
+ await installed.forget();
29
31
  }
30
32
  catch { /* Preserve the installation failure. */ }
31
33
  }
32
34
  throw error;
33
35
  }
34
36
  finally {
37
+ stopObserving?.();
35
38
  await system.disconnect();
36
39
  }
37
40
  if (!installed)
@@ -3,6 +3,7 @@ import prompts, { ReportedFailure } from "../prompts.js";
3
3
  import { accent, caution, dim, negative, positive } from "../style.js";
4
4
  import { defineCommand } from "../contract/command.js";
5
5
  import { textOutput } from "../commands/schemas.js";
6
+ import { option } from "../commands/options.js";
6
7
  /** Attach the System lifecycle without mixing it with Program commands. */
7
8
  export default function systemCommands(program, provided) {
8
9
  let lifecycle = provided;
@@ -15,18 +16,26 @@ export default function systemCommands(program, provided) {
15
16
  "Running-System Programs, Processes, Endpoints, and Windows are top-level capabilities, not System subcommands."
16
17
  ]
17
18
  });
18
- defineCommand(system, lifecycleContract("install", "install or update the System and start its service"), async function () {
19
+ defineCommand(system, {
20
+ ...lifecycleContract("install", "install or update the System and start its service"),
21
+ options: [option("--purge", "delete PHRESHOS_HOME before starting the installed System")]
22
+ }, async function ({ options }) {
19
23
  const interaction = prompts();
20
24
  interaction.begin("Install System", "official stable release");
21
- const status = await interaction.progress("Installing PhreshOS", "PhreshOS installed", () => current().install());
25
+ const status = await interaction.progress("Installing PhreshOS", "PhreshOS installed", () => current().install(options));
26
+ if (options.purge)
27
+ interaction.message("Previous System data was deleted.");
22
28
  interaction.detail("desktop", accent(status.desktop));
23
29
  interaction.finish(`PhreshOS ${accent(status.installed?.version ?? "")} installed`);
24
30
  });
25
- defineCommand(system, lifecycleContract("uninstall", "remove the System installation and service"), async function () {
31
+ defineCommand(system, {
32
+ ...lifecycleContract("uninstall", "remove the System installation and service"),
33
+ options: [option("--purge", "also delete PHRESHOS_HOME")]
34
+ }, async function ({ options }) {
26
35
  const interaction = prompts();
27
36
  interaction.begin("Uninstall System");
28
- await interaction.progress("Removing PhreshOS", "PhreshOS removed", () => current().uninstall());
29
- interaction.message("Persistent System data was kept.");
37
+ await interaction.progress("Removing PhreshOS", "PhreshOS removed", () => current().uninstall(options));
38
+ interaction.message(options.purge ? "Persistent System data was deleted." : "Persistent System data was kept.");
30
39
  interaction.finish("System uninstalled");
31
40
  });
32
41
  defineCommand(system, lifecycleContract("status", "show the System version and operating state"), async function () {
@@ -1,7 +1,8 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { existsSync, mkdirSync, writeFileSync } from "node:fs";
3
3
  import { mkdir, mkdtemp, open, readFile, readdir, readlink, rename, rm, symlink, writeFile } from "node:fs/promises";
4
- import { dirname, isAbsolute, join, relative, resolve } from "node:path";
4
+ import { dirname, isAbsolute, join, parse, relative, resolve } from "node:path";
5
+ import { homedir } from "node:os";
5
6
  import { requireSuccess } from "./process.js";
6
7
  import npmInvocation from "./npm.js";
7
8
  import { minimumSystemNodeVersion } from "./node.js";
@@ -144,6 +145,16 @@ export default class SystemInstallation {
144
145
  async remove() {
145
146
  await rm(this.paths.root, { recursive: true, force: true });
146
147
  }
148
+ /** Removes the selected System's persistent home, never a broad host directory. */
149
+ async purgeStorage() {
150
+ const storage = resolve(this.paths.storage);
151
+ const installation = relative(storage, resolve(this.paths.root));
152
+ if (storage === parse(storage).root || storage === resolve(homedir())
153
+ || installation === "" || (!installation.startsWith(`..`) && !isAbsolute(installation))) {
154
+ throw new Error("PHRESHOS_HOME cannot be purged because it contains host or System installation files");
155
+ }
156
+ await rm(storage, { recursive: true, force: true });
157
+ }
147
158
  async removeOtherReleases(current) {
148
159
  for (const entry of await readdir(this.paths.releases, { withFileTypes: true })) {
149
160
  if (!entry.isDirectory())
@@ -24,10 +24,10 @@ export default class SystemLifecycle {
24
24
  provisionSetup: dependencies?.provisionSetup ?? provisionSetup
25
25
  };
26
26
  }
27
- async install() {
28
- return await this.dependencies.installation.exclusive(() => this.installExclusive());
27
+ async install(options = {}) {
28
+ return await this.dependencies.installation.exclusive(() => this.installExclusive(options.purge === true));
29
29
  }
30
- async installExclusive() {
30
+ async installExclusive(purge) {
31
31
  const { installation, service } = this.dependencies;
32
32
  const previous = await installation.current();
33
33
  const previousService = await service.inspect();
@@ -35,7 +35,7 @@ export default class SystemLifecycle {
35
35
  const release = await this.dependencies.resolveRelease();
36
36
  const downloaded = await this.dependencies.downloadRelease(release);
37
37
  const prepared = await installation.prepare(downloaded);
38
- const activation = await this.activate(prepared, previous, previousService);
38
+ const activation = await this.activate(prepared, previous, previousService, purge);
39
39
  try {
40
40
  await service.register(definition(installation, executable));
41
41
  if ((await service.inspect()).automaticStartup)
@@ -62,10 +62,10 @@ export default class SystemLifecycle {
62
62
  }
63
63
  return await this.status();
64
64
  }
65
- async uninstall() {
66
- return await this.dependencies.installation.exclusive(() => this.uninstallExclusive());
65
+ async uninstall(options = {}) {
66
+ return await this.dependencies.installation.exclusive(() => this.uninstallExclusive(options.purge === true));
67
67
  }
68
- async uninstallExclusive() {
68
+ async uninstallExclusive(purge) {
69
69
  const { installation, service } = this.dependencies;
70
70
  const state = await service.inspect();
71
71
  if (state.running)
@@ -74,6 +74,8 @@ export default class SystemLifecycle {
74
74
  await service.disable();
75
75
  if (state.registered)
76
76
  await service.unregister();
77
+ if (purge)
78
+ await installation.purgeStorage();
77
79
  await installation.remove();
78
80
  }
79
81
  async start() {
@@ -145,11 +147,13 @@ export default class SystemLifecycle {
145
147
  throw new Error(`${message}${log}`, { cause: error });
146
148
  }
147
149
  }
148
- async activate(prepared, previous, state) {
150
+ async activate(prepared, previous, state, purge) {
149
151
  const { installation, service } = this.dependencies;
150
152
  try {
151
153
  if (state.running)
152
154
  await service.stop();
155
+ if (purge)
156
+ await installation.purgeStorage();
153
157
  return await installation.activate(prepared, previous);
154
158
  }
155
159
  catch (error) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "phresh",
3
3
  "private": true,
4
- "version": "0.1.34",
4
+ "version": "0.1.35",
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.40",
18
- "@phreshos/core": "^0.1.45",
19
- "@phreshos/react": "^0.1.22",
20
- "@phreshos/server": "^0.1.44",
17
+ "@phreshos/client": "^0.1.41",
18
+ "@phreshos/core": "^0.1.47",
19
+ "@phreshos/react": "^0.1.23",
20
+ "@phreshos/server": "^0.1.45",
21
21
  "react": "^19.2.8",
22
22
  "react-dom": "^19.2.8"
23
23
  },
24
24
  "devDependencies": {
25
- "@phreshos/cli": "^0.1.56",
25
+ "@phreshos/cli": "^0.1.57",
26
26
  "@types/node": "^26.4.0",
27
27
  "@types/react": "^19.2.18",
28
28
  "@types/react-dom": "^19.2.5",
@@ -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.34",
7
+ version: "0.1.35",
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.34",
4
- "sha256": "b4b743675ef96244be903e50f46bf4c7b5c51a8a726eb4e08a4d02cbc6d1aabb",
3
+ "version": "0.1.35",
4
+ "sha256": "8443820f5347137a6156de257b4971a7ee723827eceea664c6eeac5ee697bd6d",
5
5
  "development": true
6
6
  }
package/dist/uninstall.js CHANGED
@@ -11,10 +11,10 @@ export default async function uninstall(options = {}) {
11
11
  const program = await system.program.find(identity);
12
12
  if (!program)
13
13
  throw new Error("The System does not know this Program");
14
- for await (const chunk of program.uninstall(options.everything === true))
14
+ for await (const chunk of program.uninstall({ purge: options.purge }))
15
15
  writeProgramCommandOutput(chunk);
16
16
  heading(identity, "uninstalled");
17
- console.log(options.everything
17
+ console.log(options.purge
18
18
  ? ` ${dim("Its processes, installed files, stored data, and runtime record were removed.")}`
19
19
  : ` ${dim("Its installed files were removed. Processes, stored data, and runtime state were kept.")}`);
20
20
  blank();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@phreshos/cli",
3
3
  "type": "module",
4
- "version": "0.1.56",
4
+ "version": "0.1.57",
5
5
  "description": "The Phresh command-line interface for Program projects and system management.",
6
6
  "engines": {
7
7
  "node": ">=20.10"
@@ -47,8 +47,8 @@
47
47
  "packageManager": "bun@1.3.14",
48
48
  "dependencies": {
49
49
  "@clack/prompts": "^1.7.0",
50
- "@phreshos/core": "^0.1.45",
51
- "@phreshos/node": "^0.1.20",
50
+ "@phreshos/core": "^0.1.47",
51
+ "@phreshos/node": "^0.1.21",
52
52
  "adm-zip": "^0.6.0",
53
53
  "cli-table3": "^0.6.5",
54
54
  "commander": "^15.0.0",