@phreshos/cli 0.1.4 → 0.1.6

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.
Files changed (40) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +63 -26
  3. package/dist/build-template.js +73 -42
  4. package/dist/cli.js +135 -241
  5. package/dist/create.js +73 -80
  6. package/dist/derive.js +4 -4
  7. package/dist/init.js +113 -122
  8. package/dist/pack.js +9 -8
  9. package/dist/program-intake.js +10 -2
  10. package/dist/project-dependency.js +13 -44
  11. package/dist/project.js +2 -3
  12. package/dist/prompts.js +105 -0
  13. package/dist/style.js +9 -14
  14. package/dist/system/command.js +70 -0
  15. package/dist/system/installation.js +241 -0
  16. package/dist/system/lifecycle.js +174 -0
  17. package/dist/system/node.js +13 -0
  18. package/dist/system/paths.js +28 -0
  19. package/dist/system/process.js +23 -0
  20. package/dist/system/readiness.js +29 -0
  21. package/dist/system/release.js +86 -0
  22. package/dist/system/service/index.js +11 -0
  23. package/dist/system/service/linux.js +94 -0
  24. package/dist/system/service/macos.js +123 -0
  25. package/dist/system/types.js +0 -0
  26. package/dist/template/README.md +1 -1
  27. package/dist/template/icon.png +0 -0
  28. package/dist/template/package.json +8 -7
  29. package/dist/template/phresh.config.ts +65 -25
  30. package/dist/template/source/build.ts +3 -3
  31. package/dist/template/source/client/app.tsx +3 -8
  32. package/dist/template/source/server/main.ts +1 -1
  33. package/dist/template/vite.client.ts +22 -0
  34. package/dist/template/vite.server.ts +27 -0
  35. package/dist/template.json +5 -0
  36. package/package.json +36 -4
  37. package/dist/questions.js +0 -25
  38. package/dist/relative-value.js +0 -118
  39. package/dist/template/icons/128x128.png +0 -0
  40. package/dist/template/vite.config.ts +0 -38
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Zohayr SLILEH
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # @phreshos/cli
2
2
 
3
- The `phresh` command for creating, running, packaging, installing, and
4
- uninstalling Programs.
3
+ The `phresh` command for creating and operating Programs, and for installing
4
+ and managing the PhreshOS System on the current machine.
5
5
 
6
6
  ## Package status
7
7
 
@@ -21,6 +21,7 @@ phresh install # lay this program out on this machine
21
21
  phresh uninstall # remove its installed form
22
22
  phresh start # run what your build left, and stay with it
23
23
  phresh dev # run from source, and stay with it
