@phreshos/cli 0.1.9 → 0.1.11

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.
@@ -6,8 +6,8 @@ import AdmZip from "adm-zip";
6
6
  import { readConfig } from "./project.js";
7
7
  const repository = "PhreshOS/phresh-program";
8
8
  const release = {
9
- version: "0.1.0",
10
- sha256: "6e1a87a037a6ac5f82ff4c7d420757f972098387d73a8b4165aab2ef331cec36"
9
+ version: "0.1.2",
10
+ sha256: "dba5f1fd868c46501d5bd3463f4f8f0e098c7a231cb75794c01118689dc69339"
11
11
  };
12
12
  const archiveUrl = `https://github.com/${repository}/archive/refs/tags/v${release.version}.zip`;
13
13
  const output = resolve(import.meta.dirname, "template");
package/dist/cli.js CHANGED
@@ -86,12 +86,16 @@ describe(program.command("pack")
86
86
  "declared for each endpoint."
87
87
  ]);
88
88
  describe(program.command("install")
89
- .description("install this Program")
90
- .action(async function () {
91
- await install();
89
+ .description("install a local or official Program")
90
+ .argument("[name]", "name of an official Program")
91
+ .option("--run", "run the installed Program now")
92
+ .option("--startup", "run the Program when the System starts")
93
+ .action(async function (name, options) {
94
+ await install({ name, run: options.run === true, startup: options.startup === true });
92
95
  }), [
93
- "Builds and installs the Program declared by this project. Running",
94
- "Processes end before installed paths change; Program data is preserved."
96
+ "Without a name, builds and installs the Program declared by this project.",
97
+ "A name installs its verified official production release. --run launches",
98
+ "it now; --startup persists the same default launch for future starts."
95
99
  ]);
96
100
  describe(program.command("uninstall")
97
101
  .description("uninstall this Program")
package/dist/install.js CHANGED
@@ -1,45 +1,51 @@
1
1
  import derive from "./derive.js";
2
2
  import { readConfig } from "./project.js";
3
- import { dim, heading } from "./style.js";
4
- import speak from "./program-intake.js";
3
+ import { dim, heading, line } from "./style.js";
4
+ import installProgram, {} from "./program-installation.js";
5
+ import { prepareOfficialProgram } from "./program-release.js";
5
6
  import build from "./build-command.js";
6
7
  /**
7
8
  * Lay this program out on this machine's system.
8
9
  *
9
- * What is sent is the description the config derives the same one
10
- * `phresh start` runs from and the system copies what it names into
11
- * place. A program's parts are already on this disk at the locations it
12
- * names, so there is nothing an archive would carry that the description
13
- * does not already point at.
14
- *
15
- * **Installing takes no package, and neither does the system.** A
16
- * package is a way of *carrying* a program to another machine, which is
17
- * a different act from laying one out — and installing programs at all
18
- * is work for a program rather than for the core, so what a person
19
- * installs *from* is not this command's business either. `phresh pack`
20
- * makes a package when you have somewhere to send it, and stops there.
10
+ * A local project is built and derived from its authoring declaration. An
11
+ * official name resolves a verified production package and turns its
12
+ * canonical paths into the same concrete description. From that point on,
13
+ * both sources cross the exact same intake and the System performs the exact
14
+ * same authoritative installation.
21
15
  *
22
16
  * When the author config declares `buildCommand`, it runs here before the
23
17
  * production Program is derived and sent. The command remains authoring
24
18
  * metadata and never becomes part of the installed Program.
25
19
  *
26
- * Installing is not running. A program laid out here stays until
27
- * something removes it, and `phresh start` and `phresh dev` are for the
28
- * other way a program is used attached to the terminal that began it,
29
- * and gone when that ends.
20
+ * Installation remains distinct from execution unless `run` or `startup`
21
+ * is explicitly requested. A run created here belongs to the installed
22
+ * Program and therefore outlives this command; `phresh start` and `phresh
23
+ * dev` remain attached authoring runs whose lifetime is the terminal's.
30
24
  */
31
- export default async function install(directory = process.cwd()) {
25
+ export default async function install(options = {}) {
26
+ const directory = options.directory ?? process.cwd();
27
+ const prepared = options.name ? await prepareOfficialProgram(options.name) : null;
28
+ try {
29
+ const program = prepared?.program ?? await localProgram(directory);
30
+ const result = await installProgram(program, options);
31
+ if (options.announce !== false) {
32
+ const { name, identity, version } = result.program;
33
+ heading(`${name || identity}${version ? ` ${version}` : ""}`, result.replaced ? "reinstalled" : "installed");
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");
38
+ if (result.process)
39
+ line("process", result.process);
40
+ }
41
+ return result;
42
+ }
43
+ finally {
44
+ await prepared?.dispose();
45
+ }
46
+ }
47
+ async function localProgram(directory) {
32
48
  const config = await readConfig(directory);
33
49
  await build(config, directory);
34
- const program = derive(config, directory, "production");
35
- await speak({ word: "install", program }, function (event) {
36
- if (event.event !== "installed")
37
- return;
38
- const said = event.program;
39
- heading(`${said.name ?? String(said.identity)}${said.version ? ` ${said.version}` : ""}`, event.replaced ? "reinstalled" : "installed");
40
- // What it kept. Every process ended before the installed paths
41
- // changed, while storage stayed in its canonical place.
42
- if (event.replaced)
43
- console.log(` ${dim("its storage was kept, and its previous processes were ended")}\n`);
44
- });
50
+ return derive(config, directory, "production");
45
51
  }
package/dist/pack.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { readConfig, readManifest } from "./project.js";
2
- import { existsSync, readFileSync, statSync } from "node:fs";
2
+ import { createHash } from "node:crypto";
3
+ import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
3
4
  import { resolve } from "node:path";
4
5
  import AdmZip from "adm-zip";
5
6
  import build from "./build-command.js";
@@ -47,7 +48,10 @@ export default async function pack(directory = process.cwd()) {
47
48
  document(zip, directory, config.apiDocs);
48
49
  zip.addFile("program.json", Buffer.from(JSON.stringify(program(config, version), null, 4) + "\n"));
49
50
  const archive = `${config.identity}@${version ?? "0.0.0"}.zip`;
50
- zip.writeZip(resolve(directory, archive));
51
+ const bytes = zip.toBuffer();
52
+ writeFileSync(resolve(directory, archive), bytes);
53
+ const digest = createHash("sha256").update(bytes).digest("hex");
54
+ writeFileSync(resolve(directory, `${archive}.sha256`), `${digest} ${archive}\n`);
51
55
  console.log(`\nPacked ${archive}`);
52
56
  return archive;
53
57
  }
@@ -0,0 +1,54 @@
1
+ import speak from "./program-intake.js";
2
+ /** Install one prepared Program and await every explicitly requested outcome. */
3
+ export default async function installProgram(program, options = {}) {
4
+ let installedValue;
5
+ let replaced = false;
6
+ let startupEnabled = false;
7
+ let processValue;
8
+ await speak({
9
+ word: "install",
10
+ program,
11
+ run: options.run === true,
12
+ startup: options.startup === true
13
+ }, function (event) {
14
+ if (event.event === "installed") {
15
+ installedValue = event.program;
16
+ replaced = event.replaced === true;
17
+ }
18
+ else if (event.event === "startupEnabled")
19
+ startupEnabled = true;
20
+ else if (event.event === "running")
21
+ processValue = event.process;
22
+ });
23
+ if (installedValue === undefined)
24
+ throw new Error("The System ended Program installation without confirming it");
25
+ const installed = installedProgram(installedValue);
26
+ if (options.startup && !startupEnabled)
27
+ throw new Error("The System installed the Program without confirming startup");
28
+ const process = processValue === undefined ? null : processIdentity(processValue);
29
+ if (options.run && !process)
30
+ throw new Error("The System installed the Program without confirming that it is running");
31
+ return {
32
+ program: installed,
33
+ replaced,
34
+ startupEnabled,
35
+ process
36
+ };
37
+ }
38
+ function processIdentity(value) {
39
+ if (typeof value !== "string" || !value)
40
+ throw new Error("The System returned an invalid Process identity");
41
+ return value;
42
+ }
43
+ function installedProgram(value) {
44
+ if (!record(value)
45
+ || typeof value.identity !== "string"
46
+ || typeof value.name !== "string"
47
+ || value.version !== null && typeof value.version !== "string") {
48
+ throw new Error("The System returned an invalid installed Program");
49
+ }
50
+ return { identity: value.identity, name: value.name, version: value.version };
51
+ }
52
+ function record(value) {
53
+ return typeof value === "object" && value !== null && !Array.isArray(value);
54
+ }
@@ -14,10 +14,11 @@ import { isAbsolute, join } from "node:path";
14
14
  * question would make lifetime ambiguous. A line delimiter keeps the
15
15
  * connection available for the stream of events that follows a launch.
16
16
  *
17
- * One question, then events until the system closes. Installing and
18
- * uninstalling say one thing and end; running says one thing, then whatever
19
- * the Program says, then how it ended. None pretends to be a remote method
20
- * returning through an unrelated transport.
17
+ * One question, then events until the system closes. Installation confirms
18
+ * each requested outcome laid out, startup enabled, running and then
19
+ * ends. Uninstalling says one thing and ends; an attached run says how it
20
+ * began, whatever the Program says, and how it ended. None pretends to be a
21
+ * remote method returning through an unrelated transport.
21
22
  */
22
23
  export function programIntakePath(environment = process.env, userHome = homedir()) {
23
24
  const instanceHome = environment.PHRESHOS_HOME;
@@ -0,0 +1,165 @@
1
+ import { createHash } from "node:crypto";
2
+ import { mkdirSync, writeFileSync } from "node:fs";
3
+ import { mkdtemp, readFile, rm } from "node:fs/promises";
4
+ import { tmpdir } from "node:os";
5
+ import { dirname, isAbsolute, join, relative, resolve } from "node:path";
6
+ import AdmZip from "adm-zip";
7
+ const officialPrograms = {
8
+ phresh: {
9
+ identity: "phresh-program",
10
+ repository: "PhreshOS/phresh-program"
11
+ },
12
+ setup: {
13
+ identity: "setup",
14
+ repository: "PhreshOS/setup-program"
15
+ }
16
+ };
17
+ /** Resolve, verify, and unpack one official production Program release. */
18
+ export async function prepareOfficialProgram(name, fetcher = fetch) {
19
+ const release = await resolveOfficialProgramRelease(name, fetcher);
20
+ const bytes = await downloadProgramRelease(release, fetcher);
21
+ const directory = await mkdtemp(join(tmpdir(), `phresh-${release.identity}-`));
22
+ try {
23
+ extract(bytes, directory);
24
+ const program = await readProgram(directory, release);
25
+ return {
26
+ program,
27
+ release,
28
+ dispose: async () => { await rm(directory, { recursive: true, force: true }); }
29
+ };
30
+ }
31
+ catch (error) {
32
+ await rm(directory, { recursive: true, force: true });
33
+ throw error;
34
+ }
35
+ }
36
+ export async function resolveOfficialProgramRelease(name, fetcher = fetch) {
37
+ const official = officialPrograms[name];
38
+ if (!official)
39
+ throw new Error(`No official Program is named "${name}"`);
40
+ const response = await fetcher(`https://api.github.com/repos/${official.repository}/releases?per_page=100`, {
41
+ headers: {
42
+ Accept: "application/vnd.github+json",
43
+ "User-Agent": "@phreshos/cli"
44
+ },
45
+ signal: AbortSignal.timeout(30_000)
46
+ });
47
+ if (!response.ok)
48
+ throw new Error(`The ${name} release list could not be read (${response.status} ${response.statusText})`);
49
+ return selectProgramRelease(official.identity, await response.json());
50
+ }
51
+ export function selectProgramRelease(identity, value) {
52
+ if (!Array.isArray(value))
53
+ throw new Error(`The ${identity} release list is invalid`);
54
+ const releases = value.flatMap(function (item) {
55
+ if (!record(item) || item.draft === true || item.prerelease === true || typeof item.tag_name !== "string" || !Array.isArray(item.assets))
56
+ return [];
57
+ const version = parseVersion(item.tag_name);
58
+ if (!version)
59
+ return [];
60
+ const archiveName = `${identity}@${version}.zip`;
61
+ const archive = asset(item.assets, archiveName);
62
+ const checksum = asset(item.assets, `${archiveName}.sha256`);
63
+ return archive && checksum ? [{ identity, version, archive, checksum }] : [];
64
+ });
65
+ releases.sort((left, right) => compare(right.version, left.version));
66
+ const selected = releases[0];
67
+ if (!selected)
68
+ throw new Error(`No stable ${identity} Program release is available`);
69
+ return selected;
70
+ }
71
+ export async function downloadProgramRelease(release, fetcher = fetch) {
72
+ const [archive, checksum] = await Promise.all([
73
+ fetchAsset(release.archive, fetcher),
74
+ fetchAsset(release.checksum, fetcher)
75
+ ]);
76
+ const bytes = Buffer.from(await archive.arrayBuffer());
77
+ const said = (await checksum.text()).trim();
78
+ const name = `${release.identity}@${release.version}.zip`;
79
+ const match = /^([a-f0-9]{64})\s+(.+)$/i.exec(said);
80
+ if (!match || match[2] !== name)
81
+ throw new Error(`The checksum for ${name} is invalid`);
82
+ const digest = createHash("sha256").update(bytes).digest("hex");
83
+ if (digest !== match[1]?.toLowerCase())
84
+ throw new Error(`The downloaded ${name} does not match its SHA-256 checksum`);
85
+ return bytes;
86
+ }
87
+ async function readProgram(directory, release) {
88
+ let value;
89
+ try {
90
+ value = JSON.parse(await readFile(join(directory, "program.json"), "utf8"));
91
+ }
92
+ catch {
93
+ throw new Error(`The ${release.identity} Program package has no valid program.json`);
94
+ }
95
+ if (!record(value) || value.identity !== release.identity || value.version !== release.version) {
96
+ throw new Error(`The ${release.identity} Program package identity or version does not match its release`);
97
+ }
98
+ if (value.storage !== undefined)
99
+ throw new Error("A published Program package cannot choose its installed storage");
100
+ return {
101
+ ...value,
102
+ ...pathField(value, directory, "apiDocs", "api-docs.md"),
103
+ ...pathField(value, directory, "icon", "icon.png"),
104
+ ...half(value, directory, "server"),
105
+ ...half(value, directory, "client")
106
+ };
107
+ }
108
+ function half(value, directory, name) {
109
+ const declared = value[name];
110
+ if (declared === undefined)
111
+ return {};
112
+ if (!record(declared) || declared.location !== name)
113
+ throw new Error(`A published Program's ${name} must be packaged at ./${name}`);
114
+ return { [name]: { ...declared, location: join(directory, name) } };
115
+ }
116
+ function pathField(value, directory, field, canonical) {
117
+ const declared = value[field];
118
+ if (declared === undefined)
119
+ return {};
120
+ if (declared !== canonical)
121
+ throw new Error(`A published Program's ${field} must be packaged as ${canonical}`);
122
+ return { [field]: join(directory, canonical) };
123
+ }
124
+ function extract(bytes, directory) {
125
+ const archive = new AdmZip(bytes);
126
+ for (const entry of archive.getEntries()) {
127
+ const name = entry.entryName.replaceAll("\\", "/");
128
+ const destination = resolve(directory, name);
129
+ const within = relative(directory, destination);
130
+ if (!name || name.startsWith("/") || name.split("/").includes("..") || isAbsolute(within) || within.startsWith("..")) {
131
+ throw new Error(`The Program package contains an unsafe path: ${entry.entryName}`);
132
+ }
133
+ if (entry.isDirectory) {
134
+ mkdirSync(destination, { recursive: true, mode: 0o700 });
135
+ continue;
136
+ }
137
+ mkdirSync(dirname(destination), { recursive: true, mode: 0o700 });
138
+ writeFileSync(destination, entry.getData(), { mode: 0o600 });
139
+ }
140
+ }
141
+ async function fetchAsset(url, fetcher) {
142
+ const response = await fetcher(url, {
143
+ headers: { "User-Agent": "@phreshos/cli" },
144
+ signal: AbortSignal.timeout(120_000)
145
+ });
146
+ if (!response.ok)
147
+ throw new Error(`A Program release asset could not be downloaded (${response.status} ${response.statusText})`);
148
+ return response;
149
+ }
150
+ function asset(assets, name) {
151
+ const found = assets.find(item => record(item) && item.name === name && typeof item.browser_download_url === "string");
152
+ return record(found) && typeof found.browser_download_url === "string" ? found.browser_download_url : undefined;
153
+ }
154
+ function parseVersion(tag) {
155
+ const match = /^v(\d+)\.(\d+)\.(\d+)$/.exec(tag);
156
+ return match ? tag.slice(1) : undefined;
157
+ }
158
+ function compare(left, right) {
159
+ const a = left.split(".").map(Number);
160
+ const b = right.split(".").map(Number);
161
+ return (a[0] ?? 0) - (b[0] ?? 0) || (a[1] ?? 0) - (b[1] ?? 0) || (a[2] ?? 0) - (b[2] ?? 0);
162
+ }
163
+ function record(value) {
164
+ return typeof value === "object" && value !== null && !Array.isArray(value);
165
+ }
@@ -3,6 +3,7 @@ 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
4
  import { dirname, isAbsolute, join, relative, resolve } from "node:path";
5
5
  import { requireSuccess } from "./process.js";
6
+ import { minimumSystemNodeVersion } from "./node.js";
6
7
  import AdmZip from "adm-zip";
7
8
  /** Owns immutable release directories and the one atomic active record. */
8
9
  export default class SystemInstallation {
@@ -171,7 +172,13 @@ async function validateDistribution(directory) {
171
172
  catch {
172
173
  throw new Error("The System release has no valid production package manifest");
173
174
  }
174
- if (!record(manifest) || manifest.type !== "module" || !record(manifest.scripts) || manifest.scripts.start !== "node server/main.js" || !record(manifest.dependencies)) {
175
+ if (!record(manifest)
176
+ || manifest.type !== "module"
177
+ || !record(manifest.scripts)
178
+ || manifest.scripts.start !== "node server/main.js"
179
+ || !record(manifest.engines)
180
+ || manifest.engines.node !== `>=${minimumSystemNodeVersion}`
181
+ || !record(manifest.dependencies)) {
175
182
  throw new Error("The System release package manifest is invalid");
176
183
  }
177
184
  for (const path of ["server/main.js", "client/index.html"]) {
@@ -4,6 +4,7 @@ import { intakeReady, waitForIntake } from "./readiness.js";
4
4
  import systemPaths from "./paths.js";
5
5
  import systemService from "./service/index.js";
6
6
  import nodeExecutable from "./node.js";
7
+ import installProgram from "../install.js";
7
8
  import { existsSync } from "node:fs";
8
9
  import { join } from "node:path";
9
10
  /** Coordinates acquisition, immutable files, and the native service as one transaction. */
@@ -18,7 +19,8 @@ export default class SystemLifecycle {
18
19
  resolveRelease: dependencies?.resolveRelease ?? resolveSystemRelease,
19
20
  downloadRelease: dependencies?.downloadRelease ?? downloadSystemRelease,
20
21
  ready: dependencies?.ready ?? intakeReady,
21
- wait: dependencies?.wait ?? waitForIntake
22
+ wait: dependencies?.wait ?? waitForIntake,
23
+ provisionSetup: dependencies?.provisionSetup ?? provisionSetup
22
24
  };
23
25
  }
24
26
  async install() {
@@ -40,13 +42,24 @@ export default class SystemLifecycle {
40
42
  await service.start();
41
43
  await this.waitUntilReady();
42
44
  await activation.commit();
43
- return await this.status();
44
45
  }
45
46
  catch (error) {
46
47
  await service.stop().catch(() => undefined);
47
48
  await activation.rollback();
48
49
  return await this.restoredFailure(error, previous, previousService);
49
50
  }
51
+ // The System transaction is complete before a Program crosses its
52
+ // live intake. A provisioning failure therefore leaves a healthy
53
+ // System available for a retry instead of rolling it back around a
54
+ // separate Program installation that may already have succeeded.
55
+ try {
56
+ await this.dependencies.provisionSetup();
57
+ }
58
+ catch (error) {
59
+ const reason = error instanceof Error ? error.message : String(error);
60
+ throw new Error(`The PhreshOS System is running, but Setup could not be provisioned: ${reason}`, { cause: error });
61
+ }
62
+ return await this.status();
50
63
  }
51
64
  async uninstall() {
52
65
  return await this.dependencies.installation.exclusive(() => this.uninstallExclusive());
@@ -168,6 +181,9 @@ export default class SystemLifecycle {
168
181
  throw error;
169
182
  }
170
183
  }
184
+ async function provisionSetup() {
185
+ await installProgram({ name: "setup", run: true, startup: true, announce: false });
186
+ }
171
187
  function definition(installation, executable) {
172
188
  return {
173
189
  executable,
@@ -1,9 +1,28 @@
1
1
  import { isAbsolute } from "node:path";
2
2
  import { requireSuccess } from "./process.js";
3
+ export const minimumSystemNodeVersion = "24.15.0";
3
4
  /** Resolve the real Node executable even when another runtime invoked the CLI. */
4
5
  export default async function nodeExecutable() {
5
- if (!process.versions.bun && process.release.name === "node" && isAbsolute(process.execPath))
6
- return process.execPath;
6
+ const executable = !process.versions.bun && process.release.name === "node" && isAbsolute(process.execPath)
7
+ ? process.execPath
8
+ : await discoveredNodeExecutable();
9
+ const result = await requireSuccess(executable, ["-p", "process.versions.node"]);
10
+ const version = result.stdout.trim();
11
+ if (!supportsSystemNode(version)) {
12
+ throw new Error(`Node.js ${minimumSystemNodeVersion} or newer is required to run the PhreshOS System${version ? ` (found ${version})` : ""}`);
13
+ }
14
+ return executable;
15
+ }
16
+ export function supportsSystemNode(version) {
17
+ const actual = parseVersion(version);
18
+ const minimum = parseVersion(minimumSystemNodeVersion);
19
+ if (!actual || !minimum)
20
+ return false;
21
+ return actual.major > minimum.major
22
+ || actual.major === minimum.major && actual.minor > minimum.minor
23
+ || actual.major === minimum.major && actual.minor === minimum.minor && actual.patch >= minimum.patch;
24
+ }
25
+ async function discoveredNodeExecutable() {
7
26
  const command = process.platform === "win32" ? "node.exe" : "node";
8
27
  const result = await requireSuccess(command, ["-p", "process.execPath"]);
9
28
  const executable = result.stdout.trim();
@@ -11,3 +30,13 @@ export default async function nodeExecutable() {
11
30
  throw new Error("A real Node.js executable is required to run the PhreshOS System service");
12
31
  return executable;
13
32
  }
33
+ function parseVersion(version) {
34
+ const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(version);
35
+ if (!match)
36
+ return undefined;
37
+ return {
38
+ major: Number(match[1]),
39
+ minor: Number(match[2]),
40
+ patch: Number(match[3])
41
+ };
42
+ }
@@ -14,6 +14,7 @@ dist-ssr
14
14
 
15
15
  # Made by `phresh pack` in the project root.
16
16
  /*.zip
17
+ /*.zip.sha256
17
18
 
18
19
  # Editor directories and files
19
20
  .vscode/*
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "phresh-program",
3
3
  "private": true,
4
- "version": "0.1.0",
4
+ "version": "0.1.2",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "phresh dev",
@@ -16,7 +16,7 @@
16
16
  "react-dom": "^19.2.8"
17
17
  },
18
18
  "devDependencies": {
19
- "@phreshos/cli": "^0.1.9",
19
+ "@phreshos/cli": "^0.1.11",
20
20
  "@types/node": "^26.2.0",
21
21
  "@types/react": "^19.2.18",
22
22
  "@types/react-dom": "^19.2.4",
@@ -22,7 +22,7 @@ export default defineConfig({
22
22
  // these values determines the Program's identity.
23
23
  name: "Phresh Program",
24
24
  description: "A simple counter whose state lives on the Server.",
25
- version: "0.1.0",
25
+ version: "0.1.2",
26
26
 
27
27
  // Markdown entry point for the API owned by this Program. It documents the
28
28
  // counter service contract; PhreshOS endpoint mechanics belong in PhreshOS
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "repository": "PhreshOS/phresh-program",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "development": true
5
5
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@phreshos/cli",
3
3
  "type": "module",
4
- "version": "0.1.9",
4
+ "version": "0.1.11",
5
5
  "description": "The Phresh command-line interface for Program projects and system management.",
6
6
  "engines": {
7
7
  "node": ">=20.10"