@phreshos/cli 0.1.51 → 0.1.53
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 +4 -0
- package/dist/commands/describe.js +2 -2
- package/dist/commands/endpoint.js +10 -10
- package/dist/commands/process.js +7 -7
- package/dist/commands/program.js +9 -6
- package/dist/commands/schemas.js +53 -9
- package/dist/commands/window.js +11 -11
- package/dist/contract/command.js +3 -11
- package/dist/contract/output.js +0 -0
- package/dist/contract/schema.js +3 -0
- package/dist/output/table.js +88 -0
- package/dist/output/write.js +39 -0
- package/dist/program-installation.js +2 -2
- package/dist/template/package.json +6 -6
- package/dist/template/phresh.config.ts +1 -1
- package/dist/template.json +2 -2
- package/package.json +6 -4
package/README.md
CHANGED
|
@@ -41,6 +41,10 @@ to read the complete machine-discoverable contract. See the
|
|
|
41
41
|
[CLI documentation](https://docs.phreshos.com/sdks/cli) for Program project,
|
|
42
42
|
System, and runtime commands.
|
|
43
43
|
|
|
44
|
+
Runtime commands render responsive tables or labeled fields for people by
|
|
45
|
+
default. Add `--json` to receive the complete result as one JSON value for
|
|
46
|
+
automation.
|
|
47
|
+
|
|
44
48
|
## Development
|
|
45
49
|
|
|
46
50
|
```sh
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defineCommand, readCommandContract } from "../contract/command.js";
|
|
2
2
|
import { value } from "../contract/schema.js";
|
|
3
3
|
import { jsonOption, option } from "./options.js";
|
|
4
|
-
import {
|
|
4
|
+
import { commandPresentation, dataOutput } from "./schemas.js";
|
|
5
5
|
/** Expose the authoritative CLI contract without contacting the System. */
|
|
6
6
|
export default function describeCommands(root) {
|
|
7
7
|
defineCommand(root, {
|
|
@@ -14,7 +14,7 @@ export default function describeCommands(root) {
|
|
|
14
14
|
],
|
|
15
15
|
guidance: ["Omit the path to start at the CLI root. Add --all to retrieve the complete selected contract tree."],
|
|
16
16
|
examples: ["phresh describe", "phresh describe process create", "phresh describe --all --json"],
|
|
17
|
-
output:
|
|
17
|
+
output: dataOutput(value.any("one command contract or a complete contract tree"), "CLI command contract", commandPresentation)
|
|
18
18
|
}, ({ arguments: [path], options }) => {
|
|
19
19
|
const command = resolveCommand(root, path);
|
|
20
20
|
return options.all ? describeTree(command, path) : describe(command, path);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defineCommand } from "../contract/command.js";
|
|
2
2
|
import { value } from "../contract/schema.js";
|
|
3
3
|
import { clientOverrideOptions, endpointOptions, option, serverOverrideOptions, timeoutOption, withJson } from "./options.js";
|
|
4
|
-
import { endpointOutput, eventOutput,
|
|
4
|
+
import { dataOutput, endpointActionPresentation, endpointOutput, endpointPresentation, eventOutput, eventPresentation, lifecyclePresentation, valuePresentation } from "./schemas.js";
|
|
5
5
|
import { connected, requireProcess } from "./connection.js";
|
|
6
6
|
import { bounded, clientLaunch, payload, serverLaunch } from "./input.js";
|
|
7
7
|
import { wait } from "./observation.js";
|
|
@@ -17,7 +17,7 @@ export default function endpointCommands(root, connect) {
|
|
|
17
17
|
description: "read whether one Endpoint is declared and running",
|
|
18
18
|
requiresSystem: true,
|
|
19
19
|
options: withJson(...endpointOptions),
|
|
20
|
-
output:
|
|
20
|
+
output: dataOutput(endpointOutput, "The selected Endpoint", endpointPresentation),
|
|
21
21
|
examples: ["phresh endpoint inspect --process main --program terminal --endpoint server"]
|
|
22
22
|
}, async ({ options }) => withEndpoint(connect, options, async (process, name) => {
|
|
23
23
|
return await endpointView(process, name);
|
|
@@ -27,7 +27,7 @@ export default function endpointCommands(root, connect) {
|
|
|
27
27
|
description: "start a fresh Endpoint incarnation",
|
|
28
28
|
requiresSystem: true,
|
|
29
29
|
options: withJson(...endpointOptions, ...clientOverrideOptions, ...serverOverrideOptions),
|
|
30
|
-
output:
|
|
30
|
+
output: dataOutput(endpointOutput, "The started Endpoint", endpointActionPresentation),
|
|
31
31
|
examples: ["phresh endpoint start --process main --program terminal --endpoint server --server-service"]
|
|
32
32
|
}, async ({ options }) => withEndpoint(connect, options, async (process, name) => {
|
|
33
33
|
const client = clientLaunch(options);
|
|
@@ -47,7 +47,7 @@ export default function endpointCommands(root, connect) {
|
|
|
47
47
|
description: "stop one Endpoint",
|
|
48
48
|
requiresSystem: true,
|
|
49
49
|
options: withJson(...endpointOptions),
|
|
50
|
-
output:
|
|
50
|
+
output: dataOutput(endpointOutput, "The stopped Endpoint", endpointActionPresentation),
|
|
51
51
|
examples: ["phresh endpoint stop --process main --program terminal --endpoint client"]
|
|
52
52
|
}, async ({ options }) => withEndpoint(connect, options, async (process, name) => {
|
|
53
53
|
await endpoint(process, name).stop();
|
|
@@ -59,7 +59,7 @@ export default function endpointCommands(root, connect) {
|
|
|
59
59
|
description: "wait until the Server Endpoint reports readiness",
|
|
60
60
|
requiresSystem: true,
|
|
61
61
|
options: withJson(...endpointOptions, timeoutOption),
|
|
62
|
-
output:
|
|
62
|
+
output: dataOutput(endpointOutput, "The ready Server Endpoint", endpointActionPresentation),
|
|
63
63
|
examples: ["phresh endpoint wait-ready --process main --program terminal --endpoint server --timeout 30000"]
|
|
64
64
|
}, async ({ options }) => withEndpoint(connect, options, async (process, name) => {
|
|
65
65
|
if (name !== "server")
|
|
@@ -73,10 +73,10 @@ export default function endpointCommands(root, connect) {
|
|
|
73
73
|
description: "wait for one lifecycle transition of an exact Endpoint",
|
|
74
74
|
requiresSystem: true,
|
|
75
75
|
options: withJson(...endpointOptions, option("--event <event>", "Endpoint lifecycle event", { mandatory: true, choices: ["start", "stop"] }), timeoutOption),
|
|
76
|
-
output:
|
|
76
|
+
output: dataOutput(value.object({
|
|
77
77
|
scope: value.string("Endpoint lifecycle subscription scope"),
|
|
78
78
|
event: value.enumeration(["start", "stop"], "observed lifecycle event")
|
|
79
|
-
}, ["scope", "event"], "Endpoint lifecycle event"), "One Endpoint lifecycle event"),
|
|
79
|
+
}, ["scope", "event"], "Endpoint lifecycle event"), "One Endpoint lifecycle event", lifecyclePresentation),
|
|
80
80
|
examples: ["phresh endpoint wait-lifecycle --process main --program terminal --endpoint server --event start"]
|
|
81
81
|
}, async ({ options }) => withEndpoint(connect, options, async (process, name) => {
|
|
82
82
|
await wait(endpoint(process, name).lifecycle, options.event, timeout(options.timeout));
|
|
@@ -87,7 +87,7 @@ export default function endpointCommands(root, connect) {
|
|
|
87
87
|
description: "ask a Server event and return its answer",
|
|
88
88
|
requiresSystem: true,
|
|
89
89
|
options: withJson(...endpointOptions, option("--event <event>", "event name", { mandatory: true }), option("--payload <json>", "arbitrary event payload as JSON"), timeoutOption),
|
|
90
|
-
output:
|
|
90
|
+
output: dataOutput(value.any("answer returned by the Server event contract"), "The Server answer", valuePresentation),
|
|
91
91
|
examples: ["phresh endpoint ask --process main --program terminal --endpoint server --event status --json"]
|
|
92
92
|
}, async ({ options }) => withEndpoint(connect, options, async (process, name) => {
|
|
93
93
|
if (name !== "server")
|
|
@@ -103,7 +103,7 @@ export default function endpointCommands(root, connect) {
|
|
|
103
103
|
description: "publish one event without waiting for an answer",
|
|
104
104
|
requiresSystem: true,
|
|
105
105
|
options: withJson(...endpointOptions, option("--event <event>", "event name", { mandatory: true }), option("--payload <json>", "arbitrary event payload as JSON")),
|
|
106
|
-
output:
|
|
106
|
+
output: dataOutput(endpointOutput, "The Endpoint after publishing", endpointActionPresentation),
|
|
107
107
|
examples: ["phresh endpoint publish --process main --program terminal --endpoint client --event changed --payload '{\"value\":1}'"]
|
|
108
108
|
}, async ({ options }) => withEndpoint(connect, options, async (process, name) => {
|
|
109
109
|
endpoint(process, name).publish(options.event, payload(options.payload));
|
|
@@ -114,7 +114,7 @@ export default function endpointCommands(root, connect) {
|
|
|
114
114
|
description: "wait for the next event emitted by one live Endpoint",
|
|
115
115
|
requiresSystem: true,
|
|
116
116
|
options: withJson(...endpointOptions, option("--event <event>", "event name", { mandatory: true }), timeoutOption),
|
|
117
|
-
output:
|
|
117
|
+
output: dataOutput(eventOutput("The observed Endpoint event"), "One Endpoint event", eventPresentation),
|
|
118
118
|
examples: ["phresh endpoint wait --process main --program terminal --endpoint client --event changed --json"]
|
|
119
119
|
}, async ({ options }) => withEndpoint(connect, options, async (process, name) => {
|
|
120
120
|
return {
|
package/dist/commands/process.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defineCommand } from "../contract/command.js";
|
|
2
2
|
import { value } from "../contract/schema.js";
|
|
3
3
|
import { launchOptions, namedLaunchOptions, option, processOptions, timeoutOption, withJson } from "./options.js";
|
|
4
|
-
import { eventOutput,
|
|
4
|
+
import { dataOutput, eventOutput, eventPresentation, pageOutput, processActionPresentation, processIdentityPresentation, processListPresentation, processOutput, processPresentation } from "./schemas.js";
|
|
5
5
|
import { connected, requireProcess, requireProgram } from "./connection.js";
|
|
6
6
|
import { bounded, integer, launch, page } from "./input.js";
|
|
7
7
|
import { wait } from "./observation.js";
|
|
@@ -17,7 +17,7 @@ export default function processCommands(root, connect) {
|
|
|
17
17
|
description: "list live Processes with bounded filtering",
|
|
18
18
|
requiresSystem: true,
|
|
19
19
|
options: withJson(option("--program <identity>", "restrict results to one Program"), option("--search <text>", "case-insensitive identity or name search"), option("--limit <count>", "maximum returned Processes", { parse: value => integer(value), default: 30 }), option("--offset <count>", "number of matching Processes to skip", { parse: value => integer(value), default: 0 })),
|
|
20
|
-
output:
|
|
20
|
+
output: dataOutput(pageOutput(processOutput, "matching Processes"), "A bounded page of Processes", processListPresentation),
|
|
21
21
|
examples: ["phresh process list", "phresh process list --program terminal --json"]
|
|
22
22
|
}, async ({ options }) => connected(connect, async (system) => {
|
|
23
23
|
const processes = options.program
|
|
@@ -31,7 +31,7 @@ export default function processCommands(root, connect) {
|
|
|
31
31
|
description: "read one live Process and its Endpoint state",
|
|
32
32
|
requiresSystem: true,
|
|
33
33
|
options: withJson(...processOptions),
|
|
34
|
-
output:
|
|
34
|
+
output: dataOutput(processOutput, "The selected Process", processPresentation),
|
|
35
35
|
examples: ["phresh process inspect --process main --program terminal"]
|
|
36
36
|
}, async ({ options }) => connected(connect, async (system) => {
|
|
37
37
|
return await processView(await requireProcess(system, options.process, options.program));
|
|
@@ -41,7 +41,7 @@ export default function processCommands(root, connect) {
|
|
|
41
41
|
description: "create a Process",
|
|
42
42
|
requiresSystem: true,
|
|
43
43
|
options: withJson(option("--program <identity>", "owning Program identity", { mandatory: true }), ...launchOptions),
|
|
44
|
-
output:
|
|
44
|
+
output: dataOutput(processOutput, "The created Process", processActionPresentation),
|
|
45
45
|
examples: ["phresh process create --program terminal --server --client", "phresh process create --program terminal --name main --json"]
|
|
46
46
|
}, async ({ options }) => connected(connect, async (system) => {
|
|
47
47
|
const program = await requireProgram(system, options.program);
|
|
@@ -53,7 +53,7 @@ export default function processCommands(root, connect) {
|
|
|
53
53
|
description: "find the named Process or create it atomically",
|
|
54
54
|
requiresSystem: true,
|
|
55
55
|
options: withJson(option("--program <identity>", "owning Program identity", { mandatory: true }), ...namedLaunchOptions),
|
|
56
|
-
output:
|
|
56
|
+
output: dataOutput(processOutput, "The existing or created Process", processActionPresentation),
|
|
57
57
|
examples: ["phresh process find-or-create --program terminal --name main --json"]
|
|
58
58
|
}, async ({ options }) => connected(connect, async (system) => {
|
|
59
59
|
const program = await requireProgram(system, options.program);
|
|
@@ -64,7 +64,7 @@ export default function processCommands(root, connect) {
|
|
|
64
64
|
description: "exit one Process and all of its live Endpoints",
|
|
65
65
|
requiresSystem: true,
|
|
66
66
|
options: withJson(...processOptions),
|
|
67
|
-
output:
|
|
67
|
+
output: dataOutput(processOutput, "The Process state immediately before exit", processIdentityPresentation),
|
|
68
68
|
examples: ["phresh process exit --process main --program terminal"]
|
|
69
69
|
}, async ({ options }) => connected(connect, async (system) => {
|
|
70
70
|
const process = await requireProcess(system, options.process, options.program);
|
|
@@ -77,7 +77,7 @@ export default function processCommands(root, connect) {
|
|
|
77
77
|
description: "wait for one Process lifecycle event",
|
|
78
78
|
requiresSystem: true,
|
|
79
79
|
options: withJson(option("--event <event>", "Process lifecycle event", { mandatory: true, choices: ["create", "exit"] }), option("--process <identity>", "scope the event to one Process"), option("--program <identity>", "scope the event to one Program"), timeoutOption),
|
|
80
|
-
output:
|
|
80
|
+
output: dataOutput(eventOutput("The observed Process event", value.any("Process state or exit result")), "One Process event", eventPresentation),
|
|
81
81
|
examples: ["phresh process wait --event create", "phresh process wait --event exit --process main --program terminal --json"]
|
|
82
82
|
}, async ({ options }) => connected(connect, async (system) => {
|
|
83
83
|
if (options.process && options.event === "create")
|
package/dist/commands/program.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defineCommand } from "../contract/command.js";
|
|
2
2
|
import { value } from "../contract/schema.js";
|
|
3
3
|
import { option, timeoutOption, withJson } from "./options.js";
|
|
4
|
-
import { eventOutput,
|
|
4
|
+
import { dataOutput, eventOutput, eventPresentation, pageOutput, programListPresentation, programOutput, programPresentation } from "./schemas.js";
|
|
5
5
|
import { connected, requireProgram } from "./connection.js";
|
|
6
6
|
import { bounded, integer, page } from "./input.js";
|
|
7
7
|
import { wait } from "./observation.js";
|
|
@@ -17,7 +17,7 @@ export default function programCommands(root, connect) {
|
|
|
17
17
|
description: "list Programs with bounded filtering",
|
|
18
18
|
requiresSystem: true,
|
|
19
19
|
options: withJson(option("--installed-only", "return only installed Programs"), option("--search <text>", "case-insensitive identity, name, or description search"), option("--limit <count>", "maximum returned Programs", { parse: value => integer(value), default: 30 }), option("--offset <count>", "number of matching Programs to skip", { parse: value => integer(value), default: 0 })),
|
|
20
|
-
output:
|
|
20
|
+
output: dataOutput(pageOutput(programOutput, "matching Programs"), "A bounded page of Programs", programListPresentation),
|
|
21
21
|
examples: ["phresh program list", "phresh program list --installed-only --json"]
|
|
22
22
|
}, async ({ options }) => connected(connect, async (system) => {
|
|
23
23
|
const programs = await system.program.list(options.installedOnly === true);
|
|
@@ -29,7 +29,7 @@ export default function programCommands(root, connect) {
|
|
|
29
29
|
description: "read one Program declaration and installed state",
|
|
30
30
|
requiresSystem: true,
|
|
31
31
|
options: withJson(option("--program <identity>", "Program identity", { mandatory: true })),
|
|
32
|
-
output:
|
|
32
|
+
output: dataOutput(programOutput, "The selected Program", programPresentation),
|
|
33
33
|
examples: ["phresh program inspect --program terminal"]
|
|
34
34
|
}, async ({ options }) => connected(connect, async (system) => {
|
|
35
35
|
return await programView(await requireProgram(system, options.program));
|
|
@@ -39,10 +39,13 @@ export default function programCommands(root, connect) {
|
|
|
39
39
|
description: "read a Program's own agent operating policy",
|
|
40
40
|
requiresSystem: true,
|
|
41
41
|
options: withJson(option("--program <identity>", "Program identity", { mandatory: true })),
|
|
42
|
-
output:
|
|
42
|
+
output: dataOutput(value.object({
|
|
43
43
|
program: value.string("Program identity"),
|
|
44
44
|
content: value.string("Program-owned agent documentation")
|
|
45
|
-
}, ["program", "content"], "Program agent documentation"), "The Program's agent documentation"
|
|
45
|
+
}, ["program", "content"], "Program agent documentation"), "The Program's agent documentation", {
|
|
46
|
+
format: "document",
|
|
47
|
+
content: "content"
|
|
48
|
+
}),
|
|
46
49
|
examples: ["phresh program agent --program terminal --json"]
|
|
47
50
|
}, async ({ options }) => connected(connect, async (system) => {
|
|
48
51
|
const program = await requireProgram(system, options.program);
|
|
@@ -59,7 +62,7 @@ export default function programCommands(root, connect) {
|
|
|
59
62
|
mandatory: true,
|
|
60
63
|
choices: ["create", "forget", "install", "uninstall"]
|
|
61
64
|
}), option("--program <identity>", "scope forget or uninstall to one Program"), timeoutOption),
|
|
62
|
-
output:
|
|
65
|
+
output: dataOutput(eventOutput("The observed Program event", value.any("Program state or uninstall result")), "One Program event", eventPresentation),
|
|
63
66
|
examples: ["phresh program wait --event create", "phresh program wait --event uninstall --program terminal --json"]
|
|
64
67
|
}, async ({ options }) => connected(connect, async (system) => {
|
|
65
68
|
if (options.program && options.event !== "forget" && options.event !== "uninstall") {
|
package/dist/commands/schemas.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { clientPermissionCatalog } 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")]
|
|
@@ -6,13 +7,10 @@ const endpointDeclaration = value.nullable(value.object({
|
|
|
6
7
|
start: value.boolean("whether a default Process starts this Endpoint"),
|
|
7
8
|
service: value.boolean("default Service role for new incarnations")
|
|
8
9
|
}, ["start", "service"], "resolved Server Endpoint declaration"));
|
|
9
|
-
const permissions = value.object(
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
appearance: value.array(value.string("permission value"), "appearance permission values"),
|
|
14
|
-
desktopPreferences: value.array(value.string("permission value"), "Desktop preferences permission values")
|
|
15
|
-
}, [], "immutable Client permissions");
|
|
10
|
+
const permissions = value.object(Object.fromEntries(Object.entries(clientPermissionCatalog).map(([name, domain]) => [name, domain === "none"
|
|
11
|
+
? { type: "array", maxItems: 0, description: `${name} presence-only grant` }
|
|
12
|
+
: value.array(value.string(permissionValueDescription(domain)), `${name} permission values`)
|
|
13
|
+
])), [], "immutable Client permissions");
|
|
16
14
|
const clientDeclaration = value.nullable(value.object({
|
|
17
15
|
start: value.boolean("whether a default Process starts this Endpoint"),
|
|
18
16
|
service: value.boolean("default Service role for new incarnations"),
|
|
@@ -60,6 +58,36 @@ export const windowOutput = value.object({
|
|
|
60
58
|
layer: value.enumeration(["window", "under", "over"], "Window layer"),
|
|
61
59
|
location: value.string("current Client location")
|
|
62
60
|
}, ["process", "title", "position", "size", "minimized", "front", "layer", "location"], "Window state");
|
|
61
|
+
export const programPresentation = fields(["Identity", "identity"], ["Asset", "assetId"], ["Name", "name"], ["Version", "version"], ["Description", "description"], ["Installed", "installed"], ["Agent", "hasAgent"], ["Server", "server"], ["Client", "client"]);
|
|
62
|
+
export const programListPresentation = table("data", "Program", "Programs", "No matching Programs", [
|
|
63
|
+
{ label: "Name", path: "name", width: 2 },
|
|
64
|
+
{ label: "Identity", path: "identity", width: 2 },
|
|
65
|
+
{ label: "Version", path: "version" },
|
|
66
|
+
{ label: "Installed", path: "installed" }
|
|
67
|
+
]);
|
|
68
|
+
export const processPresentation = fields(["Identity", "identity"], ["Name", "name"], ["Program", "program"], ["Started", "startedAt"], ["Server declared", "server.declared"], ["Server running", "server.running"], ["Server service", "server.service"], ["Client declared", "client.declared"], ["Client running", "client.running"], ["Client service", "client.service"]);
|
|
69
|
+
export const processActionPresentation = fields(["Process", "identity"], ["Name", "name"], ["Program", "program"], ["Server", "server.running"], ["Client", "client.running"]);
|
|
70
|
+
export const processIdentityPresentation = fields(["Process", "identity"], ["Program", "program"]);
|
|
71
|
+
export const processListPresentation = table("data", "Process", "Processes", "No matching Processes", [
|
|
72
|
+
{ label: "Name", path: "name", width: 2 },
|
|
73
|
+
{ label: "Identity", path: "identity", width: 2 },
|
|
74
|
+
{ label: "Program", path: "program", width: 2 },
|
|
75
|
+
{ label: "Server", path: "server.running" },
|
|
76
|
+
{ label: "Client", path: "client.running" }
|
|
77
|
+
]);
|
|
78
|
+
export const endpointPresentation = fields(["Process", "process"], ["Program", "program"], ["Endpoint", "endpoint"], ["Declared", "declared"], ["Running", "running"], ["Service", "service"]);
|
|
79
|
+
export const endpointActionPresentation = fields(["Process", "process"], ["Endpoint", "endpoint"], ["Running", "running"], ["Service", "service"]);
|
|
80
|
+
export const windowPresentation = fields(["Process", "process"], ["Title", "title"], ["Position", "position"], ["Size", "size"], ["Minimized", "minimized"], ["Front", "front"], ["Layer", "layer"], ["Location", "location"]);
|
|
81
|
+
export const windowPositionPresentation = fields(["Process", "process"], ["Position", "position"]);
|
|
82
|
+
export const windowSizePresentation = fields(["Process", "process"], ["Size", "size"]);
|
|
83
|
+
export const windowGeometryPresentation = fields(["Process", "process"], ["Position", "position"], ["Size", "size"]);
|
|
84
|
+
export const windowMinimizePresentation = fields(["Process", "process"], ["Minimized", "minimized"]);
|
|
85
|
+
export const windowTitlePresentation = fields(["Process", "process"], ["Title", "title"]);
|
|
86
|
+
export const windowRaisePresentation = fields(["Process", "process"], ["Front", "front"]);
|
|
87
|
+
export const eventPresentation = fields(["Scope", "scope"], ["Event", "event"], ["Payload", "payload"]);
|
|
88
|
+
export const lifecyclePresentation = fields(["Scope", "scope"], ["Event", "event"]);
|
|
89
|
+
export const commandPresentation = fields(["Path", "path"], ["Name", "name"], ["Aliases", "aliases"], ["Description", "description"], ["Arguments", "arguments"], ["Options", "options"], ["Guidance", "guidance"], ["Examples", "examples"], ["Requires System", "requiresSystem"], ["Output", "output"], ["Commands", "commands"]);
|
|
90
|
+
export const valuePresentation = { format: "value" };
|
|
63
91
|
export function pageOutput(items, description) {
|
|
64
92
|
return value.object({
|
|
65
93
|
data: value.array(items, description),
|
|
@@ -74,8 +102,8 @@ export function eventOutput(description, payload = value.any("event payload")) {
|
|
|
74
102
|
payload
|
|
75
103
|
}, ["scope", "event", "payload"], description);
|
|
76
104
|
}
|
|
77
|
-
export function
|
|
78
|
-
return { format: "
|
|
105
|
+
export function dataOutput(schema, description, presentation) {
|
|
106
|
+
return { format: "data", description, value: schema, presentation };
|
|
79
107
|
}
|
|
80
108
|
export function textOutput(description) {
|
|
81
109
|
return { format: "text", description };
|
|
@@ -87,3 +115,19 @@ function endpointState(description) {
|
|
|
87
115
|
service: value.boolean("whether this Endpoint incarnation is a Service")
|
|
88
116
|
}, ["declared", "running", "service"], description);
|
|
89
117
|
}
|
|
118
|
+
function fields(...values) {
|
|
119
|
+
return {
|
|
120
|
+
format: "fields",
|
|
121
|
+
fields: values.map(([label, path]) => ({ label, path }))
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function table(rows, item, items, empty, columns) {
|
|
125
|
+
return { format: "table", rows, columns, item, items, empty, total: "total", truncated: "truncated" };
|
|
126
|
+
}
|
|
127
|
+
function permissionValueDescription(domain) {
|
|
128
|
+
switch (domain) {
|
|
129
|
+
case "program": return "Program identity";
|
|
130
|
+
case "network": return "network destination scope";
|
|
131
|
+
case "storage": return "storage path scope";
|
|
132
|
+
}
|
|
133
|
+
}
|
package/dist/commands/window.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineCommand } from "../contract/command.js";
|
|
2
2
|
import { option, processOptions, timeoutOption, withJson } from "./options.js";
|
|
3
|
-
import { eventOutput,
|
|
3
|
+
import { dataOutput, eventOutput, eventPresentation, windowGeometryPresentation, windowMinimizePresentation, windowOutput, windowPositionPresentation, windowPresentation, windowRaisePresentation, windowSizePresentation, windowTitlePresentation } from "./schemas.js";
|
|
4
4
|
import { connected, requireProcess } from "./connection.js";
|
|
5
5
|
import { bounded, position, size } from "./input.js";
|
|
6
6
|
import { wait } from "./observation.js";
|
|
@@ -11,11 +11,11 @@ export default function windowCommands(root, connect) {
|
|
|
11
11
|
description: "inspect and control authoritative Client Windows",
|
|
12
12
|
guidance: ["A Window belongs to the Client Endpoint of one exact Process."]
|
|
13
13
|
});
|
|
14
|
-
defineCommand(windows, state("inspect", "read the complete current Window state"), async ({ options }) => {
|
|
14
|
+
defineCommand(windows, state("inspect", "read the complete current Window state", windowPresentation), async ({ options }) => {
|
|
15
15
|
return await withWindow(connect, options, windowView);
|
|
16
16
|
});
|
|
17
17
|
defineCommand(windows, {
|
|
18
|
-
...state("move", "change Window position"),
|
|
18
|
+
...state("move", "change Window position", windowPositionPresentation),
|
|
19
19
|
options: withJson(...processOptions, option("--x <value>", "horizontal pixels or workspace-relative expression", { mandatory: true }), option("--y <value>", "vertical pixels or workspace-relative expression", { mandatory: true })),
|
|
20
20
|
examples: ["phresh window move --process main --program terminal --x 50% --y 0"]
|
|
21
21
|
}, async ({ options }) => withWindow(connect, options, async (process) => {
|
|
@@ -23,7 +23,7 @@ export default function windowCommands(root, connect) {
|
|
|
23
23
|
return await windowView(process);
|
|
24
24
|
}));
|
|
25
25
|
defineCommand(windows, {
|
|
26
|
-
...state("resize", "change Window size"),
|
|
26
|
+
...state("resize", "change Window size", windowSizePresentation),
|
|
27
27
|
options: withJson(...processOptions, option("--width <value>", "width in pixels or a workspace-relative expression", { mandatory: true }), option("--height <value>", "height in pixels or a workspace-relative expression", { mandatory: true })),
|
|
28
28
|
examples: ["phresh window resize --process main --program terminal --width 800 --height 600"]
|
|
29
29
|
}, async ({ options }) => withWindow(connect, options, async (process) => {
|
|
@@ -31,7 +31,7 @@ export default function windowCommands(root, connect) {
|
|
|
31
31
|
return await windowView(process);
|
|
32
32
|
}));
|
|
33
33
|
defineCommand(windows, {
|
|
34
|
-
...state("setGeometry", "change Window position and size atomically"),
|
|
34
|
+
...state("setGeometry", "change Window position and size atomically", windowGeometryPresentation),
|
|
35
35
|
aliases: ["set-geometry"],
|
|
36
36
|
options: withJson(...processOptions, option("--x <value>", "horizontal pixels or workspace-relative expression", { mandatory: true }), option("--y <value>", "vertical pixels or workspace-relative expression", { mandatory: true }), option("--width <value>", "width in pixels or a workspace-relative expression", { mandatory: true }), option("--height <value>", "height in pixels or a workspace-relative expression", { mandatory: true })),
|
|
37
37
|
examples: ["phresh window set-geometry --process main --program terminal --x 0 --y 0 --width 100% --height 100%"]
|
|
@@ -43,7 +43,7 @@ export default function windowCommands(root, connect) {
|
|
|
43
43
|
return await windowView(process);
|
|
44
44
|
}));
|
|
45
45
|
defineCommand(windows, {
|
|
46
|
-
...state("minimize", "set Window visibility without changing its order"),
|
|
46
|
+
...state("minimize", "set Window visibility without changing its order", windowMinimizePresentation),
|
|
47
47
|
options: withJson(...processOptions, option("--restore", "restore rather than minimize the Window")),
|
|
48
48
|
examples: ["phresh window minimize --process main --program terminal", "phresh window minimize --process main --program terminal --restore"]
|
|
49
49
|
}, async ({ options }) => withWindow(connect, options, async (process) => {
|
|
@@ -51,7 +51,7 @@ export default function windowCommands(root, connect) {
|
|
|
51
51
|
return await windowView(process);
|
|
52
52
|
}));
|
|
53
53
|
defineCommand(windows, {
|
|
54
|
-
...state("changeTitle", "change the human-readable Window title"),
|
|
54
|
+
...state("changeTitle", "change the human-readable Window title", windowTitlePresentation),
|
|
55
55
|
aliases: ["change-title"],
|
|
56
56
|
options: withJson(...processOptions, option("--title <title>", "new Window title", { mandatory: true })),
|
|
57
57
|
examples: ["phresh window change-title --process main --program terminal --title Shell"]
|
|
@@ -60,7 +60,7 @@ export default function windowCommands(root, connect) {
|
|
|
60
60
|
return await windowView(process);
|
|
61
61
|
}));
|
|
62
62
|
defineCommand(windows, {
|
|
63
|
-
...state("raise", "raise the Window within its own layer"),
|
|
63
|
+
...state("raise", "raise the Window within its own layer", windowRaisePresentation),
|
|
64
64
|
examples: ["phresh window raise --process main --program terminal"]
|
|
65
65
|
}, async ({ options }) => withWindow(connect, options, async (process) => {
|
|
66
66
|
await windowOf(process).raise();
|
|
@@ -74,7 +74,7 @@ export default function windowCommands(root, connect) {
|
|
|
74
74
|
mandatory: true,
|
|
75
75
|
choices: ["move", "resize", "geometry", "minimize", "changeTitle", "front"]
|
|
76
76
|
}), timeoutOption),
|
|
77
|
-
output:
|
|
77
|
+
output: dataOutput(eventOutput("The observed Window event"), "One Window event", eventPresentation),
|
|
78
78
|
examples: ["phresh window wait --process main --program terminal --event geometry --json"]
|
|
79
79
|
}, async ({ options }) => withWindow(connect, options, async (process) => {
|
|
80
80
|
return {
|
|
@@ -86,13 +86,13 @@ export default function windowCommands(root, connect) {
|
|
|
86
86
|
};
|
|
87
87
|
}));
|
|
88
88
|
}
|
|
89
|
-
function state(name, description) {
|
|
89
|
+
function state(name, description, presentation) {
|
|
90
90
|
return {
|
|
91
91
|
name,
|
|
92
92
|
description,
|
|
93
93
|
requiresSystem: true,
|
|
94
94
|
options: withJson(...processOptions),
|
|
95
|
-
output:
|
|
95
|
+
output: dataOutput(windowOutput, "The current Window state", presentation)
|
|
96
96
|
};
|
|
97
97
|
}
|
|
98
98
|
async function withWindow(connect, options, action) {
|
package/dist/contract/command.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Argument, Command, Option } from "commander";
|
|
2
|
-
import {
|
|
2
|
+
import { writeData } from "../output/write.js";
|
|
3
3
|
const contracts = new WeakMap();
|
|
4
4
|
/** Register one command from the contract used by help, discovery, and execution. */
|
|
5
5
|
export function defineCommand(parent, contract, execute) {
|
|
@@ -24,8 +24,8 @@ export function defineCommand(parent, contract, execute) {
|
|
|
24
24
|
const owner = received.pop();
|
|
25
25
|
const options = received.pop();
|
|
26
26
|
const result = await execute({ arguments: received, options, command: owner });
|
|
27
|
-
if (contract.output?.format === "
|
|
28
|
-
|
|
27
|
+
if (contract.output?.format === "data")
|
|
28
|
+
writeData(result, contract.output, options.json === true);
|
|
29
29
|
});
|
|
30
30
|
return command;
|
|
31
31
|
}
|
|
@@ -82,11 +82,3 @@ function pathOf(command) {
|
|
|
82
82
|
}
|
|
83
83
|
return path;
|
|
84
84
|
}
|
|
85
|
-
function writeJson(result, contract, compact) {
|
|
86
|
-
const normalized = result ?? null;
|
|
87
|
-
if (contract.value)
|
|
88
|
-
assertValue(normalized, contract.value);
|
|
89
|
-
console.log(JSON.stringify(normalized, null, compact ? undefined : 2));
|
|
90
|
-
if (!compact)
|
|
91
|
-
console.log();
|
|
92
|
-
}
|
|
File without changes
|
package/dist/contract/schema.js
CHANGED
|
@@ -38,6 +38,9 @@ export function assertValue(value, contract, path = "result") {
|
|
|
38
38
|
if (contract.type === "array") {
|
|
39
39
|
if (!Array.isArray(value))
|
|
40
40
|
throw new Error(`${path} must be an array`);
|
|
41
|
+
if (contract.maxItems !== undefined && value.length > contract.maxItems) {
|
|
42
|
+
throw new Error(`${path} must contain no more than ${contract.maxItems} values`);
|
|
43
|
+
}
|
|
41
44
|
if (contract.items)
|
|
42
45
|
value.forEach((item, index) => assertValue(item, contract.items, `${path}[${index}]`));
|
|
43
46
|
return;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import Table from "cli-table3";
|
|
2
|
+
import stringWidth from "string-width";
|
|
3
|
+
const style = Object.freeze({ head: [], border: [] });
|
|
4
|
+
/** Render a table at its natural width, constraining and wrapping it only when necessary. */
|
|
5
|
+
export function renderTable(fields, rows) {
|
|
6
|
+
const width = terminalWidth();
|
|
7
|
+
if (width < minimumTableWidth(fields)) {
|
|
8
|
+
return rows.map(row => renderFields(fields, row)).join("\n");
|
|
9
|
+
}
|
|
10
|
+
const natural = createTable(fields, rows);
|
|
11
|
+
const rendered = natural.toString();
|
|
12
|
+
if (rendered.split("\n").every(line => stringWidth(line) <= width))
|
|
13
|
+
return rendered;
|
|
14
|
+
return createTable(fields, rows, allocateWidths(width, fields)).toString();
|
|
15
|
+
}
|
|
16
|
+
/** Render one value vertically so field names remain readable on narrow terminals. */
|
|
17
|
+
export function renderFields(fields, source) {
|
|
18
|
+
const rows = fields.map(field => [field.label, display(readPath(source, field.path), true)]);
|
|
19
|
+
const width = terminalWidth();
|
|
20
|
+
const desiredLabelWidth = Math.max(6, ...fields.map(field => stringWidth(field.label) + 2));
|
|
21
|
+
const labelWidth = Math.min(24, Math.max(6, width - 11), desiredLabelWidth);
|
|
22
|
+
const valueWidth = Math.max(8, width - labelWidth - 3);
|
|
23
|
+
const table = new Table({
|
|
24
|
+
colWidths: [labelWidth, valueWidth],
|
|
25
|
+
wordWrap: true,
|
|
26
|
+
wrapOnWordBoundary: false,
|
|
27
|
+
style
|
|
28
|
+
});
|
|
29
|
+
table.push(...rows);
|
|
30
|
+
return table.toString();
|
|
31
|
+
}
|
|
32
|
+
export function readPath(source, path) {
|
|
33
|
+
if (!path)
|
|
34
|
+
return source;
|
|
35
|
+
return path.split(".").reduce((current, key) => {
|
|
36
|
+
if (typeof current !== "object" || current === null)
|
|
37
|
+
return undefined;
|
|
38
|
+
return current[key];
|
|
39
|
+
}, source);
|
|
40
|
+
}
|
|
41
|
+
export function display(value, expanded = false) {
|
|
42
|
+
if (value === null || value === undefined || value === "")
|
|
43
|
+
return "—";
|
|
44
|
+
if (typeof value === "boolean")
|
|
45
|
+
return value ? "yes" : "no";
|
|
46
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "bigint")
|
|
47
|
+
return String(value);
|
|
48
|
+
if (Array.isArray(value) && !expanded)
|
|
49
|
+
return value.length ? value.map(item => display(item)).join(", ") : "—";
|
|
50
|
+
return JSON.stringify(value, null, expanded ? 2 : undefined);
|
|
51
|
+
}
|
|
52
|
+
function createTable(fields, rows, colWidths) {
|
|
53
|
+
const table = new Table({
|
|
54
|
+
head: fields.map(field => field.label),
|
|
55
|
+
...(colWidths ? { colWidths } : {}),
|
|
56
|
+
wordWrap: true,
|
|
57
|
+
wrapOnWordBoundary: false,
|
|
58
|
+
style
|
|
59
|
+
});
|
|
60
|
+
table.push(...rows.map(row => fields.map(field => display(readPath(row, field.path)))));
|
|
61
|
+
return table;
|
|
62
|
+
}
|
|
63
|
+
function allocateWidths(width, fields) {
|
|
64
|
+
const minimums = fields.map(field => minimumColumnWidth(field));
|
|
65
|
+
const available = width - fields.length - 1;
|
|
66
|
+
const remaining = available - minimums.reduce((total, current) => total + current, 0);
|
|
67
|
+
const weights = fields.map(field => field.width ?? 1);
|
|
68
|
+
const totalWeight = weights.reduce((total, current) => total + current, 0);
|
|
69
|
+
const widths = minimums.map((minimum, index) => minimum + Math.floor(remaining * weights[index] / totalWeight));
|
|
70
|
+
let remainder = available - widths.reduce((total, current) => total + current, 0);
|
|
71
|
+
for (let index = 0; remainder > 0; index = (index + 1) % widths.length) {
|
|
72
|
+
widths[index] = widths[index] + 1;
|
|
73
|
+
remainder--;
|
|
74
|
+
}
|
|
75
|
+
return widths;
|
|
76
|
+
}
|
|
77
|
+
function minimumTableWidth(fields) {
|
|
78
|
+
return fields.reduce((total, field) => total + minimumColumnWidth(field), fields.length + 1);
|
|
79
|
+
}
|
|
80
|
+
function minimumColumnWidth(field) {
|
|
81
|
+
return Math.max(6, stringWidth(field.label) + 2);
|
|
82
|
+
}
|
|
83
|
+
function terminalWidth() {
|
|
84
|
+
const configured = Number(process.env.COLUMNS);
|
|
85
|
+
if (Number.isFinite(configured) && configured > 0)
|
|
86
|
+
return Math.floor(configured);
|
|
87
|
+
return process.stdout.columns ?? 80;
|
|
88
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { inspect } from "node:util";
|
|
2
|
+
import { assertValue } from "../contract/schema.js";
|
|
3
|
+
import { readPath, renderFields, renderTable } from "./table.js";
|
|
4
|
+
/** Validate command data once, then select its machine or human representation. */
|
|
5
|
+
export function writeData(result, contract, machine) {
|
|
6
|
+
const normalized = result ?? null;
|
|
7
|
+
assertValue(normalized, contract.value);
|
|
8
|
+
if (machine) {
|
|
9
|
+
console.log(JSON.stringify(normalized));
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
console.log(render(normalized, contract.presentation));
|
|
13
|
+
console.log();
|
|
14
|
+
}
|
|
15
|
+
function render(value, presentation) {
|
|
16
|
+
switch (presentation.format) {
|
|
17
|
+
case "table": return renderCollection(value, presentation);
|
|
18
|
+
case "fields": return renderFields(presentation.fields, value);
|
|
19
|
+
case "document": return String(readPath(value, presentation.content) ?? "");
|
|
20
|
+
case "value": return inspect(value, { colors: false, depth: null, compact: false });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function renderCollection(value, presentation) {
|
|
24
|
+
const selected = readPath(value, presentation.rows);
|
|
25
|
+
const rows = Array.isArray(selected) ? selected : [];
|
|
26
|
+
if (rows.length === 0)
|
|
27
|
+
return presentation.empty;
|
|
28
|
+
const table = renderTable(presentation.columns, rows);
|
|
29
|
+
const totalValue = presentation.total ? readPath(value, presentation.total) : rows.length;
|
|
30
|
+
const total = typeof totalValue === "number" ? totalValue : rows.length;
|
|
31
|
+
const noun = total === 1 ? presentation.item : presentation.items;
|
|
32
|
+
const summary = rows.length === total
|
|
33
|
+
? `${total} ${noun}`
|
|
34
|
+
: `${rows.length} of ${total} ${noun}`;
|
|
35
|
+
const truncated = presentation.truncated && readPath(value, presentation.truncated) === true
|
|
36
|
+
? " · more available"
|
|
37
|
+
: "";
|
|
38
|
+
return `${table}\n${summary}${truncated}`;
|
|
39
|
+
}
|
|
@@ -12,10 +12,10 @@ export default async function installProgram(program, options = {}) {
|
|
|
12
12
|
try {
|
|
13
13
|
if (program instanceof Project) {
|
|
14
14
|
await program.build();
|
|
15
|
-
installed = await system.
|
|
15
|
+
installed = await system.program.forceCreate(program.productionDefinition());
|
|
16
16
|
}
|
|
17
17
|
else
|
|
18
|
-
installed = await system.
|
|
18
|
+
installed = await system.program.forceCreate(program);
|
|
19
19
|
for await (const chunk of installed.install())
|
|
20
20
|
writeProgramCommandOutput(chunk);
|
|
21
21
|
installationFinished = true;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "phresh",
|
|
3
3
|
"private": true,
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.31",
|
|
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.
|
|
18
|
-
"@phreshos/core": "^0.1.
|
|
19
|
-
"@phreshos/react": "^0.1.
|
|
20
|
-
"@phreshos/server": "^0.1.
|
|
17
|
+
"@phreshos/client": "^0.1.37",
|
|
18
|
+
"@phreshos/core": "^0.1.41",
|
|
19
|
+
"@phreshos/react": "^0.1.20",
|
|
20
|
+
"@phreshos/server": "^0.1.41",
|
|
21
21
|
"react": "^19.2.8",
|
|
22
22
|
"react-dom": "^19.2.8"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@phreshos/cli": "^0.1.
|
|
25
|
+
"@phreshos/cli": "^0.1.53",
|
|
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.
|
|
7
|
+
version: "0.1.31",
|
|
8
8
|
icon: "icon.png",
|
|
9
9
|
categories: ["Development"],
|
|
10
10
|
keywords: ["example", "counter", "client", "server"],
|
package/dist/template.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"repository": "PhreshOS/phresh-program",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"sha256": "
|
|
3
|
+
"version": "0.1.31",
|
|
4
|
+
"sha256": "2e6fd21f0f7e3c95934edda1790579c7d5dadb0b381fa525ebb02f747b395520",
|
|
5
5
|
"development": true
|
|
6
6
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phreshos/cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.53",
|
|
5
5
|
"description": "The Phresh command-line interface for Program projects and system management.",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=20.10"
|
|
@@ -49,11 +49,13 @@
|
|
|
49
49
|
"packageManager": "bun@1.3.14",
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@clack/prompts": "^1.7.0",
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
"@phreshos/core": "^0.1.42",
|
|
53
|
+
"@phreshos/node": "^0.1.17",
|
|
54
54
|
"adm-zip": "^0.6.0",
|
|
55
|
+
"cli-table3": "^0.6.5",
|
|
55
56
|
"commander": "^15.0.0",
|
|
56
|
-
|
|
57
|
+
"picocolors": "^1.1.1",
|
|
58
|
+
"string-width": "^4.2.3"
|
|
57
59
|
},
|
|
58
60
|
"devDependencies": {
|
|
59
61
|
"@types/adm-zip": "^0.5.8",
|