24
+ phresh system status # inspect the local System and its background service
24
25
  ```
25
26
 
26
27
  `phresh --help` lists them, `phresh <command> --help` explains one, and
@@ -28,20 +29,53 @@ phresh dev # run from source, and stay with it
28
29
  unknown command, an unknown flag and a malformed option are each refused
29
30
  and named.
30
31
 
31
- **This tool acts on the current project, and the list ends there.** It is
32
- not the machine's control panel it accepts no arbitrary program identity
33
- and has no word for a process, a window, a store or a setting. The system's
34
- local intake accepts exactly the Program this project declares, and so does
35
- this tool.
32
+ Every top-level Program command acts on the current project, and that list ends
33
+ there. It accepts no arbitrary Program identity and has no word for a Process,
34
+ Window, store, or setting. Machine lifecycle is isolated under `phresh system`;
35
+ it manages the System installation and native service, not the state inside the
36
+ System.
37
+
38
+ ## System lifecycle
39
+
40
+ ```bash
41
+ phresh system install
42
+ phresh system uninstall
43
+ phresh system status
44
+ phresh system start
45
+ phresh system stop
46
+ phresh system enable
47
+ phresh system disable
48
+ ```
49
+
50
+ `install` resolves the newest compatible stable release from the official
51
+ [`PhreshOS/system`](https://github.com/PhreshOS/system) GitHub Releases. It
52
+ downloads the production archive and adjacent checksum, verifies every byte,
53
+ installs production dependencies into a staged version directory, atomically
54
+ points the stable `current` path at it, then registers, enables, and starts the
55
+ native per-user service. The selected release and the service entry therefore
56
+ cannot become two competing sources of truth if installation is interrupted.
57
+ It never reads a source checkout and never requires Bun or TypeScript.
58
+
59
+ The System runs under `launchd` on macOS and `systemd --user` on Linux. The
60
+ native manager owns it after the CLI exits and restarts a failed active
61
+ service. `start` and `stop` change current execution only; `enable` and
62
+ `disable` change automatic startup only. `status` reads installation,
63
+ registration, startup, process, and local-intake readiness without changing
64
+ any of them.
65
+
66
+ Installation files and persistent System state have separate homes. Removing
67
+ the System unregisters its service and removes its release files while keeping
68
+ `~/.phreshos`, including Programs and owner data. Windows remains unsupported
69
+ until the System has an equally strong local-intake authorization model there.
36
70
 
37
71
  ## create
38
72
 
39
73
  `phresh create <directory>` creates a complete Server and Client Program from
40
- the maintained Get Started project. Get Started is the only authored template:
41
- the CLI build copies a filtered snapshot into the package, excluding build
42
- output, storage, installed modules, lockfiles, and archives. The installed CLI
43
- therefore creates projects offline without cloning a repository or maintaining
44
- a second template by hand.
74
+ the maintained `PhreshOS/phresh-program` repository. The CLI build downloads
75
+ one exact tagged source release, verifies its SHA-256 checksum, removes
76
+ repository-only material, and bundles the resulting authoring project. The
77
+ installed CLI therefore creates projects offline without reading a live branch
78
+ or maintaining a second template by hand.
45
79
 
46
80
  The directory name becomes the stable kebab-case Program identity. In a
47
81
  terminal, `create` asks for the directory when it is omitted, the readable
@@ -55,11 +89,9 @@ phresh create status-board \
55
89
  ```
56
90
 
57
91
  Dependencies are installed by default. `--no-install` creates the same valid
58
- project and leaves installation as the first reported next step. When the CLI
59
- runs from this repository, generated SDK dependencies point to the sibling
60
- dev-kits; a distributed CLI keeps the published versions embedded in its
61
- template. Both choices pass through the same dependency resolver used by
62
- `init`.
92
+ project and leaves installation as the first reported next step. Generated and
93
+ initialized Programs always use the published package ranges embedded in the
94
+ CLI; repository layout never changes dependency meaning.
63
95
 
64
96
  ## Saying something to a program you start
65
97
 
