@phreshos/cli 0.1.20 → 0.1.21

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
@@ -156,6 +156,12 @@ export default defineConfig({
156
156
 
157
157
  icon: "icon.png",
158
158
 
159
+ categories: ["Utilities"],
160
+
161
+ keywords: ["files", "storage"],
162
+
163
+ website: "https://example.com/file-manager",
164
+
159
165
  buildCommand: "bun run build",
160
166
 
161
167
  server: {
@@ -164,6 +170,8 @@ export default defineConfig({
164
170
 
165
171
  installCommand: "npm ci",
166
172
 
173
+ uninstallCommand: "npm run clean:external",
174
+
167
175
  startCommand: "node main.js",
168
176
 
169
177
  development: {
@@ -368,6 +376,10 @@ The command installs through the machine's local intake. A running
368
376
  Program may also install itself through the server SDK; installation is not a
369
377
  client capability.
370
378
 
379
+ When a Server declares `installCommand`, `phresh install` writes that command's
380
+ `stdout` and `stderr` chunks as the System emits them. The final installed
381
+ confirmation appears only after the output stream completes successfully.
382
+
371
383
  That is also why it names **paths** rather than sending bytes: install
372
384
  used to want an upload because the installer was a browser, which has
373
385
  bytes and no path. You have the paths.
@@ -384,13 +396,19 @@ running Processes, stored data, and runtime Program. `--everything` explicitly
384
396
  ends those Processes, removes everything the system owns for the Program, and
385
397
  forgets its runtime record.
386
398
 
399
+ When the installed Server declares `uninstallCommand`, the System runs it from
400
+ that Server directory before removing files. `phresh uninstall` writes its
401
+ ordered `stdout` and `stderr` chunks as they arrive. A failed cleanup command
402
+ aborts removal and reports the failure.
403
+
387
404
  The identity comes from this project's `phresh.config.ts`; the command does not
388
405
  accept an arbitrary Program identity.
389
406
 
390
407
  ## What pack produces
391
408
 
392
- `pack` takes what is at each half's `location` and writes
393
- `<identity>@<version>.zip`. Your program may leave its halves anywhere; the
409
+ `pack` takes what is at each half's `location` and writes both `program.json`
410
+ and `<identity>@<version>.zip`. The exact generated declaration is also stored
411
+ inside the archive. Your program may leave its halves anywhere; the
394
412
  package always keeps them in the same places, so the artifact's shape
395
413
  belongs to the contract rather than to your project. That is why the
396
414
  `program.json` it writes names `server` and `client` explicitly — those
@@ -405,5 +423,7 @@ An authored agent document may use any project-relative path, while packaging
405
423
  normalizes it to `agent.md`.
406
424
 
407
425
  Nothing about the system moves because this exists. `program.json` is
408
- still the only thing the system reads, and a package assembled by hand
409
- is still a package. This just means you do not have to.
426
+ still the only declaration the system and release catalog read. Root
427
+ `categories`, `keywords`, and `website` values are optional and cross into it
428
+ without affecting execution. The file is generated output; `phresh.config.ts`
429
+ remains the authored source.
package/dist/derive.js CHANGED
@@ -27,6 +27,9 @@ export default function derive(config, directory, which) {
27
27
  name: config.name,
28
28
  version: config.version,
29
29
  description: config.description,
30
+ categories: config.categories,
31
+ keywords: config.keywords,
32
+ website: config.website,
30
33
  // Where it already is. Unlike pack, which gives it its canonical
31
34
  // name, this points into the authoring tree and leaves it alone.
32
35
  icon: config.icon && resolve(directory, config.icon),
@@ -41,6 +44,7 @@ export default function derive(config, directory, which) {
41
44
  location: resolve(directory, server.location),
42
45
  start: server.start,
43
46
  installCommand: config.server?.installCommand,
47
+ uninstallCommand: config.server?.uninstallCommand,
44
48
  startCommand: server.startCommand
45
49
  } },
46
50
  // A URL stands as written; a directory is made absolute. The
package/dist/pack.js CHANGED
@@ -24,8 +24,9 @@ import build from "./build-command.js";
24
24
  * The archive itself therefore needs no second naming layer.
25
25
  *
26
26
  * The files go into the archive directly rather than through a staging
27
- * directory: a copy of a build is a thing that can be stale, and there
28
- * is nothing a staging directory was doing that the archive does not.
27
+ * directory: a copy of a build is a thing that can be stale. The generated
28
+ * standalone program.json is also placed beside the archive for release
29
+ * discovery; both declarations are made from the same bytes.
29
30
  */
30
31
  export default async function pack(directory = process.cwd()) {
31
32
  const config = await readConfig(directory);
@@ -47,7 +48,12 @@ export default async function pack(directory = process.cwd()) {
47
48
  file(zip, directory, config.icon, "icon.png", "Program icon");
48
49
  if (config.agent)
49
50
  file(zip, directory, config.agent, "agent.md", "Program agent documentation");
50
- zip.addFile("program.json", Buffer.from(JSON.stringify(program(config, version), null, 4) + "\n"));
51
+ const declaration = Buffer.from(JSON.stringify(program(config, version), null, 4) + "\n");
52
+ zip.addFile("program.json", declaration);
53
+ // The standalone release declaration and the declaration inside the
54
+ // archive are the same bytes. There is no separately authored catalog
55
+ // description that can drift from the installable Program.
56
+ writeFileSync(resolve(directory, "program.json"), declaration);
51
57
  const archive = `${config.identity}@${version ?? "0.0.0"}.zip`;
52
58
  const bytes = zip.toBuffer();
53
59
  writeFileSync(resolve(directory, archive), bytes);
@@ -67,7 +73,10 @@ function program(config, version) {
67
73
  description: config.description,
68
74
  icon: config.icon ? "icon.png" : undefined,
69
75
  agent: config.agent ? "agent.md" : undefined,
70
- ...config.server && { server: { location: "server", start: config.server.start, installCommand: config.server.installCommand, startCommand: config.server.startCommand } },
76
+ categories: config.categories,
77
+ keywords: config.keywords,
78
+ website: config.website,
79
+ ...config.server && { server: { location: "server", start: config.server.start, installCommand: config.server.installCommand, uninstallCommand: config.server.uninstallCommand, startCommand: config.server.startCommand } },
71
80
  ...config.client && { client: { location: "client", start: config.client.start, title: config.client.title, size: config.client.size, position: config.client.position, layer: config.client.layer, minimize: config.client.minimize } }
72
81
  };
73
82
  }
@@ -0,0 +1,8 @@
1
+ /** Writes one validated Program lifecycle-command output event verbatim. */
2
+ export default function writeProgramCommandOutput(event) {
3
+ if ((event.stream !== "stdout" && event.stream !== "stderr") || typeof event.text !== "string") {
4
+ throw new Error("The System returned an invalid Program command output chunk");
5
+ }
6
+ const output = event.stream === "stderr" ? process.stderr : process.stdout;
7
+ output.write(event.text);
8
+ }
@@ -1,4 +1,5 @@
1
1
  import speak from "./program-intake.js";
2
+ import writeProgramCommandOutput from "./program-command-output.js";
2
3
  /** Install one prepared Program and await every explicitly requested outcome. */
3
4
  export default async function installProgram(program, options = {}) {
4
5
  let installedValue;
@@ -11,7 +12,9 @@ export default async function installProgram(program, options = {}) {
11
12
  run: options.run === true,
12
13
  startup: options.startup === true
13
14
  }, function (event) {
14
- if (event.event === "installed") {
15
+ if (event.event === "output")
16
+ writeProgramCommandOutput(event);
17
+ else if (event.event === "installed") {
15
18
  installedValue = event.program;
16
19
  replaced = event.replaced === true;
17
20
  }
package/dist/project.js CHANGED
@@ -39,12 +39,28 @@ function coherent(config) {
39
39
  throw new Error("A program's identity is kebab-case, because it is also the name of its directory");
40
40
  if (!config.server && !config.client)
41
41
  throw new Error("A program must have a server half, a client half, or both");
42
- for (const field of ["name", "version", "description", "icon", "agent"]) {
42
+ for (const field of ["name", "version", "description", "icon", "agent", "website"]) {
43
43
  if (config[field] !== undefined && typeof config[field] !== "string")
44
44
  throw new Error(`A program's ${field} must be text`);
45
45
  }
46
46
  if (config.agent !== undefined && config.agent.trim().length === 0)
47
47
  throw new Error("A program's agent documentation must be a non-empty path");
48
+ if (config.website !== undefined) {
49
+ try {
50
+ new URL(config.website);
51
+ }
52
+ catch {
53
+ throw new Error("A program's website must be a valid URL");
54
+ }
55
+ }
56
+ for (const [field, maximum] of [["categories", 20], ["keywords", 50]]) {
57
+ const values = config[field];
58
+ if (values === undefined)
59
+ continue;
60
+ if (!Array.isArray(values) || values.length > maximum || values.some(value => typeof value !== "string" || value.trim().length === 0 || value.trim().length > 50)) {
61
+ throw new Error(`A program's ${field} must contain at most ${maximum} non-empty values of at most 50 characters`);
62
+ }
63
+ }
48
64
  if (config.buildCommand !== undefined && (typeof config.buildCommand !== "string" || config.buildCommand.trim().length === 0))
49
65
  throw new Error("A program's buildCommand must be non-empty text");
50
66
  for (const half of ["server", "client"]) {
@@ -64,6 +80,8 @@ function coherent(config) {
64
80
  throw new Error("A server half must say what starts it");
65
81
  if (config.server?.installCommand !== undefined && typeof config.server.installCommand !== "string")
66
82
  throw new Error("A server half's install command must be text");
83
+ if (config.server?.uninstallCommand !== undefined && typeof config.server.uninstallCommand !== "string")
84
+ throw new Error("A server half's uninstall command must be text");
67
85
  if (config.client?.title !== undefined && typeof config.client.title !== "string")
68
86
  throw new Error("A client half's title must be text");
69
87
  if (config.client?.minimize !== undefined && typeof config.client.minimize !== "boolean")
@@ -0,0 +1,11 @@
1
+ {
2
+ "schema": 1,
3
+ "identity": "phresh",
4
+ "version": "0.1.13",
5
+ "name": "Phresh Program",
6
+ "description": "A minimal Client and Server Program for exploring PhreshOS development.",
7
+ "icon": "icon.png",
8
+ "categories": ["Development"],
9
+ "keywords": ["example", "counter", "client", "server"],
10
+ "website": "https://github.com/PhreshOS/phresh-program"
11
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "phresh",
3
3
  "private": true,
4
- "version": "0.1.12",
4
+ "version": "0.1.13",
5
5
  "description": "The official PhreshOS starter Program.",
6
6
  "type": "module",
7
7
  "scripts": {
@@ -22,7 +22,7 @@
22
22
  "react-dom": "^19.2.8"
23
23
  },
24
24
  "devDependencies": {
25
- "@phreshos/cli": "^0.1.20",
25
+ "@phreshos/cli": "^0.1.21",
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.12",
7
+ version: "0.1.13",
8
8
  icon: "icon.png",
9
9
  buildCommand: "vite-node scripts/build.ts",
10
10
  server: {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "repository": "PhreshOS/phresh-program",
3
- "version": "0.1.12",
4
- "sha256": "408d2a1353c32d5ffe69773d115663d125311586394f8bcddd0a0e07f33da914",
3
+ "version": "0.1.13",
4
+ "sha256": "c55ff8129295c9c90b7a78fef8689fef6e0d180c7a284fdb180127700b74fcb0",
5
5
  "development": true
6
6
  }
package/dist/uninstall.js CHANGED
@@ -1,10 +1,13 @@
1
1
  import { readConfig } from "./project.js";
2
2
  import { dim, heading } from "./style.js";
3
3
  import speak from "./program-intake.js";
4
+ import writeProgramCommandOutput from "./program-command-output.js";
4
5
  /** Uninstall the Program declared by the current project. */
5
6
  export default async function uninstall(everything = false, directory = process.cwd()) {
6
7
  const config = await readConfig(directory);
7
8
  await speak({ word: "uninstall", identity: config.identity, everything }, function (event) {
9
+ if (event.event === "output")
10
+ return writeProgramCommandOutput(event);
8
11
  if (event.event !== "uninstalled")
9
12
  return;
10
13
  heading(config.name ?? config.identity, "uninstalled");
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@phreshos/cli",
3
3
  "type": "module",
4
- "version": "0.1.20",
4
+ "version": "0.1.21",
5
5
  "description": "The Phresh command-line interface for Program projects and system management.",
6
6
  "engines": {
7
7
  "node": ">=20.10"
@@ -49,7 +49,7 @@
49
49
  "packageManager": "bun@1.3.14",
50
50
  "dependencies": {
51
51
  "@clack/prompts": "^1.7.0",
52
- "@phreshos/core": "^0.1.12",
52
+ "@phreshos/core": "^0.1.13",
53
53
  "adm-zip": "^0.6.0",
54
54
  "commander": "^15.0.0",
55
55
  "picocolors": "^1.1.1"