@@ -117,7 +149,7 @@ export default defineConfig({
117
149
 
118
150
  apiDocs: "api.md",
119
151
 
120
- icons: "icons",
152
+ icon: "icon.png",
121
153
 
122
154
  buildCommand: "bun run build",
123
155
 
@@ -212,7 +244,9 @@ In a terminal, `init` asks for the production locations and commands needed by
212
244
  `start` and `install`, including whether a package build should prepare those
213
245
  locations. It then offers the development command and URL needed by `dev`.
214
246
  Existing `build` and `dev` package scripts become editable suggestions, never
215
- silent assumptions.
247
+ silent assumptions. A single Endpoint defaults to `dist`; when both Endpoints
248
+ exist, their defaults are `dist/server` and `dist/client`. API documentation is
249
+ opt-in and defaults to disabled even when a likely document already exists.
216
250
 
217
251
  Outside a terminal it never waits for input; the same values are supplied as
218
252
  named options:
@@ -225,7 +259,7 @@ phresh init --client \
225
259
  --client-development-start-command "bun run dev"
226
260
 
227
261
  phresh init --server \
228
- --server-location build/server \
262
+ --server-location dist \
229
263
  --server-start-command "node main.js"
230
264
  ```
231
265
 
@@ -241,7 +275,7 @@ The final `Next` line is derived from the resulting config. It always shows
241
275
  one Endpoint received a development declaration.
242
276
 
243
277
  Visual and advanced runtime defaults remain for the author to add deliberately:
244
- `icons`, `size`, `position`, `installCommand`, `start`, layers, and minimization
278
+ `icon`, `size`, `position`, `installCommand`, `start`, layers, and minimization
245
279
  are not guessed. An omitted `start` is `true`; only a default-off Endpoint needs to
246
280
  say `start: false`.
247
281
 
@@ -249,9 +283,12 @@ say `start: false`.
249
283
 
250
284
  Both **run your program without installing it, and stay attached.** They
251
285
  print the `program.json` it will be declared as, hand that to the system
252
- through the socket it listens on `~/.phreshos/intake.sock`, which only
253
- your account can open, so nothing is sent to prove anything — and then
254
- hold.
286
+ through the socket below the selected system home. With no override, that is
287
+ `~/.phreshos/intake.sock`. Set `PHRESHOS_HOME` to an absolute system home to
288
+ address another system instance; the CLI derives
289
+ `<PHRESHOS_HOME>/intake.sock` from it. The socket is not selected
290
+ separately from its instance. Only your account can open it, so nothing is
291
+ sent to prove anything, and then the command holds.
255
292
 
256
293
  **The connection is the tether, in both directions.** Ctrl-C and your
257
294
  program stops. Close the window it opened and the command returns, with
@@ -367,8 +404,8 @@ register a subscription. Those are system contracts documented once by the
367
404
  SDKs; `api-docs.md` supplies only the Program-specific meaning carried through
368
405
  them.
369
406
 
370
- There is **no wrapping directory**: `program.json`, `server/`, `client/`
371
- `icons/`, and optional `api-docs.md` sit at the package's root, and the system names the
407
+ There is **no wrapping directory**: `program.json`, `server/`, `client/`,
408
+ optional `icon.png`, and optional `api-docs.md` sit at the package's root, and the system names the
372
409
  directory it installs into from your program's `identity`.
373
410
 
374
411
  Nothing about the system moves because this exists. `program.json` is
@@ -1,8 +1,19 @@
1
- import { cpSync, existsSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
2
- import { basename, relative, resolve, sep } from "node:path";
3
- const source = resolve(import.meta.dirname, "..", "..", "get-started");
1
+ import metadata from "../package.json" with { type: "json" };
2
+ import { createHash } from "node:crypto";
3
+ import { mkdirSync, rmSync, writeFileSync } from "node:fs";
4
+ import { dirname, resolve, sep } from "node:path";
5
+ import AdmZip from "adm-zip";
6
+ import { readConfig } from "./project.js";
7
+ const repository = "PhreshOS/phresh-program";
8
+ const release = {
9
+ version: "0.1.0",
10
+ sha256: "6e1a87a037a6ac5f82ff4c7d420757f972098387d73a8b4165aab2ef331cec36"
11
+ };
12
+ const archiveUrl = `https://github.com/${repository}/archive/refs/tags/v${release.version}.zip`;
4
13
  const output = resolve(import.meta.dirname, "template");
5
- const excludedDirectories = new Set(["dist", "node_modules", "storage"]);
14
+ const descriptionOutput = resolve(import.meta.dirname, "template.json");
15
+ const excludedDirectories = new Set([".github", "dist", "node_modules", "scripts", "storage"]);
16
+ const excludedRootFiles = new Set(["CONTRIBUTING.md", "LICENSE", "SECURITY.md"]);
6
17
  const excludedFiles = new Set([
7
18
  ".DS_Store",
8
19
  "bun.lock",
@@ -11,49 +22,69 @@ const excludedFiles = new Set([
11
22
  "pnpm-lock.yaml",
12
23
  "yarn.lock"
13
24
  ]);
14
- if (!existsSync(source))
15
- throw new Error(`The Get Started source is not there: ${source}`);
25
+ const response = await fetch(archiveUrl);
26
+ if (!response.ok)
27
+ throw new Error(`Could not download ${repository} v${release.version}: ${response.status} ${response.statusText}`);
28
+ const bytes = Buffer.from(await response.arrayBuffer());
29
+ const digest = createHash("sha256").update(bytes).digest("hex");
30
+ if (digest !== release.sha256)
31
+ throw new Error(`The ${repository} v${release.version} source archive failed integrity verification`);
32
+ const archive = new AdmZip(bytes);
33
+ const entries = archive.getEntries();
34
+ const roots = new Set(entries.map(entry => entry.entryName.split("/")[0]).filter(Boolean));
35
+ if (roots.size !== 1)
36
+ throw new Error(`The ${repository} v${release.version} source archive has an invalid root`);
37
+ const root = [...roots][0];
16
38
  rmSync(output, { recursive: true, force: true });
17
- cpSync(source, output, { recursive: true, filter: included });
18
- renameSync(resolve(output, ".gitignore"), resolve(output, "gitignore"));
19
- const manifestPath = resolve(output, "package.json");
20
- const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
21
- const versions = workspaceVersions();
22
- for (const dependencies of [manifest.dependencies, manifest.devDependencies]) {
23
- if (!dependencies)
39
+ rmSync(descriptionOutput, { force: true });
40
+ for (const entry of entries) {
41
+ if (entry.isDirectory || !entry.entryName.startsWith(`${root}/`))
42
+ continue;
43
+ const local = entry.entryName.slice(root.length + 1);
44
+ if (!included(local))
24
45
  continue;
25
- for (const [name, range] of Object.entries(dependencies)) {
26
- if (!range.startsWith("workspace:"))
27
- continue;
28
- const version = versions.get(name);
29
- if (!version)
30
- throw new Error(`The template depends on ${name}, but no matching workspace package exists`);
31
- dependencies[name] = `^${version}`;
32
- }
46
+ const target = local === ".gitignore" ? "gitignore" : local;
47
+ const path = resolve(output, target);
48
+ if (!path.startsWith(`${output}${sep}`))
49
+ throw new Error(`The ${repository} source archive contains an unsafe path: ${local}`);
50
+ mkdirSync(dirname(path), { recursive: true });
51
+ writeFileSync(path, entry.getData());
33
52
  }
53
+ const manifestPath = resolve(output, "package.json");
54
+ const manifestSource = archive.readFile(`${root}/package.json`);
55
+ if (!manifestSource)
56
+ throw new Error(`The ${repository} v${release.version} source archive has no package.json`);
57
+ const manifest = JSON.parse(manifestSource.toString("utf8"));
58
+ manifest.scripts = select(manifest.scripts, ["dev", "start"]);
59
+ manifest.devDependencies = {
60
+ ...manifest.devDependencies,
61
+ "@phreshos/cli": `^${metadata.version}`
62
+ };
63
+ delete manifest.author;
64
+ delete manifest.license;
65
+ delete manifest.repository;
66
+ delete manifest.bugs;
67
+ delete manifest.homepage;
68
+ delete manifest.packageManager;
34
69
  writeFileSync(manifestPath, JSON.stringify(manifest, null, 4) + "\n");
35
- function included(path) {
36
- const local = relative(source, path);
37
- if (!local)
38
- return true;
39
- const parts = local.split(sep);
70
+ const config = await readConfig(output);
71
+ if (config.identity !== "phresh-program" || config.name !== "Phresh Program")
72
+ throw new Error(`The ${repository} release is not Phresh Program`);
73
+ writeFileSync(descriptionOutput, JSON.stringify({
74
+ repository,
75
+ version: release.version,
76
+ development: Boolean(config.server?.development || config.client?.development)
77
+ }, null, 4) + "\n");
78
+ function included(local) {
79
+ const parts = local.split("/");
40
80
  if (parts.some(part => excludedDirectories.has(part)))
41
81
  return false;
42
- const name = basename(path);
43
- return !excludedFiles.has(name) && !name.endsWith(".zip");
82
+ if (parts.length === 1 && excludedRootFiles.has(local))
83
+ return false;
84
+ if (excludedFiles.has(parts.at(-1)))
85
+ return false;
86
+ return !local.endsWith(".zip");
44
87
  }
45
- function workspaceVersions() {
46
- const versions = new Map();
47
- const devKit = resolve(source, "..");
48
- for (const entry of readdirSync(devKit, { withFileTypes: true })) {
49
- if (!entry.isDirectory())
50
- continue;
51
- const path = resolve(devKit, entry.name, "package.json");
52
- if (!existsSync(path))
53
- continue;
54
- const manifest = JSON.parse(readFileSync(path, "utf-8"));
55
- if (manifest.name && manifest.version)
56
- versions.set(manifest.name, manifest.version);
57
- }
58
- return versions;
88
+ function select(source, names) {
89
+ return Object.fromEntries(names.flatMap(name => source?.[name] === undefined ? [] : [[name, source[name]]]));
59
90
  }